diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index d91ccb64d0a..00000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,503 +0,0 @@ ---- -version: 2.1 -orbs: - win: circleci/windows@5.0 - -executors: - besu_executor_med: # 2cpu, 4G ram - docker: - - image: cimg/openjdk:17.0 - resource_class: medium - working_directory: ~/project - environment: - architecture: "amd64" - GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.parallel=true -Dorg.gradle.workers.max=2 - - besu_arm64_executor_med: # 2cpu, 8G ram - machine: #https://circleci.com/developer/machine/image/ubuntu-2204 - image: ubuntu-2204:2022.10.2 - resource_class: arm.medium - working_directory: ~/project - environment: - architecture: "arm64" - GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.parallel=true -Dorg.gradle.workers.max=2 - - besu_executor_xl: # 8cpu, 16G ram - docker: - - image: cimg/openjdk:17.0 - resource_class: xlarge - working_directory: ~/project - environment: - GRADLE_OPTS: -Dorg.gradle.daemon=false -Dorg.gradle.parallel=true -Dorg.gradle.workers.max=4 - - xl_machine_executor: - machine: #https://circleci.com/developer/machine/image/ubuntu-2204 - image: ubuntu-2204:2022.10.2 - resource_class: xlarge - - trivy_executor: - docker: - - image: docker:stable-git - resource_class: small - working_directory: ~/project - -commands: - prepare: - description: "Prepare" - steps: - - checkout - - run: - name: Install Packages - LibSodium, nssdb - command: | - sudo apt-get update - sudo apt-get install -y libsodium23 libsodium-dev libjemalloc-dev apt-transport-https haveged libnss3-tools - sudo service haveged restart - java --version - - restore_gradle_cache - restore_gradle_cache: - description: "Restore Gradle cache" - steps: - - restore_cache: - name: Restore cached gradle dependencies - keys: - - deps-{{ checksum "gradle/versions.gradle" }}-{{ .Branch }}-{{ .Revision }} - - deps-{{ checksum "gradle/versions.gradle" }} - - deps- - - capture_test_results: - description: "Capture test results" - steps: - - run: - name: Jacoco - command: | - ./gradlew --no-daemon jacocoTestReport - - run: - name: Gather test results - when: always - command: | - FILES=`find . -name test-results` - for FILE in $FILES - do - MODULE=`echo "$FILE" | sed -e 's@./\(.*\)/build/test-results@\1@'` - TARGET="build/test-results/$MODULE" - mkdir -p "$TARGET" - cp -rf ${FILE}/*/* "$TARGET" - done - - store_test_results: - path: build/test-results - - store_artifacts: - path: besu/build/reports/jacoco - - capture_test_logs: - description: "Capture test logs" - steps: - - store_artifacts: - path: acceptance-tests/tests/build/acceptanceTestLogs - destination: acceptance-tests-logs - - store_artifacts: - path: acceptance-tests/tests/build/jvmErrorLogs - -jobs: - assemble: - executor: besu_executor_xl - steps: - - prepare - - run: - name: Assemble - command: | - ./gradlew --no-daemon clean compileJava compileTestJava assemble - - save_cache: - name: Caching gradle dependencies - key: deps-{{ checksum "gradle/versions.gradle" }}-{{ .Branch }}-{{ .Revision }} - paths: - - .gradle - - ~/.gradle - - persist_to_workspace: - root: ~/project - paths: - - ./ - - store_artifacts: - name: Distribution artifacts - path: build/distributions - destination: distributions - when: always - - testWindows: - executor: win/default - steps: - - attach_workspace: - at: ~/project - - run: - name: Unzip Windows build - no_output_timeout: 20m - command: | - cd build/distributions - unzip besu-*.zip -d besu-tmp - cd besu-tmp - mv besu-* ../besu - - run: - name: Test Besu Windows executable - no_output_timeout: 10m - command: | - build\distributions\besu\bin\besu.bat --help - build\distributions\besu\bin\besu.bat --version - - dockerScan: - executor: trivy_executor - steps: - - checkout - - restore_gradle_cache - - setup_remote_docker: - docker_layer_caching: true - - run: - name: Install trivy - command: | - apk add --update-cache --upgrade curl bash - curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin - - run: - name: Scan with trivy - shell: /bin/sh - command: | - for FILE in $(ls docker) - do - if [[ $FILE == "test.sh" || $FILE == "tests" ]]; then - continue - fi - docker pull -q "hyperledger/besu:develop-$FILE" - trivy -q image --exit-code 1 --no-progress --severity HIGH,CRITICAL "hyperledger/besu:develop-$FILE" - done - - unitTests: - executor: besu_executor_xl - steps: - - prepare - - attach_workspace: - at: ~/project - - run: - name: Build - no_output_timeout: 20m - command: | - ./gradlew --no-daemon build - - capture_test_results - - integrationTests: - executor: xl_machine_executor # needs the machine executor since privacy test uses container tests (docker) - steps: - - prepare - - attach_workspace: - at: ~/project - - run: - name: IntegrationTests - command: | - ./gradlew --no-daemon integrationTest - - run: - name: CompileJmh - command: | - ./gradlew --no-daemon compileJmh - - capture_test_results - - referenceTests: - executor: besu_executor_xl - steps: - - prepare - - attach_workspace: - at: ~/project - - run: - name: ReferenceTests - no_output_timeout: 30m - command: | - git submodule update --init --recursive - ./gradlew --no-daemon referenceTest - - capture_test_results - - acceptanceTests: - parallelism: 2 - executor: xl_machine_executor # needs the machine executor since privacy test uses container tests (docker) - steps: - - prepare - - attach_workspace: - at: ~/project - - run: - name: AcceptanceTests (Mainnet) - no_output_timeout: 20m - command: | - CLASSNAMES=$(circleci tests glob "acceptance-tests/tests/src/test/java/**/*.java" \ - | sed 's@.*/src/test/java/@@' \ - | sed 's@/@.@g' \ - | sed 's/.\{5\}$//' \ - | circleci tests split --split-by=timings --timings-type=classname) - # Format the arguments to "./gradlew test" - GRADLE_ARGS=$(echo $CLASSNAMES | awk '{for (i=1; i<=NF; i++) print "--tests",$i}') - ./gradlew --no-daemon acceptanceTestMainnet $GRADLE_ARGS - - capture_test_results - - capture_test_logs - - acceptanceTestsCliqueBft: - executor: besu_executor_xl - steps: - - prepare - - attach_workspace: - at: ~/project - - run: - name: AcceptanceTests (Non-Mainnet) - no_output_timeout: 20m - command: | - ./gradlew --no-daemon acceptanceTestCliqueBft - - capture_test_results - - capture_test_logs - - acceptanceTestsPrivacy: - parallelism: 4 - executor: xl_machine_executor # needs the machine executor since it uses container tests (docker) - steps: - - prepare - - attach_workspace: - at: ~/project - - run: - name: AcceptanceTests (Non-Mainnet) - no_output_timeout: 20m - command: | - CLASSNAMES=$(circleci tests glob "acceptance-tests/tests/src/test/java/**/*.java" \ - | sed 's@.*/src/test/java/@@' \ - | sed 's@/@.@g' \ - | sed 's/.\{5\}$//' \ - | circleci tests split --split-by=timings --timings-type=classname) - # Format the arguments to "./gradlew test" - GRADLE_ARGS=$(echo $CLASSNAMES | awk '{for (i=1; i<=NF; i++) print "--tests",$i}') - ./gradlew --no-daemon acceptanceTestPrivacy $GRADLE_ARGS - - capture_test_results - - capture_test_logs - - acceptanceTestsPermissioning: - executor: besu_executor_xl - steps: - - prepare - - attach_workspace: - at: ~/project - - run: - name: AcceptanceTests (Non-Mainnet) - no_output_timeout: 20m - command: | - ./gradlew --no-daemon acceptanceTestPermissioning - - capture_test_results - - capture_test_logs - - buildDocker: - executor: besu_executor_med - steps: - - prepare - - attach_workspace: - at: ~/project - - setup_remote_docker - - run: - name: hadoLint_openjdk_17 - command: | - docker run --rm -i hadolint/hadolint < docker/openjdk-17/Dockerfile - - run: - name: hadoLint_openjdk_17_debug - command: | - docker run --rm -i hadolint/hadolint < docker/openjdk-17-debug/Dockerfile - - run: - name: hadoLint_openjdk_latest - command: | - docker run --rm -i hadolint/hadolint < docker/openjdk-latest/Dockerfile - - run: - name: hadoLint_graalvm - command: | - docker run --rm -i hadolint/hadolint < docker/graalvm/Dockerfile - - run: - name: build image - command: | - ./gradlew --no-daemon distDocker - - run: - name: test image - command: | - mkdir -p docker/reports - curl -L https://github.com/aelsabbahy/goss/releases/download/v0.3.9/goss-linux-amd64 -o ./docker/tests/goss-linux-amd64 - ./gradlew --no-daemon testDocker - - buildArm64Docker: - executor: besu_arm64_executor_med - steps: - - prepare - - attach_workspace: - at: ~/project - - run: - name: hadoLint_openjdk_17 - command: | - docker run --rm -i hadolint/hadolint < docker/openjdk-17/Dockerfile - - run: - name: hadoLint_openjdk_latest - command: | - docker run --rm -i hadolint/hadolint < docker/openjdk-latest/Dockerfile - - run: - name: hadoLint_graalvm - command: | - docker run --rm -i hadolint/hadolint < docker/graalvm/Dockerfile - - run: - name: Java_17 - command: | - sudo apt install -q --assume-yes openjdk-17-jre-headless openjdk-17-jdk-headless - sudo update-java-alternatives -a - - run: - name: build image - command: | - ./gradlew --no-daemon distDocker - - run: - name: test image - command: | - mkdir -p docker/reports - curl -L https://github.com/aelsabbahy/goss/releases/download/v0.3.9/goss-linux-arm -o ./docker/tests/goss-linux-arm64 - ./gradlew --no-daemon testDocker - - publish: - executor: besu_executor_med - steps: - - prepare - - attach_workspace: - at: ~/project - - run: - name: Publish - command: | - ./gradlew --no-daemon artifactoryPublish - - publishDocker: - executor: besu_executor_med - steps: - - prepare - - attach_workspace: - at: ~/project - - setup_remote_docker - - run: - name: Publish Docker - command: | - docker login --username "${DOCKER_USER_RW}" --password "${DOCKER_PASSWORD_RW}" - ./gradlew --no-daemon "-Pbranch=${CIRCLE_BRANCH}" dockerUpload - - publishArm64Docker: - executor: besu_arm64_executor_med - steps: - - prepare - - attach_workspace: - at: ~/project - - run: - name: Java_17 - command: | - sudo apt install -q --assume-yes openjdk-17-jre-headless openjdk-17-jdk-headless - sudo update-java-alternatives -a - - run: - name: Publish Docker - command: | - docker login --username "${DOCKER_USER_RW}" --password "${DOCKER_PASSWORD_RW}" - ./gradlew --no-daemon "-Pbranch=${CIRCLE_BRANCH}" dockerUpload - manifestDocker: - executor: besu_executor_med - steps: - - prepare - - setup_remote_docker - - run: - name: Create and publish docker manifest - command: | - docker login --username "${DOCKER_USER_RW}" --password "${DOCKER_PASSWORD_RW}" - ./gradlew --no-daemon "-Pbranch=${CIRCLE_BRANCH}" --parallel manifestDocker - -workflows: - version: 2 - default: - jobs: - - assemble - - unitTests: - requires: - - assemble - - testWindows: - requires: - - assemble - - referenceTests: - requires: - - assemble - - integrationTests: - requires: - - assemble - - acceptanceTests: - requires: - - assemble - - acceptanceTestsCliqueBft: - requires: - - assemble - - acceptanceTestsPermissioning: - requires: - - assemble - - buildDocker: - requires: - - assemble - - buildArm64Docker: - requires: - - assemble - - publish: - filters: - branches: - only: - - main - - /^release-.*/ - requires: - - assemble - - integrationTests - - unitTests - - acceptanceTests - - referenceTests - - buildDocker - - publishDocker: - filters: - branches: - only: - - main - - /^release-.*/ - requires: - - assemble - - integrationTests - - unitTests - - acceptanceTests - - referenceTests - - buildDocker - context: - - besu-dockerhub-rw - - publishArm64Docker: - filters: - branches: - only: - - main - - /^release-.*/ - requires: - - integrationTests - - unitTests - - acceptanceTests - - referenceTests - - buildArm64Docker - context: - - besu-dockerhub-rw - - manifestDocker: - filters: - branches: - only: - - main - - /^release-.*/ - requires: - - publishDocker - - publishArm64Docker - context: - - besu-dockerhub-rw - - nightly: - triggers: - - schedule: - cron: "0 19 * * *" - filters: - branches: - only: - - main - jobs: - - assemble - - dockerScan - - acceptanceTestsPrivacy: - requires: - - assemble diff --git a/.github/ISSUE_TEMPLATE/release-checklist.md b/.github/ISSUE_TEMPLATE/release-checklist.md new file mode 100644 index 00000000000..413d68933fd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/release-checklist.md @@ -0,0 +1,38 @@ +--- +name: Release Checklist +about: items to be completed for each release +title: '' +labels: '' +assignees: '' + +--- + +- [ ] Confirm anything outstanding for release with other maintainers on #besu-release in Discord +- [ ] Update changelog if necessary, and merge a PR for it to main + - [ ] Notify maintainers about updating changelog for in-flight PRs +- [ ] Optional: for hotfixes, create a release branch and cherry-pick, e.g. `release--hotfix` + - [ ] Optional: for hotfixes, create a PR into main from the hotfix branch to see the CI checks pass +- [ ] On the appropriate branch/commit, create a calver tag for the release candidate, format example: `24.4.0-RC2` + - [ ] git tag 24.4.0-RC2 + - [ ] git push upstream 24.4.0-RC2 +- [ ] Sign-off with team; announce the tag in #besu-release in Discord + - [ ] Targeting this tag for the burn-in: https://github.com/hyperledger/besu/releases/tag/24.4.0-RC2 +- [ ] Consensys staff start burn-in using this tag +- [ ] Seek sign off for burn-in + - [ ] Pass? Go ahead and complete the release process + - [ ] Fail? Put a message in #besu-release in Discord indicating the release will be aborted because it failed burn-in +- [ ] Using the same git sha, create a calver tag for the FULL RELEASE, example format `24.4.0` +- [ ] Using the FULL RELEASE tag, create a release in github to trigger the workflows. Once published: + - this is now public and notifies subscribed users + - makes the release "latest" in github + - publishes artefacts and version-specific docker tags + - publishes the docker `latest` tag variants +- [ ] Check binary SHAs are correct on the release page +- [ ] Check "Container Verify" GitHub workflow has run successfully +- [ ] Update the besu-docs version [update-version workflow](https://github.com/hyperledger/besu-docs/actions/workflows/update-version.yml) + - If the PR has not been automatically created, create the PR manually using the created branch `besu-version-` +- [ ] Create homebrew release using [update-version workflow](https://github.com/hyperledger/homebrew-besu/actions/workflows/update-version.yml) + - If the PR has not been automatically created, create the PR manually using the created branch `update-` + - Run commands `brew tap hyperledger/besu && brew install besu` on MacOSX and verify latest version has been installed +- [ ] Delete the burn-in nodes (unless required for further analysis eg performance) +- [ ] Social announcements diff --git a/.github/issue_template.md b/.github/issue_template.md index cfae3400266..54acf4c1968 100644 --- a/.github/issue_template.md +++ b/.github/issue_template.md @@ -3,7 +3,7 @@ - + diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 11bac0fb923..7e8cde29e7b 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,8 +1,21 @@ - - - ## PR description ## Fixed Issue(s) - \ No newline at end of file + + + +### Thanks for sending a pull request! Have you done the following? + +- [ ] Checked out our [contribution guidelines](https://github.com/hyperledger/besu/blob/main/CONTRIBUTING.md)? +- [ ] Considered documentation and added the `doc-change-required` label to this PR [if updates are required](https://wiki.hyperledger.org/display/BESU/Documentation). +- [ ] Considered the changelog and included an [update if required](https://wiki.hyperledger.org/display/BESU/Changelog). +- [ ] For database changes (e.g. KeyValueSegmentIdentifier) considered compatibility and performed forwards and backwards compatibility tests + +### Locally, you can run these tests to catch failures early: + +- [ ] unit tests: `./gradlew build` +- [ ] acceptance tests: `./gradlew acceptanceTest` +- [ ] integration tests: `./gradlew integrationTest` +- [ ] reference tests: `./gradlew ethereum:referenceTests:referenceTests` + diff --git a/.github/workflows/BesuContainerVerify.sh b/.github/workflows/BesuContainerVerify.sh new file mode 100644 index 00000000000..81537f32648 --- /dev/null +++ b/.github/workflows/BesuContainerVerify.sh @@ -0,0 +1,70 @@ +#!/bin/bash +## +## Copyright contributors to Hyperledger Besu. +## +## Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +## an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +## specific language governing permissions and limitations under the License. +## +## SPDX-License-Identifier: Apache-2.0 +## + +CONTAINER_NAME=${CONTAINER_NAME:-besu} +VERSION=${VERSION} +TAG=${TAG} +CHECK_LATEST=${CHECK_LATEST} +RETRY=${RETRY:-10} +SLEEP=${SLEEP:-5} + +# Helper function to throw error +log_error() { + echo "::error $1" + exit 1 +} + +# Check container is in running state +_RUN_STATE=$(docker inspect --type=container -f={{.State.Status}} ${CONTAINER_NAME}) +if [[ "${_RUN_STATE}" != "running" ]] +then + log_error "container is not running" +fi + +# Check for specific log message in container logs to verify besu started +_SUCCESS=false +while [[ ${_SUCCESS} != "true" && $RETRY -gt 0 ]] +do + docker logs ${CONTAINER_NAME} | grep -q "Ethereum main loop is up" && { + _SUCCESS=true + continue + } + echo "Waiting for the besu to start. Remaining retries $RETRY ..." + RETRY=$(expr $RETRY - 1) + sleep $SLEEP +done + +# Log entry does not present after all retries, fail the script with a message +if [[ ${_SUCCESS} != "true" ]] +then + docker logs --tail=100 ${CONTAINER_NAME} + log_error "could not find the log message 'Ethereum main loop is up'" +else + echo "Besu container started and entered main loop" +fi + +# For the latest tag check the version match +if [[ ${TAG} == "latest" && ${CHECK_LATEST} == "true" ]] +then + _VERSION_IN_LOG=$(docker logs ${CONTAINER_NAME} | grep "#" | grep "Besu version" | cut -d " " -f 4 | sed 's/\s//g') + echo "Extracted version from logs [$_VERSION_IN_LOG]" + if [[ "$_VERSION_IN_LOG" != "${VERSION}" ]] + then + log_error "version [$_VERSION_IN_LOG] extracted from container logs does not match the expected version [${VERSION}]" + else + echo "Latest Besu container version matches" + fi +fi diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml new file mode 100644 index 00000000000..287f402bced --- /dev/null +++ b/.github/workflows/acceptance-tests.yml @@ -0,0 +1,112 @@ +name: acceptance-tests +on: + workflow_dispatch: + pull_request: + branches: + - main + - release-* + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + GRADLE_OPTS: "-Xmx6g" + total-runners: 12 + +jobs: + acceptanceTestEthereum: + runs-on: ubuntu-22.04 + name: "Acceptance Runner" + permissions: + statuses: write + checks: write + strategy: + fail-fast: true + matrix: + runner_index: [0,1,2,3,4,5,6,7,8,9,10,11] + steps: + - name: Checkout Repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + with: + ref: ${{ github.event.pull_request.head.sha || github.ref }} + - name: Set up Java + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 + with: + distribution: temurin + java-version: 21 + - name: Install required packages + run: sudo apt-get install -y xmlstarlet + - name: setup gradle + uses: gradle/actions/setup-gradle@9e899d11ad247ec76be7a60bc1cf9d3abbb9e7f1 + with: + cache-disabled: true + - name: List unit tests + run: ./gradlew acceptanceTestNotPrivacy --test-dry-run -Dorg.gradle.parallel=true -Dorg.gradle.caching=true + - name: Extract current test list + run: mkdir tmp; find . -type f -name TEST-*.xml | xargs -I{} bash -c "xmlstarlet sel -t -v '/testsuite/@name' '{}'; echo ' acceptanceTestNotPrivacy'" | tee tmp/currentTests.list + - name: Get acceptance test reports + uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d + continue-on-error: true + with: + branch: main + workflow: update-test-reports.yml + name: acceptance-test-results + path: tmp/junit-xml-reports-downloaded + if_no_artifact_found: ignore + - name: Split tests + run: .github/workflows/splitTestsByTime.sh tmp/junit-xml-reports-downloaded "tmp/junit-xml-reports-downloaded/acceptance-node-.*-test-results" "TEST-" ${{env.total-runners}} ${{ matrix.runner_index }} > testList.txt + - name: format gradle args + # we do not need the module task here + run: cat testList.txt | cut -f 2- -d ' ' | tee gradleArgs.txt + - name: Upload Timing + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 + if: matrix.runner_index == 0 + with: + name: acceptance-tests-timing + path: 'tmp/timing.tsv' + - name: Upload Lists + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 + if: matrix.runner_index == 0 + with: + name: acceptance-tests-lists + path: 'tmp/*.list' + - name: Upload gradle test tasks + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 + with: + name: test-args-${{ matrix.runner_index }}.txt + path: '*.txt' + - name: run acceptance tests + run: ./gradlew acceptanceTestNotPrivacy `cat gradleArgs.txt` -Dorg.gradle.parallel=true -Dorg.gradle.caching=true + - name: Remove downloaded test results + run: rm -rf tmp/junit-xml-reports-downloaded + - name: Upload Acceptance Test Results + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 + with: + name: acceptance-node-${{matrix.runner_index}}-test-results + path: 'acceptance-tests/tests/build/test-results/**/TEST-*.xml' + - name: Upload Acceptance Test HTML Reports + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 + if: success() || failure() + with: + name: acceptance-node-${{matrix.runner_index}}-test-html-reports + path: 'acceptance-tests/tests/build/reports/tests/**' + accepttests-passed: + name: "accepttests-passed" + runs-on: ubuntu-22.04 + needs: [ acceptanceTestEthereum ] + permissions: + checks: write + statuses: write + if: always() + steps: + # Fail if any `needs` job was not a success. + # Along with `if: always()`, this allows this job to act as a single required status check for the entire workflow. + - name: Fail on workflow error + run: exit 1 + if: >- + ${{ + contains(needs.*.result, 'failure') + || contains(needs.*.result, 'cancelled') + || contains(needs.*.result, 'skipped') + }} \ No newline at end of file diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml deleted file mode 100644 index 900cc5c9775..00000000000 --- a/.github/workflows/checks.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: checks -on: - push: - branches: [ main ] - pull_request: - workflow_dispatch: - -jobs: - spotless: - runs-on: [besu-research-ubuntu-16] - if: ${{ github.actor != 'dependabot[bot]' }} - steps: - - name: Checkout Repo - uses: actions/checkout@v4 - - name: Set up Java - uses: actions/setup-java@v3 - with: - distribution: adopt - java-version: 17 - cache: gradle - - name: spotless - run: ./gradlew --no-daemon --parallel clean spotlessCheck - javadoc_17: - runs-on: [besu-research-ubuntu-8] - if: ${{ github.actor != 'dependabot[bot]' }} - steps: - - name: Checkout Repo - uses: actions/checkout@v4 - - name: Set up Java 17 - uses: actions/setup-java@v3 - with: - distribution: adopt - java-version: 17 - cache: gradle - - name: javadoc (JDK 17) - run: ./gradlew --no-daemon clean javadoc diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f4ecae04ba4..e7a13e92d40 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -12,44 +12,31 @@ name: "CodeQL" on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - paths-ignore: - - '**/*.json' - - '**/*.md' - - '**/*.properties' - - '**/*.txt' + workflow_dispatch: + schedule: + # * is a special character in YAML so you have to quote this string + # expression evaluates to midnight every night + - cron: '0 0 * * *' + jobs: analyze: name: Analyze - runs-on: [besu-research-ubuntu-16] + runs-on: ubuntu-22.04 permissions: actions: read contents: read security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'java' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] - # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support - steps: - name: Checkout repository - uses: actions/checkout@v4 - + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - name: Set up Java - uses: actions/setup-java@v3 + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 with: - distribution: adopt - java-version: 17 - + distribution: 'temurin' + java-version: 21 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@2f93e4319b2f04a2efc38fa7f78bd681bc3f7b2f with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -57,10 +44,12 @@ jobs: # Prefix the list here with "+" to use these queries and those in the config file. queries: security-and-quality,security-extended - # Autobuild failed (OOM) - # Hence, supply memory args for gradle build - - run: | - JAVA_OPTS="-Xmx1000M" ./gradlew --no-scan compileJava - + - name: setup gradle + uses: gradle/actions/setup-gradle@9e899d11ad247ec76be7a60bc1cf9d3abbb9e7f1 + with: + cache-disabled: true + - name: compileJava noscan + run: | + JAVA_OPTS="-Xmx2048M" ./gradlew --no-scan compileJava - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@2f93e4319b2f04a2efc38fa7f78bd681bc3f7b2f diff --git a/.github/workflows/container-security-scan.yml b/.github/workflows/container-security-scan.yml new file mode 100644 index 00000000000..9d41e819b5f --- /dev/null +++ b/.github/workflows/container-security-scan.yml @@ -0,0 +1,47 @@ +name: container security scan + +on: + workflow_dispatch: + inputs: + tag: + description: 'Container image tag' + required: false + default: 'develop' + schedule: + # Start of the hour is the busy time. Schedule it to run 8:17am UTC + - cron: '17 8 * * *' + +jobs: + scan-sarif: + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + + steps: + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + + # Shell parameter expansion does not support directly on a step + # Adding a separate step to set the image tag. This allows running + # this workflow with a schedule as well as manual + - name: Set image tag + id: tag + run: | + echo "TAG=${INPUT_TAG:-develop}" >> "$GITHUB_OUTPUT" + env: + INPUT_TAG: ${{ inputs.tag }} + + - name: Vulnerability scanner + id: trivy + uses: aquasecurity/trivy-action@595be6a0f6560a0a8fc419ddf630567fc623531d + with: + image-ref: hyperledger/besu:${{ steps.tag.outputs.TAG }} + format: sarif + output: 'trivy-results.sarif' + + # Check the vulnerabilities via GitHub security tab + - name: Upload results + uses: github/codeql-action/upload-sarif@23acc5c183826b7a8a97bce3cecc52db901f8251 + with: + sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/container-verify.yml b/.github/workflows/container-verify.yml new file mode 100644 index 00000000000..c8f5726af75 --- /dev/null +++ b/.github/workflows/container-verify.yml @@ -0,0 +1,57 @@ +name: container verify + +on: + workflow_dispatch: + inputs: + version: + description: 'Besu version' + required: true + verify-latest-version: + description: 'Check latest container version' + required: false + type: choice + default: "true" + options: + - "true" + - "false" + +jobs: + verify: + timeout-minutes: 4 + strategy: + matrix: + combination: + - tag: ${{ inputs.version }} + platform: '' + runner: ubuntu-latest + - tag: ${{ inputs.version }}-amd64 + platform: 'linux/amd64' + runner: ubuntu-latest + - tag: latest + platform: '' + runner: ubuntu-latest + - tag: ${{ inputs.version }}-arm64 + platform: '' + runner: besu-arm64 + runs-on: ${{ matrix.combination.runner }} + env: + CONTAINER_NAME: besu-check + steps: + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + + - name: Start container + run: | + PLATFORM_OPT="" + [[ x${{ matrix.combination.platform }} != 'x' ]] && PLATFORM_OPT="--platform ${{ matrix.combination.platform }}" + docker run -d $PLATFORM_OPT --name ${{ env.CONTAINER_NAME }} hyperledger/besu:${{ matrix.combination.tag }} + + - name: Verify besu container + run: bash .github/workflows/BesuContainerVerify.sh + env: + TAG: ${{ matrix.combination.tag }} + VERSION: ${{ inputs.version }} + CHECK_LATEST: ${{ inputs.verify-latest-version }} + + - name: Stop container + run: docker stop ${{ env.CONTAINER_NAME }} diff --git a/.github/workflows/dco-merge-group.yml b/.github/workflows/dco-merge-group.yml deleted file mode 100644 index fee29b6c5d7..00000000000 --- a/.github/workflows/dco-merge-group.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: dco -on: - merge_group: - -jobs: - dco: - runs-on: [besu-research-ubuntu-8] - if: ${{ github.actor != 'dependabot[bot]' }} - steps: - - run: echo "This DCO job runs on merge_queue event and doesn't check PR contents" diff --git a/.github/workflows/dco.yml b/.github/workflows/dco.yml index 5fa9931c77a..da0d2115acf 100644 --- a/.github/workflows/dco.yml +++ b/.github/workflows/dco.yml @@ -1,20 +1,19 @@ -name: dco -on: - pull_request: - workflow_dispatch: +name: DCO + +on: [pull_request] jobs: - dco: - runs-on: [besu-research-ubuntu-8] + dco_check: + runs-on: ubuntu-latest + name: DCO if: ${{ github.actor != 'dependabot[bot]' }} steps: - - run: echo "This DCO job runs on pull_request event and workflow_dispatch" - - name: Get PR Commits - id: 'get-pr-commits' - uses: tim-actions/get-pr-commits@v1.2.0 - with: - token: ${{ secrets.GITHUB_TOKEN }} - - name: DCO Check - uses: tim-actions/dco@v1.1.0 - with: - commits: ${{ steps.get-pr-commits.outputs.commits }} + - name: Get PR Commits + id: 'get-pr-commits' + uses: tim-actions/get-pr-commits@198af03565609bb4ed924d1260247b4881f09e7d + with: + token: ${{ secrets.GITHUB_TOKEN }} + - name: DCO Check + uses: tim-actions/dco@f2279e6e62d5a7d9115b0cb8e837b777b1b02e21 + with: + commits: ${{ steps.get-pr-commits.outputs.commits }} diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml new file mode 100644 index 00000000000..b9bb3d95973 --- /dev/null +++ b/.github/workflows/develop.yml @@ -0,0 +1,121 @@ +name: docker develop + +on: + push: + branches: + - main +env: + registry: docker.io + +jobs: + hadolint: + runs-on: ubuntu-22.04 + steps: + - name: Checkout Repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - name: Set up Java + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 + with: + distribution: temurin + java-version: 21 + - name: setup gradle + uses: gradle/actions/setup-gradle@9e899d11ad247ec76be7a60bc1cf9d3abbb9e7f1 + with: + cache-disabled: true + + - name: hadoLint + run: docker run --rm -i hadolint/hadolint < docker/Dockerfile + buildDocker: + needs: hadolint + permissions: + contents: read + packages: write + + strategy: + fail-fast: false + matrix: + platform: + - ubuntu-22.04 + - besu-arm64 + runs-on: ${{ matrix.platform }} + steps: + - name: Prepare + id: prep + run: | + platform=${{ matrix.platform }} + if [ "$platform" = 'ubuntu-22.04' ]; then + echo "PLATFORM_PAIR=linux-amd64" >> $GITHUB_OUTPUT + echo "ARCH=amd64" >> $GITHUB_OUTPUT + else + echo "PLATFORM_PAIR=linux-arm64" >> $GITHUB_OUTPUT + echo "ARCH=arm64" >> $GITHUB_OUTPUT + fi + + # Get the current date and time in the format YY.MM + DATE_TIME=$(date +"%y.%-m") + # Get the short SHA of the merge commit + SHORT_SHA=${GITHUB_SHA::7} + # Construct the build target name + BUILD_TARGET_NAME="${DATE_TIME}-develop-${SHORT_SHA}" + echo "Build Target Name: $BUILD_TARGET_NAME" + # Set the build target name as an environment variable + echo "BUILD_TARGET_NAME=${BUILD_TARGET_NAME}" >> $GITHUB_ENV + + - name: Checkout Repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - name: Set up Java + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 + with: + distribution: temurin + java-version: 21 + - name: setup gradle + uses: gradle/actions/setup-gradle@9e899d11ad247ec76be7a60bc1cf9d3abbb9e7f1 + with: + cache-disabled: true + - name: install goss + run: | + mkdir -p docker/reports + curl -L https://github.com/aelsabbahy/goss/releases/download/v0.4.4/goss-${{ steps.prep.outputs.PLATFORM_PAIR }} -o ./docker/tests/goss-${{ steps.prep.outputs.PLATFORM_PAIR }} + - name: login to ${{ env.registry }} + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d + with: + registry: ${{ env.registry }} + username: ${{ secrets.DOCKER_USER_RW }} + password: ${{ secrets.DOCKER_PASSWORD_RW }} + - name: build and test docker + uses: gradle/actions/setup-gradle@9e899d11ad247ec76be7a60bc1cf9d3abbb9e7f1 + env: + architecture: ${{ steps.prep.outputs.ARCH }} + with: + cache-disabled: true + arguments: testDocker -PdockerOrgName=${{ env.registry }}/${{ secrets.DOCKER_ORG }} -Pversion=${{ env.BUILD_TARGET_NAME}} -Prelease.releaseVersion=develop + - name: publish + env: + architecture: ${{ steps.prep.outputs.ARCH }} + run: ./gradlew --no-daemon dockerUpload -PdockerOrgName=${{ env.registry }}/${{ secrets.DOCKER_ORG }} -Pversion=${{ env.BUILD_TARGET_NAME }} -Prelease.releaseVersion=develop + multiArch: + needs: buildDocker + runs-on: ubuntu-22.04 + permissions: + contents: read + packages: write + steps: + - name: Checkout Repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - name: Set up Java + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 + with: + distribution: temurin + java-version: 21 + - name: setup gradle + uses: gradle/actions/setup-gradle@9e899d11ad247ec76be7a60bc1cf9d3abbb9e7f1 + with: + cache-disabled: true + - name: login to ${{ env.registry }} + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d + with: + registry: ${{ env.registry }} + username: ${{ secrets.DOCKER_USER_RW }} + password: ${{ secrets.DOCKER_PASSWORD_RW }} + - name: multi-arch docker + run: ./gradlew manifestDocker -PdockerOrgName=${{ env.registry }}/${{ secrets.DOCKER_ORG }} -Pversion=${{ env.BUILD_TARGET_NAME }} -Prelease.releaseVersion=develop diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml deleted file mode 100644 index 6f44fe43e54..00000000000 --- a/.github/workflows/gradle-wrapper-validation.yml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -name: "Validate Gradle Wrapper" -on: [push, pull_request] - -jobs: - validation: - name: "Gradle Wrapper Validation" - runs-on: [besu-research-ubuntu-8] - steps: - - uses: actions/checkout@v4 - - uses: gradle/wrapper-validation-action@v1 diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml new file mode 100644 index 00000000000..171ddeb85ec --- /dev/null +++ b/.github/workflows/integration-tests.yml @@ -0,0 +1,40 @@ +name: integration-tests +on: + workflow_dispatch: + pull_request: + branches: + - main + - release-* + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + GRADLE_OPTS: "-Xmx6g -Dorg.gradle.daemon=false -Dorg.gradle.parallel=true -Dorg.gradle.caching=true" + +jobs: + integration-tests: + name: "integration-passed" + runs-on: ubuntu-22.04 + permissions: + statuses: write + checks: write + steps: + - name: Checkout Repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + with: + ref: ${{ github.event.pull_request.head.sha || github.ref }} + - name: Set up Java + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 + with: + distribution: temurin + java-version: 21 + - name: setup gradle + uses: gradle/actions/setup-gradle@9e899d11ad247ec76be7a60bc1cf9d3abbb9e7f1 + with: + cache-disabled: true + - name: run integration tests + run: ./gradlew integrationTest compileJmh + + diff --git a/.github/workflows/pr-checklist-on-open.yml b/.github/workflows/pr-checklist-on-open.yml deleted file mode 100644 index 3a4d4342c58..00000000000 --- a/.github/workflows/pr-checklist-on-open.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: "comment on pr with checklist" -on: - pull_request_target: - types: [ opened ] - branches: [ main ] -jobs: - checklist: - name: "add checklist as a comment on newly opened PRs" - runs-on: [besu-research-ubuntu-8] - steps: - - uses: actions/github-script@v5 - with: - github-token: ${{secrets.GITHUB_TOKEN}} - script: | - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: '- [ ] I thought about documentation and added the `doc-change-required` label to this PR if [updates are required](https://wiki.hyperledger.org/display/BESU/Documentation).\n- [ ] I thought about the changelog and included a [changelog update if required](https://wiki.hyperledger.org/display/BESU/Changelog).\n- [ ] If my PR includes database changes (e.g. KeyValueSegmentIdentifier) I have thought about compatibility and performed forwards and backwards compatibility tests' - }) diff --git a/.github/workflows/pre-review.yml b/.github/workflows/pre-review.yml new file mode 100644 index 00000000000..cba13f1ebda --- /dev/null +++ b/.github/workflows/pre-review.yml @@ -0,0 +1,168 @@ +name: pre-review + +on: + pull_request: + branches: + - main + - release-* + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + GRADLE_OPTS: "-Xmx6g -Dorg.gradle.parallel=true" + total-runners: 8 + +jobs: + repolint: + name: "Repository Linting" + runs-on: ubuntu-22.04 + container: ghcr.io/todogroup/repolinter:v0.11.2 + steps: + - name: Checkout Code + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + with: + ref: ${{ github.event.pull_request.head.sha || github.ref }} + - name: Lint Repo + run: bundle exec /app/bin/repolinter.js --rulesetUrl https://raw.githubusercontent.com/hyperledger-labs/hyperledger-community-management-tools/main/repo_structure/repolint.json --format markdown + gradle-wrapper: + name: "Gradle Wrapper Validation" + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + with: + ref: ${{ github.event.pull_request.head.sha || github.ref }} + - uses: gradle/wrapper-validation-action@56b90f209b02bf6d1deae490e9ef18b21a389cd4 + spotless: + runs-on: ubuntu-22.04 + steps: + - name: Checkout Repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + with: + ref: ${{ github.event.pull_request.head.sha || github.ref }} + - name: Set up Java + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 + with: + distribution: temurin + java-version: 21 + - name: Setup Gradle + uses: gradle/actions/setup-gradle@9e899d11ad247ec76be7a60bc1cf9d3abbb9e7f1 + with: + cache-disabled: true + - name: run spotless + run: ./gradlew spotlessCheck + compile: + runs-on: ubuntu-22.04 + timeout-minutes: 30 + needs: [spotless, gradle-wrapper, repolint] + steps: + - name: Checkout Repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + with: + ref: ${{ github.event.pull_request.head.sha || github.ref }} + - name: Set up Java + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 + with: + distribution: temurin + java-version: 21 + - name: Setup Gradle + uses: gradle/actions/setup-gradle@9e899d11ad247ec76be7a60bc1cf9d3abbb9e7f1 + with: + cache-disabled: true + - name: Gradle Compile + run: ./gradlew build -x test -x spotlessCheck + unitTests: + env: + GRADLEW_UNIT_TEST_ARGS: ${{matrix.gradle_args}} + runs-on: ubuntu-22.04 + needs: [spotless, gradle-wrapper, repolint] + permissions: + checks: write + statuses: write + strategy: + fail-fast: true + matrix: + runner_index: [0,1,2,3,4,5,6,7] + steps: + - name: Checkout Repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + with: + ref: ${{ github.event.pull_request.head.sha || github.ref }} + - name: Set up Java + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 + with: + distribution: temurin + java-version: 21 + - name: Install required packages + run: sudo apt-get install -y xmlstarlet + - name: Setup Gradle + uses: gradle/actions/setup-gradle@9e899d11ad247ec76be7a60bc1cf9d3abbb9e7f1 + with: + cache-disabled: true + - name: List unit tests + run: ./gradlew test --test-dry-run -Dorg.gradle.parallel=true -Dorg.gradle.caching=true + - name: Extract current test list + run: mkdir tmp; find . -type f -name TEST-*.xml | xargs -I{} bash -c "xmlstarlet sel -t -v '/testsuite/@name' '{}'; echo '{}' | sed 's#\./\(.*\)/build/test-results/.*# \1#'" | tee tmp/currentTests.list + - name: Get unit test reports + uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d + continue-on-error: true + with: + branch: main + workflow: update-test-reports.yml + name: unit-test-results + path: tmp/junit-xml-reports-downloaded + if_no_artifact_found: ignore + - name: Split tests + run: .github/workflows/splitTestsByTime.sh tmp/junit-xml-reports-downloaded "tmp/junit-xml-reports-downloaded/unit-.*-test-results" "build/test-results" ${{env.total-runners}} ${{ matrix.runner_index }} > testList.txt + - name: Upload Timing + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 + if: matrix.runner_index == 0 + with: + name: unit-tests-timing + path: 'tmp/timing.tsv' + - name: Upload Lists + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 + if: matrix.runner_index == 0 + with: + name: unit-tests-lists + path: 'tmp/*.list' + - name: Upload gradle test tasks + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 + with: + name: testList-${{ matrix.runner_index }}.txt + path: testList.txt + - name: run unit tests + run: cat testList.txt | xargs -P 1 ./gradlew -Dorg.gradle.parallel=true -Dorg.gradle.caching=true + - name: Remove downloaded test results + run: rm -rf tmp/junit-xml-reports-downloaded + - name: Upload Unit Test Results + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 + with: + name: unit-${{matrix.runner_index}}-test-results + path: '**/test-results/**/TEST-*.xml' + - name: Upload Unit Test HTML Reports + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 + if: success() || failure() + with: + name: unit-${{matrix.runner_index}}-test-html-reports + path: '**/build/reports/tests/test/**' + unittests-passed: + name: "unittests-passed" + runs-on: ubuntu-22.04 + needs: [compile, unitTests] + permissions: + checks: write + statuses: write + if: always() + steps: + # Fail if any `needs` job was not a success. + # Along with `if: always()`, this allows this job to act as a single required status check for the entire workflow. + - name: Fail on workflow error + run: exit 1 + if: >- + ${{ + contains(needs.*.result, 'failure') + || contains(needs.*.result, 'cancelled') + || contains(needs.*.result, 'skipped') + }} diff --git a/.github/workflows/reference-tests.yml b/.github/workflows/reference-tests.yml new file mode 100644 index 00000000000..4e458e057e3 --- /dev/null +++ b/.github/workflows/reference-tests.yml @@ -0,0 +1,83 @@ +name: reference-tests +on: + workflow_dispatch: + pull_request: + branches: + - main + - release-* + +env: + GRADLE_OPTS: "-Xmx6g -Dorg.gradle.daemon=false -Dorg.gradle.parallel=true -Dorg.gradle.caching=true" + total-runners: 10 + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + referenceTestEthereum: + runs-on: ubuntu-22.04 + permissions: + statuses: write + checks: write + packages: read + strategy: + fail-fast: true + matrix: + runner_index: [1,2,3,4,5,6,7,8,9,10] + steps: + - name: Checkout Repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + with: + ref: ${{ github.event.pull_request.head.sha || github.ref }} + submodules: recursive + - name: Set up Java + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 + with: + distribution: semeru # IBM Semeru with OpenJ9 + java-version: 21 + - name: setup gradle + uses: gradle/actions/setup-gradle@9e899d11ad247ec76be7a60bc1cf9d3abbb9e7f1 + with: + cache-disabled: true + - name: execute generate reference tests + run: ./gradlew ethereum:referencetests:referenceTestClasses -Dorg.gradle.parallel=true -Dorg.gradle.caching=true + - name: list test files generated + run: find ethereum/referencetests/build/generated/sources/reference-test -name "*.java" | sort >> filenames.txt + - name: Split tests + run: ./.github/workflows/splitList.sh filenames.txt ${{env.total-runners}} + - name: echo test file count + run: cat group_${{matrix.runner_index}}.txt | wc + - name: convert to test suite classnames + run: cat group_${{matrix.runner_index}}.txt | sed -e 's/^.*java\///' -e 's@/@.@g' -e 's/\.java//' -e 's/^/--tests /' > testClasses.txt + - name: compose gradle args + run: tr '\n' ' ' < testClasses.txt > refTestArgs.txt + - name: refTestArgs.txt + run: cat refTestArgs.txt + - name: run reference tests + run: ./gradlew ethereum:referenceTests:referenceTests `cat refTestArgs.txt` + - name: Upload Test Report + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 + if: always() # always run even if the previous step fails + with: + name: reference-test-node-${{matrix.runner_index}}-results + path: '**/build/test-results/referenceTests/TEST-*.xml' + reftests-passed: + name: "reftests-passed" + runs-on: ubuntu-22.04 + needs: [ referenceTestEthereum ] + permissions: + checks: write + statuses: write + if: always() + steps: + # Fail if any `needs` job was not a success. + # Along with `if: always()`, this allows this job to act as a single required status check for the entire workflow. + - name: Fail on workflow error + run: exit 1 + if: >- + ${{ + contains(needs.*.result, 'failure') + || contains(needs.*.result, 'cancelled') + || contains(needs.*.result, 'skipped') + }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c2041368b0f..2f0661e3cfb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,21 +1,315 @@ -name: release besu +name: release + on: release: - types: released + types: [released] + +env: + registry: docker.io + GRADLE_OPTS: "-Dorg.gradle.parallel=true -Dorg.gradle.caching=true" + jobs: + preprocess_release: + runs-on: ubuntu-22.04 + steps: + - name: Pre-process Release Name + id: pre_process_release_name + run: | + RELEASE_NAME="${{ github.event.release.name }}" + # strip all whitespace + RELEASE_NAME="${RELEASE_NAME//[[:space:]]/}" + if [[ ! "$RELEASE_NAME" =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?(-.*)?$ ]]; then + echo "Release name does not conform to a valid besu release format YY.M.v[-suffix], e.g. 24.8.0-RC1." + exit 1 + fi + echo "release_name=$RELEASE_NAME" >> $GITHUB_OUTPUT # Set as output using the new syntax + outputs: + release_name: ${{ steps.pre_process_release_name.outputs.release_name }} + + artifacts: + runs-on: ubuntu-22.04 + needs: preprocess_release + env: + RELEASE_NAME: ${{ needs.preprocess_release.outputs.release_name }} # Use the output from the pre_process_release job + permissions: + contents: write + outputs: + tarSha: ${{steps.hashes.outputs.tarSha}} + zipSha: ${{steps.hashes.outputs.zipSha}} + steps: + - name: checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - name: Set up Java + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 + with: + distribution: temurin + java-version: 21 + - name: setup gradle + uses: gradle/actions/setup-gradle@9e899d11ad247ec76be7a60bc1cf9d3abbb9e7f1 + with: + cache-disabled: true + - name: assemble release + run: + ./gradlew -Prelease.releaseVersion=${{env.RELEASE_NAME}} -Pversion=${{env.RELEASE_NAME}} assemble + - name: hashes + id: hashes + run: | + cd build/distributions + echo "zipSha=$(shasum -a 256 besu*.zip)" + echo "tarSha=$(shasum -a 256 besu*.tar.gz)" + echo "zipSha=$(shasum -a 256 besu*.zip)" >> $GITHUB_OUTPUT + echo "tarSha=$(shasum -a 256 besu*.tar.gz)" >> $GITHUB_OUTPUT + - name: upload tarball + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 + with: + path: 'build/distributions/besu*.tar.gz' + name: besu-${{ env.RELEASE_NAME }}.tar.gz + compression-level: 0 + - name: upload zipfile + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 + with: + path: 'build/distributions/besu*.zip' + name: besu-${{ env.RELEASE_NAME }}.zip + compression-level: 0 + + testWindows: + runs-on: windows-2022 + needs: artifacts + timeout-minutes: 10 + steps: + - name: Set up Java + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 + with: + distribution: temurin + java-version: 21 + - name: Download zip + uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe + with: + pattern: besu-*.zip + merge-multiple: true + - name: test Besu + run: | + dir + unzip besu-*.zip -d besu-tmp + cd besu-tmp + mv besu-* ../besu + cd .. + besu\bin\besu.bat --help + besu\bin\besu.bat --version + + publish: + runs-on: ubuntu-22.04 + needs: [preprocess_release, testWindows, artifacts] + env: + RELEASE_NAME: ${{ needs.preprocess_release.outputs.release_name }} # Use the output from the pre_process_release job + permissions: + contents: write + steps: + - name: Download archives + uses: actions/download-artifact@eaceaf801fd36c7dee90939fad912460b18a1ffe + with: + pattern: besu-* + merge-multiple: true + path: 'build/distributions' + - name: Upload Release assets + uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 + with: + append_body: true + files: | + build/distributions/besu*.tar.gz + build/distributions/besu*.zip + body: | + ${{needs.artifacts.outputs.tarSha}} + ${{needs.artifacts.outputs.zipSha}} + + + + artifactoryPublish: + runs-on: ubuntu-22.04 + needs: [preprocess_release, artifacts] + env: + RELEASE_NAME: ${{ needs.preprocess_release.outputs.release_name }} # Use the output from the pre_process_release job + steps: + - name: checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - name: Set up Java + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 + with: + distribution: temurin + java-version: 21 + - name: setup gradle + uses: gradle/actions/setup-gradle@9e899d11ad247ec76be7a60bc1cf9d3abbb9e7f1 + with: + cache-disabled: true + - name: Artifactory Publish + env: + ARTIFACTORY_USER: ${{ secrets.BESU_ARTIFACTORY_USER }} + ARTIFACTORY_KEY: ${{ secrets.BESU_ARTIFACTORY_TOKEN }} + run: ./gradlew -Prelease.releaseVersion=${{ env.RELEASE_NAME }} -Pversion=${{env.RELEASE_NAME}} artifactoryPublish + + hadolint: + runs-on: ubuntu-22.04 + steps: + - name: Checkout Repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - name: Set up Java + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 + with: + distribution: temurin + java-version: 21 + - name: setup gradle + uses: gradle/actions/setup-gradle@9e899d11ad247ec76be7a60bc1cf9d3abbb9e7f1 + with: + cache-disabled: true + - name: hadoLint + run: docker run --rm -i hadolint/hadolint < docker/Dockerfile + + buildDocker: + needs: [preprocess_release, hadolint] + env: + RELEASE_NAME: ${{ needs.preprocess_release.outputs.release_name }} # Use the output from the pre_process_release job + permissions: + contents: read + packages: write + + strategy: + fail-fast: false + matrix: + platform: + - ubuntu-22.04 + - besu-arm64 + runs-on: ${{ matrix.platform }} + steps: + - name: Prepare + id: prep + run: | + platform=${{ matrix.platform }} + if [ "$platform" = 'ubuntu-22.04' ]; then + echo "PLATFORM_PAIR=linux-amd64" >> $GITHUB_OUTPUT + echo "ARCH=amd64" >> $GITHUB_OUTPUT + else + echo "PLATFORM_PAIR=linux-arm64" >> $GITHUB_OUTPUT + echo "ARCH=arm64" >> $GITHUB_OUTPUT + fi + - name: Checkout Repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - name: short sha + id: shortSha + run: echo "sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + - name: Set up Java + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 + with: + distribution: temurin + java-version: 21 + - name: setup gradle + uses: gradle/actions/setup-gradle@9e899d11ad247ec76be7a60bc1cf9d3abbb9e7f1 + with: + cache-disabled: true + - name: install goss + run: | + mkdir -p docker/reports + curl -L https://github.com/aelsabbahy/goss/releases/download/v0.4.4/goss-${{ steps.prep.outputs.PLATFORM_PAIR }} -o ./docker/tests/goss-${{ steps.prep.outputs.PLATFORM_PAIR }} + - name: login to ${{ env.registry }} + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d + with: + registry: ${{ env.registry }} + username: ${{ secrets.DOCKER_USER_RW }} + password: ${{ secrets.DOCKER_PASSWORD_RW }} + - name: build and test docker + uses: gradle/actions/setup-gradle@9e899d11ad247ec76be7a60bc1cf9d3abbb9e7f1 + env: + architecture: ${{ steps.prep.outputs.ARCH }} + with: + cache-disabled: true + arguments: testDocker -PdockerOrgName=${{ env.registry }}/${{ secrets.DOCKER_ORG }} -Pversion=${{env.RELEASE_NAME}} -Prelease.releaseVersion=${{ env.RELEASE_NAME }} + - name: publish + env: + architecture: ${{ steps.prep.outputs.ARCH }} + run: ./gradlew --no-daemon dockerUpload -PdockerOrgName=${{ env.registry }}/${{ secrets.DOCKER_ORG }} -Pversion=${{env.RELEASE_NAME}} -Prelease.releaseVersion=${{ env.RELEASE_NAME }} + + multiArch: + needs: [preprocess_release, buildDocker] + env: + RELEASE_NAME: ${{ needs.preprocess_release.outputs.release_name }} # Use the output from the pre_process_release job + runs-on: ubuntu-22.04 + permissions: + contents: read + packages: write + steps: + - name: Checkout Repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - name: Set up Java + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 + with: + distribution: temurin + java-version: 21 + - name: setup gradle + uses: gradle/actions/setup-gradle@9e899d11ad247ec76be7a60bc1cf9d3abbb9e7f1 + with: + cache-disabled: true + - name: login to ${{ env.registry }} + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d + with: + registry: ${{ env.registry }} + username: ${{ secrets.DOCKER_USER_RW }} + password: ${{ secrets.DOCKER_PASSWORD_RW }} + - name: multi-arch docker + run: ./gradlew manifestDocker -PdockerOrgName=${{ env.registry }}/${{ secrets.DOCKER_ORG }} -Pversion=${{env.RELEASE_NAME}} -Prelease.releaseVersion=${{ env.RELEASE_NAME }} + + amendNotes: + needs: [preprocess_release, multiArch] + env: + RELEASE_NAME: ${{ needs.preprocess_release.outputs.release_name }} # Use the output from the pre_process_release job + runs-on: ubuntu-22.04 + permissions: + contents: write + steps: + - name: add pull command to release notes + uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 + with: + append_body: true + body: | + `docker pull ${{env.registry}}/${{secrets.DOCKER_ORG}}/besu:${{env.RELEASE_NAME}}` + dockerPromoteX64: - runs-on: [besu-research-ubuntu-16] + needs: [preprocess_release, multiArch] + env: + RELEASE_NAME: ${{ needs.preprocess_release.outputs.release_name }} # Use the output from the pre_process_release job + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v3 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 + with: + distribution: temurin + java-version: 21 + cache: gradle + - name: login to ${{ env.registry }} + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d with: - distribution: 'temurin' # See 'Supported distributions' for available options - java-version: '17' - - name: Login to DockerHub - run: echo '${{ secrets.DOCKER_PASSWORD_RW }}' | docker login -u '${{ secrets.DOCKER_USER_RW }}' --password-stdin + registry: ${{ env.registry }} + username: ${{ secrets.DOCKER_USER_RW }} + password: ${{ secrets.DOCKER_PASSWORD_RW }} - name: Setup Gradle - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@9e899d11ad247ec76be7a60bc1cf9d3abbb9e7f1 + with: + cache-disabled: true - name: Docker upload - run: ./gradlew "-Prelease.releaseVersion=${{ github.ref_name }}" "-PdockerOrgName=${{ secrets.DOCKER_ORG }}" dockerUploadRelease + run: ./gradlew "-Prelease.releaseVersion=${{ env.RELEASE_NAME }}" "-PdockerOrgName=${{ env.registry }}/${{ secrets.DOCKER_ORG }}" dockerUploadRelease - name: Docker manifest - run: ./gradlew "-Prelease.releaseVersion=${{ github.ref_name }}" "-PdockerOrgName=${{ secrets.DOCKER_ORG }}" manifestDockerRelease + run: ./gradlew "-Prelease.releaseVersion=${{ env.RELEASE_NAME }}" "-PdockerOrgName=${{ env.registry }}/${{ secrets.DOCKER_ORG }}" manifestDockerRelease + + verifyContainer: + needs: [preprocess_release, dockerPromoteX64] + env: + RELEASE_NAME: ${{ needs.preprocess_release.outputs.release_name }} # Use the output from the pre_process_release job + runs-on: ubuntu-22.04 + permissions: + contents: read + actions: write + steps: + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - name: Trigger container verify + run: echo '{"version":"${{ env.RELEASE_NAME }}","verify-latest-version":"true"}' | gh workflow run container-verify.yml --json + env: + GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/repolinter.yml b/.github/workflows/repolinter.yml deleted file mode 100644 index 983164e1061..00000000000 --- a/.github/workflows/repolinter.yml +++ /dev/null @@ -1,24 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# Hyperledger Repolinter Action -name: Repolinter - -on: - workflow_dispatch: - push: - branches: - - master - - main - pull_request: - branches: - - master - - main - -jobs: - build: - runs-on: [besu-research-ubuntu-16] - container: ghcr.io/todogroup/repolinter:v0.10.1 - steps: - - name: Checkout Code - uses: actions/checkout@v4 - - name: Lint Repo - run: bundle exec /app/bin/repolinter.js --rulesetUrl https://raw.githubusercontent.com/hyperledger-labs/hyperledger-community-management-tools/main/repo_structure/repolint.json --format markdown diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 5a2ba5143ee..7549656d223 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -14,29 +14,28 @@ permissions: jobs: Analysis: runs-on: ubuntu-latest - if: github.repository == 'hyperledger/besu' steps: - name: checkout - uses: actions/checkout@v4 - - name: Set up JDK 17 - uses: actions/setup-java@v3 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - name: Set up Java + uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 with: - distribution: 'temurin' - java-version: '17' + distribution: temurin + java-version: 21 - name: Cache SonarCloud packages - uses: actions/cache@v3 + uses: actions/cache@e12d46a63a90f2fae62d114769bbf2a179198b5c with: path: ~/.sonar/cache key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar - - name: Cache Gradle packages - uses: actions/cache@v3 + - name: setup gradle + uses: gradle/actions/setup-gradle@9e899d11ad247ec76be7a60bc1cf9d3abbb9e7f1 with: - path: ~/.gradle/caches - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} - restore-keys: ${{ runner.os }}-gradle + cache-disabled: true - name: Build and analyze env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: ./gradlew build sonarqube --continue --info + SONAR_ORGANIZATION: ${{ vars.SONAR_ORGANIZATION }} + SONAR_PROJECT_KEY: ${{ vars.SONAR_PROJECT_KEY }} + run: ./gradlew build sonarqube --continue --info -Dorg.gradle.parallel=true -Dorg.gradle.caching=true diff --git a/.github/workflows/splitList.sh b/.github/workflows/splitList.sh new file mode 100755 index 00000000000..4f302dc0cc2 --- /dev/null +++ b/.github/workflows/splitList.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +## +## Copyright contributors to Hyperledger Besu. +## +## Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +## an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +## specific language governing permissions and limitations under the License. +## +## SPDX-License-Identifier: Apache-2.0 +## + +N=$2 # Number of groups +i=0 # Initialize counter +cat $1 | while read line; do + echo "$line" >> "group_$((i % N + 1)).txt" + let i++ +done diff --git a/.github/workflows/splitTestsByTime.sh b/.github/workflows/splitTestsByTime.sh new file mode 100755 index 00000000000..219d2d74e78 --- /dev/null +++ b/.github/workflows/splitTestsByTime.sh @@ -0,0 +1,128 @@ +#!/bin/bash +## +## Copyright contributors to Hyperledger Besu. +## +## Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +## an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +## specific language governing permissions and limitations under the License. +## +## SPDX-License-Identifier: Apache-2.0 +## + +REPORTS_DIR="$1" +REPORT_STRIP_PREFIX="$2" +REPORT_STRIP_SUFFIX="$3" +SPLIT_COUNT=$4 +SPLIT_INDEX=$5 + +# extract tests time from Junit XML reports +find "$REPORTS_DIR" -type f -name TEST-*.xml | xargs -I{} bash -c "xmlstarlet sel -t -v 'concat(sum(//testcase/@time), \" \", //testsuite/@name)' '{}'; echo '{}' | sed \"s#${REPORT_STRIP_PREFIX}/\(.*\)/${REPORT_STRIP_SUFFIX}.*# \1#\"" > tmp/timing.tsv + +# Sort times in descending order +IFS=$'\n' sorted=($(sort -nr tmp/timing.tsv)) +unset IFS + +sums=() +tests=() + +# Initialize sums +for ((i=0; i tmp/processedTests.list + +# add tests to groups trying to balance the sum of execution time of each group +for line in "${sorted[@]}"; do + line_parts=( $line ) + test_time=$( echo "${line_parts[0]} * 1000 / 1" | bc ) # convert to millis without decimals + test_name=${line_parts[1]} + module_dir=${line_parts[2]} + test_with_module="$test_name $module_dir" + + # deduplication check to avoid executing a test multiple time + if grep -F -q --line-regexp "$test_with_module" tmp/processedTests.list + then + continue + fi + + # Does the test still exists? + if grep -F -q --line-regexp "$test_with_module" tmp/currentTests.list + then + # Find index of min sum + idx_min_sum=0 + min_sum=${sums[0]} + for ((i=0; i> tmp/processedTests.list + fi +done + +# Any new test? +grep -F --line-regexp -v -f tmp/processedTests.list tmp/currentTests.list > tmp/newTests.list +idx_new_test=0 +while read -r new_test_with_module +do + idx_group=$(( idx_new_test % SPLIT_COUNT )) + group=${tests[$idx_group]} + tests[$idx_group]="${group}${new_test_with_module}," + idx_new_test=$(( idx_new_test + 1 )) +done < tmp/newTests.list + +# remove last comma +for ((i=0; i> "$GITHUB_OUTPUT" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Get unit test reports from latest merged PR + uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d + with: + workflow: pre-review.yml + workflow_conclusion: success + pr: ${{ env.LATEST_MERGED_PR_NUMBER }} + name_is_regexp: true + name: 'unit-\d+-test-results' + path: unit-test-results + if_no_artifact_found: fail + env: + LATEST_MERGED_PR_NUMBER: ${{ steps.latest_merged_pr_number.outputs.PULL_REQUEST_NUMBER }} + - name: Upload unit test results + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 + with: + name: unit-test-results + path: 'unit-test-results/**/test-results/**/TEST-*.xml' + - name: Get acceptance test reports from latest merged PR + uses: dawidd6/action-download-artifact@e7466d1a7587ed14867642c2ca74b5bcc1e19a2d + with: + workflow: acceptance-tests.yml + workflow_conclusion: success + pr: ${{ env.LATEST_MERGED_PR_NUMBER }} + name_is_regexp: true + name: 'acceptance-node-\d+-test-results' + path: acceptance-test-results + if_no_artifact_found: fail + env: + LATEST_MERGED_PR_NUMBER: ${{ steps.latest_merged_pr_number.outputs.PULL_REQUEST_NUMBER }} + - name: Upload acceptance test results + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 + with: + name: acceptance-test-results + path: 'acceptance-test-results/**/TEST-*.xml' diff --git a/CHANGELOG.md b/CHANGELOG.md index 65e8bbaa7f4..d690f01f760 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,447 @@ # Changelog -## 23.10.3 +## [Unreleased] + +### Upcoming Breaking Changes +- k8s (KUBERNETES) Nat method is now deprecated and will be removed in a future release + +### Breaking Changes + +### Additions and Improvements +- Remove privacy test classes support [#7569](https://github.com/hyperledger/besu/pull/7569) + +### Bug fixes +- Fix for `debug_traceCall` to handle transactions without specified gas price. [#7510](https://github.com/hyperledger/besu/pull/7510) + + - Fix for IBFT2 chains using the BONSAI DB format [#7631](https://github.com/hyperledger/besu/pull/7631) + +## 24.9.1 + +### Upcoming Breaking Changes + +### Breaking Changes +- Receipt compaction is enabled by default. It will no longer be possible to downgrade Besu to versions prior to 24.5.1. + +### Additions and Improvements +- Add 'inbound' field to admin_peers JSON-RPC Call [#7461](https://github.com/hyperledger/besu/pull/7461) +- Add pending block header to `TransactionEvaluationContext` plugin API [#7483](https://github.com/hyperledger/besu/pull/7483) +- Add bootnode to holesky config [#7500](https://github.com/hyperledger/besu/pull/7500) +- Implement engine_getClientVersionV1 [#7512](https://github.com/hyperledger/besu/pull/7512) +- Performance optimzation for ECMUL (1 of 2) [#7509](https://github.com/hyperledger/besu/pull/7509) +- Performance optimzation for ECMUL (2 of 2) [#7543](https://github.com/hyperledger/besu/pull/7543) +- Include current chain head block when computing `eth_maxPriorityFeePerGas` [#7485](https://github.com/hyperledger/besu/pull/7485) +- Remove (old) documentation updates from the changelog [#7562](https://github.com/hyperledger/besu/pull/7562) +- Update Java and Gradle dependencies [#7571](https://github.com/hyperledger/besu/pull/7571) +- Layered txpool: new options `--tx-pool-min-score` to remove a tx from pool when its score is lower than the specified value [#7576](https://github.com/hyperledger/besu/pull/7576) +- Add `engine_getBlobsV1` method to the Engine API [#7553](https://github.com/hyperledger/besu/pull/7553) + +### Bug fixes +- Fix tracing in precompiled contracts when halting for out of gas [#7318](https://github.com/hyperledger/besu/issues/7318) +- Correctly release txpool save and restore lock in case of exceptions [#7473](https://github.com/hyperledger/besu/pull/7473) +- Fix for `eth_gasPrice` could not retrieve block error [#7482](https://github.com/hyperledger/besu/pull/7482) +- Correctly drops messages that exceeds local message size limit [#5455](https://github.com/hyperledger/besu/pull/7507) +- **DebugMetrics**: Fixed a `ClassCastException` occurring in `DebugMetrics` when handling nested metric structures. Previously, `Double` values within these structures were incorrectly cast to `Map` objects, leading to errors. This update allows for proper handling of both direct values and nested structures at the same level. Issue# [#7383](https://github.com/hyperledger/besu/pull/7383) +- `evmtool` was not respecting the `--genesis` setting, resulting in unexpected trace results. [#7433](https://github.com/hyperledger/besu/pull/7433) +- The genesis config override `contractSizeLimit` was not wired into code size limits [#7557](https://github.com/hyperledger/besu/pull/7557) +- Fix incorrect key filtering in LayeredKeyValueStorage stream [#7535](https://github.com/hyperledger/besu/pull/7557) +- Layered txpool: do not send notifications when moving tx between layers [#7539](https://github.com/hyperledger/besu/pull/7539) +- Layered txpool: fix for unsent drop notifications on remove [#7538](https://github.com/hyperledger/besu/pull/7538) +- Honor block number or tag parameter in eth_estimateGas and eth_createAccessList [#7502](https://github.com/hyperledger/besu/pull/7502) +- Fixed NPE during DefaultBlockchain object initialization [#7601](https://github.com/hyperledger/besu/pull/7601) + +## 24.9.0 + +This release version has been deprecated release due to CI bug + +## 24.8.0 + +### Upcoming Breaking Changes +- Receipt compaction will be enabled by default in a future version of Besu. After this change it will not be possible to downgrade to the previous Besu version. +- --Xbonsai-limit-trie-logs-enabled is deprecated, use --bonsai-limit-trie-logs-enabled instead +- --Xbonsai-trie-logs-pruning-window-size is deprecated, use --bonsai-trie-logs-pruning-window-size instead +- `besu storage x-trie-log` subcommand is deprecated, use `besu storage trie-log` instead +- Allow configuration of Withdrawal Request Contract Address via genesis configuration [#7356](https://github.com/hyperledger/besu/pull/7356) + +### Breaking Changes +- Remove long-deprecated `perm*whitelist*` methods [#7401](https://github.com/hyperledger/besu/pull/7401) + +### Additions and Improvements +- Expose set finalized/safe block in plugin api BlockchainService. These method can be used by plugins to set finalized/safe block for a PoA network (such as QBFT, IBFT and Clique).[#7382](https://github.com/hyperledger/besu/pull/7382) +- In process RPC service [#7395](https://github.com/hyperledger/besu/pull/7395) +- Added support for tracing private transactions using `priv_traceTransaction` API. [#6161](https://github.com/hyperledger/besu/pull/6161) +- Wrap WorldUpdater into EVMWorldupdater [#7434](https://github.com/hyperledger/besu/pull/7434) +- Bump besu-native to 0.9.4 [#7456](https://github.com/hyperledger/besu/pull/7456)= + +### Bug fixes +- Correct entrypoint in Docker evmtool [#7430](https://github.com/hyperledger/besu/pull/7430) +- Fix protocol schedule check for devnets [#7429](https://github.com/hyperledger/besu/pull/7429) +- Fix behaviour when starting in a pre-merge network [#7431](https://github.com/hyperledger/besu/pull/7431) +- Fix Null pointer from DNS daemon [#7505](https://github.com/hyperledger/besu/issues/7505) + +### Download Links +https://github.com/hyperledger/besu/releases/tag/24.8.0 +https://github.com/hyperledger/besu/releases/download/24.8.0/besu-24.8.0.tar.gz / sha256 9671157a623fb94005357bc409d1697a0d62bb6fd434b1733441bb301a9534a4 +https://github.com/hyperledger/besu/releases/download/24.8.0/besu-24.8.0.zip / sha256 9ee217d2188e8da89002c3f42e4f85f89aab782e9512bd03520296f0a4dcdd90 + +## 24.7.1 + +### Breaking Changes +- Remove deprecated sync modes (X_SNAP and X_CHECKPOINT). Use SNAP and CHECKPOINT instead [#7309](https://github.com/hyperledger/besu/pull/7309) +- Remove PKI-backed QBFT (deprecated in 24.5.1) Other forms of QBFT remain unchanged. [#7293](https://github.com/hyperledger/besu/pull/7293) +- Do not maintain connections to PoA bootnodes [#7358](https://github.com/hyperledger/besu/pull/7358). See [#7314](https://github.com/hyperledger/besu/pull/7314) for recommended alternative behaviour. + +### Upcoming Breaking Changes +- Receipt compaction will be enabled by default in a future version of Besu. After this change it will not be possible to downgrade to the previous Besu version. +- --Xbonsai-limit-trie-logs-enabled is deprecated, use --bonsai-limit-trie-logs-enabled instead +- --Xbonsai-trie-logs-pruning-window-size is deprecated, use --bonsai-trie-logs-pruning-window-size instead +- `besu storage x-trie-log` subcommand is deprecated, use `besu storage trie-log` instead + +### Additions and Improvements +- `--Xsnapsync-bft-enabled` option enables experimental support for snap sync with IBFT/QBFT permissioned Bonsai-DB chains [#7140](https://github.com/hyperledger/besu/pull/7140) +- Add support to load external profiles using `--profile` [#7265](https://github.com/hyperledger/besu/issues/7265) +- `privacy-nonce-always-increments` option enables private transactions to always increment the nonce, even if the transaction is invalid [#6593](https://github.com/hyperledger/besu/pull/6593) +- Added `block-test` subcommand to the evmtool which runs blockchain reference tests [#7293](https://github.com/hyperledger/besu/pull/7293) +- removed PKI backed QBFT [#7310](https://github.com/hyperledger/besu/pull/7310) +- Implement gnark-crypto for eip-2537 [#7316](https://github.com/hyperledger/besu/pull/7316) +- Improve blob size transaction selector [#7312](https://github.com/hyperledger/besu/pull/7312) +- Added EIP-7702 [#7237](https://github.com/hyperledger/besu/pull/7237) +- Implement gnark-crypto for eip-196 [#7262](https://github.com/hyperledger/besu/pull/7262) +- Add trie log pruner metrics [#7352](https://github.com/hyperledger/besu/pull/7352) +- Force bonsai-limit-trie-logs-enabled=false when sync-mode=FULL instead of startup error [#7357](https://github.com/hyperledger/besu/pull/7357) +- `--Xbonsai-parallel-tx-processing-enabled` option enables executing transactions in parallel during block processing for Bonsai nodes +- Reduce default trie log pruning window size from 30,000 to 5,000 [#7365](https://github.com/hyperledger/besu/pull/7365) +- Add option `--poa-discovery-retry-bootnodes` for PoA networks to always use bootnodes during peer refresh, not just on first start [#7314](https://github.com/hyperledger/besu/pull/7314) + +### Bug fixes +- Fix `eth_call` deserialization to correctly ignore unknown fields in the transaction object. [#7323](https://github.com/hyperledger/besu/pull/7323) +- Prevent Besu from starting up with sync-mode=FULL and bonsai-limit-trie-logs-enabled=true for private networks [#7357](https://github.com/hyperledger/besu/pull/7357) +- Add 30 second timeout to trie log pruner preload [#7365](https://github.com/hyperledger/besu/pull/7365) +- Avoid executing pruner preload during trie log subcommands [#7366](https://github.com/hyperledger/besu/pull/7366) + +### Download Links +https://github.com/hyperledger/besu/releases/tag/24.7.1 +https://github.com/hyperledger/besu/releases/download/24.7.1/besu-24.7.1.tar.gz / sha256 59ac352a86fd887225737a5fe4dad1742347edd3c3fbed98b079177e4ea8d544 +https://github.com/hyperledger/besu/releases/download/24.7.1/besu-24.7.1.zip / sha256 e616f8100f026a71a146a33847b40257c279b38085b17bb991df045cccb6f832 + +## 24.7.0 + +### Upcoming Breaking Changes +- Receipt compaction will be enabled by default in a future version of Besu. After this change it will not be possible to downgrade to the previous Besu version. +- PKI-backed QBFT will be removed in a future version of Besu. Other forms of QBFT will remain unchanged. +- --Xbonsai-limit-trie-logs-enabled is deprecated, use --bonsai-limit-trie-logs-enabled instead +- --Xbonsai-trie-logs-pruning-window-size is deprecated, use --bonsai-trie-logs-pruning-window-size instead +- `besu storage x-trie-log` subcommand is deprecated, use `besu storage trie-log` instead + +### Breaking Changes +- `Xp2p-peer-lower-bound` has been removed. [#7247](https://github.com/hyperledger/besu/pull/7247) + +### Additions and Improvements +- Support for eth_maxPriorityFeePerGas [#5658](https://github.com/hyperledger/besu/issues/5658) +- Improve genesis state performance at startup [#6977](https://github.com/hyperledger/besu/pull/6977) +- Enable continuous profiling with default setting [#7006](https://github.com/hyperledger/besu/pull/7006) +- A full and up to date implementation of EOF for Prague [#7169](https://github.com/hyperledger/besu/pull/7169) +- Add Subnet-Based Peer Permissions. [#7168](https://github.com/hyperledger/besu/pull/7168) +- Reduce lock contention on transaction pool when building a block [#7180](https://github.com/hyperledger/besu/pull/7180) +- Update Docker base image to Ubuntu 24.04 [#7251](https://github.com/hyperledger/besu/pull/7251) +- Add LUKSO as predefined network name [#7223](https://github.com/hyperledger/besu/pull/7223) +- Refactored how code, initcode, and max stack size are configured in forks. [#7245](https://github.com/hyperledger/besu/pull/7245) +- Nodes in a permissioned chain maintain (and retry) connections to bootnodes [#7257](https://github.com/hyperledger/besu/pull/7257) +- Promote experimental `besu storage x-trie-log` subcommand to production-ready [#7278](https://github.com/hyperledger/besu/pull/7278) +- Enhanced BFT round-change diagnostics [#7271](https://github.com/hyperledger/besu/pull/7271) + +### Bug fixes +- Validation errors ignored in accounts-allowlist and empty list [#7138](https://github.com/hyperledger/besu/issues/7138) +- Fix "Invalid block detected" for BFT chains using Bonsai DB [#7204](https://github.com/hyperledger/besu/pull/7204) +- Fix "Could not confirm best peer had pivot block" [#7109](https://github.com/hyperledger/besu/issues/7109) +- Fix "Chain Download Halt" [#6884](https://github.com/hyperledger/besu/issues/6884) + +### Download Links +https://github.com/hyperledger/besu/releases/tag/24.7.0 +https://github.com/hyperledger/besu/releases/download/24.7.0/besu-24.7.0.tar.gz / sha256 96cf47defd1d8c10bfc22634e53e3d640eaa81ef58cb0808e5f4265998979530 +https://github.com/hyperledger/besu/releases/download/24.7.0/besu-24.7.0.zip / sha256 7e92e2eb469be197af8c8ca7ac494e7a2e7ee91cbdb02d99ff87fb5209e0c2a0 + + + +## 24.6.0 + +### Breaking Changes +- Java 21 has been enforced as minimum version to build and run Besu. +- With --Xbonsai-limit-trie-logs-enabled by default in this release, historic trie log data will be removed from the database unless sync-mode=FULL. It respects the --bonsai-historical-block-limit setting so shouldn't break any RPCs, but may be breaking if you are accessing this data from the database directly. Can be disabled with --bonsai-limit-trie-logs-enabled=false +- In profile=ENTERPRISE, use sync-mode=FULL (instead of FAST) and data-storage-format=FOREST (instead of BONSAI) [#7186](https://github.com/hyperledger/besu/pull/7186) + - If this breaks your node, you can reset sync-mode=FAST and data-storage-format=BONSAI + +### Upcoming Breaking Changes +- Receipt compaction will be enabled by default in a future version of Besu. After this change it will not be possible to downgrade to the previous Besu version. +- PKI-backed QBFT will be removed in a future version of Besu. Other forms of QBFT will remain unchanged. +- --Xbonsai-limit-trie-logs-enabled is deprecated, use --bonsai-limit-trie-logs-enabled instead +- --Xbonsai-trie-logs-pruning-window-size is deprecated, use --bonsai-trie-logs-pruning-window-size instead + +### Additions and Improvements +- Add two counters to DefaultBlockchain in order to be able to calculate TPS and Mgas/s [#7105](https://github.com/hyperledger/besu/pull/7105) +- Enable --Xbonsai-limit-trie-logs-enabled by default, unless sync-mode=FULL [#7181](https://github.com/hyperledger/besu/pull/7181) +- Promote experimental --Xbonsai-limit-trie-logs-enabled to production-ready, --bonsai-limit-trie-logs-enabled [#7192](https://github.com/hyperledger/besu/pull/7192) +- Promote experimental --Xbonsai-trie-logs-pruning-window-size to production-ready, --bonsai-trie-logs-pruning-window-size [#7192](https://github.com/hyperledger/besu/pull/7192) +- `admin_nodeInfo` JSON/RPC call returns the currently active EVM version [#7127](https://github.com/hyperledger/besu/pull/7127) +- Improve the selection of the most profitable built block [#7174](https://github.com/hyperledger/besu/pull/7174) + +### Bug fixes +- Make `eth_gasPrice` aware of the base fee market [#7102](https://github.com/hyperledger/besu/pull/7102) + +### Download Links +https://github.com/hyperledger/besu/releases/tag/24.6.0 +https://github.com/hyperledger/besu/releases/download/24.6.0/besu-24.6.0.tar.gz / sha256 fa86e5c6873718cd568e3326151ce06957a5e7546b52df79a831ea9e39b857ab +https://github.com/hyperledger/besu/releases/download/24.6.0/besu-24.6.0.zip / sha256 8b2d3a674cd7ead68b9ca68fea21e46d5ec9b278bbadc73f8c13c6a1e1bc0e4d + +## 24.5.2 + +### Upcoming Breaking Changes +- Version 24.5.x will be the last series to support Java 17. Next release after versions 24.5.x will require Java 21 to build and run. +- Receipt compaction will be enabled by default in a future version of Besu. After this change it will not be possible to downgrade to the previous Besu version. +- PKI-backed QBFT will be removed in a future version of Besu. Other forms of QBFT will remain unchanged. + +### Additions and Improvements +- Remove deprecated Goerli testnet [#7049](https://github.com/hyperledger/besu/pull/7049) +- Default bonsai to use full-flat db and code-storage-by-code-hash [#6984](https://github.com/hyperledger/besu/pull/6894) +- New RPC methods miner_setExtraData and miner_getExtraData [#7078](https://github.com/hyperledger/besu/pull/7078) +- Disconnect peers that have multiple discovery ports since they give us bad neighbours [#7089](https://github.com/hyperledger/besu/pull/7089) +- Port Tuweni dns-discovery into Besu. [#7129](https://github.com/hyperledger/besu/pull/7129) + +### Known Issues +- [Frequency: occasional < 10%] Chain download halt. Only affects new syncs (new nodes syncing from scratch). Symptom: Block import halts, despite having a full set of peers and world state downloading finishing. Generally restarting besu will resolve the issue. We are tracking this in [#6884](https://github.com/hyperledger/besu/pull/6884) + +### Bug fixes +- Fix parsing `gasLimit` parameter when its value is > `Long.MAX_VALUE` [#7116](https://github.com/hyperledger/besu/pull/7116) +- Skip validation of withdrawals when importing BFT blocks since withdrawals don't apply to BFT chains [#7115](https://github.com/hyperledger/besu/pull/7115) +- Make `v` abd `yParity` match in type 1 and 2 transactions in JSON-RPC and GraphQL [#7139](https://github.com/hyperledger/besu/pull/7139) + +### Download Links +https://github.com/hyperledger/besu/releases/tag/24.5.2 +https://github.com/hyperledger/besu/releases/download/24.5.2/besu-24.5.2.tar.gz / sha256 4049bf48022ae073065b46e27088399dfb22035e9134ed4ac2c86dd8c5b5fbe9 +https://github.com/hyperledger/besu/releases/download/24.5.2/besu-24.5.2.zip / sha256 23966b501a69e320e8f8f46a3d103ccca45b53f8fee35a6543bd9a260b5784ee + +## 24.5.1 ### Breaking Changes +- RocksDB database metadata format has changed to be more expressive, the migration of an existing metadata file to the new format is automatic at startup. Before performing a downgrade to a previous version it is mandatory to revert to the original format using the subcommand `besu --data-path=/path/to/besu/datadir storage revert-metadata v2-to-v1`. +- BFT networks won't start with SNAP or CHECKPOINT sync (previously Besu would start with this config but quietly fail to sync, so it's now more obvious that it won't work) [#6625](https://github.com/hyperledger/besu/pull/6625), [#6667](https://github.com/hyperledger/besu/pull/6667) +- Forest pruning has been removed, it was deprecated since 24.1.0. In case you are still using it you must now remove any of the following options: `pruning-enabled`, `pruning-blocks-retained` and `pruning-block-confirmations`, from your configuration, and you may want to consider switching to Bonsai. +- Deprecated Goerli testnet has been removed. + +### Upcoming Breaking Changes +- Version 24.5.x will be the last series to support Java 17. Next release after versions 24.5.x will require Java 21 to build and run. +- Receipt compaction will be enabled by default in a future version of Besu. After this change it will not be possible to downgrade to the previous Besu version. +- PKI-backed QBFT will be removed in a future version of Besu. Other forms of QBFT will remain unchanged. + +### Known Issues +- [Frequency: occasional < 10%] Chain download halt. Only affects new syncs (new nodes syncing from scratch). Symptom: Block import halts, despite having a full set of peers and world state downloading finishing. Generally restarting besu will resolve the issue. We are tracking this in [#6884](https://github.com/hyperledger/besu/pull/6884) +- [Frequency: occasional < 10%] Low peer numbers. More likely to occur on testnets (holesky and sepolia) but also can occur on mainnet. Symptom: peer count stays at 0 for an hour or more. Generally restarting besu will resolve the issue. We are tracking this in [#6805](https://github.com/hyperledger/besu/pull/6805) + +### Additions and Improvements +- Update "host allow list" logic to transition from deprecated `host()` method to suggested `authority()` method.[#6878](https://github.com/hyperledger/besu/issues/6878) +- `txpool_besuPendingTransactions`change parameter `numResults` to optional parameter [#6708](https://github.com/hyperledger/besu/pull/6708) +- Extend `Blockchain` service [#6592](https://github.com/hyperledger/besu/pull/6592) +- Add bft-style `blockperiodseconds` transitions to Clique [#6596](https://github.com/hyperledger/besu/pull/6596) +- Add `createemptyblocks` transitions to Clique [#6608](https://github.com/hyperledger/besu/pull/6608) +- RocksDB database metadata refactoring [#6555](https://github.com/hyperledger/besu/pull/6555) +- Make layered txpool aware of `minGasPrice` and `minPriorityFeePerGas` dynamic options [#6611](https://github.com/hyperledger/besu/pull/6611) +- Update commons-compress to 1.26.0 [#6648](https://github.com/hyperledger/besu/pull/6648) +- Update Vert.x to 4.5.4 [#6666](https://github.com/hyperledger/besu/pull/6666) +- Refactor and extend `TransactionPoolValidatorService` [#6636](https://github.com/hyperledger/besu/pull/6636) +- Introduce `TransactionSimulationService` [#6686](https://github.com/hyperledger/besu/pull/6686) +- Transaction call object to accept both `input` and `data` field simultaneously if they are set to equal values [#6702](https://github.com/hyperledger/besu/pull/6702) +- `eth_call` for blob tx allows for empty `maxFeePerBlobGas` [#6731](https://github.com/hyperledger/besu/pull/6731) +- Extend error handling of plugin RPC methods [#6759](https://github.com/hyperledger/besu/pull/6759) +- Added engine_newPayloadV4 and engine_getPayloadV4 methods [#6783](https://github.com/hyperledger/besu/pull/6783) +- Reduce storage size of receipts [#6602](https://github.com/hyperledger/besu/pull/6602) +- Dedicated log marker for invalid txs removed from the txpool [#6826](https://github.com/hyperledger/besu/pull/6826) +- Prevent startup with BONSAI and privacy enabled [#6809](https://github.com/hyperledger/besu/pull/6809) +- Remove deprecated Forest pruning [#6810](https://github.com/hyperledger/besu/pull/6810) +- Experimental Snap Sync Server [#6640](https://github.com/hyperledger/besu/pull/6640) +- Upgrade Reference Tests to 13.2 [#6854](https://github.com/hyperledger/besu/pull/6854) +- Update Web3j dependencies [#6811](https://github.com/hyperledger/besu/pull/6811) +- Add `tx-pool-blob-price-bump` option to configure the price bump percentage required to replace blob transactions (by default 100%) [#6874](https://github.com/hyperledger/besu/pull/6874) +- Log detailed timing of block creation steps [#6880](https://github.com/hyperledger/besu/pull/6880) +- Expose transaction count by type metrics for the layered txpool [#6903](https://github.com/hyperledger/besu/pull/6903) +- Expose bad block events via the BesuEvents plugin API [#6848](https://github.com/hyperledger/besu/pull/6848) +- Add RPC errors metric [#6919](https://github.com/hyperledger/besu/pull/6919/) +- Add `rlp decode` subcommand to decode IBFT/QBFT extraData to validator list [#6895](https://github.com/hyperledger/besu/pull/6895) +- Allow users to specify which plugins are registered [#6700](https://github.com/hyperledger/besu/pull/6700) +- Layered txpool tuning for blob transactions [#6940](https://github.com/hyperledger/besu/pull/6940) + +### Bug fixes +- Fix txpool dump/restore race condition [#6665](https://github.com/hyperledger/besu/pull/6665) +- Make block transaction selection max time aware of PoA transitions [#6676](https://github.com/hyperledger/besu/pull/6676) +- Don't enable the BFT mining coordinator when running sub commands such as `blocks export` [#6675](https://github.com/hyperledger/besu/pull/6675) +- In JSON-RPC return optional `v` fields for type 1 and type 2 transactions [#6762](https://github.com/hyperledger/besu/pull/6762) +- Fix Shanghai/QBFT block import bug when syncing new nodes [#6765](https://github.com/hyperledger/besu/pull/6765) +- Fix to avoid broadcasting full blob txs, instead of only the tx announcement, to a subset of nodes [#6835](https://github.com/hyperledger/besu/pull/6835) +- Snap client fixes discovered during snap server testing [#6847](https://github.com/hyperledger/besu/pull/6847) +- Correctly initialize the txpool as disabled on creation [#6890](https://github.com/hyperledger/besu/pull/6890) +- Fix worldstate download halt when using snap sync during initial sync [#6981](https://github.com/hyperledger/besu/pull/6981) +- Fix chain halt due to peers only partially responding with headers. And worldstate halts caused by a halt in the chain sync [#7027](https://github.com/hyperledger/besu/pull/7027) + +### Download Links +https://github.com/hyperledger/besu/releases/tag/24.5.1 +https://github.com/hyperledger/besu/releases/download/24.5.1/besu-24.5.1.tar.gz / sha256 77e39b21dbd4186136193fc6e832ddc1225eb5078a5ac980fb754b33ad35d554 +https://github.com/hyperledger/besu/releases/download/24.5.1/besu-24.5.1.zip / sha256 13d75b6b22e1303f39fd3eaddf736b24ca150b2bafa7b98fce7c7782e54b213f + +## 24.3.0 + +### Breaking Changes +- SNAP - Snap sync is now the default for named networks [#6530](https://github.com/hyperledger/besu/pull/6530) + - if you want to use the previous default behavior, you'll need to specify `--sync-mode=FAST` +- BONSAI - Default data storage format is now Bonsai [#6536](https://github.com/hyperledger/besu/pull/6536) + - if you had previously used the default (FOREST), at startup you will get an error indicating the mismatch + `Mismatch: DB at '/your-path' is FOREST (Version 1) but config expects BONSAI (Version 2). Please check your config.` + - to fix this mismatch, specify the format explicitly using `--data-storage-format=FOREST` +- Following the OpenMetrics convention, the updated Prometheus client adds the `_total` suffix to every metrics of type counter, with the effect that some existing metrics have been renamed to have this suffix. If you are using the official Besu Grafana dashboard [(available here)](https://grafana.com/grafana/dashboards/16455-besu-full/), just update it to the latest revision, that accepts the old and the new name of the affected metrics. If you have a custom dashboard or use the metrics in other ways, then you need to manually update it to support the new naming. +- The `trace-filter` method in JSON-RPC API now has a default block range limit of 1000, adjustable with `--rpc-max-trace-filter-range` (thanks @alyokaz) [#6446](https://github.com/hyperledger/besu/pull/6446) +- Requesting the Ethereum Node Record (ENR) to acquire the fork id from bonded peers is now enabled by default, so the following change has been made [#5628](https://github.com/hyperledger/besu/pull/5628): +- `--Xfilter-on-enr-fork-id` has been removed. To disable the feature use `--filter-on-enr-fork-id=false`. +- `--engine-jwt-enabled` has been removed. Use `--engine-jwt-disabled` instead. [#6491](https://github.com/hyperledger/besu/pull/6491) +- Release docker images now provided at ghcr.io instead of dockerhub + +### Deprecations +- X_SNAP and X_CHECKPOINT are marked for deprecation and will be removed in 24.6.0 in favor of SNAP and CHECKPOINT [#6405](https://github.com/hyperledger/besu/pull/6405) +- `--Xp2p-peer-lower-bound` is deprecated. [#6501](https://github.com/hyperledger/besu/pull/6501) + +### Upcoming Breaking Changes +- `--Xbonsai-limit-trie-logs-enabled` will be removed. You will need to use `--bonsai-limit-trie-logs-enabled` instead. Additionally, this limit will change to be enabled by default. + - If you do not want the limit enabled (eg you have `--bonsai-historical-block-limit` set < 512), you need to explicitly disable it using `--bonsai-limit-trie-logs-enabled=false` or increase the limit. [#6561](https://github.com/hyperledger/besu/pull/6561) + +### Additions and Improvements +- Upgrade Prometheus and Opentelemetry dependencies [#6422](https://github.com/hyperledger/besu/pull/6422) +- Add `OperationTracer.tracePrepareTransaction`, where the sender account has not yet been altered[#6453](https://github.com/hyperledger/besu/pull/6453) +- Improve the high spec flag by limiting it to a few column families [#6354](https://github.com/hyperledger/besu/pull/6354) +- Log blob count when importing a block via Engine API [#6466](https://github.com/hyperledger/besu/pull/6466) +- Introduce `--Xbonsai-limit-trie-logs-enabled` experimental feature which by default will only retain the latest 512 trie logs, saving about 3GB per week in database growth [#5390](https://github.com/hyperledger/besu/issues/5390) +- Introduce `besu storage x-trie-log prune` experimental offline subcommand which will prune all redundant trie logs except the latest 512 [#6303](https://github.com/hyperledger/besu/pull/6303) +- Improve flat trace generation performance [#6472](https://github.com/hyperledger/besu/pull/6472) +- SNAP and CHECKPOINT sync - early access flag removed so now simply SNAP and CHECKPOINT [#6405](https://github.com/hyperledger/besu/pull/6405) +- X_SNAP and X_CHECKPOINT are marked for deprecation and will be removed in 24.4.0 +- Github Actions based build. +- Introduce caching mechanism to optimize Keccak hash calculations for account storage slots during block processing [#6452](https://github.com/hyperledger/besu/pull/6452) +- Added configuration options for `pragueTime` to genesis file for Prague fork development [#6473](https://github.com/hyperledger/besu/pull/6473) +- Moving trielog storage to RocksDB's blobdb to improve write amplications [#6289](https://github.com/hyperledger/besu/pull/6289) +- Support for `shanghaiTime` fork and Shanghai EVM smart contracts in QBFT/IBFT chains [#6353](https://github.com/hyperledger/besu/pull/6353) +- Change ExecutionHaltReason for contract creation collision case to return ILLEGAL_STATE_CHANGE [#6518](https://github.com/hyperledger/besu/pull/6518) +- Experimental feature `--Xbonsai-code-using-code-hash-enabled` for storing Bonsai code storage by code hash [#6505](https://github.com/hyperledger/besu/pull/6505) +- More accurate column size `storage rocksdb usage` subcommand [#6540](https://github.com/hyperledger/besu/pull/6540) +- Adds `storage rocksdb x-stats` subcommand [#6540](https://github.com/hyperledger/besu/pull/6540) +- New `eth_blobBaseFee`JSON-RPC method [#6581](https://github.com/hyperledger/besu/pull/6581) +- Add blob transaction support to `eth_call` [#6661](https://github.com/hyperledger/besu/pull/6661) +- Add blobs to `eth_feeHistory` [#6679](https://github.com/hyperledger/besu/pull/6679) +- Upgrade reference tests to version 13.1 [#6574](https://github.com/hyperledger/besu/pull/6574) +- Extend `BesuConfiguration` service [#6584](https://github.com/hyperledger/besu/pull/6584) +- Add `ethereum_min_gas_price` and `ethereum_min_priority_fee` metrics to track runtime values of `min-gas-price` and `min-priority-fee` [#6587](https://github.com/hyperledger/besu/pull/6587) +- Option to perform version incompatibility checks when starting Besu. In this first release of the feature, if `--version-compatibility-protection` is set to true it checks that the version of Besu being started is the same or higher than the previous version. [6307](https://github.com/hyperledger/besu/pull/6307) +- Moved account frame warming from GasCalculator into the Call operations [#6557](https://github.com/hyperledger/besu/pull/6557) + +### Bug fixes +- Fix the way an advertised host configured with `--p2p-host` is treated when communicating with the originator of a PING packet [#6225](https://github.com/hyperledger/besu/pull/6225) +- Fix `poa-block-txs-selection-max-time` option that was inadvertently reset to its default after being configured [#6444](https://github.com/hyperledger/besu/pull/6444) +- Fix for tx incorrectly discarded when there is a timeout during block creation [#6563](https://github.com/hyperledger/besu/pull/6563) +- Fix traces so that call gas costing in traces matches other clients traces [#6525](https://github.com/hyperledger/besu/pull/6525) + +### Download Links +https://github.com/hyperledger/besu/releases/tag/24.3.0 +https://github.com/hyperledger/besu/releases/download/24.3.0/besu-24.3.0.tar.gz / sha256 8037ce51bb5bb396d29717a812ea7ff577b0d6aa341d67d1e5b77cbc55b15f84 +https://github.com/hyperledger/besu/releases/download/24.3.0/besu-24.3.0.zip / sha256 41ea2ca734a3b377f43ee178166b5b809827084789378dbbe4e5b52bbd8e0674 + +## 24.1.2 + +### Bug fixes +- Fix ETC Spiral upgrade breach of consensus [#6524](https://github.com/hyperledger/besu/pull/6524) + +### Additions and Improvements +- Adds timestamp to enable Cancun upgrade on mainnet [#6545](https://github.com/hyperledger/besu/pull/6545) +- Github Actions based build.[#6427](https://github.com/hyperledger/besu/pull/6427) + +### Download Links +https://hyperledger.jfrog.io/artifactory/besu-binaries/besu/24.1.2/besu-24.1.2.zip / sha256 9033f300edd81c770d3aff27a29f59dd4b6142a113936886a8f170718e412971 +https://hyperledger.jfrog.io/artifactory/besu-binaries/besu/24.1.2/besu-24.1.2.tar.gz / sha256 082db8cf4fb67527aa0dd757e5d254b3b497f5027c23287f9c0a74a6a743bf08 + +## 24.1.1 + +### Breaking Changes +- New `EXECUTION_HALTED` error returned if there is an error executing or simulating a transaction, with the reason for execution being halted. Replaces the generic `INTERNAL_ERROR` return code in certain cases which some applications may be checking for [#6343](https://github.com/hyperledger/besu/pull/6343) +- The Besu Docker images with `openjdk-latest` tags since 23.10.3 were incorrectly using UID 1001 instead of 1000 for the container's `besu` user. The user now uses 1000 again. Containers created from or migrated to images using UID 1001 will need to chown their persistent database files to UID 1000 (thanks @h4l) [#6360](https://github.com/hyperledger/besu/pull/6360) +- The deprecated `--privacy-onchain-groups-enabled` option has now been removed. Use the `--privacy-flexible-groups-enabled` option instead. [#6411](https://github.com/hyperledger/besu/pull/6411) +- The time that can be spent selecting transactions during block creation is not capped at 5 seconds for PoS and PoW networks, and for PoA networks, at 75% of the block period specified in the genesis. This is to prevent possible DoS attacks in case a single transaction is taking too long to execute, and to have a stable block production rate. This could be a breaking change if an existing network needs to accept transactions that take more time to execute than the newly introduced limit. If it is mandatory for these networks to keep processing these long processing transaction, then the default value of `block-txs-selection-max-time` or `poa-block-txs-selection-max-time` needs to be tuned accordingly. [#6423](https://github.com/hyperledger/besu/pull/6423) ### Deprecations +### Additions and Improvements +- Optimize RocksDB WAL files, allows for faster restart and a more linear disk space utilization [#6328](https://github.com/hyperledger/besu/pull/6328) +- Disable transaction handling when the node is not in sync, to avoid unnecessary transaction validation work [#6302](https://github.com/hyperledger/besu/pull/6302) +- Introduce TransactionEvaluationContext to pass data between transaction selectors and plugin, during block creation [#6381](https://github.com/hyperledger/besu/pull/6381) +- Upgrade dependencies [#6377](https://github.com/hyperledger/besu/pull/6377) +- Upgrade `com.fasterxml.jackson` dependencies [#6378](https://github.com/hyperledger/besu/pull/6378) +- Upgrade Guava dependency [#6396](https://github.com/hyperledger/besu/pull/6396) +- Upgrade Mockito [#6397](https://github.com/hyperledger/besu/pull/6397) +- Upgrade `tech.pegasys.discovery:discovery` [#6414](https://github.com/hyperledger/besu/pull/6414) +- Options to tune the max allowed time that can be spent selecting transactions during block creation are now stable [#6423](https://github.com/hyperledger/besu/pull/6423) +- Support for "pending" in `qbft_getValidatorsByBlockNumber` [#6436](https://github.com/hyperledger/besu/pull/6436) + +### Bug fixes +- INTERNAL_ERROR from `eth_estimateGas` JSON/RPC calls [#6344](https://github.com/hyperledger/besu/issues/6344) +- Fix Besu Docker images with `openjdk-latest` tags since 23.10.3 using UID 1001 instead of 1000 for the `besu` user [#6360](https://github.com/hyperledger/besu/pull/6360) +- Fluent EVM API definition for Tangerine Whistle had incorrect code size validation configured [#6382](https://github.com/hyperledger/besu/pull/6382) +- Correct mining beneficiary for Clique networks in TraceServiceImpl [#6390](https://github.com/hyperledger/besu/pull/6390) +- Fix to gas limit delta calculations used in block production. Besu should now increment or decrement the block gas limit towards its target correctly (thanks @arbora) #6425 +- Ensure Backward Sync waits for initial sync before starting a session [#6455](https://github.com/hyperledger/besu/issues/6455) +- Silence the noisy DNS query errors [#6458](https://github.com/hyperledger/besu/issues/6458) + +### Download Links +https://hyperledger.jfrog.io/artifactory/besu-binaries/besu/24.1.1/besu-24.1.1.zip / sha256 e23c5b790180756964a70dcdd575ee2ed2c2efa79af00bce956d23bd2f7dc67c +https://hyperledger.jfrog.io/artifactory/besu-binaries/besu/24.1.1/besu-24.1.1.tar.gz / sha256 4b0ddd5a25be2df5d2324bff935785eb63e4e3a5f421614ea690bacb5b9cb344 + +### Errata +Note, due to a CI race with the release job, the initial published version of 24.1.1 were overwritten by artifacts generated from the same sources, but differ in their embedded timestamps. The initial SHAs are noted here but are deprecated: +~~https://hyperledger.jfrog.io/artifactory/besu-binaries/besu/24.1.1/besu-24.1.1.zip / sha256 b6b64f939e0bb4937ce90fc647e0a7073ce3e359c10352b502059955070a60c6 +https://hyperledger.jfrog.io/artifactory/besu-binaries/besu/24.1.1/besu-24.1.1.tar.gz / sha256 cfcae04c30769bf338b0740ac65870f9346d3469931bb46cdba3b2f65d311e7a~~ + + +## 24.1.0 + +### Breaking Changes + +### Deprecations +- Forest pruning (`pruning-enabled` option) is deprecated and will be removed soon. To save disk space consider switching to Bonsai data storage format [#6230](https://github.com/hyperledger/besu/pull/6230) + +### Additions and Improvements +- Add error messages on authentication failures with username and password [#6212](https://github.com/hyperledger/besu/pull/6212) +- New `Sequenced` transaction pool. The pool is an evolution of the `legacy` pool and is likely to be more suitable to enterprise or permissioned chains than the `layered` transaction pool. Select to use this pool with `--tx-pool=sequenced`. Supports the same options as the `legacy` pool [#6274](https://github.com/hyperledger/besu/issues/6274) +- Set Ethereum Classic mainnet activation block for Spiral network upgrade [#6267](https://github.com/hyperledger/besu/pull/6267) +- Add custom genesis file name to config overview if specified [#6297](https://github.com/hyperledger/besu/pull/6297) +- Update Gradle plugins and replace unmaintained License Gradle Plugin with the actively maintained Gradle License Report [#6275](https://github.com/hyperledger/besu/pull/6275) +- Optimize RocksDB WAL files, allows for faster restart and a more linear disk space utilization [#6328](https://github.com/hyperledger/besu/pull/6328) +- Add a cache on senders by transaction hash [#6375](https://github.com/hyperledger/besu/pull/6375) + +### Bug fixes +- Hotfix for selfdestruct preimages on bonsai [#6359]((https://github.com/hyperledger/besu/pull/6359) +- Fix trielog shipping issue during self destruct [#6340]((https://github.com/hyperledger/besu/pull/6340) +- mitigation for trielog failure [#6315]((https://github.com/hyperledger/besu/pull/6315) + +### Download Links +https://hyperledger.jfrog.io/artifactory/besu-binaries/besu/24.1.0/besu-24.1.0.zip / sha256 d36c8aeef70f0a516d4c26d3bc696c3e2a671e515c9e6e9475a31fe759e39f64 +https://hyperledger.jfrog.io/artifactory/besu-binaries/besu/24.1.0/besu-24.1.0.tar.gz / sha256 602b04c0729a7b17361d1f0b39f4ce6a2ebe47932165add666560fe594d9ca99 + + +## 23.10.3-hotfix +This is a hotfix for a selfdestruct defect that occurred on mainnet at block [18947893](https://etherscan.io/block/18947893) + +### Bug fixes +- Hotfix for selfdestruct preimages on bonsai [#6359]((https://github.com/hyperledger/besu/pull/6359) +- mitigation for trielog failure [#6315]((https://github.com/hyperledger/besu/pull/6315) + +### Download Links +https://hyperledger.jfrog.io/artifactory/besu-binaries/besu/23.10.3-hotfix/besu-23.10.3-hotfix.zip / sha256 1c37762909858a40eca749fb85b77fb4d1e918f247aff56d518144828bd85378 +https://hyperledger.jfrog.io/artifactory/besu-binaries/besu/23.10.3-hotfix/besu-23.10.3-hotfix.tar.gz / sha256 8e38e9fd0c16e049aa324effc96f9ec31dc06e82ea4995e9dd75d571394667af + + +## 23.10.3 + ### Additions and Improvements - Implement debug_traceCall [#5885](https://github.com/hyperledger/besu/pull/5885) - Transactions that takes too long to evaluate, during block creation, are dropped from the txpool [#6163](https://github.com/hyperledger/besu/pull/6163) @@ -14,11 +450,21 @@ - Allow a transaction selection plugin to specify custom selection results [#6190](https://github.com/hyperledger/besu/pull/6190) - Add `rpc-gas-cap` to allow users to set gas limit to the RPC methods used to simulate transactions[#6156](https://github.com/hyperledger/besu/pull/6156) - Fix the unavailability of `address` field when returning an `Account` entity on GraphQL in case of unreachable world state [#6198](https://github.com/hyperledger/besu/pull/6198) +- Update OpenJ9 Docker image to latest version [#6226](https://github.com/hyperledger/besu/pull/6226) +- Add error messages on authentication failures with username and password [#6212](https://github.com/hyperledger/besu/pull/6212) +- Add `rocksdb usage` to the `storage` subcommand to allow users and dev to check columns families usage [#6185](https://github.com/hyperledger/besu/pull/6185) +- Ethereum Classic Spiral network upgrade [#6078](https://github.com/hyperledger/besu/pull/6078) +- Fix self destruct collision [#6205](https://github.com/hyperledger/besu/pull/6205) +- Mark deleted storage on cleared [#6305](https://github.com/hyperledger/besu/pull/6305) ### Bug fixes - Fix Docker image name clash between Besu and evmtool [#6194](https://github.com/hyperledger/besu/pull/6194) - Fix `logIndex` in `eth_getTransactionReceipt` JSON RPC method [#6206](https://github.com/hyperledger/besu/pull/6206) +### Download Links +https://hyperledger.jfrog.io/artifactory/besu-binaries/besu/23.10.3/besu-23.10.3.zip / sha256 da7ef8a6ceb88d3e327cacddcdb32218d1750b464c14165a74068f6dc6e0871a +https://hyperledger.jfrog.io/artifactory/besu-binaries/besu/23.10.3/besu-23.10.3.tar.gz / sha256 73c834cf32c7bbe255d7d8cc7ca5d1eb0df8430b9114935c8dcf3a675b2acbc2 + ## 23.10.2 ### Breaking Changes @@ -40,6 +486,7 @@ - Force tx replacement price bump to zero when zero base fee market is configured or `--min-gas-price` is set to 0. This allows for easier tx replacement in networks where there is not gas price. [#6079](https://github.com/hyperledger/besu/pull/6079) - Introduce the possibility to limit the time spent selecting pending transactions during block creation, using the new experimental option `Xblock-txs-selection-max-time` on PoS and PoW networks (by default set to 5000ms) or `Xpoa-block-txs-selection-max-time` on PoA networks (by default 75% of the min block time) [#6044](https://github.com/hyperledger/besu/pull/6044) - Remove LowestInvalidNonceCache from `legacy` transaction pool to make it more private networks friendly [#6148](https://github.com/hyperledger/besu/pull/6148) +- Optimization: Delete leftPad when capturing the stack before and after a frame execution [#6102](https://github.com/hyperledger/besu/pull/6102) ### Bug fixes - Upgrade netty to address CVE-2023-44487, CVE-2023-34462 [#6100](https://github.com/hyperledger/besu/pull/6100) @@ -248,7 +695,7 @@ https://hyperledger.jfrog.io/artifactory/besu-binaries/besu/23.4.4/besu-23.4.4.z - Early access - layered transaction pool implementation [#5290](https://github.com/hyperledger/besu/pull/5290) - New RPC method `debug_getRawReceipts` [#5476](https://github.com/hyperledger/besu/pull/5476) - Add TrieLogFactory plugin support [#5440](https://github.com/hyperledger/besu/pull/5440) -- Ignore `min-block-occupancy-ratio` option when on PoS networks, since in some cases, it prevents to have full blocks even if enough transactions are present [#5491](https://github.com/hyperledger/besu/pull/5491) +- Ignore `min-block-occupancy-ratio` option when on PoS networks, since in some cases, it prevents to have full blocks even if enough transactions are present [#5491](https://github.com/hyperledger/besu/pull/5491) ### Bug Fixes - Fix eth_feeHistory response for the case in which blockCount is higher than highestBlock requested. [#5397](https://github.com/hyperledger/besu/pull/5397) @@ -2134,7 +2581,7 @@ Workaround - Limit the number of blocks queried by each `eth_getLogs` call. - Implemented private contract log filters including JSON-RPC methods to interact with private filters. [\#735](https://github.com/hyperledger/besu/pull/735) - Implemented EIP-2315: Simple Subroutines for the EVM [\#717](https://github.com/hyperledger/besu/pull/717) - Implemented Splunk logging. [\#725](https://github.com/hyperledger/besu/pull/725) -- Implemented optional native library encryption. [\#675](https://github.com/hyperledger/besu/pull/675). To enable add `--Xsecp256k1-native-enabled` (for transaciton signatures) and/or `--Xaltbn128-native-enabled` (for altbn128 precomiled contracts) as command line options. +- Implemented optional native library encryption. [\#675](https://github.com/hyperledger/besu/pull/675). To enable add `--Xsecp256k1-native-enabled` (for transaction signatures) and/or `--Xaltbn128-native-enabled` (for altbn128 precomiled contracts) as command line options. ### Bug Fixes @@ -2316,7 +2763,7 @@ Early access features are available features that are not recommended for produc have unstable interfaces. * [Onchain privacy groups](https://besu.hyperledger.org/en/latest/Concepts/Privacy/Onchain-PrivacyGroups/) with add and remove members. - Not being able to to re-add a member to an onchain privacy group is a [known issue](https://github.com/hyperledger/besu/issues/455) + Not being able to re-add a member to an onchain privacy group is a [known issue](https://github.com/hyperledger/besu/issues/455) with the add and remove functionality. ### Known Issues @@ -2442,7 +2889,7 @@ If you have existing private transactions, see [migration details](docs/Private- This can be enabled using the `--rpc-http-api TRACE` CLI flag. There are some philosophical differences between Besu and other implementations that are outlined in [trace_rpc_apis](docs/trace_rpc_apis.md). -- Ability to automatically detect Docker NAT settings from inside the conainter. +- Ability to automatically detect Docker NAT settings from inside the container. The default NAT method (AUTO) can detect this so no user intervention is required to enable this. @@ -2577,10 +3024,7 @@ For compatibility with ETC Agharta upgrade, use 1.3.7 or later. - Performance improvements: * Multithread Websockets to increase throughput [\#231](https://github.com/hyperledger/besu/pull/231) * NewBlockHeaders performance improvement [\#230](https://github.com/hyperledger/besu/pull/230) -- EIP2384 - Ice Age Adustment around Istanbul [\#211](https://github.com/hyperledger/besu/pull/211) -- Documentation updates include: - * [Configuring mining using the Stratum protocol](https://besu.hyperledger.org/en/latest/HowTo/Configure/Configure-Mining/) - * [ETC network command line options](https://besu.hyperledger.org/en/latest/Reference/CLI/CLI-Syntax/#network) +- EIP2384 - Ice Age Adjustment around Istanbul [\#211](https://github.com/hyperledger/besu/pull/211) - Hard Fork Support: * MuirGlacier for Ethereum Mainnet and Ropsten Testnet * Agharta for Kotti and Mordor Testnets @@ -2665,7 +3109,7 @@ For compatibility with ETC Agharta upgrade, use 1.3.7 or later. - Update Governance and Code of Conduct verbiage [\#120](https://github.com/hyperledger/besu/pull/120) - Fix private transaction root mismatch [\#118](https://github.com/hyperledger/besu/pull/118) -- Programatically enforce plugin CLI variable names [\#117](https://github.com/hyperledger/besu/pull/117) +- Programmatically enforce plugin CLI variable names [\#117](https://github.com/hyperledger/besu/pull/117) - Additional unit test for selecting replaced pending transactions [\#116](https://github.com/hyperledger/besu/pull/116) - Only set sync targets that have an estimated height value [\#115](https://github.com/hyperledger/besu/pull/115) - Fix rlpx startup [\#114](https://github.com/hyperledger/besu/pull/114) @@ -2706,20 +3150,10 @@ For compatibility with ETC Agharta upgrade, use 1.3.7 or later. - Store db metadata file in the root data directory. [\#46](https://github.com/hyperledger/besu/pull/46) - Add `--target-gas-limit` command line option. [\#24](https://github.com/hyperledger/besu/pull/24)(thanks to new contributor [cfelde](https://github.com/cfelde)) - Allow private contracts to access public state. [\#9](https://github.com/hyperledger/besu/pull/9) -- Documentation updates include: - - Added [sample load balancer configurations](https://besu.hyperledger.org/en/latest/HowTo/Configure/Configure-HA/Sample-Configuration/) - - Added [`retesteth`](https://besu.hyperledger.org/en/latest/Reference/CLI/CLI-Subcommands/#retesteth) subcommand - - Added [`debug_accountRange`](https://besu.hyperledger.org/en/latest/Reference/API-Methods/#debug_accountrange) JSON-RPC API method - - Clarified purpose of [static nodes](https://besu.hyperledger.org/en/latest/HowTo/Find-and-Connect/Managing-Peers/#static-nodes) - - Added links [Kubernetes reference implementations](https://besu.hyperledger.org/en/latest/HowTo/Deploy/Kubernetes/) - - Added content about [access between private and public states](https://besu.hyperledger.org/en/latest/Concepts/Privacy/Privacy-Groups/#access-between-states) - - Added restriction that [account permissioning cannot be used with random key signing](https://besu.hyperledger.org/en/latest/HowTo/Use-Privacy/Sign-Privacy-Marker-Transactions/). - - Added high availability requirement for [private transaction manager](https://besu.hyperledger.org/en/latest/Concepts/Privacy/Privacy-Overview/#availability) (ie, Orion) - - Added [genesis file reference](https://besu.hyperledger.org/en/latest/Reference/Config-Items/) ### Technical Improvements -- Less verbose synching subscriptions [\#59](https://github.com/hyperledger/besu/pull/59) +- Less verbose syncing subscriptions [\#59](https://github.com/hyperledger/besu/pull/59) - Return enclave key instead of private transaction hash [\#53](https://github.com/hyperledger/besu/pull/53) - Fix mark sweep pruner bugs where nodes that should be kept were being swept [\#50](https://github.com/hyperledger/besu/pull/50) - Clean up BesuConfiguration construction [\#51](https://github.com/hyperledger/besu/pull/51) @@ -2785,11 +3219,6 @@ For compatibility with ETC Agharta upgrade, use 1.3.7 or later. - Added eea\_getTransactionCount Json Rpc [\#1861](https://github.com/PegaSysEng/pantheon/pull/1861) - PrivacyMarkerTransaction to be signed with a randomly generated key [\#1844](https://github.com/PegaSysEng/pantheon/pull/1844) - Implement eth\_getproof JSON RPC API [\#1824](https://github.com/PegaSysEng/pantheon/pull/1824) (thanks to [matkt](https://github.com/matkt)) -- Documentation updates include: - - [Improved navigation](https://docs.pantheon.pegasys.tech/en/latest/) - - [Added permissioning diagram](https://docs.pantheon.pegasys.tech/en/latest/Concepts/Permissioning/Permissioning-Overview/#onchain) - - [Added Responsible Disclosure policy](https://docs.pantheon.pegasys.tech/en/latest/Reference/Responsible-Disclosure/) - - [Added `blocks export` subcommand](https://besu.hyperledger.org/en/latest/Reference/CLI/CLI-Subcommands/#export) ### Technical Improvements - Update the `pantheon blocks export` command usage [\#1887](https://github.com/PegaSysEng/pantheon/pull/1887) (thanks to [matkt](https://github.com/matkt)) @@ -2852,7 +3281,7 @@ For compatibility with ETC Agharta upgrade, use 1.3.7 or later. - Updating Orion to v1.3.2 [#1805](https://github.com/PegaSysEng/pantheon/pull/1805) - Updaated newHeads subscription to emit events only for canonical blocks [#1798](https://github.com/PegaSysEng/pantheon/pull/1798) - Repricing for trie-size-dependent opcodes [#1795](https://github.com/PegaSysEng/pantheon/pull/1795) -- Revised Istanbul Versioning assignemnts [#1794](https://github.com/PegaSysEng/pantheon/pull/1794) +- Revised Istanbul Versioning assignments [#1794](https://github.com/PegaSysEng/pantheon/pull/1794) - Updated RevertReason to return BytesValue [#1793](https://github.com/PegaSysEng/pantheon/pull/1793) - Updated way priv_getPrivacyPrecompileAddress source [#1786](https://github.com/PegaSysEng/pantheon/pull/1786) (thanks to [iikirilov](https://github.com/iikirilov)) - Updated Chain ID opcode to return 0 as default [#1785](https://github.com/PegaSysEng/pantheon/pull/1785) @@ -2897,18 +3326,10 @@ For compatibility with ETC Agharta upgrade, use 1.3.7 or later. - Added JSON-RPC API to report validator block production information [#1687](https://github.com/PegaSysEng/pantheon/pull/1687) (thanks to [matkt](https://github.com/matkt)) - Added Mark Sweep Pruner [#1638](https://github.com/PegaSysEng/pantheon/pull/1638) - Added the Blake2b F compression function as a precompile in Besu [#1614](https://github.com/PegaSysEng/pantheon/pull/1614) (thanks to [iikirilov](https://github.com/iikirilov)) -- Documentation updates include: - - Added CPU requirements [#1734](https://github.com/PegaSysEng/pantheon/pull/1734) - - Added reference to Ansible role [#1733](https://github.com/PegaSysEng/pantheon/pull/1733) - - Updated revert reason example [#1754](https://github.com/PegaSysEng/pantheon/pull/1754) - - Added content on deploying for production [#1774](https://github.com/PegaSysEng/pantheon/pull/1774) - - Updated docker docs for location of data path [#1790](https://github.com/PegaSysEng/pantheon/pull/1790) - - Updated permissiong documentation [#1792](https://github.com/PegaSysEng/pantheon/pull/1792) [#1652](https://github.com/PegaSysEng/pantheon/pull/1652) - Added permissioning webinar in the resources [#1717](https://github.com/PegaSysEng/pantheon/pull/1717) - Add web3.js-eea reference doc [#1617](https://github.com/PegaSysEng/pantheon/pull/1617) - - Updated privacy documentation [#1650](https://github.com/PegaSysEng/pantheon/pull/1650) [#1721](https://github.com/PegaSysEng/pantheon/pull/1721) [#1722](https://github.com/PegaSysEng/pantheon/pull/1722) @@ -2931,9 +3352,7 @@ For compatibility with ETC Agharta upgrade, use 1.3.7 or later. [#1803](https://github.com/PegaSysEng/pantheon/pull/1803) [#1810](https://github.com/PegaSysEng/pantheon/pull/1810) [#1817](https://github.com/PegaSysEng/pantheon/pull/1817) - - Added documentation for getSignerMetrics [#1723](https://github.com/PegaSysEng/pantheon/pull/1723) (thanks to [matkt](https://github.com/matkt)) - Added Java 11+ as a prerequisite for installing Besu using Homebrew. [#1755](https://github.com/PegaSysEng/pantheon/pull/1755) - - Fixed documentation formatting and typos [#1718](https://github.com/PegaSysEng/pantheon/pull/1718) [#1742](https://github.com/PegaSysEng/pantheon/pull/1742) [#1763](https://github.com/PegaSysEng/pantheon/pull/1763) [#1779](https://github.com/PegaSysEng/pantheon/pull/1779) @@ -2963,25 +3382,6 @@ For compatibility with ETC Agharta upgrade, use 1.3.7 or later. - Add eea\_findPrivacyGroup endpoint to Besu [\#1635](https://github.com/PegaSysEng/pantheon/pull/1635) (thanks to [Puneetha17](https://github.com/Puneetha17)) - Updated eea send raw transaction with privacy group ID [\#1611](https://github.com/PegaSysEng/pantheon/pull/1611) (thanks to [iikirilov](https://github.com/iikirilov)) - Added Revert Reason [\#1603](https://github.com/PegaSysEng/pantheon/pull/1603) -- Documentation updates include: - - Added [UPnP content](https://besu.hyperledger.org/en/latest/HowTo/Find-and-Connect/Using-UPnP/) - - Added [load balancer image](https://besu.hyperledger.org/en/stable/) - - Added [revert reason](https://besu.hyperledger.org/en/latest/HowTo/Send-Transactions/Revert-Reason/) - - Added [admin\_changeLogLevel](https://besu.hyperledger.org/en/latest/Reference/API-Methods/#admin_changeloglevel) JSON RPC API (thanks to [matkt](https://github.com/matkt)) - - Updated for [new Docker image](https://besu.hyperledger.org/en/stable/) - - Added [Docker image migration content](https://besu.hyperledger.org/en/latest/HowTo/Get-Started/Migration-Docker/) - - Added [transaction validation content](https://besu.hyperledger.org/en/latest/Concepts/Transactions/Transaction-Validation/) - - Updated [permissioning overview](https://besu.hyperledger.org/en/stable/) for onchain account permissioning - - Updated [quickstart](https://besu.hyperledger.org/en/latest/HowTo/Deploy/Monitoring-Performance/#monitor-node-performance-using-prometheus) to include Prometheus and Grafana - - Added [remote connections limits options](https://besu.hyperledger.org/en/latest/Reference/CLI/CLI-Syntax/#remote-connections-limit-enabled) - - Updated [web3.js-eea reference](https://docs.pantheon.pegasys.tech/en/latest/Reference/web3js-eea-Methods/) to include privacy group methods - - Updated [onchain permissioning to include account permissioning](hhttps://besu.hyperledger.org/en/latest/Concepts/Permissioning/Onchain-Permissioning/) and [Permissioning Management Dapp](https://besu.hyperledger.org/en/latest/Tutorials/Permissioning/Getting-Started-Onchain-Permissioning/#start-the-development-server-for-the-permissioning-management-dapp) - - Added [deployment procedure for Permissioning Management Dapp](https://besu.hyperledger.org/en/stable/) - - Added privacy content for [EEA-compliant and Besu-extended privacy](https://besu.hyperledger.org/en/latest/Concepts/Privacy/Privacy-Groups/) - - Added content on [creating and managing privacy groups](https://besu.hyperledger.org/en/latest/Reference/web3js-eea-Methods/#createprivacygroup) - - Added content on [accessing private and privacy marker transactions](https://besu.hyperledger.org/en/latest/HowTo/Use-Privacy/Access-Private-Transactions/) - - Added content on [system requirements](https://besu.hyperledger.org/en/latest/HowTo/Get-Started/System-Requirements/) - - Added reference to [Besu role on Galaxy to deploy using Ansible](https://besu.hyperledger.org/en/latest/HowTo/Deploy/Ansible/). ### Technical Improvements @@ -3050,12 +3450,6 @@ For compatibility with ETC Agharta upgrade, use 1.3.7 or later. - Print Besu version when starting [\#1593](https://github.com/PegaSysEng/pantheon/pull/1593) - \[PAN-2746\] Add eea\_createPrivacyGroup & eea\_deletePrivacyGroup endpoint [\#1560](https://github.com/PegaSysEng/pantheon/pull/1560) (thanks to [Puneetha17](https://github.com/Puneetha17)) -Documentation updates include: -- Added [readiness and liveness endpoints](https://besu.hyperledger.org/en/latest/HowTo/Interact/APIs/Using-JSON-RPC-API/#readiness-and-liveness-endpoints) -- Added [high availability content](https://besu.hyperledger.org/en/latest/HowTo/Configure/Configure-HA/High-Availability/) -- Added [web3js-eea client library](https://besu.hyperledger.org/en/latest/Tutorials/Quickstarts/Privacy-Quickstart/#clone-eeajs-libraries) -- Added content on [setting CLI options using environment variables](https://besu.hyperledger.org/en/latest/Reference/CLI/CLI-Syntax/#specifying-options) - ### Technical Improvements - Read config from env vars when no config file specified [\#1639](https://github.com/PegaSysEng/pantheon/pull/1639) @@ -3104,16 +3498,6 @@ Documentation updates include: - Add subscribe and unsubscribe count metrics [\#1541](https://github.com/PegaSysEng/pantheon/pull/1541) - Add pivot block metrics [\#1537](https://github.com/PegaSysEng/pantheon/pull/1537) -Documentation updates include: - -- Updated [IBFT 2.0 tutorial](https://besu.hyperledger.org/en/latest/Tutorials/Private-Network/Create-IBFT-Network/) to use network configuration tool -- Added [debug\_traceBlock\* methods](https://besu.hyperledger.org/en/latest/Reference/API-Methods/#debug_traceblock) -- Reorganised [monitoring documentation](https://besu.hyperledger.org/en/latest/HowTo/Deploy/Monitoring-Performance/) -- Added [link to sample Grafana dashboard](https://besu.hyperledger.org/en/latest/HowTo/Deploy/Monitoring-Performance/#monitor-node-performance-using-prometheus) -- Added [note about replacing transactions in transaction pool](https://besu.hyperledger.org/en/latest/Concepts/Transactions/Transaction-Pool/#replacing-transactions-with-same-nonce) -- Updated [example transaction scripts](https://besu.hyperledger.org/en/latest/HowTo/Send-Transactions/Transactions/#example-javascript-scripts) -- Updated [Alethio Ethstats and Explorer documentation](https://besu.hyperledger.org/en/latest/Concepts/AlethioOverview/) - ### Technical Improvements - PAN-2816: Hiding experimental account permissioning cli options [\#1584](https://github.com/PegaSysEng/pantheon/pull/1584) @@ -3149,14 +3533,6 @@ Documentation updates include: ### Additions and Improvements -Documentation updates include: - -- Added [GraphQL options](https://besu.hyperledger.org/en/latest/Reference/CLI/CLI-Syntax/#graphql-http-cors-origins) -- Added [troubleshooting point about illegal reflective access error](https://besu.hyperledger.org/en/latest/HowTo/Troubleshoot/Troubleshooting/#illegal-reflective-access-error-on-startup) -- Added [trusted bootnode behaviour for permissioning](https://besu.hyperledger.org/en/latest/Concepts/Permissioning/Onchain-Permissioning/#bootnodes) -- Added [how to obtain a WS authentication token](https://besu.hyperledger.org/en/latest/HowTo/Interact/APIs/Authentication/#obtaining-an-authentication-token) -- Updated [example scripts and added package.json file for creating signed transactions](https://besu.hyperledger.org/en/latest/HowTo/Send-Transactions/Transactions/) - ### Technical Improvements - Replaced Void datatype with void [\#1530](https://github.com/PegaSysEng/pantheon/pull/1530) @@ -3205,12 +3581,6 @@ Documentation updates include: - Added [`--tx-pool-retention-hours`](https://besu.hyperledger.org/en/latest/Reference/CLI/CLI-Syntax/#tx-pool-retention-hours) [\#1333](https://github.com/PegaSysEng/pantheon/pull/1333) - Added Genesis file support for specifying the maximum stack size. [\#1431](https://github.com/PegaSysEng/pantheon/pull/1431) - Included transaction details when subscribed to Pending transactions [\#1410](https://github.com/PegaSysEng/pantheon/pull/1410) -- Documentation updates include: - - [Added configuration items specified in the genesis file](https://besu.hyperledger.org/en/latest/Reference/Config-Items/#configuration-items) - - [Added pending transaction details subscription](https://besu.hyperledger.org/en/latest/HowTo/Interact/APIs/RPC-PubSub/#pending-transactionss) - - [Added Troubleshooting content](https://besu.hyperledger.org/en/latest/HowTo/Troubleshoot/Troubleshooting/) - - [Added Privacy Quickstart](https://besu.hyperledger.org/en/latest/Tutorials/Quickstarts/Privacy-Quickstart/) - - [Added privacy roadmap](https://github.com/hyperledger/besu/blob/master/ROADMAP.md) ### Technical Improvements @@ -3318,12 +3688,6 @@ Documentation updates include: - [Privacy](https://besu.hyperledger.org/en/latest/Concepts/Privacy/Privacy-Overview/) - [Onchain Permissioning](https://besu.hyperledger.org/en/latest/Concepts/Permissioning/Permissioning-Overview/#onchain) - [Fastsync](https://besu.hyperledger.org/en/latest/Reference/CLI/CLI-Syntax/#fast-sync-min-peers) -- Documentation updates include: - - Added JSON-RPC methods: - - [`txpool_pantheonStatistics`](https://besu.hyperledger.org/en/latest/Reference/API-Methods/#txpool_besustatistics) - - [`net_services`](https://besu.hyperledger.org/en/latest/Reference/API-Methods/#net_services) - - [Updated to indicate Docker image doesn't run on Windows](https://besu.hyperledger.org/en/latest/HowTo/Get-Started/Run-Docker-Image/) - - [Added how to configure a free gas network](https://besu.hyperledger.org/en/latest/HowTo/Configure/FreeGas/) ### Technical Improvements @@ -3377,17 +3741,6 @@ Documentation updates include: ### Additions and Improvements - Notify of dropped messages [\#1156](https://github.com/PegaSysEng/pantheon/pull/1156) -- Documentation updates include: - - Added [Permissioning Overview](https://besu.hyperledger.org/en/latest/Concepts/Permissioning/Permissioning-Overview/) - - Added content on [Network vs Node Configuration](https://besu.hyperledger.org/en/latest/HowTo/Configure/Using-Configuration-File/) - - Updated [RAM requirements](https://besu.hyperledger.org/en/latest/HowTo/Get-Started/System-Requirements/#ram) - - Added [Privacy Overview](https://besu.hyperledger.org/en/latest/Concepts/Privacy/Privacy-Overview/) and [Processing Private Transactions](https://besu.hyperledger.org/en/latest/Concepts/Privacy/Private-Transaction-Processing/) - - Renaming of Ethstats Lite Explorer to [Ethereum Lite Explorer](https://besu.hyperledger.org/en/latest/HowTo/Deploy/Lite-Block-Explorer/#lite-block-explorer-documentation) (thanks to [tzapu](https://github.com/tzapu)) - - Added content on using [Truffle with Besu](https://besu.hyperledger.org/en/latest/HowTo/Develop-Dapps/Truffle/) - - Added [`droppedPendingTransactions` RPC Pub/Sub subscription](https://besu.hyperledger.org/en/latest/HowTo/Interact/APIs/RPC-PubSub/#dropped-transactions) - - Added [`eea_*` JSON-RPC API methods](https://besu.hyperledger.org/en/latest/Reference/API-Methods/#eea-methods) - - Added [architecture diagram](https://besu.hyperledger.org/en/latest/Concepts/ArchitectureOverview/) - - Updated [permissioning CLI options](https://besu.hyperledger.org/en/latest/Reference/CLI/CLI-Syntax/#permissions-accounts-config-file-enabled) and [permissioned network tutorial](https://besu.hyperledger.org/en/stable/) ### Technical Improvements @@ -3442,31 +3795,6 @@ Documentation updates include: - Added PendingTransactions JSON-RPC [\#1043](https://github.com/PegaSysEng/pantheon/pull/1043) (thanks to [EdwinLeeGreene](https://github.com/EdwinLeeGreene)) - Added `admin_nodeInfo` JSON-RPC [\#1012](https://github.com/PegaSysEng/pantheon/pull/1012) - Added `--metrics-category` CLI to only enable select metrics [\#969](https://github.com/PegaSysEng/pantheon/pull/969) -- Documentation updates include: - - Updated endpoints in [Private Network Quickstart](https://besu.hyperledger.org/en/latest/Tutorials/Quickstarts/Private-Network-Quickstart/) (thanks to [laubai](https://github.com/laubai)) - - Updated [documentation contribution guidelines](https://besu.hyperledger.org/en/stable/) - - Added [`admin_removePeer`](https://besu.hyperledger.org/en/latest/Reference/API-Methods/#admin_removepeer) - - Updated [tutorials](https://besu.hyperledger.org/en/latest/Tutorials/Private-Network/Create-Private-Clique-Network/) for printing of enode on startup - - Added [`txpool_pantheonTransactions`](https://besu.hyperledger.org/en/stable/Reference/API-Methods/#txpool_besutransactions) - - Added [Transaction Pool content](https://besu.hyperledger.org/en/latest/Concepts/Transactions/Transaction-Pool/) - - Added [`tx-pool-max-size` CLI option](https://besu.hyperledger.org/en/latest/Reference/CLI/CLI-Syntax/#tx-pool-max-size) - - Updated [developer build instructions to use installDist](https://besu.hyperledger.org/en/stable/) - - Added [Azure quickstart tutorial](https://besu.hyperledger.org/en/latest/Tutorials/Quickstarts/Azure-Private-Network-Quickstart/) - - Enabled copy button in code blocks - - Added [IBFT 1.0](https://besu.hyperledger.org/en/latest/HowTo/Configure/Consensus-Protocols/QuorumIBFT/) - - Added section on using [Geth attach with Besu](https://besu.hyperledger.org/en/latest/HowTo/Interact/APIs/Using-JSON-RPC-API/#geth-console) - - Enabled the edit link doc site to ease external doc contributions - - Added [EthStats docs](https://besu.hyperledger.org/HowTo/Deploy/Lite-Network-Monitor/) (thanks to [baxy](https://github.com/baxy)) - - Updated [Postman collection](https://besu.hyperledger.org/en/latest/HowTo/Interact/APIs/Authentication/#postman) - - Added [`metrics-category` CLI option](https://besu.hyperledger.org/en/latest/Reference/CLI/CLI-Syntax/#metrics-category) - - Added information on [block time and timeout settings](https://besu.hyperledger.org/en/latest/HowTo/Configure/Consensus-Protocols/IBFT/#block-time) for IBFT 2.0 - - Added [`admin_nodeInfo`](https://besu.hyperledger.org/en/latest/Reference/API-Methods/#admin_nodeinfo) - - Added [permissions images](https://besu.hyperledger.org/en/latest/Concepts/Permissioning/Permissioning-Overview/) - - Added permissioning blog to [Resources](https://besu.hyperledger.org/en/latest/Reference/Resources/) - - Updated [Create Permissioned Network](https://besu.hyperledger.org/en/latest/Tutorials/Permissioning/Create-Permissioned-Network/) tutorial to use `export-address` - - Updated [Clique](https://besu.hyperledger.org/en/latest/HowTo/Configure/Consensus-Protocols/Clique/) and [IBFT 2.0](https://besu.hyperledger.org/en/latest/HowTo/Configure/Consensus-Protocols/IBFT/) docs to include complete genesis file - - Updated [Clique tutorial](https://besu.hyperledger.org/en/latest/Tutorials/Private-Network/Create-Private-Clique-Network/) to use `export-address` subcommand - - Added IBFT 2.0 [future message configuration options](https://besu.hyperledger.org/en/latest/HowTo/Configure/Consensus-Protocols/IBFT/#optional-configuration-options) ### Technical Improvements - Fixed so self persists to the whitelist [\#1176](https://github.com/PegaSysEng/pantheon/pull/1176) @@ -3611,8 +3939,6 @@ Public key address export subcommand was missing in 1.0 release. ### Additions and Improvements - Added `public-key export-address` subcommand [\#888](https://github.com/PegaSysEng/pantheon/pull/888) -- Documentation update for the [`public-key export-address`](https://besu.hyperledger.org/en/stable/) subcommand. -- Updated [IBFT 2.0 overview](https://besu.hyperledger.org/en/stable/) to include use of `rlp encode` command and information on setting IBFT 2.0 properties to achieve your desired block time. ## 1.0 @@ -3623,16 +3949,7 @@ Public key address export subcommand was missing in 1.0 release. - Added `rlp encode` subcommand [\#965](https://github.com/PegaSysEng/pantheon/pull/965) - Method to reload permissions file [\#834](https://github.com/PegaSysEng/pantheon/pull/834) - Added rebind mitigation for Websockets. [\#905](https://github.com/PegaSysEng/pantheon/pull/905) -- Support genesis contract code [\#749](https://github.com/PegaSysEng/pantheon/pull/749) (thanks to [kziemianek](https://github.com/kziemianek)). -- Documentation updates include: - - Added details on [port configuration](https://besu.hyperledger.org/en/latest/HowTo/Find-and-Connect/Configuring-Ports/) - - Added [Resources page](https://besu.hyperledger.org/en/latest/Reference/Resources/) linking to Besu blog posts and webinars - - Added [JSON-RPC Authentication](https://besu.hyperledger.org/en/latest/HowTo/Interact/APIs/Authentication/) - - Added [tutorial to create permissioned network](https://besu.hyperledger.org/en/latest/Tutorials/Permissioning/Create-Permissioned-Network/) - - Added [Permissioning](https://besu.hyperledger.org/en/latest/Concepts/Permissioning/Permissioning-Overview/) content - - Added [Permissioning API methods](https://besu.hyperledger.org/en/latest/Reference/API-Methods/#permissioning-methods) - - Added [tutorial to create Clique private network](https://besu.hyperledger.org/en/latest/Tutorials/Private-Network/Create-Private-Clique-Network/) - - Added [tutorial to create IBFT 2.0 private network](https://besu.hyperledger.org/en/latest/Tutorials/Private-Network/Create-IBFT-Network/) +- Support genesis contract code [\#749](https://github.com/PegaSysEng/pantheon/pull/749) (thanks to [kziemianek](https://github.com/kziemianek)) ### Technical Improvements - RoundChangeCertificateValidator requires unique authors [\#997](https://github.com/PegaSysEng/pantheon/pull/997) @@ -3776,7 +4093,7 @@ Public key address export subcommand was missing in 1.0 release. - Updated IbftRound and RoundState APIs to use wrapped messages [\#740](https://github.com/PegaSysEng/pantheon/pull/740) - Exception handling [\#739](https://github.com/PegaSysEng/pantheon/pull/739) - Upgrade dependency versions and build cleanup [\#738](https://github.com/PegaSysEng/pantheon/pull/738) -- Update IbftBlockHeigntManager to accept new message types. [\#737](https://github.com/PegaSysEng/pantheon/pull/737) +- Update IbftBlockHeightManager to accept new message types. [\#737](https://github.com/PegaSysEng/pantheon/pull/737) - Error response handling for permissions APIs [\#736](https://github.com/PegaSysEng/pantheon/pull/736) - IPV6 bootnodes don't work [\#735](https://github.com/PegaSysEng/pantheon/pull/735) - Updated to use tags of pantheon build rather than another repo [\#734](https://github.com/PegaSysEng/pantheon/pull/734) @@ -3790,7 +4107,7 @@ Public key address export subcommand was missing in 1.0 release. ## 0.9.1 -Built and compatible with with JDK8. +Built and compatible with JDK8. ## 0.9 @@ -3853,13 +4170,6 @@ has been updated to use the moved quickstart. - Implement Petersburg hardfork [\#601](https://github.com/PegaSysEng/pantheon/pull/601) - Added private transaction abstraction [\#592](https://github.com/PegaSysEng/pantheon/pull/592) (thanks to [iikirilov](https://github.com/iikirilov)) - Added privacy command line commands [\#584](https://github.com/PegaSysEng/pantheon/pull/584) (thanks to [Puneetha17](https://github.com/Puneetha17)) -- Documentation updates include: - - Updated [Private Network Quickstart tutorial](https://besu.hyperledger.org/en/latest/Tutorials/Quickstarts/Private-Network-Quickstart/) - to use quickstart in `pantheon-quickstart` repository and indicate that the quickstart is not supported on Windows. - - Added IBFT 2.0 [content](https://besu.hyperledger.org/en/latest/HowTo/Configure/Consensus-Protocols/IBFT/) and [JSON RPC API methods](https://besu.hyperledger.org/en/latest/Reference/API-Methods/#ibft-20-methods). - - Added [consensus protocols content](https://besu.hyperledger.org/en/latest/Concepts/Consensus-Protocols/Comparing-PoA/). - - Added content on [events and logs](https://besu.hyperledger.org/en/latest/Concepts/Events-and-Logs/), and [using filters](https://besu.hyperledger.org/en/latest/HowTo/Interact/Filters/Accessing-Logs-Using-JSON-RPC/). - - Added content on integrating with [Prometheus Push Gateway](https://besu.hyperledger.org/en/latest/HowTo/Deploy/Monitoring-Performance/#running-prometheus-with-besu-in-push-mode) ### Technical Improvements @@ -3913,7 +4223,7 @@ has been updated to use the moved quickstart. - Fixed deprecation warnings [\#596](https://github.com/PegaSysEng/pantheon/pull/596) - IBFT Integration Tests - Future Height [\#591](https://github.com/PegaSysEng/pantheon/pull/591) - Added `getNodeData` to `EthPeer` to enable requesting node data [\#589](https://github.com/PegaSysEng/pantheon/pull/589) -- `Blockcreator` to use `parentblock` specified at constuction [\#588](https://github.com/PegaSysEng/pantheon/pull/588) +- `Blockcreator` to use `parentblock` specified at construction [\#588](https://github.com/PegaSysEng/pantheon/pull/588) - Support responding to `GetNodeData` requests [\#587](https://github.com/PegaSysEng/pantheon/pull/587) - IBFT validates block on proposal reception [\#583](https://github.com/PegaSysEng/pantheon/pull/583) - Rework `NewRoundValidator` tests [\#582](https://github.com/PegaSysEng/pantheon/pull/582) @@ -3981,11 +4291,6 @@ When restarting your node with the v0.8.4 Docker image: - Added account whitelisting [\#460](https://github.com/PegaSysEng/pantheon/pull/460) - Added configurable refresh delay for SyncingSubscriptionService on start up [\#383](https://github.com/PegaSysEng/pantheon/pull/383) - Added the Command Line Style Guide [\#530](https://github.com/PegaSysEng/pantheon/pull/530) -- Documentation updates include: - * Migrated to new [documentation site](https://docs.pantheon.pegasys.tech/en/latest/) - * Added [configuration file content](https://besu.hyperledger.org/en/stable/) - * Added [tutorial to create private network](https://besu.hyperledger.org/en/latest/Tutorials/Private-Network/Create-Private-Network/) - * Added content on [enabling non-default APIs](https://besu.hyperledger.org/en/latest/Reference/API-Methods/) ## Technical Improvements @@ -4069,11 +4374,6 @@ Specify `*` or `all` for `--host-whitelist` to effectively disable host protecti - IBFT block mining [\#169](https://github.com/PegaSysEng/pantheon/pull/169) - Added `--goerli` CLI option [\#370](https://github.com/PegaSysEng/pantheon/pull/370) (Thanks to [@Nashatyrev](https://github.com/Nashatyrev)) - Begin capturing metrics to better understand Besu's behaviour [\#326](https://github.com/PegaSysEng/pantheon/pull/326) -- Documentation updates include: - * Added Coding Conventions [\#342](https://github.com/PegaSysEng/pantheon/pull/342) - * Reorganised [Installation documentation](https://github.com/PegaSysEng/pantheon/wiki/Installation) and added [Chocolatey installation](https://github.com/PegaSysEng/pantheon/wiki/Install-Binaries#windows-with-chocolatey) for Windows - * Reorganised [JSON-RPC API documentation](https://github.com/PegaSysEng/pantheon/wiki/JSON-RPC-API) - * Updated [RPC Pub/Sub API documentation](https://github.com/PegaSysEng/pantheon/wiki/RPC-PubSub) ### Technical Improvements @@ -4150,15 +4450,6 @@ Specify `*` or `all` for `--host-whitelist` to effectively disable host protecti - Added `--banned-nodeids` option to prevent connection to specific nodes (PR [#254](https://github.com/PegaSysEng/pantheon/pull/254)) - Send client quitting disconnect message to peers on shutdown (PR [#253](https://github.com/PegaSysEng/pantheon/pull/253)) - Improved error message for port conflict error (PR [#232](https://github.com/PegaSysEng/pantheon/pull/232)) - - Improved documentation by adding the following pages: - * [Getting Started](https://github.com/PegaSysEng/pantheon/wiki/Getting-Started) - * [Network ID and Chain ID](https://github.com/PegaSysEng/pantheon/wiki/NetworkID-And-ChainID) - * [Node Keys](https://github.com/PegaSysEng/pantheon/wiki/Node-Keys) - * [Networking](https://github.com/PegaSysEng/pantheon/wiki/Networking) - * [Accounts for Testing](https://github.com/PegaSysEng/pantheon/wiki/Accounts-for-Testing) - * [Logging](https://github.com/PegaSysEng/pantheon/wiki/Logging) - * [Proof of Authority](https://github.com/PegaSysEng/pantheon/wiki/Proof-of-Authority) - * [Passing JVM Options](https://github.com/PegaSysEng/pantheon/wiki/Passing-JVM-Options) ### Technical Improvements diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eb4a656b0e4..c2d6c57d25a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,7 +28,7 @@ Having the following accounts is necessary for contributing code/issues to Besu. ### Other important information -* [Roadmap](https://wiki.hyperledger.org/display/BESU/Roadmap) +* [Roadmap](https://wiki.hyperledger.org/pages/viewpage.action?pageId=24781786) * [Code of Conduct](https://wiki.hyperledger.org/display/BESU/Code+of+Conduct) * [Governance](https://wiki.hyperledger.org/display/BESU/Governance) diff --git a/NOTICE b/NOTICE new file mode 100644 index 00000000000..af5acfe009e --- /dev/null +++ b/NOTICE @@ -0,0 +1,19 @@ +Hyperledger Besu +Copyright contributors to Hyperledger Besu. + +This product includes software adapted from Tuweni. (https://tuweni.tmio.io/) +Copyright 2023-2024 The Machine Consultancy LLC +Licensed under Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + +The following NOTICE file content is provided by Tuweni: +------------------------------------------------------------ +This product includes code developed under the Apache Tuweni incubation project. + +Copyright 2019-2023 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + +In addition, this product includes software developed by +Copyright 2018-2019 ConsenSys, Inc. +------------------------------------------------------------ diff --git a/NOTICE.md b/NOTICE.md deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/README.md b/README.md index f0a8690d0f2..cd4930fc76b 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # Besu Ethereum Client [![CircleCI](https://circleci.com/gh/hyperledger/besu/tree/main.svg?style=svg)](https://circleci.com/gh/hyperledger/besu/tree/main) - [![Documentation Status](https://readthedocs.org/projects/hyperledger-besu/badge/?version=latest)](https://besu.hyperledger.org/en/latest/?badge=latest) + [![Documentation](https://img.shields.io/github/actions/workflow/status/hyperledger/besu-docs/publish-main-docs.yml?branch=main&label=docs)](https://github.com/hyperledger/besu-docs/actions/workflows/publish-main-docs.yml) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/3174/badge)](https://bestpractices.coreinfrastructure.org/projects/3174) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/hyperledger/besu/blob/main/LICENSE) [![Discord](https://img.shields.io/discord/905194001349627914?logo=Hyperledger&style=plastic)](https://discord.gg/hyperledger) [![Twitter Follow](https://img.shields.io/twitter/follow/HyperledgerBesu)](https://twitter.com/HyperledgerBesu) -[Download](https://hyperledger.jfrog.io/artifactory/besu-binaries/besu/) +[Download](https://github.com/hyperledger/besu/releases) Besu is an Apache 2.0 licensed, MainNet compatible, Ethereum client written in Java. @@ -24,7 +24,7 @@ Besu is an Apache 2.0 licensed, MainNet compatible, Ethereum client written in J Besu issues are tracked [in the github issues tab][Besu Issues]. See our [guidelines](https://wiki.hyperledger.org/display/BESU/Issues) for more details on searching and creating issues. -If you have any questions, queries or comments, [Besu channel on Hyperledger Discord] is the place to find us. +If you have any questions, queries or comments, [Besu channel on Discord] is the place to find us. ## Besu Users @@ -47,7 +47,7 @@ Instructions for how to get started with developing on the Besu codebase. Please * [Checking Out and Building](https://wiki.hyperledger.org/display/BESU/Building+from+source) * [Running Developer Builds](https://wiki.hyperledger.org/display/BESU/Building+from+source#running-developer-builds) * [Code Coverage](https://wiki.hyperledger.org/display/BESU/Code+coverage) -* [Logging](https://wiki.hyperledger.org/display/BESU/Logging) or the [Documentation's Logging section](https://besu.hyperledger.org/en/stable/HowTo/Monitor/Logging/) +* [Logging](https://wiki.hyperledger.org/display/BESU/Logging) or the [Documentation's Logging section](https://besu.hyperledger.org/public-networks/how-to/monitor/logging) ## Release Notes @@ -68,5 +68,5 @@ and YourKit YouMonitor. [Besu Issues]: https://github.com/hyperledger/besu/issues [Besu User Documentation]: https://besu.hyperledger.org -[Besu channel on Hyperledger Discord]: https://discord.gg/hyperledger +[Besu channel on Discord]: https://discord.gg/hyperledger [Contributing Guidelines]: CONTRIBUTING.md diff --git a/SUPPORT.md b/SUPPORT.md index 16a9bccd6ae..a9eb54acb86 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -7,7 +7,7 @@ Welcome to the Besu repository! The following links are a set of guidelines for Having Github, Discord, and Linux Foundation accounts is necessary for obtaining support for Besu through the community channels, wiki and issue management. * If you want to raise an issue, you can do so [on the github issue tab](https://github.com/hyperledger/besu/issues). * Hyperledger Discord requires a [Discord account]. -* The Hyperlegder wiki also requires a [Linux Foundation (LF) account] in order to edit pages. +* The Hyperledger wiki also requires a [Linux Foundation (LF) account] in order to edit pages. ### Useful support links diff --git a/acceptance-tests/dsl/build.gradle b/acceptance-tests/dsl/build.gradle index 7aff9aaffb5..6c9090fe9b9 100644 --- a/acceptance-tests/dsl/build.gradle +++ b/acceptance-tests/dsl/build.gradle @@ -26,7 +26,6 @@ dependencies { implementation project(':testutil') implementation project(':util') - implementation 'com.github.tomakehurst:wiremock-jre8' implementation 'com.google.guava:guava' implementation 'com.google.dagger:dagger' annotationProcessor 'com.google.dagger:dagger-compiler' @@ -45,6 +44,8 @@ dependencies { implementation 'org.web3j:abi' implementation 'org.web3j:besu' implementation 'org.web3j:crypto' + implementation 'org.wiremock:wiremock' implementation 'org.testcontainers:testcontainers' + implementation 'org.junit.jupiter:junit-jupiter' } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/AcceptanceTestBase.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/AcceptanceTestBase.java index 617e0e534cb..5256e71ee30 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/AcceptanceTestBase.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/AcceptanceTestBase.java @@ -26,7 +26,6 @@ import org.hyperledger.besu.tests.acceptance.dsl.condition.login.LoginConditions; import org.hyperledger.besu.tests.acceptance.dsl.condition.net.NetConditions; import org.hyperledger.besu.tests.acceptance.dsl.condition.perm.PermissioningConditions; -import org.hyperledger.besu.tests.acceptance.dsl.condition.priv.PrivConditions; import org.hyperledger.besu.tests.acceptance.dsl.condition.process.ExitedWithCode; import org.hyperledger.besu.tests.acceptance.dsl.condition.txpool.TxPoolConditions; import org.hyperledger.besu.tests.acceptance.dsl.condition.web3.Web3Conditions; @@ -44,12 +43,10 @@ import org.hyperledger.besu.tests.acceptance.dsl.transaction.miner.MinerTransactions; import org.hyperledger.besu.tests.acceptance.dsl.transaction.net.NetTransactions; import org.hyperledger.besu.tests.acceptance.dsl.transaction.perm.PermissioningTransactions; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivacyTransactions; import org.hyperledger.besu.tests.acceptance.dsl.transaction.txpool.TxPoolTransactions; import org.hyperledger.besu.tests.acceptance.dsl.transaction.web3.Web3Transactions; import java.io.BufferedReader; -import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.lang.ProcessBuilder.Redirect; @@ -58,14 +55,16 @@ import java.util.concurrent.Executors; import org.junit.After; -import org.junit.Rule; -import org.junit.rules.TestName; -import org.junit.rules.TestWatcher; -import org.junit.runner.Description; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.slf4j.MDC; +/** + * Superclass for acceptance tests. For now (transition to junit5 is ongoing) this class supports + * junit4 format. + */ +@ExtendWith(AcceptanceTestBaseTestWatcher.class) public class AcceptanceTestBase { private static final Logger LOG = LoggerFactory.getLogger(AcceptanceTestBase.class); @@ -92,8 +91,6 @@ public class AcceptanceTestBase { protected final PermissioningTransactions permissioningTransactions; protected final MinerTransactions minerTransactions; protected final Web3Conditions web3; - protected final PrivConditions priv; - protected final PrivacyTransactions privacyTransactions; protected final TxPoolConditions txPoolConditions; protected final TxPoolTransactions txPoolTransactions; protected final ExitedWithCode exitedSuccessfully; @@ -108,10 +105,8 @@ protected AcceptanceTestBase() { bftTransactions = new BftTransactions(); accountTransactions = new AccountTransactions(accounts); permissioningTransactions = new PermissioningTransactions(); - privacyTransactions = new PrivacyTransactions(); contractTransactions = new ContractTransactions(); minerTransactions = new MinerTransactions(); - blockchain = new Blockchain(ethTransactions); clique = new CliqueConditions(ethTransactions, cliqueTransactions); eth = new EthConditions(ethTransactions); @@ -120,7 +115,6 @@ protected AcceptanceTestBase() { net = new NetConditions(new NetTransactions()); cluster = new Cluster(net); perm = new PermissioningConditions(permissioningTransactions); - priv = new PrivConditions(privacyTransactions); admin = new AdminConditions(adminTransactions); web3 = new Web3Conditions(new Web3Transactions()); besu = new BesuNodeFactory(); @@ -131,8 +125,6 @@ protected AcceptanceTestBase() { exitedSuccessfully = new ExitedWithCode(0); } - @Rule public final TestName name = new TestName(); - @After public void tearDownAcceptanceTestBase() { reportMemory(); @@ -178,49 +170,6 @@ private void printOutput(final Process process) { } } - @Rule - public TestWatcher logEraser = - new TestWatcher() { - - @Override - protected void starting(final Description description) { - MDC.put("test", description.getMethodName()); - MDC.put("class", description.getClassName()); - - final String errorMessage = "Uncaught exception in thread \"{}\""; - Thread.currentThread() - .setUncaughtExceptionHandler( - (thread, error) -> LOG.error(errorMessage, thread.getName(), error)); - Thread.setDefaultUncaughtExceptionHandler( - (thread, error) -> LOG.error(errorMessage, thread.getName(), error)); - } - - @Override - protected void failed(final Throwable e, final Description description) { - // add the result at the end of the log so it is self-sufficient - LOG.error( - "=========================================================================================="); - LOG.error("Test failed. Reported Throwable at the point of failure:", e); - LOG.error(e.getMessage()); - } - - @Override - protected void succeeded(final Description description) { - // if so configured, delete logs of successful tests - if (!Boolean.getBoolean("acctests.keepLogsOfPassingTests")) { - String pathname = - "build/acceptanceTestLogs/" - + description.getClassName() - + "." - + description.getMethodName() - + ".log"; - LOG.info("Test successful, deleting log at {}", pathname); - File file = new File(pathname); - file.delete(); - } - } - }; - protected void waitForBlockHeight(final Node node, final long blockchainHeight) { WaitUtils.waitFor( 120, @@ -228,4 +177,11 @@ protected void waitForBlockHeight(final Node node, final long blockchainHeight) assertThat(node.execute(ethTransactions.blockNumber())) .isGreaterThanOrEqualTo(BigInteger.valueOf(blockchainHeight))); } + + @Test + public void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/AcceptanceTestBaseJunit5.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/AcceptanceTestBaseJunit5.java new file mode 100644 index 00000000000..4f3046c1abf --- /dev/null +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/AcceptanceTestBaseJunit5.java @@ -0,0 +1,200 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.dsl; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.tests.acceptance.dsl.account.Accounts; +import org.hyperledger.besu.tests.acceptance.dsl.blockchain.Blockchain; +import org.hyperledger.besu.tests.acceptance.dsl.condition.admin.AdminConditions; +import org.hyperledger.besu.tests.acceptance.dsl.condition.bft.BftConditions; +import org.hyperledger.besu.tests.acceptance.dsl.condition.clique.CliqueConditions; +import org.hyperledger.besu.tests.acceptance.dsl.condition.eth.EthConditions; +import org.hyperledger.besu.tests.acceptance.dsl.condition.login.LoginConditions; +import org.hyperledger.besu.tests.acceptance.dsl.condition.net.NetConditions; +import org.hyperledger.besu.tests.acceptance.dsl.condition.perm.PermissioningConditions; +import org.hyperledger.besu.tests.acceptance.dsl.condition.process.ExitedWithCode; +import org.hyperledger.besu.tests.acceptance.dsl.condition.txpool.TxPoolConditions; +import org.hyperledger.besu.tests.acceptance.dsl.condition.web3.Web3Conditions; +import org.hyperledger.besu.tests.acceptance.dsl.contract.ContractVerifier; +import org.hyperledger.besu.tests.acceptance.dsl.node.Node; +import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.Cluster; +import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.BesuNodeFactory; +import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.permissioning.PermissionedNodeBuilder; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.account.AccountTransactions; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.admin.AdminTransactions; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.bft.BftTransactions; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.clique.CliqueTransactions; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.contract.ContractTransactions; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.eth.EthTransactions; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.miner.MinerTransactions; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.net.NetTransactions; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.perm.PermissioningTransactions; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.txpool.TxPoolTransactions; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.web3.Web3Transactions; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.math.BigInteger; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.apache.logging.log4j.ThreadContext; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.api.extension.ExtendWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Superclass for acceptance tests. For now (transition to junit5 is ongoing) this class supports + * junit5 format. Once the transition is complete, this class can be removed and recombined with + * AcceptanceTestBase (original). + */ +@ExtendWith(AcceptanceTestBaseTestWatcher.class) +public class AcceptanceTestBaseJunit5 { + + private static final Logger LOG = LoggerFactory.getLogger(AcceptanceTestBaseJunit5.class); + + protected final Accounts accounts; + protected final AccountTransactions accountTransactions; + protected final AdminConditions admin; + protected final AdminTransactions adminTransactions; + protected final Blockchain blockchain; + protected final CliqueConditions clique; + protected final CliqueTransactions cliqueTransactions; + protected final Cluster cluster; + protected final ContractVerifier contractVerifier; + protected final ContractTransactions contractTransactions; + protected final EthConditions eth; + protected final EthTransactions ethTransactions; + protected final BftTransactions bftTransactions; + protected final BftConditions bft; + protected final LoginConditions login; + protected final NetConditions net; + protected final BesuNodeFactory besu; + protected final PermissioningConditions perm; + protected final PermissionedNodeBuilder permissionedNodeBuilder; + protected final PermissioningTransactions permissioningTransactions; + protected final MinerTransactions minerTransactions; + protected final Web3Conditions web3; + protected final TxPoolConditions txPoolConditions; + protected final TxPoolTransactions txPoolTransactions; + protected final ExitedWithCode exitedSuccessfully; + + private final ExecutorService outputProcessorExecutor = Executors.newCachedThreadPool(); + + protected AcceptanceTestBaseJunit5() { + ethTransactions = new EthTransactions(); + accounts = new Accounts(ethTransactions); + adminTransactions = new AdminTransactions(); + cliqueTransactions = new CliqueTransactions(); + bftTransactions = new BftTransactions(); + accountTransactions = new AccountTransactions(accounts); + permissioningTransactions = new PermissioningTransactions(); + contractTransactions = new ContractTransactions(); + minerTransactions = new MinerTransactions(); + blockchain = new Blockchain(ethTransactions); + clique = new CliqueConditions(ethTransactions, cliqueTransactions); + eth = new EthConditions(ethTransactions); + bft = new BftConditions(bftTransactions); + login = new LoginConditions(); + net = new NetConditions(new NetTransactions()); + cluster = new Cluster(net); + perm = new PermissioningConditions(permissioningTransactions); + admin = new AdminConditions(adminTransactions); + web3 = new Web3Conditions(new Web3Transactions()); + besu = new BesuNodeFactory(); + txPoolTransactions = new TxPoolTransactions(); + txPoolConditions = new TxPoolConditions(txPoolTransactions); + contractVerifier = new ContractVerifier(accounts.getPrimaryBenefactor()); + permissionedNodeBuilder = new PermissionedNodeBuilder(); + exitedSuccessfully = new ExitedWithCode(0); + } + + @BeforeEach + public void setUp(final TestInfo testInfo) { + // log4j is configured to create a file per test + // build/acceptanceTestLogs/${ctx:class}.${ctx:test}.log + ThreadContext.put("class", this.getClass().getSimpleName()); + ThreadContext.put("test", testInfo.getTestMethod().get().getName()); + } + + @AfterEach + public void tearDownAcceptanceTestBase() { + reportMemory(); + cluster.close(); + } + + public void reportMemory() { + String os = System.getProperty("os.name"); + String[] command = null; + if (os.contains("Linux")) { + command = new String[] {"/usr/bin/top", "-n", "1", "-o", "%MEM", "-b", "-c", "-w", "180"}; + } + if (os.contains("Mac")) { + command = new String[] {"/usr/bin/top", "-l", "1", "-o", "mem", "-n", "20"}; + } + if (command != null) { + LOG.info("Memory usage at end of test:"); + final ProcessBuilder processBuilder = + new ProcessBuilder(command) + .redirectErrorStream(true) + .redirectInput(ProcessBuilder.Redirect.INHERIT); + try { + final Process memInfoProcess = processBuilder.start(); + outputProcessorExecutor.execute(() -> printOutput(memInfoProcess)); + memInfoProcess.waitFor(); + LOG.debug("Memory info process exited with code {}", memInfoProcess.exitValue()); + } catch (final Exception e) { + LOG.warn("Error running memory information process", e); + } + } else { + LOG.info("Don't know how to report memory for OS {}", os); + } + } + + private void printOutput(final Process process) { + try (final BufferedReader in = + new BufferedReader(new InputStreamReader(process.getInputStream(), UTF_8))) { + String line = in.readLine(); + while (line != null) { + LOG.info(line); + line = in.readLine(); + } + } catch (final IOException e) { + LOG.warn("Failed to read output from memory information process: ", e); + } + } + + protected void waitForBlockHeight(final Node node, final long blockchainHeight) { + WaitUtils.waitFor( + 120, + () -> + assertThat(node.execute(ethTransactions.blockNumber())) + .isGreaterThanOrEqualTo(BigInteger.valueOf(blockchainHeight))); + } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } +} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/AcceptanceTestBaseTestWatcher.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/AcceptanceTestBaseTestWatcher.java new file mode 100644 index 00000000000..5a75413bd00 --- /dev/null +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/AcceptanceTestBaseTestWatcher.java @@ -0,0 +1,57 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.dsl; + +import java.io.File; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestWatcher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AcceptanceTestBaseTestWatcher implements TestWatcher { + private static final Logger LOG = LoggerFactory.getLogger(AcceptanceTestBaseTestWatcher.class); + + @Override + public void testFailed(final ExtensionContext extensionContext, final Throwable e) { + // add the result at the end of the log, so it is self-sufficient + LOG.error( + "=========================================================================================="); + LOG.error("Test failed. Reported Throwable at the point of failure:", e); + LOG.error(e.getMessage()); + } + + @Override + public void testSuccessful(final ExtensionContext extensionContext) { + // if so configured, delete logs of successful tests + if (!Boolean.getBoolean("acctests.keepLogsOfPassingTests")) { + try { + // log4j is configured to create a file per test + // build/acceptanceTestLogs/${ctx:class}.${ctx:test}.log + String pathname = + "build/acceptanceTestLogs/" + + extensionContext.getTestClass().get().getSimpleName() + + "." + + extensionContext.getTestMethod().get().getName() + + ".log"; + LOG.info("Test successful, deleting log at {}", pathname); + final File file = new File(pathname); + file.delete(); + } catch (final Exception e) { + LOG.error("could not delete test file", e); + } + } + } +} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/admin/ExpectHasPeer.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/admin/ExpectHasPeer.java index 58f91c8e0a3..6632f16b855 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/admin/ExpectHasPeer.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/admin/ExpectHasPeer.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.tests.acceptance.dsl.condition.admin; import static org.assertj.core.api.Assertions.assertThat; diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/admin/ExpectNotHavePeer.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/admin/ExpectNotHavePeer.java index 64f06bec4c1..2b6189b6bfe 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/admin/ExpectNotHavePeer.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/admin/ExpectNotHavePeer.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.tests.acceptance.dsl.condition.admin; import static org.assertj.core.api.Assertions.assertThat; diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/blockchain/ExpectBeneficiary.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/blockchain/ExpectBeneficiary.java index 25abc078287..fe765cbeaec 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/blockchain/ExpectBeneficiary.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/blockchain/ExpectBeneficiary.java @@ -14,7 +14,7 @@ */ package org.hyperledger.besu.tests.acceptance.dsl.condition.blockchain; -import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import org.hyperledger.besu.tests.acceptance.dsl.WaitUtils; import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/blockchain/ExpectBlockNumberAbove.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/blockchain/ExpectBlockNumberAbove.java index 60fa6ad5460..6919581bf6c 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/blockchain/ExpectBlockNumberAbove.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/blockchain/ExpectBlockNumberAbove.java @@ -14,7 +14,7 @@ */ package org.hyperledger.besu.tests.acceptance.dsl.condition.blockchain; -import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import org.hyperledger.besu.tests.acceptance.dsl.WaitUtils; import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/clique/ExpectNonceVote.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/clique/ExpectNonceVote.java index 6882890c140..1e8f4b8a99f 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/clique/ExpectNonceVote.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/clique/ExpectNonceVote.java @@ -14,7 +14,7 @@ */ package org.hyperledger.besu.tests.acceptance.dsl.condition.clique; -import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.tests.acceptance.dsl.condition.clique.ExpectNonceVote.CLIQUE_NONCE_VOTE.AUTH; import org.hyperledger.besu.tests.acceptance.dsl.WaitUtils; diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/clique/ExpectedBlockHasProposer.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/clique/ExpectedBlockHasProposer.java index 53b166c7149..4abba67a0d5 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/clique/ExpectedBlockHasProposer.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/clique/ExpectedBlockHasProposer.java @@ -14,7 +14,7 @@ */ package org.hyperledger.besu.tests.acceptance.dsl.condition.clique; -import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import org.hyperledger.besu.consensus.clique.CliqueBlockHeaderFunctions; import org.hyperledger.besu.consensus.clique.CliqueExtraData; diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/eth/EthConditions.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/eth/EthConditions.java index c4ee1347278..d3a86bafa86 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/eth/EthConditions.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/eth/EthConditions.java @@ -77,6 +77,10 @@ public Condition miningStatus(final boolean isMining) { return new MiningStatusCondition(transactions.mining(), isMining); } + public Condition syncingStatus(final boolean isSyncing) { + return new SyncingStatusCondition(transactions.syncing(), isSyncing); + } + public Condition expectNewPendingTransactions( final BigInteger filterId, final List transactionHashes) { return new NewPendingTransactionFilterChangesCondition( diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/eth/SyncingStatusCondition.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/eth/SyncingStatusCondition.java new file mode 100644 index 00000000000..47cd39fb922 --- /dev/null +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/eth/SyncingStatusCondition.java @@ -0,0 +1,40 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.dsl.condition.eth; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.tests.acceptance.dsl.WaitUtils; +import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; +import org.hyperledger.besu.tests.acceptance.dsl.node.Node; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.eth.EthSyncingTransaction; + +public class SyncingStatusCondition implements Condition { + + private final EthSyncingTransaction transaction; + private final boolean syncingMiningStatus; + + public SyncingStatusCondition( + final EthSyncingTransaction transaction, final boolean syncingStatus) { + this.transaction = transaction; + this.syncingMiningStatus = syncingStatus; + } + + @Override + public void verify(final Node node) { + WaitUtils.waitFor( + 10, () -> assertThat(node.execute(transaction)).isEqualTo(syncingMiningStatus)); + } +} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/perm/AllowListContainsKeyAndValue.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/perm/AllowListContainsKeyAndValue.java index a2ffc9b36da..0913fd227f3 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/perm/AllowListContainsKeyAndValue.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/perm/AllowListContainsKeyAndValue.java @@ -24,11 +24,16 @@ import java.nio.file.Path; import java.util.Collection; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class AllowListContainsKeyAndValue implements Condition { private final ALLOWLIST_TYPE allowlistType; private final Collection allowlistValues; private final Path configFilePath; + private static final Logger LOG = LoggerFactory.getLogger(AllowListContainsKeyAndValue.class); + public AllowListContainsKeyAndValue( final ALLOWLIST_TYPE allowlistType, final Collection allowlistValues, @@ -47,6 +52,7 @@ public void verify(final Node node) { allowlistType, allowlistValues, configFilePath); } catch (final Exception e) { result = false; + LOG.error("Error verifying allowlist contains key and value", e); } assertThat(result).isTrue(); } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/EeaSendRawTransactionSuccess.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/EeaSendRawTransactionSuccess.java deleted file mode 100644 index 9aef5825ea4..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/EeaSendRawTransactionSuccess.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.condition.priv; - -import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; -import org.hyperledger.besu.tests.acceptance.dsl.node.Node; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.EeaSendRawTransactionTransaction; - -public class EeaSendRawTransactionSuccess implements Condition { - - private final EeaSendRawTransactionTransaction sendRawTransactionTransaction; - - public EeaSendRawTransactionSuccess( - final EeaSendRawTransactionTransaction sendRawTransactionTransaction) { - this.sendRawTransactionTransaction = sendRawTransactionTransaction; - } - - @Override - public void verify(final Node node) { - final Hash transactionHash = node.execute(sendRawTransactionTransaction); - assertThat(transactionHash).isNotNull(); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/ExpectJsonRpcError.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/ExpectJsonRpcError.java deleted file mode 100644 index a642a2731c9..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/ExpectJsonRpcError.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.condition.priv; - -import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; - -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; -import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; -import org.hyperledger.besu.tests.acceptance.dsl.node.Node; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import org.assertj.core.api.Assertions; -import org.web3j.protocol.exceptions.ClientConnectionException; - -public class ExpectJsonRpcError implements Condition { - - private final Transaction transaction; - private final JsonRpcError error; - - public ExpectJsonRpcError(final Transaction transaction, final JsonRpcError error) { - this.transaction = transaction; - this.error = error; - } - - public ExpectJsonRpcError(final Transaction transaction, final RpcErrorType error) { - this(transaction, new JsonRpcError(error)); - } - - @Override - public void verify(final Node node) { - try { - node.execute(transaction); - failBecauseExceptionWasNotThrown(ClientConnectionException.class); - } catch (final Exception e) { - Assertions.assertThat(e) - .isInstanceOf(RuntimeException.class) - .hasMessageContaining(error.getMessage()); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivConditions.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivConditions.java deleted file mode 100644 index a9a165c68b8..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivConditions.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.condition.priv; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; -import org.hyperledger.besu.ethereum.privacy.PrivateTransaction; -import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivacyTransactions; - -import java.util.List; - -public class PrivConditions { - - private final PrivacyTransactions transactions; - - public PrivConditions(final PrivacyTransactions transactions) { - this.transactions = transactions; - } - - public Condition getPrivacyPrecompileAddress(final Address precompileAddress) { - return new PrivGetPrivacyPrecompileAddressSuccess( - transactions.getPrivacyPrecompileAddress(), precompileAddress); - } - - public Condition getPrivateTransaction( - final Hash transactionHash, final PrivateTransaction privateTransaction) { - return new PrivGetPrivateTransactionSuccess( - transactions.getPrivateTransaction(transactionHash), privateTransaction); - } - - public Condition getPrivateTransactionReturnsNull(final Hash transactionHash) { - return new PrivGetPrivateTransactionReturnsNull( - transactions.getPrivateTransaction(transactionHash)); - } - - public Condition createPrivacyGroup( - final List addresses, - final String groupName, - final String groupDescription, - final String groupId) { - return new PrivCreatePrivacyGroupSuccess( - transactions.createPrivacyGroup(addresses, groupName, groupDescription), groupId); - } - - public Condition deletePrivacyGroup(final String groupId) { - return new PrivDeletePrivacyGroupSuccess(transactions.deletePrivacyGroup(groupId), groupId); - } - - public Condition findPrivacyGroup(final int numGroups, final String... groupMembers) { - return new PrivFindPrivacyGroupSuccess(transactions.findPrivacyGroup(groupMembers), numGroups); - } - - public Condition eeaSendRawTransaction(final String transaction) { - return new EeaSendRawTransactionSuccess(transactions.sendRawTransaction(transaction)); - } - - public Condition distributeRawTransaction( - final String transactionRLP, final String enclaveResponseKey) { - return new PrivDistributeRawTransactionSuccess( - transactions.distributeRawTransaction(transactionRLP), enclaveResponseKey); - } - - public Condition getTransactionCount( - final String accountAddress, - final String privacyGroupId, - final int expectedTransactionCount) { - return new PrivGetTransactionCountSuccess( - transactions.getTransactionCount(accountAddress, privacyGroupId), expectedTransactionCount); - } - - public Condition getEeaTransactionCount( - final String accountAddress, - final String privateFrom, - final String[] privateFor, - final int expectedTransactionCount) { - return new PrivGetEeaTransactionCountSuccess( - transactions.getEeaTransactionCount(accountAddress, privateFrom, privateFor), - expectedTransactionCount); - } - - public Condition getSuccessfulTransactionReceipt(final Hash transactionHash) { - return new PrivGetExpectedSuccessfulTransactionReceipt( - transactions.getTransactionReceipt(transactionHash)); - } - - public Condition getFailedTransactionReceipt(final Hash transactionHash) { - return new PrivGetExpectedFailedTransactionReceipt( - transactions.getTransactionReceipt(transactionHash)); - } - - public Condition getInvalidTransactionReceipt(final Hash transactionHash) { - return new PrivGetExpectedInvalidTransactionReceipt( - transactions.getTransactionReceipt(transactionHash)); - } - - public Condition multiTenancyValidationFail( - final Transaction transaction, final RpcErrorType error) { - return new ExpectJsonRpcError(transaction, error); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivCreatePrivacyGroupSuccess.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivCreatePrivacyGroupSuccess.java deleted file mode 100644 index c507ca4b50f..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivCreatePrivacyGroupSuccess.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.condition.priv; - -import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; -import org.hyperledger.besu.tests.acceptance.dsl.node.Node; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivCreatePrivacyGroupTransaction; - -public class PrivCreatePrivacyGroupSuccess implements Condition { - - private final PrivCreatePrivacyGroupTransaction transaction; - private final String groupId; - - public PrivCreatePrivacyGroupSuccess( - final PrivCreatePrivacyGroupTransaction transaction, final String groupId) { - this.transaction = transaction; - this.groupId = groupId; - } - - @Override - public void verify(final Node node) { - final String result = node.execute(transaction); - assertThat(result).isEqualTo(groupId); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivDeletePrivacyGroupSuccess.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivDeletePrivacyGroupSuccess.java deleted file mode 100644 index b2cd4a40ce5..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivDeletePrivacyGroupSuccess.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.condition.priv; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; -import org.hyperledger.besu.tests.acceptance.dsl.node.Node; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivDeletePrivacyGroupTransaction; - -public class PrivDeletePrivacyGroupSuccess implements Condition { - - private final PrivDeletePrivacyGroupTransaction transaction; - private final String groupId; - - public PrivDeletePrivacyGroupSuccess( - final PrivDeletePrivacyGroupTransaction transaction, final String groupId) { - this.transaction = transaction; - this.groupId = groupId; - } - - @Override - public void verify(final Node node) { - final String result = node.execute(transaction); - assertThat(result).isEqualTo(groupId); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivDistributeRawTransactionSuccess.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivDistributeRawTransactionSuccess.java deleted file mode 100644 index 4184102cf5a..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivDistributeRawTransactionSuccess.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.condition.priv; - -import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; -import org.hyperledger.besu.tests.acceptance.dsl.node.Node; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivDistributeRawTransactionTransaction; - -public class PrivDistributeRawTransactionSuccess implements Condition { - - private final PrivDistributeRawTransactionTransaction sendRawTransactionTransaction; - private final String enclaveResponseKey; - - public PrivDistributeRawTransactionSuccess( - final PrivDistributeRawTransactionTransaction sendRawTransactionTransaction, - final String enclaveResponseKey) { - this.sendRawTransactionTransaction = sendRawTransactionTransaction; - this.enclaveResponseKey = enclaveResponseKey; - } - - @Override - public void verify(final Node node) { - final String result = node.execute(sendRawTransactionTransaction); - assertThat(result).isNotNull(); - assertThat(result).isInstanceOf(String.class); - assertThat(result).isEqualTo(enclaveResponseKey); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivFindPrivacyGroupSuccess.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivFindPrivacyGroupSuccess.java deleted file mode 100644 index f4a069a243f..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivFindPrivacyGroupSuccess.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.condition.priv; - -import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; - -import org.hyperledger.besu.enclave.types.PrivacyGroup; -import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; -import org.hyperledger.besu.tests.acceptance.dsl.node.Node; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivFindPrivacyGroupTransaction; - -public class PrivFindPrivacyGroupSuccess implements Condition { - - private final PrivFindPrivacyGroupTransaction transaction; - private final int numGroups; - - public PrivFindPrivacyGroupSuccess( - final PrivFindPrivacyGroupTransaction transaction, final int numGroups) { - this.transaction = transaction; - this.numGroups = numGroups; - } - - @Override - public void verify(final Node node) { - final PrivacyGroup[] privacyGroups = node.execute(transaction); - assertThat(privacyGroups).hasSize(numGroups); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivGetEeaTransactionCountSuccess.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivGetEeaTransactionCountSuccess.java deleted file mode 100644 index def5502d98c..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivGetEeaTransactionCountSuccess.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.condition.priv; - -import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; -import org.hyperledger.besu.tests.acceptance.dsl.node.Node; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivGetEeaTransactionCountTransaction; - -public class PrivGetEeaTransactionCountSuccess implements Condition { - - private final PrivGetEeaTransactionCountTransaction privGetEeaTransactionCountTransaction; - private final int expectedTransactionCount; - - public PrivGetEeaTransactionCountSuccess( - final PrivGetEeaTransactionCountTransaction privGetEeaTransactionCountTransaction, - final int expectedTransactionCount) { - this.privGetEeaTransactionCountTransaction = privGetEeaTransactionCountTransaction; - this.expectedTransactionCount = expectedTransactionCount; - } - - @Override - public void verify(final Node node) { - final int result = node.execute(privGetEeaTransactionCountTransaction); - assertThat(result).isNotNull(); - assertThat(result).isInstanceOf(Integer.class); - assertThat(result).isEqualTo(expectedTransactionCount); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivGetExpectedFailedTransactionReceipt.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivGetExpectedFailedTransactionReceipt.java deleted file mode 100644 index fc3b0818796..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivGetExpectedFailedTransactionReceipt.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.condition.priv; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.WaitUtils; -import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; -import org.hyperledger.besu.tests.acceptance.dsl.node.Node; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivGetTransactionReceiptTransaction; - -public class PrivGetExpectedFailedTransactionReceipt implements Condition { - - private final PrivGetTransactionReceiptTransaction getTransactionReceiptTransaction; - - public PrivGetExpectedFailedTransactionReceipt( - final PrivGetTransactionReceiptTransaction getTransactionReceiptTransaction) { - this.getTransactionReceiptTransaction = getTransactionReceiptTransaction; - } - - @Override - public void verify(final Node node) { - WaitUtils.waitFor(() -> assertThat(node.execute(getTransactionReceiptTransaction)).isNotNull()); - assertThat(node.execute(getTransactionReceiptTransaction).getStatus()).isEqualTo("0x0"); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivGetExpectedInvalidTransactionReceipt.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivGetExpectedInvalidTransactionReceipt.java deleted file mode 100644 index 75a344ded77..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivGetExpectedInvalidTransactionReceipt.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.condition.priv; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.WaitUtils; -import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; -import org.hyperledger.besu.tests.acceptance.dsl.node.Node; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivGetTransactionReceiptTransaction; - -public class PrivGetExpectedInvalidTransactionReceipt implements Condition { - - private final PrivGetTransactionReceiptTransaction getTransactionReceiptTransaction; - - public PrivGetExpectedInvalidTransactionReceipt( - final PrivGetTransactionReceiptTransaction getTransactionReceiptTransaction) { - this.getTransactionReceiptTransaction = getTransactionReceiptTransaction; - } - - @Override - public void verify(final Node node) { - WaitUtils.waitFor(() -> assertThat(node.execute(getTransactionReceiptTransaction)).isNotNull()); - assertThat(node.execute(getTransactionReceiptTransaction).getStatus()).isEqualTo("0x2"); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivGetExpectedSuccessfulTransactionReceipt.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivGetExpectedSuccessfulTransactionReceipt.java deleted file mode 100644 index cefc110e1b2..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivGetExpectedSuccessfulTransactionReceipt.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.condition.priv; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.WaitUtils; -import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; -import org.hyperledger.besu.tests.acceptance.dsl.node.Node; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivGetTransactionReceiptTransaction; - -public class PrivGetExpectedSuccessfulTransactionReceipt implements Condition { - - private final PrivGetTransactionReceiptTransaction getTransactionReceiptTransaction; - - public PrivGetExpectedSuccessfulTransactionReceipt( - final PrivGetTransactionReceiptTransaction getTransactionReceiptTransaction) { - this.getTransactionReceiptTransaction = getTransactionReceiptTransaction; - } - - @Override - public void verify(final Node node) { - WaitUtils.waitFor(() -> assertThat(node.execute(getTransactionReceiptTransaction)).isNotNull()); - assertThat(node.execute(getTransactionReceiptTransaction).getStatus()).isEqualTo("0x1"); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivGetPrivacyPrecompileAddressSuccess.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivGetPrivacyPrecompileAddressSuccess.java deleted file mode 100644 index a3992da4327..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivGetPrivacyPrecompileAddressSuccess.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.condition.priv; - -import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; -import org.hyperledger.besu.tests.acceptance.dsl.node.Node; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivGetPrivacyPrecompileAddressTransaction; - -public class PrivGetPrivacyPrecompileAddressSuccess implements Condition { - - private final PrivGetPrivacyPrecompileAddressTransaction transaction; - private final Address precompileAddress; - - public PrivGetPrivacyPrecompileAddressSuccess( - final PrivGetPrivacyPrecompileAddressTransaction transaction, - final Address precompileAddress) { - this.transaction = transaction; - this.precompileAddress = precompileAddress; - } - - @Override - public void verify(final Node node) { - final Address result = node.execute(transaction); - assertThat(result).isInstanceOf(Address.class); - assertThat(result).isEqualTo(precompileAddress); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivGetPrivateTransactionReturnsNull.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivGetPrivateTransactionReturnsNull.java deleted file mode 100644 index da6f7f519bf..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivGetPrivateTransactionReturnsNull.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.condition.priv; - -import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; -import org.hyperledger.besu.tests.acceptance.dsl.node.Node; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivateTransactionGroupResponse; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivGetPrivateTransactionTransaction; - -public class PrivGetPrivateTransactionReturnsNull implements Condition { - - private final PrivGetPrivateTransactionTransaction transaction; - - public PrivGetPrivateTransactionReturnsNull( - final PrivGetPrivateTransactionTransaction transaction) { - this.transaction = transaction; - } - - @Override - public void verify(final Node node) { - final PrivateTransactionGroupResponse result = node.execute(transaction); - assertThat(result).isNull(); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivGetPrivateTransactionSuccess.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivGetPrivateTransactionSuccess.java deleted file mode 100644 index b09b1b29bca..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivGetPrivateTransactionSuccess.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.condition.priv; - -import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; - -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.privacy.PrivateTransactionGroupResult; -import org.hyperledger.besu.ethereum.privacy.PrivateTransaction; -import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; -import org.hyperledger.besu.tests.acceptance.dsl.node.Node; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivateTransactionGroupResponse; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivGetPrivateTransactionTransaction; - -public class PrivGetPrivateTransactionSuccess implements Condition { - - private final PrivGetPrivateTransactionTransaction transaction; - private final PrivateTransactionGroupResult privateTransaction; - - public PrivGetPrivateTransactionSuccess( - final PrivGetPrivateTransactionTransaction transaction, - final PrivateTransaction privateTransaction) { - this.transaction = transaction; - this.privateTransaction = new PrivateTransactionGroupResult(privateTransaction); - } - - @Override - public void verify(final Node node) { - final PrivateTransactionGroupResponse result = node.execute(transaction); - assertThat(result).usingRecursiveComparison().isEqualTo(privateTransaction); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivGetTransactionCountSuccess.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivGetTransactionCountSuccess.java deleted file mode 100644 index 823548effaf..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/condition/priv/PrivGetTransactionCountSuccess.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.condition.priv; - -import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; -import org.hyperledger.besu.tests.acceptance.dsl.node.Node; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivGetTransactionCountTransaction; - -public class PrivGetTransactionCountSuccess implements Condition { - - private final PrivGetTransactionCountTransaction privGetTransactionCountTransaction; - private final int expectedTransactionCount; - - public PrivGetTransactionCountSuccess( - final PrivGetTransactionCountTransaction privGetTransactionCountTransaction, - final int expectedTransactionCount) { - this.privGetTransactionCountTransaction = privGetTransactionCountTransaction; - this.expectedTransactionCount = expectedTransactionCount; - } - - @Override - public void verify(final Node node) { - final Integer result = node.execute(privGetTransactionCountTransaction); - assertThat(result).isNotNull(); - assertThat(result).isInstanceOf(Integer.class); - assertThat(result).isEqualTo(expectedTransactionCount); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/BesuNode.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/BesuNode.java index c8613e9a4ba..157421531bb 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/BesuNode.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/BesuNode.java @@ -23,17 +23,19 @@ import org.hyperledger.besu.crypto.KeyPairUtil; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.api.ApiConfiguration; +import org.hyperledger.besu.ethereum.api.jsonrpc.InProcessRpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.ipc.JsonRpcIpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.Util; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration; import org.hyperledger.besu.ethereum.p2p.rlpx.connections.netty.TLSConfiguration; import org.hyperledger.besu.ethereum.permissioning.PermissioningConfiguration; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; -import org.hyperledger.besu.pki.config.PkiKeyStoreConfiguration; import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.NodeConfiguration; import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.genesis.GenesisConfigurationProvider; @@ -47,7 +49,6 @@ import org.hyperledger.besu.tests.acceptance.dsl.transaction.miner.MinerRequestFactory; import org.hyperledger.besu.tests.acceptance.dsl.transaction.net.CustomRequestFactory; import org.hyperledger.besu.tests.acceptance.dsl.transaction.perm.PermissioningJsonRpcRequestFactory; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivacyRequestFactory; import org.hyperledger.besu.tests.acceptance.dsl.transaction.txpool.TxPoolRequestFactory; import java.io.File; @@ -60,6 +61,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Properties; @@ -100,13 +102,16 @@ public class BesuNode implements NodeConfiguration, RunnableNode, AutoCloseable private final String name; private MiningParameters miningParameters; + private TransactionPoolConfiguration txPoolConfiguration; private final List runCommand; private PrivacyParameters privacyParameters = PrivacyParameters.DEFAULT; private final JsonRpcConfiguration jsonRpcConfiguration; private final Optional engineRpcConfiguration; private final WebSocketConfiguration webSocketConfiguration; private final JsonRpcIpcConfiguration jsonRpcIpcConfiguration; + private final InProcessRpcConfiguration inProcessRpcConfiguration; private final MetricsConfiguration metricsConfiguration; + private final DataStorageConfiguration dataStorageConfiguration; private Optional permissioningConfiguration; private final ApiConfiguration apiConfiguration; private final GenesisConfigurationProvider genesisConfigProvider; @@ -127,7 +132,6 @@ public class BesuNode implements NodeConfiguration, RunnableNode, AutoCloseable private final List staticNodes; private boolean isDnsEnabled = false; private Optional exitCode = Optional.empty(); - private Optional pkiKeyStoreConfiguration = Optional.empty(); private final boolean isStrictTxReplayProtectionEnabled; private final Map environment; @@ -135,13 +139,16 @@ public BesuNode( final String name, final Optional dataPath, final MiningParameters miningParameters, + final TransactionPoolConfiguration txPoolConfiguration, final JsonRpcConfiguration jsonRpcConfiguration, final Optional engineRpcConfiguration, final WebSocketConfiguration webSocketConfiguration, final JsonRpcIpcConfiguration jsonRpcIpcConfiguration, + final InProcessRpcConfiguration inProcessRpcConfiguration, final MetricsConfiguration metricsConfiguration, final Optional permissioningConfiguration, final ApiConfiguration apiConfiguration, + final DataStorageConfiguration dataStorageConfiguration, final Optional keyfilePath, final boolean devMode, final NetworkName network, @@ -162,7 +169,6 @@ public BesuNode( final Optional privacyParameters, final List runCommand, final Optional keyPair, - final Optional pkiKeyStoreConfiguration, final boolean isStrictTxReplayProtectionEnabled, final Map environment) throws IOException { @@ -184,13 +190,16 @@ public BesuNode( () -> this.keyPair = KeyPairUtil.loadKeyPair(homeDirectory)); this.name = name; this.miningParameters = miningParameters; + this.txPoolConfiguration = txPoolConfiguration; this.jsonRpcConfiguration = jsonRpcConfiguration; this.engineRpcConfiguration = engineRpcConfiguration; this.webSocketConfiguration = webSocketConfiguration; this.jsonRpcIpcConfiguration = jsonRpcIpcConfiguration; + this.inProcessRpcConfiguration = inProcessRpcConfiguration; this.metricsConfiguration = metricsConfiguration; this.permissioningConfiguration = permissioningConfiguration; this.apiConfiguration = apiConfiguration; + this.dataStorageConfiguration = dataStorageConfiguration; this.genesisConfigProvider = genesisConfigProvider; this.devMode = devMode; this.network = network; @@ -221,7 +230,6 @@ public BesuNode( this.staticNodes = staticNodes; this.isDnsEnabled = isDnsEnabled; privacyParameters.ifPresent(this::setPrivacyParameters); - this.pkiKeyStoreConfiguration = pkiKeyStoreConfiguration; this.environment = environment; LOG.info("Created BesuNode {}", this); } @@ -423,17 +431,19 @@ public NodeRequests nodeRequests() { getGenesisConfig() .map( gc -> - gc.toLowerCase().contains("ibft") ? ConsensusType.IBFT2 : ConsensusType.QBFT) + gc.toLowerCase(Locale.ROOT).contains("ibft") + ? ConsensusType.IBFT2 + : ConsensusType.QBFT) .orElse(ConsensusType.IBFT2); nodeRequests = new NodeRequests( + web3jService, new JsonRpc2_0Web3j(web3jService, 2000, Async.defaultExecutorService()), new CliqueRequestFactory(web3jService), new BftRequestFactory(web3jService, bftType), new PermissioningJsonRpcRequestFactory(web3jService), new AdminRequestFactory(web3jService), - new PrivacyRequestFactory(web3jService), new CustomRequestFactory(web3jService), new MinerRequestFactory(web3jService), new TxPoolRequestFactory(web3jService), @@ -616,6 +626,10 @@ JsonRpcIpcConfiguration jsonRpcIpcConfiguration() { return jsonRpcIpcConfiguration; } + InProcessRpcConfiguration inProcessRpcConfiguration() { + return inProcessRpcConfiguration; + } + Optional wsRpcListenHost() { return Optional.of(webSocketConfiguration().getHost()); } @@ -661,7 +675,7 @@ public void setBootnodes(final List bootnodes) { this.bootnodes.addAll(bootnodes); } - MiningParameters getMiningParameters() { + public MiningParameters getMiningParameters() { return miningParameters; } @@ -669,6 +683,15 @@ public void setMiningParameters(final MiningParameters miningParameters) { this.miningParameters = miningParameters; } + public TransactionPoolConfiguration getTransactionPoolConfiguration() { + return txPoolConfiguration; + } + + public void setTransactionPoolConfiguration( + final TransactionPoolConfiguration txPoolConfiguration) { + this.txPoolConfiguration = txPoolConfiguration; + } + public PrivacyParameters getPrivacyParameters() { return privacyParameters; } @@ -677,6 +700,10 @@ public void setPrivacyParameters(final PrivacyParameters privacyParameters) { this.privacyParameters = privacyParameters; } + public DataStorageConfiguration getDataStorageConfiguration() { + return dataStorageConfiguration; + } + public boolean isDevMode() { return devMode; } @@ -738,10 +765,6 @@ public List getRunCommand() { return runCommand; } - public Optional getPkiKeyStoreConfiguration() { - return pkiKeyStoreConfiguration; - } - public boolean isStrictTxReplayProtectionEnabled() { return isStrictTxReplayProtectionEnabled; } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java index f01c58549bc..c6696b15a22 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ProcessBesuNodeRunner.java @@ -17,8 +17,11 @@ import static com.google.common.base.Preconditions.checkState; import static java.nio.charset.StandardCharsets.UTF_8; +import org.hyperledger.besu.cli.options.TransactionPoolOptions; +import org.hyperledger.besu.cli.options.stable.DataStorageOptions; import org.hyperledger.besu.cli.options.unstable.NetworkingOptions; import org.hyperledger.besu.ethereum.api.jsonrpc.ipc.JsonRpcIpcConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.p2p.rlpx.connections.netty.TLSConfiguration; import org.hyperledger.besu.ethereum.permissioning.PermissioningConfiguration; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; @@ -98,6 +101,18 @@ public void startNode(final BesuNode node) { params.add("--p2p-port"); params.add(node.getP2pPort()); + params.addAll( + TransactionPoolOptions.fromConfig( + ImmutableTransactionPoolConfiguration.builder() + .from(node.getTransactionPoolConfiguration()) + .strictTransactionReplayProtectionEnabled( + node.isStrictTxReplayProtectionEnabled()) + .build()) + .getCLIOptions()); + + params.addAll( + DataStorageOptions.fromConfig(node.getDataStorageConfiguration()).getCLIOptions()); + if (node.getMiningParameters().isMiningEnabled()) { params.add("--miner-enabled"); params.add("--miner-coinbase"); @@ -145,6 +160,9 @@ public void startNode(final BesuNode node) { if (node.getPrivacyParameters().isPrivacyPluginEnabled()) { params.add("--Xprivacy-plugin-enabled"); } + if (node.getPrivacyParameters().isPrivateNonceAlwaysIncrementsEnabled()) { + params.add("privacy-nonce-always-increments"); + } } if (!node.getBootnodes().isEmpty()) { @@ -356,33 +374,6 @@ public void startNode(final BesuNode node) { permissioningConfiguration.getNodeSmartContractInterfaceVersion())); }); - node.getPkiKeyStoreConfiguration() - .ifPresent( - pkiConfig -> { - params.add("--Xpki-block-creation-enabled"); - - params.add("--Xpki-block-creation-keystore-certificate-alias"); - params.add(pkiConfig.getCertificateAlias()); - - params.add("--Xpki-block-creation-keystore-type"); - params.add(pkiConfig.getKeyStoreType()); - - params.add("--Xpki-block-creation-keystore-file"); - params.add(pkiConfig.getKeyStorePath().toAbsolutePath().toString()); - - params.add("--Xpki-block-creation-keystore-password-file"); - params.add(pkiConfig.getKeyStorePasswordPath().toAbsolutePath().toString()); - - params.add("--Xpki-block-creation-truststore-type"); - params.add(pkiConfig.getTrustStoreType()); - - params.add("--Xpki-block-creation-truststore-file"); - params.add(pkiConfig.getTrustStorePath().toAbsolutePath().toString()); - - params.add("--Xpki-block-creation-truststore-password-file"); - params.add(pkiConfig.getTrustStorePasswordPath().toAbsolutePath().toString()); - }); - params.addAll(node.getExtraCLIOptions()); params.add("--key-value-storage"); @@ -391,9 +382,6 @@ public void startNode(final BesuNode node) { params.add("--auto-log-bloom-caching-enabled"); params.add("false"); - params.add("--strict-tx-replay-protection-enabled"); - params.add(Boolean.toString(node.isStrictTxReplayProtectionEnabled())); - final String level = System.getProperty("root.log.level"); if (level != null) { params.add("--logging=" + level); diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java index 501674ea383..d4ec54045d3 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java @@ -1,5 +1,5 @@ /* - * Copyright ConsenSys AG. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -18,62 +18,89 @@ import org.hyperledger.besu.Runner; import org.hyperledger.besu.RunnerBuilder; +import org.hyperledger.besu.chainexport.RlpBlockExporter; +import org.hyperledger.besu.chainimport.JsonBlockImporter; +import org.hyperledger.besu.chainimport.RlpBlockImporter; +import org.hyperledger.besu.cli.BesuCommand; import org.hyperledger.besu.cli.config.EthNetworkConfig; import org.hyperledger.besu.cli.config.NetworkName; +import org.hyperledger.besu.components.BesuComponent; import org.hyperledger.besu.config.GenesisConfigFile; -import org.hyperledger.besu.consensus.qbft.pki.PkiBlockCreationConfigurationProvider; import org.hyperledger.besu.controller.BesuController; import org.hyperledger.besu.controller.BesuControllerBuilder; import org.hyperledger.besu.crypto.KeyPairUtil; import org.hyperledger.besu.cryptoservices.KeyPairSecurityModule; import org.hyperledger.besu.cryptoservices.NodeKey; import org.hyperledger.besu.ethereum.GasLimitCalculator; +import org.hyperledger.besu.ethereum.api.ApiConfiguration; import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration; +import org.hyperledger.besu.ethereum.api.jsonrpc.InProcessRpcConfiguration; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters; +import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.ethereum.core.plugins.PluginConfiguration; import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCacheModule; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStorageProvider; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStorageProviderBuilder; +import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.BonsaiCachedMerkleTrieLoaderModule; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.evm.internal.EvmConfiguration; -import org.hyperledger.besu.metrics.MetricsSystemFactory; +import org.hyperledger.besu.metrics.MetricsSystemModule; import org.hyperledger.besu.metrics.ObservableMetricsSystem; +import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; import org.hyperledger.besu.plugin.data.EnodeURL; import org.hyperledger.besu.plugin.services.BesuConfiguration; import org.hyperledger.besu.plugin.services.BesuEvents; +import org.hyperledger.besu.plugin.services.BlockchainService; +import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.PermissioningService; import org.hyperledger.besu.plugin.services.PicoCLIOptions; -import org.hyperledger.besu.plugin.services.PluginTransactionValidatorService; +import org.hyperledger.besu.plugin.services.PrivacyPluginService; import org.hyperledger.besu.plugin.services.RpcEndpointService; import org.hyperledger.besu.plugin.services.SecurityModuleService; import org.hyperledger.besu.plugin.services.StorageService; +import org.hyperledger.besu.plugin.services.TransactionPoolValidatorService; import org.hyperledger.besu.plugin.services.TransactionSelectionService; +import org.hyperledger.besu.plugin.services.TransactionSimulationService; import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBPlugin; -import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelectorFactory; -import org.hyperledger.besu.plugin.services.txvalidator.PluginTransactionValidatorFactory; import org.hyperledger.besu.services.BesuConfigurationImpl; import org.hyperledger.besu.services.BesuEventsImpl; import org.hyperledger.besu.services.BesuPluginContextImpl; +import org.hyperledger.besu.services.BlockchainServiceImpl; import org.hyperledger.besu.services.PermissioningServiceImpl; import org.hyperledger.besu.services.PicoCLIOptionsImpl; -import org.hyperledger.besu.services.PluginTransactionValidatorServiceImpl; +import org.hyperledger.besu.services.PrivacyPluginServiceImpl; import org.hyperledger.besu.services.RpcEndpointServiceImpl; import org.hyperledger.besu.services.SecurityModuleServiceImpl; import org.hyperledger.besu.services.StorageServiceImpl; +import org.hyperledger.besu.services.TransactionPoolValidatorServiceImpl; import org.hyperledger.besu.services.TransactionSelectionServiceImpl; +import org.hyperledger.besu.services.TransactionSimulationServiceImpl; +import org.hyperledger.besu.services.kvstore.InMemoryStoragePlugin; import java.io.File; import java.nio.file.Path; import java.time.Clock; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import dagger.Component; +import dagger.Module; +import dagger.Provides; import io.opentelemetry.api.GlobalOpenTelemetry; import io.vertx.core.Vertx; import org.slf4j.Logger; @@ -89,46 +116,6 @@ public class ThreadBesuNodeRunner implements BesuNodeRunner { private final Map besuPluginContextMap = new ConcurrentHashMap<>(); - private BesuPluginContextImpl buildPluginContext( - final BesuNode node, - final StorageServiceImpl storageService, - final SecurityModuleServiceImpl securityModuleService, - final BesuConfiguration commonPluginConfiguration) { - final CommandLine commandLine = new CommandLine(CommandSpec.create()); - final BesuPluginContextImpl besuPluginContext = new BesuPluginContextImpl(); - besuPluginContext.addService(StorageService.class, storageService); - besuPluginContext.addService(SecurityModuleService.class, securityModuleService); - besuPluginContext.addService(PicoCLIOptions.class, new PicoCLIOptionsImpl(commandLine)); - besuPluginContext.addService(RpcEndpointService.class, new RpcEndpointServiceImpl()); - besuPluginContext.addService( - TransactionSelectionService.class, new TransactionSelectionServiceImpl()); - besuPluginContext.addService( - PluginTransactionValidatorService.class, new PluginTransactionValidatorServiceImpl()); - final Path pluginsPath; - final String pluginDir = System.getProperty("besu.plugins.dir"); - if (pluginDir == null || pluginDir.isEmpty()) { - pluginsPath = node.homeDirectory().resolve("plugins"); - final File pluginsDirFile = pluginsPath.toFile(); - if (!pluginsDirFile.isDirectory()) { - pluginsDirFile.mkdirs(); - pluginsDirFile.deleteOnExit(); - } - System.setProperty("besu.plugins.dir", pluginsPath.toString()); - } else { - pluginsPath = Path.of(pluginDir); - } - besuPluginContext.registerPlugins(pluginsPath); - - commandLine.parseArgs(node.getConfiguration().getExtraCLIOptions().toArray(new String[0])); - - besuPluginContext.addService(BesuConfiguration.class, commonPluginConfiguration); - - // register built-in plugins - new RocksDBPlugin().register(besuPluginContext); - - return besuPluginContext; - } - @Override public void startNode(final BesuNode node) { @@ -141,87 +128,45 @@ public void startNode(final BesuNode node) { throw new UnsupportedOperationException("commands are not supported with thread runner"); } - final StorageServiceImpl storageService = new StorageServiceImpl(); - final SecurityModuleServiceImpl securityModuleService = new SecurityModuleServiceImpl(); + BesuNodeProviderModule module = new BesuNodeProviderModule(node); + AcceptanceTestBesuComponent component = + DaggerThreadBesuNodeRunner_AcceptanceTestBesuComponent.builder() + .besuNodeProviderModule(module) + .build(); + final Path dataDir = node.homeDirectory(); - final BesuConfiguration commonPluginConfiguration = - new BesuConfigurationImpl(dataDir, dataDir.resolve(DATABASE_PATH)); - final BesuPluginContextImpl besuPluginContext = - besuPluginContextMap.computeIfAbsent( - node, - n -> - buildPluginContext( - node, storageService, securityModuleService, commonPluginConfiguration)); + final PermissioningServiceImpl permissioningService = new PermissioningServiceImpl(); GlobalOpenTelemetry.resetForTest(); - final ObservableMetricsSystem metricsSystem = - MetricsSystemFactory.create(node.getMetricsConfiguration()); + final ObservableMetricsSystem metricsSystem = component.getObservableMetricsSystem(); final List bootnodes = - node.getConfiguration().getBootnodes().stream() - .map(EnodeURLImpl::fromURI) - .collect(Collectors.toList()); - final NetworkName network = node.getNetwork() == null ? NetworkName.DEV : node.getNetwork(); - final EthNetworkConfig.Builder networkConfigBuilder = - new EthNetworkConfig.Builder(EthNetworkConfig.getNetworkConfig(network)) - .setBootNodes(bootnodes); - node.getConfiguration().getGenesisConfig().ifPresent(networkConfigBuilder::setGenesisConfig); - final EthNetworkConfig ethNetworkConfig = networkConfigBuilder.build(); - final SynchronizerConfiguration synchronizerConfiguration = - new SynchronizerConfiguration.Builder().build(); - final BesuControllerBuilder builder = - new BesuController.Builder() - .fromEthNetworkConfig( - ethNetworkConfig, Collections.emptyMap(), synchronizerConfiguration.getSyncMode()); - - final KeyValueStorageProvider storageProvider = - new KeyValueStorageProviderBuilder() - .withStorageFactory(storageService.getByName("rocksdb").get()) - .withCommonConfiguration(commonPluginConfiguration) - .withMetricsSystem(metricsSystem) - .build(); - - final TransactionPoolConfiguration txPoolConfig = - ImmutableTransactionPoolConfiguration.builder() - .strictTransactionReplayProtectionEnabled(node.isStrictTxReplayProtectionEnabled()) - .build(); - - final int maxPeers = 25; + node.getConfiguration().getBootnodes().stream().map(EnodeURLImpl::fromURI).toList(); - final Optional transactionSelectorFactory = - getTransactionSelectorFactory(besuPluginContext); + final EthNetworkConfig.Builder networkConfigBuilder = component.ethNetworkConfigBuilder(); + networkConfigBuilder.setBootNodes(bootnodes); + node.getConfiguration() + .getGenesisConfig() + .map(GenesisConfigFile::fromConfig) + .ifPresent(networkConfigBuilder::setGenesisConfigFile); + final EthNetworkConfig ethNetworkConfig = networkConfigBuilder.build(); + final BesuControllerBuilder builder = component.besuControllerBuilder(); + builder.isRevertReasonEnabled(node.isRevertReasonEnabled()); + builder.networkConfiguration(node.getNetworkingConfiguration()); - final PluginTransactionValidatorFactory pluginTransactionValidatorFactory = - getPluginTransactionValidatorFactory(besuPluginContext); - builder - .synchronizerConfiguration(new SynchronizerConfiguration.Builder().build()) - .dataDirectory(node.homeDirectory()) - .miningParameters(node.getMiningParameters()) - .privacyParameters(node.getPrivacyParameters()) - .nodeKey(new NodeKey(new KeyPairSecurityModule(KeyPairUtil.loadKeyPair(dataDir)))) - .metricsSystem(metricsSystem) - .transactionPoolConfiguration(txPoolConfig) - .ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig()) - .clock(Clock.systemUTC()) - .isRevertReasonEnabled(node.isRevertReasonEnabled()) - .storageProvider(storageProvider) - .gasLimitCalculator(GasLimitCalculator.constant()) - .pkiBlockCreationConfiguration( - node.getPkiKeyStoreConfiguration() - .map(pkiConfig -> new PkiBlockCreationConfigurationProvider().load(pkiConfig))) - .evmConfiguration(EvmConfiguration.DEFAULT) - .maxPeers(maxPeers) - .lowerBoundPeers(maxPeers) - .maxRemotelyInitiatedPeers(15) - .networkConfiguration(node.getNetworkingConfiguration()) - .randomPeerPriority(false) - .transactionSelectorFactory(transactionSelectorFactory) - .pluginTransactionValidatorFactory(pluginTransactionValidatorFactory); + builder.dataDirectory(dataDir); + builder.nodeKey(new NodeKey(new KeyPairSecurityModule(KeyPairUtil.loadKeyPair(dataDir)))); + builder.privacyParameters(node.getPrivacyParameters()); node.getGenesisConfig() .map(GenesisConfigFile::fromConfig) .ifPresent(builder::genesisConfigFile); - final BesuController besuController = builder.build(); + final BesuController besuController = component.besuController(); + + InProcessRpcConfiguration inProcessRpcConfiguration = node.inProcessRpcConfiguration(); + + final BesuPluginContextImpl besuPluginContext = + besuPluginContextMap.computeIfAbsent(node, n -> component.getBesuPluginContext()); final RunnerBuilder runnerBuilder = new RunnerBuilder(); runnerBuilder.permissioningConfiguration(node.getPermissioningConfiguration()); @@ -240,24 +185,20 @@ public void startNode(final BesuNode node) { .jsonRpcIpcConfiguration(node.jsonRpcIpcConfiguration()) .dataDir(node.homeDirectory()) .metricsSystem(metricsSystem) - .permissioningService(new PermissioningServiceImpl()) + .permissioningService(permissioningService) .metricsConfiguration(node.getMetricsConfiguration()) .p2pEnabled(node.isP2pEnabled()) .p2pTLSConfiguration(node.getTLSConfiguration()) .graphQLConfiguration(GraphQLConfiguration.createDefault()) - .staticNodes( - node.getStaticNodes().stream() - .map(EnodeURLImpl::fromString) - .collect(Collectors.toList())) - .besuPluginContext(new BesuPluginContextImpl()) + .staticNodes(node.getStaticNodes().stream().map(EnodeURLImpl::fromString).toList()) + .besuPluginContext(besuPluginContext) .autoLogBloomCaching(false) - .storageProvider(storageProvider) - .rpcEndpointService(new RpcEndpointServiceImpl()); + .storageProvider(besuController.getStorageProvider()) + .rpcEndpointService(component.rpcEndpointService()) + .inProcessRpcConfiguration(inProcessRpcConfiguration); node.engineRpcConfiguration().ifPresent(runnerBuilder::engineJsonRpcConfiguration); - - final Runner runner = runnerBuilder.build(); - besuPluginContext.beforeExternalServices(); + final Runner runner = runnerBuilder.build(); runner.startExternalServices(); @@ -267,7 +208,11 @@ public void startNode(final BesuNode node) { besuController.getProtocolContext().getBlockchain(), besuController.getProtocolManager().getBlockBroadcaster(), besuController.getTransactionPool(), - besuController.getSyncState())); + besuController.getSyncState(), + besuController.getProtocolContext().getBadBlockManager())); + + component.rpcEndpointService().init(runner.getInProcessRpcMethods()); + besuPluginContext.startPlugins(); runner.startEthereumMainLoop(); @@ -326,17 +271,330 @@ public String getConsoleContents() { throw new RuntimeException("Console contents can only be captured in process execution"); } - private Optional getTransactionSelectorFactory( - final BesuPluginContextImpl besuPluginContext) { - final Optional txSelectionService = - besuPluginContext.getService(TransactionSelectionService.class); - return txSelectionService.isPresent() ? txSelectionService.get().get() : Optional.empty(); + @Module + static class BesuNodeProviderModule { + + private final BesuNode toProvide; + + public BesuNodeProviderModule(final BesuNode toProvide) { + this.toProvide = toProvide; + } + + @Provides + public BesuNode provideBesuNodeRunner() { + return toProvide; + } + + @Provides + @Named("ExtraCLIOptions") + public List provideExtraCLIOptions() { + return toProvide.getExtraCLIOptions(); + } + + @Provides + Path provideDataDir() { + return toProvide.homeDirectory(); + } + + @Provides + @Singleton + RpcEndpointServiceImpl provideRpcEndpointService() { + return new RpcEndpointServiceImpl(); + } + + @Provides + @Singleton + BlockchainServiceImpl provideBlockchainService(final BesuController besuController) { + BlockchainServiceImpl retval = new BlockchainServiceImpl(); + retval.init(besuController.getProtocolContext(), besuController.getProtocolSchedule()); + return retval; + } + + @Provides + @Singleton + Blockchain provideBlockchain(final BesuController besuController) { + return besuController.getProtocolContext().getBlockchain(); + } + + @Provides + @SuppressWarnings("CloseableProvides") + WorldStateArchive provideWorldStateArchive(final BesuController besuController) { + return besuController.getProtocolContext().getWorldStateArchive(); + } + + @Provides + ProtocolSchedule provideProtocolSchedule(final BesuController besuController) { + return besuController.getProtocolSchedule(); + } + + @Provides + ApiConfiguration provideApiConfiguration(final BesuNode node) { + return node.getApiConfiguration(); + } + + @Provides + @Singleton + TransactionPoolValidatorServiceImpl provideTransactionPoolValidatorService() { + return new TransactionPoolValidatorServiceImpl(); + } + + @Provides + @Singleton + TransactionSelectionServiceImpl provideTransactionSelectionService() { + return new TransactionSelectionServiceImpl(); + } + + @Provides + @Singleton + TransactionPoolConfiguration provideTransactionPoolConfiguration( + final BesuNode node, + final TransactionPoolValidatorServiceImpl transactionPoolValidatorServiceImpl) { + + TransactionPoolConfiguration txPoolConfig = + ImmutableTransactionPoolConfiguration.builder() + .from(node.getTransactionPoolConfiguration()) + .strictTransactionReplayProtectionEnabled(node.isStrictTxReplayProtectionEnabled()) + .transactionPoolValidatorService(transactionPoolValidatorServiceImpl) + .build(); + return txPoolConfig; + } + + @Provides + @Singleton + TransactionSimulator provideTransactionSimulator( + final Blockchain blockchain, + final WorldStateArchive worldStateArchive, + final ProtocolSchedule protocolSchedule, + final ApiConfiguration apiConfiguration) { + return new TransactionSimulator( + blockchain, worldStateArchive, protocolSchedule, apiConfiguration.getGasCap()); + } + + @Provides + @Singleton + TransactionSimulationServiceImpl provideTransactionSimulationService( + final Blockchain blockchain, final TransactionSimulator transactionSimulator) { + TransactionSimulationServiceImpl retval = new TransactionSimulationServiceImpl(); + retval.init(blockchain, transactionSimulator); + return retval; + } + } + + @Module + @SuppressWarnings("CloseableProvides") + static class BesuControllerModule { + @Provides + @Singleton + public SynchronizerConfiguration provideSynchronizationConfiguration() { + final SynchronizerConfiguration synchronizerConfiguration = + SynchronizerConfiguration.builder().build(); + return synchronizerConfiguration; + } + + @Singleton + @Provides + public BesuControllerBuilder provideBesuControllerBuilder( + final EthNetworkConfig ethNetworkConfig, + final SynchronizerConfiguration synchronizerConfiguration, + final TransactionPoolConfiguration transactionPoolConfiguration) { + + final BesuControllerBuilder builder = + new BesuController.Builder() + .fromEthNetworkConfig(ethNetworkConfig, synchronizerConfiguration.getSyncMode()); + builder.transactionPoolConfiguration(transactionPoolConfiguration); + return builder; + } + + @Provides + @Singleton + public BesuController provideBesuController( + final SynchronizerConfiguration synchronizerConfiguration, + final BesuControllerBuilder builder, + final ObservableMetricsSystem metricsSystem, + final KeyValueStorageProvider storageProvider, + final MiningParameters miningParameters) { + + builder + .synchronizerConfiguration(synchronizerConfiguration) + .metricsSystem(metricsSystem) + .dataStorageConfiguration(DataStorageConfiguration.DEFAULT_FOREST_CONFIG) + .ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig()) + .clock(Clock.systemUTC()) + .storageProvider(storageProvider) + .gasLimitCalculator(GasLimitCalculator.constant()) + .evmConfiguration(EvmConfiguration.DEFAULT) + .maxPeers(25) + .maxRemotelyInitiatedPeers(15) + .miningParameters(miningParameters) + .randomPeerPriority(false) + .besuComponent(null); + return builder.build(); + } + + @Provides + @Singleton + public EthNetworkConfig.Builder provideEthNetworkConfigBuilder() { + final EthNetworkConfig.Builder networkConfigBuilder = + new EthNetworkConfig.Builder(EthNetworkConfig.getNetworkConfig(NetworkName.DEV)); + return networkConfigBuilder; + } + + @Provides + public EthNetworkConfig provideEthNetworkConfig( + final EthNetworkConfig.Builder networkConfigBuilder) { + + final EthNetworkConfig ethNetworkConfig = networkConfigBuilder.build(); + return ethNetworkConfig; + } + + @Provides + public BesuPluginContextImpl providePluginContext( + final StorageServiceImpl storageService, + final SecurityModuleServiceImpl securityModuleService, + final TransactionSimulationServiceImpl transactionSimulationServiceImpl, + final TransactionSelectionServiceImpl transactionSelectionServiceImpl, + final TransactionPoolValidatorServiceImpl transactionPoolValidatorServiceImpl, + final BlockchainServiceImpl blockchainServiceImpl, + final RpcEndpointServiceImpl rpcEndpointServiceImpl, + final BesuConfiguration commonPluginConfiguration, + final PermissioningServiceImpl permissioningService, + final @Named("ExtraCLIOptions") List extraCLIOptions) { + final CommandLine commandLine = new CommandLine(CommandSpec.create()); + final BesuPluginContextImpl besuPluginContext = new BesuPluginContextImpl(); + besuPluginContext.addService(StorageService.class, storageService); + besuPluginContext.addService(SecurityModuleService.class, securityModuleService); + besuPluginContext.addService(PicoCLIOptions.class, new PicoCLIOptionsImpl(commandLine)); + besuPluginContext.addService(RpcEndpointService.class, rpcEndpointServiceImpl); + besuPluginContext.addService( + TransactionSelectionService.class, transactionSelectionServiceImpl); + besuPluginContext.addService( + TransactionPoolValidatorService.class, transactionPoolValidatorServiceImpl); + besuPluginContext.addService( + TransactionSimulationService.class, transactionSimulationServiceImpl); + besuPluginContext.addService(BlockchainService.class, blockchainServiceImpl); + besuPluginContext.addService(BesuConfiguration.class, commonPluginConfiguration); + + final Path pluginsPath; + final String pluginDir = System.getProperty("besu.plugins.dir"); + if (pluginDir == null || pluginDir.isEmpty()) { + pluginsPath = commonPluginConfiguration.getDataPath().resolve("plugins"); + final File pluginsDirFile = pluginsPath.toFile(); + if (!pluginsDirFile.isDirectory()) { + pluginsDirFile.mkdirs(); + pluginsDirFile.deleteOnExit(); + } + System.setProperty("besu.plugins.dir", pluginsPath.toString()); + } else { + pluginsPath = Path.of(pluginDir); + } + + besuPluginContext.addService(BesuConfiguration.class, commonPluginConfiguration); + besuPluginContext.addService(PermissioningService.class, permissioningService); + besuPluginContext.addService(PrivacyPluginService.class, new PrivacyPluginServiceImpl()); + + besuPluginContext.registerPlugins( + new PluginConfiguration.Builder().pluginsDir(pluginsPath).build()); + commandLine.parseArgs(extraCLIOptions.toArray(new String[0])); + + // register built-in plugins + new RocksDBPlugin().register(besuPluginContext); + return besuPluginContext; + } + + @Provides + public KeyValueStorageProvider provideKeyValueStorageProvider( + final BesuConfiguration commonPluginConfiguration, final MetricsSystem metricsSystem) { + + final StorageServiceImpl storageService = new StorageServiceImpl(); + storageService.registerKeyValueStorage( + new InMemoryStoragePlugin.InMemoryKeyValueStorageFactory("memory")); + final KeyValueStorageProvider storageProvider = + new KeyValueStorageProviderBuilder() + .withStorageFactory(storageService.getByName("memory").get()) + .withCommonConfiguration(commonPluginConfiguration) + .withMetricsSystem(metricsSystem) + .build(); + + return storageProvider; + } + + @Provides + public MiningParameters provideMiningParameters( + final TransactionSelectionServiceImpl transactionSelectionServiceImpl, + final BesuNode node) { + final var miningParameters = + ImmutableMiningParameters.builder() + .from(node.getMiningParameters()) + .transactionSelectionService(transactionSelectionServiceImpl) + .build(); + + return miningParameters; + } + + @Provides + @Inject + BesuConfiguration provideBesuConfiguration( + final Path dataDir, final MiningParameters miningParameters, final BesuNode node) { + final BesuConfigurationImpl commonPluginConfiguration = new BesuConfigurationImpl(); + commonPluginConfiguration.init( + dataDir, dataDir.resolve(DATABASE_PATH), node.getDataStorageConfiguration()); + commonPluginConfiguration.withMiningParameters(miningParameters); + return commonPluginConfiguration; + } + } + + @Module + static class MockBesuCommandModule { + + @Provides + BesuCommand provideBesuCommand(final BesuPluginContextImpl pluginContext) { + final BesuCommand besuCommand = + new BesuCommand( + RlpBlockImporter::new, + JsonBlockImporter::new, + RlpBlockExporter::new, + new RunnerBuilder(), + new BesuController.Builder(), + pluginContext, + System.getenv(), + LoggerFactory.getLogger(MockBesuCommandModule.class)); + besuCommand.toCommandLine(); + return besuCommand; + } + + @Provides + @Singleton + MetricsConfiguration provideMetricsConfiguration() { + return MetricsConfiguration.builder().build(); + } + + @Provides + @Named("besuCommandLogger") + @Singleton + Logger provideBesuCommandLogger() { + return LoggerFactory.getLogger(MockBesuCommandModule.class); + } } - private PluginTransactionValidatorFactory getPluginTransactionValidatorFactory( - final BesuPluginContextImpl besuPluginContext) { - final Optional txValidatorService = - besuPluginContext.getService(PluginTransactionValidatorService.class); - return txValidatorService.map(PluginTransactionValidatorService::get).orElse(null); + @Singleton + @Component( + modules = { + ThreadBesuNodeRunner.BesuControllerModule.class, + ThreadBesuNodeRunner.MockBesuCommandModule.class, + BonsaiCachedMerkleTrieLoaderModule.class, + MetricsSystemModule.class, + ThreadBesuNodeRunner.BesuNodeProviderModule.class, + BlobCacheModule.class + }) + public interface AcceptanceTestBesuComponent extends BesuComponent { + BesuController besuController(); + + BesuControllerBuilder besuControllerBuilder(); // TODO: needing this sucks + + EthNetworkConfig.Builder ethNetworkConfigBuilder(); + + RpcEndpointServiceImpl rpcEndpointService(); + + BlockchainServiceImpl blockchainService(); } } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/cluster/Cluster.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/cluster/Cluster.java index 16a02874262..b454894e066 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/cluster/Cluster.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/cluster/Cluster.java @@ -86,11 +86,8 @@ public void start(final List nodes) { final Optional bootnode = selectAndStartBootnode(nodes); nodes.parallelStream() - .filter( - node -> { - LOG.info("starting non-bootnode {}", node.getName()); - return bootnode.map(boot -> boot != node).orElse(true); - }) + .filter(node -> bootnode.map(boot -> boot != node).orElse(true)) + .peek(node -> LOG.info("starting non-bootnode {}", node.getName())) .forEach(this::startNode); if (clusterConfiguration.isAwaitPeerDiscovery()) { diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfiguration.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfiguration.java index 3a61749881a..c77a6c8ac7b 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfiguration.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfiguration.java @@ -17,16 +17,18 @@ import org.hyperledger.besu.cli.config.NetworkName; import org.hyperledger.besu.crypto.KeyPair; import org.hyperledger.besu.ethereum.api.ApiConfiguration; +import org.hyperledger.besu.ethereum.api.jsonrpc.InProcessRpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.ipc.JsonRpcIpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration; import org.hyperledger.besu.ethereum.p2p.rlpx.connections.netty.TLSConfiguration; import org.hyperledger.besu.ethereum.permissioning.PermissioningConfiguration; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; -import org.hyperledger.besu.pki.config.PkiKeyStoreConfiguration; import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.genesis.GenesisConfigurationProvider; import java.nio.file.Path; @@ -39,13 +41,16 @@ public class BesuNodeConfiguration { private final String name; private final Optional dataPath; private final MiningParameters miningParameters; + private final TransactionPoolConfiguration transactionPoolConfiguration; private final JsonRpcConfiguration jsonRpcConfiguration; private final Optional engineRpcConfiguration; private final WebSocketConfiguration webSocketConfiguration; private final JsonRpcIpcConfiguration jsonRpcIpcConfiguration; + private final InProcessRpcConfiguration inProcessRpcConfiguration; private final MetricsConfiguration metricsConfiguration; private final Optional permissioningConfiguration; private final ApiConfiguration apiConfiguration; + private final DataStorageConfiguration dataStorageConfiguration; private final Optional keyFilePath; private final boolean devMode; private final GenesisConfigurationProvider genesisConfigProvider; @@ -66,7 +71,6 @@ public class BesuNodeConfiguration { private final List runCommand; private final NetworkName network; private final Optional keyPair; - private final Optional pkiKeyStoreConfiguration; private final boolean strictTxReplayProtectionEnabled; private final Map environment; @@ -74,13 +78,16 @@ public class BesuNodeConfiguration { final String name, final Optional dataPath, final MiningParameters miningParameters, + final TransactionPoolConfiguration transactionPoolConfiguration, final JsonRpcConfiguration jsonRpcConfiguration, final Optional engineRpcConfiguration, final WebSocketConfiguration webSocketConfiguration, final JsonRpcIpcConfiguration jsonRpcIpcConfiguration, + final InProcessRpcConfiguration inProcessRpcConfiguration, final MetricsConfiguration metricsConfiguration, final Optional permissioningConfiguration, final ApiConfiguration apiConfiguration, + final DataStorageConfiguration dataStorageConfiguration, final Optional keyFilePath, final boolean devMode, final NetworkName network, @@ -101,18 +108,20 @@ public class BesuNodeConfiguration { final Optional privacyParameters, final List runCommand, final Optional keyPair, - final Optional pkiKeyStoreConfiguration, final boolean strictTxReplayProtectionEnabled, final Map environment) { this.name = name; this.miningParameters = miningParameters; + this.transactionPoolConfiguration = transactionPoolConfiguration; this.jsonRpcConfiguration = jsonRpcConfiguration; this.engineRpcConfiguration = engineRpcConfiguration; this.webSocketConfiguration = webSocketConfiguration; this.jsonRpcIpcConfiguration = jsonRpcIpcConfiguration; + this.inProcessRpcConfiguration = inProcessRpcConfiguration; this.metricsConfiguration = metricsConfiguration; this.permissioningConfiguration = permissioningConfiguration; this.apiConfiguration = apiConfiguration; + this.dataStorageConfiguration = dataStorageConfiguration; this.keyFilePath = keyFilePath; this.dataPath = dataPath; this.devMode = devMode; @@ -134,7 +143,6 @@ public class BesuNodeConfiguration { this.privacyParameters = privacyParameters; this.runCommand = runCommand; this.keyPair = keyPair; - this.pkiKeyStoreConfiguration = pkiKeyStoreConfiguration; this.strictTxReplayProtectionEnabled = strictTxReplayProtectionEnabled; this.environment = environment; } @@ -147,6 +155,10 @@ public MiningParameters getMiningParameters() { return miningParameters; } + public TransactionPoolConfiguration getTransactionPoolConfiguration() { + return transactionPoolConfiguration; + } + public JsonRpcConfiguration getJsonRpcConfiguration() { return jsonRpcConfiguration; } @@ -163,6 +175,10 @@ public JsonRpcIpcConfiguration getJsonRpcIpcConfiguration() { return jsonRpcIpcConfiguration; } + public InProcessRpcConfiguration getInProcessRpcConfiguration() { + return inProcessRpcConfiguration; + } + public MetricsConfiguration getMetricsConfiguration() { return metricsConfiguration; } @@ -175,6 +191,10 @@ public ApiConfiguration getApiConfiguration() { return apiConfiguration; } + public DataStorageConfiguration getDataStorageConfiguration() { + return dataStorageConfiguration; + } + public Optional getKeyFilePath() { return keyFilePath; } @@ -259,10 +279,6 @@ public Optional getKeyPair() { return keyPair; } - public Optional getPkiKeyStoreConfiguration() { - return pkiKeyStoreConfiguration; - } - public boolean isStrictTxReplayProtectionEnabled() { return strictTxReplayProtectionEnabled; } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfigurationBuilder.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfigurationBuilder.java index 2a7f16d24fd..d8b5e0f9040 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfigurationBuilder.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeConfigurationBuilder.java @@ -24,6 +24,8 @@ import org.hyperledger.besu.crypto.KeyPair; import org.hyperledger.besu.ethereum.api.ApiConfiguration; import org.hyperledger.besu.ethereum.api.ImmutableApiConfiguration; +import org.hyperledger.besu.ethereum.api.jsonrpc.ImmutableInProcessRpcConfiguration; +import org.hyperledger.besu.ethereum.api.jsonrpc.InProcessRpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis; import org.hyperledger.besu.ethereum.api.jsonrpc.authentication.JwtAlgorithm; @@ -35,11 +37,12 @@ import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters.MutableInitValues; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration; import org.hyperledger.besu.ethereum.p2p.rlpx.connections.netty.TLSConfiguration; import org.hyperledger.besu.ethereum.permissioning.PermissioningConfiguration; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; -import org.hyperledger.besu.pki.config.PkiKeyStoreConfiguration; import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.genesis.GenesisConfigurationProvider; import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.pki.PKCS11Utils; @@ -63,14 +66,19 @@ public class BesuNodeConfigurationBuilder { .mutableInitValues( MutableInitValues.builder().coinbase(AddressHelpers.ofValue(1)).build()) .build(); - + private TransactionPoolConfiguration transactionPoolConfiguration = + TransactionPoolConfiguration.DEFAULT; private JsonRpcConfiguration jsonRpcConfiguration = JsonRpcConfiguration.createDefault(); private JsonRpcConfiguration engineRpcConfiguration = JsonRpcConfiguration.createEngineDefault(); private WebSocketConfiguration webSocketConfiguration = WebSocketConfiguration.createDefault(); private JsonRpcIpcConfiguration jsonRpcIpcConfiguration = new JsonRpcIpcConfiguration(); + private InProcessRpcConfiguration inProcessRpcConfiguration = + ImmutableInProcessRpcConfiguration.builder().build(); private MetricsConfiguration metricsConfiguration = MetricsConfiguration.builder().build(); private Optional permissioningConfiguration = Optional.empty(); private ApiConfiguration apiConfiguration = ImmutableApiConfiguration.builder().build(); + private DataStorageConfiguration dataStorageConfiguration = + DataStorageConfiguration.DEFAULT_FOREST_CONFIG; private String keyFilePath = null; private boolean devMode = true; private GenesisConfigurationProvider genesisConfigProvider = ignore -> Optional.empty(); @@ -91,7 +99,6 @@ public class BesuNodeConfigurationBuilder { private Optional privacyParameters = Optional.empty(); private List runCommand = new ArrayList<>(); private Optional keyPair = Optional.empty(); - private Optional pkiKeyStoreConfiguration = Optional.empty(); private Boolean strictTxReplayProtectionEnabled = false; private Map environment = new HashMap<>(); @@ -128,6 +135,12 @@ public BesuNodeConfigurationBuilder miningConfiguration(final MiningParameters m return this; } + public BesuNodeConfigurationBuilder transactionPoolConfiguration( + final TransactionPoolConfiguration transactionPoolConfiguration) { + this.transactionPoolConfiguration = transactionPoolConfiguration; + return this; + } + public BesuNodeConfigurationBuilder jsonRpcConfiguration( final JsonRpcConfiguration jsonRpcConfiguration) { this.jsonRpcConfiguration = jsonRpcConfiguration; @@ -249,6 +262,12 @@ public BesuNodeConfigurationBuilder jsonRpcIpcConfiguration( return this; } + public BesuNodeConfigurationBuilder inProcessRpcConfiguration( + final InProcessRpcConfiguration inProcessRpcConfiguration) { + this.inProcessRpcConfiguration = inProcessRpcConfiguration; + return this; + } + public BesuNodeConfigurationBuilder metricsConfiguration( final MetricsConfiguration metricsConfiguration) { this.metricsConfiguration = metricsConfiguration; @@ -418,13 +437,6 @@ public BesuNodeConfigurationBuilder p2pTLSEnabled(final String name, final Strin return this; } - public BesuNodeConfigurationBuilder pkiBlockCreationEnabled( - final PkiKeyStoreConfiguration pkiKeyStoreConfiguration) { - this.pkiKeyStoreConfiguration = Optional.of(pkiKeyStoreConfiguration); - - return this; - } - public BesuNodeConfigurationBuilder discoveryEnabled(final boolean discoveryEnabled) { this.discoveryEnabled = discoveryEnabled; return this; @@ -498,18 +510,27 @@ public BesuNodeConfigurationBuilder apiConfiguration(final ApiConfiguration apiC return this; } + public BesuNodeConfigurationBuilder dataStorageConfiguration( + final DataStorageConfiguration dataStorageConfiguration) { + this.dataStorageConfiguration = dataStorageConfiguration; + return this; + } + public BesuNodeConfiguration build() { return new BesuNodeConfiguration( name, dataPath, miningParameters, + transactionPoolConfiguration, jsonRpcConfiguration, Optional.of(engineRpcConfiguration), webSocketConfiguration, jsonRpcIpcConfiguration, + inProcessRpcConfiguration, metricsConfiguration, permissioningConfiguration, apiConfiguration, + dataStorageConfiguration, Optional.ofNullable(keyFilePath), devMode, network, @@ -530,7 +551,6 @@ public BesuNodeConfiguration build() { privacyParameters, runCommand, keyPair, - pkiKeyStoreConfiguration, strictTxReplayProtectionEnabled, environment); } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java index 4b24071ab33..0451d2a7212 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java @@ -1,16 +1,13 @@ /* * Copyright ConsenSys AG. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See - * the License for the + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 @@ -19,58 +16,53 @@ import static java.util.Arrays.asList; import static java.util.stream.Collectors.toList; +import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.ADMIN; +import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.IBFT; import org.hyperledger.besu.crypto.KeyPair; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.enclave.EnclaveFactory; import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration; -import org.hyperledger.besu.ethereum.core.AddressHelpers; -import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters; -import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters.MutableInitValues; -import org.hyperledger.besu.ethereum.core.InMemoryPrivacyStorageProvider; -import org.hyperledger.besu.ethereum.core.MiningParameters; -import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.permissioning.LocalPermissioningConfiguration; import org.hyperledger.besu.ethereum.permissioning.PermissioningConfiguration; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.pki.keystore.KeyStoreWrapper; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; import org.hyperledger.besu.tests.acceptance.dsl.node.Node; import org.hyperledger.besu.tests.acceptance.dsl.node.RunnableNode; import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.genesis.GenesisConfigurationFactory; import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.genesis.GenesisConfigurationFactory.CliqueOptions; -import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.pki.PkiKeystoreConfigurationFactory; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; -import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.function.UnaryOperator; -import io.vertx.core.Vertx; - public class BesuNodeFactory { private final NodeConfigurationFactory node = new NodeConfigurationFactory(); - private final PkiKeystoreConfigurationFactory pkiKeystoreConfigurationFactory = - new PkiKeystoreConfigurationFactory(); public BesuNode create(final BesuNodeConfiguration config) throws IOException { return new BesuNode( config.getName(), config.getDataPath(), config.getMiningParameters(), + config.getTransactionPoolConfiguration(), config.getJsonRpcConfiguration(), config.getEngineRpcConfiguration(), config.getWebSocketConfiguration(), config.getJsonRpcIpcConfiguration(), + config.getInProcessRpcConfiguration(), config.getMetricsConfiguration(), config.getPermissioningConfiguration(), config.getApiConfiguration(), + config.getDataStorageConfiguration(), config.getKeyFilePath(), config.isDevMode(), config.getNetwork(), @@ -91,7 +83,6 @@ public BesuNode create(final BesuNodeConfiguration config) throws IOException { config.getPrivacyParameters(), config.getRunCommand(), config.getKeyPair(), - config.getPkiKeyStoreConfiguration(), config.isStrictTxReplayProtectionEnabled(), config.getEnvironment()); } @@ -286,58 +277,25 @@ public BesuNode createNodeWithP2pDisabled(final String name) throws IOException .build()); } - public BesuNode createNodeWithMultiTenantedPrivacy( - final String name, - final String enclaveUrl, - final String authFile, - final String privTransactionSigningKey, - final boolean enableFlexiblePrivacy) - throws IOException, URISyntaxException { - final PrivacyParameters.Builder privacyParametersBuilder = new PrivacyParameters.Builder(); - final PrivacyParameters privacyParameters = - privacyParametersBuilder - .setMultiTenancyEnabled(true) - .setEnabled(true) - .setFlexiblePrivacyGroupsEnabled(enableFlexiblePrivacy) - .setStorageProvider(new InMemoryPrivacyStorageProvider()) - .setEnclaveFactory(new EnclaveFactory(Vertx.vertx())) - .setEnclaveUrl(URI.create(enclaveUrl)) - .setPrivateKeyPath( - Paths.get(ClassLoader.getSystemResource(privTransactionSigningKey).toURI())) - .build(); - - final MiningParameters miningParameters = - ImmutableMiningParameters.builder() - .mutableInitValues( - MutableInitValues.builder() - .isMiningEnabled(true) - .minTransactionGasPrice(Wei.ZERO) - .coinbase(AddressHelpers.ofValue(1)) - .build()) - .build(); - - return create( - new BesuNodeConfigurationBuilder() - .name(name) - .jsonRpcEnabled() - .jsonRpcAuthenticationConfiguration(authFile) - .enablePrivateTransactions() - .privacyParameters(privacyParameters) - .miningConfiguration(miningParameters) - .build()); - } - public BesuNode createArchiveNodeWithRpcDisabled(final String name) throws IOException { return create(new BesuNodeConfigurationBuilder().name(name).build()); } public BesuNode createPluginsNode( - final String name, final List plugins, final List extraCLIOptions) + final String name, + final List plugins, + final List extraCLIOptions, + final String... extraRpcApis) throws IOException { + + final List enableRpcApis = new ArrayList<>(Arrays.asList(extraRpcApis)); + enableRpcApis.addAll(List.of(IBFT.name(), ADMIN.name())); + return create( new BesuNodeConfigurationBuilder() .name(name) - .jsonRpcConfiguration(node.createJsonRpcWithIbft2AdminEnabledConfig()) + .jsonRpcConfiguration( + node.createJsonRpcWithRpcApiEnabledConfig(enableRpcApis.toArray(String[]::new))) .webSocketConfiguration(node.createWebSocketEnabledConfig()) .plugins(plugins) .extraCLIOptions(extraCLIOptions) @@ -374,18 +332,29 @@ public BesuNode createCliqueNode(final String name) throws IOException { public BesuNode createCliqueNode(final String name, final CliqueOptions cliqueOptions) throws IOException { - return createCliqueNodeWithExtraCliOptions(name, cliqueOptions, List.of()); + return createCliqueNodeWithExtraCliOptionsAndRpcApis(name, cliqueOptions, List.of()); } - public BesuNode createCliqueNodeWithExtraCliOptions( + public BesuNode createCliqueNodeWithExtraCliOptionsAndRpcApis( final String name, final CliqueOptions cliqueOptions, final List extraCliOptions) throws IOException { + return createCliqueNodeWithExtraCliOptionsAndRpcApis( + name, cliqueOptions, extraCliOptions, Set.of()); + } + + public BesuNode createCliqueNodeWithExtraCliOptionsAndRpcApis( + final String name, + final CliqueOptions cliqueOptions, + final List extraCliOptions, + final Set extraRpcApis) + throws IOException { return create( new BesuNodeConfigurationBuilder() .name(name) .miningEnabled() - .jsonRpcConfiguration(node.createJsonRpcWithCliqueEnabledConfig()) + .jsonRpcConfiguration(node.createJsonRpcWithCliqueEnabledConfig(extraRpcApis)) .webSocketConfiguration(node.createWebSocketEnabledConfig()) + .inProcessRpcConfiguration(node.createInProcessRpcConfiguration(extraRpcApis)) .devMode(false) .jsonRpcTxPool() .genesisConfigProvider( @@ -455,78 +424,70 @@ public BesuNode createIbft2Node(final String name, final String genesisFile) thr .build()); } - public BesuNode createIbft2Node(final String name) throws IOException { - return create( - new BesuNodeConfigurationBuilder() - .name(name) - .miningEnabled() - .jsonRpcConfiguration(node.createJsonRpcWithIbft2EnabledConfig(false)) - .webSocketConfiguration(node.createWebSocketEnabledConfig()) - .devMode(false) - .genesisConfigProvider(GenesisConfigurationFactory::createIbft2GenesisConfig) - .build()); - } - - public BesuNode createQbftNodeWithTLS(final String name, final String type) throws IOException { - return create( - new BesuNodeConfigurationBuilder() - .name(name) - .miningEnabled() - .p2pTLSEnabled(name, type) - .jsonRpcConfiguration(node.createJsonRpcWithQbftEnabledConfig(false)) - .webSocketConfiguration(node.createWebSocketEnabledConfig()) - .devMode(false) - .genesisConfigProvider(GenesisConfigurationFactory::createQbftGenesisConfig) - .build()); - } - - public BesuNode createQbftNodeWithTLSJKS(final String name) throws IOException { - return createQbftNodeWithTLS(name, KeyStoreWrapper.KEYSTORE_TYPE_JKS); - } - - public BesuNode createQbftNodeWithTLSPKCS12(final String name) throws IOException { - return createQbftNodeWithTLS(name, KeyStoreWrapper.KEYSTORE_TYPE_PKCS12); - } - - public BesuNode createQbftNodeWithTLSPKCS11(final String name) throws IOException { - return createQbftNodeWithTLS(name, KeyStoreWrapper.KEYSTORE_TYPE_PKCS11); - } + public BesuNode createIbft2Node( + final String name, final boolean fixedPort, final DataStorageFormat storageFormat) + throws IOException { + JsonRpcConfiguration rpcConfig = node.createJsonRpcWithIbft2EnabledConfig(false); + rpcConfig.addRpcApi("ADMIN,TXPOOL"); + if (fixedPort) { + rpcConfig.setPort( + Math.abs(name.hashCode() % 60000) + + 1024); // Generate a consistent port for p2p based on node name + } - public BesuNode createQbftNode(final String name) throws IOException { - return create( + BesuNodeConfigurationBuilder builder = new BesuNodeConfigurationBuilder() .name(name) .miningEnabled() - .jsonRpcConfiguration(node.createJsonRpcWithQbftEnabledConfig(false)) + .jsonRpcConfiguration(rpcConfig) .webSocketConfiguration(node.createWebSocketEnabledConfig()) .devMode(false) - .genesisConfigProvider(GenesisConfigurationFactory::createQbftGenesisConfig) - .build()); - } - - public BesuNode createPkiQbftJKSNode(final String name) throws IOException { - return createPkiQbftNode(KeyStoreWrapper.KEYSTORE_TYPE_JKS, name); - } - - public BesuNode createPkiQbftPKCS11Node(final String name) throws IOException { - return createPkiQbftNode(KeyStoreWrapper.KEYSTORE_TYPE_PKCS11, name); + .dataStorageConfiguration( + storageFormat == DataStorageFormat.FOREST + ? DataStorageConfiguration.DEFAULT_FOREST_CONFIG + : DataStorageConfiguration.DEFAULT_BONSAI_CONFIG) + .genesisConfigProvider(GenesisConfigurationFactory::createIbft2GenesisConfig); + if (fixedPort) { + builder.p2pPort( + Math.abs(name.hashCode() % 60000) + + 1024 + + 500); // Generate a consistent port for p2p based on node name (+ 500 to avoid + // clashing with RPC port or other nodes with a similar name) + } + return create(builder.build()); } - public BesuNode createPkiQbftPKCS12Node(final String name) throws IOException { - return createPkiQbftNode(KeyStoreWrapper.KEYSTORE_TYPE_PKCS12, name); - } + public BesuNode createQbftNode( + final String name, final boolean fixedPort, final DataStorageFormat storageFormat) + throws IOException { + JsonRpcConfiguration rpcConfig = node.createJsonRpcWithQbftEnabledConfig(false); + rpcConfig.addRpcApi("ADMIN,TXPOOL"); + if (fixedPort) { + rpcConfig.setPort( + Math.abs(name.hashCode() % 60000) + + 1024); // Generate a consistent port for p2p based on node name + } - public BesuNode createPkiQbftNode(final String type, final String name) throws IOException { - return create( + BesuNodeConfigurationBuilder builder = new BesuNodeConfigurationBuilder() .name(name) .miningEnabled() - .jsonRpcConfiguration(node.createJsonRpcWithQbftEnabledConfig(false)) + .jsonRpcConfiguration(rpcConfig) .webSocketConfiguration(node.createWebSocketEnabledConfig()) .devMode(false) - .genesisConfigProvider(GenesisConfigurationFactory::createQbftGenesisConfig) - .pkiBlockCreationEnabled(pkiKeystoreConfigurationFactory.createPkiConfig(type, name)) - .build()); + .dataStorageConfiguration( + storageFormat == DataStorageFormat.FOREST + ? DataStorageConfiguration.DEFAULT_FOREST_CONFIG + : DataStorageConfiguration.DEFAULT_BONSAI_CONFIG) + .genesisConfigProvider(GenesisConfigurationFactory::createQbftGenesisConfig); + if (fixedPort) { + builder.p2pPort( + Math.abs(name.hashCode() % 60000) + + 1024 + + 500); // Generate a consistent port for p2p based on node name (+ 500 to avoid + // clashing with RPC port or other nodes with a similar name) + } + return create(builder.build()); } public BesuNode createCustomGenesisNode( @@ -582,7 +543,7 @@ public BesuNode createCliqueNodeWithValidators(final String name, final String.. new BesuNodeConfigurationBuilder() .name(name) .miningEnabled() - .jsonRpcConfiguration(node.createJsonRpcWithCliqueEnabledConfig()) + .jsonRpcConfiguration(node.createJsonRpcWithCliqueEnabledConfig(Set.of())) .webSocketConfiguration(node.createWebSocketEnabledConfig()) .jsonRpcTxPool() .devMode(false) @@ -686,41 +647,6 @@ public BesuNode createQbftNodeWithContractBasedValidators( .build()); } - public BesuNode createPkiQbftJKSNodeWithValidators(final String name, final String... validators) - throws IOException { - return createPkiQbftNodeWithValidators(KeyStoreWrapper.KEYSTORE_TYPE_JKS, name, validators); - } - - public BesuNode createPkiQbftPKCS11NodeWithValidators( - final String name, final String... validators) throws IOException { - return createPkiQbftNodeWithValidators(KeyStoreWrapper.KEYSTORE_TYPE_PKCS11, name, validators); - } - - public BesuNode createPkiQbftPKCS12NodeWithValidators( - final String name, final String... validators) throws IOException { - return createPkiQbftNodeWithValidators(KeyStoreWrapper.KEYSTORE_TYPE_PKCS12, name, validators); - } - - public BesuNode createPkiQbftNodeWithValidators( - final String type, final String name, final String... validators) throws IOException { - - return create( - new BesuNodeConfigurationBuilder() - .name(name) - .miningEnabled() - .jsonRpcConfiguration(node.createJsonRpcWithQbftEnabledConfig(false)) - .webSocketConfiguration(node.createWebSocketEnabledConfig()) - .devMode(false) - .pkiBlockCreationEnabled(pkiKeystoreConfigurationFactory.createPkiConfig(type, name)) - .genesisConfigProvider( - nodes -> - node.createGenesisConfigForValidators( - asList(validators), - nodes, - GenesisConfigurationFactory::createQbftGenesisConfig)) - .build()); - } - public BesuNode createNodeWithStaticNodes(final String name, final List staticNodes) throws IOException { diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/NodeConfigurationFactory.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/NodeConfigurationFactory.java index f2682993f8a..c2e82c0f835 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/NodeConfigurationFactory.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/NodeConfigurationFactory.java @@ -22,6 +22,8 @@ import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.MINER; import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.QBFT; +import org.hyperledger.besu.ethereum.api.jsonrpc.ImmutableInProcessRpcConfiguration; +import org.hyperledger.besu.ethereum.api.jsonrpc.InProcessRpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration; import org.hyperledger.besu.tests.acceptance.dsl.node.RunnableNode; @@ -30,8 +32,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; public class NodeConfigurationFactory { @@ -44,8 +48,10 @@ public Optional createGenesisConfigForValidators( return genesisConfigProvider.create(nodes); } - public JsonRpcConfiguration createJsonRpcWithCliqueEnabledConfig() { - return createJsonRpcWithRpcApiEnabledConfig(CLIQUE.name()); + public JsonRpcConfiguration createJsonRpcWithCliqueEnabledConfig(final Set extraRpcApis) { + final var enabledApis = new HashSet<>(extraRpcApis); + enabledApis.add(CLIQUE.name()); + return createJsonRpcWithRpcApiEnabledConfig(enabledApis.toArray(String[]::new)); } public JsonRpcConfiguration createJsonRpcWithIbft2EnabledConfig(final boolean minerEnabled) { @@ -90,4 +96,14 @@ public JsonRpcConfiguration createJsonRpcWithRpcApiEnabledConfig(final String... jsonRpcConfig.setRpcApis(rpcApis); return jsonRpcConfig; } + + public InProcessRpcConfiguration createInProcessRpcConfiguration(final Set extraRpcApis) { + final Set rpcApis = + new HashSet<>(ImmutableInProcessRpcConfiguration.DEFAULT_IN_PROCESS_RPC_APIS); + rpcApis.addAll(extraRpcApis); + return ImmutableInProcessRpcConfiguration.builder() + .inProcessRpcApis(rpcApis) + .isEnabled(true) + .build(); + } } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/genesis/QbftValidatorContractConfigFactory.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/genesis/QbftValidatorContractConfigFactory.java index 1afdae90ffc..7bf9e2f31af 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/genesis/QbftValidatorContractConfigFactory.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/genesis/QbftValidatorContractConfigFactory.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/pki/PKCS11Utils.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/pki/PKCS11Utils.java index 7c77722c8b2..76799d12e87 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/pki/PKCS11Utils.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/pki/PKCS11Utils.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.tests.acceptance.dsl.node.configuration.pki; import java.io.File; diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/pki/PkiKeystoreConfigurationFactory.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/pki/PkiKeystoreConfigurationFactory.java deleted file mode 100644 index ac7f814d433..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/pki/PkiKeystoreConfigurationFactory.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See - * the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.besu.tests.acceptance.dsl.node.configuration.pki; - -import static org.hyperledger.besu.pki.util.TestCertificateUtils.createKeyPair; -import static org.hyperledger.besu.pki.util.TestCertificateUtils.createSelfSignedCertificate; -import static org.hyperledger.besu.pki.util.TestCertificateUtils.issueCertificate; - -import org.hyperledger.besu.pki.config.PkiKeyStoreConfiguration; -import org.hyperledger.besu.pki.keystore.KeyStoreWrapper; -import org.hyperledger.besu.pki.util.TestCertificateUtils; - -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.KeyPair; -import java.security.KeyStore; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Objects; -import java.util.UUID; - -public class PkiKeystoreConfigurationFactory { - - /* - PKCS11 config files - */ - final String NSSCONFIG_PATH_STRING = "/pki-certs/%s/nss.cfg"; - final String NSSPIN_PATH_STRING = "/pki-certs/%s/nsspin.txt"; - final String TRUSTSTORE_PATH_STRING = "/pki-certs/%s/truststore.p12"; - final String CRL_PATH_STRING = "/pki-certs/crl/crl.pem"; - - /* - Software keystore config - */ - public static final String KEYSTORE_DEFAULT_PASSWORD = "password"; - - private KeyPair caKeyPair; - private X509Certificate caCertificate; - private Path trustStoreFile; - private Path passwordFile; - - public PkiKeyStoreConfiguration createPkiConfig(final String type, final String name) { - if (KeyStoreWrapper.KEYSTORE_TYPE_PKCS11.equals(type)) { - return createPKCS11PkiConfig(name); - } else { - return createSoftwareKeyStorePkiConfig(type, name); - } - } - - private PkiKeyStoreConfiguration createPKCS11PkiConfig(final String name) { - final PkiKeyStoreConfiguration.Builder pkiKeyStoreConfigBuilder = - new PkiKeyStoreConfiguration.Builder(); - - try { - pkiKeyStoreConfigBuilder - .withKeyStoreType(KeyStoreWrapper.KEYSTORE_TYPE_PKCS11) - .withKeyStorePath( - PKCS11Utils.initNSSConfigFile( - readResourceAsPath(String.format(NSSCONFIG_PATH_STRING, name)))) - .withKeyStorePasswordPath(readResourceAsPath(String.format(NSSPIN_PATH_STRING, name))) - .withTrustStoreType(KeyStoreWrapper.KEYSTORE_TYPE_PKCS12) - .withTrustStorePath(readResourceAsPath(String.format(TRUSTSTORE_PATH_STRING, name))) - .withTrustStorePasswordPath(readResourceAsPath(String.format(NSSPIN_PATH_STRING, name))) - .withCrlFilePath(readResourceAsPath(CRL_PATH_STRING)) - .withCertificateAlias(name); - - } catch (Exception e) { - throw new RuntimeException(e); - } - - return pkiKeyStoreConfigBuilder.build(); - } - - private PkiKeyStoreConfiguration createSoftwareKeyStorePkiConfig( - final String type, final String name) { - PkiKeyStoreConfiguration.Builder pkiKeyStoreConfigBuilder = - new PkiKeyStoreConfiguration.Builder(); - - pkiKeyStoreConfigBuilder.withTrustStoreType(type); - pkiKeyStoreConfigBuilder.withTrustStorePath(createTrustStore(type)); - pkiKeyStoreConfigBuilder.withTrustStorePasswordPath(passwordFile); - - pkiKeyStoreConfigBuilder.withKeyStoreType(type); - pkiKeyStoreConfigBuilder.withKeyStorePath(createKeyStore(type, name)); - pkiKeyStoreConfigBuilder.withKeyStorePasswordPath(passwordFile); - - pkiKeyStoreConfigBuilder.withCertificateAlias(name); - - return pkiKeyStoreConfigBuilder.build(); - } - - private Path createTrustStore(final String type) { - // Only create the truststore if this is the first time this method is being called - if (caKeyPair == null) { - try { - caKeyPair = createKeyPair(TestCertificateUtils.Algorithm.RSA); - caCertificate = createSelfSignedCertificate("ca", notBefore(), notAfter(), caKeyPair); - - final KeyStore truststore = KeyStore.getInstance(type); - truststore.load(null, null); - truststore.setCertificateEntry("ca", caCertificate); - - final String uniqueId = UUID.randomUUID().toString(); - trustStoreFile = writeKeyStoreFile(truststore, "truststore", uniqueId); - passwordFile = writePasswordFile(KEYSTORE_DEFAULT_PASSWORD, "password", uniqueId); - } catch (final Exception e) { - throw new RuntimeException("Error creating truststore for Acceptance Test", e); - } - } - - return trustStoreFile; - } - - private Path createKeyStore(final String type, final String alias) { - if (caKeyPair == null) { - createTrustStore(type); - } - - final KeyPair kp = createKeyPair(TestCertificateUtils.Algorithm.RSA); - final X509Certificate certificate = - issueCertificate(caCertificate, caKeyPair, "validator", notBefore(), notAfter(), kp, false); - - try { - final KeyStore keyStore = KeyStore.getInstance(type); - keyStore.load(null, null); - keyStore.setKeyEntry( - alias, - kp.getPrivate(), - KEYSTORE_DEFAULT_PASSWORD.toCharArray(), - new Certificate[] {certificate, caCertificate}); - - final String id = UUID.randomUUID().toString(); - return writeKeyStoreFile(keyStore, "keystore", id); - } catch (final Exception e) { - throw new RuntimeException("Error creating keystore for Acceptance Test", e); - } - } - - private Path writeKeyStoreFile( - final KeyStore keyStore, final String prefix, final String suffix) { - try { - final Path file = Files.createTempFile(prefix, suffix != null ? suffix : ""); - file.toFile().deleteOnExit(); - final FileOutputStream keyStoreFOS = new FileOutputStream(file.toFile()); - keyStore.store(keyStoreFOS, KEYSTORE_DEFAULT_PASSWORD.toCharArray()); - - return file; - } catch (final Exception e) { - throw new RuntimeException("Error creating keystore file", e); - } - } - - private Path writePasswordFile(final String password, final String prefix, final String suffix) { - try { - final Path file = Files.createTempFile(prefix, suffix); - file.toFile().deleteOnExit(); - Files.write(file, password.getBytes(StandardCharsets.UTF_8)); - return file; - } catch (final IOException e) { - throw new RuntimeException("Error creating password file", e); - } - } - - private Instant notBefore() { - return Instant.now().minus(1, ChronoUnit.DAYS); - } - - private Instant notAfter() { - return Instant.now().plus(10, ChronoUnit.DAYS); - } - - private Path readResourceAsPath(final String path) throws Exception { - return Path.of(Objects.requireNonNull(this.getClass().getResource(path)).toURI()); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/privacy/PrivacyNodeConfiguration.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/privacy/PrivacyNodeConfiguration.java deleted file mode 100644 index 05c989e54aa..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/privacy/PrivacyNodeConfiguration.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.node.configuration.privacy; - -import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.BesuNodeConfiguration; -import org.hyperledger.enclave.testutil.EnclaveKeyConfiguration; - -public class PrivacyNodeConfiguration { - - private final boolean isFlexiblePrivacyGroupEnabled; - private final boolean isMultitenancyEnabled; - private final boolean isPrivacyPluginEnabled; - private final BesuNodeConfiguration besuConfig; - private final EnclaveKeyConfiguration keyConfig; - - PrivacyNodeConfiguration( - final BesuNodeConfiguration besuConfig, final EnclaveKeyConfiguration keyConfig) { - this(false, false, false, besuConfig, keyConfig); - } - - public PrivacyNodeConfiguration( - final boolean isFlexiblePrivacyGroupEnabled, - final boolean isMultitenancyEnabled, - final boolean isPrivacyPluginEnabled, - final BesuNodeConfiguration besuConfig, - final EnclaveKeyConfiguration keyConfig) { - this.isFlexiblePrivacyGroupEnabled = isFlexiblePrivacyGroupEnabled; - this.besuConfig = besuConfig; - this.keyConfig = keyConfig; - this.isMultitenancyEnabled = isMultitenancyEnabled; - this.isPrivacyPluginEnabled = isPrivacyPluginEnabled; - } - - public boolean isFlexiblePrivacyGroupEnabled() { - return isFlexiblePrivacyGroupEnabled; - } - - public boolean isMultitenancyEnabled() { - return isMultitenancyEnabled; - } - - public boolean isPrivacyPluginEnabled() { - return isPrivacyPluginEnabled; - } - - public BesuNodeConfiguration getBesuConfig() { - return besuConfig; - } - - public EnclaveKeyConfiguration getKeyConfig() { - return keyConfig; - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/privacy/PrivacyNodeFactory.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/privacy/PrivacyNodeFactory.java deleted file mode 100644 index 03188db8a27..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/privacy/PrivacyNodeFactory.java +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.node.configuration.privacy; - -import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.BesuNodeConfigurationBuilder; -import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.NodeConfigurationFactory; -import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.genesis.GenesisConfigurationFactory; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.account.PrivacyAccount; -import org.hyperledger.enclave.testutil.EnclaveEncryptorType; -import org.hyperledger.enclave.testutil.EnclaveKeyConfiguration; -import org.hyperledger.enclave.testutil.EnclaveType; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import io.vertx.core.Vertx; -import org.testcontainers.containers.Network; - -public class PrivacyNodeFactory { - - private final NodeConfigurationFactory node = new NodeConfigurationFactory(); - private final Vertx vertx; - - public PrivacyNodeFactory(final Vertx vertx) { - this.vertx = vertx; - } - - public PrivacyNode create( - final PrivacyNodeConfiguration privacyNodeConfig, - final EnclaveType enclaveType, - final Optional containerNetwork) - throws IOException { - return new PrivacyNode(privacyNodeConfig, vertx, enclaveType, containerNetwork); - } - - public PrivacyNode createPrivateTransactionEnabledMinerNode( - final String name, - final PrivacyAccount privacyAccount, - final EnclaveType enclaveType, - final Optional containerNetwork, - final boolean isFlexiblePrivacyGroupEnabled, - final boolean isMultitenancyEnabled, - final boolean isPrivacyPluginEnabled) - throws IOException { - return create( - new PrivacyNodeConfiguration( - isFlexiblePrivacyGroupEnabled, - isMultitenancyEnabled, - isPrivacyPluginEnabled, - new BesuNodeConfigurationBuilder() - .name(name) - .miningEnabled() - .jsonRpcEnabled() - .webSocketEnabled() - .enablePrivateTransactions() - .keyFilePath(privacyAccount.getPrivateKeyPath()) - .plugins(Collections.singletonList("testPlugins")) - .extraCLIOptions(List.of("--plugin-privacy-service-encryption-prefix=0xAA")) - .build(), - new EnclaveKeyConfiguration( - privacyAccount.getEnclaveKeyPaths(), - privacyAccount.getEnclavePrivateKeyPaths(), - privacyAccount.getEnclaveEncryptorType())), - enclaveType, - containerNetwork); - } - - public PrivacyNode createPrivateTransactionEnabledNode( - final String name, - final PrivacyAccount privacyAccount, - final EnclaveType enclaveType, - final Optional containerNetwork, - final boolean isFlexiblePrivacyGroupEnabled, - final boolean isMultitenancyEnabled, - final boolean isPrivacyPluginEnabled) - throws IOException { - return create( - new PrivacyNodeConfiguration( - isFlexiblePrivacyGroupEnabled, - isMultitenancyEnabled, - isPrivacyPluginEnabled, - new BesuNodeConfigurationBuilder() - .name(name) - .jsonRpcEnabled() - .keyFilePath(privacyAccount.getPrivateKeyPath()) - .enablePrivateTransactions() - .webSocketEnabled() - .plugins(Collections.singletonList("testPlugins")) - .extraCLIOptions(List.of("--plugin-privacy-service-encryption-prefix=0xBB")) - .build(), - new EnclaveKeyConfiguration( - privacyAccount.getEnclaveKeyPaths(), - privacyAccount.getEnclavePrivateKeyPaths(), - privacyAccount.getEnclaveEncryptorType())), - enclaveType, - containerNetwork); - } - - public PrivacyNode createIbft2NodePrivacyEnabled( - final String name, - final PrivacyAccount privacyAccount, - final boolean minerEnabled, - final EnclaveType enclaveType, - final Optional containerNetwork, - final boolean isFlexiblePrivacyGroupEnabled, - final boolean isMultitenancyEnabled, - final boolean isPrivacyPluginEnabled, - final String unrestrictedPrefix) - throws IOException { - return create( - new PrivacyNodeConfiguration( - isFlexiblePrivacyGroupEnabled, - isMultitenancyEnabled, - isPrivacyPluginEnabled, - new BesuNodeConfigurationBuilder() - .name(name) - .miningEnabled() - .jsonRpcConfiguration(node.createJsonRpcWithIbft2EnabledConfig(minerEnabled)) - .webSocketConfiguration(node.createWebSocketEnabledConfig()) - .devMode(false) - .genesisConfigProvider(GenesisConfigurationFactory::createPrivacyIbft2GenesisConfig) - .keyFilePath(privacyAccount.getPrivateKeyPath()) - .enablePrivateTransactions() - .plugins(Collections.singletonList("testPlugins")) - .extraCLIOptions( - List.of("--plugin-privacy-service-encryption-prefix=" + unrestrictedPrefix)) - .build(), - new EnclaveKeyConfiguration( - privacyAccount.getEnclaveKeyPaths(), - privacyAccount.getEnclavePrivateKeyPaths(), - privacyAccount.getEnclaveEncryptorType())), - enclaveType, - containerNetwork); - } - - public PrivacyNode createIbft2NodePrivacyEnabledWithGenesis( - final String name, - final PrivacyAccount privacyAccount, - final boolean minerEnabled, - final EnclaveType enclaveType, - final Optional containerNetwork, - final boolean isFlexiblePrivacyGroupEnabled, - final boolean isMultitenancyEnabled, - final boolean isPrivacyPluginEnabled, - final String unrestrictedPrefix) - throws IOException { - return create( - new PrivacyNodeConfiguration( - isFlexiblePrivacyGroupEnabled, - isMultitenancyEnabled, - isPrivacyPluginEnabled, - new BesuNodeConfigurationBuilder() - .name(name) - .miningEnabled() - .jsonRpcConfiguration(node.createJsonRpcWithIbft2EnabledConfig(minerEnabled)) - .webSocketConfiguration(node.createWebSocketEnabledConfig()) - .devMode(false) - .genesisConfigProvider(GenesisConfigurationFactory::createPrivacyIbft2GenesisConfig) - .keyFilePath(privacyAccount.getPrivateKeyPath()) - .enablePrivateTransactions() - .plugins(Collections.singletonList("testPlugins")) - .extraCLIOptions( - List.of( - "--plugin-privacy-service-encryption-prefix=" + unrestrictedPrefix, - "--plugin-privacy-service-genesis-enabled=true")) - .build(), - new EnclaveKeyConfiguration( - privacyAccount.getEnclaveKeyPaths(), - privacyAccount.getEnclavePrivateKeyPaths(), - privacyAccount.getEnclaveEncryptorType())), - enclaveType, - containerNetwork); - } - - public PrivacyNode createQbftNodePrivacyEnabled( - final String name, - final PrivacyAccount privacyAccount, - final EnclaveType enclaveType, - final Optional containerNetwork, - final boolean isFlexiblePrivacyGroupEnabled, - final boolean isMultitenancyEnabled, - final boolean isPrivacyPluginEnabled, - final String unrestrictedPrefix) - throws IOException { - return create( - new PrivacyNodeConfiguration( - isFlexiblePrivacyGroupEnabled, - isMultitenancyEnabled, - isPrivacyPluginEnabled, - new BesuNodeConfigurationBuilder() - .name(name) - .miningEnabled() - .jsonRpcConfiguration(node.createJsonRpcWithQbftEnabledConfig(false)) - .webSocketConfiguration(node.createWebSocketEnabledConfig()) - .devMode(false) - .genesisConfigProvider(GenesisConfigurationFactory::createQbftGenesisConfig) - .keyFilePath(privacyAccount.getPrivateKeyPath()) - .enablePrivateTransactions() - .plugins(Collections.singletonList("testPlugins")) - .extraCLIOptions( - List.of("--plugin-privacy-service-encryption-prefix=" + unrestrictedPrefix)) - .build(), - new EnclaveKeyConfiguration( - privacyAccount.getEnclaveKeyPaths(), - privacyAccount.getEnclavePrivateKeyPaths(), - privacyAccount.getEnclaveEncryptorType())), - enclaveType, - containerNetwork); - } - - public PrivacyNode createFlexiblePrivacyGroupEnabledMinerNode( - final String name, - final PrivacyAccount privacyAccount, - final boolean multiTenancyEnabled, - final EnclaveType enclaveType, - final Optional containerNetwork) - throws IOException, URISyntaxException { - final BesuNodeConfigurationBuilder besuNodeConfigurationBuilder = - new BesuNodeConfigurationBuilder(); - if (multiTenancyEnabled) { - final String authPrivTomlPath = - EnclaveEncryptorType.EC.equals(privacyAccount.getEnclaveEncryptorType()) - ? "authentication/auth_priv_ec_pubkey.toml" - : "authentication/auth_priv.toml"; - besuNodeConfigurationBuilder.jsonRpcAuthenticationConfiguration(authPrivTomlPath); - } - return create( - new PrivacyNodeConfiguration( - true, - multiTenancyEnabled, - false, - besuNodeConfigurationBuilder - .name(name) - .miningEnabled() - .jsonRpcEnabled() - .webSocketEnabled() - .enablePrivateTransactions() - .plugins(Collections.singletonList("testPlugins")) - .extraCLIOptions(List.of("--plugin-privacy-service-genesis-enabled=true")) - .keyFilePath(privacyAccount.getPrivateKeyPath()) - .build(), - new EnclaveKeyConfiguration( - privacyAccount.getEnclaveKeyPaths(), - privacyAccount.getEnclavePrivateKeyPaths(), - privacyAccount.getEnclaveEncryptorType())), - enclaveType, - containerNetwork); - } - - public PrivacyNode createFlexiblePrivacyGroupEnabledNode( - final String name, - final PrivacyAccount privacyAccount, - final boolean multiTenancyEnabled, - final EnclaveType enclaveType, - final Optional containerNetwork) - throws IOException { - return create( - new PrivacyNodeConfiguration( - true, - multiTenancyEnabled, - false, - new BesuNodeConfigurationBuilder() - .name(name) - .jsonRpcEnabled() - .keyFilePath(privacyAccount.getPrivateKeyPath()) - .enablePrivateTransactions() - .plugins(Collections.singletonList("testPlugins")) - .extraCLIOptions(List.of("--plugin-privacy-service-genesis-enabled=true")) - .webSocketEnabled() - .build(), - new EnclaveKeyConfiguration( - privacyAccount.getEnclaveKeyPaths(), - privacyAccount.getEnclavePrivateKeyPaths(), - privacyAccount.getEnclaveEncryptorType())), - enclaveType, - containerNetwork); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/ParameterizedEnclaveTestBase.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/ParameterizedEnclaveTestBase.java deleted file mode 100644 index 581fd71722d..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/ParameterizedEnclaveTestBase.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy; - -import static org.hyperledger.enclave.testutil.EnclaveEncryptorType.EC; -import static org.hyperledger.enclave.testutil.EnclaveEncryptorType.NACL; -import static org.hyperledger.enclave.testutil.EnclaveType.NOOP; -import static org.hyperledger.enclave.testutil.EnclaveType.TESSERA; -import static org.web3j.utils.Restriction.RESTRICTED; -import static org.web3j.utils.Restriction.UNRESTRICTED; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction.PluginCreateRandomPrivacyGroupIdTransaction; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction.RestrictedCreatePrivacyGroupTransaction; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; -import org.hyperledger.enclave.testutil.EnclaveEncryptorType; -import org.hyperledger.enclave.testutil.EnclaveType; - -import java.util.Arrays; -import java.util.Collection; - -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; -import org.web3j.utils.Restriction; - -@RunWith(Parameterized.class) -public abstract class ParameterizedEnclaveTestBase extends PrivacyAcceptanceTestBase { - protected final Restriction restriction; - protected final EnclaveType enclaveType; - protected final EnclaveEncryptorType enclaveEncryptorType; - - protected ParameterizedEnclaveTestBase( - final Restriction restriction, - final EnclaveType enclaveType, - final EnclaveEncryptorType enclaveEncryptorType) { - this.restriction = restriction; - this.enclaveType = enclaveType; - this.enclaveEncryptorType = enclaveEncryptorType; - } - - @Parameters(name = "{0} tx with {1} enclave and {2} encryptor type") - public static Collection params() { - return Arrays.asList( - new Object[][] { - {RESTRICTED, TESSERA, NACL}, - {RESTRICTED, TESSERA, EC}, - {UNRESTRICTED, NOOP, EnclaveEncryptorType.NOOP} - }); - } - - public Transaction createPrivacyGroup( - final String name, final String description, final PrivacyNode... nodes) { - - if (restriction == RESTRICTED) { - return new RestrictedCreatePrivacyGroupTransaction(name, description, nodes); - } else if (restriction == UNRESTRICTED) { - return new PluginCreateRandomPrivacyGroupIdTransaction(); - } else { - throw new RuntimeException("Do not know how to handle " + restriction); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/PrivacyAcceptanceTestBase.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/PrivacyAcceptanceTestBase.java deleted file mode 100644 index 8e12cbc7a80..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/PrivacyAcceptanceTestBase.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.WaitUtils; -import org.hyperledger.besu.tests.acceptance.dsl.condition.eth.EthConditions; -import org.hyperledger.besu.tests.acceptance.dsl.condition.net.NetConditions; -import org.hyperledger.besu.tests.acceptance.dsl.condition.priv.PrivConditions; -import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.privacy.PrivacyNodeFactory; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.condition.PrivateContractVerifier; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.condition.PrivateTransactionVerifier; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.contract.PrivateContractTransactions; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction.PrivacyTransactions; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.contract.ContractTransactions; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.eth.EthTransactions; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.net.NetTransactions; - -import java.math.BigInteger; - -import io.vertx.core.Vertx; -import org.junit.After; -import org.junit.ClassRule; -import org.junit.rules.TemporaryFolder; - -public class PrivacyAcceptanceTestBase { - @ClassRule public static final TemporaryFolder privacy = new TemporaryFolder(); - - protected final PrivacyTransactions privacyTransactions; - protected final PrivateContractVerifier privateContractVerifier; - protected final PrivateTransactionVerifier privateTransactionVerifier; - protected final PrivacyNodeFactory privacyBesu; - protected final PrivateContractTransactions privateContractTransactions; - protected final PrivConditions priv; - protected final PrivacyCluster privacyCluster; - protected final ContractTransactions contractTransactions; - protected final NetConditions net; - protected final EthTransactions ethTransactions; - protected final EthConditions eth; - private final Vertx vertx = Vertx.vertx(); - - public PrivacyAcceptanceTestBase() { - ethTransactions = new EthTransactions(); - net = new NetConditions(new NetTransactions()); - privacyTransactions = new PrivacyTransactions(); - privateContractVerifier = new PrivateContractVerifier(); - privateTransactionVerifier = new PrivateTransactionVerifier(privacyTransactions); - privacyBesu = new PrivacyNodeFactory(vertx); - privateContractTransactions = new PrivateContractTransactions(); - privacyCluster = new PrivacyCluster(net); - priv = - new PrivConditions( - new org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy - .PrivacyTransactions()); - contractTransactions = new ContractTransactions(); - eth = new EthConditions(ethTransactions); - } - - protected void waitForBlockHeight(final PrivacyNode node, final long blockchainHeight) { - WaitUtils.waitFor( - 120, - () -> - assertThat(node.execute(ethTransactions.blockNumber())) - .isGreaterThanOrEqualTo(BigInteger.valueOf(blockchainHeight))); - } - - @After - public void tearDownAcceptanceTestBase() { - privacyCluster.close(); - vertx.close(); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/PrivacyCluster.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/PrivacyCluster.java deleted file mode 100644 index 3d1fc148059..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/PrivacyCluster.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy; - -import static java.util.Collections.emptyList; -import static java.util.function.Predicate.not; - -import org.hyperledger.besu.tests.acceptance.dsl.condition.net.NetConditions; -import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNodeRunner; -import org.hyperledger.besu.tests.acceptance.dsl.node.RunnableNode; -import org.hyperledger.enclave.testutil.EnclaveType; -import org.hyperledger.enclave.testutil.TesseraTestHarness; - -import java.net.URI; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class PrivacyCluster { - private static final Logger LOG = LoggerFactory.getLogger(PrivacyCluster.class); - private final NetConditions net; - private final BesuNodeRunner besuNodeRunner; - - private List nodes = emptyList(); - private List runnableNodes = emptyList(); - private PrivacyNode bootNode; - - public PrivacyCluster(final NetConditions net) { - this.net = net; - this.besuNodeRunner = BesuNodeRunner.instance(); - } - - public void start(final PrivacyNode... nodes) { - start(Arrays.asList(nodes)); - } - - public void start(final List nodes) { - startNodes(nodes); - awaitPeerCount(nodes); - } - - public void startNodes(final PrivacyNode... nodes) { - startNodes(Arrays.asList(nodes)); - } - - public void startNodes(final List nodes) { - if (nodes.isEmpty()) { - throw new IllegalArgumentException("Can't start a cluster with no nodes"); - } - this.nodes = nodes; - this.runnableNodes = nodes.stream().map(PrivacyNode::getBesu).collect(Collectors.toList()); - - final Optional bootNode = selectAndStartBootnode(nodes); - - nodes.parallelStream() - .filter(node -> bootNode.map(boot -> boot != node).orElse(true)) - .forEach(this::startNode); - } - - public void awaitPeerCount(final PrivacyNode... nodes) { - awaitPeerCount(Arrays.asList(nodes)); - } - - public void awaitPeerCount(final List nodes) { - for (final PrivacyNode node : nodes) { - LOG.info("Awaiting peer discovery for node {}", node.getName()); - node.awaitPeerDiscovery(net.awaitPeerCount(nodes.size() - 1)); - } - - verifyAllEnclaveNetworkConnections(); - } - - public List getNodes() { - return nodes; - } - - /** Verify that each Enclave has connected to every other Enclave */ - public void verifyAllEnclaveNetworkConnections() { - nodes.forEach( - privacyNode -> { - final List otherNodes = - nodes.stream() - .filter(not(privacyNode::equals)) - .collect(Collectors.toUnmodifiableList()); - privacyNode.testEnclaveConnection(otherNodes); - }); - } - - public void stop() { - for (final PrivacyNode node : nodes) { - besuNodeRunner.stopNode(node.getBesu()); - } - } - - public void stopNode(final PrivacyNode node) { - node.getEnclave().stop(); - besuNodeRunner.stopNode(node.getBesu()); - } - - public void close() { - stop(); - for (final PrivacyNode node : nodes) { - node.close(); - } - besuNodeRunner.shutdown(); - } - - private Optional selectAndStartBootnode(final List nodes) { - final Optional bootNode = - nodes.stream() - .filter(node -> node.getConfiguration().isBootnodeEligible()) - .filter(node -> node.getConfiguration().isP2pEnabled()) - .filter(node -> node.getConfiguration().isDiscoveryEnabled()) - .findFirst(); - - bootNode.ifPresent( - b -> { - LOG.info("Selected node {} as bootnode", b.getName()); - startNode(b, true); - this.bootNode = b; - }); - - return bootNode; - } - - private void startNode(final PrivacyNode node) { - startNode(node, false); - } - - private void startNode(final PrivacyNode node, final boolean isBootNode) { - node.getConfiguration() - .setBootnodes(isBootNode ? emptyList() : Collections.singletonList(bootNode.enodeUrl())); - - node.getConfiguration() - .getGenesisConfigProvider() - .create(runnableNodes) - .ifPresent(node.getConfiguration()::setGenesisConfig); - - if (!isBootNode) { - if (bootNode.getEnclave().getEnclaveType() == EnclaveType.TESSERA) { - final URI otherNode = bootNode.getEnclave().nodeUrl(); - try { - // Substitute IP with hostname for test container network - final URI otherNodeHostname = - new URI( - otherNode.getScheme() - + "://" - + bootNode.getName() - + ":" - + TesseraTestHarness.p2pPort); - node.addOtherEnclaveNode(otherNodeHostname); - } catch (Exception ex) { - throw new RuntimeException("Invalid node URI"); - } - } else { - node.addOtherEnclaveNode(bootNode.getEnclave().nodeUrl()); - } - } - - LOG.info( - "Starting node {} (id = {}...{})", - node.getName(), - node.getNodeId().substring(0, 4), - node.getNodeId().substring(124)); - node.start(besuNodeRunner); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/PrivacyNode.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/PrivacyNode.java deleted file mode 100644 index 3d397e24e27..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/PrivacyNode.java +++ /dev/null @@ -1,308 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy; - -import static org.hyperledger.besu.controller.BesuController.DATABASE_PATH; -import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_BACKGROUND_THREAD_COUNT; -import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_CACHE_CAPACITY; -import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_IS_HIGH_SPEC; -import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_MAX_OPEN_FILES; - -import org.hyperledger.besu.crypto.KeyPairUtil; -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.enclave.Enclave; -import org.hyperledger.besu.enclave.EnclaveClientException; -import org.hyperledger.besu.enclave.EnclaveFactory; -import org.hyperledger.besu.enclave.EnclaveIOException; -import org.hyperledger.besu.enclave.EnclaveServerException; -import org.hyperledger.besu.ethereum.core.PrivacyParameters; -import org.hyperledger.besu.ethereum.privacy.storage.PrivacyStorageProvider; -import org.hyperledger.besu.ethereum.privacy.storage.keyvalue.PrivacyKeyValueStorageProviderBuilder; -import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; -import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; -import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBKeyValuePrivacyStorageFactory; -import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBKeyValueStorageFactory; -import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBMetricsFactory; -import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBFactoryConfiguration; -import org.hyperledger.besu.services.BesuConfigurationImpl; -import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; -import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; -import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNodeRunner; -import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.BesuNodeConfiguration; -import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.NodeConfiguration; -import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.privacy.PrivacyNodeConfiguration; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.condition.PrivateCondition; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; -import org.hyperledger.enclave.testutil.EnclaveTestHarness; -import org.hyperledger.enclave.testutil.EnclaveType; -import org.hyperledger.enclave.testutil.NoopEnclaveTestHarness; -import org.hyperledger.enclave.testutil.TesseraTestHarnessFactory; - -import java.io.IOException; -import java.net.URI; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -import io.vertx.core.Vertx; -import org.awaitility.Awaitility; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.testcontainers.containers.Network; - -public class PrivacyNode implements AutoCloseable { - - private static final Logger LOG = LoggerFactory.getLogger(PrivacyNode.class); - - private final EnclaveTestHarness enclave; - private final BesuNode besu; - private final Vertx vertx; - private final boolean isFlexiblePrivacyEnabled; - private final boolean isMultitenancyEnabled; - private final boolean isPrivacyPluginEnabled; - - public PrivacyNode( - final PrivacyNodeConfiguration privacyConfiguration, - final Vertx vertx, - final EnclaveType enclaveType, - final Optional containerNetwork) - throws IOException { - final Path enclaveDir = Files.createTempDirectory("acctest-orion"); - final BesuNodeConfiguration config = privacyConfiguration.getBesuConfig(); - this.enclave = - selectEnclave(enclaveType, enclaveDir, config, privacyConfiguration, containerNetwork); - this.vertx = vertx; - - final BesuNodeConfiguration besuConfig = config; - - isFlexiblePrivacyEnabled = privacyConfiguration.isFlexiblePrivacyGroupEnabled(); - isMultitenancyEnabled = privacyConfiguration.isMultitenancyEnabled(); - isPrivacyPluginEnabled = privacyConfiguration.isPrivacyPluginEnabled(); - - this.besu = - new BesuNode( - besuConfig.getName(), - besuConfig.getDataPath(), - besuConfig.getMiningParameters(), - besuConfig.getJsonRpcConfiguration(), - besuConfig.getEngineRpcConfiguration(), - besuConfig.getWebSocketConfiguration(), - besuConfig.getJsonRpcIpcConfiguration(), - besuConfig.getMetricsConfiguration(), - besuConfig.getPermissioningConfiguration(), - besuConfig.getApiConfiguration(), - besuConfig.getKeyFilePath(), - besuConfig.isDevMode(), - besuConfig.getNetwork(), - besuConfig.getGenesisConfigProvider(), - besuConfig.isP2pEnabled(), - besuConfig.getP2pPort(), - besuConfig.getTLSConfiguration(), - besuConfig.getNetworkingConfiguration(), - besuConfig.isDiscoveryEnabled(), - besuConfig.isBootnodeEligible(), - besuConfig.isRevertReasonEnabled(), - besuConfig.isSecp256k1Native(), - besuConfig.isAltbn128Native(), - besuConfig.getPlugins(), - besuConfig.getExtraCLIOptions(), - Collections.emptyList(), - besuConfig.isDnsEnabled(), - besuConfig.getPrivacyParameters(), - List.of(), - Optional.empty(), - Optional.empty(), - besuConfig.isStrictTxReplayProtectionEnabled(), - besuConfig.getEnvironment()); - } - - public void testEnclaveConnection(final List otherNodes) { - if (this.isPrivacyPluginEnabled) { - LOG.info("Skipping test as node has no enclave (isPrivacyPluginEnabled=true)"); - return; - } - - if (!otherNodes.isEmpty()) { - LOG.debug( - String.format( - "Testing Enclave connectivity between %s (%s) and %s (%s)", - besu.getName(), - enclave.nodeUrl(), - Arrays.toString(otherNodes.stream().map(node -> node.besu.getName()).toArray()), - Arrays.toString(otherNodes.stream().map(node -> node.enclave.nodeUrl()).toArray()))); - final EnclaveFactory factory = new EnclaveFactory(vertx); - final Enclave enclaveClient = factory.createVertxEnclave(enclave.clientUrl()); - final String payload = "SGVsbG8sIFdvcmxkIQ=="; - final List to = - otherNodes.stream() - .map(node -> node.enclave.getDefaultPublicKey()) - .collect(Collectors.toList()); - - Awaitility.await() - .until( - () -> { - try { - enclaveClient.send(payload, enclave.getDefaultPublicKey(), to); - return true; - } catch (final EnclaveClientException - | EnclaveIOException - | EnclaveServerException e) { - LOG.warn( - "Waiting for enclave connectivity between {} and {}: " + e.getMessage(), - enclave.getDefaultPublicKey(), - to.get(0)); - return false; - } - }); - } - } - - public EnclaveTestHarness getEnclave() { - return enclave; - } - - public BesuNode getBesu() { - return besu; - } - - public void stop() { - besu.stop(); - enclave.stop(); - } - - @Override - public void close() { - besu.close(); - enclave.close(); - } - - public void start(final BesuNodeRunner runner) { - enclave.start(); - - final PrivacyParameters privacyParameters; - - try { - final Path dataDir = Files.createTempDirectory("acctest-privacy"); - final Path dbDir = dataDir.resolve(DATABASE_PATH); - - var builder = - new PrivacyParameters.Builder() - .setEnabled(true) - .setEnclaveUrl(enclave.clientUrl()) - .setStorageProvider(createKeyValueStorageProvider(dataDir, dbDir)) - .setPrivateKeyPath(KeyPairUtil.getDefaultKeyFile(besu.homeDirectory()).toPath()) - .setEnclaveFactory(new EnclaveFactory(vertx)) - .setFlexiblePrivacyGroupsEnabled(isFlexiblePrivacyEnabled) - .setMultiTenancyEnabled(isMultitenancyEnabled) - .setPrivacyPluginEnabled(isPrivacyPluginEnabled); - - if (enclave.getPublicKeyPaths().size() > 0) { - builder.setPrivacyUserIdUsingFile(enclave.getPublicKeyPaths().get(0).toFile()); - } - - privacyParameters = builder.build(); - - } catch (final IOException e) { - throw new RuntimeException(e); - } - besu.setPrivacyParameters(privacyParameters); - besu.start(runner); - } - - public void awaitPeerDiscovery(final Condition condition) { - besu.awaitPeerDiscovery(condition); - } - - public String getName() { - return besu.getName(); - } - - public Address getAddress() { - return besu.getAddress(); - } - - public URI enodeUrl() { - return besu.enodeUrl(); - } - - public String getNodeId() { - return besu.getNodeId(); - } - - public T execute(final Transaction transaction) { - return besu.execute(transaction); - } - - public void verify(final PrivateCondition expected) { - expected.verify(this); - } - - public String getEnclaveKey() { - return enclave.getDefaultPublicKey(); - } - - public String getTransactionSigningKey() { - return besu.getPrivacyParameters().getSigningKeyPair().orElseThrow().getPrivateKey().toString(); - } - - public void addOtherEnclaveNode(final URI otherNode) { - enclave.addOtherNode(otherNode); - } - - public NodeConfiguration getConfiguration() { - return besu.getConfiguration(); - } - - private PrivacyStorageProvider createKeyValueStorageProvider( - final Path dataLocation, final Path dbLocation) { - return new PrivacyKeyValueStorageProviderBuilder() - .withStorageFactory( - new RocksDBKeyValuePrivacyStorageFactory( - new RocksDBKeyValueStorageFactory( - () -> - new RocksDBFactoryConfiguration( - DEFAULT_MAX_OPEN_FILES, - DEFAULT_BACKGROUND_THREAD_COUNT, - DEFAULT_CACHE_CAPACITY, - DEFAULT_IS_HIGH_SPEC), - Arrays.asList(KeyValueSegmentIdentifier.values()), - RocksDBMetricsFactory.PRIVATE_ROCKS_DB_METRICS))) - .withCommonConfiguration(new BesuConfigurationImpl(dataLocation, dbLocation)) - .withMetricsSystem(new NoOpMetricsSystem()) - .build(); - } - - private EnclaveTestHarness selectEnclave( - final EnclaveType enclaveType, - final Path tempDir, - final BesuNodeConfiguration config, - final PrivacyNodeConfiguration privacyConfiguration, - final Optional containerNetwork) { - - switch (enclaveType) { - case ORION: - throw new UnsupportedOperationException("The Orion tests are getting deprecated"); - case TESSERA: - return TesseraTestHarnessFactory.create( - config.getName(), tempDir, privacyConfiguration.getKeyConfig(), containerNetwork); - default: - return new NoopEnclaveTestHarness(tempDir, privacyConfiguration.getKeyConfig()); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/PrivateTransactionGroupResponse.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/PrivateTransactionGroupResponse.java deleted file mode 100644 index 3cd7ca49a49..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/PrivateTransactionGroupResponse.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class PrivateTransactionGroupResponse { - private final String from; - private final String gas; - private final String gasPrice; - private final String input; - private final String nonce; - private final String to; - private final String value; - private final String v; - private final String r; - private final String s; - private final String privateFrom; - private final String restriction; - private final String privacyGroupId; - - @JsonCreator - public PrivateTransactionGroupResponse( - @JsonProperty("from") final String from, - @JsonProperty("gas") final String gas, - @JsonProperty("gasPrice") final String gasPrice, - @JsonProperty("hash") final String hash, - @JsonProperty("input") final String input, - @JsonProperty("nonce") final String nonce, - @JsonProperty("to") final String to, - @JsonProperty("value") final String value, - @JsonProperty("v") final String v, - @JsonProperty("r") final String r, - @JsonProperty("s") final String s, - @JsonProperty("privateFrom") final String privateFrom, - @JsonProperty("restriction") final String restriction, - @JsonProperty("privacyGroupId") final String privacyGroupId) { - this.from = from; - this.gas = gas; - this.gasPrice = gasPrice; - this.input = input; - this.nonce = nonce; - this.to = to; - this.value = value; - this.v = v; - this.r = r; - this.s = s; - this.privateFrom = privateFrom; - this.restriction = restriction; - this.privacyGroupId = privacyGroupId; - } - - public String getFrom() { - return from; - } - - public String getGas() { - return gas; - } - - public String getGasPrice() { - return gasPrice; - } - - public String getInput() { - return input; - } - - public String getNonce() { - return nonce; - } - - public String getTo() { - return to; - } - - public String getValue() { - return value; - } - - public String getV() { - return v; - } - - public String getR() { - return r; - } - - public String getS() { - return s; - } - - public String getPrivateFrom() { - return privateFrom; - } - - public String getRestriction() { - return restriction; - } - - public String getPrivacyGroupId() { - return privacyGroupId; - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/account/PrivacyAccount.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/account/PrivacyAccount.java deleted file mode 100644 index c246dd585a5..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/account/PrivacyAccount.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.account; - -import org.hyperledger.enclave.testutil.EnclaveEncryptorType; - -import java.io.File; -import java.net.URL; -import java.util.Arrays; - -public class PrivacyAccount { - - private final URL privateKeyPath; - private final URL[] enclaveKeyPaths; - private final URL[] enclavePrivateKeyPaths; - private final EnclaveEncryptorType enclaveEncryptorType; - - private PrivacyAccount( - final URL privateKeyPath, - final URL[] enclavePublicKeyPaths, - final URL[] enclavePrivateKeyPaths, - final EnclaveEncryptorType enclaveEncryptorType) { - this.privateKeyPath = privateKeyPath; - this.enclaveKeyPaths = enclavePublicKeyPaths; - this.enclavePrivateKeyPaths = enclavePrivateKeyPaths; - this.enclaveEncryptorType = enclaveEncryptorType; - } - - public static PrivacyAccount create( - final URL privateKeyPath, - final URL enclavePublicKeyPath, - final URL enclavePrivateKeyPath, - final EnclaveEncryptorType enclaveEncryptorType) { - return new PrivacyAccount( - privateKeyPath, - new URL[] {enclavePublicKeyPath}, - new URL[] {enclavePrivateKeyPath}, - enclaveEncryptorType); - } - - public static PrivacyAccount create( - final URL privateKeyPath, - final URL[] enclavePublicKeyPath, - final URL[] enclavePrivateKeyPath, - final EnclaveEncryptorType enclaveEncryptorType) { - return new PrivacyAccount( - privateKeyPath, enclavePublicKeyPath, enclavePrivateKeyPath, enclaveEncryptorType); - } - - public String getPrivateKeyPath() { - return toStringResource(privateKeyPath); - } - - public String[] getEnclaveKeyPaths() { - return Arrays.stream(enclaveKeyPaths) - .map(path -> toStringResource(path)) - .toArray(String[]::new); - } - - public String[] getEnclavePrivateKeyPaths() { - return Arrays.stream(enclavePrivateKeyPaths) - .map(path -> toStringResource(path)) - .toArray(String[]::new); - } - - public EnclaveEncryptorType getEnclaveEncryptorType() { - return enclaveEncryptorType; - } - - private String toStringResource(final URL path) { - return path.getPath().substring(path.getPath().lastIndexOf(File.separator) + 1); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/account/PrivacyAccountResolver.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/account/PrivacyAccountResolver.java deleted file mode 100644 index 7a6e26f7bce..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/account/PrivacyAccountResolver.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.account; - -import org.hyperledger.enclave.testutil.EnclaveEncryptorType; - -import java.net.URL; - -/** Supplier of known funded accounts defined in dev.json */ -public enum PrivacyAccountResolver { - ALICE { - @Override - public PrivacyAccount resolve(final EnclaveEncryptorType enclaveEncryptorType) { - return PrivacyAccount.create( - resolveResource("key"), - enclaveEncryptorType.equals(EnclaveEncryptorType.EC) - ? resolveResource("enclave_ec_key_0.pub") - : resolveResource("enclave_key_0.pub"), - enclaveEncryptorType.equals(EnclaveEncryptorType.EC) - ? resolveResource("enclave_ec_key_0.key") - : resolveResource("enclave_key_0.key"), - enclaveEncryptorType); - } - }, - BOB { - @Override - public PrivacyAccount resolve(final EnclaveEncryptorType enclaveEncryptorType) { - return PrivacyAccount.create( - resolveResource("key1"), - enclaveEncryptorType.equals(EnclaveEncryptorType.EC) - ? resolveResource("enclave_ec_key_1.pub") - : resolveResource("enclave_key_1.pub"), - enclaveEncryptorType.equals(EnclaveEncryptorType.EC) - ? resolveResource("enclave_ec_key_1.key") - : resolveResource("enclave_key_1.key"), - enclaveEncryptorType); - } - }, - CHARLIE { - @Override - public PrivacyAccount resolve(final EnclaveEncryptorType enclaveEncryptorType) { - return PrivacyAccount.create( - resolveResource("key2"), - enclaveEncryptorType.equals(EnclaveEncryptorType.EC) - ? resolveResource("enclave_ec_key_2.pub") - : resolveResource("enclave_key_2.pub"), - enclaveEncryptorType.equals(EnclaveEncryptorType.EC) - ? resolveResource("enclave_ec_key_2.key") - : resolveResource("enclave_key_2.key"), - enclaveEncryptorType); - } - }, - MULTI_TENANCY { - @Override - public PrivacyAccount resolve(final EnclaveEncryptorType enclaveEncryptorType) { - return PrivacyAccount.create( - resolveResource("key"), - new URL[] { - enclaveEncryptorType.equals(EnclaveEncryptorType.EC) - ? resolveResource("enclave_ec_key_0.pub") - : resolveResource("enclave_key_0.pub"), - enclaveEncryptorType.equals(EnclaveEncryptorType.EC) - ? resolveResource("enclave_ec_key_1.pub") - : resolveResource("enclave_key_1.pub"), - enclaveEncryptorType.equals(EnclaveEncryptorType.EC) - ? resolveResource("enclave_ec_key_2.pub") - : resolveResource("enclave_key_2.pub") - }, - new URL[] { - enclaveEncryptorType.equals(EnclaveEncryptorType.EC) - ? resolveResource("enclave_ec_key_0.key") - : resolveResource("enclave_key_0.key"), - enclaveEncryptorType.equals(EnclaveEncryptorType.EC) - ? resolveResource("enclave_ec_key_1.key") - : resolveResource("enclave_key_1.key"), - enclaveEncryptorType.equals(EnclaveEncryptorType.EC) - ? resolveResource("enclave_ec_key_2.key") - : resolveResource("enclave_key_2.key") - }, - enclaveEncryptorType); - } - }; - - public abstract PrivacyAccount resolve(final EnclaveEncryptorType enclaveEncryptorType); - - URL resolveResource(final String resource) { - return PrivacyAccountResolver.class.getClassLoader().getResource(resource); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectExistingPrivateTransactionReceipt.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectExistingPrivateTransactionReceipt.java deleted file mode 100644 index 7d1c97c13d7..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectExistingPrivateTransactionReceipt.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.condition; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction.PrivacyTransactions; - -import org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt; - -public class ExpectExistingPrivateTransactionReceipt implements PrivateCondition { - private final PrivacyTransactions transactions; - private final String transactionHash; - - public ExpectExistingPrivateTransactionReceipt( - final PrivacyTransactions transactions, final String transactionHash) { - - this.transactions = transactions; - this.transactionHash = transactionHash; - } - - @Override - public void verify(final PrivacyNode node) { - final PrivateTransactionReceipt privateTransactionReceipt = - node.execute(transactions.getPrivateTransactionReceipt(transactionHash)); - assertThat(privateTransactionReceipt).isNotNull(); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectInternalErrorPrivateTransactionReceipt.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectInternalErrorPrivateTransactionReceipt.java deleted file mode 100644 index 450be72d427..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectInternalErrorPrivateTransactionReceipt.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.condition; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction.PrivacyTransactions; - -public class ExpectInternalErrorPrivateTransactionReceipt implements PrivateCondition { - private final PrivacyTransactions transactions; - private final String transactionHash; - - public ExpectInternalErrorPrivateTransactionReceipt( - final PrivacyTransactions transactions, final String transactionHash) { - this.transactions = transactions; - this.transactionHash = transactionHash; - } - - @Override - public void verify(final PrivacyNode node) { - try { - node.execute(transactions.getPrivateTransactionReceipt(transactionHash)); - } catch (final RuntimeException e) { - assertThat(e.getMessage()).contains("Internal error"); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectNoPrivateTransactionReceipt.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectNoPrivateTransactionReceipt.java deleted file mode 100644 index d14d0dde736..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectNoPrivateTransactionReceipt.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.condition; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.catchThrowable; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction.PrivacyTransactions; - -public class ExpectNoPrivateTransactionReceipt implements PrivateCondition { - private final PrivacyTransactions transactions; - private final String transactionHash; - - public ExpectNoPrivateTransactionReceipt( - final PrivacyTransactions transactions, final String transactionHash) { - this.transactions = transactions; - this.transactionHash = transactionHash; - } - - @Override - public void verify(final PrivacyNode node) { - final Throwable t = - catchThrowable( - () -> node.execute(transactions.getPrivateTransactionReceipt(transactionHash))); - - assertThat(t).hasMessageContaining("TransactionException"); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidContractCode.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidContractCode.java deleted file mode 100644 index 574ace120da..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidContractCode.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.condition; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.io.IOException; - -import org.web3j.tx.Contract; - -public class ExpectValidContractCode implements PrivateContractCondition { - @Override - public void verify(final Contract contract) throws IOException { - assertThat(contract).isNotNull(); - assertThat(contract.isValid()).isEqualTo(true); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidFlexiblePrivacyGroupCreated.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidFlexiblePrivacyGroupCreated.java deleted file mode 100644 index ce9c93d23d6..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidFlexiblePrivacyGroupCreated.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.condition; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction.PrivacyTransactions; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivacyRequestFactory; - -import java.util.List; -import java.util.concurrent.TimeUnit; - -import org.awaitility.Awaitility; -import org.web3j.utils.Base64String; - -public class ExpectValidFlexiblePrivacyGroupCreated implements PrivateCondition { - - private final PrivacyTransactions transactions; - private final PrivacyRequestFactory.FlexiblePrivacyGroup expected; - - public ExpectValidFlexiblePrivacyGroupCreated( - final PrivacyTransactions transactions, - final PrivacyRequestFactory.FlexiblePrivacyGroup expected) { - this.transactions = transactions; - this.expected = expected; - } - - @Override - public void verify(final PrivacyNode node) { - Awaitility.waitAtMost(20, TimeUnit.SECONDS) - .untilAsserted( - () -> { - final List groups = - node.execute( - transactions.findFlexiblePrivacyGroup( - Base64String.unwrapList(expected.getMembers()))); - assertThat(groups).contains(expected); - }); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidPrivacyGroupCreated.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidPrivacyGroupCreated.java deleted file mode 100644 index 813050cc588..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidPrivacyGroupCreated.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.condition; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction.PrivacyTransactions; - -import java.util.List; - -import org.awaitility.Awaitility; -import org.web3j.protocol.besu.response.privacy.PrivacyGroup; -import org.web3j.utils.Base64String; - -public class ExpectValidPrivacyGroupCreated implements PrivateCondition { - - private final PrivacyTransactions transactions; - private final PrivacyGroup expected; - - public ExpectValidPrivacyGroupCreated( - final PrivacyTransactions transactions, final PrivacyGroup expected) { - this.transactions = transactions; - this.expected = expected; - } - - @Override - public void verify(final PrivacyNode node) { - Awaitility.await() - .untilAsserted( - () -> { - final List groups = - node.execute( - transactions.findPrivacyGroup( - Base64String.unwrapList(expected.getMembers()))); - assertThat(groups).contains(expected); - }); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidPrivateContractDeployedReceipt.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidPrivateContractDeployedReceipt.java deleted file mode 100644 index 687d6360c2d..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidPrivateContractDeployedReceipt.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.condition; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.Optional; - -import org.web3j.protocol.core.methods.response.TransactionReceipt; -import org.web3j.tx.Contract; - -public class ExpectValidPrivateContractDeployedReceipt implements PrivateContractCondition { - - private final String contractAddress; - private final String senderAddress; - - public ExpectValidPrivateContractDeployedReceipt( - final String contractAddress, final String senderAddress) { - this.contractAddress = contractAddress; - this.senderAddress = senderAddress; - } - - @Override - public void verify(final Contract contract) { - - assertThat(contract).isNotNull(); - final Optional receipt = contract.getTransactionReceipt(); - - // We're expecting a receipt - assertThat(receipt).isNotNull(); - assertThat(receipt.isPresent()).isTrue(); - final TransactionReceipt transactionReceipt = receipt.get(); - - assertThat(transactionReceipt.isStatusOK()).isTrue(); - - // Contract transaction has no 'to' address or contract address - assertThat(transactionReceipt.getTo()).isNull(); - - // Address generation is deterministic, based on the sender address and the transaction nonce - assertThat(transactionReceipt.getContractAddress()).isEqualTo(contractAddress); - - // Address for the account that signed (and paid) for the contract deployment transaction - assertThat(transactionReceipt.getFrom()).isEqualTo(senderAddress); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidPrivateTransactionReceipt.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidPrivateTransactionReceipt.java deleted file mode 100644 index ff8e0c2c5f4..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/ExpectValidPrivateTransactionReceipt.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.condition; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction.PrivacyTransactions; - -import org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt; - -public class ExpectValidPrivateTransactionReceipt implements PrivateCondition { - private final PrivacyTransactions transactions; - private final String transactionHash; - private final PrivateTransactionReceipt expectedReceipt; - private final boolean ignoreOutput; - - public ExpectValidPrivateTransactionReceipt( - final PrivacyTransactions transactions, - final String transactionHash, - final PrivateTransactionReceipt expectedReceipt) { - this(transactions, transactionHash, expectedReceipt, false); - } - - public ExpectValidPrivateTransactionReceipt( - final PrivacyTransactions transactions, - final String transactionHash, - final PrivateTransactionReceipt expectedReceipt, - final boolean ignoreOutput) { - - this.transactions = transactions; - this.transactionHash = transactionHash; - this.expectedReceipt = expectedReceipt; - this.ignoreOutput = ignoreOutput; - } - - @Override - public void verify(final PrivacyNode node) { - final PrivateTransactionReceipt actualReceipt = - node.execute(transactions.getPrivateTransactionReceipt(transactionHash)); - if (ignoreOutput) { - // output can be ignored if it is checked separately - assertThat(actualReceipt) - .usingRecursiveComparison() - .ignoringFields( - "commitmentHash", - "logs", - "blockHash", - "blockNumber", - "logsBloom", - "transactionIndex", - "output") - // TODO: The fields blockHash, blockNumber, logsBloom, transactionIndex and output have to - // be ignored as - // the class org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt does not - // contain - // these fields. Once web3j has been updated these ignores can be removed. - .isEqualTo(expectedReceipt); - } else { - assertThat(actualReceipt) - .usingRecursiveComparison() - .ignoringFields( - "commitmentHash", "logs", "blockHash", "blockNumber", "logsBloom", "transactionIndex") - // TODO: The fields blockHash, blockNumber, logsBloom and transactionIndex have to be - // ignored as the class - // org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt does not contain - // these fields. Once web3j has been updated these ignores can be removed. - .isEqualTo(expectedReceipt); - } - assertThat(actualReceipt.getLogs().size()).isEqualTo(expectedReceipt.getLogs().size()); - - for (int i = 0; i < expectedReceipt.getLogs().size(); i++) { - assertThat(actualReceipt.getLogs().get(i)) - .usingRecursiveComparison() - .ignoringFields("blockHash", "blockNumber") - .isEqualTo(expectedReceipt.getLogs().get(i)); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivGetTransactionReceiptTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivGetTransactionReceiptTransaction.java deleted file mode 100644 index b5922b25b0f..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivGetTransactionReceiptTransaction.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.condition; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; - -import org.web3j.protocol.besu.Besu; -import org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt; -import org.web3j.protocol.exceptions.TransactionException; -import org.web3j.tx.response.PollingPrivateTransactionReceiptProcessor; - -public class PrivGetTransactionReceiptTransaction - implements Transaction { - - private final String transactionHash; - - public PrivGetTransactionReceiptTransaction(final String transactionHash) { - this.transactionHash = transactionHash; - } - - @Override - public PrivateTransactionReceipt execute(final NodeRequests node) { - final Besu besu = node.privacy().getBesuClient(); - final PollingPrivateTransactionReceiptProcessor receiptProcessor = - new PollingPrivateTransactionReceiptProcessor(besu, 1000, 15); - try { - final PrivateTransactionReceipt result = - receiptProcessor.waitForTransactionReceipt(transactionHash); - assertThat(result).isNotNull(); - return result; - } catch (final IOException | TransactionException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateCondition.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateCondition.java deleted file mode 100644 index 873f015a499..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateCondition.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.condition; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; - -public interface PrivateCondition { - void verify(PrivacyNode node); -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateContractCondition.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateContractCondition.java deleted file mode 100644 index 7969e2d3576..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateContractCondition.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.condition; - -import java.io.IOException; - -import org.web3j.tx.Contract; - -public interface PrivateContractCondition { - void verify(final Contract contract) throws IOException; -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateContractVerifier.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateContractVerifier.java deleted file mode 100644 index de9f36d08d7..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateContractVerifier.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.condition; - -public class PrivateContractVerifier { - - public PrivateContractVerifier() {} - - public ExpectValidPrivateContractDeployedReceipt validPrivateContractDeployed( - final String contractAddress, final String senderAddress) { - return new ExpectValidPrivateContractDeployedReceipt(contractAddress, senderAddress); - } - - public ExpectValidContractCode validContractCodeProvided() { - return new ExpectValidContractCode(); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateTransactionVerifier.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateTransactionVerifier.java deleted file mode 100644 index cfd83b65821..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/condition/PrivateTransactionVerifier.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.condition; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction.PrivacyTransactions; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivacyRequestFactory.FlexiblePrivacyGroup; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import org.web3j.protocol.besu.response.privacy.PrivacyGroup; -import org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt; -import org.web3j.utils.Base64String; - -public class PrivateTransactionVerifier { - - private final PrivacyTransactions transactions; - - public PrivateTransactionVerifier(final PrivacyTransactions transactions) { - this.transactions = transactions; - } - - public ExpectValidPrivateTransactionReceipt validPrivateTransactionReceipt( - final String transactionHash, final PrivateTransactionReceipt receipt) { - return new ExpectValidPrivateTransactionReceipt(transactions, transactionHash, receipt); - } - - public ExpectValidPrivateTransactionReceipt validPrivateTransactionReceipt( - final String transactionHash, - final PrivateTransactionReceipt receipt, - final boolean ignoreOutput) { - return new ExpectValidPrivateTransactionReceipt( - transactions, transactionHash, receipt, ignoreOutput); - } - - public ExpectExistingPrivateTransactionReceipt existingPrivateTransactionReceipt( - final String transactionHash) { - return new ExpectExistingPrivateTransactionReceipt(transactions, transactionHash); - } - - public ExpectNoPrivateTransactionReceipt noPrivateTransactionReceipt( - final String transactionHash) { - return new ExpectNoPrivateTransactionReceipt(transactions, transactionHash); - } - - public ExpectValidPrivacyGroupCreated validPrivacyGroupCreated(final PrivacyGroup expected) { - return new ExpectValidPrivacyGroupCreated(transactions, expected); - } - - public ExpectValidFlexiblePrivacyGroupCreated flexiblePrivacyGroupExists( - final String privacyGroupId, final PrivacyNode... members) { - - final List membersEnclaveKeys = - Arrays.stream(members) - .map(PrivacyNode::getEnclaveKey) - .map(Base64String::wrap) - .collect(Collectors.toList()); - return flexiblePrivacyGroupExists(privacyGroupId, membersEnclaveKeys); - } - - public ExpectValidFlexiblePrivacyGroupCreated flexiblePrivacyGroupExists( - final String privacyGroupId, final List membersEnclaveKeys) { - - final FlexiblePrivacyGroup expectedGroup = - new FlexiblePrivacyGroup(privacyGroupId, membersEnclaveKeys); - - return new ExpectValidFlexiblePrivacyGroupCreated(transactions, expectedGroup); - } - - public ExpectInternalErrorPrivateTransactionReceipt internalErrorPrivateTransactionReceipt( - final String transactionHash) { - return new ExpectInternalErrorPrivateTransactionReceipt(transactions, transactionHash); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/CallOnchainPermissioningPrivateSmartContractFunction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/CallOnchainPermissioningPrivateSmartContractFunction.java deleted file mode 100644 index 0820d65d1d5..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/CallOnchainPermissioningPrivateSmartContractFunction.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.contract; - -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; -import java.math.BigInteger; - -import org.web3j.crypto.Credentials; -import org.web3j.tx.PrivateTransactionManager; -import org.web3j.tx.gas.BesuPrivacyGasProvider; -import org.web3j.utils.Base64String; -import org.web3j.utils.Restriction; - -public class CallOnchainPermissioningPrivateSmartContractFunction implements Transaction { - - private static final BesuPrivacyGasProvider GAS_PROVIDER = - new BesuPrivacyGasProvider(BigInteger.valueOf(1000)); - private final String contractAddress; - private final String encodedFunction; - private final Credentials senderCredentials; - private final Base64String privateFrom; - private final Base64String privacyGroupId; - - public CallOnchainPermissioningPrivateSmartContractFunction( - final String contractAddress, - final String encodedFunction, - final String transactionSigningKey, - final String privateFrom, - final String privacyGroupId) { - - this.contractAddress = contractAddress; - this.encodedFunction = encodedFunction; - this.senderCredentials = Credentials.create(transactionSigningKey); - this.privateFrom = Base64String.wrap(privateFrom); - this.privacyGroupId = Base64String.wrap(privacyGroupId); - } - - @Override - public String execute(final NodeRequests node) { - final PrivateTransactionManager privateTransactionManager = - node.privacy() - .getTransactionManager( - senderCredentials, privateFrom, privacyGroupId, Restriction.RESTRICTED); - - try { - return privateTransactionManager - .sendTransaction( - GAS_PROVIDER.getGasPrice(), - GAS_PROVIDER.getGasLimit(), - contractAddress, - encodedFunction, - null) - .getTransactionHash(); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/CallPrivateSmartContractFunction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/CallPrivateSmartContractFunction.java deleted file mode 100644 index d3ec7989e5e..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/CallPrivateSmartContractFunction.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.contract; - -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; -import java.math.BigInteger; -import java.util.List; - -import org.web3j.crypto.Credentials; -import org.web3j.tx.PrivateTransactionManager; -import org.web3j.tx.gas.BesuPrivacyGasProvider; -import org.web3j.utils.Base64String; -import org.web3j.utils.Restriction; - -public class CallPrivateSmartContractFunction implements Transaction { - - private static final BesuPrivacyGasProvider GAS_PROVIDER = - new BesuPrivacyGasProvider(BigInteger.valueOf(1000)); - private final String contractAddress; - private final String encodedFunction; - private final Credentials senderCredentials; - private final Restriction restriction; - private final Base64String privateFrom; - private final List privateFor; - private final Base64String privacyGroupId; - - public CallPrivateSmartContractFunction( - final String contractAddress, - final String encodedFunction, - final String transactionSigningKey, - final Restriction restriction, - final String privateFrom, - final List privateFor) { - this( - contractAddress, - encodedFunction, - transactionSigningKey, - restriction, - privateFrom, - privateFor, - null); - } - - public CallPrivateSmartContractFunction( - final String contractAddress, - final String encodedFunction, - final String transactionSigningKey, - final Restriction restriction, - final String privateFrom, - final String privacyGroupId) { - this( - contractAddress, - encodedFunction, - transactionSigningKey, - restriction, - privateFrom, - null, - privacyGroupId); - } - - private CallPrivateSmartContractFunction( - final String contractAddress, - final String encodedFunction, - final String transactionSigningKey, - final Restriction restriction, - final String privateFrom, - final List privateFor, - final String privacyGroupId) { - - this.contractAddress = contractAddress; - this.encodedFunction = encodedFunction; - this.senderCredentials = Credentials.create(transactionSigningKey); - this.restriction = restriction; - this.privateFrom = Base64String.wrap(privateFrom); - this.privateFor = privateFor != null ? Base64String.wrapList(privateFor) : null; - this.privacyGroupId = privacyGroupId != null ? Base64String.wrap(privacyGroupId) : null; - } - - @Override - public String execute(final NodeRequests node) { - final PrivateTransactionManager privateTransactionManager; - - if (privateFor != null) { - privateTransactionManager = - node.privacy() - .getTransactionManager(senderCredentials, privateFrom, privateFor, restriction); - } else { - privateTransactionManager = - node.privacy() - .getTransactionManager(senderCredentials, privateFrom, privacyGroupId, restriction); - } - - try { - return privateTransactionManager - .sendTransaction( - GAS_PROVIDER.getGasPrice(), - GAS_PROVIDER.getGasLimit(), - contractAddress, - encodedFunction, - null) - .getTransactionHash(); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/DeployPrivateSmartContractTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/DeployPrivateSmartContractTransaction.java deleted file mode 100644 index ed7ca9cd9af..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/DeployPrivateSmartContractTransaction.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.contract; - -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.lang.reflect.Method; -import java.math.BigInteger; -import java.util.List; - -import org.web3j.crypto.Credentials; -import org.web3j.protocol.Web3j; -import org.web3j.protocol.core.RemoteCall; -import org.web3j.tx.Contract; -import org.web3j.tx.PrivateTransactionManager; -import org.web3j.tx.TransactionManager; -import org.web3j.tx.gas.BesuPrivacyGasProvider; -import org.web3j.tx.gas.ContractGasProvider; -import org.web3j.utils.Base64String; -import org.web3j.utils.Restriction; - -public class DeployPrivateSmartContractTransaction implements Transaction { - - private static final BesuPrivacyGasProvider GAS_PROVIDER = - new BesuPrivacyGasProvider(BigInteger.valueOf(1000)); - private static final Object METHOD_IS_STATIC = null; - - private final Class clazz; - private final Credentials senderCredentials; - private final Base64String privateFrom; - private final List privateFor; - - public DeployPrivateSmartContractTransaction( - final Class clazz, - final String transactionSigningKey, - final String privateFrom, - final List privateFor) { - this.clazz = clazz; - this.senderCredentials = Credentials.create(transactionSigningKey); - this.privateFrom = Base64String.wrap(privateFrom); - this.privateFor = Base64String.wrapList(privateFor); - } - - @Override - public T execute(final NodeRequests node) { - - final PrivateTransactionManager privateTransactionManager = - node.privacy() - .getTransactionManager( - senderCredentials, privateFrom, privateFor, Restriction.RESTRICTED); - - try { - final Method method = - clazz.getMethod( - "deploy", Web3j.class, TransactionManager.class, ContractGasProvider.class); - - final Object invoked = - method.invoke( - METHOD_IS_STATIC, - node.privacy().getBesuClient(), - privateTransactionManager, - GAS_PROVIDER); - - return cast(invoked).send(); - } catch (final Exception e) { - throw new RuntimeException(e); - } - } - - @SuppressWarnings("unchecked") - private RemoteCall cast(final Object invokedMethod) { - return (RemoteCall) invokedMethod; - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/DeployPrivateSmartContractWithPrivacyGroupIdTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/DeployPrivateSmartContractWithPrivacyGroupIdTransaction.java deleted file mode 100644 index c5d0116431a..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/DeployPrivateSmartContractWithPrivacyGroupIdTransaction.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.contract; - -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.lang.reflect.Method; -import java.math.BigInteger; - -import org.web3j.crypto.Credentials; -import org.web3j.protocol.Web3j; -import org.web3j.protocol.core.RemoteCall; -import org.web3j.tx.Contract; -import org.web3j.tx.PrivateTransactionManager; -import org.web3j.tx.TransactionManager; -import org.web3j.tx.gas.BesuPrivacyGasProvider; -import org.web3j.tx.gas.ContractGasProvider; -import org.web3j.utils.Base64String; -import org.web3j.utils.Restriction; - -public class DeployPrivateSmartContractWithPrivacyGroupIdTransaction - implements Transaction { - - private static final BesuPrivacyGasProvider GAS_PROVIDER = - new BesuPrivacyGasProvider(BigInteger.valueOf(1000)); - private static final Object METHOD_IS_STATIC = null; - - private final Class clazz; - private final Credentials senderCredentials; - private final Restriction restriction; - private final Base64String privateFrom; - private final Base64String privacyGroupId; - - public DeployPrivateSmartContractWithPrivacyGroupIdTransaction( - final Class clazz, - final String transactionSigningKey, - final Restriction restriction, - final String privateFrom, - final String privacyGroupId) { - this.clazz = clazz; - this.senderCredentials = Credentials.create(transactionSigningKey); - this.restriction = restriction; - this.privateFrom = Base64String.wrap(privateFrom); - this.privacyGroupId = Base64String.wrap(privacyGroupId); - } - - @Override - public T execute(final NodeRequests node) { - - final PrivateTransactionManager privateTransactionManager = - node.privacy() - .getTransactionManager(senderCredentials, privateFrom, privacyGroupId, restriction); - - try { - final Method method = - clazz.getMethod( - "deploy", Web3j.class, TransactionManager.class, ContractGasProvider.class); - - final Object invoked = - method.invoke( - METHOD_IS_STATIC, - node.privacy().getBesuClient(), - privateTransactionManager, - GAS_PROVIDER); - - return cast(invoked).send(); - } catch (final Exception e) { - throw new RuntimeException(e); - } - } - - @SuppressWarnings("unchecked") - private RemoteCall cast(final Object invokedMethod) { - return (RemoteCall) invokedMethod; - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/LoadPrivateSmartContractTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/LoadPrivateSmartContractTransaction.java deleted file mode 100644 index aac5f293f9b..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/LoadPrivateSmartContractTransaction.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.contract; - -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.lang.reflect.Method; -import java.math.BigInteger; -import java.util.List; - -import org.web3j.crypto.Credentials; -import org.web3j.protocol.Web3j; -import org.web3j.tx.Contract; -import org.web3j.tx.PrivateTransactionManager; -import org.web3j.tx.TransactionManager; -import org.web3j.tx.gas.BesuPrivacyGasProvider; -import org.web3j.tx.gas.ContractGasProvider; -import org.web3j.utils.Base64String; -import org.web3j.utils.Restriction; - -public class LoadPrivateSmartContractTransaction implements Transaction { - private static final BesuPrivacyGasProvider GAS_PROVIDER = - new BesuPrivacyGasProvider(BigInteger.valueOf(1000)); - private static final Object METHOD_IS_STATIC = null; - - private final Class clazz; - private final Credentials senderCredentials; - private final Base64String privateFrom; - private final List privateFor; - private final String contractAddress; - - public LoadPrivateSmartContractTransaction( - final String contractAddress, - final Class clazz, - final String transactionSigningKey, - final String privateFrom, - final List privateFor) { - - this.contractAddress = contractAddress; - this.clazz = clazz; - this.senderCredentials = Credentials.create(transactionSigningKey); - this.privateFrom = Base64String.wrap(privateFrom); - this.privateFor = Base64String.wrapList(privateFor); - } - - @SuppressWarnings("unchecked") - @Override - public T execute(final NodeRequests node) { - final PrivateTransactionManager privateTransactionManager = - node.privacy() - .getTransactionManager( - senderCredentials, privateFrom, privateFor, Restriction.RESTRICTED); - - try { - final Method method = - clazz.getMethod( - "load", - String.class, - Web3j.class, - TransactionManager.class, - ContractGasProvider.class); - - return (T) - method.invoke( - METHOD_IS_STATIC, - contractAddress, - node.privacy().getBesuClient(), - privateTransactionManager, - GAS_PROVIDER); - } catch (final Exception e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/LoadPrivateSmartContractTransactionWithPrivacyGroupId.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/LoadPrivateSmartContractTransactionWithPrivacyGroupId.java deleted file mode 100644 index 11a5045752c..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/LoadPrivateSmartContractTransactionWithPrivacyGroupId.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.contract; - -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.lang.reflect.Method; -import java.math.BigInteger; - -import org.web3j.crypto.Credentials; -import org.web3j.protocol.Web3j; -import org.web3j.tx.Contract; -import org.web3j.tx.PrivateTransactionManager; -import org.web3j.tx.TransactionManager; -import org.web3j.tx.gas.BesuPrivacyGasProvider; -import org.web3j.tx.gas.ContractGasProvider; -import org.web3j.utils.Base64String; -import org.web3j.utils.Restriction; - -public class LoadPrivateSmartContractTransactionWithPrivacyGroupId - implements Transaction { - - private static final BesuPrivacyGasProvider GAS_PROVIDER = - new BesuPrivacyGasProvider(BigInteger.valueOf(1000)); - private static final Object METHOD_IS_STATIC = null; - - private final Class clazz; - private final Credentials senderCredentials; - private final Base64String privateFrom; - private final Base64String privacyGroupId; - private final String contractAddress; - - public LoadPrivateSmartContractTransactionWithPrivacyGroupId( - final String contractAddress, - final Class clazz, - final String transactionSigningKey, - final String privateFrom, - final String privacyGroupId) { - - this.contractAddress = contractAddress; - this.clazz = clazz; - this.senderCredentials = Credentials.create(transactionSigningKey); - this.privateFrom = Base64String.wrap(privateFrom); - this.privacyGroupId = Base64String.wrap(privacyGroupId); - } - - @SuppressWarnings("unchecked") - @Override - public T execute(final NodeRequests node) { - final PrivateTransactionManager privateTransactionManager = - node.privacy() - .getTransactionManager( - senderCredentials, privateFrom, privacyGroupId, Restriction.RESTRICTED); - - try { - final Method method = - clazz.getMethod( - "load", - String.class, - Web3j.class, - TransactionManager.class, - ContractGasProvider.class); - - return (T) - method.invoke( - METHOD_IS_STATIC, - contractAddress, - node.privacy().getBesuClient(), - privateTransactionManager, - GAS_PROVIDER); - } catch (final Exception e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/PrivateContractTransactions.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/PrivateContractTransactions.java deleted file mode 100644 index 97e7e654018..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/contract/PrivateContractTransactions.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.contract; - -import java.util.Arrays; -import java.util.List; - -import org.web3j.tx.Contract; -import org.web3j.utils.Restriction; - -public class PrivateContractTransactions { - - public - DeployPrivateSmartContractWithPrivacyGroupIdTransaction - createSmartContractWithPrivacyGroupId( - final Class clazz, - final String transactionSigningKey, - final Restriction restriction, - final String privateFrom, - final String privacyGroupId) { - return new DeployPrivateSmartContractWithPrivacyGroupIdTransaction<>( - clazz, transactionSigningKey, restriction, privateFrom, privacyGroupId); - } - - public - DeployPrivateSmartContractWithPrivacyGroupIdTransaction - createSmartContractWithPrivacyGroupId( - final Class clazz, - final String transactionSigningKey, - final String privateFrom, - final String privacyGroupId) { - return new DeployPrivateSmartContractWithPrivacyGroupIdTransaction<>( - clazz, transactionSigningKey, Restriction.RESTRICTED, privateFrom, privacyGroupId); - } - - public DeployPrivateSmartContractTransaction createSmartContract( - final Class clazz, - final String transactionSigningKey, - final String privateFrom, - final String... privateFor) { - return createSmartContract( - clazz, transactionSigningKey, privateFrom, Arrays.asList(privateFor)); - } - - public DeployPrivateSmartContractTransaction createSmartContract( - final Class clazz, - final String transactionSigningKey, - final String privateFrom, - final List privateFor) { - return new DeployPrivateSmartContractTransaction<>( - clazz, transactionSigningKey, privateFrom, privateFor); - } - - public CallPrivateSmartContractFunction callSmartContract( - final String contractAddress, - final String encodedFunction, - final String transactionSigningKey, - final Restriction restriction, - final String privateFrom, - final String... privateFor) { - return callSmartContract( - contractAddress, - encodedFunction, - transactionSigningKey, - restriction, - privateFrom, - Arrays.asList(privateFor)); - } - - public CallPrivateSmartContractFunction callSmartContract( - final String contractAddress, - final String encodedFunction, - final String transactionSigningKey, - final Restriction restriction, - final String privateFrom, - final List privateFor) { - return new CallPrivateSmartContractFunction( - contractAddress, - encodedFunction, - transactionSigningKey, - restriction, - privateFrom, - privateFor); - } - - public CallPrivateSmartContractFunction callSmartContractWithPrivacyGroupId( - final String contractAddress, - final String encodedFunction, - final String transactionSigningKey, - final Restriction restriction, - final String privateFrom, - final String privacyGroupId) { - return new CallPrivateSmartContractFunction( - contractAddress, - encodedFunction, - transactionSigningKey, - restriction, - privateFrom, - privacyGroupId); - } - - public LoadPrivateSmartContractTransaction loadSmartContract( - final String contractAddress, - final Class clazz, - final String transactionSigningKey, - final String privateFrom, - final String... privateFor) { - return loadSmartContract( - contractAddress, clazz, transactionSigningKey, privateFrom, Arrays.asList(privateFor)); - } - - public LoadPrivateSmartContractTransaction loadSmartContract( - final String contractAddress, - final Class clazz, - final String transactionSigningKey, - final String privateFrom, - final List privateFor) { - return new LoadPrivateSmartContractTransaction<>( - contractAddress, clazz, transactionSigningKey, privateFrom, privateFor); - } - - public - LoadPrivateSmartContractTransactionWithPrivacyGroupId loadSmartContractWithPrivacyGroupId( - final String contractAddress, - final Class clazz, - final String transactionSigningKey, - final String privateFrom, - final String privacyGroupId) { - return new LoadPrivateSmartContractTransactionWithPrivacyGroupId<>( - contractAddress, clazz, transactionSigningKey, privateFrom, privacyGroupId); - } - - public CallOnchainPermissioningPrivateSmartContractFunction callOnchainPermissioningSmartContract( - final String contractAddress, - final String encodedFunction, - final String transactionSigningKey, - final String privateFrom, - final String privacyGroupId) { - return new CallOnchainPermissioningPrivateSmartContractFunction( - contractAddress, encodedFunction, transactionSigningKey, privateFrom, privacyGroupId); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/AddToFlexiblePrivacyGroupTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/AddToFlexiblePrivacyGroupTransaction.java deleted file mode 100644 index 9c879bc421c..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/AddToFlexiblePrivacyGroupTransaction.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import org.web3j.crypto.Credentials; -import org.web3j.utils.Base64String; - -public class AddToFlexiblePrivacyGroupTransaction implements Transaction { - private final Base64String privacyGroupId; - private final PrivacyNode adder; - private final List addresses; - private final Credentials signer; - - public AddToFlexiblePrivacyGroupTransaction( - final String privacyGroupId, - final PrivacyNode adder, - final Credentials signer, - final PrivacyNode... nodes) { - this.privacyGroupId = Base64String.wrap(privacyGroupId); - this.adder = adder; - this.signer = signer; - this.addresses = - Arrays.stream(nodes) - .map(n -> n.getEnclave().getDefaultPublicKey()) - .collect(Collectors.toList()); - } - - @Override - public String execute(final NodeRequests node) { - try { - return node.privacy().privxAddToPrivacyGroup(privacyGroupId, adder, signer, addresses); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/CreateFlexiblePrivacyGroupTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/CreateFlexiblePrivacyGroupTransaction.java deleted file mode 100644 index bf2814a4b15..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/CreateFlexiblePrivacyGroupTransaction.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivacyRequestFactory.PrivxCreatePrivacyGroupResponse; - -import java.io.IOException; -import java.util.List; - -public class CreateFlexiblePrivacyGroupTransaction - implements Transaction { - private final PrivacyNode creator; - private final List addresses; - private final String privateFrom; - - CreateFlexiblePrivacyGroupTransaction( - final PrivacyNode creator, final String privateFrom, final List addresses) { - this.creator = creator; - this.addresses = addresses; - this.privateFrom = privateFrom; - } - - @Override - public PrivxCreatePrivacyGroupResponse execute(final NodeRequests node) { - try { - return node.privacy().privxCreatePrivacyGroup(creator, privateFrom, addresses); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/FindFlexiblePrivacyGroupTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/FindFlexiblePrivacyGroupTransaction.java deleted file mode 100644 index 0e8ac95b4f6..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/FindFlexiblePrivacyGroupTransaction.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivacyRequestFactory; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivacyRequestFactory.PrivxFindPrivacyGroupResponse; - -import java.io.IOException; -import java.util.List; -import java.util.stream.Collectors; - -import org.web3j.utils.Base64String; - -public class FindFlexiblePrivacyGroupTransaction - implements Transaction> { - private final List nodes; - - public FindFlexiblePrivacyGroupTransaction(final List nodeEnclaveKeys) { - this.nodes = nodeEnclaveKeys.stream().map(Base64String::wrap).collect(Collectors.toList()); - } - - @Override - public List execute(final NodeRequests node) { - try { - PrivxFindPrivacyGroupResponse result = - node.privacy().privxFindFlexiblePrivacyGroup(nodes).send(); - assertThat(result).isNotNull(); - if (result.hasError()) { - throw new RuntimeException(result.getError().getMessage()); - } - return result.getGroups(); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/FindPrivacyGroupTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/FindPrivacyGroupTransaction.java deleted file mode 100644 index 0b15b4c0c80..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/FindPrivacyGroupTransaction.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction; - -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; -import java.util.List; -import java.util.stream.Collectors; - -import org.web3j.protocol.besu.Besu; -import org.web3j.protocol.besu.response.privacy.PrivacyGroup; -import org.web3j.utils.Base64String; - -public class FindPrivacyGroupTransaction implements Transaction> { - private final List nodes; - - public FindPrivacyGroupTransaction(final List nodeEnclaveKeys) { - - this.nodes = nodeEnclaveKeys.stream().map(Base64String::wrap).collect(Collectors.toList()); - } - - @Override - public List execute(final NodeRequests node) { - final Besu besu = node.privacy().getBesuClient(); - try { - return besu.privFindPrivacyGroup(nodes).send().getGroups(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/GetAllPrivateMarkerTransactionHashes.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/GetAllPrivateMarkerTransactionHashes.java deleted file mode 100644 index d0056f50b43..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/GetAllPrivateMarkerTransactionHashes.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction; - -import static org.hyperledger.besu.ethereum.core.PrivacyParameters.DEFAULT_PRIVACY; - -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.List; - -import org.web3j.protocol.besu.Besu; -import org.web3j.protocol.core.DefaultBlockParameter; -import org.web3j.protocol.core.methods.response.EthBlock; - -public class GetAllPrivateMarkerTransactionHashes implements Transaction> { - @Override - public List execute(final NodeRequests node) { - final Besu besu = node.privacy().getBesuClient(); - final List toReturn = new ArrayList<>(); - try { - final long blockchainHeight = besu.ethBlockNumber().send().getBlockNumber().longValueExact(); - for (long i = 0; i <= blockchainHeight; i++) { - besu.ethGetBlockByNumber(DefaultBlockParameter.valueOf(BigInteger.valueOf(i)), true) - .send() - .getBlock() - .getTransactions() - .forEach( - t -> { - if (((EthBlock.TransactionObject) t).getTo().equals(DEFAULT_PRIVACY.toString())) { - toReturn.add(((EthBlock.TransactionObject) t).getHash()); - } - }); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - - return toReturn; - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/LockFlexiblePrivacyGroupTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/LockFlexiblePrivacyGroupTransaction.java deleted file mode 100644 index 92ed003ad43..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/LockFlexiblePrivacyGroupTransaction.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; - -import org.web3j.crypto.Credentials; -import org.web3j.protocol.exceptions.TransactionException; -import org.web3j.utils.Base64String; - -public class LockFlexiblePrivacyGroupTransaction implements Transaction { - private final Base64String privacyGroupId; - private final PrivacyNode locker; - private final Credentials signer; - - public LockFlexiblePrivacyGroupTransaction( - final String privacyGroupId, final PrivacyNode locker, final Credentials signer) { - this.privacyGroupId = Base64String.wrap(privacyGroupId); - this.locker = locker; - this.signer = signer; - } - - @Override - public String execute(final NodeRequests node) { - try { - return node.privacy().privxLockPrivacyGroup(locker, privacyGroupId, signer); - } catch (final IOException | TransactionException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/PluginCreateRandomPrivacyGroupIdTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/PluginCreateRandomPrivacyGroupIdTransaction.java deleted file mode 100644 index 180764449f7..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/PluginCreateRandomPrivacyGroupIdTransaction.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction; - -import org.hyperledger.besu.crypto.SecureRandomProvider; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import org.apache.tuweni.bytes.Bytes; - -public class PluginCreateRandomPrivacyGroupIdTransaction implements Transaction { - - @Override - public String execute(final NodeRequests node) { - final byte[] bytes = new byte[32]; - SecureRandomProvider.createSecureRandom().nextBytes(bytes); - return Bytes.wrap(bytes).toBase64String(); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/PrivDistributeTransactionTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/PrivDistributeTransactionTransaction.java deleted file mode 100644 index 60f0a746a99..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/PrivDistributeTransactionTransaction.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction; - -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; - -public class PrivDistributeTransactionTransaction implements Transaction { - private final String signedPrivateTransaction; - - public PrivDistributeTransactionTransaction(final String signedPrivateTransaction) { - this.signedPrivateTransaction = signedPrivateTransaction; - } - - @Override - public String execute(final NodeRequests node) { - try { - return node.privacy() - .privDistributeTransaction(signedPrivateTransaction) - .send() - .getTransactionKey(); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/PrivacyTransactions.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/PrivacyTransactions.java deleted file mode 100644 index 3763e40bb7c..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/PrivacyTransactions.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.ethereum.privacy.PrivacyGroupUtil; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.condition.PrivGetTransactionReceiptTransaction; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.util.LogFilterJsonParameter; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.EeaSendRawTransactionTransaction; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivCallTransaction; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivDebugGetStateRoot; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivGetCodeTransaction; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivGetLogsTransaction; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivGetTransaction; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.filter.PrivGetFilterChangesTransaction; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.filter.PrivGetFilterLogsTransaction; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.filter.PrivNewFilterTransaction; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.filter.PrivUninstallFilterTransaction; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.web3j.crypto.Credentials; -import org.web3j.tx.Contract; - -public class PrivacyTransactions { - - public PrivGetTransactionReceiptTransaction getPrivateTransactionReceipt( - final String transactionHash) { - return new PrivGetTransactionReceiptTransaction(transactionHash); - } - - public RestrictedCreatePrivacyGroupTransaction createPrivacyGroup( - final String name, final String description, final PrivacyNode... nodes) { - return new RestrictedCreatePrivacyGroupTransaction(name, description, nodes); - } - - public CreateFlexiblePrivacyGroupTransaction createFlexiblePrivacyGroup( - final PrivacyNode creator, - final String privateFrom, - final List addresses, - final String token) { - creator.getBesu().useAuthenticationTokenInHeaderForJsonRpc(token); - return new CreateFlexiblePrivacyGroupTransaction(creator, privateFrom, addresses); - } - - public CreateFlexiblePrivacyGroupTransaction createFlexiblePrivacyGroup( - final PrivacyNode creator, final String privateFrom, final List addresses) { - return new CreateFlexiblePrivacyGroupTransaction(creator, privateFrom, addresses); - } - - public AddToFlexiblePrivacyGroupTransaction addToPrivacyGroup( - final String privacyGroupId, - final PrivacyNode adder, - final Credentials signer, - final PrivacyNode... nodes) { - return new AddToFlexiblePrivacyGroupTransaction(privacyGroupId, adder, signer, nodes); - } - - public LockFlexiblePrivacyGroupTransaction privxLockPrivacyGroupAndCheck( - final String privacyGroupId, final PrivacyNode locker, final Credentials signer) { - return new LockFlexiblePrivacyGroupTransaction(privacyGroupId, locker, signer); - } - - public UnlockFlexiblePrivacyGroupTransaction privxUnlockPrivacyGroupAndCheck( - final String privacyGroupId, final PrivacyNode locker, final Credentials signer) { - return new UnlockFlexiblePrivacyGroupTransaction(privacyGroupId, locker, signer); - } - - public FindPrivacyGroupTransaction findPrivacyGroup(final List nodes) { - return new FindPrivacyGroupTransaction(nodes); - } - - public FindFlexiblePrivacyGroupTransaction findFlexiblePrivacyGroup(final List nodes) { - return new FindFlexiblePrivacyGroupTransaction(nodes); - } - - public PrivDistributeTransactionTransaction privDistributeTransaction( - final String signedPrivateTransaction) { - return new PrivDistributeTransactionTransaction(signedPrivateTransaction); - } - - public PrivCallTransaction privCall( - final String privacyGroupId, final Contract contract, final String encoded) { - return new PrivCallTransaction(privacyGroupId, contract, encoded); - } - - public PrivGetTransaction privGetTransaction(final String transactionHash) { - return new PrivGetTransaction(transactionHash); - } - - public PrivGetCodeTransaction privGetCode( - final String privacyGroupId, final Address contractAddress, final String blockParameter) { - return new PrivGetCodeTransaction(privacyGroupId, contractAddress, blockParameter); - } - - public PrivGetLogsTransaction privGetLogs( - final String privacyGroupId, final LogFilterJsonParameter filterParameter) { - return new PrivGetLogsTransaction(privacyGroupId, filterParameter); - } - - public RemoveFromFlexiblePrivacyGroupTransaction removeFromPrivacyGroup( - final String privacyGroupId, - final String remover, - final Credentials signer, - final String memberToRemove) { - return new RemoveFromFlexiblePrivacyGroupTransaction( - privacyGroupId, remover, signer, memberToRemove); - } - - public EeaSendRawTransactionTransaction sendRawTransaction(final String transaction) { - return new EeaSendRawTransactionTransaction(transaction); - } - - public PrivNewFilterTransaction newFilter( - final String privacyGroupId, final LogFilterJsonParameter filterParameter) { - return new PrivNewFilterTransaction(privacyGroupId, filterParameter); - } - - public PrivUninstallFilterTransaction uninstallFilter( - final String privacyGroupId, final String filterId) { - return new PrivUninstallFilterTransaction(privacyGroupId, filterId); - } - - public PrivGetFilterLogsTransaction getFilterLogs( - final String privacyGroupId, final String filterId) { - return new PrivGetFilterLogsTransaction(privacyGroupId, filterId); - } - - public PrivGetFilterChangesTransaction getFilterChanges( - final String privacyGroupId, final String filterId) { - return new PrivGetFilterChangesTransaction(privacyGroupId, filterId); - } - - public PrivDebugGetStateRoot debugGetStateRoot( - final String privacyGroupId, final String blockParam) { - return new PrivDebugGetStateRoot(privacyGroupId, blockParam); - } - - public String getLegacyPrivacyGroupId(final String privateFrom, final String... privateFor) { - - final Bytes32 privacyGroupId = - PrivacyGroupUtil.calculateEeaPrivacyGroupId( - Bytes.fromBase64String(privateFrom), - Arrays.stream(privateFor).map(Bytes::fromBase64String).collect(Collectors.toList())); - - return privacyGroupId.toBase64String(); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/RemoveFromFlexiblePrivacyGroupTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/RemoveFromFlexiblePrivacyGroupTransaction.java deleted file mode 100644 index 7f2712c81dc..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/RemoveFromFlexiblePrivacyGroupTransaction.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction; - -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; - -import org.web3j.crypto.Credentials; -import org.web3j.utils.Base64String; - -public class RemoveFromFlexiblePrivacyGroupTransaction implements Transaction { - private final Base64String privacyGroupId; - private final String remover; - private final String toRemove; - private final Credentials signer; - - public RemoveFromFlexiblePrivacyGroupTransaction( - final String privacyGroupId, - final String remover, - final Credentials signer, - final String toRemove) { - this.privacyGroupId = Base64String.wrap(privacyGroupId); - this.remover = remover; - this.signer = signer; - this.toRemove = toRemove; - } - - @Override - public String execute(final NodeRequests node) { - try { - return node.privacy().privxRemoveFromPrivacyGroup(privacyGroupId, remover, signer, toRemove); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/RestrictedCreatePrivacyGroupTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/RestrictedCreatePrivacyGroupTransaction.java deleted file mode 100644 index a455076f9fb..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/RestrictedCreatePrivacyGroupTransaction.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import org.web3j.protocol.besu.Besu; -import org.web3j.protocol.besu.response.privacy.PrivCreatePrivacyGroup; -import org.web3j.utils.Base64String; - -public class RestrictedCreatePrivacyGroupTransaction implements Transaction { - private final String name; - private final String description; - private final List addresses; - - public RestrictedCreatePrivacyGroupTransaction( - final String name, final String description, final PrivacyNode... nodes) { - this.name = name; - this.description = description; - this.addresses = - Arrays.stream(nodes) - .map(n -> Base64String.wrap(n.getEnclave().getDefaultPublicKey())) - .collect(Collectors.toList()); - } - - @Override - public String execute(final NodeRequests node) { - final Besu besu = node.privacy().getBesuClient(); - try { - final PrivCreatePrivacyGroup result = - besu.privCreatePrivacyGroup(addresses, name, description).send(); - if (result.hasError()) { - throw new RuntimeException(result.getError().getMessage()); - } else { - return result.getPrivacyGroupId().toString(); - } - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/UnlockFlexiblePrivacyGroupTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/UnlockFlexiblePrivacyGroupTransaction.java deleted file mode 100644 index 98b0236c32d..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/transaction/UnlockFlexiblePrivacyGroupTransaction.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; - -import org.web3j.crypto.Credentials; -import org.web3j.protocol.exceptions.TransactionException; -import org.web3j.utils.Base64String; - -public class UnlockFlexiblePrivacyGroupTransaction implements Transaction { - private final Base64String privacyGroupId; - private final PrivacyNode locker; - private final Credentials signer; - - public UnlockFlexiblePrivacyGroupTransaction( - final String privacyGroupId, final PrivacyNode locker, final Credentials signer) { - this.privacyGroupId = Base64String.wrap(privacyGroupId); - this.locker = locker; - this.signer = signer; - } - - @Override - public String execute(final NodeRequests node) { - try { - return node.privacy().privxUnlockPrivacyGroup(locker, privacyGroupId, signer); - } catch (final IOException | TransactionException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/util/LogFilterJsonParameter.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/util/LogFilterJsonParameter.java deleted file mode 100644 index 0ef7c51dc00..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/privacy/util/LogFilterJsonParameter.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.privacy.util; - -import java.util.List; - -public class LogFilterJsonParameter { - - private final String fromBlock; - private final String toBlock; - private final List addresses; - private final List> topics; - private final String blockhash; - - public LogFilterJsonParameter( - final String fromBlock, - final String toBlock, - final List addresses, - final List> topics, - final String blockhash) { - this.fromBlock = fromBlock; - this.toBlock = toBlock; - this.addresses = addresses; - this.topics = topics; - this.blockhash = blockhash; - } - - public String getFromBlock() { - return fromBlock; - } - - public String getToBlock() { - return toBlock; - } - - public List getAddresses() { - return addresses; - } - - public List> getTopics() { - return topics; - } - - public String getBlockhash() { - return blockhash; - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/pubsub/WebSocket.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/pubsub/WebSocket.java index 381800f5282..3976912e097 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/pubsub/WebSocket.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/pubsub/WebSocket.java @@ -53,6 +53,6 @@ public void unsubscribe(final Subscription subscription) { public void verifyTotalEventsReceived(final int expectedTotalEventCount) { WaitUtils.waitFor( - () -> assertThat(connection.getSubscriptionEvents()).hasSize(expectedTotalEventCount)); + 60, () -> assertThat(connection.getSubscriptionEvents()).hasSize(expectedTotalEventCount)); } } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/rpc/JsonRpcTestCase.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/rpc/JsonRpcTestCase.java index 50dbff13f94..662e49a1f7b 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/rpc/JsonRpcTestCase.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/rpc/JsonRpcTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -22,15 +22,18 @@ public class JsonRpcTestCase { private final JsonNode request; private final JsonNode response; private final int statusCode; + private final long waitTime; @JsonCreator public JsonRpcTestCase( @JsonProperty("request") final JsonNode request, @JsonProperty("response") final JsonNode response, - @JsonProperty("statusCode") final int statusCode) { + @JsonProperty("statusCode") final int statusCode, + @JsonProperty(value = "waitTime", defaultValue = "0") final long waitTime) { this.request = request; this.response = response; this.statusCode = statusCode; + this.waitTime = waitTime; } public JsonNode getRequest() { @@ -44,4 +47,8 @@ public JsonNode getResponse() { public int getStatusCode() { return statusCode; } + + public long getWaitTime() { + return waitTime; + } } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/DeploySmartContractTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/DeploySmartContractTransaction.java index 14bc9a550cb..97e5c9d3878 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/DeploySmartContractTransaction.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/DeploySmartContractTransaction.java @@ -20,6 +20,7 @@ import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.stream.Collectors; import org.web3j.crypto.Credentials; @@ -83,7 +84,7 @@ && parameterTypesAreEqual(i.getParameterTypes(), parameterObjects)) @SuppressWarnings("rawtypes") private boolean parameterTypesAreEqual( - final Class[] expectedTypes, final ArrayList actualObjects) { + final Class[] expectedTypes, final List actualObjects) { if (expectedTypes.length != actualObjects.size()) { return false; } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/NodeRequests.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/NodeRequests.java index 4741e397ddc..fea5dba3ff0 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/NodeRequests.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/NodeRequests.java @@ -21,22 +21,21 @@ import org.hyperledger.besu.tests.acceptance.dsl.transaction.miner.MinerRequestFactory; import org.hyperledger.besu.tests.acceptance.dsl.transaction.net.CustomRequestFactory; import org.hyperledger.besu.tests.acceptance.dsl.transaction.perm.PermissioningJsonRpcRequestFactory; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivacyRequestFactory; import org.hyperledger.besu.tests.acceptance.dsl.transaction.txpool.TxPoolRequestFactory; import java.util.Optional; import org.web3j.protocol.Web3j; +import org.web3j.protocol.Web3jService; import org.web3j.protocol.websocket.WebSocketService; public class NodeRequests { - + private final Web3jService web3jService; private final Web3j netEth; private final CliqueRequestFactory clique; private final BftRequestFactory bft; private final PermissioningJsonRpcRequestFactory perm; private final AdminRequestFactory admin; - private final PrivacyRequestFactory privacy; private final CustomRequestFactory custom; private final Optional websocketService; private final LoginRequestFactory login; @@ -44,23 +43,23 @@ public class NodeRequests { private final TxPoolRequestFactory txPool; public NodeRequests( + final Web3jService web3jService, final Web3j netEth, final CliqueRequestFactory clique, final BftRequestFactory bft, final PermissioningJsonRpcRequestFactory perm, final AdminRequestFactory admin, - final PrivacyRequestFactory privacy, final CustomRequestFactory custom, final MinerRequestFactory miner, final TxPoolRequestFactory txPool, final Optional websocketService, final LoginRequestFactory login) { + this.web3jService = web3jService; this.netEth = netEth; this.clique = clique; this.bft = bft; this.perm = perm; this.admin = admin; - this.privacy = privacy; this.custom = custom; this.miner = miner; this.txPool = txPool; @@ -96,10 +95,6 @@ public CustomRequestFactory custom() { return custom; } - public PrivacyRequestFactory privacy() { - return privacy; - } - public LoginRequestFactory login() { return login; } @@ -116,4 +111,8 @@ public void shutdown() { netEth.shutdown(); websocketService.ifPresent(WebSocketService::close); } + + public Web3jService getWeb3jService() { + return web3jService; + } } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/admin/AdminPeersTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/admin/AdminPeersTransaction.java index dffd700b93b..d2a5364dab4 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/admin/AdminPeersTransaction.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/admin/AdminPeersTransaction.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.tests.acceptance.dsl.transaction.admin; import static org.assertj.core.api.Assertions.assertThat; diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthCallTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthCallTransaction.java index 28ea6a0206a..8749a4083a8 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthCallTransaction.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthCallTransaction.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthEstimateGasTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthEstimateGasTransaction.java index f89bf75c7d7..31e0c518200 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthEstimateGasTransaction.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthEstimateGasTransaction.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthSyncingTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthSyncingTransaction.java new file mode 100644 index 00000000000..0d3d9a7d824 --- /dev/null +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthSyncingTransaction.java @@ -0,0 +1,40 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.dsl.transaction.eth; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; + +import java.io.IOException; + +import org.web3j.protocol.core.methods.response.EthSyncing; + +public class EthSyncingTransaction implements Transaction { + + EthSyncingTransaction() {} + + @Override + public Boolean execute(final NodeRequests node) { + try { + EthSyncing response = node.eth().ethSyncing().send(); + assertThat(response).isNotNull(); + return response.isSyncing(); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthTransactions.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthTransactions.java index 84882b822d0..b8b72052d0b 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthTransactions.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/eth/EthTransactions.java @@ -73,6 +73,10 @@ public EthMiningTransaction mining() { return new EthMiningTransaction(); } + public EthSyncingTransaction syncing() { + return new EthSyncingTransaction(); + } + public EthNewPendingTransactionFilterTransaction newPendingTransactionsFilter() { return new EthNewPendingTransactionFilterTransaction(); } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/miner/MinerGetMinGasPriceTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/miner/MinerGetMinGasPriceTransaction.java new file mode 100644 index 00000000000..991e9f7bc4e --- /dev/null +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/miner/MinerGetMinGasPriceTransaction.java @@ -0,0 +1,42 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.dsl.transaction.miner; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; + +import java.io.IOException; +import java.math.BigInteger; + +import org.web3j.protocol.core.methods.response.EthGasPrice; + +public class MinerGetMinGasPriceTransaction implements Transaction { + + @Override + public BigInteger execute(final NodeRequests node) { + try { + final EthGasPrice result = node.miner().minerGetMinGasPrice().send(); + assertThat(result).isNotNull(); + assertThat(result.hasError()).isFalse(); + + return result.getGasPrice(); + + } catch (final IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/miner/MinerRequestFactory.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/miner/MinerRequestFactory.java index 26be9d3a968..5c8b5ef6c10 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/miner/MinerRequestFactory.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/miner/MinerRequestFactory.java @@ -16,6 +16,7 @@ import org.web3j.protocol.Web3jService; import org.web3j.protocol.core.Request; +import org.web3j.protocol.core.methods.response.EthGasPrice; public class MinerRequestFactory { @@ -40,4 +41,8 @@ Request minerStop() { web3jService, org.web3j.protocol.core.methods.response.VoidResponse.class); } + + Request minerGetMinGasPrice() { + return new Request<>("miner_getMinGasPrice", null, web3jService, EthGasPrice.class); + } } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/miner/MinerTransactions.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/miner/MinerTransactions.java index e5cacd4e7a6..a8d24515a98 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/miner/MinerTransactions.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/miner/MinerTransactions.java @@ -23,4 +23,8 @@ public MinerStartTransaction minerStart() { public MinerStopTransaction minerStop() { return new MinerStopTransaction(); } + + public MinerGetMinGasPriceTransaction minerGetMinGasPrice() { + return new MinerGetMinGasPriceTransaction(); + } } diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/EeaSendRawTransactionTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/EeaSendRawTransactionTransaction.java deleted file mode 100644 index 5d2786ae8a6..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/EeaSendRawTransactionTransaction.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; - -public class EeaSendRawTransactionTransaction implements Transaction { - - final String transaction; - - public EeaSendRawTransactionTransaction(final String transaction) { - this.transaction = transaction; - } - - @Override - public Hash execute(final NodeRequests node) { - try { - final PrivacyRequestFactory.SendRawTransactionResponse result = - node.privacy().eeaSendRawTransaction(transaction).send(); - assertThat(result).isNotNull(); - if (result.hasError()) { - throw new RuntimeException(result.getError().getMessage()); - } - return result.getResult(); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivCallTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivCallTransaction.java deleted file mode 100644 index 7520ada4838..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivCallTransaction.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; - -import org.web3j.protocol.core.methods.response.EthCall; -import org.web3j.tx.Contract; - -public class PrivCallTransaction implements Transaction { - - private final String privacyGroupId; - private final Contract contract; - private final String encoded; - private final String blockNumberLatestPending; - - public PrivCallTransaction( - final String privacyGroupId, final Contract contract, final String encoded) { - this(privacyGroupId, contract, encoded, "latest"); - } - - public PrivCallTransaction( - final String privacyGroupId, - final Contract contract, - final String encoded, - final String blockNumberLatestPending) { - this.privacyGroupId = privacyGroupId; - this.contract = contract; - this.encoded = encoded; - this.blockNumberLatestPending = blockNumberLatestPending; - } - - @Override - public EthCall execute(final NodeRequests node) { - try { - final EthCall response = - node.privacy() - .privCall(privacyGroupId, contract, encoded, blockNumberLatestPending) - .send(); - assertThat(response).as("check response is not null").isNotNull(); - assertThat(response.getResult()).as("check result in response isn't null").isNotNull(); - return response; - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivCreatePrivacyGroupTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivCreatePrivacyGroupTransaction.java deleted file mode 100644 index eaddb84fc52..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivCreatePrivacyGroupTransaction.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.parameters.CreatePrivacyGroupParameter; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; -import java.util.List; - -public class PrivCreatePrivacyGroupTransaction implements Transaction { - - final CreatePrivacyGroupParameter params; - - public PrivCreatePrivacyGroupTransaction( - final List addresses, final String groupName, final String groupDescription) { - this.params = new CreatePrivacyGroupParameter(addresses, groupName, groupDescription); - } - - @Override - public String execute(final NodeRequests node) { - try { - final PrivacyRequestFactory.CreatePrivacyGroupResponse result = - node.privacy().privCreatePrivacyGroup(params).send(); - assertThat(result).isNotNull(); - return result.getResult(); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivDebugGetStateRoot.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivDebugGetStateRoot.java deleted file mode 100644 index 59e9dd36a6d..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivDebugGetStateRoot.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; - -public class PrivDebugGetStateRoot implements Transaction { - - private final String privacyGroupId; - private final String blockParam; - - public PrivDebugGetStateRoot(final String privacyGroupId, final String blockParam) { - this.privacyGroupId = privacyGroupId; - this.blockParam = blockParam; - } - - @Override - public PrivacyRequestFactory.DebugGetStateRoot execute(final NodeRequests node) { - try { - final PrivacyRequestFactory.DebugGetStateRoot response = - node.privacy().privDebugGetStateRoot(privacyGroupId, blockParam).send(); - assertThat(response).as("check response is not null").isNotNull(); - return response; - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivDeletePrivacyGroupTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivDeletePrivacyGroupTransaction.java deleted file mode 100644 index caf755839d8..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivDeletePrivacyGroupTransaction.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; - -public class PrivDeletePrivacyGroupTransaction implements Transaction { - - final String transactionHash; - - public PrivDeletePrivacyGroupTransaction(final String transactionHash) { - this.transactionHash = transactionHash; - } - - @Override - public String execute(final NodeRequests node) { - try { - final PrivacyRequestFactory.DeletePrivacyGroupResponse result = - node.privacy().privDeletePrivacyGroup(transactionHash).send(); - assertThat(result).isNotNull(); - if (result.hasError()) { - throw new RuntimeException(result.getError().getMessage()); - } - return result.getResult(); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivDistributeRawTransactionTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivDistributeRawTransactionTransaction.java deleted file mode 100644 index 8a9c70f8736..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivDistributeRawTransactionTransaction.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; - -public class PrivDistributeRawTransactionTransaction implements Transaction { - - final String transaction; - - public PrivDistributeRawTransactionTransaction(final String transaction) { - this.transaction = transaction; - } - - @Override - public String execute(final NodeRequests node) { - try { - final PrivacyRequestFactory.PrivDistributeTransactionResponse result = - node.privacy().privDistributeTransaction(transaction).send(); - assertThat(result).isNotNull(); - if (result.hasError()) { - throw new RuntimeException(result.getError().getMessage()); - } - return result.getTransactionKey(); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivFindPrivacyGroupTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivFindPrivacyGroupTransaction.java deleted file mode 100644 index 47fad9455c6..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivFindPrivacyGroupTransaction.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.enclave.types.PrivacyGroup; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; - -public class PrivFindPrivacyGroupTransaction implements Transaction { - - final String[] groupMembers; - - public PrivFindPrivacyGroupTransaction(final String[] groupMembers) { - this.groupMembers = groupMembers; - } - - @Override - public PrivacyGroup[] execute(final NodeRequests node) { - try { - final PrivacyRequestFactory.FindPrivacyGroupResponse result = - node.privacy().privFindPrivacyGroup(groupMembers).send(); - assertThat(result).isNotNull(); - if (result.hasError()) { - throw new RuntimeException(result.getError().getMessage()); - } - return result.getResult(); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivGetCodeTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivGetCodeTransaction.java deleted file mode 100644 index dbe708f53b5..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivGetCodeTransaction.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; - -import org.apache.tuweni.bytes.Bytes; - -public class PrivGetCodeTransaction implements Transaction { - - private final String privacyGroupId; - private final Address contractAddress; - private final String blockParameter; - - public PrivGetCodeTransaction( - final String privacyGroupId, final Address contractAddress, final String blockParameter) { - this.privacyGroupId = privacyGroupId; - this.contractAddress = contractAddress; - this.blockParameter = blockParameter; - } - - @Override - public Bytes execute(final NodeRequests node) { - try { - final PrivacyRequestFactory.GetCodeResponse response = - node.privacy() - .privGetCode(privacyGroupId, contractAddress.toHexString(), blockParameter) - .send(); - assertThat(response).as("check response is not null").isNotNull(); - assertThat(response.getResult()).as("check code in response isn't null").isNotNull(); - return Bytes.fromHexString(response.getResult()); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivGetEeaTransactionCountTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivGetEeaTransactionCountTransaction.java deleted file mode 100644 index b27240d1bc8..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivGetEeaTransactionCountTransaction.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; - -public class PrivGetEeaTransactionCountTransaction implements Transaction { - - private final Object[] params; - - public PrivGetEeaTransactionCountTransaction( - final String accountAddress, final String privateFrom, final String[] privateFor) { - this.params = new Object[] {accountAddress, privateFrom, privateFor}; - } - - @Override - public Integer execute(final NodeRequests node) { - try { - final PrivacyRequestFactory.GetTransactionCountResponse result = - node.privacy().privGetEeaTransactionCount(params).send(); - assertThat(result).isNotNull(); - if (result.hasError()) { - throw new RuntimeException(result.getError().getMessage()); - } - return result.getCount(); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivGetLogsTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivGetLogsTransaction.java deleted file mode 100644 index 562a247b5fd..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivGetLogsTransaction.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.util.LogFilterJsonParameter; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; -import java.util.List; - -import org.web3j.protocol.core.methods.response.EthLog; -import org.web3j.protocol.core.methods.response.EthLog.LogResult; - -@SuppressWarnings("rawtypes") -public class PrivGetLogsTransaction implements Transaction> { - - private final String privacyGroupId; - private final LogFilterJsonParameter filterParameter; - - public PrivGetLogsTransaction( - final String privacyGroupId, final LogFilterJsonParameter filterParameter) { - this.privacyGroupId = privacyGroupId; - this.filterParameter = filterParameter; - } - - @Override - public List execute(final NodeRequests node) { - try { - final EthLog response = node.privacy().privGetLogs(privacyGroupId, filterParameter).send(); - - assertThat(response).as("check response is not null").isNotNull(); - assertThat(response.getResult()).as("check result in response isn't null").isNotNull(); - - return response.getLogs(); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivGetPrivacyPrecompileAddressTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivGetPrivacyPrecompileAddressTransaction.java deleted file mode 100644 index cf8cd3f0656..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivGetPrivacyPrecompileAddressTransaction.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; - -public class PrivGetPrivacyPrecompileAddressTransaction implements Transaction
{ - - @Override - public Address execute(final NodeRequests node) { - try { - final PrivacyRequestFactory.GetPrivacyPrecompileAddressResponse result = - node.privacy().privGetPrivacyPrecompileAddress().send(); - assertThat(result).isNotNull(); - return result.getResult(); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivGetPrivateTransactionTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivGetPrivateTransactionTransaction.java deleted file mode 100644 index e91a83f314b..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivGetPrivateTransactionTransaction.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivateTransactionGroupResponse; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; - -public class PrivGetPrivateTransactionTransaction - implements Transaction { - - private final Hash transactionHash; - - public PrivGetPrivateTransactionTransaction(final Hash transactionHash) { - this.transactionHash = transactionHash; - } - - @Override - public PrivateTransactionGroupResponse execute(final NodeRequests node) { - try { - final PrivacyRequestFactory.GetPrivateTransactionResponse result = - node.privacy().privGetPrivateTransaction(transactionHash).send(); - assertThat(result).isNotNull(); - return result.getResult(); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivGetTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivGetTransaction.java deleted file mode 100644 index 0d6e599a1e2..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivGetTransaction.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; - -public class PrivGetTransaction - implements Transaction { - - private final String transactionHash; - - public PrivGetTransaction(final String transactionHash) { - this.transactionHash = transactionHash; - } - - @Override - public PrivacyRequestFactory.GetPrivateTransactionResponse execute(final NodeRequests node) { - try { - final PrivacyRequestFactory.GetPrivateTransactionResponse response = - node.privacy().privGetPrivateTransaction(Hash.fromHexString(transactionHash)).send(); - assertThat(response).as("check response is not null").isNotNull(); - assertThat(response.getResult()).as("check result in response isn't null").isNotNull(); - return response; - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivGetTransactionCountTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivGetTransactionCountTransaction.java deleted file mode 100644 index e222516beeb..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivGetTransactionCountTransaction.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; - -public class PrivGetTransactionCountTransaction implements Transaction { - - private final Object[] params; - - public PrivGetTransactionCountTransaction( - final String accountAddress, final String privacyGroupId) { - this.params = new String[] {accountAddress, privacyGroupId}; - } - - @Override - public Integer execute(final NodeRequests node) { - try { - final PrivacyRequestFactory.GetTransactionCountResponse result = - node.privacy().privGetTransactionCount(params).send(); - assertThat(result).isNotNull(); - if (result.hasError()) { - throw new RuntimeException(result.getError().getMessage()); - } - return result.getCount(); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivGetTransactionReceiptTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivGetTransactionReceiptTransaction.java deleted file mode 100644 index da2f6b310c6..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivGetTransactionReceiptTransaction.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; - -import org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt; - -public class PrivGetTransactionReceiptTransaction - implements Transaction { - - private final Hash transaction; - - public PrivGetTransactionReceiptTransaction(final Hash transaction) { - this.transaction = transaction; - } - - @Override - public PrivateTransactionReceipt execute(final NodeRequests node) { - try { - final PrivacyRequestFactory.GetTransactionReceiptResponse result = - node.privacy().privGetTransactionReceipt(transaction).send(); - assertThat(result).isNotNull(); - return result.getResult(); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivacyRequestFactory.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivacyRequestFactory.java deleted file mode 100644 index 056598a9d56..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivacyRequestFactory.java +++ /dev/null @@ -1,617 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy; - -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.ethereum.core.PrivacyParameters.FLEXIBLE_PRIVACY_PROXY; - -import org.hyperledger.besu.crypto.SecureRandomProvider; -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.enclave.types.PrivacyGroup; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.parameters.CreatePrivacyGroupParameter; -import org.hyperledger.besu.ethereum.privacy.group.FlexibleGroupManagement; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivateTransactionGroupResponse; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.util.LogFilterJsonParameter; - -import java.io.IOException; -import java.math.BigInteger; -import java.security.SecureRandom; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import org.apache.tuweni.bytes.Bytes; -import org.web3j.abi.FunctionEncoder; -import org.web3j.abi.TypeReference; -import org.web3j.abi.Utils; -import org.web3j.abi.datatypes.Bool; -import org.web3j.abi.datatypes.DynamicArray; -import org.web3j.abi.datatypes.DynamicBytes; -import org.web3j.abi.datatypes.Function; -import org.web3j.abi.datatypes.Type; -import org.web3j.crypto.Credentials; -import org.web3j.protocol.Web3jService; -import org.web3j.protocol.besu.Besu; -import org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt; -import org.web3j.protocol.core.Request; -import org.web3j.protocol.core.Response; -import org.web3j.protocol.core.methods.response.EthCall; -import org.web3j.protocol.core.methods.response.EthFilter; -import org.web3j.protocol.core.methods.response.EthLog; -import org.web3j.protocol.core.methods.response.EthSendTransaction; -import org.web3j.protocol.core.methods.response.EthUninstallFilter; -import org.web3j.protocol.eea.crypto.PrivateTransactionEncoder; -import org.web3j.protocol.eea.crypto.RawPrivateTransaction; -import org.web3j.protocol.exceptions.TransactionException; -import org.web3j.tx.ChainIdLong; -import org.web3j.tx.Contract; -import org.web3j.tx.PrivateTransactionManager; -import org.web3j.tx.response.PollingPrivateTransactionReceiptProcessor; -import org.web3j.utils.Base64String; -import org.web3j.utils.Numeric; -import org.web3j.utils.Restriction; - -public class PrivacyRequestFactory { - - private final SecureRandom secureRandom; - - public PrivateTransactionManager getTransactionManager( - final Credentials credentials, - final Base64String privateFrom, - final List privateFor, - final Restriction restriction) { - return new PrivateTransactionManager( - getBesuClient(), - credentials, - new PollingPrivateTransactionReceiptProcessor(getBesuClient(), 1000, 60), - ChainIdLong.NONE, - privateFrom, - privateFor, - restriction); - } - - public PrivateTransactionManager getTransactionManager( - final Credentials credentials, - final Base64String privateFrom, - final Base64String privacyGroupId, - final Restriction restriction) { - return new PrivateTransactionManager( - getBesuClient(), - credentials, - new PollingPrivateTransactionReceiptProcessor(getBesuClient(), 1000, 60), - ChainIdLong.NONE, - privateFrom, - privacyGroupId, - restriction); - } - - public static class GetPrivacyPrecompileAddressResponse extends Response
{} - - public static class GetPrivateTransactionResponse - extends Response {} - - public static class CreatePrivacyGroupResponse extends Response {} - - public static class DeletePrivacyGroupResponse extends Response {} - - public static class FindPrivacyGroupResponse extends Response {} - - public static class SendRawTransactionResponse extends Response {} - - public static class GetTransactionReceiptResponse extends Response {} - - public static class GetTransactionCountResponse extends Response { - - final Integer count; - - @JsonCreator - public GetTransactionCountResponse(@JsonProperty("result") final String result) { - this.count = result == null ? null : Integer.decode(result); - } - - public Integer getCount() { - return count; - } - } - - public static class GetCodeResponse extends Response {} - - public static class DebugGetStateRoot extends Response {} - - public Request privDistributeTransaction( - final String signedPrivateTransaction) { - return new Request<>( - "priv_distributeRawTransaction", - singletonList(signedPrivateTransaction), - web3jService, - PrivDistributeTransactionResponse.class); - } - - private final Besu besuClient; - private final Web3jService web3jService; - - public PrivacyRequestFactory(final Web3jService web3jService) { - this.web3jService = web3jService; - this.besuClient = Besu.build(web3jService); - this.secureRandom = SecureRandomProvider.createSecureRandom(); - } - - public Besu getBesuClient() { - return besuClient; - } - - public static class PrivDistributeTransactionResponse extends Response { - - public PrivDistributeTransactionResponse() {} - - public String getTransactionKey() { - return getResult(); - } - } - - public String privxAddToPrivacyGroup( - final Base64String privacyGroupId, - final PrivacyNode adder, - final Credentials signer, - final List addresses) - throws IOException { - - final BigInteger nonce = - besuClient - .privGetTransactionCount(signer.getAddress(), privacyGroupId) - .send() - .getTransactionCount(); - - final Bytes payload = - encodeAddToGroupFunctionCall( - addresses.stream().map(Bytes::fromBase64String).collect(Collectors.toList())); - - final RawPrivateTransaction privateTransaction = - RawPrivateTransaction.createTransaction( - nonce, - BigInteger.valueOf(1000), - BigInteger.valueOf(3000000), - FLEXIBLE_PRIVACY_PROXY.toHexString(), - payload.toHexString(), - Base64String.wrap(adder.getEnclaveKey()), - privacyGroupId, - org.web3j.utils.Restriction.RESTRICTED); - - return besuClient - .eeaSendRawTransaction( - Numeric.toHexString(PrivateTransactionEncoder.signMessage(privateTransaction, signer))) - .send() - .getTransactionHash(); - } - - public String privxRemoveFromPrivacyGroup( - final Base64String privacyGroupId, - final String removerTenant, - final Credentials signer, - final String toRemove) - throws IOException { - - final BigInteger nonce = - besuClient - .privGetTransactionCount(signer.getAddress(), privacyGroupId) - .send() - .getTransactionCount(); - - final Bytes payload = encodeRemoveFromGroupFunctionCall(Bytes.fromBase64String(toRemove)); - - final RawPrivateTransaction privateTransaction = - RawPrivateTransaction.createTransaction( - nonce, - BigInteger.valueOf(1000), - BigInteger.valueOf(3000000), - FLEXIBLE_PRIVACY_PROXY.toHexString(), - payload.toHexString(), - Base64String.wrap(removerTenant), - privacyGroupId, - org.web3j.utils.Restriction.RESTRICTED); - - return besuClient - .eeaSendRawTransaction( - Numeric.toHexString(PrivateTransactionEncoder.signMessage(privateTransaction, signer))) - .send() - .getTransactionHash(); - } - - private Bytes encodeRemoveFromGroupFunctionCall(final Bytes toRemove) { - final Function function = - new Function( - "removeParticipant", - Arrays.asList(new DynamicBytes(toRemove.toArrayUnsafe())), - Arrays.asList(new TypeReference() {})); - - return Bytes.fromHexString(FunctionEncoder.encode(function)); - } - - public String privxLockPrivacyGroup( - final PrivacyNode locker, final Base64String privacyGroupId, final Credentials signer) - throws IOException, TransactionException { - return privxLockOrUnlockPrivacyGroup( - locker, - privacyGroupId, - signer, - FlexibleGroupManagement.LOCK_GROUP_METHOD_SIGNATURE.toHexString()); - } - - public String privxUnlockPrivacyGroup( - final PrivacyNode locker, final Base64String privacyGroupId, final Credentials signer) - throws IOException, TransactionException { - return privxLockOrUnlockPrivacyGroup( - locker, - privacyGroupId, - signer, - FlexibleGroupManagement.UNLOCK_GROUP_METHOD_SIGNATURE.toHexString()); - } - - private String privxLockOrUnlockPrivacyGroup( - final PrivacyNode locker, - final Base64String privacyGroupId, - final Credentials signer, - final String callData) - throws IOException, TransactionException { - final BigInteger nonce = - besuClient - .privGetTransactionCount(signer.getAddress(), privacyGroupId) - .send() - .getTransactionCount(); - - final RawPrivateTransaction privateTransaction = - RawPrivateTransaction.createTransaction( - nonce, - BigInteger.valueOf(1000), - BigInteger.valueOf(3000000), - FLEXIBLE_PRIVACY_PROXY.toHexString(), - callData, - Base64String.wrap(locker.getEnclaveKey()), - privacyGroupId, - org.web3j.utils.Restriction.RESTRICTED); - - final String transactionHash = - besuClient - .eeaSendRawTransaction( - Numeric.toHexString( - PrivateTransactionEncoder.signMessage(privateTransaction, signer))) - .send() - .getTransactionHash(); - - final PrivateTransactionReceipt privateTransactionReceipt = - new PollingPrivateTransactionReceiptProcessor(besuClient, 3000, 10) - .waitForTransactionReceipt(transactionHash); - - assertThat(privateTransactionReceipt.getStatus()).isEqualTo("0x1"); - - return privateTransactionReceipt.getcommitmentHash(); - } - - public PrivxCreatePrivacyGroupResponse privxCreatePrivacyGroup( - final PrivacyNode creator, final String privateFrom, final List addresses) - throws IOException { - - final byte[] bytes = new byte[32]; - secureRandom.nextBytes(bytes); - final Bytes privacyGroupId = Bytes.wrap(bytes); - - final Bytes payload = - encodeAddToGroupFunctionCall( - addresses.stream().map(Bytes::fromBase64String).collect(Collectors.toList())); - - final RawPrivateTransaction privateTransaction = - RawPrivateTransaction.createTransaction( - BigInteger.ZERO, - BigInteger.valueOf(1000), - BigInteger.valueOf(3000000), - FLEXIBLE_PRIVACY_PROXY.toHexString(), - payload.toHexString(), - Base64String.wrap(privateFrom), - Base64String.wrap(privacyGroupId.toArrayUnsafe()), - org.web3j.utils.Restriction.RESTRICTED); - - final Request ethSendTransactionRequest = - besuClient.eeaSendRawTransaction( - Numeric.toHexString( - PrivateTransactionEncoder.signMessage( - privateTransaction, Credentials.create(creator.getTransactionSigningKey())))); - final String transactionHash = ethSendTransactionRequest.send().getTransactionHash(); - return new PrivxCreatePrivacyGroupResponse(privacyGroupId.toBase64String(), transactionHash); - } - - public Request privxFindFlexiblePrivacyGroup( - final List nodes) { - return new Request<>( - "privx_findFlexiblePrivacyGroup", - singletonList(nodes), - web3jService, - PrivxFindPrivacyGroupResponse.class); - } - - public Request privGetPrivacyPrecompileAddress() { - return new Request<>( - "priv_getPrivacyPrecompileAddress", - Collections.emptyList(), - web3jService, - GetPrivacyPrecompileAddressResponse.class); - } - - public Request privGetPrivateTransaction( - final Hash transactionHash) { - return new Request<>( - "priv_getPrivateTransaction", - singletonList(transactionHash.toHexString()), - web3jService, - GetPrivateTransactionResponse.class); - } - - public Request privCreatePrivacyGroup( - final CreatePrivacyGroupParameter params) { - return new Request<>( - "priv_createPrivacyGroup", - singletonList(params), - web3jService, - CreatePrivacyGroupResponse.class); - } - - public Request privDeletePrivacyGroup(final String groupId) { - return new Request<>( - "priv_deletePrivacyGroup", - singletonList(groupId), - web3jService, - DeletePrivacyGroupResponse.class); - } - - public Request privFindPrivacyGroup(final String[] groupMembers) { - return new Request<>( - "priv_findPrivacyGroup", - singletonList(groupMembers), - web3jService, - FindPrivacyGroupResponse.class); - } - - public Request eeaSendRawTransaction(final String transaction) { - return new Request<>( - "eea_sendRawTransaction", - singletonList(transaction), - web3jService, - SendRawTransactionResponse.class); - } - - public Request privGetTransactionReceipt( - final Hash transactionHash) { - return new Request<>( - "priv_getTransactionReceipt", - singletonList(transactionHash.toHexString()), - web3jService, - GetTransactionReceiptResponse.class); - } - - public Request privGetTransactionCount(final Object[] params) { - return new Request<>( - "priv_getTransactionCount", - List.of(params), - web3jService, - GetTransactionCountResponse.class); - } - - public Request privGetEeaTransactionCount(final Object[] params) { - return new Request<>( - "priv_getEeaTransactionCount", - List.of(params), - web3jService, - GetTransactionCountResponse.class); - } - - public Request privGetCode( - final String privacyGroupId, final String contractAddress, final String blockParameter) { - return new Request<>( - "priv_getCode", - List.of(privacyGroupId, contractAddress, blockParameter), - web3jService, - GetCodeResponse.class); - } - - public Request privCall( - final String privacyGroupId, - final Contract contract, - final String encoded, - final String blockNumberLatestPending) { - - final org.web3j.protocol.core.methods.request.Transaction transaction = - org.web3j.protocol.core.methods.request.Transaction.createEthCallTransaction( - null, contract.getContractAddress(), encoded); - - return new Request<>( - "priv_call", - Arrays.asList(privacyGroupId, transaction, blockNumberLatestPending), - web3jService, - EthCall.class); - } - - public Request privGetLogs( - final String privacyGroupId, final LogFilterJsonParameter filterParameter) { - - return new Request<>( - "priv_getLogs", Arrays.asList(privacyGroupId, filterParameter), web3jService, EthLog.class); - } - - public Request privNewFilter( - final String privacyGroupId, final LogFilterJsonParameter filterParameter) { - return new Request<>( - "priv_newFilter", - Arrays.asList(privacyGroupId, filterParameter), - web3jService, - EthFilter.class); - } - - public Request privUninstallFilter( - final String privacyGroupId, final String filterId) { - return new Request<>( - "priv_uninstallFilter", - Arrays.asList(privacyGroupId, filterId), - web3jService, - EthUninstallFilter.class); - } - - public Request privGetFilterLogs(final String privacyGroupId, final String filterId) { - - return new Request<>( - "priv_getFilterLogs", Arrays.asList(privacyGroupId, filterId), web3jService, EthLog.class); - } - - public Request privGetFilterChanges( - final String privacyGroupId, final String filterId) { - - return new Request<>( - "priv_getFilterChanges", - Arrays.asList(privacyGroupId, filterId), - web3jService, - EthLog.class); - } - - public Request privDebugGetStateRoot( - final String privacyGroupId, final String blockParam) { - return new Request<>( - "priv_debugGetStateRoot", - Arrays.asList(privacyGroupId, blockParam), - web3jService, - DebugGetStateRoot.class); - } - - public static class PrivxFindPrivacyGroupResponse extends Response> { - - public List getGroups() { - return getResult(); - } - } - - public static class FlexiblePrivacyGroup { - - private final Base64String privacyGroupId; - private final List members; - private final String name; - private final String description; - - public enum Type { - FLEXIBLE - } - - @JsonCreator - public FlexiblePrivacyGroup( - @JsonProperty(value = "privacyGroupId") final String privacyGroupId, - @JsonProperty(value = "type") final Type type, - @JsonProperty(value = "name") final String name, - @JsonProperty(value = "description") final String description, - @JsonProperty(value = "members") final List members) { - this(privacyGroupId, members); - } - - public FlexiblePrivacyGroup(final String privacyGroupId, final List members) { - this.privacyGroupId = Base64String.wrap(privacyGroupId); - this.name = ""; - this.description = ""; - this.members = members; - } - - public Base64String getPrivacyGroupId() { - return privacyGroupId; - } - - public String getName() { - return name; - } - - public String getDescription() { - return description; - } - - public Type getType() { - return Type.FLEXIBLE; - } - - public List getMembers() { - return members; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - final FlexiblePrivacyGroup that = (FlexiblePrivacyGroup) o; - return getPrivacyGroupId().equals(that.getPrivacyGroupId()) - && getName().equals(that.getName()) - && getDescription().equals(that.getDescription()) - && getType() == that.getType() - && getMembers().equals(that.getMembers()); - } - - @Override - public int hashCode() { - return Objects.hash( - getPrivacyGroupId(), getName(), getDescription(), getType(), getMembers()); - } - } - - public static class PrivxCreatePrivacyGroupResponse { - - final String privacyGroupId; - final String transactionHash; - - @JsonCreator - public PrivxCreatePrivacyGroupResponse( - @JsonProperty("privacyGroupId") final String privacyGroupId, - @JsonProperty("transactionHash") final String transactionHash) { - this.privacyGroupId = privacyGroupId; - this.transactionHash = transactionHash; - } - - public String getPrivacyGroupId() { - return privacyGroupId; - } - - public String getTransactionHash() { - return transactionHash; - } - } - - private Bytes encodeAddToGroupFunctionCall(final List participants) { - final Function function = - new Function( - "addParticipants", - Arrays.asList( - new DynamicArray<>( - DynamicBytes.class, - Utils.typeMap( - participants.stream() - .map(Bytes::toArrayUnsafe) - .collect(Collectors.toList()), - DynamicBytes.class))), - Collections.emptyList()); - - return Bytes.fromHexString(FunctionEncoder.encode(function)); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivacyTransactions.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivacyTransactions.java deleted file mode 100644 index 89a06e8b73b..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/PrivacyTransactions.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy; - -import org.hyperledger.besu.datatypes.Hash; - -import java.util.List; - -public class PrivacyTransactions { - public PrivGetPrivacyPrecompileAddressTransaction getPrivacyPrecompileAddress() { - return new PrivGetPrivacyPrecompileAddressTransaction(); - } - - public PrivGetPrivateTransactionTransaction getPrivateTransaction(final Hash transactionHash) { - return new PrivGetPrivateTransactionTransaction(transactionHash); - } - - public PrivCreatePrivacyGroupTransaction createPrivacyGroup( - final List addresses, final String groupName, final String groupDescription) { - return new PrivCreatePrivacyGroupTransaction(addresses, groupName, groupDescription); - } - - public PrivDeletePrivacyGroupTransaction deletePrivacyGroup(final String transactionHash) { - return new PrivDeletePrivacyGroupTransaction(transactionHash); - } - - public PrivFindPrivacyGroupTransaction findPrivacyGroup(final String[] groupMembers) { - return new PrivFindPrivacyGroupTransaction(groupMembers); - } - - public EeaSendRawTransactionTransaction sendRawTransaction(final String transaction) { - return new EeaSendRawTransactionTransaction(transaction); - } - - public PrivDistributeRawTransactionTransaction distributeRawTransaction( - final String transaction) { - return new PrivDistributeRawTransactionTransaction(transaction); - } - - public PrivGetTransactionCountTransaction getTransactionCount( - final String accountAddress, final String privacyGroupId) { - return new PrivGetTransactionCountTransaction(accountAddress, privacyGroupId); - } - - public PrivGetEeaTransactionCountTransaction getEeaTransactionCount( - final String accountAddress, final String privateFrom, final String[] privateFor) { - return new PrivGetEeaTransactionCountTransaction(accountAddress, privateFrom, privateFor); - } - - public PrivGetTransactionReceiptTransaction getTransactionReceipt(final Hash transactionHash) { - return new PrivGetTransactionReceiptTransaction(transactionHash); - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/filter/PrivGetFilterChangesTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/filter/PrivGetFilterChangesTransaction.java deleted file mode 100644 index de8f2390c86..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/filter/PrivGetFilterChangesTransaction.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.filter; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; -import java.util.List; - -import org.web3j.protocol.core.methods.response.EthLog; -import org.web3j.protocol.core.methods.response.EthLog.LogResult; - -@SuppressWarnings("rawtypes") -public class PrivGetFilterChangesTransaction implements Transaction> { - - private final String privacyGroupId; - private final String filterId; - - public PrivGetFilterChangesTransaction(final String privacyGroupId, final String filterId) { - this.privacyGroupId = privacyGroupId; - this.filterId = filterId; - } - - @Override - public List execute(final NodeRequests node) { - try { - final EthLog response = node.privacy().privGetFilterChanges(privacyGroupId, filterId).send(); - - assertThat(response).as("check response is not null").isNotNull(); - assertThat(response.getResult()).as("check result in response isn't null").isNotNull(); - assertThat(response.getLogs()).isNotNull(); - - return response.getLogs(); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/filter/PrivGetFilterLogsTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/filter/PrivGetFilterLogsTransaction.java deleted file mode 100644 index 4362062d8f2..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/filter/PrivGetFilterLogsTransaction.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.filter; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; -import java.util.List; - -import org.web3j.protocol.core.methods.response.EthLog; -import org.web3j.protocol.core.methods.response.EthLog.LogResult; - -@SuppressWarnings("rawtypes") -public class PrivGetFilterLogsTransaction implements Transaction> { - - private final String privacyGroupId; - private final String filterId; - - public PrivGetFilterLogsTransaction(final String privacyGroupId, final String filterId) { - this.privacyGroupId = privacyGroupId; - this.filterId = filterId; - } - - @Override - public List execute(final NodeRequests node) { - try { - final EthLog response = node.privacy().privGetFilterLogs(privacyGroupId, filterId).send(); - - assertThat(response).as("check response is not null").isNotNull(); - assertThat(response.getResult()).as("check result in response isn't null").isNotNull(); - assertThat(response.getLogs()).isNotNull(); - - return response.getLogs(); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/filter/PrivNewFilterTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/filter/PrivNewFilterTransaction.java deleted file mode 100644 index fd2d76f4323..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/filter/PrivNewFilterTransaction.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.filter; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.util.LogFilterJsonParameter; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; - -import org.web3j.protocol.core.methods.response.EthFilter; - -public class PrivNewFilterTransaction implements Transaction { - - private final String privacyGroupId; - private final LogFilterJsonParameter filterParameter; - - public PrivNewFilterTransaction( - final String privacyGroupId, final LogFilterJsonParameter filterParameter) { - this.privacyGroupId = privacyGroupId; - this.filterParameter = filterParameter; - } - - @Override - public String execute(final NodeRequests node) { - try { - final EthFilter response = - node.privacy().privNewFilter(privacyGroupId, filterParameter).send(); - - assertThat(response).as("check response is not null").isNotNull(); - assertThat(response.getResult()).as("check result in response isn't null").isNotNull(); - - return response.getResult(); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/filter/PrivUninstallFilterTransaction.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/filter/PrivUninstallFilterTransaction.java deleted file mode 100644 index b628d99cd41..00000000000 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/privacy/filter/PrivUninstallFilterTransaction.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.filter; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.io.IOException; - -import org.web3j.protocol.core.methods.response.EthUninstallFilter; - -public class PrivUninstallFilterTransaction implements Transaction { - - private final String privacyGroupId; - private final String filterId; - - public PrivUninstallFilterTransaction(final String privacyGroupId, final String filterId) { - this.privacyGroupId = privacyGroupId; - this.filterId = filterId; - } - - @Override - public Boolean execute(final NodeRequests node) { - try { - final EthUninstallFilter response = - node.privacy().privUninstallFilter(privacyGroupId, filterId).send(); - - assertThat(response).as("check response is not null").isNotNull(); - assertThat(response.getResult()).as("check result in response isn't null").isNotNull(); - - return response.isUninstalled(); - } catch (final IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/acceptance-tests/test-plugins/build.gradle b/acceptance-tests/test-plugins/build.gradle index f9e960b4944..65a3dc64dc8 100644 --- a/acceptance-tests/test-plugins/build.gradle +++ b/acceptance-tests/test-plugins/build.gradle @@ -7,11 +7,11 @@ dependencies { implementation project(':datatypes') implementation project(':ethereum:core') implementation project(':ethereum:rlp') + implementation project(':ethereum:api') implementation project(':plugin-api') implementation 'com.google.auto.service:auto-service' implementation 'info.picocli:picocli' - testImplementation 'junit:junit' testImplementation 'org.assertj:assertj-core' testImplementation 'org.junit.jupiter:junit-jupiter' @@ -25,7 +25,8 @@ task testPluginsJar(type: Jar) { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } from sourceSets.main.output diff --git a/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/BadCLIOptionsPlugin.java b/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/BadCLIOptionsPlugin.java index a3fe06c572c..d9bde19b1ea 100644 --- a/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/BadCLIOptionsPlugin.java +++ b/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/BadCLIOptionsPlugin.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.tests.acceptance.plugins; diff --git a/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/TestBlockchainServiceFinalizedPlugin.java b/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/TestBlockchainServiceFinalizedPlugin.java new file mode 100644 index 00000000000..a61b80ab8e4 --- /dev/null +++ b/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/TestBlockchainServiceFinalizedPlugin.java @@ -0,0 +1,149 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.plugins; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; +import org.hyperledger.besu.plugin.BesuContext; +import org.hyperledger.besu.plugin.BesuPlugin; +import org.hyperledger.besu.plugin.data.BlockContext; +import org.hyperledger.besu.plugin.services.BlockchainService; +import org.hyperledger.besu.plugin.services.RpcEndpointService; +import org.hyperledger.besu.plugin.services.exception.PluginRpcEndpointException; +import org.hyperledger.besu.plugin.services.rpc.PluginRpcRequest; + +import java.util.Optional; + +import com.google.auto.service.AutoService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@AutoService(BesuPlugin.class) +public class TestBlockchainServiceFinalizedPlugin implements BesuPlugin { + private static final Logger LOG = + LoggerFactory.getLogger(TestBlockchainServiceFinalizedPlugin.class); + private static final String RPC_NAMESPACE = "updater"; + private static final String RPC_METHOD_FINALIZED_BLOCK = "updateFinalizedBlockV1"; + private static final String RPC_METHOD_SAFE_BLOCK = "updateSafeBlockV1"; + + @Override + public void register(final BesuContext besuContext) { + LOG.trace("Registering plugin ..."); + + final RpcEndpointService rpcEndpointService = + besuContext + .getService(RpcEndpointService.class) + .orElseThrow( + () -> + new RuntimeException( + "Failed to obtain RpcEndpointService from the BesuContext.")); + + final BlockchainService blockchainService = + besuContext + .getService(BlockchainService.class) + .orElseThrow( + () -> + new RuntimeException( + "Failed to obtain BlockchainService from the BesuContext.")); + + final FinalizationUpdaterRpcMethod rpcMethod = + new FinalizationUpdaterRpcMethod(blockchainService); + rpcEndpointService.registerRPCEndpoint( + RPC_NAMESPACE, RPC_METHOD_FINALIZED_BLOCK, rpcMethod::setFinalizedBlock); + rpcEndpointService.registerRPCEndpoint( + RPC_NAMESPACE, RPC_METHOD_SAFE_BLOCK, rpcMethod::setSafeBlock); + } + + @Override + public void start() { + LOG.trace("Starting plugin ..."); + } + + @Override + public void stop() { + LOG.trace("Stopping plugin ..."); + } + + static class FinalizationUpdaterRpcMethod { + private final BlockchainService blockchainService; + private final JsonRpcParameter parameterParser = new JsonRpcParameter(); + + FinalizationUpdaterRpcMethod(final BlockchainService blockchainService) { + this.blockchainService = blockchainService; + } + + Boolean setFinalizedBlock(final PluginRpcRequest request) { + return setFinalizedOrSafeBlock(request, true); + } + + Boolean setSafeBlock(final PluginRpcRequest request) { + return setFinalizedOrSafeBlock(request, false); + } + + private Boolean setFinalizedOrSafeBlock( + final PluginRpcRequest request, final boolean isFinalized) { + final Long blockNumberToSet = parseResult(request); + + // lookup finalized block by number in local chain + final Optional finalizedBlock = + blockchainService.getBlockByNumber(blockNumberToSet); + if (finalizedBlock.isEmpty()) { + throw new PluginRpcEndpointException( + RpcErrorType.BLOCK_NOT_FOUND, + "Block not found in the local chain: " + blockNumberToSet); + } + + try { + final Hash blockHash = finalizedBlock.get().getBlockHeader().getBlockHash(); + if (isFinalized) { + blockchainService.setFinalizedBlock(blockHash); + } else { + blockchainService.setSafeBlock(blockHash); + } + } catch (final IllegalArgumentException e) { + throw new PluginRpcEndpointException( + RpcErrorType.BLOCK_NOT_FOUND, + "Block not found in the local chain: " + blockNumberToSet); + } catch (final UnsupportedOperationException e) { + throw new PluginRpcEndpointException( + RpcErrorType.METHOD_NOT_ENABLED, + "Method not enabled for PoS network: setFinalizedBlock"); + } catch (final Exception e) { + throw new PluginRpcEndpointException( + RpcErrorType.INTERNAL_ERROR, "Error setting finalized block: " + blockNumberToSet); + } + + return Boolean.TRUE; + } + + private Long parseResult(final PluginRpcRequest request) { + Long blockNumber; + try { + final Object[] params = request.getParams(); + blockNumber = parameterParser.required(params, 0, Long.class); + } catch (final Exception e) { + throw new PluginRpcEndpointException(RpcErrorType.INVALID_PARAMS, e.getMessage()); + } + + if (blockNumber <= 0) { + throw new PluginRpcEndpointException( + RpcErrorType.INVALID_PARAMS, "Block number must be greater than 0"); + } + + return blockNumber; + } + } +} diff --git a/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/TestInProcessRpcServicePlugin.java b/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/TestInProcessRpcServicePlugin.java new file mode 100644 index 00000000000..3ebced0df91 --- /dev/null +++ b/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/TestInProcessRpcServicePlugin.java @@ -0,0 +1,81 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.plugins; + +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.plugin.BesuContext; +import org.hyperledger.besu.plugin.BesuPlugin; +import org.hyperledger.besu.plugin.services.PicoCLIOptions; +import org.hyperledger.besu.plugin.services.RpcEndpointService; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; + +import com.google.auto.service.AutoService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import picocli.CommandLine; + +@AutoService(BesuPlugin.class) +public class TestInProcessRpcServicePlugin implements BesuPlugin { + private static final Logger LOG = LoggerFactory.getLogger(TestInProcessRpcServicePlugin.class); + + private RpcEndpointService rpcEndpointService; + + @CommandLine.Option(names = {"--plugin-test-set-min-gas-price"}) + long minGasPrice = -1; + + @Override + public void register(final BesuContext context) { + final PicoCLIOptions cmdlineOptions = + context + .getService(PicoCLIOptions.class) + .orElseThrow( + () -> + new IllegalStateException( + "Failed to obtain PicoCLI options from the BesuContext")); + + cmdlineOptions.addPicoCLIOptions("test", this); + + rpcEndpointService = + context + .getService(RpcEndpointService.class) + .orElseThrow( + () -> + new RuntimeException( + "Failed to obtain RpcEndpointService from the BesuContext.")); + } + + @Override + public void start() { + LOG.info("TestInProcessRpcServicePlugin minGasPrice option: {}", minGasPrice); + if (minGasPrice >= 0) { + callSetMinGasPrice(minGasPrice); + } + } + + @Override + public void stop() {} + + private void callSetMinGasPrice(final long minGasPrice) { + LOG.info("Setting minGasPrice via in-process RPC service"); + final var minGasPriceWei = Wei.of(minGasPrice); + final var resp = + rpcEndpointService.call( + "miner_setMinGasPrice", new Object[] {minGasPriceWei.toShortHexString()}); + LOG.info("miner_setMinGasPrice response: {}", resp); + if (!resp.getType().equals(RpcResponseType.SUCCESS)) { + throw new RuntimeException("Internal setMinGasPrice method failed: " + resp); + } + } +} diff --git a/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/TestPermissioningPlugin.java b/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/TestPermissioningPlugin.java index 2964e76229f..44afeeadaa5 100644 --- a/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/TestPermissioningPlugin.java +++ b/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/TestPermissioningPlugin.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.tests.acceptance.plugins; import org.hyperledger.besu.plugin.BesuContext; diff --git a/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/TestRpcEndpointServicePlugin.java b/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/TestRpcEndpointServicePlugin.java index fee061f5449..67980373ea2 100644 --- a/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/TestRpcEndpointServicePlugin.java +++ b/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/TestRpcEndpointServicePlugin.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/privacy/TestPrivacyGroupGenesisProvider.java b/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/privacy/TestPrivacyGroupGenesisProvider.java index d763683ba7f..24d32e759a3 100644 --- a/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/privacy/TestPrivacyGroupGenesisProvider.java +++ b/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/privacy/TestPrivacyGroupGenesisProvider.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.tests.acceptance.plugins.privacy; diff --git a/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/privacy/TestPrivacyPluginPayloadProvider.java b/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/privacy/TestPrivacyPluginPayloadProvider.java index 2a26218a6b8..c375946c98b 100644 --- a/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/privacy/TestPrivacyPluginPayloadProvider.java +++ b/acceptance-tests/test-plugins/src/main/java/org/hyperledger/besu/tests/acceptance/plugins/privacy/TestPrivacyPluginPayloadProvider.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.tests.acceptance.plugins.privacy; diff --git a/acceptance-tests/test-plugins/src/test/java/org/hyperledger/besu/services/BesuPluginContextImplTest.java b/acceptance-tests/test-plugins/src/test/java/org/hyperledger/besu/services/BesuPluginContextImplTest.java index 5fd73b66b8f..7e277041776 100644 --- a/acceptance-tests/test-plugins/src/test/java/org/hyperledger/besu/services/BesuPluginContextImplTest.java +++ b/acceptance-tests/test-plugins/src/test/java/org/hyperledger/besu/services/BesuPluginContextImplTest.java @@ -16,26 +16,33 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.junit.jupiter.api.Assertions.assertThrows; +import org.hyperledger.besu.ethereum.core.plugins.PluginConfiguration; +import org.hyperledger.besu.ethereum.core.plugins.PluginInfo; import org.hyperledger.besu.plugin.BesuPlugin; +import org.hyperledger.besu.tests.acceptance.plugins.TestBesuEventsPlugin; import org.hyperledger.besu.tests.acceptance.plugins.TestPicoCLIPlugin; -import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; +import java.util.NoSuchElementException; import java.util.Optional; -import org.assertj.core.api.Assertions; import org.assertj.core.api.ThrowableAssert; -import org.junit.After; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class BesuPluginContextImplTest { + private static final Path DEFAULT_PLUGIN_DIRECTORY = Paths.get("."); + private BesuPluginContextImpl contextImpl; - @BeforeClass + @BeforeAll public static void createFakePluginDir() throws IOException { if (System.getProperty("besu.plugins.dir") == null) { final Path pluginDir = Files.createTempDirectory("besuTest"); @@ -44,21 +51,26 @@ public static void createFakePluginDir() throws IOException { } } - @After + @AfterEach public void clearTestPluginState() { System.clearProperty("testPicoCLIPlugin.testOption"); } + @BeforeEach + void setup() { + contextImpl = new BesuPluginContextImpl(); + } + @Test public void verifyEverythingGoesSmoothly() { - final BesuPluginContextImpl contextImpl = new BesuPluginContextImpl(); + assertThat(contextImpl.getRegisteredPlugins()).isEmpty(); + contextImpl.registerPlugins( + PluginConfiguration.builder().pluginsDir(DEFAULT_PLUGIN_DIRECTORY).build()); + assertThat(contextImpl.getRegisteredPlugins()).isNotEmpty(); - assertThat(contextImpl.getPlugins()).isEmpty(); - contextImpl.registerPlugins(new File(".").toPath()); - assertThat(contextImpl.getPlugins()).isNotEmpty(); - - final Optional testPluginOptional = findTestPlugin(contextImpl.getPlugins()); - Assertions.assertThat(testPluginOptional).isPresent(); + final Optional testPluginOptional = + findTestPlugin(contextImpl.getRegisteredPlugins(), TestPicoCLIPlugin.class); + assertThat(testPluginOptional).isPresent(); final TestPicoCLIPlugin testPicoCLIPlugin = testPluginOptional.get(); assertThat(testPicoCLIPlugin.getState()).isEqualTo("registered"); @@ -72,33 +84,36 @@ public void verifyEverythingGoesSmoothly() { @Test public void registrationErrorsHandledSmoothly() { - final BesuPluginContextImpl contextImpl = new BesuPluginContextImpl(); System.setProperty("testPicoCLIPlugin.testOption", "FAILREGISTER"); - assertThat(contextImpl.getPlugins()).isEmpty(); - contextImpl.registerPlugins(new File(".").toPath()); - assertThat(contextImpl.getPlugins()).isNotInstanceOfAny(TestPicoCLIPlugin.class); + assertThat(contextImpl.getRegisteredPlugins()).isEmpty(); + contextImpl.registerPlugins( + PluginConfiguration.builder().pluginsDir(DEFAULT_PLUGIN_DIRECTORY).build()); + assertThat(contextImpl.getRegisteredPlugins()).isNotInstanceOfAny(TestPicoCLIPlugin.class); contextImpl.beforeExternalServices(); - assertThat(contextImpl.getPlugins()).isNotInstanceOfAny(TestPicoCLIPlugin.class); + assertThat(contextImpl.getRegisteredPlugins()).isNotInstanceOfAny(TestPicoCLIPlugin.class); contextImpl.startPlugins(); - assertThat(contextImpl.getPlugins()).isNotInstanceOfAny(TestPicoCLIPlugin.class); + assertThat(contextImpl.getRegisteredPlugins()).isNotInstanceOfAny(TestPicoCLIPlugin.class); contextImpl.stopPlugins(); - assertThat(contextImpl.getPlugins()).isNotInstanceOfAny(TestPicoCLIPlugin.class); + assertThat(contextImpl.getRegisteredPlugins()).isNotInstanceOfAny(TestPicoCLIPlugin.class); } @Test public void startErrorsHandledSmoothly() { - final BesuPluginContextImpl contextImpl = new BesuPluginContextImpl(); System.setProperty("testPicoCLIPlugin.testOption", "FAILSTART"); - assertThat(contextImpl.getPlugins()).isEmpty(); - contextImpl.registerPlugins(new File(".").toPath()); - assertThat(contextImpl.getPlugins()).extracting("class").contains(TestPicoCLIPlugin.class); + assertThat(contextImpl.getRegisteredPlugins()).isEmpty(); + contextImpl.registerPlugins( + PluginConfiguration.builder().pluginsDir(DEFAULT_PLUGIN_DIRECTORY).build()); + assertThat(contextImpl.getRegisteredPlugins()) + .extracting("class") + .contains(TestPicoCLIPlugin.class); - final Optional testPluginOptional = findTestPlugin(contextImpl.getPlugins()); + final Optional testPluginOptional = + findTestPlugin(contextImpl.getRegisteredPlugins(), TestPicoCLIPlugin.class); assertThat(testPluginOptional).isPresent(); final TestPicoCLIPlugin testPicoCLIPlugin = testPluginOptional.get(); assertThat(testPicoCLIPlugin.getState()).isEqualTo("registered"); @@ -106,22 +121,25 @@ public void startErrorsHandledSmoothly() { contextImpl.beforeExternalServices(); contextImpl.startPlugins(); assertThat(testPicoCLIPlugin.getState()).isEqualTo("failstart"); - assertThat(contextImpl.getPlugins()).isNotInstanceOfAny(TestPicoCLIPlugin.class); + assertThat(contextImpl.getRegisteredPlugins()).isNotInstanceOfAny(TestPicoCLIPlugin.class); contextImpl.stopPlugins(); - assertThat(contextImpl.getPlugins()).isNotInstanceOfAny(TestPicoCLIPlugin.class); + assertThat(contextImpl.getRegisteredPlugins()).isNotInstanceOfAny(TestPicoCLIPlugin.class); } @Test public void stopErrorsHandledSmoothly() { - final BesuPluginContextImpl contextImpl = new BesuPluginContextImpl(); System.setProperty("testPicoCLIPlugin.testOption", "FAILSTOP"); - assertThat(contextImpl.getPlugins()).isEmpty(); - contextImpl.registerPlugins(new File(".").toPath()); - assertThat(contextImpl.getPlugins()).extracting("class").contains(TestPicoCLIPlugin.class); + assertThat(contextImpl.getRegisteredPlugins()).isEmpty(); + contextImpl.registerPlugins( + PluginConfiguration.builder().pluginsDir(DEFAULT_PLUGIN_DIRECTORY).build()); + assertThat(contextImpl.getRegisteredPlugins()) + .extracting("class") + .contains(TestPicoCLIPlugin.class); - final Optional testPluginOptional = findTestPlugin(contextImpl.getPlugins()); + final Optional testPluginOptional = + findTestPlugin(contextImpl.getRegisteredPlugins(), TestPicoCLIPlugin.class); assertThat(testPluginOptional).isPresent(); final TestPicoCLIPlugin testPicoCLIPlugin = testPluginOptional.get(); assertThat(testPicoCLIPlugin.getState()).isEqualTo("registered"); @@ -136,9 +154,10 @@ public void stopErrorsHandledSmoothly() { @Test public void lifecycleExceptions() throws Throwable { - final BesuPluginContextImpl contextImpl = new BesuPluginContextImpl(); final ThrowableAssert.ThrowingCallable registerPlugins = - () -> contextImpl.registerPlugins(new File(".").toPath()); + () -> + contextImpl.registerPlugins( + PluginConfiguration.builder().pluginsDir(DEFAULT_PLUGIN_DIRECTORY).build()); assertThatExceptionOfType(IllegalStateException.class).isThrownBy(contextImpl::startPlugins); assertThatExceptionOfType(IllegalStateException.class).isThrownBy(contextImpl::stopPlugins); @@ -158,9 +177,96 @@ public void lifecycleExceptions() throws Throwable { assertThatExceptionOfType(IllegalStateException.class).isThrownBy(contextImpl::stopPlugins); } - private Optional findTestPlugin(final List plugins) { + @Test + public void shouldRegisterAllPluginsWhenNoPluginsOption() { + final PluginConfiguration config = + PluginConfiguration.builder().pluginsDir(DEFAULT_PLUGIN_DIRECTORY).build(); + + assertThat(contextImpl.getRegisteredPlugins()).isEmpty(); + contextImpl.registerPlugins(config); + final Optional testPluginOptional = + findTestPlugin(contextImpl.getRegisteredPlugins(), TestPicoCLIPlugin.class); + assertThat(testPluginOptional).isPresent(); + final TestPicoCLIPlugin testPicoCLIPlugin = testPluginOptional.get(); + assertThat(testPicoCLIPlugin.getState()).isEqualTo("registered"); + } + + @Test + public void shouldRegisterOnlySpecifiedPluginWhenPluginsOptionIsSet() { + final PluginConfiguration config = createConfigurationForSpecificPlugin("TestPicoCLIPlugin"); + + assertThat(contextImpl.getRegisteredPlugins()).isEmpty(); + contextImpl.registerPlugins(config); + + final Optional requestedPlugin = + findTestPlugin(contextImpl.getRegisteredPlugins(), TestPicoCLIPlugin.class); + + assertThat(requestedPlugin).isPresent(); + assertThat(requestedPlugin.get().getState()).isEqualTo("registered"); + + final Optional nonRequestedPlugin = + findTestPlugin(contextImpl.getRegisteredPlugins(), TestBesuEventsPlugin.class); + + assertThat(nonRequestedPlugin).isEmpty(); + } + + @Test + public void shouldNotRegisterUnspecifiedPluginsWhenPluginsOptionIsSet() { + final PluginConfiguration config = createConfigurationForSpecificPlugin("TestPicoCLIPlugin"); + assertThat(contextImpl.getRegisteredPlugins()).isEmpty(); + contextImpl.registerPlugins(config); + + final Optional nonRequestedPlugin = + findTestPlugin(contextImpl.getRegisteredPlugins(), TestBesuEventsPlugin.class); + assertThat(nonRequestedPlugin).isEmpty(); + } + + @Test + void shouldThrowExceptionIfExplicitlySpecifiedPluginNotFound() { + PluginConfiguration config = createConfigurationForSpecificPlugin("NonExistentPlugin"); + + String exceptionMessage = + assertThrows(NoSuchElementException.class, () -> contextImpl.registerPlugins(config)) + .getMessage(); + final String expectedMessage = + "The following requested plugins were not found: NonExistentPlugin"; + assertThat(exceptionMessage).isEqualTo(expectedMessage); + assertThat(contextImpl.getRegisteredPlugins()).isEmpty(); + } + + @Test + void shouldNotRegisterAnyPluginsIfExternalPluginsDisabled() { + PluginConfiguration config = + PluginConfiguration.builder() + .pluginsDir(DEFAULT_PLUGIN_DIRECTORY) + .externalPluginsEnabled(false) + .build(); + contextImpl.registerPlugins(config); + assertThat(contextImpl.getRegisteredPlugins().isEmpty()).isTrue(); + } + + @Test + void shouldRegisterPluginsIfExternalPluginsEnabled() { + PluginConfiguration config = + PluginConfiguration.builder() + .pluginsDir(DEFAULT_PLUGIN_DIRECTORY) + .externalPluginsEnabled(true) + .build(); + contextImpl.registerPlugins(config); + assertThat(contextImpl.getRegisteredPlugins().isEmpty()).isFalse(); + } + + private PluginConfiguration createConfigurationForSpecificPlugin(final String pluginName) { + return PluginConfiguration.builder() + .requestedPlugins(List.of(new PluginInfo(pluginName))) + .pluginsDir(DEFAULT_PLUGIN_DIRECTORY) + .build(); + } + + private Optional findTestPlugin( + final List plugins, final Class type) { return plugins.stream() - .filter(p -> p instanceof TestPicoCLIPlugin) + .filter(p -> type.equals(p.getClass())) .map(p -> (TestPicoCLIPlugin) p) .findFirst(); } diff --git a/acceptance-tests/tests/build.gradle b/acceptance-tests/tests/build.gradle index cc4a54b009b..8bb8fb2f2f7 100644 --- a/acceptance-tests/tests/build.gradle +++ b/acceptance-tests/tests/build.gradle @@ -12,24 +12,20 @@ */ plugins { - id 'org.web3j' version '4.9.2' - id 'org.web3j.solidity' version '0.3.5' -} - -configurations.all { - resolutionStrategy.eachDependency { DependencyResolveDetails details -> - if (details.requested.group == 'org.web3j' && details.requested.version == '4.9.2') { - details.useVersion '4.9.4' - details.because 'Plugin version is 4.9.2 (latest), but we want it to use web3j libs version 4.9.4' - } - } + id 'org.web3j' version '4.11.3' + id 'org.web3j.solidity' version '0.4.1' } web3j { generatedPackageName = 'org.hyperledger.besu.tests.web3j.generated' } sourceSets.main.solidity.srcDirs = ["$projectDir/contracts"] -solidity { resolvePackages = false } +solidity { + resolvePackages = false + // TODO: remove the forced version, when DEV network is upgraded to support latest forks + version '0.8.19' + evmVersion 'london' +} dependencies { api 'org.slf4j:slf4j-api' @@ -55,7 +51,6 @@ dependencies { testImplementation project(':testutil') testImplementation project(':util') - testImplementation 'com.github.tomakehurst:wiremock-jre8-standalone' testImplementation 'commons-io:commons-io' testImplementation 'io.grpc:grpc-all' testImplementation 'io.grpc:grpc-core' @@ -84,6 +79,8 @@ dependencies { testImplementation 'org.web3j:abi' testImplementation 'org.web3j:besu' testImplementation 'org.web3j:core' + testImplementation 'org.wiremock:wiremock' + testImplementation project(path: ':acceptance-tests:tests:shanghai') testRuntimeOnly 'org.junit.vintage:junit-vintage-engine' } @@ -98,7 +95,7 @@ sourceSets { } } -processTestResources.dependsOn(':acceptance-tests:test-plugins:testPluginsJar') +processTestResources.dependsOn(':acceptance-tests:test-plugins:testPluginsJar',':acceptance-tests:test-plugins:jar') task acceptanceTest(type: Test) { inputs.property "integration.date", LocalTime.now() // so it runs at every invocation @@ -125,12 +122,12 @@ task acceptanceTest(type: Test) { doFirst { mkdir "${buildDir}/jvmErrorLogs" } } -task acceptanceTestMainnet(type: Test) { + +task acceptanceTestNotPrivacy(type: Test) { inputs.property "integration.date", LocalTime.now() // so it runs at every invocation - exclude '**/bft/**' - exclude '**/clique/**' - exclude '**/permissioning/**' exclude '**/privacy/**' + exclude '**/permissioning/**' + exclude '**/bftsoak/**' useJUnitPlatform {} @@ -139,7 +136,7 @@ task acceptanceTestMainnet(type: Test) { systemProperty 'acctests.runBesuAsProcess', 'true' systemProperty 'java.security.properties', "${buildDir}/resources/test/acceptanceTesting.security" mustRunAfter rootProject.subprojects*.test - description = 'Runs MAINNET Besu acceptance tests (excluding permissioning, privacy and some other stable features).' + description = 'Runs MAINNET Besu acceptance tests (excluding specific non-mainnet suites).' group = 'verification' jvmArgs "-XX:ErrorFile=${buildDir}/jvmErrorLogs/java_err_pid%p.log" @@ -183,18 +180,20 @@ task acceptanceTestCliqueBft(type: Test) { doFirst { mkdir "${buildDir}/jvmErrorLogs" } } -task acceptanceTestPrivacy(type: Test) { +task acceptanceTestBftSoak(type: Test) { inputs.property "integration.date", LocalTime.now() // so it runs at every invocation - include '**/privacy/**' + include '**/bftsoak/**' useJUnitPlatform {} dependsOn(rootProject.installDist) setSystemProperties(test.getSystemProperties()) systemProperty 'acctests.runBesuAsProcess', 'true' + // Set to any time > 60 minutes to run the soak test for longer + // systemProperty 'acctests.soakTimeMins', '120' systemProperty 'java.security.properties', "${buildDir}/resources/test/acceptanceTesting.security" mustRunAfter rootProject.subprojects*.test - description = 'Runs Privacy Besu acceptance tests.' + description = 'Runs BFT soak test.' group = 'verification' jvmArgs "-XX:ErrorFile=${buildDir}/jvmErrorLogs/java_err_pid%p.log" @@ -202,7 +201,7 @@ task acceptanceTestPrivacy(type: Test) { testLogging { exceptionFormat = 'full' showStackTraces = true - showStandardStreams = Boolean.getBoolean('acctests.showStandardStreams') + showStandardStreams = true showExceptions = true showCauses = true } diff --git a/acceptance-tests/tests/contracts/CrossContractReader.sol b/acceptance-tests/tests/contracts/CrossContractReader.sol index e54e4c712eb..9524d5bde86 100644 --- a/acceptance-tests/tests/contracts/CrossContractReader.sol +++ b/acceptance-tests/tests/contracts/CrossContractReader.sol @@ -19,7 +19,7 @@ import "./EventEmitter.sol"; // compile with: // solc CrossContractReader.sol --bin --abi --optimize --overwrite -o . // then create web3j wrappers with: -// web3j solidity generate -b ./generated/CrossContractReader.bin -a ./generated/CrossContractReader.abi -o ../../../../../ -p org.hyperledger.besu.tests.web3j.generated +// web3j generate solidity -b ./generated/CrossContractReader.bin -a ./generated/CrossContractReader.abi -o ../../../../../ -p org.hyperledger.besu.tests.web3j.generated contract CrossContractReader { uint counter; @@ -59,4 +59,4 @@ contract CrossContractReader { CrossContractReader cross = CrossContractReader(crossAddress); cross.destroy(); } -} \ No newline at end of file +} diff --git a/acceptance-tests/tests/contracts/EventEmitter.sol b/acceptance-tests/tests/contracts/EventEmitter.sol index 22afa7a0868..05b8868eee9 100644 --- a/acceptance-tests/tests/contracts/EventEmitter.sol +++ b/acceptance-tests/tests/contracts/EventEmitter.sol @@ -17,7 +17,7 @@ pragma solidity >=0.7.0 <0.9.0; // compile with: // solc EventEmitter.sol --bin --abi --optimize --overwrite -o . // then create web3j wrappers with: -// web3j solidity generate -b ./generated/EventEmitter.bin -a ./generated/EventEmitter.abi -o ../../../../../ -p org.hyperledger.besu.tests.web3j.generated +// web3j generate solidity -b ./generated/EventEmitter.bin -a ./generated/EventEmitter.abi -o ../../../../../ -p org.hyperledger.besu.tests.web3j.generated contract EventEmitter { address owner; event stored(address _to, uint _amount); @@ -41,4 +41,4 @@ contract EventEmitter { function sender() view public returns (address) { return _sender; } -} \ No newline at end of file +} diff --git a/acceptance-tests/tests/contracts/RemoteSimpleStorage.sol b/acceptance-tests/tests/contracts/RemoteSimpleStorage.sol index 072948e035b..f399658789f 100644 --- a/acceptance-tests/tests/contracts/RemoteSimpleStorage.sol +++ b/acceptance-tests/tests/contracts/RemoteSimpleStorage.sol @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -19,7 +19,7 @@ import "./SimpleStorage.sol"; // compile with: // solc RemoteSimpleStorage.sol --bin --abi --optimize --overwrite -o . // then create web3j wrappers with: -// web3j solidity generate -b ./generated/RemoteSimpleStorage.bin -a ./generated/RemoteSimpleStorage.abi -o ../../../../../ -p org.hyperledger.besu.tests.web3j.generated +// web3j generate solidity -b ./generated/RemoteSimpleStorage.bin -a ./generated/RemoteSimpleStorage.abi -o ../../../../../ -p org.hyperledger.besu.tests.web3j.generated contract RemoteSimpleStorage { SimpleStorage public simpleStorage; @@ -34,4 +34,4 @@ contract RemoteSimpleStorage { function get() public view returns (uint) { return simpleStorage.get(); } -} \ No newline at end of file +} diff --git a/acceptance-tests/tests/contracts/RevertReason.sol b/acceptance-tests/tests/contracts/RevertReason.sol index d632910662c..2d42cafe3c2 100644 --- a/acceptance-tests/tests/contracts/RevertReason.sol +++ b/acceptance-tests/tests/contracts/RevertReason.sol @@ -17,7 +17,7 @@ pragma solidity >=0.7.0 <0.9.0; // compile with: // solc RevertReason.sol --bin --abi --optimize --overwrite -o . // then create web3j wrappers with: -// web3j solidity generate -b ./generated/RevertReason.bin -a ./generated/RevertReason.abi -o ../../../../../ -p org.hyperledger.besu.tests.web3j.generated +// web3j generate solidity -b ./generated/RevertReason.bin -a ./generated/RevertReason.abi -o ../../../../../ -p org.hyperledger.besu.tests.web3j.generated contract RevertReason { function revertWithRevertReason() public pure returns (bool) { @@ -27,4 +27,4 @@ contract RevertReason { function revertWithoutRevertReason() public pure returns (bool) { revert(); } -} \ No newline at end of file +} diff --git a/acceptance-tests/tests/contracts/SimpleStorage.sol b/acceptance-tests/tests/contracts/SimpleStorage.sol index 35dc7c3932f..712e2a87456 100644 --- a/acceptance-tests/tests/contracts/SimpleStorage.sol +++ b/acceptance-tests/tests/contracts/SimpleStorage.sol @@ -12,12 +12,12 @@ * * SPDX-License-Identifier: Apache-2.0 */ -pragma solidity >=0.7.0 <0.9.0; +pragma solidity >=0.7.0 <0.8.20; // compile with: // solc SimpleStorage.sol --bin --abi --optimize --overwrite -o . // then create web3j wrappers with: -// web3j solidity generate -b ./generated/SimpleStorage.bin -a ./generated/SimpleStorage.abi -o ../../../../../ -p org.hyperledger.besu.tests.web3j.generated +// web3j generate solidity -b ./generated/SimpleStorage.bin -a ./generated/SimpleStorage.abi -o ../../../../../ -p org.hyperledger.besu.tests.web3j.generated contract SimpleStorage { uint data; @@ -28,4 +28,4 @@ contract SimpleStorage { function get() public view returns (uint) { return data; } -} \ No newline at end of file +} diff --git a/acceptance-tests/tests/contracts/TestDepth.sol b/acceptance-tests/tests/contracts/TestDepth.sol index ec49a1ad8bf..3b4e17904c3 100644 --- a/acceptance-tests/tests/contracts/TestDepth.sol +++ b/acceptance-tests/tests/contracts/TestDepth.sol @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -29,4 +29,4 @@ contract TestDepth { x = gasleft(); } } -} \ No newline at end of file +} diff --git a/acceptance-tests/tests/shanghai/build.gradle b/acceptance-tests/tests/shanghai/build.gradle new file mode 100644 index 00000000000..c309d9e9914 --- /dev/null +++ b/acceptance-tests/tests/shanghai/build.gradle @@ -0,0 +1,21 @@ + +plugins { + id 'org.web3j' version '4.11.3' + id 'org.web3j.solidity' version '0.4.1' +} + +jar { enabled = true } + +web3j { + generatedPackageName = 'org.hyperledger.besu.tests.web3j.generated' +} + +sourceSets.main.solidity.srcDirs = [ + "$projectDir/shanghaicontracts" +] + +solidity { + resolvePackages = false + version '0.8.25' + evmVersion 'shanghai' +} diff --git a/acceptance-tests/tests/shanghai/shanghaicontracts/SimpleStorageShanghai.sol b/acceptance-tests/tests/shanghai/shanghaicontracts/SimpleStorageShanghai.sol new file mode 100644 index 00000000000..32a7d9a2a09 --- /dev/null +++ b/acceptance-tests/tests/shanghai/shanghaicontracts/SimpleStorageShanghai.sol @@ -0,0 +1,31 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +pragma solidity >=0.8.20; + +// compile with: +// solc SimpleStorageShanghai.sol --bin --abi --optimize --overwrite -o . +// then create web3j wrappers with: +// web3j generate solidity -b ./SimpleStorageShanghai.bin -a ./SimpleStorageShanghai.abi -o ../../../../../ -p org.hyperledger.besu.tests.web3j.generated +contract SimpleStorageShanghai { + uint data; + + function set(uint value) public { + data = value; + } + + function get() public view returns (uint) { + return data; + } +} diff --git a/acceptance-tests/tests/simple-permissioning-smart-contract/README.md b/acceptance-tests/tests/simple-permissioning-smart-contract/README.md index 40dc739ed5d..bad77088805 100644 --- a/acceptance-tests/tests/simple-permissioning-smart-contract/README.md +++ b/acceptance-tests/tests/simple-permissioning-smart-contract/README.md @@ -1,5 +1,5 @@ ## Pre-requisites -* [Truffle](https://truffleframework.com/) installed +* [Truffle](https://archive.trufflesuite.com/truffle/) installed ``` npm install -g truffle ``` @@ -18,7 +18,7 @@ cd acceptance-tests/simple-permissioning-smart-contract * Also this truffle.js uses address and private key generated by Ganache with default mnemonic "candy maple cake sugar pudding cream honey rich smooth crumble sweet treat" * To run the Truffle example with Besu, you need Besu running - * [check out and build Besu](../../README.md) + * [check out and build Besu](../../../README.md) * run Besu (either in IDE or via command line), with mining and RPC enabled. * Run Truffle migrate against Ganache diff --git a/acceptance-tests/tests/simple-permissioning-smart-contract/contracts/Migrations.sol b/acceptance-tests/tests/simple-permissioning-smart-contract/contracts/Migrations.sol index f47e9cea31c..e0e4dd0959d 100644 --- a/acceptance-tests/tests/simple-permissioning-smart-contract/contracts/Migrations.sol +++ b/acceptance-tests/tests/simple-permissioning-smart-contract/contracts/Migrations.sol @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - pragma solidity ^0.5.0; contract Migrations { diff --git a/acceptance-tests/tests/simple-permissioning-smart-contract/contracts/SimpleAccountPermissioning.sol b/acceptance-tests/tests/simple-permissioning-smart-contract/contracts/SimpleAccountPermissioning.sol index 04542a50551..72215b4a9cd 100644 --- a/acceptance-tests/tests/simple-permissioning-smart-contract/contracts/SimpleAccountPermissioning.sol +++ b/acceptance-tests/tests/simple-permissioning-smart-contract/contracts/SimpleAccountPermissioning.sol @@ -1,3 +1,17 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ pragma solidity >=0.4.0 <0.6.0; // THIS CONTRACT IS FOR TESTING PURPOSES ONLY // DO NOT USE THIS CONTRACT IN PRODUCTION APPLICATIONS @@ -43,4 +57,4 @@ contract SimpleAccountPermissioning { function getSize() public view returns(uint256) { return size; } -} \ No newline at end of file +} diff --git a/acceptance-tests/tests/simple-permissioning-smart-contract/contracts/SimpleNodePermissioning.sol b/acceptance-tests/tests/simple-permissioning-smart-contract/contracts/SimpleNodePermissioning.sol index 334cf959c50..57c72370b82 100644 --- a/acceptance-tests/tests/simple-permissioning-smart-contract/contracts/SimpleNodePermissioning.sol +++ b/acceptance-tests/tests/simple-permissioning-smart-contract/contracts/SimpleNodePermissioning.sol @@ -1,3 +1,17 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ pragma solidity >=0.4.0 <0.6.0; // THIS CONTRACT IS FOR TESTING PURPOSES ONLY // DO NOT USE THIS CONTRACT IN PRODUCTION APPLICATIONS diff --git a/acceptance-tests/tests/simple-permissioning-smart-contract/contracts/SimpleNodePermissioningV2.sol b/acceptance-tests/tests/simple-permissioning-smart-contract/contracts/SimpleNodePermissioningV2.sol index 9458f0619bf..a3b7ffa47b5 100644 --- a/acceptance-tests/tests/simple-permissioning-smart-contract/contracts/SimpleNodePermissioningV2.sol +++ b/acceptance-tests/tests/simple-permissioning-smart-contract/contracts/SimpleNodePermissioningV2.sol @@ -1,3 +1,17 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ pragma solidity >=0.4.0 <0.6.0; // THIS CONTRACT IS FOR TESTING PURPOSES ONLY // DO NOT USE THIS CONTRACT IN PRODUCTION APPLICATIONS diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/AbstractPreexistingNodeTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/AbstractPreexistingNodeTest.java index 5c4b58ec019..fe148921002 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/AbstractPreexistingNodeTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/AbstractPreexistingNodeTest.java @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.tests.acceptance; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; @@ -43,15 +41,8 @@ import org.apache.commons.compress.utils.IOUtils; public class AbstractPreexistingNodeTest extends AcceptanceTestBase { - protected final String testName; - protected final String dataPath; protected Path hostDataPath; - public AbstractPreexistingNodeTest(final String testName, final String dataPath) { - this.testName = testName; - this.dataPath = dataPath; - } - protected static void extract(final Path path, final String destDirectory) throws IOException { try (final TarArchiveInputStream fin = new TarArchiveInputStream( diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/LoggingTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/LoggingTest.java index e0276842f06..8a231a775db 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/LoggingTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/LoggingTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -21,7 +21,7 @@ import java.io.IOException; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class LoggingTest extends AcceptanceTestBase { @@ -33,6 +33,6 @@ public void testDefaultLoggingIsAtLeastInfo() throws IOException { node.verify(net.awaitPeerCount(0)); - assertThat(cluster.getConsoleContents()).contains("| INFO |"); + assertThat(cluster.getConsoleContents()).contains("INFO"); } } diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/backup/BackupRoundTripAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/backup/BackupRoundTripAcceptanceTest.java index 5136cc64ab3..56560247435 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/backup/BackupRoundTripAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/backup/BackupRoundTripAcceptanceTest.java @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.tests.acceptance.backup; import static java.util.Collections.singletonList; @@ -29,7 +27,6 @@ import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.BesuNodeConfigurationBuilder; -import java.io.IOException; import java.math.BigInteger; import java.net.URL; import java.nio.file.Files; @@ -37,64 +34,42 @@ import java.nio.file.Paths; import java.util.List; import java.util.function.UnaryOperator; +import java.util.stream.Stream; import javax.annotation.Nonnull; import com.fasterxml.jackson.databind.node.ObjectNode; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; -@RunWith(Parameterized.class) public class BackupRoundTripAcceptanceTest extends AbstractPreexistingNodeTest { - private final Path backupPath; - private final Path restorePath; - private final Path rebackupPath; - - @SuppressWarnings({"unused", "FieldCanBeLocal"}) - private final List testAccounts; - - @SuppressWarnings({"unused", "FieldCanBeLocal"}) - private final long expectedChainHeight; + private Path backupPath; + private Path restorePath; + private Path rebackupPath; + + public static Stream getParameters() { + // First 10 blocks of ropsten + return Stream.of( + Arguments.of( + "After versioning was enabled and using multiple RocksDB columns", + "version1", + 0xA, + singletonList( + new AccountData( + "0xd1aeb42885a43b72b518182ef893125814811048", + BigInteger.valueOf(0xA), + Wei.fromHexString("0x2B5E3AF16B1880000"))))); + } - public BackupRoundTripAcceptanceTest( - final String testName, - final String dataPath, - final long expectedChainHeight, - final List testAccounts) - throws IOException { - super(testName, dataPath); - this.expectedChainHeight = expectedChainHeight; - this.testAccounts = testAccounts; + public void setUp(final String testName, final String dataPath) throws Exception { backupPath = Files.createTempDirectory("backup"); backupPath.toFile().deleteOnExit(); restorePath = Files.createTempDirectory("restore"); restorePath.toFile().deleteOnExit(); rebackupPath = Files.createTempDirectory("rebackup"); rebackupPath.toFile().deleteOnExit(); - } - - @Parameters(name = "{0}") - public static Object[][] getParameters() { - return new Object[][] { - // First 10 blocks of ropsten - new Object[] { - "After versioning was enabled and using multiple RocksDB columns", - "version1", - 0xA, - singletonList( - new AccountData( - "0xd1aeb42885a43b72b518182ef893125814811048", - BigInteger.valueOf(0xA), - Wei.fromHexString("0x2B5E3AF16B1880000"))) - } - }; - } - @Before - public void setUp() throws Exception { final URL rootURL = DatabaseMigrationAcceptanceTest.class.getResource(dataPath); hostDataPath = copyDataDir(rootURL); final Path databaseArchive = @@ -105,8 +80,16 @@ public void setUp() throws Exception { extract(databaseArchive, hostDataPath.toAbsolutePath().toString()); } - @Test - public void backupRoundtripAndBack() throws IOException { + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("getParameters") + public void backupRoundtripAndBack( + final String testName, + final String dataPath, + final long expectedChainHeight, + final List testAccounts) + throws Exception { + + setUp(testName, dataPath); // backup from existing files final BesuNode backupNode = diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftAcceptanceTestParameterization.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftAcceptanceTestParameterization.java index b7464190abe..2351f740bfb 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftAcceptanceTestParameterization.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftAcceptanceTestParameterization.java @@ -14,35 +14,41 @@ */ package org.hyperledger.besu.tests.acceptance.bft; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.BesuNodeFactory; -import java.util.ArrayList; -import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.params.provider.Arguments; public class BftAcceptanceTestParameterization { - public static List getFactories() { - final List ret = new ArrayList<>(); - ret.addAll( - List.of( - new Object[] { - "ibft2", - new BftAcceptanceTestParameterization( - BesuNodeFactory::createIbft2Node, BesuNodeFactory::createIbft2NodeWithValidators) - }, - new Object[] { - "qbft", - new BftAcceptanceTestParameterization( - BesuNodeFactory::createQbftNode, BesuNodeFactory::createQbftNodeWithValidators) - })); - return ret; + public static Stream getFactories() { + return Stream.of( + Arguments.of( + "ibft2", + new BftAcceptanceTestParameterization( + BesuNodeFactory::createIbft2Node, BesuNodeFactory::createIbft2NodeWithValidators)), + Arguments.of( + "qbft", + new BftAcceptanceTestParameterization( + BesuNodeFactory::createQbftNode, BesuNodeFactory::createQbftNodeWithValidators))); } @FunctionalInterface public interface NodeCreator { - BesuNode create(BesuNodeFactory factory, String name) throws Exception; + BesuNode create( + BesuNodeFactory factory, String name, boolean fixedPort, DataStorageFormat storageFormat) + throws Exception; + } + + @FunctionalInterface + public interface FixedPortNodeCreator { + + BesuNode createFixedPort(BesuNodeFactory factory, String name, boolean fixedPort) + throws Exception; } @FunctionalInterface @@ -61,7 +67,15 @@ public BftAcceptanceTestParameterization( } public BesuNode createNode(BesuNodeFactory factory, String name) throws Exception { - return creatorFn.create(factory, name); + return creatorFn.create(factory, name, false, DataStorageFormat.FOREST); + } + + public BesuNode createBonsaiNodeFixedPort(BesuNodeFactory factory, String name) throws Exception { + return creatorFn.create(factory, name, true, DataStorageFormat.BONSAI); + } + + public BesuNode createForestNodeFixedPort(BesuNodeFactory factory, String name) throws Exception { + return creatorFn.create(factory, name, true, DataStorageFormat.FOREST); } public BesuNode createNodeWithValidators( diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftBlockRewardPaymentAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftBlockRewardPaymentAcceptanceTest.java index 4225035b7ff..86978ae0b0e 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftBlockRewardPaymentAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftBlockRewardPaymentAcceptanceTest.java @@ -32,19 +32,18 @@ import java.util.Optional; import java.util.TreeMap; -import org.junit.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class BftBlockRewardPaymentAcceptanceTest extends ParameterizedBftTestBase { private static final Amount BLOCK_REWARD = Amount.wei(new BigInteger("5000000000000000000", 10)); - public BftBlockRewardPaymentAcceptanceTest( - final String testName, final BftAcceptanceTestParameterization nodeFactory) { - super(testName, nodeFactory); - } - - @Test - public void validatorsArePaidBlockReward() throws Exception { + @ParameterizedTest(name = "{0} bft node factory type") + @MethodSource("factoryFunctions") + public void validatorsArePaidBlockReward( + final String testName, final BftAcceptanceTestParameterization nodeFactory) throws Exception { + setUp(testName, nodeFactory); final String[] validators = {"validator"}; final BesuNode validator = nodeFactory.createNodeWithValidators(besu, "validator", validators); final BesuNode nonValidator = @@ -61,8 +60,11 @@ public void validatorsArePaidBlockReward() throws Exception { Amount.ether(blockRewardEth * blockToCheck), BigInteger.valueOf(blockToCheck))); } - @Test - public void payBlockRewardToConfiguredNode() throws Exception { + @ParameterizedTest(name = "{0} bft node factory type") + @MethodSource("factoryFunctions") + public void payBlockRewardToConfiguredNode( + final String testName, final BftAcceptanceTestParameterization nodeFactory) throws Exception { + setUp(testName, nodeFactory); final String[] validators = {"validator1"}; final BesuNode validator1 = nodeFactory.createNodeWithValidators(besu, "validator1", validators); @@ -90,9 +92,11 @@ public void payBlockRewardToConfiguredNode() throws Exception { Amount.ether(blockRewardEth * blockToCheck), BigInteger.valueOf(blockToCheck))); } - @Test - public void payBlockRewardAccordingToTransitions_defaultInitialMiningBeneficiary() - throws Exception { + @ParameterizedTest(name = "{0} bft node factory type") + @MethodSource("factoryFunctions") + public void payBlockRewardAccordingToTransitions_defaultInitialMiningBeneficiary( + final String testName, final BftAcceptanceTestParameterization nodeFactory) throws Exception { + setUp(testName, nodeFactory); final List
addresses = generateAddresses(2); final Map> transitions = Map.of( @@ -103,9 +107,11 @@ public void payBlockRewardAccordingToTransitions_defaultInitialMiningBeneficiary testMiningBeneficiaryTransitions(Optional.empty(), transitions); } - @Test - public void payBlockRewardAccordingToTransitions_customInitialMiningBeneficiary() - throws Exception { + @ParameterizedTest(name = "{0} bft node factory type") + @MethodSource("factoryFunctions") + public void payBlockRewardAccordingToTransitions_customInitialMiningBeneficiary( + final String testName, final BftAcceptanceTestParameterization nodeFactory) throws Exception { + setUp(testName, nodeFactory); final List
addresses = generateAddresses(4); final Map> transitions = Map.of( diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftDiscardRpcAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftDiscardRpcAcceptanceTest.java index 40d0e881459..0e7484529fe 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftDiscardRpcAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftDiscardRpcAcceptanceTest.java @@ -16,17 +16,16 @@ import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; -import org.junit.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class BftDiscardRpcAcceptanceTest extends ParameterizedBftTestBase { - public BftDiscardRpcAcceptanceTest( - final String testName, final BftAcceptanceTestParameterization nodeFactory) { - super(testName, nodeFactory); - } - - @Test - public void shouldDiscardVotes() throws Exception { + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("factoryFunctions") + public void shouldDiscardVotes( + final String testName, final BftAcceptanceTestParameterization nodeFactory) throws Exception { + setUp(testName, nodeFactory); final String[] validators = {"validator1", "validator3"}; final BesuNode validator1 = nodeFactory.createNodeWithValidators(besu, "validator1", validators); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftMiningAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftMiningAcceptanceTest.java index a109a3b8567..1e741a5cc68 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftMiningAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftMiningAcceptanceTest.java @@ -28,17 +28,16 @@ import java.util.Optional; import com.fasterxml.jackson.databind.node.ObjectNode; -import org.junit.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class BftMiningAcceptanceTest extends ParameterizedBftTestBase { - public BftMiningAcceptanceTest( - final String testName, final BftAcceptanceTestParameterization nodeFactory) { - super(testName, nodeFactory); - } - - @Test - public void shouldMineOnSingleNodeWithPaidGas_Berlin() throws Exception { + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("factoryFunctions") + public void shouldMineOnSingleNodeWithPaidGas_Berlin( + final String testName, final BftAcceptanceTestParameterization nodeFactory) throws Exception { + setUp(testName, nodeFactory); final BesuNode minerNode = nodeFactory.createNode(besu, "miner1"); cluster.start(minerNode); @@ -57,8 +56,11 @@ public void shouldMineOnSingleNodeWithPaidGas_Berlin() throws Exception { cluster.verify(receiver.balanceEquals(3)); } - @Test - public void shouldMineOnSingleNodeWithFreeGas_Berlin() throws Exception { + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("factoryFunctions") + public void shouldMineOnSingleNodeWithFreeGas_Berlin( + final String testName, final BftAcceptanceTestParameterization nodeFactory) throws Exception { + setUp(testName, nodeFactory); final BesuNode minerNode = nodeFactory.createNode(besu, "miner1"); final MiningParameters zeroGasMiningParams = ImmutableMiningParameters.builder() @@ -90,8 +92,11 @@ public void shouldMineOnSingleNodeWithFreeGas_Berlin() throws Exception { cluster.verify(receiver.balanceEquals(3)); } - @Test - public void shouldMineOnSingleNodeWithPaidGas_London() throws Exception { + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("factoryFunctions") + public void shouldMineOnSingleNodeWithPaidGas_London( + final String testName, final BftAcceptanceTestParameterization nodeFactory) throws Exception { + setUp(testName, nodeFactory); final BesuNode minerNode = nodeFactory.createNode(besu, "miner1"); updateGenesisConfigToLondon(minerNode, false); @@ -115,8 +120,11 @@ public void shouldMineOnSingleNodeWithPaidGas_London() throws Exception { cluster.verify(receiver.balanceEquals(3)); } - @Test - public void shouldMineOnSingleNodeWithFreeGas_London() throws Exception { + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("factoryFunctions") + public void shouldMineOnSingleNodeWithFreeGas_London( + final String testName, final BftAcceptanceTestParameterization nodeFactory) throws Exception { + setUp(testName, nodeFactory); final BesuNode minerNode = nodeFactory.createNode(besu, "miner1"); updateGenesisConfigToLondon(minerNode, true); @@ -142,8 +150,41 @@ public void shouldMineOnSingleNodeWithFreeGas_London() throws Exception { cluster.verify(receiver.balanceEquals(3)); } - @Test - public void shouldMineOnMultipleNodes() throws Exception { + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("factoryFunctions") + public void shouldMineOnSingleNodeWithFreeGas_Shanghai( + final String testName, final BftAcceptanceTestParameterization nodeFactory) throws Exception { + setUp(testName, nodeFactory); + final BesuNode minerNode = nodeFactory.createNode(besu, "miner1"); + updateGenesisConfigToShanghai(minerNode, true); + + cluster.start(minerNode); + + cluster.verify(blockchain.reachesHeight(minerNode, 1)); + + final Account sender = accounts.createAccount("account1"); + final Account receiver = accounts.createAccount("account2"); + + minerNode.execute(accountTransactions.createTransfer(sender, 50, Amount.ZERO)); + cluster.verify(sender.balanceEquals(50)); + + minerNode.execute(accountTransactions.create1559Transfer(sender, 50, 4, Amount.ZERO)); + cluster.verify(sender.balanceEquals(100)); + + minerNode.execute( + accountTransactions.createIncrementalTransfers(sender, receiver, 1, Amount.ZERO)); + cluster.verify(receiver.balanceEquals(1)); + + minerNode.execute( + accountTransactions.create1559IncrementalTransfers(sender, receiver, 2, 4, Amount.ZERO)); + cluster.verify(receiver.balanceEquals(3)); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("factoryFunctions") + public void shouldMineOnMultipleNodes( + final String testName, final BftAcceptanceTestParameterization nodeFactory) throws Exception { + setUp(testName, nodeFactory); final BesuNode minerNode1 = nodeFactory.createNode(besu, "miner1"); final BesuNode minerNode2 = nodeFactory.createNode(besu, "miner2"); final BesuNode minerNode3 = nodeFactory.createNode(besu, "miner3"); @@ -168,8 +209,11 @@ public void shouldMineOnMultipleNodes() throws Exception { cluster.verify(receiver.balanceEquals(6)); } - @Test - public void shouldMineOnMultipleNodesEvenWhenClusterContainsNonValidator() throws Exception { + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("factoryFunctions") + public void shouldMineOnMultipleNodesEvenWhenClusterContainsNonValidator( + final String testName, final BftAcceptanceTestParameterization nodeFactory) throws Exception { + setUp(testName, nodeFactory); final String[] validators = {"validator1", "validator2", "validator3"}; final BesuNode validator1 = nodeFactory.createNodeWithValidators(besu, "validator1", validators); @@ -196,9 +240,11 @@ public void shouldMineOnMultipleNodesEvenWhenClusterContainsNonValidator() throw cluster.verify(receiver.balanceEquals(3)); } - @Test - public void shouldStillMineWhenANonProposerNodeFailsAndHasSufficientValidators() - throws Exception { + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("factoryFunctions") + public void shouldStillMineWhenANonProposerNodeFailsAndHasSufficientValidators( + final String testName, final BftAcceptanceTestParameterization nodeFactory) throws Exception { + setUp(testName, nodeFactory); final BesuNode minerNode1 = nodeFactory.createNode(besu, "miner1"); final BesuNode minerNode2 = nodeFactory.createNode(besu, "miner2"); final BesuNode minerNode3 = nodeFactory.createNode(besu, "miner3"); @@ -229,4 +275,16 @@ private static void updateGenesisConfigToLondon( config.put("zeroBaseFee", zeroBaseFeeEnabled); minerNode.setGenesisConfig(genesisConfigNode.toString()); } + + private static void updateGenesisConfigToShanghai( + final BesuNode minerNode, final boolean zeroBaseFeeEnabled) { + final Optional genesisConfig = + minerNode.getGenesisConfigProvider().create(List.of(minerNode)); + final ObjectNode genesisConfigNode = JsonUtil.objectNodeFromString(genesisConfig.orElseThrow()); + final ObjectNode config = (ObjectNode) genesisConfigNode.get("config"); + config.remove("berlinBlock"); + config.put("shanghaiTime", 100); + config.put("zeroBaseFee", zeroBaseFeeEnabled); + minerNode.setGenesisConfig(genesisConfigNode.toString()); + } } diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftProposalRpcAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftProposalRpcAcceptanceTest.java index 7fccf33fa27..eecd383003f 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftProposalRpcAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftProposalRpcAcceptanceTest.java @@ -16,17 +16,16 @@ import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; -import org.junit.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; public class BftProposalRpcAcceptanceTest extends ParameterizedBftTestBase { - public BftProposalRpcAcceptanceTest( - final String testName, final BftAcceptanceTestParameterization nodeFactory) { - super(testName, nodeFactory); - } - - @Test - public void shouldReturnProposals() throws Exception { + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("factoryFunctions") + public void shouldReturnProposals( + final String testName, final BftAcceptanceTestParameterization nodeFactory) throws Exception { + setUp(testName, nodeFactory); final String[] validators = {"validator1", "validator2", "validator3"}; final BesuNode validator1 = nodeFactory.createNodeWithValidators(besu, "validator1", validators); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftProposeRpcAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftProposeRpcAcceptanceTest.java index 7db3d6a5b9f..191884f41a2 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftProposeRpcAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftProposeRpcAcceptanceTest.java @@ -17,19 +17,18 @@ import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; -import org.junit.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; // These tests prove the ibft_proposeValidatorVote and ibft_getValidatorsByBlockNumber (implicitly) // JSON RPC calls. public class BftProposeRpcAcceptanceTest extends ParameterizedBftTestBase { - public BftProposeRpcAcceptanceTest( - final String testName, final BftAcceptanceTestParameterization nodeFactory) { - super(testName, nodeFactory); - } - - @Test - public void validatorsCanBeAddedAndThenRemoved() throws Exception { + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("factoryFunctions") + public void validatorsCanBeAddedAndThenRemoved( + final String testName, final BftAcceptanceTestParameterization nodeFactory) throws Exception { + setUp(testName, nodeFactory); final String[] validators = {"validator1", "validator2", "validator3"}; final BesuNode validator1 = nodeFactory.createNodeWithValidators(besu, "validator1", validators); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftZeroValidators.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftZeroValidators.java deleted file mode 100644 index 898ad47b3f8..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftZeroValidators.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.bft; - -import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; - -import org.junit.Test; - -public class BftZeroValidators extends ParameterizedBftTestBase { - - public BftZeroValidators( - final String testName, final BftAcceptanceTestParameterization nodeFactory) { - super(testName, nodeFactory); - } - - @Test - public void zeroValidatorsFormValidCluster() throws Exception { - final String[] validators = {}; - final BesuNode node1 = nodeFactory.createNodeWithValidators(besu, "node1", validators); - final BesuNode node2 = nodeFactory.createNodeWithValidators(besu, "node2", validators); - - cluster.start(node1, node2); - - cluster.verify(net.awaitPeerCount(1)); - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftZeroValidatorsAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftZeroValidatorsAcceptanceTest.java new file mode 100644 index 00000000000..2b107937b88 --- /dev/null +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/BftZeroValidatorsAcceptanceTest.java @@ -0,0 +1,37 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.bft; + +import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +public class BftZeroValidatorsAcceptanceTest extends ParameterizedBftTestBase { + + @ParameterizedTest(name = "{0} bft node factory type") + @MethodSource("factoryFunctions") + public void zeroValidatorsFormValidCluster( + final String testName, final BftAcceptanceTestParameterization nodeFactory) throws Exception { + setUp(testName, nodeFactory); + final String[] validators = {}; + final BesuNode node1 = nodeFactory.createNodeWithValidators(besu, "node1", validators); + final BesuNode node2 = nodeFactory.createNodeWithValidators(besu, "node2", validators); + + cluster.start(node1, node2); + + cluster.verify(net.awaitPeerCount(1)); + } +} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/ParameterizedBftTestBase.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/ParameterizedBftTestBase.java index e08d4a5c206..b3d1e321aea 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/ParameterizedBftTestBase.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/ParameterizedBftTestBase.java @@ -14,29 +14,23 @@ */ package org.hyperledger.besu.tests.acceptance.bft; -import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; +import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBaseJunit5; -import java.util.Collection; +import java.util.stream.Stream; -import org.junit.Ignore; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.params.provider.Arguments; -@RunWith(Parameterized.class) -@Ignore("This is not a test class, it offers BFT parameterization only.") -public abstract class ParameterizedBftTestBase extends AcceptanceTestBase { +@Disabled("This is not a test class, it offers BFT parameterization only.") +public abstract class ParameterizedBftTestBase extends AcceptanceTestBaseJunit5 { + protected String bftType; + protected BftAcceptanceTestParameterization nodeFactory; - protected final String bftType; - protected final BftAcceptanceTestParameterization nodeFactory; - - @Parameters(name = "{0}") - public static Collection factoryFunctions() { + public static Stream factoryFunctions() { return BftAcceptanceTestParameterization.getFactories(); } - protected ParameterizedBftTestBase( - final String bftType, final BftAcceptanceTestParameterization input) { + protected void setUp(final String bftType, final BftAcceptanceTestParameterization input) { this.bftType = bftType; this.nodeFactory = input; } diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/pki/ParameterizedPkiQbftTestBase.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/pki/ParameterizedPkiQbftTestBase.java deleted file mode 100644 index 29c00b65d96..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/pki/ParameterizedPkiQbftTestBase.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2020 ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.bft.pki; - -import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; - -import java.util.Collection; - -import org.junit.Ignore; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - -@RunWith(Parameterized.class) -@Ignore("This is not a test class, it offers PKI QBFT parameterization only.") -public abstract class ParameterizedPkiQbftTestBase extends AcceptanceTestBase { - - protected final PkiQbftAcceptanceTestParameterization nodeFactory; - - @Parameters(name = "{0}") - public static Collection factoryFunctions() { - return PkiQbftAcceptanceTestParameterization.getFactories(); - } - - protected ParameterizedPkiQbftTestBase( - final String testName, final PkiQbftAcceptanceTestParameterization input) { - this.nodeFactory = input; - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/pki/PkiQbftAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/pki/PkiQbftAcceptanceTest.java deleted file mode 100644 index 8fecb5f6779..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/pki/PkiQbftAcceptanceTest.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See - * the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.bft.pki; - -import org.hyperledger.besu.tests.acceptance.dsl.account.Account; -import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; - -import org.junit.Test; - -public class PkiQbftAcceptanceTest extends ParameterizedPkiQbftTestBase { - - public PkiQbftAcceptanceTest( - final String testName, final PkiQbftAcceptanceTestParameterization input) { - super(testName, input); - } - - @Test - public void shouldMineOnSingleNode() throws Exception { - final BesuNode minerNode = nodeFactory.createNode(besu, "miner1"); - cluster.start(minerNode); - - cluster.verify(blockchain.reachesHeight(minerNode, 1)); - - final Account sender = accounts.createAccount("account1"); - final Account receiver = accounts.createAccount("account2"); - - minerNode.execute(accountTransactions.createTransfer(sender, 50)); - cluster.verify(sender.balanceEquals(50)); - - minerNode.execute(accountTransactions.createIncrementalTransfers(sender, receiver, 1)); - cluster.verify(receiver.balanceEquals(1)); - - minerNode.execute(accountTransactions.createIncrementalTransfers(sender, receiver, 2)); - cluster.verify(receiver.balanceEquals(3)); - } - - @Test - public void shouldMineOnMultipleNodes() throws Exception { - final BesuNode minerNode1 = nodeFactory.createNode(besu, "miner1"); - final BesuNode minerNode2 = nodeFactory.createNode(besu, "miner2"); - final BesuNode minerNode3 = nodeFactory.createNode(besu, "miner3"); - final BesuNode minerNode4 = nodeFactory.createNode(besu, "miner4"); - cluster.start(minerNode1, minerNode2, minerNode3, minerNode4); - - cluster.verify(blockchain.reachesHeight(minerNode1, 1, 85)); - - final Account sender = accounts.createAccount("account1"); - final Account receiver = accounts.createAccount("account2"); - - minerNode1.execute(accountTransactions.createTransfer(sender, 50)); - cluster.verify(sender.balanceEquals(50)); - - minerNode2.execute(accountTransactions.createIncrementalTransfers(sender, receiver, 1)); - cluster.verify(receiver.balanceEquals(1)); - - minerNode3.execute(accountTransactions.createIncrementalTransfers(sender, receiver, 2)); - cluster.verify(receiver.balanceEquals(3)); - - minerNode4.execute(accountTransactions.createIncrementalTransfers(sender, receiver, 3)); - cluster.verify(receiver.balanceEquals(6)); - } - - @Test - public void shouldMineWithIgnoringANodeInCRL() throws Exception { - final BesuNode minerNode1 = nodeFactory.createNode(besu, "miner1"); - final BesuNode minerNode2 = nodeFactory.createNode(besu, "miner2"); - final BesuNode minerNode3 = nodeFactory.createNode(besu, "miner3"); - final BesuNode minerNode4 = nodeFactory.createNode(besu, "miner4"); - final BesuNode minerNode5 = nodeFactory.createNode(besu, "miner5"); - final BesuNode minerNode6 = nodeFactory.createNode(besu, "miner6"); - try { - cluster.start(minerNode1, minerNode2, minerNode3, minerNode4); - - cluster.startNode(minerNode5); - cluster.startNode(minerNode6); - - cluster.verify(blockchain.reachesHeight(minerNode1, 1, 85)); - - final Account sender = accounts.createAccount("account1"); - final Account receiver = accounts.createAccount("account2"); - - minerNode1.execute(accountTransactions.createTransfer(sender, 50)); - cluster.verify(sender.balanceEquals(50)); - - minerNode2.execute(accountTransactions.createIncrementalTransfers(sender, receiver, 1)); - cluster.verify(receiver.balanceEquals(1)); - - minerNode3.execute(accountTransactions.createIncrementalTransfers(sender, receiver, 2)); - cluster.verify(receiver.balanceEquals(3)); - - minerNode4.execute(accountTransactions.createIncrementalTransfers(sender, receiver, 3)); - cluster.verify(receiver.balanceEquals(6)); - - if (minerNode1.getTLSConfiguration().isEmpty()) { - minerNode1.verify(net.awaitPeerCount(5)); - minerNode5.verify(net.awaitPeerCount(5)); - minerNode6.verify(net.awaitPeerCount(5)); - } else { - minerNode1.verify(net.awaitPeerCount(3)); - minerNode5.verify(net.awaitPeerCount(0)); - minerNode6.verify(net.awaitPeerCount(0)); - } - } finally { - cluster.stopNode(minerNode5); - cluster.stopNode(minerNode6); - minerNode5.close(); - minerNode6.close(); - } - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/pki/PkiQbftAcceptanceTestParameterization.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/pki/PkiQbftAcceptanceTestParameterization.java deleted file mode 100644 index 10d4866fef7..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/pki/PkiQbftAcceptanceTestParameterization.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2020 ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See - * the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.bft.pki; - -import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; -import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.BesuNodeFactory; - -import java.util.ArrayList; -import java.util.List; - -public class PkiQbftAcceptanceTestParameterization { - - public static List getFactories() { - final List ret = new ArrayList<>(); - - /* - BLOCK CREATION - */ - - ret.add( - new Object[] { - "qbft-pki-jks", - new PkiQbftAcceptanceTestParameterization( - BesuNodeFactory::createPkiQbftJKSNode, - BesuNodeFactory::createPkiQbftJKSNodeWithValidators) - }); - - ret.add( - new Object[] { - "qbft-pki-pkcs12", - new PkiQbftAcceptanceTestParameterization( - BesuNodeFactory::createPkiQbftPKCS12Node, - BesuNodeFactory::createPkiQbftPKCS12NodeWithValidators) - }); - - if (Boolean.getBoolean("acctests.runBesuAsProcess")) { - ret.add( - new Object[] { - "qbft-pki-pkcs11", - new PkiQbftAcceptanceTestParameterization( - BesuNodeFactory::createPkiQbftPKCS11Node, - BesuNodeFactory::createPkiQbftPKCS11NodeWithValidators) - }); - } - - /* - TLS - */ - - ret.add( - new Object[] { - "qbft-tls-jks", - new PkiQbftAcceptanceTestParameterization( - BesuNodeFactory::createQbftNodeWithTLSJKS, - BesuNodeFactory::createQbftTLSJKSNodeWithValidators) - }); - - ret.add( - new Object[] { - "qbft-tls-pkcs12", - new PkiQbftAcceptanceTestParameterization( - BesuNodeFactory::createQbftNodeWithTLSPKCS12, - BesuNodeFactory::createQbftTLSPKCS12NodeWithValidators) - }); - - if (Boolean.getBoolean("acctests.runBesuAsProcess")) { - ret.add( - new Object[] { - "qbft-tls-pkcs11", - new PkiQbftAcceptanceTestParameterization( - BesuNodeFactory::createQbftNodeWithTLSPKCS11, - BesuNodeFactory::createQbftTLSPKCS11NodeWithValidators) - }); - } - - return ret; - } - - @FunctionalInterface - public interface NodeCreator { - - BesuNode create(BesuNodeFactory factory, String name) throws Exception; - } - - @FunctionalInterface - public interface NodeWithValidatorsCreator { - - BesuNode create(BesuNodeFactory factory, String name, String[] validators) throws Exception; - } - - private final NodeCreator creatorFn; - private final NodeWithValidatorsCreator createorWithValidatorFn; - - public PkiQbftAcceptanceTestParameterization( - final NodeCreator creatorFn, final NodeWithValidatorsCreator createorWithValidatorFn) { - this.creatorFn = creatorFn; - this.createorWithValidatorFn = createorWithValidatorFn; - } - - public BesuNode createNode(BesuNodeFactory factory, String name) throws Exception { - return creatorFn.create(factory, name); - } - - public BesuNode createNodeWithValidators( - BesuNodeFactory factory, String name, String[] validators) throws Exception { - return createorWithValidatorFn.create(factory, name, validators); - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/qbft/QbftContractAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/qbft/QbftContractAcceptanceTest.java index e3b2878b052..b62eb96e195 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/qbft/QbftContractAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bft/qbft/QbftContractAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,13 +14,13 @@ */ package org.hyperledger.besu.tests.acceptance.bft.qbft; -import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; +import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBaseJunit5; import org.hyperledger.besu.tests.acceptance.dsl.account.Account; import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; -import org.junit.Test; +import org.junit.jupiter.api.Test; -public class QbftContractAcceptanceTest extends AcceptanceTestBase { +public class QbftContractAcceptanceTest extends AcceptanceTestBaseJunit5 { @Test public void shouldMineOnMultipleNodesEvenWhenClusterContainsNonValidator() throws Exception { diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bftsoak/BftMiningSoakTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bftsoak/BftMiningSoakTest.java new file mode 100644 index 00000000000..878e503ba39 --- /dev/null +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bftsoak/BftMiningSoakTest.java @@ -0,0 +1,352 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.bftsoak; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.config.JsonUtil; +import org.hyperledger.besu.tests.acceptance.bft.BftAcceptanceTestParameterization; +import org.hyperledger.besu.tests.acceptance.bft.ParameterizedBftTestBase; +import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; +import org.hyperledger.besu.tests.web3j.generated.SimpleStorage; +import org.hyperledger.besu.tests.web3j.generated.SimpleStorageShanghai; + +import java.math.BigInteger; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +public class BftMiningSoakTest extends ParameterizedBftTestBase { + + private final int NUM_STEPS = 5; + + private final int MIN_TEST_TIME_MINS = 60; + + private static final long ONE_MINUTE = Duration.of(1, ChronoUnit.MINUTES).toMillis(); + + private static final long THREE_MINUTES = Duration.of(1, ChronoUnit.MINUTES).toMillis(); + + private static final long TEN_SECONDS = Duration.of(10, ChronoUnit.SECONDS).toMillis(); + + static int getTestDurationMins() { + // Use a default soak time of 60 mins + return Integer.getInteger("acctests.soakTimeMins", 60); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("factoryFunctions") + public void shouldBeStableDuringLongTest( + final String testName, final BftAcceptanceTestParameterization nodeFactory) throws Exception { + setUp(testName, nodeFactory); + + // There is a minimum amount of time the test can be run for, due to hard coded delays + // in between certain steps. There should be no upper-limit to how long the test is run for + assertThat(getTestDurationMins()).isGreaterThanOrEqualTo(MIN_TEST_TIME_MINS); + + // Create a mix of Bonsai and Forest DB nodes + final BesuNode minerNode1 = nodeFactory.createBonsaiNodeFixedPort(besu, "miner1"); + final BesuNode minerNode2 = nodeFactory.createForestNodeFixedPort(besu, "miner2"); + final BesuNode minerNode3 = nodeFactory.createBonsaiNodeFixedPort(besu, "miner3"); + final BesuNode minerNode4 = nodeFactory.createForestNodeFixedPort(besu, "miner4"); + + // Each step should be given a minimum of 3 minutes to complete successfully. If the time + // give to run the soak test results in a time-per-step lower than this then the time + // needs to be increased. + assertThat(getTestDurationMins() / NUM_STEPS).isGreaterThanOrEqualTo(3); + + cluster.start(minerNode1, minerNode2, minerNode3, minerNode4); + + cluster.verify(blockchain.reachesHeight(minerNode1, 1, 85)); + + // Setup + // Deploy a contract that we'll invoke periodically to ensure state + // is correct during the test, especially after stopping nodes and + // applying new forks. + SimpleStorage simpleStorageContract = + minerNode1.execute(contractTransactions.createSmartContract(SimpleStorage.class)); + + // Check the contract address is as expected for this sender & nonce + contractVerifier + .validTransactionReceipt("0x42699a7612a82f1d9c36148af9c77354759b210b") + .verify(simpleStorageContract); + + // Before upgrading to newer forks, try creating a shanghai-evm contract and check that + // the transaction fails + try { + minerNode1.execute(contractTransactions.createSmartContract(SimpleStorageShanghai.class)); + Assertions.fail("Shanghai transaction should not be executed on a pre-shanghai chain"); + } catch (RuntimeException e) { + assertThat(e.getMessage()) + .contains( + "Revert reason: 'Transaction processing could not be completed due to an exception'"); + } + + // Should initially be set to 0 + assertThat(simpleStorageContract.get().send()).isEqualTo(BigInteger.valueOf(0)); + + // Set to something new + simpleStorageContract.set(BigInteger.valueOf(101)).send(); + + // Check the state of the contract has updated correctly. We'll set & get this several times + // during the test + assertThat(simpleStorageContract.get().send()).isEqualTo(BigInteger.valueOf(101)); + + // Step 1 + // Run for the configured time period, periodically checking that + // the chain is progressing as expected + BigInteger chainHeight = minerNode1.execute(ethTransactions.blockNumber()); + assertThat(chainHeight.compareTo(BigInteger.ZERO)).isGreaterThanOrEqualTo(1); + BigInteger lastChainHeight = chainHeight; + + Instant startTime = Instant.now(); + Instant nextStepEndTime = startTime.plus(getTestDurationMins() / NUM_STEPS, ChronoUnit.MINUTES); + + while (System.currentTimeMillis() < nextStepEndTime.toEpochMilli()) { + Thread.sleep(ONE_MINUTE); + chainHeight = minerNode1.execute(ethTransactions.blockNumber()); + + // With 1-second block period chain height should have moved on by at least 50 blocks + assertThat(chainHeight.compareTo(lastChainHeight.add(BigInteger.valueOf(50)))) + .isGreaterThanOrEqualTo(1); + lastChainHeight = chainHeight; + } + Instant previousStepEndTime = Instant.now(); + + // Step 2 + // Stop one of the nodes, check that the chain continues mining + // blocks + stopNode(minerNode4); + + nextStepEndTime = + previousStepEndTime.plus(getTestDurationMins() / NUM_STEPS, ChronoUnit.MINUTES); + + while (System.currentTimeMillis() < nextStepEndTime.toEpochMilli()) { + Thread.sleep(ONE_MINUTE); + chainHeight = minerNode1.execute(ethTransactions.blockNumber()); + + // Chain height should have moved on by at least 5 blocks + assertThat(chainHeight.compareTo(lastChainHeight.add(BigInteger.valueOf(20)))) + .isGreaterThanOrEqualTo(1); + lastChainHeight = chainHeight; + } + previousStepEndTime = Instant.now(); + + // Step 3 + // Stop another one of the nodes, check that the chain now stops + // mining blocks + stopNode(minerNode3); + + chainHeight = minerNode1.execute(ethTransactions.blockNumber()); + lastChainHeight = chainHeight; + + // Leave the chain stalled for 3 minutes. Check no new blocks are mined. Then + // resume the other validators. + nextStepEndTime = previousStepEndTime.plus(3, ChronoUnit.MINUTES); + while (System.currentTimeMillis() < nextStepEndTime.toEpochMilli()) { + Thread.sleep(ONE_MINUTE); + chainHeight = minerNode1.execute(ethTransactions.blockNumber()); + + // Chain height should not have moved on + assertThat(chainHeight.equals(lastChainHeight)).isTrue(); + } + + // Step 4 + // Restart both of the stopped nodes. Check that the chain resumes + // mining blocks + startNode(minerNode3); + + startNode(minerNode4); + + previousStepEndTime = Instant.now(); + + // This step gives the stalled chain time to re-sync and agree on the next BFT round + chainHeight = minerNode1.execute(ethTransactions.blockNumber()); + nextStepEndTime = + previousStepEndTime.plus((getTestDurationMins() / NUM_STEPS), ChronoUnit.MINUTES); + lastChainHeight = chainHeight; + + while (System.currentTimeMillis() < nextStepEndTime.toEpochMilli()) { + Thread.sleep(ONE_MINUTE); + chainHeight = minerNode1.execute(ethTransactions.blockNumber()); + lastChainHeight = chainHeight; + } + previousStepEndTime = Instant.now(); + + // By this loop it should be producing blocks again + nextStepEndTime = + previousStepEndTime.plus(getTestDurationMins() / NUM_STEPS, ChronoUnit.MINUTES); + + while (System.currentTimeMillis() < nextStepEndTime.toEpochMilli()) { + Thread.sleep(ONE_MINUTE); + chainHeight = minerNode1.execute(ethTransactions.blockNumber()); + + // Chain height should have moved on by at least 1 block + assertThat(chainHeight.compareTo(lastChainHeight.add(BigInteger.valueOf(1)))) + .isGreaterThanOrEqualTo(1); + lastChainHeight = chainHeight; + } + + // Update our smart contract before upgrading from berlin to london + assertThat(simpleStorageContract.get().send()).isEqualTo(BigInteger.valueOf(101)); + simpleStorageContract.set(BigInteger.valueOf(201)).send(); + assertThat(simpleStorageContract.get().send()).isEqualTo(BigInteger.valueOf(201)); + + // Upgrade the chain from berlin to london in 120 blocks time + upgradeToLondon( + minerNode1, minerNode2, minerNode3, minerNode4, lastChainHeight.intValue() + 120); + + previousStepEndTime = Instant.now(); + + chainHeight = minerNode1.execute(ethTransactions.blockNumber()); + nextStepEndTime = + previousStepEndTime.plus(getTestDurationMins() / NUM_STEPS, ChronoUnit.MINUTES); + lastChainHeight = chainHeight; + + while (System.currentTimeMillis() < nextStepEndTime.toEpochMilli()) { + Thread.sleep(ONE_MINUTE); + chainHeight = minerNode1.execute(ethTransactions.blockNumber()); + + // Chain height should have moved on by at least 50 blocks + assertThat(chainHeight.compareTo(lastChainHeight.add(BigInteger.valueOf(50)))) + .isGreaterThanOrEqualTo(1); + lastChainHeight = chainHeight; + } + + // Check that the state of our smart contract is still correct + assertThat(simpleStorageContract.get().send()).isEqualTo(BigInteger.valueOf(201)); + + // Update it once more to check new transactions are mined OK + simpleStorageContract.set(BigInteger.valueOf(301)).send(); + assertThat(simpleStorageContract.get().send()).isEqualTo(BigInteger.valueOf(301)); + + // Upgrade the chain to shanghai in 120 seconds. Then try to deploy a shanghai contract + upgradeToShanghai( + minerNode1, minerNode2, minerNode3, minerNode4, Instant.now().getEpochSecond() + 120); + + Thread.sleep(THREE_MINUTES); + + SimpleStorageShanghai simpleStorageContractShanghai = + minerNode1.execute(contractTransactions.createSmartContract(SimpleStorageShanghai.class)); + + // Check the contract address is as expected for this sender & nonce + contractVerifier + .validTransactionReceipt("0x05d91b9031a655d08e654177336d08543ac4b711") + .verify(simpleStorageContractShanghai); + } + + private static void updateGenesisConfigToLondon( + final BesuNode minerNode, final boolean zeroBaseFeeEnabled, final int blockNumber) { + + if (minerNode.getGenesisConfig().isPresent()) { + final ObjectNode genesisConfigNode = + JsonUtil.objectNodeFromString(minerNode.getGenesisConfig().get()); + final ObjectNode config = (ObjectNode) genesisConfigNode.get("config"); + config.put("londonBlock", blockNumber); + config.put("zeroBaseFee", zeroBaseFeeEnabled); + minerNode.setGenesisConfig(genesisConfigNode.toString()); + } + } + + private static void updateGenesisConfigToShanghai( + final BesuNode minerNode, final long blockTimestamp) { + + if (minerNode.getGenesisConfig().isPresent()) { + final ObjectNode genesisConfigNode = + JsonUtil.objectNodeFromString(minerNode.getGenesisConfig().get()); + final ObjectNode config = (ObjectNode) genesisConfigNode.get("config"); + config.put("shanghaiTime", blockTimestamp); + minerNode.setGenesisConfig(genesisConfigNode.toString()); + } + } + + private void upgradeToLondon( + final BesuNode minerNode1, + final BesuNode minerNode2, + final BesuNode minerNode3, + final BesuNode minerNode4, + final int londonBlockNumber) + throws InterruptedException { + // Node 1 + stopNode(minerNode1); + updateGenesisConfigToLondon(minerNode1, true, londonBlockNumber); + startNode(minerNode1); + + // Node 2 + stopNode(minerNode2); + updateGenesisConfigToLondon(minerNode2, true, londonBlockNumber); + startNode(minerNode2); + + // Node 3 + stopNode(minerNode3); + updateGenesisConfigToLondon(minerNode3, true, londonBlockNumber); + startNode(minerNode3); + + // Node 4 + stopNode(minerNode4); + updateGenesisConfigToLondon(minerNode4, true, londonBlockNumber); + startNode(minerNode4); + } + + private void upgradeToShanghai( + final BesuNode minerNode1, + final BesuNode minerNode2, + final BesuNode minerNode3, + final BesuNode minerNode4, + final long shanghaiTime) + throws InterruptedException { + // Node 1 + stopNode(minerNode1); + updateGenesisConfigToShanghai(minerNode1, shanghaiTime); + startNode(minerNode1); + + // Node 2 + stopNode(minerNode2); + updateGenesisConfigToShanghai(minerNode2, shanghaiTime); + startNode(minerNode2); + + // Node 3 + stopNode(minerNode3); + updateGenesisConfigToShanghai(minerNode3, shanghaiTime); + startNode(minerNode3); + + // Node 4 + stopNode(minerNode4); + updateGenesisConfigToShanghai(minerNode4, shanghaiTime); + startNode(minerNode4); + } + + // Start a node with a delay before returning to give it time to start + private void startNode(final BesuNode node) throws InterruptedException { + cluster.startNode(node); + Thread.sleep(TEN_SECONDS); + } + + // Stop a node with a delay before returning to give it time to stop + private void stopNode(final BesuNode node) throws InterruptedException { + cluster.stopNode(node); + Thread.sleep(TEN_SECONDS); + } + + @Override + public void tearDownAcceptanceTestBase() { + cluster.stop(); + super.tearDownAcceptanceTestBase(); + } +} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bootstrap/ClusterAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bootstrap/ClusterAcceptanceTest.java index c4c9e42671c..e7ad04dc594 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bootstrap/ClusterAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bootstrap/ClusterAcceptanceTest.java @@ -17,15 +17,15 @@ import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; import org.hyperledger.besu.tests.acceptance.dsl.node.Node; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class ClusterAcceptanceTest extends AcceptanceTestBase { private Node minerNode; private Node fullNode; - @Before + @BeforeEach public void setUp() throws Exception { minerNode = besu.createMinerNode("node1"); fullNode = besu.createArchiveNode("node2"); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bootstrap/ClusterNoDiscoveryAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bootstrap/ClusterNoDiscoveryAcceptanceTest.java index 6c7c8127997..3a7da4f4768 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bootstrap/ClusterNoDiscoveryAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bootstrap/ClusterNoDiscoveryAcceptanceTest.java @@ -20,8 +20,8 @@ import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.ClusterConfiguration; import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.ClusterConfigurationBuilder; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class ClusterNoDiscoveryAcceptanceTest extends AcceptanceTestBase { @@ -29,7 +29,7 @@ public class ClusterNoDiscoveryAcceptanceTest extends AcceptanceTestBase { private Node noDiscoveryNode; private Cluster noDiscoveryCluster; - @Before + @BeforeEach public void setUp() throws Exception { final ClusterConfiguration clusterConfiguration = new ClusterConfigurationBuilder().awaitPeerDiscovery(false).build(); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bootstrap/ClusterThreadNodeRunnerAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bootstrap/ClusterThreadNodeRunnerAcceptanceTest.java index 805749a0bf8..39cc5ad26fc 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bootstrap/ClusterThreadNodeRunnerAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bootstrap/ClusterThreadNodeRunnerAcceptanceTest.java @@ -23,22 +23,22 @@ import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.ClusterConfiguration; import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.ClusterConfigurationBuilder; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class ClusterThreadNodeRunnerAcceptanceTest extends AcceptanceTestBase { private Node fullNode; private Cluster noDiscoveryCluster; - @Before + @BeforeEach public void setUp() throws Exception { final ClusterConfiguration clusterConfiguration = new ClusterConfigurationBuilder().awaitPeerDiscovery(false).build(); final BesuNodeRunner besuNodeRunner = new ThreadBesuNodeRunner(); noDiscoveryCluster = new Cluster(clusterConfiguration, net, besuNodeRunner); final BesuNode noDiscoveryNode = besu.createNodeWithNoDiscovery("noDiscovery"); - fullNode = besu.createArchiveNode("node2"); + fullNode = besu.createArchiveNode("archive"); noDiscoveryCluster.start(noDiscoveryNode, fullNode); } diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bootstrap/P2pDisabledAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bootstrap/P2pDisabledAcceptanceTest.java index aa71839b181..67a9bf2754f 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bootstrap/P2pDisabledAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bootstrap/P2pDisabledAcceptanceTest.java @@ -20,14 +20,14 @@ import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.ClusterConfiguration; import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.ClusterConfigurationBuilder; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class P2pDisabledAcceptanceTest extends AcceptanceTestBase { private Node node; private Cluster p2pDisabledCluster; - @Before + @BeforeEach public void setUp() throws Exception { final ClusterConfiguration clusterConfiguration = new ClusterConfigurationBuilder().awaitPeerDiscovery(false).build(); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bootstrap/StaticNodesAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bootstrap/StaticNodesAcceptanceTest.java index 1f28a4a9f3c..0ce5ea02b13 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bootstrap/StaticNodesAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/bootstrap/StaticNodesAcceptanceTest.java @@ -20,15 +20,15 @@ import java.util.Arrays; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class StaticNodesAcceptanceTest extends AcceptanceTestBase { private Node otherNode; private Node node; - @Before + @BeforeEach public void setUp() throws Exception { otherNode = besu.createNodeWithNoDiscovery("other-node"); cluster.start(otherNode); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueDiscardRpcAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueDiscardRpcAcceptanceTest.java index 9dbf376aac5..6ea274b6991 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueDiscardRpcAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueDiscardRpcAcceptanceTest.java @@ -14,14 +14,14 @@ */ package org.hyperledger.besu.tests.acceptance.clique; -import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; +import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBaseJunit5; import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; import java.io.IOException; -import org.junit.Test; +import org.junit.jupiter.api.Test; -public class CliqueDiscardRpcAcceptanceTest extends AcceptanceTestBase { +public class CliqueDiscardRpcAcceptanceTest extends AcceptanceTestBaseJunit5 { @Test public void shouldDiscardVotes() throws IOException { diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueGetSignersRpcAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueGetSignersRpcAcceptanceTest.java new file mode 100644 index 00000000000..ba848b0052c --- /dev/null +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueGetSignersRpcAcceptanceTest.java @@ -0,0 +1,60 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.clique; + +import static org.hyperledger.besu.tests.acceptance.dsl.transaction.clique.CliqueTransactions.LATEST; + +import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBaseJunit5; +import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +@Disabled("flaky test due to hardcoded block numbers") +public class CliqueGetSignersRpcAcceptanceTest extends AcceptanceTestBaseJunit5 { + private BesuNode minerNode1; + private BesuNode minerNode2; + + @BeforeEach + public void setUp() throws Exception { + final String[] validators = {"miner1"}; + minerNode1 = besu.createCliqueNodeWithValidators("miner1", validators); + minerNode2 = besu.createCliqueNodeWithValidators("miner2", validators); + cluster.start(minerNode1, minerNode2); + } + + @Test + public void shouldBeAbleToGetValidatorsForBlockNumber() { + cluster.verify(clique.validatorsAtBlockEqual("0x0", minerNode1)); + minerNode1.verify(blockchain.minimumHeight(1)); + + minerNode1.execute(cliqueTransactions.createAddProposal(minerNode2)); + cluster.verify(blockchain.reachesHeight(minerNode1, 1)); + cluster.verify(clique.validatorsAtBlockEqual("0x2", minerNode1, minerNode2)); + cluster.verify(clique.validatorsAtBlockEqual(LATEST, minerNode1, minerNode2)); + } + + @Test + public void shouldBeAbleToGetValidatorsForBlockHash() { + cluster.verify(clique.validatorsAtBlockHashFromBlockNumberEqual(minerNode1, 0, minerNode1)); + minerNode1.verify(blockchain.minimumHeight(1)); + + minerNode1.execute(cliqueTransactions.createAddProposal(minerNode2)); + cluster.verify(blockchain.reachesHeight(minerNode1, 1)); + cluster.verify( + clique.validatorsAtBlockHashFromBlockNumberEqual(minerNode1, 2, minerNode1, minerNode2)); + } +} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueGetSignersRpcTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueGetSignersRpcTest.java deleted file mode 100644 index 92987595dfd..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueGetSignersRpcTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.clique; - -import static org.hyperledger.besu.tests.acceptance.dsl.transaction.clique.CliqueTransactions.LATEST; - -import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; -import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; - -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; - -@Ignore -public class CliqueGetSignersRpcTest extends AcceptanceTestBase { - private BesuNode minerNode1; - private BesuNode minerNode2; - - @Before - public void setUp() throws Exception { - final String[] validators = {"miner1"}; - minerNode1 = besu.createCliqueNodeWithValidators("miner1", validators); - minerNode2 = besu.createCliqueNodeWithValidators("miner2", validators); - cluster.start(minerNode1, minerNode2); - } - - @Test - public void shouldBeAbleToGetValidatorsForBlockNumber() { - cluster.verify(clique.validatorsAtBlockEqual("0x0", minerNode1)); - minerNode1.verify(blockchain.minimumHeight(1)); - - minerNode1.execute(cliqueTransactions.createAddProposal(minerNode2)); - cluster.verify(blockchain.reachesHeight(minerNode1, 1)); - cluster.verify(clique.validatorsAtBlockEqual("0x2", minerNode1, minerNode2)); - cluster.verify(clique.validatorsAtBlockEqual(LATEST, minerNode1, minerNode2)); - } - - @Test - public void shouldBeAbleToGetValidatorsForBlockHash() { - cluster.verify(clique.validatorsAtBlockHashFromBlockNumberEqual(minerNode1, 0, minerNode1)); - minerNode1.verify(blockchain.minimumHeight(1)); - - minerNode1.execute(cliqueTransactions.createAddProposal(minerNode2)); - cluster.verify(blockchain.reachesHeight(minerNode1, 1)); - cluster.verify( - clique.validatorsAtBlockHashFromBlockNumberEqual(minerNode1, 2, minerNode1, minerNode2)); - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueMiningAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueMiningAcceptanceTest.java index 4ac728dec73..f6718b4ded1 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueMiningAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueMiningAcceptanceTest.java @@ -14,16 +14,25 @@ */ package org.hyperledger.besu.tests.acceptance.clique; -import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; +import static java.util.stream.Collectors.joining; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.data.Percentage.withPercentage; + +import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBaseJunit5; import org.hyperledger.besu.tests.acceptance.dsl.account.Account; import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.genesis.GenesisConfigurationFactory.CliqueOptions; import java.io.IOException; +import java.math.BigInteger; +import java.util.List; +import java.util.Map; +import java.util.Optional; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.web3j.protocol.core.DefaultBlockParameter; -public class CliqueMiningAcceptanceTest extends AcceptanceTestBase { +public class CliqueMiningAcceptanceTest extends AcceptanceTestBaseJunit5 { @Test public void shouldMineTransactionsOnSingleNode() throws IOException { @@ -55,7 +64,7 @@ public void shouldNotMineBlocksIfNoTransactionsWhenCreateEmptyBlockIsFalse() thr } @Test - public void shouldMineBlocksOnlyWhenTransactionsArePresentWhenCreateEmptyBlockIsFalse() + public void shouldMineBlocksOnlyWhenTransactionsArePresentWhenCreateEmptyBlocksIsFalse() throws IOException { final var cliqueOptionsNoEmptyBlocks = new CliqueOptions( @@ -77,7 +86,7 @@ public void shouldMineTransactionsOnMultipleNodes() throws IOException { final BesuNode minerNode1 = besu.createCliqueNode("miner1"); final BesuNode minerNode2 = besu.createCliqueNode("miner2"); final BesuNode minerNode3 = besu.createCliqueNode("miner3"); - cluster.start(minerNode1, minerNode2, minerNode3); + startClusterAndVerifyProducingBlocks(minerNode1, minerNode2, minerNode3); final Account sender = accounts.createAccount("account1"); final Account receiver = accounts.createAccount("account2"); @@ -97,7 +106,7 @@ public void shouldStallMiningWhenInsufficientValidators() throws IOException { final BesuNode minerNode1 = besu.createCliqueNode("miner1"); final BesuNode minerNode2 = besu.createCliqueNode("miner2"); final BesuNode minerNode3 = besu.createCliqueNode("miner3"); - cluster.start(minerNode1, minerNode2, minerNode3); + startClusterAndVerifyProducingBlocks(minerNode1, minerNode2, minerNode3); cluster.stopNode(minerNode2); cluster.stopNode(minerNode3); @@ -107,12 +116,23 @@ public void shouldStallMiningWhenInsufficientValidators() throws IOException { minerNode1.verify(clique.noNewBlockCreated(minerNode1)); } + private void startClusterAndVerifyProducingBlocks( + final BesuNode minerNode1, final BesuNode minerNode2, final BesuNode minerNode3) { + cluster.start(minerNode1, minerNode2, minerNode3); + + // verify that we have started producing blocks + waitForBlockHeight(minerNode1, 1); + final var minerChainHead = minerNode1.execute(ethTransactions.block()); + minerNode2.verify(blockchain.minimumHeight(minerChainHead.getNumber().longValue())); + minerNode3.verify(blockchain.minimumHeight(minerChainHead.getNumber().longValue())); + } + @Test public void shouldStillMineWhenANodeFailsAndHasSufficientValidators() throws IOException { final BesuNode minerNode1 = besu.createCliqueNode("miner1"); final BesuNode minerNode2 = besu.createCliqueNode("miner2"); final BesuNode minerNode3 = besu.createCliqueNode("miner3"); - cluster.start(minerNode1, minerNode2, minerNode3); + startClusterAndVerifyProducingBlocks(minerNode1, minerNode2, minerNode3); cluster.verifyOnActiveNodes(blockchain.reachesHeight(minerNode1, 1, 85)); @@ -123,4 +143,173 @@ public void shouldStillMineWhenANodeFailsAndHasSufficientValidators() throws IOE cluster.verifyOnActiveNodes(clique.blockIsCreatedByProposer(minerNode1)); cluster.verifyOnActiveNodes(clique.blockIsCreatedByProposer(minerNode2)); } + + @Test + public void shouldMineBlocksAccordingToBlockPeriodTransitions() throws IOException { + + final var cliqueOptions = new CliqueOptions(3, CliqueOptions.DEFAULT.epochLength(), true); + final BesuNode minerNode = besu.createCliqueNode("miner1", cliqueOptions); + + // setup transitions + final Map decreasePeriodTo2_Transition = + Map.of("block", 3, "blockperiodseconds", 2); + final Map decreasePeriodTo1_Transition = + Map.of("block", 4, "blockperiodseconds", 1); + // ensure previous blockperiodseconds transition is carried over + final Map dummy_Transition = Map.of("block", 5, "createemptyblocks", true); + final Map increasePeriodTo2_Transition = + Map.of("block", 6, "blockperiodseconds", 2); + + final Optional initialGenesis = + minerNode.getGenesisConfigProvider().create(List.of(minerNode)); + final String genesisWithTransitions = + prependTransitionsToCliqueOptions( + initialGenesis.orElseThrow(), + List.of( + decreasePeriodTo2_Transition, + decreasePeriodTo1_Transition, + dummy_Transition, + increasePeriodTo2_Transition)); + minerNode.setGenesisConfig(genesisWithTransitions); + + // Mine 6 blocks + cluster.start(minerNode); + minerNode.verify(blockchain.reachesHeight(minerNode, 5)); + + // Assert the block period decreased/increased after each transition + final long block1Timestamp = getTimestampForBlock(minerNode, 1); + final long block2Timestamp = getTimestampForBlock(minerNode, 2); + final long block3Timestamp = getTimestampForBlock(minerNode, 3); + final long block4Timestamp = getTimestampForBlock(minerNode, 4); + final long block5Timestamp = getTimestampForBlock(minerNode, 5); + final long block6Timestamp = getTimestampForBlock(minerNode, 6); + assertThat(block2Timestamp - block1Timestamp).isCloseTo(3, withPercentage(20)); + assertThat(block3Timestamp - block2Timestamp).isCloseTo(2, withPercentage(20)); + assertThat(block4Timestamp - block3Timestamp).isCloseTo(1, withPercentage(20)); + assertThat(block5Timestamp - block4Timestamp).isCloseTo(1, withPercentage(20)); + assertThat(block6Timestamp - block5Timestamp).isCloseTo(2, withPercentage(20)); + } + + @Test + public void shouldMineBlocksAccordingToCreateEmptyBlocksTransitions() throws IOException { + + final var cliqueOptionsEmptyBlocks = + new CliqueOptions(2, CliqueOptions.DEFAULT.epochLength(), true); + final BesuNode minerNode = besu.createCliqueNode("miner1", cliqueOptionsEmptyBlocks); + + // setup transitions + final Map noEmptyBlocks_Transition = + Map.of("block", 3, "createemptyblocks", false); + final Map emptyBlocks_Transition = + Map.of("block", 4, "createemptyblocks", true); + final Map secondNoEmptyBlocks_Transition = + Map.of("block", 6, "createemptyblocks", false); + // ensure previous createemptyblocks transition is carried over + final Map dummy_Transition = Map.of("block", 7, "blockperiodseconds", 1); + + final Optional initialGenesis = + minerNode.getGenesisConfigProvider().create(List.of(minerNode)); + final String genesisWithTransitions = + prependTransitionsToCliqueOptions( + initialGenesis.orElseThrow(), + List.of( + noEmptyBlocks_Transition, + emptyBlocks_Transition, + secondNoEmptyBlocks_Transition, + dummy_Transition)); + minerNode.setGenesisConfig(genesisWithTransitions); + + final Account sender = accounts.createAccount("account1"); + + // Mine 2 blocks + cluster.start(minerNode); + minerNode.verify(blockchain.reachesHeight(minerNode, 1)); + + // tx required to mine block + cluster.verify(clique.noNewBlockCreated(minerNode)); + minerNode.execute(accountTransactions.createTransfer(sender, 50)); + minerNode.verify(clique.blockIsCreatedByProposer(minerNode)); + + // Mine 2 more blocks so chain head is 5 + minerNode.verify(blockchain.reachesHeight(minerNode, 2)); + + // tx required to mine block 6 + cluster.verify(clique.noNewBlockCreated(minerNode)); + minerNode.execute(accountTransactions.createTransfer(sender, 50)); + minerNode.verify(clique.blockIsCreatedByProposer(minerNode)); + + // check createemptyblocks transition carried over when other transition activated... + // tx required to mine block 7 + cluster.verify(clique.noNewBlockCreated(minerNode)); + } + + private long getTimestampForBlock(final BesuNode minerNode, final int blockNumber) { + return minerNode + .execute( + ethTransactions.block(DefaultBlockParameter.valueOf(BigInteger.valueOf(blockNumber)))) + .getTimestamp() + .longValue(); + } + + private String prependTransitionsToCliqueOptions( + final String originalOptions, final List> transitions) { + final StringBuilder stringBuilder = + new StringBuilder() + .append(formatCliqueTransitionsOptions(transitions)) + .append(",\n") + .append(quote("clique")) + .append(": {"); + + return originalOptions.replace(quote("clique") + ": {", stringBuilder.toString()); + } + + private String formatCliqueTransitionsOptions(final List> transitions) { + final StringBuilder stringBuilder = new StringBuilder(); + + stringBuilder.append(quote("transitions")); + stringBuilder.append(": {\n"); + stringBuilder.append(quote("clique")); + stringBuilder.append(": ["); + final String formattedTransitions = + transitions.stream().map(this::formatTransition).collect(joining(",\n")); + stringBuilder.append(formattedTransitions); + stringBuilder.append("\n]"); + stringBuilder.append("}\n"); + + return stringBuilder.toString(); + } + + private String quote(final Object value) { + return '"' + value.toString() + '"'; + } + + private String formatTransition(final Map transition) { + final StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("{"); + String formattedTransition = + transition.keySet().stream() + .map(key -> formatKeyValues(key, transition.get(key))) + .collect(joining(",")); + stringBuilder.append(formattedTransition); + stringBuilder.append("}"); + return stringBuilder.toString(); + } + + private String formatKeyValues(final Object... keyOrValue) { + if (keyOrValue.length % 2 == 1) { + // An odd number of strings cannot form a set of key-value pairs + throw new IllegalArgumentException("Must supply key-value pairs"); + } + final StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < keyOrValue.length; i += 2) { + if (i > 0) { + stringBuilder.append(", "); + } + final String key = keyOrValue[i].toString(); + final Object value = keyOrValue[i + 1]; + final String valueStr = value instanceof String ? quote(value) : value.toString(); + stringBuilder.append(String.format("\n%s: %s", quote(key), valueStr)); + } + return stringBuilder.toString(); + } } diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueProposalRpcAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueProposalRpcAcceptanceTest.java index 65340c57d27..a7e8ebb9c9c 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueProposalRpcAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueProposalRpcAcceptanceTest.java @@ -14,14 +14,14 @@ */ package org.hyperledger.besu.tests.acceptance.clique; -import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; +import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBaseJunit5; import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; import java.io.IOException; -import org.junit.Test; +import org.junit.jupiter.api.Test; -public class CliqueProposalRpcAcceptanceTest extends AcceptanceTestBase { +public class CliqueProposalRpcAcceptanceTest extends AcceptanceTestBaseJunit5 { @Test public void shouldReturnProposals() throws IOException { diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueProposeRpcAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueProposeRpcAcceptanceTest.java index 4767f6267b8..d4fb71c36b4 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueProposeRpcAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueProposeRpcAcceptanceTest.java @@ -14,16 +14,16 @@ */ package org.hyperledger.besu.tests.acceptance.clique; -import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; +import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBaseJunit5; import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition; import org.hyperledger.besu.tests.acceptance.dsl.condition.clique.ExpectNonceVote.CLIQUE_NONCE_VOTE; import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; import java.io.IOException; -import org.junit.Test; +import org.junit.jupiter.api.Test; -public class CliqueProposeRpcAcceptanceTest extends AcceptanceTestBase { +public class CliqueProposeRpcAcceptanceTest extends AcceptanceTestBaseJunit5 { @Test public void shouldAddValidators() throws IOException { diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueZeroValidators.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueZeroValidators.java deleted file mode 100644 index 3f8ed7b5e9c..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueZeroValidators.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.clique; - -import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; -import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; - -import java.io.IOException; - -import org.junit.Test; - -public class CliqueZeroValidators extends AcceptanceTestBase { - - @Test - public void zeroValidatorsFormValidCluster() throws IOException { - final String[] signers = {}; - final BesuNode node1 = besu.createCliqueNodeWithValidators("node1", signers); - final BesuNode node2 = besu.createCliqueNodeWithValidators("node2", signers); - - cluster.start(node1, node2); - - cluster.verify(net.awaitPeerCount(1)); - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueZeroValidatorsAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueZeroValidatorsAcceptanceTest.java new file mode 100644 index 00000000000..b0c6afd93fc --- /dev/null +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/clique/CliqueZeroValidatorsAcceptanceTest.java @@ -0,0 +1,36 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.clique; + +import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBaseJunit5; +import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +public class CliqueZeroValidatorsAcceptanceTest extends AcceptanceTestBaseJunit5 { + + @Test + public void zeroValidatorsFormValidCluster() throws IOException { + final String[] signers = {}; + final BesuNode node1 = besu.createCliqueNodeWithValidators("node1", signers); + final BesuNode node2 = besu.createCliqueNodeWithValidators("node2", signers); + + cluster.start(node1, node2); + + cluster.verify(net.awaitPeerCount(1)); + } +} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/config/BootNodesGenesisSetupTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/config/BootNodesGenesisSetupTest.java index 1ad2587ded3..616afc19669 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/config/BootNodesGenesisSetupTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/config/BootNodesGenesisSetupTest.java @@ -29,9 +29,9 @@ import org.bouncycastle.asn1.sec.SECNamedCurves; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.params.ECDomainParameters; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class BootNodesGenesisSetupTest extends AcceptanceTestBase { private static final String CURVE_NAME = "secp256k1"; @@ -42,13 +42,13 @@ public class BootNodesGenesisSetupTest extends AcceptanceTestBase { private Node nodeA; private Node nodeB; - @BeforeClass + @BeforeAll public static void environment() { final X9ECParameters params = SECNamedCurves.getByName(CURVE_NAME); curve = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH()); } - @Before + @BeforeEach public void setUp() throws Exception { int nodeAP2pBindingPort; int nodeBP2pBindingPort; diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/crypto/SECP256R1AcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/crypto/SECP256R1AcceptanceTest.java index 8b4ef9f864b..27679463bfd 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/crypto/SECP256R1AcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/crypto/SECP256R1AcceptanceTest.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.tests.acceptance.crypto; @@ -32,8 +31,8 @@ import java.util.List; import org.apache.tuweni.bytes.Bytes32; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class SECP256R1AcceptanceTest extends AcceptanceTestBase { private Node minerNode; @@ -49,7 +48,7 @@ public class SECP256R1AcceptanceTest extends AcceptanceTestBase { private static final SECP256R1 SECP256R1_SIGNATURE_ALGORITHM = new SECP256R1(); - @Before + @BeforeEach public void setUp() throws Exception { KeyPair minerNodeKeyPair = createKeyPair(MINER_NODE_PRIVATE_KEY); KeyPair otherNodeKeyPair = createKeyPair(OTHER_NODE_PRIVATE_KEY); @@ -67,6 +66,12 @@ public void setUp() throws Exception { besu.createNodeWithNonDefaultSignatureAlgorithm( "otherNode", GENESIS_FILE, otherNodeKeyPair, List.of(minerNode)); noDiscoveryCluster.addNode(otherNode); + + minerNode.verify(net.awaitPeerCount(1)); + otherNode.verify(net.awaitPeerCount(1)); + + final var minerChainHead = minerNode.execute(ethTransactions.block()); + otherNode.verify(blockchain.minimumHeight(minerChainHead.getNumber().longValue())); } @Test diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/database/DatabaseMigrationAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/database/DatabaseMigrationAcceptanceTest.java index 291506498ba..428803544ce 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/database/DatabaseMigrationAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/database/DatabaseMigrationAcceptanceTest.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.tests.acceptance.database; import static java.util.Collections.singletonList; @@ -28,49 +27,31 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; +import java.util.stream.Stream; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; -@RunWith(Parameterized.class) public class DatabaseMigrationAcceptanceTest extends org.hyperledger.besu.tests.acceptance.AbstractPreexistingNodeTest { - private final long expectedChainHeight; private BesuNode node; - private final List testAccounts; - - public DatabaseMigrationAcceptanceTest( - final String testName, - final String dataPath, - final long expectedChainHeight, - final List testAccounts) { - super(testName, dataPath); - this.expectedChainHeight = expectedChainHeight; - this.testAccounts = testAccounts; - } - @Parameters(name = "{0}") - public static Object[][] getParameters() { - return new Object[][] { - // First 10 blocks of ropsten - new Object[] { - "After versioning was enabled and using multiple RocksDB columns", - "version1", - 0xA, - singletonList( - new AccountData( - "0xd1aeb42885a43b72b518182ef893125814811048", - BigInteger.valueOf(0xA), - Wei.fromHexString("0x2B5E3AF16B1880000"))) - } - }; + public static Stream getParameters() { + // First 10 blocks of ropsten + return Stream.of( + Arguments.of( + "After versioning was enabled and using multiple RocksDB columns", + "version1", + 0xA, + singletonList( + new AccountData( + "0xd1aeb42885a43b72b518182ef893125814811048", + BigInteger.valueOf(0xA), + Wei.fromHexString("0x2B5E3AF16B1880000"))))); } - @Before - public void setUp() throws Exception { + public void setUp(final String testName, final String dataPath) throws Exception { final URL rootURL = DatabaseMigrationAcceptanceTest.class.getResource(dataPath); hostDataPath = copyDataDir(rootURL); final Path databaseArchive = @@ -83,13 +64,27 @@ public void setUp() throws Exception { cluster.start(node); } - @Test - public void shouldReturnCorrectBlockHeight() { + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("getParameters") + public void shouldReturnCorrectBlockHeight( + final String testName, + final String dataPath, + final long expectedChainHeight, + final List testAccounts) + throws Exception { + setUp(testName, dataPath); blockchain.currentHeight(expectedChainHeight).verify(node); } - @Test - public void shouldReturnCorrectAccountBalance() { + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("getParameters") + public void shouldReturnCorrectAccountBalance( + final String testName, + final String dataPath, + final long expectedChainHeight, + final List testAccounts) + throws Exception { + setUp(testName, dataPath); testAccounts.forEach( accountData -> accounts diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/ethereum/CodeDelegationTransactionAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/ethereum/CodeDelegationTransactionAcceptanceTest.java new file mode 100644 index 00000000000..ee896ccd278 --- /dev/null +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/ethereum/CodeDelegationTransactionAcceptanceTest.java @@ -0,0 +1,235 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.ethereum; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.crypto.SECP256K1; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.CodeDelegation; +import org.hyperledger.besu.datatypes.TransactionType; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; +import org.hyperledger.besu.tests.acceptance.dsl.account.Account; +import org.hyperledger.besu.tests.acceptance.dsl.blockchain.Amount; +import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; + +import java.io.IOException; +import java.math.BigInteger; +import java.util.List; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.web3j.protocol.core.methods.response.TransactionReceipt; + +public class CodeDelegationTransactionAcceptanceTest extends AcceptanceTestBase { + private static final String GENESIS_FILE = "/dev/dev_prague.json"; + private static final SECP256K1 secp256k1 = new SECP256K1(); + + public static final Address SEND_ALL_ETH_CONTRACT_ADDRESS = + Address.fromHexStringStrict("0000000000000000000000000000000000009999"); + + private final Account authorizer = + accounts.createAccount( + Address.fromHexStringStrict("8da48afC965480220a3dB9244771bd3afcB5d895")); + public static final Bytes AUTHORIZER_PRIVATE_KEY = + Bytes.fromHexString("11f2e7b6a734ab03fa682450e0d4681d18a944f8b83c99bf7b9b4de6c0f35ea1"); + + private final Account transactionSponsor = + accounts.createAccount( + Address.fromHexStringStrict("a05b21E5186Ce93d2a226722b85D6e550Ac7D6E3")); + public static final Bytes TRANSACTION_SPONSOR_PRIVATE_KEY = + Bytes.fromHexString("3a4ff6d22d7502ef2452368165422861c01a0f72f851793b372b87888dc3c453"); + + private final Account otherAccount = accounts.createAccount("otherAccount"); + + private BesuNode besuNode; + private PragueAcceptanceTestHelper testHelper; + + @BeforeEach + void setUp() throws IOException { + besuNode = besu.createExecutionEngineGenesisNode("besuNode", GENESIS_FILE); + cluster.start(besuNode); + + testHelper = new PragueAcceptanceTestHelper(besuNode, ethTransactions); + } + + @AfterEach + void tearDown() { + besuNode.close(); + } + + /** + * At the beginning of the test both the authorizer and the transaction sponsor have a balance of + * 90000 ETH. The authorizer creates an authorization for a contract that send all its ETH to any + * given address. The transaction sponsor sponsors the 7702 transaction and sends all the ETH from + * the authorizer to itself. The authorizer balance should be 0 and the transaction sponsor's + * balance should be 180000 ETH minus the transaction costs. + */ + @Test + public void shouldTransferAllEthOfAuthorizerToSponsor() throws IOException { + + // 7702 transaction + final CodeDelegation authorization = + org.hyperledger.besu.ethereum.core.CodeDelegation.builder() + .chainId(BigInteger.valueOf(20211)) + .address(SEND_ALL_ETH_CONTRACT_ADDRESS) + .nonce(0) + .signAndBuild( + secp256k1.createKeyPair( + secp256k1.createPrivateKey(AUTHORIZER_PRIVATE_KEY.toUnsignedBigInteger()))); + + final Transaction tx = + Transaction.builder() + .type(TransactionType.DELEGATE_CODE) + .chainId(BigInteger.valueOf(20211)) + .nonce(0) + .maxPriorityFeePerGas(Wei.of(1000000000)) + .maxFeePerGas(Wei.fromHexString("0x02540BE400")) + .gasLimit(1000000) + .to(Address.fromHexStringStrict(authorizer.getAddress())) + .value(Wei.ZERO) + .payload(Bytes32.leftPad(Bytes.fromHexString(transactionSponsor.getAddress()))) + .accessList(List.of()) + .codeDelegations(List.of(authorization)) + .signAndBuild( + secp256k1.createKeyPair( + secp256k1.createPrivateKey( + TRANSACTION_SPONSOR_PRIVATE_KEY.toUnsignedBigInteger()))); + + final String txHash = + besuNode.execute(ethTransactions.sendRawTransaction(tx.encoded().toHexString())); + testHelper.buildNewBlock(); + + Optional maybeTransactionReceipt = + besuNode.execute(ethTransactions.getTransactionReceipt(txHash)); + assertThat(maybeTransactionReceipt).isPresent(); + + cluster.verify(authorizer.balanceEquals(0)); + + final String gasPriceWithout0x = + maybeTransactionReceipt.get().getEffectiveGasPrice().substring(2); + final BigInteger txCost = + maybeTransactionReceipt.get().getGasUsed().multiply(new BigInteger(gasPriceWithout0x, 16)); + BigInteger expectedSponsorBalance = new BigInteger("180000000000000000000000").subtract(txCost); + cluster.verify(transactionSponsor.balanceEquals(Amount.wei(expectedSponsorBalance))); + } + + /** + * The authorizer creates an authorization for a contract that sends all its ETH to any given + * address. The nonce is 1 and the authorization list is processed after the nonce increase of the + * sender. Therefore, the authorization should be valid. The authorizer balance should be 0 and + * the transaction sponsor's * balance should be 180000 ETH minus the transaction costs. + */ + @Test + public void shouldCheckNonceAfterNonceIncreaseOfSender() throws IOException { + final long GAS_LIMIT = 1000000L; + cluster.verify(authorizer.balanceEquals(Amount.ether(90000))); + + final CodeDelegation authorization = + org.hyperledger.besu.ethereum.core.CodeDelegation.builder() + .chainId(BigInteger.valueOf(20211)) + .nonce( + 1L) // nonce is 1, but because it is validated before the nonce increase, it should + // be 0 + .address(SEND_ALL_ETH_CONTRACT_ADDRESS) + .signAndBuild( + secp256k1.createKeyPair( + secp256k1.createPrivateKey(AUTHORIZER_PRIVATE_KEY.toUnsignedBigInteger()))); + + final Transaction tx = + Transaction.builder() + .type(TransactionType.DELEGATE_CODE) + .chainId(BigInteger.valueOf(20211)) + .nonce(0) + .maxPriorityFeePerGas(Wei.of(1000000000)) + .maxFeePerGas(Wei.fromHexString("0x02540BE400")) + .gasLimit(GAS_LIMIT) + .to(Address.fromHexStringStrict(authorizer.getAddress())) + .value(Wei.ZERO) + .payload(Bytes32.leftPad(Bytes.fromHexString(otherAccount.getAddress()))) + .accessList(List.of()) + .codeDelegations(List.of(authorization)) + .signAndBuild( + secp256k1.createKeyPair( + secp256k1.createPrivateKey(AUTHORIZER_PRIVATE_KEY.toUnsignedBigInteger()))); + + final String txHash = + besuNode.execute(ethTransactions.sendRawTransaction(tx.encoded().toHexString())); + testHelper.buildNewBlock(); + + final Optional maybeFirstTransactionReceipt = + besuNode.execute(ethTransactions.getTransactionReceipt(txHash)); + assertThat(maybeFirstTransactionReceipt).isPresent(); + + final String gasPriceWithout0x = + maybeFirstTransactionReceipt.get().getEffectiveGasPrice().substring(2); + final BigInteger gasPrice = new BigInteger(gasPriceWithout0x, 16); + final BigInteger txCost = maybeFirstTransactionReceipt.get().getGasUsed().multiply(gasPrice); + + final BigInteger authorizerBalanceAfterFirstTx = + besuNode.execute(ethTransactions.getBalance(authorizer)); + + // The remaining balance of the authorizer should the gas limit multiplied by the gas price + // minus the transaction cost. + // The following executes this calculation in reverse. + assertThat(GAS_LIMIT) + .isEqualTo(authorizerBalanceAfterFirstTx.add(txCost).divide(gasPrice).longValue()); + + // The other accounts balance should be the initial 9000 ETH balance from the authorizer minus + // the remaining balance of the authorizer and minus the transaction cost + final BigInteger otherAccountBalanceAfterFirstTx = + new BigInteger("90000000000000000000000") + .subtract(authorizerBalanceAfterFirstTx) + .subtract(txCost); + + cluster.verify(otherAccount.balanceEquals(Amount.wei(otherAccountBalanceAfterFirstTx))); + + final Transaction txSendEthToOtherAccount = + Transaction.builder() + .type(TransactionType.EIP1559) + .chainId(BigInteger.valueOf(20211)) + .nonce(2) + .maxPriorityFeePerGas(Wei.of(10)) + .maxFeePerGas(Wei.of(100)) + .gasLimit(21000) + .to(Address.fromHexStringStrict(otherAccount.getAddress())) + .value(Wei.ONE) + .payload(Bytes.EMPTY) + .signAndBuild( + secp256k1.createKeyPair( + secp256k1.createPrivateKey(AUTHORIZER_PRIVATE_KEY.toUnsignedBigInteger()))); + + final String txSendEthToOtherAccountHash = + besuNode.execute( + ethTransactions.sendRawTransaction(txSendEthToOtherAccount.encoded().toHexString())); + testHelper.buildNewBlock(); + + final Optional maybeSecondTransactionReceipt = + besuNode.execute(ethTransactions.getTransactionReceipt(txSendEthToOtherAccountHash)); + assertThat(maybeSecondTransactionReceipt).isPresent(); + + // the balance of the other account should be the previous balance plus the value of the 1 Wei + final BigInteger otherAccountBalanceAfterSecondTx = + besuNode.execute(ethTransactions.getBalance(otherAccount)); + assertThat(otherAccountBalanceAfterFirstTx.add(BigInteger.ONE)) + .isEqualTo(otherAccountBalanceAfterSecondTx); + } +} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/ethereum/PragueAcceptanceTestHelper.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/ethereum/PragueAcceptanceTestHelper.java new file mode 100644 index 00000000000..fa190f3de17 --- /dev/null +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/ethereum/PragueAcceptanceTestHelper.java @@ -0,0 +1,193 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.ethereum; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; +import org.hyperledger.besu.tests.acceptance.dsl.transaction.eth.EthTransactions; + +import java.io.IOException; +import java.util.Optional; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import okhttp3.Call; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import org.web3j.protocol.core.methods.response.EthBlock; + +public class PragueAcceptanceTestHelper { + protected static final MediaType MEDIA_TYPE_JSON = + MediaType.parse("application/json; charset=utf-8"); + + private final OkHttpClient httpClient; + private final ObjectMapper mapper; + private final BesuNode besuNode; + private final EthTransactions ethTransactions; + + private long blockTimeStamp = 0; + + PragueAcceptanceTestHelper(final BesuNode besuNode, final EthTransactions ethTransactions) { + this.besuNode = besuNode; + this.ethTransactions = ethTransactions; + httpClient = new OkHttpClient(); + mapper = new ObjectMapper(); + } + + public void buildNewBlock() throws IOException { + final EthBlock.Block block = besuNode.execute(ethTransactions.block()); + + blockTimeStamp += 1; + final Call buildBlockRequest = + createEngineCall(createForkChoiceRequest(block.getHash(), blockTimeStamp)); + + final String payloadId; + try (final Response buildBlockResponse = buildBlockRequest.execute()) { + payloadId = + mapper + .readTree(buildBlockResponse.body().string()) + .get("result") + .get("payloadId") + .asText(); + + assertThat(payloadId).isNotEmpty(); + } + + waitFor(500); + + final Call getPayloadRequest = createEngineCall(createGetPayloadRequest(payloadId)); + + final ObjectNode executionPayload; + final String newBlockHash; + final String parentBeaconBlockRoot; + try (final Response getPayloadResponse = getPayloadRequest.execute()) { + assertThat(getPayloadResponse.code()).isEqualTo(200); + + executionPayload = + (ObjectNode) + mapper + .readTree(getPayloadResponse.body().string()) + .get("result") + .get("executionPayload"); + + newBlockHash = executionPayload.get("blockHash").asText(); + parentBeaconBlockRoot = executionPayload.remove("parentBeaconBlockRoot").asText(); + + assertThat(newBlockHash).isNotEmpty(); + } + + final Call newPayloadRequest = + createEngineCall( + createNewPayloadRequest(executionPayload.toString(), parentBeaconBlockRoot)); + try (final Response newPayloadResponse = newPayloadRequest.execute()) { + assertThat(newPayloadResponse.code()).isEqualTo(200); + } + + final Call moveChainAheadRequest = createEngineCall(createForkChoiceRequest(newBlockHash)); + + try (final Response moveChainAheadResponse = moveChainAheadRequest.execute()) { + assertThat(moveChainAheadResponse.code()).isEqualTo(200); + } + } + + private Call createEngineCall(final String request) { + return httpClient.newCall( + new Request.Builder() + .url(besuNode.engineRpcUrl().get()) + .post(RequestBody.create(request, MEDIA_TYPE_JSON)) + .build()); + } + + private String createForkChoiceRequest(final String blockHash) { + return createForkChoiceRequest(blockHash, null); + } + + private String createForkChoiceRequest(final String parentBlockHash, final Long timeStamp) { + final Optional maybeTimeStamp = Optional.ofNullable(timeStamp); + + String forkChoiceRequest = + "{" + + " \"jsonrpc\": \"2.0\"," + + " \"method\": \"engine_forkchoiceUpdatedV3\"," + + " \"params\": [" + + " {" + + " \"headBlockHash\": \"" + + parentBlockHash + + "\"," + + " \"safeBlockHash\": \"" + + parentBlockHash + + "\"," + + " \"finalizedBlockHash\": \"" + + parentBlockHash + + "\"" + + " }"; + + if (maybeTimeStamp.isPresent()) { + forkChoiceRequest += + " ,{" + + " \"timestamp\": \"" + + Long.toHexString(maybeTimeStamp.get()) + + "\"," + + " \"prevRandao\": \"0x0000000000000000000000000000000000000000000000000000000000000000\"," + + " \"suggestedFeeRecipient\": \"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b\"," + + " \"withdrawals\": []," + + " \"parentBeaconBlockRoot\": \"0x0000000000000000000000000000000000000000000000000000000000000000\"" + + " }"; + } + + forkChoiceRequest += " ]," + " \"id\": 67" + "}"; + + return forkChoiceRequest; + } + + private String createGetPayloadRequest(final String payloadId) { + return "{" + + " \"jsonrpc\": \"2.0\"," + + " \"method\": \"engine_getPayloadV4\"," + + " \"params\": [\"" + + payloadId + + "\"]," + + " \"id\": 67" + + "}"; + } + + private String createNewPayloadRequest( + final String executionPayload, final String parentBeaconBlockRoot) { + return "{" + + " \"jsonrpc\": \"2.0\"," + + " \"method\": \"engine_newPayloadV4\"," + + " \"params\": [" + + executionPayload + + ",[]," + + "\"" + + parentBeaconBlockRoot + + "\"" + + "]," + + " \"id\": 67" + + "}"; + } + + private static void waitFor(final long durationMilliSeconds) { + try { + Thread.sleep(durationMilliSeconds); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } +} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/AbstractJsonRpcTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/AbstractJsonRpcTest.java index 508fb891c0e..12907491763 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/AbstractJsonRpcTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/AbstractJsonRpcTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -30,7 +30,7 @@ import java.net.URISyntaxException; import java.net.URL; import java.util.Arrays; -import java.util.stream.Collectors; +import java.util.stream.Stream; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -40,10 +40,13 @@ import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; abstract class AbstractJsonRpcTest { - private static final MediaType MEDIA_TYPE_JSON = + protected static final MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json; charset=utf-8"); static class JsonRpcTestsContext { @@ -69,16 +72,14 @@ public void tearDown() { } private final JsonRpcTestsContext testsContext; - private final URI testCaseFileURI; - public AbstractJsonRpcTest( - final String ignored, final JsonRpcTestsContext testsContext, final URI testCaseFileURI) { - this.testCaseFileURI = testCaseFileURI; + public AbstractJsonRpcTest(final JsonRpcTestsContext testsContext) { this.testsContext = testsContext; } - @Test - public void test() throws IOException { + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("testCases") + public void test(final URI testCaseFileURI) throws IOException { final JsonRpcTestCase testCase = testsContext.mapper.readValue(testCaseFileURI.toURL(), JsonRpcTestCase.class); @@ -93,6 +94,11 @@ public void test() throws IOException { .readTimeout(900, java.util.concurrent.TimeUnit.SECONDS) .build(); } + + if (testCase.getWaitTime() > 0L) { + waitForMillis(testCase.getWaitTime()); + } + final Call testRequest = client.newCall( new Request.Builder() @@ -112,13 +118,21 @@ public void test() throws IOException { .isEqualTo(expectedBody); } + private static void waitForMillis(final long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + protected void evaluateResponse( final ObjectNode responseBody, final Call testRequest, final JsonRpcTestCase testCase, final URL url) {} - private String getRpcUrl(final String rpcMethod) { + protected String getRpcUrl(final String rpcMethod) { if (rpcMethod.contains("eth_") || rpcMethod.contains("engine_")) { return testsContext.besuNode.engineRpcUrl().get(); } @@ -126,14 +140,19 @@ private String getRpcUrl(final String rpcMethod) { return testsContext.besuNode.jsonRpcBaseUrl().get(); } - public static Iterable testCases(final String testCasesPath) throws URISyntaxException { + public static Stream testCasesFromPath(final String testCasesPath) + throws URISyntaxException { final File[] testCasesList = new File(AbstractJsonRpcTest.class.getResource(testCasesPath).toURI()).listFiles(); - return Arrays.stream(testCasesList) - .sorted() - .map(file -> new Object[] {file.getName(), file.toURI()}) - .collect(Collectors.toList()); + return Arrays.stream(testCasesList).sorted().map(File::toURI).map(Arguments::of); + } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); } } diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/DebugReplayBlockAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/DebugReplayBlockAcceptanceTest.java index 0b6f76f4de2..9fafeac3a77 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/DebugReplayBlockAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/DebugReplayBlockAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,36 +15,33 @@ package org.hyperledger.besu.tests.acceptance.jsonrpc; import java.io.IOException; -import java.net.URI; import java.net.URISyntaxException; +import java.util.stream.Stream; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.provider.Arguments; -@RunWith(Parameterized.class) public class DebugReplayBlockAcceptanceTest extends AbstractJsonRpcTest { private static final String GENESIS_FILE = "/jsonrpc/debug/replayBlock/genesis.json"; private static final String TEST_CASE_PATH = "/jsonrpc/debug/replayBlock/test-cases/"; private static AbstractJsonRpcTest.JsonRpcTestsContext testsContext; - public DebugReplayBlockAcceptanceTest(final String ignored, final URI testCaseFileURI) { - super(ignored, testsContext, testCaseFileURI); + public DebugReplayBlockAcceptanceTest() { + super(testsContext); } - @BeforeClass + @BeforeAll public static void init() throws IOException { testsContext = new JsonRpcTestsContext(GENESIS_FILE); } - @Parameterized.Parameters(name = "{0}") - public static Iterable testCases() throws URISyntaxException { - return testCases(TEST_CASE_PATH); + public static Stream testCases() throws URISyntaxException { + return testCasesFromPath(TEST_CASE_PATH); } - @AfterClass + @AfterAll public static void tearDown() { testsContext.cluster.close(); } diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/DebugSetHeadAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/DebugSetHeadAcceptanceTest.java index b4db91a39aa..d6f6740d2b5 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/DebugSetHeadAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/DebugSetHeadAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,36 +15,33 @@ package org.hyperledger.besu.tests.acceptance.jsonrpc; import java.io.IOException; -import java.net.URI; import java.net.URISyntaxException; +import java.util.stream.Stream; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.provider.Arguments; -@RunWith(Parameterized.class) public class DebugSetHeadAcceptanceTest extends AbstractJsonRpcTest { private static final String GENESIS_FILE = "/jsonrpc/debug/setHead/genesis.json"; private static final String TEST_CASE_PATH = "/jsonrpc/debug/setHead/test-cases/"; private static JsonRpcTestsContext testsContext; - public DebugSetHeadAcceptanceTest(final String ignored, final URI testCaseFileURI) { - super(ignored, testsContext, testCaseFileURI); + public DebugSetHeadAcceptanceTest() { + super(testsContext); } - @BeforeClass + @BeforeAll public static void init() throws IOException { testsContext = new JsonRpcTestsContext(GENESIS_FILE); } - @Parameterized.Parameters(name = "{0}") - public static Iterable testCases() throws URISyntaxException { - return testCases(TEST_CASE_PATH); + public static Stream testCases() throws URISyntaxException { + return testCasesFromPath(TEST_CASE_PATH); } - @AfterClass + @AfterAll public static void tearDown() { testsContext.cluster.close(); } diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/EthEstimateGasAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/EthEstimateGasAcceptanceTest.java index b8b4e2e45f4..a09f44093ea 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/EthEstimateGasAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/EthEstimateGasAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -28,8 +28,8 @@ import java.util.ArrayList; import java.util.List; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class EthEstimateGasAcceptanceTest extends AcceptanceTestBase { @@ -38,7 +38,7 @@ public class EthEstimateGasAcceptanceTest extends AcceptanceTestBase { List> testCase = new ArrayList<>(); - @Before + @BeforeEach public void setUp() throws Exception { node = besu.createMinerNode( diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/EthGetBlockByNumberAndHashShanghaiAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/EthGetBlockByNumberAndHashShanghaiAcceptanceTest.java index f50e6c4a3bf..e3dab61ba6b 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/EthGetBlockByNumberAndHashShanghaiAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/EthGetBlockByNumberAndHashShanghaiAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,15 +15,13 @@ package org.hyperledger.besu.tests.acceptance.jsonrpc; import java.io.IOException; -import java.net.URI; import java.net.URISyntaxException; +import java.util.stream.Stream; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.provider.Arguments; -@RunWith(Parameterized.class) public class EthGetBlockByNumberAndHashShanghaiAcceptanceTest extends AbstractJsonRpcTest { private static final String TEST_RESOURCES_DIR = "/jsonrpc/eth/getBlockBy/"; private static final String GENESIS_FILE = TEST_RESOURCES_DIR + "genesis.json"; @@ -31,22 +29,20 @@ public class EthGetBlockByNumberAndHashShanghaiAcceptanceTest extends AbstractJs private static AbstractJsonRpcTest.JsonRpcTestsContext testsContext; - public EthGetBlockByNumberAndHashShanghaiAcceptanceTest( - final String ignored, final URI testCaseFileURI) { - super(ignored, testsContext, testCaseFileURI); + public EthGetBlockByNumberAndHashShanghaiAcceptanceTest() { + super(testsContext); } - @BeforeClass + @BeforeAll public static void init() throws IOException { testsContext = new JsonRpcTestsContext(GENESIS_FILE); } - @Parameterized.Parameters(name = "{0}") - public static Iterable testCases() throws URISyntaxException { - return testCases(TEST_CASE_PATH); + public static Stream testCases() throws URISyntaxException { + return testCasesFromPath(TEST_CASE_PATH); } - @AfterClass + @AfterAll public static void tearDown() { testsContext.cluster.close(); } diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/EthSendRawTransactionAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/EthSendRawTransactionAcceptanceTest.java index 4e4330da03b..30c32546c5a 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/EthSendRawTransactionAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/EthSendRawTransactionAcceptanceTest.java @@ -44,6 +44,17 @@ public void setUp() throws Exception { strictNode = besu.createArchiveNode("strictNode", configureNode((true))); miningNode = besu.createMinerNode("strictMiningNode", configureNode((true))); cluster.start(lenientNode, strictNode, miningNode); + + // verify nodes are fully connected otherwise tx could not be propagated + lenientNode.verify(net.awaitPeerCount(2)); + strictNode.verify(net.awaitPeerCount(2)); + miningNode.verify(net.awaitPeerCount(2)); + + // verify that the miner started producing blocks and all other nodes are syncing from it + waitForBlockHeight(miningNode, 1); + final var minerChainHead = miningNode.execute(ethTransactions.block()); + lenientNode.verify(blockchain.minimumHeight(minerChainHead.getNumber().longValue())); + strictNode.verify(blockchain.minimumHeight(minerChainHead.getNumber().longValue())); } @Test @@ -53,6 +64,7 @@ public void shouldSendSuccessfullyToLenientNodeWithoutChainId() { final String txHash = tx.transactionHash(); lenientNode.verify(eth.expectSuccessfulEthRawTransaction(rawTx)); + // Tx should be included on-chain miningNode.verify(eth.expectSuccessfulTransactionReceipt(txHash)); } diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/ExecutionEngineCancunBlockBuildingAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/ExecutionEngineCancunBlockBuildingAcceptanceTest.java index 7fe498e4c94..cf1d4715e34 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/ExecutionEngineCancunBlockBuildingAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/ExecutionEngineCancunBlockBuildingAcceptanceTest.java @@ -19,39 +19,35 @@ import org.hyperledger.besu.tests.acceptance.dsl.rpc.JsonRpcTestCase; import java.io.IOException; -import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.util.stream.Stream; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.JsonNodeType; import com.fasterxml.jackson.databind.node.ObjectNode; import okhttp3.Call; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.provider.Arguments; -@RunWith(Parameterized.class) public class ExecutionEngineCancunBlockBuildingAcceptanceTest extends AbstractJsonRpcTest { private static final String GENESIS_FILE = "/jsonrpc/engine/cancun/genesis.json"; private static final String TEST_CASE_PATH = "/jsonrpc/engine/cancun/test-cases/block-production"; private static JsonRpcTestsContext testsContext; - public ExecutionEngineCancunBlockBuildingAcceptanceTest( - final String ignored, final URI testCaseFileURI) { - super(ignored, testsContext, testCaseFileURI); + public ExecutionEngineCancunBlockBuildingAcceptanceTest() { + super(testsContext); } - @BeforeClass + @BeforeAll public static void init() throws IOException { testsContext = new JsonRpcTestsContext(GENESIS_FILE); } - @Parameterized.Parameters(name = "{0}") - public static Iterable testCases() throws URISyntaxException { - return testCases(TEST_CASE_PATH); + public static Stream testCases() throws URISyntaxException { + return testCasesFromPath(TEST_CASE_PATH); } @Override @@ -60,14 +56,6 @@ protected void evaluateResponse( final Call testRequest, final JsonRpcTestCase testCase, final URL url) { - if (url.toString().endsWith("10_cancun_build_on_genesis.json")) { - // if we just asked the node to build, give it some time to build - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } if (url.toString().endsWith("12_cancun_get_built_block.json")) { // final ObjectNode rpcResponse = JsonUtil.objectNodeFromString(response.body().string()); @@ -90,7 +78,7 @@ protected void evaluateResponse( } } - @AfterClass + @AfterAll public static void tearDown() { testsContext.cluster.close(); } diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/ExecutionEngineEip6110AcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/ExecutionEngineEip6110AcceptanceTest.java deleted file mode 100644 index d1771ac5990..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/ExecutionEngineEip6110AcceptanceTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright contributors to Hyperledger Besu. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.jsonrpc; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; - -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -@RunWith(Parameterized.class) -public class ExecutionEngineEip6110AcceptanceTest extends AbstractJsonRpcTest { - private static final String GENESIS_FILE = "/jsonrpc/engine/eip6110/genesis.json"; - private static final String TEST_CASE_PATH = "/jsonrpc/engine/eip6110/test-cases/"; - - private static JsonRpcTestsContext testsContext; - - public ExecutionEngineEip6110AcceptanceTest(final String ignored, final URI testCaseFileURI) { - super(ignored, testsContext, testCaseFileURI); - } - - @BeforeClass - public static void init() throws IOException { - testsContext = new JsonRpcTestsContext(GENESIS_FILE); - } - - @Parameterized.Parameters(name = "{0}") - public static Iterable testCases() throws URISyntaxException { - return testCases(TEST_CASE_PATH); - } - - @AfterClass - public static void tearDown() { - testsContext.cluster.close(); - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/ExecutionEngineParisAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/ExecutionEngineParisAcceptanceTest.java index c6cb8a62ea4..a0fdc9c3026 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/ExecutionEngineParisAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/ExecutionEngineParisAcceptanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,36 +15,33 @@ package org.hyperledger.besu.tests.acceptance.jsonrpc; import java.io.IOException; -import java.net.URI; import java.net.URISyntaxException; +import java.util.stream.Stream; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.provider.Arguments; -@RunWith(Parameterized.class) public class ExecutionEngineParisAcceptanceTest extends AbstractJsonRpcTest { private static final String GENESIS_FILE = "/jsonrpc/engine/paris/genesis.json"; private static final String TEST_CASE_PATH = "/jsonrpc/engine/paris/test-cases/"; private static JsonRpcTestsContext testsContext; - public ExecutionEngineParisAcceptanceTest(final String ignored, final URI testCaseFileURI) { - super(ignored, testsContext, testCaseFileURI); + public ExecutionEngineParisAcceptanceTest() { + super(testsContext); } - @BeforeClass + @BeforeAll public static void init() throws IOException { testsContext = new JsonRpcTestsContext(GENESIS_FILE); } - @Parameterized.Parameters(name = "{0}") - public static Iterable testCases() throws URISyntaxException { - return testCases(TEST_CASE_PATH); + public static Stream testCases() throws URISyntaxException { + return testCasesFromPath(TEST_CASE_PATH); } - @AfterClass + @AfterAll public static void tearDown() { testsContext.cluster.close(); } diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/ExecutionEnginePragueAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/ExecutionEnginePragueAcceptanceTest.java new file mode 100644 index 00000000000..966c02bebde --- /dev/null +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/ExecutionEnginePragueAcceptanceTest.java @@ -0,0 +1,48 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.jsonrpc; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.stream.Stream; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.provider.Arguments; + +public class ExecutionEnginePragueAcceptanceTest extends AbstractJsonRpcTest { + private static final String GENESIS_FILE = "/jsonrpc/engine/prague/genesis.json"; + private static final String TEST_CASE_PATH = "/jsonrpc/engine/prague/test-cases/"; + + private static JsonRpcTestsContext testsContext; + + public ExecutionEnginePragueAcceptanceTest() { + super(testsContext); + } + + @BeforeAll + public static void init() throws IOException { + testsContext = new JsonRpcTestsContext(GENESIS_FILE); + } + + public static Stream testCases() throws URISyntaxException { + return testCasesFromPath(TEST_CASE_PATH); + } + + @AfterAll + public static void tearDown() { + testsContext.cluster.close(); + } +} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/ExecutionEngineShanghaiAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/ExecutionEngineShanghaiAcceptanceTest.java index c676af5922b..85f966e3d2d 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/ExecutionEngineShanghaiAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/ExecutionEngineShanghaiAcceptanceTest.java @@ -15,36 +15,32 @@ package org.hyperledger.besu.tests.acceptance.jsonrpc; import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; +import java.util.stream.Stream; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.provider.Arguments; -@RunWith(Parameterized.class) public class ExecutionEngineShanghaiAcceptanceTest extends AbstractJsonRpcTest { private static final String GENESIS_FILE = "/jsonrpc/engine/shanghai/genesis.json"; private static final String TEST_CASE_PATH = "/jsonrpc/engine/shanghai/test-cases/"; private static JsonRpcTestsContext testsContext; - public ExecutionEngineShanghaiAcceptanceTest(final String ignored, final URI testCaseFileURI) { - super(ignored, testsContext, testCaseFileURI); + public ExecutionEngineShanghaiAcceptanceTest() { + super(testsContext); } - @BeforeClass + @BeforeAll public static void init() throws IOException { testsContext = new JsonRpcTestsContext(GENESIS_FILE); } - @Parameterized.Parameters(name = "{0}") - public static Iterable testCases() throws URISyntaxException { - return testCases(TEST_CASE_PATH); + public static Stream testCases() throws Exception { + return testCasesFromPath(TEST_CASE_PATH); } - @AfterClass + @AfterAll public static void tearDown() { testsContext.cluster.close(); } diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/ipc/Web3JSupportAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/ipc/Web3JSupportAcceptanceTest.java index c13c195cb33..b1887303ba2 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/ipc/Web3JSupportAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/jsonrpc/ipc/Web3JSupportAcceptanceTest.java @@ -29,8 +29,8 @@ import java.util.Collections; import java.util.Locale; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.web3j.protocol.Web3j; import org.web3j.protocol.core.Request; import org.web3j.protocol.core.methods.response.NetVersion; @@ -41,7 +41,7 @@ public class Web3JSupportAcceptanceTest extends AcceptanceTestBase { private Node node; private Path socketPath; - @Before + @BeforeEach public void setUp() throws Exception { socketPath = Files.createTempFile("besu-test-", ".ipc"); node = diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AccountLocalAndOnchainPermissioningAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AccountLocalAndOnchainPermissioningAcceptanceTest.java index 4b6401303be..1bd6d4cf968 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AccountLocalAndOnchainPermissioningAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AccountLocalAndOnchainPermissioningAcceptanceTest.java @@ -21,15 +21,17 @@ import java.math.BigInteger; import java.util.Arrays; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +@Disabled("permissioning tests flaky with timeouts") public class AccountLocalAndOnchainPermissioningAcceptanceTest extends AccountSmartContractPermissioningAcceptanceTestBase { private Account senderC; - @Before + @BeforeEach public void setUp() { senderC = accounts.createAccount("Account-C"); } diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AccountLocalConfigPermissioningAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AccountLocalConfigPermissioningAcceptanceTest.java index 78a9fee0413..4b163a4efc9 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AccountLocalConfigPermissioningAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AccountLocalConfigPermissioningAcceptanceTest.java @@ -22,8 +22,8 @@ import java.math.BigInteger; import java.util.Collections; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class AccountLocalConfigPermissioningAcceptanceTest extends AcceptanceTestBase { @@ -31,7 +31,7 @@ public class AccountLocalConfigPermissioningAcceptanceTest extends AcceptanceTes private Account senderA; private Account senderB; - @Before + @BeforeEach public void setUp() throws Exception { senderA = accounts.getPrimaryBenefactor(); senderB = accounts.getSecondaryBenefactor(); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AccountLocalConfigPermissioningImportAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AccountLocalConfigPermissioningImportAcceptanceTest.java index 4415a03f807..c3a2ab6e11a 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AccountLocalConfigPermissioningImportAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AccountLocalConfigPermissioningImportAcceptanceTest.java @@ -21,20 +21,19 @@ import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.Cluster; import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.ClusterConfigurationBuilder; -import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.List; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; public class AccountLocalConfigPermissioningImportAcceptanceTest extends AcceptanceTestBase { - @Rule public TemporaryFolder folder = new TemporaryFolder(); + @TempDir Path folder; private static final String GENESIS_FILE = "/ibft/ibft.json"; @@ -45,20 +44,20 @@ public class AccountLocalConfigPermissioningImportAcceptanceTest extends Accepta private BesuNode nodeB; private Cluster permissionedCluster; - @Before + @BeforeEach public void setUp() throws IOException { sender = accounts.getPrimaryBenefactor(); beneficiary = accounts.createAccount("beneficiary"); final List allowList = List.of(sender.getAddress(), beneficiary.getAddress()); - final File sharedFile = folder.newFile(); - persistAllowList(allowList, sharedFile.toPath()); + final Path sharedFile = Files.createFile(folder.resolve("sharedFile")); + persistAllowList(allowList, sharedFile); bootnode = besu.createIbft2NonValidatorBootnode("bootnode", GENESIS_FILE); nodeA = besu.createIbft2NodeWithLocalAccountPermissioning( - "nodeA", GENESIS_FILE, allowList, sharedFile); + "nodeA", GENESIS_FILE, allowList, sharedFile.toFile()); nodeB = besu.createIbft2NodeWithLocalAccountPermissioning( - "nodeB", GENESIS_FILE, allowList, sharedFile); + "nodeB", GENESIS_FILE, allowList, sharedFile.toFile()); permissionedCluster = new Cluster(new ClusterConfigurationBuilder().awaitPeerDiscovery(false).build(), net); @@ -67,12 +66,12 @@ public void setUp() throws IOException { @Test public void transactionFromDeniedAccountShouldNotBreakBlockImport() throws IOException { - final File newPermissionsFile = folder.newFile(); + final Path newPermissionsFile = Files.createFile(folder.resolve("newPermissionsFile")); final List allowList = List.of(beneficiary.getAddress()); - persistAllowList(allowList, newPermissionsFile.toPath()); + persistAllowList(allowList, newPermissionsFile); final BesuNode nodeC = besu.createIbft2NodeWithLocalAccountPermissioning( - "nodeC", GENESIS_FILE, allowList, newPermissionsFile); + "nodeC", GENESIS_FILE, allowList, newPermissionsFile.toFile()); waitForBlockHeight(bootnode, 2); @@ -91,7 +90,7 @@ private void persistAllowList(final List allowList, final Path path) thr AllowlistPersistor.ALLOWLIST_TYPE.ACCOUNTS, allowList, path); } - @After + @AfterEach public void tearDown() { permissionedCluster.stop(); } diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AccountSmartContractPermissioningAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AccountSmartContractPermissioningAcceptanceTest.java index 6d9e74e04a6..9009721d94d 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AccountSmartContractPermissioningAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AccountSmartContractPermissioningAcceptanceTest.java @@ -21,8 +21,8 @@ import java.math.BigInteger; import java.util.Collections; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class AccountSmartContractPermissioningAcceptanceTest extends AccountSmartContractPermissioningAcceptanceTestBase { @@ -31,7 +31,7 @@ public class AccountSmartContractPermissioningAcceptanceTest private Account allowedSender; private Account otherAccount; - @Before + @BeforeEach public void setUp() { node = permissionedNode("node1", Collections.emptyList()); permissionedCluster.start(node); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AllowlistPersistorAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AllowlistPersistorAcceptanceTest.java index 5a563cc5264..71258222d78 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AllowlistPersistorAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AllowlistPersistorAcceptanceTest.java @@ -26,8 +26,8 @@ import java.util.Collections; import org.assertj.core.api.Assertions; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class AllowlistPersistorAcceptanceTest extends AcceptanceTestBase { @@ -45,7 +45,7 @@ public class AllowlistPersistorAcceptanceTest extends AcceptanceTestBase { private Account senderB; private Path tempFile; - @Before + @BeforeEach public void setUp() throws Exception { senderA = accounts.getPrimaryBenefactor(); senderB = accounts.getSecondaryBenefactor(); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AllowlistWithDnsPersistorAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AllowlistWithDnsPersistorAcceptanceTest.java index 88c7e783777..08761ef6e07 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AllowlistWithDnsPersistorAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/AllowlistWithDnsPersistorAcceptanceTest.java @@ -31,8 +31,8 @@ import java.util.ArrayList; import java.util.Collections; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class AllowlistWithDnsPersistorAcceptanceTest extends AcceptanceTestBase { @@ -48,7 +48,7 @@ public class AllowlistWithDnsPersistorAcceptanceTest extends AcceptanceTestBase private Account senderA; private Path tempFile; - @Before + @BeforeEach public void setUp() throws Exception { ENODE_LOCALHOST_DNS = ENODE_PREFIX + InetAddress.getLocalHost().getHostName() + PORT_SUFFIX; ENODE_LOCALHOST_IP = ENODE_PREFIX + "127.0.0.1" + PORT_SUFFIX; diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeLocalAndOnchainPermissioningAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeLocalAndOnchainPermissioningAcceptanceTest.java index 6a7b8e9b35f..9271c0d977f 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeLocalAndOnchainPermissioningAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeLocalAndOnchainPermissioningAcceptanceTest.java @@ -16,9 +16,11 @@ import org.hyperledger.besu.tests.acceptance.dsl.node.Node; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +@Disabled("permissioning tests flaky with timeouts") public class NodeLocalAndOnchainPermissioningAcceptanceTest extends NodeSmartContractPermissioningAcceptanceTestBase { @@ -27,7 +29,7 @@ public class NodeLocalAndOnchainPermissioningAcceptanceTest private Node allowedNode; private Node forbiddenNode; - @Before + @BeforeEach public void setUp() { bootnode = bootnode("bootnode"); forbiddenNode = node("forbidden-node"); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeLocalConfigPermissioningAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeLocalConfigPermissioningAcceptanceTest.java index 7d8128d66c7..d889abb9fbb 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeLocalConfigPermissioningAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeLocalConfigPermissioningAcceptanceTest.java @@ -20,8 +20,8 @@ import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.ClusterConfiguration; import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.ClusterConfigurationBuilder; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class NodeLocalConfigPermissioningAcceptanceTest extends AcceptanceTestBase { @@ -31,7 +31,7 @@ public class NodeLocalConfigPermissioningAcceptanceTest extends AcceptanceTestBa private Node allowedNode; private Node permissionedNode; - @Before + @BeforeEach public void setUp() throws Exception { final ClusterConfiguration clusterConfiguration = new ClusterConfigurationBuilder().awaitPeerDiscovery(false).build(); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningAcceptanceTest.java index c5fbb693043..f1ed4e14902 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningAcceptanceTest.java @@ -16,8 +16,9 @@ import org.hyperledger.besu.tests.acceptance.dsl.node.Node; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; public class NodeSmartContractPermissioningAcceptanceTest extends NodeSmartContractPermissioningAcceptanceTestBase { @@ -27,7 +28,7 @@ public class NodeSmartContractPermissioningAcceptanceTest private Node allowedNode; private Node forbiddenNode; - @Before + @BeforeEach public void setUp() { bootnode = bootnode("bootnode"); forbiddenNode = node("forbidden-node"); @@ -37,9 +38,10 @@ public void setUp() { permissionedCluster.start(bootnode, forbiddenNode, allowedNode, permissionedNode); // updating permissioning smart contract with allowed nodes - + permissionedNode.verify(nodeIsForbidden(bootnode)); permissionedNode.execute(allowNode(bootnode)); permissionedNode.verify(nodeIsAllowed(bootnode)); + permissionedNode.verify(admin.hasPeer(bootnode)); permissionedNode.execute(allowNode(allowedNode)); permissionedNode.verify(nodeIsAllowed(allowedNode)); @@ -47,11 +49,16 @@ public void setUp() { permissionedNode.execute(allowNode(permissionedNode)); permissionedNode.verify(nodeIsAllowed(permissionedNode)); - permissionedNode.verify(admin.addPeer(bootnode)); permissionedNode.verify(admin.addPeer(allowedNode)); + + allowedNode.verify(eth.syncingStatus(false)); + bootnode.verify(eth.syncingStatus(false)); + permissionedNode.verify(eth.syncingStatus(false)); + forbiddenNode.verify(eth.syncingStatus(false)); } @Test + @Disabled("test is flaky") public void permissionedNodeShouldPeerOnlyWithAllowedNodes() { bootnode.verify(net.awaitPeerCount(3)); allowedNode.verify(net.awaitPeerCount(3)); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningIbft2StallAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningIbft2StallAcceptanceTest.java index e9da331a9e3..ed48ad9db97 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningIbft2StallAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningIbft2StallAcceptanceTest.java @@ -22,7 +22,7 @@ import java.io.IOException; import java.util.Optional; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class NodeSmartContractPermissioningIbft2StallAcceptanceTest extends NodeSmartContractPermissioningAcceptanceTestBase { diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningOutOfSyncAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningOutOfSyncAcceptanceTest.java index 2f8adf43867..de888904fdf 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningOutOfSyncAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningOutOfSyncAcceptanceTest.java @@ -16,8 +16,9 @@ import org.hyperledger.besu.tests.acceptance.dsl.node.Node; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; public class NodeSmartContractPermissioningOutOfSyncAcceptanceTest extends NodeSmartContractPermissioningAcceptanceTestBase { @@ -25,7 +26,7 @@ public class NodeSmartContractPermissioningOutOfSyncAcceptanceTest private Node permissionedNodeA; private Node permissionedNodeB; - @Before + @BeforeEach public void setUp() throws InterruptedException { bootnode = bootnode("bootnode"); permissionedNodeA = permissionedNode("permissioned-node-A"); @@ -42,18 +43,23 @@ public void setUp() throws InterruptedException { } @Test + @Disabled("test is flaky #7108") public void addNodeToClusterAndVerifyNonBootNodePeerConnectionWorksAfterSync() { final long blockchainHeight = 25L; waitForBlockHeight(permissionedNodeA, blockchainHeight); - // Add Node B + // verify Node A is in sync with bootnode + final var minerChainHead = bootnode.execute(ethTransactions.block()); + permissionedNodeA.verify(blockchain.minimumHeight(minerChainHead.getNumber().longValue())); + + // check that connection is forbidden (before node b is permitted) permissionedCluster.addNode(permissionedNodeB); + permissionedNodeB.verify(connectionIsForbidden(permissionedNodeA, permissionedNodeB)); + + // Permit Node B permissionedNodeA.execute(allowNode(permissionedNodeB)); permissionedNodeA.verify(admin.addPeer(permissionedNodeB)); - // check that connection is forbidden (while node b is syncing) - permissionedNodeB.verify(connectionIsForbidden(permissionedNodeA, permissionedNodeB)); - // connection should be allowed after node B syncs waitForBlockHeight(permissionedNodeB, blockchainHeight); permissionedNodeB.verify(connectionIsAllowed(permissionedNodeA, permissionedNodeB)); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningV2AcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningV2AcceptanceTest.java index 72aab9cb7e4..1f2cc682c69 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningV2AcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningV2AcceptanceTest.java @@ -16,8 +16,9 @@ import org.hyperledger.besu.tests.acceptance.dsl.node.Node; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; public class NodeSmartContractPermissioningV2AcceptanceTest extends NodeSmartContractPermissioningV2AcceptanceTestBase { @@ -27,7 +28,7 @@ public class NodeSmartContractPermissioningV2AcceptanceTest private Node allowedNode; private Node forbiddenNode; - @Before + @BeforeEach public void setUp() { bootnode = bootnode("bootnode"); forbiddenNode = node("forbidden-node"); @@ -36,6 +37,8 @@ public void setUp() { permissionedCluster.start(bootnode, forbiddenNode, allowedNode, permissionedNode); + verifyAllNodesAreInSyncWithMiner(); + // updating permissioning smart contract with allowed nodes permissionedNode.execute(allowNode(bootnode)); @@ -49,6 +52,7 @@ public void setUp() { } @Test + @Disabled("test is flaky") public void permissionedNodeShouldPeerOnlyWithAllowedNodes() { bootnode.verify(net.awaitPeerCount(3)); allowedNode.verify(net.awaitPeerCount(3)); @@ -58,7 +62,7 @@ public void permissionedNodeShouldPeerOnlyWithAllowedNodes() { @Test public void permissionedNodeShouldDisconnectFromNodeNotPermittedAnymore() { - permissionedNode.verify(admin.addPeer(bootnode)); + permissionedNode.verify(admin.hasPeer(bootnode)); permissionedNode.verify(admin.addPeer(allowedNode)); permissionedNode.verify(net.awaitPeerCount(2)); @@ -70,10 +74,12 @@ public void permissionedNodeShouldDisconnectFromNodeNotPermittedAnymore() { @Test public void permissionedNodeShouldConnectToNewlyPermittedNode() { - permissionedNode.verify(admin.addPeer(bootnode)); + permissionedNode.verify(admin.hasPeer(bootnode)); permissionedNode.verify(admin.addPeer(allowedNode)); permissionedNode.verify(net.awaitPeerCount(2)); + verifyAllNodesAreInSyncWithMiner(); + permissionedNode.execute(allowNode(forbiddenNode)); permissionedNode.verify(connectionIsAllowed(forbiddenNode)); permissionedNode.verify(admin.addPeer(forbiddenNode)); @@ -83,10 +89,12 @@ public void permissionedNodeShouldConnectToNewlyPermittedNode() { @Test public void permissioningUpdatesPropagateThroughNetwork() { - permissionedNode.verify(admin.addPeer(bootnode)); + permissionedNode.verify(admin.hasPeer(bootnode)); permissionedNode.verify(admin.addPeer(allowedNode)); permissionedNode.verify(net.awaitPeerCount(2)); + verifyAllNodesAreInSyncWithMiner(); + // permissioning changes in peer should propagate to permissioned node allowedNode.execute(allowNode(forbiddenNode)); allowedNode.verify(connectionIsAllowed(forbiddenNode)); @@ -96,11 +104,22 @@ public void permissioningUpdatesPropagateThroughNetwork() { permissionedNode.verify(net.awaitPeerCount(3)); } + private void verifyAllNodesAreInSyncWithMiner() { + // verify the miner (permissionedNode) started producing blocks and other nodes are syncing + // from it + waitForBlockHeight(permissionedNode, 1); + final var minerChainHead = permissionedNode.execute(ethTransactions.block()); + bootnode.verify(blockchain.minimumHeight(minerChainHead.getNumber().longValue())); + allowedNode.verify(blockchain.minimumHeight(minerChainHead.getNumber().longValue())); + } + @Test public void onchainPermissioningAllowlistShouldPersistAcrossRestarts() { permissionedCluster.stop(); permissionedCluster.start(bootnode, forbiddenNode, allowedNode, permissionedNode); + verifyAllNodesAreInSyncWithMiner(); + permissionedNode.verify(connectionIsAllowed(allowedNode)); permissionedNode.verify(connectionIsAllowed(bootnode)); permissionedNode.verify(connectionIsAllowed(permissionedNode)); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningV2AcceptanceTestBase.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningV2AcceptanceTestBase.java index 2cfbbf93deb..21afa801d0f 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningV2AcceptanceTestBase.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningV2AcceptanceTestBase.java @@ -93,14 +93,6 @@ protected Node node(final String name) { } } - protected Node miner(final String name) { - try { - return besu.createCustomGenesisNode(name, GENESIS_FILE, false, true); - } catch (IOException e) { - throw new RuntimeException("Error creating node", e); - } - } - @Override public void tearDownAcceptanceTestBase() { permissionedCluster.stop(); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningV2DNSAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningV2DNSAcceptanceTest.java index b2ed27d7c5d..435333a7760 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningV2DNSAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodeSmartContractPermissioningV2DNSAcceptanceTest.java @@ -24,9 +24,11 @@ import java.net.UnknownHostException; import org.assertj.core.api.Assertions; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +@Disabled("test is flaky #7191") public class NodeSmartContractPermissioningV2DNSAcceptanceTest extends NodeSmartContractPermissioningV2AcceptanceTestBase { @@ -38,7 +40,7 @@ public class NodeSmartContractPermissioningV2DNSAcceptanceTest final ImmutableEnodeDnsConfiguration enodeDnsConfiguration = ImmutableEnodeDnsConfiguration.builder().dnsEnabled(true).updateEnabled(true).build(); - @Before + @BeforeEach public void setUp() { bootnode = bootnode("bootnode"); forbiddenNode = node("forbidden-node"); @@ -57,12 +59,6 @@ public void setUp() { permissionedNode.execute(allowNode(permissionedNode)); permissionedNode.verify(connectionIsAllowed(permissionedNode)); - - // Verify initial configuration - bootnode.verify(net.awaitPeerCount(3)); - allowedNode.verify(net.awaitPeerCount(3)); - forbiddenNode.verify(net.awaitPeerCount(2)); - permissionedNode.verify(net.awaitPeerCount(2)); } @Test diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodesSmartContractPermissioningStaticNodesAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodesSmartContractPermissioningStaticNodesAcceptanceTest.java index 57253d474c2..898f0e47ad0 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodesSmartContractPermissioningStaticNodesAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/permissioning/NodesSmartContractPermissioningStaticNodesAcceptanceTest.java @@ -24,16 +24,18 @@ import java.util.List; import javax.annotation.Nonnull; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +@Disabled("flaky test #7155") public class NodesSmartContractPermissioningStaticNodesAcceptanceTest extends NodeSmartContractPermissioningAcceptanceTestBase { private Node miner; private Node permissionedNode; - @Before + @BeforeEach public void setUp() { miner = miner("miner"); permissionedCluster.start(miner); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/BadCLIOptionsPluginTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/BadCLIOptionsPluginTest.java index 02299f4fdb8..fb35aaacf54 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/BadCLIOptionsPluginTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/BadCLIOptionsPluginTest.java @@ -11,13 +11,12 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.tests.acceptance.plugins; import static org.assertj.core.api.Assertions.assertThat; -import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; +import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBaseJunit5; import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; import java.io.File; @@ -32,8 +31,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; -public class BadCLIOptionsPluginTest extends AcceptanceTestBase { +public class BadCLIOptionsPluginTest extends AcceptanceTestBaseJunit5 { private BesuNode node; @BeforeEach @@ -52,6 +53,7 @@ public void tearDown() { } @Test + @DisabledOnOs(OS.MAC) public void shouldNotRegister() { final Path registrationFile = node.homeDirectory().resolve("plugins/badCLIOptions.init"); waitForFile(registrationFile); @@ -59,6 +61,7 @@ public void shouldNotRegister() { } @Test + @DisabledOnOs(OS.MAC) public void shouldNotStart() { // depend on the good PicoCLIOptions to tell us when it should be up final Path registrationFile = node.homeDirectory().resolve("plugins/pluginLifecycle.started"); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/BesuEventsPluginTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/BesuEventsPluginTest.java index 4906193362c..a35309ef6f0 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/BesuEventsPluginTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/BesuEventsPluginTest.java @@ -14,7 +14,7 @@ */ package org.hyperledger.besu.tests.acceptance.plugins; -import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; +import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBaseJunit5; import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; import java.io.File; @@ -28,7 +28,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class BesuEventsPluginTest extends AcceptanceTestBase { +public class BesuEventsPluginTest extends AcceptanceTestBaseJunit5 { private BesuNode pluginNode; private BesuNode minerNode; diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/BlockchainServiceFinalizedBlockPluginTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/BlockchainServiceFinalizedBlockPluginTest.java new file mode 100644 index 00000000000..dd620844b13 --- /dev/null +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/BlockchainServiceFinalizedBlockPluginTest.java @@ -0,0 +1,158 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.plugins; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.config.JsonUtil; +import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; +import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +public class BlockchainServiceFinalizedBlockPluginTest extends AcceptanceTestBase { + + private BesuNode pluginNode; + private BesuNode minerNode; + private OkHttpClient client; + protected static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + + @BeforeEach + public void setUp() throws Exception { + minerNode = besu.createMinerNode("minerNode"); + pluginNode = + besu.createPluginsNode("node1", List.of("testPlugins"), List.of("--rpc-http-api=UPDATER")); + cluster.start(minerNode, pluginNode); + client = new OkHttpClient(); + } + + @Test + @DisplayName("Calling update{Finalized/Safe}BlockV1 will set block") + public void canUpdateFinalizedBlock() throws IOException { + pluginNode.verify(blockchain.minimumHeight(5)); + + // RPC Call. Set the safe block number to 3 + final ObjectNode resultJson = callTestMethod("updater_updateSafeBlockV1", List.of(3L)); + assertThat(resultJson.get("result").asBoolean()).isTrue(); + + // RPC Call. Set the finalized block number to 4 + final ObjectNode finalizedResultJson = + callTestMethod("updater_updateFinalizedBlockV1", List.of(4L)); + assertThat(finalizedResultJson.get("result").asBoolean()).isTrue(); + + final ObjectNode blockNumberSafeResult = + callTestMethod("eth_getBlockByNumber", List.of("SAFE", true)); + assertThat(blockNumberSafeResult.get("result").get("number").asText()).isEqualTo("0x3"); + + // Verify the value was set + final ObjectNode blockNumberFinalizedResult = + callTestMethod("eth_getBlockByNumber", List.of("FINALIZED", true)); + assertThat(blockNumberFinalizedResult.get("result").get("number").asText()).isEqualTo("0x4"); + } + + @Test + @DisplayName("Calling update{Finalized/Safe}BlockV1 with non-existing block number returns error") + public void nonExistingBlockNumberReturnsError() throws IOException { + pluginNode.verify(blockchain.minimumHeight(5)); + + final ObjectNode[] resultsJson = new ObjectNode[2]; + resultsJson[0] = callTestMethod("updater_updateFinalizedBlockV1", List.of(250L)); + resultsJson[1] = callTestMethod("updater_updateSafeBlockV1", List.of(250L)); + + for (int i = 0; i < resultsJson.length; i++) { + assertThat(resultsJson[i].get("error").get("code").asInt()).isEqualTo(-32000); + assertThat(resultsJson[i].get("error").get("message").asText()).isEqualTo("Block not found"); + assertThat(resultsJson[i].get("error").get("data").asText()) + .isEqualTo("Block not found in the local chain: 250"); + } + } + + @ParameterizedTest(name = "{index} - blockNumber={0}") + @ValueSource(longs = {-1, 0}) + @DisplayName("Calling update{Finalized/Safe}BlockV1 with block number <= 0 returns error") + public void invalidBlockNumberReturnsError(final long blockNumber) throws IOException { + pluginNode.verify(blockchain.minimumHeight(5)); + + final ObjectNode[] resultsJson = new ObjectNode[2]; + resultsJson[0] = callTestMethod("updater_updateFinalizedBlockV1", List.of(blockNumber)); + resultsJson[1] = callTestMethod("updater_updateSafeBlockV1", List.of(blockNumber)); + + for (int i = 0; i < resultsJson.length; i++) { + assertThat(resultsJson[i].get("error").get("code").asInt()).isEqualTo(-32602); + assertThat(resultsJson[i].get("error").get("message").asText()).isEqualTo("Invalid params"); + assertThat(resultsJson[i].get("error").get("data").asText()) + .isEqualTo("Block number must be greater than 0"); + } + } + + @Test + @DisplayName("Calling update{Finalized/Safe}BlockV1 with invalid block number type returns error") + public void invalidBlockNumberTypeReturnsError() throws IOException { + pluginNode.verify(blockchain.minimumHeight(5)); + + final ObjectNode[] resultsJson = new ObjectNode[2]; + resultsJson[0] = callTestMethod("updater_updateFinalizedBlockV1", List.of("testblock")); + resultsJson[1] = callTestMethod("updater_updateSafeBlockV1", List.of("testblock")); + + for (int i = 0; i < resultsJson.length; i++) { + assertThat(resultsJson[i].get("error").get("code").asInt()).isEqualTo(-32602); + assertThat(resultsJson[i].get("error").get("message").asText()).isEqualTo("Invalid params"); + assertThat(resultsJson[i].get("error").get("data").asText()) + .isEqualTo( + "Invalid json rpc parameter at index 0. Supplied value was: 'testblock' of type: 'java.lang.String' - expected type: 'java.lang.Long'"); + } + } + + private ObjectNode callTestMethod(final String method, final List params) + throws IOException { + String format = + String.format( + "{\"jsonrpc\":\"2.0\",\"method\":\"%s\",\"params\":[%s],\"id\":42}", + method, + params.stream().map(value -> "\"" + value + "\"").collect(Collectors.joining(","))); + + RequestBody body = RequestBody.create(format, JSON); + + final String resultString = + client + .newCall( + new Request.Builder() + .post(body) + .url( + "http://" + + pluginNode.getHostName() + + ":" + + pluginNode.getJsonRpcPort().get() + + "/") + .build()) + .execute() + .body() + .string(); + return JsonUtil.objectNodeFromString(resultString); + } +} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/InProcessRpcServicePluginTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/InProcessRpcServicePluginTest.java new file mode 100644 index 00000000000..4dbf8fd5d18 --- /dev/null +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/InProcessRpcServicePluginTest.java @@ -0,0 +1,51 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.tests.acceptance.plugins; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; +import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; + +import java.math.BigInteger; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class InProcessRpcServicePluginTest extends AcceptanceTestBase { + private static final long MIN_GAS_PRICE = 123456; + private BesuNode node; + + @BeforeEach + public void setUp() throws Exception { + node = + besu.createPluginsNode( + "node1", + List.of("testPlugins"), + List.of( + "--Xin-process-rpc-enabled=true", + "--Xin-process-rpc-apis=MINER", + "--plugin-test-set-min-gas-price=" + MIN_GAS_PRICE), + "MINER"); + cluster.start(node); + } + + @Test + public void smokeTest() { + final var currMinGasPrice = node.execute(minerTransactions.minerGetMinGasPrice()); + assertThat(currMinGasPrice).isEqualTo(BigInteger.valueOf(MIN_GAS_PRICE)); + } +} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/PermissioningPluginTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/PermissioningPluginTest.java index af6a3f7a16f..86756a42082 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/PermissioningPluginTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/PermissioningPluginTest.java @@ -12,11 +12,10 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.tests.acceptance.plugins; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; +import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBaseJunit5; import org.hyperledger.besu.tests.acceptance.dsl.account.Account; import org.hyperledger.besu.tests.acceptance.dsl.blockchain.Amount; import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; @@ -28,7 +27,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class PermissioningPluginTest extends AcceptanceTestBase { +public class PermissioningPluginTest extends AcceptanceTestBaseJunit5 { private BesuNode minerNode; private BesuNode aliceNode; @@ -37,22 +36,13 @@ public class PermissioningPluginTest extends AcceptanceTestBase { @BeforeEach public void setUp() throws Exception { - final BesuNodeConfigurationBuilder builder = - new BesuNodeConfigurationBuilder() - .miningEnabled(false) - .plugins(List.of("testPlugins")) - .extraCLIOptions(List.of("--plugin-permissioning-test-enabled=true")) - .jsonRpcEnabled() - .jsonRpcTxPool() - .jsonRpcAdmin(); - - minerNode = besu.create(builder.name("miner").build()); + minerNode = besu.create(createNodeBuilder().name("miner").build()); - aliceNode = besu.create(builder.name("alice").keyFilePath("key").build()); + aliceNode = besu.create(createNodeBuilder().name("alice").keyFilePath("key").build()); - bobNode = besu.create(builder.name("bob").keyFilePath("key1").build()); + bobNode = besu.create(createNodeBuilder().name("bob").keyFilePath("key1").build()); - charlieNode = besu.create(builder.name("charlie").keyFilePath("key2").build()); + charlieNode = besu.create(createNodeBuilder().name("charlie").keyFilePath("key2").build()); cluster.start(minerNode, charlieNode); @@ -63,6 +53,16 @@ public void setUp() throws Exception { bobNode.awaitPeerDiscovery(net.awaitPeerCount(2)); } + private BesuNodeConfigurationBuilder createNodeBuilder() { + return new BesuNodeConfigurationBuilder() + .miningEnabled(false) + .plugins(List.of("testPlugins")) + .extraCLIOptions(List.of("--plugin-permissioning-test-enabled=true")) + .jsonRpcEnabled() + .jsonRpcTxPool() + .jsonRpcAdmin(); + } + @Test public void blockedConnectionNodeCanOnlyConnectToTransactionNode() { minerNode.verify(admin.hasPeer(aliceNode)); diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/RpcEndpointServicePluginTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/RpcEndpointServicePluginTest.java index 09da91663af..5ad125719cf 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/RpcEndpointServicePluginTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/plugins/RpcEndpointServicePluginTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/BftPrivacyClusterAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/BftPrivacyClusterAcceptanceTest.java deleted file mode 100644 index 2baf1d1f5ce..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/BftPrivacyClusterAcceptanceTest.java +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.privacy; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyAcceptanceTestBase; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.account.PrivacyAccountResolver; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.bft.ConsensusType; -import org.hyperledger.besu.tests.web3j.generated.EventEmitter; -import org.hyperledger.enclave.testutil.EnclaveEncryptorType; -import org.hyperledger.enclave.testutil.EnclaveType; - -import java.io.IOException; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Optional; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.testcontainers.containers.Network; -import org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt; -import org.web3j.utils.Restriction; - -@RunWith(Parameterized.class) -public class BftPrivacyClusterAcceptanceTest extends PrivacyAcceptanceTestBase { - private final BftPrivacyType bftPrivacyType; - - public static class BftPrivacyType { - private final EnclaveType enclaveType; - private final EnclaveEncryptorType enclaveEncryptorType; - private final ConsensusType consensusType; - private final Restriction restriction; - - public BftPrivacyType( - final EnclaveType enclaveType, - final EnclaveEncryptorType enclaveEncryptorType, - final ConsensusType consensusType, - final Restriction restriction) { - this.enclaveType = enclaveType; - this.enclaveEncryptorType = enclaveEncryptorType; - this.consensusType = consensusType; - this.restriction = restriction; - } - - @Override - public String toString() { - return String.join( - ",", - enclaveType.toString(), - enclaveEncryptorType.toString(), - consensusType.toString(), - restriction.toString()); - } - } - - public BftPrivacyClusterAcceptanceTest(final BftPrivacyType bftPrivacyType) { - this.bftPrivacyType = bftPrivacyType; - } - - @Parameterized.Parameters(name = "{0}") - public static Collection bftPrivacyTypes() { - final List bftPrivacyTypes = new ArrayList<>(); - for (EnclaveType x : EnclaveType.valuesForTests()) { - for (ConsensusType consensusType : ConsensusType.values()) { - bftPrivacyTypes.add( - new BftPrivacyType( - x, EnclaveEncryptorType.NACL, consensusType, Restriction.RESTRICTED)); - bftPrivacyTypes.add( - new BftPrivacyType(x, EnclaveEncryptorType.EC, consensusType, Restriction.RESTRICTED)); - } - } - - for (ConsensusType consensusType : ConsensusType.values()) { - bftPrivacyTypes.add( - new BftPrivacyType( - EnclaveType.NOOP, - EnclaveEncryptorType.NOOP, - consensusType, - Restriction.UNRESTRICTED)); - } - - return bftPrivacyTypes; - } - - private PrivacyNode alice; - private PrivacyNode bob; - private PrivacyNode charlie; - - @Before - public void setUp() throws Exception { - final Network containerNetwork = Network.newNetwork(); - - alice = createNode(containerNetwork, "node1", 0); - bob = createNode(containerNetwork, "node2", 1); - charlie = createNode(containerNetwork, "node3", 2); - - privacyCluster.start(alice, bob, charlie); - } - - private PrivacyNode createNode( - final Network containerNetwork, final String nodeName, final int privacyAccount) - throws IOException { - if (bftPrivacyType.consensusType == ConsensusType.IBFT2) { - return privacyBesu.createIbft2NodePrivacyEnabled( - nodeName, - PrivacyAccountResolver.values()[privacyAccount].resolve( - bftPrivacyType.enclaveEncryptorType), - true, - bftPrivacyType.enclaveType, - Optional.of(containerNetwork), - false, - false, - bftPrivacyType.restriction == Restriction.UNRESTRICTED, - "0xAA"); - } else if (bftPrivacyType.consensusType == ConsensusType.QBFT) { - return privacyBesu.createQbftNodePrivacyEnabled( - nodeName, - PrivacyAccountResolver.values()[privacyAccount].resolve( - bftPrivacyType.enclaveEncryptorType), - bftPrivacyType.enclaveType, - Optional.of(containerNetwork), - false, - false, - bftPrivacyType.restriction == Restriction.UNRESTRICTED, - "0xAA"); - } else { - throw new IllegalStateException("Unknown consensus type " + bftPrivacyType.consensusType); - } - } - - @Test - public void onlyAliceAndBobCanExecuteContract() { - // Contract address is generated from sender address and transaction nonce - final String contractAddress = - EnclaveEncryptorType.EC.equals(bftPrivacyType.enclaveEncryptorType) - ? "0x3e5d325a03ad3ce5640502219833d30b89ce3ce1" - : "0xebf56429e6500e84442467292183d4d621359838"; - - final EventEmitter eventEmitter = - alice.execute( - privateContractTransactions.createSmartContract( - EventEmitter.class, - alice.getTransactionSigningKey(), - alice.getEnclaveKey(), - bob.getEnclaveKey())); - - privateContractVerifier - .validPrivateContractDeployed(contractAddress, alice.getAddress().toString()) - .verify(eventEmitter); - - final String transactionHash = - alice.execute( - privateContractTransactions.callSmartContract( - eventEmitter.getContractAddress(), - eventEmitter.store(BigInteger.ONE).encodeFunctionCall(), - alice.getTransactionSigningKey(), - bftPrivacyType.restriction, - alice.getEnclaveKey(), - bob.getEnclaveKey())); - - final PrivateTransactionReceipt expectedReceipt = - alice.execute(privacyTransactions.getPrivateTransactionReceipt(transactionHash)); - - bob.verify( - privateTransactionVerifier.validPrivateTransactionReceipt( - transactionHash, expectedReceipt)); - - if (bftPrivacyType.restriction != Restriction.UNRESTRICTED) { - charlie.verify(privateTransactionVerifier.noPrivateTransactionReceipt(transactionHash)); - } - } - - @Test - public void aliceCanDeployMultipleTimesInSingleGroup() { - final String firstDeployedAddress = - EnclaveEncryptorType.EC.equals(bftPrivacyType.enclaveEncryptorType) - ? "0x3e5d325a03ad3ce5640502219833d30b89ce3ce1" - : "0xebf56429e6500e84442467292183d4d621359838"; - - privacyCluster.stopNode(charlie); - - final EventEmitter firstEventEmitter = - alice.execute( - privateContractTransactions.createSmartContract( - EventEmitter.class, - alice.getTransactionSigningKey(), - alice.getEnclaveKey(), - bob.getEnclaveKey())); - - privateContractVerifier - .validPrivateContractDeployed(firstDeployedAddress, alice.getAddress().toString()) - .verify(firstEventEmitter); - - final String secondDeployedAddress = - EnclaveEncryptorType.EC.equals(bftPrivacyType.enclaveEncryptorType) - ? "0x5194e214fae257530710d18c868df7a295d9d53b" - : "0x10f807f8a905da5bd319196da7523c6bd768690f"; - - final EventEmitter secondEventEmitter = - alice.execute( - privateContractTransactions.createSmartContract( - EventEmitter.class, - alice.getTransactionSigningKey(), - alice.getEnclaveKey(), - bob.getEnclaveKey())); - - privateContractVerifier - .validPrivateContractDeployed(secondDeployedAddress, alice.getAddress().toString()) - .verify(secondEventEmitter); - } - - @Test - public void canInteractWithMultiplePrivacyGroups() { - // alice deploys contract - final String firstDeployedAddress = - EnclaveEncryptorType.EC.equals(bftPrivacyType.enclaveEncryptorType) - ? "0x760359bc605b3848f5199829bde6b382d90fb8eb" - : "0xff206d21150a8da5b83629d8a722f3135ed532b1"; - - final EventEmitter firstEventEmitter = - alice.execute( - privateContractTransactions.createSmartContract( - EventEmitter.class, - alice.getTransactionSigningKey(), - alice.getEnclaveKey(), - bob.getEnclaveKey(), - charlie.getEnclaveKey())); - - privateContractVerifier - .validPrivateContractDeployed(firstDeployedAddress, alice.getAddress().toString()) - .verify(firstEventEmitter); - - // charlie interacts with contract - final String firstTransactionHash = - charlie.execute( - privateContractTransactions.callSmartContract( - firstEventEmitter.getContractAddress(), - firstEventEmitter.store(BigInteger.ONE).encodeFunctionCall(), - charlie.getTransactionSigningKey(), - bftPrivacyType.restriction, - charlie.getEnclaveKey(), - alice.getEnclaveKey(), - bob.getEnclaveKey())); - - // alice gets receipt from charlie's interaction - final PrivateTransactionReceipt aliceReceipt = - alice.execute(privacyTransactions.getPrivateTransactionReceipt(firstTransactionHash)); - - // verify bob and charlie have access to the same receipt - bob.verify( - privateTransactionVerifier.validPrivateTransactionReceipt( - firstTransactionHash, aliceReceipt)); - charlie.verify( - privateTransactionVerifier.validPrivateTransactionReceipt( - firstTransactionHash, aliceReceipt)); - - // alice deploys second contract - final String secondDeployedAddress = - EnclaveEncryptorType.EC.equals(bftPrivacyType.enclaveEncryptorType) - ? "0x3e5d325a03ad3ce5640502219833d30b89ce3ce1" - : "0xebf56429e6500e84442467292183d4d621359838"; - - final EventEmitter secondEventEmitter = - alice.execute( - privateContractTransactions.createSmartContract( - EventEmitter.class, - alice.getTransactionSigningKey(), - alice.getEnclaveKey(), - bob.getEnclaveKey())); - - privateContractVerifier - .validPrivateContractDeployed(secondDeployedAddress, alice.getAddress().toString()) - .verify(secondEventEmitter); - - // bob interacts with contract - final String secondTransactionHash = - bob.execute( - privateContractTransactions.callSmartContract( - secondEventEmitter.getContractAddress(), - secondEventEmitter.store(BigInteger.ONE).encodeFunctionCall(), - bob.getTransactionSigningKey(), - bftPrivacyType.restriction, - bob.getEnclaveKey(), - alice.getEnclaveKey())); - - // alice gets receipt from bob's interaction - final PrivateTransactionReceipt secondExpectedReceipt = - alice.execute(privacyTransactions.getPrivateTransactionReceipt(secondTransactionHash)); - - bob.verify( - privateTransactionVerifier.validPrivateTransactionReceipt( - secondTransactionHash, secondExpectedReceipt)); - - // charlie cannot see the receipt - if (bftPrivacyType.restriction != Restriction.UNRESTRICTED) { - charlie.verify(privateTransactionVerifier.noPrivateTransactionReceipt(secondTransactionHash)); - } - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/DeployPrivateSmartContractAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/DeployPrivateSmartContractAcceptanceTest.java deleted file mode 100644 index 71aa618cdd8..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/DeployPrivateSmartContractAcceptanceTest.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.privacy; - -import static org.web3j.utils.Restriction.UNRESTRICTED; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.ParameterizedEnclaveTestBase; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.account.PrivacyAccountResolver; -import org.hyperledger.besu.tests.web3j.generated.EventEmitter; -import org.hyperledger.enclave.testutil.EnclaveEncryptorType; -import org.hyperledger.enclave.testutil.EnclaveType; - -import java.io.IOException; -import java.util.Optional; - -import org.junit.Test; -import org.web3j.utils.Restriction; - -public class DeployPrivateSmartContractAcceptanceTest extends ParameterizedEnclaveTestBase { - - private final PrivacyNode minerNode; - - public DeployPrivateSmartContractAcceptanceTest( - final Restriction restriction, - final EnclaveType enclaveType, - final EnclaveEncryptorType enclaveEncryptorType) - throws IOException { - super(restriction, enclaveType, enclaveEncryptorType); - - minerNode = - privacyBesu.createPrivateTransactionEnabledMinerNode( - restriction + "-node", - PrivacyAccountResolver.ALICE.resolve(enclaveEncryptorType), - enclaveType, - Optional.empty(), - false, - false, - restriction == UNRESTRICTED); - - privacyCluster.start(minerNode); - } - - @Test - public void deployingMustGiveValidReceiptAndCode() throws Exception { - final String contractAddress = - EnclaveEncryptorType.EC.equals(enclaveEncryptorType) - ? "0xfeeb2367e77e28f75fc3bcc55b70a535752db058" - : "0x89ce396d0f9f937ddfa71113e29b2081c4869555"; - - final EventEmitter eventEmitter = - minerNode.execute( - privateContractTransactions.createSmartContract( - EventEmitter.class, - minerNode.getTransactionSigningKey(), - minerNode.getEnclaveKey())); - - privateContractVerifier - .validPrivateContractDeployed(contractAddress, minerNode.getAddress().toString()) - .verify(eventEmitter); - - privateContractVerifier.validContractCodeProvided().verify(eventEmitter); - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/EnclaveErrorAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/EnclaveErrorAcceptanceTest.java deleted file mode 100644 index 266284a498e..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/EnclaveErrorAcceptanceTest.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.privacy; - -import static org.assertj.core.api.Assertions.catchThrowable; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.hyperledger.enclave.testutil.EnclaveEncryptorType.EC; -import static org.hyperledger.enclave.testutil.EnclaveEncryptorType.NACL; -import static org.hyperledger.enclave.testutil.EnclaveType.TESSERA; - -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyAcceptanceTestBase; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.account.PrivacyAccountResolver; -import org.hyperledger.besu.tests.web3j.generated.EventEmitter; -import org.hyperledger.enclave.testutil.EnclaveEncryptorType; -import org.hyperledger.enclave.testutil.EnclaveType; - -import java.io.IOException; -import java.math.BigInteger; -import java.security.KeyPairGenerator; -import java.security.spec.ECGenParameterSpec; -import java.util.Arrays; -import java.util.Base64; -import java.util.Collection; -import java.util.Optional; - -import org.apache.tuweni.crypto.sodium.Box; -import org.assertj.core.api.Condition; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; -import org.testcontainers.containers.Network; -import org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt; -import org.web3j.utils.Restriction; - -@RunWith(Parameterized.class) -public class EnclaveErrorAcceptanceTest extends PrivacyAcceptanceTestBase { - - private final PrivacyNode alice; - private final PrivacyNode bob; - private final String wrongPublicKey; - - @Parameters(name = "{0} enclave type with {1} encryptor") - public static Collection enclaveParameters() { - return Arrays.asList( - new Object[][] { - {TESSERA, NACL}, - {TESSERA, EC} - }); - } - - public EnclaveErrorAcceptanceTest( - final EnclaveType enclaveType, final EnclaveEncryptorType enclaveEncryptorType) - throws IOException { - - final Network containerNetwork = Network.newNetwork(); - - alice = - privacyBesu.createIbft2NodePrivacyEnabled( - "node1", - PrivacyAccountResolver.ALICE.resolve(enclaveEncryptorType), - false, - enclaveType, - Optional.of(containerNetwork), - false, - false, - false, - "0xAA"); - bob = - privacyBesu.createIbft2NodePrivacyEnabled( - "node2", - PrivacyAccountResolver.BOB.resolve(enclaveEncryptorType), - false, - enclaveType, - Optional.of(containerNetwork), - false, - false, - false, - "0xBB"); - privacyCluster.start(alice, bob); - - final byte[] wrongPublicKeyBytes = - EnclaveEncryptorType.EC.equals(enclaveEncryptorType) - ? getSECP256r1PublicKeyByteArray() - : Box.KeyPair.random().publicKey().bytesArray(); - - wrongPublicKey = Base64.getEncoder().encodeToString(wrongPublicKeyBytes); - } - - @Test - public void aliceCannotSendTransactionFromBobNode() { - final Throwable throwable = - catchThrowable( - () -> - alice.execute( - privateContractTransactions.createSmartContract( - EventEmitter.class, - alice.getTransactionSigningKey(), - wrongPublicKey, - bob.getEnclaveKey()))); - - assertThat(throwable) - .hasMessageContaining( - RpcErrorType.PRIVATE_FROM_DOES_NOT_MATCH_ENCLAVE_PUBLIC_KEY.getMessage()); - } - - @Test - public void enclaveNoPeerUrlError() { - final Throwable throwable = - catchThrowable( - () -> - alice.execute( - privateContractTransactions.createSmartContract( - EventEmitter.class, - alice.getTransactionSigningKey(), - alice.getEnclaveKey(), - wrongPublicKey))); - - final String tesseraMessage = RpcErrorType.TESSERA_NODE_MISSING_PEER_URL.getMessage(); - - assertThat(throwable.getMessage()).has(matchTesseraEnclaveMessage(tesseraMessage)); - } - - @Test - public void whenEnclaveIsDisconnectedGetReceiptReturnsInternalError() { - final EventEmitter eventEmitter = - alice.execute( - privateContractTransactions.createSmartContract( - EventEmitter.class, - alice.getTransactionSigningKey(), - alice.getEnclaveKey(), - bob.getEnclaveKey())); - - privateContractVerifier - .validPrivateContractDeployed( - eventEmitter.getContractAddress(), alice.getAddress().toString()) - .verify(eventEmitter); - - final String transactionHash = - alice.execute( - privateContractTransactions.callSmartContract( - eventEmitter.getContractAddress(), - eventEmitter.store(BigInteger.ONE).encodeFunctionCall(), - alice.getTransactionSigningKey(), - Restriction.RESTRICTED, - alice.getEnclaveKey(), - bob.getEnclaveKey())); - - final PrivateTransactionReceipt receiptBeforeEnclaveLosesConnection = - alice.execute(privacyTransactions.getPrivateTransactionReceipt(transactionHash)); - - alice.verify( - privateTransactionVerifier.validPrivateTransactionReceipt( - transactionHash, receiptBeforeEnclaveLosesConnection)); - - alice.getEnclave().stop(); - - alice.verify( - privateTransactionVerifier.internalErrorPrivateTransactionReceipt(transactionHash)); - } - - @Test - @Ignore("Web3J is broken by PR #1426") - public void transactionFailsIfPartyIsOffline() { - // Contract address is generated from sender address and transaction nonce - final String contractAddress = "0xebf56429e6500e84442467292183d4d621359838"; - - final EventEmitter eventEmitter = - alice.execute( - privateContractTransactions.createSmartContract( - EventEmitter.class, - alice.getTransactionSigningKey(), - alice.getEnclaveKey(), - bob.getEnclaveKey())); - - privateContractVerifier - .validPrivateContractDeployed(contractAddress, alice.getAddress().toString()) - .verify(eventEmitter); - - bob.getEnclave().stop(); - - final Throwable throwable = - catchThrowable( - () -> - alice.execute( - privateContractTransactions.callSmartContract( - eventEmitter.getContractAddress(), - eventEmitter.store(BigInteger.ONE).encodeFunctionCall(), - alice.getTransactionSigningKey(), - Restriction.RESTRICTED, - alice.getEnclaveKey(), - bob.getEnclaveKey()))); - - assertThat(throwable).hasMessageContaining("NodePropagatingToAllPeers"); - } - - @Test - public void createPrivacyGroupReturnsCorrectError() { - final Throwable throwable = - catchThrowable(() -> alice.execute(privacyTransactions.createPrivacyGroup(null, null))); - final String tesseraMessage = RpcErrorType.TESSERA_CREATE_GROUP_INCLUDE_SELF.getMessage(); - - assertThat(throwable.getMessage()).has(matchTesseraEnclaveMessage(tesseraMessage)); - } - - private Condition matchTesseraEnclaveMessage(final String enclaveMessage) { - return new Condition<>( - message -> message.contains(enclaveMessage), - "Message did not match Tessera expected output"); - } - - private byte[] getSECP256r1PublicKeyByteArray() { - try { - final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC"); - final ECGenParameterSpec spec = new ECGenParameterSpec("secp256r1"); - keyGen.initialize(spec); - return keyGen.generateKeyPair().getPublic().getEncoded(); - } catch (Exception exception) { - return new byte[0]; - } - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/FlexiblePrivacyAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/FlexiblePrivacyAcceptanceTest.java deleted file mode 100644 index 3f32904ba76..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/FlexiblePrivacyAcceptanceTest.java +++ /dev/null @@ -1,638 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.privacy; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.hyperledger.besu.ethereum.core.PrivacyParameters.FLEXIBLE_PRIVACY_PROXY; -import static org.hyperledger.enclave.testutil.EnclaveEncryptorType.EC; -import static org.hyperledger.enclave.testutil.EnclaveEncryptorType.NACL; -import static org.hyperledger.enclave.testutil.EnclaveType.TESSERA; -import static org.junit.runners.Parameterized.Parameters; - -import org.hyperledger.besu.tests.acceptance.dsl.condition.eth.EthConditions; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.account.PrivacyAccountResolver; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.miner.MinerTransactions; -import org.hyperledger.besu.tests.web3j.generated.EventEmitter; -import org.hyperledger.enclave.testutil.EnclaveEncryptorType; -import org.hyperledger.enclave.testutil.EnclaveType; - -import java.math.BigInteger; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.function.BiConsumer; -import java.util.function.Supplier; - -import com.google.common.collect.Lists; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.testcontainers.containers.Network; -import org.web3j.crypto.Credentials; -import org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt; -import org.web3j.protocol.core.methods.response.EthCall; -import org.web3j.protocol.core.methods.response.Log; -import org.web3j.protocol.core.methods.response.TransactionReceipt; -import org.web3j.tx.Contract; - -@RunWith(Parameterized.class) -public class FlexiblePrivacyAcceptanceTest extends FlexiblePrivacyAcceptanceTestBase { - - private final EnclaveType enclaveType; - private final EnclaveEncryptorType enclaveEncryptorType; - - public FlexiblePrivacyAcceptanceTest( - final EnclaveType enclaveType, final EnclaveEncryptorType enclaveEncryptorType) { - this.enclaveType = enclaveType; - this.enclaveEncryptorType = enclaveEncryptorType; - } - - @Parameters(name = "{0} enclave type with {1} encryptor") - public static Collection enclaveParameters() { - return Arrays.asList( - new Object[][] { - {TESSERA, NACL}, - {TESSERA, EC} - }); - } - - private PrivacyNode alice; - private PrivacyNode bob; - private PrivacyNode charlie; - - private final MinerTransactions minerTransactions = new MinerTransactions(); - private final EthConditions ethConditions = new EthConditions(ethTransactions); - - private static final String EXPECTED_STORE_OUTPUT_DATA = - "0x000000000000000000000000f17f52151ebef6c7334fad080c5704d77216b7320000000000000000000000000000000000000000000000000000000000000539"; - private static final String EXPECTED_STORE_EVENT_TOPIC = - "0xc9db20adedc6cf2b5d25252b101ab03e124902a73fcb12b753f3d1aaa2d8f9f5"; - - @Before - public void setUp() throws Exception { - final Network containerNetwork = Network.newNetwork(); - - alice = - privacyBesu.createFlexiblePrivacyGroupEnabledMinerNode( - "node1", - PrivacyAccountResolver.ALICE.resolve(enclaveEncryptorType), - false, - enclaveType, - Optional.of(containerNetwork)); - bob = - privacyBesu.createFlexiblePrivacyGroupEnabledNode( - "node2", - PrivacyAccountResolver.BOB.resolve(enclaveEncryptorType), - false, - enclaveType, - Optional.of(containerNetwork)); - charlie = - privacyBesu.createFlexiblePrivacyGroupEnabledNode( - "node3", - PrivacyAccountResolver.CHARLIE.resolve(enclaveEncryptorType), - false, - enclaveType, - Optional.of(containerNetwork)); - privacyCluster.start(alice, bob, charlie); - } - - @Test - public void nodeCanCreatePrivacyGroup() { - final String privacyGroupId = createFlexiblePrivacyGroup(alice); - checkFlexiblePrivacyGroupExists(privacyGroupId, alice); - } - - @Test - public void deployingMustGiveValidReceipt() { - final String privacyGroupId = createFlexiblePrivacyGroup(alice, bob); - final Contract eventEmitter = deployPrivateContract(EventEmitter.class, privacyGroupId, alice); - final String commitmentHash = getContractDeploymentCommitmentHash(eventEmitter); - - alice.verify(privateTransactionVerifier.existingPrivateTransactionReceipt(commitmentHash)); - bob.verify(privateTransactionVerifier.existingPrivateTransactionReceipt(commitmentHash)); - } - - @Test - public void canAddParticipantToGroup() { - final String privacyGroupId = createFlexiblePrivacyGroup(alice, bob); - final Contract eventEmitter = deployPrivateContract(EventEmitter.class, privacyGroupId, alice); - final String commitmentHash = getContractDeploymentCommitmentHash(eventEmitter); - - charlie.verify(privateTransactionVerifier.noPrivateTransactionReceipt(commitmentHash)); - - final Credentials aliceCredentials = Credentials.create(alice.getTransactionSigningKey()); - lockPrivacyGroup(privacyGroupId, alice, aliceCredentials); - addMembersToPrivacyGroup(privacyGroupId, alice, aliceCredentials, charlie); - checkFlexiblePrivacyGroupExists(privacyGroupId, alice, bob, charlie); - - charlie.verify(privateTransactionVerifier.existingPrivateTransactionReceipt(commitmentHash)); - } - - @Test - public void removedMemberCannotSendTransactionToGroup() { - final String privacyGroupId = createFlexiblePrivacyGroup(alice, bob); - - final String removeHash = - removeFromPrivacyGroup( - privacyGroupId, alice, Credentials.create(alice.getTransactionSigningKey()), bob); - - bob.verify(privateTransactionVerifier.existingPrivateTransactionReceipt(removeHash)); - - assertThatThrownBy(() -> deployPrivateContract(EventEmitter.class, privacyGroupId, bob)) - .isInstanceOf(RuntimeException.class) - .hasMessageContaining("Flexible Privacy group does not exist."); - } - - @Test - public void canInteractWithPrivateGenesisPreCompile() throws Exception { - final String privacyGroupId = createFlexiblePrivacyGroup(alice, bob); - - final EventEmitter eventEmitter = - alice.execute( - privateContractTransactions.loadSmartContractWithPrivacyGroupId( - "0x1000000000000000000000000000000000000001", - EventEmitter.class, - alice.getTransactionSigningKey(), - alice.getEnclaveKey(), - privacyGroupId)); - - privateTransactionVerifier.existingPrivateTransactionReceipt( - eventEmitter.store(BigInteger.valueOf(42)).send().getTransactionHash()); - - final String aliceResponse = - alice - .execute( - privacyTransactions.privCall( - privacyGroupId, eventEmitter, eventEmitter.value().encodeFunctionCall())) - .getValue(); - - assertThat(new BigInteger(aliceResponse.substring(2), 16)) - .isEqualByComparingTo(BigInteger.valueOf(42)); - - final String bobResponse = - bob.execute( - privacyTransactions.privCall( - privacyGroupId, eventEmitter, eventEmitter.value().encodeFunctionCall())) - .getValue(); - - assertThat(new BigInteger(bobResponse.substring(2), 16)) - .isEqualByComparingTo(BigInteger.valueOf(42)); - - final String charlieResponse = - charlie - .execute( - privacyTransactions.privCall( - privacyGroupId, eventEmitter, eventEmitter.value().encodeFunctionCall())) - .getValue(); - - assertThat(charlieResponse).isEqualTo("0x"); - } - - @Test - public void memberCanBeAddedAfterBeingRemoved() { - final String privacyGroupId = createFlexiblePrivacyGroup(alice); - - checkFlexiblePrivacyGroupExists(privacyGroupId, alice); - - lockPrivacyGroup(privacyGroupId, alice, Credentials.create(alice.getTransactionSigningKey())); - addMembersToPrivacyGroup( - privacyGroupId, alice, Credentials.create(alice.getTransactionSigningKey()), bob); - - checkFlexiblePrivacyGroupExists(privacyGroupId, alice, bob); - - final EventEmitter eventEmitter = - deployPrivateContract(EventEmitter.class, privacyGroupId, alice); - - final int valueSetWhileBobWasMember = 17; - final PrivateTransactionReceipt receiptWhileBobMember = - setEventEmitterValueAndCheck( - Lists.newArrayList(alice, bob), - privacyGroupId, - eventEmitter, - valueSetWhileBobWasMember); - - removeFromPrivacyGroup( - privacyGroupId, alice, Credentials.create(alice.getTransactionSigningKey()), bob); - - checkFlexiblePrivacyGroupExists(privacyGroupId, alice); - - final int valueSetWhileBobWas_NOT_aMember = 1337; - final PrivateTransactionReceipt receiptWhileBobRemoved = - setEventEmitterValueAndCheck( - Lists.newArrayList(alice), - privacyGroupId, - eventEmitter, - valueSetWhileBobWas_NOT_aMember); - checkEmitterValue( - Lists.newArrayList(bob), - privacyGroupId, - eventEmitter, - valueSetWhileBobWasMember); // bob did not get the last transaction - - lockPrivacyGroup(privacyGroupId, alice, Credentials.create(alice.getTransactionSigningKey())); - addMembersToPrivacyGroup( - privacyGroupId, alice, Credentials.create(alice.getTransactionSigningKey()), bob); - - checkFlexiblePrivacyGroupExists(privacyGroupId, alice, bob); - - checkEmitterValue( - Lists.newArrayList(alice, bob), - privacyGroupId, - eventEmitter, - valueSetWhileBobWas_NOT_aMember); - - PrivateTransactionReceipt receipt = - bob.execute( - privacyTransactions.getPrivateTransactionReceipt( - receiptWhileBobRemoved.getcommitmentHash())); - assertThat(receipt.getStatus()).isEqualTo("0x1"); - - receipt = - bob.execute( - privacyTransactions.getPrivateTransactionReceipt( - receiptWhileBobMember.getcommitmentHash())); - assertThat(receipt.getStatus()).isEqualTo("0x1"); - } - - PrivateTransactionReceipt setEventEmitterValueAndCheck( - final List nodes, - final String privacyGroupId, - final EventEmitter eventEmitter, - final int value) { - final PrivateTransactionReceipt receiptWhileBobMember = - setEventEmitterValue(nodes, privacyGroupId, eventEmitter, value); - - checkEmitterValue(nodes, privacyGroupId, eventEmitter, value); - return receiptWhileBobMember; - } - - private PrivateTransactionReceipt setEventEmitterValue( - final List nodes, - final String privacyGroupId, - final EventEmitter eventEmitter, - final int value) { - final PrivacyNode firstNode = nodes.get(0); - final String txHash = - firstNode.execute( - privateContractTransactions.callOnchainPermissioningSmartContract( - eventEmitter.getContractAddress(), - eventEmitter.store(BigInteger.valueOf(value)).encodeFunctionCall(), - firstNode.getTransactionSigningKey(), - firstNode.getEnclaveKey(), - privacyGroupId)); - PrivateTransactionReceipt receipt = null; - for (final PrivacyNode node : nodes) { - receipt = node.execute(privacyTransactions.getPrivateTransactionReceipt(txHash)); - assertThat(receipt.getStatus()).isEqualTo("0x1"); - } - return receipt; - } - - private void checkEmitterValue( - final List nodes, - final String privacyGroupId, - final EventEmitter eventEmitter, - final int expectedValue) { - for (final PrivacyNode node : nodes) { - final EthCall response = - node.execute( - privacyTransactions.privCall( - privacyGroupId, eventEmitter, eventEmitter.value().encodeFunctionCall())); - - assertThat(new BigInteger(response.getValue().substring(2), 16)) - .isEqualByComparingTo(BigInteger.valueOf(expectedValue)); - } - } - - @Test - public void bobCanAddCharlieAfterBeingAddedByAlice() { - final String privacyGroupId = createFlexiblePrivacyGroup(alice); - checkFlexiblePrivacyGroupExists(privacyGroupId, alice); - final EventEmitter eventEmitter = - alice.execute( - privateContractTransactions.createSmartContractWithPrivacyGroupId( - EventEmitter.class, - alice.getTransactionSigningKey(), - alice.getEnclaveKey(), - privacyGroupId)); - - privateContractVerifier - .validPrivateContractDeployed( - eventEmitter.getContractAddress(), alice.getAddress().toString()) - .verify(eventEmitter); - - final Credentials aliceCredentials = Credentials.create(alice.getTransactionSigningKey()); - final String aliceLockHash = - alice.execute( - privacyTransactions.privxLockPrivacyGroupAndCheck( - privacyGroupId, alice, aliceCredentials)); - - alice.execute( - privacyTransactions.addToPrivacyGroup(privacyGroupId, alice, aliceCredentials, bob)); - - checkFlexiblePrivacyGroupExists(privacyGroupId, alice, bob); - - bob.execute( - privacyTransactions.privxLockPrivacyGroupAndCheck(privacyGroupId, bob, aliceCredentials)); - - alice.execute(minerTransactions.minerStop()); - - alice.getBesu().verify(ethConditions.miningStatus(false)); - - final BigInteger pendingTransactionFilterId = - alice.execute(ethTransactions.newPendingTransactionsFilter()); - - final String callHash = - alice.execute( - privateContractTransactions.callOnchainPermissioningSmartContract( - eventEmitter.getContractAddress(), - eventEmitter.value().encodeFunctionCall(), - alice.getTransactionSigningKey(), - alice.getEnclaveKey(), - privacyGroupId)); - - final String bobAddHash = - addMembersToPrivacyGroup(privacyGroupId, bob, aliceCredentials, charlie); - - alice - .getBesu() - .verify( - ethConditions.expectNewPendingTransactions( - pendingTransactionFilterId, Arrays.asList(callHash, bobAddHash))); - - alice.execute(minerTransactions.minerStart()); - - alice.getBesu().verify(ethConditions.miningStatus(true)); - - checkFlexiblePrivacyGroupExists(privacyGroupId, alice, bob, charlie); - - final Optional aliceAddReceipt = - alice.execute(ethTransactions.getTransactionReceipt(bobAddHash)); - assertThat(aliceAddReceipt.get().getStatus()) - .isEqualTo("0x1"); // this means the PMT for the "add" succeeded which is what we expect - - final Optional alicePublicReceipt = - alice.execute(ethTransactions.getTransactionReceipt(callHash)); - if (alicePublicReceipt.isPresent()) { - assertThat(alicePublicReceipt.get().getBlockHash()) - .isEqualTo( - aliceAddReceipt - .get() - .getBlockHash()); // ensure that "add" and "call" are in the same block - assertThat(alicePublicReceipt.get().getStatus()) - .isEqualTo( - "0x1"); // this means the PMT for the "call" succeeded which is what we expect because - // it is in the same block as the "add" and there is no way to tell that this - // will happen before the block is mined - } - - final PrivateTransactionReceipt aliceReceipt = - alice.execute(privacyTransactions.getPrivateTransactionReceipt(callHash)); - assertThat(aliceReceipt.getStatus()) - .isEqualTo( - "0x0"); // this means the "call" failed which is what we expect because the group was - // locked! - final PrivateTransactionReceipt bobReceipt = - alice.execute(privacyTransactions.getPrivateTransactionReceipt(callHash)); - assertThat(bobReceipt.getStatus()) - .isEqualTo( - "0x0"); // this means the "call" failed which is what we expect because the group was - // locked! - - // assert charlie can access private transaction information from before he was added - final PrivateTransactionReceipt expectedAliceLockReceipt = - new PrivateTransactionReceipt( - null, - alice.getAddress().toHexString(), - FLEXIBLE_PRIVACY_PROXY.toHexString(), - "0x", - Collections.emptyList(), - null, - null, - alice.getEnclaveKey(), - null, - privacyGroupId, - "0x1", - null); - charlie.verify( - privateTransactionVerifier.validPrivateTransactionReceipt( - aliceLockHash, expectedAliceLockReceipt)); - - final String aliceStoreHash = - charlie.execute( - privateContractTransactions.callOnchainPermissioningSmartContract( - eventEmitter.getContractAddress(), - eventEmitter.store(BigInteger.valueOf(1337)).encodeFunctionCall(), - charlie.getTransactionSigningKey(), - charlie.getEnclaveKey(), - privacyGroupId)); - - final PrivateTransactionReceipt expectedStoreReceipt = - new PrivateTransactionReceipt( - null, - charlie.getAddress().toHexString(), - eventEmitter.getContractAddress(), - "0x", - Collections.singletonList( - new Log( - false, - "0x0", - "0x0", - aliceStoreHash, - null, - null, - eventEmitter.getContractAddress(), - EXPECTED_STORE_OUTPUT_DATA, - null, - Collections.singletonList(EXPECTED_STORE_EVENT_TOPIC))), - null, - null, - charlie.getEnclaveKey(), - null, - privacyGroupId, - "0x1", - null); - - alice.verify( - privateTransactionVerifier.validPrivateTransactionReceipt( - aliceStoreHash, expectedStoreReceipt)); - - bob.verify( - privateTransactionVerifier.validPrivateTransactionReceipt( - aliceStoreHash, expectedStoreReceipt)); - - charlie.verify( - privateTransactionVerifier.validPrivateTransactionReceipt( - aliceStoreHash, expectedStoreReceipt)); - - removeFromPrivacyGroup(privacyGroupId, bob, aliceCredentials, charlie); - - checkFlexiblePrivacyGroupExists(privacyGroupId, alice, bob); - } - - @Test - public void canOnlyCallProxyContractWhenGroupLocked() { - final String privacyGroupId = createFlexiblePrivacyGroup(alice); - checkFlexiblePrivacyGroupExists(privacyGroupId, alice); - - final EventEmitter eventEmitter = - alice.execute( - privateContractTransactions.createSmartContractWithPrivacyGroupId( - EventEmitter.class, - alice.getTransactionSigningKey(), - alice.getEnclaveKey(), - privacyGroupId)); - - privateContractVerifier - .validPrivateContractDeployed( - eventEmitter.getContractAddress(), alice.getAddress().toString()) - .verify(eventEmitter); - - final Credentials aliceCredentials = Credentials.create(alice.getTransactionSigningKey()); - - final Supplier callContract = - () -> { - return alice.execute( - privateContractTransactions.callOnchainPermissioningSmartContract( - eventEmitter.getContractAddress(), - eventEmitter.value().encodeFunctionCall(), - alice.getTransactionSigningKey(), - alice.getEnclaveKey(), - privacyGroupId)); - }; - - final String lockHash = - alice.execute( - privacyTransactions.privxLockPrivacyGroupAndCheck( - privacyGroupId, alice, aliceCredentials)); - - final String callWhileLockedHash = callContract.get(); - - final String unlockHash = - alice.execute( - privacyTransactions.privxUnlockPrivacyGroupAndCheck( - privacyGroupId, alice, aliceCredentials)); - - final String callAfterUnlockedHash = callContract.get(); - - alice.execute(minerTransactions.minerStart()); - alice.getBesu().verify(ethConditions.miningStatus(true)); - - final BiConsumer assertThatTransactionReceiptIs = - (String hash, String expectedResult) -> { - final PrivateTransactionReceipt receipt = - alice.execute(privacyTransactions.getPrivateTransactionReceipt(hash)); - assertThat(receipt.getStatus()).isEqualTo(expectedResult); - }; - - // when locking a group succeeds ... - assertThatTransactionReceiptIs.accept(lockHash, "0x1"); - // ... calls to contracts fail ... - assertThatTransactionReceiptIs.accept(callWhileLockedHash, "0x0"); - // ... but unlock the group works ... - assertThatTransactionReceiptIs.accept(unlockHash, "0x1"); - // ... and afterwards we can call contracts again - assertThatTransactionReceiptIs.accept(callAfterUnlockedHash, "0x1"); - } - - @Test - public void addMembersToTwoGroupsInTheSameBlock() { - final String privacyGroupId1 = createFlexiblePrivacyGroup(alice); - final String privacyGroupId2 = createFlexiblePrivacyGroup(bob); - checkFlexiblePrivacyGroupExists(privacyGroupId1, alice); - checkFlexiblePrivacyGroupExists(privacyGroupId2, bob); - - lockPrivacyGroup(privacyGroupId1, alice, Credentials.create(alice.getTransactionSigningKey())); - lockPrivacyGroup(privacyGroupId2, bob, Credentials.create(bob.getTransactionSigningKey())); - - final BigInteger pendingTransactionFilterId = - alice.execute(ethTransactions.newPendingTransactionsFilter()); - - alice.execute(minerTransactions.minerStop()); - alice.getBesu().verify(ethConditions.miningStatus(false)); - - final String aliceAddHash = - addMembersToPrivacyGroup( - privacyGroupId1, alice, Credentials.create(alice.getTransactionSigningKey()), charlie); - final String bobAddHash = - addMembersToPrivacyGroup( - privacyGroupId2, bob, Credentials.create(bob.getTransactionSigningKey()), alice); - - alice - .getBesu() - .verify( - ethConditions.expectNewPendingTransactions( - pendingTransactionFilterId, Arrays.asList(aliceAddHash, bobAddHash))); - - alice.execute(minerTransactions.minerStart()); - - checkFlexiblePrivacyGroupExists(privacyGroupId1, alice, charlie); - checkFlexiblePrivacyGroupExists(privacyGroupId2, bob, alice); - } - - private T deployPrivateContract( - final Class clazz, final String privacyGroupId, final PrivacyNode sender) { - final T contract = - sender.execute( - privateContractTransactions.createSmartContractWithPrivacyGroupId( - clazz, sender.getTransactionSigningKey(), sender.getEnclaveKey(), privacyGroupId)); - - privateContractVerifier - .validPrivateContractDeployed(contract.getContractAddress(), sender.getAddress().toString()) - .verify(contract); - - return contract; - } - - private String addMembersToPrivacyGroup( - final String privacyGroupId, - final PrivacyNode nodeAddingMember, - final Credentials signer, - final PrivacyNode... newMembers) { - return nodeAddingMember.execute( - privacyTransactions.addToPrivacyGroup( - privacyGroupId, nodeAddingMember, signer, newMembers)); - } - - private String removeFromPrivacyGroup( - final String privacyGroupId, - final PrivacyNode nodeRemovingMember, - final Credentials signer, - final PrivacyNode memberBeingRemoved) { - return nodeRemovingMember.execute( - privacyTransactions.removeFromPrivacyGroup( - privacyGroupId, - nodeRemovingMember.getEnclaveKey(), - signer, - memberBeingRemoved.getEnclaveKey())); - } - - /** - * This method will send a transaction to lock the privacy group identified by the specified id. - * This also checks if the lock transaction was successful. - * - * @return the hash of the lock privacy group transaction - */ - private String lockPrivacyGroup( - final String privacyGroupId, final PrivacyNode member, final Credentials signer) { - return member.execute( - privacyTransactions.privxLockPrivacyGroupAndCheck(privacyGroupId, member, signer)); - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/FlexiblePrivacyAcceptanceTestBase.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/FlexiblePrivacyAcceptanceTestBase.java deleted file mode 100644 index 9e4f1638dad..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/FlexiblePrivacyAcceptanceTestBase.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.privacy; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.ethereum.core.PrivacyParameters.FLEXIBLE_PRIVACY_PROXY; -import static org.hyperledger.besu.ethereum.privacy.group.FlexibleGroupManagement.GET_PARTICIPANTS_METHOD_SIGNATURE; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyAcceptanceTestBase; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.condition.ExpectValidFlexiblePrivacyGroupCreated; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction.CreateFlexiblePrivacyGroupTransaction; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivacyRequestFactory; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -import org.apache.tuweni.bytes.Bytes; -import org.web3j.abi.FunctionEncoder; -import org.web3j.abi.Utils; -import org.web3j.abi.datatypes.DynamicArray; -import org.web3j.abi.datatypes.DynamicBytes; -import org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt; -import org.web3j.protocol.core.methods.response.TransactionReceipt; -import org.web3j.tx.Contract; -import org.web3j.utils.Base64String; - -public class FlexiblePrivacyAcceptanceTestBase extends PrivacyAcceptanceTestBase { - - protected String createFlexiblePrivacyGroup(final PrivacyNode... members) { - final List addresses = - Arrays.stream(members).map(PrivacyNode::getEnclaveKey).collect(Collectors.toList()); - return createFlexiblePrivacyGroup(members[0].getEnclaveKey(), addresses, members); - } - - /** - * Create an flexible privacy group. The privacy group id will be randomly generated. - * - *

This method also checks that each node member has successfully processed the transaction and - * has the expected list of member for the group. - * - * @param members the list of members of the privacy group. The first member of the list will be - * the creator of the group. - * @return the id of the privacy group - */ - protected String createFlexiblePrivacyGroup( - final String privateFrom, final List addresses, final PrivacyNode... members) { - - final PrivacyNode groupCreator = members[0]; - - final CreateFlexiblePrivacyGroupTransaction createTx = - privacyTransactions.createFlexiblePrivacyGroup(groupCreator, privateFrom, addresses); - - final PrivacyRequestFactory.PrivxCreatePrivacyGroupResponse createResponse = - groupCreator.execute(createTx); - final String privacyGroupId = createResponse.getPrivacyGroupId(); - - final List membersEnclaveKeys = - Arrays.stream(members) - .map(m -> Base64String.wrap(m.getEnclaveKey())) - .collect(Collectors.toList()); - - for (final PrivacyNode member : members) { - member.verify(flexiblePrivacyGroupExists(privacyGroupId, membersEnclaveKeys)); - } - - final String commitmentHash = - callGetParticipantsMethodAndReturnCommitmentHash(privacyGroupId, groupCreator, privateFrom); - final PrivateTransactionReceipt expectedReceipt = - buildExpectedAddMemberTransactionReceipt(privacyGroupId, groupCreator, addresses); - - for (final PrivacyNode member : members) { - member.verify( - privateTransactionVerifier.validPrivateTransactionReceipt( - commitmentHash, expectedReceipt)); - } - - return privacyGroupId; - } - - protected String callGetParticipantsMethodAndReturnCommitmentHash( - final String privacyGroupId, final PrivacyNode groupCreator, final String privateFrom) { - return groupCreator.execute( - privateContractTransactions.callOnchainPermissioningSmartContract( - FLEXIBLE_PRIVACY_PROXY.toHexString(), - GET_PARTICIPANTS_METHOD_SIGNATURE.toString(), - groupCreator.getTransactionSigningKey(), - privateFrom, - privacyGroupId)); - } - - protected PrivateTransactionReceipt buildExpectedAddMemberTransactionReceipt( - final String privacyGroupId, final PrivacyNode groupCreator, final List members) { - return buildExpectedAddMemberTransactionReceipt( - privacyGroupId, groupCreator, groupCreator.getEnclaveKey(), members); - } - - protected PrivateTransactionReceipt buildExpectedAddMemberTransactionReceipt( - final String privacyGroupId, - final PrivacyNode groupCreator, - final String privateFrom, - final List members) { - - final StringBuilder output = new StringBuilder(); - // hex prefix - output.append("0x"); - - final String encodedParameters = - FunctionEncoder.encode( - "", - Arrays.asList( - new DynamicArray<>( - DynamicBytes.class, - Utils.typeMap( - members.stream() - .map(Bytes::fromBase64String) - .map(Bytes::toArrayUnsafe) - .collect(Collectors.toList()), - DynamicBytes.class)))); - - output.append(encodedParameters); - - return new PrivateTransactionReceipt( - null, - groupCreator.getAddress().toHexString(), - FLEXIBLE_PRIVACY_PROXY.toHexString(), - output.toString(), - Collections.emptyList(), - null, - null, - privateFrom, - null, - privacyGroupId, - "0x1", - null); - } - - protected ExpectValidFlexiblePrivacyGroupCreated flexiblePrivacyGroupExists( - final String privacyGroupId, final List members) { - return privateTransactionVerifier.flexiblePrivacyGroupExists(privacyGroupId, members); - } - - protected String getContractDeploymentCommitmentHash(final Contract contract) { - final Optional transactionReceipt = contract.getTransactionReceipt(); - assertThat(transactionReceipt).isPresent(); - final PrivateTransactionReceipt privateTransactionReceipt = - (PrivateTransactionReceipt) transactionReceipt.get(); - return privateTransactionReceipt.getcommitmentHash(); - } - - /** - * This method will check if a privacy group with the specified id and list of members exists. - * Each one of the members node will be queried to ensure that they all have the same privacy - * group in their private state. - * - * @param privacyGroupId the id of the privacy group - * @param members the list of member in the privacy group - */ - protected void checkFlexiblePrivacyGroupExists( - final String privacyGroupId, final PrivacyNode... members) { - final List membersEnclaveKeys = - Arrays.stream(members) - .map(PrivacyNode::getEnclaveKey) - .map(Base64String::wrap) - .collect(Collectors.toList()); - - for (final PrivacyNode member : members) { - member.verify(flexiblePrivacyGroupExists(privacyGroupId, membersEnclaveKeys)); - } - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PluginPrivacySigningAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PluginPrivacySigningAcceptanceTest.java deleted file mode 100644 index 68d5117fd6b..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PluginPrivacySigningAcceptanceTest.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.privacy; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.BesuNodeConfigurationBuilder; -import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.privacy.PrivacyNodeConfiguration; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyAcceptanceTestBase; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.account.PrivacyAccount; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.account.PrivacyAccountResolver; -import org.hyperledger.besu.tests.web3j.generated.EventEmitter; -import org.hyperledger.enclave.testutil.EnclaveEncryptorType; -import org.hyperledger.enclave.testutil.EnclaveKeyConfiguration; -import org.hyperledger.enclave.testutil.EnclaveType; - -import java.io.IOException; -import java.math.BigInteger; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.web3j.protocol.core.DefaultBlockParameter; -import org.web3j.protocol.core.methods.response.EthBlock.Block; -import org.web3j.protocol.core.methods.response.TransactionReceipt; - -@RunWith(Parameterized.class) -public class PluginPrivacySigningAcceptanceTest extends PrivacyAcceptanceTestBase { - private PrivacyNode minerNode; - - private final EnclaveEncryptorType enclaveEncryptorType; - - public PluginPrivacySigningAcceptanceTest(final EnclaveEncryptorType enclaveEncryptorType) { - this.enclaveEncryptorType = enclaveEncryptorType; - } - - @Parameterized.Parameters(name = "{0}") - public static Collection enclaveEncryptorTypes() { - return Arrays.stream(EnclaveEncryptorType.values()) - .filter(encryptorType -> !EnclaveEncryptorType.NOOP.equals(encryptorType)) - .collect(Collectors.toList()); - } - - @Before - public void setup() throws IOException { - final PrivacyAccount BOB = PrivacyAccountResolver.BOB.resolve(enclaveEncryptorType); - - minerNode = - privacyBesu.create( - new PrivacyNodeConfiguration( - false, - false, - true, - new BesuNodeConfigurationBuilder() - .name("miner") - .miningEnabled() - .jsonRpcEnabled() - .webSocketEnabled() - .enablePrivateTransactions() - .keyFilePath(BOB.getPrivateKeyPath()) - .plugins(Collections.singletonList("testPlugins")) - .extraCLIOptions( - List.of( - "--plugin-privacy-service-encryption-prefix=0xAA", - "--plugin-privacy-service-signing-enabled=true", - "--plugin-privacy-service-signing-key=8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63")) - .build(), - new EnclaveKeyConfiguration( - BOB.getEnclaveKeyPaths(), - BOB.getEnclavePrivateKeyPaths(), - BOB.getEnclaveEncryptorType())), - EnclaveType.NOOP, - Optional.empty()); - - privacyCluster.start(minerNode); - } - - @Test - public void canDeployContractSignedByPlugin() throws Exception { - final String contractAddress = - EnclaveEncryptorType.EC.equals(enclaveEncryptorType) - ? "0xf01ec73d91fdeb8bb9388ec74e6a3981da86e021" - : "0xd0152772c54cecfa7684f09f7616dcc825545dff"; - - final EventEmitter eventEmitter = - minerNode.execute( - privateContractTransactions.createSmartContract( - EventEmitter.class, - minerNode.getTransactionSigningKey(), - minerNode.getEnclaveKey())); - - privateContractVerifier - .validPrivateContractDeployed(contractAddress, minerNode.getAddress().toString()) - .verify(eventEmitter); - privateContractVerifier.validContractCodeProvided().verify(eventEmitter); - - final BigInteger blockNumberContractDeployed = - eventEmitter.getTransactionReceipt().get().getBlockNumber(); - final Block blockContractDeployed = - minerNode.execute( - ethTransactions.block(DefaultBlockParameter.valueOf(blockNumberContractDeployed))); - - assertThat(blockContractDeployed.getTransactions().size()).isEqualTo(1); - - final String transactionHashContractDeployed = - (String) blockContractDeployed.getTransactions().get(0).get(); - final TransactionReceipt pmtReceipt = - minerNode - .execute(ethTransactions.getTransactionReceipt(transactionHashContractDeployed)) - .get(); - - assertThat(pmtReceipt.getStatus()).isEqualTo("0x1"); - assertThat(pmtReceipt.getFrom()).isEqualTo("0xfe3b557e8fb62b89f4916b721be55ceb828dbd73"); - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivCallAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivCallAcceptanceTest.java deleted file mode 100644 index bb5e829dc5a..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivCallAcceptanceTest.java +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.privacy; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.tests.web3j.generated.RevertReason.FUNC_REVERTWITHREVERTREASON; -import static org.web3j.utils.Restriction.UNRESTRICTED; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.ParameterizedEnclaveTestBase; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.account.PrivacyAccountResolver; -import org.hyperledger.besu.tests.web3j.generated.EventEmitter; -import org.hyperledger.besu.tests.web3j.generated.RevertReason; -import org.hyperledger.enclave.testutil.EnclaveEncryptorType; -import org.hyperledger.enclave.testutil.EnclaveType; - -import java.io.IOException; -import java.math.BigInteger; -import java.nio.charset.Charset; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import javax.annotation.Nonnull; - -import org.bouncycastle.util.encoders.Hex; -import org.junit.Test; -import org.web3j.abi.FunctionEncoder; -import org.web3j.abi.TypeReference; -import org.web3j.abi.datatypes.Bool; -import org.web3j.abi.datatypes.Function; -import org.web3j.abi.datatypes.Type; -import org.web3j.abi.datatypes.generated.Uint256; -import org.web3j.protocol.core.Request; -import org.web3j.protocol.core.methods.request.Transaction; -import org.web3j.protocol.core.methods.response.EthCall; -import org.web3j.protocol.core.methods.response.TransactionReceipt; -import org.web3j.protocol.http.HttpService; -import org.web3j.tx.Contract; -import org.web3j.utils.Restriction; - -public class PrivCallAcceptanceTest extends ParameterizedEnclaveTestBase { - - private static final int VALUE = 1024; - - private final PrivacyNode minerNode; - - public PrivCallAcceptanceTest( - final Restriction restriction, - final EnclaveType enclaveType, - final EnclaveEncryptorType enclaveEncryptorType) - throws IOException { - - super(restriction, enclaveType, enclaveEncryptorType); - - minerNode = - privacyBesu.createPrivateTransactionEnabledMinerNode( - restriction + "-node", - PrivacyAccountResolver.ALICE.resolve(enclaveEncryptorType), - enclaveType, - Optional.empty(), - false, - false, - restriction == UNRESTRICTED); - - privacyCluster.start(minerNode); - } - - @Test - public void mustReturnCorrectValue() throws Exception { - - final String privacyGroupId = - minerNode.execute(createPrivacyGroup("myGroupName", "my group description", minerNode)); - - final EventEmitter eventEmitter = - minerNode.execute( - privateContractTransactions.createSmartContractWithPrivacyGroupId( - EventEmitter.class, - minerNode.getTransactionSigningKey(), - restriction, - minerNode.getEnclaveKey(), - privacyGroupId)); - - privateContractVerifier - .validPrivateContractDeployed( - eventEmitter.getContractAddress(), minerNode.getAddress().toString()) - .verify(eventEmitter); - - final Request priv_call = - privCall(privacyGroupId, eventEmitter, false, false, false); - - EthCall resp = priv_call.send(); - - String value = resp.getValue(); - assertThat(new BigInteger(value.substring(2), 16)).isEqualByComparingTo(BigInteger.ZERO); - - final TransactionReceipt receipt = eventEmitter.store(BigInteger.valueOf(VALUE)).send(); - assertThat(receipt).isNotNull(); - - resp = priv_call.send(); - value = resp.getValue(); - assertThat(new BigInteger(value.substring(2), 16)) - .isEqualByComparingTo(BigInteger.valueOf(VALUE)); - } - - @Test - public void mustRevertWithRevertReason() throws Exception { - - final String privacyGroupId = - minerNode.execute(createPrivacyGroup("myGroupName", "my group description", minerNode)); - - final RevertReason revertReasonContract = - minerNode.execute( - privateContractTransactions.createSmartContractWithPrivacyGroupId( - RevertReason.class, - minerNode.getTransactionSigningKey(), - restriction, - minerNode.getEnclaveKey(), - privacyGroupId)); - - privateContractVerifier - .validPrivateContractDeployed( - revertReasonContract.getContractAddress(), minerNode.getAddress().toString()) - .verify(revertReasonContract); - - final Request priv_call = - privCall(privacyGroupId, revertReasonContract, false, false, true); - - EthCall resp = priv_call.send(); - assertThat(resp.getRevertReason()).isEqualTo("Execution reverted: RevertReason"); - - byte[] bytes = Hex.decode(resp.getError().getData().substring(3, 203)); - String revertMessage = - new String(Arrays.copyOfRange(bytes, 4, bytes.length), Charset.defaultCharset()).trim(); - assertThat(revertMessage).isEqualTo("RevertReason"); - } - - @Test - public void shouldReturnEmptyResultWithNonExistingPrivacyGroup() throws IOException { - - final String privacyGroupId = - minerNode.execute(createPrivacyGroup("myGroupName", "my group description", minerNode)); - - final EventEmitter eventEmitter = - minerNode.execute( - privateContractTransactions.createSmartContractWithPrivacyGroupId( - EventEmitter.class, - minerNode.getTransactionSigningKey(), - restriction, - minerNode.getEnclaveKey(), - privacyGroupId)); - - privateContractVerifier - .validPrivateContractDeployed( - eventEmitter.getContractAddress(), minerNode.getAddress().toString()) - .verify(eventEmitter); - - final String invalidPrivacyGroup = constructInvalidString(privacyGroupId); - final Request privCall = - privCall(invalidPrivacyGroup, eventEmitter, false, false, false); - - final EthCall result = privCall.send(); - - assertThat(result.getResult()).isEqualTo("0x"); - } - - @Test - public void mustNotSucceedWithWronglyEncodedFunction() throws IOException { - - final String privacyGroupId = - minerNode.execute(createPrivacyGroup("myGroupName", "my group description", minerNode)); - - final EventEmitter eventEmitter = - minerNode.execute( - privateContractTransactions.createSmartContractWithPrivacyGroupId( - EventEmitter.class, - minerNode.getTransactionSigningKey(), - restriction, - minerNode.getEnclaveKey(), - privacyGroupId)); - - privateContractVerifier - .validPrivateContractDeployed( - eventEmitter.getContractAddress(), minerNode.getAddress().toString()) - .verify(eventEmitter); - - final Request priv_call = - privCall(privacyGroupId, eventEmitter, true, false, false); - - final String errorMessage = priv_call.send().getError().getMessage(); - assertThat(errorMessage).isEqualTo("Private transaction failed"); - } - - @Test - public void mustReturn0xUsingInvalidContractAddress() throws IOException { - - final String privacyGroupId = - minerNode.execute(createPrivacyGroup("myGroupName", "my group description", minerNode)); - - final EventEmitter eventEmitter = - minerNode.execute( - privateContractTransactions.createSmartContractWithPrivacyGroupId( - EventEmitter.class, - minerNode.getTransactionSigningKey(), - restriction, - minerNode.getEnclaveKey(), - privacyGroupId)); - - privateContractVerifier - .validPrivateContractDeployed( - eventEmitter.getContractAddress(), minerNode.getAddress().toString()) - .verify(eventEmitter); - - final Request priv_call = - privCall(privacyGroupId, eventEmitter, false, true, false); - - final EthCall result = priv_call.send(); - - assertThat(result).isNotNull(); - assertThat(result.getResult()).isEqualTo("0x"); - } - - @Nonnull - private String constructInvalidString(final String privacyGroupId) { - final char[] chars = privacyGroupId.toCharArray(); - if (chars[3] == '0') { - chars[3] = '1'; - } else { - chars[3] = '0'; - } - return String.valueOf(chars); - } - - @Nonnull - private Request privCall( - final String privacyGroupId, - final Contract contract, - final boolean useInvalidParameters, - final boolean useInvalidContractAddress, - final boolean useRevertFunction) { - - final Uint256 invalid = new Uint256(BigInteger.TEN); - - @SuppressWarnings("rawtypes") - final List inputParameters = - useInvalidParameters ? Arrays.asList(invalid) : Collections.emptyList(); - - final Function function = - useRevertFunction - ? new Function( - FUNC_REVERTWITHREVERTREASON, - inputParameters, - Arrays.>asList(new TypeReference() {})) - : new Function( - "value", - inputParameters, - Arrays.>asList(new TypeReference() {})); - - final String encoded = FunctionEncoder.encode(function); - - final HttpService httpService = - new HttpService( - "http://" - + minerNode.getBesu().getHostName() - + ":" - + minerNode.getBesu().getJsonRpcPort().get()); - - final String validContractAddress = contract.getContractAddress(); - final String invalidContractAddress = constructInvalidString(validContractAddress); - final String contractAddress = - useInvalidContractAddress ? invalidContractAddress : validContractAddress; - - final Transaction transaction = - Transaction.createEthCallTransaction(null, contractAddress, encoded); - - return new Request<>( - "priv_call", - Arrays.asList(privacyGroupId, transaction, "latest"), - httpService, - EthCall.class); - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivDebugGetStateRootFlexibleGroupAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivDebugGetStateRootFlexibleGroupAcceptanceTest.java deleted file mode 100644 index 59973fd00be..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivDebugGetStateRootFlexibleGroupAcceptanceTest.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.privacy; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.account.PrivacyAccountResolver; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivacyRequestFactory; -import org.hyperledger.besu.tests.web3j.generated.EventEmitter; -import org.hyperledger.enclave.testutil.EnclaveEncryptorType; -import org.hyperledger.enclave.testutil.EnclaveType; - -import java.io.IOException; -import java.math.BigInteger; -import java.net.URISyntaxException; -import java.util.Collection; -import java.util.Optional; - -import org.apache.tuweni.bytes.Bytes32; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; -import org.testcontainers.containers.Network; - -@RunWith(Parameterized.class) -public class PrivDebugGetStateRootFlexibleGroupAcceptanceTest - extends FlexiblePrivacyAcceptanceTestBase { - - private final EnclaveType enclaveType; - - public PrivDebugGetStateRootFlexibleGroupAcceptanceTest(final EnclaveType enclaveType) { - this.enclaveType = enclaveType; - } - - @Parameters(name = "{0}") - public static Collection enclaveTypes() { - return EnclaveType.valuesForTests(); - } - - private PrivacyNode aliceNode; - private PrivacyNode bobNode; - - @Before - public void setUp() throws IOException, URISyntaxException { - final Network containerNetwork = Network.newNetwork(); - - aliceNode = - privacyBesu.createFlexiblePrivacyGroupEnabledMinerNode( - "alice-node", - PrivacyAccountResolver.ALICE.resolve(EnclaveEncryptorType.NACL), - false, - enclaveType, - Optional.of(containerNetwork)); - bobNode = - privacyBesu.createFlexiblePrivacyGroupEnabledNode( - "bob-node", - PrivacyAccountResolver.BOB.resolve(EnclaveEncryptorType.NACL), - false, - enclaveType, - Optional.of(containerNetwork)); - - privacyCluster.start(aliceNode, bobNode); - } - - @Test - public void nodesInGroupShouldHaveSameStateRoot() { - final String privacyGroupId = createFlexiblePrivacyGroup(aliceNode, bobNode); - - final Hash aliceStateRootId = - aliceNode - .execute(privacyTransactions.debugGetStateRoot(privacyGroupId, "latest")) - .getResult(); - - final Hash bobStateRootId = - bobNode - .execute(privacyTransactions.debugGetStateRoot(privacyGroupId, "latest")) - .getResult(); - - assertThat(aliceStateRootId).isEqualTo(bobStateRootId); - } - - @Test - public void unknownGroupShouldReturnError() { - final PrivacyRequestFactory.DebugGetStateRoot aliceResult = - aliceNode.execute( - privacyTransactions.debugGetStateRoot( - Hash.wrap(Bytes32.random()).toBase64String(), "latest")); - - assertThat(aliceResult.getResult()).isNull(); - assertThat(aliceResult.hasError()).isTrue(); - assertThat(aliceResult.getError()).isNotNull(); - assertThat(aliceResult.getError().getMessage()).contains("Error finding privacy group"); - } - - @Test - public void blockParamShouldBeApplied() { - waitForBlockHeight(aliceNode, 2); - waitForBlockHeight(bobNode, 2); - - final String privacyGroupId = createFlexiblePrivacyGroup(aliceNode, bobNode); - - waitForBlockHeight(aliceNode, 10); - waitForBlockHeight(bobNode, 10); - - final Hash aliceResult1 = - aliceNode.execute(privacyTransactions.debugGetStateRoot(privacyGroupId, "1")).getResult(); - final Hash bobResultInt1 = - bobNode.execute(privacyTransactions.debugGetStateRoot(privacyGroupId, "1")).getResult(); - - assertThat(aliceResult1).isEqualTo(bobResultInt1); - - final Hash aliceResultLatest = - aliceNode - .execute(privacyTransactions.debugGetStateRoot(privacyGroupId, "latest")) - .getResult(); - - final Hash bobResultLatest = - bobNode - .execute(privacyTransactions.debugGetStateRoot(privacyGroupId, "latest")) - .getResult(); - - assertThat(aliceResultLatest).isEqualTo(bobResultLatest); - assertThat(aliceResult1).isNotEqualTo(aliceResultLatest); - } - - @Test - public void canInteractWithPrivateGenesisPreCompile() throws Exception { - final String privacyGroupId = createFlexiblePrivacyGroup(aliceNode, bobNode); - - final EventEmitter eventEmitter = - aliceNode.execute( - privateContractTransactions.loadSmartContractWithPrivacyGroupId( - "0x1000000000000000000000000000000000000001", - EventEmitter.class, - aliceNode.getTransactionSigningKey(), - aliceNode.getEnclaveKey(), - privacyGroupId)); - - privateTransactionVerifier.existingPrivateTransactionReceipt( - eventEmitter.store(BigInteger.valueOf(42)).send().getTransactionHash()); - - final String aliceResponse = - aliceNode - .execute( - privacyTransactions.privCall( - privacyGroupId, eventEmitter, eventEmitter.value().encodeFunctionCall())) - .getValue(); - - assertThat(new BigInteger(aliceResponse.substring(2), 16)) - .isEqualByComparingTo(BigInteger.valueOf(42)); - - final String bobResponse = - bobNode - .execute( - privacyTransactions.privCall( - privacyGroupId, eventEmitter, eventEmitter.value().encodeFunctionCall())) - .getValue(); - - assertThat(new BigInteger(bobResponse.substring(2), 16)) - .isEqualByComparingTo(BigInteger.valueOf(42)); - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivDebugGetStateRootOffchainGroupAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivDebugGetStateRootOffchainGroupAcceptanceTest.java deleted file mode 100644 index 92643446ddb..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivDebugGetStateRootOffchainGroupAcceptanceTest.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.privacy; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.web3j.utils.Restriction.UNRESTRICTED; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.ParameterizedEnclaveTestBase; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.account.PrivacyAccountResolver; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivacyRequestFactory; -import org.hyperledger.enclave.testutil.EnclaveEncryptorType; -import org.hyperledger.enclave.testutil.EnclaveType; - -import java.io.IOException; -import java.util.Optional; - -import org.apache.tuweni.bytes.Bytes32; -import org.junit.Test; -import org.testcontainers.containers.Network; -import org.web3j.utils.Restriction; - -public class PrivDebugGetStateRootOffchainGroupAcceptanceTest extends ParameterizedEnclaveTestBase { - - private final PrivacyNode aliceNode; - private final PrivacyNode bobNode; - - public PrivDebugGetStateRootOffchainGroupAcceptanceTest( - final Restriction restriction, - final EnclaveType enclaveType, - final EnclaveEncryptorType enclaveEncryptorType) - throws IOException { - - super(restriction, enclaveType, enclaveEncryptorType); - - final Network containerNetwork = Network.newNetwork(); - - aliceNode = - privacyBesu.createIbft2NodePrivacyEnabled( - "alice-node", - PrivacyAccountResolver.ALICE.resolve(enclaveEncryptorType), - false, - enclaveType, - Optional.of(containerNetwork), - false, - false, - restriction == UNRESTRICTED, - "0xAA"); - bobNode = - privacyBesu.createIbft2NodePrivacyEnabled( - "bob-node", - PrivacyAccountResolver.BOB.resolve(enclaveEncryptorType), - false, - enclaveType, - Optional.of(containerNetwork), - false, - false, - restriction == UNRESTRICTED, - "0xBB"); - - privacyCluster.start(aliceNode, bobNode); - } - - @Test - public void nodesInGroupShouldHaveSameStateRoot() { - final String privacyGroupId = - aliceNode.execute( - createPrivacyGroup("testGroup", "A group for everyone", aliceNode, bobNode)); - - final Hash aliceStateRootId = - aliceNode - .execute(privacyTransactions.debugGetStateRoot(privacyGroupId, "latest")) - .getResult(); - - final Hash bobStateRootId = - bobNode - .execute(privacyTransactions.debugGetStateRoot(privacyGroupId, "latest")) - .getResult(); - - assertThat(aliceStateRootId).isEqualTo(bobStateRootId); - } - - @Test - public void unknownGroupShouldReturnError() { - if (restriction != UNRESTRICTED) { - final PrivacyRequestFactory.DebugGetStateRoot aliceResult = - aliceNode.execute( - privacyTransactions.debugGetStateRoot( - Hash.wrap(Bytes32.random()).toBase64String(), "latest")); - - assertThat(aliceResult.getResult()).isNull(); - assertThat(aliceResult.hasError()).isTrue(); - assertThat(aliceResult.getError()).isNotNull(); - assertThat(aliceResult.getError().getMessage()).contains("Error finding privacy group"); - } - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivGetCodeAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivGetCodeAcceptanceTest.java deleted file mode 100644 index c08006f694b..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivGetCodeAcceptanceTest.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.privacy; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.web3j.utils.Restriction.UNRESTRICTED; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.ParameterizedEnclaveTestBase; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.account.PrivacyAccountResolver; -import org.hyperledger.besu.tests.web3j.generated.EventEmitter; -import org.hyperledger.enclave.testutil.EnclaveEncryptorType; -import org.hyperledger.enclave.testutil.EnclaveType; - -import java.io.IOException; -import java.util.Optional; - -import org.apache.tuweni.bytes.Bytes; -import org.junit.Test; -import org.web3j.utils.Restriction; - -public class PrivGetCodeAcceptanceTest extends ParameterizedEnclaveTestBase { - - private final PrivacyNode alice; - - public PrivGetCodeAcceptanceTest( - final Restriction restriction, - final EnclaveType enclaveType, - final EnclaveEncryptorType enclaveEncryptorType) - throws IOException { - - super(restriction, enclaveType, enclaveEncryptorType); - - alice = - privacyBesu.createPrivateTransactionEnabledMinerNode( - restriction + "-node", - PrivacyAccountResolver.ALICE.resolve(enclaveEncryptorType), - enclaveType, - Optional.empty(), - false, - false, - restriction == UNRESTRICTED); - - privacyCluster.start(alice); - } - - @Test - public void privGetCodeReturnsDeployedContractBytecode() { - final String privacyGroupId = createPrivacyGroup(); - final EventEmitter eventEmitterContract = deployPrivateContract(privacyGroupId); - - final Bytes deployedContractCode = - alice.execute( - privacyTransactions.privGetCode( - privacyGroupId, - Address.fromHexString(eventEmitterContract.getContractAddress()), - "latest")); - - assertThat(eventEmitterContract.getContractBinary()) - .contains(deployedContractCode.toUnprefixedHexString()); - } - - private EventEmitter deployPrivateContract(final String privacyGroupId) { - final EventEmitter eventEmitter = - alice.execute( - privateContractTransactions.createSmartContractWithPrivacyGroupId( - EventEmitter.class, - alice.getTransactionSigningKey(), - restriction, - alice.getEnclaveKey(), - privacyGroupId)); - - privateContractVerifier - .validPrivateContractDeployed( - eventEmitter.getContractAddress(), alice.getAddress().toString()) - .verify(eventEmitter); - return eventEmitter; - } - - private String createPrivacyGroup() { - final String privacyGroupId = - alice.execute(createPrivacyGroup("myGroupName", "my group description", alice)); - - assertThat(privacyGroupId).isNotNull(); - - return privacyGroupId; - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivGetLogsAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivGetLogsAcceptanceTest.java deleted file mode 100644 index 62519385a2e..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivGetLogsAcceptanceTest.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.privacy; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.web3j.utils.Restriction.UNRESTRICTED; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.ParameterizedEnclaveTestBase; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.account.PrivacyAccountResolver; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.util.LogFilterJsonParameter; -import org.hyperledger.besu.tests.web3j.generated.EventEmitter; -import org.hyperledger.enclave.testutil.EnclaveEncryptorType; -import org.hyperledger.enclave.testutil.EnclaveType; - -import java.io.IOException; -import java.math.BigInteger; -import java.util.List; -import java.util.Optional; - -import org.junit.Test; -import org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt; -import org.web3j.protocol.core.methods.response.EthLog.LogResult; -import org.web3j.utils.Restriction; - -@SuppressWarnings("rawtypes") -public class PrivGetLogsAcceptanceTest extends ParameterizedEnclaveTestBase { - - /* - This value is derived from the contract event signature - */ - private static final String EVENT_EMITTER_EVENT_TOPIC = - "0xc9db20adedc6cf2b5d25252b101ab03e124902a73fcb12b753f3d1aaa2d8f9f5"; - - private final PrivacyNode node; - - public PrivGetLogsAcceptanceTest( - final Restriction restriction, - final EnclaveType enclaveType, - final EnclaveEncryptorType enclaveEncryptorType) - throws IOException { - - super(restriction, enclaveType, enclaveEncryptorType); - - node = - privacyBesu.createPrivateTransactionEnabledMinerNode( - restriction + "-node", - PrivacyAccountResolver.ALICE.resolve(enclaveEncryptorType), - enclaveType, - Optional.empty(), - false, - false, - restriction == UNRESTRICTED); - - privacyCluster.start(node); - } - - @Test - public void getLogsUsingBlockRangeFilter() { - final String privacyGroupId = createPrivacyGroup(); - final EventEmitter eventEmitterContract = deployEventEmitterContract(privacyGroupId); - - /* - Updating the contract value 2 times - */ - updateContractValue(privacyGroupId, eventEmitterContract, 1); - updateContractValue(privacyGroupId, eventEmitterContract, 2); - - final LogFilterJsonParameter filter = - blockRangeLogFilter("earliest", "latest", eventEmitterContract.getContractAddress()); - - final List logs = - node.execute(privacyTransactions.privGetLogs(privacyGroupId, filter)); - - /* - We expect one log entry per tx changing the contract value - */ - assertThat(logs).hasSize(2); - } - - @Test - public void getLogsUsingBlockHashFilter() { - final String privacyGroupId = createPrivacyGroup(); - final EventEmitter eventEmitterContract = deployEventEmitterContract(privacyGroupId); - - /* - Updating the contract value 1 times - */ - final PrivateTransactionReceipt updateValueReceipt = - updateContractValue(privacyGroupId, eventEmitterContract, 1); - final String blockHash = updateValueReceipt.getBlockHash(); - - final LogFilterJsonParameter filter = - blockHashLogFilter(blockHash, eventEmitterContract.getContractAddress()); - - final List logs = - node.execute(privacyTransactions.privGetLogs(privacyGroupId, filter)); - - assertThat(logs).hasSize(1); - } - - private LogFilterJsonParameter blockRangeLogFilter( - final String fromBlock, final String toBlock, final String contractAddress) { - return new LogFilterJsonParameter( - fromBlock, - toBlock, - List.of(contractAddress), - List.of(List.of(EVENT_EMITTER_EVENT_TOPIC)), - null); - } - - private LogFilterJsonParameter blockHashLogFilter( - final String blockHash, final String contractAddress) { - return new LogFilterJsonParameter( - null, - null, - List.of(contractAddress), - List.of(List.of(EVENT_EMITTER_EVENT_TOPIC)), - blockHash); - } - - private String createPrivacyGroup() { - return node.execute(createPrivacyGroup("myGroupName", "my group description", node)); - } - - private EventEmitter deployEventEmitterContract(final String privacyGroupId) { - final EventEmitter eventEmitter = - node.execute( - privateContractTransactions.createSmartContractWithPrivacyGroupId( - EventEmitter.class, - node.getTransactionSigningKey(), - restriction, - node.getEnclaveKey(), - privacyGroupId)); - - privateContractVerifier - .validPrivateContractDeployed( - eventEmitter.getContractAddress(), node.getAddress().toString()) - .verify(eventEmitter); - - return eventEmitter; - } - - private PrivateTransactionReceipt updateContractValue( - final String privacyGroupId, final EventEmitter eventEmitterContract, final int value) { - final String transactionHash = - node.execute( - privateContractTransactions.callSmartContractWithPrivacyGroupId( - eventEmitterContract.getContractAddress(), - eventEmitterContract.store(BigInteger.valueOf(value)).encodeFunctionCall(), - node.getTransactionSigningKey(), - restriction, - node.getEnclaveKey(), - privacyGroupId)); - - return node.execute(privacyTransactions.getPrivateTransactionReceipt(transactionHash)); - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivGetPrivateTransactionAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivGetPrivateTransactionAcceptanceTest.java deleted file mode 100644 index 91896761d09..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivGetPrivateTransactionAcceptanceTest.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.privacy; - -import static org.web3j.utils.Restriction.RESTRICTED; -import static org.web3j.utils.Restriction.UNRESTRICTED; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.ethereum.privacy.PrivateTransaction; -import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.ParameterizedEnclaveTestBase; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.account.PrivacyAccountResolver; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; -import org.hyperledger.enclave.testutil.EnclaveEncryptorType; -import org.hyperledger.enclave.testutil.EnclaveType; - -import java.io.IOException; -import java.util.Optional; - -import org.apache.tuweni.bytes.Bytes; -import org.junit.Test; -import org.testcontainers.containers.Network; -import org.web3j.utils.Restriction; - -public class PrivGetPrivateTransactionAcceptanceTest extends ParameterizedEnclaveTestBase { - - private final PrivacyNode alice; - private final PrivacyNode bob; - - public PrivGetPrivateTransactionAcceptanceTest( - final Restriction restriction, - final EnclaveType enclaveType, - final EnclaveEncryptorType enclaveEncryptorType) - throws IOException { - - super(restriction, enclaveType, enclaveEncryptorType); - - final Network containerNetwork = Network.newNetwork(); - - alice = - privacyBesu.createIbft2NodePrivacyEnabled( - "node1", - PrivacyAccountResolver.ALICE.resolve(enclaveEncryptorType), - false, - enclaveType, - Optional.of(containerNetwork), - false, - false, - restriction == UNRESTRICTED, - "0xAA"); - bob = - privacyBesu.createIbft2NodePrivacyEnabled( - "node2", - PrivacyAccountResolver.BOB.resolve(enclaveEncryptorType), - false, - enclaveType, - Optional.of(containerNetwork), - false, - false, - restriction == UNRESTRICTED, - "0xBB"); - - privacyCluster.start(alice, bob); - } - - @Test - public void returnsTransaction() { - final Transaction onlyAlice = createPrivacyGroup("Only Alice", "", alice); - - final String privacyGroupId = alice.execute(onlyAlice); - - final PrivateTransaction validSignedPrivateTransaction = - getValidSignedPrivateTransaction(alice, privacyGroupId); - final BytesValueRLPOutput rlpOutput = getRLPOutput(validSignedPrivateTransaction); - - final Hash transactionHash = - alice.execute(privacyTransactions.sendRawTransaction(rlpOutput.encoded().toHexString())); - - alice.getBesu().verify(eth.expectSuccessfulTransactionReceipt(transactionHash.toString())); - - alice - .getBesu() - .verify(priv.getPrivateTransaction(transactionHash, validSignedPrivateTransaction)); - } - - @Test - public void nonExistentHashReturnsNull() { - alice.getBesu().verify(priv.getPrivateTransactionReturnsNull(Hash.ZERO)); - } - - @Test - public void returnsNullTransactionNotInNodesPrivacyGroup() { - final Transaction onlyAlice = createPrivacyGroup("Only Alice", "", alice); - - final String privacyGroupId = alice.execute(onlyAlice); - - final PrivateTransaction validSignedPrivateTransaction = - getValidSignedPrivateTransaction(alice, privacyGroupId); - final BytesValueRLPOutput rlpOutput = getRLPOutput(validSignedPrivateTransaction); - - final Hash transactionHash = - alice.execute(privacyTransactions.sendRawTransaction(rlpOutput.encoded().toHexString())); - - alice.getBesu().verify(eth.expectSuccessfulTransactionReceipt(transactionHash.toString())); - - bob.getBesu().verify(priv.getPrivateTransactionReturnsNull(transactionHash)); - } - - private BytesValueRLPOutput getRLPOutput(final PrivateTransaction privateTransaction) { - final BytesValueRLPOutput bvrlpo = new BytesValueRLPOutput(); - privateTransaction.writeTo(bvrlpo); - return bvrlpo; - } - - private PrivateTransaction getValidSignedPrivateTransaction( - final PrivacyNode node, final String privacyGoupId) { - - org.hyperledger.besu.plugin.data.Restriction besuRestriction = - restriction == RESTRICTED - ? org.hyperledger.besu.plugin.data.Restriction.RESTRICTED - : org.hyperledger.besu.plugin.data.Restriction.UNRESTRICTED; - - return PrivateTransaction.builder() - .nonce(0) - .gasPrice(Wei.of(999999)) - .gasLimit(3000000) - .to(null) - .value(Wei.ZERO) - .payload(Bytes.wrap(new byte[] {})) - .sender(node.getAddress()) - .privateFrom(Bytes.fromBase64String(node.getEnclaveKey())) - .restriction(besuRestriction) - .privacyGroupId(Bytes.fromBase64String(privacyGoupId)) - .signAndBuild(node.getBesu().getPrivacyParameters().getSigningKeyPair().get()); - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivacyClusterAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivacyClusterAcceptanceTest.java deleted file mode 100644 index 1284daf917e..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivacyClusterAcceptanceTest.java +++ /dev/null @@ -1,402 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.privacy; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.ethereum.core.PrivacyParameters.DEFAULT_PRIVACY; -import static org.hyperledger.enclave.testutil.EnclaveEncryptorType.EC; -import static org.hyperledger.enclave.testutil.EnclaveEncryptorType.NACL; -import static org.hyperledger.enclave.testutil.EnclaveType.TESSERA; -import static org.web3j.utils.Restriction.RESTRICTED; - -import org.hyperledger.besu.enclave.Enclave; -import org.hyperledger.besu.enclave.EnclaveFactory; -import org.hyperledger.besu.enclave.types.ReceiveResponse; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyAcceptanceTestBase; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.account.PrivacyAccountResolver; -import org.hyperledger.besu.tests.web3j.generated.EventEmitter; -import org.hyperledger.enclave.testutil.EnclaveEncryptorType; -import org.hyperledger.enclave.testutil.EnclaveType; - -import java.io.IOException; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Optional; - -import io.vertx.core.Vertx; -import org.apache.tuweni.bytes.Bytes; -import org.junit.After; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; -import org.testcontainers.containers.Network; -import org.web3j.crypto.Credentials; -import org.web3j.crypto.RawTransaction; -import org.web3j.crypto.TransactionEncoder; -import org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt; -import org.web3j.protocol.eea.crypto.PrivateTransactionEncoder; -import org.web3j.protocol.eea.crypto.RawPrivateTransaction; -import org.web3j.utils.Base64String; -import org.web3j.utils.Numeric; - -@RunWith(Parameterized.class) -public class PrivacyClusterAcceptanceTest extends PrivacyAcceptanceTestBase { - - private final PrivacyNode alice; - private final PrivacyNode bob; - private final PrivacyNode charlie; - private final EnclaveEncryptorType enclaveEncryptorType; - private final Vertx vertx = Vertx.vertx(); - private final EnclaveFactory enclaveFactory = new EnclaveFactory(vertx); - - @Parameters(name = "{0} enclave type with {1} encryptor") - public static Collection enclaveParameters() { - return Arrays.asList( - new Object[][] { - {TESSERA, NACL}, - {TESSERA, EC} - }); - } - - public PrivacyClusterAcceptanceTest( - final EnclaveType enclaveType, final EnclaveEncryptorType enclaveEncryptorType) - throws IOException { - this.enclaveEncryptorType = enclaveEncryptorType; - final Network containerNetwork = Network.newNetwork(); - alice = - privacyBesu.createPrivateTransactionEnabledMinerNode( - "node1", - PrivacyAccountResolver.ALICE.resolve(enclaveEncryptorType), - enclaveType, - Optional.of(containerNetwork), - false, - false, - false); - bob = - privacyBesu.createPrivateTransactionEnabledNode( - "node2", - PrivacyAccountResolver.BOB.resolve(enclaveEncryptorType), - enclaveType, - Optional.of(containerNetwork), - false, - false, - false); - charlie = - privacyBesu.createPrivateTransactionEnabledNode( - "node3", - PrivacyAccountResolver.CHARLIE.resolve(enclaveEncryptorType), - enclaveType, - Optional.of(containerNetwork), - false, - false, - false); - privacyCluster.start(alice, bob, charlie); - } - - @After - public void cleanUp() { - vertx.close(); - } - - @Test - public void onlyAliceAndBobCanExecuteContract() { - // Contract address is generated from sender address and transaction nonce - final String contractAddress = - EnclaveEncryptorType.EC.equals(enclaveEncryptorType) - ? "0x3e5d325a03ad3ce5640502219833d30b89ce3ce1" - : "0xebf56429e6500e84442467292183d4d621359838"; - - final EventEmitter eventEmitter = - alice.execute( - privateContractTransactions.createSmartContract( - EventEmitter.class, - alice.getTransactionSigningKey(), - alice.getEnclaveKey(), - bob.getEnclaveKey())); - - privateContractVerifier - .validPrivateContractDeployed(contractAddress, alice.getAddress().toString()) - .verify(eventEmitter); - - final String transactionHash = - alice.execute( - privateContractTransactions.callSmartContract( - eventEmitter.getContractAddress(), - eventEmitter.store(BigInteger.ONE).encodeFunctionCall(), - alice.getTransactionSigningKey(), - RESTRICTED, - alice.getEnclaveKey(), - bob.getEnclaveKey())); - - final PrivateTransactionReceipt expectedReceipt = - alice.execute(privacyTransactions.getPrivateTransactionReceipt(transactionHash)); - - bob.verify( - privateTransactionVerifier.validPrivateTransactionReceipt( - transactionHash, expectedReceipt)); - - charlie.verify(privateTransactionVerifier.noPrivateTransactionReceipt(transactionHash)); - - // When Alice executes a contract call in the wrong privacy group the transaction should pass - // but it should NOT return any output - final String transactionHash2 = - alice.execute( - privateContractTransactions.callSmartContract( - eventEmitter.getContractAddress(), - eventEmitter.value().encodeFunctionCall(), - alice.getTransactionSigningKey(), - RESTRICTED, - alice.getEnclaveKey(), - charlie.getEnclaveKey())); - - final PrivateTransactionReceipt expectedReceipt2 = - alice.execute(privacyTransactions.getPrivateTransactionReceipt(transactionHash2)); - - assertThat(expectedReceipt2.getOutput()).isEqualTo("0x"); - - charlie.verify( - privateTransactionVerifier.validPrivateTransactionReceipt( - transactionHash2, expectedReceipt2)); - } - - @Test - public void aliceCanUsePrivDistributeTransaction() { - // Contract address is generated from sender address and transaction nonce - final String contractAddress = - EnclaveEncryptorType.EC.equals(enclaveEncryptorType) - ? "0x3e5d325a03ad3ce5640502219833d30b89ce3ce1" - : "0xebf56429e6500e84442467292183d4d621359838"; - - final RawPrivateTransaction rawPrivateTransaction = - RawPrivateTransaction.createContractTransaction( - BigInteger.ZERO, - BigInteger.ZERO, - BigInteger.ZERO, - Numeric.prependHexPrefix(EventEmitter.BINARY), - Base64String.wrap(alice.getEnclaveKey()), - Collections.singletonList(Base64String.wrap(bob.getEnclaveKey())), - RESTRICTED); - - final String signedPrivateTransaction = - Numeric.toHexString( - PrivateTransactionEncoder.signMessage( - rawPrivateTransaction, Credentials.create(alice.getTransactionSigningKey()))); - final String transactionKey = - alice.execute(privacyTransactions.privDistributeTransaction(signedPrivateTransaction)); - - final Enclave aliceEnclave = enclaveFactory.createVertxEnclave(alice.getEnclave().clientUrl()); - final ReceiveResponse aliceRR = - aliceEnclave.receive( - Bytes.fromHexString(transactionKey).toBase64String(), alice.getEnclaveKey()); - - final Enclave bobEnclave = enclaveFactory.createVertxEnclave(bob.getEnclave().clientUrl()); - final ReceiveResponse bobRR = - bobEnclave.receive( - Bytes.fromHexString(transactionKey).toBase64String(), bob.getEnclaveKey()); - - assertThat(bobRR).usingRecursiveComparison().isEqualTo(aliceRR); - - final RawTransaction pmt = - RawTransaction.createTransaction( - BigInteger.ZERO, - BigInteger.valueOf(1000), - BigInteger.valueOf(65000), - DEFAULT_PRIVACY.toString(), - transactionKey); - - final String signedPmt = - Numeric.toHexString( - TransactionEncoder.signMessage( - pmt, Credentials.create(alice.getTransactionSigningKey()))); - - final String transactionHash = alice.execute(ethTransactions.sendRawTransaction(signedPmt)); - - final String receiptPrivateFrom = - EnclaveEncryptorType.EC.equals(enclaveEncryptorType) - ? "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAES8nC4qT/KdoAoTSF3qs/47DUsDihyVbWiRjZAiyvqp9eSDkqV1RzlM+58oOwnpFRwvWNZM+AxMVxT+MvxdsqMA==" - : "A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; - final ArrayList receiptPrivateFor = - EnclaveEncryptorType.EC.equals(enclaveEncryptorType) - ? new ArrayList<>( - Collections.singletonList( - "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEXIgZqRA25V+3nN+Do6b5r0jiUunub6ubjPhqwHpPxP44uUYh9RKCQNRnsqCJ9PjeTnC8R3ieJk7HWAlycU1bug==")) - : new ArrayList<>( - Collections.singletonList("Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs=")); - final String receiptPrivacyGroupId = - EnclaveEncryptorType.EC.equals(enclaveEncryptorType) - ? "MjuFB4b9Hz+f8zvkWWasxZWRjHWXU4t7B2nOHo4mekA=" - : "DyAOiF/ynpc+JXa2YAGB0bCitSlOMNm+ShmB/7M6C4w="; - - final PrivateTransactionReceipt expectedReceipt = - new PrivateTransactionReceipt( - contractAddress, - "0xfe3b557e8fb62b89f4916b721be55ceb828dbd73", - null, - null, // ignored in the following call, checked separately below - Collections.emptyList(), - "0x023955c49d6265c579561940287449242704d5fd239ff07ea36a3fc7aface61c", - "0x82e521ee16ff13104c5f81e8354ecaaafd5450b710b07f620204032bfe76041a", - receiptPrivateFrom, - receiptPrivateFor, - receiptPrivacyGroupId, - "0x1", - null); - - alice.verify( - privateTransactionVerifier.validPrivateTransactionReceipt( - transactionHash, expectedReceipt, true)); - - final PrivateTransactionReceipt alicePrivateTransactionReceipt = - alice.execute(privacyTransactions.getPrivateTransactionReceipt(transactionHash)); - assertThat(EventEmitter.BINARY) - .contains(alicePrivateTransactionReceipt.getOutput().substring(2)); - - bob.verify( - privateTransactionVerifier.validPrivateTransactionReceipt( - transactionHash, expectedReceipt, true)); - - final PrivateTransactionReceipt bobPrivateTransactionReceipt = - bob.execute(privacyTransactions.getPrivateTransactionReceipt(transactionHash)); - assertThat(EventEmitter.BINARY).contains(bobPrivateTransactionReceipt.getOutput().substring(2)); - } - - @Test - public void aliceCanDeployMultipleTimesInSingleGroup() { - final String firstDeployedAddress = - EnclaveEncryptorType.EC.equals(enclaveEncryptorType) - ? "0x3e5d325a03ad3ce5640502219833d30b89ce3ce1" - : "0xebf56429e6500e84442467292183d4d621359838"; - - final EventEmitter firstEventEmitter = - alice.execute( - privateContractTransactions.createSmartContract( - EventEmitter.class, - alice.getTransactionSigningKey(), - alice.getEnclaveKey(), - bob.getEnclaveKey())); - - privateContractVerifier - .validPrivateContractDeployed(firstDeployedAddress, alice.getAddress().toString()) - .verify(firstEventEmitter); - - final String secondDeployedAddress = - EnclaveEncryptorType.EC.equals(enclaveEncryptorType) - ? "0x5194e214fae257530710d18c868df7a295d9d53b" - : "0x10f807f8a905da5bd319196da7523c6bd768690f"; - - final EventEmitter secondEventEmitter = - alice.execute( - privateContractTransactions.createSmartContract( - EventEmitter.class, - alice.getTransactionSigningKey(), - alice.getEnclaveKey(), - bob.getEnclaveKey())); - - privateContractVerifier - .validPrivateContractDeployed(secondDeployedAddress, alice.getAddress().toString()) - .verify(secondEventEmitter); - } - - @Test - public void canInteractWithMultiplePrivacyGroups() { - // alice deploys contract - final String firstDeployedAddress = - EnclaveEncryptorType.EC.equals(enclaveEncryptorType) - ? "0x760359bc605b3848f5199829bde6b382d90fb8eb" - : "0xff206d21150a8da5b83629d8a722f3135ed532b1"; - - final EventEmitter firstEventEmitter = - alice.execute( - privateContractTransactions.createSmartContract( - EventEmitter.class, - alice.getTransactionSigningKey(), - alice.getEnclaveKey(), - bob.getEnclaveKey(), - charlie.getEnclaveKey())); - - privateContractVerifier - .validPrivateContractDeployed(firstDeployedAddress, alice.getAddress().toString()) - .verify(firstEventEmitter); - - // charlie interacts with contract - final String firstTransactionHash = - charlie.execute( - privateContractTransactions.callSmartContract( - firstEventEmitter.getContractAddress(), - firstEventEmitter.store(BigInteger.ONE).encodeFunctionCall(), - charlie.getTransactionSigningKey(), - RESTRICTED, - charlie.getEnclaveKey(), - alice.getEnclaveKey(), - bob.getEnclaveKey())); - - // alice gets receipt from charlie's interaction - final PrivateTransactionReceipt firstExpectedReceipt = - alice.execute(privacyTransactions.getPrivateTransactionReceipt(firstTransactionHash)); - - // verify bob and charlie have access to the same receipt - bob.verify( - privateTransactionVerifier.validPrivateTransactionReceipt( - firstTransactionHash, firstExpectedReceipt)); - charlie.verify( - privateTransactionVerifier.validPrivateTransactionReceipt( - firstTransactionHash, firstExpectedReceipt)); - - // alice deploys second contract - final String secondDeployedAddress = - EnclaveEncryptorType.EC.equals(enclaveEncryptorType) - ? "0x3e5d325a03ad3ce5640502219833d30b89ce3ce1" - : "0xebf56429e6500e84442467292183d4d621359838"; - - final EventEmitter secondEventEmitter = - alice.execute( - privateContractTransactions.createSmartContract( - EventEmitter.class, - alice.getTransactionSigningKey(), - alice.getEnclaveKey(), - bob.getEnclaveKey())); - - privateContractVerifier - .validPrivateContractDeployed(secondDeployedAddress, alice.getAddress().toString()) - .verify(secondEventEmitter); - - // bob interacts with contract - final String secondTransactionHash = - bob.execute( - privateContractTransactions.callSmartContract( - secondEventEmitter.getContractAddress(), - secondEventEmitter.store(BigInteger.ONE).encodeFunctionCall(), - bob.getTransactionSigningKey(), - RESTRICTED, - bob.getEnclaveKey(), - alice.getEnclaveKey())); - - // alice gets receipt from bob's interaction - final PrivateTransactionReceipt secondExpectedReceipt = - alice.execute(privacyTransactions.getPrivateTransactionReceipt(secondTransactionHash)); - - bob.verify( - privateTransactionVerifier.validPrivateTransactionReceipt( - secondTransactionHash, secondExpectedReceipt)); - - // charlie cannot see the receipt - charlie.verify(privateTransactionVerifier.noPrivateTransactionReceipt(secondTransactionHash)); - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivacyGroupAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivacyGroupAcceptanceTest.java deleted file mode 100644 index a1739b1fc8d..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivacyGroupAcceptanceTest.java +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.privacy; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.enclave.testutil.EnclaveEncryptorType.EC; -import static org.hyperledger.enclave.testutil.EnclaveEncryptorType.NACL; -import static org.hyperledger.enclave.testutil.EnclaveType.TESSERA; -import static org.web3j.utils.Restriction.RESTRICTED; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyAcceptanceTestBase; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.account.PrivacyAccountResolver; -import org.hyperledger.besu.tests.web3j.generated.EventEmitter; -import org.hyperledger.besu.util.LogConfigurator; -import org.hyperledger.enclave.testutil.EnclaveEncryptorType; -import org.hyperledger.enclave.testutil.EnclaveType; - -import java.io.IOException; -import java.math.BigInteger; -import java.util.Arrays; -import java.util.Collection; -import java.util.Optional; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; -import org.testcontainers.containers.Network; -import org.web3j.protocol.besu.response.privacy.PrivacyGroup; -import org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt; -import org.web3j.utils.Base64String; - -@RunWith(Parameterized.class) -public class PrivacyGroupAcceptanceTest extends PrivacyAcceptanceTestBase { - - private final PrivacyNode alice; - private final PrivacyNode bob; - private final PrivacyNode charlie; - - @Parameters(name = "{0} enclave type with {1} encryptor") - public static Collection enclaveParameters() { - return Arrays.asList( - new Object[][] { - {TESSERA, NACL}, - {TESSERA, EC} - }); - } - - public PrivacyGroupAcceptanceTest( - final EnclaveType enclaveType, final EnclaveEncryptorType enclaveEncryptorType) - throws IOException { - - final Network containerNetwork = Network.newNetwork(); - - alice = - privacyBesu.createPrivateTransactionEnabledMinerNode( - "node1", - PrivacyAccountResolver.ALICE.resolve(enclaveEncryptorType), - enclaveType, - Optional.of(containerNetwork), - false, - false, - false); - bob = - privacyBesu.createPrivateTransactionEnabledNode( - "node2", - PrivacyAccountResolver.BOB.resolve(enclaveEncryptorType), - enclaveType, - Optional.of(containerNetwork), - false, - false, - false); - - charlie = - privacyBesu.createPrivateTransactionEnabledNode( - "node3", - PrivacyAccountResolver.CHARLIE.resolve(enclaveEncryptorType), - enclaveType, - Optional.of(containerNetwork), - false, - false, - false); - privacyCluster.start(alice, bob, charlie); - } - - @Test - public void nodeCanCreatePrivacyGroup() { - LogConfigurator.setLevel("", "DEBUG"); - final String privacyGroupId = - alice.execute( - privacyTransactions.createPrivacyGroup( - "myGroupName", "my group description", alice, bob)); - - assertThat(privacyGroupId).isNotNull(); - - final PrivacyGroup expected = - new PrivacyGroup( - privacyGroupId, - PrivacyGroup.Type.PANTHEON, - "myGroupName", - "my group description", - Base64String.wrapList(alice.getEnclaveKey(), bob.getEnclaveKey())); - - alice.verify(privateTransactionVerifier.validPrivacyGroupCreated(expected)); - - bob.verify(privateTransactionVerifier.validPrivacyGroupCreated(expected)); - } - - @Test - public void nodeCanCreatePrivacyGroupWithoutName() { - final String privacyGroupId = - alice.execute( - privacyTransactions.createPrivacyGroup(null, "my group description", alice, bob)); - - assertThat(privacyGroupId).isNotNull(); - - final PrivacyGroup expected = - new PrivacyGroup( - privacyGroupId, - PrivacyGroup.Type.PANTHEON, - "", - "my group description", - Base64String.wrapList(alice.getEnclaveKey(), bob.getEnclaveKey())); - - alice.verify(privateTransactionVerifier.validPrivacyGroupCreated(expected)); - - bob.verify(privateTransactionVerifier.validPrivacyGroupCreated(expected)); - } - - @Test - public void nodeCanCreatePrivacyGroupWithoutDescription() { - final String privacyGroupId = - alice.execute(privacyTransactions.createPrivacyGroup("myGroupName", null, alice, bob)); - - assertThat(privacyGroupId).isNotNull(); - - final PrivacyGroup expected = - new PrivacyGroup( - privacyGroupId, - PrivacyGroup.Type.PANTHEON, - "myGroupName", - "", - Base64String.wrapList(alice.getEnclaveKey(), bob.getEnclaveKey())); - - alice.verify(privateTransactionVerifier.validPrivacyGroupCreated(expected)); - - bob.verify(privateTransactionVerifier.validPrivacyGroupCreated(expected)); - } - - @Test - public void nodeCanCreatePrivacyGroupWithoutOptionalParams() { - final String privacyGroupId = - alice.execute(privacyTransactions.createPrivacyGroup(null, null, alice)); - - assertThat(privacyGroupId).isNotNull(); - - final PrivacyGroup expected = - new PrivacyGroup( - privacyGroupId, - PrivacyGroup.Type.PANTHEON, - "", - "", - Base64String.wrapList(alice.getEnclaveKey())); - - alice.verify(privateTransactionVerifier.validPrivacyGroupCreated(expected)); - } - - @Test - public void canInteractWithMultiplePrivacyGroups() { - final String privacyGroupIdABC = - alice.execute(privacyTransactions.createPrivacyGroup(null, null, alice, bob, charlie)); - - final EventEmitter firstEventEmitter = - alice.execute( - privateContractTransactions.createSmartContractWithPrivacyGroupId( - EventEmitter.class, - alice.getTransactionSigningKey(), - alice.getEnclaveKey(), - privacyGroupIdABC)); - - // charlie interacts with contract - final String firstTransactionHash = - charlie.execute( - privateContractTransactions.callSmartContractWithPrivacyGroupId( - firstEventEmitter.getContractAddress(), - firstEventEmitter.store(BigInteger.ONE).encodeFunctionCall(), - charlie.getTransactionSigningKey(), - RESTRICTED, - charlie.getEnclaveKey(), - privacyGroupIdABC)); - - // alice gets receipt from charlie's interaction - final PrivateTransactionReceipt firstExpectedReceipt = - alice.execute(privacyTransactions.getPrivateTransactionReceipt(firstTransactionHash)); - - // verify bob and charlie have access to the same receipt - bob.verify( - privateTransactionVerifier.validPrivateTransactionReceipt( - firstTransactionHash, firstExpectedReceipt)); - charlie.verify( - privateTransactionVerifier.validPrivateTransactionReceipt( - firstTransactionHash, firstExpectedReceipt)); - - // alice deploys second contract - final String privacyGroupIdAB = - alice.execute(privacyTransactions.createPrivacyGroup(null, null, alice, bob)); - - final EventEmitter secondEventEmitter = - alice.execute( - privateContractTransactions.createSmartContractWithPrivacyGroupId( - EventEmitter.class, - alice.getTransactionSigningKey(), - alice.getEnclaveKey(), - privacyGroupIdAB)); - - // bob interacts with contract - final String secondTransactionHash = - bob.execute( - privateContractTransactions.callSmartContractWithPrivacyGroupId( - secondEventEmitter.getContractAddress(), - secondEventEmitter.store(BigInteger.ONE).encodeFunctionCall(), - bob.getTransactionSigningKey(), - RESTRICTED, - bob.getEnclaveKey(), - privacyGroupIdAB)); - - // alice gets receipt from bob's interaction - final PrivateTransactionReceipt secondExpectedReceipt = - alice.execute(privacyTransactions.getPrivateTransactionReceipt(secondTransactionHash)); - - bob.verify( - privateTransactionVerifier.validPrivateTransactionReceipt( - secondTransactionHash, secondExpectedReceipt)); - - // charlie cannot see the receipt - charlie.verify(privateTransactionVerifier.noPrivateTransactionReceipt(secondTransactionHash)); - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivacyReceiptAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivacyReceiptAcceptanceTest.java deleted file mode 100644 index 0b45d29732a..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivacyReceiptAcceptanceTest.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.privacy; - -import static java.util.Optional.empty; -import static org.web3j.utils.Restriction.RESTRICTED; -import static org.web3j.utils.Restriction.UNRESTRICTED; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.ethereum.privacy.PrivateTransaction; -import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.ParameterizedEnclaveTestBase; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.account.PrivacyAccountResolver; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.miner.MinerTransactions; -import org.hyperledger.enclave.testutil.EnclaveEncryptorType; -import org.hyperledger.enclave.testutil.EnclaveType; - -import java.io.IOException; -import java.util.Optional; - -import org.apache.tuweni.bytes.Bytes; -import org.junit.Test; -import org.web3j.utils.Restriction; - -public class PrivacyReceiptAcceptanceTest extends ParameterizedEnclaveTestBase { - final MinerTransactions minerTransactions = new MinerTransactions(); - - private final PrivacyNode alice; - - public PrivacyReceiptAcceptanceTest( - final Restriction restriction, - final EnclaveType enclaveType, - final EnclaveEncryptorType enclaveEncryptorType) - throws IOException { - super(restriction, enclaveType, enclaveEncryptorType); - - alice = - privacyBesu.createIbft2NodePrivacyEnabled( - "node1", - PrivacyAccountResolver.ALICE.resolve(enclaveEncryptorType), - false, - enclaveType, - Optional.empty(), - false, - false, - restriction == UNRESTRICTED, - "0xAA"); - privacyCluster.start(alice); - } - - @Test - public void createPrivateTransactionReceiptSuccessfulTransaction() { - final Transaction onlyAlice = createPrivacyGroup("Only Alice", "", alice); - - final String privacyGroupId = alice.execute(onlyAlice); - - final PrivateTransaction validTransaction = - createSignedTransaction(alice, privacyGroupId, empty()); - final BytesValueRLPOutput rlpOutput = getRLPOutput(validTransaction); - - final Hash transactionHash = - alice.execute(privacyTransactions.sendRawTransaction(rlpOutput.encoded().toHexString())); - - // Successful PMT - alice.getBesu().verify(eth.expectSuccessfulTransactionReceipt(transactionHash.toString())); - // Successful private transaction - alice.getBesu().verify(priv.getSuccessfulTransactionReceipt(transactionHash)); - } - - @Test - public void createPrivateTransactionReceiptFailedTransaction() { - final Transaction onlyAlice = createPrivacyGroup("Only Alice", "", alice); - - final String privacyGroupId = alice.execute(onlyAlice); - - final PrivateTransaction invalidPayloadTransaction = - createSignedTransaction( - alice, privacyGroupId, Optional.of(Bytes.fromBase64String("invalidPayload"))); - final BytesValueRLPOutput rlpOutput = getRLPOutput(invalidPayloadTransaction); - - final Hash transactionHash = - alice.execute(privacyTransactions.sendRawTransaction(rlpOutput.encoded().toHexString())); - - // Successful PMT - alice.getBesu().verify(eth.expectSuccessfulTransactionReceipt(transactionHash.toString())); - // Failed private transaction - alice.getBesu().verify(priv.getFailedTransactionReceipt(transactionHash)); - } - - @Test - public void createPrivateTransactionReceiptInvalidTransaction() { - final Transaction onlyAlice = createPrivacyGroup("Only Alice", "", alice); - - final String privacyGroupId = alice.execute(onlyAlice); - - final PrivateTransaction validTransaction = - createSignedTransaction(alice, privacyGroupId, empty()); - final BytesValueRLPOutput rlpOutput = getRLPOutput(validTransaction); - - // Stop mining, to allow adding duplicate nonce block - alice.getBesu().execute(minerTransactions.minerStop()); - - final Hash transactionHash1 = - alice.execute(privacyTransactions.sendRawTransaction(rlpOutput.encoded().toHexString())); - final Hash transactionHash2 = - alice.execute(privacyTransactions.sendRawTransaction(rlpOutput.encoded().toHexString())); - - // Start mining again - alice.getBesu().execute(minerTransactions.minerStart()); - - // Successful PMTs - alice.getBesu().verify(eth.expectSuccessfulTransactionReceipt(transactionHash1.toString())); - alice.getBesu().verify(eth.expectSuccessfulTransactionReceipt(transactionHash2.toString())); - // Successful first private transaction - alice.getBesu().verify(priv.getSuccessfulTransactionReceipt(transactionHash1)); - // Invalid second private transaction - alice.getBesu().verify(priv.getInvalidTransactionReceipt(transactionHash2)); - } - - private BytesValueRLPOutput getRLPOutput(final PrivateTransaction privateTransaction) { - final BytesValueRLPOutput bvrlpo = new BytesValueRLPOutput(); - privateTransaction.writeTo(bvrlpo); - return bvrlpo; - } - - private PrivateTransaction createSignedTransaction( - final PrivacyNode node, final String privacyGoupId, final Optional payload) { - - org.hyperledger.besu.plugin.data.Restriction besuRestriction = - restriction == RESTRICTED - ? org.hyperledger.besu.plugin.data.Restriction.RESTRICTED - : org.hyperledger.besu.plugin.data.Restriction.UNRESTRICTED; - - final Bytes defaultPayload = Bytes.wrap(new byte[] {}); - return PrivateTransaction.builder() - .nonce(0) - .gasPrice(Wei.of(999999)) - .gasLimit(3000000) - .to(null) - .value(Wei.ZERO) - .payload(payload.orElse(defaultPayload)) - .sender(node.getAddress()) - .privateFrom(Bytes.fromBase64String(node.getEnclaveKey())) - .restriction(besuRestriction) - .privacyGroupId(Bytes.fromBase64String(privacyGoupId)) - .signAndBuild(node.getBesu().getPrivacyParameters().getSigningKeyPair().get()); - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivateContractPublicStateAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivateContractPublicStateAcceptanceTest.java deleted file mode 100644 index 83a7ce66d8b..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivateContractPublicStateAcceptanceTest.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.privacy; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.web3j.utils.Restriction.UNRESTRICTED; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.ParameterizedEnclaveTestBase; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.account.PrivacyAccountResolver; -import org.hyperledger.besu.tests.web3j.generated.CrossContractReader; -import org.hyperledger.besu.tests.web3j.generated.EventEmitter; -import org.hyperledger.besu.tests.web3j.generated.RemoteSimpleStorage; -import org.hyperledger.besu.tests.web3j.generated.SimpleStorage; -import org.hyperledger.enclave.testutil.EnclaveEncryptorType; -import org.hyperledger.enclave.testutil.EnclaveType; - -import java.io.IOException; -import java.math.BigInteger; -import java.util.Optional; - -import org.junit.Test; -import org.testcontainers.containers.Network; -import org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt; -import org.web3j.protocol.core.RemoteFunctionCall; -import org.web3j.protocol.core.methods.response.TransactionReceipt; -import org.web3j.protocol.exceptions.TransactionException; -import org.web3j.tx.exceptions.ContractCallException; -import org.web3j.utils.Restriction; - -public class PrivateContractPublicStateAcceptanceTest extends ParameterizedEnclaveTestBase { - - private final PrivacyNode transactionNode; - - public PrivateContractPublicStateAcceptanceTest( - final Restriction restriction, - final EnclaveType enclaveType, - final EnclaveEncryptorType enclaveEncryptorType) - throws IOException { - super(restriction, enclaveType, enclaveEncryptorType); - final Network containerNetwork = Network.newNetwork(); - - final PrivacyNode minerNode = - privacyBesu.createPrivateTransactionEnabledMinerNode( - restriction + "-miner-node", - PrivacyAccountResolver.ALICE.resolve(enclaveEncryptorType), - enclaveType, - Optional.of(containerNetwork), - false, - false, - restriction == UNRESTRICTED); - - transactionNode = - privacyBesu.createPrivateTransactionEnabledNode( - restriction + "-transaction-node", - PrivacyAccountResolver.BOB.resolve(enclaveEncryptorType), - enclaveType, - Optional.of(containerNetwork), - false, - false, - restriction == UNRESTRICTED); - - privacyCluster.start(minerNode, transactionNode); - } - - @Test - public void mustAllowAccessToPublicStateFromPrivateTx() throws Exception { - final EventEmitter publicEventEmitter = - transactionNode.execute(contractTransactions.createSmartContract(EventEmitter.class)); - - final TransactionReceipt receipt = publicEventEmitter.store(BigInteger.valueOf(12)).send(); - assertThat(receipt).isNotNull(); - - final CrossContractReader reader = - transactionNode.execute( - privateContractTransactions.createSmartContract( - CrossContractReader.class, - transactionNode.getTransactionSigningKey(), - transactionNode.getEnclaveKey())); - - final RemoteFunctionCall remoteFunctionCall = - reader.read(publicEventEmitter.getContractAddress()); - final BigInteger result = remoteFunctionCall.send(); - - assertThat(result).isEqualTo(BigInteger.valueOf(12)); - } - - @Test - public void mustNotAllowAccessToPrivateStateFromPublicTx() throws Exception { - final EventEmitter privateEventEmitter = - transactionNode.execute( - (privateContractTransactions.createSmartContract( - EventEmitter.class, - transactionNode.getTransactionSigningKey(), - transactionNode.getEnclaveKey()))); - - final TransactionReceipt receipt = privateEventEmitter.store(BigInteger.valueOf(12)).send(); - assertThat(receipt).isNotNull(); - - final CrossContractReader publicReader = - transactionNode.execute( - contractTransactions.createSmartContract(CrossContractReader.class)); - final RemoteFunctionCall functionCall = - publicReader.read(privateEventEmitter.getContractAddress()); - assertThatThrownBy(functionCall::send).isInstanceOf(ContractCallException.class); - } - - @Test - public void privateContractMustNotBeAbleToCallPublicContractWhichChangesState() throws Exception { - final CrossContractReader privateReader = - transactionNode.execute( - privateContractTransactions.createSmartContract( - CrossContractReader.class, - transactionNode.getTransactionSigningKey(), - transactionNode.getEnclaveKey())); - - final CrossContractReader publicReader = - transactionNode.execute( - contractTransactions.createSmartContract(CrossContractReader.class)); - - assertThatExceptionOfType(TransactionException.class) - .isThrownBy(() -> privateReader.incrementRemote(publicReader.getContractAddress()).send()) - .returns( - "0x", e -> ((PrivateTransactionReceipt) e.getTransactionReceipt().get()).getOutput()); - } - - @Test - public void privateContractMustNotBeAbleToCallPublicContractWhichInstantiatesContract() - throws Exception { - final CrossContractReader privateReader = - transactionNode.execute( - privateContractTransactions.createSmartContract( - CrossContractReader.class, - transactionNode.getTransactionSigningKey(), - transactionNode.getEnclaveKey())); - - final CrossContractReader publicReader = - transactionNode.execute( - contractTransactions.createSmartContract(CrossContractReader.class)); - - assertThatExceptionOfType(TransactionException.class) - .isThrownBy(() -> privateReader.deployRemote(publicReader.getContractAddress()).send()) - .returns(0, e -> e.getTransactionReceipt().get().getLogs().size()); - } - - @Test - public void privateContractMustNotBeAbleToCallSelfDestructOnPublicContract() throws Exception { - final CrossContractReader privateReader = - transactionNode.execute( - privateContractTransactions.createSmartContract( - CrossContractReader.class, - transactionNode.getTransactionSigningKey(), - transactionNode.getEnclaveKey())); - - final CrossContractReader publicReader = - transactionNode - .getBesu() - .execute(contractTransactions.createSmartContract(CrossContractReader.class)); - - assertThatExceptionOfType(TransactionException.class) - .isThrownBy(() -> privateReader.remoteDestroy(publicReader.getContractAddress()).send()) - .withMessage( - "Transaction null has failed with status: 0x0. Gas used: unknown. Revert reason: '0x'.") - .returns( - "0x", e -> ((PrivateTransactionReceipt) e.getTransactionReceipt().get()).getOutput()); - } - - @Test - public void privateContractCanCallPublicContractThatCallsPublicContract() throws Exception { - final SimpleStorage simpleStorage = - transactionNode - .getBesu() - .execute(contractTransactions.createSmartContract(SimpleStorage.class)); - - final RemoteSimpleStorage remoteSimpleStorage = - transactionNode - .getBesu() - .execute(contractTransactions.createSmartContract(RemoteSimpleStorage.class)); - - remoteSimpleStorage.setRemote(simpleStorage.getContractAddress()).send(); - - final RemoteSimpleStorage reallyRemoteSimpleStorage = - transactionNode - .getBesu() - .execute(contractTransactions.createSmartContract(RemoteSimpleStorage.class)); - - reallyRemoteSimpleStorage.setRemote(remoteSimpleStorage.getContractAddress()).send(); - - simpleStorage.set(BigInteger.valueOf(42)).send(); - - assertThat(simpleStorage.get().send()).isEqualTo(BigInteger.valueOf(42)); - assertThat(remoteSimpleStorage.get().send()).isEqualTo(BigInteger.valueOf(42)); - assertThat(reallyRemoteSimpleStorage.get().send()).isEqualTo(BigInteger.valueOf(42)); - - final RemoteSimpleStorage privateRemoteSimpleStorage = - transactionNode.execute( - privateContractTransactions.createSmartContract( - RemoteSimpleStorage.class, - transactionNode.getTransactionSigningKey(), - transactionNode.getEnclaveKey())); - - privateRemoteSimpleStorage.setRemote(simpleStorage.getContractAddress()).send(); - assertThat(privateRemoteSimpleStorage.get().send()).isEqualTo(BigInteger.valueOf(42)); - - privateRemoteSimpleStorage.setRemote(reallyRemoteSimpleStorage.getContractAddress()).send(); - assertThat(privateRemoteSimpleStorage.get().send()).isEqualTo(BigInteger.valueOf(42)); - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivateGenesisAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivateGenesisAcceptanceTest.java deleted file mode 100644 index 29d5b655e3c..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivateGenesisAcceptanceTest.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.privacy; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.web3j.utils.Restriction.RESTRICTED; -import static org.web3j.utils.Restriction.UNRESTRICTED; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.ParameterizedEnclaveTestBase; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.account.PrivacyAccountResolver; -import org.hyperledger.besu.tests.web3j.generated.EventEmitter; -import org.hyperledger.enclave.testutil.EnclaveEncryptorType; -import org.hyperledger.enclave.testutil.EnclaveType; - -import java.io.IOException; -import java.math.BigInteger; -import java.util.Optional; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.web3j.protocol.core.methods.response.EthCall; -import org.web3j.utils.Restriction; - -@RunWith(Parameterized.class) -public class PrivateGenesisAcceptanceTest extends ParameterizedEnclaveTestBase { - private final PrivacyNode alice; - - public PrivateGenesisAcceptanceTest( - final Restriction restriction, - final EnclaveType enclaveType, - final EnclaveEncryptorType enclaveEncryptorType) - throws IOException { - - super(restriction, enclaveType, enclaveEncryptorType); - - alice = - privacyBesu.createIbft2NodePrivacyEnabledWithGenesis( - "node1", - PrivacyAccountResolver.ALICE.resolve(enclaveEncryptorType), - true, - enclaveType, - Optional.empty(), - false, - false, - restriction == UNRESTRICTED, - "AA"); - - privacyCluster.start(alice); - } - - @Test - public void canInteractWithPrivateGenesisPreCompile() throws Exception { - final String privacyGroupId = createPrivacyGroup(); - - final EventEmitter eventEmitter = - alice.execute( - privateContractTransactions.loadSmartContractWithPrivacyGroupId( - "0x1000000000000000000000000000000000000001", - EventEmitter.class, - alice.getTransactionSigningKey(), - alice.getEnclaveKey(), - privacyGroupId)); - - eventEmitter.store(BigInteger.valueOf(42)).send(); - - final EthCall response = - alice.execute( - privacyTransactions.privCall( - privacyGroupId, eventEmitter, eventEmitter.value().encodeFunctionCall())); - - final String value = response.getValue(); - - assertThat(new BigInteger(value.substring(2), 16)).isEqualByComparingTo(BigInteger.valueOf(42)); - } - - private String createPrivacyGroup() { - if (restriction == RESTRICTED) { - return alice.execute(privacyTransactions.createPrivacyGroup("name", "description", alice)); - } else if (restriction == UNRESTRICTED) { - return "gsvwYfGPurL7wgXKmgFtCamXarAl9fA5jaSXi8TLpJw="; - } else { - throw new RuntimeException("Do not know how to handle " + restriction); - } - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivateLogFilterAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivateLogFilterAcceptanceTest.java deleted file mode 100644 index a6837f95465..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/PrivateLogFilterAcceptanceTest.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.privacy; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.web3j.utils.Restriction.UNRESTRICTED; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.ParameterizedEnclaveTestBase; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.account.PrivacyAccountResolver; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.util.LogFilterJsonParameter; -import org.hyperledger.besu.tests.web3j.generated.EventEmitter; -import org.hyperledger.enclave.testutil.EnclaveEncryptorType; -import org.hyperledger.enclave.testutil.EnclaveType; - -import java.io.IOException; -import java.math.BigInteger; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import org.junit.Test; -import org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt; -import org.web3j.protocol.core.methods.response.EthLog.LogResult; -import org.web3j.utils.Restriction; - -@SuppressWarnings("rawtypes") -public class PrivateLogFilterAcceptanceTest extends ParameterizedEnclaveTestBase { - - private final PrivacyNode node; - - public PrivateLogFilterAcceptanceTest( - final Restriction restriction, - final EnclaveType enclaveType, - final EnclaveEncryptorType enclaveEncryptorType) - throws IOException { - - super(restriction, enclaveType, enclaveEncryptorType); - - node = - privacyBesu.createPrivateTransactionEnabledMinerNode( - restriction + "-node", - PrivacyAccountResolver.ALICE.resolve(enclaveEncryptorType), - enclaveType, - Optional.empty(), - false, - false, - restriction == UNRESTRICTED); - - privacyCluster.start(node); - } - - @Test - public void installAndUninstallFilter() { - final String privacyGroupId = createPrivacyGroup(); - final EventEmitter eventEmitterContract = deployEventEmitterContract(privacyGroupId); - - final LogFilterJsonParameter filter = - blockRangeLogFilter("earliest", "latest", eventEmitterContract.getContractAddress()); - - final String filterId = node.execute(privacyTransactions.newFilter(privacyGroupId, filter)); - - final boolean filterUninstalled = - node.execute(privacyTransactions.uninstallFilter(privacyGroupId, filterId)); - - assertThat(filterUninstalled).isTrue(); - } - - @Test - public void getFilterLogs() { - final String privacyGroupId = createPrivacyGroup(); - final EventEmitter eventEmitterContract = deployEventEmitterContract(privacyGroupId); - - final LogFilterJsonParameter filter = - blockRangeLogFilter("earliest", "latest", eventEmitterContract.getContractAddress()); - final String filterId = node.execute(privacyTransactions.newFilter(privacyGroupId, filter)); - - updateContractValue(privacyGroupId, eventEmitterContract, 1); - - final List logs = - node.execute(privacyTransactions.getFilterLogs(privacyGroupId, filterId)); - - assertThat(logs).hasSize(1); - } - - @Test - public void getFilterChanges() { - final String privacyGroupId = createPrivacyGroup(); - final EventEmitter eventEmitterContract = deployEventEmitterContract(privacyGroupId); - - final LogFilterJsonParameter filter = - blockRangeLogFilter("earliest", "latest", eventEmitterContract.getContractAddress()); - final String filterId = node.execute(privacyTransactions.newFilter(privacyGroupId, filter)); - - updateContractValue(privacyGroupId, eventEmitterContract, 1); - updateContractValue(privacyGroupId, eventEmitterContract, 2); - - assertThat(node.execute(privacyTransactions.getFilterChanges(privacyGroupId, filterId))) - .hasSize(2); - - updateContractValue(privacyGroupId, eventEmitterContract, 3); - - assertThat(node.execute(privacyTransactions.getFilterChanges(privacyGroupId, filterId))) - .hasSize(1); - } - - private LogFilterJsonParameter blockRangeLogFilter( - final String fromBlock, final String toBlock, final String contractAddress) { - return new LogFilterJsonParameter( - fromBlock, toBlock, List.of(contractAddress), Collections.emptyList(), null); - } - - private String createPrivacyGroup() { - return node.execute(createPrivacyGroup("myGroupName", "my group description", node)); - } - - private EventEmitter deployEventEmitterContract(final String privacyGroupId) { - final EventEmitter eventEmitter = - node.execute( - privateContractTransactions.createSmartContractWithPrivacyGroupId( - EventEmitter.class, - node.getTransactionSigningKey(), - restriction, - node.getEnclaveKey(), - privacyGroupId)); - - privateContractVerifier - .validPrivateContractDeployed( - eventEmitter.getContractAddress(), node.getAddress().toString()) - .verify(eventEmitter); - - return eventEmitter; - } - - private PrivateTransactionReceipt updateContractValue( - final String privacyGroupId, final EventEmitter eventEmitterContract, final int value) { - final String transactionHash = - node.execute( - privateContractTransactions.callSmartContractWithPrivacyGroupId( - eventEmitterContract.getContractAddress(), - eventEmitterContract.store(BigInteger.valueOf(value)).encodeFunctionCall(), - node.getTransactionSigningKey(), - restriction, - node.getEnclaveKey(), - privacyGroupId)); - - return node.execute(privacyTransactions.getPrivateTransactionReceipt(transactionHash)); - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/contracts/PrivacyGroupTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/contracts/PrivacyGroupTest.java deleted file mode 100644 index b805fa43c4e..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/contracts/PrivacyGroupTest.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.privacy.contracts; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import org.hyperledger.besu.privacy.contracts.generated.DefaultFlexiblePrivacyGroupManagementContract; -import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; -import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.web3j.protocol.core.RemoteFunctionCall; -import org.web3j.protocol.core.methods.response.TransactionReceipt; -import org.web3j.protocol.exceptions.TransactionException; -import org.web3j.utils.Base64String; - -@SuppressWarnings("unchecked") -public class PrivacyGroupTest extends AcceptanceTestBase { - - private final Base64String firstParticipant = - Base64String.wrap("A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="); - private final Base64String secondParticipant = - Base64String.wrap("Ko2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs="); - private final Base64String thirdParticipant = - Base64String.wrap("Jo2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs="); - private DefaultFlexiblePrivacyGroupManagementContract defaultPrivacyGroupManagementContract; - - private static final String RAW_FIRST_PARTICIPANT = "0x5aa68ac0"; - private static final String RAW_ADD_PARTICIPANT = - "0x965a25ef00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000202a8d9b56a0fe9cd94d60be4413bcb721d3a7be27ed8e28b3a6346df874ee141b"; - private static final String RAW_REMOVE_PARTICIPANT = - "0x1f52a8ee000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000202a8d9b56a0fe9cd94d60be4413bcb721d3a7be27ed8e28b3a6346df874ee141b"; - private static final String RAW_LOCK = "0xf83d08ba"; - private static final String RAW_UNLOCK = "0xa69df4b5"; - private static final String RAW_CAN_EXECUTE = "0x78b90337"; - private static final String RAW_GET_VERSION = "0x0d8e6e2c"; - - private BesuNode minerNode; - - @BeforeEach - public void setUp() throws Exception { - minerNode = besu.createMinerNode("node"); - cluster.start(minerNode); - defaultPrivacyGroupManagementContract = - minerNode.execute( - contractTransactions.createSmartContract( - DefaultFlexiblePrivacyGroupManagementContract.class)); - } - - @Test - public void rlp() throws Exception { - final String contractAddress = "0x42699a7612a82f1d9c36148af9c77354759b210b"; - assertThat(defaultPrivacyGroupManagementContract.isValid()).isEqualTo(true); - contractVerifier - .validTransactionReceipt(contractAddress) - .verify(defaultPrivacyGroupManagementContract); - // 0x0b0235be - assertThat(defaultPrivacyGroupManagementContract.getParticipants().encodeFunctionCall()) - .isEqualTo(RAW_FIRST_PARTICIPANT); - // 0xf744b089 - assertThat( - defaultPrivacyGroupManagementContract - .addParticipants(Collections.singletonList(secondParticipant.raw())) - .encodeFunctionCall()) - .isEqualTo(RAW_ADD_PARTICIPANT); - // 0xf744b089 - assertThat( - defaultPrivacyGroupManagementContract - .removeParticipant(secondParticipant.raw()) - .encodeFunctionCall()) - .isEqualTo(RAW_REMOVE_PARTICIPANT); - assertThat(defaultPrivacyGroupManagementContract.lock().encodeFunctionCall()) - .isEqualTo(RAW_LOCK); - assertThat(defaultPrivacyGroupManagementContract.unlock().encodeFunctionCall()) - .isEqualTo(RAW_UNLOCK); - assertThat(defaultPrivacyGroupManagementContract.canExecute().encodeFunctionCall()) - .isEqualTo(RAW_CAN_EXECUTE); - assertThat(defaultPrivacyGroupManagementContract.getVersion().encodeFunctionCall()) - .isEqualTo(RAW_GET_VERSION); - } - - @Test - public void canInitiallyAddParticipants() throws Exception { - final RemoteFunctionCall transactionReceiptRemoteFunctionCall = - defaultPrivacyGroupManagementContract.addParticipants( - Arrays.asList(firstParticipant.raw(), secondParticipant.raw())); - transactionReceiptRemoteFunctionCall.send(); - final List participants = - defaultPrivacyGroupManagementContract.getParticipants().send(); - assertThat(participants.size()).isEqualTo(2); - assertThat(firstParticipant.raw()).isEqualTo(participants.get(0)); - assertThat(secondParticipant.raw()).isEqualTo(participants.get(1)); - } - - @Test - public void canRemoveParticipant() throws Exception { - defaultPrivacyGroupManagementContract - .addParticipants(Arrays.asList(firstParticipant.raw(), secondParticipant.raw())) - .send(); - final List participants = - defaultPrivacyGroupManagementContract.getParticipants().send(); - assertThat(participants.size()).isEqualTo(2); - assertThat(firstParticipant.raw()).isEqualTo(participants.get(0)); - assertThat(secondParticipant.raw()).isEqualTo(participants.get(1)); - defaultPrivacyGroupManagementContract.removeParticipant(secondParticipant.raw()).send(); - final List participantsAfterRemove = - defaultPrivacyGroupManagementContract.getParticipants().send(); - assertThat(participantsAfterRemove.size()).isEqualTo(1); - assertThat(firstParticipant.raw()).isEqualTo(participantsAfterRemove.get(0)); - } - - @Test - public void cannotAddToContractWhenNotLocked() throws Exception { - defaultPrivacyGroupManagementContract - .addParticipants(Collections.singletonList(thirdParticipant.raw())) - .send(); - - assertThatThrownBy( - () -> - defaultPrivacyGroupManagementContract - .addParticipants(Collections.singletonList(secondParticipant.raw())) - .send()) - .isInstanceOf(TransactionException.class); - } - - @Test - public void ensureContractIsLockedAfterDeploy() throws Exception { - assertThat(defaultPrivacyGroupManagementContract.canExecute().send()).isFalse(); - } - - @Test - public void ensurePrivacyGroupVersionIsAlwaysDifferent() throws Exception { - defaultPrivacyGroupManagementContract - .addParticipants(Collections.singletonList(secondParticipant.raw())) - .send(); - final byte[] version1 = defaultPrivacyGroupManagementContract.getVersion().send(); - defaultPrivacyGroupManagementContract.lock().send(); - defaultPrivacyGroupManagementContract - .addParticipants(Collections.singletonList(thirdParticipant.raw())) - .send(); - final byte[] version2 = defaultPrivacyGroupManagementContract.getVersion().send(); - defaultPrivacyGroupManagementContract.removeParticipant(secondParticipant.raw()).send(); - final byte[] version3 = defaultPrivacyGroupManagementContract.getVersion().send(); - - assertThat(version1).isNotEqualTo(version2); - assertThat(version1).isNotEqualTo(version3); - assertThat(version2).isNotEqualTo(version3); - } - - @Test - public void canAddTwiceToContractWhenCallLock() throws Exception { - defaultPrivacyGroupManagementContract - .addParticipants(Arrays.asList(firstParticipant.raw(), thirdParticipant.raw())) - .send(); - defaultPrivacyGroupManagementContract.lock().send(); - defaultPrivacyGroupManagementContract - .addParticipants(Collections.singletonList(secondParticipant.raw())) - .send(); - - final List participants = - defaultPrivacyGroupManagementContract.getParticipants().send(); - assertThat(participants.size()).isEqualTo(3); - assertThat(firstParticipant.raw()).isEqualTo(participants.get(0)); - assertThat(thirdParticipant.raw()).isEqualTo(participants.get(1)); - assertThat(secondParticipant.raw()).isEqualTo(participants.get(2)); - } - - @Test - public void cannotLockTwice() throws Exception { - defaultPrivacyGroupManagementContract - .addParticipants(Collections.singletonList(thirdParticipant.raw())) - .send(); - defaultPrivacyGroupManagementContract.lock().send(); - assertThatThrownBy(() -> defaultPrivacyGroupManagementContract.lock().send()) - .isInstanceOf(TransactionException.class); - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/contracts/PrivacyProxyTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/contracts/PrivacyProxyTest.java deleted file mode 100644 index 931770ee321..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/contracts/PrivacyProxyTest.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.privacy.contracts; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import org.hyperledger.besu.privacy.contracts.generated.DefaultFlexiblePrivacyGroupManagementContract; -import org.hyperledger.besu.privacy.contracts.generated.FlexiblePrivacyGroupManagementProxy; -import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; -import org.hyperledger.besu.tests.acceptance.dsl.account.Accounts; -import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.web3j.crypto.Credentials; -import org.web3j.protocol.Web3j; -import org.web3j.protocol.exceptions.TransactionException; -import org.web3j.protocol.http.HttpService; -import org.web3j.tx.gas.DefaultGasProvider; -import org.web3j.utils.Base64String; - -@SuppressWarnings("unchecked") -public class PrivacyProxyTest extends AcceptanceTestBase { - - private final Base64String firstParticipant = - Base64String.wrap("93Ky7lXwFkMc7+ckoFgUMku5bpr9tz4zhmWmk9RlNng="); - private final Base64String secondParticipant = - Base64String.wrap("9iaJ6OObl6TUWYjXAOyZsL0VaDPwF+tRFkMwwYSeqqw="); - private final Base64String thirdParticipant = - Base64String.wrap("Jo2bVqD+nNlNYL5EE7y3IdOnviftjiizpjRt+HTuFBs="); - private FlexiblePrivacyGroupManagementProxy flexiblePrivacyGroupManagementProxy; - - private static final String RAW_GET_PARTICIPANTS = "0x5aa68ac0"; - private static final String RAW_ADD_PARTICIPANT = - "0x965a25ef0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020f772b2ee55f016431cefe724a05814324bb96e9afdb73e338665a693d4653678"; - - private BesuNode minerNode; - private DefaultFlexiblePrivacyGroupManagementContract - defaultFlexiblePrivacyGroupManagementContract; - - @BeforeEach - public void setUp() throws Exception { - minerNode = besu.createMinerNode("node"); - cluster.start(minerNode); - defaultFlexiblePrivacyGroupManagementContract = - minerNode.execute( - contractTransactions.createSmartContract( - DefaultFlexiblePrivacyGroupManagementContract.class)); - flexiblePrivacyGroupManagementProxy = - minerNode.execute( - contractTransactions.createSmartContract( - FlexiblePrivacyGroupManagementProxy.class, - defaultFlexiblePrivacyGroupManagementContract.getContractAddress())); - } - - @Test - public void rlp() throws Exception { - assertThat(flexiblePrivacyGroupManagementProxy.isValid()).isEqualTo(true); - contractVerifier - .validTransactionReceipt(flexiblePrivacyGroupManagementProxy.getContractAddress()) - .verify(flexiblePrivacyGroupManagementProxy); - assertThat(flexiblePrivacyGroupManagementProxy.getParticipants().encodeFunctionCall()) - .isEqualTo(RAW_GET_PARTICIPANTS); - - assertThat( - flexiblePrivacyGroupManagementProxy - .addParticipants(List.of(firstParticipant.raw())) - .encodeFunctionCall()) - .isEqualTo(RAW_ADD_PARTICIPANT); - } - - @Test - public void deploysWithNoParticipant() throws Exception { - final List participants = flexiblePrivacyGroupManagementProxy.getParticipants().send(); - assertThat(participants.size()).isEqualTo(0); - } - - @Test - public void canAddParticipants() throws Exception { - flexiblePrivacyGroupManagementProxy - .addParticipants(Arrays.asList(firstParticipant.raw(), secondParticipant.raw())) - .send(); - final List participants = flexiblePrivacyGroupManagementProxy.getParticipants().send(); - assertThat(participants.size()).isEqualTo(2); - assertThat(firstParticipant.raw()).isEqualTo(participants.get(0)); - assertThat(secondParticipant.raw()).isEqualTo(participants.get(1)); - } - - @Test - public void nonOwnerCannotUpgrade() throws Exception { - flexiblePrivacyGroupManagementProxy - .addParticipants(Arrays.asList(firstParticipant.raw(), secondParticipant.raw())) - .send(); - final List participants = flexiblePrivacyGroupManagementProxy.getParticipants().send(); - assertThat(participants.size()).isEqualTo(2); - assertThat(firstParticipant.raw()).isEqualTo(participants.get(0)); - assertThat(secondParticipant.raw()).isEqualTo(participants.get(1)); - - final DefaultFlexiblePrivacyGroupManagementContract upgradedContract = - minerNode.execute( - contractTransactions.createSmartContract( - DefaultFlexiblePrivacyGroupManagementContract.class)); - - final HttpService httpService = - new HttpService( - "http://" + minerNode.getHostName() + ":" + minerNode.getJsonRpcPort().get()); - final Web3j web3j = Web3j.build(httpService); - - // load the proxy contract, use it with another signer - final FlexiblePrivacyGroupManagementProxy proxyContractAccount2 = - FlexiblePrivacyGroupManagementProxy.load( - flexiblePrivacyGroupManagementProxy.getContractAddress(), - web3j, - Credentials.create(Accounts.GENESIS_ACCOUNT_TWO_PRIVATE_KEY), - new DefaultGasProvider()); - // contract is the proxy contract and uses genesis account 2. It should not be able to upgrade - // the contract, because it is not the owner of "upgradedContract" - assertThatThrownBy( - () -> proxyContractAccount2.upgradeTo(upgradedContract.getContractAddress()).send()) - .isInstanceOf(TransactionException.class); - } - - @Test - public void ownerCanUpgrade() throws Exception { - flexiblePrivacyGroupManagementProxy - .addParticipants(Arrays.asList(firstParticipant.raw(), secondParticipant.raw())) - .send(); - final List participants = flexiblePrivacyGroupManagementProxy.getParticipants().send(); - assertThat(participants.size()).isEqualTo(2); - assertThat(firstParticipant.raw()).isEqualTo(participants.get(0)); - assertThat(secondParticipant.raw()).isEqualTo(participants.get(1)); - - final DefaultFlexiblePrivacyGroupManagementContract upgradedContract = - minerNode.execute( - contractTransactions.createSmartContract( - DefaultFlexiblePrivacyGroupManagementContract.class)); - - flexiblePrivacyGroupManagementProxy.upgradeTo(upgradedContract.getContractAddress()).send(); - final List participantsAfterUpgrade = - flexiblePrivacyGroupManagementProxy.getParticipants().send(); - assertThat(participantsAfterUpgrade.size()).isEqualTo(2); - assertThat(firstParticipant.raw()).isEqualTo(participantsAfterUpgrade.get(0)); - assertThat(secondParticipant.raw()).isEqualTo(participantsAfterUpgrade.get(1)); - } - - @Test - public void canAddTwiceToContractWhenCallLock() throws Exception { - flexiblePrivacyGroupManagementProxy - .addParticipants(Arrays.asList(firstParticipant.raw(), thirdParticipant.raw())) - .send(); - flexiblePrivacyGroupManagementProxy.lock().send(); - flexiblePrivacyGroupManagementProxy - .addParticipants(Collections.singletonList(secondParticipant.raw())) - .send(); - final List participants = flexiblePrivacyGroupManagementProxy.getParticipants().send(); - assertThat(participants.size()).isEqualTo(3); - assertThat(firstParticipant.raw()).isEqualTo(participants.get(0)); - assertThat(thirdParticipant.raw()).isEqualTo(participants.get(1)); - assertThat(secondParticipant.raw()).isEqualTo(participants.get(2)); - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/FlexibleMultiTenancyAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/FlexibleMultiTenancyAcceptanceTest.java deleted file mode 100644 index da692f21ba1..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/FlexibleMultiTenancyAcceptanceTest.java +++ /dev/null @@ -1,491 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.privacy.multitenancy; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.hyperledger.enclave.testutil.EnclaveEncryptorType.EC; -import static org.hyperledger.enclave.testutil.EnclaveEncryptorType.NACL; -import static org.hyperledger.enclave.testutil.EnclaveType.TESSERA; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.account.PrivacyAccountResolver; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.contract.CallPrivateSmartContractFunction; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.transaction.CreateFlexiblePrivacyGroupTransaction; -import org.hyperledger.besu.tests.acceptance.dsl.privacy.util.LogFilterJsonParameter; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.perm.PermissioningTransactions; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.privacy.PrivacyRequestFactory; -import org.hyperledger.besu.tests.acceptance.privacy.FlexiblePrivacyAcceptanceTestBase; -import org.hyperledger.besu.tests.web3j.generated.EventEmitter; -import org.hyperledger.enclave.testutil.EnclaveEncryptorType; -import org.hyperledger.enclave.testutil.EnclaveType; - -import java.math.BigInteger; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.web3j.crypto.Credentials; -import org.web3j.protocol.besu.response.privacy.PrivateTransactionReceipt; -import org.web3j.protocol.core.methods.response.EthCall; -import org.web3j.utils.Base64String; -import org.web3j.utils.Restriction; - -@RunWith(Parameterized.class) -public class FlexibleMultiTenancyAcceptanceTest extends FlexiblePrivacyAcceptanceTestBase { - - private final EnclaveType enclaveType; - private final EnclaveEncryptorType enclaveEncryptorType; - - public FlexibleMultiTenancyAcceptanceTest( - final EnclaveType enclaveType, final EnclaveEncryptorType enclaveEncryptorType) { - this.enclaveType = enclaveType; - this.enclaveEncryptorType = enclaveEncryptorType; - } - - @Parameterized.Parameters(name = "{0} enclave type with {1} encryptor") - public static Collection enclaveParameters() { - return Arrays.asList( - new Object[][] { - {TESSERA, NACL}, - {TESSERA, EC} - }); - } - - private static final PermissioningTransactions permissioningTransactions = - new PermissioningTransactions(); - private static final long VALUE_SET = 10L; - - private PrivacyNode alice; - private MultiTenancyPrivacyNode aliceMultiTenancyPrivacyNode; - - @Before - public void setUp() throws Exception { - alice = - privacyBesu.createFlexiblePrivacyGroupEnabledMinerNode( - "node1", - PrivacyAccountResolver.MULTI_TENANCY.resolve(enclaveEncryptorType), - true, - enclaveType, - Optional.empty()); - final BesuNode aliceBesu = alice.getBesu(); - privacyCluster.startNodes(alice); - final String alice1Token = - aliceBesu.execute(permissioningTransactions.createSuccessfulLogin("user", "pegasys")); - aliceBesu.useAuthenticationTokenInHeaderForJsonRpc(alice1Token); - final String alice2Token = - aliceBesu.execute(permissioningTransactions.createSuccessfulLogin("user2", "Password2")); - final String alice3Token = - aliceBesu.execute(permissioningTransactions.createSuccessfulLogin("user3", "Password3")); - privacyCluster.awaitPeerCount(alice); - - final String alice1EnclaveKey = alice.getEnclave().getPublicKeys().get(0); - final String alice2EnclaveKey = alice.getEnclave().getPublicKeys().get(1); - final String alice3EnclaveKey = alice.getEnclave().getPublicKeys().get(2); - - aliceMultiTenancyPrivacyNode = new MultiTenancyPrivacyNode(alice); - aliceMultiTenancyPrivacyNode - .addTenantWithToken(alice1EnclaveKey, alice1Token) - .addTenantWithToken(alice2EnclaveKey, alice2Token) - .addTenantWithToken(alice3EnclaveKey, alice3Token); - } - - @After - public void tearDown() { - privacyCluster.close(); - } - - @Test - public void createPrivacyGroup() { - createFlexiblePrivacyGroup(alice); - } - - @Test - public void createPrivacyGroupWithAllTenants() { - final MultiTenancyPrivacyGroup privacyGroup = new MultiTenancyPrivacyGroup(); - privacyGroup.addNodeWithTenants( - aliceMultiTenancyPrivacyNode, aliceMultiTenancyPrivacyNode.getTenants()); - createFlexiblePrivacyGroup(privacyGroup); - } - - @Test - public void noAccessWhenNotAMember() { - final MultiTenancyPrivacyGroup twoTenantsFromAlice = new MultiTenancyPrivacyGroup(); - final List tenants = aliceMultiTenancyPrivacyNode.getTenants(); - final String removedTenant = tenants.remove(tenants.size() - 1); - twoTenantsFromAlice.addNodeWithTenants(aliceMultiTenancyPrivacyNode, tenants); - final String privacyGroupId = createFlexiblePrivacyGroup(twoTenantsFromAlice); - - final MultiTenancyPrivacyNode multiTenancyPrivacyNode = - twoTenantsFromAlice.getPrivacyNodes().get(0); - final String tenant = tenants.get(0); - final PrivacyNode privacyNode = multiTenancyPrivacyNode.getPrivacyNode(); - final BesuNode privacyNodeBesu = privacyNode.getBesu(); - privacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( - multiTenancyPrivacyNode.getTokenForTenant(tenant)); - final EventEmitter eventEmitter = - privacyNode.execute( - privateContractTransactions.createSmartContractWithPrivacyGroupId( - EventEmitter.class, - privacyNode.getTransactionSigningKey(), - tenant, - privacyGroupId)); - - final String transactionHash = getContractDeploymentCommitmentHash(eventEmitter); - - // check that a member can get the transaction receipt - privacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( - multiTenancyPrivacyNode.getTokenForTenant(tenant)); - privacyNode.verify( - privateTransactionVerifier.validPrivateTransactionReceipt( - transactionHash, - (PrivateTransactionReceipt) eventEmitter.getTransactionReceipt().get())); - final String actual = - privacyNode - .execute( - privacyTransactions.privGetCode( - privacyGroupId, - Address.fromHexString(eventEmitter.getContractAddress()), - "latest")) - .toHexString(); - assertThat(EventEmitter.BINARY).contains(actual.substring(2)); - - // check that getting the transaction receipt does not work if you are not a member - privacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( - multiTenancyPrivacyNode.getTokenForTenant(removedTenant)); - privacyNode.verify( - privateTransactionVerifier.noPrivateTransactionReceipt( - transactionHash)); // returning null because the RPC is using the enclave key - - // check that getting the code of the event emitter does not work when you are not a member - assertThatThrownBy( - () -> - privacyNode.execute( - privacyTransactions.privGetCode( - privacyGroupId, - Address.fromHexString(eventEmitter.getContractAddress()), - "latest"))) - .hasMessageContaining("Unauthorized"); - - final LogFilterJsonParameter filterParameter = - new LogFilterJsonParameter( - "earliest", - "latest", - List.of(eventEmitter.getContractAddress()), - Collections.emptyList(), - null); - - // create a valid filter - privacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( - multiTenancyPrivacyNode.getTokenForTenant(tenant)); - final String filterId = - privacyNode.execute(privacyTransactions.newFilter(privacyGroupId, filterParameter)); - - privacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( - multiTenancyPrivacyNode.getTokenForTenant(tenant)); - final CallPrivateSmartContractFunction storeTransaction = - privateContractTransactions.callSmartContractWithPrivacyGroupId( - eventEmitter.getContractAddress(), - eventEmitter.store(BigInteger.valueOf(VALUE_SET)).encodeFunctionCall(), - privacyNode.getTransactionSigningKey(), - Restriction.RESTRICTED, - tenant, - privacyGroupId); - final String storeTransactionHash = privacyNode.execute(storeTransaction); - - privacyNode.execute(privacyTransactions.getPrivateTransactionReceipt(storeTransactionHash)); - - // check that getting the filter changes works for a member - assertThat(privacyNode.execute(privacyTransactions.getFilterChanges(privacyGroupId, filterId))) - .hasSize(1); - - // check that getting the filter changes does not work if you are not a member - privacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( - multiTenancyPrivacyNode.getTokenForTenant(removedTenant)); - assertThatThrownBy( - () -> - privacyNode.execute(privacyTransactions.getFilterChanges(privacyGroupId, filterId))) - .hasMessageContaining("Unauthorized"); - - // check that getting the filter logs works for a member - privacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( - multiTenancyPrivacyNode.getTokenForTenant(tenant)); - assertThat(privacyNode.execute(privacyTransactions.getFilterLogs(privacyGroupId, filterId))) - .hasSize(3); // create privacy group, deploy event emitter, store on event emitter - - // check that getting the filter logs does not work if you are not a member - privacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( - multiTenancyPrivacyNode.getTokenForTenant(removedTenant)); - assertThatThrownBy( - () -> privacyNode.execute(privacyTransactions.getFilterLogs(privacyGroupId, filterId))) - .hasMessageContaining("Unauthorized"); - - // check that getting the logs works for a member - privacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( - multiTenancyPrivacyNode.getTokenForTenant(tenant)); - assertThat( - privacyNode.execute(privacyTransactions.privGetLogs(privacyGroupId, filterParameter))) - .hasSize(3); // create privacy group, deploy event emitter, store on event emitter - - // check that getting the logs does not work if you are not a member - privacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( - multiTenancyPrivacyNode.getTokenForTenant(removedTenant)); - assertThatThrownBy( - () -> - privacyNode.execute( - privacyTransactions.privGetLogs(privacyGroupId, filterParameter))) - .hasMessageContaining("Unauthorized"); - - final List base64StringList = - tenants.stream().map(Base64String::wrap).collect(Collectors.toList()); - - // check that a member can find the on-chain privacy group - privacyNode - .getBesu() - .useAuthenticationTokenInHeaderForJsonRpc( - multiTenancyPrivacyNode.getTokenForTenant(tenant)); - final List group = - privacyNode.execute( - privacyTransactions.findFlexiblePrivacyGroup( - Base64String.unwrapList(base64StringList))); - assertThat(group.size()).isEqualTo(1); - assertThat(group.get(0).getMembers()).containsAll(base64StringList).hasSize(2); - - // check that when you are not a member you cannot find the privacy group - privacyNode - .getBesu() - .useAuthenticationTokenInHeaderForJsonRpc( - multiTenancyPrivacyNode.getTokenForTenant(removedTenant)); - assertThatThrownBy( - () -> - privacyNode.execute( - privacyTransactions.findFlexiblePrivacyGroup( - Base64String.unwrapList(base64StringList)))) - .hasMessageContaining("Error finding flexible privacy group"); - - // check that a member can do a priv_call - privacyNode - .getBesu() - .useAuthenticationTokenInHeaderForJsonRpc( - multiTenancyPrivacyNode.getTokenForTenant(tenant)); - final EthCall readValue = - privacyNode.execute( - privacyTransactions.privCall( - privacyGroupId, eventEmitter, eventEmitter.value().encodeFunctionCall())); - assertThat(new BigInteger(readValue.getValue().substring(2), 16)) - .isEqualByComparingTo(BigInteger.valueOf(VALUE_SET)); - - // check that when you are not a member you cannot do a priv_call - privacyNode - .getBesu() - .useAuthenticationTokenInHeaderForJsonRpc( - multiTenancyPrivacyNode.getTokenForTenant(removedTenant)); - assertThatThrownBy( - () -> - privacyNode.execute( - privacyTransactions.privCall( - privacyGroupId, eventEmitter, eventEmitter.value().encodeFunctionCall()))) - .hasMessageContaining("Unauthorized"); - - // check that a member can do a priv_getTransaction - privacyNode - .getBesu() - .useAuthenticationTokenInHeaderForJsonRpc( - multiTenancyPrivacyNode.getTokenForTenant(tenant)); - final PrivacyRequestFactory.GetPrivateTransactionResponse privTransaction = - privacyNode.execute(privacyTransactions.privGetTransaction(storeTransactionHash)); - assertThat(privTransaction.getResult().getPrivacyGroupId()).isEqualTo(privacyGroupId); - - // check that when you are not a member you cannot do a priv_getTransaction - privacyNode - .getBesu() - .useAuthenticationTokenInHeaderForJsonRpc( - multiTenancyPrivacyNode.getTokenForTenant(removedTenant)); - assertThatThrownBy( - () -> privacyNode.execute(privacyTransactions.privGetTransaction(storeTransactionHash))) - .hasMessageContaining( - "Expecting actual not to be null"); // TODO: returning null because the RPC is using the - // enclave key - } - - @Test - public void removedMemberCannotGetFilterChanges() { - final MultiTenancyPrivacyGroup allTenantsFromAlice = new MultiTenancyPrivacyGroup(); - final List tenants = aliceMultiTenancyPrivacyNode.getTenants(); - allTenantsFromAlice.addNodeWithTenants(aliceMultiTenancyPrivacyNode, tenants); - final String privacyGroupId = createFlexiblePrivacyGroup(allTenantsFromAlice); - final MultiTenancyPrivacyNode multiTenancyPrivacyNode = - allTenantsFromAlice.getPrivacyNodes().get(0); - final String groupCreatingTenant = allTenantsFromAlice.getGroupCreatingTenant(); - final String tenantToBeRemoved = - tenants.stream().filter(t -> !t.equals(groupCreatingTenant)).findFirst().orElseThrow(); - final PrivacyNode groupCreatingPrivacyNode = allTenantsFromAlice.getGroupCreatingPrivacyNode(); - final BesuNode groupCreatingPrivacyNodeBesu = groupCreatingPrivacyNode.getBesu(); - groupCreatingPrivacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( - multiTenancyPrivacyNode.getTokenForTenant(groupCreatingTenant)); - - final EventEmitter eventEmitter = - groupCreatingPrivacyNode.execute( - privateContractTransactions.createSmartContractWithPrivacyGroupId( - EventEmitter.class, - groupCreatingPrivacyNode.getTransactionSigningKey(), - groupCreatingTenant, - privacyGroupId)); - - final LogFilterJsonParameter filterParameter = - new LogFilterJsonParameter( - "earliest", - "latest", - List.of(eventEmitter.getContractAddress()), - Collections.emptyList(), - null); - - final String filterId = - groupCreatingPrivacyNode.execute( - privacyTransactions.newFilter(privacyGroupId, filterParameter)); - - final CallPrivateSmartContractFunction storeTransaction = - privateContractTransactions.callSmartContractWithPrivacyGroupId( - eventEmitter.getContractAddress(), - eventEmitter.store(BigInteger.valueOf(VALUE_SET)).encodeFunctionCall(), - groupCreatingPrivacyNode.getTransactionSigningKey(), - Restriction.RESTRICTED, - groupCreatingTenant, - privacyGroupId); - final String storeTransactionHash = groupCreatingPrivacyNode.execute(storeTransaction); - - groupCreatingPrivacyNode.execute( - privacyTransactions.getPrivateTransactionReceipt(storeTransactionHash)); - - // check that getting the filter changes works for a member - groupCreatingPrivacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( - multiTenancyPrivacyNode.getTokenForTenant(tenantToBeRemoved)); - - assertThat( - groupCreatingPrivacyNode.execute( - privacyTransactions.getFilterChanges(privacyGroupId, filterId))) - .hasSize(1); - - groupCreatingPrivacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( - multiTenancyPrivacyNode.getTokenForTenant(groupCreatingTenant)); - final CallPrivateSmartContractFunction store2Transaction = - privateContractTransactions.callSmartContractWithPrivacyGroupId( - eventEmitter.getContractAddress(), - eventEmitter.store(BigInteger.valueOf(VALUE_SET)).encodeFunctionCall(), - groupCreatingPrivacyNode.getTransactionSigningKey(), - Restriction.RESTRICTED, - groupCreatingTenant, - privacyGroupId); - final String store2TransactionHash = groupCreatingPrivacyNode.execute(store2Transaction); - - groupCreatingPrivacyNode.execute( - privacyTransactions.getPrivateTransactionReceipt(store2TransactionHash)); - - // now remove from privacy group - final String removeTransactionHash = - removeFromPrivacyGroup( - privacyGroupId, - groupCreatingPrivacyNode, - groupCreatingTenant, - Credentials.create(groupCreatingPrivacyNode.getTransactionSigningKey()), - tenantToBeRemoved); - groupCreatingPrivacyNode.execute( - privacyTransactions.getPrivateTransactionReceipt(removeTransactionHash)); - - // check that it does not work anymore when member has been removed - groupCreatingPrivacyNodeBesu.useAuthenticationTokenInHeaderForJsonRpc( - multiTenancyPrivacyNode.getTokenForTenant(tenantToBeRemoved)); - assertThatThrownBy( - () -> - groupCreatingPrivacyNode.execute( - privacyTransactions.getFilterChanges(privacyGroupId, filterId))) - .hasMessageContaining("Unauthorized"); - } - - private String createFlexiblePrivacyGroup(final MultiTenancyPrivacyGroup group) { - final List multiTenancyPrivacyNodes = group.getPrivacyNodes(); - final MultiTenancyPrivacyNode groupCreatorMultiTenancyPrivacyNode = - multiTenancyPrivacyNodes.get(0); - final PrivacyNode groupCreatorNode = group.getGroupCreatingPrivacyNode(); - final String groupCreatorTenant = group.getGroupCreatingTenant(); - final List members = group.getTenants(); - final String token = groupCreatorMultiTenancyPrivacyNode.getTokenForTenant(groupCreatorTenant); - final CreateFlexiblePrivacyGroupTransaction createTx = - privacyTransactions.createFlexiblePrivacyGroup( - groupCreatorNode, groupCreatorTenant, members, token); - - final PrivacyRequestFactory.PrivxCreatePrivacyGroupResponse createResponse = - groupCreatorNode.execute(createTx); - final String privacyGroupId = createResponse.getPrivacyGroupId(); - - final List base64StringList = - members.stream().map(Base64String::wrap).collect(Collectors.toList()); - for (final MultiTenancyPrivacyNode mtpn : multiTenancyPrivacyNodes) { - final PrivacyNode privacyNode = mtpn.getPrivacyNode(); - for (final String tenant : mtpn.getTenants()) { - if (members.contains(tenant)) { - privacyNode - .getBesu() - .useAuthenticationTokenInHeaderForJsonRpc(mtpn.getTokenForTenant(tenant)); - privacyNode.verify(flexiblePrivacyGroupExists(privacyGroupId, base64StringList)); - } - } - } - groupCreatorNode.getBesu().useAuthenticationTokenInHeaderForJsonRpc(token); - final String commitmentHash = - callGetParticipantsMethodAndReturnCommitmentHash( - privacyGroupId, groupCreatorNode, groupCreatorTenant); - final PrivateTransactionReceipt expectedReceipt = - buildExpectedAddMemberTransactionReceipt( - privacyGroupId, groupCreatorNode, groupCreatorTenant, members); - - for (final MultiTenancyPrivacyNode mtpn : multiTenancyPrivacyNodes) { - final PrivacyNode privacyNode = mtpn.getPrivacyNode(); - for (final String tenant : mtpn.getTenants()) { - if (members.contains(tenant)) { - privacyNode - .getBesu() - .useAuthenticationTokenInHeaderForJsonRpc(mtpn.getTokenForTenant(tenant)); - privacyNode.verify( - privateTransactionVerifier.validPrivateTransactionReceipt( - commitmentHash, expectedReceipt)); - } - } - } - - return privacyGroupId; - } - - private String removeFromPrivacyGroup( - final String privacyGroupId, - final PrivacyNode node, - final String nodeRemovingMember, - final Credentials signer, - final String memberBeingRemoved) { - return node.execute( - privacyTransactions.removeFromPrivacyGroup( - privacyGroupId, nodeRemovingMember, signer, memberBeingRemoved)); - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyAcceptanceTest.java deleted file mode 100644 index e2fdd2188b1..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyAcceptanceTest.java +++ /dev/null @@ -1,407 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.privacy.multitenancy; - -import static com.github.tomakehurst.wiremock.client.WireMock.ok; -import static com.github.tomakehurst.wiremock.client.WireMock.post; -import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; -import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.hyperledger.besu.ethereum.core.PrivacyParameters.DEFAULT_PRIVACY; - -import org.hyperledger.besu.crypto.KeyPair; -import org.hyperledger.besu.crypto.SignatureAlgorithm; -import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.enclave.types.PrivacyGroup; -import org.hyperledger.besu.enclave.types.ReceiveResponse; -import org.hyperledger.besu.enclave.types.SendResponse; -import org.hyperledger.besu.ethereum.privacy.PrivacyGroupUtil; -import org.hyperledger.besu.ethereum.privacy.PrivateTransaction; -import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; -import org.hyperledger.besu.plugin.data.Restriction; -import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; -import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; -import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.Cluster; -import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.ClusterConfiguration; -import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.ClusterConfigurationBuilder; - -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; -import javax.annotation.Nonnull; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.tomakehurst.wiremock.junit.WireMockRule; -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; - -public class MultiTenancyAcceptanceTest extends AcceptanceTestBase { - private BesuNode node; - private final ObjectMapper mapper = new ObjectMapper(); - private Cluster multiTenancyCluster; - - private static final Supplier SIGNATURE_ALGORITHM = - Suppliers.memoize(SignatureAlgorithmFactory::getInstance); - private static final KeyPair TEST_KEY = - SIGNATURE_ALGORITHM - .get() - .createKeyPair( - SIGNATURE_ALGORITHM - .get() - .createPrivateKey( - new BigInteger( - "853d7f0010fd86d0d7811c1f9d968ea89a24484a8127b4a483ddf5d2cfec766d", 16))); - private static final String PRIVACY_GROUP_ID = "B1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; - private static final String PARTICIPANT_ENCLAVE_KEY0 = - "A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; - private static final Bytes LEAGCY_PRIVATE_FROM = Bytes.fromBase64String(PARTICIPANT_ENCLAVE_KEY0); - private static final String PARTICIPANT_ENCLAVE_KEY1 = - "sgFkVOyFndZe/5SAZJO5UYbrl7pezHetveriBBWWnE8="; - private static final List LEGACY_PRIVATE_FOR = - List.of(Bytes.fromBase64String(PARTICIPANT_ENCLAVE_KEY1)); - private static final String PARTICIPANT_ENCLAVE_KEY2 = - "R1kW75NQC9XX3kwNpyPjCBFflM29+XvnKKS9VLrUkzo="; - private static final String PARTICIPANT_ENCLAVE_KEY3 = - "A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; - private final Address senderAddress = - Address.wrap(Bytes.fromHexString(accounts.getPrimaryBenefactor().getAddress())); - - @Rule public WireMockRule wireMockRule = new WireMockRule(options().dynamicPort()); - - @Before - public void setUp() throws Exception { - final ClusterConfiguration clusterConfiguration = - new ClusterConfigurationBuilder().awaitPeerDiscovery(false).build(); - multiTenancyCluster = new Cluster(clusterConfiguration, net); - node = - besu.createNodeWithMultiTenantedPrivacy( - "node1", - "http://127.0.0.1:" + wireMockRule.port(), - "authentication/auth_priv.toml", - "authentication/auth_priv_key", - false); - multiTenancyCluster.start(node); - final String token = - node.execute(permissioningTransactions.createSuccessfulLogin("user", "pegasys")); - node.useAuthenticationTokenInHeaderForJsonRpc(token); - } - - @After - public void tearDown() { - multiTenancyCluster.close(); - } - - @Test - public void privGetPrivacyPrecompileAddressShouldReturnExpectedAddress() { - node.verify(priv.getPrivacyPrecompileAddress(DEFAULT_PRIVACY)); - } - - @Test - public void privGetPrivateTransactionSuccessShouldReturnExpectedPrivateTransaction() - throws JsonProcessingException { - final PrivateTransaction validSignedPrivateTransaction = - getValidSignedPrivateTransaction(senderAddress); - - receiveEnclaveStub(validSignedPrivateTransaction); - retrievePrivacyGroupEnclaveStub(); - sendEnclaveStub(PARTICIPANT_ENCLAVE_KEY1); - - final Hash transactionHash = - node.execute( - privacyTransactions.sendRawTransaction( - getRLPOutput(validSignedPrivateTransaction).encoded().toHexString())); - node.verify(priv.getSuccessfulTransactionReceipt(transactionHash)); - node.verify(priv.getPrivateTransaction(transactionHash, validSignedPrivateTransaction)); - } - - @Test - public void privCreatePrivacyGroupSuccessShouldReturnNewId() throws JsonProcessingException { - createPrivacyGroupEnclaveStub(); - - node.verify( - priv.createPrivacyGroup( - List.of(PARTICIPANT_ENCLAVE_KEY1, PARTICIPANT_ENCLAVE_KEY2, PARTICIPANT_ENCLAVE_KEY3), - "GroupName", - "Group description.", - PRIVACY_GROUP_ID)); - } - - @Test - public void privDeletePrivacyGroupSuccessShouldReturnId() throws JsonProcessingException { - retrievePrivacyGroupEnclaveStub(); - deletePrivacyGroupEnclaveStub(); - - node.verify(priv.deletePrivacyGroup(PRIVACY_GROUP_ID)); - } - - @Test - public void privFindPrivacyGroupSuccessShouldReturnExpectedGroupMembership() - throws JsonProcessingException { - final List groupMembership = - List.of( - testPrivacyGroup(singletonList(PARTICIPANT_ENCLAVE_KEY0), PrivacyGroup.Type.PANTHEON), - testPrivacyGroup(singletonList(PARTICIPANT_ENCLAVE_KEY0), PrivacyGroup.Type.PANTHEON), - testPrivacyGroup(singletonList(PARTICIPANT_ENCLAVE_KEY0), PrivacyGroup.Type.PANTHEON)); - - findPrivacyGroupEnclaveStub(groupMembership); - - node.verify(priv.findPrivacyGroup(groupMembership.size(), PARTICIPANT_ENCLAVE_KEY0)); - } - - @Test - public void eeaSendRawTransactionSuccessShouldReturnPrivateTransactionHash() - throws JsonProcessingException { - final PrivateTransaction validSignedPrivateTransaction = - getValidSignedPrivateTransaction(senderAddress); - - retrievePrivacyGroupEnclaveStub(); - sendEnclaveStub(PARTICIPANT_ENCLAVE_KEY1); - receiveEnclaveStub(validSignedPrivateTransaction); - - node.verify( - priv.eeaSendRawTransaction( - getRLPOutput(validSignedPrivateTransaction).encoded().toHexString())); - } - - @Test - public void privGetTransactionCountSuccessShouldReturnExpectedTransactionCount() - throws JsonProcessingException { - final PrivateTransaction validSignedPrivateTransaction = - getValidSignedPrivateTransaction(senderAddress); - final String accountAddress = validSignedPrivateTransaction.getSender().toHexString(); - final BytesValueRLPOutput rlpOutput = getRLPOutput(validSignedPrivateTransaction); - - retrievePrivacyGroupEnclaveStub(); - sendEnclaveStub(PARTICIPANT_ENCLAVE_KEY1); - receiveEnclaveStub(validSignedPrivateTransaction); - - node.verify(priv.getTransactionCount(accountAddress, PRIVACY_GROUP_ID, 0)); - final Hash transactionReceipt = - node.execute(privacyTransactions.sendRawTransaction(rlpOutput.encoded().toHexString())); - - node.verify(priv.getSuccessfulTransactionReceipt(transactionReceipt)); - node.verify(priv.getTransactionCount(accountAddress, PRIVACY_GROUP_ID, 1)); - } - - @Test - public void privDistributeRawTransactionSuccessShouldReturnEnclaveKey() - throws JsonProcessingException { - final String enclaveResponseKeyBytes = - Bytes.wrap(Bytes.fromBase64String(PARTICIPANT_ENCLAVE_KEY1)).toString(); - - retrievePrivacyGroupEnclaveStub(); - sendEnclaveStub(PARTICIPANT_ENCLAVE_KEY1); - - node.verify( - priv.distributeRawTransaction( - getRLPOutput(getValidSignedPrivateTransaction(senderAddress)).encoded().toHexString(), - enclaveResponseKeyBytes)); - } - - @Test - public void privGetTransactionReceiptSuccessShouldReturnTransactionReceiptAfterMined() - throws JsonProcessingException { - final PrivateTransaction validSignedPrivateTransaction = - getValidSignedPrivateTransaction(senderAddress); - final BytesValueRLPOutput rlpOutput = getRLPOutput(validSignedPrivateTransaction); - - retrievePrivacyGroupEnclaveStub(); - sendEnclaveStub(PARTICIPANT_ENCLAVE_KEY1); - receiveEnclaveStub(validSignedPrivateTransaction); - - final Hash transactionReceipt = - node.execute(privacyTransactions.sendRawTransaction(rlpOutput.encoded().toHexString())); - - node.verify(priv.getSuccessfulTransactionReceipt(transactionReceipt)); - } - - @Test - public void privGetEeaTransactionCountSuccessShouldReturnExpectedTransactionCount() - throws JsonProcessingException { - final PrivateTransaction validSignedPrivateTransaction = - getValidLegacySignedPrivateTransaction(senderAddress); - final String accountAddress = validSignedPrivateTransaction.getSender().toHexString(); - final String privateTxRlp = getRLPOutput(validSignedPrivateTransaction).encoded().toHexString(); - - retrieveEeaPrivacyGroupEnclaveStub(validSignedPrivateTransaction); - sendEnclaveStub( - Bytes32.ZERO.toBase64String()); // can be any value, as we are stubbing the enclave - receiveEnclaveStubEea(validSignedPrivateTransaction); - - final String privateFrom = validSignedPrivateTransaction.getPrivateFrom().toBase64String(); - final String[] privateFor = - validSignedPrivateTransaction.getPrivateFor().orElseThrow().stream() - .map(Bytes::toBase64String) - .toArray(String[]::new); - node.verify(priv.getEeaTransactionCount(accountAddress, privateFrom, privateFor, 0)); - - final Hash transactionHash = node.execute(privacyTransactions.sendRawTransaction(privateTxRlp)); - - node.verify(priv.getSuccessfulTransactionReceipt(transactionHash)); - - node.verify(priv.getEeaTransactionCount(accountAddress, privateFrom, privateFor, 1)); - } - - @Nonnull - private Bytes32 getPrivacyGroupIdFromEeaTransaction( - final PrivateTransaction validSignedPrivateTransaction) { - return PrivacyGroupUtil.calculateEeaPrivacyGroupId( - validSignedPrivateTransaction.getPrivateFrom(), - validSignedPrivateTransaction.getPrivateFor().get()); - } - - private void findPrivacyGroupEnclaveStub(final List groupMembership) - throws JsonProcessingException { - final String findGroupResponse = mapper.writeValueAsString(groupMembership); - stubFor(post("/findPrivacyGroup").willReturn(ok(findGroupResponse))); - } - - private void createPrivacyGroupEnclaveStub() throws JsonProcessingException { - final String createGroupResponse = - mapper.writeValueAsString(testPrivacyGroup(emptyList(), PrivacyGroup.Type.PANTHEON)); - stubFor(post("/createPrivacyGroup").willReturn(ok(createGroupResponse))); - } - - private void deletePrivacyGroupEnclaveStub() throws JsonProcessingException { - final String deleteGroupResponse = mapper.writeValueAsString(PRIVACY_GROUP_ID); - stubFor(post("/deletePrivacyGroup").willReturn(ok(deleteGroupResponse))); - } - - private void retrievePrivacyGroupEnclaveStub() throws JsonProcessingException { - final String retrieveGroupResponse = - mapper.writeValueAsString( - testPrivacyGroup( - List.of(PARTICIPANT_ENCLAVE_KEY0, PARTICIPANT_ENCLAVE_KEY1), - PrivacyGroup.Type.PANTHEON)); - stubFor(post("/retrievePrivacyGroup").willReturn(ok(retrieveGroupResponse))); - } - - private void retrieveEeaPrivacyGroupEnclaveStub(final PrivateTransaction tx) - throws JsonProcessingException { - final ArrayList members = new ArrayList<>(); - members.add(tx.getPrivateFrom().toBase64String()); - members.addAll( - tx.getPrivateFor().orElseThrow().stream() - .map(Bytes::toBase64String) - .collect(Collectors.toList())); - final String retrieveGroupResponse = - mapper.writeValueAsString(testPrivacyGroupEea(members, PrivacyGroup.Type.LEGACY)); - stubFor(post("/retrievePrivacyGroup").willReturn(ok(retrieveGroupResponse))); - } - - private void sendEnclaveStub(final String testKey) throws JsonProcessingException { - final String sendResponse = mapper.writeValueAsString(new SendResponse(testKey)); - stubFor(post("/send").willReturn(ok(sendResponse))); - } - - private void receiveEnclaveStub(final PrivateTransaction privTx) throws JsonProcessingException { - final BytesValueRLPOutput rlpOutput = getRLPOutputForReceiveResponse(privTx); - final String senderKey = privTx.getPrivateFrom().toBase64String(); - final String receiveResponse = - mapper.writeValueAsString( - new ReceiveResponse( - rlpOutput.encoded().toBase64String().getBytes(UTF_8), PRIVACY_GROUP_ID, senderKey)); - stubFor(post("/receive").willReturn(ok(receiveResponse))); - } - - private void receiveEnclaveStubEea(final PrivateTransaction privTx) - throws JsonProcessingException { - final BytesValueRLPOutput rlpOutput = getRLPOutputForReceiveResponse(privTx); - final String senderKey = privTx.getPrivateFrom().toBase64String(); - final String receiveResponse = - mapper.writeValueAsString( - new ReceiveResponse( - rlpOutput.encoded().toBase64String().getBytes(UTF_8), - getPrivacyGroupIdFromEeaTransaction(privTx).toBase64String(), - senderKey)); - stubFor(post("/receive").willReturn(ok(receiveResponse))); - } - - private BytesValueRLPOutput getRLPOutputForReceiveResponse( - final PrivateTransaction privateTransaction) { - final BytesValueRLPOutput bvrlpo = new BytesValueRLPOutput(); - privateTransaction.writeTo(bvrlpo); - return bvrlpo; - } - - private BytesValueRLPOutput getRLPOutput(final PrivateTransaction privateTransaction) { - final BytesValueRLPOutput bvrlpo = new BytesValueRLPOutput(); - privateTransaction.writeTo(bvrlpo); - return bvrlpo; - } - - private PrivacyGroup testPrivacyGroup( - final List groupMembers, final PrivacyGroup.Type groupType) { - return new PrivacyGroup(PRIVACY_GROUP_ID, groupType, "test", "testGroup", groupMembers); - } - - private PrivacyGroup testPrivacyGroupEea( - final List groupMembers, final PrivacyGroup.Type groupType) { - final Bytes32 privacyGroupId = - PrivacyGroupUtil.calculateEeaPrivacyGroupId( - Bytes.fromBase64String(groupMembers.get(0)), - groupMembers.stream() - .map(gm -> Bytes.fromBase64String(gm)) - .collect(Collectors.toList())); - return new PrivacyGroup( - privacyGroupId.toBase64String(), groupType, "test", "testGroup", groupMembers); - } - - private static PrivateTransaction getValidSignedPrivateTransaction(final Address senderAddress) { - return PrivateTransaction.builder() - .nonce(0) - .gasPrice(Wei.ZERO) - .gasLimit(3000000) - .to(null) - .value(Wei.ZERO) - .payload(Bytes.wrap(new byte[] {})) - .sender(senderAddress) - .chainId(BigInteger.valueOf(1337)) - .privateFrom(Bytes.fromBase64String(PARTICIPANT_ENCLAVE_KEY0)) - .restriction(Restriction.RESTRICTED) - .privacyGroupId(Bytes.fromBase64String(PRIVACY_GROUP_ID)) - .signAndBuild(TEST_KEY); - } - - private static PrivateTransaction getValidLegacySignedPrivateTransaction( - final Address senderAddress) { - return PrivateTransaction.builder() - .nonce(0) - .gasPrice(Wei.ZERO) - .gasLimit(3000000) - .to(null) - .value(Wei.ZERO) - .payload(Bytes.wrap(new byte[] {})) - .sender(senderAddress) - .chainId(BigInteger.valueOf(1337)) - .privateFrom(LEAGCY_PRIVATE_FROM) - .privateFor(LEGACY_PRIVATE_FOR) - .restriction(Restriction.RESTRICTED) - .signAndBuild(TEST_KEY); - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyPrivacyGroup.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyPrivacyGroup.java deleted file mode 100644 index a53a173a0d7..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyPrivacyGroup.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.privacy.multitenancy; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; - -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -public class MultiTenancyPrivacyGroup { - - private final Map> map; - - public MultiTenancyPrivacyGroup() { - this.map = new HashMap<>(); - } - - public MultiTenancyPrivacyGroup addNodeWithTenants( - final MultiTenancyPrivacyNode privacyNode, final List tenants) { - map.put(privacyNode, tenants); - return this; - } - - public List getPrivacyNodes() { - return map.keySet().stream().collect(Collectors.toList()); - } - - public List getTenantsForNode(final MultiTenancyPrivacyNode privacyNode) { - return map.get(privacyNode); - } - - public List getTenants() { - return map.values().stream().flatMap(Collection::stream).collect(Collectors.toList()); - } - - public PrivacyNode getGroupCreatingPrivacyNode() { - return getPrivacyNodes().get(0).getPrivacyNode(); - } - - public String getGroupCreatingTenant() { - return getPrivacyNodes().get(0).getTenants().get(0); - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyPrivacyNode.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyPrivacyNode.java deleted file mode 100644 index 1126d440674..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyPrivacyNode.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.privacy.multitenancy; - -import org.hyperledger.besu.tests.acceptance.dsl.privacy.PrivacyNode; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -public class MultiTenancyPrivacyNode { - - private final PrivacyNode privacyNode; - private final Map tenantToTokenMap; - - public MultiTenancyPrivacyNode(final PrivacyNode privacyNode) { - this.privacyNode = privacyNode; - this.tenantToTokenMap = new HashMap<>(); - } - - public MultiTenancyPrivacyNode addTenantWithToken(final String tenant, final String token) { - tenantToTokenMap.put(tenant, token); - return this; - } - - public List getTenants() { - return tenantToTokenMap.keySet().stream().collect(Collectors.toList()); - } - - public String getTokenForTenant(final String tenant) { - return tenantToTokenMap.get(tenant); - } - - public PrivacyNode getPrivacyNode() { - return privacyNode; - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyValidationFailAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyValidationFailAcceptanceTest.java deleted file mode 100644 index 84e6fa7ea3e..00000000000 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/privacy/multitenancy/MultiTenancyValidationFailAcceptanceTest.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.tests.acceptance.privacy.multitenancy; - -import static com.github.tomakehurst.wiremock.client.WireMock.ok; -import static com.github.tomakehurst.wiremock.client.WireMock.post; -import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; -import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; -import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.DELETE_PRIVACY_GROUP_ERROR; -import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.ENCLAVE_ERROR; -import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.FIND_PRIVACY_GROUP_ERROR; -import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.GET_PRIVATE_TRANSACTION_NONCE_ERROR; -import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.PRIVATE_FROM_DOES_NOT_MATCH_ENCLAVE_PUBLIC_KEY; - -import org.hyperledger.besu.crypto.SignatureAlgorithm; -import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.enclave.types.PrivacyGroup; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; -import org.hyperledger.besu.ethereum.privacy.PrivateTransaction; -import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; -import org.hyperledger.besu.plugin.data.Restriction; -import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase; -import org.hyperledger.besu.tests.acceptance.dsl.node.BesuNode; -import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.Cluster; -import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.ClusterConfiguration; -import org.hyperledger.besu.tests.acceptance.dsl.node.cluster.ClusterConfigurationBuilder; -import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction; - -import java.math.BigInteger; -import java.util.List; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.tomakehurst.wiremock.junit.WireMockRule; -import org.apache.tuweni.bytes.Bytes; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; - -public class MultiTenancyValidationFailAcceptanceTest extends AcceptanceTestBase { - private BesuNode node; - private final ObjectMapper mapper = new ObjectMapper(); - private Cluster multiTenancyCluster; - - private static final String PRIVACY_GROUP_ID = "B1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; - private static final String ENCLAVE_PUBLIC_KEY = "B1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; - private static final String OTHER_ENCLAVE_PUBLIC_KEY = - "A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; - private final Address senderAddress = - Address.wrap(Bytes.fromHexString(accounts.getPrimaryBenefactor().getAddress())); - - @Rule public WireMockRule wireMockRule = new WireMockRule(options().dynamicPort()); - - @Before - public void setUp() throws Exception { - final ClusterConfiguration clusterConfiguration = - new ClusterConfigurationBuilder().awaitPeerDiscovery(false).build(); - multiTenancyCluster = new Cluster(clusterConfiguration, net); - node = - besu.createNodeWithMultiTenantedPrivacy( - "node1", - "http://127.0.0.1:" + wireMockRule.port(), - "authentication/auth_priv.toml", - "authentication/auth_priv_key", - false); - multiTenancyCluster.start(node); - - final String token = - node.execute(permissioningTransactions.createSuccessfulLogin("failUser", "pegasys")); - node.useAuthenticationTokenInHeaderForJsonRpc(token); - } - - @After - public void tearDown() { - multiTenancyCluster.close(); - } - - @Test - public void sendRawTransactionShouldFailWhenPrivateFromNotMatchEnclaveKey() - throws JsonProcessingException { - final PrivateTransaction validSignedPrivateTransaction = - getValidSignedPrivateTransaction(senderAddress, OTHER_ENCLAVE_PUBLIC_KEY); - retrievePrivacyGroupEnclaveStub(); - final Transaction transaction = - privacyTransactions.sendRawTransaction( - getRLPOutput(validSignedPrivateTransaction).encoded().toHexString()); - node.verify( - priv.multiTenancyValidationFail( - transaction, RpcErrorType.PRIVATE_FROM_DOES_NOT_MATCH_ENCLAVE_PUBLIC_KEY)); - } - - @Test - public void sendRawTransactionShouldFailWhenPrivacyGroupDoesNotContainEnclaveKey() - throws JsonProcessingException { - final PrivateTransaction validSignedPrivateTransaction = - getValidSignedPrivateTransaction(senderAddress, ENCLAVE_PUBLIC_KEY); - retrievePrivacyGroupEnclaveStub(); - final Transaction transaction = - privacyTransactions.sendRawTransaction( - getRLPOutput(validSignedPrivateTransaction).encoded().toHexString()); - node.verify(priv.multiTenancyValidationFail(transaction, ENCLAVE_ERROR)); - } - - @Test - public void distributeRawTransactionShouldFailWhenPrivateFromNotMatchEnclaveKey() { - final Address senderAddress = - Address.wrap(Bytes.fromHexString(accounts.getPrimaryBenefactor().getAddress())); - - final Transaction transaction = - privacyTransactions.distributeRawTransaction( - getRLPOutput(getValidSignedPrivateTransaction(senderAddress, OTHER_ENCLAVE_PUBLIC_KEY)) - .encoded() - .toHexString()); - node.verify( - priv.multiTenancyValidationFail( - transaction, PRIVATE_FROM_DOES_NOT_MATCH_ENCLAVE_PUBLIC_KEY)); - } - - @Test - public void distributeRawTransactionShouldFailWhenPrivacyGroupDoesNotContainEnclaveKey() - throws JsonProcessingException { - final Address senderAddress = - Address.wrap(Bytes.fromHexString(accounts.getPrimaryBenefactor().getAddress())); - - retrievePrivacyGroupEnclaveStub(); - - final Transaction transaction = - privacyTransactions.distributeRawTransaction( - getRLPOutput(getValidSignedPrivateTransaction(senderAddress, ENCLAVE_PUBLIC_KEY)) - .encoded() - .toHexString()); - node.verify(priv.multiTenancyValidationFail(transaction, ENCLAVE_ERROR)); - } - - @Test - public void deletePrivacyGroupShouldFailWhenEnclaveKeyNotInPrivacyGroup() - throws JsonProcessingException { - retrievePrivacyGroupEnclaveStub(); - final Transaction transaction = - privacyTransactions.deletePrivacyGroup(PRIVACY_GROUP_ID); - node.verify(priv.multiTenancyValidationFail(transaction, DELETE_PRIVACY_GROUP_ERROR)); - } - - @Test - public void findPrivacyGroupShouldFailWhenEnclaveKeyNotInPrivacyGroup() { - final Transaction transaction = - privacyTransactions.findPrivacyGroup(new String[] {OTHER_ENCLAVE_PUBLIC_KEY}); - node.verify(priv.multiTenancyValidationFail(transaction, FIND_PRIVACY_GROUP_ERROR)); - } - - @Test - public void determineEeaNonceShouldFailWhenPrivateFromNotMatchEnclaveKey() { - final String accountAddress = Address.ZERO.toHexString(); - final String senderAddressBase64 = Bytes.fromHexString(accountAddress).toBase64String(); - final String[] privateFor = {senderAddressBase64}; - final Transaction transaction = - privacyTransactions.getEeaTransactionCount( - accountAddress, OTHER_ENCLAVE_PUBLIC_KEY, privateFor); - node.verify( - priv.multiTenancyValidationFail( - transaction, PRIVATE_FROM_DOES_NOT_MATCH_ENCLAVE_PUBLIC_KEY)); - } - - @Test - public void determineBesuNonceShouldFailWhenEnclaveKeyNotInPrivacyGroup() - throws JsonProcessingException { - retrievePrivacyGroupEnclaveStub(); - final String accountAddress = Address.ZERO.toHexString(); - final Transaction transaction = - privacyTransactions.getTransactionCount(accountAddress, PRIVACY_GROUP_ID); - node.verify(priv.multiTenancyValidationFail(transaction, GET_PRIVATE_TRANSACTION_NONCE_ERROR)); - } - - private void retrievePrivacyGroupEnclaveStub() throws JsonProcessingException { - final String retrieveGroupResponse = - mapper.writeValueAsString( - testPrivacyGroup(List.of(OTHER_ENCLAVE_PUBLIC_KEY), PrivacyGroup.Type.PANTHEON)); - stubFor(post("/retrievePrivacyGroup").willReturn(ok(retrieveGroupResponse))); - } - - private PrivacyGroup testPrivacyGroup( - final List groupMembers, final PrivacyGroup.Type groupType) { - return new PrivacyGroup(PRIVACY_GROUP_ID, groupType, "test", "testGroup", groupMembers); - } - - private BytesValueRLPOutput getRLPOutput(final PrivateTransaction validSignedPrivateTransaction) { - final BytesValueRLPOutput bvrlpo = new BytesValueRLPOutput(); - validSignedPrivateTransaction.writeTo(bvrlpo); - return bvrlpo; - } - - private static PrivateTransaction getValidSignedPrivateTransaction( - final Address senderAddress, final String privateFrom) { - - final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithmFactory.getInstance(); - - return PrivateTransaction.builder() - .nonce(0) - .gasPrice(Wei.ZERO) - .gasLimit(3000000) - .to(null) - .value(Wei.ZERO) - .payload(Bytes.wrap(new byte[] {})) - .sender(senderAddress) - .chainId(BigInteger.valueOf(2018)) - .privateFrom(Bytes.fromBase64String(privateFrom)) - .restriction(Restriction.RESTRICTED) - .privacyGroupId(Bytes.fromBase64String(PRIVACY_GROUP_ID)) - .signAndBuild( - signatureAlgorithm.createKeyPair( - signatureAlgorithm.createPrivateKey( - new BigInteger( - "853d7f0010fd86d0d7811c1f9d968ea89a24484a8127b4a483ddf5d2cfec766d", 16)))); - } -} diff --git a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/pubsub/NewPendingTransactionAcceptanceTest.java b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/pubsub/NewPendingTransactionAcceptanceTest.java index 4ae94924b25..fe7a04c8446 100644 --- a/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/pubsub/NewPendingTransactionAcceptanceTest.java +++ b/acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/pubsub/NewPendingTransactionAcceptanceTest.java @@ -24,6 +24,7 @@ import io.vertx.core.Vertx; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; public class NewPendingTransactionAcceptanceTest extends AcceptanceTestBase { @@ -41,9 +42,19 @@ public void setUp() throws Exception { minerNode = besu.createMinerNode("miner-node1"); archiveNode = besu.createArchiveNode("full-node1"); cluster.start(minerNode, archiveNode); + + // verify nodes are fully connected otherwise tx could not be propagated + minerNode.verify(net.awaitPeerCount(1)); + archiveNode.verify(net.awaitPeerCount(1)); + accountOne = accounts.createAccount("account-one"); minerWebSocket = new WebSocket(vertx, minerNode.getConfiguration()); archiveWebSocket = new WebSocket(vertx, archiveNode.getConfiguration()); + + // verify that the miner started producing blocks and all other nodes are syncing from it + waitForBlockHeight(minerNode, 1); + final var minerChainHead = minerNode.execute(ethTransactions.block()); + archiveNode.verify(blockchain.minimumHeight(minerChainHead.getNumber().longValue())); } @AfterEach @@ -105,6 +116,7 @@ public void everySubscriptionMustReceivePublishEvent() { } @Test + @Disabled("test is flaky - sometimes returns more than 3 total events #6909") public void subscriptionToMinerNodeMustReceiveEveryPublishEvent() { final Subscription minerSubscription = minerWebSocket.subscribe(); @@ -147,6 +159,7 @@ public void subscriptionToArchiveNodeMustReceiveEveryPublishEvent() { } @Test + @Disabled("This test is flaky and needs to be fixed") public void everySubscriptionMustReceiveEveryPublishEvent() { final Subscription minerSubscriptionOne = minerWebSocket.subscribe(); final Subscription minerSubscriptionTwo = minerWebSocket.subscribe(); diff --git a/acceptance-tests/tests/src/test/resources/dev/dev_prague.json b/acceptance-tests/tests/src/test/resources/dev/dev_prague.json new file mode 100644 index 00000000000..26e59992d16 --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/dev/dev_prague.json @@ -0,0 +1,112 @@ +{ + "config": { + "chainId":20211, + "homesteadBlock":0, + "eip150Block":0, + "eip155Block":0, + "eip158Block":0, + "byzantiumBlock":0, + "constantinopleBlock":0, + "petersburgBlock":0, + "istanbulBlock":0, + "muirGlacierBlock":0, + "berlinBlock":0, + "londonBlock":0, + "terminalTotalDifficulty":0, + "cancunTime":0, + "pragueTime":0, + "clique": { + "period": 5, + "epoch": 30000 + }, + "depositContractAddress": "0x4242424242424242424242424242424242424242" + }, + "nonce":"0x42", + "timestamp":"0x0", + "extraData":"0x0000000000000000000000000000000000000000000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "gasLimit":"0x1C9C380", + "difficulty":"0x400000000", + "mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase":"0x0000000000000000000000000000000000000000", + "alloc":{ + "a05b21E5186Ce93d2a226722b85D6e550Ac7D6E3": { + "privateKey": "3a4ff6d22d7502ef2452368165422861c01a0f72f851793b372b87888dc3c453", + "balance": "90000000000000000000000" + }, + "8da48afC965480220a3dB9244771bd3afcB5d895": { + "comment": "This account has signed a authorization for contract 0x0000000000000000000000000000000000009999 to send a 7702 transaction", + "privateKey": "11f2e7b6a734ab03fa682450e0d4681d18a944f8b83c99bf7b9b4de6c0f35ea1", + "balance": "90000000000000000000000" + }, + "0x0000000000000000000000000000000000009999": { + "comment": "Contract sends all its Ether to the address provided as a call data.", + "balance": "0", + "code": "5F5F5F5F475F355AF100", + "codeDecompiled": "PUSH0 PUSH0 PUSH0 PUSH0 SELFBALANCE PUSH0 CALLDATALOAD GAS CALL STOP", + "storage": {} + }, + "0xa4664C40AACeBD82A2Db79f0ea36C06Bc6A19Adb": { + "balance": "1000000000000000000000000000" + }, + "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f": { + "comment": "This is the account used to sign the transaction that creates a validator exit", + "balance": "1000000000000000000000000000" + }, + "0x00A3ca265EBcb825B45F985A16CEFB49958cE017": { + "comment": "This is the runtime bytecode for the Withdrawal Request Smart Contract. It was created from the deployment transaction in EIP-7002 (https://eips.ethereum.org/EIPS/eip-7002#deployment)", + "balance": "0", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe146090573615156028575f545f5260205ff35b36603814156101215760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061012157600154600101600155600354806003026004013381556001015f3581556001016020359055600101600355005b6003546002548082038060101160a4575060105b5f5b81811460dd5780604c02838201600302600401805490600101805490600101549160601b83528260140152906034015260010160a6565b910180921460ed579060025560f8565b90505f6002555f6003555b5f5460015460028282011161010f5750505f610115565b01600290035b5f555f600155604c025ff35b5f5ffd", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000004": "000000000000000000000000a4664C40AACeBD82A2Db79f0ea36C06Bc6A19Adb", + "0x0000000000000000000000000000000000000000000000000000000000000005": "b10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee9922355", + "0x0000000000000000000000000000000000000000000000000000000000000006": "5d8601f0cb3bcc4ce1af9864779a416e00000000000000000000000000000000" + } + }, + "0x4242424242424242424242424242424242424242": { + "comment": "The deposit contract", + "balance": "0", + "code": "0x60806040526004361061003f5760003560e01c806301ffc9a71461004457806322895118146100a4578063621fd130146101ba578063c5f2892f14610244575b600080fd5b34801561005057600080fd5b506100906004803603602081101561006757600080fd5b50357fffffffff000000000000000000000000000000000000000000000000000000001661026b565b604080519115158252519081900360200190f35b6101b8600480360360808110156100ba57600080fd5b8101906020810181356401000000008111156100d557600080fd5b8201836020820111156100e757600080fd5b8035906020019184600183028401116401000000008311171561010957600080fd5b91939092909160208101903564010000000081111561012757600080fd5b82018360208201111561013957600080fd5b8035906020019184600183028401116401000000008311171561015b57600080fd5b91939092909160208101903564010000000081111561017957600080fd5b82018360208201111561018b57600080fd5b803590602001918460018302840111640100000000831117156101ad57600080fd5b919350915035610304565b005b3480156101c657600080fd5b506101cf6110b5565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102095781810151838201526020016101f1565b50505050905090810190601f1680156102365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561025057600080fd5b506102596110c7565b60408051918252519081900360200190f35b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a70000000000000000000000000000000000000000000000000000000014806102fe57507fffffffff0000000000000000000000000000000000000000000000000000000082167f8564090700000000000000000000000000000000000000000000000000000000145b92915050565b6030861461035d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118056026913960400191505060405180910390fd5b602084146103b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603681526020018061179c6036913960400191505060405180910390fd5b6060821461040f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806118786029913960400191505060405180910390fd5b670de0b6b3a7640000341015610470576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118526026913960400191505060405180910390fd5b633b9aca003406156104cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260338152602001806117d26033913960400191505060405180910390fd5b633b9aca00340467ffffffffffffffff811115610535576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602781526020018061182b6027913960400191505060405180910390fd5b6060610540826114ba565b90507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c589898989858a8a6105756020546114ba565b6040805160a0808252810189905290819060208201908201606083016080840160c085018e8e80828437600083820152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690910187810386528c815260200190508c8c808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690920188810386528c5181528c51602091820193918e019250908190849084905b83811015610648578181015183820152602001610630565b50505050905090810190601f1680156106755780820380516001836020036101000a031916815260200191505b5086810383528881526020018989808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169092018881038452895181528951602091820193918b019250908190849084905b838110156106ef5781810151838201526020016106d7565b50505050905090810190601f16801561071c5780820380516001836020036101000a031916815260200191505b509d505050505050505050505050505060405180910390a1600060028a8a600060801b604051602001808484808284377fffffffffffffffffffffffffffffffff0000000000000000000000000000000090941691909301908152604080517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0818403018152601090920190819052815191955093508392506020850191508083835b602083106107fc57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016107bf565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610859573d6000803e3d6000fd5b5050506040513d602081101561086e57600080fd5b5051905060006002806108846040848a8c6116fe565b6040516020018083838082843780830192505050925050506040516020818303038152906040526040518082805190602001908083835b602083106108f857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016108bb565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610955573d6000803e3d6000fd5b5050506040513d602081101561096a57600080fd5b5051600261097b896040818d6116fe565b60405160009060200180848480828437919091019283525050604080518083038152602092830191829052805190945090925082918401908083835b602083106109f457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016109b7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610a51573d6000803e3d6000fd5b5050506040513d6020811015610a6657600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610ada57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610a9d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610b37573d6000803e3d6000fd5b5050506040513d6020811015610b4c57600080fd5b50516040805160208101858152929350600092600292839287928f928f92018383808284378083019250505093505050506040516020818303038152906040526040518082805190602001908083835b60208310610bd957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610b9c565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610c36573d6000803e3d6000fd5b5050506040513d6020811015610c4b57600080fd5b50516040518651600291889160009188916020918201918291908601908083835b60208310610ca957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610c6c565b6001836020036101000a0380198251168184511680821785525050505050509050018367ffffffffffffffff191667ffffffffffffffff1916815260180182815260200193505050506040516020818303038152906040526040518082805190602001908083835b60208310610d4e57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610d11565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610dab573d6000803e3d6000fd5b5050506040513d6020811015610dc057600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610e3457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610df7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610e91573d6000803e3d6000fd5b5050506040513d6020811015610ea657600080fd5b50519050858114610f02576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260548152602001806117486054913960600191505060405180910390fd5b60205463ffffffff11610f60576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806117276021913960400191505060405180910390fd5b602080546001019081905560005b60208110156110a9578160011660011415610fa0578260008260208110610f9157fe5b0155506110ac95505050505050565b600260008260208110610faf57fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061102557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610fe8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015611082573d6000803e3d6000fd5b5050506040513d602081101561109757600080fd5b50519250600282049150600101610f6e565b50fe5b50505050505050565b60606110c26020546114ba565b905090565b6020546000908190815b60208110156112f05781600116600114156111e6576002600082602081106110f557fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061116b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161112e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156111c8573d6000803e3d6000fd5b5050506040513d60208110156111dd57600080fd5b505192506112e2565b600283602183602081106111f657fe5b015460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061126b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161122e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156112c8573d6000803e3d6000fd5b5050506040513d60208110156112dd57600080fd5b505192505b6002820491506001016110d1565b506002826112ff6020546114ba565b600060401b6040516020018084815260200183805190602001908083835b6020831061135a57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161131d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790527fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000095909516920191825250604080518083037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8018152601890920190819052815191955093508392850191508083835b6020831061143f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101611402565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa15801561149c573d6000803e3d6000fd5b5050506040513d60208110156114b157600080fd5b50519250505090565b60408051600880825281830190925260609160208201818036833701905050905060c082901b8060071a60f81b826000815181106114f457fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060061a60f81b8260018151811061153757fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060051a60f81b8260028151811061157a57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060041a60f81b826003815181106115bd57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060031a60f81b8260048151811061160057fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060021a60f81b8260058151811061164357fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060011a60f81b8260068151811061168657fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060001a60f81b826007815181106116c957fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535050919050565b6000808585111561170d578182fd5b83861115611719578182fd5b505082019391909203915056fe4465706f736974436f6e74726163743a206d65726b6c6520747265652066756c6c4465706f736974436f6e74726163743a207265636f6e7374727563746564204465706f7369744461746120646f6573206e6f74206d6174636820737570706c696564206465706f7369745f646174615f726f6f744465706f736974436f6e74726163743a20696e76616c6964207769746864726177616c5f63726564656e7469616c73206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c7565206e6f74206d756c7469706c65206f6620677765694465706f736974436f6e74726163743a20696e76616c6964207075626b6579206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f20686967684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f206c6f774465706f736974436f6e74726163743a20696e76616c6964207369676e6174757265206c656e677468a26469706673582212201dd26f37a621703009abf16e77e69c93dc50c79db7f6cc37543e3e0e3decdc9764736f6c634300060b0033", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000022": "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", + "0x0000000000000000000000000000000000000000000000000000000000000023": "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", + "0x0000000000000000000000000000000000000000000000000000000000000024": "0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c", + "0x0000000000000000000000000000000000000000000000000000000000000025": "0x536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c", + "0x0000000000000000000000000000000000000000000000000000000000000026": "0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30", + "0x0000000000000000000000000000000000000000000000000000000000000027": "0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1", + "0x0000000000000000000000000000000000000000000000000000000000000028": "0x87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c", + "0x0000000000000000000000000000000000000000000000000000000000000029": "0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193", + "0x000000000000000000000000000000000000000000000000000000000000002a": "0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1", + "0x000000000000000000000000000000000000000000000000000000000000002b": "0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b", + "0x000000000000000000000000000000000000000000000000000000000000002c": "0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220", + "0x000000000000000000000000000000000000000000000000000000000000002d": "0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f", + "0x000000000000000000000000000000000000000000000000000000000000002e": "0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e", + "0x000000000000000000000000000000000000000000000000000000000000002f": "0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784", + "0x0000000000000000000000000000000000000000000000000000000000000030": "0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb", + "0x0000000000000000000000000000000000000000000000000000000000000031": "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb", + "0x0000000000000000000000000000000000000000000000000000000000000032": "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab", + "0x0000000000000000000000000000000000000000000000000000000000000033": "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", + "0x0000000000000000000000000000000000000000000000000000000000000034": "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f", + "0x0000000000000000000000000000000000000000000000000000000000000035": "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", + "0x0000000000000000000000000000000000000000000000000000000000000036": "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", + "0x0000000000000000000000000000000000000000000000000000000000000037": "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", + "0x0000000000000000000000000000000000000000000000000000000000000038": "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", + "0x0000000000000000000000000000000000000000000000000000000000000039": "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", + "0x000000000000000000000000000000000000000000000000000000000000003a": "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", + "0x000000000000000000000000000000000000000000000000000000000000003b": "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", + "0x000000000000000000000000000000000000000000000000000000000000003c": "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", + "0x000000000000000000000000000000000000000000000000000000000000003d": "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", + "0x000000000000000000000000000000000000000000000000000000000000003e": "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", + "0x000000000000000000000000000000000000000000000000000000000000003f": "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", + "0x0000000000000000000000000000000000000000000000000000000000000040": "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7" + } + } + }, + "number":"0x0", + "gasUsed":"0x0", + "parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000", + "baseFeePerGas":"0x7" +} \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/cancun/test-cases/block-production/11_cancun_tx_count.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/cancun/test-cases/block-production/11_cancun_tx_count.json index 080e951ff44..dec31a580b6 100644 --- a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/cancun/test-cases/block-production/11_cancun_tx_count.json +++ b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/cancun/test-cases/block-production/11_cancun_tx_count.json @@ -14,5 +14,6 @@ "remoteCount": 0 } }, - "statusCode": 200 + "statusCode": 200, + "waitTime": 2000 } \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/cancun/test-cases/block-production/12_cancun_get_built_block.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/cancun/test-cases/block-production/12_cancun_get_built_block.json index 75300119246..8cbdd626427 100644 --- a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/cancun/test-cases/block-production/12_cancun_get_built_block.json +++ b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/cancun/test-cases/block-production/12_cancun_get_built_block.json @@ -10,7 +10,7 @@ "stateRoot" : "0x8d9115d9211932d4a3a1f068fb8fe262b0b2ab0bfd74eaece1a572efe6336677", "logsBloom" : "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "prevRandao" : "0xc13da06dc53836ca0766057413b9683eb9a8773bbb8fcc5691e41c25b56dda1d", - "gasLimit" : "0x2ff3d8", + "gasLimit" : "0x2ffbd2", "gasUsed" : "0xf618", "timestamp" : "0x1236", "extraData" : "0x", @@ -70,7 +70,7 @@ "amount" : "0x64" } ], "blockNumber" : "0x1", - "blockHash" : "0xf1e35607932349e87f29e1053a4fb2666782e09fde21ded74c1f7e4a57d3fa2b", + "blockHash" : "0x736bdddc2eca36fe8ed4ed515e5d295a08d7eaddc0d0fda2a35408127eb890d0", "receiptsRoot" : "0x9af165447e5b3193e9ac8389418648ee6d6cb1d37459fe65cfc245fc358721bd", "blobGasUsed" : "0x60000" }, diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/01_cancun_prepare_payload.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/01_cancun_prepare_payload.json deleted file mode 100644 index b398cda18af..00000000000 --- a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/01_cancun_prepare_payload.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "request": { - "jsonrpc": "2.0", - "method": "engine_forkchoiceUpdatedV3", - "params": [ - { - "headBlockHash": "0x26118cf71453320edcebbc4ebb34af5b578087a32385b80108bf691fa23efc42", - "safeBlockHash": "0x26118cf71453320edcebbc4ebb34af5b578087a32385b80108bf691fa23efc42", - "finalizedBlockHash": "0x0000000000000000000000000000000000000000000000000000000000000000" - }, - { - "timestamp": "0x10", - "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", - "suggestedFeeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", - "withdrawals": [], - "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000" - } - ], - "id": 67 - }, - "response": { - "jsonrpc": "2.0", - "id": 67, - "result": { - "payloadStatus": { - "status": "VALID", - "latestValidHash": "0x26118cf71453320edcebbc4ebb34af5b578087a32385b80108bf691fa23efc42", - "validationError": null - }, - "payloadId": "0x282643c14de2dfef" - } - }, - "statusCode" : 200 -} \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/04_cancun_forkchoiceUpdatedV3.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/04_cancun_forkchoiceUpdatedV3.json deleted file mode 100644 index 80610561a0f..00000000000 --- a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/04_cancun_forkchoiceUpdatedV3.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "request": { - "jsonrpc": "2.0", - "method": "engine_forkchoiceUpdatedV3", - "params": [ - { - "headBlockHash": "0x45811fa27a100ce9035e5e086b9669275041a4ec0ebbd920be028fd7b0aa2356", - "safeBlockHash": "0x45811fa27a100ce9035e5e086b9669275041a4ec0ebbd920be028fd7b0aa2356", - "finalizedBlockHash": "0x45811fa27a100ce9035e5e086b9669275041a4ec0ebbd920be028fd7b0aa2356" - }, - null - ], - "id": 67 - }, - "response": { - "jsonrpc": "2.0", - "id": 67, - "result": { - "payloadStatus": { - "status": "VALID", - "latestValidHash": "0x45811fa27a100ce9035e5e086b9669275041a4ec0ebbd920be028fd7b0aa2356", - "validationError": null - }, - "payloadId": null - } - }, - "statusCode": 200 -} \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/05_eip6110_forkchoiceUpdatedV3.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/05_eip6110_forkchoiceUpdatedV3.json deleted file mode 100644 index 4f83ac7dcee..00000000000 --- a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/05_eip6110_forkchoiceUpdatedV3.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "request": { - "jsonrpc": "2.0", - "method": "engine_forkchoiceUpdatedV3", - "params": [ - { - "headBlockHash": "0x45811fa27a100ce9035e5e086b9669275041a4ec0ebbd920be028fd7b0aa2356", - "safeBlockHash": "0x45811fa27a100ce9035e5e086b9669275041a4ec0ebbd920be028fd7b0aa2356", - "finalizedBlockHash": "0x0000000000000000000000000000000000000000000000000000000000000000" - }, - { - "timestamp": "0x20", - "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", - "suggestedFeeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", - "withdrawals": [], - "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000" - } - ], - "id": 67 - }, - "response": { - "jsonrpc": "2.0", - "id": 67, - "result": { - "payloadStatus": { - "status": "VALID", - "latestValidHash": "0x45811fa27a100ce9035e5e086b9669275041a4ec0ebbd920be028fd7b0aa2356", - "validationError": null - }, - "payloadId": "0x282643b9c2d2a4df" - } - }, - "statusCode": 200 -} \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/06_eip6110_getPayloadV6110.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/06_eip6110_getPayloadV6110.json deleted file mode 100644 index 9b1653a198d..00000000000 --- a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/06_eip6110_getPayloadV6110.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "request": { - "jsonrpc": "2.0", - "method": "engine_getPayloadV6110", - "params": [ - "0x282643b9c2d2a4df" - ], - "id": 67 - }, - "response": { - "jsonrpc": "2.0", - "id": 67, - "result": { - "executionPayload": { - "parentHash": "0x45811fa27a100ce9035e5e086b9669275041a4ec0ebbd920be028fd7b0aa2356", - "feeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", - "stateRoot": "0x9b8c4a9a86cb49252075c0db2f0e72fb1e49350a0f70ea36f26f700201961e62", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", - "gasLimit": "0x1c9c380", - "gasUsed": "0x0", - "timestamp": "0x20", - "extraData": "0x", - "baseFeePerGas": "0x7", - "excessBlobGas": "0x0", - "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", - "transactions": [], - "withdrawals": [], - "depositReceipts": [], - "blockNumber": "0x2", - "blockHash": "0xf6c3f1180ba58d6ea4c69c9328c7afb1fda41df06c368741c1f8310567879de7", - "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "blobGasUsed": "0x0" - }, - "blockValue": "0x0", - "blobsBundle": { - "commitments": [], - "proofs": [], - "blobs": [] - }, - "shouldOverrideBuilder": false - } - }, - "statusCode": 200 -} \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/08_eip6110_invalid_null_deposits_execute_payload.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/08_eip6110_invalid_null_deposits_execute_payload.json deleted file mode 100644 index ebf5de77500..00000000000 --- a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/08_eip6110_invalid_null_deposits_execute_payload.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "request": { - "jsonrpc": "2.0", - "method": "engine_newPayloadV6110", - "params": [ - { - "parentHash": "0x45811fa27a100ce9035e5e086b9669275041a4ec0ebbd920be028fd7b0aa2356", - "feeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", - "stateRoot": "0x9b8c4a9a86cb49252075c0db2f0e72fb1e49350a0f70ea36f26f700201961e62", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", - "gasLimit": "0x1c9c380", - "gasUsed": "0x0", - "timestamp": "0x20", - "extraData": "0x", - "baseFeePerGas": "0x7", - "excessBlobGas": "0x0", - "transactions": [], - "withdrawals": [], - "depositReceipts" : null, - "blockNumber": "0x2", - "blockHash": "0xf6c3f1180ba58d6ea4c69c9328c7afb1fda41df06c368741c1f8310567879de7", - "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "blobGasUsed": "0x0" - }, - [], - "0x0000000000000000000000000000000000000000000000000000000000000000" - ], - "id": 67 - }, - "response": { - "jsonrpc": "2.0", - "id": 67, - "error": { - "code": -32602, - "message": "Invalid params", - "data" : "Missing deposit field" - } - }, - "statusCode": 200 -} \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/09_eip6110_newPayloadV6110.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/09_eip6110_newPayloadV6110.json deleted file mode 100644 index 0964114e62c..00000000000 --- a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/09_eip6110_newPayloadV6110.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "request": { - "jsonrpc": "2.0", - "method": "engine_newPayloadV6110", - "params": [ - { - "parentHash": "0x45811fa27a100ce9035e5e086b9669275041a4ec0ebbd920be028fd7b0aa2356", - "feeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", - "stateRoot": "0x14208ac0e218167936e220b72d5d5887a963cb858ea2f2d268518f014a3da3fa", - "logsBloom": "0x10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000", - "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", - "gasLimit": "0x1c9c380", - "gasUsed": "0x14B6E", - "timestamp": "0x20", - "extraData": "0x", - "baseFeePerGas": "0x7", - "excessBlobGas": "0x0", - "transactions": [ - "0x02f9021c8217de808459682f008459682f0e830271009442424242424242424242424242424242424242428901bc16d674ec800000b901a422895118000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120749715de5d1226545c6b3790f515d551a5cc5bf1d49c87a696860554d2fc4f14000000000000000000000000000000000000000000000000000000000000003096a96086cff07df17668f35f7418ef8798079167e3f4f9b72ecde17b28226137cf454ab1dd20ef5d924786ab3483c2f9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020003f5102dabe0a27b1746098d1dc17a5d3fbd478759fea9287e4e419b3c3cef20000000000000000000000000000000000000000000000000000000000000060b1acdb2c4d3df3f1b8d3bfd33421660df358d84d78d16c4603551935f4b67643373e7eb63dcb16ec359be0ec41fee33b03a16e80745f2374ff1d3c352508ac5d857c6476d3c3bcf7e6ca37427c9209f17be3af5264c0e2132b3dd1156c28b4e9c080a09f597089338d7f44f5c59f8230bb38f243849228a8d4e9d2e2956e6050f5b2c7a076486996c7e62802b8f95eee114783e4b403fd11093ba96286ff42c595f24452" - ], - "withdrawals": [], - "depositReceipts" : [ - {"amount":"0x773594000","index":"0x0","pubkey":"0x96a96086cff07df17668f35f7418ef8798079167e3f4f9b72ecde17b28226137cf454ab1dd20ef5d924786ab3483c2f9","signature":"0xb1acdb2c4d3df3f1b8d3bfd33421660df358d84d78d16c4603551935f4b67643373e7eb63dcb16ec359be0ec41fee33b03a16e80745f2374ff1d3c352508ac5d857c6476d3c3bcf7e6ca37427c9209f17be3af5264c0e2132b3dd1156c28b4e9","withdrawalCredentials":"0x003f5102dabe0a27b1746098d1dc17a5d3fbd478759fea9287e4e419b3c3cef2"} - ], - "blockNumber": "0x2", - "blockHash": "0xb3b483867217b83b1e4a2f95c84d2da30cbff12eb8636f2becbcc05f4507fa7a", - "receiptsRoot": "0x79ee3424eb720a3ad4b1c5a372bb8160580cbe4d893778660f34213c685627a9", - "blobGasUsed": "0x0" - }, - [], - "0x0000000000000000000000000000000000000000000000000000000000000000" - ], - "id": 67 - }, - "response": { - "jsonrpc": "2.0", - "id": 67, - "result": { - "status": "VALID", - "latestValidHash": "0xb3b483867217b83b1e4a2f95c84d2da30cbff12eb8636f2becbcc05f4507fa7a", - "validationError": null - } - }, - "statusCode": 200 -} \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/10_eip6110_forkchoiceUpdatedV3.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/10_eip6110_forkchoiceUpdatedV3.json deleted file mode 100644 index 5a32569d611..00000000000 --- a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/10_eip6110_forkchoiceUpdatedV3.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "request": { - "jsonrpc": "2.0", - "method": "engine_forkchoiceUpdatedV3", - "params": [ - { - "headBlockHash": "0xb3b483867217b83b1e4a2f95c84d2da30cbff12eb8636f2becbcc05f4507fa7a", - "safeBlockHash": "0xb3b483867217b83b1e4a2f95c84d2da30cbff12eb8636f2becbcc05f4507fa7a", - "finalizedBlockHash": "0x0000000000000000000000000000000000000000000000000000000000000000" - }, - { - "timestamp": "0x30", - "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", - "suggestedFeeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", - "withdrawals": [], - "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000" - } - ], - "id": 67 - }, - "response": { - "jsonrpc": "2.0", - "id": 67, - "result": { - "payloadStatus": { - "status": "VALID", - "latestValidHash": "0xb3b483867217b83b1e4a2f95c84d2da30cbff12eb8636f2becbcc05f4507fa7a", - "validationError": null - }, - "payloadId": "0x282643daa04b7631" - } - }, - "statusCode" : 200 -} \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/11_eip6110_getPayloadV6110.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/11_eip6110_getPayloadV6110.json deleted file mode 100644 index 6c546cd267d..00000000000 --- a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/11_eip6110_getPayloadV6110.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "request": { - "jsonrpc": "2.0", - "method": "engine_getPayloadV6110", - "params": [ - "0x282643daa04b7631" - ], - "id": 67 - }, - "response": { - "jsonrpc": "2.0", - "id": 67, - "result": { - "executionPayload": { - "parentHash": "0xb3b483867217b83b1e4a2f95c84d2da30cbff12eb8636f2becbcc05f4507fa7a", - "feeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", - "stateRoot": "0x14208ac0e218167936e220b72d5d5887a963cb858ea2f2d268518f014a3da3fa", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", - "gasLimit": "0x1c9c380", - "gasUsed": "0x0", - "timestamp": "0x30", - "extraData": "0x", - "baseFeePerGas": "0x7", - "excessBlobGas": "0x0", - "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", - "transactions": [], - "withdrawals": [], - "depositReceipts": [], - "blockNumber": "0x3", - "blockHash": "0xa28bf4db3363ce5b67848eb2ad52dbfead62ddb2287ae7eed36daa002528d1af", - "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "blobGasUsed": "0x0" - }, - "blockValue": "0x0", - "blobsBundle": { - "commitments": [], - "proofs": [], - "blobs": [] - }, - "shouldOverrideBuilder": false - } - }, - "statusCode": 200 -} \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/genesis.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/genesis.json similarity index 81% rename from acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/genesis.json rename to acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/genesis.json index 232c577f6c8..ac474c02453 100644 --- a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/genesis.json +++ b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/genesis.json @@ -14,7 +14,7 @@ "londonBlock":0, "terminalTotalDifficulty":0, "cancunTime":0, - "experimentalEipsTime":20, + "pragueTime":20, "clique": { "period": 5, "epoch": 30000 @@ -32,6 +32,38 @@ "0xa4664C40AACeBD82A2Db79f0ea36C06Bc6A19Adb": { "balance": "1000000000000000000000000000" }, + "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f": { + "comment": "This is the account used to sign the transactions that create a validator exit, and consolidation request", + "balance": "1000000000000000000000000000" + }, + "0x00A3ca265EBcb825B45F985A16CEFB49958cE017": { + "comment": "This is the runtime bytecode for the Withdrawal Request Smart Contract. It was created from the deployment transaction in EIP-7002 (https://eips.ethereum.org/EIPS/eip-7002#deployment)", + "balance": "0", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe146090573615156028575f545f5260205ff35b36603814156101215760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061012157600154600101600155600354806003026004013381556001015f3581556001016020359055600101600355005b6003546002548082038060101160a4575060105b5f5b81811460dd5780604c02838201600302600401805490600101805490600101549160601b83528260140152906034015260010160a6565b910180921460ed579060025560f8565b90505f6002555f6003555b5f5460015460028282011161010f5750505f610115565b01600290035b5f555f600155604c025ff35b5f5ffd", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000004": "000000000000000000000000a4664C40AACeBD82A2Db79f0ea36C06Bc6A19Adb", + "0x0000000000000000000000000000000000000000000000000000000000000005": "b10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee9922355", + "0x0000000000000000000000000000000000000000000000000000000000000006": "5d8601f0cb3bcc4ce1af9864779a416e00000000000000000000000000000000" + } + }, + "0x00b42dbF2194e931E80326D950320f7d9Dbeac02": { + "comment": "This is the runtime bytecode for the Consolidation Request Smart Contract. It was created from the deployment transaction in EIP-7251 (https://eips.ethereum.org/EIPS/eip-7251#deployment)", + "balance": "0", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe146098573615156028575f545f5260205ff35b36606014156101445760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061014457600154600101600155600354806004026004013381556001015f35815560010160203581556001016040359055600101600355005b6003546002548082038060011160ac575060015b5f5b81811460f15780607402838201600402600401805490600101805490600101805490600101549260601b84529083601401528260340152906054015260010160ae565b9101809214610103579060025561010e565b90505f6002555f6003555b5f548061049d141561011d57505f5b6001546001828201116101325750505f610138565b01600190035b5f555f6001556074025ff35b5f5ffd", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000004": "0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000005": "0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000006": "0000000000000000000000000000000000000000000000000000000000000000" + } + }, "0x4242424242424242424242424242424242424242": { "balance": "0", "code": "0x60806040526004361061003f5760003560e01c806301ffc9a71461004457806322895118146100a4578063621fd130146101ba578063c5f2892f14610244575b600080fd5b34801561005057600080fd5b506100906004803603602081101561006757600080fd5b50357fffffffff000000000000000000000000000000000000000000000000000000001661026b565b604080519115158252519081900360200190f35b6101b8600480360360808110156100ba57600080fd5b8101906020810181356401000000008111156100d557600080fd5b8201836020820111156100e757600080fd5b8035906020019184600183028401116401000000008311171561010957600080fd5b91939092909160208101903564010000000081111561012757600080fd5b82018360208201111561013957600080fd5b8035906020019184600183028401116401000000008311171561015b57600080fd5b91939092909160208101903564010000000081111561017957600080fd5b82018360208201111561018b57600080fd5b803590602001918460018302840111640100000000831117156101ad57600080fd5b919350915035610304565b005b3480156101c657600080fd5b506101cf6110b5565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102095781810151838201526020016101f1565b50505050905090810190601f1680156102365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561025057600080fd5b506102596110c7565b60408051918252519081900360200190f35b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a70000000000000000000000000000000000000000000000000000000014806102fe57507fffffffff0000000000000000000000000000000000000000000000000000000082167f8564090700000000000000000000000000000000000000000000000000000000145b92915050565b6030861461035d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118056026913960400191505060405180910390fd5b602084146103b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603681526020018061179c6036913960400191505060405180910390fd5b6060821461040f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806118786029913960400191505060405180910390fd5b670de0b6b3a7640000341015610470576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118526026913960400191505060405180910390fd5b633b9aca003406156104cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260338152602001806117d26033913960400191505060405180910390fd5b633b9aca00340467ffffffffffffffff811115610535576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602781526020018061182b6027913960400191505060405180910390fd5b6060610540826114ba565b90507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c589898989858a8a6105756020546114ba565b6040805160a0808252810189905290819060208201908201606083016080840160c085018e8e80828437600083820152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690910187810386528c815260200190508c8c808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690920188810386528c5181528c51602091820193918e019250908190849084905b83811015610648578181015183820152602001610630565b50505050905090810190601f1680156106755780820380516001836020036101000a031916815260200191505b5086810383528881526020018989808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169092018881038452895181528951602091820193918b019250908190849084905b838110156106ef5781810151838201526020016106d7565b50505050905090810190601f16801561071c5780820380516001836020036101000a031916815260200191505b509d505050505050505050505050505060405180910390a1600060028a8a600060801b604051602001808484808284377fffffffffffffffffffffffffffffffff0000000000000000000000000000000090941691909301908152604080517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0818403018152601090920190819052815191955093508392506020850191508083835b602083106107fc57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016107bf565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610859573d6000803e3d6000fd5b5050506040513d602081101561086e57600080fd5b5051905060006002806108846040848a8c6116fe565b6040516020018083838082843780830192505050925050506040516020818303038152906040526040518082805190602001908083835b602083106108f857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016108bb565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610955573d6000803e3d6000fd5b5050506040513d602081101561096a57600080fd5b5051600261097b896040818d6116fe565b60405160009060200180848480828437919091019283525050604080518083038152602092830191829052805190945090925082918401908083835b602083106109f457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016109b7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610a51573d6000803e3d6000fd5b5050506040513d6020811015610a6657600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610ada57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610a9d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610b37573d6000803e3d6000fd5b5050506040513d6020811015610b4c57600080fd5b50516040805160208101858152929350600092600292839287928f928f92018383808284378083019250505093505050506040516020818303038152906040526040518082805190602001908083835b60208310610bd957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610b9c565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610c36573d6000803e3d6000fd5b5050506040513d6020811015610c4b57600080fd5b50516040518651600291889160009188916020918201918291908601908083835b60208310610ca957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610c6c565b6001836020036101000a0380198251168184511680821785525050505050509050018367ffffffffffffffff191667ffffffffffffffff1916815260180182815260200193505050506040516020818303038152906040526040518082805190602001908083835b60208310610d4e57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610d11565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610dab573d6000803e3d6000fd5b5050506040513d6020811015610dc057600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610e3457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610df7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610e91573d6000803e3d6000fd5b5050506040513d6020811015610ea657600080fd5b50519050858114610f02576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260548152602001806117486054913960600191505060405180910390fd5b60205463ffffffff11610f60576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806117276021913960400191505060405180910390fd5b602080546001019081905560005b60208110156110a9578160011660011415610fa0578260008260208110610f9157fe5b0155506110ac95505050505050565b600260008260208110610faf57fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061102557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610fe8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015611082573d6000803e3d6000fd5b5050506040513d602081101561109757600080fd5b50519250600282049150600101610f6e565b50fe5b50505050505050565b60606110c26020546114ba565b905090565b6020546000908190815b60208110156112f05781600116600114156111e6576002600082602081106110f557fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061116b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161112e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156111c8573d6000803e3d6000fd5b5050506040513d60208110156111dd57600080fd5b505192506112e2565b600283602183602081106111f657fe5b015460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061126b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161122e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156112c8573d6000803e3d6000fd5b5050506040513d60208110156112dd57600080fd5b505192505b6002820491506001016110d1565b506002826112ff6020546114ba565b600060401b6040516020018084815260200183805190602001908083835b6020831061135a57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161131d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790527fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000095909516920191825250604080518083037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8018152601890920190819052815191955093508392850191508083835b6020831061143f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101611402565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa15801561149c573d6000803e3d6000fd5b5050506040513d60208110156114b157600080fd5b50519250505090565b60408051600880825281830190925260609160208201818036833701905050905060c082901b8060071a60f81b826000815181106114f457fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060061a60f81b8260018151811061153757fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060051a60f81b8260028151811061157a57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060041a60f81b826003815181106115bd57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060031a60f81b8260048151811061160057fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060021a60f81b8260058151811061164357fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060011a60f81b8260068151811061168657fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060001a60f81b826007815181106116c957fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535050919050565b6000808585111561170d578182fd5b83861115611719578182fd5b505082019391909203915056fe4465706f736974436f6e74726163743a206d65726b6c6520747265652066756c6c4465706f736974436f6e74726163743a207265636f6e7374727563746564204465706f7369744461746120646f6573206e6f74206d6174636820737570706c696564206465706f7369745f646174615f726f6f744465706f736974436f6e74726163743a20696e76616c6964207769746864726177616c5f63726564656e7469616c73206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c7565206e6f74206d756c7469706c65206f6620677765694465706f736974436f6e74726163743a20696e76616c6964207075626b6579206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f20686967684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f206c6f774465706f736974436f6e74726163743a20696e76616c6964207369676e6174757265206c656e677468a26469706673582212201dd26f37a621703009abf16e77e69c93dc50c79db7f6cc37543e3e0e3decdc9764736f6c634300060b0033", diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/00_get_genesis_block_info.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/00_get_genesis_block_info.json new file mode 100644 index 00000000000..d1a18826a9e --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/00_get_genesis_block_info.json @@ -0,0 +1,43 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "eth_getBlockByNumber", + "params": [ + "0x00", true + ], + "id": 67 + }, + "response": { + "jsonrpc": "2.0", + "id": 67, + "result": { + "number": "0x0", + "hash" : "0x38d7daa68e8bac41a0a237b7cbfcef480cb9bd9adc7b282d7b0d23ff4eb8d6e5", + "mixHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", + "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce" : "0x0000000000000042", + "sha3Uncles" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom" : "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "transactionsRoot" : "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "stateRoot" : "0x3ed8435adb5f3526144e6babdd3fc8c661a86097cf7e743441b41fda096fc4dd", + "receiptsRoot" : "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "miner" : "0x0000000000000000000000000000000000000000", + "difficulty" : "0x400000000", + "totalDifficulty" : "0x400000000", + "extraData" : "0x0000000000000000000000000000000000000000000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "baseFeePerGas" : "0x7", + "size" : "0x2ba", + "gasLimit" : "0x1c9c380", + "gasUsed" : "0x0", + "timestamp" : "0x0", + "uncles" : [ ], + "transactions" : [ ], + "withdrawalsRoot" : "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "withdrawals" : [ ], + "blobGasUsed" : "0x0", + "excessBlobGas" : "0x0", + "parentBeaconBlockRoot" : "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/01_cancun_prepare_payload.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/01_cancun_prepare_payload.json new file mode 100644 index 00000000000..ad4b9be2d08 --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/01_cancun_prepare_payload.json @@ -0,0 +1,34 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "engine_forkchoiceUpdatedV3", + "params": [ + { + "headBlockHash": "0x38d7daa68e8bac41a0a237b7cbfcef480cb9bd9adc7b282d7b0d23ff4eb8d6e5", + "safeBlockHash": "0x38d7daa68e8bac41a0a237b7cbfcef480cb9bd9adc7b282d7b0d23ff4eb8d6e5", + "finalizedBlockHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + { + "timestamp": "0x10", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "suggestedFeeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "withdrawals": [], + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + ], + "id": 67 + }, + "response": { + "jsonrpc": "2.0", + "id": 67, + "result": { + "payloadStatus": { + "status": "VALID", + "latestValidHash": "0x38d7daa68e8bac41a0a237b7cbfcef480cb9bd9adc7b282d7b0d23ff4eb8d6e5", + "validationError": null + }, + "payloadId": "0x28264396eca1deef" + } + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/02_cancun_getPayloadV3.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/02_cancun_getPayloadV3.json similarity index 82% rename from acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/02_cancun_getPayloadV3.json rename to acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/02_cancun_getPayloadV3.json index 73cbe51bdeb..63cfeec26a1 100644 --- a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/02_cancun_getPayloadV3.json +++ b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/02_cancun_getPayloadV3.json @@ -3,7 +3,7 @@ "jsonrpc": "2.0", "method": "engine_getPayloadV3", "params": [ - "0x282643c14de2dfef" + "0x28264396eca1deef" ], "id": 67 }, @@ -12,9 +12,9 @@ "id": 67, "result": { "executionPayload": { - "parentHash": "0x26118cf71453320edcebbc4ebb34af5b578087a32385b80108bf691fa23efc42", + "parentHash": "0x38d7daa68e8bac41a0a237b7cbfcef480cb9bd9adc7b282d7b0d23ff4eb8d6e5", "feeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", - "stateRoot": "0x9b8c4a9a86cb49252075c0db2f0e72fb1e49350a0f70ea36f26f700201961e62", + "stateRoot": "0x3ed8435adb5f3526144e6babdd3fc8c661a86097cf7e743441b41fda096fc4dd", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", "gasLimit": "0x1c9c380", @@ -28,8 +28,8 @@ "withdrawals": [], "blockNumber": "0x1", "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "blockHash": "0x45811fa27a100ce9035e5e086b9669275041a4ec0ebbd920be028fd7b0aa2356", - "blobGasUsed": "0x0" + "blobGasUsed": "0x0", + "blockHash": "0x8082deff44f79489ea92415be59afb48b6f46b939553f855479828a6f87f9593" }, "blockValue": "0x0", "blobsBundle": { diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/03_cancun_newPayloadV3.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/03_cancun_newPayloadV3.json similarity index 79% rename from acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/03_cancun_newPayloadV3.json rename to acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/03_cancun_newPayloadV3.json index d4d76df31de..9f984b2f350 100644 --- a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/03_cancun_newPayloadV3.json +++ b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/03_cancun_newPayloadV3.json @@ -4,9 +4,9 @@ "method": "engine_newPayloadV3", "params": [ { - "parentHash": "0x26118cf71453320edcebbc4ebb34af5b578087a32385b80108bf691fa23efc42", + "parentHash": "0x38d7daa68e8bac41a0a237b7cbfcef480cb9bd9adc7b282d7b0d23ff4eb8d6e5", "feeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", - "stateRoot": "0x9b8c4a9a86cb49252075c0db2f0e72fb1e49350a0f70ea36f26f700201961e62", + "stateRoot": "0x3ed8435adb5f3526144e6babdd3fc8c661a86097cf7e743441b41fda096fc4dd", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", "gasLimit": "0x1c9c380", @@ -17,7 +17,7 @@ "transactions": [], "withdrawals": [], "blockNumber": "0x1", - "blockHash": "0x45811fa27a100ce9035e5e086b9669275041a4ec0ebbd920be028fd7b0aa2356", + "blockHash": "0x8082deff44f79489ea92415be59afb48b6f46b939553f855479828a6f87f9593", "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "excessBlobGas": "0x0", "blobGasUsed": "0x0" @@ -32,7 +32,7 @@ "id": 67, "result": { "status": "VALID", - "latestValidHash": "0x45811fa27a100ce9035e5e086b9669275041a4ec0ebbd920be028fd7b0aa2356", + "latestValidHash": "0x8082deff44f79489ea92415be59afb48b6f46b939553f855479828a6f87f9593", "validationError": null } }, diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/04_cancun_forkchoiceUpdatedV3.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/04_cancun_forkchoiceUpdatedV3.json new file mode 100644 index 00000000000..b0ce0f70c1a --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/04_cancun_forkchoiceUpdatedV3.json @@ -0,0 +1,28 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "engine_forkchoiceUpdatedV3", + "params": [ + { + "headBlockHash": "0x8082deff44f79489ea92415be59afb48b6f46b939553f855479828a6f87f9593", + "safeBlockHash": "0x8082deff44f79489ea92415be59afb48b6f46b939553f855479828a6f87f9593", + "finalizedBlockHash": "0x8082deff44f79489ea92415be59afb48b6f46b939553f855479828a6f87f9593" + }, + null + ], + "id": 67 + }, + "response": { + "jsonrpc": "2.0", + "id": 67, + "result": { + "payloadStatus": { + "status": "VALID", + "latestValidHash": "0x8082deff44f79489ea92415be59afb48b6f46b939553f855479828a6f87f9593", + "validationError": null + }, + "payloadId": null + } + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/05_prague_forkchoiceUpdatedV3.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/05_prague_forkchoiceUpdatedV3.json new file mode 100644 index 00000000000..1eae1c881ad --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/05_prague_forkchoiceUpdatedV3.json @@ -0,0 +1,34 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "engine_forkchoiceUpdatedV3", + "params": [ + { + "headBlockHash": "0x8082deff44f79489ea92415be59afb48b6f46b939553f855479828a6f87f9593", + "safeBlockHash": "0x8082deff44f79489ea92415be59afb48b6f46b939553f855479828a6f87f9593", + "finalizedBlockHash": "0x8082deff44f79489ea92415be59afb48b6f46b939553f855479828a6f87f9593" + }, + { + "timestamp": "0x20", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "suggestedFeeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "withdrawals": [], + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + ], + "id": 67 + }, + "response": { + "jsonrpc": "2.0", + "id": 67, + "result": { + "payloadStatus": { + "status": "VALID", + "latestValidHash": "0x8082deff44f79489ea92415be59afb48b6f46b939553f855479828a6f87f9593", + "validationError": null + }, + "payloadId": "0x282643d3a905e721" + } + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/06_prague_getPayloadV4.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/06_prague_getPayloadV4.json new file mode 100644 index 00000000000..4cb85d5f546 --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/06_prague_getPayloadV4.json @@ -0,0 +1,53 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "engine_getPayloadV4", + "params": [ + "0x282643d3a905e721" + ], + "id": 67 + }, + "response": { + "jsonrpc": "2.0", + "id": 67, + "result": { + "executionPayload": { + "parentHash": "0x8082deff44f79489ea92415be59afb48b6f46b939553f855479828a6f87f9593", + "feeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "stateRoot": "0x2e59916a57b535875bcd80d8472aeaa0027aa685d159804e8caa2f12d060155e", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "gasLimit": "0x1c9c380", + "gasUsed": "0x0", + "timestamp": "0x20", + "extraData": "0x", + "baseFeePerGas": "0x7", + "excessBlobGas": "0x0", + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactions": [], + "withdrawals": [], + "depositRequests": [], + "withdrawalRequests": [ + { + "sourceAddress": "0xa4664c40aacebd82a2db79f0ea36c06bc6a19adb", + "validatorPubkey": "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e", + "amount": "0x0" + } + ], + "consolidationRequests" : [], + "blockNumber": "0x2", + "blockHash": "0x27a2bc2ac21b3fc796f636bec1ec9cba100435f9a793176a83a5d4fa7cc13006", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "blobGasUsed": "0x0" + }, + "blockValue": "0x0", + "blobsBundle": { + "commitments": [], + "proofs": [], + "blobs": [] + }, + "shouldOverrideBuilder": false + } + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/07_eip6110_send_raw_transaction.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/07_prague_send_raw_transaction.json similarity index 100% rename from acceptance-tests/tests/src/test/resources/jsonrpc/engine/eip6110/test-cases/07_eip6110_send_raw_transaction.json rename to acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/07_prague_send_raw_transaction.json diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/08_prague_invalid_null_deposits_execute_payload.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/08_prague_invalid_null_deposits_execute_payload.json new file mode 100644 index 00000000000..ed75e54aafb --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/08_prague_invalid_null_deposits_execute_payload.json @@ -0,0 +1,41 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "engine_newPayloadV4", + "params": [ + { + "parentHash": "0x27a2bc2ac21b3fc796f636bec1ec9cba100435f9a793176a83a5d4fa7cc13006", + "feeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "stateRoot": "0x9b8c4a9a86cb49252075c0db2f0e72fb1e49350a0f70ea36f26f700201961e62", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "gasLimit": "0x1c9c380", + "gasUsed": "0x0", + "timestamp": "0x20", + "extraData": "0x", + "baseFeePerGas": "0x7", + "excessBlobGas": "0x0", + "transactions": [], + "withdrawals": [], + "depositRequests": null, + "blockNumber": "0x2", + "blockHash": "0x2331b2dc9c453e9a33685099742cbbcd529d42bd5681969f45754f06866c6766", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "blobGasUsed": "0x0" + }, + [], + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "id": 67 + }, + "response": { + "jsonrpc": "2.0", + "id": 67, + "error": { + "code": -32602, + "message": "Invalid params", + "data": "Missing deposit field" + } + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/09_prague_newPayloadV4.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/09_prague_newPayloadV4.json new file mode 100644 index 00000000000..17496f9ed78 --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/09_prague_newPayloadV4.json @@ -0,0 +1,59 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "engine_newPayloadV4", + "params": [ + { + "parentHash": "0x8082deff44f79489ea92415be59afb48b6f46b939553f855479828a6f87f9593", + "feeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "stateRoot": "0x961878fdcdff52ea42db0026f59aa414a5ec2835e56ed1a8ae50c80a9fe3a04b", + "logsBloom": "0x10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "gasLimit": "0x1c9c380", + "gasUsed": "0x14B6E", + "timestamp": "0x20", + "extraData": "0x", + "baseFeePerGas": "0x7", + "excessBlobGas": "0x0", + "transactions": [ + "0x02f9021c8217de808459682f008459682f0e830271009442424242424242424242424242424242424242428901bc16d674ec800000b901a422895118000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000120749715de5d1226545c6b3790f515d551a5cc5bf1d49c87a696860554d2fc4f14000000000000000000000000000000000000000000000000000000000000003096a96086cff07df17668f35f7418ef8798079167e3f4f9b72ecde17b28226137cf454ab1dd20ef5d924786ab3483c2f9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020003f5102dabe0a27b1746098d1dc17a5d3fbd478759fea9287e4e419b3c3cef20000000000000000000000000000000000000000000000000000000000000060b1acdb2c4d3df3f1b8d3bfd33421660df358d84d78d16c4603551935f4b67643373e7eb63dcb16ec359be0ec41fee33b03a16e80745f2374ff1d3c352508ac5d857c6476d3c3bcf7e6ca37427c9209f17be3af5264c0e2132b3dd1156c28b4e9c080a09f597089338d7f44f5c59f8230bb38f243849228a8d4e9d2e2956e6050f5b2c7a076486996c7e62802b8f95eee114783e4b403fd11093ba96286ff42c595f24452" + ], + "withdrawals": [], + "depositRequests": [ + { + "amount": "0x773594000", + "index": "0x0", + "pubkey": "0x96a96086cff07df17668f35f7418ef8798079167e3f4f9b72ecde17b28226137cf454ab1dd20ef5d924786ab3483c2f9", + "signature": "0xb1acdb2c4d3df3f1b8d3bfd33421660df358d84d78d16c4603551935f4b67643373e7eb63dcb16ec359be0ec41fee33b03a16e80745f2374ff1d3c352508ac5d857c6476d3c3bcf7e6ca37427c9209f17be3af5264c0e2132b3dd1156c28b4e9", + "withdrawalCredentials": "0x003f5102dabe0a27b1746098d1dc17a5d3fbd478759fea9287e4e419b3c3cef2" + } + ], + "withdrawalRequests": [ + { + "sourceAddress": "0xa4664c40aacebd82a2db79f0ea36c06bc6a19adb", + "amount": "0x0", + "validatorPubkey": "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e" + } + ], + "consolidationRequests": [], + "blockNumber": "0x2", + "blockHash": "0xc67a660f5d3c20ee603911bdff1e409e976f306883dff8ef4999dca3176f7dca", + "receiptsRoot": "0x79ee3424eb720a3ad4b1c5a372bb8160580cbe4d893778660f34213c685627a9", + "blobGasUsed": "0x0" + }, + [], + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "id": 67 + }, + "response": { + "jsonrpc": "2.0", + "id": 67, + "result": { + "status": "VALID", + "latestValidHash": "0xc67a660f5d3c20ee603911bdff1e409e976f306883dff8ef4999dca3176f7dca", + "validationError": null + } + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/10_prague_forkchoiceUpdatedV3.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/10_prague_forkchoiceUpdatedV3.json new file mode 100644 index 00000000000..ba0f1e8e5bd --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/10_prague_forkchoiceUpdatedV3.json @@ -0,0 +1,34 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "engine_forkchoiceUpdatedV3", + "params": [ + { + "headBlockHash": "0xc67a660f5d3c20ee603911bdff1e409e976f306883dff8ef4999dca3176f7dca", + "safeBlockHash": "0xc67a660f5d3c20ee603911bdff1e409e976f306883dff8ef4999dca3176f7dca", + "finalizedBlockHash": "0xc67a660f5d3c20ee603911bdff1e409e976f306883dff8ef4999dca3176f7dca" + }, + { + "timestamp": "0x30", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "suggestedFeeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "withdrawals": [], + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + ], + "id": 67 + }, + "response": { + "jsonrpc": "2.0", + "id": 67, + "result": { + "payloadStatus": { + "status": "VALID", + "latestValidHash": "0xc67a660f5d3c20ee603911bdff1e409e976f306883dff8ef4999dca3176f7dca", + "validationError": null + }, + "payloadId": "0x282643a16a58b5cf" + } + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/11_prague_getPayloadV4.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/11_prague_getPayloadV4.json new file mode 100644 index 00000000000..3b76fac10a1 --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/11_prague_getPayloadV4.json @@ -0,0 +1,47 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "engine_getPayloadV4", + "params": [ + "0x282643a16a58b5cf" + ], + "id": 67 + }, + "response": { + "jsonrpc": "2.0", + "id": 67, + "result": { + "executionPayload": { + "parentHash": "0xc67a660f5d3c20ee603911bdff1e409e976f306883dff8ef4999dca3176f7dca", + "feeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "stateRoot": "0x5fc31c01a451fe02f0e938de7ec7044aaba1159a81a1be64357bc70af226f304", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "gasLimit": "0x1c9c380", + "gasUsed": "0x0", + "timestamp": "0x30", + "extraData": "0x", + "baseFeePerGas": "0x7", + "excessBlobGas": "0x0", + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactions": [], + "withdrawals": [], + "depositRequests": [], + "withdrawalRequests": [], + "consolidationRequests" : [], + "blockNumber": "0x3", + "blockHash": "0xdbb55a049f14b8152695bf3bbd754aa1fd55bbe10b306eb49caa4bd7d7fcb634", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "blobGasUsed": "0x0" + }, + "blockValue": "0x0", + "blobsBundle": { + "commitments": [], + "proofs": [], + "blobs": [] + }, + "shouldOverrideBuilder": false + } + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/12_cancun_newPayloadV3.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/12_cancun_newPayloadV3.json new file mode 100644 index 00000000000..546872807c5 --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/12_cancun_newPayloadV3.json @@ -0,0 +1,42 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "engine_newPayloadV3", + "params": [ + { + "parentHash": "0xc67a660f5d3c20ee603911bdff1e409e976f306883dff8ef4999dca3176f7dca", + "feeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "stateRoot": "0x5fc31c01a451fe02f0e938de7ec7044aaba1159a81a1be64357bc70af226f304", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "gasLimit": "0x1c9c380", + "gasUsed": "0x0", + "timestamp": "0x30", + "extraData": "0x", + "baseFeePerGas": "0x7", + "transactions": [], + "withdrawals": [], + "depositRequests": [], + "withdrawalRequests": [], + "blockNumber": "0x3", + "blockHash": "0xdbb55a049f14b8152695bf3bbd754aa1fd55bbe10b306eb49caa4bd7d7fcb634", + "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "excessBlobGas": "0x0", + "blobGasUsed": "0x0" + }, + [], + "0x0000000000000000000000000000000000000000000000000000000000000000" + ], + "id": 67 + }, + "response": { + "jsonrpc": "2.0", + "id": 67, + "result": { + "status": "VALID", + "latestValidHash": "0xdbb55a049f14b8152695bf3bbd754aa1fd55bbe10b306eb49caa4bd7d7fcb634", + "validationError": null + } + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/13_prague_send_raw_transaction_create_exit.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/13_prague_send_raw_transaction_create_exit.json new file mode 100644 index 00000000000..fca8dd88e41 --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/13_prague_send_raw_transaction_create_exit.json @@ -0,0 +1,14 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "eth_sendRawTransaction", + "params": ["0xf8a08085e8d4a51000832dc6c09400a3ca265ebcb825b45f985a16cefb49958ce01702b8388706d19a62f28a6a6549f96c5adaebac9124a61d44868ec94f6d2d707c6a2f82c9162071231dfeb40e24bfde4ffdf2430000000000000000822fdfa00476c1a81f80f4c130acb5f8b8075468ba0893d766b7ec51a8d9723c573ad034a03bd3eaedabbaaf745f15023185ba66584ad3ee8bb40b9bef8c0b9ed27f8b1959"], + "id": 67 + }, + "response": { + "jsonrpc": "2.0", + "id": 67, + "result": "0x5878bb7458f51c06a121396769543bdc8b40221b7e214880b1e292fa04058f17" + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/14_prague_send_raw_transaction_consolidation_request.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/14_prague_send_raw_transaction_consolidation_request.json new file mode 100644 index 00000000000..85c41f2991f --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/14_prague_send_raw_transaction_consolidation_request.json @@ -0,0 +1,14 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "eth_sendRawTransaction", + "params": ["0xf8c80185e8d4a51000832dc6c09400b42dbf2194e931e80326d950320f7d9dbeac0201b860fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe822fe0a05b88b593926d340f448918ef1c6263356c37f2434774e0fdb1cb9d90cfa5a23ba003a86aac4adb774181ba51eda17efb5fbed99ad57895e6eb56ccdf508a88a7cc"], + "id": 67 + }, + "response": { + "jsonrpc": "2.0", + "id": 67, + "result": "0xa4252f576c4e16cb020f86f8a30d4fa990ee0cbfc84198a6d0eb118dd2f8b72d" + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/15_prague_forkchoiceUpdatedV3.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/15_prague_forkchoiceUpdatedV3.json new file mode 100644 index 00000000000..61750afbe74 --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/15_prague_forkchoiceUpdatedV3.json @@ -0,0 +1,34 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "engine_forkchoiceUpdatedV3", + "params": [ + { + "headBlockHash": "0xdbb55a049f14b8152695bf3bbd754aa1fd55bbe10b306eb49caa4bd7d7fcb634", + "safeBlockHash": "0xdbb55a049f14b8152695bf3bbd754aa1fd55bbe10b306eb49caa4bd7d7fcb634", + "finalizedBlockHash": "0xdbb55a049f14b8152695bf3bbd754aa1fd55bbe10b306eb49caa4bd7d7fcb634" + }, + { + "timestamp": "0x40", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "suggestedFeeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "withdrawals": [], + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + ], + "id": 67 + }, + "response": { + "jsonrpc": "2.0", + "id": 67, + "result": { + "payloadStatus": { + "status": "VALID", + "latestValidHash": "0xdbb55a049f14b8152695bf3bbd754aa1fd55bbe10b306eb49caa4bd7d7fcb634", + "validationError": null + }, + "payloadId": "0x28264396a9634d41" + } + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/16_prague_getPayloadV4.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/16_prague_getPayloadV4.json new file mode 100644 index 00000000000..51843931e7d --- /dev/null +++ b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/prague/test-cases/16_prague_getPayloadV4.json @@ -0,0 +1,63 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "engine_getPayloadV4", + "params": [ + "0x28264396a9634d41" + ], + "id": 67 + }, + "response": { + "jsonrpc": "2.0", + "id": 67, + "result": { + "executionPayload": { + "parentHash": "0xdbb55a049f14b8152695bf3bbd754aa1fd55bbe10b306eb49caa4bd7d7fcb634", + "feeRecipient": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "stateRoot": "0x49df1f1a1d28a23fa752230d442077768787d392e9edb70c83d727d31e55eaac", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000", + "gasLimit": "0x1c9c380", + "gasUsed": "0x3ad4d", + "timestamp": "0x40", + "extraData": "0x", + "baseFeePerGas": "0x7", + "excessBlobGas": "0x0", + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactions": [ + "0xf8a08085e8d4a51000832dc6c09400a3ca265ebcb825b45f985a16cefb49958ce01702b8388706d19a62f28a6a6549f96c5adaebac9124a61d44868ec94f6d2d707c6a2f82c9162071231dfeb40e24bfde4ffdf2430000000000000000822fdfa00476c1a81f80f4c130acb5f8b8075468ba0893d766b7ec51a8d9723c573ad034a03bd3eaedabbaaf745f15023185ba66584ad3ee8bb40b9bef8c0b9ed27f8b1959", + "0xf8c80185e8d4a51000832dc6c09400b42dbf2194e931e80326d950320f7d9dbeac0201b860fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe822fe0a05b88b593926d340f448918ef1c6263356c37f2434774e0fdb1cb9d90cfa5a23ba003a86aac4adb774181ba51eda17efb5fbed99ad57895e6eb56ccdf508a88a7cc" + ], + "withdrawals": [], + "depositRequests": [], + "withdrawalRequests": [ + { + "sourceAddress": "0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f", + "amount": "0x0", + "validatorPubkey": "0x8706d19a62f28a6a6549f96c5adaebac9124a61d44868ec94f6d2d707c6a2f82c9162071231dfeb40e24bfde4ffdf243" + } + ], + "consolidationRequests": [ + { + "sourceAddress": "0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f", + "sourcePubkey": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + "targetPubkey": "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe" + } + ], + "blockNumber": "0x4", + "receiptsRoot": "0x970fc81bb3e7fb21435f9a65a184aa9e3fd2f52b89fd859302b46954354266b5", + "blobGasUsed": "0x0", + "blockHash": "0x93df6f3484202f24c692354e2ab96e9948ae45eea6ad85faea121a389e468ea8" + }, + "blockValue": "0x3581baab15c12e5", + "blobsBundle": { + "commitments": [], + "proofs": [], + "blobs": [] + }, + "shouldOverrideBuilder": false + } + }, + "statusCode": 200, + "waitTime": 1500 +} \ No newline at end of file diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/shanghai/test-cases/05_shanghai_prepare_payload_invalid_null_withdrawals.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/shanghai/test-cases/05_shanghai_prepare_payload_invalid_null_withdrawals.json index ac5947c79be..ec5e2251791 100644 --- a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/shanghai/test-cases/05_shanghai_prepare_payload_invalid_null_withdrawals.json +++ b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/shanghai/test-cases/05_shanghai_prepare_payload_invalid_null_withdrawals.json @@ -21,7 +21,7 @@ "id" : 67, "error" : { "code" : -32602, - "message" : "Invalid params" + "message" : "Invalid withdrawals" } }, "statusCode" : 200 diff --git a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/shanghai/test-cases/09_shanghai_newPayloadV2_invalid_null_withdrawals.json b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/shanghai/test-cases/09_shanghai_newPayloadV2_invalid_null_withdrawals.json index 41718648049..0fe0a15dfc8 100644 --- a/acceptance-tests/tests/src/test/resources/jsonrpc/engine/shanghai/test-cases/09_shanghai_newPayloadV2_invalid_null_withdrawals.json +++ b/acceptance-tests/tests/src/test/resources/jsonrpc/engine/shanghai/test-cases/09_shanghai_newPayloadV2_invalid_null_withdrawals.json @@ -28,8 +28,7 @@ "id": 67, "error": { "code": -32602, - "message": "Invalid params", - "data": "Invalid withdrawals" + "message": "Invalid withdrawals" } }, "statusCode": 200 diff --git a/acceptance-tests/tests/src/test/resources/permissioning/simple_permissioning_genesis.json b/acceptance-tests/tests/src/test/resources/permissioning/simple_permissioning_genesis.json index 04ee1a4882e..5e8bc92a46a 100644 --- a/acceptance-tests/tests/src/test/resources/permissioning/simple_permissioning_genesis.json +++ b/acceptance-tests/tests/src/test/resources/permissioning/simple_permissioning_genesis.json @@ -4,7 +4,7 @@ "londonBlock": 0, "zeroBaseFee": true, "ethash": { - "fixeddifficulty": 100 + "fixeddifficulty": 500 } }, "nonce": "0x42", diff --git a/acceptance-tests/tests/src/test/resources/permissioning/simple_permissioning_v2_genesis.json b/acceptance-tests/tests/src/test/resources/permissioning/simple_permissioning_v2_genesis.json index b7ec620f310..aab9a35cdd3 100644 --- a/acceptance-tests/tests/src/test/resources/permissioning/simple_permissioning_v2_genesis.json +++ b/acceptance-tests/tests/src/test/resources/permissioning/simple_permissioning_v2_genesis.json @@ -4,7 +4,7 @@ "londonBlock": 0, "zeroBaseFee": true, "ethash": { - "fixeddifficulty": 100 + "fixeddifficulty": 500 } }, "nonce": "0x42", diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/README.md b/acceptance-tests/tests/src/test/resources/pki-certs/README.md deleted file mode 100644 index 25b23db2031..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/README.md +++ /dev/null @@ -1,127 +0,0 @@ -See `ethereum/p2p/src/test/resources/keys/README.md` which describes the details on how the -certificates are created. The same CA are used to create miner1-miner6. - -For `PkiQbftAcceptanceTest`: -`miner1`-`miner5` are signed with `partner1_ca` and `miner6` is signed with `partner2_ca`. -`miner5` and `miner6` are revoked and added in the crl list. - -Sample shell script that can be executed to recreate the certificates -~~~ -#! /bin/sh - -names=("partner1:miner1" "partner1:miner2" "partner1:miner3" "partner1:miner4" "partner1:miner5" "partner2:miner6") -crls=("partner1:miner5" "partner2:miner6") -KEY_ALG="EC -groupname secp256r1" -#KEY_ALG="RSA -keysize 2048" - -########## -CA_CERTS_PATH=./ca_certs -ROOT_CA_KS=$CA_CERTS_PATH/root_ca.p12 -INTER_CA_KS=$CA_CERTS_PATH/inter_ca.p12 -CRL_DIR=./crl - -keytool -exportcert -keystore $ROOT_CA_KS -storepass test123 -alias root_ca -rfc -file $CA_CERTS_PATH/root_ca.pem - -echo "Generating miner keystores..." -### Generate client keystores -for name in "${names[@]}" -do - IFS=':' read -r -a array <<< "$name" - partner=${array[0]} - client=${array[1]} - - PARTNER_CA_KEYSTORE="$CA_CERTS_PATH/${partner}_ca.p12" - CLIENT_PATH="./${client}" - KEYSTORE_PATH="./$CLIENT_PATH/${client}.p12" - NSSDB_PATH="${CLIENT_PATH}/nssdb" - - echo "$PARTNER_CA_KEYSTORE" - - mkdir -p $NSSDB_PATH - - echo "Generating keystore for Partner $partner Client $client" - keytool -genkeypair -keystore $KEYSTORE_PATH -storepass test123 -alias ${client} \ - -keyalg $KEY_ALG -validity 36500 \ - -dname "CN=localhost, OU=${partner}" \ - -ext san=dns:localhost,ip:127.0.0.1 - - echo "Creating CSR for $client and signing it with ${partner}_ca" - keytool -storepass test123 -keystore $KEYSTORE_PATH -certreq -alias ${client} \ - | keytool -storepass test123 -keystore $PARTNER_CA_KEYSTORE -gencert -alias "${partner}_ca" -ext ku:c=digitalSignature,nonRepudiation,keyEncipherment -ext eku=sA,cA \ - -rfc > "${CLIENT_PATH}/${client}.pem" - - echo "Concat root_ca.pem to ${client}.pem" - cat "${CA_CERTS_PATH}/root_ca.pem" >> "${CLIENT_PATH}/${client}.pem" - - echo "Importing signed $client.pem CSR into $KEYSTORE_PATH" - keytool -keystore $KEYSTORE_PATH -importcert -alias $client \ - -storepass test123 -noprompt -file "${CLIENT_PATH}/${client}.pem" - - echo "Converting p12 to jks" - keytool -importkeystore -srckeystore $KEYSTORE_PATH -srcstoretype PKCS12 -destkeystore "$CLIENT_PATH/${client}.jks" -deststoretype JKS -srcstorepass test123 -deststorepass test123 -srcalias $client -destalias $client -srckeypass test123 -destkeypass test123 -noprompt - - echo "Initialize nss" - echo "test123" > ${CLIENT_PATH}/nsspin.txt - certutil -N -d sql:${NSSDB_PATH} -f "${CLIENT_PATH}/nsspin.txt" - # hack to make Java SunPKCS11 work with new sql version of nssdb - touch ${NSSDB_PATH}/secmod.db - - pk12util -i $KEYSTORE_PATH -d sql:${NSSDB_PATH} -k ${CLIENT_PATH}/nsspin.txt -W test123 - echo "Fixing truststores in sql:${NSSDB_PATH}" - certutil -M -n "CN=root.ca.besu.com" -t CT,C,C -d sql:"$NSSDB_PATH" -f ${CLIENT_PATH}/nsspin.txt - certutil -M -n "CN=inter.ca.besu.com" -t CT,C,C -d sql:"$NSSDB_PATH" -f ${CLIENT_PATH}/nsspin.txt - certutil -M -n "CN=${partner}.ca.besu.com" -t CT,C,C -d sql:"$NSSDB_PATH" -f ${CLIENT_PATH}/nsspin.txt - - certutil -d sql:"$NSSDB_PATH" -f nsspin.txt -L - - echo "Creating pkcs11 nss config file" - cat <${CLIENT_PATH}/nss.cfg -name = NSScrypto-${partner}-${client} -nssSecmodDirectory = ./src/test/resources/pki-certs/${client}/nssdb -nssDbMode = readOnly -nssModule = keystore -showInfo = true -EOF - - # remove pem files - rm "${CLIENT_PATH}/${client}.pem" - - # create truststore - echo "Creating truststore ..." - keytool -exportcert -keystore $ROOT_CA_KS -storepass test123 -alias root_ca -rfc | keytool -import -trustcacerts -alias root_ca -keystore "${CLIENT_PATH}/truststore.p12" -storepass test123 -noprompt - keytool -exportcert -keystore $INTER_CA_KS -storepass test123 -alias inter_ca -rfc | keytool -import -trustcacerts -alias inter_ca -keystore "${CLIENT_PATH}/truststore.p12" -storepass test123 -noprompt - keytool -exportcert -keystore $PARTNER_CA_KEYSTORE -storepass test123 -alias "${partner}_ca" -rfc | keytool -import -trustcacerts -alias "${partner}_ca" -keystore "${CLIENT_PATH}/truststore.p12" -storepass test123 -noprompt - -done -rm $CA_CERTS_PATH/root_ca.pem -echo "Keystores and nss database created" - -## create crl list -mkdir -p $CRL_DIR -rm $CRL_DIR/crl.pem - -for crl in "${crls[@]}" -do - IFS=':' read -r -a array <<< "$crl" - partner=${array[0]} - client=${array[1]} - - echo "Exporting CA certificate and private key" - openssl pkcs12 -nodes -in "$CA_CERTS_PATH/${partner}_ca.p12" -out "$CRL_DIR/${partner}_ca_key.pem" -passin pass:test123 -nocerts - openssl pkcs12 -nodes -in "$CA_CERTS_PATH/${partner}_ca.p12" -out "$CRL_DIR/${partner}_ca.pem" -passin pass:test123 -nokeys - - echo "Export $client certificate" - openssl pkcs12 -nodes -in "./${client}/${client}.p12" -out "$CRL_DIR/${client}.pem" -passin pass:test123 -nokeys - - ## On Mac, use gnutls-certtool, on Linux use certtool - echo "Creating crl" - printf '365\n\n' | gnutls-certtool --generate-crl --load-ca-privkey "$CRL_DIR/${partner}_ca_key.pem" --load-ca-certificate "$CRL_DIR/${partner}_ca.pem" \ - --load-certificate "$CRL_DIR/${client}.pem" >> $CRL_DIR/crl.pem - - rm "$CRL_DIR/${partner}_ca_key.pem" - rm "$CRL_DIR/${partner}_ca.pem" - rm "$CRL_DIR/${client}.pem" - -done - -~~~ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/ca_certs/inter_ca.p12 b/acceptance-tests/tests/src/test/resources/pki-certs/ca_certs/inter_ca.p12 deleted file mode 100644 index a6e945c2f06..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/ca_certs/inter_ca.p12 and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/ca_certs/partner1_ca.p12 b/acceptance-tests/tests/src/test/resources/pki-certs/ca_certs/partner1_ca.p12 deleted file mode 100644 index fc89f3e90f4..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/ca_certs/partner1_ca.p12 and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/ca_certs/partner2_ca.p12 b/acceptance-tests/tests/src/test/resources/pki-certs/ca_certs/partner2_ca.p12 deleted file mode 100644 index c9fe7186792..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/ca_certs/partner2_ca.p12 and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/ca_certs/root_ca.p12 b/acceptance-tests/tests/src/test/resources/pki-certs/ca_certs/root_ca.p12 deleted file mode 100644 index 8fc62a23ace..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/ca_certs/root_ca.p12 and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/create.sh b/acceptance-tests/tests/src/test/resources/pki-certs/create.sh deleted file mode 100644 index c780e5e113e..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/create.sh +++ /dev/null @@ -1,176 +0,0 @@ -#! /bin/sh - -set -e - -names=("partner1:miner1" "partner1:miner2" "partner1:miner3" "partner1:miner4" "partner1:miner5" "partner2:miner6") -crls=("partner1:miner5" "partner2:miner6") -KEY_ALG="EC -groupname secp256r1" -#KEY_ALG="RSA -keysize 2048" - -########## -CA_CERTS_PATH=./ca_certs -ROOT_CA_KS=$CA_CERTS_PATH/root_ca.p12 -INTER_CA_KS=$CA_CERTS_PATH/inter_ca.p12 -PARTNER1_CA_KS=$CA_CERTS_PATH/partner1_ca.p12 -PARTNER2_CA_KS=$CA_CERTS_PATH/partner2_ca.p12 -CRL_DIR=./crl - -mkdir $CA_CERTS_PATH - -keytool -genkeypair -alias root_ca -dname "CN=root.ca.besu.com" -ext bc:c -keyalg RSA -keysize 2048 \ --sigalg SHA256WithRSA -validity 36500 \ --storepass test123 \ --keystore $ROOT_CA_KS - -keytool -exportcert -keystore $ROOT_CA_KS -storepass test123 -alias root_ca -rfc -file $CA_CERTS_PATH/root_ca.pem - -keytool -genkeypair -alias inter_ca -dname "CN=inter.ca.besu.com" \ --ext bc:c=ca:true,pathlen:1 -ext ku:c=dS,kCS,cRLs \ --keyalg RSA -sigalg SHA256WithRSA -validity 36500 \ --storepass test123 \ --keystore $INTER_CA_KS - -keytool -exportcert -keystore $INTER_CA_KS -storepass test123 -alias inter_ca -rfc -file $CA_CERTS_PATH/inter_ca.pem - -keytool -genkeypair -alias partner1_ca -dname "CN=partner1.ca.besu.com" \ --ext bc:c=ca:true,pathlen:0 -ext ku:c=dS,kCS,cRLs \ --keyalg RSA -sigalg SHA256WithRSA -validity 36500 \ --storepass test123 \ --keystore $PARTNER1_CA_KS - -keytool -exportcert -keystore $PARTNER1_CA_KS -storepass test123 -alias partner1_ca -rfc -file $CA_CERTS_PATH/partner1_ca.pem - -keytool -genkeypair -alias partner2_ca -dname "CN=partner2.ca.besu.com" \ --ext bc:c=ca:true,pathlen:0 -ext ku:c=dS,kCS,cRLs \ --keyalg RSA -sigalg SHA256WithRSA -validity 36500 \ --storepass test123 \ --keystore $PARTNER2_CA_KS - -keytool -exportcert -keystore $PARTNER2_CA_KS -storepass test123 -alias partner2_ca -rfc -file $CA_CERTS_PATH/partner2_ca.pem - -keytool -storepass test123 -keystore $INTER_CA_KS -certreq -alias inter_ca \ -| keytool -storepass test123 -keystore $ROOT_CA_KS -gencert -validity 36500 -alias root_ca \ --ext bc:c=ca:true,pathlen:1 -ext ku:c=dS,kCS,cRLs -rfc > $CA_CERTS_PATH/inter_ca.pem - -cat $CA_CERTS_PATH/root_ca.pem >> $CA_CERTS_PATH/inter_ca.pem - -keytool -keystore $INTER_CA_KS -importcert -alias inter_ca \ --storepass test123 -noprompt -file $CA_CERTS_PATH/inter_ca.pem - -keytool -storepass test123 -keystore $PARTNER1_CA_KS -certreq -alias partner1_ca \ -| keytool -storepass test123 -keystore $INTER_CA_KS -gencert -validity 36500 -alias inter_ca \ --ext bc:c=ca:true,pathlen:0 -ext ku:c=dS,kCS,cRLs -rfc > $CA_CERTS_PATH/partner1_ca.pem - -keytool -storepass test123 -keystore $PARTNER2_CA_KS -certreq -alias partner2_ca \ -| keytool -storepass test123 -keystore $INTER_CA_KS -gencert -validity 36500 -alias inter_ca \ --ext bc:c=ca:true,pathlen:0 -ext ku:c=dS,kCS,cRLs -rfc > $CA_CERTS_PATH/partner2_ca.pem - -cat $CA_CERTS_PATH/inter_ca.pem >> $CA_CERTS_PATH/partner1_ca.pem -cat $CA_CERTS_PATH/inter_ca.pem >> $CA_CERTS_PATH/partner2_ca.pem - -keytool -keystore $PARTNER1_CA_KS -importcert -alias partner1_ca \ --storepass test123 -noprompt -file $CA_CERTS_PATH/partner1_ca.pem - -keytool -keystore $PARTNER2_CA_KS -importcert -alias partner2_ca \ --storepass test123 -noprompt -file $CA_CERTS_PATH/partner2_ca.pem - -echo "Generating miner keystores..." -### Generate client keystores -for name in "${names[@]}" -do - IFS=':' read -r -a array <<< "$name" - partner=${array[0]} - client=${array[1]} - - PARTNER_CA_KEYSTORE="$CA_CERTS_PATH/${partner}_ca.p12" - CLIENT_PATH="./${client}" - KEYSTORE_PATH="./$CLIENT_PATH/${client}.p12" - NSSDB_PATH="${CLIENT_PATH}/nssdb" - - echo "$PARTNER_CA_KEYSTORE" - - mkdir -p $NSSDB_PATH - - echo "Generating keystore for Partner $partner Client $client" - keytool -genkeypair -keystore $KEYSTORE_PATH -storepass test123 -alias ${client} \ - -keyalg $KEY_ALG -validity 36500 \ - -dname "CN=localhost, OU=${partner}" \ - -ext san=dns:localhost,ip:127.0.0.1 - - echo "Creating CSR for $client and signing it with ${partner}_ca" - keytool -storepass test123 -keystore $KEYSTORE_PATH -certreq -alias ${client} \ - | keytool -storepass test123 -keystore $PARTNER_CA_KEYSTORE -gencert -validity 36500 -alias "${partner}_ca" -ext ku:c=digitalSignature,nonRepudiation,keyEncipherment -ext eku=sA,cA \ - -rfc > "${CLIENT_PATH}/${client}.pem" - - echo "Concat root_ca.pem to ${client}.pem" - cat "${CA_CERTS_PATH}/root_ca.pem" >> "${CLIENT_PATH}/${client}.pem" - - echo "Importing signed $client.pem CSR into $KEYSTORE_PATH" - keytool -keystore $KEYSTORE_PATH -importcert -alias $client \ - -storepass test123 -noprompt -file "${CLIENT_PATH}/${client}.pem" - - echo "Converting p12 to jks" - keytool -importkeystore -srckeystore $KEYSTORE_PATH -srcstoretype PKCS12 -destkeystore "$CLIENT_PATH/${client}.jks" -deststoretype JKS -srcstorepass test123 -deststorepass test123 -srcalias $client -destalias $client -srckeypass test123 -destkeypass test123 -noprompt - - echo "Initialize nss" - echo "test123" > ${CLIENT_PATH}/nsspin.txt - certutil -N -d sql:${NSSDB_PATH} -f "${CLIENT_PATH}/nsspin.txt" - # hack to make Java SunPKCS11 work with new sql version of nssdb - touch ${NSSDB_PATH}/secmod.db - - pk12util -i $KEYSTORE_PATH -d sql:${NSSDB_PATH} -k ${CLIENT_PATH}/nsspin.txt -W test123 - echo "Fixing truststores in sql:${NSSDB_PATH}" - certutil -M -n "CN=root.ca.besu.com" -t CT,C,C -d sql:"$NSSDB_PATH" -f ${CLIENT_PATH}/nsspin.txt - certutil -M -n "CN=inter.ca.besu.com" -t u,u,u -d sql:"$NSSDB_PATH" -f ${CLIENT_PATH}/nsspin.txt - certutil -M -n "CN=${partner}.ca.besu.com" -t u,u,u -d sql:"$NSSDB_PATH" -f ${CLIENT_PATH}/nsspin.txt - - certutil -d sql:"$NSSDB_PATH" -f nsspin.txt -L - - echo "Creating pkcs11 nss config file" - cat <${CLIENT_PATH}/nss.cfg -name = NSScrypto-${partner}-${client} -nssSecmodDirectory = ./src/test/resources/pki-certs/${client}/nssdb -nssDbMode = readOnly -nssModule = keystore -showInfo = true -EOF - - # remove pem files - rm "${CLIENT_PATH}/${client}.pem" - - # create truststore - echo "Creating truststore ..." - keytool -exportcert -keystore $ROOT_CA_KS -storepass test123 -alias root_ca -rfc | keytool -import -trustcacerts -alias root_ca -keystore "${CLIENT_PATH}/truststore.p12" -storepass test123 -noprompt -## keytool -exportcert -keystore $INTER_CA_KS -storepass test123 -alias inter_ca -rfc | keytool -import -trustcacerts -alias inter_ca -keystore "${CLIENT_PATH}/truststore.p12" -storepass test123 -noprompt -## keytool -exportcert -keystore $PARTNER_CA_KEYSTORE -storepass test123 -alias "${partner}_ca" -rfc | keytool -import -trustcacerts -alias "${partner}_ca" -keystore "${CLIENT_PATH}/truststore.p12" -storepass test123 -noprompt - -done -rm $CA_CERTS_PATH/root_ca.pem -echo "Keystores and nss database created" - -## create crl list -mkdir -p $CRL_DIR -## rm $CRL_DIR/crl.pem - -for crl in "${crls[@]}" -do - IFS=':' read -r -a array <<< "$crl" - partner=${array[0]} - client=${array[1]} - - echo "Exporting CA certificate and private key" - openssl pkcs12 -nodes -in "$CA_CERTS_PATH/${partner}_ca.p12" -out "$CRL_DIR/${partner}_ca_key.pem" -passin pass:test123 -nocerts - openssl pkcs12 -nodes -in "$CA_CERTS_PATH/${partner}_ca.p12" -out "$CRL_DIR/${partner}_ca.pem" -passin pass:test123 -nokeys - - echo "Export $client certificate" - openssl pkcs12 -nodes -in "./${client}/${client}.p12" -out "$CRL_DIR/${client}.pem" -passin pass:test123 -nokeys - - ## On Mac, use gnutls-certtool, on Linux use certtool - echo "Creating crl" - printf '365\n\n' | gnutls-certtool --generate-crl --load-ca-privkey "$CRL_DIR/${partner}_ca_key.pem" --load-ca-certificate "$CRL_DIR/${partner}_ca.pem" \ - --load-certificate "$CRL_DIR/${client}.pem" >> $CRL_DIR/crl.pem - - rm "$CRL_DIR/${partner}_ca_key.pem" - rm "$CRL_DIR/${partner}_ca.pem" - rm "$CRL_DIR/${client}.pem" -done diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/crl/crl.pem b/acceptance-tests/tests/src/test/resources/pki-certs/crl/crl.pem deleted file mode 100644 index 763d01c19d4..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/crl/crl.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN X509 CRL----- -MIICGzCCAQMCAQEwDQYJKoZIhvcNAQELBQAwHzEdMBsGA1UEAxMUcGFydG5lcjEu -Y2EuYmVzdS5jb20XDTIzMDUwNDEwMTUzNVoXDTI0MDUwMzEwMTUzNVowbTAZAghF -KUAnwQI0rxcNMjMwNTA0MTAxNTM1WjAZAggBqEW2S0yD7hcNMjMwNTA0MTAxNTM1 -WjAZAghxoeavxdMy8xcNMjMwNTA0MTAxNTM1WjAaAgkAgWKzx/tKZWkXDTIzMDUw -NDEwMTUzNVqgQTA/MB8GA1UdIwQYMBaAFJcP76nbNK50KG5jmh6CvsxlAhJjMBwG -A1UdFAQVAhNkU4XHFwNbgBqsTIFexJjuzEvyMA0GCSqGSIb3DQEBCwUAA4IBAQB2 -fhAhVwRBtHdwqhGjRlgbz4i6E0CtoL/02Vazib1OiRAXCkyFJL04U3FGcrPa89Dt -ClZE0G38+Jw0Be0tEpn9A8doSbLr73w1GqW3BqNTw/qjbc6R2x28A1VIVPwV6bZH -5P59YtDV+SjSPNxqkwRMyXqGZ2WIMwUS3u47Es9vMsjChXUJWU6W+jf3LYO/dt+V -7xSchRpljhBtMB8MIoXILBq9uOSFalLmy94YzK2Rw1ZG2SVy2QZ6ZXHvZ/omLbPL -kd4oAiN7L0OLOkFVHyb9bVP6DUWfXxSxBdszbQzHCy74NEsFUC0xqq0xpxwQRRfD -codJtbEVJraSsSBkB78n ------END X509 CRL----- ------BEGIN X509 CRL----- -MIICGzCCAQMCAQEwDQYJKoZIhvcNAQELBQAwHzEdMBsGA1UEAxMUcGFydG5lcjIu -Y2EuYmVzdS5jb20XDTIzMDUwNDEwMTUzNVoXDTI0MDUwMzEwMTUzNVowbTAZAggF -p9b0zZl1RxcNMjMwNTA0MTAxNTM1WjAZAgh7MQ7e4x/GbRcNMjMwNTA0MTAxNTM1 -WjAZAghxoeavxdMy8xcNMjMwNTA0MTAxNTM1WjAaAgkAgWKzx/tKZWkXDTIzMDUw -NDEwMTUzNVqgQTA/MB8GA1UdIwQYMBaAFJuQMv8IsgbJS8FfPZZx+hSgj7PBMBwG -A1UdFAQVAhNkU4XHGmnm4OkmS4KBFW1nS4csMA0GCSqGSIb3DQEBCwUAA4IBAQB2 -43mCjuMmB+MXpl+Axn3b/4V2f0HmbUFhF/andWKUwzC47HoQ+WzXoTV0xisHGCgH -SYlrLdWd+pFh24H7TrKgqvmwtVmUFwm6DphXW3AHvaePWIrAy7L5ZrdOQB9TZPC1 -Ly+6x0oKoueiHodWivLQx+CJVbPAzxFEVh0JjecoFw8Tf9FGTqy8jJRdno9HgKDg -BB7w7kPGF7xoaAbukwTXFz7f1nep44oqge+leEc398tdFDxmwralXAUB0A2v/vDG -cSZTr+fyTri+zHjQzeq6//y2GF7S56KSyBXDXTJrvqtuijiVHTzQku+pbVNNrid5 -LgCJI7Phj2Q8k26z0+JJ ------END X509 CRL----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner1/miner1.jks b/acceptance-tests/tests/src/test/resources/pki-certs/miner1/miner1.jks deleted file mode 100644 index df9dfad4b5d..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner1/miner1.jks and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner1/miner1.p12 b/acceptance-tests/tests/src/test/resources/pki-certs/miner1/miner1.p12 deleted file mode 100644 index baba59128aa..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner1/miner1.p12 and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner1/nss.cfg b/acceptance-tests/tests/src/test/resources/pki-certs/miner1/nss.cfg deleted file mode 100644 index d76bd9c163c..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/miner1/nss.cfg +++ /dev/null @@ -1,5 +0,0 @@ -name = NSScrypto-partner1-miner1 -nssSecmodDirectory = ./src/test/resources/pki-certs/miner1/nssdb -nssDbMode = readOnly -nssModule = keystore -showInfo = true diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner1/nssdb/cert9.db b/acceptance-tests/tests/src/test/resources/pki-certs/miner1/nssdb/cert9.db deleted file mode 100644 index 33e83d02b71..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner1/nssdb/cert9.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner1/nssdb/key4.db b/acceptance-tests/tests/src/test/resources/pki-certs/miner1/nssdb/key4.db deleted file mode 100644 index 03a4a5e5b86..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner1/nssdb/key4.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner1/nssdb/pkcs11.txt b/acceptance-tests/tests/src/test/resources/pki-certs/miner1/nssdb/pkcs11.txt deleted file mode 100644 index fe947a8221a..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/miner1/nssdb/pkcs11.txt +++ /dev/null @@ -1,5 +0,0 @@ -library= -name=NSS Internal PKCS #11 Module -parameters=configdir='sql:./miner1/nssdb' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription='' -NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30}) - diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner1/nssdb/secmod.db b/acceptance-tests/tests/src/test/resources/pki-certs/miner1/nssdb/secmod.db deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner1/nsspin.txt b/acceptance-tests/tests/src/test/resources/pki-certs/miner1/nsspin.txt deleted file mode 100644 index 5271a526801..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/miner1/nsspin.txt +++ /dev/null @@ -1 +0,0 @@ -test123 diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner1/truststore.p12 b/acceptance-tests/tests/src/test/resources/pki-certs/miner1/truststore.p12 deleted file mode 100644 index ddbd8ee57be..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner1/truststore.p12 and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner2/miner2.jks b/acceptance-tests/tests/src/test/resources/pki-certs/miner2/miner2.jks deleted file mode 100644 index 2abd88e9d80..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner2/miner2.jks and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner2/miner2.p12 b/acceptance-tests/tests/src/test/resources/pki-certs/miner2/miner2.p12 deleted file mode 100644 index aeec006f0e9..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner2/miner2.p12 and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner2/nss.cfg b/acceptance-tests/tests/src/test/resources/pki-certs/miner2/nss.cfg deleted file mode 100644 index 995d452fc4c..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/miner2/nss.cfg +++ /dev/null @@ -1,5 +0,0 @@ -name = NSScrypto-partner1-miner2 -nssSecmodDirectory = ./src/test/resources/pki-certs/miner2/nssdb -nssDbMode = readOnly -nssModule = keystore -showInfo = true diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner2/nssdb/cert9.db b/acceptance-tests/tests/src/test/resources/pki-certs/miner2/nssdb/cert9.db deleted file mode 100644 index 21d6ea18410..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner2/nssdb/cert9.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner2/nssdb/key4.db b/acceptance-tests/tests/src/test/resources/pki-certs/miner2/nssdb/key4.db deleted file mode 100644 index 3d77b19abb4..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner2/nssdb/key4.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner2/nssdb/pkcs11.txt b/acceptance-tests/tests/src/test/resources/pki-certs/miner2/nssdb/pkcs11.txt deleted file mode 100644 index 90b09f83b25..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/miner2/nssdb/pkcs11.txt +++ /dev/null @@ -1,5 +0,0 @@ -library= -name=NSS Internal PKCS #11 Module -parameters=configdir='sql:./miner2/nssdb' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription='' -NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30}) - diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner2/nssdb/secmod.db b/acceptance-tests/tests/src/test/resources/pki-certs/miner2/nssdb/secmod.db deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner2/nsspin.txt b/acceptance-tests/tests/src/test/resources/pki-certs/miner2/nsspin.txt deleted file mode 100644 index 5271a526801..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/miner2/nsspin.txt +++ /dev/null @@ -1 +0,0 @@ -test123 diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner2/truststore.p12 b/acceptance-tests/tests/src/test/resources/pki-certs/miner2/truststore.p12 deleted file mode 100644 index 8d9754074b7..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner2/truststore.p12 and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner3/miner3.jks b/acceptance-tests/tests/src/test/resources/pki-certs/miner3/miner3.jks deleted file mode 100644 index 79ee2cb0ecc..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner3/miner3.jks and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner3/miner3.p12 b/acceptance-tests/tests/src/test/resources/pki-certs/miner3/miner3.p12 deleted file mode 100644 index c7e42d1af69..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner3/miner3.p12 and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner3/nss.cfg b/acceptance-tests/tests/src/test/resources/pki-certs/miner3/nss.cfg deleted file mode 100644 index 65e04bccbfd..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/miner3/nss.cfg +++ /dev/null @@ -1,5 +0,0 @@ -name = NSScrypto-partner1-miner3 -nssSecmodDirectory = ./src/test/resources/pki-certs/miner3/nssdb -nssDbMode = readOnly -nssModule = keystore -showInfo = true diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner3/nssdb/cert9.db b/acceptance-tests/tests/src/test/resources/pki-certs/miner3/nssdb/cert9.db deleted file mode 100644 index 5b295bcda9a..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner3/nssdb/cert9.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner3/nssdb/key4.db b/acceptance-tests/tests/src/test/resources/pki-certs/miner3/nssdb/key4.db deleted file mode 100644 index d05913b651a..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner3/nssdb/key4.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner3/nssdb/pkcs11.txt b/acceptance-tests/tests/src/test/resources/pki-certs/miner3/nssdb/pkcs11.txt deleted file mode 100644 index c4368a178ff..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/miner3/nssdb/pkcs11.txt +++ /dev/null @@ -1,5 +0,0 @@ -library= -name=NSS Internal PKCS #11 Module -parameters=configdir='sql:./miner3/nssdb' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription='' -NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30}) - diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner3/nssdb/secmod.db b/acceptance-tests/tests/src/test/resources/pki-certs/miner3/nssdb/secmod.db deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner3/nsspin.txt b/acceptance-tests/tests/src/test/resources/pki-certs/miner3/nsspin.txt deleted file mode 100644 index 5271a526801..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/miner3/nsspin.txt +++ /dev/null @@ -1 +0,0 @@ -test123 diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner3/truststore.p12 b/acceptance-tests/tests/src/test/resources/pki-certs/miner3/truststore.p12 deleted file mode 100644 index 028424cebdf..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner3/truststore.p12 and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner4/miner4.jks b/acceptance-tests/tests/src/test/resources/pki-certs/miner4/miner4.jks deleted file mode 100644 index 6cde7b1a0ba..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner4/miner4.jks and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner4/miner4.p12 b/acceptance-tests/tests/src/test/resources/pki-certs/miner4/miner4.p12 deleted file mode 100644 index 386fe0d4b0b..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner4/miner4.p12 and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner4/nss.cfg b/acceptance-tests/tests/src/test/resources/pki-certs/miner4/nss.cfg deleted file mode 100644 index 29082f64b54..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/miner4/nss.cfg +++ /dev/null @@ -1,5 +0,0 @@ -name = NSScrypto-partner1-miner4 -nssSecmodDirectory = ./src/test/resources/pki-certs/miner4/nssdb -nssDbMode = readOnly -nssModule = keystore -showInfo = true diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner4/nssdb/cert9.db b/acceptance-tests/tests/src/test/resources/pki-certs/miner4/nssdb/cert9.db deleted file mode 100644 index fd5b048d8c9..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner4/nssdb/cert9.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner4/nssdb/key4.db b/acceptance-tests/tests/src/test/resources/pki-certs/miner4/nssdb/key4.db deleted file mode 100644 index 127bbd567e9..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner4/nssdb/key4.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner4/nssdb/pkcs11.txt b/acceptance-tests/tests/src/test/resources/pki-certs/miner4/nssdb/pkcs11.txt deleted file mode 100644 index 730c97feb8e..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/miner4/nssdb/pkcs11.txt +++ /dev/null @@ -1,5 +0,0 @@ -library= -name=NSS Internal PKCS #11 Module -parameters=configdir='sql:./miner4/nssdb' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription='' -NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30}) - diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner4/nssdb/secmod.db b/acceptance-tests/tests/src/test/resources/pki-certs/miner4/nssdb/secmod.db deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner4/nsspin.txt b/acceptance-tests/tests/src/test/resources/pki-certs/miner4/nsspin.txt deleted file mode 100644 index 5271a526801..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/miner4/nsspin.txt +++ /dev/null @@ -1 +0,0 @@ -test123 diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner4/truststore.p12 b/acceptance-tests/tests/src/test/resources/pki-certs/miner4/truststore.p12 deleted file mode 100644 index c6c7b5decff..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner4/truststore.p12 and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner5/miner5.jks b/acceptance-tests/tests/src/test/resources/pki-certs/miner5/miner5.jks deleted file mode 100644 index a9dc3190a97..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner5/miner5.jks and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner5/miner5.p12 b/acceptance-tests/tests/src/test/resources/pki-certs/miner5/miner5.p12 deleted file mode 100644 index 7049be61bd0..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner5/miner5.p12 and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner5/nss.cfg b/acceptance-tests/tests/src/test/resources/pki-certs/miner5/nss.cfg deleted file mode 100644 index 063e31a2a35..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/miner5/nss.cfg +++ /dev/null @@ -1,5 +0,0 @@ -name = NSScrypto-partner1-miner5 -nssSecmodDirectory = ./src/test/resources/pki-certs/miner5/nssdb -nssDbMode = readOnly -nssModule = keystore -showInfo = true diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner5/nssdb/cert9.db b/acceptance-tests/tests/src/test/resources/pki-certs/miner5/nssdb/cert9.db deleted file mode 100644 index f731ba7404c..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner5/nssdb/cert9.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner5/nssdb/key4.db b/acceptance-tests/tests/src/test/resources/pki-certs/miner5/nssdb/key4.db deleted file mode 100644 index e366aa9e272..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner5/nssdb/key4.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner5/nssdb/pkcs11.txt b/acceptance-tests/tests/src/test/resources/pki-certs/miner5/nssdb/pkcs11.txt deleted file mode 100644 index bd574fbdc7f..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/miner5/nssdb/pkcs11.txt +++ /dev/null @@ -1,5 +0,0 @@ -library= -name=NSS Internal PKCS #11 Module -parameters=configdir='sql:./miner5/nssdb' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription='' -NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30}) - diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner5/nssdb/secmod.db b/acceptance-tests/tests/src/test/resources/pki-certs/miner5/nssdb/secmod.db deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner5/nsspin.txt b/acceptance-tests/tests/src/test/resources/pki-certs/miner5/nsspin.txt deleted file mode 100644 index 5271a526801..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/miner5/nsspin.txt +++ /dev/null @@ -1 +0,0 @@ -test123 diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner5/truststore.p12 b/acceptance-tests/tests/src/test/resources/pki-certs/miner5/truststore.p12 deleted file mode 100644 index 7d54dfe7f6d..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner5/truststore.p12 and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner6/miner6.jks b/acceptance-tests/tests/src/test/resources/pki-certs/miner6/miner6.jks deleted file mode 100644 index b73d203e889..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner6/miner6.jks and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner6/miner6.p12 b/acceptance-tests/tests/src/test/resources/pki-certs/miner6/miner6.p12 deleted file mode 100644 index 8df1575b0e3..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner6/miner6.p12 and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner6/nss.cfg b/acceptance-tests/tests/src/test/resources/pki-certs/miner6/nss.cfg deleted file mode 100644 index 60a2e65bc46..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/miner6/nss.cfg +++ /dev/null @@ -1,5 +0,0 @@ -name = NSScrypto-partner2-miner6 -nssSecmodDirectory = ./src/test/resources/pki-certs/miner6/nssdb -nssDbMode = readOnly -nssModule = keystore -showInfo = true diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner6/nssdb/cert9.db b/acceptance-tests/tests/src/test/resources/pki-certs/miner6/nssdb/cert9.db deleted file mode 100644 index 8bc1ba42f15..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner6/nssdb/cert9.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner6/nssdb/key4.db b/acceptance-tests/tests/src/test/resources/pki-certs/miner6/nssdb/key4.db deleted file mode 100644 index aa078f69b7d..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner6/nssdb/key4.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner6/nssdb/pkcs11.txt b/acceptance-tests/tests/src/test/resources/pki-certs/miner6/nssdb/pkcs11.txt deleted file mode 100644 index a47ca829b24..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/miner6/nssdb/pkcs11.txt +++ /dev/null @@ -1,5 +0,0 @@ -library= -name=NSS Internal PKCS #11 Module -parameters=configdir='sql:./miner6/nssdb' certPrefix='' keyPrefix='' secmod='secmod.db' flags= updatedir='' updateCertPrefix='' updateKeyPrefix='' updateid='' updateTokenDescription='' -NSS=Flags=internal,critical trustOrder=75 cipherOrder=100 slotParams=(1={slotFlags=[ECC,RSA,DSA,DH,RC2,RC4,DES,RANDOM,SHA1,MD5,MD2,SSL,TLS,AES,Camellia,SEED,SHA256,SHA512] askpw=any timeout=30}) - diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner6/nssdb/secmod.db b/acceptance-tests/tests/src/test/resources/pki-certs/miner6/nssdb/secmod.db deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner6/nsspin.txt b/acceptance-tests/tests/src/test/resources/pki-certs/miner6/nsspin.txt deleted file mode 100644 index 5271a526801..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/miner6/nsspin.txt +++ /dev/null @@ -1 +0,0 @@ -test123 diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/miner6/truststore.p12 b/acceptance-tests/tests/src/test/resources/pki-certs/miner6/truststore.p12 deleted file mode 100644 index bb56eecd08a..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/miner6/truststore.p12 and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/node1/crl.pem b/acceptance-tests/tests/src/test/resources/pki-certs/node1/crl.pem deleted file mode 100644 index 8ced006c911..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/node1/crl.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN X509 CRL----- -MIICBDCB7QIBATANBgkqhkiG9w0BAQsFADCBgTELMAkGA1UEBhMCVVMxCzAJBgNV -BAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJBgNVBAoMAk1DMQ0wCwYDVQQLDARyb290 -MRMwEQYDVQQDDApwYXJ0bmVyYWNhMSYwJAYJKoZIhvcNAQkBFhdwYXJ0bmVyYWNh -QHBhcnRuZXJhLmNvbRcNMjEwNzA5MTkxMjM1WhcNMjIwNzA5MTkxMjM1WjAnMCUC -FF9rJlU9U6JdFIeK/xRojaoxHdc5Fw0yMTA3MDkxOTEyMzVaoA4wDDAKBgNVHRQE -AwIBADANBgkqhkiG9w0BAQsFAAOCAQEAfByR//FGHSsVQbaS51d59o82XocOGnnT -p1hjceqtLGv3bhiebVrsRCOB5TsvE/r2IbB/yHYTe3+LJisIUqBxblQ6xK6IM+qA -3fY646YnPT5pvdZAPZ2BCN/xP3xqGffFKapQ9cz0/36YE3vaEoUDlC2VHK0OXI0t -4CLwAmiptUT2GW4Bk1RtokAsFiUNwNIOlRX5bywUNwkG7EuitR90QSGH3l/vyii2 -0c1Fm9He9MskwipjXpJKKb+t+m1pdpOVkSjRfjmVqi4BZwWlnQjELSLywhJ+WZG2 -Z1NgRjzPXotFKK+YD97Kx1L260A1eUZ46zSq73oUZr0EDZRnNJTr6A== ------END X509 CRL----- ------BEGIN X509 CRL----- -MIICLDCCARQCAQEwDQYJKoZIhvcNAQELBQAwgYExCzAJBgNVBAYTAlVTMQswCQYD -VQQIDAJDQTEMMAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9v -dDETMBEGA1UEAwwKcGFydG5lcmJjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmJj -YUBwYXJ0bmVyYi5jb20XDTIxMDcwOTE5MTIzNVoXDTIyMDcwOTE5MTIzNVowTjAl -AhQTZhDgbKuvX7iLRUBBTiWXBPKM1xcNMjEwNzA5MTkxMjM1WjAlAhRfayZVPVOi -XRSHiv8UaI2qMR3XORcNMjEwNzA5MTkxMjM1WqAOMAwwCgYDVR0UBAMCAQEwDQYJ -KoZIhvcNAQELBQADggEBAMIltmJ036f1BmK/baISJTZTu7PKZgSZMNORnpFT8KvC -s2GNRor5bGp5qvD6LHvsx92YVppCC6xd/beCFBtdyYifqw5xtOvqLQKuqCfxruLz -EqYjKXE/3v8VdyU71J7kFqi0U0Gy4/h/YCL92e5KNbATlmcn5ToyI2EBIEfBfV08 -mm7FBXvbHRzqhfrnCNEjBWBWz3zkJMc9Rib26eCCofYIDkY2HvYSN78YgrnMmD6O -hWOXrPoxArxvmDr5rG4vCadqbQYRkkCAOP0hBeMiB0SAcO2W2LNNAmHWXX7FvU3n -ZRZUX31WoVjhNeEQtNBb2mPYBXHQzLC66qYm1p97afc= ------END X509 CRL----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/node1/keys.p12 b/acceptance-tests/tests/src/test/resources/pki-certs/node1/keys.p12 deleted file mode 100644 index 1aeb2fec791..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/node1/keys.p12 and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/node1/keystore.jks b/acceptance-tests/tests/src/test/resources/pki-certs/node1/keystore.jks deleted file mode 100644 index 3b1db9eb88b..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/node1/keystore.jks and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/node1/nss.cfg b/acceptance-tests/tests/src/test/resources/pki-certs/node1/nss.cfg deleted file mode 100644 index 320163767f8..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/node1/nss.cfg +++ /dev/null @@ -1,6 +0,0 @@ - -name = NSScrypto-node1 -nssSecmodDirectory = ./src/test/resources/pki-certs/node1/nssdb -nssDbMode = readOnly -nssModule = keystore - diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/node1/nssdb/cert8.db b/acceptance-tests/tests/src/test/resources/pki-certs/node1/nssdb/cert8.db deleted file mode 100644 index a6e5cc1c572..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/node1/nssdb/cert8.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/node1/nssdb/key3.db b/acceptance-tests/tests/src/test/resources/pki-certs/node1/nssdb/key3.db deleted file mode 100644 index 0b103995be1..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/node1/nssdb/key3.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/node1/nssdb/secmod.db b/acceptance-tests/tests/src/test/resources/pki-certs/node1/nssdb/secmod.db deleted file mode 100644 index 08102e23979..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/node1/nssdb/secmod.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/node1/nsspin.txt b/acceptance-tests/tests/src/test/resources/pki-certs/node1/nsspin.txt deleted file mode 100644 index 5271a526801..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/node1/nsspin.txt +++ /dev/null @@ -1 +0,0 @@ -test123 diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/node1/ssl-ca.pem b/acceptance-tests/tests/src/test/resources/pki-certs/node1/ssl-ca.pem deleted file mode 100644 index 61a8f5230ed..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/node1/ssl-ca.pem +++ /dev/null @@ -1,70 +0,0 @@ ------BEGIN CERTIFICATE----- -MIID8DCCAtigAwIBAgIUcATI/N49JsZyAuyS8gfW6BppdBEwDQYJKoZIhvcNAQEL -BQAweDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRAwDgYDVQQDDAdpbnRlcmNhMSAwHgYJ -KoZIhvcNAQkBFhFpbnRlcmNhQGFkbWluLmNvbTAgFw0yMTA3MDkxOTExNTRaGA8y -MTIxMDYxNTE5MTE1NFowgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoG -A1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwK -cGFydG5lcmFjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmFjYUBwYXJ0bmVyYS5j -b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDccGhUDD8T8m6e2x13 -B0LgCDR8F2vlfAw9FN/tXOKKU3DpagJk0CAutL6YFFYs9MXg8D6bZx+w0iXUnaN/ -6rjIC1LFs79AizpEaoFWDzcVI66ItSW1Swu3ts0I1kK7EGgAMba0TDTnssfrtuPw -rOA2TOdVX03owOE+gaaPonIQlnew5+NNGhbW+P/5ix1IQXhyuNjV/uu3VEJBGNTh -cAuMGdjvglFuz4zyFon4kNRcQvghs5ztjs2h5ZNHjEo6YtNIdJuRS/XOXII9MfPN -plcwJArWTM3yEATNBtdc5FypOSI6cO/Pl+SVEf4kKmnzJQZWOSGiDXbpm938hGVw -ByZPAgMBAAGjZjBkMB0GA1UdDgQWBBQhpkZg+Ktqh6sFCjtm9HE+9bLJ3TAfBgNV -HSMEGDAWgBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjASBgNVHRMBAf8ECDAGAQH/AgEA -MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEAIggdc0DCxEiNaKak -/xbc7uGiGBb+UzBTjk4BmX9kEFrZf4XEaHzsptltqcQDCVtzcbFFFxMYhv/w/1g/ -WMNuUhL0mmGIGMxfoqkg50tNyuHW6aSrBXErel9WthGXAhUSzdHgkWxl3pe2wgda -qIcSwTCFgOeVWTZRdKWqKNPHirBglJHUpMgLo0qa0Ug1hxiyOQRt2yXLpe/mH3pG -cozhrEDYQMOcDhYTpPwM2Bl+TsRPpxFaV4QDfiOyvUDFLMM0dyhl2pukjay/Xpp2 -+6Tsnaa+Ui0sDcM3A36Q07+E4Vugxjzfb9a36Eg4XE+Jg3jDA8UOqjeFg9gkK3aY -NaI0qQ== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID6jCCAtKgAwIBAgIUYdazVD+VnI7jBu7xLaW+npfwHJIwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEQMA4GA1UE -AwwHaW50ZXJjYTEgMB4GCSqGSIb3DQEJARYRaW50ZXJjYUBhZG1pbi5jb20wggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ8QgVwEjy3CgJFvET7tYNBw97 -4i33EHQT4ZuayGCJ+ADY3ZFpsw2M1IPlbAguqfBkcLd8TAjWNRAdsm9ubGlIcTZr -7LNle3gvc7qEP4qg0i6M7D06CDqtBaIJ1PMTJchOouGU9ntBe+h0qg8tzpiqJdIw -jIOPRWW98Hw9KgF6++2jtlcOW1IxiFSWqf0Mpc81qKukcxnsHjvdxmBp/Z1vL42E -m5xNOGXoxpjq8NygSuVDhQ/bZUnmHLmvv9MXe9Ob52rlzea/YafLpOeNGSA1aCxm -Fx0lcoXWp4xpoyJn9St7vmH3t7quBKdqt54zwcuHjhgZPSR6RikodKHtsl8TAgMB -AAGjZjBkMB0GA1UdDgQWBBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjAfBgNVHSMEGDAW -gBSMsBNO3UGBteMZOTq5fDPG2aPx8DASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1Ud -DwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEA0hJCtrCI9Mf47+y3pvsTjiaT -TZwpNE0cdxHjpcCHJWX2jmGbqqA6wvQ7yy4PFarmnFOoW3hQFeiLgpz9X86YTRzF -8dj3Q2MKXf6i6/iW+Y96GFqurshKp7wV25wfzWwLXcVCiM1xYPWYSyGsZAGotu4M -c7uolVABjJu5nci9mBxVmaYV5oT1mxrvq3dCPm2AvmVFNWPNRbMSAuT5B7FUDvWG -xvd1aDFduqL0iLAcrTifMIYI3XL4pBSIlL78dgY45WL6616EF3mHhW/Y4k4PNq1I -Fz08Q3y99ilhzeAci1jv6KBVHiALZNFFtYjd10KX95qnF9SomBjCTPsQ9PTC5g== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID2zCCAsOgAwIBAgIUdxRGMrv1ONRI/dJXUHa3isjVXuwwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEVMBMGA1UE -AwwMcm9vdGNhLmFkbWluMR8wHQYJKoZIhvcNAQkBFhByb290Y2FAYWRtaW4uY29t -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Bqlx+ngRZfWjktX6urJ -Tpg2//n3uMW8Gp55SVq+lbpkNXuA3W0uTlom/2fsnzmMmBAJUvv/NaLcFSN++0/d -fYGVhogxjlRoyaXa4sdFPg2ocM+lCHLGhWpYuVdTWPknikTOVNbh9z94SLRfrP8N -kuxGNZNktnVm+QJmrCLmjvnPvQlcP/WqFYehFM26NaRzswOhLLRU1YK8aVlh9Vto -PGSTPtZK60XwTfyCj/zEvHqpbGbKJPp7W0rpxl7ehPQIzGQt5IMH3zKrqLm4/pt+ -XFEVqy724sstthS73bvXguWxBLOzuMze8CbI6SJpcHaH8HO5pfFMPyE5N5l2x9Co -uQIDAQABo1MwUTAdBgNVHQ4EFgQUjLATTt1BgbXjGTk6uXwzxtmj8fAwHwYDVR0j -BBgwFoAUjLATTt1BgbXjGTk6uXwzxtmj8fAwDwYDVR0TAQH/BAUwAwEB/zANBgkq -hkiG9w0BAQsFAAOCAQEAL9/7YtxIbuTt7dgH1KOMb/y6P3Qb79fTVZTx073E2ZBj -OfSuMGUhKC30LWQXCQEsY4WpugJ4cS0NsArSYsO5XWguqgjYlWL6poOmYhGvj7ou -oi44oYqcPm6EbvLrAKdI67bkbSPLzgs+6TrxTyXHzPKFBqGBnuHaFKjgb4hGiPmd -JWKrh3fbTtuLjqSHd0Gey2uBNFql4LhPutdsCUhSOD3BibAeTbXyln70AgpTCoWT -9Qr9Ux6HYIiAHSDBzzuN8EcrlnAm9RO4zRyUVwxDsHoNsOUwybn50IM66p83HuiY -h/iX5uNjaFAADfRR4rQOwb6MLreVH2XYUSiz4M/ijg== ------END CERTIFICATE----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/node1/ssl.pem b/acceptance-tests/tests/src/test/resources/pki-certs/node1/ssl.pem deleted file mode 100644 index 291b730311c..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/node1/ssl.pem +++ /dev/null @@ -1,126 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCw8JqVhfSxCPC+ -8UKvPAGHyZOzqrXd3SyrdYQnUKP2NkAhbXpJMNnIyk41+dVJxzjNJ9RiSr3D8hJH -3NOMLtj93UROtp7Az1PdnpPRjeHsT7kt9+79J3I4r6eEU7uhfig2UNPJBaKxqoht -MyBoGJgIYl3O8SD6grFV40cFfCFOWFgKi+Q+U7I0uMl/y+eXw1/g8DBwGPxhhw9Z -est7Ov3Qnk4x2np8Ejh6hl+dAqubojjLFdWrs2goQXrkE+qLEM2jze49L6gL5FwJ -brMkyewsaF6xVKfptMw9EK92NFzQuCUF8SXq548FzYHkiVVNweheBD7+4iCOpi0b -eiC4N6RXAgMBAAECggEABx01aeuf4fpVC6hRoRANk7MUIDYt58S7xg0MijQg04Zs -36syLn0rVHpl2J+55qR+8Si3zlfo91blS0RisRE1IkfiNjP7gA8V9SQqF1PYEJNI -A8QjNMSAQC031Ac11RFodn6bdUqntvL/VzymAelzO2sREH/7bncQwD5KHbtW73pm -un5CvnP6dHQTHX+AVDuH6yOdukuyNM+2Xn8J+PAIRsPn9yENgmvzWIW8UOvKj2Ox -4ckkT7zkihwyf8BnztNizdxSlR+ebsvP76C2gAULXoYjJlI7XxvQ1/qhma0yXhGx -I/fBBmK5rz84DabblgS0mOc2ul/nlhJI5kQSHr/PQQKBgQDVLnoJUOCa/R+s9zOn -WcUu38C8JYsj1J1PqHEOkoTim8gD3JxxCEuHLLFMSxMp4umGa3oYxut6HmgdQaym -mYPCKr0oWYDhZ8M0zwKFGTGjhGv/gkRQKdYk7IV5ASKEijRHwxfMBL9Qbc0+G0xO -/CQgJ4GsQ8neGfrDavzfsnt79wKBgQDUeqApjX0bQXTRYMyJU926N7vasyW+2kFd -KE2aUxgdEm1LG1ZDaCuPi7YZYH+J9krKISLehUJRCa4rmu6L0JjWHJAZNDQdKi2E -jNfQUWU9hoQHs3J/c8e63MmgUy7K16gt7oelRtlyL2A5NXnqvTv8U0kw7+KJkxda -et0Cke9CoQKBgBo6SJtizu9imwYNt9YsDj1xG+ZJ/C9hViVZxOcVEbJLljUbbEff -zGMKFZocVnQ9zZtTF8d6mbgBi6+lx9cLZhF6bqFLObcBrBuQKHA1pEzI2vypizwN -maIbU2SKxjNmSX5Wu1PHdb4wdCLi+uVw0gT+t055XJmtupNWU1w2OkkLAoGATdFD -SzSYdxz37Z7oCft/Hy4+TJCD6GwCuF2EXlrEYBFZmcI/S5yhJ4dhf9XJn/tl1LVi -EeD4Mrm64hlx+B7DetcnJzPpSi0iUoNKnu7Qh0H2pONhsa/znEo0RdVxM7Tb0Uak -wzE5fgo/0XP2XLRFfdn62kNg0kXerbKFsuVDnUECgYARX+jedlHmj1aY6ldhoRGf -5/RAfYAH2f0NbS4oAiRvchjydN9EjCVp8KoF7m9ToaLqPFgHP8zIjYsk7B49jXZr -DjtyMERpjMsBoRbjSKyD1glm1UQeoavgiorPz0w9QDYBstlhH/d0ejfEBCpGk5/2 -YxMs98KdYA62DUkmuaiySQ== ------END PRIVATE KEY----- ------BEGIN CERTIFICATE----- -MIIEszCCA5ugAwIBAgIUX2smVT1Tol0Uh4r/FGiNqjEd1zgwDQYJKoZIhvcNAQEL -BQAwgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDU0ZPMQsw -CQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwKcGFydG5lcmFjYTEm -MCQGCSqGSIb3DQEJARYXcGFydG5lcmFjYUBwYXJ0bmVyYS5jb20wIBcNMjEwNzA5 -MTkxMjA4WhgPMjEyMTA2MTUxOTEyMDhaMHcxCzAJBgNVBAYTAlVTMQswCQYDVQQI -DAJDQTEMMAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEO -MAwGA1UEAwwFbm9kZTExITAfBgkqhkiG9w0BCQEWEm5vZGUxQHBhcnRuZXJhLmNv -bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALDwmpWF9LEI8L7xQq88 -AYfJk7Oqtd3dLKt1hCdQo/Y2QCFtekkw2cjKTjX51UnHOM0n1GJKvcPyEkfc04wu -2P3dRE62nsDPU92ek9GN4exPuS337v0ncjivp4RTu6F+KDZQ08kForGqiG0zIGgY -mAhiXc7xIPqCsVXjRwV8IU5YWAqL5D5TsjS4yX/L55fDX+DwMHAY/GGHD1l6y3s6 -/dCeTjHaenwSOHqGX50Cq5uiOMsV1auzaChBeuQT6osQzaPN7j0vqAvkXAlusyTJ -7CxoXrFUp+m0zD0Qr3Y0XNC4JQXxJernjwXNgeSJVU3B6F4EPv7iII6mLRt6ILg3 -pFcCAwEAAaOCASgwggEkMBEGCWCGSAGG+EIBAQQEAwIGwDAdBgNVHQ4EFgQUAWkS -lH1KF5emjGJvHhaERb3a7DIwCQYDVR0TBAIwADAOBgNVHQ8BAf8EBAMCBeAwHQYD -VR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMIG1BgNVHSMEga0wgaqAFCGmRmD4 -q2qHqwUKO2b0cT71ssndoXykejB4MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0Ex -DDAKBgNVBAcMA1NGTzELMAkGA1UECgwCTUMxDTALBgNVBAsMBHJvb3QxEDAOBgNV -BAMMB2ludGVyY2ExIDAeBgkqhkiG9w0BCQEWEWludGVyY2FAYWRtaW4uY29tghRw -BMj83j0mxnIC7JLyB9boGml0ETANBgkqhkiG9w0BAQsFAAOCAQEAWq/10HjhRI3A -OOs2JTrG5Xpzz7E8rpH45XQE0ecB1I9Zcm5bnHx+WdIo5GtxvFzGF3EtlAaxDlcM -iE1u1bfrwEF6qr0nQc8q+wHfnNdb1lqqFlXrhoerLBVrChwqkGbxILUQ+mPFhSQd -a2791g8SS/jLM3lnf9bFp7AdzJCqg7Ly0BEkIpfVsuZn0HgfYf7JG68B05RdU7/v -Hw5wGTbEmukODWZtAYRFBnKT4pq5G30heNjATjkmaWeoImJNSPgyoPzYJsYJMUNC -GYj3Ap0ueY6LbsNXst61jUGeBtCcWDa3uRg8Hl7j1dz4USPTJwBEEuSSN3CzjJuI -VGvgw1MPpQ== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID8DCCAtigAwIBAgIUcATI/N49JsZyAuyS8gfW6BppdBEwDQYJKoZIhvcNAQEL -BQAweDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRAwDgYDVQQDDAdpbnRlcmNhMSAwHgYJ -KoZIhvcNAQkBFhFpbnRlcmNhQGFkbWluLmNvbTAgFw0yMTA3MDkxOTExNTRaGA8y -MTIxMDYxNTE5MTE1NFowgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoG -A1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwK -cGFydG5lcmFjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmFjYUBwYXJ0bmVyYS5j -b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDccGhUDD8T8m6e2x13 -B0LgCDR8F2vlfAw9FN/tXOKKU3DpagJk0CAutL6YFFYs9MXg8D6bZx+w0iXUnaN/ -6rjIC1LFs79AizpEaoFWDzcVI66ItSW1Swu3ts0I1kK7EGgAMba0TDTnssfrtuPw -rOA2TOdVX03owOE+gaaPonIQlnew5+NNGhbW+P/5ix1IQXhyuNjV/uu3VEJBGNTh -cAuMGdjvglFuz4zyFon4kNRcQvghs5ztjs2h5ZNHjEo6YtNIdJuRS/XOXII9MfPN -plcwJArWTM3yEATNBtdc5FypOSI6cO/Pl+SVEf4kKmnzJQZWOSGiDXbpm938hGVw -ByZPAgMBAAGjZjBkMB0GA1UdDgQWBBQhpkZg+Ktqh6sFCjtm9HE+9bLJ3TAfBgNV -HSMEGDAWgBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjASBgNVHRMBAf8ECDAGAQH/AgEA -MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEAIggdc0DCxEiNaKak -/xbc7uGiGBb+UzBTjk4BmX9kEFrZf4XEaHzsptltqcQDCVtzcbFFFxMYhv/w/1g/ -WMNuUhL0mmGIGMxfoqkg50tNyuHW6aSrBXErel9WthGXAhUSzdHgkWxl3pe2wgda -qIcSwTCFgOeVWTZRdKWqKNPHirBglJHUpMgLo0qa0Ug1hxiyOQRt2yXLpe/mH3pG -cozhrEDYQMOcDhYTpPwM2Bl+TsRPpxFaV4QDfiOyvUDFLMM0dyhl2pukjay/Xpp2 -+6Tsnaa+Ui0sDcM3A36Q07+E4Vugxjzfb9a36Eg4XE+Jg3jDA8UOqjeFg9gkK3aY -NaI0qQ== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID6jCCAtKgAwIBAgIUYdazVD+VnI7jBu7xLaW+npfwHJIwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEQMA4GA1UE -AwwHaW50ZXJjYTEgMB4GCSqGSIb3DQEJARYRaW50ZXJjYUBhZG1pbi5jb20wggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ8QgVwEjy3CgJFvET7tYNBw97 -4i33EHQT4ZuayGCJ+ADY3ZFpsw2M1IPlbAguqfBkcLd8TAjWNRAdsm9ubGlIcTZr -7LNle3gvc7qEP4qg0i6M7D06CDqtBaIJ1PMTJchOouGU9ntBe+h0qg8tzpiqJdIw -jIOPRWW98Hw9KgF6++2jtlcOW1IxiFSWqf0Mpc81qKukcxnsHjvdxmBp/Z1vL42E -m5xNOGXoxpjq8NygSuVDhQ/bZUnmHLmvv9MXe9Ob52rlzea/YafLpOeNGSA1aCxm -Fx0lcoXWp4xpoyJn9St7vmH3t7quBKdqt54zwcuHjhgZPSR6RikodKHtsl8TAgMB -AAGjZjBkMB0GA1UdDgQWBBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjAfBgNVHSMEGDAW -gBSMsBNO3UGBteMZOTq5fDPG2aPx8DASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1Ud -DwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEA0hJCtrCI9Mf47+y3pvsTjiaT -TZwpNE0cdxHjpcCHJWX2jmGbqqA6wvQ7yy4PFarmnFOoW3hQFeiLgpz9X86YTRzF -8dj3Q2MKXf6i6/iW+Y96GFqurshKp7wV25wfzWwLXcVCiM1xYPWYSyGsZAGotu4M -c7uolVABjJu5nci9mBxVmaYV5oT1mxrvq3dCPm2AvmVFNWPNRbMSAuT5B7FUDvWG -xvd1aDFduqL0iLAcrTifMIYI3XL4pBSIlL78dgY45WL6616EF3mHhW/Y4k4PNq1I -Fz08Q3y99ilhzeAci1jv6KBVHiALZNFFtYjd10KX95qnF9SomBjCTPsQ9PTC5g== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID2zCCAsOgAwIBAgIUdxRGMrv1ONRI/dJXUHa3isjVXuwwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEVMBMGA1UE -AwwMcm9vdGNhLmFkbWluMR8wHQYJKoZIhvcNAQkBFhByb290Y2FAYWRtaW4uY29t -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Bqlx+ngRZfWjktX6urJ -Tpg2//n3uMW8Gp55SVq+lbpkNXuA3W0uTlom/2fsnzmMmBAJUvv/NaLcFSN++0/d -fYGVhogxjlRoyaXa4sdFPg2ocM+lCHLGhWpYuVdTWPknikTOVNbh9z94SLRfrP8N -kuxGNZNktnVm+QJmrCLmjvnPvQlcP/WqFYehFM26NaRzswOhLLRU1YK8aVlh9Vto -PGSTPtZK60XwTfyCj/zEvHqpbGbKJPp7W0rpxl7ehPQIzGQt5IMH3zKrqLm4/pt+ -XFEVqy724sstthS73bvXguWxBLOzuMze8CbI6SJpcHaH8HO5pfFMPyE5N5l2x9Co -uQIDAQABo1MwUTAdBgNVHQ4EFgQUjLATTt1BgbXjGTk6uXwzxtmj8fAwHwYDVR0j -BBgwFoAUjLATTt1BgbXjGTk6uXwzxtmj8fAwDwYDVR0TAQH/BAUwAwEB/zANBgkq -hkiG9w0BAQsFAAOCAQEAL9/7YtxIbuTt7dgH1KOMb/y6P3Qb79fTVZTx073E2ZBj -OfSuMGUhKC30LWQXCQEsY4WpugJ4cS0NsArSYsO5XWguqgjYlWL6poOmYhGvj7ou -oi44oYqcPm6EbvLrAKdI67bkbSPLzgs+6TrxTyXHzPKFBqGBnuHaFKjgb4hGiPmd -JWKrh3fbTtuLjqSHd0Gey2uBNFql4LhPutdsCUhSOD3BibAeTbXyln70AgpTCoWT -9Qr9Ux6HYIiAHSDBzzuN8EcrlnAm9RO4zRyUVwxDsHoNsOUwybn50IM66p83HuiY -h/iX5uNjaFAADfRR4rQOwb6MLreVH2XYUSiz4M/ijg== ------END CERTIFICATE----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/node1/truststore.jks b/acceptance-tests/tests/src/test/resources/pki-certs/node1/truststore.jks deleted file mode 100644 index d707235c76a..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/node1/truststore.jks and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/node2/crl.pem b/acceptance-tests/tests/src/test/resources/pki-certs/node2/crl.pem deleted file mode 100644 index 8ced006c911..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/node2/crl.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN X509 CRL----- -MIICBDCB7QIBATANBgkqhkiG9w0BAQsFADCBgTELMAkGA1UEBhMCVVMxCzAJBgNV -BAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJBgNVBAoMAk1DMQ0wCwYDVQQLDARyb290 -MRMwEQYDVQQDDApwYXJ0bmVyYWNhMSYwJAYJKoZIhvcNAQkBFhdwYXJ0bmVyYWNh -QHBhcnRuZXJhLmNvbRcNMjEwNzA5MTkxMjM1WhcNMjIwNzA5MTkxMjM1WjAnMCUC -FF9rJlU9U6JdFIeK/xRojaoxHdc5Fw0yMTA3MDkxOTEyMzVaoA4wDDAKBgNVHRQE -AwIBADANBgkqhkiG9w0BAQsFAAOCAQEAfByR//FGHSsVQbaS51d59o82XocOGnnT -p1hjceqtLGv3bhiebVrsRCOB5TsvE/r2IbB/yHYTe3+LJisIUqBxblQ6xK6IM+qA -3fY646YnPT5pvdZAPZ2BCN/xP3xqGffFKapQ9cz0/36YE3vaEoUDlC2VHK0OXI0t -4CLwAmiptUT2GW4Bk1RtokAsFiUNwNIOlRX5bywUNwkG7EuitR90QSGH3l/vyii2 -0c1Fm9He9MskwipjXpJKKb+t+m1pdpOVkSjRfjmVqi4BZwWlnQjELSLywhJ+WZG2 -Z1NgRjzPXotFKK+YD97Kx1L260A1eUZ46zSq73oUZr0EDZRnNJTr6A== ------END X509 CRL----- ------BEGIN X509 CRL----- -MIICLDCCARQCAQEwDQYJKoZIhvcNAQELBQAwgYExCzAJBgNVBAYTAlVTMQswCQYD -VQQIDAJDQTEMMAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9v -dDETMBEGA1UEAwwKcGFydG5lcmJjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmJj -YUBwYXJ0bmVyYi5jb20XDTIxMDcwOTE5MTIzNVoXDTIyMDcwOTE5MTIzNVowTjAl -AhQTZhDgbKuvX7iLRUBBTiWXBPKM1xcNMjEwNzA5MTkxMjM1WjAlAhRfayZVPVOi -XRSHiv8UaI2qMR3XORcNMjEwNzA5MTkxMjM1WqAOMAwwCgYDVR0UBAMCAQEwDQYJ -KoZIhvcNAQELBQADggEBAMIltmJ036f1BmK/baISJTZTu7PKZgSZMNORnpFT8KvC -s2GNRor5bGp5qvD6LHvsx92YVppCC6xd/beCFBtdyYifqw5xtOvqLQKuqCfxruLz -EqYjKXE/3v8VdyU71J7kFqi0U0Gy4/h/YCL92e5KNbATlmcn5ToyI2EBIEfBfV08 -mm7FBXvbHRzqhfrnCNEjBWBWz3zkJMc9Rib26eCCofYIDkY2HvYSN78YgrnMmD6O -hWOXrPoxArxvmDr5rG4vCadqbQYRkkCAOP0hBeMiB0SAcO2W2LNNAmHWXX7FvU3n -ZRZUX31WoVjhNeEQtNBb2mPYBXHQzLC66qYm1p97afc= ------END X509 CRL----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/node2/keys.p12 b/acceptance-tests/tests/src/test/resources/pki-certs/node2/keys.p12 deleted file mode 100644 index 5b3aa85caa4..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/node2/keys.p12 and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/node2/keystore.jks b/acceptance-tests/tests/src/test/resources/pki-certs/node2/keystore.jks deleted file mode 100644 index 90b5e93ec75..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/node2/keystore.jks and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/node2/nss.cfg b/acceptance-tests/tests/src/test/resources/pki-certs/node2/nss.cfg deleted file mode 100644 index a0db3bdc995..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/node2/nss.cfg +++ /dev/null @@ -1,6 +0,0 @@ - -name = NSScrypto-node2 -nssSecmodDirectory = ./src/test/resources/pki-certs/node2/nssdb -nssDbMode = readOnly -nssModule = keystore - diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/node2/nssdb/cert8.db b/acceptance-tests/tests/src/test/resources/pki-certs/node2/nssdb/cert8.db deleted file mode 100644 index 10555edf0d7..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/node2/nssdb/cert8.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/node2/nssdb/key3.db b/acceptance-tests/tests/src/test/resources/pki-certs/node2/nssdb/key3.db deleted file mode 100644 index 137e24bdb49..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/node2/nssdb/key3.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/node2/nssdb/secmod.db b/acceptance-tests/tests/src/test/resources/pki-certs/node2/nssdb/secmod.db deleted file mode 100644 index ebbf2f03064..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/node2/nssdb/secmod.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/node2/nsspin.txt b/acceptance-tests/tests/src/test/resources/pki-certs/node2/nsspin.txt deleted file mode 100644 index 5271a526801..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/node2/nsspin.txt +++ /dev/null @@ -1 +0,0 @@ -test123 diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/node2/ssl-ca.pem b/acceptance-tests/tests/src/test/resources/pki-certs/node2/ssl-ca.pem deleted file mode 100644 index c0c4bf478f6..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/node2/ssl-ca.pem +++ /dev/null @@ -1,70 +0,0 @@ ------BEGIN CERTIFICATE----- -MIID8DCCAtigAwIBAgIUcATI/N49JsZyAuyS8gfW6BppdBIwDQYJKoZIhvcNAQEL -BQAweDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRAwDgYDVQQDDAdpbnRlcmNhMSAwHgYJ -KoZIhvcNAQkBFhFpbnRlcmNhQGFkbWluLmNvbTAgFw0yMTA3MDkxOTEyMTFaGA8y -MTIxMDYxNTE5MTIxMVowgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoG -A1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwK -cGFydG5lcmJjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmJjYUBwYXJ0bmVyYi5j -b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNTP8IviC0X2ozlU2+ -9+mPLL9NqJKRCsEiH7XypcXkqkzCYZv/+6nNWXGLr6HsF5qXaM/YVMJYtwiaJquw -qW1CpOKxhHA1gubNt0Fzmt69/5yP6D1zxTPzAcybNMuCy8VcLJpovYHO40+2HD6I -D91/zh3jtrgKPoEXgaW3eX4+hykQWaru3P/G4PrIvPyZXnccoCdBSRCqyZSBJUko -e5ZLnCQMlkwgDONGT3MqQyUC5lqFFMzZ75pOm2reiBe6HzP3H4wK7Ldpxds9ubGk -Acb08+QzcUT7uHvENyvLm5mbf68QurFTwIDEVg2K03dgqQWbJudSzRJAmJYSszFQ -vLZdAgMBAAGjZjBkMB0GA1UdDgQWBBR5aiJ1nA1HHi0XzbRsp4V7T8eKBjAfBgNV -HSMEGDAWgBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjASBgNVHRMBAf8ECDAGAQH/AgEA -MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEAsmFsG3SV1fQg6kO+ -dYvFrDKNOJzTBdVYmFhg4y4H7Qt+dwQcs/ZcOCIaAFUfMUH6CWe1AAdCymfwUHxn -l/FMeJHh/+d1cybC+Wj9wtBbz7nPsWh/PXbbg3Zi8N4+a6Y0NhZAGlNn1UgMHx+E -SkSIXk63FkGT01xM2aYtfr9ABuDdoLK/YPecduiawZoRb02WwIpZFNeEE/MS7XG6 -fX9TdUrNOal8pnWUM79K+rzdlzstpCx7uFFbACUEiiIuYiqG6X+DuypfGmQnjRXB -zbc6NsQONUxg7fnJObdpV7kKTlbpREFoECkTBdqKd7AuJwYRvGaQDWK9P1IYFr1W -Cnm4Cg== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID6jCCAtKgAwIBAgIUYdazVD+VnI7jBu7xLaW+npfwHJIwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEQMA4GA1UE -AwwHaW50ZXJjYTEgMB4GCSqGSIb3DQEJARYRaW50ZXJjYUBhZG1pbi5jb20wggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ8QgVwEjy3CgJFvET7tYNBw97 -4i33EHQT4ZuayGCJ+ADY3ZFpsw2M1IPlbAguqfBkcLd8TAjWNRAdsm9ubGlIcTZr -7LNle3gvc7qEP4qg0i6M7D06CDqtBaIJ1PMTJchOouGU9ntBe+h0qg8tzpiqJdIw -jIOPRWW98Hw9KgF6++2jtlcOW1IxiFSWqf0Mpc81qKukcxnsHjvdxmBp/Z1vL42E -m5xNOGXoxpjq8NygSuVDhQ/bZUnmHLmvv9MXe9Ob52rlzea/YafLpOeNGSA1aCxm -Fx0lcoXWp4xpoyJn9St7vmH3t7quBKdqt54zwcuHjhgZPSR6RikodKHtsl8TAgMB -AAGjZjBkMB0GA1UdDgQWBBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjAfBgNVHSMEGDAW -gBSMsBNO3UGBteMZOTq5fDPG2aPx8DASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1Ud -DwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEA0hJCtrCI9Mf47+y3pvsTjiaT -TZwpNE0cdxHjpcCHJWX2jmGbqqA6wvQ7yy4PFarmnFOoW3hQFeiLgpz9X86YTRzF -8dj3Q2MKXf6i6/iW+Y96GFqurshKp7wV25wfzWwLXcVCiM1xYPWYSyGsZAGotu4M -c7uolVABjJu5nci9mBxVmaYV5oT1mxrvq3dCPm2AvmVFNWPNRbMSAuT5B7FUDvWG -xvd1aDFduqL0iLAcrTifMIYI3XL4pBSIlL78dgY45WL6616EF3mHhW/Y4k4PNq1I -Fz08Q3y99ilhzeAci1jv6KBVHiALZNFFtYjd10KX95qnF9SomBjCTPsQ9PTC5g== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID2zCCAsOgAwIBAgIUdxRGMrv1ONRI/dJXUHa3isjVXuwwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEVMBMGA1UE -AwwMcm9vdGNhLmFkbWluMR8wHQYJKoZIhvcNAQkBFhByb290Y2FAYWRtaW4uY29t -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Bqlx+ngRZfWjktX6urJ -Tpg2//n3uMW8Gp55SVq+lbpkNXuA3W0uTlom/2fsnzmMmBAJUvv/NaLcFSN++0/d -fYGVhogxjlRoyaXa4sdFPg2ocM+lCHLGhWpYuVdTWPknikTOVNbh9z94SLRfrP8N -kuxGNZNktnVm+QJmrCLmjvnPvQlcP/WqFYehFM26NaRzswOhLLRU1YK8aVlh9Vto -PGSTPtZK60XwTfyCj/zEvHqpbGbKJPp7W0rpxl7ehPQIzGQt5IMH3zKrqLm4/pt+ -XFEVqy724sstthS73bvXguWxBLOzuMze8CbI6SJpcHaH8HO5pfFMPyE5N5l2x9Co -uQIDAQABo1MwUTAdBgNVHQ4EFgQUjLATTt1BgbXjGTk6uXwzxtmj8fAwHwYDVR0j -BBgwFoAUjLATTt1BgbXjGTk6uXwzxtmj8fAwDwYDVR0TAQH/BAUwAwEB/zANBgkq -hkiG9w0BAQsFAAOCAQEAL9/7YtxIbuTt7dgH1KOMb/y6P3Qb79fTVZTx073E2ZBj -OfSuMGUhKC30LWQXCQEsY4WpugJ4cS0NsArSYsO5XWguqgjYlWL6poOmYhGvj7ou -oi44oYqcPm6EbvLrAKdI67bkbSPLzgs+6TrxTyXHzPKFBqGBnuHaFKjgb4hGiPmd -JWKrh3fbTtuLjqSHd0Gey2uBNFql4LhPutdsCUhSOD3BibAeTbXyln70AgpTCoWT -9Qr9Ux6HYIiAHSDBzzuN8EcrlnAm9RO4zRyUVwxDsHoNsOUwybn50IM66p83HuiY -h/iX5uNjaFAADfRR4rQOwb6MLreVH2XYUSiz4M/ijg== ------END CERTIFICATE----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/node2/ssl.pem b/acceptance-tests/tests/src/test/resources/pki-certs/node2/ssl.pem deleted file mode 100644 index 42244eb2420..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/node2/ssl.pem +++ /dev/null @@ -1,126 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDOGeuw5pq4E18A -cZOYdLSEbyuSQMmWKtp15Blpb1buD4VEt+6xxBwQvq6BC8A24AWRFGPDM6GFkThr -9vnU64wg6xAjCQZEQYqAncO7FXKLJWagaKYE03rRTKGnNQZsH1uLEywIdEirV8j3 -31zOMgQkk8JS+Kv2tsodOZzbYTIaxvRpeTqLzzv+/eg8RQoNNr9XsqIfYrVDhvBc -tfSpnsxVuR/GAxKiqN001DFa+Yjn0sTrEjlyTbniTW8I5V5dnUOfU8awWdjXFFBL -YaDWHnOOQARlZLyg2AFdSNX6NuYhcUuNBECHFdQtBKkXNZEB6Bqx+ZHNG6AZjwYk -6JY+0kxdAgMBAAECggEAR5qn0Qj9PfC08+GBxtjdEpnRCHnrz3lJ9uVtkrz09bZ8 -tZg1nJsmqQamd/0OJcKFDJeJ5XqdplsCwDfJKpk/UPx52cI75CDlLrQhGXFlH0S6 -OAbDjy+uHuNB0mbQuWXXlyTtCm7kBGt2Te/TPWwRFmgjflIY5Jky9R5cCbfmAXj3 -5ieNPzfXtRiT7qAvLcofC+isVX7OPY6c0iMvJm1cHJ+hDwh56uPVdb5+Sa5+BFEd -cCQ8uTBPkGc1e+nUYtu7b8WnUvzCXOSHTZ+ej93/3WefMBaiTigNdmPZyzkAgO+3 -bX18hv1YBPf6XnDj3kB8MZTVpPGqTJaD96mCSDCQQQKBgQD1Ds0/sy0fJ8XZ2nwn -wJeqM6rctRbmutOA52z6xrRoqOl4ZgYEZ/MngAT0DyfenhaBu2ShD1wvlKE+TEJP -sSwiWwMoNzxqMvIFdjj4NECBNREL5oV3vokz2XGOquyk9OXaOWP7iiZIswHoHhVp -bqwC7Ga9iqVwt8Kj3l7RIf3aMQKBgQDXTdDJPj0MBXWuQp3nH3y5h49I3AKGiefW -H8G834DMA3pU3//VT7Q14qNSkQ7dvWISLG6zkXIWnMtv2w7VMJhSK3dRTYBEw3hs -67mg5DJMHz2xXLShPUM3Fcx12z0JFOLd72da/GXB5BSjYDxLEZ4KO+jZeqFHNC+o -D6GpqOPd7QKBgAiT6g9OwEeU5tIHWc0yapykjliHZjPRWlHqx9/HUM1RDEsQlylU -UiiRzJqWZTjfo74Kxyz0Eq//rd02YiPT+c2CDgw8UrsM6XfTeY8GlTNwnhYijY1x -d+/+roDl7r6fa+RFmgBWB50H1ws+8lTscMzxZW9BCSV26TM/N2ZuoCohAoGAF7Z4 -UlHKg3JufZzUq0nqgLyPaWTh2lOB9zFUMysiklWnuYVaNfIrVOv7NPLJAl8DWCEQ -qDJSsG7gcRddUNmjTRmAcLHqeKhWsRhdP30ilQOz1b7yHfgTWRPLiwd0d3WXbv4Z -EgpOYsZuB3hn65SrJVFn2dwJuz/iJ85g7cCuKBkCgYEA6C6O18FYYqa0sY2qZifs -ryW1ekJJFUhE4rluJCx4J3YvXtzpbbAXqRzfQX+BgSjosTuhKDstYR7J8C5drFpS -oU4PxqxrmoxfAgCca84prbaHRVPA1r1mj7LSHv0jAfGMa3TpKHBbk5wMwO/g1OMi -/tYajeoYLP9p39sNtgsF8HE= ------END PRIVATE KEY----- ------BEGIN CERTIFICATE----- -MIIEszCCA5ugAwIBAgIUE2YQ4Gyrr1+4i0VAQU4llwTyjNYwDQYJKoZIhvcNAQEL -BQAwgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDU0ZPMQsw -CQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwKcGFydG5lcmJjYTEm -MCQGCSqGSIb3DQEJARYXcGFydG5lcmJjYUBwYXJ0bmVyYi5jb20wIBcNMjEwNzA5 -MTkxMjE2WhgPMjEyMTA2MTUxOTEyMTZaMHcxCzAJBgNVBAYTAlVTMQswCQYDVQQI -DAJDQTEMMAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEO -MAwGA1UEAwwFbm9kZTIxITAfBgkqhkiG9w0BCQEWEm5vZGUyQHBhcnRuZXJiLmNv -bTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM4Z67DmmrgTXwBxk5h0 -tIRvK5JAyZYq2nXkGWlvVu4PhUS37rHEHBC+roELwDbgBZEUY8MzoYWROGv2+dTr -jCDrECMJBkRBioCdw7sVcoslZqBopgTTetFMoac1BmwfW4sTLAh0SKtXyPffXM4y -BCSTwlL4q/a2yh05nNthMhrG9Gl5OovPO/796DxFCg02v1eyoh9itUOG8Fy19Kme -zFW5H8YDEqKo3TTUMVr5iOfSxOsSOXJNueJNbwjlXl2dQ59TxrBZ2NcUUEthoNYe -c45ABGVkvKDYAV1I1fo25iFxS40EQIcV1C0EqRc1kQHoGrH5kc0boBmPBiTolj7S -TF0CAwEAAaOCASgwggEkMBEGCWCGSAGG+EIBAQQEAwIGwDAdBgNVHQ4EFgQUC+rE -//mis2iLbyls/S/ja/4/qtgwCQYDVR0TBAIwADAOBgNVHQ8BAf8EBAMCBeAwHQYD -VR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMIG1BgNVHSMEga0wgaqAFHlqInWc -DUceLRfNtGynhXtPx4oGoXykejB4MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0Ex -DDAKBgNVBAcMA1NGTzELMAkGA1UECgwCTUMxDTALBgNVBAsMBHJvb3QxEDAOBgNV -BAMMB2ludGVyY2ExIDAeBgkqhkiG9w0BCQEWEWludGVyY2FAYWRtaW4uY29tghRw -BMj83j0mxnIC7JLyB9boGml0EjANBgkqhkiG9w0BAQsFAAOCAQEApOPnX+voWhWH -cwc2ZOa2+lp8VMysh939AKQWLWvUmTUBcnPtvcN5pgTUzTW5xbsnLBvX4yBpsufa -RqqQps6/pMJudHPiyiE2cswnYDhOH1Lb735YOewqVjD+cvrm1rWiilMyb5WNmxZH -ktuyOlp6hkSIG6788SHJQyyOYcgPlenE43iAk7Mz9ESxXWnolCCawgYlYFI3qF/r -g5fJrR7Ys1+J9gbqBzjXys9cWyQlfg6y4G8Naa3xfrVmH1LhVc0sSzTIneZK1fFh -PwZ+exAhAUnbXCALp8B2Hd9Mehr13fTWzi6vfq5BB0PalB4w0Qg/7v7/W4de6sZw -4t9Yo0Vpyg== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID8DCCAtigAwIBAgIUcATI/N49JsZyAuyS8gfW6BppdBIwDQYJKoZIhvcNAQEL -BQAweDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRAwDgYDVQQDDAdpbnRlcmNhMSAwHgYJ -KoZIhvcNAQkBFhFpbnRlcmNhQGFkbWluLmNvbTAgFw0yMTA3MDkxOTEyMTFaGA8y -MTIxMDYxNTE5MTIxMVowgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoG -A1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwK -cGFydG5lcmJjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmJjYUBwYXJ0bmVyYi5j -b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNTP8IviC0X2ozlU2+ -9+mPLL9NqJKRCsEiH7XypcXkqkzCYZv/+6nNWXGLr6HsF5qXaM/YVMJYtwiaJquw -qW1CpOKxhHA1gubNt0Fzmt69/5yP6D1zxTPzAcybNMuCy8VcLJpovYHO40+2HD6I -D91/zh3jtrgKPoEXgaW3eX4+hykQWaru3P/G4PrIvPyZXnccoCdBSRCqyZSBJUko -e5ZLnCQMlkwgDONGT3MqQyUC5lqFFMzZ75pOm2reiBe6HzP3H4wK7Ldpxds9ubGk -Acb08+QzcUT7uHvENyvLm5mbf68QurFTwIDEVg2K03dgqQWbJudSzRJAmJYSszFQ -vLZdAgMBAAGjZjBkMB0GA1UdDgQWBBR5aiJ1nA1HHi0XzbRsp4V7T8eKBjAfBgNV -HSMEGDAWgBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjASBgNVHRMBAf8ECDAGAQH/AgEA -MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEAsmFsG3SV1fQg6kO+ -dYvFrDKNOJzTBdVYmFhg4y4H7Qt+dwQcs/ZcOCIaAFUfMUH6CWe1AAdCymfwUHxn -l/FMeJHh/+d1cybC+Wj9wtBbz7nPsWh/PXbbg3Zi8N4+a6Y0NhZAGlNn1UgMHx+E -SkSIXk63FkGT01xM2aYtfr9ABuDdoLK/YPecduiawZoRb02WwIpZFNeEE/MS7XG6 -fX9TdUrNOal8pnWUM79K+rzdlzstpCx7uFFbACUEiiIuYiqG6X+DuypfGmQnjRXB -zbc6NsQONUxg7fnJObdpV7kKTlbpREFoECkTBdqKd7AuJwYRvGaQDWK9P1IYFr1W -Cnm4Cg== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID6jCCAtKgAwIBAgIUYdazVD+VnI7jBu7xLaW+npfwHJIwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEQMA4GA1UE -AwwHaW50ZXJjYTEgMB4GCSqGSIb3DQEJARYRaW50ZXJjYUBhZG1pbi5jb20wggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ8QgVwEjy3CgJFvET7tYNBw97 -4i33EHQT4ZuayGCJ+ADY3ZFpsw2M1IPlbAguqfBkcLd8TAjWNRAdsm9ubGlIcTZr -7LNle3gvc7qEP4qg0i6M7D06CDqtBaIJ1PMTJchOouGU9ntBe+h0qg8tzpiqJdIw -jIOPRWW98Hw9KgF6++2jtlcOW1IxiFSWqf0Mpc81qKukcxnsHjvdxmBp/Z1vL42E -m5xNOGXoxpjq8NygSuVDhQ/bZUnmHLmvv9MXe9Ob52rlzea/YafLpOeNGSA1aCxm -Fx0lcoXWp4xpoyJn9St7vmH3t7quBKdqt54zwcuHjhgZPSR6RikodKHtsl8TAgMB -AAGjZjBkMB0GA1UdDgQWBBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjAfBgNVHSMEGDAW -gBSMsBNO3UGBteMZOTq5fDPG2aPx8DASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1Ud -DwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEA0hJCtrCI9Mf47+y3pvsTjiaT -TZwpNE0cdxHjpcCHJWX2jmGbqqA6wvQ7yy4PFarmnFOoW3hQFeiLgpz9X86YTRzF -8dj3Q2MKXf6i6/iW+Y96GFqurshKp7wV25wfzWwLXcVCiM1xYPWYSyGsZAGotu4M -c7uolVABjJu5nci9mBxVmaYV5oT1mxrvq3dCPm2AvmVFNWPNRbMSAuT5B7FUDvWG -xvd1aDFduqL0iLAcrTifMIYI3XL4pBSIlL78dgY45WL6616EF3mHhW/Y4k4PNq1I -Fz08Q3y99ilhzeAci1jv6KBVHiALZNFFtYjd10KX95qnF9SomBjCTPsQ9PTC5g== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID2zCCAsOgAwIBAgIUdxRGMrv1ONRI/dJXUHa3isjVXuwwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEVMBMGA1UE -AwwMcm9vdGNhLmFkbWluMR8wHQYJKoZIhvcNAQkBFhByb290Y2FAYWRtaW4uY29t -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Bqlx+ngRZfWjktX6urJ -Tpg2//n3uMW8Gp55SVq+lbpkNXuA3W0uTlom/2fsnzmMmBAJUvv/NaLcFSN++0/d -fYGVhogxjlRoyaXa4sdFPg2ocM+lCHLGhWpYuVdTWPknikTOVNbh9z94SLRfrP8N -kuxGNZNktnVm+QJmrCLmjvnPvQlcP/WqFYehFM26NaRzswOhLLRU1YK8aVlh9Vto -PGSTPtZK60XwTfyCj/zEvHqpbGbKJPp7W0rpxl7ehPQIzGQt5IMH3zKrqLm4/pt+ -XFEVqy724sstthS73bvXguWxBLOzuMze8CbI6SJpcHaH8HO5pfFMPyE5N5l2x9Co -uQIDAQABo1MwUTAdBgNVHQ4EFgQUjLATTt1BgbXjGTk6uXwzxtmj8fAwHwYDVR0j -BBgwFoAUjLATTt1BgbXjGTk6uXwzxtmj8fAwDwYDVR0TAQH/BAUwAwEB/zANBgkq -hkiG9w0BAQsFAAOCAQEAL9/7YtxIbuTt7dgH1KOMb/y6P3Qb79fTVZTx073E2ZBj -OfSuMGUhKC30LWQXCQEsY4WpugJ4cS0NsArSYsO5XWguqgjYlWL6poOmYhGvj7ou -oi44oYqcPm6EbvLrAKdI67bkbSPLzgs+6TrxTyXHzPKFBqGBnuHaFKjgb4hGiPmd -JWKrh3fbTtuLjqSHd0Gey2uBNFql4LhPutdsCUhSOD3BibAeTbXyln70AgpTCoWT -9Qr9Ux6HYIiAHSDBzzuN8EcrlnAm9RO4zRyUVwxDsHoNsOUwybn50IM66p83HuiY -h/iX5uNjaFAADfRR4rQOwb6MLreVH2XYUSiz4M/ijg== ------END CERTIFICATE----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/node2/truststore.jks b/acceptance-tests/tests/src/test/resources/pki-certs/node2/truststore.jks deleted file mode 100644 index a94ec44bc5a..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/node2/truststore.jks and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/crl.pem b/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/crl.pem deleted file mode 100644 index 8ced006c911..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/crl.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN X509 CRL----- -MIICBDCB7QIBATANBgkqhkiG9w0BAQsFADCBgTELMAkGA1UEBhMCVVMxCzAJBgNV -BAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJBgNVBAoMAk1DMQ0wCwYDVQQLDARyb290 -MRMwEQYDVQQDDApwYXJ0bmVyYWNhMSYwJAYJKoZIhvcNAQkBFhdwYXJ0bmVyYWNh -QHBhcnRuZXJhLmNvbRcNMjEwNzA5MTkxMjM1WhcNMjIwNzA5MTkxMjM1WjAnMCUC -FF9rJlU9U6JdFIeK/xRojaoxHdc5Fw0yMTA3MDkxOTEyMzVaoA4wDDAKBgNVHRQE -AwIBADANBgkqhkiG9w0BAQsFAAOCAQEAfByR//FGHSsVQbaS51d59o82XocOGnnT -p1hjceqtLGv3bhiebVrsRCOB5TsvE/r2IbB/yHYTe3+LJisIUqBxblQ6xK6IM+qA -3fY646YnPT5pvdZAPZ2BCN/xP3xqGffFKapQ9cz0/36YE3vaEoUDlC2VHK0OXI0t -4CLwAmiptUT2GW4Bk1RtokAsFiUNwNIOlRX5bywUNwkG7EuitR90QSGH3l/vyii2 -0c1Fm9He9MskwipjXpJKKb+t+m1pdpOVkSjRfjmVqi4BZwWlnQjELSLywhJ+WZG2 -Z1NgRjzPXotFKK+YD97Kx1L260A1eUZ46zSq73oUZr0EDZRnNJTr6A== ------END X509 CRL----- ------BEGIN X509 CRL----- -MIICLDCCARQCAQEwDQYJKoZIhvcNAQELBQAwgYExCzAJBgNVBAYTAlVTMQswCQYD -VQQIDAJDQTEMMAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9v -dDETMBEGA1UEAwwKcGFydG5lcmJjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmJj -YUBwYXJ0bmVyYi5jb20XDTIxMDcwOTE5MTIzNVoXDTIyMDcwOTE5MTIzNVowTjAl -AhQTZhDgbKuvX7iLRUBBTiWXBPKM1xcNMjEwNzA5MTkxMjM1WjAlAhRfayZVPVOi -XRSHiv8UaI2qMR3XORcNMjEwNzA5MTkxMjM1WqAOMAwwCgYDVR0UBAMCAQEwDQYJ -KoZIhvcNAQELBQADggEBAMIltmJ036f1BmK/baISJTZTu7PKZgSZMNORnpFT8KvC -s2GNRor5bGp5qvD6LHvsx92YVppCC6xd/beCFBtdyYifqw5xtOvqLQKuqCfxruLz -EqYjKXE/3v8VdyU71J7kFqi0U0Gy4/h/YCL92e5KNbATlmcn5ToyI2EBIEfBfV08 -mm7FBXvbHRzqhfrnCNEjBWBWz3zkJMc9Rib26eCCofYIDkY2HvYSN78YgrnMmD6O -hWOXrPoxArxvmDr5rG4vCadqbQYRkkCAOP0hBeMiB0SAcO2W2LNNAmHWXX7FvU3n -ZRZUX31WoVjhNeEQtNBb2mPYBXHQzLC66qYm1p97afc= ------END X509 CRL----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/keys.p12 b/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/keys.p12 deleted file mode 100644 index 30115f1a0db..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/keys.p12 and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/keystore.jks b/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/keystore.jks deleted file mode 100644 index 310fbb591ca..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/keystore.jks and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/nss.cfg b/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/nss.cfg deleted file mode 100644 index 3615d46bd88..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/nss.cfg +++ /dev/null @@ -1,6 +0,0 @@ - -name = NSScrypto-non-validator -nssSecmodDirectory = ./src/test/resources/pki-certs/non-validator/nssdb -nssDbMode = readOnly -nssModule = keystore - diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/nssdb/cert8.db b/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/nssdb/cert8.db deleted file mode 100644 index 13f76b09f90..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/nssdb/cert8.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/nssdb/key3.db b/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/nssdb/key3.db deleted file mode 100644 index 9ae401fba81..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/nssdb/key3.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/nssdb/secmod.db b/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/nssdb/secmod.db deleted file mode 100644 index 1320aa6f1c3..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/nssdb/secmod.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/nsspin.txt b/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/nsspin.txt deleted file mode 100644 index 5271a526801..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/nsspin.txt +++ /dev/null @@ -1 +0,0 @@ -test123 diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/ssl-ca.pem b/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/ssl-ca.pem deleted file mode 100644 index 61a8f5230ed..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/ssl-ca.pem +++ /dev/null @@ -1,70 +0,0 @@ ------BEGIN CERTIFICATE----- -MIID8DCCAtigAwIBAgIUcATI/N49JsZyAuyS8gfW6BppdBEwDQYJKoZIhvcNAQEL -BQAweDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRAwDgYDVQQDDAdpbnRlcmNhMSAwHgYJ -KoZIhvcNAQkBFhFpbnRlcmNhQGFkbWluLmNvbTAgFw0yMTA3MDkxOTExNTRaGA8y -MTIxMDYxNTE5MTE1NFowgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoG -A1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwK -cGFydG5lcmFjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmFjYUBwYXJ0bmVyYS5j -b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDccGhUDD8T8m6e2x13 -B0LgCDR8F2vlfAw9FN/tXOKKU3DpagJk0CAutL6YFFYs9MXg8D6bZx+w0iXUnaN/ -6rjIC1LFs79AizpEaoFWDzcVI66ItSW1Swu3ts0I1kK7EGgAMba0TDTnssfrtuPw -rOA2TOdVX03owOE+gaaPonIQlnew5+NNGhbW+P/5ix1IQXhyuNjV/uu3VEJBGNTh -cAuMGdjvglFuz4zyFon4kNRcQvghs5ztjs2h5ZNHjEo6YtNIdJuRS/XOXII9MfPN -plcwJArWTM3yEATNBtdc5FypOSI6cO/Pl+SVEf4kKmnzJQZWOSGiDXbpm938hGVw -ByZPAgMBAAGjZjBkMB0GA1UdDgQWBBQhpkZg+Ktqh6sFCjtm9HE+9bLJ3TAfBgNV -HSMEGDAWgBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjASBgNVHRMBAf8ECDAGAQH/AgEA -MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEAIggdc0DCxEiNaKak -/xbc7uGiGBb+UzBTjk4BmX9kEFrZf4XEaHzsptltqcQDCVtzcbFFFxMYhv/w/1g/ -WMNuUhL0mmGIGMxfoqkg50tNyuHW6aSrBXErel9WthGXAhUSzdHgkWxl3pe2wgda -qIcSwTCFgOeVWTZRdKWqKNPHirBglJHUpMgLo0qa0Ug1hxiyOQRt2yXLpe/mH3pG -cozhrEDYQMOcDhYTpPwM2Bl+TsRPpxFaV4QDfiOyvUDFLMM0dyhl2pukjay/Xpp2 -+6Tsnaa+Ui0sDcM3A36Q07+E4Vugxjzfb9a36Eg4XE+Jg3jDA8UOqjeFg9gkK3aY -NaI0qQ== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID6jCCAtKgAwIBAgIUYdazVD+VnI7jBu7xLaW+npfwHJIwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEQMA4GA1UE -AwwHaW50ZXJjYTEgMB4GCSqGSIb3DQEJARYRaW50ZXJjYUBhZG1pbi5jb20wggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ8QgVwEjy3CgJFvET7tYNBw97 -4i33EHQT4ZuayGCJ+ADY3ZFpsw2M1IPlbAguqfBkcLd8TAjWNRAdsm9ubGlIcTZr -7LNle3gvc7qEP4qg0i6M7D06CDqtBaIJ1PMTJchOouGU9ntBe+h0qg8tzpiqJdIw -jIOPRWW98Hw9KgF6++2jtlcOW1IxiFSWqf0Mpc81qKukcxnsHjvdxmBp/Z1vL42E -m5xNOGXoxpjq8NygSuVDhQ/bZUnmHLmvv9MXe9Ob52rlzea/YafLpOeNGSA1aCxm -Fx0lcoXWp4xpoyJn9St7vmH3t7quBKdqt54zwcuHjhgZPSR6RikodKHtsl8TAgMB -AAGjZjBkMB0GA1UdDgQWBBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjAfBgNVHSMEGDAW -gBSMsBNO3UGBteMZOTq5fDPG2aPx8DASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1Ud -DwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEA0hJCtrCI9Mf47+y3pvsTjiaT -TZwpNE0cdxHjpcCHJWX2jmGbqqA6wvQ7yy4PFarmnFOoW3hQFeiLgpz9X86YTRzF -8dj3Q2MKXf6i6/iW+Y96GFqurshKp7wV25wfzWwLXcVCiM1xYPWYSyGsZAGotu4M -c7uolVABjJu5nci9mBxVmaYV5oT1mxrvq3dCPm2AvmVFNWPNRbMSAuT5B7FUDvWG -xvd1aDFduqL0iLAcrTifMIYI3XL4pBSIlL78dgY45WL6616EF3mHhW/Y4k4PNq1I -Fz08Q3y99ilhzeAci1jv6KBVHiALZNFFtYjd10KX95qnF9SomBjCTPsQ9PTC5g== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID2zCCAsOgAwIBAgIUdxRGMrv1ONRI/dJXUHa3isjVXuwwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEVMBMGA1UE -AwwMcm9vdGNhLmFkbWluMR8wHQYJKoZIhvcNAQkBFhByb290Y2FAYWRtaW4uY29t -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Bqlx+ngRZfWjktX6urJ -Tpg2//n3uMW8Gp55SVq+lbpkNXuA3W0uTlom/2fsnzmMmBAJUvv/NaLcFSN++0/d -fYGVhogxjlRoyaXa4sdFPg2ocM+lCHLGhWpYuVdTWPknikTOVNbh9z94SLRfrP8N -kuxGNZNktnVm+QJmrCLmjvnPvQlcP/WqFYehFM26NaRzswOhLLRU1YK8aVlh9Vto -PGSTPtZK60XwTfyCj/zEvHqpbGbKJPp7W0rpxl7ehPQIzGQt5IMH3zKrqLm4/pt+ -XFEVqy724sstthS73bvXguWxBLOzuMze8CbI6SJpcHaH8HO5pfFMPyE5N5l2x9Co -uQIDAQABo1MwUTAdBgNVHQ4EFgQUjLATTt1BgbXjGTk6uXwzxtmj8fAwHwYDVR0j -BBgwFoAUjLATTt1BgbXjGTk6uXwzxtmj8fAwDwYDVR0TAQH/BAUwAwEB/zANBgkq -hkiG9w0BAQsFAAOCAQEAL9/7YtxIbuTt7dgH1KOMb/y6P3Qb79fTVZTx073E2ZBj -OfSuMGUhKC30LWQXCQEsY4WpugJ4cS0NsArSYsO5XWguqgjYlWL6poOmYhGvj7ou -oi44oYqcPm6EbvLrAKdI67bkbSPLzgs+6TrxTyXHzPKFBqGBnuHaFKjgb4hGiPmd -JWKrh3fbTtuLjqSHd0Gey2uBNFql4LhPutdsCUhSOD3BibAeTbXyln70AgpTCoWT -9Qr9Ux6HYIiAHSDBzzuN8EcrlnAm9RO4zRyUVwxDsHoNsOUwybn50IM66p83HuiY -h/iX5uNjaFAADfRR4rQOwb6MLreVH2XYUSiz4M/ijg== ------END CERTIFICATE----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/ssl.pem b/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/ssl.pem deleted file mode 100644 index c885996bea1..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/ssl.pem +++ /dev/null @@ -1,126 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDE53iE0LzazjEw -rPyUsnsn6GAwRmL8YdEQsD2SyUN59DnEV2gNuwmI446xv+eimdOcxaQTG4zjlIzN -RHpjYeV9JVrMfUV0T39/rbBVtv4mwsa2rTaC9qUyBzX7NjzYr+fEJ4G0TYlVaiGz -We8KyIRn47zlkmYPx42Hip75pE/7EcP87G0b38i6phdYkvsyAq6eIDbgTYa/otMx -z+mSejtcWgFxkShib6jXa5ycDJdxaAUlzqqUoYprvbiixgr/ga1gZs6lhB+sBO1P -hKjHFNnLNogFz+xDF8JRcYwqHPp4dCr150+mZasZcKxGRxB+a9/Ccjz2ZMlGRjGE -Vu6qwK0DAgMBAAECggEBALuBlaIcyQsweWVaBjlv8ZmiTx8JmC+w0o8q1afXozsa -WHxhpAQXTdAeqv8XuXR9Oie7ijBr9vGtv5ulj0reqdRP4zSCQsatAcRLWXV2inXM -28CQGC4ExHFsrnooPy816+Y+nORfRd2h8K/Df8BsDLKGC5cun6R9KoideCBQ2Z1C -tJYVrVC4/pPzAQAJIpE+xH6UwsrUnnBo31HdzaIl7Nh/4S/4F0RVSyrqKjZfEE4p -7Xt1ZpcQOQUo32YLOPjPwJuWGwVDM9jwaqI4fR3ckpaicMb8ihc0zlLjxQk2Cj8X -tockv6X4RdnviHp56Z9Lzfr+A/u9/dlT5Q8Sqz10K/ECgYEA6/ss5kbEEy4QjOlz -6lgJJhdTD5Tb1Zbbwu5unk8vVQrIbFCFc8o29qSQ5NCoRQ0b4BAPTlL5UsUMfwi2 -+g3iXmZScuNpR8ai9cfImEkZcxezknKBnJxLZ0nAym5eGCoF/da2dWGZ9NL/3kN2 -6e4Tz5++xp12/oQ58MoYd+eZFJkCgYEA1ZuoPPdy3NA/dgjoaJ6r0HYgNW18FMq/ -Lj7fGXT4IluCRndXhW/FBcNfqpzXcOBwRsf0PiiMuLW+LDJnT9XLr58/IqpbjS+s -I/oAtaTwkwBtcmSAR/L33O/IpZ9lh2PD0I+5JDlvAQp8ESoSS2ceD1xxyz4nyIVn -xTaRVKdnM/sCgYEAmzlUVoD0Fv21C0vA4MRZ+EFddKFhXYTnN7ko8F977f+yyHTK -49h/AQxM9wY9uXYfAOrvdoDj2cseJi2wVpltbtT1pPbr2LfTl8J7s5hZ8jgD+e/y -S/cPLnj4JPAmWtTBrjBfQqUZ5U4p2qUcAlKFm4wjrHgFvvNxPW9iWvTv3/kCgYEA -oChvgha+hpWVn3iaWSbvCAQpriWC7Uv/ADufp0Sub4RRL2h3tNLS1LliV1f1qOp/ -BHMTzC83I58REa5RC+jvKaSq/+DfeAKQfQEL2yXqQy4E4co87LVd5sfal2PmChdh -UFUSqfhuJEeqMmxq/yI3xai4oXze81gjMBjz6pcBqFUCgYEArVZbnwTSueEwSSGn -Ba9FBjwj44hrD+khAGODcpyBg0sC22hYuSJiUgpiRHb77XGNLK/Rn2i3ZUyuww03 -HfYgeXFO6DeuSy8jU7nP0FcLFmvZq7JqzFS6T46z7zepTSnIK5PSVuHAPy7nZTqC -BAHouXhT1BsH1rCY7vVH7LVXQhs= ------END PRIVATE KEY----- ------BEGIN CERTIFICATE----- -MIIExDCCA6ygAwIBAgIUX2smVT1Tol0Uh4r/FGiNqjEd1zYwDQYJKoZIhvcNAQEL -BQAwgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDU0ZPMQsw -CQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwKcGFydG5lcmFjYTEm -MCQGCSqGSIb3DQEJARYXcGFydG5lcmFjYUBwYXJ0bmVyYS5jb20wIBcNMjEwNzA5 -MTkxMjAzWhgPMjEyMTA2MTUxOTEyMDNaMIGHMQswCQYDVQQGEwJVUzELMAkGA1UE -CAwCQ0ExDDAKBgNVBAcMA1NGTzELMAkGA1UECgwCTUMxDTALBgNVBAsMBHJvb3Qx -FjAUBgNVBAMMDW5vbi12YWxpZGF0b3IxKTAnBgkqhkiG9w0BCQEWGm5vbi12YWxp -ZGF0b3JAcGFydG5lcmEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEAxOd4hNC82s4xMKz8lLJ7J+hgMEZi/GHRELA9kslDefQ5xFdoDbsJiOOOsb/n -opnTnMWkExuM45SMzUR6Y2HlfSVazH1FdE9/f62wVbb+JsLGtq02gvalMgc1+zY8 -2K/nxCeBtE2JVWohs1nvCsiEZ+O85ZJmD8eNh4qe+aRP+xHD/OxtG9/IuqYXWJL7 -MgKuniA24E2Gv6LTMc/pkno7XFoBcZEoYm+o12ucnAyXcWgFJc6qlKGKa724osYK -/4GtYGbOpYQfrATtT4SoxxTZyzaIBc/sQxfCUXGMKhz6eHQq9edPpmWrGXCsRkcQ -fmvfwnI89mTJRkYxhFbuqsCtAwIDAQABo4IBKDCCASQwEQYJYIZIAYb4QgEBBAQD -AgbAMB0GA1UdDgQWBBRXMoam1AQo0jGVQxCRxbHlU18FPTAJBgNVHRMEAjAAMA4G -A1UdDwEB/wQEAwIF4DAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwgbUG -A1UdIwSBrTCBqoAUIaZGYPiraoerBQo7ZvRxPvWyyd2hfKR6MHgxCzAJBgNVBAYT -AlVTMQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsG -A1UECwwEcm9vdDEQMA4GA1UEAwwHaW50ZXJjYTEgMB4GCSqGSIb3DQEJARYRaW50 -ZXJjYUBhZG1pbi5jb22CFHAEyPzePSbGcgLskvIH1ugaaXQRMA0GCSqGSIb3DQEB -CwUAA4IBAQCdXem4j/D85ME7kOU6G4DpMOfFsGJEQ20oovKpEoqm0wKaEp61TGci -q9UjEQI0Istxb0x7xykqP5UAlGXk/xDXFLcz+pe2M/vKo/4sJBEvxV+yr33Cgwli -PL3XgiRqaDEXVcRXa2RcwW+HVzrBOvQz8IYdFeeqt7y+sCqw3oMkaAPKDv9H+gcL -5EI+3r9WRnQmy8VWF8FuGWZG1daDnlFC0mm/PvOctVP+YROB7mbwfs+68xtqadWe -pHdIG+rX7Bs4hZ5mXNu4Hwl+iCPRRj1uyfNkwfx3BsWnR142RksFgZjlTLaGCFCk -Qf3PEFIHpi+kiPzI5Qm8hUVLNWcJiHhO ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID8DCCAtigAwIBAgIUcATI/N49JsZyAuyS8gfW6BppdBEwDQYJKoZIhvcNAQEL -BQAweDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRAwDgYDVQQDDAdpbnRlcmNhMSAwHgYJ -KoZIhvcNAQkBFhFpbnRlcmNhQGFkbWluLmNvbTAgFw0yMTA3MDkxOTExNTRaGA8y -MTIxMDYxNTE5MTE1NFowgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoG -A1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwK -cGFydG5lcmFjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmFjYUBwYXJ0bmVyYS5j -b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDccGhUDD8T8m6e2x13 -B0LgCDR8F2vlfAw9FN/tXOKKU3DpagJk0CAutL6YFFYs9MXg8D6bZx+w0iXUnaN/ -6rjIC1LFs79AizpEaoFWDzcVI66ItSW1Swu3ts0I1kK7EGgAMba0TDTnssfrtuPw -rOA2TOdVX03owOE+gaaPonIQlnew5+NNGhbW+P/5ix1IQXhyuNjV/uu3VEJBGNTh -cAuMGdjvglFuz4zyFon4kNRcQvghs5ztjs2h5ZNHjEo6YtNIdJuRS/XOXII9MfPN -plcwJArWTM3yEATNBtdc5FypOSI6cO/Pl+SVEf4kKmnzJQZWOSGiDXbpm938hGVw -ByZPAgMBAAGjZjBkMB0GA1UdDgQWBBQhpkZg+Ktqh6sFCjtm9HE+9bLJ3TAfBgNV -HSMEGDAWgBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjASBgNVHRMBAf8ECDAGAQH/AgEA -MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEAIggdc0DCxEiNaKak -/xbc7uGiGBb+UzBTjk4BmX9kEFrZf4XEaHzsptltqcQDCVtzcbFFFxMYhv/w/1g/ -WMNuUhL0mmGIGMxfoqkg50tNyuHW6aSrBXErel9WthGXAhUSzdHgkWxl3pe2wgda -qIcSwTCFgOeVWTZRdKWqKNPHirBglJHUpMgLo0qa0Ug1hxiyOQRt2yXLpe/mH3pG -cozhrEDYQMOcDhYTpPwM2Bl+TsRPpxFaV4QDfiOyvUDFLMM0dyhl2pukjay/Xpp2 -+6Tsnaa+Ui0sDcM3A36Q07+E4Vugxjzfb9a36Eg4XE+Jg3jDA8UOqjeFg9gkK3aY -NaI0qQ== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID6jCCAtKgAwIBAgIUYdazVD+VnI7jBu7xLaW+npfwHJIwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEQMA4GA1UE -AwwHaW50ZXJjYTEgMB4GCSqGSIb3DQEJARYRaW50ZXJjYUBhZG1pbi5jb20wggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ8QgVwEjy3CgJFvET7tYNBw97 -4i33EHQT4ZuayGCJ+ADY3ZFpsw2M1IPlbAguqfBkcLd8TAjWNRAdsm9ubGlIcTZr -7LNle3gvc7qEP4qg0i6M7D06CDqtBaIJ1PMTJchOouGU9ntBe+h0qg8tzpiqJdIw -jIOPRWW98Hw9KgF6++2jtlcOW1IxiFSWqf0Mpc81qKukcxnsHjvdxmBp/Z1vL42E -m5xNOGXoxpjq8NygSuVDhQ/bZUnmHLmvv9MXe9Ob52rlzea/YafLpOeNGSA1aCxm -Fx0lcoXWp4xpoyJn9St7vmH3t7quBKdqt54zwcuHjhgZPSR6RikodKHtsl8TAgMB -AAGjZjBkMB0GA1UdDgQWBBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjAfBgNVHSMEGDAW -gBSMsBNO3UGBteMZOTq5fDPG2aPx8DASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1Ud -DwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEA0hJCtrCI9Mf47+y3pvsTjiaT -TZwpNE0cdxHjpcCHJWX2jmGbqqA6wvQ7yy4PFarmnFOoW3hQFeiLgpz9X86YTRzF -8dj3Q2MKXf6i6/iW+Y96GFqurshKp7wV25wfzWwLXcVCiM1xYPWYSyGsZAGotu4M -c7uolVABjJu5nci9mBxVmaYV5oT1mxrvq3dCPm2AvmVFNWPNRbMSAuT5B7FUDvWG -xvd1aDFduqL0iLAcrTifMIYI3XL4pBSIlL78dgY45WL6616EF3mHhW/Y4k4PNq1I -Fz08Q3y99ilhzeAci1jv6KBVHiALZNFFtYjd10KX95qnF9SomBjCTPsQ9PTC5g== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID2zCCAsOgAwIBAgIUdxRGMrv1ONRI/dJXUHa3isjVXuwwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEVMBMGA1UE -AwwMcm9vdGNhLmFkbWluMR8wHQYJKoZIhvcNAQkBFhByb290Y2FAYWRtaW4uY29t -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Bqlx+ngRZfWjktX6urJ -Tpg2//n3uMW8Gp55SVq+lbpkNXuA3W0uTlom/2fsnzmMmBAJUvv/NaLcFSN++0/d -fYGVhogxjlRoyaXa4sdFPg2ocM+lCHLGhWpYuVdTWPknikTOVNbh9z94SLRfrP8N -kuxGNZNktnVm+QJmrCLmjvnPvQlcP/WqFYehFM26NaRzswOhLLRU1YK8aVlh9Vto -PGSTPtZK60XwTfyCj/zEvHqpbGbKJPp7W0rpxl7ehPQIzGQt5IMH3zKrqLm4/pt+ -XFEVqy724sstthS73bvXguWxBLOzuMze8CbI6SJpcHaH8HO5pfFMPyE5N5l2x9Co -uQIDAQABo1MwUTAdBgNVHQ4EFgQUjLATTt1BgbXjGTk6uXwzxtmj8fAwHwYDVR0j -BBgwFoAUjLATTt1BgbXjGTk6uXwzxtmj8fAwDwYDVR0TAQH/BAUwAwEB/zANBgkq -hkiG9w0BAQsFAAOCAQEAL9/7YtxIbuTt7dgH1KOMb/y6P3Qb79fTVZTx073E2ZBj -OfSuMGUhKC30LWQXCQEsY4WpugJ4cS0NsArSYsO5XWguqgjYlWL6poOmYhGvj7ou -oi44oYqcPm6EbvLrAKdI67bkbSPLzgs+6TrxTyXHzPKFBqGBnuHaFKjgb4hGiPmd -JWKrh3fbTtuLjqSHd0Gey2uBNFql4LhPutdsCUhSOD3BibAeTbXyln70AgpTCoWT -9Qr9Ux6HYIiAHSDBzzuN8EcrlnAm9RO4zRyUVwxDsHoNsOUwybn50IM66p83HuiY -h/iX5uNjaFAADfRR4rQOwb6MLreVH2XYUSiz4M/ijg== ------END CERTIFICATE----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/truststore.jks b/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/truststore.jks deleted file mode 100644 index bac9b25c751..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/non-validator/truststore.jks and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/crl.pem b/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/crl.pem deleted file mode 100644 index 8ced006c911..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/crl.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN X509 CRL----- -MIICBDCB7QIBATANBgkqhkiG9w0BAQsFADCBgTELMAkGA1UEBhMCVVMxCzAJBgNV -BAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJBgNVBAoMAk1DMQ0wCwYDVQQLDARyb290 -MRMwEQYDVQQDDApwYXJ0bmVyYWNhMSYwJAYJKoZIhvcNAQkBFhdwYXJ0bmVyYWNh -QHBhcnRuZXJhLmNvbRcNMjEwNzA5MTkxMjM1WhcNMjIwNzA5MTkxMjM1WjAnMCUC -FF9rJlU9U6JdFIeK/xRojaoxHdc5Fw0yMTA3MDkxOTEyMzVaoA4wDDAKBgNVHRQE -AwIBADANBgkqhkiG9w0BAQsFAAOCAQEAfByR//FGHSsVQbaS51d59o82XocOGnnT -p1hjceqtLGv3bhiebVrsRCOB5TsvE/r2IbB/yHYTe3+LJisIUqBxblQ6xK6IM+qA -3fY646YnPT5pvdZAPZ2BCN/xP3xqGffFKapQ9cz0/36YE3vaEoUDlC2VHK0OXI0t -4CLwAmiptUT2GW4Bk1RtokAsFiUNwNIOlRX5bywUNwkG7EuitR90QSGH3l/vyii2 -0c1Fm9He9MskwipjXpJKKb+t+m1pdpOVkSjRfjmVqi4BZwWlnQjELSLywhJ+WZG2 -Z1NgRjzPXotFKK+YD97Kx1L260A1eUZ46zSq73oUZr0EDZRnNJTr6A== ------END X509 CRL----- ------BEGIN X509 CRL----- -MIICLDCCARQCAQEwDQYJKoZIhvcNAQELBQAwgYExCzAJBgNVBAYTAlVTMQswCQYD -VQQIDAJDQTEMMAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9v -dDETMBEGA1UEAwwKcGFydG5lcmJjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmJj -YUBwYXJ0bmVyYi5jb20XDTIxMDcwOTE5MTIzNVoXDTIyMDcwOTE5MTIzNVowTjAl -AhQTZhDgbKuvX7iLRUBBTiWXBPKM1xcNMjEwNzA5MTkxMjM1WjAlAhRfayZVPVOi -XRSHiv8UaI2qMR3XORcNMjEwNzA5MTkxMjM1WqAOMAwwCgYDVR0UBAMCAQEwDQYJ -KoZIhvcNAQELBQADggEBAMIltmJ036f1BmK/baISJTZTu7PKZgSZMNORnpFT8KvC -s2GNRor5bGp5qvD6LHvsx92YVppCC6xd/beCFBtdyYifqw5xtOvqLQKuqCfxruLz -EqYjKXE/3v8VdyU71J7kFqi0U0Gy4/h/YCL92e5KNbATlmcn5ToyI2EBIEfBfV08 -mm7FBXvbHRzqhfrnCNEjBWBWz3zkJMc9Rib26eCCofYIDkY2HvYSN78YgrnMmD6O -hWOXrPoxArxvmDr5rG4vCadqbQYRkkCAOP0hBeMiB0SAcO2W2LNNAmHWXX7FvU3n -ZRZUX31WoVjhNeEQtNBb2mPYBXHQzLC66qYm1p97afc= ------END X509 CRL----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/keys.p12 b/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/keys.p12 deleted file mode 100644 index 0f5879ff1c0..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/keys.p12 and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/keystore.jks b/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/keystore.jks deleted file mode 100644 index 1adfb524170..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/keystore.jks and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/nss.cfg b/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/nss.cfg deleted file mode 100644 index afe2f17e0f2..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/nss.cfg +++ /dev/null @@ -1,6 +0,0 @@ - -name = NSScrypto-nonValidator -nssSecmodDirectory = ./src/test/resources/pki-certs/nonValidator/nssdb -nssDbMode = readOnly -nssModule = keystore - diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/nssdb/cert8.db b/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/nssdb/cert8.db deleted file mode 100644 index 3a477ba52eb..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/nssdb/cert8.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/nssdb/key3.db b/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/nssdb/key3.db deleted file mode 100644 index b6d6179cd2c..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/nssdb/key3.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/nssdb/secmod.db b/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/nssdb/secmod.db deleted file mode 100644 index 1d70f9b3a97..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/nssdb/secmod.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/nsspin.txt b/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/nsspin.txt deleted file mode 100644 index 5271a526801..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/nsspin.txt +++ /dev/null @@ -1 +0,0 @@ -test123 diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/ssl-ca.pem b/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/ssl-ca.pem deleted file mode 100644 index 61a8f5230ed..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/ssl-ca.pem +++ /dev/null @@ -1,70 +0,0 @@ ------BEGIN CERTIFICATE----- -MIID8DCCAtigAwIBAgIUcATI/N49JsZyAuyS8gfW6BppdBEwDQYJKoZIhvcNAQEL -BQAweDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRAwDgYDVQQDDAdpbnRlcmNhMSAwHgYJ -KoZIhvcNAQkBFhFpbnRlcmNhQGFkbWluLmNvbTAgFw0yMTA3MDkxOTExNTRaGA8y -MTIxMDYxNTE5MTE1NFowgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoG -A1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwK -cGFydG5lcmFjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmFjYUBwYXJ0bmVyYS5j -b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDccGhUDD8T8m6e2x13 -B0LgCDR8F2vlfAw9FN/tXOKKU3DpagJk0CAutL6YFFYs9MXg8D6bZx+w0iXUnaN/ -6rjIC1LFs79AizpEaoFWDzcVI66ItSW1Swu3ts0I1kK7EGgAMba0TDTnssfrtuPw -rOA2TOdVX03owOE+gaaPonIQlnew5+NNGhbW+P/5ix1IQXhyuNjV/uu3VEJBGNTh -cAuMGdjvglFuz4zyFon4kNRcQvghs5ztjs2h5ZNHjEo6YtNIdJuRS/XOXII9MfPN -plcwJArWTM3yEATNBtdc5FypOSI6cO/Pl+SVEf4kKmnzJQZWOSGiDXbpm938hGVw -ByZPAgMBAAGjZjBkMB0GA1UdDgQWBBQhpkZg+Ktqh6sFCjtm9HE+9bLJ3TAfBgNV -HSMEGDAWgBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjASBgNVHRMBAf8ECDAGAQH/AgEA -MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEAIggdc0DCxEiNaKak -/xbc7uGiGBb+UzBTjk4BmX9kEFrZf4XEaHzsptltqcQDCVtzcbFFFxMYhv/w/1g/ -WMNuUhL0mmGIGMxfoqkg50tNyuHW6aSrBXErel9WthGXAhUSzdHgkWxl3pe2wgda -qIcSwTCFgOeVWTZRdKWqKNPHirBglJHUpMgLo0qa0Ug1hxiyOQRt2yXLpe/mH3pG -cozhrEDYQMOcDhYTpPwM2Bl+TsRPpxFaV4QDfiOyvUDFLMM0dyhl2pukjay/Xpp2 -+6Tsnaa+Ui0sDcM3A36Q07+E4Vugxjzfb9a36Eg4XE+Jg3jDA8UOqjeFg9gkK3aY -NaI0qQ== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID6jCCAtKgAwIBAgIUYdazVD+VnI7jBu7xLaW+npfwHJIwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEQMA4GA1UE -AwwHaW50ZXJjYTEgMB4GCSqGSIb3DQEJARYRaW50ZXJjYUBhZG1pbi5jb20wggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ8QgVwEjy3CgJFvET7tYNBw97 -4i33EHQT4ZuayGCJ+ADY3ZFpsw2M1IPlbAguqfBkcLd8TAjWNRAdsm9ubGlIcTZr -7LNle3gvc7qEP4qg0i6M7D06CDqtBaIJ1PMTJchOouGU9ntBe+h0qg8tzpiqJdIw -jIOPRWW98Hw9KgF6++2jtlcOW1IxiFSWqf0Mpc81qKukcxnsHjvdxmBp/Z1vL42E -m5xNOGXoxpjq8NygSuVDhQ/bZUnmHLmvv9MXe9Ob52rlzea/YafLpOeNGSA1aCxm -Fx0lcoXWp4xpoyJn9St7vmH3t7quBKdqt54zwcuHjhgZPSR6RikodKHtsl8TAgMB -AAGjZjBkMB0GA1UdDgQWBBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjAfBgNVHSMEGDAW -gBSMsBNO3UGBteMZOTq5fDPG2aPx8DASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1Ud -DwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEA0hJCtrCI9Mf47+y3pvsTjiaT -TZwpNE0cdxHjpcCHJWX2jmGbqqA6wvQ7yy4PFarmnFOoW3hQFeiLgpz9X86YTRzF -8dj3Q2MKXf6i6/iW+Y96GFqurshKp7wV25wfzWwLXcVCiM1xYPWYSyGsZAGotu4M -c7uolVABjJu5nci9mBxVmaYV5oT1mxrvq3dCPm2AvmVFNWPNRbMSAuT5B7FUDvWG -xvd1aDFduqL0iLAcrTifMIYI3XL4pBSIlL78dgY45WL6616EF3mHhW/Y4k4PNq1I -Fz08Q3y99ilhzeAci1jv6KBVHiALZNFFtYjd10KX95qnF9SomBjCTPsQ9PTC5g== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID2zCCAsOgAwIBAgIUdxRGMrv1ONRI/dJXUHa3isjVXuwwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEVMBMGA1UE -AwwMcm9vdGNhLmFkbWluMR8wHQYJKoZIhvcNAQkBFhByb290Y2FAYWRtaW4uY29t -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Bqlx+ngRZfWjktX6urJ -Tpg2//n3uMW8Gp55SVq+lbpkNXuA3W0uTlom/2fsnzmMmBAJUvv/NaLcFSN++0/d -fYGVhogxjlRoyaXa4sdFPg2ocM+lCHLGhWpYuVdTWPknikTOVNbh9z94SLRfrP8N -kuxGNZNktnVm+QJmrCLmjvnPvQlcP/WqFYehFM26NaRzswOhLLRU1YK8aVlh9Vto -PGSTPtZK60XwTfyCj/zEvHqpbGbKJPp7W0rpxl7ehPQIzGQt5IMH3zKrqLm4/pt+ -XFEVqy724sstthS73bvXguWxBLOzuMze8CbI6SJpcHaH8HO5pfFMPyE5N5l2x9Co -uQIDAQABo1MwUTAdBgNVHQ4EFgQUjLATTt1BgbXjGTk6uXwzxtmj8fAwHwYDVR0j -BBgwFoAUjLATTt1BgbXjGTk6uXwzxtmj8fAwDwYDVR0TAQH/BAUwAwEB/zANBgkq -hkiG9w0BAQsFAAOCAQEAL9/7YtxIbuTt7dgH1KOMb/y6P3Qb79fTVZTx073E2ZBj -OfSuMGUhKC30LWQXCQEsY4WpugJ4cS0NsArSYsO5XWguqgjYlWL6poOmYhGvj7ou -oi44oYqcPm6EbvLrAKdI67bkbSPLzgs+6TrxTyXHzPKFBqGBnuHaFKjgb4hGiPmd -JWKrh3fbTtuLjqSHd0Gey2uBNFql4LhPutdsCUhSOD3BibAeTbXyln70AgpTCoWT -9Qr9Ux6HYIiAHSDBzzuN8EcrlnAm9RO4zRyUVwxDsHoNsOUwybn50IM66p83HuiY -h/iX5uNjaFAADfRR4rQOwb6MLreVH2XYUSiz4M/ijg== ------END CERTIFICATE----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/ssl.pem b/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/ssl.pem deleted file mode 100644 index 5fa819c3f6d..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/ssl.pem +++ /dev/null @@ -1,126 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQD2G3cW10hPZCtX -rRYKLaa+yvxmnMNm4HijA1QbK/biD1j+6HDlbL00VVw5w3E8fc8DJD/qkL6lHy4w -u12Y1OL8EuZeBEz6+u3xp4OB5YH4LsMXc3gUcLI25wx7eqfaFcPuqSu+blGXwpJ9 -lGzwj69+lX8AYqA/+ZXufdWmyU5syTwvbWAiXF8SGnjoeNIEABYgdQ5ngy3ZLYVS -7mDwDWBoPkv1tfInZlzq9pjZFRicVNR0zfQKfMS6X2FX2M06A0R9MvWe55DJxA0s -082AHGI2+XuPomvwI+nje1b3RQhvW2SekYoHUtKnI6La4/wrLNuEEWV2UzphUa3v -fiT7aqF/AgMBAAECggEAVHC7/ZRD9SlzTZPPmhJrwc63pyVmTef6vEOqGeCNxqZp -bJyoD6+tg0U+bXtHYO/xlSxc2chsAQ+fs4y14ng4oTCC5gMnvn1WIrHNB0aNv2+b -9HHj429/ViMUP1GoQcVyAnL81UvYpu9Vf8zrS+ag2hfFCYbNYOXrsXZvVdiAFgaR -ZXh65uU7nBEODyqFj9sRtD+/ABfdoC9Vf9qs9vMaVxuIanNPtu7hHAUQ5z/WVIX/ -ZQGnfePvq5OyYDKzCHh2kWjWLLXPd/ICn6XNvjfY6k618R+3fafQ4SVyRFHNSn6H -7k7SInp4kw+QBCJXP3L47sZzZXrMEeoNslrOGMUkQQKBgQD9Vn/94JQHgAlZ8PaS -LdoNDQz53jWjBv7TZqwtZj76nHcZxGmXceSls4TZnuzZVyfk1RRxu6V4vj7sjlCB -nGnESm+158PqNcpHm2qkvuSYxZxupcx6gbnK8AWitD4Ruah2xLtIWZe75HNJPdA8 -8lew2xE9j/jo4qt1GpswCIFJzwKBgQD4sYOo5VAuNLnpV3QrXLu0svAAUZNVrQi3 -/SbYdrOg52BfmHiSPdhUy6xYgmWpcPsSPsgwl7Xr4SEMevbxNN1XvOnTVi+aTtR3 -7VmdSjpepqvx15QKXw2IekxiP3lueaJqjRgrfHNC3uv4wS+UHw4Kgx5qNL8rqoZw -nN4IjvYJUQKBgQCbzEaKa0KeGi+mEHqTC3+95vjD8dQqnnK4r7vTb73rga39Or11 -VaW4Ad8BtqsSPndaU3BvQWdSCi3IbP01uGuTO/PXeuEReGMeBoZfKsaK7Xx2z6Ag -9wa7t0C//vjtDbB82iATLoYSFzyPHmSiFqe3LAI+SfxZvIyVR/eIYrKgIQKBgHKO -6vQpNccjGKSpWWUEIZ86ch3jdfgYbhS682LxQwuSqa0/ggLzpCxjae+XmZb06WLd -MsBXkkCnAt9J4Wj5DiD1b8yfPexZ9buMxSQUZlDle/06bgJdCNHebEMxtBE0zKFC -f2jAuiOKpq5+5xjXOVOUVLVduNY3hhhn2kuc9y8RAoGAKgRwLBSk5XfOFdIkiulh -oeJ2xZHUDGxUPxteBqjXPdvMmVOJuPpLhvvV+4HwA0OXtG5GZXsl8zKhdouiVzUv -z0LXtlGX/4s1Oin+7N0qs4y+iPa4FSaypy3W5hzepwLr3HrrVnZRIjkWE0HZkBPl -MEfmbKXS0HuPCklNjx0yYio= ------END PRIVATE KEY----- ------BEGIN CERTIFICATE----- -MIIEwjCCA6qgAwIBAgIUX2smVT1Tol0Uh4r/FGiNqjEd1zUwDQYJKoZIhvcNAQEL -BQAwgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDU0ZPMQsw -CQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwKcGFydG5lcmFjYTEm -MCQGCSqGSIb3DQEJARYXcGFydG5lcmFjYUBwYXJ0bmVyYS5jb20wIBcNMjEwNzA5 -MTkxMTU5WhgPMjEyMTA2MTUxOTExNTlaMIGFMQswCQYDVQQGEwJVUzELMAkGA1UE -CAwCQ0ExDDAKBgNVBAcMA1NGTzELMAkGA1UECgwCTUMxDTALBgNVBAsMBHJvb3Qx -FTATBgNVBAMMDG5vblZhbGlkYXRvcjEoMCYGCSqGSIb3DQEJARYZbm9uVmFsaWRh -dG9yQHBhcnRuZXJhLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -APYbdxbXSE9kK1etFgotpr7K/Gacw2bgeKMDVBsr9uIPWP7ocOVsvTRVXDnDcTx9 -zwMkP+qQvqUfLjC7XZjU4vwS5l4ETPr67fGng4HlgfguwxdzeBRwsjbnDHt6p9oV -w+6pK75uUZfCkn2UbPCPr36VfwBioD/5le591abJTmzJPC9tYCJcXxIaeOh40gQA -FiB1DmeDLdkthVLuYPANYGg+S/W18idmXOr2mNkVGJxU1HTN9Ap8xLpfYVfYzToD -RH0y9Z7nkMnEDSzTzYAcYjb5e4+ia/Aj6eN7VvdFCG9bZJ6RigdS0qcjotrj/Css -24QRZXZTOmFRre9+JPtqoX8CAwEAAaOCASgwggEkMBEGCWCGSAGG+EIBAQQEAwIG -wDAdBgNVHQ4EFgQUUxNHyKHVtefbfv7VxdzZLgFQd9cwCQYDVR0TBAIwADAOBgNV -HQ8BAf8EBAMCBeAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMIG1BgNV -HSMEga0wgaqAFCGmRmD4q2qHqwUKO2b0cT71ssndoXykejB4MQswCQYDVQQGEwJV -UzELMAkGA1UECAwCQ0ExDDAKBgNVBAcMA1NGTzELMAkGA1UECgwCTUMxDTALBgNV -BAsMBHJvb3QxEDAOBgNVBAMMB2ludGVyY2ExIDAeBgkqhkiG9w0BCQEWEWludGVy -Y2FAYWRtaW4uY29tghRwBMj83j0mxnIC7JLyB9boGml0ETANBgkqhkiG9w0BAQsF -AAOCAQEAWMx7UZJTA7XWCSODWiGh1bJkvTCSc7XSY3h0hNjAwWOZQIvjIUAEaxtX -2sPlVWWnd2gt05mHbiiTqgcCPJuydGE9ne9PcSGU5fj11ZN7ofOP+V49Ru+R44KL -dV5zWsYN+STBjESwOE1uGa6ri1aUF9Viebp4Sg7/WZWOYO6WOop2Kc9KDnSm5NYP -ThQtcwbwZzmiNN62RodnpIlYU4QOu6B7wddNSGtzlGYvBI2aCCQuXxMpxKCfQ63t -QKznbR+DJXXPshcbKOMjXJZG9MSTYww9bPV50Ok4DtWfgP048CNAm4KYp+WsyoAj -RW8R1hN3vIf9V30mTixkLcTt7JVqBA== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID8DCCAtigAwIBAgIUcATI/N49JsZyAuyS8gfW6BppdBEwDQYJKoZIhvcNAQEL -BQAweDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRAwDgYDVQQDDAdpbnRlcmNhMSAwHgYJ -KoZIhvcNAQkBFhFpbnRlcmNhQGFkbWluLmNvbTAgFw0yMTA3MDkxOTExNTRaGA8y -MTIxMDYxNTE5MTE1NFowgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoG -A1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwK -cGFydG5lcmFjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmFjYUBwYXJ0bmVyYS5j -b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDccGhUDD8T8m6e2x13 -B0LgCDR8F2vlfAw9FN/tXOKKU3DpagJk0CAutL6YFFYs9MXg8D6bZx+w0iXUnaN/ -6rjIC1LFs79AizpEaoFWDzcVI66ItSW1Swu3ts0I1kK7EGgAMba0TDTnssfrtuPw -rOA2TOdVX03owOE+gaaPonIQlnew5+NNGhbW+P/5ix1IQXhyuNjV/uu3VEJBGNTh -cAuMGdjvglFuz4zyFon4kNRcQvghs5ztjs2h5ZNHjEo6YtNIdJuRS/XOXII9MfPN -plcwJArWTM3yEATNBtdc5FypOSI6cO/Pl+SVEf4kKmnzJQZWOSGiDXbpm938hGVw -ByZPAgMBAAGjZjBkMB0GA1UdDgQWBBQhpkZg+Ktqh6sFCjtm9HE+9bLJ3TAfBgNV -HSMEGDAWgBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjASBgNVHRMBAf8ECDAGAQH/AgEA -MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEAIggdc0DCxEiNaKak -/xbc7uGiGBb+UzBTjk4BmX9kEFrZf4XEaHzsptltqcQDCVtzcbFFFxMYhv/w/1g/ -WMNuUhL0mmGIGMxfoqkg50tNyuHW6aSrBXErel9WthGXAhUSzdHgkWxl3pe2wgda -qIcSwTCFgOeVWTZRdKWqKNPHirBglJHUpMgLo0qa0Ug1hxiyOQRt2yXLpe/mH3pG -cozhrEDYQMOcDhYTpPwM2Bl+TsRPpxFaV4QDfiOyvUDFLMM0dyhl2pukjay/Xpp2 -+6Tsnaa+Ui0sDcM3A36Q07+E4Vugxjzfb9a36Eg4XE+Jg3jDA8UOqjeFg9gkK3aY -NaI0qQ== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID6jCCAtKgAwIBAgIUYdazVD+VnI7jBu7xLaW+npfwHJIwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEQMA4GA1UE -AwwHaW50ZXJjYTEgMB4GCSqGSIb3DQEJARYRaW50ZXJjYUBhZG1pbi5jb20wggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ8QgVwEjy3CgJFvET7tYNBw97 -4i33EHQT4ZuayGCJ+ADY3ZFpsw2M1IPlbAguqfBkcLd8TAjWNRAdsm9ubGlIcTZr -7LNle3gvc7qEP4qg0i6M7D06CDqtBaIJ1PMTJchOouGU9ntBe+h0qg8tzpiqJdIw -jIOPRWW98Hw9KgF6++2jtlcOW1IxiFSWqf0Mpc81qKukcxnsHjvdxmBp/Z1vL42E -m5xNOGXoxpjq8NygSuVDhQ/bZUnmHLmvv9MXe9Ob52rlzea/YafLpOeNGSA1aCxm -Fx0lcoXWp4xpoyJn9St7vmH3t7quBKdqt54zwcuHjhgZPSR6RikodKHtsl8TAgMB -AAGjZjBkMB0GA1UdDgQWBBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjAfBgNVHSMEGDAW -gBSMsBNO3UGBteMZOTq5fDPG2aPx8DASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1Ud -DwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEA0hJCtrCI9Mf47+y3pvsTjiaT -TZwpNE0cdxHjpcCHJWX2jmGbqqA6wvQ7yy4PFarmnFOoW3hQFeiLgpz9X86YTRzF -8dj3Q2MKXf6i6/iW+Y96GFqurshKp7wV25wfzWwLXcVCiM1xYPWYSyGsZAGotu4M -c7uolVABjJu5nci9mBxVmaYV5oT1mxrvq3dCPm2AvmVFNWPNRbMSAuT5B7FUDvWG -xvd1aDFduqL0iLAcrTifMIYI3XL4pBSIlL78dgY45WL6616EF3mHhW/Y4k4PNq1I -Fz08Q3y99ilhzeAci1jv6KBVHiALZNFFtYjd10KX95qnF9SomBjCTPsQ9PTC5g== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID2zCCAsOgAwIBAgIUdxRGMrv1ONRI/dJXUHa3isjVXuwwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEVMBMGA1UE -AwwMcm9vdGNhLmFkbWluMR8wHQYJKoZIhvcNAQkBFhByb290Y2FAYWRtaW4uY29t -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Bqlx+ngRZfWjktX6urJ -Tpg2//n3uMW8Gp55SVq+lbpkNXuA3W0uTlom/2fsnzmMmBAJUvv/NaLcFSN++0/d -fYGVhogxjlRoyaXa4sdFPg2ocM+lCHLGhWpYuVdTWPknikTOVNbh9z94SLRfrP8N -kuxGNZNktnVm+QJmrCLmjvnPvQlcP/WqFYehFM26NaRzswOhLLRU1YK8aVlh9Vto -PGSTPtZK60XwTfyCj/zEvHqpbGbKJPp7W0rpxl7ehPQIzGQt5IMH3zKrqLm4/pt+ -XFEVqy724sstthS73bvXguWxBLOzuMze8CbI6SJpcHaH8HO5pfFMPyE5N5l2x9Co -uQIDAQABo1MwUTAdBgNVHQ4EFgQUjLATTt1BgbXjGTk6uXwzxtmj8fAwHwYDVR0j -BBgwFoAUjLATTt1BgbXjGTk6uXwzxtmj8fAwDwYDVR0TAQH/BAUwAwEB/zANBgkq -hkiG9w0BAQsFAAOCAQEAL9/7YtxIbuTt7dgH1KOMb/y6P3Qb79fTVZTx073E2ZBj -OfSuMGUhKC30LWQXCQEsY4WpugJ4cS0NsArSYsO5XWguqgjYlWL6poOmYhGvj7ou -oi44oYqcPm6EbvLrAKdI67bkbSPLzgs+6TrxTyXHzPKFBqGBnuHaFKjgb4hGiPmd -JWKrh3fbTtuLjqSHd0Gey2uBNFql4LhPutdsCUhSOD3BibAeTbXyln70AgpTCoWT -9Qr9Ux6HYIiAHSDBzzuN8EcrlnAm9RO4zRyUVwxDsHoNsOUwybn50IM66p83HuiY -h/iX5uNjaFAADfRR4rQOwb6MLreVH2XYUSiz4M/ijg== ------END CERTIFICATE----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/truststore.jks b/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/truststore.jks deleted file mode 100644 index d50fba298f8..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/nonValidator/truststore.jks and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/truststore/truststore.p12 b/acceptance-tests/tests/src/test/resources/pki-certs/truststore/truststore.p12 deleted file mode 100644 index 33079569a10..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/truststore/truststore.p12 and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator/crl.pem b/acceptance-tests/tests/src/test/resources/pki-certs/validator/crl.pem deleted file mode 100644 index 8ced006c911..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/validator/crl.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN X509 CRL----- -MIICBDCB7QIBATANBgkqhkiG9w0BAQsFADCBgTELMAkGA1UEBhMCVVMxCzAJBgNV -BAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJBgNVBAoMAk1DMQ0wCwYDVQQLDARyb290 -MRMwEQYDVQQDDApwYXJ0bmVyYWNhMSYwJAYJKoZIhvcNAQkBFhdwYXJ0bmVyYWNh -QHBhcnRuZXJhLmNvbRcNMjEwNzA5MTkxMjM1WhcNMjIwNzA5MTkxMjM1WjAnMCUC -FF9rJlU9U6JdFIeK/xRojaoxHdc5Fw0yMTA3MDkxOTEyMzVaoA4wDDAKBgNVHRQE -AwIBADANBgkqhkiG9w0BAQsFAAOCAQEAfByR//FGHSsVQbaS51d59o82XocOGnnT -p1hjceqtLGv3bhiebVrsRCOB5TsvE/r2IbB/yHYTe3+LJisIUqBxblQ6xK6IM+qA -3fY646YnPT5pvdZAPZ2BCN/xP3xqGffFKapQ9cz0/36YE3vaEoUDlC2VHK0OXI0t -4CLwAmiptUT2GW4Bk1RtokAsFiUNwNIOlRX5bywUNwkG7EuitR90QSGH3l/vyii2 -0c1Fm9He9MskwipjXpJKKb+t+m1pdpOVkSjRfjmVqi4BZwWlnQjELSLywhJ+WZG2 -Z1NgRjzPXotFKK+YD97Kx1L260A1eUZ46zSq73oUZr0EDZRnNJTr6A== ------END X509 CRL----- ------BEGIN X509 CRL----- -MIICLDCCARQCAQEwDQYJKoZIhvcNAQELBQAwgYExCzAJBgNVBAYTAlVTMQswCQYD -VQQIDAJDQTEMMAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9v -dDETMBEGA1UEAwwKcGFydG5lcmJjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmJj -YUBwYXJ0bmVyYi5jb20XDTIxMDcwOTE5MTIzNVoXDTIyMDcwOTE5MTIzNVowTjAl -AhQTZhDgbKuvX7iLRUBBTiWXBPKM1xcNMjEwNzA5MTkxMjM1WjAlAhRfayZVPVOi -XRSHiv8UaI2qMR3XORcNMjEwNzA5MTkxMjM1WqAOMAwwCgYDVR0UBAMCAQEwDQYJ -KoZIhvcNAQELBQADggEBAMIltmJ036f1BmK/baISJTZTu7PKZgSZMNORnpFT8KvC -s2GNRor5bGp5qvD6LHvsx92YVppCC6xd/beCFBtdyYifqw5xtOvqLQKuqCfxruLz -EqYjKXE/3v8VdyU71J7kFqi0U0Gy4/h/YCL92e5KNbATlmcn5ToyI2EBIEfBfV08 -mm7FBXvbHRzqhfrnCNEjBWBWz3zkJMc9Rib26eCCofYIDkY2HvYSN78YgrnMmD6O -hWOXrPoxArxvmDr5rG4vCadqbQYRkkCAOP0hBeMiB0SAcO2W2LNNAmHWXX7FvU3n -ZRZUX31WoVjhNeEQtNBb2mPYBXHQzLC66qYm1p97afc= ------END X509 CRL----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator/keys.p12 b/acceptance-tests/tests/src/test/resources/pki-certs/validator/keys.p12 deleted file mode 100644 index c3be57147fe..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator/keys.p12 and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator/keystore.jks b/acceptance-tests/tests/src/test/resources/pki-certs/validator/keystore.jks deleted file mode 100644 index 4fb942a9e0f..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator/keystore.jks and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator/nss.cfg b/acceptance-tests/tests/src/test/resources/pki-certs/validator/nss.cfg deleted file mode 100644 index f3849919caa..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/validator/nss.cfg +++ /dev/null @@ -1,6 +0,0 @@ - -name = NSScrypto-validator -nssSecmodDirectory = ./src/test/resources/pki-certs/validator/nssdb -nssDbMode = readOnly -nssModule = keystore - diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator/nssdb/cert8.db b/acceptance-tests/tests/src/test/resources/pki-certs/validator/nssdb/cert8.db deleted file mode 100644 index f1aa64c06ca..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator/nssdb/cert8.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator/nssdb/key3.db b/acceptance-tests/tests/src/test/resources/pki-certs/validator/nssdb/key3.db deleted file mode 100644 index cb2eb74985d..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator/nssdb/key3.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator/nssdb/secmod.db b/acceptance-tests/tests/src/test/resources/pki-certs/validator/nssdb/secmod.db deleted file mode 100644 index c51600ebfcf..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator/nssdb/secmod.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator/nsspin.txt b/acceptance-tests/tests/src/test/resources/pki-certs/validator/nsspin.txt deleted file mode 100644 index 5271a526801..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/validator/nsspin.txt +++ /dev/null @@ -1 +0,0 @@ -test123 diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator/ssl-ca.pem b/acceptance-tests/tests/src/test/resources/pki-certs/validator/ssl-ca.pem deleted file mode 100644 index 61a8f5230ed..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/validator/ssl-ca.pem +++ /dev/null @@ -1,70 +0,0 @@ ------BEGIN CERTIFICATE----- -MIID8DCCAtigAwIBAgIUcATI/N49JsZyAuyS8gfW6BppdBEwDQYJKoZIhvcNAQEL -BQAweDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRAwDgYDVQQDDAdpbnRlcmNhMSAwHgYJ -KoZIhvcNAQkBFhFpbnRlcmNhQGFkbWluLmNvbTAgFw0yMTA3MDkxOTExNTRaGA8y -MTIxMDYxNTE5MTE1NFowgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoG -A1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwK -cGFydG5lcmFjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmFjYUBwYXJ0bmVyYS5j -b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDccGhUDD8T8m6e2x13 -B0LgCDR8F2vlfAw9FN/tXOKKU3DpagJk0CAutL6YFFYs9MXg8D6bZx+w0iXUnaN/ -6rjIC1LFs79AizpEaoFWDzcVI66ItSW1Swu3ts0I1kK7EGgAMba0TDTnssfrtuPw -rOA2TOdVX03owOE+gaaPonIQlnew5+NNGhbW+P/5ix1IQXhyuNjV/uu3VEJBGNTh -cAuMGdjvglFuz4zyFon4kNRcQvghs5ztjs2h5ZNHjEo6YtNIdJuRS/XOXII9MfPN -plcwJArWTM3yEATNBtdc5FypOSI6cO/Pl+SVEf4kKmnzJQZWOSGiDXbpm938hGVw -ByZPAgMBAAGjZjBkMB0GA1UdDgQWBBQhpkZg+Ktqh6sFCjtm9HE+9bLJ3TAfBgNV -HSMEGDAWgBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjASBgNVHRMBAf8ECDAGAQH/AgEA -MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEAIggdc0DCxEiNaKak -/xbc7uGiGBb+UzBTjk4BmX9kEFrZf4XEaHzsptltqcQDCVtzcbFFFxMYhv/w/1g/ -WMNuUhL0mmGIGMxfoqkg50tNyuHW6aSrBXErel9WthGXAhUSzdHgkWxl3pe2wgda -qIcSwTCFgOeVWTZRdKWqKNPHirBglJHUpMgLo0qa0Ug1hxiyOQRt2yXLpe/mH3pG -cozhrEDYQMOcDhYTpPwM2Bl+TsRPpxFaV4QDfiOyvUDFLMM0dyhl2pukjay/Xpp2 -+6Tsnaa+Ui0sDcM3A36Q07+E4Vugxjzfb9a36Eg4XE+Jg3jDA8UOqjeFg9gkK3aY -NaI0qQ== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID6jCCAtKgAwIBAgIUYdazVD+VnI7jBu7xLaW+npfwHJIwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEQMA4GA1UE -AwwHaW50ZXJjYTEgMB4GCSqGSIb3DQEJARYRaW50ZXJjYUBhZG1pbi5jb20wggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ8QgVwEjy3CgJFvET7tYNBw97 -4i33EHQT4ZuayGCJ+ADY3ZFpsw2M1IPlbAguqfBkcLd8TAjWNRAdsm9ubGlIcTZr -7LNle3gvc7qEP4qg0i6M7D06CDqtBaIJ1PMTJchOouGU9ntBe+h0qg8tzpiqJdIw -jIOPRWW98Hw9KgF6++2jtlcOW1IxiFSWqf0Mpc81qKukcxnsHjvdxmBp/Z1vL42E -m5xNOGXoxpjq8NygSuVDhQ/bZUnmHLmvv9MXe9Ob52rlzea/YafLpOeNGSA1aCxm -Fx0lcoXWp4xpoyJn9St7vmH3t7quBKdqt54zwcuHjhgZPSR6RikodKHtsl8TAgMB -AAGjZjBkMB0GA1UdDgQWBBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjAfBgNVHSMEGDAW -gBSMsBNO3UGBteMZOTq5fDPG2aPx8DASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1Ud -DwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEA0hJCtrCI9Mf47+y3pvsTjiaT -TZwpNE0cdxHjpcCHJWX2jmGbqqA6wvQ7yy4PFarmnFOoW3hQFeiLgpz9X86YTRzF -8dj3Q2MKXf6i6/iW+Y96GFqurshKp7wV25wfzWwLXcVCiM1xYPWYSyGsZAGotu4M -c7uolVABjJu5nci9mBxVmaYV5oT1mxrvq3dCPm2AvmVFNWPNRbMSAuT5B7FUDvWG -xvd1aDFduqL0iLAcrTifMIYI3XL4pBSIlL78dgY45WL6616EF3mHhW/Y4k4PNq1I -Fz08Q3y99ilhzeAci1jv6KBVHiALZNFFtYjd10KX95qnF9SomBjCTPsQ9PTC5g== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID2zCCAsOgAwIBAgIUdxRGMrv1ONRI/dJXUHa3isjVXuwwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEVMBMGA1UE -AwwMcm9vdGNhLmFkbWluMR8wHQYJKoZIhvcNAQkBFhByb290Y2FAYWRtaW4uY29t -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Bqlx+ngRZfWjktX6urJ -Tpg2//n3uMW8Gp55SVq+lbpkNXuA3W0uTlom/2fsnzmMmBAJUvv/NaLcFSN++0/d -fYGVhogxjlRoyaXa4sdFPg2ocM+lCHLGhWpYuVdTWPknikTOVNbh9z94SLRfrP8N -kuxGNZNktnVm+QJmrCLmjvnPvQlcP/WqFYehFM26NaRzswOhLLRU1YK8aVlh9Vto -PGSTPtZK60XwTfyCj/zEvHqpbGbKJPp7W0rpxl7ehPQIzGQt5IMH3zKrqLm4/pt+ -XFEVqy724sstthS73bvXguWxBLOzuMze8CbI6SJpcHaH8HO5pfFMPyE5N5l2x9Co -uQIDAQABo1MwUTAdBgNVHQ4EFgQUjLATTt1BgbXjGTk6uXwzxtmj8fAwHwYDVR0j -BBgwFoAUjLATTt1BgbXjGTk6uXwzxtmj8fAwDwYDVR0TAQH/BAUwAwEB/zANBgkq -hkiG9w0BAQsFAAOCAQEAL9/7YtxIbuTt7dgH1KOMb/y6P3Qb79fTVZTx073E2ZBj -OfSuMGUhKC30LWQXCQEsY4WpugJ4cS0NsArSYsO5XWguqgjYlWL6poOmYhGvj7ou -oi44oYqcPm6EbvLrAKdI67bkbSPLzgs+6TrxTyXHzPKFBqGBnuHaFKjgb4hGiPmd -JWKrh3fbTtuLjqSHd0Gey2uBNFql4LhPutdsCUhSOD3BibAeTbXyln70AgpTCoWT -9Qr9Ux6HYIiAHSDBzzuN8EcrlnAm9RO4zRyUVwxDsHoNsOUwybn50IM66p83HuiY -h/iX5uNjaFAADfRR4rQOwb6MLreVH2XYUSiz4M/ijg== ------END CERTIFICATE----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator/ssl.pem b/acceptance-tests/tests/src/test/resources/pki-certs/validator/ssl.pem deleted file mode 100644 index a699dca2af4..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/validator/ssl.pem +++ /dev/null @@ -1,126 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCoFSKbOeGCRhkr -fL6hOZ/+Nddfcep2vLcRXW/7MIN4OK26uGQx2xJr2p3Svl+m6ib4jLWx6E//tPVs -PbsipMWIazlwM6pa8rA2SHmJ7WORq/S7RRgElxqgOnRsmgxyylhffAyHvb1aOtGl -Hqzy3C3p+k6wSwleQfDhPdV1+4CLWJFJA9iHjVZzo7wr2JlOzsvnUZmlsIBeN7sb -M+Nn5ttdL9FAEbB0YtALEdbhJQrOTASzR+rK36VGn2iYQVuhCxUHb35WbZ0E/U/M -76QkMHRZs2sQ4BAluS2bOl1pXtaVW1ZSNCcNgzSBlu5gUJSoNqdaWqh5RSQDHRaU -1subXekFAgMBAAECggEAIgvd7alkx3mhgZCMIKZGhTysupWdk52YS3ksxKnIB6Mb -dbYB/aNNcPFghJ6xS2Mkr7X42+edxMMH73v1mwfbR03I3bRpbnC4RtaZX9npEEnP -kdtmCQ/+gbQ1fwYZPWtFSVocQ6aoY02Ex7iV+b+lwT5FkCoy5Jfgzg8zXOmr2Q2s -k/7wX8rj01/our7SSO7piwrlBz/uFKpRA/1NFa42R2FdYhHVTUa3nacpI2YBUSCB -TdN9tzWT7hOmVBFQm6RBnUE31sLDvyfJ2cFTpzbIogg1ZZ1DqUbY0obIwR4EBLfS -BvmsMRmCelgrRBENCDgV4CFcxN9oADH897avIy/QQQKBgQDWJnfjDAIAjSwN3f1M -Q9AzjLuNcWIg45sWdO8k0UnfUNAyPjNFlAu9GQ2nkvsUF/J5+ujATYbLo5GkzoH8 -A7aAljYU2jsErlMb0CRfQwvetQDTDwomeKSGH+6vjV54dqJ8y5/USswpy77zo5KF -ocFZPcViyZM+e3OsAKd1wSx91QKBgQDI7fyhXo4GAAFPuJDS+kVq3ObFSvxA9ahk -vDVB+Ryvuomblekm1sZT3mOgjTiWNfU3XeS4FM0EOTXAtUFRXUBEuum/G6/gNoHq -/FPpBICEHg6mKlHs3esOUWX1VENg0zA0oQYn23ZtlYel9Wm+2tqflX3TCUR10sFd -R0QqVHnmcQKBgDLcTgrpliwlWzor5/XOAUvM99pe/GxNQk8yFH4NCttz6Q6VwT2c -LLGLmrea6/DYwyj/U3ogr4v73CJlPW/qPdUKV+vZ7Lxu3qQzagAJ/260VG7MGqAK -dSC5wsmE7NP1cR/siJg174lZr49T4P/F7BLS3edZcpQbY9yNBzirWXLFAoGAUK3W -scjwyBH6dNNMO5rIgtc+RlwMm9zIB+LWk63+ScVh+bohCzpUGlsCr/XkkdQKqXen -Hg8kHx0Fq2JcXtpNswtGTjlEzoe0dPFqk/b9z47uHOSDTtPwzSf8A50ZDOjfw1Ig -0aI6lFflRssa8cqY3Fk/U5y2A4VjpRu3kDOpWtECgYEAvyJCc69wZSphrX3lRG75 -hw92hMAB0FEqLz0L0j5iM5CS7T7nte5i0AGOWkokFeL12KAk6yH7+2mJNNfCIepm -H4DuDMOqUAhXNEKjOUUcZTSzzyIa05OnLbIflHLtNXrQWcx66H64aUyolALr3HQ/ -Nzs74GQxjAgkS6nSB0xk1wE= ------END PRIVATE KEY----- ------BEGIN CERTIFICATE----- -MIIEuzCCA6OgAwIBAgIUX2smVT1Tol0Uh4r/FGiNqjEd1zQwDQYJKoZIhvcNAQEL -BQAwgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDU0ZPMQsw -CQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwKcGFydG5lcmFjYTEm -MCQGCSqGSIb3DQEJARYXcGFydG5lcmFjYUBwYXJ0bmVyYS5jb20wIBcNMjEwNzA5 -MTkxMTU2WhgPMjEyMTA2MTUxOTExNTZaMH8xCzAJBgNVBAYTAlVTMQswCQYDVQQI -DAJDQTEMMAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDES -MBAGA1UEAwwJdmFsaWRhdG9yMSUwIwYJKoZIhvcNAQkBFhZ2YWxpZGF0b3JAcGFy -dG5lcmEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqBUimznh -gkYZK3y+oTmf/jXXX3Hqdry3EV1v+zCDeDiturhkMdsSa9qd0r5fpuom+Iy1sehP -/7T1bD27IqTFiGs5cDOqWvKwNkh5ie1jkav0u0UYBJcaoDp0bJoMcspYX3wMh729 -WjrRpR6s8twt6fpOsEsJXkHw4T3VdfuAi1iRSQPYh41Wc6O8K9iZTs7L51GZpbCA -Xje7GzPjZ+bbXS/RQBGwdGLQCxHW4SUKzkwEs0fqyt+lRp9omEFboQsVB29+Vm2d -BP1PzO+kJDB0WbNrEOAQJbktmzpdaV7WlVtWUjQnDYM0gZbuYFCUqDanWlqoeUUk -Ax0WlNbLm13pBQIDAQABo4IBKDCCASQwEQYJYIZIAYb4QgEBBAQDAgbAMB0GA1Ud -DgQWBBS3AIMGNhY4RF3tSpfawFoYcyjy3jAJBgNVHRMEAjAAMA4GA1UdDwEB/wQE -AwIF4DAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwgbUGA1UdIwSBrTCB -qoAUIaZGYPiraoerBQo7ZvRxPvWyyd2hfKR6MHgxCzAJBgNVBAYTAlVTMQswCQYD -VQQIDAJDQTEMMAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9v -dDEQMA4GA1UEAwwHaW50ZXJjYTEgMB4GCSqGSIb3DQEJARYRaW50ZXJjYUBhZG1p -bi5jb22CFHAEyPzePSbGcgLskvIH1ugaaXQRMA0GCSqGSIb3DQEBCwUAA4IBAQCm -e6f/tVSVbIzdwJiqBL6yMoaC26m7nVk8pcVArQJQeHzQ6rVkyv/MWdJiYXzWjBfx -YaFoeLTLoM/hiLjjL/PtskHny/rLlYVhV8sFVpv0xsXG6OP22UZEbpoESDVU3e8D -7XPNXTvqyOdaht7VzRJNK/iv49EdDkOVTAUsK8kOukx6DZPAQGQT+5fkt2dq0sNH -+MH/Z8p5Ue/WxL1L8XLgGv1P0K1xpW7CJoSx5pa+vds4F9NuD4TThY0xK4F1hD3+ -nTtExFHquh0DhansjOG5EZPuj8HLf2Sn9cyFXErAAOedtwWKx4HjSatdHcVCysWm -jiQxcAxGeRqlG0te9oX4 ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID8DCCAtigAwIBAgIUcATI/N49JsZyAuyS8gfW6BppdBEwDQYJKoZIhvcNAQEL -BQAweDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRAwDgYDVQQDDAdpbnRlcmNhMSAwHgYJ -KoZIhvcNAQkBFhFpbnRlcmNhQGFkbWluLmNvbTAgFw0yMTA3MDkxOTExNTRaGA8y -MTIxMDYxNTE5MTE1NFowgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoG -A1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwK -cGFydG5lcmFjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmFjYUBwYXJ0bmVyYS5j -b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDccGhUDD8T8m6e2x13 -B0LgCDR8F2vlfAw9FN/tXOKKU3DpagJk0CAutL6YFFYs9MXg8D6bZx+w0iXUnaN/ -6rjIC1LFs79AizpEaoFWDzcVI66ItSW1Swu3ts0I1kK7EGgAMba0TDTnssfrtuPw -rOA2TOdVX03owOE+gaaPonIQlnew5+NNGhbW+P/5ix1IQXhyuNjV/uu3VEJBGNTh -cAuMGdjvglFuz4zyFon4kNRcQvghs5ztjs2h5ZNHjEo6YtNIdJuRS/XOXII9MfPN -plcwJArWTM3yEATNBtdc5FypOSI6cO/Pl+SVEf4kKmnzJQZWOSGiDXbpm938hGVw -ByZPAgMBAAGjZjBkMB0GA1UdDgQWBBQhpkZg+Ktqh6sFCjtm9HE+9bLJ3TAfBgNV -HSMEGDAWgBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjASBgNVHRMBAf8ECDAGAQH/AgEA -MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEAIggdc0DCxEiNaKak -/xbc7uGiGBb+UzBTjk4BmX9kEFrZf4XEaHzsptltqcQDCVtzcbFFFxMYhv/w/1g/ -WMNuUhL0mmGIGMxfoqkg50tNyuHW6aSrBXErel9WthGXAhUSzdHgkWxl3pe2wgda -qIcSwTCFgOeVWTZRdKWqKNPHirBglJHUpMgLo0qa0Ug1hxiyOQRt2yXLpe/mH3pG -cozhrEDYQMOcDhYTpPwM2Bl+TsRPpxFaV4QDfiOyvUDFLMM0dyhl2pukjay/Xpp2 -+6Tsnaa+Ui0sDcM3A36Q07+E4Vugxjzfb9a36Eg4XE+Jg3jDA8UOqjeFg9gkK3aY -NaI0qQ== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID6jCCAtKgAwIBAgIUYdazVD+VnI7jBu7xLaW+npfwHJIwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEQMA4GA1UE -AwwHaW50ZXJjYTEgMB4GCSqGSIb3DQEJARYRaW50ZXJjYUBhZG1pbi5jb20wggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ8QgVwEjy3CgJFvET7tYNBw97 -4i33EHQT4ZuayGCJ+ADY3ZFpsw2M1IPlbAguqfBkcLd8TAjWNRAdsm9ubGlIcTZr -7LNle3gvc7qEP4qg0i6M7D06CDqtBaIJ1PMTJchOouGU9ntBe+h0qg8tzpiqJdIw -jIOPRWW98Hw9KgF6++2jtlcOW1IxiFSWqf0Mpc81qKukcxnsHjvdxmBp/Z1vL42E -m5xNOGXoxpjq8NygSuVDhQ/bZUnmHLmvv9MXe9Ob52rlzea/YafLpOeNGSA1aCxm -Fx0lcoXWp4xpoyJn9St7vmH3t7quBKdqt54zwcuHjhgZPSR6RikodKHtsl8TAgMB -AAGjZjBkMB0GA1UdDgQWBBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjAfBgNVHSMEGDAW -gBSMsBNO3UGBteMZOTq5fDPG2aPx8DASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1Ud -DwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEA0hJCtrCI9Mf47+y3pvsTjiaT -TZwpNE0cdxHjpcCHJWX2jmGbqqA6wvQ7yy4PFarmnFOoW3hQFeiLgpz9X86YTRzF -8dj3Q2MKXf6i6/iW+Y96GFqurshKp7wV25wfzWwLXcVCiM1xYPWYSyGsZAGotu4M -c7uolVABjJu5nci9mBxVmaYV5oT1mxrvq3dCPm2AvmVFNWPNRbMSAuT5B7FUDvWG -xvd1aDFduqL0iLAcrTifMIYI3XL4pBSIlL78dgY45WL6616EF3mHhW/Y4k4PNq1I -Fz08Q3y99ilhzeAci1jv6KBVHiALZNFFtYjd10KX95qnF9SomBjCTPsQ9PTC5g== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID2zCCAsOgAwIBAgIUdxRGMrv1ONRI/dJXUHa3isjVXuwwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEVMBMGA1UE -AwwMcm9vdGNhLmFkbWluMR8wHQYJKoZIhvcNAQkBFhByb290Y2FAYWRtaW4uY29t -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Bqlx+ngRZfWjktX6urJ -Tpg2//n3uMW8Gp55SVq+lbpkNXuA3W0uTlom/2fsnzmMmBAJUvv/NaLcFSN++0/d -fYGVhogxjlRoyaXa4sdFPg2ocM+lCHLGhWpYuVdTWPknikTOVNbh9z94SLRfrP8N -kuxGNZNktnVm+QJmrCLmjvnPvQlcP/WqFYehFM26NaRzswOhLLRU1YK8aVlh9Vto -PGSTPtZK60XwTfyCj/zEvHqpbGbKJPp7W0rpxl7ehPQIzGQt5IMH3zKrqLm4/pt+ -XFEVqy724sstthS73bvXguWxBLOzuMze8CbI6SJpcHaH8HO5pfFMPyE5N5l2x9Co -uQIDAQABo1MwUTAdBgNVHQ4EFgQUjLATTt1BgbXjGTk6uXwzxtmj8fAwHwYDVR0j -BBgwFoAUjLATTt1BgbXjGTk6uXwzxtmj8fAwDwYDVR0TAQH/BAUwAwEB/zANBgkq -hkiG9w0BAQsFAAOCAQEAL9/7YtxIbuTt7dgH1KOMb/y6P3Qb79fTVZTx073E2ZBj -OfSuMGUhKC30LWQXCQEsY4WpugJ4cS0NsArSYsO5XWguqgjYlWL6poOmYhGvj7ou -oi44oYqcPm6EbvLrAKdI67bkbSPLzgs+6TrxTyXHzPKFBqGBnuHaFKjgb4hGiPmd -JWKrh3fbTtuLjqSHd0Gey2uBNFql4LhPutdsCUhSOD3BibAeTbXyln70AgpTCoWT -9Qr9Ux6HYIiAHSDBzzuN8EcrlnAm9RO4zRyUVwxDsHoNsOUwybn50IM66p83HuiY -h/iX5uNjaFAADfRR4rQOwb6MLreVH2XYUSiz4M/ijg== ------END CERTIFICATE----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator/truststore.jks b/acceptance-tests/tests/src/test/resources/pki-certs/validator/truststore.jks deleted file mode 100644 index 711a7b5148d..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator/truststore.jks and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator1/crl.pem b/acceptance-tests/tests/src/test/resources/pki-certs/validator1/crl.pem deleted file mode 100644 index 8ced006c911..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/validator1/crl.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN X509 CRL----- -MIICBDCB7QIBATANBgkqhkiG9w0BAQsFADCBgTELMAkGA1UEBhMCVVMxCzAJBgNV -BAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJBgNVBAoMAk1DMQ0wCwYDVQQLDARyb290 -MRMwEQYDVQQDDApwYXJ0bmVyYWNhMSYwJAYJKoZIhvcNAQkBFhdwYXJ0bmVyYWNh -QHBhcnRuZXJhLmNvbRcNMjEwNzA5MTkxMjM1WhcNMjIwNzA5MTkxMjM1WjAnMCUC -FF9rJlU9U6JdFIeK/xRojaoxHdc5Fw0yMTA3MDkxOTEyMzVaoA4wDDAKBgNVHRQE -AwIBADANBgkqhkiG9w0BAQsFAAOCAQEAfByR//FGHSsVQbaS51d59o82XocOGnnT -p1hjceqtLGv3bhiebVrsRCOB5TsvE/r2IbB/yHYTe3+LJisIUqBxblQ6xK6IM+qA -3fY646YnPT5pvdZAPZ2BCN/xP3xqGffFKapQ9cz0/36YE3vaEoUDlC2VHK0OXI0t -4CLwAmiptUT2GW4Bk1RtokAsFiUNwNIOlRX5bywUNwkG7EuitR90QSGH3l/vyii2 -0c1Fm9He9MskwipjXpJKKb+t+m1pdpOVkSjRfjmVqi4BZwWlnQjELSLywhJ+WZG2 -Z1NgRjzPXotFKK+YD97Kx1L260A1eUZ46zSq73oUZr0EDZRnNJTr6A== ------END X509 CRL----- ------BEGIN X509 CRL----- -MIICLDCCARQCAQEwDQYJKoZIhvcNAQELBQAwgYExCzAJBgNVBAYTAlVTMQswCQYD -VQQIDAJDQTEMMAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9v -dDETMBEGA1UEAwwKcGFydG5lcmJjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmJj -YUBwYXJ0bmVyYi5jb20XDTIxMDcwOTE5MTIzNVoXDTIyMDcwOTE5MTIzNVowTjAl -AhQTZhDgbKuvX7iLRUBBTiWXBPKM1xcNMjEwNzA5MTkxMjM1WjAlAhRfayZVPVOi -XRSHiv8UaI2qMR3XORcNMjEwNzA5MTkxMjM1WqAOMAwwCgYDVR0UBAMCAQEwDQYJ -KoZIhvcNAQELBQADggEBAMIltmJ036f1BmK/baISJTZTu7PKZgSZMNORnpFT8KvC -s2GNRor5bGp5qvD6LHvsx92YVppCC6xd/beCFBtdyYifqw5xtOvqLQKuqCfxruLz -EqYjKXE/3v8VdyU71J7kFqi0U0Gy4/h/YCL92e5KNbATlmcn5ToyI2EBIEfBfV08 -mm7FBXvbHRzqhfrnCNEjBWBWz3zkJMc9Rib26eCCofYIDkY2HvYSN78YgrnMmD6O -hWOXrPoxArxvmDr5rG4vCadqbQYRkkCAOP0hBeMiB0SAcO2W2LNNAmHWXX7FvU3n -ZRZUX31WoVjhNeEQtNBb2mPYBXHQzLC66qYm1p97afc= ------END X509 CRL----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator1/keys.p12 b/acceptance-tests/tests/src/test/resources/pki-certs/validator1/keys.p12 deleted file mode 100644 index e98020b148e..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator1/keys.p12 and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator1/keystore.jks b/acceptance-tests/tests/src/test/resources/pki-certs/validator1/keystore.jks deleted file mode 100644 index 2b652b862ec..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator1/keystore.jks and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator1/nss.cfg b/acceptance-tests/tests/src/test/resources/pki-certs/validator1/nss.cfg deleted file mode 100644 index b908a6800f8..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/validator1/nss.cfg +++ /dev/null @@ -1,6 +0,0 @@ - -name = NSScrypto-validator1 -nssSecmodDirectory = ./src/test/resources/pki-certs/validator1/nssdb -nssDbMode = readOnly -nssModule = keystore - diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator1/nssdb/cert8.db b/acceptance-tests/tests/src/test/resources/pki-certs/validator1/nssdb/cert8.db deleted file mode 100644 index c9633c71418..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator1/nssdb/cert8.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator1/nssdb/key3.db b/acceptance-tests/tests/src/test/resources/pki-certs/validator1/nssdb/key3.db deleted file mode 100644 index 2b98e97f3b0..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator1/nssdb/key3.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator1/nssdb/secmod.db b/acceptance-tests/tests/src/test/resources/pki-certs/validator1/nssdb/secmod.db deleted file mode 100644 index bc3748fac05..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator1/nssdb/secmod.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator1/nsspin.txt b/acceptance-tests/tests/src/test/resources/pki-certs/validator1/nsspin.txt deleted file mode 100644 index 5271a526801..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/validator1/nsspin.txt +++ /dev/null @@ -1 +0,0 @@ -test123 diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator1/ssl-ca.pem b/acceptance-tests/tests/src/test/resources/pki-certs/validator1/ssl-ca.pem deleted file mode 100644 index 61a8f5230ed..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/validator1/ssl-ca.pem +++ /dev/null @@ -1,70 +0,0 @@ ------BEGIN CERTIFICATE----- -MIID8DCCAtigAwIBAgIUcATI/N49JsZyAuyS8gfW6BppdBEwDQYJKoZIhvcNAQEL -BQAweDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRAwDgYDVQQDDAdpbnRlcmNhMSAwHgYJ -KoZIhvcNAQkBFhFpbnRlcmNhQGFkbWluLmNvbTAgFw0yMTA3MDkxOTExNTRaGA8y -MTIxMDYxNTE5MTE1NFowgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoG -A1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwK -cGFydG5lcmFjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmFjYUBwYXJ0bmVyYS5j -b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDccGhUDD8T8m6e2x13 -B0LgCDR8F2vlfAw9FN/tXOKKU3DpagJk0CAutL6YFFYs9MXg8D6bZx+w0iXUnaN/ -6rjIC1LFs79AizpEaoFWDzcVI66ItSW1Swu3ts0I1kK7EGgAMba0TDTnssfrtuPw -rOA2TOdVX03owOE+gaaPonIQlnew5+NNGhbW+P/5ix1IQXhyuNjV/uu3VEJBGNTh -cAuMGdjvglFuz4zyFon4kNRcQvghs5ztjs2h5ZNHjEo6YtNIdJuRS/XOXII9MfPN -plcwJArWTM3yEATNBtdc5FypOSI6cO/Pl+SVEf4kKmnzJQZWOSGiDXbpm938hGVw -ByZPAgMBAAGjZjBkMB0GA1UdDgQWBBQhpkZg+Ktqh6sFCjtm9HE+9bLJ3TAfBgNV -HSMEGDAWgBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjASBgNVHRMBAf8ECDAGAQH/AgEA -MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEAIggdc0DCxEiNaKak -/xbc7uGiGBb+UzBTjk4BmX9kEFrZf4XEaHzsptltqcQDCVtzcbFFFxMYhv/w/1g/ -WMNuUhL0mmGIGMxfoqkg50tNyuHW6aSrBXErel9WthGXAhUSzdHgkWxl3pe2wgda -qIcSwTCFgOeVWTZRdKWqKNPHirBglJHUpMgLo0qa0Ug1hxiyOQRt2yXLpe/mH3pG -cozhrEDYQMOcDhYTpPwM2Bl+TsRPpxFaV4QDfiOyvUDFLMM0dyhl2pukjay/Xpp2 -+6Tsnaa+Ui0sDcM3A36Q07+E4Vugxjzfb9a36Eg4XE+Jg3jDA8UOqjeFg9gkK3aY -NaI0qQ== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID6jCCAtKgAwIBAgIUYdazVD+VnI7jBu7xLaW+npfwHJIwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEQMA4GA1UE -AwwHaW50ZXJjYTEgMB4GCSqGSIb3DQEJARYRaW50ZXJjYUBhZG1pbi5jb20wggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ8QgVwEjy3CgJFvET7tYNBw97 -4i33EHQT4ZuayGCJ+ADY3ZFpsw2M1IPlbAguqfBkcLd8TAjWNRAdsm9ubGlIcTZr -7LNle3gvc7qEP4qg0i6M7D06CDqtBaIJ1PMTJchOouGU9ntBe+h0qg8tzpiqJdIw -jIOPRWW98Hw9KgF6++2jtlcOW1IxiFSWqf0Mpc81qKukcxnsHjvdxmBp/Z1vL42E -m5xNOGXoxpjq8NygSuVDhQ/bZUnmHLmvv9MXe9Ob52rlzea/YafLpOeNGSA1aCxm -Fx0lcoXWp4xpoyJn9St7vmH3t7quBKdqt54zwcuHjhgZPSR6RikodKHtsl8TAgMB -AAGjZjBkMB0GA1UdDgQWBBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjAfBgNVHSMEGDAW -gBSMsBNO3UGBteMZOTq5fDPG2aPx8DASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1Ud -DwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEA0hJCtrCI9Mf47+y3pvsTjiaT -TZwpNE0cdxHjpcCHJWX2jmGbqqA6wvQ7yy4PFarmnFOoW3hQFeiLgpz9X86YTRzF -8dj3Q2MKXf6i6/iW+Y96GFqurshKp7wV25wfzWwLXcVCiM1xYPWYSyGsZAGotu4M -c7uolVABjJu5nci9mBxVmaYV5oT1mxrvq3dCPm2AvmVFNWPNRbMSAuT5B7FUDvWG -xvd1aDFduqL0iLAcrTifMIYI3XL4pBSIlL78dgY45WL6616EF3mHhW/Y4k4PNq1I -Fz08Q3y99ilhzeAci1jv6KBVHiALZNFFtYjd10KX95qnF9SomBjCTPsQ9PTC5g== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID2zCCAsOgAwIBAgIUdxRGMrv1ONRI/dJXUHa3isjVXuwwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEVMBMGA1UE -AwwMcm9vdGNhLmFkbWluMR8wHQYJKoZIhvcNAQkBFhByb290Y2FAYWRtaW4uY29t -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Bqlx+ngRZfWjktX6urJ -Tpg2//n3uMW8Gp55SVq+lbpkNXuA3W0uTlom/2fsnzmMmBAJUvv/NaLcFSN++0/d -fYGVhogxjlRoyaXa4sdFPg2ocM+lCHLGhWpYuVdTWPknikTOVNbh9z94SLRfrP8N -kuxGNZNktnVm+QJmrCLmjvnPvQlcP/WqFYehFM26NaRzswOhLLRU1YK8aVlh9Vto -PGSTPtZK60XwTfyCj/zEvHqpbGbKJPp7W0rpxl7ehPQIzGQt5IMH3zKrqLm4/pt+ -XFEVqy724sstthS73bvXguWxBLOzuMze8CbI6SJpcHaH8HO5pfFMPyE5N5l2x9Co -uQIDAQABo1MwUTAdBgNVHQ4EFgQUjLATTt1BgbXjGTk6uXwzxtmj8fAwHwYDVR0j -BBgwFoAUjLATTt1BgbXjGTk6uXwzxtmj8fAwDwYDVR0TAQH/BAUwAwEB/zANBgkq -hkiG9w0BAQsFAAOCAQEAL9/7YtxIbuTt7dgH1KOMb/y6P3Qb79fTVZTx073E2ZBj -OfSuMGUhKC30LWQXCQEsY4WpugJ4cS0NsArSYsO5XWguqgjYlWL6poOmYhGvj7ou -oi44oYqcPm6EbvLrAKdI67bkbSPLzgs+6TrxTyXHzPKFBqGBnuHaFKjgb4hGiPmd -JWKrh3fbTtuLjqSHd0Gey2uBNFql4LhPutdsCUhSOD3BibAeTbXyln70AgpTCoWT -9Qr9Ux6HYIiAHSDBzzuN8EcrlnAm9RO4zRyUVwxDsHoNsOUwybn50IM66p83HuiY -h/iX5uNjaFAADfRR4rQOwb6MLreVH2XYUSiz4M/ijg== ------END CERTIFICATE----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator1/ssl.pem b/acceptance-tests/tests/src/test/resources/pki-certs/validator1/ssl.pem deleted file mode 100644 index 696511ad10b..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/validator1/ssl.pem +++ /dev/null @@ -1,126 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDL9plpUXsE94Ok -8bCw5GLKlh0UQEjS9UWVFpBSSxJrTuL/nCvAAVsn76XHxhgXBnbBgUHZ+eIzCKaq -jV+cIvbihF+u1uGlvQc6zDiWKv9ijQaTr0aN0ZbKvs0h40FJyEONZqNwODp4kXnU -Cptf6iIeu7xUXclUKg0WOHYGasBGxN5vx6BRHYmY6LiyBcO560gs/fLKk5nHla1j -/oxRQ47SVEdB8aDVxJJtKZC0d0a6ik69WpmnUpCLf/+zws7zYXC3sgs80uNnXDzV -iuk9ydWMKm4w2YvsCgMgzQETJu7GLFCtR1kAWPXDkpFfIicrj0J0xMyaus/VwRdl -CCV+ENSHAgMBAAECggEAXIgPqTwR93ri+l27r0StnQLlRl6zqyEi5DRMXUk5nf03 -9oa/QmhbFo8eQreYDjocfkVJvr4i5Mwz8IQasBccsj8juYo67JoJQZ+MvHPdULRy -G2YVOBmxs2FEZu9Wm0aDfw7lbu/Tdwdooc3+F1F9ETxSgGPcBpbdQQt0YAzmFjRC -b1xVIM4bC89ks5rIRcWi0dE0dMHeZG8NKryox/baTEWzb43lv2gK+vd4aQIjKsX3 -4TqqLFO4lyTv0PW4MmlHp+iEAAlS/UtSzKogVOJqain+yOeOzaVQWGASM7c/ht5Z -cdftRguBYCj31UZ/4dT1rb6OaL0Sja9AdenVhm5lQQKBgQD0/ddhyjQHF9E+99rs -xR9zh2CAXHyenflPKpHmYi7QHKZrbYB6t308oGqaSqig0Y3WWQN34LrDlb5aMJHI -WmZw+h3ejSKLdeb1Nd9GQmDIvjfMLsAWaySzP5aOFNbZB+JhUng625oV7D744HO/ -Hc+RyOG47fEPNqpBgBuwtPZfnwKBgQDVIM5Zn02P63V+utN/kxrm8JfHcq9SLNX5 -XZsun/kjY2ZK9c3Is/pxOS34pdmzv18nxcCGzMaqvELgzbVoM8ey0tPSg9WDheEC -rESUoPHSP/GpCyv00lAzJDJlvEblTRCoKdtIdF3TGc/b4GeexxGPhBI5q2Fm+3HG -l6cYypXCGQKBgE6EfF7u+zW6wGk57oQruSTBooABJD+BycFBl3kKxo4UYJHVinPK -Eu03THMesmS4RIkKBj6JW9ILT/J/1nkr3+EdiC0ndobTxq2YEnLmGza7IA7kfNOy -ixSXsJzguOrdyAQA9l+Guh+Ek7oX1YDPARNsGku2lqeVkYNkl3tOxBdpAoGAQNrt -0Dn42U3WgE0L5St3qUUXetr8XO5F4s9IVMTbhJhP3Ym1KFkUYfX7dknJ2LfolerI -bnzMC9KkK1KN3Hq9Tz52tP3nMFHCKhUP/YaIb+mDeWRr6OFDBH0EVQ8ZJ9futK2g -DAlVL9MP/KHqBlgfkOOoxCHRzwu/EKa/c3zaW6kCgYEAqdSB1tmhNOTyfZduxFUQ -W+kSHxTvXtyy88qSx9mhOqD+9Xq/liD14Y4sprplFaIBsErZaNXEPheVsEbXmdzU -PNfdu95Vqpc+gFDpsTzKHnwt0twmRqXsusQzr3ppOrs/GPdetS46ck2yrOd/r5bt -IeHhV43mdsgE4XpPLhZSWyk= ------END PRIVATE KEY----- ------BEGIN CERTIFICATE----- -MIIEvjCCA6agAwIBAgIUX2smVT1Tol0Uh4r/FGiNqjEd1zMwDQYJKoZIhvcNAQEL -BQAwgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDU0ZPMQsw -CQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwKcGFydG5lcmFjYTEm -MCQGCSqGSIb3DQEJARYXcGFydG5lcmFjYUBwYXJ0bmVyYS5jb20wIBcNMjEwNzA5 -MTkxMTU0WhgPMjEyMTA2MTUxOTExNTRaMIGBMQswCQYDVQQGEwJVUzELMAkGA1UE -CAwCQ0ExDDAKBgNVBAcMA1NGTzELMAkGA1UECgwCTUMxDTALBgNVBAsMBHJvb3Qx -EzARBgNVBAMMCnZhbGlkYXRvcjExJjAkBgkqhkiG9w0BCQEWF3ZhbGlkYXRvcjFA -cGFydG5lcmEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy/aZ -aVF7BPeDpPGwsORiypYdFEBI0vVFlRaQUksSa07i/5wrwAFbJ++lx8YYFwZ2wYFB -2fniMwimqo1fnCL24oRfrtbhpb0HOsw4lir/Yo0Gk69GjdGWyr7NIeNBSchDjWaj -cDg6eJF51AqbX+oiHru8VF3JVCoNFjh2BmrARsTeb8egUR2JmOi4sgXDuetILP3y -ypOZx5WtY/6MUUOO0lRHQfGg1cSSbSmQtHdGuopOvVqZp1KQi3//s8LO82Fwt7IL -PNLjZ1w81YrpPcnVjCpuMNmL7AoDIM0BEybuxixQrUdZAFj1w5KRXyInK49CdMTM -mrrP1cEXZQglfhDUhwIDAQABo4IBKDCCASQwEQYJYIZIAYb4QgEBBAQDAgbAMB0G -A1UdDgQWBBTYucEx67wBKeANHFZjSwmx+7kSszAJBgNVHRMEAjAAMA4GA1UdDwEB -/wQEAwIF4DAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwgbUGA1UdIwSB -rTCBqoAUIaZGYPiraoerBQo7ZvRxPvWyyd2hfKR6MHgxCzAJBgNVBAYTAlVTMQsw -CQYDVQQIDAJDQTEMMAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwE -cm9vdDEQMA4GA1UEAwwHaW50ZXJjYTEgMB4GCSqGSIb3DQEJARYRaW50ZXJjYUBh -ZG1pbi5jb22CFHAEyPzePSbGcgLskvIH1ugaaXQRMA0GCSqGSIb3DQEBCwUAA4IB -AQCo3ZgqNtVIf99TEsVOpvQQRQw0ccZCpsQSgN6vITxInYEydyg9NxGgFHVNwcSY -XpGw0r+Ghz7BS/a2AIz1PHmf0FjpkKLMJ72StaspovMLONPc4yHjMy1bUbe+7JAn -1zC8u9JyjnGX7UIA8qPnAvV+ZAI/MM5674Q3D9waQHiuToo1tV6/HlwMWQthbIy6 -iQyztsEHq+M2edWZmEOwiUk+aIgNFtpFk9gBj09RBgm93iVscmykX5vAPjxOJ8Zh -71jW0P3Q0ZTZImxZYIv7SOcseioneZBQJekdg3UDUrPQlh4WVuKY5aqVh4x2kJRG -jZI47U1/ihglkdSuDuStTS/S ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID8DCCAtigAwIBAgIUcATI/N49JsZyAuyS8gfW6BppdBEwDQYJKoZIhvcNAQEL -BQAweDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRAwDgYDVQQDDAdpbnRlcmNhMSAwHgYJ -KoZIhvcNAQkBFhFpbnRlcmNhQGFkbWluLmNvbTAgFw0yMTA3MDkxOTExNTRaGA8y -MTIxMDYxNTE5MTE1NFowgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoG -A1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwK -cGFydG5lcmFjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmFjYUBwYXJ0bmVyYS5j -b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDccGhUDD8T8m6e2x13 -B0LgCDR8F2vlfAw9FN/tXOKKU3DpagJk0CAutL6YFFYs9MXg8D6bZx+w0iXUnaN/ -6rjIC1LFs79AizpEaoFWDzcVI66ItSW1Swu3ts0I1kK7EGgAMba0TDTnssfrtuPw -rOA2TOdVX03owOE+gaaPonIQlnew5+NNGhbW+P/5ix1IQXhyuNjV/uu3VEJBGNTh -cAuMGdjvglFuz4zyFon4kNRcQvghs5ztjs2h5ZNHjEo6YtNIdJuRS/XOXII9MfPN -plcwJArWTM3yEATNBtdc5FypOSI6cO/Pl+SVEf4kKmnzJQZWOSGiDXbpm938hGVw -ByZPAgMBAAGjZjBkMB0GA1UdDgQWBBQhpkZg+Ktqh6sFCjtm9HE+9bLJ3TAfBgNV -HSMEGDAWgBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjASBgNVHRMBAf8ECDAGAQH/AgEA -MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEAIggdc0DCxEiNaKak -/xbc7uGiGBb+UzBTjk4BmX9kEFrZf4XEaHzsptltqcQDCVtzcbFFFxMYhv/w/1g/ -WMNuUhL0mmGIGMxfoqkg50tNyuHW6aSrBXErel9WthGXAhUSzdHgkWxl3pe2wgda -qIcSwTCFgOeVWTZRdKWqKNPHirBglJHUpMgLo0qa0Ug1hxiyOQRt2yXLpe/mH3pG -cozhrEDYQMOcDhYTpPwM2Bl+TsRPpxFaV4QDfiOyvUDFLMM0dyhl2pukjay/Xpp2 -+6Tsnaa+Ui0sDcM3A36Q07+E4Vugxjzfb9a36Eg4XE+Jg3jDA8UOqjeFg9gkK3aY -NaI0qQ== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID6jCCAtKgAwIBAgIUYdazVD+VnI7jBu7xLaW+npfwHJIwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEQMA4GA1UE -AwwHaW50ZXJjYTEgMB4GCSqGSIb3DQEJARYRaW50ZXJjYUBhZG1pbi5jb20wggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ8QgVwEjy3CgJFvET7tYNBw97 -4i33EHQT4ZuayGCJ+ADY3ZFpsw2M1IPlbAguqfBkcLd8TAjWNRAdsm9ubGlIcTZr -7LNle3gvc7qEP4qg0i6M7D06CDqtBaIJ1PMTJchOouGU9ntBe+h0qg8tzpiqJdIw -jIOPRWW98Hw9KgF6++2jtlcOW1IxiFSWqf0Mpc81qKukcxnsHjvdxmBp/Z1vL42E -m5xNOGXoxpjq8NygSuVDhQ/bZUnmHLmvv9MXe9Ob52rlzea/YafLpOeNGSA1aCxm -Fx0lcoXWp4xpoyJn9St7vmH3t7quBKdqt54zwcuHjhgZPSR6RikodKHtsl8TAgMB -AAGjZjBkMB0GA1UdDgQWBBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjAfBgNVHSMEGDAW -gBSMsBNO3UGBteMZOTq5fDPG2aPx8DASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1Ud -DwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEA0hJCtrCI9Mf47+y3pvsTjiaT -TZwpNE0cdxHjpcCHJWX2jmGbqqA6wvQ7yy4PFarmnFOoW3hQFeiLgpz9X86YTRzF -8dj3Q2MKXf6i6/iW+Y96GFqurshKp7wV25wfzWwLXcVCiM1xYPWYSyGsZAGotu4M -c7uolVABjJu5nci9mBxVmaYV5oT1mxrvq3dCPm2AvmVFNWPNRbMSAuT5B7FUDvWG -xvd1aDFduqL0iLAcrTifMIYI3XL4pBSIlL78dgY45WL6616EF3mHhW/Y4k4PNq1I -Fz08Q3y99ilhzeAci1jv6KBVHiALZNFFtYjd10KX95qnF9SomBjCTPsQ9PTC5g== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID2zCCAsOgAwIBAgIUdxRGMrv1ONRI/dJXUHa3isjVXuwwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEVMBMGA1UE -AwwMcm9vdGNhLmFkbWluMR8wHQYJKoZIhvcNAQkBFhByb290Y2FAYWRtaW4uY29t -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Bqlx+ngRZfWjktX6urJ -Tpg2//n3uMW8Gp55SVq+lbpkNXuA3W0uTlom/2fsnzmMmBAJUvv/NaLcFSN++0/d -fYGVhogxjlRoyaXa4sdFPg2ocM+lCHLGhWpYuVdTWPknikTOVNbh9z94SLRfrP8N -kuxGNZNktnVm+QJmrCLmjvnPvQlcP/WqFYehFM26NaRzswOhLLRU1YK8aVlh9Vto -PGSTPtZK60XwTfyCj/zEvHqpbGbKJPp7W0rpxl7ehPQIzGQt5IMH3zKrqLm4/pt+ -XFEVqy724sstthS73bvXguWxBLOzuMze8CbI6SJpcHaH8HO5pfFMPyE5N5l2x9Co -uQIDAQABo1MwUTAdBgNVHQ4EFgQUjLATTt1BgbXjGTk6uXwzxtmj8fAwHwYDVR0j -BBgwFoAUjLATTt1BgbXjGTk6uXwzxtmj8fAwDwYDVR0TAQH/BAUwAwEB/zANBgkq -hkiG9w0BAQsFAAOCAQEAL9/7YtxIbuTt7dgH1KOMb/y6P3Qb79fTVZTx073E2ZBj -OfSuMGUhKC30LWQXCQEsY4WpugJ4cS0NsArSYsO5XWguqgjYlWL6poOmYhGvj7ou -oi44oYqcPm6EbvLrAKdI67bkbSPLzgs+6TrxTyXHzPKFBqGBnuHaFKjgb4hGiPmd -JWKrh3fbTtuLjqSHd0Gey2uBNFql4LhPutdsCUhSOD3BibAeTbXyln70AgpTCoWT -9Qr9Ux6HYIiAHSDBzzuN8EcrlnAm9RO4zRyUVwxDsHoNsOUwybn50IM66p83HuiY -h/iX5uNjaFAADfRR4rQOwb6MLreVH2XYUSiz4M/ijg== ------END CERTIFICATE----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator1/truststore.jks b/acceptance-tests/tests/src/test/resources/pki-certs/validator1/truststore.jks deleted file mode 100644 index c6fd60f037d..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator1/truststore.jks and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator2/crl.pem b/acceptance-tests/tests/src/test/resources/pki-certs/validator2/crl.pem deleted file mode 100644 index 8ced006c911..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/validator2/crl.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN X509 CRL----- -MIICBDCB7QIBATANBgkqhkiG9w0BAQsFADCBgTELMAkGA1UEBhMCVVMxCzAJBgNV -BAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJBgNVBAoMAk1DMQ0wCwYDVQQLDARyb290 -MRMwEQYDVQQDDApwYXJ0bmVyYWNhMSYwJAYJKoZIhvcNAQkBFhdwYXJ0bmVyYWNh -QHBhcnRuZXJhLmNvbRcNMjEwNzA5MTkxMjM1WhcNMjIwNzA5MTkxMjM1WjAnMCUC -FF9rJlU9U6JdFIeK/xRojaoxHdc5Fw0yMTA3MDkxOTEyMzVaoA4wDDAKBgNVHRQE -AwIBADANBgkqhkiG9w0BAQsFAAOCAQEAfByR//FGHSsVQbaS51d59o82XocOGnnT -p1hjceqtLGv3bhiebVrsRCOB5TsvE/r2IbB/yHYTe3+LJisIUqBxblQ6xK6IM+qA -3fY646YnPT5pvdZAPZ2BCN/xP3xqGffFKapQ9cz0/36YE3vaEoUDlC2VHK0OXI0t -4CLwAmiptUT2GW4Bk1RtokAsFiUNwNIOlRX5bywUNwkG7EuitR90QSGH3l/vyii2 -0c1Fm9He9MskwipjXpJKKb+t+m1pdpOVkSjRfjmVqi4BZwWlnQjELSLywhJ+WZG2 -Z1NgRjzPXotFKK+YD97Kx1L260A1eUZ46zSq73oUZr0EDZRnNJTr6A== ------END X509 CRL----- ------BEGIN X509 CRL----- -MIICLDCCARQCAQEwDQYJKoZIhvcNAQELBQAwgYExCzAJBgNVBAYTAlVTMQswCQYD -VQQIDAJDQTEMMAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9v -dDETMBEGA1UEAwwKcGFydG5lcmJjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmJj -YUBwYXJ0bmVyYi5jb20XDTIxMDcwOTE5MTIzNVoXDTIyMDcwOTE5MTIzNVowTjAl -AhQTZhDgbKuvX7iLRUBBTiWXBPKM1xcNMjEwNzA5MTkxMjM1WjAlAhRfayZVPVOi -XRSHiv8UaI2qMR3XORcNMjEwNzA5MTkxMjM1WqAOMAwwCgYDVR0UBAMCAQEwDQYJ -KoZIhvcNAQELBQADggEBAMIltmJ036f1BmK/baISJTZTu7PKZgSZMNORnpFT8KvC -s2GNRor5bGp5qvD6LHvsx92YVppCC6xd/beCFBtdyYifqw5xtOvqLQKuqCfxruLz -EqYjKXE/3v8VdyU71J7kFqi0U0Gy4/h/YCL92e5KNbATlmcn5ToyI2EBIEfBfV08 -mm7FBXvbHRzqhfrnCNEjBWBWz3zkJMc9Rib26eCCofYIDkY2HvYSN78YgrnMmD6O -hWOXrPoxArxvmDr5rG4vCadqbQYRkkCAOP0hBeMiB0SAcO2W2LNNAmHWXX7FvU3n -ZRZUX31WoVjhNeEQtNBb2mPYBXHQzLC66qYm1p97afc= ------END X509 CRL----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator2/keys.p12 b/acceptance-tests/tests/src/test/resources/pki-certs/validator2/keys.p12 deleted file mode 100644 index c74b1a39873..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator2/keys.p12 and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator2/keystore.jks b/acceptance-tests/tests/src/test/resources/pki-certs/validator2/keystore.jks deleted file mode 100644 index 414b903e133..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator2/keystore.jks and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator2/nss.cfg b/acceptance-tests/tests/src/test/resources/pki-certs/validator2/nss.cfg deleted file mode 100644 index a6725a100fe..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/validator2/nss.cfg +++ /dev/null @@ -1,6 +0,0 @@ - -name = NSScrypto-validator2 -nssSecmodDirectory = ./src/test/resources/pki-certs/validator2/nssdb -nssDbMode = readOnly -nssModule = keystore - diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator2/nssdb/cert8.db b/acceptance-tests/tests/src/test/resources/pki-certs/validator2/nssdb/cert8.db deleted file mode 100644 index 7dd3fae7dee..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator2/nssdb/cert8.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator2/nssdb/key3.db b/acceptance-tests/tests/src/test/resources/pki-certs/validator2/nssdb/key3.db deleted file mode 100644 index 3cdbd6121b4..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator2/nssdb/key3.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator2/nssdb/secmod.db b/acceptance-tests/tests/src/test/resources/pki-certs/validator2/nssdb/secmod.db deleted file mode 100644 index 40f3b7e3c4d..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator2/nssdb/secmod.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator2/nsspin.txt b/acceptance-tests/tests/src/test/resources/pki-certs/validator2/nsspin.txt deleted file mode 100644 index 5271a526801..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/validator2/nsspin.txt +++ /dev/null @@ -1 +0,0 @@ -test123 diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator2/ssl-ca.pem b/acceptance-tests/tests/src/test/resources/pki-certs/validator2/ssl-ca.pem deleted file mode 100644 index c0c4bf478f6..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/validator2/ssl-ca.pem +++ /dev/null @@ -1,70 +0,0 @@ ------BEGIN CERTIFICATE----- -MIID8DCCAtigAwIBAgIUcATI/N49JsZyAuyS8gfW6BppdBIwDQYJKoZIhvcNAQEL -BQAweDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRAwDgYDVQQDDAdpbnRlcmNhMSAwHgYJ -KoZIhvcNAQkBFhFpbnRlcmNhQGFkbWluLmNvbTAgFw0yMTA3MDkxOTEyMTFaGA8y -MTIxMDYxNTE5MTIxMVowgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoG -A1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwK -cGFydG5lcmJjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmJjYUBwYXJ0bmVyYi5j -b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNTP8IviC0X2ozlU2+ -9+mPLL9NqJKRCsEiH7XypcXkqkzCYZv/+6nNWXGLr6HsF5qXaM/YVMJYtwiaJquw -qW1CpOKxhHA1gubNt0Fzmt69/5yP6D1zxTPzAcybNMuCy8VcLJpovYHO40+2HD6I -D91/zh3jtrgKPoEXgaW3eX4+hykQWaru3P/G4PrIvPyZXnccoCdBSRCqyZSBJUko -e5ZLnCQMlkwgDONGT3MqQyUC5lqFFMzZ75pOm2reiBe6HzP3H4wK7Ldpxds9ubGk -Acb08+QzcUT7uHvENyvLm5mbf68QurFTwIDEVg2K03dgqQWbJudSzRJAmJYSszFQ -vLZdAgMBAAGjZjBkMB0GA1UdDgQWBBR5aiJ1nA1HHi0XzbRsp4V7T8eKBjAfBgNV -HSMEGDAWgBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjASBgNVHRMBAf8ECDAGAQH/AgEA -MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEAsmFsG3SV1fQg6kO+ -dYvFrDKNOJzTBdVYmFhg4y4H7Qt+dwQcs/ZcOCIaAFUfMUH6CWe1AAdCymfwUHxn -l/FMeJHh/+d1cybC+Wj9wtBbz7nPsWh/PXbbg3Zi8N4+a6Y0NhZAGlNn1UgMHx+E -SkSIXk63FkGT01xM2aYtfr9ABuDdoLK/YPecduiawZoRb02WwIpZFNeEE/MS7XG6 -fX9TdUrNOal8pnWUM79K+rzdlzstpCx7uFFbACUEiiIuYiqG6X+DuypfGmQnjRXB -zbc6NsQONUxg7fnJObdpV7kKTlbpREFoECkTBdqKd7AuJwYRvGaQDWK9P1IYFr1W -Cnm4Cg== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID6jCCAtKgAwIBAgIUYdazVD+VnI7jBu7xLaW+npfwHJIwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEQMA4GA1UE -AwwHaW50ZXJjYTEgMB4GCSqGSIb3DQEJARYRaW50ZXJjYUBhZG1pbi5jb20wggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ8QgVwEjy3CgJFvET7tYNBw97 -4i33EHQT4ZuayGCJ+ADY3ZFpsw2M1IPlbAguqfBkcLd8TAjWNRAdsm9ubGlIcTZr -7LNle3gvc7qEP4qg0i6M7D06CDqtBaIJ1PMTJchOouGU9ntBe+h0qg8tzpiqJdIw -jIOPRWW98Hw9KgF6++2jtlcOW1IxiFSWqf0Mpc81qKukcxnsHjvdxmBp/Z1vL42E -m5xNOGXoxpjq8NygSuVDhQ/bZUnmHLmvv9MXe9Ob52rlzea/YafLpOeNGSA1aCxm -Fx0lcoXWp4xpoyJn9St7vmH3t7quBKdqt54zwcuHjhgZPSR6RikodKHtsl8TAgMB -AAGjZjBkMB0GA1UdDgQWBBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjAfBgNVHSMEGDAW -gBSMsBNO3UGBteMZOTq5fDPG2aPx8DASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1Ud -DwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEA0hJCtrCI9Mf47+y3pvsTjiaT -TZwpNE0cdxHjpcCHJWX2jmGbqqA6wvQ7yy4PFarmnFOoW3hQFeiLgpz9X86YTRzF -8dj3Q2MKXf6i6/iW+Y96GFqurshKp7wV25wfzWwLXcVCiM1xYPWYSyGsZAGotu4M -c7uolVABjJu5nci9mBxVmaYV5oT1mxrvq3dCPm2AvmVFNWPNRbMSAuT5B7FUDvWG -xvd1aDFduqL0iLAcrTifMIYI3XL4pBSIlL78dgY45WL6616EF3mHhW/Y4k4PNq1I -Fz08Q3y99ilhzeAci1jv6KBVHiALZNFFtYjd10KX95qnF9SomBjCTPsQ9PTC5g== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID2zCCAsOgAwIBAgIUdxRGMrv1ONRI/dJXUHa3isjVXuwwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEVMBMGA1UE -AwwMcm9vdGNhLmFkbWluMR8wHQYJKoZIhvcNAQkBFhByb290Y2FAYWRtaW4uY29t -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Bqlx+ngRZfWjktX6urJ -Tpg2//n3uMW8Gp55SVq+lbpkNXuA3W0uTlom/2fsnzmMmBAJUvv/NaLcFSN++0/d -fYGVhogxjlRoyaXa4sdFPg2ocM+lCHLGhWpYuVdTWPknikTOVNbh9z94SLRfrP8N -kuxGNZNktnVm+QJmrCLmjvnPvQlcP/WqFYehFM26NaRzswOhLLRU1YK8aVlh9Vto -PGSTPtZK60XwTfyCj/zEvHqpbGbKJPp7W0rpxl7ehPQIzGQt5IMH3zKrqLm4/pt+ -XFEVqy724sstthS73bvXguWxBLOzuMze8CbI6SJpcHaH8HO5pfFMPyE5N5l2x9Co -uQIDAQABo1MwUTAdBgNVHQ4EFgQUjLATTt1BgbXjGTk6uXwzxtmj8fAwHwYDVR0j -BBgwFoAUjLATTt1BgbXjGTk6uXwzxtmj8fAwDwYDVR0TAQH/BAUwAwEB/zANBgkq -hkiG9w0BAQsFAAOCAQEAL9/7YtxIbuTt7dgH1KOMb/y6P3Qb79fTVZTx073E2ZBj -OfSuMGUhKC30LWQXCQEsY4WpugJ4cS0NsArSYsO5XWguqgjYlWL6poOmYhGvj7ou -oi44oYqcPm6EbvLrAKdI67bkbSPLzgs+6TrxTyXHzPKFBqGBnuHaFKjgb4hGiPmd -JWKrh3fbTtuLjqSHd0Gey2uBNFql4LhPutdsCUhSOD3BibAeTbXyln70AgpTCoWT -9Qr9Ux6HYIiAHSDBzzuN8EcrlnAm9RO4zRyUVwxDsHoNsOUwybn50IM66p83HuiY -h/iX5uNjaFAADfRR4rQOwb6MLreVH2XYUSiz4M/ijg== ------END CERTIFICATE----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator2/ssl.pem b/acceptance-tests/tests/src/test/resources/pki-certs/validator2/ssl.pem deleted file mode 100644 index dbe6313f06c..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/validator2/ssl.pem +++ /dev/null @@ -1,126 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDDR/V9lCYi6g49 -91p6yUVKsPmPedT4/gNS2gVOLhGrgwxNJW2cWMjv4wZG0864LxtK/gkoMR4hN4lM -BvePO73bnUBHwTmm2fAOelmoJAdLAhUkXXgbsHnok6PP7v8hgZEFMLw4+UXO+LB5 -5YbZIyNOB1FcCteac7bdVRJfswgdO5MdhlZOVcg766TMK9/8ezSthExN06exvViZ -4v/L9X5MOLmdRRPq/I1X9wCKf/fIY/FO/zJkwv2VZNhPwkW8SpiS3UevsIhxRJz6 -8zyYXfKhnUBIShDuERladJqthPkdgOp6ca0dZrTqS64MqDE7rZE6cU3xOkRLhVbd -vEbGPfTTAgMBAAECggEAXxy9RNzTdk3pgeTn/MXrKeXopyj/wgZ2N/RYgSuPD6pU -Y37HVE8qDyzT8ctX8/19nZj9fMXvlt4l0RHTob/HSR/eHxTNr6aWdXbCFFL3+mCZ -S4z64wGhXtYKA0y0lu00ZQLHqjg+9//IdJaGh1vF5ktKJTpiqKeTeVnmCRZ57UIr -j8LF8M/I1NO/ScF+CeMDHmiyecLGlFjqiEu9GtE1u9ZQNA1rmPMQ/0/nITOrmTJz -IhZK7H5xIRCdCENNuB41JGmO4Y5tMn8Bj11vJRFW+9OPF5R+lefBl2J9ZVxH3beu -yTtAdf7HDhQsVe45vHohDjkVbqEo4D2HyUShWsYiAQKBgQD8UyPPr7uqGCPXDxif -cuc7kySuWL2XbnFb8xcXsj+l6TtAod4O5ei31xcGw2hy2G8f0yGQ2U+OVKPCAXum -18rkjQNWCpPY0sRdM849bF9ZDCNrlTihD45xnPrrFdQfF5JDnPOyku7hrUs18k8R -IhPxXazJJpXmUmPJZM67z7tbMQKBgQDGIB3UGLeuuGdF/sTp9TsSHTrpAVzk1VR3 -D1PiJST3dsiCAnqnzs75agbOI8f8bdrMcWVrHUbMShuRjQWkcnRN/J9nOduCUIMr -AMTEozNfF6MQBc5nceZtvvuCGqD2TUg/bmoC3yp4q0SGIx4ZufhYUhmQ65FQvrGX -rvrNkPLHQwKBgC9IgSh1HOyl82lkic+vX4d5bmFBfd23s4RwjD5Dh8vWZeUZsBwI -l1CD/OumiJck2cYT8XFMV60rTTiYwIwmWf30MtDX0JbMAEIa6CX4ffGY5bZOUztT -R/UgXqDZCVP+PclvyvcGs78Uk0h7rLdgNG6BiFR1Z5eujsZ5nofxSIGxAoGBAJdA -jYUOGaxZOv5EsS+q952skvyu/av08La0DOXoVYBWPlyCaAET8Q6X0vosdtBPTWom -Z7aOcy4ipUlIz7Xrar7ezRKRKPMjY//AZnd34BAIi4P/6sMuQSwnUPrSOW/PMAi8 -0lG+7T2SAo4k4SXiw6UpiDsKm7V43ztdajrlHXLhAoGAXTpF0AGnc5uje11Y1Tvi -zhu3NIXfZYzbtPR8UOxCDmUvwwKiW5DkLBe6rSx2DnVuGdS6ntQLq0Us1Tamo6mW -CNhOZmJyU9kzz/XfHFzkWaen92Cq41w7Hd0oziF1T8W0UqLSk42Xq8qERorRoqlg -1rV/3EvT/wSYmTdTdPsdcsk= ------END PRIVATE KEY----- ------BEGIN CERTIFICATE----- -MIIEvjCCA6agAwIBAgIUE2YQ4Gyrr1+4i0VAQU4llwTyjNQwDQYJKoZIhvcNAQEL -BQAwgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDU0ZPMQsw -CQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwKcGFydG5lcmJjYTEm -MCQGCSqGSIb3DQEJARYXcGFydG5lcmJjYUBwYXJ0bmVyYi5jb20wIBcNMjEwNzA5 -MTkxMjExWhgPMjEyMTA2MTUxOTEyMTFaMIGBMQswCQYDVQQGEwJVUzELMAkGA1UE -CAwCQ0ExDDAKBgNVBAcMA1NGTzELMAkGA1UECgwCTUMxDTALBgNVBAsMBHJvb3Qx -EzARBgNVBAMMCnZhbGlkYXRvcjIxJjAkBgkqhkiG9w0BCQEWF3ZhbGlkYXRvcjJA -cGFydG5lcmIuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw0f1 -fZQmIuoOPfdaeslFSrD5j3nU+P4DUtoFTi4Rq4MMTSVtnFjI7+MGRtPOuC8bSv4J -KDEeITeJTAb3jzu9251AR8E5ptnwDnpZqCQHSwIVJF14G7B56JOjz+7/IYGRBTC8 -OPlFzviweeWG2SMjTgdRXArXmnO23VUSX7MIHTuTHYZWTlXIO+ukzCvf/Hs0rYRM -TdOnsb1YmeL/y/V+TDi5nUUT6vyNV/cAin/3yGPxTv8yZML9lWTYT8JFvEqYkt1H -r7CIcUSc+vM8mF3yoZ1ASEoQ7hEZWnSarYT5HYDqenGtHWa06kuuDKgxO62ROnFN -8TpES4VW3bxGxj300wIDAQABo4IBKDCCASQwEQYJYIZIAYb4QgEBBAQDAgbAMB0G -A1UdDgQWBBRvD8e/IWuuGwe8ErupvKv8q5mbcTAJBgNVHRMEAjAAMA4GA1UdDwEB -/wQEAwIF4DAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwgbUGA1UdIwSB -rTCBqoAUeWoidZwNRx4tF820bKeFe0/HigahfKR6MHgxCzAJBgNVBAYTAlVTMQsw -CQYDVQQIDAJDQTEMMAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwE -cm9vdDEQMA4GA1UEAwwHaW50ZXJjYTEgMB4GCSqGSIb3DQEJARYRaW50ZXJjYUBh -ZG1pbi5jb22CFHAEyPzePSbGcgLskvIH1ugaaXQSMA0GCSqGSIb3DQEBCwUAA4IB -AQBOM0aMjtCa34n3o4MRUkGOSgcw8bhnKmJSHIjBKvhkiroPVJbXHC72UZTJ/yiO -/C1rrIrWES4iosAEE+qhqFqRxyHiLlrLrnRqIbNchaNVQch2Q2IZOY0vwSIv+0Vh -cnHMWGOPtMwvWRZdM7eXIiB0bjvXnvCk1erI00UjTeuXMxtQkKe5l3CVl7QYU/ns -WbihGXkt4Gzp2vtUhUVcpZOahhlAMTCxkeArrFTCi2zyX9beR2oFqqD4U78QmvSD -pSGonPFj7YxgbDx63LXvvcCRkQqVfK3SnqAlI1LkPnV0zVNxNOBol/5Fr7oZec5q -VkVF2lVfOUIwg6OXsNS21mzR ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID8DCCAtigAwIBAgIUcATI/N49JsZyAuyS8gfW6BppdBIwDQYJKoZIhvcNAQEL -BQAweDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRAwDgYDVQQDDAdpbnRlcmNhMSAwHgYJ -KoZIhvcNAQkBFhFpbnRlcmNhQGFkbWluLmNvbTAgFw0yMTA3MDkxOTEyMTFaGA8y -MTIxMDYxNTE5MTIxMVowgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoG -A1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwK -cGFydG5lcmJjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmJjYUBwYXJ0bmVyYi5j -b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNTP8IviC0X2ozlU2+ -9+mPLL9NqJKRCsEiH7XypcXkqkzCYZv/+6nNWXGLr6HsF5qXaM/YVMJYtwiaJquw -qW1CpOKxhHA1gubNt0Fzmt69/5yP6D1zxTPzAcybNMuCy8VcLJpovYHO40+2HD6I -D91/zh3jtrgKPoEXgaW3eX4+hykQWaru3P/G4PrIvPyZXnccoCdBSRCqyZSBJUko -e5ZLnCQMlkwgDONGT3MqQyUC5lqFFMzZ75pOm2reiBe6HzP3H4wK7Ldpxds9ubGk -Acb08+QzcUT7uHvENyvLm5mbf68QurFTwIDEVg2K03dgqQWbJudSzRJAmJYSszFQ -vLZdAgMBAAGjZjBkMB0GA1UdDgQWBBR5aiJ1nA1HHi0XzbRsp4V7T8eKBjAfBgNV -HSMEGDAWgBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjASBgNVHRMBAf8ECDAGAQH/AgEA -MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEAsmFsG3SV1fQg6kO+ -dYvFrDKNOJzTBdVYmFhg4y4H7Qt+dwQcs/ZcOCIaAFUfMUH6CWe1AAdCymfwUHxn -l/FMeJHh/+d1cybC+Wj9wtBbz7nPsWh/PXbbg3Zi8N4+a6Y0NhZAGlNn1UgMHx+E -SkSIXk63FkGT01xM2aYtfr9ABuDdoLK/YPecduiawZoRb02WwIpZFNeEE/MS7XG6 -fX9TdUrNOal8pnWUM79K+rzdlzstpCx7uFFbACUEiiIuYiqG6X+DuypfGmQnjRXB -zbc6NsQONUxg7fnJObdpV7kKTlbpREFoECkTBdqKd7AuJwYRvGaQDWK9P1IYFr1W -Cnm4Cg== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID6jCCAtKgAwIBAgIUYdazVD+VnI7jBu7xLaW+npfwHJIwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEQMA4GA1UE -AwwHaW50ZXJjYTEgMB4GCSqGSIb3DQEJARYRaW50ZXJjYUBhZG1pbi5jb20wggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ8QgVwEjy3CgJFvET7tYNBw97 -4i33EHQT4ZuayGCJ+ADY3ZFpsw2M1IPlbAguqfBkcLd8TAjWNRAdsm9ubGlIcTZr -7LNle3gvc7qEP4qg0i6M7D06CDqtBaIJ1PMTJchOouGU9ntBe+h0qg8tzpiqJdIw -jIOPRWW98Hw9KgF6++2jtlcOW1IxiFSWqf0Mpc81qKukcxnsHjvdxmBp/Z1vL42E -m5xNOGXoxpjq8NygSuVDhQ/bZUnmHLmvv9MXe9Ob52rlzea/YafLpOeNGSA1aCxm -Fx0lcoXWp4xpoyJn9St7vmH3t7quBKdqt54zwcuHjhgZPSR6RikodKHtsl8TAgMB -AAGjZjBkMB0GA1UdDgQWBBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjAfBgNVHSMEGDAW -gBSMsBNO3UGBteMZOTq5fDPG2aPx8DASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1Ud -DwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEA0hJCtrCI9Mf47+y3pvsTjiaT -TZwpNE0cdxHjpcCHJWX2jmGbqqA6wvQ7yy4PFarmnFOoW3hQFeiLgpz9X86YTRzF -8dj3Q2MKXf6i6/iW+Y96GFqurshKp7wV25wfzWwLXcVCiM1xYPWYSyGsZAGotu4M -c7uolVABjJu5nci9mBxVmaYV5oT1mxrvq3dCPm2AvmVFNWPNRbMSAuT5B7FUDvWG -xvd1aDFduqL0iLAcrTifMIYI3XL4pBSIlL78dgY45WL6616EF3mHhW/Y4k4PNq1I -Fz08Q3y99ilhzeAci1jv6KBVHiALZNFFtYjd10KX95qnF9SomBjCTPsQ9PTC5g== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID2zCCAsOgAwIBAgIUdxRGMrv1ONRI/dJXUHa3isjVXuwwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEVMBMGA1UE -AwwMcm9vdGNhLmFkbWluMR8wHQYJKoZIhvcNAQkBFhByb290Y2FAYWRtaW4uY29t -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Bqlx+ngRZfWjktX6urJ -Tpg2//n3uMW8Gp55SVq+lbpkNXuA3W0uTlom/2fsnzmMmBAJUvv/NaLcFSN++0/d -fYGVhogxjlRoyaXa4sdFPg2ocM+lCHLGhWpYuVdTWPknikTOVNbh9z94SLRfrP8N -kuxGNZNktnVm+QJmrCLmjvnPvQlcP/WqFYehFM26NaRzswOhLLRU1YK8aVlh9Vto -PGSTPtZK60XwTfyCj/zEvHqpbGbKJPp7W0rpxl7ehPQIzGQt5IMH3zKrqLm4/pt+ -XFEVqy724sstthS73bvXguWxBLOzuMze8CbI6SJpcHaH8HO5pfFMPyE5N5l2x9Co -uQIDAQABo1MwUTAdBgNVHQ4EFgQUjLATTt1BgbXjGTk6uXwzxtmj8fAwHwYDVR0j -BBgwFoAUjLATTt1BgbXjGTk6uXwzxtmj8fAwDwYDVR0TAQH/BAUwAwEB/zANBgkq -hkiG9w0BAQsFAAOCAQEAL9/7YtxIbuTt7dgH1KOMb/y6P3Qb79fTVZTx073E2ZBj -OfSuMGUhKC30LWQXCQEsY4WpugJ4cS0NsArSYsO5XWguqgjYlWL6poOmYhGvj7ou -oi44oYqcPm6EbvLrAKdI67bkbSPLzgs+6TrxTyXHzPKFBqGBnuHaFKjgb4hGiPmd -JWKrh3fbTtuLjqSHd0Gey2uBNFql4LhPutdsCUhSOD3BibAeTbXyln70AgpTCoWT -9Qr9Ux6HYIiAHSDBzzuN8EcrlnAm9RO4zRyUVwxDsHoNsOUwybn50IM66p83HuiY -h/iX5uNjaFAADfRR4rQOwb6MLreVH2XYUSiz4M/ijg== ------END CERTIFICATE----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator2/truststore.jks b/acceptance-tests/tests/src/test/resources/pki-certs/validator2/truststore.jks deleted file mode 100644 index fb45ce73051..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator2/truststore.jks and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator3/crl.pem b/acceptance-tests/tests/src/test/resources/pki-certs/validator3/crl.pem deleted file mode 100644 index 8ced006c911..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/validator3/crl.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN X509 CRL----- -MIICBDCB7QIBATANBgkqhkiG9w0BAQsFADCBgTELMAkGA1UEBhMCVVMxCzAJBgNV -BAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJBgNVBAoMAk1DMQ0wCwYDVQQLDARyb290 -MRMwEQYDVQQDDApwYXJ0bmVyYWNhMSYwJAYJKoZIhvcNAQkBFhdwYXJ0bmVyYWNh -QHBhcnRuZXJhLmNvbRcNMjEwNzA5MTkxMjM1WhcNMjIwNzA5MTkxMjM1WjAnMCUC -FF9rJlU9U6JdFIeK/xRojaoxHdc5Fw0yMTA3MDkxOTEyMzVaoA4wDDAKBgNVHRQE -AwIBADANBgkqhkiG9w0BAQsFAAOCAQEAfByR//FGHSsVQbaS51d59o82XocOGnnT -p1hjceqtLGv3bhiebVrsRCOB5TsvE/r2IbB/yHYTe3+LJisIUqBxblQ6xK6IM+qA -3fY646YnPT5pvdZAPZ2BCN/xP3xqGffFKapQ9cz0/36YE3vaEoUDlC2VHK0OXI0t -4CLwAmiptUT2GW4Bk1RtokAsFiUNwNIOlRX5bywUNwkG7EuitR90QSGH3l/vyii2 -0c1Fm9He9MskwipjXpJKKb+t+m1pdpOVkSjRfjmVqi4BZwWlnQjELSLywhJ+WZG2 -Z1NgRjzPXotFKK+YD97Kx1L260A1eUZ46zSq73oUZr0EDZRnNJTr6A== ------END X509 CRL----- ------BEGIN X509 CRL----- -MIICLDCCARQCAQEwDQYJKoZIhvcNAQELBQAwgYExCzAJBgNVBAYTAlVTMQswCQYD -VQQIDAJDQTEMMAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9v -dDETMBEGA1UEAwwKcGFydG5lcmJjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmJj -YUBwYXJ0bmVyYi5jb20XDTIxMDcwOTE5MTIzNVoXDTIyMDcwOTE5MTIzNVowTjAl -AhQTZhDgbKuvX7iLRUBBTiWXBPKM1xcNMjEwNzA5MTkxMjM1WjAlAhRfayZVPVOi -XRSHiv8UaI2qMR3XORcNMjEwNzA5MTkxMjM1WqAOMAwwCgYDVR0UBAMCAQEwDQYJ -KoZIhvcNAQELBQADggEBAMIltmJ036f1BmK/baISJTZTu7PKZgSZMNORnpFT8KvC -s2GNRor5bGp5qvD6LHvsx92YVppCC6xd/beCFBtdyYifqw5xtOvqLQKuqCfxruLz -EqYjKXE/3v8VdyU71J7kFqi0U0Gy4/h/YCL92e5KNbATlmcn5ToyI2EBIEfBfV08 -mm7FBXvbHRzqhfrnCNEjBWBWz3zkJMc9Rib26eCCofYIDkY2HvYSN78YgrnMmD6O -hWOXrPoxArxvmDr5rG4vCadqbQYRkkCAOP0hBeMiB0SAcO2W2LNNAmHWXX7FvU3n -ZRZUX31WoVjhNeEQtNBb2mPYBXHQzLC66qYm1p97afc= ------END X509 CRL----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator3/keys.p12 b/acceptance-tests/tests/src/test/resources/pki-certs/validator3/keys.p12 deleted file mode 100644 index b36ede08e5a..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator3/keys.p12 and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator3/keystore.jks b/acceptance-tests/tests/src/test/resources/pki-certs/validator3/keystore.jks deleted file mode 100644 index 228bb46ce74..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator3/keystore.jks and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator3/nss.cfg b/acceptance-tests/tests/src/test/resources/pki-certs/validator3/nss.cfg deleted file mode 100644 index d2c68dae6c4..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/validator3/nss.cfg +++ /dev/null @@ -1,6 +0,0 @@ - -name = NSScrypto-validator3 -nssSecmodDirectory = ./src/test/resources/pki-certs/validator3/nssdb -nssDbMode = readOnly -nssModule = keystore - diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator3/nssdb/cert8.db b/acceptance-tests/tests/src/test/resources/pki-certs/validator3/nssdb/cert8.db deleted file mode 100644 index cbfa00c240b..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator3/nssdb/cert8.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator3/nssdb/key3.db b/acceptance-tests/tests/src/test/resources/pki-certs/validator3/nssdb/key3.db deleted file mode 100644 index 6414460a69c..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator3/nssdb/key3.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator3/nssdb/secmod.db b/acceptance-tests/tests/src/test/resources/pki-certs/validator3/nssdb/secmod.db deleted file mode 100644 index 75bd6e3d96d..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator3/nssdb/secmod.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator3/nsspin.txt b/acceptance-tests/tests/src/test/resources/pki-certs/validator3/nsspin.txt deleted file mode 100644 index 5271a526801..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/validator3/nsspin.txt +++ /dev/null @@ -1 +0,0 @@ -test123 diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator3/ssl-ca.pem b/acceptance-tests/tests/src/test/resources/pki-certs/validator3/ssl-ca.pem deleted file mode 100644 index a5fdc343f56..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/validator3/ssl-ca.pem +++ /dev/null @@ -1,70 +0,0 @@ ------BEGIN CERTIFICATE----- -MIID8DCCAtigAwIBAgIUcATI/N49JsZyAuyS8gfW6BppdBMwDQYJKoZIhvcNAQEL -BQAweDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRAwDgYDVQQDDAdpbnRlcmNhMSAwHgYJ -KoZIhvcNAQkBFhFpbnRlcmNhQGFkbWluLmNvbTAgFw0yMTA3MDkxOTEyMThaGA8y -MTIxMDYxNTE5MTIxOFowgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoG -A1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwK -cGFydG5lcmNjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmNjYUBwYXJ0bmVyYy5j -b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+31Xz+vdtagGGF4Zs -JPzLNkPRWUZ2aB2nB6O3GFFItRERIeKQQ809jpuWb+3nPwmHgAeu/5KDuHExXUp3 -zsq/pBhjmIxysN/tSB0ZWgnDZj0T/O1cFxmFPSIQi2YqN9ex/k1P0A/bPLYid6sp -BrMB2oDuemjG9OL8zwWxTWFstwMZgh+PUoLv4xPt3vnv8hiBvMxIRe2IjseFRvR9 -zoaxAW94oeLPngPgWanUZvU+hL0BR2h0RZbsmPciw4K7PzHKCfOnDcJN3/k32GuQ -1ETYXVqBZstMwZ/yUAZzHDUFuungfM/VyuVVqjV1Njmi1ulrZNaLj3pe+x0WwrXq -VbgfAgMBAAGjZjBkMB0GA1UdDgQWBBRoOd/JgcA/UZW816OxlkI45xhLJDAfBgNV -HSMEGDAWgBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjASBgNVHRMBAf8ECDAGAQH/AgEA -MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEAbIUXWJ1SU2cwnGfJ -gor9Y3QX8q3bLRBQ6nuVXUhyHtOKnecEYY+6cvTgAy4tcaEbTrmXnIwwddzWXR5c -IEgpem6t9tJqjvDZ3SMmlIf57L/rKp1rQQXmOBm2fBy86zC6RChYMshf9LQ3pCeZ -kESZ+WzHqmTDe2vpe3AaZTsGy+HucyVv5Ha5WLATFMPSMbY76sMbFDEr8viskCNm -JzDvYbqHMfZUJu4eiWiRJ8v6GY1HJ8SJrFUz2ry6WNYiizoEABHC8VrWV+Kss1Vq -MiFfAZND4wBS5TZmaKi4BQt/+cnUAX0ej6wYSug/+Atz9DSu/b+AsEZ5tXnxDMGO -vqwj8g== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID6jCCAtKgAwIBAgIUYdazVD+VnI7jBu7xLaW+npfwHJIwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEQMA4GA1UE -AwwHaW50ZXJjYTEgMB4GCSqGSIb3DQEJARYRaW50ZXJjYUBhZG1pbi5jb20wggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ8QgVwEjy3CgJFvET7tYNBw97 -4i33EHQT4ZuayGCJ+ADY3ZFpsw2M1IPlbAguqfBkcLd8TAjWNRAdsm9ubGlIcTZr -7LNle3gvc7qEP4qg0i6M7D06CDqtBaIJ1PMTJchOouGU9ntBe+h0qg8tzpiqJdIw -jIOPRWW98Hw9KgF6++2jtlcOW1IxiFSWqf0Mpc81qKukcxnsHjvdxmBp/Z1vL42E -m5xNOGXoxpjq8NygSuVDhQ/bZUnmHLmvv9MXe9Ob52rlzea/YafLpOeNGSA1aCxm -Fx0lcoXWp4xpoyJn9St7vmH3t7quBKdqt54zwcuHjhgZPSR6RikodKHtsl8TAgMB -AAGjZjBkMB0GA1UdDgQWBBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjAfBgNVHSMEGDAW -gBSMsBNO3UGBteMZOTq5fDPG2aPx8DASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1Ud -DwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEA0hJCtrCI9Mf47+y3pvsTjiaT -TZwpNE0cdxHjpcCHJWX2jmGbqqA6wvQ7yy4PFarmnFOoW3hQFeiLgpz9X86YTRzF -8dj3Q2MKXf6i6/iW+Y96GFqurshKp7wV25wfzWwLXcVCiM1xYPWYSyGsZAGotu4M -c7uolVABjJu5nci9mBxVmaYV5oT1mxrvq3dCPm2AvmVFNWPNRbMSAuT5B7FUDvWG -xvd1aDFduqL0iLAcrTifMIYI3XL4pBSIlL78dgY45WL6616EF3mHhW/Y4k4PNq1I -Fz08Q3y99ilhzeAci1jv6KBVHiALZNFFtYjd10KX95qnF9SomBjCTPsQ9PTC5g== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID2zCCAsOgAwIBAgIUdxRGMrv1ONRI/dJXUHa3isjVXuwwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEVMBMGA1UE -AwwMcm9vdGNhLmFkbWluMR8wHQYJKoZIhvcNAQkBFhByb290Y2FAYWRtaW4uY29t -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Bqlx+ngRZfWjktX6urJ -Tpg2//n3uMW8Gp55SVq+lbpkNXuA3W0uTlom/2fsnzmMmBAJUvv/NaLcFSN++0/d -fYGVhogxjlRoyaXa4sdFPg2ocM+lCHLGhWpYuVdTWPknikTOVNbh9z94SLRfrP8N -kuxGNZNktnVm+QJmrCLmjvnPvQlcP/WqFYehFM26NaRzswOhLLRU1YK8aVlh9Vto -PGSTPtZK60XwTfyCj/zEvHqpbGbKJPp7W0rpxl7ehPQIzGQt5IMH3zKrqLm4/pt+ -XFEVqy724sstthS73bvXguWxBLOzuMze8CbI6SJpcHaH8HO5pfFMPyE5N5l2x9Co -uQIDAQABo1MwUTAdBgNVHQ4EFgQUjLATTt1BgbXjGTk6uXwzxtmj8fAwHwYDVR0j -BBgwFoAUjLATTt1BgbXjGTk6uXwzxtmj8fAwDwYDVR0TAQH/BAUwAwEB/zANBgkq -hkiG9w0BAQsFAAOCAQEAL9/7YtxIbuTt7dgH1KOMb/y6P3Qb79fTVZTx073E2ZBj -OfSuMGUhKC30LWQXCQEsY4WpugJ4cS0NsArSYsO5XWguqgjYlWL6poOmYhGvj7ou -oi44oYqcPm6EbvLrAKdI67bkbSPLzgs+6TrxTyXHzPKFBqGBnuHaFKjgb4hGiPmd -JWKrh3fbTtuLjqSHd0Gey2uBNFql4LhPutdsCUhSOD3BibAeTbXyln70AgpTCoWT -9Qr9Ux6HYIiAHSDBzzuN8EcrlnAm9RO4zRyUVwxDsHoNsOUwybn50IM66p83HuiY -h/iX5uNjaFAADfRR4rQOwb6MLreVH2XYUSiz4M/ijg== ------END CERTIFICATE----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator3/ssl.pem b/acceptance-tests/tests/src/test/resources/pki-certs/validator3/ssl.pem deleted file mode 100644 index 474187b0e21..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/validator3/ssl.pem +++ /dev/null @@ -1,126 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDW0t2JaCSa2iXy -7Njgdb7vpTBUxYV44tueVscVtVEAKbe9HN0iOro3cmHymmujuxmqXJA4bHK8OqeX -fsWoyhnK17aRXhRGUNHcd7OTLSsmWgLsTCQ3syfYrC1G8Dbgggdw7gp/KXDfHRLc -WZY/1H2JSH0QJwTyiNPE1Jhz7Dw2YHlTE5iOC95g02ABXI7LLiB1DvTkjwkBT9bk -Bcokx8OgX/aUScV4OJsMcog+zf2bic/lcU/SI/wPSu95EGmLB/0PER8TOCbp+GTk -Q1CiIkL1ZSjW6JtRwGhPoc3JGhKED5P+y1t6LHdE3wBbX37AfcPJqd4vMErlkn98 -1spNvckHAgMBAAECggEBALhlG5/ccr8wzS4tuyNALnqJsmixexX+tEC/f/fUQQkA -xiRf6Dh/bGQ3nHefsRFDgo5HEcw82mdTeZOCT6vIfkcszhsNRB3vNZQmc0+7j7RF -wwiNevEMqQFFGUAw5Qnk0vpOeVgZfDcnP49AnsV/YZo7TI5Wz9pXNF9ZlNq8Rhrt -NYTF92vzWfrCskQnYL2Tn29qUTWIvsqq6wt7oR0NKwTfbm4420ZGbpAKgtVWsnXN -13nRhAgB7aNEAAXn9FsRjn/aVaOLWv8rfL7AnWx/DREMOBOKNUIW7mkT4xwNFrnl -kVS4mjnMRVtMG+/xK781E9voJ+2GYXiCE3ldEY1ZhDECgYEA9I7ej9ZWYLwnf6jS -IsRDWLwqLsy5upHv88zeqGTNR/kiO8On5+GMaFT93II53TQk+gLBTdmUYR1jYdq+ -raR9nOSVjV35kE747NZIhqRGkMCJg3qLLF7haK5bvynAD60dIpme6IiDEKLjgPmr -k2CQo0rPIASed+vXpdYVYGVsaMUCgYEA4N/cLv/Mi59tAdFNUaxyIAVgFD++bG1r -RkJmhYcCqj563kIRhqd+YtWhtedeBMTrmMBztd/1p1qjUmnB/drhvl60J+4WoULR -1YSA1hLOz25iGTBEPJH05115hD4OC7o2R88NdC4TYizIqzSbyuYIw3kTLGDzO9b9 -wYaR4+TdD1sCgYEAo8a985O1uwimZw6200OeWmGA2mCODQzIpfvQLPN8qjOEooNw -AZEBx6kLbU3k1GekT63iqHEKtlLJCHJGeeELojNOmCKUC/lnKKyEW27ONwhJzzwU -0f0kdUc29/lMvvLO7Sn1yoIJS+3Z77jEF2l203Q465dMTMErZMrO/ARQKoUCgYBW -n0T0wcpsGjxmT9al2HSZyxhIlIlowPlqthdB5lDrfWfdB7wyj4CK19oT9Qztb2GF -lNS9wYiiL7LsAxscC12b9GUXYIpNEi08P29VhyctFkspgsM2Rc799K7jdvf4IoHY -RXTcRQdocPHXqUs97ECB87ikBrCLY3SaQJkgHT/eMwKBgGRqRCW3J+DHaGYVrfFh -jTtVnMAw+eNMrfEzbueav8XYFDA035lHANMaW1ImCEnGcNxTKTNHYlEI+GJGlf3j -nLyFxHt2t7t3EH+Z1TRBpTWcI9NIBA9suGSwHF6JSYYoP/XycknvHR3CKnptVqEh -2BvRskmyqDqlJ3or8iilBVG4 ------END PRIVATE KEY----- ------BEGIN CERTIFICATE----- -MIIEvjCCA6agAwIBAgIUeHwvCYkMzLzluPyGlFa5V/lmFnowDQYJKoZIhvcNAQEL -BQAwgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDU0ZPMQsw -CQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwKcGFydG5lcmNjYTEm -MCQGCSqGSIb3DQEJARYXcGFydG5lcmNjYUBwYXJ0bmVyYy5jb20wIBcNMjEwNzA5 -MTkxMjE4WhgPMjEyMTA2MTUxOTEyMThaMIGBMQswCQYDVQQGEwJVUzELMAkGA1UE -CAwCQ0ExDDAKBgNVBAcMA1NGTzELMAkGA1UECgwCTUMxDTALBgNVBAsMBHJvb3Qx -EzARBgNVBAMMCnZhbGlkYXRvcjMxJjAkBgkqhkiG9w0BCQEWF3ZhbGlkYXRvcjNA -cGFydG5lcmMuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1tLd -iWgkmtol8uzY4HW+76UwVMWFeOLbnlbHFbVRACm3vRzdIjq6N3Jh8ppro7sZqlyQ -OGxyvDqnl37FqMoZyte2kV4URlDR3Hezky0rJloC7EwkN7Mn2KwtRvA24IIHcO4K -fylw3x0S3FmWP9R9iUh9ECcE8ojTxNSYc+w8NmB5UxOYjgveYNNgAVyOyy4gdQ70 -5I8JAU/W5AXKJMfDoF/2lEnFeDibDHKIPs39m4nP5XFP0iP8D0rveRBpiwf9DxEf -Ezgm6fhk5ENQoiJC9WUo1uibUcBoT6HNyRoShA+T/stbeix3RN8AW19+wH3Dyane -LzBK5ZJ/fNbKTb3JBwIDAQABo4IBKDCCASQwEQYJYIZIAYb4QgEBBAQDAgbAMB0G -A1UdDgQWBBSl7BsGlNKcg8bg/9K/Pt6beFmUbDAJBgNVHRMEAjAAMA4GA1UdDwEB -/wQEAwIF4DAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwgbUGA1UdIwSB -rTCBqoAUaDnfyYHAP1GVvNejsZZCOOcYSyShfKR6MHgxCzAJBgNVBAYTAlVTMQsw -CQYDVQQIDAJDQTEMMAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwE -cm9vdDEQMA4GA1UEAwwHaW50ZXJjYTEgMB4GCSqGSIb3DQEJARYRaW50ZXJjYUBh -ZG1pbi5jb22CFHAEyPzePSbGcgLskvIH1ugaaXQTMA0GCSqGSIb3DQEBCwUAA4IB -AQAM49KuL0GR6f9O6bS8YkPNcNcOP3y5AbGzV08boCHcnL0iJmSyUoYwD4ksEj+Z -41YcBOGtPE++MR27LvpMXV6wMoJvmg4G97M1Oql+fs+eZnKyWRuQSWoCLUpsh5Cq -j9QUHv95xUyadH3HSQnDHd2Khhsu60MEqF6wuyU+EsjoNp5Ejsmk0DDWobXPwA8K -WaqyleZOjA52HeRwAeextO9Zvv4yJX/I/DeGf3LTPz8IAE0rLXROpEj31rX0CeKA -BPiBVVn0qRTeimjm9G22AUwKKBNCta2plMkbq2shHAKTXWzI72aW6kHHG35zdXWV -qnQZrcVShlqsexiZAGRfKqsb ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID8DCCAtigAwIBAgIUcATI/N49JsZyAuyS8gfW6BppdBMwDQYJKoZIhvcNAQEL -BQAweDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRAwDgYDVQQDDAdpbnRlcmNhMSAwHgYJ -KoZIhvcNAQkBFhFpbnRlcmNhQGFkbWluLmNvbTAgFw0yMTA3MDkxOTEyMThaGA8y -MTIxMDYxNTE5MTIxOFowgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoG -A1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwK -cGFydG5lcmNjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmNjYUBwYXJ0bmVyYy5j -b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+31Xz+vdtagGGF4Zs -JPzLNkPRWUZ2aB2nB6O3GFFItRERIeKQQ809jpuWb+3nPwmHgAeu/5KDuHExXUp3 -zsq/pBhjmIxysN/tSB0ZWgnDZj0T/O1cFxmFPSIQi2YqN9ex/k1P0A/bPLYid6sp -BrMB2oDuemjG9OL8zwWxTWFstwMZgh+PUoLv4xPt3vnv8hiBvMxIRe2IjseFRvR9 -zoaxAW94oeLPngPgWanUZvU+hL0BR2h0RZbsmPciw4K7PzHKCfOnDcJN3/k32GuQ -1ETYXVqBZstMwZ/yUAZzHDUFuungfM/VyuVVqjV1Njmi1ulrZNaLj3pe+x0WwrXq -VbgfAgMBAAGjZjBkMB0GA1UdDgQWBBRoOd/JgcA/UZW816OxlkI45xhLJDAfBgNV -HSMEGDAWgBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjASBgNVHRMBAf8ECDAGAQH/AgEA -MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEAbIUXWJ1SU2cwnGfJ -gor9Y3QX8q3bLRBQ6nuVXUhyHtOKnecEYY+6cvTgAy4tcaEbTrmXnIwwddzWXR5c -IEgpem6t9tJqjvDZ3SMmlIf57L/rKp1rQQXmOBm2fBy86zC6RChYMshf9LQ3pCeZ -kESZ+WzHqmTDe2vpe3AaZTsGy+HucyVv5Ha5WLATFMPSMbY76sMbFDEr8viskCNm -JzDvYbqHMfZUJu4eiWiRJ8v6GY1HJ8SJrFUz2ry6WNYiizoEABHC8VrWV+Kss1Vq -MiFfAZND4wBS5TZmaKi4BQt/+cnUAX0ej6wYSug/+Atz9DSu/b+AsEZ5tXnxDMGO -vqwj8g== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID6jCCAtKgAwIBAgIUYdazVD+VnI7jBu7xLaW+npfwHJIwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEQMA4GA1UE -AwwHaW50ZXJjYTEgMB4GCSqGSIb3DQEJARYRaW50ZXJjYUBhZG1pbi5jb20wggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ8QgVwEjy3CgJFvET7tYNBw97 -4i33EHQT4ZuayGCJ+ADY3ZFpsw2M1IPlbAguqfBkcLd8TAjWNRAdsm9ubGlIcTZr -7LNle3gvc7qEP4qg0i6M7D06CDqtBaIJ1PMTJchOouGU9ntBe+h0qg8tzpiqJdIw -jIOPRWW98Hw9KgF6++2jtlcOW1IxiFSWqf0Mpc81qKukcxnsHjvdxmBp/Z1vL42E -m5xNOGXoxpjq8NygSuVDhQ/bZUnmHLmvv9MXe9Ob52rlzea/YafLpOeNGSA1aCxm -Fx0lcoXWp4xpoyJn9St7vmH3t7quBKdqt54zwcuHjhgZPSR6RikodKHtsl8TAgMB -AAGjZjBkMB0GA1UdDgQWBBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjAfBgNVHSMEGDAW -gBSMsBNO3UGBteMZOTq5fDPG2aPx8DASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1Ud -DwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEA0hJCtrCI9Mf47+y3pvsTjiaT -TZwpNE0cdxHjpcCHJWX2jmGbqqA6wvQ7yy4PFarmnFOoW3hQFeiLgpz9X86YTRzF -8dj3Q2MKXf6i6/iW+Y96GFqurshKp7wV25wfzWwLXcVCiM1xYPWYSyGsZAGotu4M -c7uolVABjJu5nci9mBxVmaYV5oT1mxrvq3dCPm2AvmVFNWPNRbMSAuT5B7FUDvWG -xvd1aDFduqL0iLAcrTifMIYI3XL4pBSIlL78dgY45WL6616EF3mHhW/Y4k4PNq1I -Fz08Q3y99ilhzeAci1jv6KBVHiALZNFFtYjd10KX95qnF9SomBjCTPsQ9PTC5g== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID2zCCAsOgAwIBAgIUdxRGMrv1ONRI/dJXUHa3isjVXuwwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEVMBMGA1UE -AwwMcm9vdGNhLmFkbWluMR8wHQYJKoZIhvcNAQkBFhByb290Y2FAYWRtaW4uY29t -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Bqlx+ngRZfWjktX6urJ -Tpg2//n3uMW8Gp55SVq+lbpkNXuA3W0uTlom/2fsnzmMmBAJUvv/NaLcFSN++0/d -fYGVhogxjlRoyaXa4sdFPg2ocM+lCHLGhWpYuVdTWPknikTOVNbh9z94SLRfrP8N -kuxGNZNktnVm+QJmrCLmjvnPvQlcP/WqFYehFM26NaRzswOhLLRU1YK8aVlh9Vto -PGSTPtZK60XwTfyCj/zEvHqpbGbKJPp7W0rpxl7ehPQIzGQt5IMH3zKrqLm4/pt+ -XFEVqy724sstthS73bvXguWxBLOzuMze8CbI6SJpcHaH8HO5pfFMPyE5N5l2x9Co -uQIDAQABo1MwUTAdBgNVHQ4EFgQUjLATTt1BgbXjGTk6uXwzxtmj8fAwHwYDVR0j -BBgwFoAUjLATTt1BgbXjGTk6uXwzxtmj8fAwDwYDVR0TAQH/BAUwAwEB/zANBgkq -hkiG9w0BAQsFAAOCAQEAL9/7YtxIbuTt7dgH1KOMb/y6P3Qb79fTVZTx073E2ZBj -OfSuMGUhKC30LWQXCQEsY4WpugJ4cS0NsArSYsO5XWguqgjYlWL6poOmYhGvj7ou -oi44oYqcPm6EbvLrAKdI67bkbSPLzgs+6TrxTyXHzPKFBqGBnuHaFKjgb4hGiPmd -JWKrh3fbTtuLjqSHd0Gey2uBNFql4LhPutdsCUhSOD3BibAeTbXyln70AgpTCoWT -9Qr9Ux6HYIiAHSDBzzuN8EcrlnAm9RO4zRyUVwxDsHoNsOUwybn50IM66p83HuiY -h/iX5uNjaFAADfRR4rQOwb6MLreVH2XYUSiz4M/ijg== ------END CERTIFICATE----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator3/truststore.jks b/acceptance-tests/tests/src/test/resources/pki-certs/validator3/truststore.jks deleted file mode 100644 index 7acd9015b86..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator3/truststore.jks and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator4/crl.pem b/acceptance-tests/tests/src/test/resources/pki-certs/validator4/crl.pem deleted file mode 100644 index 8ced006c911..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/validator4/crl.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN X509 CRL----- -MIICBDCB7QIBATANBgkqhkiG9w0BAQsFADCBgTELMAkGA1UEBhMCVVMxCzAJBgNV -BAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJBgNVBAoMAk1DMQ0wCwYDVQQLDARyb290 -MRMwEQYDVQQDDApwYXJ0bmVyYWNhMSYwJAYJKoZIhvcNAQkBFhdwYXJ0bmVyYWNh -QHBhcnRuZXJhLmNvbRcNMjEwNzA5MTkxMjM1WhcNMjIwNzA5MTkxMjM1WjAnMCUC -FF9rJlU9U6JdFIeK/xRojaoxHdc5Fw0yMTA3MDkxOTEyMzVaoA4wDDAKBgNVHRQE -AwIBADANBgkqhkiG9w0BAQsFAAOCAQEAfByR//FGHSsVQbaS51d59o82XocOGnnT -p1hjceqtLGv3bhiebVrsRCOB5TsvE/r2IbB/yHYTe3+LJisIUqBxblQ6xK6IM+qA -3fY646YnPT5pvdZAPZ2BCN/xP3xqGffFKapQ9cz0/36YE3vaEoUDlC2VHK0OXI0t -4CLwAmiptUT2GW4Bk1RtokAsFiUNwNIOlRX5bywUNwkG7EuitR90QSGH3l/vyii2 -0c1Fm9He9MskwipjXpJKKb+t+m1pdpOVkSjRfjmVqi4BZwWlnQjELSLywhJ+WZG2 -Z1NgRjzPXotFKK+YD97Kx1L260A1eUZ46zSq73oUZr0EDZRnNJTr6A== ------END X509 CRL----- ------BEGIN X509 CRL----- -MIICLDCCARQCAQEwDQYJKoZIhvcNAQELBQAwgYExCzAJBgNVBAYTAlVTMQswCQYD -VQQIDAJDQTEMMAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9v -dDETMBEGA1UEAwwKcGFydG5lcmJjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmJj -YUBwYXJ0bmVyYi5jb20XDTIxMDcwOTE5MTIzNVoXDTIyMDcwOTE5MTIzNVowTjAl -AhQTZhDgbKuvX7iLRUBBTiWXBPKM1xcNMjEwNzA5MTkxMjM1WjAlAhRfayZVPVOi -XRSHiv8UaI2qMR3XORcNMjEwNzA5MTkxMjM1WqAOMAwwCgYDVR0UBAMCAQEwDQYJ -KoZIhvcNAQELBQADggEBAMIltmJ036f1BmK/baISJTZTu7PKZgSZMNORnpFT8KvC -s2GNRor5bGp5qvD6LHvsx92YVppCC6xd/beCFBtdyYifqw5xtOvqLQKuqCfxruLz -EqYjKXE/3v8VdyU71J7kFqi0U0Gy4/h/YCL92e5KNbATlmcn5ToyI2EBIEfBfV08 -mm7FBXvbHRzqhfrnCNEjBWBWz3zkJMc9Rib26eCCofYIDkY2HvYSN78YgrnMmD6O -hWOXrPoxArxvmDr5rG4vCadqbQYRkkCAOP0hBeMiB0SAcO2W2LNNAmHWXX7FvU3n -ZRZUX31WoVjhNeEQtNBb2mPYBXHQzLC66qYm1p97afc= ------END X509 CRL----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator4/keys.p12 b/acceptance-tests/tests/src/test/resources/pki-certs/validator4/keys.p12 deleted file mode 100644 index 70373b93084..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator4/keys.p12 and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator4/keystore.jks b/acceptance-tests/tests/src/test/resources/pki-certs/validator4/keystore.jks deleted file mode 100644 index ef941f664e3..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator4/keystore.jks and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator4/nss.cfg b/acceptance-tests/tests/src/test/resources/pki-certs/validator4/nss.cfg deleted file mode 100644 index c114f78b09d..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/validator4/nss.cfg +++ /dev/null @@ -1,6 +0,0 @@ - -name = NSScrypto-validator4 -nssSecmodDirectory = ./src/test/resources/pki-certs/validator4/nssdb -nssDbMode = readOnly -nssModule = keystore - diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator4/nssdb/cert8.db b/acceptance-tests/tests/src/test/resources/pki-certs/validator4/nssdb/cert8.db deleted file mode 100644 index 7c94b589ac7..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator4/nssdb/cert8.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator4/nssdb/key3.db b/acceptance-tests/tests/src/test/resources/pki-certs/validator4/nssdb/key3.db deleted file mode 100644 index 0b60173dbf9..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator4/nssdb/key3.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator4/nssdb/secmod.db b/acceptance-tests/tests/src/test/resources/pki-certs/validator4/nssdb/secmod.db deleted file mode 100644 index 346dc33e691..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator4/nssdb/secmod.db and /dev/null differ diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator4/nsspin.txt b/acceptance-tests/tests/src/test/resources/pki-certs/validator4/nsspin.txt deleted file mode 100644 index 5271a526801..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/validator4/nsspin.txt +++ /dev/null @@ -1 +0,0 @@ -test123 diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator4/ssl-ca.pem b/acceptance-tests/tests/src/test/resources/pki-certs/validator4/ssl-ca.pem deleted file mode 100644 index 8a3ea186f1c..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/validator4/ssl-ca.pem +++ /dev/null @@ -1,70 +0,0 @@ ------BEGIN CERTIFICATE----- -MIID8DCCAtigAwIBAgIUcATI/N49JsZyAuyS8gfW6BppdBQwDQYJKoZIhvcNAQEL -BQAweDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRAwDgYDVQQDDAdpbnRlcmNhMSAwHgYJ -KoZIhvcNAQkBFhFpbnRlcmNhQGFkbWluLmNvbTAgFw0yMTA3MDkxOTEyMjRaGA8y -MTIxMDYxNTE5MTIyNFowgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoG -A1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwK -cGFydG5lcmRjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmRjYUBwYXJ0bmVyZC5j -b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCbWjqued8UEhetS67V -pNCkbCBjdeSXyjZgEIxMqmPILY6oymkClkty1ktxMYkEKcDi44+oCq93MIQOAkHx -qRGlu+z9Eql/aEJzVu85gIfa+64/GJ5OgGIqfI0XOaETu1MGhZN3P7Kb38tmqX7l -ppP/lfADvdlA/C+SY21Y2OMmeXo8VUtrW5YcUZ2OHt6esFZHCanI7FGYi7A71l/R -3ZXX1BVvHYJlssY5GwBLT3Yng6sshMihhXviU4f+RBEUm1pSRG/P+0RSjQVyp3bW -EiwXHu0WD9j7Kjgesrz7J2mFTgjNEQQ6lQioN6ZCrboM6WbxWM2zzSWCPSV/ON1W -QUVTAgMBAAGjZjBkMB0GA1UdDgQWBBRdtro7rYf1BatcNzBHlkpYj4sW7jAfBgNV -HSMEGDAWgBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjASBgNVHRMBAf8ECDAGAQH/AgEA -MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEAJd86d928wxKO8C6+ -VksOT7G2RcYNk9ok90bcAJdj6zAhEM4h5XZPomHYibKvdmGD1IH2TlzX+P4+Z2tc -mHOkpHCa99vwGlIeb+objfR5fYrZDPlFMfYdzeesXhlmZ5j9bzxMUEAMGVCZOm99 -Gq+uHoj1+bPaLkyaFfETchhlXw6EYXoM5bkdXGZMnXvL3DWWvPauA53tUiRh++Q7 -bPCSbmT9YooVbjqyT2IGzIWXzU0O6u5H4PHMNs+DCiGcoTqvC9uZDcU0l+4g6UV7 -v83YnAhDr+Ed/WJ8EpfvbxmNFu5d/SCmmzJnv2sBn27//q4xUNxV9OaNkbxzVOwd -cQyzwQ== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID6jCCAtKgAwIBAgIUYdazVD+VnI7jBu7xLaW+npfwHJIwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEQMA4GA1UE -AwwHaW50ZXJjYTEgMB4GCSqGSIb3DQEJARYRaW50ZXJjYUBhZG1pbi5jb20wggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ8QgVwEjy3CgJFvET7tYNBw97 -4i33EHQT4ZuayGCJ+ADY3ZFpsw2M1IPlbAguqfBkcLd8TAjWNRAdsm9ubGlIcTZr -7LNle3gvc7qEP4qg0i6M7D06CDqtBaIJ1PMTJchOouGU9ntBe+h0qg8tzpiqJdIw -jIOPRWW98Hw9KgF6++2jtlcOW1IxiFSWqf0Mpc81qKukcxnsHjvdxmBp/Z1vL42E -m5xNOGXoxpjq8NygSuVDhQ/bZUnmHLmvv9MXe9Ob52rlzea/YafLpOeNGSA1aCxm -Fx0lcoXWp4xpoyJn9St7vmH3t7quBKdqt54zwcuHjhgZPSR6RikodKHtsl8TAgMB -AAGjZjBkMB0GA1UdDgQWBBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjAfBgNVHSMEGDAW -gBSMsBNO3UGBteMZOTq5fDPG2aPx8DASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1Ud -DwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEA0hJCtrCI9Mf47+y3pvsTjiaT -TZwpNE0cdxHjpcCHJWX2jmGbqqA6wvQ7yy4PFarmnFOoW3hQFeiLgpz9X86YTRzF -8dj3Q2MKXf6i6/iW+Y96GFqurshKp7wV25wfzWwLXcVCiM1xYPWYSyGsZAGotu4M -c7uolVABjJu5nci9mBxVmaYV5oT1mxrvq3dCPm2AvmVFNWPNRbMSAuT5B7FUDvWG -xvd1aDFduqL0iLAcrTifMIYI3XL4pBSIlL78dgY45WL6616EF3mHhW/Y4k4PNq1I -Fz08Q3y99ilhzeAci1jv6KBVHiALZNFFtYjd10KX95qnF9SomBjCTPsQ9PTC5g== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID2zCCAsOgAwIBAgIUdxRGMrv1ONRI/dJXUHa3isjVXuwwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEVMBMGA1UE -AwwMcm9vdGNhLmFkbWluMR8wHQYJKoZIhvcNAQkBFhByb290Y2FAYWRtaW4uY29t -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Bqlx+ngRZfWjktX6urJ -Tpg2//n3uMW8Gp55SVq+lbpkNXuA3W0uTlom/2fsnzmMmBAJUvv/NaLcFSN++0/d -fYGVhogxjlRoyaXa4sdFPg2ocM+lCHLGhWpYuVdTWPknikTOVNbh9z94SLRfrP8N -kuxGNZNktnVm+QJmrCLmjvnPvQlcP/WqFYehFM26NaRzswOhLLRU1YK8aVlh9Vto -PGSTPtZK60XwTfyCj/zEvHqpbGbKJPp7W0rpxl7ehPQIzGQt5IMH3zKrqLm4/pt+ -XFEVqy724sstthS73bvXguWxBLOzuMze8CbI6SJpcHaH8HO5pfFMPyE5N5l2x9Co -uQIDAQABo1MwUTAdBgNVHQ4EFgQUjLATTt1BgbXjGTk6uXwzxtmj8fAwHwYDVR0j -BBgwFoAUjLATTt1BgbXjGTk6uXwzxtmj8fAwDwYDVR0TAQH/BAUwAwEB/zANBgkq -hkiG9w0BAQsFAAOCAQEAL9/7YtxIbuTt7dgH1KOMb/y6P3Qb79fTVZTx073E2ZBj -OfSuMGUhKC30LWQXCQEsY4WpugJ4cS0NsArSYsO5XWguqgjYlWL6poOmYhGvj7ou -oi44oYqcPm6EbvLrAKdI67bkbSPLzgs+6TrxTyXHzPKFBqGBnuHaFKjgb4hGiPmd -JWKrh3fbTtuLjqSHd0Gey2uBNFql4LhPutdsCUhSOD3BibAeTbXyln70AgpTCoWT -9Qr9Ux6HYIiAHSDBzzuN8EcrlnAm9RO4zRyUVwxDsHoNsOUwybn50IM66p83HuiY -h/iX5uNjaFAADfRR4rQOwb6MLreVH2XYUSiz4M/ijg== ------END CERTIFICATE----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator4/ssl.pem b/acceptance-tests/tests/src/test/resources/pki-certs/validator4/ssl.pem deleted file mode 100644 index 75a6df9ec29..00000000000 --- a/acceptance-tests/tests/src/test/resources/pki-certs/validator4/ssl.pem +++ /dev/null @@ -1,126 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC4R9B0DJ8/0ZNm -0dOt8wv86NvtIXFggiIol5YhOeCP2uHNDZd+B5IfigMHJKNVW5lB0pxgltSM8T8v -R8zIUV1KcBkdbG0LXd6RBu8T7MtuGpIYkPG+PNXp6VNWG5e6+3IdtFp6tsg+ht6K -qgzp4n5ILjibZRQpSPSn37vw6D2v/HHH9MXOUIraMenB+RixTM+5F/eArNOrKxtn -DEXPMTA0kb5LGQsRm4S7new8opAIGaCXtGnhFgZYvmD/GlZQmYgPVw/IaRjHy69D -6/6ogtoKwrdGJoI36IUB6Obvz/VVxl1BZ0H05PPb0warAH2gmeAIYgHQs3lMjyLw -Qy04dBIhAgMBAAECggEBAIBNV8AErbekNclnm8bHqngKtFyUZTi5QgkbCWbWwhfG -YGIA7ZHMuZsF3vfRNXsPjFO8zC2fthrvOHu1tMlfhOF3zLSksgMw9u3ocvpfan+d -NkyuVfGKr7DtR51zFqATwy8VS82WhzKpejX3EYihriSZMcZXiML5zYU4C8U+HPzy -Sq0SCbpMd+3yXHu3RKJKDXtvXwF+6Iu3Pcgir7zAP8kRqpnnb/Ve3qyzkXtZhhHg -ka2+1EfEFw8YoI/VKe81Y1hS+Ra2A+a/iYgDnqpD0C1Aa2/1Yf5zBP7oXlrQyOP/ -N9Of6yOgWWQixcl+26cnJ65JN1NlyBmZc3SJTDkMvsUCgYEA4SeeyCBNOl0ehOfw -DkifuVUetFW8BsCq2TjvLoWYAK2Atc90tNfvN7z/f5Mw47s7THLoFxyhGLMnXa9W -FSkg4baSwUrHlUyMC4bQ1BK6tsfv+zbc5xoGQr1B256wZPQ4Ab5RrJ8NgnrPqAgq -CnfDiFph1YoFG93QUmeBl+5WY0MCgYEA0YaymqLi4+a5joab67SNyEdjoPVq0RBz -jyozVOHiJCL2SMaWGpTcZieDHgaq4uBrkD81mRR+XEjBWIgxT+QK+phapHHjOy5A -8f2Knr0+Pb9n5t7+Xq0V1siDZkGDokkJqOGa83nUeY/ZMju8czoOj3CMwEEb/C8q -Gm4DMX0TdMsCgYAcyG3Z85cr0N+n6eacTNhuXt1P/G3PBy5QGqEo91cZLxT3KRAo -Vjp0i6q16mEjm7nW6hGVcsl9WNm9dj6w8dMMR8S3BRJCmciFaLSlh8dqumCJYtgW -ixMl0P/sTXsLTNUkE+ppAOQ/O+fz+Sc2s1zBv5wmKkDA8ThCcIz2mTdd9QKBgBNd -WJp0HXYkt9eVV0nEiSoOdwXESNgDpjqAEvhwW/8tAAgf2nWgdnI2iqa2xQps5WQS -g3ifbUJIMx5kj/IrSFx6wj3ajL7Gepgi77HWGa+JRigPSgEkDBWquII14d2Y1Hy4 -RSLlip6ljdRXswYPQpM9OrT2D4kSLtj8e+0R+z9bAoGBAIvnrMlmScUlshKO8Z9Y -x7Xxmtwsagv/TYuaJcizTzUOZ5VcHTrZXd9oAzmaITmw0VxOsb2wbm7OvIsV/iqr -0GmAeWBMl00dHnvqLSH1fO59THpYxBuH612OIGMdJ3qYguqzSTrRPN3C1ivTH2U8 -qy4tgRxhH9uSfnZ+H6RfL5Ph ------END PRIVATE KEY----- ------BEGIN CERTIFICATE----- -MIIEvjCCA6agAwIBAgIUBBRpdaDIsULA/tPnSkiiUQ2MPqgwDQYJKoZIhvcNAQEL -BQAwgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoGA1UEBwwDU0ZPMQsw -CQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwKcGFydG5lcmRjYTEm -MCQGCSqGSIb3DQEJARYXcGFydG5lcmRjYUBwYXJ0bmVyZC5jb20wIBcNMjEwNzA5 -MTkxMjI1WhgPMjEyMTA2MTUxOTEyMjVaMIGBMQswCQYDVQQGEwJVUzELMAkGA1UE -CAwCQ0ExDDAKBgNVBAcMA1NGTzELMAkGA1UECgwCTUMxDTALBgNVBAsMBHJvb3Qx -EzARBgNVBAMMCnZhbGlkYXRvcjQxJjAkBgkqhkiG9w0BCQEWF3ZhbGlkYXRvcjRA -cGFydG5lcmQuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuEfQ -dAyfP9GTZtHTrfML/Ojb7SFxYIIiKJeWITngj9rhzQ2XfgeSH4oDBySjVVuZQdKc -YJbUjPE/L0fMyFFdSnAZHWxtC13ekQbvE+zLbhqSGJDxvjzV6elTVhuXuvtyHbRa -erbIPobeiqoM6eJ+SC44m2UUKUj0p9+78Og9r/xxx/TFzlCK2jHpwfkYsUzPuRf3 -gKzTqysbZwxFzzEwNJG+SxkLEZuEu53sPKKQCBmgl7Rp4RYGWL5g/xpWUJmID1cP -yGkYx8uvQ+v+qILaCsK3RiaCN+iFAejm78/1VcZdQWdB9OTz29MGqwB9oJngCGIB -0LN5TI8i8EMtOHQSIQIDAQABo4IBKDCCASQwEQYJYIZIAYb4QgEBBAQDAgbAMB0G -A1UdDgQWBBTiYWgjfuJswsCgAvS4hMukchm0/TAJBgNVHRMEAjAAMA4GA1UdDwEB -/wQEAwIF4DAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwgbUGA1UdIwSB -rTCBqoAUXba6O62H9QWrXDcwR5ZKWI+LFu6hfKR6MHgxCzAJBgNVBAYTAlVTMQsw -CQYDVQQIDAJDQTEMMAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwE -cm9vdDEQMA4GA1UEAwwHaW50ZXJjYTEgMB4GCSqGSIb3DQEJARYRaW50ZXJjYUBh -ZG1pbi5jb22CFHAEyPzePSbGcgLskvIH1ugaaXQUMA0GCSqGSIb3DQEBCwUAA4IB -AQB0yQiERMtzUId9mShNX/h09rI74S1e2YNNppGXaDv+ZAKoutYDaP/V1LLnQJ7a -aChpGIW4O66d2I0y5On8SbNTIxXhfJhqQd/vWPGEozekFw2+gaH6D3rqghQUNT4V -6gV7WRRCn5TgXZ7wDfWQua7/H1A7B+Axg19n5if9wUo3bvvwrk2q9vFj0D/jGTVd -LLoROx5dif+urxwPouwpe11rJouuolSRlkxC9kaFvWpR1ELLfrYG0fj6tFUSUNuw -p+lNisyoifUgHlzARnNCXCilAo4yq9dcYXHeY+0uB1pY1Na/xiue21GUTAVtpr4a -QvnaWDse4YykkozutA/iETLW ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID8DCCAtigAwIBAgIUcATI/N49JsZyAuyS8gfW6BppdBQwDQYJKoZIhvcNAQEL -BQAweDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRAwDgYDVQQDDAdpbnRlcmNhMSAwHgYJ -KoZIhvcNAQkBFhFpbnRlcmNhQGFkbWluLmNvbTAgFw0yMTA3MDkxOTEyMjRaGA8y -MTIxMDYxNTE5MTIyNFowgYExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEMMAoG -A1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDETMBEGA1UEAwwK -cGFydG5lcmRjYTEmMCQGCSqGSIb3DQEJARYXcGFydG5lcmRjYUBwYXJ0bmVyZC5j -b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCbWjqued8UEhetS67V -pNCkbCBjdeSXyjZgEIxMqmPILY6oymkClkty1ktxMYkEKcDi44+oCq93MIQOAkHx -qRGlu+z9Eql/aEJzVu85gIfa+64/GJ5OgGIqfI0XOaETu1MGhZN3P7Kb38tmqX7l -ppP/lfADvdlA/C+SY21Y2OMmeXo8VUtrW5YcUZ2OHt6esFZHCanI7FGYi7A71l/R -3ZXX1BVvHYJlssY5GwBLT3Yng6sshMihhXviU4f+RBEUm1pSRG/P+0RSjQVyp3bW -EiwXHu0WD9j7Kjgesrz7J2mFTgjNEQQ6lQioN6ZCrboM6WbxWM2zzSWCPSV/ON1W -QUVTAgMBAAGjZjBkMB0GA1UdDgQWBBRdtro7rYf1BatcNzBHlkpYj4sW7jAfBgNV -HSMEGDAWgBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjASBgNVHRMBAf8ECDAGAQH/AgEA -MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEAJd86d928wxKO8C6+ -VksOT7G2RcYNk9ok90bcAJdj6zAhEM4h5XZPomHYibKvdmGD1IH2TlzX+P4+Z2tc -mHOkpHCa99vwGlIeb+objfR5fYrZDPlFMfYdzeesXhlmZ5j9bzxMUEAMGVCZOm99 -Gq+uHoj1+bPaLkyaFfETchhlXw6EYXoM5bkdXGZMnXvL3DWWvPauA53tUiRh++Q7 -bPCSbmT9YooVbjqyT2IGzIWXzU0O6u5H4PHMNs+DCiGcoTqvC9uZDcU0l+4g6UV7 -v83YnAhDr+Ed/WJ8EpfvbxmNFu5d/SCmmzJnv2sBn27//q4xUNxV9OaNkbxzVOwd -cQyzwQ== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID6jCCAtKgAwIBAgIUYdazVD+VnI7jBu7xLaW+npfwHJIwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEQMA4GA1UE -AwwHaW50ZXJjYTEgMB4GCSqGSIb3DQEJARYRaW50ZXJjYUBhZG1pbi5jb20wggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ8QgVwEjy3CgJFvET7tYNBw97 -4i33EHQT4ZuayGCJ+ADY3ZFpsw2M1IPlbAguqfBkcLd8TAjWNRAdsm9ubGlIcTZr -7LNle3gvc7qEP4qg0i6M7D06CDqtBaIJ1PMTJchOouGU9ntBe+h0qg8tzpiqJdIw -jIOPRWW98Hw9KgF6++2jtlcOW1IxiFSWqf0Mpc81qKukcxnsHjvdxmBp/Z1vL42E -m5xNOGXoxpjq8NygSuVDhQ/bZUnmHLmvv9MXe9Ob52rlzea/YafLpOeNGSA1aCxm -Fx0lcoXWp4xpoyJn9St7vmH3t7quBKdqt54zwcuHjhgZPSR6RikodKHtsl8TAgMB -AAGjZjBkMB0GA1UdDgQWBBTdAXu8aBbvkvxYD5Ss9/Ml5VgohjAfBgNVHSMEGDAW -gBSMsBNO3UGBteMZOTq5fDPG2aPx8DASBgNVHRMBAf8ECDAGAQH/AgEBMA4GA1Ud -DwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEA0hJCtrCI9Mf47+y3pvsTjiaT -TZwpNE0cdxHjpcCHJWX2jmGbqqA6wvQ7yy4PFarmnFOoW3hQFeiLgpz9X86YTRzF -8dj3Q2MKXf6i6/iW+Y96GFqurshKp7wV25wfzWwLXcVCiM1xYPWYSyGsZAGotu4M -c7uolVABjJu5nci9mBxVmaYV5oT1mxrvq3dCPm2AvmVFNWPNRbMSAuT5B7FUDvWG -xvd1aDFduqL0iLAcrTifMIYI3XL4pBSIlL78dgY45WL6616EF3mHhW/Y4k4PNq1I -Fz08Q3y99ilhzeAci1jv6KBVHiALZNFFtYjd10KX95qnF9SomBjCTPsQ9PTC5g== ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIID2zCCAsOgAwIBAgIUdxRGMrv1ONRI/dJXUHa3isjVXuwwDQYJKoZIhvcNAQEL -BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMQwwCgYDVQQHDANTRk8xCzAJ -BgNVBAoMAk1DMQ0wCwYDVQQLDARyb290MRUwEwYDVQQDDAxyb290Y2EuYWRtaW4x -HzAdBgkqhkiG9w0BCQEWEHJvb3RjYUBhZG1pbi5jb20wIBcNMjEwNzA5MTkxMTU0 -WhgPMjEyMTA2MTUxOTExNTRaMHwxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEM -MAoGA1UEBwwDU0ZPMQswCQYDVQQKDAJNQzENMAsGA1UECwwEcm9vdDEVMBMGA1UE -AwwMcm9vdGNhLmFkbWluMR8wHQYJKoZIhvcNAQkBFhByb290Y2FAYWRtaW4uY29t -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Bqlx+ngRZfWjktX6urJ -Tpg2//n3uMW8Gp55SVq+lbpkNXuA3W0uTlom/2fsnzmMmBAJUvv/NaLcFSN++0/d -fYGVhogxjlRoyaXa4sdFPg2ocM+lCHLGhWpYuVdTWPknikTOVNbh9z94SLRfrP8N -kuxGNZNktnVm+QJmrCLmjvnPvQlcP/WqFYehFM26NaRzswOhLLRU1YK8aVlh9Vto -PGSTPtZK60XwTfyCj/zEvHqpbGbKJPp7W0rpxl7ehPQIzGQt5IMH3zKrqLm4/pt+ -XFEVqy724sstthS73bvXguWxBLOzuMze8CbI6SJpcHaH8HO5pfFMPyE5N5l2x9Co -uQIDAQABo1MwUTAdBgNVHQ4EFgQUjLATTt1BgbXjGTk6uXwzxtmj8fAwHwYDVR0j -BBgwFoAUjLATTt1BgbXjGTk6uXwzxtmj8fAwDwYDVR0TAQH/BAUwAwEB/zANBgkq -hkiG9w0BAQsFAAOCAQEAL9/7YtxIbuTt7dgH1KOMb/y6P3Qb79fTVZTx073E2ZBj -OfSuMGUhKC30LWQXCQEsY4WpugJ4cS0NsArSYsO5XWguqgjYlWL6poOmYhGvj7ou -oi44oYqcPm6EbvLrAKdI67bkbSPLzgs+6TrxTyXHzPKFBqGBnuHaFKjgb4hGiPmd -JWKrh3fbTtuLjqSHd0Gey2uBNFql4LhPutdsCUhSOD3BibAeTbXyln70AgpTCoWT -9Qr9Ux6HYIiAHSDBzzuN8EcrlnAm9RO4zRyUVwxDsHoNsOUwybn50IM66p83HuiY -h/iX5uNjaFAADfRR4rQOwb6MLreVH2XYUSiz4M/ijg== ------END CERTIFICATE----- diff --git a/acceptance-tests/tests/src/test/resources/pki-certs/validator4/truststore.jks b/acceptance-tests/tests/src/test/resources/pki-certs/validator4/truststore.jks deleted file mode 100644 index 0128950f21c..00000000000 Binary files a/acceptance-tests/tests/src/test/resources/pki-certs/validator4/truststore.jks and /dev/null differ diff --git a/besu/build.gradle b/besu/build.gradle index 9d72f50b021..fa2b0548c56 100644 --- a/besu/build.gradle +++ b/besu/build.gradle @@ -22,12 +22,15 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } dependencies { + api project(':datatypes') + api 'org.slf4j:slf4j-api' implementation project(':config') @@ -77,6 +80,8 @@ dependencies { implementation 'org.springframework.security:spring-security-crypto' implementation 'org.xerial.snappy:snappy-java' implementation 'tech.pegasys:jc-kzg-4844' + implementation 'org.rocksdb:rocksdbjni' + implementation 'commons-net:commons-net' runtimeOnly 'org.apache.logging.log4j:log4j-jul' runtimeOnly 'com.splunk.logging:splunk-library-javalogging' @@ -98,10 +103,10 @@ dependencies { testImplementation 'org.awaitility:awaitility' testImplementation 'org.junit.jupiter:junit-jupiter' testImplementation 'org.mockito:mockito-core' - testImplementation 'org.mockito:mockito-junit-jupiter' testImplementation 'org.testcontainers:testcontainers' testImplementation 'tech.pegasys.discovery:discovery' + testImplementation 'com.google.dagger:dagger' - testRuntimeOnly 'org.junit.vintage:junit-vintage-engine' annotationProcessor 'com.google.dagger:dagger-compiler' + testAnnotationProcessor 'com.google.dagger:dagger-compiler' } diff --git a/besu/src/main/java/org/hyperledger/besu/Besu.java b/besu/src/main/java/org/hyperledger/besu/Besu.java index 6bdd87d0cff..8d40a10eb49 100644 --- a/besu/src/main/java/org/hyperledger/besu/Besu.java +++ b/besu/src/main/java/org/hyperledger/besu/Besu.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -26,6 +26,8 @@ /** Besu bootstrap class. */ public final class Besu { + /** Default constructor. */ + public Besu() {} /** * The main entrypoint to Besu application diff --git a/besu/src/main/java/org/hyperledger/besu/BesuInfo.java b/besu/src/main/java/org/hyperledger/besu/BesuInfo.java index 54447a59600..94414ee5f74 100644 --- a/besu/src/main/java/org/hyperledger/besu/BesuInfo.java +++ b/besu/src/main/java/org/hyperledger/besu/BesuInfo.java @@ -16,7 +16,11 @@ import org.hyperledger.besu.util.platform.PlatformDetector; +import java.net.JarURLConnection; +import java.net.URL; import java.util.Optional; +import java.util.jar.Attributes; +import java.util.jar.Manifest; /** * Represent Besu information such as version, OS etc. Used with --version option and during Besu @@ -27,6 +31,24 @@ public final class BesuInfo { private static final String VERSION = BesuInfo.class.getPackage().getImplementationVersion(); private static final String OS = PlatformDetector.getOS(); private static final String VM = PlatformDetector.getVM(); + private static final String COMMIT; + + static { + String className = BesuInfo.class.getSimpleName() + ".class"; + String classPath = BesuInfo.class.getResource(className).toString(); + + String commit; + try { + URL url = new URL(classPath); + JarURLConnection jarConnection = (JarURLConnection) url.openConnection(); + Manifest manifest = jarConnection.getManifest(); + Attributes attributes = manifest.getMainAttributes(); + commit = attributes.getValue("Commit-Hash"); + } catch (Exception e) { + commit = null; + } + COMMIT = commit; + } private BesuInfo() {} @@ -60,4 +82,13 @@ public static String nodeName(final Optional maybeIdentity) { .map(identity -> String.format("%s/%s/v%s/%s/%s", CLIENT, identity, VERSION, OS, VM)) .orElse(version()); } + + /** + * Generate the commit hash for this besu version + * + * @return the commit hash for this besu version + */ + public static String commit() { + return COMMIT; + } } diff --git a/besu/src/main/java/org/hyperledger/besu/Runner.java b/besu/src/main/java/org/hyperledger/besu/Runner.java index 32c4a46ddeb..609dab117ff 100644 --- a/besu/src/main/java/org/hyperledger/besu/Runner.java +++ b/besu/src/main/java/org/hyperledger/besu/Runner.java @@ -18,6 +18,7 @@ import org.hyperledger.besu.ethereum.api.graphql.GraphQLHttpService; import org.hyperledger.besu.ethereum.api.jsonrpc.EngineJsonRpcService; import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcHttpService; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.ipc.JsonRpcIpcService; import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketService; import org.hyperledger.besu.ethereum.api.query.cache.AutoTransactionLogBloomCachingService; @@ -25,6 +26,7 @@ import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolEvictionService; import org.hyperledger.besu.ethereum.p2p.network.NetworkRunner; +import org.hyperledger.besu.ethereum.p2p.network.P2PNetwork; import org.hyperledger.besu.ethereum.stratum.StratumServer; import org.hyperledger.besu.ethstats.EthStatsService; import org.hyperledger.besu.metrics.MetricsService; @@ -38,6 +40,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; +import java.util.Map; import java.util.Optional; import java.util.Properties; import java.util.concurrent.CompletableFuture; @@ -68,6 +71,7 @@ public class Runner implements AutoCloseable { private final Optional engineJsonRpc; private final Optional metrics; private final Optional ipcJsonRpc; + private final Map inProcessRpcMethods; private final Optional pidPath; private final Optional webSocketRpc; private final TransactionPoolEvictionService transactionPoolEvictionService; @@ -89,6 +93,7 @@ public class Runner implements AutoCloseable { * @param graphQLHttp the graph ql http * @param webSocketRpc the web socket rpc * @param ipcJsonRpc the ipc json rpc + * @param inProcessRpcMethods the in-process rpc methods * @param stratumServer the stratum server * @param metrics the metrics * @param ethStatsService the eth stats service @@ -107,6 +112,7 @@ public class Runner implements AutoCloseable { final Optional graphQLHttp, final Optional webSocketRpc, final Optional ipcJsonRpc, + final Map inProcessRpcMethods, final Optional stratumServer, final Optional metrics, final Optional ethStatsService, @@ -124,6 +130,7 @@ public class Runner implements AutoCloseable { this.engineJsonRpc = engineJsonRpc; this.webSocketRpc = webSocketRpc; this.ipcJsonRpc = ipcJsonRpc; + this.inProcessRpcMethods = inProcessRpcMethods; this.metrics = metrics; this.ethStatsService = ethStatsService; this.besuController = besuController; @@ -252,7 +259,7 @@ private void waitForServiceToStop(final String serviceName, final SynchronousShu try { shutdown.await(); } catch (final InterruptedException e) { - LOG.debug("Interrupted while waiting for service " + serviceName + " to stop", e); + LOG.debug("Interrupted while waiting for service {} to stop {}", serviceName, e); Thread.currentThread().interrupt(); } } @@ -412,6 +419,15 @@ public Optional getMetricsPort() { } } + /** + * Get the RPC methods that can be called in-process + * + * @return RPC methods by name + */ + public Map getInProcessRpcMethods() { + return inProcessRpcMethods; + } + /** * Gets local enode. * @@ -422,6 +438,15 @@ Optional getLocalEnode() { return networkRunner.getNetwork().getLocalEnode(); } + /** + * get P2PNetwork service. + * + * @return p2p network service. + */ + public P2PNetwork getP2PNetwork() { + return networkRunner.getNetwork(); + } + @FunctionalInterface private interface SynchronousShutdown { /** diff --git a/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java b/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java index d75d119cdea..96eef547fed 100644 --- a/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java @@ -34,6 +34,7 @@ import org.hyperledger.besu.ethereum.api.graphql.GraphQLHttpService; import org.hyperledger.besu.ethereum.api.graphql.GraphQLProvider; import org.hyperledger.besu.ethereum.api.jsonrpc.EngineJsonRpcService; +import org.hyperledger.besu.ethereum.api.jsonrpc.InProcessRpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcHttpService; import org.hyperledger.besu.ethereum.api.jsonrpc.authentication.AuthenticationService; @@ -88,6 +89,7 @@ import org.hyperledger.besu.ethereum.p2p.network.ProtocolManager; import org.hyperledger.besu.ethereum.p2p.peers.DefaultPeer; import org.hyperledger.besu.ethereum.p2p.peers.EnodeDnsConfiguration; +import org.hyperledger.besu.ethereum.p2p.permissions.PeerPermissionSubnet; import org.hyperledger.besu.ethereum.p2p.permissions.PeerPermissions; import org.hyperledger.besu.ethereum.p2p.permissions.PeerPermissionsDenylist; import org.hyperledger.besu.ethereum.p2p.rlpx.connections.netty.TLSConfiguration; @@ -106,7 +108,6 @@ import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.stratum.StratumServer; import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; -import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.ethstats.EthStatsService; import org.hyperledger.besu.ethstats.util.EthStatsConnectOptions; import org.hyperledger.besu.metrics.MetricsService; @@ -133,6 +134,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -146,6 +148,7 @@ import graphql.GraphQL; import io.vertx.core.Vertx; import io.vertx.core.VertxOptions; +import org.apache.commons.net.util.SubnetUtils.SubnetInfo; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; import org.slf4j.Logger; @@ -176,6 +179,7 @@ public class RunnerBuilder { private Optional engineJsonRpcConfiguration = Optional.empty(); private GraphQLConfiguration graphQLConfiguration; private WebSocketConfiguration webSocketConfiguration; + private InProcessRpcConfiguration inProcessRpcConfiguration; private ApiConfiguration apiConfiguration; private Path dataDir; private Optional pidPath = Optional.empty(); @@ -192,6 +196,11 @@ public class RunnerBuilder { private JsonRpcIpcConfiguration jsonRpcIpcConfiguration; private boolean legacyForkIdEnabled; private Optional enodeDnsConfiguration; + private List allowedSubnets = new ArrayList<>(); + private boolean poaDiscoveryRetryBootnodes = true; + + /** Instantiates a new Runner builder. */ + public RunnerBuilder() {} /** * Add Vertx. @@ -407,6 +416,18 @@ public RunnerBuilder webSocketConfiguration(final WebSocketConfiguration webSock return this; } + /** + * Add In-Process RPC configuration. + * + * @param inProcessRpcConfiguration the in-process RPC configuration + * @return the runner builder + */ + public RunnerBuilder inProcessRpcConfiguration( + final InProcessRpcConfiguration inProcessRpcConfiguration) { + this.inProcessRpcConfiguration = inProcessRpcConfiguration; + return this; + } + /** * Add Api configuration. * @@ -586,6 +607,28 @@ public RunnerBuilder enodeDnsConfiguration(final EnodeDnsConfiguration enodeDnsC return this; } + /** + * Add subnet configuration + * + * @param allowedSubnets the allowedSubnets + * @return the runner builder + */ + public RunnerBuilder allowedSubnets(final List allowedSubnets) { + this.allowedSubnets = allowedSubnets; + return this; + } + + /** + * Flag to indicate if peer table refreshes should always query bootnodes + * + * @param poaDiscoveryRetryBootnodes whether to always query bootnodes + * @return the runner builder + */ + public RunnerBuilder poaDiscoveryRetryBootnodes(final boolean poaDiscoveryRetryBootnodes) { + this.poaDiscoveryRetryBootnodes = poaDiscoveryRetryBootnodes; + return this; + } + /** * Build Runner instance. * @@ -602,13 +645,17 @@ public Runner build() { .setAdvertisedHost(p2pAdvertisedHost); if (discovery) { final List bootstrap; - if (ethNetworkConfig.getBootNodes() == null) { - bootstrap = EthNetworkConfig.getNetworkConfig(NetworkName.MAINNET).getBootNodes(); + if (ethNetworkConfig.bootNodes() == null) { + bootstrap = EthNetworkConfig.getNetworkConfig(NetworkName.MAINNET).bootNodes(); } else { - bootstrap = ethNetworkConfig.getBootNodes(); + bootstrap = ethNetworkConfig.bootNodes(); } discoveryConfiguration.setBootnodes(bootstrap); - discoveryConfiguration.setDnsDiscoveryURL(ethNetworkConfig.getDnsDiscoveryUrl()); + discoveryConfiguration.setIncludeBootnodesOnPeerRefresh( + besuController.getGenesisConfigOptions().isPoa() && poaDiscoveryRetryBootnodes); + LOG.info("Resolved {} bootnodes.", bootstrap.size()); + LOG.debug("Bootnodes = {}", bootstrap); + discoveryConfiguration.setDnsDiscoveryURL(ethNetworkConfig.dnsDiscoveryUrl()); discoveryConfiguration.setDiscoveryV5Enabled( networkingConfiguration.getDiscovery().isDiscoveryV5Enabled()); discoveryConfiguration.setFilterOnEnrForkId( @@ -643,6 +690,10 @@ public Runner build() { final PeerPermissionsDenylist bannedNodes = PeerPermissionsDenylist.create(); bannedNodeIds.forEach(bannedNodes::add); + PeerPermissionSubnet peerPermissionSubnet = new PeerPermissionSubnet(allowedSubnets); + final PeerPermissions defaultPeerPermissions = + PeerPermissions.combine(peerPermissionSubnet, bannedNodes); + final List bootnodes = discoveryConfiguration.getBootnodes(); final Synchronizer synchronizer = besuController.getSynchronizer(); @@ -662,16 +713,18 @@ public Runner build() { final PeerPermissions peerPermissions = nodePermissioningController .map(nodePC -> new PeerPermissionsAdapter(nodePC, bootnodes, context.getBlockchain())) - .map(nodePerms -> PeerPermissions.combine(nodePerms, bannedNodes)) - .orElse(bannedNodes); + .map(nodePerms -> PeerPermissions.combine(nodePerms, defaultPeerPermissions)) + .orElse(defaultPeerPermissions); + + final EthPeers ethPeers = besuController.getEthPeers(); LOG.info("Detecting NAT service."); final boolean fallbackEnabled = natMethod == NatMethod.AUTO || natMethodFallbackEnabled; final NatService natService = new NatService(buildNatManager(natMethod), fallbackEnabled); final NetworkBuilder inactiveNetwork = caps -> new NoopP2PNetwork(); + final NetworkBuilder activeNetwork = caps -> { - final EthPeers ethPeers = besuController.getEthPeers(); return DefaultP2PNetwork.builder() .vertx(vertx) .nodeKey(nodeKey) @@ -686,9 +739,9 @@ public Runner build() { .blockchain(context.getBlockchain()) .blockNumberForks(besuController.getGenesisConfigOptions().getForkBlockNumbers()) .timestampForks(besuController.getGenesisConfigOptions().getForkBlockTimestamps()) - .allConnectionsSupplier(ethPeers::getAllConnections) - .allActiveConnectionsSupplier(ethPeers::getAllActiveConnections) - .peersLowerBound(ethPeers.getPeerLowerBound()) + .allConnectionsSupplier(ethPeers::streamAllConnections) + .allActiveConnectionsSupplier(ethPeers::streamAllActiveConnections) + .maxPeers(ethPeers.getMaxPeers()) .build(); }; @@ -698,9 +751,10 @@ public Runner build() { .subProtocols(subProtocols) .network(p2pEnabled ? activeNetwork : inactiveNetwork) .metricsSystem(metricsSystem) + .ethPeersShouldConnect(ethPeers::shouldTryToConnect) .build(); - besuController.getEthPeers().setRlpxAgent(networkRunner.getRlpxAgent()); + ethPeers.setRlpxAgent(networkRunner.getRlpxAgent()); final P2PNetwork network = networkRunner.getNetwork(); // ForkId in Ethereum Node Record needs updating when we transition to a new protocol spec @@ -719,14 +773,17 @@ public Runner build() { final TransactionPool transactionPool = besuController.getTransactionPool(); final MiningCoordinator miningCoordinator = besuController.getMiningCoordinator(); + final MiningParameters miningParameters = besuController.getMiningParameters(); final BlockchainQueries blockchainQueries = new BlockchainQueries( + protocolSchedule, context.getBlockchain(), context.getWorldStateArchive(), Optional.of(dataDir.resolve(CACHE_PATH)), Optional.of(besuController.getProtocolManager().ethContext().getScheduler()), - apiConfiguration); + apiConfiguration, + miningParameters); final PrivacyParameters privacyParameters = besuController.getPrivacyParameters(); @@ -743,13 +800,12 @@ public Runner build() { final P2PNetwork peerNetwork = networkRunner.getNetwork(); - final MiningParameters miningParameters = besuController.getMiningParameters(); Optional stratumServer = Optional.empty(); if (miningParameters.isStratumMiningEnabled()) { if (!(miningCoordinator instanceof PoWMiningCoordinator powMiningCoordinator)) { throw new IllegalArgumentException( - "Stratum server requires an PoWMiningCoordinator not " + "Stratum mining requires the network option(--network) to be set to CLASSIC. Stratum server requires a PoWMiningCoordinator not " + ((miningCoordinator == null) ? "null" : miningCoordinator.getClass().getName())); } stratumServer = @@ -798,7 +854,7 @@ public Runner build() { metricsSystem, supportedCapabilities, jsonRpcConfiguration.getRpcApis().stream() - .filter(apiGroup -> !apiGroup.toLowerCase().startsWith("engine")) + .filter(apiGroup -> !apiGroup.toLowerCase(Locale.ROOT).startsWith("engine")) .collect(Collectors.toList()), filterManager, accountLocalConfigPermissioningController, @@ -807,6 +863,7 @@ public Runner build() { jsonRpcConfiguration, webSocketConfiguration, metricsConfiguration, + graphQLConfiguration, natService, besuPluginContext.getNamedPlugins(), dataDir, @@ -851,6 +908,7 @@ public Runner build() { engineJsonRpcConfiguration.get(), webSocketConfiguration, metricsConfiguration, + graphQLConfiguration, natService, besuPluginContext.getNamedPlugins(), dataDir, @@ -936,7 +994,7 @@ public Runner build() { metricsSystem, supportedCapabilities, webSocketConfiguration.getRpcApis().stream() - .filter(apiGroup -> !apiGroup.toLowerCase().startsWith("engine")) + .filter(apiGroup -> !apiGroup.toLowerCase(Locale.ROOT).startsWith("engine")) .collect(Collectors.toList()), filterManager, accountLocalConfigPermissioningController, @@ -945,16 +1003,14 @@ public Runner build() { jsonRpcConfiguration, webSocketConfiguration, metricsConfiguration, + graphQLConfiguration, natService, besuPluginContext.getNamedPlugins(), dataDir, rpcEndpointServiceImpl); createLogsSubscriptionService( - context.getBlockchain(), - context.getWorldStateArchive(), - subscriptionManager, - privacyParameters); + context.getBlockchain(), subscriptionManager, privacyParameters, blockchainQueries); createNewBlockHeadersSubscriptionService( context.getBlockchain(), blockchainQueries, subscriptionManager); @@ -1019,7 +1075,7 @@ public Runner build() { metricsSystem, supportedCapabilities, jsonRpcIpcConfiguration.getEnabledApis().stream() - .filter(apiGroup -> !apiGroup.toLowerCase().startsWith("engine")) + .filter(apiGroup -> !apiGroup.toLowerCase(Locale.ROOT).startsWith("engine")) .collect(Collectors.toList()), filterManager, accountLocalConfigPermissioningController, @@ -1028,6 +1084,7 @@ public Runner build() { jsonRpcConfiguration, webSocketConfiguration, metricsConfiguration, + graphQLConfiguration, natService, besuPluginContext.getNamedPlugins(), dataDir, @@ -1043,6 +1100,38 @@ public Runner build() { jsonRpcIpcService = Optional.empty(); } + final Map inProcessRpcMethods; + if (inProcessRpcConfiguration.isEnabled()) { + inProcessRpcMethods = + jsonRpcMethods( + protocolSchedule, + context, + besuController, + peerNetwork, + blockchainQueries, + synchronizer, + transactionPool, + miningParameters, + miningCoordinator, + metricsSystem, + supportedCapabilities, + inProcessRpcConfiguration.getInProcessRpcApis(), + filterManager, + accountLocalConfigPermissioningController, + nodeLocalConfigPermissioningController, + privacyParameters, + jsonRpcConfiguration, + webSocketConfiguration, + metricsConfiguration, + graphQLConfiguration, + natService, + besuPluginContext.getNamedPlugins(), + dataDir, + rpcEndpointServiceImpl); + } else { + inProcessRpcMethods = Map.of(); + } + return new Runner( vertx, networkRunner, @@ -1052,6 +1141,7 @@ public Runner build() { graphQLHttpService, webSocketService, jsonRpcIpcService, + inProcessRpcMethods, stratumServer, metricsService, ethStatsService, @@ -1195,6 +1285,7 @@ private Map jsonRpcMethods( final JsonRpcConfiguration jsonRpcConfiguration, final WebSocketConfiguration webSocketConfiguration, final MetricsConfiguration metricsConfiguration, + final GraphQLConfiguration graphQLConfiguration, final NatService natService, final Map namedPlugins, final Path dataDir, @@ -1206,7 +1297,9 @@ private Map jsonRpcMethods( new JsonRpcMethodsFactory() .methods( BesuInfo.nodeName(identityString), - ethNetworkConfig.getNetworkId(), + BesuInfo.shortVersion(), + BesuInfo.commit(), + ethNetworkConfig.networkId(), besuController.getGenesisConfigOptions(), network, blockchainQueries, @@ -1226,6 +1319,7 @@ private Map jsonRpcMethods( jsonRpcConfiguration, webSocketConfiguration, metricsConfiguration, + graphQLConfiguration, natService, namedPlugins, dataDir, @@ -1267,15 +1361,12 @@ private SubscriptionManager createSubscriptionManager( private void createLogsSubscriptionService( final Blockchain blockchain, - final WorldStateArchive worldStateArchive, final SubscriptionManager subscriptionManager, - final PrivacyParameters privacyParameters) { + final PrivacyParameters privacyParameters, + final BlockchainQueries blockchainQueries) { Optional privacyQueries = Optional.empty(); if (privacyParameters.isEnabled()) { - final BlockchainQueries blockchainQueries = - new BlockchainQueries( - blockchain, worldStateArchive, Optional.empty(), Optional.empty(), apiConfiguration); privacyQueries = Optional.of( new PrivacyQueries( diff --git a/besu/src/main/java/org/hyperledger/besu/chainimport/JsonBlockImporter.java b/besu/src/main/java/org/hyperledger/besu/chainimport/JsonBlockImporter.java index 13c239d6d88..efd35d40ff4 100644 --- a/besu/src/main/java/org/hyperledger/besu/chainimport/JsonBlockImporter.java +++ b/besu/src/main/java/org/hyperledger/besu/chainimport/JsonBlockImporter.java @@ -136,7 +136,7 @@ private void setOptionalFields( // For simplicity only set these for PoW consensus algorithms. // Other consensus algorithms use these fields for special purposes or ignore them. miner.setCoinbase(blockData.getCoinbase().orElse(Address.ZERO)); - miner.setExtraData(blockData.getExtraData().orElse(Bytes.EMPTY)); + controller.getMiningParameters().setExtraData(blockData.getExtraData().orElse(Bytes.EMPTY)); } else if (blockData.getCoinbase().isPresent() || blockData.getExtraData().isPresent()) { // Fail if these fields are set for non-ethash chains final Stream.Builder fields = Stream.builder(); diff --git a/besu/src/main/java/org/hyperledger/besu/chainimport/RlpBlockImporter.java b/besu/src/main/java/org/hyperledger/besu/chainimport/RlpBlockImporter.java index f60a24ff869..d006f42010e 100644 --- a/besu/src/main/java/org/hyperledger/besu/chainimport/RlpBlockImporter.java +++ b/besu/src/main/java/org/hyperledger/besu/chainimport/RlpBlockImporter.java @@ -65,6 +65,9 @@ public class RlpBlockImporter implements Closeable { private final Stopwatch segmentTimer = Stopwatch.createUnstarted(); private static final long SEGMENT_SIZE = 1000; + /** Default Constructor. */ + public RlpBlockImporter() {} + /** * Imports blocks that are stored as concatenated RLP sections in the given file into Besu's block * storage. diff --git a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java index a909b1994ac..ecfc0eaadb2 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -16,26 +16,15 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; -import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.hyperledger.besu.cli.DefaultCommandValues.getDefaultBesuDataPath; import static org.hyperledger.besu.cli.config.NetworkName.MAINNET; import static org.hyperledger.besu.cli.util.CommandLineUtils.DEPENDENCY_WARNING_MSG; -import static org.hyperledger.besu.cli.util.CommandLineUtils.DEPRECATION_WARNING_MSG; +import static org.hyperledger.besu.cli.util.CommandLineUtils.isOptionSet; import static org.hyperledger.besu.controller.BesuController.DATABASE_PATH; -import static org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration.DEFAULT_GRAPHQL_HTTP_PORT; import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration.DEFAULT_ENGINE_JSON_RPC_PORT; -import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration.DEFAULT_JSON_RPC_PORT; -import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration.DEFAULT_PRETTY_JSON_ENABLED; -import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.DEFAULT_RPC_APIS; -import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.VALID_APIS; import static org.hyperledger.besu.ethereum.api.jsonrpc.authentication.EngineAuthService.EPHEMERAL_JWT_FILE; -import static org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration.DEFAULT_WEBSOCKET_PORT; -import static org.hyperledger.besu.metrics.BesuMetricCategory.DEFAULT_METRIC_CATEGORIES; -import static org.hyperledger.besu.metrics.MetricsProtocol.PROMETHEUS; -import static org.hyperledger.besu.metrics.prometheus.MetricsConfiguration.DEFAULT_METRICS_PORT; -import static org.hyperledger.besu.metrics.prometheus.MetricsConfiguration.DEFAULT_METRICS_PUSH_PORT; import static org.hyperledger.besu.nat.kubernetes.KubernetesNatManager.DEFAULT_BESU_SERVICE_NAME_FILTER; import org.hyperledger.besu.BesuInfo; @@ -46,30 +35,37 @@ import org.hyperledger.besu.chainimport.RlpBlockImporter; import org.hyperledger.besu.cli.config.EthNetworkConfig; import org.hyperledger.besu.cli.config.NetworkName; +import org.hyperledger.besu.cli.config.ProfilesCompletionCandidates; import org.hyperledger.besu.cli.converter.MetricCategoryConverter; import org.hyperledger.besu.cli.converter.PercentageConverter; -import org.hyperledger.besu.cli.custom.CorsAllowedOriginsProperty; +import org.hyperledger.besu.cli.converter.SubnetInfoConverter; import org.hyperledger.besu.cli.custom.JsonRPCAllowlistHostsProperty; -import org.hyperledger.besu.cli.custom.RpcAuthFileValidator; import org.hyperledger.besu.cli.error.BesuExecutionExceptionHandler; import org.hyperledger.besu.cli.error.BesuParameterExceptionHandler; import org.hyperledger.besu.cli.options.MiningOptions; import org.hyperledger.besu.cli.options.TransactionPoolOptions; +import org.hyperledger.besu.cli.options.stable.ApiConfigurationOptions; import org.hyperledger.besu.cli.options.stable.DataStorageOptions; import org.hyperledger.besu.cli.options.stable.EthstatsOptions; +import org.hyperledger.besu.cli.options.stable.GraphQlOptions; +import org.hyperledger.besu.cli.options.stable.JsonRpcHttpOptions; import org.hyperledger.besu.cli.options.stable.LoggingLevelOption; +import org.hyperledger.besu.cli.options.stable.MetricsOptionGroup; import org.hyperledger.besu.cli.options.stable.NodePrivateKeyFileOption; import org.hyperledger.besu.cli.options.stable.P2PTLSConfigOptions; +import org.hyperledger.besu.cli.options.stable.PermissionsOptions; +import org.hyperledger.besu.cli.options.stable.PluginsConfigurationOptions; +import org.hyperledger.besu.cli.options.stable.RpcWebsocketOptions; import org.hyperledger.besu.cli.options.unstable.ChainPruningOptions; import org.hyperledger.besu.cli.options.unstable.DnsOptions; import org.hyperledger.besu.cli.options.unstable.EthProtocolOptions; import org.hyperledger.besu.cli.options.unstable.EvmOptions; +import org.hyperledger.besu.cli.options.unstable.InProcessRpcOptions; import org.hyperledger.besu.cli.options.unstable.IpcOptions; import org.hyperledger.besu.cli.options.unstable.MetricsCLIOptions; import org.hyperledger.besu.cli.options.unstable.NatOptions; import org.hyperledger.besu.cli.options.unstable.NativeLibraryOptions; import org.hyperledger.besu.cli.options.unstable.NetworkingOptions; -import org.hyperledger.besu.cli.options.unstable.PkiBlockCreationOptions; import org.hyperledger.besu.cli.options.unstable.PrivacyPluginOptions; import org.hyperledger.besu.cli.options.unstable.RPCOptions; import org.hyperledger.besu.cli.options.unstable.SynchronizerOptions; @@ -78,6 +74,7 @@ import org.hyperledger.besu.cli.subcommands.PasswordSubCommand; import org.hyperledger.besu.cli.subcommands.PublicKeySubCommand; import org.hyperledger.besu.cli.subcommands.RetestethSubCommand; +import org.hyperledger.besu.cli.subcommands.TxParseSubCommand; import org.hyperledger.besu.cli.subcommands.ValidateConfigSubCommand; import org.hyperledger.besu.cli.subcommands.blocks.BlocksSubCommand; import org.hyperledger.besu.cli.subcommands.operator.OperatorSubCommand; @@ -85,15 +82,12 @@ import org.hyperledger.besu.cli.subcommands.storage.StorageSubCommand; import org.hyperledger.besu.cli.util.BesuCommandCustomFactory; import org.hyperledger.besu.cli.util.CommandLineUtils; -import org.hyperledger.besu.cli.util.ConfigOptionSearchAndRunHandler; +import org.hyperledger.besu.cli.util.ConfigDefaultValueProviderStrategy; import org.hyperledger.besu.cli.util.VersionProvider; -import org.hyperledger.besu.components.BesuComponent; import org.hyperledger.besu.config.CheckpointConfigOptions; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.config.MergeConfigOptions; -import org.hyperledger.besu.consensus.qbft.pki.PkiBlockCreationConfiguration; -import org.hyperledger.besu.consensus.qbft.pki.PkiBlockCreationConfigurationProvider; import org.hyperledger.besu.controller.BesuController; import org.hyperledger.besu.controller.BesuControllerBuilder; import org.hyperledger.besu.crypto.Blake2bfMessageDigest; @@ -109,22 +103,20 @@ import org.hyperledger.besu.enclave.EnclaveFactory; import org.hyperledger.besu.ethereum.GasLimitCalculator; import org.hyperledger.besu.ethereum.api.ApiConfiguration; -import org.hyperledger.besu.ethereum.api.ImmutableApiConfiguration; import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration; +import org.hyperledger.besu.ethereum.api.jsonrpc.InProcessRpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis; -import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.authentication.JwtAlgorithm; import org.hyperledger.besu.ethereum.api.jsonrpc.ipc.JsonRpcIpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; -import org.hyperledger.besu.ethereum.api.tls.FileBasedPasswordProvider; -import org.hyperledger.besu.ethereum.api.tls.TlsClientAuthConfiguration; -import org.hyperledger.besu.ethereum.api.tls.TlsConfiguration; import org.hyperledger.besu.ethereum.chain.Blockchain; -import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters; import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.ethereum.core.MiningParametersMetrics; import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.ethereum.core.VersionMetadata; +import org.hyperledger.besu.ethereum.core.plugins.PluginConfiguration; import org.hyperledger.besu.ethereum.eth.sync.SyncMode; import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; @@ -137,15 +129,15 @@ import org.hyperledger.besu.ethereum.p2p.rlpx.connections.netty.TLSConfiguration; import org.hyperledger.besu.ethereum.permissioning.LocalPermissioningConfiguration; import org.hyperledger.besu.ethereum.permissioning.PermissioningConfiguration; -import org.hyperledger.besu.ethereum.permissioning.PermissioningConfigurationBuilder; -import org.hyperledger.besu.ethereum.permissioning.SmartContractPermissioningConfiguration; import org.hyperledger.besu.ethereum.privacy.storage.keyvalue.PrivacyKeyValueStorageProvider; import org.hyperledger.besu.ethereum.privacy.storage.keyvalue.PrivacyKeyValueStorageProviderBuilder; import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStorageProvider; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStorageProviderBuilder; -import org.hyperledger.besu.ethereum.worldstate.PrunerConfiguration; +import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.ethereum.worldstate.ImmutableDataStorageConfiguration; import org.hyperledger.besu.evm.precompile.AbstractAltBnPrecompiledContract; import org.hyperledger.besu.evm.precompile.BigIntegerModularExponentiationPrecompiledContract; import org.hyperledger.besu.evm.precompile.KZGPointEvalPrecompiledContract; @@ -165,33 +157,43 @@ import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.plugin.services.PermissioningService; import org.hyperledger.besu.plugin.services.PicoCLIOptions; -import org.hyperledger.besu.plugin.services.PluginTransactionValidatorService; import org.hyperledger.besu.plugin.services.PrivacyPluginService; import org.hyperledger.besu.plugin.services.RpcEndpointService; import org.hyperledger.besu.plugin.services.SecurityModuleService; import org.hyperledger.besu.plugin.services.StorageService; import org.hyperledger.besu.plugin.services.TraceService; +import org.hyperledger.besu.plugin.services.TransactionPoolValidatorService; import org.hyperledger.besu.plugin.services.TransactionSelectionService; +import org.hyperledger.besu.plugin.services.TransactionSimulationService; import org.hyperledger.besu.plugin.services.exception.StorageException; import org.hyperledger.besu.plugin.services.metrics.MetricCategory; import org.hyperledger.besu.plugin.services.metrics.MetricCategoryRegistry; +import org.hyperledger.besu.plugin.services.p2p.P2PService; +import org.hyperledger.besu.plugin.services.rlp.RlpConverterService; import org.hyperledger.besu.plugin.services.securitymodule.SecurityModule; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.plugin.services.storage.PrivacyKeyValueStorageFactory; import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBPlugin; -import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelectorFactory; -import org.hyperledger.besu.plugin.services.txvalidator.PluginTransactionValidatorFactory; +import org.hyperledger.besu.plugin.services.sync.SynchronizationService; +import org.hyperledger.besu.plugin.services.transactionpool.TransactionPoolService; +import org.hyperledger.besu.services.BesuConfigurationImpl; import org.hyperledger.besu.services.BesuEventsImpl; import org.hyperledger.besu.services.BesuPluginContextImpl; import org.hyperledger.besu.services.BlockchainServiceImpl; +import org.hyperledger.besu.services.P2PServiceImpl; import org.hyperledger.besu.services.PermissioningServiceImpl; import org.hyperledger.besu.services.PicoCLIOptionsImpl; -import org.hyperledger.besu.services.PluginTransactionValidatorServiceImpl; import org.hyperledger.besu.services.PrivacyPluginServiceImpl; +import org.hyperledger.besu.services.RlpConverterServiceImpl; import org.hyperledger.besu.services.RpcEndpointServiceImpl; import org.hyperledger.besu.services.SecurityModuleServiceImpl; import org.hyperledger.besu.services.StorageServiceImpl; +import org.hyperledger.besu.services.SynchronizationServiceImpl; import org.hyperledger.besu.services.TraceServiceImpl; +import org.hyperledger.besu.services.TransactionPoolServiceImpl; +import org.hyperledger.besu.services.TransactionPoolValidatorServiceImpl; import org.hyperledger.besu.services.TransactionSelectionServiceImpl; +import org.hyperledger.besu.services.TransactionSimulationServiceImpl; import org.hyperledger.besu.services.kvstore.InMemoryStoragePlugin; import org.hyperledger.besu.util.InvalidConfigurationException; import org.hyperledger.besu.util.LogConfigurator; @@ -208,10 +210,9 @@ import java.net.InetAddress; import java.net.SocketException; import java.net.URI; +import java.net.URL; import java.net.UnknownHostException; import java.nio.file.Path; -import java.security.KeyManagementException; -import java.security.NoSuchAlgorithmException; import java.time.Clock; import java.util.ArrayList; import java.util.Arrays; @@ -222,27 +223,25 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.OptionalInt; import java.util.Set; import java.util.TreeMap; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableMap; -import com.google.common.io.Resources; import io.vertx.core.Vertx; import io.vertx.core.VertxOptions; import io.vertx.core.json.DecodeException; import io.vertx.core.metrics.MetricsOptions; +import org.apache.commons.net.util.SubnetUtils.SubnetInfo; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import picocli.AutoComplete; import picocli.CommandLine; @@ -265,8 +264,12 @@ synopsisHeading = "%n", descriptionHeading = "%n@|bold,fg(cyan) Description:|@%n%n", optionListHeading = "%n@|bold,fg(cyan) Options:|@%n", - footerHeading = "%n", - footer = "Besu is licensed under the Apache License 2.0") + footerHeading = "%nBesu is licensed under the Apache License 2.0%n", + footer = { + "%n%n@|fg(cyan) To get started quickly, just choose a network to sync and a profile to run with suggested defaults:|@", + "%n@|fg(cyan) for Mainnet|@ --network=mainnet --profile=[minimalist_staker|staker]", + "%nMore info and other profiles at https://besu.hyperledger.org%n" + }) public class BesuCommand implements DefaultCommandValues, Runnable { @SuppressWarnings("PrivateStaticFinalLoggers") @@ -307,7 +310,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable { final MiningOptions miningOptions = MiningOptions.create(); private final RunnerBuilder runnerBuilder; - private final BesuController.Builder controllerBuilderFactory; + private final BesuController.Builder controllerBuilder; private final BesuPluginContextImpl besuPluginContext; private final StorageServiceImpl storageService; private final SecurityModuleServiceImpl securityModuleService; @@ -324,14 +327,17 @@ public class BesuCommand implements DefaultCommandValues, Runnable { new PreSynchronizationTaskRunner(); private final Set allocatedPorts = new HashSet<>(); - private final PkiBlockCreationConfigurationProvider pkiBlockCreationConfigProvider; - private GenesisConfigOptions genesisConfigOptions; + private final Supplier genesisConfigFileSupplier = + Suppliers.memoize(this::readGenesisConfigFile); + private final Supplier genesisConfigOptionsSupplier = + Suppliers.memoize(this::readGenesisConfigOptions); + private final Supplier miningParametersSupplier = + Suppliers.memoize(this::getMiningParameters); private RocksDBPlugin rocksDBPlugin; private int maxPeers; private int maxRemoteInitiatedPeers; - private int peersLowerBound; // CLI options defined by user at runtime. // Options parsing is done with CLI library Picocli https://picocli.info/ @@ -351,30 +357,39 @@ public class BesuCommand implements DefaultCommandValues, Runnable { description = "The path to Besu data directory (default: ${DEFAULT-VALUE})") final Path dataPath = getDefaultBesuDataPath(this); - // Genesis file path with null default option if the option - // is not defined on command line as this default is handled by Runner + // Genesis file path with null default option. + // This default is handled by Runner // to use mainnet json file from resources as indicated in the // default network option - // Then we have no control over genesis default value here. + // Then we ignore genesis default value here. @CommandLine.Option( names = {"--genesis-file"}, paramLabel = MANDATORY_FILE_FORMAT_HELP, description = - "Genesis file. Setting this option makes --network option ignored and requires --network-id to be set.") + "Genesis file for your custom network. Setting this option requires --network-id to be set. (Cannot be used with --network)") private final File genesisFile = null; + @Option( + names = {"--genesis-state-hash-cache-enabled"}, + description = + "Use genesis state hash from data on startup if specified (default: ${DEFAULT-VALUE})") + private final Boolean genesisStateHashCacheEnabled = false; + @Option( names = "--identity", paramLabel = "", description = "Identification for this node in the Client ID", arity = "1") private final Optional identityString = Optional.empty(); + // P2P Discovery Option Group @CommandLine.ArgGroup(validate = false, heading = "@|bold P2P Discovery Options|@%n") P2PDiscoveryOptionGroup p2PDiscoveryOptionGroup = new P2PDiscoveryOptionGroup(); private final TransactionSelectionServiceImpl transactionSelectionServiceImpl; - private final PluginTransactionValidatorServiceImpl transactionValidatorServiceImpl; + private final TransactionPoolValidatorServiceImpl transactionValidatorServiceImpl; + private final TransactionSimulationServiceImpl transactionSimulationServiceImpl; + private final BlockchainServiceImpl blockchainServiceImpl; static class P2PDiscoveryOptionGroup { @@ -498,6 +513,19 @@ void setBannedNodeIds(final List values) { } } + // Boolean option to set that in a PoA network the bootnodes should always be queried during + // peer table refresh. If this flag is disabled bootnodes are only sent FINDN requests on first + // startup, meaning that an offline bootnode or network outage at the client can prevent it + // discovering any peers without a restart. + @Option( + names = {"--poa-discovery-retry-bootnodes"}, + description = + "Always use of bootnodes for discovery in PoA networks. Disabling this reverts " + + " to the same behaviour as non-PoA networks, where neighbours are only discovered from bootnodes on first startup." + + "(default: ${DEFAULT-VALUE})", + arity = "1") + private final Boolean poaDiscoveryRetryBootnodes = true; + private Collection bannedNodeIds = new ArrayList<>(); // Used to discover the default IP of the client. @@ -509,21 +537,30 @@ private InetAddress autoDiscoverDefaultIP() { return autoDiscoveredDefaultIP; } + + @Option( + names = {"--net-restrict"}, + arity = "1..*", + split = ",", + converter = SubnetInfoConverter.class, + description = + "Comma-separated list of allowed IP subnets (e.g., '192.168.1.0/24,10.0.0.0/8').") + private List allowedSubnets; } @Option( names = {"--sync-mode"}, paramLabel = MANDATORY_MODE_FORMAT_HELP, description = - "Synchronization mode, possible values are ${COMPLETION-CANDIDATES} (default: FAST if a --network is supplied and privacy isn't enabled. FULL otherwise.)") + "Synchronization mode, possible values are ${COMPLETION-CANDIDATES} (default: SNAP if a --network is supplied and privacy isn't enabled. FULL otherwise.)") private SyncMode syncMode = null; @Option( - names = {"--fast-sync-min-peers"}, + names = {"--sync-min-peers", "--fast-sync-min-peers"}, paramLabel = MANDATORY_INTEGER_FORMAT_HELP, description = - "Minimum number of peers required before starting fast sync. Has only effect on PoW networks. (default: ${DEFAULT-VALUE})") - private final Integer fastSyncMinPeerCount = FAST_SYNC_MIN_PEER_COUNT; + "Minimum number of peers required before starting sync. Has effect only on non-PoS networks. (default: ${DEFAULT-VALUE})") + private final Integer syncMinPeerCount = SYNC_MIN_PEER_COUNT; @Option( names = {"--network"}, @@ -534,6 +571,14 @@ private InetAddress autoDiscoverDefaultIP() { + " (default: ${DEFAULT-VALUE})") private final NetworkName network = null; + @Option( + names = {PROFILE_OPTION_NAME}, + paramLabel = PROFILE_FORMAT_HELP, + completionCandidates = ProfilesCompletionCandidates.class, + description = + "Overwrite default settings. Possible values are ${COMPLETION-CANDIDATES}. (default: none)") + private String profile = null; // don't set it as final due to picocli completion candidates + @Option( names = {"--nat-method"}, description = @@ -558,36 +603,14 @@ private InetAddress autoDiscoverDefaultIP() { arity = "1") private final Path kzgTrustedSetupFile = null; - @CommandLine.ArgGroup(validate = false, heading = "@|bold GraphQL Options|@%n") - GraphQlOptionGroup graphQlOptionGroup = new GraphQlOptionGroup(); - - static class GraphQlOptionGroup { - @Option( - names = {"--graphql-http-enabled"}, - description = "Set to start the GraphQL HTTP service (default: ${DEFAULT-VALUE})") - private final Boolean isGraphQLHttpEnabled = false; - - @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. - @Option( - names = {"--graphql-http-host"}, - paramLabel = MANDATORY_HOST_FORMAT_HELP, - description = "Host for GraphQL HTTP to listen on (default: ${DEFAULT-VALUE})", - arity = "1") - private String graphQLHttpHost; - - @Option( - names = {"--graphql-http-port"}, - paramLabel = MANDATORY_PORT_FORMAT_HELP, - description = "Port for GraphQL HTTP to listen on (default: ${DEFAULT-VALUE})", - arity = "1") - private final Integer graphQLHttpPort = DEFAULT_GRAPHQL_HTTP_PORT; + @Option( + names = {"--version-compatibility-protection"}, + description = + "Perform compatibility checks between the version of Besu being started and the version of Besu that last started with this data directory. (default: ${DEFAULT-VALUE})") + private Boolean versionCompatibilityProtection = null; - @Option( - names = {"--graphql-http-cors-origins"}, - description = "Comma separated origin domain URLs for CORS validation (default: none)") - protected final CorsAllowedOriginsProperty graphQLHttpCorsAllowedOrigins = - new CorsAllowedOriginsProperty(); - } + @CommandLine.ArgGroup(validate = false, heading = "@|bold GraphQL Options|@%n") + GraphQlOptions graphQlOptions = new GraphQlOptions(); // Engine JSON-PRC Options @CommandLine.ArgGroup(validate = false, heading = "@|bold Engine JSON-RPC Options|@%n") @@ -613,13 +636,6 @@ static class EngineRPCOptionGroup { description = "Path to file containing shared secret key for JWT signature verification") private final Path engineJwtKeyFile = null; - @Option( - names = {"--engine-jwt-enabled"}, - description = "deprecated option, engine jwt auth is enabled by default", - hidden = true) - @SuppressWarnings({"FieldCanBeFinal", "UnusedVariable"}) - private final Boolean deprecatedIsEngineAuthEnabled = true; - @Option( names = {"--engine-jwt-disabled"}, description = "Disable authentication for Engine APIs (default: ${DEFAULT-VALUE})") @@ -637,252 +653,15 @@ static class EngineRPCOptionGroup { // JSON-RPC HTTP Options @CommandLine.ArgGroup(validate = false, heading = "@|bold JSON-RPC HTTP Options|@%n") - JsonRPCHttpOptionGroup jsonRPCHttpOptionGroup = new JsonRPCHttpOptionGroup(); - - static class JsonRPCHttpOptionGroup { - @Option( - names = {"--rpc-http-enabled"}, - description = "Set to start the JSON-RPC HTTP service (default: ${DEFAULT-VALUE})") - private final Boolean isRpcHttpEnabled = false; - - @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. - @Option( - names = {"--rpc-http-host"}, - paramLabel = MANDATORY_HOST_FORMAT_HELP, - description = "Host for JSON-RPC HTTP to listen on (default: ${DEFAULT-VALUE})", - arity = "1") - private String rpcHttpHost; - - @Option( - names = {"--rpc-http-port"}, - paramLabel = MANDATORY_PORT_FORMAT_HELP, - description = "Port for JSON-RPC HTTP to listen on (default: ${DEFAULT-VALUE})", - arity = "1") - private final Integer rpcHttpPort = DEFAULT_JSON_RPC_PORT; - - @Option( - names = {"--rpc-http-max-active-connections"}, - description = - "Maximum number of HTTP connections allowed for JSON-RPC (default: ${DEFAULT-VALUE}). Once this limit is reached, incoming connections will be rejected.", - arity = "1") - private final Integer rpcHttpMaxConnections = DEFAULT_HTTP_MAX_CONNECTIONS; - - // A list of origins URLs that are accepted by the JsonRpcHttpServer (CORS) - @Option( - names = {"--rpc-http-cors-origins"}, - description = "Comma separated origin domain URLs for CORS validation (default: none)") - private final CorsAllowedOriginsProperty rpcHttpCorsAllowedOrigins = - new CorsAllowedOriginsProperty(); - - @Option( - names = {"--rpc-http-api", "--rpc-http-apis"}, - paramLabel = "", - split = " {0,1}, {0,1}", - arity = "1..*", - description = - "Comma separated list of APIs to enable on JSON-RPC HTTP service (default: ${DEFAULT-VALUE})") - private final List rpcHttpApis = DEFAULT_RPC_APIS; - - @Option( - names = {"--rpc-http-api-method-no-auth", "--rpc-http-api-methods-no-auth"}, - paramLabel = "", - split = " {0,1}, {0,1}", - arity = "1..*", - description = - "Comma separated list of API methods to exclude from RPC authentication services, RPC HTTP authentication must be enabled") - private final List rpcHttpApiMethodsNoAuth = new ArrayList(); - - @Option( - names = {"--rpc-http-authentication-enabled"}, - description = - "Require authentication for the JSON-RPC HTTP service (default: ${DEFAULT-VALUE})") - private final Boolean isRpcHttpAuthenticationEnabled = false; - - @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. - @CommandLine.Option( - names = {"--rpc-http-authentication-credentials-file"}, - paramLabel = MANDATORY_FILE_FORMAT_HELP, - description = - "Storage file for JSON-RPC HTTP authentication credentials (default: ${DEFAULT-VALUE})", - arity = "1") - private String rpcHttpAuthenticationCredentialsFile = null; - - @CommandLine.Option( - names = {"--rpc-http-authentication-jwt-public-key-file"}, - paramLabel = MANDATORY_FILE_FORMAT_HELP, - description = "JWT public key file for JSON-RPC HTTP authentication", - arity = "1") - private final File rpcHttpAuthenticationPublicKeyFile = null; - - @Option( - names = {"--rpc-http-authentication-jwt-algorithm"}, - description = - "Encryption algorithm used for HTTP JWT public key. Possible values are ${COMPLETION-CANDIDATES}" - + " (default: ${DEFAULT-VALUE})", - arity = "1") - private final JwtAlgorithm rpcHttpAuthenticationAlgorithm = DEFAULT_JWT_ALGORITHM; - - @Option( - names = {"--rpc-http-tls-enabled"}, - description = "Enable TLS for the JSON-RPC HTTP service (default: ${DEFAULT-VALUE})") - private final Boolean isRpcHttpTlsEnabled = false; - - @Option( - names = {"--rpc-http-tls-keystore-file"}, - paramLabel = MANDATORY_FILE_FORMAT_HELP, - description = - "Keystore (PKCS#12) containing key/certificate for the JSON-RPC HTTP service. Required if TLS is enabled.") - private final Path rpcHttpTlsKeyStoreFile = null; - - @Option( - names = {"--rpc-http-tls-keystore-password-file"}, - paramLabel = MANDATORY_FILE_FORMAT_HELP, - description = - "File containing password to unlock keystore for the JSON-RPC HTTP service. Required if TLS is enabled.") - private final Path rpcHttpTlsKeyStorePasswordFile = null; - - @Option( - names = {"--rpc-http-tls-client-auth-enabled"}, - description = - "Enable TLS client authentication for the JSON-RPC HTTP service (default: ${DEFAULT-VALUE})") - private final Boolean isRpcHttpTlsClientAuthEnabled = false; - - @Option( - names = {"--rpc-http-tls-known-clients-file"}, - paramLabel = MANDATORY_FILE_FORMAT_HELP, - description = - "Path to file containing clients certificate common name and fingerprint for client authentication") - private final Path rpcHttpTlsKnownClientsFile = null; - - @Option( - names = {"--rpc-http-tls-ca-clients-enabled"}, - description = - "Enable to accept clients certificate signed by a valid CA for client authentication (default: ${DEFAULT-VALUE})") - private final Boolean isRpcHttpTlsCAClientsEnabled = false; - - @Option( - names = {"--rpc-http-tls-protocol", "--rpc-http-tls-protocols"}, - description = - "Comma separated list of TLS protocols to support (default: ${DEFAULT-VALUE})", - split = ",", - arity = "1..*") - private final List rpcHttpTlsProtocols = new ArrayList<>(DEFAULT_TLS_PROTOCOLS); - - @Option( - names = {"--rpc-http-tls-cipher-suite", "--rpc-http-tls-cipher-suites"}, - description = "Comma separated list of TLS cipher suites to support", - split = ",", - arity = "1..*") - private final List rpcHttpTlsCipherSuites = new ArrayList<>(); - - @CommandLine.Option( - names = {"--rpc-http-max-batch-size"}, - paramLabel = MANDATORY_INTEGER_FORMAT_HELP, - description = - "Specifies the maximum number of requests in a single RPC batch request via RPC. -1 specifies no limit (default: ${DEFAULT-VALUE})") - private final Integer rpcHttpMaxBatchSize = DEFAULT_HTTP_MAX_BATCH_SIZE; - - @CommandLine.Option( - names = {"--rpc-http-max-request-content-length"}, - paramLabel = MANDATORY_LONG_FORMAT_HELP, - description = "Specifies the maximum request content length. (default: ${DEFAULT-VALUE})") - private final Long rpcHttpMaxRequestContentLength = DEFAULT_MAX_REQUEST_CONTENT_LENGTH; - - @Option( - names = {"--json-pretty-print-enabled"}, - description = "Enable JSON pretty print format (default: ${DEFAULT-VALUE})") - private final Boolean prettyJsonEnabled = DEFAULT_PRETTY_JSON_ENABLED; - } + JsonRpcHttpOptions jsonRpcHttpOptions = new JsonRpcHttpOptions(); // JSON-RPC Websocket Options @CommandLine.ArgGroup(validate = false, heading = "@|bold JSON-RPC Websocket Options|@%n") - JsonRPCWebsocketOptionGroup jsonRPCWebsocketOptionGroup = new JsonRPCWebsocketOptionGroup(); - - static class JsonRPCWebsocketOptionGroup { - @Option( - names = {"--rpc-ws-authentication-jwt-algorithm"}, - description = - "Encryption algorithm used for Websockets JWT public key. Possible values are ${COMPLETION-CANDIDATES}" - + " (default: ${DEFAULT-VALUE})", - arity = "1") - private final JwtAlgorithm rpcWebsocketsAuthenticationAlgorithm = DEFAULT_JWT_ALGORITHM; - - @Option( - names = {"--rpc-ws-enabled"}, - description = "Set to start the JSON-RPC WebSocket service (default: ${DEFAULT-VALUE})") - private final Boolean isRpcWsEnabled = false; - - @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. - @Option( - names = {"--rpc-ws-host"}, - paramLabel = MANDATORY_HOST_FORMAT_HELP, - description = - "Host for JSON-RPC WebSocket service to listen on (default: ${DEFAULT-VALUE})", - arity = "1") - private String rpcWsHost; - - @Option( - names = {"--rpc-ws-port"}, - paramLabel = MANDATORY_PORT_FORMAT_HELP, - description = - "Port for JSON-RPC WebSocket service to listen on (default: ${DEFAULT-VALUE})", - arity = "1") - private final Integer rpcWsPort = DEFAULT_WEBSOCKET_PORT; - - @Option( - names = {"--rpc-ws-max-frame-size"}, - description = - "Maximum size in bytes for JSON-RPC WebSocket frames (default: ${DEFAULT-VALUE}). If this limit is exceeded, the websocket will be disconnected.", - arity = "1") - private final Integer rpcWsMaxFrameSize = DEFAULT_WS_MAX_FRAME_SIZE; - - @Option( - names = {"--rpc-ws-max-active-connections"}, - description = - "Maximum number of WebSocket connections allowed for JSON-RPC (default: ${DEFAULT-VALUE}). Once this limit is reached, incoming connections will be rejected.", - arity = "1") - private final Integer rpcWsMaxConnections = DEFAULT_WS_MAX_CONNECTIONS; - - @Option( - names = {"--rpc-ws-api", "--rpc-ws-apis"}, - paramLabel = "", - split = " {0,1}, {0,1}", - arity = "1..*", - description = - "Comma separated list of APIs to enable on JSON-RPC WebSocket service (default: ${DEFAULT-VALUE})") - private final List rpcWsApis = DEFAULT_RPC_APIS; - - @Option( - names = {"--rpc-ws-api-methods-no-auth", "--rpc-ws-api-method-no-auth"}, - paramLabel = "", - split = " {0,1}, {0,1}", - arity = "1..*", - description = - "Comma separated list of RPC methods to exclude from RPC authentication services, RPC WebSocket authentication must be enabled") - private final List rpcWsApiMethodsNoAuth = new ArrayList(); - - @Option( - names = {"--rpc-ws-authentication-enabled"}, - description = - "Require authentication for the JSON-RPC WebSocket service (default: ${DEFAULT-VALUE})") - private final Boolean isRpcWsAuthenticationEnabled = false; + RpcWebsocketOptions rpcWebsocketOptions = new RpcWebsocketOptions(); - @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. - @CommandLine.Option( - names = {"--rpc-ws-authentication-credentials-file"}, - paramLabel = MANDATORY_FILE_FORMAT_HELP, - description = - "Storage file for JSON-RPC WebSocket authentication credentials (default: ${DEFAULT-VALUE})", - arity = "1") - private String rpcWsAuthenticationCredentialsFile = null; - - @CommandLine.Option( - names = {"--rpc-ws-authentication-jwt-public-key-file"}, - paramLabel = MANDATORY_FILE_FORMAT_HELP, - description = "JWT public key file for JSON-RPC WebSocket authentication", - arity = "1") - private final File rpcWsAuthenticationPublicKeyFile = null; - } + // In-Process RPC Options + @CommandLine.ArgGroup(validate = false, heading = "@|bold In-Process RPC Options|@%n") + InProcessRpcOptions inProcessRpcOptions = InProcessRpcOptions.create(); // Privacy Options Group @CommandLine.ArgGroup(validate = false, heading = "@|bold Privacy Options|@%n") @@ -952,92 +731,17 @@ static class PrivacyOptionGroup { private final Boolean isFlexiblePrivacyGroupsEnabled = false; @Option( - hidden = true, - names = {"--privacy-onchain-groups-enabled"}, + names = {"--privacy-nonce-always-increments"}, description = - "!!DEPRECATED!! Use `--privacy-flexible-groups-enabled` instead. Enable flexible (onchain) privacy groups (default: ${DEFAULT-VALUE})") - private final Boolean isOnchainPrivacyGroupsEnabled = false; + "Enable private nonce " + + "incrementation even if the transaction didn't succeeded (default: ${DEFAULT-VALUE})") + private final Boolean isPrivateNonceAlwaysIncrementsEnabled = false; } // Metrics Option Group @CommandLine.ArgGroup(validate = false, heading = "@|bold Metrics Options|@%n") MetricsOptionGroup metricsOptionGroup = new MetricsOptionGroup(); - static class MetricsOptionGroup { - @Option( - names = {"--metrics-enabled"}, - description = "Set to start the metrics exporter (default: ${DEFAULT-VALUE})") - private final Boolean isMetricsEnabled = false; - - @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. - @Option( - names = {"--metrics-protocol"}, - description = - "Metrics protocol, one of PROMETHEUS, OPENTELEMETRY or NONE. (default: ${DEFAULT-VALUE})") - private MetricsProtocol metricsProtocol = PROMETHEUS; - - @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. - @Option( - names = {"--metrics-host"}, - paramLabel = MANDATORY_HOST_FORMAT_HELP, - description = "Host for the metrics exporter to listen on (default: ${DEFAULT-VALUE})", - arity = "1") - private String metricsHost; - - @Option( - names = {"--metrics-port"}, - paramLabel = MANDATORY_PORT_FORMAT_HELP, - description = "Port for the metrics exporter to listen on (default: ${DEFAULT-VALUE})", - arity = "1") - private final Integer metricsPort = DEFAULT_METRICS_PORT; - - @Option( - names = {"--metrics-category", "--metrics-categories"}, - paramLabel = "", - split = ",", - arity = "1..*", - description = - "Comma separated list of categories to track metrics for (default: ${DEFAULT-VALUE})") - private final Set metricCategories = DEFAULT_METRIC_CATEGORIES; - - @Option( - names = {"--metrics-push-enabled"}, - description = "Enable the metrics push gateway integration (default: ${DEFAULT-VALUE})") - private final Boolean isMetricsPushEnabled = false; - - @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. - @Option( - names = {"--metrics-push-host"}, - paramLabel = MANDATORY_HOST_FORMAT_HELP, - description = - "Host of the Prometheus Push Gateway for push mode (default: ${DEFAULT-VALUE})", - arity = "1") - private String metricsPushHost; - - @Option( - names = {"--metrics-push-port"}, - paramLabel = MANDATORY_PORT_FORMAT_HELP, - description = - "Port of the Prometheus Push Gateway for push mode (default: ${DEFAULT-VALUE})", - arity = "1") - private final Integer metricsPushPort = DEFAULT_METRICS_PUSH_PORT; - - @Option( - names = {"--metrics-push-interval"}, - paramLabel = MANDATORY_INTEGER_FORMAT_HELP, - description = - "Interval in seconds to push metrics when in push mode (default: ${DEFAULT-VALUE})", - arity = "1") - private final Integer metricsPushInterval = 15; - - @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. - @Option( - names = {"--metrics-push-prometheus-job"}, - description = "Job name to use when in push mode (default: ${DEFAULT-VALUE})", - arity = "1") - private String metricsPrometheusJob = "besu-client"; - } - @Option( names = {"--host-allowlist"}, paramLabel = "[,...]... or * or all", @@ -1067,70 +771,9 @@ static class MetricsOptionGroup { "How deep a chain reorganization must be in order for it to be logged (default: ${DEFAULT-VALUE})") private final Long reorgLoggingThreshold = 6L; - @Option( - names = {"--pruning-enabled"}, - description = - "Enable disk-space saving optimization that removes old state that is unlikely to be required (default: ${DEFAULT-VALUE})") - private final Boolean pruningEnabled = false; - // Permission Option Group @CommandLine.ArgGroup(validate = false, heading = "@|bold Permissions Options|@%n") - PermissionsOptionGroup permissionsOptionGroup = new PermissionsOptionGroup(); - - static class PermissionsOptionGroup { - @Option( - names = {"--permissions-nodes-config-file-enabled"}, - description = "Enable node level permissions (default: ${DEFAULT-VALUE})") - private final Boolean permissionsNodesEnabled = false; - - @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. - @CommandLine.Option( - names = {"--permissions-nodes-config-file"}, - description = - "Node permissioning config TOML file (default: a file named \"permissions_config.toml\" in the Besu data folder)") - private String nodePermissionsConfigFile = null; - - @Option( - names = {"--permissions-accounts-config-file-enabled"}, - description = "Enable account level permissions (default: ${DEFAULT-VALUE})") - private final Boolean permissionsAccountsEnabled = false; - - @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. - @CommandLine.Option( - names = {"--permissions-accounts-config-file"}, - description = - "Account permissioning config TOML file (default: a file named \"permissions_config.toml\" in the Besu data folder)") - private String accountPermissionsConfigFile = null; - - @Option( - names = {"--permissions-nodes-contract-address"}, - description = "Address of the node permissioning smart contract", - arity = "1") - private final Address permissionsNodesContractAddress = null; - - @Option( - names = {"--permissions-nodes-contract-version"}, - description = "Version of the EEA Node Permissioning interface (default: ${DEFAULT-VALUE})") - private final Integer permissionsNodesContractVersion = 1; - - @Option( - names = {"--permissions-nodes-contract-enabled"}, - description = - "Enable node level permissions via smart contract (default: ${DEFAULT-VALUE})") - private final Boolean permissionsNodesContractEnabled = false; - - @Option( - names = {"--permissions-accounts-contract-address"}, - description = "Address of the account permissioning smart contract", - arity = "1") - private final Address permissionsAccountsContractAddress = null; - - @Option( - names = {"--permissions-accounts-contract-enabled"}, - description = - "Enable account level permissions via smart contract (default: ${DEFAULT-VALUE})") - private final Boolean permissionsAccountsContractEnabled = false; - } + PermissionsOptions permissionsOptions = new PermissionsOptions(); @Option( names = {"--revert-reason-enabled"}, @@ -1177,45 +820,15 @@ static class PermissionsOptionGroup { private final Map genesisConfigOverrides = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - @Option( - names = {"--pruning-blocks-retained"}, - defaultValue = "1024", - paramLabel = "", - description = - "Minimum number of recent blocks for which to keep entire world state (default: ${DEFAULT-VALUE})", - arity = "1") - private final Integer pruningBlocksRetained = PrunerConfiguration.DEFAULT_PRUNING_BLOCKS_RETAINED; - - @Option( - names = {"--pruning-block-confirmations"}, - defaultValue = "10", - paramLabel = "", - description = - "Minimum number of confirmations on a block before marking begins (default: ${DEFAULT-VALUE})", - arity = "1") - private final Integer pruningBlockConfirmations = - PrunerConfiguration.DEFAULT_PRUNING_BLOCK_CONFIRMATIONS; - @CommandLine.Option( names = {"--pid-path"}, paramLabel = MANDATORY_PATH_FORMAT_HELP, description = "Path to PID file (optional)") private final Path pidPath = null; - @CommandLine.Option( - names = {"--api-gas-price-blocks"}, - description = "Number of blocks to consider for eth_gasPrice (default: ${DEFAULT-VALUE})") - private final Long apiGasPriceBlocks = 100L; - - @CommandLine.Option( - names = {"--api-gas-price-percentile"}, - description = "Percentile value to measure for eth_gasPrice (default: ${DEFAULT-VALUE})") - private final Double apiGasPricePercentile = 50.0; - - @CommandLine.Option( - names = {"--api-gas-price-max"}, - description = "Maximum gas price for eth_gasPrice (default: ${DEFAULT-VALUE})") - private final Long apiGasPriceMax = 500_000_000_000L; + // API Configuration Option Group + @CommandLine.ArgGroup(validate = false, heading = "@|bold API Configuration Options|@%n") + ApiConfigurationOptions apiConfigurationOptions = new ApiConfigurationOptions(); @CommandLine.Option( names = {"--static-nodes-file"}, @@ -1224,18 +837,6 @@ static class PermissionsOptionGroup { "Specifies the static node file containing the static nodes for this node to connect to") private final Path staticNodesFile = null; - @CommandLine.Option( - names = {"--rpc-max-logs-range"}, - description = - "Specifies the maximum number of blocks to retrieve logs from via RPC. Must be >=0. 0 specifies no limit (default: ${DEFAULT-VALUE})") - private final Long rpcMaxLogsRange = 5000L; - - @CommandLine.Option( - names = {"--rpc-gas-cap"}, - description = - "Specifies the gasLimit cap for transaction simulation RPC methods. Must be >=0. 0 specifies no limit (default: ${DEFAULT-VALUE})") - private final Long rpcGasCap = 0L; - @CommandLine.Option( names = {"--cache-last-blocks"}, description = "Specifies the number of last blocks to cache (default: ${DEFAULT-VALUE})") @@ -1243,7 +844,9 @@ static class PermissionsOptionGroup { @Mixin private P2PTLSConfigOptions p2pTLSConfigOptions; - @Mixin private PkiBlockCreationOptions pkiBlockCreationOptions; + // Plugins Configuration Option Group + @CommandLine.ArgGroup(validate = false) + PluginsConfigurationOptions pluginsConfigurationOptions = new PluginsConfigurationOptions(); private EthNetworkConfig ethNetworkConfig; private JsonRpcConfiguration jsonRpcConfiguration; @@ -1251,22 +854,19 @@ static class PermissionsOptionGroup { private GraphQLConfiguration graphQLConfiguration; private WebSocketConfiguration webSocketConfiguration; private JsonRpcIpcConfiguration jsonRpcIpcConfiguration; + private InProcessRpcConfiguration inProcessRpcConfiguration; private ApiConfiguration apiConfiguration; private MetricsConfiguration metricsConfiguration; private Optional permissioningConfiguration; private Optional p2pTLSConfiguration; + private DataStorageConfiguration dataStorageConfiguration; private Collection staticNodes; private BesuController besuController; - private BesuConfiguration pluginCommonConfiguration; - private MiningParameters miningParameters; + private BesuConfigurationImpl pluginCommonConfiguration; - private BesuComponent besuComponent; private final Supplier metricsSystem = - Suppliers.memoize( - () -> - besuComponent == null || besuComponent.getObservableMetricsSystem() == null - ? MetricsSystemFactory.create(metricsConfiguration()) - : besuComponent.getObservableMetricsSystem()); + Suppliers.memoize(() -> MetricsSystemFactory.create(metricsConfiguration())); + private Vertx vertx; private EnodeDnsConfiguration enodeDnsConfiguration; private KeyValueStorageProvider keyValueStorageProvider; @@ -1274,100 +874,109 @@ static class PermissionsOptionGroup { /** * Besu command constructor. * - * @param besuComponent BesuComponent which acts as our application context * @param rlpBlockImporter RlpBlockImporter supplier * @param jsonBlockImporterFactory instance of {@code Function} * @param rlpBlockExporterFactory instance of {@code Function} * @param runnerBuilder instance of RunnerBuilder - * @param controllerBuilderFactory instance of BesuController.Builder + * @param controllerBuilder instance of BesuController.Builder * @param besuPluginContext instance of BesuPluginContextImpl * @param environment Environment variables map + * @param commandLogger instance of Logger for outputting to the CLI */ public BesuCommand( - final BesuComponent besuComponent, final Supplier rlpBlockImporter, final Function jsonBlockImporterFactory, final Function rlpBlockExporterFactory, final RunnerBuilder runnerBuilder, - final BesuController.Builder controllerBuilderFactory, + final BesuController.Builder controllerBuilder, final BesuPluginContextImpl besuPluginContext, - final Map environment) { + final Map environment, + final Logger commandLogger) { this( - besuComponent, rlpBlockImporter, jsonBlockImporterFactory, rlpBlockExporterFactory, runnerBuilder, - controllerBuilderFactory, + controllerBuilder, besuPluginContext, environment, new StorageServiceImpl(), new SecurityModuleServiceImpl(), new PermissioningServiceImpl(), new PrivacyPluginServiceImpl(), - new PkiBlockCreationConfigurationProvider(), new RpcEndpointServiceImpl(), new TransactionSelectionServiceImpl(), - new PluginTransactionValidatorServiceImpl()); + new TransactionPoolValidatorServiceImpl(), + new TransactionSimulationServiceImpl(), + new BlockchainServiceImpl(), + commandLogger); } /** * Overloaded Besu command constructor visible for testing. * - * @param besuComponent BesuComponent which acts as our application context * @param rlpBlockImporter RlpBlockImporter supplier * @param jsonBlockImporterFactory instance of {@code Function} * @param rlpBlockExporterFactory instance of {@code Function} * @param runnerBuilder instance of RunnerBuilder - * @param controllerBuilderFactory instance of BesuController.Builder + * @param controllerBuilder instance of BesuController.Builder * @param besuPluginContext instance of BesuPluginContextImpl * @param environment Environment variables map * @param storageService instance of StorageServiceImpl * @param securityModuleService instance of SecurityModuleServiceImpl * @param permissioningService instance of PermissioningServiceImpl * @param privacyPluginService instance of PrivacyPluginServiceImpl - * @param pkiBlockCreationConfigProvider instance of PkiBlockCreationConfigurationProvider * @param rpcEndpointServiceImpl instance of RpcEndpointServiceImpl * @param transactionSelectionServiceImpl instance of TransactionSelectionServiceImpl * @param transactionValidatorServiceImpl instance of TransactionValidatorServiceImpl + * @param transactionSimulationServiceImpl instance of TransactionSimulationServiceImpl + * @param blockchainServiceImpl instance of BlockchainServiceImpl + * @param commandLogger instance of Logger for outputting to the CLI */ @VisibleForTesting protected BesuCommand( - final BesuComponent besuComponent, final Supplier rlpBlockImporter, final Function jsonBlockImporterFactory, final Function rlpBlockExporterFactory, final RunnerBuilder runnerBuilder, - final BesuController.Builder controllerBuilderFactory, + final BesuController.Builder controllerBuilder, final BesuPluginContextImpl besuPluginContext, final Map environment, final StorageServiceImpl storageService, final SecurityModuleServiceImpl securityModuleService, final PermissioningServiceImpl permissioningService, final PrivacyPluginServiceImpl privacyPluginService, - final PkiBlockCreationConfigurationProvider pkiBlockCreationConfigProvider, final RpcEndpointServiceImpl rpcEndpointServiceImpl, final TransactionSelectionServiceImpl transactionSelectionServiceImpl, - final PluginTransactionValidatorServiceImpl transactionValidatorServiceImpl) { - this.besuComponent = besuComponent; - this.logger = besuComponent.getBesuCommandLogger(); + final TransactionPoolValidatorServiceImpl transactionValidatorServiceImpl, + final TransactionSimulationServiceImpl transactionSimulationServiceImpl, + final BlockchainServiceImpl blockchainServiceImpl, + final Logger commandLogger) { + + this.logger = commandLogger; this.rlpBlockImporter = rlpBlockImporter; this.rlpBlockExporterFactory = rlpBlockExporterFactory; this.jsonBlockImporterFactory = jsonBlockImporterFactory; this.runnerBuilder = runnerBuilder; - this.controllerBuilderFactory = controllerBuilderFactory; + this.controllerBuilder = controllerBuilder; this.besuPluginContext = besuPluginContext; this.environment = environment; this.storageService = storageService; this.securityModuleService = securityModuleService; this.permissioningService = permissioningService; this.privacyPluginService = privacyPluginService; - pluginCommonConfiguration = new BesuCommandConfigurationService(); - besuPluginContext.addService(BesuConfiguration.class, pluginCommonConfiguration); - this.pkiBlockCreationConfigProvider = pkiBlockCreationConfigProvider; + if (besuPluginContext.getService(BesuConfigurationImpl.class).isPresent()) { + this.pluginCommonConfiguration = + besuPluginContext.getService(BesuConfigurationImpl.class).get(); + } else { + this.pluginCommonConfiguration = new BesuConfigurationImpl(); + besuPluginContext.addService(BesuConfiguration.class, this.pluginCommonConfiguration); + } this.rpcEndpointServiceImpl = rpcEndpointServiceImpl; this.transactionSelectionServiceImpl = transactionSelectionServiceImpl; this.transactionValidatorServiceImpl = transactionValidatorServiceImpl; + this.transactionSimulationServiceImpl = transactionSimulationServiceImpl; + this.blockchainServiceImpl = blockchainServiceImpl; } /** @@ -1380,6 +989,16 @@ protected BesuCommand( * @param args arguments to Besu command * @return success or failure exit code. */ + /** + * Parses command line arguments and configures the application accordingly. + * + * @param resultHandler The strategy to handle the execution result. + * @param parameterExceptionHandler Handler for exceptions related to command line parameters. + * @param executionExceptionHandler Handler for exceptions during command execution. + * @param in The input stream for commands. + * @param args The command line arguments. + * @return The execution result status code. + */ public int parse( final IExecutionStrategy resultHandler, final BesuParameterExceptionHandler parameterExceptionHandler, @@ -1387,18 +1006,76 @@ public int parse( final InputStream in, final String... args) { + initializeCommandLineSettings(in); + + // Create the execution strategy chain. + final IExecutionStrategy executeTask = createExecuteTask(resultHandler); + final IExecutionStrategy pluginRegistrationTask = createPluginRegistrationTask(executeTask); + final IExecutionStrategy setDefaultValueProviderTask = + createDefaultValueProviderTask(pluginRegistrationTask); + + // 1- Config default value provider + // 2- Register plugins + // 3- Execute command + return executeCommandLine( + setDefaultValueProviderTask, parameterExceptionHandler, executionExceptionHandler, args); + } + + private void initializeCommandLineSettings(final InputStream in) { toCommandLine(); + // Automatically adjust the width of usage messages to the terminal width. + commandLine.getCommandSpec().usageMessage().autoWidth(true); handleStableOptions(); addSubCommands(in); registerConverters(); handleUnstableOptions(); preparePlugins(); + } - final int exitCode = - parse(resultHandler, executionExceptionHandler, parameterExceptionHandler, args); + private IExecutionStrategy createExecuteTask(final IExecutionStrategy nextStep) { + return parseResult -> { + commandLine.setExecutionStrategy(nextStep); + // At this point we don't allow unmatched options since plugins were already registered + commandLine.setUnmatchedArgumentsAllowed(false); + return commandLine.execute(parseResult.originalArgs().toArray(new String[0])); + }; + } - return exitCode; + private IExecutionStrategy createPluginRegistrationTask(final IExecutionStrategy nextStep) { + return parseResult -> { + PluginConfiguration configuration = + PluginsConfigurationOptions.fromCommandLine(parseResult.commandSpec().commandLine()); + besuPluginContext.registerPlugins(configuration); + commandLine.setExecutionStrategy(nextStep); + return commandLine.execute(parseResult.originalArgs().toArray(new String[0])); + }; + } + + private IExecutionStrategy createDefaultValueProviderTask(final IExecutionStrategy nextStep) { + return new ConfigDefaultValueProviderStrategy(nextStep, environment); + } + + /** + * Executes the command line with the provided execution strategy and exception handlers. + * + * @param executionStrategy The execution strategy to use. + * @param args The command line arguments. + * @return The execution result status code. + */ + private int executeCommandLine( + final IExecutionStrategy executionStrategy, + final BesuParameterExceptionHandler parameterExceptionHandler, + final BesuExecutionExceptionHandler executionExceptionHandler, + final String... args) { + return commandLine + .setExecutionStrategy(executionStrategy) + .setParameterExceptionHandler(parameterExceptionHandler) + .setExecutionExceptionHandler(executionExceptionHandler) + // As this happens before the plugins registration and plugins can add options, we must + // allow unmatched options + .setUnmatchedArgumentsAllowed(true) + .execute(args); } /** Used by Dagger to parse all options into a commandline instance. */ @@ -1413,14 +1090,9 @@ public void run() { if (network != null && network.isDeprecated()) { logger.warn(NetworkDeprecationMessage.generate(network)); } - try { configureLogging(true); - if (genesisFile != null) { - genesisConfigOptions = readGenesisConfigOptions(); - } - // set merge config on the basis of genesis config setMergeConfigOptions(); @@ -1434,21 +1106,30 @@ public void run() { vertx = createVertx(createVertxOptions(metricsSystem.get())); validateOptions(); + configure(); + + // If we're not running against a named network, or if version compat protection has been + // explicitly enabled, perform compatibility check + VersionMetadata.versionCompatibilityChecks(versionCompatibilityProtection, dataDir()); + configureNativeLibs(); - besuController = initController(); + besuController = buildController(); besuPluginContext.beforeExternalServices(); final var runner = buildRunner(); runner.startExternalServices(); - startPlugins(); - validatePluginOptions(); + startPlugins(runner); + validatePrivacyPluginOptions(); setReleaseMetrics(); preSynchronization(); runner.startEthereumMainLoop(); + + besuPluginContext.afterExternalServicesMainLoop(); + runner.awaitStop(); } catch (final Exception e) { @@ -1458,7 +1139,7 @@ public void run() { } @VisibleForTesting - void setBesuConfiguration(final BesuConfiguration pluginCommonConfiguration) { + void setBesuConfiguration(final BesuConfigurationImpl pluginCommonConfiguration) { this.pluginCommonConfiguration = pluginCommonConfiguration; } @@ -1470,6 +1151,8 @@ private void addSubCommands(final InputStream in) { jsonBlockImporterFactory, rlpBlockExporterFactory, commandLine.getOut())); + commandLine.addSubcommand( + TxParseSubCommand.COMMAND_NAME, new TxParseSubCommand(commandLine.getOut())); commandLine.addSubcommand( PublicKeySubCommand.COMMAND_NAME, new PublicKeySubCommand(commandLine.getOut())); commandLine.addSubcommand( @@ -1548,15 +1231,16 @@ private void preparePlugins() { besuPluginContext.addService( TransactionSelectionService.class, transactionSelectionServiceImpl); besuPluginContext.addService( - PluginTransactionValidatorService.class, transactionValidatorServiceImpl); + TransactionPoolValidatorService.class, transactionValidatorServiceImpl); + besuPluginContext.addService( + TransactionSimulationService.class, transactionSimulationServiceImpl); + besuPluginContext.addService(BlockchainService.class, blockchainServiceImpl); // register built-in plugins rocksDBPlugin = new RocksDBPlugin(); rocksDBPlugin.register(besuPluginContext); new InMemoryStoragePlugin().register(besuPluginContext); - besuPluginContext.registerPlugins(pluginsDir()); - metricCategoryRegistry .getMetricCategories() .forEach(metricCategoryConverter::addRegistryCategory); @@ -1570,8 +1254,6 @@ private SecurityModule defaultSecurityModule() { return new KeyPairSecurityModule(loadKeyPair(nodePrivateKeyFileOption.getNodePrivateKeyFile())); } - // loadKeyPair() is public because it is accessed by subcommands - /** * Load key pair from private key. Visible to be accessed by subcommands. * @@ -1582,26 +1264,6 @@ public KeyPair loadKeyPair(final File nodePrivateKeyFile) { return KeyPairUtil.loadKeyPair(resolveNodePrivateKeyFile(nodePrivateKeyFile)); } - private int parse( - final CommandLine.IExecutionStrategy resultHandler, - final BesuExecutionExceptionHandler besuExecutionExceptionHandler, - final BesuParameterExceptionHandler besuParameterExceptionHandler, - final String... args) { - // Create a handler that will search for a config file option and use it for - // default values - // and eventually it will run regular parsing of the remaining options. - - final ConfigOptionSearchAndRunHandler configParsingHandler = - new ConfigOptionSearchAndRunHandler( - resultHandler, besuParameterExceptionHandler, environment); - - return commandLine - .setExecutionStrategy(configParsingHandler) - .setParameterExceptionHandler(besuParameterExceptionHandler) - .setExecutionExceptionHandler(besuExecutionExceptionHandler) - .execute(args); - } - private void preSynchronization() { preSynchronizationTaskRunner.runTasks(besuController); } @@ -1621,6 +1283,7 @@ private Runner buildRunner() { engineJsonRpcConfiguration, webSocketConfiguration, jsonRpcIpcConfiguration, + inProcessRpcConfiguration, apiConfiguration, metricsConfiguration, permissioningConfiguration, @@ -1628,33 +1291,63 @@ private Runner buildRunner() { pidPath); } - private void startPlugins() { + private void startPlugins(final Runner runner) { + blockchainServiceImpl.init( + besuController.getProtocolContext(), besuController.getProtocolSchedule()); + transactionSimulationServiceImpl.init( + besuController.getProtocolContext().getBlockchain(), + new TransactionSimulator( + besuController.getProtocolContext().getBlockchain(), + besuController.getProtocolContext().getWorldStateArchive(), + besuController.getProtocolSchedule(), + apiConfiguration.getGasCap())); + rpcEndpointServiceImpl.init(runner.getInProcessRpcMethods()); + besuPluginContext.addService( BesuEvents.class, new BesuEventsImpl( besuController.getProtocolContext().getBlockchain(), besuController.getProtocolManager().getBlockBroadcaster(), besuController.getTransactionPool(), - besuController.getSyncState())); + besuController.getSyncState(), + besuController.getProtocolContext().getBadBlockManager())); besuPluginContext.addService(MetricsSystem.class, getMetricsSystem()); + besuPluginContext.addService(BlockchainService.class, blockchainServiceImpl); + + besuPluginContext.addService( + SynchronizationService.class, + new SynchronizationServiceImpl( + besuController.getProtocolContext(), + besuController.getProtocolSchedule(), + besuController.getSyncState(), + besuController.getProtocolContext().getWorldStateArchive())); + + besuPluginContext.addService(P2PService.class, new P2PServiceImpl(runner.getP2PNetwork())); + + besuPluginContext.addService( + TransactionPoolService.class, + new TransactionPoolServiceImpl(besuController.getTransactionPool())); + besuPluginContext.addService( - BlockchainService.class, - new BlockchainServiceImpl(besuController.getProtocolContext().getBlockchain())); + RlpConverterService.class, + new RlpConverterServiceImpl(besuController.getProtocolSchedule())); besuPluginContext.addService( TraceService.class, new TraceServiceImpl( new BlockchainQueries( + besuController.getProtocolSchedule(), besuController.getProtocolContext().getBlockchain(), - besuController.getProtocolContext().getWorldStateArchive()), + besuController.getProtocolContext().getWorldStateArchive(), + miningParametersSupplier.get()), besuController.getProtocolSchedule())); besuController.getAdditionalPluginServices().appendPluginServices(besuPluginContext); besuPluginContext.startPlugins(); } - private void validatePluginOptions() { + private void validatePrivacyPluginOptions() { // plugins do not 'wire up' until start has been called // consequently you can only do some configuration checks // after start has been called on plugins @@ -1669,7 +1362,7 @@ private void validatePluginOptions() { "--privacy-marker-transaction-signing-key-file can not be used in conjunction with a plugin that specifies a PrivateMarkerTransactionFactory"); } - if (Wei.ZERO.compareTo(getMiningParameters().getMinTransactionGasPrice()) < 0 + if (Wei.ZERO.compareTo(miningParametersSupplier.get().getMinTransactionGasPrice()) < 0 && (privacyOptionGroup.privateMarkerTransactionSigningKeyPath == null && (privacyPluginService == null || privacyPluginService.getPrivateMarkerTransactionFactory() == null))) { @@ -1689,8 +1382,7 @@ private void validatePluginOptions() { } if (unstablePrivacyPluginOptions.isPrivacyPluginEnabled() - && (privacyOptionGroup.isFlexiblePrivacyGroupsEnabled - || privacyOptionGroup.isOnchainPrivacyGroupsEnabled)) { + && privacyOptionGroup.isFlexiblePrivacyGroupsEnabled) { throw new ParameterException( commandLine, "Privacy Plugin can not be used with flexible privacy groups"); } @@ -1734,7 +1426,7 @@ public static Optional getColorEnabled() { private void configureNativeLibs() { if (unstableNativeLibraryOptions.getNativeAltbn128() - && AbstractAltBnPrecompiledContract.isNative()) { + && AbstractAltBnPrecompiledContract.maybeEnableNative()) { logger.info("Using the native implementation of alt bn128"); } else { AbstractAltBnPrecompiledContract.disableNative(); @@ -1750,7 +1442,7 @@ private void configureNativeLibs() { } if (unstableNativeLibraryOptions.getNativeSecp() - && SignatureAlgorithmFactory.getInstance().isNative()) { + && SignatureAlgorithmFactory.getInstance().maybeEnableNative()) { logger.info("Using the native implementation of the signature algorithm"); } else { SignatureAlgorithmFactory.getInstance().disableNative(); @@ -1765,11 +1457,14 @@ private void configureNativeLibs() { logger.info("Using the Java implementation of the blake2bf algorithm"); } - if (getActualGenesisConfigOptions().getCancunTime().isPresent()) { + if (genesisConfigOptionsSupplier.get().getCancunTime().isPresent() + || genesisConfigOptionsSupplier.get().getCancunEOFTime().isPresent() + || genesisConfigOptionsSupplier.get().getPragueTime().isPresent() + || genesisConfigOptionsSupplier.get().getPragueEOFTime().isPresent()) { if (kzgTrustedSetupFile != null) { KZGPointEvalPrecompiledContract.init(kzgTrustedSetupFile); } else { - KZGPointEvalPrecompiledContract.init(network.name()); + KZGPointEvalPrecompiledContract.init(); } } else if (kzgTrustedSetupFile != null) { throw new ParameterException( @@ -1788,16 +1483,45 @@ private void validateOptions() { validateDnsOptionsParams(); ensureValidPeerBoundParams(); validateRpcOptionsParams(); + validateRpcWsOptions(); validateChainDataPruningParams(); validatePostMergeCheckpointBlockRequirements(); validateTransactionPoolOptions(); validateDataStorageOptions(); + validateGraphQlOptions(); + validateApiOptions(); + validateConsensusSyncCompatibilityOptions(); + validatePluginOptions(); p2pTLSConfigOptions.checkP2PTLSOptionsDependencies(logger, commandLine); - pkiBlockCreationOptions.checkPkiBlockCreationOptionsDependencies(logger, commandLine); + } + + private void validateConsensusSyncCompatibilityOptions() { + // snap and checkpoint are experimental for BFT + if ((genesisConfigOptionsSupplier.get().isIbftLegacy() + || genesisConfigOptionsSupplier.get().isIbft2() + || genesisConfigOptionsSupplier.get().isQbft()) + && !unstableSynchronizerOptions.isSnapSyncBftEnabled()) { + final String errorSuffix = "can't be used with BFT networks"; + if (SyncMode.CHECKPOINT.equals(syncMode)) { + throw new ParameterException( + commandLine, String.format("%s %s", "Checkpoint sync", errorSuffix)); + } + if (syncMode == SyncMode.SNAP) { + throw new ParameterException(commandLine, String.format("%s %s", "Snap sync", errorSuffix)); + } + } + } + + private void validatePluginOptions() { + pluginsConfigurationOptions.validate(commandLine); + } + + private void validateApiOptions() { + apiConfigurationOptions.validate(commandLine, logger); } private void validateTransactionPoolOptions() { - transactionPoolOptions.validate(commandLine, getActualGenesisConfigOptions()); + transactionPoolOptions.validate(commandLine, genesisConfigOptionsSupplier.get()); } private void validateDataStorageOptions() { @@ -1818,7 +1542,8 @@ private void validateRequiredOptions() { } private void validateMiningParams() { - miningOptions.validate(commandLine, getActualGenesisConfigOptions(), isMergeEnabled(), logger); + miningOptions.validate( + commandLine, genesisConfigOptionsSupplier.get(), isMergeEnabled(), logger); } /** @@ -1837,8 +1562,19 @@ protected void validateP2PInterface(final String p2pInterface) { } } + private void validateGraphQlOptions() { + graphQlOptions.validate(logger, commandLine); + } + @SuppressWarnings("ConstantConditions") private void validateNatParams() { + if (natMethod.equals(NatMethod.KUBERNETES)) { + logger.warn("Kubernetes NAT method is deprecated. Please use Docker or UPNP"); + } + if (!unstableNatOptions.getNatManagerServiceName().equals(DEFAULT_BESU_SERVICE_NAME_FILTER)) { + logger.warn( + "`--Xnat-kube-service-name` and Kubernetes NAT method are deprecated. Please use Docker or UPNP"); + } if (!(natMethod.equals(NatMethod.AUTO) || natMethod.equals(NatMethod.KUBERNETES)) && !unstableNatOptions .getNatManagerServiceName() @@ -1877,16 +1613,6 @@ private void validateDnsOptionsParams() { private void ensureValidPeerBoundParams() { maxPeers = p2PDiscoveryOptionGroup.maxPeers; - peersLowerBound = unstableNetworkingOptions.toDomainObject().getPeerLowerBound(); - if (peersLowerBound > maxPeers) { - logger.warn( - "`--Xp2p-peer-lower-bound` " - + peersLowerBound - + " must not exceed --max-peers " - + maxPeers); - logger.warn("setting --Xp2p-peer-lower-bound=" + maxPeers); - peersLowerBound = maxPeers; - } final Boolean isLimitRemoteWireConnectionsEnabled = p2PDiscoveryOptionGroup.isLimitRemoteWireConnectionsEnabled; if (isLimitRemoteWireConnectionsEnabled) { @@ -1896,7 +1622,7 @@ private void ensureValidPeerBoundParams() { checkState( fraction >= 0.0 && fraction <= 1.0, "Fraction of remote connections allowed must be between 0.0 and 1.0 (inclusive)."); - maxRemoteInitiatedPeers = (int) Math.floor(fraction * maxPeers); + maxRemoteInitiatedPeers = Math.round(fraction * maxPeers); } else { maxRemoteInitiatedPeers = maxPeers; } @@ -1908,68 +1634,45 @@ private void validateRpcOptionsParams() { Arrays.stream(RpcApis.values()) .anyMatch(builtInApi -> apiName.equals(builtInApi.name())) || rpcEndpointServiceImpl.hasNamespace(apiName); + jsonRpcHttpOptions.validate(logger, commandLine, configuredApis); + } - if (!jsonRPCHttpOptionGroup.rpcHttpApis.stream().allMatch(configuredApis)) { - final List invalidHttpApis = - new ArrayList(jsonRPCHttpOptionGroup.rpcHttpApis); - invalidHttpApis.removeAll(VALID_APIS); - throw new ParameterException( - this.commandLine, - "Invalid value for option '--rpc-http-api': invalid entries found " - + invalidHttpApis.toString()); - } - - if (!jsonRPCWebsocketOptionGroup.rpcWsApis.stream().allMatch(configuredApis)) { - final List invalidWsApis = - new ArrayList(jsonRPCWebsocketOptionGroup.rpcWsApis); - invalidWsApis.removeAll(VALID_APIS); - throw new ParameterException( - this.commandLine, - "Invalid value for option '--rpc-ws-api': invalid entries found " + invalidWsApis); - } - - final boolean validHttpApiMethods = - jsonRPCHttpOptionGroup.rpcHttpApiMethodsNoAuth.stream() - .allMatch(RpcMethod::rpcMethodExists); - - if (!validHttpApiMethods) { - throw new ParameterException( - this.commandLine, - "Invalid value for option '--rpc-http-api-methods-no-auth', options must be valid RPC methods"); - } - - final boolean validWsApiMethods = - jsonRPCWebsocketOptionGroup.rpcWsApiMethodsNoAuth.stream() - .allMatch(RpcMethod::rpcMethodExists); - - if (!validWsApiMethods) { - throw new ParameterException( - this.commandLine, - "Invalid value for option '--rpc-ws-api-methods-no-auth', options must be valid RPC methods"); - } + private void validateRpcWsOptions() { + final Predicate configuredApis = + apiName -> + Arrays.stream(RpcApis.values()) + .anyMatch(builtInApi -> apiName.equals(builtInApi.name())) + || rpcEndpointServiceImpl.hasNamespace(apiName); + rpcWebsocketOptions.validate(logger, commandLine, configuredApis); } private void validateChainDataPruningParams() { if (unstableChainPruningOptions.getChainDataPruningEnabled() && unstableChainPruningOptions.getChainDataPruningBlocksRetained() - < ChainPruningOptions.DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED) { + < unstableChainPruningOptions.getChainDataPruningBlocksRetainedLimit()) { throw new ParameterException( this.commandLine, "--Xchain-pruning-blocks-retained must be >= " - + ChainPruningOptions.DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED); + + unstableChainPruningOptions.getChainDataPruningBlocksRetainedLimit()); } } - private GenesisConfigOptions readGenesisConfigOptions() { + private GenesisConfigFile readGenesisConfigFile() { + final GenesisConfigFile effectiveGenesisFile = + genesisFile != null + ? GenesisConfigFile.fromSource(genesisConfigSource(genesisFile)) + : GenesisConfigFile.fromResource( + Optional.ofNullable(network).orElse(MAINNET).getGenesisFile()); + return effectiveGenesisFile.withOverrides(genesisConfigOverrides); + } + private GenesisConfigOptions readGenesisConfigOptions() { try { - final GenesisConfigFile genesisConfigFile = GenesisConfigFile.fromConfig(genesisConfig()); - genesisConfigOptions = genesisConfigFile.getConfigOptions(genesisConfigOverrides); + return genesisConfigFileSupplier.get().getConfigOptions(); } catch (final Exception e) { throw new ParameterException( this.commandLine, "Unable to load genesis file. " + e.getCause()); } - return genesisConfigOptions; } private void issueOptionWarnings() { @@ -1991,22 +1694,21 @@ private void issueOptionWarnings() { "--p2p-port", "--remote-connections-max-percentage")); - CommandLineUtils.failIfOptionDoesntMeetRequirement( - commandLine, - "--fast-sync-min-peers can't be used with FULL sync-mode", - !SyncMode.isFullSync(getDefaultSyncModeIfNotSet()), - singletonList("--fast-sync-min-peers")); + if (SyncMode.isFullSync(getDefaultSyncModeIfNotSet()) + && isOptionSet(commandLine, "--sync-min-peers")) { + logger.warn("--sync-min-peers is ignored in FULL sync-mode"); + } CommandLineUtils.failIfOptionDoesntMeetRequirement( commandLine, - "--Xcheckpoint-post-merge-enabled can only be used with X_CHECKPOINT sync-mode", - SyncMode.X_CHECKPOINT.equals(getDefaultSyncModeIfNotSet()), + "--Xcheckpoint-post-merge-enabled can only be used with CHECKPOINT sync-mode", + getDefaultSyncModeIfNotSet() == SyncMode.CHECKPOINT, singletonList("--Xcheckpoint-post-merge-enabled")); CommandLineUtils.failIfOptionDoesntMeetRequirement( commandLine, - "--Xsnapsync-synchronizer-flat option can only be used when -Xsnapsync-synchronizer-flat-db-healing-enabled is true", - unstableSynchronizerOptions.isSnapsyncFlatDbHealingEnabled(), + "--Xsnapsync-synchronizer-flat option can only be used when --Xbonsai-full-flat-db-enabled is true", + dataStorageOptions.toDomainObject().getUnstable().getBonsaiFullFlatDbEnabled(), asList( "--Xsnapsync-synchronizer-flat-account-healed-count-per-request", "--Xsnapsync-synchronizer-flat-slot-healed-count-per-request")); @@ -2018,43 +1720,45 @@ private void issueOptionWarnings() { "--node-private-key-file", "--security-module=" + DEFAULT_SECURITY_MODULE); } - - if (Boolean.TRUE.equals(privacyOptionGroup.isOnchainPrivacyGroupsEnabled)) { - logger.warn( - DEPRECATION_WARNING_MSG, - "--privacy-onchain-groups-enabled", - "--privacy-flexible-groups-enabled"); - } } private void configure() throws Exception { checkPortClash(); checkIfRequiredPortsAreAvailable(); syncMode = getDefaultSyncModeIfNotSet(); + versionCompatibilityProtection = getDefaultVersionCompatibilityProtectionIfNotSet(); ethNetworkConfig = updateNetworkConfig(network); jsonRpcConfiguration = - jsonRpcConfiguration( - jsonRPCHttpOptionGroup.rpcHttpPort, jsonRPCHttpOptionGroup.rpcHttpApis, hostsAllowlist); + jsonRpcHttpOptions.jsonRpcConfiguration( + hostsAllowlist, + p2PDiscoveryOptionGroup.autoDiscoverDefaultIP().getHostAddress(), + unstableRPCOptions.getHttpTimeoutSec()); if (isEngineApiEnabled()) { engineJsonRpcConfiguration = createEngineJsonRpcConfiguration( engineRPCOptionGroup.engineRpcPort, engineRPCOptionGroup.engineHostsAllowlist); } p2pTLSConfiguration = p2pTLSConfigOptions.p2pTLSConfiguration(commandLine); - graphQLConfiguration = graphQLConfiguration(); + graphQLConfiguration = + graphQlOptions.graphQLConfiguration( + hostsAllowlist, + p2PDiscoveryOptionGroup.autoDiscoverDefaultIP().getHostAddress(), + unstableRPCOptions.getHttpTimeoutSec()); webSocketConfiguration = - webSocketConfiguration( - jsonRPCWebsocketOptionGroup.rpcWsPort, - jsonRPCWebsocketOptionGroup.rpcWsApis, - hostsAllowlist); + rpcWebsocketOptions.webSocketConfiguration( + hostsAllowlist, + p2PDiscoveryOptionGroup.autoDiscoverDefaultIP().getHostAddress(), + unstableRPCOptions.getWsTimeoutSec()); jsonRpcIpcConfiguration = jsonRpcIpcConfiguration( unstableIpcOptions.isEnabled(), unstableIpcOptions.getIpcPath(), unstableIpcOptions.getRpcIpcApis()); - apiConfiguration = apiConfiguration(); + inProcessRpcConfiguration = inProcessRpcOptions.toDomainObject(); + apiConfiguration = apiConfigurationOptions.apiConfiguration(); + dataStorageConfiguration = getDataStorageConfiguration(); // hostsWhitelist is a hidden option. If it is specified, add the list to hostAllowlist if (!hostsWhitelist.isEmpty()) { // if allowlist == default values, remove the default values @@ -2068,7 +1772,7 @@ private void configure() throws Exception { permissioningConfiguration = permissioningConfiguration(); staticNodes = loadStaticNodes(); - final List enodeURIs = ethNetworkConfig.getBootNodes(); + final List enodeURIs = ethNetworkConfig.bootNodes(); permissioningConfiguration .flatMap(PermissioningConfiguration::getLocalConfig) .ifPresent(p -> ensureAllNodesAreInAllowlist(enodeURIs, p)); @@ -2081,11 +1785,19 @@ private void configure() throws Exception { instantiateSignatureAlgorithmFactory(); logger.info(generateConfigurationOverview()); - logger.info("Connecting to {} static nodes.", staticNodes.size()); - logger.trace("Static Nodes = {}", staticNodes); logger.info("Security Module: {}", securityModuleName); } + private Optional permissioningConfiguration() throws Exception { + return permissionsOptions.permissioningConfiguration( + jsonRpcHttpOptions, + rpcWebsocketOptions, + getEnodeDnsConfiguration(), + dataDir(), + logger, + commandLine); + } + private JsonRpcIpcConfiguration jsonRpcIpcConfiguration( final Boolean enabled, final Path ipcPath, final List rpcIpcApis) { final Path actualPath; @@ -2109,10 +1821,6 @@ private void ensureAllNodesAreInAllowlist( } } - private BesuController initController() { - return buildController(); - } - /** * Builds BesuController * @@ -2120,9 +1828,7 @@ private BesuController initController() { */ public BesuController buildController() { try { - return this.besuComponent == null - ? getControllerBuilder().build() - : getControllerBuilder().besuComponent(this.besuComponent).build(); + return setupControllerBuilder().build(); } catch (final Exception e) { throw new ExecutionException(this.commandLine, e.getMessage(), e); } @@ -2133,86 +1839,55 @@ public BesuController buildController() { * * @return instance of BesuControllerBuilder */ - public BesuControllerBuilder getControllerBuilder() { + public BesuControllerBuilder setupControllerBuilder() { + pluginCommonConfiguration + .init(dataDir(), dataDir().resolve(DATABASE_PATH), getDataStorageConfiguration()) + .withMiningParameters(miningParametersSupplier.get()) + .withJsonRpcHttpOptions(jsonRpcHttpOptions); final KeyValueStorageProvider storageProvider = keyValueStorageProvider(keyValueStorageName); - return controllerBuilderFactory - .fromEthNetworkConfig( - updateNetworkConfig(network), genesisConfigOverrides, getDefaultSyncModeIfNotSet()) + return controllerBuilder + .fromEthNetworkConfig(updateNetworkConfig(network), getDefaultSyncModeIfNotSet()) .synchronizerConfiguration(buildSyncConfig()) .ethProtocolConfiguration(unstableEthProtocolOptions.toDomainObject()) .networkConfiguration(unstableNetworkingOptions.toDomainObject()) - .transactionSelectorFactory(getTransactionSelectorFactory()) - .pluginTransactionValidatorFactory(getPluginTransactionValidatorFactory()) .dataDirectory(dataDir()) - .miningParameters(getMiningParameters()) + .dataStorageConfiguration(getDataStorageConfiguration()) + .miningParameters(miningParametersSupplier.get()) .transactionPoolConfiguration(buildTransactionPoolConfiguration()) .nodeKey(new NodeKey(securityModule())) .metricsSystem(metricsSystem.get()) .messagePermissioningProviders(permissioningService.getMessagePermissioningProviders()) .privacyParameters(privacyParameters()) - .pkiBlockCreationConfiguration(maybePkiBlockCreationConfiguration()) .clock(Clock.systemUTC()) .isRevertReasonEnabled(isRevertReasonEnabled) + .isParallelTxProcessingEnabled( + dataStorageConfiguration.getUnstable().isParallelTxProcessingEnabled()) .storageProvider(storageProvider) - .isPruningEnabled(isPruningEnabled()) - .pruningConfiguration( - new PrunerConfiguration(pruningBlockConfirmations, pruningBlocksRetained)) - .genesisConfigOverrides(genesisConfigOverrides) .gasLimitCalculator( - getMiningParameters().getTargetGasLimit().isPresent() + miningParametersSupplier.get().getTargetGasLimit().isPresent() ? new FrontierTargetingGasLimitCalculator() : GasLimitCalculator.constant()) .requiredBlocks(requiredBlocks) .reorgLoggingThreshold(reorgLoggingThreshold) .evmConfiguration(unstableEvmOptions.toDomainObject()) - .dataStorageConfiguration(dataStorageOptions.toDomainObject()) .maxPeers(p2PDiscoveryOptionGroup.maxPeers) - .lowerBoundPeers(peersLowerBound) .maxRemotelyInitiatedPeers(maxRemoteInitiatedPeers) .randomPeerPriority(p2PDiscoveryOptionGroup.randomPeerPriority) .chainPruningConfiguration(unstableChainPruningOptions.toDomainObject()) - .cacheLastBlocks(numberOfblocksToCache); - } - - @NotNull - private Optional getTransactionSelectorFactory() { - final Optional txSelectionService = - besuPluginContext.getService(TransactionSelectionService.class); - return txSelectionService.isPresent() ? txSelectionService.get().get() : Optional.empty(); - } - - private PluginTransactionValidatorFactory getPluginTransactionValidatorFactory() { - final Optional txSValidatorService = - besuPluginContext.getService(PluginTransactionValidatorService.class); - return txSValidatorService.map(PluginTransactionValidatorService::get).orElse(null); - } - - private GraphQLConfiguration graphQLConfiguration() { - - CommandLineUtils.checkOptionDependencies( - logger, - commandLine, - "--graphql-http-enabled", - !graphQlOptionGroup.isGraphQLHttpEnabled, - asList("--graphql-http-cors-origins", "--graphql-http-host", "--graphql-http-port")); - final GraphQLConfiguration graphQLConfiguration = GraphQLConfiguration.createDefault(); - graphQLConfiguration.setEnabled(graphQlOptionGroup.isGraphQLHttpEnabled); - graphQLConfiguration.setHost( - Strings.isNullOrEmpty(graphQlOptionGroup.graphQLHttpHost) - ? p2PDiscoveryOptionGroup.autoDiscoverDefaultIP().getHostAddress() - : graphQlOptionGroup.graphQLHttpHost); - graphQLConfiguration.setPort(graphQlOptionGroup.graphQLHttpPort); - graphQLConfiguration.setHostsAllowlist(hostsAllowlist); - graphQLConfiguration.setCorsAllowedDomains(graphQlOptionGroup.graphQLHttpCorsAllowedOrigins); - graphQLConfiguration.setHttpTimeoutSec(unstableRPCOptions.getHttpTimeoutSec()); - - return graphQLConfiguration; + .cacheLastBlocks(numberOfblocksToCache) + .genesisStateHashCacheEnabled(genesisStateHashCacheEnabled); } private JsonRpcConfiguration createEngineJsonRpcConfiguration( - final Integer listenPort, final List allowCallsFrom) { + final Integer engineListenPort, final List allowCallsFrom) { + jsonRpcHttpOptions.checkDependencies(logger, commandLine); final JsonRpcConfiguration engineConfig = - jsonRpcConfiguration(listenPort, Arrays.asList("ENGINE", "ETH"), allowCallsFrom); + jsonRpcHttpOptions.jsonRpcConfiguration( + allowCallsFrom, + p2PDiscoveryOptionGroup.autoDiscoverDefaultIP().getHostAddress(), + unstableRPCOptions.getWsTimeoutSec()); + engineConfig.setPort(engineListenPort); + engineConfig.setRpcApis(Arrays.asList("ENGINE", "ETH")); engineConfig.setEnabled(isEngineApiEnabled()); if (!engineRPCOptionGroup.isEngineAuthDisabled) { engineConfig.setAuthenticationEnabled(true); @@ -2228,116 +1903,6 @@ private JsonRpcConfiguration createEngineJsonRpcConfiguration( return engineConfig; } - private JsonRpcConfiguration jsonRpcConfiguration( - final Integer listenPort, final List apiGroups, final List allowCallsFrom) { - checkRpcTlsClientAuthOptionsDependencies(); - checkRpcTlsOptionsDependencies(); - checkRpcHttpOptionsDependencies(); - - if (jsonRPCHttpOptionGroup.isRpcHttpAuthenticationEnabled) { - CommandLineUtils.checkOptionDependencies( - logger, - commandLine, - "--rpc-http-authentication-public-key-file", - jsonRPCHttpOptionGroup.rpcHttpAuthenticationPublicKeyFile == null, - asList("--rpc-http-authentication-jwt-algorithm")); - } - - if (jsonRPCHttpOptionGroup.isRpcHttpAuthenticationEnabled - && rpcHttpAuthenticationCredentialsFile() == null - && jsonRPCHttpOptionGroup.rpcHttpAuthenticationPublicKeyFile == null) { - throw new ParameterException( - commandLine, - "Unable to authenticate JSON-RPC HTTP endpoint without a supplied credentials file or authentication public key file"); - } - - final JsonRpcConfiguration jsonRpcConfiguration = JsonRpcConfiguration.createDefault(); - jsonRpcConfiguration.setEnabled(jsonRPCHttpOptionGroup.isRpcHttpEnabled); - jsonRpcConfiguration.setHost( - Strings.isNullOrEmpty(jsonRPCHttpOptionGroup.rpcHttpHost) - ? p2PDiscoveryOptionGroup.autoDiscoverDefaultIP().getHostAddress() - : jsonRPCHttpOptionGroup.rpcHttpHost); - jsonRpcConfiguration.setPort(listenPort); - jsonRpcConfiguration.setMaxActiveConnections(jsonRPCHttpOptionGroup.rpcHttpMaxConnections); - jsonRpcConfiguration.setCorsAllowedDomains(jsonRPCHttpOptionGroup.rpcHttpCorsAllowedOrigins); - jsonRpcConfiguration.setRpcApis(apiGroups.stream().distinct().collect(Collectors.toList())); - jsonRpcConfiguration.setNoAuthRpcApis( - jsonRPCHttpOptionGroup.rpcHttpApiMethodsNoAuth.stream() - .distinct() - .collect(Collectors.toList())); - jsonRpcConfiguration.setHostsAllowlist(allowCallsFrom); - jsonRpcConfiguration.setAuthenticationEnabled( - jsonRPCHttpOptionGroup.isRpcHttpAuthenticationEnabled); - jsonRpcConfiguration.setAuthenticationCredentialsFile(rpcHttpAuthenticationCredentialsFile()); - jsonRpcConfiguration.setAuthenticationPublicKeyFile( - jsonRPCHttpOptionGroup.rpcHttpAuthenticationPublicKeyFile); - jsonRpcConfiguration.setAuthenticationAlgorithm( - jsonRPCHttpOptionGroup.rpcHttpAuthenticationAlgorithm); - jsonRpcConfiguration.setTlsConfiguration(rpcHttpTlsConfiguration()); - jsonRpcConfiguration.setHttpTimeoutSec(unstableRPCOptions.getHttpTimeoutSec()); - jsonRpcConfiguration.setMaxBatchSize(jsonRPCHttpOptionGroup.rpcHttpMaxBatchSize); - jsonRpcConfiguration.setMaxRequestContentLength( - jsonRPCHttpOptionGroup.rpcHttpMaxRequestContentLength); - jsonRpcConfiguration.setPrettyJsonEnabled(jsonRPCHttpOptionGroup.prettyJsonEnabled); - return jsonRpcConfiguration; - } - - private void checkRpcHttpOptionsDependencies() { - CommandLineUtils.checkOptionDependencies( - logger, - commandLine, - "--rpc-http-enabled", - !jsonRPCHttpOptionGroup.isRpcHttpEnabled, - asList( - "--rpc-http-api", - "--rpc-http-apis", - "--rpc-http-api-method-no-auth", - "--rpc-http-api-methods-no-auth", - "--rpc-http-cors-origins", - "--rpc-http-host", - "--rpc-http-port", - "--rpc-http-max-active-connections", - "--rpc-http-authentication-enabled", - "--rpc-http-authentication-credentials-file", - "--rpc-http-authentication-public-key-file", - "--rpc-http-tls-enabled", - "--rpc-http-tls-keystore-file", - "--rpc-http-tls-keystore-password-file", - "--rpc-http-tls-client-auth-enabled", - "--rpc-http-tls-known-clients-file", - "--rpc-http-tls-ca-clients-enabled", - "--rpc-http-authentication-jwt-algorithm", - "--rpc-http-tls-protocols", - "--rpc-http-tls-cipher-suite", - "--rpc-http-tls-cipher-suites")); - } - - private void checkRpcTlsOptionsDependencies() { - CommandLineUtils.checkOptionDependencies( - logger, - commandLine, - "--rpc-http-tls-enabled", - !jsonRPCHttpOptionGroup.isRpcHttpTlsEnabled, - asList( - "--rpc-http-tls-keystore-file", - "--rpc-http-tls-keystore-password-file", - "--rpc-http-tls-client-auth-enabled", - "--rpc-http-tls-known-clients-file", - "--rpc-http-tls-ca-clients-enabled", - "--rpc-http-tls-protocols", - "--rpc-http-tls-cipher-suite", - "--rpc-http-tls-cipher-suites")); - } - - private void checkRpcTlsClientAuthOptionsDependencies() { - CommandLineUtils.checkOptionDependencies( - logger, - commandLine, - "--rpc-http-tls-client-auth-enabled", - !jsonRPCHttpOptionGroup.isRpcHttpTlsClientAuthEnabled, - asList("--rpc-http-tls-known-clients-file", "--rpc-http-tls-ca-clients-enabled")); - } - private void checkPrivacyTlsOptionsDependencies() { CommandLineUtils.checkOptionDependencies( logger, @@ -2350,159 +1915,13 @@ private void checkPrivacyTlsOptionsDependencies() { "--privacy-tls-known-enclave-file")); } - private Optional rpcHttpTlsConfiguration() { - if (!isRpcTlsConfigurationRequired()) { - return Optional.empty(); - } - - if (jsonRPCHttpOptionGroup.rpcHttpTlsKeyStoreFile == null) { - throw new ParameterException( - commandLine, "Keystore file is required when TLS is enabled for JSON-RPC HTTP endpoint"); - } - - if (jsonRPCHttpOptionGroup.rpcHttpTlsKeyStorePasswordFile == null) { - throw new ParameterException( - commandLine, - "File containing password to unlock keystore is required when TLS is enabled for JSON-RPC HTTP endpoint"); - } - - if (jsonRPCHttpOptionGroup.isRpcHttpTlsClientAuthEnabled - && !jsonRPCHttpOptionGroup.isRpcHttpTlsCAClientsEnabled - && jsonRPCHttpOptionGroup.rpcHttpTlsKnownClientsFile == null) { - throw new ParameterException( - commandLine, - "Known-clients file must be specified or CA clients must be enabled when TLS client authentication is enabled for JSON-RPC HTTP endpoint"); - } - - jsonRPCHttpOptionGroup.rpcHttpTlsProtocols.retainAll(getJDKEnabledProtocols()); - if (jsonRPCHttpOptionGroup.rpcHttpTlsProtocols.isEmpty()) { - throw new ParameterException( - commandLine, - "No valid TLS protocols specified (the following protocols are enabled: " - + getJDKEnabledProtocols() - + ")"); - } - - for (final String cipherSuite : jsonRPCHttpOptionGroup.rpcHttpTlsCipherSuites) { - if (!getJDKEnabledCipherSuites().contains(cipherSuite)) { - throw new ParameterException( - commandLine, "Invalid TLS cipher suite specified " + cipherSuite); - } - } - - jsonRPCHttpOptionGroup.rpcHttpTlsCipherSuites.retainAll(getJDKEnabledCipherSuites()); - - return Optional.of( - TlsConfiguration.Builder.aTlsConfiguration() - .withKeyStorePath(jsonRPCHttpOptionGroup.rpcHttpTlsKeyStoreFile) - .withKeyStorePasswordSupplier( - new FileBasedPasswordProvider( - jsonRPCHttpOptionGroup.rpcHttpTlsKeyStorePasswordFile)) - .withClientAuthConfiguration(rpcHttpTlsClientAuthConfiguration()) - .withSecureTransportProtocols(jsonRPCHttpOptionGroup.rpcHttpTlsProtocols) - .withCipherSuites(jsonRPCHttpOptionGroup.rpcHttpTlsCipherSuites) - .build()); - } - - private TlsClientAuthConfiguration rpcHttpTlsClientAuthConfiguration() { - if (jsonRPCHttpOptionGroup.isRpcHttpTlsClientAuthEnabled) { - return TlsClientAuthConfiguration.Builder.aTlsClientAuthConfiguration() - .withKnownClientsFile(jsonRPCHttpOptionGroup.rpcHttpTlsKnownClientsFile) - .withCaClientsEnabled(jsonRPCHttpOptionGroup.isRpcHttpTlsCAClientsEnabled) - .build(); - } - - return null; - } - - private boolean isRpcTlsConfigurationRequired() { - return jsonRPCHttpOptionGroup.isRpcHttpEnabled && jsonRPCHttpOptionGroup.isRpcHttpTlsEnabled; - } - - private WebSocketConfiguration webSocketConfiguration( - final Integer listenPort, final List apiGroups, final List allowCallsFrom) { - - CommandLineUtils.checkOptionDependencies( - logger, - commandLine, - "--rpc-ws-enabled", - !jsonRPCWebsocketOptionGroup.isRpcWsEnabled, - asList( - "--rpc-ws-api", - "--rpc-ws-apis", - "--rpc-ws-api-method-no-auth", - "--rpc-ws-api-methods-no-auth", - "--rpc-ws-host", - "--rpc-ws-port", - "--rpc-ws-max-frame-size", - "--rpc-ws-max-active-connections", - "--rpc-ws-authentication-enabled", - "--rpc-ws-authentication-credentials-file", - "--rpc-ws-authentication-public-key-file", - "--rpc-ws-authentication-jwt-algorithm")); - - if (jsonRPCWebsocketOptionGroup.isRpcWsAuthenticationEnabled) { - CommandLineUtils.checkOptionDependencies( - logger, - commandLine, - "--rpc-ws-authentication-public-key-file", - jsonRPCWebsocketOptionGroup.rpcWsAuthenticationPublicKeyFile == null, - asList("--rpc-ws-authentication-jwt-algorithm")); - } - - if (jsonRPCWebsocketOptionGroup.isRpcWsAuthenticationEnabled - && rpcWsAuthenticationCredentialsFile() == null - && jsonRPCWebsocketOptionGroup.rpcWsAuthenticationPublicKeyFile == null) { - throw new ParameterException( - commandLine, - "Unable to authenticate JSON-RPC WebSocket endpoint without a supplied credentials file or authentication public key file"); - } - - final WebSocketConfiguration webSocketConfiguration = WebSocketConfiguration.createDefault(); - webSocketConfiguration.setEnabled(jsonRPCWebsocketOptionGroup.isRpcWsEnabled); - webSocketConfiguration.setHost( - Strings.isNullOrEmpty(jsonRPCWebsocketOptionGroup.rpcWsHost) - ? p2PDiscoveryOptionGroup.autoDiscoverDefaultIP().getHostAddress() - : jsonRPCWebsocketOptionGroup.rpcWsHost); - webSocketConfiguration.setPort(listenPort); - webSocketConfiguration.setMaxFrameSize(jsonRPCWebsocketOptionGroup.rpcWsMaxFrameSize); - webSocketConfiguration.setMaxActiveConnections(jsonRPCWebsocketOptionGroup.rpcWsMaxConnections); - webSocketConfiguration.setRpcApis(apiGroups); - webSocketConfiguration.setRpcApisNoAuth( - jsonRPCWebsocketOptionGroup.rpcWsApiMethodsNoAuth.stream() - .distinct() - .collect(Collectors.toList())); - webSocketConfiguration.setAuthenticationEnabled( - jsonRPCWebsocketOptionGroup.isRpcWsAuthenticationEnabled); - webSocketConfiguration.setAuthenticationCredentialsFile(rpcWsAuthenticationCredentialsFile()); - webSocketConfiguration.setHostsAllowlist(allowCallsFrom); - webSocketConfiguration.setAuthenticationPublicKeyFile( - jsonRPCWebsocketOptionGroup.rpcWsAuthenticationPublicKeyFile); - webSocketConfiguration.setAuthenticationAlgorithm( - jsonRPCWebsocketOptionGroup.rpcWebsocketsAuthenticationAlgorithm); - webSocketConfiguration.setTimeoutSec(unstableRPCOptions.getWsTimeoutSec()); - return webSocketConfiguration; - } - - private ApiConfiguration apiConfiguration() { - return ImmutableApiConfiguration.builder() - .gasPriceBlocks(apiGasPriceBlocks) - .gasPricePercentile(apiGasPricePercentile) - .gasPriceMinSupplier( - getMiningParameters().getMinTransactionGasPrice().getAsBigInteger()::longValueExact) - .gasPriceMax(apiGasPriceMax) - .maxLogsRange(rpcMaxLogsRange) - .gasCap(rpcGasCap) - .build(); - } - /** * Metrics Configuration for Besu * * @return instance of MetricsConfiguration. */ public MetricsConfiguration metricsConfiguration() { - if (metricsOptionGroup.isMetricsEnabled && metricsOptionGroup.isMetricsPushEnabled) { + if (metricsOptionGroup.getMetricsEnabled() && metricsOptionGroup.getMetricsPushEnabled()) { throw new ParameterException( this.commandLine, "--metrics-enabled option and --metrics-push-enabled option can't be used at the same " @@ -2513,14 +1932,14 @@ public MetricsConfiguration metricsConfiguration() { logger, commandLine, "--metrics-enabled", - !metricsOptionGroup.isMetricsEnabled, + !metricsOptionGroup.getMetricsEnabled(), asList("--metrics-host", "--metrics-port")); CommandLineUtils.checkOptionDependencies( logger, commandLine, "--metrics-push-enabled", - !metricsOptionGroup.isMetricsPushEnabled, + !metricsOptionGroup.getMetricsPushEnabled(), asList( "--metrics-push-host", "--metrics-push-port", @@ -2529,126 +1948,26 @@ public MetricsConfiguration metricsConfiguration() { return unstableMetricsCLIOptions .toDomainObject() - .enabled(metricsOptionGroup.isMetricsEnabled) + .enabled(metricsOptionGroup.getMetricsEnabled()) .host( - Strings.isNullOrEmpty(metricsOptionGroup.metricsHost) + Strings.isNullOrEmpty(metricsOptionGroup.getMetricsHost()) ? p2PDiscoveryOptionGroup.autoDiscoverDefaultIP().getHostAddress() - : metricsOptionGroup.metricsHost) - .port(metricsOptionGroup.metricsPort) - .protocol(metricsOptionGroup.metricsProtocol) - .metricCategories(metricsOptionGroup.metricCategories) - .pushEnabled(metricsOptionGroup.isMetricsPushEnabled) + : metricsOptionGroup.getMetricsHost()) + .port(metricsOptionGroup.getMetricsPort()) + .protocol(metricsOptionGroup.getMetricsProtocol()) + .metricCategories(metricsOptionGroup.getMetricCategories()) + .pushEnabled(metricsOptionGroup.getMetricsPushEnabled()) .pushHost( - Strings.isNullOrEmpty(metricsOptionGroup.metricsPushHost) + Strings.isNullOrEmpty(metricsOptionGroup.getMetricsPushHost()) ? p2PDiscoveryOptionGroup.autoDiscoverDefaultIP().getHostAddress() - : metricsOptionGroup.metricsPushHost) - .pushPort(metricsOptionGroup.metricsPushPort) - .pushInterval(metricsOptionGroup.metricsPushInterval) + : metricsOptionGroup.getMetricsPushHost()) + .pushPort(metricsOptionGroup.getMetricsPushPort()) + .pushInterval(metricsOptionGroup.getMetricsPushInterval()) .hostsAllowlist(hostsAllowlist) - .prometheusJob(metricsOptionGroup.metricsPrometheusJob) + .prometheusJob(metricsOptionGroup.getMetricsPrometheusJob()) .build(); } - private Optional permissioningConfiguration() throws Exception { - if (!(localPermissionsEnabled() || contractPermissionsEnabled())) { - if (jsonRPCHttpOptionGroup.rpcHttpApis.contains(RpcApis.PERM.name()) - || jsonRPCWebsocketOptionGroup.rpcWsApis.contains(RpcApis.PERM.name())) { - logger.warn( - "Permissions are disabled. Cannot enable PERM APIs when not using Permissions."); - } - return Optional.empty(); - } - - final Optional localPermissioningConfigurationOptional; - if (localPermissionsEnabled()) { - final Optional nodePermissioningConfigFile = - Optional.ofNullable(permissionsOptionGroup.nodePermissionsConfigFile); - final Optional accountPermissioningConfigFile = - Optional.ofNullable(permissionsOptionGroup.accountPermissionsConfigFile); - - final LocalPermissioningConfiguration localPermissioningConfiguration = - PermissioningConfigurationBuilder.permissioningConfiguration( - permissionsOptionGroup.permissionsNodesEnabled, - getEnodeDnsConfiguration(), - nodePermissioningConfigFile.orElse(getDefaultPermissioningFilePath()), - permissionsOptionGroup.permissionsAccountsEnabled, - accountPermissioningConfigFile.orElse(getDefaultPermissioningFilePath())); - - localPermissioningConfigurationOptional = Optional.of(localPermissioningConfiguration); - } else { - if (permissionsOptionGroup.nodePermissionsConfigFile != null - && !permissionsOptionGroup.permissionsNodesEnabled) { - logger.warn( - "Node permissioning config file set {} but no permissions enabled", - permissionsOptionGroup.nodePermissionsConfigFile); - } - - if (permissionsOptionGroup.accountPermissionsConfigFile != null - && !permissionsOptionGroup.permissionsAccountsEnabled) { - logger.warn( - "Account permissioning config file set {} but no permissions enabled", - permissionsOptionGroup.accountPermissionsConfigFile); - } - localPermissioningConfigurationOptional = Optional.empty(); - } - - final SmartContractPermissioningConfiguration smartContractPermissioningConfiguration = - SmartContractPermissioningConfiguration.createDefault(); - - if (Boolean.TRUE.equals(permissionsOptionGroup.permissionsNodesContractEnabled)) { - if (permissionsOptionGroup.permissionsNodesContractAddress == null) { - throw new ParameterException( - this.commandLine, - "No node permissioning contract address specified. Cannot enable smart contract based node permissioning."); - } else { - smartContractPermissioningConfiguration.setSmartContractNodeAllowlistEnabled( - permissionsOptionGroup.permissionsNodesContractEnabled); - smartContractPermissioningConfiguration.setNodeSmartContractAddress( - permissionsOptionGroup.permissionsNodesContractAddress); - smartContractPermissioningConfiguration.setNodeSmartContractInterfaceVersion( - permissionsOptionGroup.permissionsNodesContractVersion); - } - } else if (permissionsOptionGroup.permissionsNodesContractAddress != null) { - logger.warn( - "Node permissioning smart contract address set {} but smart contract node permissioning is disabled.", - permissionsOptionGroup.permissionsNodesContractAddress); - } - - if (Boolean.TRUE.equals(permissionsOptionGroup.permissionsAccountsContractEnabled)) { - if (permissionsOptionGroup.permissionsAccountsContractAddress == null) { - throw new ParameterException( - this.commandLine, - "No account permissioning contract address specified. Cannot enable smart contract based account permissioning."); - } else { - smartContractPermissioningConfiguration.setSmartContractAccountAllowlistEnabled( - permissionsOptionGroup.permissionsAccountsContractEnabled); - smartContractPermissioningConfiguration.setAccountSmartContractAddress( - permissionsOptionGroup.permissionsAccountsContractAddress); - } - } else if (permissionsOptionGroup.permissionsAccountsContractAddress != null) { - logger.warn( - "Account permissioning smart contract address set {} but smart contract account permissioning is disabled.", - permissionsOptionGroup.permissionsAccountsContractAddress); - } - - final PermissioningConfiguration permissioningConfiguration = - new PermissioningConfiguration( - localPermissioningConfigurationOptional, - Optional.of(smartContractPermissioningConfiguration)); - - return Optional.of(permissioningConfiguration); - } - - private boolean localPermissionsEnabled() { - return permissionsOptionGroup.permissionsAccountsEnabled - || permissionsOptionGroup.permissionsNodesEnabled; - } - - private boolean contractPermissionsEnabled() { - return permissionsOptionGroup.permissionsNodesContractEnabled - || permissionsOptionGroup.permissionsAccountsContractEnabled; - } - private PrivacyParameters privacyParameters() { CommandLineUtils.checkOptionDependencies( @@ -2673,8 +1992,15 @@ private PrivacyParameters privacyParameters() { if (syncMode == SyncMode.FAST) { throw new ParameterException(commandLine, String.format("%s %s", "Fast sync", errorSuffix)); } - if (isPruningEnabled()) { - throw new ParameterException(commandLine, String.format("%s %s", "Pruning", errorSuffix)); + if (syncMode == SyncMode.SNAP) { + throw new ParameterException(commandLine, String.format("%s %s", "Snap sync", errorSuffix)); + } + if (syncMode == SyncMode.CHECKPOINT) { + throw new ParameterException( + commandLine, String.format("%s %s", "Checkpoint sync", errorSuffix)); + } + if (getDataStorageConfiguration().getDataStorageFormat().equals(DataStorageFormat.BONSAI)) { + throw new ParameterException(commandLine, String.format("%s %s", "Bonsai", errorSuffix)); } if (Boolean.TRUE.equals(privacyOptionGroup.isPrivacyMultiTenancyEnabled) @@ -2690,10 +2016,11 @@ private PrivacyParameters privacyParameters() { privacyParametersBuilder.setMultiTenancyEnabled( privacyOptionGroup.isPrivacyMultiTenancyEnabled); privacyParametersBuilder.setFlexiblePrivacyGroupsEnabled( - privacyOptionGroup.isFlexiblePrivacyGroupsEnabled - || privacyOptionGroup.isOnchainPrivacyGroupsEnabled); + privacyOptionGroup.isFlexiblePrivacyGroupsEnabled); privacyParametersBuilder.setPrivacyPluginEnabled( unstablePrivacyPluginOptions.isPrivacyPluginEnabled()); + privacyParametersBuilder.setPrivateNonceAlwaysIncrementsEnabled( + privacyOptionGroup.isPrivateNonceAlwaysIncrementsEnabled); final boolean hasPrivacyPublicKey = privacyOptionGroup.privacyPublicKeyFile != null; @@ -2755,10 +2082,10 @@ private PrivacyParameters privacyParameters() { } private boolean anyPrivacyApiEnabled() { - return jsonRPCHttpOptionGroup.rpcHttpApis.contains(RpcApis.EEA.name()) - || jsonRPCWebsocketOptionGroup.rpcWsApis.contains(RpcApis.EEA.name()) - || jsonRPCHttpOptionGroup.rpcHttpApis.contains(RpcApis.PRIV.name()) - || jsonRPCWebsocketOptionGroup.rpcWsApis.contains(RpcApis.PRIV.name()); + return jsonRpcHttpOptions.getRpcHttpApis().contains(RpcApis.EEA.name()) + || rpcWebsocketOptions.getRpcWsApis().contains(RpcApis.EEA.name()) + || jsonRpcHttpOptions.getRpcHttpApis().contains(RpcApis.PRIV.name()) + || rpcWebsocketOptions.getRpcWsApis().contains(RpcApis.PRIV.name()); } private PrivacyKeyValueStorageProvider privacyKeyStorageProvider(final String name) { @@ -2804,41 +2131,39 @@ public StorageProvider getStorageProvider() { return keyValueStorageProvider(keyValueStorageName); } - private Optional maybePkiBlockCreationConfiguration() { - return pkiBlockCreationOptions - .asDomainConfig(commandLine) - .map(pkiBlockCreationConfigProvider::load); - } - private SynchronizerConfiguration buildSyncConfig() { return unstableSynchronizerOptions .toDomainObject() .syncMode(syncMode) - .fastSyncMinimumPeerCount(fastSyncMinPeerCount) + .syncMinimumPeerCount(syncMinPeerCount) .build(); } private TransactionPoolConfiguration buildTransactionPoolConfiguration() { + transactionPoolOptions.setPluginTransactionValidatorService(transactionValidatorServiceImpl); final var txPoolConf = transactionPoolOptions.toDomainObject(); final var txPoolConfBuilder = ImmutableTransactionPoolConfiguration.builder() .from(txPoolConf) .saveFile((dataPath.resolve(txPoolConf.getSaveFile().getPath()).toFile())); - if (getActualGenesisConfigOptions().isZeroBaseFee()) { - logger.info( + if (genesisConfigOptionsSupplier.get().isZeroBaseFee()) { + logger.warn( "Forcing price bump for transaction replacement to 0, since we are on a zero basefee network"); txPoolConfBuilder.priceBump(Percentage.ZERO); } - if (getMiningParameters().getMinTransactionGasPrice().equals(Wei.ZERO) + if (miningParametersSupplier.get().getMinTransactionGasPrice().equals(Wei.ZERO) && !transactionPoolOptions.isPriceBumpSet(commandLine)) { - logger.info( + logger.warn( "Forcing price bump for transaction replacement to 0, since min-gas-price is set to 0"); txPoolConfBuilder.priceBump(Percentage.ZERO); } - if (getMiningParameters().getMinTransactionGasPrice().lessThan(txPoolConf.getMinGasPrice())) { + if (miningParametersSupplier + .get() + .getMinTransactionGasPrice() + .lessThan(txPoolConf.getMinGasPrice())) { if (transactionPoolOptions.isMinGasPriceSet(commandLine)) { throw new ParameterException( commandLine, "tx-pool-min-gas-price cannot be greater than the value of min-gas-price"); @@ -2849,9 +2174,9 @@ private TransactionPoolConfiguration buildTransactionPoolConfiguration() { // the user of the change logger.warn( "Forcing tx-pool-min-gas-price=" - + getMiningParameters().getMinTransactionGasPrice().toDecimalString() + + miningParametersSupplier.get().getMinTransactionGasPrice().toDecimalString() + ", since it cannot be greater than the value of min-gas-price"); - txPoolConfBuilder.minGasPrice(getMiningParameters().getMinTransactionGasPrice()); + txPoolConfBuilder.minGasPrice(miningParametersSupplier.get().getMinTransactionGasPrice()); } } @@ -2859,39 +2184,71 @@ private TransactionPoolConfiguration buildTransactionPoolConfiguration() { } private MiningParameters getMiningParameters() { - if (miningParameters == null) { - final var miningParametersBuilder = - ImmutableMiningParameters.builder().from(miningOptions.toDomainObject()); - final var actualGenesisOptions = getActualGenesisConfigOptions(); - if (actualGenesisOptions.isPoa()) { - miningParametersBuilder.unstable( - ImmutableMiningParameters.Unstable.builder() - .minBlockTime(getMinBlockTime(actualGenesisOptions)) - .build()); + miningOptions.setTransactionSelectionService(transactionSelectionServiceImpl); + final var miningParameters = miningOptions.toDomainObject(); + getGenesisBlockPeriodSeconds(genesisConfigOptionsSupplier.get()) + .ifPresent(miningParameters::setBlockPeriodSeconds); + initMiningParametersMetrics(miningParameters); + return miningParameters; + } + + /** + * Get the data storage configuration + * + * @return the data storage configuration + */ + public DataStorageConfiguration getDataStorageConfiguration() { + if (dataStorageConfiguration == null) { + dataStorageConfiguration = dataStorageOptions.toDomainObject(); + } + + if (SyncMode.FULL.equals(getDefaultSyncModeIfNotSet()) + && DataStorageFormat.BONSAI.equals(dataStorageConfiguration.getDataStorageFormat()) + && dataStorageConfiguration.getBonsaiLimitTrieLogsEnabled()) { + + if (CommandLineUtils.isOptionSet( + commandLine, DataStorageOptions.BONSAI_LIMIT_TRIE_LOGS_ENABLED)) { + throw new ParameterException( + commandLine, + String.format( + "Cannot enable %s with --sync-mode=%s and --data-storage-format=%s. You must set %s or use a different sync-mode", + DataStorageOptions.BONSAI_LIMIT_TRIE_LOGS_ENABLED, + SyncMode.FULL, + DataStorageFormat.BONSAI, + DataStorageOptions.BONSAI_LIMIT_TRIE_LOGS_ENABLED + "=false")); } - miningParameters = miningParametersBuilder.build(); + + dataStorageConfiguration = + ImmutableDataStorageConfiguration.copyOf(dataStorageConfiguration) + .withBonsaiLimitTrieLogsEnabled(false); + logger.warn( + "Forcing {}, since it cannot be enabled with --sync-mode={} and --data-storage-format={}.", + DataStorageOptions.BONSAI_LIMIT_TRIE_LOGS_ENABLED + "=false", + SyncMode.FULL, + DataStorageFormat.BONSAI); } - return miningParameters; + return dataStorageConfiguration; } - private int getMinBlockTime(final GenesisConfigOptions genesisConfigOptions) { + private void initMiningParametersMetrics(final MiningParameters miningParameters) { + new MiningParametersMetrics(getMetricsSystem(), miningParameters); + } + + private OptionalInt getGenesisBlockPeriodSeconds( + final GenesisConfigOptions genesisConfigOptions) { if (genesisConfigOptions.isClique()) { - return genesisConfigOptions.getCliqueConfigOptions().getBlockPeriodSeconds(); + return OptionalInt.of(genesisConfigOptions.getCliqueConfigOptions().getBlockPeriodSeconds()); } if (genesisConfigOptions.isIbft2()) { - return genesisConfigOptions.getBftConfigOptions().getBlockPeriodSeconds(); + return OptionalInt.of(genesisConfigOptions.getBftConfigOptions().getBlockPeriodSeconds()); } if (genesisConfigOptions.isQbft()) { - return genesisConfigOptions.getQbftConfigOptions().getBlockPeriodSeconds(); + return OptionalInt.of(genesisConfigOptions.getQbftConfigOptions().getBlockPeriodSeconds()); } - throw new IllegalArgumentException("Should only be called for a PoA network"); - } - - private boolean isPruningEnabled() { - return pruningEnabled; + return OptionalInt.empty(); } // Blockchain synchronization from peers. @@ -2909,6 +2266,7 @@ private Runner synchronize( final JsonRpcConfiguration engineJsonRpcConfiguration, final WebSocketConfiguration webSocketConfiguration, final JsonRpcIpcConfiguration jsonRpcIpcConfiguration, + final InProcessRpcConfiguration inProcessRpcConfiguration, final ApiConfiguration apiConfiguration, final MetricsConfiguration metricsConfiguration, final Optional permissioningConfiguration, @@ -2941,6 +2299,7 @@ private Runner synchronize( .engineJsonRpcConfiguration(engineJsonRpcConfiguration) .webSocketConfiguration(webSocketConfiguration) .jsonRpcIpcConfiguration(jsonRpcIpcConfiguration) + .inProcessRpcConfiguration(inProcessRpcConfiguration) .apiConfiguration(apiConfiguration) .pidPath(pidPath) .dataDir(dataDir()) @@ -2956,6 +2315,8 @@ private Runner synchronize( .storageProvider(keyValueStorageProvider(keyValueStorageName)) .rpcEndpointService(rpcEndpointServiceImpl) .enodeDnsConfiguration(getEnodeDnsConfiguration()) + .allowedSubnets(p2PDiscoveryOptionGroup.allowedSubnets) + .poaDiscoveryRetryBootnodes(p2PDiscoveryOptionGroup.poaDiscoveryRetryBootnodes) .build(); addShutdownHook(runner); @@ -3011,16 +2372,14 @@ private EthNetworkConfig updateNetworkConfig(final NetworkName network) { + "refer to CLI reference for more details about this constraint."); } - builder.setGenesisConfig(genesisConfig()); - if (networkId == null) { // If no chain id is found in the genesis, use mainnet network id try { builder.setNetworkId( - getGenesisConfigFile() - .getConfigOptions(genesisConfigOverrides) + genesisConfigOptionsSupplier + .get() .getChainId() - .orElse(EthNetworkConfig.getNetworkConfig(MAINNET).getNetworkId())); + .orElse(EthNetworkConfig.getNetworkConfig(MAINNET).networkId())); } catch (final DecodeException e) { throw new ParameterException( this.commandLine, String.format("Unable to parse genesis file %s.", genesisFile), e); @@ -3038,18 +2397,20 @@ private EthNetworkConfig updateNetworkConfig(final NetworkName network) { builder.setDnsDiscoveryUrl(null); } + builder.setGenesisConfigFile(genesisConfigFileSupplier.get()); + + if (networkId != null) { + builder.setNetworkId(networkId); + } + if (p2PDiscoveryOptionGroup.discoveryDnsUrl != null) { builder.setDnsDiscoveryUrl(p2PDiscoveryOptionGroup.discoveryDnsUrl); - } else if (genesisConfigOptions != null) { + } else { final Optional discoveryDnsUrlFromGenesis = - genesisConfigOptions.getDiscoveryOptions().getDiscoveryDnsUrl(); + genesisConfigOptionsSupplier.get().getDiscoveryOptions().getDiscoveryDnsUrl(); discoveryDnsUrlFromGenesis.ifPresent(builder::setDnsDiscoveryUrl); } - if (networkId != null) { - builder.setNetworkId(networkId); - } - List listBootNodes = null; if (p2PDiscoveryOptionGroup.bootNodes != null) { try { @@ -3057,9 +2418,9 @@ private EthNetworkConfig updateNetworkConfig(final NetworkName network) { } catch (final IllegalArgumentException e) { throw new ParameterException(commandLine, e.getMessage()); } - } else if (genesisConfigOptions != null) { + } else { final Optional> bootNodesFromGenesis = - genesisConfigOptions.getDiscoveryOptions().getBootNodes(); + genesisConfigOptionsSupplier.get().getDiscoveryOptions().getBootNodes(); if (bootNodesFromGenesis.isPresent()) { listBootNodes = buildEnodes(bootNodesFromGenesis.get(), getEnodeDnsConfiguration()); } @@ -3074,28 +2435,15 @@ private EthNetworkConfig updateNetworkConfig(final NetworkName network) { return builder.build(); } - private GenesisConfigFile getGenesisConfigFile() { - return GenesisConfigFile.fromConfig(genesisConfig()); - } - - private String genesisConfig() { + private URL genesisConfigSource(final File genesisFile) { try { - return Resources.toString(genesisFile.toURI().toURL(), UTF_8); + return genesisFile.toURI().toURL(); } catch (final IOException e) { throw new ParameterException( this.commandLine, String.format("Unable to load genesis URL %s.", genesisFile), e); } } - private static String genesisConfig(final NetworkName networkName) { - try (final InputStream genesisFileInputStream = - EthNetworkConfig.class.getResourceAsStream(networkName.getGenesisFile())) { - return new String(genesisFileInputStream.readAllBytes(), UTF_8); - } catch (final IOException | NullPointerException e) { - throw new IllegalStateException(e); - } - } - /** * Returns data directory used by Besu. Visible as it is accessed by other subcommands. * @@ -3105,15 +2453,6 @@ public Path dataDir() { return dataPath.toAbsolutePath(); } - private Path pluginsDir() { - final String pluginsDir = System.getProperty("besu.plugins.dir"); - if (pluginsDir == null) { - return new File(System.getProperty("besu.home", "."), "plugins").toPath(); - } else { - return new File(pluginsDir).toPath(); - } - } - private SecurityModule securityModule() { return securityModuleService .getByName(securityModuleName) @@ -3126,30 +2465,6 @@ private File resolveNodePrivateKeyFile(final File nodePrivateKeyFile) { .orElseGet(() -> KeyPairUtil.getDefaultKeyFile(dataDir())); } - private String rpcHttpAuthenticationCredentialsFile() { - final String filename = jsonRPCHttpOptionGroup.rpcHttpAuthenticationCredentialsFile; - - if (filename != null) { - RpcAuthFileValidator.validate(commandLine, filename, "HTTP"); - } - return filename; - } - - private String rpcWsAuthenticationCredentialsFile() { - final String filename = jsonRPCWebsocketOptionGroup.rpcWsAuthenticationCredentialsFile; - - if (filename != null) { - RpcAuthFileValidator.validate(commandLine, filename, "WS"); - } - return filename; - } - - private String getDefaultPermissioningFilePath() { - return dataDir() - + System.getProperty("file.separator") - + DefaultCommandValues.PERMISSIONING_CONFIG_LOCATION; - } - /** * Metrics System used by Besu * @@ -3172,7 +2487,11 @@ private Set loadStaticNodes() throws IOException { staticNodesPath = dataDir().resolve(staticNodesFilename); } logger.debug("Static Nodes file: {}", staticNodesPath); - return StaticNodesParser.fromPath(staticNodesPath, getEnodeDnsConfiguration()); + final Set staticNodes = + StaticNodesParser.fromPath(staticNodesPath, getEnodeDnsConfiguration()); + logger.info("Connecting to {} static nodes.", staticNodes.size()); + logger.debug("Static Nodes = {}", staticNodes); + return staticNodes; } private List buildEnodes( @@ -3184,7 +2503,7 @@ private List buildEnodes( } /** - * Besu CLI Paramaters exception handler used by VertX. Visible for testing. + * Besu CLI Parameters exception handler used by VertX. Visible for testing. * * @return instance of BesuParameterExceptionHandler */ @@ -3243,11 +2562,12 @@ protected void checkIfRequiredPortsAreAvailable() { .forEach( port -> { if (port.equals(p2PDiscoveryOptionGroup.p2pPort) - && !NetworkUtility.isPortAvailable(port)) { + && (NetworkUtility.isPortUnavailableForTcp(port) + || NetworkUtility.isPortUnavailableForUdp(port))) { unavailablePorts.add(port); } if (!port.equals(p2PDiscoveryOptionGroup.p2pPort) - && !NetworkUtility.isPortAvailableForTcp(port)) { + && NetworkUtility.isPortUnavailableForTcp(port)) { unavailablePorts.add(port); } }); @@ -3269,24 +2589,20 @@ private List getEffectivePorts() { addPortIfEnabled( effectivePorts, p2PDiscoveryOptionGroup.p2pPort, p2PDiscoveryOptionGroup.p2pEnabled); addPortIfEnabled( - effectivePorts, - graphQlOptionGroup.graphQLHttpPort, - graphQlOptionGroup.isGraphQLHttpEnabled); + effectivePorts, graphQlOptions.getGraphQLHttpPort(), graphQlOptions.isGraphQLHttpEnabled()); addPortIfEnabled( - effectivePorts, - jsonRPCHttpOptionGroup.rpcHttpPort, - jsonRPCHttpOptionGroup.isRpcHttpEnabled); + effectivePorts, jsonRpcHttpOptions.getRpcHttpPort(), jsonRpcHttpOptions.isRpcHttpEnabled()); addPortIfEnabled( - effectivePorts, - jsonRPCWebsocketOptionGroup.rpcWsPort, - jsonRPCWebsocketOptionGroup.isRpcWsEnabled); + effectivePorts, rpcWebsocketOptions.getRpcWsPort(), rpcWebsocketOptions.isRpcWsEnabled()); addPortIfEnabled(effectivePorts, engineRPCOptionGroup.engineRpcPort, isEngineApiEnabled()); addPortIfEnabled( - effectivePorts, metricsOptionGroup.metricsPort, metricsOptionGroup.isMetricsEnabled); + effectivePorts, + metricsOptionGroup.getMetricsPort(), + metricsOptionGroup.getMetricsEnabled()); addPortIfEnabled( effectivePorts, - getMiningParameters().getStratumPort(), - getMiningParameters().isStratumMiningEnabled()); + miningParametersSupplier.get().getStratumPort(), + miningParametersSupplier.get().isStratumMiningEnabled()); return effectivePorts; } @@ -3309,22 +2625,14 @@ String getLogLevel() { return loggingLevelOption.getLogLevel(); } - private class BesuCommandConfigurationService implements BesuConfiguration { - - @Override - public Path getStoragePath() { - return dataDir().resolve(DATABASE_PATH); - } - - @Override - public Path getDataPath() { - return dataDir(); - } - - @Override - public int getDatabaseVersion() { - return dataStorageOptions.toDomainObject().getDataStorageFormat().getDatabaseVersion(); - } + /** + * Returns the flag indicating that version compatibility checks will be made. + * + * @return true if compatibility checks should be made, otherwise false + */ + @VisibleForTesting + public Boolean getVersionCompatibilityProtection() { + return versionCompatibilityProtection; } private void instantiateSignatureAlgorithmFactory() { @@ -3343,10 +2651,7 @@ private void instantiateSignatureAlgorithmFactory() { SignatureAlgorithmFactory.setInstance(SignatureAlgorithmType.create(ecCurve.get())); } catch (final IllegalArgumentException e) { throw new CommandLine.InitializationException( - new StringBuilder() - .append("Invalid genesis file configuration for ecCurve. ") - .append(e.getMessage()) - .toString()); + "Invalid genesis file configuration for ecCurve. " + e.getMessage()); } } @@ -3354,21 +2659,21 @@ private Optional getEcCurveFromGenesisFile() { if (genesisFile == null) { return Optional.empty(); } - return genesisConfigOptions.getEcCurve(); + return genesisConfigOptionsSupplier.get().getEcCurve(); } - private GenesisConfigOptions getActualGenesisConfigOptions() { - return Optional.ofNullable(genesisConfigOptions) - .orElseGet( - () -> - GenesisConfigFile.fromConfig( - genesisConfig(Optional.ofNullable(network).orElse(MAINNET))) - .getConfigOptions(genesisConfigOverrides)); + /** + * Return the genesis config options + * + * @return the genesis config options + */ + protected GenesisConfigOptions getGenesisConfigOptions() { + return genesisConfigOptionsSupplier.get(); } private void setMergeConfigOptions() { MergeConfigOptions.setMergeEnabled( - getActualGenesisConfigOptions().getTerminalTotalDifficulty().isPresent()); + genesisConfigOptionsSupplier.get().getTerminalTotalDifficulty().isPresent()); } /** Set ignorable segments in RocksDB Storage Provider plugin. */ @@ -3379,11 +2684,12 @@ public void setIgnorableStorageSegments() { } private void validatePostMergeCheckpointBlockRequirements() { - final GenesisConfigOptions genesisOptions = getActualGenesisConfigOptions(); final SynchronizerConfiguration synchronizerConfiguration = unstableSynchronizerOptions.toDomainObject().build(); - final Optional terminalTotalDifficulty = genesisOptions.getTerminalTotalDifficulty(); - final CheckpointConfigOptions checkpointConfigOptions = genesisOptions.getCheckpointOptions(); + final Optional terminalTotalDifficulty = + genesisConfigOptionsSupplier.get().getTerminalTotalDifficulty(); + final CheckpointConfigOptions checkpointConfigOptions = + genesisConfigOptionsSupplier.get().getCheckpointOptions(); if (synchronizerConfiguration.isCheckpointPostMergeEnabled()) { if (!checkpointConfigOptions.isValid()) { throw new InvalidConfigurationException( @@ -3391,15 +2697,13 @@ private void validatePostMergeCheckpointBlockRequirements() { } terminalTotalDifficulty.ifPresentOrElse( ttd -> { - if (UInt256.fromHexString( - genesisOptions.getCheckpointOptions().getTotalDifficulty().get()) + if (UInt256.fromHexString(checkpointConfigOptions.getTotalDifficulty().get()) .equals(UInt256.ZERO) && ttd.equals(UInt256.ZERO)) { throw new InvalidConfigurationException( "PoS checkpoint sync can't be used with TTD = 0 and checkpoint totalDifficulty = 0"); } - if (UInt256.fromHexString( - genesisOptions.getCheckpointOptions().getTotalDifficulty().get()) + if (UInt256.fromHexString(checkpointConfigOptions.getTotalDifficulty().get()) .lessThan(ttd)) { throw new InvalidConfigurationException( "PoS checkpoint sync requires a block with total difficulty greater or equal than the TTD"); @@ -3420,38 +2724,23 @@ private boolean isEngineApiEnabled() { return engineRPCOptionGroup.overrideEngineRpcEnabled || isMergeEnabled(); } - private static List getJDKEnabledCipherSuites() { - try { - final SSLContext context = SSLContext.getInstance("TLS"); - context.init(null, null, null); - final SSLEngine engine = context.createSSLEngine(); - return Arrays.asList(engine.getEnabledCipherSuites()); - } catch (final KeyManagementException | NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - } - - private static List getJDKEnabledProtocols() { - try { - final SSLContext context = SSLContext.getInstance("TLS"); - context.init(null, null, null); - final SSLEngine engine = context.createSSLEngine(); - return Arrays.asList(engine.getEnabledProtocols()); - } catch (final KeyManagementException | NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - } - private SyncMode getDefaultSyncModeIfNotSet() { return Optional.ofNullable(syncMode) .orElse( genesisFile == null && !privacyOptionGroup.isPrivacyEnabled - && Optional.ofNullable(network).map(NetworkName::canFastSync).orElse(false) - ? SyncMode.FAST + && Optional.ofNullable(network).map(NetworkName::canSnapSync).orElse(false) + ? SyncMode.SNAP : SyncMode.FULL); } + private Boolean getDefaultVersionCompatibilityProtectionIfNotSet() { + // Version compatibility protection is enabled by default for non-named networks + return Optional.ofNullable(versionCompatibilityProtection) + // if we have a specific genesis file or custom network id, we are not using a named network + .orElse(genesisFile != null || networkId != null); + } + private String generateConfigurationOverview() { final ConfigurationOverviewBuilder builder = new ConfigurationOverviewBuilder(logger); @@ -3463,8 +2752,15 @@ private String generateConfigurationOverview() { builder.setNetwork(network.normalize()); } + if (profile != null) { + builder.setProfile(profile); + } + builder.setHasCustomGenesis(genesisFile != null); - builder.setNetworkId(ethNetworkConfig.getNetworkId()); + if (genesisFile != null) { + builder.setCustomGenesis(genesisFile.getAbsolutePath()); + } + builder.setNetworkId(ethNetworkConfig.networkId()); builder .setDataStorage(dataStorageOptions.normalizeDataStorageFormat()) @@ -3495,18 +2791,21 @@ private String generateConfigurationOverview() { builder.setHighSpecEnabled(); } - if (dataStorageOptions.toDomainObject().getUnstable().getBonsaiTrieLogPruningEnabled()) { - builder.setTrieLogPruningEnabled(); - builder.setTrieLogRetentionThreshold( - dataStorageOptions.toDomainObject().getUnstable().getBonsaiTrieLogRetentionThreshold()); - builder.setTrieLogPruningLimit( - dataStorageOptions.toDomainObject().getUnstable().getBonsaiTrieLogPruningLimit()); + if (DataStorageFormat.BONSAI.equals(getDataStorageConfiguration().getDataStorageFormat()) + && getDataStorageConfiguration().getBonsaiLimitTrieLogsEnabled()) { + builder.setLimitTrieLogsEnabled(); + builder.setTrieLogRetentionLimit(getDataStorageConfiguration().getBonsaiMaxLayersToLoad()); + builder.setTrieLogsPruningWindowSize( + getDataStorageConfiguration().getBonsaiTrieLogPruningWindowSize()); } + builder.setSnapServerEnabled(this.unstableSynchronizerOptions.isSnapsyncServerEnabled()); + builder.setSnapSyncBftEnabled(this.unstableSynchronizerOptions.isSnapSyncBftEnabled()); + builder.setTxPoolImplementation(buildTransactionPoolConfiguration().getTxPoolImplementation()); builder.setWorldStateUpdateMode(unstableEvmOptions.toDomainObject().worldUpdaterMode()); - builder.setPluginContext(besuComponent.getBesuPluginContext()); + builder.setPluginContext(this.besuPluginContext); return builder.build(); } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilder.java b/besu/src/main/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilder.java index 5ed209ca311..b2f89a349e2 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -41,7 +41,9 @@ public class ConfigurationOverviewBuilder { private String network; private BigInteger networkId; + private String profile; private boolean hasCustomGenesis; + private String customGenesisFileName; private String dataStorage; private String syncMode; private Integer rpcPort; @@ -50,15 +52,19 @@ public class ConfigurationOverviewBuilder { private Collection engineApis; private String engineJwtFilePath; private boolean isHighSpec = false; - private boolean isTrieLogPruningEnabled = false; - private long trieLogRetentionThreshold = 0; - private Integer trieLogPruningLimit = null; + private boolean isBonsaiLimitTrieLogsEnabled = false; + private long trieLogRetentionLimit = 0; + private Integer trieLogsPruningWindowSize = null; + private boolean isSnapServerEnabled = false; + private boolean isSnapSyncBftEnabled = false; private TransactionPoolConfiguration.Implementation txPoolImplementation; private EvmConfiguration.WorldUpdaterMode worldStateUpdateMode; private Map environment; private BesuPluginContextImpl besuPluginContext; /** + * Create a new ConfigurationOverviewBuilder. + * * @param logger the logger */ public ConfigurationOverviewBuilder(final Logger logger) { @@ -87,6 +93,17 @@ public ConfigurationOverviewBuilder setNetworkId(final BigInteger networkId) { return this; } + /** + * Sets profile. + * + * @param profile the profile + * @return the profile + */ + public ConfigurationOverviewBuilder setProfile(final String profile) { + this.profile = profile; + return this; + } + /** * Sets whether a custom genesis has been specified. * @@ -98,6 +115,17 @@ public ConfigurationOverviewBuilder setHasCustomGenesis(final boolean hasCustomG return this; } + /** + * Sets location of custom genesis file specified. + * + * @param customGenesisFileName the filename of the custom genesis file, only set if specified + * @return the builder + */ + public ConfigurationOverviewBuilder setCustomGenesis(final String customGenesisFileName) { + this.customGenesisFileName = customGenesisFileName; + return this; + } + /** * Sets data storage. * @@ -175,34 +203,56 @@ public ConfigurationOverviewBuilder setHighSpecEnabled() { } /** - * Sets trie log pruning enabled + * Sets limit trie logs enabled + * + * @return the builder + */ + public ConfigurationOverviewBuilder setLimitTrieLogsEnabled() { + isBonsaiLimitTrieLogsEnabled = true; + return this; + } + + /** + * Sets trie log retention limit * + * @param limit the number of blocks to retain trie logs for * @return the builder */ - public ConfigurationOverviewBuilder setTrieLogPruningEnabled() { - isTrieLogPruningEnabled = true; + public ConfigurationOverviewBuilder setTrieLogRetentionLimit(final long limit) { + trieLogRetentionLimit = limit; return this; } /** - * Sets trie log retention threshold + * Sets snap server enabled/disabled * - * @param threshold the number of blocks to retain trie logs for + * @param snapServerEnabled bool to indicate if snap server is enabled * @return the builder */ - public ConfigurationOverviewBuilder setTrieLogRetentionThreshold(final long threshold) { - trieLogRetentionThreshold = threshold; + public ConfigurationOverviewBuilder setSnapServerEnabled(final boolean snapServerEnabled) { + isSnapServerEnabled = snapServerEnabled; return this; } /** - * Sets trie log pruning limit + * Sets snap sync BFT enabled/disabled * - * @param limit the max number of blocks to load and prune trie logs for at startup + * @param snapSyncBftEnabled bool to indicate if snap sync for BFT is enabled * @return the builder */ - public ConfigurationOverviewBuilder setTrieLogPruningLimit(final int limit) { - trieLogPruningLimit = limit; + public ConfigurationOverviewBuilder setSnapSyncBftEnabled(final boolean snapSyncBftEnabled) { + isSnapSyncBftEnabled = snapSyncBftEnabled; + return this; + } + + /** + * Sets trie logs pruning window size + * + * @param size the max number of blocks to load and prune trie logs for at startup + * @return the builder + */ + public ConfigurationOverviewBuilder setTrieLogsPruningWindowSize(final int size) { + trieLogsPruningWindowSize = size; return this; } @@ -269,13 +319,19 @@ public String build() { } if (hasCustomGenesis) { - lines.add("Network: Custom genesis file specified"); + lines.add("Network: Custom genesis file"); + lines.add( + customGenesisFileName == null ? "Custom genesis file is null" : customGenesisFileName); } if (networkId != null) { lines.add("Network Id: " + networkId); } + if (profile != null) { + lines.add("Profile: " + profile); + } + if (dataStorage != null) { lines.add("Data storage: " + dataStorage); } @@ -309,13 +365,21 @@ public String build() { lines.add("Using " + worldStateUpdateMode + " worldstate update mode"); - if (isTrieLogPruningEnabled) { + if (isSnapServerEnabled) { + lines.add("Experimental Snap Sync server enabled"); + } + + if (isSnapSyncBftEnabled) { + lines.add("Experimental Snap Sync for BFT enabled"); + } + + if (isBonsaiLimitTrieLogsEnabled) { final StringBuilder trieLogPruningString = new StringBuilder(); trieLogPruningString - .append("Trie log pruning enabled: retention: ") - .append(trieLogRetentionThreshold); - if (trieLogPruningLimit != null) { - trieLogPruningString.append("; prune limit: ").append(trieLogPruningLimit); + .append("Limit trie logs enabled: retention: ") + .append(trieLogRetentionLimit); + if (trieLogsPruningWindowSize != null) { + trieLogPruningString.append("; prune window: ").append(trieLogsPruningWindowSize); } lines.add(trieLogPruningString.toString()); } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/DefaultCommandValues.java b/besu/src/main/java/org/hyperledger/besu/cli/DefaultCommandValues.java index b7d603b8caa..ba05f455246 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/DefaultCommandValues.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/DefaultCommandValues.java @@ -36,64 +36,101 @@ public interface DefaultCommandValues { /** The constant MANDATORY_PATH_FORMAT_HELP. */ String MANDATORY_PATH_FORMAT_HELP = ""; + /** The constant MANDATORY_FILE_FORMAT_HELP. */ String MANDATORY_FILE_FORMAT_HELP = ""; + /** The constant MANDATORY_DIRECTORY_FORMAT_HELP. */ String MANDATORY_DIRECTORY_FORMAT_HELP = ""; + /** The constant BESU_HOME_PROPERTY_NAME. */ String BESU_HOME_PROPERTY_NAME = "besu.home"; + /** The constant DEFAULT_DATA_DIR_PATH. */ String DEFAULT_DATA_DIR_PATH = "./build/data"; + /** The constant MANDATORY_INTEGER_FORMAT_HELP. */ String MANDATORY_INTEGER_FORMAT_HELP = ""; + /** The constant MANDATORY_DOUBLE_FORMAT_HELP. */ String MANDATORY_DOUBLE_FORMAT_HELP = ""; + /** The constant MANDATORY_LONG_FORMAT_HELP. */ String MANDATORY_LONG_FORMAT_HELP = ""; + /** The constant MANDATORY_MODE_FORMAT_HELP. */ String MANDATORY_MODE_FORMAT_HELP = ""; + /** The constant MANDATORY_NETWORK_FORMAT_HELP. */ String MANDATORY_NETWORK_FORMAT_HELP = ""; + + /** The constant PROFILE_OPTION_NAME. */ + String PROFILE_OPTION_NAME = "--profile"; + + /** The constant PROFILE_FORMAT_HELP. */ + String PROFILE_FORMAT_HELP = ""; + /** The constant MANDATORY_NODE_ID_FORMAT_HELP. */ String MANDATORY_NODE_ID_FORMAT_HELP = ""; + /** The constant PERMISSIONING_CONFIG_LOCATION. */ String PERMISSIONING_CONFIG_LOCATION = "permissions_config.toml"; + /** The constant MANDATORY_HOST_FORMAT_HELP. */ String MANDATORY_HOST_FORMAT_HELP = ""; + /** The constant MANDATORY_PORT_FORMAT_HELP. */ String MANDATORY_PORT_FORMAT_HELP = ""; + /** The constant DEFAULT_NAT_METHOD. */ NatMethod DEFAULT_NAT_METHOD = NatMethod.AUTO; + /** The constant DEFAULT_JWT_ALGORITHM. */ JwtAlgorithm DEFAULT_JWT_ALGORITHM = JwtAlgorithm.RS256; - /** The constant FAST_SYNC_MIN_PEER_COUNT. */ - int FAST_SYNC_MIN_PEER_COUNT = 5; + + /** The constant SYNC_MIN_PEER_COUNT. */ + int SYNC_MIN_PEER_COUNT = 5; + /** The constant DEFAULT_MAX_PEERS. */ int DEFAULT_MAX_PEERS = 25; - /** The constant DEFAULT_P2P_PEER_LOWER_BOUND. */ - int DEFAULT_P2P_PEER_LOWER_BOUND = 25; + /** The constant DEFAULT_HTTP_MAX_CONNECTIONS. */ int DEFAULT_HTTP_MAX_CONNECTIONS = 80; + /** The constant DEFAULT_HTTP_MAX_BATCH_SIZE. */ int DEFAULT_HTTP_MAX_BATCH_SIZE = 1024; + /** The constant DEFAULT_MAX_REQUEST_CONTENT_LENGTH. */ long DEFAULT_MAX_REQUEST_CONTENT_LENGTH = 5 * 1024 * 1024; // 5MB + /** The constant DEFAULT_WS_MAX_CONNECTIONS. */ int DEFAULT_WS_MAX_CONNECTIONS = 80; + /** The constant DEFAULT_WS_MAX_FRAME_SIZE. */ int DEFAULT_WS_MAX_FRAME_SIZE = 1024 * 1024; + /** The constant DEFAULT_FRACTION_REMOTE_WIRE_CONNECTIONS_ALLOWED. */ float DEFAULT_FRACTION_REMOTE_WIRE_CONNECTIONS_ALLOWED = RlpxConfiguration.DEFAULT_FRACTION_REMOTE_CONNECTIONS_ALLOWED; + /** The constant DEFAULT_KEY_VALUE_STORAGE_NAME. */ String DEFAULT_KEY_VALUE_STORAGE_NAME = "rocksdb"; + /** The constant DEFAULT_SECURITY_MODULE. */ String DEFAULT_SECURITY_MODULE = "localfile"; + /** The constant DEFAULT_KEYSTORE_TYPE. */ String DEFAULT_KEYSTORE_TYPE = "JKS"; + /** The Default tls protocols. */ List DEFAULT_TLS_PROTOCOLS = List.of("TLSv1.3", "TLSv1.2"); + /** The constant DEFAULT_PLUGINS_OPTION_NAME. */ + String DEFAULT_PLUGINS_OPTION_NAME = "--plugins"; + + /** The constant DEFAULT_PLUGINS_EXTERNAL_ENABLED_OPTION_NAME. */ + String DEFAULT_PLUGINS_EXTERNAL_ENABLED_OPTION_NAME = "--Xplugins-external-enabled"; + /** * Gets default besu data path. * diff --git a/besu/src/main/java/org/hyperledger/besu/cli/NetworkDeprecationMessage.java b/besu/src/main/java/org/hyperledger/besu/cli/NetworkDeprecationMessage.java index 10c97d6a5ad..e4b4440787d 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/NetworkDeprecationMessage.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/NetworkDeprecationMessage.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -21,6 +21,7 @@ /** The Network deprecation message. */ public class NetworkDeprecationMessage { + private NetworkDeprecationMessage() {} /** * Generate deprecation message for specified testnet network. diff --git a/besu/src/main/java/org/hyperledger/besu/cli/config/EthNetworkConfig.java b/besu/src/main/java/org/hyperledger/besu/cli/config/EthNetworkConfig.java index 3af85016c8c..dd5e43e63fe 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/config/EthNetworkConfig.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/config/EthNetworkConfig.java @@ -14,8 +14,6 @@ */ package org.hyperledger.besu.cli.config; -import static java.nio.charset.StandardCharsets.UTF_8; - import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl; @@ -24,109 +22,41 @@ import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; +import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; -/** The Eth network config. */ -public class EthNetworkConfig { - - private final String genesisConfig; - private final BigInteger networkId; - private final List bootNodes; - private final String dnsDiscoveryUrl; +/** + * The Eth network config. + * + * @param genesisConfigFile Genesis Config File + * @param networkId Network Id + * @param bootNodes Boot Nodes + * @param dnsDiscoveryUrl DNS Discovery URL + */ +public record EthNetworkConfig( + GenesisConfigFile genesisConfigFile, + BigInteger networkId, + List bootNodes, + String dnsDiscoveryUrl) { /** - * Instantiates a new Eth network config. + * Validate parameters on new record creation * - * @param genesisConfig the genesis config + * @param genesisConfigFile the genesis config * @param networkId the network id * @param bootNodes the boot nodes * @param dnsDiscoveryUrl the dns discovery url */ - public EthNetworkConfig( - final String genesisConfig, - final BigInteger networkId, - final List bootNodes, - final String dnsDiscoveryUrl) { - Objects.requireNonNull(genesisConfig); + @SuppressWarnings( + "MethodInputParametersMustBeFinal") // needed since record constructors are not yet supported + public EthNetworkConfig { + Objects.requireNonNull(genesisConfigFile); Objects.requireNonNull(bootNodes); - this.genesisConfig = genesisConfig; - this.networkId = networkId; - this.bootNodes = bootNodes; - this.dnsDiscoveryUrl = dnsDiscoveryUrl; - } - - /** - * Gets genesis config. - * - * @return the genesis config - */ - public String getGenesisConfig() { - return genesisConfig; - } - - /** - * Gets network id. - * - * @return the network id - */ - public BigInteger getNetworkId() { - return networkId; - } - - /** - * Gets boot nodes. - * - * @return the boot nodes - */ - public List getBootNodes() { - return bootNodes; - } - - /** - * Gets dns discovery url. - * - * @return the dns discovery url - */ - public String getDnsDiscoveryUrl() { - return dnsDiscoveryUrl; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - final EthNetworkConfig that = (EthNetworkConfig) o; - return networkId.equals(that.networkId) - && Objects.equals(genesisConfig, that.genesisConfig) - && Objects.equals(bootNodes, that.bootNodes) - && Objects.equals(dnsDiscoveryUrl, that.dnsDiscoveryUrl); - } - - @Override - public int hashCode() { - return Objects.hash(genesisConfig, networkId, bootNodes, dnsDiscoveryUrl); - } - - @Override - public String toString() { - return "EthNetworkConfig{" - + "genesisConfig=" - + genesisConfig - + ", networkId=" - + networkId - + ", bootNodes=" - + bootNodes - + ", dnsDiscoveryUrl=" - + dnsDiscoveryUrl - + '}'; } /** @@ -136,9 +66,9 @@ public String toString() { * @return the network config */ public static EthNetworkConfig getNetworkConfig(final NetworkName networkName) { - final String genesisContent = jsonConfig(networkName.getGenesisFile()); - final GenesisConfigOptions genesisConfigOptions = - GenesisConfigFile.fromConfig(genesisContent).getConfigOptions(); + final URL genesisSource = jsonConfigSource(networkName.getGenesisFile()); + final GenesisConfigFile genesisConfigFile = GenesisConfigFile.fromSource(genesisSource); + final GenesisConfigOptions genesisConfigOptions = genesisConfigFile.getConfigOptions(); final Optional> rawBootNodes = genesisConfigOptions.getDiscoveryOptions().getBootNodes(); final List bootNodes = @@ -148,36 +78,36 @@ public static EthNetworkConfig getNetworkConfig(final NetworkName networkName) { strings.stream().map(EnodeURLImpl::fromString).collect(Collectors.toList())) .orElse(Collections.emptyList()); return new EthNetworkConfig( - genesisContent, + genesisConfigFile, networkName.getNetworkId(), bootNodes, genesisConfigOptions.getDiscoveryOptions().getDiscoveryDnsUrl().orElse(null)); } - private static String jsonConfig(final String resourceName) { - try (final InputStream genesisFileInputStream = - EthNetworkConfig.class.getResourceAsStream(resourceName)) { - return new String(genesisFileInputStream.readAllBytes(), UTF_8); - } catch (IOException | NullPointerException e) { - throw new IllegalStateException(e); - } + private static URL jsonConfigSource(final String resourceName) { + return EthNetworkConfig.class.getResource(resourceName); } /** * Json config string. * - * @param network the network - * @return the string + * @param network the named network + * @return the json string */ public static String jsonConfig(final NetworkName network) { - return jsonConfig(network.getGenesisFile()); + try (final InputStream genesisFileInputStream = + EthNetworkConfig.class.getResourceAsStream(network.getGenesisFile())) { + return new String(genesisFileInputStream.readAllBytes(), StandardCharsets.UTF_8); + } catch (IOException | NullPointerException e) { + throw new IllegalStateException(e); + } } /** The type Builder. */ public static class Builder { private String dnsDiscoveryUrl; - private String genesisConfig; + private GenesisConfigFile genesisConfigFile; private BigInteger networkId; private List bootNodes; @@ -187,20 +117,20 @@ public static class Builder { * @param ethNetworkConfig the eth network config */ public Builder(final EthNetworkConfig ethNetworkConfig) { - this.genesisConfig = ethNetworkConfig.genesisConfig; + this.genesisConfigFile = ethNetworkConfig.genesisConfigFile; this.networkId = ethNetworkConfig.networkId; this.bootNodes = ethNetworkConfig.bootNodes; this.dnsDiscoveryUrl = ethNetworkConfig.dnsDiscoveryUrl; } /** - * Sets genesis config. + * Sets genesis config file. * - * @param genesisConfig the genesis config - * @return the genesis config + * @param genesisConfigFile the genesis config + * @return this builder */ - public Builder setGenesisConfig(final String genesisConfig) { - this.genesisConfig = genesisConfig; + public Builder setGenesisConfigFile(final GenesisConfigFile genesisConfigFile) { + this.genesisConfigFile = genesisConfigFile; return this; } @@ -208,7 +138,7 @@ public Builder setGenesisConfig(final String genesisConfig) { * Sets network id. * * @param networkId the network id - * @return the network id + * @return this builder */ public Builder setNetworkId(final BigInteger networkId) { this.networkId = networkId; @@ -219,7 +149,7 @@ public Builder setNetworkId(final BigInteger networkId) { * Sets boot nodes. * * @param bootNodes the boot nodes - * @return the boot nodes + * @return this builder */ public Builder setBootNodes(final List bootNodes) { this.bootNodes = bootNodes; @@ -230,7 +160,7 @@ public Builder setBootNodes(final List bootNodes) { * Sets dns discovery url. * * @param dnsDiscoveryUrl the dns discovery url - * @return the dns discovery url + * @return this builder */ public Builder setDnsDiscoveryUrl(final String dnsDiscoveryUrl) { this.dnsDiscoveryUrl = dnsDiscoveryUrl; @@ -243,7 +173,7 @@ public Builder setDnsDiscoveryUrl(final String dnsDiscoveryUrl) { * @return the eth network config */ public EthNetworkConfig build() { - return new EthNetworkConfig(genesisConfig, networkId, bootNodes, dnsDiscoveryUrl); + return new EthNetworkConfig(genesisConfigFile, networkId, bootNodes, dnsDiscoveryUrl); } } } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/config/InternalProfileName.java b/besu/src/main/java/org/hyperledger/besu/cli/config/InternalProfileName.java new file mode 100644 index 00000000000..efac7a52032 --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/config/InternalProfileName.java @@ -0,0 +1,88 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.config; + +import java.util.Arrays; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; + +/** + * Enum for profile names which are bundled. Each profile corresponds to a bundled configuration + * file. + */ +public enum InternalProfileName { + /** The 'STAKER' profile */ + STAKER("profiles/staker.toml"), + /** The 'MINIMALIST_STAKER' profile */ + MINIMALIST_STAKER("profiles/minimalist-staker.toml"), + /** The 'ENTERPRISE' profile */ + ENTERPRISE("profiles/enterprise-private.toml"), + /** The 'PRIVATE' profile */ + PRIVATE("profiles/enterprise-private.toml"), + /** The 'DEV' profile. */ + DEV("profiles/dev.toml"); + + private final String configFile; + + /** + * Returns the InternalProfileName that matches the given name, ignoring case. + * + * @param name The profile name + * @return Optional InternalProfileName if found, otherwise empty + */ + public static Optional valueOfIgnoreCase(final String name) { + return Arrays.stream(values()) + .filter(profile -> profile.name().equalsIgnoreCase(name)) + .findFirst(); + } + + /** + * Returns the set of internal profile names as lowercase. + * + * @return Set of internal profile names + */ + public static Set getInternalProfileNames() { + return Arrays.stream(InternalProfileName.values()) + .map(InternalProfileName::name) + .map(String::toLowerCase) + .collect(Collectors.toSet()); + } + + /** + * Constructs a new ProfileName. + * + * @param configFile the configuration file corresponding to the profile + */ + InternalProfileName(final String configFile) { + this.configFile = configFile; + } + + /** + * Gets the configuration file corresponding to the profile. + * + * @return the configuration file + */ + public String getConfigFile() { + return configFile; + } + + @Override + public String toString() { + return StringUtils.capitalize(name().replaceAll("_", " ")); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/config/NetworkName.java b/besu/src/main/java/org/hyperledger/besu/cli/config/NetworkName.java index 6638ddfa44e..f864068bb4e 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/config/NetworkName.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/config/NetworkName.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.cli.config; import java.math.BigInteger; +import java.util.Locale; import java.util.Optional; import org.apache.commons.lang3.StringUtils; @@ -25,10 +26,10 @@ public enum NetworkName { MAINNET("/mainnet.json", BigInteger.valueOf(1)), /** Sepolia network name. */ SEPOLIA("/sepolia.json", BigInteger.valueOf(11155111)), - /** Goerli network name. */ - GOERLI("/goerli.json", BigInteger.valueOf(5)), /** Holešky network name. */ HOLESKY("/holesky.json", BigInteger.valueOf(17000)), + /** LUKSO mainnet network name. */ + LUKSO("/lukso.json", BigInteger.valueOf(42)), /** Dev network name. */ DEV("/dev.json", BigInteger.valueOf(2018), false), @@ -43,17 +44,17 @@ public enum NetworkName { private final String genesisFile; private final BigInteger networkId; - private final boolean canFastSync; + private final boolean canSnapSync; private final String deprecationDate; NetworkName(final String genesisFile, final BigInteger networkId) { this(genesisFile, networkId, true); } - NetworkName(final String genesisFile, final BigInteger networkId, final boolean canFastSync) { + NetworkName(final String genesisFile, final BigInteger networkId, final boolean canSnapSync) { this.genesisFile = genesisFile; this.networkId = networkId; - this.canFastSync = canFastSync; + this.canSnapSync = canSnapSync; // no deprecations planned this.deprecationDate = null; } @@ -77,12 +78,12 @@ public BigInteger getNetworkId() { } /** - * Can fast sync boolean. + * Can SNAP sync boolean. * * @return the boolean */ - public boolean canFastSync() { - return canFastSync; + public boolean canSnapSync() { + return canSnapSync; } /** @@ -91,7 +92,7 @@ public boolean canFastSync() { * @return the string */ public String normalize() { - return StringUtils.capitalize(name().toLowerCase()); + return StringUtils.capitalize(name().toLowerCase(Locale.ROOT)); } /** diff --git a/besu/src/main/java/org/hyperledger/besu/cli/config/ProfilesCompletionCandidates.java b/besu/src/main/java/org/hyperledger/besu/cli/config/ProfilesCompletionCandidates.java new file mode 100644 index 00000000000..51e9c49a0ab --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/config/ProfilesCompletionCandidates.java @@ -0,0 +1,37 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.config; + +import org.hyperledger.besu.cli.util.ProfileFinder; + +import java.util.Iterator; +import java.util.Set; +import java.util.TreeSet; + +/** Provides a list of profile names that can be used for command line completion. */ +public class ProfilesCompletionCandidates implements Iterable { + /** + * Create a new instance of ProfilesCompletionCandidates. This constructor is required for + * Picocli. + */ + public ProfilesCompletionCandidates() {} + + @Override + public Iterator iterator() { + final Set profileNames = new TreeSet<>(InternalProfileName.getInternalProfileNames()); + profileNames.addAll(ProfileFinder.getExternalProfileNames()); + return profileNames.iterator(); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/converter/DurationMillisConverter.java b/besu/src/main/java/org/hyperledger/besu/cli/converter/DurationMillisConverter.java index bfaffa8826a..bb1e15c18e8 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/converter/DurationMillisConverter.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/converter/DurationMillisConverter.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -24,6 +24,9 @@ public class DurationMillisConverter implements CommandLine.ITypeConverter, TypeFormatter { + /** Default constructor. */ + public DurationMillisConverter() {} + @Override public Duration convert(final String value) throws DurationConversionException { try { diff --git a/besu/src/main/java/org/hyperledger/besu/cli/converter/FractionConverter.java b/besu/src/main/java/org/hyperledger/besu/cli/converter/FractionConverter.java index 64edf7cd9ce..6cfbe59f570 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/converter/FractionConverter.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/converter/FractionConverter.java @@ -22,6 +22,9 @@ /** The Fraction converter to convert floats in CLI. */ public class FractionConverter implements CommandLine.ITypeConverter { + /** Default constructor. */ + public FractionConverter() {} + @Override public Fraction convert(final String value) throws FractionConversionException { try { diff --git a/besu/src/main/java/org/hyperledger/besu/cli/converter/MetricCategoryConverter.java b/besu/src/main/java/org/hyperledger/besu/cli/converter/MetricCategoryConverter.java index 684c3ce30c0..339da86b2d7 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/converter/MetricCategoryConverter.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/converter/MetricCategoryConverter.java @@ -18,6 +18,7 @@ import java.util.EnumSet; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import com.google.common.annotations.VisibleForTesting; @@ -28,6 +29,9 @@ public class MetricCategoryConverter implements CommandLine.ITypeConverter metricCategories = new HashMap<>(); + /** Default Constructor. */ + public MetricCategoryConverter() {} + @Override public MetricCategory convert(final String value) { final MetricCategory category = metricCategories.get(value); @@ -54,7 +58,7 @@ public & MetricCategory> void addCategories(final Class ca * @param metricCategory the metric category */ public void addRegistryCategory(final MetricCategory metricCategory) { - metricCategories.put(metricCategory.getName().toUpperCase(), metricCategory); + metricCategories.put(metricCategory.getName().toUpperCase(Locale.ROOT), metricCategory); } /** diff --git a/besu/src/main/java/org/hyperledger/besu/cli/converter/PercentageConverter.java b/besu/src/main/java/org/hyperledger/besu/cli/converter/PercentageConverter.java index 410ec83a21d..9967c26fbf5 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/converter/PercentageConverter.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/converter/PercentageConverter.java @@ -21,6 +21,8 @@ /** The Percentage Cli type converter. */ public class PercentageConverter implements CommandLine.ITypeConverter { + /** Default Constructor. */ + public PercentageConverter() {} @Override public Percentage convert(final String value) throws PercentageConversionException { diff --git a/besu/src/main/java/org/hyperledger/besu/cli/converter/PluginInfoConverter.java b/besu/src/main/java/org/hyperledger/besu/cli/converter/PluginInfoConverter.java new file mode 100644 index 00000000000..8f701dc608c --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/converter/PluginInfoConverter.java @@ -0,0 +1,55 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.converter; + +import org.hyperledger.besu.ethereum.core.plugins.PluginInfo; + +import java.util.List; +import java.util.stream.Stream; + +import picocli.CommandLine; + +/** + * Converts a comma-separated string into a list of {@link PluginInfo} objects. This converter is + * intended for use with PicoCLI to process command line arguments that specify plugin information. + */ +public class PluginInfoConverter implements CommandLine.ITypeConverter> { + /** Default Constructor. */ + public PluginInfoConverter() {} + + /** + * Converts a comma-separated string into a list of {@link PluginInfo}. + * + * @param value The comma-separated string representing plugin names. + * @return A list of {@link PluginInfo} objects created from the provided string. + */ + @Override + public List convert(final String value) { + if (value == null || value.isBlank()) { + return List.of(); + } + return Stream.of(value.split(",")).map(String::trim).map(this::toPluginInfo).toList(); + } + + /** + * Creates a {@link PluginInfo} object from a plugin name. + * + * @param pluginName The name of the plugin. + * @return A {@link PluginInfo} object representing the plugin. + */ + private PluginInfo toPluginInfo(final String pluginName) { + return new PluginInfo(pluginName); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/converter/PositiveNumberConverter.java b/besu/src/main/java/org/hyperledger/besu/cli/converter/PositiveNumberConverter.java new file mode 100644 index 00000000000..1b37b12700d --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/converter/PositiveNumberConverter.java @@ -0,0 +1,35 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.converter; + +import org.hyperledger.besu.cli.converter.exception.PercentageConversionException; +import org.hyperledger.besu.util.number.PositiveNumber; + +import picocli.CommandLine; + +/** The PositiveNumber Cli type converter. */ +public class PositiveNumberConverter implements CommandLine.ITypeConverter { + /** Default Constructor. */ + public PositiveNumberConverter() {} + + @Override + public PositiveNumber convert(final String value) throws PercentageConversionException { + try { + return PositiveNumber.fromString(value); + } catch (NullPointerException | IllegalArgumentException e) { + throw new PercentageConversionException(value); + } + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/converter/SubnetInfoConverter.java b/besu/src/main/java/org/hyperledger/besu/cli/converter/SubnetInfoConverter.java new file mode 100644 index 00000000000..9ad9db9d14e --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/converter/SubnetInfoConverter.java @@ -0,0 +1,36 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.converter; + +import org.apache.commons.net.util.SubnetUtils; +import org.apache.commons.net.util.SubnetUtils.SubnetInfo; +import picocli.CommandLine; + +/** The SubnetInfo converter for CLI options. */ +public class SubnetInfoConverter implements CommandLine.ITypeConverter { + /** Default Constructor. */ + public SubnetInfoConverter() {} + + /** + * Converts an IP addresses with CIDR notation into SubnetInfo + * + * @param value The IP addresses with CIDR notation. + * @return the SubnetInfo + */ + @Override + public SubnetInfo convert(final String value) { + return new SubnetUtils(value).getInfo(); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/converter/TypeFormatter.java b/besu/src/main/java/org/hyperledger/besu/cli/converter/TypeFormatter.java index 59658d135ac..965cac9b6cf 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/converter/TypeFormatter.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/converter/TypeFormatter.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/besu/src/main/java/org/hyperledger/besu/cli/converter/exception/DurationConversionException.java b/besu/src/main/java/org/hyperledger/besu/cli/converter/exception/DurationConversionException.java index cd56d06188c..a2000c5d65b 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/converter/exception/DurationConversionException.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/converter/exception/DurationConversionException.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/besu/src/main/java/org/hyperledger/besu/cli/converter/exception/PositiveNumberConversionException.java b/besu/src/main/java/org/hyperledger/besu/cli/converter/exception/PositiveNumberConversionException.java new file mode 100644 index 00000000000..26689aba8e3 --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/converter/exception/PositiveNumberConversionException.java @@ -0,0 +1,30 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.converter.exception; + +import static java.lang.String.format; + +/** The custom PositiveNumber conversion exception. */ +public final class PositiveNumberConversionException extends Exception { + + /** + * Instantiates a new PositiveNumber conversion exception. + * + * @param value the invalid value to add in exception message + */ + public PositiveNumberConversionException(final String value) { + super(format("Invalid value: %s, should be a positive number >0.", value)); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/custom/RpcAuthFileValidator.java b/besu/src/main/java/org/hyperledger/besu/cli/custom/RpcAuthFileValidator.java index 35c5bf13865..68b5afe48a3 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/custom/RpcAuthFileValidator.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/custom/RpcAuthFileValidator.java @@ -29,6 +29,8 @@ /** The Rpc authentication file validator. */ public class RpcAuthFileValidator { + /** Default Constructor. */ + RpcAuthFileValidator() {} /** * Validate auth file. diff --git a/besu/src/main/java/org/hyperledger/besu/cli/error/BesuExecutionExceptionHandler.java b/besu/src/main/java/org/hyperledger/besu/cli/error/BesuExecutionExceptionHandler.java index 77b458eb78c..8c2e3b36114 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/error/BesuExecutionExceptionHandler.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/error/BesuExecutionExceptionHandler.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -20,6 +20,10 @@ /** Custom Execution Exception Handler used by PicoCLI framework. */ public class BesuExecutionExceptionHandler implements IExecutionExceptionHandler { + + /** Default constructor. */ + public BesuExecutionExceptionHandler() {} + @Override public int handleExecutionException( final Exception ex, diff --git a/besu/src/main/java/org/hyperledger/besu/cli/logging/BesuLoggingConfigurationFactory.java b/besu/src/main/java/org/hyperledger/besu/cli/logging/BesuLoggingConfigurationFactory.java index c4af7e23b5b..0051d1bc284 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/logging/BesuLoggingConfigurationFactory.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/logging/BesuLoggingConfigurationFactory.java @@ -21,6 +21,8 @@ /** Custom Log4J Configuration Factory for Besu */ public class BesuLoggingConfigurationFactory extends ConfigurationFactory { + /** Default constructor. */ + public BesuLoggingConfigurationFactory() {} @Override protected String[] getSupportedTypes() { diff --git a/besu/src/main/java/org/hyperledger/besu/cli/logging/XmlExtensionConfiguration.java b/besu/src/main/java/org/hyperledger/besu/cli/logging/XmlExtensionConfiguration.java index d2ae7071bdb..1e873276557 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/logging/XmlExtensionConfiguration.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/logging/XmlExtensionConfiguration.java @@ -102,7 +102,7 @@ private void createConsoleAppender() { dim("%t"), colorize("%-5level"), dim("%c{1}"), - colorize("%msg%n%throwable"))) + colorize("%msgc%n%throwable"))) .build(); final ConsoleAppender consoleAppender = ConsoleAppender.newBuilder().setName("Console").setLayout(patternLayout).build(); diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/MiningOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/MiningOptions.java index 78e2032e4f6..a884edfe334 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/MiningOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/MiningOptions.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,22 +14,23 @@ */ package org.hyperledger.besu.cli.options; +import static com.google.common.base.Preconditions.checkNotNull; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; +import static org.hyperledger.besu.ethereum.core.MiningParameters.DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME; +import static org.hyperledger.besu.ethereum.core.MiningParameters.DEFAULT_POA_BLOCK_TXS_SELECTION_MAX_TIME; import static org.hyperledger.besu.ethereum.core.MiningParameters.MutableInitValues.DEFAULT_EXTRA_DATA; import static org.hyperledger.besu.ethereum.core.MiningParameters.MutableInitValues.DEFAULT_MIN_BLOCK_OCCUPANCY_RATIO; import static org.hyperledger.besu.ethereum.core.MiningParameters.MutableInitValues.DEFAULT_MIN_PRIORITY_FEE_PER_GAS; import static org.hyperledger.besu.ethereum.core.MiningParameters.MutableInitValues.DEFAULT_MIN_TRANSACTION_GAS_PRICE; import static org.hyperledger.besu.ethereum.core.MiningParameters.Unstable.DEFAULT_MAX_OMMERS_DEPTH; -import static org.hyperledger.besu.ethereum.core.MiningParameters.Unstable.DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME; -import static org.hyperledger.besu.ethereum.core.MiningParameters.Unstable.DEFAULT_POA_BLOCK_TXS_SELECTION_MAX_TIME; import static org.hyperledger.besu.ethereum.core.MiningParameters.Unstable.DEFAULT_POS_BLOCK_CREATION_MAX_TIME; import static org.hyperledger.besu.ethereum.core.MiningParameters.Unstable.DEFAULT_POS_BLOCK_CREATION_REPETITION_MIN_DURATION; import static org.hyperledger.besu.ethereum.core.MiningParameters.Unstable.DEFAULT_POW_JOB_TTL; import static org.hyperledger.besu.ethereum.core.MiningParameters.Unstable.DEFAULT_REMOTE_SEALERS_LIMIT; import static org.hyperledger.besu.ethereum.core.MiningParameters.Unstable.DEFAULT_REMOTE_SEALERS_TTL; -import org.hyperledger.besu.cli.converter.PercentageConverter; +import org.hyperledger.besu.cli.converter.PositiveNumberConverter; import org.hyperledger.besu.cli.util.CommandLineUtils; import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.datatypes.Address; @@ -37,7 +38,8 @@ import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters; import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters.MutableInitValues; import org.hyperledger.besu.ethereum.core.MiningParameters; -import org.hyperledger.besu.util.number.Percentage; +import org.hyperledger.besu.plugin.services.TransactionSelectionService; +import org.hyperledger.besu.util.number.PositiveNumber; import java.util.List; @@ -57,7 +59,10 @@ public class MiningOptions implements CLIOptions { @Option( names = {"--miner-stratum-enabled"}, - description = "Set if node will perform Stratum mining (default: ${DEFAULT-VALUE})") + description = + "Set if node will perform Stratum mining (default: ${DEFAULT-VALUE})." + + " Compatible with Proof of Work (PoW) only." + + " Requires the network option (--network) to be set to CLASSIC.") private Boolean iStratumMiningEnabled = false; @Option( @@ -115,6 +120,24 @@ public class MiningOptions implements CLIOptions { + " If set, each block's gas limit will approach this setting over time.") private Long targetGasLimit = null; + @Option( + names = {"--block-txs-selection-max-time"}, + converter = PositiveNumberConverter.class, + description = + "Specifies the maximum time, in milliseconds, that could be spent selecting transactions to be included in the block." + + " Not compatible with PoA networks, see poa-block-txs-selection-max-time. (default: ${DEFAULT-VALUE})") + private PositiveNumber nonPoaBlockTxsSelectionMaxTime = + DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME; + + @Option( + names = {"--poa-block-txs-selection-max-time"}, + converter = PositiveNumberConverter.class, + description = + "Specifies the maximum time that could be spent selecting transactions to be included in the block, as a percentage of the fixed block time of the PoA network." + + " To be only used on PoA networks, for other networks see block-txs-selection-max-time." + + " (default: ${DEFAULT-VALUE})") + private PositiveNumber poaBlockTxsSelectionMaxTime = DEFAULT_POA_BLOCK_TXS_SELECTION_MAX_TIME; + @CommandLine.ArgGroup(validate = false) private final Unstable unstableOptions = new Unstable(); @@ -168,27 +191,10 @@ static class Unstable { + " then it waits before next repetition. Must be positive and ≤ 2000 (default: ${DEFAULT-VALUE} milliseconds)") private Long posBlockCreationRepetitionMinDuration = DEFAULT_POS_BLOCK_CREATION_REPETITION_MIN_DURATION; - - @CommandLine.Option( - hidden = true, - names = {"--Xblock-txs-selection-max-time"}, - description = - "Specifies the maximum time, in milliseconds, that could be spent selecting transactions to be included in the block." - + " Not compatible with PoA networks, see Xpoa-block-txs-selection-max-time." - + " Must be positive and ≤ (default: ${DEFAULT-VALUE})") - private Long nonPoaBlockTxsSelectionMaxTime = DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME; - - @CommandLine.Option( - hidden = true, - names = {"--Xpoa-block-txs-selection-max-time"}, - converter = PercentageConverter.class, - description = - "Specifies the maximum time that could be spent selecting transactions to be included in the block, as a percentage of the fixed block time of the PoA network." - + " To be only used on PoA networks, for other networks see Xblock-txs-selection-max-time." - + " (default: ${DEFAULT-VALUE})") - private Percentage poaBlockTxsSelectionMaxTime = DEFAULT_POA_BLOCK_TXS_SELECTION_MAX_TIME; } + private TransactionSelectionService transactionSelectionService; + private MiningOptions() {} /** @@ -200,6 +206,16 @@ public static MiningOptions create() { return new MiningOptions(); } + /** + * Set the transaction selection service + * + * @param transactionSelectionService the transaction selection service + */ + public void setTransactionSelectionService( + final TransactionSelectionService transactionSelectionService) { + this.transactionSelectionService = transactionSelectionService; + } + /** * Validate that there are no inconsistencies in the specified options. For example that the * options are valid for the selected implementation. @@ -270,31 +286,23 @@ public void validate( if (genesisConfigOptions.isPoa()) { CommandLineUtils.failIfOptionDoesntMeetRequirement( commandLine, - "--Xblock-txs-selection-max-time can't be used with PoA networks," - + " see Xpoa-block-txs-selection-max-time instead", + "--block-txs-selection-max-time can't be used with PoA networks," + + " see poa-block-txs-selection-max-time instead", false, - singletonList("--Xblock-txs-selection-max-time")); + singletonList("--block-txs-selection-max-time")); } else { CommandLineUtils.failIfOptionDoesntMeetRequirement( commandLine, - "--Xpoa-block-txs-selection-max-time can be only used with PoA networks," - + " see --Xblock-txs-selection-max-time instead", + "--poa-block-txs-selection-max-time can be only used with PoA networks," + + " see --block-txs-selection-max-time instead", false, - singletonList("--Xpoa-block-txs-selection-max-time")); - - if (unstableOptions.nonPoaBlockTxsSelectionMaxTime <= 0 - || unstableOptions.nonPoaBlockTxsSelectionMaxTime - > DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME) { - throw new ParameterException( - commandLine, - "--Xblock-txs-selection-max-time must be positive and ≤ " - + DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME); - } + singletonList("--poa-block-txs-selection-max-time")); } } static MiningOptions fromConfig(final MiningParameters miningParameters) { final MiningOptions miningOptions = MiningOptions.create(); + miningOptions.setTransactionSelectionService(miningParameters.getTransactionSelectionService()); miningOptions.isMiningEnabled = miningParameters.isMiningEnabled(); miningOptions.iStratumMiningEnabled = miningParameters.isStratumMiningEnabled(); miningOptions.stratumNetworkInterface = miningParameters.getStratumNetworkInterface(); @@ -303,6 +311,10 @@ static MiningOptions fromConfig(final MiningParameters miningParameters) { miningOptions.minTransactionGasPrice = miningParameters.getMinTransactionGasPrice(); miningOptions.minPriorityFeePerGas = miningParameters.getMinPriorityFeePerGas(); miningOptions.minBlockOccupancyRatio = miningParameters.getMinBlockOccupancyRatio(); + miningOptions.nonPoaBlockTxsSelectionMaxTime = + miningParameters.getNonPoaBlockTxsSelectionMaxTime(); + miningOptions.poaBlockTxsSelectionMaxTime = miningParameters.getPoaBlockTxsSelectionMaxTime(); + miningOptions.unstableOptions.remoteSealersLimit = miningParameters.getUnstable().getRemoteSealersLimit(); miningOptions.unstableOptions.remoteSealersTimeToLive = @@ -317,10 +329,6 @@ static MiningOptions fromConfig(final MiningParameters miningParameters) { miningParameters.getUnstable().getPosBlockCreationMaxTime(); miningOptions.unstableOptions.posBlockCreationRepetitionMinDuration = miningParameters.getUnstable().getPosBlockCreationRepetitionMinDuration(); - miningOptions.unstableOptions.nonPoaBlockTxsSelectionMaxTime = - miningParameters.getUnstable().getBlockTxsSelectionMaxTime(); - miningOptions.unstableOptions.poaBlockTxsSelectionMaxTime = - miningParameters.getUnstable().getPoaBlockTxsSelectionMaxTime(); miningParameters.getCoinbase().ifPresent(coinbase -> miningOptions.coinbase = coinbase); miningParameters.getTargetGasLimit().ifPresent(tgl -> miningOptions.targetGasLimit = tgl); @@ -329,6 +337,10 @@ static MiningOptions fromConfig(final MiningParameters miningParameters) { @Override public MiningParameters toDomainObject() { + checkNotNull( + transactionSelectionService, + "transactionSelectionService must be set before using this object"); + final var updatableInitValuesBuilder = MutableInitValues.builder() .isMiningEnabled(isMiningEnabled) @@ -344,27 +356,26 @@ public MiningParameters toDomainObject() { updatableInitValuesBuilder.coinbase(coinbase); } - final var miningParametersBuilder = - ImmutableMiningParameters.builder() - .mutableInitValues(updatableInitValuesBuilder.build()) - .isStratumMiningEnabled(iStratumMiningEnabled) - .stratumNetworkInterface(stratumNetworkInterface) - .stratumPort(stratumPort) - .unstable( - ImmutableMiningParameters.Unstable.builder() - .remoteSealersLimit(unstableOptions.remoteSealersLimit) - .remoteSealersTimeToLive(unstableOptions.remoteSealersTimeToLive) - .powJobTimeToLive(unstableOptions.powJobTimeToLive) - .maxOmmerDepth(unstableOptions.maxOmmersDepth) - .stratumExtranonce(unstableOptions.stratumExtranonce) - .posBlockCreationMaxTime(unstableOptions.posBlockCreationMaxTime) - .posBlockCreationRepetitionMinDuration( - unstableOptions.posBlockCreationRepetitionMinDuration) - .nonPoaBlockTxsSelectionMaxTime(unstableOptions.nonPoaBlockTxsSelectionMaxTime) - .poaBlockTxsSelectionMaxTime(unstableOptions.poaBlockTxsSelectionMaxTime) - .build()); - - return miningParametersBuilder.build(); + return ImmutableMiningParameters.builder() + .transactionSelectionService(transactionSelectionService) + .mutableInitValues(updatableInitValuesBuilder.build()) + .isStratumMiningEnabled(iStratumMiningEnabled) + .stratumNetworkInterface(stratumNetworkInterface) + .stratumPort(stratumPort) + .nonPoaBlockTxsSelectionMaxTime(nonPoaBlockTxsSelectionMaxTime) + .poaBlockTxsSelectionMaxTime(poaBlockTxsSelectionMaxTime) + .unstable( + ImmutableMiningParameters.Unstable.builder() + .remoteSealersLimit(unstableOptions.remoteSealersLimit) + .remoteSealersTimeToLive(unstableOptions.remoteSealersTimeToLive) + .powJobTimeToLive(unstableOptions.powJobTimeToLive) + .maxOmmerDepth(unstableOptions.maxOmmersDepth) + .stratumExtranonce(unstableOptions.stratumExtranonce) + .posBlockCreationMaxTime(unstableOptions.posBlockCreationMaxTime) + .posBlockCreationRepetitionMinDuration( + unstableOptions.posBlockCreationRepetitionMinDuration) + .build()) + .build(); } @Override diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/OptionParser.java b/besu/src/main/java/org/hyperledger/besu/cli/options/OptionParser.java index 003ad8846e8..379a7f7bc72 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/OptionParser.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/OptionParser.java @@ -29,6 +29,8 @@ /** The Option parser. */ public class OptionParser { + /** Default Constructor. */ + OptionParser() {} /** * Parse long range range. diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/TransactionPoolOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/TransactionPoolOptions.java index 3b34c4d2a61..6d1a6141ef4 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/TransactionPoolOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/TransactionPoolOptions.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -19,6 +19,7 @@ import static org.hyperledger.besu.cli.DefaultCommandValues.MANDATORY_LONG_FORMAT_HELP; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration.Implementation.LAYERED; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration.Implementation.LEGACY; +import static org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration.Implementation.SEQUENCED; import org.hyperledger.besu.cli.converter.DurationMillisConverter; import org.hyperledger.besu.cli.converter.FractionConverter; @@ -26,15 +27,18 @@ import org.hyperledger.besu.cli.util.CommandLineUtils; import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.plugin.services.TransactionPoolValidatorService; import org.hyperledger.besu.util.number.Fraction; import org.hyperledger.besu.util.number.Percentage; import java.io.File; import java.time.Duration; import java.util.List; +import java.util.Map; import java.util.Set; import picocli.CommandLine; @@ -42,6 +46,7 @@ /** The Transaction pool Cli stable options. */ public class TransactionPoolOptions implements CLIOptions { private static final String TX_POOL_IMPLEMENTATION = "--tx-pool"; + /** Use TX_POOL_NO_LOCAL_PRIORITY instead */ @Deprecated(forRemoval = true) private static final String TX_POOL_DISABLE_LOCALS = "--tx-pool-disable-locals"; @@ -50,12 +55,15 @@ public class TransactionPoolOptions implements CLIOptions txPoolMaxPrioritizedByType = + TransactionPoolConfiguration.DEFAULT_MAX_PRIORITIZED_TRANSACTIONS_BY_TYPE; + @CommandLine.Option( names = {TX_POOL_MAX_FUTURE_BY_SENDER}, paramLabel = MANDATORY_INTEGER_FORMAT_HELP, @@ -167,14 +197,23 @@ static class Layered { "Max number of future pending transactions allowed for a single sender (default: ${DEFAULT-VALUE})", arity = "1") Integer txPoolMaxFutureBySender = TransactionPoolConfiguration.DEFAULT_MAX_FUTURE_BY_SENDER; + + @CommandLine.Option( + names = {TX_POOL_MIN_SCORE}, + paramLabel = "", + description = + "Remove a pending transaction from the txpool if its score is lower than this value." + + "Accepts values between -128 and 127 (default: ${DEFAULT-VALUE})", + arity = "1") + Byte minScore = TransactionPoolConfiguration.DEFAULT_TX_POOL_MIN_SCORE; } @CommandLine.ArgGroup( validate = false, - heading = "@|bold Tx Pool Legacy Implementation Options|@%n") - private final Legacy legacyOptions = new Legacy(); + heading = "@|bold Tx Pool Sequenced Implementation Options|@%n") + private final Sequenced sequencedOptions = new Sequenced(); - static class Legacy { + static class Sequenced { private static final String TX_POOL_RETENTION_HOURS = "--tx-pool-retention-hours"; private static final String TX_POOL_LIMIT_BY_ACCOUNT_PERCENTAGE = "--tx-pool-limit-by-account-percentage"; @@ -251,6 +290,16 @@ public static TransactionPoolOptions create() { return new TransactionPoolOptions(); } + /** + * Set the plugin txpool validator service + * + * @param transactionPoolValidatorService the plugin txpool validator service + */ + public void setPluginTransactionValidatorService( + final TransactionPoolValidatorService transactionPoolValidatorService) { + this.transactionPoolValidatorService = transactionPoolValidatorService; + } + /** * Create Transaction Pool Options from Transaction Pool Configuration. * @@ -263,6 +312,7 @@ public static TransactionPoolOptions fromConfig(final TransactionPoolConfigurati options.saveRestoreEnabled = config.getEnableSaveRestore(); options.noLocalPriority = config.getNoLocalPriority(); options.priceBump = config.getPriceBump(); + options.blobPriceBump = config.getBlobPriceBump(); options.txFeeCap = config.getTxFeeCap(); options.saveFile = config.getSaveFile(); options.strictTxReplayProtectionEnabled = config.getStrictTransactionReplayProtectionEnabled(); @@ -271,11 +321,15 @@ public static TransactionPoolOptions fromConfig(final TransactionPoolConfigurati options.layeredOptions.txPoolLayerMaxCapacity = config.getPendingTransactionsLayerMaxCapacityBytes(); options.layeredOptions.txPoolMaxPrioritized = config.getMaxPrioritizedTransactions(); + options.layeredOptions.txPoolMaxPrioritizedByType = + config.getMaxPrioritizedTransactionsByType(); options.layeredOptions.txPoolMaxFutureBySender = config.getMaxFutureBySender(); - options.legacyOptions.txPoolLimitByAccountPercentage = + options.layeredOptions.minScore = config.getMinScore(); + options.sequencedOptions.txPoolLimitByAccountPercentage = config.getTxPoolLimitByAccountPercentage(); - options.legacyOptions.txPoolMaxSize = config.getTxPoolMaxSize(); - options.legacyOptions.pendingTxRetentionPeriod = config.getPendingTxRetentionPeriod(); + options.sequencedOptions.txPoolMaxSize = config.getTxPoolMaxSize(); + options.sequencedOptions.pendingTxRetentionPeriod = config.getPendingTxRetentionPeriod(); + options.transactionPoolValidatorService = config.getTransactionPoolValidatorService(); options.unstableOptions.txMessageKeepAliveSeconds = config.getUnstable().getTxMessageKeepAliveSeconds(); options.unstableOptions.eth65TrxAnnouncedBufferingPeriod = @@ -295,14 +349,14 @@ public void validate( final CommandLine commandLine, final GenesisConfigOptions genesisConfigOptions) { CommandLineUtils.failIfOptionDoesntMeetRequirement( commandLine, - "Could not use legacy transaction pool options with layered implementation", + "Could not use legacy or sequenced transaction pool options with layered implementation", !txPoolImplementation.equals(LAYERED), - CommandLineUtils.getCLIOptionNames(Legacy.class)); + CommandLineUtils.getCLIOptionNames(Sequenced.class)); CommandLineUtils.failIfOptionDoesntMeetRequirement( commandLine, - "Could not use layered transaction pool options with legacy implementation", - !txPoolImplementation.equals(LEGACY), + "Could not use layered transaction pool options with legacy or sequenced implementation", + !txPoolImplementation.equals(LEGACY) && !txPoolImplementation.equals(SEQUENCED), CommandLineUtils.getCLIOptionNames(Layered.class)); CommandLineUtils.failIfOptionDoesntMeetRequirement( @@ -319,6 +373,7 @@ public TransactionPoolConfiguration toDomainObject() { .enableSaveRestore(saveRestoreEnabled) .noLocalPriority(noLocalPriority) .priceBump(priceBump) + .blobPriceBump(blobPriceBump) .txFeeCap(txFeeCap) .saveFile(saveFile) .strictTransactionReplayProtectionEnabled(strictTxReplayProtectionEnabled) @@ -326,10 +381,13 @@ public TransactionPoolConfiguration toDomainObject() { .minGasPrice(minGasPrice) .pendingTransactionsLayerMaxCapacityBytes(layeredOptions.txPoolLayerMaxCapacity) .maxPrioritizedTransactions(layeredOptions.txPoolMaxPrioritized) + .maxPrioritizedTransactionsByType(layeredOptions.txPoolMaxPrioritizedByType) .maxFutureBySender(layeredOptions.txPoolMaxFutureBySender) - .txPoolLimitByAccountPercentage(legacyOptions.txPoolLimitByAccountPercentage) - .txPoolMaxSize(legacyOptions.txPoolMaxSize) - .pendingTxRetentionPeriod(legacyOptions.pendingTxRetentionPeriod) + .minScore(layeredOptions.minScore) + .txPoolLimitByAccountPercentage(sequencedOptions.txPoolLimitByAccountPercentage) + .txPoolMaxSize(sequencedOptions.txPoolMaxSize) + .pendingTxRetentionPeriod(sequencedOptions.pendingTxRetentionPeriod) + .transactionPoolValidatorService(transactionPoolValidatorService) .unstable( ImmutableTransactionPoolConfiguration.Unstable.builder() .txMessageKeepAliveSeconds(unstableOptions.txMessageKeepAliveSeconds) diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/ApiConfigurationOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/ApiConfigurationOptions.java new file mode 100644 index 00000000000..fbed68de0cd --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/ApiConfigurationOptions.java @@ -0,0 +1,141 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.options.stable; + +import static java.util.Arrays.asList; + +import org.hyperledger.besu.cli.util.CommandLineUtils; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.api.ApiConfiguration; +import org.hyperledger.besu.ethereum.api.ImmutableApiConfiguration; + +import org.slf4j.Logger; +import picocli.CommandLine; + +/** + * Handles configuration options for the API in Besu, including gas price settings, RPC log range, + * and trace filter range. + */ +public class ApiConfigurationOptions { + /** Default constructor. */ + public ApiConfigurationOptions() {} + + @CommandLine.Option( + names = {"--api-gas-price-blocks"}, + description = "Number of blocks to consider for eth_gasPrice (default: ${DEFAULT-VALUE})") + private final Long apiGasPriceBlocks = 100L; + + @CommandLine.Option( + names = {"--api-gas-price-percentile"}, + description = "Percentile value to measure for eth_gasPrice (default: ${DEFAULT-VALUE})") + private final Double apiGasPricePercentile = 50.0; + + @CommandLine.Option( + names = {"--api-gas-price-max"}, + description = "Maximum gas price for eth_gasPrice (default: ${DEFAULT-VALUE})") + private final Long apiGasPriceMax = 500_000_000_000L; + + @CommandLine.Option( + names = {"--api-gas-and-priority-fee-limiting-enabled"}, + hidden = true, + description = + "Set to enable gas price and minimum priority fee limit in eth_getGasPrice and eth_feeHistory (default: ${DEFAULT-VALUE})") + private final Boolean apiGasAndPriorityFeeLimitingEnabled = false; + + @CommandLine.Option( + names = {"--api-gas-and-priority-fee-lower-bound-coefficient"}, + hidden = true, + description = + "Coefficient for setting the lower limit of gas price and minimum priority fee in eth_getGasPrice and eth_feeHistory (default: ${DEFAULT-VALUE})") + private final Long apiGasAndPriorityFeeLowerBoundCoefficient = + ApiConfiguration.DEFAULT_LOWER_BOUND_GAS_AND_PRIORITY_FEE_COEFFICIENT; + + @CommandLine.Option( + names = {"--api-gas-and-priority-fee-upper-bound-coefficient"}, + hidden = true, + description = + "Coefficient for setting the upper limit of gas price and minimum priority fee in eth_getGasPrice and eth_feeHistory (default: ${DEFAULT-VALUE})") + private final Long apiGasAndPriorityFeeUpperBoundCoefficient = + ApiConfiguration.DEFAULT_UPPER_BOUND_GAS_AND_PRIORITY_FEE_COEFFICIENT; + + @CommandLine.Option( + names = {"--rpc-max-logs-range"}, + description = + "Specifies the maximum number of blocks to retrieve logs from via RPC. Must be >=0. 0 specifies no limit (default: ${DEFAULT-VALUE})") + private final Long rpcMaxLogsRange = 5000L; + + @CommandLine.Option( + names = {"--rpc-gas-cap"}, + description = + "Specifies the gasLimit cap for transaction simulation RPC methods. Must be >=0. 0 specifies no limit (default: ${DEFAULT-VALUE})") + private final Long rpcGasCap = 0L; + + @CommandLine.Option( + names = {"--rpc-max-trace-filter-range"}, + description = + "Specifies the maximum number of blocks for the trace_filter method. Must be >=0. 0 specifies no limit (default: ${DEFAULT-VALUE})") + private final Long maxTraceFilterRange = 1000L; + + /** + * Validates the API options. + * + * @param commandLine CommandLine instance + * @param logger Logger instance + */ + public void validate(final CommandLine commandLine, final Logger logger) { + if (apiGasAndPriorityFeeLimitingEnabled) { + if (apiGasAndPriorityFeeLowerBoundCoefficient > apiGasAndPriorityFeeUpperBoundCoefficient) { + throw new CommandLine.ParameterException( + commandLine, + "--api-gas-and-priority-fee-lower-bound-coefficient cannot be greater than the value of --api-gas-and-priority-fee-upper-bound-coefficient"); + } + } + checkApiOptionsDependencies(commandLine, logger); + } + + private void checkApiOptionsDependencies(final CommandLine commandLine, final Logger logger) { + CommandLineUtils.checkOptionDependencies( + logger, + commandLine, + "--api-gas-and-priority-fee-limiting-enabled", + !apiGasAndPriorityFeeLimitingEnabled, + asList( + "--api-gas-and-priority-fee-upper-bound-coefficient", + "--api-gas-and-priority-fee-lower-bound-coefficient")); + } + + /** + * Creates an ApiConfiguration based on the provided options. + * + * @return An ApiConfiguration instance + */ + public ApiConfiguration apiConfiguration() { + var builder = + ImmutableApiConfiguration.builder() + .gasPriceBlocks(apiGasPriceBlocks) + .gasPricePercentile(apiGasPricePercentile) + .gasPriceMax(Wei.of(apiGasPriceMax)) + .maxLogsRange(rpcMaxLogsRange) + .gasCap(rpcGasCap) + .isGasAndPriorityFeeLimitingEnabled(apiGasAndPriorityFeeLimitingEnabled) + .maxTraceFilterRange(maxTraceFilterRange); + if (apiGasAndPriorityFeeLimitingEnabled) { + builder + .lowerBoundGasAndPriorityFeeCoefficient(apiGasAndPriorityFeeLowerBoundCoefficient) + .upperBoundGasAndPriorityFeeCoefficient(apiGasAndPriorityFeeUpperBoundCoefficient); + } + return builder.build(); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/DataStorageOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/DataStorageOptions.java index e0b19735683..3d53a595ec8 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/DataStorageOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/DataStorageOptions.java @@ -11,24 +11,25 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.cli.options.stable; +import static org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration.DEFAULT_BONSAI_LIMIT_TRIE_LOGS_ENABLED; import static org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration.DEFAULT_BONSAI_MAX_LAYERS_TO_LOAD; -import static org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration.Unstable.DEFAULT_BONSAI_TRIE_LOG_PRUNING_ENABLED; -import static org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration.Unstable.DEFAULT_BONSAI_TRIE_LOG_PRUNING_LIMIT; -import static org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration.Unstable.DEFAULT_BONSAI_TRIE_LOG_RETENTION_THRESHOLD; -import static org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration.Unstable.MINIMUM_BONSAI_TRIE_LOG_RETENTION_THRESHOLD; +import static org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration.DEFAULT_BONSAI_TRIE_LOG_PRUNING_WINDOW_SIZE; +import static org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration.DEFAULT_RECEIPT_COMPACTION_ENABLED; +import static org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration.MINIMUM_BONSAI_TRIE_LOG_RETENTION_LIMIT; +import static org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration.Unstable.DEFAULT_BONSAI_CODE_USING_CODE_HASH_ENABLED; +import static org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration.Unstable.DEFAULT_BONSAI_FULL_FLAT_DB_ENABLED; import org.hyperledger.besu.cli.options.CLIOptions; import org.hyperledger.besu.cli.util.CommandLineUtils; import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; import org.hyperledger.besu.ethereum.worldstate.ImmutableDataStorageConfiguration; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import java.util.List; +import java.util.Locale; import org.apache.commons.lang3.StringUtils; import picocli.CommandLine; @@ -39,7 +40,8 @@ public class DataStorageOptions implements CLIOptions private static final String DATA_STORAGE_FORMAT = "--data-storage-format"; - private static final String BONSAI_STORAGE_FORMAT_MAX_LAYERS_TO_LOAD = + /** The maximum number of historical layers to load. */ + public static final String BONSAI_STORAGE_FORMAT_MAX_LAYERS_TO_LOAD = "--bonsai-historical-block-limit"; // Use Bonsai DB @@ -48,41 +50,96 @@ public class DataStorageOptions implements CLIOptions description = "Format to store trie data in. Either FOREST or BONSAI (default: ${DEFAULT-VALUE}).", arity = "1") - private DataStorageFormat dataStorageFormat = DataStorageFormat.FOREST; + private DataStorageFormat dataStorageFormat = DataStorageFormat.BONSAI; @Option( names = {BONSAI_STORAGE_FORMAT_MAX_LAYERS_TO_LOAD, "--bonsai-maximum-back-layers-to-load"}, paramLabel = "", description = - "Limit of historical layers that can be loaded with BONSAI (default: ${DEFAULT-VALUE}).", + "Limit of historical layers that can be loaded with BONSAI (default: ${DEFAULT-VALUE}). When using " + + BONSAI_LIMIT_TRIE_LOGS_ENABLED + + " it will also be used as the number of layers of trie logs to retain.", arity = "1") private Long bonsaiMaxLayersToLoad = DEFAULT_BONSAI_MAX_LAYERS_TO_LOAD; + /** The bonsai limit trie logs enabled option name */ + public static final String BONSAI_LIMIT_TRIE_LOGS_ENABLED = "--bonsai-limit-trie-logs-enabled"; + + /** The bonsai trie logs pruning window size. */ + public static final String BONSAI_TRIE_LOG_PRUNING_WINDOW_SIZE = + "--bonsai-trie-logs-pruning-window-size"; + + // TODO --Xbonsai-limit-trie-logs-enabled and --Xbonsai-trie-log-pruning-enabled are deprecated, + // remove in a future release + @SuppressWarnings("ExperimentalCliOptionMustBeCorrectlyDisplayed") + @CommandLine.Option( + names = { + BONSAI_LIMIT_TRIE_LOGS_ENABLED, + "--Xbonsai-limit-trie-logs-enabled", // deprecated + "--Xbonsai-trie-log-pruning-enabled" // deprecated + }, + fallbackValue = "true", + description = "Limit the number of trie logs that are retained. (default: ${DEFAULT-VALUE})") + private Boolean bonsaiLimitTrieLogsEnabled = DEFAULT_BONSAI_LIMIT_TRIE_LOGS_ENABLED; + + // TODO --Xbonsai-trie-logs-pruning-window-size is deprecated, remove in a future release + @SuppressWarnings("ExperimentalCliOptionMustBeCorrectlyDisplayed") + @CommandLine.Option( + names = { + BONSAI_TRIE_LOG_PRUNING_WINDOW_SIZE, + "--Xbonsai-trie-logs-pruning-window-size" // deprecated + }, + description = + "The max number of blocks to load and prune trie logs for at startup. (default: ${DEFAULT-VALUE})") + private Integer bonsaiTrieLogPruningWindowSize = DEFAULT_BONSAI_TRIE_LOG_PRUNING_WINDOW_SIZE; + + @Option( + names = "--receipt-compaction-enabled", + description = "Enables compact storing of receipts (default: ${DEFAULT-VALUE})", + fallbackValue = "true") + private Boolean receiptCompactionEnabled = DEFAULT_RECEIPT_COMPACTION_ENABLED; + @CommandLine.ArgGroup(validate = false) private final DataStorageOptions.Unstable unstableOptions = new Unstable(); - static class Unstable { + /** Default Constructor. */ + DataStorageOptions() {} + + /** The unstable options for data storage. */ + public static class Unstable { + // TODO: --Xsnapsync-synchronizer-flat-db-healing-enabled is deprecated, remove it in a future + // release @CommandLine.Option( hidden = true, - names = {"--Xbonsai-trie-log-pruning-enabled"}, - description = "Enable trie log pruning. (default: ${DEFAULT-VALUE})") - private boolean bonsaiTrieLogPruningEnabled = DEFAULT_BONSAI_TRIE_LOG_PRUNING_ENABLED; + names = { + "--Xbonsai-full-flat-db-enabled", + "--Xsnapsync-synchronizer-flat-db-healing-enabled" + }, + arity = "1", + description = "Enables bonsai full flat database strategy. (default: ${DEFAULT-VALUE})") + private Boolean bonsaiFullFlatDbEnabled = DEFAULT_BONSAI_FULL_FLAT_DB_ENABLED; @CommandLine.Option( hidden = true, - names = {"--Xbonsai-trie-log-retention-threshold"}, + names = {"--Xbonsai-code-using-code-hash-enabled"}, + arity = "1", description = - "The number of blocks for which to retain trie logs. (default: ${DEFAULT-VALUE})") - private long bonsaiTrieLogRetentionThreshold = DEFAULT_BONSAI_TRIE_LOG_RETENTION_THRESHOLD; + "Enables code storage using code hash instead of by account hash. (default: ${DEFAULT-VALUE})") + private boolean bonsaiCodeUsingCodeHashEnabled = DEFAULT_BONSAI_CODE_USING_CODE_HASH_ENABLED; @CommandLine.Option( hidden = true, - names = {"--Xbonsai-trie-log-pruning-limit"}, + names = {"--Xbonsai-parallel-tx-processing-enabled"}, + arity = "1", description = - "The max number of blocks to load and prune trie logs for at startup. (default: ${DEFAULT-VALUE})") - private int bonsaiTrieLogPruningLimit = DEFAULT_BONSAI_TRIE_LOG_PRUNING_LIMIT; + "Enables parallelization of transactions to optimize processing speed by concurrently loading and executing necessary data in advance. (default: ${DEFAULT-VALUE})") + private Boolean isParallelTxProcessingEnabled = false; + + /** Default Constructor. */ + Unstable() {} } + /** * Create data storage options. * @@ -98,35 +155,63 @@ public static DataStorageOptions create() { * @param commandLine the full commandLine to check all the options specified by the user */ public void validate(final CommandLine commandLine) { - if (unstableOptions.bonsaiTrieLogPruningEnabled) { - if (unstableOptions.bonsaiTrieLogRetentionThreshold - < MINIMUM_BONSAI_TRIE_LOG_RETENTION_THRESHOLD) { - throw new CommandLine.ParameterException( - commandLine, - String.format( - "--Xbonsai-trie-log-retention-threshold minimum value is %d", - MINIMUM_BONSAI_TRIE_LOG_RETENTION_THRESHOLD)); + if (DataStorageFormat.BONSAI == dataStorageFormat) { + if (bonsaiLimitTrieLogsEnabled) { + if (bonsaiMaxLayersToLoad < MINIMUM_BONSAI_TRIE_LOG_RETENTION_LIMIT) { + throw new CommandLine.ParameterException( + commandLine, + String.format( + BONSAI_STORAGE_FORMAT_MAX_LAYERS_TO_LOAD + " minimum value is %d", + MINIMUM_BONSAI_TRIE_LOG_RETENTION_LIMIT)); + } + if (bonsaiTrieLogPruningWindowSize <= 0) { + throw new CommandLine.ParameterException( + commandLine, + String.format( + BONSAI_TRIE_LOG_PRUNING_WINDOW_SIZE + "=%d must be greater than 0", + bonsaiTrieLogPruningWindowSize)); + } + if (bonsaiTrieLogPruningWindowSize <= bonsaiMaxLayersToLoad) { + throw new CommandLine.ParameterException( + commandLine, + String.format( + BONSAI_TRIE_LOG_PRUNING_WINDOW_SIZE + + "=%d must be greater than " + + BONSAI_STORAGE_FORMAT_MAX_LAYERS_TO_LOAD + + "=%d", + bonsaiTrieLogPruningWindowSize, + bonsaiMaxLayersToLoad)); + } } - if (unstableOptions.bonsaiTrieLogPruningLimit <= 0) { + } else { + if (unstableOptions.isParallelTxProcessingEnabled) { throw new CommandLine.ParameterException( commandLine, - String.format( - "--Xbonsai-trie-log-pruning-limit=%d must be greater than 0", - unstableOptions.bonsaiTrieLogPruningLimit)); + "Transaction parallelization is not supported unless operating in a 'diffbased' mode, such as Bonsai."); } } } - static DataStorageOptions fromConfig(final DataStorageConfiguration domainObject) { + /** + * Converts to options from the configuration + * + * @param domainObject to be reversed + * @return the options that correspond to the configuration + */ + public static DataStorageOptions fromConfig(final DataStorageConfiguration domainObject) { final DataStorageOptions dataStorageOptions = DataStorageOptions.create(); dataStorageOptions.dataStorageFormat = domainObject.getDataStorageFormat(); dataStorageOptions.bonsaiMaxLayersToLoad = domainObject.getBonsaiMaxLayersToLoad(); - dataStorageOptions.unstableOptions.bonsaiTrieLogPruningEnabled = - domainObject.getUnstable().getBonsaiTrieLogPruningEnabled(); - dataStorageOptions.unstableOptions.bonsaiTrieLogRetentionThreshold = - domainObject.getUnstable().getBonsaiTrieLogRetentionThreshold(); - dataStorageOptions.unstableOptions.bonsaiTrieLogPruningLimit = - domainObject.getUnstable().getBonsaiTrieLogPruningLimit(); + dataStorageOptions.receiptCompactionEnabled = domainObject.getReceiptCompactionEnabled(); + dataStorageOptions.bonsaiLimitTrieLogsEnabled = domainObject.getBonsaiLimitTrieLogsEnabled(); + dataStorageOptions.bonsaiTrieLogPruningWindowSize = + domainObject.getBonsaiTrieLogPruningWindowSize(); + dataStorageOptions.unstableOptions.bonsaiFullFlatDbEnabled = + domainObject.getUnstable().getBonsaiFullFlatDbEnabled(); + dataStorageOptions.unstableOptions.bonsaiCodeUsingCodeHashEnabled = + domainObject.getUnstable().getBonsaiCodeStoredByCodeHashEnabled(); + dataStorageOptions.unstableOptions.isParallelTxProcessingEnabled = + domainObject.getUnstable().isParallelTxProcessingEnabled(); return dataStorageOptions; } @@ -136,11 +221,14 @@ public DataStorageConfiguration toDomainObject() { return ImmutableDataStorageConfiguration.builder() .dataStorageFormat(dataStorageFormat) .bonsaiMaxLayersToLoad(bonsaiMaxLayersToLoad) + .receiptCompactionEnabled(receiptCompactionEnabled) + .bonsaiLimitTrieLogsEnabled(bonsaiLimitTrieLogsEnabled) + .bonsaiTrieLogPruningWindowSize(bonsaiTrieLogPruningWindowSize) .unstable( ImmutableDataStorageConfiguration.Unstable.builder() - .bonsaiTrieLogPruningEnabled(unstableOptions.bonsaiTrieLogPruningEnabled) - .bonsaiTrieLogRetentionThreshold(unstableOptions.bonsaiTrieLogRetentionThreshold) - .bonsaiTrieLogPruningLimit(unstableOptions.bonsaiTrieLogPruningLimit) + .bonsaiFullFlatDbEnabled(unstableOptions.bonsaiFullFlatDbEnabled) + .bonsaiCodeStoredByCodeHashEnabled(unstableOptions.bonsaiCodeUsingCodeHashEnabled) + .isParallelTxProcessingEnabled(unstableOptions.isParallelTxProcessingEnabled) .build()) .build(); } @@ -156,6 +244,6 @@ public List getCLIOptions() { * @return the normalized string */ public String normalizeDataStorageFormat() { - return StringUtils.capitalize(dataStorageFormat.toString().toLowerCase()); + return StringUtils.capitalize(dataStorageFormat.toString().toLowerCase(Locale.ROOT)); } } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/GraphQlOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/GraphQlOptions.java new file mode 100644 index 00000000000..77cc1703052 --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/GraphQlOptions.java @@ -0,0 +1,115 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.options.stable; + +import static java.util.Arrays.asList; +import static org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration.DEFAULT_GRAPHQL_HTTP_PORT; + +import org.hyperledger.besu.cli.DefaultCommandValues; +import org.hyperledger.besu.cli.custom.CorsAllowedOriginsProperty; +import org.hyperledger.besu.cli.util.CommandLineUtils; +import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration; + +import java.util.List; + +import com.google.common.base.Strings; +import org.slf4j.Logger; +import picocli.CommandLine; + +/** Handles configuration options for the GraphQL HTTP service in Besu. */ +public class GraphQlOptions { + @CommandLine.Option( + names = {"--graphql-http-enabled"}, + description = "Set to start the GraphQL HTTP service (default: ${DEFAULT-VALUE})") + private final Boolean isGraphQLHttpEnabled = false; + + @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. + @CommandLine.Option( + names = {"--graphql-http-host"}, + paramLabel = DefaultCommandValues.MANDATORY_HOST_FORMAT_HELP, + description = "Host for GraphQL HTTP to listen on (default: ${DEFAULT-VALUE})", + arity = "1") + private String graphQLHttpHost; + + @CommandLine.Option( + names = {"--graphql-http-port"}, + paramLabel = DefaultCommandValues.MANDATORY_PORT_FORMAT_HELP, + description = "Port for GraphQL HTTP to listen on (default: ${DEFAULT-VALUE})", + arity = "1") + private final Integer graphQLHttpPort = DEFAULT_GRAPHQL_HTTP_PORT; + + @CommandLine.Option( + names = {"--graphql-http-cors-origins"}, + description = "Comma separated origin domain URLs for CORS validation (default: none)") + private final CorsAllowedOriginsProperty graphQLHttpCorsAllowedOrigins = + new CorsAllowedOriginsProperty(); + + /** Default constructor */ + public GraphQlOptions() {} + + /** + * Validates the GraphQL HTTP options. + * + * @param logger Logger instance + * @param commandLine CommandLine instance + */ + public void validate(final Logger logger, final CommandLine commandLine) { + CommandLineUtils.checkOptionDependencies( + logger, + commandLine, + "--graphql-http-enabled", + !isGraphQLHttpEnabled, + asList("--graphql-http-cors-origins", "--graphql-http-host", "--graphql-http-port")); + } + + /** + * Creates a GraphQLConfiguration based on the provided options. + * + * @param hostsAllowlist List of hosts allowed + * @param defaultHostAddress Default host address + * @param timoutSec Timeout in seconds + * @return A GraphQLConfiguration instance + */ + public GraphQLConfiguration graphQLConfiguration( + final List hostsAllowlist, final String defaultHostAddress, final Long timoutSec) { + final GraphQLConfiguration graphQLConfiguration = GraphQLConfiguration.createDefault(); + graphQLConfiguration.setEnabled(isGraphQLHttpEnabled); + graphQLConfiguration.setHost( + Strings.isNullOrEmpty(graphQLHttpHost) ? defaultHostAddress : graphQLHttpHost); + graphQLConfiguration.setPort(graphQLHttpPort); + graphQLConfiguration.setHostsAllowlist(hostsAllowlist); + graphQLConfiguration.setCorsAllowedDomains(graphQLHttpCorsAllowedOrigins); + graphQLConfiguration.setHttpTimeoutSec(timoutSec); + return graphQLConfiguration; + } + + /** + * Checks if GraphQL over HTTP is enabled. + * + * @return true if enabled, false otherwise + */ + public Boolean isGraphQLHttpEnabled() { + return isGraphQLHttpEnabled; + } + + /** + * Returns the port for GraphQL over HTTP. + * + * @return The port number + */ + public Integer getGraphQLHttpPort() { + return graphQLHttpPort; + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/JsonRpcHttpOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/JsonRpcHttpOptions.java new file mode 100644 index 00000000000..026b83b5537 --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/JsonRpcHttpOptions.java @@ -0,0 +1,504 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.options.stable; + +import static java.util.Arrays.asList; +import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration.DEFAULT_JSON_RPC_PORT; +import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration.DEFAULT_PRETTY_JSON_ENABLED; +import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.DEFAULT_RPC_APIS; +import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.VALID_APIS; + +import org.hyperledger.besu.cli.DefaultCommandValues; +import org.hyperledger.besu.cli.custom.CorsAllowedOriginsProperty; +import org.hyperledger.besu.cli.custom.RpcAuthFileValidator; +import org.hyperledger.besu.cli.util.CommandLineUtils; +import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration; +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.authentication.JwtAlgorithm; +import org.hyperledger.besu.ethereum.api.tls.FileBasedPasswordProvider; +import org.hyperledger.besu.ethereum.api.tls.TlsClientAuthConfiguration; +import org.hyperledger.besu.ethereum.api.tls.TlsConfiguration; + +import java.io.File; +import java.nio.file.Path; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; + +import com.google.common.base.Strings; +import org.slf4j.Logger; +import picocli.CommandLine; + +/** + * Handles configuration options for the JSON-RPC HTTP service, including validation and creation of + * a JSON-RPC configuration. + */ +public class JsonRpcHttpOptions { + @CommandLine.Option( + names = {"--rpc-http-enabled"}, + description = "Set to start the JSON-RPC HTTP service (default: ${DEFAULT-VALUE})") + private final Boolean isRpcHttpEnabled = false; + + @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. + @CommandLine.Option( + names = {"--rpc-http-host"}, + paramLabel = DefaultCommandValues.MANDATORY_HOST_FORMAT_HELP, + description = "Host for JSON-RPC HTTP to listen on (default: ${DEFAULT-VALUE})", + arity = "1") + private String rpcHttpHost; + + @CommandLine.Option( + names = {"--rpc-http-port"}, + paramLabel = DefaultCommandValues.MANDATORY_PORT_FORMAT_HELP, + description = "Port for JSON-RPC HTTP to listen on (default: ${DEFAULT-VALUE})", + arity = "1") + private final Integer rpcHttpPort = DEFAULT_JSON_RPC_PORT; + + @CommandLine.Option( + names = {"--rpc-http-max-active-connections"}, + description = + "Maximum number of HTTP connections allowed for JSON-RPC (default: ${DEFAULT-VALUE}). Once this limit is reached, incoming connections will be rejected.", + arity = "1") + private final Integer rpcHttpMaxConnections = DefaultCommandValues.DEFAULT_HTTP_MAX_CONNECTIONS; + + // A list of origins URLs that are accepted by the JsonRpcHttpServer (CORS) + @CommandLine.Option( + names = {"--rpc-http-cors-origins"}, + description = "Comma separated origin domain URLs for CORS validation (default: none)") + private final CorsAllowedOriginsProperty rpcHttpCorsAllowedOrigins = + new CorsAllowedOriginsProperty(); + + @CommandLine.Option( + names = {"--rpc-http-api", "--rpc-http-apis"}, + paramLabel = "", + split = " {0,1}, {0,1}", + arity = "1..*", + description = + "Comma separated list of APIs to enable on JSON-RPC HTTP service (default: ${DEFAULT-VALUE})") + private final List rpcHttpApis = DEFAULT_RPC_APIS; + + @CommandLine.Option( + names = {"--rpc-http-api-method-no-auth", "--rpc-http-api-methods-no-auth"}, + paramLabel = "", + split = " {0,1}, {0,1}", + arity = "1..*", + description = + "Comma separated list of API methods to exclude from RPC authentication services, RPC HTTP authentication must be enabled") + private final List rpcHttpApiMethodsNoAuth = new ArrayList(); + + @CommandLine.Option( + names = {"--rpc-http-authentication-enabled"}, + description = + "Require authentication for the JSON-RPC HTTP service (default: ${DEFAULT-VALUE})") + private final Boolean isRpcHttpAuthenticationEnabled = false; + + @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. + @CommandLine.Option( + names = {"--rpc-http-authentication-credentials-file"}, + paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP, + description = + "Storage file for JSON-RPC HTTP authentication credentials (default: ${DEFAULT-VALUE})", + arity = "1") + private String rpcHttpAuthenticationCredentialsFile = null; + + @CommandLine.Option( + names = {"--rpc-http-authentication-jwt-public-key-file"}, + paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP, + description = "JWT public key file for JSON-RPC HTTP authentication", + arity = "1") + private final File rpcHttpAuthenticationPublicKeyFile = null; + + @CommandLine.Option( + names = {"--rpc-http-authentication-jwt-algorithm"}, + description = + "Encryption algorithm used for HTTP JWT public key. Possible values are ${COMPLETION-CANDIDATES}" + + " (default: ${DEFAULT-VALUE})", + arity = "1") + private final JwtAlgorithm rpcHttpAuthenticationAlgorithm = + DefaultCommandValues.DEFAULT_JWT_ALGORITHM; + + @CommandLine.Option( + names = {"--rpc-http-tls-enabled"}, + description = "Enable TLS for the JSON-RPC HTTP service (default: ${DEFAULT-VALUE})") + private final Boolean isRpcHttpTlsEnabled = false; + + @CommandLine.Option( + names = {"--rpc-http-tls-keystore-file"}, + paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP, + description = + "Keystore (PKCS#12) containing key/certificate for the JSON-RPC HTTP service. Required if TLS is enabled.") + private final Path rpcHttpTlsKeyStoreFile = null; + + @CommandLine.Option( + names = {"--rpc-http-tls-keystore-password-file"}, + paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP, + description = + "File containing password to unlock keystore for the JSON-RPC HTTP service. Required if TLS is enabled.") + private final Path rpcHttpTlsKeyStorePasswordFile = null; + + @CommandLine.Option( + names = {"--rpc-http-tls-client-auth-enabled"}, + description = + "Enable TLS client authentication for the JSON-RPC HTTP service (default: ${DEFAULT-VALUE})") + private final Boolean isRpcHttpTlsClientAuthEnabled = false; + + @CommandLine.Option( + names = {"--rpc-http-tls-known-clients-file"}, + paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP, + description = + "Path to file containing clients certificate common name and fingerprint for client authentication") + private final Path rpcHttpTlsKnownClientsFile = null; + + @CommandLine.Option( + names = {"--rpc-http-tls-ca-clients-enabled"}, + description = + "Enable to accept clients certificate signed by a valid CA for client authentication (default: ${DEFAULT-VALUE})") + private final Boolean isRpcHttpTlsCAClientsEnabled = false; + + @CommandLine.Option( + names = {"--rpc-http-tls-protocol", "--rpc-http-tls-protocols"}, + description = "Comma separated list of TLS protocols to support (default: ${DEFAULT-VALUE})", + split = ",", + arity = "1..*") + private final List rpcHttpTlsProtocols = + new ArrayList<>(DefaultCommandValues.DEFAULT_TLS_PROTOCOLS); + + @CommandLine.Option( + names = {"--rpc-http-tls-cipher-suite", "--rpc-http-tls-cipher-suites"}, + description = "Comma separated list of TLS cipher suites to support", + split = ",", + arity = "1..*") + private final List rpcHttpTlsCipherSuites = new ArrayList<>(); + + @CommandLine.Option( + names = {"--rpc-http-max-batch-size"}, + paramLabel = DefaultCommandValues.MANDATORY_INTEGER_FORMAT_HELP, + description = + "Specifies the maximum number of requests in a single RPC batch request via RPC. -1 specifies no limit (default: ${DEFAULT-VALUE})") + private final Integer rpcHttpMaxBatchSize = DefaultCommandValues.DEFAULT_HTTP_MAX_BATCH_SIZE; + + @CommandLine.Option( + names = {"--rpc-http-max-request-content-length"}, + paramLabel = DefaultCommandValues.MANDATORY_LONG_FORMAT_HELP, + description = "Specifies the maximum request content length. (default: ${DEFAULT-VALUE})") + private final Long rpcHttpMaxRequestContentLength = + DefaultCommandValues.DEFAULT_MAX_REQUEST_CONTENT_LENGTH; + + @CommandLine.Option( + names = {"--json-pretty-print-enabled"}, + description = "Enable JSON pretty print format (default: ${DEFAULT-VALUE})") + private final Boolean prettyJsonEnabled = DEFAULT_PRETTY_JSON_ENABLED; + + /** Default constructor */ + public JsonRpcHttpOptions() {} + + /** + * Validates the Rpc Http options. + * + * @param logger Logger instance + * @param commandLine CommandLine instance + * @param configuredApis Predicate for configured APIs + */ + public void validate( + final Logger logger, final CommandLine commandLine, final Predicate configuredApis) { + + if (!rpcHttpApis.stream().allMatch(configuredApis)) { + final List invalidHttpApis = new ArrayList<>(rpcHttpApis); + invalidHttpApis.removeAll(VALID_APIS); + throw new CommandLine.ParameterException( + commandLine, + "Invalid value for option '--rpc-http-api': invalid entries found " + invalidHttpApis); + } + + final boolean validHttpApiMethods = + rpcHttpApiMethodsNoAuth.stream().allMatch(RpcMethod::rpcMethodExists); + + if (!validHttpApiMethods) { + throw new CommandLine.ParameterException( + commandLine, + "Invalid value for option '--rpc-http-api-methods-no-auth', options must be valid RPC methods"); + } + + if (isRpcHttpAuthenticationEnabled) { + CommandLineUtils.checkOptionDependencies( + logger, + commandLine, + "--rpc-http-authentication-public-key-file", + rpcHttpAuthenticationPublicKeyFile == null, + List.of("--rpc-http-authentication-jwt-algorithm")); + } + + if (isRpcHttpAuthenticationEnabled + && rpcHttpAuthenticationCredentialsFile(commandLine) == null + && rpcHttpAuthenticationPublicKeyFile == null) { + throw new CommandLine.ParameterException( + commandLine, + "Unable to authenticate JSON-RPC HTTP endpoint without a supplied credentials file or authentication public key file"); + } + + checkDependencies(logger, commandLine); + + if (isRpcTlsConfigurationRequired()) { + validateTls(commandLine); + } + } + + /** + * Creates a JsonRpcConfiguration based on the provided options. + * + * @param hostsAllowlist List of hosts allowed + * @param defaultHostAddress Default host address + * @param timoutSec timeout in seconds + * @return A JsonRpcConfiguration instance + */ + public JsonRpcConfiguration jsonRpcConfiguration( + final List hostsAllowlist, final String defaultHostAddress, final Long timoutSec) { + + final JsonRpcConfiguration jsonRpcConfiguration = JsonRpcConfiguration.createDefault(); + jsonRpcConfiguration.setEnabled(isRpcHttpEnabled); + jsonRpcConfiguration.setHost( + Strings.isNullOrEmpty(rpcHttpHost) ? defaultHostAddress : rpcHttpHost); + jsonRpcConfiguration.setPort(rpcHttpPort); + jsonRpcConfiguration.setMaxActiveConnections(rpcHttpMaxConnections); + jsonRpcConfiguration.setCorsAllowedDomains(rpcHttpCorsAllowedOrigins); + jsonRpcConfiguration.setRpcApis(rpcHttpApis.stream().distinct().collect(Collectors.toList())); + jsonRpcConfiguration.setNoAuthRpcApis( + rpcHttpApiMethodsNoAuth.stream().distinct().collect(Collectors.toList())); + jsonRpcConfiguration.setHostsAllowlist(hostsAllowlist); + jsonRpcConfiguration.setAuthenticationEnabled(isRpcHttpAuthenticationEnabled); + jsonRpcConfiguration.setAuthenticationCredentialsFile(rpcHttpAuthenticationCredentialsFile); + jsonRpcConfiguration.setAuthenticationPublicKeyFile(rpcHttpAuthenticationPublicKeyFile); + jsonRpcConfiguration.setAuthenticationAlgorithm(rpcHttpAuthenticationAlgorithm); + jsonRpcConfiguration.setTlsConfiguration(rpcHttpTlsConfiguration()); + jsonRpcConfiguration.setHttpTimeoutSec(timoutSec); + jsonRpcConfiguration.setMaxBatchSize(rpcHttpMaxBatchSize); + jsonRpcConfiguration.setMaxRequestContentLength(rpcHttpMaxRequestContentLength); + jsonRpcConfiguration.setPrettyJsonEnabled(prettyJsonEnabled); + return jsonRpcConfiguration; + } + + /** + * Checks dependencies between options. + * + * @param logger Logger instance + * @param commandLine CommandLine instance + */ + public void checkDependencies(final Logger logger, final CommandLine commandLine) { + checkRpcTlsClientAuthOptionsDependencies(logger, commandLine); + checkRpcTlsOptionsDependencies(logger, commandLine); + checkRpcHttpOptionsDependencies(logger, commandLine); + } + + private void checkRpcTlsClientAuthOptionsDependencies( + final Logger logger, final CommandLine commandLine) { + CommandLineUtils.checkOptionDependencies( + logger, + commandLine, + "--rpc-http-tls-client-auth-enabled", + !isRpcHttpTlsClientAuthEnabled, + asList("--rpc-http-tls-known-clients-file", "--rpc-http-tls-ca-clients-enabled")); + } + + private void checkRpcTlsOptionsDependencies(final Logger logger, final CommandLine commandLine) { + CommandLineUtils.checkOptionDependencies( + logger, + commandLine, + "--rpc-http-tls-enabled", + !isRpcHttpTlsEnabled, + asList( + "--rpc-http-tls-keystore-file", + "--rpc-http-tls-keystore-password-file", + "--rpc-http-tls-client-auth-enabled", + "--rpc-http-tls-known-clients-file", + "--rpc-http-tls-ca-clients-enabled", + "--rpc-http-tls-protocols", + "--rpc-http-tls-cipher-suite", + "--rpc-http-tls-cipher-suites")); + } + + private void checkRpcHttpOptionsDependencies(final Logger logger, final CommandLine commandLine) { + CommandLineUtils.checkOptionDependencies( + logger, + commandLine, + "--rpc-http-enabled", + !isRpcHttpEnabled, + asList( + "--rpc-http-api", + "--rpc-http-apis", + "--rpc-http-api-method-no-auth", + "--rpc-http-api-methods-no-auth", + "--rpc-http-cors-origins", + "--rpc-http-host", + "--rpc-http-port", + "--rpc-http-max-active-connections", + "--rpc-http-authentication-enabled", + "--rpc-http-authentication-credentials-file", + "--rpc-http-authentication-public-key-file", + "--rpc-http-tls-enabled", + "--rpc-http-tls-keystore-file", + "--rpc-http-tls-keystore-password-file", + "--rpc-http-tls-client-auth-enabled", + "--rpc-http-tls-known-clients-file", + "--rpc-http-tls-ca-clients-enabled", + "--rpc-http-authentication-jwt-algorithm", + "--rpc-http-tls-protocols", + "--rpc-http-tls-cipher-suite", + "--rpc-http-tls-cipher-suites")); + } + + private void validateTls(final CommandLine commandLine) { + if (rpcHttpTlsKeyStoreFile == null) { + throw new CommandLine.ParameterException( + commandLine, "Keystore file is required when TLS is enabled for JSON-RPC HTTP endpoint"); + } + + if (rpcHttpTlsKeyStorePasswordFile == null) { + throw new CommandLine.ParameterException( + commandLine, + "File containing password to unlock keystore is required when TLS is enabled for JSON-RPC HTTP endpoint"); + } + + if (isRpcHttpTlsClientAuthEnabled + && !isRpcHttpTlsCAClientsEnabled + && rpcHttpTlsKnownClientsFile == null) { + throw new CommandLine.ParameterException( + commandLine, + "Known-clients file must be specified or CA clients must be enabled when TLS client authentication is enabled for JSON-RPC HTTP endpoint"); + } + + rpcHttpTlsProtocols.retainAll(getJDKEnabledProtocols()); + if (rpcHttpTlsProtocols.isEmpty()) { + throw new CommandLine.ParameterException( + commandLine, + "No valid TLS protocols specified (the following protocols are enabled: " + + getJDKEnabledProtocols() + + ")"); + } + + for (final String cipherSuite : rpcHttpTlsCipherSuites) { + if (!getJDKEnabledCipherSuites().contains(cipherSuite)) { + throw new CommandLine.ParameterException( + commandLine, "Invalid TLS cipher suite specified " + cipherSuite); + } + } + } + + private Optional rpcHttpTlsConfiguration() { + if (!isRpcTlsConfigurationRequired()) { + return Optional.empty(); + } + + rpcHttpTlsCipherSuites.retainAll(getJDKEnabledCipherSuites()); + + return Optional.of( + TlsConfiguration.Builder.aTlsConfiguration() + .withKeyStorePath(rpcHttpTlsKeyStoreFile) + .withKeyStorePasswordSupplier( + new FileBasedPasswordProvider(rpcHttpTlsKeyStorePasswordFile)) + .withClientAuthConfiguration(rpcHttpTlsClientAuthConfiguration()) + .withSecureTransportProtocols(rpcHttpTlsProtocols) + .withCipherSuites(rpcHttpTlsCipherSuites) + .build()); + } + + private boolean isRpcTlsConfigurationRequired() { + return isRpcHttpEnabled && isRpcHttpTlsEnabled; + } + + private TlsClientAuthConfiguration rpcHttpTlsClientAuthConfiguration() { + if (isRpcHttpTlsClientAuthEnabled) { + return TlsClientAuthConfiguration.Builder.aTlsClientAuthConfiguration() + .withKnownClientsFile(rpcHttpTlsKnownClientsFile) + .withCaClientsEnabled(isRpcHttpTlsCAClientsEnabled) + .build(); + } + + return null; + } + + private static List getJDKEnabledCipherSuites() { + try { + final SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, null, null); + final SSLEngine engine = context.createSSLEngine(); + return Arrays.asList(engine.getEnabledCipherSuites()); + } catch (final KeyManagementException | NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + private static List getJDKEnabledProtocols() { + try { + final SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, null, null); + final SSLEngine engine = context.createSSLEngine(); + return Arrays.asList(engine.getEnabledProtocols()); + } catch (final KeyManagementException | NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + private String rpcHttpAuthenticationCredentialsFile(final CommandLine commandLine) { + final String filename = rpcHttpAuthenticationCredentialsFile; + + if (filename != null) { + RpcAuthFileValidator.validate(commandLine, filename, "HTTP"); + } + return filename; + } + + /** + * Returns the list of APIs enabled for RPC over HTTP. + * + * @return A list of APIs + */ + public List getRpcHttpApis() { + return rpcHttpApis; + } + + /** + * Returns the host for RPC over HTTP. + * + * @return The port number + */ + public String getRpcHttpHost() { + return rpcHttpHost; + } + + /** + * Returns the port for RPC over HTTP. + * + * @return The port number + */ + public Integer getRpcHttpPort() { + return rpcHttpPort; + } + + /** + * Checks if RPC over HTTP is enabled. + * + * @return true if enabled, false otherwise + */ + public Boolean isRpcHttpEnabled() { + return isRpcHttpEnabled; + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/LoggingLevelOption.java b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/LoggingLevelOption.java index ad119ea61a0..39b2a2482b8 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/LoggingLevelOption.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/LoggingLevelOption.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.cli.options.stable; +import java.util.Locale; import java.util.Set; import picocli.CommandLine; @@ -22,6 +23,8 @@ /** The Logging level CLI option. */ public class LoggingLevelOption { + /** Default Constructor. */ + LoggingLevelOption() {} /** * Create logging level option. @@ -34,6 +37,7 @@ public static LoggingLevelOption create() { private static final Set ACCEPTED_VALUES = Set.of("OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE", "ALL"); + /** The Picocli CommandSpec. Visible for testing. Injected by Picocli framework at runtime. */ @Spec CommandSpec spec; @@ -52,8 +56,8 @@ public void setLogLevel(final String logLevel) { if ("FATAL".equalsIgnoreCase(logLevel)) { System.out.println("FATAL level is deprecated"); this.logLevel = "ERROR"; - } else if (ACCEPTED_VALUES.contains(logLevel.toUpperCase())) { - this.logLevel = logLevel.toUpperCase(); + } else if (ACCEPTED_VALUES.contains(logLevel.toUpperCase(Locale.ROOT))) { + this.logLevel = logLevel.toUpperCase(Locale.ROOT); } else { throw new CommandLine.ParameterException( spec.commandLine(), "Unknown logging value: " + logLevel); diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/MetricsOptionGroup.java b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/MetricsOptionGroup.java new file mode 100644 index 00000000000..add2bf16553 --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/MetricsOptionGroup.java @@ -0,0 +1,197 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.options.stable; + +import static org.hyperledger.besu.cli.DefaultCommandValues.MANDATORY_HOST_FORMAT_HELP; +import static org.hyperledger.besu.cli.DefaultCommandValues.MANDATORY_INTEGER_FORMAT_HELP; +import static org.hyperledger.besu.cli.DefaultCommandValues.MANDATORY_PORT_FORMAT_HELP; +import static org.hyperledger.besu.metrics.BesuMetricCategory.DEFAULT_METRIC_CATEGORIES; +import static org.hyperledger.besu.metrics.MetricsProtocol.PROMETHEUS; +import static org.hyperledger.besu.metrics.prometheus.MetricsConfiguration.DEFAULT_METRICS_PORT; +import static org.hyperledger.besu.metrics.prometheus.MetricsConfiguration.DEFAULT_METRICS_PUSH_PORT; + +import org.hyperledger.besu.metrics.MetricsProtocol; +import org.hyperledger.besu.plugin.services.metrics.MetricCategory; + +import java.util.Set; + +import picocli.CommandLine; + +/** Command line options for configuring metrics. */ +public class MetricsOptionGroup { + @CommandLine.Option( + names = {"--metrics-enabled"}, + description = "Set to start the metrics exporter (default: ${DEFAULT-VALUE})") + private final Boolean isMetricsEnabled = false; + + @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. + @CommandLine.Option( + names = {"--metrics-protocol"}, + description = + "Metrics protocol, one of PROMETHEUS, OPENTELEMETRY or NONE. (default: ${DEFAULT-VALUE})") + private MetricsProtocol metricsProtocol = PROMETHEUS; + + @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. + @CommandLine.Option( + names = {"--metrics-host"}, + paramLabel = MANDATORY_HOST_FORMAT_HELP, + description = "Host for the metrics exporter to listen on (default: ${DEFAULT-VALUE})", + arity = "1") + private String metricsHost; + + @CommandLine.Option( + names = {"--metrics-port"}, + paramLabel = MANDATORY_PORT_FORMAT_HELP, + description = "Port for the metrics exporter to listen on (default: ${DEFAULT-VALUE})", + arity = "1") + private final Integer metricsPort = DEFAULT_METRICS_PORT; + + @CommandLine.Option( + names = {"--metrics-category", "--metrics-categories"}, + paramLabel = "", + split = ",", + arity = "1..*", + description = + "Comma separated list of categories to track metrics for (default: ${DEFAULT-VALUE})") + private final Set metricCategories = DEFAULT_METRIC_CATEGORIES; + + @CommandLine.Option( + names = {"--metrics-push-enabled"}, + description = "Enable the metrics push gateway integration (default: ${DEFAULT-VALUE})") + private final Boolean isMetricsPushEnabled = false; + + @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. + @CommandLine.Option( + names = {"--metrics-push-host"}, + paramLabel = MANDATORY_HOST_FORMAT_HELP, + description = "Host of the Prometheus Push Gateway for push mode (default: ${DEFAULT-VALUE})", + arity = "1") + private String metricsPushHost; + + @CommandLine.Option( + names = {"--metrics-push-port"}, + paramLabel = MANDATORY_PORT_FORMAT_HELP, + description = "Port of the Prometheus Push Gateway for push mode (default: ${DEFAULT-VALUE})", + arity = "1") + private final Integer metricsPushPort = DEFAULT_METRICS_PUSH_PORT; + + @CommandLine.Option( + names = {"--metrics-push-interval"}, + paramLabel = MANDATORY_INTEGER_FORMAT_HELP, + description = + "Interval in seconds to push metrics when in push mode (default: ${DEFAULT-VALUE})", + arity = "1") + private final Integer metricsPushInterval = 15; + + @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. + @CommandLine.Option( + names = {"--metrics-push-prometheus-job"}, + description = "Job name to use when in push mode (default: ${DEFAULT-VALUE})", + arity = "1") + private String metricsPrometheusJob = "besu-client"; + + /** Returns a newly created {@link MetricsOptionGroup} with default values. */ + public MetricsOptionGroup() {} + + /** + * Returns whether metrics are enabled. + * + * @return true if metrics are enabled, otherwise false + */ + public Boolean getMetricsEnabled() { + return isMetricsEnabled; + } + + /** + * Returns the metrics protocol. + * + * @return the metrics protocol + */ + public MetricsProtocol getMetricsProtocol() { + return metricsProtocol; + } + + /** + * Returns the metrics host. + * + * @return the metrics host + */ + public String getMetricsHost() { + return metricsHost; + } + + /** + * Returns the metrics port. + * + * @return the metrics port + */ + public Integer getMetricsPort() { + return metricsPort; + } + + /** + * Returns the metric categories. + * + * @return the metric categories + */ + public Set getMetricCategories() { + return metricCategories; + } + + /** + * Returns whether metrics push is enabled. + * + * @return true if metrics push is enabled, otherwise false + */ + public Boolean getMetricsPushEnabled() { + return isMetricsPushEnabled; + } + + /** + * Returns the metrics push host. + * + * @return the metrics push host + */ + public String getMetricsPushHost() { + return metricsPushHost; + } + + /** + * Returns the metrics push port. + * + * @return the metrics push port + */ + public Integer getMetricsPushPort() { + return metricsPushPort; + } + + /** + * Returns the metrics push interval. + * + * @return the metrics push interval + */ + public Integer getMetricsPushInterval() { + return metricsPushInterval; + } + + /** + * Returns the metrics prometheus job. + * + * @return the metrics prometheus job + */ + public String getMetricsPrometheusJob() { + return metricsPrometheusJob; + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/NodePrivateKeyFileOption.java b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/NodePrivateKeyFileOption.java index 42789627d64..107ced545d1 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/NodePrivateKeyFileOption.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/NodePrivateKeyFileOption.java @@ -22,6 +22,8 @@ /** The Node private key file Cli option. */ public class NodePrivateKeyFileOption { + /** Default constructor. */ + NodePrivateKeyFileOption() {} /** * Create node private key file option. diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/P2PTLSConfigOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/P2PTLSConfigOptions.java index fefea4bb758..c3f8c56219f 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/P2PTLSConfigOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/P2PTLSConfigOptions.java @@ -103,6 +103,9 @@ public class P2PTLSConfigOptions { "Whether to send a SNI header in the TLS ClientHello message (default: ${DEFAULT-VALUE})") private final Boolean p2pTlsClientHelloSniHeaderEnabled = false; + /** Default constructor. */ + P2PTLSConfigOptions() {} + /** * Generate P2p tls configuration. * diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/PermissionsOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/PermissionsOptions.java new file mode 100644 index 00000000000..f1eafaab165 --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/PermissionsOptions.java @@ -0,0 +1,209 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.options.stable; + +import org.hyperledger.besu.cli.DefaultCommandValues; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis; +import org.hyperledger.besu.ethereum.p2p.peers.EnodeDnsConfiguration; +import org.hyperledger.besu.ethereum.permissioning.LocalPermissioningConfiguration; +import org.hyperledger.besu.ethereum.permissioning.PermissioningConfiguration; +import org.hyperledger.besu.ethereum.permissioning.PermissioningConfigurationBuilder; +import org.hyperledger.besu.ethereum.permissioning.SmartContractPermissioningConfiguration; + +import java.nio.file.Path; +import java.util.Optional; + +import org.slf4j.Logger; +import picocli.CommandLine; + +/** Handles configuration options for permissions in Besu. */ +public class PermissionsOptions { + @CommandLine.Option( + names = {"--permissions-nodes-config-file-enabled"}, + description = "Enable node level permissions (default: ${DEFAULT-VALUE})") + private final Boolean permissionsNodesEnabled = false; + + @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. + @CommandLine.Option( + names = {"--permissions-nodes-config-file"}, + description = + "Node permissioning config TOML file (default: a file named \"permissions_config.toml\" in the Besu data folder)") + private String nodePermissionsConfigFile = null; + + @CommandLine.Option( + names = {"--permissions-accounts-config-file-enabled"}, + description = "Enable account level permissions (default: ${DEFAULT-VALUE})") + private final Boolean permissionsAccountsEnabled = false; + + @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. + @CommandLine.Option( + names = {"--permissions-accounts-config-file"}, + description = + "Account permissioning config TOML file (default: a file named \"permissions_config.toml\" in the Besu data folder)") + private String accountPermissionsConfigFile = null; + + @CommandLine.Option( + names = {"--permissions-nodes-contract-address"}, + description = "Address of the node permissioning smart contract", + arity = "1") + private final Address permissionsNodesContractAddress = null; + + @CommandLine.Option( + names = {"--permissions-nodes-contract-version"}, + description = "Version of the EEA Node Permissioning interface (default: ${DEFAULT-VALUE})") + private final Integer permissionsNodesContractVersion = 1; + + @CommandLine.Option( + names = {"--permissions-nodes-contract-enabled"}, + description = "Enable node level permissions via smart contract (default: ${DEFAULT-VALUE})") + private final Boolean permissionsNodesContractEnabled = false; + + @CommandLine.Option( + names = {"--permissions-accounts-contract-address"}, + description = "Address of the account permissioning smart contract", + arity = "1") + private final Address permissionsAccountsContractAddress = null; + + @CommandLine.Option( + names = {"--permissions-accounts-contract-enabled"}, + description = + "Enable account level permissions via smart contract (default: ${DEFAULT-VALUE})") + private final Boolean permissionsAccountsContractEnabled = false; + + /** Default constructor. */ + public PermissionsOptions() {} + + /** + * Creates a PermissioningConfiguration based on the provided options. + * + * @param jsonRpcHttpOptions The JSON-RPC HTTP options + * @param rpcWebsocketOptions The RPC websocket options + * @param enodeDnsConfiguration The enode DNS configuration + * @param dataPath The data path + * @param logger The logger + * @param commandLine The command line + * @return An Optional PermissioningConfiguration instance + * @throws Exception If an error occurs while creating the configuration + */ + public Optional permissioningConfiguration( + final JsonRpcHttpOptions jsonRpcHttpOptions, + final RpcWebsocketOptions rpcWebsocketOptions, + final EnodeDnsConfiguration enodeDnsConfiguration, + final Path dataPath, + final Logger logger, + final CommandLine commandLine) + throws Exception { + if (!(localPermissionsEnabled() || contractPermissionsEnabled())) { + if (jsonRpcHttpOptions.getRpcHttpApis().contains(RpcApis.PERM.name()) + || rpcWebsocketOptions.getRpcWsApis().contains(RpcApis.PERM.name())) { + logger.warn( + "Permissions are disabled. Cannot enable PERM APIs when not using Permissions."); + } + return Optional.empty(); + } + + final Optional localPermissioningConfigurationOptional; + if (localPermissionsEnabled()) { + final Optional nodePermissioningConfigFile = + Optional.ofNullable(nodePermissionsConfigFile); + final Optional accountPermissioningConfigFile = + Optional.ofNullable(accountPermissionsConfigFile); + + final LocalPermissioningConfiguration localPermissioningConfiguration = + PermissioningConfigurationBuilder.permissioningConfiguration( + permissionsNodesEnabled, + enodeDnsConfiguration, + nodePermissioningConfigFile.orElse(getDefaultPermissioningFilePath(dataPath)), + permissionsAccountsEnabled, + accountPermissioningConfigFile.orElse(getDefaultPermissioningFilePath(dataPath))); + + localPermissioningConfigurationOptional = Optional.of(localPermissioningConfiguration); + } else { + if (nodePermissionsConfigFile != null && !permissionsNodesEnabled) { + logger.warn( + "Node permissioning config file set {} but no permissions enabled", + nodePermissionsConfigFile); + } + + if (accountPermissionsConfigFile != null && !permissionsAccountsEnabled) { + logger.warn( + "Account permissioning config file set {} but no permissions enabled", + accountPermissionsConfigFile); + } + localPermissioningConfigurationOptional = Optional.empty(); + } + + final SmartContractPermissioningConfiguration smartContractPermissioningConfiguration = + SmartContractPermissioningConfiguration.createDefault(); + + if (Boolean.TRUE.equals(permissionsNodesContractEnabled)) { + if (permissionsNodesContractAddress == null) { + throw new CommandLine.ParameterException( + commandLine, + "No node permissioning contract address specified. Cannot enable smart contract based node permissioning."); + } else { + smartContractPermissioningConfiguration.setSmartContractNodeAllowlistEnabled( + permissionsNodesContractEnabled); + smartContractPermissioningConfiguration.setNodeSmartContractAddress( + permissionsNodesContractAddress); + smartContractPermissioningConfiguration.setNodeSmartContractInterfaceVersion( + permissionsNodesContractVersion); + } + } else if (permissionsNodesContractAddress != null) { + logger.warn( + "Node permissioning smart contract address set {} but smart contract node permissioning is disabled.", + permissionsNodesContractAddress); + } + + if (Boolean.TRUE.equals(permissionsAccountsContractEnabled)) { + if (permissionsAccountsContractAddress == null) { + throw new CommandLine.ParameterException( + commandLine, + "No account permissioning contract address specified. Cannot enable smart contract based account permissioning."); + } else { + smartContractPermissioningConfiguration.setSmartContractAccountAllowlistEnabled( + permissionsAccountsContractEnabled); + smartContractPermissioningConfiguration.setAccountSmartContractAddress( + permissionsAccountsContractAddress); + } + } else if (permissionsAccountsContractAddress != null) { + logger.warn( + "Account permissioning smart contract address set {} but smart contract account permissioning is disabled.", + permissionsAccountsContractAddress); + } + + final PermissioningConfiguration permissioningConfiguration = + new PermissioningConfiguration( + localPermissioningConfigurationOptional, + Optional.of(smartContractPermissioningConfiguration)); + + return Optional.of(permissioningConfiguration); + } + + private boolean localPermissionsEnabled() { + return permissionsAccountsEnabled || permissionsNodesEnabled; + } + + private boolean contractPermissionsEnabled() { + return permissionsNodesContractEnabled || permissionsAccountsContractEnabled; + } + + private String getDefaultPermissioningFilePath(final Path dataPath) { + return dataPath + + System.getProperty("file.separator") + + DefaultCommandValues.PERMISSIONING_CONFIG_LOCATION; + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/PluginsConfigurationOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/PluginsConfigurationOptions.java new file mode 100644 index 00000000000..47df831c577 --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/PluginsConfigurationOptions.java @@ -0,0 +1,100 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.options.stable; + +import static org.hyperledger.besu.cli.DefaultCommandValues.DEFAULT_PLUGINS_EXTERNAL_ENABLED_OPTION_NAME; +import static org.hyperledger.besu.cli.DefaultCommandValues.DEFAULT_PLUGINS_OPTION_NAME; + +import org.hyperledger.besu.cli.converter.PluginInfoConverter; +import org.hyperledger.besu.cli.options.CLIOptions; +import org.hyperledger.besu.cli.util.CommandLineUtils; +import org.hyperledger.besu.ethereum.core.plugins.PluginConfiguration; +import org.hyperledger.besu.ethereum.core.plugins.PluginInfo; + +import java.util.List; + +import picocli.CommandLine; + +/** The Plugins Options options. */ +public class PluginsConfigurationOptions implements CLIOptions { + + @CommandLine.Option( + names = {DEFAULT_PLUGINS_EXTERNAL_ENABLED_OPTION_NAME}, + description = "Enables external plugins (default: ${DEFAULT-VALUE})", + hidden = true, + defaultValue = "true", + arity = "1") + private Boolean externalPluginsEnabled = true; + + @CommandLine.Option( + names = {DEFAULT_PLUGINS_OPTION_NAME}, + description = "Comma-separated list of plugin names", + split = ",", + hidden = true, + converter = PluginInfoConverter.class, + arity = "1..*") + private List plugins; + + /** Default Constructor. */ + public PluginsConfigurationOptions() {} + + @Override + public PluginConfiguration toDomainObject() { + return new PluginConfiguration.Builder() + .externalPluginsEnabled(externalPluginsEnabled) + .requestedPlugins(plugins) + .build(); + } + + /** + * Validate that there are no inconsistencies in the specified options. + * + * @param commandLine the full commandLine to check all the options specified by the user + */ + public void validate(final CommandLine commandLine) { + String errorMessage = + String.format( + "%s option can only be used when %s is true", + DEFAULT_PLUGINS_OPTION_NAME, DEFAULT_PLUGINS_EXTERNAL_ENABLED_OPTION_NAME); + CommandLineUtils.failIfOptionDoesntMeetRequirement( + commandLine, errorMessage, externalPluginsEnabled, List.of(DEFAULT_PLUGINS_OPTION_NAME)); + } + + @Override + public List getCLIOptions() { + return CommandLineUtils.getCLIOptions(this, new PluginsConfigurationOptions()); + } + + /** + * Constructs a {@link PluginConfiguration} instance based on the command line options. + * + * @param commandLine The command line instance containing parsed options. + * @return A new {@link PluginConfiguration} instance. + */ + public static PluginConfiguration fromCommandLine(final CommandLine commandLine) { + List plugins = + CommandLineUtils.getOptionValueOrDefault( + commandLine, DEFAULT_PLUGINS_OPTION_NAME, new PluginInfoConverter()); + + boolean externalPluginsEnabled = + CommandLineUtils.getOptionValueOrDefault( + commandLine, DEFAULT_PLUGINS_EXTERNAL_ENABLED_OPTION_NAME, Boolean::parseBoolean); + + return new PluginConfiguration.Builder() + .requestedPlugins(plugins) + .externalPluginsEnabled(externalPluginsEnabled) + .build(); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/stable/RpcWebsocketOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/RpcWebsocketOptions.java new file mode 100644 index 00000000000..40edeecc0da --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/stable/RpcWebsocketOptions.java @@ -0,0 +1,269 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.options.stable; + +import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.DEFAULT_RPC_APIS; +import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.VALID_APIS; +import static org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration.DEFAULT_WEBSOCKET_PORT; + +import org.hyperledger.besu.cli.DefaultCommandValues; +import org.hyperledger.besu.cli.custom.RpcAuthFileValidator; +import org.hyperledger.besu.cli.util.CommandLineUtils; +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.authentication.JwtAlgorithm; +import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import com.google.common.base.Strings; +import org.slf4j.Logger; +import picocli.CommandLine; + +/** This class represents the WebSocket options for the RPC. */ +public class RpcWebsocketOptions { + @CommandLine.Option( + names = {"--rpc-ws-authentication-jwt-algorithm"}, + description = + "Encryption algorithm used for Websockets JWT public key. Possible values are ${COMPLETION-CANDIDATES}" + + " (default: ${DEFAULT-VALUE})", + arity = "1") + private final JwtAlgorithm rpcWebsocketsAuthenticationAlgorithm = + DefaultCommandValues.DEFAULT_JWT_ALGORITHM; + + @CommandLine.Option( + names = {"--rpc-ws-enabled"}, + description = "Set to start the JSON-RPC WebSocket service (default: ${DEFAULT-VALUE})") + private final Boolean isRpcWsEnabled = false; + + @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. + @CommandLine.Option( + names = {"--rpc-ws-host"}, + paramLabel = DefaultCommandValues.MANDATORY_HOST_FORMAT_HELP, + description = "Host for JSON-RPC WebSocket service to listen on (default: ${DEFAULT-VALUE})", + arity = "1") + private String rpcWsHost; + + @CommandLine.Option( + names = {"--rpc-ws-port"}, + paramLabel = DefaultCommandValues.MANDATORY_PORT_FORMAT_HELP, + description = "Port for JSON-RPC WebSocket service to listen on (default: ${DEFAULT-VALUE})", + arity = "1") + private final Integer rpcWsPort = DEFAULT_WEBSOCKET_PORT; + + @CommandLine.Option( + names = {"--rpc-ws-max-frame-size"}, + description = + "Maximum size in bytes for JSON-RPC WebSocket frames (default: ${DEFAULT-VALUE}). If this limit is exceeded, the websocket will be disconnected.", + arity = "1") + private final Integer rpcWsMaxFrameSize = DefaultCommandValues.DEFAULT_WS_MAX_FRAME_SIZE; + + @CommandLine.Option( + names = {"--rpc-ws-max-active-connections"}, + description = + "Maximum number of WebSocket connections allowed for JSON-RPC (default: ${DEFAULT-VALUE}). Once this limit is reached, incoming connections will be rejected.", + arity = "1") + private final Integer rpcWsMaxConnections = DefaultCommandValues.DEFAULT_WS_MAX_CONNECTIONS; + + @CommandLine.Option( + names = {"--rpc-ws-api", "--rpc-ws-apis"}, + paramLabel = "", + split = " {0,1}, {0,1}", + arity = "1..*", + description = + "Comma separated list of APIs to enable on JSON-RPC WebSocket service (default: ${DEFAULT-VALUE})") + private final List rpcWsApis = DEFAULT_RPC_APIS; + + @CommandLine.Option( + names = {"--rpc-ws-api-methods-no-auth", "--rpc-ws-api-method-no-auth"}, + paramLabel = "", + split = " {0,1}, {0,1}", + arity = "1..*", + description = + "Comma separated list of RPC methods to exclude from RPC authentication services, RPC WebSocket authentication must be enabled") + private final List rpcWsApiMethodsNoAuth = new ArrayList(); + + @CommandLine.Option( + names = {"--rpc-ws-authentication-enabled"}, + description = + "Require authentication for the JSON-RPC WebSocket service (default: ${DEFAULT-VALUE})") + private final Boolean isRpcWsAuthenticationEnabled = false; + + @SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings. + @CommandLine.Option( + names = {"--rpc-ws-authentication-credentials-file"}, + paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP, + description = + "Storage file for JSON-RPC WebSocket authentication credentials (default: ${DEFAULT-VALUE})", + arity = "1") + private String rpcWsAuthenticationCredentialsFile = null; + + @CommandLine.Option( + names = {"--rpc-ws-authentication-jwt-public-key-file"}, + paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP, + description = "JWT public key file for JSON-RPC WebSocket authentication", + arity = "1") + private final File rpcWsAuthenticationPublicKeyFile = null; + + /** Default Constructor. */ + public RpcWebsocketOptions() {} + + /** + * Validates the WebSocket options. + * + * @param logger Logger instance + * @param commandLine CommandLine instance + * @param configuredApis Predicate for configured APIs + */ + public void validate( + final Logger logger, final CommandLine commandLine, final Predicate configuredApis) { + checkOptionDependencies(logger, commandLine); + + if (!rpcWsApis.stream().allMatch(configuredApis)) { + final List invalidWsApis = new ArrayList<>(rpcWsApis); + invalidWsApis.removeAll(VALID_APIS); + throw new CommandLine.ParameterException( + commandLine, + "Invalid value for option '--rpc-ws-api': invalid entries found " + invalidWsApis); + } + + final boolean validWsApiMethods = + rpcWsApiMethodsNoAuth.stream().allMatch(RpcMethod::rpcMethodExists); + + if (!validWsApiMethods) { + throw new CommandLine.ParameterException( + commandLine, + "Invalid value for option '--rpc-ws-api-methods-no-auth', options must be valid RPC methods"); + } + + if (isRpcWsAuthenticationEnabled + && rpcWsAuthenticationCredentialsFile(commandLine) == null + && rpcWsAuthenticationPublicKeyFile == null) { + throw new CommandLine.ParameterException( + commandLine, + "Unable to authenticate JSON-RPC WebSocket endpoint without a supplied credentials file or authentication public key file"); + } + } + + /** + * Checks the dependencies of the WebSocket options. + * + * @param logger Logger instance + * @param commandLine CommandLine instance + */ + private void checkOptionDependencies(final Logger logger, final CommandLine commandLine) { + CommandLineUtils.checkOptionDependencies( + logger, + commandLine, + "--rpc-ws-enabled", + !isRpcWsEnabled, + List.of( + "--rpc-ws-api", + "--rpc-ws-apis", + "--rpc-ws-api-method-no-auth", + "--rpc-ws-api-methods-no-auth", + "--rpc-ws-host", + "--rpc-ws-port", + "--rpc-ws-max-frame-size", + "--rpc-ws-max-active-connections", + "--rpc-ws-authentication-enabled", + "--rpc-ws-authentication-credentials-file", + "--rpc-ws-authentication-public-key-file", + "--rpc-ws-authentication-jwt-algorithm")); + + if (isRpcWsAuthenticationEnabled) { + CommandLineUtils.checkOptionDependencies( + logger, + commandLine, + "--rpc-ws-authentication-public-key-file", + rpcWsAuthenticationPublicKeyFile == null, + List.of("--rpc-ws-authentication-jwt-algorithm")); + } + } + + /** + * Creates a WebSocket configuration based on the WebSocket options. + * + * @param hostsAllowlist List of allowed hosts + * @param defaultHostAddress Default host address + * @param wsTimoutSec WebSocket timeout in seconds + * @return WebSocketConfiguration instance + */ + public WebSocketConfiguration webSocketConfiguration( + final List hostsAllowlist, final String defaultHostAddress, final Long wsTimoutSec) { + final WebSocketConfiguration webSocketConfiguration = WebSocketConfiguration.createDefault(); + webSocketConfiguration.setEnabled(isRpcWsEnabled); + webSocketConfiguration.setHost( + Strings.isNullOrEmpty(rpcWsHost) ? defaultHostAddress : rpcWsHost); + webSocketConfiguration.setPort(rpcWsPort); + webSocketConfiguration.setMaxFrameSize(rpcWsMaxFrameSize); + webSocketConfiguration.setMaxActiveConnections(rpcWsMaxConnections); + webSocketConfiguration.setRpcApis(rpcWsApis); + webSocketConfiguration.setRpcApisNoAuth( + rpcWsApiMethodsNoAuth.stream().distinct().collect(Collectors.toList())); + webSocketConfiguration.setAuthenticationEnabled(isRpcWsAuthenticationEnabled); + webSocketConfiguration.setAuthenticationCredentialsFile(rpcWsAuthenticationCredentialsFile); + webSocketConfiguration.setHostsAllowlist(hostsAllowlist); + webSocketConfiguration.setAuthenticationPublicKeyFile(rpcWsAuthenticationPublicKeyFile); + webSocketConfiguration.setAuthenticationAlgorithm(rpcWebsocketsAuthenticationAlgorithm); + webSocketConfiguration.setTimeoutSec(wsTimoutSec); + return webSocketConfiguration; + } + + /** + * Validates the authentication credentials file for the WebSocket. + * + * @param commandLine CommandLine instance + * @return Filename of the authentication credentials file + */ + private String rpcWsAuthenticationCredentialsFile(final CommandLine commandLine) { + final String filename = rpcWsAuthenticationCredentialsFile; + + if (filename != null) { + RpcAuthFileValidator.validate(commandLine, filename, "WS"); + } + return filename; + } + + /** + * Returns the list of APIs for the WebSocket. + * + * @return List of APIs + */ + public List getRpcWsApis() { + return rpcWsApis; + } + + /** + * Checks if the WebSocket service is enabled. + * + * @return Boolean indicating if the WebSocket service is enabled + */ + public Boolean isRpcWsEnabled() { + return isRpcWsEnabled; + } + + /** + * Returns the port for the WebSocket service. + * + * @return Port number + */ + public Integer getRpcWsPort() { + return rpcWsPort; + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainPruningOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainPruningOptions.java index e164ec8fa85..0a59ef9b04e 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainPruningOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/ChainPruningOptions.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.cli.options.unstable; @@ -29,9 +28,18 @@ public class ChainPruningOptions implements CLIOptions private static final String CHAIN_PRUNING_ENABLED_FLAG = "--Xchain-pruning-enabled"; private static final String CHAIN_PRUNING_BLOCKS_RETAINED_FLAG = "--Xchain-pruning-blocks-retained"; + private static final String CHAIN_PRUNING_BLOCKS_RETAINED_LIMIT_FLAG = + "--Xchain-pruning-blocks-retained-limit"; private static final String CHAIN_PRUNING_FREQUENCY_FLAG = "--Xchain-pruning-frequency"; - /** The constant DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED. */ - public static final long DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED = 7200; + + /** + * The "CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED_LIMIT" field sets the minimum limit for the + * "CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED" value. For most networks, the default value of this + * limit is the safest. Reducing this value requires careful consideration and understanding of + * the potential implications. Lowering this limit may have unintended side effects. + */ + public static final long CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED_LIMIT = 7200; + /** The constant DEFAULT_CHAIN_DATA_PRUNING_FREQUENCY. */ public static final int DEFAULT_CHAIN_DATA_PRUNING_FREQUENCY = 256; @@ -46,11 +54,21 @@ public class ChainPruningOptions implements CLIOptions hidden = true, names = {CHAIN_PRUNING_BLOCKS_RETAINED_FLAG}, description = - "The number of recent blocks for which to keep the chain data. Must be >= " - + DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED + "The number of recent blocks for which to keep the chain data. Should be >= " + + CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED_LIMIT + + " (default: ${DEFAULT-VALUE})") + private final Long chainDataPruningBlocksRetained = CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED_LIMIT; + + @CommandLine.Option( + hidden = true, + names = {CHAIN_PRUNING_BLOCKS_RETAINED_LIMIT_FLAG}, + description = + "Allows setting the limit below which no more blocks can be pruned. This prevents setting a value lower than this for " + + CHAIN_PRUNING_BLOCKS_RETAINED_FLAG + + ". This flag should be used with caution as reducing the limit may have unintended side effects." + " (default: ${DEFAULT-VALUE})") - private final Long chainDataPruningBlocksRetained = - DEFAULT_CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED; + private final Long chainDataPruningBlocksRetainedLimit = + CHAIN_DATA_PRUNING_MIN_BLOCKS_RETAINED_LIMIT; @CommandLine.Option( hidden = true, @@ -60,6 +78,9 @@ public class ChainPruningOptions implements CLIOptions private final PositiveNumber chainDataPruningBlocksFrequency = PositiveNumber.fromInt(DEFAULT_CHAIN_DATA_PRUNING_FREQUENCY); + /** Default Constructor. */ + ChainPruningOptions() {} + /** * Create chain pruning options. * @@ -87,11 +108,21 @@ public Long getChainDataPruningBlocksRetained() { return chainDataPruningBlocksRetained; } + /** + * Get the configured number of retained blocks for chain pruning. + * + * @return the number of retained blocks + */ + public Long getChainDataPruningBlocksRetainedLimit() { + return chainDataPruningBlocksRetainedLimit; + } + @Override public ChainPrunerConfiguration toDomainObject() { return new ChainPrunerConfiguration( chainDataPruningEnabled, chainDataPruningBlocksRetained, + chainDataPruningBlocksRetainedLimit, chainDataPruningBlocksFrequency.getValue()); } @@ -102,6 +133,8 @@ public List getCLIOptions() { chainDataPruningEnabled.toString(), CHAIN_PRUNING_BLOCKS_RETAINED_FLAG, chainDataPruningBlocksRetained.toString(), + CHAIN_PRUNING_BLOCKS_RETAINED_LIMIT_FLAG, + chainDataPruningBlocksRetainedLimit.toString(), CHAIN_PRUNING_FREQUENCY_FLAG, chainDataPruningBlocksFrequency.toString()); } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/DnsOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/DnsOptions.java index 8d65a3264e9..9f9670e8e66 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/DnsOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/DnsOptions.java @@ -43,6 +43,9 @@ public class DnsOptions implements CLIOptions { arity = "1") private Boolean dnsUpdateEnabled = Boolean.FALSE; + /** Instantiates a new Dns options. */ + DnsOptions() {} + /** * Create dns options. * diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/EvmOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/EvmOptions.java index f91fcc880f7..39d95324df2 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/EvmOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/EvmOptions.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.cli.options.unstable; import org.hyperledger.besu.cli.options.CLIOptions; @@ -31,6 +30,9 @@ public class EvmOptions implements CLIOptions { /** The constant WORLDSTATE_UPDATE_MODE. */ public static final String WORLDSTATE_UPDATE_MODE = "--Xevm-worldstate-update-mode"; + /** Default constructor. */ + EvmOptions() {} + /** * Create evm options. * @@ -47,7 +49,6 @@ public static EvmOptions create() { "size in kilobytes to allow the cache " + "of valid jump destinations to grow to before evicting the least recently used entry", fallbackValue = "32000", - defaultValue = "32000", hidden = true, arity = "1") private Long jumpDestCacheWeightKilobytes = @@ -57,7 +58,6 @@ public static EvmOptions create() { names = {WORLDSTATE_UPDATE_MODE}, description = "How to handle worldstate updates within a transaction", fallbackValue = "STACKED", - defaultValue = "STACKED", hidden = true, arity = "1") private EvmConfiguration.WorldUpdaterMode worldstateUpdateMode = diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/InProcessRpcOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/InProcessRpcOptions.java new file mode 100644 index 00000000000..9adeb37db7d --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/InProcessRpcOptions.java @@ -0,0 +1,73 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.options.unstable; + +import static org.hyperledger.besu.ethereum.api.jsonrpc.InProcessRpcConfiguration.DEFAULT_IN_PROCESS_RPC_APIS; +import static org.hyperledger.besu.ethereum.api.jsonrpc.InProcessRpcConfiguration.DEFAULT_IN_PROCESS_RPC_ENABLED; + +import org.hyperledger.besu.cli.options.CLIOptions; +import org.hyperledger.besu.cli.util.CommandLineUtils; +import org.hyperledger.besu.ethereum.api.jsonrpc.ImmutableInProcessRpcConfiguration; +import org.hyperledger.besu.ethereum.api.jsonrpc.InProcessRpcConfiguration; + +import java.util.List; +import java.util.Set; + +import picocli.CommandLine; + +/** The in process RPC options. */ +public class InProcessRpcOptions implements CLIOptions { + + /** Default constructor. */ + InProcessRpcOptions() {} + + /** + * Create ipc options. + * + * @return the ipc options + */ + public static InProcessRpcOptions create() { + return new InProcessRpcOptions(); + } + + @CommandLine.Option( + names = {"--Xin-process-rpc-enabled"}, + hidden = true, + description = "Set to enalbe in-process RPC method call service (default: ${DEFAULT-VALUE})") + private final Boolean enabled = DEFAULT_IN_PROCESS_RPC_ENABLED; + + @CommandLine.Option( + names = {"--Xin-process-rpc-api", "--Xin-process-rpc-apis"}, + hidden = true, + paramLabel = "", + split = " {0,1}, {0,1}", + arity = "1..*", + description = + "Comma separated list of APIs to enable on in-process RPC method call service (default: ${DEFAULT-VALUE})") + private final Set inProcessRpcApis = DEFAULT_IN_PROCESS_RPC_APIS; + + @Override + public InProcessRpcConfiguration toDomainObject() { + return ImmutableInProcessRpcConfiguration.builder() + .isEnabled(enabled) + .inProcessRpcApis(inProcessRpcApis) + .build(); + } + + @Override + public List getCLIOptions() { + return CommandLineUtils.getCLIOptions(this, new InProcessRpcOptions()); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/IpcOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/IpcOptions.java index d8b868ef909..19ba2941932 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/IpcOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/IpcOptions.java @@ -25,6 +25,9 @@ public class IpcOptions { private static final String DEFAULT_IPC_FILE = "besu.ipc"; + /** Default constructor. */ + IpcOptions() {} + /** * Create ipc options. * diff --git a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/MetricsCLIOptions.java b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/MetricsCLIOptions.java index 45270c808c9..95164c31cc6 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/MetricsCLIOptions.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/options/unstable/MetricsCLIOptions.java @@ -30,7 +30,6 @@ public class MetricsCLIOptions implements CLIOptions

Decode a RLP hex string into a validator list. + */ + @Command( + name = "decode", + description = "This command decodes a JSON typed RLP hex string into validator list.", + mixinStandardHelpOptions = true, + versionProvider = VersionProvider.class) + static class DecodeSubCommand implements Runnable { + + @SuppressWarnings("unused") + @ParentCommand + private RLPSubCommand parentCommand; // Picocli injects reference to parent command + + @SuppressWarnings("unused") + @Spec + private CommandSpec spec; + + @Option( + names = "--type", + description = + "Type of the RLP data to Decode, possible values are ${COMPLETION-CANDIDATES}. (default: ${DEFAULT-VALUE})", + arity = "1..1") + private final RLPType type = RLPType.IBFT_EXTRA_DATA; + + @Option( + names = "--from", + paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP, + description = "File containing JSON object to decode", + arity = "1..1") + private final File jsonSourceFile = null; + + @Option( + names = "--to", + paramLabel = DefaultCommandValues.MANDATORY_FILE_FORMAT_HELP, + description = "File to write decoded RLP string to.", + arity = "1..1") + private final File rlpTargetFile = null; + + @Override + public void run() { + checkNotNull(parentCommand); + readInput(); + } + + /** + * Reads the stdin or from a file if one is specified by {@link #jsonSourceFile} then goes to + * {@link #decode(String)} this data + */ + private void readInput() { + // if we have an output file defined, print to it + // otherwise print to defined output, usually standard output. + final String inputData; + + if (jsonSourceFile != null) { + try { + BufferedReader reader = Files.newBufferedReader(jsonSourceFile.toPath(), UTF_8); + + // Read only the first line if there are many lines + inputData = reader.readLine(); + } catch (IOException e) { + throw new ExecutionException(spec.commandLine(), "Unable to read input file."); + } + } else { + // get data from standard input + try (Scanner scanner = new Scanner(parentCommand.in, UTF_8.name())) { + inputData = scanner.nextLine(); + } catch (NoSuchElementException e) { + throw new ParameterException(spec.commandLine(), "Unable to read input data." + e); + } + } + + decode(inputData); + } + + /** + * Decodes the string input into an validator data based on the {@link #type} then goes to + * {@link #writeOutput(BftExtraData)} this data to file or stdout + * + * @param inputData the string data to decode + */ + private void decode(final String inputData) { + if (inputData == null || inputData.isEmpty()) { + throw new ParameterException( + spec.commandLine(), "An error occurred while trying to read the input data."); + } else { + try { + // decode and write the value + writeOutput(type.getAdapter().decode(inputData)); + } catch (MismatchedInputException e) { + throw new ParameterException( + spec.commandLine(), + "Unable to map the input data with selected type. Please check input format. " + e); + } catch (IOException e) { + throw new ParameterException( + spec.commandLine(), "Unable to load the input data. Please check input format. " + e); + } + } + } + + /** + * write the decoded result to stdout or a file if the option is specified + * + * @param bftExtraDataOutput the BFT extra data output to write to file or stdout + */ + private void writeOutput(final BftExtraData bftExtraDataOutput) { + if (rlpTargetFile != null) { + final Path targetPath = rlpTargetFile.toPath(); + + try (final BufferedWriter fileWriter = Files.newBufferedWriter(targetPath, UTF_8)) { + fileWriter.write(bftExtraDataOutput.getValidators().toString()); + } catch (final IOException e) { + throw new ParameterException( + spec.commandLine(), + "An error occurred while trying to write the validator list. " + e.getMessage()); + } + } else { + parentCommand.out.println(bftExtraDataOutput.getValidators().toString()); + } + } + } } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RevertMetadataSubCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RevertMetadataSubCommand.java new file mode 100644 index 00000000000..c1e903e0808 --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RevertMetadataSubCommand.java @@ -0,0 +1,138 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.subcommands.storage; + +import org.hyperledger.besu.cli.util.VersionProvider; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.OptionalInt; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.ParentCommand; + +/** The revert metadata to v1 subcommand. */ +@Command( + name = "revert-metadata", + description = "Revert database metadata to previous format", + mixinStandardHelpOptions = true, + versionProvider = VersionProvider.class, + subcommands = RevertMetadataSubCommand.v2ToV1.class) +public class RevertMetadataSubCommand implements Runnable { + private static final Logger LOG = LoggerFactory.getLogger(RevertMetadataSubCommand.class); + private static final String METADATA_FILENAME = "DATABASE_METADATA.json"; + private static final ObjectMapper MAPPER = + new ObjectMapper() + .registerModule(new Jdk8Module()) + .setSerializationInclusion(JsonInclude.Include.NON_ABSENT) + .enable(SerializationFeature.INDENT_OUTPUT); + + @SuppressWarnings("unused") + @ParentCommand + private StorageSubCommand parentCommand; + + @SuppressWarnings("unused") + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; + + /** Default Constructor. */ + public RevertMetadataSubCommand() {} + + @Override + public void run() { + spec.commandLine().usage(System.out); + } + + @Command( + name = "v2-to-v1", + description = "Revert a database metadata v2 format to v1 format", + mixinStandardHelpOptions = true, + versionProvider = VersionProvider.class) + static class v2ToV1 implements Runnable { + + @SuppressWarnings("unused") + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; + + @SuppressWarnings("unused") + @ParentCommand + private RevertMetadataSubCommand parentCommand; + + @Override + public void run() { + + final Path dataDir = parentCommand.parentCommand.besuCommand.dataDir(); + + final File dbMetadata = dataDir.resolve(METADATA_FILENAME).toFile(); + if (!dbMetadata.exists()) { + String errMsg = + String.format( + "Could not find database metadata file %s, check your data dir %s", + dbMetadata, dataDir); + LOG.error(errMsg); + throw new IllegalArgumentException(errMsg); + } + try { + final var root = MAPPER.readTree(dbMetadata); + if (!root.has("v2")) { + String errMsg = + String.format("Database metadata file %s is not in v2 format", dbMetadata); + LOG.error(errMsg); + throw new IllegalArgumentException(errMsg); + } + + final var v2Obj = root.get("v2"); + if (!v2Obj.has("format")) { + String errMsg = + String.format( + "Database metadata file %s is malformed, \"format\" field not found", dbMetadata); + LOG.error(errMsg); + throw new IllegalArgumentException(errMsg); + } + + final var formatField = v2Obj.get("format").asText(); + final OptionalInt maybePrivacyVersion = + v2Obj.has("privacyVersion") + ? OptionalInt.of(v2Obj.get("privacyVersion").asInt()) + : OptionalInt.empty(); + + final DataStorageFormat dataStorageFormat = DataStorageFormat.valueOf(formatField); + final int v1Version = + switch (dataStorageFormat) { + case FOREST -> 1; + case BONSAI -> 2; + }; + + @JsonSerialize + record V1(int version, OptionalInt privacyVersion) {} + + MAPPER.writeValue(dbMetadata, new V1(v1Version, maybePrivacyVersion)); + LOG.info("Successfully reverted database metadata from v2 to v1 in {}", dbMetadata); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RocksDbHelper.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RocksDbHelper.java new file mode 100644 index 00000000000..572066264c0 --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RocksDbHelper.java @@ -0,0 +1,327 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.subcommands.storage; + +import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.stream.Stream; + +import org.bouncycastle.util.Arrays; +import org.rocksdb.ColumnFamilyDescriptor; +import org.rocksdb.ColumnFamilyHandle; +import org.rocksdb.Options; +import org.rocksdb.RocksDB; +import org.rocksdb.RocksDBException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** RocksDB subcommand helper methods. */ +public class RocksDbHelper { + private static final Logger LOG = LoggerFactory.getLogger(RocksDbHelper.class); + + /** Default Constructor. */ + RocksDbHelper() {} + + static void forEachColumnFamily( + final String dbPath, final BiConsumer task) { + RocksDB.loadLibrary(); + Options options = new Options(); + options.setCreateIfMissing(true); + + // Open the RocksDB database with multiple column families + List cfNames; + try { + cfNames = RocksDB.listColumnFamilies(options, dbPath); + } catch (RocksDBException e) { + throw new RuntimeException(e); + } + final List cfHandles = new ArrayList<>(); + final List cfDescriptors = new ArrayList<>(); + for (byte[] cfName : cfNames) { + cfDescriptors.add(new ColumnFamilyDescriptor(cfName)); + } + try (final RocksDB rocksdb = RocksDB.openReadOnly(dbPath, cfDescriptors, cfHandles)) { + for (ColumnFamilyHandle cfHandle : cfHandles) { + task.accept(rocksdb, cfHandle); + } + } catch (RocksDBException e) { + throw new RuntimeException(e); + } finally { + for (ColumnFamilyHandle cfHandle : cfHandles) { + cfHandle.close(); + } + } + } + + static void printStatsForColumnFamily( + final RocksDB rocksdb, final ColumnFamilyHandle cfHandle, final PrintWriter out) + throws RocksDBException { + final String size = rocksdb.getProperty(cfHandle, "rocksdb.estimate-live-data-size"); + final String numberOfKeys = rocksdb.getProperty(cfHandle, "rocksdb.estimate-num-keys"); + final long sizeLong = Long.parseLong(size); + final long numberOfKeysLong = Long.parseLong(numberOfKeys); + if (!size.isBlank() + && !numberOfKeys.isBlank() + && isPopulatedColumnFamily(sizeLong, numberOfKeysLong)) { + out.println("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-="); + out.println("Column Family: " + getNameById(cfHandle.getName())); + + final String prefix = "rocksdb."; + final String cfstats = "cfstats"; + final String cfstats_no_file_histogram = "cfstats-no-file-histogram"; + final String cf_file_histogram = "cf-file-histogram"; + final String cf_write_stall_stats = "cf-write-stall-stats"; + final String dbstats = "dbstats"; + final String db_write_stall_stats = "db-write-stall-stats"; + final String levelstats = "levelstats"; + final String block_cache_entry_stats = "block-cache-entry-stats"; + final String fast_block_cache_entry_stats = "fast-block-cache-entry-stats"; + final String num_immutable_mem_table = "num-immutable-mem-table"; + final String num_immutable_mem_table_flushed = "num-immutable-mem-table-flushed"; + final String mem_table_flush_pending = "mem-table-flush-pending"; + final String compaction_pending = "compaction-pending"; + final String background_errors = "background-errors"; + final String cur_size_active_mem_table = "cur-size-active-mem-table"; + final String cur_size_all_mem_tables = "cur-size-all-mem-tables"; + final String size_all_mem_tables = "size-all-mem-tables"; + final String num_entries_active_mem_table = "num-entries-active-mem-table"; + final String num_entries_imm_mem_tables = "num-entries-imm-mem-tables"; + final String num_deletes_active_mem_table = "num-deletes-active-mem-table"; + final String num_deletes_imm_mem_tables = "num-deletes-imm-mem-tables"; + final String estimate_num_keys = "estimate-num-keys"; + final String estimate_table_readers_mem = "estimate-table-readers-mem"; + final String is_file_deletions_enabled = "is-file-deletions-enabled"; + final String num_snapshots = "num-snapshots"; + final String oldest_snapshot_time = "oldest-snapshot-time"; + final String oldest_snapshot_sequence = "oldest-snapshot-sequence"; + final String num_live_versions = "num-live-versions"; + final String current_version_number = "current-super-version-number"; + final String estimate_live_data_size = "estimate-live-data-size"; + final String min_log_number_to_keep_str = "min-log-number-to-keep"; + final String min_obsolete_sst_number_to_keep_str = "min-obsolete-sst-number-to-keep"; + final String base_level_str = "base-level"; + final String total_sst_files_size = "total-sst-files-size"; + final String live_sst_files_size = "live-sst-files-size"; + final String obsolete_sst_files_size = "obsolete-sst-files-size"; + final String live_sst_files_size_at_temperature = "live-sst-files-size-at-temperature"; + final String estimate_pending_comp_bytes = "estimate-pending-compaction-bytes"; + final String aggregated_table_properties = "aggregated-table-properties"; + final String num_running_compactions = "num-running-compactions"; + final String num_running_flushes = "num-running-flushes"; + final String actual_delayed_write_rate = "actual-delayed-write-rate"; + final String is_write_stopped = "is-write-stopped"; + final String estimate_oldest_key_time = "estimate-oldest-key-time"; + final String block_cache_capacity = "block-cache-capacity"; + final String block_cache_usage = "block-cache-usage"; + final String block_cache_pinned_usage = "block-cache-pinned-usage"; + final String options_statistics = "options-statistics"; + final String num_blob_files = "num-blob-files"; + final String blob_stats = "blob-stats"; + final String total_blob_file_size = "total-blob-file-size"; + final String live_blob_file_size = "live-blob-file-size"; + final String live_blob_file_garbage_size = "live-blob-file-garbage-size"; + final String blob_cache_capacity = "blob-cache-capacity"; + final String blob_cache_usage = "blob-cache-usage"; + final String blob_cache_pinned_usage = "blob-cache-pinned-usage"; + Stream.of( + cfstats, + cfstats_no_file_histogram, + cf_file_histogram, + cf_write_stall_stats, + dbstats, + db_write_stall_stats, + levelstats, + block_cache_entry_stats, + fast_block_cache_entry_stats, + num_immutable_mem_table, + num_immutable_mem_table_flushed, + mem_table_flush_pending, + compaction_pending, + background_errors, + cur_size_active_mem_table, + cur_size_all_mem_tables, + size_all_mem_tables, + num_entries_active_mem_table, + num_entries_imm_mem_tables, + num_deletes_active_mem_table, + num_deletes_imm_mem_tables, + estimate_num_keys, + estimate_table_readers_mem, + is_file_deletions_enabled, + num_snapshots, + oldest_snapshot_time, + oldest_snapshot_sequence, + num_live_versions, + current_version_number, + estimate_live_data_size, + min_log_number_to_keep_str, + min_obsolete_sst_number_to_keep_str, + base_level_str, + total_sst_files_size, + live_sst_files_size, + obsolete_sst_files_size, + live_sst_files_size_at_temperature, + estimate_pending_comp_bytes, + aggregated_table_properties, + num_running_compactions, + num_running_flushes, + actual_delayed_write_rate, + is_write_stopped, + estimate_oldest_key_time, + block_cache_capacity, + block_cache_usage, + block_cache_pinned_usage, + options_statistics, + num_blob_files, + blob_stats, + total_blob_file_size, + live_blob_file_size, + live_blob_file_garbage_size, + blob_cache_capacity, + blob_cache_usage, + blob_cache_pinned_usage) + .forEach( + prop -> { + try { + final String value = rocksdb.getProperty(cfHandle, prefix + prop); + if (!value.isBlank()) { + out.println(prop + ": " + value); + } + } catch (RocksDBException e) { + LOG.debug("couldn't get property {}", prop); + } + }); + out.println("=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-="); + } + } + + static ColumnFamilyUsage getAndPrintUsageForColumnFamily( + final RocksDB rocksdb, final ColumnFamilyHandle cfHandle, final PrintWriter out) + throws RocksDBException, NumberFormatException { + final String numberOfKeys = rocksdb.getProperty(cfHandle, "rocksdb.estimate-num-keys"); + if (!numberOfKeys.isBlank()) { + try { + final long numberOfKeysLong = Long.parseLong(numberOfKeys); + final String totalSstFilesSize = + rocksdb.getProperty(cfHandle, "rocksdb.total-sst-files-size"); + final long totalSstFilesSizeLong = + !totalSstFilesSize.isBlank() ? Long.parseLong(totalSstFilesSize) : 0; + + final String totalBlobFilesSize = + rocksdb.getProperty(cfHandle, "rocksdb.total-blob-file-size"); + final long totalBlobFilesSizeLong = + !totalBlobFilesSize.isBlank() ? Long.parseLong(totalBlobFilesSize) : 0; + + final long totalFilesSize = totalSstFilesSizeLong + totalBlobFilesSizeLong; + if (isPopulatedColumnFamily(0, numberOfKeysLong)) { + printLine( + out, + getNameById(cfHandle.getName()), + rocksdb.getProperty(cfHandle, "rocksdb.estimate-num-keys"), + formatOutputSize(totalFilesSize), + formatOutputSize(totalSstFilesSizeLong), + formatOutputSize(totalBlobFilesSizeLong)); + } + return new ColumnFamilyUsage( + getNameById(cfHandle.getName()), + numberOfKeysLong, + totalFilesSize, + totalSstFilesSizeLong, + totalBlobFilesSizeLong); + } catch (NumberFormatException e) { + LOG.error("Failed to parse string into long: " + e.getMessage()); + } + } + // return empty usage on error + return new ColumnFamilyUsage(getNameById(cfHandle.getName()), 0, 0, 0, 0); + } + + static void printTotals(final PrintWriter out, final List columnFamilyUsages) { + final long totalKeys = columnFamilyUsages.stream().mapToLong(ColumnFamilyUsage::keys).sum(); + final long totalSize = + columnFamilyUsages.stream().mapToLong(ColumnFamilyUsage::totalSize).sum(); + final long totalSsts = + columnFamilyUsages.stream().mapToLong(ColumnFamilyUsage::sstFilesSize).sum(); + final long totalBlobs = + columnFamilyUsages.stream().mapToLong(ColumnFamilyUsage::blobFilesSize).sum(); + printSeparator(out); + printLine( + out, + "ESTIMATED TOTAL", + String.valueOf(totalKeys), + formatOutputSize(totalSize), + formatOutputSize(totalSsts), + formatOutputSize(totalBlobs)); + printSeparator(out); + } + + private static boolean isPopulatedColumnFamily(final long size, final long numberOfKeys) { + return size != 0 || numberOfKeys != 0; + } + + static String formatOutputSize(final long size) { + if (size > (1024 * 1024 * 1024)) { + long sizeInGiB = size / (1024 * 1024 * 1024); + return sizeInGiB + " GiB"; + } else if (size > (1024 * 1024)) { + long sizeInMiB = size / (1024 * 1024); + return sizeInMiB + " MiB"; + } else if (size > 1024) { + long sizeInKiB = size / 1024; + return sizeInKiB + " KiB"; + } else { + return size + " B"; + } + } + + private static String getNameById(final byte[] id) { + for (KeyValueSegmentIdentifier segment : KeyValueSegmentIdentifier.values()) { + if (Arrays.areEqual(segment.getId(), id)) { + return segment.getName(); + } + } + return null; // id not found + } + + static void printTableHeader(final PrintWriter out) { + printSeparator(out); + out.format( + "| Column Family | Keys | Total Size | SST Files Size | Blob Files Size | \n"); + printSeparator(out); + } + + private static void printSeparator(final PrintWriter out) { + out.format( + "|--------------------------------|-----------------|-------------|-----------------|------------------|\n"); + } + + static void printLine( + final PrintWriter out, + final String cfName, + final String keys, + final String totalFilesSize, + final String sstFilesSize, + final String blobFilesSize) { + final String format = "| %-30s | %-15s | %-11s | %-15s | %-16s |\n"; + out.format(format, cfName, keys, totalFilesSize, sstFilesSize, blobFilesSize); + } + + record ColumnFamilyUsage( + String name, long keys, long totalSize, long sstFilesSize, long blobFilesSize) {} +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RocksDbSubCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RocksDbSubCommand.java new file mode 100644 index 00000000000..c2c3bd9b7be --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/RocksDbSubCommand.java @@ -0,0 +1,140 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.subcommands.storage; + +import static org.hyperledger.besu.controller.BesuController.DATABASE_PATH; + +import org.hyperledger.besu.cli.util.VersionProvider; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +import org.rocksdb.RocksDBException; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.ParentCommand; + +/** The RocksDB subcommand. */ +@Command( + name = "rocksdb", + description = "Print RocksDB information", + mixinStandardHelpOptions = true, + versionProvider = VersionProvider.class, + subcommands = {RocksDbSubCommand.RocksDbUsage.class, RocksDbSubCommand.RocksDbStats.class}) +public class RocksDbSubCommand implements Runnable { + + @SuppressWarnings("unused") + @ParentCommand + private StorageSubCommand storageSubCommand; + + @SuppressWarnings("unused") + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; + + /** Default Constructor. */ + public RocksDbSubCommand() {} + + @Override + public void run() { + spec.commandLine().usage(System.out); + } + + @Command( + name = "usage", + description = "Print disk usage", + mixinStandardHelpOptions = true, + versionProvider = VersionProvider.class) + static class RocksDbUsage implements Runnable { + + @SuppressWarnings("unused") + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; + + @SuppressWarnings("unused") + @ParentCommand + private RocksDbSubCommand rocksDbSubCommand; + + @Override + public void run() { + + final PrintWriter out = spec.commandLine().getOut(); + + final String dbPath = + rocksDbSubCommand + .storageSubCommand + .besuCommand + .dataDir() + .resolve(DATABASE_PATH) + .toString(); + + RocksDbHelper.printTableHeader(out); + + final List columnFamilyUsages = new ArrayList<>(); + RocksDbHelper.forEachColumnFamily( + dbPath, + (rocksdb, cfHandle) -> { + try { + columnFamilyUsages.add( + RocksDbHelper.getAndPrintUsageForColumnFamily(rocksdb, cfHandle, out)); + } catch (RocksDBException e) { + throw new RuntimeException(e); + } + }); + RocksDbHelper.printTotals(out, columnFamilyUsages); + } + } + + @Command( + name = "x-stats", + description = "Print rocksdb stats", + mixinStandardHelpOptions = true, + versionProvider = VersionProvider.class) + static class RocksDbStats implements Runnable { + + @SuppressWarnings("unused") + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; + + @SuppressWarnings("unused") + @ParentCommand + private RocksDbSubCommand rocksDbSubCommand; + + @Override + public void run() { + + final PrintWriter out = spec.commandLine().getOut(); + + final String dbPath = + rocksDbSubCommand + .storageSubCommand + .besuCommand + .dataDir() + .resolve(DATABASE_PATH) + .toString(); + + out.println("Column Family Stats..."); + RocksDbHelper.forEachColumnFamily( + dbPath, + (rocksdb, cfHandle) -> { + try { + RocksDbHelper.printStatsForColumnFamily(rocksdb, cfHandle, out); + } catch (RocksDBException e) { + throw new RuntimeException(e); + } + }); + } + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/StorageSubCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/StorageSubCommand.java index bd40a42a431..e06e24f0c28 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/StorageSubCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/StorageSubCommand.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -24,6 +24,7 @@ import org.hyperledger.besu.cli.BesuCommand; import org.hyperledger.besu.cli.util.VersionProvider; +import org.hyperledger.besu.controller.BesuController; import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; @@ -45,7 +46,12 @@ description = "This command provides storage related actions.", mixinStandardHelpOptions = true, versionProvider = VersionProvider.class, - subcommands = {StorageSubCommand.RevertVariablesStorage.class}) + subcommands = { + StorageSubCommand.RevertVariablesStorage.class, + RocksDbSubCommand.class, + TrieLogSubCommand.class, + RevertMetadataSubCommand.class + }) public class StorageSubCommand implements Runnable { /** The constant COMMAND_NAME. */ @@ -53,7 +59,7 @@ public class StorageSubCommand implements Runnable { @SuppressWarnings("unused") @ParentCommand - private BesuCommand parentCommand; + BesuCommand besuCommand; @SuppressWarnings("unused") @Spec @@ -93,21 +99,19 @@ static class RevertVariablesStorage implements Runnable { public void run() { checkNotNull(parentCommand); - final var storageProvider = getStorageProvider(); + final var storageProvider = createBesuController().getStorageProvider(); revert(storageProvider); } - private StorageProvider getStorageProvider() { - // init collection of ignorable segments - parentCommand.parentCommand.setIgnorableStorageSegments(); - return parentCommand.parentCommand.getStorageProvider(); + private BesuController createBesuController() { + return parentCommand.besuCommand.buildController(); } private void revert(final StorageProvider storageProvider) { final var variablesStorage = storageProvider.createVariablesStorage(); final var blockchainStorage = - getStorageProvider().getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.BLOCKCHAIN); + storageProvider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.BLOCKCHAIN); final var blockchainUpdater = blockchainStorage.startTransaction(); final var variablesUpdater = variablesStorage.updater(); diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/TrieLogHelper.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/TrieLogHelper.java new file mode 100644 index 00000000000..6bcfe4d3531 --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/TrieLogHelper.java @@ -0,0 +1,477 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.subcommands.storage; + +import static com.google.common.base.Preconditions.checkArgument; +import static org.hyperledger.besu.cli.options.stable.DataStorageOptions.BONSAI_STORAGE_FORMAT_MAX_LAYERS_TO_LOAD; +import static org.hyperledger.besu.controller.BesuController.DATABASE_PATH; +import static org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration.DEFAULT_BONSAI_TRIE_LOG_PRUNING_WINDOW_SIZE; + +import org.hyperledger.besu.cli.options.stable.DataStorageOptions; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.chain.MutableBlockchain; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.trielog.TrieLogFactoryImpl; +import org.hyperledger.besu.ethereum.trie.diffbased.common.trielog.TrieLogLayer; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; + +import com.google.common.annotations.VisibleForTesting; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Helper class for counting and pruning trie logs */ +public class TrieLogHelper { + private static final String TRIE_LOG_FILE = "trieLogsToRetain"; + private static final long BATCH_SIZE = 20_000; + private static final int ROCKSDB_MAX_INSERTS_PER_TRANSACTION = 1000; + private static final Logger LOG = LoggerFactory.getLogger(TrieLogHelper.class); + + /** Default Constructor. */ + public TrieLogHelper() {} + + boolean prune( + final DataStorageConfiguration config, + final BonsaiWorldStateKeyValueStorage rootWorldStateStorage, + final MutableBlockchain blockchain, + final Path dataDirectoryPath) { + + final String batchFileNameBase = + dataDirectoryPath.resolve(DATABASE_PATH).resolve(TRIE_LOG_FILE).toString(); + + validatePruneConfiguration(config); + + final long layersToRetain = config.getBonsaiMaxLayersToLoad(); + + final long chainHeight = blockchain.getChainHeadBlockNumber(); + + final long lastBlockNumberToRetainTrieLogsFor = chainHeight - layersToRetain + 1; + + if (!validatePruneRequirements( + blockchain, + chainHeight, + lastBlockNumberToRetainTrieLogsFor, + rootWorldStateStorage, + layersToRetain)) { + return false; + } + + final long numberOfBatches = calculateNumberOfBatches(layersToRetain); + LOG.info("Retain {} trie logs, processing in {} batches", layersToRetain, numberOfBatches); + + processTrieLogBatches( + rootWorldStateStorage, + blockchain, + chainHeight, + lastBlockNumberToRetainTrieLogsFor, + numberOfBatches, + batchFileNameBase); + + // Should only be layersToRetain left but loading extra just in case of an unforeseen bug + final long countAfterPrune = + rootWorldStateStorage + .streamTrieLogKeys(layersToRetain + DEFAULT_BONSAI_TRIE_LOG_PRUNING_WINDOW_SIZE) + .count(); + if (countAfterPrune == layersToRetain) { + if (deleteFiles(batchFileNameBase, numberOfBatches)) { + return true; + } else { + throw new IllegalStateException( + "There was an error deleting the trie log backup files. Please ensure besu is working before deleting them manually."); + } + } else { + throw new IllegalStateException( + String.format( + "Remaining trie logs (%d) did not match %s (%d). Trie logs backup files (in %s) have not been deleted, it is safe to rerun the subcommand.", + countAfterPrune, + BONSAI_STORAGE_FORMAT_MAX_LAYERS_TO_LOAD, + layersToRetain, + batchFileNameBase)); + } + } + + private void processTrieLogBatches( + final BonsaiWorldStateKeyValueStorage rootWorldStateStorage, + final MutableBlockchain blockchain, + final long chainHeight, + final long lastBlockNumberToRetainTrieLogsFor, + final long numberOfBatches, + final String batchFileNameBase) { + + for (long batchNumber = 1; batchNumber <= numberOfBatches; batchNumber++) { + final String batchFileName = batchFileNameBase + "-" + batchNumber; + final long firstBlockOfBatch = chainHeight - ((batchNumber - 1) * BATCH_SIZE); + final long lastBlockOfBatch = + Math.max(chainHeight - (batchNumber * BATCH_SIZE), lastBlockNumberToRetainTrieLogsFor); + final List trieLogKeys = + getTrieLogKeysForBlocks(blockchain, firstBlockOfBatch, lastBlockOfBatch); + + LOG.info("Saving trie logs to retain in file {} (batch {})...", batchFileName, batchNumber); + saveTrieLogBatches(batchFileName, rootWorldStateStorage, trieLogKeys); + } + + LOG.info("Clear trie logs..."); + rootWorldStateStorage.clearTrieLog(); + + for (long batchNumber = 1; batchNumber <= numberOfBatches; batchNumber++) { + restoreTrieLogBatches(rootWorldStateStorage, batchNumber, batchFileNameBase); + } + } + + private void saveTrieLogBatches( + final String batchFileName, + final BonsaiWorldStateKeyValueStorage rootWorldStateStorage, + final List trieLogKeys) { + + try { + saveTrieLogsInFile(trieLogKeys, rootWorldStateStorage, batchFileName); + } catch (IOException e) { + LOG.error("Error saving trie logs to file: {}", e.getMessage()); + throw new RuntimeException(e); + } + } + + private void restoreTrieLogBatches( + final BonsaiWorldStateKeyValueStorage rootWorldStateStorage, + final long batchNumber, + final String batchFileNameBase) { + + try { + LOG.info("Restoring trie logs retained from batch {}...", batchNumber); + recreateTrieLogs(rootWorldStateStorage, batchNumber, batchFileNameBase); + } catch (IOException e) { + LOG.error("Error recreating trie logs from batch {}: {}", batchNumber, e.getMessage()); + throw new RuntimeException(e); + } + } + + private boolean deleteFiles(final String batchFileNameBase, final long numberOfBatches) { + + LOG.info("Deleting files..."); + + try { + for (long batchNumber = 1; batchNumber <= numberOfBatches; batchNumber++) { + File file = new File(batchFileNameBase + "-" + batchNumber); + if (file.exists()) { + file.delete(); + } + } + return true; + } catch (Exception e) { + LOG.error("Error deleting files", e); + return false; + } + } + + private List getTrieLogKeysForBlocks( + final MutableBlockchain blockchain, + final long firstBlockOfBatch, + final long lastBlockOfBatch) { + final List trieLogKeys = new ArrayList<>(); + for (long i = firstBlockOfBatch; i >= lastBlockOfBatch; i--) { + final Optional header = blockchain.getBlockHeader(i); + header.ifPresentOrElse( + blockHeader -> trieLogKeys.add(blockHeader.getHash()), + () -> LOG.error("Error retrieving block")); + } + return trieLogKeys; + } + + private long calculateNumberOfBatches(final long layersToRetain) { + return layersToRetain / BATCH_SIZE + ((layersToRetain % BATCH_SIZE == 0) ? 0 : 1); + } + + private boolean validatePruneRequirements( + final MutableBlockchain blockchain, + final long chainHeight, + final long lastBlockNumberToRetainTrieLogsFor, + final BonsaiWorldStateKeyValueStorage rootWorldStateStorage, + final long layersToRetain) { + + if (lastBlockNumberToRetainTrieLogsFor < 0) { + throw new IllegalArgumentException( + "Trying to retain more trie logs than chain length (" + + chainHeight + + "), skipping pruning"); + } + + // Need to ensure we're loading at least layersToRetain if they exist + // plus extra threshold to account forks and orphans + final long clampedCountBeforePruning = + rootWorldStateStorage + .streamTrieLogKeys(layersToRetain + DEFAULT_BONSAI_TRIE_LOG_PRUNING_WINDOW_SIZE) + .count(); + if (clampedCountBeforePruning < layersToRetain) { + throw new IllegalArgumentException( + String.format( + "Trie log count (%d) is less than retention limit (%d), skipping pruning", + clampedCountBeforePruning, layersToRetain)); + } + + final Optional finalizedBlockHash = blockchain.getFinalized(); + + if (finalizedBlockHash.isEmpty()) { + throw new RuntimeException("No finalized block present, can't safely run trie log prune"); + } else { + final Hash finalizedHash = finalizedBlockHash.get(); + final Optional finalizedBlockHeader = blockchain.getBlockHeader(finalizedHash); + if (finalizedBlockHeader.isPresent() + && finalizedBlockHeader.get().getNumber() < lastBlockNumberToRetainTrieLogsFor) { + throw new IllegalArgumentException( + "Trying to prune more layers than the finalized block height, skipping pruning"); + } + } + return true; + } + + private void recreateTrieLogs( + final BonsaiWorldStateKeyValueStorage rootWorldStateStorage, + final long batchNumber, + final String batchFileNameBase) + throws IOException { + // process in chunk to avoid OOM + final String batchFileName = batchFileNameBase + "-" + batchNumber; + IdentityHashMap trieLogsToRetain = readTrieLogsFromFile(batchFileName); + final int chunkSize = ROCKSDB_MAX_INSERTS_PER_TRANSACTION; + List keys = new ArrayList<>(trieLogsToRetain.keySet()); + + for (int startIndex = 0; startIndex < keys.size(); startIndex += chunkSize) { + processTransactionChunk(startIndex, chunkSize, keys, trieLogsToRetain, rootWorldStateStorage); + } + } + + private void processTransactionChunk( + final int startIndex, + final int chunkSize, + final List keys, + final IdentityHashMap trieLogsToRetain, + final BonsaiWorldStateKeyValueStorage rootWorldStateStorage) { + + var updater = rootWorldStateStorage.updater(); + int endIndex = Math.min(startIndex + chunkSize, keys.size()); + + for (int i = startIndex; i < endIndex; i++) { + byte[] key = keys.get(i); + byte[] value = trieLogsToRetain.get(key); + updater.getTrieLogStorageTransaction().put(key, value); + LOG.info("Key({}): {}", i, Bytes32.wrap(key).toShortHexString()); + } + + updater.getTrieLogStorageTransaction().commit(); + } + + @VisibleForTesting + void validatePruneConfiguration(final DataStorageConfiguration config) { + checkArgument( + config.getBonsaiMaxLayersToLoad() + >= DataStorageConfiguration.MINIMUM_BONSAI_TRIE_LOG_RETENTION_LIMIT, + String.format( + BONSAI_STORAGE_FORMAT_MAX_LAYERS_TO_LOAD + " minimum value is %d", + DataStorageConfiguration.MINIMUM_BONSAI_TRIE_LOG_RETENTION_LIMIT)); + checkArgument( + config.getBonsaiTrieLogPruningWindowSize() > 0, + String.format( + DataStorageOptions.BONSAI_TRIE_LOG_PRUNING_WINDOW_SIZE + "=%d must be greater than 0", + config.getBonsaiTrieLogPruningWindowSize())); + checkArgument( + config.getBonsaiTrieLogPruningWindowSize() > config.getBonsaiMaxLayersToLoad(), + String.format( + DataStorageOptions.BONSAI_TRIE_LOG_PRUNING_WINDOW_SIZE + + "=%d must be greater than " + + BONSAI_STORAGE_FORMAT_MAX_LAYERS_TO_LOAD + + "=%d", + config.getBonsaiTrieLogPruningWindowSize(), + config.getBonsaiMaxLayersToLoad())); + } + + private void saveTrieLogsInFile( + final List trieLogsKeys, + final BonsaiWorldStateKeyValueStorage rootWorldStateStorage, + final String batchFileName) + throws IOException { + + File file = new File(batchFileName); + if (file.exists()) { + LOG.warn("File already exists {}, skipping file creation", batchFileName); + return; + } + + try (FileOutputStream fos = new FileOutputStream(file)) { + ObjectOutputStream oos = new ObjectOutputStream(fos); + oos.writeObject(getTrieLogs(trieLogsKeys, rootWorldStateStorage)); + } catch (IOException e) { + LOG.error(e.getMessage()); + throw new RuntimeException(e); + } + } + + @SuppressWarnings("unchecked") + IdentityHashMap readTrieLogsFromFile(final String batchFileName) { + + IdentityHashMap trieLogs; + try (FileInputStream fis = new FileInputStream(batchFileName); + ObjectInputStream ois = new ObjectInputStream(fis)) { + + trieLogs = (IdentityHashMap) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + LOG.error(e.getMessage()); + throw new RuntimeException(e); + } + + return trieLogs; + } + + private void saveTrieLogsAsRlpInFile( + final List trieLogsKeys, + final BonsaiWorldStateKeyValueStorage rootWorldStateStorage, + final String batchFileName) { + File file = new File(batchFileName); + if (file.exists()) { + LOG.warn("File already exists {}, skipping file creation", batchFileName); + return; + } + + final IdentityHashMap trieLogs = + getTrieLogs(trieLogsKeys, rootWorldStateStorage); + final Bytes rlp = + RLP.encode( + o -> + o.writeList( + trieLogs.entrySet(), (val, out) -> out.writeRaw(Bytes.wrap(val.getValue())))); + try { + Files.write(file.toPath(), rlp.toArrayUnsafe()); + } catch (IOException e) { + LOG.error(e.getMessage()); + throw new RuntimeException(e); + } + } + + IdentityHashMap readTrieLogsAsRlpFromFile(final String batchFileName) { + try { + final Bytes file = Bytes.wrap(Files.readAllBytes(Path.of(batchFileName))); + final BytesValueRLPInput input = new BytesValueRLPInput(file, false); + + input.enterList(); + final IdentityHashMap trieLogs = new IdentityHashMap<>(); + while (!input.isEndOfCurrentList()) { + final Bytes trieLogBytes = input.currentListAsBytes(); + TrieLogLayer trieLogLayer = + TrieLogFactoryImpl.readFrom(new BytesValueRLPInput(Bytes.wrap(trieLogBytes), false)); + trieLogs.put(trieLogLayer.getBlockHash().toArrayUnsafe(), trieLogBytes.toArrayUnsafe()); + } + input.leaveList(); + + return trieLogs; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private IdentityHashMap getTrieLogs( + final List trieLogKeys, final BonsaiWorldStateKeyValueStorage rootWorldStateStorage) { + IdentityHashMap trieLogsToRetain = new IdentityHashMap<>(); + + LOG.info("Obtaining trielogs from db, this may take a few minutes..."); + trieLogKeys.forEach( + hash -> + rootWorldStateStorage + .getTrieLog(hash) + .ifPresent(trieLog -> trieLogsToRetain.put(hash.toArrayUnsafe(), trieLog))); + return trieLogsToRetain; + } + + TrieLogCount getCount( + final BonsaiWorldStateKeyValueStorage rootWorldStateStorage, + final int limit, + final Blockchain blockchain) { + final AtomicInteger total = new AtomicInteger(); + final AtomicInteger canonicalCount = new AtomicInteger(); + final AtomicInteger forkCount = new AtomicInteger(); + final AtomicInteger orphanCount = new AtomicInteger(); + rootWorldStateStorage + .streamTrieLogKeys(limit) + .map(Bytes32::wrap) + .map(Hash::wrap) + .forEach( + hash -> { + total.getAndIncrement(); + blockchain + .getBlockHeader(hash) + .ifPresentOrElse( + (header) -> { + long number = header.getNumber(); + final Optional headerByNumber = + blockchain.getBlockHeader(number); + if (headerByNumber.isPresent() + && headerByNumber.get().getHash().equals(hash)) { + canonicalCount.getAndIncrement(); + } else { + forkCount.getAndIncrement(); + } + }, + orphanCount::getAndIncrement); + }); + + return new TrieLogCount(total.get(), canonicalCount.get(), forkCount.get(), orphanCount.get()); + } + + void printCount(final PrintWriter out, final TrieLogCount count) { + out.printf( + "trieLog count: %s\n - canonical count: %s\n - fork count: %s\n - orphaned count: %s\n", + count.total, count.canonicalCount, count.forkCount, count.orphanCount); + } + + void importTrieLog( + final BonsaiWorldStateKeyValueStorage rootWorldStateStorage, final Path trieLogFilePath) { + + var trieLog = readTrieLogsAsRlpFromFile(trieLogFilePath.toString()); + + var updater = rootWorldStateStorage.updater(); + trieLog.forEach((key, value) -> updater.getTrieLogStorageTransaction().put(key, value)); + updater.getTrieLogStorageTransaction().commit(); + } + + void exportTrieLog( + final BonsaiWorldStateKeyValueStorage rootWorldStateStorage, + final List trieLogHash, + final Path directoryPath) + throws IOException { + final String trieLogFile = directoryPath.toString(); + + saveTrieLogsAsRlpInFile(trieLogHash, rootWorldStateStorage, trieLogFile); + } + + record TrieLogCount(int total, int canonicalCount, int forkCount, int orphanCount) {} +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/TrieLogSubCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/TrieLogSubCommand.java new file mode 100644 index 00000000000..6fe6d058b3f --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/subcommands/storage/TrieLogSubCommand.java @@ -0,0 +1,335 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.subcommands.storage; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static org.hyperledger.besu.cli.subcommands.storage.RocksDbHelper.formatOutputSize; +import static org.hyperledger.besu.controller.BesuController.DATABASE_PATH; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_LOG_STORAGE; + +import org.hyperledger.besu.cli.util.VersionProvider; +import org.hyperledger.besu.controller.BesuController; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.chain.MutableBlockchain; +import org.hyperledger.besu.ethereum.storage.StorageProvider; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.common.trielog.TrieLogPruner; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.ethereum.worldstate.ImmutableDataStorageConfiguration; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; + +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.config.Configurator; +import org.rocksdb.RocksDBException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.ParentCommand; + +/** The Trie Log subcommand. */ +@Command( + name = "trie-log", + aliases = "x-trie-log", + description = "Manipulate trie logs", + mixinStandardHelpOptions = true, + versionProvider = VersionProvider.class, + subcommands = { + TrieLogSubCommand.CountTrieLog.class, + TrieLogSubCommand.PruneTrieLog.class, + TrieLogSubCommand.ExportTrieLog.class, + TrieLogSubCommand.ImportTrieLog.class + }) +public class TrieLogSubCommand implements Runnable { + + private static final Logger LOG = LoggerFactory.getLogger(TrieLogSubCommand.class); + + @SuppressWarnings("UnusedVariable") + @ParentCommand + private static StorageSubCommand parentCommand; + + @SuppressWarnings("unused") + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; // Picocli injects reference to command spec + + /** Default Constructor. */ + TrieLogSubCommand() {} + + @Override + public void run() { + final PrintWriter out = spec.commandLine().getOut(); + spec.commandLine().usage(out); + } + + private static BesuController createBesuController() { + final DataStorageConfiguration config = parentCommand.besuCommand.getDataStorageConfiguration(); + // disable limit trie logs to avoid preloading during subcommand execution + return parentCommand + .besuCommand + .setupControllerBuilder() + .dataStorageConfiguration( + ImmutableDataStorageConfiguration.copyOf(config).withBonsaiLimitTrieLogsEnabled(false)) + .build(); + } + + @Command( + name = "count", + description = "This command counts all the trie logs", + mixinStandardHelpOptions = true, + versionProvider = VersionProvider.class) + static class CountTrieLog implements Runnable { + + @SuppressWarnings("unused") + @ParentCommand + private TrieLogSubCommand parentCommand; + + @SuppressWarnings("unused") + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; // Picocli injects reference to command spec + + @Override + public void run() { + final TrieLogContext context = getTrieLogContext(); + + final PrintWriter out = spec.commandLine().getOut(); + + out.println("Counting trie logs..."); + final TrieLogHelper trieLogHelper = new TrieLogHelper(); + trieLogHelper.printCount( + out, + trieLogHelper.getCount( + context.rootWorldStateStorage, Integer.MAX_VALUE, context.blockchain)); + } + } + + @Command( + name = "prune", + description = + "This command prunes all trie log layers below the retention limit, including orphaned trie logs.", + mixinStandardHelpOptions = true, + versionProvider = VersionProvider.class) + static class PruneTrieLog implements Runnable { + + @SuppressWarnings("unused") + @ParentCommand + private TrieLogSubCommand parentCommand; + + @SuppressWarnings("unused") + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; // Picocli injects reference to command spec + + @Override + public void run() { + final TrieLogContext context = getTrieLogContext(); + final Path dataDirectoryPath = + Paths.get( + TrieLogSubCommand.parentCommand.besuCommand.dataDir().toAbsolutePath().toString()); + + LOG.info("Estimating trie logs size before pruning..."); + long sizeBefore = estimatedSizeOfTrieLogs(); + LOG.info("Estimated trie logs size before pruning: {}", formatOutputSize(sizeBefore)); + LOG.info("Starting pruning..."); + final TrieLogHelper trieLogHelper = new TrieLogHelper(); + boolean success = + trieLogHelper.prune( + context.config(), + context.rootWorldStateStorage(), + context.blockchain(), + dataDirectoryPath); + + if (success) { + LOG.info("Finished pruning. Re-estimating trie logs size..."); + final long sizeAfter = estimatedSizeOfTrieLogs(); + LOG.info( + "Estimated trie logs size after pruning: {} (0 B estimate is normal when using default settings)", + formatOutputSize(sizeAfter)); + long estimatedSaving = sizeBefore - sizeAfter; + LOG.info( + "Prune ran successfully. We estimate you freed up {}! \uD83D\uDE80", + formatOutputSize(estimatedSaving)); + spec.commandLine() + .getOut() + .printf( + "Prune ran successfully. We estimate you freed up %s! \uD83D\uDE80\n", + formatOutputSize(estimatedSaving)); + } + } + + private long estimatedSizeOfTrieLogs() { + final String dbPath = + TrieLogSubCommand.parentCommand + .besuCommand + .dataDir() + .toString() + .concat("/") + .concat(DATABASE_PATH); + + AtomicLong estimatedSaving = new AtomicLong(0L); + try { + RocksDbHelper.forEachColumnFamily( + dbPath, + (rocksdb, cfHandle) -> { + try { + if (Arrays.equals(cfHandle.getName(), TRIE_LOG_STORAGE.getId())) { + + final long sstSize = + Long.parseLong(rocksdb.getProperty(cfHandle, "rocksdb.total-sst-files-size")); + final long blobSize = + Long.parseLong(rocksdb.getProperty(cfHandle, "rocksdb.total-blob-file-size")); + + estimatedSaving.set(sstSize + blobSize); + } + } catch (RocksDBException | NumberFormatException e) { + throw new RuntimeException(e); + } + }); + } catch (Exception e) { + LOG.warn("Error while estimating trie log size, returning 0 for estimate", e); + return 0L; + } + + return estimatedSaving.get(); + } + } + + @Command( + name = "export", + description = "This command exports the trie log of a determined block to a binary file", + mixinStandardHelpOptions = true, + versionProvider = VersionProvider.class) + static class ExportTrieLog implements Runnable { + + @SuppressWarnings("unused") + @ParentCommand + private TrieLogSubCommand parentCommand; + + @SuppressWarnings("unused") + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; // Picocli injects reference to command spec + + @SuppressWarnings("unused") + @CommandLine.Option( + names = "--trie-log-block-hash", + description = + "Comma separated list of hashes from the blocks you want to export the trie logs of", + split = " {0,1}, {0,1}", + arity = "1..*") + private List trieLogBlockHashList; + + @CommandLine.Option( + names = "--trie-log-file-path", + description = "The file you want to export the trie logs to", + arity = "1..1") + private Path trieLogFilePath = null; + + @Override + public void run() { + if (trieLogFilePath == null) { + trieLogFilePath = + Paths.get( + TrieLogSubCommand.parentCommand + .besuCommand + .dataDir() + .resolve("trie-logs.bin") + .toAbsolutePath() + .toString()); + } + + final TrieLogContext context = getTrieLogContext(); + + final List listOfBlockHashes = + trieLogBlockHashList.stream().map(Hash::fromHexString).toList(); + + final TrieLogHelper trieLogHelper = new TrieLogHelper(); + + try { + trieLogHelper.exportTrieLog( + context.rootWorldStateStorage(), listOfBlockHashes, trieLogFilePath); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + @Command( + name = "import", + description = "This command imports a trie log exported by another besu node", + mixinStandardHelpOptions = true, + versionProvider = VersionProvider.class) + static class ImportTrieLog implements Runnable { + + @SuppressWarnings("unused") + @ParentCommand + private TrieLogSubCommand parentCommand; + + @SuppressWarnings("unused") + @CommandLine.Spec + private CommandLine.Model.CommandSpec spec; // Picocli injects reference to command spec + + @CommandLine.Option( + names = "--trie-log-file-path", + description = "The file you want to import the trie logs from", + arity = "1..1") + private Path trieLogFilePath = null; + + @Override + public void run() { + if (trieLogFilePath == null) { + trieLogFilePath = + Paths.get( + TrieLogSubCommand.parentCommand + .besuCommand + .dataDir() + .resolve("trie-logs.bin") + .toAbsolutePath() + .toString()); + } + + TrieLogContext context = getTrieLogContext(); + final TrieLogHelper trieLogHelper = new TrieLogHelper(); + trieLogHelper.importTrieLog(context.rootWorldStateStorage(), trieLogFilePath); + } + } + + record TrieLogContext( + DataStorageConfiguration config, + BonsaiWorldStateKeyValueStorage rootWorldStateStorage, + MutableBlockchain blockchain) {} + + private static TrieLogContext getTrieLogContext() { + Configurator.setLevel(LoggerFactory.getLogger(TrieLogPruner.class).getName(), Level.DEBUG); + checkNotNull(parentCommand); + BesuController besuController = createBesuController(); + final DataStorageConfiguration config = besuController.getDataStorageConfiguration(); + checkArgument( + DataStorageFormat.BONSAI.equals(config.getDataStorageFormat()), + "Subcommand only works with data-storage-format=BONSAI"); + + final StorageProvider storageProvider = besuController.getStorageProvider(); + final BonsaiWorldStateKeyValueStorage rootWorldStateStorage = + (BonsaiWorldStateKeyValueStorage) storageProvider.createWorldStateStorage(config); + final MutableBlockchain blockchain = besuController.getProtocolContext().getBlockchain(); + return new TrieLogContext(config, rootWorldStateStorage, blockchain); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/util/AbstractConfigurationFinder.java b/besu/src/main/java/org/hyperledger/besu/cli/util/AbstractConfigurationFinder.java new file mode 100644 index 00000000000..2e043ba4303 --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/util/AbstractConfigurationFinder.java @@ -0,0 +1,127 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.util; + +import java.util.Map; +import java.util.Optional; + +import picocli.CommandLine; + +/** + * Abstract class for finding configuration resources. This class provides a common structure for + * classes that need to find configuration resources based on command line options and environment + * variables. + * + * @param the type of configuration resource this finder will return + */ +public abstract class AbstractConfigurationFinder { + /** Default Constructor. */ + public AbstractConfigurationFinder() {} + + /** + * Returns the name of the configuration option. + * + * @return the name of the configuration option + */ + protected abstract String getConfigOptionName(); + + /** + * Returns the name of the environment variable for the configuration. + * + * @return the name of the environment variable for the configuration + */ + protected abstract String getConfigEnvName(); + + /** + * Finds the configuration resource based on command line options and environment variables. + * + * @param environment the environment variables + * @param parseResult the command line parse result + * @return an Optional containing the configuration resource, or an empty Optional if no + * configuration resource was found + */ + public Optional findConfiguration( + final Map environment, final CommandLine.ParseResult parseResult) { + final CommandLine commandLine = parseResult.commandSpec().commandLine(); + if (isConfigSpecifiedInBothSources(environment, parseResult)) { + throwExceptionForBothSourcesSpecified(environment, parseResult, commandLine); + } + if (parseResult.hasMatchedOption(getConfigOptionName())) { + return getFromOption(parseResult, commandLine); + } + if (environment.containsKey(getConfigEnvName())) { + return getFromEnvironment(environment, commandLine); + } + return Optional.empty(); + } + + /** + * Gets the configuration resource from the command line option. + * + * @param parseResult the command line parse result + * @param commandLine the command line + * @return an Optional containing the configuration resource, or an empty Optional if the + * configuration resource was not specified in the command line option + */ + protected abstract Optional getFromOption( + final CommandLine.ParseResult parseResult, final CommandLine commandLine); + + /** + * Gets the configuration resource from the environment variable. + * + * @param environment the environment variables + * @param commandLine the command line + * @return an Optional containing the configuration resource, or an empty Optional if the + * configuration resource was not specified in the environment variable + */ + protected abstract Optional getFromEnvironment( + final Map environment, final CommandLine commandLine); + + /** + * Checks if the configuration resource is specified in both command line options and environment + * variables. + * + * @param environment the environment variables + * @param parseResult the command line parse result + * @return true if the configuration resource is specified in both places, false otherwise + */ + public boolean isConfigSpecifiedInBothSources( + final Map environment, final CommandLine.ParseResult parseResult) { + return parseResult.hasMatchedOption(getConfigOptionName()) + && environment.containsKey(getConfigEnvName()); + } + + /** + * Throws an exception if the configuration resource is specified in both command line options and + * environment variables. + * + * @param environment the environment variables + * @param parseResult the command line parse result + * @param commandLine the command line + */ + public void throwExceptionForBothSourcesSpecified( + final Map environment, + final CommandLine.ParseResult parseResult, + final CommandLine commandLine) { + throw new CommandLine.ParameterException( + commandLine, + String.format( + "Both %s=%s and %s %s specified. Please specify only one.", + getConfigEnvName(), + getConfigOptionName(), + environment.get(getConfigEnvName()), + parseResult.matchedOption(getConfigOptionName()).stringValues())); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/util/CascadingDefaultProvider.java b/besu/src/main/java/org/hyperledger/besu/cli/util/CascadingDefaultProvider.java index 489d61af95c..1e5d0bec6e7 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/util/CascadingDefaultProvider.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/util/CascadingDefaultProvider.java @@ -14,8 +14,6 @@ */ package org.hyperledger.besu.cli.util; -import static java.util.Arrays.asList; - import java.util.List; import picocli.CommandLine.IDefaultValueProvider; @@ -34,8 +32,8 @@ public class CascadingDefaultProvider implements IDefaultValueProvider { * * @param defaultValueProviders List of default value providers */ - public CascadingDefaultProvider(final IDefaultValueProvider... defaultValueProviders) { - this.defaultValueProviders = asList(defaultValueProviders); + public CascadingDefaultProvider(final List defaultValueProviders) { + this.defaultValueProviders = defaultValueProviders; } @Override diff --git a/besu/src/main/java/org/hyperledger/besu/cli/util/CommandLineUtils.java b/besu/src/main/java/org/hyperledger/besu/cli/util/CommandLineUtils.java index 397592459c6..dc4880c04e8 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/util/CommandLineUtils.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/util/CommandLineUtils.java @@ -36,15 +36,20 @@ public class CommandLineUtils { /** The constant DEPENDENCY_WARNING_MSG. */ public static final String DEPENDENCY_WARNING_MSG = "{} has been ignored because {} was not defined on the command line."; + /** The constant MULTI_DEPENDENCY_WARNING_MSG. */ public static final String MULTI_DEPENDENCY_WARNING_MSG = "{} ignored because none of {} was defined."; + /** The constant DEPRECATION_WARNING_MSG. */ public static final String DEPRECATION_WARNING_MSG = "{} has been deprecated, use {} instead."; + /** The constant DEPRECATED_AND_USELESS_WARNING_MSG. */ public static final String DEPRECATED_AND_USELESS_WARNING_MSG = "{} has been deprecated and is now useless, remove it."; + private CommandLineUtils() {} + /** * Check if options are passed that require an option to be true to have any effect and log a * warning with the list of affected options. @@ -261,4 +266,61 @@ public static boolean isOptionSet(final CommandLine commandLine, final String op .filter(optionSpec -> Arrays.stream(optionSpec.names()).anyMatch(optionName::equals)) .anyMatch(CommandLineUtils::isOptionSet); } + + /** + * Retrieves the value of a specified command line option, converting it to its appropriate type, + * or returns the default value if the option was not specified. + * + * @param The type of the option value. + * @param commandLine The {@link CommandLine} instance containing the parsed command line options. + * @param optionName The name of the option whose value is to be retrieved. + * @param converter A converter that converts the option's string value to its appropriate type. + * @return The value of the specified option converted to its type, or the default value if the + * option was not specified. Returns {@code null} if the option does not exist or if there is + * no default value and the option was not specified. + */ + public static T getOptionValueOrDefault( + final CommandLine commandLine, + final String optionName, + final CommandLine.ITypeConverter converter) { + + return commandLine + .getParseResult() + .matchedOptionValue(optionName, getDefaultOptionValue(commandLine, optionName, converter)); + } + + /** + * Retrieves the default value for a specified command line option, converting it to its + * appropriate type. + * + * @param The type of the option value. + * @param commandLine The {@link CommandLine} instance containing the parsed command line options. + * @param optionName The name of the option whose default value is to be retrieved. + * @param converter A converter that converts the option's default string value to its appropriate + * type. + * @return The default value of the specified option converted to its type, or {@code null} if the + * option does not exist, does not have a default value, or if an error occurs during + * conversion. + * @throws RuntimeException if there is an error converting the default value string to its type. + */ + private static T getDefaultOptionValue( + final CommandLine commandLine, + final String optionName, + final CommandLine.ITypeConverter converter) { + + CommandLine.Model.OptionSpec optionSpec = commandLine.getCommandSpec().findOption(optionName); + if (optionSpec == null || commandLine.getDefaultValueProvider() == null) { + return null; + } + + try { + String defaultValueString = commandLine.getDefaultValueProvider().defaultValue(optionSpec); + return defaultValueString != null + ? converter.convert(defaultValueString) + : optionSpec.getValue(); + } catch (Exception e) { + throw new RuntimeException( + "Failed to convert default value for option " + optionName + ": " + e.getMessage(), e); + } + } } diff --git a/besu/src/main/java/org/hyperledger/besu/cli/util/ConfigDefaultValueProviderStrategy.java b/besu/src/main/java/org/hyperledger/besu/cli/util/ConfigDefaultValueProviderStrategy.java new file mode 100644 index 00000000000..04ae8b2aff7 --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/util/ConfigDefaultValueProviderStrategy.java @@ -0,0 +1,88 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.util; + +import java.io.File; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import com.google.common.annotations.VisibleForTesting; +import picocli.CommandLine; +import picocli.CommandLine.IDefaultValueProvider; +import picocli.CommandLine.IExecutionStrategy; +import picocli.CommandLine.ParameterException; +import picocli.CommandLine.ParseResult; + +/** Custom Config option search and run handler. */ +public class ConfigDefaultValueProviderStrategy implements IExecutionStrategy { + private final IExecutionStrategy resultHandler; + private final Map environment; + + /** + * Instantiates a new Config option search and run handler. + * + * @param resultHandler the result handler + * @param environment the environment variables map + */ + public ConfigDefaultValueProviderStrategy( + final IExecutionStrategy resultHandler, final Map environment) { + this.resultHandler = resultHandler; + this.environment = environment; + } + + @Override + public int execute(final ParseResult parseResult) + throws CommandLine.ExecutionException, ParameterException { + final CommandLine commandLine = parseResult.commandSpec().commandLine(); + commandLine.setDefaultValueProvider( + createDefaultValueProvider( + commandLine, + new ConfigFileFinder().findConfiguration(environment, parseResult), + new ProfileFinder().findConfiguration(environment, parseResult))); + commandLine.setExecutionStrategy(resultHandler); + return commandLine.execute(parseResult.originalArgs().toArray(new String[0])); + } + + /** + * Create default value provider default value provider. + * + * @param commandLine the command line + * @param configFile the config file + * @param profile the profile file + * @return the default value provider + */ + @VisibleForTesting + public IDefaultValueProvider createDefaultValueProvider( + final CommandLine commandLine, + final Optional configFile, + final Optional profile) { + List providers = new ArrayList<>(); + providers.add(new EnvironmentVariableDefaultProvider(environment)); + + configFile.ifPresent( + config -> { + if (config.exists()) { + providers.add(TomlConfigurationDefaultProvider.fromFile(commandLine, config)); + } + }); + + profile.ifPresent( + p -> providers.add(TomlConfigurationDefaultProvider.fromInputStream(commandLine, p))); + return new CascadingDefaultProvider(providers); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/util/ConfigFileFinder.java b/besu/src/main/java/org/hyperledger/besu/cli/util/ConfigFileFinder.java new file mode 100644 index 00000000000..346481740dc --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/util/ConfigFileFinder.java @@ -0,0 +1,103 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.util; + +import static org.hyperledger.besu.cli.DefaultCommandValues.CONFIG_FILE_OPTION_NAME; + +import java.io.File; +import java.util.Map; +import java.util.Optional; + +import picocli.CommandLine; + +/** + * Class for finding configuration files. This class extends the AbstractConfigurationFinder and + * provides methods for finding configuration files based on command line options and environment + * variables. + */ +public class ConfigFileFinder extends AbstractConfigurationFinder { + private static final String CONFIG_FILE_ENV_NAME = "BESU_CONFIG_FILE"; + + /** Default constructor. */ + public ConfigFileFinder() {} + + /** + * Returns the name of the configuration option. + * + * @return the name of the configuration option + */ + @Override + protected String getConfigOptionName() { + return CONFIG_FILE_OPTION_NAME; + } + + /** + * Returns the name of the environment variable for the configuration. + * + * @return the name of the environment variable for the configuration + */ + @Override + protected String getConfigEnvName() { + return CONFIG_FILE_ENV_NAME; + } + + /** + * Gets the configuration file from the command line option. + * + * @param parseResult the command line parse result + * @param commandLine the command line + * @return an Optional containing the configuration file, or an empty Optional if the + * configuration file was not specified in the command line option + */ + @Override + public Optional getFromOption( + final CommandLine.ParseResult parseResult, final CommandLine commandLine) { + final CommandLine.Model.OptionSpec configFileOption = + parseResult.matchedOption(CONFIG_FILE_OPTION_NAME); + try { + File file = configFileOption.getter().get(); + if (!file.exists()) { + throw new CommandLine.ParameterException( + commandLine, + String.format("Unable to read TOML configuration, file not found: %s", file)); + } + return Optional.of(file); + } catch (final Exception e) { + throw new CommandLine.ParameterException(commandLine, e.getMessage(), e); + } + } + + /** + * Gets the configuration file from the environment variable. + * + * @param environment the environment variables + * @param commandLine the command line + * @return an Optional containing the configuration file, or an empty Optional if the + * configuration file was not specified in the environment variable + */ + @Override + public Optional getFromEnvironment( + final Map environment, final CommandLine commandLine) { + final File toml = new File(environment.get(CONFIG_FILE_ENV_NAME)); + if (!toml.exists()) { + throw new CommandLine.ParameterException( + commandLine, + String.format( + "TOML file %s specified in environment variable %s not found", + CONFIG_FILE_ENV_NAME, environment.get(CONFIG_FILE_ENV_NAME))); + } + return Optional.of(toml); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/util/ConfigOptionSearchAndRunHandler.java b/besu/src/main/java/org/hyperledger/besu/cli/util/ConfigOptionSearchAndRunHandler.java deleted file mode 100644 index 17cf2e649c9..00000000000 --- a/besu/src/main/java/org/hyperledger/besu/cli/util/ConfigOptionSearchAndRunHandler.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.cli.util; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import com.google.common.annotations.VisibleForTesting; -import picocli.CommandLine; -import picocli.CommandLine.IDefaultValueProvider; -import picocli.CommandLine.IExecutionStrategy; -import picocli.CommandLine.IParameterExceptionHandler; -import picocli.CommandLine.Model.OptionSpec; -import picocli.CommandLine.ParameterException; -import picocli.CommandLine.ParseResult; - -/** Custom Config option search and run handler. */ -public class ConfigOptionSearchAndRunHandler extends CommandLine.RunLast { - private final IExecutionStrategy resultHandler; - private final IParameterExceptionHandler parameterExceptionHandler; - private final Map environment; - - /** - * Instantiates a new Config option search and run handler. - * - * @param resultHandler the result handler - * @param parameterExceptionHandler the parameter exception handler - * @param environment the environment variables map - */ - public ConfigOptionSearchAndRunHandler( - final IExecutionStrategy resultHandler, - final IParameterExceptionHandler parameterExceptionHandler, - final Map environment) { - this.resultHandler = resultHandler; - this.parameterExceptionHandler = parameterExceptionHandler; - this.environment = environment; - } - - @Override - public List handle(final ParseResult parseResult) throws ParameterException { - final CommandLine commandLine = parseResult.commandSpec().commandLine(); - final Optional configFile = findConfigFile(parseResult, commandLine); - validatePrivacyOptions(parseResult, commandLine); - commandLine.setDefaultValueProvider(createDefaultValueProvider(commandLine, configFile)); - commandLine.setExecutionStrategy(resultHandler); - commandLine.setParameterExceptionHandler(parameterExceptionHandler); - commandLine.execute(parseResult.originalArgs().toArray(new String[0])); - - return new ArrayList<>(); - } - - private void validatePrivacyOptions( - final ParseResult parseResult, final CommandLine commandLine) { - if (parseResult.hasMatchedOption("--privacy-onchain-groups-enabled") - && parseResult.hasMatchedOption("--privacy-flexible-groups-enabled")) { - throw new ParameterException( - commandLine, - "The `--privacy-onchain-groups-enabled` option is deprecated and you should only use `--privacy-flexible-groups-enabled`"); - } - } - - private Optional findConfigFile( - final ParseResult parseResult, final CommandLine commandLine) { - if (parseResult.hasMatchedOption("--config-file") - && environment.containsKey("BESU_CONFIG_FILE")) { - throw new ParameterException( - commandLine, - String.format( - "TOML file specified using BESU_CONFIG_FILE=%s and --config-file %s", - environment.get("BESU_CONFIG_FILE"), - parseResult.matchedOption("--config-file").stringValues())); - } else if (parseResult.hasMatchedOption("--config-file")) { - final OptionSpec configFileOption = parseResult.matchedOption("--config-file"); - try { - return Optional.of(configFileOption.getter().get()); - } catch (final Exception e) { - throw new ParameterException(commandLine, e.getMessage(), e); - } - } else if (environment.containsKey("BESU_CONFIG_FILE")) { - final File toml = new File(environment.get("BESU_CONFIG_FILE")); - if (!toml.exists()) { - throw new ParameterException( - commandLine, - String.format( - "TOML file %s specified in environment variable BESU_CONFIG_FILE not found", - environment.get("BESU_CONFIG_FILE"))); - } - return Optional.of(toml); - } - - return Optional.empty(); - } - - /** - * Create default value provider default value provider. - * - * @param commandLine the command line - * @param configFile the config file - * @return the default value provider - */ - @VisibleForTesting - IDefaultValueProvider createDefaultValueProvider( - final CommandLine commandLine, final Optional configFile) { - if (configFile.isPresent()) { - return new CascadingDefaultProvider( - new EnvironmentVariableDefaultProvider(environment), - new TomlConfigFileDefaultProvider(commandLine, configFile.get())); - } else { - return new EnvironmentVariableDefaultProvider(environment); - } - } - - @Override - public ConfigOptionSearchAndRunHandler self() { - return this; - } -} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/util/ProfileFinder.java b/besu/src/main/java/org/hyperledger/besu/cli/util/ProfileFinder.java new file mode 100644 index 00000000000..ba35f6b69db --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/util/ProfileFinder.java @@ -0,0 +1,151 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.util; + +import static org.hyperledger.besu.cli.DefaultCommandValues.PROFILE_OPTION_NAME; + +import org.hyperledger.besu.cli.config.InternalProfileName; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import picocli.CommandLine; + +/** + * Class for finding profile configurations. This class extends the AbstractConfigurationFinder and + * provides methods for finding profile configurations based on command line options and environment + * variables. Each profile corresponds to a TOML configuration file that contains settings for + * various options. The profile to use can be specified with the '--profile' command line option or + * the 'BESU_PROFILE' environment variable. + */ +public class ProfileFinder extends AbstractConfigurationFinder { + private static final String PROFILE_ENV_NAME = "BESU_PROFILE"; + + /** Default Constructor. */ + public ProfileFinder() {} + + @Override + protected String getConfigOptionName() { + return PROFILE_OPTION_NAME; + } + + @Override + protected String getConfigEnvName() { + return PROFILE_ENV_NAME; + } + + @Override + public Optional getFromOption( + final CommandLine.ParseResult parseResult, final CommandLine commandLine) { + final String profileName; + try { + profileName = parseResult.matchedOption(PROFILE_OPTION_NAME).getter().get(); + } catch (final Exception e) { + throw new CommandLine.ParameterException( + commandLine, "Unexpected error in obtaining value of --profile", e); + } + return getProfile(profileName, commandLine); + } + + @Override + public Optional getFromEnvironment( + final Map environment, final CommandLine commandLine) { + return getProfile(environment.get(PROFILE_ENV_NAME), commandLine); + } + + private static Optional getProfile( + final String profileName, final CommandLine commandLine) { + final Optional internalProfileConfigPath = + InternalProfileName.valueOfIgnoreCase(profileName).map(InternalProfileName::getConfigFile); + if (internalProfileConfigPath.isPresent()) { + return Optional.of(getTomlFileFromClasspath(internalProfileConfigPath.get())); + } else { + final Path externalProfileFile = defaultProfilesDir().resolve(profileName + ".toml"); + if (Files.exists(externalProfileFile)) { + try { + return Optional.of(Files.newInputStream(externalProfileFile)); + } catch (IOException e) { + throw new CommandLine.ParameterException( + commandLine, "Error reading external profile: " + profileName); + } + } else { + throw new CommandLine.ParameterException( + commandLine, "Unable to load external profile: " + profileName); + } + } + } + + private static InputStream getTomlFileFromClasspath(final String profileConfigFile) { + InputStream resourceUrl = + ProfileFinder.class.getClassLoader().getResourceAsStream(profileConfigFile); + // this is not meant to happen, because for each InternalProfileName there is a corresponding + // TOML file in resources + if (resourceUrl == null) { + throw new IllegalStateException( + String.format("Internal Profile TOML %s not found", profileConfigFile)); + } + return resourceUrl; + } + + /** + * Returns the external profile names which are file names without extension in the default + * profiles directory. + * + * @return Set of external profile names + */ + public static Set getExternalProfileNames() { + final Path profilesDir = defaultProfilesDir(); + if (!Files.exists(profilesDir)) { + return Set.of(); + } + + try (Stream pathStream = Files.list(profilesDir)) { + return pathStream + .filter(Files::isRegularFile) + .filter(path -> path.toString().endsWith(".toml")) + .map( + path -> + path.getFileName() + .toString() + .substring(0, path.getFileName().toString().length() - 5)) + .collect(Collectors.toSet()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** + * Return default profiles directory location + * + * @return Path to default profiles directory + */ + private static Path defaultProfilesDir() { + final String profilesDir = System.getProperty("besu.profiles.dir"); + if (profilesDir == null) { + return Paths.get(System.getProperty("besu.home", "."), "profiles"); + } else { + return Paths.get(profilesDir); + } + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/util/TomlConfigFileDefaultProvider.java b/besu/src/main/java/org/hyperledger/besu/cli/util/TomlConfigFileDefaultProvider.java deleted file mode 100644 index c765b7f5dfc..00000000000 --- a/besu/src/main/java/org/hyperledger/besu/cli/util/TomlConfigFileDefaultProvider.java +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.cli.util; - -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.util.number.Fraction; -import org.hyperledger.besu.util.number.Percentage; - -import java.io.File; -import java.io.IOException; -import java.math.BigInteger; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -import org.apache.tuweni.toml.Toml; -import org.apache.tuweni.toml.TomlArray; -import org.apache.tuweni.toml.TomlParseError; -import org.apache.tuweni.toml.TomlParseResult; -import picocli.CommandLine; -import picocli.CommandLine.IDefaultValueProvider; -import picocli.CommandLine.Model.ArgSpec; -import picocli.CommandLine.Model.CommandSpec; -import picocli.CommandLine.Model.OptionSpec; -import picocli.CommandLine.ParameterException; - -/** The Toml config file default value provider used by PicoCli. */ -public class TomlConfigFileDefaultProvider implements IDefaultValueProvider { - - private final CommandLine commandLine; - private final File configFile; - private TomlParseResult result; - - /** - * Instantiates a new Toml config file default value provider. - * - * @param commandLine the command line - * @param configFile the config file - */ - public TomlConfigFileDefaultProvider(final CommandLine commandLine, final File configFile) { - this.commandLine = commandLine; - this.configFile = configFile; - } - - @Override - public String defaultValue(final ArgSpec argSpec) { - loadConfigurationFromFile(); - - // only options can be used in config because a name is needed for the key - // so we skip default for positional params - return argSpec.isOption() ? getConfigurationValue(((OptionSpec) argSpec)) : null; - } - - private String getConfigurationValue(final OptionSpec optionSpec) { - // NOTE: This temporary fix is necessary to make certain options be treated as a multi-value. - // This can be done automatically by picocli if the object implements Collection. - final boolean isArray = - getKeyName(optionSpec).map(keyName -> result.isArray(keyName)).orElse(false); - final String defaultValue; - - // Convert config values to the right string representation for default string value - if (optionSpec.type().equals(Boolean.class) || optionSpec.type().equals(boolean.class)) { - defaultValue = getBooleanEntryAsString(optionSpec); - } else if (optionSpec.isMultiValue() || isArray) { - defaultValue = getListEntryAsString(optionSpec); - } else if (optionSpec.type().equals(Integer.class) || optionSpec.type().equals(int.class)) { - defaultValue = getNumericEntryAsString(optionSpec); - } else if (optionSpec.type().equals(Long.class) || optionSpec.type().equals(long.class)) { - defaultValue = getNumericEntryAsString(optionSpec); - } else if (optionSpec.type().equals(Wei.class)) { - defaultValue = getNumericEntryAsString(optionSpec); - } else if (optionSpec.type().equals(BigInteger.class)) { - defaultValue = getNumericEntryAsString(optionSpec); - } else if (optionSpec.type().equals(Double.class) || optionSpec.type().equals(double.class)) { - defaultValue = getNumericEntryAsString(optionSpec); - } else if (optionSpec.type().equals(Float.class) || optionSpec.type().equals(float.class)) { - defaultValue = getNumericEntryAsString(optionSpec); - } else if (optionSpec.type().equals(Percentage.class)) { - defaultValue = getNumericEntryAsString(optionSpec); - } else if (optionSpec.type().equals(Fraction.class)) { - defaultValue = getNumericEntryAsString(optionSpec); - } else { // else will be treated as String - defaultValue = getEntryAsString(optionSpec); - } - return defaultValue; - } - - private String getEntryAsString(final OptionSpec spec) { - // returns the string value of the config line corresponding to the option in toml file - // or null if not present in the config - return getKeyName(spec).map(result::getString).orElse(null); - } - - private Optional getKeyName(final OptionSpec spec) { - // If any of the names of the option are used as key in the toml results - // then returns the value of first one. - Optional keyName = - Arrays.stream(spec.names()) - // remove leading dashes on option name as we can have "--" or "-" options - .map(name -> name.replaceFirst("^-+", "")) - .filter(result::contains) - .findFirst(); - - if (keyName.isEmpty()) { - // If the base key name doesn't exist in the file it may be under a TOML table heading - // e.g. TxPool.tx-pool-max-size - keyName = getDottedKeyName(spec); - } - - return keyName; - } - - /* - For all spec names, look to see if any of the TOML keyPathSet entries contain - the name. A key path set might look like ["TxPool", "tx-max-pool-size"] where - "TxPool" is the TOML table heading (which we ignore) and "tx-max-pool-size" is - the name of the option being requested. For a request for "tx-max-pool-size" this - function will return "TxPool.tx-max-pool-size" which can then be used directly - as a query on the TOML result structure. - */ - private Optional getDottedKeyName(final OptionSpec spec) { - List foundNames = new ArrayList<>(); - - Arrays.stream(spec.names()) - .forEach( - nextSpecName -> { - String specName = - result.keyPathSet().stream() - .filter(option -> option.contains(nextSpecName.replaceFirst("^-+", ""))) - .findFirst() - .orElse(new ArrayList<>()) - .stream() - .collect(Collectors.joining(".")); - if (specName.length() > 0) { - foundNames.add(specName); - } - }); - - return foundNames.stream().findFirst(); - } - - private String getListEntryAsString(final OptionSpec spec) { - // returns the string representation of the array value of the config line in CLI format - // corresponding to the option in toml file - // or null if not present in the config - return decodeTomlArray( - getKeyName(spec).map(result::getArray).map(tomlArray -> tomlArray.toList()).orElse(null)); - } - - private String decodeTomlArray(final List tomlArrayElements) { - if (tomlArrayElements == null) return null; - return tomlArrayElements.stream() - .map( - tomlObject -> { - if (tomlObject instanceof TomlArray) { - return "[".concat(decodeTomlArray(((TomlArray) tomlObject).toList())).concat("]"); - } else { - return tomlObject.toString(); - } - }) - .collect(Collectors.joining(",")); - } - - private String getBooleanEntryAsString(final OptionSpec spec) { - // return the string representation of the boolean value corresponding to the option in toml - // file - // or null if not present in the config - return getKeyName(spec).map(result::getBoolean).map(Object::toString).orElse(null); - } - - private String getNumericEntryAsString(final OptionSpec spec) { - // return the string representation of the numeric value corresponding to the option in toml - // file - this works for integer, double, and float - // or null if not present in the config - - return getKeyName(spec).map(result::get).map(Object::toString).orElse(null); - } - - private void checkConfigurationValidity() { - if (result == null || result.isEmpty()) - throw new ParameterException( - commandLine, String.format("Unable to read TOML configuration file %s", configFile)); - } - - /** Load configuration from file. */ - public void loadConfigurationFromFile() { - - if (result == null) { - try { - final TomlParseResult result = Toml.parse(configFile.toPath()); - - if (result.hasErrors()) { - final String errors = - result.errors().stream() - .map(TomlParseError::toString) - .collect(Collectors.joining("%n")); - - throw new ParameterException( - commandLine, String.format("Invalid TOML configuration: %s", errors)); - } - - checkUnknownOptions(result); - - this.result = result; - - } catch (final IOException e) { - throw new ParameterException( - commandLine, "Unable to read TOML configuration, file not found."); - } - } - - checkConfigurationValidity(); - } - - private void checkUnknownOptions(final TomlParseResult result) { - final CommandSpec commandSpec = commandLine.getCommandSpec(); - - // Besu ignores TOML table headings (e.g. [TxPool]) so we use keyPathSet() and take the - // last element in each one. For a TOML parameter that's not defined inside a table, the lists - // returned in keyPathSet() will contain a single entry - the config parameter itself. For a - // TOML - // entry that is in a table the list will contain N entries, the last one being the config - // parameter itself. - final Set optionsWithoutTables = new HashSet(); - result.keyPathSet().stream() - .forEach( - strings -> { - optionsWithoutTables.add(strings.get(strings.size() - 1)); - }); - - // Once we've stripped TOML table headings from the lists, we can check that the remaining - // options are valid - final Set unknownOptionsList = - optionsWithoutTables.stream() - .filter(option -> !commandSpec.optionsMap().containsKey("--" + option)) - .collect(Collectors.toSet()); - - if (!unknownOptionsList.isEmpty()) { - final String options = unknownOptionsList.size() > 1 ? "options" : "option"; - final String csvUnknownOptions = - unknownOptionsList.stream().collect(Collectors.joining(", ")); - throw new ParameterException( - commandLine, - String.format("Unknown %s in TOML configuration file: %s", options, csvUnknownOptions)); - } - } -} diff --git a/besu/src/main/java/org/hyperledger/besu/cli/util/TomlConfigurationDefaultProvider.java b/besu/src/main/java/org/hyperledger/besu/cli/util/TomlConfigurationDefaultProvider.java new file mode 100644 index 00000000000..ca76b40b686 --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/cli/util/TomlConfigurationDefaultProvider.java @@ -0,0 +1,299 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.util; + +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.util.number.Fraction; +import org.hyperledger.besu.util.number.Percentage; +import org.hyperledger.besu.util.number.PositiveNumber; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.tuweni.toml.Toml; +import org.apache.tuweni.toml.TomlArray; +import org.apache.tuweni.toml.TomlParseError; +import org.apache.tuweni.toml.TomlParseResult; +import picocli.CommandLine; +import picocli.CommandLine.IDefaultValueProvider; +import picocli.CommandLine.Model.ArgSpec; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Model.OptionSpec; +import picocli.CommandLine.ParameterException; + +/** The Toml config file default value provider used by PicoCli. */ +public class TomlConfigurationDefaultProvider implements IDefaultValueProvider { + + private final CommandLine commandLine; + private final InputStream configurationInputStream; + private TomlParseResult result; + private boolean isUnknownOptionsChecked; + + /** + * Instantiates a new Toml config file default value provider. + * + * @param commandLine the command line + * @param configurationInputStream the input stream + */ + private TomlConfigurationDefaultProvider( + final CommandLine commandLine, final InputStream configurationInputStream) { + this.commandLine = commandLine; + this.configurationInputStream = configurationInputStream; + } + + /** + * Creates a new TomlConfigurationDefaultProvider from a file. + * + * @param commandLine the command line + * @param configFile the configuration file + * @return a new TomlConfigurationDefaultProvider + * @throws ParameterException if the configuration file is not found + */ + public static TomlConfigurationDefaultProvider fromFile( + final CommandLine commandLine, final File configFile) { + try { + return new TomlConfigurationDefaultProvider(commandLine, new FileInputStream(configFile)); + } catch (final FileNotFoundException e) { + throw new ParameterException( + commandLine, "Unable to read TOML configuration, file not found."); + } + } + + /** + * Creates a new TomlConfigurationDefaultProvider from an input stream. + * + * @param commandLine the command line + * @param inputStream the input stream + * @return a new TomlConfigurationDefaultProvider + */ + public static TomlConfigurationDefaultProvider fromInputStream( + final CommandLine commandLine, final InputStream inputStream) { + return new TomlConfigurationDefaultProvider(commandLine, inputStream); + } + + @Override + public String defaultValue(final ArgSpec argSpec) { + loadConfigurationIfNotLoaded(); + + // only options can be used in config because a name is needed for the key + // so we skip default for positional params + return argSpec.isOption() ? getConfigurationValue(((OptionSpec) argSpec)) : null; + } + + private String getConfigurationValue(final OptionSpec optionSpec) { + // NOTE: This temporary fix is necessary to make certain options be treated as a multi-value. + // This can be done automatically by picocli if the object implements Collection. + final boolean isArray = getKeyName(optionSpec).map(result::isArray).orElse(false); + + if (optionSpec.type().equals(Boolean.class) || optionSpec.type().equals(boolean.class)) { + return getBooleanEntryAsString(optionSpec); + } else if (optionSpec.isMultiValue() || isArray) { + return getListEntryAsString(optionSpec); + } else if (isNumericType(optionSpec.type())) { + return getNumericEntryAsString(optionSpec); + } else { // else will be treated as String + return getEntryAsString(optionSpec); + } + } + + private boolean isNumericType(final Class type) { + return type.equals(Integer.class) + || type.equals(int.class) + || type.equals(Long.class) + || type.equals(long.class) + || type.equals(Wei.class) + || type.equals(BigInteger.class) + || type.equals(Double.class) + || type.equals(double.class) + || type.equals(Float.class) + || type.equals(float.class) + || type.equals(Percentage.class) + || type.equals(Fraction.class) + || type.equals(PositiveNumber.class); + } + + private String getEntryAsString(final OptionSpec spec) { + // returns the string value of the config line corresponding to the option in toml file + // or null if not present in the config + return getKeyName(spec).map(result::getString).orElse(null); + } + + private Optional getKeyName(final OptionSpec spec) { + // If any of the names of the option are used as key in the toml results + // then returns the value of first one. + Optional keyName = + Arrays.stream(spec.names()) + // remove leading dashes on option name as we can have "--" or "-" options + .map(name -> name.replaceFirst("^-+", "")) + .filter(result::contains) + .findFirst(); + + if (keyName.isEmpty()) { + // If the base key name doesn't exist in the file it may be under a TOML table heading + // e.g. TxPool.tx-pool-max-size + keyName = getDottedKeyName(spec); + } + + return keyName; + } + + /* + For all spec names, look to see if any of the TOML keyPathSet entries contain + the name. A key path set might look like ["TxPool", "tx-max-pool-size"] where + "TxPool" is the TOML table heading (which we ignore) and "tx-max-pool-size" is + the name of the option being requested. For a request for "tx-max-pool-size" this + function will return "TxPool.tx-max-pool-size" which can then be used directly + as a query on the TOML result structure. + */ + private Optional getDottedKeyName(final OptionSpec spec) { + List foundNames = new ArrayList<>(); + + Arrays.stream(spec.names()) + .forEach( + nextSpecName -> { + String specName = + result.keyPathSet().stream() + .filter(option -> option.contains(nextSpecName.replaceFirst("^-+", ""))) + .findFirst() + .orElse(new ArrayList<>()) + .stream() + .collect(Collectors.joining(".")); + if (specName.length() > 0) { + foundNames.add(specName); + } + }); + + return foundNames.stream().findFirst(); + } + + private String getListEntryAsString(final OptionSpec spec) { + // returns the string representation of the array value of the config line in CLI format + // corresponding to the option in toml file + // or null if not present in the config + return decodeTomlArray( + getKeyName(spec).map(result::getArray).map(tomlArray -> tomlArray.toList()).orElse(null)); + } + + private String decodeTomlArray(final List tomlArrayElements) { + if (tomlArrayElements == null) return null; + return tomlArrayElements.stream() + .map( + tomlObject -> { + if (tomlObject instanceof TomlArray) { + return "[".concat(decodeTomlArray(((TomlArray) tomlObject).toList())).concat("]"); + } else { + return tomlObject.toString(); + } + }) + .collect(Collectors.joining(",")); + } + + private String getBooleanEntryAsString(final OptionSpec spec) { + // return the string representation of the boolean value corresponding to the option in toml + // file + // or null if not present in the config + return getKeyName(spec).map(result::getBoolean).map(Object::toString).orElse(null); + } + + private String getNumericEntryAsString(final OptionSpec spec) { + // return the string representation of the numeric value corresponding to the option in toml + // file - this works for integer, double, and float + // or null if not present in the config + + return getKeyName(spec).map(result::get).map(Object::toString).orElse(null); + } + + private void checkConfigurationValidity() { + if (result == null || result.isEmpty()) { + throw new ParameterException( + commandLine, "Unable to read from empty TOML configuration file."); + } + + if (!isUnknownOptionsChecked && !commandLine.isUnmatchedArgumentsAllowed()) { + checkUnknownOptions(result); + isUnknownOptionsChecked = true; + } + } + + /** Load configuration from file. */ + public void loadConfigurationIfNotLoaded() { + if (result == null) { + try { + final TomlParseResult result = Toml.parse(configurationInputStream); + + if (result.hasErrors()) { + final String errors = + result.errors().stream() + .map(TomlParseError::toString) + .collect(Collectors.joining("%n")); + + throw new ParameterException( + commandLine, String.format("Invalid TOML configuration: %s", errors)); + } + + this.result = result; + + } catch (final IOException e) { + throw new ParameterException( + commandLine, "Unable to read TOML configuration, file not found."); + } + } + checkConfigurationValidity(); + } + + private void checkUnknownOptions(final TomlParseResult result) { + final CommandSpec commandSpec = commandLine.getCommandSpec(); + + // Besu ignores TOML table headings (e.g. [TxPool]) so we use keyPathSet() and take the + // last element in each one. For a TOML parameter that's not defined inside a table, the lists + // returned in keyPathSet() will contain a single entry - the config parameter itself. For a + // TOML + // entry that is in a table the list will contain N entries, the last one being the config + // parameter itself. + final Set optionsWithoutTables = new HashSet(); + result.keyPathSet().stream() + .forEach( + strings -> { + optionsWithoutTables.add(strings.get(strings.size() - 1)); + }); + + // Once we've stripped TOML table headings from the lists, we can check that the remaining + // options are valid + final Set unknownOptionsList = + optionsWithoutTables.stream() + .filter(option -> !commandSpec.optionsMap().containsKey("--" + option)) + .collect(Collectors.toSet()); + + if (!unknownOptionsList.isEmpty()) { + final String csvUnknownOptions = String.join(", ", unknownOptionsList); + throw new ParameterException( + commandLine, + String.format( + "Unknown option%s in TOML configuration file: %s", + unknownOptionsList.size() > 1 ? "s" : "", csvUnknownOptions)); + } + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/components/BesuCommandModule.java b/besu/src/main/java/org/hyperledger/besu/components/BesuCommandModule.java index b418153521d..86aa8c75949 100644 --- a/besu/src/main/java/org/hyperledger/besu/components/BesuCommandModule.java +++ b/besu/src/main/java/org/hyperledger/besu/components/BesuCommandModule.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.components; import org.hyperledger.besu.Besu; @@ -23,8 +22,8 @@ import org.hyperledger.besu.cli.BesuCommand; import org.hyperledger.besu.controller.BesuController; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; +import org.hyperledger.besu.services.BesuPluginContextImpl; -import java.util.Optional; import javax.inject.Named; import javax.inject.Singleton; @@ -38,20 +37,24 @@ */ @Module public class BesuCommandModule { + /** Default constructor. */ + public BesuCommandModule() {} @Provides @Singleton - BesuCommand provideBesuCommand(final BesuComponent besuComponent) { + BesuCommand provideBesuCommand( + final BesuPluginContextImpl pluginContext, + final @Named("besuCommandLogger") Logger commandLogger) { final BesuCommand besuCommand = new BesuCommand( - besuComponent, RlpBlockImporter::new, JsonBlockImporter::new, RlpBlockExporter::new, new RunnerBuilder(), new BesuController.Builder(), - Optional.ofNullable(besuComponent.getBesuPluginContext()).orElse(null), - System.getenv()); + pluginContext, + System.getenv(), + commandLogger); besuCommand.toCommandLine(); return besuCommand; } diff --git a/besu/src/main/java/org/hyperledger/besu/components/BesuComponent.java b/besu/src/main/java/org/hyperledger/besu/components/BesuComponent.java index 0edc5f15e4b..9f810a6dc6e 100644 --- a/besu/src/main/java/org/hyperledger/besu/components/BesuComponent.java +++ b/besu/src/main/java/org/hyperledger/besu/components/BesuComponent.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,16 +11,14 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.components; import org.hyperledger.besu.cli.BesuCommand; -import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; -import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoaderModule; import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.BlobCacheModule; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.BonsaiCachedMerkleTrieLoader; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.BonsaiCachedMerkleTrieLoaderModule; import org.hyperledger.besu.metrics.MetricsSystemModule; import org.hyperledger.besu.metrics.ObservableMetricsSystem; import org.hyperledger.besu.services.BesuPluginContextImpl; @@ -37,7 +35,7 @@ modules = { BesuCommandModule.class, MetricsSystemModule.class, - CachedMerkleTrieLoaderModule.class, + BonsaiCachedMerkleTrieLoaderModule.class, BesuPluginContextModule.class, BlobCacheModule.class }) @@ -55,7 +53,7 @@ public interface BesuComponent { * * @return CachedMerkleTrieLoader */ - CachedMerkleTrieLoader getCachedMerkleTrieLoader(); + BonsaiCachedMerkleTrieLoader getCachedMerkleTrieLoader(); /** * a metrics system that is observable by a Prometheus or OTEL metrics collection subsystem @@ -77,7 +75,6 @@ public interface BesuComponent { * * @return BesuPluginContextImpl */ - @Named("besuPluginContext") BesuPluginContextImpl getBesuPluginContext(); /** diff --git a/besu/src/main/java/org/hyperledger/besu/components/BesuPluginContextModule.java b/besu/src/main/java/org/hyperledger/besu/components/BesuPluginContextModule.java index ddbb4c2a6e0..d62ab702244 100644 --- a/besu/src/main/java/org/hyperledger/besu/components/BesuPluginContextModule.java +++ b/besu/src/main/java/org/hyperledger/besu/components/BesuPluginContextModule.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,13 +11,13 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.components; +import org.hyperledger.besu.plugin.services.BesuConfiguration; +import org.hyperledger.besu.services.BesuConfigurationImpl; import org.hyperledger.besu.services.BesuPluginContextImpl; -import javax.inject.Named; import javax.inject.Singleton; import dagger.Module; @@ -27,15 +27,26 @@ @Module public class BesuPluginContextModule { + /** Default constructor. */ + public BesuPluginContextModule() {} + + @Provides + @Singleton + BesuConfigurationImpl provideBesuPluginConfig() { + return new BesuConfigurationImpl(); + } + /** * Creates a BesuPluginContextImpl, used for plugin service discovery. * + * @param pluginConfig the BesuConfigurationImpl * @return the BesuPluginContext */ @Provides - @Named("besuPluginContext") @Singleton - public BesuPluginContextImpl provideBesuPluginContext() { - return new BesuPluginContextImpl(); + public BesuPluginContextImpl provideBesuPluginContext(final BesuConfigurationImpl pluginConfig) { + BesuPluginContextImpl retval = new BesuPluginContextImpl(); + retval.addService(BesuConfiguration.class, pluginConfig); + return retval; } } diff --git a/besu/src/main/java/org/hyperledger/besu/controller/BesuController.java b/besu/src/main/java/org/hyperledger/besu/controller/BesuController.java index bb46003b24b..fb99318905a 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuController.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuController.java @@ -14,8 +14,6 @@ */ package org.hyperledger.besu.controller; -import static org.hyperledger.besu.ethereum.eth.sync.SyncMode.isCheckpointSync; - import org.hyperledger.besu.cli.config.EthNetworkConfig; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.config.GenesisConfigOptions; @@ -37,11 +35,11 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.p2p.config.SubProtocolConfiguration; import org.hyperledger.besu.ethereum.storage.StorageProvider; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import java.io.Closeable; import java.io.IOException; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -56,6 +54,7 @@ public class BesuController implements java.io.Closeable { /** The constant DATABASE_PATH. */ public static final String DATABASE_PATH = "database"; + /** The constant CACHE_PATH. */ public static final String CACHE_PATH = "caches"; @@ -77,6 +76,7 @@ public class BesuController implements java.io.Closeable { private final SyncState syncState; private final EthPeers ethPeers; private final StorageProvider storageProvider; + private final DataStorageConfiguration dataStorageConfiguration; /** * Instantiates a new Besu controller. @@ -96,6 +96,9 @@ public class BesuController implements java.io.Closeable { * @param nodeKey the node key * @param closeables the closeables * @param additionalPluginServices the additional plugin services + * @param ethPeers the eth peers + * @param storageProvider the storage provider + * @param dataStorageConfiguration the data storage configuration */ BesuController( final ProtocolSchedule protocolSchedule, @@ -114,7 +117,8 @@ public class BesuController implements java.io.Closeable { final List closeables, final PluginServiceFactory additionalPluginServices, final EthPeers ethPeers, - final StorageProvider storageProvider) { + final StorageProvider storageProvider, + final DataStorageConfiguration dataStorageConfiguration) { this.protocolSchedule = protocolSchedule; this.protocolContext = protocolContext; this.ethProtocolManager = ethProtocolManager; @@ -132,6 +136,7 @@ public class BesuController implements java.io.Closeable { this.additionalPluginServices = additionalPluginServices; this.ethPeers = ethPeers; this.storageProvider = storageProvider; + this.dataStorageConfiguration = dataStorageConfiguration; } /** @@ -293,57 +298,47 @@ public PluginServiceFactory getAdditionalPluginServices() { return additionalPluginServices; } + /** + * Gets data storage configuration. + * + * @return the data storage configuration + */ + public DataStorageConfiguration getDataStorageConfiguration() { + return dataStorageConfiguration; + } + /** The type Builder. */ public static class Builder { + /** Instantiates a new Builder. */ + public Builder() {} /** * From eth network config besu controller builder. * * @param ethNetworkConfig the eth network config - * @param genesisConfigOverrides the genesis config overrides * @param syncMode The sync mode * @return the besu controller builder */ public BesuControllerBuilder fromEthNetworkConfig( - final EthNetworkConfig ethNetworkConfig, - final Map genesisConfigOverrides, - final SyncMode syncMode) { - return fromGenesisConfig( - GenesisConfigFile.fromConfig(ethNetworkConfig.getGenesisConfig()), - genesisConfigOverrides, - syncMode) - .networkId(ethNetworkConfig.getNetworkId()); - } - - /** - * From genesis config besu controller builder. - * - * @param genesisConfig the genesis config - * @param syncMode The Sync Mode - * @return the besu controller builder - */ - public BesuControllerBuilder fromGenesisConfig( - final GenesisConfigFile genesisConfig, final SyncMode syncMode) { - return fromGenesisConfig(genesisConfig, Collections.emptyMap(), syncMode); + final EthNetworkConfig ethNetworkConfig, final SyncMode syncMode) { + return fromGenesisFile(ethNetworkConfig.genesisConfigFile(), syncMode) + .networkId(ethNetworkConfig.networkId()); } /** * From genesis config besu controller builder. * - * @param genesisConfig the genesis config - * @param genesisConfigOverrides the genesis config overrides + * @param genesisConfigFile the genesis config file + * @param syncMode the sync mode * @return the besu controller builder */ - BesuControllerBuilder fromGenesisConfig( - final GenesisConfigFile genesisConfig, - final Map genesisConfigOverrides, - final SyncMode syncMode) { - final GenesisConfigOptions configOptions = - genesisConfig.getConfigOptions(genesisConfigOverrides); + public BesuControllerBuilder fromGenesisFile( + final GenesisConfigFile genesisConfigFile, final SyncMode syncMode) { final BesuControllerBuilder builder; + final var configOptions = genesisConfigFile.getConfigOptions(); if (configOptions.isConsensusMigration()) { - return createConsensusScheduleBesuControllerBuilder(genesisConfig, configOptions); + return createConsensusScheduleBesuControllerBuilder(genesisConfigFile); } if (configOptions.getPowAlgorithm() != PowAlgorithm.UNSUPPORTED) { @@ -364,22 +359,23 @@ BesuControllerBuilder fromGenesisConfig( // wrap with TransitionBesuControllerBuilder if we have a terminal total difficulty: if (configOptions.getTerminalTotalDifficulty().isPresent()) { // Enable start with vanilla MergeBesuControllerBuilder for PoS checkpoint block - if (isCheckpointSync(syncMode) && isCheckpointPoSBlock(configOptions)) { - return new MergeBesuControllerBuilder().genesisConfigFile(genesisConfig); + if (syncMode == SyncMode.CHECKPOINT && isCheckpointPoSBlock(configOptions)) { + return new MergeBesuControllerBuilder().genesisConfigFile(genesisConfigFile); } else { // TODO this should be changed to vanilla MergeBesuControllerBuilder and the Transition* // series of classes removed after we successfully transition to PoS // https://github.com/hyperledger/besu/issues/2897 return new TransitionBesuControllerBuilder(builder, new MergeBesuControllerBuilder()) - .genesisConfigFile(genesisConfig); + .genesisConfigFile(genesisConfigFile); } - } else return builder.genesisConfigFile(genesisConfig); + } else return builder.genesisConfigFile(genesisConfigFile); } private BesuControllerBuilder createConsensusScheduleBesuControllerBuilder( - final GenesisConfigFile genesisConfig, final GenesisConfigOptions configOptions) { + final GenesisConfigFile genesisConfigFile) { final Map besuControllerBuilderSchedule = new HashMap<>(); + final var configOptions = genesisConfigFile.getConfigOptions(); final BesuControllerBuilder originalControllerBuilder; if (configOptions.isIbft2()) { @@ -398,7 +394,7 @@ private BesuControllerBuilder createConsensusScheduleBesuControllerBuilder( besuControllerBuilderSchedule.put(qbftBlock, new QbftBesuControllerBuilder()); return new ConsensusScheduleBesuControllerBuilder(besuControllerBuilderSchedule) - .genesisConfigFile(genesisConfig); + .genesisConfigFile(genesisConfigFile); } private Long readQbftStartBlockConfig(final QbftConfigOptions qbftConfigOptions) { diff --git a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java index 643aed546cb..6bb3fb117c1 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BesuControllerBuilder.java @@ -22,7 +22,7 @@ import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.consensus.merge.MergeContext; import org.hyperledger.besu.consensus.merge.UnverifiedForkchoiceSupplier; -import org.hyperledger.besu.consensus.qbft.pki.PkiBlockCreationConfiguration; +import org.hyperledger.besu.consensus.qbft.BFTPivotSelectorFromPeers; import org.hyperledger.besu.cryptoservices.NodeKey; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ConsensusContext; @@ -31,10 +31,7 @@ import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.methods.JsonRpcMethods; import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator; -import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateProvider; -import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogPruner; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.BlockchainStorage; import org.hyperledger.besu.ethereum.chain.ChainDataPruner; @@ -44,10 +41,10 @@ import org.hyperledger.besu.ethereum.chain.GenesisState; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.chain.VariablesStorage; +import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; -import org.hyperledger.besu.ethereum.core.Synchronizer; import org.hyperledger.besu.ethereum.eth.EthProtocol; import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.SnapProtocol; @@ -78,27 +75,29 @@ import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolFactory; +import org.hyperledger.besu.ethereum.forkid.ForkIdManager; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration; import org.hyperledger.besu.ethereum.p2p.config.SubProtocolConfiguration; import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.BonsaiWorldStateProvider; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.BonsaiCachedMerkleTrieLoader; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.common.trielog.TrieLogManager; +import org.hyperledger.besu.ethereum.trie.diffbased.common.trielog.TrieLogPruner; +import org.hyperledger.besu.ethereum.trie.forest.ForestWorldStateArchive; import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; -import org.hyperledger.besu.ethereum.worldstate.DefaultWorldStateArchive; -import org.hyperledger.besu.ethereum.worldstate.MarkSweepPruner; -import org.hyperledger.besu.ethereum.worldstate.Pruner; -import org.hyperledger.besu.ethereum.worldstate.PrunerConfiguration; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.worldstate.WorldStatePreimageStorage; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.ObservableMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.plugin.services.permissioning.NodeMessagePermissioningProvider; -import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelectorFactory; -import org.hyperledger.besu.plugin.services.txvalidator.PluginTransactionValidatorFactory; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import java.io.Closeable; import java.math.BigInteger; @@ -107,6 +106,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.OptionalLong; @@ -119,79 +119,96 @@ public abstract class BesuControllerBuilder implements MiningParameterOverrides { private static final Logger LOG = LoggerFactory.getLogger(BesuControllerBuilder.class); - private GenesisConfigFile genesisConfig; - private Map genesisConfigOverrides = Collections.emptyMap(); + /** The genesis file */ + protected GenesisConfigFile genesisConfigFile; - /** The Config options supplier. */ - protected Supplier configOptionsSupplier = - () -> - Optional.ofNullable(genesisConfig) - .map(conf -> conf.getConfigOptions(genesisConfigOverrides)) - .orElseThrow(); + /** The genesis config options; */ + protected GenesisConfigOptions genesisConfigOptions; + + /** The is genesis state hash from data. */ + protected boolean genesisStateHashCacheEnabled; /** The Sync config. */ protected SynchronizerConfiguration syncConfig; + /** The Ethereum wire protocol configuration. */ protected EthProtocolConfiguration ethereumWireProtocolConfiguration; + /** The Transaction pool configuration. */ protected TransactionPoolConfiguration transactionPoolConfiguration; + /** The Network id. */ protected BigInteger networkId; + /** The Mining parameters. */ protected MiningParameters miningParameters; + /** The Metrics system. */ protected ObservableMetricsSystem metricsSystem; + /** The Privacy parameters. */ protected PrivacyParameters privacyParameters; - /** The Pki block creation configuration. */ - protected Optional pkiBlockCreationConfiguration = - Optional.empty(); + /** The Data directory. */ protected Path dataDirectory; + /** The Clock. */ protected Clock clock; + /** The Node key. */ protected NodeKey nodeKey; + /** The Is revert reason enabled. */ protected boolean isRevertReasonEnabled; + /** The Gas limit calculator. */ GasLimitCalculator gasLimitCalculator; + /** The Storage provider. */ protected StorageProvider storageProvider; - /** The Is pruning enabled. */ - protected boolean isPruningEnabled; - /** The Pruner configuration. */ - protected PrunerConfiguration prunerConfiguration; + /** The Required blocks. */ protected Map requiredBlocks = Collections.emptyMap(); + /** The Reorg logging threshold. */ protected long reorgLoggingThreshold; + /** The Data storage configuration. */ protected DataStorageConfiguration dataStorageConfiguration = DataStorageConfiguration.DEFAULT_CONFIG; + /** The Message permissioning providers. */ protected List messagePermissioningProviders = Collections.emptyList(); + /** The Evm configuration. */ protected EvmConfiguration evmConfiguration; + /** The Max peers. */ protected int maxPeers; - private int peerLowerBound; + /** Manages a cache of bad blocks globally */ + protected final BadBlockManager badBlockManager = new BadBlockManager(); + private int maxRemotelyInitiatedPeers; + /** The Chain pruner configuration. */ protected ChainPrunerConfiguration chainPrunerConfiguration = ChainPrunerConfiguration.DEFAULT; private NetworkingConfiguration networkingConfiguration; private Boolean randomPeerPriority; - private Optional transactionSelectorFactory = Optional.empty(); + /** the Dagger configured context that can provide dependencies */ protected Optional besuComponent = Optional.empty(); - private PluginTransactionValidatorFactory pluginTransactionValidatorFactory; - private int numberOfBlocksToCache = 0; + /** whether parallel transaction processing is enabled or not */ + protected boolean isParallelTxProcessingEnabled; + + /** Instantiates a new Besu controller builder. */ + protected BesuControllerBuilder() {} + /** * Provide a BesuComponent which can be used to get other dependencies * @@ -221,7 +238,20 @@ public BesuControllerBuilder storageProvider(final StorageProvider storageProvid * @return the besu controller builder */ public BesuControllerBuilder genesisConfigFile(final GenesisConfigFile genesisConfig) { - this.genesisConfig = genesisConfig; + this.genesisConfigFile = genesisConfig; + this.genesisConfigOptions = genesisConfig.getConfigOptions(); + return this; + } + + /** + * Genesis state hash from data besu controller builder. + * + * @param genesisStateHashCacheEnabled the is genesis state hash from data + * @return the besu controller builder + */ + public BesuControllerBuilder genesisStateHashCacheEnabled( + final Boolean genesisStateHashCacheEnabled) { + this.genesisStateHashCacheEnabled = genesisStateHashCacheEnabled; return this; } @@ -316,18 +346,6 @@ public BesuControllerBuilder privacyParameters(final PrivacyParameters privacyPa return this; } - /** - * Pki block creation configuration besu controller builder. - * - * @param pkiBlockCreationConfiguration the pki block creation configuration - * @return the besu controller builder - */ - public BesuControllerBuilder pkiBlockCreationConfiguration( - final Optional pkiBlockCreationConfiguration) { - this.pkiBlockCreationConfiguration = pkiBlockCreationConfiguration; - return this; - } - /** * Data directory besu controller builder. * @@ -373,40 +391,6 @@ public BesuControllerBuilder isRevertReasonEnabled(final boolean isRevertReasonE return this; } - /** - * Is pruning enabled besu controller builder. - * - * @param isPruningEnabled the is pruning enabled - * @return the besu controller builder - */ - public BesuControllerBuilder isPruningEnabled(final boolean isPruningEnabled) { - this.isPruningEnabled = isPruningEnabled; - return this; - } - - /** - * Pruning configuration besu controller builder. - * - * @param prunerConfiguration the pruner configuration - * @return the besu controller builder - */ - public BesuControllerBuilder pruningConfiguration(final PrunerConfiguration prunerConfiguration) { - this.prunerConfiguration = prunerConfiguration; - return this; - } - - /** - * Genesis config overrides besu controller builder. - * - * @param genesisConfigOverrides the genesis config overrides - * @return the besu controller builder - */ - public BesuControllerBuilder genesisConfigOverrides( - final Map genesisConfigOverrides) { - this.genesisConfigOverrides = genesisConfigOverrides; - return this; - } - /** * Gas limit calculator besu controller builder. * @@ -474,22 +458,10 @@ public BesuControllerBuilder maxPeers(final int maxPeers) { return this; } - /** - * Lower bound of peers where we stop actively trying to initiate new outgoing connections - * - * @param peerLowerBound lower bound of peers where we stop actively trying to initiate new - * outgoing connections - * @return the besu controller builder - */ - public BesuControllerBuilder lowerBoundPeers(final int peerLowerBound) { - this.peerLowerBound = peerLowerBound; - return this; - } - /** * Maximum number of remotely initiated peer connections * - * @param maxRemotelyInitiatedPeers aximum number of remotely initiated peer connections + * @param maxRemotelyInitiatedPeers maximum number of remotely initiated peer connections * @return the besu controller builder */ public BesuControllerBuilder maxRemotelyInitiatedPeers(final int maxRemotelyInitiatedPeers) { @@ -510,7 +482,7 @@ public BesuControllerBuilder chainPruningConfiguration( } /** - * Chain pruning configuration besu controller builder. + * Sets the number of blocks to cache. * * @param numberOfBlocksToCache the number of blocks to cache * @return the besu controller builder @@ -544,26 +516,16 @@ public BesuControllerBuilder randomPeerPriority(final Boolean randomPeerPriority } /** - * sets the transactionSelectorFactory in the builder - * - * @param transactionSelectorFactory the optional transaction selector factory - * @return the besu controller builder - */ - public BesuControllerBuilder transactionSelectorFactory( - final Optional transactionSelectorFactory) { - this.transactionSelectorFactory = transactionSelectorFactory; - return this; - } - - /** - * sets the pluginTransactionValidatorFactory + * Sets whether parallel transaction processing is enabled. When parallel transaction processing + * is enabled, transactions within a block can be processed in parallel and potentially improving + * performance * - * @param pluginTransactionValidatorFactory factory that creates plugin transaction Validators - * @return the besu controller builder + * @param isParallelTxProcessingEnabled true to enable parallel transaction + * @return the besu controller */ - public BesuControllerBuilder pluginTransactionValidatorFactory( - final PluginTransactionValidatorFactory pluginTransactionValidatorFactory) { - this.pluginTransactionValidatorFactory = pluginTransactionValidatorFactory; + public BesuControllerBuilder isParallelTxProcessingEnabled( + final boolean isParallelTxProcessingEnabled) { + this.isParallelTxProcessingEnabled = isParallelTxProcessingEnabled; return this; } @@ -573,7 +535,8 @@ public BesuControllerBuilder pluginTransactionValidatorFactory( * @return the besu controller */ public BesuController build() { - checkNotNull(genesisConfig, "Missing genesis config"); + checkNotNull(genesisConfigFile, "Missing genesis config file"); + checkNotNull(genesisConfigOptions, "Missing genesis config options"); checkNotNull(syncConfig, "Missing sync config"); checkNotNull(ethereumWireProtocolConfiguration, "Missing ethereum protocol configuration"); checkNotNull(networkId, "Missing network ID"); @@ -588,18 +551,27 @@ public BesuController build() { checkNotNull(gasLimitCalculator, "Missing gas limit calculator"); checkNotNull(evmConfiguration, "Missing evm config"); checkNotNull(networkingConfiguration, "Missing network configuration"); + checkNotNull(dataStorageConfiguration, "Missing data storage configuration"); + prepForBuild(); final ProtocolSchedule protocolSchedule = createProtocolSchedule(); - final GenesisState genesisState = GenesisState.fromConfig(genesisConfig, protocolSchedule); final VariablesStorage variablesStorage = storageProvider.createVariablesStorage(); - final WorldStateStorage worldStateStorage = - storageProvider.createWorldStateStorage(dataStorageConfiguration.getDataStorageFormat()); + final WorldStateStorageCoordinator worldStateStorageCoordinator = + storageProvider.createWorldStateStorageCoordinator(dataStorageConfiguration); final BlockchainStorage blockchainStorage = - storageProvider.createBlockchainStorage(protocolSchedule, variablesStorage); + storageProvider.createBlockchainStorage( + protocolSchedule, variablesStorage, dataStorageConfiguration); + + final var maybeStoredGenesisBlockHash = blockchainStorage.getBlockHash(0L); + + final var genesisState = + getGenesisState( + maybeStoredGenesisBlockHash.flatMap(blockchainStorage::getBlockHeader), + protocolSchedule); final MutableBlockchain blockchain = DefaultBlockchain.createMutable( @@ -609,66 +581,36 @@ public BesuController build() { reorgLoggingThreshold, dataDirectory.toString(), numberOfBlocksToCache); - - final CachedMerkleTrieLoader cachedMerkleTrieLoader = + final BonsaiCachedMerkleTrieLoader bonsaiCachedMerkleTrieLoader = besuComponent .map(BesuComponent::getCachedMerkleTrieLoader) - .orElseGet(() -> new CachedMerkleTrieLoader(metricsSystem)); + .orElseGet(() -> new BonsaiCachedMerkleTrieLoader(metricsSystem)); final WorldStateArchive worldStateArchive = - createWorldStateArchive(worldStateStorage, blockchain, cachedMerkleTrieLoader); + createWorldStateArchive( + worldStateStorageCoordinator, blockchain, bonsaiCachedMerkleTrieLoader); - if (blockchain.getChainHeadBlockNumber() < 1) { + if (maybeStoredGenesisBlockHash.isEmpty()) { genesisState.writeStateTo(worldStateArchive.getMutable()); } final ProtocolContext protocolContext = createProtocolContext( - blockchain, - worldStateArchive, - protocolSchedule, - this::createConsensusContext, - transactionSelectorFactory); + blockchain, worldStateArchive, protocolSchedule, this::createConsensusContext); validateContext(protocolContext); - if (chainPrunerConfiguration.getChainPruningEnabled()) { - protocolContext - .safeConsensusContext(MergeContext.class) - .ifPresent(mergeContext -> mergeContext.setIsChainPruningEnabled(true)); - final ChainDataPruner chainDataPruner = createChainPruner(blockchainStorage); - blockchain.observeBlockAdded(chainDataPruner); - LOG.info( - "Chain data pruning enabled with recent blocks retained to be: " - + chainPrunerConfiguration.getChainPruningBlocksRetained() - + " and frequency to be: " - + chainPrunerConfiguration.getChainPruningBlocksFrequency()); - } - protocolSchedule.setPublicWorldStateArchiveForPrivacyBlockProcessor( protocolContext.getWorldStateArchive()); - Optional maybePruner = Optional.empty(); - if (isPruningEnabled) { - if (dataStorageConfiguration.getDataStorageFormat().equals(DataStorageFormat.BONSAI)) { - LOG.warn( - "Cannot enable pruning with Bonsai data storage format. Disabling. Change the data storage format or disable pruning explicitly on the command line to remove this warning."); - } else { - maybePruner = - Optional.of( - new Pruner( - new MarkSweepPruner( - ((DefaultWorldStateArchive) worldStateArchive).getWorldStateStorage(), - blockchain, - storageProvider.getStorageBySegmentIdentifier( - KeyValueSegmentIdentifier.PRUNING_STATE), - metricsSystem), - blockchain, - prunerConfiguration)); - } - } final int maxMessageSize = ethereumWireProtocolConfiguration.getMaxMessageSize(); final Supplier currentProtocolSpecSupplier = () -> protocolSchedule.getByBlockHeader(blockchain.getChainHeadHeader()); + final ForkIdManager forkIdManager = + new ForkIdManager( + blockchain, + genesisConfigOptions.getForkBlockNumbers(), + genesisConfigOptions.getForkBlockTimestamps(), + ethereumWireProtocolConfiguration.isLegacyEth64ForkIdEnabled()); final EthPeers ethPeers = new EthPeers( getSupportedProtocol(), @@ -678,10 +620,11 @@ public BesuController build() { maxMessageSize, messagePermissioningProviders, nodeKey.getPublicKey().getEncodedBytes(), - peerLowerBound, maxPeers, maxRemotelyInitiatedPeers, - randomPeerPriority); + randomPeerPriority, + syncConfig.getSyncMode(), + forkIdManager); final EthMessages ethMessages = new EthMessages(); final EthMessages snapMessages = new EthMessages(); @@ -693,20 +636,18 @@ public BesuController build() { syncConfig.getComputationParallelism(), metricsSystem); - final GenesisConfigOptions configOptions = - genesisConfig.getConfigOptions(genesisConfigOverrides); - Optional checkpoint = Optional.empty(); - if (configOptions.getCheckpointOptions().isValid()) { + if (genesisConfigOptions.getCheckpointOptions().isValid()) { checkpoint = Optional.of( ImmutableCheckpoint.builder() .blockHash( - Hash.fromHexString(configOptions.getCheckpointOptions().getHash().get())) - .blockNumber(configOptions.getCheckpointOptions().getNumber().getAsLong()) + Hash.fromHexString( + genesisConfigOptions.getCheckpointOptions().getHash().get())) + .blockNumber(genesisConfigOptions.getCheckpointOptions().getNumber().getAsLong()) .totalDifficulty( Difficulty.fromHexString( - configOptions.getCheckpointOptions().getTotalDifficulty().get())) + genesisConfigOptions.getCheckpointOptions().getTotalDifficulty().get())) .build()); } @@ -714,6 +655,16 @@ public BesuController build() { final boolean fullSyncDisabled = !SyncMode.isFullSync(syncConfig.getSyncMode()); final SyncState syncState = new SyncState(blockchain, ethPeers, fullSyncDisabled, checkpoint); + if (chainPrunerConfiguration.getChainPruningEnabled()) { + final ChainDataPruner chainDataPruner = createChainPruner(blockchainStorage); + blockchain.observeBlockAdded(chainDataPruner); + LOG.info( + "Chain data pruning enabled with recent blocks retained to be: " + + chainPrunerConfiguration.getChainPruningBlocksRetained() + + " and frequency to be: " + + chainPrunerConfiguration.getChainPruningBlocksFrequency()); + } + final TransactionPool transactionPool = TransactionPoolFactory.createTransactionPool( protocolSchedule, @@ -723,8 +674,8 @@ public BesuController build() { metricsSystem, syncState, transactionPoolConfiguration, - pluginTransactionValidatorFactory, - besuComponent.map(BesuComponent::getBlobCache).orElse(new BlobCache())); + besuComponent.map(BesuComponent::getBlobCache).orElse(new BlobCache()), + miningParameters); final List peerValidators = createPeerValidators(protocolSchedule); @@ -739,28 +690,39 @@ public BesuController build() { ethMessages, scheduler, peerValidators, - Optional.empty()); - - final Optional maybeSnapProtocolManager = - createSnapProtocolManager(peerValidators, ethPeers, snapMessages, worldStateArchive); + Optional.empty(), + forkIdManager); final PivotBlockSelector pivotBlockSelector = createPivotSelector( - protocolSchedule, protocolContext, ethContext, syncState, metricsSystem); + protocolSchedule, protocolContext, ethContext, syncState, metricsSystem, blockchain); - final Synchronizer synchronizer = + final DefaultSynchronizer synchronizer = createSynchronizer( protocolSchedule, - worldStateStorage, + worldStateStorageCoordinator, protocolContext, - maybePruner, ethContext, syncState, ethProtocolManager, pivotBlockSelector); + ethPeers.setTrailingPeerRequirementsSupplier(synchronizer::calculateTrailingPeerRequirements); + + if (syncConfig.getSyncMode() == SyncMode.SNAP + || syncConfig.getSyncMode() == SyncMode.CHECKPOINT) { + synchronizer.subscribeInSync((b) -> ethPeers.snapServerPeersNeeded(!b)); + ethPeers.snapServerPeersNeeded(true); + } else { + ethPeers.snapServerPeersNeeded(false); + } + protocolContext.setSynchronizer(Optional.of(synchronizer)); + final Optional maybeSnapProtocolManager = + createSnapProtocolManager( + protocolContext, worldStateStorageCoordinator, ethPeers, snapMessages); + final MiningCoordinator miningCoordinator = createMiningCoordinator( protocolSchedule, @@ -777,7 +739,18 @@ public BesuController build() { createSubProtocolConfiguration(ethProtocolManager, maybeSnapProtocolManager); final JsonRpcMethods additionalJsonRpcMethodFactory = - createAdditionalJsonRpcMethodFactory(protocolContext); + createAdditionalJsonRpcMethodFactory(protocolContext, protocolSchedule, miningParameters); + + if (dataStorageConfiguration.getBonsaiLimitTrieLogsEnabled() + && DataStorageFormat.BONSAI.equals(dataStorageConfiguration.getDataStorageFormat())) { + final TrieLogManager trieLogManager = + ((BonsaiWorldStateProvider) worldStateArchive).getTrieLogManager(); + final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage = + worldStateStorageCoordinator.getStrategy(BonsaiWorldStateKeyValueStorage.class); + final TrieLogPruner trieLogPruner = + createTrieLogPruner(worldStateKeyValueStorage, blockchain, scheduler); + trieLogManager.subscribe(trieLogPruner); + } final List closeables = new ArrayList<>(); closeables.add(protocolContext.getWorldStateArchive()); @@ -790,7 +763,7 @@ public BesuController build() { protocolSchedule, protocolContext, ethProtocolManager, - configOptionsSupplier.get(), + genesisConfigOptions, subProtocolConfiguration, synchronizer, syncState, @@ -803,27 +776,64 @@ public BesuController build() { closeables, additionalPluginServices, ethPeers, - storageProvider); + storageProvider, + dataStorageConfiguration); + } + + private GenesisState getGenesisState( + final Optional maybeGenesisBlockHeader, + final ProtocolSchedule protocolSchedule) { + final Optional maybeGenesisStateRoot = + genesisStateHashCacheEnabled + ? maybeGenesisBlockHeader.map(BlockHeader::getStateRoot) + : Optional.empty(); + + return maybeGenesisStateRoot + .map( + genesisStateRoot -> + GenesisState.fromStorage(genesisStateRoot, genesisConfigFile, protocolSchedule)) + .orElseGet( + () -> + GenesisState.fromConfig( + dataStorageConfiguration, genesisConfigFile, protocolSchedule)); + } + + private TrieLogPruner createTrieLogPruner( + final WorldStateKeyValueStorage worldStateStorage, + final Blockchain blockchain, + final EthScheduler scheduler) { + final boolean isProofOfStake = genesisConfigOptions.getTerminalTotalDifficulty().isPresent(); + + final TrieLogPruner trieLogPruner = + new TrieLogPruner( + (BonsaiWorldStateKeyValueStorage) worldStateStorage, + blockchain, + scheduler::executeServiceTask, + dataStorageConfiguration.getBonsaiMaxLayersToLoad(), + dataStorageConfiguration.getBonsaiTrieLogPruningWindowSize(), + isProofOfStake, + metricsSystem); + trieLogPruner.initialize(); + + return trieLogPruner; } /** * Create synchronizer synchronizer. * * @param protocolSchedule the protocol schedule - * @param worldStateStorage the world state storage + * @param worldStateStorageCoordinator the world state storage * @param protocolContext the protocol context - * @param maybePruner the maybe pruner * @param ethContext the eth context * @param syncState the sync state * @param ethProtocolManager the eth protocol manager * @param pivotBlockSelector the pivot block selector * @return the synchronizer */ - protected Synchronizer createSynchronizer( + protected DefaultSynchronizer createSynchronizer( final ProtocolSchedule protocolSchedule, - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final ProtocolContext protocolContext, - final Optional maybePruner, final EthContext ethContext, final SyncState syncState, final EthProtocolManager ethProtocolManager, @@ -833,9 +843,8 @@ protected Synchronizer createSynchronizer( syncConfig, protocolSchedule, protocolContext, - worldStateStorage, + worldStateStorageCoordinator, ethProtocolManager.getBlockBroadcaster(), - maybePruner, ethContext, syncState, dataDirectory, @@ -851,11 +860,22 @@ private PivotBlockSelector createPivotSelector( final ProtocolContext protocolContext, final EthContext ethContext, final SyncState syncState, - final MetricsSystem metricsSystem) { - - final GenesisConfigOptions genesisConfigOptions = configOptionsSupplier.get(); + final MetricsSystem metricsSystem, + final Blockchain blockchain) { - if (genesisConfigOptions.getTerminalTotalDifficulty().isPresent()) { + if (genesisConfigOptions.isQbft() || genesisConfigOptions.isIbft2()) { + LOG.info( + "{} is configured, creating initial sync for BFT", + genesisConfigOptions.getConsensusEngine().toUpperCase(Locale.ROOT)); + return new BFTPivotSelectorFromPeers( + ethContext, + syncConfig, + syncState, + metricsSystem, + protocolContext, + nodeKey, + blockchain.getChainHeadHeader()); + } else if (genesisConfigOptions.getTerminalTotalDifficulty().isPresent()) { LOG.info("TTD difficulty is present, creating initial sync for PoS"); final MergeContext mergeContext = protocolContext.getConsensusContext(MergeContext.class); @@ -891,8 +911,7 @@ private PivotBlockSelector createPivotSelector( * @return the full sync termination condition */ protected SyncTerminationCondition getFullSyncTerminationCondition(final Blockchain blockchain) { - return configOptionsSupplier - .get() + return genesisConfigOptions .getTerminalTotalDifficulty() .map(difficulty -> SyncTerminationCondition.difficulty(difficulty, blockchain)) .orElse(SyncTerminationCondition.never()); @@ -905,10 +924,14 @@ protected void prepForBuild() {} * Create additional json rpc method factory json rpc methods. * * @param protocolContext the protocol context + * @param protocolSchedule the protocol schedule + * @param miningParameters the mining parameters * @return the json rpc methods */ protected JsonRpcMethods createAdditionalJsonRpcMethodFactory( - final ProtocolContext protocolContext) { + final ProtocolContext protocolContext, + final ProtocolSchedule protocolSchedule, + final MiningParameters miningParameters) { return apis -> Collections.emptyMap(); } @@ -998,6 +1021,7 @@ protected String getSupportedProtocol() { * @param scheduler the scheduler * @param peerValidators the peer validators * @param mergePeerFilter the merge peer filter + * @param forkIdManager the fork id manager * @return the eth protocol manager */ protected EthProtocolManager createEthProtocolManager( @@ -1010,7 +1034,8 @@ protected EthProtocolManager createEthProtocolManager( final EthMessages ethMessages, final EthScheduler scheduler, final List peerValidators, - final Optional mergePeerFilter) { + final Optional mergePeerFilter, + final ForkIdManager forkIdManager) { return new EthProtocolManager( protocolContext.getBlockchain(), networkId, @@ -1024,8 +1049,7 @@ protected EthProtocolManager createEthProtocolManager( mergePeerFilter, synchronizerConfiguration, scheduler, - genesisConfig.getForkBlockNumbers(), - genesisConfig.getForkTimestamps()); + forkIdManager); } /** @@ -1035,66 +1059,56 @@ protected EthProtocolManager createEthProtocolManager( * @param worldStateArchive the world state archive * @param protocolSchedule the protocol schedule * @param consensusContextFactory the consensus context factory - * @param transactionSelectorFactory optional transaction selector factory * @return the protocol context */ protected ProtocolContext createProtocolContext( final MutableBlockchain blockchain, final WorldStateArchive worldStateArchive, final ProtocolSchedule protocolSchedule, - final ConsensusContextFactory consensusContextFactory, - final Optional transactionSelectorFactory) { + final ConsensusContextFactory consensusContextFactory) { return ProtocolContext.init( - blockchain, - worldStateArchive, - protocolSchedule, - consensusContextFactory, - transactionSelectorFactory); + blockchain, worldStateArchive, protocolSchedule, consensusContextFactory, badBlockManager); } private Optional createSnapProtocolManager( - final List peerValidators, + final ProtocolContext protocolContext, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final EthPeers ethPeers, - final EthMessages snapMessages, - final WorldStateArchive worldStateArchive) { + final EthMessages snapMessages) { return Optional.of( - new SnapProtocolManager(peerValidators, ethPeers, snapMessages, worldStateArchive)); + new SnapProtocolManager( + worldStateStorageCoordinator, + syncConfig.getSnapSyncConfiguration(), + ethPeers, + snapMessages, + protocolContext)); } WorldStateArchive createWorldStateArchive( - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final Blockchain blockchain, - final CachedMerkleTrieLoader cachedMerkleTrieLoader) { + final BonsaiCachedMerkleTrieLoader bonsaiCachedMerkleTrieLoader) { return switch (dataStorageConfiguration.getDataStorageFormat()) { case BONSAI -> { - final GenesisConfigOptions genesisConfigOptions = configOptionsSupplier.get(); - final boolean isProofOfStake = - genesisConfigOptions.getTerminalTotalDifficulty().isPresent(); - final TrieLogPruner trieLogPruner = - dataStorageConfiguration.getUnstable().getBonsaiTrieLogPruningEnabled() - ? new TrieLogPruner( - (BonsaiWorldStateKeyValueStorage) worldStateStorage, - blockchain, - dataStorageConfiguration.getUnstable().getBonsaiTrieLogRetentionThreshold(), - dataStorageConfiguration.getUnstable().getBonsaiTrieLogPruningLimit(), - isProofOfStake) - : TrieLogPruner.noOpTrieLogPruner(); - trieLogPruner.initialize(); + final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage = + worldStateStorageCoordinator.getStrategy(BonsaiWorldStateKeyValueStorage.class); yield new BonsaiWorldStateProvider( - (BonsaiWorldStateKeyValueStorage) worldStateStorage, + worldStateKeyValueStorage, blockchain, Optional.of(dataStorageConfiguration.getBonsaiMaxLayersToLoad()), - cachedMerkleTrieLoader, - metricsSystem, + bonsaiCachedMerkleTrieLoader, besuComponent.map(BesuComponent::getBesuPluginContext).orElse(null), - evmConfiguration, - trieLogPruner); + evmConfiguration); } case FOREST -> { final WorldStatePreimageStorage preimageStorage = storageProvider.createWorldStatePreimageStorage(); - yield new DefaultWorldStateArchive(worldStateStorage, preimageStorage, evmConfiguration); + yield new ForestWorldStateArchive( + worldStateStorageCoordinator, preimageStorage, evmConfiguration); } + default -> + throw new IllegalStateException( + "Unexpected value: " + dataStorageConfiguration.getDataStorageFormat()); }; } @@ -1123,14 +1137,14 @@ private ChainDataPruner createChainPruner(final BlockchainStorage blockchainStor protected List createPeerValidators(final ProtocolSchedule protocolSchedule) { final List validators = new ArrayList<>(); - final OptionalLong daoBlock = configOptionsSupplier.get().getDaoForkBlock(); + final OptionalLong daoBlock = genesisConfigOptions.getDaoForkBlock(); if (daoBlock.isPresent()) { // Setup dao validator validators.add( new DaoForkPeerValidator(protocolSchedule, metricsSystem, daoBlock.getAsLong())); } - final OptionalLong classicBlock = configOptionsSupplier.get().getClassicForkBlock(); + final OptionalLong classicBlock = genesisConfigOptions.getClassicForkBlock(); // setup classic validator if (classicBlock.isPresent()) { validators.add( @@ -1144,9 +1158,8 @@ protected List createPeerValidators(final ProtocolSchedule protoc } final CheckpointConfigOptions checkpointConfigOptions = - genesisConfig.getConfigOptions(genesisConfigOverrides).getCheckpointOptions(); - if (SyncMode.X_CHECKPOINT.equals(syncConfig.getSyncMode()) - && checkpointConfigOptions.isValid()) { + genesisConfigOptions.getCheckpointOptions(); + if (syncConfig.getSyncMode() == SyncMode.CHECKPOINT && checkpointConfigOptions.isValid()) { validators.add( new CheckpointBlocksPeerValidator( protocolSchedule, diff --git a/besu/src/main/java/org/hyperledger/besu/controller/BftBesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/BftBesuControllerBuilder.java index 4e15a3829c3..328b51ce58b 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/BftBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/BftBesuControllerBuilder.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.controller; import org.hyperledger.besu.consensus.common.bft.BftBlockInterface; @@ -25,6 +24,9 @@ /** Base class for BFT based Besu Controller Builders. */ public abstract class BftBesuControllerBuilder extends BesuControllerBuilder { + /** Default constructor to be used by subclasses. */ + protected BftBesuControllerBuilder() {} + /** * Bft extra data codec supplier. * diff --git a/besu/src/main/java/org/hyperledger/besu/controller/CliqueBesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/CliqueBesuControllerBuilder.java index ba6a43906e8..b4ada605498 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/CliqueBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/CliqueBesuControllerBuilder.java @@ -19,6 +19,7 @@ import org.hyperledger.besu.config.CliqueConfigOptions; import org.hyperledger.besu.consensus.clique.CliqueBlockInterface; import org.hyperledger.besu.consensus.clique.CliqueContext; +import org.hyperledger.besu.consensus.clique.CliqueForksSchedulesFactory; import org.hyperledger.besu.consensus.clique.CliqueMiningTracker; import org.hyperledger.besu.consensus.clique.CliqueProtocolSchedule; import org.hyperledger.besu.consensus.clique.blockcreation.CliqueBlockScheduler; @@ -27,6 +28,7 @@ import org.hyperledger.besu.consensus.clique.jsonrpc.CliqueJsonRpcMethods; import org.hyperledger.besu.consensus.common.BlockInterface; import org.hyperledger.besu.consensus.common.EpochManager; +import org.hyperledger.besu.consensus.common.ForksSchedule; import org.hyperledger.besu.consensus.common.validator.blockbased.BlockValidatorProvider; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.ProtocolContext; @@ -52,25 +54,28 @@ public class CliqueBesuControllerBuilder extends BesuControllerBuilder { private Address localAddress; private EpochManager epochManager; - private long secondsBetweenBlocks; - private boolean createEmptyBlocks = true; private final BlockInterface blockInterface = new CliqueBlockInterface(); + private ForksSchedule forksSchedule; + + /** Default constructor. */ + public CliqueBesuControllerBuilder() {} @Override protected void prepForBuild() { localAddress = Util.publicKeyToAddress(nodeKey.getPublicKey()); - final CliqueConfigOptions cliqueConfig = configOptionsSupplier.get().getCliqueConfigOptions(); + final CliqueConfigOptions cliqueConfig = genesisConfigOptions.getCliqueConfigOptions(); final long blocksPerEpoch = cliqueConfig.getEpochLength(); - secondsBetweenBlocks = cliqueConfig.getBlockPeriodSeconds(); - createEmptyBlocks = cliqueConfig.getCreateEmptyBlocks(); epochManager = new EpochManager(blocksPerEpoch); + forksSchedule = CliqueForksSchedulesFactory.create(genesisConfigOptions); } @Override protected JsonRpcMethods createAdditionalJsonRpcMethodFactory( - final ProtocolContext protocolContext) { - return new CliqueJsonRpcMethods(protocolContext); + final ProtocolContext protocolContext, + final ProtocolSchedule protocolSchedule, + final MiningParameters miningParameters) { + return new CliqueJsonRpcMethods(protocolContext, protocolSchedule, miningParameters); } @Override @@ -92,9 +97,9 @@ protected MiningCoordinator createMiningCoordinator( clock, protocolContext.getConsensusContext(CliqueContext.class).getValidatorProvider(), localAddress, - secondsBetweenBlocks), + forksSchedule), epochManager, - createEmptyBlocks, + forksSchedule, ethProtocolManager.ethContext().getScheduler()); final CliqueMiningCoordinator miningCoordinator = new CliqueMiningCoordinator( @@ -102,6 +107,18 @@ protected MiningCoordinator createMiningCoordinator( miningExecutor, syncState, new CliqueMiningTracker(localAddress, protocolContext)); + + // Update the next block period in seconds according to the transition schedule + protocolContext + .getBlockchain() + .observeBlockAdded( + o -> + miningParameters.setBlockPeriodSeconds( + forksSchedule + .getFork(o.getBlock().getHeader().getNumber() + 1) + .getValue() + .getBlockPeriodSeconds())); + miningCoordinator.addMinedBlockObserver(ethProtocolManager); // Clique mining is implicitly enabled. @@ -112,11 +129,16 @@ protected MiningCoordinator createMiningCoordinator( @Override protected ProtocolSchedule createProtocolSchedule() { return CliqueProtocolSchedule.create( - configOptionsSupplier.get(), + genesisConfigOptions, + forksSchedule, nodeKey, privacyParameters, isRevertReasonEnabled, - evmConfiguration); + evmConfiguration, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem); } @Override diff --git a/besu/src/main/java/org/hyperledger/besu/controller/ConsensusScheduleBesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/ConsensusScheduleBesuControllerBuilder.java index 921289c66e5..0d0f8d2fd87 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/ConsensusScheduleBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/ConsensusScheduleBesuControllerBuilder.java @@ -1,21 +1,17 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See - * the License for the + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.controller; import static org.hyperledger.besu.ethereum.core.BlockHeader.GENESIS_BLOCK_NUMBER; @@ -27,7 +23,6 @@ import org.hyperledger.besu.consensus.common.MigratingContext; import org.hyperledger.besu.consensus.common.MigratingMiningCoordinator; import org.hyperledger.besu.consensus.common.MigratingProtocolContext; -import org.hyperledger.besu.consensus.qbft.pki.PkiBlockCreationConfiguration; import org.hyperledger.besu.cryptoservices.NodeKey; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ConsensusContext; @@ -53,16 +48,15 @@ import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.forkid.ForkIdManager; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.p2p.config.SubProtocolConfiguration; import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; -import org.hyperledger.besu.ethereum.worldstate.PrunerConfiguration; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.ObservableMetricsSystem; import org.hyperledger.besu.plugin.services.permissioning.NodeMessagePermissioningProvider; -import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelectorFactory; import java.math.BigInteger; import java.nio.file.Path; @@ -166,7 +160,7 @@ protected ProtocolSchedule createProtocolSchedule() { besuControllerBuilderSchedule.entrySet().stream() .map(e -> new ForkSpec<>(e.getKey(), e.getValue().createProtocolSchedule())) .collect(Collectors.toCollection(() -> new TreeSet<>(ForkSpec.COMPARATOR))); - final Optional chainId = configOptionsSupplier.get().getChainId(); + final Optional chainId = genesisConfigOptions.getChainId(); return combinedProtocolScheduleFactory.apply(protocolScheduleSpecs, chainId); } @@ -175,14 +169,9 @@ protected ProtocolContext createProtocolContext( final MutableBlockchain blockchain, final WorldStateArchive worldStateArchive, final ProtocolSchedule protocolSchedule, - final ConsensusContextFactory consensusContextFactory, - final Optional transactionSelectorFactory) { + final ConsensusContextFactory consensusContextFactory) { return MigratingProtocolContext.init( - blockchain, - worldStateArchive, - protocolSchedule, - consensusContextFactory, - transactionSelectorFactory); + blockchain, worldStateArchive, protocolSchedule, consensusContextFactory, badBlockManager); } @Override @@ -215,10 +204,12 @@ protected PluginServiceFactory createAdditionalPluginServices( @Override protected JsonRpcMethods createAdditionalJsonRpcMethodFactory( - final ProtocolContext protocolContext) { + final ProtocolContext protocolContext, + final ProtocolSchedule protocolSchedule, + final MiningParameters miningParameters) { return besuControllerBuilderSchedule .get(0L) - .createAdditionalJsonRpcMethodFactory(protocolContext); + .createAdditionalJsonRpcMethodFactory(protocolContext, protocolSchedule, miningParameters); } @Override @@ -251,7 +242,8 @@ protected EthProtocolManager createEthProtocolManager( final EthMessages ethMessages, final EthScheduler scheduler, final List peerValidators, - final Optional mergePeerFilter) { + final Optional mergePeerFilter, + final ForkIdManager forkIdManager) { return besuControllerBuilderSchedule .get(0L) .createEthProtocolManager( @@ -264,7 +256,8 @@ protected EthProtocolManager createEthProtocolManager( ethMessages, scheduler, peerValidators, - mergePeerFilter); + mergePeerFilter, + forkIdManager); } @Override @@ -336,15 +329,6 @@ public BesuControllerBuilder privacyParameters(final PrivacyParameters privacyPa return super.privacyParameters(privacyParameters); } - @Override - public BesuControllerBuilder pkiBlockCreationConfiguration( - final Optional pkiBlockCreationConfiguration) { - besuControllerBuilderSchedule - .values() - .forEach(b -> b.pkiBlockCreationConfiguration(pkiBlockCreationConfiguration)); - return super.pkiBlockCreationConfiguration(pkiBlockCreationConfiguration); - } - @Override public BesuControllerBuilder dataDirectory(final Path dataDirectory) { besuControllerBuilderSchedule.values().forEach(b -> b.dataDirectory(dataDirectory)); @@ -375,26 +359,12 @@ public BesuControllerBuilder isRevertReasonEnabled(final boolean isRevertReasonE } @Override - public BesuControllerBuilder isPruningEnabled(final boolean isPruningEnabled) { - besuControllerBuilderSchedule.values().forEach(b -> b.isPruningEnabled(isPruningEnabled)); - return super.isPruningEnabled(isPruningEnabled); - } - - @Override - public BesuControllerBuilder pruningConfiguration(final PrunerConfiguration prunerConfiguration) { - besuControllerBuilderSchedule - .values() - .forEach(b -> b.pruningConfiguration(prunerConfiguration)); - return super.pruningConfiguration(prunerConfiguration); - } - - @Override - public BesuControllerBuilder genesisConfigOverrides( - final Map genesisConfigOverrides) { + public BesuControllerBuilder isParallelTxProcessingEnabled( + final boolean isParallelTxProcessingEnabled) { besuControllerBuilderSchedule .values() - .forEach(b -> b.genesisConfigOverrides(genesisConfigOverrides)); - return super.genesisConfigOverrides(genesisConfigOverrides); + .forEach(b -> b.isParallelTxProcessingEnabled(isParallelTxProcessingEnabled)); + return super.isParallelTxProcessingEnabled(isParallelTxProcessingEnabled); } @Override diff --git a/besu/src/main/java/org/hyperledger/besu/controller/IbftBesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/IbftBesuControllerBuilder.java index 6075afc386d..b8d4d2645e0 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/IbftBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/IbftBesuControllerBuilder.java @@ -16,7 +16,6 @@ import org.hyperledger.besu.config.BftConfigOptions; import org.hyperledger.besu.config.BftFork; -import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.consensus.common.BftValidatorOverrides; import org.hyperledger.besu.consensus.common.EpochManager; import org.hyperledger.besu.consensus.common.ForksSchedule; @@ -95,6 +94,9 @@ public class IbftBesuControllerBuilder extends BftBesuControllerBuilder { private ForksSchedule forksSchedule; private ValidatorPeers peers; + /** Default Constructor */ + public IbftBesuControllerBuilder() {} + @Override protected Supplier bftExtraDataCodec() { return Suppliers.memoize(IbftExtraDataCodec::new); @@ -102,15 +104,17 @@ protected Supplier bftExtraDataCodec() { @Override protected void prepForBuild() { - bftConfig = configOptionsSupplier.get().getBftConfigOptions(); + bftConfig = genesisConfigOptions.getBftConfigOptions(); bftEventQueue = new BftEventQueue(bftConfig.getMessageQueueLimit()); - forksSchedule = IbftForksSchedulesFactory.create(configOptionsSupplier.get()); + forksSchedule = IbftForksSchedulesFactory.create(genesisConfigOptions); } @Override protected JsonRpcMethods createAdditionalJsonRpcMethodFactory( - final ProtocolContext protocolContext) { - return new IbftJsonRpcMethods(protocolContext); + final ProtocolContext protocolContext, + final ProtocolSchedule protocolSchedule, + final MiningParameters miningParameters) { + return new IbftJsonRpcMethods(protocolContext, protocolSchedule, miningParameters); } @Override @@ -234,12 +238,19 @@ protected MiningCoordinator createMiningCoordinator( blockchain, bftEventQueue); + // Update the next block period in seconds according to the transition schedule + protocolContext + .getBlockchain() + .observeBlockAdded( + o -> + miningParameters.setBlockPeriodSeconds( + forksSchedule + .getFork(o.getBlock().getHeader().getNumber() + 1) + .getValue() + .getBlockPeriodSeconds())); + if (syncState.isInitialSyncPhaseDone()) { - LOG.info("Starting IBFT mining coordinator"); ibftMiningCoordinator.enable(); - ibftMiningCoordinator.start(); - } else { - LOG.info("IBFT mining coordinator not starting while initial sync in progress"); } syncState.subscribeCompletionReached( @@ -273,12 +284,16 @@ protected PluginServiceFactory createAdditionalPluginServices( @Override protected ProtocolSchedule createProtocolSchedule() { return IbftProtocolScheduleBuilder.create( - configOptionsSupplier.get(), + genesisConfigOptions, forksSchedule, privacyParameters, isRevertReasonEnabled, bftExtraDataCodec().get(), - evmConfiguration); + evmConfiguration, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem); } @Override @@ -295,12 +310,11 @@ protected BftContext createConsensusContext( final Blockchain blockchain, final WorldStateArchive worldStateArchive, final ProtocolSchedule protocolSchedule) { - final GenesisConfigOptions configOptions = configOptionsSupplier.get(); - final BftConfigOptions ibftConfig = configOptions.getBftConfigOptions(); + final BftConfigOptions ibftConfig = genesisConfigOptions.getBftConfigOptions(); final EpochManager epochManager = new EpochManager(ibftConfig.getEpochLength()); final BftValidatorOverrides validatorOverrides = - convertIbftForks(configOptions.getTransitions().getIbftForks()); + convertIbftForks(genesisConfigOptions.getTransitions().getIbftForks()); return new BftContext( BlockValidatorProvider.forkingValidatorProvider( diff --git a/besu/src/main/java/org/hyperledger/besu/controller/MainnetBesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/MainnetBesuControllerBuilder.java index 8942f337e3b..e0fbed608ab 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/MainnetBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/MainnetBesuControllerBuilder.java @@ -36,6 +36,9 @@ public class MainnetBesuControllerBuilder extends BesuControllerBuilder { private EpochCalculator epochCalculator = new EpochCalculator.DefaultEpochCalculator(); + /** Default constructor. */ + public MainnetBesuControllerBuilder() {} + @Override protected MiningCoordinator createMiningCoordinator( final ProtocolSchedule protocolSchedule, @@ -91,13 +94,19 @@ protected PluginServiceFactory createAdditionalPluginServices( @Override protected ProtocolSchedule createProtocolSchedule() { return MainnetProtocolSchedule.fromConfig( - configOptionsSupplier.get(), privacyParameters, isRevertReasonEnabled, evmConfiguration); + genesisConfigOptions, + privacyParameters, + isRevertReasonEnabled, + evmConfiguration, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem); } @Override protected void prepForBuild() { - configOptionsSupplier - .get() + genesisConfigOptions .getThanosBlockNumber() .ifPresent( activationBlock -> epochCalculator = new EpochCalculator.Ecip1099EpochCalculator()); diff --git a/besu/src/main/java/org/hyperledger/besu/controller/MergeBesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/MergeBesuControllerBuilder.java index b9a7b6ff5a6..f5fc75959e1 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/MergeBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/MergeBesuControllerBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,7 +14,6 @@ */ package org.hyperledger.besu.controller; -import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.consensus.merge.MergeContext; import org.hyperledger.besu.consensus.merge.MergeProtocolSchedule; import org.hyperledger.besu.consensus.merge.PostMergeContext; @@ -42,6 +41,7 @@ import org.hyperledger.besu.ethereum.eth.sync.backwardsync.BackwardSyncContext; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; +import org.hyperledger.besu.ethereum.forkid.ForkIdManager; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; @@ -59,6 +59,9 @@ public class MergeBesuControllerBuilder extends BesuControllerBuilder { private final AtomicReference syncState = new AtomicReference<>(); private static final Logger LOG = LoggerFactory.getLogger(MergeBesuControllerBuilder.class); + /** Default constructor. */ + public MergeBesuControllerBuilder() {} + @Override protected MiningCoordinator createMiningCoordinator( final ProtocolSchedule protocolSchedule, @@ -95,18 +98,15 @@ protected EthProtocolManager createEthProtocolManager( final EthMessages ethMessages, final EthScheduler scheduler, final List peerValidators, - final Optional mergePeerFilter) { + final Optional mergePeerFilter, + final ForkIdManager forkIdManager) { var mergeContext = protocolContext.getConsensusContext(MergeContext.class); var mergeBestPeerComparator = new TransitionBestPeerComparator( - configOptionsSupplier - .get() - .getTerminalTotalDifficulty() - .map(Difficulty::of) - .orElseThrow()); - ethPeers.setBestChainComparator(mergeBestPeerComparator); + genesisConfigOptions.getTerminalTotalDifficulty().map(Difficulty::of).orElseThrow()); + ethPeers.setBestPeerComparator(mergeBestPeerComparator); mergeContext.observeNewIsPostMergeState(mergeBestPeerComparator); Optional filterToUse = Optional.of(new MergePeerFilter()); @@ -128,7 +128,8 @@ protected EthProtocolManager createEthProtocolManager( ethMessages, scheduler, peerValidators, - filterToUse); + filterToUse, + forkIdManager); return ethProtocolManager; } @@ -156,7 +157,6 @@ protected MiningCoordinator createTransitionMiningCoordinator( this.syncState.set(syncState); - final GenesisConfigOptions genesisConfigOptions = configOptionsSupplier.get(); final Optional
depositContractAddress = genesisConfigOptions.getDepositContractAddress(); @@ -173,7 +173,13 @@ protected MiningCoordinator createTransitionMiningCoordinator( @Override protected ProtocolSchedule createProtocolSchedule() { return MergeProtocolSchedule.create( - configOptionsSupplier.get(), privacyParameters, isRevertReasonEnabled); + genesisConfigOptions, + privacyParameters, + isRevertReasonEnabled, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem); } @Override @@ -182,7 +188,6 @@ protected MergeContext createConsensusContext( final WorldStateArchive worldStateArchive, final ProtocolSchedule protocolSchedule) { - final GenesisConfigOptions genesisConfigOptions = configOptionsSupplier.get(); final OptionalLong terminalBlockNumber = genesisConfigOptions.getTerminalBlockNumber(); final Optional terminalBlockHash = genesisConfigOptions.getTerminalBlockHash(); final boolean isPostMergeAtGenesis = @@ -198,7 +203,6 @@ protected MergeContext createConsensusContext( .getTerminalTotalDifficulty() .map(Difficulty::of) .orElse(Difficulty.ZERO)) - .setCheckpointPostMergeSync(syncConfig.isCheckpointPostMergeEnabled()) .setPostMergeAtGenesis(isPostMergeAtGenesis); blockchain @@ -233,9 +237,8 @@ protected PluginServiceFactory createAdditionalPluginServices( @Override protected List createPeerValidators(final ProtocolSchedule protocolSchedule) { List retval = super.createPeerValidators(protocolSchedule); - final OptionalLong powTerminalBlockNumber = - configOptionsSupplier.get().getTerminalBlockNumber(); - final Optional powTerminalBlockHash = configOptionsSupplier.get().getTerminalBlockHash(); + final OptionalLong powTerminalBlockNumber = genesisConfigOptions.getTerminalBlockNumber(); + final Optional powTerminalBlockHash = genesisConfigOptions.getTerminalBlockHash(); if (powTerminalBlockHash.isPresent() && powTerminalBlockNumber.isPresent()) { retval.add( new RequiredBlocksPeerValidator( diff --git a/besu/src/main/java/org/hyperledger/besu/controller/MiningParameterOverrides.java b/besu/src/main/java/org/hyperledger/besu/controller/MiningParameterOverrides.java index a00186ea0c2..e4cebc0d648 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/MiningParameterOverrides.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/MiningParameterOverrides.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/besu/src/main/java/org/hyperledger/besu/controller/NoopPluginServiceFactory.java b/besu/src/main/java/org/hyperledger/besu/controller/NoopPluginServiceFactory.java index a538b1d04e7..1fde6dddf00 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/NoopPluginServiceFactory.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/NoopPluginServiceFactory.java @@ -18,6 +18,8 @@ /** The Noop plugin service factory. */ public class NoopPluginServiceFactory implements PluginServiceFactory { + /** Default Constructor. */ + public NoopPluginServiceFactory() {} @Override public void appendPluginServices(final BesuPluginContextImpl besuContext) {} diff --git a/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java index 133dfcdb0f0..7961305c48e 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/QbftBesuControllerBuilder.java @@ -16,6 +16,7 @@ import static com.google.common.base.Preconditions.checkNotNull; +import org.hyperledger.besu.config.BftConfigOptions; import org.hyperledger.besu.config.BftFork; import org.hyperledger.besu.config.QbftConfigOptions; import org.hyperledger.besu.config.QbftFork; @@ -44,7 +45,6 @@ import org.hyperledger.besu.consensus.common.bft.statemachine.FutureMessageBuffer; import org.hyperledger.besu.consensus.common.validator.ValidatorProvider; import org.hyperledger.besu.consensus.common.validator.blockbased.BlockValidatorProvider; -import org.hyperledger.besu.consensus.qbft.QbftContext; import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec; import org.hyperledger.besu.consensus.qbft.QbftForksSchedulesFactory; import org.hyperledger.besu.consensus.qbft.QbftGossip; @@ -52,7 +52,6 @@ import org.hyperledger.besu.consensus.qbft.blockcreation.QbftBlockCreatorFactory; import org.hyperledger.besu.consensus.qbft.jsonrpc.QbftJsonRpcMethods; import org.hyperledger.besu.consensus.qbft.payload.MessageFactory; -import org.hyperledger.besu.consensus.qbft.pki.PkiQbftExtraDataCodec; import org.hyperledger.besu.consensus.qbft.protocol.Istanbul100SubProtocol; import org.hyperledger.besu.consensus.qbft.statemachine.QbftBlockHeightManagerFactory; import org.hyperledger.besu.consensus.qbft.statemachine.QbftController; @@ -105,32 +104,39 @@ public class QbftBesuControllerBuilder extends BftBesuControllerBuilder { private ForksSchedule qbftForksSchedule; private ValidatorPeers peers; private TransactionValidatorProvider transactionValidatorProvider; + private BftConfigOptions bftConfigOptions; + + /** Default Constructor. */ + public QbftBesuControllerBuilder() {} @Override protected Supplier bftExtraDataCodec() { return Suppliers.memoize( () -> { - if (pkiBlockCreationConfiguration.isPresent()) { - return new PkiQbftExtraDataCodec(); - } else { - return new QbftExtraDataCodec(); - } + return new QbftExtraDataCodec(); }); } @Override protected void prepForBuild() { - qbftConfig = configOptionsSupplier.get().getQbftConfigOptions(); + qbftConfig = genesisConfigOptions.getQbftConfigOptions(); bftEventQueue = new BftEventQueue(qbftConfig.getMessageQueueLimit()); - qbftForksSchedule = QbftForksSchedulesFactory.create(configOptionsSupplier.get()); + qbftForksSchedule = QbftForksSchedulesFactory.create(genesisConfigOptions); + bftConfigOptions = qbftConfig; } @Override protected JsonRpcMethods createAdditionalJsonRpcMethodFactory( - final ProtocolContext protocolContext) { + final ProtocolContext protocolContext, + final ProtocolSchedule protocolSchedule, + final MiningParameters miningParameters) { return new QbftJsonRpcMethods( - protocolContext, createReadOnlyValidatorProvider(protocolContext.getBlockchain())); + protocolContext, + protocolSchedule, + miningParameters, + createReadOnlyValidatorProvider(protocolContext.getBlockchain()), + bftConfigOptions); } private ValidatorProvider createReadOnlyValidatorProvider(final Blockchain blockchain) { @@ -274,12 +280,19 @@ protected MiningCoordinator createMiningCoordinator( blockchain, bftEventQueue); + // Update the next block period in seconds according to the transition schedule + protocolContext + .getBlockchain() + .observeBlockAdded( + o -> + miningParameters.setBlockPeriodSeconds( + qbftForksSchedule + .getFork(o.getBlock().getHeader().getNumber() + 1) + .getValue() + .getBlockPeriodSeconds())); + if (syncState.isInitialSyncPhaseDone()) { - LOG.info("Starting QBFT mining coordinator"); miningCoordinator.enable(); - miningCoordinator.start(); - } else { - LOG.info("QBFT mining coordinator not starting while initial sync in progress"); } syncState.subscribeCompletionReached( @@ -313,12 +326,16 @@ protected PluginServiceFactory createAdditionalPluginServices( @Override protected ProtocolSchedule createProtocolSchedule() { return QbftProtocolScheduleBuilder.create( - configOptionsSupplier.get(), + genesisConfigOptions, qbftForksSchedule, privacyParameters, isRevertReasonEnabled, bftExtraDataCodec().get(), - evmConfiguration); + evmConfiguration, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem); } @Override @@ -346,7 +363,7 @@ private boolean usingValidatorBlockHeaderModeButNoSignersIn( } private boolean isValidatorContractMode() { - return configOptionsSupplier.get().getQbftConfigOptions().isValidatorContractMode(); + return genesisConfigOptions.getQbftConfigOptions().isValidatorContractMode(); } private boolean signersExistIn(final BlockHeader genesisBlockHeader) { @@ -361,7 +378,7 @@ protected BftContext createConsensusContext( final EpochManager epochManager = new EpochManager(qbftConfig.getEpochLength()); final BftValidatorOverrides validatorOverrides = - convertBftForks(configOptionsSupplier.get().getTransitions().getQbftForks()); + convertBftForks(genesisConfigOptions.getTransitions().getQbftForks()); final BlockValidatorProvider blockValidatorProvider = BlockValidatorProvider.forkingValidatorProvider( blockchain, epochManager, bftBlockInterface().get(), validatorOverrides); @@ -376,8 +393,7 @@ protected BftContext createConsensusContext( new ForkingValidatorProvider( blockchain, qbftForksSchedule, blockValidatorProvider, transactionValidatorProvider); - return new QbftContext( - validatorProvider, epochManager, bftBlockInterface().get(), pkiBlockCreationConfiguration); + return new BftContext(validatorProvider, epochManager, bftBlockInterface().get()); } private BftValidatorOverrides convertBftForks(final List bftForks) { diff --git a/besu/src/main/java/org/hyperledger/besu/controller/TransitionBesuControllerBuilder.java b/besu/src/main/java/org/hyperledger/besu/controller/TransitionBesuControllerBuilder.java index 8231f8e26a5..ee2611d9c87 100644 --- a/besu/src/main/java/org/hyperledger/besu/controller/TransitionBesuControllerBuilder.java +++ b/besu/src/main/java/org/hyperledger/besu/controller/TransitionBesuControllerBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,14 +15,12 @@ package org.hyperledger.besu.controller; import org.hyperledger.besu.config.GenesisConfigFile; -import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.consensus.merge.MergeContext; import org.hyperledger.besu.consensus.merge.PostMergeContext; import org.hyperledger.besu.consensus.merge.TransitionBackwardSyncContext; import org.hyperledger.besu.consensus.merge.TransitionContext; import org.hyperledger.besu.consensus.merge.TransitionProtocolSchedule; import org.hyperledger.besu.consensus.merge.blockcreation.TransitionCoordinator; -import org.hyperledger.besu.consensus.qbft.pki.PkiBlockCreationConfiguration; import org.hyperledger.besu.cryptoservices.NodeKey; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ConsensusContext; @@ -32,9 +30,9 @@ import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; +import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; -import org.hyperledger.besu.ethereum.core.Synchronizer; import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthMessages; @@ -50,17 +48,15 @@ import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.forkid.ForkIdManager; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; -import org.hyperledger.besu.ethereum.worldstate.Pruner; -import org.hyperledger.besu.ethereum.worldstate.PrunerConfiguration; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.ObservableMetricsSystem; import org.hyperledger.besu.plugin.services.permissioning.NodeMessagePermissioningProvider; -import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelectorFactory; import java.math.BigInteger; import java.nio.file.Path; @@ -134,7 +130,13 @@ protected MiningCoordinator createMiningCoordinator( transitionProtocolSchedule.getPreMergeSchedule(), protocolContext, transactionPool, - MiningParameters.MINING_DISABLED, + ImmutableMiningParameters.builder() + .from(miningParameters) + .mutableInitValues( + ImmutableMiningParameters.MutableInitValues.builder() + .isMiningEnabled(false) + .build()) + .build(), syncState, ethProtocolManager), mergeBesuControllerBuilder.createTransitionMiningCoordinator( @@ -160,7 +162,8 @@ protected EthProtocolManager createEthProtocolManager( final EthMessages ethMessages, final EthScheduler scheduler, final List peerValidators, - final Optional mergePeerFilter) { + final Optional mergePeerFilter, + final ForkIdManager forkIdManager) { return mergeBesuControllerBuilder.createEthProtocolManager( protocolContext, synchronizerConfiguration, @@ -171,7 +174,8 @@ protected EthProtocolManager createEthProtocolManager( ethMessages, scheduler, peerValidators, - mergePeerFilter); + mergePeerFilter, + forkIdManager); } @Override @@ -189,15 +193,10 @@ protected ProtocolContext createProtocolContext( final MutableBlockchain blockchain, final WorldStateArchive worldStateArchive, final ProtocolSchedule protocolSchedule, - final ConsensusContextFactory consensusContextFactory, - final Optional transactionSelectorFactory) { + final ConsensusContextFactory consensusContextFactory) { final ProtocolContext protocolContext = super.createProtocolContext( - blockchain, - worldStateArchive, - protocolSchedule, - consensusContextFactory, - transactionSelectorFactory); + blockchain, worldStateArchive, protocolSchedule, consensusContextFactory); transitionProtocolSchedule.setProtocolContext(protocolContext); return protocolContext; } @@ -221,30 +220,26 @@ protected PluginServiceFactory createAdditionalPluginServices( } @Override - protected Synchronizer createSynchronizer( + protected DefaultSynchronizer createSynchronizer( final ProtocolSchedule protocolSchedule, - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final ProtocolContext protocolContext, - final Optional maybePruner, final EthContext ethContext, final SyncState syncState, final EthProtocolManager ethProtocolManager, final PivotBlockSelector pivotBlockSelector) { DefaultSynchronizer sync = - (DefaultSynchronizer) - super.createSynchronizer( - protocolSchedule, - worldStateStorage, - protocolContext, - maybePruner, - ethContext, - syncState, - ethProtocolManager, - pivotBlockSelector); - final GenesisConfigOptions maybeForTTD = configOptionsSupplier.get(); + super.createSynchronizer( + protocolSchedule, + worldStateStorageCoordinator, + protocolContext, + ethContext, + syncState, + ethProtocolManager, + pivotBlockSelector); - if (maybeForTTD.getTerminalTotalDifficulty().isPresent()) { + if (genesisConfigOptions.getTerminalTotalDifficulty().isPresent()) { LOG.info( "TTD present, creating DefaultSynchronizer that stops propagating after finalization"); protocolContext @@ -255,6 +250,7 @@ protected Synchronizer createSynchronizer( return sync; } + @SuppressWarnings("UnusedVariable") private void initTransitionWatcher( final ProtocolContext protocolContext, final TransitionCoordinator composedCoordinator) { @@ -360,13 +356,6 @@ public BesuControllerBuilder privacyParameters(final PrivacyParameters privacyPa return propagateConfig(z -> z.privacyParameters(privacyParameters)); } - @Override - public BesuControllerBuilder pkiBlockCreationConfiguration( - final Optional pkiBlockCreationConfiguration) { - super.pkiBlockCreationConfiguration(pkiBlockCreationConfiguration); - return propagateConfig(z -> z.pkiBlockCreationConfiguration(pkiBlockCreationConfiguration)); - } - @Override public BesuControllerBuilder dataDirectory(final Path dataDirectory) { super.dataDirectory(dataDirectory); @@ -393,22 +382,10 @@ public BesuControllerBuilder isRevertReasonEnabled(final boolean isRevertReasonE } @Override - public BesuControllerBuilder isPruningEnabled(final boolean isPruningEnabled) { - super.isPruningEnabled(isPruningEnabled); - return propagateConfig(z -> z.isPruningEnabled(isPruningEnabled)); - } - - @Override - public BesuControllerBuilder pruningConfiguration(final PrunerConfiguration prunerConfiguration) { - super.pruningConfiguration(prunerConfiguration); - return propagateConfig(z -> z.pruningConfiguration(prunerConfiguration)); - } - - @Override - public BesuControllerBuilder genesisConfigOverrides( - final Map genesisConfigOverrides) { - super.genesisConfigOverrides(genesisConfigOverrides); - return propagateConfig(z -> z.genesisConfigOverrides(genesisConfigOverrides)); + public BesuControllerBuilder isParallelTxProcessingEnabled( + final boolean isParallelTxProcessingEnabled) { + super.isParallelTxProcessingEnabled(isParallelTxProcessingEnabled); + return propagateConfig(z -> z.isParallelTxProcessingEnabled(isParallelTxProcessingEnabled)); } @Override @@ -436,9 +413,9 @@ public BesuControllerBuilder dataStorageConfiguration( return propagateConfig(z -> z.dataStorageConfiguration(dataStorageConfiguration)); } - private BesuControllerBuilder propagateConfig(final Consumer toPropogate) { - toPropogate.accept(preMergeBesuControllerBuilder); - toPropogate.accept(mergeBesuControllerBuilder); + private BesuControllerBuilder propagateConfig(final Consumer toPropagate) { + toPropagate.accept(preMergeBesuControllerBuilder); + toPropagate.accept(mergeBesuControllerBuilder); return this; } } diff --git a/besu/src/main/java/org/hyperledger/besu/services/BesuConfigurationImpl.java b/besu/src/main/java/org/hyperledger/besu/services/BesuConfigurationImpl.java index a2974115522..0adbda23b74 100644 --- a/besu/src/main/java/org/hyperledger/besu/services/BesuConfigurationImpl.java +++ b/besu/src/main/java/org/hyperledger/besu/services/BesuConfigurationImpl.java @@ -14,25 +14,79 @@ */ package org.hyperledger.besu.services; +import org.hyperledger.besu.cli.options.stable.JsonRpcHttpOptions; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.plugin.services.BesuConfiguration; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import java.nio.file.Path; +import java.util.Optional; /** A concrete implementation of BesuConfiguration which is used in Besu plugin framework. */ public class BesuConfigurationImpl implements BesuConfiguration { + private Path storagePath; + private Path dataPath; + private DataStorageConfiguration dataStorageConfiguration; - private final Path storagePath; - private final Path dataPath; + // defaults + private MiningParameters miningParameters = MiningParameters.newDefault(); + private Optional rpcHttpHost = Optional.of("http://localhost"); + private Optional rpcHttpPort = Optional.of(8545); + + /** Default Constructor. */ + public BesuConfigurationImpl() {} /** - * BesuConfigurationImpl Constructor. + * Post creation initialization * * @param dataPath The Path representing data folder * @param storagePath The path representing storage folder + * @param dataStorageConfiguration The data storage configuration + * @return BesuConfigurationImpl instance */ - public BesuConfigurationImpl(final Path dataPath, final Path storagePath) { + public BesuConfigurationImpl init( + final Path dataPath, + final Path storagePath, + final DataStorageConfiguration dataStorageConfiguration) { this.dataPath = dataPath; this.storagePath = storagePath; + this.dataStorageConfiguration = dataStorageConfiguration; + return this; + } + + /** + * Set the mining parameters + * + * @param miningParameters configured mining parameters + * @return BesuConfigurationImpl instance + */ + public BesuConfigurationImpl withMiningParameters(final MiningParameters miningParameters) { + this.miningParameters = miningParameters; + return this; + } + + /** + * Set the RPC http options + * + * @param rpcHttpOptions configured rpc http options + * @return BesuConfigurationImpl instance + */ + public BesuConfigurationImpl withJsonRpcHttpOptions(final JsonRpcHttpOptions rpcHttpOptions) { + this.rpcHttpHost = Optional.ofNullable(rpcHttpOptions.getRpcHttpHost()); + this.rpcHttpPort = Optional.ofNullable(rpcHttpOptions.getRpcHttpPort()); + return this; + } + + @Override + public Optional getRpcHttpHost() { + return rpcHttpHost; + } + + @Override + public Optional getRpcHttpPort() { + return rpcHttpPort; } @Override @@ -44,4 +98,48 @@ public Path getStoragePath() { public Path getDataPath() { return dataPath; } + + @Override + public DataStorageFormat getDatabaseFormat() { + return dataStorageConfiguration.getDataStorageFormat(); + } + + @Override + public Wei getMinGasPrice() { + return miningParameters.getMinTransactionGasPrice(); + } + + @Override + public org.hyperledger.besu.plugin.services.storage.DataStorageConfiguration + getDataStorageConfiguration() { + return new DataStoreConfigurationImpl(dataStorageConfiguration); + } + + /** + * A concrete implementation of DataStorageConfiguration which is used in Besu plugin framework. + */ + public static class DataStoreConfigurationImpl + implements org.hyperledger.besu.plugin.services.storage.DataStorageConfiguration { + + private final DataStorageConfiguration dataStorageConfiguration; + + /** + * Instantiate the concrete implementation of the plugin DataStorageConfiguration. + * + * @param dataStorageConfiguration The Ethereum core module data storage configuration + */ + public DataStoreConfigurationImpl(final DataStorageConfiguration dataStorageConfiguration) { + this.dataStorageConfiguration = dataStorageConfiguration; + } + + @Override + public DataStorageFormat getDatabaseFormat() { + return dataStorageConfiguration.getDataStorageFormat(); + } + + @Override + public boolean getReceiptCompactionEnabled() { + return dataStorageConfiguration.getReceiptCompactionEnabled(); + } + } } diff --git a/besu/src/main/java/org/hyperledger/besu/services/BesuEventsImpl.java b/besu/src/main/java/org/hyperledger/besu/services/BesuEventsImpl.java index 205b325ea52..aeb7025872a 100644 --- a/besu/src/main/java/org/hyperledger/besu/services/BesuEventsImpl.java +++ b/besu/src/main/java/org/hyperledger/besu/services/BesuEventsImpl.java @@ -18,6 +18,7 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.api.query.LogsQuery; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.Difficulty; @@ -44,6 +45,7 @@ public class BesuEventsImpl implements BesuEvents { private final BlockBroadcaster blockBroadcaster; private final TransactionPool transactionPool; private final SyncState syncState; + private final BadBlockManager badBlockManager; /** * Constructor for BesuEventsImpl @@ -52,16 +54,19 @@ public class BesuEventsImpl implements BesuEvents { * @param blockBroadcaster An instance of BlockBroadcaster * @param transactionPool An instance of TransactionPool * @param syncState An instance of SyncState + * @param badBlockManager A cache of bad blocks encountered on the network */ public BesuEventsImpl( final Blockchain blockchain, final BlockBroadcaster blockBroadcaster, final TransactionPool transactionPool, - final SyncState syncState) { + final SyncState syncState, + final BadBlockManager badBlockManager) { this.blockchain = blockchain; this.blockBroadcaster = blockBroadcaster; this.transactionPool = transactionPool; this.syncState = syncState; + this.badBlockManager = badBlockManager; } @Override @@ -109,6 +114,11 @@ public void removeBlockReorgListener(final long listenerIdentifier) { blockchain.removeObserver(listenerIdentifier); } + @Override + public long addInitialSyncCompletionListener(final InitialSyncCompletionListener listener) { + return syncState.subscribeCompletionReached(listener); + } + @Override public long addTransactionAddedListener(final TransactionAddedListener listener) { return transactionPool.subscribePendingTransactions(listener::onTransactionAdded); @@ -166,6 +176,16 @@ public void removeLogListener(final long listenerIdentifier) { blockchain.removeObserver(listenerIdentifier); } + @Override + public long addBadBlockListener(final BadBlockListener listener) { + return badBlockManager.subscribeToBadBlocks(listener); + } + + @Override + public void removeBadBlockListener(final long listenerIdentifier) { + badBlockManager.unsubscribeFromBadBlocks(listenerIdentifier); + } + private static PropagatedBlockContext blockPropagatedContext( final Supplier blockHeaderSupplier, final Supplier blockBodySupplier, diff --git a/besu/src/main/java/org/hyperledger/besu/services/BesuPluginContextImpl.java b/besu/src/main/java/org/hyperledger/besu/services/BesuPluginContextImpl.java index 184c85f45de..c89733f9354 100644 --- a/besu/src/main/java/org/hyperledger/besu/services/BesuPluginContextImpl.java +++ b/besu/src/main/java/org/hyperledger/besu/services/BesuPluginContextImpl.java @@ -17,6 +17,7 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; +import org.hyperledger.besu.ethereum.core.plugins.PluginConfiguration; import org.hyperledger.besu.plugin.BesuContext; import org.hyperledger.besu.plugin.BesuPlugin; import org.hyperledger.besu.plugin.services.BesuService; @@ -35,11 +36,13 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; import java.util.Optional; import java.util.ServiceLoader; -import java.util.function.Predicate; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; +import java.util.stream.StreamSupport; import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; @@ -73,9 +76,16 @@ private enum Lifecycle { private Lifecycle state = Lifecycle.UNINITIALIZED; private final Map, ? super BesuService> serviceRegistry = new HashMap<>(); - private final List plugins = new ArrayList<>(); + + private List detectedPlugins = new ArrayList<>(); + private List requestedPlugins = new ArrayList<>(); + + private final List registeredPlugins = new ArrayList<>(); + private final List pluginVersions = new ArrayList<>(); - final List lines = new ArrayList<>(); + + /** Instantiates a new Besu plugin context. */ + public BesuPluginContextImpl() {} /** * Add service. @@ -99,75 +109,100 @@ public Optional getService(final Class serviceType return Optional.ofNullable((T) serviceRegistry.get(serviceType)); } + private List detectPlugins(final PluginConfiguration config) { + ClassLoader pluginLoader = + pluginDirectoryLoader(config.getPluginsDir()).orElse(getClass().getClassLoader()); + ServiceLoader serviceLoader = ServiceLoader.load(BesuPlugin.class, pluginLoader); + return StreamSupport.stream(serviceLoader.spliterator(), false).toList(); + } + /** - * Register plugins. + * Registers plugins based on the provided {@link PluginConfiguration}. This method finds plugins + * according to the configuration settings, filters them if necessary and then registers the + * filtered or found plugins * - * @param pluginsDir the plugins dir + * @param config The configuration settings used to find and filter plugins for registration. The + * configuration includes the plugin directory and any configured plugin identifiers if + * applicable. + * @throws IllegalStateException if the system is not in the UNINITIALIZED state. */ - public void registerPlugins(final Path pluginsDir) { - lines.add("Plugins:"); + public void registerPlugins(final PluginConfiguration config) { checkState( state == Lifecycle.UNINITIALIZED, - "Besu plugins have already been registered. Cannot register additional plugins."); - - final ClassLoader pluginLoader = - pluginDirectoryLoader(pluginsDir).orElse(this.getClass().getClassLoader()); - + "Besu plugins have already been registered. Cannot register additional plugins."); state = Lifecycle.REGISTERING; - final ServiceLoader serviceLoader = - ServiceLoader.load(BesuPlugin.class, pluginLoader); + if (config.isExternalPluginsEnabled()) { + detectedPlugins = detectPlugins(config); - int pluginsCount = 0; - for (final BesuPlugin plugin : serviceLoader) { - pluginsCount++; - try { - plugin.register(this); - LOG.info("Registered plugin of type {}.", plugin.getClass().getName()); - lines.add(String.format(plugin.getClass().getSimpleName())); - addPluginVersion(plugin); - } catch (final Exception e) { - LOG.error( - "Error registering plugin of type " - + plugin.getClass().getName() - + ", start and stop will not be called.", - e); - lines.add(String.format("ERROR %s", plugin.getClass().getSimpleName())); - continue; + if (config.getRequestedPlugins().isEmpty()) { + // If no plugins were specified, register all detected plugins + registerPlugins(detectedPlugins); + } else { + // Register only the plugins that were explicitly requested and validated + requestedPlugins = config.getRequestedPlugins(); + // Match and validate the requested plugins against the detected plugins + List registeringPlugins = + matchAndValidateRequestedPlugins(requestedPlugins, detectedPlugins); + + registerPlugins(registeringPlugins); } - plugins.add(plugin); + } else { + LOG.debug("External plugins are disabled. Skipping plugins registration."); } - - LOG.debug("Plugin registration complete."); - lines.add( - String.format( - "TOTAL = %d of %d plugins successfully loaded", plugins.size(), pluginsCount)); - lines.add(String.format("from %s", pluginsDir.toAbsolutePath())); - state = Lifecycle.REGISTERED; } - /** - * get the summary log, as a list of string lines - * - * @return the summary - */ - public List getPluginsSummaryLog() { - return lines; + private List matchAndValidateRequestedPlugins( + final List requestedPluginNames, final List detectedPlugins) + throws NoSuchElementException { + + // Filter detected plugins to include only those that match the requested names + List matchingPlugins = + detectedPlugins.stream() + .filter(plugin -> requestedPluginNames.contains(plugin.getClass().getSimpleName())) + .toList(); + + // Check if all requested plugins were found among the detected plugins + if (matchingPlugins.size() != requestedPluginNames.size()) { + // Find which requested plugins were not matched to throw a detailed exception + Set matchedPluginNames = + matchingPlugins.stream() + .map(plugin -> plugin.getClass().getSimpleName()) + .collect(Collectors.toSet()); + String missingPlugins = + requestedPluginNames.stream() + .filter(name -> !matchedPluginNames.contains(name)) + .collect(Collectors.joining(", ")); + throw new NoSuchElementException( + "The following requested plugins were not found: " + missingPlugins); + } + return matchingPlugins; } - private void addPluginVersion(final BesuPlugin plugin) { - final Package pluginPackage = plugin.getClass().getPackage(); - final String implTitle = - Optional.ofNullable(pluginPackage.getImplementationTitle()) - .filter(Predicate.not(String::isBlank)) - .orElse(plugin.getClass().getSimpleName()); - final String implVersion = - Optional.ofNullable(pluginPackage.getImplementationVersion()) - .filter(Predicate.not(String::isBlank)) - .orElse(""); - final String pluginVersion = implTitle + "/v" + implVersion; - pluginVersions.add(pluginVersion); + private void registerPlugins(final List pluginsToRegister) { + + for (final BesuPlugin plugin : pluginsToRegister) { + if (registerPlugin(plugin)) { + registeredPlugins.add(plugin); + } + } + } + + private boolean registerPlugin(final BesuPlugin plugin) { + try { + plugin.register(this); + LOG.info("Registered plugin of type {}.", plugin.getClass().getName()); + pluginVersions.add(plugin.getVersion()); + } catch (final Exception e) { + LOG.error( + "Error registering plugin of type " + + plugin.getClass().getName() + + ", start and stop will not be called.", + e); + return false; + } + return true; } /** Before external services. */ @@ -178,7 +213,7 @@ public void beforeExternalServices() { Lifecycle.REGISTERED, state); state = Lifecycle.BEFORE_EXTERNAL_SERVICES_STARTED; - final Iterator pluginsIterator = plugins.iterator(); + final Iterator pluginsIterator = registeredPlugins.iterator(); while (pluginsIterator.hasNext()) { final BesuPlugin plugin = pluginsIterator.next(); @@ -209,7 +244,7 @@ public void startPlugins() { Lifecycle.BEFORE_EXTERNAL_SERVICES_FINISHED, state); state = Lifecycle.BEFORE_MAIN_LOOP_STARTED; - final Iterator pluginsIterator = plugins.iterator(); + final Iterator pluginsIterator = registeredPlugins.iterator(); while (pluginsIterator.hasNext()) { final BesuPlugin plugin = pluginsIterator.next(); @@ -231,6 +266,25 @@ public void startPlugins() { state = Lifecycle.BEFORE_MAIN_LOOP_FINISHED; } + /** Execute all plugin setup code after external services. */ + public void afterExternalServicesMainLoop() { + checkState( + state == Lifecycle.BEFORE_MAIN_LOOP_FINISHED, + "BesuContext should be in state %s but it was in %s", + Lifecycle.BEFORE_MAIN_LOOP_FINISHED, + state); + final Iterator pluginsIterator = registeredPlugins.iterator(); + + while (pluginsIterator.hasNext()) { + final BesuPlugin plugin = pluginsIterator.next(); + try { + plugin.afterExternalServicePostMainLoop(); + } finally { + pluginsIterator.remove(); + } + } + } + /** Stop plugins. */ public void stopPlugins() { checkState( @@ -240,7 +294,7 @@ public void stopPlugins() { state); state = Lifecycle.STOPPING; - for (final BesuPlugin plugin : plugins) { + for (final BesuPlugin plugin : registeredPlugins) { try { plugin.stop(); LOG.debug("Stopped plugin of type {}.", plugin.getClass().getName()); @@ -253,11 +307,6 @@ public void stopPlugins() { state = Lifecycle.STOPPED; } - @Override - public Collection getPluginVersions() { - return Collections.unmodifiableList(pluginVersions); - } - private static URL pathToURIOrNull(final Path p) { try { return p.toUri().toURL(); @@ -266,16 +315,6 @@ private static URL pathToURIOrNull(final Path p) { } } - /** - * Gets plugins. - * - * @return the plugins - */ - @VisibleForTesting - List getPlugins() { - return Collections.unmodifiableList(plugins); - } - private Optional pluginDirectoryLoader(final Path pluginsDir) { if (pluginsDir != null && pluginsDir.toFile().isDirectory()) { LOG.debug("Searching for plugins in {}", pluginsDir.toAbsolutePath()); @@ -299,14 +338,71 @@ private Optional pluginDirectoryLoader(final Path pluginsDir) { return Optional.empty(); } + @Override + public Collection getPluginVersions() { + return Collections.unmodifiableList(pluginVersions); + } + + /** + * Gets plugins. + * + * @return the plugins + */ + @VisibleForTesting + List getRegisteredPlugins() { + return Collections.unmodifiableList(registeredPlugins); + } + /** * Gets named plugins. * * @return the named plugins */ public Map getNamedPlugins() { - return plugins.stream() + return registeredPlugins.stream() .filter(plugin -> plugin.getName().isPresent()) .collect(Collectors.toMap(plugin -> plugin.getName().get(), plugin -> plugin, (a, b) -> b)); } + + /** + * Generates a summary log of plugin registration. The summary includes registered plugins, + * detected but not registered (skipped) plugins + * + * @return A list of strings, each representing a line in the summary log. + */ + public List getPluginsSummaryLog() { + List summary = new ArrayList<>(); + summary.add("Plugin Registration Summary:"); + + // Log registered plugins with their names and versions + if (registeredPlugins.isEmpty()) { + summary.add("No plugins have been registered."); + } else { + summary.add("Registered Plugins:"); + registeredPlugins.forEach( + plugin -> + summary.add( + String.format( + " - %s (%s)", plugin.getClass().getSimpleName(), plugin.getVersion()))); + } + + // Identify and log detected but not registered (skipped) plugins + List skippedPlugins = + detectedPlugins.stream().filter(plugin -> !registeredPlugins.contains(plugin)).toList(); + + if (!skippedPlugins.isEmpty()) { + summary.add("Detected but not registered:"); + skippedPlugins.forEach( + plugin -> + summary.add( + String.format( + " - %s (%s)", plugin.getClass().getSimpleName(), plugin.getVersion()))); + } + summary.add( + String.format( + "TOTAL = %d of %d plugins successfully registered.", + registeredPlugins.size(), detectedPlugins.size())); + + return summary; + } } diff --git a/besu/src/main/java/org/hyperledger/besu/services/BlockchainServiceImpl.java b/besu/src/main/java/org/hyperledger/besu/services/BlockchainServiceImpl.java index a3b37a6665a..1f014ee0610 100644 --- a/besu/src/main/java/org/hyperledger/besu/services/BlockchainServiceImpl.java +++ b/besu/src/main/java/org/hyperledger/besu/services/BlockchainServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,32 +12,50 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.services; -import org.hyperledger.besu.ethereum.chain.Blockchain; -import org.hyperledger.besu.ethereum.core.BlockBody; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.MutableBlockchain; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket; +import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.plugin.Unstable; +import org.hyperledger.besu.plugin.data.BlockBody; import org.hyperledger.besu.plugin.data.BlockContext; import org.hyperledger.besu.plugin.data.BlockHeader; +import org.hyperledger.besu.plugin.data.TransactionReceipt; import org.hyperledger.besu.plugin.services.BlockchainService; +import java.util.List; import java.util.Optional; import java.util.function.Supplier; +import java.util.stream.Collectors; /** The Blockchain service implementation. */ @Unstable public class BlockchainServiceImpl implements BlockchainService { - private final Blockchain blockchain; + private ProtocolContext protocolContext; + private ProtocolSchedule protocolSchedule; + private MutableBlockchain blockchain; + + /** Instantiates a new Blockchain service implementation. */ + public BlockchainServiceImpl() {} /** - * Instantiates a new Blockchain service. + * Initialize the Blockchain service. * - * @param blockchain the blockchain + * @param protocolContext the protocol context + * @param protocolSchedule the protocol schedule */ - public BlockchainServiceImpl(final Blockchain blockchain) { - this.blockchain = blockchain; + public void init(final ProtocolContext protocolContext, final ProtocolSchedule protocolSchedule) { + this.protocolContext = protocolContext; + this.protocolSchedule = protocolSchedule; + this.blockchain = protocolContext.getBlockchain(); } /** @@ -48,11 +66,107 @@ public BlockchainServiceImpl(final Blockchain blockchain) { */ @Override public Optional getBlockByNumber(final long number) { - return blockchain + return protocolContext + .getBlockchain() .getBlockByNumber(number) .map(block -> blockContext(block::getHeader, block::getBody)); } + @Override + public Hash getChainHeadHash() { + return protocolContext.getBlockchain().getChainHeadHash(); + } + + @Override + public BlockHeader getChainHeadHeader() { + return protocolContext.getBlockchain().getChainHeadHeader(); + } + + @Override + public Optional getNextBlockBaseFee() { + final var chainHeadHeader = protocolContext.getBlockchain().getChainHeadHeader(); + final var protocolSpec = + protocolSchedule.getForNextBlockHeader(chainHeadHeader, System.currentTimeMillis()); + return Optional.of(protocolSpec.getFeeMarket()) + .filter(FeeMarket::implementsBaseFee) + .map(BaseFeeMarket.class::cast) + .map( + feeMarket -> + feeMarket.computeBaseFee( + chainHeadHeader.getNumber() + 1, + chainHeadHeader.getBaseFee().orElse(Wei.ZERO), + chainHeadHeader.getGasUsed(), + feeMarket.targetGasUsed(chainHeadHeader))); + } + + @Override + public Optional> getReceiptsByBlockHash(final Hash blockHash) { + return blockchain + .getTxReceipts(blockHash) + .map( + list -> list.stream().map(TransactionReceipt.class::cast).collect(Collectors.toList())); + } + + @Override + public void storeBlock( + final BlockHeader blockHeader, + final BlockBody blockBody, + final List receipts) { + final org.hyperledger.besu.ethereum.core.BlockHeader coreHeader = + (org.hyperledger.besu.ethereum.core.BlockHeader) blockHeader; + final org.hyperledger.besu.ethereum.core.BlockBody coreBody = + (org.hyperledger.besu.ethereum.core.BlockBody) blockBody; + final List coreReceipts = + receipts.stream() + .map(org.hyperledger.besu.ethereum.core.TransactionReceipt.class::cast) + .toList(); + blockchain.unsafeImportBlock( + new Block(coreHeader, coreBody), + coreReceipts, + Optional.ofNullable(blockchain.calculateTotalDifficulty(coreHeader))); + } + + @Override + public Optional getSafeBlock() { + return blockchain.getSafeBlock(); + } + + @Override + public Optional getFinalizedBlock() { + return blockchain.getFinalized(); + } + + @Override + public void setFinalizedBlock(final Hash blockHash) { + final var protocolSpec = getProtocolSpec(blockHash); + + if (protocolSpec.isPoS()) { + throw new UnsupportedOperationException( + "Marking block as finalized is not supported for PoS networks"); + } + blockchain.setFinalized(blockHash); + } + + @Override + public void setSafeBlock(final Hash blockHash) { + final var protocolSpec = getProtocolSpec(blockHash); + + if (protocolSpec.isPoS()) { + throw new UnsupportedOperationException( + "Marking block as safe is not supported for PoS networks"); + } + + blockchain.setSafeBlock(blockHash); + } + + private ProtocolSpec getProtocolSpec(final Hash blockHash) { + return blockchain + .getBlockByHash(blockHash) + .map(Block::getHeader) + .map(protocolSchedule::getByBlockHeader) + .orElseThrow(() -> new IllegalArgumentException("Block not found: " + blockHash)); + } + private static BlockContext blockContext( final Supplier blockHeaderSupplier, final Supplier blockBodySupplier) { diff --git a/besu/src/main/java/org/hyperledger/besu/services/P2PServiceImpl.java b/besu/src/main/java/org/hyperledger/besu/services/P2PServiceImpl.java new file mode 100644 index 00000000000..dc1fb0833a8 --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/services/P2PServiceImpl.java @@ -0,0 +1,44 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.services; + +import org.hyperledger.besu.ethereum.p2p.network.P2PNetwork; +import org.hyperledger.besu.plugin.services.p2p.P2PService; + +/** Service to enable and disable P2P discovery. */ +public class P2PServiceImpl implements P2PService { + + private final P2PNetwork p2PNetwork; + + /** + * Creates a new P2PServiceImpl. + * + * @param p2PNetwork the P2P network to enable and disable. + */ + public P2PServiceImpl(final P2PNetwork p2PNetwork) { + this.p2PNetwork = p2PNetwork; + } + + /** Enables P2P discovery. */ + @Override + public void enableDiscovery() { + p2PNetwork.start(); + } + + @Override + public void disableDiscovery() { + p2PNetwork.stop(); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/services/PermissioningServiceImpl.java b/besu/src/main/java/org/hyperledger/besu/services/PermissioningServiceImpl.java index a44e70843bc..e43bf1c054e 100644 --- a/besu/src/main/java/org/hyperledger/besu/services/PermissioningServiceImpl.java +++ b/besu/src/main/java/org/hyperledger/besu/services/PermissioningServiceImpl.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.services; import org.hyperledger.besu.plugin.services.PermissioningService; @@ -20,6 +19,7 @@ import org.hyperledger.besu.plugin.services.permissioning.NodeMessagePermissioningProvider; import java.util.List; +import javax.inject.Inject; import com.google.common.collect.Lists; @@ -29,6 +29,10 @@ public class PermissioningServiceImpl implements PermissioningService { private final List connectionPermissioningProviders = Lists.newArrayList(); + /** Default Constructor. */ + @Inject + public PermissioningServiceImpl() {} + @Override public void registerNodePermissioningProvider( final NodeConnectionPermissioningProvider provider) { diff --git a/besu/src/main/java/org/hyperledger/besu/services/PluginTransactionValidatorServiceImpl.java b/besu/src/main/java/org/hyperledger/besu/services/PluginTransactionValidatorServiceImpl.java deleted file mode 100644 index 171a31388c8..00000000000 --- a/besu/src/main/java/org/hyperledger/besu/services/PluginTransactionValidatorServiceImpl.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright contributors to Hyperledger Besu. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.services; - -import org.hyperledger.besu.plugin.services.PluginTransactionValidatorService; -import org.hyperledger.besu.plugin.services.txvalidator.PluginTransactionValidatorFactory; - -/** The Transaction Validation service implementation. */ -public class PluginTransactionValidatorServiceImpl implements PluginTransactionValidatorService { - - private PluginTransactionValidatorFactory factory; - - @Override - public PluginTransactionValidatorFactory get() { - return factory; - } - - @Override - public void registerTransactionValidatorFactory( - final PluginTransactionValidatorFactory transactionValidatorFactory) { - factory = transactionValidatorFactory; - } -} diff --git a/besu/src/main/java/org/hyperledger/besu/services/PrivacyPluginServiceImpl.java b/besu/src/main/java/org/hyperledger/besu/services/PrivacyPluginServiceImpl.java index 25a8782b7b3..4592753b9ce 100644 --- a/besu/src/main/java/org/hyperledger/besu/services/PrivacyPluginServiceImpl.java +++ b/besu/src/main/java/org/hyperledger/besu/services/PrivacyPluginServiceImpl.java @@ -34,6 +34,9 @@ public class PrivacyPluginServiceImpl implements PrivacyPluginService { (privacyGroupId, privacyUserId, blockNumber) -> true; private PrivacyGroupGenesisProvider privacyGroupGenesisProvider; + /** Default Constructor. */ + public PrivacyPluginServiceImpl() {} + @Override public void setPayloadProvider(final PrivacyPluginPayloadProvider privacyPluginPayloadProvider) { this.privacyPluginPayloadProvider = privacyPluginPayloadProvider; diff --git a/besu/src/main/java/org/hyperledger/besu/services/RlpConverterServiceImpl.java b/besu/src/main/java/org/hyperledger/besu/services/RlpConverterServiceImpl.java new file mode 100644 index 00000000000..d7a7b1ce66c --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/services/RlpConverterServiceImpl.java @@ -0,0 +1,82 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.services; + +import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.plugin.data.BlockBody; +import org.hyperledger.besu.plugin.data.BlockHeader; +import org.hyperledger.besu.plugin.data.TransactionReceipt; +import org.hyperledger.besu.plugin.services.rlp.RlpConverterService; + +import org.apache.tuweni.bytes.Bytes; + +/** RLP Serialization/Deserialization service. */ +public class RlpConverterServiceImpl implements RlpConverterService { + + private final BlockHeaderFunctions blockHeaderFunctions; + + /** + * Constructor for RlpConverterServiceImpl. + * + * @param protocolSchedule the protocol schedule. + */ + public RlpConverterServiceImpl(final ProtocolSchedule protocolSchedule) { + this.blockHeaderFunctions = ScheduleBasedBlockHeaderFunctions.create(protocolSchedule); + } + + @Override + public BlockHeader buildHeaderFromRlp(final Bytes rlp) { + return org.hyperledger.besu.ethereum.core.BlockHeader.readFrom( + RLP.input(rlp), blockHeaderFunctions); + } + + @Override + public BlockBody buildBodyFromRlp(final Bytes rlp) { + return org.hyperledger.besu.ethereum.core.BlockBody.readWrappedBodyFrom( + RLP.input(rlp), blockHeaderFunctions); + } + + @Override + public TransactionReceipt buildReceiptFromRlp(final Bytes rlp) { + return org.hyperledger.besu.ethereum.core.TransactionReceipt.readFrom(RLP.input(rlp)); + } + + @Override + public Bytes buildRlpFromHeader(final BlockHeader blockHeader) { + return RLP.encode( + org.hyperledger.besu.ethereum.core.BlockHeader.convertPluginBlockHeader( + blockHeader, blockHeaderFunctions) + ::writeTo); + } + + @Override + public Bytes buildRlpFromBody(final BlockBody blockBody) { + return RLP.encode( + rlpOutput -> + ((org.hyperledger.besu.ethereum.core.BlockBody) blockBody) + .writeWrappedBodyTo(rlpOutput)); + } + + @Override + public Bytes buildRlpFromReceipt(final TransactionReceipt receipt) { + return RLP.encode( + rlpOutput -> + ((org.hyperledger.besu.ethereum.core.TransactionReceipt) receipt) + .writeToForNetwork(rlpOutput)); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/services/RpcEndpointServiceImpl.java b/besu/src/main/java/org/hyperledger/besu/services/RpcEndpointServiceImpl.java index 94bb5560839..dcfe29146e7 100644 --- a/besu/src/main/java/org/hyperledger/besu/services/RpcEndpointServiceImpl.java +++ b/besu/src/main/java/org/hyperledger/besu/services/RpcEndpointServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,20 +17,47 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.PluginJsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.plugin.services.RpcEndpointService; import org.hyperledger.besu.plugin.services.rpc.PluginRpcRequest; +import org.hyperledger.besu.plugin.services.rpc.PluginRpcResponse; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.Locale; import java.util.Map; +import java.util.NoSuchElementException; import java.util.function.Function; import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** The RPC endpoint service implementation. */ public class RpcEndpointServiceImpl implements RpcEndpointService { + private static final Logger LOG = LoggerFactory.getLogger(RpcEndpointServiceImpl.class); + private final Map> rpcMethods = new HashMap<>(); + private Map inProcessRpcMethods; + + /** Default Constructor. */ + public RpcEndpointServiceImpl() {} + + /** + * Init the service + * + * @param inProcessRpcMethods set of RPC methods that can be called + */ + public void init(final Map inProcessRpcMethods) { + this.inProcessRpcMethods = inProcessRpcMethods; + } @Override public void registerRPCEndpoint( @@ -44,6 +71,44 @@ public void registerRPCEndpoint( rpcMethods.put(namespace + "_" + functionName, function); } + @Override + public PluginRpcResponse call(final String methodName, final Object[] params) { + checkNotNull( + inProcessRpcMethods, + "Service not initialized yet, this method must be called after plugin 'beforeExternalServices' call completes"); + + LOG.atTrace() + .setMessage("Calling method:{} with params:{}") + .addArgument(methodName) + .addArgument(() -> Arrays.toString(params)) + .log(); + + final var method = inProcessRpcMethods.get(methodName); + + if (method == null) { + throw new NoSuchElementException("Unknown or not enabled method: " + methodName); + } + + final var requestContext = + new JsonRpcRequestContext(new JsonRpcRequest("2.0", methodName, params)); + final var response = method.response(requestContext); + return new PluginRpcResponse() { + @Override + public Object getResult() { + return switch (response.getType()) { + case NONE, UNAUTHORIZED -> null; + case SUCCESS -> ((JsonRpcSuccessResponse) response).getResult(); + case ERROR -> ((JsonRpcErrorResponse) response).getError(); + }; + } + + @Override + public RpcResponseType getType() { + return response.getType(); + } + }; + } + /** * Gets plugin methods. * @@ -58,7 +123,10 @@ public void registerRPCEndpoint( namespaces.stream() .anyMatch( namespace -> - entry.getKey().toUpperCase().startsWith(namespace.toUpperCase()))) + entry + .getKey() + .toUpperCase(Locale.ROOT) + .startsWith(namespace.toUpperCase(Locale.ROOT)))) .map(entry -> new PluginJsonRpcMethod(entry.getKey(), entry.getValue())) .collect(Collectors.toMap(PluginJsonRpcMethod::getName, e -> e)); } @@ -71,6 +139,7 @@ public void registerRPCEndpoint( */ public boolean hasNamespace(final String namespace) { return rpcMethods.keySet().stream() - .anyMatch(key -> key.toUpperCase().startsWith(namespace.toUpperCase())); + .anyMatch( + key -> key.toUpperCase(Locale.ROOT).startsWith(namespace.toUpperCase(Locale.ROOT))); } } diff --git a/besu/src/main/java/org/hyperledger/besu/services/SecurityModuleServiceImpl.java b/besu/src/main/java/org/hyperledger/besu/services/SecurityModuleServiceImpl.java index d2cc953ecbc..5605fe49b5f 100644 --- a/besu/src/main/java/org/hyperledger/besu/services/SecurityModuleServiceImpl.java +++ b/besu/src/main/java/org/hyperledger/besu/services/SecurityModuleServiceImpl.java @@ -21,9 +21,15 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; +import javax.inject.Inject; /** The Security module service implementation. */ public class SecurityModuleServiceImpl implements SecurityModuleService { + + /** Default Constructor. */ + @Inject + public SecurityModuleServiceImpl() {} + private final Map> securityModuleSuppliers = new ConcurrentHashMap<>(); diff --git a/besu/src/main/java/org/hyperledger/besu/services/StorageServiceImpl.java b/besu/src/main/java/org/hyperledger/besu/services/StorageServiceImpl.java index 6629da690e8..dd5d822ccc3 100644 --- a/besu/src/main/java/org/hyperledger/besu/services/StorageServiceImpl.java +++ b/besu/src/main/java/org/hyperledger/besu/services/StorageServiceImpl.java @@ -23,6 +23,7 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import javax.inject.Inject; /** The Storage service implementation. */ public class StorageServiceImpl implements StorageService { @@ -31,6 +32,7 @@ public class StorageServiceImpl implements StorageService { private final Map factories; /** Instantiates a new Storage service. */ + @Inject public StorageServiceImpl() { this.segments = List.of(KeyValueSegmentIdentifier.values()); this.factories = new ConcurrentHashMap<>(); diff --git a/besu/src/main/java/org/hyperledger/besu/services/SynchronizationServiceImpl.java b/besu/src/main/java/org/hyperledger/besu/services/SynchronizationServiceImpl.java new file mode 100644 index 00000000000..2e15e2ab82f --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/services/SynchronizationServiceImpl.java @@ -0,0 +1,160 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.services; + +import org.hyperledger.besu.consensus.merge.MergeContext; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.MutableBlockchain; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockImporter; +import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; +import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.common.DiffBasedWorldStateProvider; +import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.plugin.data.BlockBody; +import org.hyperledger.besu.plugin.data.BlockHeader; +import org.hyperledger.besu.plugin.services.sync.SynchronizationService; + +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Synchronization service. */ +public class SynchronizationServiceImpl implements SynchronizationService { + + private static final Logger LOG = LoggerFactory.getLogger(SynchronizationServiceImpl.class); + + private final ProtocolContext protocolContext; + private final ProtocolSchedule protocolSchedule; + + private final SyncState syncState; + private final Optional worldStateArchive; + + /** + * Constructor for SynchronizationServiceImpl. + * + * @param protocolContext protocol context + * @param protocolSchedule protocol schedule + * @param syncState sync state + * @param worldStateArchive world state archive + */ + public SynchronizationServiceImpl( + final ProtocolContext protocolContext, + final ProtocolSchedule protocolSchedule, + final SyncState syncState, + final WorldStateArchive worldStateArchive) { + this.protocolContext = protocolContext; + this.protocolSchedule = protocolSchedule; + this.syncState = syncState; + this.worldStateArchive = + Optional.ofNullable(worldStateArchive) + .filter(z -> z instanceof DiffBasedWorldStateProvider) + .map(DiffBasedWorldStateProvider.class::cast); + } + + @Override + public void fireNewUnverifiedForkchoiceEvent( + final Hash head, final Hash safeBlock, final Hash finalizedBlock) { + protocolContext + .safeConsensusContext(MergeContext.class) + .ifPresent(mc -> mc.fireNewUnverifiedForkchoiceEvent(head, safeBlock, finalizedBlock)); + protocolContext.getBlockchain().setFinalized(finalizedBlock); + protocolContext.getBlockchain().setSafeBlock(safeBlock); + } + + @Override + public boolean setHead(final BlockHeader blockHeader, final BlockBody blockBody) { + final BlockImporter blockImporter = + protocolSchedule + .getByBlockHeader((org.hyperledger.besu.ethereum.core.BlockHeader) blockHeader) + .getBlockImporter(); + return blockImporter + .importBlock( + protocolContext, + new Block( + (org.hyperledger.besu.ethereum.core.BlockHeader) blockHeader, + (org.hyperledger.besu.ethereum.core.BlockBody) blockBody), + HeaderValidationMode.SKIP_DETACHED) + .isImported(); + } + + @Override + public boolean setHeadUnsafe(final BlockHeader blockHeader, final BlockBody blockBody) { + final org.hyperledger.besu.ethereum.core.BlockHeader coreHeader = + (org.hyperledger.besu.ethereum.core.BlockHeader) blockHeader; + + final MutableBlockchain blockchain = protocolContext.getBlockchain(); + + if (worldStateArchive.flatMap(archive -> archive.getMutable(coreHeader, true)).isPresent()) { + if (coreHeader.getParentHash().equals(blockchain.getChainHeadHash())) { + LOG.atDebug() + .setMessage( + "Forwarding chain head to the block {} saved from a previous newPayload invocation") + .addArgument(coreHeader::toLogString) + .log(); + return blockchain.forwardToBlock(coreHeader); + } else { + LOG.atDebug() + .setMessage("New head {} is a chain reorg, rewind chain head to it") + .addArgument(coreHeader::toLogString) + .log(); + return blockchain.rewindToBlock(coreHeader.getBlockHash()); + } + } else { + LOG.atWarn() + .setMessage("The world state is unavailable, setting of head cannot be performed.") + .log(); + } + return false; + } + + @Override + public boolean isInitialSyncPhaseDone() { + return syncState.isInitialSyncPhaseDone(); + } + + @Override + public void disableWorldStateTrie() { + // TODO maybe find a best way in the future to delete and disable trie + worldStateArchive.ifPresent( + archive -> { + archive.getDefaultWorldStateConfig().setTrieDisabled(true); + final DiffBasedWorldStateKeyValueStorage worldStateStorage = + archive.getWorldStateKeyValueStorage(); + final Optional worldStateBlockHash = worldStateStorage.getWorldStateBlockHash(); + final Optional worldStateRootHash = worldStateStorage.getWorldStateRootHash(); + if (worldStateRootHash.isPresent() && worldStateBlockHash.isPresent()) { + worldStateStorage.clearTrie(); + // keep root and block hash in the trie branch + final DiffBasedWorldStateKeyValueStorage.Updater updater = worldStateStorage.updater(); + updater.saveWorldState( + worldStateBlockHash.get(), Bytes32.wrap(worldStateRootHash.get()), Bytes.EMPTY); + updater.commit(); + + // currently only bonsai needs an explicit upgrade to full flat db + if (worldStateStorage instanceof BonsaiWorldStateKeyValueStorage bonsaiStorage) { + bonsaiStorage.upgradeToFullFlatDbMode(); + } + } + }); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/services/TraceServiceImpl.java b/besu/src/main/java/org/hyperledger/besu/services/TraceServiceImpl.java index 98c233197f3..6a0804b6f46 100644 --- a/besu/src/main/java/org/hyperledger/besu/services/TraceServiceImpl.java +++ b/besu/src/main/java/org/hyperledger/besu/services/TraceServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -162,7 +162,6 @@ public void trace( blocks.forEach( block -> { results.addAll(trace(blockchain, block, chainUpdater, tracer)); - tracer.traceEndBlock(block.getHeader(), block.getBody()); }); afterTracing.accept(chainUpdater.getNextUpdater()); return Optional.of(results); @@ -180,7 +179,6 @@ private Optional> trace( block.getHash(), traceableState -> Optional.of(trace(blockchain, block, new ChainUpdater(traceableState), tracer))); - tracer.traceEndBlock(block.getHeader(), block.getBody()); return results; } @@ -212,29 +210,17 @@ private List trace( .orElse(BlobGas.ZERO)); final WorldUpdater worldUpdater = chainUpdater.getNextUpdater(); - tracer.traceStartTransaction(worldUpdater, transaction); final TransactionProcessingResult result = transactionProcessor.processTransaction( - blockchain, worldUpdater, header, transaction, - header.getCoinbase(), + protocolSpec.getMiningBeneficiaryCalculator().calculateBeneficiary(header), tracer, new CachingBlockHashLookup(header, blockchain), false, blobGasPrice); - long transactionGasUsed = transaction.getGasLimit() - result.getGasRemaining(); - tracer.traceEndTransaction( - worldUpdater, - transaction, - result.isSuccessful(), - result.getOutput(), - result.getLogs(), - transactionGasUsed, - 0); - results.add(result); }); diff --git a/besu/src/main/java/org/hyperledger/besu/services/TransactionPoolServiceImpl.java b/besu/src/main/java/org/hyperledger/besu/services/TransactionPoolServiceImpl.java new file mode 100644 index 00000000000..16d033ac9e4 --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/services/TransactionPoolServiceImpl.java @@ -0,0 +1,43 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.services; + +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; +import org.hyperledger.besu.plugin.services.transactionpool.TransactionPoolService; + +/** Service to enable and disable the transaction pool. */ +public class TransactionPoolServiceImpl implements TransactionPoolService { + + private final TransactionPool transactionPool; + + /** + * Creates a new TransactionPoolServiceImpl. + * + * @param transactionPool the transaction pool to control + */ + public TransactionPoolServiceImpl(final TransactionPool transactionPool) { + this.transactionPool = transactionPool; + } + + @Override + public void disableTransactionPool() { + transactionPool.setDisabled(); + } + + @Override + public void enableTransactionPool() { + transactionPool.setEnabled(); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/services/TransactionPoolValidatorServiceImpl.java b/besu/src/main/java/org/hyperledger/besu/services/TransactionPoolValidatorServiceImpl.java new file mode 100644 index 00000000000..1e1f94fb324 --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/services/TransactionPoolValidatorServiceImpl.java @@ -0,0 +1,43 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.services; + +import org.hyperledger.besu.plugin.services.TransactionPoolValidatorService; +import org.hyperledger.besu.plugin.services.txvalidator.PluginTransactionPoolValidator; +import org.hyperledger.besu.plugin.services.txvalidator.PluginTransactionPoolValidatorFactory; + +import java.util.Optional; + +/** The Transaction pool validator service implementation. */ +public class TransactionPoolValidatorServiceImpl implements TransactionPoolValidatorService { + + private Optional factory = Optional.empty(); + + /** Default Constructor. */ + public TransactionPoolValidatorServiceImpl() {} + + @Override + public PluginTransactionPoolValidator createTransactionValidator() { + return factory + .map(PluginTransactionPoolValidatorFactory::createTransactionValidator) + .orElse(PluginTransactionPoolValidator.VALIDATE_ALL); + } + + @Override + public void registerPluginTransactionValidatorFactory( + final PluginTransactionPoolValidatorFactory pluginTransactionPoolValidatorFactory) { + factory = Optional.ofNullable(pluginTransactionPoolValidatorFactory); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/services/TransactionSelectionServiceImpl.java b/besu/src/main/java/org/hyperledger/besu/services/TransactionSelectionServiceImpl.java index 3175fe731f9..1ea8db7eab7 100644 --- a/besu/src/main/java/org/hyperledger/besu/services/TransactionSelectionServiceImpl.java +++ b/besu/src/main/java/org/hyperledger/besu/services/TransactionSelectionServiceImpl.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.services; import org.hyperledger.besu.plugin.services.TransactionSelectionService; +import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelector; import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelectorFactory; import java.util.Optional; @@ -22,16 +23,21 @@ /** The Transaction Selection service implementation. */ public class TransactionSelectionServiceImpl implements TransactionSelectionService { + /** Default Constructor. */ + public TransactionSelectionServiceImpl() {} + private Optional factory = Optional.empty(); @Override - public Optional get() { - return factory; + public PluginTransactionSelector createPluginTransactionSelector() { + return factory + .map(PluginTransactionSelectorFactory::create) + .orElse(PluginTransactionSelector.ACCEPT_ALL); } @Override - public void registerTransactionSelectorFactory( - final PluginTransactionSelectorFactory transactionSelectorFactory) { - factory = Optional.ofNullable(transactionSelectorFactory); + public void registerPluginTransactionSelectorFactory( + final PluginTransactionSelectorFactory pluginTransactionSelectorFactory) { + factory = Optional.ofNullable(pluginTransactionSelectorFactory); } } diff --git a/besu/src/main/java/org/hyperledger/besu/services/TransactionSimulationServiceImpl.java b/besu/src/main/java/org/hyperledger/besu/services/TransactionSimulationServiceImpl.java new file mode 100644 index 00000000000..54cce205a36 --- /dev/null +++ b/besu/src/main/java/org/hyperledger/besu/services/TransactionSimulationServiceImpl.java @@ -0,0 +1,89 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.services; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Transaction; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.mainnet.ImmutableTransactionValidationParams; +import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; +import org.hyperledger.besu.ethereum.mainnet.ValidationResult; +import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; +import org.hyperledger.besu.ethereum.transaction.CallParameter; +import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; +import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; +import org.hyperledger.besu.evm.tracing.OperationTracer; +import org.hyperledger.besu.plugin.Unstable; +import org.hyperledger.besu.plugin.data.TransactionSimulationResult; +import org.hyperledger.besu.plugin.services.TransactionSimulationService; + +import java.util.Optional; + +/** TransactionSimulationServiceImpl */ +@Unstable +public class TransactionSimulationServiceImpl implements TransactionSimulationService { + private static final TransactionValidationParams SIMULATOR_ALLOWING_EXCEEDING_BALANCE = + ImmutableTransactionValidationParams.builder() + .from(TransactionValidationParams.transactionSimulator()) + .isAllowExceedingBalance(true) + .build(); + private Blockchain blockchain; + private TransactionSimulator transactionSimulator; + + /** Create an instance to be configured */ + public TransactionSimulationServiceImpl() {} + + /** + * Configure the service + * + * @param blockchain the blockchain + * @param transactionSimulator transaction simulator + */ + public void init(final Blockchain blockchain, final TransactionSimulator transactionSimulator) { + this.blockchain = blockchain; + this.transactionSimulator = transactionSimulator; + } + + @Override + public Optional simulate( + final Transaction transaction, + final Hash blockHash, + final OperationTracer operationTracer, + final boolean isAllowExceedingBalance) { + + final CallParameter callParameter = CallParameter.fromTransaction(transaction); + + final var maybeBlockHeader = + blockchain.getBlockHeader(blockHash).or(() -> blockchain.getBlockHeaderSafe(blockHash)); + + if (maybeBlockHeader.isEmpty()) { + return Optional.of( + new TransactionSimulationResult( + transaction, + TransactionProcessingResult.invalid( + ValidationResult.invalid(TransactionInvalidReason.BLOCK_NOT_FOUND)))); + } + + return transactionSimulator + .process( + callParameter, + isAllowExceedingBalance + ? SIMULATOR_ALLOWING_EXCEEDING_BALANCE + : TransactionValidationParams.transactionSimulator(), + operationTracer, + maybeBlockHeader.get()) + .map(res -> new TransactionSimulationResult(transaction, res.result())); + } +} diff --git a/besu/src/main/java/org/hyperledger/besu/util/PermissioningConfigurationValidator.java b/besu/src/main/java/org/hyperledger/besu/util/PermissioningConfigurationValidator.java index d1b4a107455..6c5fa233644 100644 --- a/besu/src/main/java/org/hyperledger/besu/util/PermissioningConfigurationValidator.java +++ b/besu/src/main/java/org/hyperledger/besu/util/PermissioningConfigurationValidator.java @@ -26,6 +26,8 @@ /** The Permissioning configuration validator. */ public class PermissioningConfigurationValidator { + /** Default Constructor. */ + PermissioningConfigurationValidator() {} /** * Check if all nodes are in allowlist. diff --git a/besu/src/main/java/org/hyperledger/besu/util/StringUtils.java b/besu/src/main/java/org/hyperledger/besu/util/StringUtils.java index cd1c33c9424..7adb82d839a 100644 --- a/besu/src/main/java/org/hyperledger/besu/util/StringUtils.java +++ b/besu/src/main/java/org/hyperledger/besu/util/StringUtils.java @@ -19,6 +19,8 @@ /** some useful tools to display strings in command line help or error messages */ public class StringUtils { + /** Default Constructor. */ + StringUtils() {} /** * Joins a list into string elements with a delimiter but having a last different delimiter diff --git a/besu/src/main/resources/log4j2.xml b/besu/src/main/resources/log4j2.xml index 6bf83a973f7..a4b51ec3e16 100644 --- a/besu/src/main/resources/log4j2.xml +++ b/besu/src/main/resources/log4j2.xml @@ -42,6 +42,18 @@ + + + + + + + + + + + + diff --git a/besu/src/main/resources/org/hyperledger/besu/cli/launcher.json b/besu/src/main/resources/org/hyperledger/besu/cli/launcher.json index 6db0ef2e5bc..226ca165e3b 100644 --- a/besu/src/main/resources/org/hyperledger/besu/cli/launcher.json +++ b/besu/src/main/resources/org/hyperledger/besu/cli/launcher.json @@ -13,12 +13,6 @@ "config-key": "sync-mode", "available-options": "org.hyperledger.besu.ethereum.eth.sync.SyncMode" }, - { - "prompt-type": "CONFIRM", - "question": "Do you want to enable pruning?", - "config-key": "pruning-enabled", - "default-option": "no" - }, { "prompt-type": "INPUT", "question": "What is the data directory ?", diff --git a/besu/src/test/java/org/hyperledger/besu/FlexGroupPrivacyTest.java b/besu/src/test/java/org/hyperledger/besu/FlexGroupPrivacyTest.java new file mode 100644 index 00000000000..a246d18d2ff --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/FlexGroupPrivacyTest.java @@ -0,0 +1,168 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.core.PrivacyParameters.FLEXIBLE_PRIVACY; + +import org.hyperledger.besu.components.BesuComponent; +import org.hyperledger.besu.components.BesuPluginContextModule; +import org.hyperledger.besu.components.MockBesuCommandModule; +import org.hyperledger.besu.components.NoOpMetricsSystemModule; +import org.hyperledger.besu.components.PrivacyTestModule; +import org.hyperledger.besu.config.GenesisConfigFile; +import org.hyperledger.besu.controller.BesuController; +import org.hyperledger.besu.cryptoservices.NodeKeyUtils; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.enclave.EnclaveFactory; +import org.hyperledger.besu.ethereum.GasLimitCalculator; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; +import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; +import org.hyperledger.besu.ethereum.eth.sync.SyncMode; +import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCacheModule; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration; +import org.hyperledger.besu.ethereum.privacy.storage.PrivacyStorageProvider; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.BonsaiCachedMerkleTrieLoaderModule; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.precompile.PrecompiledContract; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.testutil.TestClock; + +import java.math.BigInteger; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Path; +import javax.inject.Named; +import javax.inject.Singleton; + +import dagger.Component; +import dagger.Module; +import dagger.Provides; +import io.vertx.core.Vertx; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +class FlexGroupPrivacyTest { + + private final Vertx vertx = Vertx.vertx(); + + @AfterEach + public void cleanUp() { + vertx.close(); + } + + @Test + void flexibleEnabledPrivacy() { + final BesuController besuController = + DaggerFlexGroupPrivacyTest_FlexGroupPrivacyTestComponent.builder() + .build() + .getBesuController(); + + final PrecompiledContract flexiblePrecompiledContract = + getPrecompile(besuController, FLEXIBLE_PRIVACY); + + assertThat(flexiblePrecompiledContract.getName()).isEqualTo("FlexiblePrivacy"); + } + + private PrecompiledContract getPrecompile( + final BesuController besuController, final Address defaultPrivacy) { + return besuController + .getProtocolSchedule() + .getByBlockHeader(blockHeader(0)) + .getPrecompileContractRegistry() + .get(defaultPrivacy); + } + + private BlockHeader blockHeader(final long number) { + return new BlockHeaderTestFixture().number(number).buildHeader(); + } + + @Singleton + @Component( + modules = { + FlexGroupPrivacyParametersModule.class, + FlexGroupPrivacyTest.PrivacyTestBesuControllerModule.class, + PrivacyTestModule.class, + MockBesuCommandModule.class, + BonsaiCachedMerkleTrieLoaderModule.class, + NoOpMetricsSystemModule.class, + BesuPluginContextModule.class, + BlobCacheModule.class + }) + interface FlexGroupPrivacyTestComponent extends BesuComponent { + BesuController getBesuController(); + } + + @Module + static class FlexGroupPrivacyParametersModule { + + @Provides + PrivacyParameters providePrivacyParameters( + final PrivacyStorageProvider storageProvider, final Vertx vertx) { + try { + return new PrivacyParameters.Builder() + .setEnabled(true) + .setEnclaveUrl(new URI("http://127.0.0.1:8000")) + .setStorageProvider(storageProvider) + .setEnclaveFactory(new EnclaveFactory(vertx)) + .setFlexiblePrivacyGroupsEnabled(true) + .build(); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + } + + @Module + static class PrivacyTestBesuControllerModule { + + @Provides + @Singleton + @SuppressWarnings("CloseableProvides") + BesuController provideBesuController( + final PrivacyParameters privacyParameters, + final DataStorageConfiguration dataStorageConfiguration, + final FlexGroupPrivacyTestComponent context, + @Named("dataDir") final Path dataDir) { + + return new BesuController.Builder() + .fromGenesisFile(GenesisConfigFile.mainnet(), SyncMode.FULL) + .synchronizerConfiguration(SynchronizerConfiguration.builder().build()) + .ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig()) + .storageProvider(new InMemoryKeyValueStorageProvider()) + .networkId(BigInteger.ONE) + .miningParameters(MiningParameters.newDefault()) + .dataStorageConfiguration(dataStorageConfiguration) + .nodeKey(NodeKeyUtils.generate()) + .metricsSystem(new NoOpMetricsSystem()) + .dataDirectory(dataDir) + .clock(TestClock.fixed()) + .privacyParameters(privacyParameters) + .transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT) + .gasLimitCalculator(GasLimitCalculator.constant()) + .evmConfiguration(EvmConfiguration.DEFAULT) + .networkConfiguration(NetworkingConfiguration.create()) + .besuComponent(context) + .build(); + } + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/ForkIdsNetworkConfigTest.java b/besu/src/test/java/org/hyperledger/besu/ForkIdsNetworkConfigTest.java index 5bc1c30bd61..28d4c77036f 100644 --- a/besu/src/test/java/org/hyperledger/besu/ForkIdsNetworkConfigTest.java +++ b/besu/src/test/java/org/hyperledger/besu/ForkIdsNetworkConfigTest.java @@ -11,16 +11,13 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import org.hyperledger.besu.cli.config.EthNetworkConfig; import org.hyperledger.besu.cli.config.NetworkName; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.config.GenesisConfigOptions; @@ -28,14 +25,17 @@ import org.hyperledger.besu.consensus.merge.PostMergeContext; import org.hyperledger.besu.consensus.merge.TransitionProtocolSchedule; import org.hyperledger.besu.consensus.merge.TransitionUtils; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.GenesisState; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.MilestoneStreamingProtocolSchedule; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.forkid.ForkId; import org.hyperledger.besu.ethereum.forkid.ForkIdManager; import org.hyperledger.besu.ethereum.mainnet.DefaultProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSchedule; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import java.util.Collection; import java.util.List; @@ -45,182 +45,184 @@ import com.google.common.collect.Streams; import org.apache.tuweni.bytes.Bytes; -import org.junit.Test; -import org.junit.experimental.runners.Enclosed; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -@RunWith(Enclosed.class) +@RunWith(Parameterized.class) public class ForkIdsNetworkConfigTest { - public static class NotParameterized { - @Test - public void testFromRaw() { - final ForkId forkId = new ForkId(Bytes.ofUnsignedInt(0xfe3366e7L), 1735371L); - final List> forkIdAsBytesList = List.of(forkId.getForkIdAsBytesList()); - assertThat(ForkId.fromRawForkId(forkIdAsBytesList).get()).isEqualTo(forkId); - } + @Parameterized.Parameter public NetworkName chainName; + + @Parameterized.Parameter(1) + public List expectedForkIds; + + @Parameterized.Parameters(name = "{0}") + public static Collection parameters() { + return List.of( + new Object[] { + NetworkName.SEPOLIA, + List.of( + new ForkId(Bytes.ofUnsignedInt(0xfe3366e7L), 1735371L), + new ForkId(Bytes.ofUnsignedInt(0xb96cbd13L), 1677557088L), + new ForkId(Bytes.ofUnsignedInt(0xf7f9bc08L), 1706655072L), + new ForkId(Bytes.ofUnsignedInt(0x88cf81d9L), 0L), + new ForkId(Bytes.ofUnsignedInt(0x88cf81d9L), 0L)) + }, + new Object[] { + NetworkName.HOLESKY, + List.of( + new ForkId(Bytes.ofUnsignedInt(0xc61a6098L), 1696000704L), + new ForkId(Bytes.ofUnsignedInt(0xfd4f016bL), 1707305664L), + new ForkId(Bytes.ofUnsignedInt(0x9b192ad0L), 0L), + new ForkId(Bytes.ofUnsignedInt(0x9b192ad0L), 0L)) + }, + new Object[] { + NetworkName.MAINNET, + List.of( + new ForkId(Bytes.ofUnsignedInt(0xfc64ec04L), 1150000L), + new ForkId(Bytes.ofUnsignedInt(0x97c2c34cL), 1920000L), + new ForkId(Bytes.ofUnsignedInt(0x91d1f948L), 2463000L), + new ForkId(Bytes.ofUnsignedInt(0x91d1f948L), 2463000L), + new ForkId(Bytes.ofUnsignedInt(0x91d1f948L), 2463000L), + new ForkId(Bytes.ofUnsignedInt(0x7a64da13L), 2675000L), + new ForkId(Bytes.ofUnsignedInt(0x3edd5b10L), 4370000L), + new ForkId(Bytes.ofUnsignedInt(0xa00bc324L), 7280000L), + new ForkId(Bytes.ofUnsignedInt(0x668db0afL), 9069000L), + new ForkId(Bytes.ofUnsignedInt(0x879d6e30L), 9200000L), + new ForkId(Bytes.ofUnsignedInt(0xe029e991L), 12244000L), + new ForkId(Bytes.ofUnsignedInt(0xeb440f6L), 12965000L), + new ForkId(Bytes.ofUnsignedInt(0xb715077dL), 13773000L), + new ForkId(Bytes.ofUnsignedInt(0x20c327fcL), 15050000L), + new ForkId(Bytes.ofUnsignedInt(0xf0afd0e3L), 1681338455L), + new ForkId(Bytes.ofUnsignedInt(0xdce96c2dL), 1710338135L), + new ForkId(Bytes.ofUnsignedInt(0x9f3d2254L), 0L), + new ForkId(Bytes.ofUnsignedInt(0x9f3d2254L), 0L)) + }, + new Object[] { + NetworkName.MORDOR, + List.of( + new ForkId(Bytes.ofUnsignedInt(0x175782aaL), 301243L), + new ForkId(Bytes.ofUnsignedInt(0x604f6ee1L), 999983L), + new ForkId(Bytes.ofUnsignedInt(0xf42f5539L), 2520000L), + new ForkId(Bytes.ofUnsignedInt(0x66b5c286L), 3985893), + new ForkId(Bytes.ofUnsignedInt(0x92b323e0L), 5520000L), + new ForkId(Bytes.ofUnsignedInt(0x8c9b1797L), 9957000L), + new ForkId(Bytes.ofUnsignedInt(0x3a6b00d7L), 0L), + new ForkId(Bytes.ofUnsignedInt(0x3a6b00d7L), 0L)) + }, + new Object[] { + NetworkName.CLASSIC, + List.of( + new ForkId(Bytes.ofUnsignedInt(0xfc64ec04L), 1150000L), + new ForkId(Bytes.ofUnsignedInt(0x97c2c34cL), 2500000L), + new ForkId(Bytes.ofUnsignedInt(0x97c2c34cL), 2500000L), + new ForkId(Bytes.ofUnsignedInt(0x97c2c34cL), 2500000L), + new ForkId(Bytes.ofUnsignedInt(0xdb06803fL), 3000000L), + new ForkId(Bytes.ofUnsignedInt(0xaff4bed4L), 5000000L), + new ForkId(Bytes.ofUnsignedInt(0xf79a63c0L), 5900000L), + new ForkId(Bytes.ofUnsignedInt(0x744899d6L), 8772000L), + new ForkId(Bytes.ofUnsignedInt(0x518b59c6L), 9573000L), + new ForkId(Bytes.ofUnsignedInt(0x7ba22882L), 10500839L), + new ForkId(Bytes.ofUnsignedInt(0x9007bfccL), 11700000L), + new ForkId(Bytes.ofUnsignedInt(0xdb63a1caL), 13189133), + new ForkId(Bytes.ofUnsignedInt(0x0f6bf187L), 14525000L), + new ForkId(Bytes.ofUnsignedInt(0x7fd1bb25L), 19250000L), + new ForkId(Bytes.ofUnsignedInt(0xbe46d57cL), 0L), + new ForkId(Bytes.ofUnsignedInt(0xbe46d57cL), 0L)) + }); } - @RunWith(Parameterized.class) - public static class ParametrizedForkIdTest { - - @Parameterized.Parameter public NetworkName chainName; - - @Parameterized.Parameter(1) - public List expectedForkIds; - - @Parameterized.Parameters(name = "{0}") - public static Collection parameters() { - return List.of( - new Object[] { - NetworkName.SEPOLIA, - List.of( - new ForkId(Bytes.ofUnsignedInt(0xfe3366e7L), 1735371L), - new ForkId(Bytes.ofUnsignedInt(0xb96cbd13L), 1677557088L), - new ForkId(Bytes.ofUnsignedInt(0xf7f9bc08L), 0L), - new ForkId(Bytes.ofUnsignedInt(0xf7f9bc08L), 0L)) - }, - new Object[] { - NetworkName.HOLESKY, - List.of( - new ForkId(Bytes.ofUnsignedInt(0xc61a6098L), 1696000704L), - new ForkId(Bytes.ofUnsignedInt(0xfd4f016bL), 0L), - new ForkId(Bytes.ofUnsignedInt(0xfd4f016bL), 0L)) - }, - new Object[] { - NetworkName.GOERLI, - List.of( - new ForkId(Bytes.ofUnsignedInt(0xa3f5ab08L), 1561651L), - new ForkId(Bytes.ofUnsignedInt(0xc25efa5cL), 4460644L), - new ForkId(Bytes.ofUnsignedInt(0x757a1c47L), 5062605L), - new ForkId(Bytes.ofUnsignedInt(0xb8c6299dL), 1678832736L), - new ForkId(Bytes.ofUnsignedInt(0xf9843abfL), 0L), - new ForkId(Bytes.ofUnsignedInt(0xf9843abfL), 0L)) - }, - new Object[] { - NetworkName.MAINNET, - List.of( - new ForkId(Bytes.ofUnsignedInt(0xfc64ec04L), 1150000L), - new ForkId(Bytes.ofUnsignedInt(0x97c2c34cL), 1920000L), - new ForkId(Bytes.ofUnsignedInt(0x91d1f948L), 2463000L), - new ForkId(Bytes.ofUnsignedInt(0x91d1f948L), 2463000L), - new ForkId(Bytes.ofUnsignedInt(0x91d1f948L), 2463000L), - new ForkId(Bytes.ofUnsignedInt(0x7a64da13L), 2675000L), - new ForkId(Bytes.ofUnsignedInt(0x3edd5b10L), 4370000L), - new ForkId(Bytes.ofUnsignedInt(0xa00bc324L), 7280000L), - new ForkId(Bytes.ofUnsignedInt(0x668db0afL), 9069000L), - new ForkId(Bytes.ofUnsignedInt(0x879d6e30L), 9200000L), - new ForkId(Bytes.ofUnsignedInt(0xe029e991L), 12244000L), - new ForkId(Bytes.ofUnsignedInt(0xeb440f6L), 12965000L), - new ForkId(Bytes.ofUnsignedInt(0xb715077dL), 13773000L), - new ForkId(Bytes.ofUnsignedInt(0x20c327fcL), 15050000L), - new ForkId(Bytes.ofUnsignedInt(0xf0afd0e3L), 1681338455L), - new ForkId(Bytes.ofUnsignedInt(0xdce96c2dL), 0L), - new ForkId(Bytes.ofUnsignedInt(0xdce96c2dL), 0L)) - }, - new Object[] { - NetworkName.MORDOR, - List.of( - new ForkId(Bytes.ofUnsignedInt(0x175782aaL), 301243L), - new ForkId(Bytes.ofUnsignedInt(0x604f6ee1L), 999983L), - new ForkId(Bytes.ofUnsignedInt(0xf42f5539L), 2520000L), - new ForkId(Bytes.ofUnsignedInt(0x66b5c286L), 3985893), - new ForkId(Bytes.ofUnsignedInt(0x92b323e0L), 5520000L), - new ForkId(Bytes.ofUnsignedInt(0x8c9b1797L), 9957000L), - new ForkId(Bytes.ofUnsignedInt(0x3a6b00d7L), 0L), - new ForkId(Bytes.ofUnsignedInt(0x3a6b00d7L), 0L)) - }, - new Object[] { - NetworkName.CLASSIC, - List.of( - new ForkId(Bytes.ofUnsignedInt(0xfc64ec04L), 1150000L), - new ForkId(Bytes.ofUnsignedInt(0x97c2c34cL), 2500000L), - new ForkId(Bytes.ofUnsignedInt(0x97c2c34cL), 2500000L), - new ForkId(Bytes.ofUnsignedInt(0x97c2c34cL), 2500000L), - new ForkId(Bytes.ofUnsignedInt(0xdb06803fL), 3000000L), - new ForkId(Bytes.ofUnsignedInt(0xaff4bed4L), 5000000L), - new ForkId(Bytes.ofUnsignedInt(0xf79a63c0L), 5900000L), - new ForkId(Bytes.ofUnsignedInt(0x744899d6L), 8772000L), - new ForkId(Bytes.ofUnsignedInt(0x518b59c6L), 9573000L), - new ForkId(Bytes.ofUnsignedInt(0x7ba22882L), 10500839L), - new ForkId(Bytes.ofUnsignedInt(0x9007bfccL), 11700000L), - new ForkId(Bytes.ofUnsignedInt(0xdb63a1caL), 13189133), - new ForkId(Bytes.ofUnsignedInt(0x0f6bf187L), 14525000L), - new ForkId(Bytes.ofUnsignedInt(0x7fd1bb25L), 0L), - new ForkId(Bytes.ofUnsignedInt(0x7fd1bb25L), 0L)) - }); - } - - @Test - public void testForkId() { - final GenesisConfigFile genesisConfigFile = - GenesisConfigFile.fromConfig(EthNetworkConfig.jsonConfig(chainName)); - final MilestoneStreamingTransitionProtocolSchedule schedule = - createSchedule(genesisConfigFile); - final GenesisState genesisState = GenesisState.fromConfig(genesisConfigFile, schedule); - final Blockchain mockBlockchain = mock(Blockchain.class); - final BlockHeader mockBlockHeader = mock(BlockHeader.class); - - when(mockBlockchain.getGenesisBlock()).thenReturn(genesisState.getBlock()); - - final AtomicLong blockNumber = new AtomicLong(); - when(mockBlockchain.getChainHeadHeader()).thenReturn(mockBlockHeader); - when(mockBlockHeader.getNumber()).thenAnswer(o -> blockNumber.get()); - when(mockBlockHeader.getTimestamp()).thenAnswer(o -> blockNumber.get()); - - final ForkIdManager forkIdManager = - new ForkIdManager( - mockBlockchain, - genesisConfigFile.getForkBlockNumbers(), - genesisConfigFile.getForkTimestamps(), - false); - - final List actualForkIds = - Streams.concat(schedule.streamMilestoneBlocks(), Stream.of(Long.MAX_VALUE)) - .map( - block -> { - blockNumber.set(block); - return forkIdManager.getForkIdForChainHead(); - }) - .collect(Collectors.toList()); - - assertThat(actualForkIds).containsExactlyElementsOf(expectedForkIds); - } + @ParameterizedTest + @MethodSource("parameters") + public void testForkId(final NetworkName chainName, final List expectedForkIds) { + final GenesisConfigFile genesisConfigFile = + GenesisConfigFile.fromResource(chainName.getGenesisFile()); + final MilestoneStreamingTransitionProtocolSchedule schedule = createSchedule(genesisConfigFile); + final GenesisState genesisState = GenesisState.fromConfig(genesisConfigFile, schedule); + final Blockchain mockBlockchain = mock(Blockchain.class); + final BlockHeader mockBlockHeader = mock(BlockHeader.class); + + when(mockBlockchain.getGenesisBlock()).thenReturn(genesisState.getBlock()); + + final AtomicLong blockNumber = new AtomicLong(); + when(mockBlockchain.getChainHeadHeader()).thenReturn(mockBlockHeader); + when(mockBlockHeader.getNumber()).thenAnswer(o -> blockNumber.get()); + when(mockBlockHeader.getTimestamp()).thenAnswer(o -> blockNumber.get()); + + final ForkIdManager forkIdManager = + new ForkIdManager( + mockBlockchain, + genesisConfigFile.getForkBlockNumbers(), + genesisConfigFile.getForkTimestamps(), + false); + + final List actualForkIds = + Streams.concat(schedule.streamMilestoneBlocks(), Stream.of(Long.MAX_VALUE)) + .map( + block -> { + blockNumber.set(block); + return forkIdManager.getForkIdForChainHead(); + }) + .collect(Collectors.toList()); + + assertThat(actualForkIds).containsExactlyElementsOf(expectedForkIds); + } - private static MilestoneStreamingTransitionProtocolSchedule createSchedule( - final GenesisConfigFile genesisConfigFile) { - final GenesisConfigOptions configOptions = genesisConfigFile.getConfigOptions(); - MilestoneStreamingProtocolSchedule preMergeProtocolSchedule = - new MilestoneStreamingProtocolSchedule( - (DefaultProtocolSchedule) MainnetProtocolSchedule.fromConfig(configOptions)); - MilestoneStreamingProtocolSchedule postMergeProtocolSchedule = - new MilestoneStreamingProtocolSchedule( - (DefaultProtocolSchedule) MergeProtocolSchedule.create(configOptions, false)); - final MilestoneStreamingTransitionProtocolSchedule schedule = - new MilestoneStreamingTransitionProtocolSchedule( - preMergeProtocolSchedule, postMergeProtocolSchedule); - return schedule; - } + private static MilestoneStreamingTransitionProtocolSchedule createSchedule( + final GenesisConfigFile genesisConfigFile) { + final GenesisConfigOptions configOptions = genesisConfigFile.getConfigOptions(); + MilestoneStreamingProtocolSchedule preMergeProtocolSchedule = + new MilestoneStreamingProtocolSchedule( + (DefaultProtocolSchedule) + MainnetProtocolSchedule.fromConfig( + configOptions, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem())); + MilestoneStreamingProtocolSchedule postMergeProtocolSchedule = + new MilestoneStreamingProtocolSchedule( + (DefaultProtocolSchedule) + MergeProtocolSchedule.create( + configOptions, + false, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem())); + final MilestoneStreamingTransitionProtocolSchedule schedule = + new MilestoneStreamingTransitionProtocolSchedule( + preMergeProtocolSchedule, postMergeProtocolSchedule); + return schedule; + } - public static class MilestoneStreamingTransitionProtocolSchedule - extends TransitionProtocolSchedule { + public static class MilestoneStreamingTransitionProtocolSchedule + extends TransitionProtocolSchedule { - private final TransitionUtils transitionUtils; + private final TransitionUtils transitionUtils; - public MilestoneStreamingTransitionProtocolSchedule( - final MilestoneStreamingProtocolSchedule preMergeProtocolSchedule, - final MilestoneStreamingProtocolSchedule postMergeProtocolSchedule) { - super(preMergeProtocolSchedule, postMergeProtocolSchedule, PostMergeContext.get()); - transitionUtils = - new TransitionUtils<>( - preMergeProtocolSchedule, postMergeProtocolSchedule, PostMergeContext.get()); - } + public MilestoneStreamingTransitionProtocolSchedule( + final MilestoneStreamingProtocolSchedule preMergeProtocolSchedule, + final MilestoneStreamingProtocolSchedule postMergeProtocolSchedule) { + super(preMergeProtocolSchedule, postMergeProtocolSchedule, PostMergeContext.get()); + transitionUtils = + new TransitionUtils<>( + preMergeProtocolSchedule, postMergeProtocolSchedule, PostMergeContext.get()); + } - public Stream streamMilestoneBlocks() { - return transitionUtils.dispatchFunctionAccordingToMergeState( - MilestoneStreamingProtocolSchedule::streamMilestoneBlocks); - } + public Stream streamMilestoneBlocks() { + return transitionUtils.dispatchFunctionAccordingToMergeState( + MilestoneStreamingProtocolSchedule::streamMilestoneBlocks); } } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/besu/src/test/java/org/hyperledger/besu/PrivacyReorgTest.java b/besu/src/test/java/org/hyperledger/besu/PrivacyReorgTest.java index 276740fbfed..6298b70a953 100644 --- a/besu/src/test/java/org/hyperledger/besu/PrivacyReorgTest.java +++ b/besu/src/test/java/org/hyperledger/besu/PrivacyReorgTest.java @@ -21,6 +21,12 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import org.hyperledger.besu.components.BesuComponent; +import org.hyperledger.besu.components.BesuPluginContextModule; +import org.hyperledger.besu.components.EnclaveModule; +import org.hyperledger.besu.components.MockBesuCommandModule; +import org.hyperledger.besu.components.NoOpMetricsSystemModule; +import org.hyperledger.besu.components.PrivacyTestModule; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.controller.BesuController; import org.hyperledger.besu.crypto.KeyPair; @@ -47,7 +53,9 @@ import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.sync.SyncMode; import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCacheModule; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.mainnet.BlockImportResult; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration; import org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver; @@ -56,6 +64,7 @@ import org.hyperledger.besu.ethereum.privacy.storage.PrivacyStorageProvider; import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.BonsaiCachedMerkleTrieLoaderModule; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.log.LogsBloomFilter; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; @@ -70,19 +79,21 @@ import java.util.Collections; import java.util.Optional; import java.util.function.Supplier; +import javax.inject.Named; +import javax.inject.Singleton; import com.google.common.base.Suppliers; +import dagger.Component; +import dagger.Module; +import dagger.Provides; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; @SuppressWarnings("rawtypes") public class PrivacyReorgTest { - @TempDir private static Path folder; - private static final Supplier SIGNATURE_ALGORITHM = Suppliers.memoize(SignatureAlgorithmFactory::getInstance); @@ -130,11 +141,15 @@ public class PrivacyReorgTest { .signAndBuild(KEY_PAIR); private final BlockDataGenerator gen = new BlockDataGenerator(); - private BesuController besuController; - private PrivateStateRootResolver privateStateRootResolver; private PrivacyParameters privacyParameters; private Enclave mockEnclave; private Transaction privacyMarkerTransaction; + private final PrivacyReorgTestComponent component = + DaggerPrivacyReorgTest_PrivacyReorgTestComponent.create(); + + private final BesuController besuController = component.getBesuController(); + private final PrivateStateRootResolver privateStateRootResolver = + component.getPrivacyParameters().getPrivateStateRootResolver(); @BeforeEach public void setUp() throws IOException { @@ -174,30 +189,6 @@ public void setUp() throws IOException { .build(); privacyParameters.setPrivacyUserId(ENCLAVE_PUBLIC_KEY.toBase64String()); - - privateStateRootResolver = - new PrivateStateRootResolver(privacyParameters.getPrivateStateStorage()); - - besuController = - new BesuController.Builder() - .fromGenesisConfig( - GenesisConfigFile.genesisFileFromResources("/privacy_reorg_genesis.json"), - SyncMode.FULL) - .synchronizerConfiguration(SynchronizerConfiguration.builder().build()) - .ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig()) - .storageProvider(new InMemoryKeyValueStorageProvider()) - .networkId(BigInteger.ONE) - .miningParameters(MiningParameters.newDefault()) - .nodeKey(NodeKeyUtils.generate()) - .metricsSystem(new NoOpMetricsSystem()) - .dataDirectory(folder) - .clock(TestClock.fixed()) - .privacyParameters(privacyParameters) - .transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT) - .gasLimitCalculator(GasLimitCalculator.constant()) - .evmConfiguration(EvmConfiguration.DEFAULT) - .networkConfiguration(NetworkingConfiguration.create()) - .build(); } @Test @@ -205,7 +196,8 @@ public void privacyGroupHeadIsTracked() { // Setup an initial blockchain with one private transaction final ProtocolContext protocolContext = besuController.getProtocolContext(); final DefaultBlockchain blockchain = (DefaultBlockchain) protocolContext.getBlockchain(); - final PrivateStateStorage privateStateStorage = privacyParameters.getPrivateStateStorage(); + final PrivateStateStorage privateStateStorage = + component.getPrivacyParameters().getPrivateStateStorage(); final Block firstBlock = gen.block( @@ -245,7 +237,7 @@ public void reorgToChainAtEqualHeight() { // Setup an initial blockchain with one private transaction final ProtocolContext protocolContext = besuController.getProtocolContext(); final DefaultBlockchain blockchain = (DefaultBlockchain) protocolContext.getBlockchain(); - + assertThat(blockchain.getChainHeadBlockNumber()).isEqualTo(0); final Block firstBlock = gen.block( getBlockOptionsWithTransaction( @@ -253,8 +245,9 @@ public void reorgToChainAtEqualHeight() { privacyMarkerTransaction, FIRST_BLOCK_WITH_SINGLE_TRANSACTION_STATE_ROOT)); - appendBlock(besuController, blockchain, protocolContext, firstBlock); - + var importResult = appendBlock(besuController, blockchain, protocolContext, firstBlock); + assertThat(importResult.isImported()).isTrue(); + assertThat(blockchain.getChainHeadBlockNumber()).isEqualTo(1); // Check that the private state root is not the empty state assertPrivateStateRoot( privateStateRootResolver, blockchain, STATE_ROOT_AFTER_TRANSACTION_APPENDED_TO_EMPTY_STATE); @@ -395,12 +388,12 @@ public void reorgToLongerChain() { } @SuppressWarnings("unchecked") - private void appendBlock( + private BlockImportResult appendBlock( final BesuController besuController, final DefaultBlockchain blockchain, final ProtocolContext protocolContext, final Block block) { - besuController + return besuController .getProtocolSchedule() .getByBlockHeader(blockchain.getChainHeadHeader()) .getBlockImporter() @@ -488,4 +481,93 @@ private BlockDataGenerator.BlockOptions getBlockOptions( .hasOmmers(false) .setLogsBloom(LogsBloomFilter.empty()); } + + @Singleton + @Component( + modules = { + PrivacyReorgTest.PrivacyReorgParametersModule.class, + PrivacyReorgTest.PrivacyReorgTestBesuControllerModule.class, + PrivacyReorgTest.PrivacyReorgTestGenesisConfigModule.class, + EnclaveModule.class, + PrivacyTestModule.class, + MockBesuCommandModule.class, + NoOpMetricsSystemModule.class, + BonsaiCachedMerkleTrieLoaderModule.class, + BlobCacheModule.class, + BesuPluginContextModule.class + }) + interface PrivacyReorgTestComponent extends BesuComponent { + + BesuController getBesuController(); + + PrivacyParameters getPrivacyParameters(); + } + + @Module + static class PrivacyReorgParametersModule { + + // TODO: copypasta, get this from the enclave factory + private static final Bytes ENCLAVE_PUBLIC_KEY = + Bytes.fromBase64String("A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="); + + @Provides + PrivacyParameters providePrivacyReorgParameters( + final PrivacyStorageProvider storageProvider, final EnclaveFactory enclaveFactory) { + + PrivacyParameters retval = + new PrivacyParameters.Builder() + .setEnabled(true) + .setStorageProvider(storageProvider) + .setEnclaveUrl(URI.create("http//1.1.1.1:1234")) + .setEnclaveFactory(enclaveFactory) + .build(); + retval.setPrivacyUserId(ENCLAVE_PUBLIC_KEY.toBase64String()); + return retval; + } + } + + @Module + static class PrivacyReorgTestBesuControllerModule { + + @Provides + @Singleton + @SuppressWarnings("CloseableProvides") + BesuController provideBesuController( + final PrivacyParameters privacyParameters, + final GenesisConfigFile genesisConfigFile, + final PrivacyReorgTestComponent context, + final @Named("dataDir") Path dataDir) { + + // dataStorageConfiguration default + // named privacyReorgParams + BesuController retval = + new BesuController.Builder() + .fromGenesisFile(genesisConfigFile, SyncMode.FULL) + .synchronizerConfiguration(SynchronizerConfiguration.builder().build()) + .ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig()) + .storageProvider(new InMemoryKeyValueStorageProvider()) + .networkId(BigInteger.ONE) + .miningParameters(MiningParameters.newDefault()) + .nodeKey(NodeKeyUtils.generate()) + .metricsSystem(new NoOpMetricsSystem()) + .dataDirectory(dataDir) + .clock(TestClock.fixed()) + .privacyParameters(privacyParameters) + .transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT) + .gasLimitCalculator(GasLimitCalculator.constant()) + .evmConfiguration(EvmConfiguration.DEFAULT) + .networkConfiguration(NetworkingConfiguration.create()) + .besuComponent(context) + .build(); + return retval; + } + } + + @Module + static class PrivacyReorgTestGenesisConfigModule { + @Provides + GenesisConfigFile providePrivacyReorgGenesisConfigFile() { + return GenesisConfigFile.fromResource("/privacy_reorg_genesis.json"); + } + } } diff --git a/besu/src/test/java/org/hyperledger/besu/PrivacyTest.java b/besu/src/test/java/org/hyperledger/besu/PrivacyTest.java index c71d44284fb..4d488ced3bc 100644 --- a/besu/src/test/java/org/hyperledger/besu/PrivacyTest.java +++ b/besu/src/test/java/org/hyperledger/besu/PrivacyTest.java @@ -1,5 +1,5 @@ /* - * Copyright ConsenSys AG. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -16,17 +16,17 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.ethereum.core.PrivacyParameters.DEFAULT_PRIVACY; -import static org.hyperledger.besu.ethereum.core.PrivacyParameters.FLEXIBLE_PRIVACY; -import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_BACKGROUND_THREAD_COUNT; -import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_CACHE_CAPACITY; -import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_IS_HIGH_SPEC; -import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_MAX_OPEN_FILES; +import org.hyperledger.besu.components.BesuComponent; +import org.hyperledger.besu.components.BesuPluginContextModule; +import org.hyperledger.besu.components.MockBesuCommandModule; +import org.hyperledger.besu.components.NoOpMetricsSystemModule; +import org.hyperledger.besu.components.PrivacyParametersModule; +import org.hyperledger.besu.components.PrivacyTestModule; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.controller.BesuController; import org.hyperledger.besu.cryptoservices.NodeKeyUtils; import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.enclave.EnclaveFactory; import org.hyperledger.besu.ethereum.GasLimitCalculator; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; @@ -36,113 +36,47 @@ import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.sync.SyncMode; import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCacheModule; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration; -import org.hyperledger.besu.ethereum.privacy.storage.PrivacyStorageProvider; -import org.hyperledger.besu.ethereum.privacy.storage.keyvalue.PrivacyKeyValueStorageProviderBuilder; -import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.BonsaiCachedMerkleTrieLoaderModule; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.precompile.PrecompiledContract; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; -import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBKeyValuePrivacyStorageFactory; -import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBKeyValueStorageFactory; -import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBMetricsFactory; -import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBFactoryConfiguration; -import org.hyperledger.besu.services.BesuConfigurationImpl; import org.hyperledger.besu.testutil.TestClock; -import java.io.IOException; import java.math.BigInteger; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.Files; import java.nio.file.Path; -import java.util.Arrays; +import javax.inject.Named; +import javax.inject.Singleton; +import dagger.Component; +import dagger.Module; +import dagger.Provides; import io.vertx.core.Vertx; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -public class PrivacyTest { +class PrivacyTest { private final Vertx vertx = Vertx.vertx(); - @TempDir private static Path dataDir; - @AfterEach public void cleanUp() { vertx.close(); } @Test - public void defaultPrivacy() throws IOException, URISyntaxException { - final BesuController besuController = setUpControllerWithPrivacyEnabled(false); + void defaultPrivacy() { + final BesuController besuController = + DaggerPrivacyTest_PrivacyTestComponent.builder().build().getBesuController(); final PrecompiledContract precompiledContract = getPrecompile(besuController, DEFAULT_PRIVACY); assertThat(precompiledContract.getName()).isEqualTo("Privacy"); } - @Test - public void flexibleEnabledPrivacy() throws IOException, URISyntaxException { - final BesuController besuController = setUpControllerWithPrivacyEnabled(true); - - final PrecompiledContract flexiblePrecompiledContract = - getPrecompile(besuController, FLEXIBLE_PRIVACY); - - assertThat(flexiblePrecompiledContract.getName()).isEqualTo("FlexiblePrivacy"); - } - - private BesuController setUpControllerWithPrivacyEnabled(final boolean flexibleEnabled) - throws IOException, URISyntaxException { - final Path dbDir = Files.createTempDirectory(dataDir, "database"); - final PrivacyParameters privacyParameters = - new PrivacyParameters.Builder() - .setEnabled(true) - .setEnclaveUrl(new URI("http://127.0.0.1:8000")) - .setStorageProvider(createKeyValueStorageProvider(dataDir, dbDir)) - .setEnclaveFactory(new EnclaveFactory(vertx)) - .setFlexiblePrivacyGroupsEnabled(flexibleEnabled) - .build(); - return new BesuController.Builder() - .fromGenesisConfig(GenesisConfigFile.mainnet(), SyncMode.FULL) - .synchronizerConfiguration(SynchronizerConfiguration.builder().build()) - .ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig()) - .storageProvider(new InMemoryKeyValueStorageProvider()) - .networkId(BigInteger.ONE) - .miningParameters(MiningParameters.newDefault()) - .nodeKey(NodeKeyUtils.generate()) - .metricsSystem(new NoOpMetricsSystem()) - .dataDirectory(dataDir) - .clock(TestClock.fixed()) - .privacyParameters(privacyParameters) - .transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT) - .gasLimitCalculator(GasLimitCalculator.constant()) - .evmConfiguration(EvmConfiguration.DEFAULT) - .networkConfiguration(NetworkingConfiguration.create()) - .build(); - } - - private PrivacyStorageProvider createKeyValueStorageProvider( - final Path dataDir, final Path dbDir) { - return new PrivacyKeyValueStorageProviderBuilder() - .withStorageFactory( - new RocksDBKeyValuePrivacyStorageFactory( - new RocksDBKeyValueStorageFactory( - () -> - new RocksDBFactoryConfiguration( - DEFAULT_MAX_OPEN_FILES, - DEFAULT_BACKGROUND_THREAD_COUNT, - DEFAULT_CACHE_CAPACITY, - DEFAULT_IS_HIGH_SPEC), - Arrays.asList(KeyValueSegmentIdentifier.values()), - RocksDBMetricsFactory.PRIVATE_ROCKS_DB_METRICS))) - .withCommonConfiguration(new BesuConfigurationImpl(dataDir, dbDir)) - .withMetricsSystem(new NoOpMetricsSystem()) - .build(); - } - private PrecompiledContract getPrecompile( final BesuController besuController, final Address defaultPrivacy) { return besuController @@ -155,4 +89,55 @@ private PrecompiledContract getPrecompile( private BlockHeader blockHeader(final long number) { return new BlockHeaderTestFixture().number(number).buildHeader(); } + + @Singleton + @Component( + modules = { + PrivacyParametersModule.class, + PrivacyTest.PrivacyTestBesuControllerModule.class, + PrivacyTestModule.class, + MockBesuCommandModule.class, + BonsaiCachedMerkleTrieLoaderModule.class, + NoOpMetricsSystemModule.class, + BesuPluginContextModule.class, + BlobCacheModule.class + }) + interface PrivacyTestComponent extends BesuComponent { + + BesuController getBesuController(); + } + + @Module + static class PrivacyTestBesuControllerModule { + + @Provides + @Singleton + @SuppressWarnings("CloseableProvides") + BesuController provideBesuController( + final PrivacyParameters privacyParameters, + final DataStorageConfiguration dataStorageConfiguration, + final PrivacyTestComponent context, + @Named("dataDir") final Path dataDir) { + + return new BesuController.Builder() + .fromGenesisFile(GenesisConfigFile.mainnet(), SyncMode.FULL) + .synchronizerConfiguration(SynchronizerConfiguration.builder().build()) + .ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig()) + .storageProvider(new InMemoryKeyValueStorageProvider()) + .networkId(BigInteger.ONE) + .miningParameters(MiningParameters.newDefault()) + .dataStorageConfiguration(dataStorageConfiguration) + .nodeKey(NodeKeyUtils.generate()) + .metricsSystem(new NoOpMetricsSystem()) + .dataDirectory(dataDir) + .clock(TestClock.fixed()) + .privacyParameters(privacyParameters) + .transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT) + .gasLimitCalculator(GasLimitCalculator.constant()) + .evmConfiguration(EvmConfiguration.DEFAULT) + .networkConfiguration(NetworkingConfiguration.create()) + .besuComponent(context) + .build(); + } + } } diff --git a/besu/src/test/java/org/hyperledger/besu/RawForkIdTest.java b/besu/src/test/java/org/hyperledger/besu/RawForkIdTest.java new file mode 100644 index 00000000000..ae9de89b5f9 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/RawForkIdTest.java @@ -0,0 +1,33 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.ethereum.forkid.ForkId; + +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; + +public class RawForkIdTest { + @Test + public void testFromRaw() { + final ForkId forkId = new ForkId(Bytes.ofUnsignedInt(0xfe3366e7L), 1735371L); + final List> forkIdAsBytesList = List.of(forkId.getForkIdAsBytesList()); + assertThat(ForkId.fromRawForkId(forkIdAsBytesList).get()).isEqualTo(forkId); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/RunnerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/RunnerBuilderTest.java index cbd18961ad4..c7180b958c1 100644 --- a/besu/src/test/java/org/hyperledger/besu/RunnerBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/RunnerBuilderTest.java @@ -38,6 +38,7 @@ import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.ImmutableApiConfiguration; import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration; +import org.hyperledger.besu.ethereum.api.jsonrpc.InProcessRpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.ipc.JsonRpcIpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration; @@ -163,6 +164,7 @@ public void enodeUrlShouldHaveAdvertisedHostWhenDiscoveryDisabled() { .graphQLConfiguration(mock(GraphQLConfiguration.class)) .webSocketConfiguration(mock(WebSocketConfiguration.class)) .jsonRpcIpcConfiguration(mock(JsonRpcIpcConfiguration.class)) + .inProcessRpcConfiguration(mock(InProcessRpcConfiguration.class)) .metricsConfiguration(mock(MetricsConfiguration.class)) .vertx(vertx) .dataDir(dataDir.getRoot()) @@ -208,6 +210,7 @@ public void movingAcrossProtocolSpecsUpdatesNodeRecord() { .graphQLConfiguration(mock(GraphQLConfiguration.class)) .webSocketConfiguration(mock(WebSocketConfiguration.class)) .jsonRpcIpcConfiguration(mock(JsonRpcIpcConfiguration.class)) + .inProcessRpcConfiguration(mock(InProcessRpcConfiguration.class)) .metricsConfiguration(mock(MetricsConfiguration.class)) .vertx(Vertx.vertx()) .dataDir(dataDir.getRoot()) @@ -246,7 +249,7 @@ public void whenEngineApiAddedListensOnDefaultPort() { final JsonRpcConfiguration engine = JsonRpcConfiguration.createEngineDefault(); engine.setEnabled(true); final EthNetworkConfig mockMainnet = mock(EthNetworkConfig.class); - when(mockMainnet.getNetworkId()).thenReturn(BigInteger.ONE); + when(mockMainnet.networkId()).thenReturn(BigInteger.ONE); MergeConfigOptions.setMergeEnabled(true); when(besuController.getMiningCoordinator()).thenReturn(mock(MergeMiningCoordinator.class)); @@ -267,6 +270,7 @@ public void whenEngineApiAddedListensOnDefaultPort() { .graphQLConfiguration(mock(GraphQLConfiguration.class)) .webSocketConfiguration(mock(WebSocketConfiguration.class)) .jsonRpcIpcConfiguration(mock(JsonRpcIpcConfiguration.class)) + .inProcessRpcConfiguration(mock(InProcessRpcConfiguration.class)) .metricsConfiguration(mock(MetricsConfiguration.class)) .vertx(Vertx.vertx()) .dataDir(dataDir.getRoot()) @@ -287,7 +291,7 @@ public void whenEngineApiAddedWebSocketReadyOnSamePort() { final WebSocketConfiguration wsRpc = WebSocketConfiguration.createDefault(); wsRpc.setEnabled(true); final EthNetworkConfig mockMainnet = mock(EthNetworkConfig.class); - when(mockMainnet.getNetworkId()).thenReturn(BigInteger.ONE); + when(mockMainnet.networkId()).thenReturn(BigInteger.ONE); MergeConfigOptions.setMergeEnabled(true); when(besuController.getMiningCoordinator()).thenReturn(mock(MergeMiningCoordinator.class)); final JsonRpcConfiguration engineConf = JsonRpcConfiguration.createEngineDefault(); @@ -309,6 +313,7 @@ public void whenEngineApiAddedWebSocketReadyOnSamePort() { .engineJsonRpcConfiguration(engineConf) .webSocketConfiguration(wsRpc) .jsonRpcIpcConfiguration(mock(JsonRpcIpcConfiguration.class)) + .inProcessRpcConfiguration(mock(InProcessRpcConfiguration.class)) .graphQLConfiguration(mock(GraphQLConfiguration.class)) .metricsConfiguration(mock(MetricsConfiguration.class)) .vertx(Vertx.vertx()) @@ -329,7 +334,7 @@ public void whenEngineApiAddedEthSubscribeAvailable() { final WebSocketConfiguration wsRpc = WebSocketConfiguration.createDefault(); wsRpc.setEnabled(true); final EthNetworkConfig mockMainnet = mock(EthNetworkConfig.class); - when(mockMainnet.getNetworkId()).thenReturn(BigInteger.ONE); + when(mockMainnet.networkId()).thenReturn(BigInteger.ONE); MergeConfigOptions.setMergeEnabled(true); when(besuController.getMiningCoordinator()).thenReturn(mock(MergeMiningCoordinator.class)); final JsonRpcConfiguration engineConf = JsonRpcConfiguration.createEngineDefault(); @@ -351,6 +356,7 @@ public void whenEngineApiAddedEthSubscribeAvailable() { .engineJsonRpcConfiguration(engineConf) .webSocketConfiguration(wsRpc) .jsonRpcIpcConfiguration(mock(JsonRpcIpcConfiguration.class)) + .inProcessRpcConfiguration(mock(InProcessRpcConfiguration.class)) .graphQLConfiguration(mock(GraphQLConfiguration.class)) .metricsConfiguration(mock(MetricsConfiguration.class)) .vertx(Vertx.vertx()) @@ -376,7 +382,7 @@ public void noEngineApiNoServiceForMethods() { final WebSocketConfiguration defaultWebSockConfig = WebSocketConfiguration.createDefault(); defaultWebSockConfig.setEnabled(true); final EthNetworkConfig mockMainnet = mock(EthNetworkConfig.class); - when(mockMainnet.getNetworkId()).thenReturn(BigInteger.ONE); + when(mockMainnet.networkId()).thenReturn(BigInteger.ONE); MergeConfigOptions.setMergeEnabled(true); final Runner runner = @@ -395,6 +401,7 @@ public void noEngineApiNoServiceForMethods() { .graphQLConfiguration(mock(GraphQLConfiguration.class)) .webSocketConfiguration(defaultWebSockConfig) .jsonRpcIpcConfiguration(mock(JsonRpcIpcConfiguration.class)) + .inProcessRpcConfiguration(mock(InProcessRpcConfiguration.class)) .metricsConfiguration(mock(MetricsConfiguration.class)) .vertx(Vertx.vertx()) .dataDir(dataDir.getRoot()) diff --git a/besu/src/test/java/org/hyperledger/besu/RunnerTest.java b/besu/src/test/java/org/hyperledger/besu/RunnerTest.java index 20d3c3ee448..5fefc7ef224 100644 --- a/besu/src/test/java/org/hyperledger/besu/RunnerTest.java +++ b/besu/src/test/java/org/hyperledger/besu/RunnerTest.java @@ -17,12 +17,15 @@ import static java.util.Collections.emptySet; import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.cli.config.NetworkName.DEV; +import static org.hyperledger.besu.cli.config.NetworkName.MAINNET; import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_BACKGROUND_THREAD_COUNT; import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_CACHE_CAPACITY; import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_IS_HIGH_SPEC; import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_MAX_OPEN_FILES; +import static org.mockito.Mockito.mock; import org.hyperledger.besu.cli.config.EthNetworkConfig; +import org.hyperledger.besu.components.BesuComponent; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.config.JsonUtil; import org.hyperledger.besu.config.MergeConfigOptions; @@ -35,6 +38,7 @@ import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.ImmutableApiConfiguration; import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration; +import org.hyperledger.besu.ethereum.api.jsonrpc.ImmutableInProcessRpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.ipc.JsonRpcIpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration; @@ -57,6 +61,7 @@ import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStorageProviderBuilder; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.ObservableMetricsSystem; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; @@ -84,6 +89,7 @@ import java.util.Optional; import java.util.concurrent.TimeUnit; +import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import io.vertx.core.Future; import io.vertx.core.Promise; @@ -122,7 +128,7 @@ public void stopVertx() { vertx.close(); } - @TempDir private static Path temp; + @TempDir private Path temp; @Test public void getFixedNodes() { @@ -163,12 +169,13 @@ private void syncFromGenesis(final SyncMode mode, final GenesisConfigFile genesi final Path dataDirAhead = Files.createTempDirectory(temp, "db-ahead"); final Path dbAhead = dataDirAhead.resolve("database"); final int blockCount = 500; - final NodeKey aheadDbNodeKey = NodeKeyUtils.createFrom(KeyPairUtil.loadKeyPair(dbAhead)); + final NodeKey aheadDbNodeKey = NodeKeyUtils.createFrom(KeyPairUtil.loadKeyPair(dataDirAhead)); final NodeKey behindDbNodeKey = NodeKeyUtils.generate(); final SynchronizerConfiguration syncConfigAhead = SynchronizerConfiguration.builder().syncMode(SyncMode.FULL).build(); final ObservableMetricsSystem noOpMetricsSystem = new NoOpMetricsSystem(); - + final var miningParameters = MiningParameters.newDefault(); + final var dataStorageConfiguration = DataStorageConfiguration.DEFAULT_FOREST_CONFIG; // Setup Runner with blocks final BesuController controllerAhead = getController( @@ -176,8 +183,10 @@ private void syncFromGenesis(final SyncMode mode, final GenesisConfigFile genesi syncConfigAhead, dataDirAhead, aheadDbNodeKey, - createKeyValueStorageProvider(dataDirAhead, dbAhead), - noOpMetricsSystem); + createKeyValueStorageProvider( + dataDirAhead, dbAhead, dataStorageConfiguration, miningParameters), + noOpMetricsSystem, + miningParameters); setupState( blockCount, controllerAhead.getProtocolSchedule(), controllerAhead.getProtocolContext()); @@ -205,6 +214,7 @@ private void syncFromGenesis(final SyncMode mode, final GenesisConfigFile genesi .graphQLConfiguration(graphQLConfiguration()) .webSocketConfiguration(wsRpcConfiguration()) .jsonRpcIpcConfiguration(new JsonRpcIpcConfiguration()) + .inProcessRpcConfiguration(ImmutableInProcessRpcConfiguration.builder().build()) .metricsConfiguration(metricsConfiguration()) .dataDir(dbAhead) .pidPath(pidPath) @@ -219,8 +229,8 @@ private void syncFromGenesis(final SyncMode mode, final GenesisConfigFile genesi final SynchronizerConfiguration syncConfigBehind = SynchronizerConfiguration.builder() .syncMode(mode) - .fastSyncPivotDistance(5) - .fastSyncMinimumPeerCount(1) + .syncPivotDistance(5) + .syncMinimumPeerCount(1) .build(); final Path dataDirBehind = Files.createTempDirectory(temp, "db-behind"); @@ -232,12 +242,13 @@ private void syncFromGenesis(final SyncMode mode, final GenesisConfigFile genesi dataDirBehind, behindDbNodeKey, new InMemoryKeyValueStorageProvider(), - noOpMetricsSystem); + noOpMetricsSystem, + miningParameters); final EnodeURL aheadEnode = runnerAhead.getLocalEnode().get(); final EthNetworkConfig behindEthNetworkConfiguration = new EthNetworkConfig( - EthNetworkConfig.jsonConfig(DEV), + GenesisConfigFile.fromResource(DEV.getGenesisFile()), DEV.getNetworkId(), Collections.singletonList(aheadEnode), null); @@ -362,8 +373,11 @@ private Request getRequest(final String method, final String baseUrl) { .build(); } - private GenesisConfigFile getFastSyncGenesis() { - final ObjectNode jsonNode = GenesisConfigFile.mainnetJsonNode(); + private GenesisConfigFile getFastSyncGenesis() throws IOException { + final ObjectNode jsonNode = + (ObjectNode) + new ObjectMapper() + .readTree(GenesisConfigFile.class.getResource(MAINNET.getGenesisFile())); final Optional configNode = JsonUtil.getObjectNode(jsonNode, "config"); configNode.ifPresent( (node) -> { @@ -375,7 +389,15 @@ private GenesisConfigFile getFastSyncGenesis() { return GenesisConfigFile.fromConfig(jsonNode); } - private StorageProvider createKeyValueStorageProvider(final Path dataDir, final Path dbDir) { + private StorageProvider createKeyValueStorageProvider( + final Path dataDir, + final Path dbDir, + final DataStorageConfiguration dataStorageConfiguration, + final MiningParameters miningParameters) { + final var besuConfiguration = new BesuConfigurationImpl(); + besuConfiguration + .init(dataDir, dbDir, dataStorageConfiguration) + .withMiningParameters(miningParameters); return new KeyValueStorageProviderBuilder() .withStorageFactory( new RocksDBKeyValueStorageFactory( @@ -387,7 +409,7 @@ private StorageProvider createKeyValueStorageProvider(final Path dataDir, final DEFAULT_IS_HIGH_SPEC), Arrays.asList(KeyValueSegmentIdentifier.values()), RocksDBMetricsFactory.PUBLIC_ROCKS_DB_METRICS)) - .withCommonConfiguration(new BesuConfigurationImpl(dataDir, dbDir)) + .withCommonConfiguration(besuConfiguration) .withMetricsSystem(new NoOpMetricsSystem()) .build(); } @@ -443,26 +465,28 @@ private BesuController getController( final Path dataDir, final NodeKey nodeKey, final StorageProvider storageProvider, - final ObservableMetricsSystem metricsSystem) { + final ObservableMetricsSystem metricsSystem, + final MiningParameters miningParameters) { return new MainnetBesuControllerBuilder() .genesisConfigFile(genesisConfig) .synchronizerConfiguration(syncConfig) .ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig()) .dataDirectory(dataDir) .networkId(NETWORK_ID) - .miningParameters(MiningParameters.newDefault()) + .miningParameters(miningParameters) .nodeKey(nodeKey) .storageProvider(storageProvider) .metricsSystem(metricsSystem) .privacyParameters(PrivacyParameters.DEFAULT) .clock(TestClock.fixed()) .transactionPoolConfiguration(TransactionPoolConfiguration.DEFAULT) + .dataStorageConfiguration(DataStorageConfiguration.DEFAULT_FOREST_CONFIG) .gasLimitCalculator(GasLimitCalculator.constant()) .evmConfiguration(EvmConfiguration.DEFAULT) .networkConfiguration(NetworkingConfiguration.create()) .randomPeerPriority(Boolean.FALSE) + .besuComponent(mock(BesuComponent.class)) .maxPeers(25) - .lowerBoundPeers(25) .maxRemotelyInitiatedPeers(15) .build(); } diff --git a/besu/src/test/java/org/hyperledger/besu/chainexport/RlpBlockExporterTest.java b/besu/src/test/java/org/hyperledger/besu/chainexport/RlpBlockExporterTest.java index 89e1b255479..bbc7dea1abb 100644 --- a/besu/src/test/java/org/hyperledger/besu/chainexport/RlpBlockExporterTest.java +++ b/besu/src/test/java/org/hyperledger/besu/chainexport/RlpBlockExporterTest.java @@ -16,9 +16,12 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; import org.hyperledger.besu.chainimport.RlpBlockImporter; -import org.hyperledger.besu.config.GenesisConfigFile; +import org.hyperledger.besu.cli.config.EthNetworkConfig; +import org.hyperledger.besu.cli.config.NetworkName; +import org.hyperledger.besu.components.BesuComponent; import org.hyperledger.besu.controller.BesuController; import org.hyperledger.besu.cryptoservices.NodeKeyUtils; import org.hyperledger.besu.ethereum.GasLimitCalculator; @@ -46,29 +49,30 @@ import java.io.File; import java.io.IOException; import java.math.BigInteger; +import java.nio.file.Files; import java.nio.file.Path; import java.util.Optional; -import org.junit.BeforeClass; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.junit.jupiter.MockitoExtension; /** Tests for {@link BlockExporter}. */ -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public final class RlpBlockExporterTest { - @ClassRule public static final TemporaryFolder folder = new TemporaryFolder(); + @TempDir public static Path folder; private static Blockchain blockchain; private static long chainHead; private static ProtocolSchedule protocolSchedule; - @BeforeClass + @BeforeAll public static void setupBlockchain() throws IOException { - final BesuController controller = createController(); - final Path blocks = folder.newFile("1000.blocks").toPath(); + final BesuController controller = + createController(Files.createTempDirectory(folder, "rlpBlockExporterTestData")); + final Path blocks = Files.createTempFile(folder, "1000", "blocks"); BlockTestUtil.write1000Blocks(blocks); blockchain = importBlocks(controller, blocks); chainHead = blockchain.getChainHeadBlockNumber(); @@ -83,10 +87,9 @@ private static Blockchain importBlocks(final BesuController controller, final Pa return controller.getProtocolContext().getBlockchain(); } - private static BesuController createController() throws IOException { - final Path dataDir = folder.newFolder().toPath(); + private static BesuController createController(final @TempDir Path dataDir) throws IOException { return new BesuController.Builder() - .fromGenesisConfig(GenesisConfigFile.mainnet(), SyncMode.FAST) + .fromEthNetworkConfig(EthNetworkConfig.getNetworkConfig(NetworkName.MAINNET), SyncMode.FAST) .synchronizerConfiguration(SynchronizerConfiguration.builder().build()) .ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig()) .storageProvider(new InMemoryKeyValueStorageProvider()) @@ -101,17 +104,18 @@ private static BesuController createController() throws IOException { .gasLimitCalculator(GasLimitCalculator.constant()) .evmConfiguration(EvmConfiguration.DEFAULT) .networkConfiguration(NetworkingConfiguration.create()) + .besuComponent(mock(BesuComponent.class)) .build(); } @Test - public void exportBlocks_noBounds() throws IOException { - final File outputPath = folder.newFile(); + public void exportBlocks_noBounds(final @TempDir Path outputDir) throws IOException { + final Path outputPath = outputDir.resolve("output"); final RlpBlockExporter exporter = new RlpBlockExporter(blockchain); - exporter.exportBlocks(outputPath, Optional.empty(), Optional.empty()); + exporter.exportBlocks(outputPath.toFile(), Optional.empty(), Optional.empty()); // Iterate over blocks and check that they match expectations - final RawBlockIterator blockIterator = getBlockIterator(outputPath.toPath()); + final RawBlockIterator blockIterator = getBlockIterator(outputPath); long currentBlockNumber = 0; while (blockIterator.hasNext()) { final Block actual = blockIterator.next(); @@ -125,15 +129,15 @@ public void exportBlocks_noBounds() throws IOException { } @Test - public void exportBlocks_withLowerBound() throws IOException { - final File outputPath = folder.newFile(); + public void exportBlocks_withLowerBound(final @TempDir Path outputDir) throws IOException { + final Path outputPath = outputDir.resolve("output"); final RlpBlockExporter exporter = new RlpBlockExporter(blockchain); final long lowerBound = 990; - exporter.exportBlocks(outputPath, Optional.of(lowerBound), Optional.empty()); + exporter.exportBlocks(outputPath.toFile(), Optional.of(lowerBound), Optional.empty()); // Iterate over blocks and check that they match expectations - final RawBlockIterator blockIterator = getBlockIterator(outputPath.toPath()); + final RawBlockIterator blockIterator = getBlockIterator(outputPath); long currentBlockNumber = lowerBound; while (blockIterator.hasNext()) { final Block actual = blockIterator.next(); @@ -147,15 +151,15 @@ public void exportBlocks_withLowerBound() throws IOException { } @Test - public void exportBlocks_withUpperBound() throws IOException { - final File outputPath = folder.newFile(); + public void exportBlocks_withUpperBound(final @TempDir Path outputDir) throws IOException { + final Path outputPath = outputDir.resolve("output"); final RlpBlockExporter exporter = new RlpBlockExporter(blockchain); final long upperBound = 10; - exporter.exportBlocks(outputPath, Optional.empty(), Optional.of(upperBound)); + exporter.exportBlocks(outputPath.toFile(), Optional.empty(), Optional.of(upperBound)); // Iterate over blocks and check that they match expectations - final RawBlockIterator blockIterator = getBlockIterator(outputPath.toPath()); + final RawBlockIterator blockIterator = getBlockIterator(outputPath); long currentBlockNumber = 0; while (blockIterator.hasNext()) { final Block actual = blockIterator.next(); @@ -169,16 +173,17 @@ public void exportBlocks_withUpperBound() throws IOException { } @Test - public void exportBlocks_withUpperAndLowerBounds() throws IOException { - final File outputPath = folder.newFile(); + public void exportBlocks_withUpperAndLowerBounds(final @TempDir Path outputDir) + throws IOException { + final Path outputPath = outputDir.resolve("output"); final RlpBlockExporter exporter = new RlpBlockExporter(blockchain); final long lowerBound = 5; final long upperBound = 10; - exporter.exportBlocks(outputPath, Optional.of(lowerBound), Optional.of(upperBound)); + exporter.exportBlocks(outputPath.toFile(), Optional.of(lowerBound), Optional.of(upperBound)); // Iterate over blocks and check that they match expectations - final RawBlockIterator blockIterator = getBlockIterator(outputPath.toPath()); + final RawBlockIterator blockIterator = getBlockIterator(outputPath); long currentBlockNumber = lowerBound; while (blockIterator.hasNext()) { final Block actual = blockIterator.next(); @@ -192,16 +197,17 @@ public void exportBlocks_withUpperAndLowerBounds() throws IOException { } @Test - public void exportBlocks_withRangeBeyondChainHead() throws IOException { - final File outputPath = folder.newFile(); + public void exportBlocks_withRangeBeyondChainHead(final @TempDir Path outputDir) + throws IOException { + final Path outputPath = outputDir.resolve("output"); final RlpBlockExporter exporter = new RlpBlockExporter(blockchain); final long lowerBound = chainHead - 10; final long upperBound = chainHead + 10; - exporter.exportBlocks(outputPath, Optional.of(lowerBound), Optional.of(upperBound)); + exporter.exportBlocks(outputPath.toFile(), Optional.of(lowerBound), Optional.of(upperBound)); // Iterate over blocks and check that they match expectations - final RawBlockIterator blockIterator = getBlockIterator(outputPath.toPath()); + final RawBlockIterator blockIterator = getBlockIterator(outputPath); long currentBlockNumber = lowerBound; while (blockIterator.hasNext()) { final Block actual = blockIterator.next(); @@ -215,8 +221,7 @@ public void exportBlocks_withRangeBeyondChainHead() throws IOException { } @Test - public void exportBlocks_negativeStartNumber() throws IOException { - final File outputPath = folder.newFile(); + public void exportBlocks_negativeStartNumber(final @TempDir File outputPath) throws IOException { final RlpBlockExporter exporter = new RlpBlockExporter(blockchain); assertThatThrownBy(() -> exporter.exportBlocks(outputPath, Optional.of(-1L), Optional.empty())) @@ -225,8 +230,7 @@ public void exportBlocks_negativeStartNumber() throws IOException { } @Test - public void exportBlocks_negativeEndNumber() throws IOException { - final File outputPath = folder.newFile(); + public void exportBlocks_negativeEndNumber(final @TempDir File outputPath) throws IOException { final RlpBlockExporter exporter = new RlpBlockExporter(blockchain); assertThatThrownBy(() -> exporter.exportBlocks(outputPath, Optional.empty(), Optional.of(-1L))) @@ -235,8 +239,7 @@ public void exportBlocks_negativeEndNumber() throws IOException { } @Test - public void exportBlocks_outOfOrderBounds() throws IOException { - final File outputPath = folder.newFile(); + public void exportBlocks_outOfOrderBounds(final @TempDir File outputPath) throws IOException { final RlpBlockExporter exporter = new RlpBlockExporter(blockchain); assertThatThrownBy(() -> exporter.exportBlocks(outputPath, Optional.of(10L), Optional.of(2L))) diff --git a/besu/src/test/java/org/hyperledger/besu/chainimport/JsonBlockImporterTest.java b/besu/src/test/java/org/hyperledger/besu/chainimport/JsonBlockImporterTest.java index 00ebc610823..7b1a4bc7d2c 100644 --- a/besu/src/test/java/org/hyperledger/besu/chainimport/JsonBlockImporterTest.java +++ b/besu/src/test/java/org/hyperledger/besu/chainimport/JsonBlockImporterTest.java @@ -17,7 +17,9 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import org.hyperledger.besu.components.BesuComponent; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.config.JsonUtil; import org.hyperledger.besu.controller.BesuController; @@ -48,29 +50,28 @@ import java.net.URL; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; import java.util.List; +import java.util.stream.Stream; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.io.Resources; import org.apache.tuweni.bytes.Bytes; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; public abstract class JsonBlockImporterTest { - @Rule public final TemporaryFolder folder = new TemporaryFolder(); + @TempDir public Path dataDir; - protected final String consensusEngine; - protected final GenesisConfigFile genesisConfigFile; - protected final boolean isEthash; + protected String consensusEngine; + protected GenesisConfigFile genesisConfigFile; + protected boolean isEthash; - protected JsonBlockImporterTest(final String consensusEngine) throws IOException { + protected void setup(final String consensusEngine) throws IOException { this.consensusEngine = consensusEngine; final String genesisData = getFileContents("genesis.json"); this.genesisConfigFile = GenesisConfigFile.fromConfig(genesisData); @@ -78,8 +79,10 @@ protected JsonBlockImporterTest(final String consensusEngine) throws IOException } public static class SingletonTests extends JsonBlockImporterTest { - public SingletonTests() throws IOException { - super("unsupported"); + + @BeforeEach + public void setup() throws IOException { + super.setup("unsupported"); } @Test @@ -97,21 +100,23 @@ public void importChain_unsupportedConsensusAlgorithm() throws IOException { } } - @RunWith(Parameterized.class) public static class ParameterizedTests extends JsonBlockImporterTest { - public ParameterizedTests(final String consensusEngine) throws IOException { - super(consensusEngine); + @Override + public void setup(final String consensusEngine) throws IOException { + super.setup(consensusEngine); } - @Parameters(name = "Name: {0}") - public static Collection getParameters() { - final Object[][] params = {{"ethash"}, {"clique"}}; - return Arrays.asList(params); + public static Stream getParameters() { + return Stream.of(Arguments.of("ethash"), Arguments.of("clique")); } - @Test - public void importChain_validJson_withBlockNumbers() throws IOException { + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("getParameters") + public void importChain_validJson_withBlockNumbers(final String consensusEngine) + throws IOException { + setup(consensusEngine); + final BesuController controller = createController(); final JsonBlockImporter importer = new JsonBlockImporter(controller); @@ -201,8 +206,12 @@ public void importChain_validJson_withBlockNumbers() throws IOException { assertThat(tx.getNonce()).isEqualTo(1L); } - @Test - public void importChain_validJson_noBlockIdentifiers() throws IOException { + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("getParameters") + public void importChain_validJson_noBlockIdentifiers(final String consensusEngine) + throws IOException { + setup(consensusEngine); + final BesuController controller = createController(); final JsonBlockImporter importer = new JsonBlockImporter(controller); @@ -292,8 +301,12 @@ public void importChain_validJson_noBlockIdentifiers() throws IOException { assertThat(tx.getNonce()).isEqualTo(1L); } - @Test - public void importChain_validJson_withParentHashes() throws IOException { + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("getParameters") + public void importChain_validJson_withParentHashes(final String consensusEngine) + throws IOException { + setup(consensusEngine); + final BesuController controller = createController(); final JsonBlockImporter importer = new JsonBlockImporter(controller); @@ -343,8 +356,11 @@ public void importChain_validJson_withParentHashes() throws IOException { assertThat(tx.getNonce()).isEqualTo(2L); } - @Test - public void importChain_invalidParent() throws IOException { + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("getParameters") + public void importChain_invalidParent(final String consensusEngine) throws IOException { + setup(consensusEngine); + final BesuController controller = createController(); final JsonBlockImporter importer = new JsonBlockImporter(controller); @@ -355,8 +371,11 @@ public void importChain_invalidParent() throws IOException { .hasMessageStartingWith("Unable to locate block parent at 2456"); } - @Test - public void importChain_invalidTransaction() throws IOException { + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("getParameters") + public void importChain_invalidTransaction(final String consensusEngine) throws IOException { + setup(consensusEngine); + final BesuController controller = createController(); final JsonBlockImporter importer = new JsonBlockImporter(controller); @@ -368,8 +387,11 @@ public void importChain_invalidTransaction() throws IOException { "Unable to create block. 1 transaction(s) were found to be invalid."); } - @Test - public void importChain_specialFields() throws IOException { + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("getParameters") + public void importChain_specialFields(final String consensusEngine) throws IOException { + setup(consensusEngine); + final BesuController controller = createController(); final JsonBlockImporter importer = new JsonBlockImporter(controller); @@ -390,6 +412,13 @@ public void importChain_specialFields() throws IOException { + genesisConfigFile.getConfigOptions().getConsensusEngine()); } } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } protected Block getBlockAt(final Blockchain blockchain, final long blockNumber) { @@ -412,11 +441,9 @@ protected BesuController createController() throws IOException { return createController(genesisConfigFile); } - protected BesuController createController(final GenesisConfigFile genesisConfigFile) - throws IOException { - final Path dataDir = folder.newFolder().toPath(); + protected BesuController createController(final GenesisConfigFile genesisConfigFile) { return new BesuController.Builder() - .fromGenesisConfig(genesisConfigFile, SyncMode.FAST) + .fromGenesisFile(genesisConfigFile, SyncMode.FAST) .synchronizerConfiguration(SynchronizerConfiguration.builder().build()) .ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig()) .storageProvider(new InMemoryKeyValueStorageProvider()) @@ -438,6 +465,7 @@ protected BesuController createController(final GenesisConfigFile genesisConfigF .gasLimitCalculator(GasLimitCalculator.constant()) .evmConfiguration(EvmConfiguration.DEFAULT) .networkConfiguration(NetworkingConfiguration.create()) + .besuComponent(mock(BesuComponent.class)) .build(); } } diff --git a/besu/src/test/java/org/hyperledger/besu/chainimport/RlpBlockImporterTest.java b/besu/src/test/java/org/hyperledger/besu/chainimport/RlpBlockImporterTest.java index d65569535aa..7d4fabb2225 100644 --- a/besu/src/test/java/org/hyperledger/besu/chainimport/RlpBlockImporterTest.java +++ b/besu/src/test/java/org/hyperledger/besu/chainimport/RlpBlockImporterTest.java @@ -16,8 +16,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; -import org.hyperledger.besu.config.GenesisConfigFile; +import org.hyperledger.besu.cli.config.EthNetworkConfig; +import org.hyperledger.besu.cli.config.NetworkName; +import org.hyperledger.besu.components.BesuComponent; import org.hyperledger.besu.config.MergeConfigOptions; import org.hyperledger.besu.controller.BesuController; import org.hyperledger.besu.cryptoservices.NodeKeyUtils; @@ -60,7 +63,8 @@ public void blockImport() throws IOException { BlockTestUtil.write1000Blocks(source); final BesuController targetController = new BesuController.Builder() - .fromGenesisConfig(GenesisConfigFile.mainnet(), SyncMode.FAST) + .fromEthNetworkConfig( + EthNetworkConfig.getNetworkConfig(NetworkName.MAINNET), SyncMode.FAST) .synchronizerConfiguration(SynchronizerConfiguration.builder().build()) .ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig()) .storageProvider(new InMemoryKeyValueStorageProvider()) @@ -75,6 +79,7 @@ public void blockImport() throws IOException { .gasLimitCalculator(GasLimitCalculator.constant()) .evmConfiguration(EvmConfiguration.DEFAULT) .networkConfiguration(NetworkingConfiguration.create()) + .besuComponent(mock(BesuComponent.class)) .build(); final RlpBlockImporter.ImportResult result = rlpBlockImporter.importBlockchain(source, targetController, false); @@ -92,7 +97,8 @@ public void blockImportRejectsBadPow() throws IOException { BlockTestUtil.writeBadPowBlocks(source); final BesuController targetController = new BesuController.Builder() - .fromGenesisConfig(GenesisConfigFile.mainnet(), SyncMode.FAST) + .fromEthNetworkConfig( + EthNetworkConfig.getNetworkConfig(NetworkName.MAINNET), SyncMode.FAST) .synchronizerConfiguration(SynchronizerConfiguration.builder().build()) .ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig()) .storageProvider(new InMemoryKeyValueStorageProvider()) @@ -107,6 +113,7 @@ public void blockImportRejectsBadPow() throws IOException { .gasLimitCalculator(GasLimitCalculator.constant()) .evmConfiguration(EvmConfiguration.DEFAULT) .networkConfiguration(NetworkingConfiguration.create()) + .besuComponent(mock(BesuComponent.class)) .build(); assertThatThrownBy( @@ -121,7 +128,8 @@ public void blockImportCanSkipPow() throws IOException { BlockTestUtil.writeBadPowBlocks(source); final BesuController targetController = new BesuController.Builder() - .fromGenesisConfig(GenesisConfigFile.mainnet(), SyncMode.FAST) + .fromEthNetworkConfig( + EthNetworkConfig.getNetworkConfig(NetworkName.MAINNET), SyncMode.FAST) .synchronizerConfiguration(SynchronizerConfiguration.builder().build()) .ethProtocolConfiguration(EthProtocolConfiguration.defaultConfig()) .storageProvider(new InMemoryKeyValueStorageProvider()) @@ -136,6 +144,7 @@ public void blockImportCanSkipPow() throws IOException { .gasLimitCalculator(GasLimitCalculator.constant()) .evmConfiguration(EvmConfiguration.DEFAULT) .networkConfiguration(NetworkingConfiguration.create()) + .besuComponent(mock(BesuComponent.class)) .build(); final RlpBlockImporter.ImportResult result = diff --git a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java index 19e42e1cd82..e6e0e859517 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java @@ -15,51 +15,37 @@ package org.hyperledger.besu.cli; import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.startsWith; import static org.hyperledger.besu.cli.config.NetworkName.CLASSIC; import static org.hyperledger.besu.cli.config.NetworkName.DEV; import static org.hyperledger.besu.cli.config.NetworkName.EXPERIMENTAL_EIPS; import static org.hyperledger.besu.cli.config.NetworkName.FUTURE_EIPS; -import static org.hyperledger.besu.cli.config.NetworkName.GOERLI; import static org.hyperledger.besu.cli.config.NetworkName.HOLESKY; +import static org.hyperledger.besu.cli.config.NetworkName.LUKSO; import static org.hyperledger.besu.cli.config.NetworkName.MAINNET; import static org.hyperledger.besu.cli.config.NetworkName.MORDOR; import static org.hyperledger.besu.cli.config.NetworkName.SEPOLIA; -import static org.hyperledger.besu.cli.util.CommandLineUtils.DEPENDENCY_WARNING_MSG; -import static org.hyperledger.besu.cli.util.CommandLineUtils.DEPRECATION_WARNING_MSG; import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.ENGINE; -import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.ETH; -import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.NET; -import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.PERM; -import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.WEB3; -import static org.hyperledger.besu.ethereum.p2p.config.DefaultDiscoveryConfiguration.GOERLI_BOOTSTRAP_NODES; -import static org.hyperledger.besu.ethereum.p2p.config.DefaultDiscoveryConfiguration.GOERLI_DISCOVERY_URL; import static org.hyperledger.besu.ethereum.p2p.config.DefaultDiscoveryConfiguration.MAINNET_BOOTSTRAP_NODES; import static org.hyperledger.besu.ethereum.p2p.config.DefaultDiscoveryConfiguration.MAINNET_DISCOVERY_URL; -import static org.hyperledger.besu.ethereum.worldstate.DataStorageFormat.BONSAI; -import static org.hyperledger.besu.nat.kubernetes.KubernetesNatManager.DEFAULT_BESU_SERVICE_NAME_FILTER; +import static org.hyperledger.besu.ethereum.p2p.config.DefaultDiscoveryConfiguration.SEPOLIA_BOOTSTRAP_NODES; +import static org.hyperledger.besu.ethereum.p2p.config.DefaultDiscoveryConfiguration.SEPOLIA_DISCOVERY_URL; +import static org.hyperledger.besu.plugin.services.storage.DataStorageFormat.BONSAI; import static org.junit.Assume.assumeThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.contains; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNotNull; import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; import org.hyperledger.besu.BesuInfo; import org.hyperledger.besu.cli.config.EthNetworkConfig; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.config.MergeConfigOptions; import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; -import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.GasLimitCalculator; @@ -68,85 +54,59 @@ import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration; import org.hyperledger.besu.ethereum.api.handlers.TimeoutOptions; import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration; -import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; -import org.hyperledger.besu.ethereum.api.jsonrpc.authentication.JwtAlgorithm; import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration; -import org.hyperledger.besu.ethereum.api.tls.TlsConfiguration; -import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters; -import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters.MutableInitValues; import org.hyperledger.besu.ethereum.core.MiningParameters; -import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.eth.sync.SyncMode; import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; -import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl; -import org.hyperledger.besu.ethereum.permissioning.LocalPermissioningConfiguration; -import org.hyperledger.besu.ethereum.permissioning.PermissioningConfiguration; -import org.hyperledger.besu.ethereum.permissioning.SmartContractPermissioningConfiguration; import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; -import org.hyperledger.besu.ethereum.worldstate.PrunerConfiguration; import org.hyperledger.besu.evm.precompile.AbstractAltBnPrecompiledContract; import org.hyperledger.besu.evm.precompile.KZGPointEvalPrecompiledContract; import org.hyperledger.besu.metrics.StandardMetricCategory; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; -import org.hyperledger.besu.nat.NatMethod; -import org.hyperledger.besu.pki.config.PkiKeyStoreConfiguration; import org.hyperledger.besu.plugin.data.EnodeURL; -import org.hyperledger.besu.plugin.services.privacy.PrivateMarkerTransactionFactory; -import org.hyperledger.besu.plugin.services.rpc.PluginRpcRequest; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.util.number.Fraction; import org.hyperledger.besu.util.number.Percentage; +import org.hyperledger.besu.util.number.PositiveNumber; import org.hyperledger.besu.util.platform.PlatformDetector; import java.io.File; import java.io.IOException; import java.math.BigInteger; -import java.net.ServerSocket; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Arrays; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; -import com.google.common.collect.Lists; import com.google.common.io.Resources; import io.vertx.core.json.JsonObject; -import org.apache.commons.io.FileUtils; -import org.apache.commons.text.StringEscapeUtils; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.toml.Toml; import org.apache.tuweni.toml.TomlParseResult; -import org.junit.After; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; import picocli.CommandLine; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class BesuCommandTest extends CommandTestAbstract { - private static final String ENCLAVE_URI = "http://1.2.3.4:5555"; - private static final String ENCLAVE_PUBLIC_KEY = "A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; private static final String VALID_NODE_ID = "6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0"; - private static final String PERMISSIONING_CONFIG_TOML = "/permissioning_config.toml"; private static final JsonRpcConfiguration DEFAULT_JSON_RPC_CONFIGURATION; private static final GraphQLConfiguration DEFAULT_GRAPH_QL_CONFIGURATION; private static final WebSocketConfiguration DEFAULT_WEB_SOCKET_CONFIGURATION; @@ -163,8 +123,6 @@ public class BesuCommandTest extends CommandTestAbstract { (new JsonObject()).put("config", new JsonObject().put("ecCurve", "abcd")); private static final JsonObject VALID_GENESIS_EC_CURVE = (new JsonObject()).put("config", new JsonObject().put("ecCurve", "secp256k1")); - private static final String ENCLAVE_PUBLIC_KEY_PATH = - BesuCommand.class.getResource("/orion_publickey.pub").getPath(); private static final String[] VALID_ENODE_STRINGS = { "enode://" + VALID_NODE_ID + "@192.168.0.1:4567", @@ -187,9 +145,6 @@ public class BesuCommandTest extends CommandTestAbstract { private static final JsonObject GENESIS_WITH_DATA_BLOBS_ENABLED = new JsonObject().put("config", new JsonObject().put("cancunTime", 1L)); - private static final JsonObject GENESIS_WITH_ZERO_BASE_FEE_MARKET = - new JsonObject().put("config", new JsonObject().put("zeroBaseFee", true)); - static { DEFAULT_JSON_RPC_CONFIGURATION = JsonRpcConfiguration.createDefault(); DEFAULT_GRAPH_QL_CONFIGURATION = GraphQLConfiguration.createDefault(); @@ -198,7 +153,7 @@ public class BesuCommandTest extends CommandTestAbstract { DEFAULT_API_CONFIGURATION = ImmutableApiConfiguration.builder().build(); } - @Before + @BeforeEach public void setup() { try { // optimistically tear down a potential previous loaded trusted setup @@ -210,12 +165,66 @@ public void setup() { MergeConfigOptions.setMergeEnabled(false); } - @After + @AfterEach public void tearDown() { MergeConfigOptions.setMergeEnabled(false); } + @Test + public void testGenesisOverrideOptions() throws Exception { + parseCommand("--override-genesis-config", "shanghaiTime=123"); + + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); + + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); + + final EthNetworkConfig config = networkArg.getValue(); + // mainnet defaults + assertThat(config.networkId()).isEqualTo(BigInteger.valueOf(1)); + + // assert that shanghaiTime override is applied + final GenesisConfigFile actualGenesisConfigFile = (config.genesisConfigFile()); + assertThat(actualGenesisConfigFile).isNotNull(); + assertThat(actualGenesisConfigFile.getConfigOptions().getShanghaiTime()).isNotEmpty(); + assertThat(actualGenesisConfigFile.getConfigOptions().getShanghaiTime().getAsLong()) + .isEqualTo(123); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void testGenesisOverrideOptionsWithCustomGenesis() throws Exception { + final Path genesisFile = createFakeGenesisFile(GENESIS_VALID_JSON); + + parseCommand( + "--genesis-file", genesisFile.toString(), "--override-genesis-config", "shanghaiTime=123"); + + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); + + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); + + final EthNetworkConfig config = networkArg.getValue(); + assertThat(config.bootNodes()).isEmpty(); + assertThat(config.dnsDiscoveryUrl()).isNull(); + assertThat(config.networkId()).isEqualTo(BigInteger.valueOf(3141592)); + + // then assert that the shanghaiTime is applied + final GenesisConfigFile actualGenesisConfigFile = (config.genesisConfigFile()); + assertThat(actualGenesisConfigFile).isNotNull(); + assertThat(actualGenesisConfigFile.getConfigOptions().getShanghaiTime()).isNotEmpty(); + assertThat(actualGenesisConfigFile.getConfigOptions().getShanghaiTime().getAsLong()) + .isEqualTo(123); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + @Test public void callingHelpSubCommandMustDisplayUsage() { parseCommand("--help"); @@ -251,7 +260,7 @@ public void callingBesuCommandWithoutOptionsMustSyncWithDefaultValues() { verify(mockRunnerBuilder) .ethNetworkConfig( new EthNetworkConfig( - EthNetworkConfig.jsonConfig(MAINNET), + GenesisConfigFile.fromResource(MAINNET.getGenesisFile()), MAINNET.getNetworkId(), MAINNET_BOOTSTRAP_NODES, MAINNET_DISCOVERY_URL)); @@ -266,8 +275,7 @@ public void callingBesuCommandWithoutOptionsMustSyncWithDefaultValues() { verify(mockRunnerBuilder).apiConfiguration(DEFAULT_API_CONFIGURATION); verify(mockRunnerBuilder).build(); - verify(mockControllerBuilderFactory) - .fromEthNetworkConfig(ethNetworkArg.capture(), any(), any()); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(ethNetworkArg.capture(), any()); final ArgumentCaptor miningArg = ArgumentCaptor.forClass(MiningParameters.class); verify(mockControllerBuilder).synchronizerConfiguration(syncConfigurationCaptor.capture()); @@ -277,18 +285,17 @@ public void callingBesuCommandWithoutOptionsMustSyncWithDefaultValues() { verify(mockControllerBuilder).storageProvider(storageProviderArgumentCaptor.capture()); verify(mockControllerBuilder).gasLimitCalculator(eq(GasLimitCalculator.constant())); verify(mockControllerBuilder).maxPeers(eq(maxPeers)); - verify(mockControllerBuilder).lowerBoundPeers(eq(maxPeers)); verify(mockControllerBuilder).maxRemotelyInitiatedPeers(eq((int) Math.floor(0.6 * maxPeers))); verify(mockControllerBuilder).build(); assertThat(storageProviderArgumentCaptor.getValue()).isNotNull(); - assertThat(syncConfigurationCaptor.getValue().getSyncMode()).isEqualTo(SyncMode.FAST); + assertThat(syncConfigurationCaptor.getValue().getSyncMode()).isEqualTo(SyncMode.SNAP); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); assertThat(miningArg.getValue().getCoinbase()).isEqualTo(Optional.empty()); assertThat(miningArg.getValue().getMinTransactionGasPrice()).isEqualTo(Wei.of(1000)); assertThat(miningArg.getValue().getExtraData()).isEqualTo(Bytes.EMPTY); - assertThat(ethNetworkArg.getValue().getNetworkId()).isEqualTo(1); - assertThat(ethNetworkArg.getValue().getBootNodes()).isEqualTo(MAINNET_BOOTSTRAP_NODES); + assertThat(ethNetworkArg.getValue().networkId()).isEqualTo(1); + assertThat(ethNetworkArg.getValue().bootNodes()).isEqualTo(MAINNET_BOOTSTRAP_NODES); } // Testing each option @@ -307,17 +314,18 @@ public void callingWithConfigOptionButNonExistingFileShouldDisplayHelp() throws final Path tempConfigFilePath = createTempFile("an-invalid-file-name-without-extension", ""); parseCommand("--config-file", tempConfigFilePath.toString()); - final String expectedOutputStart = - "Unable to read TOML configuration file " + tempConfigFilePath; + final String expectedOutputStart = "Unable to read from empty TOML configuration file."; assertThat(commandErrorOutput.toString(UTF_8)).startsWith(expectedOutputStart); assertThat(commandOutput.toString(UTF_8)).isEmpty(); } @Test public void callingWithConfigOptionButTomlFileNotFoundShouldDisplayHelp() { - parseCommand("--config-file", "./an-invalid-file-name-sdsd87sjhqoi34io23.toml"); + String invalidFile = "./an-invalid-file-name-sdsd87sjhqoi34io23.toml"; + parseCommand("--config-file", invalidFile); - final String expectedOutputStart = "Unable to read TOML configuration, file not found."; + final String expectedOutputStart = + String.format("Unable to read TOML configuration, file not found: %s", invalidFile); assertThat(commandErrorOutput.toString(UTF_8)).startsWith(expectedOutputStart); assertThat(commandOutput.toString(UTF_8)).isEmpty(); } @@ -345,7 +353,7 @@ public void callingWithNoBootnodesConfig() throws Exception { parseCommand("--config-file", toml.toAbsolutePath().toString()); verify(mockRunnerBuilder).ethNetworkConfig(ethNetworkConfigArgumentCaptor.capture()); - assertThat(ethNetworkConfigArgumentCaptor.getValue().getBootNodes()).isEmpty(); + assertThat(ethNetworkConfigArgumentCaptor.getValue().bootNodes()).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); assertThat(commandOutput.toString(UTF_8)).isEmpty(); @@ -366,4229 +374,1703 @@ public void callingWithConfigOptionButInvalidValueTomlFileShouldDisplayHelp() th } @Test - public void overrideDefaultValuesIfKeyIsPresentInConfigFile() throws IOException { - final URL configFile = this.getClass().getResource("/complete_config.toml"); - final Path genesisFile = createFakeGenesisFile(GENESIS_VALID_JSON); - final File dataFolder = temp.newFolder(); - final String updatedConfig = - Resources.toString(configFile, UTF_8) - .replace("/opt/besu/genesis.json", escapeTomlString(genesisFile.toString())) - .replace( - "data-path=\"/opt/besu\"", - "data-path=\"" + escapeTomlString(dataFolder.getPath()) + "\""); - - final Path toml = createTempFile("toml", updatedConfig.getBytes(UTF_8)); - - final List expectedApis = asList(ETH.name(), WEB3.name()); - - final JsonRpcConfiguration jsonRpcConfiguration = JsonRpcConfiguration.createDefault(); - jsonRpcConfiguration.setEnabled(false); - jsonRpcConfiguration.setHost("5.6.7.8"); - jsonRpcConfiguration.setPort(5678); - jsonRpcConfiguration.setCorsAllowedDomains(Collections.emptyList()); - jsonRpcConfiguration.setRpcApis(expectedApis); - jsonRpcConfiguration.setMaxActiveConnections(1000); + public void tomlThatConfiguresEverythingExceptPermissioningToml() throws IOException { + // Load a TOML that configures literally everything (except permissioning TOML config) + final URL configFile = this.getClass().getResource("/everything_config.toml"); + final Path toml = createTempFile("toml", Resources.toByteArray(configFile)); - final GraphQLConfiguration graphQLConfiguration = GraphQLConfiguration.createDefault(); - graphQLConfiguration.setEnabled(false); - graphQLConfiguration.setHost("6.7.8.9"); - graphQLConfiguration.setPort(6789); + // Parse it. + final CommandLine.Model.CommandSpec spec = parseCommand("--config-file", toml.toString()).spec; + final TomlParseResult tomlResult = Toml.parse(toml); - final WebSocketConfiguration webSocketConfiguration = WebSocketConfiguration.createDefault(); - webSocketConfiguration.setEnabled(false); - webSocketConfiguration.setHost("9.10.11.12"); - webSocketConfiguration.setPort(9101); - webSocketConfiguration.setRpcApis(expectedApis); + // Verify we configured everything + final HashSet options = new HashSet<>(spec.options()); - final MetricsConfiguration metricsConfiguration = - MetricsConfiguration.builder().enabled(false).host("8.6.7.5").port(309).build(); + // Except for meta-options + options.remove(spec.optionsMap().get("--config-file")); + options.remove(spec.optionsMap().get("--help")); + options.remove(spec.optionsMap().get("--version")); - parseCommand("--config-file", toml.toString()); + for (final String tomlKey : tomlResult.keySet()) { + final CommandLine.Model.OptionSpec optionSpec = spec.optionsMap().get("--" + tomlKey); + assertThat(optionSpec) + .describedAs("Option '%s' should be a configurable option.", tomlKey) + .isNotNull(); + // Verify TOML stores it by the appropriate type + if (optionSpec.type().equals(Boolean.class)) { + tomlResult.getBoolean(tomlKey); + } else if (optionSpec.isMultiValue() || optionSpec.arity().max > 1) { + tomlResult.getArray(tomlKey); + } else if (optionSpec.type().equals(Double.class)) { + tomlResult.getDouble(tomlKey); + } else if (optionSpec.type().equals(Float.class)) { + tomlResult.getDouble(tomlKey); + } else if (Number.class.isAssignableFrom(optionSpec.type())) { + tomlResult.getLong(tomlKey); + } else if (Wei.class.isAssignableFrom(optionSpec.type())) { + tomlResult.getLong(tomlKey); + } else if (Fraction.class.isAssignableFrom(optionSpec.type())) { + tomlResult.getDouble(tomlKey); + } else if (Percentage.class.isAssignableFrom(optionSpec.type())) { + tomlResult.getLong(tomlKey); + } else if (PositiveNumber.class.isAssignableFrom(optionSpec.type())) { + tomlResult.getLong(tomlKey); + } else { + tomlResult.getString(tomlKey); + } + options.remove(optionSpec); + } + assertThat( + options.stream() + .filter(optionSpec -> !optionSpec.hidden()) + .map(CommandLine.Model.OptionSpec::longestName)) + .isEmpty(); + } - verify(mockRunnerBuilder).discovery(eq(false)); - verify(mockRunnerBuilder).ethNetworkConfig(ethNetworkConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).p2pAdvertisedHost(eq("1.2.3.4")); - verify(mockRunnerBuilder).p2pListenPort(eq(1234)); - verify(mockRunnerBuilder).jsonRpcConfiguration(eq(jsonRpcConfiguration)); - verify(mockRunnerBuilder).graphQLConfiguration(eq(graphQLConfiguration)); - verify(mockRunnerBuilder).webSocketConfiguration(eq(webSocketConfiguration)); - verify(mockRunnerBuilder).metricsConfiguration(eq(metricsConfiguration)); - verify(mockRunnerBuilder).build(); + @Test + public void nodekeyOptionMustBeUsed() throws Exception { + final File file = new File("./specific/enclavePrivateKey"); - final List nodes = - asList( - EnodeURLImpl.fromString("enode://" + VALID_NODE_ID + "@192.168.0.1:4567"), - EnodeURLImpl.fromString("enode://" + VALID_NODE_ID + "@192.168.0.1:4567"), - EnodeURLImpl.fromString("enode://" + VALID_NODE_ID + "@192.168.0.1:4567")); - assertThat(ethNetworkConfigArgumentCaptor.getValue().getBootNodes()).isEqualTo(nodes); - - final EthNetworkConfig networkConfig = - new EthNetworkConfig.Builder(EthNetworkConfig.getNetworkConfig(MAINNET)) - .setNetworkId(BigInteger.valueOf(42)) - .setGenesisConfig(encodeJsonGenesis(GENESIS_VALID_JSON)) - .setBootNodes(nodes) - .setDnsDiscoveryUrl(null) - .build(); - verify(mockControllerBuilder).dataDirectory(eq(dataFolder.toPath())); - verify(mockControllerBuilderFactory).fromEthNetworkConfig(eq(networkConfig), any(), any()); - verify(mockControllerBuilder).synchronizerConfiguration(syncConfigurationCaptor.capture()); + parseCommand("--node-private-key-file", file.getPath()); - assertThat(syncConfigurationCaptor.getValue().getSyncMode()).isEqualTo(SyncMode.FAST); - assertThat(syncConfigurationCaptor.getValue().getFastSyncMinimumPeerCount()).isEqualTo(13); + verify(mockControllerBuilder).dataDirectory(isNotNull()); + verify(mockControllerBuilder).nodeKey(isNotNull()); + verify(mockControllerBuilder).build(); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void nodePermissionsSmartContractWithoutOptionMustError() { - parseCommand("--permissions-nodes-contract-address"); - - Mockito.verifyNoInteractions(mockRunnerBuilder); + public void dataDirOptionMustBeUsed(final @TempDir Path path) { - assertThat(commandErrorOutput.toString(UTF_8)) - .startsWith("Missing required parameter for option '--permissions-nodes-contract-address'"); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - } + parseCommand("--data-path", path.toString()); - @Test - public void nodePermissionsEnabledWithoutContractAddressMustError() { - parseCommand("--permissions-nodes-contract-enabled"); + verify(mockControllerBuilder).dataDirectory(pathArgumentCaptor.capture()); + verify(mockControllerBuilder).nodeKey(isNotNull()); + verify(mockControllerBuilder).build(); - Mockito.verifyNoInteractions(mockRunnerBuilder); + assertThat(pathArgumentCaptor.getValue()).isEqualByComparingTo(path.toAbsolutePath()); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("No node permissioning contract address specified"); assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void nodePermissionsEnabledWithInvalidContractAddressMustError() { - parseCommand( - "--permissions-nodes-contract-enabled", - "--permissions-nodes-contract-address", - "invalid-smart-contract-address"); - - Mockito.verifyNoInteractions(mockRunnerBuilder); + public void genesisPathOptionMustBeUsed() throws Exception { + final Path genesisFile = createFakeGenesisFile(GENESIS_VALID_JSON); + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); - assertThat(commandErrorOutput.toString(UTF_8)).contains("Invalid value"); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - } + parseCommand("--genesis-file", genesisFile.toString()); - @Test - public void nodePermissionsEnabledWithTooShortContractAddressMustError() { - parseCommand( - "--permissions-nodes-contract-enabled", "--permissions-nodes-contract-address", "0x1234"); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); - Mockito.verifyNoInteractions(mockRunnerBuilder); + assertThat(networkArg.getValue().genesisConfigFile()) + .isEqualTo(GenesisConfigFile.fromConfig(encodeJsonGenesis(GENESIS_VALID_JSON))); - assertThat(commandErrorOutput.toString(UTF_8)).contains("Invalid value"); assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void nodePermissionsSmartContractMustUseOption() { - - final String smartContractAddress = "0x0000000000000000000000000000000000001234"; + public void testGenesisPathEthOptions() throws Exception { + final Path genesisFile = createFakeGenesisFile(GENESIS_VALID_JSON); - parseCommand( - "--permissions-nodes-contract-enabled", - "--permissions-nodes-contract-address", - smartContractAddress); - final SmartContractPermissioningConfiguration smartContractPermissioningConfiguration = - new SmartContractPermissioningConfiguration(); - smartContractPermissioningConfiguration.setNodeSmartContractAddress( - Address.fromHexString(smartContractAddress)); - smartContractPermissioningConfiguration.setSmartContractNodeAllowlistEnabled(true); + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); - verify(mockRunnerBuilder) - .permissioningConfiguration(permissioningConfigurationArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); + parseCommand("--genesis-file", genesisFile.toString()); - final PermissioningConfiguration config = - permissioningConfigurationArgumentCaptor.getValue().get(); - assertThat(config.getSmartContractConfig().get()) - .usingRecursiveComparison() - .isEqualTo(smartContractPermissioningConfiguration); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); + final EthNetworkConfig config = networkArg.getValue(); + assertThat(config.bootNodes()).isEmpty(); + assertThat(config.dnsDiscoveryUrl()).isNull(); + assertThat(config.networkId()).isEqualTo(BigInteger.valueOf(3141592)); } @Test - public void nodePermissionsContractVersionDefaultValue() { - final SmartContractPermissioningConfiguration expectedConfig = - new SmartContractPermissioningConfiguration(); - expectedConfig.setNodeSmartContractAddress( - Address.fromHexString("0x0000000000000000000000000000000000001234")); - expectedConfig.setSmartContractNodeAllowlistEnabled(true); - expectedConfig.setNodeSmartContractInterfaceVersion(1); + public void testGenesisPathMainnetEthConfig() { + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); - parseCommand( - "--permissions-nodes-contract-enabled", - "--permissions-nodes-contract-address", - "0x0000000000000000000000000000000000001234"); + parseCommand("--network", "mainnet"); - verify(mockRunnerBuilder) - .permissioningConfiguration(permissioningConfigurationArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); - final PermissioningConfiguration config = - permissioningConfigurationArgumentCaptor.getValue().get(); - assertThat(config.getSmartContractConfig().get()) - .usingRecursiveComparison() - .isEqualTo(expectedConfig); + final EthNetworkConfig config = networkArg.getValue(); + assertThat(config.bootNodes()).isEqualTo(MAINNET_BOOTSTRAP_NODES); + assertThat(config.dnsDiscoveryUrl()).isEqualTo(MAINNET_DISCOVERY_URL); + assertThat(config.networkId()).isEqualTo(BigInteger.valueOf(1)); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); + verify(mockLogger, never()).warn(contains("Mainnet is deprecated and will be shutdown")); } @Test - public void nodePermissionsContractVersionSetsValue() { - final SmartContractPermissioningConfiguration expectedConfig = - new SmartContractPermissioningConfiguration(); - expectedConfig.setNodeSmartContractAddress( - Address.fromHexString("0x0000000000000000000000000000000000001234")); - expectedConfig.setSmartContractNodeAllowlistEnabled(true); - expectedConfig.setNodeSmartContractInterfaceVersion(2); - - parseCommand( - "--permissions-nodes-contract-enabled", - "--permissions-nodes-contract-address", - "0x0000000000000000000000000000000000001234", - "--permissions-nodes-contract-version", - "2"); + public void testGenesisPathSepoliaEthConfig() { + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); - verify(mockRunnerBuilder) - .permissioningConfiguration(permissioningConfigurationArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); + parseCommand("--network", "sepolia"); - final PermissioningConfiguration config = - permissioningConfigurationArgumentCaptor.getValue().get(); - assertThat(config.getSmartContractConfig().get()) - .usingRecursiveComparison() - .isEqualTo(expectedConfig); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); + final EthNetworkConfig config = networkArg.getValue(); + assertThat(config.bootNodes()).isEqualTo(SEPOLIA_BOOTSTRAP_NODES); + assertThat(config.dnsDiscoveryUrl()).isEqualTo(SEPOLIA_DISCOVERY_URL); + assertThat(config.networkId()).isEqualTo(BigInteger.valueOf(11155111)); } @Test - public void accountPermissionsSmartContractWithoutOptionMustError() { - parseCommand("--permissions-accounts-contract-address"); + public void testGenesisPathFutureEipsEthConfig() { + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); - Mockito.verifyNoInteractions(mockRunnerBuilder); + parseCommand("--network", "future_eips"); - assertThat(commandErrorOutput.toString(UTF_8)) - .startsWith( - "Missing required parameter for option '--permissions-accounts-contract-address'"); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); + + final EthNetworkConfig config = networkArg.getValue(); + assertThat(config.bootNodes()).isEmpty(); + assertThat(config.dnsDiscoveryUrl()).isNull(); + assertThat(config.networkId()).isEqualTo(BigInteger.valueOf(2022)); } @Test - public void accountPermissionsEnabledWithoutContractAddressMustError() { - parseCommand("--permissions-accounts-contract-enabled"); + public void testGenesisPathExperimentalEipsEthConfig() { + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); - Mockito.verifyNoInteractions(mockRunnerBuilder); + parseCommand("--network", "experimental_eips"); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("No account permissioning contract address specified"); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); + + final EthNetworkConfig config = networkArg.getValue(); + assertThat(config.bootNodes()).isEmpty(); + assertThat(config.dnsDiscoveryUrl()).isNull(); + assertThat(config.networkId()).isEqualTo(BigInteger.valueOf(2023)); } @Test - public void accountPermissionsEnabledWithInvalidContractAddressMustError() { - parseCommand( - "--permissions-accounts-contract-enabled", - "--permissions-accounts-contract-address", - "invalid-smart-contract-address"); + public void genesisAndNetworkMustNotBeUsedTogether() throws Exception { + final Path genesisFile = createFakeGenesisFile(GENESIS_VALID_JSON); + + parseCommand("--genesis-file", genesisFile.toString(), "--network", "mainnet"); Mockito.verifyNoInteractions(mockRunnerBuilder); - assertThat(commandErrorOutput.toString(UTF_8)).contains("Invalid value"); assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .startsWith("--network option and --genesis-file option can't be used at the same time."); } @Test - public void accountPermissionsEnabledWithTooShortContractAddressMustError() { - parseCommand( - "--permissions-accounts-contract-enabled", - "--permissions-accounts-contract-address", - "0x1234"); + public void nonExistentGenesisGivesError() { + final String nonExistentGenesis = "non-existent-genesis.json"; + parseCommand("--genesis-file", nonExistentGenesis); Mockito.verifyNoInteractions(mockRunnerBuilder); - assertThat(commandErrorOutput.toString(UTF_8)).contains("Invalid value"); assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).startsWith("Unable to load genesis file"); + assertThat(commandErrorOutput.toString(UTF_8)).contains(nonExistentGenesis); } @Test - public void accountPermissionsSmartContractMustUseOption() { - final String smartContractAddress = "0x0000000000000000000000000000000000001234"; + public void testDnsDiscoveryUrlEthConfig() { + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); parseCommand( - "--permissions-accounts-contract-enabled", - "--permissions-accounts-contract-address", - smartContractAddress); - final SmartContractPermissioningConfiguration smartContractPermissioningConfiguration = - new SmartContractPermissioningConfiguration(); - smartContractPermissioningConfiguration.setAccountSmartContractAddress( - Address.fromHexString(smartContractAddress)); - smartContractPermissioningConfiguration.setSmartContractAccountAllowlistEnabled(true); - - verify(mockRunnerBuilder) - .permissioningConfiguration(permissioningConfigurationArgumentCaptor.capture()); - final PermissioningConfiguration permissioningConfiguration = - permissioningConfigurationArgumentCaptor.getValue().get(); - assertThat(permissioningConfiguration.getSmartContractConfig()).isPresent(); + "--discovery-dns-url", + "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@nodes.example.org"); - final SmartContractPermissioningConfiguration effectiveSmartContractConfig = - permissioningConfiguration.getSmartContractConfig().get(); - assertThat(effectiveSmartContractConfig.isSmartContractAccountAllowlistEnabled()).isTrue(); - assertThat(effectiveSmartContractConfig.getAccountSmartContractAddress()) - .isEqualTo(Address.fromHexString(smartContractAddress)); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); + final EthNetworkConfig config = networkArg.getValue(); + assertThat(config.dnsDiscoveryUrl()) + .isEqualTo( + "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@nodes.example.org"); } @Test - public void nodePermissioningTomlPathWithoutOptionMustDisplayUsage() { - parseCommand("--permissions-nodes-config-file"); + public void testDnsDiscoveryUrlOverridesNetworkEthConfig() { + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); - Mockito.verifyNoInteractions(mockRunnerBuilder); + parseCommand( + "--network", + "dev", + "--discovery-dns-url", + "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@nodes.example.org"); - assertThat(commandErrorOutput.toString(UTF_8)) - .startsWith("Missing required parameter for option '--permissions-nodes-config-file'"); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); + + final EthNetworkConfig config = networkArg.getValue(); + assertThat(config.dnsDiscoveryUrl()) + .isEqualTo( + "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@nodes.example.org"); } @Test - public void accountPermissioningTomlPathWithoutOptionMustDisplayUsage() { - parseCommand("--permissions-accounts-config-file"); + public void defaultNetworkIdAndBootnodesForCustomNetworkOptions() throws Exception { + final Path genesisFile = createFakeGenesisFile(GENESIS_VALID_JSON); - Mockito.verifyNoInteractions(mockRunnerBuilder); + parseCommand("--genesis-file", genesisFile.toString()); - assertThat(commandErrorOutput.toString(UTF_8)) - .startsWith("Missing required parameter for option '--permissions-accounts-config-file'"); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - } + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); - @Test - public void nodePermissioningEnabledWithNonexistentConfigFileMustError() { - parseCommand( - "--permissions-nodes-config-file-enabled", - "--permissions-nodes-config-file", - "file-does-not-exist"); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); - Mockito.verifyNoInteractions(mockRunnerBuilder); + assertThat(networkArg.getValue().genesisConfigFile()) + .isEqualTo(GenesisConfigFile.fromConfig(encodeJsonGenesis(GENESIS_VALID_JSON))); + assertThat(networkArg.getValue().bootNodes()).isEmpty(); + assertThat(networkArg.getValue().networkId()).isEqualTo(GENESIS_CONFIG_TEST_CHAINID); - assertThat(commandErrorOutput.toString(UTF_8)).contains("Configuration file does not exist"); assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void accountPermissioningEnabledWithNonexistentConfigFileMustError() { - parseCommand( - "--permissions-accounts-config-file-enabled", - "--permissions-accounts-config-file", - "file-does-not-exist"); - - Mockito.verifyNoInteractions(mockRunnerBuilder); + public void defaultNetworkIdForInvalidGenesisMustBeMainnetNetworkId() throws Exception { + final Path genesisFile = createFakeGenesisFile(GENESIS_INVALID_DATA); - assertThat(commandErrorOutput.toString(UTF_8)).contains("Configuration file does not exist"); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - } + parseCommand("--genesis-file", genesisFile.toString()); - @Test - public void nodePermissioningTomlFileWithNoPermissionsEnabledMustNotError() throws IOException { + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); - final URL configFile = this.getClass().getResource(PERMISSIONING_CONFIG_TOML); - final Path permToml = createTempFile("toml", Resources.toByteArray(configFile)); - parseCommand("--permissions-nodes-config-file", permToml.toString()); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); - verify(mockRunnerBuilder).build(); + assertThat(networkArg.getValue().genesisConfigFile()) + .isEqualTo(GenesisConfigFile.fromConfig(encodeJsonGenesis(GENESIS_INVALID_DATA))); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void accountPermissioningTomlFileWithNoPermissionsEnabledMustNotError() - throws IOException { - - final URL configFile = this.getClass().getResource(PERMISSIONING_CONFIG_TOML); - final Path permToml = createTempFile("toml", Resources.toByteArray(configFile)); - parseCommand("--permissions-accounts-config-file", permToml.toString()); - - verify(mockRunnerBuilder).build(); - - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void defaultPermissionsTomlFileWithNoPermissionsEnabledMustNotError() { - parseCommand("--p2p-enabled", "false"); - - verify(mockRunnerBuilder).build(); - - assertThat(commandErrorOutput.toString(UTF_8)).doesNotContain("no permissions enabled"); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void nodePermissioningTomlPathMustUseOption() throws IOException { - final List allowedNodes = - Lists.newArrayList( - EnodeURLImpl.fromString( - "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.9:4567"), - EnodeURLImpl.fromString( - "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.169.0.9:4568")); - - final URL configFile = this.getClass().getResource(PERMISSIONING_CONFIG_TOML); - final Path permToml = createTempFile("toml", Resources.toByteArray(configFile)); - - final String allowedNodesString = - allowedNodes.stream().map(Object::toString).collect(Collectors.joining(",")); - parseCommand( - "--permissions-nodes-config-file-enabled", - "--permissions-nodes-config-file", - permToml.toString(), - "--bootnodes", - allowedNodesString); - final LocalPermissioningConfiguration localPermissioningConfiguration = - LocalPermissioningConfiguration.createDefault(); - localPermissioningConfiguration.setNodePermissioningConfigFilePath(permToml.toString()); - localPermissioningConfiguration.setNodeAllowlist(allowedNodes); - - verify(mockRunnerBuilder) - .permissioningConfiguration(permissioningConfigurationArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - final PermissioningConfiguration config = - permissioningConfigurationArgumentCaptor.getValue().get(); - assertThat(config.getLocalConfig().get()) - .usingRecursiveComparison() - .isEqualTo(localPermissioningConfiguration); - - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void accountPermissioningTomlPathMustUseOption() throws IOException { - - final URL configFile = this.getClass().getResource(PERMISSIONING_CONFIG_TOML); - final Path permToml = createTempFile("toml", Resources.toByteArray(configFile)); - - parseCommand( - "--permissions-accounts-config-file-enabled", - "--permissions-accounts-config-file", - permToml.toString()); - final LocalPermissioningConfiguration localPermissioningConfiguration = - LocalPermissioningConfiguration.createDefault(); - localPermissioningConfiguration.setAccountPermissioningConfigFilePath(permToml.toString()); - localPermissioningConfiguration.setAccountAllowlist( - Collections.singletonList("0x0000000000000000000000000000000000000009")); - - verify(mockRunnerBuilder) - .permissioningConfiguration(permissioningConfigurationArgumentCaptor.capture()); - final PermissioningConfiguration permissioningConfiguration = - permissioningConfigurationArgumentCaptor.getValue().get(); - assertThat(permissioningConfiguration.getLocalConfig()).isPresent(); - - final LocalPermissioningConfiguration effectiveLocalPermissioningConfig = - permissioningConfiguration.getLocalConfig().get(); - assertThat(effectiveLocalPermissioningConfig.isAccountAllowlistEnabled()).isTrue(); - assertThat(effectiveLocalPermissioningConfig.getAccountPermissioningConfigFilePath()) - .isEqualTo(permToml.toString()); - - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void tomlThatConfiguresEverythingExceptPermissioningToml() throws IOException { - // Load a TOML that configures literally everything (except permissioning TOML config) - final URL configFile = this.getClass().getResource("/everything_config.toml"); - final Path toml = createTempFile("toml", Resources.toByteArray(configFile)); - - // Parse it. - final CommandLine.Model.CommandSpec spec = parseCommand("--config-file", toml.toString()).spec; - final TomlParseResult tomlResult = Toml.parse(toml); - - // Verify we configured everything - final HashSet options = new HashSet<>(spec.options()); - - // Except for meta-options - options.remove(spec.optionsMap().get("--config-file")); - options.remove(spec.optionsMap().get("--help")); - options.remove(spec.optionsMap().get("--version")); - - for (final String tomlKey : tomlResult.keySet()) { - final CommandLine.Model.OptionSpec optionSpec = spec.optionsMap().get("--" + tomlKey); - assertThat(optionSpec) - .describedAs("Option '%s' should be a configurable option.", tomlKey) - .isNotNull(); - // Verify TOML stores it by the appropriate type - if (optionSpec.type().equals(Boolean.class)) { - tomlResult.getBoolean(tomlKey); - } else if (optionSpec.isMultiValue() || optionSpec.arity().max > 1) { - tomlResult.getArray(tomlKey); - } else if (optionSpec.type().equals(Double.class)) { - tomlResult.getDouble(tomlKey); - } else if (optionSpec.type().equals(Float.class)) { - tomlResult.getDouble(tomlKey); - } else if (Number.class.isAssignableFrom(optionSpec.type())) { - tomlResult.getLong(tomlKey); - } else if (Wei.class.isAssignableFrom(optionSpec.type())) { - tomlResult.getLong(tomlKey); - } else if (Fraction.class.isAssignableFrom(optionSpec.type())) { - tomlResult.getDouble(tomlKey); - } else if (Percentage.class.isAssignableFrom(optionSpec.type())) { - tomlResult.getLong(tomlKey); - } else { - tomlResult.getString(tomlKey); - } - options.remove(optionSpec); - } - assertThat( - options.stream() - .filter(optionSpec -> !optionSpec.hidden()) - .map(CommandLine.Model.OptionSpec::longestName)) - .isEmpty(); - } - - @Test - public void noOverrideDefaultValuesIfKeyIsNotPresentInConfigFile() { - final String configFile = this.getClass().getResource("/partial_config.toml").getFile(); - - parseCommand("--config-file", configFile); - final JsonRpcConfiguration jsonRpcConfiguration = JsonRpcConfiguration.createDefault(); - - final GraphQLConfiguration graphQLConfiguration = GraphQLConfiguration.createDefault(); - - final WebSocketConfiguration webSocketConfiguration = WebSocketConfiguration.createDefault(); - - final MetricsConfiguration metricsConfiguration = MetricsConfiguration.builder().build(); - - verify(mockRunnerBuilder).discovery(eq(true)); - verify(mockRunnerBuilder) - .ethNetworkConfig( - new EthNetworkConfig( - EthNetworkConfig.jsonConfig(MAINNET), - MAINNET.getNetworkId(), - MAINNET_BOOTSTRAP_NODES, - MAINNET_DISCOVERY_URL)); - verify(mockRunnerBuilder).p2pAdvertisedHost(eq("127.0.0.1")); - verify(mockRunnerBuilder).p2pListenPort(eq(30303)); - verify(mockRunnerBuilder).jsonRpcConfiguration(eq(jsonRpcConfiguration)); - verify(mockRunnerBuilder).graphQLConfiguration(eq(graphQLConfiguration)); - verify(mockRunnerBuilder).webSocketConfiguration(eq(webSocketConfiguration)); - verify(mockRunnerBuilder).metricsConfiguration(eq(metricsConfiguration)); - verify(mockRunnerBuilder).build(); - verify(mockControllerBuilder).build(); - verify(mockControllerBuilder).synchronizerConfiguration(syncConfigurationCaptor.capture()); - - final SynchronizerConfiguration syncConfig = syncConfigurationCaptor.getValue(); - assertThat(syncConfig.getSyncMode()).isEqualTo(SyncMode.FAST); - assertThat(syncConfig.getFastSyncMinimumPeerCount()).isEqualTo(5); - - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void envVariableOverridesValueFromConfigFile() { - final String configFile = this.getClass().getResource("/partial_config.toml").getFile(); - final String expectedCoinbase = "0x0000000000000000000000000000000000000004"; - setEnvironmentVariable("BESU_MINER_COINBASE", expectedCoinbase); - parseCommand("--config-file", configFile); - - verify(mockControllerBuilder) - .miningParameters( - ImmutableMiningParameters.builder() - .mutableInitValues( - MutableInitValues.builder() - .coinbase(Address.fromHexString(expectedCoinbase)) - .build()) - .build()); - } - - @Test - public void cliOptionOverridesEnvVariableAndConfig() { - final String configFile = this.getClass().getResource("/partial_config.toml").getFile(); - final String expectedCoinbase = "0x0000000000000000000000000000000000000006"; - setEnvironmentVariable("BESU_MINER_COINBASE", "0x0000000000000000000000000000000000000004"); - parseCommand("--config-file", configFile, "--miner-coinbase", expectedCoinbase); - - verify(mockControllerBuilder) - .miningParameters( - ImmutableMiningParameters.builder() - .mutableInitValues( - MutableInitValues.builder() - .coinbase(Address.fromHexString(expectedCoinbase)) - .build()) - .build()); - } - - @Test - public void nodekeyOptionMustBeUsed() throws Exception { - final File file = new File("./specific/enclavePrivateKey"); - - parseCommand("--node-private-key-file", file.getPath()); - - verify(mockControllerBuilder).dataDirectory(isNotNull()); - verify(mockControllerBuilder).nodeKey(isNotNull()); - verify(mockControllerBuilder).build(); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void dataDirOptionMustBeUsed() { - final Path path = Paths.get("."); - - parseCommand("--data-path", path.toString()); - - verify(mockControllerBuilder).dataDirectory(pathArgumentCaptor.capture()); - verify(mockControllerBuilder).nodeKey(isNotNull()); - verify(mockControllerBuilder).build(); - - assertThat(pathArgumentCaptor.getValue()).isEqualByComparingTo(path.toAbsolutePath()); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void genesisPathOptionMustBeUsed() throws Exception { - final Path genesisFile = createFakeGenesisFile(GENESIS_VALID_JSON); - final ArgumentCaptor networkArg = - ArgumentCaptor.forClass(EthNetworkConfig.class); - - parseCommand("--genesis-file", genesisFile.toString()); - - verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any(), any()); - verify(mockControllerBuilder).build(); - - assertThat(networkArg.getValue().getGenesisConfig()) - .isEqualTo(encodeJsonGenesis(GENESIS_VALID_JSON)); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void testGenesisPathEthOptions() throws Exception { - final Path genesisFile = createFakeGenesisFile(GENESIS_VALID_JSON); - - final ArgumentCaptor networkArg = - ArgumentCaptor.forClass(EthNetworkConfig.class); - - parseCommand("--genesis-file", genesisFile.toString()); - - verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any(), any()); - verify(mockControllerBuilder).build(); - - final EthNetworkConfig config = networkArg.getValue(); - assertThat(config.getBootNodes()).isEmpty(); - assertThat(config.getDnsDiscoveryUrl()).isNull(); - assertThat(config.getNetworkId()).isEqualTo(BigInteger.valueOf(3141592)); - } - - @Test - public void testGenesisPathMainnetEthConfig() throws Exception { - final ArgumentCaptor networkArg = - ArgumentCaptor.forClass(EthNetworkConfig.class); - - parseCommand("--network", "mainnet"); - - verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any(), any()); - verify(mockControllerBuilder).build(); - - final EthNetworkConfig config = networkArg.getValue(); - assertThat(config.getBootNodes()).isEqualTo(MAINNET_BOOTSTRAP_NODES); - assertThat(config.getDnsDiscoveryUrl()).isEqualTo(MAINNET_DISCOVERY_URL); - assertThat(config.getNetworkId()).isEqualTo(BigInteger.valueOf(1)); - - verify(mockLogger, never()).warn(contains("Mainnet is deprecated and will be shutdown")); - } - - @Test - public void testGenesisPathGoerliEthConfig() { - final ArgumentCaptor networkArg = - ArgumentCaptor.forClass(EthNetworkConfig.class); - - parseCommand("--network", "goerli"); - - verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any(), any()); - verify(mockControllerBuilder).build(); - - final EthNetworkConfig config = networkArg.getValue(); - assertThat(config.getBootNodes()).isEqualTo(GOERLI_BOOTSTRAP_NODES); - assertThat(config.getDnsDiscoveryUrl()).isEqualTo(GOERLI_DISCOVERY_URL); - assertThat(config.getNetworkId()).isEqualTo(BigInteger.valueOf(5)); - } - - @Test - public void testGenesisPathFutureEipsEthConfig() { - final ArgumentCaptor networkArg = - ArgumentCaptor.forClass(EthNetworkConfig.class); - - parseCommand("--network", "future_eips"); - - verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any(), any()); - verify(mockControllerBuilder).build(); - - final EthNetworkConfig config = networkArg.getValue(); - assertThat(config.getBootNodes()).isEmpty(); - assertThat(config.getDnsDiscoveryUrl()).isNull(); - assertThat(config.getNetworkId()).isEqualTo(BigInteger.valueOf(2022)); - } - - @Test - public void testGenesisPathExperimentalEipsEthConfig() { - final ArgumentCaptor networkArg = - ArgumentCaptor.forClass(EthNetworkConfig.class); - - parseCommand("--network", "experimental_eips"); - - verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any(), any()); - verify(mockControllerBuilder).build(); - - final EthNetworkConfig config = networkArg.getValue(); - assertThat(config.getBootNodes()).isEmpty(); - assertThat(config.getDnsDiscoveryUrl()).isNull(); - assertThat(config.getNetworkId()).isEqualTo(BigInteger.valueOf(2023)); - } - - @Test - public void genesisAndNetworkMustNotBeUsedTogether() throws Exception { - final Path genesisFile = createFakeGenesisFile(GENESIS_VALID_JSON); - - parseCommand("--genesis-file", genesisFile.toString(), "--network", "mainnet"); - - Mockito.verifyNoInteractions(mockRunnerBuilder); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .startsWith("--network option and --genesis-file option can't be used at the same time."); - } - - @Test - public void nonExistentGenesisGivesError() throws Exception { - final String nonExistentGenesis = "non-existent-genesis.json"; - parseCommand("--genesis-file", nonExistentGenesis); - - Mockito.verifyNoInteractions(mockRunnerBuilder); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).startsWith("Unable to load genesis file"); - assertThat(commandErrorOutput.toString(UTF_8)).contains(nonExistentGenesis); - } - - @Test - public void testDnsDiscoveryUrlEthConfig() throws Exception { - final ArgumentCaptor networkArg = - ArgumentCaptor.forClass(EthNetworkConfig.class); - - parseCommand( - "--discovery-dns-url", - "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@nodes.example.org"); - - verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any(), any()); - verify(mockControllerBuilder).build(); - - final EthNetworkConfig config = networkArg.getValue(); - assertThat(config.getDnsDiscoveryUrl()) - .isEqualTo( - "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@nodes.example.org"); - } - - @Test - public void testDnsDiscoveryUrlOverridesNetworkEthConfig() throws Exception { - final ArgumentCaptor networkArg = - ArgumentCaptor.forClass(EthNetworkConfig.class); - - parseCommand( - "--network", - "dev", - "--discovery-dns-url", - "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@nodes.example.org"); - - verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any(), any()); - verify(mockControllerBuilder).build(); - - final EthNetworkConfig config = networkArg.getValue(); - assertThat(config.getDnsDiscoveryUrl()) - .isEqualTo( - "enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@nodes.example.org"); - } - - @Test - public void defaultNetworkIdAndBootnodesForCustomNetworkOptions() throws Exception { - final Path genesisFile = createFakeGenesisFile(GENESIS_VALID_JSON); - - parseCommand("--genesis-file", genesisFile.toString()); - - final ArgumentCaptor networkArg = - ArgumentCaptor.forClass(EthNetworkConfig.class); - - verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any(), any()); - verify(mockControllerBuilder).build(); - - assertThat(networkArg.getValue().getGenesisConfig()) - .isEqualTo(encodeJsonGenesis(GENESIS_VALID_JSON)); - assertThat(networkArg.getValue().getBootNodes()).isEmpty(); - assertThat(networkArg.getValue().getNetworkId()).isEqualTo(GENESIS_CONFIG_TEST_CHAINID); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void defaultNetworkIdForInvalidGenesisMustBeMainnetNetworkId() throws Exception { - final Path genesisFile = createFakeGenesisFile(GENESIS_INVALID_DATA); - - parseCommand("--genesis-file", genesisFile.toString()); - - final ArgumentCaptor networkArg = - ArgumentCaptor.forClass(EthNetworkConfig.class); - - verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any(), any()); - verify(mockControllerBuilder).build(); - - assertThat(networkArg.getValue().getGenesisConfig()) - .isEqualTo(encodeJsonGenesis(GENESIS_INVALID_DATA)); - - // assertThat(networkArg.getValue().getNetworkId()) - // .isEqualTo(EthNetworkConfig.getNetworkConfig(MAINNET).getNetworkId()); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void predefinedNetworkIdsMustBeEqualToChainIds() { - // check the network id against the one in mainnet genesis config - // it implies that EthNetworkConfig.mainnet().getNetworkId() returns a value equals to the chain - // id - // in this network genesis file. - - final GenesisConfigFile genesisConfigFile = - GenesisConfigFile.fromConfig(EthNetworkConfig.getNetworkConfig(MAINNET).getGenesisConfig()); - assertThat(genesisConfigFile.getConfigOptions().getChainId().isPresent()).isTrue(); - assertThat(genesisConfigFile.getConfigOptions().getChainId().get()) - .isEqualTo(EthNetworkConfig.getNetworkConfig(MAINNET).getNetworkId()); - } - - @Test - public void identityValueTrueMustBeUsed() { - parseCommand("--identity", "test"); - - verify(mockRunnerBuilder).identityString(eq(Optional.of("test"))); - verify(mockRunnerBuilder).build(); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void p2pEnabledOptionValueTrueMustBeUsed() { - parseCommand("--p2p-enabled", "true"); - - verify(mockRunnerBuilder).p2pEnabled(eq(true)); - verify(mockRunnerBuilder).build(); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void p2pEnabledOptionValueFalseMustBeUsed() { - parseCommand("--p2p-enabled", "false"); - - verify(mockRunnerBuilder).p2pEnabled(eq(false)); - verify(mockRunnerBuilder).build(); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void p2pOptionsRequiresServiceToBeEnabled() { - final String[] nodes = { - "6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0" - }; - - parseCommand( - "--p2p-enabled", - "false", - "--bootnodes", - String.join(",", VALID_ENODE_STRINGS), - "--discovery-enabled", - "false", - "--max-peers", - "42", - "--remote-connections-max-percentage", - "50", - "--banned-node-id", - String.join(",", nodes), - "--banned-node-ids", - String.join(",", nodes)); - - verifyOptionsConstraintLoggerCall( - "--p2p-enabled", - "--discovery-enabled", - "--bootnodes", - "--max-peers", - "--banned-node-ids", - "--remote-connections-max-percentage"); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void p2pOptionsRequiresServiceToBeEnabledToml() throws IOException { - final String[] nodes = { - "6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0" - }; - - final Path toml = - createTempFile( - "toml", - "p2p-enabled=false\n" - + "bootnodes=[\"" - + String.join("\",\"", VALID_ENODE_STRINGS) - + "\"]\n" - + "discovery-enabled=false\n" - + "max-peers=42\n" - + "remote-connections-max-percentage=50\n" - + "banned-node-id=[\"" - + String.join(",", nodes) - + "\"]\n" - + "banned-node-ids=[\"" - + String.join(",", nodes) - + "\"]\n"); - - parseCommand("--config-file", toml.toString()); - - verifyOptionsConstraintLoggerCall( - "--p2p-enabled", - "--discovery-enabled", - "--bootnodes", - "--max-peers", - "--banned-node-ids", - "--remote-connections-max-percentage"); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void discoveryOptionValueTrueMustBeUsed() { - parseCommand("--discovery-enabled", "true"); - - verify(mockRunnerBuilder).discovery(eq(true)); - verify(mockRunnerBuilder).build(); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void discoveryOptionValueFalseMustBeUsed() { - parseCommand("--discovery-enabled", "false"); - - verify(mockRunnerBuilder).discovery(eq(false)); - verify(mockRunnerBuilder).build(); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void loadDiscoveryOptionsFromGenesisFile() throws IOException { - final Path genesisFile = createFakeGenesisFile(VALID_GENESIS_WITH_DISCOVERY_OPTIONS); - parseCommand("--genesis-file", genesisFile.toString()); - - final ArgumentCaptor networkArg = - ArgumentCaptor.forClass(EthNetworkConfig.class); - - verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any(), any()); - verify(mockControllerBuilder).build(); - - final EthNetworkConfig config = networkArg.getValue(); - assertThat(config.getDnsDiscoveryUrl()).isEqualTo(DNS_DISCOVERY_URL); - - assertThat(config.getBootNodes()) - .extracting(bootnode -> bootnode.toURI().toString()) - .containsExactly(VALID_ENODE_STRINGS); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void discoveryDnsUrlCliArgTakesPrecedenceOverGenesisFile() throws IOException { - final Path genesisFile = createFakeGenesisFile(VALID_GENESIS_WITH_DISCOVERY_OPTIONS); - final String discoveryDnsUrlCliArg = - "enrtree://XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX@nodes.example.org"; - parseCommand( - "--genesis-file", genesisFile.toString(), "--discovery-dns-url", discoveryDnsUrlCliArg); - - final ArgumentCaptor networkArg = - ArgumentCaptor.forClass(EthNetworkConfig.class); - verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any(), any()); - verify(mockControllerBuilder).build(); - - final EthNetworkConfig config = networkArg.getValue(); - assertThat(config.getDnsDiscoveryUrl()).isEqualTo(discoveryDnsUrlCliArg); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void bootnodesUrlCliArgTakesPrecedenceOverGenesisFile() throws IOException { - final Path genesisFile = createFakeGenesisFile(VALID_GENESIS_WITH_DISCOVERY_OPTIONS); - final URI bootnode = - URI.create( - "enode://d2567893371ea5a6fa6371d483891ed0d129e79a8fc74d6df95a00a6545444cd4a6960bbffe0b4e2edcf35135271de57ee559c0909236bbc2074346ef2b5b47c@127.0.0.1:30304"); - - parseCommand("--genesis-file", genesisFile.toString(), "--bootnodes", bootnode.toString()); - - final ArgumentCaptor networkArg = - ArgumentCaptor.forClass(EthNetworkConfig.class); - verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any(), any()); - verify(mockControllerBuilder).build(); - - final EthNetworkConfig config = networkArg.getValue(); - assertThat(config.getBootNodes()).extracting(EnodeURL::toURI).containsExactly(bootnode); - - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void callingWithBootnodesOptionButNoValueMustPassEmptyBootnodeList() { - parseCommand("--bootnodes"); - - verify(mockRunnerBuilder).ethNetworkConfig(ethNetworkConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(ethNetworkConfigArgumentCaptor.getValue().getBootNodes()).isEmpty(); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void callingWithValidBootnodeMustSucceed() { - parseCommand( - "--bootnodes", - "enode://d2567893371ea5a6fa6371d483891ed0d129e79a8fc74d6df95a00a6545444cd4a6960bbffe0b4e2edcf35135271de57ee559c0909236bbc2074346ef2b5b47c@127.0.0.1:30304"); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void callingWithValidBootnodeButDiscoveryDisabledMustDisplayWarning() { - parseCommand( - "--bootnodes", - "enode://d2567893371ea5a6fa6371d483891ed0d129e79a8fc74d6df95a00a6545444cd4a6960bbffe0b4e2edcf35135271de57ee559c0909236bbc2074346ef2b5b47c@127.0.0.1:30304", - "--discovery-enabled", - "false"); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - verify(mockRunnerBuilder).build(); - verify(mockLogger, atLeast(1)).warn("Discovery disabled: bootnodes will be ignored."); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void callingWithInvalidBootnodeMustDisplayError() { - parseCommand("--bootnodes", "invalid_enode_url"); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - final String expectedErrorOutputStart = - "Invalid enode URL syntax 'invalid_enode_url'. Enode URL should have the following format " - + "'enode://@:[?discport=]'."; - assertThat(commandErrorOutput.toString(UTF_8)).startsWith(expectedErrorOutputStart); - } - - @Test - public void callingWithBootnodeThatHasDiscoveryDisabledMustDisplayError() { - final String validBootnode = - "enode://d2567893371ea5a6fa6371d483891ed0d129e79a8fc74d6df95a00a6545444cd4a6960bbffe0b4e2edcf35135271de57ee559c0909236bbc2074346ef2b5b47c@127.0.0.1:30304"; - final String invalidBootnode = - "enode://02567893371ea5a6fa6371d483891ed0d129e79a8fc74d6df95a00a6545444cd4a6960bbffe0b4e2edcf35135271de57ee559c0909236bbc2074346ef2b5b47c@127.0.0.1:30303?discport=0"; - final String bootnodesValue = validBootnode + "," + invalidBootnode; - parseCommand("--bootnodes", bootnodesValue); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - final String expectedErrorOutputStart = - "Bootnodes must have discovery enabled. Invalid bootnodes: " + invalidBootnode + "."; - assertThat(commandErrorOutput.toString(UTF_8)).startsWith(expectedErrorOutputStart); - } - - // This test ensures non regression on https://pegasys1.atlassian.net/browse/PAN-2387 - @Test - public void callingWithInvalidBootnodeAndEqualSignMustDisplayError() { - parseCommand("--bootnodes=invalid_enode_url"); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - final String expectedErrorOutputStart = - "Invalid enode URL syntax 'invalid_enode_url'. Enode URL should have the following format " - + "'enode://@:[?discport=]'."; - assertThat(commandErrorOutput.toString(UTF_8)).startsWith(expectedErrorOutputStart); - } - - @Test - public void bootnodesOptionMustBeUsed() { - parseCommand("--bootnodes", String.join(",", VALID_ENODE_STRINGS)); - - verify(mockRunnerBuilder).ethNetworkConfig(ethNetworkConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(ethNetworkConfigArgumentCaptor.getValue().getBootNodes()) - .isEqualTo( - Stream.of(VALID_ENODE_STRINGS) - .map(EnodeURLImpl::fromString) - .collect(Collectors.toList())); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void bannedNodeIdsOptionMustBeUsed() { - final Bytes[] nodes = { - Bytes.fromHexString( - "6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0"), - Bytes.fromHexString( - "7f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0"), - Bytes.fromHexString( - "0x8f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0") - }; - - final String nodeIdsArg = - Arrays.stream(nodes).map(Bytes::toShortHexString).collect(Collectors.joining(",")); - parseCommand("--banned-node-ids", nodeIdsArg); - - verify(mockRunnerBuilder).bannedNodeIds(bytesCollectionCollector.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(bytesCollectionCollector.getValue().toArray()).isEqualTo(nodes); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void callingWithBannedNodeidsOptionButNoValueMustDisplayError() { - parseCommand("--banned-node-ids"); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - final String expectedErrorOutputStart = - "Missing required parameter for option '--banned-node-ids' at index 0 ()"; - assertThat(commandErrorOutput.toString(UTF_8)).startsWith(expectedErrorOutputStart); - } - - @Test - public void callingWithBannedNodeidsOptionWithInvalidValuesMustDisplayError() { - parseCommand("--banned-node-ids", "0x10,20,30"); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - final String expectedErrorOutputStart = - "Invalid ids supplied to '--banned-node-ids'. Expected 64 bytes in 0x10"; - assertThat(commandErrorOutput.toString(UTF_8)).startsWith(expectedErrorOutputStart); - } - - @Test - public void p2pHostAndPortOptionsAreRespected() { - - final String host = "1.2.3.4"; - final int port = 1234; - parseCommand("--p2p-host", host, "--p2p-port", String.valueOf(port)); - - verify(mockRunnerBuilder).p2pAdvertisedHost(stringArgumentCaptor.capture()); - verify(mockRunnerBuilder).p2pListenPort(intArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(stringArgumentCaptor.getValue()).isEqualTo(host); - assertThat(intArgumentCaptor.getValue()).isEqualTo(port); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void p2pInterfaceOptionIsRespected() { - - final String ip = "1.2.3.4"; - parseCommand("--p2p-interface", ip); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - - verify(mockRunnerBuilder).p2pListenInterface(stringArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(stringArgumentCaptor.getValue()).isEqualTo(ip); - } - - @Test - public void p2pHostMayBeLocalhost() { - - final String host = "localhost"; - parseCommand("--p2p-host", host); - - verify(mockRunnerBuilder).p2pAdvertisedHost(stringArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(stringArgumentCaptor.getValue()).isEqualTo(host); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void p2pHostMayBeIPv6() { - - final String host = "2600:DB8::8545"; - parseCommand("--p2p-host", host); - - verify(mockRunnerBuilder).p2pAdvertisedHost(stringArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(stringArgumentCaptor.getValue()).isEqualTo(host); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void maxpeersOptionMustBeUsed() { - - final int maxPeers = 123; - parseCommand("--max-peers", String.valueOf(maxPeers)); - - verify(mockControllerBuilder).maxPeers(intArgumentCaptor.capture()); - verify(mockControllerBuilder).build(); - - assertThat(intArgumentCaptor.getValue()).isEqualTo(maxPeers); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void rpcMaxLogsRangeOptionMustBeUsed() { - - final long rpcMaxLogsRange = 150L; - parseCommand("--rpc-max-logs-range", Long.toString(rpcMaxLogsRange)); - - verify(mockRunnerBuilder).apiConfiguration(apiConfigurationCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(apiConfigurationCaptor.getValue()) - .isEqualTo(ImmutableApiConfiguration.builder().maxLogsRange((rpcMaxLogsRange)).build()); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void rpcGasCapOptionMustBeUsed() { - final long rpcGasCap = 150L; - parseCommand("--rpc-gas-cap", Long.toString(rpcGasCap)); - - verify(mockRunnerBuilder).apiConfiguration(apiConfigurationCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(apiConfigurationCaptor.getValue()) - .isEqualTo(ImmutableApiConfiguration.builder().gasCap((rpcGasCap)).build()); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void p2pPeerUpperBound_without_p2pPeerLowerBound_shouldSetLowerBoundEqualToUpperBound() { - - final int maxPeers = 23; - parseCommand("--p2p-peer-upper-bound", String.valueOf(maxPeers)); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - - verify(mockControllerBuilder).maxPeers(intArgumentCaptor.capture()); - assertThat(intArgumentCaptor.getValue()).isEqualTo(maxPeers); - - verify(mockControllerBuilder).lowerBoundPeers(intArgumentCaptor.capture()); - assertThat(intArgumentCaptor.getValue()).isEqualTo(maxPeers); - - verify(mockRunnerBuilder).build(); - } - - @Test - public void rpcHttpMaxBatchSizeOptionMustBeUsed() { - final int rpcHttpMaxBatchSize = 1; - parseCommand("--rpc-http-max-batch-size", Integer.toString(rpcHttpMaxBatchSize)); - - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(jsonRpcConfigArgumentCaptor.getValue().getMaxBatchSize()) - .isEqualTo(rpcHttpMaxBatchSize); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void rpcHttpMaxRequestContentLengthOptionMustBeUsed() { - final int rpcHttpMaxRequestContentLength = 1; - parseCommand( - "--rpc-http-max-request-content-length", Long.toString(rpcHttpMaxRequestContentLength)); - - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(jsonRpcConfigArgumentCaptor.getValue().getMaxRequestContentLength()) - .isEqualTo(rpcHttpMaxRequestContentLength); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void maxpeersSet_p2pPeerLowerBoundSet() { - - final int maxPeers = 123; - final int minPeers = 66; - parseCommand( - "--max-peers", - String.valueOf(maxPeers), - "--Xp2p-peer-lower-bound", - String.valueOf(minPeers)); - - verify(mockControllerBuilder).maxPeers(intArgumentCaptor.capture()); - assertThat(intArgumentCaptor.getValue()).isEqualTo(maxPeers); - - verify(mockControllerBuilder).lowerBoundPeers(intArgumentCaptor.capture()); - assertThat(intArgumentCaptor.getValue()).isEqualTo(minPeers); - - verify(mockRunnerBuilder).build(); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void remoteConnectionsPercentageOptionMustBeUsed() { - - final int remoteConnectionsPercentage = 12; - parseCommand( - "--remote-connections-limit-enabled=true", - "--remote-connections-max-percentage", - String.valueOf(remoteConnectionsPercentage)); - - verify(mockControllerBuilder).maxRemotelyInitiatedPeers(intArgumentCaptor.capture()); - verify(mockControllerBuilder).build(); - - assertThat(intArgumentCaptor.getValue()) - .isEqualTo( - (int) Math.floor(25 * Fraction.fromPercentage(remoteConnectionsPercentage).getValue())); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void remoteConnectionsPercentageWithInvalidFormatMustFail() { - - parseCommand( - "--remote-connections-limit-enabled", "--remote-connections-max-percentage", "invalid"); - Mockito.verifyNoInteractions(mockRunnerBuilder); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains( - "Invalid value for option '--remote-connections-max-percentage'", - "should be a number between 0 and 100 inclusive"); - } - - @Test - public void remoteConnectionsPercentageWithOutOfRangeMustFail() { - - parseCommand( - "--remote-connections-limit-enabled", "--remote-connections-max-percentage", "150"); - Mockito.verifyNoInteractions(mockRunnerBuilder); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains( - "Invalid value for option '--remote-connections-max-percentage'", - "should be a number between 0 and 100 inclusive"); - } - - @Test - public void syncMode_fast() { - parseCommand("--sync-mode", "FAST"); - verify(mockControllerBuilder).synchronizerConfiguration(syncConfigurationCaptor.capture()); - - final SynchronizerConfiguration syncConfig = syncConfigurationCaptor.getValue(); - assertThat(syncConfig.getSyncMode()).isEqualTo(SyncMode.FAST); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void syncMode_full() { - parseCommand("--sync-mode", "FULL"); - verify(mockControllerBuilder).synchronizerConfiguration(syncConfigurationCaptor.capture()); - - final SynchronizerConfiguration syncConfig = syncConfigurationCaptor.getValue(); - assertThat(syncConfig.getSyncMode()).isEqualTo(SyncMode.FULL); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void syncMode_invalid() { - parseCommand("--sync-mode", "bogus"); - Mockito.verifyNoInteractions(mockRunnerBuilder); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains( - "Invalid value for option '--sync-mode': expected one of [FULL, FAST, X_SNAP, X_CHECKPOINT] (case-insensitive) but was 'bogus'"); - } - - @Test - public void syncMode_full_by_default_for_dev() { - parseCommand("--network", "dev"); - verify(mockControllerBuilder).synchronizerConfiguration(syncConfigurationCaptor.capture()); - - final SynchronizerConfiguration syncConfig = syncConfigurationCaptor.getValue(); - assertThat(syncConfig.getSyncMode()).isEqualTo(SyncMode.FULL); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void helpShouldDisplayFastSyncOptions() { - parseCommand("--help"); - - Mockito.verifyNoInteractions(mockRunnerBuilder); - - assertThat(commandOutput.toString(UTF_8)).contains("--fast-sync-min-peers"); - // whitelist is now a hidden option - assertThat(commandOutput.toString(UTF_8)).doesNotContain("whitelist"); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void checkValidDefaultFastSyncMinPeers() { - parseCommand("--sync-mode", "FAST"); - verify(mockControllerBuilder).synchronizerConfiguration(syncConfigurationCaptor.capture()); - - final SynchronizerConfiguration syncConfig = syncConfigurationCaptor.getValue(); - assertThat(syncConfig.getSyncMode()).isEqualTo(SyncMode.FAST); - assertThat(syncConfig.getFastSyncMinimumPeerCount()).isEqualTo(5); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void parsesValidFastSyncMinPeersOption() { - parseCommand("--sync-mode", "FAST", "--fast-sync-min-peers", "11"); - verify(mockControllerBuilder).synchronizerConfiguration(syncConfigurationCaptor.capture()); - - final SynchronizerConfiguration syncConfig = syncConfigurationCaptor.getValue(); - assertThat(syncConfig.getSyncMode()).isEqualTo(SyncMode.FAST); - assertThat(syncConfig.getFastSyncMinimumPeerCount()).isEqualTo(11); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void parsesInvalidFastSyncMinPeersOptionWrongFormatShouldFail() { - - parseCommand("--sync-mode", "FAST", "--fast-sync-min-peers", "ten"); - Mockito.verifyNoInteractions(mockRunnerBuilder); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("Invalid value for option '--fast-sync-min-peers': 'ten' is not an int"); - } - - @Test - public void natMethodOptionIsParsedCorrectly() { - - parseCommand("--nat-method", "NONE"); - verify(mockRunnerBuilder).natMethod(eq(NatMethod.NONE)); - - parseCommand("--nat-method", "UPNP"); - verify(mockRunnerBuilder).natMethod(eq(NatMethod.UPNP)); - - parseCommand("--nat-method", "UPNPP2PONLY"); - verify(mockRunnerBuilder).natMethod(eq(NatMethod.UPNPP2PONLY)); - - parseCommand("--nat-method", "AUTO"); - verify(mockRunnerBuilder).natMethod(eq(NatMethod.AUTO)); - - parseCommand("--nat-method", "DOCKER"); - verify(mockRunnerBuilder).natMethod(eq(NatMethod.DOCKER)); - - parseCommand("--nat-method", "KUBERNETES"); - verify(mockRunnerBuilder).natMethod(eq(NatMethod.KUBERNETES)); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void parsesInvalidNatMethodOptionsShouldFail() { - - parseCommand("--nat-method", "invalid"); - Mockito.verifyNoInteractions(mockRunnerBuilder); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains( - "Invalid value for option '--nat-method': expected one of [UPNP, UPNPP2PONLY, DOCKER, KUBERNETES, AUTO, NONE] (case-insensitive) but was 'invalid'"); - } - - @Test - public void ethStatsOptionIsParsedCorrectly() { - final String url = "besu-node:secret@host:443"; - parseCommand("--ethstats", url); - verify(mockRunnerBuilder).ethstatsOptions(ethstatsOptionsArgumentCaptor.capture()); - assertThat(ethstatsOptionsArgumentCaptor.getValue().getEthstatsUrl()).isEqualTo(url); - } - - @Test - public void ethStatsContactOptionIsParsedCorrectly() { - final String contact = "contact@mail.net"; - parseCommand("--ethstats", "besu-node:secret@host:443", "--ethstats-contact", contact); - verify(mockRunnerBuilder).ethstatsOptions(ethstatsOptionsArgumentCaptor.capture()); - assertThat(ethstatsOptionsArgumentCaptor.getValue().getEthstatsContact()).isEqualTo(contact); - } - - @Test - public void ethStatsContactOptionCannotBeUsedWithoutEthStatsServerProvided() { - parseCommand("--ethstats-contact", "besu-updated"); - Mockito.verifyNoInteractions(mockRunnerBuilder); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains( - "The `--ethstats-contact` requires ethstats server URL to be provided. Either remove --ethstats-contact or provide a URL (via --ethstats=nodename:secret@host:port)"); - } - - @Test - public void privacyOnchainGroupsEnabledCannotBeUsedWithPrivacyFlexibleGroupsEnabled() { - parseCommand("--privacy-onchain-groups-enabled", "--privacy-flexible-groups-enabled"); - Mockito.verifyNoInteractions(mockRunnerBuilder); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains( - "The `--privacy-onchain-groups-enabled` option is deprecated and you should only use `--privacy-flexible-groups-enabled`"); - } - - @Test - public void parsesValidBonsaiTrieLimitBackLayersOption() { - parseCommand("--data-storage-format", "BONSAI", "--bonsai-historical-block-limit", "11"); - verify(mockControllerBuilder) - .dataStorageConfiguration(dataStorageConfigurationArgumentCaptor.capture()); - - final DataStorageConfiguration dataStorageConfiguration = - dataStorageConfigurationArgumentCaptor.getValue(); - assertThat(dataStorageConfiguration.getDataStorageFormat()).isEqualTo(BONSAI); - assertThat(dataStorageConfiguration.getBonsaiMaxLayersToLoad()).isEqualTo(11); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void parsesInvalidBonsaiTrieLimitBackLayersOption() { - - parseCommand("--data-storage-format", "BONSAI", "--bonsai-maximum-back-layers-to-load", "ten"); - - Mockito.verifyNoInteractions(mockRunnerBuilder); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains( - "Invalid value for option '--bonsai-maximum-back-layers-to-load': 'ten' is not a long"); - } - - @Test - public void dnsEnabledOptionIsParsedCorrectly() { - final TestBesuCommand besuCommand = parseCommand("--Xdns-enabled", "true"); - - assertThat(besuCommand.getEnodeDnsConfiguration().dnsEnabled()).isTrue(); - assertThat(besuCommand.getEnodeDnsConfiguration().updateEnabled()).isFalse(); - } - - @Test - public void dnsUpdateEnabledOptionIsParsedCorrectly() { - final TestBesuCommand besuCommand = - parseCommand("--Xdns-enabled", "true", "--Xdns-update-enabled", "true"); - - assertThat(besuCommand.getEnodeDnsConfiguration().dnsEnabled()).isTrue(); - assertThat(besuCommand.getEnodeDnsConfiguration().updateEnabled()).isTrue(); - } - - @Test - public void dnsUpdateEnabledOptionCannotBeUsedWithoutDnsEnabled() { - parseCommand("--Xdns-update-enabled", "true"); - Mockito.verifyNoInteractions(mockRunnerBuilder); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains( - "The `--Xdns-update-enabled` requires dns to be enabled. Either remove --Xdns-update-enabled or specify dns is enabled (--Xdns-enabled)"); - } - - @Test - public void helpShouldDisplayNatMethodInfo() { - parseCommand("--help"); - - Mockito.verifyNoInteractions(mockRunnerBuilder); - - assertThat(commandOutput.toString(UTF_8)).contains("--nat-method"); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void natMethodPropertyDefaultIsAuto() { - parseCommand(); - - verify(mockRunnerBuilder).natMethod(eq(NatMethod.AUTO)); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void natManagerPodNamePropertyDefaultIsBesu() { - parseCommand(); - - verify(mockRunnerBuilder).natManagerServiceName(eq(DEFAULT_BESU_SERVICE_NAME_FILTER)); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void natManagerPodNamePropertyIsCorrectlyUpdated() { - final String podName = "besu-updated"; - parseCommand("--Xnat-kube-service-name", podName); - - verify(mockRunnerBuilder).natManagerServiceName(eq(podName)); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void natManagerPodNameCannotBeUsedWithNatDockerMethod() { - parseCommand("--nat-method", "DOCKER", "--Xnat-kube-service-name", "besu-updated"); - Mockito.verifyNoInteractions(mockRunnerBuilder); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains( - "The `--Xnat-kube-service-name` parameter is only used in kubernetes mode. Either remove --Xnat-kube-service-name or select the KUBERNETES mode (via --nat--method=KUBERNETES)"); - } - - @Test - public void natManagerPodNameCannotBeUsedWithNatNoneMethod() { - parseCommand("--nat-method", "NONE", "--Xnat-kube-service-name", "besu-updated"); - Mockito.verifyNoInteractions(mockRunnerBuilder); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains( - "The `--Xnat-kube-service-name` parameter is only used in kubernetes mode. Either remove --Xnat-kube-service-name or select the KUBERNETES mode (via --nat--method=KUBERNETES)"); - } - - @Test - public void natMethodFallbackEnabledPropertyIsCorrectlyUpdatedWithKubernetes() { - - parseCommand("--nat-method", "KUBERNETES", "--Xnat-method-fallback-enabled", "false"); - verify(mockRunnerBuilder).natMethodFallbackEnabled(eq(false)); - parseCommand("--nat-method", "KUBERNETES", "--Xnat-method-fallback-enabled", "true"); - verify(mockRunnerBuilder).natMethodFallbackEnabled(eq(true)); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void natMethodFallbackEnabledPropertyIsCorrectlyUpdatedWithDocker() { - - parseCommand("--nat-method", "DOCKER", "--Xnat-method-fallback-enabled", "false"); - verify(mockRunnerBuilder).natMethodFallbackEnabled(eq(false)); - parseCommand("--nat-method", "DOCKER", "--Xnat-method-fallback-enabled", "true"); - verify(mockRunnerBuilder).natMethodFallbackEnabled(eq(true)); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void natMethodFallbackEnabledPropertyIsCorrectlyUpdatedWithUpnp() { - - parseCommand("--nat-method", "UPNP", "--Xnat-method-fallback-enabled", "false"); - verify(mockRunnerBuilder).natMethodFallbackEnabled(eq(false)); - parseCommand("--nat-method", "UPNP", "--Xnat-method-fallback-enabled", "true"); - verify(mockRunnerBuilder).natMethodFallbackEnabled(eq(true)); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void natMethodFallbackEnabledCannotBeUsedWithAutoMethod() { - parseCommand("--nat-method", "AUTO", "--Xnat-method-fallback-enabled", "false"); - Mockito.verifyNoInteractions(mockRunnerBuilder); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains( - "The `--Xnat-method-fallback-enabled` parameter cannot be used in AUTO mode. Either remove --Xnat-method-fallback-enabled or select another mode (via --nat--method=XXXX)"); - } - - @Test - public void rpcHttpEnabledPropertyDefaultIsFalse() { - parseCommand(); - - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(jsonRpcConfigArgumentCaptor.getValue().isEnabled()).isFalse(); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void rpcHttpEnabledPropertyMustBeUsed() { - parseCommand("--rpc-http-enabled"); - - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(jsonRpcConfigArgumentCaptor.getValue().isEnabled()).isTrue(); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void graphQLHttpEnabledPropertyDefaultIsFalse() { - parseCommand(); - - verify(mockRunnerBuilder).graphQLConfiguration(graphQLConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(graphQLConfigArgumentCaptor.getValue().isEnabled()).isFalse(); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void graphQLHttpEnabledPropertyMustBeUsed() { - parseCommand("--graphql-http-enabled"); - - verify(mockRunnerBuilder).graphQLConfiguration(graphQLConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(graphQLConfigArgumentCaptor.getValue().isEnabled()).isTrue(); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void rpcApisPropertyMustBeUsed() { - parseCommand("--rpc-http-api", "ETH,NET,PERM", "--rpc-http-enabled"); - - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - verify(mockLogger) - .warn("Permissions are disabled. Cannot enable PERM APIs when not using Permissions."); - - assertThat(jsonRpcConfigArgumentCaptor.getValue().getRpcApis()) - .containsExactlyInAnyOrder(ETH.name(), NET.name(), PERM.name()); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void rpcApisSupportsEngine() { - parseCommand("--rpc-http-api", "ENGINE", "--rpc-http-enabled"); - - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(jsonRpcConfigArgumentCaptor.getValue().getRpcApis()) - .containsExactlyInAnyOrder(ENGINE.name()); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void rpcApisPropertyIgnoresDuplicatesAndMustBeUsed() { - parseCommand("--rpc-http-api", "ETH,NET,NET", "--rpc-http-enabled"); - - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(jsonRpcConfigArgumentCaptor.getValue().getRpcApis()) - .containsExactlyInAnyOrder(ETH.name(), NET.name()); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void rpcApiNoAuthMethodsIgnoresDuplicatesAndMustBeUsed() { - parseCommand( - "--rpc-http-api-methods-no-auth", - "admin_peers, admin_peers, eth_getWork", - "--rpc-http-enabled"); - - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(jsonRpcConfigArgumentCaptor.getValue().getNoAuthRpcApis()) - .containsExactlyInAnyOrder( - RpcMethod.ADMIN_PEERS.getMethodName(), RpcMethod.ETH_GET_WORK.getMethodName()); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void engineApiAuthOptions() { - // TODO: once we have mainnet TTD, we can remove the TTD override parameter here - // https://github.com/hyperledger/besu/issues/3874 - parseCommand( - "--override-genesis-config", - "terminalTotalDifficulty=1337", - "--rpc-http-enabled", - "--engine-jwt-secret", - "/tmp/fakeKey.hex"); - verify(mockRunnerBuilder).engineJsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - assertThat(jsonRpcConfigArgumentCaptor.getValue().isAuthenticationEnabled()).isTrue(); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void engineApiDisableAuthOptions() { - // TODO: once we have mainnet TTD, we can remove the TTD override parameter here - // https://github.com/hyperledger/besu/issues/3874 - parseCommand( - "--override-genesis-config", - "terminalTotalDifficulty=1337", - "--rpc-http-enabled", - "--engine-jwt-disabled", - "--engine-jwt-secret", - "/tmp/fakeKey.hex"); - verify(mockRunnerBuilder).engineJsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - assertThat(jsonRpcConfigArgumentCaptor.getValue().isAuthenticationEnabled()).isFalse(); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void rpcHttpNoAuthApiMethodsCannotBeInvalid() { - parseCommand("--rpc-http-enabled", "--rpc-http-api-method-no-auth", "invalid"); - - Mockito.verifyNoInteractions(mockRunnerBuilder); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains( - "Invalid value for option '--rpc-http-api-methods-no-auth', options must be valid RPC methods"); - } - - @Test - public void rpcWsNoAuthApiMethodsCannotBeInvalid() { - parseCommand("--rpc-ws-enabled", "--rpc-ws-api-methods-no-auth", "invalid"); - - Mockito.verifyNoInteractions(mockRunnerBuilder); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains( - "Invalid value for option '--rpc-ws-api-methods-no-auth', options must be valid RPC methods"); - } - - @Test - public void rpcHttpOptionsRequiresServiceToBeEnabled() { - parseCommand( - "--rpc-http-api", - "ETH,NET", - "--rpc-http-host", - "0.0.0.0", - "--rpc-http-port", - "1234", - "--rpc-http-cors-origins", - "all", - "--rpc-http-max-active-connections", - "88"); - - verifyOptionsConstraintLoggerCall( - "--rpc-http-enabled", - "--rpc-http-host", - "--rpc-http-port", - "--rpc-http-cors-origins", - "--rpc-http-api", - "--rpc-http-max-active-connections"); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void rpcHttpOptionsRequiresServiceToBeEnabledToml() throws IOException { - final Path toml = - createTempFile( - "toml", - "rpc-http-api=[\"ETH\",\"NET\"]\n" - + "rpc-http-host=\"0.0.0.0\"\n" - + "rpc-http-port=1234\n" - + "rpc-http-cors-origins=[\"all\"]\n" - + "rpc-http-max-active-connections=88"); - - parseCommand("--config-file", toml.toString()); - - verifyOptionsConstraintLoggerCall( - "--rpc-http-enabled", - "--rpc-http-host", - "--rpc-http-port", - "--rpc-http-cors-origins", - "--rpc-http-api", - "--rpc-http-max-active-connections"); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void privacyTlsOptionsRequiresTlsToBeEnabled() { - when(storageService.getByName("rocksdb-privacy")) - .thenReturn(Optional.of(rocksDBSPrivacyStorageFactory)); - final URL configFile = this.getClass().getResource("/orion_publickey.pub"); - final String coinbaseStr = String.format("%040x", 1); - - parseCommand( - "--privacy-enabled", - "--miner-enabled", - "--miner-coinbase=" + coinbaseStr, - "--min-gas-price", - "0", - "--privacy-url", - ENCLAVE_URI, - "--privacy-public-key-file", - configFile.getPath(), - "--privacy-tls-keystore-file", - "/Users/me/key"); - - verifyOptionsConstraintLoggerCall("--privacy-tls-enabled", "--privacy-tls-keystore-file"); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void privacyTlsOptionsRequiresTlsToBeEnabledToml() throws IOException { - when(storageService.getByName("rocksdb-privacy")) - .thenReturn(Optional.of(rocksDBSPrivacyStorageFactory)); - final URL configFile = this.getClass().getResource("/orion_publickey.pub"); - final String coinbaseStr = String.format("%040x", 1); - - final Path toml = - createTempFile( - "toml", - "privacy-enabled=true\n" - + "miner-enabled=true\n" - + "miner-coinbase=\"" - + coinbaseStr - + "\"\n" - + "min-gas-price=0\n" - + "privacy-url=\"" - + ENCLAVE_URI - + "\"\n" - + "privacy-public-key-file=\"" - + configFile.getPath() - + "\"\n" - + "privacy-tls-keystore-file=\"/Users/me/key\""); - - parseCommand("--config-file", toml.toString()); - - verifyOptionsConstraintLoggerCall("--privacy-tls-enabled", "--privacy-tls-keystore-file"); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void privacyTlsOptionsRequiresPrivacyToBeEnabled() { - parseCommand("--privacy-tls-enabled", "--privacy-tls-keystore-file", "/Users/me/key"); - - verifyOptionsConstraintLoggerCall("--privacy-enabled", "--privacy-tls-enabled"); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void privacyTlsOptionsRequiresPrivacyToBeEnabledToml() throws IOException { - final Path toml = - createTempFile( - "toml", "privacy-tls-enabled=true\n" + "privacy-tls-keystore-file=\"/Users/me/key\""); - - parseCommand("--config-file", toml.toString()); - - verifyOptionsConstraintLoggerCall("--privacy-enabled", "--privacy-tls-enabled"); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void rpcApisPropertyWithInvalidEntryMustDisplayError() { - parseCommand("--rpc-http-api", "BOB"); - - Mockito.verifyNoInteractions(mockRunnerBuilder); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - // PicoCLI uses longest option name for message when option has multiple names, so here plural. - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("Invalid value for option '--rpc-http-api': invalid entries found [BOB]"); - } - - @Test - public void rpcWsApisPropertyWithInvalidEntryMustDisplayError() { - parseCommand("--rpc-ws-api", "ETH,BOB,TEST"); - - Mockito.verifyNoInteractions(mockRunnerBuilder); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - - assertThat(commandErrorOutput.toString(UTF_8).trim()) - .contains("Invalid value for option '--rpc-ws-api': invalid entries found [BOB, TEST]"); - } - - @Test - public void rpcApisPropertyWithPluginNamespaceAreValid() { - - rpcEndpointServiceImpl.registerRPCEndpoint( - "bob", "method", (Function) request -> "nothing"); - - parseCommand("--rpc-http-api", "BOB"); - - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(jsonRpcConfigArgumentCaptor.getValue().getRpcApis()) - .containsExactlyInAnyOrder("BOB"); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void rpcHttpHostAndPortOptionsMustBeUsed() { - - final String host = "1.2.3.4"; - final int port = 1234; - parseCommand( - "--rpc-http-enabled", "--rpc-http-host", host, "--rpc-http-port", String.valueOf(port)); - - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void rpcHttpHostMayBeLocalhost() { - - final String host = "localhost"; - parseCommand("--rpc-http-enabled", "--rpc-http-host", host); - - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void rpcHttpHostMayBeIPv6() { - - final String host = "2600:DB8::8545"; - parseCommand("--rpc-http-enabled", "--rpc-http-host", host); - - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void rpcHttpMaxActiveConnectionsPropertyMustBeUsed() { - final int maxConnections = 99; - parseCommand("--rpc-http-max-active-connections", String.valueOf(maxConnections)); - - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(jsonRpcConfigArgumentCaptor.getValue().getMaxActiveConnections()) - .isEqualTo(maxConnections); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void rpcWsMaxFrameSizePropertyMustBeUsed() { - final int maxFrameSize = 65535; - parseCommand("--rpc-ws-max-frame-size", String.valueOf(maxFrameSize)); - - verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(wsRpcConfigArgumentCaptor.getValue().getMaxFrameSize()).isEqualTo(maxFrameSize); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void rpcWsMaxActiveConnectionsPropertyMustBeUsed() { - final int maxConnections = 99; - parseCommand("--rpc-ws-max-active-connections", String.valueOf(maxConnections)); - - verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(wsRpcConfigArgumentCaptor.getValue().getMaxActiveConnections()) - .isEqualTo(maxConnections); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void rpcHttpTlsRequiresRpcHttpEnabled() { - parseCommand("--rpc-http-tls-enabled"); - - verifyOptionsConstraintLoggerCall("--rpc-http-enabled", "--rpc-http-tls-enabled"); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void rpcHttpTlsRequiresRpcHttpEnabledToml() throws IOException { - final Path toml = createTempFile("toml", "rpc-http-tls-enabled=true\n"); - - parseCommand("--config-file", toml.toString()); - - verifyOptionsConstraintLoggerCall("--rpc-http-enabled", "--rpc-http-tls-enabled"); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void rpcHttpTlsWithoutKeystoreReportsError() { - parseCommand("--rpc-http-enabled", "--rpc-http-tls-enabled"); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("Keystore file is required when TLS is enabled for JSON-RPC HTTP endpoint"); - } - - @Test - public void rpcHttpTlsWithoutPasswordfileReportsError() { - parseCommand( - "--rpc-http-enabled", - "--rpc-http-tls-enabled", - "--rpc-http-tls-keystore-file", - "/tmp/test.p12"); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains( - "File containing password to unlock keystore is required when TLS is enabled for JSON-RPC HTTP endpoint"); - } - - @Test - public void rpcHttpTlsKeystoreAndPasswordMustBeUsed() { - final String host = "1.2.3.4"; - final int port = 1234; - final String keystoreFile = "/tmp/test.p12"; - final String keystorePasswordFile = "/tmp/test.txt"; - - parseCommand( - "--rpc-http-enabled", - "--rpc-http-host", - host, - "--rpc-http-port", - String.valueOf(port), - "--rpc-http-tls-enabled", - "--rpc-http-tls-keystore-file", - keystoreFile, - "--rpc-http-tls-keystore-password-file", - keystorePasswordFile); - - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port); - final Optional tlsConfiguration = - jsonRpcConfigArgumentCaptor.getValue().getTlsConfiguration(); - assertThat(tlsConfiguration.isPresent()).isTrue(); - assertThat(tlsConfiguration.get().getKeyStorePath()).isEqualTo(Path.of(keystoreFile)); - assertThat(tlsConfiguration.get().getClientAuthConfiguration().isEmpty()).isTrue(); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void rpcHttpTlsClientAuthWithoutKnownFileReportsError() { - final String host = "1.2.3.4"; - final int port = 1234; - final String keystoreFile = "/tmp/test.p12"; - final String keystorePasswordFile = "/tmp/test.txt"; - parseCommand( - "--rpc-http-enabled", - "--rpc-http-host", - host, - "--rpc-http-port", - String.valueOf(port), - "--rpc-http-tls-enabled", - "--rpc-http-tls-keystore-file", - keystoreFile, - "--rpc-http-tls-keystore-password-file", - keystorePasswordFile, - "--rpc-http-tls-client-auth-enabled"); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains( - "Known-clients file must be specified or CA clients must be enabled when TLS client authentication is enabled for JSON-RPC HTTP endpoint"); - } - - @Test - public void rpcHttpTlsClientAuthWithKnownClientFile() { - final String host = "1.2.3.4"; - final int port = 1234; - final String keystoreFile = "/tmp/test.p12"; - final String keystorePasswordFile = "/tmp/test.txt"; - final String knownClientFile = "/tmp/knownClientFile"; - parseCommand( - "--rpc-http-enabled", - "--rpc-http-host", - host, - "--rpc-http-port", - String.valueOf(port), - "--rpc-http-tls-enabled", - "--rpc-http-tls-keystore-file", - keystoreFile, - "--rpc-http-tls-keystore-password-file", - keystorePasswordFile, - "--rpc-http-tls-client-auth-enabled", - "--rpc-http-tls-known-clients-file", - knownClientFile); - - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port); - final Optional tlsConfiguration = - jsonRpcConfigArgumentCaptor.getValue().getTlsConfiguration(); - assertThat(tlsConfiguration.isPresent()).isTrue(); - assertThat(tlsConfiguration.get().getKeyStorePath()).isEqualTo(Path.of(keystoreFile)); - assertThat(tlsConfiguration.get().getClientAuthConfiguration().isPresent()).isTrue(); - assertThat( - tlsConfiguration.get().getClientAuthConfiguration().get().getKnownClientsFile().get()) - .isEqualTo(Path.of(knownClientFile)); - assertThat(tlsConfiguration.get().getClientAuthConfiguration().get().isCaClientsEnabled()) - .isFalse(); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void rpcHttpTlsClientAuthWithCAClient() { - final String host = "1.2.3.4"; - final int port = 1234; - final String keystoreFile = "/tmp/test.p12"; - final String keystorePasswordFile = "/tmp/test.txt"; - parseCommand( - "--rpc-http-enabled", - "--rpc-http-host", - host, - "--rpc-http-port", - String.valueOf(port), - "--rpc-http-tls-enabled", - "--rpc-http-tls-keystore-file", - keystoreFile, - "--rpc-http-tls-keystore-password-file", - keystorePasswordFile, - "--rpc-http-tls-client-auth-enabled", - "--rpc-http-tls-ca-clients-enabled"); - - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port); - final Optional tlsConfiguration = - jsonRpcConfigArgumentCaptor.getValue().getTlsConfiguration(); - assertThat(tlsConfiguration.isPresent()).isTrue(); - assertThat(tlsConfiguration.get().getKeyStorePath()).isEqualTo(Path.of(keystoreFile)); - assertThat(tlsConfiguration.get().getClientAuthConfiguration().isPresent()).isTrue(); - assertThat( - tlsConfiguration - .get() - .getClientAuthConfiguration() - .get() - .getKnownClientsFile() - .isEmpty()) - .isTrue(); - assertThat(tlsConfiguration.get().getClientAuthConfiguration().get().isCaClientsEnabled()) - .isTrue(); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void rpcHttpTlsClientAuthWithCAClientAndKnownClientFile() { - final String host = "1.2.3.4"; - final int port = 1234; - final String keystoreFile = "/tmp/test.p12"; - final String keystorePasswordFile = "/tmp/test.txt"; - final String knownClientFile = "/tmp/knownClientFile"; - parseCommand( - "--rpc-http-enabled", - "--rpc-http-host", - host, - "--rpc-http-port", - String.valueOf(port), - "--rpc-http-tls-enabled", - "--rpc-http-tls-keystore-file", - keystoreFile, - "--rpc-http-tls-keystore-password-file", - keystorePasswordFile, - "--rpc-http-tls-client-auth-enabled", - "--rpc-http-tls-ca-clients-enabled", - "--rpc-http-tls-known-clients-file", - knownClientFile); - - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port); - final Optional tlsConfiguration = - jsonRpcConfigArgumentCaptor.getValue().getTlsConfiguration(); - assertThat(tlsConfiguration.isPresent()).isTrue(); - assertThat(tlsConfiguration.get().getKeyStorePath()).isEqualTo(Path.of(keystoreFile)); - assertThat(tlsConfiguration.get().getClientAuthConfiguration().isPresent()).isTrue(); - assertThat( - tlsConfiguration.get().getClientAuthConfiguration().get().getKnownClientsFile().get()) - .isEqualTo(Path.of(knownClientFile)); - assertThat(tlsConfiguration.get().getClientAuthConfiguration().get().isCaClientsEnabled()) - .isTrue(); + public void predefinedNetworkIdsMustBeEqualToChainIds() { + // check the network id against the one in mainnet genesis config + // it implies that EthNetworkConfig.mainnet().getNetworkId() returns a value equals to the chain + // id + // in this network genesis file. - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + final var genesisConfig = + EthNetworkConfig.getNetworkConfig(MAINNET).genesisConfigFile().getConfigOptions(); + assertThat(genesisConfig.getChainId().isPresent()).isTrue(); + assertThat(genesisConfig.getChainId().get()) + .isEqualTo(EthNetworkConfig.getNetworkConfig(MAINNET).networkId()); } @Test - public void rpcHttpTlsCheckDefaultProtocolsAndCipherSuites() { - final String host = "1.2.3.4"; - final int port = 1234; - final String keystoreFile = "/tmp/test.p12"; - final String keystorePasswordFile = "/tmp/test.txt"; - - parseCommand( - "--rpc-http-enabled", - "--rpc-http-host", - host, - "--rpc-http-port", - String.valueOf(port), - "--rpc-http-tls-enabled", - "--rpc-http-tls-keystore-file", - keystoreFile, - "--rpc-http-tls-keystore-password-file", - keystorePasswordFile); + public void identityValueTrueMustBeUsed() { + parseCommand("--identity", "test"); - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).identityString(eq(Optional.of("test"))); verify(mockRunnerBuilder).build(); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port); - final Optional tlsConfiguration = - jsonRpcConfigArgumentCaptor.getValue().getTlsConfiguration(); - assertThat(tlsConfiguration).isPresent(); - assertThat(tlsConfiguration.get().getKeyStorePath()).isEqualTo(Path.of(keystoreFile)); - assertThat(tlsConfiguration.get().getClientAuthConfiguration()).isEmpty(); - assertThat(tlsConfiguration.get().getCipherSuites().get()).isEmpty(); - assertThat(tlsConfiguration.get().getSecureTransportProtocols().get()) - .containsExactly("TLSv1.3", "TLSv1.2"); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void rpcHttpTlsCheckInvalidProtocols() { - final String host = "1.2.3.4"; - final int port = 1234; - final String keystoreFile = "/tmp/test.p12"; - final String keystorePasswordFile = "/tmp/test.txt"; - final String protocol = "TLsv1.4"; - - parseCommand( - "--rpc-http-enabled", - "--rpc-http-host", - host, - "--rpc-http-port", - String.valueOf(port), - "--rpc-http-tls-enabled", - "--rpc-http-tls-keystore-file", - keystoreFile, - "--rpc-http-tls-keystore-password-file", - keystorePasswordFile, - "--rpc-http-tls-protocols", - protocol); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).contains("No valid TLS protocols specified"); - } - - @Test - public void rpcHttpTlsCheckInvalidCipherSuites() { - final String host = "1.2.3.4"; - final int port = 1234; - final String keystoreFile = "/tmp/test.p12"; - final String keystorePasswordFile = "/tmp/test.txt"; - final String cipherSuites = "Invalid"; - - parseCommand( - "--rpc-http-enabled", - "--rpc-http-host", - host, - "--rpc-http-port", - String.valueOf(port), - "--rpc-http-tls-enabled", - "--rpc-http-tls-keystore-file", - keystoreFile, - "--rpc-http-tls-keystore-password-file", - keystorePasswordFile, - "--rpc-http-tls-cipher-suites", - cipherSuites); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("Invalid TLS cipher suite specified " + cipherSuites); - } - - @Test - public void rpcHttpTlsCheckValidProtocolsAndCipherSuites() { - final String host = "1.2.3.4"; - final int port = 1234; - final String keystoreFile = "/tmp/test.p12"; - final String keystorePasswordFile = "/tmp/test.txt"; - final String protocols = "TLSv1.3,TLSv1.2"; - final String cipherSuites = - "TLS_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"; - - parseCommand( - "--rpc-http-enabled", - "--rpc-http-host", - host, - "--rpc-http-port", - String.valueOf(port), - "--rpc-http-tls-enabled", - "--rpc-http-tls-keystore-file", - keystoreFile, - "--rpc-http-tls-keystore-password-file", - keystorePasswordFile, - "--rpc-http-tls-protocols", - protocols, - "--rpc-http-tls-cipher-suites", - cipherSuites); + public void p2pEnabledOptionValueTrueMustBeUsed() { + parseCommand("--p2p-enabled", "true"); - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).p2pEnabled(eq(true)); verify(mockRunnerBuilder).build(); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port); - final Optional tlsConfiguration = - jsonRpcConfigArgumentCaptor.getValue().getTlsConfiguration(); - assertThat(tlsConfiguration).isPresent(); - assertThat(tlsConfiguration.get().getKeyStorePath()).isEqualTo(Path.of(keystoreFile)); - assertThat(tlsConfiguration.get().getClientAuthConfiguration()).isEmpty(); - assertThat(tlsConfiguration.get().getCipherSuites().get()) - .containsExactlyInAnyOrder( - "TLS_AES_256_GCM_SHA384", - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"); - assertThat(tlsConfiguration.get().getSecureTransportProtocols().get()) - .containsExactlyInAnyOrder("TLSv1.2", "TLSv1.3"); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void rpcHttpTlsWarnIfCipherSuitesSpecifiedWithoutTls() { - final String host = "1.2.3.4"; - final int port = 1234; - final String cipherSuites = "Invalid"; - - parseCommand( - "--rpc-http-enabled", - "--engine-rpc-enabled", - "--rpc-http-host", - host, - "--rpc-http-port", - String.valueOf(port), - "--rpc-http-tls-cipher-suite", - cipherSuites); - verify( - mockLogger, - times(2)) // this is verified for both the full suite of apis, and the engine group. - .warn( - "{} has been ignored because {} was not defined on the command line.", - "--rpc-http-tls-cipher-suite", - "--rpc-http-tls-enabled"); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void graphQLHttpHostAndPortOptionsMustBeUsed() { - - final String host = "1.2.3.4"; - final int port = 1234; - parseCommand( - "--graphql-http-enabled", - "--graphql-http-host", - host, - "--graphql-http-port", - String.valueOf(port)); + public void p2pEnabledOptionValueFalseMustBeUsed() { + parseCommand("--p2p-enabled", "false"); - verify(mockRunnerBuilder).graphQLConfiguration(graphQLConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).p2pEnabled(eq(false)); verify(mockRunnerBuilder).build(); - assertThat(graphQLConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); - assertThat(graphQLConfigArgumentCaptor.getValue().getPort()).isEqualTo(port); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void graphQLHttpHostMayBeLocalhost() { - - final String host = "localhost"; - parseCommand("--graphql-http-enabled", "--graphql-http-host", host); + public void p2pOptionsRequiresServiceToBeEnabled() { + final String[] nodes = { + "6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0" + }; - verify(mockRunnerBuilder).graphQLConfiguration(graphQLConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); + parseCommand( + "--p2p-enabled", + "false", + "--bootnodes", + String.join(",", VALID_ENODE_STRINGS), + "--discovery-enabled", + "false", + "--max-peers", + "42", + "--remote-connections-max-percentage", + "50", + "--banned-node-id", + String.join(",", nodes), + "--banned-node-ids", + String.join(",", nodes)); - assertThat(graphQLConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); + verifyOptionsConstraintLoggerCall( + "--p2p-enabled", + "--discovery-enabled", + "--bootnodes", + "--max-peers", + "--banned-node-ids", + "--remote-connections-max-percentage"); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void graphQLHttpHostMayBeIPv6() { - - final String host = "2600:DB8::8545"; - parseCommand("--graphql-http-enabled", "--graphql-http-host", host); - - verify(mockRunnerBuilder).graphQLConfiguration(graphQLConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(graphQLConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } + public void p2pOptionsRequiresServiceToBeEnabledToml() throws IOException { + final String[] nodes = { + "6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0" + }; - @Test - public void rpcHttpCorsOriginsTwoDomainsMustBuildListWithBothDomains() { - final String[] origins = {"http://domain1.com", "https://domain2.com"}; - parseCommand("--rpc-http-enabled", "--rpc-http-cors-origins", String.join(",", origins)); + final Path toml = + createTempFile( + "toml", + "p2p-enabled=false\n" + + "bootnodes=[\"" + + String.join("\",\"", VALID_ENODE_STRINGS) + + "\"]\n" + + "discovery-enabled=false\n" + + "max-peers=42\n" + + "remote-connections-max-percentage=50\n" + + "banned-node-id=[\"" + + String.join(",", nodes) + + "\"]\n" + + "banned-node-ids=[\"" + + String.join(",", nodes) + + "\"]\n"); - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); + parseCommand("--config-file", toml.toString()); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getCorsAllowedDomains().toArray()) - .isEqualTo(origins); + verifyOptionsConstraintLoggerCall( + "--p2p-enabled", + "--discovery-enabled", + "--bootnodes", + "--max-peers", + "--banned-node-ids", + "--remote-connections-max-percentage"); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void rpcHttpCorsOriginsDoubleCommaFilteredOut() { - final String[] origins = {"http://domain1.com", "https://domain2.com"}; - parseCommand("--rpc-http-enabled", "--rpc-http-cors-origins", String.join(",,", origins)); + public void discoveryOptionValueTrueMustBeUsed() { + parseCommand("--discovery-enabled", "true"); - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).discovery(eq(true)); verify(mockRunnerBuilder).build(); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getCorsAllowedDomains().toArray()) - .isEqualTo(origins); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void rpcHttpCorsOriginsWithWildcardMustBuildListWithWildcard() { - final String[] origins = {"*"}; - parseCommand("--rpc-http-enabled", "--rpc-http-cors-origins", String.join(",", origins)); + public void discoveryOptionValueFalseMustBeUsed() { + parseCommand("--discovery-enabled", "false"); - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).discovery(eq(false)); verify(mockRunnerBuilder).build(); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getCorsAllowedDomains().toArray()) - .isEqualTo(origins); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void rpcHttpCorsOriginsWithAllMustBuildListWithWildcard() { - parseCommand("--rpc-http-enabled", "--rpc-http-cors-origins", "all"); - - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(jsonRpcConfigArgumentCaptor.getValue().getCorsAllowedDomains()).containsExactly("*"); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } + public void loadDiscoveryOptionsFromGenesisFile() throws IOException { + final Path genesisFile = createFakeGenesisFile(VALID_GENESIS_WITH_DISCOVERY_OPTIONS); + parseCommand("--genesis-file", genesisFile.toString()); - @Test - public void rpcHttpCorsOriginsWithNoneMustBuildEmptyList() { - final String[] origins = {"none"}; - parseCommand("--rpc-http-enabled", "--rpc-http-cors-origins", String.join(",", origins)); + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getCorsAllowedDomains()).isEmpty(); + final EthNetworkConfig config = networkArg.getValue(); + assertThat(config.dnsDiscoveryUrl()).isEqualTo(DNS_DISCOVERY_URL); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(config.bootNodes()) + .extracting(bootnode -> bootnode.toURI().toString()) + .containsExactly(VALID_ENODE_STRINGS); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void rpcHttpCorsOriginsNoneWithAnotherDomainMustFail() { - final String[] origins = {"http://domain1.com", "none"}; - parseCommand("--rpc-http-cors-origins", String.join(",", origins)); - - Mockito.verifyNoInteractions(mockRunnerBuilder); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("Value 'none' can't be used with other domains"); - } - - @Test - public void rpcHttpCorsOriginsNoneWithAnotherDomainMustFailNoneFirst() { - final String[] origins = {"none", "http://domain1.com"}; - parseCommand("--rpc-http-cors-origins", String.join(",", origins)); - - Mockito.verifyNoInteractions(mockRunnerBuilder); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("Value 'none' can't be used with other domains"); - } - - @Test - public void rpcHttpCorsOriginsAllWithAnotherDomainMustFail() { - parseCommand("--rpc-http-cors-origins=http://domain1.com,all"); - - Mockito.verifyNoInteractions(mockRunnerBuilder); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("Values '*' or 'all' can't be used with other domains"); - } - - @Test - public void rpcHttpCorsOriginsAllWithAnotherDomainMustFailAsFlags() { - parseCommand("--rpc-http-cors-origins=http://domain1.com", "--rpc-http-cors-origins=all"); - - Mockito.verifyNoInteractions(mockRunnerBuilder); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("Values '*' or 'all' can't be used with other domains"); - } - - @Test - public void rpcHttpCorsOriginsWildcardWithAnotherDomainMustFail() { - parseCommand("--rpc-http-cors-origins=http://domain1.com,*"); - - Mockito.verifyNoInteractions(mockRunnerBuilder); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("Values '*' or 'all' can't be used with other domains"); - } - - @Test - public void rpcHttpCorsOriginsWildcardWithAnotherDomainMustFailAsFlags() { - parseCommand("--rpc-http-cors-origins=http://domain1.com", "--rpc-http-cors-origins=*"); - - Mockito.verifyNoInteractions(mockRunnerBuilder); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("Values '*' or 'all' can't be used with other domains"); - } - - @Test - public void rpcHttpCorsOriginsInvalidRegexShouldFail() { - final String[] origins = {"**"}; - parseCommand("--rpc-http-cors-origins", String.join(",", origins)); - - Mockito.verifyNoInteractions(mockRunnerBuilder); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("Domain values result in invalid regex pattern"); - } - - @Test - public void rpcHttpCorsOriginsEmptyValueFails() { - parseCommand("--rpc-http-cors-origins="); + public void discoveryDnsUrlCliArgTakesPrecedenceOverGenesisFile() throws IOException { + final Path genesisFile = createFakeGenesisFile(VALID_GENESIS_WITH_DISCOVERY_OPTIONS); + final String discoveryDnsUrlCliArg = + "enrtree://XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX@nodes.example.org"; + parseCommand( + "--genesis-file", genesisFile.toString(), "--discovery-dns-url", discoveryDnsUrlCliArg); - Mockito.verifyNoInteractions(mockRunnerBuilder); + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("Domain cannot be empty string or null string."); + final EthNetworkConfig config = networkArg.getValue(); + assertThat(config.dnsDiscoveryUrl()).isEqualTo(discoveryDnsUrlCliArg); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } - /** test deprecated CLI option * */ - @Deprecated @Test - public void rpcHttpHostWhitelistAcceptsSingleArgument() { - parseCommand("--host-whitelist", "a"); + public void bootnodesUrlCliArgTakesPrecedenceOverGenesisFile() throws IOException { + final Path genesisFile = createFakeGenesisFile(VALID_GENESIS_WITH_DISCOVERY_OPTIONS); + final URI bootnode = + URI.create( + "enode://d2567893371ea5a6fa6371d483891ed0d129e79a8fc74d6df95a00a6545444cd4a6960bbffe0b4e2edcf35135271de57ee559c0909236bbc2074346ef2b5b47c@127.0.0.1:30304"); - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); + parseCommand("--genesis-file", genesisFile.toString(), "--bootnodes", bootnode.toString()); + + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist().size()).isEqualTo(1); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist()).contains("a"); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist()) - .doesNotContain("localhost"); + final EthNetworkConfig config = networkArg.getValue(); + assertThat(config.bootNodes()).extracting(EnodeURL::toURI).containsExactly(bootnode); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void rpcHttpHostAllowlistAcceptsSingleArgument() { - parseCommand("--host-allowlist", "a"); + public void poaDiscoveryRetryBootnodesValueTrueMustBeUsed() { + parseCommand("--poa-discovery-retry-bootnodes", "true"); - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).poaDiscoveryRetryBootnodes(eq(true)); verify(mockRunnerBuilder).build(); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist().size()).isEqualTo(1); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist()).contains("a"); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist()) - .doesNotContain("localhost"); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void rpcHttpHostAllowlistAcceptsMultipleArguments() { - parseCommand("--host-allowlist", "a,b"); + public void poaDiscoveryRetryBootnodesValueFalseMustBeUsed() { + parseCommand("--poa-discovery-retry-bootnodes", "false"); - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).poaDiscoveryRetryBootnodes(eq(false)); verify(mockRunnerBuilder).build(); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist().size()).isEqualTo(2); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist()).contains("a", "b"); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist()) - .doesNotContain("*", "localhost"); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void rpcHttpHostAllowlistAcceptsDoubleComma() { - parseCommand("--host-allowlist", "a,,b"); + public void callingWithBootnodesOptionButNoValueMustPassEmptyBootnodeList() { + parseCommand("--bootnodes"); - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).ethNetworkConfig(ethNetworkConfigArgumentCaptor.capture()); verify(mockRunnerBuilder).build(); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist().size()).isEqualTo(2); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist()).contains("a", "b"); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist()) - .doesNotContain("*", "localhost"); + assertThat(ethNetworkConfigArgumentCaptor.getValue().bootNodes()).isEmpty(); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } - @Deprecated @Test - public void rpcHttpHostWhitelistAllowlistAcceptsMultipleFlags() { - parseCommand("--host-whitelist=a", "--host-allowlist=b"); - - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist().size()).isEqualTo(2); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist()).contains("a", "b"); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist()) - .doesNotContain("*", "localhost"); - + public void callingWithValidBootnodeMustSucceed() { + parseCommand( + "--bootnodes", + "enode://d2567893371ea5a6fa6371d483891ed0d129e79a8fc74d6df95a00a6545444cd4a6960bbffe0b4e2edcf35135271de57ee559c0909236bbc2074346ef2b5b47c@127.0.0.1:30304"); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void rpcHttpHostAllowlistAcceptsMultipleFlags() { - parseCommand("--host-allowlist=a", "--host-allowlist=b"); - - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist().size()).isEqualTo(2); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist()).contains("a", "b"); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist()) - .doesNotContain("*", "localhost"); - + public void callingWithValidBootnodeButDiscoveryDisabledMustDisplayWarning() { + parseCommand( + "--bootnodes", + "enode://d2567893371ea5a6fa6371d483891ed0d129e79a8fc74d6df95a00a6545444cd4a6960bbffe0b4e2edcf35135271de57ee559c0909236bbc2074346ef2b5b47c@127.0.0.1:30304", + "--discovery-enabled", + "false"); assertThat(commandOutput.toString(UTF_8)).isEmpty(); + verify(mockRunnerBuilder).build(); + verify(mockLogger, atLeast(1)).warn("Discovery disabled: bootnodes will be ignored."); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void rpcHttpHostAllowlistStarWithAnotherHostnameMustFail() { - final String[] origins = {"friend", "*"}; - parseCommand("--host-allowlist", String.join(",", origins)); - - Mockito.verifyNoInteractions(mockRunnerBuilder); - + public void callingWithInvalidBootnodeMustDisplayError() { + parseCommand("--bootnodes", "invalid_enode_url"); assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("Values '*' or 'all' can't be used with other hostnames"); + final String expectedErrorOutputStart = + "Invalid enode URL syntax 'invalid_enode_url'. Enode URL should have the following format " + + "'enode://@:[?discport=]'."; + assertThat(commandErrorOutput.toString(UTF_8)).startsWith(expectedErrorOutputStart); } @Test - public void rpcHttpHostAllowlistStarWithAnotherHostnameMustFailStarFirst() { - final String[] origins = {"*", "friend"}; - parseCommand("--host-allowlist", String.join(",", origins)); - - Mockito.verifyNoInteractions(mockRunnerBuilder); - + public void callingWithBootnodeThatHasDiscoveryDisabledMustDisplayError() { + final String validBootnode = + "enode://d2567893371ea5a6fa6371d483891ed0d129e79a8fc74d6df95a00a6545444cd4a6960bbffe0b4e2edcf35135271de57ee559c0909236bbc2074346ef2b5b47c@127.0.0.1:30304"; + final String invalidBootnode = + "enode://02567893371ea5a6fa6371d483891ed0d129e79a8fc74d6df95a00a6545444cd4a6960bbffe0b4e2edcf35135271de57ee559c0909236bbc2074346ef2b5b47c@127.0.0.1:30303?discport=0"; + final String bootnodesValue = validBootnode + "," + invalidBootnode; + parseCommand("--bootnodes", bootnodesValue); assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("Values '*' or 'all' can't be used with other hostnames"); + final String expectedErrorOutputStart = + "Bootnodes must have discovery enabled. Invalid bootnodes: " + invalidBootnode + "."; + assertThat(commandErrorOutput.toString(UTF_8)).startsWith(expectedErrorOutputStart); } + // This test ensures non regression on https://pegasys1.atlassian.net/browse/PAN-2387 @Test - public void rpcHttpHostAllowlistAllWithAnotherHostnameMustFail() { - final String[] origins = {"friend", "all"}; - parseCommand("--host-allowlist", String.join(",", origins)); - - Mockito.verifyNoInteractions(mockRunnerBuilder); - + public void callingWithInvalidBootnodeAndEqualSignMustDisplayError() { + parseCommand("--bootnodes=invalid_enode_url"); assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("Values '*' or 'all' can't be used with other hostnames"); + final String expectedErrorOutputStart = + "Invalid enode URL syntax 'invalid_enode_url'. Enode URL should have the following format " + + "'enode://@:[?discport=]'."; + assertThat(commandErrorOutput.toString(UTF_8)).startsWith(expectedErrorOutputStart); } @Test - public void rpcHttpHostAllowlistWithNoneMustBuildEmptyList() { - final String[] origins = {"none"}; - parseCommand("--host-allowlist", String.join(",", origins)); + public void bootnodesOptionMustBeUsed() { + parseCommand("--bootnodes", String.join(",", VALID_ENODE_STRINGS)); - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).ethNetworkConfig(ethNetworkConfigArgumentCaptor.capture()); verify(mockRunnerBuilder).build(); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist()).isEmpty(); + assertThat(ethNetworkConfigArgumentCaptor.getValue().bootNodes()) + .isEqualTo( + Stream.of(VALID_ENODE_STRINGS) + .map(EnodeURLImpl::fromString) + .collect(Collectors.toList())); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void rpcHttpHostAllowlistNoneWithAnotherDomainMustFail() { - final String[] origins = {"http://domain1.com", "none"}; - parseCommand("--host-allowlist", String.join(",", origins)); - - Mockito.verifyNoInteractions(mockRunnerBuilder); + public void bannedNodeIdsOptionMustBeUsed() { + final Bytes[] nodes = { + Bytes.fromHexString( + "6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0"), + Bytes.fromHexString( + "7f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0"), + Bytes.fromHexString( + "0x8f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0") + }; - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("Value 'none' can't be used with other hostnames"); - } + final String nodeIdsArg = + Arrays.stream(nodes).map(Bytes::toShortHexString).collect(Collectors.joining(",")); + parseCommand("--banned-node-ids", nodeIdsArg); - @Test - public void rpcHttpHostAllowlistNoneWithAnotherDomainMustFailNoneFirst() { - final String[] origins = {"none", "http://domain1.com"}; - parseCommand("--host-allowlist", String.join(",", origins)); + verify(mockRunnerBuilder).bannedNodeIds(bytesCollectionCollector.capture()); + verify(mockRunnerBuilder).build(); - Mockito.verifyNoInteractions(mockRunnerBuilder); + assertThat(bytesCollectionCollector.getValue().toArray()).isEqualTo(nodes); assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("Value 'none' can't be used with other hostnames"); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void rpcHttpHostAllowlistEmptyValueFails() { - parseCommand("--host-allowlist="); - - Mockito.verifyNoInteractions(mockRunnerBuilder); - + public void callingWithBannedNodeidsOptionButNoValueMustDisplayError() { + parseCommand("--banned-node-ids"); assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("Hostname cannot be empty string or null string."); + final String expectedErrorOutputStart = + "Missing required parameter for option '--banned-node-ids' at index 0 ()"; + assertThat(commandErrorOutput.toString(UTF_8)).startsWith(expectedErrorOutputStart); } @Test - public void rpcWsRpcEnabledPropertyDefaultIsFalse() { - parseCommand(); - - verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(wsRpcConfigArgumentCaptor.getValue().isEnabled()).isFalse(); - + public void callingWithBannedNodeidsOptionWithInvalidValuesMustDisplayError() { + parseCommand("--banned-node-ids", "0x10,20,30"); assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + final String expectedErrorOutputStart = + "Invalid ids supplied to '--banned-node-ids'. Expected 64 bytes in 0x10"; + assertThat(commandErrorOutput.toString(UTF_8)).startsWith(expectedErrorOutputStart); } @Test - public void rpcWsRpcEnabledPropertyMustBeUsed() { - parseCommand("--rpc-ws-enabled"); + public void p2pHostAndPortOptionsAreRespected() { - verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); + final String host = "1.2.3.4"; + final int port = 1234; + parseCommand("--p2p-host", host, "--p2p-port", String.valueOf(port)); + + verify(mockRunnerBuilder).p2pAdvertisedHost(stringArgumentCaptor.capture()); + verify(mockRunnerBuilder).p2pListenPort(intArgumentCaptor.capture()); verify(mockRunnerBuilder).build(); - assertThat(wsRpcConfigArgumentCaptor.getValue().isEnabled()).isTrue(); + assertThat(stringArgumentCaptor.getValue()).isEqualTo(host); + assertThat(intArgumentCaptor.getValue()).isEqualTo(port); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void rpcWsOptionsRequiresServiceToBeEnabled() { - parseCommand( - "--rpc-ws-api", - "ETH,NET", - "--rpc-ws-host", - "0.0.0.0", - "--rpc-ws-port", - "1234", - "--rpc-ws-max-active-connections", - "77", - "--rpc-ws-max-frame-size", - "65535"); + public void p2pInterfaceOptionIsRespected() { - verifyOptionsConstraintLoggerCall( - "--rpc-ws-enabled", - "--rpc-ws-host", - "--rpc-ws-port", - "--rpc-ws-api", - "--rpc-ws-max-active-connections", - "--rpc-ws-max-frame-size"); + final String ip = "1.2.3.4"; + parseCommand("--p2p-interface", ip); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + + verify(mockRunnerBuilder).p2pListenInterface(stringArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(stringArgumentCaptor.getValue()).isEqualTo(ip); } @Test - public void rpcWsOptionsRequiresServiceToBeEnabledToml() throws IOException { - final Path toml = - createTempFile( - "toml", - "rpc-ws-api=[\"ETH\", \"NET\"]\n" - + "rpc-ws-host=\"0.0.0.0\"\n" - + "rpc-ws-port=1234\n" - + "rpc-ws-max-active-connections=77\n" - + "rpc-ws-max-frame-size=65535\n"); + public void p2pHostMayBeLocalhost() { - parseCommand("--config-file", toml.toString()); + final String host = "localhost"; + parseCommand("--p2p-host", host); - verifyOptionsConstraintLoggerCall( - "--rpc-ws-enabled", - "--rpc-ws-host", - "--rpc-ws-port", - "--rpc-ws-api", - "--rpc-ws-max-active-connections", - "--rpc-ws-max-frame-size"); + verify(mockRunnerBuilder).p2pAdvertisedHost(stringArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(stringArgumentCaptor.getValue()).isEqualTo(host); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void rpcWsApiPropertyMustBeUsed() { - final TestBesuCommand command = parseCommand("--rpc-ws-enabled", "--rpc-ws-api", "ETH, NET"); + public void p2pHostMayBeIPv6() { - assertThat(command).isNotNull(); - verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); + final String host = "2600:DB8::8545"; + parseCommand("--p2p-host", host); + + verify(mockRunnerBuilder).p2pAdvertisedHost(stringArgumentCaptor.capture()); verify(mockRunnerBuilder).build(); - assertThat(wsRpcConfigArgumentCaptor.getValue().getRpcApis()) - .containsExactlyInAnyOrder(ETH.name(), NET.name()); + assertThat(stringArgumentCaptor.getValue()).isEqualTo(host); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void rpcWsHostAndPortOptionMustBeUsed() { - final String host = "1.2.3.4"; - final int port = 1234; - parseCommand("--rpc-ws-enabled", "--rpc-ws-host", host, "--rpc-ws-port", String.valueOf(port)); + public void maxpeersOptionMustBeUsed() { - verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); + final int maxPeers = 123; + parseCommand("--max-peers", String.valueOf(maxPeers)); + + verify(mockControllerBuilder).maxPeers(intArgumentCaptor.capture()); + verify(mockControllerBuilder).build(); - assertThat(wsRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); - assertThat(wsRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port); + assertThat(intArgumentCaptor.getValue()).isEqualTo(maxPeers); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void rpcWsHostAndMayBeLocalhost() { - final String host = "localhost"; - parseCommand("--rpc-ws-enabled", "--rpc-ws-host", host); - - verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); + public void p2pPeerUpperBound_without_p2pPeerLowerBound_shouldSetMaxPeers() { - assertThat(wsRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); + final int maxPeers = 23; + parseCommand("--p2p-peer-upper-bound", String.valueOf(maxPeers)); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - @Test - public void rpcWsHostAndMayBeIPv6() { - final String host = "2600:DB8::8545"; - parseCommand("--rpc-ws-enabled", "--rpc-ws-host", host); + verify(mockControllerBuilder).maxPeers(intArgumentCaptor.capture()); + assertThat(intArgumentCaptor.getValue()).isEqualTo(maxPeers); - verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); verify(mockRunnerBuilder).build(); - assertThat(wsRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void metricsEnabledPropertyDefaultIsFalse() { - parseCommand(); + public void remoteConnectionsPercentageOptionMustBeUsed() { - verify(mockRunnerBuilder).metricsConfiguration(metricsConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); + final int remoteConnectionsPercentage = 12; + parseCommand( + "--remote-connections-limit-enabled=true", + "--remote-connections-max-percentage", + String.valueOf(remoteConnectionsPercentage)); - assertThat(metricsConfigArgumentCaptor.getValue().isEnabled()).isFalse(); + verify(mockControllerBuilder).maxRemotelyInitiatedPeers(intArgumentCaptor.capture()); + verify(mockControllerBuilder).build(); + + assertThat(intArgumentCaptor.getValue()) + .isEqualTo( + (int) Math.floor(25 * Fraction.fromPercentage(remoteConnectionsPercentage).getValue())); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void metricsEnabledPropertyMustBeUsed() { - parseCommand("--metrics-enabled"); + public void remoteConnectionsPercentageWithInvalidFormatMustFail() { - verify(mockRunnerBuilder).metricsConfiguration(metricsConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); + parseCommand( + "--remote-connections-limit-enabled", "--remote-connections-max-percentage", "invalid"); + Mockito.verifyNoInteractions(mockRunnerBuilder); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains( + "Invalid value for option '--remote-connections-max-percentage'", + "should be a number between 0 and 100 inclusive"); + } - assertThat(metricsConfigArgumentCaptor.getValue().isEnabled()).isTrue(); + @Test + public void remoteConnectionsPercentageWithOutOfRangeMustFail() { + parseCommand( + "--remote-connections-limit-enabled", "--remote-connections-max-percentage", "150"); + Mockito.verifyNoInteractions(mockRunnerBuilder); assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains( + "Invalid value for option '--remote-connections-max-percentage'", + "should be a number between 0 and 100 inclusive"); } @Test - public void metricsPushOptionsRequiresPushToBeEnabled() { - parseCommand( - "--metrics-push-host", - "0.0.0.0", - "--metrics-push-port", - "1234", - "--metrics-push-interval", - "2", - "--metrics-push-prometheus-job", - "job-name"); + public void syncMode_fast() { + parseCommand("--sync-mode", "FAST"); + verify(mockControllerBuilder).synchronizerConfiguration(syncConfigurationCaptor.capture()); - verifyOptionsConstraintLoggerCall( - "--metrics-push-enabled", - "--metrics-push-host", - "--metrics-push-port", - "--metrics-push-interval", - "--metrics-push-prometheus-job"); + final SynchronizerConfiguration syncConfig = syncConfigurationCaptor.getValue(); + assertThat(syncConfig.getSyncMode()).isEqualTo(SyncMode.FAST); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void metricsPushOptionsRequiresPushToBeEnabledToml() throws IOException { - final Path toml = - createTempFile( - "toml", - "metrics-push-host=\"0.0.0.0\"\n" - + "metrics-push-port=1234\n" - + "metrics-push-interval=2\n" - + "metrics-push-prometheus-job=\"job-name\"\n"); - - parseCommand("--config-file", toml.toString()); + public void syncMode_full_requires_bonsaiLimitTrieLogsToBeDisabled() { + parseCommand("--sync-mode", "FULL", "--bonsai-limit-trie-logs-enabled=false"); + verify(mockControllerBuilder).synchronizerConfiguration(syncConfigurationCaptor.capture()); - verifyOptionsConstraintLoggerCall( - "--metrics-push-enabled", - "--metrics-push-host", - "--metrics-push-port", - "--metrics-push-interval", - "--metrics-push-prometheus-job"); + final SynchronizerConfiguration syncConfig = syncConfigurationCaptor.getValue(); + assertThat(syncConfig.getSyncMode()).isEqualTo(SyncMode.FULL); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void metricsOptionsRequiresPullMetricsToBeEnabled() { - parseCommand("--metrics-host", "0.0.0.0", "--metrics-port", "1234"); - - verifyOptionsConstraintLoggerCall("--metrics-enabled", "--metrics-host", "--metrics-port"); + public void syncMode_invalid() { + parseCommand("--sync-mode", "bogus"); + Mockito.verifyNoInteractions(mockRunnerBuilder); assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains( + "Invalid value for option '--sync-mode': expected one of [FULL, FAST, SNAP, CHECKPOINT] (case-insensitive) but was 'bogus'"); } @Test - public void metricsOptionsRequiresPullMetricsToBeEnabledToml() throws IOException { - final Path toml = createTempFile("toml", "metrics-host=\"0.0.0.0\"\n" + "metrics-port=1234\n"); - - parseCommand("--config-file", toml.toString()); + public void syncMode_full_by_default_for_dev() { + parseCommand("--network", "dev"); + verify(mockControllerBuilder).synchronizerConfiguration(syncConfigurationCaptor.capture()); - verifyOptionsConstraintLoggerCall("--metrics-enabled", "--metrics-host", "--metrics-port"); + final SynchronizerConfiguration syncConfig = syncConfigurationCaptor.getValue(); + assertThat(syncConfig.getSyncMode()).isEqualTo(SyncMode.FULL); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void metricsHostAndPortOptionMustBeUsed() { - final String host = "1.2.3.4"; - final int port = 1234; - parseCommand( - "--metrics-enabled", "--metrics-host", host, "--metrics-port", String.valueOf(port)); - - verify(mockRunnerBuilder).metricsConfiguration(metricsConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); + public void storage_bonsai_by_default() { + parseCommand(); + verify(mockControllerBuilder) + .dataStorageConfiguration(dataStorageConfigurationArgumentCaptor.capture()); - assertThat(metricsConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); - assertThat(metricsConfigArgumentCaptor.getValue().getPort()).isEqualTo(port); + final DataStorageConfiguration dataStorageConfig = + dataStorageConfigurationArgumentCaptor.getValue(); + assertThat(dataStorageConfig.getDataStorageFormat()).isEqualTo(BONSAI); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void metricsHostMayBeLocalhost() { - final String host = "localhost"; - parseCommand("--metrics-enabled", "--metrics-host", host); - - verify(mockRunnerBuilder).metricsConfiguration(metricsConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); + public void syncMode_snap_by_default() { + parseCommand(); + verify(mockControllerBuilder).synchronizerConfiguration(syncConfigurationCaptor.capture()); - assertThat(metricsConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); + final SynchronizerConfiguration syncConfig = syncConfigurationCaptor.getValue(); + assertThat(syncConfig.getSyncMode()).isEqualTo(SyncMode.SNAP); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void metricsHostMayBeIPv6() { - final String host = "2600:DB8::8545"; - parseCommand("--metrics-enabled", "--metrics-host", host); - - verify(mockRunnerBuilder).metricsConfiguration(metricsConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); + public void helpShouldDisplayFastSyncOptions() { + parseCommand("--help"); - assertThat(metricsConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); + Mockito.verifyNoInteractions(mockRunnerBuilder); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandOutput.toString(UTF_8)).contains("--fast-sync-min-peers"); + // whitelist is now a hidden option + assertThat(commandOutput.toString(UTF_8)).doesNotContain("whitelist"); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void metricsCategoryPropertyMustBeUsed() { - parseCommand("--metrics-enabled", "--metrics-category", StandardMetricCategory.JVM.toString()); - - verify(mockRunnerBuilder).metricsConfiguration(metricsConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(metricsConfigArgumentCaptor.getValue().getMetricCategories()) - .containsExactly(StandardMetricCategory.JVM); + public void checkValidDefaultFastSyncMinPeers() { + parseCommand("--sync-mode", "FAST"); + verify(mockControllerBuilder).synchronizerConfiguration(syncConfigurationCaptor.capture()); + final SynchronizerConfiguration syncConfig = syncConfigurationCaptor.getValue(); + assertThat(syncConfig.getSyncMode()).isEqualTo(SyncMode.FAST); + assertThat(syncConfig.getSyncMinimumPeerCount()).isEqualTo(5); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void metricsPushEnabledPropertyMustBeUsed() { - parseCommand("--metrics-push-enabled"); - - verify(mockRunnerBuilder).metricsConfiguration(metricsConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - - assertThat(metricsConfigArgumentCaptor.getValue().isPushEnabled()).isTrue(); + public void parsesValidFastSyncMinPeersOption() { + parseCommand("--sync-mode", "FAST", "--fast-sync-min-peers", "11"); + verify(mockControllerBuilder).synchronizerConfiguration(syncConfigurationCaptor.capture()); + final SynchronizerConfiguration syncConfig = syncConfigurationCaptor.getValue(); + assertThat(syncConfig.getSyncMode()).isEqualTo(SyncMode.FAST); + assertThat(syncConfig.getSyncMinimumPeerCount()).isEqualTo(11); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void metricsPushHostAndPushPortOptionMustBeUsed() { - final String host = "1.2.3.4"; - final int port = 1234; - parseCommand( - "--metrics-push-enabled", - "--metrics-push-host", - host, - "--metrics-push-port", - String.valueOf(port)); + public void parsesValidSnapSyncMinPeersOption() { + parseCommand("--sync-mode", "SNAP", "--sync-min-peers", "11"); + verify(mockControllerBuilder).synchronizerConfiguration(syncConfigurationCaptor.capture()); - verify(mockRunnerBuilder).metricsConfiguration(metricsConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); + final SynchronizerConfiguration syncConfig = syncConfigurationCaptor.getValue(); + assertThat(syncConfig.getSyncMode()).isEqualTo(SyncMode.SNAP); + assertThat(syncConfig.getSyncMinimumPeerCount()).isEqualTo(11); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } - assertThat(metricsConfigArgumentCaptor.getValue().getPushHost()).isEqualTo(host); - assertThat(metricsConfigArgumentCaptor.getValue().getPushPort()).isEqualTo(port); + @Test + public void parsesValidSyncMinPeersOption() { + parseCommand("--sync-mode", "FAST", "--sync-min-peers", "11"); + verify(mockControllerBuilder).synchronizerConfiguration(syncConfigurationCaptor.capture()); + final SynchronizerConfiguration syncConfig = syncConfigurationCaptor.getValue(); + assertThat(syncConfig.getSyncMode()).isEqualTo(SyncMode.FAST); + assertThat(syncConfig.getSyncMinimumPeerCount()).isEqualTo(11); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void metricsPushHostMayBeLocalhost() { - final String host = "localhost"; - parseCommand("--metrics-push-enabled", "--metrics-push-host", host); + public void parsesInvalidFastSyncMinPeersOptionWrongFormatShouldFail() { - verify(mockRunnerBuilder).metricsConfiguration(metricsConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); + parseCommand("--sync-mode", "FAST", "--fast-sync-min-peers", "ten"); + Mockito.verifyNoInteractions(mockRunnerBuilder); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Invalid value for option '--fast-sync-min-peers': 'ten' is not an int"); + } - assertThat(metricsConfigArgumentCaptor.getValue().getPushHost()).isEqualTo(host); + @Test + public void netRestrictParsedCorrectly() { + final String subnet1 = "127.0.0.1/24"; + final String subnet2 = "10.0.0.1/24"; + parseCommand("--net-restrict", String.join(",", subnet1, subnet2)); + verify(mockRunnerBuilder).allowedSubnets(allowedSubnetsArgumentCaptor.capture()); + assertThat(allowedSubnetsArgumentCaptor.getValue().size()).isEqualTo(2); + assertThat(allowedSubnetsArgumentCaptor.getValue().get(0).getCidrSignature()) + .isEqualTo(subnet1); + assertThat(allowedSubnetsArgumentCaptor.getValue().get(1).getCidrSignature()) + .isEqualTo(subnet2); + } - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + @Test + public void netRestrictInvalidShouldFail() { + final String subnet = "127.0.0.1/abc"; + parseCommand("--net-restrict", subnet); + Mockito.verifyNoInteractions(mockRunnerBuilder); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Invalid value for option '--net-restrict'"); } @Test - public void metricsPushIntervalMustBeUsed() { - parseCommand("--metrics-push-enabled", "--metrics-push-interval", "42"); + public void ethStatsOptionIsParsedCorrectly() { + final String url = "besu-node:secret@host:443"; + parseCommand("--ethstats", url); + verify(mockRunnerBuilder).ethstatsOptions(ethstatsOptionsArgumentCaptor.capture()); + assertThat(ethstatsOptionsArgumentCaptor.getValue().getEthstatsUrl()).isEqualTo(url); + } - verify(mockRunnerBuilder).metricsConfiguration(metricsConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); + @Test + public void ethStatsContactOptionIsParsedCorrectly() { + final String contact = "contact@mail.net"; + parseCommand("--ethstats", "besu-node:secret@host:443", "--ethstats-contact", contact); + verify(mockRunnerBuilder).ethstatsOptions(ethstatsOptionsArgumentCaptor.capture()); + assertThat(ethstatsOptionsArgumentCaptor.getValue().getEthstatsContact()).isEqualTo(contact); + } - assertThat(metricsConfigArgumentCaptor.getValue().getPushInterval()).isEqualTo(42); + @Test + public void ethStatsContactOptionCannotBeUsedWithoutEthStatsServerProvided() { + parseCommand("--ethstats-contact", "besu-updated"); + Mockito.verifyNoInteractions(mockRunnerBuilder); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains( + "The `--ethstats-contact` requires ethstats server URL to be provided. Either remove --ethstats-contact or provide a URL (via --ethstats=nodename:secret@host:port)"); + } + + @Test + public void bonsaiLimitTrieLogsEnabledByDefault() { + parseCommand(); + verify(mockControllerBuilder) + .dataStorageConfiguration(dataStorageConfigurationArgumentCaptor.capture()); + final DataStorageConfiguration dataStorageConfiguration = + dataStorageConfigurationArgumentCaptor.getValue(); + assertThat(dataStorageConfiguration.getDataStorageFormat()).isEqualTo(BONSAI); + assertThat(dataStorageConfiguration.getBonsaiLimitTrieLogsEnabled()).isTrue(); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void metricsPrometheusJobMustBeUsed() { - parseCommand("--metrics-push-enabled", "--metrics-push-prometheus-job", "besu-command-test"); - - verify(mockRunnerBuilder).metricsConfiguration(metricsConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); + public void bonsaiLimitTrieLogsDisabledWhenFullSyncEnabled() { + parseCommand("--sync-mode=FULL"); - assertThat(metricsConfigArgumentCaptor.getValue().getPrometheusJob()) - .isEqualTo("besu-command-test"); + verify(mockControllerBuilder) + .dataStorageConfiguration(dataStorageConfigurationArgumentCaptor.capture()); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); + final DataStorageConfiguration dataStorageConfiguration = + dataStorageConfigurationArgumentCaptor.getValue(); + assertThat(dataStorageConfiguration.getDataStorageFormat()).isEqualTo(BONSAI); + assertThat(dataStorageConfiguration.getBonsaiLimitTrieLogsEnabled()).isFalse(); + verify(mockLogger) + .warn( + "Forcing {}, since it cannot be enabled with --sync-mode={} and --data-storage-format={}.", + "--bonsai-limit-trie-logs-enabled=false", + SyncMode.FULL, + DataStorageFormat.BONSAI); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void metricsAndMetricsPushMustNotBeUsedTogether() { - parseCommand("--metrics-enabled", "--metrics-push-enabled"); + public void parsesInvalidWhenFullSyncAndBonsaiLimitTrieLogsExplicitlyTrue() { + parseCommand("--sync-mode=FULL", "--bonsai-limit-trie-logs-enabled=true"); Mockito.verifyNoInteractions(mockRunnerBuilder); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)) - .startsWith("--metrics-enabled option and --metrics-push-enabled option can't be used"); + .contains( + "Cannot enable --bonsai-limit-trie-logs-enabled with --sync-mode=FULL and --data-storage-format=BONSAI. You must set --bonsai-limit-trie-logs-enabled=false or use a different sync-mode"); } @Test - public void colorCanBeEnabledOrDisabledExplicitly() { - Stream.of(true, false) - .forEach( - bool -> { - parseCommand("--color-enabled", bool.toString()); - assertThat(BesuCommand.getColorEnabled()).contains(bool); - }); - } - - @Ignore - public void pruningIsEnabledIfSyncModeIsFast() { - parseCommand("--sync-mode", "FAST"); - - verify(mockControllerBuilder).isPruningEnabled(true); - verify(mockControllerBuilder).build(); + public void parsesValidBonsaiHistoricalBlockLimitOption() { + parseCommand( + "--bonsai-limit-trie-logs-enabled=false", + "--data-storage-format", + "BONSAI", + "--bonsai-historical-block-limit", + "11"); + verify(mockControllerBuilder) + .dataStorageConfiguration(dataStorageConfigurationArgumentCaptor.capture()); + final DataStorageConfiguration dataStorageConfiguration = + dataStorageConfigurationArgumentCaptor.getValue(); + assertThat(dataStorageConfiguration.getDataStorageFormat()).isEqualTo(BONSAI); + assertThat(dataStorageConfiguration.getBonsaiMaxLayersToLoad()).isEqualTo(11); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } - @Ignore - public void pruningIsDisabledIfSyncModeIsFull() { - parseCommand("--sync-mode", "FULL"); + @Test + public void parsesInvalidBonsaiHistoricalBlockLimitOption() { - verify(mockControllerBuilder).isPruningEnabled(false); - verify(mockControllerBuilder).build(); + parseCommand("--data-storage-format", "BONSAI", "--bonsai-maximum-back-layers-to-load", "ten"); + Mockito.verifyNoInteractions(mockRunnerBuilder); assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains( + "Invalid value for option '--bonsai-maximum-back-layers-to-load': 'ten' is not a long"); } @Test - public void pruningEnabledExplicitly() { - parseCommand("--pruning-enabled", "--sync-mode=FULL"); + public void dnsEnabledOptionIsParsedCorrectly() { + final TestBesuCommand besuCommand = parseCommand("--Xdns-enabled", "true"); - verify(mockControllerBuilder).isPruningEnabled(true); - verify(mockControllerBuilder).build(); + assertThat(besuCommand.getEnodeDnsConfiguration().dnsEnabled()).isTrue(); + assertThat(besuCommand.getEnodeDnsConfiguration().updateEnabled()).isFalse(); + } - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + @Test + public void versionCompatibilityProtectionTrueOptionIsParsedCorrectly() { + final TestBesuCommand besuCommand = parseCommand("--version-compatibility-protection", "true"); + + assertThat(besuCommand.getVersionCompatibilityProtection()).isTrue(); } - @Ignore - public void pruningDisabledExplicitly() { - parseCommand("--pruning-enabled=false", "--sync-mode=FAST"); + @Test + public void dnsUpdateEnabledOptionIsParsedCorrectly() { + final TestBesuCommand besuCommand = + parseCommand("--Xdns-enabled", "true", "--Xdns-update-enabled", "true"); - verify(mockControllerBuilder).isPruningEnabled(false); - verify(mockControllerBuilder).build(); + assertThat(besuCommand.getEnodeDnsConfiguration().dnsEnabled()).isTrue(); + assertThat(besuCommand.getEnodeDnsConfiguration().updateEnabled()).isTrue(); + } + @Test + public void dnsUpdateEnabledOptionCannotBeUsedWithoutDnsEnabled() { + parseCommand("--Xdns-update-enabled", "true"); + Mockito.verifyNoInteractions(mockRunnerBuilder); assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains( + "The `--Xdns-update-enabled` requires dns to be enabled. Either remove --Xdns-update-enabled or specify dns is enabled (--Xdns-enabled)"); } @Test - public void pruningDisabledByDefault() { + public void rpcHttpEnabledPropertyDefaultIsFalse() { parseCommand(); - verify(mockControllerBuilder).isPruningEnabled(false); + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().isEnabled()).isFalse(); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void pruningParametersAreCaptured() throws Exception { - parseCommand( - "--pruning-enabled", "--pruning-blocks-retained=15", "--pruning-block-confirmations=4"); + public void graphQLHttpEnabledPropertyDefaultIsFalse() { + parseCommand(); - final ArgumentCaptor pruningArg = - ArgumentCaptor.forClass(PrunerConfiguration.class); + verify(mockRunnerBuilder).graphQLConfiguration(graphQLConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); - verify(mockControllerBuilder).pruningConfiguration(pruningArg.capture()); - verify(mockControllerBuilder).build(); + assertThat(graphQLConfigArgumentCaptor.getValue().isEnabled()).isFalse(); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - assertThat(pruningArg.getValue().getBlocksRetained()).isEqualTo(15); - assertThat(pruningArg.getValue().getBlockConfirmations()).isEqualTo(4); } @Test - public void devModeOptionMustBeUsed() throws Exception { - parseCommand("--network", "dev"); - - final ArgumentCaptor networkArg = - ArgumentCaptor.forClass(EthNetworkConfig.class); + public void rpcApisSupportsEngine() { + parseCommand("--rpc-http-api", "ENGINE", "--rpc-http-enabled"); - verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any(), any()); - verify(mockControllerBuilder).build(); + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); - assertThat(networkArg.getValue()).isEqualTo(EthNetworkConfig.getNetworkConfig(DEV)); + assertThat(jsonRpcConfigArgumentCaptor.getValue().getRpcApis()) + .containsExactlyInAnyOrder(ENGINE.name()); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void goerliValuesAreUsed() { - parseCommand("--network", "goerli"); - - final ArgumentCaptor networkArg = - ArgumentCaptor.forClass(EthNetworkConfig.class); - - verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any(), any()); - verify(mockControllerBuilder).build(); - - assertThat(networkArg.getValue()).isEqualTo(EthNetworkConfig.getNetworkConfig(GOERLI)); - + public void engineApiAuthOptions() { + parseCommand("--rpc-http-enabled", "--engine-jwt-secret", "/tmp/fakeKey.hex"); + verify(mockRunnerBuilder).engineJsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + assertThat(jsonRpcConfigArgumentCaptor.getValue().isAuthenticationEnabled()).isTrue(); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - - verify(mockLogger, never()).warn(contains("Goerli is deprecated and will be shutdown")); } @Test - public void futureEipsValuesAreUsed() { - parseCommand("--network", "future_eips"); - - final ArgumentCaptor networkArg = - ArgumentCaptor.forClass(EthNetworkConfig.class); - - verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any(), any()); - verify(mockControllerBuilder).build(); - - assertThat(networkArg.getValue()).isEqualTo(EthNetworkConfig.getNetworkConfig(FUTURE_EIPS)); - + public void engineApiDisableAuthOptions() { + parseCommand( + "--rpc-http-enabled", "--engine-jwt-disabled", "--engine-jwt-secret", "/tmp/fakeKey.hex"); + verify(mockRunnerBuilder).engineJsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + assertThat(jsonRpcConfigArgumentCaptor.getValue().isAuthenticationEnabled()).isFalse(); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void experimentalEipsValuesAreUsed() { - parseCommand("--network", "experimental_eips"); - - final ArgumentCaptor networkArg = - ArgumentCaptor.forClass(EthNetworkConfig.class); + public void metricsEnabledPropertyDefaultIsFalse() { + parseCommand(); - verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any(), any()); - verify(mockControllerBuilder).build(); + verify(mockRunnerBuilder).metricsConfiguration(metricsConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); - assertThat(networkArg.getValue()) - .isEqualTo(EthNetworkConfig.getNetworkConfig(EXPERIMENTAL_EIPS)); + assertThat(metricsConfigArgumentCaptor.getValue().isEnabled()).isFalse(); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void sepoliaValuesAreUsed() { - parseCommand("--network", "sepolia"); - - final ArgumentCaptor networkArg = - ArgumentCaptor.forClass(EthNetworkConfig.class); + public void metricsEnabledPropertyMustBeUsed() { + parseCommand("--metrics-enabled"); - verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any(), any()); - verify(mockControllerBuilder).build(); + verify(mockRunnerBuilder).metricsConfiguration(metricsConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); - assertThat(networkArg.getValue()).isEqualTo(EthNetworkConfig.getNetworkConfig(SEPOLIA)); + assertThat(metricsConfigArgumentCaptor.getValue().isEnabled()).isTrue(); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - - verify(mockLogger, never()).warn(contains("Sepolia is deprecated and will be shutdown")); } @Test - public void holeskyValuesAreUsed() { - parseCommand("--network", "holesky"); - - final ArgumentCaptor networkArg = - ArgumentCaptor.forClass(EthNetworkConfig.class); - - verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any(), any()); - verify(mockControllerBuilder).build(); + public void metricsPushOptionsRequiresPushToBeEnabled() { + parseCommand( + "--metrics-push-host", + "0.0.0.0", + "--metrics-push-port", + "1234", + "--metrics-push-interval", + "2", + "--metrics-push-prometheus-job", + "job-name"); - assertThat(networkArg.getValue()).isEqualTo(EthNetworkConfig.getNetworkConfig(HOLESKY)); + verifyOptionsConstraintLoggerCall( + "--metrics-push-enabled", + "--metrics-push-host", + "--metrics-push-port", + "--metrics-push-interval", + "--metrics-push-prometheus-job"); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - - verify(mockLogger, never()).warn(contains("Holesky is deprecated and will be shutdown")); } @Test - public void classicValuesAreUsed() throws Exception { - parseCommand("--network", "classic"); - - final ArgumentCaptor networkArg = - ArgumentCaptor.forClass(EthNetworkConfig.class); + public void metricsPushOptionsRequiresPushToBeEnabledToml() throws IOException { + final Path toml = + createTempFile( + "toml", + "metrics-push-host=\"0.0.0.0\"\n" + + "metrics-push-port=1234\n" + + "metrics-push-interval=2\n" + + "metrics-push-prometheus-job=\"job-name\"\n"); - verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any(), any()); - verify(mockControllerBuilder).build(); + parseCommand("--config-file", toml.toString()); - assertThat(networkArg.getValue()).isEqualTo(EthNetworkConfig.getNetworkConfig(CLASSIC)); + verifyOptionsConstraintLoggerCall( + "--metrics-push-enabled", + "--metrics-push-host", + "--metrics-push-port", + "--metrics-push-interval", + "--metrics-push-prometheus-job"); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void mordorValuesAreUsed() throws Exception { - parseCommand("--network", "mordor"); - - final ArgumentCaptor networkArg = - ArgumentCaptor.forClass(EthNetworkConfig.class); - - verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any(), any()); - verify(mockControllerBuilder).build(); + public void metricsOptionsRequiresPullMetricsToBeEnabled() { + parseCommand("--metrics-host", "0.0.0.0", "--metrics-port", "1234"); - assertThat(networkArg.getValue()).isEqualTo(EthNetworkConfig.getNetworkConfig(MORDOR)); + verifyOptionsConstraintLoggerCall("--metrics-enabled", "--metrics-host", "--metrics-port"); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void goerliValuesCanBeOverridden() throws Exception { - networkValuesCanBeOverridden("goerli"); - } - - @Test - public void futureEipsValuesCanBeOverridden() throws Exception { - networkValuesCanBeOverridden("future_eips"); - } + public void metricsOptionsRequiresPullMetricsToBeEnabledToml() throws IOException { + final Path toml = createTempFile("toml", "metrics-host=\"0.0.0.0\"\n" + "metrics-port=1234\n"); - @Test - public void experimentalEipsValuesCanBeOverridden() throws Exception { - networkValuesCanBeOverridden("experimental_eips"); - } + parseCommand("--config-file", toml.toString()); - @Test - public void devValuesCanBeOverridden() throws Exception { - networkValuesCanBeOverridden("dev"); - } + verifyOptionsConstraintLoggerCall("--metrics-enabled", "--metrics-host", "--metrics-port"); - @Test - public void classicValuesCanBeOverridden() throws Exception { - networkValuesCanBeOverridden("classic"); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void mordorValuesCanBeOverridden() throws Exception { - networkValuesCanBeOverridden("mordor"); - } - - private void networkValuesCanBeOverridden(final String network) throws Exception { + public void metricsHostAndPortOptionMustBeUsed() { + final String host = "1.2.3.4"; + final int port = 1234; parseCommand( - "--network", - network, - "--network-id", - "1234567", - "--bootnodes", - String.join(",", VALID_ENODE_STRINGS)); - - final ArgumentCaptor networkArg = - ArgumentCaptor.forClass(EthNetworkConfig.class); + "--metrics-enabled", "--metrics-host", host, "--metrics-port", String.valueOf(port)); - verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any(), any()); - verify(mockControllerBuilder).build(); + verify(mockRunnerBuilder).metricsConfiguration(metricsConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); - assertThat(networkArg.getValue().getBootNodes()) - .isEqualTo( - Stream.of(VALID_ENODE_STRINGS) - .map(EnodeURLImpl::fromString) - .collect(Collectors.toList())); - assertThat(networkArg.getValue().getNetworkId()).isEqualTo(1234567); + assertThat(metricsConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); + assertThat(metricsConfigArgumentCaptor.getValue().getPort()).isEqualTo(port); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void fullCLIOptionsShown() { - parseCommand("--help"); - - Mockito.verifyNoInteractions(mockRunnerBuilder); - - assertThat(commandOutput.toString(UTF_8)).contains("--config-file"); - assertThat(commandOutput.toString(UTF_8)).contains("--data-path"); - assertThat(commandOutput.toString(UTF_8)).contains("--genesis-file"); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void mustUseEnclaveUriAndOptions() { - final URL configFile = this.getClass().getResource("/orion_publickey.pub"); + public void metricsHostMayBeLocalhost() { + final String host = "localhost"; + parseCommand("--metrics-enabled", "--metrics-host", host); - parseCommand( - "--privacy-enabled", - "--privacy-url", - ENCLAVE_URI, - "--privacy-public-key-file", - configFile.getPath(), - "--min-gas-price", - "0"); - - final ArgumentCaptor enclaveArg = - ArgumentCaptor.forClass(PrivacyParameters.class); - - verify(mockControllerBuilder).privacyParameters(enclaveArg.capture()); - verify(mockControllerBuilder).build(); + verify(mockRunnerBuilder).metricsConfiguration(metricsConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); - assertThat(enclaveArg.getValue().isEnabled()).isEqualTo(true); - assertThat(enclaveArg.getValue().getEnclaveUri()).isEqualTo(URI.create(ENCLAVE_URI)); - assertThat(enclaveArg.getValue().getPrivacyUserId()).isEqualTo(ENCLAVE_PUBLIC_KEY); + assertThat(metricsConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void privacyOptionsRequiresServiceToBeEnabled() { - - final File file = new File("./specific/enclavePublicKey"); - file.deleteOnExit(); + public void metricsHostMayBeIPv6() { + final String host = "2600:DB8::8545"; + parseCommand("--metrics-enabled", "--metrics-host", host); - parseCommand("--privacy-url", ENCLAVE_URI, "--privacy-public-key-file", file.toString()); + verify(mockRunnerBuilder).metricsConfiguration(metricsConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); - verifyMultiOptionsConstraintLoggerCall( - "--privacy-url and/or --privacy-public-key-file ignored because none of --privacy-enabled was defined."); + assertThat(metricsConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void privacyWithoutPrivacyPublicKeyFails() { - parseCommand("--privacy-enabled", "--privacy-url", ENCLAVE_URI); - - assertThat(commandErrorOutput.toString(UTF_8)) - .startsWith("Please specify Enclave public key file path to enable privacy"); - } - - @Test - public void mustVerifyPrivacyIsDisabled() { - parseCommand(); + public void metricsCategoryPropertyMustBeUsed() { + parseCommand("--metrics-enabled", "--metrics-category", StandardMetricCategory.JVM.toString()); - final ArgumentCaptor enclaveArg = - ArgumentCaptor.forClass(PrivacyParameters.class); + verify(mockRunnerBuilder).metricsConfiguration(metricsConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); - verify(mockControllerBuilder).privacyParameters(enclaveArg.capture()); - verify(mockControllerBuilder).build(); + assertThat(metricsConfigArgumentCaptor.getValue().getMetricCategories()) + .containsExactly(StandardMetricCategory.JVM); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - assertThat(enclaveArg.getValue().isEnabled()).isEqualTo(false); - } - - @Test - public void privacyMultiTenancyIsConfiguredWhenConfiguredWithNecessaryOptions() { - parseCommand( - "--privacy-enabled", - "--rpc-http-authentication-enabled", - "--privacy-multi-tenancy-enabled", - "--rpc-http-authentication-jwt-public-key-file", - "/non/existent/file", - "--min-gas-price", - "0"); - - final ArgumentCaptor privacyParametersArgumentCaptor = - ArgumentCaptor.forClass(PrivacyParameters.class); - - verify(mockControllerBuilder).privacyParameters(privacyParametersArgumentCaptor.capture()); - verify(mockControllerBuilder).build(); - - assertThat(privacyParametersArgumentCaptor.getValue().isMultiTenancyEnabled()).isTrue(); } @Test - public void privacyMultiTenancyWithoutAuthenticationFails() { - parseCommand( - "--privacy-enabled", - "--privacy-multi-tenancy-enabled", - "--rpc-http-authentication-jwt-public-key-file", - "/non/existent/file"); + public void metricsPushEnabledPropertyMustBeUsed() { + parseCommand("--metrics-push-enabled"); - assertThat(commandErrorOutput.toString(UTF_8)) - .startsWith( - "Privacy multi-tenancy requires either http authentication to be enabled or WebSocket authentication to be enabled"); - } + verify(mockRunnerBuilder).metricsConfiguration(metricsConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); - @Test - public void privacyMultiTenancyWithPrivacyPublicKeyFileFails() { - parseCommand( - "--privacy-enabled", - "--rpc-http-authentication-enabled", - "--privacy-multi-tenancy-enabled", - "--rpc-http-authentication-jwt-public-key-file", - "/non/existent/file", - "--privacy-public-key-file", - ENCLAVE_PUBLIC_KEY_PATH); + assertThat(metricsConfigArgumentCaptor.getValue().isPushEnabled()).isTrue(); - assertThat(commandErrorOutput.toString(UTF_8)) - .startsWith("Privacy multi-tenancy and privacy public key cannot be used together"); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void flexiblePrivacyGroupEnabledFlagDefaultValueIsFalse() { + public void metricsPushHostAndPushPortOptionMustBeUsed() { + final String host = "1.2.3.4"; + final int port = 1234; parseCommand( - "--privacy-enabled", - "--privacy-public-key-file", - ENCLAVE_PUBLIC_KEY_PATH, - "--min-gas-price", - "0"); + "--metrics-push-enabled", + "--metrics-push-host", + host, + "--metrics-push-port", + String.valueOf(port)); - final ArgumentCaptor privacyParametersArgumentCaptor = - ArgumentCaptor.forClass(PrivacyParameters.class); + verify(mockRunnerBuilder).metricsConfiguration(metricsConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); - verify(mockControllerBuilder).privacyParameters(privacyParametersArgumentCaptor.capture()); - verify(mockControllerBuilder).build(); + assertThat(metricsConfigArgumentCaptor.getValue().getPushHost()).isEqualTo(host); + assertThat(metricsConfigArgumentCaptor.getValue().getPushPort()).isEqualTo(port); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - - final PrivacyParameters privacyParameters = privacyParametersArgumentCaptor.getValue(); - assertThat(privacyParameters.isFlexiblePrivacyGroupsEnabled()).isEqualTo(false); } @Test - public void onchainPrivacyGroupEnabledFlagValueIsSet() { - parseCommand( - "--privacy-enabled", - "--privacy-public-key-file", - ENCLAVE_PUBLIC_KEY_PATH, - "--privacy-onchain-groups-enabled", - "--min-gas-price", - "0"); + public void metricsPushHostMayBeLocalhost() { + final String host = "localhost"; + parseCommand("--metrics-push-enabled", "--metrics-push-host", host); - final ArgumentCaptor privacyParametersArgumentCaptor = - ArgumentCaptor.forClass(PrivacyParameters.class); + verify(mockRunnerBuilder).metricsConfiguration(metricsConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); - verify(mockControllerBuilder).privacyParameters(privacyParametersArgumentCaptor.capture()); - verify(mockControllerBuilder).build(); + assertThat(metricsConfigArgumentCaptor.getValue().getPushHost()).isEqualTo(host); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - - final PrivacyParameters privacyParameters = privacyParametersArgumentCaptor.getValue(); - assertThat(privacyParameters.isFlexiblePrivacyGroupsEnabled()).isEqualTo(true); - } - - @Test - public void onchainPrivacyGroupEnabledOptionIsDeprecated() { - parseCommand( - "--privacy-enabled", - "--privacy-public-key-file", - ENCLAVE_PUBLIC_KEY_PATH, - "--privacy-onchain-groups-enabled", - "--min-gas-price", - "0"); - - verify(mockLogger) - .warn( - DEPRECATION_WARNING_MSG, - "--privacy-onchain-groups-enabled", - "--privacy-flexible-groups-enabled"); } @Test - public void flexiblePrivacyGroupEnabledFlagValueIsSet() { - parseCommand( - "--privacy-enabled", - "--privacy-public-key-file", - ENCLAVE_PUBLIC_KEY_PATH, - "--privacy-flexible-groups-enabled", - "--min-gas-price", - "0"); + public void metricsPushIntervalMustBeUsed() { + parseCommand("--metrics-push-enabled", "--metrics-push-interval", "42"); - final ArgumentCaptor privacyParametersArgumentCaptor = - ArgumentCaptor.forClass(PrivacyParameters.class); + verify(mockRunnerBuilder).metricsConfiguration(metricsConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); - verify(mockControllerBuilder).privacyParameters(privacyParametersArgumentCaptor.capture()); - verify(mockControllerBuilder).build(); + assertThat(metricsConfigArgumentCaptor.getValue().getPushInterval()).isEqualTo(42); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - - final PrivacyParameters privacyParameters = privacyParametersArgumentCaptor.getValue(); - assertThat(privacyParameters.isFlexiblePrivacyGroupsEnabled()).isEqualTo(true); } @Test - public void privateMarkerTransactionSigningKeyFileRequiredIfMinGasPriceNonZero() { - parseCommand( - "--privacy-enabled", - "--privacy-public-key-file", - ENCLAVE_PUBLIC_KEY_PATH, - "--min-gas-price", - "1"); - - assertThat(commandErrorOutput.toString(UTF_8)) - .startsWith( - "Not a free gas network. --privacy-marker-transaction-signing-key-file must be specified"); - } - - @Test - public void - privateMarkerTransactionSigningKeyFileNotRequiredIfMinGasPriceNonZeroAndUsingPluginPrivateMarkerTransactionFactory() { + public void metricsPrometheusJobMustBeUsed() { + parseCommand("--metrics-push-enabled", "--metrics-push-prometheus-job", "besu-command-test"); - when(privacyPluginService.getPrivateMarkerTransactionFactory()) - .thenReturn(mock(PrivateMarkerTransactionFactory.class)); + verify(mockRunnerBuilder).metricsConfiguration(metricsConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); - parseCommand( - "--privacy-enabled", - "--privacy-public-key-file", - ENCLAVE_PUBLIC_KEY_PATH, - "--min-gas-price", - "1"); + assertThat(metricsConfigArgumentCaptor.getValue().getPrometheusJob()) + .isEqualTo("besu-command-test"); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void - privateMarkerTransactionSigningKeyFileNotCanNotBeUsedWithPluginPrivateMarkerTransactionFactory() - throws IOException { - when(privacyPluginService.getPrivateMarkerTransactionFactory()) - .thenReturn(mock(PrivateMarkerTransactionFactory.class)); - final Path toml = - createTempFile( - "key", - "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63".getBytes(UTF_8)); + public void metricsAndMetricsPushMustNotBeUsedTogether() { + parseCommand("--metrics-enabled", "--metrics-push-enabled"); - parseCommand( - "--privacy-enabled", - "--privacy-public-key-file", - ENCLAVE_PUBLIC_KEY_PATH, - "--privacy-marker-transaction-signing-key-file", - toml.toString()); + Mockito.verifyNoInteractions(mockRunnerBuilder); assertThat(commandOutput.toString(UTF_8)).isEmpty(); - // assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)) - .startsWith( - "--privacy-marker-transaction-signing-key-file can not be used in conjunction with a plugin that specifies"); + .startsWith("--metrics-enabled option and --metrics-push-enabled option can't be used"); } @Test - public void mustProvidePayloadWhenPrivacyPluginEnabled() { - parseCommand("--privacy-enabled", "--Xprivacy-plugin-enabled", "--min-gas-price", "0"); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .startsWith( - "No Payload Provider has been provided. You must register one when enabling privacy plugin!"); + public void colorCanBeEnabledOrDisabledExplicitly() { + Stream.of(true, false) + .forEach( + bool -> { + parseCommand("--color-enabled", bool.toString()); + assertThat(BesuCommand.getColorEnabled()).contains(bool); + }); } @Test - public void canNotUseFlexiblePrivacyWhenPrivacyPluginEnabled() { - parseCommand( - "--privacy-enabled", - "--Xprivacy-plugin-enabled", - "--min-gas-price", - "0", - "--privacy-flexible-groups-enabled"); + public void devModeOptionMustBeUsed() { + parseCommand("--network", "dev"); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .startsWith( - "No Payload Provider has been provided. You must register one when enabling privacy plugin!"); - } - - private static String escapeTomlString(final String s) { - return StringEscapeUtils.escapeJava(s); - } - - /** - * Check logger calls - * - *

Here we check the calls to logger and not the result of the log line as we don't test the - * logger itself but the fact that we call it. - * - * @param dependentOptions the string representing the list of dependent options names - * @param mainOption the main option name - */ - private void verifyOptionsConstraintLoggerCall( - final String mainOption, final String... dependentOptions) { - verify(mockLogger, atLeast(1)) - .warn( - stringArgumentCaptor.capture(), - stringArgumentCaptor.capture(), - stringArgumentCaptor.capture()); - assertThat(stringArgumentCaptor.getAllValues().get(0)).isEqualTo(DEPENDENCY_WARNING_MSG); + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); - for (final String option : dependentOptions) { - assertThat(stringArgumentCaptor.getAllValues().get(1)).contains(option); - } + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); - assertThat(stringArgumentCaptor.getAllValues().get(2)).isEqualTo(mainOption); - } + assertThat(networkArg.getValue()).isEqualTo(EthNetworkConfig.getNetworkConfig(DEV)); - /** - * Check logger calls - * - *

Here we check the calls to logger and not the result of the log line as we don't test the - * logger itself but the fact that we call it. - * - * @param stringToLog the string that is logged - */ - private void verifyMultiOptionsConstraintLoggerCall(final String stringToLog) { - verify(mockLogger, atLeast(1)).warn(stringToLog); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void privacyWithFastSyncMustError() { - parseCommand("--sync-mode=FAST", "--privacy-enabled"); + public void futureEipsValuesAreUsed() { + parseCommand("--network", "future_eips"); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("Fast sync cannot be enabled with privacy."); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - } + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); - @Test - public void privacyWithPruningMustError() { - parseCommand("--pruning-enabled", "--privacy-enabled"); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); + + assertThat(networkArg.getValue()).isEqualTo(EthNetworkConfig.getNetworkConfig(FUTURE_EIPS)); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("Pruning cannot be enabled with privacy."); assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } - @Rule public TemporaryFolder testFolder = new TemporaryFolder(); - @Test - public void errorIsRaisedIfStaticNodesAreNotAllowed() throws IOException { - final File staticNodesFile = testFolder.newFile("static-nodes.json"); - staticNodesFile.deleteOnExit(); - final File permissioningConfig = testFolder.newFile("permissioning"); - permissioningConfig.deleteOnExit(); + public void experimentalEipsValuesAreUsed() { + parseCommand("--network", "experimental_eips"); - final EnodeURL staticNodeURI = - EnodeURLImpl.builder() - .nodeId( - "50203c6bfca6874370e71aecc8958529fd723feb05013dc1abca8fc1fff845c5259faba05852e9dfe5ce172a7d6e7c2a3a5eaa8b541c8af15ea5518bbff5f2fa") - .ipAddress("127.0.0.1") - .useDefaultPorts() - .build(); + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); - final EnodeURL allowedNode = - EnodeURLImpl.builder() - .nodeId( - "50203c6bfca6874370e71aecc8958529fd723feb05013dc1abca8fc1fff845c5259faba05852e9dfe5ce172a7d6e7c2a3a5eaa8b541c8af15ea5518bbff5f2fa") - .useDefaultPorts() - .ipAddress("127.0.0.1") - .listeningPort(30304) - .build(); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); - Files.write( - staticNodesFile.toPath(), ("[\"" + staticNodeURI.toString() + "\"]").getBytes(UTF_8)); - Files.write( - permissioningConfig.toPath(), - ("nodes-allowlist=[\"" + allowedNode.toString() + "\"]").getBytes(UTF_8)); + assertThat(networkArg.getValue()) + .isEqualTo(EthNetworkConfig.getNetworkConfig(EXPERIMENTAL_EIPS)); - parseCommand( - "--data-path=" + testFolder.getRoot().getPath(), - "--bootnodes", - "--permissions-nodes-config-file-enabled=true", - "--permissions-nodes-config-file=" + permissioningConfig.getPath()); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains(staticNodeURI.toString(), "not in nodes-allowlist"); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void tomlThatHasInvalidOptions() throws IOException { - final URL configFile = this.getClass().getResource("/complete_config.toml"); - // update genesis file path, "similar" valid option and add invalid options - final Path genesisFile = createFakeGenesisFile(GENESIS_VALID_JSON); - final String updatedConfig = - Resources.toString(configFile, UTF_8) - .replace("/opt/besu/genesis.json", escapeTomlString(genesisFile.toString())) - .replace("rpc-http-api", "rpc-http-apis") - + System.lineSeparator() - + "invalid_option=true" - + System.lineSeparator() - + "invalid_option2=true"; + public void sepoliaValuesAreUsed() { + parseCommand("--network", "sepolia"); - final Path toml = createTempFile("toml", updatedConfig.getBytes(UTF_8)); + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); - // Parse it. - parseCommand("--config-file", toml.toString()); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("Unknown options in TOML configuration file: invalid_option, invalid_option2"); + assertThat(networkArg.getValue()).isEqualTo(EthNetworkConfig.getNetworkConfig(SEPOLIA)); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + + verify(mockLogger, never()).warn(contains("Sepolia is deprecated and will be shutdown")); } @Test - public void requiredBlocksSetWhenSpecified() { - final long blockNumber = 8675309L; - final String hash = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; - - parseCommand("--required-block=" + blockNumber + "=" + hash); + public void holeskyValuesAreUsed() { + parseCommand("--network", "holesky"); - @SuppressWarnings("unchecked") - final ArgumentCaptor> requiredBlocksArg = ArgumentCaptor.forClass(Map.class); + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); - verify(mockControllerBuilder).requiredBlocks(requiredBlocksArg.capture()); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); verify(mockControllerBuilder).build(); + assertThat(networkArg.getValue()).isEqualTo(EthNetworkConfig.getNetworkConfig(HOLESKY)); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - assertThat(requiredBlocksArg.getValue()).containsOnlyKeys(blockNumber); - assertThat(requiredBlocksArg.getValue()) - .containsEntry(blockNumber, Hash.fromHexStringLenient(hash)); + verify(mockLogger, never()).warn(contains("Holesky is deprecated and will be shutdown")); } @Test - public void requiredBlocksEmptyWhenNotSpecified() { - parseCommand(); + public void luksoValuesAreUsed() { + parseCommand("--network", "lukso"); - @SuppressWarnings("unchecked") - final ArgumentCaptor> requiredBlocksArg = ArgumentCaptor.forClass(Map.class); + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); - verify(mockControllerBuilder).requiredBlocks(requiredBlocksArg.capture()); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); verify(mockControllerBuilder).build(); + assertThat(networkArg.getValue()).isEqualTo(EthNetworkConfig.getNetworkConfig(LUKSO)); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - - assertThat(requiredBlocksArg.getValue()).isEmpty(); } @Test - public void requiredBlocksMulpleBlocksOneArg() { - final long block1 = 8675309L; - final long block2 = 5551212L; - final String hash1 = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; - final String hash2 = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; - - parseCommand("--required-block=" + block1 + "=" + hash1 + "," + block2 + "=" + hash2); + public void classicValuesAreUsed() { + parseCommand("--network", "classic"); - @SuppressWarnings("unchecked") - final ArgumentCaptor> requiredBlocksArg = ArgumentCaptor.forClass(Map.class); + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); - verify(mockControllerBuilder).requiredBlocks(requiredBlocksArg.capture()); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); verify(mockControllerBuilder).build(); + assertThat(networkArg.getValue()).isEqualTo(EthNetworkConfig.getNetworkConfig(CLASSIC)); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - - assertThat(requiredBlocksArg.getValue()).containsOnlyKeys(block1, block2); - assertThat(requiredBlocksArg.getValue()) - .containsEntry(block1, Hash.fromHexStringLenient(hash1)); - assertThat(requiredBlocksArg.getValue()) - .containsEntry(block2, Hash.fromHexStringLenient(hash2)); } @Test - public void requiredBlocksMultipleBlocksTwoArgs() { - final long block1 = 8675309L; - final long block2 = 5551212L; - final String hash1 = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; - final String hash2 = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; - - parseCommand( - "--required-block=" + block1 + "=" + hash1, "--required-block=" + block2 + "=" + hash2); + public void mordorValuesAreUsed() { + parseCommand("--network", "mordor"); - @SuppressWarnings("unchecked") - final ArgumentCaptor> requiredBlocksArg = ArgumentCaptor.forClass(Map.class); + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); - verify(mockControllerBuilder).requiredBlocks(requiredBlocksArg.capture()); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); verify(mockControllerBuilder).build(); + assertThat(networkArg.getValue()).isEqualTo(EthNetworkConfig.getNetworkConfig(MORDOR)); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - - assertThat(requiredBlocksArg.getValue()).containsOnlyKeys(block1, block2); - assertThat(requiredBlocksArg.getValue()) - .containsEntry(block1, Hash.fromHexStringLenient(hash1)); - assertThat(requiredBlocksArg.getValue()) - .containsEntry(block2, Hash.fromHexStringLenient(hash2)); } @Test - public void httpAuthenticationAlgorithIsConfigured() { - parseCommand("--rpc-http-authentication-jwt-algorithm", "ES256"); - - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); + public void sepoliaValuesCanBeOverridden() { + networkValuesCanBeOverridden("sepolia"); + } - assertThat(jsonRpcConfigArgumentCaptor.getValue().getAuthenticationAlgorithm()) - .isEqualTo(JwtAlgorithm.ES256); + @Test + public void futureEipsValuesCanBeOverridden() { + networkValuesCanBeOverridden("future_eips"); } @Test - public void webSocketAuthenticationAlgorithIsConfigured() { - parseCommand("--rpc-ws-authentication-jwt-algorithm", "ES256"); + public void experimentalEipsValuesCanBeOverridden() { + networkValuesCanBeOverridden("experimental_eips"); + } - verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); + @Test + public void devValuesCanBeOverridden() { + networkValuesCanBeOverridden("dev"); + } - assertThat(wsRpcConfigArgumentCaptor.getValue().getAuthenticationAlgorithm()) - .isEqualTo(JwtAlgorithm.ES256); + @Test + public void classicValuesCanBeOverridden() { + networkValuesCanBeOverridden("classic"); } @Test - public void httpAuthenticationPublicKeyIsConfigured() throws IOException { - final Path publicKey = Files.createTempFile("public_key", ""); - parseCommand("--rpc-http-authentication-jwt-public-key-file", publicKey.toString()); + public void mordorValuesCanBeOverridden() { + networkValuesCanBeOverridden("mordor"); + } - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); + private void networkValuesCanBeOverridden(final String network) { + parseCommand( + "--network", + network, + "--network-id", + "1234567", + "--bootnodes", + String.join(",", VALID_ENODE_STRINGS)); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getAuthenticationPublicKeyFile().getPath()) - .isEqualTo(publicKey.toString()); - } + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); - @Test - public void httpAuthenticationWithoutRequiredConfiguredOptionsMustFail() { - parseCommand("--rpc-http-enabled", "--rpc-http-authentication-enabled"); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); + + assertThat(networkArg.getValue().bootNodes()) + .isEqualTo( + Stream.of(VALID_ENODE_STRINGS) + .map(EnodeURLImpl::fromString) + .collect(Collectors.toList())); + assertThat(networkArg.getValue().networkId()).isEqualTo(1234567); - verifyNoInteractions(mockRunnerBuilder); assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains( - "Unable to authenticate JSON-RPC HTTP endpoint without a supplied credentials file or authentication public key file"); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void wsAuthenticationPublicKeyIsConfigured() throws IOException { - final Path publicKey = Files.createTempFile("public_key", ""); - parseCommand("--rpc-ws-authentication-jwt-public-key-file", publicKey.toString()); + public void fullCLIOptionsShown() { + parseCommand("--help"); - verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); + Mockito.verifyNoInteractions(mockRunnerBuilder); - assertThat(wsRpcConfigArgumentCaptor.getValue().getAuthenticationPublicKeyFile().getPath()) - .isEqualTo(publicKey.toString()); + assertThat(commandOutput.toString(UTF_8)).contains("--config-file"); + assertThat(commandOutput.toString(UTF_8)).contains("--data-path"); + assertThat(commandOutput.toString(UTF_8)).contains("--genesis-file"); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void wsAuthenticationWithoutRequiredConfiguredOptionsMustFail() { - parseCommand("--rpc-ws-enabled", "--rpc-ws-authentication-enabled"); + public void tomlThatHasInvalidOptions() throws IOException { + final URL configFile = this.getClass().getResource("/complete_config.toml"); + // update genesis file path, "similar" valid option and add invalid options + final Path genesisFile = createFakeGenesisFile(GENESIS_VALID_JSON); + final String updatedConfig = + Resources.toString(configFile, UTF_8) + .replace("/opt/besu/genesis.json", escapeTomlString(genesisFile.toString())) + .replace("rpc-http-api", "rpc-http-apis") + + System.lineSeparator() + + "invalid_option=true" + + System.lineSeparator() + + "invalid_option2=true"; + + final Path toml = createTempFile("toml", updatedConfig.getBytes(UTF_8)); + + // Parse it. + parseCommand("--config-file", toml.toString()); - verifyNoInteractions(mockRunnerBuilder); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)) - .contains( - "Unable to authenticate JSON-RPC WebSocket endpoint without a supplied credentials file or authentication public key file"); + .contains("Unknown options in TOML configuration file: invalid_option, invalid_option2"); } @Test - public void privHttpApisWithPrivacyDisabledLogsWarning() { - parseCommand("--privacy-enabled=false", "--rpc-http-api", "PRIV", "--rpc-http-enabled"); - - verify(mockRunnerBuilder).build(); - verify(mockLogger) - .warn("Privacy is disabled. Cannot use EEA/PRIV API methods when not using Privacy."); + public void requiredBlocksSetWhenSpecified() { + final long blockNumber = 8675309L; + final String hash = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } + parseCommand("--required-block=" + blockNumber + "=" + hash); - @Test - public void privWsApisWithPrivacyDisabledLogsWarning() { - parseCommand("--privacy-enabled=false", "--rpc-ws-api", "PRIV", "--rpc-ws-enabled"); + @SuppressWarnings("unchecked") + final ArgumentCaptor> requiredBlocksArg = ArgumentCaptor.forClass(Map.class); - verify(mockRunnerBuilder).build(); - verify(mockLogger) - .warn("Privacy is disabled. Cannot use EEA/PRIV API methods when not using Privacy."); + verify(mockControllerBuilder).requiredBlocks(requiredBlocksArg.capture()); + verify(mockControllerBuilder).build(); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + + assertThat(requiredBlocksArg.getValue()).containsOnlyKeys(blockNumber); + assertThat(requiredBlocksArg.getValue()) + .containsEntry(blockNumber, Hash.fromHexStringLenient(hash)); } @Test - public void eeaHttpApisWithPrivacyDisabledLogsWarning() { - parseCommand("--privacy-enabled=false", "--rpc-http-api", "EEA", "--rpc-http-enabled"); + public void requiredBlocksEmptyWhenNotSpecified() { + parseCommand(); - verify(mockRunnerBuilder).build(); - verify(mockLogger) - .warn("Privacy is disabled. Cannot use EEA/PRIV API methods when not using Privacy."); + @SuppressWarnings("unchecked") + final ArgumentCaptor> requiredBlocksArg = ArgumentCaptor.forClass(Map.class); + + verify(mockControllerBuilder).requiredBlocks(requiredBlocksArg.capture()); + verify(mockControllerBuilder).build(); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + + assertThat(requiredBlocksArg.getValue()).isEmpty(); } @Test - public void eeaWsApisWithPrivacyDisabledLogsWarning() { - parseCommand("--privacy-enabled=false", "--rpc-ws-api", "EEA", "--rpc-ws-enabled"); + public void requiredBlocksMulpleBlocksOneArg() { + final long block1 = 8675309L; + final long block2 = 5551212L; + final String hash1 = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; + final String hash2 = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; - verify(mockRunnerBuilder).build(); - verify(mockLogger) - .warn("Privacy is disabled. Cannot use EEA/PRIV API methods when not using Privacy."); + parseCommand("--required-block=" + block1 + "=" + hash1 + "," + block2 + "=" + hash2); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } + @SuppressWarnings("unchecked") + final ArgumentCaptor> requiredBlocksArg = ArgumentCaptor.forClass(Map.class); - @Test - public void privEnclaveKeyFileDoesNotExist() { - assumeThat( - "Ignored if system language is not English", - System.getProperty("user.language"), - startsWith("en")); - parseCommand("--privacy-enabled=true", "--privacy-public-key-file", "/non/existent/file"); + verify(mockControllerBuilder).requiredBlocks(requiredBlocksArg.capture()); + verify(mockControllerBuilder).build(); assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .startsWith("Problem with privacy-public-key-file"); - assertThat(commandErrorOutput.toString(UTF_8)).contains("No such file"); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + + assertThat(requiredBlocksArg.getValue()).containsOnlyKeys(block1, block2); + assertThat(requiredBlocksArg.getValue()) + .containsEntry(block1, Hash.fromHexStringLenient(hash1)); + assertThat(requiredBlocksArg.getValue()) + .containsEntry(block2, Hash.fromHexStringLenient(hash2)); } @Test - public void privEnclaveKeyFileInvalidContentTooShort() throws IOException { - final Path file = createTempFile("privacy.key", "lkjashdfiluhwelrk"); - parseCommand("--privacy-enabled=true", "--privacy-public-key-file", file.toString()); + public void requiredBlocksMultipleBlocksTwoArgs() { + final long block1 = 8675309L; + final long block2 = 5551212L; + final String hash1 = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; + final String hash2 = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .startsWith("Contents of privacy-public-key-file invalid"); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("Last unit does not have enough valid bits"); - } + parseCommand( + "--required-block=" + block1 + "=" + hash1, "--required-block=" + block2 + "=" + hash2); - @Test - public void privEnclaveKeyFileInvalidContentNotValidBase64() throws IOException { - final Path file = createTempFile("privacy.key", "l*jashdfillk9ashdfillkjashdfillkjashdfilrtg="); - parseCommand("--privacy-enabled=true", "--privacy-public-key-file", file.toString()); + @SuppressWarnings("unchecked") + final ArgumentCaptor> requiredBlocksArg = ArgumentCaptor.forClass(Map.class); + + verify(mockControllerBuilder).requiredBlocks(requiredBlocksArg.capture()); + verify(mockControllerBuilder).build(); assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .startsWith("Contents of privacy-public-key-file invalid"); - assertThat(commandErrorOutput.toString(UTF_8)).contains("Illegal base64 character"); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + + assertThat(requiredBlocksArg.getValue()).containsOnlyKeys(block1, block2); + assertThat(requiredBlocksArg.getValue()) + .containsEntry(block1, Hash.fromHexStringLenient(hash1)); + assertThat(requiredBlocksArg.getValue()) + .containsEntry(block2, Hash.fromHexStringLenient(hash2)); } @Test @@ -4737,29 +2219,6 @@ public void assertThatCompatibilityEth64ForkIdIsPresentInHelpMessage() { assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } - @Test - public void assertThatCheckPortClashAcceptsAsExpected() throws Exception { - // use WS port for HTTP - final int port = 8546; - parseCommand("--rpc-http-enabled", "--rpc-http-port", String.valueOf(port)); - verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); - verify(mockRunnerBuilder).build(); - assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void assertThatCheckPortClashRejectsAsExpected() throws Exception { - // use WS port for HTTP - final int port = 8546; - parseCommand("--rpc-http-enabled", "--rpc-http-port", String.valueOf(port), "--rpc-ws-enabled"); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains( - "Port number '8546' has been specified multiple times. Please review the supplied configuration."); - } - @Test public void assertThatCheckPortClashRejectsAsExpectedForEngineApi() throws Exception { // use WS port for HTTP @@ -4870,75 +2329,6 @@ public void nativeLibrariesAreEnabledByDefault() { verify(mockLogger).info("Using the native implementation of alt bn128"); } - @Test - public void pkiBlockCreationIsDisabledByDefault() { - parseCommand(); - - verifyNoInteractions(mockPkiBlockCreationConfigProvider); - } - - @Test - public void pkiBlockCreationKeyStoreFileRequired() { - parseCommand( - "--Xpki-block-creation-enabled", - "--Xpki-block-creation-keystore-password-file", - "/tmp/pwd"); - - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("KeyStore file is required when PKI Block Creation is enabled"); - } - - @Test - public void pkiBlockCreationPasswordFileRequired() { - parseCommand( - "--Xpki-block-creation-enabled", "--Xpki-block-creation-keystore-file", "/tmp/keystore"); - - assertThat(commandErrorOutput.toString(UTF_8)) - .contains( - "File containing password to unlock keystore is required when PKI Block Creation is enabled"); - } - - @Rule public TemporaryFolder pkiTempFolder = new TemporaryFolder(); - - @Test - public void pkiBlockCreationFullConfig() throws Exception { - // Create temp file with password - final File pwdFile = pkiTempFolder.newFile("pwd"); - FileUtils.writeStringToFile(pwdFile, "foo", UTF_8); - - parseCommand( - "--Xpki-block-creation-enabled", - "--Xpki-block-creation-keystore-type", - "JKS", - "--Xpki-block-creation-keystore-file", - "/tmp/keystore", - "--Xpki-block-creation-keystore-password-file", - pwdFile.getAbsolutePath(), - "--Xpki-block-creation-keystore-certificate-alias", - "anAlias", - "--Xpki-block-creation-truststore-type", - "JKS", - "--Xpki-block-creation-truststore-file", - "/tmp/truststore", - "--Xpki-block-creation-truststore-password-file", - pwdFile.getAbsolutePath(), - "--Xpki-block-creation-crl-file", - "/tmp/crl"); - - final PkiKeyStoreConfiguration pkiKeyStoreConfig = - pkiKeyStoreConfigurationArgumentCaptor.getValue(); - - assertThat(pkiKeyStoreConfig).isNotNull(); - assertThat(pkiKeyStoreConfig.getKeyStoreType()).isEqualTo("JKS"); - assertThat(pkiKeyStoreConfig.getKeyStorePath()).isEqualTo(Path.of("/tmp/keystore")); - assertThat(pkiKeyStoreConfig.getKeyStorePassword()).isEqualTo("foo"); - assertThat(pkiKeyStoreConfig.getCertificateAlias()).isEqualTo("anAlias"); - assertThat(pkiKeyStoreConfig.getTrustStoreType()).isEqualTo("JKS"); - assertThat(pkiKeyStoreConfig.getTrustStorePath()).isEqualTo(Path.of("/tmp/truststore")); - assertThat(pkiKeyStoreConfig.getTrustStorePassword()).isEqualTo("foo"); - assertThat(pkiKeyStoreConfig.getCrlFilePath()).hasValue(Path.of("/tmp/crl")); - } - @Test public void logsWarningWhenFailToLoadJemalloc() { assumeThat(PlatformDetector.getOSType(), is("linux")); @@ -4962,57 +2352,7 @@ public void logsSuggestInstallingJemallocWhenEnvVarNotPresent() { @Test public void logWarnIfFastSyncMinPeersUsedWithFullSync() { parseCommand("--sync-mode", "FULL", "--fast-sync-min-peers", "1"); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("--fast-sync-min-peers can't be used with FULL sync-mode"); - } - - @Test - public void portInUseReportsError() throws IOException { - final ServerSocket serverSocket = new ServerSocket(8545); - - parseCommandWithPortCheck("--rpc-http-enabled"); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("Port(s) '[8545]' already in use. Check for other processes using the port(s)."); - - serverSocket.close(); - } - - @Test - public void presentRequiredOptionShouldPass() { - parseCommandWithRequiredOption("--accept-terms-and-conditions", "true"); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void missingRequiredOptionShouldFail() { - parseCommandWithRequiredOption(); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .startsWith( - "Missing required option: '--accept-terms-and-conditions='"); - } - - @Test - public void havingRequiredOptionInEnvVarShouldFail() { - setEnvironmentVariable("BESU_ACCEPT_TERMS_AND_CONDITIONS", "true"); - parseCommandWithRequiredOption(); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .startsWith( - "Missing required option: '--accept-terms-and-conditions='"); - } - - @Test - public void havingRequiredOptionInConfigShouldFail() throws IOException { - final Path toml = createTempFile("toml", "accept-terms-and-conditions=true\n"); - parseCommandWithRequiredOption("--config-file", toml.toString()); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .startsWith( - "Missing required option: '--accept-terms-and-conditions='"); + verify(mockLogger).warn("--sync-min-peers is ignored in FULL sync-mode"); } @Test @@ -5027,7 +2367,7 @@ public void checkpointPostMergeShouldFailWhenGenesisHasNoTTD() throws IOExceptio "--genesis-file", genesisFile.toString(), "--sync-mode", - "X_CHECKPOINT", + "CHECKPOINT", "--Xcheckpoint-post-merge-enabled"); assertThat(commandOutput.toString(UTF_8)).isEmpty(); @@ -5038,7 +2378,7 @@ public void checkpointPostMergeShouldFailWhenGenesisHasNoTTD() throws IOExceptio @Test public void checkpointPostMergeShouldFailWhenGenesisUsesCheckpointFromPreMerge() { // using the default genesis which has a checkpoint sync block prior to the merge - parseCommand("--sync-mode", "X_CHECKPOINT", "--Xcheckpoint-post-merge-enabled"); + parseCommand("--sync-mode", "CHECKPOINT", "--Xcheckpoint-post-merge-enabled"); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)) @@ -5049,9 +2389,9 @@ public void checkpointPostMergeShouldFailWhenGenesisUsesCheckpointFromPreMerge() @Test public void checkpointPostMergeShouldFailWhenSyncModeIsNotCheckpoint() { - parseCommand("--sync-mode", "X_SNAP", "--Xcheckpoint-post-merge-enabled"); + parseCommand("--sync-mode", "SNAP", "--Xcheckpoint-post-merge-enabled"); assertThat(commandErrorOutput.toString(UTF_8)) - .contains("--Xcheckpoint-post-merge-enabled can only be used with X_CHECKPOINT sync-mode"); + .contains("--Xcheckpoint-post-merge-enabled can only be used with CHECKPOINT sync-mode"); } @Test @@ -5066,7 +2406,7 @@ public void checkpointPostMergeWithPostMergeBlockSucceeds() throws IOException { "--genesis-file", genesisFile.toString(), "--sync-mode", - "X_CHECKPOINT", + "CHECKPOINT", "--Xcheckpoint-post-merge-enabled"); assertThat(commandOutput.toString(UTF_8)).isEmpty(); @@ -5085,7 +2425,7 @@ public void checkpointPostMergeWithPostMergeBlockTDEqualsTTDSucceeds() throws IO "--genesis-file", genesisFile.toString(), "--sync-mode", - "X_CHECKPOINT", + "CHECKPOINT", "--Xcheckpoint-post-merge-enabled"); assertThat(commandOutput.toString(UTF_8)).isEmpty(); @@ -5104,7 +2444,7 @@ public void checkpointMergeAtGenesisWithGenesisBlockDifficultyZeroFails() throws "--genesis-file", genesisFile.toString(), "--sync-mode", - "X_CHECKPOINT", + "CHECKPOINT", "--Xcheckpoint-post-merge-enabled"); assertThat(commandOutput.toString(UTF_8)).isEmpty(); @@ -5113,20 +2453,6 @@ public void checkpointMergeAtGenesisWithGenesisBlockDifficultyZeroFails() throws "PoS checkpoint sync can't be used with TTD = 0 and checkpoint totalDifficulty = 0"); } - @Test - public void checkP2pPeerLowerBound_isSet() { - final int lowerBound = 13; - parseCommand("--Xp2p-peer-lower-bound", String.valueOf(lowerBound)); - - verify(mockControllerBuilder).lowerBoundPeers(intArgumentCaptor.capture()); - verify(mockControllerBuilder).build(); - - assertThat(intArgumentCaptor.getValue()).isEqualTo(lowerBound); - - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - } - @Test public void kzgTrustedSetupFileRequiresDataBlobEnabledNetwork() throws IOException { final Path genesisFileWithoutBlobs = @@ -5159,177 +2485,88 @@ public void kzgTrustedSetupFileLoadedWithCustomGenesisFile() } @Test - public void txpoolDefaultSaveFileRelativeToDataPath() throws IOException { - final Path dataDir = Files.createTempDirectory("data-dir"); - parseCommand("--data-path", dataDir.toString(), "--tx-pool-enable-save-restore", "true"); - verify(mockControllerBuilder) - .transactionPoolConfiguration(transactionPoolConfigCaptor.capture()); - - assertThat(transactionPoolConfigCaptor.getValue().getSaveFile()) - .isEqualTo(dataDir.resolve(TransactionPoolConfiguration.DEFAULT_SAVE_FILE_NAME).toFile()); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + public void bonsaiFlatDbShouldBeEnabledByDefault() { + final TestBesuCommand besuCommand = parseCommand(); + assertThat( + besuCommand + .getDataStorageOptions() + .toDomainObject() + .getUnstable() + .getBonsaiFullFlatDbEnabled()) + .isTrue(); } @Test - public void txpoolCustomSaveFileRelativeToDataPath() throws IOException { - final Path dataDir = Files.createTempDirectory("data-dir"); - dataDir.toFile().deleteOnExit(); - final File saveFile = Files.createTempFile(dataDir, "txpool", "save").toFile(); - saveFile.deleteOnExit(); - parseCommand( - "--data-path", - dataDir.toString(), - "--tx-pool-enable-save-restore", - "true", - "--tx-pool-save-file", - saveFile.getName()); - verify(mockControllerBuilder) - .transactionPoolConfiguration(transactionPoolConfigCaptor.capture()); - - final File configuredSaveFile = transactionPoolConfigCaptor.getValue().getSaveFile(); - assertThat(configuredSaveFile).isEqualTo(saveFile); - assertThat(configuredSaveFile.toPath().getParent()).isEqualTo(dataDir); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + public void snapsyncHealingOptionShouldWork() { + final TestBesuCommand besuCommand = parseCommand("--Xbonsai-full-flat-db-enabled", "false"); + assertThat( + besuCommand + .dataStorageOptions + .toDomainObject() + .getUnstable() + .getBonsaiFullFlatDbEnabled()) + .isFalse(); } @Test - public void txpoolSaveFileAbsolutePathOutsideDataPath() throws IOException { - final Path dataDir = Files.createTempDirectory("data-dir"); - dataDir.toFile().deleteOnExit(); - final File saveFile = File.createTempFile("txpool", "dump"); - saveFile.deleteOnExit(); + public void snapsyncForHealingFeaturesShouldFailWhenHealingIsNotEnabled() { parseCommand( - "--data-path", - dataDir.toString(), - "--tx-pool-enable-save-restore", - "true", - "--tx-pool-save-file", - saveFile.getAbsolutePath()); - verify(mockControllerBuilder) - .transactionPoolConfiguration(transactionPoolConfigCaptor.capture()); - - final File configuredSaveFile = transactionPoolConfigCaptor.getValue().getSaveFile(); - assertThat(configuredSaveFile).isEqualTo(saveFile); - assertThat(configuredSaveFile.toPath().getParent()).isNotEqualTo(dataDir); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void txpoolForcePriceBumpToZeroWhenZeroBaseFeeMarket() throws IOException { - final Path genesisFile = createFakeGenesisFile(GENESIS_WITH_ZERO_BASE_FEE_MARKET); - parseCommand("--genesis-file", genesisFile.toString()); - verify(mockControllerBuilder) - .transactionPoolConfiguration(transactionPoolConfigCaptor.capture()); - - final Percentage priceBump = transactionPoolConfigCaptor.getValue().getPriceBump(); - assertThat(priceBump).isEqualTo(Percentage.ZERO); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void txpoolPriceBumpOptionIncompatibleWithZeroWhenZeroBaseFeeMarket() throws IOException { - final Path genesisFile = createFakeGenesisFile(GENESIS_WITH_ZERO_BASE_FEE_MARKET); - parseCommand("--genesis-file", genesisFile.toString(), "--tx-pool-price-bump", "5"); + "--Xbonsai-full-flat-db-enabled", + "false", + "--Xsnapsync-synchronizer-flat-account-healed-count-per-request", + "100"); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains( + "--Xsnapsync-synchronizer-flat option can only be used when --Xbonsai-full-flat-db-enabled is true"); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); + parseCommand( + "--Xbonsai-full-flat-db-enabled", + "false", + "--Xsnapsync-synchronizer-flat-slot-healed-count-per-request", + "100"); assertThat(commandErrorOutput.toString(UTF_8)) - .contains("Price bump option is not compatible with zero base fee market"); + .contains( + "--Xsnapsync-synchronizer-flat option can only be used when --Xbonsai-full-flat-db-enabled is true"); } @Test - public void txpoolForcePriceBumpToZeroWhenMinGasPriceZero() { - parseCommand("--min-gas-price", "0"); - verify(mockControllerBuilder) - .transactionPoolConfiguration(transactionPoolConfigCaptor.capture()); - - final Percentage priceBump = transactionPoolConfigCaptor.getValue().getPriceBump(); - assertThat(priceBump).isEqualTo(Percentage.ZERO); + public void cacheLastBlocksOptionShouldWork() { + int numberOfBlocksToCache = 512; + parseCommand("--cache-last-blocks", String.valueOf(numberOfBlocksToCache)); + verify(mockControllerBuilder).cacheLastBlocks(intArgumentCaptor.capture()); + verify(mockControllerBuilder).build(); + assertThat(intArgumentCaptor.getValue()).isEqualTo(numberOfBlocksToCache); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); } @Test - public void txpoolPriceBumpKeepItsValueIfSetEvenWhenMinGasPriceZero() { - parseCommand("--min-gas-price", "0", "--tx-pool-price-bump", "1"); - verify(mockControllerBuilder) - .transactionPoolConfiguration(transactionPoolConfigCaptor.capture()); - - final Percentage priceBump = transactionPoolConfigCaptor.getValue().getPriceBump(); - assertThat(priceBump).isEqualTo(Percentage.fromInt(1)); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } + public void genesisStateHashCacheEnabledShouldWork() throws IOException { + final Path genesisFile = createFakeGenesisFile(GENESIS_VALID_JSON); + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); - @Test - public void txpoolWhenNotSetForceTxPoolMinGasPriceToZeroWhenMinGasPriceZero() { - parseCommand("--min-gas-price", "0"); - verify(mockControllerBuilder) - .transactionPoolConfiguration(transactionPoolConfigCaptor.capture()); + parseCommand( + "--genesis-file", genesisFile.toString(), "--genesis-state-hash-cache-enabled=true"); - final Wei txPoolMinGasPrice = transactionPoolConfigCaptor.getValue().getMinGasPrice(); - assertThat(txPoolMinGasPrice).isEqualTo(Wei.ZERO); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); + verify(mockControllerBuilder).genesisStateHashCacheEnabled(eq(true)); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - verify(mockLogger, atLeast(1)) - .warn( - contains( - "Forcing tx-pool-min-gas-price=0, since it cannot be greater than the value of min-gas-price")); - } - - @Test - public void txpoolTxPoolMinGasPriceMustNotBeGreaterThanMinGasPriceZero() { - parseCommand("--min-gas-price", "100", "--tx-pool-min-gas-price", "101"); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains("tx-pool-min-gas-price cannot be greater than the value of min-gas-price"); - } - - @Test - public void snapsyncHealingOptionShouldBeDisabledByDefault() { - final TestBesuCommand besuCommand = parseCommand(); - assertThat(besuCommand.unstableSynchronizerOptions.isSnapsyncFlatDbHealingEnabled()).isFalse(); - } - - @Test - public void snapsyncHealingOptionShouldWork() { - final TestBesuCommand besuCommand = - parseCommand("--Xsnapsync-synchronizer-flat-db-healing-enabled", "true"); - assertThat(besuCommand.unstableSynchronizerOptions.isSnapsyncFlatDbHealingEnabled()).isTrue(); } @Test - public void snapsyncForHealingFeaturesShouldFailWhenHealingIsNotEnabled() { - parseCommand("--Xsnapsync-synchronizer-flat-account-healed-count-per-request", "100"); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains( - "--Xsnapsync-synchronizer-flat option can only be used when -Xsnapsync-synchronizer-flat-db-healing-enabled is true"); + void helpOutputShouldDisplayCorrectDefaultValues() { + parseCommand("--help"); - parseCommand("--Xsnapsync-synchronizer-flat-slot-healed-count-per-request", "100"); - assertThat(commandErrorOutput.toString(UTF_8)) - .contains( - "--Xsnapsync-synchronizer-flat option can only be used when -Xsnapsync-synchronizer-flat-db-healing-enabled is true"); - } + final String commandOutputString = commandOutput.toString(UTF_8); + final String errorOutputString = commandErrorOutput.toString(UTF_8); - @Test - public void cacheLastBlocksOptionShouldWork() { - int numberOfBlocksToCache = 512; - parseCommand("--cache-last-blocks", String.valueOf(numberOfBlocksToCache)); - verify(mockControllerBuilder).cacheLastBlocks(intArgumentCaptor.capture()); - verify(mockControllerBuilder).build(); + assertThat(commandOutputString).doesNotContain("$DEFAULT-VALUE"); - assertThat(intArgumentCaptor.getValue()).isEqualTo(numberOfBlocksToCache); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + assertThat(errorOutputString).isEmpty(); } } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandWithRequiredOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandWithRequiredOptionsTest.java new file mode 100644 index 00000000000..9198e199112 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/BesuCommandWithRequiredOptionsTest.java @@ -0,0 +1,62 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; + +public class BesuCommandWithRequiredOptionsTest extends CommandTestAbstract { + + @Test + public void presentRequiredOptionShouldPass() { + parseCommandWithRequiredOption("--accept-terms-and-conditions", "true"); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void missingRequiredOptionShouldFail() { + parseCommandWithRequiredOption(); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .startsWith( + "Missing required option: '--accept-terms-and-conditions='"); + } + + @Test + public void havingRequiredOptionInEnvVarShouldFail() { + setEnvironmentVariable("BESU_ACCEPT_TERMS_AND_CONDITIONS", "true"); + parseCommandWithRequiredOption(); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .startsWith( + "Missing required option: '--accept-terms-and-conditions='"); + } + + @Test + public void havingRequiredOptionInConfigShouldFail() throws IOException { + final Path toml = createTempFile("toml", "accept-terms-and-conditions=true\n"); + parseCommandWithRequiredOption("--config-file", toml.toString()); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .startsWith( + "Missing required option: '--accept-terms-and-conditions='"); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/CascadingDefaultProviderTest.java b/besu/src/test/java/org/hyperledger/besu/cli/CascadingDefaultProviderTest.java new file mode 100644 index 00000000000..4cdd8163d95 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/CascadingDefaultProviderTest.java @@ -0,0 +1,300 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.cli.config.NetworkName.DEV; +import static org.hyperledger.besu.cli.config.NetworkName.MAINNET; +import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.ETH; +import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.WEB3; +import static org.hyperledger.besu.ethereum.p2p.config.DefaultDiscoveryConfiguration.MAINNET_BOOTSTRAP_NODES; +import static org.hyperledger.besu.ethereum.p2p.config.DefaultDiscoveryConfiguration.MAINNET_DISCOVERY_URL; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +import org.hyperledger.besu.cli.config.EthNetworkConfig; +import org.hyperledger.besu.config.GenesisConfigFile; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration; +import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration; +import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration; +import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.ethereum.eth.sync.SyncMode; +import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; +import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl; +import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; +import org.hyperledger.besu.plugin.data.EnodeURL; + +import java.io.File; +import java.io.IOException; +import java.math.BigInteger; +import java.net.URL; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; + +import com.google.common.io.Resources; +import io.vertx.core.json.JsonObject; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.ArgumentCaptor; + +public class CascadingDefaultProviderTest extends CommandTestAbstract { + private static final int GENESIS_CONFIG_TEST_CHAINID = 3141592; + private static final String VALID_NODE_ID = + "6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0"; + private static final JsonObject GENESIS_VALID_JSON = + (new JsonObject()) + .put("config", (new JsonObject()).put("chainId", GENESIS_CONFIG_TEST_CHAINID)); + + /** + * Test if the default values are overridden if the key is present in the configuration file. The + * test checks if the configuration file correctly overrides the default values for various + * settings, such as the JSON-RPC configuration, GraphQL configuration, WebSocket configuration, + * and metrics configuration. + */ + @Test + public void overrideDefaultValuesIfKeyIsPresentInConfigFile(final @TempDir File dataFolder) + throws IOException { + final URL configFile = this.getClass().getResource("/complete_config.toml"); + final Path genesisFile = createFakeGenesisFile(GENESIS_VALID_JSON); + final String updatedConfig = + Resources.toString(configFile, UTF_8) + .replace("/opt/besu/genesis.json", escapeTomlString(genesisFile.toString())) + .replace( + "data-path=\"/opt/besu\"", + "data-path=\"" + escapeTomlString(dataFolder.getPath()) + "\""); + + final Path toml = createTempFile("toml", updatedConfig.getBytes(UTF_8)); + + final List expectedApis = asList(ETH.name(), WEB3.name()); + + final JsonRpcConfiguration jsonRpcConfiguration = JsonRpcConfiguration.createDefault(); + jsonRpcConfiguration.setEnabled(false); + jsonRpcConfiguration.setHost("5.6.7.8"); + jsonRpcConfiguration.setPort(5678); + jsonRpcConfiguration.setCorsAllowedDomains(Collections.emptyList()); + jsonRpcConfiguration.setRpcApis(expectedApis); + jsonRpcConfiguration.setMaxActiveConnections(1000); + + final GraphQLConfiguration graphQLConfiguration = GraphQLConfiguration.createDefault(); + graphQLConfiguration.setEnabled(false); + graphQLConfiguration.setHost("6.7.8.9"); + graphQLConfiguration.setPort(6789); + + final WebSocketConfiguration webSocketConfiguration = WebSocketConfiguration.createDefault(); + webSocketConfiguration.setEnabled(false); + webSocketConfiguration.setHost("9.10.11.12"); + webSocketConfiguration.setPort(9101); + webSocketConfiguration.setRpcApis(expectedApis); + + final MetricsConfiguration metricsConfiguration = + MetricsConfiguration.builder().enabled(false).host("8.6.7.5").port(309).build(); + + parseCommand("--config-file", toml.toString()); + + verify(mockRunnerBuilder).discovery(eq(false)); + verify(mockRunnerBuilder).ethNetworkConfig(ethNetworkConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).p2pAdvertisedHost(eq("1.2.3.4")); + verify(mockRunnerBuilder).p2pListenPort(eq(1234)); + verify(mockRunnerBuilder).jsonRpcConfiguration(eq(jsonRpcConfiguration)); + verify(mockRunnerBuilder).graphQLConfiguration(eq(graphQLConfiguration)); + verify(mockRunnerBuilder).webSocketConfiguration(eq(webSocketConfiguration)); + verify(mockRunnerBuilder).metricsConfiguration(eq(metricsConfiguration)); + verify(mockRunnerBuilder).build(); + + final List nodes = + asList( + EnodeURLImpl.fromString("enode://" + VALID_NODE_ID + "@192.168.0.1:4567"), + EnodeURLImpl.fromString("enode://" + VALID_NODE_ID + "@192.168.0.1:4567"), + EnodeURLImpl.fromString("enode://" + VALID_NODE_ID + "@192.168.0.1:4567")); + assertThat(ethNetworkConfigArgumentCaptor.getValue().bootNodes()).isEqualTo(nodes); + + final EthNetworkConfig networkConfig = + new EthNetworkConfig.Builder(EthNetworkConfig.getNetworkConfig(MAINNET)) + .setNetworkId(BigInteger.valueOf(42)) + .setGenesisConfigFile( + GenesisConfigFile.fromConfig(encodeJsonGenesis(GENESIS_VALID_JSON))) + .setBootNodes(nodes) + .setDnsDiscoveryUrl(null) + .build(); + verify(mockControllerBuilder).dataDirectory(eq(dataFolder.toPath())); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(eq(networkConfig), any()); + verify(mockControllerBuilder).synchronizerConfiguration(syncConfigurationCaptor.capture()); + + assertThat(syncConfigurationCaptor.getValue().getSyncMode()).isEqualTo(SyncMode.FAST); + assertThat(syncConfigurationCaptor.getValue().getSyncMinimumPeerCount()).isEqualTo(13); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + /** + * Test if the default values are not overridden if the key is not present in the configuration + * file. The test checks if the default values for various settings remain unchanged when the + * corresponding keys are not present in the configuration file. + */ + @Test + public void noOverrideDefaultValuesIfKeyIsNotPresentInConfigFile() { + final String configFile = this.getClass().getResource("/partial_config.toml").getFile(); + + parseCommand("--config-file", configFile); + final JsonRpcConfiguration jsonRpcConfiguration = JsonRpcConfiguration.createDefault(); + + final GraphQLConfiguration graphQLConfiguration = GraphQLConfiguration.createDefault(); + + final WebSocketConfiguration webSocketConfiguration = WebSocketConfiguration.createDefault(); + + final MetricsConfiguration metricsConfiguration = MetricsConfiguration.builder().build(); + + verify(mockRunnerBuilder).discovery(eq(true)); + verify(mockRunnerBuilder) + .ethNetworkConfig( + new EthNetworkConfig( + GenesisConfigFile.fromResource(MAINNET.getGenesisFile()), + MAINNET.getNetworkId(), + MAINNET_BOOTSTRAP_NODES, + MAINNET_DISCOVERY_URL)); + verify(mockRunnerBuilder).p2pAdvertisedHost(eq("127.0.0.1")); + verify(mockRunnerBuilder).p2pListenPort(eq(30303)); + verify(mockRunnerBuilder).jsonRpcConfiguration(eq(jsonRpcConfiguration)); + verify(mockRunnerBuilder).graphQLConfiguration(eq(graphQLConfiguration)); + verify(mockRunnerBuilder).webSocketConfiguration(eq(webSocketConfiguration)); + verify(mockRunnerBuilder).metricsConfiguration(eq(metricsConfiguration)); + verify(mockRunnerBuilder).build(); + verify(mockControllerBuilder).build(); + verify(mockControllerBuilder).synchronizerConfiguration(syncConfigurationCaptor.capture()); + + final SynchronizerConfiguration syncConfig = syncConfigurationCaptor.getValue(); + assertThat(syncConfig.getSyncMode()).isEqualTo(SyncMode.SNAP); + assertThat(syncConfig.getSyncMinimumPeerCount()).isEqualTo(5); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + /** + * Test if the environment variable overrides the value from the configuration file. The test + * checks if the value of the miner's coinbase address set through an environment variable + * correctly overrides the value specified in the configuration file. + */ + @Test + public void envVariableOverridesValueFromConfigFile() { + final String configFile = this.getClass().getResource("/partial_config.toml").getFile(); + final String expectedCoinbase = "0x0000000000000000000000000000000000000004"; + setEnvironmentVariable("BESU_MINER_COINBASE", expectedCoinbase); + parseCommand("--config-file", configFile); + + final var captMiningParameters = ArgumentCaptor.forClass(MiningParameters.class); + verify(mockControllerBuilder).miningParameters(captMiningParameters.capture()); + + assertThat(captMiningParameters.getValue().getCoinbase()) + .contains(Address.fromHexString(expectedCoinbase)); + } + + /** + * Test if the command line option overrides the environment variable and configuration. The test + * checks if the value of the miner's coinbase address set through a command line option correctly + * overrides the value specified in the environment variable and the configuration file. + */ + @Test + public void cliOptionOverridesEnvVariableAndConfig() { + final String configFile = this.getClass().getResource("/partial_config.toml").getFile(); + final String expectedCoinbase = "0x0000000000000000000000000000000000000006"; + setEnvironmentVariable("BESU_MINER_COINBASE", "0x0000000000000000000000000000000000000004"); + parseCommand("--config-file", configFile, "--miner-coinbase", expectedCoinbase); + + final var captMiningParameters = ArgumentCaptor.forClass(MiningParameters.class); + verify(mockControllerBuilder).miningParameters(captMiningParameters.capture()); + + assertThat(captMiningParameters.getValue().getCoinbase()) + .contains(Address.fromHexString(expectedCoinbase)); + } + + /** + * Test if the profile option sets the correct defaults. The test checks if the 'dev' profile + * correctly sets the network ID to the expected value. + */ + @Test + public void profileOptionShouldSetCorrectDefaults() { + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); + + parseCommand("--profile", "dev"); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); + + final EthNetworkConfig config = networkArg.getValue(); + assertThat(config.networkId()).isEqualTo(DEV.getNetworkId()); + } + + /** + * Test if the command line option overrides the profile configuration. The test checks if the + * network ID set through a command line option correctly overrides the value specified in the + * 'dev' profile. + */ + @Test + public void cliOptionOverridesProfileConfiguration() { + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); + + parseCommand("--profile", "dev", "--network", "MAINNET"); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); + + final EthNetworkConfig config = networkArg.getValue(); + assertThat(config.networkId()).isEqualTo(MAINNET.getNetworkId()); + } + + /** + * Test if the configuration file overrides the profile configuration. The test checks if the + * network ID specified in the configuration file correctly overrides the value specified in the + * 'dev' profile. + */ + @Test + public void configFileOverridesProfileConfiguration() { + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); + + final String configFile = this.getClass().getResource("/partial_config.toml").getFile(); + parseCommand("--profile", "dev", "--config-file", configFile); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); + + final EthNetworkConfig config = networkArg.getValue(); + assertThat(config.networkId()).isEqualTo(MAINNET.getNetworkId()); + } + + /** + * Test if the environment variable overrides the profile configuration. The test checks if the + * network ID set through an environment variable correctly overrides the value specified in the + * 'dev' profile. + */ + @Test + public void environmentVariableOverridesProfileConfiguration() { + final ArgumentCaptor networkArg = + ArgumentCaptor.forClass(EthNetworkConfig.class); + setEnvironmentVariable("BESU_NETWORK", "MAINNET"); + parseCommand("--profile", "dev"); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(networkArg.capture(), any()); + verify(mockControllerBuilder).build(); + + final EthNetworkConfig config = networkArg.getValue(); + assertThat(config.networkId()).isEqualTo(MAINNET.getNetworkId()); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/CommandLineUtilsDefaultsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/CommandLineUtilsDefaultsTest.java new file mode 100644 index 00000000000..27f3773959e --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/CommandLineUtilsDefaultsTest.java @@ -0,0 +1,109 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.cli.util.CommandLineUtils.getOptionValueOrDefault; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.cli.util.CommandLineUtils; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import picocli.CommandLine; + +/** + * Unit tests for {@link CommandLineUtils} focusing on the retrieval of option values + * (getOptionValueOrDefault). + */ +public class CommandLineUtilsDefaultsTest { + private static final String OPTION_NAME = "option"; + private static final String OPTION_VALUE = "optionValue"; + private static final String DEFAULT_VALUE = "defaultValue"; + public final CommandLine.ITypeConverter converter = String::valueOf; + private CommandLine commandLine; + private CommandLine.Model.OptionSpec optionSpec; + private CommandLine.IDefaultValueProvider defaultValueProvider; + private CommandLine.ParseResult parseResult; + + @BeforeEach + public void setUp() { + commandLine = mock(CommandLine.class); + parseResult = mock(CommandLine.ParseResult.class); + CommandLine.Model.CommandSpec commandSpec = mock(CommandLine.Model.CommandSpec.class); + optionSpec = mock(CommandLine.Model.OptionSpec.class); + defaultValueProvider = mock(CommandLine.IDefaultValueProvider.class); + when(commandLine.getParseResult()).thenReturn(parseResult); + when(commandLine.getCommandSpec()).thenReturn(commandSpec); + when(commandLine.getDefaultValueProvider()).thenReturn(defaultValueProvider); + when(parseResult.matchedOptionValue(anyString(), any())).thenCallRealMethod(); + when(commandSpec.findOption(OPTION_NAME)).thenReturn(optionSpec); + } + + @Test + public void testGetOptionValueOrDefault_UserProvidedValue() { + when(parseResult.matchedOption(OPTION_NAME)).thenReturn(optionSpec); + when(optionSpec.getValue()).thenReturn(OPTION_VALUE); + + String result = getOptionValueOrDefault(commandLine, OPTION_NAME, converter); + assertThat(result).isEqualTo(OPTION_VALUE); + } + + @Test + public void testGetOptionValueOrDefault_DefaultValue() throws Exception { + when(defaultValueProvider.defaultValue(optionSpec)).thenReturn(DEFAULT_VALUE); + String result = getOptionValueOrDefault(commandLine, OPTION_NAME, converter); + assertThat(result).isEqualTo(DEFAULT_VALUE); + } + + @Test + public void userOptionOverridesDefaultValue() throws Exception { + when(parseResult.matchedOption(OPTION_NAME)).thenReturn(optionSpec); + when(optionSpec.getValue()).thenReturn(OPTION_VALUE); + + when(defaultValueProvider.defaultValue(optionSpec)).thenReturn(DEFAULT_VALUE); + String result = getOptionValueOrDefault(commandLine, OPTION_NAME, converter); + assertThat(result).isEqualTo(OPTION_VALUE); + } + + @Test + public void testGetOptionValueOrDefault_NoValueOrDefault() { + String result = getOptionValueOrDefault(commandLine, OPTION_NAME, converter); + assertThat(result).isNull(); + } + + @Test + public void testGetOptionValueOrDefault_ConversionFailure() throws Exception { + when(defaultValueProvider.defaultValue(optionSpec)).thenReturn(DEFAULT_VALUE); + + CommandLine.ITypeConverter failingConverter = + value -> { + throw new Exception("Conversion failed"); + }; + + String actualMessage = + assertThrows( + RuntimeException.class, + () -> getOptionValueOrDefault(commandLine, OPTION_NAME, failingConverter)) + .getMessage(); + final String expectedMessage = + "Failed to convert default value for option option: Conversion failed"; + assertThat(actualMessage).isEqualTo(expectedMessage); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/CommandLineUtilsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/CommandLineUtilsTest.java index f8bc8d8972d..24b840dc74b 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/CommandLineUtilsTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/CommandLineUtilsTest.java @@ -22,7 +22,7 @@ import org.hyperledger.besu.cli.util.CommandLineUtils; import org.hyperledger.besu.cli.util.EnvironmentVariableDefaultProvider; -import org.hyperledger.besu.cli.util.TomlConfigFileDefaultProvider; +import org.hyperledger.besu.cli.util.TomlConfigurationDefaultProvider; import org.hyperledger.besu.util.StringUtils; import java.io.IOException; @@ -35,18 +35,18 @@ import java.util.List; import java.util.Map; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; import org.slf4j.Logger; import picocli.CommandLine; import picocli.CommandLine.Command; import picocli.CommandLine.Option; import picocli.CommandLine.RunLast; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class CommandLineUtilsTest { @SuppressWarnings("PrivateStaticFinalLoggers") // @Mocks are inited by JUnit @Mock @@ -69,7 +69,6 @@ private abstract static class AbstractTestCommand implements Runnable { commandLine.setDefaultValueProvider(new EnvironmentVariableDefaultProvider(environment)); } - // Completely disables p2p within Besu. @Option( names = {"--option-enabled"}, arity = "1") @@ -252,7 +251,7 @@ public void multipleMainOptionsToml() throws IOException { final AbstractTestCommand testCommand = new TestMultiCommandWithDeps(mockLogger); testCommand.commandLine.setDefaultValueProvider( - new TomlConfigFileDefaultProvider(testCommand.commandLine, toml.toFile())); + TomlConfigurationDefaultProvider.fromFile(testCommand.commandLine, toml.toFile())); testCommand.commandLine.parseWithHandlers(new RunLast(), defaultExceptionHandler()); verifyMultiOptionsConstraintLoggerCall( diff --git a/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java b/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java index 8597c0582df..3dd9641b487 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/CommandTestAbstract.java @@ -15,14 +15,18 @@ package org.hyperledger.besu.cli; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.cli.util.CommandLineUtils.DEPENDENCY_WARNING_MSG; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import org.hyperledger.besu.Runner; @@ -40,8 +44,7 @@ import org.hyperledger.besu.cli.options.unstable.NetworkingOptions; import org.hyperledger.besu.cli.options.unstable.SynchronizerOptions; import org.hyperledger.besu.components.BesuComponent; -import org.hyperledger.besu.consensus.qbft.pki.PkiBlockCreationConfiguration; -import org.hyperledger.besu.consensus.qbft.pki.PkiBlockCreationConfigurationProvider; +import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.controller.BesuController; import org.hyperledger.besu.controller.BesuControllerBuilder; import org.hyperledger.besu.controller.NoopPluginServiceFactory; @@ -69,22 +72,24 @@ import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; -import org.hyperledger.besu.pki.config.PkiKeyStoreConfiguration; -import org.hyperledger.besu.plugin.services.BesuConfiguration; import org.hyperledger.besu.plugin.services.PicoCLIOptions; import org.hyperledger.besu.plugin.services.StorageService; +import org.hyperledger.besu.plugin.services.TransactionSelectionService; import org.hyperledger.besu.plugin.services.securitymodule.SecurityModule; import org.hyperledger.besu.plugin.services.storage.KeyValueStorageFactory; import org.hyperledger.besu.plugin.services.storage.PrivacyKeyValueStorageFactory; import org.hyperledger.besu.plugin.services.storage.SegmentIdentifier; +import org.hyperledger.besu.services.BesuConfigurationImpl; import org.hyperledger.besu.services.BesuPluginContextImpl; +import org.hyperledger.besu.services.BlockchainServiceImpl; import org.hyperledger.besu.services.PermissioningServiceImpl; -import org.hyperledger.besu.services.PluginTransactionValidatorServiceImpl; import org.hyperledger.besu.services.PrivacyPluginServiceImpl; import org.hyperledger.besu.services.RpcEndpointServiceImpl; import org.hyperledger.besu.services.SecurityModuleServiceImpl; import org.hyperledger.besu.services.StorageServiceImpl; +import org.hyperledger.besu.services.TransactionPoolValidatorServiceImpl; import org.hyperledger.besu.services.TransactionSelectionServiceImpl; +import org.hyperledger.besu.services.TransactionSimulationServiceImpl; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import java.io.ByteArrayOutputStream; @@ -109,41 +114,62 @@ import io.vertx.core.Vertx; import io.vertx.core.VertxOptions; import io.vertx.core.json.JsonObject; +import org.apache.commons.net.util.SubnetUtils.SubnetInfo; +import org.apache.commons.text.StringEscapeUtils; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.awaitility.Awaitility; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import picocli.CommandLine; import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.RunLast; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public abstract class CommandTestAbstract { private static final Logger TEST_LOGGER = LoggerFactory.getLogger(CommandTestAbstract.class); + + protected static final int POA_BLOCK_PERIOD_SECONDS = 5; protected static final JsonObject VALID_GENESIS_QBFT_POST_LONDON = (new JsonObject()) .put( "config", new JsonObject() .put("londonBlock", 0) - .put("qbft", new JsonObject().put("blockperiodseconds", 5))); + .put( + "qbft", + new JsonObject().put("blockperiodseconds", POA_BLOCK_PERIOD_SECONDS))); protected static final JsonObject VALID_GENESIS_IBFT2_POST_LONDON = (new JsonObject()) .put( "config", new JsonObject() .put("londonBlock", 0) - .put("ibft2", new JsonObject().put("blockperiodseconds", 5))); + .put( + "ibft2", + new JsonObject().put("blockperiodseconds", POA_BLOCK_PERIOD_SECONDS))); + + protected static final JsonObject VALID_GENESIS_CLIQUE_POST_LONDON = + (new JsonObject()) + .put( + "config", + new JsonObject() + .put("londonBlock", 0) + .put( + "clique", + new JsonObject().put("blockperiodseconds", POA_BLOCK_PERIOD_SECONDS))); + + protected static final JsonObject GENESIS_WITH_ZERO_BASE_FEE_MARKET = + new JsonObject().put("config", new JsonObject().put("zeroBaseFee", true)); + protected final PrintStream originalOut = System.out; protected final PrintStream originalErr = System.err; protected final ByteArrayOutputStream commandOutput = new ByteArrayOutputStream(); @@ -156,24 +182,38 @@ public abstract class CommandTestAbstract { protected static final RpcEndpointServiceImpl rpcEndpointServiceImpl = new RpcEndpointServiceImpl(); - @Mock protected RunnerBuilder mockRunnerBuilder; + @Mock(lenient = true) + protected RunnerBuilder mockRunnerBuilder; + @Mock protected Runner mockRunner; - @Mock protected BesuController.Builder mockControllerBuilderFactory; + @Mock(lenient = true) + protected BesuController.Builder mockControllerBuilderFactory; + + @Mock(lenient = true) + protected BesuControllerBuilder mockControllerBuilder; + + @Mock(lenient = true) + protected EthProtocolManager mockEthProtocolManager; - @Mock protected BesuControllerBuilder mockControllerBuilder; - @Mock protected EthProtocolManager mockEthProtocolManager; @Mock protected ProtocolSchedule mockProtocolSchedule; - @Mock protected ProtocolContext mockProtocolContext; + + @Mock(lenient = true) + protected ProtocolContext mockProtocolContext; + @Mock protected BlockBroadcaster mockBlockBroadcaster; - @Mock protected BesuController mockController; + + @Mock(lenient = true) + protected BesuController mockController; + @Mock protected RlpBlockExporter rlpBlockExporter; @Mock protected JsonBlockImporter jsonBlockImporter; @Mock protected RlpBlockImporter rlpBlockImporter; @Mock protected StorageServiceImpl storageService; + @Mock protected TransactionSelectionServiceImpl txSelectionService; @Mock protected SecurityModuleServiceImpl securityModuleService; @Mock protected SecurityModule securityModule; - @Mock protected BesuConfiguration commonPluginConfiguration; + @Spy protected BesuConfigurationImpl commonPluginConfiguration = new BesuConfigurationImpl(); @Mock protected KeyValueStorageFactory rocksDBStorageFactory; @Mock protected PrivacyKeyValueStorageFactory rocksDBSPrivacyStorageFactory; @Mock protected PicoCLIOptions cliOptions; @@ -183,16 +223,12 @@ public abstract class CommandTestAbstract { @Mock protected WorldStateArchive mockWorldStateArchive; @Mock protected TransactionPool mockTransactionPool; @Mock protected PrivacyPluginServiceImpl privacyPluginService; + @Mock protected StorageProvider storageProvider; @SuppressWarnings("PrivateStaticFinalLoggers") // @Mocks are inited by JUnit @Mock protected Logger mockLogger; - @Mock protected BesuComponent mockBesuComponent; - - @Mock protected PkiBlockCreationConfigurationProvider mockPkiBlockCreationConfigProvider; - @Mock protected PkiBlockCreationConfiguration mockPkiBlockCreationConfiguration; - @Captor protected ArgumentCaptor> bytesCollectionCollector; @Captor protected ArgumentCaptor pathArgumentCaptor; @Captor protected ArgumentCaptor stringArgumentCaptor; @@ -207,7 +243,6 @@ public abstract class CommandTestAbstract { @Captor protected ArgumentCaptor storageProviderArgumentCaptor; @Captor protected ArgumentCaptor ethProtocolConfigurationArgumentCaptor; @Captor protected ArgumentCaptor dataStorageConfigurationArgumentCaptor; - @Captor protected ArgumentCaptor pkiKeyStoreConfigurationArgumentCaptor; @Captor protected ArgumentCaptor> @@ -217,15 +252,14 @@ public abstract class CommandTestAbstract { @Captor protected ArgumentCaptor apiConfigurationCaptor; @Captor protected ArgumentCaptor ethstatsOptionsArgumentCaptor; + @Captor protected ArgumentCaptor> allowedSubnetsArgumentCaptor; - @Rule public final TemporaryFolder temp = new TemporaryFolder(); - - @Before + @BeforeEach public void initMocks() throws Exception { // doReturn used because of generic BesuController doReturn(mockControllerBuilder) .when(mockControllerBuilderFactory) - .fromEthNetworkConfig(any(), any(), any()); + .fromEthNetworkConfig(any(), any()); when(mockControllerBuilder.synchronizerConfiguration(any())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.ethProtocolConfiguration(any())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.transactionPoolConfiguration(any())) @@ -237,14 +271,11 @@ public void initMocks() throws Exception { when(mockControllerBuilder.messagePermissioningProviders(any())) .thenReturn(mockControllerBuilder); when(mockControllerBuilder.privacyParameters(any())).thenReturn(mockControllerBuilder); - when(mockControllerBuilder.pkiBlockCreationConfiguration(any())) - .thenReturn(mockControllerBuilder); when(mockControllerBuilder.clock(any())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.isRevertReasonEnabled(false)).thenReturn(mockControllerBuilder); + when(mockControllerBuilder.isParallelTxProcessingEnabled(false)) + .thenReturn(mockControllerBuilder); when(mockControllerBuilder.storageProvider(any())).thenReturn(mockControllerBuilder); - when(mockControllerBuilder.isPruningEnabled(anyBoolean())).thenReturn(mockControllerBuilder); - when(mockControllerBuilder.pruningConfiguration(any())).thenReturn(mockControllerBuilder); - when(mockControllerBuilder.genesisConfigOverrides(any())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.gasLimitCalculator(any())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.requiredBlocks(any())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.reorgLoggingThreshold(anyLong())).thenReturn(mockControllerBuilder); @@ -255,15 +286,13 @@ public void initMocks() throws Exception { when(mockControllerBuilder.maxPeers(anyInt())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.chainPruningConfiguration(any())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.maxPeers(anyInt())).thenReturn(mockControllerBuilder); - when(mockControllerBuilder.lowerBoundPeers(anyInt())).thenReturn(mockControllerBuilder); when(mockControllerBuilder.maxRemotelyInitiatedPeers(anyInt())) .thenReturn(mockControllerBuilder); - when(mockControllerBuilder.transactionSelectorFactory(any())).thenReturn(mockControllerBuilder); - when(mockControllerBuilder.pluginTransactionValidatorFactory(any())) - .thenReturn(mockControllerBuilder); when(mockControllerBuilder.besuComponent(any(BesuComponent.class))) .thenReturn(mockControllerBuilder); when(mockControllerBuilder.cacheLastBlocks(any())).thenReturn(mockControllerBuilder); + when(mockControllerBuilder.genesisStateHashCacheEnabled(any())) + .thenReturn(mockControllerBuilder); // doReturn used because of generic BesuController doReturn(mockController).when(mockControllerBuilder).build(); @@ -280,6 +309,7 @@ public void initMocks() throws Exception { when(mockProtocolContext.getBlockchain()).thenReturn(mockMutableBlockchain); lenient().when(mockProtocolContext.getWorldStateArchive()).thenReturn(mockWorldStateArchive); when(mockController.getTransactionPool()).thenReturn(mockTransactionPool); + when(mockController.getStorageProvider()).thenReturn(storageProvider); when(mockRunnerBuilder.vertx(any())).thenReturn(mockRunnerBuilder); when(mockRunnerBuilder.besuController(any())).thenReturn(mockRunnerBuilder); @@ -299,6 +329,7 @@ public void initMocks() throws Exception { when(mockRunnerBuilder.graphQLConfiguration(any())).thenReturn(mockRunnerBuilder); when(mockRunnerBuilder.webSocketConfiguration(any())).thenReturn(mockRunnerBuilder); when(mockRunnerBuilder.jsonRpcIpcConfiguration(any())).thenReturn(mockRunnerBuilder); + when(mockRunnerBuilder.inProcessRpcConfiguration(any())).thenReturn(mockRunnerBuilder); when(mockRunnerBuilder.apiConfiguration(any())).thenReturn(mockRunnerBuilder); when(mockRunnerBuilder.dataDir(any())).thenReturn(mockRunnerBuilder); when(mockRunnerBuilder.bannedNodeIds(any())).thenReturn(mockRunnerBuilder); @@ -316,6 +347,8 @@ public void initMocks() throws Exception { when(mockRunnerBuilder.legacyForkId(anyBoolean())).thenReturn(mockRunnerBuilder); when(mockRunnerBuilder.apiConfiguration(any())).thenReturn(mockRunnerBuilder); when(mockRunnerBuilder.enodeDnsConfiguration(any())).thenReturn(mockRunnerBuilder); + when(mockRunnerBuilder.allowedSubnets(any())).thenReturn(mockRunnerBuilder); + when(mockRunnerBuilder.poaDiscoveryRetryBootnodes(anyBoolean())).thenReturn(mockRunnerBuilder); when(mockRunnerBuilder.build()).thenReturn(mockRunner); final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithmFactory.getInstance(); @@ -345,15 +378,12 @@ public void initMocks() throws Exception { lenient() .when(mockBesuPluginContext.getService(StorageService.class)) .thenReturn(Optional.of(storageService)); - lenient() - .doReturn(mockPkiBlockCreationConfiguration) - .when(mockPkiBlockCreationConfigProvider) - .load(pkiKeyStoreConfigurationArgumentCaptor.capture()); - when(mockBesuComponent.getBesuCommandLogger()).thenReturn(mockLogger); + .when(mockBesuPluginContext.getService(TransactionSelectionService.class)) + .thenReturn(Optional.of(txSelectionService)); } - @Before + @BeforeEach public void setUpStreams() { // reset the global opentelemetry singleton GlobalOpenTelemetry.resetForTest(); @@ -364,7 +394,7 @@ public void setUpStreams() { } // Display outputs for debug purpose - @After + @AfterEach public void displayOutput() throws IOException { TEST_LOGGER.info("Standard output {}", commandOutput.toString(UTF_8)); TEST_LOGGER.info("Standard error {}", commandErrorOutput.toString(UTF_8)); @@ -435,7 +465,6 @@ private TestBesuCommand getTestBesuCommand(final TestType testType) { switch (testType) { case REQUIRED_OPTION: return new TestBesuCommandWithRequiredOption( - mockBesuComponent, () -> rlpBlockImporter, this::jsonBlockImporterFactory, (blockchain) -> rlpBlockExporter, @@ -445,11 +474,10 @@ private TestBesuCommand getTestBesuCommand(final TestType testType) { environment, storageService, securityModuleService, - mockPkiBlockCreationConfigProvider, - privacyPluginService); + privacyPluginService, + mockLogger); case PORT_CHECK: return new TestBesuCommand( - mockBesuComponent, () -> rlpBlockImporter, this::jsonBlockImporterFactory, (blockchain) -> rlpBlockExporter, @@ -459,11 +487,10 @@ private TestBesuCommand getTestBesuCommand(final TestType testType) { environment, storageService, securityModuleService, - mockPkiBlockCreationConfigProvider, - privacyPluginService); + privacyPluginService, + mockLogger); default: return new TestBesuCommandWithoutPortCheck( - mockBesuComponent, () -> rlpBlockImporter, this::jsonBlockImporterFactory, (blockchain) -> rlpBlockExporter, @@ -473,8 +500,8 @@ private TestBesuCommand getTestBesuCommand(final TestType testType) { environment, storageService, securityModuleService, - mockPkiBlockCreationConfigProvider, - privacyPluginService); + privacyPluginService, + mockLogger); } } @@ -504,7 +531,6 @@ public static class TestBesuCommand extends BesuCommand { private Vertx vertx; TestBesuCommand( - final BesuComponent besuComponent, final Supplier mockBlockImporter, final Function jsonBlockImporterFactory, final Function rlpBlockExporterFactory, @@ -514,10 +540,9 @@ public static class TestBesuCommand extends BesuCommand { final Map environment, final StorageServiceImpl storageService, final SecurityModuleServiceImpl securityModuleService, - final PkiBlockCreationConfigurationProvider pkiBlockCreationConfigProvider, - final PrivacyPluginServiceImpl privacyPluginService) { + final PrivacyPluginServiceImpl privacyPluginService, + final Logger commandLogger) { super( - besuComponent, mockBlockImporter, jsonBlockImporterFactory, rlpBlockExporterFactory, @@ -529,10 +554,12 @@ public static class TestBesuCommand extends BesuCommand { securityModuleService, new PermissioningServiceImpl(), privacyPluginService, - pkiBlockCreationConfigProvider, rpcEndpointServiceImpl, new TransactionSelectionServiceImpl(), - new PluginTransactionValidatorServiceImpl()); + new TransactionPoolValidatorServiceImpl(), + new TransactionSimulationServiceImpl(), + new BlockchainServiceImpl(), + commandLogger); } @Override @@ -546,6 +573,11 @@ protected Vertx createVertx(final VertxOptions vertxOptions) { return vertx; } + @Override + public GenesisConfigOptions getGenesisConfigOptions() { + return super.getGenesisConfigOptions(); + } + public CommandSpec getSpec() { return spec; } @@ -598,7 +630,6 @@ public static class TestBesuCommandWithRequiredOption extends TestBesuCommand { private final Boolean acceptTermsAndConditions = false; TestBesuCommandWithRequiredOption( - final BesuComponent besuComponent, final Supplier mockBlockImporter, final Function jsonBlockImporterFactory, final Function rlpBlockExporterFactory, @@ -608,10 +639,9 @@ public static class TestBesuCommandWithRequiredOption extends TestBesuCommand { final Map environment, final StorageServiceImpl storageService, final SecurityModuleServiceImpl securityModuleService, - final PkiBlockCreationConfigurationProvider pkiBlockCreationConfigProvider, - final PrivacyPluginServiceImpl privacyPluginService) { + final PrivacyPluginServiceImpl privacyPluginService, + final Logger commandLogger) { super( - besuComponent, mockBlockImporter, jsonBlockImporterFactory, rlpBlockExporterFactory, @@ -621,8 +651,8 @@ public static class TestBesuCommandWithRequiredOption extends TestBesuCommand { environment, storageService, securityModuleService, - pkiBlockCreationConfigProvider, - privacyPluginService); + privacyPluginService, + commandLogger); } public Boolean getAcceptTermsAndConditions() { @@ -634,7 +664,6 @@ public Boolean getAcceptTermsAndConditions() { public static class TestBesuCommandWithoutPortCheck extends TestBesuCommand { TestBesuCommandWithoutPortCheck( - final BesuComponent context, final Supplier mockBlockImporter, final Function jsonBlockImporterFactory, final Function rlpBlockExporterFactory, @@ -644,10 +673,9 @@ public static class TestBesuCommandWithoutPortCheck extends TestBesuCommand { final Map environment, final StorageServiceImpl storageService, final SecurityModuleServiceImpl securityModuleService, - final PkiBlockCreationConfigurationProvider pkiBlockCreationConfigProvider, - final PrivacyPluginServiceImpl privacyPluginService) { + final PrivacyPluginServiceImpl privacyPluginService, + final Logger commandLogger) { super( - context, mockBlockImporter, jsonBlockImporterFactory, rlpBlockExporterFactory, @@ -657,8 +685,8 @@ public static class TestBesuCommandWithoutPortCheck extends TestBesuCommand { environment, storageService, securityModuleService, - pkiBlockCreationConfigProvider, - privacyPluginService); + privacyPluginService, + commandLogger); } @Override @@ -672,4 +700,45 @@ private enum TestType { PORT_CHECK, NO_PORT_CHECK } + + protected static String escapeTomlString(final String s) { + return StringEscapeUtils.escapeJava(s); + } + + /** + * Check logger calls + * + *

Here we check the calls to logger and not the result of the log line as we don't test the + * logger itself but the fact that we call it. + * + * @param dependentOptions the string representing the list of dependent options names + * @param mainOption the main option name + */ + protected void verifyOptionsConstraintLoggerCall( + final String mainOption, final String... dependentOptions) { + verify(mockLogger, atLeast(1)) + .warn( + stringArgumentCaptor.capture(), + stringArgumentCaptor.capture(), + stringArgumentCaptor.capture()); + assertThat(stringArgumentCaptor.getAllValues().get(0)).isEqualTo(DEPENDENCY_WARNING_MSG); + + for (final String option : dependentOptions) { + assertThat(stringArgumentCaptor.getAllValues().get(1)).contains(option); + } + + assertThat(stringArgumentCaptor.getAllValues().get(2)).isEqualTo(mainOption); + } + + /** + * Check logger calls + * + *

Here we check the calls to logger and not the result of the log line as we don't test the + * logger itself but the fact that we call it. + * + * @param stringToLog the string that is logged + */ + void verifyMultiOptionsConstraintLoggerCall(final String stringToLog) { + verify(mockLogger, atLeast(1)).warn(stringToLog); + } } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilderTest.java index 44718e96c67..841c6680007 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/ConfigurationOverviewBuilderTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,8 +17,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration.Implementation.LAYERED; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration.Implementation.LEGACY; +import static org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration.Implementation.SEQUENCED; import static org.mockito.Mockito.mock; +import org.hyperledger.besu.cli.config.InternalProfileName; import org.hyperledger.besu.evm.internal.EvmConfiguration; import java.math.BigInteger; @@ -57,8 +59,9 @@ void setGenesisFile() { assertThat(networkSet).contains("Network: foobar"); builder.setHasCustomGenesis(true); + builder.setCustomGenesis("file.name"); final String genesisSet = builder.build(); - assertThat(genesisSet).contains("Network: Custom genesis file specified"); + assertThat(genesisSet).contains("Network: Custom genesis file"); assertThat(genesisSet).doesNotContain("Network: foobar"); } @@ -149,21 +152,21 @@ void setHighSpecEnabled() { } @Test - void setTrieLogPruningEnabled() { - final String noTrieLogRetentionThresholdSet = builder.build(); - assertThat(noTrieLogRetentionThresholdSet).doesNotContain("Trie log pruning enabled"); + void setBonsaiLimitTrieLogsEnabled() { + final String noTrieLogRetentionLimitSet = builder.build(); + assertThat(noTrieLogRetentionLimitSet).doesNotContain("Limit trie logs enabled"); - builder.setTrieLogPruningEnabled(); - builder.setTrieLogRetentionThreshold(42); - String trieLogRetentionThresholdSet = builder.build(); - assertThat(trieLogRetentionThresholdSet) - .contains("Trie log pruning enabled") + builder.setLimitTrieLogsEnabled(); + builder.setTrieLogRetentionLimit(42); + String trieLogRetentionLimitSet = builder.build(); + assertThat(trieLogRetentionLimitSet) + .contains("Limit trie logs enabled") .contains("retention: 42"); - assertThat(trieLogRetentionThresholdSet).doesNotContain("prune limit"); + assertThat(trieLogRetentionLimitSet).doesNotContain("prune window"); - builder.setTrieLogPruningLimit(1000); - trieLogRetentionThresholdSet = builder.build(); - assertThat(trieLogRetentionThresholdSet).contains("prune limit: 1000"); + builder.setTrieLogsPruningWindowSize(1000); + trieLogRetentionLimitSet = builder.build(); + assertThat(trieLogRetentionLimitSet).contains("prune window: 1000"); } @Test @@ -180,6 +183,13 @@ void setTxPoolImplementationLegacy() { assertThat(legacyTxPoolSelected).contains("Using LEGACY transaction pool implementation"); } + @Test + void setTxPoolImplementationSequenced() { + builder.setTxPoolImplementation(SEQUENCED); + final String sequencedTxPoolSelected = builder.build(); + assertThat(sequencedTxPoolSelected).contains("Using SEQUENCED transaction pool implementation"); + } + @Test void setWorldStateUpdateModeDefault() { builder.setWorldStateUpdateMode(EvmConfiguration.DEFAULT.worldUpdaterMode()); @@ -200,4 +210,11 @@ void setWorldStateUpdateModeJournaled() { final String layeredTxPoolSelected = builder.build(); assertThat(layeredTxPoolSelected).contains("Using JOURNALED worldstate update mode"); } + + @Test + void setProfile() { + builder.setProfile(InternalProfileName.DEV.name()); + final String profileSelected = builder.build(); + assertThat(profileSelected).contains("Profile: DEV"); + } } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/EnvironmentVariableDefaultProviderTest.java b/besu/src/test/java/org/hyperledger/besu/cli/EnvironmentVariableDefaultProviderTest.java index af609327340..9953ca519f7 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/EnvironmentVariableDefaultProviderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/EnvironmentVariableDefaultProviderTest.java @@ -21,13 +21,13 @@ import java.util.HashMap; import java.util.Map; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; import picocli.CommandLine.Model.OptionSpec; import picocli.CommandLine.Model.PositionalParamSpec; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class EnvironmentVariableDefaultProviderTest { private final Map environment = new HashMap<>(); diff --git a/besu/src/test/java/org/hyperledger/besu/cli/HostAllowlistOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/HostAllowlistOptionsTest.java new file mode 100644 index 00000000000..c1f867f4892 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/HostAllowlistOptionsTest.java @@ -0,0 +1,209 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; + +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +public class HostAllowlistOptionsTest extends CommandTestAbstract { + + /** test deprecated CLI option * */ + @Deprecated + @Test + public void rpcHttpHostWhitelistAcceptsSingleArgument() { + parseCommand("--host-whitelist", "a"); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist().size()).isEqualTo(1); + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist()).contains("a"); + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist()) + .doesNotContain("localhost"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpHostAllowlistAcceptsSingleArgument() { + parseCommand("--host-allowlist", "a"); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist().size()).isEqualTo(1); + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist()).contains("a"); + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist()) + .doesNotContain("localhost"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpHostAllowlistAcceptsMultipleArguments() { + parseCommand("--host-allowlist", "a,b"); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist().size()).isEqualTo(2); + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist()).contains("a", "b"); + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist()) + .doesNotContain("*", "localhost"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpHostAllowlistAcceptsDoubleComma() { + parseCommand("--host-allowlist", "a,,b"); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist().size()).isEqualTo(2); + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist()).contains("a", "b"); + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist()) + .doesNotContain("*", "localhost"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Deprecated + @Test + public void rpcHttpHostWhitelistAllowlistAcceptsMultipleFlags() { + parseCommand("--host-whitelist=a", "--host-allowlist=b"); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist().size()).isEqualTo(2); + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist()).contains("a", "b"); + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist()) + .doesNotContain("*", "localhost"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpHostAllowlistAcceptsMultipleFlags() { + parseCommand("--host-allowlist=a", "--host-allowlist=b"); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist().size()).isEqualTo(2); + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist()).contains("a", "b"); + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist()) + .doesNotContain("*", "localhost"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpHostAllowlistStarWithAnotherHostnameMustFail() { + final String[] origins = {"friend", "*"}; + parseCommand("--host-allowlist", String.join(",", origins)); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Values '*' or 'all' can't be used with other hostnames"); + } + + @Test + public void rpcHttpHostAllowlistStarWithAnotherHostnameMustFailStarFirst() { + final String[] origins = {"*", "friend"}; + parseCommand("--host-allowlist", String.join(",", origins)); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Values '*' or 'all' can't be used with other hostnames"); + } + + @Test + public void rpcHttpHostAllowlistAllWithAnotherHostnameMustFail() { + final String[] origins = {"friend", "all"}; + parseCommand("--host-allowlist", String.join(",", origins)); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Values '*' or 'all' can't be used with other hostnames"); + } + + @Test + public void rpcHttpHostAllowlistWithNoneMustBuildEmptyList() { + final String[] origins = {"none"}; + parseCommand("--host-allowlist", String.join(",", origins)); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHostsAllowlist()).isEmpty(); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpHostAllowlistNoneWithAnotherDomainMustFail() { + final String[] origins = {"http://domain1.com", "none"}; + parseCommand("--host-allowlist", String.join(",", origins)); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Value 'none' can't be used with other hostnames"); + } + + @Test + public void rpcHttpHostAllowlistNoneWithAnotherDomainMustFailNoneFirst() { + final String[] origins = {"none", "http://domain1.com"}; + parseCommand("--host-allowlist", String.join(",", origins)); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Value 'none' can't be used with other hostnames"); + } + + @Test + public void rpcHttpHostAllowlistEmptyValueFails() { + parseCommand("--host-allowlist="); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Hostname cannot be empty string or null string."); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/NatOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/NatOptionsTest.java new file mode 100644 index 00000000000..f5fb8cf9543 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/NatOptionsTest.java @@ -0,0 +1,172 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.nat.kubernetes.KubernetesNatManager.DEFAULT_BESU_SERVICE_NAME_FILTER; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +import org.hyperledger.besu.nat.NatMethod; + +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +public class NatOptionsTest extends CommandTestAbstract { + + @Test + public void helpShouldDisplayNatMethodInfo() { + parseCommand("--help"); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandOutput.toString(UTF_8)).contains("--nat-method"); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void natMethodPropertyDefaultIsAuto() { + parseCommand(); + + verify(mockRunnerBuilder).natMethod(eq(NatMethod.AUTO)); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void natManagerPodNamePropertyDefaultIsBesu() { + parseCommand(); + + verify(mockRunnerBuilder).natManagerServiceName(eq(DEFAULT_BESU_SERVICE_NAME_FILTER)); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void natMethodOptionIsParsedCorrectly() { + + parseCommand("--nat-method", "NONE"); + verify(mockRunnerBuilder).natMethod(eq(NatMethod.NONE)); + + parseCommand("--nat-method", "UPNP"); + verify(mockRunnerBuilder).natMethod(eq(NatMethod.UPNP)); + + parseCommand("--nat-method", "UPNPP2PONLY"); + verify(mockRunnerBuilder).natMethod(eq(NatMethod.UPNPP2PONLY)); + + parseCommand("--nat-method", "AUTO"); + verify(mockRunnerBuilder).natMethod(eq(NatMethod.AUTO)); + + parseCommand("--nat-method", "DOCKER"); + verify(mockRunnerBuilder).natMethod(eq(NatMethod.DOCKER)); + + parseCommand("--nat-method", "KUBERNETES"); + verify(mockRunnerBuilder).natMethod(eq(NatMethod.KUBERNETES)); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void natManagerPodNamePropertyIsCorrectlyUpdated() { + final String podName = "besu-updated"; + parseCommand("--Xnat-kube-service-name", podName); + + verify(mockRunnerBuilder).natManagerServiceName(eq(podName)); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void natManagerPodNameCannotBeUsedWithNatDockerMethod() { + parseCommand("--nat-method", "DOCKER", "--Xnat-kube-service-name", "besu-updated"); + Mockito.verifyNoInteractions(mockRunnerBuilder); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains( + "The `--Xnat-kube-service-name` parameter is only used in kubernetes mode. Either remove --Xnat-kube-service-name or select the KUBERNETES mode (via --nat--method=KUBERNETES)"); + } + + @Test + public void natManagerPodNameCannotBeUsedWithNatNoneMethod() { + parseCommand("--nat-method", "NONE", "--Xnat-kube-service-name", "besu-updated"); + Mockito.verifyNoInteractions(mockRunnerBuilder); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains( + "The `--Xnat-kube-service-name` parameter is only used in kubernetes mode. Either remove --Xnat-kube-service-name or select the KUBERNETES mode (via --nat--method=KUBERNETES)"); + } + + @Test + public void natMethodFallbackEnabledPropertyIsCorrectlyUpdatedWithKubernetes() { + + parseCommand("--nat-method", "KUBERNETES", "--Xnat-method-fallback-enabled", "false"); + verify(mockRunnerBuilder).natMethodFallbackEnabled(eq(false)); + parseCommand("--nat-method", "KUBERNETES", "--Xnat-method-fallback-enabled", "true"); + verify(mockRunnerBuilder).natMethodFallbackEnabled(eq(true)); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void natMethodFallbackEnabledPropertyIsCorrectlyUpdatedWithDocker() { + + parseCommand("--nat-method", "DOCKER", "--Xnat-method-fallback-enabled", "false"); + verify(mockRunnerBuilder).natMethodFallbackEnabled(eq(false)); + parseCommand("--nat-method", "DOCKER", "--Xnat-method-fallback-enabled", "true"); + verify(mockRunnerBuilder).natMethodFallbackEnabled(eq(true)); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void natMethodFallbackEnabledPropertyIsCorrectlyUpdatedWithUpnp() { + + parseCommand("--nat-method", "UPNP", "--Xnat-method-fallback-enabled", "false"); + verify(mockRunnerBuilder).natMethodFallbackEnabled(eq(false)); + parseCommand("--nat-method", "UPNP", "--Xnat-method-fallback-enabled", "true"); + verify(mockRunnerBuilder).natMethodFallbackEnabled(eq(true)); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void natMethodFallbackEnabledCannotBeUsedWithAutoMethod() { + parseCommand("--nat-method", "AUTO", "--Xnat-method-fallback-enabled", "false"); + Mockito.verifyNoInteractions(mockRunnerBuilder); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains( + "The `--Xnat-method-fallback-enabled` parameter cannot be used in AUTO mode. Either remove --Xnat-method-fallback-enabled or select another mode (via --nat--method=XXXX)"); + } + + @Test + public void parsesInvalidNatMethodOptionsShouldFail() { + + parseCommand("--nat-method", "invalid"); + Mockito.verifyNoInteractions(mockRunnerBuilder); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains( + "Invalid value for option '--nat-method': expected one of [UPNP, UPNPP2PONLY, DOCKER, KUBERNETES, AUTO, NONE] (case-insensitive) but was 'invalid'"); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/NetworkDeprecationMessageTest.java b/besu/src/test/java/org/hyperledger/besu/cli/NetworkDeprecationMessageTest.java index f40dde9313d..87be2a699e3 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/NetworkDeprecationMessageTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/NetworkDeprecationMessageTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -20,6 +20,7 @@ import org.hyperledger.besu.cli.config.NetworkName; import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; @@ -39,10 +40,17 @@ void shouldGenerateDeprecationMessageForDeprecatedNetworks(final NetworkName net @EnumSource( value = NetworkName.class, names = { - "MAINNET", "SEPOLIA", "GOERLI", "DEV", "CLASSIC", "MORDOR", "HOLESKY", + "MAINNET", "SEPOLIA", "DEV", "CLASSIC", "MORDOR", "HOLESKY", "LUKSO", }) void shouldThrowErrorForNonDeprecatedNetworks(final NetworkName network) { assertThatThrownBy(() -> NetworkDeprecationMessage.generate(network)) .isInstanceOf(AssertionError.class); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/PasswordSubCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/PasswordSubCommandTest.java index 62d35830c0b..5d661f92038 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/PasswordSubCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/PasswordSubCommandTest.java @@ -19,12 +19,12 @@ import org.hyperledger.besu.BesuInfo; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; import picocli.CommandLine.Model.CommandSpec; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class PasswordSubCommandTest extends CommandTestAbstract { @Test diff --git a/besu/src/test/java/org/hyperledger/besu/cli/PluginsOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/PluginsOptionsTest.java new file mode 100644 index 00000000000..1adfb585e9d --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/PluginsOptionsTest.java @@ -0,0 +1,118 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; + +import org.hyperledger.besu.ethereum.core.plugins.PluginConfiguration; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; + +public class PluginsOptionsTest extends CommandTestAbstract { + + @Captor protected ArgumentCaptor pluginConfigurationArgumentCaptor; + + @Test + public void shouldParsePluginOptionForSinglePlugin() { + parseCommand("--plugins", "pluginA"); + verify(mockBesuPluginContext).registerPlugins(pluginConfigurationArgumentCaptor.capture()); + assertThat(pluginConfigurationArgumentCaptor.getValue().getRequestedPlugins()) + .isEqualTo(List.of("pluginA")); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void shouldParsePluginOptionForMultiplePlugins() { + parseCommand("--plugins", "pluginA,pluginB"); + verify(mockBesuPluginContext).registerPlugins(pluginConfigurationArgumentCaptor.capture()); + assertThat(pluginConfigurationArgumentCaptor.getValue().getRequestedPlugins()) + .isEqualTo(List.of("pluginA", "pluginB")); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void shouldNotUsePluginOptionWhenNoPluginsSpecified() { + parseCommand(); + verify(mockBesuPluginContext).registerPlugins(pluginConfigurationArgumentCaptor.capture()); + assertThat(pluginConfigurationArgumentCaptor.getValue().getRequestedPlugins()) + .isEqualTo(List.of()); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void shouldNotParseAnyPluginsWhenPluginOptionIsEmpty() { + parseCommand("--plugins", ""); + verify(mockBesuPluginContext).registerPlugins(pluginConfigurationArgumentCaptor.capture()); + assertThat(pluginConfigurationArgumentCaptor.getValue().getRequestedPlugins()) + .isEqualTo(List.of()); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void shouldParsePluginsExternalEnabledOptionWhenFalse() { + parseCommand("--Xplugins-external-enabled=false"); + verify(mockBesuPluginContext).registerPlugins(pluginConfigurationArgumentCaptor.capture()); + + assertThat(pluginConfigurationArgumentCaptor.getValue().isExternalPluginsEnabled()) + .isEqualTo(false); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void shouldParsePluginsExternalEnabledOptionWhenTrue() { + parseCommand("--Xplugins-external-enabled=true"); + verify(mockBesuPluginContext).registerPlugins(pluginConfigurationArgumentCaptor.capture()); + + assertThat(pluginConfigurationArgumentCaptor.getValue().isExternalPluginsEnabled()) + .isEqualTo(true); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void shouldEnablePluginsExternalByDefault() { + parseCommand(); + verify(mockBesuPluginContext).registerPlugins(pluginConfigurationArgumentCaptor.capture()); + assertThat(pluginConfigurationArgumentCaptor.getValue().isExternalPluginsEnabled()) + .isEqualTo(true); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void shouldFailWhenPluginsIsDisabledAndPluginsExplicitlyRequested() { + parseCommand("--Xplugins-external-enabled=false", "--plugins", "pluginA"); + verify(mockBesuPluginContext).registerPlugins(pluginConfigurationArgumentCaptor.capture()); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("--plugins option can only be used when --Xplugins-external-enabled is true"); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/PrivacyOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/PrivacyOptionsTest.java new file mode 100644 index 00000000000..af43535a8ff --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/PrivacyOptionsTest.java @@ -0,0 +1,509 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assume.assumeThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.plugin.services.privacy.PrivateMarkerTransactionFactory; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +public class PrivacyOptionsTest extends CommandTestAbstract { + private static final String ENCLAVE_URI = "http://1.2.3.4:5555"; + private static final String ENCLAVE_PUBLIC_KEY = "A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="; + private static final String ENCLAVE_PUBLIC_KEY_PATH = + BesuCommand.class.getResource("/orion_publickey.pub").getPath(); + + @Test + public void privacyTlsOptionsRequiresTlsToBeEnabled() { + when(storageService.getByName("rocksdb-privacy")) + .thenReturn(Optional.of(rocksDBSPrivacyStorageFactory)); + final URL configFile = this.getClass().getResource("/orion_publickey.pub"); + final String coinbaseStr = String.format("%040x", 1); + + parseCommand( + "--privacy-enabled", + "--miner-enabled", + "--miner-coinbase=" + coinbaseStr, + "--min-gas-price", + "0", + "--privacy-url", + ENCLAVE_URI, + "--privacy-public-key-file", + configFile.getPath(), + "--privacy-tls-keystore-file", + "/Users/me/key"); + + verifyOptionsConstraintLoggerCall("--privacy-tls-enabled", "--privacy-tls-keystore-file"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void privacyTlsOptionsRequiresTlsToBeEnabledToml() throws IOException { + when(storageService.getByName("rocksdb-privacy")) + .thenReturn(Optional.of(rocksDBSPrivacyStorageFactory)); + final URL configFile = this.getClass().getResource("/orion_publickey.pub"); + final String coinbaseStr = String.format("%040x", 1); + + final Path toml = + createTempFile( + "toml", + "privacy-enabled=true\n" + + "miner-enabled=true\n" + + "miner-coinbase=\"" + + coinbaseStr + + "\"\n" + + "min-gas-price=0\n" + + "privacy-url=\"" + + ENCLAVE_URI + + "\"\n" + + "privacy-public-key-file=\"" + + configFile.getPath() + + "\"\n" + + "privacy-tls-keystore-file=\"/Users/me/key\""); + + parseCommand("--config-file", toml.toString()); + + verifyOptionsConstraintLoggerCall("--privacy-tls-enabled", "--privacy-tls-keystore-file"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void privacyTlsOptionsRequiresPrivacyToBeEnabled() { + parseCommand("--privacy-tls-enabled", "--privacy-tls-keystore-file", "/Users/me/key"); + + verifyOptionsConstraintLoggerCall("--privacy-enabled", "--privacy-tls-enabled"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void privacyTlsOptionsRequiresPrivacyToBeEnabledToml() throws IOException { + final Path toml = + createTempFile( + "toml", "privacy-tls-enabled=true\n" + "privacy-tls-keystore-file=\"/Users/me/key\""); + + parseCommand("--config-file", toml.toString()); + + verifyOptionsConstraintLoggerCall("--privacy-enabled", "--privacy-tls-enabled"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void mustUseEnclaveUriAndOptions() { + final URL configFile = this.getClass().getResource("/orion_publickey.pub"); + + parseCommand( + "--privacy-enabled", + "--privacy-url", + ENCLAVE_URI, + "--privacy-public-key-file", + configFile.getPath(), + "--min-gas-price", + "0"); + + final ArgumentCaptor enclaveArg = + ArgumentCaptor.forClass(PrivacyParameters.class); + + verify(mockControllerBuilder).privacyParameters(enclaveArg.capture()); + verify(mockControllerBuilder).build(); + + assertThat(enclaveArg.getValue().isEnabled()).isEqualTo(true); + assertThat(enclaveArg.getValue().getEnclaveUri()).isEqualTo(URI.create(ENCLAVE_URI)); + assertThat(enclaveArg.getValue().getPrivacyUserId()).isEqualTo(ENCLAVE_PUBLIC_KEY); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void privacyOptionsRequiresServiceToBeEnabled() { + + final File file = new File("./specific/enclavePublicKey"); + file.deleteOnExit(); + + parseCommand("--privacy-url", ENCLAVE_URI, "--privacy-public-key-file", file.toString()); + + verifyMultiOptionsConstraintLoggerCall( + "--privacy-url and/or --privacy-public-key-file ignored because none of --privacy-enabled was defined."); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void privacyWithFastSyncMustError() { + parseCommand("--sync-mode=FAST", "--privacy-enabled"); + + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Fast sync cannot be enabled with privacy."); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void privacyWithSnapSyncMustError() { + parseCommand("--sync-mode=SNAP", "--privacy-enabled"); + + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Snap sync cannot be enabled with privacy."); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void privacyWithCheckpointSyncMustError() { + parseCommand("--sync-mode=CHECKPOINT", "--privacy-enabled"); + + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Checkpoint sync cannot be enabled with privacy."); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void privacyWithBonsaiDefaultMustError() { + // bypass overridden parseCommand method which specifies bonsai + super.parseCommand("--privacy-enabled"); + + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Bonsai cannot be enabled with privacy."); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void privacyWithBonsaiExplicitMustError() { + // bypass overridden parseCommand method which specifies bonsai + super.parseCommand("--privacy-enabled", "--data-storage-format", "BONSAI"); + + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Bonsai cannot be enabled with privacy."); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void privacyWithoutPrivacyPublicKeyFails() { + parseCommand("--privacy-enabled", "--privacy-url", ENCLAVE_URI); + + assertThat(commandErrorOutput.toString(UTF_8)) + .startsWith("Please specify Enclave public key file path to enable privacy"); + } + + @Test + public void mustVerifyPrivacyIsDisabled() { + parseCommand(); + + final ArgumentCaptor enclaveArg = + ArgumentCaptor.forClass(PrivacyParameters.class); + + verify(mockControllerBuilder).privacyParameters(enclaveArg.capture()); + verify(mockControllerBuilder).build(); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + assertThat(enclaveArg.getValue().isEnabled()).isEqualTo(false); + } + + @Test + public void privacyMultiTenancyIsConfiguredWhenConfiguredWithNecessaryOptions() { + parseCommand( + "--privacy-enabled", + "--rpc-http-authentication-enabled", + "--privacy-multi-tenancy-enabled", + "--rpc-http-authentication-jwt-public-key-file", + "/non/existent/file", + "--min-gas-price", + "0"); + + final ArgumentCaptor privacyParametersArgumentCaptor = + ArgumentCaptor.forClass(PrivacyParameters.class); + + verify(mockControllerBuilder).privacyParameters(privacyParametersArgumentCaptor.capture()); + verify(mockControllerBuilder).build(); + + assertThat(privacyParametersArgumentCaptor.getValue().isMultiTenancyEnabled()).isTrue(); + } + + @Test + public void privacyMultiTenancyWithoutAuthenticationFails() { + parseCommand( + "--privacy-enabled", + "--privacy-multi-tenancy-enabled", + "--rpc-http-authentication-jwt-public-key-file", + "/non/existent/file"); + + assertThat(commandErrorOutput.toString(UTF_8)) + .startsWith( + "Privacy multi-tenancy requires either http authentication to be enabled or WebSocket authentication to be enabled"); + } + + @Test + public void privacyMultiTenancyWithPrivacyPublicKeyFileFails() { + parseCommand( + "--privacy-enabled", + "--rpc-http-authentication-enabled", + "--privacy-multi-tenancy-enabled", + "--rpc-http-authentication-jwt-public-key-file", + "/non/existent/file", + "--privacy-public-key-file", + ENCLAVE_PUBLIC_KEY_PATH); + + assertThat(commandErrorOutput.toString(UTF_8)) + .startsWith("Privacy multi-tenancy and privacy public key cannot be used together"); + } + + @Test + public void flexiblePrivacyGroupEnabledFlagDefaultValueIsFalse() { + parseCommand( + "--privacy-enabled", + "--privacy-public-key-file", + ENCLAVE_PUBLIC_KEY_PATH, + "--min-gas-price", + "0"); + + final ArgumentCaptor privacyParametersArgumentCaptor = + ArgumentCaptor.forClass(PrivacyParameters.class); + + verify(mockControllerBuilder).privacyParameters(privacyParametersArgumentCaptor.capture()); + verify(mockControllerBuilder).build(); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + + final PrivacyParameters privacyParameters = privacyParametersArgumentCaptor.getValue(); + assertThat(privacyParameters.isFlexiblePrivacyGroupsEnabled()).isEqualTo(false); + } + + @Test + public void flexiblePrivacyGroupEnabledFlagValueIsSet() { + parseCommand( + "--privacy-enabled", + "--privacy-public-key-file", + ENCLAVE_PUBLIC_KEY_PATH, + "--privacy-flexible-groups-enabled", + "--min-gas-price", + "0"); + + final ArgumentCaptor privacyParametersArgumentCaptor = + ArgumentCaptor.forClass(PrivacyParameters.class); + + verify(mockControllerBuilder).privacyParameters(privacyParametersArgumentCaptor.capture()); + verify(mockControllerBuilder).build(); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + + final PrivacyParameters privacyParameters = privacyParametersArgumentCaptor.getValue(); + assertThat(privacyParameters.isFlexiblePrivacyGroupsEnabled()).isEqualTo(true); + } + + @Test + public void privateMarkerTransactionSigningKeyFileRequiredIfMinGasPriceNonZero() { + parseCommand( + "--privacy-enabled", + "--privacy-public-key-file", + ENCLAVE_PUBLIC_KEY_PATH, + "--min-gas-price", + "1"); + + assertThat(commandErrorOutput.toString(UTF_8)) + .startsWith( + "Not a free gas network. --privacy-marker-transaction-signing-key-file must be specified"); + } + + @Test + public void + privateMarkerTransactionSigningKeyFileNotRequiredIfMinGasPriceNonZeroAndUsingPluginPrivateMarkerTransactionFactory() { + + when(privacyPluginService.getPrivateMarkerTransactionFactory()) + .thenReturn(mock(PrivateMarkerTransactionFactory.class)); + + parseCommand( + "--privacy-enabled", + "--privacy-public-key-file", + ENCLAVE_PUBLIC_KEY_PATH, + "--min-gas-price", + "1"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void + privateMarkerTransactionSigningKeyFileNotCanNotBeUsedWithPluginPrivateMarkerTransactionFactory() + throws IOException { + when(privacyPluginService.getPrivateMarkerTransactionFactory()) + .thenReturn(mock(PrivateMarkerTransactionFactory.class)); + final Path toml = + createTempFile( + "key", + "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63".getBytes(UTF_8)); + + parseCommand( + "--privacy-enabled", + "--privacy-public-key-file", + ENCLAVE_PUBLIC_KEY_PATH, + "--privacy-marker-transaction-signing-key-file", + toml.toString()); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + // assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .startsWith( + "--privacy-marker-transaction-signing-key-file can not be used in conjunction with a plugin that specifies"); + } + + @Test + public void mustProvidePayloadWhenPrivacyPluginEnabled() { + parseCommand("--privacy-enabled", "--Xprivacy-plugin-enabled", "--min-gas-price", "0"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .startsWith( + "No Payload Provider has been provided. You must register one when enabling privacy plugin!"); + } + + @Test + public void canNotUseFlexiblePrivacyWhenPrivacyPluginEnabled() { + parseCommand( + "--privacy-enabled", + "--Xprivacy-plugin-enabled", + "--min-gas-price", + "0", + "--privacy-flexible-groups-enabled"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .startsWith( + "No Payload Provider has been provided. You must register one when enabling privacy plugin!"); + } + + @Test + public void privEnclaveKeyFileDoesNotExist() { + assumeThat( + "Ignored if system language is not English", + System.getProperty("user.language"), + startsWith("en")); + parseCommand("--privacy-enabled=true", "--privacy-public-key-file", "/non/existent/file"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .startsWith("Problem with privacy-public-key-file"); + assertThat(commandErrorOutput.toString(UTF_8)).contains("No such file"); + } + + @Test + public void privEnclaveKeyFileInvalidContentTooShort() throws IOException { + final Path file = createTempFile("privacy.key", "lkjashdfiluhwelrk"); + parseCommand("--privacy-enabled=true", "--privacy-public-key-file", file.toString()); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .startsWith("Contents of privacy-public-key-file invalid"); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Last unit does not have enough valid bits"); + } + + @Test + public void privEnclaveKeyFileInvalidContentNotValidBase64() throws IOException { + final Path file = createTempFile("privacy.key", "l*jashdfillk9ashdfillkjashdfillkjashdfilrtg="); + parseCommand("--privacy-enabled=true", "--privacy-public-key-file", file.toString()); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .startsWith("Contents of privacy-public-key-file invalid"); + assertThat(commandErrorOutput.toString(UTF_8)).contains("Illegal base64 character"); + } + + @Test + public void privHttpApisWithPrivacyDisabledLogsWarning() { + parseCommand("--privacy-enabled=false", "--rpc-http-api", "PRIV", "--rpc-http-enabled"); + + verify(mockRunnerBuilder).build(); + verify(mockLogger) + .warn("Privacy is disabled. Cannot use EEA/PRIV API methods when not using Privacy."); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void privWsApisWithPrivacyDisabledLogsWarning() { + parseCommand("--privacy-enabled=false", "--rpc-ws-api", "PRIV", "--rpc-ws-enabled"); + + verify(mockRunnerBuilder).build(); + verify(mockLogger) + .warn("Privacy is disabled. Cannot use EEA/PRIV API methods when not using Privacy."); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void eeaHttpApisWithPrivacyDisabledLogsWarning() { + parseCommand("--privacy-enabled=false", "--rpc-http-api", "EEA", "--rpc-http-enabled"); + + verify(mockRunnerBuilder).build(); + verify(mockLogger) + .warn("Privacy is disabled. Cannot use EEA/PRIV API methods when not using Privacy."); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void eeaWsApisWithPrivacyDisabledLogsWarning() { + parseCommand("--privacy-enabled=false", "--rpc-ws-api", "EEA", "--rpc-ws-enabled"); + + verify(mockRunnerBuilder).build(); + verify(mockLogger) + .warn("Privacy is disabled. Cannot use EEA/PRIV API methods when not using Privacy."); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Override + protected TestBesuCommand parseCommand(final String... args) { + // privacy requires forest to be specified + final List argsPlusForest = new ArrayList<>(Arrays.stream(args).toList()); + argsPlusForest.add("--data-storage-format"); + argsPlusForest.add("FOREST"); + return super.parseCommand(argsPlusForest.toArray(String[]::new)); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/ProfilesTest.java b/besu/src/test/java/org/hyperledger/besu/cli/ProfilesTest.java new file mode 100644 index 00000000000..019ff7a7ed2 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/ProfilesTest.java @@ -0,0 +1,120 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.cli.config.InternalProfileName; +import org.hyperledger.besu.cli.util.ProfileFinder; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class ProfilesTest extends CommandTestAbstract { + @TempDir private static Path tempProfilesDir; + private static String originalProfilesDirProperty; + + @BeforeAll + public static void copyExternalProfiles() throws IOException { + for (String internalProfileName : InternalProfileName.getInternalProfileNames()) { + final Path profilePath = tempProfilesDir.resolve(internalProfileName + "_external.toml"); + + String profileConfigFile = + InternalProfileName.valueOfIgnoreCase(internalProfileName).get().getConfigFile(); + try (InputStream resourceUrl = + ProfileFinder.class.getClassLoader().getResourceAsStream(profileConfigFile)) { + if (resourceUrl != null) { + Files.copy(resourceUrl, profilePath); + } + } + } + + // add an empty external profile + Files.createFile(tempProfilesDir.resolve("empty_external.toml")); + } + + @BeforeAll + public static void setupSystemProperty() { + originalProfilesDirProperty = System.getProperty("besu.profiles.dir"); + // sets the system property for the test + System.setProperty("besu.profiles.dir", tempProfilesDir.toString()); + } + + static Stream profileNameProvider() { + final Set profileNames = new TreeSet<>(InternalProfileName.getInternalProfileNames()); + final Set externalProfileNames = + InternalProfileName.getInternalProfileNames().stream() + .map(name -> name + "_external") + .collect(Collectors.toSet()); + profileNames.addAll(externalProfileNames); + return profileNames.stream().map(Arguments::of); + } + + /** Test if besu will validate the combination of options within the given profile. */ + @ParameterizedTest(name = "{index} - Profile Name override: {0}") + @DisplayName("Valid Profile with overrides does not error") + @MethodSource("profileNameProvider") + public void testProfileWithNoOverrides_doesNotError(final String profileName) { + parseCommand("--profile", profileName); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + @DisplayName("Empty external profile file results in error") + public void emptyProfileFile_ShouldResultInError() { + parseCommand("--profile", "empty_external"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Unable to read from empty TOML configuration file."); + } + + @Test + @DisplayName("Non Existing profile results in error") + public void nonExistentProfileFile_ShouldResultInError() { + parseCommand("--profile", "non_existent_profile"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Unable to load external profile: non_existent_profile"); + } + + @AfterAll + public static void clearSystemProperty() { + if (originalProfilesDirProperty != null) { + System.setProperty("besu.profiles.dir", originalProfilesDirProperty); + } else { + System.clearProperty("besu.profiles.dir"); + } + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/PublicKeySubCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/PublicKeySubCommandTest.java index 3da574e8e32..d2ca00990a0 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/PublicKeySubCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/PublicKeySubCommandTest.java @@ -35,14 +35,14 @@ import org.bouncycastle.asn1.sec.SECNamedCurves; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.params.ECDomainParameters; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; import picocli.CommandLine.Model.CommandSpec; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class PublicKeySubCommandTest extends CommandTestAbstract { private static final String EXPECTED_PUBLIC_KEY_USAGE = @@ -126,13 +126,13 @@ public class PublicKeySubCommandTest extends CommandTestAbstract { private static final String ALGORITHM = SignatureAlgorithm.ALGORITHM; private static ECDomainParameters curve; - @BeforeClass + @BeforeAll public static void setUp() { final X9ECParameters params = SECNamedCurves.getByName(CURVE_NAME); curve = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH()); } - @Before + @BeforeEach public void before() { SignatureAlgorithmFactory.resetInstance(); } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/TomlConfigFileDefaultProviderTest.java b/besu/src/test/java/org/hyperledger/besu/cli/TomlConfigFileDefaultProviderTest.java deleted file mode 100644 index 1481db9526d..00000000000 --- a/besu/src/test/java/org/hyperledger/besu/cli/TomlConfigFileDefaultProviderTest.java +++ /dev/null @@ -1,375 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.cli; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.cli.util.TomlConfigFileDefaultProvider; -import org.hyperledger.besu.datatypes.Wei; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import picocli.CommandLine; -import picocli.CommandLine.Model.CommandSpec; -import picocli.CommandLine.Model.OptionSpec; -import picocli.CommandLine.ParameterException; - -@RunWith(MockitoJUnitRunner.class) -public class TomlConfigFileDefaultProviderTest { - @Mock CommandLine mockCommandLine; - - @Mock CommandSpec mockCommandSpec; - - @Rule public final TemporaryFolder temp = new TemporaryFolder(); - - @Test - public void defaultValueForMatchingKey() throws IOException { - when(mockCommandLine.getCommandSpec()).thenReturn(mockCommandSpec); - Map validOptionsMap = new HashMap<>(); - validOptionsMap.put("--a-short-option", null); - validOptionsMap.put("--an-actual-long-option", null); - validOptionsMap.put("--a-longer-option", null); - when(mockCommandSpec.optionsMap()).thenReturn(validOptionsMap); - - final File tempConfigFile = temp.newFile("config.toml"); - try (final BufferedWriter fileWriter = - Files.newBufferedWriter(tempConfigFile.toPath(), UTF_8)) { - - fileWriter.write("a-short-option='123'"); - fileWriter.newLine(); - fileWriter.write("an-actual-long-option=" + Long.MAX_VALUE); - fileWriter.newLine(); - fileWriter.write("a-longer-option='1234'"); - fileWriter.flush(); - - final TomlConfigFileDefaultProvider providerUnderTest = - new TomlConfigFileDefaultProvider(mockCommandLine, tempConfigFile); - - // this option must be found in config - assertThat( - providerUnderTest.defaultValue( - OptionSpec.builder("a-short-option").type(Integer.class).build())) - .isEqualTo("123"); - - // this option must be found in config as one of its names is present in the file. - // also this is the shortest one. - assertThat( - providerUnderTest.defaultValue( - OptionSpec.builder("a-short-option", "another-name-for-the-option") - .type(Integer.class) - .build())) - .isEqualTo("123"); - - // this option must be found in config as one of its names is present in the file. - // also this is a long. - assertThat( - providerUnderTest.defaultValue( - OptionSpec.builder("an-actual-long-option", "another-name-for-the-option") - .type(Long.class) - .build())) - .isEqualTo(String.valueOf(Long.MAX_VALUE)); - - // this option must be found in config as one of its names is present in the file. - // also this is the longest one. - assertThat( - providerUnderTest.defaultValue( - OptionSpec.builder("l", "longer", "a-longer-option").type(Integer.class).build())) - .isEqualTo("1234"); - } - } - - @Test - public void defaultValueForOptionMustMatchType() throws IOException { - when(mockCommandLine.getCommandSpec()).thenReturn(mockCommandSpec); - Map validOptionsMap = new HashMap<>(); - validOptionsMap.put("--a-boolean-option", null); - validOptionsMap.put("--another-boolean-option", null); - validOptionsMap.put("--a-primitive-boolean-option", null); - validOptionsMap.put("--another-primitive-boolean-option", null); - validOptionsMap.put("--a-multi-value-option", null); - validOptionsMap.put("--an-int-value-option", null); - validOptionsMap.put("--a-primitive-int-value-option", null); - validOptionsMap.put("--a-wei-value-option", null); - validOptionsMap.put("--a-string-value-option", null); - validOptionsMap.put("--a-nested-multi-value-option", null); - validOptionsMap.put("--a-double-value-option", null); - validOptionsMap.put("--a-double-value-option-int", null); - - when(mockCommandSpec.optionsMap()).thenReturn(validOptionsMap); - - final File tempConfigFile = temp.newFile("config.toml"); - try (final BufferedWriter fileWriter = - Files.newBufferedWriter(tempConfigFile.toPath(), UTF_8)) { - - fileWriter.write("a-boolean-option=true"); - fileWriter.newLine(); - fileWriter.write("another-boolean-option=false"); - fileWriter.newLine(); - fileWriter.write("a-primitive-boolean-option=true"); - fileWriter.newLine(); - fileWriter.write("another-primitive-boolean-option=false"); - fileWriter.newLine(); - fileWriter.write("a-multi-value-option=[\"value1\", \"value2\"]"); - fileWriter.newLine(); - fileWriter.write("an-int-value-option=123"); - fileWriter.newLine(); - fileWriter.write("a-primitive-int-value-option=456"); - fileWriter.newLine(); - fileWriter.write("a-wei-value-option=1"); - fileWriter.newLine(); - fileWriter.write("a-string-value-option='my value'"); - fileWriter.newLine(); - fileWriter.write( - "a-nested-multi-value-option=[ [\"value1\", \"value2\"], [\"value3\", \"value4\"] ]"); - fileWriter.newLine(); - fileWriter.write("a-double-value-option=0.01"); - fileWriter.newLine(); - fileWriter.write("a-double-value-option-int=1"); // should be able to parse int as double - fileWriter.flush(); - - final TomlConfigFileDefaultProvider providerUnderTest = - new TomlConfigFileDefaultProvider(mockCommandLine, tempConfigFile); - - assertThat( - providerUnderTest.defaultValue( - OptionSpec.builder("a-boolean-option").type(Boolean.class).build())) - .isEqualTo("true"); - - assertThat( - providerUnderTest.defaultValue( - OptionSpec.builder("another-boolean-option").type(Boolean.class).build())) - .isEqualTo("false"); - - assertThat( - providerUnderTest.defaultValue( - OptionSpec.builder("a-primitive-boolean-option").type(boolean.class).build())) - .isEqualTo("true"); - - assertThat( - providerUnderTest.defaultValue( - OptionSpec.builder("another-primitive-boolean-option") - .type(boolean.class) - .build())) - .isEqualTo("false"); - - assertThat( - providerUnderTest.defaultValue( - OptionSpec.builder("a-multi-value-option").type(Collection.class).build())) - .isEqualTo("value1,value2"); - - assertThat( - providerUnderTest.defaultValue( - OptionSpec.builder("an-int-value-option").type(Integer.class).build())) - .isEqualTo("123"); - - assertThat( - providerUnderTest.defaultValue( - OptionSpec.builder("a-primitive-int-value-option").type(int.class).build())) - .isEqualTo("456"); - - assertThat( - providerUnderTest.defaultValue( - OptionSpec.builder("a-wei-value-option").type(Wei.class).build())) - .isEqualTo("1"); - - assertThat( - providerUnderTest.defaultValue( - OptionSpec.builder("a-string-value-option").type(String.class).build())) - .isEqualTo("my value"); - - assertThat( - providerUnderTest.defaultValue( - OptionSpec.builder("a-double-value-option").type(Double.class).build())) - .isEqualTo("0.01"); - - assertThat( - providerUnderTest.defaultValue( - OptionSpec.builder("a-double-value-option-int").type(Double.class).build())) - .isEqualTo("1"); - - assertThat( - providerUnderTest.defaultValue( - OptionSpec.builder("a-nested-multi-value-option").type(Collection.class).build())) - .isEqualTo("[value1,value2],[value3,value4]"); - } - } - - @Test - public void configFileNotFoundMustThrow() { - - final File nonExistingFile = new File("doesnt.exit"); - - final TomlConfigFileDefaultProvider providerUnderTest = - new TomlConfigFileDefaultProvider(mockCommandLine, nonExistingFile); - - assertThatThrownBy( - () -> - providerUnderTest.defaultValue( - OptionSpec.builder("an-option").type(String.class).build())) - .isInstanceOf(ParameterException.class) - .hasMessage("Unable to read TOML configuration, file not found."); - } - - @Test - public void invalidConfigMustThrow() throws IOException { - - final File tempConfigFile = temp.newFile("config.toml"); - - final TomlConfigFileDefaultProvider providerUnderTest = - new TomlConfigFileDefaultProvider(mockCommandLine, tempConfigFile); - - assertThatThrownBy( - () -> - providerUnderTest.defaultValue( - OptionSpec.builder("an-option").type(String.class).build())) - .isInstanceOf(ParameterException.class) - .hasMessageContaining("Unable to read TOML configuration file"); - } - - @Test - public void invalidConfigContentMustThrow() throws IOException { - - final File tempConfigFile = temp.newFile("config.toml"); - final BufferedWriter fileWriter = Files.newBufferedWriter(tempConfigFile.toPath(), UTF_8); - - fileWriter.write("an-invalid-syntax=======...."); - fileWriter.flush(); - - final TomlConfigFileDefaultProvider providerUnderTest = - new TomlConfigFileDefaultProvider(mockCommandLine, tempConfigFile); - - assertThatThrownBy( - () -> - providerUnderTest.defaultValue( - OptionSpec.builder("an-option").type(String.class).build())) - .isInstanceOf(ParameterException.class) - .hasMessage( - "Invalid TOML configuration: org.apache.tuweni.toml.TomlParseError: Unexpected '=', expected ', \", ''', " - + "\"\"\", a number, a boolean, a date/time, an array, or a table (line 1, column 19)"); - } - - @Test - public void unknownOptionMustThrow() throws IOException { - - when(mockCommandLine.getCommandSpec()).thenReturn(mockCommandSpec); - Map validOptionsMap = new HashMap<>(); - when(mockCommandSpec.optionsMap()).thenReturn(validOptionsMap); - - final File tempConfigFile = temp.newFile("config.toml"); - final BufferedWriter fileWriter = Files.newBufferedWriter(tempConfigFile.toPath(), UTF_8); - - fileWriter.write("invalid_option=true"); - fileWriter.flush(); - - final TomlConfigFileDefaultProvider providerUnderTest = - new TomlConfigFileDefaultProvider(mockCommandLine, tempConfigFile); - - assertThatThrownBy( - () -> - providerUnderTest.defaultValue( - OptionSpec.builder("an-option").type(String.class).build())) - .isInstanceOf(ParameterException.class) - .hasMessage("Unknown option in TOML configuration file: invalid_option"); - } - - @Test - public void tomlTableHeadingsMustBeIgnored() throws IOException { - - when(mockCommandLine.getCommandSpec()).thenReturn(mockCommandSpec); - - Map validOptionsMap = new HashMap<>(); - validOptionsMap.put("--a-valid-option", null); - validOptionsMap.put("--another-valid-option", null); - validOptionsMap.put("--onemore-valid-option", null); - when(mockCommandSpec.optionsMap()).thenReturn(validOptionsMap); - - final File tempConfigFile = temp.newFile("config.toml"); - final BufferedWriter fileWriter = Files.newBufferedWriter(tempConfigFile.toPath(), UTF_8); - - fileWriter.write("a-valid-option=123"); - fileWriter.newLine(); - fileWriter.write("[ignoreme]"); - fileWriter.newLine(); - fileWriter.write("another-valid-option=456"); - fileWriter.newLine(); - fileWriter.write("onemore-valid-option=789"); - fileWriter.newLine(); - fileWriter.flush(); - - final TomlConfigFileDefaultProvider providerUnderTest = - new TomlConfigFileDefaultProvider(mockCommandLine, tempConfigFile); - - assertThat( - providerUnderTest.defaultValue( - OptionSpec.builder("a-valid-option").type(Integer.class).build())) - .isEqualTo("123"); - - assertThat( - providerUnderTest.defaultValue( - OptionSpec.builder("another-valid-option").type(Integer.class).build())) - .isEqualTo("456"); - - assertThat( - providerUnderTest.defaultValue( - OptionSpec.builder("onemore-valid-option").type(Integer.class).build())) - .isEqualTo("789"); - } - - @Test - public void tomlTableHeadingsMustNotSkipValidationOfUnknownOptions() throws IOException { - - when(mockCommandLine.getCommandSpec()).thenReturn(mockCommandSpec); - - Map validOptionsMap = new HashMap<>(); - validOptionsMap.put("--a-valid-option", null); - when(mockCommandSpec.optionsMap()).thenReturn(validOptionsMap); - - final File tempConfigFile = temp.newFile("config.toml"); - final BufferedWriter fileWriter = Files.newBufferedWriter(tempConfigFile.toPath(), UTF_8); - - fileWriter.write("[ignoreme]"); - fileWriter.newLine(); - fileWriter.write("a-valid-option=123"); - fileWriter.newLine(); - fileWriter.write("invalid-option=789"); - fileWriter.newLine(); - fileWriter.flush(); - - final TomlConfigFileDefaultProvider providerUnderTest = - new TomlConfigFileDefaultProvider(mockCommandLine, tempConfigFile); - - assertThatThrownBy( - () -> - providerUnderTest.defaultValue( - OptionSpec.builder("an-option").type(String.class).build())) - .isInstanceOf(ParameterException.class) - .hasMessage("Unknown option in TOML configuration file: invalid-option"); - } -} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/TomlConfigurationDefaultProviderTest.java b/besu/src/test/java/org/hyperledger/besu/cli/TomlConfigurationDefaultProviderTest.java new file mode 100644 index 00000000000..983e92cec17 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/TomlConfigurationDefaultProviderTest.java @@ -0,0 +1,367 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.cli.util.TomlConfigurationDefaultProvider; +import org.hyperledger.besu.datatypes.Wei; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import picocli.CommandLine; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Model.OptionSpec; +import picocli.CommandLine.ParameterException; + +@ExtendWith(MockitoExtension.class) +public class TomlConfigurationDefaultProviderTest { + @Mock CommandLine mockCommandLine; + + @Mock CommandSpec mockCommandSpec; + + @Test + public void defaultValueForMatchingKey(final @TempDir Path temp) throws IOException { + when(mockCommandLine.getCommandSpec()).thenReturn(mockCommandSpec); + Map validOptionsMap = new HashMap<>(); + validOptionsMap.put("--a-short-option", null); + validOptionsMap.put("--an-actual-long-option", null); + validOptionsMap.put("--a-longer-option", null); + when(mockCommandSpec.optionsMap()).thenReturn(validOptionsMap); + + final File tempConfigFile = temp.resolve("config.toml").toFile(); + try (final BufferedWriter fileWriter = + Files.newBufferedWriter(tempConfigFile.toPath(), UTF_8)) { + + fileWriter.write("a-short-option='123'"); + fileWriter.newLine(); + fileWriter.write("an-actual-long-option=" + Long.MAX_VALUE); + fileWriter.newLine(); + fileWriter.write("a-longer-option='1234'"); + fileWriter.flush(); + + final TomlConfigurationDefaultProvider providerUnderTest = + TomlConfigurationDefaultProvider.fromFile(mockCommandLine, tempConfigFile); + + // this option must be found in config + assertThat( + providerUnderTest.defaultValue( + OptionSpec.builder("a-short-option").type(Integer.class).build())) + .isEqualTo("123"); + + // this option must be found in config as one of its names is present in the file. + // also this is the shortest one. + assertThat( + providerUnderTest.defaultValue( + OptionSpec.builder("a-short-option", "another-name-for-the-option") + .type(Integer.class) + .build())) + .isEqualTo("123"); + + // this option must be found in config as one of its names is present in the file. + // also this is a long. + assertThat( + providerUnderTest.defaultValue( + OptionSpec.builder("an-actual-long-option", "another-name-for-the-option") + .type(Long.class) + .build())) + .isEqualTo(String.valueOf(Long.MAX_VALUE)); + + // this option must be found in config as one of its names is present in the file. + // also this is the longest one. + assertThat( + providerUnderTest.defaultValue( + OptionSpec.builder("l", "longer", "a-longer-option").type(Integer.class).build())) + .isEqualTo("1234"); + } + } + + @Test + public void defaultValueForOptionMustMatchType(final @TempDir Path temp) throws IOException { + when(mockCommandLine.getCommandSpec()).thenReturn(mockCommandSpec); + Map validOptionsMap = new HashMap<>(); + validOptionsMap.put("--a-boolean-option", null); + validOptionsMap.put("--another-boolean-option", null); + validOptionsMap.put("--a-primitive-boolean-option", null); + validOptionsMap.put("--another-primitive-boolean-option", null); + validOptionsMap.put("--a-multi-value-option", null); + validOptionsMap.put("--an-int-value-option", null); + validOptionsMap.put("--a-primitive-int-value-option", null); + validOptionsMap.put("--a-wei-value-option", null); + validOptionsMap.put("--a-string-value-option", null); + validOptionsMap.put("--a-nested-multi-value-option", null); + validOptionsMap.put("--a-double-value-option", null); + validOptionsMap.put("--a-double-value-option-int", null); + + when(mockCommandSpec.optionsMap()).thenReturn(validOptionsMap); + + final File tempConfigFile = temp.resolve("config.toml").toFile(); + try (final BufferedWriter fileWriter = + Files.newBufferedWriter(tempConfigFile.toPath(), UTF_8)) { + + fileWriter.write("a-boolean-option=true"); + fileWriter.newLine(); + fileWriter.write("another-boolean-option=false"); + fileWriter.newLine(); + fileWriter.write("a-primitive-boolean-option=true"); + fileWriter.newLine(); + fileWriter.write("another-primitive-boolean-option=false"); + fileWriter.newLine(); + fileWriter.write("a-multi-value-option=[\"value1\", \"value2\"]"); + fileWriter.newLine(); + fileWriter.write("an-int-value-option=123"); + fileWriter.newLine(); + fileWriter.write("a-primitive-int-value-option=456"); + fileWriter.newLine(); + fileWriter.write("a-wei-value-option=1"); + fileWriter.newLine(); + fileWriter.write("a-string-value-option='my value'"); + fileWriter.newLine(); + fileWriter.write( + "a-nested-multi-value-option=[ [\"value1\", \"value2\"], [\"value3\", \"value4\"] ]"); + fileWriter.newLine(); + fileWriter.write("a-double-value-option=0.01"); + fileWriter.newLine(); + fileWriter.write("a-double-value-option-int=1"); // should be able to parse int as double + fileWriter.flush(); + + final TomlConfigurationDefaultProvider providerUnderTest = + TomlConfigurationDefaultProvider.fromFile(mockCommandLine, tempConfigFile); + + assertThat( + providerUnderTest.defaultValue( + OptionSpec.builder("a-boolean-option").type(Boolean.class).build())) + .isEqualTo("true"); + + assertThat( + providerUnderTest.defaultValue( + OptionSpec.builder("another-boolean-option").type(Boolean.class).build())) + .isEqualTo("false"); + + assertThat( + providerUnderTest.defaultValue( + OptionSpec.builder("a-primitive-boolean-option").type(boolean.class).build())) + .isEqualTo("true"); + + assertThat( + providerUnderTest.defaultValue( + OptionSpec.builder("another-primitive-boolean-option") + .type(boolean.class) + .build())) + .isEqualTo("false"); + + assertThat( + providerUnderTest.defaultValue( + OptionSpec.builder("a-multi-value-option").type(Collection.class).build())) + .isEqualTo("value1,value2"); + + assertThat( + providerUnderTest.defaultValue( + OptionSpec.builder("an-int-value-option").type(Integer.class).build())) + .isEqualTo("123"); + + assertThat( + providerUnderTest.defaultValue( + OptionSpec.builder("a-primitive-int-value-option").type(int.class).build())) + .isEqualTo("456"); + + assertThat( + providerUnderTest.defaultValue( + OptionSpec.builder("a-wei-value-option").type(Wei.class).build())) + .isEqualTo("1"); + + assertThat( + providerUnderTest.defaultValue( + OptionSpec.builder("a-string-value-option").type(String.class).build())) + .isEqualTo("my value"); + + assertThat( + providerUnderTest.defaultValue( + OptionSpec.builder("a-double-value-option").type(Double.class).build())) + .isEqualTo("0.01"); + + assertThat( + providerUnderTest.defaultValue( + OptionSpec.builder("a-double-value-option-int").type(Double.class).build())) + .isEqualTo("1"); + + assertThat( + providerUnderTest.defaultValue( + OptionSpec.builder("a-nested-multi-value-option").type(Collection.class).build())) + .isEqualTo("[value1,value2],[value3,value4]"); + } + } + + @Test + public void configFileNotFoundMustThrow() { + final File nonExistingFile = new File("doesnt.exit"); + assertThatThrownBy( + () -> TomlConfigurationDefaultProvider.fromFile(mockCommandLine, nonExistingFile)) + .isInstanceOf(ParameterException.class) + .hasMessage("Unable to read TOML configuration, file not found."); + } + + @Test + public void invalidConfigMustThrow(final @TempDir Path temp) throws IOException { + + final File tempConfigFile = Files.createTempFile("invalid", "toml").toFile(); + + final TomlConfigurationDefaultProvider providerUnderTest = + TomlConfigurationDefaultProvider.fromFile(mockCommandLine, tempConfigFile); + + assertThatThrownBy( + () -> + providerUnderTest.defaultValue( + OptionSpec.builder("an-option").type(String.class).build())) + .isInstanceOf(ParameterException.class) + .hasMessageContaining("Unable to read from empty TOML configuration file."); + } + + @Test + public void invalidConfigContentMustThrow(final @TempDir Path temp) throws IOException { + + final File tempConfigFile = temp.resolve("config.toml").toFile(); + final BufferedWriter fileWriter = Files.newBufferedWriter(tempConfigFile.toPath(), UTF_8); + + fileWriter.write("an-invalid-syntax=======...."); + fileWriter.flush(); + + final TomlConfigurationDefaultProvider providerUnderTest = + TomlConfigurationDefaultProvider.fromFile(mockCommandLine, tempConfigFile); + + assertThatThrownBy( + () -> + providerUnderTest.defaultValue( + OptionSpec.builder("an-option").type(String.class).build())) + .isInstanceOf(ParameterException.class) + .hasMessage( + "Invalid TOML configuration: org.apache.tuweni.toml.TomlParseError: Unexpected '=', expected ', \", ''', " + + "\"\"\", a number, a boolean, a date/time, an array, or a table (line 1, column 19)"); + } + + @Test + public void unknownOptionMustThrow(final @TempDir Path temp) throws IOException { + + when(mockCommandLine.getCommandSpec()).thenReturn(mockCommandSpec); + Map validOptionsMap = new HashMap<>(); + when(mockCommandSpec.optionsMap()).thenReturn(validOptionsMap); + + final File tempConfigFile = temp.resolve("config.toml").toFile(); + final BufferedWriter fileWriter = Files.newBufferedWriter(tempConfigFile.toPath(), UTF_8); + + fileWriter.write("invalid_option=true"); + fileWriter.flush(); + + final TomlConfigurationDefaultProvider providerUnderTest = + TomlConfigurationDefaultProvider.fromFile(mockCommandLine, tempConfigFile); + + assertThatThrownBy( + () -> + providerUnderTest.defaultValue( + OptionSpec.builder("an-option").type(String.class).build())) + .isInstanceOf(ParameterException.class) + .hasMessage("Unknown option in TOML configuration file: invalid_option"); + } + + @Test + public void tomlTableHeadingsMustBeIgnored(final @TempDir Path temp) throws IOException { + + when(mockCommandLine.getCommandSpec()).thenReturn(mockCommandSpec); + + Map validOptionsMap = new HashMap<>(); + validOptionsMap.put("--a-valid-option", null); + validOptionsMap.put("--another-valid-option", null); + validOptionsMap.put("--onemore-valid-option", null); + when(mockCommandSpec.optionsMap()).thenReturn(validOptionsMap); + + final File tempConfigFile = temp.resolve("config.toml").toFile(); + final BufferedWriter fileWriter = Files.newBufferedWriter(tempConfigFile.toPath(), UTF_8); + + fileWriter.write("a-valid-option=123"); + fileWriter.newLine(); + fileWriter.write("[ignoreme]"); + fileWriter.newLine(); + fileWriter.write("another-valid-option=456"); + fileWriter.newLine(); + fileWriter.write("onemore-valid-option=789"); + fileWriter.newLine(); + fileWriter.flush(); + + final TomlConfigurationDefaultProvider providerUnderTest = + TomlConfigurationDefaultProvider.fromFile(mockCommandLine, tempConfigFile); + + assertThat( + providerUnderTest.defaultValue( + OptionSpec.builder("a-valid-option").type(Integer.class).build())) + .isEqualTo("123"); + + assertThat( + providerUnderTest.defaultValue( + OptionSpec.builder("another-valid-option").type(Integer.class).build())) + .isEqualTo("456"); + + assertThat( + providerUnderTest.defaultValue( + OptionSpec.builder("onemore-valid-option").type(Integer.class).build())) + .isEqualTo("789"); + } + + @Test + public void tomlTableHeadingsMustNotSkipValidationOfUnknownOptions(final @TempDir Path temp) + throws IOException { + + when(mockCommandLine.getCommandSpec()).thenReturn(mockCommandSpec); + + Map validOptionsMap = new HashMap<>(); + validOptionsMap.put("--a-valid-option", null); + when(mockCommandSpec.optionsMap()).thenReturn(validOptionsMap); + + final File tempConfigFile = temp.resolve("config.toml").toFile(); + final BufferedWriter fileWriter = Files.newBufferedWriter(tempConfigFile.toPath(), UTF_8); + + fileWriter.write("[ignoreme]"); + fileWriter.newLine(); + fileWriter.write("a-valid-option=123"); + fileWriter.newLine(); + fileWriter.write("invalid-option=789"); + fileWriter.newLine(); + fileWriter.flush(); + + final TomlConfigurationDefaultProvider providerUnderTest = + TomlConfigurationDefaultProvider.fromFile(mockCommandLine, tempConfigFile); + + assertThatThrownBy( + () -> + providerUnderTest.defaultValue( + OptionSpec.builder("an-option").type(String.class).build())) + .isInstanceOf(ParameterException.class) + .hasMessage("Unknown option in TOML configuration file: invalid-option"); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/TxPoolOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/TxPoolOptionsTest.java new file mode 100644 index 00000000000..8389ab5bae8 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/TxPoolOptionsTest.java @@ -0,0 +1,175 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.cli.BesuCommandTest.GENESIS_WITH_ZERO_BASE_FEE_MARKET; +import static org.mockito.ArgumentMatchers.contains; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.verify; + +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.util.number.Percentage; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +public class TxPoolOptionsTest extends CommandTestAbstract { + + @Test + public void txpoolDefaultSaveFileRelativeToDataPath() throws IOException { + final Path dataDir = Files.createTempDirectory("data-dir"); + parseCommand("--data-path", dataDir.toString(), "--tx-pool-enable-save-restore", "true"); + verify(mockControllerBuilder) + .transactionPoolConfiguration(transactionPoolConfigCaptor.capture()); + + assertThat(transactionPoolConfigCaptor.getValue().getSaveFile()) + .isEqualTo(dataDir.resolve(TransactionPoolConfiguration.DEFAULT_SAVE_FILE_NAME).toFile()); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void txpoolCustomSaveFileRelativeToDataPath() throws IOException { + final Path dataDir = Files.createTempDirectory("data-dir"); + dataDir.toFile().deleteOnExit(); + final File saveFile = Files.createTempFile(dataDir, "txpool", "save").toFile(); + saveFile.deleteOnExit(); + parseCommand( + "--data-path", + dataDir.toString(), + "--tx-pool-enable-save-restore", + "true", + "--tx-pool-save-file", + saveFile.getName()); + verify(mockControllerBuilder) + .transactionPoolConfiguration(transactionPoolConfigCaptor.capture()); + + final File configuredSaveFile = transactionPoolConfigCaptor.getValue().getSaveFile(); + assertThat(configuredSaveFile).isEqualTo(saveFile); + assertThat(configuredSaveFile.toPath().getParent()).isEqualTo(dataDir); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void txpoolSaveFileAbsolutePathOutsideDataPath() throws IOException { + final Path dataDir = Files.createTempDirectory("data-dir"); + dataDir.toFile().deleteOnExit(); + final File saveFile = File.createTempFile("txpool", "dump"); + saveFile.deleteOnExit(); + parseCommand( + "--data-path", + dataDir.toString(), + "--tx-pool-enable-save-restore", + "true", + "--tx-pool-save-file", + saveFile.getAbsolutePath()); + verify(mockControllerBuilder) + .transactionPoolConfiguration(transactionPoolConfigCaptor.capture()); + + final File configuredSaveFile = transactionPoolConfigCaptor.getValue().getSaveFile(); + assertThat(configuredSaveFile).isEqualTo(saveFile); + assertThat(configuredSaveFile.toPath().getParent()).isNotEqualTo(dataDir); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + @Disabled // Failing in CI, but not locally + public void txpoolForcePriceBumpToZeroWhenZeroBaseFeeMarket() throws IOException { + final Path genesisFile = createFakeGenesisFile(GENESIS_WITH_ZERO_BASE_FEE_MARKET); + parseCommand("--genesis-file", genesisFile.toString()); + verify(mockControllerBuilder) + .transactionPoolConfiguration(transactionPoolConfigCaptor.capture()); + + final Percentage priceBump = transactionPoolConfigCaptor.getValue().getPriceBump(); + assertThat(priceBump).isEqualTo(Percentage.ZERO); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void txpoolPriceBumpOptionIncompatibleWithZeroWhenZeroBaseFeeMarket() throws IOException { + final Path genesisFile = createFakeGenesisFile(GENESIS_WITH_ZERO_BASE_FEE_MARKET); + parseCommand("--genesis-file", genesisFile.toString(), "--tx-pool-price-bump", "5"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Price bump option is not compatible with zero base fee market"); + } + + @Test + public void txpoolForcePriceBumpToZeroWhenMinGasPriceZero() { + parseCommand("--min-gas-price", "0"); + verify(mockControllerBuilder) + .transactionPoolConfiguration(transactionPoolConfigCaptor.capture()); + + final Percentage priceBump = transactionPoolConfigCaptor.getValue().getPriceBump(); + assertThat(priceBump).isEqualTo(Percentage.ZERO); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void txpoolPriceBumpKeepItsValueIfSetEvenWhenMinGasPriceZero() { + parseCommand("--min-gas-price", "0", "--tx-pool-price-bump", "1"); + verify(mockControllerBuilder) + .transactionPoolConfiguration(transactionPoolConfigCaptor.capture()); + + final Percentage priceBump = transactionPoolConfigCaptor.getValue().getPriceBump(); + assertThat(priceBump).isEqualTo(Percentage.fromInt(1)); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void txpoolWhenNotSetForceTxPoolMinGasPriceToZeroWhenMinGasPriceZero() { + parseCommand("--min-gas-price", "0"); + verify(mockControllerBuilder) + .transactionPoolConfiguration(transactionPoolConfigCaptor.capture()); + + final Wei txPoolMinGasPrice = transactionPoolConfigCaptor.getValue().getMinGasPrice(); + assertThat(txPoolMinGasPrice).isEqualTo(Wei.ZERO); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + verify(mockLogger, atLeast(1)) + .warn( + contains( + "Forcing tx-pool-min-gas-price=0, since it cannot be greater than the value of min-gas-price")); + } + + @Test + public void txpoolTxPoolMinGasPriceMustNotBeGreaterThanMinGasPriceZero() { + parseCommand("--min-gas-price", "100", "--tx-pool-min-gas-price", "101"); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("tx-pool-min-gas-price cannot be greater than the value of min-gas-price"); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/ValidateConfigSubCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/ValidateConfigSubCommandTest.java index dc8d0548e0c..ac6efbf405b 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/ValidateConfigSubCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/ValidateConfigSubCommandTest.java @@ -25,12 +25,12 @@ import java.nio.file.Path; import com.google.common.io.Resources; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; import picocli.CommandLine.Model.CommandSpec; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class ValidateConfigSubCommandTest extends CommandTestAbstract { private static final String EXPECTED_PUBLIC_KEY_USAGE = diff --git a/besu/src/test/java/org/hyperledger/besu/cli/config/EthNetworkConfigTest.java b/besu/src/test/java/org/hyperledger/besu/cli/config/EthNetworkConfigTest.java index 66e8592b5e4..3af43eb4e5f 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/config/EthNetworkConfigTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/config/EthNetworkConfigTest.java @@ -16,58 +16,60 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.cli.config.NetworkName.MAINNET; -import static org.hyperledger.besu.ethereum.p2p.config.DefaultDiscoveryConfiguration.GOERLI_BOOTSTRAP_NODES; -import static org.hyperledger.besu.ethereum.p2p.config.DefaultDiscoveryConfiguration.GOERLI_DISCOVERY_URL; import static org.hyperledger.besu.ethereum.p2p.config.DefaultDiscoveryConfiguration.MAINNET_BOOTSTRAP_NODES; import static org.hyperledger.besu.ethereum.p2p.config.DefaultDiscoveryConfiguration.MAINNET_DISCOVERY_URL; +import static org.hyperledger.besu.ethereum.p2p.config.DefaultDiscoveryConfiguration.SEPOLIA_BOOTSTRAP_NODES; +import static org.hyperledger.besu.ethereum.p2p.config.DefaultDiscoveryConfiguration.SEPOLIA_DISCOVERY_URL; + +import org.hyperledger.besu.config.GenesisConfigFile; import java.math.BigInteger; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class EthNetworkConfigTest { @Test public void testDefaultMainnetConfig() { EthNetworkConfig config = EthNetworkConfig.getNetworkConfig(NetworkName.MAINNET); - assertThat(config.getDnsDiscoveryUrl()).isEqualTo(MAINNET_DISCOVERY_URL); - assertThat(config.getBootNodes()).isEqualTo(MAINNET_BOOTSTRAP_NODES); - assertThat(config.getNetworkId()).isEqualTo(BigInteger.ONE); + assertThat(config.dnsDiscoveryUrl()).isEqualTo(MAINNET_DISCOVERY_URL); + assertThat(config.bootNodes()).isEqualTo(MAINNET_BOOTSTRAP_NODES); + assertThat(config.networkId()).isEqualTo(BigInteger.ONE); } @Test - public void testDefaultGoerliConfig() { - EthNetworkConfig config = EthNetworkConfig.getNetworkConfig(NetworkName.GOERLI); - assertThat(config.getDnsDiscoveryUrl()).isEqualTo(GOERLI_DISCOVERY_URL); - assertThat(config.getBootNodes()).isEqualTo(GOERLI_BOOTSTRAP_NODES); - assertThat(config.getNetworkId()).isEqualTo(BigInteger.valueOf(5)); + public void testDefaultSepoliaConfig() { + EthNetworkConfig config = EthNetworkConfig.getNetworkConfig(NetworkName.SEPOLIA); + assertThat(config.dnsDiscoveryUrl()).isEqualTo(SEPOLIA_DISCOVERY_URL); + assertThat(config.bootNodes()).isEqualTo(SEPOLIA_BOOTSTRAP_NODES); + assertThat(config.networkId()).isEqualTo(BigInteger.valueOf(11155111)); } @Test public void testDefaultDevConfig() { EthNetworkConfig config = EthNetworkConfig.getNetworkConfig(NetworkName.DEV); - assertThat(config.getDnsDiscoveryUrl()).isNull(); - assertThat(config.getBootNodes()).isEmpty(); - assertThat(config.getNetworkId()).isEqualTo(BigInteger.valueOf(2018)); + assertThat(config.dnsDiscoveryUrl()).isNull(); + assertThat(config.bootNodes()).isEmpty(); + assertThat(config.networkId()).isEqualTo(BigInteger.valueOf(2018)); } @Test public void testDefaultFutureConfig() { EthNetworkConfig config = EthNetworkConfig.getNetworkConfig(NetworkName.FUTURE_EIPS); - assertThat(config.getDnsDiscoveryUrl()).isNull(); - assertThat(config.getBootNodes()).isEmpty(); - assertThat(config.getNetworkId()).isEqualTo(BigInteger.valueOf(2022)); + assertThat(config.dnsDiscoveryUrl()).isNull(); + assertThat(config.bootNodes()).isEmpty(); + assertThat(config.networkId()).isEqualTo(BigInteger.valueOf(2022)); } @Test public void testDefaultExperimentalConfig() { EthNetworkConfig config = EthNetworkConfig.getNetworkConfig(NetworkName.EXPERIMENTAL_EIPS); - assertThat(config.getDnsDiscoveryUrl()).isNull(); - assertThat(config.getBootNodes()).isEmpty(); - assertThat(config.getNetworkId()).isEqualTo(BigInteger.valueOf(2023)); + assertThat(config.dnsDiscoveryUrl()).isNull(); + assertThat(config.bootNodes()).isEmpty(); + assertThat(config.networkId()).isEqualTo(BigInteger.valueOf(2023)); } @Test @@ -75,11 +77,20 @@ public void testBuilderWithNetworkId() { EthNetworkConfig config = new EthNetworkConfig.Builder(EthNetworkConfig.getNetworkConfig(MAINNET)) .setNetworkId(BigInteger.valueOf(42)) - .setGenesisConfig("{\"config\":{\"chainId\":\"1234567\"}") + .setGenesisConfigFile( + GenesisConfigFile.fromConfig( + """ + { + "config":{ + "chainId":"1234567" + } + } + """)) .build(); - assertThat(config.getGenesisConfig()).isEqualTo("{\"config\":{\"chainId\":\"1234567\"}"); - assertThat(config.getDnsDiscoveryUrl()).isNotNull(); - assertThat(config.getBootNodes()).isNotEmpty(); - assertThat(config.getNetworkId()).isEqualTo(BigInteger.valueOf(42)); + assertThat(config.genesisConfigFile().getConfigOptions().getChainId()) + .contains(BigInteger.valueOf(1234567)); + assertThat(config.dnsDiscoveryUrl()).isNotNull(); + assertThat(config.bootNodes()).isNotEmpty(); + assertThat(config.networkId()).isEqualTo(BigInteger.valueOf(42)); } } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/config/ProfilesCompletionCandidatesTest.java b/besu/src/test/java/org/hyperledger/besu/cli/config/ProfilesCompletionCandidatesTest.java new file mode 100644 index 00000000000..69cbf1de700 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/config/ProfilesCompletionCandidatesTest.java @@ -0,0 +1,91 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.config; + +import org.hyperledger.besu.cli.util.ProfileFinder; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class ProfilesCompletionCandidatesTest { + @TempDir private static Path tempProfilesDir; + private static String originalProfilesDirProperty; + + @BeforeAll + public static void copyExternalProfiles() throws IOException { + for (String internalProfileName : InternalProfileName.getInternalProfileNames()) { + final Path profilePath = tempProfilesDir.resolve(internalProfileName + "_external.toml"); + + String profileConfigFile = + InternalProfileName.valueOfIgnoreCase(internalProfileName).get().getConfigFile(); + try (InputStream resourceUrl = + ProfileFinder.class.getClassLoader().getResourceAsStream(profileConfigFile)) { + if (resourceUrl != null) { + Files.copy(resourceUrl, profilePath); + } + } + } + } + + @BeforeAll + public static void setupSystemProperty() { + originalProfilesDirProperty = System.getProperty("besu.profiles.dir"); + // sets the system property for the test + System.setProperty("besu.profiles.dir", tempProfilesDir.toString()); + } + + @Test + void profileCompletionCandidates_shouldIncludeInternalAndExternalProfiles() { + Iterator candidates = new ProfilesCompletionCandidates().iterator(); + // convert Iterator to List + List candidatesList = new ArrayList<>(); + candidates.forEachRemaining(candidatesList::add); + + Assertions.assertThat(candidatesList).containsExactlyInAnyOrderElementsOf(allProfileNames()); + } + + static Set allProfileNames() { + final Set profileNames = new TreeSet<>(InternalProfileName.getInternalProfileNames()); + final Set externalProfileNames = + InternalProfileName.getInternalProfileNames().stream() + .map(name -> name + "_external") + .collect(Collectors.toSet()); + profileNames.addAll(externalProfileNames); + return profileNames; + } + + @AfterAll + public static void clearSystemProperty() { + if (originalProfilesDirProperty != null) { + System.setProperty("besu.profiles.dir", originalProfilesDirProperty); + } else { + System.clearProperty("besu.profiles.dir"); + } + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/converter/FractionConverterTest.java b/besu/src/test/java/org/hyperledger/besu/cli/converter/FractionConverterTest.java index 611109274d5..74abdeef5ae 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/converter/FractionConverterTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/converter/FractionConverterTest.java @@ -20,11 +20,11 @@ import org.hyperledger.besu.cli.converter.exception.FractionConversionException; import org.hyperledger.besu.util.number.Fraction; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class FractionConverterTest { private final FractionConverter fractionConverter = new FractionConverter(); diff --git a/besu/src/test/java/org/hyperledger/besu/cli/converter/MetricCategoryConverterTest.java b/besu/src/test/java/org/hyperledger/besu/cli/converter/MetricCategoryConverterTest.java index db34fb9d5e4..8c78b7fecc2 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/converter/MetricCategoryConverterTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/converter/MetricCategoryConverterTest.java @@ -20,20 +20,20 @@ import org.hyperledger.besu.plugin.services.metrics.MetricCategory; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class MetricCategoryConverterTest { private MetricCategoryConverter metricCategoryConverter; @Mock MetricCategory metricCategory; - @Before + @BeforeEach public void setUp() { metricCategoryConverter = new MetricCategoryConverter(); } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/converter/PercentageConverterTest.java b/besu/src/test/java/org/hyperledger/besu/cli/converter/PercentageConverterTest.java index 829a4ef35f6..8ee5143c207 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/converter/PercentageConverterTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/converter/PercentageConverterTest.java @@ -20,11 +20,11 @@ import org.hyperledger.besu.cli.converter.exception.PercentageConversionException; import org.hyperledger.besu.util.number.Percentage; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class PercentageConverterTest { private final PercentageConverter percentageConverter = new PercentageConverter(); diff --git a/besu/src/test/java/org/hyperledger/besu/cli/converter/SubnetInfoConverterTest.java b/besu/src/test/java/org/hyperledger/besu/cli/converter/SubnetInfoConverterTest.java new file mode 100644 index 00000000000..aaeb4536142 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/converter/SubnetInfoConverterTest.java @@ -0,0 +1,59 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.converter; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.apache.commons.net.util.SubnetUtils.SubnetInfo; +import org.junit.jupiter.api.Test; + +public class SubnetInfoConverterTest { + + @Test + void testCreateIpRestrictionHandlerWithValidSubnets() { + String subnet = "192.168.1.0/24"; + assertThat(parseSubnetRules(subnet).getCidrSignature()).isEqualTo(subnet); + } + + @Test + void testCreateIpRestrictionHandlerWithInvalidSubnet() { + assertThrows(IllegalArgumentException.class, () -> parseSubnetRules("abc")); + } + + @Test + void testCreateIpRestrictionHandlerMissingCIDR() { + assertThrows(IllegalArgumentException.class, () -> parseSubnetRules("192.168.1.0")); + } + + @Test + void testCreateIpRestrictionHandlerBigCIDR() { + assertThrows(IllegalArgumentException.class, () -> parseSubnetRules("192.168.1.0:25")); + } + + @Test + void testCreateIpRestrictionHandlerWithInvalidCIDR() { + assertThrows(IllegalArgumentException.class, () -> parseSubnetRules("192.168.1.0/abc")); + } + + @Test + void testCreateIpRestrictionHandlerWithEmptyString() { + assertThrows(IllegalArgumentException.class, () -> parseSubnetRules("")); + } + + private SubnetInfo parseSubnetRules(final String subnet) { + return new SubnetInfoConverter().convert(subnet); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/custom/RpcAuthFileValidatorTest.java b/besu/src/test/java/org/hyperledger/besu/cli/custom/RpcAuthFileValidatorTest.java index 714086245a4..32db1368af8 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/custom/RpcAuthFileValidatorTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/custom/RpcAuthFileValidatorTest.java @@ -17,14 +17,14 @@ import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; import picocli.CommandLine; import picocli.CommandLine.ParameterException; -@RunWith(MockitoJUnitRunner.StrictStubs.class) +@ExtendWith(MockitoExtension.class) public class RpcAuthFileValidatorTest { private static final String CORRECT_TOML = "/rpcauth/auth_correct.toml"; diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/AbstractCLIOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/AbstractCLIOptionsTest.java index 09b4e3b1365..1760b957ec6 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/options/AbstractCLIOptionsTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/AbstractCLIOptionsTest.java @@ -16,17 +16,16 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.cli.util.CommandLineUtils.DEPENDENCY_WARNING_MSG; -import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.verify; import org.hyperledger.besu.cli.CommandTestAbstract; import java.util.Collections; import java.util.List; +import java.util.function.BiFunction; import java.util.function.Consumer; -import org.junit.Test; +import org.junit.jupiter.api.Test; public abstract class AbstractCLIOptionsTest> extends CommandTestAbstract { @@ -69,7 +68,10 @@ private void getCLIOptions(final D domainObject) { final TestBesuCommand cmd = parseCommand(cliOptions); final T optionsFromCommand = getOptionsFromBesuCommand(cmd); - assertThat(optionsFromCommand).usingRecursiveComparison().isEqualTo(options); + assertThat(optionsFromCommand) + .usingRecursiveComparison() + .ignoringFields(getNonOptionFields()) + .isEqualTo(options); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); @@ -84,10 +86,10 @@ public void defaultValues() { final T optionsFromCommand = getOptionsFromBesuCommand(cmd); // Check default values supplied by CLI match expected default values - final String[] fieldsToIgnore = getFieldsWithComputedDefaults().toArray(new String[0]); assertThat(optionsFromCommand) .usingRecursiveComparison() - .ignoringFields(fieldsToIgnore) + .ignoringFields(getFieldsWithComputedDefaults()) + .ignoringFields(getNonOptionFields()) .isEqualTo(defaultOptions); } @@ -95,8 +97,12 @@ public void defaultValues() { protected abstract D createCustomizedDomainObject(); - protected List getFieldsWithComputedDefaults() { - return Collections.emptyList(); + protected String[] getFieldsWithComputedDefaults() { + return new String[0]; + } + + protected String[] getNonOptionFields() { + return new String[0]; } protected List getFieldsToIgnore() { @@ -108,10 +114,18 @@ protected List getFieldsToIgnore() { protected abstract T getOptionsFromBesuCommand(final TestBesuCommand besuCommand); protected void internalTestSuccess(final Consumer assertion, final String... args) { + internalTestSuccess((bc, conf) -> conf, assertion, args); + } + + protected void internalTestSuccess( + final BiFunction runtimeConf, + final Consumer assertion, + final String... args) { final TestBesuCommand cmd = parseCommand(args); final T options = getOptionsFromBesuCommand(cmd); - final D config = options.toDomainObject(); + final D config = runtimeConf.apply(cmd, options.toDomainObject()); + assertion.accept(config); assertThat(commandOutput.toString(UTF_8)).isEmpty(); @@ -125,29 +139,4 @@ protected void internalTestFailure(final String errorMsg, final String... args) assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).contains(errorMsg); } - - /** - * Check logger calls - * - *

Here we check the calls to logger and not the result of the log line as we don't test the - * logger itself but the fact that we call it. - * - * @param dependentOptions the string representing the list of dependent options names - * @param mainOption the main option name - */ - protected void verifyOptionsConstraintLoggerCall( - final String mainOption, final String... dependentOptions) { - verify(mockLogger, atLeast(1)) - .warn( - stringArgumentCaptor.capture(), - stringArgumentCaptor.capture(), - stringArgumentCaptor.capture()); - assertThat(stringArgumentCaptor.getAllValues().get(0)).isEqualTo(DEPENDENCY_WARNING_MSG); - - for (final String option : dependentOptions) { - assertThat(stringArgumentCaptor.getAllValues().get(1)).contains(option); - } - - assertThat(stringArgumentCaptor.getAllValues().get(2)).isEqualTo(mainOption); - } } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/ApiConfigurationOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/ApiConfigurationOptionsTest.java new file mode 100644 index 00000000000..6e6c17f4dda --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/ApiConfigurationOptionsTest.java @@ -0,0 +1,152 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.options; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; + +import org.hyperledger.besu.cli.CommandTestAbstract; +import org.hyperledger.besu.ethereum.api.ImmutableApiConfiguration; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class ApiConfigurationOptionsTest extends CommandTestAbstract { + + @Test + public void apiPriorityFeeLimitingEnabledOptionMustBeUsed() { + parseCommand("--api-gas-and-priority-fee-limiting-enabled"); + verify(mockRunnerBuilder).apiConfiguration(apiConfigurationCaptor.capture()); + verify(mockRunnerBuilder).build(); + assertThat(apiConfigurationCaptor.getValue()) + .isEqualTo( + ImmutableApiConfiguration.builder().isGasAndPriorityFeeLimitingEnabled(true).build()); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void apiPriorityFeeLowerBoundCoefficientOptionMustBeUsed() { + final long lowerBound = 150L; + parseCommand( + "--api-gas-and-priority-fee-lower-bound-coefficient", + Long.toString(lowerBound), + "--api-gas-and-priority-fee-limiting-enabled"); + verify(mockRunnerBuilder).apiConfiguration(apiConfigurationCaptor.capture()); + verify(mockRunnerBuilder).build(); + assertThat(apiConfigurationCaptor.getValue()) + .isEqualTo( + ImmutableApiConfiguration.builder() + .lowerBoundGasAndPriorityFeeCoefficient(lowerBound) + .isGasAndPriorityFeeLimitingEnabled(true) + .build()); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void + apiPriorityFeeLowerBoundCoefficients_MustNotBeGreaterThan_apiPriorityFeeUpperBoundCoefficient() { + final long lowerBound = 200L; + final long upperBound = 100L; + + parseCommand( + "--api-gas-and-priority-fee-limiting-enabled", + "--api-gas-and-priority-fee-lower-bound-coefficient", + Long.toString(lowerBound), + "--api-gas-and-priority-fee-upper-bound-coefficient", + Long.toString(upperBound)); + Mockito.verifyNoInteractions(mockRunnerBuilder); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains( + "--api-gas-and-priority-fee-lower-bound-coefficient cannot be greater than the value of --api-gas-and-priority-fee-upper-bound-coefficient"); + } + + @Test + public void apiPriorityFeeUpperBoundCoefficientsOptionMustBeUsed() { + final long upperBound = 200L; + parseCommand( + "--api-gas-and-priority-fee-upper-bound-coefficient", + Long.toString(upperBound), + "--api-gas-and-priority-fee-limiting-enabled"); + verify(mockRunnerBuilder).apiConfiguration(apiConfigurationCaptor.capture()); + verify(mockRunnerBuilder).build(); + assertThat(apiConfigurationCaptor.getValue()) + .isEqualTo( + ImmutableApiConfiguration.builder() + .upperBoundGasAndPriorityFeeCoefficient(upperBound) + .isGasAndPriorityFeeLimitingEnabled(true) + .build()); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcMaxLogsRangeOptionMustBeUsed() { + + final long rpcMaxLogsRange = 150L; + parseCommand("--rpc-max-logs-range", Long.toString(rpcMaxLogsRange)); + + verify(mockRunnerBuilder).apiConfiguration(apiConfigurationCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(apiConfigurationCaptor.getValue()) + .isEqualTo(ImmutableApiConfiguration.builder().maxLogsRange((rpcMaxLogsRange)).build()); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcGasCapOptionMustBeUsed() { + final long rpcGasCap = 150L; + parseCommand("--rpc-gas-cap", Long.toString(rpcGasCap)); + + verify(mockRunnerBuilder).apiConfiguration(apiConfigurationCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(apiConfigurationCaptor.getValue()) + .isEqualTo(ImmutableApiConfiguration.builder().gasCap((rpcGasCap)).build()); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcMaxTraceFilterOptionMustBeUsed() { + final long rpcMaxTraceFilterOption = 150L; + parseCommand("--rpc-max-trace-filter-range", Long.toString(rpcMaxTraceFilterOption)); + + verify(mockRunnerBuilder).apiConfiguration(apiConfigurationCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(apiConfigurationCaptor.getValue()) + .isEqualTo( + ImmutableApiConfiguration.builder() + .maxTraceFilterRange((rpcMaxTraceFilterOption)) + .build()); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/EthProtocolOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/EthProtocolOptionsTest.java index 9d1d696ab43..6f7aa637cdd 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/options/EthProtocolOptionsTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/EthProtocolOptionsTest.java @@ -21,11 +21,11 @@ import org.hyperledger.besu.cli.options.unstable.EthProtocolOptions; import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class EthProtocolOptionsTest extends AbstractCLIOptionsTest { diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/GraphQlOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/GraphQlOptionsTest.java new file mode 100644 index 00000000000..8c0e32fb2f5 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/GraphQlOptionsTest.java @@ -0,0 +1,93 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.options; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; + +import org.hyperledger.besu.cli.CommandTestAbstract; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class GraphQlOptionsTest extends CommandTestAbstract { + @Test + public void graphQLHttpEnabledPropertyMustBeUsed() { + parseCommand("--graphql-http-enabled"); + + verify(mockRunnerBuilder).graphQLConfiguration(graphQLConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(graphQLConfigArgumentCaptor.getValue().isEnabled()).isTrue(); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void graphQLHttpHostAndPortOptionsMustBeUsed() { + + final String host = "1.2.3.4"; + final int port = 1234; + parseCommand( + "--graphql-http-enabled", + "--graphql-http-host", + host, + "--graphql-http-port", + String.valueOf(port)); + + verify(mockRunnerBuilder).graphQLConfiguration(graphQLConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(graphQLConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); + assertThat(graphQLConfigArgumentCaptor.getValue().getPort()).isEqualTo(port); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void graphQLHttpHostMayBeLocalhost() { + + final String host = "localhost"; + parseCommand("--graphql-http-enabled", "--graphql-http-host", host); + + verify(mockRunnerBuilder).graphQLConfiguration(graphQLConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(graphQLConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void graphQLHttpHostMayBeIPv6() { + + final String host = "2600:DB8::8545"; + parseCommand("--graphql-http-enabled", "--graphql-http-host", host); + + verify(mockRunnerBuilder).graphQLConfiguration(graphQLConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(graphQLConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/JsonRpcHttpOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/JsonRpcHttpOptionsTest.java new file mode 100644 index 00000000000..afb53bac934 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/JsonRpcHttpOptionsTest.java @@ -0,0 +1,926 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.options; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.ETH; +import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.NET; +import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.PERM; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; + +import org.hyperledger.besu.cli.CommandTestAbstract; +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.authentication.JwtAlgorithm; +import org.hyperledger.besu.ethereum.api.tls.TlsConfiguration; +import org.hyperledger.besu.plugin.services.rpc.PluginRpcRequest; + +import java.io.IOException; +import java.net.ServerSocket; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; +import java.util.function.Function; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class JsonRpcHttpOptionsTest extends CommandTestAbstract { + + @Test + public void rpcHttpEnabledPropertyMustBeUsed() { + parseCommand("--rpc-http-enabled"); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().isEnabled()).isTrue(); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcApisPropertyMustBeUsed() { + parseCommand("--rpc-http-api", "ETH,NET,PERM", "--rpc-http-enabled"); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + verify(mockLogger) + .warn("Permissions are disabled. Cannot enable PERM APIs when not using Permissions."); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getRpcApis()) + .containsExactlyInAnyOrder(ETH.name(), NET.name(), PERM.name()); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcApisPropertyIgnoresDuplicatesAndMustBeUsed() { + parseCommand("--rpc-http-api", "ETH,NET,NET", "--rpc-http-enabled"); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getRpcApis()) + .containsExactlyInAnyOrder(ETH.name(), NET.name()); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcApiNoAuthMethodsIgnoresDuplicatesAndMustBeUsed() { + parseCommand( + "--rpc-http-api-methods-no-auth", + "admin_peers, admin_peers, eth_getWork", + "--rpc-http-enabled"); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getNoAuthRpcApis()) + .containsExactlyInAnyOrder( + RpcMethod.ADMIN_PEERS.getMethodName(), RpcMethod.ETH_GET_WORK.getMethodName()); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpNoAuthApiMethodsCannotBeInvalid() { + parseCommand("--rpc-http-enabled", "--rpc-http-api-method-no-auth", "invalid"); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains( + "Invalid value for option '--rpc-http-api-methods-no-auth', options must be valid RPC methods"); + } + + @Test + public void rpcHttpOptionsRequiresServiceToBeEnabled() { + parseCommand( + "--rpc-http-api", + "ETH,NET", + "--rpc-http-host", + "0.0.0.0", + "--rpc-http-port", + "1234", + "--rpc-http-cors-origins", + "all", + "--rpc-http-max-active-connections", + "88"); + + verifyOptionsConstraintLoggerCall( + "--rpc-http-enabled", + "--rpc-http-host", + "--rpc-http-port", + "--rpc-http-cors-origins", + "--rpc-http-api", + "--rpc-http-max-active-connections"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpOptionsRequiresServiceToBeEnabledToml() throws IOException { + final Path toml = + createTempFile( + "toml", + "rpc-http-api=[\"ETH\",\"NET\"]\n" + + "rpc-http-host=\"0.0.0.0\"\n" + + "rpc-http-port=1234\n" + + "rpc-http-cors-origins=[\"all\"]\n" + + "rpc-http-max-active-connections=88"); + + parseCommand("--config-file", toml.toString()); + + verifyOptionsConstraintLoggerCall( + "--rpc-http-enabled", + "--rpc-http-host", + "--rpc-http-port", + "--rpc-http-cors-origins", + "--rpc-http-api", + "--rpc-http-max-active-connections"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpHostAndPortOptionsMustBeUsed() { + + final String host = "1.2.3.4"; + final int port = 1234; + parseCommand( + "--rpc-http-enabled", "--rpc-http-host", host, "--rpc-http-port", String.valueOf(port)); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); + assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpHostMayBeLocalhost() { + + final String host = "localhost"; + parseCommand("--rpc-http-enabled", "--rpc-http-host", host); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpHostMayBeIPv6() { + + final String host = "2600:DB8::8545"; + parseCommand("--rpc-http-enabled", "--rpc-http-host", host); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpMaxActiveConnectionsPropertyMustBeUsed() { + final int maxConnections = 99; + parseCommand("--rpc-http-max-active-connections", String.valueOf(maxConnections)); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getMaxActiveConnections()) + .isEqualTo(maxConnections); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpTlsRequiresRpcHttpEnabled() { + parseCommand("--rpc-http-tls-enabled"); + + verifyOptionsConstraintLoggerCall("--rpc-http-enabled", "--rpc-http-tls-enabled"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpTlsRequiresRpcHttpEnabledToml() throws IOException { + final Path toml = createTempFile("toml", "rpc-http-tls-enabled=true\n"); + + parseCommand("--config-file", toml.toString()); + + verifyOptionsConstraintLoggerCall("--rpc-http-enabled", "--rpc-http-tls-enabled"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpTlsWithoutKeystoreReportsError() { + parseCommand("--rpc-http-enabled", "--rpc-http-tls-enabled"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Keystore file is required when TLS is enabled for JSON-RPC HTTP endpoint"); + } + + @Test + public void rpcHttpTlsWithoutPasswordfileReportsError() { + parseCommand( + "--rpc-http-enabled", + "--rpc-http-tls-enabled", + "--rpc-http-tls-keystore-file", + "/tmp/test.p12"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains( + "File containing password to unlock keystore is required when TLS is enabled for JSON-RPC HTTP endpoint"); + } + + @Test + public void rpcHttpTlsKeystoreAndPasswordMustBeUsed() { + final String host = "1.2.3.4"; + final int port = 1234; + final String keystoreFile = "/tmp/test.p12"; + final String keystorePasswordFile = "/tmp/test.txt"; + + parseCommand( + "--rpc-http-enabled", + "--rpc-http-host", + host, + "--rpc-http-port", + String.valueOf(port), + "--rpc-http-tls-enabled", + "--rpc-http-tls-keystore-file", + keystoreFile, + "--rpc-http-tls-keystore-password-file", + keystorePasswordFile); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); + assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port); + final Optional tlsConfiguration = + jsonRpcConfigArgumentCaptor.getValue().getTlsConfiguration(); + assertThat(tlsConfiguration.isPresent()).isTrue(); + assertThat(tlsConfiguration.get().getKeyStorePath()).isEqualTo(Path.of(keystoreFile)); + assertThat(tlsConfiguration.get().getClientAuthConfiguration().isEmpty()).isTrue(); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpTlsClientAuthWithoutKnownFileReportsError() { + final String host = "1.2.3.4"; + final int port = 1234; + final String keystoreFile = "/tmp/test.p12"; + final String keystorePasswordFile = "/tmp/test.txt"; + parseCommand( + "--rpc-http-enabled", + "--rpc-http-host", + host, + "--rpc-http-port", + String.valueOf(port), + "--rpc-http-tls-enabled", + "--rpc-http-tls-keystore-file", + keystoreFile, + "--rpc-http-tls-keystore-password-file", + keystorePasswordFile, + "--rpc-http-tls-client-auth-enabled"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains( + "Known-clients file must be specified or CA clients must be enabled when TLS client authentication is enabled for JSON-RPC HTTP endpoint"); + } + + @Test + public void rpcHttpTlsClientAuthWithKnownClientFile() { + final String host = "1.2.3.4"; + final int port = 1234; + final String keystoreFile = "/tmp/test.p12"; + final String keystorePasswordFile = "/tmp/test.txt"; + final String knownClientFile = "/tmp/knownClientFile"; + parseCommand( + "--rpc-http-enabled", + "--rpc-http-host", + host, + "--rpc-http-port", + String.valueOf(port), + "--rpc-http-tls-enabled", + "--rpc-http-tls-keystore-file", + keystoreFile, + "--rpc-http-tls-keystore-password-file", + keystorePasswordFile, + "--rpc-http-tls-client-auth-enabled", + "--rpc-http-tls-known-clients-file", + knownClientFile); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); + assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port); + final Optional tlsConfiguration = + jsonRpcConfigArgumentCaptor.getValue().getTlsConfiguration(); + assertThat(tlsConfiguration.isPresent()).isTrue(); + assertThat(tlsConfiguration.get().getKeyStorePath()).isEqualTo(Path.of(keystoreFile)); + assertThat(tlsConfiguration.get().getClientAuthConfiguration().isPresent()).isTrue(); + assertThat( + tlsConfiguration.get().getClientAuthConfiguration().get().getKnownClientsFile().get()) + .isEqualTo(Path.of(knownClientFile)); + assertThat(tlsConfiguration.get().getClientAuthConfiguration().get().isCaClientsEnabled()) + .isFalse(); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpTlsClientAuthWithCAClient() { + final String host = "1.2.3.4"; + final int port = 1234; + final String keystoreFile = "/tmp/test.p12"; + final String keystorePasswordFile = "/tmp/test.txt"; + parseCommand( + "--rpc-http-enabled", + "--rpc-http-host", + host, + "--rpc-http-port", + String.valueOf(port), + "--rpc-http-tls-enabled", + "--rpc-http-tls-keystore-file", + keystoreFile, + "--rpc-http-tls-keystore-password-file", + keystorePasswordFile, + "--rpc-http-tls-client-auth-enabled", + "--rpc-http-tls-ca-clients-enabled"); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); + assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port); + final Optional tlsConfiguration = + jsonRpcConfigArgumentCaptor.getValue().getTlsConfiguration(); + assertThat(tlsConfiguration.isPresent()).isTrue(); + assertThat(tlsConfiguration.get().getKeyStorePath()).isEqualTo(Path.of(keystoreFile)); + assertThat(tlsConfiguration.get().getClientAuthConfiguration().isPresent()).isTrue(); + assertThat( + tlsConfiguration + .get() + .getClientAuthConfiguration() + .get() + .getKnownClientsFile() + .isEmpty()) + .isTrue(); + assertThat(tlsConfiguration.get().getClientAuthConfiguration().get().isCaClientsEnabled()) + .isTrue(); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpTlsClientAuthWithCAClientAndKnownClientFile() { + final String host = "1.2.3.4"; + final int port = 1234; + final String keystoreFile = "/tmp/test.p12"; + final String keystorePasswordFile = "/tmp/test.txt"; + final String knownClientFile = "/tmp/knownClientFile"; + parseCommand( + "--rpc-http-enabled", + "--rpc-http-host", + host, + "--rpc-http-port", + String.valueOf(port), + "--rpc-http-tls-enabled", + "--rpc-http-tls-keystore-file", + keystoreFile, + "--rpc-http-tls-keystore-password-file", + keystorePasswordFile, + "--rpc-http-tls-client-auth-enabled", + "--rpc-http-tls-ca-clients-enabled", + "--rpc-http-tls-known-clients-file", + knownClientFile); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); + assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port); + final Optional tlsConfiguration = + jsonRpcConfigArgumentCaptor.getValue().getTlsConfiguration(); + assertThat(tlsConfiguration.isPresent()).isTrue(); + assertThat(tlsConfiguration.get().getKeyStorePath()).isEqualTo(Path.of(keystoreFile)); + assertThat(tlsConfiguration.get().getClientAuthConfiguration().isPresent()).isTrue(); + assertThat( + tlsConfiguration.get().getClientAuthConfiguration().get().getKnownClientsFile().get()) + .isEqualTo(Path.of(knownClientFile)); + assertThat(tlsConfiguration.get().getClientAuthConfiguration().get().isCaClientsEnabled()) + .isTrue(); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpTlsCheckDefaultProtocolsAndCipherSuites() { + final String host = "1.2.3.4"; + final int port = 1234; + final String keystoreFile = "/tmp/test.p12"; + final String keystorePasswordFile = "/tmp/test.txt"; + + parseCommand( + "--rpc-http-enabled", + "--rpc-http-host", + host, + "--rpc-http-port", + String.valueOf(port), + "--rpc-http-tls-enabled", + "--rpc-http-tls-keystore-file", + keystoreFile, + "--rpc-http-tls-keystore-password-file", + keystorePasswordFile); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); + assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port); + final Optional tlsConfiguration = + jsonRpcConfigArgumentCaptor.getValue().getTlsConfiguration(); + assertThat(tlsConfiguration).isPresent(); + assertThat(tlsConfiguration.get().getKeyStorePath()).isEqualTo(Path.of(keystoreFile)); + assertThat(tlsConfiguration.get().getClientAuthConfiguration()).isEmpty(); + assertThat(tlsConfiguration.get().getCipherSuites().get()).isEmpty(); + assertThat(tlsConfiguration.get().getSecureTransportProtocols().get()) + .containsExactly("TLSv1.3", "TLSv1.2"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpTlsCheckInvalidProtocols() { + final String host = "1.2.3.4"; + final int port = 1234; + final String keystoreFile = "/tmp/test.p12"; + final String keystorePasswordFile = "/tmp/test.txt"; + final String protocol = "TLsv1.4"; + + parseCommand( + "--rpc-http-enabled", + "--rpc-http-host", + host, + "--rpc-http-port", + String.valueOf(port), + "--rpc-http-tls-enabled", + "--rpc-http-tls-keystore-file", + keystoreFile, + "--rpc-http-tls-keystore-password-file", + keystorePasswordFile, + "--rpc-http-tls-protocols", + protocol); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).contains("No valid TLS protocols specified"); + } + + @Test + public void rpcHttpTlsCheckInvalidCipherSuites() { + final String host = "1.2.3.4"; + final int port = 1234; + final String keystoreFile = "/tmp/test.p12"; + final String keystorePasswordFile = "/tmp/test.txt"; + final String cipherSuites = "Invalid"; + + parseCommand( + "--rpc-http-enabled", + "--rpc-http-host", + host, + "--rpc-http-port", + String.valueOf(port), + "--rpc-http-tls-enabled", + "--rpc-http-tls-keystore-file", + keystoreFile, + "--rpc-http-tls-keystore-password-file", + keystorePasswordFile, + "--rpc-http-tls-cipher-suites", + cipherSuites); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Invalid TLS cipher suite specified " + cipherSuites); + } + + @Test + public void rpcHttpTlsCheckValidProtocolsAndCipherSuites() { + final String host = "1.2.3.4"; + final int port = 1234; + final String keystoreFile = "/tmp/test.p12"; + final String keystorePasswordFile = "/tmp/test.txt"; + final String protocols = "TLSv1.3,TLSv1.2"; + final String cipherSuites = + "TLS_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"; + + parseCommand( + "--rpc-http-enabled", + "--rpc-http-host", + host, + "--rpc-http-port", + String.valueOf(port), + "--rpc-http-tls-enabled", + "--rpc-http-tls-keystore-file", + keystoreFile, + "--rpc-http-tls-keystore-password-file", + keystorePasswordFile, + "--rpc-http-tls-protocols", + protocols, + "--rpc-http-tls-cipher-suites", + cipherSuites); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); + assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port); + final Optional tlsConfiguration = + jsonRpcConfigArgumentCaptor.getValue().getTlsConfiguration(); + assertThat(tlsConfiguration).isPresent(); + assertThat(tlsConfiguration.get().getKeyStorePath()).isEqualTo(Path.of(keystoreFile)); + assertThat(tlsConfiguration.get().getClientAuthConfiguration()).isEmpty(); + assertThat(tlsConfiguration.get().getCipherSuites().get()) + .containsExactlyInAnyOrder( + "TLS_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"); + assertThat(tlsConfiguration.get().getSecureTransportProtocols().get()) + .containsExactlyInAnyOrder("TLSv1.2", "TLSv1.3"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpTlsWarnIfCipherSuitesSpecifiedWithoutTls() { + final String host = "1.2.3.4"; + final int port = 1234; + final String cipherSuites = "Invalid"; + + parseCommand( + "--rpc-http-enabled", + "--engine-rpc-enabled", + "--rpc-http-host", + host, + "--rpc-http-port", + String.valueOf(port), + "--rpc-http-tls-cipher-suite", + cipherSuites); + verify( + mockLogger, + times(2)) // this is verified for both the full suite of apis, and the engine group. + .warn( + "{} has been ignored because {} was not defined on the command line.", + "--rpc-http-tls-cipher-suite", + "--rpc-http-tls-enabled"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpCorsOriginsTwoDomainsMustBuildListWithBothDomains() { + final String[] origins = {"http://domain1.com", "https://domain2.com"}; + parseCommand("--rpc-http-enabled", "--rpc-http-cors-origins", String.join(",", origins)); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getCorsAllowedDomains().toArray()) + .isEqualTo(origins); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpCorsOriginsDoubleCommaFilteredOut() { + final String[] origins = {"http://domain1.com", "https://domain2.com"}; + parseCommand("--rpc-http-enabled", "--rpc-http-cors-origins", String.join(",,", origins)); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getCorsAllowedDomains().toArray()) + .isEqualTo(origins); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpCorsOriginsWithWildcardMustBuildListWithWildcard() { + final String[] origins = {"*"}; + parseCommand("--rpc-http-enabled", "--rpc-http-cors-origins", String.join(",", origins)); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getCorsAllowedDomains().toArray()) + .isEqualTo(origins); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpCorsOriginsWithAllMustBuildListWithWildcard() { + parseCommand("--rpc-http-enabled", "--rpc-http-cors-origins", "all"); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getCorsAllowedDomains()).containsExactly("*"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpCorsOriginsWithNoneMustBuildEmptyList() { + final String[] origins = {"none"}; + parseCommand("--rpc-http-enabled", "--rpc-http-cors-origins", String.join(",", origins)); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getCorsAllowedDomains()).isEmpty(); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpCorsOriginsNoneWithAnotherDomainMustFail() { + final String[] origins = {"http://domain1.com", "none"}; + parseCommand("--rpc-http-cors-origins", String.join(",", origins)); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Value 'none' can't be used with other domains"); + } + + @Test + public void rpcHttpCorsOriginsNoneWithAnotherDomainMustFailNoneFirst() { + final String[] origins = {"none", "http://domain1.com"}; + parseCommand("--rpc-http-cors-origins", String.join(",", origins)); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Value 'none' can't be used with other domains"); + } + + @Test + public void rpcHttpCorsOriginsAllWithAnotherDomainMustFail() { + parseCommand("--rpc-http-cors-origins=http://domain1.com,all"); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Values '*' or 'all' can't be used with other domains"); + } + + @Test + public void rpcHttpCorsOriginsAllWithAnotherDomainMustFailAsFlags() { + parseCommand("--rpc-http-cors-origins=http://domain1.com", "--rpc-http-cors-origins=all"); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Values '*' or 'all' can't be used with other domains"); + } + + @Test + public void rpcHttpCorsOriginsWildcardWithAnotherDomainMustFail() { + parseCommand("--rpc-http-cors-origins=http://domain1.com,*"); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Values '*' or 'all' can't be used with other domains"); + } + + @Test + public void rpcHttpCorsOriginsWildcardWithAnotherDomainMustFailAsFlags() { + parseCommand("--rpc-http-cors-origins=http://domain1.com", "--rpc-http-cors-origins=*"); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Values '*' or 'all' can't be used with other domains"); + } + + @Test + public void rpcHttpCorsOriginsInvalidRegexShouldFail() { + final String[] origins = {"**"}; + parseCommand("--rpc-http-cors-origins", String.join(",", origins)); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Domain values result in invalid regex pattern"); + } + + @Test + public void rpcHttpCorsOriginsEmptyValueFails() { + parseCommand("--rpc-http-cors-origins="); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Domain cannot be empty string or null string."); + } + + @Test + public void rpcApisPropertyWithInvalidEntryMustDisplayError() { + parseCommand("--rpc-http-api", "BOB"); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + // PicoCLI uses longest option name for message when option has multiple names, so here plural. + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Invalid value for option '--rpc-http-api': invalid entries found [BOB]"); + } + + @Test + public void rpcApisPropertyWithPluginNamespaceAreValid() { + + rpcEndpointServiceImpl.registerRPCEndpoint( + "bob", "method", (Function) request -> "nothing"); + + parseCommand("--rpc-http-api", "BOB"); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getRpcApis()) + .containsExactlyInAnyOrder("BOB"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpMaxRequestContentLengthOptionMustBeUsed() { + final int rpcHttpMaxRequestContentLength = 1; + parseCommand( + "--rpc-http-max-request-content-length", Long.toString(rpcHttpMaxRequestContentLength)); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getMaxRequestContentLength()) + .isEqualTo(rpcHttpMaxRequestContentLength); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcHttpMaxBatchSizeOptionMustBeUsed() { + final int rpcHttpMaxBatchSize = 1; + parseCommand("--rpc-http-max-batch-size", Integer.toString(rpcHttpMaxBatchSize)); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getMaxBatchSize()) + .isEqualTo(rpcHttpMaxBatchSize); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void portInUseReportsError() throws IOException { + final ServerSocket serverSocket = new ServerSocket(8545); + + parseCommandWithPortCheck("--rpc-http-enabled"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("Port(s) '[8545]' already in use. Check for other processes using the port(s)."); + + serverSocket.close(); + } + + @Test + public void assertThatCheckPortClashRejectsAsExpected() throws Exception { + // use WS port for HTTP + final int port = 8546; + parseCommand("--rpc-http-enabled", "--rpc-http-port", String.valueOf(port), "--rpc-ws-enabled"); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains( + "Port number '8546' has been specified multiple times. Please review the supplied configuration."); + } + + @Test + public void assertThatCheckPortClashAcceptsAsExpected() throws Exception { + // use WS port for HTTP + final int port = 8546; + parseCommand("--rpc-http-enabled", "--rpc-http-port", String.valueOf(port)); + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + assertThat(jsonRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void httpAuthenticationWithoutRequiredConfiguredOptionsMustFail() { + parseCommand("--rpc-http-enabled", "--rpc-http-authentication-enabled"); + + verifyNoInteractions(mockRunnerBuilder); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains( + "Unable to authenticate JSON-RPC HTTP endpoint without a supplied credentials file or authentication public key file"); + } + + @Test + public void httpAuthenticationAlgorithIsConfigured() { + parseCommand("--rpc-http-authentication-jwt-algorithm", "ES256"); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getAuthenticationAlgorithm()) + .isEqualTo(JwtAlgorithm.ES256); + } + + @Test + public void httpAuthenticationPublicKeyIsConfigured() throws IOException { + final Path publicKey = Files.createTempFile("public_key", ""); + parseCommand("--rpc-http-authentication-jwt-public-key-file", publicKey.toString()); + + verify(mockRunnerBuilder).jsonRpcConfiguration(jsonRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(jsonRpcConfigArgumentCaptor.getValue().getAuthenticationPublicKeyFile().getPath()) + .isEqualTo(publicKey.toString()); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/MetricsCLIOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/MetricsCLIOptionsTest.java index 8044770a9b4..e88462216f4 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/options/MetricsCLIOptionsTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/MetricsCLIOptionsTest.java @@ -17,10 +17,10 @@ import org.hyperledger.besu.cli.options.unstable.MetricsCLIOptions; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class MetricsCLIOptionsTest extends AbstractCLIOptionsTest { diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/MiningOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/MiningOptionsTest.java index 0b7cc5e1224..e32ff0e4630 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/options/MiningOptionsTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/MiningOptionsTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,8 +15,8 @@ package org.hyperledger.besu.cli.options; import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.ethereum.core.MiningParameters.Unstable.DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME; -import static org.hyperledger.besu.ethereum.core.MiningParameters.Unstable.DEFAULT_POA_BLOCK_TXS_SELECTION_MAX_TIME; +import static org.hyperledger.besu.ethereum.core.MiningParameters.DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME; +import static org.hyperledger.besu.ethereum.core.MiningParameters.DEFAULT_POA_BLOCK_TXS_SELECTION_MAX_TIME; import static org.hyperledger.besu.ethereum.core.MiningParameters.Unstable.DEFAULT_POS_BLOCK_CREATION_MAX_TIME; import static org.mockito.Mockito.atMost; import static org.mockito.Mockito.verify; @@ -28,19 +28,20 @@ import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters.MutableInitValues; import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters.Unstable; import org.hyperledger.besu.ethereum.core.MiningParameters; -import org.hyperledger.besu.util.number.Percentage; +import org.hyperledger.besu.util.number.PositiveNumber; import java.io.IOException; import java.nio.file.Path; +import java.time.Duration; import java.util.Optional; import org.apache.tuweni.bytes.Bytes; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class MiningOptionsTest extends AbstractCLIOptionsTest { @Test @@ -314,44 +315,38 @@ public void posBlockCreationMaxTimeOutOfAllowedRange() { @Test public void blockTxsSelectionMaxTimeDefaultValue() { internalTestSuccess( + this::runtimeConfiguration, miningParams -> - assertThat(miningParams.getUnstable().getBlockTxsSelectionMaxTime()) + assertThat(miningParams.getNonPoaBlockTxsSelectionMaxTime()) .isEqualTo(DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME)); } @Test public void blockTxsSelectionMaxTimeOption() { internalTestSuccess( - miningParams -> - assertThat(miningParams.getUnstable().getBlockTxsSelectionMaxTime()).isEqualTo(1700L), - "--Xblock-txs-selection-max-time", + this::runtimeConfiguration, + miningParams -> assertThat(miningParams.getBlockTxsSelectionMaxTime()).isEqualTo(1700L), + "--block-txs-selection-max-time", "1700"); } - @Test - public void blockTxsSelectionMaxTimeOutOfAllowedRange() { - internalTestFailure( - "--Xblock-txs-selection-max-time must be positive and ≤ 5000", - "--Xblock-txs-selection-max-time", - "6000"); - } - @Test public void blockTxsSelectionMaxTimeIncompatibleWithPoaNetworks() throws IOException { final Path genesisFileIBFT2 = createFakeGenesisFile(VALID_GENESIS_IBFT2_POST_LONDON); internalTestFailure( - "--Xblock-txs-selection-max-time can't be used with PoA networks, see Xpoa-block-txs-selection-max-time instead", + "--block-txs-selection-max-time can't be used with PoA networks, see poa-block-txs-selection-max-time instead", "--genesis-file", genesisFileIBFT2.toString(), - "--Xblock-txs-selection-max-time", + "--block-txs-selection-max-time", "2"); } @Test public void poaBlockTxsSelectionMaxTimeDefaultValue() { internalTestSuccess( + this::runtimeConfiguration, miningParams -> - assertThat(miningParams.getUnstable().getPoaBlockTxsSelectionMaxTime()) + assertThat(miningParams.getPoaBlockTxsSelectionMaxTime()) .isEqualTo(DEFAULT_POA_BLOCK_TXS_SELECTION_MAX_TIME)); } @@ -359,28 +354,38 @@ public void poaBlockTxsSelectionMaxTimeDefaultValue() { public void poaBlockTxsSelectionMaxTimeOption() throws IOException { final Path genesisFileIBFT2 = createFakeGenesisFile(VALID_GENESIS_IBFT2_POST_LONDON); internalTestSuccess( + this::runtimeConfiguration, miningParams -> - assertThat(miningParams.getUnstable().getPoaBlockTxsSelectionMaxTime()) - .isEqualTo(Percentage.fromInt(80)), + assertThat(miningParams.getPoaBlockTxsSelectionMaxTime()) + .isEqualTo(PositiveNumber.fromInt(80)), "--genesis-file", genesisFileIBFT2.toString(), - "--Xpoa-block-txs-selection-max-time", + "--poa-block-txs-selection-max-time", "80"); } @Test - public void poaBlockTxsSelectionMaxTimeOutOfAllowedRange() { - internalTestFailure( - "Invalid value for option '--Xpoa-block-txs-selection-max-time': cannot convert '110' to Percentage", - "--Xpoa-block-txs-selection-max-time", - "110"); + public void poaBlockTxsSelectionMaxTimeOptionOver100Percent() throws IOException { + final Path genesisFileClique = createFakeGenesisFile(VALID_GENESIS_CLIQUE_POST_LONDON); + internalTestSuccess( + this::runtimeConfiguration, + miningParams -> { + assertThat(miningParams.getPoaBlockTxsSelectionMaxTime()) + .isEqualTo(PositiveNumber.fromInt(200)); + assertThat(miningParams.getBlockTxsSelectionMaxTime()) + .isEqualTo(Duration.ofSeconds(POA_BLOCK_PERIOD_SECONDS * 2).toMillis()); + }, + "--genesis-file", + genesisFileClique.toString(), + "--poa-block-txs-selection-max-time", + "200"); } @Test public void poaBlockTxsSelectionMaxTimeOnlyCompatibleWithPoaNetworks() { internalTestFailure( - "--Xpoa-block-txs-selection-max-time can be only used with PoA networks, see --Xblock-txs-selection-max-time instead", - "--Xpoa-block-txs-selection-max-time", + "--poa-block-txs-selection-max-time can be only used with PoA networks, see --block-txs-selection-max-time instead", + "--poa-block-txs-selection-max-time", "90"); } @@ -413,4 +418,17 @@ protected MiningOptions optionsFromDomainObject(final MiningParameters domainObj protected MiningOptions getOptionsFromBesuCommand(final TestBesuCommand besuCommand) { return besuCommand.getMiningOptions(); } + + @Override + protected String[] getNonOptionFields() { + return new String[] {"transactionSelectionService"}; + } + + private MiningParameters runtimeConfiguration( + final TestBesuCommand besuCommand, final MiningParameters miningParameters) { + if (besuCommand.getGenesisConfigOptions().isPoa()) { + miningParameters.setBlockPeriodSeconds(POA_BLOCK_PERIOD_SECONDS); + } + return miningParameters; + } } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/NetworkingOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/NetworkingOptionsTest.java index a2cbdc5942b..a645f28b548 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/options/NetworkingOptionsTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/NetworkingOptionsTest.java @@ -23,11 +23,11 @@ import java.util.Arrays; import java.util.List; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class NetworkingOptionsTest extends AbstractCLIOptionsTest { @@ -134,7 +134,7 @@ public void checkFilterByForkIdNotSet() { final NetworkingOptions options = cmd.getNetworkingOptions(); final NetworkingConfiguration networkingConfig = options.toDomainObject(); - assertThat(networkingConfig.getDiscovery().isFilterOnEnrForkIdEnabled()).isEqualTo(false); + assertThat(networkingConfig.getDiscovery().isFilterOnEnrForkIdEnabled()).isEqualTo(true); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); assertThat(commandOutput.toString(UTF_8)).isEmpty(); @@ -176,7 +176,6 @@ protected NetworkingConfiguration createCustomizedDomainObject() { NetworkingConfiguration.DEFAULT_INITIATE_CONNECTIONS_FREQUENCY_SEC + 10); config.setCheckMaintainedConnectionsFrequency( NetworkingConfiguration.DEFAULT_CHECK_MAINTAINED_CONNECTIONS_FREQUENCY_SEC + 10); - config.setPeerLowerBound(NetworkingConfiguration.DEFAULT_PEER_LOWER_BOUND - 10); return config; } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/OptionParserTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/OptionParserTest.java index db5e69e174b..d57e15d2612 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/options/OptionParserTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/OptionParserTest.java @@ -21,11 +21,11 @@ import com.google.common.collect.Range; import org.apache.tuweni.units.bigints.UInt256; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class OptionParserTest { @Test diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/PermissionsOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/PermissionsOptionsTest.java new file mode 100644 index 00000000000..59f2c1acd00 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/PermissionsOptionsTest.java @@ -0,0 +1,453 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.options; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; + +import org.hyperledger.besu.cli.CommandTestAbstract; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl; +import org.hyperledger.besu.ethereum.permissioning.LocalPermissioningConfiguration; +import org.hyperledger.besu.ethereum.permissioning.PermissioningConfiguration; +import org.hyperledger.besu.ethereum.permissioning.SmartContractPermissioningConfiguration; +import org.hyperledger.besu.plugin.data.EnodeURL; + +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import com.google.common.collect.Lists; +import com.google.common.io.Resources; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class PermissionsOptionsTest extends CommandTestAbstract { + private static final String PERMISSIONING_CONFIG_TOML = "/permissioning_config.toml"; + + @Test + public void errorIsRaisedIfStaticNodesAreNotAllowed(final @TempDir Path testFolder) + throws IOException { + final Path staticNodesFile = testFolder.resolve("static-nodes.json"); + final Path permissioningConfig = testFolder.resolve("permissioning.json"); + + final EnodeURL staticNodeURI = + EnodeURLImpl.builder() + .nodeId( + "50203c6bfca6874370e71aecc8958529fd723feb05013dc1abca8fc1fff845c5259faba05852e9dfe5ce172a7d6e7c2a3a5eaa8b541c8af15ea5518bbff5f2fa") + .ipAddress("127.0.0.1") + .useDefaultPorts() + .build(); + + final EnodeURL allowedNode = + EnodeURLImpl.builder() + .nodeId( + "50203c6bfca6874370e71aecc8958529fd723feb05013dc1abca8fc1fff845c5259faba05852e9dfe5ce172a7d6e7c2a3a5eaa8b541c8af15ea5518bbff5f2fa") + .useDefaultPorts() + .ipAddress("127.0.0.1") + .listeningPort(30304) + .build(); + + Files.write(staticNodesFile, ("[\"" + staticNodeURI.toString() + "\"]").getBytes(UTF_8)); + Files.write( + permissioningConfig, + ("nodes-allowlist=[\"" + allowedNode.toString() + "\"]").getBytes(UTF_8)); + + parseCommand( + "--data-path=" + testFolder, + "--bootnodes", + "--permissions-nodes-config-file-enabled=true", + "--permissions-nodes-config-file=" + permissioningConfig); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains(staticNodeURI.toString(), "not in nodes-allowlist"); + } + + @Test + public void nodePermissionsSmartContractWithoutOptionMustError() { + parseCommand("--permissions-nodes-contract-address"); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandErrorOutput.toString(UTF_8)) + .startsWith("Missing required parameter for option '--permissions-nodes-contract-address'"); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void nodePermissionsEnabledWithoutContractAddressMustError() { + parseCommand("--permissions-nodes-contract-enabled"); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("No node permissioning contract address specified"); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void nodePermissionsEnabledWithInvalidContractAddressMustError() { + parseCommand( + "--permissions-nodes-contract-enabled", + "--permissions-nodes-contract-address", + "invalid-smart-contract-address"); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandErrorOutput.toString(UTF_8)).contains("Invalid value"); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void nodePermissionsEnabledWithTooShortContractAddressMustError() { + parseCommand( + "--permissions-nodes-contract-enabled", "--permissions-nodes-contract-address", "0x1234"); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandErrorOutput.toString(UTF_8)).contains("Invalid value"); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void nodePermissionsSmartContractMustUseOption() { + + final String smartContractAddress = "0x0000000000000000000000000000000000001234"; + + parseCommand( + "--permissions-nodes-contract-enabled", + "--permissions-nodes-contract-address", + smartContractAddress); + final SmartContractPermissioningConfiguration smartContractPermissioningConfiguration = + new SmartContractPermissioningConfiguration(); + smartContractPermissioningConfiguration.setNodeSmartContractAddress( + Address.fromHexString(smartContractAddress)); + smartContractPermissioningConfiguration.setSmartContractNodeAllowlistEnabled(true); + + verify(mockRunnerBuilder) + .permissioningConfiguration(permissioningConfigurationArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + final PermissioningConfiguration config = + permissioningConfigurationArgumentCaptor.getValue().get(); + assertThat(config.getSmartContractConfig().get()) + .usingRecursiveComparison() + .isEqualTo(smartContractPermissioningConfiguration); + + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void nodePermissionsContractVersionDefaultValue() { + final SmartContractPermissioningConfiguration expectedConfig = + new SmartContractPermissioningConfiguration(); + expectedConfig.setNodeSmartContractAddress( + Address.fromHexString("0x0000000000000000000000000000000000001234")); + expectedConfig.setSmartContractNodeAllowlistEnabled(true); + expectedConfig.setNodeSmartContractInterfaceVersion(1); + + parseCommand( + "--permissions-nodes-contract-enabled", + "--permissions-nodes-contract-address", + "0x0000000000000000000000000000000000001234"); + + verify(mockRunnerBuilder) + .permissioningConfiguration(permissioningConfigurationArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + final PermissioningConfiguration config = + permissioningConfigurationArgumentCaptor.getValue().get(); + assertThat(config.getSmartContractConfig().get()) + .usingRecursiveComparison() + .isEqualTo(expectedConfig); + + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void nodePermissionsContractVersionSetsValue() { + final SmartContractPermissioningConfiguration expectedConfig = + new SmartContractPermissioningConfiguration(); + expectedConfig.setNodeSmartContractAddress( + Address.fromHexString("0x0000000000000000000000000000000000001234")); + expectedConfig.setSmartContractNodeAllowlistEnabled(true); + expectedConfig.setNodeSmartContractInterfaceVersion(2); + + parseCommand( + "--permissions-nodes-contract-enabled", + "--permissions-nodes-contract-address", + "0x0000000000000000000000000000000000001234", + "--permissions-nodes-contract-version", + "2"); + + verify(mockRunnerBuilder) + .permissioningConfiguration(permissioningConfigurationArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + final PermissioningConfiguration config = + permissioningConfigurationArgumentCaptor.getValue().get(); + assertThat(config.getSmartContractConfig().get()) + .usingRecursiveComparison() + .isEqualTo(expectedConfig); + + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void accountPermissionsSmartContractWithoutOptionMustError() { + parseCommand("--permissions-accounts-contract-address"); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandErrorOutput.toString(UTF_8)) + .startsWith( + "Missing required parameter for option '--permissions-accounts-contract-address'"); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void accountPermissionsEnabledWithoutContractAddressMustError() { + parseCommand("--permissions-accounts-contract-enabled"); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandErrorOutput.toString(UTF_8)) + .contains("No account permissioning contract address specified"); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void accountPermissionsEnabledWithInvalidContractAddressMustError() { + parseCommand( + "--permissions-accounts-contract-enabled", + "--permissions-accounts-contract-address", + "invalid-smart-contract-address"); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandErrorOutput.toString(UTF_8)).contains("Invalid value"); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void accountPermissionsEnabledWithTooShortContractAddressMustError() { + parseCommand( + "--permissions-accounts-contract-enabled", + "--permissions-accounts-contract-address", + "0x1234"); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandErrorOutput.toString(UTF_8)).contains("Invalid value"); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void accountPermissionsSmartContractMustUseOption() { + final String smartContractAddress = "0x0000000000000000000000000000000000001234"; + + parseCommand( + "--permissions-accounts-contract-enabled", + "--permissions-accounts-contract-address", + smartContractAddress); + final SmartContractPermissioningConfiguration smartContractPermissioningConfiguration = + new SmartContractPermissioningConfiguration(); + smartContractPermissioningConfiguration.setAccountSmartContractAddress( + Address.fromHexString(smartContractAddress)); + smartContractPermissioningConfiguration.setSmartContractAccountAllowlistEnabled(true); + + verify(mockRunnerBuilder) + .permissioningConfiguration(permissioningConfigurationArgumentCaptor.capture()); + final PermissioningConfiguration permissioningConfiguration = + permissioningConfigurationArgumentCaptor.getValue().get(); + assertThat(permissioningConfiguration.getSmartContractConfig()).isPresent(); + + final SmartContractPermissioningConfiguration effectiveSmartContractConfig = + permissioningConfiguration.getSmartContractConfig().get(); + assertThat(effectiveSmartContractConfig.isSmartContractAccountAllowlistEnabled()).isTrue(); + assertThat(effectiveSmartContractConfig.getAccountSmartContractAddress()) + .isEqualTo(Address.fromHexString(smartContractAddress)); + + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void nodePermissioningTomlPathWithoutOptionMustDisplayUsage() { + parseCommand("--permissions-nodes-config-file"); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandErrorOutput.toString(UTF_8)) + .startsWith("Missing required parameter for option '--permissions-nodes-config-file'"); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void accountPermissioningTomlPathWithoutOptionMustDisplayUsage() { + parseCommand("--permissions-accounts-config-file"); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandErrorOutput.toString(UTF_8)) + .startsWith("Missing required parameter for option '--permissions-accounts-config-file'"); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void nodePermissioningEnabledWithNonexistentConfigFileMustError() { + parseCommand( + "--permissions-nodes-config-file-enabled", + "--permissions-nodes-config-file", + "file-does-not-exist"); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandErrorOutput.toString(UTF_8)).contains("Configuration file does not exist"); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void accountPermissioningEnabledWithNonexistentConfigFileMustError() { + parseCommand( + "--permissions-accounts-config-file-enabled", + "--permissions-accounts-config-file", + "file-does-not-exist"); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandErrorOutput.toString(UTF_8)).contains("Configuration file does not exist"); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void nodePermissioningTomlFileWithNoPermissionsEnabledMustNotError() throws IOException { + + final URL configFile = this.getClass().getResource(PERMISSIONING_CONFIG_TOML); + final Path permToml = createTempFile("toml", Resources.toByteArray(configFile)); + parseCommand("--permissions-nodes-config-file", permToml.toString()); + + verify(mockRunnerBuilder).build(); + + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void accountPermissioningTomlFileWithNoPermissionsEnabledMustNotError() + throws IOException { + + final URL configFile = this.getClass().getResource(PERMISSIONING_CONFIG_TOML); + final Path permToml = createTempFile("toml", Resources.toByteArray(configFile)); + parseCommand("--permissions-accounts-config-file", permToml.toString()); + + verify(mockRunnerBuilder).build(); + + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void defaultPermissionsTomlFileWithNoPermissionsEnabledMustNotError() { + parseCommand("--p2p-enabled", "false"); + + verify(mockRunnerBuilder).build(); + + assertThat(commandErrorOutput.toString(UTF_8)).doesNotContain("no permissions enabled"); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void nodePermissioningTomlPathMustUseOption() throws IOException { + final List allowedNodes = + Lists.newArrayList( + EnodeURLImpl.fromString( + "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.9:4567"), + EnodeURLImpl.fromString( + "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.169.0.9:4568")); + + final URL configFile = this.getClass().getResource(PERMISSIONING_CONFIG_TOML); + final Path permToml = createTempFile("toml", Resources.toByteArray(configFile)); + + final String allowedNodesString = + allowedNodes.stream().map(Object::toString).collect(Collectors.joining(",")); + parseCommand( + "--permissions-nodes-config-file-enabled", + "--permissions-nodes-config-file", + permToml.toString(), + "--bootnodes", + allowedNodesString); + final LocalPermissioningConfiguration localPermissioningConfiguration = + LocalPermissioningConfiguration.createDefault(); + localPermissioningConfiguration.setNodePermissioningConfigFilePath(permToml.toString()); + localPermissioningConfiguration.setNodeAllowlist(allowedNodes); + + verify(mockRunnerBuilder) + .permissioningConfiguration(permissioningConfigurationArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + final PermissioningConfiguration config = + permissioningConfigurationArgumentCaptor.getValue().get(); + assertThat(config.getLocalConfig().get()) + .usingRecursiveComparison() + .isEqualTo(localPermissioningConfiguration); + + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void accountPermissioningTomlPathMustUseOption() throws IOException { + + final URL configFile = this.getClass().getResource(PERMISSIONING_CONFIG_TOML); + final Path permToml = createTempFile("toml", Resources.toByteArray(configFile)); + + parseCommand( + "--permissions-accounts-config-file-enabled", + "--permissions-accounts-config-file", + permToml.toString()); + final LocalPermissioningConfiguration localPermissioningConfiguration = + LocalPermissioningConfiguration.createDefault(); + localPermissioningConfiguration.setAccountPermissioningConfigFilePath(permToml.toString()); + localPermissioningConfiguration.setAccountAllowlist( + Collections.singletonList("0x0000000000000000000000000000000000000009")); + + verify(mockRunnerBuilder) + .permissioningConfiguration(permissioningConfigurationArgumentCaptor.capture()); + final PermissioningConfiguration permissioningConfiguration = + permissioningConfigurationArgumentCaptor.getValue().get(); + assertThat(permissioningConfiguration.getLocalConfig()).isPresent(); + + final LocalPermissioningConfiguration effectiveLocalPermissioningConfig = + permissioningConfiguration.getLocalConfig().get(); + assertThat(effectiveLocalPermissioningConfig.isAccountAllowlistEnabled()).isTrue(); + assertThat(effectiveLocalPermissioningConfig.getAccountPermissioningConfigFilePath()) + .isEqualTo(permToml.toString()); + + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/RpcWebsocketOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/RpcWebsocketOptionsTest.java new file mode 100644 index 00000000000..64483462ca6 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/RpcWebsocketOptionsTest.java @@ -0,0 +1,262 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.options; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.ETH; +import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.NET; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; + +import org.hyperledger.besu.cli.CommandTestAbstract; +import org.hyperledger.besu.ethereum.api.jsonrpc.authentication.JwtAlgorithm; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class RpcWebsocketOptionsTest extends CommandTestAbstract { + + @Test + public void rpcWsApiPropertyMustBeUsed() { + final CommandTestAbstract.TestBesuCommand command = + parseCommand("--rpc-ws-enabled", "--rpc-ws-api", "ETH, NET"); + + assertThat(command).isNotNull(); + verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(wsRpcConfigArgumentCaptor.getValue().getRpcApis()) + .containsExactlyInAnyOrder(ETH.name(), NET.name()); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcWsHostAndPortOptionMustBeUsed() { + final String host = "1.2.3.4"; + final int port = 1234; + parseCommand("--rpc-ws-enabled", "--rpc-ws-host", host, "--rpc-ws-port", String.valueOf(port)); + + verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(wsRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); + assertThat(wsRpcConfigArgumentCaptor.getValue().getPort()).isEqualTo(port); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcWsMaxFrameSizePropertyMustBeUsed() { + final int maxFrameSize = 65535; + parseCommand("--rpc-ws-max-frame-size", String.valueOf(maxFrameSize)); + + verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(wsRpcConfigArgumentCaptor.getValue().getMaxFrameSize()).isEqualTo(maxFrameSize); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcWsMaxActiveConnectionsPropertyMustBeUsed() { + final int maxConnections = 99; + parseCommand("--rpc-ws-max-active-connections", String.valueOf(maxConnections)); + + verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(wsRpcConfigArgumentCaptor.getValue().getMaxActiveConnections()) + .isEqualTo(maxConnections); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcWsRpcEnabledPropertyMustBeUsed() { + parseCommand("--rpc-ws-enabled"); + + verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(wsRpcConfigArgumentCaptor.getValue().isEnabled()).isTrue(); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void webSocketAuthenticationAlgorithmIsConfigured() { + parseCommand("--rpc-ws-authentication-jwt-algorithm", "ES256"); + + verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(wsRpcConfigArgumentCaptor.getValue().getAuthenticationAlgorithm()) + .isEqualTo(JwtAlgorithm.ES256); + } + + @Test + public void wsAuthenticationPublicKeyIsConfigured() throws IOException { + final Path publicKey = Files.createTempFile("public_key", ""); + parseCommand("--rpc-ws-authentication-jwt-public-key-file", publicKey.toString()); + + verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(wsRpcConfigArgumentCaptor.getValue().getAuthenticationPublicKeyFile().getPath()) + .isEqualTo(publicKey.toString()); + } + + @Test + public void rpcWsHostAndMayBeLocalhost() { + final String host = "localhost"; + parseCommand("--rpc-ws-enabled", "--rpc-ws-host", host); + + verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(wsRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcWsHostAndMayBeIPv6() { + final String host = "2600:DB8::8545"; + parseCommand("--rpc-ws-enabled", "--rpc-ws-host", host); + + verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(wsRpcConfigArgumentCaptor.getValue().getHost()).isEqualTo(host); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcWsNoAuthApiMethodsCannotBeInvalid() { + parseCommand("--rpc-ws-enabled", "--rpc-ws-api-methods-no-auth", "invalid"); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains( + "Invalid value for option '--rpc-ws-api-methods-no-auth', options must be valid RPC methods"); + } + + @Test + public void rpcWsApisPropertyWithInvalidEntryMustDisplayError() { + parseCommand("--rpc-ws-api", "ETH,BOB,TEST"); + + Mockito.verifyNoInteractions(mockRunnerBuilder); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + + assertThat(commandErrorOutput.toString(UTF_8).trim()) + .contains("Invalid value for option '--rpc-ws-api': invalid entries found [BOB, TEST]"); + } + + @Test + public void rpcWsOptionsRequiresServiceToBeEnabled() { + parseCommand( + "--rpc-ws-api", + "ETH,NET", + "--rpc-ws-host", + "0.0.0.0", + "--rpc-ws-port", + "1234", + "--rpc-ws-max-active-connections", + "77", + "--rpc-ws-max-frame-size", + "65535"); + + verifyOptionsConstraintLoggerCall( + "--rpc-ws-enabled", + "--rpc-ws-host", + "--rpc-ws-port", + "--rpc-ws-api", + "--rpc-ws-max-active-connections", + "--rpc-ws-max-frame-size"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void rpcWsOptionsRequiresServiceToBeEnabledToml() throws IOException { + final Path toml = + createTempFile( + "toml", + "rpc-ws-api=[\"ETH\", \"NET\"]\n" + + "rpc-ws-host=\"0.0.0.0\"\n" + + "rpc-ws-port=1234\n" + + "rpc-ws-max-active-connections=77\n" + + "rpc-ws-max-frame-size=65535\n"); + + parseCommand("--config-file", toml.toString()); + + verifyOptionsConstraintLoggerCall( + "--rpc-ws-enabled", + "--rpc-ws-host", + "--rpc-ws-port", + "--rpc-ws-api", + "--rpc-ws-max-active-connections", + "--rpc-ws-max-frame-size"); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void wsAuthenticationWithoutRequiredConfiguredOptionsMustFail() { + parseCommand("--rpc-ws-enabled", "--rpc-ws-authentication-enabled"); + + verifyNoInteractions(mockRunnerBuilder); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .contains( + "Unable to authenticate JSON-RPC WebSocket endpoint without a supplied credentials file or authentication public key file"); + } + + @Test + public void rpcWsRpcEnabledPropertyDefaultIsFalse() { + parseCommand(); + + verify(mockRunnerBuilder).webSocketConfiguration(wsRpcConfigArgumentCaptor.capture()); + verify(mockRunnerBuilder).build(); + + assertThat(wsRpcConfigArgumentCaptor.getValue().isEnabled()).isFalse(); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/SynchronizerOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/SynchronizerOptionsTest.java index 0e814a07e26..0511279c19f 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/options/SynchronizerOptionsTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/SynchronizerOptionsTest.java @@ -23,10 +23,10 @@ import java.util.List; import com.google.common.collect.Range; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class SynchronizerOptionsTest extends AbstractCLIOptionsTest { @@ -38,9 +38,9 @@ protected SynchronizerConfiguration.Builder createDefaultDomainObject() { @Override protected SynchronizerConfiguration.Builder createCustomizedDomainObject() { return SynchronizerConfiguration.builder() - .fastSyncPivotDistance(SynchronizerConfiguration.DEFAULT_PIVOT_DISTANCE_FROM_HEAD + 10) + .syncPivotDistance(SynchronizerConfiguration.DEFAULT_PIVOT_DISTANCE_FROM_HEAD + 10) .fastSyncFullValidationRate(SynchronizerConfiguration.DEFAULT_FULL_VALIDATION_RATE / 2) - .fastSyncMinimumPeerCount(SynchronizerConfiguration.DEFAULT_FAST_SYNC_MINIMUM_PEERS + 2) + .syncMinimumPeerCount(SynchronizerConfiguration.DEFAULT_SYNC_MINIMUM_PEERS + 2) .worldStateHashCountPerRequest( SynchronizerConfiguration.DEFAULT_WORLD_STATE_HASH_COUNT_PER_REQUEST + 2) .worldStateRequestParallelism( @@ -60,7 +60,7 @@ protected SynchronizerConfiguration.Builder createCustomizedDomainObject() { SynchronizerConfiguration.DEFAULT_DOWNLOADER_CHANGE_TARGET_THRESHOLD_BY_TD.add(2L)) .downloaderHeadersRequestSize( SynchronizerConfiguration.DEFAULT_DOWNLOADER_HEADER_REQUEST_SIZE + 2) - .downloaderCheckpointTimeoutsPermitted( + .downloaderCheckpointRetries( SynchronizerConfiguration.DEFAULT_DOWNLOADER_CHECKPOINT_TIMEOUTS_PERMITTED + 2) .downloaderChainSegmentSize( SynchronizerConfiguration.DEFAULT_DOWNLOADER_CHAIN_SEGMENT_SIZE + 2) @@ -78,12 +78,13 @@ protected SynchronizerConfiguration.Builder createCustomizedDomainObject() { .storageCountPerRequest(SnapSyncConfiguration.DEFAULT_STORAGE_COUNT_PER_REQUEST + 2) .bytecodeCountPerRequest( SnapSyncConfiguration.DEFAULT_BYTECODE_COUNT_PER_REQUEST + 2) + .isSnapServerEnabled(Boolean.TRUE) .build()); } @Override - protected List getFieldsWithComputedDefaults() { - return Arrays.asList("maxTrailingPeers", "computationParallelism"); + protected String[] getFieldsWithComputedDefaults() { + return new String[] {"maxTrailingPeers", "computationParallelism"}; } @Override @@ -93,7 +94,7 @@ protected SynchronizerOptions getOptionsFromBesuCommand(final TestBesuCommand be @Override protected List getFieldsToIgnore() { - return Arrays.asList("fastSyncMinimumPeerCount"); + return Arrays.asList("syncMinimumPeerCount"); } @Override diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/TransactionPoolOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/TransactionPoolOptionsTest.java index 5b5fff5014e..eb48a3f2a5c 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/options/TransactionPoolOptionsTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/TransactionPoolOptionsTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,21 +17,25 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration.Implementation.LAYERED; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration.Implementation.LEGACY; +import static org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration.Implementation.SEQUENCED; import org.hyperledger.besu.cli.converter.DurationMillisConverter; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.util.number.Percentage; +import java.io.IOException; +import java.nio.file.Path; import java.time.Duration; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class TransactionPoolOptionsTest extends AbstractCLIOptionsTest { @@ -63,7 +67,7 @@ public void strictTxReplayProtection_default() { } @Test - public void pendingTransactionRetentionPeriod() { + public void pendingTransactionRetentionPeriodLegacy() { final int pendingTxRetentionHours = 999; internalTestSuccess( config -> @@ -73,6 +77,17 @@ public void pendingTransactionRetentionPeriod() { "--tx-pool=legacy"); } + @Test + public void pendingTransactionRetentionPeriodSequenced() { + final int pendingTxRetentionHours = 999; + internalTestSuccess( + config -> + assertThat(config.getPendingTxRetentionPeriod()).isEqualTo(pendingTxRetentionHours), + "--tx-pool-retention-hours", + String.valueOf(pendingTxRetentionHours), + "--tx-pool=sequenced"); + } + @Test public void disableLocalsDefault() { internalTestSuccess(config -> assertThat(config.getNoLocalPriority()).isFalse()); @@ -156,6 +171,31 @@ public void invalidPriceBumpShouldFail() { "101"); } + @Test + public void blobPriceBump() { + final Percentage blobPriceBump = Percentage.fromInt(50); + internalTestSuccess( + config -> assertThat(config.getBlobPriceBump()).isEqualTo(blobPriceBump), + "--tx-pool-blob-price-bump", + blobPriceBump.toString()); + } + + @Test + public void invalidBlobPriceBumpShouldFail() { + internalTestFailure( + "Invalid value: 101, should be a number between 0 and 100 inclusive", + "--tx-pool-blob-price-bump", + "101"); + } + + @Test + public void defaultBlobPriceBump() { + internalTestSuccess( + config -> + assertThat(config.getBlobPriceBump()) + .isEqualTo(TransactionPoolConfiguration.DEFAULT_BLOB_PRICE_BUMP)); + } + @Test public void txFeeCap() { final Wei txFeeCap = Wei.fromEth(2); @@ -193,17 +233,24 @@ public void selectLegacyImplementationByArg() { "--tx-pool=legacy"); } + @Test + public void selectSequencedImplementationByArg() { + internalTestSuccess( + config -> assertThat(config.getTxPoolImplementation()).isEqualTo(SEQUENCED), + "--tx-pool=sequenced"); + } + @Test public void failIfLegacyOptionsWhenLayeredSelectedByDefault() { internalTestFailure( - "Could not use legacy transaction pool options with layered implementation", + "Could not use legacy or sequenced transaction pool options with layered implementation", "--tx-pool-max-size=1000"); } @Test public void failIfLegacyOptionsWhenLayeredSelectedByArg() { internalTestFailure( - "Could not use legacy transaction pool options with layered implementation", + "Could not use legacy or sequenced transaction pool options with layered implementation", "--tx-pool=layered", "--tx-pool-max-size=1000"); } @@ -211,11 +258,19 @@ public void failIfLegacyOptionsWhenLayeredSelectedByArg() { @Test public void failIfLayeredOptionsWhenLegacySelectedByArg() { internalTestFailure( - "Could not use layered transaction pool options with legacy implementation", + "Could not use layered transaction pool options with legacy or sequenced implementation", "--tx-pool=legacy", "--tx-pool-max-prioritized=1000"); } + @Test + public void failIfLayeredOptionsWhenSequencedSelectedByArg() { + internalTestFailure( + "Could not use layered transaction pool options with legacy or sequenced implementation", + "--tx-pool=sequenced", + "--tx-pool-max-prioritized=1000"); + } + @Test public void byDefaultNoPrioritySenders() { internalTestSuccess(config -> assertThat(config.getPrioritySenders()).isEmpty()); @@ -317,6 +372,70 @@ public void eth65TrxAnnouncedBufferingPeriodWithInvalidInputShouldFail2() { "-1"); } + @Test + public void maxPrioritizedTxsPerType() { + final int maxBlobs = 2; + final int maxFrontier = 200; + internalTestSuccess( + config -> { + assertThat(config.getMaxPrioritizedTransactionsByType().get(TransactionType.BLOB)) + .isEqualTo(maxBlobs); + assertThat(config.getMaxPrioritizedTransactionsByType().get(TransactionType.FRONTIER)) + .isEqualTo(maxFrontier); + }, + "--tx-pool-max-prioritized-by-type", + "BLOB=" + maxBlobs + ",FRONTIER=" + maxFrontier); + } + + @Test + public void maxPrioritizedTxsPerTypeConfigFile() throws IOException { + final int maxBlobs = 2; + final int maxFrontier = 200; + final Path tempConfigFilePath = + createTempFile( + "config", + String.format( + """ + tx-pool-max-prioritized-by-type=["BLOB=%s","FRONTIER=%s"] + """, + maxBlobs, maxFrontier)); + internalTestSuccess( + config -> { + assertThat(config.getMaxPrioritizedTransactionsByType().get(TransactionType.BLOB)) + .isEqualTo(maxBlobs); + assertThat(config.getMaxPrioritizedTransactionsByType().get(TransactionType.FRONTIER)) + .isEqualTo(maxFrontier); + }, + "--config-file", + tempConfigFilePath.toString()); + } + + @Test + public void maxPrioritizedTxsPerTypeWrongTxType() { + internalTestFailure( + "Invalid value for option '--tx-pool-max-prioritized-by-type' (MAP): expected one of [FRONTIER, ACCESS_LIST, EIP1559, BLOB, DELEGATE_CODE] (case-insensitive) but was 'WRONG_TYPE'", + "--tx-pool-max-prioritized-by-type", + "WRONG_TYPE=1"); + } + + @Test + public void minScoreWorks() { + final byte minScore = -10; + internalTestSuccess( + config -> assertThat(config.getMinScore()).isEqualTo(minScore), + "--tx-pool-min-score", + Byte.toString(minScore)); + } + + @Test + public void minScoreNonByteValueReturnError() { + final var overflowMinScore = Integer.toString(-300); + internalTestFailure( + "Invalid value for option '--tx-pool-min-score': '" + overflowMinScore + "' is not a byte", + "--tx-pool-min-score", + overflowMinScore); + } + @Override protected TransactionPoolConfiguration createDefaultDomainObject() { return TransactionPoolConfiguration.DEFAULT; @@ -343,4 +462,9 @@ protected TransactionPoolOptions optionsFromDomainObject( protected TransactionPoolOptions getOptionsFromBesuCommand(final TestBesuCommand besuCommand) { return besuCommand.getTransactionPoolOptions(); } + + @Override + protected String[] getNonOptionFields() { + return new String[] {"transactionPoolValidatorService"}; + } } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/stable/DataStorageOptionsTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/stable/DataStorageOptionsTest.java index 5b4e0228550..29fa3d66071 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/options/stable/DataStorageOptionsTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/stable/DataStorageOptionsTest.java @@ -12,18 +12,17 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.cli.options.stable; import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration.Unstable.MINIMUM_BONSAI_TRIE_LOG_RETENTION_THRESHOLD; +import static org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration.MINIMUM_BONSAI_TRIE_LOG_RETENTION_LIMIT; import org.hyperledger.besu.cli.options.AbstractCLIOptionsTest; import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; import org.hyperledger.besu.ethereum.worldstate.ImmutableDataStorageConfiguration; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class DataStorageOptionsTest extends AbstractCLIOptionsTest { @@ -32,53 +31,125 @@ public class DataStorageOptionsTest public void bonsaiTrieLogPruningLimitOption() { internalTestSuccess( dataStorageConfiguration -> - assertThat(dataStorageConfiguration.getUnstable().getBonsaiTrieLogPruningLimit()) - .isEqualTo(1), - "--Xbonsai-trie-log-pruning-enabled", - "--Xbonsai-trie-log-pruning-limit", - "1"); + assertThat(dataStorageConfiguration.getBonsaiTrieLogPruningWindowSize()).isEqualTo(600), + "--bonsai-limit-trie-logs-enabled", + "--bonsai-trie-logs-pruning-window-size", + "600"); + } + + @Test + public void bonsaiTrieLogPruningLimitLegacyOption() { + internalTestSuccess( + dataStorageConfiguration -> + assertThat(dataStorageConfiguration.getBonsaiTrieLogPruningWindowSize()).isEqualTo(600), + "--Xbonsai-limit-trie-logs-enabled", + "--Xbonsai-trie-logs-pruning-window-size", + "600"); + } + + @Test + public void bonsaiTrieLogsEnabled_explicitlySetToFalse() { + internalTestSuccess( + dataStorageConfiguration -> + assertThat(dataStorageConfiguration.getBonsaiLimitTrieLogsEnabled()).isEqualTo(false), + "--bonsai-limit-trie-logs-enabled=false"); } @Test - public void bonsaiTrieLogPruningLimitShouldBePositive() { + public void bonsaiTrieLogPruningWindowSizeShouldBePositive() { internalTestFailure( - "--Xbonsai-trie-log-pruning-limit=0 must be greater than 0", - "--Xbonsai-trie-log-pruning-enabled", - "--Xbonsai-trie-log-pruning-limit", + "--bonsai-trie-logs-pruning-window-size=0 must be greater than 0", + "--bonsai-limit-trie-logs-enabled", + "--bonsai-trie-logs-pruning-window-size", "0"); } @Test - public void bonsaiTrieLogRetentionThresholdOption() { + public void bonsaiTrieLogPruningWindowSizeShouldBeAboveRetentionLimit() { + internalTestFailure( + "--bonsai-trie-logs-pruning-window-size=512 must be greater than --bonsai-historical-block-limit=512", + "--bonsai-limit-trie-logs-enabled", + "--bonsai-trie-logs-pruning-window-size", + "512"); + } + + @Test + public void bonsaiTrieLogRetentionLimitOption() { internalTestSuccess( dataStorageConfiguration -> - assertThat(dataStorageConfiguration.getUnstable().getBonsaiTrieLogRetentionThreshold()) - .isEqualTo(MINIMUM_BONSAI_TRIE_LOG_RETENTION_THRESHOLD + 1), - "--Xbonsai-trie-log-pruning-enabled", - "--Xbonsai-trie-log-retention-threshold", + assertThat(dataStorageConfiguration.getBonsaiMaxLayersToLoad()) + .isEqualTo(MINIMUM_BONSAI_TRIE_LOG_RETENTION_LIMIT + 1), + "--bonsai-limit-trie-logs-enabled", + "--bonsai-historical-block-limit", "513"); } @Test - public void bonsaiTrieLogRetentionThresholdOption_boundaryTest() { + public void bonsaiTrieLogRetentionLimitOption_boundaryTest() { internalTestSuccess( dataStorageConfiguration -> - assertThat(dataStorageConfiguration.getUnstable().getBonsaiTrieLogRetentionThreshold()) - .isEqualTo(MINIMUM_BONSAI_TRIE_LOG_RETENTION_THRESHOLD), - "--Xbonsai-trie-log-pruning-enabled", - "--Xbonsai-trie-log-retention-threshold", + assertThat(dataStorageConfiguration.getBonsaiMaxLayersToLoad()) + .isEqualTo(MINIMUM_BONSAI_TRIE_LOG_RETENTION_LIMIT), + "--bonsai-limit-trie-logs-enabled", + "--bonsai-historical-block-limit", "512"); } @Test - public void bonsaiTrieLogRetentionThresholdShouldBeAboveMinimum() { + public void bonsaiTrieLogRetentionLimitShouldBeAboveMinimum() { internalTestFailure( - "--Xbonsai-trie-log-retention-threshold minimum value is 512", - "--Xbonsai-trie-log-pruning-enabled", - "--Xbonsai-trie-log-retention-threshold", + "--bonsai-historical-block-limit minimum value is 512", + "--bonsai-limit-trie-logs-enabled", + "--bonsai-historical-block-limit", "511"); } + @Test + public void bonsaiCodeUsingCodeHashEnabledCanBeEnabled() { + internalTestSuccess( + dataStorageConfiguration -> + assertThat( + dataStorageConfiguration.getUnstable().getBonsaiCodeStoredByCodeHashEnabled()) + .isEqualTo(true), + "--Xbonsai-code-using-code-hash-enabled", + "true"); + } + + @Test + public void bonsaiCodeUsingCodeHashEnabledCanBeDisabled() { + internalTestSuccess( + dataStorageConfiguration -> + assertThat( + dataStorageConfiguration.getUnstable().getBonsaiCodeStoredByCodeHashEnabled()) + .isEqualTo(false), + "--Xbonsai-code-using-code-hash-enabled", + "false"); + } + + @Test + public void receiptCompactionCanBeEnabledWithImplicitTrueValue() { + internalTestSuccess( + dataStorageConfiguration -> + assertThat(dataStorageConfiguration.getReceiptCompactionEnabled()).isEqualTo(true), + "--receipt-compaction-enabled"); + } + + @Test + public void receiptCompactionCanBeEnabled() { + internalTestSuccess( + dataStorageConfiguration -> + assertThat(dataStorageConfiguration.getReceiptCompactionEnabled()).isEqualTo(true), + "--receipt-compaction-enabled=true"); + } + + @Test + public void receiptCompactionCanBeDisabled() { + internalTestSuccess( + dataStorageConfiguration -> + assertThat(dataStorageConfiguration.getReceiptCompactionEnabled()).isEqualTo(false), + "--receipt-compaction-enabled=false"); + } + @Override protected DataStorageConfiguration createDefaultDomainObject() { return DataStorageConfiguration.DEFAULT_CONFIG; @@ -88,13 +159,9 @@ protected DataStorageConfiguration createDefaultDomainObject() { protected DataStorageConfiguration createCustomizedDomainObject() { return ImmutableDataStorageConfiguration.builder() .dataStorageFormat(DataStorageFormat.BONSAI) - .bonsaiMaxLayersToLoad(100L) - .unstable( - ImmutableDataStorageConfiguration.Unstable.builder() - .bonsaiTrieLogPruningEnabled(true) - .bonsaiTrieLogRetentionThreshold(1000L) - .bonsaiTrieLogPruningLimit(20) - .build()) + .bonsaiMaxLayersToLoad(513L) + .bonsaiLimitTrieLogsEnabled(true) + .bonsaiTrieLogPruningWindowSize(514) .build(); } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/options/stable/LoggingLevelOptionTest.java b/besu/src/test/java/org/hyperledger/besu/cli/options/stable/LoggingLevelOptionTest.java index 4d0a67e75ca..b1fb3217c28 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/options/stable/LoggingLevelOptionTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/options/stable/LoggingLevelOptionTest.java @@ -21,8 +21,8 @@ import java.util.Arrays; import org.apache.logging.log4j.Level; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.mockito.Mockito; import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.ParameterException; @@ -31,9 +31,9 @@ public class LoggingLevelOptionTest { private LoggingLevelOption levelOption; - @Before + @BeforeEach public void setUp() { - levelOption = new LoggingLevelOption(); + levelOption = LoggingLevelOption.create(); } @Test diff --git a/besu/src/test/java/org/hyperledger/besu/cli/rlp/RLPSubCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/rlp/RLPSubCommandTest.java deleted file mode 100644 index 822c7322718..00000000000 --- a/besu/src/test/java/org/hyperledger/besu/cli/rlp/RLPSubCommandTest.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.cli.rlp; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.contentOf; - -import org.hyperledger.besu.BesuInfo; -import org.hyperledger.besu.cli.CommandTestAbstract; - -import java.io.BufferedWriter; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; - -import org.junit.After; -import org.junit.Test; -import picocli.CommandLine.Model.CommandSpec; - -public class RLPSubCommandTest extends CommandTestAbstract { - - private static final String EXPECTED_RLP_USAGE = - "Usage: besu rlp [-hV] [COMMAND]" - + System.lineSeparator() - + "This command provides RLP data related actions." - + System.lineSeparator() - + " -h, --help Show this help message and exit." - + System.lineSeparator() - + " -V, --version Print version information and exit." - + System.lineSeparator() - + "Commands:" - + System.lineSeparator() - + " encode This command encodes a JSON typed data into an RLP hex string."; - - private static final String EXPECTED_RLP_ENCODE_USAGE = - "Usage: besu rlp encode [-hV] [--from=] [--to=] [--type=]" - + System.lineSeparator() - + "This command encodes a JSON typed data into an RLP hex string." - + System.lineSeparator() - + " --from= File containing JSON object to encode" - + System.lineSeparator() - + " -h, --help Show this help message and exit." - + System.lineSeparator() - + " --to= File to write encoded RLP string to." - + System.lineSeparator() - + " --type= Type of the RLP data to encode, possible values are" - + System.lineSeparator() - + " IBFT_EXTRA_DATA, QBFT_EXTRA_DATA. (default:" - + System.lineSeparator() - + " IBFT_EXTRA_DATA)" - + System.lineSeparator() - + " -V, --version Print version information and exit."; - - private static final String RLP_SUBCOMMAND_NAME = "rlp"; - private static final String RLP_ENCODE_SUBCOMMAND_NAME = "encode"; - private static final String RLP_QBFT_TYPE = "QBFT_EXTRA_DATA"; - - // RLP sub-command - @Test - public void rlpSubCommandExistsAndHasSubCommands() { - final CommandSpec spec = parseCommand().getSpec(); - assertThat(spec.subcommands()).containsKeys(RLP_SUBCOMMAND_NAME); - assertThat(spec.subcommands().get(RLP_SUBCOMMAND_NAME).getSubcommands()) - .containsKeys(RLP_ENCODE_SUBCOMMAND_NAME); - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void callingRLPSubCommandWithoutSubSubcommandMustDisplayUsage() { - parseCommand(RLP_SUBCOMMAND_NAME); - assertThat(commandOutput.toString(UTF_8)).startsWith(EXPECTED_RLP_USAGE); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void callingRPLSubCommandHelpMustDisplayUsage() { - parseCommand(RLP_SUBCOMMAND_NAME, "--help"); - assertThat(commandOutput.toString(UTF_8)).startsWith(EXPECTED_RLP_USAGE); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - // Encode RLP sub-command - @Test - public void callingRPLEncodeSubCommandHelpMustDisplayUsage() { - parseCommand(RLP_SUBCOMMAND_NAME, RLP_ENCODE_SUBCOMMAND_NAME, "--help"); - assertThat(commandOutput.toString(UTF_8)).startsWith(EXPECTED_RLP_ENCODE_USAGE); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void callingRPLSubCommandVersionMustDisplayVersion() { - parseCommand(RLP_SUBCOMMAND_NAME, "--version"); - assertThat(commandOutput.toString(UTF_8)).isEqualToIgnoringWhitespace(BesuInfo.version()); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void callingRPLEncodeSubCommandVersionMustDisplayVersion() { - parseCommand(RLP_SUBCOMMAND_NAME, RLP_ENCODE_SUBCOMMAND_NAME, "--version"); - assertThat(commandOutput.toString(UTF_8)).isEqualToIgnoringWhitespace(BesuInfo.version()); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void encodeWithoutPathMustWriteToStandardOutput() { - - final String jsonInput = - "[\"be068f726a13c8d46c44be6ce9d275600e1735a4\", \"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\"]"; - - // set stdin - final ByteArrayInputStream stdIn = new ByteArrayInputStream(jsonInput.getBytes(UTF_8)); - - parseCommand(stdIn, RLP_SUBCOMMAND_NAME, RLP_ENCODE_SUBCOMMAND_NAME); - - final String expectedRlpString = - "0xf853a00000000000000000000000000000000000000000000000000000000000000000ea94be068f726a13c8d" - + "46c44be6ce9d275600e1735a4945ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193808400000000c0"; - assertThat(commandOutput.toString(UTF_8)).contains(expectedRlpString); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void encodeWithOutputFileMustWriteInThisFile() throws Exception { - - final File file = File.createTempFile("ibftExtraData", "rlp"); - - final String jsonInput = - "[\"be068f726a13c8d46c44be6ce9d275600e1735a4\", \"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\"]"; - - // set stdin - final ByteArrayInputStream stdIn = new ByteArrayInputStream(jsonInput.getBytes(UTF_8)); - - parseCommand(stdIn, RLP_SUBCOMMAND_NAME, RLP_ENCODE_SUBCOMMAND_NAME, "--to", file.getPath()); - - final String expectedRlpString = - "0xf853a00000000000000000000000000000000000000000000000000000000000000000ea94be068f726a13c8d" - + "46c44be6ce9d275600e1735a4945ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193808400000000c0"; - - assertThat(contentOf(file)).contains(expectedRlpString); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - - @Test - public void encodeWithInputFilePathMustReadFromThisFile() throws Exception { - - final File tempJsonFile = temp.newFile("test.json"); - try (final BufferedWriter fileWriter = Files.newBufferedWriter(tempJsonFile.toPath(), UTF_8)) { - - fileWriter.write( - "[\"be068f726a13c8d46c44be6ce9d275600e1735a4\", \"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\"]"); - - fileWriter.flush(); - - parseCommand( - RLP_SUBCOMMAND_NAME, RLP_ENCODE_SUBCOMMAND_NAME, "--from", tempJsonFile.getPath()); - - final String expectedRlpString = - "0xf853a00000000000000000000000000000000000000000000000000000000000000000ea94be068f726a13c8d" - + "46c44be6ce9d275600e1735a4945ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193808400000000c0"; - assertThat(commandOutput.toString(UTF_8)).contains(expectedRlpString); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - } - - @Test - public void canEncodeToQbftExtraData() throws IOException { - final File tempJsonFile = temp.newFile("test.json"); - try (final BufferedWriter fileWriter = Files.newBufferedWriter(tempJsonFile.toPath(), UTF_8)) { - - fileWriter.write( - "[\"be068f726a13c8d46c44be6ce9d275600e1735a4\", \"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\"]"); - - fileWriter.flush(); - - parseCommand( - RLP_SUBCOMMAND_NAME, - RLP_ENCODE_SUBCOMMAND_NAME, - "--from", - tempJsonFile.getPath(), - "--type", - RLP_QBFT_TYPE); - - final String expectedRlpString = - "0xf84fa00000000000000000000000000000000000000000000000000000000000000000ea94be068f726a13c8d" - + "46c44be6ce9d275600e1735a4945ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193c080c0"; - assertThat(commandOutput.toString(UTF_8)).contains(expectedRlpString); - assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); - } - } - - @Test - public void encodeWithInvalidInputMustRaiseAnError() throws Exception { - - final File tempJsonFile = temp.newFile("invalid_test.json"); - try (final BufferedWriter fileWriter = Files.newBufferedWriter(tempJsonFile.toPath(), UTF_8)) { - - fileWriter.write("{\"property\":0}"); - - fileWriter.flush(); - - parseCommand( - RLP_SUBCOMMAND_NAME, RLP_ENCODE_SUBCOMMAND_NAME, "--from", tempJsonFile.getPath()); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .startsWith( - "Unable to map the JSON data with selected type. Please check JSON input format."); - } - } - - @Test - public void encodeWithEmptyInputMustRaiseAnError() throws Exception { - - final File tempJsonFile = temp.newFile("empty.json"); - - parseCommand(RLP_SUBCOMMAND_NAME, RLP_ENCODE_SUBCOMMAND_NAME, "--from", tempJsonFile.getPath()); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .startsWith("An error occurred while trying to read the JSON data."); - } - - @Test - public void encodeWithEmptyStdInputMustRaiseAnError() throws Exception { - - // set empty stdin - final String jsonInput = ""; - final ByteArrayInputStream stdIn = new ByteArrayInputStream(jsonInput.getBytes(UTF_8)); - - parseCommand(stdIn, RLP_SUBCOMMAND_NAME, RLP_ENCODE_SUBCOMMAND_NAME); - - assertThat(commandOutput.toString(UTF_8)).isEmpty(); - assertThat(commandErrorOutput.toString(UTF_8)) - .startsWith("An error occurred while trying to read the JSON data."); - } - - @After - public void restoreStdin() { - System.setIn(System.in); - } -} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/subcommands/TxParseSubCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/TxParseSubCommandTest.java new file mode 100644 index 00000000000..ced46b41231 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/TxParseSubCommandTest.java @@ -0,0 +1,69 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.subcommands; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; + +public class TxParseSubCommandTest { + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintWriter writer = new PrintWriter(new OutputStreamWriter(baos, UTF_8), true); + TxParseSubCommand txParseSubCommand = new TxParseSubCommand(writer); + + @Test + public void smokeTest() { + txParseSubCommand.dump( + Bytes.fromHexString( + "0x02f875018363bc5a8477359400850c570bd200825208943893d427c770de9b92065da03a8c825f13498da28702fbf8ccf8530480c080a0dd49141ecf93eeeaed5f3499a6103d6a9778e24a37b1f5c6a336c60c8a078c15a0511b51a3050771f66c1b779210a46a51be521a2446a3f87fc656dcfd1782aa5e")); + String output = baos.toString(UTF_8); + assertThat(output).isEqualTo("0xeb2629a2734e272bcc07bda959863f316f4bd4cf\n"); + } + + @Test + public void assertInvalidRlp() { + txParseSubCommand.dump( + Bytes.fromHexString( + "0x03f90124f9011e010516058261a89403040500000000000000000000000000000000006383000000f8b6f859940000000000000000000000000000000074657374f842a00100000000000000000000000000000000000000000000000000000000000000a00200000000000000000000000000000000000000000000000000000000000000f859940000000000000000000000000000000074657374f842a00100000000000000000000000000000000000000000000000000000000000000a002000000000000000000000000000000000000000000000000000000000000000fc080a082f96cae43a3f2554cad899a6e9168a3cd613454f82e93488f9b4c1a998cc814a06b7519cd0e3159d6ce9bf811ff3ca4e9c5d7ac63fc52142370a0725e4c5273b2c0c0c0")); + String output = baos.toString(UTF_8); + assertThat(output).startsWith("err: "); + assertThat(output).contains("arbitrary precision scalar"); + } + + @Test + public void assertValidRlp() { + txParseSubCommand.dump( + Bytes.fromHexString( + "0x03f9013f010516058261a89403040500000000000000000000000000000000006383000000f8b6f859940000000000000000000000000000000074657374f842a00100000000000000000000000000000000000000000000000000000000000000a00200000000000000000000000000000000000000000000000000000000000000f859940000000000000000000000000000000074657374f842a00100000000000000000000000000000000000000000000000000000000000000a002000000000000000000000000000000000000000000000000000000000000000fe1a0010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c44401401a0d98465c5489a09933e6428657f1fc4461d49febd8556c1c7e7053ed0be49cc4fa02c0d67e40aed75f87ea640cc47064d79f4383e24dec283ac6eb81e19da46cc26")); + String output = baos.toString(UTF_8); + assertThat(output).isEqualTo("0x730aa72228b55236de378f5267dfc77723b1380c\n"); + } + + @Test + public void assertInvalidChainId() { + txParseSubCommand.dump( + Bytes.fromHexString( + "0xf901fc303083303030803030b901ae30303030303030303030433030303030303030303030303030303030303030303030303030303030203030413030303030303030303000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038390030000000002800380000413159000021000000002b0000000000003800000000003800000000000000000000002b633279315a00633200303041374222610063416200325832325a002543323861795930314130383058435931317a633043304239623900310031254363384361000023433158253041380037000027285839005a007937000000623700002761002b5a003937622e000000006300007863410000002a2e320000000000005a0000000000000037242778002039006120412e6362002138300000002a313030303030303030303030373030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030a03030303030303030303030303030303030303030303030303030303030303030a03030303030303030303030303030303030303030303030303030303030303030")); + String output = baos.toString(UTF_8); + assertThat(output).isEqualTo("err: wrong chain id\n"); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/subcommands/blocks/BlocksSubCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/blocks/BlocksSubCommandTest.java index 5c62076e692..f43d039b4a3 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/subcommands/blocks/BlocksSubCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/blocks/BlocksSubCommandTest.java @@ -36,17 +36,16 @@ import java.nio.file.Paths; import java.util.Optional; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.junit.jupiter.MockitoExtension; import picocli.CommandLine.Model.CommandSpec; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class BlocksSubCommandTest extends CommandTestAbstract { - @Rule public final TemporaryFolder folder = new TemporaryFolder(); + @TempDir public Path folder; private static final String EXPECTED_BLOCK_USAGE = "Usage: besu blocks [-hV] [COMMAND]" @@ -187,10 +186,13 @@ public void callingBlockImportSubCommandVersionMustDisplayVersion() { } @Test - public void callingBlockImportSubCommandWithPathMustImportBlocksWithThisPath() throws Exception { - final File fileToImport = temp.newFile("blocks.file"); + public void callingBlockImportSubCommandWithPathMustImportBlocksWithThisPath( + final @TempDir File fileToImport) throws Exception { parseCommand( - BLOCK_SUBCOMMAND_NAME, BLOCK_IMPORT_SUBCOMMAND_NAME, "--from", fileToImport.getPath()); + BLOCK_SUBCOMMAND_NAME, + BLOCK_IMPORT_SUBCOMMAND_NAME, + "--from", + fileToImport.getAbsolutePath().toString()); verify(rlpBlockImporter) .importBlockchain(pathArgumentCaptor.capture(), any(), anyBoolean(), anyLong(), anyLong()); @@ -202,8 +204,7 @@ public void callingBlockImportSubCommandWithPathMustImportBlocksWithThisPath() t } @Test - public void blocksImport_rlpFormat() throws Exception { - final File fileToImport = temp.newFile("blocks.file"); + public void blocksImport_rlpFormat(final @TempDir File fileToImport) throws Exception { parseCommand( BLOCK_SUBCOMMAND_NAME, BLOCK_IMPORT_SUBCOMMAND_NAME, @@ -222,10 +223,11 @@ public void blocksImport_rlpFormat() throws Exception { } @Test - public void blocksImport_rlpFormatMultiple() throws Exception { - final File fileToImport = temp.newFile("blocks.file"); - final File file2ToImport = temp.newFile("blocks2.file"); - final File file3ToImport = temp.newFile("blocks3.file"); + public void blocksImport_rlpFormatMultiple( + final @TempDir File fileToImport, + final @TempDir File file2ToImport, + final @TempDir File file3ToImport) + throws Exception { parseCommand( BLOCK_SUBCOMMAND_NAME, BLOCK_IMPORT_SUBCOMMAND_NAME, @@ -247,10 +249,10 @@ public void blocksImport_rlpFormatMultiple() throws Exception { } @Test - public void blocksImport_jsonFormat() throws Exception { + public void blocksImport_jsonFormat(final @TempDir Path dir) throws Exception { final String fileContent = "test"; - final File fileToImport = temp.newFile("blocks.file"); - final Writer fileWriter = Files.newBufferedWriter(fileToImport.toPath(), UTF_8); + final Path fileToImport = Files.createTempFile(dir, "tmp", "json"); + final Writer fileWriter = Files.newBufferedWriter(fileToImport, UTF_8); fileWriter.write(fileContent); fileWriter.close(); @@ -260,7 +262,7 @@ public void blocksImport_jsonFormat() throws Exception { "--format", "JSON", "--from", - fileToImport.getPath()); + fileToImport.toFile().getAbsolutePath()); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); @@ -274,9 +276,7 @@ public void blocksImport_jsonFormat() throws Exception { public void blocksExport_missingFileParam() throws IOException { createDbDirectory(true); parseCommand( - "--data-path=" + folder.getRoot().getAbsolutePath(), - BLOCK_SUBCOMMAND_NAME, - BLOCK_EXPORT_SUBCOMMAND_NAME); + "--data-path=" + folder.getRoot(), BLOCK_SUBCOMMAND_NAME, BLOCK_EXPORT_SUBCOMMAND_NAME); final String expectedErrorOutputStart = "Missing required option: '--to='"; assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).startsWith(expectedErrorOutputStart); @@ -285,17 +285,17 @@ public void blocksExport_missingFileParam() throws IOException { } @Test - public void blocksExport_noDbDirectory() throws IOException { - final File outputFile = folder.newFile("blocks.bin"); + public void blocksExport_noDbDirectory(final @TempDir Path noDbDir) throws IOException { + final File outputFile = noDbDir.resolve("blocks.bin").toFile(); parseCommand( - "--data-path=" + folder.getRoot().getAbsolutePath(), + "--data-path=" + noDbDir, BLOCK_SUBCOMMAND_NAME, BLOCK_EXPORT_SUBCOMMAND_NAME, "--to", - outputFile.getPath()); + outputFile.getAbsolutePath()); final String expectedErrorOutputStart = "Chain is empty. Unable to export blocks from specified data directory: " - + folder.getRoot().getAbsolutePath() + + noDbDir + File.separator + BesuController.DATABASE_PATH; assertThat(commandOutput.toString(UTF_8)).isEmpty(); @@ -307,16 +307,16 @@ public void blocksExport_noDbDirectory() throws IOException { @Test public void blocksExport_emptyDbDirectory() throws IOException { createDbDirectory(false); - final File outputFile = folder.newFile("blocks.bin"); + final File outputFile = Files.createFile(folder.resolve("empty")).toFile(); parseCommand( - "--data-path=" + folder.getRoot().getAbsolutePath(), + "--data-path=" + folder, BLOCK_SUBCOMMAND_NAME, BLOCK_EXPORT_SUBCOMMAND_NAME, "--to", outputFile.getPath()); final String expectedErrorOutputStart = "Chain is empty. Unable to export blocks from specified data directory: " - + folder.getRoot().getAbsolutePath() + + folder + File.separator + BesuController.DATABASE_PATH; assertThat(commandOutput.toString(UTF_8)).isEmpty(); @@ -328,13 +328,13 @@ public void blocksExport_emptyDbDirectory() throws IOException { @Test public void blocksExport_noStartOrEnd() throws IOException { createDbDirectory(true); - final File outputFile = folder.newFile("blocks.bin"); + final File outputFile = Files.createTempFile(folder, "blocks", "bin").toFile(); parseCommand( - "--data-path=" + folder.getRoot().getAbsolutePath(), + "--data-path=" + folder, BLOCK_SUBCOMMAND_NAME, BLOCK_EXPORT_SUBCOMMAND_NAME, "--to", - outputFile.getPath()); + outputFile.getAbsolutePath()); assertThat(commandOutput.toString(UTF_8)).isEmpty(); assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); @@ -344,9 +344,9 @@ public void blocksExport_noStartOrEnd() throws IOException { @Test public void blocksExport_withStartAndNoEnd() throws IOException { createDbDirectory(true); - final File outputFile = folder.newFile("blocks.bin"); + final File outputFile = Files.createFile(folder.resolve("blocks.bin")).toFile(); parseCommand( - "--data-path=" + folder.getRoot().getAbsolutePath(), + "--data-path=" + folder, BLOCK_SUBCOMMAND_NAME, BLOCK_EXPORT_SUBCOMMAND_NAME, "--to", @@ -361,9 +361,9 @@ public void blocksExport_withStartAndNoEnd() throws IOException { @Test public void blocksExport_withEndAndNoStart() throws IOException { createDbDirectory(true); - final File outputFile = folder.newFile("blocks.bin"); + final File outputFile = Files.createTempFile(folder, "blocks", "bin").toFile(); parseCommand( - "--data-path=" + folder.getRoot().getAbsolutePath(), + "--data-path=" + folder, BLOCK_SUBCOMMAND_NAME, BLOCK_EXPORT_SUBCOMMAND_NAME, "--to", @@ -378,13 +378,13 @@ public void blocksExport_withEndAndNoStart() throws IOException { @Test public void blocksExport_withStartAndEnd() throws IOException { createDbDirectory(true); - final File outputFile = folder.newFile("blocks.bin"); + final File outputFile = Files.createTempFile(folder, "blocks", "bin").toFile(); parseCommand( - "--data-path=" + folder.getRoot().getAbsolutePath(), + "--data-path=" + folder, BLOCK_SUBCOMMAND_NAME, BLOCK_EXPORT_SUBCOMMAND_NAME, "--to", - outputFile.getPath(), + outputFile.getAbsolutePath(), "--start-block=1", "--end-block=10"); assertThat(commandOutput.toString(UTF_8)).isEmpty(); @@ -396,13 +396,13 @@ public void blocksExport_withStartAndEnd() throws IOException { @Test public void blocksExport_withOutOfOrderStartAndEnd() throws IOException { createDbDirectory(true); - final File outputFile = folder.newFile("blocks.bin"); + final File outputFile = Files.createTempFile(folder, "blocks", "bin").toFile(); parseCommand( - "--data-path=" + folder.getRoot().getAbsolutePath(), + "--data-path=" + folder, BLOCK_SUBCOMMAND_NAME, BLOCK_EXPORT_SUBCOMMAND_NAME, "--to", - outputFile.getPath(), + outputFile.getAbsolutePath(), "--start-block=10", "--end-block=1"); assertThat(commandErrorOutput.toString(UTF_8)) @@ -415,9 +415,9 @@ public void blocksExport_withOutOfOrderStartAndEnd() throws IOException { @Test public void blocksExport_withEmptyRange() throws IOException { createDbDirectory(true); - final File outputFile = folder.newFile("blocks.bin"); + final File outputFile = Files.createTempFile(folder, "blocks", "bin").toFile(); parseCommand( - "--data-path=" + folder.getRoot().getAbsolutePath(), + "--data-path=" + folder, BLOCK_SUBCOMMAND_NAME, BLOCK_EXPORT_SUBCOMMAND_NAME, "--to", @@ -434,9 +434,9 @@ public void blocksExport_withEmptyRange() throws IOException { @Test public void blocksExport_withInvalidStart() throws IOException { createDbDirectory(true); - final File outputFile = folder.newFile("blocks.bin"); + final File outputFile = Files.createTempFile(folder, "blocks", "bin").toFile(); parseCommand( - "--data-path=" + folder.getRoot().getAbsolutePath(), + "--data-path=" + folder.getRoot(), BLOCK_SUBCOMMAND_NAME, BLOCK_EXPORT_SUBCOMMAND_NAME, "--to", @@ -452,9 +452,9 @@ public void blocksExport_withInvalidStart() throws IOException { @Test public void blocksExport_withInvalidEnd() throws IOException { createDbDirectory(true); - final File outputFile = folder.newFile("blocks.bin"); + final File outputFile = Files.createTempFile(folder, "blocks", "bin").toFile(); parseCommand( - "--data-path=" + folder.getRoot().getAbsolutePath(), + "--data-path=" + folder.getRoot(), BLOCK_SUBCOMMAND_NAME, BLOCK_EXPORT_SUBCOMMAND_NAME, "--to", @@ -482,20 +482,21 @@ public void callingBlockExportSubCommandVersionMustDisplayVersion() { } private void createDbDirectory(final boolean createDataFiles) throws IOException { - final File dbDir = folder.newFolder(BesuController.DATABASE_PATH); + final Path dbDir = Files.createDirectory(folder.resolve(BesuController.DATABASE_PATH)); + if (createDataFiles) { - final Path dataFilePath = Paths.get(dbDir.getAbsolutePath(), "0000001.sst"); - final boolean success = new File(dataFilePath.toString()).createNewFile(); + final Path dataFilePath = Paths.get(dbDir.toFile().getAbsolutePath(), "0000001.sst"); + final boolean success = new File(dataFilePath.toFile().getAbsolutePath()).createNewFile(); assertThat(success).isTrue(); } } @Test - public void blocksImportWithNoSyncModeDoesNotRaiseNPE() throws IOException { - final File fileToImport = temp.newFile("blocks.file"); + public void blocksImportWithNoSyncModeDoesNotRaiseNPE(final @TempDir File fileToImport) + throws IOException { parseCommand( BLOCK_SUBCOMMAND_NAME, BLOCK_IMPORT_SUBCOMMAND_NAME, "--from", fileToImport.getPath()); - verify(mockControllerBuilderFactory).fromEthNetworkConfig(any(), any(), isNotNull()); + verify(mockControllerBuilderFactory).fromEthNetworkConfig(any(), isNotNull()); } } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/operator/OperatorSubCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/operator/OperatorSubCommandTest.java similarity index 97% rename from besu/src/test/java/org/hyperledger/besu/cli/operator/OperatorSubCommandTest.java rename to besu/src/test/java/org/hyperledger/besu/cli/subcommands/operator/OperatorSubCommandTest.java index 414412ebb4a..7e9a36719d7 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/operator/OperatorSubCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/operator/OperatorSubCommandTest.java @@ -12,7 +12,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.cli.operator; +package org.hyperledger.besu.cli.subcommands.operator; import static java.lang.String.format; import static java.lang.System.currentTimeMillis; @@ -22,11 +22,10 @@ import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.contentOf; -import static org.hyperledger.besu.cli.operator.OperatorSubCommandTest.Cmd.cmd; +import static org.hyperledger.besu.cli.subcommands.operator.OperatorSubCommandTest.Cmd.cmd; import org.hyperledger.besu.BesuInfo; import org.hyperledger.besu.cli.CommandTestAbstract; -import org.hyperledger.besu.cli.subcommands.operator.OperatorSubCommand; import org.hyperledger.besu.crypto.SECP256K1; import org.hyperledger.besu.crypto.SECP256R1; import org.hyperledger.besu.crypto.SECPPrivateKey; @@ -52,13 +51,13 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.vertx.core.json.JsonObject; import org.apache.tuweni.bytes.Bytes32; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; import picocli.CommandLine.Model.CommandSpec; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class OperatorSubCommandTest extends CommandTestAbstract { private static final String EXPECTED_OPERATOR_USAGE = @@ -72,7 +71,7 @@ public class OperatorSubCommandTest extends CommandTestAbstract { + System.lineSeparator() + "Commands:" + System.lineSeparator() - + " generate-blockchain-config Generates node keypairs and genesis file with RLP" + + " generate-blockchain-config Generate node keypairs and genesis file with RLP" + System.lineSeparator() + " encoded extra data." + System.lineSeparator() @@ -80,7 +79,7 @@ public class OperatorSubCommandTest extends CommandTestAbstract { private Path tmpOutputDirectoryPath; - @Before + @BeforeEach public void init() throws IOException { SignatureAlgorithmFactory.resetInstance(); tmpOutputDirectoryPath = createTempDirectory(format("output-%d", currentTimeMillis())); diff --git a/besu/src/test/java/org/hyperledger/besu/cli/subcommands/rlp/RLPSubCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/rlp/RLPSubCommandTest.java new file mode 100644 index 00000000000..27dcdc16f63 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/rlp/RLPSubCommandTest.java @@ -0,0 +1,418 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.subcommands.rlp; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.contentOf; + +import org.hyperledger.besu.BesuInfo; +import org.hyperledger.besu.cli.CommandTestAbstract; + +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import picocli.CommandLine.Model.CommandSpec; + +public class RLPSubCommandTest extends CommandTestAbstract { + + private static final String EXPECTED_RLP_USAGE = + "Usage: besu rlp [-hV] [COMMAND]" + + System.lineSeparator() + + "This command provides RLP data related actions." + + System.lineSeparator() + + " -h, --help Show this help message and exit." + + System.lineSeparator() + + " -V, --version Print version information and exit." + + System.lineSeparator() + + "Commands:" + + System.lineSeparator() + + " encode This command encodes a JSON typed data into an RLP hex string."; + + private static final String EXPECTED_RLP_ENCODE_USAGE = + "Usage: besu rlp encode [-hV] [--from=] [--to=] [--type=]" + + System.lineSeparator() + + "This command encodes a JSON typed data into an RLP hex string." + + System.lineSeparator() + + " --from= File containing JSON object to encode" + + System.lineSeparator() + + " -h, --help Show this help message and exit." + + System.lineSeparator() + + " --to= File to write encoded RLP string to." + + System.lineSeparator() + + " --type= Type of the RLP data to encode, possible values are" + + System.lineSeparator() + + " IBFT_EXTRA_DATA, QBFT_EXTRA_DATA. (default:" + + System.lineSeparator() + + " IBFT_EXTRA_DATA)" + + System.lineSeparator() + + " -V, --version Print version information and exit."; + + private static final String RLP_SUBCOMMAND_NAME = "rlp"; + private static final String RLP_ENCODE_SUBCOMMAND_NAME = "encode"; + private static final String RLP_DECODE_SUBCOMMAND_NAME = "decode"; + private static final String RLP_QBFT_TYPE = "QBFT_EXTRA_DATA"; + + // RLP sub-command + @Test + public void rlpSubCommandExistsAndHasSubCommands() { + final CommandSpec spec = parseCommand().getSpec(); + assertThat(spec.subcommands()).containsKeys(RLP_SUBCOMMAND_NAME); + assertThat(spec.subcommands().get(RLP_SUBCOMMAND_NAME).getSubcommands()) + .containsKeys(RLP_ENCODE_SUBCOMMAND_NAME); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void callingRLPSubCommandWithoutSubSubcommandMustDisplayUsage() { + parseCommand(RLP_SUBCOMMAND_NAME); + assertThat(commandOutput.toString(UTF_8)).startsWith(EXPECTED_RLP_USAGE); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void callingRPLSubCommandHelpMustDisplayUsage() { + parseCommand(RLP_SUBCOMMAND_NAME, "--help"); + assertThat(commandOutput.toString(UTF_8)).startsWith(EXPECTED_RLP_USAGE); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + // Encode RLP sub-command + @Test + public void callingRPLEncodeSubCommandHelpMustDisplayUsage() { + parseCommand(RLP_SUBCOMMAND_NAME, RLP_ENCODE_SUBCOMMAND_NAME, "--help"); + assertThat(commandOutput.toString(UTF_8)).startsWith(EXPECTED_RLP_ENCODE_USAGE); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void callingRPLSubCommandVersionMustDisplayVersion() { + parseCommand(RLP_SUBCOMMAND_NAME, "--version"); + assertThat(commandOutput.toString(UTF_8)).isEqualToIgnoringWhitespace(BesuInfo.version()); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void callingRPLEncodeSubCommandVersionMustDisplayVersion() { + parseCommand(RLP_SUBCOMMAND_NAME, RLP_ENCODE_SUBCOMMAND_NAME, "--version"); + assertThat(commandOutput.toString(UTF_8)).isEqualToIgnoringWhitespace(BesuInfo.version()); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void encodeWithoutPathMustWriteToStandardOutput() { + + final String jsonInput = + "[\"be068f726a13c8d46c44be6ce9d275600e1735a4\", \"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\"]"; + + // set stdin + final ByteArrayInputStream stdIn = new ByteArrayInputStream(jsonInput.getBytes(UTF_8)); + + parseCommand(stdIn, RLP_SUBCOMMAND_NAME, RLP_ENCODE_SUBCOMMAND_NAME); + + final String expectedRlpString = + "0xf853a00000000000000000000000000000000000000000000000000000000000000000ea94be068f726a13c8d" + + "46c44be6ce9d275600e1735a4945ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193808400000000c0"; + assertThat(commandOutput.toString(UTF_8)).contains(expectedRlpString); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void encodeWithOutputFileMustWriteInThisFile() throws Exception { + + final File file = File.createTempFile("ibftExtraData", "rlp"); + + final String jsonInput = + "[\"be068f726a13c8d46c44be6ce9d275600e1735a4\", \"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\"]"; + + // set stdin + final ByteArrayInputStream stdIn = new ByteArrayInputStream(jsonInput.getBytes(UTF_8)); + + parseCommand(stdIn, RLP_SUBCOMMAND_NAME, RLP_ENCODE_SUBCOMMAND_NAME, "--to", file.getPath()); + + final String expectedRlpString = + "0xf853a00000000000000000000000000000000000000000000000000000000000000000ea94be068f726a13c8d" + + "46c44be6ce9d275600e1735a4945ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193808400000000c0"; + + assertThat(contentOf(file)).contains(expectedRlpString); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void encodeWithInputFilePathMustReadFromThisFile(final @TempDir Path dir) + throws Exception { + final Path tempJsonFile = Files.createTempFile(dir, "input", "json"); + try (final BufferedWriter fileWriter = Files.newBufferedWriter(tempJsonFile, UTF_8)) { + + fileWriter.write( + "[\"be068f726a13c8d46c44be6ce9d275600e1735a4\", \"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\"]"); + + fileWriter.flush(); + + parseCommand( + RLP_SUBCOMMAND_NAME, + RLP_ENCODE_SUBCOMMAND_NAME, + "--from", + tempJsonFile.toFile().getAbsolutePath()); + + final String expectedRlpString = + "0xf853a00000000000000000000000000000000000000000000000000000000000000000ea94be068f726a13c8d" + + "46c44be6ce9d275600e1735a4945ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193808400000000c0"; + assertThat(commandOutput.toString(UTF_8)).contains(expectedRlpString); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + } + + @Test + public void canEncodeToQbftExtraData(final @TempDir Path dir) throws Exception { + final Path tempJsonFile = Files.createTempFile(dir, "qbft", "json"); + try (final BufferedWriter fileWriter = Files.newBufferedWriter(tempJsonFile, UTF_8)) { + + fileWriter.write( + "[\"be068f726a13c8d46c44be6ce9d275600e1735a4\", \"5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193\"]"); + + fileWriter.flush(); + + parseCommand( + RLP_SUBCOMMAND_NAME, + RLP_ENCODE_SUBCOMMAND_NAME, + "--from", + tempJsonFile.toFile().getAbsolutePath(), + "--type", + RLP_QBFT_TYPE); + + final String expectedRlpString = + "0xf84fa00000000000000000000000000000000000000000000000000000000000000000ea94be068f726a13c8d" + + "46c44be6ce9d275600e1735a4945ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193c080c0"; + assertThat(commandOutput.toString(UTF_8)).contains(expectedRlpString); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + } + + @Test + public void encodeWithInvalidInputMustRaiseAnError(final @TempDir Path dir) throws Exception { + final Path tempJsonFile = Files.createTempFile(dir, "invalid", "json"); + try (final BufferedWriter fileWriter = Files.newBufferedWriter(tempJsonFile, UTF_8)) { + + fileWriter.write("{\"property\":0}"); + + fileWriter.flush(); + + parseCommand( + RLP_SUBCOMMAND_NAME, + RLP_ENCODE_SUBCOMMAND_NAME, + "--from", + tempJsonFile.toFile().getAbsolutePath()); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .startsWith( + "Unable to map the JSON data with selected type. Please check JSON input format."); + } + } + + @Test + public void encodeWithEmptyInputMustRaiseAnError(final @TempDir Path dir) throws Exception { + final Path emptyFile = Files.createTempFile(dir, "empty", "json"); + parseCommand( + RLP_SUBCOMMAND_NAME, + RLP_ENCODE_SUBCOMMAND_NAME, + "--from", + emptyFile.toFile().getAbsolutePath()); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .startsWith("An error occurred while trying to read the JSON data."); + } + + @Test + public void encodeWithEmptyStdInputMustRaiseAnError() throws Exception { + + // set empty stdin + final String jsonInput = ""; + final ByteArrayInputStream stdIn = new ByteArrayInputStream(jsonInput.getBytes(UTF_8)); + + parseCommand(stdIn, RLP_SUBCOMMAND_NAME, RLP_ENCODE_SUBCOMMAND_NAME); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .startsWith("An error occurred while trying to read the JSON data."); + } + + @Test + public void decodeWithoutPathMustWriteToStandardOutput() { + + final String inputData = + "0xf853a00000000000000000000000000000000000000000000000000000000000000000ea94be068f726a13c8d" + + "46c44be6ce9d275600e1735a4945ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193808400000000c0"; + + // set stdin + final ByteArrayInputStream stdIn = new ByteArrayInputStream(inputData.getBytes(UTF_8)); + + parseCommand(stdIn, RLP_SUBCOMMAND_NAME, RLP_DECODE_SUBCOMMAND_NAME); + + final String expectedValidatorString = + "[0xbe068f726a13c8d46c44be6ce9d275600e1735a4, 0x5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193]"; + assertThat(commandOutput.toString(UTF_8)).contains(expectedValidatorString); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void decodeQBFTWithoutPathMustWriteToStandardOutput() { + + final String inputData = + "0xf84fa00000000000000000000000000000000000000000000000000000000000000000ea94241f804efb46f71acaa" + + "5be94a62f7798e89c3724946cdf72da457453063ea92e7fa5ac30afbcec28cdc080c0"; + + // set stdin + final ByteArrayInputStream stdIn = new ByteArrayInputStream(inputData.getBytes(UTF_8)); + + parseCommand(stdIn, RLP_SUBCOMMAND_NAME, RLP_DECODE_SUBCOMMAND_NAME, "--type", RLP_QBFT_TYPE); + + final String expectedValidatorString = + "[0x241f804efb46f71acaa5be94a62f7798e89c3724, 0x6cdf72da457453063ea92e7fa5ac30afbcec28cd]"; + assertThat(commandOutput.toString(UTF_8)).contains(expectedValidatorString); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void decodeWithOutputFileMustWriteInThisFile() throws Exception { + + final File file = File.createTempFile("ibftValidators", "rlp"); + + final String inputData = + "0xf853a00000000000000000000000000000000000000000000000000000000000000000ea94be068f726a13c8d" + + "46c44be6ce9d275600e1735a4945ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193808400000000c0"; + + // set stdin + final ByteArrayInputStream stdIn = new ByteArrayInputStream(inputData.getBytes(UTF_8)); + + parseCommand(stdIn, RLP_SUBCOMMAND_NAME, RLP_DECODE_SUBCOMMAND_NAME, "--to", file.getPath()); + + final String expectedValidatorString = + "[0xbe068f726a13c8d46c44be6ce9d275600e1735a4, 0x5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193]"; + + assertThat(contentOf(file)).contains(expectedValidatorString); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + + @Test + public void decodeWithInputFilePathMustReadFromThisFile(final @TempDir Path dir) + throws Exception { + final Path tempJsonFile = Files.createTempFile(dir, "input", "json"); + try (final BufferedWriter fileWriter = Files.newBufferedWriter(tempJsonFile, UTF_8)) { + + fileWriter.write( + "0xf853a00000000000000000000000000000000000000000000000000000000000000000ea94be068f726a13c8d46c44be6ce9d275600e1735a4945ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193808400000000c0"); + + fileWriter.flush(); + + parseCommand( + RLP_SUBCOMMAND_NAME, + RLP_DECODE_SUBCOMMAND_NAME, + "--from", + tempJsonFile.toFile().getAbsolutePath()); + + final String expectedValidatorString = + "[0xbe068f726a13c8d46c44be6ce9d275600e1735a4, 0x5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193]"; + + assertThat(commandOutput.toString(UTF_8)).contains(expectedValidatorString); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + } + + @Test + public void decodeWithInputFilePathToOutputFile(final @TempDir Path dir) throws Exception { + final Path tempInputFile = Files.createTempFile(dir, "input", "json"); + final File tempOutputFile = File.createTempFile("ibftValidators", "rlp"); + try (final BufferedWriter fileWriter = Files.newBufferedWriter(tempInputFile, UTF_8)) { + + fileWriter.write( + "0xf853a00000000000000000000000000000000000000000000000000000000000000000ea94be068f726a13c8d46c44be6ce9d275600e1735a4945ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193808400000000c0"); + + fileWriter.flush(); + + parseCommand( + RLP_SUBCOMMAND_NAME, + RLP_DECODE_SUBCOMMAND_NAME, + "--from", + tempInputFile.toFile().getAbsolutePath(), + "--to", + tempOutputFile.getPath()); + + final String expectedValidatorString = + "[0xbe068f726a13c8d46c44be6ce9d275600e1735a4, 0x5ff6f4b66a46a2b2310a6f3a93aaddc0d9a1c193]"; + + assertThat(contentOf(tempOutputFile)).contains(expectedValidatorString); + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).isEmpty(); + } + } + + @Test + public void decodeWithEmptyStdInputMustRaiseAnError() throws Exception { + + // set empty stdin + final String jsonInput = ""; + final ByteArrayInputStream stdIn = new ByteArrayInputStream(jsonInput.getBytes(UTF_8)); + + parseCommand(stdIn, RLP_SUBCOMMAND_NAME, RLP_DECODE_SUBCOMMAND_NAME); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).startsWith("Unable to read input data."); + } + + @Test + public void decodeWithInputFilePathMustThrowErrorFileNotExist(final @TempDir Path dir) + throws Exception { + + final String nonExistingFileName = "/incorrectPath/wrongFile.json"; + + parseCommand(RLP_SUBCOMMAND_NAME, RLP_DECODE_SUBCOMMAND_NAME, "--from", nonExistingFileName); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)).contains("Unable to read input file"); + } + + @Test + public void decodeWithEmptyInputMustRaiseAnError(final @TempDir Path dir) throws Exception { + final Path emptyFile = Files.createTempFile(dir, "empty", "json"); + parseCommand( + RLP_SUBCOMMAND_NAME, + RLP_DECODE_SUBCOMMAND_NAME, + "--from", + emptyFile.toFile().getAbsolutePath()); + + assertThat(commandOutput.toString(UTF_8)).isEmpty(); + assertThat(commandErrorOutput.toString(UTF_8)) + .startsWith("An error occurred while trying to read the input data."); + } + + @AfterEach + public void restoreStdin() { + System.setIn(System.in); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/StorageSubCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/StorageSubCommandTest.java index e3b67d8b430..149b88a390f 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/StorageSubCommandTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/StorageSubCommandTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -25,22 +25,19 @@ import static org.hyperledger.besu.ethereum.core.VariablesStorageHelper.populateVariablesStorage; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.BLOCKCHAIN; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.VARIABLES; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import org.hyperledger.besu.cli.CommandTestAbstract; +import org.hyperledger.besu.ethereum.storage.keyvalue.VariablesKeyValueStorage; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import org.hyperledger.besu.services.kvstore.SegmentedInMemoryKeyValueStorage; import org.hyperledger.besu.services.kvstore.SegmentedKeyValueStorageAdapter; -import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; - -@RunWith(MockitoJUnitRunner.Silent.class) +@ExtendWith(MockitoExtension.class) public class StorageSubCommandTest extends CommandTestAbstract { @Test @@ -67,10 +64,10 @@ public void revertVariables() { final var kvVariables = new SegmentedKeyValueStorageAdapter(VARIABLES, kvVariablesSeg); final var kvBlockchainSeg = new SegmentedInMemoryKeyValueStorage(); final var kvBlockchain = new SegmentedKeyValueStorageAdapter(BLOCKCHAIN, kvBlockchainSeg); - when(rocksDBStorageFactory.create(eq(List.of(VARIABLES)), any(), any())) - .thenReturn(kvVariablesSeg); - when(rocksDBStorageFactory.create(eq(List.of(BLOCKCHAIN)), any(), any())) - .thenReturn(kvBlockchainSeg); + when(storageProvider.createVariablesStorage()) + .thenReturn(new VariablesKeyValueStorage(kvVariables)); + when(storageProvider.getStorageBySegmentIdentifier(BLOCKCHAIN)).thenReturn(kvBlockchain); + final var variableValues = getSampleVariableValues(); assertNoVariablesInStorage(kvBlockchain); populateVariablesStorage(kvVariables, variableValues); @@ -87,10 +84,9 @@ public void revertVariablesWhenSomeVariablesDoNotExist() { final var kvVariables = new SegmentedKeyValueStorageAdapter(VARIABLES, kvVariablesSeg); final var kvBlockchainSeg = new SegmentedInMemoryKeyValueStorage(); final var kvBlockchain = new SegmentedKeyValueStorageAdapter(BLOCKCHAIN, kvBlockchainSeg); - when(rocksDBStorageFactory.create(eq(List.of(VARIABLES)), any(), any())) - .thenReturn(kvVariablesSeg); - when(rocksDBStorageFactory.create(eq(List.of(BLOCKCHAIN)), any(), any())) - .thenReturn(kvBlockchainSeg); + when(storageProvider.createVariablesStorage()) + .thenReturn(new VariablesKeyValueStorage(kvVariables)); + when(storageProvider.getStorageBySegmentIdentifier(BLOCKCHAIN)).thenReturn(kvBlockchain); final var variableValues = getSampleVariableValues(); variableValues.remove(FINALIZED_BLOCK_HASH); @@ -108,8 +104,9 @@ public void revertVariablesWhenSomeVariablesDoNotExist() { public void doesNothingWhenVariablesAlreadyReverted() { final var kvVariables = new InMemoryKeyValueStorage(); final var kvBlockchain = new InMemoryKeyValueStorage(); - when(rocksDBStorageFactory.create(eq(VARIABLES), any(), any())).thenReturn(kvVariables); - when(rocksDBStorageFactory.create(eq(BLOCKCHAIN), any(), any())).thenReturn(kvBlockchain); + when(storageProvider.createVariablesStorage()) + .thenReturn(new VariablesKeyValueStorage(kvVariables)); + when(storageProvider.getStorageBySegmentIdentifier(BLOCKCHAIN)).thenReturn(kvBlockchain); final var variableValues = getSampleVariableValues(); assertNoVariablesInStorage(kvVariables); diff --git a/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/TrieLogHelperTest.java b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/TrieLogHelperTest.java new file mode 100644 index 00000000000..2f3a693f3de --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/TrieLogHelperTest.java @@ -0,0 +1,470 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.subcommands.storage; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration.DEFAULT_BONSAI_TRIE_LOG_PRUNING_WINDOW_SIZE; +import static org.hyperledger.besu.plugin.services.storage.DataStorageFormat.BONSAI; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.chain.MutableBlockchain; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.ethereum.storage.StorageProvider; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.trielog.TrieLogFactoryImpl; +import org.hyperledger.besu.ethereum.trie.diffbased.common.trielog.TrieLogLayer; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.ethereum.worldstate.ImmutableDataStorageConfiguration; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class TrieLogHelperTest { + + private static final StorageProvider storageProvider = new InMemoryKeyValueStorageProvider(); + private static BonsaiWorldStateKeyValueStorage inMemoryWorldState; + private TrieLogHelper nonValidatingTrieLogHelper; + + private static class NonValidatingTrieLogHelper extends TrieLogHelper { + @Override + void validatePruneConfiguration(final DataStorageConfiguration config) {} + } + + @Mock private MutableBlockchain blockchain; + static BlockHeader blockHeader1; + static BlockHeader blockHeader2; + static BlockHeader blockHeader3; + static BlockHeader blockHeader4; + + static BlockHeader blockHeader5; + + @BeforeEach + public void setup() throws IOException { + + blockHeader1 = new BlockHeaderTestFixture().number(1).buildHeader(); + blockHeader2 = new BlockHeaderTestFixture().number(2).buildHeader(); + blockHeader3 = new BlockHeaderTestFixture().number(3).buildHeader(); + blockHeader4 = new BlockHeaderTestFixture().number(4).buildHeader(); + blockHeader5 = new BlockHeaderTestFixture().number(5).buildHeader(); + + inMemoryWorldState = + new BonsaiWorldStateKeyValueStorage( + storageProvider, + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG); + + createTrieLog(blockHeader1); + + var updater = inMemoryWorldState.updater(); + updater + .getTrieLogStorageTransaction() + .put(blockHeader1.getHash().toArrayUnsafe(), createTrieLog(blockHeader1)); + updater + .getTrieLogStorageTransaction() + .put(blockHeader2.getHash().toArrayUnsafe(), createTrieLog(blockHeader2)); + updater + .getTrieLogStorageTransaction() + .put(blockHeader3.getHash().toArrayUnsafe(), createTrieLog(blockHeader3)); + updater + .getTrieLogStorageTransaction() + .put(blockHeader4.getHash().toArrayUnsafe(), createTrieLog(blockHeader4)); + updater + .getTrieLogStorageTransaction() + .put(blockHeader5.getHash().toArrayUnsafe(), createTrieLog(blockHeader5)); + updater.getTrieLogStorageTransaction().commit(); + + nonValidatingTrieLogHelper = new NonValidatingTrieLogHelper(); + } + + private static byte[] createTrieLog(final BlockHeader blockHeader) { + TrieLogLayer trieLogLayer = new TrieLogLayer(); + trieLogLayer.setBlockHash(blockHeader.getBlockHash()); + final BytesValueRLPOutput rlpLog = new BytesValueRLPOutput(); + TrieLogFactoryImpl.writeTo(trieLogLayer, rlpLog); + return rlpLog.encoded().toArrayUnsafe(); + } + + void mockBlockchainBase() { + when(blockchain.getChainHeadBlockNumber()).thenReturn(5L); + when(blockchain.getFinalized()).thenReturn(Optional.of(blockHeader3.getBlockHash())); + when(blockchain.getBlockHeader(any(Hash.class))).thenReturn(Optional.of(blockHeader3)); + } + + @Test + public void prune(final @TempDir Path dataDir) throws IOException { + Files.createDirectories(dataDir.resolve("database")); + + DataStorageConfiguration dataStorageConfiguration = + ImmutableDataStorageConfiguration.builder() + .dataStorageFormat(BONSAI) + .bonsaiMaxLayersToLoad(3L) + .bonsaiLimitTrieLogsEnabled(true) + .build(); + + mockBlockchainBase(); + when(blockchain.getBlockHeader(5)).thenReturn(Optional.of(blockHeader5)); + when(blockchain.getBlockHeader(4)).thenReturn(Optional.of(blockHeader4)); + when(blockchain.getBlockHeader(3)).thenReturn(Optional.of(blockHeader3)); + + // assert trie logs that will be pruned exist before prune call + assertThat(inMemoryWorldState.getTrieLog(blockHeader1.getHash()).get()) + .isEqualTo(createTrieLog(blockHeader1)); + assertThat(inMemoryWorldState.getTrieLog(blockHeader2.getHash()).get()) + .isEqualTo(createTrieLog(blockHeader2)); + assertThat(inMemoryWorldState.getTrieLog(blockHeader3.getHash()).get()) + .isEqualTo(createTrieLog(blockHeader3)); + + nonValidatingTrieLogHelper.prune( + dataStorageConfiguration, inMemoryWorldState, blockchain, dataDir); + + // assert pruned trie logs are not in the DB + assertThat(inMemoryWorldState.getTrieLog(blockHeader1.getHash())).isEqualTo(Optional.empty()); + assertThat(inMemoryWorldState.getTrieLog(blockHeader2.getHash())).isEqualTo(Optional.empty()); + + // assert retained trie logs are in the DB + assertThat(inMemoryWorldState.getTrieLog(blockHeader3.getHash()).get()) + .isEqualTo(createTrieLog(blockHeader3)); + assertThat(inMemoryWorldState.getTrieLog(blockHeader4.getHash()).get()) + .isEqualTo(createTrieLog(blockHeader4)); + assertThat(inMemoryWorldState.getTrieLog(blockHeader5.getHash()).get()) + .isEqualTo(createTrieLog(blockHeader5)); + } + + @Test + public void cannotPruneIfNoFinalizedIsFound() { + DataStorageConfiguration dataStorageConfiguration = + ImmutableDataStorageConfiguration.builder() + .dataStorageFormat(BONSAI) + .bonsaiMaxLayersToLoad(2L) + .bonsaiLimitTrieLogsEnabled(true) + .build(); + + when(blockchain.getChainHeadBlockNumber()).thenReturn(5L); + when(blockchain.getFinalized()).thenReturn(Optional.empty()); + + assertThatThrownBy( + () -> + nonValidatingTrieLogHelper.prune( + dataStorageConfiguration, inMemoryWorldState, blockchain, Path.of(""))) + .isInstanceOf(RuntimeException.class) + .hasMessage("No finalized block present, can't safely run trie log prune"); + } + + @Test + public void cannotPruneIfUserRetainsMoreLayersThanExistingChainLength() { + DataStorageConfiguration dataStorageConfiguration = + ImmutableDataStorageConfiguration.builder() + .dataStorageFormat(BONSAI) + .bonsaiMaxLayersToLoad(10L) + .bonsaiLimitTrieLogsEnabled(true) + .build(); + + when(blockchain.getChainHeadBlockNumber()).thenReturn(5L); + + assertThatThrownBy( + () -> + nonValidatingTrieLogHelper.prune( + dataStorageConfiguration, inMemoryWorldState, blockchain, Path.of(""))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Trying to retain more trie logs than chain length (5), skipping pruning"); + } + + @Test + public void cannotPruneIfUserRequiredFurtherThanFinalized(final @TempDir Path dataDir) { + + DataStorageConfiguration dataStorageConfiguration = + ImmutableDataStorageConfiguration.builder() + .dataStorageFormat(BONSAI) + .bonsaiMaxLayersToLoad(2L) + .bonsaiLimitTrieLogsEnabled(true) + .build(); + + mockBlockchainBase(); + + assertThatThrownBy( + () -> + nonValidatingTrieLogHelper.prune( + dataStorageConfiguration, inMemoryWorldState, blockchain, dataDir)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage( + "Trying to prune more layers than the finalized block height, skipping pruning"); + } + + @Test + public void skipPruningIfTrieLogCountIsLessThanMaxLayersToLoad() { + + DataStorageConfiguration dataStorageConfiguration = + ImmutableDataStorageConfiguration.builder() + .dataStorageFormat(BONSAI) + .bonsaiMaxLayersToLoad(6L) + .bonsaiLimitTrieLogsEnabled(true) + .build(); + + when(blockchain.getChainHeadBlockNumber()).thenReturn(5L); + + assertThatThrownBy( + () -> + nonValidatingTrieLogHelper.prune( + dataStorageConfiguration, inMemoryWorldState, blockchain, Path.of(""))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Trie log count (5) is less than retention limit (6), skipping pruning"); + } + + @Test + public void mismatchInPrunedTrieLogCountShouldNotDeleteFiles(final @TempDir Path dataDir) + throws IOException { + Files.createDirectories(dataDir.resolve("database")); + + DataStorageConfiguration dataStorageConfiguration = + ImmutableDataStorageConfiguration.builder() + .dataStorageFormat(BONSAI) + .bonsaiMaxLayersToLoad(3L) + .bonsaiLimitTrieLogsEnabled(true) + .build(); + + mockBlockchainBase(); + when(blockchain.getBlockHeader(5)).thenReturn(Optional.of(blockHeader5)); + when(blockchain.getBlockHeader(4)).thenReturn(Optional.of(blockHeader4)); + when(blockchain.getBlockHeader(3)).thenReturn(Optional.of(blockHeader3)); + + final BonsaiWorldStateKeyValueStorage inMemoryWorldStateSpy = spy(inMemoryWorldState); + // force a different value the second time the trie log count is called + when(inMemoryWorldStateSpy.streamTrieLogKeys(3L + DEFAULT_BONSAI_TRIE_LOG_PRUNING_WINDOW_SIZE)) + .thenCallRealMethod() + .thenReturn(Stream.empty()); + assertThatThrownBy( + () -> + nonValidatingTrieLogHelper.prune( + dataStorageConfiguration, inMemoryWorldStateSpy, blockchain, dataDir)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining( + "Remaining trie logs (0) did not match --bonsai-historical-block-limit (3)"); + } + + @Test + public void trieLogRetentionLimitShouldBeAboveMinimum() { + + DataStorageConfiguration dataStorageConfiguration = + ImmutableDataStorageConfiguration.builder() + .dataStorageFormat(BONSAI) + .bonsaiMaxLayersToLoad(511L) + .bonsaiLimitTrieLogsEnabled(true) + .build(); + + TrieLogHelper helper = new TrieLogHelper(); + assertThatThrownBy( + () -> + helper.prune(dataStorageConfiguration, inMemoryWorldState, blockchain, Path.of(""))) + .isInstanceOf(RuntimeException.class) + .hasMessage("--bonsai-historical-block-limit minimum value is 512"); + } + + @Test + public void trieLogPruningWindowSizeShouldBePositive() { + + DataStorageConfiguration dataStorageConfiguration = + ImmutableDataStorageConfiguration.builder() + .dataStorageFormat(BONSAI) + .bonsaiMaxLayersToLoad(512L) + .bonsaiLimitTrieLogsEnabled(true) + .bonsaiTrieLogPruningWindowSize(0) + .build(); + + TrieLogHelper helper = new TrieLogHelper(); + assertThatThrownBy( + () -> + helper.prune(dataStorageConfiguration, inMemoryWorldState, blockchain, Path.of(""))) + .isInstanceOf(RuntimeException.class) + .hasMessage("--bonsai-trie-logs-pruning-window-size=0 must be greater than 0"); + } + + @Test + public void trieLogPruningWindowSizeShouldBeAboveRetentionLimit() { + DataStorageConfiguration dataStorageConfiguration = + ImmutableDataStorageConfiguration.builder() + .dataStorageFormat(BONSAI) + .bonsaiMaxLayersToLoad(512L) + .bonsaiLimitTrieLogsEnabled(true) + .bonsaiTrieLogPruningWindowSize(512) + .build(); + + TrieLogHelper helper = new TrieLogHelper(); + assertThatThrownBy( + () -> + helper.prune(dataStorageConfiguration, inMemoryWorldState, blockchain, Path.of(""))) + .isInstanceOf(RuntimeException.class) + .hasMessage( + "--bonsai-trie-logs-pruning-window-size=512 must be greater than --bonsai-historical-block-limit=512"); + } + + @Test + public void exceptionWhileSavingFileStopsPruneProcess(final @TempDir Path dataDir) { + + DataStorageConfiguration dataStorageConfiguration = + ImmutableDataStorageConfiguration.builder() + .dataStorageFormat(BONSAI) + .bonsaiMaxLayersToLoad(3L) + .bonsaiLimitTrieLogsEnabled(true) + .build(); + + mockBlockchainBase(); + when(blockchain.getBlockHeader(5)).thenReturn(Optional.of(blockHeader5)); + when(blockchain.getBlockHeader(4)).thenReturn(Optional.of(blockHeader4)); + when(blockchain.getBlockHeader(3)).thenReturn(Optional.of(blockHeader3)); + + assertThatThrownBy( + () -> + nonValidatingTrieLogHelper.prune( + dataStorageConfiguration, + inMemoryWorldState, + blockchain, + dataDir.resolve("unknownPath"))) + .isInstanceOf(RuntimeException.class) + .hasCauseExactlyInstanceOf(FileNotFoundException.class); + + // assert all trie logs are still in the DB + assertThat(inMemoryWorldState.getTrieLog(blockHeader1.getHash()).get()) + .isEqualTo(createTrieLog(blockHeader1)); + assertThat(inMemoryWorldState.getTrieLog(blockHeader2.getHash()).get()) + .isEqualTo(createTrieLog(blockHeader2)); + assertThat(inMemoryWorldState.getTrieLog(blockHeader3.getHash()).get()) + .isEqualTo(createTrieLog(blockHeader3)); + assertThat(inMemoryWorldState.getTrieLog(blockHeader4.getHash()).get()) + .isEqualTo(createTrieLog(blockHeader4)); + assertThat(inMemoryWorldState.getTrieLog(blockHeader5.getHash()).get()) + .isEqualTo(createTrieLog(blockHeader5)); + } + + @Test + public void exportedTrieMatchesDbTrieLog(final @TempDir Path dataDir) throws IOException { + nonValidatingTrieLogHelper.exportTrieLog( + inMemoryWorldState, + singletonList(blockHeader1.getHash()), + dataDir.resolve("trie-log-dump")); + + var trieLog = + nonValidatingTrieLogHelper + .readTrieLogsAsRlpFromFile(dataDir.resolve("trie-log-dump").toString()) + .entrySet() + .stream() + .findFirst() + .get(); + + assertThat(trieLog.getKey()).isEqualTo(blockHeader1.getHash().toArrayUnsafe()); + assertThat(trieLog.getValue()) + .isEqualTo(inMemoryWorldState.getTrieLog(blockHeader1.getHash()).get()); + } + + @Test + public void exportedMultipleTriesMatchDbTrieLogs(final @TempDir Path dataDir) throws IOException { + nonValidatingTrieLogHelper.exportTrieLog( + inMemoryWorldState, + List.of(blockHeader1.getHash(), blockHeader2.getHash(), blockHeader3.getHash()), + dataDir.resolve("trie-log-dump")); + + var trieLogs = + nonValidatingTrieLogHelper + .readTrieLogsAsRlpFromFile(dataDir.resolve("trie-log-dump").toString()) + .entrySet() + .stream() + .collect(Collectors.toMap(e -> Bytes.wrap(e.getKey()), Map.Entry::getValue)); + + assertThat(trieLogs.get(blockHeader1.getHash())) + .isEqualTo(inMemoryWorldState.getTrieLog(blockHeader1.getHash()).get()); + assertThat(trieLogs.get(blockHeader2.getHash())) + .isEqualTo(inMemoryWorldState.getTrieLog(blockHeader2.getHash()).get()); + assertThat(trieLogs.get(blockHeader3.getHash())) + .isEqualTo(inMemoryWorldState.getTrieLog(blockHeader3.getHash()).get()); + } + + @Test + public void importedTrieLogMatchesDbTrieLog(final @TempDir Path dataDir) throws IOException { + StorageProvider tempStorageProvider = new InMemoryKeyValueStorageProvider(); + BonsaiWorldStateKeyValueStorage inMemoryWorldState2 = + new BonsaiWorldStateKeyValueStorage( + tempStorageProvider, new NoOpMetricsSystem(), DataStorageConfiguration.DEFAULT_CONFIG); + + nonValidatingTrieLogHelper.exportTrieLog( + inMemoryWorldState, + singletonList(blockHeader1.getHash()), + dataDir.resolve("trie-log-dump")); + + var trieLog = + nonValidatingTrieLogHelper.readTrieLogsAsRlpFromFile( + dataDir.resolve("trie-log-dump").toString()); + var updater = inMemoryWorldState2.updater(); + + trieLog.forEach((k, v) -> updater.getTrieLogStorageTransaction().put(k, v)); + + updater.getTrieLogStorageTransaction().commit(); + + assertThat(inMemoryWorldState2.getTrieLog(blockHeader1.getHash()).get()) + .isEqualTo(inMemoryWorldState.getTrieLog(blockHeader1.getHash()).get()); + } + + @Test + public void importedMultipleTriesMatchDbTrieLogs(final @TempDir Path dataDir) throws IOException { + StorageProvider tempStorageProvider = new InMemoryKeyValueStorageProvider(); + BonsaiWorldStateKeyValueStorage inMemoryWorldState2 = + new BonsaiWorldStateKeyValueStorage( + tempStorageProvider, new NoOpMetricsSystem(), DataStorageConfiguration.DEFAULT_CONFIG); + + nonValidatingTrieLogHelper.exportTrieLog( + inMemoryWorldState, + List.of(blockHeader1.getHash(), blockHeader2.getHash(), blockHeader3.getHash()), + dataDir.resolve("trie-log-dump")); + + var trieLog = + nonValidatingTrieLogHelper.readTrieLogsAsRlpFromFile( + dataDir.resolve("trie-log-dump").toString()); + var updater = inMemoryWorldState2.updater(); + + trieLog.forEach((k, v) -> updater.getTrieLogStorageTransaction().put(k, v)); + + updater.getTrieLogStorageTransaction().commit(); + + assertThat(inMemoryWorldState2.getTrieLog(blockHeader1.getHash()).get()) + .isEqualTo(inMemoryWorldState.getTrieLog(blockHeader1.getHash()).get()); + assertThat(inMemoryWorldState2.getTrieLog(blockHeader2.getHash()).get()) + .isEqualTo(inMemoryWorldState.getTrieLog(blockHeader2.getHash()).get()); + assertThat(inMemoryWorldState2.getTrieLog(blockHeader3.getHash()).get()) + .isEqualTo(inMemoryWorldState.getTrieLog(blockHeader3.getHash()).get()); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/TrieLogSubCommandTest.java b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/TrieLogSubCommandTest.java new file mode 100644 index 00000000000..66536070a7b --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/subcommands/storage/TrieLogSubCommandTest.java @@ -0,0 +1,74 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.subcommands.storage; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.verify; + +import org.hyperledger.besu.cli.CommandTestAbstract; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +class TrieLogSubCommandTest extends CommandTestAbstract { + + @Test + void limitTrieLogsDefaultDisabledForAllSubcommands() { + assertTrieLogSubcommand("prune"); + assertTrieLogSubcommand("count"); + assertTrieLogSubcommand("import"); + assertTrieLogSubcommand("export"); + } + + @Test + void limitTrieLogsDisabledForAllSubcommands() { + assertTrieLogSubcommandWithExplicitLimitEnabled("prune"); + assertTrieLogSubcommandWithExplicitLimitEnabled("count"); + assertTrieLogSubcommandWithExplicitLimitEnabled("import"); + assertTrieLogSubcommandWithExplicitLimitEnabled("export"); + } + + private void assertTrieLogSubcommand(final String trieLogSubcommand) { + parseCommand("storage", "trie-log", trieLogSubcommand); + assertConfigurationIsDisabledBySubcommand(); + } + + private void assertTrieLogSubcommandWithExplicitLimitEnabled(final String trieLogSubcommand) { + parseCommand("--bonsai-limit-trie-logs-enabled=true", "storage", "trie-log", trieLogSubcommand); + assertConfigurationIsDisabledBySubcommand(); + } + + private void assertConfigurationIsDisabledBySubcommand() { + verify(mockControllerBuilder, atLeastOnce()) + .dataStorageConfiguration(dataStorageConfigurationArgumentCaptor.capture()); + final List configs = + dataStorageConfigurationArgumentCaptor.getAllValues(); + assertThat(configs.get(0).getBonsaiLimitTrieLogsEnabled()).isTrue(); + assertThat(configs.get(1).getBonsaiLimitTrieLogsEnabled()).isFalse(); + } + + @Test + void limitTrieLogsDefaultEnabledForBesuMainCommand() { + parseCommand(); + verify(mockControllerBuilder, atLeastOnce()) + .dataStorageConfiguration(dataStorageConfigurationArgumentCaptor.capture()); + final List configs = + dataStorageConfigurationArgumentCaptor.getAllValues(); + assertThat(configs).allMatch(DataStorageConfiguration::getBonsaiLimitTrieLogsEnabled); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/util/BesuCommandCustomFactoryTest.java b/besu/src/test/java/org/hyperledger/besu/cli/util/BesuCommandCustomFactoryTest.java index d83fa43a655..f3494b506b0 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/util/BesuCommandCustomFactoryTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/util/BesuCommandCustomFactoryTest.java @@ -22,18 +22,18 @@ import java.util.Arrays; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class BesuCommandCustomFactoryTest { @Mock private PluginVersionsProvider pluginVersionsProvider; - @Before + @BeforeEach public void initMocks() { when(pluginVersionsProvider.getPluginVersions()).thenReturn(Arrays.asList("v1", "v2")); } diff --git a/besu/src/test/java/org/hyperledger/besu/cli/util/ConfigDefaultValueProviderStrategyTest.java b/besu/src/test/java/org/hyperledger/besu/cli/util/ConfigDefaultValueProviderStrategyTest.java new file mode 100644 index 00000000000..50eadceb3bb --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/cli/util/ConfigDefaultValueProviderStrategyTest.java @@ -0,0 +1,168 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.cli.util; + +import static java.util.Collections.singletonMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.cli.error.BesuParameterExceptionHandler; +import org.hyperledger.besu.cli.options.stable.LoggingLevelOption; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import picocli.CommandLine; +import picocli.CommandLine.IDefaultValueProvider; +import picocli.CommandLine.IExecutionStrategy; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Model.IGetter; +import picocli.CommandLine.Model.OptionSpec; +import picocli.CommandLine.ParseResult; +import picocli.CommandLine.RunLast; + +@ExtendWith(MockitoExtension.class) +public class ConfigDefaultValueProviderStrategyTest { + + private static final String CONFIG_FILE_OPTION_NAME = "--config-file"; + @TempDir public Path temp; + + private LoggingLevelOption levelOption; + private final IExecutionStrategy resultHandler = new RunLast(); + + private final Map environment = singletonMap("BESU_LOGGING", "ERROR"); + private ConfigDefaultValueProviderStrategy configParsingHandler; + + @Mock ParseResult mockParseResult; + @Mock CommandSpec mockCommandSpec; + @Mock CommandLine mockCommandLine; + @Mock OptionSpec mockConfigOptionSpec; + @Mock IGetter mockConfigOptionGetter; + @Mock BesuParameterExceptionHandler mockParameterExceptionHandler; + + @BeforeEach + public void initMocks() { + lenient().when(mockCommandSpec.commandLine()).thenReturn(mockCommandLine); + lenient().when(mockParseResult.commandSpec()).thenReturn(mockCommandSpec); + final List originalArgs = new ArrayList<>(); + originalArgs.add(CONFIG_FILE_OPTION_NAME); + lenient().when(mockParseResult.originalArgs()).thenReturn(originalArgs); + lenient() + .when(mockParseResult.matchedOption(CONFIG_FILE_OPTION_NAME)) + .thenReturn(mockConfigOptionSpec); + lenient().when(mockParseResult.hasMatchedOption(CONFIG_FILE_OPTION_NAME)).thenReturn(true); + lenient().when(mockConfigOptionSpec.getter()).thenReturn(mockConfigOptionGetter); + levelOption = LoggingLevelOption.create(); + levelOption.setLogLevel("INFO"); + configParsingHandler = new ConfigDefaultValueProviderStrategy(resultHandler, environment); + } + + @Test + public void handleWithCommandLineOption() throws Exception { + when(mockConfigOptionGetter.get()).thenReturn(Files.createTempFile("tmp", "txt").toFile()); + configParsingHandler.execute(mockParseResult); + verify(mockCommandLine).setDefaultValueProvider(any(IDefaultValueProvider.class)); + verify(mockCommandLine).setExecutionStrategy(eq(resultHandler)); + verify(mockCommandLine).execute(anyString()); + } + + @Test + public void handleWithEnvironmentVariable() throws IOException { + when(mockParseResult.hasMatchedOption(CONFIG_FILE_OPTION_NAME)).thenReturn(false); + + final ConfigDefaultValueProviderStrategy environmentConfigFileParsingHandler = + new ConfigDefaultValueProviderStrategy( + resultHandler, + singletonMap( + "BESU_CONFIG_FILE", + Files.createFile(temp.resolve("tmp")).toFile().getAbsolutePath())); + + when(mockParseResult.hasMatchedOption(CONFIG_FILE_OPTION_NAME)).thenReturn(false); + + environmentConfigFileParsingHandler.execute(mockParseResult); + } + + @Test + public void handleWithCommandLineOptionShouldRaiseExceptionIfNoFileParam() throws Exception { + final String error_message = "an error occurred during get"; + when(mockConfigOptionGetter.get()).thenThrow(new Exception(error_message)); + assertThatThrownBy(() -> configParsingHandler.execute(mockParseResult)) + .isInstanceOf(Exception.class) + .hasMessage(error_message); + } + + @Test + public void handleWithEnvironmentVariableOptionShouldRaiseExceptionIfNoFileParam() { + final ConfigDefaultValueProviderStrategy environmentConfigFileParsingHandler = + new ConfigDefaultValueProviderStrategy( + resultHandler, singletonMap("BESU_CONFIG_FILE", "not_found.toml")); + + when(mockParseResult.hasMatchedOption(CONFIG_FILE_OPTION_NAME)).thenReturn(false); + + assertThatThrownBy(() -> environmentConfigFileParsingHandler.execute(mockParseResult)) + .isInstanceOf(CommandLine.ParameterException.class); + } + + @Test + public void shouldRetrieveConfigFromEnvironmentWhenConfigFileSpecified() throws Exception { + final IDefaultValueProvider defaultValueProvider = + configParsingHandler.createDefaultValueProvider( + mockCommandLine, Optional.of(new File("foo")), Optional.empty()); + final String value = defaultValueProvider.defaultValue(OptionSpec.builder("--logging").build()); + assertThat(value).isEqualTo("ERROR"); + } + + @Test + public void shouldRetrieveConfigFromEnvironmentWhenConfigFileNotSpecified() throws Exception { + final IDefaultValueProvider defaultValueProvider = + configParsingHandler.createDefaultValueProvider( + mockCommandLine, Optional.empty(), Optional.empty()); + final String value = defaultValueProvider.defaultValue(OptionSpec.builder("--logging").build()); + assertThat(value).isEqualTo("ERROR"); + } + + @Test + public void handleThrowsErrorWithWithEnvironmentVariableAndCommandLineSpecified() + throws IOException { + + final ConfigDefaultValueProviderStrategy environmentConfigFileParsingHandler = + new ConfigDefaultValueProviderStrategy( + resultHandler, + singletonMap("BESU_CONFIG_FILE", temp.resolve("tmp").toFile().getAbsolutePath())); + + when(mockParseResult.hasMatchedOption(CONFIG_FILE_OPTION_NAME)).thenReturn(true); + + assertThatThrownBy(() -> environmentConfigFileParsingHandler.execute(mockParseResult)) + .isInstanceOf(CommandLine.ParameterException.class); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/util/ConfigOptionSearchAndRunHandlerTest.java b/besu/src/test/java/org/hyperledger/besu/cli/util/ConfigOptionSearchAndRunHandlerTest.java deleted file mode 100644 index 0f0c1e696a3..00000000000 --- a/besu/src/test/java/org/hyperledger/besu/cli/util/ConfigOptionSearchAndRunHandlerTest.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.cli.util; - -import static java.util.Collections.singletonMap; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.cli.error.BesuParameterExceptionHandler; -import org.hyperledger.besu.cli.options.stable.LoggingLevelOption; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import picocli.CommandLine; -import picocli.CommandLine.IDefaultValueProvider; -import picocli.CommandLine.IExecutionStrategy; -import picocli.CommandLine.Model.CommandSpec; -import picocli.CommandLine.Model.IGetter; -import picocli.CommandLine.Model.OptionSpec; -import picocli.CommandLine.ParseResult; -import picocli.CommandLine.RunLast; - -@RunWith(MockitoJUnitRunner.class) -public class ConfigOptionSearchAndRunHandlerTest { - - private static final String CONFIG_FILE_OPTION_NAME = "--config-file"; - @Rule public final TemporaryFolder temp = new TemporaryFolder(); - - private LoggingLevelOption levelOption; - private final IExecutionStrategy resultHandler = new RunLast(); - - private final Map environment = singletonMap("BESU_LOGGING", "ERROR"); - private ConfigOptionSearchAndRunHandler configParsingHandler; - - @Mock ParseResult mockParseResult; - @Mock CommandSpec mockCommandSpec; - @Mock CommandLine mockCommandLine; - @Mock OptionSpec mockConfigOptionSpec; - @Mock IGetter mockConfigOptionGetter; - @Mock BesuParameterExceptionHandler mockParameterExceptionHandler; - - @Before - public void initMocks() { - when(mockCommandSpec.commandLine()).thenReturn(mockCommandLine); - when(mockParseResult.commandSpec()).thenReturn(mockCommandSpec); - final List originalArgs = new ArrayList<>(); - originalArgs.add(CONFIG_FILE_OPTION_NAME); - when(mockParseResult.originalArgs()).thenReturn(originalArgs); - when(mockParseResult.matchedOption(CONFIG_FILE_OPTION_NAME)).thenReturn(mockConfigOptionSpec); - when(mockParseResult.hasMatchedOption(CONFIG_FILE_OPTION_NAME)).thenReturn(true); - when(mockConfigOptionSpec.getter()).thenReturn(mockConfigOptionGetter); - levelOption = new LoggingLevelOption(); - levelOption.setLogLevel("INFO"); - configParsingHandler = - new ConfigOptionSearchAndRunHandler( - resultHandler, mockParameterExceptionHandler, environment); - } - - @Test - public void handleWithCommandLineOption() throws Exception { - when(mockConfigOptionGetter.get()).thenReturn(temp.newFile()); - final List result = configParsingHandler.handle(mockParseResult); - verify(mockCommandLine).setDefaultValueProvider(any(IDefaultValueProvider.class)); - verify(mockCommandLine).setExecutionStrategy(eq(resultHandler)); - verify(mockCommandLine).setParameterExceptionHandler(eq(mockParameterExceptionHandler)); - verify(mockCommandLine).execute(anyString()); - - assertThat(result).isEmpty(); - } - - @Test - public void handleWithEnvironmentVariable() throws IOException { - when(mockParseResult.hasMatchedOption(CONFIG_FILE_OPTION_NAME)).thenReturn(false); - - final ConfigOptionSearchAndRunHandler environmentConfigFileParsingHandler = - new ConfigOptionSearchAndRunHandler( - resultHandler, - mockParameterExceptionHandler, - singletonMap("BESU_CONFIG_FILE", temp.newFile().getAbsolutePath())); - - when(mockParseResult.hasMatchedOption(CONFIG_FILE_OPTION_NAME)).thenReturn(false); - - environmentConfigFileParsingHandler.handle(mockParseResult); - } - - @Test - public void handleWithCommandLineOptionShouldRaiseExceptionIfNoFileParam() throws Exception { - final String error_message = "an error occurred during get"; - when(mockConfigOptionGetter.get()).thenThrow(new Exception(error_message)); - assertThatThrownBy(() -> configParsingHandler.handle(mockParseResult)) - .isInstanceOf(Exception.class) - .hasMessage(error_message); - } - - @Test - public void handleWithEnvironmentVariableOptionShouldRaiseExceptionIfNoFileParam() { - final ConfigOptionSearchAndRunHandler environmentConfigFileParsingHandler = - new ConfigOptionSearchAndRunHandler( - resultHandler, - mockParameterExceptionHandler, - singletonMap("BESU_CONFIG_FILE", "not_found.toml")); - - when(mockParseResult.hasMatchedOption(CONFIG_FILE_OPTION_NAME)).thenReturn(false); - - assertThatThrownBy(() -> environmentConfigFileParsingHandler.handle(mockParseResult)) - .isInstanceOf(CommandLine.ParameterException.class); - } - - @Test - public void shouldRetrieveConfigFromEnvironmentWhenConfigFileSpecified() throws Exception { - final IDefaultValueProvider defaultValueProvider = - configParsingHandler.createDefaultValueProvider( - mockCommandLine, Optional.of(new File("foo"))); - final String value = defaultValueProvider.defaultValue(OptionSpec.builder("--logging").build()); - assertThat(value).isEqualTo("ERROR"); - } - - @Test - public void shouldRetrieveConfigFromEnvironmentWhenConfigFileNotSpecified() throws Exception { - final IDefaultValueProvider defaultValueProvider = - configParsingHandler.createDefaultValueProvider(mockCommandLine, Optional.empty()); - final String value = defaultValueProvider.defaultValue(OptionSpec.builder("--logging").build()); - assertThat(value).isEqualTo("ERROR"); - } - - @Test - public void handleThrowsErrorWithWithEnvironmentVariableAndCommandLineSpecified() - throws IOException { - - final ConfigOptionSearchAndRunHandler environmentConfigFileParsingHandler = - new ConfigOptionSearchAndRunHandler( - resultHandler, - mockParameterExceptionHandler, - singletonMap("BESU_CONFIG_FILE", temp.newFile().getAbsolutePath())); - - when(mockParseResult.hasMatchedOption(CONFIG_FILE_OPTION_NAME)).thenReturn(true); - - assertThatThrownBy(() -> environmentConfigFileParsingHandler.handle(mockParseResult)) - .isInstanceOf(CommandLine.ParameterException.class); - } -} diff --git a/besu/src/test/java/org/hyperledger/besu/cli/util/VersionProviderTest.java b/besu/src/test/java/org/hyperledger/besu/cli/util/VersionProviderTest.java index 6fba9cf66a4..57f16c90210 100644 --- a/besu/src/test/java/org/hyperledger/besu/cli/util/VersionProviderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/cli/util/VersionProviderTest.java @@ -23,12 +23,12 @@ import java.util.Collections; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class VersionProviderTest { @Mock private PluginVersionsProvider pluginVersionsProvider; diff --git a/besu/src/test/java/org/hyperledger/besu/components/EnclaveModule.java b/besu/src/test/java/org/hyperledger/besu/components/EnclaveModule.java new file mode 100644 index 00000000000..20f9c1bf497 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/components/EnclaveModule.java @@ -0,0 +1,98 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.components; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.crypto.KeyPair; +import org.hyperledger.besu.crypto.SignatureAlgorithm; +import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.enclave.Enclave; +import org.hyperledger.besu.enclave.EnclaveFactory; +import org.hyperledger.besu.enclave.types.ReceiveResponse; +import org.hyperledger.besu.ethereum.privacy.PrivateTransaction; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.plugin.data.Restriction; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.function.Supplier; + +import com.google.common.base.Suppliers; +import dagger.Module; +import dagger.Provides; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +@Module +public class EnclaveModule { + + private static final Supplier SIGNATURE_ALGORITHM = + Suppliers.memoize(SignatureAlgorithmFactory::getInstance); + + private static final Bytes ENCLAVE_PUBLIC_KEY = + Bytes.fromBase64String("A1aVtMxLCUHmBVHXoZzzBgPbW/wj5axDpW9X8l91SGo="); + + private static final Bytes32 PRIVACY_GROUP_BYTES32 = + Bytes32.fromHexString("0xf250d523ae9164722b06ca25cfa2a7f3c45df96b09e215236f886c876f715bfa"); + + private static final Bytes MOCK_PAYLOAD = + Bytes.fromHexString( + "0x608060405234801561001057600080fd5b5060008054600160a060020a03191633179055610199806100326000396000f3fe6080604052600436106100565763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416633fa4f245811461005b5780636057361d1461008257806367e404ce146100ae575b600080fd5b34801561006757600080fd5b506100706100ec565b60408051918252519081900360200190f35b34801561008e57600080fd5b506100ac600480360360208110156100a557600080fd5b50356100f2565b005b3480156100ba57600080fd5b506100c3610151565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b60025490565b604080513381526020810183905281517fc9db20adedc6cf2b5d25252b101ab03e124902a73fcb12b753f3d1aaa2d8f9f5929181900390910190a16002556001805473ffffffffffffffffffffffffffffffffffffffff191633179055565b60015473ffffffffffffffffffffffffffffffffffffffff169056fea165627a7a72305820c7f729cb24e05c221f5aa913700793994656f233fe2ce3b9fd9a505ea17e8d8a0029"); + + private static final KeyPair KEY_PAIR = + SIGNATURE_ALGORITHM + .get() + .createKeyPair( + SIGNATURE_ALGORITHM + .get() + .createPrivateKey( + new BigInteger( + "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63", 16))); + + private static final PrivateTransaction PRIVATE_TRANSACTION = + PrivateTransaction.builder() + .chainId(BigInteger.valueOf(1337)) + .gasLimit(1000) + .gasPrice(Wei.ZERO) + .nonce(0) + .payload(MOCK_PAYLOAD) + .to(null) + .privateFrom(ENCLAVE_PUBLIC_KEY) + .privateFor(Collections.singletonList(ENCLAVE_PUBLIC_KEY)) + .restriction(Restriction.RESTRICTED) + .value(Wei.ZERO) + .signAndBuild(KEY_PAIR); + + @Provides + EnclaveFactory provideMockableEnclaveFactory() { + Enclave mockEnclave = mock(Enclave.class); + final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput(); + PRIVATE_TRANSACTION.writeTo(rlpOutput); + when(mockEnclave.receive(any())) + .thenReturn( + new ReceiveResponse( + rlpOutput.encoded().toBase64String().getBytes(StandardCharsets.UTF_8), + PRIVACY_GROUP_BYTES32.toBase64String(), + ENCLAVE_PUBLIC_KEY.toBase64String())); + EnclaveFactory enclaveFactory = mock(EnclaveFactory.class); + when(enclaveFactory.createVertxEnclave(any())).thenReturn(mockEnclave); + return enclaveFactory; + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/components/GenesisConfigModule.java b/besu/src/test/java/org/hyperledger/besu/components/GenesisConfigModule.java new file mode 100644 index 00000000000..ae82b8b9288 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/components/GenesisConfigModule.java @@ -0,0 +1,38 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.components; + +import org.hyperledger.besu.config.GenesisConfigFile; + +import javax.inject.Named; + +import dagger.Module; +import dagger.Provides; + +@Module +public class GenesisConfigModule { + + @Named("default") + @Provides + GenesisConfigFile provideDefaultGenesisConfigFile() { + return GenesisConfigFile.DEFAULT; + } + + @Named("mainnet") + @Provides + GenesisConfigFile provideMainnetGenesisConfigFile() { + return GenesisConfigFile.mainnet(); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/components/MockBesuCommandModule.java b/besu/src/test/java/org/hyperledger/besu/components/MockBesuCommandModule.java new file mode 100644 index 00000000000..743b4ee8de9 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/components/MockBesuCommandModule.java @@ -0,0 +1,50 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.components; + +import static org.mockito.Mockito.mock; + +import org.hyperledger.besu.cli.BesuCommand; +import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; + +import javax.inject.Named; +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Module +public class MockBesuCommandModule { + + @Provides + BesuCommand provideBesuCommand() { + return mock(BesuCommand.class); + } + + @Provides + @Singleton + MetricsConfiguration provideMetricsConfiguration() { + return MetricsConfiguration.builder().build(); + } + + @Provides + @Named("besuCommandLogger") + @Singleton + Logger provideBesuCommandLogger() { + return LoggerFactory.getLogger(MockBesuCommandModule.class); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/components/NoOpMetricsSystemModule.java b/besu/src/test/java/org/hyperledger/besu/components/NoOpMetricsSystemModule.java new file mode 100644 index 00000000000..e7807e3d759 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/components/NoOpMetricsSystemModule.java @@ -0,0 +1,40 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.components; + +import org.hyperledger.besu.metrics.ObservableMetricsSystem; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.MetricsSystem; + +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; + +@Module +public class NoOpMetricsSystemModule { + + @Provides + @Singleton + MetricsSystem provideMetricsSystem() { + return new NoOpMetricsSystem(); + } + + @Provides + @Singleton + ObservableMetricsSystem provideObservableMetricsSystem() { + return new NoOpMetricsSystem(); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/components/PrivacyParametersModule.java b/besu/src/test/java/org/hyperledger/besu/components/PrivacyParametersModule.java new file mode 100644 index 00000000000..0b8fcf744ff --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/components/PrivacyParametersModule.java @@ -0,0 +1,47 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.components; + +import org.hyperledger.besu.enclave.EnclaveFactory; +import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.ethereum.privacy.storage.PrivacyStorageProvider; + +import java.net.URI; +import java.net.URISyntaxException; + +import dagger.Module; +import dagger.Provides; +import io.vertx.core.Vertx; + +/** Provides a general use PrivacyParameters instance for testing. */ +@Module +public class PrivacyParametersModule { + + @Provides + PrivacyParameters providePrivacyParameters( + final PrivacyStorageProvider storageProvider, final Vertx vertx) { + try { + return new PrivacyParameters.Builder() + .setEnabled(true) + .setEnclaveUrl(new URI("http://127.0.0.1:8000")) + .setStorageProvider(storageProvider) + .setEnclaveFactory(new EnclaveFactory(vertx)) + .setFlexiblePrivacyGroupsEnabled(false) + .build(); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/components/PrivacyTestModule.java b/besu/src/test/java/org/hyperledger/besu/components/PrivacyTestModule.java new file mode 100644 index 00000000000..13cafe1ab45 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/components/PrivacyTestModule.java @@ -0,0 +1,111 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.components; + +import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_BACKGROUND_THREAD_COUNT; +import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_CACHE_CAPACITY; +import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_IS_HIGH_SPEC; +import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_MAX_OPEN_FILES; + +import org.hyperledger.besu.ethereum.privacy.storage.PrivacyStorageProvider; +import org.hyperledger.besu.ethereum.privacy.storage.keyvalue.PrivacyKeyValueStorageProviderBuilder; +import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBKeyValuePrivacyStorageFactory; +import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBKeyValueStorageFactory; +import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBMetricsFactory; +import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBFactoryConfiguration; +import org.hyperledger.besu.services.BesuConfigurationImpl; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import javax.inject.Named; +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; +import io.vertx.core.Vertx; + +@Module +public class PrivacyTestModule { + + @Provides + @Named("dataDir") + Path provideDataDir() { + try { + return Files.createTempDirectory("PrivacyTestDatadir"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Provides + Vertx provideVertx() { + return Vertx.vertx(); + } + + @Provides + DataStorageConfiguration provideDataStorageConfiguration() { + return DataStorageConfiguration.DEFAULT_FOREST_CONFIG; + } + + @Provides + @Singleton + @Named("dbDir") + Path provideDbDir(@Named("dataDir") final Path dataDir) { + try { + final Path dbDir = Files.createTempDirectory(dataDir, "database"); + return dbDir; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Provides + @Singleton + @Named("flexibleEnabled") + Boolean provideFlexibleEnabled() { + return true; + } + + @Provides + @Singleton + @SuppressWarnings("CloseableProvides") + PrivacyStorageProvider provideKeyValueStorageProvider( + @Named("dbDir") final Path dbDir, + final DataStorageConfiguration dataStorageConfiguration, + @Named("dataDir") final Path dataDir) { + final var besuConfiguration = new BesuConfigurationImpl(); + besuConfiguration.init(dataDir, dbDir, dataStorageConfiguration); + return new PrivacyKeyValueStorageProviderBuilder() + .withStorageFactory( + new RocksDBKeyValuePrivacyStorageFactory( + new RocksDBKeyValueStorageFactory( + () -> + new RocksDBFactoryConfiguration( + DEFAULT_MAX_OPEN_FILES, + DEFAULT_BACKGROUND_THREAD_COUNT, + DEFAULT_CACHE_CAPACITY, + DEFAULT_IS_HIGH_SPEC), + Arrays.asList(KeyValueSegmentIdentifier.values()), + RocksDBMetricsFactory.PRIVATE_ROCKS_DB_METRICS))) + .withCommonConfiguration(besuConfiguration) + .withMetricsSystem(new NoOpMetricsSystem()) + .build(); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/controller/AbstractBftBesuControllerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/controller/AbstractBftBesuControllerBuilderTest.java new file mode 100644 index 00000000000..621ae8a98c7 --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/controller/AbstractBftBesuControllerBuilderTest.java @@ -0,0 +1,206 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.controller; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; + +import org.hyperledger.besu.components.BesuComponent; +import org.hyperledger.besu.config.CheckpointConfigOptions; +import org.hyperledger.besu.config.GenesisConfigFile; +import org.hyperledger.besu.config.GenesisConfigOptions; +import org.hyperledger.besu.cryptoservices.NodeKey; +import org.hyperledger.besu.cryptoservices.NodeKeyUtils; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.GasLimitCalculator; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockBody; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; +import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; +import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; +import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration; +import org.hyperledger.besu.ethereum.storage.StorageProvider; +import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; +import org.hyperledger.besu.ethereum.storage.keyvalue.VariablesKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.ethereum.worldstate.WorldStatePreimageStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.log.LogsBloomFilter; +import org.hyperledger.besu.metrics.ObservableMetricsSystem; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; + +import java.math.BigInteger; +import java.nio.file.Path; +import java.time.Clock; +import java.util.List; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Range; +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public abstract class AbstractBftBesuControllerBuilderTest { + + protected BesuControllerBuilder bftBesuControllerBuilder; + @Mock protected GenesisConfigFile genesisConfigFile; + @Mock protected GenesisConfigOptions genesisConfigOptions; + @Mock private SynchronizerConfiguration synchronizerConfiguration; + @Mock private EthProtocolConfiguration ethProtocolConfiguration; + @Mock CheckpointConfigOptions checkpointConfigOptions; + @Mock private PrivacyParameters privacyParameters; + @Mock private Clock clock; + @Mock private StorageProvider storageProvider; + @Mock private GasLimitCalculator gasLimitCalculator; + @Mock private WorldStatePreimageStorage worldStatePreimageStorage; + private static final BigInteger networkId = BigInteger.ONE; + private static final NodeKey nodeKey = NodeKeyUtils.generate(); + private final TransactionPoolConfiguration poolConfiguration = + TransactionPoolConfiguration.DEFAULT; + private final ObservableMetricsSystem observableMetricsSystem = new NoOpMetricsSystem(); + protected final ObjectMapper objectMapper = new ObjectMapper(); + private final MiningParameters miningParameters = MiningParameters.newDefault(); + @TempDir Path tempDir; + + @BeforeEach + public void setup() throws JsonProcessingException { + // besu controller setup + final ForestWorldStateKeyValueStorage worldStateKeyValueStorage = + mock(ForestWorldStateKeyValueStorage.class); + final WorldStateStorageCoordinator worldStateStorageCoordinator = + new WorldStateStorageCoordinator(worldStateKeyValueStorage); + + lenient().when(genesisConfigFile.getParentHash()).thenReturn(Hash.ZERO.toHexString()); + lenient().when(genesisConfigFile.getDifficulty()).thenReturn(Bytes.of(0).toHexString()); + lenient().when(genesisConfigFile.getMixHash()).thenReturn(Hash.ZERO.toHexString()); + lenient().when(genesisConfigFile.getNonce()).thenReturn(Long.toHexString(1)); + lenient().when(genesisConfigFile.getConfigOptions()).thenReturn(genesisConfigOptions); + lenient().when(genesisConfigOptions.getCheckpointOptions()).thenReturn(checkpointConfigOptions); + lenient() + .when(storageProvider.createBlockchainStorage(any(), any(), any())) + .thenReturn( + new KeyValueStoragePrefixedKeyBlockchainStorage( + new InMemoryKeyValueStorage(), + new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), + new MainnetBlockHeaderFunctions(), + false)); + lenient() + .when( + storageProvider.createWorldStateStorageCoordinator( + DataStorageConfiguration.DEFAULT_FOREST_CONFIG)) + .thenReturn(worldStateStorageCoordinator); + lenient().when(worldStateKeyValueStorage.isWorldStateAvailable(any())).thenReturn(true); + lenient() + .when(worldStateKeyValueStorage.updater()) + .thenReturn(mock(ForestWorldStateKeyValueStorage.Updater.class)); + lenient() + .when(worldStatePreimageStorage.updater()) + .thenReturn(mock(WorldStatePreimageStorage.Updater.class)); + lenient() + .when(storageProvider.createWorldStatePreimageStorage()) + .thenReturn(worldStatePreimageStorage); + lenient().when(synchronizerConfiguration.getDownloaderParallelism()).thenReturn(1); + lenient().when(synchronizerConfiguration.getTransactionsParallelism()).thenReturn(1); + lenient().when(synchronizerConfiguration.getComputationParallelism()).thenReturn(1); + + lenient() + .when(synchronizerConfiguration.getBlockPropagationRange()) + .thenReturn(Range.closed(1L, 2L)); + + setupBftGenesisConfigFile(); + + bftBesuControllerBuilder = + createBftControllerBuilder() + .genesisConfigFile(genesisConfigFile) + .synchronizerConfiguration(synchronizerConfiguration) + .ethProtocolConfiguration(ethProtocolConfiguration) + .networkId(networkId) + .miningParameters(miningParameters) + .metricsSystem(observableMetricsSystem) + .privacyParameters(privacyParameters) + .dataDirectory(tempDir) + .clock(clock) + .transactionPoolConfiguration(poolConfiguration) + .dataStorageConfiguration(DataStorageConfiguration.DEFAULT_FOREST_CONFIG) + .nodeKey(nodeKey) + .storageProvider(storageProvider) + .gasLimitCalculator(gasLimitCalculator) + .evmConfiguration(EvmConfiguration.DEFAULT) + .besuComponent(mock(BesuComponent.class)) + .networkConfiguration(NetworkingConfiguration.create()); + } + + protected abstract void setupBftGenesisConfigFile() throws JsonProcessingException; + + protected abstract BesuControllerBuilder createBftControllerBuilder(); + + @Test + public void miningParametersBlockPeriodSecondsIsUpdatedOnTransition() { + final var besuController = bftBesuControllerBuilder.build(); + final var protocolContext = besuController.getProtocolContext(); + + final BlockHeader header1 = + new BlockHeader( + protocolContext.getBlockchain().getChainHeadHash(), + Hash.EMPTY_TRIE_HASH, + Address.ZERO, + Hash.EMPTY_TRIE_HASH, + Hash.EMPTY_TRIE_HASH, + Hash.EMPTY_TRIE_HASH, + LogsBloomFilter.builder().build(), + Difficulty.ONE, + 1, + 0, + 0, + 0, + protocolContext.getBlockchain().getChainHead().getBlockHeader().getExtraData(), + Wei.ZERO, + Hash.EMPTY, + 0, + null, + null, + null, + null, + null, + getBlockHeaderFunctions()); + final Block block1 = new Block(header1, BlockBody.empty()); + + protocolContext.getBlockchain().appendBlock(block1, List.of()); + + assertThat(miningParameters.getBlockPeriodSeconds()).isNotEmpty().hasValue(2); + assertThat(miningParameters.getBlockTxsSelectionMaxTime()).isEqualTo(2000 * 75 / 100); + } + + protected abstract BlockHeaderFunctions getBlockHeaderFunctions(); +} diff --git a/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerBuilderTest.java deleted file mode 100644 index a24479e8617..00000000000 --- a/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerBuilderTest.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright 2019 ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.controller; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.config.CheckpointConfigOptions; -import org.hyperledger.besu.config.EthashConfigOptions; -import org.hyperledger.besu.config.GenesisConfigFile; -import org.hyperledger.besu.config.GenesisConfigOptions; -import org.hyperledger.besu.cryptoservices.NodeKey; -import org.hyperledger.besu.cryptoservices.NodeKeyUtils; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.GasLimitCalculator; -import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; -import org.hyperledger.besu.ethereum.chain.Blockchain; -import org.hyperledger.besu.ethereum.core.MiningParameters; -import org.hyperledger.besu.ethereum.core.PrivacyParameters; -import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; -import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; -import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; -import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; -import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration; -import org.hyperledger.besu.ethereum.storage.StorageProvider; -import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; -import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; -import org.hyperledger.besu.ethereum.storage.keyvalue.VariablesKeyValueStorage; -import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; -import org.hyperledger.besu.ethereum.worldstate.ImmutableDataStorageConfiguration; -import org.hyperledger.besu.ethereum.worldstate.PrunerConfiguration; -import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; -import org.hyperledger.besu.ethereum.worldstate.WorldStatePreimageStorage; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; -import org.hyperledger.besu.evm.internal.EvmConfiguration; -import org.hyperledger.besu.metrics.ObservableMetricsSystem; -import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; -import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; - -import java.math.BigInteger; -import java.time.Clock; -import java.util.OptionalLong; - -import com.google.common.collect.Range; -import org.apache.tuweni.bytes.Bytes; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.mockito.Answers; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; - -@RunWith(MockitoJUnitRunner.class) -public class BesuControllerBuilderTest { - - private BesuControllerBuilder besuControllerBuilder; - private static final NodeKey nodeKey = NodeKeyUtils.generate(); - - @Mock GenesisConfigFile genesisConfigFile; - @Mock GenesisConfigOptions genesisConfigOptions; - @Mock EthashConfigOptions ethashConfigOptions; - @Mock CheckpointConfigOptions checkpointConfigOptions; - @Mock SynchronizerConfiguration synchronizerConfiguration; - @Mock EthProtocolConfiguration ethProtocolConfiguration; - @Mock PrivacyParameters privacyParameters; - @Mock Clock clock; - @Mock StorageProvider storageProvider; - @Mock GasLimitCalculator gasLimitCalculator; - @Mock WorldStateStorage worldStateStorage; - @Mock WorldStateArchive worldStateArchive; - @Mock BonsaiWorldStateKeyValueStorage bonsaiWorldStateStorage; - @Mock WorldStatePreimageStorage worldStatePreimageStorage; - private final TransactionPoolConfiguration poolConfiguration = - TransactionPoolConfiguration.DEFAULT; - private final MiningParameters miningParameters = MiningParameters.newDefault(); - - private final ObservableMetricsSystem observableMetricsSystem = new NoOpMetricsSystem(); - - BigInteger networkId = BigInteger.ONE; - - @Rule public final TemporaryFolder tempDirRule = new TemporaryFolder(); - - @Before - public void setup() { - when(genesisConfigFile.getParentHash()).thenReturn(Hash.ZERO.toHexString()); - when(genesisConfigFile.getDifficulty()).thenReturn(Bytes.of(0).toHexString()); - when(genesisConfigFile.getExtraData()).thenReturn(Bytes.EMPTY.toHexString()); - when(genesisConfigFile.getMixHash()).thenReturn(Hash.ZERO.toHexString()); - when(genesisConfigFile.getNonce()).thenReturn(Long.toHexString(1)); - when(genesisConfigFile.getConfigOptions(any())).thenReturn(genesisConfigOptions); - when(genesisConfigFile.getConfigOptions()).thenReturn(genesisConfigOptions); - when(genesisConfigOptions.getThanosBlockNumber()).thenReturn(OptionalLong.empty()); - when(genesisConfigOptions.getEthashConfigOptions()).thenReturn(ethashConfigOptions); - when(genesisConfigOptions.getCheckpointOptions()).thenReturn(checkpointConfigOptions); - when(ethashConfigOptions.getFixedDifficulty()).thenReturn(OptionalLong.empty()); - when(storageProvider.getStorageBySegmentIdentifier(any())) - .thenReturn(new InMemoryKeyValueStorage()); - when(storageProvider.createBlockchainStorage(any(), any())) - .thenReturn( - new KeyValueStoragePrefixedKeyBlockchainStorage( - new InMemoryKeyValueStorage(), - new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), - new MainnetBlockHeaderFunctions())); - when(synchronizerConfiguration.getDownloaderParallelism()).thenReturn(1); - when(synchronizerConfiguration.getTransactionsParallelism()).thenReturn(1); - when(synchronizerConfiguration.getComputationParallelism()).thenReturn(1); - - when(synchronizerConfiguration.getBlockPropagationRange()).thenReturn(Range.closed(1L, 2L)); - - when(storageProvider.createWorldStateStorage(DataStorageFormat.FOREST)) - .thenReturn(worldStateStorage); - when(storageProvider.createWorldStatePreimageStorage()).thenReturn(worldStatePreimageStorage); - - when(worldStateStorage.isWorldStateAvailable(any(), any())).thenReturn(true); - when(worldStatePreimageStorage.updater()) - .thenReturn(mock(WorldStatePreimageStorage.Updater.class)); - when(worldStateStorage.updater()).thenReturn(mock(WorldStateStorage.Updater.class)); - besuControllerBuilder = spy(visitWithMockConfigs(new MainnetBesuControllerBuilder())); - } - - BesuControllerBuilder visitWithMockConfigs(final BesuControllerBuilder builder) { - return builder - .gasLimitCalculator(gasLimitCalculator) - .genesisConfigFile(genesisConfigFile) - .synchronizerConfiguration(synchronizerConfiguration) - .ethProtocolConfiguration(ethProtocolConfiguration) - .miningParameters(miningParameters) - .metricsSystem(observableMetricsSystem) - .privacyParameters(privacyParameters) - .dataDirectory(tempDirRule.getRoot().toPath()) - .clock(clock) - .transactionPoolConfiguration(poolConfiguration) - .nodeKey(nodeKey) - .storageProvider(storageProvider) - .evmConfiguration(EvmConfiguration.DEFAULT) - .networkConfiguration(NetworkingConfiguration.create()) - .networkId(networkId); - } - - @Test - public void shouldDisablePruningIfBonsaiIsEnabled() { - BonsaiWorldState mockWorldState = mock(BonsaiWorldState.class, Answers.RETURNS_DEEP_STUBS); - doReturn(worldStateArchive) - .when(besuControllerBuilder) - .createWorldStateArchive( - any(WorldStateStorage.class), any(Blockchain.class), any(CachedMerkleTrieLoader.class)); - doReturn(mockWorldState).when(worldStateArchive).getMutable(); - - when(storageProvider.createWorldStateStorage(DataStorageFormat.BONSAI)) - .thenReturn(bonsaiWorldStateStorage); - besuControllerBuilder - .isPruningEnabled(true) - .dataStorageConfiguration( - ImmutableDataStorageConfiguration.builder() - .dataStorageFormat(DataStorageFormat.BONSAI) - .bonsaiMaxLayersToLoad(DataStorageConfiguration.DEFAULT_BONSAI_MAX_LAYERS_TO_LOAD) - .build()); - besuControllerBuilder.build(); - - verify(storageProvider, never()) - .getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.PRUNING_STATE); - } - - @Test - public void shouldUsePruningIfForestIsEnabled() { - besuControllerBuilder - .isPruningEnabled(true) - .pruningConfiguration(new PrunerConfiguration(1, 2)) - .dataStorageConfiguration( - ImmutableDataStorageConfiguration.builder() - .dataStorageFormat(DataStorageFormat.FOREST) - .bonsaiMaxLayersToLoad(DataStorageConfiguration.DEFAULT_BONSAI_MAX_LAYERS_TO_LOAD) - .build()); - besuControllerBuilder.build(); - - verify(storageProvider).getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.PRUNING_STATE); - } -} diff --git a/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerTest.java b/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerTest.java index e6b631a4e88..94c68f7cbe2 100644 --- a/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerTest.java +++ b/besu/src/test/java/org/hyperledger/besu/controller/BesuControllerTest.java @@ -1,27 +1,23 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See - * the License for the + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.controller; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; import org.hyperledger.besu.config.GenesisConfigFile; @@ -30,31 +26,33 @@ import org.hyperledger.besu.ethereum.eth.sync.SyncMode; import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Collections; import java.util.Locale; import java.util.Map; import java.util.OptionalLong; -import com.google.common.io.Resources; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.mockito.Spy; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class BesuControllerTest { - @Spy private GenesisConfigFile genesisConfigFile = GenesisConfigFile.mainnet(); - @Mock private GenesisConfigOptions genesisConfigOptions; - @Mock private QbftConfigOptions qbftConfigOptions; + @Mock GenesisConfigFile genesisConfigFile; + @Mock GenesisConfigOptions genesisConfigOptions; + @Mock QbftConfigOptions qbftConfigOptions; + + @BeforeEach + public void setUp() { + lenient().when(genesisConfigFile.getConfigOptions()).thenReturn(genesisConfigOptions); + } @Test public void missingQbftStartBlock() { mockGenesisConfigForMigration("ibft2", OptionalLong.empty()); assertThatThrownBy( - () -> new BesuController.Builder().fromGenesisConfig(genesisConfigFile, SyncMode.FULL)) + () -> new BesuController.Builder().fromGenesisFile(genesisConfigFile, SyncMode.FULL)) .isInstanceOf(IllegalStateException.class) .hasMessage("Missing QBFT startBlock config in genesis file"); } @@ -63,19 +61,18 @@ public void missingQbftStartBlock() { public void invalidQbftStartBlock() { mockGenesisConfigForMigration("ibft2", OptionalLong.of(-1L)); assertThatThrownBy( - () -> new BesuController.Builder().fromGenesisConfig(genesisConfigFile, SyncMode.FULL)) + () -> new BesuController.Builder().fromGenesisFile(genesisConfigFile, SyncMode.FULL)) .isInstanceOf(IllegalStateException.class) .hasMessage("Invalid QBFT startBlock config in genesis file"); } @Test public void invalidConsensusCombination() { - when(genesisConfigFile.getConfigOptions(any())).thenReturn(genesisConfigOptions); when(genesisConfigOptions.isConsensusMigration()).thenReturn(true); // explicitly not setting isIbft2() for genesisConfigOptions assertThatThrownBy( - () -> new BesuController.Builder().fromGenesisConfig(genesisConfigFile, SyncMode.FULL)) + () -> new BesuController.Builder().fromGenesisFile(genesisConfigFile, SyncMode.FULL)) .isInstanceOf(IllegalStateException.class) .hasMessage( "Invalid genesis migration config. Migration is supported from IBFT (legacy) or IBFT2 to QBFT)"); @@ -87,7 +84,7 @@ public void createConsensusScheduleBesuControllerBuilderWhenMigratingFromIbft2To mockGenesisConfigForMigration("ibft2", OptionalLong.of(qbftStartBlock)); final BesuControllerBuilder besuControllerBuilder = - new BesuController.Builder().fromGenesisConfig(genesisConfigFile, SyncMode.FULL); + new BesuController.Builder().fromGenesisFile(genesisConfigFile, SyncMode.FULL); assertThat(besuControllerBuilder).isInstanceOf(ConsensusScheduleBesuControllerBuilder.class); @@ -103,7 +100,6 @@ public void createConsensusScheduleBesuControllerBuilderWhenMigratingFromIbft2To private void mockGenesisConfigForMigration( final String consensus, final OptionalLong startBlock) { - when(genesisConfigFile.getConfigOptions(any())).thenReturn(genesisConfigOptions); when(genesisConfigOptions.isConsensusMigration()).thenReturn(true); switch (consensus.toLowerCase(Locale.ROOT)) { @@ -121,16 +117,12 @@ private void mockGenesisConfigForMigration( } @Test - public void postMergeCheckpointSyncUsesMergeControllerBuilder() throws IOException { + public void postMergeCheckpointSyncUsesMergeControllerBuilder() { final GenesisConfigFile postMergeGenesisFile = - GenesisConfigFile.fromConfig( - Resources.toString( - Resources.getResource("valid_post_merge_near_head_checkpoint.json"), - StandardCharsets.UTF_8)); + GenesisConfigFile.fromResource("/valid_post_merge_near_head_checkpoint.json"); final BesuControllerBuilder besuControllerBuilder = - new BesuController.Builder() - .fromGenesisConfig(postMergeGenesisFile, Collections.emptyMap(), SyncMode.X_CHECKPOINT); + new BesuController.Builder().fromGenesisFile(postMergeGenesisFile, SyncMode.CHECKPOINT); assertThat(besuControllerBuilder).isInstanceOf(MergeBesuControllerBuilder.class); } @@ -139,15 +131,11 @@ public void postMergeCheckpointSyncUsesMergeControllerBuilder() throws IOExcepti public void postMergeCheckpointSyncWithTotalDifficultyEqualsTTDUsesTransitionControllerBuilder() throws IOException { final GenesisConfigFile mergeAtGenesisFile = - GenesisConfigFile.fromConfig( - Resources.toString( - Resources.getResource( - "invalid_post_merge_checkpoint_total_difficulty_same_as_TTD.json"), - StandardCharsets.UTF_8)); + GenesisConfigFile.fromResource( + "/invalid_post_merge_checkpoint_total_difficulty_same_as_TTD.json"); final BesuControllerBuilder besuControllerBuilder = - new BesuController.Builder() - .fromGenesisConfig(mergeAtGenesisFile, Collections.emptyMap(), SyncMode.X_CHECKPOINT); + new BesuController.Builder().fromGenesisFile(mergeAtGenesisFile, SyncMode.CHECKPOINT); assertThat(besuControllerBuilder).isInstanceOf(TransitionBesuControllerBuilder.class); } @@ -156,7 +144,7 @@ public void postMergeCheckpointSyncWithTotalDifficultyEqualsTTDUsesTransitionCon public void preMergeCheckpointSyncUsesTransitionControllerBuilder() { final BesuControllerBuilder besuControllerBuilder = new BesuController.Builder() - .fromGenesisConfig(genesisConfigFile, Collections.emptyMap(), SyncMode.X_CHECKPOINT); + .fromGenesisFile(GenesisConfigFile.mainnet(), SyncMode.CHECKPOINT); assertThat(besuControllerBuilder).isInstanceOf(TransitionBesuControllerBuilder.class); } @@ -164,8 +152,7 @@ public void preMergeCheckpointSyncUsesTransitionControllerBuilder() { @Test public void nonCheckpointSyncUsesTransitionControllerBuild() { final BesuControllerBuilder besuControllerBuilder = - new BesuController.Builder() - .fromGenesisConfig(genesisConfigFile, Collections.emptyMap(), SyncMode.X_SNAP); + new BesuController.Builder().fromGenesisFile(GenesisConfigFile.mainnet(), SyncMode.SNAP); assertThat(besuControllerBuilder).isInstanceOf(TransitionBesuControllerBuilder.class); } diff --git a/besu/src/test/java/org/hyperledger/besu/controller/CliqueBesuControllerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/controller/CliqueBesuControllerBuilderTest.java new file mode 100644 index 00000000000..e9e93de9b9d --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/controller/CliqueBesuControllerBuilderTest.java @@ -0,0 +1,233 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.controller; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.components.BesuComponent; +import org.hyperledger.besu.config.CheckpointConfigOptions; +import org.hyperledger.besu.config.GenesisConfigFile; +import org.hyperledger.besu.config.GenesisConfigOptions; +import org.hyperledger.besu.config.ImmutableCliqueConfigOptions; +import org.hyperledger.besu.config.TransitionsConfigOptions; +import org.hyperledger.besu.consensus.clique.CliqueBlockHeaderFunctions; +import org.hyperledger.besu.cryptoservices.NodeKey; +import org.hyperledger.besu.cryptoservices.NodeKeyUtils; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.GasLimitCalculator; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockBody; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; +import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; +import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration; +import org.hyperledger.besu.ethereum.storage.StorageProvider; +import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; +import org.hyperledger.besu.ethereum.storage.keyvalue.VariablesKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.ethereum.worldstate.WorldStatePreimageStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.log.LogsBloomFilter; +import org.hyperledger.besu.metrics.ObservableMetricsSystem; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; + +import java.math.BigInteger; +import java.nio.file.Path; +import java.time.Clock; +import java.util.List; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.Range; +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class CliqueBesuControllerBuilderTest { + + private BesuControllerBuilder cliqueBesuControllerBuilder; + + @Mock private GenesisConfigFile genesisConfigFile; + @Mock private GenesisConfigOptions genesisConfigOptions; + @Mock private SynchronizerConfiguration synchronizerConfiguration; + @Mock private EthProtocolConfiguration ethProtocolConfiguration; + @Mock private CheckpointConfigOptions checkpointConfigOptions; + @Mock private PrivacyParameters privacyParameters; + @Mock private Clock clock; + @Mock private StorageProvider storageProvider; + @Mock private GasLimitCalculator gasLimitCalculator; + @Mock private WorldStatePreimageStorage worldStatePreimageStorage; + private static final BigInteger networkId = BigInteger.ONE; + private static final NodeKey nodeKey = NodeKeyUtils.generate(); + private final TransactionPoolConfiguration poolConfiguration = + TransactionPoolConfiguration.DEFAULT; + private final ObservableMetricsSystem observableMetricsSystem = new NoOpMetricsSystem(); + private final ObjectMapper objectMapper = new ObjectMapper(); + private final MiningParameters miningParameters = MiningParameters.newDefault(); + + @TempDir Path tempDir; + + @BeforeEach + public void setup() throws JsonProcessingException { + // Clique Besu controller setup + final ForestWorldStateKeyValueStorage worldStateKeyValueStorage = + mock(ForestWorldStateKeyValueStorage.class); + final WorldStateStorageCoordinator worldStateStorageCoordinator = + new WorldStateStorageCoordinator(worldStateKeyValueStorage); + + lenient().when(genesisConfigFile.getParentHash()).thenReturn(Hash.ZERO.toHexString()); + lenient().when(genesisConfigFile.getDifficulty()).thenReturn(Bytes.of(0).toHexString()); + when(genesisConfigFile.getExtraData()) + .thenReturn( + "0x0000000000000000000000000000000000000000000000000000000000000000b9b81ee349c3807e46bc71aa2632203c5b4620340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + lenient().when(genesisConfigFile.getMixHash()).thenReturn(Hash.ZERO.toHexString()); + lenient().when(genesisConfigFile.getNonce()).thenReturn(Long.toHexString(1)); + lenient().when(genesisConfigFile.getConfigOptions()).thenReturn(genesisConfigOptions); + lenient().when(genesisConfigOptions.getCheckpointOptions()).thenReturn(checkpointConfigOptions); + lenient() + .when(storageProvider.createBlockchainStorage(any(), any(), any())) + .thenReturn( + new KeyValueStoragePrefixedKeyBlockchainStorage( + new InMemoryKeyValueStorage(), + new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), + new MainnetBlockHeaderFunctions(), + false)); + lenient() + .when( + storageProvider.createWorldStateStorageCoordinator( + DataStorageConfiguration.DEFAULT_FOREST_CONFIG)) + .thenReturn(worldStateStorageCoordinator); + lenient().when(worldStateKeyValueStorage.isWorldStateAvailable(any())).thenReturn(true); + lenient() + .when(worldStateKeyValueStorage.updater()) + .thenReturn(mock(ForestWorldStateKeyValueStorage.Updater.class)); + lenient() + .when(worldStatePreimageStorage.updater()) + .thenReturn(mock(WorldStatePreimageStorage.Updater.class)); + lenient() + .when(storageProvider.createWorldStatePreimageStorage()) + .thenReturn(worldStatePreimageStorage); + lenient().when(synchronizerConfiguration.getDownloaderParallelism()).thenReturn(1); + lenient().when(synchronizerConfiguration.getTransactionsParallelism()).thenReturn(1); + lenient().when(synchronizerConfiguration.getComputationParallelism()).thenReturn(1); + + lenient() + .when(synchronizerConfiguration.getBlockPropagationRange()) + .thenReturn(Range.closed(1L, 2L)); + + // clique prepForBuild setup + lenient() + .when(genesisConfigOptions.getCliqueConfigOptions()) + .thenReturn( + ImmutableCliqueConfigOptions.builder() + .epochLength(30) + .createEmptyBlocks(true) + .blockPeriodSeconds(1) + .build()); + + final var jsonTransitions = + (ObjectNode) + objectMapper.readTree( + """ + {"clique": [ + { + "block": 2, + "blockperiodseconds": 2 + } + ]} + """); + + lenient() + .when(genesisConfigOptions.getTransitions()) + .thenReturn(new TransitionsConfigOptions(jsonTransitions)); + + cliqueBesuControllerBuilder = + new CliqueBesuControllerBuilder() + .genesisConfigFile(genesisConfigFile) + .synchronizerConfiguration(synchronizerConfiguration) + .ethProtocolConfiguration(ethProtocolConfiguration) + .networkId(networkId) + .miningParameters(miningParameters) + .metricsSystem(observableMetricsSystem) + .privacyParameters(privacyParameters) + .dataDirectory(tempDir) + .clock(clock) + .transactionPoolConfiguration(poolConfiguration) + .dataStorageConfiguration(DataStorageConfiguration.DEFAULT_FOREST_CONFIG) + .nodeKey(nodeKey) + .storageProvider(storageProvider) + .gasLimitCalculator(gasLimitCalculator) + .evmConfiguration(EvmConfiguration.DEFAULT) + .besuComponent(mock(BesuComponent.class)) + .networkConfiguration(NetworkingConfiguration.create()); + } + + @Test + public void miningParametersBlockPeriodSecondsIsUpdatedOnTransition() { + final var besuController = cliqueBesuControllerBuilder.build(); + final var protocolContext = besuController.getProtocolContext(); + + final BlockHeader header1 = + new BlockHeader( + protocolContext.getBlockchain().getChainHeadHash(), + Hash.EMPTY_TRIE_HASH, + Address.ZERO, + Hash.EMPTY_TRIE_HASH, + Hash.EMPTY_TRIE_HASH, + Hash.EMPTY_TRIE_HASH, + LogsBloomFilter.builder().build(), + Difficulty.ONE, + 1, + 0, + 0, + 0, + Bytes.EMPTY, + Wei.ZERO, + Hash.EMPTY, + 0, + null, + null, + null, + null, + null, + new CliqueBlockHeaderFunctions()); + final Block block1 = new Block(header1, BlockBody.empty()); + + protocolContext.getBlockchain().appendBlock(block1, List.of()); + + assertThat(miningParameters.getBlockPeriodSeconds()).isNotEmpty().hasValue(2); + assertThat(miningParameters.getBlockTxsSelectionMaxTime()).isEqualTo(2000 * 75 / 100); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/controller/ConsensusScheduleBesuControllerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/controller/ConsensusScheduleBesuControllerBuilderTest.java index 4e1d87f4a68..5efeed50b24 100644 --- a/besu/src/test/java/org/hyperledger/besu/controller/ConsensusScheduleBesuControllerBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/controller/ConsensusScheduleBesuControllerBuilderTest.java @@ -1,16 +1,13 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See - * the License for the + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 @@ -20,7 +17,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -34,7 +30,6 @@ import org.hyperledger.besu.ethereum.ConsensusContext; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator; -import org.hyperledger.besu.ethereum.blockcreation.NoopMiningCoordinator; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.MiningParameters; @@ -54,13 +49,13 @@ import java.util.function.BiFunction; import org.assertj.core.api.SoftAssertions; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class ConsensusScheduleBesuControllerBuilderTest { private @Mock BiFunction< NavigableSet>, Optional, ProtocolSchedule> @@ -72,7 +67,7 @@ public class ConsensusScheduleBesuControllerBuilderTest { private @Mock ProtocolSchedule protocolSchedule1; private @Mock ProtocolSchedule protocolSchedule2; private @Mock ProtocolSchedule protocolSchedule3; - private @Mock NoopMiningCoordinator miningCoordinator1; + private @Mock MiningCoordinator miningCoordinator1; private @Mock BftMiningCoordinator miningCoordinator2; @Test @@ -104,11 +99,11 @@ public void mustCreateCombinedProtocolScheduleUsingProtocolSchedulesOrderedByBlo final StubGenesisConfigOptions genesisConfigOptions = new StubGenesisConfigOptions(); genesisConfigOptions.chainId(BigInteger.TEN); - when(genesisConfigFile.getConfigOptions(anyMap())).thenReturn(genesisConfigOptions); final ConsensusScheduleBesuControllerBuilder consensusScheduleBesuControllerBuilder = new ConsensusScheduleBesuControllerBuilder( besuControllerBuilderSchedule, combinedProtocolScheduleFactory); + when(genesisConfigFile.getConfigOptions()).thenReturn(genesisConfigOptions); consensusScheduleBesuControllerBuilder.genesisConfigFile(genesisConfigFile); consensusScheduleBesuControllerBuilder.createProtocolSchedule(); diff --git a/besu/src/test/java/org/hyperledger/besu/controller/IbftBesuControllerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/controller/IbftBesuControllerBuilderTest.java new file mode 100644 index 00000000000..055e136c8ca --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/controller/IbftBesuControllerBuilderTest.java @@ -0,0 +1,73 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.controller; + +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.config.JsonBftConfigOptions; +import org.hyperledger.besu.config.TransitionsConfigOptions; +import org.hyperledger.besu.consensus.common.bft.BftBlockHeaderFunctions; +import org.hyperledger.besu.consensus.common.bft.MutableBftConfigOptions; +import org.hyperledger.besu.consensus.ibft.IbftExtraDataCodec; +import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class IbftBesuControllerBuilderTest extends AbstractBftBesuControllerBuilderTest { + + @Override + public void setupBftGenesisConfigFile() throws JsonProcessingException { + + // Ibft prepForBuild setup + lenient() + .when(genesisConfigOptions.getBftConfigOptions()) + .thenReturn(new MutableBftConfigOptions(JsonBftConfigOptions.DEFAULT)); + + final var jsonTransitions = + (ObjectNode) + objectMapper.readTree( + """ + {"ibft2": [ + { + "block": 2, + "blockperiodseconds": 2 + } + ]} + """); + + lenient() + .when(genesisConfigOptions.getTransitions()) + .thenReturn(new TransitionsConfigOptions(jsonTransitions)); + + when(genesisConfigFile.getExtraData()) + .thenReturn( + "0xf83ea00000000000000000000000000000000000000000000000000000000000000000d594c2ab482b506de561668e07f04547232a72897daf808400000000c0"); + } + + @Override + protected BesuControllerBuilder createBftControllerBuilder() { + return new IbftBesuControllerBuilder(); + } + + @Override + protected BlockHeaderFunctions getBlockHeaderFunctions() { + return BftBlockHeaderFunctions.forOnchainBlock(new IbftExtraDataCodec()); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/controller/MergeBesuControllerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/controller/MergeBesuControllerBuilderTest.java index 33a539f8bc2..f8c00b20ea7 100644 --- a/besu/src/test/java/org/hyperledger/besu/controller/MergeBesuControllerBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/controller/MergeBesuControllerBuilderTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -18,10 +18,12 @@ import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryBlockchain; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import org.hyperledger.besu.components.BesuComponent; import org.hyperledger.besu.config.CheckpointConfigOptions; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.config.GenesisConfigOptions; @@ -51,16 +53,18 @@ import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; import org.hyperledger.besu.ethereum.storage.keyvalue.VariablesKeyValueStorage; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.ethereum.worldstate.WorldStatePreimageStorage; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.ObservableMetricsSystem; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import java.math.BigInteger; +import java.nio.file.Path; import java.time.Clock; import java.util.Collections; import java.util.Optional; @@ -70,16 +74,15 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; import org.mockito.Answers; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class MergeBesuControllerBuilderTest { private MergeBesuControllerBuilder besuControllerBuilder; @@ -98,7 +101,6 @@ public class MergeBesuControllerBuilderTest { @Mock Clock clock; @Mock StorageProvider storageProvider; @Mock GasLimitCalculator gasLimitCalculator; - @Mock WorldStateStorage worldStateStorage; @Mock WorldStatePreimageStorage worldStatePreimageStorage; BigInteger networkId = BigInteger.ONE; @@ -108,46 +110,64 @@ public class MergeBesuControllerBuilderTest { TransactionPoolConfiguration.DEFAULT; private final ObservableMetricsSystem observableMetricsSystem = new NoOpMetricsSystem(); - @Rule public final TemporaryFolder tempDirRule = new TemporaryFolder(); + @TempDir Path tempDir; - @Before + @BeforeEach public void setup() { - when(genesisConfigFile.getParentHash()).thenReturn(Hash.ZERO.toHexString()); - when(genesisConfigFile.getDifficulty()).thenReturn(Bytes.of(0).toHexString()); - when(genesisConfigFile.getExtraData()).thenReturn(Bytes.EMPTY.toHexString()); - when(genesisConfigFile.getMixHash()).thenReturn(Hash.ZERO.toHexString()); - when(genesisConfigFile.getNonce()).thenReturn(Long.toHexString(1)); - when(genesisConfigFile.getConfigOptions(any())).thenReturn(genesisConfigOptions); - when(genesisConfigFile.getConfigOptions()).thenReturn(genesisConfigOptions); - when(genesisConfigOptions.getCheckpointOptions()).thenReturn(checkpointConfigOptions); + + final ForestWorldStateKeyValueStorage worldStateKeyValueStorage = + mock(ForestWorldStateKeyValueStorage.class); + final WorldStateStorageCoordinator worldStateStorageCoordinator = + new WorldStateStorageCoordinator(worldStateKeyValueStorage); + + lenient().when(genesisConfigFile.getParentHash()).thenReturn(Hash.ZERO.toHexString()); + lenient().when(genesisConfigFile.getDifficulty()).thenReturn(Bytes.of(0).toHexString()); + lenient().when(genesisConfigFile.getExtraData()).thenReturn(Bytes.EMPTY.toHexString()); + lenient().when(genesisConfigFile.getMixHash()).thenReturn(Hash.ZERO.toHexString()); + lenient().when(genesisConfigFile.getNonce()).thenReturn(Long.toHexString(1)); + lenient().when(genesisConfigFile.getConfigOptions()).thenReturn(genesisConfigOptions); + lenient().when(genesisConfigOptions.getCheckpointOptions()).thenReturn(checkpointConfigOptions); when(genesisConfigOptions.getTerminalTotalDifficulty()) .thenReturn((Optional.of(UInt256.valueOf(100L)))); when(genesisConfigOptions.getThanosBlockNumber()).thenReturn(OptionalLong.empty()); when(genesisConfigOptions.getTerminalBlockHash()).thenReturn(Optional.of(Hash.ZERO)); - when(genesisConfigOptions.getTerminalBlockNumber()).thenReturn(OptionalLong.of(1L)); - when(storageProvider.createBlockchainStorage(any(), any())) + lenient().when(genesisConfigOptions.getTerminalBlockNumber()).thenReturn(OptionalLong.of(1L)); + lenient() + .when(storageProvider.createBlockchainStorage(any(), any(), any())) .thenReturn( new KeyValueStoragePrefixedKeyBlockchainStorage( new InMemoryKeyValueStorage(), new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), - new MainnetBlockHeaderFunctions())); - when(storageProvider.getStorageBySegmentIdentifier(any())) + new MainnetBlockHeaderFunctions(), + false)); + lenient() + .when(storageProvider.getStorageBySegmentIdentifier(any())) .thenReturn(new InMemoryKeyValueStorage()); - when(synchronizerConfiguration.getDownloaderParallelism()).thenReturn(1); - when(synchronizerConfiguration.getTransactionsParallelism()).thenReturn(1); - when(synchronizerConfiguration.getComputationParallelism()).thenReturn(1); + lenient().when(synchronizerConfiguration.getDownloaderParallelism()).thenReturn(1); + lenient().when(synchronizerConfiguration.getTransactionsParallelism()).thenReturn(1); + lenient().when(synchronizerConfiguration.getComputationParallelism()).thenReturn(1); - when(synchronizerConfiguration.getBlockPropagationRange()).thenReturn(Range.closed(1L, 2L)); + lenient() + .when(synchronizerConfiguration.getBlockPropagationRange()) + .thenReturn(Range.closed(1L, 2L)); - when(storageProvider.createWorldStateStorage(DataStorageFormat.FOREST)) - .thenReturn(worldStateStorage); - when(storageProvider.createWorldStatePreimageStorage()).thenReturn(worldStatePreimageStorage); + lenient() + .when( + storageProvider.createWorldStateStorageCoordinator( + DataStorageConfiguration.DEFAULT_FOREST_CONFIG)) + .thenReturn(worldStateStorageCoordinator); + lenient() + .when(storageProvider.createWorldStatePreimageStorage()) + .thenReturn(worldStatePreimageStorage); - when(worldStateStorage.isWorldStateAvailable(any(), any())).thenReturn(true); - when(worldStatePreimageStorage.updater()) + lenient().when(worldStateKeyValueStorage.isWorldStateAvailable(any())).thenReturn(true); + lenient() + .when(worldStatePreimageStorage.updater()) .thenReturn(mock(WorldStatePreimageStorage.Updater.class)); - when(worldStateStorage.updater()).thenReturn(mock(WorldStateStorage.Updater.class)); - when(miningParameters.getTargetGasLimit()).thenReturn(OptionalLong.empty()); + lenient() + .when(worldStateKeyValueStorage.updater()) + .thenReturn(mock(ForestWorldStateKeyValueStorage.Updater.class)); + lenient().when(miningParameters.getTargetGasLimit()).thenReturn(OptionalLong.empty()); besuControllerBuilder = visitWithMockConfigs(new MergeBesuControllerBuilder()); } @@ -162,13 +182,15 @@ MergeBesuControllerBuilder visitWithMockConfigs(final MergeBesuControllerBuilder .miningParameters(miningParameters) .metricsSystem(observableMetricsSystem) .privacyParameters(privacyParameters) - .dataDirectory(tempDirRule.getRoot().toPath()) + .dataDirectory(tempDir) .clock(clock) .transactionPoolConfiguration(poolConfiguration) + .dataStorageConfiguration(DataStorageConfiguration.DEFAULT_FOREST_CONFIG) .nodeKey(nodeKey) .storageProvider(storageProvider) .evmConfiguration(EvmConfiguration.DEFAULT) .networkConfiguration(NetworkingConfiguration.create()) + .besuComponent(mock(BesuComponent.class)) .networkId(networkId); } diff --git a/besu/src/test/java/org/hyperledger/besu/controller/QbftBesuControllerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/controller/QbftBesuControllerBuilderTest.java index 585f138027e..3b88ae258b7 100644 --- a/besu/src/test/java/org/hyperledger/besu/controller/QbftBesuControllerBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/controller/QbftBesuControllerBuilderTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -16,144 +16,79 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import org.hyperledger.besu.config.CheckpointConfigOptions; -import org.hyperledger.besu.config.GenesisConfigFile; -import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.config.JsonQbftConfigOptions; import org.hyperledger.besu.config.TransitionsConfigOptions; +import org.hyperledger.besu.consensus.common.bft.BftBlockHeaderFunctions; import org.hyperledger.besu.consensus.common.bft.BftContext; import org.hyperledger.besu.consensus.common.validator.ValidatorProvider; import org.hyperledger.besu.consensus.qbft.MutableQbftConfigOptions; import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec; import org.hyperledger.besu.consensus.qbft.validator.ForkingValidatorProvider; -import org.hyperledger.besu.cryptoservices.NodeKey; -import org.hyperledger.besu.cryptoservices.NodeKeyUtils; import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.GasLimitCalculator; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; +import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; import org.hyperledger.besu.ethereum.core.MiningParameters; -import org.hyperledger.besu.ethereum.core.PrivacyParameters; -import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; -import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; -import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; -import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; -import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration; -import org.hyperledger.besu.ethereum.storage.StorageProvider; -import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; -import org.hyperledger.besu.ethereum.storage.keyvalue.VariablesKeyValueStorage; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; -import org.hyperledger.besu.ethereum.worldstate.WorldStatePreimageStorage; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; -import org.hyperledger.besu.evm.internal.EvmConfiguration; -import org.hyperledger.besu.metrics.ObservableMetricsSystem; -import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; -import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; - -import java.math.BigInteger; -import java.time.Clock; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; + import java.util.List; -import com.google.common.collect.Range; -import org.apache.tuweni.bytes.Bytes; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; - -@RunWith(MockitoJUnitRunner.class) -public class QbftBesuControllerBuilderTest { - - private BesuControllerBuilder qbftBesuControllerBuilder; - - @Mock private GenesisConfigFile genesisConfigFile; - @Mock private GenesisConfigOptions genesisConfigOptions; - @Mock private SynchronizerConfiguration synchronizerConfiguration; - @Mock private EthProtocolConfiguration ethProtocolConfiguration; - @Mock CheckpointConfigOptions checkpointConfigOptions; - @Mock private MiningParameters miningParameters; - @Mock private PrivacyParameters privacyParameters; - @Mock private Clock clock; - @Mock private StorageProvider storageProvider; - @Mock private GasLimitCalculator gasLimitCalculator; - @Mock private WorldStateStorage worldStateStorage; - @Mock private WorldStatePreimageStorage worldStatePreimageStorage; - private static final BigInteger networkId = BigInteger.ONE; - private static final NodeKey nodeKey = NodeKeyUtils.generate(); - private final TransactionPoolConfiguration poolConfiguration = - TransactionPoolConfiguration.DEFAULT; - private final ObservableMetricsSystem observableMetricsSystem = new NoOpMetricsSystem(); - - @Rule public final TemporaryFolder tempDirRule = new TemporaryFolder(); - - @Before - public void setup() { - // besu controller setup - when(genesisConfigFile.getParentHash()).thenReturn(Hash.ZERO.toHexString()); - when(genesisConfigFile.getDifficulty()).thenReturn(Bytes.of(0).toHexString()); - when(genesisConfigFile.getExtraData()).thenReturn(Bytes.EMPTY.toHexString()); - when(genesisConfigFile.getMixHash()).thenReturn(Hash.ZERO.toHexString()); - when(genesisConfigFile.getNonce()).thenReturn(Long.toHexString(1)); - when(genesisConfigFile.getConfigOptions(any())).thenReturn(genesisConfigOptions); - when(genesisConfigFile.getConfigOptions()).thenReturn(genesisConfigOptions); - when(genesisConfigOptions.getCheckpointOptions()).thenReturn(checkpointConfigOptions); - when(storageProvider.createBlockchainStorage(any(), any())) - .thenReturn( - new KeyValueStoragePrefixedKeyBlockchainStorage( - new InMemoryKeyValueStorage(), - new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), - new MainnetBlockHeaderFunctions())); - when(storageProvider.createWorldStateStorage(DataStorageFormat.FOREST)) - .thenReturn(worldStateStorage); - when(worldStateStorage.isWorldStateAvailable(any(), any())).thenReturn(true); - when(worldStateStorage.updater()).thenReturn(mock(WorldStateStorage.Updater.class)); - when(worldStatePreimageStorage.updater()) - .thenReturn(mock(WorldStatePreimageStorage.Updater.class)); - when(storageProvider.createWorldStatePreimageStorage()).thenReturn(worldStatePreimageStorage); - when(synchronizerConfiguration.getDownloaderParallelism()).thenReturn(1); - when(synchronizerConfiguration.getTransactionsParallelism()).thenReturn(1); - when(synchronizerConfiguration.getComputationParallelism()).thenReturn(1); - - when(synchronizerConfiguration.getBlockPropagationRange()).thenReturn(Range.closed(1L, 2L)); +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class QbftBesuControllerBuilderTest extends AbstractBftBesuControllerBuilderTest { + + @Override + public void setupBftGenesisConfigFile() throws JsonProcessingException { // qbft prepForBuild setup - when(genesisConfigOptions.getQbftConfigOptions()) + lenient() + .when(genesisConfigOptions.getQbftConfigOptions()) .thenReturn(new MutableQbftConfigOptions(JsonQbftConfigOptions.DEFAULT)); - when(genesisConfigOptions.getTransitions()).thenReturn(mock(TransitionsConfigOptions.class)); - when(genesisConfigFile.getExtraData()) + + final var jsonTransitions = + (ObjectNode) + objectMapper.readTree( + """ + {"qbft": [ + { + "block": 2, + "blockperiodseconds": 2 + } + ]} + """); + + lenient() + .when(genesisConfigOptions.getTransitions()) + .thenReturn(new TransitionsConfigOptions(jsonTransitions)); + + lenient() + .when(genesisConfigFile.getExtraData()) .thenReturn( QbftExtraDataCodec.createGenesisExtraDataString(List.of(Address.fromHexString("1")))); + } + + @Override + protected BesuControllerBuilder createBftControllerBuilder() { + return new QbftBesuControllerBuilder(); + } - qbftBesuControllerBuilder = - new QbftBesuControllerBuilder() - .genesisConfigFile(genesisConfigFile) - .synchronizerConfiguration(synchronizerConfiguration) - .ethProtocolConfiguration(ethProtocolConfiguration) - .networkId(networkId) - .miningParameters(miningParameters) - .metricsSystem(observableMetricsSystem) - .privacyParameters(privacyParameters) - .dataDirectory(tempDirRule.getRoot().toPath()) - .clock(clock) - .transactionPoolConfiguration(poolConfiguration) - .nodeKey(nodeKey) - .storageProvider(storageProvider) - .gasLimitCalculator(gasLimitCalculator) - .evmConfiguration(EvmConfiguration.DEFAULT) - .networkConfiguration(NetworkingConfiguration.create()); + @Override + protected BlockHeaderFunctions getBlockHeaderFunctions() { + return BftBlockHeaderFunctions.forOnchainBlock(new QbftExtraDataCodec()); } @Test public void forkingValidatorProviderIsAvailableOnBftContext() { - final BesuController besuController = qbftBesuControllerBuilder.build(); + final BesuController besuController = bftBesuControllerBuilder.build(); final ValidatorProvider validatorProvider = besuController @@ -166,10 +101,13 @@ public void forkingValidatorProviderIsAvailableOnBftContext() { @Test public void missingTransactionValidatorProviderThrowsError() { final ProtocolContext protocolContext = mock(ProtocolContext.class); + final ProtocolSchedule protocolSchedule = mock(ProtocolSchedule.class); when(protocolContext.getBlockchain()).thenReturn(mock(MutableBlockchain.class)); assertThatThrownBy( - () -> qbftBesuControllerBuilder.createAdditionalJsonRpcMethodFactory(protocolContext)) + () -> + bftBesuControllerBuilder.createAdditionalJsonRpcMethodFactory( + protocolContext, protocolSchedule, MiningParameters.newDefault())) .isInstanceOf(NullPointerException.class) .hasMessage("transactionValidatorProvider should have been initialised"); } diff --git a/besu/src/test/java/org/hyperledger/besu/controller/TransitionControllerBuilderTest.java b/besu/src/test/java/org/hyperledger/besu/controller/TransitionControllerBuilderTest.java index ab914ad4f5f..40d72e4ed19 100644 --- a/besu/src/test/java/org/hyperledger/besu/controller/TransitionControllerBuilderTest.java +++ b/besu/src/test/java/org/hyperledger/besu/controller/TransitionControllerBuilderTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -16,6 +16,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -53,18 +54,18 @@ import java.util.Optional; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Answers; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; /** * We only bother testing transitionControllerBuilder for PoW and Clique since those are the only * network types that are transitioning to PoS. */ -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class TransitionControllerBuilderTest { @Mock ProtocolSchedule preMergeProtocolSchedule; @@ -87,7 +88,7 @@ public class TransitionControllerBuilderTest { TransitionProtocolSchedule transitionProtocolSchedule; - @Before + @BeforeEach public void setup() { transitionProtocolSchedule = spy( @@ -99,13 +100,22 @@ public void setup() { powBuilder.genesisConfigFile(GenesisConfigFile.DEFAULT); postMergeBuilder.genesisConfigFile(GenesisConfigFile.DEFAULT); postMergeBuilder.storageProvider(storageProvider); - when(protocolContext.getBlockchain()).thenReturn(mockBlockchain); - when(transitionProtocolSchedule.getPostMergeSchedule()).thenReturn(postMergeProtocolSchedule); - when(transitionProtocolSchedule.getPreMergeSchedule()).thenReturn(preMergeProtocolSchedule); - when(protocolContext.getConsensusContext(CliqueContext.class)) + lenient().when(protocolContext.getBlockchain()).thenReturn(mockBlockchain); + lenient() + .when(transitionProtocolSchedule.getPostMergeSchedule()) + .thenReturn(postMergeProtocolSchedule); + lenient() + .when(transitionProtocolSchedule.getPreMergeSchedule()) + .thenReturn(preMergeProtocolSchedule); + lenient() + .when(protocolContext.getConsensusContext(CliqueContext.class)) .thenReturn(mock(CliqueContext.class)); - when(protocolContext.getConsensusContext(PostMergeContext.class)).thenReturn(mergeContext); - when(protocolContext.getConsensusContext(MergeContext.class)).thenReturn(mergeContext); + lenient() + .when(protocolContext.getConsensusContext(PostMergeContext.class)) + .thenReturn(mergeContext); + lenient() + .when(protocolContext.getConsensusContext(MergeContext.class)) + .thenReturn(mergeContext); when(ethProtocolManager.ethContext().getScheduler()) .thenReturn(new DeterministicEthScheduler()); miningParameters = MiningParameters.newDefault(); diff --git a/besu/src/test/java/org/hyperledger/besu/ethereum/p2p/config/DefaultDiscoveryConfiguration.java b/besu/src/test/java/org/hyperledger/besu/ethereum/p2p/config/DefaultDiscoveryConfiguration.java index a5b4f965001..2c3b33bb37a 100644 --- a/besu/src/test/java/org/hyperledger/besu/ethereum/p2p/config/DefaultDiscoveryConfiguration.java +++ b/besu/src/test/java/org/hyperledger/besu/ethereum/p2p/config/DefaultDiscoveryConfiguration.java @@ -24,8 +24,8 @@ import java.util.stream.Stream; public class DefaultDiscoveryConfiguration { - public static final String GOERLI_DISCOVERY_URL = - "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@all.goerli.ethdisco.net"; + public static final String SEPOLIA_DISCOVERY_URL = + "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@all.sepolia.ethdisco.net"; public static final String MAINNET_DISCOVERY_URL = "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@all.mainnet.ethdisco.net"; @@ -40,22 +40,6 @@ public class DefaultDiscoveryConfiguration { ) .map(EnodeURLImpl::fromString) .collect(toList())); - public static final List GOERLI_BOOTSTRAP_NODES = - Collections.unmodifiableList( - Stream.of( - "enode://011f758e6552d105183b1761c5e2dea0111bc20fd5f6422bc7f91e0fabbec9a6595caf6239b37feb773dddd3f87240d99d859431891e4a642cf2a0a9e6cbb98a@51.141.78.53:30303", - "enode://176b9417f511d05b6b2cf3e34b756cf0a7096b3094572a8f6ef4cdcb9d1f9d00683bf0f83347eebdf3b81c3521c2332086d9592802230bf528eaf606a1d9677b@13.93.54.137:30303", - "enode://46add44b9f13965f7b9875ac6b85f016f341012d84f975377573800a863526f4da19ae2c620ec73d11591fa9510e992ecc03ad0751f53cc02f7c7ed6d55c7291@94.237.54.114:30313", - "enode://b5948a2d3e9d486c4d75bf32713221c2bd6cf86463302339299bd227dc2e276cd5a1c7ca4f43a0e9122fe9af884efed563bd2a1fd28661f3b5f5ad7bf1de5949@18.218.250.66:30303", - - // Ethereum Foundation bootnode - "enode://a61215641fb8714a373c80edbfa0ea8878243193f57c96eeb44d0bc019ef295abd4e044fd619bfc4c59731a73fb79afe84e9ab6da0c743ceb479cbb6d263fa91@3.11.147.67:30303", - - // Goerli Initiative bootnodes - "enode://d4f764a48ec2a8ecf883735776fdefe0a3949eb0ca476bd7bc8d0954a9defe8fea15ae5da7d40b5d2d59ce9524a99daedadf6da6283fca492cc80b53689fb3b3@46.4.99.122:32109", - "enode://d2b720352e8216c9efc470091aa91ddafc53e222b32780f505c817ceef69e01d5b0b0797b69db254c586f493872352f5a022b4d8479a00fc92ec55f9ad46a27e@88.99.70.182:30303") - .map(EnodeURLImpl::fromString) - .collect(toList())); public static final List SEPOLIA_BOOTSTRAP_NODES = Collections.unmodifiableList( Stream.of( diff --git a/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java b/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java index 6245a8a6ba8..478ce4ebc42 100644 --- a/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java +++ b/besu/src/test/java/org/hyperledger/besu/services/BesuEventsImplTest.java @@ -17,6 +17,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -26,6 +27,8 @@ import org.hyperledger.besu.datatypes.Transaction; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockCause; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.DefaultBlockchain; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; @@ -33,6 +36,7 @@ import org.hyperledger.besu.ethereum.core.BlockDataGenerator; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.core.TransactionTestFixture; @@ -40,7 +44,6 @@ import org.hyperledger.besu.ethereum.eth.manager.EthMessages; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; import org.hyperledger.besu.ethereum.eth.manager.EthPeers; -import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.sync.BlockBroadcaster; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; @@ -63,7 +66,9 @@ import org.hyperledger.besu.plugin.data.PropagatedBlockContext; import org.hyperledger.besu.plugin.data.SyncStatus; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; +import org.hyperledger.besu.testutil.DeterministicEthScheduler; import org.hyperledger.besu.testutil.TestClock; +import org.hyperledger.besu.util.number.ByteUnits; import java.math.BigInteger; import java.time.ZoneId; @@ -76,15 +81,15 @@ import com.google.common.base.Supplier; import com.google.common.base.Suppliers; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Answers; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; @SuppressWarnings("unchecked") -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class BesuEventsImplTest { private static final Supplier SIGNATURE_ALGORITHM = @@ -99,7 +104,6 @@ public class BesuEventsImplTest { @Mock private EthPeers mockEthPeers; @Mock private EthContext mockEthContext; @Mock private EthMessages mockEthMessages; - @Mock private EthScheduler mockEthScheduler; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private TransactionValidatorFactory mockTransactionValidatorFactory; @@ -112,8 +116,9 @@ public class BesuEventsImplTest { private BesuEventsImpl serviceImpl; private MutableBlockchain blockchain; private final BlockDataGenerator gen = new BlockDataGenerator(); + private final BadBlockManager badBlockManager = new BadBlockManager(); - @Before + @BeforeEach public void setUp() { blockchain = DefaultBlockchain.createMutable( @@ -121,28 +126,36 @@ public void setUp() { new KeyValueStoragePrefixedKeyBlockchainStorage( new InMemoryKeyValueStorage(), new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), - new MainnetBlockHeaderFunctions()), + new MainnetBlockHeaderFunctions(), + false), new NoOpMetricsSystem(), 0); when(mockEthContext.getEthMessages()).thenReturn(mockEthMessages); when(mockEthContext.getEthPeers()).thenReturn(mockEthPeers); - when(mockEthContext.getScheduler()).thenReturn(mockEthScheduler); - when(mockEthPeers.streamAvailablePeers()).thenAnswer(z -> Stream.empty()); + when(mockEthContext.getScheduler()).thenReturn(new DeterministicEthScheduler()); + lenient().when(mockEthPeers.streamAvailablePeers()).thenAnswer(z -> Stream.empty()); when(mockProtocolContext.getBlockchain()).thenReturn(blockchain); - when(mockProtocolContext.getWorldStateArchive()).thenReturn(mockWorldStateArchive); - when(mockProtocolSchedule.getByBlockHeader(any())).thenReturn(mockProtocolSpec); - when(mockProtocolSpec.getTransactionValidatorFactory()) + lenient().when(mockProtocolContext.getWorldStateArchive()).thenReturn(mockWorldStateArchive); + lenient().when(mockProtocolSchedule.getByBlockHeader(any())).thenReturn(mockProtocolSpec); + lenient() + .when(mockProtocolSpec.getTransactionValidatorFactory()) .thenReturn(mockTransactionValidatorFactory); - when(mockProtocolSpec.getFeeMarket()).thenReturn(FeeMarket.london(0L)); - when(mockTransactionValidatorFactory.get().validate(any(), any(Optional.class), any())) + lenient().when(mockProtocolSpec.getFeeMarket()).thenReturn(FeeMarket.london(0L)); + lenient() + .when( + mockTransactionValidatorFactory + .get() + .validate(any(), any(Optional.class), any(Optional.class), any())) .thenReturn(ValidationResult.valid()); - when(mockTransactionValidatorFactory.get().validateForSender(any(), any(), any())) + lenient() + .when(mockTransactionValidatorFactory.get().validateForSender(any(), any(), any())) .thenReturn(ValidationResult.valid()); - when(mockWorldStateArchive.getMutable(any(), anyBoolean())) + lenient() + .when(mockWorldStateArchive.getMutable(any(), anyBoolean())) .thenReturn(Optional.of(mockWorldState)); - blockBroadcaster = new BlockBroadcaster(mockEthContext); + blockBroadcaster = new BlockBroadcaster(mockEthContext, 10 * ByteUnits.MEGABYTE); syncState = new SyncState(blockchain, mockEthPeers); TransactionPoolConfiguration txPoolConfig = ImmutableTransactionPoolConfiguration.builder() @@ -159,10 +172,12 @@ public void setUp() { new NoOpMetricsSystem(), syncState, txPoolConfig, - null, - new BlobCache()); + new BlobCache(), + MiningParameters.newDefault()); - serviceImpl = new BesuEventsImpl(blockchain, blockBroadcaster, transactionPool, syncState); + serviceImpl = + new BesuEventsImpl( + blockchain, blockBroadcaster, transactionPool, syncState, badBlockManager); } @Test @@ -495,6 +510,85 @@ public void logEventDoesNotFireAfterUnsubscribe() { assertThat(result).isEmpty(); } + @Test + public void badBlockEventFiresAfterSubscribe_badBlockAdded() { + // Track bad block events + final AtomicReference badBlockResult = + new AtomicReference<>(); + final AtomicReference badBlockCauseResult = + new AtomicReference<>(); + + serviceImpl.addBadBlockListener( + (badBlock, cause) -> { + badBlockResult.set(badBlock); + badBlockCauseResult.set(cause); + }); + + // Add bad block + final BadBlockCause blockCause = BadBlockCause.fromValidationFailure("failed"); + final Block block = gen.block(new BlockDataGenerator.BlockOptions()); + badBlockManager.addBadBlock(block, blockCause); + + // Check we caught the bad block + assertThat(badBlockResult.get()).isEqualTo(block.getHeader()); + assertThat(badBlockCauseResult.get()).isEqualTo(blockCause); + } + + @Test + public void badBlockEventFiresAfterSubscribe_badBlockHeaderAdded() { + // Track bad block events + final AtomicReference badBlockResult = + new AtomicReference<>(); + final AtomicReference badBlockCauseResult = + new AtomicReference<>(); + + serviceImpl.addBadBlockListener( + (badBlock, cause) -> { + badBlockResult.set(badBlock); + badBlockCauseResult.set(cause); + }); + + // Add bad block header + final BadBlockCause cause = BadBlockCause.fromValidationFailure("oops"); + final Block badBlock = gen.block(new BlockDataGenerator.BlockOptions()); + badBlockManager.addBadHeader(badBlock.getHeader(), cause); + + // Check we caught the bad block + assertThat(badBlockResult.get()).isEqualTo(badBlock.getHeader()); + assertThat(badBlockCauseResult.get()).isEqualTo(cause); + } + + @Test + public void badBlockEventDoesNotFireAfterUnsubscribe() { + // Track bad block events + final AtomicReference badBlockResult = + new AtomicReference<>(); + final AtomicReference badBlockCauseResult = + new AtomicReference<>(); + + final long listenerId = + serviceImpl.addBadBlockListener( + (badBlock, cause) -> { + badBlockResult.set(badBlock); + badBlockCauseResult.set(cause); + }); + // Unsubscribe + serviceImpl.removeBadBlockListener(listenerId); + + // Add bad block + final BadBlockCause blockCause = BadBlockCause.fromValidationFailure("failed"); + final Block block = gen.block(new BlockDataGenerator.BlockOptions()); + badBlockManager.addBadBlock(block, blockCause); + // Add bad block header + final BadBlockCause headerCause = BadBlockCause.fromValidationFailure("oops"); + final Block headerBlock = gen.block(new BlockDataGenerator.BlockOptions()); + badBlockManager.addBadHeader(headerBlock.getHeader(), headerCause); + + // Check we did not process any events + assertThat(badBlockResult.get()).isNull(); + assertThat(badBlockCauseResult.get()).isNull(); + } + private Block generateBlock() { final BlockBody body = new BlockBody(Collections.emptyList(), Collections.emptyList()); return new Block(new BlockHeaderTestFixture().buildHeader(), body); diff --git a/besu/src/test/java/org/hyperledger/besu/services/PicoCLIOptionsImplTest.java b/besu/src/test/java/org/hyperledger/besu/services/PicoCLIOptionsImplTest.java index ac7e7fb9593..52f37f769a9 100644 --- a/besu/src/test/java/org/hyperledger/besu/services/PicoCLIOptionsImplTest.java +++ b/besu/src/test/java/org/hyperledger/besu/services/PicoCLIOptionsImplTest.java @@ -17,16 +17,16 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; import picocli.CommandLine; import picocli.CommandLine.Command; import picocli.CommandLine.Option; import picocli.CommandLine.UnmatchedArgumentException; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class PicoCLIOptionsImplTest { @Command @@ -46,7 +46,7 @@ static final class MixinOptions { private CommandLine commandLine; private PicoCLIOptionsImpl serviceImpl; - @Before + @BeforeEach public void setUp() { command = new SimpleCommand(); mixin = new MixinOptions(); diff --git a/besu/src/test/java/org/hyperledger/besu/services/RlpConverterServiceImplTest.java b/besu/src/test/java/org/hyperledger/besu/services/RlpConverterServiceImplTest.java new file mode 100644 index 00000000000..7a3bdbbfe6b --- /dev/null +++ b/besu/src/test/java/org/hyperledger/besu/services/RlpConverterServiceImplTest.java @@ -0,0 +1,54 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.services; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.datatypes.BlobGas; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.core.ProtocolScheduleFixture; +import org.hyperledger.besu.plugin.data.BlockHeader; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; + +public class RlpConverterServiceImplTest { + + @Test + public void testBuildRlpFromHeader() { + // Arrange + RlpConverterServiceImpl rlpConverterServiceImpl = + new RlpConverterServiceImpl(ProtocolScheduleFixture.MAINNET); + // header with cancun fields + BlockHeader header = + new BlockHeaderTestFixture() + .timestamp(1710338135 + 1) + .baseFeePerGas(Wei.of(1000)) + .requestsRoot(Hash.ZERO) + .withdrawalsRoot(Hash.ZERO) + .blobGasUsed(500L) + .excessBlobGas(BlobGas.of(500L)) + .buildHeader(); + + Bytes rlpBytes = rlpConverterServiceImpl.buildRlpFromHeader(header); + BlockHeader deserialized = rlpConverterServiceImpl.buildHeaderFromRlp(rlpBytes); + // Assert + assertThat(header).isEqualTo(deserialized); + assertThat(header.getBlobGasUsed()).isEqualTo(deserialized.getBlobGasUsed()); + assertThat(header.getExcessBlobGas()).isEqualTo(deserialized.getExcessBlobGas()); + } +} diff --git a/besu/src/test/java/org/hyperledger/besu/services/TraceServiceImplTest.java b/besu/src/test/java/org/hyperledger/besu/services/TraceServiceImplTest.java index 30d51cfa737..1e734f62312 100644 --- a/besu/src/test/java/org/hyperledger/besu/services/TraceServiceImplTest.java +++ b/besu/src/test/java/org/hyperledger/besu/services/TraceServiceImplTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,31 +15,47 @@ package org.hyperledger.besu.services; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Transaction; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; +import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.evm.log.Log; import org.hyperledger.besu.evm.worldstate.WorldView; +import org.hyperledger.besu.plugin.data.BlockBody; +import org.hyperledger.besu.plugin.data.BlockHeader; import org.hyperledger.besu.plugin.data.BlockTraceResult; import org.hyperledger.besu.plugin.data.TransactionTraceResult; import org.hyperledger.besu.plugin.services.TraceService; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.plugin.services.tracer.BlockAwareOperationTracer; +import java.util.HashSet; import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.LongStream; import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) class TraceServiceImplTest { TraceService traceService; @@ -59,7 +75,12 @@ public void setup() { blockchainSetupUtil.importAllBlocks(); blockchain = blockchainSetupUtil.getBlockchain(); worldStateArchive = blockchainSetupUtil.getWorldArchive(); - blockchainQueries = new BlockchainQueries(blockchain, worldStateArchive); + blockchainQueries = + new BlockchainQueries( + blockchainSetupUtil.getProtocolSchedule(), + blockchain, + worldStateArchive, + MiningParameters.newDefault()); traceService = new TraceServiceImpl(blockchainQueries, blockchainSetupUtil.getProtocolSchedule()); } @@ -73,19 +94,41 @@ void shouldRetrieveStateUpdatePostTracingForOneBlock() { final long persistedNonceForAccount = worldStateArchive.getMutable().get(addressToVerify).getNonce(); + final long blockNumber = 2; + + final BlockAwareOperationTracer opTracer = mock(BlockAwareOperationTracer.class); + traceService.trace( - 2, - 2, + blockNumber, + blockNumber, worldState -> { assertThat(worldState.get(addressToVerify).getNonce()).isEqualTo(1); }, worldState -> { assertThat(worldState.get(addressToVerify).getNonce()).isEqualTo(2); }, - BlockAwareOperationTracer.NO_TRACING); + opTracer); assertThat(worldStateArchive.getMutable().get(addressToVerify).getNonce()) .isEqualTo(persistedNonceForAccount); + + final Block tracedBlock = blockchain.getBlockByNumber(blockNumber).get(); + + verify(opTracer).traceStartBlock(tracedBlock.getHeader(), tracedBlock.getBody()); + + tracedBlock + .getBody() + .getTransactions() + .forEach( + tx -> { + verify(opTracer).tracePrepareTransaction(any(), eq(tx)); + verify(opTracer).traceStartTransaction(any(), eq(tx)); + verify(opTracer) + .traceEndTransaction( + any(), eq(tx), anyBoolean(), any(), any(), anyLong(), any(), anyLong()); + }); + + verify(opTracer).traceEndBlock(tracedBlock.getHeader(), tracedBlock.getBody()); } @Test @@ -96,9 +139,13 @@ void shouldRetrieveStateUpdatePostTracingForAllBlocks() { final long persistedNonceForAccount = worldStateArchive.getMutable().get(addressToVerify).getNonce(); + final long startBlock = 1; + final long endBlock = 32; + final BlockAwareOperationTracer opTracer = mock(BlockAwareOperationTracer.class); + traceService.trace( - 0, - 32, + startBlock, + endBlock, worldState -> { assertThat(worldState.get(addressToVerify).getNonce()).isEqualTo(0); }, @@ -106,10 +153,38 @@ void shouldRetrieveStateUpdatePostTracingForAllBlocks() { assertThat(worldState.get(addressToVerify).getNonce()) .isEqualTo(persistedNonceForAccount); }, - BlockAwareOperationTracer.NO_TRACING); + opTracer); assertThat(worldStateArchive.getMutable().get(addressToVerify).getNonce()) .isEqualTo(persistedNonceForAccount); + + LongStream.rangeClosed(startBlock, endBlock) + .mapToObj(blockchain::getBlockByNumber) + .map(Optional::get) + .forEach( + tracedBlock -> { + verify(opTracer).traceStartBlock(tracedBlock.getHeader(), tracedBlock.getBody()); + tracedBlock + .getBody() + .getTransactions() + .forEach( + tx -> { + verify(opTracer).tracePrepareTransaction(any(), eq(tx)); + verify(opTracer).traceStartTransaction(any(), eq(tx)); + verify(opTracer) + .traceEndTransaction( + any(), + eq(tx), + anyBoolean(), + any(), + any(), + anyLong(), + any(), + anyLong()); + }); + + verify(opTracer).traceEndBlock(tracedBlock.getHeader(), tracedBlock.getBody()); + }); } @Test @@ -154,6 +229,7 @@ void shouldReturnTheCorrectWorldViewForTxStartEnd() { assertThat(txStartEndTracer.txEndStatus).isTrue(); assertThat(txStartEndTracer.txEndOutput).isEqualTo(Bytes.fromHexString("0x")); assertThat(txStartEndTracer.txEndGasUsed).isEqualTo(24303); + assertThat(txStartEndTracer.txEndSelfDestructs).isEmpty(); assertThat(txStartEndTracer.txEndTimeNs).isNotNull(); assertThat(txStartEndTracer.txEndLogs).isNotEmpty(); @@ -195,10 +271,19 @@ private static class TxStartEndTracer implements BlockAwareOperationTracer { public Bytes txEndOutput; public List txEndLogs; public long txEndGasUsed; + public Set
txEndSelfDestructs; public Long txEndTimeNs; + private final Set traceStartTxCalled = new HashSet<>(); + private final Set traceEndTxCalled = new HashSet<>(); + private final Set traceStartBlockCalled = new HashSet<>(); + private final Set traceEndBlockCalled = new HashSet<>(); + @Override public void traceStartTransaction(final WorldView worldView, final Transaction transaction) { + if (!traceStartTxCalled.add(transaction)) { + fail("traceStartTransaction already called for tx " + transaction); + } txStartWorldView = worldView; txStartTransaction = transaction; } @@ -211,14 +296,33 @@ public void traceEndTransaction( final Bytes output, final List logs, final long gasUsed, + final Set
selfDestructs, final long timeNs) { + if (!traceEndTxCalled.add(transaction)) { + fail("traceEndTransaction already called for tx " + transaction); + } txEndWorldView = worldView; txEndTransaction = transaction; txEndStatus = status; txEndOutput = output; txEndLogs = logs; txEndGasUsed = gasUsed; + txEndSelfDestructs = selfDestructs; txEndTimeNs = timeNs; } + + @Override + public void traceStartBlock(final BlockHeader blockHeader, final BlockBody blockBody) { + if (!traceStartBlockCalled.add(blockHeader.getBlockHash())) { + fail("traceStartBlock already called for block " + blockHeader); + } + } + + @Override + public void traceEndBlock(final BlockHeader blockHeader, final BlockBody blockBody) { + if (!traceEndBlockCalled.add(blockHeader.getBlockHash())) { + fail("traceEndBlock already called for block " + blockHeader); + } + } } } diff --git a/besu/src/test/java/org/hyperledger/besu/util/LocalPermissioningConfigurationValidatorTest.java b/besu/src/test/java/org/hyperledger/besu/util/LocalPermissioningConfigurationValidatorTest.java index a5b3bb90d99..6a39bd3b6b1 100644 --- a/besu/src/test/java/org/hyperledger/besu/util/LocalPermissioningConfigurationValidatorTest.java +++ b/besu/src/test/java/org/hyperledger/besu/util/LocalPermissioningConfigurationValidatorTest.java @@ -67,7 +67,7 @@ public void sepoliaWithNodesAllowlistOptionWhichDoesIncludeRopstenBootnodesMustN true, toml.toAbsolutePath().toString()); - final List enodeURIs = ethNetworkConfig.getBootNodes(); + final List enodeURIs = ethNetworkConfig.bootNodes(); PermissioningConfigurationValidator.areAllNodesInAllowlist( enodeURIs, permissioningConfiguration); } @@ -91,7 +91,7 @@ public void nodesAllowlistOptionWhichDoesNotIncludeBootnodesMustError() throws E toml.toAbsolutePath().toString()); try { - final List enodeURIs = ethNetworkConfig.getBootNodes(); + final List enodeURIs = ethNetworkConfig.bootNodes(); PermissioningConfigurationValidator.areAllNodesInAllowlist( enodeURIs, permissioningConfiguration); fail("expected exception because sepolia bootnodes are not in node-allowlist"); @@ -232,7 +232,6 @@ public void nodeAllowlistCheckShouldNotWorkWithUnknownHostnameWhenOnlyDnsEnabled true, toml.toAbsolutePath().toString())) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining( - "Invalid IP address (or DNS query resolved an invalid IP). --Xdns-enabled is true but --Xdns-update-enabled flag is false."); + .hasMessageContaining("Invalid IP address"); } } diff --git a/besu/src/test/resources/complete_config.toml b/besu/src/test/resources/complete_config.toml index 3243b056135..8ae2105efd6 100644 --- a/besu/src/test/resources/complete_config.toml +++ b/besu/src/test/resources/complete_config.toml @@ -6,6 +6,7 @@ data-path="/opt/besu" # Path # network discovery-enabled=false +poa-discovery-retry-bootnodes=true bootnodes=[ "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.1:4567", "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.1:4567", diff --git a/besu/src/test/resources/everything_config.toml b/besu/src/test/resources/everything_config.toml index be337272e97..e3d7a3f28d3 100644 --- a/besu/src/test/resources/everything_config.toml +++ b/besu/src/test/resources/everything_config.toml @@ -16,7 +16,9 @@ node-private-key-file="./path/to/privateKey" pid-path="~/.pid" reorg-logging-threshold=0 static-nodes-file="~/besudata/static-nodes.json" +version-compatibility-protection=true +profile="NONE" # Security Module plugin to use security-module="localfile" @@ -27,6 +29,7 @@ nat-method="NONE" Xnat-kube-service-name="besu" Xnat-method-fallback-enabled=true discovery-enabled=false +poa-discovery-retry-bootnodes=true bootnodes=[ "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.1:4567", "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.1:4567", @@ -49,10 +52,12 @@ engine-jwt-disabled=true engine-jwt-secret="/tmp/jwt.hex" required-blocks=["8675309=123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"] discovery-dns-url="enrtree://AM5FCQLWIZX2QFPNJAP7VUERCCRNGRHWZG3YYHIUV7BVDQ5FDPRT2@nodes.example.org" +net-restrict=["none"] # chain network="MAINNET" genesis-file="~/genesis.json" +genesis-state-hash-cache-enabled=false sync-mode="fast" fast-sync-min-peers=5 network-id=303 @@ -90,6 +95,7 @@ rpc-max-logs-range=100 json-pretty-print-enabled=false cache-last-blocks=512 rpc-gas-cap = 50000000 +rpc-max-trace-filter-range=100 # PRIVACY TLS privacy-tls-enabled=false @@ -142,15 +148,12 @@ min-priority-fee=0 min-block-occupancy-ratio=0.7 miner-stratum-host="0.0.0.0" miner-stratum-port=8008 +block-txs-selection-max-time=5000 +poa-block-txs-selection-max-time=75 Xminer-remote-sealers-limit=1000 Xminer-remote-sealers-hashrate-ttl=10 Xpos-block-creation-max-time=5 -# Pruning -pruning-enabled=true -pruning-blocks-retained=1024 -pruning-block-confirmations=10 - # Permissioning permissions-nodes-config-file-enabled=false permissions-nodes-config-file="./permissions_config.toml" @@ -169,12 +172,13 @@ privacy-enabled=false privacy-multi-tenancy-enabled=true privacy-marker-transaction-signing-key-file="./signerKey" privacy-enable-database-migration=false -privacy-onchain-groups-enabled=false privacy-flexible-groups-enabled=false +privacy-nonce-always-increments=false # Transaction Pool tx-pool="layered" tx-pool-price-bump=13 +tx-pool-blob-price-bump=100 rpc-tx-feecap=2000000000000000000 strict-tx-replay-protection-enabled=true tx-pool-no-local-priority=false @@ -184,12 +188,14 @@ tx-pool-save-file="txpool.dump" ## Layered tx-pool-layer-max-capacity=12345678 tx-pool-max-prioritized=9876 +tx-pool-max-prioritized-by-type=["BLOB=10","FRONTIER=100"] tx-pool-max-future-by-sender=321 -## Legacy +## Legacy/Sequenced tx-pool-retention-hours=999 tx-pool-max-size=1234 tx-pool-limit-by-account-percentage=0.017 tx-pool-min-gas-price=1000 +tx-pool-min-score=100 # Revert Reason revert-reason-enabled=false @@ -211,10 +217,15 @@ ethstats-cacert-file="./root.cert" # Data storage data-storage-format="BONSAI" bonsai-historical-block-limit=512 +bonsai-limit-trie-logs-enabled=true +bonsai-trie-logs-pruning-window-size=100_000 +receipt-compaction-enabled=true # feature flags Xsecp256k1-native-enabled=false Xaltbn128-native-enabled=false +Xsnapsync-server-enabled=true +Xbonsai-full-flat-db-enabled=true # compatibility flags compatibility-eth64-forkid-enabled=false @@ -231,4 +242,8 @@ Xp2p-tls-crl-file="none.file" Xp2p-tls-clienthello-sni=false #contracts -Xevm-jumpdest-cache-weight-kb=32000 \ No newline at end of file +Xevm-jumpdest-cache-weight-kb=32000 + +# plugins +Xplugins-external-enabled=true +plugins=["none"] \ No newline at end of file diff --git a/besu/src/test/resources/partial_config.toml b/besu/src/test/resources/partial_config.toml index f2d8eaa061f..1763bb6bdc6 100644 --- a/besu/src/test/resources/partial_config.toml +++ b/besu/src/test/resources/partial_config.toml @@ -1,4 +1,7 @@ # this is a valid partial TOML config file #mining -miner-coinbase="0x0000000000000000000000000000000000000002" \ No newline at end of file +miner-coinbase="0x0000000000000000000000000000000000000002" + +#network +network="mainnet" \ No newline at end of file diff --git a/build.gradle b/build.gradle index 364d969db22..792355417f4 100644 --- a/build.gradle +++ b/build.gradle @@ -14,6 +14,7 @@ */ +import com.github.jk1.license.filter.LicenseBundleNormalizer import groovy.transform.CompileStatic import groovy.transform.Memoized import net.ltgt.gradle.errorprone.CheckSeverity @@ -22,21 +23,22 @@ import java.text.SimpleDateFormat import java.util.regex.Pattern plugins { - id 'com.diffplug.spotless' version '6.12.0' - id 'com.github.ben-manes.versions' version '0.42.0' - id 'com.github.hierynomus.license' version '0.16.1-fix' - id 'com.jfrog.artifactory' version '4.28.3' - id 'io.spring.dependency-management' version '1.0.11.RELEASE' - id 'me.champeau.jmh' version '0.7.0' apply false - id 'net.ltgt.errorprone' version '2.0.2' + id 'com.diffplug.spotless' version '6.25.0' + id 'com.github.ben-manes.versions' version '0.51.0' + id 'com.github.jk1.dependency-license-report' version '2.9' + id 'com.jfrog.artifactory' version '5.2.5' + id 'io.spring.dependency-management' version '1.1.6' + id 'me.champeau.jmh' version '0.7.2' apply false + id 'net.ltgt.errorprone' version '4.0.1' id 'maven-publish' - id 'org.sonarqube' version '3.4.0.2513' + id 'org.sonarqube' version '5.1.0.4882' } sonarqube { properties { - property "sonar.projectKey", "hyperledger_besu" - property "sonar.organization", "hyperledger" + property "sonar.projectKey", "$System.env.SONAR_PROJECT_KEY" + property "sonar.organization", "$System.env.SONAR_ORGANIZATION" + property "sonar.gradle.skipCompile", "true" property "sonar.host.url", "https://sonarcloud.io" property "sonar.coverage.jacoco.xmlReportPaths", "${buildDir}/reports/jacoco/jacocoRootReport/jacocoRootReport.xml" property "sonar.coverage.exclusions", "acceptance-tests/**/*" @@ -45,21 +47,24 @@ sonarqube { project.tasks["sonarqube"].dependsOn "jacocoRootReport" -if (!JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_17)) { - throw new GradleException("Java 17 or later is required to build Besu.\n" + +if (!JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_21)) { + throw new GradleException("Java 21 or later is required to build Besu.\n" + " Detected version ${JavaVersion.current()}") } group = 'org.hyperledger.besu' -defaultTasks 'build', 'checkLicenses', 'javadoc' +defaultTasks 'build', 'checkLicense', 'javadoc' -def buildAliases = ['dev': [ +def buildAliases = [ + 'dev': [ 'spotlessApply', 'build', - 'checkLicenses', + 'checkLicense', 'javadoc' - ]] + ], + 'build': ['spotlessCheck', 'build'] +] def expandedTaskList = [] gradle.startParameter.taskNames.each { @@ -87,35 +92,63 @@ def _strListCmdArg(name) { return _strListCmdArg(name, null) } +// set the shell command to use according to os +def shell = org.gradle.internal.os.OperatingSystem.current().isWindows() ? "${projectDir}\\wslsh.bat" : '/bin/bash' + +licenseReport { + // This is for the allowed-licenses-file in checkLicense Task + // Accepts File, URL or String path to local or remote file + allowedLicensesFile = new File("$rootDir/gradle/allowed-licenses.json") + + excludes = [ + // only used for static analysis, not actually shipped + 'com.google.errorprone:javac', + 'org.checkerframework:dataflow-shaded', + 'org.checkerframework:dataflow-errorprone', + // exclude Kotlin multiplatform dependencies container, they have the same license of what they contain + 'com.squareup.okio:okio', + 'org.jetbrains.kotlinx:kotlinx-coroutines-core' + ] + + // If set to true, then all boms will be excluded from the report + excludeBoms = true + + filters = [ + new LicenseBundleNormalizer(bundlePath: "$rootDir/gradle/license-normalizer-bundle.json") + ] +} + allprojects { apply plugin: 'java-library' apply plugin: 'io.spring.dependency-management' apply plugin: 'jacoco' apply plugin: 'net.ltgt.errorprone' apply from: "${rootDir}/gradle/versions.gradle" - apply from: "${rootDir}/gradle/check-licenses.gradle" - version = rootProject.version + version = calculateVersion() jacoco { - toolVersion = '0.8.8' + toolVersion = '0.8.11' if (project.tasks.findByName('referenceTests')) { applyTo referenceTests } } task sourcesJar(type: Jar, dependsOn: classes) { - classifier = 'sources' + archiveClassifier = 'sources' from sourceSets.main.allSource } task javadocJar(type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' + archiveClassifier = 'javadoc' from javadoc.outputDirectory } - sourceCompatibility = 17 - targetCompatibility = 17 + tasks.build { + dependsOn 'javadoc' + } + sourceCompatibility = 21 + targetCompatibility = 21 repositories { maven { @@ -130,6 +163,7 @@ allprojects { url 'https://splunk.jfrog.io/splunk/ext-releases-local' content { includeGroupByRegex('com\\.splunk\\..*') } } + mavenCentral() // ethereum execution spec tests fixtures. Exclusively for ethereum submodule to run ref tests @@ -151,6 +185,8 @@ allprojects { dependencies { components.all(BouncyCastleCapability) errorprone 'com.google.errorprone:error_prone_core' + // https://github.com/hyperledger/besu-errorprone-checks/ + errorprone "org.hyperledger.besu:besu-errorprone-checks" } configurations.all { @@ -170,23 +206,43 @@ allprojects { target 'src/**/*.java' targetExclude '**/src/reference-test/**', '**/src/main/generated/**', '**/src/test/generated/**', '**/src/jmh/generated/**' removeUnusedImports() - googleJavaFormat('1.15.0') + googleJavaFormat('1.22.0') importOrder 'org.hyperledger', 'java', '' trimTrailingWhitespace() endWithNewline() + // apply appropriate license header files. + licenseHeaderFile("${rootDir}/gradle/spotless/java.former.license").named("older").onlyIfContentMatches("^/\\*\\r?\\n.*Copyright ConsenSys AG\\.") + licenseHeaderFile("${rootDir}/gradle/spotless/java.former.date.license").named("older.year").onlyIfContentMatches("^/\\*\\r?\\n.* Copyright \\d{4} ConsenSys AG\\.") + licenseHeaderFile("${rootDir}/gradle/spotless/java.current.license").named("current").onlyIfContentMatches("^(?!/\\*\\r?\\n \\*.*ConsenSys AG\\.)") } + // spotless check applied to build.gradle (groovy) files groovyGradle { target '*.gradle' - greclipse('4.21.0').configFile(rootProject.file('gradle/formatter.properties')) + greclipse('4.31').configFile(rootProject.file('gradle/spotless/greclipse.properties')) endWithNewline() } // Below this line are currently only license header tasks - format 'groovy', { target '**/src/*/grovy/**/*.groovy' } - format 'bash', { target '**/*.sh' } - format 'sol', { target '**/*.sol' } + format 'ShellScripts', { + target '**/*.sh' + targetExclude '**/src/reference-test/**', '**/src/main/generated/**', '**/src/test/generated/**', '**/src/jmh/generated/**' + trimTrailingWhitespace() + endWithNewline() + + licenseHeaderFile("${rootDir}/gradle/spotless/sh.license","^(?!##).+").skipLinesMatching("^#!.+?\$") + } + format 'Solidity', { + target '**/*.sol' + targetExclude '**/src/reference-test/**', '**/src/main/generated/**', '**/src/test/generated/**', '**/src/jmh/generated/**' + trimTrailingWhitespace() + endWithNewline() + + licenseHeaderFile("${rootDir}/gradle/spotless/java.former.license","^pragma solidity.+?").named("former").onlyIfContentMatches("^/\\*\\r?\\n.*Copyright ConsenSys AG\\.") + licenseHeaderFile("${rootDir}/gradle/spotless/java.former.date.license","^pragma solidity.+?").named("former.date").onlyIfContentMatches("^/\\*\\r?\\n.* Copyright \\d{4} ConsenSys AG\\.") + licenseHeaderFile("${rootDir}/gradle/spotless/java.current.license","^pragma solidity.+?").named("current").onlyIfContentMatches("^(?!/\\*\\r?\\n \\*.*ConsenSys AG\\.)") + } } - tasks.withType(JavaCompile) { + tasks.withType(JavaCompile).configureEach { options.compilerArgs += [ '-Xlint:unchecked', '-Xlint:cast', @@ -199,8 +255,8 @@ allprojects { ] options.errorprone { - excludedPaths = '.*/(generated/*.*|.*ReferenceTest_.*|build/.*/annotation-output/.*)' - + excludedPaths = '.*/generated/*.*' + disableWarningsInGeneratedCode = true // Our equals need to be symmetric, this checker doesn't respect that. check('EqualsGetClass', CheckSeverity.OFF) // We like to use futures with no return values. @@ -262,7 +318,7 @@ allprojects { * */ test { - jvmArgs = [ + jvmArgs += [ '-Xmx4g', '-XX:-UseGCOverheadLimit', // Mockito and jackson-databind do some strange reflection during tests. @@ -312,18 +368,69 @@ allprojects { options.addBooleanOption('Xdoclint/package:-org.hyperledger.besu.privacy.contracts.generated,' + '-org.hyperledger.besu.tests.acceptance.*,' + '-org.hyperledger.besu.tests.web3j.generated,' + - // TODO: these are temporary disabled (ethereum and sub modules), it should be removed in a future PR. - '-org.hyperledger.besu.ethereum,' + - '-org.hyperledger.besu.ethereum.*,' + - '-org.hyperledger.besu.ethstats.*,' + - '-org.hyperledger.besu.evmtool', + // TODO: these are temporary excluded from lint (ethereum sub modules), it should be removed in a future PR. + // ethereum api module + '-org.hyperledger.besu.ethereum.api.handlers,' + + '-org.hyperledger.besu.ethereum.api.jsonrpc,' + + '-org.hyperledger.besu.ethereum.api.jsonrpc.*,' + + '-org.hyperledger.besu.ethereum.api.query,' + + '-org.hyperledger.besu.ethereum.api.query.*,' + + '-org.hyperledger.besu.ethereum.api.tls,' + + '-org.hyperledger.besu.ethereum.api.util,' + + // ethereum blockcreation module + '-org.hyperledger.besu.ethereum.blockcreation,' + + '-org.hyperledger.besu.ethereum.blockcreation.*,' + + // ethereum core module + '-org.hyperledger.besu.ethereum.chain,' + + '-org.hyperledger.besu.ethereum.core,' + + '-org.hyperledger.besu.ethereum.core.*,' + + '-org.hyperledger.besu.ethereum.debug,' + + '-org.hyperledger.besu.ethereum.difficulty.fixed,' + + '-org.hyperledger.besu.ethereum.forkid,' + + '-org.hyperledger.besu.ethereum.mainnet,' + + '-org.hyperledger.besu.ethereum.mainnet.*,' + + '-org.hyperledger.besu.ethereum.privacy,' + + '-org.hyperledger.besu.ethereum.privacy.*,' + + '-org.hyperledger.besu.ethereum.processing,' + + '-org.hyperledger.besu.ethereum.proof,' + + '-org.hyperledger.besu.ethereum.storage,' + + '-org.hyperledger.besu.ethereum.storage.*,' + + '-org.hyperledger.besu.ethereum.transaction,' + + '-org.hyperledger.besu.ethereum.trie.*,' + + '-org.hyperledger.besu.ethereum.util,' + + '-org.hyperledger.besu.ethereum.vm,' + + '-org.hyperledger.besu.ethereum.worldstate,' + + // ethereum eth module + '-org.hyperledger.besu.ethereum.eth.*,' + + '-org.hyperledger.besu.ethereum.eth,' + + '-org.hyperledger.besu.consensus.merge,' + + // p2p module + '-org.hyperledger.besu.ethereum.p2p,' + + '-org.hyperledger.besu.ethereum.p2p.*,' + + // permissioning module + '-org.hyperledger.besu.ethereum.permissioning,' + + '-org.hyperledger.besu.ethereum.permissioning.*,' + + // referencetests module + '-org.hyperledger.besu.ethereum.referencetests,' + + // retesteth module + '-org.hyperledger.besu.ethereum.retesteth.methods,' + + '-org.hyperledger.besu.ethereum.retesteth,' + + //rlp module + '-org.hyperledger.besu.ethereum.rlp,' + + // stratum module + '-org.hyperledger.besu.ethereum.stratum,' + + // trie module + '-org.hyperledger.besu.ethereum.trie.*,' + + '-org.hyperledger.besu.ethereum.trie,' + + // verkle trie module + '-org.hyperledger.besu.ethereum.verkletrie,' + + '-org.hyperledger.besu.ethereum.verkletrie.*', true) options.addStringOption('Xmaxerrs','65535') options.addStringOption('Xmaxwarns','65535') options.addStringOption('Xwerror', '-html5') options.encoding = 'UTF-8' } - } task deploy() {} @@ -334,7 +441,7 @@ task checkMavenCoordinateCollisions { getAllprojects().forEach { if (it.properties.containsKey('publishing') && it.jar?.enabled) { def coordinate = it.publishing?.publications[0].coordinates - if (coordinates.containsKey(coordinate)) { + if (coordinate.toString().startsWith("org") && coordinates.containsKey(coordinate)) { throw new GradleException("Duplicate maven coordinates detected, ${coordinate} is used by " + "both ${coordinates[coordinate]} and ${it.path}.\n" + "Please add a `publishing` script block to one or both subprojects.") @@ -368,7 +475,7 @@ subprojects { task testSupportJar(type: Jar) { archiveBaseName = "${project.name}-support-test" - classifier = 'test-support' + archiveClassifier = 'test-support' from sourceSets.testSupport.output } } @@ -514,6 +621,13 @@ subprojects { dependencies { jmh 'org.slf4j:slf4j-api' } } + + // making sure assemble task invokes integration test compile + afterEvaluate { project -> + if (project.tasks.findByName('compileIntegrationTestJava')) { + project.tasks.assemble.dependsOn compileIntegrationTestJava + } + } } jar { enabled = false } @@ -575,7 +689,9 @@ startScripts { defaultJvmOpts = applicationDefaultJvmArgs + [ "-XX:G1ConcRefinementThreads=2", "-XX:G1HeapWastePercent=15", - "-XX:MaxGCPauseMillis=100" + "-XX:MaxGCPauseMillis=100", + "-XX:StartFlightRecording,dumponexit=true,settings=default.jfc", + "-Xlog:jfr*=off" ] unixStartScriptGenerator.template = resources.text.fromFile("${projectDir}/besu/src/main/scripts/unixStartScript.txt") windowsStartScriptGenerator.template = resources.text.fromFile("${projectDir}/besu/src/main/scripts/windowsStartScript.txt") @@ -590,7 +706,7 @@ task untunedStartScripts(type: CreateStartScripts) { defaultJvmOpts = applicationDefaultJvmArgs unixStartScriptGenerator.template = resources.text.fromFile("${projectDir}/besu/src/main/scripts/unixStartScript.txt") windowsStartScriptGenerator.template = resources.text.fromFile("${projectDir}/besu/src/main/scripts/windowsStartScript.txt") - doLast { tweakStartScript(startScripts) } + doLast { tweakStartScript(untunedStartScripts) } } task evmToolStartScripts(type: CreateStartScripts) { @@ -624,22 +740,26 @@ task autocomplete(type: JavaExec) { } } -installDist { dependsOn checkLicenses, untunedStartScripts, evmToolStartScripts } +def archiveBuildVersion = project.hasProperty('release.releaseVersion') ? project.property('release.releaseVersion') : "${rootProject.version}" + +installDist { dependsOn checkLicense, untunedStartScripts, evmToolStartScripts } distTar { - dependsOn checkLicenses, autocomplete, untunedStartScripts, evmToolStartScripts + dependsOn checkLicense, autocomplete, untunedStartScripts, evmToolStartScripts doFirst { delete fileTree(dir: 'build/distributions', include: '*.tar.gz') } compression = Compression.GZIP + setVersion(archiveBuildVersion) archiveExtension = 'tar.gz' } distZip { - dependsOn checkLicenses, autocomplete, untunedStartScripts, evmToolStartScripts + dependsOn checkLicense, autocomplete, untunedStartScripts, evmToolStartScripts doFirst { delete fileTree(dir: 'build/distributions', include: '*.zip') } + setVersion(archiveBuildVersion) } publishing { @@ -648,28 +768,6 @@ publishing { groupId = '.' version = project.version artifactId = 'besu' - artifact("$buildDir/distributions/besu-${project.version}.zip") - artifact("$buildDir/distributions/besu-${project.version}.tar.gz") { extension = 'tar.gz' } - } - } -} - -def artifactoryUser = project.hasProperty('artifactoryUser') ? project.property('artifactoryUser') : System.getenv('ARTIFACTORY_USER') -def artifactoryKey = project.hasProperty('artifactoryApiKey') ? project.property('artifactoryApiKey') : System.getenv('ARTIFACTORY_KEY') -def artifactoryOrg = System.getenv('ARTIFACTORY_ORG') ?: 'hyperledger' - -artifactory { - contextUrl = "https://hyperledger.jfrog.io/${artifactoryOrg}" - publish { - repository { - repoKey = "besu-binaries" - username = artifactoryUser - password = artifactoryKey - } - defaults { - publications('distArtifactory') - publishArtifacts = true - publishPom = false } } } @@ -683,13 +781,6 @@ def dockerBuildVersion = project.hasProperty('release.releaseVersion') ? project def dockerOrgName = project.hasProperty('dockerOrgName') ? project.getProperty("dockerOrgName") : "hyperledger" def dockerArtifactName = project.hasProperty("dockerArtifactName") ? project.getProperty("dockerArtifactName") : "besu" def dockerImageName = "${dockerOrgName}/${dockerArtifactName}" -def dockerVariants = project.hasProperty("dockerVariants") ? project.getProperty("dockerVariants").split(",") : [ - "openjdk-17", - "openjdk-17-debug", - "openj9-jdk-17", - "graalvm", - "openjdk-latest" -] // rename the top level dir from besu- to besu and this makes it really // simple for use in docker @@ -717,33 +808,21 @@ task distDocker { def dockerBuildDir = "build/docker-besu/" doLast { - for (def jvmVariant in dockerVariants) { - copy { - from file("${projectDir}/docker/${jvmVariant}/Dockerfile") - into(dockerBuildDir) - } - exec { - def image = "${dockerImageName}:${dockerBuildVersion}-${jvmVariant}" - def dockerPlatform = "" - if (project.hasProperty('docker-platform')){ - dockerPlatform = "--platform ${project.getProperty('docker-platform')}" - println "Building for platform ${project.getProperty('docker-platform')}" - } - executable "sh" - workingDir dockerBuildDir - args "-c", "docker build ${dockerPlatform} --build-arg BUILD_DATE=${buildTime()} --build-arg VERSION=${dockerBuildVersion} --build-arg VCS_REF=${getCheckedOutGitCommitHash()} -t ${image} ." - } - } - - // tag the "default" (which is the variant in the zero position) - exec { - executable "sh" - args "-c", "docker tag '${dockerImageName}:${dockerBuildVersion}-${dockerVariants[0]}' '${dockerImageName}:${dockerBuildVersion}'" + copy { + from file("${projectDir}/docker/Dockerfile") + into(dockerBuildDir) } - // create a static tag for the benchmark target exec { - executable "sh" - args "-c", "docker tag '${dockerImageName}:${dockerBuildVersion}-${dockerVariants[0]}' '${dockerImageName}:benchmark'" + def image = "${dockerImageName}:${dockerBuildVersion}" + def dockerPlatform = "" + if (project.hasProperty('docker-platform')){ + dockerPlatform = "--platform ${project.getProperty('docker-platform')}" + println "Building for platform ${project.getProperty('docker-platform')}" + } + def gitDetails = getGitCommitDetails(7) + executable shell + workingDir dockerBuildDir + args "-c", "docker build ${dockerPlatform} --build-arg BUILD_DATE=${buildTime()} --build-arg VERSION=${dockerBuildVersion} --build-arg VCS_REF=${gitDetails.hash} -t ${image} ." } } } @@ -757,13 +836,11 @@ task testDocker { } doLast { - for (def variant in dockerVariants) { - exec { - def image = project.hasProperty('release.releaseVersion') ? "${dockerImageName}:" + project.property('release.releaseVersion') : "${dockerImageName}:${project.version}" - workingDir "${projectDir}/docker/${variant}" - executable "sh" - args "-c", "bash ../test.sh ${image}-${variant}" - } + exec { + def image = project.hasProperty('release.releaseVersion') ? "${dockerImageName}:" + project.property('release.releaseVersion') : "${dockerImageName}:${project.version}" + workingDir "${projectDir}/docker" + executable shell + args "-c", "./test.sh ${image}" } } } @@ -783,24 +860,11 @@ task dockerUpload { } doLast { - for (def variant in dockerVariants) { - def variantImage = "${image}-${variant}" - exec { - def archVariantImage = "${variantImage}-${architecture}" - def cmd = "docker tag '${variantImage}' '${archVariantImage}' && docker push '${archVariantImage}'" - additionalTags.each { tag -> cmd += " && docker tag '${variantImage}' '${dockerImageName}:${tag.trim()}-${variant}-${architecture}' && docker push '${dockerImageName}:${tag.trim()}-${variant}-${architecture}'" } - println "Executing '${cmd}'" - executable "sh" - args "-c", cmd - } - } - exec { - def archImage = "${image}-${architecture}" - def cmd = "docker tag ${image} ${archImage} && docker push '${archImage}'" - additionalTags.each { tag -> cmd += " && docker tag '${image}' '${dockerImageName}:${tag.trim()}-${architecture}' && docker push '${dockerImageName}:${tag.trim()}-${architecture}'" } + def archVariantImage = "${image}-${architecture}" + def cmd = "docker tag '${image}' '${archVariantImage}' && docker push '${archVariantImage}'" println "Executing '${cmd}'" - executable "sh" + executable shell args "-c", cmd } } @@ -812,127 +876,79 @@ task dockerUploadRelease { doLast { for (def architecture in archs) { - for (def variant in dockerVariants) { - def variantImage = "${image}-${variant}" - exec { - def cmd = "docker pull '${variantImage}-${architecture}' && docker tag '${variantImage}-${architecture}' '${dockerImageName}:latest-${variant}-${architecture}'" - println "Executing '${cmd}'" - executable "sh" - args "-c", cmd - } - exec { - def cmd = "docker push '${dockerImageName}:latest-${variant}-${architecture}'" - println "Executing '${cmd}'" - executable "sh" - args "-c", cmd - } - } exec { - def archImage = "${image}-${architecture}" - def cmd = "docker pull '${archImage}' && docker tag ${archImage} '${dockerImageName}:latest-${architecture}'" + def cmd = "docker pull '${image}-${architecture}' && docker tag '${image}-${architecture}' '${dockerImageName}:latest-${architecture}'" println "Executing '${cmd}'" - executable "sh" + executable shell args "-c", cmd } exec { def cmd = "docker push '${dockerImageName}:latest-${architecture}'" println "Executing '${cmd}'" - executable "sh" + executable shell args "-c", cmd } - } - } -} -task manifestDocker { - def image = "${dockerImageName}:${dockerBuildVersion}" - def archs = ["arm64", "amd64"] - def tags = ["${image}"] - - if (project.hasProperty('branch') && project.property('branch') == 'main') { - tags.add("${dockerImageName}:develop") - } - - if (!isInterimBuild(dockerBuildVersion)) { - tags.add("${dockerImageName}:" + dockerBuildVersion.split(/\./)[0..1].join('.')) - } - - doLast { - for (baseTag in tags) { - for (def variant in dockerVariants) { - def variantImage = "${baseTag}-${variant}" - def targets = "" - archs.forEach { arch -> targets += "'${variantImage}-${arch}' " } - - exec { - def cmd = "docker manifest create '${variantImage}' ${targets}" - println "Executing '${cmd}'" - executable "sh" - args "-c", cmd - } - exec { - def cmd = "docker manifest push '${variantImage}'" - println "Executing '${cmd}'" - executable "sh" - args "-c", cmd - } - } exec { - def targets = "" - archs.forEach { arch -> targets += "'${baseTag}-${arch}' " } - def cmd = "docker manifest create '${baseTag}' ${targets}" + def archImage = "${image}-${architecture}" + def cmd = "docker pull '${archImage}' && docker tag ${archImage} '${dockerImageName}:latest-${architecture}'" println "Executing '${cmd}'" - executable "sh" + executable shell args "-c", cmd } exec { - def cmd = "docker manifest push '${baseTag}'" + def cmd = "docker push '${dockerImageName}:latest-${architecture}'" println "Executing '${cmd}'" - executable "sh" + executable shell args "-c", cmd } } } } +task manifestDocker { + def image = "${dockerImageName}:${dockerBuildVersion}" + def archs = [ + "arm64", + "amd64"] //TODO: this assumes dockerUpload task has already been run on 2 different archs! + doLast { + exec { + def targets = "" + archs.forEach { arch -> targets += "'${image}-${arch}' " } + def cmd = "docker manifest create '${image}' ${targets}" + println "Executing '${cmd}'" + executable shell + args "-c", cmd + } + exec { + def cmd = "docker manifest push '${image}'" + println "Executing '${cmd}'" + executable shell + args "-c", cmd + } + } +} + task manifestDockerRelease { def archs = ["arm64", "amd64"] def baseTag = "${dockerImageName}:latest"; doLast { - for (def variant in dockerVariants) { - def variantImage = "${baseTag}-${variant}" - def targets = "" - archs.forEach { arch -> targets += "'${variantImage}-${arch}' " } - - exec { - def cmd = "docker manifest create '${variantImage}' ${targets} --amend" - println "Executing '${cmd}'" - executable "sh" - args "-c", cmd - } - exec { - def cmd = "docker manifest push '${variantImage}'" - println "Executing '${cmd}'" - executable "sh" - args "-c", cmd - } - } exec { def targets = "" archs.forEach { arch -> targets += "'${baseTag}-${arch}' " } def cmd = "docker manifest create '${baseTag}' ${targets} --amend" println "Executing '${cmd}'" - executable "sh" + executable shell args "-c", cmd } exec { def cmd = "docker manifest push '${baseTag}'" println "Executing '${cmd}'" - executable "sh" + executable shell args "-c", cmd } } @@ -940,25 +956,10 @@ task manifestDockerRelease { def sep = Pattern.quote(File.separator) -task checkSpdxHeader(type: CheckSpdxHeader) { - apply plugin: 'groovy' - - rootPath = "${projectDir}" - spdxHeader = "* SPDX-License-Identifier: Apache-2.0" - filesRegex = "(.*.java)|(.*.groovy)" - excludeRegex = [ - "(.*${sep}generalstate${sep}GeneralStateRegressionReferenceTest.*)", - "(.*${sep}generalstate${sep}GeneralStateReferenceTest.*)", - "(.*${sep}generalstate${sep}LegacyGeneralStateReferenceTest.*)", - "(.*${sep}blockchain${sep}BlockchainReferenceTest.*)", - "(.*${sep}blockchain${sep}LegacyBlockchainReferenceTest.*)", - "(.*${sep}.gradle${sep}.*)", - "(.*${sep}.idea${sep}.*)", - "(.*${sep}out${sep}.*)", - "(.*${sep}build${sep}.*)", - "(.*${sep}src${sep}[^${sep}]+${sep}generated${sep}.*)" - ].join("|") - +jacocoTestReport { + reports { + xml.required = true + } } task jacocoRootReport(type: org.gradle.testing.jacoco.tasks.JacocoReport) { @@ -968,25 +969,12 @@ task jacocoRootReport(type: org.gradle.testing.jacoco.tasks.JacocoReport) { executionData.from fileTree(dir: '.', includes: ['**/jacoco/*.exec']) reports { xml.required = true - xml.enabled = true csv.required = true html.destination file("build/reports/jacocoHtml") } onlyIf = { true } } -configurations { annotationProcessor } - -// Prevent errorprone-checks being dependent upon errorprone-checks! -// However, ensure all subprojects comply with the custom rules. -configure(subprojects.findAll { it.name != 'errorprone-checks' }) { - dependencies { annotationProcessor project(":errorprone-checks") } - - tasks.withType(JavaCompile) { - options.annotationProcessorPath = configurations.annotationProcessor - } -} - // http://label-schema.org/rc1/ // using the RFC3339 format "2016-04-12T23:20:50.52Z" def buildTime() { @@ -995,43 +983,54 @@ def buildTime() { return df.format(new Date()) } -// Takes the version, and if -SNAPSHOT is part of it replaces SNAPSHOT -// with the git commit version. @Memoized def calculateVersion() { - String version = rootProject.version - if (version.endsWith("-SNAPSHOT")) { - version = version.replace("-SNAPSHOT", "-dev-" + getCheckedOutGitCommitHash()) + // Regex pattern for basic calendar versioning, with provision to omit patch rev + def calVerPattern = ~/\d+\.\d+(\.\d+)?(-.*)?/ + + if (project.hasProperty('version') && (project.version =~ calVerPattern)) { + if (project.hasProperty('versionappendcommit') && project.versionappendcommit == "true") { + def gitDetails = getGitCommitDetails(7) // Adjust length as needed + return "${project.version}-${gitDetails.hash}" + } + return "${project.version}" + } else { + // If no version is supplied or it doesn't match the semantic versioning, calculate from git + println("Generating project version as supplied is version not semver: ${project.version}") + def gitDetails = getGitCommitDetails(7) // Adjust length as needed + return "${gitDetails.date}-develop-${gitDetails.hash}" } - return version } - -def getCheckedOutGitCommitHash(length = 8) { +def getGitCommitDetails(length = 8) { try { def gitFolder = "$projectDir/.git/" if (!file(gitFolder).isDirectory()) { - // We are in a submodule. The file's contents are `gitdir: \n`. - // Read the file, cut off the front, and trim the whitespace. gitFolder = file(gitFolder).text.substring(length).trim() + "/" } def takeFromHash = length - /* - * '.git/HEAD' contains either - * in case of detached head: the currently checked out commit hash - * otherwise: a reference to a file containing the current commit hash - */ - def head = new File(gitFolder + "HEAD").text.split(":") // .git/HEAD - def isCommit = head.length == 1 // e5a7c79edabbf7dd39888442df081b1c9d8e88fd - - if (isCommit) return head[0].trim().take(takeFromHash) // e5a7c79edabb - - def refHead = new File(gitFolder + head[1].trim()) // .git/refs/heads/master - refHead.text.trim().take takeFromHash + def head = new File(gitFolder + "HEAD").text.split(":") + def isCommit = head.length == 1 + + def commitHash, refHeadFile + if (isCommit) { + commitHash = head[0].trim().take(takeFromHash) + refHeadFile = new File(gitFolder + "HEAD") + } else { + refHeadFile = new File(gitFolder + head[1].trim()) + commitHash = refHeadFile.text.trim().take(takeFromHash) + } + + // Use head file modification time as a proxy for the build date + def lastModified = new Date(refHeadFile.lastModified()) + // Format the date as "yy.M" (e.g. 24.3 for March 2024) + def formattedDate = new SimpleDateFormat("yy.M").format(lastModified) + + return [hash: commitHash, date: formattedDate] } catch (Exception e) { - logger.warn('Could not calculate git commit, using "xxxxxxxx" (run with --info for stacktrace)') - logger.info('Error retrieving git commit', e) - return "xxxxxxxx" + logger.warn('Could not calculate git commit details, using defaults (run with --info for stacktrace)') + logger.info('Error retrieving git commit details', e) + return [hash: "xxxxxxxx", date: "00.0"] } } @@ -1039,6 +1038,7 @@ def getCheckedOutGitCommitHash(length = 8) { def isInterimBuild(dockerBuildVersion) { return (dockerBuildVersion ==~ /.*-SNAPSHOT/) || (dockerBuildVersion ==~ /.*-alpha/) || (dockerBuildVersion ==~ /.*-beta/) || (dockerBuildVersion ==~ /.*-RC.*/) + || (dockerBuildVersion ==~ /.*develop.*/) } tasks.register("verifyDistributions") { @@ -1060,9 +1060,11 @@ tasks.register("verifyDistributions") { } dependencies { + errorprone 'com.google.errorprone:error_prone_core' + // https://github.com/hyperledger/besu-errorprone-checks/ + errorprone 'org.hyperledger.besu:besu-errorprone-checks' implementation project(':besu') implementation project(':ethereum:evmtool') - errorprone 'com.google.errorprone:error_prone_core' } @CompileStatic @@ -1095,12 +1097,11 @@ distributions { from("build/reports/license/license-dependency.html") { into "." } from("./docs/GettingStartedBinaries.md") { into "." } from("./docs/DocsArchive0.8.0.html") { into "." } - from("build/besu.autocomplete.sh") { into "." } + from(autocomplete) { into "." } } } } -check.dependsOn checkSpdxHeader build.dependsOn verifyDistributions artifactoryPublish.dependsOn verifyDistributions artifactoryPublish.mustRunAfter(distTar) diff --git a/buildSrc/src/main/groovy/CheckSpdxHeader.groovy b/buildSrc/src/main/groovy/CheckSpdxHeader.groovy deleted file mode 100644 index 27bcbe239ae..00000000000 --- a/buildSrc/src/main/groovy/CheckSpdxHeader.groovy +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2019 ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - - - -import org.gradle.api.DefaultTask -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.TaskAction -import org.gradle.tooling.BuildException - -class CheckSpdxHeader extends DefaultTask { - private String rootPath - private String spdxHeader - private String filesRegex - private String excludeRegex - - @Input - String getRootPath() { - return rootPath - } - - void setRootPath(final String rootPath) { - this.rootPath = rootPath - } - - @Input - String getSpdxHeader() { - return spdxHeader - } - - void setSpdxHeader(final String spdxHeader) { - this.spdxHeader = spdxHeader - } - - @Input - String getFilesRegex() { - return filesRegex - } - - void setFilesRegex(final String filesRegex) { - this.filesRegex = filesRegex - } - - @Input - String getExcludeRegex() { - return excludeRegex - } - - void setExcludeRegex(final String excludeRegex) { - this.excludeRegex = excludeRegex - } - - @TaskAction - void checkHeaders() { - def filesWithoutHeader = [] - new File(rootPath).traverse( - type: groovy.io.FileType.FILES, - nameFilter: ~/${filesRegex}/, - excludeFilter: ~/${excludeRegex}/ - ) { - f -> - if (!f.getText().contains(spdxHeader)) { - filesWithoutHeader.add(f) - } - } - - if (!filesWithoutHeader.isEmpty()) { - throw new BuildException("Files without headers: " + filesWithoutHeader.join('\n'), null) - } - } - -} \ No newline at end of file diff --git a/config/build.gradle b/config/build.gradle index acfbc83f43f..a78b9a7105c 100644 --- a/config/build.gradle +++ b/config/build.gradle @@ -22,7 +22,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } @@ -38,6 +39,8 @@ dependencies { implementation 'info.picocli:picocli' implementation 'io.tmio:tuweni-bytes' implementation 'io.tmio:tuweni-units' + implementation "org.immutables:value-annotations" + annotationProcessor "org.immutables:value" testImplementation project(':testutil') diff --git a/config/src/main/java/org/hyperledger/besu/config/BftFork.java b/config/src/main/java/org/hyperledger/besu/config/BftFork.java index 2b2031cea45..30f8e1c5d5b 100644 --- a/config/src/main/java/org/hyperledger/besu/config/BftFork.java +++ b/config/src/main/java/org/hyperledger/besu/config/BftFork.java @@ -18,6 +18,7 @@ import java.math.BigInteger; import java.util.List; +import java.util.Locale; import java.util.Optional; import java.util.OptionalInt; @@ -28,16 +29,20 @@ import org.apache.tuweni.bytes.Bytes; /** The Bft fork. */ -public class BftFork { +public class BftFork implements Fork { /** The constant FORK_BLOCK_KEY. */ public static final String FORK_BLOCK_KEY = "block"; + /** The constant VALIDATORS_KEY. */ public static final String VALIDATORS_KEY = "validators"; + /** The constant BLOCK_PERIOD_SECONDS_KEY. */ public static final String BLOCK_PERIOD_SECONDS_KEY = "blockperiodseconds"; + /** The constant BLOCK_REWARD_KEY. */ public static final String BLOCK_REWARD_KEY = "blockreward"; + /** The constant MINING_BENEFICIARY_KEY. */ public static final String MINING_BENEFICIARY_KEY = "miningbeneficiary"; @@ -59,6 +64,7 @@ public BftFork(final ObjectNode forkConfigRoot) { * * @return the fork block */ + @Override public long getForkBlock() { return JsonUtil.getLong(forkConfigRoot, FORK_BLOCK_KEY) .orElseThrow( @@ -88,7 +94,7 @@ public Optional getBlockRewardWei() { return Optional.empty(); } final String weiStr = configFileContent.get(); - if (weiStr.toLowerCase().startsWith("0x")) { + if (weiStr.toLowerCase(Locale.ROOT).startsWith("0x")) { return Optional.of(new BigInteger(1, Bytes.fromHexStringLenient(weiStr).toArrayUnsafe())); } return Optional.of(new BigInteger(weiStr)); diff --git a/config/src/main/java/org/hyperledger/besu/config/CheckpointConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/CheckpointConfigOptions.java index 360a0bf002f..c5ac9a8d7db 100644 --- a/config/src/main/java/org/hyperledger/besu/config/CheckpointConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/CheckpointConfigOptions.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/config/src/main/java/org/hyperledger/besu/config/CliqueConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/CliqueConfigOptions.java index c0ba496f892..08ac0ef0fee 100644 --- a/config/src/main/java/org/hyperledger/besu/config/CliqueConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/CliqueConfigOptions.java @@ -1,5 +1,5 @@ /* - * Copyright ConsenSys AG. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -16,71 +16,37 @@ import java.util.Map; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.common.collect.ImmutableMap; +import org.immutables.value.Value; -/** The Clique config options. */ -public class CliqueConfigOptions { - - /** The constant DEFAULT. */ - public static final CliqueConfigOptions DEFAULT = - new CliqueConfigOptions(JsonUtil.createEmptyObjectNode()); - - private static final long DEFAULT_EPOCH_LENGTH = 30_000; - private static final int DEFAULT_BLOCK_PERIOD_SECONDS = 15; - private static final boolean DEFAULT_CREATE_EMPTY_BLOCKS = true; - - private final ObjectNode cliqueConfigRoot; - - /** - * Instantiates a new Clique config options. - * - * @param cliqueConfigRoot the clique config root - */ - CliqueConfigOptions(final ObjectNode cliqueConfigRoot) { - this.cliqueConfigRoot = cliqueConfigRoot; - } +/** Configuration options for the Clique consensus mechanism. */ +@Value.Immutable +public interface CliqueConfigOptions { /** * The number of blocks in an epoch. * * @return the epoch length */ - public long getEpochLength() { - return JsonUtil.getLong(cliqueConfigRoot, "epochlength", DEFAULT_EPOCH_LENGTH); - } + long getEpochLength(); /** * Gets block period seconds. * * @return the block period seconds */ - public int getBlockPeriodSeconds() { - return JsonUtil.getPositiveInt( - cliqueConfigRoot, "blockperiodseconds", DEFAULT_BLOCK_PERIOD_SECONDS); - } + int getBlockPeriodSeconds(); /** - * Whether the creation of empty blocks is allowed. + * Gets create empty blocks. * - * @return the create empty block status + * @return whether empty blocks are permitted */ - public boolean getCreateEmptyBlocks() { - return JsonUtil.getBoolean(cliqueConfigRoot, "createemptyblocks", DEFAULT_CREATE_EMPTY_BLOCKS); - } + boolean getCreateEmptyBlocks(); /** - * As map. + * A map of the config options. * * @return the map */ - Map asMap() { - return ImmutableMap.of( - "epochLength", - getEpochLength(), - "blockPeriodSeconds", - getBlockPeriodSeconds(), - "createemptyblocks", - getCreateEmptyBlocks()); - } + Map asMap(); } diff --git a/config/src/main/java/org/hyperledger/besu/config/CliqueFork.java b/config/src/main/java/org/hyperledger/besu/config/CliqueFork.java new file mode 100644 index 00000000000..00f65572f21 --- /dev/null +++ b/config/src/main/java/org/hyperledger/besu/config/CliqueFork.java @@ -0,0 +1,79 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.config; + +import java.util.Optional; +import java.util.OptionalInt; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** The Clique fork. */ +public class CliqueFork implements Fork { + + /** The constant FORK_BLOCK_KEY. */ + public static final String FORK_BLOCK_KEY = "block"; + + /** The constant BLOCK_PERIOD_SECONDS_KEY. */ + public static final String BLOCK_PERIOD_SECONDS_KEY = "blockperiodseconds"; + + /** The constant CREATE_EMPTY_BLOCKS_KEY. */ + public static final String CREATE_EMPTY_BLOCKS_KEY = "createemptyblocks"; + + /** The Fork config root. */ + protected final ObjectNode forkConfigRoot; + + /** + * Instantiates a new Clique fork. + * + * @param forkConfigRoot the fork config root + */ + @JsonCreator + public CliqueFork(final ObjectNode forkConfigRoot) { + this.forkConfigRoot = forkConfigRoot; + } + + /** + * Gets fork block. + * + * @return the fork block + */ + @Override + public long getForkBlock() { + return JsonUtil.getLong(forkConfigRoot, FORK_BLOCK_KEY) + .orElseThrow( + () -> + new IllegalArgumentException( + "Fork block not specified for Clique fork in custom forks")); + } + + /** + * Gets block period seconds. + * + * @return the block period seconds + */ + public OptionalInt getBlockPeriodSeconds() { + return JsonUtil.getPositiveInt(forkConfigRoot, BLOCK_PERIOD_SECONDS_KEY); + } + + /** + * Gets create empty blocks. + * + * @return the create empty blocks + */ + public Optional getCreateEmptyBlocks() { + return JsonUtil.getBoolean(forkConfigRoot, CREATE_EMPTY_BLOCKS_KEY); + } +} diff --git a/config/src/main/java/org/hyperledger/besu/config/Fork.java b/config/src/main/java/org/hyperledger/besu/config/Fork.java new file mode 100644 index 00000000000..77ef6d10423 --- /dev/null +++ b/config/src/main/java/org/hyperledger/besu/config/Fork.java @@ -0,0 +1,26 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.config; + +/** A "custom hard fork" used for Proof of Authority network Transitions */ +public interface Fork { + + /** + * The block number at which the fork occurs. + * + * @return the block number at which the fork occurs + */ + long getForkBlock(); +} diff --git a/config/src/main/java/org/hyperledger/besu/config/GenesisAccount.java b/config/src/main/java/org/hyperledger/besu/config/GenesisAccount.java new file mode 100644 index 00000000000..70bb80e0422 --- /dev/null +++ b/config/src/main/java/org/hyperledger/besu/config/GenesisAccount.java @@ -0,0 +1,42 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.config; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; + +import java.util.Map; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; + +/** + * Genesis account + * + * @param address of the account + * @param nonce nonce of the account at genesis + * @param balance balance of the account at genesis + * @param code code of the account at genesis, can be null + * @param storage storage of the account at genesis + * @param privateKey of the account, only use for testing + */ +public record GenesisAccount( + Address address, + long nonce, + Wei balance, + Bytes code, + Map storage, + Bytes32 privateKey) {} diff --git a/config/src/main/java/org/hyperledger/besu/config/GenesisAllocation.java b/config/src/main/java/org/hyperledger/besu/config/GenesisAllocation.java deleted file mode 100644 index c53ded9e865..00000000000 --- a/config/src/main/java/org/hyperledger/besu/config/GenesisAllocation.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.config; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import com.fasterxml.jackson.databind.node.ObjectNode; - -/** The Genesis allocation configuration. */ -public class GenesisAllocation { - private final String address; - private final ObjectNode data; - - /** - * Instantiates a new Genesis allocation. - * - * @param address the address - * @param data the data - */ - GenesisAllocation(final String address, final ObjectNode data) { - this.address = address; - this.data = data; - } - - /** - * Gets address. - * - * @return the address - */ - public String getAddress() { - return address; - } - - /** - * Gets private key. - * - * @return the private key - */ - public Optional getPrivateKey() { - return Optional.ofNullable(JsonUtil.getString(data, "privatekey", null)); - } - - /** - * Gets balance. - * - * @return the balance - */ - public String getBalance() { - return JsonUtil.getValueAsString(data, "balance", "0"); - } - - /** - * Gets code. - * - * @return the code - */ - public String getCode() { - return JsonUtil.getString(data, "code", null); - } - - /** - * Gets nonce. - * - * @return the nonce - */ - public String getNonce() { - return JsonUtil.getValueAsString(data, "nonce", "0"); - } - - /** - * Gets version. - * - * @return the version - */ - public String getVersion() { - return JsonUtil.getValueAsString(data, "version", null); - } - - /** - * Gets storage map. - * - * @return fields under storage as a map - */ - public Map getStorage() { - final Map map = new HashMap<>(); - JsonUtil.getObjectNode(data, "storage") - .orElse(JsonUtil.createEmptyObjectNode()) - .fields() - .forEachRemaining( - (entry) -> { - map.put(entry.getKey(), entry.getValue().asText()); - }); - return map; - } -} diff --git a/config/src/main/java/org/hyperledger/besu/config/GenesisConfigFile.java b/config/src/main/java/org/hyperledger/besu/config/GenesisConfigFile.java index 638d221e0ad..453c38dac20 100644 --- a/config/src/main/java/org/hyperledger/besu/config/GenesisConfigFile.java +++ b/config/src/main/java/org/hyperledger/besu/config/GenesisConfigFile.java @@ -14,38 +14,37 @@ */ package org.hyperledger.besu.config; -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.hyperledger.besu.config.JsonUtil.normalizeKeys; - import org.hyperledger.besu.datatypes.Wei; -import java.io.IOException; +import java.net.URL; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; +import java.util.stream.Collectors; import java.util.stream.Stream; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.common.collect.Streams; -import com.google.common.io.Resources; /** The Genesis config file. */ public class GenesisConfigFile { /** The constant DEFAULT. */ public static final GenesisConfigFile DEFAULT = - new GenesisConfigFile(JsonUtil.createEmptyObjectNode()); + new GenesisConfigFile(new GenesisReader.FromObjectNode(JsonUtil.createEmptyObjectNode())); /** The constant BASEFEE_AT_GENESIS_DEFAULT_VALUE. */ public static final Wei BASEFEE_AT_GENESIS_DEFAULT_VALUE = Wei.of(1_000_000_000L); - private final ObjectNode configRoot; + private final GenesisReader loader; + private final ObjectNode genesisRoot; + private Map overrides; - private GenesisConfigFile(final ObjectNode config) { - this.configRoot = config; + private GenesisConfigFile(final GenesisReader loader) { + this.loader = loader; + this.genesisRoot = loader.getRoot(); } /** @@ -54,56 +53,47 @@ private GenesisConfigFile(final ObjectNode config) { * @return the genesis config file */ public static GenesisConfigFile mainnet() { - return genesisFileFromResources("/mainnet.json"); + return fromSource(GenesisConfigFile.class.getResource("/mainnet.json")); } /** - * Mainnet json node object node. + * Genesis file from URL. * - * @return the object node + * @param jsonSource the URL + * @return the genesis config file */ - public static ObjectNode mainnetJsonNode() { - try { - final String jsonString = - Resources.toString(GenesisConfigFile.class.getResource("/mainnet.json"), UTF_8); - return JsonUtil.objectNodeFromString(jsonString, false); - } catch (final IOException e) { - throw new IllegalStateException(e); - } + public static GenesisConfigFile fromSource(final URL jsonSource) { + return fromConfig(JsonUtil.objectNodeFromURL(jsonSource, false)); } /** - * Development genesis config file. + * Genesis file from resource. * + * @param resourceName the resource name * @return the genesis config file */ - public static GenesisConfigFile development() { - return genesisFileFromResources("/dev.json"); + public static GenesisConfigFile fromResource(final String resourceName) { + return fromConfig(GenesisConfigFile.class.getResource(resourceName)); } /** - * Genesis file from resources genesis config file. + * From config genesis config file. * - * @param resourceName the resource name + * @param jsonSource the json string * @return the genesis config file */ - public static GenesisConfigFile genesisFileFromResources(final String resourceName) { - try { - return fromConfig( - Resources.toString(GenesisConfigFile.class.getResource(resourceName), UTF_8)); - } catch (final IOException e) { - throw new IllegalStateException(e); - } + public static GenesisConfigFile fromConfig(final URL jsonSource) { + return new GenesisConfigFile(new GenesisReader.FromURL(jsonSource)); } /** * From config genesis config file. * - * @param jsonString the json string + * @param json the json string * @return the genesis config file */ - public static GenesisConfigFile fromConfig(final String jsonString) { - return fromConfig(JsonUtil.objectNodeFromString(jsonString, false)); + public static GenesisConfigFile fromConfig(final String json) { + return fromConfig(JsonUtil.objectNodeFromString(json, false)); } /** @@ -113,55 +103,53 @@ public static GenesisConfigFile fromConfig(final String jsonString) { * @return the genesis config file */ public static GenesisConfigFile fromConfig(final ObjectNode config) { - return new GenesisConfigFile(normalizeKeys(config)); + return new GenesisConfigFile(new GenesisReader.FromObjectNode(config)); } /** - * Gets config options. + * Gets config options, including any overrides. * * @return the config options */ public GenesisConfigOptions getConfigOptions() { - return getConfigOptions(Collections.emptyMap()); - } - - /** - * Gets config options. - * - * @param overrides the overrides - * @return the config options - */ - public GenesisConfigOptions getConfigOptions(final Map overrides) { - final ObjectNode config = - JsonUtil.getObjectNode(configRoot, "config").orElse(JsonUtil.createEmptyObjectNode()); - - Map overridesRef = overrides; + final ObjectNode config = loader.getConfig(); + // are there any overrides to apply? + if (this.overrides == null) { + return JsonGenesisConfigOptions.fromJsonObject(config); + } + // otherwise apply overrides + Map overridesRef = this.overrides; // if baseFeePerGas has been explicitly configured, pass it as an override: final var optBaseFee = getBaseFeePerGas(); if (optBaseFee.isPresent()) { // streams and maps cannot handle null values. - overridesRef = new HashMap<>(overrides); + overridesRef = new HashMap<>(this.overrides); overridesRef.put("baseFeePerGas", optBaseFee.get().toShortHexString()); } return JsonGenesisConfigOptions.fromJsonObjectWithOverrides(config, overridesRef); } + /** + * Sets overrides for genesis options. + * + * @param overrides the overrides + * @return the config options + */ + public GenesisConfigFile withOverrides(final Map overrides) { + + this.overrides = overrides; + return this; + } + /** * Stream allocations stream. * * @return the stream */ - public Stream streamAllocations() { - return JsonUtil.getObjectNode(configRoot, "alloc").stream() - .flatMap( - allocations -> - Streams.stream(allocations.fieldNames()) - .map( - key -> - new GenesisAllocation( - key, JsonUtil.getObjectNode(allocations, key).get()))); + public Stream streamAllocations() { + return loader.streamAllocations(); } /** @@ -170,7 +158,7 @@ public Stream streamAllocations() { * @return the parent hash */ public String getParentHash() { - return JsonUtil.getString(configRoot, "parenthash", ""); + return JsonUtil.getString(genesisRoot, "parenthash", ""); } /** @@ -188,7 +176,7 @@ public String getDifficulty() { * @return the extra data */ public String getExtraData() { - return JsonUtil.getString(configRoot, "extradata", ""); + return JsonUtil.getString(genesisRoot, "extradata", ""); } /** @@ -206,7 +194,7 @@ public long getGasLimit() { * @return the base fee per gas */ public Optional getBaseFeePerGas() { - return JsonUtil.getString(configRoot, "basefeepergas") + return JsonUtil.getString(genesisRoot, "basefeepergas") .map(baseFeeStr -> Wei.of(parseLong("baseFeePerGas", baseFeeStr))); } @@ -235,7 +223,7 @@ public Optional getGenesisBaseFeePerGas() { * @return the mix hash */ public String getMixHash() { - return JsonUtil.getString(configRoot, "mixhash", ""); + return JsonUtil.getString(genesisRoot, "mixhash", ""); } /** @@ -244,7 +232,7 @@ public String getMixHash() { * @return the nonce */ public String getNonce() { - return JsonUtil.getValueAsString(configRoot, "nonce", "0x0"); + return JsonUtil.getValueAsString(genesisRoot, "nonce", "0x0"); } /** @@ -253,7 +241,7 @@ public String getNonce() { * @return the excess blob gas */ public String getExcessBlobGas() { - return JsonUtil.getValueAsString(configRoot, "excessblobgas", "0x0"); + return JsonUtil.getValueAsString(genesisRoot, "excessblobgas", "0x0"); } /** @@ -262,7 +250,7 @@ public String getExcessBlobGas() { * @return the blob gas used */ public String getBlobGasUsed() { - return JsonUtil.getValueAsString(configRoot, "blobgasused", "0x0"); + return JsonUtil.getValueAsString(genesisRoot, "blobgasused", "0x0"); } /** @@ -272,7 +260,7 @@ public String getBlobGasUsed() { */ public String getParentBeaconBlockRoot() { return JsonUtil.getValueAsString( - configRoot, + genesisRoot, "parentbeaconblockroot", "0x0000000000000000000000000000000000000000000000000000000000000000"); } @@ -283,7 +271,7 @@ public String getParentBeaconBlockRoot() { * @return the coinbase */ public Optional getCoinbase() { - return JsonUtil.getString(configRoot, "coinbase"); + return JsonUtil.getString(genesisRoot, "coinbase"); } /** @@ -292,7 +280,7 @@ public Optional getCoinbase() { * @return the timestamp */ public long getTimestamp() { - return parseLong("timestamp", JsonUtil.getValueAsString(configRoot, "timestamp", "0x0")); + return parseLong("timestamp", JsonUtil.getValueAsString(genesisRoot, "timestamp", "0x0")); } private String getRequiredString(final String key) { @@ -302,9 +290,9 @@ private String getRequiredString(final String key) { private String getFirstRequiredString(final String... keys) { List keysList = Arrays.asList(keys); return keysList.stream() - .filter(configRoot::has) + .filter(genesisRoot::has) .findFirst() - .map(key -> configRoot.get(key).asText()) + .map(key -> genesisRoot.get(key).asText()) .orElseThrow( () -> new IllegalArgumentException( @@ -343,4 +331,27 @@ public List getForkBlockNumbers() { public List getForkTimestamps() { return getConfigOptions().getForkBlockTimestamps(); } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final GenesisConfigFile that = (GenesisConfigFile) o; + return Objects.equals(genesisRoot, that.genesisRoot); + } + + @Override + public int hashCode() { + return Objects.hashCode(genesisRoot); + } + + @Override + public String toString() { + return "GenesisConfigFile{" + + "genesisRoot=" + + genesisRoot + + ", allocations=" + + loader.streamAllocations().map(GenesisAccount::toString).collect(Collectors.joining(",")) + + '}'; + } } diff --git a/config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java index a315ce72449..d6323ee9bfb 100644 --- a/config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/GenesisConfigOptions.java @@ -242,6 +242,27 @@ default boolean isConsensusMigration() { */ OptionalLong getCancunTime(); + /** + * Gets cancun EOF time. + * + * @return the cancun EOF time + */ + OptionalLong getCancunEOFTime(); + + /** + * Gets prague time. + * + * @return the prague time + */ + OptionalLong getPragueTime(); + + /** + * Gets Prague EOF time. + * + * @return the prague time + */ + OptionalLong getPragueEOFTime(); + /** * Gets future eips time. * @@ -497,6 +518,20 @@ default boolean isConsensusMigration() { */ boolean isZeroBaseFee(); + /** + * Force a Base Fee as Gas Price network to used with London/EIP-1559. + * + * @return true, if you want the next block to use the base fee as gas price. + */ + boolean isFixedBaseFee(); + + /** + * The withdrawal request predeploy address + * + * @return the withdrawal request predeploy address + */ + Optional
getWithdrawalRequestContractAddress(); + /** * The deposit contract address that should be in the logger field in Receipt of Deposit * transaction diff --git a/config/src/main/java/org/hyperledger/besu/config/GenesisReader.java b/config/src/main/java/org/hyperledger/besu/config/GenesisReader.java new file mode 100644 index 00000000000..316aa73d043 --- /dev/null +++ b/config/src/main/java/org/hyperledger/besu/config/GenesisReader.java @@ -0,0 +1,242 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.config; + +import static org.hyperledger.besu.config.JsonUtil.normalizeKey; +import static org.hyperledger.besu.config.JsonUtil.normalizeKeys; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; + +import java.io.IOException; +import java.math.BigInteger; +import java.net.URL; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.Streams; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; + +interface GenesisReader { + String CONFIG_FIELD = "config"; + String ALLOCATION_FIELD = "alloc"; + + ObjectNode getRoot(); + + ObjectNode getConfig(); + + Stream streamAllocations(); + + class FromObjectNode implements GenesisReader { + private final ObjectNode allocations; + private final ObjectNode rootWithoutAllocations; + + public FromObjectNode(final ObjectNode root) { + final var removedAllocations = root.remove(ALLOCATION_FIELD); + this.allocations = + removedAllocations != null + ? (ObjectNode) removedAllocations + : JsonUtil.createEmptyObjectNode(); + this.rootWithoutAllocations = normalizeKeys(root); + } + + @Override + public ObjectNode getRoot() { + return rootWithoutAllocations; + } + + @Override + public ObjectNode getConfig() { + return JsonUtil.getObjectNode(rootWithoutAllocations, CONFIG_FIELD) + .orElse(JsonUtil.createEmptyObjectNode()); + } + + @Override + public Stream streamAllocations() { + return Streams.stream(allocations.fields()) + .map( + entry -> { + final var on = normalizeKeys((ObjectNode) entry.getValue()); + return new GenesisAccount( + Address.fromHexString(entry.getKey()), + JsonUtil.getString(on, "nonce").map(ParserUtils::parseUnsignedLong).orElse(0L), + JsonUtil.getString(on, "balance") + .map(ParserUtils::parseBalance) + .orElse(Wei.ZERO), + JsonUtil.getBytes(on, "code", null), + ParserUtils.getStorageMap(on, "storage"), + JsonUtil.getBytes(on, "privatekey").map(Bytes32::wrap).orElse(null)); + }); + } + } + + class FromURL implements GenesisReader { + private final URL url; + private final ObjectNode rootWithoutAllocations; + + public FromURL(final URL url) { + this.url = url; + this.rootWithoutAllocations = + normalizeKeys(JsonUtil.objectNodeFromURL(url, false, ALLOCATION_FIELD)); + } + + @Override + public ObjectNode getRoot() { + return rootWithoutAllocations; + } + + @Override + public ObjectNode getConfig() { + return JsonUtil.getObjectNode(rootWithoutAllocations, CONFIG_FIELD) + .orElse(JsonUtil.createEmptyObjectNode()); + } + + @Override + public Stream streamAllocations() { + final var parser = JsonUtil.jsonParserFromURL(url, false); + + try { + parser.nextToken(); + while (parser.nextToken() != JsonToken.END_OBJECT) { + if (ALLOCATION_FIELD.equals(parser.getCurrentName())) { + parser.nextToken(); + parser.nextToken(); + break; + } else { + parser.skipChildren(); + } + } + } catch (final IOException e) { + throw new RuntimeException(e); + } + + return Streams.stream(new AllocationIterator(parser)); + } + + private static class AllocationIterator implements Iterator { + final JsonParser parser; + + public AllocationIterator(final JsonParser parser) { + this.parser = parser; + } + + @Override + public boolean hasNext() { + final var end = parser.currentToken() == JsonToken.END_OBJECT; + if (end) { + try { + parser.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return !end; + } + + @Override + public GenesisAccount next() { + try { + final Address address = Address.fromHexString(parser.currentName()); + long nonce = 0; + Wei balance = Wei.ZERO; + Bytes code = null; + Map storage = Map.of(); + Bytes32 privateKey = null; + parser.nextToken(); // consume start object + while (parser.nextToken() != JsonToken.END_OBJECT) { + switch (normalizeKey(parser.currentName())) { + case "nonce": + parser.nextToken(); + nonce = ParserUtils.parseUnsignedLong(parser.getText()); + break; + case "balance": + parser.nextToken(); + balance = ParserUtils.parseBalance(parser.getText()); + break; + case "code": + parser.nextToken(); + code = Bytes.fromHexStringLenient(parser.getText()); + break; + case "privatekey": + parser.nextToken(); + privateKey = Bytes32.fromHexStringLenient(parser.getText()); + break; + case "storage": + parser.nextToken(); + storage = new HashMap<>(); + while (parser.nextToken() != JsonToken.END_OBJECT) { + final var key = UInt256.fromHexString(parser.currentName()); + parser.nextToken(); + final var value = UInt256.fromHexString(parser.getText()); + storage.put(key, value); + } + break; + } + if (parser.currentToken() == JsonToken.START_OBJECT) { + // ignore any unknown nested object + parser.skipChildren(); + } + } + parser.nextToken(); + return new GenesisAccount(address, nonce, balance, code, storage, privateKey); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + } + + class ParserUtils { + static long parseUnsignedLong(final String value) { + String v = value.toLowerCase(Locale.US); + if (v.startsWith("0x")) { + v = v.substring(2); + } + return Long.parseUnsignedLong(v, 16); + } + + static Wei parseBalance(final String balance) { + final BigInteger val; + if (balance.startsWith("0x")) { + val = new BigInteger(1, Bytes.fromHexStringLenient(balance).toArrayUnsafe()); + } else { + val = new BigInteger(balance); + } + + return Wei.of(val); + } + + static Map getStorageMap(final ObjectNode json, final String key) { + return JsonUtil.getObjectNode(json, key) + .map( + storageMap -> + Streams.stream(storageMap.fields()) + .collect( + Collectors.toMap( + e -> UInt256.fromHexString(e.getKey()), + e -> UInt256.fromHexString(e.getValue().asText())))) + .orElse(Map.of()); + } + } +} diff --git a/config/src/main/java/org/hyperledger/besu/config/JsonBftConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/JsonBftConfigOptions.java index 4dfa49fa3a0..95f2d9f7ce7 100644 --- a/config/src/main/java/org/hyperledger/besu/config/JsonBftConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/JsonBftConfigOptions.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,6 +17,7 @@ import org.hyperledger.besu.datatypes.Address; import java.math.BigInteger; +import java.util.Locale; import java.util.Map; import java.util.Optional; @@ -117,7 +118,7 @@ public BigInteger getBlockRewardWei() { return BigInteger.ZERO; } final String weiStr = configFileContent.get(); - if (weiStr.toLowerCase().startsWith("0x")) { + if (weiStr.toLowerCase(Locale.ROOT).startsWith("0x")) { return new BigInteger(1, Bytes.fromHexStringLenient(weiStr).toArrayUnsafe()); } return new BigInteger(weiStr); diff --git a/config/src/main/java/org/hyperledger/besu/config/JsonCliqueConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/JsonCliqueConfigOptions.java new file mode 100644 index 00000000000..e57d7edb8f8 --- /dev/null +++ b/config/src/main/java/org/hyperledger/besu/config/JsonCliqueConfigOptions.java @@ -0,0 +1,90 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.config; + +import java.util.Map; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.ImmutableMap; + +/** The Clique config options. */ +public class JsonCliqueConfigOptions implements CliqueConfigOptions { + + private static final long DEFAULT_EPOCH_LENGTH = 30_000; + private static final int DEFAULT_BLOCK_PERIOD_SECONDS = 15; + private static final boolean DEFAULT_CREATE_EMPTY_BLOCKS = true; + + private final ObjectNode cliqueConfigRoot; + + /** The constant DEFAULT. */ + public static final JsonCliqueConfigOptions DEFAULT = + new JsonCliqueConfigOptions(JsonUtil.createEmptyObjectNode()); + + /** + * Instantiates a new Clique config options. + * + * @param cliqueConfigRoot the clique config root + */ + JsonCliqueConfigOptions(final ObjectNode cliqueConfigRoot) { + this.cliqueConfigRoot = cliqueConfigRoot; + } + + /** + * The number of blocks in an epoch. + * + * @return the epoch length + */ + @Override + public long getEpochLength() { + return JsonUtil.getLong(cliqueConfigRoot, "epochlength", DEFAULT_EPOCH_LENGTH); + } + + /** + * Gets block period seconds. + * + * @return the block period seconds + */ + @Override + public int getBlockPeriodSeconds() { + return JsonUtil.getPositiveInt( + cliqueConfigRoot, "blockperiodseconds", DEFAULT_BLOCK_PERIOD_SECONDS); + } + + /** + * Whether the creation of empty blocks is allowed. + * + * @return the create empty block status + */ + @Override + public boolean getCreateEmptyBlocks() { + return JsonUtil.getBoolean(cliqueConfigRoot, "createemptyblocks", DEFAULT_CREATE_EMPTY_BLOCKS); + } + + /** + * As map. + * + * @return the map + */ + @Override + public Map asMap() { + return ImmutableMap.of( + "epochLength", + getEpochLength(), + "blockPeriodSeconds", + getBlockPeriodSeconds(), + "createemptyblocks", + getCreateEmptyBlocks()); + } +} diff --git a/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java index 29eaef94468..67114b29bf3 100644 --- a/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/JsonGenesisConfigOptions.java @@ -24,11 +24,11 @@ import java.math.BigInteger; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.OptionalInt; import java.util.OptionalLong; import java.util.TreeMap; -import java.util.stream.Collectors; import java.util.stream.Stream; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -48,6 +48,9 @@ public class JsonGenesisConfigOptions implements GenesisConfigOptions { private static final String DISCOVERY_CONFIG_KEY = "discovery"; private static final String CHECKPOINT_CONFIG_KEY = "checkpoint"; private static final String ZERO_BASE_FEE_KEY = "zerobasefee"; + private static final String FIXED_BASE_FEE_KEY = "fixedbasefee"; + private static final String WITHDRAWAL_REQUEST_CONTRACT_ADDRESS_KEY = + "withdrawalrequestcontractaddress"; private static final String DEPOSIT_CONTRACT_ADDRESS_KEY = "depositcontractaddress"; private final ObjectNode configRoot; @@ -181,10 +184,10 @@ public CheckpointConfigOptions getCheckpointOptions() { } @Override - public CliqueConfigOptions getCliqueConfigOptions() { + public JsonCliqueConfigOptions getCliqueConfigOptions() { return JsonUtil.getObjectNode(configRoot, CLIQUE_CONFIG_KEY) - .map(CliqueConfigOptions::new) - .orElse(CliqueConfigOptions.DEFAULT); + .map(JsonCliqueConfigOptions::new) + .orElse(JsonCliqueConfigOptions.DEFAULT); } @Override @@ -287,11 +290,26 @@ public OptionalLong getShanghaiTime() { return getOptionalLong("shanghaitime"); } + @Override + public OptionalLong getCancunEOFTime() { + return getOptionalLong("cancuneoftime"); + } + @Override public OptionalLong getCancunTime() { return getOptionalLong("cancuntime"); } + @Override + public OptionalLong getPragueTime() { + return getOptionalLong("praguetime"); + } + + @Override + public OptionalLong getPragueEOFTime() { + return getOptionalLong("pragueeoftime"); + } + @Override public OptionalLong getFutureEipsTime() { return getOptionalLong("futureeipstime"); @@ -304,10 +322,7 @@ public OptionalLong getExperimentalEipsTime() { @Override public Optional getBaseFeePerGas() { - return Optional.ofNullable(configOverrides.get("baseFeePerGas")) - .map(Wei::fromHexString) - .map(Optional::of) - .orElse(Optional.empty()); + return Optional.ofNullable(configOverrides.get("baseFeePerGas")).map(Wei::fromHexString); } @Override @@ -420,6 +435,18 @@ public boolean isZeroBaseFee() { return getOptionalBoolean(ZERO_BASE_FEE_KEY).orElse(false); } + @Override + public boolean isFixedBaseFee() { + return getOptionalBoolean(FIXED_BASE_FEE_KEY).orElse(false); + } + + @Override + public Optional
getWithdrawalRequestContractAddress() { + Optional inputAddress = + JsonUtil.getString(configRoot, WITHDRAWAL_REQUEST_CONTRACT_ADDRESS_KEY); + return inputAddress.map(Address::fromHexString); + } + @Override public Optional
getDepositContractAddress() { Optional inputAddress = JsonUtil.getString(configRoot, DEPOSIT_CONTRACT_ADDRESS_KEY); @@ -448,6 +475,9 @@ public Map asMap() { getMergeNetSplitBlockNumber().ifPresent(l -> builder.put("mergeNetSplitBlock", l)); getShanghaiTime().ifPresent(l -> builder.put("shanghaiTime", l)); getCancunTime().ifPresent(l -> builder.put("cancunTime", l)); + getCancunEOFTime().ifPresent(l -> builder.put("cancunEOFTime", l)); + getPragueTime().ifPresent(l -> builder.put("pragueTime", l)); + getPragueEOFTime().ifPresent(l -> builder.put("pragueEOFTime", l)); getTerminalBlockNumber().ifPresent(l -> builder.put("terminalBlockNumber", l)); getTerminalBlockHash().ifPresent(h -> builder.put("terminalBlockHash", h.toHexString())); getFutureEipsTime().ifPresent(l -> builder.put("futureEipsTime", l)); @@ -471,6 +501,8 @@ public Map asMap() { getEvmStackSize().ifPresent(l -> builder.put("evmstacksize", l)); getEcip1017EraRounds().ifPresent(l -> builder.put("ecip1017EraRounds", l)); + getWithdrawalRequestContractAddress() + .ifPresent(l -> builder.put("withdrawalRequestContractAddress", l)); getDepositContractAddress().ifPresent(l -> builder.put("depositContractAddress", l)); if (isClique()) { @@ -490,6 +522,10 @@ public Map asMap() { builder.put("zeroBaseFee", true); } + if (isFixedBaseFee()) { + builder.put("fixedBaseFee", true); + } + return builder.build(); } @@ -582,14 +618,20 @@ public List getForkBlockNumbers() { .map(OptionalLong::getAsLong) .distinct() .sorted() - .collect(Collectors.toList()); + .toList(); } @Override public List getForkBlockTimestamps() { Stream forkBlockTimestamps = Stream.of( - getShanghaiTime(), getCancunTime(), getFutureEipsTime(), getExperimentalEipsTime()); + getShanghaiTime(), + getCancunTime(), + getCancunEOFTime(), + getPragueTime(), + getPragueEOFTime(), + getFutureEipsTime(), + getExperimentalEipsTime()); // when adding forks add an entry to ${REPO_ROOT}/config/src/test/resources/all_forks.json return forkBlockTimestamps @@ -597,6 +639,20 @@ public List getForkBlockTimestamps() { .map(OptionalLong::getAsLong) .distinct() .sorted() - .collect(Collectors.toList()); + .toList(); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final JsonGenesisConfigOptions that = (JsonGenesisConfigOptions) o; + return Objects.equals(configRoot, that.configRoot) + && Objects.equals(configOverrides, that.configOverrides); + } + + @Override + public int hashCode() { + return Objects.hash(configRoot, configOverrides); } } diff --git a/config/src/main/java/org/hyperledger/besu/config/JsonQbftConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/JsonQbftConfigOptions.java index a9bcf750f2e..3db29e5b764 100644 --- a/config/src/main/java/org/hyperledger/besu/config/JsonQbftConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/JsonQbftConfigOptions.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -26,8 +26,10 @@ public class JsonQbftConfigOptions extends JsonBftConfigOptions implements QbftC /** The constant DEFAULT. */ public static final JsonQbftConfigOptions DEFAULT = new JsonQbftConfigOptions(JsonUtil.createEmptyObjectNode()); + /** The constant VALIDATOR_CONTRACT_ADDRESS. */ public static final String VALIDATOR_CONTRACT_ADDRESS = "validatorcontractaddress"; + /** The constant START_BLOCK. */ public static final String START_BLOCK = "startblock"; diff --git a/config/src/main/java/org/hyperledger/besu/config/JsonUtil.java b/config/src/main/java/org/hyperledger/besu/config/JsonUtil.java index 45c933d09d8..bcb89c64edb 100644 --- a/config/src/main/java/org/hyperledger/besu/config/JsonUtil.java +++ b/config/src/main/java/org/hyperledger/besu/config/JsonUtil.java @@ -17,22 +17,38 @@ import org.hyperledger.besu.util.number.PositiveNumber; import java.io.IOException; +import java.net.URL; import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.OptionalInt; import java.util.OptionalLong; +import java.util.Set; +import java.util.function.Function; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonParser.Feature; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.filter.FilteringParserDelegate; +import com.fasterxml.jackson.core.filter.TokenFilter; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.JsonNodeType; import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.tuweni.bytes.Bytes; /** The Json util class. */ public class JsonUtil { + private static final JsonFactory JSON_FACTORY = + JsonFactory.builder() + .disable(JsonFactory.Feature.INTERN_FIELD_NAMES) + .disable(JsonFactory.Feature.CANONICALIZE_FIELD_NAMES) + .build(); + + /** Default constructor. */ + private JsonUtil() {} /** * Converts all the object keys (but none of the string values) to lowercase for easier lookup. @@ -50,7 +66,7 @@ public static ObjectNode normalizeKeys(final ObjectNode objectNode) { entry -> { final String key = entry.getKey(); final JsonNode value = entry.getValue(); - final String normalizedKey = key.toLowerCase(Locale.US); + final String normalizedKey = normalizeKey(key); if (value instanceof ObjectNode) { normalized.set(normalizedKey, normalizeKeys((ObjectNode) value)); } else if (value instanceof ArrayNode) { @@ -62,6 +78,17 @@ public static ObjectNode normalizeKeys(final ObjectNode objectNode) { return normalized; } + /** + * Converts the key to lowercase for easier lookup. This is useful in cases such as the + * 'genesis.json' file where all keys are assumed to be case insensitive. + * + * @param key the key to be normalized + * @return key in lower case. + */ + public static String normalizeKey(final String key) { + return key.toLowerCase(Locale.US); + } + private static ArrayNode normalizeKeysInArray(final ArrayNode arrayNode) { final ArrayNode normalizedArray = JsonUtil.createEmptyArrayNode(); arrayNode.forEach( @@ -260,6 +287,35 @@ public static boolean getBoolean( return getBoolean(node, key).orElse(defaultValue); } + /** + * Gets Bytes. + * + * @param json the json + * @param key the key + * @return the Bytes + */ + public static Optional getBytes(final ObjectNode json, final String key) { + return getParsedValue(json, key, Bytes::fromHexString); + } + + /** + * Gets Wei. + * + * @param json the json + * @param key the key + * @param defaultValue the default value + * @return the Wei + */ + public static Bytes getBytes(final ObjectNode json, final String key, final Bytes defaultValue) { + return getBytes(json, key).orElse(defaultValue); + } + + private static Optional getParsedValue( + final ObjectNode json, final String name, final Function parser) { + + return getValue(json, name).map(JsonNode::asText).map(parser); + } + /** * Create empty object node object node. * @@ -287,7 +343,7 @@ public static ArrayNode createEmptyArrayNode() { * @return the object node */ public static ObjectNode objectNodeFromMap(final Map map) { - return (ObjectNode) getObjectMapper().valueToTree(map); + return getObjectMapper().valueToTree(map); } /** @@ -305,14 +361,92 @@ public static ObjectNode objectNodeFromString(final String jsonData) { * * @param jsonData the json data * @param allowComments true to allow comments + * @param excludeFields names of the fields to not read * @return the object node */ public static ObjectNode objectNodeFromString( - final String jsonData, final boolean allowComments) { + final String jsonData, final boolean allowComments, final String... excludeFields) { + try { + return objectNodeFromParser( + JSON_FACTORY.createParser(jsonData), allowComments, excludeFields); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Object node from string object node. + * + * @param jsonSource the json data + * @param allowComments true to allow comments + * @param excludeFields names of the fields to not read + * @return the object node + */ + public static ObjectNode objectNodeFromURL( + final URL jsonSource, final boolean allowComments, final String... excludeFields) { + try { + return objectNodeFromParser( + JSON_FACTORY.createParser(jsonSource).enable(Feature.AUTO_CLOSE_SOURCE), + allowComments, + excludeFields); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Get a JsonParser to parse JSON from URL. + * + * @param jsonSource the json source + * @param allowComments true to allow comments + * @return the json parser + */ + public static JsonParser jsonParserFromURL(final URL jsonSource, final boolean allowComments) { + try { + return JSON_FACTORY + .createParser(jsonSource) + .enable(Feature.AUTO_CLOSE_SOURCE) + .configure(Feature.ALLOW_COMMENTS, allowComments); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static ObjectNode objectNodeFromParser( + final JsonParser baseParser, final boolean allowComments, final String... excludeFields) { + try { + final var parser = + excludeFields.length > 0 + ? new FilteringParserDelegate( + baseParser, + new NameExcludeFilter(excludeFields), + TokenFilter.Inclusion.INCLUDE_ALL_AND_PATH, + true) + : baseParser; + parser.configure(Feature.ALLOW_COMMENTS, allowComments); + + final ObjectMapper objectMapper = new ObjectMapper(); + final JsonNode jsonNode = objectMapper.readTree(parser); + validateType(jsonNode, JsonNodeType.OBJECT); + return (ObjectNode) jsonNode; + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Object node from URL. + * + * @param jsonSource the URL of the json source + * @param allowComments true to allow comments + * @return the object node + */ + public static ObjectNode objectNodeFromURL(final URL jsonSource, final boolean allowComments) { + final ObjectMapper objectMapper = new ObjectMapper(); objectMapper.configure(Feature.ALLOW_COMMENTS, allowComments); try { - final JsonNode jsonNode = objectMapper.readTree(jsonData); + final JsonNode jsonNode = objectMapper.readTree(jsonSource); validateType(jsonNode, JsonNodeType.OBJECT); return (ObjectNode) jsonNode; } catch (final IOException e) { @@ -446,7 +580,8 @@ private static boolean validateType(final JsonNode node, final JsonNodeType expe final String errorMessage = String.format( "Expected %s value but got %s", - expectedType.toString().toLowerCase(), node.getNodeType().toString().toLowerCase()); + expectedType.toString().toLowerCase(Locale.ROOT), + node.getNodeType().toString().toLowerCase(Locale.ROOT)); throw new IllegalArgumentException(errorMessage); } return true; @@ -454,15 +589,41 @@ private static boolean validateType(final JsonNode node, final JsonNodeType expe private static boolean validateLong(final JsonNode node) { if (!node.canConvertToLong()) { - throw new IllegalArgumentException("Cannot convert value to long: " + node.toString()); + throw new IllegalArgumentException("Cannot convert value to long: " + node); } return true; } private static boolean validateInt(final JsonNode node) { if (!node.canConvertToInt()) { - throw new IllegalArgumentException("Cannot convert value to integer: " + node.toString()); + throw new IllegalArgumentException("Cannot convert value to integer: " + node); } return true; } + + private static class NameExcludeFilter extends TokenFilter { + private final Set names; + + public NameExcludeFilter(final String... names) { + this.names = Set.of(names); + } + + @Override + public TokenFilter includeProperty(final String name) { + if (names.contains(name)) { + return null; + } + return this; + } + + @Override + public boolean includeEmptyObject(final boolean contentsFiltered) { + return !contentsFiltered; + } + + @Override + public boolean includeEmptyArray(final boolean contentsFiltered) { + return !contentsFiltered; + } + } } diff --git a/config/src/main/java/org/hyperledger/besu/config/MergeConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/MergeConfigOptions.java index bfc9cd3e784..53faf690521 100644 --- a/config/src/main/java/org/hyperledger/besu/config/MergeConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/MergeConfigOptions.java @@ -20,6 +20,9 @@ public class MergeConfigOptions { private static final AtomicBoolean mergeEnabled = new AtomicBoolean(false); + /** Default constructor. */ + private MergeConfigOptions() {} + /** * Enables merge. * diff --git a/config/src/main/java/org/hyperledger/besu/config/QbftFork.java b/config/src/main/java/org/hyperledger/besu/config/QbftFork.java index b080a557499..14d49e9ac2f 100644 --- a/config/src/main/java/org/hyperledger/besu/config/QbftFork.java +++ b/config/src/main/java/org/hyperledger/besu/config/QbftFork.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.config; import java.util.Arrays; @@ -34,6 +33,7 @@ public enum VALIDATOR_SELECTION_MODE { /** The constant VALIDATOR_SELECTION_MODE_KEY. */ public static final String VALIDATOR_SELECTION_MODE_KEY = "validatorselectionmode"; + /** The constant VALIDATOR_CONTRACT_ADDRESS_KEY. */ public static final String VALIDATOR_CONTRACT_ADDRESS_KEY = "validatorcontractaddress"; diff --git a/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java index d36b823c745..efe56a086d0 100644 --- a/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/StubGenesisConfigOptions.java @@ -48,6 +48,9 @@ public class StubGenesisConfigOptions implements GenesisConfigOptions, Cloneable private OptionalLong mergeNetSplitBlockNumber = OptionalLong.empty(); private OptionalLong shanghaiTime = OptionalLong.empty(); private OptionalLong cancunTime = OptionalLong.empty(); + private OptionalLong cancunEOFTime = OptionalLong.empty(); + private OptionalLong pragueTime = OptionalLong.empty(); + private OptionalLong pragueEOFTime = OptionalLong.empty(); private OptionalLong futureEipsTime = OptionalLong.empty(); private OptionalLong experimentalEipsTime = OptionalLong.empty(); private OptionalLong terminalBlockNumber = OptionalLong.empty(); @@ -77,6 +80,12 @@ public class StubGenesisConfigOptions implements GenesisConfigOptions, Cloneable private TransitionsConfigOptions transitions = TransitionsConfigOptions.DEFAULT; private static final DiscoveryOptions DISCOVERY_OPTIONS = DiscoveryOptions.DEFAULT; private boolean zeroBaseFee = false; + private boolean fixedBaseFee = false; + + /** Default constructor. */ + public StubGenesisConfigOptions() { + // Explicit default constructor because of JavaDoc linting + } @Override public StubGenesisConfigOptions clone() { @@ -128,8 +137,8 @@ public CheckpointConfigOptions getCheckpointOptions() { } @Override - public CliqueConfigOptions getCliqueConfigOptions() { - return CliqueConfigOptions.DEFAULT; + public JsonCliqueConfigOptions getCliqueConfigOptions() { + return JsonCliqueConfigOptions.DEFAULT; } @Override @@ -232,6 +241,21 @@ public OptionalLong getCancunTime() { return cancunTime; } + @Override + public OptionalLong getCancunEOFTime() { + return cancunEOFTime; + } + + @Override + public OptionalLong getPragueTime() { + return pragueTime; + } + + @Override + public OptionalLong getPragueEOFTime() { + return pragueEOFTime; + } + @Override public OptionalLong getFutureEipsTime() { return futureEipsTime; @@ -364,6 +388,7 @@ public Map asMap() { getMergeNetSplitBlockNumber().ifPresent(l -> builder.put("mergeNetSplitBlock", l)); getShanghaiTime().ifPresent(l -> builder.put("shanghaiTime", l)); getCancunTime().ifPresent(l -> builder.put("cancunTime", l)); + getPragueTime().ifPresent(l -> builder.put("pragueTime", l)); getFutureEipsTime().ifPresent(l -> builder.put("futureEipsTime", l)); getExperimentalEipsTime().ifPresent(l -> builder.put("experimentalEipsTime", l)); getTerminalBlockNumber().ifPresent(l -> builder.put("terminalBlockNumber", l)); @@ -417,6 +442,11 @@ public boolean isZeroBaseFee() { return zeroBaseFee; } + @Override + public boolean isFixedBaseFee() { + return fixedBaseFee; + } + @Override public List getForkBlockNumbers() { return Collections.emptyList(); @@ -427,6 +457,11 @@ public List getForkBlockTimestamps() { return Collections.emptyList(); } + @Override + public Optional
getWithdrawalRequestContractAddress() { + return Optional.empty(); + } + @Override public Optional
getDepositContractAddress() { return Optional.empty(); @@ -608,6 +643,40 @@ public StubGenesisConfigOptions cancunTime(final long timestamp) { return this; } + /** + * Cancun EOF time. + * + * @param timestamp the timestamp + * @return the stub genesis config options + */ + public StubGenesisConfigOptions cancunEOFTime(final long timestamp) { + cancunEOFTime = OptionalLong.of(timestamp); + return this; + } + + /** + * Prague time. + * + * @param timestamp the timestamp + * @return the stub genesis config options + */ + public StubGenesisConfigOptions pragueTime(final long timestamp) { + pragueTime = OptionalLong.of(timestamp); + return this; + } + + /** + * PragueEOF time. + * + * @param timestamp the timestamp + * @return the stub genesis config options + */ + public StubGenesisConfigOptions pragueEOFTime(final long timestamp) { + pragueTime = OptionalLong.of(timestamp); + pragueEOFTime = pragueTime; + return this; + } + /** * Future EIPs Time block. * @@ -686,6 +755,17 @@ public StubGenesisConfigOptions zeroBaseFee(final boolean zeroBaseFee) { return this; } + /** + * Fixed base fee per gas stub genesis config options. + * + * @param fixedBaseFee the zero base fee override + * @return the stub genesis config options + */ + public StubGenesisConfigOptions fixedBaseFee(final boolean fixedBaseFee) { + this.fixedBaseFee = fixedBaseFee; + return this; + } + /** * Classic fork block stub genesis config options. * diff --git a/config/src/main/java/org/hyperledger/besu/config/TransitionsConfigOptions.java b/config/src/main/java/org/hyperledger/besu/config/TransitionsConfigOptions.java index 0b6687efce0..6a2125aa805 100644 --- a/config/src/main/java/org/hyperledger/besu/config/TransitionsConfigOptions.java +++ b/config/src/main/java/org/hyperledger/besu/config/TransitionsConfigOptions.java @@ -51,7 +51,7 @@ public TransitionsConfigOptions(final ObjectNode customForkConfigRoot) { * @return the ibft forks */ public List getIbftForks() { - return getBftForks("ibft2", BftFork::new); + return getForks("ibft2", BftFork::new); } /** @@ -60,30 +60,39 @@ public List getIbftForks() { * @return the qbft forks */ public List getQbftForks() { - return getBftForks("qbft", QbftFork::new); + return getForks("qbft", QbftFork::new); } - private List getBftForks( + /** + * Gets clique forks. + * + * @return the clique forks + */ + public List getCliqueForks() { + return getForks("clique", CliqueFork::new); + } + + private List getForks( final String fieldKey, final Function forkConstructor) { - final Optional bftForksNode = JsonUtil.getArrayNode(customForkConfigRoot, fieldKey); + final Optional forksNode = JsonUtil.getArrayNode(customForkConfigRoot, fieldKey); - if (bftForksNode.isEmpty()) { + if (forksNode.isEmpty()) { return emptyList(); } - final List bftForks = Lists.newArrayList(); + final List forks = Lists.newArrayList(); - bftForksNode + forksNode .get() .elements() .forEachRemaining( node -> { if (!node.isObject()) { - throw new IllegalArgumentException("Bft fork is illegally formatted."); + throw new IllegalArgumentException("Transition is illegally formatted."); } - bftForks.add(forkConstructor.apply((ObjectNode) node)); + forks.add(forkConstructor.apply((ObjectNode) node)); }); - return Collections.unmodifiableList(bftForks); + return Collections.unmodifiableList(forks); } } diff --git a/config/src/main/resources/classic.json b/config/src/main/resources/classic.json index efbc5be4fcf..ea7c49e1fa3 100644 --- a/config/src/main/resources/classic.json +++ b/config/src/main/resources/classic.json @@ -13,10 +13,12 @@ "thanosBlock": 11700000, "magnetoBlock": 13189133, "mystiqueBlock": 14525000, + "spiralBlock": 19250000, "ethash": { }, "discovery" : { + "dns": "enrtree://AJE62Q4DUX4QMMXEHCSSCSC65TDHZYSMONSD64P3WULVLSF6MRQ3K@all.classic.blockd.info", "bootnodes" : [ "enode://8e73168affd8d445edda09c561d607081ca5d7963317caae2702f701eb6546b06948b7f8687a795de576f6a5f33c44828e25a90aa63de18db380a11e660dd06f@159.203.37.80:30303", "enode://2b1ef75e8b7119b6e0294f2e51ead2cf1a5400472452c199e9587727ada99e7e2b1199e36adcad6cbae65dce2410559546e4d83d8c93d45a559e723e56444c03@67.207.93.100:30303", diff --git a/config/src/main/resources/goerli.json b/config/src/main/resources/goerli.json deleted file mode 100644 index 5ccc9e420ad..00000000000 --- a/config/src/main/resources/goerli.json +++ /dev/null @@ -1,822 +0,0 @@ -{ - "config":{ - "chainId":5, - "petersburgBlock":0, - "istanbulBlock":1561651, - "berlinBlock":4460644, - "londonBlock":5062605, - "terminalTotalDifficulty": 10790000, - "shanghaiTime": 1678832736, - "clique":{ - "blockperiodseconds":15, - "epochlength":30000 - }, - "discovery": { - "dns": "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@all.goerli.ethdisco.net", - "bootnodes": [ - "enode://011f758e6552d105183b1761c5e2dea0111bc20fd5f6422bc7f91e0fabbec9a6595caf6239b37feb773dddd3f87240d99d859431891e4a642cf2a0a9e6cbb98a@51.141.78.53:30303", - "enode://176b9417f511d05b6b2cf3e34b756cf0a7096b3094572a8f6ef4cdcb9d1f9d00683bf0f83347eebdf3b81c3521c2332086d9592802230bf528eaf606a1d9677b@13.93.54.137:30303", - "enode://46add44b9f13965f7b9875ac6b85f016f341012d84f975377573800a863526f4da19ae2c620ec73d11591fa9510e992ecc03ad0751f53cc02f7c7ed6d55c7291@94.237.54.114:30313", - "enode://b5948a2d3e9d486c4d75bf32713221c2bd6cf86463302339299bd227dc2e276cd5a1c7ca4f43a0e9122fe9af884efed563bd2a1fd28661f3b5f5ad7bf1de5949@18.218.250.66:30303", - "enode://a61215641fb8714a373c80edbfa0ea8878243193f57c96eeb44d0bc019ef295abd4e044fd619bfc4c59731a73fb79afe84e9ab6da0c743ceb479cbb6d263fa91@3.11.147.67:30303", - "enode://d4f764a48ec2a8ecf883735776fdefe0a3949eb0ca476bd7bc8d0954a9defe8fea15ae5da7d40b5d2d59ce9524a99daedadf6da6283fca492cc80b53689fb3b3@46.4.99.122:32109", - "enode://d2b720352e8216c9efc470091aa91ddafc53e222b32780f505c817ceef69e01d5b0b0797b69db254c586f493872352f5a022b4d8479a00fc92ec55f9ad46a27e@88.99.70.182:30303" - ] - }, - "checkpoint": { - "hash": "0x50e55c39a725f062af438c5332a5c5bec9a36d02c829ee6ac2cc27d1db719446", - "number": 4350000, - "totalDifficulty": "0x61DBBF", - "_comment": "must be the beginning of a clique epoch" - } - }, - "coinbase":"0x0000000000000000000000000000000000000000", - "difficulty":"0x1", - "extraData":"0x22466c6578692069732061207468696e6722202d204166726900000000000000e0a2bd4258d2768837baa26a28fe71dc079f84c70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "gasLimit":"0xa00000", - "mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000", - "nonce":"0x0", - "timestamp":"0x5c51a607", - "alloc":{ - "0000000000000000000000000000000000000000":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000001":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000002":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000003":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000004":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000005":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000006":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000007":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000008":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000009":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000000a":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000000b":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000000c":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000000d":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000000e":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000000f":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000010":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000011":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000012":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000013":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000014":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000015":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000016":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000017":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000018":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000019":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000001a":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000001b":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000001c":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000001d":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000001e":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000001f":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000020":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000021":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000022":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000023":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000024":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000025":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000026":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000027":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000028":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000029":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000002a":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000002b":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000002c":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000002d":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000002e":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000002f":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000030":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000031":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000032":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000033":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000034":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000035":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000036":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000037":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000038":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000039":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000003a":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000003b":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000003c":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000003d":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000003e":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000003f":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000040":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000041":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000042":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000043":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000044":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000045":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000046":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000047":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000048":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000049":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000004a":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000004b":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000004c":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000004d":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000004e":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000004f":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000050":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000051":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000052":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000053":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000054":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000055":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000056":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000057":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000058":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000059":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000005a":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000005b":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000005c":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000005d":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000005e":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000005f":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000060":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000061":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000062":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000063":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000064":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000065":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000066":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000067":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000068":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000069":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000006a":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000006b":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000006c":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000006d":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000006e":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000006f":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000070":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000071":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000072":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000073":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000074":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000075":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000076":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000077":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000078":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000079":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000007a":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000007b":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000007c":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000007d":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000007e":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000007f":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000080":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000081":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000082":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000083":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000084":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000085":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000086":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000087":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000088":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000089":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000008a":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000008b":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000008c":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000008d":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000008e":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000008f":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000090":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000091":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000092":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000093":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000094":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000095":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000096":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000097":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000098":{ - "balance":"0x1" - }, - "0000000000000000000000000000000000000099":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000009a":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000009b":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000009c":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000009d":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000009e":{ - "balance":"0x1" - }, - "000000000000000000000000000000000000009f":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000a0":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000a1":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000a2":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000a3":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000a4":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000a5":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000a6":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000a7":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000a8":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000a9":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000aa":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000ab":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000ac":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000ad":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000ae":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000af":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000b0":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000b1":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000b2":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000b3":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000b4":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000b5":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000b6":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000b7":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000b8":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000b9":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000ba":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000bb":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000bc":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000bd":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000be":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000bf":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000c0":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000c1":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000c2":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000c3":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000c4":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000c5":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000c6":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000c7":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000c8":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000c9":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000ca":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000cb":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000cc":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000cd":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000ce":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000cf":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000d0":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000d1":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000d2":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000d3":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000d4":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000d5":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000d6":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000d7":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000d8":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000d9":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000da":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000db":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000dc":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000dd":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000de":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000df":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000e0":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000e1":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000e2":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000e3":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000e4":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000e5":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000e6":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000e7":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000e8":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000e9":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000ea":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000eb":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000ec":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000ed":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000ee":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000ef":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000f0":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000f1":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000f2":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000f3":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000f4":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000f5":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000f6":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000f7":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000f8":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000f9":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000fa":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000fb":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000fc":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000fd":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000fe":{ - "balance":"0x1" - }, - "00000000000000000000000000000000000000ff":{ - "balance":"0x1" - }, - "4c2ae482593505f0163cdefc073e81c63cda4107": { - "balance": "0x152d02c7e14af6800000" - }, - "a8e8f14732658e4b51e8711931053a8a69baf2b1": { - "balance": "0x152d02c7e14af6800000" - }, - "d9a5179f091d85051d3c982785efd1455cec8699": { - "balance": "0x84595161401484a000000" - }, - "e0a2bd4258d2768837baa26a28fe71dc079f84c7": { - "balance": "0x4a47e3c12448f4ad000000" - } - } -} diff --git a/config/src/main/resources/holesky.json b/config/src/main/resources/holesky.json index a8ae193c856..d91971b3515 100644 --- a/config/src/main/resources/holesky.json +++ b/config/src/main/resources/holesky.json @@ -14,11 +14,14 @@ "preMergeForkBlock": 0, "terminalTotalDifficulty": 0, "shanghaiTime": 1696000704, + "cancunTime": 1707305664, "ethash": {}, "discovery": { + "dns": "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@all.holesky.ethdisco.net", "bootnodes": [ "enode://ac906289e4b7f12df423d654c5a962b6ebe5b3a74cc9e06292a85221f9a64a6f1cfdd6b714ed6dacef51578f92b34c60ee91e9ede9c7f8fadc4d347326d95e2b@146.190.13.128:30303", - "enode://a3435a0155a3e837c02f5e7f5662a2f1fbc25b48e4dc232016e1c51b544cb5b4510ef633ea3278c0e970fa8ad8141e2d4d0f9f95456c537ff05fdf9b31c15072@178.128.136.233:30303" + "enode://a3435a0155a3e837c02f5e7f5662a2f1fbc25b48e4dc232016e1c51b544cb5b4510ef633ea3278c0e970fa8ad8141e2d4d0f9f95456c537ff05fdf9b31c15072@178.128.136.233:30303", + "enode://7fa09f1e8bb179ab5e73f45d3a7169a946e7b3de5ef5cea3a0d4546677e4435ee38baea4dd10b3ddfdc1f1c5e869052932af8b8aeb6f9738598ec4590d0b11a6@65.109.94.124:30303" ] } }, diff --git a/config/src/main/resources/lukso.json b/config/src/main/resources/lukso.json new file mode 100644 index 00000000000..ff4296eae84 --- /dev/null +++ b/config/src/main/resources/lukso.json @@ -0,0 +1,852 @@ +{ + "config": { + "ethash": {}, + "chainId": 42, + "homesteadBlock": 0, + "eip150Block": 0, + "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "mergeForkBlock": 0, + "terminalTotalDifficulty": 0, + "terminalTotalDifficultyPassed": true, + "shanghaiTime": 1687969198, + "cancunTime": 1767182400, + "discovery": { + "bootnodes": [ + "enode://c2bb19ce658cfdf1fecb45da599ee6c7bf36e5292efb3fb61303a0b2cd07f96c20ac9b376a464d687ac456675a2e4a44aec39a0509bcb4b6d8221eedec25aca2@34.147.73.193:30303", + "enode://276f14e4049840a0f5aa5e568b772ab6639251149a52ba244647277175b83f47b135f3b3d8d846cf81a8e681684e37e9fc10ec205a9841d3ae219aa08aa9717b@34.32.192.211:30303" + ] + } + }, + "number": "0x0", + "nonce": "0x1", + "difficulty": "0x1", + "gasLimit": "0x280de80", + "gasUsed": "0x0", + "timestamp": "0x646CE7B0", + "extraData": "0x416e642074686520776f726c6420626563616d65206f6e65", + "coinbase": "0x0000000000000000000000000000000000000000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "alloc": { + "0x953534215D31D0f2756b8A855aaFB7E641848c2e": { + "balance": "11143518000000000000000000" + }, + "0x5a4154edddE64858664F6219e031B6a47BA30993": { + "balance": "30525730000000000000000000" + }, + "0x0000000000000000000000000000000000000000": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000001": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000002": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000003": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000004": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000005": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000006": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000007": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000008": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000009": { + "balance": "1" + }, + "0x000000000000000000000000000000000000000a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000000b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000000c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000000d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000000e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000000f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000010": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000011": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000012": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000013": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000014": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000015": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000016": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000017": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000018": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000019": { + "balance": "1" + }, + "0x000000000000000000000000000000000000001a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000001b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000001c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000001d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000001e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000001f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000020": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000021": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000022": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000023": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000024": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000025": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000026": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000027": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000028": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000029": { + "balance": "1" + }, + "0x000000000000000000000000000000000000002a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000002b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000002c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000002d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000002e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000002f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000030": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000031": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000032": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000033": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000034": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000035": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000036": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000037": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000038": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000039": { + "balance": "1" + }, + "0x000000000000000000000000000000000000003a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000003b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000003c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000003d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000003e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000003f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000040": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000041": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000042": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000043": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000044": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000045": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000046": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000047": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000048": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000049": { + "balance": "1" + }, + "0x000000000000000000000000000000000000004a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000004b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000004c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000004d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000004e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000004f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000050": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000051": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000052": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000053": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000054": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000055": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000056": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000057": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000058": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000059": { + "balance": "1" + }, + "0x000000000000000000000000000000000000005a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000005b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000005c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000005d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000005e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000005f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000060": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000061": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000062": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000063": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000064": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000065": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000066": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000067": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000068": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000069": { + "balance": "1" + }, + "0x000000000000000000000000000000000000006a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000006b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000006c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000006d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000006e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000006f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000070": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000071": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000072": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000073": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000074": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000075": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000076": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000077": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000078": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000079": { + "balance": "1" + }, + "0x000000000000000000000000000000000000007a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000007b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000007c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000007d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000007e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000007f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000080": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000081": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000082": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000083": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000084": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000085": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000086": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000087": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000088": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000089": { + "balance": "1" + }, + "0x000000000000000000000000000000000000008a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000008b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000008c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000008d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000008e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000008f": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000090": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000091": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000092": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000093": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000094": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000095": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000096": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000097": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000098": { + "balance": "1" + }, + "0x0000000000000000000000000000000000000099": { + "balance": "1" + }, + "0x000000000000000000000000000000000000009a": { + "balance": "1" + }, + "0x000000000000000000000000000000000000009b": { + "balance": "1" + }, + "0x000000000000000000000000000000000000009c": { + "balance": "1" + }, + "0x000000000000000000000000000000000000009d": { + "balance": "1" + }, + "0x000000000000000000000000000000000000009e": { + "balance": "1" + }, + "0x000000000000000000000000000000000000009f": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a0": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a1": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a2": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a3": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a4": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a5": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a6": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a7": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a8": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000a9": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000aa": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ab": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ac": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ad": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ae": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000af": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b0": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b1": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b2": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b3": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b4": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b5": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b6": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b7": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b8": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000b9": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ba": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000bb": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000bc": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000bd": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000be": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000bf": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c0": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c1": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c2": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c3": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c4": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c5": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c6": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c7": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c8": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000c9": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ca": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000cb": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000cc": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000cd": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ce": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000cf": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d0": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d1": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d2": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d3": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d4": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d5": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d6": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d7": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d8": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000d9": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000da": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000db": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000dc": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000dd": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000de": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000df": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e0": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e1": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e2": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e3": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e4": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e5": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e6": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e7": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e8": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000e9": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ea": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000eb": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ec": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ed": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ee": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ef": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f0": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f1": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f2": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f3": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f4": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f5": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f6": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f7": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f8": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000f9": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000fa": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000fb": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000fc": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000fd": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000fe": { + "balance": "1" + }, + "0x00000000000000000000000000000000000000ff": { + "balance": "1" + }, + "0xcafe00000000000000000000000000000000cafe": { + "balance": "0", + "code": "0x60806040526004361061003f5760003560e01c806301ffc9a71461004457806322895118146100a4578063621fd130146101ba578063c5f2892f14610244575b600080fd5b34801561005057600080fd5b506100906004803603602081101561006757600080fd5b50357fffffffff000000000000000000000000000000000000000000000000000000001661026b565b604080519115158252519081900360200190f35b6101b8600480360360808110156100ba57600080fd5b8101906020810181356401000000008111156100d557600080fd5b8201836020820111156100e757600080fd5b8035906020019184600183028401116401000000008311171561010957600080fd5b91939092909160208101903564010000000081111561012757600080fd5b82018360208201111561013957600080fd5b8035906020019184600183028401116401000000008311171561015b57600080fd5b91939092909160208101903564010000000081111561017957600080fd5b82018360208201111561018b57600080fd5b803590602001918460018302840111640100000000831117156101ad57600080fd5b919350915035610304565b005b3480156101c657600080fd5b506101cf6110b5565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102095781810151838201526020016101f1565b50505050905090810190601f1680156102365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561025057600080fd5b506102596110c7565b60408051918252519081900360200190f35b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a70000000000000000000000000000000000000000000000000000000014806102fe57507fffffffff0000000000000000000000000000000000000000000000000000000082167f8564090700000000000000000000000000000000000000000000000000000000145b92915050565b6030861461035d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118056026913960400191505060405180910390fd5b602084146103b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603681526020018061179c6036913960400191505060405180910390fd5b6060821461040f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806118786029913960400191505060405180910390fd5b670de0b6b3a7640000341015610470576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118526026913960400191505060405180910390fd5b633b9aca003406156104cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260338152602001806117d26033913960400191505060405180910390fd5b633b9aca00340467ffffffffffffffff811115610535576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602781526020018061182b6027913960400191505060405180910390fd5b6060610540826114ba565b90507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c589898989858a8a6105756020546114ba565b6040805160a0808252810189905290819060208201908201606083016080840160c085018e8e80828437600083820152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690910187810386528c815260200190508c8c808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690920188810386528c5181528c51602091820193918e019250908190849084905b83811015610648578181015183820152602001610630565b50505050905090810190601f1680156106755780820380516001836020036101000a031916815260200191505b5086810383528881526020018989808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169092018881038452895181528951602091820193918b019250908190849084905b838110156106ef5781810151838201526020016106d7565b50505050905090810190601f16801561071c5780820380516001836020036101000a031916815260200191505b509d505050505050505050505050505060405180910390a1600060028a8a600060801b604051602001808484808284377fffffffffffffffffffffffffffffffff0000000000000000000000000000000090941691909301908152604080517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0818403018152601090920190819052815191955093508392506020850191508083835b602083106107fc57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016107bf565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610859573d6000803e3d6000fd5b5050506040513d602081101561086e57600080fd5b5051905060006002806108846040848a8c6116fe565b6040516020018083838082843780830192505050925050506040516020818303038152906040526040518082805190602001908083835b602083106108f857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016108bb565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610955573d6000803e3d6000fd5b5050506040513d602081101561096a57600080fd5b5051600261097b896040818d6116fe565b60405160009060200180848480828437919091019283525050604080518083038152602092830191829052805190945090925082918401908083835b602083106109f457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016109b7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610a51573d6000803e3d6000fd5b5050506040513d6020811015610a6657600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610ada57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610a9d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610b37573d6000803e3d6000fd5b5050506040513d6020811015610b4c57600080fd5b50516040805160208101858152929350600092600292839287928f928f92018383808284378083019250505093505050506040516020818303038152906040526040518082805190602001908083835b60208310610bd957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610b9c565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610c36573d6000803e3d6000fd5b5050506040513d6020811015610c4b57600080fd5b50516040518651600291889160009188916020918201918291908601908083835b60208310610ca957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610c6c565b6001836020036101000a0380198251168184511680821785525050505050509050018367ffffffffffffffff191667ffffffffffffffff1916815260180182815260200193505050506040516020818303038152906040526040518082805190602001908083835b60208310610d4e57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610d11565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610dab573d6000803e3d6000fd5b5050506040513d6020811015610dc057600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610e3457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610df7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610e91573d6000803e3d6000fd5b5050506040513d6020811015610ea657600080fd5b50519050858114610f02576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260548152602001806117486054913960600191505060405180910390fd5b60205463ffffffff11610f60576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806117276021913960400191505060405180910390fd5b602080546001019081905560005b60208110156110a9578160011660011415610fa0578260008260208110610f9157fe5b0155506110ac95505050505050565b600260008260208110610faf57fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061102557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610fe8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015611082573d6000803e3d6000fd5b5050506040513d602081101561109757600080fd5b50519250600282049150600101610f6e565b50fe5b50505050505050565b60606110c26020546114ba565b905090565b6020546000908190815b60208110156112f05781600116600114156111e6576002600082602081106110f557fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061116b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161112e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156111c8573d6000803e3d6000fd5b5050506040513d60208110156111dd57600080fd5b505192506112e2565b600283602183602081106111f657fe5b015460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061126b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161122e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156112c8573d6000803e3d6000fd5b5050506040513d60208110156112dd57600080fd5b505192505b6002820491506001016110d1565b506002826112ff6020546114ba565b600060401b6040516020018084815260200183805190602001908083835b6020831061135a57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161131d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790527fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000095909516920191825250604080518083037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8018152601890920190819052815191955093508392850191508083835b6020831061143f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101611402565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa15801561149c573d6000803e3d6000fd5b5050506040513d60208110156114b157600080fd5b50519250505090565b60408051600880825281830190925260609160208201818036833701905050905060c082901b8060071a60f81b826000815181106114f457fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060061a60f81b8260018151811061153757fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060051a60f81b8260028151811061157a57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060041a60f81b826003815181106115bd57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060031a60f81b8260048151811061160057fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060021a60f81b8260058151811061164357fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060011a60f81b8260068151811061168657fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060001a60f81b826007815181106116c957fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535050919050565b6000808585111561170d578182fd5b83861115611719578182fd5b505082019391909203915056fe4465706f736974436f6e74726163743a206d65726b6c6520747265652066756c6c4465706f736974436f6e74726163743a207265636f6e7374727563746564204465706f7369744461746120646f6573206e6f74206d6174636820737570706c696564206465706f7369745f646174615f726f6f744465706f736974436f6e74726163743a20696e76616c6964207769746864726177616c5f63726564656e7469616c73206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c7565206e6f74206d756c7469706c65206f6620677765694465706f736974436f6e74726163743a20696e76616c6964207075626b6579206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f20686967684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f206c6f774465706f736974436f6e74726163743a20696e76616c6964207369676e6174757265206c656e677468a26469706673582212201dd26f37a621703009abf16e77e69c93dc50c79db7f6cc37543e3e0e3decdc9764736f6c634300060b0033", + "storage": { + "0x00000000000000000000000000000000000000000000000000000000000022":"0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", + "0x00000000000000000000000000000000000000000000000000000000000023":"0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", + "0x00000000000000000000000000000000000000000000000000000000000024":"0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c", + "0x00000000000000000000000000000000000000000000000000000000000025":"0x536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c", + "0x00000000000000000000000000000000000000000000000000000000000026":"0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30", + "0x00000000000000000000000000000000000000000000000000000000000027":"0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1", + "0x00000000000000000000000000000000000000000000000000000000000028":"0x87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c", + "0x00000000000000000000000000000000000000000000000000000000000029":"0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193", + "0x0000000000000000000000000000000000000000000000000000000000002a":"0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1", + "0x0000000000000000000000000000000000000000000000000000000000002b":"0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b", + "0x0000000000000000000000000000000000000000000000000000000000002c":"0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220", + "0x0000000000000000000000000000000000000000000000000000000000002d":"0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f", + "0x0000000000000000000000000000000000000000000000000000000000002e":"0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e", + "0x0000000000000000000000000000000000000000000000000000000000002f":"0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784", + "0x00000000000000000000000000000000000000000000000000000000000030":"0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb", + "0x00000000000000000000000000000000000000000000000000000000000031":"0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb", + "0x00000000000000000000000000000000000000000000000000000000000032":"0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab", + "0x00000000000000000000000000000000000000000000000000000000000033":"0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", + "0x00000000000000000000000000000000000000000000000000000000000034":"0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f", + "0x00000000000000000000000000000000000000000000000000000000000035":"0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", + "0x00000000000000000000000000000000000000000000000000000000000036":"0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", + "0x00000000000000000000000000000000000000000000000000000000000037":"0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", + "0x00000000000000000000000000000000000000000000000000000000000038":"0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", + "0x00000000000000000000000000000000000000000000000000000000000039":"0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", + "0x0000000000000000000000000000000000000000000000000000000000003a":"0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", + "0x0000000000000000000000000000000000000000000000000000000000003b":"0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", + "0x0000000000000000000000000000000000000000000000000000000000003c":"0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", + "0x0000000000000000000000000000000000000000000000000000000000003d":"0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", + "0x0000000000000000000000000000000000000000000000000000000000003e":"0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", + "0x0000000000000000000000000000000000000000000000000000000000003f":"0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", + "0x00000000000000000000000000000000000000000000000000000000000040":"0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7" + } + } + } +} + diff --git a/config/src/main/resources/mainnet.json b/config/src/main/resources/mainnet.json index 645ddb09d47..ab35a5aba35 100644 --- a/config/src/main/resources/mainnet.json +++ b/config/src/main/resources/mainnet.json @@ -15,6 +15,7 @@ "grayGlacierBlock": 15050000, "terminalTotalDifficulty": 58750000000000000000000, "shanghaiTime": 1681338455, + "cancunTime": 1710338135, "ethash": { }, "discovery": { diff --git a/config/src/main/resources/profiles/dev.toml b/config/src/main/resources/profiles/dev.toml new file mode 100644 index 00000000000..3c80770b4ef --- /dev/null +++ b/config/src/main/resources/profiles/dev.toml @@ -0,0 +1 @@ +network="DEV" \ No newline at end of file diff --git a/config/src/main/resources/profiles/enterprise-private.toml b/config/src/main/resources/profiles/enterprise-private.toml new file mode 100644 index 00000000000..ea87d5cfc18 --- /dev/null +++ b/config/src/main/resources/profiles/enterprise-private.toml @@ -0,0 +1,10 @@ +sync-mode="FULL" +data-storage-format="FOREST" +sync-min-peers=1 +remote-connections-limit-enabled=false +tx-pool="SEQUENCED" +tx-pool-no-local-priority=true +tx-pool-limit-by-account-percentage=0.15 +rpc-http-max-active-connections=300 +min-gas-price=0 +bonsai-limit-trie-logs-enabled=false \ No newline at end of file diff --git a/config/src/main/resources/profiles/minimalist-staker.toml b/config/src/main/resources/profiles/minimalist-staker.toml new file mode 100644 index 00000000000..cfda132e374 --- /dev/null +++ b/config/src/main/resources/profiles/minimalist-staker.toml @@ -0,0 +1,3 @@ +sync-mode="CHECKPOINT" +data-storage-format="BONSAI" +max-peers=25 \ No newline at end of file diff --git a/config/src/main/resources/profiles/staker.toml b/config/src/main/resources/profiles/staker.toml new file mode 100644 index 00000000000..4a2fd4618fc --- /dev/null +++ b/config/src/main/resources/profiles/staker.toml @@ -0,0 +1 @@ +network="MAINNET" \ No newline at end of file diff --git a/config/src/main/resources/sepolia.json b/config/src/main/resources/sepolia.json index 8a0799ce0cc..713e46ff640 100644 --- a/config/src/main/resources/sepolia.json +++ b/config/src/main/resources/sepolia.json @@ -14,6 +14,7 @@ "mergeNetSplitBlock": 1735371, "terminalTotalDifficulty": 17000000000000000, "shanghaiTime": 1677557088, + "cancunTime": 1706655072, "ethash":{}, "discovery": { "dns": "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@all.sepolia.ethdisco.net", diff --git a/config/src/test/java/org/hyperledger/besu/config/BFTForkTest.java b/config/src/test/java/org/hyperledger/besu/config/BFTForkTest.java index 2c3499601f1..e40b3a370fd 100644 --- a/config/src/test/java/org/hyperledger/besu/config/BFTForkTest.java +++ b/config/src/test/java/org/hyperledger/besu/config/BFTForkTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/config/src/test/java/org/hyperledger/besu/config/CliqueConfigOptionsTest.java b/config/src/test/java/org/hyperledger/besu/config/CliqueConfigOptionsTest.java index 355dc8f8aa0..f445730ac18 100644 --- a/config/src/test/java/org/hyperledger/besu/config/CliqueConfigOptionsTest.java +++ b/config/src/test/java/org/hyperledger/besu/config/CliqueConfigOptionsTest.java @@ -43,7 +43,7 @@ public void shouldFallbackToDefaultEpochLength() { @Test public void shouldGetDefaultEpochLengthFromDefaultConfig() { - assertThat(CliqueConfigOptions.DEFAULT.getEpochLength()) + assertThat(JsonCliqueConfigOptions.DEFAULT.getEpochLength()) .isEqualTo(EXPECTED_DEFAULT_EPOCH_LENGTH); } @@ -61,7 +61,7 @@ public void shouldFallbackToDefaultBlockPeriod() { @Test public void shouldGetDefaultBlockPeriodFromDefaultConfig() { - assertThat(CliqueConfigOptions.DEFAULT.getBlockPeriodSeconds()) + assertThat(JsonCliqueConfigOptions.DEFAULT.getBlockPeriodSeconds()) .isEqualTo(EXPECTED_DEFAULT_BLOCK_PERIOD); } diff --git a/config/src/test/java/org/hyperledger/besu/config/GenesisConfigFileTest.java b/config/src/test/java/org/hyperledger/besu/config/GenesisConfigFileTest.java index c61e97bf0b8..3e6b488b9bd 100644 --- a/config/src/test/java/org/hyperledger/besu/config/GenesisConfigFileTest.java +++ b/config/src/test/java/org/hyperledger/besu/config/GenesisConfigFileTest.java @@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.hyperledger.besu.config.GenesisConfigFile.fromConfig; +import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; import java.io.IOException; @@ -38,19 +39,23 @@ import org.assertj.core.api.ThrowableAssert.ThrowingCallable; import org.junit.jupiter.api.Test; -public class GenesisConfigFileTest { +class GenesisConfigFileTest { private static final BigInteger MAINNET_CHAIN_ID = BigInteger.ONE; private static final BigInteger DEVELOPMENT_CHAIN_ID = BigInteger.valueOf(1337); private static final GenesisConfigFile EMPTY_CONFIG = fromConfig("{}"); @Test - public void shouldLoadMainnetConfigFile() { + void shouldLoadMainnetConfigFile() { final GenesisConfigFile config = GenesisConfigFile.mainnet(); // Sanity check some basic properties to confirm this is the mainnet file. assertThat(config.getConfigOptions().isEthHash()).isTrue(); assertThat(config.getConfigOptions().getChainId()).hasValue(MAINNET_CHAIN_ID); - assertThat(config.streamAllocations().map(GenesisAllocation::getAddress)) + assertThat( + config + .streamAllocations() + .map(GenesisAccount::address) + .map(Address::toUnprefixedHexString)) .contains( "000d836201318ec6899a67540690382780743280", "001762430ea9c3a26e5749afdb70da5f78ddbb8c", @@ -58,12 +63,16 @@ public void shouldLoadMainnetConfigFile() { } @Test - public void shouldLoadDevelopmentConfigFile() { - final GenesisConfigFile config = GenesisConfigFile.development(); + void shouldLoadDevelopmentConfigFile() { + final GenesisConfigFile config = GenesisConfigFile.fromResource("/dev.json"); // Sanity check some basic properties to confirm this is the dev file. assertThat(config.getConfigOptions().isEthHash()).isTrue(); assertThat(config.getConfigOptions().getChainId()).hasValue(DEVELOPMENT_CHAIN_ID); - assertThat(config.streamAllocations().map(GenesisAllocation::getAddress)) + assertThat( + config + .streamAllocations() + .map(GenesisAccount::address) + .map(Address::toUnprefixedHexString)) .contains( "fe3b557e8fb62b89f4916b721be55ceb828dbd73", "627306090abab3a6e1400e9345bc60c78a8bef57", @@ -71,82 +80,82 @@ public void shouldLoadDevelopmentConfigFile() { } @Test - public void shouldGetParentHash() { + void shouldGetParentHash() { assertThat(configWithProperty("parentHash", "844633").getParentHash()).isEqualTo("844633"); } @Test - public void shouldDefaultParentHashToEmptyString() { + void shouldDefaultParentHashToEmptyString() { assertThat(EMPTY_CONFIG.getParentHash()).isEmpty(); } @Test - public void shouldGetDifficulty() { + void shouldGetDifficulty() { assertThat(configWithProperty("difficulty", "1234").getDifficulty()).isEqualTo("1234"); } @Test - public void shouldRequireDifficulty() { + void shouldRequireDifficulty() { assertInvalidConfiguration(EMPTY_CONFIG::getDifficulty); } @Test - public void shouldGetExtraData() { + void shouldGetExtraData() { assertThat(configWithProperty("extraData", "yay").getExtraData()).isEqualTo("yay"); } @Test - public void shouldDefaultExtraDataToEmptyString() { + void shouldDefaultExtraDataToEmptyString() { assertThat(EMPTY_CONFIG.getExtraData()).isEmpty(); } @Test - public void shouldGetGasLimit() { + void shouldGetGasLimit() { assertThat(configWithProperty("gasLimit", "1000").getGasLimit()).isEqualTo(1000); } @Test - public void shouldRequireGasLimit() { + void shouldRequireGasLimit() { assertInvalidConfiguration(EMPTY_CONFIG::getGasLimit); } @Test - public void shouldGetMixHash() { + void shouldGetMixHash() { assertThat(configWithProperty("mixHash", "asdf").getMixHash()).isEqualTo("asdf"); } @Test - public void shouldDefaultMixHashToEmptyString() { + void shouldDefaultMixHashToEmptyString() { assertThat(EMPTY_CONFIG.getMixHash()).isEmpty(); } @Test - public void shouldGetNonce() { + void shouldGetNonce() { assertThat(configWithProperty("nonce", "0x10").getNonce()).isEqualTo("0x10"); } @Test - public void shouldDefaultNonceToZero() { + void shouldDefaultNonceToZero() { assertThat(EMPTY_CONFIG.getNonce()).isEqualTo("0x0"); } @Test - public void shouldGetCoinbase() { + void shouldGetCoinbase() { assertThat(configWithProperty("coinbase", "abcd").getCoinbase()).contains("abcd"); } @Test - public void shouldReturnEmptyWhenCoinbaseNotSpecified() { + void shouldReturnEmptyWhenCoinbaseNotSpecified() { assertThat(EMPTY_CONFIG.getCoinbase()).isEmpty(); } @Test - public void shouldGetTimestamp() { + void shouldGetTimestamp() { assertThat(configWithProperty("timestamp", "0x10").getTimestamp()).isEqualTo(16L); } @Test - public void shouldGetBaseFeeAtGenesis() { + void shouldGetBaseFeeAtGenesis() { GenesisConfigFile withBaseFeeAtGenesis = GenesisConfigFile.fromConfig("{\"config\":{\"londonBlock\":0},\"baseFeePerGas\":\"0xa\"}"); assertThat(withBaseFeeAtGenesis.getBaseFeePerGas()).isPresent(); @@ -154,7 +163,7 @@ public void shouldGetBaseFeeAtGenesis() { } @Test - public void shouldGetDefaultBaseFeeAtGenesis() { + void shouldGetDefaultBaseFeeAtGenesis() { GenesisConfigFile withBaseFeeAtGenesis = GenesisConfigFile.fromConfig("{\"config\":{\"londonBlock\":0}}"); // no specified baseFeePerGas: @@ -165,7 +174,7 @@ public void shouldGetDefaultBaseFeeAtGenesis() { } @Test - public void shouldGetBaseFeeExplicitlyAtGenesis() { + void shouldGetBaseFeeExplicitlyAtGenesis() { GenesisConfigFile withBaseFeeNotAtGenesis = GenesisConfigFile.fromConfig("{\"config\":{\"londonBlock\":10},\"baseFeePerGas\":\"0xa\"}"); // specified baseFeePerGas: @@ -176,14 +185,16 @@ public void shouldGetBaseFeeExplicitlyAtGenesis() { } @Test - public void shouldOverrideConfigOptionsBaseFeeWhenSpecified() { + void shouldOverrideConfigOptionsBaseFeeWhenSpecified() { GenesisConfigOptions withOverrides = - EMPTY_CONFIG.getConfigOptions(Map.of("baseFeePerGas", Wei.of(8).toString())); + EMPTY_CONFIG + .withOverrides(Map.of("baseFeePerGas", Wei.of(8).toString())) + .getConfigOptions(); assertThat(withOverrides.getBaseFeePerGas()).contains(Wei.of(8L)); } @Test - public void shouldGetTerminalTotalDifficultyAtGenesis() { + void shouldGetTerminalTotalDifficultyAtGenesis() { GenesisConfigFile withTerminalTotalDifficultyAtGenesis = fromConfig("{\"config\":{\"terminalTotalDifficulty\":1000}}"); assertThat(withTerminalTotalDifficultyAtGenesis.getConfigOptions().getTerminalTotalDifficulty()) @@ -191,14 +202,14 @@ public void shouldGetTerminalTotalDifficultyAtGenesis() { } @Test - public void shouldGetEmptyTerminalTotalDifficultyAtGenesis() { + void shouldGetEmptyTerminalTotalDifficultyAtGenesis() { assertThat(EMPTY_CONFIG.getConfigOptions().getTerminalTotalDifficulty()).isNotPresent(); } @Test - public void assertSepoliaTerminalTotalDifficulty() { + void assertSepoliaTerminalTotalDifficulty() { GenesisConfigOptions sepoliaOptions = - GenesisConfigFile.genesisFileFromResources("/sepolia.json").getConfigOptions(); + GenesisConfigFile.fromResource("/sepolia.json").getConfigOptions(); assertThat(sepoliaOptions.getTerminalTotalDifficulty()).isPresent(); assertThat(sepoliaOptions.getTerminalTotalDifficulty()) @@ -206,19 +217,9 @@ public void assertSepoliaTerminalTotalDifficulty() { } @Test - public void assertGoerliTerminalTotalDifficulty() { - GenesisConfigOptions goerliOptions = - GenesisConfigFile.genesisFileFromResources("/goerli.json").getConfigOptions(); - - assertThat(goerliOptions.getTerminalTotalDifficulty()).isPresent(); - assertThat(goerliOptions.getTerminalTotalDifficulty()) - .contains(UInt256.valueOf(new BigInteger("10790000"))); - } - - @Test - public void assertMainnetTerminalTotalDifficulty() { + void assertMainnetTerminalTotalDifficulty() { GenesisConfigOptions mainnetOptions = - GenesisConfigFile.genesisFileFromResources("/mainnet.json").getConfigOptions(); + GenesisConfigFile.fromResource("/mainnet.json").getConfigOptions(); assertThat(mainnetOptions.getTerminalTotalDifficulty()).isPresent(); // tentative as of 2022-08-11: @@ -227,10 +228,11 @@ public void assertMainnetTerminalTotalDifficulty() { } @Test - public void assertTerminalTotalDifficultyOverride() { + void assertTerminalTotalDifficultyOverride() { GenesisConfigOptions sepoliaOverrideOptions = - GenesisConfigFile.genesisFileFromResources("/sepolia.json") - .getConfigOptions(Map.of("terminalTotalDifficulty", String.valueOf(Long.MAX_VALUE))); + GenesisConfigFile.fromResource("/sepolia.json") + .withOverrides(Map.of("terminalTotalDifficulty", String.valueOf(Long.MAX_VALUE))) + .getConfigOptions(); assertThat(sepoliaOverrideOptions.getTerminalTotalDifficulty()).isPresent(); assertThat(sepoliaOverrideOptions.getTerminalTotalDifficulty()) @@ -238,7 +240,7 @@ public void assertTerminalTotalDifficultyOverride() { } @Test - public void shouldFindMergeNetSplitForkAndAlias() { + void shouldFindMergeNetSplitForkAndAlias() { GenesisConfigFile mergeNetSplitGenesis = GenesisConfigFile.fromConfig( "{\"config\":{\"mergeNetsplitBlock\":11},\"baseFeePerGas\":\"0xa\"}"); @@ -255,12 +257,12 @@ public void shouldFindMergeNetSplitForkAndAlias() { } @Test - public void shouldDefaultTimestampToZero() { + void shouldDefaultTimestampToZero() { assertThat(EMPTY_CONFIG.getTimestamp()).isZero(); } @Test - public void shouldGetAllocations() { + void shouldGetAllocations() { final GenesisConfigFile config = fromConfig( "{" @@ -281,41 +283,51 @@ public void shouldGetAllocations() { + " }" + "}"); - final Map allocations = + final Map allocations = config .streamAllocations() - .collect(Collectors.toMap(GenesisAllocation::getAddress, Function.identity())); - assertThat(allocations) - .containsOnlyKeys( + .collect(Collectors.toMap(GenesisAccount::address, Function.identity())); + assertThat(allocations.keySet()) + .map(Address::toUnprefixedHexString) + .containsOnly( "fe3b557e8fb62b89f4916b721be55ceb828dbd73", "627306090abab3a6e1400e9345bc60c78a8bef57", "f17f52151ebef6c7334fad080c5704d77216b732"); - final GenesisAllocation alloc1 = allocations.get("fe3b557e8fb62b89f4916b721be55ceb828dbd73"); - final GenesisAllocation alloc2 = allocations.get("627306090abab3a6e1400e9345bc60c78a8bef57"); - final GenesisAllocation alloc3 = allocations.get("f17f52151ebef6c7334fad080c5704d77216b732"); - - assertThat(alloc1.getBalance()).isEqualTo("0xad78ebc5ac6200000"); - assertThat(alloc2.getBalance()).isEqualTo("1000"); - assertThat(alloc3.getBalance()).isEqualTo("90000000000000000000000"); - assertThat(alloc3.getStorage()).hasSize(2); - assertThat(alloc3.getStorage()) + final GenesisAccount alloc1 = + allocations.get(Address.fromHexString("fe3b557e8fb62b89f4916b721be55ceb828dbd73")); + final GenesisAccount alloc2 = + allocations.get(Address.fromHexString("627306090abab3a6e1400e9345bc60c78a8bef57")); + final GenesisAccount alloc3 = + allocations.get(Address.fromHexString("f17f52151ebef6c7334fad080c5704d77216b732")); + + assertThat(alloc1.balance()) + .isEqualTo(GenesisReader.ParserUtils.parseBalance("0xad78ebc5ac6200000")); + assertThat(alloc2.balance()).isEqualTo(GenesisReader.ParserUtils.parseBalance("1000")); + assertThat(alloc3.balance()) + .isEqualTo(GenesisReader.ParserUtils.parseBalance("90000000000000000000000")); + assertThat(alloc3.storage()).hasSize(2); + assertThat(alloc3.storage()) .containsEntry( - "0xc4c3a3f99b26e5e534b71d6f33ca6ea5c174decfb16dd7237c60eff9774ef4a4", - "0x937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0"); - assertThat(alloc3.getStorage()) + UInt256.fromHexString( + "0xc4c3a3f99b26e5e534b71d6f33ca6ea5c174decfb16dd7237c60eff9774ef4a4"), + UInt256.fromHexString( + "0x937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0")); + assertThat(alloc3.storage()) .containsEntry( - "0xc4c3a3f99b26e5e534b71d6f33ca6ea5c174decfb16dd7237c60eff9774ef4a3", - "0x6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012"); + UInt256.fromHexString( + "0xc4c3a3f99b26e5e534b71d6f33ca6ea5c174decfb16dd7237c60eff9774ef4a3"), + UInt256.fromHexString( + "0x6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012")); } @Test - public void shouldGetEmptyAllocationsWhenAllocNotPresent() { + void shouldGetEmptyAllocationsWhenAllocNotPresent() { final GenesisConfigFile config = fromConfig("{}"); assertThat(config.streamAllocations()).isEmpty(); } @Test - public void shouldGetLargeChainId() { + void shouldGetLargeChainId() { final GenesisConfigFile config = fromConfig( "{\"config\": { \"chainId\": 31415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095 }}"); @@ -326,7 +338,7 @@ public void shouldGetLargeChainId() { } @Test - public void mustNotAcceptComments() { + void mustNotAcceptComments() { assertThatThrownBy( () -> fromConfig( @@ -336,8 +348,8 @@ public void mustNotAcceptComments() { } @Test - public void testOverridePresent() { - final GenesisConfigFile config = GenesisConfigFile.development(); + void testOverridePresent() { + final GenesisConfigFile config = GenesisConfigFile.fromResource("/dev.json"); final int bigBlock = 999_999_999; final String bigBlockString = Integer.toString(bigBlock); final Map override = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); @@ -346,29 +358,33 @@ public void testOverridePresent() { override.put("contractSizeLimit", bigBlockString); assertThat(config.getForkBlockNumbers()).isNotEmpty(); - assertThat(config.getConfigOptions(override).getIstanbulBlockNumber()).hasValue(bigBlock); - assertThat(config.getConfigOptions(override).getChainId()) + assertThat(config.withOverrides(override).getConfigOptions().getIstanbulBlockNumber()) + .hasValue(bigBlock); + assertThat(config.withOverrides(override).getConfigOptions().getChainId()) .hasValue(BigInteger.valueOf(bigBlock)); - assertThat(config.getConfigOptions(override).getContractSizeLimit()).hasValue(bigBlock); + assertThat(config.withOverrides(override).getConfigOptions().getContractSizeLimit()) + .hasValue(bigBlock); } @Test - public void testOverrideNull() { - final GenesisConfigFile config = GenesisConfigFile.development(); + void testOverrideNull() { + final GenesisConfigFile config = GenesisConfigFile.fromResource("/dev.json"); final Map override = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); override.put("istanbulBlock", null); override.put("chainId", null); override.put("contractSizeLimit", null); assertThat(config.getForkBlockNumbers()).isNotEmpty(); - assertThat(config.getConfigOptions(override).getIstanbulBlockNumber()).isNotPresent(); - assertThat(config.getConfigOptions(override).getChainId()).isNotPresent(); - assertThat(config.getConfigOptions(override).getContractSizeLimit()).isNotPresent(); + assertThat(config.withOverrides(override).getConfigOptions().getIstanbulBlockNumber()) + .isNotPresent(); + assertThat(config.withOverrides(override).getConfigOptions().getChainId()).isNotPresent(); + assertThat(config.withOverrides(override).getConfigOptions().getContractSizeLimit()) + .isNotPresent(); } @Test - public void testOverrideCaseInsensitivity() { - final GenesisConfigFile config = GenesisConfigFile.development(); + void testOverrideCaseInsensitivity() { + final GenesisConfigFile config = GenesisConfigFile.fromResource("/dev.json"); final int bigBlock = 999_999_999; final String bigBlockString = Integer.toString(bigBlock); final Map override = new HashMap<>(); @@ -379,28 +395,32 @@ public void testOverrideCaseInsensitivity() { // all lower case override.put("contractsizelimit", bigBlockString); - assertThat(config.getConfigOptions(override).getIstanbulBlockNumber()).hasValue(bigBlock); - assertThat(config.getConfigOptions(override).getChainId()) + assertThat(config.withOverrides(override).getConfigOptions().getIstanbulBlockNumber()) + .hasValue(bigBlock); + assertThat(config.withOverrides(override).getConfigOptions().getChainId()) .hasValue(BigInteger.valueOf(bigBlock)); - assertThat(config.getConfigOptions(override).getContractSizeLimit()).hasValue(bigBlock); + assertThat(config.withOverrides(override).getConfigOptions().getContractSizeLimit()) + .hasValue(bigBlock); } @Test - public void testOverrideEmptyString() { - final GenesisConfigFile config = GenesisConfigFile.development(); + void testOverrideEmptyString() { + final GenesisConfigFile config = GenesisConfigFile.fromResource("/dev.json"); final Map override = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); override.put("istanbulBlock", ""); override.put("chainId", ""); override.put("contractSizeLimit", ""); - assertThat(config.getConfigOptions(override).getIstanbulBlockNumber()).isNotPresent(); - assertThat(config.getConfigOptions(override).getChainId()).isNotPresent(); - assertThat(config.getConfigOptions(override).getContractSizeLimit()).isNotPresent(); + assertThat(config.withOverrides(override).getConfigOptions().getIstanbulBlockNumber()) + .isNotPresent(); + assertThat(config.withOverrides(override).getConfigOptions().getChainId()).isNotPresent(); + assertThat(config.withOverrides(override).getConfigOptions().getContractSizeLimit()) + .isNotPresent(); } @Test - public void testNoOverride() { - final GenesisConfigFile config = GenesisConfigFile.development(); + void testNoOverride() { + final GenesisConfigFile config = GenesisConfigFile.fromResource("/dev.json"); assertThat(config.getConfigOptions().getLondonBlockNumber()).hasValue(0); assertThat(config.getConfigOptions().getIstanbulBlockNumber()).isNotPresent(); @@ -411,9 +431,9 @@ public void testNoOverride() { } @Test - public void testConstantinopleFixShouldNotBeSupportedAlongPetersburg() { + void testConstantinopleFixShouldNotBeSupportedAlongPetersburg() { // petersburg node - final GenesisConfigFile config = GenesisConfigFile.genesisFileFromResources("/all_forks.json"); + final GenesisConfigFile config = GenesisConfigFile.fromResource("/all_forks.json"); assertThat(config.getConfigOptions().getPetersburgBlockNumber()).hasValue(7); @@ -422,13 +442,14 @@ public void testConstantinopleFixShouldNotBeSupportedAlongPetersburg() { override.put("constantinopleFixBlock", "1000"); assertThatExceptionOfType(RuntimeException.class) - .isThrownBy(() -> config.getConfigOptions(override).getPetersburgBlockNumber()) + .isThrownBy( + () -> config.withOverrides(override).getConfigOptions().getPetersburgBlockNumber()) .withMessage( "Genesis files cannot specify both petersburgBlock and constantinopleFixBlock."); } @Test - public void shouldLoadForksInSortedOrder() throws IOException { + void shouldLoadForksInSortedOrder() throws IOException { final ObjectNode configNode = new ObjectMapper() .createObjectNode() @@ -449,7 +470,7 @@ public void shouldLoadForksInSortedOrder() throws IOException { } @Test - public void shouldLoadForksIgnoreClassicForkBlock() throws IOException { + void shouldLoadForksIgnoreClassicForkBlock() throws IOException { final ObjectNode configNode = new ObjectMapper() .createObjectNode() @@ -469,7 +490,7 @@ public void shouldLoadForksIgnoreClassicForkBlock() throws IOException { } @Test - public void shouldLoadForksIgnoreUnexpectedValues() throws IOException { + void shouldLoadForksIgnoreUnexpectedValues() throws IOException { final ObjectNode configNoUnexpectedForks = new ObjectMapper() .createObjectNode() @@ -533,7 +554,7 @@ public void shouldLoadForksIgnoreUnexpectedValues() throws IOException { * been case agnostic. */ @Test - public void roundTripForkIdBlocks() throws IOException { + void roundTripForkIdBlocks() throws IOException { final String configText = Resources.toString(Resources.getResource("all_forks.json"), StandardCharsets.UTF_8); final ObjectNode genesisNode = JsonUtil.objectNodeFromString(configText); diff --git a/config/src/test/java/org/hyperledger/besu/config/GenesisConfigOptionsTest.java b/config/src/test/java/org/hyperledger/besu/config/GenesisConfigOptionsTest.java index 7dcc21a2c7c..219ea4fcf8a 100644 --- a/config/src/test/java/org/hyperledger/besu/config/GenesisConfigOptionsTest.java +++ b/config/src/test/java/org/hyperledger/besu/config/GenesisConfigOptionsTest.java @@ -29,30 +29,31 @@ import org.apache.tuweni.units.bigints.UInt256; import org.junit.jupiter.api.Test; -public class GenesisConfigOptionsTest { +class GenesisConfigOptionsTest { @Test - public void shouldUseEthHashWhenEthHashInConfig() { + void shouldUseEthHashWhenEthHashInConfig() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("ethash", emptyMap())); assertThat(config.isEthHash()).isTrue(); assertThat(config.getConsensusEngine()).isEqualTo("ethash"); } @Test - public void shouldNotUseEthHashIfEthHashNotPresent() { + void shouldNotUseEthHashIfEthHashNotPresent() { final GenesisConfigOptions config = fromConfigOptions(emptyMap()); assertThat(config.isEthHash()).isFalse(); } @Test - public void shouldUseIbft2WhenIbft2InConfig() { + void shouldUseIbft2WhenIbft2InConfig() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("ibft2", emptyMap())); assertThat(config.isIbft2()).isTrue(); assertThat(config.isPoa()).isTrue(); assertThat(config.getConsensusEngine()).isEqualTo("ibft2"); } - public void shouldUseQbftWhenQbftInConfig() { + @Test + void shouldUseQbftWhenQbftInConfig() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("qbft", emptyMap())); assertThat(config.isQbft()).isTrue(); assertThat(config.isPoa()).isTrue(); @@ -60,80 +61,80 @@ public void shouldUseQbftWhenQbftInConfig() { } @Test - public void shouldUseCliqueWhenCliqueInConfig() { + void shouldUseCliqueWhenCliqueInConfig() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("clique", emptyMap())); assertThat(config.isClique()).isTrue(); assertThat(config.isPoa()).isTrue(); - assertThat(config.getCliqueConfigOptions()).isNotSameAs(CliqueConfigOptions.DEFAULT); + assertThat(config.getCliqueConfigOptions()).isNotSameAs(JsonCliqueConfigOptions.DEFAULT); assertThat(config.getConsensusEngine()).isEqualTo("clique"); } @Test - public void shouldNotUseCliqueIfCliqueNotPresent() { + void shouldNotUseCliqueIfCliqueNotPresent() { final GenesisConfigOptions config = fromConfigOptions(emptyMap()); assertThat(config.isClique()).isFalse(); assertThat(config.isPoa()).isFalse(); - assertThat(config.getCliqueConfigOptions()).isSameAs(CliqueConfigOptions.DEFAULT); + assertThat(config.getCliqueConfigOptions()).isSameAs(JsonCliqueConfigOptions.DEFAULT); } @Test - public void shouldGetHomesteadBlockNumber() { + void shouldGetHomesteadBlockNumber() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("homesteadBlock", 1000)); assertThat(config.getHomesteadBlockNumber()).hasValue(1000); } @Test - public void shouldGetDaoForkBlockNumber() { + void shouldGetDaoForkBlockNumber() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("daoForkBlock", 1000)); assertThat(config.getDaoForkBlock()).hasValue(1000); } @Test - public void shouldNotHaveDaoForkBlockWhenSetToZero() { + void shouldNotHaveDaoForkBlockWhenSetToZero() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("daoForkBlock", 0)); assertThat(config.getDaoForkBlock()).isEmpty(); } @Test - public void shouldGetTangerineWhistleBlockNumber() { + void shouldGetTangerineWhistleBlockNumber() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("eip150Block", 1000)); assertThat(config.getTangerineWhistleBlockNumber()).hasValue(1000); } @Test - public void shouldGetSpuriousDragonBlockNumber() { + void shouldGetSpuriousDragonBlockNumber() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("eip158Block", 1000)); assertThat(config.getSpuriousDragonBlockNumber()).hasValue(1000); } @Test - public void shouldGetByzantiumBlockNumber() { + void shouldGetByzantiumBlockNumber() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("byzantiumBlock", 1000)); assertThat(config.getByzantiumBlockNumber()).hasValue(1000); } @Test - public void shouldGetConstantinopleBlockNumber() { + void shouldGetConstantinopleBlockNumber() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("constantinopleBlock", 1000)); assertThat(config.getConstantinopleBlockNumber()).hasValue(1000); } @Test - public void shouldGetConstantinopleFixBlockNumber() { + void shouldGetConstantinopleFixBlockNumber() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("constantinopleFixBlock", 1000)); assertThat(config.getPetersburgBlockNumber()).hasValue(1000); } @Test - public void shouldGetPetersburgBlockNumber() { + void shouldGetPetersburgBlockNumber() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("petersburgBlock", 1000)); assertThat(config.getPetersburgBlockNumber()).hasValue(1000); } @Test - public void shouldFailWithBothPetersburgAndConstantinopleFixBlockNumber() { + void shouldFailWithBothPetersburgAndConstantinopleFixBlockNumber() { Map configMap = new HashMap<>(); configMap.put("constantinopleFixBlock", 1000); configMap.put("petersburgBlock", 1000); @@ -145,68 +146,88 @@ public void shouldFailWithBothPetersburgAndConstantinopleFixBlockNumber() { } @Test - public void shouldGetIstanbulBlockNumber() { + void shouldGetIstanbulBlockNumber() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("istanbulBlock", 1000)); assertThat(config.getIstanbulBlockNumber()).hasValue(1000); } @Test - public void shouldGetMuirGlacierBlockNumber() { + void shouldGetMuirGlacierBlockNumber() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("muirGlacierBlock", 1000)); assertThat(config.getMuirGlacierBlockNumber()).hasValue(1000); } @Test - public void shouldGetBerlinBlockNumber() { + void shouldGetBerlinBlockNumber() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("berlinBlock", 1000)); assertThat(config.getBerlinBlockNumber()).hasValue(1000); } @Test - public void shouldGetLondonBlockNumber() { + void shouldGetLondonBlockNumber() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("londonblock", 1000)); assertThat(config.getLondonBlockNumber()).hasValue(1000); } @Test - public void shouldGetArrowGlacierBlockNumber() { + void shouldGetArrowGlacierBlockNumber() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("arrowGlacierBlock", 1000)); assertThat(config.getArrowGlacierBlockNumber()).hasValue(1000); } @Test - public void shouldGetGrayGlacierBlockNumber() { + void shouldGetGrayGlacierBlockNumber() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("grayGlacierBlock", 4242)); assertThat(config.getGrayGlacierBlockNumber()).hasValue(4242); } @Test - public void shouldGetShanghaiTime() { + void shouldGetShanghaiTime() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("shanghaiTime", 1670470141)); assertThat(config.getShanghaiTime()).hasValue(1670470141); } @Test - public void shouldGetCancunTime() { + void shouldGetCancunTime() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("cancunTime", 1670470142)); assertThat(config.getCancunTime()).hasValue(1670470142); } @Test - public void shouldGetFutureEipsTime() { + void shouldGetCancunEOFTime() { + final GenesisConfigOptions config = + fromConfigOptions(singletonMap("cancunEOFTime", 1670470142)); + assertThat(config.getCancunEOFTime()).hasValue(1670470142); + } + + @Test + void shouldGetPragueTime() { + final GenesisConfigOptions config = fromConfigOptions(singletonMap("pragueTime", 1670470143)); + assertThat(config.getPragueTime()).hasValue(1670470143); + } + + @Test + void shouldGetPragueEOFTime() { + final GenesisConfigOptions config = + fromConfigOptions(singletonMap("pragueEOFTime", 1670470143)); + assertThat(config.getPragueEOFTime()).hasValue(1670470143); + } + + @Test + void shouldGetFutureEipsTime() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("futureEipsTime", 1337)); assertThat(config.getFutureEipsTime()).hasValue(1337); } @Test - public void shouldGetExperimentalEipsTime() { + void shouldGetExperimentalEipsTime() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("experimentalEipsTime", 1337)); assertThat(config.getExperimentalEipsTime()).hasValue(1337); } @Test - public void shouldNotReturnEmptyOptionalWhenBlockNumberNotSpecified() { + void shouldNotReturnEmptyOptionalWhenBlockNumberNotSpecified() { final GenesisConfigOptions config = fromConfigOptions(emptyMap()); assertThat(config.getHomesteadBlockNumber()).isEmpty(); assertThat(config.getDaoForkBlock()).isEmpty(); @@ -224,19 +245,22 @@ public void shouldNotReturnEmptyOptionalWhenBlockNumberNotSpecified() { assertThat(config.getMergeNetSplitBlockNumber()).isEmpty(); assertThat(config.getShanghaiTime()).isEmpty(); assertThat(config.getCancunTime()).isEmpty(); + assertThat(config.getCancunEOFTime()).isEmpty(); + assertThat(config.getPragueTime()).isEmpty(); + assertThat(config.getPragueEOFTime()).isEmpty(); assertThat(config.getFutureEipsTime()).isEmpty(); assertThat(config.getExperimentalEipsTime()).isEmpty(); } @Test - public void shouldGetChainIdWhenSpecified() { + void shouldGetChainIdWhenSpecified() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("chainId", BigInteger.valueOf(32))); assertThat(config.getChainId()).hasValue(BigInteger.valueOf(32)); } @Test - public void shouldSupportEmptyGenesisConfig() { + void shouldSupportEmptyGenesisConfig() { final GenesisConfigOptions config = GenesisConfigFile.fromConfig("{}").getConfigOptions(); assertThat(config.isEthHash()).isFalse(); assertThat(config.isClique()).isFalse(); @@ -245,7 +269,7 @@ public void shouldSupportEmptyGenesisConfig() { } @Test - public void shouldGetTerminalTotalDifficultyWhenSpecified() { + void shouldGetTerminalTotalDifficultyWhenSpecified() { final GenesisConfigOptions config = fromConfigOptions(singletonMap("terminalTotalDifficulty", BigInteger.valueOf(1000))); assertThat(config.getTerminalTotalDifficulty()).isPresent(); @@ -259,7 +283,7 @@ public void shouldGetTerminalTotalDifficultyWhenSpecified() { } @Test - public void shouldNotReturnTerminalTotalDifficultyWhenNotSpecified() { + void shouldNotReturnTerminalTotalDifficultyWhenNotSpecified() { final GenesisConfigOptions config = fromConfigOptions(emptyMap()); assertThat(config.getTerminalTotalDifficulty()).isNotPresent(); // stubJsonGenesis @@ -267,28 +291,75 @@ public void shouldNotReturnTerminalTotalDifficultyWhenNotSpecified() { } @Test - public void isZeroBaseFeeShouldDefaultToFalse() { + void isZeroBaseFeeShouldDefaultToFalse() { final GenesisConfigOptions config = GenesisConfigFile.fromConfig("{}").getConfigOptions(); assertThat(config.isZeroBaseFee()).isFalse(); } @Test - public void isZeroBaseFeeParsedCorrectly() { + void isZeroBaseFeeParsedCorrectly() { final GenesisConfigOptions config = fromConfigOptions(Map.of("zerobasefee", true)); assertThat(config.isZeroBaseFee()).isTrue(); } @Test - public void asMapIncludesZeroBaseFee() { + void asMapIncludesZeroBaseFee() { final GenesisConfigOptions config = fromConfigOptions(Map.of("zerobasefee", true)); assertThat(config.asMap()).containsOnlyKeys("zeroBaseFee").containsValue(true); } @Test - public void shouldGetDepositContractAddress() { + void isFixedBaseFeeShouldDefaultToFalse() { + final GenesisConfigOptions config = GenesisConfigFile.fromConfig("{}").getConfigOptions(); + + assertThat(config.isFixedBaseFee()).isFalse(); + } + + @Test + void isFixedBaseFeeParsedCorrectly() { + final GenesisConfigOptions config = fromConfigOptions(Map.of("fixedbasefee", true)); + + assertThat(config.isFixedBaseFee()).isTrue(); + } + + @Test + void asMapIncludesFixedBaseFee() { + final GenesisConfigOptions config = fromConfigOptions(Map.of("fixedbasefee", true)); + + assertThat(config.asMap()).containsOnlyKeys("fixedBaseFee").containsValue(true); + } + + @Test + void shouldGetWithdrawalRequestContractAddress() { + final GenesisConfigOptions config = + fromConfigOptions( + singletonMap( + "withdrawalRequestContractAddress", "0x00000000219ab540356cbb839cbe05303d7705fa")); + assertThat(config.getWithdrawalRequestContractAddress()) + .hasValue(Address.fromHexString("0x00000000219ab540356cbb839cbe05303d7705fa")); + } + + @Test + void shouldNotHaveWithdrawalRequestContractAddressWhenEmpty() { + final GenesisConfigOptions config = fromConfigOptions(emptyMap()); + assertThat(config.getWithdrawalRequestContractAddress()).isEmpty(); + } + + @Test + void asMapIncludesWithdrawalRequestContractAddress() { + final GenesisConfigOptions config = + fromConfigOptions(Map.of("withdrawalRequestContractAddress", "0x0")); + + assertThat(config.asMap()) + .containsOnlyKeys("withdrawalRequestContractAddress") + .containsValue(Address.ZERO); + } + + @Test + void shouldGetDepositContractAddress() { final GenesisConfigOptions config = fromConfigOptions( singletonMap("depositContractAddress", "0x00000000219ab540356cbb839cbe05303d7705fa")); @@ -297,13 +368,13 @@ public void shouldGetDepositContractAddress() { } @Test - public void shouldNotHaveDepositContractAddressWhenEmpty() { + void shouldNotHaveDepositContractAddressWhenEmpty() { final GenesisConfigOptions config = fromConfigOptions(emptyMap()); assertThat(config.getDepositContractAddress()).isEmpty(); } @Test - public void asMapIncludesDepositContractAddress() { + void asMapIncludesDepositContractAddress() { final GenesisConfigOptions config = fromConfigOptions(Map.of("depositContractAddress", "0x0")); assertThat(config.asMap()) diff --git a/config/src/test/java/org/hyperledger/besu/config/GenesisReaderTest.java b/config/src/test/java/org/hyperledger/besu/config/GenesisReaderTest.java new file mode 100644 index 00000000000..5d73a2829f5 --- /dev/null +++ b/config/src/test/java/org/hyperledger/besu/config/GenesisReaderTest.java @@ -0,0 +1,98 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.config; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.config.GenesisReader.ALLOCATION_FIELD; +import static org.hyperledger.besu.config.GenesisReader.CONFIG_FIELD; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class GenesisReaderTest { + private final ObjectMapper mapper = new ObjectMapper(); + + @Test + public void readGenesisFromObjectNode() { + final var configNode = mapper.createObjectNode(); + configNode.put("londonBlock", 1); + final var allocNode = mapper.createObjectNode(); + allocNode.put(Address.BLS12_G2MUL.toUnprefixedHexString(), generateAllocation(Wei.ONE)); + final var rootNode = mapper.createObjectNode(); + rootNode.put("chainId", 12); + rootNode.put(CONFIG_FIELD, configNode); + rootNode.put(ALLOCATION_FIELD, allocNode); + final var genesisReader = new GenesisReader.FromObjectNode(rootNode); + + assertThat(genesisReader.getRoot().get("chainid").asInt()).isEqualTo(12); + assertThat(genesisReader.getRoot().has(ALLOCATION_FIELD)).isFalse(); + assertThat(genesisReader.getConfig().get("londonblock").asInt()).isEqualTo(1); + assertThat(genesisReader.streamAllocations()) + .containsExactly(new GenesisAccount(Address.BLS12_G2MUL, 0, Wei.ONE, null, Map.of(), null)); + } + + @Test + public void readGenesisFromURL(@TempDir final Path folder) throws IOException { + final String jsonStr = + """ + { + "chainId":11, + "config": { + "londonBlock":1 + }, + "alloc": { + "000d836201318ec6899a67540690382780743280": { + "balance": "0xad78ebc5ac6200000" + } + }, + "gasLimit": "0x1" + } + """; + + final var genesisFile = Files.writeString(folder.resolve("genesis.json"), jsonStr); + + final var genesisReader = new GenesisReader.FromURL(genesisFile.toUri().toURL()); + + assertThat(genesisReader.getRoot().get("chainid").asInt()).isEqualTo(11); + assertThat(genesisReader.getRoot().get("gaslimit").asText()).isEqualTo("0x1"); + assertThat(genesisReader.getRoot().has(ALLOCATION_FIELD)).isFalse(); + assertThat(genesisReader.getConfig().get("londonblock").asInt()).isEqualTo(1); + assertThat(genesisReader.streamAllocations()) + .containsExactly( + new GenesisAccount( + Address.fromHexString("000d836201318ec6899a67540690382780743280"), + 0, + Wei.fromHexString("0xad78ebc5ac6200000"), + null, + Map.of(), + null)); + } + + private ObjectNode generateAllocation(final Wei balance) { + final ObjectNode entry = mapper.createObjectNode(); + entry.put("balance", balance.toShortHexString()); + return entry; + } +} diff --git a/config/src/test/java/org/hyperledger/besu/config/JsonQbftConfigOptionsTest.java b/config/src/test/java/org/hyperledger/besu/config/JsonQbftConfigOptionsTest.java index 448a5c18adc..8fd4bd64d74 100644 --- a/config/src/test/java/org/hyperledger/besu/config/JsonQbftConfigOptionsTest.java +++ b/config/src/test/java/org/hyperledger/besu/config/JsonQbftConfigOptionsTest.java @@ -1,21 +1,17 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See - * the License for the + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.config; import static org.assertj.core.api.Assertions.assertThat; diff --git a/config/src/test/java/org/hyperledger/besu/config/JsonUtilTest.java b/config/src/test/java/org/hyperledger/besu/config/JsonUtilTest.java index 962bb42f055..1e1df382e71 100644 --- a/config/src/test/java/org/hyperledger/besu/config/JsonUtilTest.java +++ b/config/src/test/java/org/hyperledger/besu/config/JsonUtilTest.java @@ -17,6 +17,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.List; import java.util.Locale; import java.util.Map; @@ -31,6 +34,7 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; public class JsonUtilTest { private final ObjectMapper mapper = new ObjectMapper(); @@ -654,6 +658,41 @@ public void objectNodeFromString_withComments_commentsEnabled() { assertThat(result.get("b").asInt()).isEqualTo(2); } + @Test + public void objectNodeFromString_excludingField() { + final String jsonStr = + """ + { + "a":1, + "b":2, + "c":3 + } + """; + + final ObjectNode result = JsonUtil.objectNodeFromString(jsonStr, false, "b"); + assertThat(result.get("a").asInt()).isEqualTo(1); + assertThat(result.has("b")).isFalse(); + assertThat(result.get("c").asInt()).isEqualTo(3); + } + + @Test + public void objectNodeFromURL_excludingField(@TempDir final Path folder) throws IOException { + final String jsonStr = + """ + { + "a":1, + "b":2, + "c":3 + } + """; + final var genesisFile = Files.writeString(folder.resolve("genesis.json"), jsonStr); + + final ObjectNode result = JsonUtil.objectNodeFromURL(genesisFile.toUri().toURL(), false, "b"); + assertThat(result.get("a").asInt()).isEqualTo(1); + assertThat(result.has("b")).isFalse(); + assertThat(result.get("c").asInt()).isEqualTo(3); + } + @Test public void getJson() throws JsonProcessingException { final String jsonStr = "{\"a\":1, \"b\":2}"; diff --git a/config/src/test/resources/all_forks.json b/config/src/test/resources/all_forks.json index a697a3748fd..6c26c7703e3 100644 --- a/config/src/test/resources/all_forks.json +++ b/config/src/test/resources/all_forks.json @@ -16,6 +16,7 @@ "mergeNetSplitBlock": 14, "shanghaiTime": 15, "cancunTime": 16, + "pragueTime": 17, "futureEipsTime": 98, "experimentalEipsTime": 99, "ecip1015Block": 102, diff --git a/consensus/clique/build.gradle b/consensus/clique/build.gradle index 4c5634f17c2..d2fb44c6ef8 100644 --- a/consensus/clique/build.gradle +++ b/consensus/clique/build.gradle @@ -22,7 +22,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } @@ -60,6 +61,4 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter' testImplementation 'org.mockito:mockito-core' testImplementation 'org.mockito:mockito-junit-jupiter' - - testRuntimeOnly 'org.junit.vintage:junit-vintage-engine' } diff --git a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/BlockHeaderValidationRulesetFactory.java b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/BlockHeaderValidationRulesetFactory.java index 15346fb4911..0274736c0fa 100644 --- a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/BlockHeaderValidationRulesetFactory.java +++ b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/BlockHeaderValidationRulesetFactory.java @@ -44,6 +44,8 @@ /** The Block header validation ruleset factory. */ public class BlockHeaderValidationRulesetFactory { + /** Default constructor. */ + private BlockHeaderValidationRulesetFactory() {} /** * Creates a set of rules which when executed will determine if a given block header is valid with diff --git a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueBlockHashing.java b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueBlockHashing.java index a8ebacba2b7..925ada48218 100644 --- a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueBlockHashing.java +++ b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueBlockHashing.java @@ -27,6 +27,9 @@ /** The Clique block hashing. */ public class CliqueBlockHashing { + /** Default constructor. */ + private CliqueBlockHashing() {} + /** * Constructs a hash of the block header, suitable for use when creating the proposer seal. The * extra data is modified to have a null proposer seal and empty list of committed seals. diff --git a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueBlockHeaderFunctions.java b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueBlockHeaderFunctions.java index 3f70cbec6f3..4f493a0aee0 100644 --- a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueBlockHeaderFunctions.java +++ b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueBlockHeaderFunctions.java @@ -21,6 +21,8 @@ /** The Clique block header functions. */ public class CliqueBlockHeaderFunctions implements BlockHeaderFunctions { + /** Default constructor. */ + public CliqueBlockHeaderFunctions() {} @Override public Hash hash(final BlockHeader header) { diff --git a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueBlockInterface.java b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueBlockInterface.java index 75c5998c8ab..1f2b7c08cd9 100644 --- a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueBlockInterface.java +++ b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueBlockInterface.java @@ -35,6 +35,7 @@ public class CliqueBlockInterface implements BlockInterface { /** The constant ADD_NONCE. */ public static final long ADD_NONCE = 0xFFFFFFFFFFFFFFFFL; + /** The constant DROP_NONCE. */ public static final long DROP_NONCE = 0x0L; @@ -43,6 +44,9 @@ public class CliqueBlockInterface implements BlockInterface { VoteType.ADD, ADD_NONCE, VoteType.DROP, DROP_NONCE); + /** Default constructor. */ + public CliqueBlockInterface() {} + @Override public Address getProposerOfBlock(final BlockHeader header) { return CliqueHelpers.getProposerOfBlock(header); diff --git a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueExtraData.java b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueExtraData.java index bc8d096cf8f..f49df0066f1 100644 --- a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueExtraData.java +++ b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueExtraData.java @@ -39,6 +39,7 @@ */ public class CliqueExtraData implements ParsedExtraData { private static final Logger LOG = LoggerFactory.getLogger(CliqueExtraData.class); + /** The constant EXTRA_VANITY_LENGTH. */ public static final int EXTRA_VANITY_LENGTH = 32; diff --git a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueForksSchedulesFactory.java b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueForksSchedulesFactory.java new file mode 100644 index 00000000000..9e10407ddae --- /dev/null +++ b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueForksSchedulesFactory.java @@ -0,0 +1,52 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.consensus.clique; + +import org.hyperledger.besu.config.CliqueConfigOptions; +import org.hyperledger.besu.config.CliqueFork; +import org.hyperledger.besu.config.GenesisConfigOptions; +import org.hyperledger.besu.config.ImmutableCliqueConfigOptions; +import org.hyperledger.besu.consensus.common.ForkSpec; +import org.hyperledger.besu.consensus.common.ForksSchedule; +import org.hyperledger.besu.consensus.common.ForksScheduleFactory; + +/** The Clique forks schedules factory. */ +public class CliqueForksSchedulesFactory { + /** Default constructor. */ + CliqueForksSchedulesFactory() {} + + /** + * Create forks schedule. + * + * @param genesisConfig the genesis config + * @return the forks schedule + */ + public static ForksSchedule create( + final GenesisConfigOptions genesisConfig) { + return ForksScheduleFactory.create( + genesisConfig.getCliqueConfigOptions(), + genesisConfig.getTransitions().getCliqueForks(), + CliqueForksSchedulesFactory::createCliqueConfigOptions); + } + + private static CliqueConfigOptions createCliqueConfigOptions( + final ForkSpec lastSpec, final CliqueFork fork) { + + var options = ImmutableCliqueConfigOptions.builder().from(lastSpec.getValue()); + fork.getBlockPeriodSeconds().ifPresent(options::blockPeriodSeconds); + fork.getCreateEmptyBlocks().ifPresent(options::createEmptyBlocks); + return options.build(); + } +} diff --git a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueHelpers.java b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueHelpers.java index 73d98792db5..cba98a5a5eb 100644 --- a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueHelpers.java +++ b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueHelpers.java @@ -27,6 +27,8 @@ /** The Clique helpers. */ public class CliqueHelpers { + /** Default constructor. */ + CliqueHelpers() {} /** * Gets proposer of block. diff --git a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueProtocolSchedule.java b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueProtocolSchedule.java index ef2c388cf7e..cd197556386 100644 --- a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueProtocolSchedule.java +++ b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/CliqueProtocolSchedule.java @@ -17,9 +17,12 @@ import org.hyperledger.besu.config.CliqueConfigOptions; import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.consensus.common.EpochManager; +import org.hyperledger.besu.consensus.common.ForksSchedule; import org.hyperledger.besu.cryptoservices.NodeKey; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.Util; import org.hyperledger.besu.ethereum.mainnet.BlockHeaderValidator; @@ -33,31 +36,51 @@ import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.plugin.services.MetricsSystem; import java.math.BigInteger; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; +import java.util.function.Function; + +import com.google.common.annotations.VisibleForTesting; /** Defines the protocol behaviours for a blockchain using Clique. */ public class CliqueProtocolSchedule { private static final BigInteger DEFAULT_CHAIN_ID = BigInteger.valueOf(4); + /** Default constructor. */ + CliqueProtocolSchedule() {} + /** * Create protocol schedule. * * @param config the config + * @param forksSchedule the transitions * @param nodeKey the node key * @param privacyParameters the privacy parameters * @param isRevertReasonEnabled the is revert reason enabled * @param evmConfiguration the evm configuration + * @param miningParameters the mining parameters + * @param badBlockManager the cache to use to keep invalid blocks + * @param isParallelTxProcessingEnabled indicates whether parallel transaction is enabled + * @param metricsSystem A metricSystem instance to be able to expose metrics in the underlying + * calls * @return the protocol schedule */ public static ProtocolSchedule create( final GenesisConfigOptions config, + final ForksSchedule forksSchedule, final NodeKey nodeKey, final PrivacyParameters privacyParameters, final boolean isRevertReasonEnabled, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final BadBlockManager badBlockManager, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { final CliqueConfigOptions cliqueConfig = config.getCliqueConfigOptions(); @@ -69,21 +92,33 @@ public static ProtocolSchedule create( final EpochManager epochManager = new EpochManager(cliqueConfig.getEpochLength()); + final Map> specMap = new HashMap<>(); + forksSchedule + .getForks() + .forEach( + forkSpec -> + specMap.put( + forkSpec.getBlock(), + builder -> + applyCliqueSpecificModifications( + epochManager, + forkSpec.getValue().getBlockPeriodSeconds(), + forkSpec.getValue().getCreateEmptyBlocks(), + localNodeAddress, + builder))); + final ProtocolSpecAdapters specAdapters = new ProtocolSpecAdapters(specMap); + return new ProtocolScheduleBuilder( config, DEFAULT_CHAIN_ID, - ProtocolSpecAdapters.create( - 0, - builder -> - applyCliqueSpecificModifications( - epochManager, - cliqueConfig.getBlockPeriodSeconds(), - cliqueConfig.getCreateEmptyBlocks(), - localNodeAddress, - builder)), + specAdapters, privacyParameters, isRevertReasonEnabled, - evmConfiguration) + evmConfiguration, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem) .createProtocolSchedule(); } @@ -91,18 +126,39 @@ public static ProtocolSchedule create( * Create protocol schedule. * * @param config the config + * @param forksSchedule the transitions * @param nodeKey the node key * @param isRevertReasonEnabled the is revert reason enabled * @param evmConfiguration the evm configuration + * @param miningParameters the mining parameters + * @param badBlockManager the cache to use to keep invalid blocks + * @param isParallelTxProcessingEnabled indicates whether parallel transaction is enabled + * @param metricsSystem A metricSystem instance to be able to expose metrics in the underlying + * calls * @return the protocol schedule */ + @VisibleForTesting public static ProtocolSchedule create( final GenesisConfigOptions config, + final ForksSchedule forksSchedule, final NodeKey nodeKey, final boolean isRevertReasonEnabled, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final BadBlockManager badBlockManager, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return create( - config, nodeKey, PrivacyParameters.DEFAULT, isRevertReasonEnabled, evmConfiguration); + config, + forksSchedule, + nodeKey, + PrivacyParameters.DEFAULT, + isRevertReasonEnabled, + evmConfiguration, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem); } private static ProtocolSpecBuilder applyCliqueSpecificModifications( diff --git a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreator.java b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreator.java index e703aeb48f1..ed17529a9ff 100644 --- a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreator.java +++ b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreator.java @@ -54,7 +54,6 @@ public class CliqueBlockCreator extends AbstractBlockCreator { * @param protocolContext the protocol context * @param protocolSchedule the protocol schedule * @param nodeKey the node key - * @param parentHeader the parent header * @param epochManager the epoch manager * @param ethScheduler the scheduler for asynchronous block creation tasks */ @@ -65,7 +64,6 @@ public CliqueBlockCreator( final ProtocolContext protocolContext, final ProtocolSchedule protocolSchedule, final NodeKey nodeKey, - final BlockHeader parentHeader, final EpochManager epochManager, final EthScheduler ethScheduler) { super( @@ -75,8 +73,6 @@ public CliqueBlockCreator( transactionPool, protocolContext, protocolSchedule, - parentHeader, - Optional.empty(), ethScheduler); this.nodeKey = nodeKey; this.epochManager = epochManager; @@ -113,6 +109,8 @@ protected BlockHeader createFinalBlockHeader(final SealableBlockHeader sealableB private Optional determineCliqueVote( final SealableBlockHeader sealableBlockHeader) { + BlockHeader parentHeader = + protocolContext.getBlockchain().getBlockHeader(sealableBlockHeader.getParentHash()).get(); if (epochManager.isEpochBlock(sealableBlockHeader.getNumber())) { return Optional.empty(); } else { diff --git a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockMiner.java b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockMiner.java index 698ff77eccb..bb06ae408d6 100644 --- a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockMiner.java +++ b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockMiner.java @@ -14,7 +14,9 @@ */ package org.hyperledger.besu.consensus.clique.blockcreation; +import org.hyperledger.besu.config.CliqueConfigOptions; import org.hyperledger.besu.consensus.clique.CliqueHelpers; +import org.hyperledger.besu.consensus.common.ForksSchedule; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.blockcreation.AbstractBlockScheduler; @@ -36,7 +38,7 @@ public class CliqueBlockMiner extends BlockMiner { private static final int WAIT_IN_MS_BETWEEN_EMPTY_BUILD_ATTEMPTS = 1_000; private final Address localAddress; - private final boolean createEmptyBlocks; + private final ForksSchedule forksSchedule; /** * Instantiates a new Clique block miner. @@ -48,7 +50,7 @@ public class CliqueBlockMiner extends BlockMiner { * @param scheduler the scheduler * @param parentHeader the parent header * @param localAddress the local address - * @param createEmptyBlocks whether clique should allow the creation of empty blocks. + * @param forksSchedule the transitions */ public CliqueBlockMiner( final Function blockCreator, @@ -58,10 +60,10 @@ public CliqueBlockMiner( final AbstractBlockScheduler scheduler, final BlockHeader parentHeader, final Address localAddress, - final boolean createEmptyBlocks) { + final ForksSchedule forksSchedule) { super(blockCreator, protocolSchedule, protocolContext, observers, scheduler, parentHeader); this.localAddress = localAddress; - this.createEmptyBlocks = createEmptyBlocks; + this.forksSchedule = forksSchedule; } @Override @@ -76,7 +78,7 @@ protected boolean mineBlock() throws InterruptedException { @Override protected boolean shouldImportBlock(final Block block) throws InterruptedException { - if (createEmptyBlocks) { + if (forksSchedule.getFork(block.getHeader().getNumber()).getValue().getCreateEmptyBlocks()) { return true; } diff --git a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockScheduler.java b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockScheduler.java index aa28226a515..5ffa78e09f1 100644 --- a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockScheduler.java +++ b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockScheduler.java @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.consensus.clique.blockcreation; +import org.hyperledger.besu.config.CliqueConfigOptions; +import org.hyperledger.besu.consensus.common.ForksSchedule; import org.hyperledger.besu.consensus.common.validator.ValidatorProvider; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.blockcreation.DefaultBlockScheduler; @@ -40,14 +42,22 @@ public class CliqueBlockScheduler extends DefaultBlockScheduler { * @param clock the clock * @param validatorProvider the validator provider * @param localNodeAddress the local node address - * @param secondsBetweenBlocks the seconds between blocks + * @param forksSchedule the transitions */ public CliqueBlockScheduler( final Clock clock, final ValidatorProvider validatorProvider, final Address localNodeAddress, - final long secondsBetweenBlocks) { - super(secondsBetweenBlocks, 0L, clock); + final ForksSchedule forksSchedule) { + super( + parentHeader -> + (long) + forksSchedule + .getFork(parentHeader.getNumber() + 1) + .getValue() + .getBlockPeriodSeconds(), + 0L, + clock); this.validatorProvider = validatorProvider; this.localNodeAddress = localNodeAddress; } @@ -58,10 +68,10 @@ public BlockCreationTimeResult getNextTimestamp(final BlockHeader parentHeader) final BlockCreationTimeResult result = super.getNextTimestamp(parentHeader); final long milliSecondsUntilNextBlock = - result.getMillisecondsUntilValid() + calculateTurnBasedDelay(parentHeader); + result.millisecondsUntilValid() + calculateTurnBasedDelay(parentHeader); return new BlockCreationTimeResult( - result.getTimestampForHeader(), Math.max(0, milliSecondsUntilNextBlock)); + result.timestampForHeader(), Math.max(0, milliSecondsUntilNextBlock)); } private int calculateTurnBasedDelay(final BlockHeader parentHeader) { diff --git a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutor.java b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutor.java index 10c4cf4ef9b..ef3d58fbffb 100644 --- a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutor.java +++ b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutor.java @@ -14,10 +14,12 @@ */ package org.hyperledger.besu.consensus.clique.blockcreation; +import org.hyperledger.besu.config.CliqueConfigOptions; import org.hyperledger.besu.consensus.clique.CliqueContext; import org.hyperledger.besu.consensus.clique.CliqueExtraData; import org.hyperledger.besu.consensus.common.ConsensusHelpers; import org.hyperledger.besu.consensus.common.EpochManager; +import org.hyperledger.besu.consensus.common.ForksSchedule; import org.hyperledger.besu.cryptoservices.NodeKey; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.ProtocolContext; @@ -48,7 +50,7 @@ public class CliqueMinerExecutor extends AbstractMinerExecutor private final Address localAddress; private final NodeKey nodeKey; private final EpochManager epochManager; - private final boolean createEmptyBlocks; + private final ForksSchedule forksSchedule; /** * Instantiates a new Clique miner executor. @@ -60,7 +62,7 @@ public class CliqueMinerExecutor extends AbstractMinerExecutor * @param miningParams the mining params * @param blockScheduler the block scheduler * @param epochManager the epoch manager - * @param createEmptyBlocks whether clique should allow the creation of empty blocks. + * @param forksSchedule the clique transitions * @param ethScheduler the scheduler for asynchronous block creation tasks */ public CliqueMinerExecutor( @@ -71,7 +73,7 @@ public CliqueMinerExecutor( final MiningParameters miningParams, final AbstractBlockScheduler blockScheduler, final EpochManager epochManager, - final boolean createEmptyBlocks, + final ForksSchedule forksSchedule, final EthScheduler ethScheduler) { super( protocolContext, @@ -83,7 +85,7 @@ public CliqueMinerExecutor( this.nodeKey = nodeKey; this.localAddress = Util.publicKeyToAddress(nodeKey.getPublicKey()); this.epochManager = epochManager; - this.createEmptyBlocks = createEmptyBlocks; + this.forksSchedule = forksSchedule; miningParams.setCoinbase(localAddress); } @@ -101,7 +103,6 @@ public CliqueBlockMiner createMiner( protocolContext, protocolSchedule, nodeKey, - header, epochManager, ethScheduler); @@ -113,7 +114,7 @@ public CliqueBlockMiner createMiner( blockScheduler, parentHeader, localAddress, - createEmptyBlocks); + forksSchedule); } @Override diff --git a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/headervalidationrules/CliqueDifficultyValidationRule.java b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/headervalidationrules/CliqueDifficultyValidationRule.java index d4ff394e520..a14b5187ef7 100644 --- a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/headervalidationrules/CliqueDifficultyValidationRule.java +++ b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/headervalidationrules/CliqueDifficultyValidationRule.java @@ -31,6 +31,9 @@ public class CliqueDifficultyValidationRule implements AttachedBlockHeaderValida private static final Logger LOG = LoggerFactory.getLogger(CliqueDifficultyValidationRule.class); + /** Default constructor. */ + public CliqueDifficultyValidationRule() {} + @Override public boolean validate( final BlockHeader header, final BlockHeader parent, final ProtocolContext protocolContext) { diff --git a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/headervalidationrules/CliqueNoEmptyBlockValidationRule.java b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/headervalidationrules/CliqueNoEmptyBlockValidationRule.java index c179a08495a..40bd7622cf4 100644 --- a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/headervalidationrules/CliqueNoEmptyBlockValidationRule.java +++ b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/headervalidationrules/CliqueNoEmptyBlockValidationRule.java @@ -26,6 +26,9 @@ public class CliqueNoEmptyBlockValidationRule implements DetachedBlockHeaderVali private static final Logger LOG = LoggerFactory.getLogger(CliqueNoEmptyBlockValidationRule.class); + /** Default constructor. */ + public CliqueNoEmptyBlockValidationRule() {} + /** * Responsible for ensuring there are no empty transactions. This is used when createEmptyBlocks * is false, to ensure that no empty blocks are created. diff --git a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/headervalidationrules/SignerRateLimitValidationRule.java b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/headervalidationrules/SignerRateLimitValidationRule.java index 1d2d53d7e1b..580d4653fbd 100644 --- a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/headervalidationrules/SignerRateLimitValidationRule.java +++ b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/headervalidationrules/SignerRateLimitValidationRule.java @@ -28,6 +28,9 @@ public class SignerRateLimitValidationRule implements AttachedBlockHeaderValidat private static final Logger LOG = LoggerFactory.getLogger(SignerRateLimitValidationRule.class); + /** Default constructor. */ + public SignerRateLimitValidationRule() {} + @Override public boolean validate( final BlockHeader header, final BlockHeader parent, final ProtocolContext protocolContext) { diff --git a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/headervalidationrules/VoteValidationRule.java b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/headervalidationrules/VoteValidationRule.java index 4293e5dcf9e..ff21e1a9a0c 100644 --- a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/headervalidationrules/VoteValidationRule.java +++ b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/headervalidationrules/VoteValidationRule.java @@ -26,6 +26,9 @@ public class VoteValidationRule implements DetachedBlockHeaderValidationRule { private static final Logger LOG = LoggerFactory.getLogger(VoteValidationRule.class); + /** Default constructor. */ + public VoteValidationRule() {} + /** * Responsible for ensuring the nonce is either auth or drop. * diff --git a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/jsonrpc/CliqueJsonRpcMethods.java b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/jsonrpc/CliqueJsonRpcMethods.java index 1f929e77e7c..762cce1c569 100644 --- a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/jsonrpc/CliqueJsonRpcMethods.java +++ b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/jsonrpc/CliqueJsonRpcMethods.java @@ -31,6 +31,8 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.methods.ApiGroupJsonRpcMethods; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; +import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import java.util.Map; @@ -38,14 +40,23 @@ /** The Clique json rpc methods. */ public class CliqueJsonRpcMethods extends ApiGroupJsonRpcMethods { private final ProtocolContext context; + private final ProtocolSchedule protocolSchedule; + private final MiningParameters miningParameters; /** * Instantiates a new Clique json rpc methods. * - * @param context the context + * @param context the protocol context + * @param protocolSchedule the protocol schedule + * @param miningParameters the mining parameters */ - public CliqueJsonRpcMethods(final ProtocolContext context) { + public CliqueJsonRpcMethods( + final ProtocolContext context, + final ProtocolSchedule protocolSchedule, + final MiningParameters miningParameters) { this.context = context; + this.protocolSchedule = protocolSchedule; + this.miningParameters = miningParameters; } @Override @@ -58,7 +69,7 @@ protected Map create() { final MutableBlockchain blockchain = context.getBlockchain(); final WorldStateArchive worldStateArchive = context.getWorldStateArchive(); final BlockchainQueries blockchainQueries = - new BlockchainQueries(blockchain, worldStateArchive); + new BlockchainQueries(protocolSchedule, blockchain, worldStateArchive, miningParameters); final ValidatorProvider validatorProvider = context.getConsensusContext(CliqueContext.class).getValidatorProvider(); diff --git a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/CliqueGetSigners.java b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/CliqueGetSigners.java index 9f7b2d9f9d5..057fb30ee5e 100644 --- a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/CliqueGetSigners.java +++ b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/CliqueGetSigners.java @@ -17,8 +17,10 @@ import org.hyperledger.besu.consensus.common.validator.ValidatorProvider; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -67,8 +69,13 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { } private Optional determineBlockHeader(final JsonRpcRequestContext request) { - final Optional blockParameter = - request.getOptionalParameter(0, BlockParameter.class); + final Optional blockParameter; + try { + blockParameter = request.getOptionalParameter(0, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } final long latest = blockchainQueries.headBlockNumber(); final long blockNumber = blockParameter.map(b -> b.getNumber().orElse(latest)).orElse(latest); return blockchainQueries.blockByNumber(blockNumber).map(BlockWithMetadata::getHeader); diff --git a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/CliqueGetSignersAtHash.java b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/CliqueGetSignersAtHash.java index 8dd7ac5c23a..908ff9f4123 100644 --- a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/CliqueGetSignersAtHash.java +++ b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/CliqueGetSignersAtHash.java @@ -18,7 +18,9 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -67,7 +69,13 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { } private Optional determineBlockHeader(final JsonRpcRequestContext request) { - final Hash hash = request.getRequiredParameter(0, Hash.class); + final Hash hash; + try { + hash = request.getRequiredParameter(0, Hash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block hash parameter (index 0)", RpcErrorType.INVALID_BLOCK_HASH_PARAMS, e); + } return blockchainQueries.blockByHash(hash).map(BlockWithMetadata::getHeader); } } diff --git a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/Discard.java b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/Discard.java index 4fb3af6cd46..da3214b1ef0 100644 --- a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/Discard.java +++ b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/Discard.java @@ -20,9 +20,12 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; /** The Discard Json RPC method. */ public class Discard implements JsonRpcMethod { @@ -46,7 +49,13 @@ public String getName() { public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { checkState( validatorProvider.getVoteProviderAtHead().isPresent(), "Clique requires a vote provider"); - final Address address = requestContext.getRequiredParameter(0, Address.class); + final Address address; + try { + address = requestContext.getRequiredParameter(0, Address.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid address parameter (index 0)", RpcErrorType.INVALID_ADDRESS_PARAMS, e); + } validatorProvider.getVoteProviderAtHead().get().discardVote(address); return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), true); } diff --git a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/Propose.java b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/Propose.java index 0e278dee953..26ab19c9882 100644 --- a/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/Propose.java +++ b/consensus/clique/src/main/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/Propose.java @@ -21,7 +21,9 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -49,8 +51,20 @@ public String getName() { public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { checkState( validatorProvider.getVoteProviderAtHead().isPresent(), "Clique requires a vote provider"); - final Address address = requestContext.getRequiredParameter(0, Address.class); - final Boolean auth = requestContext.getRequiredParameter(1, Boolean.class); + final Address address; + try { + address = requestContext.getRequiredParameter(0, Address.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid address parameter (index 0)", RpcErrorType.INVALID_ADDRESS_PARAMS, e); + } + final Boolean auth; + try { + auth = requestContext.getRequiredParameter(1, Boolean.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid auth parameter (index 1)", RpcErrorType.INVALID_PROPOSAL_PARAMS, e); + } if (address.equals(CliqueBlockInterface.NO_VOTE_SUBJECT)) { return new JsonRpcErrorResponse( requestContext.getRequest().getId(), RpcErrorType.INVALID_REQUEST); diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/CliqueBlockChoiceTests.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/CliqueBlockChoiceTests.java index 3b8a7e15379..2bdb89c8f14 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/CliqueBlockChoiceTests.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/CliqueBlockChoiceTests.java @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.consensus.clique; import static org.assertj.core.api.Assertions.assertThat; diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/CliqueDifficultyCalculatorTest.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/CliqueDifficultyCalculatorTest.java index 6f9c5c53e0e..3c9da2520d9 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/CliqueDifficultyCalculatorTest.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/CliqueDifficultyCalculatorTest.java @@ -24,6 +24,7 @@ import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.AddressHelpers; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; @@ -31,7 +32,6 @@ import java.math.BigInteger; import java.util.List; -import java.util.Optional; import com.google.common.collect.Lists; import org.junit.jupiter.api.BeforeEach; @@ -58,7 +58,7 @@ public void setup() { when(validatorProvider.getValidatorsAfterBlock(any())).thenReturn(validatorList); final CliqueContext cliqueContext = new CliqueContext(validatorProvider, null, blockInterface); - cliqueProtocolContext = new ProtocolContext(null, null, cliqueContext, Optional.empty()); + cliqueProtocolContext = new ProtocolContext(null, null, cliqueContext, new BadBlockManager()); blockHeaderBuilder = new BlockHeaderTestFixture(); } diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/CliqueProtocolScheduleTest.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/CliqueProtocolScheduleTest.java index ee9e610f36e..5cbdbb6d06e 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/CliqueProtocolScheduleTest.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/CliqueProtocolScheduleTest.java @@ -14,26 +14,33 @@ */ package org.hyperledger.besu.consensus.clique; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.Java6Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import org.hyperledger.besu.config.CliqueConfigOptions; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.config.GenesisConfigOptions; +import org.hyperledger.besu.config.JsonCliqueConfigOptions; +import org.hyperledger.besu.consensus.common.ForkSpec; +import org.hyperledger.besu.consensus.common.ForksSchedule; import org.hyperledger.besu.cryptoservices.NodeKey; import org.hyperledger.besu.cryptoservices.NodeKeyUtils; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import java.time.Instant; +import java.util.List; import org.junit.jupiter.api.Test; @@ -55,7 +62,16 @@ public void protocolSpecsAreCreatedAtBlockDefinedInJson() { final GenesisConfigOptions config = GenesisConfigFile.fromConfig(jsonInput).getConfigOptions(); final ProtocolSchedule protocolSchedule = - CliqueProtocolSchedule.create(config, NODE_KEY, false, EvmConfiguration.DEFAULT); + CliqueProtocolSchedule.create( + config, + new ForksSchedule<>(List.of()), + NODE_KEY, + false, + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); final ProtocolSpec homesteadSpec = protocolSchedule.getByBlockHeader(blockHeader(1)); final ProtocolSpec tangerineWhistleSpec = protocolSchedule.getByBlockHeader(blockHeader(2)); @@ -69,12 +85,19 @@ public void protocolSpecsAreCreatedAtBlockDefinedInJson() { @Test public void parametersAlignWithMainnetWithAdjustments() { + final ForksSchedule forksSchedule = + new ForksSchedule<>(List.of(new ForkSpec<>(0, JsonCliqueConfigOptions.DEFAULT))); final ProtocolSpec homestead = CliqueProtocolSchedule.create( GenesisConfigFile.DEFAULT.getConfigOptions(), + forksSchedule, NODE_KEY, false, - EvmConfiguration.DEFAULT) + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()) .getByBlockHeader(blockHeader(0)); assertThat(homestead.getName()).isEqualTo("Frontier"); @@ -85,28 +108,44 @@ public void parametersAlignWithMainnetWithAdjustments() { @Test public void zeroEpochLengthThrowsException() { - final CliqueConfigOptions cliqueOptions = mock(CliqueConfigOptions.class); + final CliqueConfigOptions cliqueOptions = mock(JsonCliqueConfigOptions.class); when(cliqueOptions.getEpochLength()).thenReturn(0L); when(genesisConfig.getCliqueConfigOptions()).thenReturn(cliqueOptions); assertThatThrownBy( () -> CliqueProtocolSchedule.create( - genesisConfig, NODE_KEY, false, EvmConfiguration.DEFAULT)) + genesisConfig, + new ForksSchedule<>(List.of()), + NODE_KEY, + false, + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem())) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Epoch length in config must be greater than zero"); } @Test public void negativeEpochLengthThrowsException() { - final CliqueConfigOptions cliqueOptions = mock(CliqueConfigOptions.class); + final CliqueConfigOptions cliqueOptions = mock(JsonCliqueConfigOptions.class); when(cliqueOptions.getEpochLength()).thenReturn(-3000L); when(genesisConfig.getCliqueConfigOptions()).thenReturn(cliqueOptions); assertThatThrownBy( () -> CliqueProtocolSchedule.create( - genesisConfig, NODE_KEY, false, EvmConfiguration.DEFAULT)) + genesisConfig, + new ForksSchedule<>(List.of()), + NODE_KEY, + false, + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem())) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Epoch length in config must be greater than zero"); } @@ -120,8 +159,19 @@ public void shouldValidateBaseFeeMarketTransition() { "{\"config\": " + "\t{\"chainId\": 1337,\n" + "\t\"londonBlock\": 2}\n" + "}"; final GenesisConfigOptions config = GenesisConfigFile.fromConfig(jsonInput).getConfigOptions(); + final ForksSchedule forksSchedule = + new ForksSchedule<>(List.of(new ForkSpec<>(0, JsonCliqueConfigOptions.DEFAULT))); final ProtocolSchedule protocolSchedule = - CliqueProtocolSchedule.create(config, NODE_KEY, false, EvmConfiguration.DEFAULT); + CliqueProtocolSchedule.create( + config, + forksSchedule, + NODE_KEY, + false, + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); BlockHeader emptyFrontierParent = headerBuilder diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/NodeCanProduceNextBlockTest.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/NodeCanProduceNextBlockTest.java index 486c1840fc9..afd9651df61 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/NodeCanProduceNextBlockTest.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/NodeCanProduceNextBlockTest.java @@ -28,6 +28,7 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.AddressHelpers; import org.hyperledger.besu.ethereum.core.Block; @@ -37,7 +38,6 @@ import org.hyperledger.besu.ethereum.core.Util; import java.util.List; -import java.util.Optional; import com.google.common.collect.Lists; import org.junit.jupiter.api.BeforeEach; @@ -80,7 +80,8 @@ public void networkWithOneValidatorIsAllowedToCreateConsecutiveBlocks() { final ValidatorProvider validatorProvider = mock(ValidatorProvider.class); when(validatorProvider.getValidatorsAfterBlock(any())).thenReturn(validatorList); final CliqueContext cliqueContext = new CliqueContext(validatorProvider, null, blockInterface); - cliqueProtocolContext = new ProtocolContext(blockChain, null, cliqueContext, Optional.empty()); + cliqueProtocolContext = + new ProtocolContext(blockChain, null, cliqueContext, new BadBlockManager()); headerBuilder.number(1).parentHash(genesisBlock.getHash()); final Block block_1 = createEmptyBlock(proposerKeyPair); @@ -104,7 +105,8 @@ public void networkWithTwoValidatorsIsAllowedToProduceBlockIfNotPreviousBlockPro final ValidatorProvider validatorProvider = mock(ValidatorProvider.class); when(validatorProvider.getValidatorsAfterBlock(any())).thenReturn(validatorList); final CliqueContext cliqueContext = new CliqueContext(validatorProvider, null, blockInterface); - cliqueProtocolContext = new ProtocolContext(blockChain, null, cliqueContext, Optional.empty()); + cliqueProtocolContext = + new ProtocolContext(blockChain, null, cliqueContext, new BadBlockManager()); headerBuilder.number(1).parentHash(genesisBlock.getHash()); final Block block_1 = createEmptyBlock(proposerKeyPair); @@ -137,7 +139,8 @@ public void networkWithTwoValidatorsIsNotAllowedToProduceBlockIfIsPreviousBlockP final ValidatorProvider validatorProvider = mock(ValidatorProvider.class); when(validatorProvider.getValidatorsAfterBlock(any())).thenReturn(validatorList); final CliqueContext cliqueContext = new CliqueContext(validatorProvider, null, blockInterface); - cliqueProtocolContext = new ProtocolContext(blockChain, null, cliqueContext, Optional.empty()); + cliqueProtocolContext = + new ProtocolContext(blockChain, null, cliqueContext, new BadBlockManager()); headerBuilder.parentHash(genesisBlock.getHash()).number(1); final Block block_1 = createEmptyBlock(proposerKeyPair); @@ -166,7 +169,8 @@ public void withThreeValidatorsMustHaveOneBlockBetweenSignings() { final ValidatorProvider validatorProvider = mock(ValidatorProvider.class); when(validatorProvider.getValidatorsAfterBlock(any())).thenReturn(validatorList); final CliqueContext cliqueContext = new CliqueContext(validatorProvider, null, blockInterface); - cliqueProtocolContext = new ProtocolContext(blockChain, null, cliqueContext, Optional.empty()); + cliqueProtocolContext = + new ProtocolContext(blockChain, null, cliqueContext, new BadBlockManager()); headerBuilder.parentHash(genesisBlock.getHash()).number(1); final Block block_1 = createEmptyBlock(proposerKeyPair); @@ -210,7 +214,8 @@ public void signerIsValidIfInsufficientBlocksExistInHistory() { final ValidatorProvider validatorProvider = mock(ValidatorProvider.class); when(validatorProvider.getValidatorsAfterBlock(any())).thenReturn(validatorList); final CliqueContext cliqueContext = new CliqueContext(validatorProvider, null, blockInterface); - cliqueProtocolContext = new ProtocolContext(blockChain, null, cliqueContext, Optional.empty()); + cliqueProtocolContext = + new ProtocolContext(blockChain, null, cliqueContext, new BadBlockManager()); headerBuilder.parentHash(genesisBlock.getHash()).number(1); final Block block_1 = createEmptyBlock(otherNodeKeyPair); @@ -238,7 +243,8 @@ public void exceptionIsThrownIfOnAnOrphanedChain() { final ValidatorProvider validatorProvider = mock(ValidatorProvider.class); when(validatorProvider.getValidatorsAfterBlock(any())).thenReturn(validatorList); final CliqueContext cliqueContext = new CliqueContext(validatorProvider, null, blockInterface); - cliqueProtocolContext = new ProtocolContext(blockChain, null, cliqueContext, Optional.empty()); + cliqueProtocolContext = + new ProtocolContext(blockChain, null, cliqueContext, new BadBlockManager()); headerBuilder.parentHash(Hash.ZERO).number(3); final BlockHeader parentHeader = @@ -261,7 +267,8 @@ public void nonValidatorIsNotAllowedToCreateABlock() { final ValidatorProvider validatorProvider = mock(ValidatorProvider.class); when(validatorProvider.getValidatorsAfterBlock(any())).thenReturn(validatorList); final CliqueContext cliqueContext = new CliqueContext(validatorProvider, null, blockInterface); - cliqueProtocolContext = new ProtocolContext(blockChain, null, cliqueContext, Optional.empty()); + cliqueProtocolContext = + new ProtocolContext(blockChain, null, cliqueContext, new BadBlockManager()); headerBuilder.parentHash(Hash.ZERO).number(3); final BlockHeader parentHeader = headerBuilder.buildHeader(); diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreatorTest.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreatorTest.java index 645a1ca9482..ca76517c62c 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreatorTest.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockCreatorTest.java @@ -14,7 +14,7 @@ */ package org.hyperledger.besu.consensus.clique.blockcreation; -import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryBlockchain; import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryWorldStateArchive; import static org.mockito.ArgumentMatchers.any; @@ -30,6 +30,7 @@ import org.hyperledger.besu.consensus.clique.CliqueProtocolSchedule; import org.hyperledger.besu.consensus.clique.TestHelpers; import org.hyperledger.besu.consensus.common.EpochManager; +import org.hyperledger.besu.consensus.common.ForksSchedule; import org.hyperledger.besu.consensus.common.validator.ValidatorProvider; import org.hyperledger.besu.consensus.common.validator.ValidatorVote; import org.hyperledger.besu.consensus.common.validator.VoteProvider; @@ -41,6 +42,7 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.GenesisState; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.AddressHelpers; @@ -53,6 +55,7 @@ import org.hyperledger.besu.ethereum.core.Util; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -73,7 +76,6 @@ import com.google.common.collect.Lists; import org.apache.tuweni.bytes.Bytes; -import org.assertj.core.api.Java6Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -96,13 +98,18 @@ public class CliqueBlockCreatorTest { private VoteProvider voteProvider; @BeforeEach - public void setup() { + void setup() { protocolSchedule = CliqueProtocolSchedule.create( GenesisConfigFile.DEFAULT.getConfigOptions(), + new ForksSchedule<>(List.of()), proposerNodeKey, false, - EvmConfiguration.DEFAULT); + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); final Address otherAddress = Util.publicKeyToAddress(otherKeyPair.getPublicKey()); validatorList.add(otherAddress); @@ -117,7 +124,7 @@ public void setup() { GenesisState.fromConfig(GenesisConfigFile.mainnet(), protocolSchedule).getBlock(); blockchain = createInMemoryBlockchain(genesis); protocolContext = - new ProtocolContext(blockchain, stateArchive, cliqueContext, Optional.empty()); + new ProtocolContext(blockchain, stateArchive, cliqueContext, new BadBlockManager()); epochManager = new EpochManager(10); // Add a block above the genesis @@ -149,13 +156,13 @@ public void proposerAddressCanBeExtractFromAConstructedBlock() { protocolContext, protocolSchedule, proposerNodeKey, - blockchain.getChainHeadHeader(), epochManager, ethScheduler); - final Block createdBlock = blockCreator.createBlock(5L).getBlock(); + final Block createdBlock = + blockCreator.createBlock(5L, blockchain.getChainHeadHeader()).getBlock(); - Java6Assertions.assertThat(CliqueHelpers.getProposerOfBlock(createdBlock.getHeader())) + assertThat(CliqueHelpers.getProposerOfBlock(createdBlock.getHeader())) .isEqualTo(proposerAddress); } @@ -178,11 +185,11 @@ public void insertsValidVoteIntoConstructedBlock() { protocolContext, protocolSchedule, proposerNodeKey, - blockchain.getChainHeadHeader(), epochManager, ethScheduler); - final Block createdBlock = blockCreator.createBlock(0L).getBlock(); + final Block createdBlock = + blockCreator.createBlock(0L, blockchain.getChainHeadHeader()).getBlock(); assertThat(createdBlock.getHeader().getNonce()).isEqualTo(CliqueBlockInterface.ADD_NONCE); assertThat(createdBlock.getHeader().getCoinbase()).isEqualTo(a1); } @@ -212,11 +219,11 @@ public void insertsNoVoteWhenAtEpoch() { protocolContext, protocolSchedule, proposerNodeKey, - blockchain.getChainHeadHeader(), epochManager, ethScheduler); - final Block createdBlock = blockCreator.createBlock(0L).getBlock(); + final Block createdBlock = + blockCreator.createBlock(0L, blockchain.getChainHeadHeader()).getBlock(); assertThat(createdBlock.getHeader().getNonce()).isEqualTo(CliqueBlockInterface.DROP_NONCE); assertThat(createdBlock.getHeader().getCoinbase()).isEqualTo(Address.fromHexString("0")); } @@ -240,7 +247,7 @@ private TransactionPool createTransactionPool() { ethContext, new TransactionPoolMetrics(metricsSystem), conf, - null); + new BlobCache()); transactionPool.setEnabled(); return transactionPool; } diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockMinerTest.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockMinerTest.java index ed1cfced5e3..8aca4713cd9 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockMinerTest.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockMinerTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.consensus.clique.blockcreation; import static org.assertj.core.api.Assertions.assertThat; @@ -23,15 +22,22 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import org.hyperledger.besu.config.CliqueConfigOptions; +import org.hyperledger.besu.config.ImmutableCliqueConfigOptions; +import org.hyperledger.besu.config.JsonCliqueConfigOptions; import org.hyperledger.besu.consensus.clique.CliqueContext; +import org.hyperledger.besu.consensus.common.ForkSpec; +import org.hyperledger.besu.consensus.common.ForksSchedule; import org.hyperledger.besu.consensus.common.validator.ValidatorProvider; import org.hyperledger.besu.crypto.KeyPair; import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.blockcreation.BlockCreationTiming; import org.hyperledger.besu.ethereum.blockcreation.BlockCreator; import org.hyperledger.besu.ethereum.blockcreation.DefaultBlockScheduler; import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MinedBlockObserver; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockBody; @@ -53,10 +59,20 @@ import java.util.function.Function; import com.google.common.collect.Lists; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; class CliqueBlockMinerTest { + private ForksSchedule forksSchedule; + + @BeforeEach + public void setup() { + var options = ImmutableCliqueConfigOptions.builder().from(JsonCliqueConfigOptions.DEFAULT); + options.createEmptyBlocks(false); + forksSchedule = new ForksSchedule<>(List.of(new ForkSpec<>(0, options.build()))); + } + @Test void doesNotMineBlockIfNoTransactionsWhenEmptyBlocksNotAllowed() throws InterruptedException { final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture(); @@ -70,14 +86,15 @@ void doesNotMineBlockIfNoTransactionsWhenEmptyBlocksNotAllowed() throws Interrup final CliqueContext cliqueContext = new CliqueContext(validatorProvider, null, null); final ProtocolContext protocolContext = - new ProtocolContext(null, null, cliqueContext, Optional.empty()); + new ProtocolContext(null, null, cliqueContext, new BadBlockManager()); final CliqueBlockCreator blockCreator = mock(CliqueBlockCreator.class); final Function blockCreatorSupplier = (parentHeader) -> blockCreator; - when(blockCreator.createBlock(anyLong())) + when(blockCreator.createBlock(anyLong(), any())) .thenReturn( - new BlockCreator.BlockCreationResult(blockToCreate, new TransactionSelectionResults())); + new BlockCreator.BlockCreationResult( + blockToCreate, new TransactionSelectionResults(), new BlockCreationTiming())); final BlockImporter blockImporter = mock(BlockImporter.class); final ProtocolSpec protocolSpec = mock(ProtocolSpec.class); @@ -99,7 +116,7 @@ void doesNotMineBlockIfNoTransactionsWhenEmptyBlocksNotAllowed() throws Interrup scheduler, headerBuilder.buildHeader(), Address.ZERO, - false); // parent header is arbitrary for the test. + forksSchedule); // parent header is arbitrary for the test. final boolean result = miner.mineBlock(); assertThat(result).isFalse(); @@ -125,14 +142,15 @@ void minesBlockIfHasTransactionsWhenEmptyBlocksNotAllowed() throws InterruptedEx final CliqueContext cliqueContext = new CliqueContext(validatorProvider, null, null); final ProtocolContext protocolContext = - new ProtocolContext(null, null, cliqueContext, Optional.empty()); + new ProtocolContext(null, null, cliqueContext, new BadBlockManager()); final CliqueBlockCreator blockCreator = mock(CliqueBlockCreator.class); final Function blockCreatorSupplier = (parentHeader) -> blockCreator; - when(blockCreator.createBlock(anyLong())) + when(blockCreator.createBlock(anyLong(), any())) .thenReturn( - new BlockCreator.BlockCreationResult(blockToCreate, new TransactionSelectionResults())); + new BlockCreator.BlockCreationResult( + blockToCreate, new TransactionSelectionResults(), new BlockCreationTiming())); final BlockImporter blockImporter = mock(BlockImporter.class); final ProtocolSpec protocolSpec = mock(ProtocolSpec.class); @@ -154,7 +172,7 @@ void minesBlockIfHasTransactionsWhenEmptyBlocksNotAllowed() throws InterruptedEx scheduler, headerBuilder.buildHeader(), Address.ZERO, - false); // parent header is arbitrary for the test. + forksSchedule); // parent header is arbitrary for the test. final boolean result = miner.mineBlock(); assertThat(result).isTrue(); diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockSchedulerTest.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockSchedulerTest.java index 87aeb9d6694..ed973306dc3 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockSchedulerTest.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueBlockSchedulerTest.java @@ -19,6 +19,11 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import org.hyperledger.besu.config.CliqueConfigOptions; +import org.hyperledger.besu.config.ImmutableCliqueConfigOptions; +import org.hyperledger.besu.config.JsonCliqueConfigOptions; +import org.hyperledger.besu.consensus.common.ForkSpec; +import org.hyperledger.besu.consensus.common.ForksSchedule; import org.hyperledger.besu.consensus.common.validator.ValidatorProvider; import org.hyperledger.besu.crypto.KeyPair; import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; @@ -37,13 +42,13 @@ import org.junit.jupiter.api.Test; public class CliqueBlockSchedulerTest { - private final KeyPair proposerKeyPair = SignatureAlgorithmFactory.getInstance().generateKeyPair(); private Address localAddr; private final List
validatorList = Lists.newArrayList(); private ValidatorProvider validatorProvider; private BlockHeaderTestFixture blockHeaderBuilder; + private ForksSchedule forksSchedule; @BeforeEach public void setup() { @@ -56,16 +61,21 @@ public void setup() { when(validatorProvider.getValidatorsAfterBlock(any())).thenReturn(validatorList); blockHeaderBuilder = new BlockHeaderTestFixture(); + + var initialTransition = + ImmutableCliqueConfigOptions.builder().from(JsonCliqueConfigOptions.DEFAULT); + initialTransition.blockPeriodSeconds(5); + forksSchedule = new ForksSchedule<>(List.of(new ForkSpec<>(0, initialTransition.build()))); } @Test public void inturnValidatorWaitsExactlyBlockInterval() { final Clock clock = mock(Clock.class); final long currentSecondsSinceEpoch = 10L; - final long secondsBetweenBlocks = 5L; + final int secondsBetweenBlocks = 5; when(clock.millis()).thenReturn(currentSecondsSinceEpoch * 1000); final CliqueBlockScheduler scheduler = - new CliqueBlockScheduler(clock, validatorProvider, localAddr, secondsBetweenBlocks); + new CliqueBlockScheduler(clock, validatorProvider, localAddr, forksSchedule); // There are 2 validators, therefore block 2 will put localAddr as the in-turn voter, therefore // parent block should be number 1. @@ -74,19 +84,67 @@ public void inturnValidatorWaitsExactlyBlockInterval() { final BlockCreationTimeResult result = scheduler.getNextTimestamp(parentHeader); - assertThat(result.getTimestampForHeader()) + assertThat(result.timestampForHeader()) .isEqualTo(currentSecondsSinceEpoch + secondsBetweenBlocks); - assertThat(result.getMillisecondsUntilValid()).isEqualTo(secondsBetweenBlocks * 1000); + assertThat(result.millisecondsUntilValid()).isEqualTo(secondsBetweenBlocks * 1000); } @Test - public void outOfturnValidatorWaitsLongerThanBlockInterval() { + public void validatorWithTransitionForBlockTimeWaitsBlockInterval() { + final Clock clock = mock(Clock.class); + final long currentSecondsSinceEpoch = 10L; + when(clock.millis()).thenReturn(currentSecondsSinceEpoch * 1000); + + final var initialTransition = + ImmutableCliqueConfigOptions.builder().from(JsonCliqueConfigOptions.DEFAULT); + initialTransition.blockPeriodSeconds(5); + final var decreaseBlockTimeTransition = + ImmutableCliqueConfigOptions.builder().from(JsonCliqueConfigOptions.DEFAULT); + decreaseBlockTimeTransition.blockPeriodSeconds(1); + forksSchedule = + new ForksSchedule<>( + List.of( + new ForkSpec<>(0, initialTransition.build()), + new ForkSpec<>(4, decreaseBlockTimeTransition.build()))); + + final CliqueBlockScheduler scheduler = + new CliqueBlockScheduler(clock, validatorProvider, localAddr, forksSchedule); + + // getNextTimestamp for last block before transition + // There are 2 validators, therefore block 3 will put localAddr as the out-of-turn voter, + // therefore + // parent block should be number 2. + BlockHeader parentHeader = + blockHeaderBuilder.number(2).timestamp(currentSecondsSinceEpoch).buildHeader(); + BlockCreationTimeResult result = scheduler.getNextTimestamp(parentHeader); + assertThat(result.timestampForHeader()).isEqualTo(currentSecondsSinceEpoch + 5); + assertThat(result.millisecondsUntilValid()).isGreaterThan(5 * 1000); + + // getNextTimestamp for transition block + // There are 2 validators, therefore block 4 will put localAddr as the in-turn voter, therefore + // parent block should be number 3. + parentHeader = blockHeaderBuilder.number(3).timestamp(currentSecondsSinceEpoch).buildHeader(); + result = scheduler.getNextTimestamp(parentHeader); + assertThat(result.timestampForHeader()).isEqualTo(currentSecondsSinceEpoch + 1); + assertThat(result.millisecondsUntilValid()).isEqualTo(1000); + + // getNextTimestamp for block after transition + // There are 2 validators, therefore block 5 will put localAddr as the out-of-turn voter, + // therefore + // parent block should be number 4. + parentHeader = blockHeaderBuilder.number(4).timestamp(currentSecondsSinceEpoch).buildHeader(); + result = scheduler.getNextTimestamp(parentHeader); + assertThat(result.timestampForHeader()).isEqualTo(currentSecondsSinceEpoch + 1); + assertThat(result.millisecondsUntilValid()).isGreaterThan(1000); + } + + @Test + public void outOfTurnValidatorWaitsLongerThanBlockInterval() { final Clock clock = mock(Clock.class); final long currentSecondsSinceEpoch = 10L; - final long secondsBetweenBlocks = 5L; when(clock.millis()).thenReturn(currentSecondsSinceEpoch * 1000); final CliqueBlockScheduler scheduler = - new CliqueBlockScheduler(clock, validatorProvider, localAddr, secondsBetweenBlocks); + new CliqueBlockScheduler(clock, validatorProvider, localAddr, forksSchedule); // There are 2 validators, therefore block 3 will put localAddr as the out-turn voter, therefore // parent block should be number 2. @@ -95,9 +153,10 @@ public void outOfturnValidatorWaitsLongerThanBlockInterval() { final BlockCreationTimeResult result = scheduler.getNextTimestamp(parentHeader); - assertThat(result.getTimestampForHeader()) + long secondsBetweenBlocks = 5L; + assertThat(result.timestampForHeader()) .isEqualTo(currentSecondsSinceEpoch + secondsBetweenBlocks); - assertThat(result.getMillisecondsUntilValid()).isGreaterThan(secondsBetweenBlocks * 1000); + assertThat(result.millisecondsUntilValid()).isGreaterThan(secondsBetweenBlocks * 1000); } @Test @@ -107,7 +166,7 @@ public void inTurnValidatorCreatesBlockNowIFParentTimestampSufficientlyBehindNow final long secondsBetweenBlocks = 5L; when(clock.millis()).thenReturn(currentSecondsSinceEpoch * 1000); final CliqueBlockScheduler scheduler = - new CliqueBlockScheduler(clock, validatorProvider, localAddr, secondsBetweenBlocks); + new CliqueBlockScheduler(clock, validatorProvider, localAddr, forksSchedule); // There are 2 validators, therefore block 2 will put localAddr as the in-turn voter, therefore // parent block should be number 1. @@ -119,7 +178,7 @@ public void inTurnValidatorCreatesBlockNowIFParentTimestampSufficientlyBehindNow final BlockCreationTimeResult result = scheduler.getNextTimestamp(parentHeader); - assertThat(result.getTimestampForHeader()).isEqualTo(currentSecondsSinceEpoch); - assertThat(result.getMillisecondsUntilValid()).isEqualTo(0); + assertThat(result.timestampForHeader()).isEqualTo(currentSecondsSinceEpoch); + assertThat(result.millisecondsUntilValid()).isEqualTo(0); } } diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutorTest.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutorTest.java index 090d2b52c5a..1aa2d75ef3b 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutorTest.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/blockcreation/CliqueMinerExecutorTest.java @@ -28,12 +28,14 @@ import org.hyperledger.besu.consensus.clique.CliqueExtraData; import org.hyperledger.besu.consensus.clique.CliqueProtocolSchedule; import org.hyperledger.besu.consensus.common.EpochManager; +import org.hyperledger.besu.consensus.common.ForksSchedule; import org.hyperledger.besu.consensus.common.validator.ValidatorProvider; import org.hyperledger.besu.cryptoservices.NodeKey; import org.hyperledger.besu.cryptoservices.NodeKeyUtils; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.AddressHelpers; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; @@ -43,6 +45,7 @@ import org.hyperledger.besu.ethereum.core.Util; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -94,10 +97,18 @@ public void setup() { when(validatorProvider.getValidatorsAfterBlock(any())).thenReturn(validatorList); final CliqueContext cliqueContext = new CliqueContext(validatorProvider, null, blockInterface); - cliqueProtocolContext = new ProtocolContext(null, null, cliqueContext, Optional.empty()); + cliqueProtocolContext = new ProtocolContext(null, null, cliqueContext, new BadBlockManager()); cliqueProtocolSchedule = CliqueProtocolSchedule.create( - GENESIS_CONFIG_OPTIONS, proposerNodeKey, false, EvmConfiguration.DEFAULT); + GENESIS_CONFIG_OPTIONS, + new ForksSchedule<>(List.of()), + proposerNodeKey, + false, + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); cliqueEthContext = mock(EthContext.class, RETURNS_DEEP_STUBS); blockHeaderBuilder = new BlockHeaderTestFixture(); } @@ -117,7 +128,7 @@ public void extraDataCreatedOnEpochBlocksContainsValidators() { miningParameters, mock(CliqueBlockScheduler.class), new EpochManager(EPOCH_LENGTH), - true, + null, ethScheduler); // NOTE: Passing in the *parent* block, so must be 1 less than EPOCH @@ -153,7 +164,7 @@ public void extraDataForNonEpochBlocksDoesNotContainValidaors() { miningParameters, mock(CliqueBlockScheduler.class), new EpochManager(EPOCH_LENGTH), - true, + null, ethScheduler); // Parent block was epoch, so the next block should contain no validators. @@ -189,10 +200,10 @@ public void shouldUseLatestVanityData() { miningParameters, mock(CliqueBlockScheduler.class), new EpochManager(EPOCH_LENGTH), - true, + null, ethScheduler); - executor.setExtraData(modifiedVanityData); + miningParameters.setExtraData(modifiedVanityData); final Bytes extraDataBytes = executor.calculateExtraData(blockHeaderBuilder.buildHeader()); final CliqueExtraData cliqueExtraData = @@ -224,7 +235,7 @@ private TransactionPool createTransactionPool() { cliqueEthContext, new TransactionPoolMetrics(metricsSystem), conf, - null); + new BlobCache()); transactionPool.setEnabled(); return transactionPool; diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/headervalidationrules/CliqueDifficultyValidationRuleTest.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/headervalidationrules/CliqueDifficultyValidationRuleTest.java index 7f7daf4eeab..d970d83d80e 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/headervalidationrules/CliqueDifficultyValidationRuleTest.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/headervalidationrules/CliqueDifficultyValidationRuleTest.java @@ -27,6 +27,7 @@ import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.AddressHelpers; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; @@ -34,7 +35,6 @@ import org.hyperledger.besu.ethereum.core.Util; import java.util.List; -import java.util.Optional; import com.google.common.collect.Lists; import org.junit.jupiter.api.BeforeEach; @@ -58,7 +58,7 @@ public void setup() { when(validatorProvider.getValidatorsAfterBlock(any())).thenReturn(validatorList); final CliqueContext cliqueContext = new CliqueContext(validatorProvider, null, blockInterface); - cliqueProtocolContext = new ProtocolContext(null, null, cliqueContext, Optional.empty()); + cliqueProtocolContext = new ProtocolContext(null, null, cliqueContext, new BadBlockManager()); blockHeaderBuilder = new BlockHeaderTestFixture(); } diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/headervalidationrules/CliqueExtraDataValidationRuleTest.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/headervalidationrules/CliqueExtraDataValidationRuleTest.java index 72043c9cb9a..afcc7e23c2f 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/headervalidationrules/CliqueExtraDataValidationRuleTest.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/headervalidationrules/CliqueExtraDataValidationRuleTest.java @@ -14,7 +14,7 @@ */ package org.hyperledger.besu.consensus.clique.headervalidationrules; -import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -29,13 +29,13 @@ import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.AddressHelpers; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.Util; import java.util.List; -import java.util.Optional; import com.google.common.collect.Lists; import org.apache.tuweni.bytes.Bytes; @@ -62,7 +62,7 @@ public void setup() { when(validatorProvider.getValidatorsAfterBlock(any())).thenReturn(validatorList); final CliqueContext cliqueContext = new CliqueContext(validatorProvider, null, blockInterface); - cliqueProtocolContext = new ProtocolContext(null, null, cliqueContext, Optional.empty()); + cliqueProtocolContext = new ProtocolContext(null, null, cliqueContext, new BadBlockManager()); } @Test diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/headervalidationrules/CliqueNoEmptyBlockValidationRuleTest.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/headervalidationrules/CliqueNoEmptyBlockValidationRuleTest.java index 807aef0b1fc..7a6ba99cf8f 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/headervalidationrules/CliqueNoEmptyBlockValidationRuleTest.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/headervalidationrules/CliqueNoEmptyBlockValidationRuleTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.consensus.clique.headervalidationrules; import static org.assertj.core.api.Assertions.assertThat; diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/headervalidationrules/VoteValidationRuleTest.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/headervalidationrules/VoteValidationRuleTest.java index fdb68859577..0ba0e5f3c8b 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/headervalidationrules/VoteValidationRuleTest.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/headervalidationrules/VoteValidationRuleTest.java @@ -14,7 +14,7 @@ */ package org.hyperledger.besu.consensus.clique.headervalidationrules; -import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import org.hyperledger.besu.consensus.clique.CliqueBlockInterface; import org.hyperledger.besu.ethereum.core.BlockHeader; @@ -22,6 +22,7 @@ import java.util.stream.Stream; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -52,4 +53,11 @@ public void test(final long input, final boolean expectedResult) { assertThat(uut.validate(header, null)).isEqualTo(expectedResult); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/CliqueGetSignerMetricsTest.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/CliqueGetSignerMetricsTest.java index 89c48fb978b..310b3b1017d 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/CliqueGetSignerMetricsTest.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/CliqueGetSignerMetricsTest.java @@ -75,14 +75,14 @@ public void returnsCorrectMethodName() { public void exceptionWhenInvalidStartBlockSupplied() { assertThatThrownBy(() -> method.response(requestWithParams("INVALID"))) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Invalid json rpc parameter at index 0"); + .hasMessageContaining("Invalid start block parameter (index 0)"); } @Test public void exceptionWhenInvalidEndBlockSupplied() { assertThatThrownBy(() -> method.response(requestWithParams("1", "INVALID"))) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Invalid json rpc parameter at index 1"); + .hasMessageContaining("Invalid end block parameter (index 1)"); } @Test diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/CliqueGetSignersAtHashTest.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/CliqueGetSignersAtHashTest.java index ee5dc0f023c..3699ccf6eed 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/CliqueGetSignersAtHashTest.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/CliqueGetSignersAtHashTest.java @@ -101,7 +101,7 @@ public void failsWhenNoParam() { assertThat(thrown) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 0"); + .hasMessage("Invalid block hash parameter (index 0)"); } @Test diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/CliqueProposalsTest.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/CliqueProposalsTest.java index 50dc379b576..cdfbeb6299b 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/CliqueProposalsTest.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/CliqueProposalsTest.java @@ -14,7 +14,7 @@ */ package org.hyperledger.besu.consensus.clique.jsonrpc.methods; -import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import org.hyperledger.besu.consensus.common.jsonrpc.AbstractVoteProposerMethod; import org.hyperledger.besu.consensus.common.jsonrpc.AbstractVoteProposerMethodTest; diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/DiscardTest.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/DiscardTest.java index f5810520fc7..80220a3f04a 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/DiscardTest.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/DiscardTest.java @@ -28,9 +28,9 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponseType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -61,7 +61,7 @@ public void discardEmpty() { final JsonRpcResponse response = discard.response(requestWithParams(a0)); - assertThat(response.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); + assertThat(response.getType()).isEqualTo(RpcResponseType.SUCCESS); final JsonRpcSuccessResponse successResponse = (JsonRpcSuccessResponse) response; assertThat(successResponse.getResult()).isEqualTo(true); assertThat(validatorProvider.getVoteProviderAtHead().get().getProposals().get(a0)).isNull(); @@ -77,7 +77,7 @@ public void discardAuth() { final JsonRpcResponse response = discard.response(requestWithParams(a0)); assertThat(validatorProvider.getVoteProviderAtHead().get().getProposals().get(a0)).isNull(); - assertThat(response.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); + assertThat(response.getType()).isEqualTo(RpcResponseType.SUCCESS); final JsonRpcSuccessResponse successResponse = (JsonRpcSuccessResponse) response; assertThat(successResponse.getResult()).isEqualTo(true); } @@ -92,7 +92,7 @@ public void discardDrop() { final JsonRpcResponse response = discard.response(requestWithParams(a0)); assertThat(validatorProvider.getVoteProviderAtHead().get().getProposals().get(a0)).isNull(); - assertThat(response.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); + assertThat(response.getType()).isEqualTo(RpcResponseType.SUCCESS); final JsonRpcSuccessResponse successResponse = (JsonRpcSuccessResponse) response; assertThat(successResponse.getResult()).isEqualTo(true); } @@ -111,7 +111,7 @@ public void discardIsolation() { assertThat(validatorProvider.getVoteProviderAtHead().get().getProposals().get(a0)).isNull(); assertThat(validatorProvider.getVoteProviderAtHead().get().getProposals().get(a1)) .isEqualTo(VoteType.ADD); - assertThat(response.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); + assertThat(response.getType()).isEqualTo(RpcResponseType.SUCCESS); final JsonRpcSuccessResponse successResponse = (JsonRpcSuccessResponse) response; assertThat(successResponse.getResult()).isEqualTo(true); } @@ -121,7 +121,7 @@ public void discardWithoutAddress() { final Discard discard = new Discard(validatorProvider); assertThatThrownBy(() -> discard.response(requestWithParams())) - .hasMessage("Missing required json rpc parameter at index 0") + .hasMessage("Invalid address parameter (index 0)") .isInstanceOf(InvalidJsonRpcParameters.class); } diff --git a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/ProposeTest.java b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/ProposeTest.java index efbc78542c5..7e7d7bf7704 100644 --- a/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/ProposeTest.java +++ b/consensus/clique/src/test/java/org/hyperledger/besu/consensus/clique/jsonrpc/methods/ProposeTest.java @@ -27,10 +27,10 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponseType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -59,7 +59,7 @@ public void testAuth() { assertThat(validatorProvider.getVoteProviderAtHead().get().getProposals().get(a1)) .isEqualTo(VoteType.ADD); - assertThat(response.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); + assertThat(response.getType()).isEqualTo(RpcResponseType.SUCCESS); final JsonRpcSuccessResponse successResponse = (JsonRpcSuccessResponse) response; assertThat(successResponse.getResult()).isEqualTo(true); } @@ -72,7 +72,7 @@ public void testAuthWithAddressZeroResultsInError() { final JsonRpcResponse response = propose.response(requestWithParams(a0, true)); assertThat(validatorProvider.getVoteProviderAtHead().get().getProposals().get(a0)).isNull(); - assertThat(response.getType()).isEqualTo(JsonRpcResponseType.ERROR); + assertThat(response.getType()).isEqualTo(RpcResponseType.ERROR); final JsonRpcErrorResponse errorResponse = (JsonRpcErrorResponse) response; assertThat(errorResponse.getErrorType()).isEqualTo(RpcErrorType.INVALID_REQUEST); } @@ -86,7 +86,7 @@ public void testDrop() { assertThat(validatorProvider.getVoteProviderAtHead().get().getProposals().get(a1)) .isEqualTo(VoteType.DROP); - assertThat(response.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); + assertThat(response.getType()).isEqualTo(RpcResponseType.SUCCESS); final JsonRpcSuccessResponse successResponse = (JsonRpcSuccessResponse) response; assertThat(successResponse.getResult()).isEqualTo(true); } @@ -99,7 +99,7 @@ public void testDropWithAddressZeroResultsInError() { final JsonRpcResponse response = propose.response(requestWithParams(a0, false)); assertThat(validatorProvider.getVoteProviderAtHead().get().getProposals().get(a0)).isNull(); - assertThat(response.getType()).isEqualTo(JsonRpcResponseType.ERROR); + assertThat(response.getType()).isEqualTo(RpcResponseType.ERROR); final JsonRpcErrorResponse errorResponse = (JsonRpcErrorResponse) response; assertThat(errorResponse.getErrorType()).isEqualTo(RpcErrorType.INVALID_REQUEST); } @@ -114,7 +114,7 @@ public void testRepeatAuth() { assertThat(validatorProvider.getVoteProviderAtHead().get().getProposals().get(a1)) .isEqualTo(VoteType.ADD); - assertThat(response.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); + assertThat(response.getType()).isEqualTo(RpcResponseType.SUCCESS); final JsonRpcSuccessResponse successResponse = (JsonRpcSuccessResponse) response; assertThat(successResponse.getResult()).isEqualTo(true); } @@ -129,7 +129,7 @@ public void testRepeatDrop() { assertThat(validatorProvider.getVoteProviderAtHead().get().getProposals().get(a1)) .isEqualTo(VoteType.DROP); - assertThat(response.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); + assertThat(response.getType()).isEqualTo(RpcResponseType.SUCCESS); final JsonRpcSuccessResponse successResponse = (JsonRpcSuccessResponse) response; assertThat(successResponse.getResult()).isEqualTo(true); } @@ -144,7 +144,7 @@ public void testChangeToAuth() { assertThat(validatorProvider.getVoteProviderAtHead().get().getProposals().get(a1)) .isEqualTo(VoteType.ADD); - assertThat(response.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); + assertThat(response.getType()).isEqualTo(RpcResponseType.SUCCESS); final JsonRpcSuccessResponse successResponse = (JsonRpcSuccessResponse) response; assertThat(successResponse.getResult()).isEqualTo(true); } @@ -159,7 +159,7 @@ public void testChangeToDrop() { assertThat(validatorProvider.getVoteProviderAtHead().get().getProposals().get(a0)) .isEqualTo(VoteType.DROP); - assertThat(response.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); + assertThat(response.getType()).isEqualTo(RpcResponseType.SUCCESS); final JsonRpcSuccessResponse successResponse = (JsonRpcSuccessResponse) response; assertThat(successResponse.getResult()).isEqualTo(true); } diff --git a/consensus/common/build.gradle b/consensus/common/build.gradle index 49c73e7d176..7a937f75733 100644 --- a/consensus/common/build.gradle +++ b/consensus/common/build.gradle @@ -22,7 +22,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } @@ -53,13 +54,11 @@ dependencies { testImplementation project( path: ':crypto:services', configuration: 'testSupportArtifacts') testImplementation project(':metrics:core') - testImplementation 'junit:junit' testImplementation 'org.assertj:assertj-core' testImplementation 'org.awaitility:awaitility' testImplementation 'org.junit.jupiter:junit-jupiter' testImplementation 'org.mockito:mockito-core' - - testRuntimeOnly 'org.junit.vintage:junit-vintage-engine' + testImplementation 'org.mockito:mockito-junit-jupiter' testSupportImplementation project( path: ':crypto:services', configuration: 'testSupportArtifacts') testSupportImplementation project( path: ':ethereum:core', configuration: 'testSupportArtifacts') diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/CombinedProtocolScheduleFactory.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/CombinedProtocolScheduleFactory.java index 31768edad4c..4319f9b2462 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/CombinedProtocolScheduleFactory.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/CombinedProtocolScheduleFactory.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -28,6 +28,8 @@ /** The Combined protocol schedule factory. */ public class CombinedProtocolScheduleFactory { + /** Default constructor. */ + public CombinedProtocolScheduleFactory() {} /** * Create protocol schedule. @@ -53,14 +55,22 @@ public BftProtocolSchedule create( protocolSchedule.getScheduledProtocolSpecs().stream() .filter(protocolSpecMatchesConsensusBlockRange(spec.getBlock(), endBlock)) .forEach( - s -> - combinedProtocolSchedule.putBlockNumberMilestone(s.fork().milestone(), s.spec())); + s -> { + if (s instanceof ScheduledProtocolSpec.TimestampProtocolSpec) { + combinedProtocolSchedule.putTimestampMilestone(s.fork().milestone(), s.spec()); + } else if (s instanceof ScheduledProtocolSpec.BlockNumberProtocolSpec) { + combinedProtocolSchedule.putBlockNumberMilestone(s.fork().milestone(), s.spec()); + } else { + throw new IllegalStateException( + "Unexpected milestone: " + s + " for milestone: " + s.fork().milestone()); + } + }); // When moving to a new consensus mechanism we want to use the last milestone but created by // our consensus mechanism's BesuControllerBuilder so any additional rules are applied if (spec.getBlock() > 0) { combinedProtocolSchedule.putBlockNumberMilestone( - spec.getBlock(), protocolSchedule.getByBlockNumber(spec.getBlock())); + spec.getBlock(), protocolSchedule.getByBlockNumberOrTimestamp(spec.getBlock(), 0L)); } } return combinedProtocolSchedule; diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/ConsensusHelpers.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/ConsensusHelpers.java index fad1160e48a..d44527f57bf 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/ConsensusHelpers.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/ConsensusHelpers.java @@ -18,6 +18,8 @@ /** The Consensus helpers. */ public class ConsensusHelpers { + /** Default constructor. */ + private ConsensusHelpers() {} /** * Zero left pad bytes. diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/ForkSpec.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/ForkSpec.java index f52bbc982b3..0af223e0afe 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/ForkSpec.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/ForkSpec.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/ForksSchedule.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/ForksSchedule.java index 1e8d724b439..3ab50b6cacb 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/ForksSchedule.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/ForksSchedule.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/ForksScheduleFactory.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/ForksScheduleFactory.java new file mode 100644 index 00000000000..c665a11510d --- /dev/null +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/ForksScheduleFactory.java @@ -0,0 +1,82 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.consensus.common; + +import static com.google.common.base.Preconditions.checkArgument; + +import org.hyperledger.besu.config.Fork; + +import java.util.Comparator; +import java.util.List; +import java.util.NavigableSet; +import java.util.TreeSet; + +/** The forks schedule factory. */ +public class ForksScheduleFactory { + + /** + * The interface spec creator. + * + * @param the type parameter + * @param the type parameter + */ + public interface SpecCreator { + /** + * Create type of ConfigOptions. + * + * @param lastSpec the last spec + * @param fork the fork + * @return the type of ConfigOptions + */ + T create(ForkSpec lastSpec, U fork); + } + + /** Default constructor. */ + private ForksScheduleFactory() {} + + /** + * Create forks schedule. + * + * @param the type parameter ConfigOptions + * @param the type parameter Fork + * @param initial the initial + * @param forks the forks + * @param specCreator the spec creator + * @return the forks schedule + */ + public static ForksSchedule create( + final T initial, final List forks, final SpecCreator specCreator) { + checkArgument( + forks.stream().allMatch(f -> f.getForkBlock() > 0), + "Transition cannot be created for genesis block"); + checkArgument( + forks.stream().map(Fork::getForkBlock).distinct().count() == forks.size(), + "Duplicate transitions cannot be created for the same block"); + + final NavigableSet> specs = new TreeSet<>(Comparator.comparing(ForkSpec::getBlock)); + final ForkSpec initialForkSpec = new ForkSpec<>(0, initial); + specs.add(initialForkSpec); + + forks.stream() + .sorted(Comparator.comparing(Fork::getForkBlock)) + .forEachOrdered( + f -> { + final T spec = specCreator.create(specs.last(), f); + specs.add(new ForkSpec<>(f.getForkBlock(), spec)); + }); + + return new ForksSchedule<>(specs); + } +} diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/MigratingContext.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/MigratingContext.java index 4011e40c723..f71e3ac7f36 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/MigratingContext.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/MigratingContext.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/MigratingMiningCoordinator.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/MigratingMiningCoordinator.java index de3fc3a3a0f..6450a108fd9 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/MigratingMiningCoordinator.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/MigratingMiningCoordinator.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -29,7 +29,6 @@ import java.util.concurrent.CompletableFuture; import com.google.common.annotations.VisibleForTesting; -import org.apache.tuweni.bytes.Bytes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -104,8 +103,8 @@ public Wei getMinTransactionGasPrice() { } @Override - public void setExtraData(final Bytes extraData) { - activeMiningCoordinator.setExtraData(extraData); + public Wei getMinPriorityFeePerGas() { + return activeMiningCoordinator.getMinPriorityFeePerGas(); } @Override diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/MigratingProtocolContext.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/MigratingProtocolContext.java index 32099e23fdf..c0557485f3e 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/MigratingProtocolContext.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/MigratingProtocolContext.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,12 +17,10 @@ import org.hyperledger.besu.ethereum.ConsensusContext; import org.hyperledger.besu.ethereum.ConsensusContextFactory; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; -import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelectorFactory; - -import java.util.Optional; /** The Migrating protocol context. */ public class MigratingProtocolContext extends ProtocolContext { @@ -35,14 +33,14 @@ public class MigratingProtocolContext extends ProtocolContext { * @param blockchain the blockchain * @param worldStateArchive the world state archive * @param consensusContextSchedule the consensus context schedule - * @param transactionSelectorFactory the optional transaction selector factory + * @param badBlockManager the cache to use to keep invalid blocks */ public MigratingProtocolContext( final MutableBlockchain blockchain, final WorldStateArchive worldStateArchive, final ForksSchedule consensusContextSchedule, - final Optional transactionSelectorFactory) { - super(blockchain, worldStateArchive, null, transactionSelectorFactory); + final BadBlockManager badBlockManager) { + super(blockchain, worldStateArchive, null, badBlockManager); this.consensusContextSchedule = consensusContextSchedule; } @@ -53,7 +51,7 @@ public MigratingProtocolContext( * @param worldStateArchive the world state archive * @param protocolSchedule the protocol schedule * @param consensusContextFactory the consensus context factory - * @param transactionSelectorFactory the optional transaction selector factory + * @param badBlockManager the cache to use to keep invalid blocks * @return the protocol context */ public static ProtocolContext init( @@ -61,7 +59,7 @@ public static ProtocolContext init( final WorldStateArchive worldStateArchive, final ProtocolSchedule protocolSchedule, final ConsensusContextFactory consensusContextFactory, - final Optional transactionSelectorFactory) { + final BadBlockManager badBlockManager) { final ConsensusContext consensusContext = consensusContextFactory.create(blockchain, worldStateArchive, protocolSchedule); final MigratingContext migratingContext = consensusContext.as(MigratingContext.class); @@ -69,7 +67,7 @@ public static ProtocolContext init( blockchain, worldStateArchive, migratingContext.getConsensusContextSchedule(), - transactionSelectorFactory); + badBlockManager); } @Override diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BaseBftProtocolScheduleBuilder.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BaseBftProtocolScheduleBuilder.java index c17652f4a43..311bf30faef 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BaseBftProtocolScheduleBuilder.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BaseBftProtocolScheduleBuilder.java @@ -18,6 +18,8 @@ import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.consensus.common.ForksSchedule; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.mainnet.BlockHeaderValidator; import org.hyperledger.besu.ethereum.mainnet.DefaultProtocolSchedule; @@ -28,8 +30,10 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecBuilder; +import org.hyperledger.besu.ethereum.mainnet.WithdrawalsValidator; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.plugin.services.MetricsSystem; import java.math.BigInteger; import java.util.HashMap; @@ -41,6 +45,9 @@ public abstract class BaseBftProtocolScheduleBuilder { private static final BigInteger DEFAULT_CHAIN_ID = BigInteger.ONE; + /** Default constructor. */ + protected BaseBftProtocolScheduleBuilder() {} + /** * Create protocol schedule. * @@ -50,6 +57,11 @@ public abstract class BaseBftProtocolScheduleBuilder { * @param isRevertReasonEnabled the is revert reason enabled * @param bftExtraDataCodec the bft extra data codec * @param evmConfiguration the evm configuration + * @param miningParameters the mining parameters + * @param badBlockManager the cache to use to keep invalid blocks + * @param isParallelTxProcessingEnabled indicates whether parallel transaction is enabled. + * @param metricsSystem metricsSystem A metricSystem instance to be able to expose metrics in the + * underlying calls * @return the protocol schedule */ public BftProtocolSchedule createProtocolSchedule( @@ -58,7 +70,11 @@ public BftProtocolSchedule createProtocolSchedule( final PrivacyParameters privacyParameters, final boolean isRevertReasonEnabled, final BftExtraDataCodec bftExtraDataCodec, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final BadBlockManager badBlockManager, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { final Map> specMap = new HashMap<>(); forksSchedule @@ -78,7 +94,11 @@ public BftProtocolSchedule createProtocolSchedule( specAdapters, privacyParameters, isRevertReasonEnabled, - evmConfiguration) + evmConfiguration, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem) .createProtocolSchedule(); return new BftProtocolSchedule((DefaultProtocolSchedule) protocolSchedule); } @@ -116,6 +136,7 @@ private ProtocolSpecBuilder applyBftChanges( .skipZeroBlockRewards(true) .blockHeaderFunctions(BftBlockHeaderFunctions.forOnchainBlock(bftExtraDataCodec)) .blockReward(Wei.of(configOptions.getBlockRewardWei())) + .withdrawalsValidator(new WithdrawalsValidator.NotApplicableWithdrawals()) .miningBeneficiaryCalculator( header -> configOptions.getMiningBeneficiary().orElseGet(header::getCoinbase)); } diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftEventQueue.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftEventQueue.java index a4f2faff788..4221fd1fac7 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftEventQueue.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftEventQueue.java @@ -15,10 +15,12 @@ package org.hyperledger.besu.consensus.common.bft; import org.hyperledger.besu.consensus.common.bft.events.BftEvent; +import org.hyperledger.besu.consensus.common.bft.events.BftEvents; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.Nullable; import org.slf4j.Logger; @@ -30,6 +32,7 @@ public class BftEventQueue { private static final Logger LOG = LoggerFactory.getLogger(BftEventQueue.class); private final int messageQueueLimit; + private final AtomicBoolean started = new AtomicBoolean(false); /** * Instantiates a new Bft event queue. @@ -40,16 +43,30 @@ public BftEventQueue(final int messageQueueLimit) { this.messageQueueLimit = messageQueueLimit; } + /** Start the event queue. Until it has been started no events will be queued for processing. */ + public void start() { + started.set(true); + } + + private boolean isStarted() { + return started.get(); + } + /** - * Put an Bft event onto the queue + * Put an Bft event onto the queue. Note: the event queue must be started before an event will be + * queued for processing. Events received before the queue is started will be discarded. * * @param event Provided bft event */ public void add(final BftEvent event) { - if (queue.size() > messageQueueLimit) { - LOG.warn("Queue size exceeded trying to add new bft event {}", event); - } else { - queue.add(event); + + // Don't queue events other than block timer expiry, until we know we can process them + if (isStarted() || event.getType() == BftEvents.Type.BLOCK_TIMER_EXPIRY) { + if (queue.size() > messageQueueLimit) { + LOG.warn("Queue size exceeded trying to add new bft event {}", event); + } else { + queue.add(event); + } } } diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftExecutors.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftExecutors.java index fc06275f61e..30038add3d4 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftExecutors.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftExecutors.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.consensus.common.bft; import org.hyperledger.besu.ethereum.eth.manager.MonitoredExecutors; diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftExtraDataCodec.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftExtraDataCodec.java index 87f107e994c..9984725e8bd 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftExtraDataCodec.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftExtraDataCodec.java @@ -38,6 +38,9 @@ protected enum EncodingType { /** The constant EXTRA_VANITY_LENGTH. */ public static int EXTRA_VANITY_LENGTH = 32; + /** Default constructor. */ + public BftExtraDataCodec() {} + /** * Encode. * diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftForksScheduleFactory.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftForksScheduleFactory.java deleted file mode 100644 index ca14c94ac34..00000000000 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftForksScheduleFactory.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright Hyperledger Besu contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.consensus.common.bft; - -import static com.google.common.base.Preconditions.checkArgument; - -import org.hyperledger.besu.config.BftConfigOptions; -import org.hyperledger.besu.config.BftFork; -import org.hyperledger.besu.consensus.common.ForkSpec; -import org.hyperledger.besu.consensus.common.ForksSchedule; - -import java.util.Comparator; -import java.util.List; -import java.util.NavigableSet; -import java.util.TreeSet; - -/** The Bft forks schedule factory. */ -public class BftForksScheduleFactory { - - /** - * The interface Bft spec creator. - * - * @param the type parameter - * @param the type parameter - */ - public interface BftSpecCreator { - /** - * Create type of BftConfigOptions. - * - * @param lastSpec the last spec - * @param fork the fork - * @return the type of BftConfigOptions - */ - T create(ForkSpec lastSpec, U fork); - } - - /** - * Create forks schedule. - * - * @param the type parameter BftConfigOptions - * @param the type parameter BftFork - * @param initial the initial - * @param forks the forks - * @param specCreator the spec creator - * @return the forks schedule - */ - public static ForksSchedule create( - final T initial, final List forks, final BftSpecCreator specCreator) { - checkArgument( - forks.stream().allMatch(f -> f.getForkBlock() > 0), - "Transition cannot be created for genesis block"); - checkArgument( - forks.stream().map(BftFork::getForkBlock).distinct().count() == forks.size(), - "Duplicate transitions cannot be created for the same block"); - - final NavigableSet> specs = new TreeSet<>(Comparator.comparing(ForkSpec::getBlock)); - final ForkSpec initialForkSpec = new ForkSpec<>(0, initial); - specs.add(initialForkSpec); - - forks.stream() - .sorted(Comparator.comparing(BftFork::getForkBlock)) - .forEachOrdered( - f -> { - final T spec = specCreator.create(specs.last(), f); - specs.add(new ForkSpec<>(f.getForkBlock(), spec)); - }); - - return new ForksSchedule<>(specs); - } -} diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftHelpers.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftHelpers.java index ea14a5dce50..318701da7cd 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftHelpers.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftHelpers.java @@ -30,6 +30,9 @@ public class BftHelpers { public static final Hash EXPECTED_MIX_HASH = Hash.fromHexString("0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365"); + /** Default constructor. */ + private BftHelpers() {} + /** * Calculate required validator quorum int. * diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftProcessor.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftProcessor.java index efd75532f94..93df77ea778 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftProcessor.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftProcessor.java @@ -61,6 +61,9 @@ public void awaitStop() throws InterruptedException { @Override public void run() { try { + // Start the event queue. Until it is started it won't accept new events from peers + incomingQueue.start(); + while (!shutdown) { nextEvent().ifPresent(eventMultiplexer::handleBftEvent); } diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftProtocolSchedule.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftProtocolSchedule.java index eca88b95841..ab6dc6eb66a 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftProtocolSchedule.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/BftProtocolSchedule.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.consensus.common.bft; import static com.google.common.base.Preconditions.checkArgument; @@ -39,12 +38,13 @@ public BftProtocolSchedule(final DefaultProtocolSchedule protocolSchedule) { } /** - * Look up ProtocolSpec by block number + * Look up ProtocolSpec by block number or timestamp * * @param number block number - * @return the protocol spec for that block number + * @param timestamp block timestamp + * @return the protocol spec for that block number or timestamp */ - public ProtocolSpec getByBlockNumber(final long number) { + public ProtocolSpec getByBlockNumberOrTimestamp(final long number, final long timestamp) { checkArgument(number >= 0, "number must be non-negative"); checkArgument( !protocolSpecs.isEmpty(), "At least 1 milestone must be provided to the protocol schedule"); @@ -53,12 +53,19 @@ public ProtocolSpec getByBlockNumber(final long number) { "There must be a milestone starting from block 0"); // protocolSpecs is sorted in descending block order, so the first one we find that's lower than // the requested level will be the most appropriate spec + ProtocolSpec theSpec = null; for (final ScheduledProtocolSpec s : protocolSpecs) { - if (number >= s.fork().milestone()) { - return s.spec(); + if ((s instanceof ScheduledProtocolSpec.BlockNumberProtocolSpec) + && (number >= s.fork().milestone())) { + theSpec = s.spec(); + break; + } else if ((s instanceof ScheduledProtocolSpec.TimestampProtocolSpec) + && (timestamp >= s.fork().milestone())) { + theSpec = s.spec(); + break; } } - return null; + return theSpec; } /** diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/MutableBftConfigOptions.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/MutableBftConfigOptions.java index ee8f546bd77..fd406ea5e12 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/MutableBftConfigOptions.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/MutableBftConfigOptions.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/RoundTimer.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/RoundTimer.java index a500a1ad7ee..a01dc652cff 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/RoundTimer.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/RoundTimer.java @@ -20,8 +20,14 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** Class for starting and keeping organised round timers */ public class RoundTimer { + + private static final Logger LOG = LoggerFactory.getLogger(RoundTimer.class); + private final BftExecutors bftExecutors; private Optional> currentTimerTask; private final BftEventQueue queue; @@ -71,6 +77,16 @@ public synchronized void startTimer(final ConsensusRoundIdentifier round) { final ScheduledFuture newTimerTask = bftExecutors.scheduleTask(newTimerRunnable, expiryTime, TimeUnit.MILLISECONDS); + + // Once we are up to round 2 start logging round expiries + if (round.getRoundNumber() >= 2) { + LOG.info( + "BFT round {} expired. Moved to round {} which will expire in {} seconds", + round.getRoundNumber() - 1, + round.getRoundNumber(), + (expiryTime / 1000)); + } + currentTimerTask = Optional.of(newTimerTask); } } diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/Vote.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/Vote.java index 7b9bbc2f1ec..ebf795defd7 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/Vote.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/Vote.java @@ -29,6 +29,7 @@ public class Vote { /** The constant ADD_BYTE_VALUE. */ public static final byte ADD_BYTE_VALUE = (byte) 0xFF; + /** The constant DROP_BYTE_VALUE. */ public static final byte DROP_BYTE_VALUE = (byte) 0x0L; diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreator.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreator.java index e6a981cec78..6bc0254b691 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreator.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreator.java @@ -19,6 +19,7 @@ import org.hyperledger.besu.consensus.common.bft.BftBlockHeaderFunctions; import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec; import org.hyperledger.besu.consensus.common.bft.BftHelpers; +import org.hyperledger.besu.consensus.common.bft.BftProtocolSchedule; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.blockcreation.AbstractBlockCreator; @@ -29,7 +30,10 @@ import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.WithdrawalsValidator; +import java.util.Collections; import java.util.Optional; /** The Bft block creator. */ @@ -49,7 +53,6 @@ public class BftBlockCreator extends AbstractBlockCreator { * @param transactionPool the pending transactions * @param protocolContext the protocol context * @param protocolSchedule the protocol schedule - * @param parentHeader the parent header * @param bftExtraDataCodec the bft extra data codec * @param ethScheduler the scheduler for asynchronous block creation tasks */ @@ -61,7 +64,6 @@ public BftBlockCreator( final TransactionPool transactionPool, final ProtocolContext protocolContext, final ProtocolSchedule protocolSchedule, - final BlockHeader parentHeader, final BftExtraDataCodec bftExtraDataCodec, final EthScheduler ethScheduler) { super( @@ -71,18 +73,40 @@ public BftBlockCreator( transactionPool, protocolContext, protocolSchedule, - parentHeader, - Optional.empty(), ethScheduler); this.bftExtraDataCodec = bftExtraDataCodec; } + @Override + public BlockCreationResult createBlock(final long timestamp, final BlockHeader parentHeader) { + ProtocolSpec protocolSpec = + ((BftProtocolSchedule) protocolSchedule) + .getByBlockNumberOrTimestamp(parentHeader.getNumber() + 1, timestamp); + + if (protocolSpec.getWithdrawalsValidator() instanceof WithdrawalsValidator.AllowedWithdrawals) { + return createEmptyWithdrawalsBlock(timestamp, parentHeader); + } else { + return createBlock(Optional.empty(), Optional.empty(), timestamp, parentHeader); + } + } + private static MiningBeneficiaryCalculator miningBeneficiaryCalculator( final Address localAddress, final ForksSchedule forksSchedule) { return blockNum -> forksSchedule.getFork(blockNum).getValue().getMiningBeneficiary().orElse(localAddress); } + @Override + public BlockCreationResult createEmptyWithdrawalsBlock( + final long timestamp, final BlockHeader parentHeader) { + return createBlock( + Optional.empty(), + Optional.empty(), + Optional.of(Collections.emptyList()), + timestamp, + parentHeader); + } + @Override protected BlockHeader createFinalBlockHeader(final SealableBlockHeader sealableBlockHeader) { final BlockHeaderBuilder builder = diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreatorFactory.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreatorFactory.java index 437c5dc65ef..7c679763b60 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreatorFactory.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftBlockCreatorFactory.java @@ -52,16 +52,21 @@ public class BftBlockCreatorFactory { /** The Forks schedule. */ protected final ForksSchedule forksSchedule; + /** The Mining parameters */ protected final MiningParameters miningParameters; private final TransactionPool transactionPool; + /** The Protocol context. */ protected final ProtocolContext protocolContext; + /** The Protocol schedule. */ protected final ProtocolSchedule protocolSchedule; + /** The Bft extra data codec. */ protected final BftExtraDataCodec bftExtraDataCodec; + /** The scheduler for asynchronous block creation tasks */ protected final EthScheduler ethScheduler; @@ -101,11 +106,10 @@ public BftBlockCreatorFactory( /** * Create block creator. * - * @param parentHeader the parent header * @param round the round * @return the block creator */ - public BlockCreator create(final BlockHeader parentHeader, final int round) { + public BlockCreator create(final int round) { return new BftBlockCreator( miningParameters, forksSchedule, @@ -114,21 +118,10 @@ public BlockCreator create(final BlockHeader parentHeader, final int round) { transactionPool, protocolContext, protocolSchedule, - parentHeader, bftExtraDataCodec, ethScheduler); } - /** - * Sets extra data. - * - * @param extraData the extra data - */ - public void setExtraData(final Bytes extraData) { - - miningParameters.setExtraData(extraData.copy()); - } - /** * Sets min transaction gas price. * @@ -147,6 +140,15 @@ public Wei getMinTransactionGasPrice() { return miningParameters.getMinTransactionGasPrice(); } + /** + * Gets min priority fee per gas + * + * @return min priority fee per gas + */ + public Wei getMinPriorityFeePerGas() { + return miningParameters.getMinPriorityFeePerGas(); + } + /** * Create extra data bytes. * diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftMiningCoordinator.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftMiningCoordinator.java index 5107a00fe15..83cd61beb31 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftMiningCoordinator.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftMiningCoordinator.java @@ -33,7 +33,6 @@ import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; -import org.apache.tuweni.bytes.Bytes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,6 +55,7 @@ private enum State { private final BftEventHandler eventHandler; private final BftProcessor bftProcessor; private final BftBlockCreatorFactory blockCreatorFactory; + /** The Blockchain. */ protected final Blockchain blockchain; @@ -149,8 +149,8 @@ public Wei getMinTransactionGasPrice() { } @Override - public void setExtraData(final Bytes extraData) { - blockCreatorFactory.setExtraData(extraData); + public Wei getMinPriorityFeePerGas() { + return blockCreatorFactory.getMinPriorityFeePerGas(); } @Override diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/events/BftEvents.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/events/BftEvents.java index 40672dd8ae5..030cb3fb5ee 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/events/BftEvents.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/events/BftEvents.java @@ -18,6 +18,9 @@ /** Static helper functions for producing and working with BftEvent objects */ public class BftEvents { + /** Default constructor. */ + private BftEvents() {} + /** * Instantiate BftEvent From message. * diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/events/BlockTimerExpiry.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/events/BlockTimerExpiry.java index 3027e0dca5d..d57e981efc4 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/events/BlockTimerExpiry.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/events/BlockTimerExpiry.java @@ -41,9 +41,9 @@ public BftEvents.Type getType() { /** * Gets round identifier. * - * @return the round indentifier + * @return the round Identifier */ - public ConsensusRoundIdentifier getRoundIndentifier() { + public ConsensusRoundIdentifier getRoundIdentifier() { return roundIdentifier; } diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftCoinbaseValidationRule.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftCoinbaseValidationRule.java index 36a222275ca..1150295d72b 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftCoinbaseValidationRule.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftCoinbaseValidationRule.java @@ -33,6 +33,9 @@ public class BftCoinbaseValidationRule implements AttachedBlockHeaderValidationR private static final Logger LOGGER = LoggerFactory.getLogger(BftCoinbaseValidationRule.class); + /** Default constructor. */ + public BftCoinbaseValidationRule() {} + @Override public boolean validate( final BlockHeader header, final BlockHeader parent, final ProtocolContext context) { diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftCommitSealsValidationRule.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftCommitSealsValidationRule.java index 78e4c8fe98e..07791f1d767 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftCommitSealsValidationRule.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftCommitSealsValidationRule.java @@ -40,6 +40,9 @@ public class BftCommitSealsValidationRule implements AttachedBlockHeaderValidati private static final Logger LOGGER = LoggerFactory.getLogger(BftCommitSealsValidationRule.class); + /** Default constructor. */ + public BftCommitSealsValidationRule() {} + @Override public boolean validate( final BlockHeader header, final BlockHeader parent, final ProtocolContext protocolContext) { diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftValidatorsValidationRule.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftValidatorsValidationRule.java index 1e0e57d5550..7107fff5f00 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftValidatorsValidationRule.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftValidatorsValidationRule.java @@ -38,6 +38,9 @@ public class BftValidatorsValidationRule implements AttachedBlockHeaderValidatio private static final Logger LOGGER = LoggerFactory.getLogger(BftValidatorsValidationRule.class); + /** Default constructor. */ + public BftValidatorsValidationRule() {} + @Override public boolean validate( final BlockHeader header, final BlockHeader parent, final ProtocolContext context) { diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftVanityDataValidationRule.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftVanityDataValidationRule.java index 6bbca4c6e3c..d5d30ffd55e 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftVanityDataValidationRule.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftVanityDataValidationRule.java @@ -29,6 +29,9 @@ public class BftVanityDataValidationRule implements AttachedBlockHeaderValidatio private static final Logger LOG = LoggerFactory.getLogger(BftVanityDataValidationRule.class); + /** Default constructor. */ + public BftVanityDataValidationRule() {} + @Override public boolean validate( final BlockHeader header, final BlockHeader parent, final ProtocolContext protocolContext) { diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/protocol/BftProtocolManager.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/protocol/BftProtocolManager.java index 0ef72f95edf..0c0dd5f67df 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/protocol/BftProtocolManager.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/protocol/BftProtocolManager.java @@ -20,7 +20,6 @@ import org.hyperledger.besu.consensus.common.bft.network.PeerConnectionTracker; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.p2p.network.ProtocolManager; -import org.hyperledger.besu.ethereum.p2p.peers.Peer; import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Capability; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Message; @@ -108,11 +107,6 @@ public void handleNewConnection(final PeerConnection peerConnection) { peers.add(peerConnection); } - @Override - public boolean shouldConnect(final Peer peer, final boolean incoming) { - return false; // for now the EthProtocolManager takes care of this - } - @Override public void handleDisconnect( final PeerConnection peerConnection, diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/statemachine/BaseBftController.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/statemachine/BaseBftController.java index e35af1af646..58d579f28e1 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/statemachine/BaseBftController.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/statemachine/BaseBftController.java @@ -44,7 +44,7 @@ public abstract class BaseBftController implements BftEventHandler { private final FutureMessageBuffer futureMessageBuffer; private final Gossiper gossiper; private final MessageTracker duplicateMessageTracker; - private final SynchronizerUpdater sychronizerUpdater; + private final SynchronizerUpdater synchronizerUpdater; private final AtomicBoolean started = new AtomicBoolean(false); @@ -56,7 +56,7 @@ public abstract class BaseBftController implements BftEventHandler { * @param gossiper the gossiper * @param duplicateMessageTracker the duplicate message tracker * @param futureMessageBuffer the future message buffer - * @param sychronizerUpdater the synchronizer updater + * @param synchronizerUpdater the synchronizer updater */ protected BaseBftController( final Blockchain blockchain, @@ -64,13 +64,13 @@ protected BaseBftController( final Gossiper gossiper, final MessageTracker duplicateMessageTracker, final FutureMessageBuffer futureMessageBuffer, - final SynchronizerUpdater sychronizerUpdater) { + final SynchronizerUpdater synchronizerUpdater) { this.blockchain = blockchain; this.bftFinalState = bftFinalState; this.futureMessageBuffer = futureMessageBuffer; this.gossiper = gossiper; this.duplicateMessageTracker = duplicateMessageTracker; - this.sychronizerUpdater = sychronizerUpdater; + this.synchronizerUpdater = synchronizerUpdater; } @Override @@ -162,14 +162,14 @@ public void handleNewBlockEvent(final NewChainHead newChainHead) { @Override public void handleBlockTimerExpiry(final BlockTimerExpiry blockTimerExpiry) { - final ConsensusRoundIdentifier roundIndentifier = blockTimerExpiry.getRoundIndentifier(); - if (isMsgForCurrentHeight(roundIndentifier)) { - getCurrentHeightManager().handleBlockTimerExpiry(roundIndentifier); + final ConsensusRoundIdentifier roundIdentifier = blockTimerExpiry.getRoundIdentifier(); + if (isMsgForCurrentHeight(roundIdentifier)) { + getCurrentHeightManager().handleBlockTimerExpiry(roundIdentifier); } else { LOG.trace( "Block timer event discarded as it is not for current block height chainHeight={} eventHeight={}", getCurrentHeightManager().getChainHeight(), - roundIndentifier.getSequenceNumber()); + roundIdentifier.getSequenceNumber()); } } @@ -221,7 +221,7 @@ private boolean processMessage(final BftMessage msg, final Message rawMsg) { futureMessageBuffer.addMessage(msgRoundIdentifier.getSequenceNumber(), rawMsg); // Notify the synchronizer the transmitting peer must have the parent block to the received // message's target height. - sychronizerUpdater.updatePeerChainState( + synchronizerUpdater.updatePeerChainState( msgRoundIdentifier.getSequenceNumber() - 1L, rawMsg.getConnection()); } else { LOG.trace( diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/statemachine/BftFinalState.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/statemachine/BftFinalState.java index 13f6959779b..5d077c8b8e0 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/statemachine/BftFinalState.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/statemachine/BftFinalState.java @@ -30,6 +30,7 @@ /** This is the full data set, or context, required for many of the aspects of BFT workflows. */ public class BftFinalState { + private final ValidatorProvider validatorProvider; private final NodeKey nodeKey; private final Address localAddress; @@ -126,7 +127,8 @@ public boolean isLocalNodeProposerForRound(final ConsensusRoundIdentifier roundI * @return the boolean */ public boolean isLocalNodeValidator() { - return getValidators().contains(localAddress); + final boolean isValidator = getValidators().contains(localAddress); + return isValidator; } /** diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/validation/ValidationHelpers.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/validation/ValidationHelpers.java index 22a3bfff270..37dbe010ee4 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/validation/ValidationHelpers.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/bft/validation/ValidationHelpers.java @@ -22,6 +22,8 @@ /** The type Validation helpers. */ public class ValidationHelpers { + /** Default constructor. */ + private ValidationHelpers() {} /** * Has duplicate authors. diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/jsonrpc/AbstractGetSignerMetricsMethod.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/jsonrpc/AbstractGetSignerMetricsMethod.java index 3566684ab14..e3785976af9 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/jsonrpc/AbstractGetSignerMetricsMethod.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/jsonrpc/AbstractGetSignerMetricsMethod.java @@ -18,7 +18,9 @@ import org.hyperledger.besu.consensus.common.validator.ValidatorProvider; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -66,17 +68,27 @@ protected AbstractGetSignerMetricsMethod( */ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final Optional startBlockParameter = - requestContext.getOptionalParameter(0, BlockParameter.class); - final Optional endBlockParameter = - requestContext.getOptionalParameter(1, BlockParameter.class); + final Optional startBlockParameter; + try { + startBlockParameter = requestContext.getOptionalParameter(0, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid start block parameter (index 0)", RpcErrorType.INVALID_BLOCK_NUMBER_PARAMS, e); + } + final Optional endBlockParameter; + try { + endBlockParameter = requestContext.getOptionalParameter(1, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid end block parameter (index 1)", RpcErrorType.INVALID_BLOCK_NUMBER_PARAMS, e); + } final long fromBlockNumber = getFromBlockNumber(startBlockParameter); final long toBlockNumber = getEndBlockNumber(endBlockParameter); if (!isValidParameters(fromBlockNumber, toBlockNumber)) { return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVALID_BLOCK_NUMBER_PARAMS); } final Map proposersMap = new HashMap<>(); diff --git a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/validator/ValidatorProvider.java b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/validator/ValidatorProvider.java index f11902baef8..05ddcd00ab4 100644 --- a/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/validator/ValidatorProvider.java +++ b/consensus/common/src/main/java/org/hyperledger/besu/consensus/common/validator/ValidatorProvider.java @@ -14,8 +14,10 @@ */ package org.hyperledger.besu.consensus.common.validator; +import org.hyperledger.besu.cryptoservices.NodeKey; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.Util; import java.util.Collection; import java.util.Optional; @@ -67,4 +69,14 @@ public interface ValidatorProvider { default Optional getVoteProviderAfterBlock(final BlockHeader header) { return getVoteProviderAtHead(); } + + /** + * Determines if this node is a validator + * + * @param nodekey our node key + * @return true if this node is a validator + */ + default boolean nodeIsValidator(final NodeKey nodekey) { + return this.getValidatorsAtHead().contains(Util.publicKeyToAddress(nodekey.getPublicKey())); + } } diff --git a/consensus/common/src/test-support/java/org/hyperledger/besu/consensus/common/bft/inttest/NetworkLayout.java b/consensus/common/src/test-support/java/org/hyperledger/besu/consensus/common/bft/inttest/NetworkLayout.java index 4fbeefcc86c..bb84888c2e3 100644 --- a/consensus/common/src/test-support/java/org/hyperledger/besu/consensus/common/bft/inttest/NetworkLayout.java +++ b/consensus/common/src/test-support/java/org/hyperledger/besu/consensus/common/bft/inttest/NetworkLayout.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.NavigableMap; import java.util.Set; import java.util.TreeMap; @@ -29,11 +30,11 @@ public class NetworkLayout { private final NodeParams localNode; - private final TreeMap addressKeyMap; + private final NavigableMap addressKeyMap; private final List remotePeers; public NetworkLayout( - final NodeParams localNode, final TreeMap addressKeyMap) { + final NodeParams localNode, final NavigableMap addressKeyMap) { this.localNode = localNode; this.addressKeyMap = addressKeyMap; this.remotePeers = new ArrayList<>(addressKeyMap.values()); @@ -42,14 +43,14 @@ public NetworkLayout( public static NetworkLayout createNetworkLayout( final int validatorCount, final int firstLocalNodeBlockNum) { - final TreeMap addressKeyMap = createValidators(validatorCount); + final NavigableMap addressKeyMap = createValidators(validatorCount); final NodeParams localNode = Iterables.get(addressKeyMap.values(), firstLocalNodeBlockNum); return new NetworkLayout(localNode, addressKeyMap); } - private static TreeMap createValidators(final int validatorCount) { + private static NavigableMap createValidators(final int validatorCount) { // Map is required to be sorted by address final TreeMap addressKeyMap = new TreeMap<>(); diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/CombinedProtocolScheduleFactoryTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/CombinedProtocolScheduleFactoryTest.java index 29b197b9ece..02689d52af1 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/CombinedProtocolScheduleFactoryTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/CombinedProtocolScheduleFactoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -19,13 +19,16 @@ import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.config.StubGenesisConfigOptions; import org.hyperledger.besu.consensus.common.bft.BftProtocolSchedule; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.MilestoneStreamingProtocolSchedule; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.mainnet.DefaultProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import java.math.BigInteger; import java.util.List; @@ -35,11 +38,11 @@ import java.util.function.Function; import java.util.stream.Collectors; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class CombinedProtocolScheduleFactoryTest { private final CombinedProtocolScheduleFactory combinedProtocolScheduleFactory = @@ -60,18 +63,20 @@ public void createsCombinedProtocolScheduleWithMilestonesFromSingleProtocolSched final BftProtocolSchedule combinedProtocolSchedule = combinedProtocolScheduleFactory.create(consensusSchedule, Optional.of(BigInteger.TEN)); - assertThat(combinedProtocolSchedule.getByBlockNumber(0L).getName()).isEqualTo("Frontier"); - assertThat(combinedProtocolSchedule.getByBlockNumber(0L)) - .isSameAs(protocolSchedule.getByBlockNumber(0L)); + assertThat(combinedProtocolSchedule.getByBlockNumberOrTimestamp(0L, 0L).getName()) + .isEqualTo("Frontier"); + assertThat(combinedProtocolSchedule.getByBlockNumberOrTimestamp(0L, 0L)) + .isSameAs(protocolSchedule.getByBlockNumberOrTimestamp(0L, 0L)); - assertThat(combinedProtocolSchedule.getByBlockNumber(5L).getName()).isEqualTo("Homestead"); - assertThat(combinedProtocolSchedule.getByBlockNumber(5L)) - .isSameAs(protocolSchedule.getByBlockNumber(5L)); + assertThat(combinedProtocolSchedule.getByBlockNumberOrTimestamp(5L, 0L).getName()) + .isEqualTo("Homestead"); + assertThat(combinedProtocolSchedule.getByBlockNumberOrTimestamp(5L, 0L)) + .isSameAs(protocolSchedule.getByBlockNumberOrTimestamp(5L, 0L)); - assertThat(combinedProtocolSchedule.getByBlockNumber(10L).getName()) + assertThat(combinedProtocolSchedule.getByBlockNumberOrTimestamp(10L, 0L).getName()) .isEqualTo("Constantinople"); - assertThat(combinedProtocolSchedule.getByBlockNumber(10L)) - .isSameAs(protocolSchedule.getByBlockNumber(10L)); + assertThat(combinedProtocolSchedule.getByBlockNumberOrTimestamp(10L, 0L)) + .isSameAs(protocolSchedule.getByBlockNumberOrTimestamp(10L, 0L)); assertThat( new MilestoneStreamingProtocolSchedule(combinedProtocolSchedule) @@ -88,63 +93,78 @@ public void createsCombinedProtocolScheduleWithMilestonesFromMultipleSchedules() genesisConfigOptions.byzantiumBlock(105L); genesisConfigOptions.berlinBlock(110L); genesisConfigOptions.londonBlock(220L); + genesisConfigOptions.shanghaiTime(1000000050L); genesisConfigOptions.chainId(BigInteger.TEN); final BftProtocolSchedule protocolSchedule1 = createProtocolSchedule(genesisConfigOptions); final BftProtocolSchedule protocolSchedule2 = createProtocolSchedule(genesisConfigOptions); final BftProtocolSchedule protocolSchedule3 = createProtocolSchedule(genesisConfigOptions); + final BftProtocolSchedule protocolSchedule4 = createProtocolSchedule(genesisConfigOptions); final NavigableSet> consensusSchedule = new TreeSet<>(ForkSpec.COMPARATOR); consensusSchedule.add(new ForkSpec<>(0, protocolSchedule1)); consensusSchedule.add(new ForkSpec<>(100L, protocolSchedule2)); consensusSchedule.add(new ForkSpec<>(200L, protocolSchedule3)); + consensusSchedule.add(new ForkSpec<>(1000000000L, protocolSchedule4)); final BftProtocolSchedule combinedProtocolSchedule = combinedProtocolScheduleFactory.create(consensusSchedule, Optional.of(BigInteger.TEN)); // consensus schedule 1 - assertThat(combinedProtocolSchedule.getByBlockNumber(0L).getName()).isEqualTo("Frontier"); - assertThat(combinedProtocolSchedule.getByBlockNumber(0L)) - .isSameAs(protocolSchedule1.getByBlockNumber(0L)); - - assertThat(combinedProtocolSchedule.getByBlockNumber(5L).getName()).isEqualTo("Homestead"); - assertThat(combinedProtocolSchedule.getByBlockNumber(5L)) - .isSameAs(protocolSchedule1.getByBlockNumber(5L)); - assertThat(combinedProtocolSchedule.getByBlockNumber(10L).getName()) + assertThat(combinedProtocolSchedule.getByBlockNumberOrTimestamp(0L, 0L).getName()) + .isEqualTo("Frontier"); + assertThat(combinedProtocolSchedule.getByBlockNumberOrTimestamp(0L, 0L)) + .isSameAs(protocolSchedule1.getByBlockNumberOrTimestamp(0L, 0L)); + + assertThat(combinedProtocolSchedule.getByBlockNumberOrTimestamp(5L, 0L).getName()) + .isEqualTo("Homestead"); + assertThat(combinedProtocolSchedule.getByBlockNumberOrTimestamp(5L, 0L)) + .isSameAs(protocolSchedule1.getByBlockNumberOrTimestamp(5L, 0L)); + assertThat(combinedProtocolSchedule.getByBlockNumberOrTimestamp(10L, 0L).getName()) .isEqualTo("Constantinople"); - assertThat(combinedProtocolSchedule.getByBlockNumber(10L)) - .isSameAs(protocolSchedule1.getByBlockNumber(10L)); + assertThat(combinedProtocolSchedule.getByBlockNumberOrTimestamp(10L, 0L)) + .isSameAs(protocolSchedule1.getByBlockNumberOrTimestamp(10L, 0L)); // consensus schedule 2 migration block - assertThat(combinedProtocolSchedule.getByBlockNumber(100L).getName()) + assertThat(combinedProtocolSchedule.getByBlockNumberOrTimestamp(100L, 0L).getName()) .isEqualTo("Constantinople"); - assertThat(combinedProtocolSchedule.getByBlockNumber(100L)) - .isSameAs(protocolSchedule2.getByBlockNumber(10L)); + assertThat(combinedProtocolSchedule.getByBlockNumberOrTimestamp(100L, 0L)) + .isSameAs(protocolSchedule2.getByBlockNumberOrTimestamp(10L, 0L)); // consensus schedule 2 - assertThat(combinedProtocolSchedule.getByBlockNumber(105L).getName()).isEqualTo("Byzantium"); - assertThat(combinedProtocolSchedule.getByBlockNumber(105L)) - .isSameAs(protocolSchedule2.getByBlockNumber(105L)); - assertThat(combinedProtocolSchedule.getByBlockNumber(110L).getName()).isEqualTo("Berlin"); - assertThat(combinedProtocolSchedule.getByBlockNumber(110L)) - .isSameAs(protocolSchedule2.getByBlockNumber(110L)); + assertThat(combinedProtocolSchedule.getByBlockNumberOrTimestamp(105L, 0L).getName()) + .isEqualTo("Byzantium"); + assertThat(combinedProtocolSchedule.getByBlockNumberOrTimestamp(105L, 0L)) + .isSameAs(protocolSchedule2.getByBlockNumberOrTimestamp(105L, 0L)); + assertThat(combinedProtocolSchedule.getByBlockNumberOrTimestamp(110L, 0L).getName()) + .isEqualTo("Berlin"); + assertThat(combinedProtocolSchedule.getByBlockNumberOrTimestamp(110L, 0L)) + .isSameAs(protocolSchedule2.getByBlockNumberOrTimestamp(110L, 0L)); // consensus schedule 3 migration block - assertThat(combinedProtocolSchedule.getByBlockNumber(200L).getName()).isEqualTo("Berlin"); - assertThat(combinedProtocolSchedule.getByBlockNumber(200L)) - .isSameAs(protocolSchedule3.getByBlockNumber(110L)); + assertThat(combinedProtocolSchedule.getByBlockNumberOrTimestamp(200L, 0L).getName()) + .isEqualTo("Berlin"); + assertThat(combinedProtocolSchedule.getByBlockNumberOrTimestamp(200L, 0L)) + .isSameAs(protocolSchedule3.getByBlockNumberOrTimestamp(110L, 0L)); // consensus schedule 3 - assertThat(combinedProtocolSchedule.getByBlockNumber(220L).getName()).isEqualTo("London"); - assertThat(combinedProtocolSchedule.getByBlockNumber(220L)) - .isSameAs(protocolSchedule3.getByBlockNumber(220L)); + assertThat(combinedProtocolSchedule.getByBlockNumberOrTimestamp(220L, 0L).getName()) + .isEqualTo("London"); + assertThat(combinedProtocolSchedule.getByBlockNumberOrTimestamp(220L, 0L)) + .isSameAs(protocolSchedule3.getByBlockNumberOrTimestamp(220L, 0L)); + + // consensus schedule 4 + assertThat(combinedProtocolSchedule.getByBlockNumberOrTimestamp(0L, 1000000050L).getName()) + .isEqualTo("Shanghai"); + assertThat(combinedProtocolSchedule.getByBlockNumberOrTimestamp(220L, 1000000050L)) + .isSameAs(protocolSchedule4.getByBlockNumberOrTimestamp(220L, 1000000050L)); assertThat( new MilestoneStreamingProtocolSchedule(combinedProtocolSchedule) .streamMilestoneBlocks() .collect(Collectors.toList())) - .isEqualTo(List.of(0L, 5L, 10L, 100L, 105L, 110L, 200L, 220L)); + .isEqualTo(List.of(0L, 5L, 10L, 100L, 105L, 110L, 200L, 220L, 1000000000L, 1000000050L)); } private BftProtocolSchedule createProtocolSchedule( @@ -156,7 +176,11 @@ private BftProtocolSchedule createProtocolSchedule( ProtocolSpecAdapters.create(0, Function.identity()), new PrivacyParameters(), false, - EvmConfiguration.DEFAULT); + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); return new BftProtocolSchedule( (DefaultProtocolSchedule) protocolScheduleBuilder.createProtocolSchedule()); diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/ForksScheduleFactoryTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/ForksScheduleFactoryTest.java new file mode 100644 index 00000000000..0cc9c129451 --- /dev/null +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/ForksScheduleFactoryTest.java @@ -0,0 +1,99 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.consensus.common; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.config.BftConfigOptions; +import org.hyperledger.besu.config.BftFork; +import org.hyperledger.besu.config.JsonBftConfigOptions; +import org.hyperledger.besu.config.JsonUtil; +import org.hyperledger.besu.consensus.common.ForksScheduleFactory.SpecCreator; +import org.hyperledger.besu.consensus.common.bft.MutableBftConfigOptions; + +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +public class ForksScheduleFactoryTest { + + @Test + @SuppressWarnings("unchecked") + public void throwsErrorIfHasForkForGenesisBlock() { + final BftConfigOptions genesisConfigOptions = JsonBftConfigOptions.DEFAULT; + final BftFork fork = createFork(0, 10); + final SpecCreator specCreator = Mockito.mock(SpecCreator.class); + + assertThatThrownBy( + () -> ForksScheduleFactory.create(genesisConfigOptions, List.of(fork), specCreator)) + .hasMessage("Transition cannot be created for genesis block"); + } + + @Test + @SuppressWarnings("unchecked") + public void throwsErrorIfHasForksWithDuplicateBlock() { + final BftConfigOptions genesisConfigOptions = JsonBftConfigOptions.DEFAULT; + final BftFork fork1 = createFork(1, 10); + final BftFork fork2 = createFork(1, 20); + final BftFork fork3 = createFork(2, 30); + final SpecCreator specCreator = Mockito.mock(SpecCreator.class); + + assertThatThrownBy( + () -> + ForksScheduleFactory.create( + genesisConfigOptions, List.of(fork1, fork2, fork3), specCreator)) + .hasMessage("Duplicate transitions cannot be created for the same block"); + } + + @SuppressWarnings("unchecked") + @Test + public void createsScheduleUsingSpecCreator() { + final BftConfigOptions genesisConfigOptions = JsonBftConfigOptions.DEFAULT; + final ForkSpec genesisForkSpec = new ForkSpec<>(0, genesisConfigOptions); + final BftFork fork1 = createFork(1, 10); + final BftFork fork2 = createFork(2, 20); + final SpecCreator specCreator = Mockito.mock(SpecCreator.class); + + final BftConfigOptions configOptions1 = createBftConfigOptions(10); + final BftConfigOptions configOptions2 = createBftConfigOptions(20); + when(specCreator.create(genesisForkSpec, fork1)).thenReturn(configOptions1); + when(specCreator.create(new ForkSpec<>(1, configOptions1), fork2)).thenReturn(configOptions2); + + final ForksSchedule schedule = + ForksScheduleFactory.create(genesisConfigOptions, List.of(fork1, fork2), specCreator); + assertThat(schedule.getFork(0)).isEqualTo(genesisForkSpec); + assertThat(schedule.getFork(1)).isEqualTo(new ForkSpec<>(1, configOptions1)); + assertThat(schedule.getFork(2)).isEqualTo(new ForkSpec<>(2, configOptions2)); + } + + private MutableBftConfigOptions createBftConfigOptions(final int blockPeriodSeconds) { + final MutableBftConfigOptions bftConfigOptions = + new MutableBftConfigOptions(JsonBftConfigOptions.DEFAULT); + bftConfigOptions.setBlockPeriodSeconds(blockPeriodSeconds); + return bftConfigOptions; + } + + private BftFork createFork(final long block, final long blockPeriodSeconds) { + return new BftFork( + JsonUtil.objectNodeFromMap( + Map.of( + BftFork.FORK_BLOCK_KEY, block, + BftFork.BLOCK_PERIOD_SECONDS_KEY, blockPeriodSeconds))); + } +} diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/ForksScheduleTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/ForksScheduleTest.java index bf6d32f4694..a70d20da8d2 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/ForksScheduleTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/ForksScheduleTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -24,7 +24,7 @@ import java.util.List; import java.util.Optional; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class ForksScheduleTest { diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/MigratingMiningCoordinatorTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/MigratingMiningCoordinatorTest.java index 31eabf22aaf..f3edea81e6c 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/MigratingMiningCoordinatorTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/MigratingMiningCoordinatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -16,6 +16,7 @@ import static java.util.Collections.emptyList; import static org.hyperledger.besu.ethereum.core.BlockHeader.GENESIS_BLOCK_NUMBER; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; @@ -29,7 +30,6 @@ import org.hyperledger.besu.consensus.common.bft.blockcreation.BftMiningCoordinator; import org.hyperledger.besu.consensus.common.bft.statemachine.BftEventHandler; import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator; -import org.hyperledger.besu.ethereum.blockcreation.NoopMiningCoordinator; import org.hyperledger.besu.ethereum.chain.BlockAddedEvent; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.Block; @@ -39,14 +39,13 @@ import java.util.List; import java.util.function.Consumer; -import org.apache.tuweni.bytes.Bytes; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class MigratingMiningCoordinatorTest { @Mock private BftMiningCoordinator coordinator1; @@ -58,7 +57,7 @@ public class MigratingMiningCoordinatorTest { private ForksSchedule coordinatorSchedule; private static final long MIGRATION_BLOCK_NUMBER = 5L; - @Before + @BeforeEach public void setup() { coordinatorSchedule = createCoordinatorSchedule(coordinator1, coordinator2); final Block block = new Block(blockHeader, blockBody); @@ -87,13 +86,12 @@ public void startShouldRegisterThisCoordinatorAsObserver() { @Test public void startShouldUnregisterDelegateCoordinatorAsObserver() { final BftMiningCoordinator delegateCoordinator = createDelegateCoordinator(); - when(blockchain.observeBlockAdded(delegateCoordinator)).thenReturn(1L); + lenient().when(blockchain.observeBlockAdded(delegateCoordinator)).thenReturn(1L); final MigratingMiningCoordinator coordinator = new MigratingMiningCoordinator( createCoordinatorSchedule(delegateCoordinator, coordinator2), blockchain); coordinator.start(); - verify(blockchain).observeBlockAdded(coordinator); verify(blockchain).observeBlockAdded(delegateCoordinator); verify(blockchain).removeObserver(1L); @@ -123,7 +121,7 @@ public void stopShouldUnregisterThisCoordinatorAsObserver() { @Test public void onBlockAddedShouldNotDelegateWhenDelegateIsNoop() { - NoopMiningCoordinator mockNoopCoordinator = mock(NoopMiningCoordinator.class); + MiningCoordinator mockNoopCoordinator = mock(MiningCoordinator.class); coordinatorSchedule = createCoordinatorSchedule(mockNoopCoordinator, coordinator2); when(blockHeader.getNumber()).thenReturn(GENESIS_BLOCK_NUMBER); @@ -162,11 +160,6 @@ public void delegatesToActiveMiningCoordinator() { coordinator2, coordinator1); - verifyDelegation( - c -> c.setExtraData(Bytes.EMPTY), GENESIS_BLOCK_NUMBER, coordinator1, coordinator2); - verifyDelegation( - c -> c.setExtraData(Bytes.EMPTY), MIGRATION_BLOCK_NUMBER, coordinator2, coordinator1); - verifyDelegation( MiningCoordinator::getCoinbase, GENESIS_BLOCK_NUMBER, coordinator1, coordinator2); verifyDelegation( diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/MigratingProtocolContextTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/MigratingProtocolContextTest.java index 21ba7638c40..d62de57eac9 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/MigratingProtocolContextTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/MigratingProtocolContextTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -19,13 +19,13 @@ import static org.mockito.Mockito.when; import org.hyperledger.besu.ethereum.ConsensusContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import java.util.List; -import java.util.Optional; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mockito.Mockito; public class MigratingProtocolContextTest { @@ -45,7 +45,7 @@ public void returnsContextForSpecificChainHeight() { new ForksSchedule<>(List.of(new ForkSpec<>(0L, context1), new ForkSpec<>(10L, context2))); final MigratingProtocolContext migratingProtocolContext = new MigratingProtocolContext( - blockchain, worldStateArchive, contextSchedule, Optional.empty()); + blockchain, worldStateArchive, contextSchedule, new BadBlockManager()); assertThat(migratingProtocolContext.getConsensusContext(ConsensusContext.class)) .isSameAs(context1); diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BaseBftProtocolScheduleBuilderTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BaseBftProtocolScheduleBuilderTest.java index d704cddce85..e23664fd8d3 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BaseBftProtocolScheduleBuilderTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BaseBftProtocolScheduleBuilderTest.java @@ -27,9 +27,11 @@ import org.hyperledger.besu.consensus.common.ForksSchedule; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.MilestoneStreamingProtocolSchedule; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.mainnet.BlockHeaderValidator; import org.hyperledger.besu.ethereum.mainnet.DefaultProtocolSchedule; @@ -37,12 +39,13 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import java.math.BigInteger; import java.util.List; import java.util.Optional; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class BaseBftProtocolScheduleBuilderTest { @@ -241,7 +244,11 @@ protected BlockHeaderValidator.Builder createBlockHeaderRuleset( PrivacyParameters.DEFAULT, false, bftExtraDataCodec, - EvmConfiguration.DEFAULT); + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); } private BftConfigOptions createBftConfig(final BigInteger blockReward) { diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BaseForksSchedulesFactoryTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BaseForksSchedulesFactoryTest.java index 5102276f8e8..298b56b3725 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BaseForksSchedulesFactoryTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BaseForksSchedulesFactoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -30,7 +30,7 @@ import java.util.function.Consumer; import com.fasterxml.jackson.databind.node.ObjectNode; -import org.junit.Test; +import org.junit.jupiter.api.Test; public abstract class BaseForksSchedulesFactoryTest< C extends BftConfigOptions, M extends MutableBftConfigOptions> { diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BftEventQueueTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BftEventQueueTest.java index 28e444e0e42..b39bef3a439 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BftEventQueueTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BftEventQueueTest.java @@ -24,7 +24,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class BftEventQueueTest { private static final int MAX_QUEUE_SIZE = 1000; @@ -32,23 +32,53 @@ public class BftEventQueueTest { private static class DummyBftEvent implements BftEvent { @Override public BftEvents.Type getType() { - return null; + return BftEvents.Type.MESSAGE; + } + } + + private static class DummyRoundExpiryBftEvent implements BftEvent { + @Override + public BftEvents.Type getType() { + return BftEvents.Type.ROUND_EXPIRY; + } + } + + private static class DummyNewChainHeadBftEvent implements BftEvent { + @Override + public BftEvents.Type getType() { + return BftEvents.Type.NEW_CHAIN_HEAD; + } + } + + private static class DummyBlockTimerExpiryBftEvent implements BftEvent { + @Override + public BftEvents.Type getType() { + return BftEvents.Type.BLOCK_TIMER_EXPIRY; } } @Test public void addPoll() throws InterruptedException { final BftEventQueue queue = new BftEventQueue(MAX_QUEUE_SIZE); + queue.start(); assertThat(queue.poll(0, TimeUnit.MICROSECONDS)).isNull(); - final DummyBftEvent dummyEvent = new DummyBftEvent(); - queue.add(dummyEvent); - assertThat(queue.poll(0, TimeUnit.MICROSECONDS)).isEqualTo(dummyEvent); + final DummyBftEvent dummyMessageEvent = new DummyBftEvent(); + final DummyRoundExpiryBftEvent dummyRoundTimerEvent = new DummyRoundExpiryBftEvent(); + final DummyNewChainHeadBftEvent dummyNewChainHeadEvent = new DummyNewChainHeadBftEvent(); + queue.add(dummyMessageEvent); + queue.add(dummyRoundTimerEvent); + queue.add(dummyNewChainHeadEvent); + assertThat(queue.poll(0, TimeUnit.MICROSECONDS)).isEqualTo(dummyMessageEvent); + assertThat(queue.poll(0, TimeUnit.MICROSECONDS)).isEqualTo(dummyRoundTimerEvent); + assertThat(queue.poll(0, TimeUnit.MICROSECONDS)).isEqualTo(dummyNewChainHeadEvent); + assertThat(queue.poll(0, TimeUnit.MICROSECONDS)).isNull(); } @Test public void queueOrdering() throws InterruptedException { final BftEventQueue queue = new BftEventQueue(MAX_QUEUE_SIZE); + queue.start(); final DummyBftEvent dummyEvent1 = new DummyBftEvent(); final DummyBftEvent dummyEvent2 = new DummyBftEvent(); @@ -69,6 +99,7 @@ public void queueOrdering() throws InterruptedException { @Test public void addSizeLimit() throws InterruptedException { final BftEventQueue queue = new BftEventQueue(MAX_QUEUE_SIZE); + queue.start(); for (int i = 0; i <= 1000; i++) { final DummyBftEvent dummyEvent = new DummyBftEvent(); @@ -85,4 +116,34 @@ public void addSizeLimit() throws InterruptedException { assertThat(drain).doesNotContainNull(); assertThat(queue.poll(0, TimeUnit.MICROSECONDS)).isNull(); } + + @Test + public void doNotAddUntilStarted() throws InterruptedException { + final BftEventQueue queue = new BftEventQueue(MAX_QUEUE_SIZE); + + assertThat(queue.poll(0, TimeUnit.MICROSECONDS)).isNull(); + final DummyBftEvent dummyMessageEvent = new DummyBftEvent(); + final DummyRoundExpiryBftEvent dummyRoundTimerEvent = new DummyRoundExpiryBftEvent(); + final DummyNewChainHeadBftEvent dummyNewChainHeadEvent = new DummyNewChainHeadBftEvent(); + + // BFT event queue needs starting before it will queue up most types of event. This prevents + // the queue filling with irrelevant messages until BFT mining has started + queue.add(dummyMessageEvent); + queue.add(dummyRoundTimerEvent); + queue.add(dummyNewChainHeadEvent); + assertThat(queue.poll(0, TimeUnit.MICROSECONDS)).isNull(); + } + + @Test + public void alwaysAddBlockTimerExpiryEvents() throws InterruptedException { + final BftEventQueue queue = new BftEventQueue(MAX_QUEUE_SIZE); + + assertThat(queue.poll(0, TimeUnit.MICROSECONDS)).isNull(); + final DummyBlockTimerExpiryBftEvent dummyBlockTimerEvent = new DummyBlockTimerExpiryBftEvent(); + + // Block expiry events need processing in order for the mining coordinator to start, + // so those should be handled regardless of whether the queue has been started + queue.add(dummyBlockTimerEvent); + assertThat(queue.poll(0, TimeUnit.MICROSECONDS)).isEqualTo(dummyBlockTimerEvent); + } } diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BftForksScheduleFactoryTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BftForksScheduleFactoryTest.java deleted file mode 100644 index dbfc16da29e..00000000000 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BftForksScheduleFactoryTest.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright Hyperledger Besu contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.consensus.common.bft; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.config.BftConfigOptions; -import org.hyperledger.besu.config.BftFork; -import org.hyperledger.besu.config.JsonBftConfigOptions; -import org.hyperledger.besu.config.JsonUtil; -import org.hyperledger.besu.consensus.common.ForkSpec; -import org.hyperledger.besu.consensus.common.ForksSchedule; -import org.hyperledger.besu.consensus.common.bft.BftForksScheduleFactory.BftSpecCreator; - -import java.util.List; -import java.util.Map; - -import org.junit.Test; -import org.mockito.Mockito; - -public class BftForksScheduleFactoryTest { - - @Test - @SuppressWarnings("unchecked") - public void throwsErrorIfHasForkForGenesisBlock() { - final BftConfigOptions genesisConfigOptions = JsonBftConfigOptions.DEFAULT; - final BftFork fork = createFork(0, 10); - final BftSpecCreator specCreator = - Mockito.mock(BftSpecCreator.class); - - assertThatThrownBy( - () -> BftForksScheduleFactory.create(genesisConfigOptions, List.of(fork), specCreator)) - .hasMessage("Transition cannot be created for genesis block"); - } - - @Test - @SuppressWarnings("unchecked") - public void throwsErrorIfHasForksWithDuplicateBlock() { - final BftConfigOptions genesisConfigOptions = JsonBftConfigOptions.DEFAULT; - final BftFork fork1 = createFork(1, 10); - final BftFork fork2 = createFork(1, 20); - final BftFork fork3 = createFork(2, 30); - final BftSpecCreator specCreator = - Mockito.mock(BftSpecCreator.class); - - assertThatThrownBy( - () -> - BftForksScheduleFactory.create( - genesisConfigOptions, List.of(fork1, fork2, fork3), specCreator)) - .hasMessage("Duplicate transitions cannot be created for the same block"); - } - - @SuppressWarnings("unchecked") - @Test - public void createsScheduleUsingSpecCreator() { - final BftConfigOptions genesisConfigOptions = JsonBftConfigOptions.DEFAULT; - final ForkSpec genesisForkSpec = new ForkSpec<>(0, genesisConfigOptions); - final BftFork fork1 = createFork(1, 10); - final BftFork fork2 = createFork(2, 20); - final BftSpecCreator specCreator = - Mockito.mock(BftSpecCreator.class); - - final BftConfigOptions configOptions1 = createBftConfigOptions(10); - final BftConfigOptions configOptions2 = createBftConfigOptions(20); - when(specCreator.create(genesisForkSpec, fork1)).thenReturn(configOptions1); - when(specCreator.create(new ForkSpec<>(1, configOptions1), fork2)).thenReturn(configOptions2); - - final ForksSchedule schedule = - BftForksScheduleFactory.create(genesisConfigOptions, List.of(fork1, fork2), specCreator); - assertThat(schedule.getFork(0)).isEqualTo(genesisForkSpec); - assertThat(schedule.getFork(1)).isEqualTo(new ForkSpec<>(1, configOptions1)); - assertThat(schedule.getFork(2)).isEqualTo(new ForkSpec<>(2, configOptions2)); - } - - private MutableBftConfigOptions createBftConfigOptions(final int blockPeriodSeconds) { - final MutableBftConfigOptions bftConfigOptions = - new MutableBftConfigOptions(JsonBftConfigOptions.DEFAULT); - bftConfigOptions.setBlockPeriodSeconds(blockPeriodSeconds); - return bftConfigOptions; - } - - private BftFork createFork(final long block, final long blockPeriodSeconds) { - return new BftFork( - JsonUtil.objectNodeFromMap( - Map.of( - BftFork.FORK_BLOCK_KEY, block, - BftFork.BLOCK_PERIOD_SECONDS_KEY, blockPeriodSeconds))); - } -} diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BftHelpersTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BftHelpersTest.java index 6daadc0fd41..67e7cc283b9 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BftHelpersTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BftHelpersTest.java @@ -15,7 +15,7 @@ package org.hyperledger.besu.consensus.common.bft; import org.assertj.core.api.Assertions; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class BftHelpersTest { diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BftProcessorTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BftProcessorTest.java index da62a8f5cc5..5c8d86f3866 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BftProcessorTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BftProcessorTest.java @@ -31,17 +31,17 @@ import java.util.concurrent.TimeUnit; import org.awaitility.Awaitility; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.StrictStubs.class) +@ExtendWith(MockitoExtension.class) public class BftProcessorTest { private EventMultiplexer mockeEventMultiplexer; - @Before + @BeforeEach public void initialise() { mockeEventMultiplexer = mock(EventMultiplexer.class); } @@ -130,6 +130,7 @@ public void handlesQueueInterruptGracefully() throws InterruptedException { @Test public void drainEventsIntoStateMachine() throws InterruptedException { final BftEventQueue queue = new BftEventQueue(1000); + queue.start(); final BftProcessor processor = new BftProcessor(queue, mockeEventMultiplexer); // Start the BftProcessor diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BlockTimerTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BlockTimerTest.java index ec0783f6a3e..70187a4d40d 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BlockTimerTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/BlockTimerTest.java @@ -37,14 +37,14 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.StrictStubs.class) +@ExtendWith(MockitoExtension.class) public class BlockTimerTest { private BftExecutors bftExecutors; @@ -52,7 +52,7 @@ public class BlockTimerTest { private Clock mockClock; private ForksSchedule mockForksSchedule; - @Before + @BeforeEach @SuppressWarnings("unchecked") public void initialise() { bftExecutors = mock(BftExecutors.class); @@ -141,7 +141,7 @@ public void aBlockTimerExpiryEventIsAddedToTheQueueOnExpiry() throws Interrupted assertThat(eventQueue.size()).isEqualTo(1); final BftEvent queuedEvent = eventQueue.poll(0, TimeUnit.SECONDS); assertThat(queuedEvent).isInstanceOf(BlockTimerExpiry.class); - assertThat(((BlockTimerExpiry) queuedEvent).getRoundIndentifier()) + assertThat(((BlockTimerExpiry) queuedEvent).getRoundIdentifier()) .usingRecursiveComparison() .isEqualTo(round); } @@ -171,7 +171,7 @@ public void eventIsImmediatelyAddedToTheQueueIfAbsoluteExpiryIsEqualToNow() { verify(mockQueue).add(bftEventCaptor.capture()); assertThat(bftEventCaptor.getValue() instanceof BlockTimerExpiry).isTrue(); - assertThat(((BlockTimerExpiry) bftEventCaptor.getValue()).getRoundIndentifier()) + assertThat(((BlockTimerExpiry) bftEventCaptor.getValue()).getRoundIdentifier()) .usingRecursiveComparison() .isEqualTo(round); } @@ -201,7 +201,7 @@ public void eventIsImmediatelyAddedToTheQueueIfAbsoluteExpiryIsInThePast() { verify(mockQueue).add(bftEventCaptor.capture()); assertThat(bftEventCaptor.getValue() instanceof BlockTimerExpiry).isTrue(); - assertThat(((BlockTimerExpiry) bftEventCaptor.getValue()).getRoundIndentifier()) + assertThat(((BlockTimerExpiry) bftEventCaptor.getValue()).getRoundIdentifier()) .usingRecursiveComparison() .isEqualTo(round); } diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/EthSynchronizerUpdaterTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/EthSynchronizerUpdaterTest.java index 7d01ebe059e..62465a183d5 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/EthSynchronizerUpdaterTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/EthSynchronizerUpdaterTest.java @@ -27,12 +27,12 @@ import org.hyperledger.besu.ethereum.eth.manager.EthPeers; import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class EthSynchronizerUpdaterTest { @Mock private EthPeers ethPeers; diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/MessageTrackerTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/MessageTrackerTest.java index 72eb918c74a..68fce007271 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/MessageTrackerTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/MessageTrackerTest.java @@ -19,7 +19,7 @@ import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; import org.apache.tuweni.bytes.Bytes; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class MessageTrackerTest { private final MessageTracker messageTracker = new MessageTracker(5); diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/RoundTimerTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/RoundTimerTest.java index 693074bdfb0..0ebca51c9e2 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/RoundTimerTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/RoundTimerTest.java @@ -28,23 +28,24 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.StrictStubs.class) +@ExtendWith(MockitoExtension.class) public class RoundTimerTest { private BftExecutors bftExecutors; private BftEventQueue queue; private RoundTimer timer; - @Before + @BeforeEach public void initialise() { bftExecutors = mock(BftExecutors.class); queue = new BftEventQueue(1000); + queue.start(); timer = new RoundTimer(queue, 1, bftExecutors); } diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/SizeLimitedMapTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/SizeLimitedMapTest.java index b6eb9a057fd..e363920078d 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/SizeLimitedMapTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/SizeLimitedMapTest.java @@ -16,7 +16,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class SizeLimitedMapTest { diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/UniqueMessageMulticasterTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/UniqueMessageMulticasterTest.java index 70c18c52303..cbc731317e9 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/UniqueMessageMulticasterTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/UniqueMessageMulticasterTest.java @@ -31,11 +31,11 @@ import com.google.common.collect.Lists; import org.apache.tuweni.bytes.Bytes; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class UniqueMessageMulticasterTest { private final MessageTracker messageTracker = mock(MessageTracker.class); diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/VoteTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/VoteTest.java index cb96b9d4f71..ec37b1d4bb2 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/VoteTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/VoteTest.java @@ -14,11 +14,11 @@ */ package org.hyperledger.besu.consensus.common.bft; -import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import org.hyperledger.besu.datatypes.Address; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class VoteTest { @Test diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftMiningCoordinatorTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftMiningCoordinatorTest.java index 5b1d7a3cf64..cb0dffba9b1 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftMiningCoordinatorTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/blockcreation/BftMiningCoordinatorTest.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.consensus.common.bft.blockcreation; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -34,14 +35,13 @@ import java.util.Collections; import java.util.concurrent.TimeUnit; -import org.apache.tuweni.bytes.Bytes; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class BftMiningCoordinatorTest { @Mock private BftEventHandler controller; @Mock private BftExecutors bftExecutors; @@ -54,14 +54,15 @@ public class BftMiningCoordinatorTest { private final BftEventQueue eventQueue = new BftEventQueue(1000); private BftMiningCoordinator bftMiningCoordinator; - @Before + @BeforeEach public void setup() { + eventQueue.start(); bftMiningCoordinator = new BftMiningCoordinator( bftExecutors, controller, bftProcessor, bftBlockCreatorFactory, blockChain, eventQueue); - when(block.getBody()).thenReturn(blockBody); - when(block.getHeader()).thenReturn(blockHeader); - when(blockBody.getTransactions()).thenReturn(Collections.emptyList()); + lenient().when(block.getBody()).thenReturn(blockBody); + lenient().when(block.getHeader()).thenReturn(blockHeader); + lenient().when(blockBody.getTransactions()).thenReturn(Collections.emptyList()); } @Test @@ -88,13 +89,6 @@ public void getsMinTransactionGasPrice() { assertThat(bftMiningCoordinator.getMinTransactionGasPrice()).isEqualTo(minGasPrice); } - @Test - public void setsTheExtraData() { - final Bytes extraData = Bytes.fromHexStringLenient("0x1234"); - bftMiningCoordinator.setExtraData(extraData); - verify(bftBlockCreatorFactory).setExtraData(extraData); - } - @Test public void addsNewChainHeadEventWhenNewCanonicalHeadBlockEventReceived() throws Exception { BlockAddedEvent headAdvancement = diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/blockcreation/ProposerSelectorTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/blockcreation/ProposerSelectorTest.java index 80737668027..ebe74b16988 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/blockcreation/ProposerSelectorTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/blockcreation/ProposerSelectorTest.java @@ -35,7 +35,7 @@ import java.util.List; import java.util.Optional; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class ProposerSelectorTest { diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftCoinbaseValidationRuleTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftCoinbaseValidationRuleTest.java index fcbea4954d8..791edaf636d 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftCoinbaseValidationRuleTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftCoinbaseValidationRuleTest.java @@ -22,15 +22,15 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.Util; import java.util.List; -import java.util.Optional; import com.google.common.collect.Lists; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class BftCoinbaseValidationRuleTest { @@ -51,7 +51,8 @@ public void proposerInValidatorListPassesValidation() { final List
validators = Lists.newArrayList(proposerAddress); final ProtocolContext context = - new ProtocolContext(null, null, setupContextWithValidators(validators), Optional.empty()); + new ProtocolContext( + null, null, setupContextWithValidators(validators), new BadBlockManager()); final BftCoinbaseValidationRule coinbaseValidationRule = new BftCoinbaseValidationRule(); @@ -71,7 +72,8 @@ public void proposerNotInValidatorListFailsValidation() { final List
validators = Lists.newArrayList(otherValidatorNodeAddress); final ProtocolContext context = - new ProtocolContext(null, null, setupContextWithValidators(validators), Optional.empty()); + new ProtocolContext( + null, null, setupContextWithValidators(validators), new BadBlockManager()); final BftCoinbaseValidationRule coinbaseValidationRule = new BftCoinbaseValidationRule(); diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftCommitSealsValidationRuleTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftCommitSealsValidationRuleTest.java index 453b28a601c..84d59397bcd 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftCommitSealsValidationRuleTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftCommitSealsValidationRuleTest.java @@ -28,17 +28,17 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Util; import java.util.Collections; import java.util.List; -import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.IntStream; import com.google.common.collect.Lists; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class BftCommitSealsValidationRuleTest { @@ -58,7 +58,8 @@ public void correctlyConstructedHeaderPassesValidation() { .collect(Collectors.toList()); final BftContext bftContext = setupContextWithValidators(committerAddresses); - final ProtocolContext context = new ProtocolContext(null, null, bftContext, Optional.empty()); + final ProtocolContext context = + new ProtocolContext(null, null, bftContext, new BadBlockManager()); when(bftContext.getBlockInterface().getCommitters(any())).thenReturn(committerAddresses); assertThat(commitSealsValidationRule.validate(blockHeader, null, context)).isTrue(); @@ -72,7 +73,8 @@ public void insufficientCommitSealsFailsValidation() { final List
validators = singletonList(committerAddress); final BftContext bftContext = setupContextWithValidators(validators); - final ProtocolContext context = new ProtocolContext(null, null, bftContext, Optional.empty()); + final ProtocolContext context = + new ProtocolContext(null, null, bftContext, new BadBlockManager()); when(bftContext.getBlockInterface().getCommitters(any())).thenReturn(emptyList()); assertThat(commitSealsValidationRule.validate(blockHeader, null, context)).isFalse(); @@ -89,7 +91,8 @@ public void committerNotInValidatorListFailsValidation() { final NodeKey nonValidatorNodeKey = NodeKeyUtils.generate(); final BftContext bftContext = setupContextWithValidators(validators); - final ProtocolContext context = new ProtocolContext(null, null, bftContext, Optional.empty()); + final ProtocolContext context = + new ProtocolContext(null, null, bftContext, new BadBlockManager()); when(bftContext.getBlockInterface().getCommitters(any())) .thenReturn(singletonList(Util.publicKeyToAddress(nonValidatorNodeKey.getPublicKey()))); @@ -136,7 +139,8 @@ public void headerContainsDuplicateSealsFailsValidation() { final List
validators = singletonList(committerAddress); final BftContext bftContext = setupContextWithValidators(validators); - final ProtocolContext context = new ProtocolContext(null, null, bftContext, Optional.empty()); + final ProtocolContext context = + new ProtocolContext(null, null, bftContext, new BadBlockManager()); when(bftContext.getBlockInterface().getCommitters(any())) .thenReturn(List.of(committerAddress, committerAddress)); @@ -155,7 +159,8 @@ private boolean subExecution(final int validatorCount, final int committerCount) Collections.sort(validators); final BftContext bftContext = setupContextWithValidators(validators); - final ProtocolContext context = new ProtocolContext(null, null, bftContext, Optional.empty()); + final ProtocolContext context = + new ProtocolContext(null, null, bftContext, new BadBlockManager()); when(bftContext.getBlockInterface().getCommitters(any())) .thenReturn(validators.subList(0, committerCount)); diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftValidatorsValidationRuleTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftValidatorsValidationRuleTest.java index 00b994295ca..8ffd5633456 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftValidatorsValidationRuleTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftValidatorsValidationRuleTest.java @@ -22,14 +22,14 @@ import org.hyperledger.besu.consensus.common.bft.BftExtraData; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.AddressHelpers; import org.hyperledger.besu.ethereum.core.BlockHeader; import java.util.List; -import java.util.Optional; import com.google.common.collect.Lists; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class BftValidatorsValidationRuleTest { @@ -46,7 +46,10 @@ public void correctlyConstructedHeaderPassesValidation() { final ProtocolContext context = new ProtocolContext( - null, null, setupContextWithBftExtraData(validators, bftExtraData), Optional.empty()); + null, + null, + setupContextWithBftExtraData(validators, bftExtraData), + new BadBlockManager()); when(bftExtraData.getValidators()).thenReturn(validators); assertThat(validatorsValidationRule.validate(blockHeader, null, context)).isTrue(); @@ -61,7 +64,10 @@ public void validatorsInNonAscendingOrderFailValidation() { final ProtocolContext context = new ProtocolContext( - null, null, setupContextWithBftExtraData(validators, bftExtraData), Optional.empty()); + null, + null, + setupContextWithBftExtraData(validators, bftExtraData), + new BadBlockManager()); when(bftExtraData.getValidators()).thenReturn(Lists.reverse(validators)); assertThat(validatorsValidationRule.validate(blockHeader, null, context)).isFalse(); @@ -82,7 +88,7 @@ public void mismatchingReportedValidatorsVsLocallyStoredListFailsValidation() { null, null, setupContextWithBftExtraData(storedValidators, bftExtraData), - Optional.empty()); + new BadBlockManager()); when(bftExtraData.getValidators()).thenReturn(Lists.reverse(reportedValidators)); assertThat(validatorsValidationRule.validate(blockHeader, null, context)).isFalse(); diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftVanityDataValidationRuleTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftVanityDataValidationRuleTest.java index cd148f8787a..39f296a9937 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftVanityDataValidationRuleTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/headervalidationrules/BftVanityDataValidationRuleTest.java @@ -22,12 +22,11 @@ import org.hyperledger.besu.consensus.common.bft.BftExtraData; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.BlockHeader; -import java.util.Optional; - import org.apache.tuweni.bytes.Bytes; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class BftVanityDataValidationRuleTest { private final BftVanityDataValidationRule validationRule = new BftVanityDataValidationRule(); @@ -47,7 +46,10 @@ public boolean headerWithVanityDataOfSize(final int extraDataSize) { final ProtocolContext context = new ProtocolContext( - null, null, setupContextWithBftExtraData(emptyList(), extraData), Optional.empty()); + null, + null, + setupContextWithBftExtraData(emptyList(), extraData), + new BadBlockManager()); return validationRule.validate(blockHeader, null, context); } } diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/network/ValidatorPeersTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/network/ValidatorPeersTest.java index 38ad26c658b..3f8c9f24153 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/network/ValidatorPeersTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/network/ValidatorPeersTest.java @@ -16,6 +16,7 @@ import static com.google.common.collect.Lists.newArrayList; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -37,13 +38,13 @@ import java.util.List; import org.apache.tuweni.bytes.Bytes; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class ValidatorPeersTest { public static final String PROTOCOL_NAME = "BFT"; @@ -53,7 +54,7 @@ public class ValidatorPeersTest { private final List peerConnections = newArrayList(); @Mock ValidatorProvider validatorProvider; - @Before + @BeforeEach public void setup() { for (int i = 0; i < 4; i++) { final SECPPublicKey pubKey = @@ -71,8 +72,8 @@ public void setup() { private PeerConnection mockPeerConnection(final Address address) { final PeerInfo peerInfo = mock(PeerInfo.class); final PeerConnection peerConnection = mock(PeerConnection.class); - when(peerConnection.getPeerInfo()).thenReturn(peerInfo); - when(peerInfo.getAddress()).thenReturn(address); + lenient().when(peerConnection.getPeerInfo()).thenReturn(peerInfo); + lenient().when(peerInfo.getAddress()).thenReturn(address); return peerConnection; } diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/queries/BftQueryServiceImplTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/queries/BftQueryServiceImplTest.java index ab161c2c401..cc99cf805fc 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/queries/BftQueryServiceImplTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/queries/BftQueryServiceImplTest.java @@ -42,13 +42,13 @@ import com.google.common.collect.Lists; import org.apache.tuweni.bytes.Bytes; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.jupiter.MockitoExtension; -@RunWith(MockitoJUnitRunner.class) +@ExtendWith(MockitoExtension.class) public class BftQueryServiceImplTest { @Mock private Blockchain blockchain; @@ -66,7 +66,7 @@ public class BftQueryServiceImplTest { private BlockHeader blockHeader; - @Before + @BeforeEach public void setup() { final BlockHeaderTestFixture blockHeaderTestFixture = new BlockHeaderTestFixture(); blockHeaderTestFixture.number(1); // can't be genesis block (due to extradata serialisation) diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/statemachine/FutureMessageBufferTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/statemachine/FutureMessageBufferTest.java index 1267d5794b6..b584c9a16b5 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/statemachine/FutureMessageBufferTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/bft/statemachine/FutureMessageBufferTest.java @@ -30,15 +30,15 @@ import java.util.stream.IntStream; import org.apache.tuweni.bytes.Bytes; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class FutureMessageBufferTest { private Message message; private FutureMessageBuffer futureMsgBuffer; private final PeerConnection peerConnection = MockPeerFactory.create(AddressHelpers.ofValue(9)); - @Before + @BeforeEach public void setup() { message = createMessage(10); futureMsgBuffer = new FutureMessageBuffer(5, 5, 0); diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/jsonrpc/AbstractVoteProposerMethodTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/jsonrpc/AbstractVoteProposerMethodTest.java index a59bd556e4d..eefe1784c82 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/jsonrpc/AbstractVoteProposerMethodTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/jsonrpc/AbstractVoteProposerMethodTest.java @@ -14,7 +14,7 @@ */ package org.hyperledger.besu.consensus.common.jsonrpc; -import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/ForkingVoteTallyCacheTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/ForkingVoteTallyCacheTest.java index f1692201cec..d877223a8fd 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/ForkingVoteTallyCacheTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/ForkingVoteTallyCacheTest.java @@ -26,7 +26,7 @@ import java.util.Map; import com.google.common.collect.Lists; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class ForkingVoteTallyCacheTest extends VoteTallyCacheTestBase { diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteProposerTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteProposerTest.java index d486a41979a..6263bc960cc 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteProposerTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteProposerTest.java @@ -24,7 +24,7 @@ import java.util.Collections; import java.util.Optional; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class VoteProposerTest { private final Address localAddress = Address.fromHexString("0"); diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyCacheTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyCacheTest.java index c69f2bb7697..514e5b8ab37 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyCacheTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyCacheTest.java @@ -35,7 +35,7 @@ import java.util.Optional; import com.google.common.util.concurrent.UncheckedExecutionException; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; public class VoteTallyCacheTest extends VoteTallyCacheTestBase { diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyCacheTestBase.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyCacheTestBase.java index 1a3dba12b1d..00e22532e2f 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyCacheTestBase.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyCacheTestBase.java @@ -33,7 +33,7 @@ import org.apache.tuweni.bytes.Bytes; import org.assertj.core.util.Lists; -import org.junit.Before; +import org.junit.jupiter.api.BeforeEach; public class VoteTallyCacheTestBase { @@ -55,7 +55,7 @@ protected Block createEmptyBlock(final long blockNumber, final Hash parentHash) protected final BlockInterface blockInterface = mock(BlockInterface.class); - @Before + @BeforeEach public void constructThreeBlockChain() { for (int i = 0; i < 3; i++) { validators.add(AddressHelpers.ofValue(i)); diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyTest.java index 96671716cae..9c8d1033cba 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyTest.java @@ -22,7 +22,7 @@ import org.hyperledger.besu.consensus.common.validator.VoteType; import org.hyperledger.besu.datatypes.Address; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class VoteTallyTest { diff --git a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyUpdaterTest.java b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyUpdaterTest.java index 6d7590edf9a..21039c84d85 100644 --- a/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyUpdaterTest.java +++ b/consensus/common/src/test/java/org/hyperledger/besu/consensus/common/validator/blockbased/VoteTallyUpdaterTest.java @@ -37,7 +37,7 @@ import java.util.Optional; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class VoteTallyUpdaterTest { diff --git a/consensus/ibft/build.gradle b/consensus/ibft/build.gradle index dd05944803f..da6304c4e58 100644 --- a/consensus/ibft/build.gradle +++ b/consensus/ibft/build.gradle @@ -22,7 +22,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } diff --git a/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContext.java b/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContext.java index fdd229c13ee..acf2507681b 100644 --- a/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContext.java +++ b/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContext.java @@ -90,8 +90,8 @@ public Block createBlockForProposal( final BlockHeader parent, final int round, final long timestamp) { return finalState .getBlockCreatorFactory() - .create(parent, round) - .createBlock(timestamp) + .create(round) + .createBlock(timestamp, parent) .getBlock(); } diff --git a/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContextBuilder.java b/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContextBuilder.java index ce06496d547..5d2b02b1a7c 100644 --- a/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContextBuilder.java +++ b/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/support/TestContextBuilder.java @@ -69,6 +69,7 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MinedBlockObserver; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.AddressHelpers; @@ -83,6 +84,7 @@ import org.hyperledger.besu.ethereum.core.Util; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -327,7 +329,14 @@ private static ControllerAndState createControllerAndFinalState( final BftProtocolSchedule protocolSchedule = IbftProtocolScheduleBuilder.create( - genesisConfigOptions, forksSchedule, IBFT_EXTRA_DATA_ENCODER, EvmConfiguration.DEFAULT); + genesisConfigOptions, + forksSchedule, + IBFT_EXTRA_DATA_ENCODER, + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); ///////////////////////////////////////////////////////////////////////////////////// // From here down is BASICALLY taken from IbftBesuController @@ -344,7 +353,7 @@ private static ControllerAndState createControllerAndFinalState( blockChain, worldStateArchive, new BftContext(validatorProvider, epochManager, blockInterface), - Optional.empty()); + new BadBlockManager()); final TransactionPoolConfiguration poolConf = ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(1).build(); @@ -364,7 +373,7 @@ private static ControllerAndState createControllerAndFinalState( ethContext, new TransactionPoolMetrics(metricsSystem), poolConf, - null); + new BlobCache()); transactionPool.setEnabled(); diff --git a/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/tests/TransitionsTest.java b/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/tests/TransitionsTest.java index 9b4b03f6564..0e51c4aaf78 100644 --- a/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/tests/TransitionsTest.java +++ b/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/tests/TransitionsTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/tests/round/IbftRoundIntegrationTest.java b/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/tests/round/IbftRoundIntegrationTest.java index ff9c47ab38c..6a2199f7138 100644 --- a/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/tests/round/IbftRoundIntegrationTest.java +++ b/consensus/ibft/src/integration-test/java/org/hyperledger/besu/consensus/ibft/tests/round/IbftRoundIntegrationTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.when; import org.hyperledger.besu.consensus.common.bft.BftExtraData; +import org.hyperledger.besu.consensus.common.bft.BftProtocolSchedule; import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier; import org.hyperledger.besu.consensus.common.bft.RoundTimer; import org.hyperledger.besu.consensus.common.bft.blockcreation.BftBlockCreator; @@ -40,6 +41,7 @@ import org.hyperledger.besu.cryptoservices.NodeKeyUtils; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MinedBlockObserver; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; @@ -48,6 +50,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.BlockImporter; import org.hyperledger.besu.ethereum.mainnet.BlockImportResult; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.plugin.services.securitymodule.SecurityModuleException; import org.hyperledger.besu.util.Subscribers; @@ -74,6 +77,8 @@ public class IbftRoundIntegrationTest { private final Subscribers subscribers = Subscribers.create(); private ProtocolContext protocolContext; + @Mock private BftProtocolSchedule protocolSchedule; + @Mock private ProtocolSpec protocolSpec; @Mock private MutableBlockchain blockChain; @Mock private WorldStateArchive worldStateArchive; @Mock private BlockImporter blockImporter; @@ -85,6 +90,7 @@ public class IbftRoundIntegrationTest { private MessageFactory throwingMessageFactory; private IbftMessageTransmitter transmitter; @Mock private StubValidatorMulticaster multicaster; + @Mock BlockHeader parentHeader; private Block proposedBlock; @@ -112,6 +118,9 @@ public void setup() { final BlockHeader header = headerTestFixture.buildHeader(); proposedBlock = new Block(header, new BlockBody(emptyList(), emptyList())); + when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); + when(protocolSpec.getBlockImporter()).thenReturn(blockImporter); + when(blockImporter.importBlock(any(), any(), any())).thenReturn(new BlockImportResult(true)); protocolContext = @@ -119,7 +128,7 @@ public void setup() { blockChain, worldStateArchive, setupContextWithBftExtraDataEncoder(emptyList(), bftExtraDataEncoder), - Optional.empty()); + new BadBlockManager()); } @Test @@ -131,13 +140,14 @@ public void signingFailsOnReceiptOfProposalUpdatesRoundButTransmitsNothing() { roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, throwingMessageFactory, transmitter, roundTimer, - bftExtraDataEncoder); + bftExtraDataEncoder, + parentHeader); round.handleProposalMessage( peerMessageFactory.createProposal(roundIdentifier, proposedBlock, Optional.empty())); @@ -158,13 +168,14 @@ public void failuresToSignStillAllowBlockToBeImported() { roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, throwingMessageFactory, transmitter, roundTimer, - bftExtraDataEncoder); + bftExtraDataEncoder, + parentHeader); // inject a block first, then a prepare on it. round.handleProposalMessage( diff --git a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftBlockHeaderValidationRulesetFactory.java b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftBlockHeaderValidationRulesetFactory.java index 398e1b518ff..0e71fe81216 100644 --- a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftBlockHeaderValidationRulesetFactory.java +++ b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftBlockHeaderValidationRulesetFactory.java @@ -38,6 +38,8 @@ /** The Ibft block header validation ruleset factory. */ public class IbftBlockHeaderValidationRulesetFactory { + /** Default constructor. */ + private IbftBlockHeaderValidationRulesetFactory() {} /** * Produces a BlockHeaderValidator configured for assessing bft block headers which are to form diff --git a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftExtraDataCodec.java b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftExtraDataCodec.java index 5272e514b6f..03f766b6d4c 100644 --- a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftExtraDataCodec.java +++ b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftExtraDataCodec.java @@ -48,6 +48,9 @@ public class IbftExtraDataCodec extends BftExtraDataCodec { VoteType.ADD, ADD_BYTE_VALUE, VoteType.DROP, DROP_BYTE_VALUE); + /** Default constructor. */ + public IbftExtraDataCodec() {} + /** * Encode from addresses. * diff --git a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftForksSchedulesFactory.java b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftForksSchedulesFactory.java index f41adff34a2..e3c7858bca8 100644 --- a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftForksSchedulesFactory.java +++ b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftForksSchedulesFactory.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -19,11 +19,13 @@ import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.consensus.common.ForkSpec; import org.hyperledger.besu.consensus.common.ForksSchedule; -import org.hyperledger.besu.consensus.common.bft.BftForksScheduleFactory; +import org.hyperledger.besu.consensus.common.ForksScheduleFactory; import org.hyperledger.besu.consensus.common.bft.MutableBftConfigOptions; /** The Ibft forks schedules factory. */ public class IbftForksSchedulesFactory { + /** Default constructor. */ + private IbftForksSchedulesFactory() {} /** * Create forks schedule. @@ -32,7 +34,7 @@ public class IbftForksSchedulesFactory { * @return the forks schedule */ public static ForksSchedule create(final GenesisConfigOptions genesisConfig) { - return BftForksScheduleFactory.create( + return ForksScheduleFactory.create( genesisConfig.getBftConfigOptions(), genesisConfig.getTransitions().getIbftForks(), IbftForksSchedulesFactory::createBftConfigOptions); diff --git a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftProtocolScheduleBuilder.java b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftProtocolScheduleBuilder.java index 80a5c77bbb2..0789f2e8981 100644 --- a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftProtocolScheduleBuilder.java +++ b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/IbftProtocolScheduleBuilder.java @@ -20,16 +20,21 @@ import org.hyperledger.besu.consensus.common.bft.BaseBftProtocolScheduleBuilder; import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec; import org.hyperledger.besu.consensus.common.bft.BftProtocolSchedule; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.mainnet.BlockHeaderValidator; import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.plugin.services.MetricsSystem; import java.util.Optional; /** Defines the protocol behaviours for a blockchain using a BFT consensus mechanism. */ public class IbftProtocolScheduleBuilder extends BaseBftProtocolScheduleBuilder { + /** Default constructor. */ + protected IbftProtocolScheduleBuilder() {} /** * Create protocol schedule. @@ -40,6 +45,11 @@ public class IbftProtocolScheduleBuilder extends BaseBftProtocolScheduleBuilder * @param isRevertReasonEnabled the is revert reason enabled * @param bftExtraDataCodec the bft extra data codec * @param evmConfiguration the evm configuration + * @param miningParameters the mining parameters + * @param badBlockManager the cache to use to keep invalid blocks + * @param isParallelTxProcessingEnabled indicates whether parallel transaction is enabled + * @param metricsSystem A metricSystem instance to be able to expose metrics in the underlying + * calls * @return the protocol schedule */ public static BftProtocolSchedule create( @@ -48,7 +58,11 @@ public static BftProtocolSchedule create( final PrivacyParameters privacyParameters, final boolean isRevertReasonEnabled, final BftExtraDataCodec bftExtraDataCodec, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final BadBlockManager badBlockManager, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return new IbftProtocolScheduleBuilder() .createProtocolSchedule( config, @@ -56,7 +70,11 @@ public static BftProtocolSchedule create( privacyParameters, isRevertReasonEnabled, bftExtraDataCodec, - evmConfiguration); + evmConfiguration, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem); } /** @@ -66,20 +84,33 @@ public static BftProtocolSchedule create( * @param forksSchedule the forks schedule * @param bftExtraDataCodec the bft extra data codec * @param evmConfiguration the evm configuration + * @param miningParameters the mining parameters + * @param badBlockManager the cache to use to keep invalid blocks + * @param isParallelTxProcessingEnabled indicates whether parallel transaction is enabled. + * @param metricsSystem A metricSystem instance to be able to expose metrics in the underlying + * calls * @return the protocol schedule */ public static BftProtocolSchedule create( final GenesisConfigOptions config, final ForksSchedule forksSchedule, final BftExtraDataCodec bftExtraDataCodec, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final BadBlockManager badBlockManager, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return create( config, forksSchedule, PrivacyParameters.DEFAULT, false, bftExtraDataCodec, - evmConfiguration); + evmConfiguration, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem); } @Override diff --git a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/jsonrpc/IbftJsonRpcMethods.java b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/jsonrpc/IbftJsonRpcMethods.java index 13ccf4214a4..1afe067867d 100644 --- a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/jsonrpc/IbftJsonRpcMethods.java +++ b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/jsonrpc/IbftJsonRpcMethods.java @@ -32,6 +32,8 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.methods.ApiGroupJsonRpcMethods; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; +import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import java.util.Map; @@ -39,14 +41,23 @@ public class IbftJsonRpcMethods extends ApiGroupJsonRpcMethods { private final ProtocolContext context; + private final ProtocolSchedule protocolSchedule; + private final MiningParameters miningParameters; /** * Instantiates a new Ibft json rpc methods. * - * @param context the context + * @param context the protocol context + * @param protocolSchedule the protocol schedule + * @param miningParameters the mining parameters */ - public IbftJsonRpcMethods(final ProtocolContext context) { + public IbftJsonRpcMethods( + final ProtocolContext context, + final ProtocolSchedule protocolSchedule, + final MiningParameters miningParameters) { this.context = context; + this.protocolSchedule = protocolSchedule; + this.miningParameters = miningParameters; } @Override @@ -58,7 +69,8 @@ protected String getApiGroup() { protected Map create() { final MutableBlockchain blockchain = context.getBlockchain(); final BlockchainQueries blockchainQueries = - new BlockchainQueries(blockchain, context.getWorldStateArchive()); + new BlockchainQueries( + protocolSchedule, blockchain, context.getWorldStateArchive(), miningParameters); final BftContext bftContext = context.getConsensusContext(BftContext.class); final BlockInterface blockInterface = bftContext.getBlockInterface(); final ValidatorProvider validatorProvider = diff --git a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftDiscardValidatorVote.java b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftDiscardValidatorVote.java index 2fdc60c1298..89534689379 100644 --- a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftDiscardValidatorVote.java +++ b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftDiscardValidatorVote.java @@ -20,9 +20,12 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,7 +53,13 @@ public String getName() { public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { checkState( validatorProvider.getVoteProviderAtHead().isPresent(), "Ibft requires a vote provider"); - final Address validatorAddress = requestContext.getRequiredParameter(0, Address.class); + final Address validatorAddress; + try { + validatorAddress = requestContext.getRequiredParameter(0, Address.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid validator address parameter (index 0)", RpcErrorType.INVALID_ADDRESS_PARAMS, e); + } LOG.trace("Received RPC rpcName={} address={}", getName(), validatorAddress); validatorProvider.getVoteProviderAtHead().get().discardVote(validatorAddress); diff --git a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftGetValidatorsByBlockHash.java b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftGetValidatorsByBlockHash.java index a14eb49e876..dfefe22c53f 100644 --- a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftGetValidatorsByBlockHash.java +++ b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftGetValidatorsByBlockHash.java @@ -18,9 +18,12 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockHeader; @@ -61,7 +64,13 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { } private Object blockResult(final JsonRpcRequestContext request) { - final Hash hash = request.getRequiredParameter(0, Hash.class); + final Hash hash; + try { + hash = request.getRequiredParameter(0, Hash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block hash parameter (index 0)", RpcErrorType.INVALID_BLOCK_HASH_PARAMS, e); + } LOG.trace("Received RPC rpcName={} blockHash={}", getName(), hash); final Optional blockHeader = blockchain.getBlockHeader(hash); return blockHeader diff --git a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftGetValidatorsByBlockNumber.java b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftGetValidatorsByBlockNumber.java index 5e36ca2070c..a878ebc66e7 100644 --- a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftGetValidatorsByBlockNumber.java +++ b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftGetValidatorsByBlockNumber.java @@ -17,9 +17,12 @@ import org.hyperledger.besu.consensus.common.BlockInterface; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.AbstractBlockParameterMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.core.BlockHeader; @@ -50,7 +53,12 @@ public IbftGetValidatorsByBlockNumber( @Override protected BlockParameter blockParameter(final JsonRpcRequestContext request) { - return request.getRequiredParameter(0, BlockParameter.class); + try { + return request.getRequiredParameter(0, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } @Override diff --git a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftProposeValidatorVote.java b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftProposeValidatorVote.java index fe89961410e..f010633e63f 100644 --- a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftProposeValidatorVote.java +++ b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftProposeValidatorVote.java @@ -21,9 +21,12 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,8 +54,20 @@ public String getName() { public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { checkState( validatorProvider.getVoteProviderAtHead().isPresent(), "Ibft requires a vote provider"); - final Address validatorAddress = requestContext.getRequiredParameter(0, Address.class); - final Boolean add = requestContext.getRequiredParameter(1, Boolean.class); + final Address validatorAddress; + try { + validatorAddress = requestContext.getRequiredParameter(0, Address.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid address parameter (index 0)", RpcErrorType.INVALID_ADDRESS_PARAMS, e); + } + final Boolean add; + try { + add = requestContext.getRequiredParameter(1, Boolean.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid vote type parameter (index 1)", RpcErrorType.INVALID_VOTE_TYPE_PARAMS, e); + } LOG.trace( "Received RPC rpcName={} voteType={} address={}", getName(), diff --git a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/messagedata/IbftV2.java b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/messagedata/IbftV2.java index c8b44c1adb7..4a650f1f3f0 100644 --- a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/messagedata/IbftV2.java +++ b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/messagedata/IbftV2.java @@ -15,16 +15,19 @@ package org.hyperledger.besu.consensus.ibft.messagedata; /** Message codes for iBFT v2 messages */ -public class IbftV2 { +public interface IbftV2 { /** The constant PROPOSAL. */ - public static final int PROPOSAL = 0; + int PROPOSAL = 0; + /** The constant PREPARE. */ - public static final int PREPARE = 1; + int PREPARE = 1; + /** The constant COMMIT. */ - public static final int COMMIT = 2; + int COMMIT = 2; + /** The constant ROUND_CHANGE. */ - public static final int ROUND_CHANGE = 3; + int ROUND_CHANGE = 3; /** The constant MESSAGE_SPACE. */ - public static final int MESSAGE_SPACE = 4; + int MESSAGE_SPACE = 4; } diff --git a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/payload/IbftPayload.java b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/payload/IbftPayload.java index 280b1eaa5d2..b13145fddc6 100644 --- a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/payload/IbftPayload.java +++ b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/payload/IbftPayload.java @@ -21,6 +21,8 @@ /** The Ibft payload. */ public abstract class IbftPayload implements Payload { + /** Default constructor. */ + protected IbftPayload() {} @Override public Hash hashForSignature() { diff --git a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/payload/PayloadDeserializers.java b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/payload/PayloadDeserializers.java index cb68290251d..916d5139aaf 100644 --- a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/payload/PayloadDeserializers.java +++ b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/payload/PayloadDeserializers.java @@ -22,6 +22,8 @@ /** The Payload deserializers. */ public class PayloadDeserializers { + /** Default constructor. */ + protected PayloadDeserializers() {} /** * Read signed proposal payload from rlp input. diff --git a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/protocol/IbftSubProtocol.java b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/protocol/IbftSubProtocol.java index d21f269b1bb..b6c7ab37933 100644 --- a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/protocol/IbftSubProtocol.java +++ b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/protocol/IbftSubProtocol.java @@ -23,11 +23,15 @@ public class IbftSubProtocol implements SubProtocol { /** The constant NAME. */ public static String NAME = "IBF"; + /** The constant IBFV1. */ public static final Capability IBFV1 = Capability.create(NAME, 1); private static final IbftSubProtocol INSTANCE = new IbftSubProtocol(); + /** Default constructor. */ + public IbftSubProtocol() {} + /** * Get ibft sub protocol. * diff --git a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftBlockHeightManager.java b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftBlockHeightManager.java index 371cdb61f77..64dd67bc793 100644 --- a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftBlockHeightManager.java +++ b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftBlockHeightManager.java @@ -32,6 +32,7 @@ import org.hyperledger.besu.consensus.ibft.payload.MessageFactory; import org.hyperledger.besu.consensus.ibft.validation.FutureRoundProposalMessageValidator; import org.hyperledger.besu.consensus.ibft.validation.MessageValidatorFactory; +import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.plugin.services.securitymodule.SecurityModuleException; @@ -122,6 +123,9 @@ public IbftBlockHeightManager( @Override public void handleBlockTimerExpiry(final ConsensusRoundIdentifier roundIdentifier) { + + logValidatorChanges(currentRound); + if (roundIdentifier.equals(currentRound.getRoundIdentifier())) { final long headerTimeStampSeconds = Math.round(clock.millis() / 1000D); currentRound.createAndSendProposalMessage(headerTimeStampSeconds); @@ -133,6 +137,30 @@ public void handleBlockTimerExpiry(final ConsensusRoundIdentifier roundIdentifie } } + /** + * If the list of validators for the next block to be proposed/imported has changed from the + * previous block, log the change. Only log for round 0 (i.e. once per block). + * + * @param ibftRound The current round + */ + private void logValidatorChanges(final IbftRound ibftRound) { + if (ibftRound.getRoundIdentifier().getRoundNumber() == 0) { + final Collection
previousValidators = + MessageValidatorFactory.getValidatorsForBlock(ibftRound.protocolContext, parentHeader); + final Collection
validatorsForHeight = + MessageValidatorFactory.getValidatorsAfterBlock(ibftRound.protocolContext, parentHeader); + if (!(validatorsForHeight.containsAll(previousValidators)) + || !(previousValidators.containsAll(validatorsForHeight))) { + LOG.info( + "Validator list change. Previous chain height {}: {}. Current chain height {}: {}.", + parentHeader.getNumber(), + previousValidators, + parentHeader.getNumber() + 1, + validatorsForHeight); + } + } + } + @Override public void roundExpired(final RoundExpiry expire) { if (!expire.getView().equals(currentRound.getRoundIdentifier())) { diff --git a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftBlockHeightManagerFactory.java b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftBlockHeightManagerFactory.java index e3e2e0c4354..50a518b4e19 100644 --- a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftBlockHeightManagerFactory.java +++ b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftBlockHeightManagerFactory.java @@ -20,9 +20,14 @@ import org.hyperledger.besu.consensus.ibft.validation.MessageValidatorFactory; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** The Ibft block height manager factory. */ public class IbftBlockHeightManagerFactory { + private static final Logger LOG = LoggerFactory.getLogger(IbftBlockHeightManagerFactory.class); + private final IbftRoundFactory roundFactory; private final BftFinalState finalState; private final MessageValidatorFactory messageValidatorFactory; @@ -55,8 +60,10 @@ public IbftBlockHeightManagerFactory( */ public BaseIbftBlockHeightManager create(final BlockHeader parentHeader) { if (finalState.isLocalNodeValidator()) { + LOG.debug("Local node is a validator"); return createFullBlockHeightManager(parentHeader); } else { + LOG.debug("Local node is a non-validator"); return createNoOpBlockHeightManager(parentHeader); } } diff --git a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftController.java b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftController.java index 2e3c879b76c..e2156bd7d6c 100644 --- a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftController.java +++ b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftController.java @@ -46,7 +46,7 @@ public class IbftController extends BaseBftController { * @param gossiper the gossiper * @param duplicateMessageTracker the duplicate message tracker * @param futureMessageBuffer the future message buffer - * @param sychronizerUpdater the sychronizer updater + * @param synchronizerUpdater the synchronizer updater */ public IbftController( final Blockchain blockchain, @@ -55,7 +55,7 @@ public IbftController( final Gossiper gossiper, final MessageTracker duplicateMessageTracker, final FutureMessageBuffer futureMessageBuffer, - final SynchronizerUpdater sychronizerUpdater) { + final SynchronizerUpdater synchronizerUpdater) { super( blockchain, @@ -63,7 +63,7 @@ public IbftController( gossiper, duplicateMessageTracker, futureMessageBuffer, - sychronizerUpdater); + synchronizerUpdater); this.ibftBlockHeightManagerFactory = ibftBlockHeightManagerFactory; } diff --git a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRound.java b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRound.java index 3b40e563e48..bff32dbf3cf 100644 --- a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRound.java +++ b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRound.java @@ -40,6 +40,7 @@ import org.hyperledger.besu.ethereum.core.BlockImporter; import org.hyperledger.besu.ethereum.mainnet.BlockImportResult; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.plugin.services.securitymodule.SecurityModuleException; import org.hyperledger.besu.util.Subscribers; @@ -56,12 +57,16 @@ public class IbftRound { private final Subscribers observers; private final RoundState roundState; private final BlockCreator blockCreator; - private final ProtocolContext protocolContext; - private final BlockImporter blockImporter; + + /** The protocol context. */ + protected final ProtocolContext protocolContext; + + private final ProtocolSchedule protocolSchedule; private final NodeKey nodeKey; private final MessageFactory messageFactory; // used only to create stored local msgs private final IbftMessageTransmitter transmitter; private final BftExtraDataCodec bftExtraDataCodec; + private final BlockHeader parentHeader; /** * Instantiates a new Ibft round. @@ -69,34 +74,37 @@ public class IbftRound { * @param roundState the round state * @param blockCreator the block creator * @param protocolContext the protocol context - * @param blockImporter the block importer + * @param protocolSchedule the protocol schedule * @param observers the observers * @param nodeKey the node key * @param messageFactory the message factory * @param transmitter the transmitter * @param roundTimer the round timer * @param bftExtraDataCodec the bft extra data codec + * @param parentHeader the parent header */ public IbftRound( final RoundState roundState, final BlockCreator blockCreator, final ProtocolContext protocolContext, - final BlockImporter blockImporter, + final ProtocolSchedule protocolSchedule, final Subscribers observers, final NodeKey nodeKey, final MessageFactory messageFactory, final IbftMessageTransmitter transmitter, final RoundTimer roundTimer, - final BftExtraDataCodec bftExtraDataCodec) { + final BftExtraDataCodec bftExtraDataCodec, + final BlockHeader parentHeader) { this.roundState = roundState; this.blockCreator = blockCreator; this.protocolContext = protocolContext; - this.blockImporter = blockImporter; + this.protocolSchedule = protocolSchedule; this.observers = observers; this.nodeKey = nodeKey; this.messageFactory = messageFactory; this.transmitter = transmitter; this.bftExtraDataCodec = bftExtraDataCodec; + this.parentHeader = parentHeader; roundTimer.startTimer(getRoundIdentifier()); } @@ -115,7 +123,8 @@ public ConsensusRoundIdentifier getRoundIdentifier() { * @param headerTimeStampSeconds the header time stamp seconds */ public void createAndSendProposalMessage(final long headerTimeStampSeconds) { - final Block block = blockCreator.createBlock(headerTimeStampSeconds).getBlock(); + final Block block = + blockCreator.createBlock(headerTimeStampSeconds, this.parentHeader).getBlock(); final BftExtraData extraData = bftExtraDataCodec.decode(block.getHeader()); LOG.debug("Creating proposed block. round={}", roundState.getRoundIdentifier()); LOG.trace( @@ -138,7 +147,7 @@ public void startRoundWith( final Block blockToPublish; if (!bestBlockFromRoundChange.isPresent()) { LOG.debug("Sending proposal with new block. round={}", roundState.getRoundIdentifier()); - blockToPublish = blockCreator.createBlock(headerTimestamp).getBlock(); + blockToPublish = blockCreator.createBlock(headerTimestamp, this.parentHeader).getBlock(); } else { LOG.debug( "Sending proposal from PreparedCertificate. round={}", roundState.getRoundIdentifier()); @@ -312,6 +321,8 @@ private void importBlockToChain() { blockToImport.getHash()); } LOG.trace("Importing block with extraData={}", extraData); + final BlockImporter blockImporter = + protocolSchedule.getByBlockHeader(blockToImport.getHeader()).getBlockImporter(); final BlockImportResult result = blockImporter.importBlock(protocolContext, blockToImport, HeaderValidationMode.FULL); if (!result.isImported()) { diff --git a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRoundFactory.java b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRoundFactory.java index ea416ba7bb0..489e23b58a4 100644 --- a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRoundFactory.java +++ b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRoundFactory.java @@ -99,9 +99,8 @@ public IbftRound createNewRound(final BlockHeader parentHeader, final int round) */ public IbftRound createNewRoundWithState( final BlockHeader parentHeader, final RoundState roundState) { - final ConsensusRoundIdentifier roundIdentifier = roundState.getRoundIdentifier(); final BlockCreator blockCreator = - blockCreatorFactory.create(parentHeader, roundIdentifier.getRoundNumber()); + blockCreatorFactory.create(roundState.getRoundIdentifier().getRoundNumber()); final IbftMessageTransmitter messageTransmitter = new IbftMessageTransmitter(messageFactory, finalState.getValidatorMulticaster()); @@ -110,12 +109,13 @@ public IbftRound createNewRoundWithState( roundState, blockCreator, protocolContext, - protocolSchedule.getByBlockNumber(roundIdentifier.getSequenceNumber()).getBlockImporter(), + protocolSchedule, minedBlockObservers, finalState.getNodeKey(), messageFactory, messageTransmitter, finalState.getRoundTimer(), - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); } } diff --git a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/validation/MessageValidator.java b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/validation/MessageValidator.java index 72fab927090..1d7b3da9d61 100644 --- a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/validation/MessageValidator.java +++ b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/validation/MessageValidator.java @@ -25,6 +25,7 @@ import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import java.util.Optional; @@ -38,7 +39,7 @@ public class MessageValidator { private final SignedDataValidator signedDataValidator; private final ProposalBlockConsistencyValidator proposalConsistencyValidator; - private final BlockValidator blockValidator; + private final ProtocolSchedule protocolSchedule; private final ProtocolContext protocolContext; private final RoundChangeCertificateValidator roundChangeCertificateValidator; @@ -47,19 +48,19 @@ public class MessageValidator { * * @param signedDataValidator the signed data validator * @param proposalConsistencyValidator the proposal consistency validator - * @param blockValidator the block validator + * @param protocolSchedule the protocol schedule * @param protocolContext the protocol context * @param roundChangeCertificateValidator the round change certificate validator */ public MessageValidator( final SignedDataValidator signedDataValidator, final ProposalBlockConsistencyValidator proposalConsistencyValidator, - final BlockValidator blockValidator, final ProtocolContext protocolContext, + final ProtocolSchedule protocolSchedule, final RoundChangeCertificateValidator roundChangeCertificateValidator) { this.signedDataValidator = signedDataValidator; this.proposalConsistencyValidator = proposalConsistencyValidator; - this.blockValidator = blockValidator; + this.protocolSchedule = protocolSchedule; this.protocolContext = protocolContext; this.roundChangeCertificateValidator = roundChangeCertificateValidator; } @@ -77,7 +78,9 @@ public boolean validateProposal(final Proposal msg) { return false; } - if (!validateBlock(msg.getBlock())) { + // We want to validate the block but not persist it yet as it's just a proposal. If it turns + // out to be an accepted block it will be persisted at block import time + if (!validateBlockWithoutPersisting(msg.getBlock())) { return false; } @@ -92,10 +95,14 @@ public boolean validateProposal(final Proposal msg) { msg.getSignedPayload(), msg.getBlock(), blockInterface); } - private boolean validateBlock(final Block block) { + private boolean validateBlockWithoutPersisting(final Block block) { + + final BlockValidator blockValidator = + protocolSchedule.getByBlockHeader(block.getHeader()).getBlockValidator(); + final var validationResult = blockValidator.validateAndProcessBlock( - protocolContext, block, HeaderValidationMode.LIGHT, HeaderValidationMode.FULL); + protocolContext, block, HeaderValidationMode.LIGHT, HeaderValidationMode.FULL, false); if (validationResult.isFailed()) { LOG.info( diff --git a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/validation/MessageValidatorFactory.java b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/validation/MessageValidatorFactory.java index 75aa52540f9..308058bec11 100644 --- a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/validation/MessageValidatorFactory.java +++ b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/validation/MessageValidatorFactory.java @@ -22,7 +22,6 @@ import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier; import org.hyperledger.besu.consensus.common.bft.blockcreation.ProposerSelector; import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.ethereum.BlockValidator; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.core.BlockHeader; @@ -55,18 +54,41 @@ public MessageValidatorFactory( this.bftExtraDataCodec = bftExtraDataCodec; } - private Collection
getValidatorsAfterBlock(final BlockHeader parentHeader) { + /** + * Get the list of validators that are applicable after the given block + * + * @param protocolContext the protocol context + * @param parentHeader the parent header + * @return the list of validators + */ + public static Collection
getValidatorsAfterBlock( + final ProtocolContext protocolContext, final BlockHeader parentHeader) { return protocolContext .getConsensusContext(BftContext.class) .getValidatorProvider() .getValidatorsAfterBlock(parentHeader); } + /** + * Get the list of validators that are applicable for the given block + * + * @param protocolContext the protocol context + * @param parentHeader the parent header + * @return the list of validators + */ + public static Collection
getValidatorsForBlock( + final ProtocolContext protocolContext, final BlockHeader parentHeader) { + return protocolContext + .getConsensusContext(BftContext.class) + .getValidatorProvider() + .getValidatorsForBlock(parentHeader); + } + private SignedDataValidator createSignedDataValidator( final ConsensusRoundIdentifier roundIdentifier, final BlockHeader parentHeader) { return new SignedDataValidator( - getValidatorsAfterBlock(parentHeader), + getValidatorsAfterBlock(protocolContext, parentHeader), proposerSelector.selectProposerForRound(roundIdentifier), roundIdentifier); } @@ -80,9 +102,7 @@ private SignedDataValidator createSignedDataValidator( */ public MessageValidator createMessageValidator( final ConsensusRoundIdentifier roundIdentifier, final BlockHeader parentHeader) { - final BlockValidator blockValidator = - protocolSchedule.getByBlockNumber(roundIdentifier.getSequenceNumber()).getBlockValidator(); - final Collection
validators = getValidatorsAfterBlock(parentHeader); + final Collection
validators = getValidatorsAfterBlock(protocolContext, parentHeader); final BftBlockInterface bftBlockInterface = protocolContext.getConsensusContext(BftContext.class).getBlockInterface(); @@ -90,8 +110,8 @@ public MessageValidator createMessageValidator( return new MessageValidator( createSignedDataValidator(roundIdentifier, parentHeader), new ProposalBlockConsistencyValidator(), - blockValidator, protocolContext, + protocolSchedule, new RoundChangeCertificateValidator( validators, (ri) -> createSignedDataValidator(ri, parentHeader), @@ -109,7 +129,7 @@ public MessageValidator createMessageValidator( */ public RoundChangeMessageValidator createRoundChangeMessageValidator( final long chainHeight, final BlockHeader parentHeader) { - final Collection
validators = getValidatorsAfterBlock(parentHeader); + final Collection
validators = getValidatorsAfterBlock(protocolContext, parentHeader); final BftBlockInterface bftBlockInterface = protocolContext.getConsensusContext(BftContext.class).getBlockInterface(); diff --git a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/validation/ProposalBlockConsistencyValidator.java b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/validation/ProposalBlockConsistencyValidator.java index 845b509117d..35422c6426a 100644 --- a/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/validation/ProposalBlockConsistencyValidator.java +++ b/consensus/ibft/src/main/java/org/hyperledger/besu/consensus/ibft/validation/ProposalBlockConsistencyValidator.java @@ -30,6 +30,9 @@ public class ProposalBlockConsistencyValidator { private static final Logger LOG = LoggerFactory.getLogger(ProposalBlockConsistencyValidator.class); + /** Default constructor. */ + public ProposalBlockConsistencyValidator() {} + /** * Validate proposal matches block. * diff --git a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/BftBlockHashingTest.java b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/BftBlockHashingTest.java index 7d5362ff7c6..1b821c78c96 100644 --- a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/BftBlockHashingTest.java +++ b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/BftBlockHashingTest.java @@ -15,7 +15,7 @@ package org.hyperledger.besu.consensus.ibft; import static java.util.Collections.emptyList; -import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import org.hyperledger.besu.consensus.common.bft.BftBlockHashing; import org.hyperledger.besu.consensus.common.bft.BftBlockHeaderFunctions; diff --git a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/IbftBlockHeaderUtils.java b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/IbftBlockHeaderUtils.java index dff11c1ffeb..da32e9b16fd 100644 --- a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/IbftBlockHeaderUtils.java +++ b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/IbftBlockHeaderUtils.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/IbftBlockHeaderValidationRulesetFactoryTest.java b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/IbftBlockHeaderValidationRulesetFactoryTest.java index e02ad9236c8..a7deb9e1973 100644 --- a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/IbftBlockHeaderValidationRulesetFactoryTest.java +++ b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/IbftBlockHeaderValidationRulesetFactoryTest.java @@ -29,6 +29,7 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.Difficulty; @@ -51,7 +52,7 @@ private ProtocolContext protocolContext(final Collection
validators) { null, null, setupContextWithBftExtraDataEncoder(validators, new IbftExtraDataCodec()), - Optional.empty()); + new BadBlockManager()); } @Test diff --git a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/IbftForksSchedulesFactoryTest.java b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/IbftForksSchedulesFactoryTest.java index f18c5b29d3e..235ba89d323 100644 --- a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/IbftForksSchedulesFactoryTest.java +++ b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/IbftForksSchedulesFactoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/IbftProtocolScheduleTest.java b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/IbftProtocolScheduleTest.java index d2125d0e1a0..e5551ff3f31 100644 --- a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/IbftProtocolScheduleTest.java +++ b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/IbftProtocolScheduleTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -37,17 +37,19 @@ import org.hyperledger.besu.cryptoservices.NodeKeyUtils; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.MilestoneStreamingProtocolSchedule; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.Util; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import java.math.BigInteger; import java.util.Collection; import java.util.List; -import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -100,7 +102,11 @@ private BftProtocolSchedule createProtocolSchedule( PrivacyParameters.DEFAULT, false, bftExtraDataCodec, - EvmConfiguration.DEFAULT); + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); } private boolean validateHeader( @@ -110,7 +116,7 @@ private boolean validateHeader( final BlockHeader blockHeader, final int block) { return schedule - .getByBlockNumber(block) + .getByBlockNumberOrTimestamp(block, blockHeader.getTimestamp()) .getBlockHeaderValidator() .validateHeader( blockHeader, parentHeader, protocolContext(validators), HeaderValidationMode.LIGHT); @@ -121,6 +127,6 @@ private ProtocolContext protocolContext(final Collection
validators) { null, null, setupContextWithBftExtraDataEncoder(BftContext.class, validators, bftExtraDataCodec), - Optional.empty()); + new BadBlockManager()); } } diff --git a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/BftBlockCreatorTest.java b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/BftBlockCreatorTest.java index 3e884139f50..8b8406b6385 100644 --- a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/BftBlockCreatorTest.java +++ b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/blockcreation/BftBlockCreatorTest.java @@ -36,6 +36,7 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.AddressHelpers; import org.hyperledger.besu.ethereum.core.Block; @@ -46,6 +47,7 @@ import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -118,13 +120,17 @@ public BlockHeaderValidator.Builder createBlockHeaderRuleset( PrivacyParameters.DEFAULT, false, bftExtraDataEncoder, - EvmConfiguration.DEFAULT); + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); final ProtocolContext protContext = new ProtocolContext( blockchain, createInMemoryWorldStateArchive(), setupContextWithBftExtraDataEncoder(initialValidatorList, bftExtraDataEncoder), - Optional.empty()); + new BadBlockManager()); final TransactionPoolConfiguration poolConf = ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(1).build(); @@ -148,7 +154,7 @@ public BlockHeaderValidator.Builder createBlockHeaderRuleset( ethContext, new TransactionPoolMetrics(metricsSystem), poolConf, - null); + new BlobCache()); transactionPool.setEnabled(); @@ -185,12 +191,12 @@ public BlockHeaderValidator.Builder createBlockHeaderRuleset( transactionPool, protContext, protocolSchedule, - parentHeader, bftExtraDataEncoder, new DeterministicEthScheduler()); final int secondsBetweenBlocks = 1; - final Block block = blockCreator.createBlock(parentHeader.getTimestamp() + 1).getBlock(); + final Block block = + blockCreator.createBlock(parentHeader.getTimestamp() + 1, parentHeader).getBlock(); final BlockHeaderValidator rules = IbftBlockHeaderValidationRulesetFactory.blockHeaderValidator( diff --git a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftDiscardValidatorVoteTest.java b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftDiscardValidatorVoteTest.java index da0a35ab2fe..7a25ab67b85 100644 --- a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftDiscardValidatorVoteTest.java +++ b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftDiscardValidatorVoteTest.java @@ -56,14 +56,14 @@ public void returnsCorrectMethodName() { public void exceptionWhenNoParamsSupplied() { assertThatThrownBy(() -> method.response(requestWithParams())) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 0"); + .hasMessage("Invalid validator address parameter (index 0)"); } @Test public void exceptionWhenInvalidAddressParameterSupplied() { assertThatThrownBy(() -> method.response(requestWithParams("InvalidAddress"))) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Invalid json rpc parameter at index 0"); + .hasMessageContaining("Invalid validator address parameter (index 0)"); } @Test diff --git a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftGetPendingVotesTest.java b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftGetPendingVotesTest.java index ddede6aefe4..b3d8cd43cca 100644 --- a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftGetPendingVotesTest.java +++ b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftGetPendingVotesTest.java @@ -14,7 +14,7 @@ */ package org.hyperledger.besu.consensus.ibft.jsonrpc.methods; -import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import org.hyperledger.besu.consensus.common.jsonrpc.AbstractVoteProposerMethod; import org.hyperledger.besu.consensus.common.jsonrpc.AbstractVoteProposerMethodTest; diff --git a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftGetSignerMetricsTest.java b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftGetSignerMetricsTest.java index bb6d44aca60..4eb8fbe12bd 100644 --- a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftGetSignerMetricsTest.java +++ b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftGetSignerMetricsTest.java @@ -75,14 +75,14 @@ public void returnsCorrectMethodName() { public void exceptionWhenInvalidStartBlockSupplied() { assertThatThrownBy(() -> method.response(requestWithParams("INVALID"))) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Invalid json rpc parameter at index 0"); + .hasMessageContaining("Invalid start block parameter (index 0)"); } @Test public void exceptionWhenInvalidEndBlockSupplied() { assertThatThrownBy(() -> method.response(requestWithParams("1", "INVALID"))) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Invalid json rpc parameter at index 1"); + .hasMessageContaining("Invalid end block parameter (index 1)"); } @Test diff --git a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftProposeValidatorVoteTest.java b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftProposeValidatorVoteTest.java index d667bb173b4..dfa6f7b6e59 100644 --- a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftProposeValidatorVoteTest.java +++ b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/jsonrpc/methods/IbftProposeValidatorVoteTest.java @@ -56,28 +56,28 @@ public void returnsCorrectMethodName() { public void exceptionWhenNoParamsSupplied() { assertThatThrownBy(() -> method.response(requestWithParams())) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 0"); + .hasMessage("Invalid address parameter (index 0)"); } @Test public void exceptionWhenNoAuthSupplied() { assertThatThrownBy(() -> method.response(requestWithParams(Address.fromHexString("1")))) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 1"); + .hasMessage("Invalid vote type parameter (index 1)"); } @Test public void exceptionWhenNoAddressSupplied() { assertThatThrownBy(() -> method.response(requestWithParams("true"))) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Invalid json rpc parameter at index 0"); + .hasMessageContaining("Invalid address parameter (index 0)"); } @Test public void exceptionWhenInvalidBoolParameterSupplied() { assertThatThrownBy(() -> method.response(requestWithParams(Address.fromHexString("1"), "c"))) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Invalid json rpc parameter at index 1"); + .hasMessageContaining("Invalid vote type parameter (index 1)"); } @Test diff --git a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java index a421774420c..b9f57debb6b 100644 --- a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java +++ b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java @@ -31,8 +31,10 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import org.hyperledger.besu.config.StubGenesisConfigOptions; import org.hyperledger.besu.consensus.common.bft.BftExtraData; import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec; +import org.hyperledger.besu.consensus.common.bft.BftProtocolSchedule; import org.hyperledger.besu.consensus.common.bft.BlockTimer; import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier; import org.hyperledger.besu.consensus.common.bft.RoundTimer; @@ -59,15 +61,25 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.blockcreation.BlockCreationTiming; import org.hyperledger.besu.ethereum.blockcreation.BlockCreator.BlockCreationResult; import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; +import org.hyperledger.besu.ethereum.chain.DefaultBlockchain; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; -import org.hyperledger.besu.ethereum.core.BlockImporter; +import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.Util; +import org.hyperledger.besu.ethereum.mainnet.DefaultProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.util.Subscribers; import java.math.BigInteger; @@ -75,6 +87,7 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.function.Function; import com.google.common.collect.Lists; import org.apache.tuweni.bytes.Bytes; @@ -101,11 +114,12 @@ public class IbftBlockHeightManagerTest { @Mock private Clock clock; @Mock private MessageValidatorFactory messageValidatorFactory; @Mock private BftBlockCreator blockCreator; - @Mock private BlockImporter blockImporter; @Mock private BlockTimer blockTimer; + @Mock private DefaultBlockchain blockchain; @Mock private RoundTimer roundTimer; @Mock private FutureRoundProposalMessageValidator futureRoundProposalMessageValidator; @Mock private ValidatorMulticaster validatorMulticaster; + @Mock private BlockHeader parentHeader; @Captor private ArgumentCaptor sentMessageArgCaptor; @@ -145,8 +159,10 @@ public void setup() { lenient().when(finalState.getQuorum()).thenReturn(3); when(finalState.getValidatorMulticaster()).thenReturn(validatorMulticaster); lenient() - .when(blockCreator.createBlock(anyLong())) - .thenReturn(new BlockCreationResult(createdBlock, new TransactionSelectionResults())); + .when(blockCreator.createBlock(anyLong(), any())) + .thenReturn( + new BlockCreationResult( + createdBlock, new TransactionSelectionResults(), new BlockCreationTiming())); lenient() .when(futureRoundProposalMessageValidator.validateProposalMessage(any())) @@ -158,7 +174,25 @@ public void setup() { .thenReturn(messageValidator); protocolContext = - new ProtocolContext(null, null, setupContextWithValidators(validators), Optional.empty()); + new ProtocolContext( + blockchain, null, setupContextWithValidators(validators), new BadBlockManager()); + + final ProtocolScheduleBuilder protocolScheduleBuilder = + new ProtocolScheduleBuilder( + new StubGenesisConfigOptions(), + BigInteger.ONE, + ProtocolSpecAdapters.create(0, Function.identity()), + new PrivacyParameters(), + false, + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); + + ProtocolSchedule protocolSchedule = + new BftProtocolSchedule( + (DefaultProtocolSchedule) protocolScheduleBuilder.createProtocolSchedule()); // Ensure the created IbftRound has the valid ConsensusRoundIdentifier; when(roundFactory.createNewRound(any(), anyInt())) @@ -171,13 +205,14 @@ public void setup() { createdRoundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, Subscribers.create(), nodeKey, messageFactory, messageTransmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); }); lenient() @@ -189,13 +224,14 @@ public void setup() { providedRoundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, Subscribers.create(), nodeKey, messageFactory, messageTransmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); }); } diff --git a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRoundTest.java b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRoundTest.java index 008d69330fb..f4ee852a04f 100644 --- a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRoundTest.java +++ b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRoundTest.java @@ -32,6 +32,7 @@ import org.hyperledger.besu.consensus.common.bft.BftBlockHashing; import org.hyperledger.besu.consensus.common.bft.BftExtraData; import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec; +import org.hyperledger.besu.consensus.common.bft.BftProtocolSchedule; import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier; import org.hyperledger.besu.consensus.common.bft.RoundTimer; import org.hyperledger.besu.consensus.common.bft.blockcreation.BftBlockCreator; @@ -46,8 +47,10 @@ import org.hyperledger.besu.cryptoservices.NodeKeyUtils; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.blockcreation.BlockCreationTiming; import org.hyperledger.besu.ethereum.blockcreation.BlockCreator.BlockCreationResult; import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MinedBlockObserver; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; @@ -56,6 +59,8 @@ import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.BlockImporter; import org.hyperledger.besu.ethereum.mainnet.BlockImportResult; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.WithdrawalsValidator; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.plugin.services.securitymodule.SecurityModuleException; import org.hyperledger.besu.util.Subscribers; @@ -83,14 +88,17 @@ public class IbftRoundTest { private final BftExtraDataCodec bftExtraDataCodec = new IbftExtraDataCodec(); private ProtocolContext protocolContext; + @Mock private BftProtocolSchedule protocolSchedule; @Mock private MutableBlockchain blockChain; @Mock private WorldStateArchive worldStateArchive; - @Mock private BlockImporter blockImporter; @Mock private IbftMessageTransmitter transmitter; @Mock private MinedBlockObserver minedBlockObserver; @Mock private BftBlockCreator blockCreator; @Mock private MessageValidator messageValidator; @Mock private RoundTimer roundTimer; + @Mock private ProtocolSpec protocolSpec; + @Mock private BlockImporter blockImporter; + @Mock private BlockHeader parentHeader; @Captor private ArgumentCaptor blockCaptor; @@ -108,7 +116,7 @@ public void setup() { blockChain, worldStateArchive, setupContextWithBftExtraDataEncoder(emptyList(), new IbftExtraDataCodec()), - Optional.empty()); + new BadBlockManager()); lenient().when(messageValidator.validateProposal(any())).thenReturn(true); lenient().when(messageValidator.validatePrepare(any())).thenReturn(true); @@ -124,12 +132,17 @@ public void setup() { proposedBlock = new Block(header, new BlockBody(emptyList(), emptyList())); lenient() - .when(blockCreator.createBlock(anyLong())) - .thenReturn(new BlockCreationResult(proposedBlock, new TransactionSelectionResults())); + .when(blockCreator.createBlock(anyLong(), any())) + .thenReturn( + new BlockCreationResult( + proposedBlock, new TransactionSelectionResults(), new BlockCreationTiming())); + + lenient().when(protocolSpec.getBlockImporter()).thenReturn(blockImporter); + lenient().when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); lenient() .when(blockImporter.importBlock(any(), any(), any())) - .thenReturn(new BlockImportResult(true)); + .thenReturn(new BlockImportResult(BlockImportResult.BlockImportStatus.IMPORTED)); subscribers.subscribe(minedBlockObserver); } @@ -141,13 +154,14 @@ public void onConstructionRoundTimerIsStarted() { roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, messageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); verify(roundTimer, times(1)).startTimer(roundIdentifier); } @@ -159,13 +173,14 @@ public void onReceptionOfValidProposalSendsAPrepareToNetworkPeers() { roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, messageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); round.handleProposalMessage( messageFactory.createProposal(roundIdentifier, proposedBlock, Optional.empty())); @@ -181,13 +196,14 @@ public void sendsAProposalWhenRequested() { roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, messageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); round.createAndSendProposalMessage(15); verify(transmitter, times(1)) @@ -204,13 +220,14 @@ public void singleValidatorImportBlocksImmediatelyOnProposalCreation() { roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, messageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); round.createAndSendProposalMessage(15); verify(transmitter, times(1)) .multicastProposal(roundIdentifier, proposedBlock, Optional.empty()); @@ -227,13 +244,14 @@ public void twoValidatorNetworkSendsPrepareOnProposalReceptionThenSendsCommitOnC roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, messageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); final Hash commitSealHash = new BftBlockHashing(new IbftExtraDataCodec()) @@ -265,19 +283,23 @@ public void twoValidatorNetworkSendsPrepareOnProposalReceptionThenSendsCommitOnC @Test public void localNodeProposesToNetworkOfTwoValidatorsImportsOnReceptionOfCommitFromPeer() { + lenient() + .when(protocolSpec.getWithdrawalsValidator()) + .thenReturn(new WithdrawalsValidator.AllowedWithdrawals()); final RoundState roundState = new RoundState(roundIdentifier, 2, messageValidator); final IbftRound round = new IbftRound( roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, messageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); final Hash commitSealHash = new BftBlockHashing(new IbftExtraDataCodec()) @@ -308,13 +330,14 @@ public void aProposalWithAnewBlockIsSentUponReceptionOfARoundChangeWithNoCertifi roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, messageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); final RoundChangeCertificate roundChangeCertificate = new RoundChangeCertificate(emptyList()); @@ -332,13 +355,14 @@ public void aProposalMessageWithTheSameBlockIsSentUponReceptionOfARoundChangeWit roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, messageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); final RoundChangeArtifacts roundChangeArtifacts = RoundChangeArtifacts.create( @@ -379,13 +403,14 @@ public void creatingNewBlockFromEmptyPreparedCertificateUpdatesInternalState() { roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, messageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); final RoundChangeArtifacts roundChangeArtifacts = RoundChangeArtifacts.create( @@ -413,13 +438,14 @@ public void creatingNewBlockNotifiesBlockMiningObservers() { roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, messageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); round.createAndSendProposalMessage(15); verify(minedBlockObserver).blockMined(any()); } @@ -434,13 +460,14 @@ public void blockIsOnlyImportedOnceWhenCommitsAreReceivedBeforeProposal() { roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, messageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); round.handleCommitMessage( messageFactory.createCommit(roundIdentifier, proposedBlock.getHash(), remoteCommitSeal)); @@ -460,13 +487,14 @@ public void blockIsImportedOnlyOnceIfQuorumCommitsAreReceivedPriorToProposal() { roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, messageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); round.handleCommitMessage( messageFactory.createCommit(roundIdentifier, proposedBlock.getHash(), remoteCommitSeal)); @@ -490,13 +518,14 @@ public void exceptionDuringNodeKeySigningDoesNotEscape() { roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, throwingNodeKey, throwingMessageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); round.handleProposalMessage( messageFactory.createProposal(roundIdentifier, proposedBlock, Optional.empty())); diff --git a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/validation/MessageValidatorTest.java b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/validation/MessageValidatorTest.java index ad4ce392797..2352642b87b 100644 --- a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/validation/MessageValidatorTest.java +++ b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/validation/MessageValidatorTest.java @@ -17,6 +17,7 @@ import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; @@ -26,6 +27,7 @@ import static org.mockito.Mockito.when; import org.hyperledger.besu.consensus.common.bft.BftContext; +import org.hyperledger.besu.consensus.common.bft.BftProtocolSchedule; import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier; import org.hyperledger.besu.consensus.common.bft.ProposedBlockHelpers; import org.hyperledger.besu.consensus.ibft.messagewrappers.Commit; @@ -39,9 +41,11 @@ import org.hyperledger.besu.ethereum.BlockProcessingResult; import org.hyperledger.besu.ethereum.BlockValidator; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.AddressHelpers; import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import java.util.List; @@ -66,6 +70,8 @@ public class MessageValidatorTest { private final ProposalBlockConsistencyValidator proposalBlockConsistencyValidator = mock(ProposalBlockConsistencyValidator.class); + @Mock private BftProtocolSchedule protocolSchedule; + @Mock private ProtocolSpec protocolSpec; @Mock private BlockValidator blockValidator; private ProtocolContext protocolContext; private final RoundChangeCertificateValidator roundChangeCertificateValidator = @@ -99,9 +105,15 @@ public void setup() { mock(MutableBlockchain.class), mock(WorldStateArchive.class), mockBftCtx, - Optional.empty()); + new BadBlockManager()); - when(blockValidator.validateAndProcessBlock(any(), any(), any(), any())) + lenient() + .when(protocolSchedule.getByBlockNumberOrTimestamp(anyLong(), anyLong())) + .thenReturn(protocolSpec); + + when(protocolSpec.getBlockValidator()).thenReturn(blockValidator); + when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); + when(blockValidator.validateAndProcessBlock(any(), any(), any(), any(), eq(false))) .thenReturn(new BlockProcessingResult(Optional.empty())); when(roundChangeCertificateValidator.validateProposalMessageMatchesLatestPrepareCertificate( @@ -115,8 +127,8 @@ public void setup() { new MessageValidator( signedDataValidator, proposalBlockConsistencyValidator, - blockValidator, protocolContext, + protocolSchedule, roundChangeCertificateValidator); } @@ -156,7 +168,7 @@ public void ifProposalConsistencyChecksFailProposalIsIllegal() { @Test public void blockValidationFailureFailsValidation() { - when(blockValidator.validateAndProcessBlock(any(), any(), any(), any())) + when(blockValidator.validateAndProcessBlock(any(), any(), any(), any(), eq(false))) .thenReturn(new BlockProcessingResult("Failed")); final Proposal proposalMsg = diff --git a/consensus/merge/build.gradle b/consensus/merge/build.gradle index 9476f09d9d7..e1b98e21e2e 100644 --- a/consensus/merge/build.gradle +++ b/consensus/merge/build.gradle @@ -22,7 +22,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/MergeContext.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/MergeContext.java index 9db6a97a0ca..2554a4537fa 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/MergeContext.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/MergeContext.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -18,7 +18,6 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ConsensusContext; import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.BlockWithReceipts; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; @@ -167,30 +166,7 @@ void fireNewUnverifiedForkchoiceEvent( * @param payloadId the payload identifier * @return the optional block with receipts */ - Optional retrieveBlockById(final PayloadIdentifier payloadId); - - /** - * Sets is chain pruning enabled. - * - * @param isChainPruningEnabled whether chain pruning is enabled - */ - default void setIsChainPruningEnabled(final boolean isChainPruningEnabled) {} - - /** - * Is chain pruning enabled. - * - * @return the boolean - */ - default boolean isChainPruningEnabled() { - return false; - } - - /** - * Is checkpoint post merge sync. - * - * @return the boolean - */ - boolean isCheckpointPostMergeSync(); + Optional retrievePayloadById(final PayloadIdentifier payloadId); /** * Is configured for a post-merge from genesis. diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/MergeProtocolSchedule.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/MergeProtocolSchedule.java index d037590dac2..abbc3b130aa 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/MergeProtocolSchedule.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/MergeProtocolSchedule.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -16,6 +16,8 @@ import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.mainnet.BlockHeaderValidator; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; @@ -25,6 +27,7 @@ import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.evm.MainnetEVMs; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.plugin.services.MetricsSystem; import java.math.BigInteger; import java.util.HashMap; @@ -37,16 +40,34 @@ public class MergeProtocolSchedule { private static final BigInteger DEFAULT_CHAIN_ID = BigInteger.valueOf(1); + /** Default constructor. */ + MergeProtocolSchedule() {} + /** * Create protocol schedule. * * @param config the config * @param isRevertReasonEnabled the is revert reason enabled + * @param miningParameters the mining parameters + * @param badBlockManager the cache to use to keep invalid blocks + * @param isParallelTxProcessingEnabled indicates whether parallel transaction is enabled. * @return the protocol schedule */ public static ProtocolSchedule create( - final GenesisConfigOptions config, final boolean isRevertReasonEnabled) { - return create(config, PrivacyParameters.DEFAULT, isRevertReasonEnabled); + final GenesisConfigOptions config, + final boolean isRevertReasonEnabled, + final MiningParameters miningParameters, + final BadBlockManager badBlockManager, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { + return create( + config, + PrivacyParameters.DEFAULT, + isRevertReasonEnabled, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem); } /** @@ -55,12 +76,19 @@ public static ProtocolSchedule create( * @param config the config * @param privacyParameters the privacy parameters * @param isRevertReasonEnabled the is revert reason enabled + * @param miningParameters the mining parameters + * @param badBlockManager the cache to use to keep invalid blocks + * @param isParallelTxProcessingEnabled indicates whether parallel transaction is enabled. * @return the protocol schedule */ public static ProtocolSchedule create( final GenesisConfigOptions config, final PrivacyParameters privacyParameters, - final boolean isRevertReasonEnabled) { + final boolean isRevertReasonEnabled, + final MiningParameters miningParameters, + final BadBlockManager badBlockManager, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { Map> postMergeModifications = new HashMap<>(); @@ -77,7 +105,11 @@ public static ProtocolSchedule create( new ProtocolSpecAdapters(postMergeModifications), privacyParameters, isRevertReasonEnabled, - EvmConfiguration.DEFAULT) + EvmConfiguration.DEFAULT, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem) .createProtocolSchedule(); } diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/MergeValidationRulesetFactory.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/MergeValidationRulesetFactory.java index 08dc99f2d63..4a028d2be9e 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/MergeValidationRulesetFactory.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/MergeValidationRulesetFactory.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -43,6 +43,9 @@ public class MergeValidationRulesetFactory { private static final EpochCalculator preMergeCalculator = new EpochCalculator.DefaultEpochCalculator(); + /** Default constructor. */ + private MergeValidationRulesetFactory() {} + /** * Creates a set of rules which when executed will determine if a given block header is valid with * respect to its parent (or chain). diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/PayloadWrapper.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/PayloadWrapper.java index 4697f8d9b8c..4a8588f84ef 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/PayloadWrapper.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/PayloadWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,8 +15,53 @@ package org.hyperledger.besu.consensus.merge; import org.hyperledger.besu.consensus.merge.blockcreation.PayloadIdentifier; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.BlockValueCalculator; import org.hyperledger.besu.ethereum.core.BlockWithReceipts; /** Wrapper for payload plus extra info. */ -public record PayloadWrapper( - PayloadIdentifier payloadIdentifier, BlockWithReceipts blockWithReceipts) {} +public class PayloadWrapper { + private final PayloadIdentifier payloadIdentifier; + private final BlockWithReceipts blockWithReceipts; + private final Wei blockValue; + + /** + * Construct a wrapper with the following fields. + * + * @param payloadIdentifier Payload identifier + * @param blockWithReceipts Block with receipts + */ + public PayloadWrapper( + final PayloadIdentifier payloadIdentifier, final BlockWithReceipts blockWithReceipts) { + this.blockWithReceipts = blockWithReceipts; + this.payloadIdentifier = payloadIdentifier; + this.blockValue = BlockValueCalculator.calculateBlockValue(blockWithReceipts); + } + + /** + * Get the block value + * + * @return block value in Wei + */ + public Wei blockValue() { + return blockValue; + } + + /** + * Get this payload identifier + * + * @return payload identifier + */ + public PayloadIdentifier payloadIdentifier() { + return payloadIdentifier; + } + + /** + * Get the block with receipts + * + * @return block with receipts + */ + public BlockWithReceipts blockWithReceipts() { + return blockWithReceipts; + } +} diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/PostMergeContext.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/PostMergeContext.java index 08985ed326b..1d7e4b76c62 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/PostMergeContext.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/PostMergeContext.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -16,12 +16,9 @@ import org.hyperledger.besu.consensus.merge.blockcreation.PayloadIdentifier; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.ConsensusContext; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.BlockValueCalculator; -import org.hyperledger.besu.ethereum.core.BlockWithReceipts; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.util.Subscribers; @@ -29,7 +26,6 @@ import java.util.Comparator; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Collectors; import java.util.stream.Stream; import com.google.common.annotations.VisibleForTesting; @@ -40,17 +36,11 @@ /** The Post merge context. */ public class PostMergeContext implements MergeContext { private static final Logger LOG = LoggerFactory.getLogger(PostMergeContext.class); + /** The Max blocks in progress. */ static final int MAX_BLOCKS_IN_PROGRESS = 12; private static final AtomicReference singleton = new AtomicReference<>(); - - private static final Comparator compareByGasUsedDesc = - Comparator.comparingLong( - (BlockWithReceipts blockWithReceipts) -> - blockWithReceipts.getBlock().getHeader().getGasUsed()) - .reversed(); - private final AtomicReference syncState; private final AtomicReference terminalTotalDifficulty; // initial postMerge state is indeterminate until it is set: @@ -69,14 +59,8 @@ public class PostMergeContext implements MergeContext { private final AtomicReference lastSafeBlock = new AtomicReference<>(); private final AtomicReference> terminalPoWBlock = new AtomicReference<>(Optional.empty()); - private final BlockValueCalculator blockValueCalculator = new BlockValueCalculator(); - private boolean isCheckpointPostMergeSync; private boolean isPostMergeAtGenesis; - // TODO: cleanup - isChainPruningEnabled will not be required after - // https://github.com/hyperledger/besu/pull/4703 is merged. - private boolean isChainPruningEnabled = false; - /** Instantiates a new Post merge context. */ @VisibleForTesting PostMergeContext() { @@ -92,7 +76,6 @@ public class PostMergeContext implements MergeContext { PostMergeContext(final Difficulty difficulty) { this.terminalTotalDifficulty = new AtomicReference<>(difficulty); this.syncState = new AtomicReference<>(); - this.isCheckpointPostMergeSync = false; } /** @@ -232,66 +215,65 @@ public boolean validateCandidateHead(final BlockHeader candidateHeader) { } @Override - public void putPayloadById(final PayloadWrapper payloadWrapper) { + public void putPayloadById(final PayloadWrapper newPayload) { + final var newBlockWithReceipts = newPayload.blockWithReceipts(); + final var newBlockValue = newPayload.blockValue(); + synchronized (blocksInProgress) { - final Optional maybeCurrBestBlock = - retrieveBlockById(payloadWrapper.payloadIdentifier()); + final Optional maybeCurrBestPayload = + retrievePayloadById(newPayload.payloadIdentifier()); - maybeCurrBestBlock.ifPresentOrElse( - currBestBlock -> { - if (compareByGasUsedDesc.compare(payloadWrapper.blockWithReceipts(), currBestBlock) - < 0) { + maybeCurrBestPayload.ifPresent( + currBestPayload -> { + if (newBlockValue.greaterThan(currBestPayload.blockValue())) { LOG.atDebug() - .setMessage("New proposal for payloadId {} {} is better than the previous one {}") - .addArgument(payloadWrapper.payloadIdentifier()) + .setMessage( + "New proposal for payloadId {} {} is better than the previous one {} by {}") + .addArgument(newPayload.payloadIdentifier()) + .addArgument(() -> logBlockProposal(newBlockWithReceipts.getBlock())) + .addArgument( + () -> logBlockProposal(currBestPayload.blockWithReceipts().getBlock())) .addArgument( - () -> logBlockProposal(payloadWrapper.blockWithReceipts().getBlock())) - .addArgument(() -> logBlockProposal(currBestBlock.getBlock())) + () -> + newBlockValue + .subtract(currBestPayload.blockValue()) + .toHumanReadableString()) .log(); + blocksInProgress.removeAll( - retrievePayloadsById(payloadWrapper.payloadIdentifier()) - .collect(Collectors.toUnmodifiableList())); - blocksInProgress.add( - new PayloadWrapper( - payloadWrapper.payloadIdentifier(), payloadWrapper.blockWithReceipts())); - logCurrentBestBlock(payloadWrapper.blockWithReceipts()); + streamPayloadsById(newPayload.payloadIdentifier()).toList()); + + logCurrentBestBlock(newPayload); } - }, - () -> - blocksInProgress.add( - new PayloadWrapper( - payloadWrapper.payloadIdentifier(), payloadWrapper.blockWithReceipts()))); + }); + blocksInProgress.add(newPayload); } } - private void logCurrentBestBlock(final BlockWithReceipts blockWithReceipts) { + private void logCurrentBestBlock(final PayloadWrapper payloadWrapper) { if (LOG.isDebugEnabled()) { - final Block block = blockWithReceipts.getBlock(); + final Block block = payloadWrapper.blockWithReceipts().getBlock(); final float gasUsedPerc = 100.0f * block.getHeader().getGasUsed() / block.getHeader().getGasLimit(); final int txsNum = block.getBody().getTransactions().size(); - final Wei reward = blockValueCalculator.calculateBlockValue(blockWithReceipts); LOG.debug( "Current best proposal for block {}: txs {}, gas used {}%, reward {}", - blockWithReceipts.getNumber(), + block.getHeader().getNumber(), txsNum, String.format("%1.2f", gasUsedPerc), - reward.toHumanReadableString()); + payloadWrapper.blockValue().toHumanReadableString()); } } @Override - public Optional retrieveBlockById(final PayloadIdentifier payloadId) { + public Optional retrievePayloadById(final PayloadIdentifier payloadId) { synchronized (blocksInProgress) { - return retrievePayloadsById(payloadId) - .map(payloadWrapper -> payloadWrapper.blockWithReceipts()) - .sorted(compareByGasUsedDesc) - .findFirst(); + return streamPayloadsById(payloadId).max(Comparator.comparing(PayloadWrapper::blockValue)); } } - private Stream retrievePayloadsById(final PayloadIdentifier payloadId) { + private Stream streamPayloadsById(final PayloadIdentifier payloadId) { return blocksInProgress.stream().filter(z -> z.payloadIdentifier().equals(payloadId)); } @@ -304,32 +286,6 @@ private String logBlockProposal(final Block block) { + block.getBody().getTransactions().size(); } - @Override - public void setIsChainPruningEnabled(final boolean isChainPruningEnabled) { - this.isChainPruningEnabled = isChainPruningEnabled; - } - - @Override - public boolean isChainPruningEnabled() { - return isChainPruningEnabled; - } - - /** - * Sets checkpoint post merge sync. - * - * @param isCheckpointPostMergeSync the is checkpoint post merge sync - * @return the checkpoint post merge sync - */ - public PostMergeContext setCheckpointPostMergeSync(final boolean isCheckpointPostMergeSync) { - this.isCheckpointPostMergeSync = isCheckpointPostMergeSync; - return this; - } - - @Override - public boolean isCheckpointPostMergeSync() { - return this.isCheckpointPostMergeSync; - } - @Override public boolean isPostMergeAtGenesis() { return this.isPostMergeAtGenesis; diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionBackwardSyncContext.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionBackwardSyncContext.java index 9af4d8d6b95..4f0531a1bc4 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionBackwardSyncContext.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionBackwardSyncContext.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionBestPeerComparator.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionBestPeerComparator.java index 4cc4fbafba8..ee5ebcb8c8e 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionBestPeerComparator.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionBestPeerComparator.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionContext.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionContext.java index 35e51db563e..9d8c927e1ee 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionContext.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionContext.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -18,7 +18,6 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ConsensusContext; import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.BlockWithReceipts; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; @@ -28,6 +27,7 @@ public class TransitionContext implements MergeContext { /** The Pre merge context. */ final ConsensusContext preMergeContext; + /** The Post merge context. */ final MergeContext postMergeContext; @@ -145,13 +145,8 @@ public void putPayloadById(final PayloadWrapper payloadWrapper) { } @Override - public Optional retrieveBlockById(final PayloadIdentifier payloadId) { - return postMergeContext.retrieveBlockById(payloadId); - } - - @Override - public boolean isCheckpointPostMergeSync() { - return false; + public Optional retrievePayloadById(final PayloadIdentifier payloadId) { + return postMergeContext.retrievePayloadById(payloadId); } @Override diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionProtocolSchedule.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionProtocolSchedule.java index b3e044dfdb4..e733cc800ec 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionProtocolSchedule.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionProtocolSchedule.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,9 +15,12 @@ package org.hyperledger.besu.consensus.merge; import org.hyperledger.besu.config.GenesisConfigOptions; +import org.hyperledger.besu.datatypes.HardforkId; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PermissionTransactionFilter; import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSchedule; @@ -25,6 +28,7 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.ScheduledProtocolSpec; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.plugin.services.MetricsSystem; import java.math.BigInteger; import java.util.Optional; @@ -61,14 +65,32 @@ public TransitionProtocolSchedule( * * @param genesisConfigOptions {@link GenesisConfigOptions} containing the config options for the * milestone starting points + * @param miningParameters the mining parameters + * @param badBlockManager the cache to use to keep invalid blocks + * @param isParallelTxProcessingEnabled indicates whether parallel transaction is enabled. * @return an initialised TransitionProtocolSchedule using post-merge defaults */ public static TransitionProtocolSchedule fromConfig( - final GenesisConfigOptions genesisConfigOptions) { + final GenesisConfigOptions genesisConfigOptions, + final MiningParameters miningParameters, + final BadBlockManager badBlockManager, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { ProtocolSchedule preMergeProtocolSchedule = - MainnetProtocolSchedule.fromConfig(genesisConfigOptions); + MainnetProtocolSchedule.fromConfig( + genesisConfigOptions, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem); ProtocolSchedule postMergeProtocolSchedule = - MergeProtocolSchedule.create(genesisConfigOptions, false); + MergeProtocolSchedule.create( + genesisConfigOptions, + false, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem); return new TransitionProtocolSchedule( preMergeProtocolSchedule, postMergeProtocolSchedule, PostMergeContext.get()); } @@ -220,6 +242,13 @@ public String listMilestones() { return transitionUtils.dispatchFunctionAccordingToMergeState(ProtocolSchedule::listMilestones); } + @Override + public Optional milestoneFor(final HardforkId hardforkId) { + return mergeContext.isPostMerge() + ? transitionUtils.getPostMergeObject().milestoneFor(hardforkId) + : transitionUtils.getPreMergeObject().milestoneFor(hardforkId); + } + /** * Sets transaction filter. * diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionUtils.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionUtils.java index d851317a94c..6c8e3613b03 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionUtils.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/TransitionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/UnverifiedForkchoiceSupplier.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/UnverifiedForkchoiceSupplier.java index e42956fae77..8a5af0fcd22 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/UnverifiedForkchoiceSupplier.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/UnverifiedForkchoiceSupplier.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -27,6 +27,9 @@ public class UnverifiedForkchoiceSupplier private volatile Optional maybeLastForkchoiceUpdate = Optional.empty(); + /** Default Constructor. */ + public UnverifiedForkchoiceSupplier() {} + @Override public void onNewUnverifiedForkchoice(final ForkchoiceEvent event) { maybeLastForkchoiceUpdate = Optional.of(event); diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeBlockCreator.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeBlockCreator.java index fa786d3c00d..c56c62d80db 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeBlockCreator.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeBlockCreator.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,7 +14,6 @@ */ package org.hyperledger.besu.consensus.merge.blockcreation; -import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.blockcreation.AbstractBlockCreator; import org.hyperledger.besu.ethereum.core.BlockHeader; @@ -54,7 +53,6 @@ public MergeBlockCreator( final ProtocolContext protocolContext, final ProtocolSchedule protocolSchedule, final BlockHeader parentHeader, - final Optional
depositContractAddress, final EthScheduler ethScheduler) { super( miningParameters, @@ -63,8 +61,6 @@ public MergeBlockCreator( transactionPool, protocolContext, protocolSchedule, - parentHeader, - depositContractAddress, ethScheduler); } @@ -83,7 +79,8 @@ public BlockCreationResult createBlock( final Bytes32 random, final long timestamp, final Optional> withdrawals, - final Optional parentBeaconBlockRoot) { + final Optional parentBeaconBlockRoot, + final BlockHeader parentHeader) { return createBlock( maybeTransactions, @@ -92,14 +89,16 @@ public BlockCreationResult createBlock( Optional.of(random), parentBeaconBlockRoot, timestamp, - false); + false, + parentHeader); } @Override public BlockCreationResult createBlock( final Optional> maybeTransactions, final Optional> maybeOmmers, - final long timestamp) { + final long timestamp, + final BlockHeader parentHeader) { throw new UnsupportedOperationException("random is required"); } diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinator.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinator.java index ab7fd05ea21..c62b6e0de88 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinator.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinator.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -25,6 +25,7 @@ import org.hyperledger.besu.ethereum.BlockProcessingResult; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.blockcreation.BlockCreator.BlockCreationResult; +import org.hyperledger.besu.ethereum.chain.BadBlockCause; import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; @@ -60,7 +61,6 @@ import java.util.function.Supplier; import com.google.common.annotations.VisibleForTesting; -import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -68,6 +68,7 @@ /** The Merge coordinator. */ public class MergeCoordinator implements MergeMiningCoordinator, BadChainListener { private static final Logger LOG = LoggerFactory.getLogger(MergeCoordinator.class); + /** * On PoS you do not need to compete with other nodes for block production, since you have an * allocated slot for that, so in this case make sense to always try to fill the block, if there @@ -78,18 +79,25 @@ public class MergeCoordinator implements MergeMiningCoordinator, BadChainListene private static final double TRY_FILL_BLOCK = 1.0; private static final long DEFAULT_TARGET_GAS_LIMIT = 30000000L; + /** The Mining parameters. */ protected final MiningParameters miningParameters; + /** The Merge block creator factory. */ protected final MergeBlockCreatorFactory mergeBlockCreatorFactory; + /** The Merge context. */ protected final MergeContext mergeContext; + /** The Protocol context. */ protected final ProtocolContext protocolContext; + /** The Block builder executor. */ protected final EthScheduler ethScheduler; + /** The Backward sync context. */ protected final BackwardSyncContext backwardSyncContext; + /** The Protocol schedule. */ protected final ProtocolSchedule protocolSchedule; @@ -141,7 +149,6 @@ public MergeCoordinator( protocolContext, protocolSchedule, parentHeader, - depositContractAddress, ethScheduler); }; @@ -212,8 +219,8 @@ public Wei getMinTransactionGasPrice() { } @Override - public void setExtraData(final Bytes extraData) { - this.miningParameters.setExtraData(extraData); + public Wei getMinPriorityFeePerGas() { + return miningParameters.getMinPriorityFeePerGas(); } @Override @@ -287,7 +294,8 @@ public PayloadIdentifier preparePayload( prevRandao, timestamp, withdrawals, - parentBeaconBlockRoot) + parentBeaconBlockRoot, + parentHeader) .getBlock(); BlockProcessingResult result = validateProposedBlock(emptyBlock); @@ -315,7 +323,8 @@ public PayloadIdentifier preparePayload( payloadIdentifier, mergeBlockCreator, withdrawals, - parentBeaconBlockRoot); + parentBeaconBlockRoot, + parentHeader); return payloadIdentifier; } @@ -356,12 +365,18 @@ private void tryToBuildBetterBlock( final PayloadIdentifier payloadIdentifier, final MergeBlockCreator mergeBlockCreator, final Optional> withdrawals, - final Optional parentBeaconBlockRoot) { + final Optional parentBeaconBlockRoot, + final BlockHeader parentHeader) { final Supplier blockCreator = () -> mergeBlockCreator.createBlock( - Optional.empty(), random, timestamp, withdrawals, parentBeaconBlockRoot); + Optional.empty(), + random, + timestamp, + withdrawals, + parentBeaconBlockRoot, + parentHeader); LOG.debug( "Block creation started for payload id {}, remaining time is {}ms", @@ -495,10 +510,6 @@ public Optional getOrSyncHeadByHash(final Hash headHash, final Hash .addArgument(maybeHeadHeader.get()::toLogString) .log(); } else { - LOG.atDebug() - .setMessage("Appending new head block hash {} to backward sync") - .addArgument(headHash::toHexString) - .log(); backwardSyncContext.maybeUpdateTargetHeight(headHash); backwardSyncContext .syncBackwardsUntil(headHash) @@ -685,9 +696,7 @@ private boolean moveWorldStateTo(final BlockHeader newHead) { @Override public Optional getLatestValidAncestor(final Hash blockHash) { final var chain = protocolContext.getBlockchain(); - final var chainHeadHeader = chain.getChainHeadHeader(); - return findValidAncestor( - chain, blockHash, protocolSchedule.getByBlockHeader(chainHeadHeader).getBadBlocksManager()); + return findValidAncestor(chain, blockHash); } @Override @@ -696,8 +705,7 @@ public Optional getLatestValidAncestor(final BlockHeader blockHeader) { final var self = chain.getBlockHeader(blockHeader.getHash()); if (self.isEmpty()) { - final var badBlocks = protocolSchedule.getByBlockHeader(blockHeader).getBadBlocksManager(); - return findValidAncestor(chain, blockHeader.getParentHash(), badBlocks); + return findValidAncestor(chain, blockHeader.getParentHash()); } return self.map(BlockHeader::getHash); } @@ -717,8 +725,7 @@ public boolean isMiningBeforeMerge() { return miningParameters.isMiningEnabled(); } - private Optional findValidAncestor( - final Blockchain chain, final Hash parentHash, final BadBlockManager badBlocks) { + private Optional findValidAncestor(final Blockchain chain, final Hash parentHash) { // check chain first return chain @@ -735,12 +742,12 @@ private Optional findValidAncestor( .map(Optional::of) .orElseGet( () -> - badBlocks + protocolContext + .getBadBlockManager() .getBadBlock(parentHash) .map( badParent -> - findValidAncestor( - chain, badParent.getHeader().getParentHash(), badBlocks)) + findValidAncestor(chain, badParent.getHeader().getParentHash())) .orElse(Optional.empty())); } @@ -780,8 +787,8 @@ public void onBadChain( final Block badBlock, final List badBlockDescendants, final List badBlockHeaderDescendants) { - LOG.trace("Adding bad block {} and all its descendants", badBlock.getHash()); - final BadBlockManager badBlockManager = getBadBlockManager(); + LOG.trace("Mark descendents of bad block {} as bad", badBlock.getHash()); + final BadBlockManager badBlockManager = protocolContext.getBadBlockManager(); final Optional parentHeader = protocolContext.getBlockchain().getBlockHeader(badBlock.getHeader().getParentHash()); @@ -790,12 +797,11 @@ public void onBadChain( ? Optional.of(parentHeader.get().getHash()) : Optional.empty(); - badBlockManager.addBadBlock(badBlock, Optional.empty()); - + // Bad block has already been marked, but we need to mark the bad block's descendants badBlockDescendants.forEach( block -> { LOG.trace("Add descendant block {} to bad blocks", block.getHash()); - badBlockManager.addBadBlock(block, Optional.empty()); + badBlockManager.addBadBlock(block, BadBlockCause.fromBadAncestorBlock(badBlock)); maybeLatestValidHash.ifPresent( latestValidHash -> badBlockManager.addLatestValidHash(block.getHash(), latestValidHash)); @@ -804,13 +810,23 @@ public void onBadChain( badBlockHeaderDescendants.forEach( header -> { LOG.trace("Add descendant header {} to bad blocks", header.getHash()); - badBlockManager.addBadHeader(header); + badBlockManager.addBadHeader(header, BadBlockCause.fromBadAncestorBlock(badBlock)); maybeLatestValidHash.ifPresent( latestValidHash -> badBlockManager.addLatestValidHash(header.getHash(), latestValidHash)); }); } + /** + * returns the instance of ethScheduler + * + * @return get the Eth scheduler + */ + @Override + public EthScheduler getEthScheduler() { + return ethScheduler; + } + /** The interface Merge block creator factory. */ @FunctionalInterface protected interface MergeBlockCreatorFactory { @@ -824,35 +840,15 @@ protected interface MergeBlockCreatorFactory { MergeBlockCreator forParams(BlockHeader header, Optional
feeRecipient); } - @Override - public void addBadBlock(final Block block, final Optional maybeCause) { - protocolSchedule - .getByBlockHeader(protocolContext.getBlockchain().getChainHeadHeader()) - .getBadBlocksManager() - .addBadBlock(block, maybeCause); - } - @Override public boolean isBadBlock(final Hash blockHash) { - final BadBlockManager badBlocksManager = getBadBlockManager(); - return badBlocksManager.getBadBlock(blockHash).isPresent() - || badBlocksManager.getBadHash(blockHash).isPresent(); - } - - private BadBlockManager getBadBlockManager() { - final BadBlockManager badBlocksManager = - protocolSchedule - .getByBlockHeader(protocolContext.getBlockchain().getChainHeadHeader()) - .getBadBlocksManager(); - return badBlocksManager; + final BadBlockManager badBlockManager = protocolContext.getBadBlockManager(); + return badBlockManager.isBadBlock(blockHash); } @Override public Optional getLatestValidHashOfBadBlock(Hash blockHash) { - return protocolSchedule - .getByBlockHeader(protocolContext.getBlockchain().getChainHeadHeader()) - .getBadBlocksManager() - .getLatestValidHash(blockHash); + return protocolContext.getBadBlockManager().getLatestValidHash(blockHash); } private boolean isPoSHeader(final BlockHeader header) { @@ -879,6 +875,7 @@ boolean isBlockCreationCancelled(final PayloadIdentifier payloadId) { private static class BlockCreationTask { /** The Block creator. */ final MergeBlockCreator blockCreator; + /** The Cancelled. */ final AtomicBoolean cancelled; diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeMiningCoordinator.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeMiningCoordinator.java index 887fef66299..0bbd1f0a69c 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeMiningCoordinator.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeMiningCoordinator.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -24,6 +24,7 @@ import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Withdrawal; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import java.util.List; import java.util.Optional; @@ -140,14 +141,6 @@ ForkchoiceResult updateForkChoice( */ boolean isMiningBeforeMerge(); - /** - * Add bad block. - * - * @param block the block - * @param maybeCause the maybe cause - */ - void addBadBlock(final Block block, Optional maybeCause); - /** * Is bad block. * @@ -171,6 +164,13 @@ ForkchoiceResult updateForkChoice( */ void finalizeProposalById(final PayloadIdentifier payloadId); + /** + * Return the scheduler + * + * @return the instance of the scheduler + */ + EthScheduler getEthScheduler(); + /** The type Forkchoice result. */ class ForkchoiceResult { /** The enum Status. */ diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/PayloadIdentifier.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/PayloadIdentifier.java index ddd3c1b3e4e..c08715897f0 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/PayloadIdentifier.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/PayloadIdentifier.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/TransitionCoordinator.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/TransitionCoordinator.java index 4044ac3d34b..681c6f334bc 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/TransitionCoordinator.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/blockcreation/TransitionCoordinator.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -27,12 +27,12 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.Withdrawal; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; -import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; /** The Transition coordinator. */ @@ -102,9 +102,8 @@ public Wei getMinTransactionGasPrice() { } @Override - public void setExtraData(final Bytes extraData) { - miningCoordinator.setExtraData(extraData); - mergeCoordinator.setExtraData(extraData); + public Wei getMinPriorityFeePerGas() { + return dispatchFunctionAccordingToMergeState(MiningCoordinator::getMinPriorityFeePerGas); } @Override @@ -204,11 +203,6 @@ public boolean isDescendantOf(final BlockHeader ancestorBlock, final BlockHeader return mergeCoordinator.isDescendantOf(ancestorBlock, newBlock); } - @Override - public void addBadBlock(final Block block, final Optional maybeCause) { - mergeCoordinator.addBadBlock(block, maybeCause); - } - @Override public boolean isBadBlock(final Hash blockHash) { return mergeCoordinator.isBadBlock(blockHash); @@ -223,4 +217,14 @@ public Optional getLatestValidHashOfBadBlock(final Hash blockHash) { public void finalizeProposalById(final PayloadIdentifier payloadId) { mergeCoordinator.finalizeProposalById(payloadId); } + + /** + * returns the instance of ethScheduler + * + * @return get the Eth scheduler + */ + @Override + public EthScheduler getEthScheduler() { + return mergeCoordinator.getEthScheduler(); + } } diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/headervalidationrules/ConstantOmmersHashRule.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/headervalidationrules/ConstantOmmersHashRule.java index 5656c8b3624..178c5fab8a1 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/headervalidationrules/ConstantOmmersHashRule.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/headervalidationrules/ConstantOmmersHashRule.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.consensus.merge.headervalidationrules; import org.hyperledger.besu.consensus.merge.MergeContext; @@ -34,6 +33,9 @@ public class ConstantOmmersHashRule implements AttachedBlockHeaderValidationRule private static final Logger LOG = LoggerFactory.getLogger(ConstantOmmersHashRule.class); + /** Default constructor. */ + public ConstantOmmersHashRule() {} + @Override public boolean validate( final BlockHeader header, final BlockHeader parent, final ProtocolContext protocolContext) { diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/headervalidationrules/IncrementalTimestampRule.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/headervalidationrules/IncrementalTimestampRule.java index fb80cae4fd5..2f3b0a0b238 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/headervalidationrules/IncrementalTimestampRule.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/headervalidationrules/IncrementalTimestampRule.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.consensus.merge.headervalidationrules; import static org.hyperledger.besu.consensus.merge.TransitionUtils.isTerminalProofOfWorkBlock; @@ -28,6 +27,9 @@ public class IncrementalTimestampRule extends MergeConsensusRule { private static final Logger LOG = LoggerFactory.getLogger(IncrementalTimestampRule.class); + /** Default Constructor. */ + public IncrementalTimestampRule() {} + @Override public boolean validate( final BlockHeader header, final BlockHeader parent, final ProtocolContext protocolContext) { diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/headervalidationrules/MergeConsensusRule.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/headervalidationrules/MergeConsensusRule.java index 3f968c46185..3c80c76e103 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/headervalidationrules/MergeConsensusRule.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/headervalidationrules/MergeConsensusRule.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -31,6 +31,9 @@ public abstract class MergeConsensusRule implements AttachedBlockHeaderValidationRule { private static final Logger LOG = LoggerFactory.getLogger(MergeConsensusRule.class); + /** Default Constructor. */ + public MergeConsensusRule() {} + /** * Should use post merge rules. * diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/headervalidationrules/NoDifficultyRule.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/headervalidationrules/NoDifficultyRule.java index 7318472e39c..9fe1421c464 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/headervalidationrules/NoDifficultyRule.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/headervalidationrules/NoDifficultyRule.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.consensus.merge.headervalidationrules; import static org.hyperledger.besu.consensus.merge.TransitionUtils.isTerminalProofOfWorkBlock; @@ -31,6 +30,9 @@ public class NoDifficultyRule extends MergeConsensusRule { private static final Logger LOG = LoggerFactory.getLogger(NoDifficultyRule.class); + /** Default Constructor. */ + public NoDifficultyRule() {} + @Override public boolean validate( final BlockHeader header, final BlockHeader parent, final ProtocolContext protocolContext) { diff --git a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/headervalidationrules/NoNonceRule.java b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/headervalidationrules/NoNonceRule.java index bfe7fffb0b5..7188cce405e 100644 --- a/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/headervalidationrules/NoNonceRule.java +++ b/consensus/merge/src/main/java/org/hyperledger/besu/consensus/merge/headervalidationrules/NoNonceRule.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.consensus.merge.headervalidationrules; import static org.hyperledger.besu.consensus.merge.TransitionUtils.isTerminalProofOfWorkBlock; @@ -31,6 +30,9 @@ public class NoNonceRule extends MergeConsensusRule { private static final Logger LOG = LoggerFactory.getLogger(NoNonceRule.class); + /** Default Constructor. */ + public NoNonceRule() {} + @Override public boolean validate( final BlockHeader header, final BlockHeader parent, final ProtocolContext protocolContext) { diff --git a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/MergeProtocolScheduleTest.java b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/MergeProtocolScheduleTest.java index e81f13ba1b4..0d4aede3a14 100644 --- a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/MergeProtocolScheduleTest.java +++ b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/MergeProtocolScheduleTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -19,14 +19,17 @@ import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockProcessor; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.evm.operation.InvalidOperation; import org.hyperledger.besu.evm.operation.PrevRanDaoOperation; import org.hyperledger.besu.evm.operation.Push0Operation; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import java.math.BigInteger; @@ -44,7 +47,14 @@ public void protocolSpecsAreCreatedAtBlockDefinedInJson() { + "}"; final GenesisConfigOptions config = GenesisConfigFile.fromConfig(jsonInput).getConfigOptions(); - final ProtocolSchedule protocolSchedule = MergeProtocolSchedule.create(config, false); + final ProtocolSchedule protocolSchedule = + MergeProtocolSchedule.create( + config, + false, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); final ProtocolSpec homesteadSpec = protocolSchedule.getByBlockHeader(blockHeader(1)); final ProtocolSpec londonSpec = protocolSchedule.getByBlockHeader(blockHeader(1559)); @@ -58,7 +68,14 @@ public void protocolSpecsAreCreatedAtBlockDefinedInJson() { public void mergeSpecificModificationsAreUnappliedForShanghai() { final GenesisConfigOptions config = GenesisConfigFile.mainnet().getConfigOptions(); - final ProtocolSchedule protocolSchedule = MergeProtocolSchedule.create(config, false); + final ProtocolSchedule protocolSchedule = + MergeProtocolSchedule.create( + config, + false, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); final long lastParisBlockNumber = 17034869L; final ProtocolSpec parisSpec = @@ -92,7 +109,14 @@ public void mergeSpecificModificationsAreUnappliedForCancun_whenShanghaiNotConfi + "}"; final GenesisConfigOptions config = GenesisConfigFile.fromConfig(jsonInput).getConfigOptions(); - final ProtocolSchedule protocolSchedule = MergeProtocolSchedule.create(config, false); + final ProtocolSchedule protocolSchedule = + MergeProtocolSchedule.create( + config, + false, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); final ProtocolSpec parisSpec = protocolSchedule.getByBlockHeader( @@ -118,7 +142,14 @@ public void mergeSpecificModificationsAreUnappliedForCancun_whenShanghaiNotConfi @Test public void mergeSpecificModificationsAreUnappliedForAllMainnetForksAfterParis() { final GenesisConfigOptions config = GenesisConfigFile.mainnet().getConfigOptions(); - final ProtocolSchedule protocolSchedule = MergeProtocolSchedule.create(config, false); + final ProtocolSchedule protocolSchedule = + MergeProtocolSchedule.create( + config, + false, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); final long lastParisBlockNumber = 17034869L; final ProtocolSpec parisSpec = @@ -146,7 +177,13 @@ public void mergeSpecificModificationsAreUnappliedForAllMainnetForksAfterParis() @Test public void parametersAlignWithMainnetWithAdjustments() { final ProtocolSpec london = - MergeProtocolSchedule.create(GenesisConfigFile.DEFAULT.getConfigOptions(), false) + MergeProtocolSchedule.create( + GenesisConfigFile.DEFAULT.getConfigOptions(), + false, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()) .getByBlockHeader(blockHeader(0)); assertThat(london.getName()).isEqualTo("Paris"); diff --git a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/PostMergeContextTest.java b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/PostMergeContextTest.java index d28d6677c81..6abf09bde0f 100644 --- a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/PostMergeContextTest.java +++ b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/PostMergeContextTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -26,6 +26,7 @@ import org.hyperledger.besu.consensus.merge.blockcreation.PayloadIdentifier; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockWithReceipts; @@ -138,9 +139,12 @@ public void putAndRetrieveFirstPayload() { BlockWithReceipts mockBlockWithReceipts = createBlockWithReceipts(1, 21000, 1); PayloadIdentifier firstPayloadId = new PayloadIdentifier(1L); - postMergeContext.putPayloadById(new PayloadWrapper(firstPayloadId, mockBlockWithReceipts)); + final var payloadWrapper = createPayloadWrapper(firstPayloadId, mockBlockWithReceipts, Wei.ONE); + postMergeContext.putPayloadById(payloadWrapper); - assertThat(postMergeContext.retrieveBlockById(firstPayloadId)).contains(mockBlockWithReceipts); + assertThat(postMergeContext.retrievePayloadById(firstPayloadId)) + .map(PayloadWrapper::blockWithReceipts) + .contains(mockBlockWithReceipts); } @Test @@ -149,10 +153,16 @@ public void puttingTwoBlocksWithTheSamePayloadIdWeRetrieveTheBest() { BlockWithReceipts betterBlockWithReceipts = createBlockWithReceipts(2, 11, 1); PayloadIdentifier payloadId = new PayloadIdentifier(1L); - postMergeContext.putPayloadById(new PayloadWrapper(payloadId, zeroTxBlockWithReceipts)); - postMergeContext.putPayloadById(new PayloadWrapper(payloadId, betterBlockWithReceipts)); - - assertThat(postMergeContext.retrieveBlockById(payloadId)).contains(betterBlockWithReceipts); + final var zeroTxPayloadWrapper = + createPayloadWrapper(payloadId, zeroTxBlockWithReceipts, Wei.ZERO); + final var betterPayloadWrapper = + createPayloadWrapper(payloadId, betterBlockWithReceipts, Wei.ONE); + postMergeContext.putPayloadById(zeroTxPayloadWrapper); + postMergeContext.putPayloadById(betterPayloadWrapper); + + assertThat(postMergeContext.retrievePayloadById(payloadId)) + .map(PayloadWrapper::blockWithReceipts) + .contains(betterBlockWithReceipts); } @Test @@ -162,25 +172,33 @@ public void puttingABlockWithTheSamePayloadIdSmallerThanAnExistingOneWeRetrieveT BlockWithReceipts smallBlockWithReceipts = createBlockWithReceipts(3, 5, 1); PayloadIdentifier payloadId = new PayloadIdentifier(1L); - postMergeContext.putPayloadById(new PayloadWrapper(payloadId, zeroTxBlockWithReceipts)); - postMergeContext.putPayloadById(new PayloadWrapper(payloadId, betterBlockWithReceipts)); - postMergeContext.putPayloadById(new PayloadWrapper(payloadId, smallBlockWithReceipts)); - - assertThat(postMergeContext.retrieveBlockById(payloadId)).contains(betterBlockWithReceipts); + final var zeroTxPayloadWrapper = + createPayloadWrapper(payloadId, zeroTxBlockWithReceipts, Wei.ZERO); + final var betterPayloadWrapper = + createPayloadWrapper(payloadId, betterBlockWithReceipts, Wei.of(2)); + final var smallPayloadWrapper = + createPayloadWrapper(payloadId, smallBlockWithReceipts, Wei.ONE); + postMergeContext.putPayloadById(zeroTxPayloadWrapper); + postMergeContext.putPayloadById(betterPayloadWrapper); + postMergeContext.putPayloadById(smallPayloadWrapper); + + assertThat(postMergeContext.retrievePayloadById(payloadId)) + .map(PayloadWrapper::blockWithReceipts) + .contains(betterBlockWithReceipts); } @Test public void tryingToRetrieveANotYetPutPayloadIdReturnsEmpty() { PayloadIdentifier payloadId = new PayloadIdentifier(1L); - assertThat(postMergeContext.retrieveBlockById(payloadId)).isEmpty(); + assertThat(postMergeContext.retrievePayloadById(payloadId)).isEmpty(); } @Test public void tryingToRetrieveABlockPutButEvictedReturnsEmpty() { PayloadIdentifier evictedPayloadId = new PayloadIdentifier(0L); - assertThat(postMergeContext.retrieveBlockById(evictedPayloadId)).isEmpty(); + assertThat(postMergeContext.retrievePayloadById(evictedPayloadId)).isEmpty(); } @Test @@ -209,6 +227,17 @@ public void syncStateNullShouldNotThrowWhenIsSyncingIsCalled() { assertThat(postMergeContext.isSyncing()).isFalse(); } + private PayloadWrapper createPayloadWrapper( + final PayloadIdentifier firstPayloadId, + final BlockWithReceipts mockBlockWithReceipts, + final Wei blockValue) { + final var payloadWrapper = mock(PayloadWrapper.class); + when(payloadWrapper.payloadIdentifier()).thenReturn(firstPayloadId); + when(payloadWrapper.blockWithReceipts()).thenReturn(mockBlockWithReceipts); + when(payloadWrapper.blockValue()).thenReturn(blockValue); + return payloadWrapper; + } + private static BlockWithReceipts createBlockWithReceipts( final int number, final long gasUsed, final int txCount) { Block mockBlock = mock(Block.class, RETURNS_DEEP_STUBS); diff --git a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/TransitionBestPeerComparatorTest.java b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/TransitionBestPeerComparatorTest.java index ce3ef0088c3..b7820121441 100644 --- a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/TransitionBestPeerComparatorTest.java +++ b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/TransitionBestPeerComparatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/TransitionProtocolScheduleTest.java b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/TransitionProtocolScheduleTest.java index b55686a24be..52a183e9f6a 100644 --- a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/TransitionProtocolScheduleTest.java +++ b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/TransitionProtocolScheduleTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinatorTest.java b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinatorTest.java index 16a147c70e5..9c1a3df075e 100644 --- a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinatorTest.java +++ b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeCoordinatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -63,6 +63,7 @@ import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.sync.backwardsync.BackwardSyncContext; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; @@ -71,7 +72,6 @@ import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolMetrics; import org.hyperledger.besu.ethereum.eth.transactions.sorter.BaseFeePendingTransactionsSorter; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket; import org.hyperledger.besu.ethereum.mainnet.feemarket.LondonFeeMarket; import org.hyperledger.besu.ethereum.trie.MerkleTrieException; @@ -186,22 +186,9 @@ public void setUp() { when(mergeContext.as(MergeContext.class)).thenReturn(mergeContext); when(mergeContext.getTerminalTotalDifficulty()) .thenReturn(genesisState.getBlock().getHeader().getDifficulty().plus(1L)); - doAnswer( - getSpecInvocation -> { - ProtocolSpec spec = (ProtocolSpec) spy(getSpecInvocation.callRealMethod()); - doAnswer( - getBadBlockInvocation -> { - return badBlockManager; - }) - .when(spec) - .getBadBlocksManager(); - return spec; - }) - .when(protocolSchedule) - .getByBlockHeader(any(BlockHeader.class)); protocolContext = - new ProtocolContext(blockchain, worldStateArchive, mergeContext, Optional.empty()); + new ProtocolContext(blockchain, worldStateArchive, mergeContext, badBlockManager); var mutable = worldStateArchive.getMutable(); genesisState.writeStateTo(mutable); mutable.persist(null); @@ -229,7 +216,7 @@ public void setUp() { ethContext, new TransactionPoolMetrics(metricsSystem), poolConf, - null); + new BlobCache()); this.transactionPool.setEnabled(); @@ -290,7 +277,6 @@ public void exceptionDuringBuildingBlockShouldNotBeInvalid() protocolContext, protocolSchedule, parentHeader, - Optional.empty(), ethScheduler)); doCallRealMethod() @@ -299,7 +285,12 @@ public void exceptionDuringBuildingBlockShouldNotBeInvalid() .doCallRealMethod() .when(beingSpiedOn) .createBlock( - any(), any(Bytes32.class), anyLong(), eq(Optional.empty()), eq(Optional.empty())); + any(), + any(Bytes32.class), + anyLong(), + eq(Optional.empty()), + eq(Optional.empty()), + any()); return beingSpiedOn; }; @@ -339,7 +330,6 @@ public void exceptionDuringBuildingBlockShouldNotBeInvalid() Optional.empty(), Optional.empty()); - verify(willThrow, never()).addBadBlock(any(), any()); blockCreationTask.get(); ArgumentCaptor payloadWrapper = ArgumentCaptor.forClass(PayloadWrapper.class); @@ -359,7 +349,6 @@ public void exceptionDuringBuildingBlockShouldNotBeInvalid() // this only verifies that adding the bad block didn't happen through the mergeCoordinator, it // still may be called directly. verify(badBlockManager, never()).addBadBlock(any(), any()); - verify(willThrow, never()).addBadBlock(any(), any()); } @Test diff --git a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeGenesisConfigHelper.java b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeGenesisConfigHelper.java index a1c8eaf1c3a..dcfe5f98e34 100644 --- a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeGenesisConfigHelper.java +++ b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeGenesisConfigHelper.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,27 +14,26 @@ */ package org.hyperledger.besu.consensus.merge.blockcreation; -import static java.nio.charset.StandardCharsets.UTF_8; - -import org.hyperledger.besu.config.GenesisAllocation; +import org.hyperledger.besu.config.GenesisAccount; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.consensus.merge.MergeProtocolSchedule; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.stream.Stream; -import com.google.common.io.Resources; - public interface MergeGenesisConfigHelper { default GenesisConfigFile getPosGenesisConfigFile() { try { final URI uri = MergeGenesisConfigHelper.class.getResource("/posAtGenesis.json").toURI(); - return GenesisConfigFile.fromConfig(Resources.toString(uri.toURL(), UTF_8)); + return GenesisConfigFile.fromSource(uri.toURL()); } catch (final URISyntaxException | IOException e) { throw new IllegalStateException(e); } @@ -43,20 +42,23 @@ default GenesisConfigFile getPosGenesisConfigFile() { default GenesisConfigFile getPowGenesisConfigFile() { try { final URI uri = MergeGenesisConfigHelper.class.getResource("/powAtGenesis.json").toURI(); - return GenesisConfigFile.fromConfig(Resources.toString(uri.toURL(), UTF_8)); + return GenesisConfigFile.fromSource(uri.toURL()); } catch (final URISyntaxException | IOException e) { throw new IllegalStateException(e); } } default Stream
genesisAllocations(final GenesisConfigFile configFile) { - return configFile - .streamAllocations() - .map(GenesisAllocation::getAddress) - .map(Address::fromHexString); + return configFile.streamAllocations().map(GenesisAccount::address); } default ProtocolSchedule getMergeProtocolSchedule() { - return MergeProtocolSchedule.create(getPosGenesisConfigFile().getConfigOptions(), false); + return MergeProtocolSchedule.create( + getPosGenesisConfigFile().getConfigOptions(), + false, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); } } diff --git a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeReorgTest.java b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeReorgTest.java index 1b2bd2bffd2..9b6cf907d2b 100644 --- a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeReorgTest.java +++ b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/MergeReorgTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -26,6 +26,7 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.BlockProcessingResult; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.GenesisState; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; @@ -75,7 +76,7 @@ public class MergeReorgTest implements MergeGenesisConfigHelper { private final MutableBlockchain blockchain = createInMemoryBlockchain(genesisState.getBlock()); private final EthScheduler ethScheduler = new DeterministicEthScheduler(); private final ProtocolContext protocolContext = - new ProtocolContext(blockchain, worldStateArchive, mergeContext, Optional.empty()); + new ProtocolContext(blockchain, worldStateArchive, mergeContext, new BadBlockManager()); private final Address coinbase = genesisAllocations(getPowGenesisConfigFile()).findFirst().get(); private final BlockHeaderTestFixture headerGenerator = new BlockHeaderTestFixture(); diff --git a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/PayloadIdentifierTest.java b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/PayloadIdentifierTest.java index 314224cf55d..cb0352dc7a0 100644 --- a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/PayloadIdentifierTest.java +++ b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/blockcreation/PayloadIdentifierTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/headervalidationrules/ConstantOmmersHashValidationTest.java b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/headervalidationrules/ConstantOmmersHashValidationTest.java index 9207871cd2d..94244263ac9 100644 --- a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/headervalidationrules/ConstantOmmersHashValidationTest.java +++ b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/headervalidationrules/ConstantOmmersHashValidationTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.consensus.merge.headervalidationrules; import static org.assertj.core.api.Assertions.assertThat; diff --git a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/headervalidationrules/IncrementalTimestampValidationTest.java b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/headervalidationrules/IncrementalTimestampValidationTest.java index 8fd28cee88d..a9571f43f07 100644 --- a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/headervalidationrules/IncrementalTimestampValidationTest.java +++ b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/headervalidationrules/IncrementalTimestampValidationTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.consensus.merge.headervalidationrules; import static org.assertj.core.api.Assertions.assertThat; diff --git a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/headervalidationrules/NoDifficultyValidationTest.java b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/headervalidationrules/NoDifficultyValidationTest.java index 02cc597b1be..e785b9ac7e7 100644 --- a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/headervalidationrules/NoDifficultyValidationTest.java +++ b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/headervalidationrules/NoDifficultyValidationTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.consensus.merge.headervalidationrules; import static org.assertj.core.api.Assertions.assertThat; diff --git a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/headervalidationrules/NoNonceValidationTest.java b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/headervalidationrules/NoNonceValidationTest.java index ad1b84b6783..8769f3974ec 100644 --- a/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/headervalidationrules/NoNonceValidationTest.java +++ b/consensus/merge/src/test/java/org/hyperledger/besu/consensus/merge/headervalidationrules/NoNonceValidationTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.consensus.merge.headervalidationrules; import static org.assertj.core.api.Assertions.assertThat; diff --git a/consensus/qbft/build.gradle b/consensus/qbft/build.gradle index bb3d9a3f894..9675c4df89e 100644 --- a/consensus/qbft/build.gradle +++ b/consensus/qbft/build.gradle @@ -22,7 +22,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } diff --git a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContext.java b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContext.java index cbce344efcc..10cdfa91501 100644 --- a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContext.java +++ b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContext.java @@ -103,7 +103,7 @@ public Block createBlockForProposalFromChainHead(final long timestamp) { public Block createBlockForProposal( final BlockHeader parent, final long timestamp, final Address proposer) { final Block block = - finalState.getBlockCreatorFactory().create(parent, 0).createBlock(timestamp).getBlock(); + finalState.getBlockCreatorFactory().create(0).createBlock(timestamp, parent).getBlock(); final BlockHeaderBuilder headerBuilder = BlockHeaderBuilder.fromHeader(block.getHeader()); headerBuilder diff --git a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java index 6d8c64a835f..8906f0de7f6 100644 --- a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java +++ b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/support/TestContextBuilder.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.when; import org.hyperledger.besu.config.BftFork; +import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.config.JsonQbftConfigOptions; import org.hyperledger.besu.config.JsonUtil; import org.hyperledger.besu.config.QbftConfigOptions; @@ -61,7 +62,6 @@ import org.hyperledger.besu.consensus.common.validator.ValidatorProvider; import org.hyperledger.besu.consensus.common.validator.blockbased.BlockValidatorProvider; import org.hyperledger.besu.consensus.qbft.MutableQbftConfigOptions; -import org.hyperledger.besu.consensus.qbft.QbftContext; import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec; import org.hyperledger.besu.consensus.qbft.QbftForksSchedulesFactory; import org.hyperledger.besu.consensus.qbft.QbftGossip; @@ -81,6 +81,7 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.GenesisState; import org.hyperledger.besu.ethereum.chain.MinedBlockObserver; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; @@ -97,6 +98,7 @@ import org.hyperledger.besu.ethereum.core.Util; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -104,7 +106,7 @@ import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolMetrics; import org.hyperledger.besu.ethereum.eth.transactions.sorter.GasPricePendingTransactionsSorter; import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; -import org.hyperledger.besu.ethereum.worldstate.DefaultWorldStateArchive; +import org.hyperledger.besu.ethereum.trie.forest.ForestWorldStateArchive; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; @@ -114,7 +116,6 @@ import org.hyperledger.besu.util.Subscribers; import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Path; import java.time.Clock; import java.time.Instant; @@ -147,7 +148,9 @@ private record ControllerAndState( private static final MetricsSystem metricsSystem = new NoOpMetricsSystem(); private boolean useValidatorContract; private boolean useLondonMilestone = false; + private boolean useShanghaiMilestone = false; private boolean useZeroBaseFee = false; + private boolean useFixedBaseFee = false; public static final int EPOCH_LENGTH = 10_000; public static final int BLOCK_TIMER_SEC = 3; public static final int ROUND_TIMER_SEC = 12; @@ -215,11 +218,21 @@ public TestContextBuilder useLondonMilestone(final boolean useLondonMilestone) { return this; } + public TestContextBuilder useShanghaiMilestone(final boolean useShanghaiMilestone) { + this.useShanghaiMilestone = useShanghaiMilestone; + return this; + } + public TestContextBuilder useZeroBaseFee(final boolean useZeroBaseFee) { this.useZeroBaseFee = useZeroBaseFee; return this; } + public TestContextBuilder useFixedBaseFee(final boolean useFixedBaseFee) { + this.useFixedBaseFee = useFixedBaseFee; + return this; + } + public TestContextBuilder qbftForks(final List qbftForks) { this.qbftForks = qbftForks; return this; @@ -241,7 +254,7 @@ public TestContext build() { } final MutableBlockchain blockChain; - final DefaultWorldStateArchive worldStateArchive = createInMemoryWorldStateArchive(); + final ForestWorldStateArchive worldStateArchive = createInMemoryWorldStateArchive(); if (genesisFile.isPresent()) { try { @@ -285,7 +298,9 @@ public TestContext build() { synchronizerUpdater, useValidatorContract, useLondonMilestone, + useShanghaiMilestone, useZeroBaseFee, + useFixedBaseFee, qbftForks); // Add each networkNode to the Multicaster (such that each can receive msgs from local node). @@ -350,8 +365,9 @@ private static Block createGenesisBlock(final Set
validators) { } private GenesisState createGenesisBlock(final String genesisFile) throws IOException { - final String json = Files.readString(Path.of(genesisFile)); - return GenesisState.fromJson(json, ProtocolScheduleFixture.MAINNET); + return GenesisState.fromConfig( + GenesisConfigFile.fromSource(Path.of(genesisFile).toUri().toURL()), + ProtocolScheduleFixture.MAINNET); } private static ControllerAndState createControllerAndFinalState( @@ -365,7 +381,9 @@ private static ControllerAndState createControllerAndFinalState( final SynchronizerUpdater synchronizerUpdater, final boolean useValidatorContract, final boolean useLondonMilestone, + final boolean useShanghaiMilestone, final boolean useZeroBaseFee, + final boolean useFixedBaseFee, final List qbftForks) { final MiningParameters miningParams = @@ -390,12 +408,17 @@ private static ControllerAndState createControllerAndFinalState( if (useLondonMilestone) { genesisConfigOptions.londonBlock(0); + } else if (useShanghaiMilestone) { + genesisConfigOptions.shanghaiTime(10); } else { genesisConfigOptions.berlinBlock(0); } if (useZeroBaseFee) { genesisConfigOptions.zeroBaseFee(true); } + if (useFixedBaseFee) { + genesisConfigOptions.fixedBaseFee(true); + } genesisConfigOptions.qbftConfigOptions( new JsonQbftConfigOptions(JsonUtil.objectNodeFromMap(qbftConfigValues))); genesisConfigOptions.transitions(TestTransitions.createQbftTestTransitions(qbftForks)); @@ -410,7 +433,14 @@ private static ControllerAndState createControllerAndFinalState( final BftProtocolSchedule protocolSchedule = QbftProtocolScheduleBuilder.create( - genesisConfigOptions, forksSchedule, BFT_EXTRA_DATA_ENCODER, EvmConfiguration.DEFAULT); + genesisConfigOptions, + forksSchedule, + BFT_EXTRA_DATA_ENCODER, + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); final BftValidatorOverrides validatorOverrides = convertBftForks(qbftForks); final TransactionSimulator transactionSimulator = @@ -430,8 +460,8 @@ private static ControllerAndState createControllerAndFinalState( new ProtocolContext( blockChain, worldStateArchive, - new QbftContext(validatorProvider, epochManager, blockInterface, Optional.empty()), - Optional.empty()); + new BftContext(validatorProvider, epochManager, blockInterface), + new BadBlockManager()); final TransactionPoolConfiguration poolConf = ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(1).build(); @@ -452,7 +482,7 @@ private static ControllerAndState createControllerAndFinalState( ethContext, new TransactionPoolMetrics(metricsSystem), poolConf, - null); + new BlobCache()); transactionPool.setEnabled(); diff --git a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/TransitionsTest.java b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/TransitionsTest.java index 8f22549dc1b..93268ab1b81 100644 --- a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/TransitionsTest.java +++ b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/TransitionsTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/ValidatorContractTest.java b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/ValidatorContractTest.java index a91ff3acbbf..c5978560065 100644 --- a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/ValidatorContractTest.java +++ b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/ValidatorContractTest.java @@ -140,6 +140,84 @@ public void retrievesValidatorsFromValidatorContract_LondonFork_ZeroBaseFee() { assertThat(validatorProvider.getValidatorsForBlock(block1)).containsExactly(NODE_ADDRESS); } + @Test + public void retrievesValidatorsFromValidatorContract_ShanghaiFork_ZeroBaseFee() { + // Using Shanghai on a free gas network + final TestContext context = + new TestContextBuilder() + .indexOfFirstLocallyProposedBlock(0) + .nodeParams( + List.of(new NodeParams(NODE_ADDRESS, NodeKeyUtils.createFrom(NODE_PRIVATE_KEY)))) + .clock(TestClock.fixed()) + .genesisFile( + Resources.getResource("genesis_validator_contract_shanghai.json").getFile()) + .useValidatorContract(true) + .useShanghaiMilestone(true) + .useZeroBaseFee(true) + .buildAndStart(); + + createNewBlockAsProposerFixedTime( + context, 1, + 266L); // 10s ahead of genesis timestamp in genesis_validator_contract_shanghai.json + + final ValidatorProvider validatorProvider = context.getValidatorProvider(); + final BlockHeader genesisBlock = context.getBlockchain().getBlockHeader(0).get(); + final BlockHeader block1 = context.getBlockchain().getBlockHeader(1).get(); + assertThat(validatorProvider.getValidatorsForBlock(genesisBlock)).containsExactly(NODE_ADDRESS); + assertThat(validatorProvider.getValidatorsForBlock(block1)).containsExactly(NODE_ADDRESS); + } + + @Test + public void retrievesValidatorsFromValidatorContract_LondonFork_FixedBaseFee() { + // Using London on a free gas network + final TestContext context = + new TestContextBuilder() + .indexOfFirstLocallyProposedBlock(0) + .nodeParams( + List.of(new NodeParams(NODE_ADDRESS, NodeKeyUtils.createFrom(NODE_PRIVATE_KEY)))) + .clock(TestClock.fixed()) + .genesisFile(Resources.getResource("genesis_validator_contract_london.json").getFile()) + .useValidatorContract(true) + .useLondonMilestone(true) + .useFixedBaseFee(true) + .buildAndStart(); + + createNewBlockAsProposer(context, 1); + + final ValidatorProvider validatorProvider = context.getValidatorProvider(); + final BlockHeader genesisBlock = context.getBlockchain().getBlockHeader(0).get(); + final BlockHeader block1 = context.getBlockchain().getBlockHeader(1).get(); + assertThat(validatorProvider.getValidatorsForBlock(genesisBlock)).containsExactly(NODE_ADDRESS); + assertThat(validatorProvider.getValidatorsForBlock(block1)).containsExactly(NODE_ADDRESS); + } + + @Test + public void retrievesValidatorsFromValidatorContract_ShanghaiFork_FixedBaseFee() { + // Using Shanghai on a free gas network + final TestContext context = + new TestContextBuilder() + .indexOfFirstLocallyProposedBlock(0) + .nodeParams( + List.of(new NodeParams(NODE_ADDRESS, NodeKeyUtils.createFrom(NODE_PRIVATE_KEY)))) + .clock(TestClock.fixed()) + .genesisFile( + Resources.getResource("genesis_validator_contract_shanghai.json").getFile()) + .useValidatorContract(true) + .useShanghaiMilestone(true) + .useFixedBaseFee(true) + .buildAndStart(); + + createNewBlockAsProposerFixedTime( + context, 1, + 266L); // 10s ahead of genesis timestamp in genesis_validator_contract_shanghai.json + + final ValidatorProvider validatorProvider = context.getValidatorProvider(); + final BlockHeader genesisBlock = context.getBlockchain().getBlockHeader(0).get(); + final BlockHeader block1 = context.getBlockchain().getBlockHeader(1).get(); + assertThat(validatorProvider.getValidatorsForBlock(genesisBlock)).containsExactly(NODE_ADDRESS); + assertThat(validatorProvider.getValidatorsForBlock(block1)).containsExactly(NODE_ADDRESS); + } + @Test public void transitionsFromBlockHeaderModeToValidatorContractMode() { final List qbftForks = @@ -397,6 +475,24 @@ private void createNewBlockAsProposer(final TestContext context, final long bloc .handleNewBlockEvent(new NewChainHead(context.getBlockchain().getChainHeadHeader())); } + private void createNewBlockAsProposerFixedTime( + final TestContext context, final long blockNumber, final long timestamp) { + ConsensusRoundIdentifier roundId = new ConsensusRoundIdentifier(blockNumber, 0); + + // trigger proposal + context.getController().handleBlockTimerExpiry(new BlockTimerExpiry(roundId)); + + // peers commit proposed block + Block proposedBlock = context.createBlockForProposalFromChainHead(timestamp); + RoundSpecificPeers peers = context.roundSpecificPeers(roundId); + peers.commitForNonProposing(roundId, proposedBlock); + + assertThat(context.getCurrentChainHeight()).isEqualTo(blockNumber); + context + .getController() + .handleNewBlockEvent(new NewChainHead(context.getBlockchain().getChainHeadHeader())); + } + private void remotePeerProposesNewBlock(final TestContext context, final long blockNumber) { ConsensusRoundIdentifier roundId = new ConsensusRoundIdentifier(blockNumber, 0); diff --git a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/round/QbftRoundIntegrationTest.java b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/round/QbftRoundIntegrationTest.java index cd3af6ac15b..7dcca442a7f 100644 --- a/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/round/QbftRoundIntegrationTest.java +++ b/consensus/qbft/src/integration-test/java/org/hyperledger/besu/consensus/qbft/test/round/QbftRoundIntegrationTest.java @@ -23,13 +23,14 @@ import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; +import org.hyperledger.besu.consensus.common.bft.BftContext; import org.hyperledger.besu.consensus.common.bft.BftExtraData; import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec; +import org.hyperledger.besu.consensus.common.bft.BftProtocolSchedule; import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier; import org.hyperledger.besu.consensus.common.bft.RoundTimer; import org.hyperledger.besu.consensus.common.bft.blockcreation.BftBlockCreator; import org.hyperledger.besu.consensus.common.bft.inttest.StubValidatorMulticaster; -import org.hyperledger.besu.consensus.qbft.QbftContext; import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec; import org.hyperledger.besu.consensus.qbft.network.QbftMessageTransmitter; import org.hyperledger.besu.consensus.qbft.payload.MessageFactory; @@ -42,6 +43,7 @@ import org.hyperledger.besu.cryptoservices.NodeKeyUtils; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MinedBlockObserver; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; @@ -50,12 +52,12 @@ import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.BlockImporter; import org.hyperledger.besu.ethereum.mainnet.BlockImportResult; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.plugin.services.securitymodule.SecurityModuleException; import org.hyperledger.besu.util.Subscribers; import java.math.BigInteger; -import java.util.Optional; import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.BeforeEach; @@ -77,6 +79,8 @@ public class QbftRoundIntegrationTest { private final BftExtraDataCodec bftExtraDataCodec = new QbftExtraDataCodec(); private ProtocolContext protocolContext; + @Mock private BftProtocolSchedule protocolSchedule; + @Mock private ProtocolSpec protocolSpec; @Mock private MutableBlockchain blockChain; @Mock private WorldStateArchive worldStateArchive; @Mock private BlockImporter blockImporter; @@ -88,6 +92,7 @@ public class QbftRoundIntegrationTest { private MessageFactory throwingMessageFactory; private QbftMessageTransmitter transmitter; @Mock private StubValidatorMulticaster multicaster; + @Mock private BlockHeader parentHeader; private Block proposedBlock; @@ -115,6 +120,9 @@ public void setup() { final BlockHeader header = headerTestFixture.buildHeader(); proposedBlock = new Block(header, new BlockBody(emptyList(), emptyList())); + when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); + when(protocolSpec.getBlockImporter()).thenReturn(blockImporter); + when(blockImporter.importBlock(any(), any(), any())).thenReturn(new BlockImportResult(true)); protocolContext = @@ -122,8 +130,8 @@ public void setup() { blockChain, worldStateArchive, setupContextWithBftExtraDataEncoder( - QbftContext.class, emptyList(), qbftExtraDataEncoder), - Optional.empty()); + BftContext.class, emptyList(), qbftExtraDataEncoder), + new BadBlockManager()); } @Test @@ -135,13 +143,14 @@ public void signingFailsOnReceiptOfProposalUpdatesRoundButTransmitsNothing() { roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, throwingMessageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); round.handleProposalMessage( peerMessageFactory.createProposal( @@ -163,13 +172,14 @@ public void failuresToSignStillAllowBlockToBeImported() { roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, throwingMessageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); // inject a block first, then a prepare on it. round.handleProposalMessage( diff --git a/consensus/qbft/src/integration-test/resources/genesis_validator_contract_shanghai.json b/consensus/qbft/src/integration-test/resources/genesis_validator_contract_shanghai.json new file mode 100644 index 00000000000..13e94ad43b7 --- /dev/null +++ b/consensus/qbft/src/integration-test/resources/genesis_validator_contract_shanghai.json @@ -0,0 +1,46 @@ +{ + "nonce": "0x0", + "timestamp": "0x100", + "extraData": "0xe5a00000000000000000000000000000000000000000000000000000000000000000c0c080c0", + "gasLimit": "0x29b92700", + "difficulty": "0x1", + "mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365", + "coinbase": "0x0000000000000000000000000000000000000000", + "alloc": { + "64d9be4177f418bcf4e56adad85f33e3a64efe22": { + "balance": "0x446c3b15f9926687d2c40534fdb564000000000000" + }, + "9f66f8a0f0a6537e4a36aa1799673ea7ae97a166": { + "balance": "0x446c3b15f9926687d2c40534fdb564000000000000" + }, + "a7f25969fb6f3d5ac09a88862c90b5ff664557a7": { + "balance": "0x446c3b15f9926687d2c40534fdb564000000000000" + }, + "f4bbfd32c11c9d63e9b4c77bb225810f840342df": { + "balance": "0x446c3b15f9926687d2c40534fdb564000000000000" + }, + "0x0000000000000000000000000000000000008888": { + "comment": "validator smart contract. This is compiled from validator_contract.sol using solc --evm-version shanghai --bin-runtime validator_contract.sol", + "balance": "0", + "code": "608060405234801561000f575f80fd5b5060043610610029575f3560e01c8063b7ab4db51461002d575b5f80fd5b61003561004b565b60405161004291906101bc565b60405180910390f35b60605f8054806020026020016040519081016040528092919081815260200182805480156100cb57602002820191905f5260205f20905b815f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019060010190808311610082575b5050505050905090565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610127826100fe565b9050919050565b6101378161011d565b82525050565b5f610148838361012e565b60208301905092915050565b5f602082019050919050565b5f61016a826100d5565b61017481856100df565b935061017f836100ef565b805f5b838110156101af578151610196888261013d565b97506101a183610154565b925050600181019050610182565b5085935050505092915050565b5f6020820190508181035f8301526101d48184610160565b90509291505056fea2646970667358221220b52fc648d3af2856c13132ebd193317528087a330aea868fcf843abcf9d9dc6d64736f6c63430008140033", + "storage": { + "0000000000000000000000000000000000000000000000000000000000000000": "0000000000000000000000000000000000000000000000000000000000000001", + "290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563": "000000000000000000000000eac51e3fe1afc9894f0dfeab8ceb471899b932df" + } + }, + "0x0000000000000000000000000000000000009999": { + "comment": "validator smart contract. This is compiled from validator_contract.sol using solc --evm-version shanghai --bin-runtime validator_contract.sol", + "balance": "0", + "code": "608060405234801561000f575f80fd5b5060043610610029575f3560e01c8063b7ab4db51461002d575b5f80fd5b61003561004b565b60405161004291906101bc565b60405180910390f35b60605f8054806020026020016040519081016040528092919081815260200182805480156100cb57602002820191905f5260205f20905b815f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019060010190808311610082575b5050505050905090565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610127826100fe565b9050919050565b6101378161011d565b82525050565b5f610148838361012e565b60208301905092915050565b5f602082019050919050565b5f61016a826100d5565b61017481856100df565b935061017f836100ef565b805f5b838110156101af578151610196888261013d565b97506101a183610154565b925050600181019050610182565b5085935050505092915050565b5f6020820190508181035f8301526101d48184610160565b90509291505056fea2646970667358221220b52fc648d3af2856c13132ebd193317528087a330aea868fcf843abcf9d9dc6d64736f6c63430008140033", + "storage": { + "0000000000000000000000000000000000000000000000000000000000000000": "0000000000000000000000000000000000000000000000000000000000000002", + "290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563": "000000000000000000000000e98d92560fac3069ccff53ef348ded26a51d4b68", + "290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e564": "000000000000000000000000eac51e3fe1afc9894f0dfeab8ceb471899b932df" + } + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "baseFeePerGas": "0x7" +} diff --git a/consensus/qbft/src/integration-test/resources/validator_contract.sol b/consensus/qbft/src/integration-test/resources/validator_contract.sol index 977c2aac22c..7d9ef984dbc 100644 --- a/consensus/qbft/src/integration-test/resources/validator_contract.sol +++ b/consensus/qbft/src/integration-test/resources/validator_contract.sol @@ -23,4 +23,4 @@ contract Validators { return validators; } -} \ No newline at end of file +} diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/BFTPivotSelectorFromPeers.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/BFTPivotSelectorFromPeers.java new file mode 100644 index 00000000000..7437b349a2a --- /dev/null +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/BFTPivotSelectorFromPeers.java @@ -0,0 +1,160 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.consensus.qbft; + +import org.hyperledger.besu.consensus.common.bft.BftContext; +import org.hyperledger.besu.consensus.common.validator.ValidatorProvider; +import org.hyperledger.besu.cryptoservices.NodeKey; +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.manager.EthPeer; +import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; +import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncState; +import org.hyperledger.besu.ethereum.eth.sync.fastsync.NoSyncRequiredException; +import org.hyperledger.besu.ethereum.eth.sync.fastsync.PivotSelectorFromPeers; +import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; +import org.hyperledger.besu.plugin.services.MetricsSystem; + +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class is a BFT-specific implementatino of the pivot-block selection used for snap-sync. It + * makes some pragmatic decisions about cases specific to permissioned chains, e.g. when there is a + * single validator node at the start of a chain, or a brand new chain has all nodes at block 0. For + * other cases the behaviour is the same as public-chain pivot selection, namely that the best peer + * is asked for its candidate pivot block. + */ +public class BFTPivotSelectorFromPeers extends PivotSelectorFromPeers { + + private static final Logger LOG = LoggerFactory.getLogger(BFTPivotSelectorFromPeers.class); + + private final ProtocolContext protocolContext; + private final BlockHeader blockHeader; + private final NodeKey nodeKey; + + /** + * Create a BFT-specific pivot selector + * + * @param ethContext the eth context + * @param syncConfig the sync config + * @param syncState the sync state + * @param metricsSystem the metrics + * @param protocolContext the protocol context + * @param nodeKey the node key + * @param blockHeader the block header + */ + public BFTPivotSelectorFromPeers( + final EthContext ethContext, + final SynchronizerConfiguration syncConfig, + final SyncState syncState, + final MetricsSystem metricsSystem, + final ProtocolContext protocolContext, + final NodeKey nodeKey, + final BlockHeader blockHeader) { + super(ethContext, syncConfig, syncState, metricsSystem); + this.protocolContext = protocolContext; + this.blockHeader = blockHeader; + this.nodeKey = nodeKey; + LOG.info("Creating pivot block selector for BFT node"); + } + + /** + * Determine if our node is a BFT validator node + * + * @param validatorProvider the validator provider + * @return true if we are a validator + */ + protected boolean weAreAValidator(final ValidatorProvider validatorProvider) { + return validatorProvider.nodeIsValidator(nodeKey); + } + + @Override + public Optional selectNewPivotBlock() { + + final BftContext bftContext = protocolContext.getConsensusContext(BftContext.class); + final ValidatorProvider validatorProvider = bftContext.getValidatorProvider(); + // See if we have a best peer + Optional bestPeer = selectBestPeer(); + + if (bestPeer.isPresent()) { + // For a recently created permissioned chain we can skip snap sync until we're past the + // pivot distance + if (bestPeer.get().chainState().getEstimatedHeight() <= syncConfig.getSyncPivotDistance()) { + LOG.info( + "Best peer for sync found but chain height hasn't reached minimum sync pivot distance {}, exiting sync process", + syncConfig.getSyncPivotDistance()); + throw new NoSyncRequiredException(); + } + + return bestPeer.flatMap(this::fromBestPeer); + } else { + // Treat us being the only validator as a special case. We are the only node that can produce + // blocks so we won't wait to sync with a non-validator node that may or may not exist + if (weAreAValidator(validatorProvider) + && validatorProvider.getValidatorsAtHead().size() == 1) { + LOG.info("This node is the only BFT validator, exiting sync process"); + throw new NoSyncRequiredException(); + } + + // Treat the case where we are at block 0 and don't yet have any validator peers with a chain + // height estimate as potentially a new QBFT chain. Check if any of the other peers have the + // same block hash as our genesis block. + if (blockHeader.getNumber() == 0) { + final AtomicInteger peerValidatorCount = new AtomicInteger(); + final AtomicBoolean peerAtOurGenesisBlock = new AtomicBoolean(); + ethContext + .getEthPeers() + .streamAllPeers() + .forEach( + peer -> { + // If we are at block 0 and our block hash matches at least one of our peers we + // assume we're all at block 0 and therefore won't try to snap sync. + if (peer.chainState() + .getBestBlock() + .getHash() + .equals(blockHeader.getBlockHash())) { + peerAtOurGenesisBlock.set(true); + } + if (!peer.getConnection().isDisconnected() + && validatorProvider + .getValidatorsAtHead() + .contains(peer.getConnection().getPeerInfo().getAddress())) { + peerValidatorCount.getAndIncrement(); + } + }); + + if (weAreAValidator(validatorProvider) + && peerValidatorCount.get() >= syncConfig.getSyncMinimumPeerCount() + && peerAtOurGenesisBlock.get()) { + // We have sync-min-peers x validators connected, all of whom have no head estimate. We'll + // assume this is a new chain and skip waiting for any more peers to sync with. The worst + // case is this puts us into full sync mode. + LOG.info( + "Peered with {} validators but no best peer found to sync from and their current block hash matches our genesis block. Assuming new BFT chain, exiting snap-sync", + peerValidatorCount.get()); + throw new NoSyncRequiredException(); + } + } + } + + return Optional.empty(); + } +} diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/MutableQbftConfigOptions.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/MutableQbftConfigOptions.java index c89b78d32f5..567876e351b 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/MutableQbftConfigOptions.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/MutableQbftConfigOptions.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactory.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactory.java index 028f939d32c..7320dfaaf7a 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactory.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactory.java @@ -37,6 +37,8 @@ /** The Qbft block header validation ruleset factory. */ public class QbftBlockHeaderValidationRulesetFactory { + /** Default constructor */ + private QbftBlockHeaderValidationRulesetFactory() {} /** * Produces a BlockHeaderValidator configured for assessing bft block headers which are to form diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftContext.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftContext.java deleted file mode 100644 index f89f4990be8..00000000000 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftContext.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.consensus.qbft; - -import org.hyperledger.besu.consensus.common.EpochManager; -import org.hyperledger.besu.consensus.common.bft.BftBlockInterface; -import org.hyperledger.besu.consensus.common.bft.BftContext; -import org.hyperledger.besu.consensus.common.validator.ValidatorProvider; -import org.hyperledger.besu.consensus.qbft.pki.PkiBlockCreationConfiguration; - -import java.util.Optional; - -/** The Qbft context. */ -public class QbftContext extends BftContext { - - private final Optional pkiBlockCreationConfiguration; - - /** - * Instantiates a new Qbft context. - * - * @param validatorProvider the validator provider - * @param epochManager the epoch manager - * @param blockInterface the block interface - * @param pkiBlockCreationConfiguration the pki block creation configuration - */ - public QbftContext( - final ValidatorProvider validatorProvider, - final EpochManager epochManager, - final BftBlockInterface blockInterface, - final Optional pkiBlockCreationConfiguration) { - super(validatorProvider, epochManager, blockInterface); - this.pkiBlockCreationConfiguration = pkiBlockCreationConfiguration; - } - - /** - * Gets pki block creation configuration. - * - * @return the pki block creation configuration - */ - public Optional getPkiBlockCreationConfiguration() { - return pkiBlockCreationConfiguration; - } -} diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftExtraDataCodec.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftExtraDataCodec.java index 065fd9575b2..e613a3960f1 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftExtraDataCodec.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftExtraDataCodec.java @@ -48,6 +48,9 @@ public class QbftExtraDataCodec extends BftExtraDataCodec { VoteType.ADD, ADD_BYTE_VALUE, VoteType.DROP, DROP_BYTE_VALUE); + /** Default constructor */ + public QbftExtraDataCodec() {} + /** * Encode from addresses. * diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftForksSchedulesFactory.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftForksSchedulesFactory.java index cf87938cb4a..90448f62015 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftForksSchedulesFactory.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftForksSchedulesFactory.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -20,13 +20,15 @@ import org.hyperledger.besu.config.QbftFork.VALIDATOR_SELECTION_MODE; import org.hyperledger.besu.consensus.common.ForkSpec; import org.hyperledger.besu.consensus.common.ForksSchedule; -import org.hyperledger.besu.consensus.common.bft.BftForksScheduleFactory; +import org.hyperledger.besu.consensus.common.ForksScheduleFactory; import java.util.List; import java.util.Optional; /** The Qbft forks schedules factory. */ public class QbftForksSchedulesFactory { + /** Default constructor */ + private QbftForksSchedulesFactory() {} /** * Create forks schedule. @@ -35,7 +37,7 @@ public class QbftForksSchedulesFactory { * @return the forks schedule */ public static ForksSchedule create(final GenesisConfigOptions genesisConfig) { - return BftForksScheduleFactory.create( + return ForksScheduleFactory.create( genesisConfig.getQbftConfigOptions(), genesisConfig.getTransitions().getQbftForks(), QbftForksSchedulesFactory::createQbftConfigOptions); diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftProtocolScheduleBuilder.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftProtocolScheduleBuilder.java index 29d4d657ac6..44c7ddfba8c 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftProtocolScheduleBuilder.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/QbftProtocolScheduleBuilder.java @@ -23,17 +23,22 @@ import org.hyperledger.besu.consensus.common.bft.BaseBftProtocolScheduleBuilder; import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec; import org.hyperledger.besu.consensus.common.bft.BftProtocolSchedule; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.mainnet.BlockHeaderValidator; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.plugin.services.MetricsSystem; import java.util.Optional; /** Defines the protocol behaviours for a blockchain using a QBFT consensus mechanism. */ public class QbftProtocolScheduleBuilder extends BaseBftProtocolScheduleBuilder { + /** Default constructor */ + QbftProtocolScheduleBuilder() {} /** * Create protocol schedule. @@ -44,6 +49,11 @@ public class QbftProtocolScheduleBuilder extends BaseBftProtocolScheduleBuilder * @param isRevertReasonEnabled the is revert reason enabled * @param bftExtraDataCodec the bft extra data codec * @param evmConfiguration the evm configuration + * @param miningParameters The mining parameters + * @param badBlockManager the cache to use to keep invalid blocks + * @param isParallelTxProcessingEnabled indicates whether parallel transaction is enabled. + * @param metricsSystem A metricSystem instance to be able to expose metrics in the underlying + * calls * @return the protocol schedule */ public static BftProtocolSchedule create( @@ -52,7 +62,11 @@ public static BftProtocolSchedule create( final PrivacyParameters privacyParameters, final boolean isRevertReasonEnabled, final BftExtraDataCodec bftExtraDataCodec, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final BadBlockManager badBlockManager, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return new QbftProtocolScheduleBuilder() .createProtocolSchedule( config, @@ -60,7 +74,11 @@ public static BftProtocolSchedule create( privacyParameters, isRevertReasonEnabled, bftExtraDataCodec, - evmConfiguration); + evmConfiguration, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem); } /** @@ -70,20 +88,33 @@ public static BftProtocolSchedule create( * @param qbftForksSchedule the qbft forks schedule * @param bftExtraDataCodec the bft extra data codec * @param evmConfiguration the evm configuration + * @param miningParameters The mining parameters + * @param badBlockManager the cache to use to keep invalid blocks + * @param isParallelTxProcessingEnabled indicates whether parallel transaction is enabled. + * @param metricsSystem A metricSystem instance to be able to expose metrics in the underlying + * calls * @return the protocol schedule */ public static BftProtocolSchedule create( final GenesisConfigOptions config, final ForksSchedule qbftForksSchedule, final BftExtraDataCodec bftExtraDataCodec, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final BadBlockManager badBlockManager, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return create( config, qbftForksSchedule, PrivacyParameters.DEFAULT, false, bftExtraDataCodec, - evmConfiguration); + evmConfiguration, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem); } /** @@ -93,20 +124,33 @@ public static BftProtocolSchedule create( * @param qbftForksSchedule the qbft forks schedule * @param isRevertReasonEnabled the is revert reason enabled * @param bftExtraDataCodec the bft extra data codec + * @param miningParameters The mining parameters + * @param badBlockManager the cache to use to keep invalid blocks + * @param isParallelTxProcessingEnabled indicates whether parallel transaction is enabled. + * @param metricsSystem A metricSystem instance to be able to expose metrics in the underlying + * calls * @return the protocol schedule */ public static ProtocolSchedule create( final GenesisConfigOptions config, final ForksSchedule qbftForksSchedule, final boolean isRevertReasonEnabled, - final BftExtraDataCodec bftExtraDataCodec) { + final BftExtraDataCodec bftExtraDataCodec, + final MiningParameters miningParameters, + final BadBlockManager badBlockManager, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return create( config, qbftForksSchedule, PrivacyParameters.DEFAULT, isRevertReasonEnabled, bftExtraDataCodec, - EvmConfiguration.DEFAULT); + EvmConfiguration.DEFAULT, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem); } @Override diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/PkiQbftBlockCreator.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/PkiQbftBlockCreator.java deleted file mode 100644 index 4ec6ea9f8aa..00000000000 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/PkiQbftBlockCreator.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.consensus.qbft.blockcreation; - -import static com.google.common.base.Preconditions.checkArgument; - -import org.hyperledger.besu.consensus.common.bft.BftBlockHeaderFunctions; -import org.hyperledger.besu.consensus.common.bft.BftExtraData; -import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec; -import org.hyperledger.besu.consensus.qbft.pki.PkiBlockCreationConfiguration; -import org.hyperledger.besu.consensus.qbft.pki.PkiQbftBlockHeaderFunctions; -import org.hyperledger.besu.consensus.qbft.pki.PkiQbftExtraData; -import org.hyperledger.besu.consensus.qbft.pki.PkiQbftExtraDataCodec; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.blockcreation.BlockCreator; -import org.hyperledger.besu.ethereum.core.Block; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; -import org.hyperledger.besu.ethereum.core.Transaction; -import org.hyperledger.besu.pki.cms.CmsCreator; - -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import com.google.common.annotations.VisibleForTesting; -import org.apache.tuweni.bytes.Bytes; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** The Pki qbft block creator. */ -public class PkiQbftBlockCreator implements BlockCreator { - - private static final Logger LOG = LoggerFactory.getLogger(PkiQbftBlockCreator.class); - - private final BlockCreator blockCreator; - private final PkiQbftExtraDataCodec pkiQbftExtraDataCodec; - private final CmsCreator cmsCreator; - - /** - * Instantiates a new Pki qbft block creator. - * - * @param blockCreator the block creator - * @param pkiBlockCreationConfiguration the pki block creation configuration - * @param pkiQbftExtraDataCodec the pki qbft extra data codec - */ - public PkiQbftBlockCreator( - final BlockCreator blockCreator, - final PkiBlockCreationConfiguration pkiBlockCreationConfiguration, - final BftExtraDataCodec pkiQbftExtraDataCodec) { - this( - blockCreator, - new CmsCreator( - pkiBlockCreationConfiguration.getKeyStore(), - pkiBlockCreationConfiguration.getCertificateAlias()), - pkiQbftExtraDataCodec); - } - - /** - * Instantiates a new Pki qbft block creator. - * - * @param blockCreator the block creator - * @param cmsCreator the cms creator - * @param bftExtraDataCodec the bft extra data codec - */ - @VisibleForTesting - public PkiQbftBlockCreator( - final BlockCreator blockCreator, - final CmsCreator cmsCreator, - final BftExtraDataCodec bftExtraDataCodec) { - this.blockCreator = blockCreator; - this.cmsCreator = cmsCreator; - - checkArgument( - bftExtraDataCodec instanceof PkiQbftExtraDataCodec, - "PkiQbftBlockCreator must use PkiQbftExtraDataCodec"); - this.pkiQbftExtraDataCodec = (PkiQbftExtraDataCodec) bftExtraDataCodec; - } - - @Override - public BlockCreationResult createBlock(final long timestamp) { - final BlockCreationResult blockCreationResult = blockCreator.createBlock(timestamp); - return replaceCmsInBlock(blockCreationResult); - } - - @Override - public BlockCreationResult createBlock( - final List transactions, final List ommers, final long timestamp) { - final BlockCreationResult blockCreationResult = - blockCreator.createBlock(transactions, ommers, timestamp); - return replaceCmsInBlock(blockCreationResult); - } - - @Override - public BlockCreationResult createBlock( - final Optional> maybeTransactions, - final Optional> maybeOmmers, - final long timestamp) { - return createBlock( - maybeTransactions.orElse(Collections.emptyList()), - maybeOmmers.orElse(Collections.emptyList()), - timestamp); - } - - private BlockCreationResult replaceCmsInBlock(final BlockCreationResult blockCreationResult) { - final Block block = blockCreationResult.getBlock(); - final BlockHeader blockHeader = block.getHeader(); - final Hash hashWithoutCms = - PkiQbftBlockHeaderFunctions.forCmsSignature(pkiQbftExtraDataCodec).hash(block.getHeader()); - - final Bytes cms = cmsCreator.create(hashWithoutCms); - - final BftExtraData previousExtraData = pkiQbftExtraDataCodec.decode(blockHeader); - final BftExtraData substituteExtraData = new PkiQbftExtraData(previousExtraData, cms); - final Bytes substituteExtraDataBytes = pkiQbftExtraDataCodec.encode(substituteExtraData); - - final BlockHeaderBuilder headerBuilder = BlockHeaderBuilder.fromHeader(blockHeader); - headerBuilder - .extraData(substituteExtraDataBytes) - .blockHeaderFunctions(BftBlockHeaderFunctions.forCommittedSeal(pkiQbftExtraDataCodec)); - final BlockHeader newHeader = headerBuilder.buildBlockHeader(); - - LOG.debug("Created CMS with signed hash {} for block {}", hashWithoutCms, newHeader.getHash()); - - return new BlockCreationResult( - new Block(newHeader, block.getBody()), - blockCreationResult.getTransactionSelectionResults()); - } -} diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactory.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactory.java index 5bb87363b39..10f61713d9e 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactory.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/blockcreation/QbftBlockCreatorFactory.java @@ -20,10 +20,8 @@ import org.hyperledger.besu.consensus.common.bft.BftExtraData; import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec; import org.hyperledger.besu.consensus.common.bft.blockcreation.BftBlockCreatorFactory; -import org.hyperledger.besu.consensus.qbft.QbftContext; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.ProtocolContext; -import org.hyperledger.besu.ethereum.blockcreation.BlockCreator; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; @@ -69,18 +67,6 @@ public QbftBlockCreatorFactory( ethScheduler); } - @Override - public BlockCreator create(final BlockHeader parentHeader, final int round) { - final BlockCreator blockCreator = super.create(parentHeader, round); - final QbftContext qbftContext = protocolContext.getConsensusContext(QbftContext.class); - if (qbftContext.getPkiBlockCreationConfiguration().isEmpty()) { - return blockCreator; - } else { - return new PkiQbftBlockCreator( - blockCreator, qbftContext.getPkiBlockCreationConfiguration().get(), bftExtraDataCodec); - } - } - @Override public Bytes createExtraData(final int round, final BlockHeader parentHeader) { if (forksSchedule.getFork(parentHeader.getNumber() + 1L).getValue().isValidatorContractMode()) { diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/jsonrpc/QbftJsonRpcMethods.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/jsonrpc/QbftJsonRpcMethods.java index 47b10bc3ed4..ce8ce388f26 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/jsonrpc/QbftJsonRpcMethods.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/jsonrpc/QbftJsonRpcMethods.java @@ -14,11 +14,13 @@ */ package org.hyperledger.besu.consensus.qbft.jsonrpc; +import org.hyperledger.besu.config.BftConfigOptions; import org.hyperledger.besu.consensus.common.BlockInterface; import org.hyperledger.besu.consensus.common.bft.BftContext; import org.hyperledger.besu.consensus.common.validator.ValidatorProvider; import org.hyperledger.besu.consensus.qbft.jsonrpc.methods.QbftDiscardValidatorVote; import org.hyperledger.besu.consensus.qbft.jsonrpc.methods.QbftGetPendingVotes; +import org.hyperledger.besu.consensus.qbft.jsonrpc.methods.QbftGetRequestTimeoutSeconds; import org.hyperledger.besu.consensus.qbft.jsonrpc.methods.QbftGetSignerMetrics; import org.hyperledger.besu.consensus.qbft.jsonrpc.methods.QbftGetValidatorsByBlockHash; import org.hyperledger.besu.consensus.qbft.jsonrpc.methods.QbftGetValidatorsByBlockNumber; @@ -28,6 +30,8 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.methods.ApiGroupJsonRpcMethods; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; +import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import java.util.Map; @@ -36,17 +40,30 @@ public class QbftJsonRpcMethods extends ApiGroupJsonRpcMethods { private final ProtocolContext context; private final ValidatorProvider readOnlyValidatorProvider; + private final ProtocolSchedule protocolSchedule; + private final MiningParameters miningParameters; + private final BftConfigOptions bftConfig; /** * Instantiates a new Qbft json rpc methods. * - * @param context the context + * @param context the protocol context + * @param protocolSchedule the protocol schedule + * @param miningParameters the mining parameters * @param readOnlyValidatorProvider the read only validator provider + * @param bftConfig the BFT config options, containing QBFT-specific settings */ public QbftJsonRpcMethods( - final ProtocolContext context, final ValidatorProvider readOnlyValidatorProvider) { + final ProtocolContext context, + final ProtocolSchedule protocolSchedule, + final MiningParameters miningParameters, + final ValidatorProvider readOnlyValidatorProvider, + final BftConfigOptions bftConfig) { this.context = context; this.readOnlyValidatorProvider = readOnlyValidatorProvider; + this.protocolSchedule = protocolSchedule; + this.miningParameters = miningParameters; + this.bftConfig = bftConfig; } @Override @@ -57,17 +74,25 @@ protected String getApiGroup() { @Override protected Map create() { final BlockchainQueries blockchainQueries = - new BlockchainQueries(context.getBlockchain(), context.getWorldStateArchive()); + new BlockchainQueries( + protocolSchedule, + context.getBlockchain(), + context.getWorldStateArchive(), + miningParameters); final BftContext bftContext = context.getConsensusContext(BftContext.class); final BlockInterface blockInterface = bftContext.getBlockInterface(); final ValidatorProvider validatorProvider = bftContext.getValidatorProvider(); - return mapOf( - new QbftProposeValidatorVote(validatorProvider), - new QbftGetValidatorsByBlockNumber(blockchainQueries, readOnlyValidatorProvider), - new QbftDiscardValidatorVote(validatorProvider), - new QbftGetValidatorsByBlockHash(context.getBlockchain(), readOnlyValidatorProvider), - new QbftGetSignerMetrics(readOnlyValidatorProvider, blockInterface, blockchainQueries), - new QbftGetPendingVotes(validatorProvider)); + Map methods = + mapOf( + new QbftProposeValidatorVote(validatorProvider), + new QbftGetValidatorsByBlockNumber(blockchainQueries, readOnlyValidatorProvider), + new QbftDiscardValidatorVote(validatorProvider), + new QbftGetValidatorsByBlockHash(context.getBlockchain(), readOnlyValidatorProvider), + new QbftGetSignerMetrics(readOnlyValidatorProvider, blockInterface, blockchainQueries), + new QbftGetPendingVotes(validatorProvider), + new QbftGetRequestTimeoutSeconds(bftConfig)); + + return methods; } } diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftDiscardValidatorVote.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftDiscardValidatorVote.java index 7e9a3b973fd..66e9cf60143 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftDiscardValidatorVote.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftDiscardValidatorVote.java @@ -18,7 +18,9 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -49,7 +51,15 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { if (validatorProvider.getVoteProviderAtHead().isPresent()) { - final Address validatorAddress = requestContext.getRequiredParameter(0, Address.class); + final Address validatorAddress; + try { + validatorAddress = requestContext.getRequiredParameter(0, Address.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid validator address parameter (index 0)", + RpcErrorType.INVALID_ADDRESS_PARAMS, + e); + } LOG.trace("Received RPC rpcName={} address={}", getName(), validatorAddress); validatorProvider.getVoteProviderAtHead().get().discardVote(validatorAddress); diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftGetRequestTimeoutSeconds.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftGetRequestTimeoutSeconds.java new file mode 100644 index 00000000000..0cd12c95e75 --- /dev/null +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftGetRequestTimeoutSeconds.java @@ -0,0 +1,51 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.consensus.qbft.jsonrpc.methods; + +import org.hyperledger.besu.config.BftConfigOptions; +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; + +/** + * Implements the qbft_getRequestTimeoutSeconds RPC method to retrieve the QBFT request timeout in + * seconds. + */ +public class QbftGetRequestTimeoutSeconds implements JsonRpcMethod { + + private final BftConfigOptions bftConfig; + + /** + * Constructs a new QbftGetRequestTimeoutSeconds instance. + * + * @param bftConfig The BFT configuration options + */ + public QbftGetRequestTimeoutSeconds(final BftConfigOptions bftConfig) { + this.bftConfig = bftConfig; + } + + @Override + public String getName() { + return RpcMethod.QBFT_GET_REQUEST_TIMEOUT_SECONDS.getMethodName(); + } + + @Override + public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { + return new JsonRpcSuccessResponse( + requestContext.getRequest().getId(), bftConfig.getRequestTimeoutSeconds()); + } +} diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftGetValidatorsByBlockHash.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftGetValidatorsByBlockHash.java index 4726e81bd33..75000bde5d3 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftGetValidatorsByBlockHash.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftGetValidatorsByBlockHash.java @@ -19,9 +19,12 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockHeader; @@ -62,7 +65,13 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { } private Object blockResult(final JsonRpcRequestContext request) { - final Hash hash = request.getRequiredParameter(0, Hash.class); + final Hash hash; + try { + hash = request.getRequiredParameter(0, Hash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block hash parameter (index 0)", RpcErrorType.INVALID_BLOCK_HASH_PARAMS, e); + } LOG.trace("Received RPC rpcName={} blockHash={}", getName(), hash); final Optional blockHeader = blockchain.getBlockHeader(hash); return blockHeader diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftGetValidatorsByBlockNumber.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftGetValidatorsByBlockNumber.java index 6210d97bbbe..b33490f0c22 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftGetValidatorsByBlockNumber.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftGetValidatorsByBlockNumber.java @@ -18,9 +18,12 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.AbstractBlockParameterMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.core.BlockHeader; @@ -50,7 +53,21 @@ public QbftGetValidatorsByBlockNumber( @Override protected BlockParameter blockParameter(final JsonRpcRequestContext request) { - return request.getRequiredParameter(0, BlockParameter.class); + try { + return request.getRequiredParameter(0, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } + } + + @Override + protected Object pendingResult(final JsonRpcRequestContext request) { + final BlockHeader blockHeader = getBlockchainQueries().headBlockHeader(); + LOG.trace("Received RPC rpcName={} block={}", getName(), blockHeader.getNumber()); + return validatorProvider.getValidatorsAfterBlock(blockHeader).stream() + .map(Address::toString) + .collect(Collectors.toList()); } @Override diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftProposeValidatorVote.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftProposeValidatorVote.java index 160ca144d69..e6ad3ff6a89 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftProposeValidatorVote.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftProposeValidatorVote.java @@ -19,7 +19,9 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -50,8 +52,22 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { if (validatorProvider.getVoteProviderAtHead().isPresent()) { - final Address validatorAddress = requestContext.getRequiredParameter(0, Address.class); - final Boolean add = requestContext.getRequiredParameter(1, Boolean.class); + final Address validatorAddress; + try { + validatorAddress = requestContext.getRequiredParameter(0, Address.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid validator address parameter (index 0)", + RpcErrorType.INVALID_ADDRESS_PARAMS, + e); + } + final Boolean add; + try { + add = requestContext.getRequiredParameter(1, Boolean.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid vote type parameter (index 1)", RpcErrorType.INVALID_VOTE_TYPE_PARAMS, e); + } LOG.trace( "Received RPC rpcName={} voteType={} address={}", getName(), diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/messagedata/QbftV1.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/messagedata/QbftV1.java index d4ad566c939..ef42312b37a 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/messagedata/QbftV1.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/messagedata/QbftV1.java @@ -15,16 +15,19 @@ package org.hyperledger.besu.consensus.qbft.messagedata; /** Message codes for QBFT v1 messages */ -public class QbftV1 { +public interface QbftV1 { /** The constant PROPOSAL. */ - public static final int PROPOSAL = 0x12; + int PROPOSAL = 0x12; + /** The constant PREPARE. */ - public static final int PREPARE = 0x13; + int PREPARE = 0x13; + /** The constant COMMIT. */ - public static final int COMMIT = 0x14; + int COMMIT = 0x14; + /** The constant ROUND_CHANGE. */ - public static final int ROUND_CHANGE = 0x15; + int ROUND_CHANGE = 0x15; /** The constant MESSAGE_SPACE. */ - public static final int MESSAGE_SPACE = 0x16; + int MESSAGE_SPACE = 0x16; } diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/payload/QbftPayload.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/payload/QbftPayload.java index c90a12cad04..4cc64e9d0d0 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/payload/QbftPayload.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/payload/QbftPayload.java @@ -23,6 +23,8 @@ /** The Qbft payload. */ public abstract class QbftPayload implements Payload { + /** Default constructor */ + protected QbftPayload() {} /** * Write consensus round. diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/DefaultKeyStoreWrapperProvider.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/DefaultKeyStoreWrapperProvider.java deleted file mode 100644 index b40edc09526..00000000000 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/DefaultKeyStoreWrapperProvider.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright Copyright contributors to Hyperledger Besu. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See - * the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.besu.consensus.qbft.pki; - -import org.hyperledger.besu.pki.keystore.HardwareKeyStoreWrapper; -import org.hyperledger.besu.pki.keystore.KeyStoreWrapper; -import org.hyperledger.besu.pki.keystore.SoftwareKeyStoreWrapper; - -import java.nio.file.Path; - -import com.google.common.annotations.VisibleForTesting; - -/** The Default key store wrapper provider. */ -public class DefaultKeyStoreWrapperProvider implements KeyStoreWrapperProvider { - - private final HardwareKeyStoreWrapperProvider hardwareKeyStoreWrapperProvider; - private final SoftwareKeyStoreWrapperProvider softwareKeyStoreWrapperProvider; - - /** Instantiates a new Default key store wrapper provider. */ - DefaultKeyStoreWrapperProvider() { - this(HardwareKeyStoreWrapper::new, SoftwareKeyStoreWrapper::new); - } - - /** - * Instantiates a new Default key store wrapper provider. - * - * @param hardwareKeyStoreWrapperProvider the hardware key store wrapper provider - * @param softwareKeyStoreWrapperProvider the software key store wrapper provider - */ - @VisibleForTesting - DefaultKeyStoreWrapperProvider( - final HardwareKeyStoreWrapperProvider hardwareKeyStoreWrapperProvider, - final SoftwareKeyStoreWrapperProvider softwareKeyStoreWrapperProvider) { - this.hardwareKeyStoreWrapperProvider = hardwareKeyStoreWrapperProvider; - this.softwareKeyStoreWrapperProvider = softwareKeyStoreWrapperProvider; - } - - @Override - public KeyStoreWrapper apply( - final String keyStoreType, - final Path keyStorePath, - final String keyStorePassword, - final Path crl) { - if (KeyStoreWrapper.KEYSTORE_TYPE_PKCS11.equalsIgnoreCase(keyStoreType)) { - return hardwareKeyStoreWrapperProvider.get(keyStorePassword, keyStorePath, crl); - } else { - return softwareKeyStoreWrapperProvider.get(keyStoreType, keyStorePath, keyStorePassword, crl); - } - } - - /** The interface Hardware key store wrapper provider. */ - interface HardwareKeyStoreWrapperProvider { - - /** - * Get hardware key store wrapper. - * - * @param keystorePassword the keystore password - * @param config the config - * @param crlLocation the crl location - * @return the hardware key store wrapper - */ - HardwareKeyStoreWrapper get( - final String keystorePassword, final Path config, final Path crlLocation); - } - - /** The interface Software key store wrapper provider. */ - interface SoftwareKeyStoreWrapperProvider { - - /** - * Get software key store wrapper. - * - * @param keystoreType the keystore type - * @param keystoreLocation the keystore location - * @param keystorePassword the keystore password - * @param crlLocation the crl location - * @return the software key store wrapper - */ - SoftwareKeyStoreWrapper get( - final String keystoreType, - final Path keystoreLocation, - final String keystorePassword, - final Path crlLocation); - } -} diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/KeyStoreWrapperProvider.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/KeyStoreWrapperProvider.java deleted file mode 100644 index 9fd7adde0c5..00000000000 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/KeyStoreWrapperProvider.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.besu.consensus.qbft.pki; - -import org.hyperledger.besu.pki.keystore.KeyStoreWrapper; - -import java.nio.file.Path; - -@FunctionalInterface -interface KeyStoreWrapperProvider { - - KeyStoreWrapper apply( - final String keyStoreType, - final Path keyStorePath, - final String keyStorePassword, - final Path crl); -} diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiBlockCreationConfiguration.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiBlockCreationConfiguration.java deleted file mode 100644 index 399886119bf..00000000000 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiBlockCreationConfiguration.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.besu.consensus.qbft.pki; - -import org.hyperledger.besu.pki.keystore.KeyStoreWrapper; - -/** The Pki block creation configuration. */ -public class PkiBlockCreationConfiguration { - - private final KeyStoreWrapper keyStore; - private final KeyStoreWrapper trustStore; - private final String certificateAlias; - - /** - * Instantiates a new Pki block creation configuration. - * - * @param keyStore the key store - * @param trustStore the trust store - * @param certificateAlias the certificate alias - */ - public PkiBlockCreationConfiguration( - final KeyStoreWrapper keyStore, - final KeyStoreWrapper trustStore, - final String certificateAlias) { - this.keyStore = keyStore; - this.trustStore = trustStore; - this.certificateAlias = certificateAlias; - } - - /** - * Gets key store. - * - * @return the key store - */ - public KeyStoreWrapper getKeyStore() { - return keyStore; - } - - /** - * Gets trust store. - * - * @return the trust store - */ - public KeyStoreWrapper getTrustStore() { - return trustStore; - } - - /** - * Gets certificate alias. - * - * @return the certificate alias - */ - public String getCertificateAlias() { - return certificateAlias; - } -} diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiBlockCreationConfigurationProvider.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiBlockCreationConfigurationProvider.java deleted file mode 100644 index 8dfb6048a1a..00000000000 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiBlockCreationConfigurationProvider.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.besu.consensus.qbft.pki; - -import static com.google.common.base.Preconditions.checkNotNull; - -import org.hyperledger.besu.pki.config.PkiKeyStoreConfiguration; -import org.hyperledger.besu.pki.keystore.KeyStoreWrapper; - -import com.google.common.annotations.VisibleForTesting; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** The Pki block creation configuration provider. */ -public class PkiBlockCreationConfigurationProvider { - - private static final Logger LOG = - LoggerFactory.getLogger(PkiBlockCreationConfigurationProvider.class); - - private final KeyStoreWrapperProvider keyStoreWrapperProvider; - - /** Instantiates a new Pki block creation configuration provider. */ - public PkiBlockCreationConfigurationProvider() { - this(new DefaultKeyStoreWrapperProvider()); - } - - /** - * Instantiates a new Pki block creation configuration provider. - * - * @param keyStoreWrapperProvider the key store wrapper provider - */ - @VisibleForTesting - PkiBlockCreationConfigurationProvider(final KeyStoreWrapperProvider keyStoreWrapperProvider) { - this.keyStoreWrapperProvider = checkNotNull(keyStoreWrapperProvider); - } - - /** - * Load pki block creation configuration. - * - * @param pkiKeyStoreConfiguration the pki key store configuration - * @return the pki block creation configuration - */ - public PkiBlockCreationConfiguration load( - final PkiKeyStoreConfiguration pkiKeyStoreConfiguration) { - KeyStoreWrapper keyStore; - try { - keyStore = - keyStoreWrapperProvider.apply( - pkiKeyStoreConfiguration.getKeyStoreType(), - pkiKeyStoreConfiguration.getKeyStorePath(), - pkiKeyStoreConfiguration.getKeyStorePassword(), - null); - LOG.info("Loaded PKI Block Creation KeyStore {}", pkiKeyStoreConfiguration.getKeyStorePath()); - } catch (Exception e) { - throw new IllegalStateException("Error loading PKI Block Creation KeyStore", e); - } - - KeyStoreWrapper trustStore; - try { - trustStore = - keyStoreWrapperProvider.apply( - pkiKeyStoreConfiguration.getTrustStoreType(), - pkiKeyStoreConfiguration.getTrustStorePath(), - pkiKeyStoreConfiguration.getTrustStorePassword(), - pkiKeyStoreConfiguration.getCrlFilePath().orElse(null)); - LOG.info( - "Loaded PKI Block Creation TrustStore {}", pkiKeyStoreConfiguration.getTrustStorePath()); - } catch (Exception e) { - throw new IllegalStateException("Error loading PKI Block Creation TrustStore", e); - } - - return new PkiBlockCreationConfiguration( - keyStore, trustStore, pkiKeyStoreConfiguration.getCertificateAlias()); - } -} diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftBlockHashing.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftBlockHashing.java deleted file mode 100644 index f3640d4d508..00000000000 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftBlockHashing.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.besu.consensus.qbft.pki; - -import org.hyperledger.besu.consensus.common.bft.BftBlockHashing; -import org.hyperledger.besu.consensus.common.bft.BftExtraData; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.core.BlockHeader; - -/** The Pki qbft block hashing. */ -public class PkiQbftBlockHashing { - - private final PkiQbftExtraDataCodec extraDataCodec; - - /** - * Instantiates a new Pki qbft block hashing. - * - * @param extraDataCodec the extra data codec - */ - public PkiQbftBlockHashing(final PkiQbftExtraDataCodec extraDataCodec) { - this.extraDataCodec = extraDataCodec; - } - - /** - * Calculate hash of bft block for cms signature. - * - * @param header the header - * @return the hash - */ - public Hash calculateHashOfBftBlockForCmsSignature(final BlockHeader header) { - final BftExtraData bftExtraData = extraDataCodec.decode(header); - return Hash.hash( - BftBlockHashing.serializeHeader( - header, () -> extraDataCodec.encodeWithoutCms(bftExtraData), extraDataCodec)); - } -} diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftBlockHeaderFunctions.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftBlockHeaderFunctions.java deleted file mode 100644 index 9dbc1f032b5..00000000000 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftBlockHeaderFunctions.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.besu.consensus.qbft.pki; - -import org.hyperledger.besu.consensus.common.bft.BftBlockHeaderFunctions; -import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; - -/** The Pki qbft block header functions. */ -public class PkiQbftBlockHeaderFunctions { - - /** - * Create block header functions for cms signature. - * - * @param bftExtraDataCodec the bft extra data codec - * @return the block header functions - */ - public static BlockHeaderFunctions forCmsSignature( - final PkiQbftExtraDataCodec bftExtraDataCodec) { - return new BftBlockHeaderFunctions( - h -> new PkiQbftBlockHashing(bftExtraDataCodec).calculateHashOfBftBlockForCmsSignature(h), - bftExtraDataCodec); - } -} diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftExtraData.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftExtraData.java deleted file mode 100644 index 9de2247c2f7..00000000000 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftExtraData.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.besu.consensus.qbft.pki; - -import org.hyperledger.besu.consensus.common.bft.BftExtraData; -import org.hyperledger.besu.consensus.common.bft.Vote; -import org.hyperledger.besu.crypto.SECPSignature; -import org.hyperledger.besu.datatypes.Address; - -import java.util.Collection; -import java.util.Optional; - -import org.apache.tuweni.bytes.Bytes; - -/** The Pki Qbft extra data. */ -public class PkiQbftExtraData extends BftExtraData { - - private final Bytes cms; - - /** - * Instantiates a new Pki Qbft extra data. - * - * @param vanityData the vanity data - * @param seals the seals - * @param vote the vote - * @param round the round - * @param validators the validators - * @param cms the cms - */ - public PkiQbftExtraData( - final Bytes vanityData, - final Collection seals, - final Optional vote, - final int round, - final Collection
validators, - final Bytes cms) { - super(vanityData, seals, vote, round, validators); - this.cms = cms; - } - - /** - * Instantiates a new Pki Qbft extra data. - * - * @param bftExtraData the bft extra data - * @param cms the cms - */ - public PkiQbftExtraData(final BftExtraData bftExtraData, final Bytes cms) { - this( - bftExtraData.getVanityData(), - bftExtraData.getSeals(), - bftExtraData.getVote(), - bftExtraData.getRound(), - bftExtraData.getValidators(), - cms); - } - - /** - * Gets cms. - * - * @return the cms - */ - public Bytes getCms() { - return cms; - } -} diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftExtraDataCodec.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftExtraDataCodec.java deleted file mode 100644 index 1493f1d0a2a..00000000000 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftExtraDataCodec.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See - * the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.consensus.qbft.pki; - -import org.hyperledger.besu.consensus.common.bft.BftExtraData; -import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec; -import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; -import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; -import org.hyperledger.besu.ethereum.rlp.RLPInput; - -import java.util.List; - -import org.apache.tuweni.bytes.Bytes; - -/** - * The PkiQbftExtraData encoding format is different from the "regular" QbftExtraData encoding. We - * have an extra bytes element in the end of the list. - */ -public class PkiQbftExtraDataCodec extends QbftExtraDataCodec { - - /** The constant QBFT_EXTRA_DATA_LIST_SIZE. */ - public static final int QBFT_EXTRA_DATA_LIST_SIZE = 5; - - @Override - public BftExtraData decodeRaw(final Bytes input) { - if (input.isEmpty()) { - throw new IllegalArgumentException("Invalid Bytes supplied - Bft Extra Data required."); - } - - final BftExtraData bftExtraData = super.decodeRaw(input); - - final RLPInput rlpInput = new BytesValueRLPInput(input, false); - - final Bytes cms; - final List elements = rlpInput.readList(RLPInput::readAsRlp); - if (elements.size() > QBFT_EXTRA_DATA_LIST_SIZE) { - final RLPInput cmsElement = elements.get(elements.size() - 1); - cms = cmsElement.readBytes(); - } else { - cms = Bytes.EMPTY; - } - - return new PkiQbftExtraData(bftExtraData, cms); - } - - @Override - protected Bytes encode(final BftExtraData bftExtraData, final EncodingType encodingType) { - return encode(bftExtraData, encodingType, true); - } - - private Bytes encode( - final BftExtraData bftExtraData, final EncodingType encodingType, final boolean includeCms) { - final Bytes encoded = super.encode(bftExtraData, encodingType); - if (!(bftExtraData instanceof PkiQbftExtraData) || !includeCms) { - return encoded; - } - - final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput(); - rlpOutput.startList(); - // Read through extraData RLP list elements and write them to the new RLP output - new BytesValueRLPInput(encoded, false) - .readList(RLPInput::readAsRlp).stream() - .map(RLPInput::raw) - .forEach(rlpOutput::writeRLPBytes); - rlpOutput.writeBytes(((PkiQbftExtraData) bftExtraData).getCms()); - rlpOutput.endList(); - - return rlpOutput.encoded(); - } - - /** - * Encode without cms. - * - * @param bftExtraData the bft extra data - * @return the bytes - */ - public Bytes encodeWithoutCms(final BftExtraData bftExtraData) { - return encode(bftExtraData, EncodingType.ALL, false); - } -} diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/protocol/Istanbul100SubProtocol.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/protocol/Istanbul100SubProtocol.java index bbf242e29ea..37f04391cbd 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/protocol/Istanbul100SubProtocol.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/protocol/Istanbul100SubProtocol.java @@ -23,11 +23,15 @@ public class Istanbul100SubProtocol implements SubProtocol { /** The constant NAME. */ public static String NAME = "istanbul"; + /** The constant ISTANBUL_100. */ public static final Capability ISTANBUL_100 = Capability.create(NAME, 100); private static final Istanbul100SubProtocol INSTANCE = new Istanbul100SubProtocol(); + /** Default constructor */ + public Istanbul100SubProtocol() {} + /** * Get istanbul100 sub protocol. * diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/CreateBlockForProposalBehaviour.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/CreateBlockForProposalBehaviour.java index 806c3bcdfd7..2a29e8452e3 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/CreateBlockForProposalBehaviour.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/CreateBlockForProposalBehaviour.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.consensus.qbft.statemachine; import org.hyperledger.besu.ethereum.core.Block; diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManager.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManager.java index 45e97ed6795..f79537cc0f9 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManager.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManager.java @@ -27,6 +27,7 @@ import org.hyperledger.besu.consensus.qbft.payload.MessageFactory; import org.hyperledger.besu.consensus.qbft.validation.FutureRoundProposalMessageValidator; import org.hyperledger.besu.consensus.qbft.validation.MessageValidatorFactory; +import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.plugin.services.securitymodule.SecurityModuleException; @@ -126,6 +127,9 @@ public void handleBlockTimerExpiry(final ConsensusRoundIdentifier roundIdentifie startNewRound(0); final QbftRound qbftRound = currentRound.get(); + + logValidatorChanges(qbftRound); + // mining will be checked against round 0 as the current round is initialised to 0 above final boolean isProposer = finalState.isLocalNodeProposerForRound(qbftRound.getRoundIdentifier()); @@ -143,6 +147,30 @@ public void handleBlockTimerExpiry(final ConsensusRoundIdentifier roundIdentifie } } + /** + * If the list of validators for the next block to be proposed/imported has changed from the + * previous block, log the change. Only log for round 0 (i.e. once per block). + * + * @param qbftRound The current round + */ + private void logValidatorChanges(final QbftRound qbftRound) { + if (qbftRound.getRoundIdentifier().getRoundNumber() == 0) { + final Collection
previousValidators = + MessageValidatorFactory.getValidatorsForBlock(qbftRound.protocolContext, parentHeader); + final Collection
validatorsForHeight = + MessageValidatorFactory.getValidatorsAfterBlock(qbftRound.protocolContext, parentHeader); + if (!(validatorsForHeight.containsAll(previousValidators)) + || !(previousValidators.containsAll(validatorsForHeight))) { + LOG.info( + "Validator list change. Previous chain height {}: {}. Current chain height {}: {}.", + parentHeader.getNumber(), + previousValidators, + parentHeader.getNumber() + 1, + validatorsForHeight); + } + } + } + @Override public void roundExpired(final RoundExpiry expire) { if (currentRound.isEmpty()) { @@ -246,17 +274,26 @@ private

> void actionOrBufferMessage( @Override public void handleRoundChangePayload(final RoundChange message) { final ConsensusRoundIdentifier targetRound = message.getRoundIdentifier(); - LOG.trace("Received a RoundChange Payload for {}", targetRound); + + LOG.debug( + "Round change from {}: block {}, round {}", + message.getAuthor(), + message.getRoundIdentifier().getSequenceNumber(), + message.getRoundIdentifier().getRoundNumber()); + + // Diagnostic logging (only logs anything if the chain has stalled) + roundChangeManager.storeAndLogRoundChangeSummary(message); final MessageAge messageAge = determineAgeOfPayload(message.getRoundIdentifier().getRoundNumber()); if (messageAge == MessageAge.PRIOR_ROUND) { - LOG.trace("Received RoundChange Payload for a prior round. targetRound={}", targetRound); + LOG.debug("Received RoundChange Payload for a prior round. targetRound={}", targetRound); return; } final Optional> result = roundChangeManager.appendRoundChangeMessage(message); + if (result.isPresent()) { LOG.debug( "Received sufficient RoundChange messages to change round to targetRound={}", diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManagerFactory.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManagerFactory.java index d4b463e5261..889f83f98a7 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManagerFactory.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManagerFactory.java @@ -21,9 +21,14 @@ import org.hyperledger.besu.consensus.qbft.validator.ValidatorModeTransitionLogger; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** The Qbft block height manager factory. */ public class QbftBlockHeightManagerFactory { + private static final Logger LOG = LoggerFactory.getLogger(QbftBlockHeightManagerFactory.class); + private final QbftRoundFactory roundFactory; private final BftFinalState finalState; private final MessageValidatorFactory messageValidatorFactory; @@ -62,8 +67,10 @@ public BaseQbftBlockHeightManager create(final BlockHeader parentHeader) { validatorModeTransitionLogger.logTransitionChange(parentHeader); if (finalState.isLocalNodeValidator()) { + LOG.debug("Local node is a validator"); return createFullBlockHeightManager(parentHeader); } else { + LOG.debug("Local node is a non-validator"); return createNoOpBlockHeightManager(parentHeader); } } @@ -79,7 +86,8 @@ private BaseQbftBlockHeightManager createFullBlockHeightManager(final BlockHeade new RoundChangeManager( BftHelpers.calculateRequiredValidatorQuorum(finalState.getValidators().size()), messageValidatorFactory.createRoundChangeMessageValidator( - parentHeader.getNumber() + 1L, parentHeader)), + parentHeader.getNumber() + 1L, parentHeader), + finalState.getLocalAddress()), roundFactory, finalState.getClock(), messageValidatorFactory, diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftController.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftController.java index c639a0f2057..57737542eaf 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftController.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftController.java @@ -48,7 +48,7 @@ public class QbftController extends BaseBftController { * @param gossiper the gossiper * @param duplicateMessageTracker the duplicate message tracker * @param futureMessageBuffer the future message buffer - * @param sychronizerUpdater the sychronizer updater + * @param synchronizerUpdater the synchronizer updater * @param bftExtraDataCodec the bft extra data codec */ public QbftController( @@ -58,7 +58,7 @@ public QbftController( final Gossiper gossiper, final MessageTracker duplicateMessageTracker, final FutureMessageBuffer futureMessageBuffer, - final SynchronizerUpdater sychronizerUpdater, + final SynchronizerUpdater synchronizerUpdater, final BftExtraDataCodec bftExtraDataCodec) { super( @@ -67,7 +67,7 @@ public QbftController( gossiper, duplicateMessageTracker, futureMessageBuffer, - sychronizerUpdater); + synchronizerUpdater); this.qbftBlockHeightManagerFactory = qbftBlockHeightManagerFactory; this.bftExtraDataCodec = bftExtraDataCodec; } diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRound.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRound.java index c31ee8be7d6..f7c43ef43dd 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRound.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRound.java @@ -44,6 +44,7 @@ import org.hyperledger.besu.ethereum.core.BlockImporter; import org.hyperledger.besu.ethereum.mainnet.BlockImportResult; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.plugin.services.securitymodule.SecurityModuleException; import org.hyperledger.besu.util.Subscribers; @@ -59,55 +60,65 @@ public class QbftRound { private static final Logger LOG = LoggerFactory.getLogger(QbftRound.class); private final Subscribers observers; + /** The Round state. */ protected final RoundState roundState; + /** The Block creator. */ protected final BlockCreator blockCreator; + /** The Protocol context. */ protected final ProtocolContext protocolContext; - private final BlockImporter blockImporter; + /** The Protocol schedule. */ + protected final ProtocolSchedule protocolSchedule; + private final NodeKey nodeKey; private final MessageFactory messageFactory; // used only to create stored local msgs private final QbftMessageTransmitter transmitter; + /** The Bft extra data codec. */ protected final BftExtraDataCodec bftExtraDataCodec; + private final BlockHeader parentHeader; + /** * Instantiates a new Qbft round. * * @param roundState the round state * @param blockCreator the block creator * @param protocolContext the protocol context - * @param blockImporter the block importer + * @param protocolSchedule the protocol schedule * @param observers the observers * @param nodeKey the node key * @param messageFactory the message factory * @param transmitter the transmitter * @param roundTimer the round timer * @param bftExtraDataCodec the bft extra data codec + * @param parentHeader the parent header */ public QbftRound( final RoundState roundState, final BlockCreator blockCreator, final ProtocolContext protocolContext, - final BlockImporter blockImporter, + final ProtocolSchedule protocolSchedule, final Subscribers observers, final NodeKey nodeKey, final MessageFactory messageFactory, final QbftMessageTransmitter transmitter, final RoundTimer roundTimer, - final BftExtraDataCodec bftExtraDataCodec) { + final BftExtraDataCodec bftExtraDataCodec, + final BlockHeader parentHeader) { this.roundState = roundState; this.blockCreator = blockCreator; this.protocolContext = protocolContext; - this.blockImporter = blockImporter; + this.protocolSchedule = protocolSchedule; this.observers = observers; this.nodeKey = nodeKey; this.messageFactory = messageFactory; this.transmitter = transmitter; this.bftExtraDataCodec = bftExtraDataCodec; - + this.parentHeader = parentHeader; roundTimer.startTimer(getRoundIdentifier()); } @@ -127,7 +138,8 @@ public ConsensusRoundIdentifier getRoundIdentifier() { */ public void createAndSendProposalMessage(final long headerTimeStampSeconds) { LOG.debug("Creating proposed block. round={}", roundState.getRoundIdentifier()); - final Block block = blockCreator.createBlock(headerTimeStampSeconds).getBlock(); + final Block block = + blockCreator.createBlock(headerTimeStampSeconds, this.parentHeader).getBlock(); LOG.trace("Creating proposed block blockHeader={}", block.getHeader()); updateStateWithProposalAndTransmit(block, emptyList(), emptyList()); @@ -147,7 +159,7 @@ public void startRoundWith( final Block blockToPublish; if (bestPreparedCertificate.isEmpty()) { LOG.debug("Sending proposal with new block. round={}", roundState.getRoundIdentifier()); - blockToPublish = blockCreator.createBlock(headerTimestamp).getBlock(); + blockToPublish = blockCreator.createBlock(headerTimestamp, this.parentHeader).getBlock(); } else { LOG.debug( "Sending proposal from PreparedCertificate. round={}", roundState.getRoundIdentifier()); @@ -341,7 +353,10 @@ private void importBlockToChain() { getRoundIdentifier(), blockToImport.getHash()); } + LOG.trace("Importing proposed block with extraData={}", extraData); + final BlockImporter blockImporter = + protocolSchedule.getByBlockHeader(blockToImport.getHeader()).getBlockImporter(); final BlockImportResult result = blockImporter.importBlock(protocolContext, blockToImport, HeaderValidationMode.FULL); if (!result.isImported()) { diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundFactory.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundFactory.java index 7f93a73d3b5..30253300096 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundFactory.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundFactory.java @@ -99,8 +99,7 @@ public QbftRound createNewRound(final BlockHeader parentHeader, final int round) */ public QbftRound createNewRoundWithState( final BlockHeader parentHeader, final RoundState roundState) { - final ConsensusRoundIdentifier roundIdentifier = roundState.getRoundIdentifier(); - final BlockCreator blockCreator = blockCreatorFactory.create(parentHeader, 0); + final BlockCreator blockCreator = blockCreatorFactory.create(0); // TODO(tmm): Why is this created everytime?! final QbftMessageTransmitter messageTransmitter = @@ -110,12 +109,13 @@ public QbftRound createNewRoundWithState( roundState, blockCreator, protocolContext, - protocolSchedule.getByBlockNumber(roundIdentifier.getSequenceNumber()).getBlockImporter(), + protocolSchedule, minedBlockObservers, finalState.getNodeKey(), messageFactory, messageTransmitter, finalState.getRoundTimer(), - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); } } diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/RoundChangeManager.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/RoundChangeManager.java index ca671a6f527..89e2888395c 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/RoundChangeManager.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/statemachine/RoundChangeManager.java @@ -100,19 +100,51 @@ public Collection createRoundChangeCertificate() { @VisibleForTesting final Map roundChangeCache = Maps.newHashMap(); + /** A summary of the latest round each validator is on, for diagnostic purposes only */ + private final Map roundSummary = Maps.newHashMap(); + private final long quorum; private final RoundChangeMessageValidator roundChangeMessageValidator; + private final Address localAddress; /** * Instantiates a new Round change manager. * * @param quorum the quorum * @param roundChangeMessageValidator the round change message validator + * @param localAddress this node's address */ public RoundChangeManager( - final long quorum, final RoundChangeMessageValidator roundChangeMessageValidator) { + final long quorum, + final RoundChangeMessageValidator roundChangeMessageValidator, + final Address localAddress) { this.quorum = quorum; this.roundChangeMessageValidator = roundChangeMessageValidator; + this.localAddress = localAddress; + } + + /** + * Store the latest round for a node, and if chain is stalled log a summary of which round each + * address is on + * + * @param message the round-change message that has just been received + */ + public void storeAndLogRoundChangeSummary(final RoundChange message) { + roundSummary.put(message.getAuthor(), message.getRoundIdentifier()); + if (roundChangeCache.keySet().stream() + .findFirst() + .orElse(new ConsensusRoundIdentifier(0, 0)) + .getRoundNumber() + >= 2) { + LOG.info("BFT round summary (quorum = {})", quorum); + for (Map.Entry nextEntry : roundSummary.entrySet()) { + LOG.info( + "Address: {} Round: {} {}", + nextEntry.getKey(), + nextEntry.getValue().getRoundNumber(), + nextEntry.getKey().equals(localAddress) ? "(Local node)" : ""); + } + } } /** diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validation/MessageValidatorFactory.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validation/MessageValidatorFactory.java index 2875d36ab3c..b5164746bf6 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validation/MessageValidatorFactory.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validation/MessageValidatorFactory.java @@ -23,7 +23,6 @@ import org.hyperledger.besu.consensus.common.bft.blockcreation.ProposerSelector; import org.hyperledger.besu.consensus.qbft.validation.MessageValidator.SubsequentMessageValidator; import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.ethereum.BlockValidator; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.core.BlockHeader; @@ -56,13 +55,36 @@ public MessageValidatorFactory( this.bftExtraDataCodec = bftExtraDataCodec; } - private Collection

getValidatorsAfterBlock(final BlockHeader parentHeader) { + /** + * Get the list of validators that are applicable after the given block + * + * @param protocolContext the protocol context + * @param parentHeader the parent header + * @return the list of validators + */ + public static Collection
getValidatorsAfterBlock( + final ProtocolContext protocolContext, final BlockHeader parentHeader) { return protocolContext .getConsensusContext(BftContext.class) .getValidatorProvider() .getValidatorsAfterBlock(parentHeader); } + /** + * Get the list of validators that are applicable for the given block + * + * @param protocolContext the protocol context + * @param parentHeader the parent header + * @return the list of validators + */ + public static Collection
getValidatorsForBlock( + final ProtocolContext protocolContext, final BlockHeader parentHeader) { + return protocolContext + .getConsensusContext(BftContext.class) + .getValidatorProvider() + .getValidatorsForBlock(parentHeader); + } + /** * Create round change message validator. * @@ -73,21 +95,19 @@ private Collection
getValidatorsAfterBlock(final BlockHeader parentHead public RoundChangeMessageValidator createRoundChangeMessageValidator( final long chainHeight, final BlockHeader parentHeader) { - final Collection
validatorsForHeight = getValidatorsAfterBlock(parentHeader); + final Collection
validatorsForHeight = + getValidatorsAfterBlock(protocolContext, parentHeader); final RoundChangePayloadValidator roundChangePayloadValidator = new RoundChangePayloadValidator(validatorsForHeight, chainHeight); - final BlockValidator blockValidator = - protocolSchedule.getByBlockNumber(chainHeight).getBlockValidator(); - return new RoundChangeMessageValidator( roundChangePayloadValidator, BftHelpers.calculateRequiredValidatorQuorum(validatorsForHeight.size()), chainHeight, validatorsForHeight, - blockValidator, - protocolContext); + protocolContext, + protocolSchedule); } /** @@ -99,15 +119,13 @@ public RoundChangeMessageValidator createRoundChangeMessageValidator( */ public MessageValidator createMessageValidator( final ConsensusRoundIdentifier roundIdentifier, final BlockHeader parentHeader) { - - final Collection
validatorsForHeight = getValidatorsAfterBlock(parentHeader); - final BlockValidator blockValidator = - protocolSchedule.getByBlockNumber(roundIdentifier.getSequenceNumber()).getBlockValidator(); + final Collection
validatorsForHeight = + getValidatorsAfterBlock(protocolContext, parentHeader); final ProposalValidator proposalValidator = new ProposalValidator( - blockValidator, protocolContext, + protocolSchedule, BftHelpers.calculateRequiredValidatorQuorum(validatorsForHeight.size()), validatorsForHeight, roundIdentifier, diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validation/ProposalPayloadValidator.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validation/ProposalPayloadValidator.java index f0df7aa6fa5..3d04f699c04 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validation/ProposalPayloadValidator.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validation/ProposalPayloadValidator.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 ConsenSys AG. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,24 +14,16 @@ */ package org.hyperledger.besu.consensus.qbft.validation; -import org.hyperledger.besu.consensus.common.bft.BftBlockInterface; -import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec; +import static com.google.common.base.Preconditions.checkState; + import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier; import org.hyperledger.besu.consensus.common.bft.payload.SignedData; -import org.hyperledger.besu.consensus.qbft.QbftContext; import org.hyperledger.besu.consensus.qbft.payload.ProposalPayload; -import org.hyperledger.besu.consensus.qbft.pki.PkiQbftBlockHeaderFunctions; -import org.hyperledger.besu.consensus.qbft.pki.PkiQbftExtraData; -import org.hyperledger.besu.consensus.qbft.pki.PkiQbftExtraDataCodec; import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.BlockValidator; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; -import org.hyperledger.besu.pki.cms.CmsValidator; - -import java.util.Optional; import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; @@ -47,35 +39,6 @@ public class ProposalPayloadValidator { private final ConsensusRoundIdentifier targetRound; private final BlockValidator blockValidator; private final ProtocolContext protocolContext; - private final BftExtraDataCodec bftExtraDataCodec; - private final Optional cmsValidator; - - /** - * Instantiates a new Proposal payload validator. - * - * @param expectedProposer the expected proposer - * @param targetRound the target round - * @param blockValidator the block validator - * @param protocolContext the protocol context - * @param bftExtraDataCodec the bft extra data codec - */ - public ProposalPayloadValidator( - final Address expectedProposer, - final ConsensusRoundIdentifier targetRound, - final BlockValidator blockValidator, - final ProtocolContext protocolContext, - final BftExtraDataCodec bftExtraDataCodec) { - this( - expectedProposer, - targetRound, - blockValidator, - protocolContext, - bftExtraDataCodec, - protocolContext - .getConsensusContext(QbftContext.class) - .getPkiBlockCreationConfiguration() - .map(config -> new CmsValidator(config.getTrustStore()))); - } /** * Instantiates a new Proposal payload validator. @@ -84,23 +47,17 @@ public ProposalPayloadValidator( * @param targetRound the target round * @param blockValidator the block validator * @param protocolContext the protocol context - * @param bftExtraDataCodec the bft extra data codec - * @param cmsValidator the cms validator */ @VisibleForTesting public ProposalPayloadValidator( final Address expectedProposer, final ConsensusRoundIdentifier targetRound, final BlockValidator blockValidator, - final ProtocolContext protocolContext, - final BftExtraDataCodec bftExtraDataCodec, - final Optional cmsValidator) { + final ProtocolContext protocolContext) { this.expectedProposer = expectedProposer; this.targetRound = targetRound; this.blockValidator = blockValidator; this.protocolContext = protocolContext; - this.bftExtraDataCodec = bftExtraDataCodec; - this.cmsValidator = cmsValidator; } /** @@ -133,20 +90,15 @@ public boolean validate(final SignedData signedPayload) { return false; } - if (cmsValidator.isPresent()) { - return validateCms( - block, - protocolContext.getConsensusContext(QbftContext.class).getBlockInterface(), - cmsValidator.get()); - } - return true; } private boolean validateBlock(final Block block) { + checkState(blockValidator != null, "block validation not possible, no block validator."); + final var validationResult = blockValidator.validateAndProcessBlock( - protocolContext, block, HeaderValidationMode.LIGHT, HeaderValidationMode.FULL); + protocolContext, block, HeaderValidationMode.LIGHT, HeaderValidationMode.FULL, false); if (!validationResult.isSuccessful()) { LOG.info( @@ -158,26 +110,4 @@ private boolean validateBlock(final Block block) { return true; } - - private boolean validateCms( - final Block block, - final BftBlockInterface bftBlockInterface, - final CmsValidator cmsValidator) { - final PkiQbftExtraData pkiExtraData = - (PkiQbftExtraData) bftBlockInterface.getExtraData(block.getHeader()); - - final Hash hashWithoutCms = - PkiQbftBlockHeaderFunctions.forCmsSignature((PkiQbftExtraDataCodec) bftExtraDataCodec) - .hash(block.getHeader()); - - LOG.debug("Validating CMS with signed hash {} in block {}", hashWithoutCms, block.getHash()); - - if (!cmsValidator.validate(pkiExtraData.getCms(), hashWithoutCms)) { - LOG.info("{}: invalid CMS in block {}", ERROR_PREFIX, block.getHash()); - return false; - } else { - LOG.trace("Valid CMS in block {}", block.getHash()); - return true; - } - } } diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validation/ProposalValidator.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validation/ProposalValidator.java index f63bf35d6d0..81cbffdff96 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validation/ProposalValidator.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validation/ProposalValidator.java @@ -33,6 +33,7 @@ import org.hyperledger.besu.ethereum.BlockValidator; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import java.util.Collection; import java.util.Comparator; @@ -49,8 +50,8 @@ public class ProposalValidator { private static final Logger LOG = LoggerFactory.getLogger(ProposalValidator.class); private static final String ERROR_PREFIX = "Invalid Proposal Payload"; - private final BlockValidator blockValidator; private final ProtocolContext protocolContext; + private final ProtocolSchedule protocolSchedule; private final int quorumMessageCount; private final Collection
validators; private final ConsensusRoundIdentifier roundIdentifier; @@ -60,8 +61,8 @@ public class ProposalValidator { /** * Instantiates a new Proposal validator. * - * @param blockValidator the block validator * @param protocolContext the protocol context + * @param protocolSchedule the protocol schedule * @param quorumMessageCount the quorum message count * @param validators the validators * @param roundIdentifier the round identifier @@ -69,15 +70,15 @@ public class ProposalValidator { * @param bftExtraDataCodec the bft extra data codec */ public ProposalValidator( - final BlockValidator blockValidator, final ProtocolContext protocolContext, + final ProtocolSchedule protocolSchedule, final int quorumMessageCount, final Collection
validators, final ConsensusRoundIdentifier roundIdentifier, final Address expectedProposer, final BftExtraDataCodec bftExtraDataCodec) { - this.blockValidator = blockValidator; this.protocolContext = protocolContext; + this.protocolSchedule = protocolSchedule; this.quorumMessageCount = quorumMessageCount; this.validators = validators; this.roundIdentifier = roundIdentifier; @@ -92,10 +93,12 @@ public ProposalValidator( * @return the boolean */ public boolean validate(final Proposal msg) { + final BlockValidator blockValidator = + protocolSchedule.getByBlockHeader(msg.getBlock().getHeader()).getBlockValidator(); final ProposalPayloadValidator payloadValidator = new ProposalPayloadValidator( - expectedProposer, roundIdentifier, blockValidator, protocolContext, bftExtraDataCodec); + expectedProposer, roundIdentifier, blockValidator, protocolContext); if (!payloadValidator.validate(msg.getSignedPayload())) { LOG.info("{}: invalid proposal payload in proposal message", ERROR_PREFIX); diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validation/RoundChangeMessageValidator.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validation/RoundChangeMessageValidator.java index 61b20343b31..0f0761d4621 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validation/RoundChangeMessageValidator.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validation/RoundChangeMessageValidator.java @@ -27,6 +27,7 @@ import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import java.util.Collection; import java.util.List; @@ -45,8 +46,8 @@ public class RoundChangeMessageValidator { private final long quorumMessageCount; private final long chainHeight; private final Collection
validators; - private final BlockValidator blockValidator; private final ProtocolContext protocolContext; + private final ProtocolSchedule protocolSchedule; /** * Instantiates a new Round change message validator. @@ -55,22 +56,22 @@ public class RoundChangeMessageValidator { * @param quorumMessageCount the quorum message count * @param chainHeight the chain height * @param validators the validators - * @param blockValidator the block validator * @param protocolContext the protocol context + * @param protocolSchedule the protocol context */ public RoundChangeMessageValidator( final RoundChangePayloadValidator roundChangePayloadValidator, final long quorumMessageCount, final long chainHeight, final Collection
validators, - final BlockValidator blockValidator, - final ProtocolContext protocolContext) { + final ProtocolContext protocolContext, + final ProtocolSchedule protocolSchedule) { this.roundChangePayloadValidator = roundChangePayloadValidator; this.quorumMessageCount = quorumMessageCount; this.chainHeight = chainHeight; this.validators = validators; - this.blockValidator = blockValidator; this.protocolContext = protocolContext; + this.protocolSchedule = protocolSchedule; } /** @@ -94,6 +95,10 @@ public boolean validate(final RoundChange msg) { } private boolean validateBlock(final Block block) { + + final BlockValidator blockValidator = + protocolSchedule.getByBlockHeader(block.getHeader()).getBlockValidator(); + final var validationResult = blockValidator.validateAndProcessBlock( protocolContext, block, HeaderValidationMode.LIGHT, HeaderValidationMode.FULL); diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ForkingValidatorProvider.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ForkingValidatorProvider.java index d24e61d75da..40d139e7d2e 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ForkingValidatorProvider.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ForkingValidatorProvider.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.consensus.qbft.validator; import org.hyperledger.besu.config.QbftConfigOptions; diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractController.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractController.java index a7b705e4a9d..5b561856ecf 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractController.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorContractController.java @@ -39,6 +39,7 @@ public class ValidatorContractController { /** The constant GET_VALIDATORS. */ public static final String GET_VALIDATORS = "getValidators"; + /** The constant CONTRACT_ERROR_MSG. */ public static final String CONTRACT_ERROR_MSG = "Failed validator smart contract call"; @@ -107,7 +108,7 @@ private List decodeResult( if (result.isSuccessful()) { final List decodedList = FunctionReturnDecoder.decode( - result.getResult().getOutput().toHexString(), function.getOutputParameters()); + result.result().getOutput().toHexString(), function.getOutputParameters()); if (decodedList.isEmpty()) { throw new IllegalStateException( diff --git a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorModeTransitionLogger.java b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorModeTransitionLogger.java index a25a32458a2..83c18262c66 100644 --- a/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorModeTransitionLogger.java +++ b/consensus/qbft/src/main/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorModeTransitionLogger.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.consensus.qbft.validator; import org.hyperledger.besu.config.QbftConfigOptions; diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/MutableQbftConfigOptionsTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/MutableQbftConfigOptionsTest.java index d41996d11db..8ad39adc5b5 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/MutableQbftConfigOptionsTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/MutableQbftConfigOptionsTest.java @@ -1,21 +1,17 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See - * the License for the + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.consensus.qbft; import static org.assertj.core.api.Assertions.assertThat; diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderUtils.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderUtils.java index 1daed4c1d46..7592aa7b079 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderUtils.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderUtils.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactoryTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactoryTest.java index 641e23db6fa..4771cf91cbd 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactoryTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftBlockHeaderValidationRulesetFactoryTest.java @@ -19,12 +19,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.consensus.common.bft.BftContextBuilder.setupContextWithBftExtraDataEncoder; +import org.hyperledger.besu.consensus.common.bft.BftContext; import org.hyperledger.besu.cryptoservices.NodeKey; import org.hyperledger.besu.cryptoservices.NodeKeyUtils; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.Util; @@ -44,9 +46,8 @@ private ProtocolContext protocolContext(final Collection
validators) { return new ProtocolContext( null, null, - setupContextWithBftExtraDataEncoder( - QbftContext.class, validators, new QbftExtraDataCodec()), - Optional.empty()); + setupContextWithBftExtraDataEncoder(BftContext.class, validators, new QbftExtraDataCodec()), + new BadBlockManager()); } @Test diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftExtraDataCodecTestUtils.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftExtraDataCodecTestUtils.java index 29903385111..9137ec55780 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftExtraDataCodecTestUtils.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftExtraDataCodecTestUtils.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.consensus.qbft; public class QbftExtraDataCodecTestUtils { diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftProtocolScheduleTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftProtocolScheduleTest.java index b0fe45ed562..020d6e0e5ae 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftProtocolScheduleTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/QbftProtocolScheduleTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -26,18 +26,22 @@ import org.hyperledger.besu.config.QbftConfigOptions; import org.hyperledger.besu.consensus.common.ForkSpec; import org.hyperledger.besu.consensus.common.ForksSchedule; +import org.hyperledger.besu.consensus.common.bft.BftContext; import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec; import org.hyperledger.besu.consensus.common.bft.BftProtocolSchedule; import org.hyperledger.besu.cryptoservices.NodeKey; import org.hyperledger.besu.cryptoservices.NodeKeyUtils; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.MilestoneStreamingProtocolSchedule; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.Util; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import java.math.BigInteger; import java.util.Collection; @@ -56,9 +60,8 @@ private ProtocolContext protocolContext(final Collection
validators) { return new ProtocolContext( null, null, - setupContextWithBftExtraDataEncoder( - QbftContext.class, validators, new QbftExtraDataCodec()), - Optional.empty()); + setupContextWithBftExtraDataEncoder(BftContext.class, validators, new QbftExtraDataCodec()), + new BadBlockManager()); } @Test @@ -134,7 +137,11 @@ private BftProtocolSchedule createProtocolSchedule( PrivacyParameters.DEFAULT, false, bftExtraDataCodec, - EvmConfiguration.DEFAULT); + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); } private boolean validateHeader( @@ -144,7 +151,7 @@ private boolean validateHeader( final BlockHeader blockHeader, final int block) { return schedule - .getByBlockNumber(block) + .getByBlockNumberOrTimestamp(block, blockHeader.getTimestamp()) .getBlockHeaderValidator() .validateHeader( blockHeader, parentHeader, protocolContext(validators), HeaderValidationMode.LIGHT); diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/blockcreation/PkiQbftBlockCreatorTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/blockcreation/PkiQbftBlockCreatorTest.java deleted file mode 100644 index 669563d6f5b..00000000000 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/blockcreation/PkiQbftBlockCreatorTest.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.consensus.qbft.blockcreation; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.hyperledger.besu.consensus.common.bft.BftExtraDataFixture.createExtraData; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.consensus.common.bft.BftBlockHeaderFunctions; -import org.hyperledger.besu.consensus.common.bft.BftExtraData; -import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec; -import org.hyperledger.besu.consensus.qbft.pki.PkiQbftBlockHeaderFunctions; -import org.hyperledger.besu.consensus.qbft.pki.PkiQbftExtraData; -import org.hyperledger.besu.consensus.qbft.pki.PkiQbftExtraDataCodec; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.blockcreation.BlockCreator; -import org.hyperledger.besu.ethereum.blockcreation.BlockCreator.BlockCreationResult; -import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults; -import org.hyperledger.besu.ethereum.core.Block; -import org.hyperledger.besu.ethereum.core.BlockBody; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; -import org.hyperledger.besu.pki.cms.CmsCreator; - -import java.util.Collections; - -import org.apache.tuweni.bytes.Bytes; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -public class PkiQbftBlockCreatorTest { - - private final PkiQbftExtraDataCodec extraDataCodec = new PkiQbftExtraDataCodec(); - - private BlockCreator blockCreator; - private CmsCreator cmsCreator; - private PkiQbftBlockCreator pkiQbftBlockCreator; - private BlockHeaderTestFixture blockHeaderBuilder; - - @BeforeEach - public void before() { - blockCreator = mock(BlockCreator.class); - cmsCreator = mock(CmsCreator.class); - - pkiQbftBlockCreator = new PkiQbftBlockCreator(blockCreator, cmsCreator, extraDataCodec); - - blockHeaderBuilder = new BlockHeaderTestFixture(); - } - - @Test - public void createProposalBehaviourWithNonPkiCodecFails() { - assertThatThrownBy( - () -> new PkiQbftBlockCreator(blockCreator, cmsCreator, new QbftExtraDataCodec())) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("PkiQbftBlockCreator must use PkiQbftExtraDataCodec"); - } - - @Test - public void cmsInProposedBlockHasValueCreatedByCmsCreator() { - createBlockBeingProposed(); - - final Bytes cms = Bytes.random(32); - when(cmsCreator.create(any(Bytes.class))).thenReturn(cms); - - final Block proposedBlock = pkiQbftBlockCreator.createBlock(1L).getBlock(); - - final PkiQbftExtraData proposedBlockExtraData = - (PkiQbftExtraData) extraDataCodec.decodeRaw(proposedBlock.getHeader().getExtraData()); - assertThat(proposedBlockExtraData).isInstanceOf(PkiQbftExtraData.class); - assertThat(proposedBlockExtraData.getCms()).isEqualTo(cms); - } - - @Test - public void cmsIsCreatedWithCorrectHashingFunction() { - final Block block = createBlockBeingProposed(); - final Hash expectedHashForCmsCreation = - PkiQbftBlockHeaderFunctions.forCmsSignature(extraDataCodec).hash(block.getHeader()); - - when(cmsCreator.create(any(Bytes.class))).thenReturn(Bytes.random(32)); - - pkiQbftBlockCreator.createBlock(1L); - - verify(cmsCreator).create(eq(expectedHashForCmsCreation)); - } - - @Test - public void proposedBlockHashUsesCommittedSealHeaderFunction() { - createBlockBeingProposed(); - when(cmsCreator.create(any(Bytes.class))).thenReturn(Bytes.random(32)); - - final Block blockWithCms = pkiQbftBlockCreator.createBlock(1L).getBlock(); - - final Hash expectedBlockHash = - BftBlockHeaderFunctions.forCommittedSeal(extraDataCodec).hash(blockWithCms.getHeader()); - - assertThat(blockWithCms.getHash()).isEqualTo(expectedBlockHash); - } - - private Block createBlockBeingProposed() { - final BftExtraData originalExtraData = - createExtraData(blockHeaderBuilder.buildHeader(), extraDataCodec); - final BlockHeader blockHeaderWithExtraData = - blockHeaderBuilder.extraData(extraDataCodec.encode(originalExtraData)).buildHeader(); - final Block block = - new Block( - blockHeaderWithExtraData, - new BlockBody(Collections.emptyList(), Collections.emptyList())); - when(blockCreator.createBlock(eq(1L))) - .thenReturn(new BlockCreationResult(block, new TransactionSelectionResults())); - - return block; - } -} diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/QbftValidatorsValidationRuleTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/QbftValidatorsValidationRuleTest.java index b006ccd3be0..28ec8fc3a33 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/QbftValidatorsValidationRuleTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/headervalidationrules/QbftValidatorsValidationRuleTest.java @@ -19,11 +19,12 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import org.hyperledger.besu.consensus.common.bft.BftContext; import org.hyperledger.besu.consensus.common.bft.BftExtraData; import org.hyperledger.besu.consensus.common.bft.Vote; -import org.hyperledger.besu.consensus.qbft.QbftContext; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.AddressHelpers; import org.hyperledger.besu.ethereum.core.BlockHeader; @@ -46,8 +47,8 @@ public void validationPassesIfValidatorsAndVoteAreEmpty() { new ProtocolContext( null, null, - setupContextWithBftExtraData(QbftContext.class, Collections.emptyList(), bftExtraData), - Optional.empty()); + setupContextWithBftExtraData(BftContext.class, Collections.emptyList(), bftExtraData), + new BadBlockManager()); when(bftExtraData.getValidators()).thenReturn(Collections.emptyList()); when(bftExtraData.getVote()).thenReturn(Optional.empty()); assertThat(qbftValidatorsValidationRule.validate(blockHeader, null, context)).isTrue(); @@ -65,8 +66,8 @@ public void validationIsDelegatedWhenConstructorFlagIsFalse() { new ProtocolContext( null, null, - setupContextWithBftExtraData(QbftContext.class, validators, bftExtraData), - Optional.empty()); + setupContextWithBftExtraData(BftContext.class, validators, bftExtraData), + new BadBlockManager()); when(bftExtraData.getValidators()).thenReturn(validators); assertThat(qbftValidatorsValidationRule.validate(blockHeader, null, context)).isTrue(); } @@ -83,8 +84,8 @@ public void validationFailsIfValidatorsAreNotEmpty() { new ProtocolContext( null, null, - setupContextWithBftExtraData(QbftContext.class, validators, bftExtraData), - Optional.empty()); + setupContextWithBftExtraData(BftContext.class, validators, bftExtraData), + new BadBlockManager()); when(bftExtraData.getValidators()).thenReturn(validators); assertThat(qbftValidatorsValidationRule.validate(blockHeader, null, context)).isFalse(); } @@ -97,8 +98,8 @@ public void validationFailsIfVoteIsPresent() { new ProtocolContext( null, null, - setupContextWithBftExtraData(QbftContext.class, Collections.emptyList(), bftExtraData), - Optional.empty()); + setupContextWithBftExtraData(BftContext.class, Collections.emptyList(), bftExtraData), + new BadBlockManager()); when(bftExtraData.getValidators()).thenReturn(Collections.emptyList()); when(bftExtraData.getVote()).thenReturn(Optional.of(mock(Vote.class))); assertThat(qbftValidatorsValidationRule.validate(blockHeader, null, context)).isFalse(); diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftDiscardValidatorVoteTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftDiscardValidatorVoteTest.java index 0716fd42d20..c8ce4195695 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftDiscardValidatorVoteTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftDiscardValidatorVoteTest.java @@ -58,14 +58,14 @@ public void returnsCorrectMethodName() { public void exceptionWhenNoParamsSupplied() { assertThatThrownBy(() -> method.response(requestWithParams())) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 0"); + .hasMessage("Invalid validator address parameter (index 0)"); } @Test public void exceptionWhenInvalidAddressParameterSupplied() { assertThatThrownBy(() -> method.response(requestWithParams("InvalidAddress"))) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Invalid json rpc parameter at index 0"); + .hasMessageContaining("Invalid validator address parameter (index 0)"); } @Test diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftGetRequestTimeoutSecondsTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftGetRequestTimeoutSecondsTest.java new file mode 100644 index 00000000000..bc7e4b8e285 --- /dev/null +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftGetRequestTimeoutSecondsTest.java @@ -0,0 +1,68 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.consensus.qbft.jsonrpc.methods; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.config.BftConfigOptions; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class QbftGetRequestTimeoutSecondsTest { + + private static final String JSON_RPC_VERSION = "2.0"; + private static final String METHOD_NAME = "qbft_getRequestTimeoutSeconds"; + + @Mock private BftConfigOptions bftConfigOptions; + + private QbftGetRequestTimeoutSeconds method; + + @BeforeEach + public void setUp() { + this.method = new QbftGetRequestTimeoutSeconds(bftConfigOptions); + } + + @Test + public void shouldReturnCorrectMethodName() { + assertThat(method.getName()).isEqualTo(METHOD_NAME); + } + + @Test + public void shouldReturnCorrectRequestTimeout() { + final int expectedTimeout = 5; + when(bftConfigOptions.getRequestTimeoutSeconds()).thenReturn(expectedTimeout); + JsonRpcRequestContext request = requestWithParams(); + + JsonRpcResponse response = method.response(request); + + assertThat(response).isInstanceOf(JsonRpcSuccessResponse.class); + JsonRpcSuccessResponse successResponse = (JsonRpcSuccessResponse) response; + assertThat(successResponse.getResult()).isEqualTo(expectedTimeout); + } + + private JsonRpcRequestContext requestWithParams(final Object... params) { + return new JsonRpcRequestContext(new JsonRpcRequest(JSON_RPC_VERSION, METHOD_NAME, params)); + } +} diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftGetSignerMetricsTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftGetSignerMetricsTest.java index d070ba47cc3..153a4ef55bc 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftGetSignerMetricsTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftGetSignerMetricsTest.java @@ -75,14 +75,14 @@ public void returnsCorrectMethodName() { public void exceptionWhenInvalidStartBlockSupplied() { assertThatThrownBy(() -> method.response(requestWithParams("INVALID"))) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Invalid json rpc parameter at index 0"); + .hasMessageContaining("Invalid start block parameter (index 0)"); } @Test public void exceptionWhenInvalidEndBlockSupplied() { assertThatThrownBy(() -> method.response(requestWithParams("1", "INVALID"))) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Invalid json rpc parameter at index 1"); + .hasMessageContaining("Invalid end block parameter (index 1)"); } @Test diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftGetValidatorsByBlockNumberTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftGetValidatorsByBlockNumberTest.java index b2e1c2b3d4d..a0235723bf4 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftGetValidatorsByBlockNumberTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftGetValidatorsByBlockNumberTest.java @@ -23,6 +23,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.core.BlockHeader; @@ -73,4 +74,33 @@ public void shouldReturnListOfValidatorsFromBlock() { Object result = method.resultByBlockNumber(request, 12); assertThat(result).isEqualTo(expectedOutput); } + + @Test + public void shouldReturnListOfValidatorsFromLatestBlock() { + request = + new JsonRpcRequestContext( + new JsonRpcRequest("2.0", "qbft_getValidatorsByBlockNumber", new String[] {"latest"})); + when(blockchainQueries.headBlockNumber()).thenReturn(12L); + when(blockchainQueries.getBlockHeaderByNumber(12)).thenReturn(Optional.of(blockHeader)); + final List
addresses = Collections.singletonList(Address.ID); + final List expectedOutput = Collections.singletonList(Address.ID.toString()); + when(validatorProvider.getValidatorsForBlock(any())).thenReturn(addresses); + Object result = method.response(request); + assertThat(result).isInstanceOf(JsonRpcSuccessResponse.class); + assertThat(((JsonRpcSuccessResponse) result).getResult()).isEqualTo(expectedOutput); + } + + @Test + public void shouldReturnListOfValidatorsFromPendingBlock() { + request = + new JsonRpcRequestContext( + new JsonRpcRequest("2.0", "qbft_getValidatorsByBlockNumber", new String[] {"pending"})); + when(blockchainQueries.headBlockHeader()).thenReturn(blockHeader); + final List
addresses = Collections.singletonList(Address.ID); + final List expectedOutput = Collections.singletonList(Address.ID.toString()); + when(validatorProvider.getValidatorsAfterBlock(any())).thenReturn(addresses); + Object result = method.response(request); + assertThat(result).isInstanceOf(JsonRpcSuccessResponse.class); + assertThat(((JsonRpcSuccessResponse) result).getResult()).isEqualTo(expectedOutput); + } } diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftProposeValidatorVoteTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftProposeValidatorVoteTest.java index fa3f89036eb..1f5b1466d97 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftProposeValidatorVoteTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/jsonrpc/methods/QbftProposeValidatorVoteTest.java @@ -58,28 +58,28 @@ public void returnsCorrectMethodName() { public void exceptionWhenNoParamsSupplied() { assertThatThrownBy(() -> method.response(requestWithParams())) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 0"); + .hasMessage("Invalid validator address parameter (index 0)"); } @Test public void exceptionWhenNoAuthSupplied() { assertThatThrownBy(() -> method.response(requestWithParams(Address.fromHexString("1")))) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 1"); + .hasMessage("Invalid vote type parameter (index 1)"); } @Test public void exceptionWhenNoAddressSupplied() { assertThatThrownBy(() -> method.response(requestWithParams("true"))) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Invalid json rpc parameter at index 0"); + .hasMessageContaining("Invalid validator address parameter"); } @Test public void exceptionWhenInvalidBoolParameterSupplied() { assertThatThrownBy(() -> method.response(requestWithParams(Address.fromHexString("1"), "c"))) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Invalid json rpc parameter at index 1"); + .hasMessageContaining("Invalid vote type parameter (index 1)"); } @Test diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/messagewrappers/ProposalTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/messagewrappers/ProposalTest.java index 46a079ef00a..b3c462656ac 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/messagewrappers/ProposalTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/messagewrappers/ProposalTest.java @@ -51,7 +51,11 @@ public class ProposalTest { private static final Block BLOCK = new Block( new BlockHeaderTestFixture().extraData(bftExtraDataCodec.encode(extraData)).buildHeader(), - new BlockBody(Collections.emptyList(), Collections.emptyList())); + new BlockBody( + Collections.emptyList(), + Collections.emptyList(), + Optional.of(Collections.emptyList()), + Optional.empty())); @Test public void canRoundTripProposalMessage() { diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/pki/DefaultKeyStoreWrapperProviderTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/pki/DefaultKeyStoreWrapperProviderTest.java deleted file mode 100644 index f5c91305cda..00000000000 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/pki/DefaultKeyStoreWrapperProviderTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See - * the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.besu.consensus.qbft.pki; - -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; - -import org.hyperledger.besu.consensus.qbft.pki.DefaultKeyStoreWrapperProvider.HardwareKeyStoreWrapperProvider; -import org.hyperledger.besu.consensus.qbft.pki.DefaultKeyStoreWrapperProvider.SoftwareKeyStoreWrapperProvider; -import org.hyperledger.besu.pki.keystore.KeyStoreWrapper; - -import java.nio.file.Path; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -public class DefaultKeyStoreWrapperProviderTest { - - @Mock private HardwareKeyStoreWrapperProvider hardwareKeyStoreWrapperProvider; - @Mock private SoftwareKeyStoreWrapperProvider softwareKeyStoreWrapperProvider; - @InjectMocks private DefaultKeyStoreWrapperProvider keyStoreWrapperProvider; - - private final Path keystorePath = Path.of("/keystore"); - private final String keystorePassword = "pwd"; - private final Path crlPath = Path.of("/crl"); - - @Test - public void configWithTypePKCS11UsesHardwareKeyStoreProvider() { - keyStoreWrapperProvider.apply( - KeyStoreWrapper.KEYSTORE_TYPE_PKCS11, keystorePath, keystorePassword, crlPath); - - verify(hardwareKeyStoreWrapperProvider) - .get(eq(keystorePassword), eq(keystorePath), eq(crlPath)); - verifyNoInteractions(softwareKeyStoreWrapperProvider); - } - - @Test - public void configWithTypePKCS12UsesSoftwareKeyStoreProvider() { - keyStoreWrapperProvider.apply( - KeyStoreWrapper.KEYSTORE_TYPE_PKCS12, keystorePath, keystorePassword, crlPath); - - verify(softwareKeyStoreWrapperProvider) - .get( - eq(KeyStoreWrapper.KEYSTORE_TYPE_PKCS12), - eq(keystorePath), - eq(keystorePassword), - eq(crlPath)); - verifyNoInteractions(hardwareKeyStoreWrapperProvider); - } - - @Test - public void configWithTypeJKSUsesSoftwareKeyStoreProvider() { - keyStoreWrapperProvider.apply( - KeyStoreWrapper.KEYSTORE_TYPE_JKS, keystorePath, keystorePassword, crlPath); - - verify(softwareKeyStoreWrapperProvider) - .get( - eq(KeyStoreWrapper.KEYSTORE_TYPE_JKS), - eq(keystorePath), - eq(keystorePassword), - eq(crlPath)); - verifyNoInteractions(hardwareKeyStoreWrapperProvider); - } -} diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/pki/PkiBlockCreationConfigurationProviderTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/pki/PkiBlockCreationConfigurationProviderTest.java deleted file mode 100644 index 27ccfe1bcfe..00000000000 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/pki/PkiBlockCreationConfigurationProviderTest.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.besu.consensus.qbft.pki; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.pki.config.PkiKeyStoreConfiguration; -import org.hyperledger.besu.pki.keystore.KeyStoreWrapper; - -import java.nio.file.Path; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -public class PkiBlockCreationConfigurationProviderTest { - - @Mock KeyStoreWrapperProvider keyStoreWrapperProvider; - @Mock KeyStoreWrapper keyStoreWrapper; - @Mock KeyStoreWrapper trustStoreWrapper; - - @Test - public void pkiBlockCreationConfigurationIsLoadedCorrectly() { - when(keyStoreWrapperProvider.apply(any(), eq(Path.of("/tmp/keystore")), eq("pwd"), isNull())) - .thenReturn(keyStoreWrapper); - when(keyStoreWrapperProvider.apply( - any(), eq(Path.of("/tmp/truststore")), eq("pwd"), eq(Path.of("/tmp/crl")))) - .thenReturn(trustStoreWrapper); - - final PkiKeyStoreConfiguration pkiKeyStoreConfiguration = - spy( - new PkiKeyStoreConfiguration.Builder() - .withKeyStorePath(Path.of("/tmp/keystore")) - .withKeyStorePasswordPath(Path.of("/tmp/password")) - .withTrustStorePath(Path.of("/tmp/truststore")) - .withTrustStorePasswordPath(Path.of("/tmp/password")) - .withCertificateAlias("anAlias") - .withCrlFilePath(Path.of("/tmp/crl")) - .build()); - doReturn("pwd").when(pkiKeyStoreConfiguration).getKeyStorePassword(); - doReturn("pwd").when(pkiKeyStoreConfiguration).getTrustStorePassword(); - - final PkiBlockCreationConfigurationProvider pkiBlockCreationConfigProvider = - new PkiBlockCreationConfigurationProvider(keyStoreWrapperProvider); - - final PkiBlockCreationConfiguration pkiBlockCreationConfiguration = - pkiBlockCreationConfigProvider.load(pkiKeyStoreConfiguration); - - assertThat(pkiBlockCreationConfiguration).isNotNull(); - assertThat(pkiBlockCreationConfiguration.getKeyStore()).isNotNull(); - assertThat(pkiBlockCreationConfiguration.getTrustStore()).isNotNull(); - assertThat(pkiBlockCreationConfiguration.getCertificateAlias()).isEqualTo("anAlias"); - } -} diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftBlockHashingTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftBlockHashingTest.java deleted file mode 100644 index 45ec1a780df..00000000000 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftBlockHashingTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See - * the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.besu.consensus.qbft.pki; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import org.hyperledger.besu.consensus.common.bft.BftBlockHashing; -import org.hyperledger.besu.consensus.common.bft.BftExtraData; -import org.hyperledger.besu.consensus.common.bft.BftExtraDataFixture; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; - -import org.apache.tuweni.bytes.Bytes; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -public class PkiQbftBlockHashingTest { - - private PkiQbftExtraDataCodec pkiExtraDataCodec = new PkiQbftExtraDataCodec(); - private PkiQbftBlockHashing pkiQbftBlockHashing; - - @BeforeEach - public void before() { - pkiExtraDataCodec = spy(new PkiQbftExtraDataCodec()); - pkiQbftBlockHashing = new PkiQbftBlockHashing(pkiExtraDataCodec); - } - - @Test - public void blockHashingUsesCorrectEncodingWithoutCmsMethodInCodec() { - final PkiQbftExtraData pkiQbftExtraData = createPkiQbftExtraData(); - final BlockHeader headerWithExtraData = - new BlockHeaderTestFixture() - .number(1L) - .extraData(pkiExtraDataCodec.encode(pkiQbftExtraData)) - .buildHeader(); - - // Expected hash using the extraData encoded by the encodeWithoutCms method of the codec - final Hash expectedHash = - Hash.hash( - BftBlockHashing.serializeHeader( - headerWithExtraData, - () -> pkiExtraDataCodec.encodeWithoutCms(pkiQbftExtraData), - pkiExtraDataCodec)); - - final Hash hash = - pkiQbftBlockHashing.calculateHashOfBftBlockForCmsSignature(headerWithExtraData); - - assertThat(hash).isEqualTo(expectedHash); - - /* - Verify that the encodeWithoutCms method was called twice, once when calculating the - expected hash and a second time as part of the hash calculation on - calculateHashOfBftBlockForCmsSignature - */ - verify(pkiExtraDataCodec, times(2)).encodeWithoutCms(any(PkiQbftExtraData.class)); - } - - private PkiQbftExtraData createPkiQbftExtraData() { - final BlockHeader blockHeader = new BlockHeaderTestFixture().buildHeader(); - final BftExtraData extraData = - BftExtraDataFixture.createExtraData(blockHeader, pkiExtraDataCodec); - return new PkiQbftExtraData(extraData, Bytes.random(32)); - } -} diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftExtraDataCodecTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftExtraDataCodecTest.java deleted file mode 100644 index 32fc034b8e8..00000000000 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/pki/PkiQbftExtraDataCodecTest.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright 2020 ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.besu.consensus.qbft.pki; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.consensus.qbft.QbftExtraDataCodecTestUtils.createNonEmptyVanityData; - -import org.hyperledger.besu.consensus.common.bft.BftExtraData; -import org.hyperledger.besu.consensus.common.bft.Vote; -import org.hyperledger.besu.crypto.SECPSignature; -import org.hyperledger.besu.crypto.SignatureAlgorithm; -import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; - -import java.math.BigInteger; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import java.util.Random; -import java.util.function.Supplier; - -import com.google.common.base.Suppliers; -import org.apache.tuweni.bytes.Bytes; -import org.junit.jupiter.api.Test; - -public class PkiQbftExtraDataCodecTest { - - private static final Supplier SIGNATURE_ALGORITHM = - Suppliers.memoize(SignatureAlgorithmFactory::getInstance); - - // Arbitrary bytes representing a non-empty CMS - private final Bytes cms = Bytes.fromHexString("0x01"); - - private final String RAW_EXCLUDE_COMMIT_SEALS_AND_ROUND_NUMBER_ENCODED_STRING = - "0xf867a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20ea940000000000000000" - + "000000000000000000000001940000000000000000000000000000000000000002d7940000000000000000" - + "00000000000000000000000181ff80c001"; - - private final String RAW_EXCLUDE_COMMIT_SEALS_ENCODED_STRING = - "0xf86aa00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20ea940000000000000000" - + "000000000000000000000001940000000000000000000000000000000000000002d7940000000000000000" - + "00000000000000000000000181ff83fedcbac001"; - - private final String RAW_ALL_ENCODED_STRING = - "0xf8f1a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20ea940000000000000000" - + "000000000000000000000001940000000000000000000000000000000000000002d7940000000000000000" - + "00000000000000000000000181ff83fedcbaf886b841000000000000000000000000000000000000000000" - + "0000000000000000000001000000000000000000000000000000000000000000000000000000000000000a" - + "00b841000000000000000000000000000000000000000000000000000000000000000a0000000000000000" - + "0000000000000000000000000000000000000000000000010001"; - - private final String RAW_QBFT_EXTRA_DATA = - "0xf8f0a00102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20ea940000000000000000" - + "000000000000000000000001940000000000000000000000000000000000000002d7940000000000000000" - + "00000000000000000000000181ff83fedcbaf886b841000000000000000000000000000000000000000000" - + "0000000000000000000001000000000000000000000000000000000000000000000000000000000000000a" - + "00b841000000000000000000000000000000000000000000000000000000000000000a0000000000000000" - + "00000000000000000000000000000000000000000000000100"; - - private final PkiQbftExtraDataCodec bftExtraDataCodec = new PkiQbftExtraDataCodec(); - - @Test - public void fullyPopulatedDataProducesCorrectlyFormedExtraDataObject() { - final List
validators = - Arrays.asList(Address.fromHexString("1"), Address.fromHexString("2")); - final int round = 0x00FEDCBA; - final List committerSeals = - Arrays.asList( - SIGNATURE_ALGORITHM.get().createSignature(BigInteger.ONE, BigInteger.TEN, (byte) 0), - SIGNATURE_ALGORITHM.get().createSignature(BigInteger.TEN, BigInteger.ONE, (byte) 0)); - - // Create randomised vanity data. - final byte[] vanity_bytes = createNonEmptyVanityData(); - new Random().nextBytes(vanity_bytes); - final Bytes vanity_data = Bytes.wrap(vanity_bytes); - - final BytesValueRLPOutput encoder = new BytesValueRLPOutput(); - encoder.startList(); // start extra data list - // vanity data - encoder.writeBytes(vanity_data); - // validators - encoder.writeList(validators, (validator, rlp) -> rlp.writeBytes(validator)); - // votes - encoder.startList(); - encoder.writeBytes(Address.fromHexString("1")); - encoder.writeByte(Vote.ADD_BYTE_VALUE); - encoder.endList(); - // rounds - encoder.writeIntScalar(round); - // committer seals - encoder.writeList(committerSeals, (committer, rlp) -> rlp.writeBytes(committer.encodedBytes())); - // cms - encoder.writeBytes(cms); - encoder.endList(); // end extra data list - - final Bytes bufferToInject = encoder.encoded(); - - final PkiQbftExtraData extraData = - (PkiQbftExtraData) bftExtraDataCodec.decodeRaw(bufferToInject); - - assertThat(extraData.getVanityData()).isEqualTo(vanity_data); - assertThat(extraData.getRound()).isEqualTo(round); - assertThat(extraData.getSeals()).isEqualTo(committerSeals); - assertThat(extraData.getValidators()).isEqualTo(validators); - assertThat(extraData.getCms()).isEqualTo(cms); - } - - @Test - public void decodingQbftExtraDataDelegatesToQbftCodec() { - final List
validators = - Arrays.asList(Address.fromHexString("1"), Address.fromHexString("2")); - final int round = 0x00FEDCBA; - final List committerSeals = - Arrays.asList( - SIGNATURE_ALGORITHM.get().createSignature(BigInteger.ONE, BigInteger.TEN, (byte) 0), - SIGNATURE_ALGORITHM.get().createSignature(BigInteger.TEN, BigInteger.ONE, (byte) 0)); - - // Create randomised vanity data. - final byte[] vanity_bytes = createNonEmptyVanityData(); - new Random().nextBytes(vanity_bytes); - final Bytes vanity_data = Bytes.wrap(vanity_bytes); - - final BytesValueRLPOutput encoder = new BytesValueRLPOutput(); - encoder.startList(); // start extra data list - // vanity data - encoder.writeBytes(vanity_data); - // validators - encoder.writeList(validators, (validator, rlp) -> rlp.writeBytes(validator)); - // votes - encoder.startList(); - encoder.writeBytes(Address.fromHexString("1")); - encoder.writeByte(Vote.ADD_BYTE_VALUE); - encoder.endList(); - // rounds - encoder.writeIntScalar(round); - // committer seals - encoder.writeList(committerSeals, (committer, rlp) -> rlp.writeBytes(committer.encodedBytes())); - // Not including the CMS in the list (to generate a non-pki QBFT extra data) - encoder.endList(); // end extra data list - - final Bytes bufferToInject = encoder.encoded(); - - final PkiQbftExtraData extraData = - (PkiQbftExtraData) bftExtraDataCodec.decodeRaw(bufferToInject); - - assertThat(extraData.getVanityData()).isEqualTo(vanity_data); - assertThat(extraData.getRound()).isEqualTo(round); - assertThat(extraData.getSeals()).isEqualTo(committerSeals); - assertThat(extraData.getValidators()).isEqualTo(validators); - assertThat(extraData.getCms()).isEqualTo(Bytes.EMPTY); - } - - /* - When encoding for blockchain, we ignore commit seals and round number, but we include the CMS - */ - @Test - public void encodingForBlockchainShouldIncludeCms() { - final Bytes expectedRawDecoding = - Bytes.fromHexString(RAW_EXCLUDE_COMMIT_SEALS_AND_ROUND_NUMBER_ENCODED_STRING); - final Bytes encoded = - bftExtraDataCodec.encodeWithoutCommitSealsAndRoundNumber(getDecodedExtraData(cms)); - - assertThat(encoded).isEqualTo(expectedRawDecoding); - } - - @Test - public void encodingWithoutCommitSealsShouldIncludeCms() { - final Bytes expectedRawDecoding = Bytes.fromHexString(RAW_EXCLUDE_COMMIT_SEALS_ENCODED_STRING); - final Bytes encoded = bftExtraDataCodec.encodeWithoutCommitSeals(getDecodedExtraData(cms)); - - assertThat(encoded).isEqualTo(expectedRawDecoding); - } - - @Test - public void encodingWithAllShouldIncludeCms() { - final Bytes expectedRawDecoding = Bytes.fromHexString(RAW_ALL_ENCODED_STRING); - final Bytes encoded = bftExtraDataCodec.encode(getDecodedExtraData(cms)); - - assertThat(encoded).isEqualTo(expectedRawDecoding); - } - - /* - When encoding for proposal, we include commit seals and round number, but we ignore the CMS - */ - @Test - public void encodingForCreatingCmsProposal() { - final Bytes expectedRawDecoding = Bytes.fromHexString(RAW_QBFT_EXTRA_DATA); - final Bytes encoded = bftExtraDataCodec.encodeWithoutCms(getDecodedExtraData(cms)); - - assertThat(encoded).isEqualTo(expectedRawDecoding); - } - - /* - When encoding non-pki extra data, we delegate to the regular QBFT encoder - */ - @Test - public void encodingQbftExtraData() { - final Bytes expectedRawDecoding = Bytes.fromHexString(RAW_QBFT_EXTRA_DATA); - final PkiQbftExtraData pkiBftExtraData = getDecodedExtraData(cms); - final BftExtraData bftExtraData = - new BftExtraData( - pkiBftExtraData.getVanityData(), - pkiBftExtraData.getSeals(), - pkiBftExtraData.getVote(), - pkiBftExtraData.getRound(), - pkiBftExtraData.getValidators()); - - final Bytes encoded = bftExtraDataCodec.encode(bftExtraData); - - assertThat(encoded).isEqualTo(expectedRawDecoding); - } - - private static PkiQbftExtraData getDecodedExtraData(final Bytes cms) { - final List
validators = - Arrays.asList(Address.fromHexString("1"), Address.fromHexString("2")); - final Optional vote = Optional.of(Vote.authVote(Address.fromHexString("1"))); - final int round = 0x00FEDCBA; - final List committerSeals = - Arrays.asList( - SIGNATURE_ALGORITHM.get().createSignature(BigInteger.ONE, BigInteger.TEN, (byte) 0), - SIGNATURE_ALGORITHM.get().createSignature(BigInteger.TEN, BigInteger.ONE, (byte) 0)); - - // Create a byte buffer with no data. - final byte[] vanity_bytes = createNonEmptyVanityData(); - final Bytes vanity_data = Bytes.wrap(vanity_bytes); - - return new PkiQbftExtraData(vanity_data, committerSeals, vote, round, validators, cms); - } -} diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManagerTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManagerTest.java index 92471799cfd..171b1607f39 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManagerTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManagerTest.java @@ -31,8 +31,11 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import org.hyperledger.besu.config.StubGenesisConfigOptions; +import org.hyperledger.besu.consensus.common.bft.BftContext; import org.hyperledger.besu.consensus.common.bft.BftExtraData; import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec; +import org.hyperledger.besu.consensus.common.bft.BftProtocolSchedule; import org.hyperledger.besu.consensus.common.bft.BlockTimer; import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier; import org.hyperledger.besu.consensus.common.bft.RoundTimer; @@ -40,7 +43,6 @@ import org.hyperledger.besu.consensus.common.bft.events.RoundExpiry; import org.hyperledger.besu.consensus.common.bft.network.ValidatorMulticaster; import org.hyperledger.besu.consensus.common.bft.statemachine.BftFinalState; -import org.hyperledger.besu.consensus.qbft.QbftContext; import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec; import org.hyperledger.besu.consensus.qbft.messagedata.RoundChangeMessageData; import org.hyperledger.besu.consensus.qbft.messagewrappers.Commit; @@ -58,22 +60,32 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.blockcreation.BlockCreationTiming; import org.hyperledger.besu.ethereum.blockcreation.BlockCreator.BlockCreationResult; import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; +import org.hyperledger.besu.ethereum.chain.DefaultBlockchain; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; -import org.hyperledger.besu.ethereum.core.BlockImporter; +import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.Util; -import org.hyperledger.besu.ethereum.mainnet.BlockImportResult; +import org.hyperledger.besu.ethereum.mainnet.DefaultProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.util.Subscribers; import java.math.BigInteger; import java.time.Clock; import java.util.List; import java.util.Optional; +import java.util.function.Function; import com.google.common.collect.Lists; import org.apache.tuweni.bytes.Bytes; @@ -104,11 +116,12 @@ public class QbftBlockHeightManagerTest { @Mock private Clock clock; @Mock private MessageValidatorFactory messageValidatorFactory; @Mock private BftBlockCreator blockCreator; - @Mock private BlockImporter blockImporter; @Mock private BlockTimer blockTimer; @Mock private RoundTimer roundTimer; + @Mock private DefaultBlockchain blockchain; @Mock private FutureRoundProposalMessageValidator futureRoundProposalMessageValidator; @Mock private ValidatorMulticaster validatorMulticaster; + @Mock private BlockHeader parentHeader; @Captor private ArgumentCaptor sentMessageArgCaptor; @@ -146,22 +159,40 @@ public void setup() { when(finalState.getBlockTimer()).thenReturn(blockTimer); when(finalState.getQuorum()).thenReturn(3); when(finalState.getValidatorMulticaster()).thenReturn(validatorMulticaster); - when(blockCreator.createBlock(anyLong())) - .thenReturn(new BlockCreationResult(createdBlock, new TransactionSelectionResults())); + when(blockCreator.createBlock(anyLong(), any())) + .thenReturn( + new BlockCreationResult( + createdBlock, new TransactionSelectionResults(), new BlockCreationTiming())); when(futureRoundProposalMessageValidator.validateProposalMessage(any())).thenReturn(true); when(messageValidatorFactory.createFutureRoundProposalMessageValidator(anyLong(), any())) .thenReturn(futureRoundProposalMessageValidator); when(messageValidatorFactory.createMessageValidator(any(), any())).thenReturn(messageValidator); - when(blockImporter.importBlock(any(), any(), any())).thenReturn(new BlockImportResult(false)); protocolContext = new ProtocolContext( - null, + blockchain, null, setupContextWithBftExtraDataEncoder( - QbftContext.class, validators, new QbftExtraDataCodec()), - Optional.empty()); + BftContext.class, validators, new QbftExtraDataCodec()), + new BadBlockManager()); + + final ProtocolScheduleBuilder protocolScheduleBuilder = + new ProtocolScheduleBuilder( + new StubGenesisConfigOptions(), + BigInteger.ONE, + ProtocolSpecAdapters.create(0, Function.identity()), + new PrivacyParameters(), + false, + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); + + ProtocolSchedule protocolSchedule = + new BftProtocolSchedule( + (DefaultProtocolSchedule) protocolScheduleBuilder.createProtocolSchedule()); // Ensure the created QbftRound has the valid ConsensusRoundIdentifier; when(roundFactory.createNewRound(any(), anyInt())) @@ -174,13 +205,14 @@ QbftContext.class, validators, new QbftExtraDataCodec()), createdRoundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, Subscribers.create(), nodeKey, messageFactory, messageTransmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); }); when(roundFactory.createNewRoundWithState(any(), any())) @@ -191,13 +223,14 @@ QbftContext.class, validators, new QbftExtraDataCodec()), providedRoundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, Subscribers.create(), nodeKey, messageFactory, messageTransmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); }); } diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundTest.java index 627f5cd9e2e..514fd653a1d 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundTest.java @@ -30,13 +30,14 @@ import static org.mockito.Mockito.when; import org.hyperledger.besu.consensus.common.bft.BftBlockHashing; +import org.hyperledger.besu.consensus.common.bft.BftContext; import org.hyperledger.besu.consensus.common.bft.BftExtraData; import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec; +import org.hyperledger.besu.consensus.common.bft.BftProtocolSchedule; import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier; import org.hyperledger.besu.consensus.common.bft.RoundTimer; import org.hyperledger.besu.consensus.common.bft.blockcreation.BftBlockCreator; import org.hyperledger.besu.consensus.common.bft.payload.SignedData; -import org.hyperledger.besu.consensus.qbft.QbftContext; import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec; import org.hyperledger.besu.consensus.qbft.messagewrappers.RoundChange; import org.hyperledger.besu.consensus.qbft.network.QbftMessageTransmitter; @@ -49,8 +50,10 @@ import org.hyperledger.besu.cryptoservices.NodeKeyUtils; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.blockcreation.BlockCreationTiming; import org.hyperledger.besu.ethereum.blockcreation.BlockCreator.BlockCreationResult; import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MinedBlockObserver; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; @@ -59,6 +62,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.BlockImporter; import org.hyperledger.besu.ethereum.mainnet.BlockImportResult; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.plugin.services.securitymodule.SecurityModuleException; import org.hyperledger.besu.util.Subscribers; @@ -92,14 +96,17 @@ public class QbftRoundTest { private final BftExtraDataCodec bftExtraDataCodec = new QbftExtraDataCodec(); private ProtocolContext protocolContext; + @Mock private BftProtocolSchedule protocolSchedule; @Mock private MutableBlockchain blockChain; @Mock private WorldStateArchive worldStateArchive; - @Mock private BlockImporter blockImporter; @Mock private QbftMessageTransmitter transmitter; @Mock private MinedBlockObserver minedBlockObserver; @Mock private BftBlockCreator blockCreator; @Mock private MessageValidator messageValidator; @Mock private RoundTimer roundTimer; + @Mock private ProtocolSpec protocolSpec; + @Mock private BlockImporter blockImporter; + @Mock private BlockHeader parentHeader; @Captor private ArgumentCaptor blockCaptor; @@ -117,8 +124,8 @@ public void setup() { blockChain, worldStateArchive, setupContextWithBftExtraDataEncoder( - QbftContext.class, emptyList(), new QbftExtraDataCodec()), - Optional.empty()); + BftContext.class, emptyList(), new QbftExtraDataCodec()), + new BadBlockManager()); when(messageValidator.validateProposal(any())).thenReturn(true); when(messageValidator.validatePrepare(any())).thenReturn(true); @@ -133,10 +140,16 @@ QbftContext.class, emptyList(), new QbftExtraDataCodec()), final BlockHeader header = headerTestFixture.buildHeader(); proposedBlock = new Block(header, new BlockBody(emptyList(), emptyList())); - when(blockCreator.createBlock(anyLong())) - .thenReturn(new BlockCreationResult(proposedBlock, new TransactionSelectionResults())); + when(blockCreator.createBlock(anyLong(), any())) + .thenReturn( + new BlockCreationResult( + proposedBlock, new TransactionSelectionResults(), new BlockCreationTiming())); + + when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); + when(protocolSpec.getBlockImporter()).thenReturn(blockImporter); - when(blockImporter.importBlock(any(), any(), any())).thenReturn(new BlockImportResult(true)); + when(blockImporter.importBlock(any(), any(), any())) + .thenReturn(new BlockImportResult(BlockImportResult.BlockImportStatus.IMPORTED)); subscribers.subscribe(minedBlockObserver); } @@ -148,13 +161,14 @@ public void onConstructionRoundTimerIsStarted() { roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, messageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); verify(roundTimer, times(1)).startTimer(roundIdentifier); } @@ -166,13 +180,14 @@ public void onReceptionOfValidProposalSendsAPrepareToNetworkPeers() { roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, messageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); round.handleProposalMessage( messageFactory.createProposal( @@ -189,13 +204,14 @@ public void sendsAProposalAndPrepareWhenSendProposalRequested() { roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, messageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); round.createAndSendProposalMessage(15); verify(transmitter, times(1)) @@ -213,20 +229,20 @@ public void singleValidatorImportBlocksImmediatelyOnProposalCreation() { roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, messageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); round.createAndSendProposalMessage(15); verify(transmitter, times(1)) .multicastProposal( roundIdentifier, proposedBlock, Collections.emptyList(), Collections.emptyList()); verify(transmitter, times(1)).multicastPrepare(roundIdentifier, proposedBlock.getHash()); verify(transmitter, times(1)).multicastCommit(any(), any(), any()); - verify(blockImporter, times(1)).importBlock(any(), any(), any()); } @Test @@ -237,13 +253,14 @@ public void localNodeProposesToNetworkOfTwoValidatorsImportsOnReceptionOfCommitF roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, messageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); final Hash commitSealHash = new BftBlockHashing(new QbftExtraDataCodec()) @@ -252,18 +269,15 @@ public void localNodeProposesToNetworkOfTwoValidatorsImportsOnReceptionOfCommitF round.createAndSendProposalMessage(15); verify(transmitter, never()).multicastCommit(any(), any(), any()); - verify(blockImporter, never()).importBlock(any(), any(), any()); round.handlePrepareMessage( messageFactory2.createPrepare(roundIdentifier, proposedBlock.getHash())); verify(transmitter, times(1)) .multicastCommit(roundIdentifier, proposedBlock.getHash(), localCommitSeal); - verify(blockImporter, never()).importBlock(any(), any(), any()); round.handleCommitMessage( messageFactory.createCommit(roundIdentifier, proposedBlock.getHash(), remoteCommitSeal)); - verify(blockImporter, times(1)).importBlock(any(), any(), any()); } @Test @@ -274,13 +288,14 @@ public void aProposalWithAnewBlockIsSentUponReceptionOfARoundChangeWithNoCertifi roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, messageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); round.startRoundWith(new RoundChangeArtifacts(emptyList(), Optional.empty()), 15); verify(transmitter, times(1)) @@ -297,13 +312,14 @@ public void aProposalMessageWithTheSameBlockIsSentUponReceptionOfARoundChangeWit roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, messageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); final SignedData preparedPayload = messageFactory.createPrepare(priorRoundChange, proposedBlock.getHash()).getSignedPayload(); @@ -345,13 +361,14 @@ public void creatingNewBlockFromEmptyPreparedCertificateUpdatesInternalState() { roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, messageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); final RoundChange roundChange = messageFactory.createRoundChange(roundIdentifier, Optional.empty()); @@ -384,13 +401,14 @@ public void creatingNewBlockNotifiesBlockMiningObservers() { roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, messageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); round.createAndSendProposalMessage(15); verify(minedBlockObserver).blockMined(any()); } @@ -405,13 +423,14 @@ public void blockIsOnlyImportedOnceWhenCommitsAreReceivedBeforeProposal() { roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, messageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); round.handleCommitMessage( messageFactory.createCommit(roundIdentifier, proposedBlock.getHash(), remoteCommitSeal)); @@ -419,8 +438,6 @@ public void blockIsOnlyImportedOnceWhenCommitsAreReceivedBeforeProposal() { round.handleProposalMessage( messageFactory.createProposal( roundIdentifier, proposedBlock, Collections.emptyList(), Collections.emptyList())); - - verify(blockImporter, times(1)).importBlock(any(), any(), any()); } @Test @@ -432,13 +449,14 @@ public void blockIsImportedOnlyOnceIfQuorumCommitsAreReceivedPriorToProposal() { roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, nodeKey, messageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); round.handleCommitMessage( messageFactory.createCommit(roundIdentifier, proposedBlock.getHash(), remoteCommitSeal)); @@ -446,8 +464,6 @@ public void blockIsImportedOnlyOnceIfQuorumCommitsAreReceivedPriorToProposal() { round.handleProposalMessage( messageFactory.createProposal( roundIdentifier, proposedBlock, Collections.emptyList(), Collections.emptyList())); - - verify(blockImporter, times(1)).importBlock(any(), any(), any()); } @Test @@ -463,13 +479,14 @@ public void exceptionDuringNodeKeySigningDoesNotEscape() { roundState, blockCreator, protocolContext, - blockImporter, + protocolSchedule, subscribers, throwingNodeKey, throwingMessageFactory, transmitter, roundTimer, - bftExtraDataCodec); + bftExtraDataCodec, + parentHeader); round.handleProposalMessage( messageFactory.createProposal( diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/sync/QbftPivotSelectorTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/sync/QbftPivotSelectorTest.java new file mode 100644 index 00000000000..2f3013ea714 --- /dev/null +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/sync/QbftPivotSelectorTest.java @@ -0,0 +1,159 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.consensus.qbft.sync; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.consensus.common.bft.BftContext; +import org.hyperledger.besu.consensus.common.validator.ValidatorProvider; +import org.hyperledger.besu.consensus.qbft.BFTPivotSelectorFromPeers; +import org.hyperledger.besu.cryptoservices.NodeKey; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.manager.EthPeers; +import org.hyperledger.besu.ethereum.eth.sync.SyncMode; +import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; +import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncState; +import org.hyperledger.besu.ethereum.eth.sync.fastsync.NoSyncRequiredException; +import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; +import org.hyperledger.besu.plugin.services.MetricsSystem; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class QbftPivotSelectorTest { + private final SynchronizerConfiguration.Builder syncConfigBuilder = + new SynchronizerConfiguration.Builder().syncMode(SyncMode.SNAP).syncMinimumPeerCount(1); + private final SynchronizerConfiguration syncConfig = syncConfigBuilder.build(); + @Mock private NodeKey nodeKey; + @Mock private BlockHeader blockHeader; + @Mock private ProtocolContext protocolContext; + @Mock private BftContext bftContext; + @Mock private SyncState syncState; + @Mock private MetricsSystem metricsSystem; + @Mock private EthContext ethContext; + @Mock private EthPeers ethPeers; + @Mock private ValidatorProvider validatorProvider; + + @BeforeEach + public void setUp() { + when(protocolContext.getConsensusContext(any())).thenReturn(bftContext); + when(bftContext.getValidatorProvider()).thenReturn(validatorProvider); + } + + @Test + public void returnEmptySyncStateIfValidatorWithOtherValidatorsButNoPeers() { + + // We're not the only validator so add some other ones + List
validatorList = new ArrayList<>(); + validatorList.add(Address.ZERO); + validatorList.add(Address.ZERO); + validatorList.add(Address.ZERO); + + when(ethContext.getEthPeers()).thenReturn(ethPeers); + when(validatorProvider.nodeIsValidator(any())).thenReturn(true); + when(validatorProvider.getValidatorsAtHead()).thenReturn(validatorList); + BFTPivotSelectorFromPeers pivotSelector = + new BFTPivotSelectorFromPeers( + ethContext, + syncConfig, + syncState, + metricsSystem, + protocolContext, + nodeKey, + blockHeader); + Optional pivotState = pivotSelector.selectNewPivotBlock(); + assertThat(pivotState.isEmpty()).isTrue(); + } + + @Test + @SuppressWarnings("unused") + public void returnNoSyncRequiredIfOnlyValidatorAndNoPeers() { + + // We're the only validator so just put us in the list + List
validatorList = new ArrayList<>(); + validatorList.add(Address.ZERO); + + when(ethContext.getEthPeers()).thenReturn(ethPeers); + when(validatorProvider.nodeIsValidator(any())).thenReturn(true); + when(validatorProvider.getValidatorsAtHead()).thenReturn(validatorList); + BFTPivotSelectorFromPeers pivotSelector = + new BFTPivotSelectorFromPeers( + ethContext, + syncConfig, + syncState, + metricsSystem, + protocolContext, + nodeKey, + blockHeader); + + try { + Optional pivotState = pivotSelector.selectNewPivotBlock(); + fail("Expected NoSyncRequiredException but none thrown"); + } catch (NoSyncRequiredException e) { + // Ignore - just make sure we get here + } + } + + @Test + public void returnEmptySyncStateIfNonValidatorWithNoBestPeer() { + when(ethContext.getEthPeers()).thenReturn(ethPeers); + when(validatorProvider.nodeIsValidator(any())).thenReturn(false); + BFTPivotSelectorFromPeers pivotSelector = + new BFTPivotSelectorFromPeers( + ethContext, + syncConfig, + syncState, + metricsSystem, + protocolContext, + nodeKey, + blockHeader); + + Optional pivotState = pivotSelector.selectNewPivotBlock(); + assertThat(pivotState.isEmpty()).isTrue(); + } + + @Test + public void returnEmptySyncStateIfValidatorAndNotAtGenesisAndOtherValidators() { + when(ethContext.getEthPeers()).thenReturn(ethPeers); + when(validatorProvider.nodeIsValidator(any())).thenReturn(false); + when(blockHeader.getNumber()).thenReturn(10L); + BFTPivotSelectorFromPeers pivotSelector = + new BFTPivotSelectorFromPeers( + ethContext, + syncConfig, + syncState, + metricsSystem, + protocolContext, + nodeKey, + blockHeader); + + Optional pivotState = pivotSelector.selectNewPivotBlock(); + assertThat(pivotState.isEmpty()).isTrue(); + } +} diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validation/ProposalPayloadValidatorTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validation/ProposalPayloadValidatorTest.java index fb8b67393d6..09e9455d89e 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validation/ProposalPayloadValidatorTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validation/ProposalPayloadValidatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -21,39 +21,28 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import org.hyperledger.besu.consensus.common.bft.BftBlockHeaderFunctions; -import org.hyperledger.besu.consensus.common.bft.BftExtraDataCodec; +import org.hyperledger.besu.consensus.common.bft.BftContext; import org.hyperledger.besu.consensus.common.bft.ConsensusRoundHelpers; import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier; import org.hyperledger.besu.consensus.common.bft.ProposedBlockHelpers; -import org.hyperledger.besu.consensus.qbft.QbftContext; import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec; import org.hyperledger.besu.consensus.qbft.messagewrappers.Proposal; import org.hyperledger.besu.consensus.qbft.payload.MessageFactory; -import org.hyperledger.besu.consensus.qbft.pki.PkiQbftBlockHeaderFunctions; -import org.hyperledger.besu.consensus.qbft.pki.PkiQbftExtraData; -import org.hyperledger.besu.consensus.qbft.pki.PkiQbftExtraDataCodec; import org.hyperledger.besu.cryptoservices.NodeKey; import org.hyperledger.besu.cryptoservices.NodeKeyUtils; import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.BlockProcessingResult; import org.hyperledger.besu.ethereum.BlockValidator; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; -import org.hyperledger.besu.ethereum.core.BlockDataGenerator; -import org.hyperledger.besu.ethereum.core.BlockDataGenerator.BlockOptions; import org.hyperledger.besu.ethereum.core.Util; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; -import org.hyperledger.besu.pki.cms.CmsValidator; -import java.util.Collections; -import java.util.List; import java.util.Optional; -import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -66,7 +55,6 @@ public class ProposalPayloadValidatorTest { @Mock private BlockValidator blockValidator; @Mock private MutableBlockchain blockChain; @Mock private WorldStateArchive worldStateArchive; - @Mock private CmsValidator cmsValidator; private ProtocolContext protocolContext; private static final int CHAIN_HEIGHT = 3; @@ -86,15 +74,15 @@ public void setup() { new ProtocolContext( blockChain, worldStateArchive, - setupContextWithBftExtraDataEncoder(QbftContext.class, emptyList(), bftExtraDataCodec), - Optional.empty()); + setupContextWithBftExtraDataEncoder(BftContext.class, emptyList(), bftExtraDataCodec), + new BadBlockManager()); } @Test public void validationPassesWhenProposerAndRoundMatchAndBlockIsValid() { final ProposalPayloadValidator payloadValidator = new ProposalPayloadValidator( - expectedProposer, roundIdentifier, blockValidator, protocolContext, bftExtraDataCodec); + expectedProposer, roundIdentifier, blockValidator, protocolContext); final Block block = ProposedBlockHelpers.createProposalBlock(emptyList(), roundIdentifier, bftExtraDataCodec); final Proposal proposal = @@ -104,7 +92,8 @@ public void validationPassesWhenProposerAndRoundMatchAndBlockIsValid() { eq(protocolContext), eq(block), eq(HeaderValidationMode.LIGHT), - eq(HeaderValidationMode.FULL))) + eq(HeaderValidationMode.FULL), + eq(false))) .thenReturn(new BlockProcessingResult(Optional.empty())); assertThat(payloadValidator.validate(proposal.getSignedPayload())).isTrue(); @@ -114,7 +103,7 @@ public void validationPassesWhenProposerAndRoundMatchAndBlockIsValid() { public void validationPassesWhenBlockRoundDoesNotMatchProposalRound() { final ProposalPayloadValidator payloadValidator = new ProposalPayloadValidator( - expectedProposer, roundIdentifier, blockValidator, protocolContext, bftExtraDataCodec); + expectedProposer, roundIdentifier, blockValidator, protocolContext); final Block block = ProposedBlockHelpers.createProposalBlock( @@ -128,7 +117,8 @@ public void validationPassesWhenBlockRoundDoesNotMatchProposalRound() { eq(protocolContext), eq(block), eq(HeaderValidationMode.LIGHT), - eq(HeaderValidationMode.FULL))) + eq(HeaderValidationMode.FULL), + eq(false))) .thenReturn(new BlockProcessingResult(Optional.empty())); assertThat(payloadValidator.validate(proposal.getSignedPayload())).isTrue(); @@ -141,7 +131,7 @@ public void validationFailsWhenBlockFailsValidation() { final ProposalPayloadValidator payloadValidator = new ProposalPayloadValidator( - expectedProposer, roundIdentifier, blockValidator, protocolContext, bftExtraDataCodec); + expectedProposer, roundIdentifier, blockValidator, protocolContext); final Block block = ProposedBlockHelpers.createProposalBlock(emptyList(), roundIdentifier, bftExtraDataCodec); final Proposal proposal = @@ -151,7 +141,8 @@ public void validationFailsWhenBlockFailsValidation() { eq(protocolContext), eq(block), eq(HeaderValidationMode.LIGHT), - eq(HeaderValidationMode.FULL))) + eq(HeaderValidationMode.FULL), + eq(false))) .thenReturn(new BlockProcessingResult("Failed")); assertThat(payloadValidator.validate(proposal.getSignedPayload())).isFalse(); @@ -161,11 +152,7 @@ public void validationFailsWhenBlockFailsValidation() { public void validationFailsWhenExpectedProposerDoesNotMatchPayloadsAuthor() { final ProposalPayloadValidator payloadValidator = new ProposalPayloadValidator( - Address.fromHexString("0x1"), - roundIdentifier, - blockValidator, - protocolContext, - bftExtraDataCodec); + Address.fromHexString("0x1"), roundIdentifier, blockValidator, protocolContext); final Block block = ProposedBlockHelpers.createProposalBlock(emptyList(), roundIdentifier); final Proposal proposal = messageFactory.createProposal(roundIdentifier, block, emptyList(), emptyList()); @@ -178,7 +165,7 @@ public void validationFailsWhenExpectedProposerDoesNotMatchPayloadsAuthor() { public void validationFailsWhenMessageMismatchesExpectedRound() { final ProposalPayloadValidator payloadValidator = new ProposalPayloadValidator( - expectedProposer, roundIdentifier, blockValidator, protocolContext, bftExtraDataCodec); + expectedProposer, roundIdentifier, blockValidator, protocolContext); final Block block = ProposedBlockHelpers.createProposalBlock(emptyList(), roundIdentifier); final Proposal proposal = @@ -196,7 +183,7 @@ public void validationFailsWhenMessageMismatchesExpectedRound() { public void validationFailsWhenMessageMismatchesExpectedHeight() { final ProposalPayloadValidator payloadValidator = new ProposalPayloadValidator( - expectedProposer, roundIdentifier, blockValidator, protocolContext, bftExtraDataCodec); + expectedProposer, roundIdentifier, blockValidator, protocolContext); final Block block = ProposedBlockHelpers.createProposalBlock(emptyList(), roundIdentifier); final Proposal proposal = @@ -214,7 +201,7 @@ public void validationFailsWhenMessageMismatchesExpectedHeight() { public void validationFailsForBlockWithIncorrectHeight() { final ProposalPayloadValidator payloadValidator = new ProposalPayloadValidator( - expectedProposer, roundIdentifier, blockValidator, protocolContext, bftExtraDataCodec); + expectedProposer, roundIdentifier, blockValidator, protocolContext); final Block block = ProposedBlockHelpers.createProposalBlock( emptyList(), @@ -227,107 +214,10 @@ public void validationFailsForBlockWithIncorrectHeight() { eq(protocolContext), eq(block), eq(HeaderValidationMode.LIGHT), - eq(HeaderValidationMode.FULL))) + eq(HeaderValidationMode.FULL), + eq(false))) .thenReturn(new BlockProcessingResult(Optional.empty())); assertThat(payloadValidator.validate(proposal.getSignedPayload())).isFalse(); } - - @Test - public void validationForCmsFailsWhenCmsFailsValidation() { - final PkiQbftExtraDataCodec pkiQbftExtraDataCodec = new PkiQbftExtraDataCodec(); - final QbftContext qbftContext = - setupContextWithBftExtraDataEncoder(QbftContext.class, emptyList(), pkiQbftExtraDataCodec); - final Bytes cms = Bytes.fromHexStringLenient("0x1"); - final ProtocolContext protocolContext = - new ProtocolContext(blockChain, worldStateArchive, qbftContext, Optional.empty()); - - final ProposalPayloadValidator payloadValidator = - new ProposalPayloadValidator( - expectedProposer, - roundIdentifier, - blockValidator, - protocolContext, - pkiQbftExtraDataCodec, - Optional.of(cmsValidator)); - final Block block = - createPkiProposalBlock(emptyList(), roundIdentifier, pkiQbftExtraDataCodec, cms); - final Proposal proposal = - messageFactory.createProposal(roundIdentifier, block, emptyList(), emptyList()); - final Hash hashWithoutCms = - PkiQbftBlockHeaderFunctions.forCmsSignature(pkiQbftExtraDataCodec).hash(block.getHeader()); - - when(blockValidator.validateAndProcessBlock( - eq(protocolContext), - eq(block), - eq(HeaderValidationMode.LIGHT), - eq(HeaderValidationMode.FULL))) - .thenReturn(new BlockProcessingResult(Optional.empty())); - when(cmsValidator.validate(eq(cms), eq(hashWithoutCms))).thenReturn(false); - - assertThat(payloadValidator.validate(proposal.getSignedPayload())).isFalse(); - } - - @Test - public void validationForCmsPassesWhenCmsIsValid() { - final PkiQbftExtraDataCodec pkiQbftExtraDataCodec = new PkiQbftExtraDataCodec(); - final QbftContext qbftContext = - setupContextWithBftExtraDataEncoder(QbftContext.class, emptyList(), pkiQbftExtraDataCodec); - final Bytes cms = Bytes.fromHexStringLenient("0x1"); - final ProtocolContext protocolContext = - new ProtocolContext(blockChain, worldStateArchive, qbftContext, Optional.empty()); - - final ProposalPayloadValidator payloadValidator = - new ProposalPayloadValidator( - expectedProposer, - roundIdentifier, - blockValidator, - protocolContext, - pkiQbftExtraDataCodec, - Optional.of(cmsValidator)); - final Block block = - createPkiProposalBlock(emptyList(), roundIdentifier, pkiQbftExtraDataCodec, cms); - final Proposal proposal = - messageFactory.createProposal(roundIdentifier, block, emptyList(), emptyList()); - final Hash hashWithoutCms = - PkiQbftBlockHeaderFunctions.forCmsSignature(pkiQbftExtraDataCodec).hash(block.getHeader()); - - when(blockValidator.validateAndProcessBlock( - eq(protocolContext), - eq(block), - eq(HeaderValidationMode.LIGHT), - eq(HeaderValidationMode.FULL))) - .thenReturn(new BlockProcessingResult(Optional.empty())); - when(cmsValidator.validate(eq(cms), eq(hashWithoutCms))).thenReturn(true); - - assertThat(payloadValidator.validate(proposal.getSignedPayload())).isTrue(); - } - - public static Block createPkiProposalBlock( - final List
validators, - final ConsensusRoundIdentifier roundId, - final BftExtraDataCodec bftExtraDataCodec, - final Bytes cms) { - final Bytes extraData = - bftExtraDataCodec.encode( - new PkiQbftExtraData( - Bytes.wrap(new byte[32]), - Collections.emptyList(), - Optional.empty(), - roundId.getRoundNumber(), - validators, - cms)); - final BlockOptions blockOptions = - BlockOptions.create() - .setExtraData(extraData) - .setBlockNumber(roundId.getSequenceNumber()) - .setBlockHeaderFunctions(BftBlockHeaderFunctions.forCommittedSeal(bftExtraDataCodec)) - .hasOmmers(false) - .hasTransactions(false); - - if (validators.size() > 0) { - blockOptions.setCoinbase(validators.get(0)); - } - return new BlockDataGenerator().block(blockOptions); - } } diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validation/ProposalValidatorTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validation/ProposalValidatorTest.java index 2c0b40c9d44..43afff35018 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validation/ProposalValidatorTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validation/ProposalValidatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 ConsenSys AG. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -24,12 +24,13 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.when; +import org.hyperledger.besu.consensus.common.bft.BftContext; import org.hyperledger.besu.consensus.common.bft.BftHelpers; +import org.hyperledger.besu.consensus.common.bft.BftProtocolSchedule; import org.hyperledger.besu.consensus.common.bft.ConsensusRoundHelpers; import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier; import org.hyperledger.besu.consensus.common.bft.ProposedBlockHelpers; import org.hyperledger.besu.consensus.common.bft.payload.SignedData; -import org.hyperledger.besu.consensus.qbft.QbftContext; import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec; import org.hyperledger.besu.consensus.qbft.messagewrappers.Prepare; import org.hyperledger.besu.consensus.qbft.messagewrappers.Proposal; @@ -41,9 +42,11 @@ import org.hyperledger.besu.ethereum.BlockProcessingResult; import org.hyperledger.besu.ethereum.BlockValidator; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import java.util.HashMap; @@ -86,6 +89,8 @@ public RoundSpecificItems( @Mock private BlockValidator blockValidator; @Mock private MutableBlockchain blockChain; @Mock private WorldStateArchive worldStateArchive; + @Mock private BftProtocolSchedule protocolSchedule; + @Mock private ProtocolSpec protocolSpec; private ProtocolContext protocolContext; private final Map roundItems = new HashMap<>(); @@ -97,18 +102,22 @@ public void setup() { new ProtocolContext( blockChain, worldStateArchive, - setupContextWithBftExtraDataEncoder( - QbftContext.class, emptyList(), bftExtraDataEncoder), - Optional.empty()); + setupContextWithBftExtraDataEncoder(BftContext.class, emptyList(), bftExtraDataEncoder), + new BadBlockManager()); // typically tests require the blockValidation to be successful when(blockValidator.validateAndProcessBlock( eq(protocolContext), any(), eq(HeaderValidationMode.LIGHT), - eq(HeaderValidationMode.FULL))) + eq(HeaderValidationMode.FULL), + eq(false))) .thenReturn(new BlockProcessingResult(Optional.empty())); + when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); + + when(protocolSpec.getBlockValidator()).thenReturn(blockValidator); + roundItems.put(ROUND_ID.ZERO, createRoundSpecificItems(0)); roundItems.put(ROUND_ID.ONE, createRoundSpecificItems(1)); } @@ -121,8 +130,8 @@ private RoundSpecificItems createRoundSpecificItems(final int roundNumber) { validators.getNodeAddresses(), roundIdentifier, bftExtraDataEncoder), roundIdentifier, new ProposalValidator( - blockValidator, protocolContext, + protocolSchedule, BftHelpers.calculateRequiredValidatorQuorum(VALIDATOR_COUNT), validators.getNodeAddresses(), roundIdentifier, @@ -159,7 +168,8 @@ public void validationFailsIfBlockIsInvalid() { eq(protocolContext), any(), eq(HeaderValidationMode.LIGHT), - eq(HeaderValidationMode.FULL))) + eq(HeaderValidationMode.FULL), + eq(false))) .thenReturn(new BlockProcessingResult("Failed")); assertThat(roundItem.messageValidator.validate(proposal)).isFalse(); diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validation/RoundChangeMessageValidatorTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validation/RoundChangeMessageValidatorTest.java index a604acc9026..82e8fb5c7a8 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validation/RoundChangeMessageValidatorTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validation/RoundChangeMessageValidatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -20,16 +20,17 @@ import static org.hyperledger.besu.consensus.common.bft.BftContextBuilder.setupContextWithBftExtraDataEncoder; import static org.hyperledger.besu.consensus.qbft.validation.ValidationTestHelpers.createPreparePayloads; import static org.hyperledger.besu.consensus.qbft.validation.ValidationTestHelpers.createPreparedCertificate; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; +import org.hyperledger.besu.consensus.common.bft.BftContext; import org.hyperledger.besu.consensus.common.bft.BftHelpers; +import org.hyperledger.besu.consensus.common.bft.BftProtocolSchedule; import org.hyperledger.besu.consensus.common.bft.ConsensusRoundHelpers; import org.hyperledger.besu.consensus.common.bft.ConsensusRoundIdentifier; import org.hyperledger.besu.consensus.common.bft.ProposedBlockHelpers; import org.hyperledger.besu.consensus.common.bft.payload.SignedData; -import org.hyperledger.besu.consensus.qbft.QbftContext; import org.hyperledger.besu.consensus.qbft.QbftExtraDataCodec; import org.hyperledger.besu.consensus.qbft.messagewrappers.RoundChange; import org.hyperledger.besu.consensus.qbft.payload.PreparedRoundMetadata; @@ -40,9 +41,10 @@ import org.hyperledger.besu.ethereum.BlockProcessingResult; import org.hyperledger.besu.ethereum.BlockValidator; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; -import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import java.util.Collections; @@ -59,9 +61,11 @@ public class RoundChangeMessageValidatorTest { @Mock private RoundChangePayloadValidator payloadValidator; - @Mock private BlockValidator blockValidator; @Mock private MutableBlockchain blockChain; @Mock private WorldStateArchive worldStateArchive; + @Mock private BftProtocolSchedule protocolSchedule; + @Mock private BlockValidator blockValidator; + @Mock private ProtocolSpec protocolSpec; private ProtocolContext protocolContext; private RoundChangeMessageValidator messageValidator; @@ -80,9 +84,12 @@ public void setup() { new ProtocolContext( blockChain, worldStateArchive, - setupContextWithBftExtraDataEncoder( - QbftContext.class, emptyList(), bftExtraDataEncoder), - Optional.empty()); + setupContextWithBftExtraDataEncoder(BftContext.class, emptyList(), bftExtraDataEncoder), + new BadBlockManager()); + + lenient().when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); + + lenient().when(protocolSpec.getBlockValidator()).thenReturn(blockValidator); } @Test @@ -94,8 +101,8 @@ public void roundChangeWithNoPiggyBackedDataIsValid() { BftHelpers.calculateRequiredValidatorQuorum(VALIDATOR_COUNT), CHAIN_HEIGHT, validators.getNodeAddresses(), - blockValidator, - protocolContext); + protocolContext, + protocolSchedule); for (int i = 0; i < VALIDATOR_COUNT; i++) { final RoundChange message = @@ -106,18 +113,17 @@ public void roundChangeWithNoPiggyBackedDataIsValid() { @Test public void roundChangeWithValidPiggyBackDataIsValid() { - when(blockValidator.validateAndProcessBlock( - any(), any(), eq(HeaderValidationMode.LIGHT), eq(HeaderValidationMode.FULL))) - .thenReturn(new BlockProcessingResult(Optional.empty())); when(payloadValidator.validate(any())).thenReturn(true); + when(blockValidator.validateAndProcessBlock(any(), any(), any(), any())) + .thenReturn(new BlockProcessingResult(Optional.empty())); messageValidator = new RoundChangeMessageValidator( payloadValidator, BftHelpers.calculateRequiredValidatorQuorum(VALIDATOR_COUNT), CHAIN_HEIGHT, validators.getNodeAddresses(), - blockValidator, - protocolContext); + protocolContext, + protocolSchedule); final Block block = ProposedBlockHelpers.createProposalBlock( @@ -133,18 +139,17 @@ public void roundChangeWithValidPiggyBackDataIsValid() { @Test public void roundChangeWithBlockRoundMismatchingPreparesIsValid() { - when(blockValidator.validateAndProcessBlock( - any(), any(), eq(HeaderValidationMode.LIGHT), eq(HeaderValidationMode.FULL))) - .thenReturn(new BlockProcessingResult(Optional.empty())); when(payloadValidator.validate(any())).thenReturn(true); + when(blockValidator.validateAndProcessBlock(any(), any(), any(), any())) + .thenReturn(new BlockProcessingResult(Optional.empty())); messageValidator = new RoundChangeMessageValidator( payloadValidator, BftHelpers.calculateRequiredValidatorQuorum(VALIDATOR_COUNT), CHAIN_HEIGHT, validators.getNodeAddresses(), - blockValidator, - protocolContext); + protocolContext, + protocolSchedule); final Block block = ProposedBlockHelpers.createProposalBlock( @@ -162,18 +167,17 @@ public void roundChangeWithBlockRoundMismatchingPreparesIsValid() { @Test public void blockIsInvalidFailsValidation() { - when(blockValidator.validateAndProcessBlock( - any(), any(), eq(HeaderValidationMode.LIGHT), eq(HeaderValidationMode.FULL))) - .thenReturn(new BlockProcessingResult("Failed")); when(payloadValidator.validate(any())).thenReturn(true); + when(blockValidator.validateAndProcessBlock(any(), any(), any(), any())) + .thenReturn(BlockProcessingResult.FAILED); messageValidator = new RoundChangeMessageValidator( payloadValidator, BftHelpers.calculateRequiredValidatorQuorum(VALIDATOR_COUNT), CHAIN_HEIGHT, validators.getNodeAddresses(), - blockValidator, - protocolContext); + protocolContext, + protocolSchedule); final Block block = ProposedBlockHelpers.createProposalBlock(Collections.emptyList(), roundIdentifier); @@ -195,8 +199,8 @@ public void invalidEmbeddedPayloadFailsValidation() { BftHelpers.calculateRequiredValidatorQuorum(VALIDATOR_COUNT), CHAIN_HEIGHT, validators.getNodeAddresses(), - blockValidator, - protocolContext); + protocolContext, + protocolSchedule); final RoundChange message = validators.getMessageFactory(0).createRoundChange(targetRound, Optional.empty()); @@ -205,18 +209,17 @@ public void invalidEmbeddedPayloadFailsValidation() { @Test public void insufficientPiggyBackedPrepareMessagesIsInvalid() { - when(blockValidator.validateAndProcessBlock( - any(), any(), eq(HeaderValidationMode.LIGHT), eq(HeaderValidationMode.FULL))) - .thenReturn(new BlockProcessingResult(Optional.empty())); when(payloadValidator.validate(any())).thenReturn(true); + when(blockValidator.validateAndProcessBlock(any(), any(), any(), any())) + .thenReturn(new BlockProcessingResult(Optional.empty())); messageValidator = new RoundChangeMessageValidator( payloadValidator, BftHelpers.calculateRequiredValidatorQuorum(VALIDATOR_COUNT), CHAIN_HEIGHT, validators.getNodeAddresses(), - blockValidator, - protocolContext); + protocolContext, + protocolSchedule); final Block block = ProposedBlockHelpers.createProposalBlock( @@ -232,18 +235,17 @@ public void insufficientPiggyBackedPrepareMessagesIsInvalid() { @Test public void prepareFromNonValidatorFails() { - when(blockValidator.validateAndProcessBlock( - any(), any(), eq(HeaderValidationMode.LIGHT), eq(HeaderValidationMode.FULL))) - .thenReturn(new BlockProcessingResult(Optional.empty())); when(payloadValidator.validate(any())).thenReturn(true); + when(blockValidator.validateAndProcessBlock(any(), any(), any(), any())) + .thenReturn(new BlockProcessingResult(Optional.empty())); messageValidator = new RoundChangeMessageValidator( payloadValidator, BftHelpers.calculateRequiredValidatorQuorum(VALIDATOR_COUNT), CHAIN_HEIGHT, validators.getNodeAddresses(), - blockValidator, - protocolContext); + protocolContext, + protocolSchedule); final QbftNode nonValidator = QbftNode.create(); @@ -261,18 +263,17 @@ public void prepareFromNonValidatorFails() { @Test public void validationFailsIfPreparedMetadataContainsDifferentRoundToBlock() { - when(blockValidator.validateAndProcessBlock( - any(), any(), eq(HeaderValidationMode.LIGHT), eq(HeaderValidationMode.FULL))) - .thenReturn(new BlockProcessingResult(Optional.empty())); when(payloadValidator.validate(any())).thenReturn(true); + when(blockValidator.validateAndProcessBlock(any(), any(), any(), any())) + .thenReturn(new BlockProcessingResult(Optional.empty())); messageValidator = new RoundChangeMessageValidator( payloadValidator, BftHelpers.calculateRequiredValidatorQuorum(VALIDATOR_COUNT), CHAIN_HEIGHT, validators.getNodeAddresses(), - blockValidator, - protocolContext); + protocolContext, + protocolSchedule); final Block block = ProposedBlockHelpers.createProposalBlock( @@ -296,18 +297,17 @@ public void validationFailsIfPreparedMetadataContainsDifferentRoundToBlock() { @Test public void validationFailsIfPreparesContainsDifferentRoundToBlock() { - when(blockValidator.validateAndProcessBlock( - any(), any(), eq(HeaderValidationMode.LIGHT), eq(HeaderValidationMode.FULL))) - .thenReturn(new BlockProcessingResult(Optional.empty())); when(payloadValidator.validate(any())).thenReturn(true); + when(blockValidator.validateAndProcessBlock(any(), any(), any(), any())) + .thenReturn(new BlockProcessingResult(Optional.empty())); messageValidator = new RoundChangeMessageValidator( payloadValidator, BftHelpers.calculateRequiredValidatorQuorum(VALIDATOR_COUNT), CHAIN_HEIGHT, validators.getNodeAddresses(), - blockValidator, - protocolContext); + protocolContext, + protocolSchedule); final Block block = ProposedBlockHelpers.createProposalBlock( @@ -333,18 +333,17 @@ public void validationFailsIfPreparesContainsDifferentRoundToBlock() { @Test public void validationFailsIfPreparesContainsWrongHeight() { - when(blockValidator.validateAndProcessBlock( - any(), any(), eq(HeaderValidationMode.LIGHT), eq(HeaderValidationMode.FULL))) - .thenReturn(new BlockProcessingResult(Optional.empty())); when(payloadValidator.validate(any())).thenReturn(true); + when(blockValidator.validateAndProcessBlock(any(), any(), any(), any())) + .thenReturn(new BlockProcessingResult(Optional.empty())); messageValidator = new RoundChangeMessageValidator( payloadValidator, BftHelpers.calculateRequiredValidatorQuorum(VALIDATOR_COUNT), CHAIN_HEIGHT, validators.getNodeAddresses(), - blockValidator, - protocolContext); + protocolContext, + protocolSchedule); final Block block = ProposedBlockHelpers.createProposalBlock( @@ -370,18 +369,17 @@ public void validationFailsIfPreparesContainsWrongHeight() { @Test public void validationFailsIfPreparesHaveDuplicateAuthors() { - when(blockValidator.validateAndProcessBlock( - any(), any(), eq(HeaderValidationMode.LIGHT), eq(HeaderValidationMode.FULL))) - .thenReturn(new BlockProcessingResult(Optional.empty())); when(payloadValidator.validate(any())).thenReturn(true); + when(blockValidator.validateAndProcessBlock(any(), any(), any(), any())) + .thenReturn(new BlockProcessingResult(Optional.empty())); messageValidator = new RoundChangeMessageValidator( payloadValidator, BftHelpers.calculateRequiredValidatorQuorum(VALIDATOR_COUNT), CHAIN_HEIGHT, validators.getNodeAddresses(), - blockValidator, - protocolContext); + protocolContext, + protocolSchedule); final Block block = ProposedBlockHelpers.createProposalBlock( @@ -402,18 +400,17 @@ public void validationFailsIfPreparesHaveDuplicateAuthors() { @Test public void validationFailsIfBlockExistsButNotPreparedMetadata() { - when(blockValidator.validateAndProcessBlock( - any(), any(), eq(HeaderValidationMode.LIGHT), eq(HeaderValidationMode.FULL))) - .thenReturn(new BlockProcessingResult(Optional.empty())); when(payloadValidator.validate(any())).thenReturn(true); + when(blockValidator.validateAndProcessBlock(any(), any(), any(), any())) + .thenReturn(new BlockProcessingResult(Optional.empty())); messageValidator = new RoundChangeMessageValidator( payloadValidator, BftHelpers.calculateRequiredValidatorQuorum(VALIDATOR_COUNT), CHAIN_HEIGHT, validators.getNodeAddresses(), - blockValidator, - protocolContext); + protocolContext, + protocolSchedule); final Block block = ProposedBlockHelpers.createProposalBlock(Collections.emptyList(), roundIdentifier); @@ -430,18 +427,17 @@ public void validationFailsIfBlockExistsButNotPreparedMetadata() { @Test public void validationFailsIfBlockHashDoesNotMatchPreparedMetadata() { - when(blockValidator.validateAndProcessBlock( - any(), any(), eq(HeaderValidationMode.LIGHT), eq(HeaderValidationMode.FULL))) - .thenReturn(new BlockProcessingResult(Optional.empty())); when(payloadValidator.validate(any())).thenReturn(true); + when(blockValidator.validateAndProcessBlock(any(), any(), any(), any())) + .thenReturn(new BlockProcessingResult(Optional.empty())); messageValidator = new RoundChangeMessageValidator( payloadValidator, BftHelpers.calculateRequiredValidatorQuorum(VALIDATOR_COUNT), CHAIN_HEIGHT, validators.getNodeAddresses(), - blockValidator, - protocolContext); + protocolContext, + protocolSchedule); final Block block = ProposedBlockHelpers.createProposalBlock(Collections.emptyList(), roundIdentifier); diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ForkingValidatorProviderTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ForkingValidatorProviderTest.java index 023e195e350..b61e33ae2c0 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ForkingValidatorProviderTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ForkingValidatorProviderTest.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.consensus.qbft.validator; import static java.util.Collections.emptyList; diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/QbftForksSchedulesFactoryTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/QbftForksSchedulesFactoryTest.java index abb75426d0a..d57ad8dd9fa 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/QbftForksSchedulesFactoryTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/QbftForksSchedulesFactoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorModeTransitionLoggerTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorModeTransitionLoggerTest.java index d6219c6e754..dabf26749a5 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorModeTransitionLoggerTest.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorModeTransitionLoggerTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.consensus.qbft.validator; import static org.mockito.ArgumentMatchers.eq; diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorTestUtils.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorTestUtils.java index 341f9a46c5c..46c940398c2 100644 --- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorTestUtils.java +++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/validator/ValidatorTestUtils.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/crypto/algorithms/build.gradle b/crypto/algorithms/build.gradle index dcddb83be34..97b05bd71ea 100644 --- a/crypto/algorithms/build.gradle +++ b/crypto/algorithms/build.gradle @@ -23,6 +23,7 @@ jar { 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash, 'Automatic-Module-Name': 'org.hyperledger.besu.internal.crypto' ) } @@ -40,9 +41,6 @@ dependencies { implementation 'org.hyperledger.besu:blake2bf' implementation 'com.google.guava:guava' - testImplementation 'junit:junit' testImplementation 'org.assertj:assertj-core' testImplementation 'org.junit.jupiter:junit-jupiter' - - testRuntimeOnly 'org.junit.vintage:junit-vintage-engine' } diff --git a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/AbstractSECP256.java b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/AbstractSECP256.java index 0ed5b5a5f34..b10e654626f 100644 --- a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/AbstractSECP256.java +++ b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/AbstractSECP256.java @@ -50,11 +50,13 @@ public abstract class AbstractSECP256 implements SignatureAlgorithm { /** The Curve. */ protected final ECDomainParameters curve; + /** The Half curve order. */ protected final BigInteger halfCurveOrder; /** The Key pair generator. */ protected final KeyPairGenerator keyPairGenerator; + /** The Curve order. */ protected final BigInteger curveOrder; @@ -395,7 +397,9 @@ public Optional recoverPublicKeyFromSignature( final Bytes32 dataHash, final SECPSignature signature) { final BigInteger publicKeyBI = recoverFromSignature(signature.getRecId(), signature.getR(), signature.getS(), dataHash); - return Optional.of(SECPPublicKey.create(publicKeyBI, ALGORITHM)); + return publicKeyBI == null + ? Optional.empty() + : Optional.of(SECPPublicKey.create(publicKeyBI, ALGORITHM)); } @Override diff --git a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/Blake2bfMessageDigest.java b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/Blake2bfMessageDigest.java index 63449d74b50..d458a4c43d5 100644 --- a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/Blake2bfMessageDigest.java +++ b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/Blake2bfMessageDigest.java @@ -34,16 +34,24 @@ public Blake2bfMessageDigest() { super(new Blake2bfDigest()); } + @Override + public Blake2bfMessageDigest clone() throws CloneNotSupportedException { + Blake2bfMessageDigest cloned = (Blake2bfMessageDigest) super.clone(); + cloned.digest = ((Blake2bfDigest) this.digest).clone(); + return cloned; + } + /** * Implementation of the `F` compression function of the Blake2b cryptographic hash function. * - *

RFC - https://tools.ietf.org/html/rfc7693 + *

RFC - ... * - *

Adapted from - https://github.com/keep-network/blake2b/blob/master/compression/f.go + *

Adapted from - ... * *

Optimized for 64-bit platforms */ - public static class Blake2bfDigest implements Digest { + public static class Blake2bfDigest implements Digest, Cloneable { /** The constant MESSAGE_LENGTH_BYTES. */ public static final int MESSAGE_LENGTH_BYTES = 213; @@ -71,27 +79,22 @@ public static class Blake2bfDigest implements Digest { // buffer which holds serialized input for this compression function // [ 4 bytes for rounds ][ 64 bytes for h ][ 128 bytes for m ] // [ 8 bytes for t_0 ][ 8 bytes for t_1 ][ 1 byte for f ] - private final byte[] buffer; + private byte[] buffer; private int bufferPos; // deserialized inputs for f compression - private final long[] h; - private final long[] m; - private final long[] t; + private long[] h; + private long[] m; + private long[] t; private boolean f; private long rounds; // unsigned integer represented as long - private final long[] v; + private long[] v; private static boolean useNative; static { - try { - useNative = LibBlake2bf.ENABLED; - } catch (UnsatisfiedLinkError ule) { - LOG.info("blake2bf native precompile not available: {}", ule.getMessage()); - useNative = false; - } + maybeEnableNative(); } /** Instantiates a new Blake2bf digest. */ @@ -112,6 +115,32 @@ public static class Blake2bfDigest implements Digest { v = new long[16]; } + @Override + public Blake2bfDigest clone() throws CloneNotSupportedException { + Blake2bfDigest cloned = (Blake2bfDigest) super.clone(); + cloned.buffer = this.buffer.clone(); + cloned.h = this.h.clone(); + cloned.m = this.m.clone(); + cloned.t = this.t.clone(); + cloned.v = this.v.clone(); + return cloned; + } + + /** + * Attempt to enable the native libreary + * + * @return true if the native library was successfully enabled. + */ + public static boolean maybeEnableNative() { + try { + useNative = LibBlake2bf.ENABLED; + } catch (UnsatisfiedLinkError | NoClassDefFoundError e) { + LOG.info("blake2bf native precompile not available: {}", e.getMessage()); + useNative = false; + } + return useNative; + } + /** Disable native. */ public static void disableNative() { useNative = false; diff --git a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/ECPointUtil.java b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/ECPointUtil.java index e000feb1f6e..c9d6e3992f7 100644 --- a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/ECPointUtil.java +++ b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/ECPointUtil.java @@ -24,6 +24,9 @@ /** The Ec point util class. */ public class ECPointUtil { + /** Default constructor. */ + private ECPointUtil() {} + /** * From bouncy castle ec point to ECPoint. * diff --git a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/KeyPairUtil.java b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/KeyPairUtil.java index f20b1b37103..ac1c9a7bf2e 100644 --- a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/KeyPairUtil.java +++ b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/KeyPairUtil.java @@ -38,6 +38,9 @@ public class KeyPairUtil { private static final Supplier SIGNATURE_ALGORITHM = Suppliers.memoize(SignatureAlgorithmFactory::getInstance); + /** Default constructor */ + private KeyPairUtil() {} + /** * Load resource file string. * @@ -84,6 +87,7 @@ public static KeyPair loadKeyPair(final File keyFile) { final KeyPair key; if (keyFile.exists()) { + LOG.info("Attempting to load public key from {}", keyFile.getAbsolutePath()); key = load(keyFile); LOG.info( "Loaded public key {} from {}", key.getPublicKey().toString(), keyFile.getAbsolutePath()); diff --git a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/MessageDigestFactory.java b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/MessageDigestFactory.java index 9f482d5fc74..24c13ba377c 100644 --- a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/MessageDigestFactory.java +++ b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/MessageDigestFactory.java @@ -30,10 +30,13 @@ private MessageDigestFactory() {} /** Keccak-256 */ public static final String KECCAK256_ALG = "KECCAK-256"; + /** SHA-256 */ public static final String SHA256_ALG = "SHA-256"; + /** RipeMD-160 */ public static final String RIPEMD160_ALG = "RIPEMD160"; + /** Blake2b F Function */ public static final String BLAKE2BF_ALG = "BLAKE2BF"; diff --git a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECP256K1.java b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECP256K1.java index 8d7ba7924f3..2ec0470e547 100644 --- a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECP256K1.java +++ b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECP256K1.java @@ -72,6 +72,7 @@ public void disableNative() { * * @return true if the native library was enabled. */ + @Override public boolean maybeEnableNative() { try { useNative = LibSecp256k1.CONTEXT != null; diff --git a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECP256R1.java b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECP256R1.java index 384723a239d..812d7762199 100644 --- a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECP256R1.java +++ b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECP256R1.java @@ -61,6 +61,22 @@ public boolean isNative() { return useNative; } + /** + * Attempt to enable the native library for secp256r1 + * + * @return true if the native library was enabled. + */ + @Override + public boolean maybeEnableNative() { + try { + useNative = BesuNativeEC.INSTANCE != null; + } catch (UnsatisfiedLinkError | NoClassDefFoundError e) { + LOG.info("Native secp256r1 not available - {}", e.getMessage()); + useNative = false; + } + return useNative; + } + /** * SECP256R1 is using the non-deterministic implementation of K calculation (standard) * diff --git a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECPPrivateKey.java b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECPPrivateKey.java index 528b569b728..19966e993c4 100644 --- a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECPPrivateKey.java +++ b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECPPrivateKey.java @@ -26,6 +26,7 @@ public class SECPPrivateKey implements java.security.PrivateKey { /** Encoded Bytes */ private final Bytes32 encoded; + /** Algorithm */ private final String algorithm; diff --git a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECPPublicKey.java b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECPPublicKey.java index 94916f35853..c51eb003d5a 100644 --- a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECPPublicKey.java +++ b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECPPublicKey.java @@ -34,6 +34,7 @@ public class SECPPublicKey implements java.security.PublicKey { /** Encoded Bytes */ private final Bytes encoded; + /** Algorithm */ private final String algorithm; diff --git a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECPSignature.java b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECPSignature.java index 49fc8848fd5..5524faac3fb 100644 --- a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECPSignature.java +++ b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SECPSignature.java @@ -31,6 +31,7 @@ public class SECPSignature { /** The constant BYTES_REQUIRED. */ public static final int BYTES_REQUIRED = 65; + /** * The recovery id to reconstruct the public key used to create the signature. * @@ -53,7 +54,7 @@ public class SECPSignature { * @param s the s * @param recId the rec id */ - SECPSignature(final BigInteger r, final BigInteger s, final byte recId) { + public SECPSignature(final BigInteger r, final BigInteger s, final byte recId) { this.r = r; this.s = s; this.recId = recId; diff --git a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SecureRandomProvider.java b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SecureRandomProvider.java index a13ef8a0731..2b987ee3917 100644 --- a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SecureRandomProvider.java +++ b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SecureRandomProvider.java @@ -23,6 +23,9 @@ public class SecureRandomProvider { private static final SecureRandom publicSecureRandom = secureRandom(); + /** Default constructor. */ + private SecureRandomProvider() {} + /** * Returns a shared instance of secure random intended to be used where the value is used publicly * diff --git a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SignatureAlgorithm.java b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SignatureAlgorithm.java index 408b1d423a9..db9565d18d0 100644 --- a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SignatureAlgorithm.java +++ b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SignatureAlgorithm.java @@ -31,6 +31,13 @@ public interface SignatureAlgorithm { /** Disable native. */ void disableNative(); + /** + * Attempt to enable the native library. + * + * @return true if the native library was enabled + */ + boolean maybeEnableNative(); + /** * Is native enabled. * diff --git a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SignatureAlgorithmFactory.java b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SignatureAlgorithmFactory.java index 0aaf6d70f1b..823561b52d0 100644 --- a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SignatureAlgorithmFactory.java +++ b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SignatureAlgorithmFactory.java @@ -49,10 +49,8 @@ public static void setInstance(final SignatureAlgorithmType signatureAlgorithmTy if (!SignatureAlgorithmType.isDefault(instance)) { LOG.info( - new StringBuilder("The signature algorithm uses the elliptic curve ") - .append(instance.getCurveName()) - .append(". The usage of alternative elliptic curves is still experimental.") - .toString()); + "The signature algorithm uses the elliptic curve {}. The usage of alternative elliptic curves is still experimental.", + instance.getCurveName()); } } diff --git a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SignatureAlgorithmType.java b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SignatureAlgorithmType.java index 26e877ab931..8f2521038a9 100644 --- a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SignatureAlgorithmType.java +++ b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/SignatureAlgorithmType.java @@ -94,11 +94,9 @@ public static boolean isDefault(final SignatureAlgorithm signatureAlgorithm) { } private static String invalidTypeErrorMessage(final String invalidEcCurve) { - return new StringBuilder() - .append(invalidEcCurve) - .append(" is not in the list of valid elliptic curves ") - .append(getEcCurvesListAsString()) - .toString(); + return invalidEcCurve + + " is not in the list of valid elliptic curves " + + getEcCurvesListAsString(); } private static String getEcCurvesListAsString() { diff --git a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/altbn128/AbstractFqp.java b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/altbn128/AbstractFqp.java index 739952cbdb8..00b9bd13b8c 100644 --- a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/altbn128/AbstractFqp.java +++ b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/altbn128/AbstractFqp.java @@ -32,8 +32,10 @@ public abstract class AbstractFqp implements FieldElement /** The Degree. */ protected final int degree; + /** The Modulus coefficients. */ protected final Fq[] modulusCoefficients; + /** The Coefficients. */ protected final Fq[] coefficients; diff --git a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/altbn128/AltBn128Fq12Pairer.java b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/altbn128/AltBn128Fq12Pairer.java index 40e0073134e..cd55e8d68c6 100644 --- a/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/altbn128/AltBn128Fq12Pairer.java +++ b/crypto/algorithms/src/main/java/org/hyperledger/besu/crypto/altbn128/AltBn128Fq12Pairer.java @@ -20,8 +20,8 @@ import java.util.Arrays; /** - * Adapted from the pc_ecc (Apache 2 License) implementation: - * https://github.com/ethereum/py_ecc/blob/master/py_ecc/bn128/bn128_field_elements.py + * Adapted from the pc_ecc (Apache 2 License) implementation. */ public class AltBn128Fq12Pairer { @@ -33,6 +33,9 @@ public class AltBn128Fq12Pairer { new BigInteger( "21888242871839275222246405745257275088548364400416034343698204186575808495617"); + /** Default constructor */ + private AltBn128Fq12Pairer() {} + /** * Pair fq 12. * diff --git a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/Blake2bfMessageDigestTest.java b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/Blake2bfMessageDigestTest.java index bd00dc7dae1..390d7d41438 100644 --- a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/Blake2bfMessageDigestTest.java +++ b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/Blake2bfMessageDigestTest.java @@ -17,6 +17,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.stream.IntStream; + import org.bouncycastle.util.Pack; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -29,6 +36,16 @@ */ public class Blake2bfMessageDigestTest { + private static final SecureRandom SECURE_RANDOM; + + static { + try { + SECURE_RANDOM = SecureRandom.getInstanceStrong(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + private Blake2bfMessageDigest messageDigest; // output when input is all 0 @@ -124,6 +141,39 @@ public void throwsIfEmptyBufferUpdatedLargeByteArray() { .isInstanceOf(IllegalArgumentException.class); } + @SuppressWarnings("unchecked") + @Test + public void testDigestThreadSafety() throws ExecutionException, InterruptedException { + final byte[] input = new byte[213]; + ; + SECURE_RANDOM.nextBytes(input); + int numberOfHashes = 10; + + CompletableFuture[] futures = + IntStream.range(0, numberOfHashes) + .mapToObj( + i -> + CompletableFuture.supplyAsync( + () -> { + try { + MessageDigest clonedDigest = messageDigest.clone(); + clonedDigest.update(input); + byte[] digest = clonedDigest.digest(); + return digest; + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + })) + .toArray(CompletableFuture[]::new); + + CompletableFuture.allOf(futures).get(); + + byte[] expectedHash = futures[0].get(); + for (CompletableFuture future : futures) { + assertThat(expectedHash).isEqualTo(future.get()); + } + } + @ParameterizedTest @CsvFileSource(resources = "eip152TestCases.csv", numLinesToSkip = 1) public void eip152TestCases(final String hexIn, final String hexExpected) { diff --git a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/ECPointUtilTest.java b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/ECPointUtilTest.java index ac95635ef1e..ff802516575 100644 --- a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/ECPointUtilTest.java +++ b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/ECPointUtilTest.java @@ -20,7 +20,7 @@ import java.security.spec.ECPoint; import org.apache.tuweni.bytes.Bytes; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class ECPointUtilTest { diff --git a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/HashTest.java b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/HashTest.java index a2d8318e120..bc71c0c1530 100644 --- a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/HashTest.java +++ b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/HashTest.java @@ -19,7 +19,7 @@ import org.apache.tuweni.bytes.Bytes; import org.bouncycastle.util.encoders.Hex; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class HashTest { diff --git a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/KeyPairTest.java b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/KeyPairTest.java index a8c1ac7e8f0..4155d2c5a4d 100644 --- a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/KeyPairTest.java +++ b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/KeyPairTest.java @@ -28,8 +28,8 @@ import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; public class KeyPairTest { public static final String ALGORITHM = SignatureAlgorithm.ALGORITHM; @@ -39,7 +39,7 @@ public class KeyPairTest { public static KeyPairGenerator keyPairGenerator; public static ECDomainParameters curve; - @BeforeClass + @BeforeAll public static void setUp() { Security.addProvider(new BouncyCastleProvider()); diff --git a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/KeyPairUtilTest.java b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/KeyPairUtilTest.java index 13c78d52bdc..cc2bcb0f460 100644 --- a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/KeyPairUtilTest.java +++ b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/KeyPairUtilTest.java @@ -19,7 +19,7 @@ import java.io.File; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class KeyPairUtilTest { diff --git a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SECP256K1Test.java b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SECP256K1Test.java index 060bdad844b..28fb0fb4f1c 100644 --- a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SECP256K1Test.java +++ b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SECP256K1Test.java @@ -28,9 +28,9 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class SECP256K1Test { @@ -39,7 +39,7 @@ public class SECP256K1Test { protected static String suiteStartTime = null; protected static String suiteName = null; - @BeforeClass + @BeforeAll public static void setTestSuiteStartTime() { suiteStartTime = LocalDateTime.now(ZoneId.systemDefault()) @@ -47,7 +47,7 @@ public static void setTestSuiteStartTime() { suiteName(SECP256K1Test.class); } - @Before + @BeforeEach public void setUp() { secp256K1 = new SECP256K1(); } diff --git a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SECP256R1Test.java b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SECP256R1Test.java index bd791ef9be3..22bd5c76512 100644 --- a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SECP256R1Test.java +++ b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SECP256R1Test.java @@ -29,9 +29,9 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class SECP256R1Test { @@ -103,7 +103,7 @@ public class SECP256R1Test { protected static String suiteStartTime = null; protected static String suiteName = null; - @BeforeClass + @BeforeAll public static void setTestSuiteStartTime() { suiteStartTime = LocalDateTime.now(ZoneId.systemDefault()) @@ -113,7 +113,7 @@ public static void setTestSuiteStartTime() { SignatureAlgorithmFactory.setInstance(SignatureAlgorithmType.create("secp256r1")); } - @Before + @BeforeEach public void setUp() { secp256R1 = new SECP256R1(); } @@ -127,7 +127,7 @@ public static String suiteName() { } @Test - public void recoverPublicKeyFromSignature() { + void recoverPublicKeyFromSignature() { final SECPPrivateKey privateKey = secp256R1.createPrivateKey( new BigInteger("c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4", 16)); @@ -139,20 +139,20 @@ public void recoverPublicKeyFromSignature() { final SECPPublicKey recoveredPublicKey = secp256R1.recoverPublicKeyFromSignature(dataHash, signature).get(); - assertThat(recoveredPublicKey.toString()).isEqualTo(keyPair.getPublicKey().toString()); + assertThat(recoveredPublicKey).hasToString(keyPair.getPublicKey().toString()); } @Test - public void signatureGenerationVerificationAndPubKeyRecovery() { + void signatureGenerationVerificationAndPubKeyRecovery() { signTestVectors.forEach( signTestVector -> { final SECPPrivateKey privateKey = - secp256R1.createPrivateKey(new BigInteger(signTestVector.getPrivateKey(), 16)); - final BigInteger publicKeyBigInt = new BigInteger(signTestVector.getPublicKey(), 16); + secp256R1.createPrivateKey(new BigInteger(signTestVector.privateKey(), 16)); + final BigInteger publicKeyBigInt = new BigInteger(signTestVector.publicKey(), 16); final SECPPublicKey publicKey = secp256R1.createPublicKey(publicKeyBigInt); final KeyPair keyPair = secp256R1.createKeyPair(privateKey); - final Bytes32 dataHash = keccak256(Bytes.wrap(signTestVector.getData().getBytes(UTF_8))); + final Bytes32 dataHash = keccak256(Bytes.wrap(signTestVector.data().getBytes(UTF_8))); final SECPSignature signature = secp256R1.sign(dataHash, keyPair); assertThat(secp256R1.verify(dataHash, signature, publicKey)).isTrue(); @@ -160,49 +160,27 @@ public void signatureGenerationVerificationAndPubKeyRecovery() { final BigInteger recoveredPubKeyBigInt = secp256R1.recoverFromSignature( signature.getRecId(), signature.getR(), signature.getS(), dataHash); - assertThat(recoveredPubKeyBigInt).isEqualTo(recoveredPubKeyBigInt); + assertThat(recoveredPubKeyBigInt).isEqualTo(publicKeyBigInt); }); } @Test - public void invalidFileThrowsInvalidKeyPairException() throws Exception { + void invalidFileThrowsInvalidKeyPairException() throws Exception { final File tempFile = Files.createTempFile(suiteName(), ".keypair").toFile(); tempFile.deleteOnExit(); - Files.write(tempFile.toPath(), "not valid".getBytes(UTF_8)); + Files.writeString(tempFile.toPath(), "not valid"); assertThatThrownBy(() -> KeyPairUtil.load(tempFile)) .isInstanceOf(IllegalArgumentException.class); } @Test - public void invalidMultiLineFileThrowsInvalidIdException() throws Exception { + void invalidMultiLineFileThrowsInvalidIdException() throws Exception { final File tempFile = Files.createTempFile(suiteName(), ".keypair").toFile(); tempFile.deleteOnExit(); - Files.write(tempFile.toPath(), "not\n\nvalid".getBytes(UTF_8)); + Files.writeString(tempFile.toPath(), "not\n\nvalid"); assertThatThrownBy(() -> KeyPairUtil.load(tempFile)) .isInstanceOf(IllegalArgumentException.class); } - private static class SignTestVector { - private final String privateKey; - private final String publicKey; - private final String data; - - public SignTestVector(final String privateKey, final String publicKey, final String data) { - this.privateKey = privateKey; - this.publicKey = publicKey; - this.data = data; - } - - public String getPrivateKey() { - return privateKey; - } - - public String getPublicKey() { - return publicKey; - } - - public String getData() { - return data; - } - } + private record SignTestVector(String privateKey, String publicKey, String data) {} } diff --git a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SECPPrivateKeyTest.java b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SECPPrivateKeyTest.java index d99d4940915..24fbdb05288 100644 --- a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SECPPrivateKeyTest.java +++ b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SECPPrivateKeyTest.java @@ -29,8 +29,8 @@ import org.bouncycastle.asn1.sec.SECNamedCurves; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.params.ECDomainParameters; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; public class SECPPrivateKeyTest { public static final String ALGORITHM = SignatureAlgorithm.ALGORITHM; @@ -40,7 +40,7 @@ public class SECPPrivateKeyTest { protected static String suiteName = null; public static ECDomainParameters curve; - @BeforeClass + @BeforeAll public static void setUp() { suiteStartTime = LocalDateTime.now(ZoneId.systemDefault()) diff --git a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SECPPublicKeyTest.java b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SECPPublicKeyTest.java index e5e9e6bb3a0..fe6e08404d5 100644 --- a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SECPPublicKeyTest.java +++ b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SECPPublicKeyTest.java @@ -24,8 +24,8 @@ import org.bouncycastle.asn1.sec.SECNamedCurves; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.params.ECDomainParameters; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class SECPPublicKeyTest { public static final String ALGORITHM = SignatureAlgorithm.ALGORITHM; @@ -33,7 +33,7 @@ public class SECPPublicKeyTest { public ECDomainParameters curve; - @Before + @BeforeEach public void setUp() { final X9ECParameters params = SECNamedCurves.getByName(CURVE_NAME); curve = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH()); diff --git a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SECPSignatureTest.java b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SECPSignatureTest.java index a737a93f0b1..4298f662067 100644 --- a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SECPSignatureTest.java +++ b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SECPSignatureTest.java @@ -22,15 +22,15 @@ import org.bouncycastle.asn1.sec.SECNamedCurves; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.params.ECDomainParameters; -import org.junit.BeforeClass; -import org.junit.Test; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; public class SECPSignatureTest { public static final String CURVE_NAME = "secp256k1"; public static BigInteger curveOrder; - @BeforeClass + @BeforeAll public static void setUp() { final X9ECParameters params = SECNamedCurves.getByName(CURVE_NAME); final ECDomainParameters curve = diff --git a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SignatureAlgorithmFactoryTest.java b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SignatureAlgorithmFactoryTest.java index 38c71c99db8..cabdaa7668a 100644 --- a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SignatureAlgorithmFactoryTest.java +++ b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SignatureAlgorithmFactoryTest.java @@ -17,12 +17,12 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class SignatureAlgorithmFactoryTest { - @Before + @BeforeEach public void setUp() { SignatureAlgorithmFactory.resetInstance(); } diff --git a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SignatureAlgorithmTypeTest.java b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SignatureAlgorithmTypeTest.java index faa0b5eeda7..13b37a05612 100644 --- a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SignatureAlgorithmTypeTest.java +++ b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/SignatureAlgorithmTypeTest.java @@ -17,7 +17,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class SignatureAlgorithmTypeTest { @Test diff --git a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/AltBn128Fq12PairerTest.java b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/AltBn128Fq12PairerTest.java index 18da8a44fcb..d88c54269fe 100644 --- a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/AltBn128Fq12PairerTest.java +++ b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/AltBn128Fq12PairerTest.java @@ -18,7 +18,7 @@ import java.math.BigInteger; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * Adapted from the pc_ecc (Apache 2 License) implementation: diff --git a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/AltBn128Fq12PointTest.java b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/AltBn128Fq12PointTest.java index 75f153e814a..0a57b76ff0f 100644 --- a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/AltBn128Fq12PointTest.java +++ b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/AltBn128Fq12PointTest.java @@ -18,7 +18,7 @@ import java.math.BigInteger; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * Adapted from the pc_ecc (Apache 2 License) implementation: diff --git a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/AltBn128Fq2PointTest.java b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/AltBn128Fq2PointTest.java index 56c925a5647..5d0871505f4 100644 --- a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/AltBn128Fq2PointTest.java +++ b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/AltBn128Fq2PointTest.java @@ -18,7 +18,7 @@ import java.math.BigInteger; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * Adapted from the pc_ecc (Apache 2 License) implementation: diff --git a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/AltBn128PointTest.java b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/AltBn128PointTest.java index 5794753c4a7..9f0a783a583 100644 --- a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/AltBn128PointTest.java +++ b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/AltBn128PointTest.java @@ -18,7 +18,7 @@ import java.math.BigInteger; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * Adapted from the pc_ecc (Apache 2 License) implementation: diff --git a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/Fq12Test.java b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/Fq12Test.java index fb806866afe..081bcadf207 100644 --- a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/Fq12Test.java +++ b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/Fq12Test.java @@ -16,7 +16,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * Adapted from the pc_ecc (Apache 2 License) implementation: diff --git a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/Fq2Test.java b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/Fq2Test.java index 6f3bc862ba9..9164657387e 100644 --- a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/Fq2Test.java +++ b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/Fq2Test.java @@ -18,7 +18,7 @@ import java.math.BigInteger; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * Adapted from the pc_ecc (Apache 2 License) implementation: diff --git a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/FqTest.java b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/FqTest.java index 8eab290be31..8752cb14d1a 100644 --- a/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/FqTest.java +++ b/crypto/algorithms/src/test/java/org/hyperledger/besu/crypto/altbn128/FqTest.java @@ -18,7 +18,7 @@ import java.math.BigInteger; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * Adapted from the pc_ecc (Apache 2 License) implementation: diff --git a/crypto/services/build.gradle b/crypto/services/build.gradle index f6e082836c0..be527e8c52c 100644 --- a/crypto/services/build.gradle +++ b/crypto/services/build.gradle @@ -23,6 +23,7 @@ jar { 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash, 'Automatic-Module-Name': 'org.hyperledger.besu.internal.crypto' ) } @@ -32,11 +33,8 @@ dependencies { api project(':crypto:algorithms') api project(':plugin-api') - testImplementation 'junit:junit' testImplementation 'org.assertj:assertj-core' testImplementation 'org.junit.jupiter:junit-jupiter' - - testRuntimeOnly 'org.junit.vintage:junit-vintage-engine' } artifacts { testSupportArtifacts testSupportJar } diff --git a/crypto/services/src/test/java/org/hyperledger/besu/cryptoservices/KeyPairSecurityModuleTest.java b/crypto/services/src/test/java/org/hyperledger/besu/cryptoservices/KeyPairSecurityModuleTest.java index 68d369ae538..fac403e041d 100644 --- a/crypto/services/src/test/java/org/hyperledger/besu/cryptoservices/KeyPairSecurityModuleTest.java +++ b/crypto/services/src/test/java/org/hyperledger/besu/cryptoservices/KeyPairSecurityModuleTest.java @@ -20,26 +20,22 @@ import org.hyperledger.besu.crypto.SECPPublicKey; import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; -import java.io.File; import java.io.IOException; +import java.nio.file.Path; import java.security.spec.ECPoint; import org.apache.tuweni.bytes.Bytes; import org.assertj.core.api.Assertions; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; public class KeyPairSecurityModuleTest { - @Rule public final TemporaryFolder temp = new TemporaryFolder(); + @TempDir public Path keyFile; @Test public void validatePublicKeyFromECPointCanBeConstructed() throws IOException { - final File keyDirectory = temp.newFolder(); - final File keyFile = new File(keyDirectory, "key"); - - final KeyPair keyPair = KeyPairUtil.loadKeyPair(keyFile); + final KeyPair keyPair = KeyPairUtil.loadKeyPair(keyFile.resolve("key")); final KeyPairSecurityModule keyPairSecurityModule = new KeyPairSecurityModule(keyPair); final ECPoint ecPoint = keyPairSecurityModule.getPublicKey().getW(); diff --git a/crypto/services/src/test/java/org/hyperledger/besu/cryptoservices/NodeKeyTest.java b/crypto/services/src/test/java/org/hyperledger/besu/cryptoservices/NodeKeyTest.java index 07008f06cbc..261271aee9b 100644 --- a/crypto/services/src/test/java/org/hyperledger/besu/cryptoservices/NodeKeyTest.java +++ b/crypto/services/src/test/java/org/hyperledger/besu/cryptoservices/NodeKeyTest.java @@ -21,12 +21,12 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.assertj.core.api.Assertions; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class NodeKeyTest { - @Before + @BeforeEach public void resetInstance() { SignatureAlgorithmFactory.resetInstance(); } diff --git a/datatypes/build.gradle b/datatypes/build.gradle index a55bf4e45c5..b1581b6c4b8 100644 --- a/datatypes/build.gradle +++ b/datatypes/build.gradle @@ -23,6 +23,7 @@ jar { 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash, 'Automatic-Module-Name': 'org.hyperledger.besu.datatypes' ) } @@ -30,6 +31,7 @@ jar { dependencies { compileOnly 'com.fasterxml.jackson.core:jackson-databind' + compileOnly 'io.vertx:vertx-core' implementation project(':crypto:algorithms') implementation project(':ethereum:rlp') @@ -39,8 +41,6 @@ dependencies { testImplementation 'org.assertj:assertj-core' testImplementation 'org.junit.jupiter:junit-jupiter' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' - testRuntimeOnly 'org.junit.vintage:junit-vintage-engine' } configurations { testArtifacts } diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/AccessListEntry.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/AccessListEntry.java index b11c213a9d5..7d242128bfd 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/AccessListEntry.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/AccessListEntry.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -22,7 +22,12 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; -/** An access list entry as defined in EIP-2930 */ +/** + * An access list entry as defined in EIP-2930 + * + * @param address The Address + * @param storageKeys List of storage keys + */ public record AccessListEntry(Address address, List storageKeys) { /** * Create access list entry. diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/AccountValue.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/AccountValue.java index b41d5b51e13..4cab94e25cb 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/AccountValue.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/AccountValue.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Address.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Address.java index 5d1fad698f7..bea562ee1f4 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Address.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Address.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -43,40 +43,58 @@ public class Address extends DelegatingBytes { /** The constant SHA256. */ public static final Address SHA256 = Address.precompiled(0x02); + /** The constant RIPEMD160. */ public static final Address RIPEMD160 = Address.precompiled(0x03); + /** The constant ID. */ public static final Address ID = Address.precompiled(0x04); + /** The constant MODEXP. */ public static final Address MODEXP = Address.precompiled(0x05); + /** The constant ALTBN128_ADD. */ public static final Address ALTBN128_ADD = Address.precompiled(0x06); + /** The constant ALTBN128_MUL. */ public static final Address ALTBN128_MUL = Address.precompiled(0x07); + /** The constant ALTBN128_PAIRING. */ public static final Address ALTBN128_PAIRING = Address.precompiled(0x08); + /** The constant BLAKE2B_F_COMPRESSION. */ public static final Address BLAKE2B_F_COMPRESSION = Address.precompiled(0x09); + /** The constant KZG_POINT_EVAL aka POINT_EVALUATION_PRECOMPILE_ADDRESS. */ public static final Address KZG_POINT_EVAL = Address.precompiled(0xA); + /** The constant BLS12_G1ADD. */ public static final Address BLS12_G1ADD = Address.precompiled(0xB); + /** The constant BLS12_G1MUL. */ public static final Address BLS12_G1MUL = Address.precompiled(0xC); + /** The constant BLS12_G1MULTIEXP. */ public static final Address BLS12_G1MULTIEXP = Address.precompiled(0xD); + /** The constant BLS12_G2ADD. */ public static final Address BLS12_G2ADD = Address.precompiled(0xE); + /** The constant BLS12_G2MUL. */ public static final Address BLS12_G2MUL = Address.precompiled(0xF); + /** The constant BLS12_G2MULTIEXP. */ public static final Address BLS12_G2MULTIEXP = Address.precompiled(0x10); + /** The constant BLS12_PAIRING. */ public static final Address BLS12_PAIRING = Address.precompiled(0x11); + /** The constant BLS12_MAP_FP_TO_G1. */ public static final Address BLS12_MAP_FP_TO_G1 = Address.precompiled(0x12); + /** The constant BLS12_MAP_FP2_TO_G2. */ public static final Address BLS12_MAP_FP2_TO_G2 = Address.precompiled(0x13); + /** The constant ZERO. */ public static final Address ZERO = Address.fromHexString("0x0"); @@ -188,7 +206,7 @@ public static Address fromHexStringStrict(final String str) { final Bytes value = Bytes.fromHexString(str); checkArgument( value.size() == SIZE, - "An account address must be be %s bytes long, got %s", + "An account address must be %s bytes long, got %s", SIZE, value.size()); return new Address(value); diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/BLSPublicKey.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/BLSPublicKey.java index 32e29cfdf71..809834ffcd8 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/BLSPublicKey.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/BLSPublicKey.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/BLSSignature.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/BLSSignature.java index 050205c1738..fa8b5827ba5 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/BLSSignature.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/BLSSignature.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Blob.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Blob.java index b753f2dc160..098bf4e5b58 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Blob.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Blob.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/BlobGas.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/BlobGas.java index 134e9cb4145..9ac72b6d8ac 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/BlobGas.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/BlobGas.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -16,6 +16,7 @@ import java.math.BigInteger; +import com.fasterxml.jackson.annotation.JsonValue; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.BaseUInt64Value; import org.apache.tuweni.units.bigints.UInt64; @@ -123,6 +124,7 @@ public BigInteger getAsBigInteger() { return toBigInteger(); } + @JsonValue @Override public String toHexString() { return super.toHexString(); @@ -140,6 +142,6 @@ public String toShortHexString() { * @return the blob gas */ public static BlobGas fromQuantity(final Quantity quantity) { - return BlobGas.wrap((Bytes) quantity); + return BlobGas.of(quantity.getAsBigInteger()); } } diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/BlobsWithCommitments.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/BlobsWithCommitments.java index 281d3ada681..6127fe9c77c 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/BlobsWithCommitments.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/BlobsWithCommitments.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -22,7 +22,14 @@ /** A class to hold the blobs, commitments, proofs and versioned hashes for a set of blobs. */ public class BlobsWithCommitments { - /** A record to hold the blob, commitment, proof and versioned hash for a blob. */ + /** + * A record to hold the blob, commitment, proof and versioned hash for a blob. + * + * @param blob The blob + * @param kzgCommitment The commitment + * @param kzgProof The proof + * @param versionedHash The versioned hash + */ public record BlobQuad( Blob blob, KZGCommitment kzgCommitment, KZGProof kzgProof, VersionedHash versionedHash) {} diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/CodeDelegation.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/CodeDelegation.java new file mode 100644 index 00000000000..7b9e3d7d447 --- /dev/null +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/CodeDelegation.java @@ -0,0 +1,85 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.datatypes; + +import org.hyperledger.besu.crypto.SECPSignature; + +import java.math.BigInteger; +import java.util.Optional; + +/** + * CodeDelegation is a data structure that represents the authorization to delegate code of an EOA + * account to another account. + */ +public interface CodeDelegation { + /** The cost of delegating code on an existing account. */ + long PER_AUTH_BASE_COST = 2_500L; + + /** + * Return the chain id. + * + * @return chain id + */ + BigInteger chainId(); + + /** + * Return the address of the account which code will be used. + * + * @return address + */ + Address address(); + + /** + * Return the signature. + * + * @return signature + */ + SECPSignature signature(); + + /** + * Return the authorizer address. + * + * @return authorizer address of the EOA which will load the code into its account + */ + Optional

authorizer(); + + /** + * Return the nonce + * + * @return the nonce + */ + long nonce(); + + /** + * Return the recovery id. + * + * @return byte + */ + byte v(); + + /** + * Return the r value of the signature. + * + * @return r value + */ + BigInteger r(); + + /** + * Return the s value of the signature. + * + * @return s value + */ + BigInteger s(); +} diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/GWei.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/GWei.java index 75bba998b04..05dcf6bd480 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/GWei.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/GWei.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/HardforkId.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/HardforkId.java new file mode 100644 index 00000000000..85ec84b4ade --- /dev/null +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/HardforkId.java @@ -0,0 +1,180 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.datatypes; + +import java.util.Comparator; +import java.util.stream.Stream; + +/** Description and metadata for a hard fork */ +public interface HardforkId { + + /** + * The name of the hard fork. + * + * @return the name for the fork + */ + String name(); + + /** + * Has the fork been finalized? i.e., could the definition change in future versions of Besu? + * + * @return true if the specification is finalized. + */ + boolean finalized(); + + /** + * A brief description of the hard fork, suitable for human consumption + * + * @return the description of the fork. + */ + String description(); + + /** List of all Ethereum Mainnet hardforks, including future and developmental forks. */ + enum MainnetHardforkId implements HardforkId { + /** Frontier fork. */ + FRONTIER(true, "Frontier"), + /** Homestead fork. */ + HOMESTEAD(true, "Homestead"), + /** DAO Fork fork. */ + DAO_FORK(true, "DAO Fork"), + /** Tangerine Whistle fork. */ + TANGERINE_WHISTLE(true, "Tangerine Whistle"), + /** Spurious Dragon fork. */ + SPURIOUS_DRAGON(true, "Spurious Dragon"), + /** Byzantium fork. */ + BYZANTIUM(true, "Byzantium"), + /** Constantinople fork. */ + CONSTANTINOPLE(true, "Constantinople"), + /** Petersburg fork. */ + PETERSBURG(true, "Petersburg"), + /** Istanbul fork. */ + ISTANBUL(true, "Istanbul"), + /** Muir Glacier fork. */ + MUIR_GLACIER(true, "Muir Glacier"), + /** Berlin fork. */ + BERLIN(true, "Berlin"), + /** London fork. */ + LONDON(true, "London"), + /** Arrow Glacier fork. */ + ARROW_GLACIER(true, "Arrow Glacier"), + /** Gray Glacier fork. */ + GRAY_GLACIER(true, "Gray Glacier"), + /** Paris fork. */ + PARIS(true, "Paris"), + /** Shanghai fork. */ + SHANGHAI(true, "Shanghai"), + /** Cancun fork. */ + CANCUN(true, "Cancun"), + /** Cancun + EOF fork. */ + CANCUN_EOF(false, "Cancun + EOF"), + /** Prague fork. */ + PRAGUE(false, "Prague"), + /** Prague + EOF fork. */ + PRAGUE_EOF(false, "Prague + EOF"), + /** Osaka fork. */ + OSAKA(false, "Osaka"), + /** Amsterdam fork. */ + AMSTERDAM(false, "Amsterdam"), + /** Bogota fork. */ + BOGOTA(false, "Bogota"), + /** Polis fork. (from the greek form of an earlier incarnation of the city of Istanbul. */ + POLIS(false, "Polis"), + /** Bangkok fork. */ + BANGKOK(false, "Bangkok"), + /** Development fork, for accepted and unscheduled EIPs. */ + FUTURE_EIPS(false, "Development, for accepted and unscheduled EIPs"), + /** Developmental fork, for experimental EIPs. */ + EXPERIMENTAL_EIPS(false, "Developmental, for experimental EIPs"); + + final boolean finalized; + final String description; + + MainnetHardforkId(final boolean finalized, final String description) { + this.finalized = finalized; + this.description = description; + } + + @Override + public boolean finalized() { + return finalized; + } + + @Override + public String description() { + return description; + } + + /** + * The most recent finalized mainnet hardfork Besu supports. This will change across versions + * and will be updated after mainnet activations. + * + * @return the most recently activated mainnet spec. + */ + public static MainnetHardforkId mostRecent() { + return Stream.of(MainnetHardforkId.values()) + .filter(MainnetHardforkId::finalized) + .max(Comparator.naturalOrder()) + .orElseThrow(); + } + } + + /** List of all Ethereum Classic hard forks. */ + enum ClassicHardforkId implements HardforkId { + /** Frontier fork. */ + FRONTIER(true, "Frontier"), + /** Homestead fork. */ + HOMESTEAD(true, "Homestead"), + /** Classic Tangerine Whistle fork. */ + CLASSIC_TANGERINE_WHISTLE(true, "Classic Tangerine Whistle"), + /** Die Hard fork. */ + DIE_HARD(true, "Die Hard"), + /** Gotham fork. */ + GOTHAM(true, "Gotham"), + /** Defuse Difficulty Bomb fork. */ + DEFUSE_DIFFICULTY_BOMB(true, "Defuse Difficulty Bomb"), + /** Atlantis fork. */ + ATLANTIS(true, "Atlantis"), + /** Agharta fork. */ + AGHARTA(true, "Agharta"), + /** Phoenix fork. */ + PHOENIX(true, "Phoenix"), + /** Thanos fork. */ + THANOS(true, "Thanos"), + /** Magneto fork. */ + MAGNETO(true, "Magneto"), + /** Mystique fork. */ + MYSTIQUE(true, "Mystique"), + /** Spiral fork. */ + SPIRAL(true, "Spiral"); + + final boolean finalized; + final String description; + + ClassicHardforkId(final boolean finalized, final String description) { + this.finalized = finalized; + this.description = description; + } + + @Override + public boolean finalized() { + return finalized; + } + + @Override + public String description() { + return description; + } + } +} diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Hash.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Hash.java index fa0b85c451b..a53843a247f 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Hash.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Hash.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -29,6 +29,9 @@ public class Hash extends DelegatingBytes32 { /** The constant ZERO. */ public static final Hash ZERO = new Hash(Bytes32.ZERO); + /** Last hash */ + public static final Hash LAST = new Hash(Bytes32.fromHexString("F".repeat(64))); + /** * Hash of an RLP encoded trie hash with no content, or * "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/KZGCommitment.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/KZGCommitment.java index d9d2e477dae..2a1d3eabaa6 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/KZGCommitment.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/KZGCommitment.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/KZGProof.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/KZGProof.java index 7d8b3aa186d..5f880640a3f 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/KZGProof.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/KZGProof.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/PendingTransaction.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/PendingTransaction.java index e19fb13f1d1..fd62c866e1c 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/PendingTransaction.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/PendingTransaction.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/PublicKey.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/PublicKey.java index 9fce4c1c233..212c42325e5 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/PublicKey.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/PublicKey.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/RequestType.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/RequestType.java new file mode 100644 index 00000000000..5a8fe97a36e --- /dev/null +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/RequestType.java @@ -0,0 +1,59 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.datatypes; + +/** Enum representing different types of requests with associated serialized type values. */ +public enum RequestType { + /** DEPOSITS */ + DEPOSIT(0x00), + /** WITHDRAWAL */ + WITHDRAWAL(0x01), + /** CONSOLIDATION */ + CONSOLIDATION(0x02); + + private final int typeValue; + + RequestType(final int typeValue) { + this.typeValue = typeValue; + } + + /** + * Gets the serialized type value of the request type. + * + * @return the serialized type value as a byte. + */ + public byte getSerializedType() { + return (byte) this.typeValue; + } + + /** + * Returns the {@link RequestType} corresponding to the given serialized type value. + * + * @param serializedTypeValue the serialized type value. + * @return the corresponding {@link RequestType}. + * @throws IllegalArgumentException if the serialized type value does not correspond to any {@link + * RequestType}. + */ + public static RequestType of(final int serializedTypeValue) { + return switch (serializedTypeValue) { + case 0x00 -> DEPOSIT; + case 0x01 -> WITHDRAWAL; + case 0x02 -> CONSOLIDATION; + default -> + throw new IllegalArgumentException( + String.format("Unsupported request type: 0x%02X", serializedTypeValue)); + }; + } +} diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Sha256Hash.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Sha256Hash.java index 9dbfeacc974..37864ec51c7 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Sha256Hash.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Sha256Hash.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.datatypes; import org.apache.tuweni.bytes.Bytes; diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/StorageSlotKey.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/StorageSlotKey.java index eb2424dfcf4..8404453cf18 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/StorageSlotKey.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/StorageSlotKey.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -16,9 +16,9 @@ import java.util.Objects; import java.util.Optional; +import javax.annotation.Nonnull; import org.apache.tuweni.units.bigints.UInt256; -import org.jetbrains.annotations.NotNull; /** * StorageSlotKey represents a key used for storage slots in Ethereum. It contains the hash of the @@ -94,7 +94,7 @@ public boolean equals(final Object o) { @Override public int hashCode() { - return Objects.hash(slotHash.hashCode()); + return slotHash.hashCode(); } @Override @@ -105,7 +105,7 @@ public String toString() { } @Override - public int compareTo(@NotNull final StorageSlotKey other) { + public int compareTo(@Nonnull final StorageSlotKey other) { return this.slotHash.compareTo(other.slotHash); } } diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Transaction.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Transaction.java index 4a77a898de9..e614901300b 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Transaction.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Transaction.java @@ -234,4 +234,18 @@ default Optional getMaxFeePerBlobGas() { * @return the size in bytes of the encoded transaction. */ int getSize(); + + /** + * Returns the code delegations if this transaction is a 7702 transaction. + * + * @return the code delegations + */ + Optional> getCodeDelegationList(); + + /** + * Returns the size of the authorization list. + * + * @return the size of the authorization list + */ + int codeDelegationListSize(); } diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/TransactionType.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/TransactionType.java index 984a4cc7467..bf1d4e77921 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/TransactionType.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/TransactionType.java @@ -27,10 +27,12 @@ public enum TransactionType { /** Eip1559 transaction type. */ EIP1559(0x02), /** Blob transaction type. */ - BLOB(0x03); + BLOB(0x03), + /** Eip7702 transaction type. */ + DELEGATE_CODE(0x04); private static final Set ACCESS_LIST_SUPPORTED_TRANSACTION_TYPES = - Set.of(ACCESS_LIST, EIP1559, BLOB); + Set.of(ACCESS_LIST, EIP1559, BLOB, DELEGATE_CODE); private static final EnumSet LEGACY_FEE_MARKET_TRANSACTION_TYPES = EnumSet.of(TransactionType.FRONTIER, TransactionType.ACCESS_LIST); @@ -83,7 +85,8 @@ public static TransactionType of(final int serializedTypeValue) { TransactionType.FRONTIER, TransactionType.ACCESS_LIST, TransactionType.EIP1559, - TransactionType.BLOB + TransactionType.BLOB, + TransactionType.DELEGATE_CODE }) .filter(transactionType -> transactionType.typeValue == serializedTypeValue) .findFirst() @@ -128,4 +131,22 @@ public boolean requiresChainId() { public boolean supportsBlob() { return this.equals(BLOB); } + + /** + * Does transaction type support delegate code. + * + * @return the boolean + */ + public boolean supportsDelegateCode() { + return this.equals(DELEGATE_CODE); + } + + /** + * Does transaction type require code. + * + * @return the boolean + */ + public boolean requiresCodeDelegation() { + return this.equals(DELEGATE_CODE); + } } diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/VersionedHash.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/VersionedHash.java index 21f296e0ae1..8495c708a05 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/VersionedHash.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/VersionedHash.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.datatypes; import java.util.Objects; diff --git a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java index 2e2a8ac4f09..7b097a9dd1a 100644 --- a/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java +++ b/datatypes/src/main/java/org/hyperledger/besu/datatypes/Wei.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -16,6 +16,7 @@ import java.math.BigInteger; import java.util.Arrays; +import java.util.Locale; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.BaseUInt256Value; @@ -194,8 +195,10 @@ enum Unit { /** The Pow. */ final int pow; + /** The Divisor. */ final double divisor; + /** The Decimals. */ final int decimals; @@ -224,7 +227,7 @@ static Unit getPreferred(final int numOfDigits) { @Override public String toString() { - return name().toLowerCase(); + return name().toLowerCase(Locale.ROOT); } } } diff --git a/datatypes/src/test/java/org/hyperledger/besu/datatypes/BlobsWithCommitmentsTest.java b/datatypes/src/test/java/org/hyperledger/besu/datatypes/BlobsWithCommitmentsTest.java index a3eb7bbe0f6..8ff81422dac 100644 --- a/datatypes/src/test/java/org/hyperledger/besu/datatypes/BlobsWithCommitmentsTest.java +++ b/datatypes/src/test/java/org/hyperledger/besu/datatypes/BlobsWithCommitmentsTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.datatypes; import static org.assertj.core.api.Assertions.assertThat; diff --git a/datatypes/src/test/java/org/hyperledger/besu/datatypes/GWeiTest.java b/datatypes/src/test/java/org/hyperledger/besu/datatypes/GWeiTest.java index a605da4d3b1..d11ec0198d0 100644 --- a/datatypes/src/test/java/org/hyperledger/besu/datatypes/GWeiTest.java +++ b/datatypes/src/test/java/org/hyperledger/besu/datatypes/GWeiTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/datatypes/src/test/java/org/hyperledger/besu/datatypes/VersionedHashTest.java b/datatypes/src/test/java/org/hyperledger/besu/datatypes/VersionedHashTest.java index ce3b264b3b8..ebb8a6cd652 100644 --- a/datatypes/src/test/java/org/hyperledger/besu/datatypes/VersionedHashTest.java +++ b/datatypes/src/test/java/org/hyperledger/besu/datatypes/VersionedHashTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.datatypes; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/datatypes/src/test/java/org/hyperledger/besu/datatypes/WeiTest.java b/datatypes/src/test/java/org/hyperledger/besu/datatypes/WeiTest.java index 1d1b38819a7..7162f98b026 100644 --- a/datatypes/src/test/java/org/hyperledger/besu/datatypes/WeiTest.java +++ b/datatypes/src/test/java/org/hyperledger/besu/datatypes/WeiTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000000..c16345a82bd --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,60 @@ +FROM ubuntu:24.04 +ARG VERSION="dev" +ENV NO_PROXY_CACHE="-o Acquire::BrokenProxy=true -o Acquire::http::No-Cache=true -o Acquire::http::Pipeline-Depth=0" + +# Update and install dependencies without using any cache +RUN apt-get update $NO_PROXY_CACHE && \ + # $NO_PROXY_CACHE must not be used here or otherwise will trigger a hadolint error + apt-get -o Acquire::BrokenProxy=true -o Acquire::http::No-Cache=true -o Acquire::http::Pipeline-Depth=0 \ + --no-install-recommends -q --assume-yes install openjdk-21-jre-headless=21* libjemalloc-dev=5.* adduser=3* && \ + # Clean apt cache + apt-get clean && \ + rm -rf /var/cache/apt/archives/* /var/cache/apt/archives/partial/* && \ + rm -rf /var/lib/apt/lists/* && \ + # Starting from version 23.10, Ubuntu comes with an "ubuntu" user with uid 1000. We need 1000 for besu. + userdel ubuntu 2>/dev/null || true && rm -rf /home/ubuntu && \ + # Ensure we use a stable UID for besu, as file permissions are tied to UIDs. + adduser --uid 1000 --disabled-password --gecos "" --home /opt/besu besu && \ + chown besu:besu /opt/besu && \ + chmod 0755 /opt/besu + +USER besu +WORKDIR /opt/besu + +COPY --chown=besu:besu besu /opt/besu/ + +# Expose services ports +# 8545 HTTP JSON-RPC +# 8546 WS JSON-RPC +# 8547 HTTP GraphQL +# 8550 HTTP ENGINE JSON-RPC +# 8551 WS ENGINE JSON-RPC +# 30303 P2P +EXPOSE 8545 8546 8547 8550 8551 30303 + +# defaults for host interfaces +ENV BESU_RPC_HTTP_HOST 0.0.0.0 +ENV BESU_RPC_WS_HOST 0.0.0.0 +ENV BESU_GRAPHQL_HTTP_HOST 0.0.0.0 +ENV BESU_PID_PATH "/tmp/pid" + +ENV OTEL_RESOURCE_ATTRIBUTES="service.name=besu,service.version=$VERSION" + +ENV OLDPATH="${PATH}" +ENV PATH="/opt/besu/bin:${OLDPATH}" + +ENTRYPOINT ["besu"] +HEALTHCHECK --start-period=5s --interval=5s --timeout=1s --retries=10 CMD bash -c "[ -f /tmp/pid ]" + +# Build-time metadata as defined at http://label-schema.org +ARG BUILD_DATE +ARG VCS_REF +LABEL org.label-schema.build-date=$BUILD_DATE \ + org.label-schema.name="Besu" \ + org.label-schema.description="Enterprise Ethereum client" \ + org.label-schema.url="https://besu.hyperledger.org/" \ + org.label-schema.vcs-ref=$VCS_REF \ + org.label-schema.vcs-url="https://github.com/hyperledger/besu.git" \ + org.label-schema.vendor="Hyperledger" \ + org.label-schema.version=$VERSION \ + org.label-schema.schema-version="1.0" diff --git a/docker/graalvm/Dockerfile b/docker/graalvm/Dockerfile deleted file mode 100644 index d4009b9c929..00000000000 --- a/docker/graalvm/Dockerfile +++ /dev/null @@ -1,49 +0,0 @@ - -FROM ghcr.io/graalvm/graalvm-ce:ol9-java17 -ARG VERSION="dev" - -RUN adduser --home /opt/besu besu && \ - chown besu:besu /opt/besu && \ - chmod 0755 /opt/besu - -USER besu -WORKDIR /opt/besu - -COPY --chown=besu:besu besu /opt/besu/ -RUN chmod -R 755 /opt/besu - -# Expose services ports -# 8545 HTTP JSON-RPC -# 8546 WS JSON-RPC -# 8547 HTTP GraphQL -# 8550 HTTP ENGINE JSON-RPC -# 8551 WS ENGINE JSON-RPC -# 30303 P2P -EXPOSE 8545 8546 8547 8550 8551 30303 - -# defaults for host interfaces -ENV BESU_RPC_HTTP_HOST 0.0.0.0 -ENV BESU_RPC_WS_HOST 0.0.0.0 -ENV BESU_GRAPHQL_HTTP_HOST 0.0.0.0 -ENV BESU_PID_PATH "/tmp/pid" - -ENV OTEL_RESOURCE_ATTRIBUTES="service.name=besu,service.version=$VERSION" - -ENV OLDPATH="${PATH}" -ENV PATH="/opt/besu/bin:${OLDPATH}" - -ENTRYPOINT ["besu"] -HEALTHCHECK --start-period=5s --interval=5s --timeout=1s --retries=10 CMD bash -c "[ -f /tmp/pid ]" - -# Build-time metadata as defined at http://label-schema.org -ARG BUILD_DATE -ARG VCS_REF -LABEL org.label-schema.build-date=$BUILD_DATE \ - org.label-schema.name="Besu" \ - org.label-schema.description="Enterprise Ethereum client" \ - org.label-schema.url="https://besu.hyperledger.org/" \ - org.label-schema.vcs-ref=$VCS_REF \ - org.label-schema.vcs-url="https://github.com/hyperledger/besu.git" \ - org.label-schema.vendor="Hyperledger" \ - org.label-schema.version=$VERSION \ - org.label-schema.schema-version="1.0" diff --git a/docker/openj9-jdk-17/Dockerfile b/docker/openj9-jdk-17/Dockerfile deleted file mode 100644 index 618b8b4e80d..00000000000 --- a/docker/openj9-jdk-17/Dockerfile +++ /dev/null @@ -1,72 +0,0 @@ -FROM ubuntu:22.04 - -ARG VERSION="dev" -ARG TARGETPLATFORM - -RUN apt-get update && \ - apt-get install --no-install-recommends -q --assume-yes curl=7* libjemalloc-dev=5.* && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* - -RUN ARCH=$(uname -m) && \ - if [ "$ARCH" = "aarch64" ]; then \ - curl -kL -o jdk-17.tar.gz https://github.com/ibmruntimes/semeru17-binaries/releases/download/jdk-17.0.5%2B8_openj9-0.35.0/ibm-semeru-open-jdk_aarch64_linux_17.0.5_8_openj9-0.35.0.tar.gz ; \ - elif [ "$(uname -s)" = "Darwin" ] && [ "$(uname -m)" = "arm64" ]; then \ - curl -kL -o jdk-17.tar.gz https://github.com/ibmruntimes/semeru17-binaries/releases/download/jdk-17.0.5%2B8_openj9-0.35.0/ibm-semeru-open-jdk_aarch64_linux_17.0.5_8_openj9-0.35.0.tar.gz ; \ - elif [ "$ARCH" = "x86_64" ]; then \ - curl -kL -o jdk-17.tar.gz https://github.com/ibmruntimes/semeru17-binaries/releases/download/jdk-17.0.5%2B8_openj9-0.35.0/ibm-semeru-open-jdk_x64_linux_17.0.5_8_openj9-0.35.0.tar.gz ; \ - else \ - echo "Unsupported platform: $ARCH"; exit 1; \ - fi - -RUN tar -xzf jdk-17.tar.gz && \ - rm jdk-17.tar.gz && \ - mv jdk-17.0.5+8 /usr/bin/ && \ - update-alternatives --install "/usr/bin/java" "java" "/usr/bin/jdk-17.0.5+8/bin/java" 1 && \ - adduser --disabled-password --gecos "" --home /opt/besu besu && \ - chown besu:besu /opt/besu && \ - chmod 0755 /opt/besu - -ENV JAVA_HOME /usr/bin/jdk-17.0.5+8/ -RUN export JAVA_HOME - -USER besu -WORKDIR /opt/besu - -COPY --chown=besu:besu besu /opt/besu/ - -# Expose services ports -# 8545 HTTP JSON-RPC -# 8546 WS JSON-RPC -# 8547 HTTP GraphQL -# 8550 HTTP ENGINE JSON-RPC -# 8551 WS ENGINE JSON-RPC -# 30303 P2P -EXPOSE 8545 8546 8547 8550 8551 30303 - -# defaults for host interfaces -ENV BESU_RPC_HTTP_HOST 0.0.0.0 -ENV BESU_RPC_WS_HOST 0.0.0.0 -ENV BESU_GRAPHQL_HTTP_HOST 0.0.0.0 -ENV BESU_PID_PATH "/tmp/pid" - -ENV OTEL_RESOURCE_ATTRIBUTES="service.name=besu,service.version=$VERSION" - -ENV OLDPATH="${PATH}" -ENV PATH="/opt/besu/bin:${OLDPATH}" - -ENTRYPOINT ["besu"] -HEALTHCHECK --start-period=5s --interval=5s --timeout=1s --retries=10 CMD bash -c "[ -f /tmp/pid ]" - -# Build-time metadata as defined at http://label-schema.org -ARG BUILD_DATE -ARG VCS_REF -LABEL org.label-schema.build-date=$BUILD_DATE \ - org.label-schema.name="Besu" \ - org.label-schema.description="Enterprise Ethereum client" \ - org.label-schema.url="https://besu.hyperledger.org/" \ - org.label-schema.vcs-ref=$VCS_REF \ - org.label-schema.vcs-url="https://github.com/hyperledger/besu.git" \ - org.label-schema.vendor="Hyperledger" \ - org.label-schema.version=$VERSION \ - org.label-schema.schema-version="1.0" diff --git a/docker/openjdk-17-debug/Dockerfile b/docker/openjdk-17-debug/Dockerfile deleted file mode 100644 index a8e2276209b..00000000000 --- a/docker/openjdk-17-debug/Dockerfile +++ /dev/null @@ -1,65 +0,0 @@ - -FROM ubuntu:22.04 - -ARG VERSION="dev" -RUN apt-get update && \ - apt-get install --no-install-recommends -q --assume-yes ca-certificates-java=20190909* && \ - apt-get install --no-install-recommends -q --assume-yes curl=7* wget=1.21* jq=1.6* net-tools=1.60* openjdk-17-jdk-headless=17* libjemalloc-dev=5.* && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* && \ - adduser --disabled-password --gecos "" --home /opt/besu besu && \ - chown besu:besu /opt/besu && \ - chmod 0755 /opt/besu - -USER besu -WORKDIR /opt/besu - -COPY --chown=besu:besu besu /opt/besu/ - -# Expose services ports -# 5005 JDWP for attaching remote debuggers -# 8545 HTTP JSON-RPC -# 8546 WS JSON-RPC -# 8547 HTTP GraphQL -# 8550 HTTP ENGINE JSON-RPC -# 8551 WS ENGINE JSON-RPC -# 9545 Metrics -# 1098 JMX / JMX-RMI -# 30303 P2P -EXPOSE 5005 8545 8546 8547 8550 8551 9545 1098 30303 - -# defaults for host interfaces -ENV BESU_RPC_HTTP_HOST 0.0.0.0 -ENV BESU_RPC_WS_HOST 0.0.0.0 -ENV BESU_GRAPHQL_HTTP_HOST 0.0.0.0 -ENV BESU_METRICS_HOST 0.0.0.0 -ENV BESU_JMX_HOST 0.0.0.0 -ENV BESU_PID_PATH "/tmp/pid" -ENV BESU_HOST_ALLOWLIST "*" - -#debug options for maximum observability -ENV BESU_LOGGING "INFO" -ENV BESU_RPC_HTTP_API "ETH,NET,TRACE,DEBUG,ADMIN,TXPOOL" -ENV JDWP_OPTS "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005" -ENV JAVA_OPTS "${JDWP_OPTS} " - -ENV OTEL_RESOURCE_ATTRIBUTES="service.name=besu,service.version=$VERSION" - -ENV OLDPATH="${PATH}" -ENV PATH="/opt/besu/bin:${OLDPATH}" - -ENTRYPOINT ["besu"] -HEALTHCHECK --start-period=5s --interval=5s --timeout=1s --retries=10 CMD bash -c "[ -f /tmp/pid ]" - -# Build-time metadata as defined at http://label-schema.org -ARG BUILD_DATE -ARG VCS_REF -LABEL org.label-schema.build-date=$BUILD_DATE \ - org.label-schema.name="Besu" \ - org.label-schema.description="Enterprise Ethereum client" \ - org.label-schema.url="https://besu.hyperledger.org/" \ - org.label-schema.vcs-ref=$VCS_REF \ - org.label-schema.vcs-url="https://github.com/hyperledger/besu.git" \ - org.label-schema.vendor="Hyperledger" \ - org.label-schema.version=$VERSION \ - org.label-schema.schema-version="1.0" diff --git a/docker/openjdk-17/Dockerfile b/docker/openjdk-17/Dockerfile deleted file mode 100644 index bb9f09c410a..00000000000 --- a/docker/openjdk-17/Dockerfile +++ /dev/null @@ -1,53 +0,0 @@ - -FROM ubuntu:22.04 -ARG VERSION="dev" - -RUN apt-get update && \ - apt-get install --no-install-recommends -q --assume-yes ca-certificates-java=20190909* && \ - apt-get install --no-install-recommends -q --assume-yes openjdk-17-jre-headless=17* libjemalloc-dev=5.* && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* && \ - adduser --disabled-password --gecos "" --home /opt/besu besu && \ - chown besu:besu /opt/besu && \ - chmod 0755 /opt/besu - -USER besu -WORKDIR /opt/besu - -COPY --chown=besu:besu besu /opt/besu/ - -# Expose services ports -# 8545 HTTP JSON-RPC -# 8546 WS JSON-RPC -# 8547 HTTP GraphQL -# 8550 HTTP ENGINE JSON-RPC -# 8551 WS ENGINE JSON-RPC -# 30303 P2P -EXPOSE 8545 8546 8547 8550 8551 30303 - -# defaults for host interfaces -ENV BESU_RPC_HTTP_HOST 0.0.0.0 -ENV BESU_RPC_WS_HOST 0.0.0.0 -ENV BESU_GRAPHQL_HTTP_HOST 0.0.0.0 -ENV BESU_PID_PATH "/tmp/pid" - -ENV OTEL_RESOURCE_ATTRIBUTES="service.name=besu,service.version=$VERSION" - -ENV OLDPATH="${PATH}" -ENV PATH="/opt/besu/bin:${OLDPATH}" - -ENTRYPOINT ["besu"] -HEALTHCHECK --start-period=5s --interval=5s --timeout=1s --retries=10 CMD bash -c "[ -f /tmp/pid ]" - -# Build-time metadata as defined at http://label-schema.org -ARG BUILD_DATE -ARG VCS_REF -LABEL org.label-schema.build-date=$BUILD_DATE \ - org.label-schema.name="Besu" \ - org.label-schema.description="Enterprise Ethereum client" \ - org.label-schema.url="https://besu.hyperledger.org/" \ - org.label-schema.vcs-ref=$VCS_REF \ - org.label-schema.vcs-url="https://github.com/hyperledger/besu.git" \ - org.label-schema.vendor="Hyperledger" \ - org.label-schema.version=$VERSION \ - org.label-schema.schema-version="1.0" diff --git a/docker/openjdk-latest/Dockerfile b/docker/openjdk-latest/Dockerfile deleted file mode 100644 index 8202718781e..00000000000 --- a/docker/openjdk-latest/Dockerfile +++ /dev/null @@ -1,52 +0,0 @@ - -FROM ubuntu:23.10 -ARG VERSION="dev" - -RUN apt-get update && \ - apt-get install --no-install-recommends -q --assume-yes openjdk-21-jre-headless=21* libjemalloc-dev=5.* adduser=3* && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* && \ - adduser --disabled-password --gecos "" --home /opt/besu besu && \ - chown besu:besu /opt/besu && \ - chmod 0755 /opt/besu - -USER besu -WORKDIR /opt/besu - -COPY --chown=besu:besu besu /opt/besu/ - -# Expose services ports -# 8545 HTTP JSON-RPC -# 8546 WS JSON-RPC -# 8547 HTTP GraphQL -# 8550 HTTP ENGINE JSON-RPC -# 8551 WS ENGINE JSON-RPC -# 30303 P2P -EXPOSE 8545 8546 8547 8550 8551 30303 - -# defaults for host interfaces -ENV BESU_RPC_HTTP_HOST 0.0.0.0 -ENV BESU_RPC_WS_HOST 0.0.0.0 -ENV BESU_GRAPHQL_HTTP_HOST 0.0.0.0 -ENV BESU_PID_PATH "/tmp/pid" - -ENV OTEL_RESOURCE_ATTRIBUTES="service.name=besu,service.version=$VERSION" - -ENV OLDPATH="${PATH}" -ENV PATH="/opt/besu/bin:${OLDPATH}" - -ENTRYPOINT ["besu"] -HEALTHCHECK --start-period=5s --interval=5s --timeout=1s --retries=10 CMD bash -c "[ -f /tmp/pid ]" - -# Build-time metadata as defined at http://label-schema.org -ARG BUILD_DATE -ARG VCS_REF -LABEL org.label-schema.build-date=$BUILD_DATE \ - org.label-schema.name="Besu" \ - org.label-schema.description="Enterprise Ethereum client" \ - org.label-schema.url="https://besu.hyperledger.org/" \ - org.label-schema.vcs-ref=$VCS_REF \ - org.label-schema.vcs-url="https://github.com/hyperledger/besu.git" \ - org.label-schema.vendor="Hyperledger" \ - org.label-schema.version=$VERSION \ - org.label-schema.schema-version="1.0" diff --git a/docker/test.sh b/docker/test.sh index 34ce7d3a64e..6e08db13b55 100755 --- a/docker/test.sh +++ b/docker/test.sh @@ -1,6 +1,20 @@ #!/bin/bash +## +## Copyright contributors to Hyperledger Besu. +## +## Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +## an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +## specific language governing permissions and limitations under the License. +## +## SPDX-License-Identifier: Apache-2.0 +## -export TEST_PATH=../tests +export TEST_PATH=./tests export GOSS_PATH=$TEST_PATH/goss-linux-${architecture} export GOSS_OPTS="$GOSS_OPTS --format junit" export GOSS_FILES_STRATEGY=cp @@ -12,7 +26,7 @@ i=0 ## Checks on the Dockerfile GOSS_FILES_PATH=$TEST_PATH/00 \ bash $TEST_PATH/dgoss dockerfile $DOCKER_IMAGE $DOCKER_FILE \ -> ../reports/00.xml || i=`expr $i + 1` +> ./reports/00.xml || i=`expr $i + 1` # fail fast if we dont pass static checks if [[ $i != 0 ]]; then exit $i; fi @@ -20,11 +34,11 @@ if [[ $i != 0 ]]; then exit $i; fi # we test that things listen on the right interface/port, not what interface the advertise # hence we dont set p2p-host=0.0.0.0 because this sets what its advertising to devp2p; the important piece is that it defaults to listening on all interfaces GOSS_FILES_PATH=$TEST_PATH/01 \ -bash $TEST_PATH/dgoss run $DOCKER_IMAGE \ +bash $TEST_PATH/dgoss run --sysctl net.ipv6.conf.all.disable_ipv6=1 $DOCKER_IMAGE \ --network=dev \ --rpc-http-enabled \ --rpc-ws-enabled \ --graphql-http-enabled \ -> ../reports/01.xml || i=`expr $i + 1` +> ./reports/01.xml || i=`expr $i + 1` exit $i diff --git a/docs/trace_rpc_apis.md b/docs/trace_rpc_apis.md index 116c7ea226b..e8db0bcbc5c 100644 --- a/docs/trace_rpc_apis.md +++ b/docs/trace_rpc_apis.md @@ -34,6 +34,6 @@ Besu reports only the actual cost of the precompiled contract call in the ### Out of Gas -Besu reports the operation that causes out fo gas exceptions, including +Besu reports the operation that causes out of gas exceptions, including calculated gas cost. The operation is not executed so no `ex` values are reported. diff --git a/docs/tracing/README.md b/docs/tracing/README.md index 0acac729d4f..77deadd16aa 100644 --- a/docs/tracing/README.md +++ b/docs/tracing/README.md @@ -21,4 +21,4 @@ Open the Zipkin UI by browsing to http://localhost:9411/ You will be able to see the detail of your traces. References: -* [OpenTelemetry Environment Variable Specification](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/sdk-environment-variables.md) +* [OpenTelemetry Environment Variable Specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/configuration/sdk-environment-variables.md) diff --git a/enclave/src/integration-test/java/org/hyperledger/besu/enclave/TlsCertificateDefinition.java b/enclave/src/integration-test/java/org/hyperledger/besu/enclave/TlsCertificateDefinition.java index 4f09831b003..ad1271e9200 100644 --- a/enclave/src/integration-test/java/org/hyperledger/besu/enclave/TlsCertificateDefinition.java +++ b/enclave/src/integration-test/java/org/hyperledger/besu/enclave/TlsCertificateDefinition.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.enclave; import java.io.File; diff --git a/enclave/src/integration-test/java/org/hyperledger/besu/enclave/TlsEnabledEnclaveTest.java b/enclave/src/integration-test/java/org/hyperledger/besu/enclave/TlsEnabledEnclaveTest.java index fc99551577d..b5947793060 100644 --- a/enclave/src/integration-test/java/org/hyperledger/besu/enclave/TlsEnabledEnclaveTest.java +++ b/enclave/src/integration-test/java/org/hyperledger/besu/enclave/TlsEnabledEnclaveTest.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.enclave; import static org.assertj.core.api.Assertions.assertThat; diff --git a/enclave/src/integration-test/java/org/hyperledger/besu/enclave/TlsEnabledHttpServerFactory.java b/enclave/src/integration-test/java/org/hyperledger/besu/enclave/TlsEnabledHttpServerFactory.java index 6538c4a4a91..7c67aeb7b9e 100644 --- a/enclave/src/integration-test/java/org/hyperledger/besu/enclave/TlsEnabledHttpServerFactory.java +++ b/enclave/src/integration-test/java/org/hyperledger/besu/enclave/TlsEnabledHttpServerFactory.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.enclave; import static org.hyperledger.besu.enclave.TlsHelpers.populateFingerprintFile; diff --git a/enclave/src/integration-test/java/org/hyperledger/besu/enclave/TlsHelpers.java b/enclave/src/integration-test/java/org/hyperledger/besu/enclave/TlsHelpers.java index 7eec56153e4..09002f81151 100644 --- a/enclave/src/integration-test/java/org/hyperledger/besu/enclave/TlsHelpers.java +++ b/enclave/src/integration-test/java/org/hyperledger/besu/enclave/TlsHelpers.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.enclave; import org.hyperledger.besu.crypto.MessageDigestFactory; @@ -32,6 +31,7 @@ import java.security.cert.X509Certificate; import java.util.Enumeration; import java.util.List; +import java.util.Locale; import java.util.Optional; import java.util.StringJoiner; @@ -93,6 +93,6 @@ private static String generateFingerprint(final X509Certificate cert) joiner.add(String.format("%02X", b)); } - return joiner.toString().toLowerCase(); + return joiner.toString().toLowerCase(Locale.ROOT); } } diff --git a/enclave/src/main/java/org/hyperledger/besu/enclave/types/PrivacyGroup.java b/enclave/src/main/java/org/hyperledger/besu/enclave/types/PrivacyGroup.java index 00cf2aab1ba..cad4c7c0163 100644 --- a/enclave/src/main/java/org/hyperledger/besu/enclave/types/PrivacyGroup.java +++ b/enclave/src/main/java/org/hyperledger/besu/enclave/types/PrivacyGroup.java @@ -22,12 +22,16 @@ public class PrivacyGroup implements Serializable { /** Private Group Id */ private String privacyGroupId; + /** Name */ private String name; + /** Description */ private String description; + /** Type */ private Type type; + /** Members */ private List members; diff --git a/errorprone-checks/README.md b/errorprone-checks/README.md deleted file mode 100644 index 34f336cbc95..00000000000 --- a/errorprone-checks/README.md +++ /dev/null @@ -1,9 +0,0 @@ -The creation of custom errorprone checkers was largely derived from: -* https://github.com/tbroyer/gradle-errorprone-plugin -* https://errorprone.info/docs/installation -* https://github.com/google/error-prone/wiki/Writing-a-check - -To allow for debugging from within intellij, the following must be added to the VM args -in the run/debug configuration (this assumes your gradle cache is at the default location under -your home): --Xbootclasspath/p:${HOME}/.gradle/caches/./modules-2/files-2.1/com.google.errorprone/javac/9+181-r4173-1/bdf4c0aa7d540ee1f7bf14d47447aea4bbf450c5/javac-9+181-r4173-1.jar diff --git a/errorprone-checks/build.gradle b/errorprone-checks/build.gradle deleted file mode 100644 index 1aff3a23b23..00000000000 --- a/errorprone-checks/build.gradle +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -// we use this config to get the path of the JDK 9 javac jar, to -// stick it in the bootclasspath when running tests -configurations.maybeCreate("epJavac") - - -apply plugin: 'java' -apply plugin: 'net.ltgt.errorprone' - -sourceCompatibility = 17 -targetCompatibility = 17 - -dependencies { - api 'org.slf4j:slf4j-api' - - annotationProcessor 'com.google.auto.service:auto-service' - - implementation 'com.google.auto.service:auto-service' - implementation 'com.google.errorprone:error_prone_annotation' - implementation 'com.google.errorprone:error_prone_core' - implementation 'info.picocli:picocli' - - testImplementation 'com.google.errorprone:error_prone_test_helpers' - testImplementation 'org.assertj:assertj-core' - testImplementation 'org.junit.jupiter:junit-jupiter' - - testRuntimeOnly 'org.junit.vintage:junit-vintage-engine' - - epJavac 'com.google.errorprone:error_prone_check_api' -} - -test { testLogging { showStandardStreams = true } } - - -tasks.withType(JavaCompile) { - options.compilerArgs += [ - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED', - '--add-exports', - 'jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED' - ] -} - -javadoc { enabled = false } diff --git a/errorprone-checks/src/main/java/org/hyperledger/errorpronechecks/BannedMethod.java b/errorprone-checks/src/main/java/org/hyperledger/errorpronechecks/BannedMethod.java deleted file mode 100644 index 8f71df921e4..00000000000 --- a/errorprone-checks/src/main/java/org/hyperledger/errorpronechecks/BannedMethod.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.errorpronechecks; - -import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; -import static com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher; -import static com.google.errorprone.matchers.Description.NO_MATCH; -import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod; - -import java.util.Map; - -import com.google.auto.service.AutoService; -import com.google.common.collect.ImmutableMap; -import com.google.errorprone.BugPattern; -import com.google.errorprone.VisitorState; -import com.google.errorprone.bugpatterns.BugChecker; -import com.google.errorprone.matchers.Description; -import com.google.errorprone.matchers.Matcher; -import com.sun.source.tree.ExpressionTree; -import com.sun.source.tree.MethodInvocationTree; - -@AutoService(BugChecker.class) -@BugPattern( - summary = "Some methods should not be used, make sure that doesn't happen.", - severity = WARNING, - linkType = BugPattern.LinkType.NONE) -public class BannedMethod extends BugChecker implements MethodInvocationTreeMatcher { - - private static final ImmutableMap, String> BANNED_METHOD_LIST = - ImmutableMap.of( - staticMethod().onClass("com.google.common.base.Objects").withAnyName(), - "Do not use com.google.common.base.Objects methods, use java.util.Objects methods instead.", - staticMethod().onClass("org.junit.Assert"), - "Do not use junit assertions. Use assertj assertions instead.", - staticMethod().onClass("org.apache.logging.log4j.LogManager"), - "Do not use org.apache.logging.log4j.LogManager, use org.slf4j.LoggerFactory methods instead."); - - @Override - public Description matchMethodInvocation( - final MethodInvocationTree tree, final VisitorState state) { - for (final Map.Entry, String> entry : BANNED_METHOD_LIST.entrySet()) { - if (entry.getKey().matches(tree, state)) { - return buildDescription(tree).setMessage(entry.getValue()).build(); - } - } - return NO_MATCH; - } -} diff --git a/errorprone-checks/src/main/java/org/hyperledger/errorpronechecks/DoNotCreateSecureRandomDirectly.java b/errorprone-checks/src/main/java/org/hyperledger/errorpronechecks/DoNotCreateSecureRandomDirectly.java deleted file mode 100644 index 95c7d18039c..00000000000 --- a/errorprone-checks/src/main/java/org/hyperledger/errorpronechecks/DoNotCreateSecureRandomDirectly.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.errorpronechecks; - -import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; - -import com.google.auto.service.AutoService; -import com.google.errorprone.BugPattern; -import com.google.errorprone.VisitorState; -import com.google.errorprone.bugpatterns.BugChecker; -import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher; -import com.google.errorprone.bugpatterns.BugChecker.NewClassTreeMatcher; -import com.google.errorprone.matchers.Description; -import com.google.errorprone.util.ASTHelpers; -import com.sun.source.tree.MethodInvocationTree; -import com.sun.source.tree.NewClassTree; -import com.sun.tools.javac.code.Symbol; - -@AutoService(BugChecker.class) -@BugPattern( - summary = "Do not create SecureRandom directly.", - severity = WARNING, - linkType = BugPattern.LinkType.NONE) -public class DoNotCreateSecureRandomDirectly extends BugChecker - implements MethodInvocationTreeMatcher, NewClassTreeMatcher { - - @SuppressWarnings("TreeToString") - @Override - public Description matchMethodInvocation( - final MethodInvocationTree tree, final VisitorState state) { - if (tree.getMethodSelect().toString().equals("SecureRandom.getInstance")) { - return describeMatch(tree); - } - - return Description.NO_MATCH; - } - - @Override - public Description matchNewClass(final NewClassTree tree, final VisitorState state) { - final Symbol sym = ASTHelpers.getSymbol(tree.getIdentifier()); - if (sym != null && sym.toString().equals("java.security.SecureRandom")) { - return describeMatch(tree); - } - - return Description.NO_MATCH; - } -} diff --git a/errorprone-checks/src/main/java/org/hyperledger/errorpronechecks/DoNotInvokeMessageDigestDirectly.java b/errorprone-checks/src/main/java/org/hyperledger/errorpronechecks/DoNotInvokeMessageDigestDirectly.java deleted file mode 100644 index 7ce0c81f05c..00000000000 --- a/errorprone-checks/src/main/java/org/hyperledger/errorpronechecks/DoNotInvokeMessageDigestDirectly.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.errorpronechecks; - -import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; - -import com.google.auto.service.AutoService; -import com.google.errorprone.BugPattern; -import com.google.errorprone.VisitorState; -import com.google.errorprone.bugpatterns.BugChecker; -import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher; -import com.google.errorprone.matchers.Description; -import com.sun.source.tree.MethodInvocationTree; - -@AutoService(BugChecker.class) -@BugPattern( - summary = "Do not invoke MessageDigest.getInstance directly.", - severity = WARNING, - linkType = BugPattern.LinkType.NONE) -public class DoNotInvokeMessageDigestDirectly extends BugChecker - implements MethodInvocationTreeMatcher { - - @SuppressWarnings("TreeToString") - @Override - public Description matchMethodInvocation( - final MethodInvocationTree tree, final VisitorState state) { - if (tree.getMethodSelect().toString().equals("MessageDigest.getInstance")) { - return describeMatch(tree); - } - return Description.NO_MATCH; - } -} diff --git a/errorprone-checks/src/main/java/org/hyperledger/errorpronechecks/DoNotReturnNullOptionals.java b/errorprone-checks/src/main/java/org/hyperledger/errorpronechecks/DoNotReturnNullOptionals.java deleted file mode 100644 index 7310058e5a7..00000000000 --- a/errorprone-checks/src/main/java/org/hyperledger/errorpronechecks/DoNotReturnNullOptionals.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.errorpronechecks; - -import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION; -import static com.google.errorprone.matchers.Matchers.contains; -import static com.sun.source.tree.Tree.Kind.NULL_LITERAL; - -import com.google.auto.service.AutoService; -import com.google.errorprone.BugPattern; -import com.google.errorprone.VisitorState; -import com.google.errorprone.bugpatterns.BugChecker; -import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher; -import com.google.errorprone.matchers.Description; -import com.google.errorprone.matchers.Matcher; -import com.sun.source.tree.MethodTree; -import com.sun.source.tree.ReturnTree; -import com.sun.source.tree.Tree; - -/* - * This is reworked from an example found at: - * https://github.com/google/error-prone/wiki/Writing-a-check - */ - -@AutoService(BugChecker.class) // the service descriptor -@BugPattern( - summary = "Do not return null optionals.", - severity = SUGGESTION, - linkType = BugPattern.LinkType.NONE) -public class DoNotReturnNullOptionals extends BugChecker implements MethodTreeMatcher { - - private static class ReturnNullMatcher implements Matcher { - - @Override - public boolean matches(final Tree tree, final VisitorState state) { - if ((tree instanceof ReturnTree) && (((ReturnTree) tree).getExpression() != null)) { - return ((ReturnTree) tree).getExpression().getKind() == NULL_LITERAL; - } - return false; - } - } - - private static final Matcher RETURN_NULL = new ReturnNullMatcher(); - private static final Matcher CONTAINS_RETURN_NULL = contains(RETURN_NULL); - - @SuppressWarnings("TreeToString") - @Override - public Description matchMethod(final MethodTree tree, final VisitorState state) { - if ((tree.getReturnType() == null) - || !tree.getReturnType().toString().startsWith("Optional<") - || (tree.getBody() == null) - || (!CONTAINS_RETURN_NULL.matches(tree.getBody(), state))) { - return Description.NO_MATCH; - } - return describeMatch(tree); - } -} diff --git a/errorprone-checks/src/main/java/org/hyperledger/errorpronechecks/ExperimentalCliOptionMustBeCorrectlyDisplayed.java b/errorprone-checks/src/main/java/org/hyperledger/errorpronechecks/ExperimentalCliOptionMustBeCorrectlyDisplayed.java deleted file mode 100644 index 3cda884792c..00000000000 --- a/errorprone-checks/src/main/java/org/hyperledger/errorpronechecks/ExperimentalCliOptionMustBeCorrectlyDisplayed.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.errorpronechecks; - -import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; - -import java.util.Map; -import java.util.Optional; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.ExecutableElement; - -import com.google.auto.service.AutoService; -import com.google.errorprone.BugPattern; -import com.google.errorprone.VisitorState; -import com.google.errorprone.bugpatterns.BugChecker; -import com.google.errorprone.bugpatterns.BugChecker.AnnotationTreeMatcher; -import com.google.errorprone.matchers.Description; -import com.google.errorprone.util.ASTHelpers; -import com.sun.source.tree.AnnotationTree; -import com.sun.tools.javac.tree.JCTree; - -@AutoService(BugChecker.class) -@BugPattern( - summary = "Experimental options must be hidden and not present in the BesuCommand class.", - severity = WARNING, - linkType = BugPattern.LinkType.NONE) -public class ExperimentalCliOptionMustBeCorrectlyDisplayed extends BugChecker - implements AnnotationTreeMatcher { - - @Override - public Description matchAnnotation(AnnotationTree tree, VisitorState state) { - final AnnotationMirror annotationMirror = ASTHelpers.getAnnotationMirror(tree); - if (annotationMirror.getAnnotationType().toString().equals("picocli.CommandLine.Option")) { - final Optional names = - getAnnotationValue(annotationMirror, "names"); - if (names.isPresent() && names.get().getValue().toString().contains("--X")) { - final JCTree.JCCompilationUnit compilation = - (JCTree.JCCompilationUnit) state.getPath().getCompilationUnit(); - if (compilation.getSourceFile().getName().endsWith("BesuCommand.java")) { - return describeMatch(tree); - } - final Optional isHidden = - getAnnotationValue(annotationMirror, "hidden"); - if (isHidden.isEmpty() || !((boolean) isHidden.get().getValue())) { - return describeMatch(tree); - } - } - } - return Description.NO_MATCH; - } - - private Optional getAnnotationValue( - final AnnotationMirror annotationMirror, final String name) { - final Map elementValues = - annotationMirror.getElementValues(); - final Optional retValue = - elementValues.keySet().stream() - .filter(k -> k.getSimpleName().toString().equals(name)) - .map(elementValues::get) - .findAny(); - return retValue; - } -} diff --git a/errorprone-checks/src/main/java/org/hyperledger/errorpronechecks/MethodInputParametersMustBeFinal.java b/errorprone-checks/src/main/java/org/hyperledger/errorpronechecks/MethodInputParametersMustBeFinal.java deleted file mode 100644 index abd6f3b58d0..00000000000 --- a/errorprone-checks/src/main/java/org/hyperledger/errorpronechecks/MethodInputParametersMustBeFinal.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.errorpronechecks; - -import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; - -import javax.lang.model.element.Modifier; - -import com.google.auto.service.AutoService; -import com.google.errorprone.BugPattern; -import com.google.errorprone.VisitorState; -import com.google.errorprone.bugpatterns.BugChecker; -import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher; -import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher; -import com.google.errorprone.matchers.Description; -import com.google.errorprone.util.ASTHelpers; -import com.sun.source.tree.ClassTree; -import com.sun.source.tree.MethodTree; -import com.sun.source.tree.ModifiersTree; -import com.sun.source.tree.VariableTree; - -@AutoService(BugChecker.class) -@BugPattern( - summary = "Method input parameters must be final.", - severity = WARNING, - linkType = BugPattern.LinkType.NONE) -public class MethodInputParametersMustBeFinal extends BugChecker - implements MethodTreeMatcher, ClassTreeMatcher { - - private boolean isAbstraction = false; - private boolean isGenerated = false; - - @Override - public Description matchClass(final ClassTree tree, final VisitorState state) { - isAbstraction = - isInterface(tree.getModifiers()) - || isAnonymousClassInAbstraction(tree) - || isEnumInAbstraction(tree); - isGenerated = ASTHelpers.hasDirectAnnotationWithSimpleName(tree, "Generated"); - return Description.NO_MATCH; - } - - @Override - public Description matchMethod(final MethodTree tree, final VisitorState state) { - if (isGenerated) { - return Description.NO_MATCH; - } - - final ModifiersTree mods = tree.getModifiers(); - - if (isAbstraction) { - if (isConcreteMethod(mods)) { - return matchParameters(tree); - } - } else if (isNotAbstract(mods)) { - return matchParameters(tree); - } - - return Description.NO_MATCH; - } - - private Description matchParameters(final MethodTree tree) { - for (final VariableTree inputParameter : tree.getParameters()) { - if (isMissingFinalModifier(inputParameter)) { - return describeMatch(tree); - } - } - - return Description.NO_MATCH; - } - - private boolean isMissingFinalModifier(final VariableTree inputParameter) { - return !inputParameter.getModifiers().getFlags().contains(Modifier.FINAL); - } - - private boolean isNotAbstract(final ModifiersTree mods) { - return !mods.getFlags().contains(Modifier.ABSTRACT); - } - - @SuppressWarnings("TreeToString") - private boolean isInterface(final ModifiersTree mods) { - return mods.toString().contains("interface"); - } - - private boolean isConcreteMethod(final ModifiersTree mods) { - return mods.getFlags().contains(Modifier.DEFAULT) || mods.getFlags().contains(Modifier.STATIC); - } - - private boolean isAnonymousClassInAbstraction(final ClassTree tree) { - return isAbstraction && isAnonymousClass(tree); - } - - private boolean isAnonymousClass(final ClassTree tree) { - return tree.getSimpleName().contentEquals(""); - } - - private boolean isEnumInAbstraction(final ClassTree tree) { - return isAbstraction && isEnum(tree); - } - - @SuppressWarnings("TreeToString") - private boolean isEnum(final ClassTree tree) { - return tree.toString().contains("enum"); - } - - @Override - public boolean equals(Object o) { - // isAbstract and isGenerated are transient calculations, not relevant to equality checks - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - return super.equals(o); - } - - @Override - public int hashCode() { - // isAbstract and isGenerated are transient calculations, not relevant to equality checks - return super.hashCode(); - } -} diff --git a/errorprone-checks/src/main/java/org/hyperledger/errorpronechecks/PrivateStaticFinalLoggers.java b/errorprone-checks/src/main/java/org/hyperledger/errorpronechecks/PrivateStaticFinalLoggers.java deleted file mode 100644 index 445f3226d80..00000000000 --- a/errorprone-checks/src/main/java/org/hyperledger/errorpronechecks/PrivateStaticFinalLoggers.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.errorpronechecks; - -import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; -import static com.google.errorprone.fixes.SuggestedFixes.addModifiers; -import static com.google.errorprone.matchers.Description.NO_MATCH; -import static com.google.errorprone.util.ASTHelpers.getType; -import static com.google.errorprone.util.ASTHelpers.isSubtype; - -import java.util.List; -import java.util.Optional; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.Modifier; - -import com.google.auto.service.AutoService; -import com.google.errorprone.BugPattern; -import com.google.errorprone.VisitorState; -import com.google.errorprone.bugpatterns.BugChecker; -import com.google.errorprone.bugpatterns.BugChecker.VariableTreeMatcher; -import com.google.errorprone.fixes.SuggestedFix; -import com.google.errorprone.matchers.Description; -import com.google.errorprone.suppliers.Supplier; -import com.google.errorprone.suppliers.Suppliers; -import com.google.errorprone.util.ASTHelpers; -import com.sun.source.tree.VariableTree; -import com.sun.tools.javac.code.Symbol; -import com.sun.tools.javac.code.Type; - -@AutoService(BugChecker.class) -@BugPattern( - summary = "Logger classes should be private, static, and final.", - severity = WARNING, - linkType = BugPattern.LinkType.NONE) -public class PrivateStaticFinalLoggers extends BugChecker implements VariableTreeMatcher { - - static final Supplier ORG_SLF4J_LOGGER = Suppliers.typeFromString("org.slf4j.Logger"); - - @Override - public Description matchVariable(final VariableTree tree, final VisitorState state) { - final Symbol.VarSymbol sym = ASTHelpers.getSymbol(tree); - if (sym == null || sym.getKind() != ElementKind.FIELD) { - return NO_MATCH; - } - if (sym.getModifiers() - .containsAll(List.of(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL))) { - return NO_MATCH; - } - if (!isSubtype(getType(tree), ORG_SLF4J_LOGGER.get(state), state)) { - return NO_MATCH; - } - Optional fixes = - addModifiers(tree, state, Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL); - return buildDescription(tree) - .addFix(fixes.isPresent() ? fixes.get() : SuggestedFix.emptyFix()) - .build(); - } -} diff --git a/errorprone-checks/src/test/java/org/hyperledger/errorpronechecks/BannedMethodTest.java b/errorprone-checks/src/test/java/org/hyperledger/errorpronechecks/BannedMethodTest.java deleted file mode 100644 index 48f382946d8..00000000000 --- a/errorprone-checks/src/test/java/org/hyperledger/errorpronechecks/BannedMethodTest.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.errorpronechecks; - -import com.google.errorprone.CompilationTestHelper; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -public class BannedMethodTest { - - private CompilationTestHelper compilationHelper; - - @BeforeEach - public void setup() { - compilationHelper = CompilationTestHelper.newInstance(BannedMethod.class, getClass()); - } - - @Test - public void bannedMethodsPositiveCases() { - compilationHelper.addSourceFile("BannedMethodPositiveCases.java").doTest(); - } - - @Test - public void bannedMethodsNegativeCases() { - compilationHelper.addSourceFile("BannedMethodNegativeCases.java").doTest(); - } -} diff --git a/errorprone-checks/src/test/java/org/hyperledger/errorpronechecks/DoNotCreateSecureRandomDirectlyTest.java b/errorprone-checks/src/test/java/org/hyperledger/errorpronechecks/DoNotCreateSecureRandomDirectlyTest.java deleted file mode 100644 index 525f5a8689a..00000000000 --- a/errorprone-checks/src/test/java/org/hyperledger/errorpronechecks/DoNotCreateSecureRandomDirectlyTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.errorpronechecks; - -import com.google.errorprone.CompilationTestHelper; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -public class DoNotCreateSecureRandomDirectlyTest { - - private CompilationTestHelper compilationHelper; - - @BeforeEach - public void setup() { - compilationHelper = - CompilationTestHelper.newInstance(DoNotCreateSecureRandomDirectly.class, getClass()); - } - - @Test - public void doNotCreateSecureRandomDirectlyPositiveCases() { - compilationHelper.addSourceFile("DoNotCreateSecureRandomDirectlyPositiveCases.java").doTest(); - } - - @Test - public void doNotCreateSecureRandomDirectlyNegativeCases() { - compilationHelper.addSourceFile("DoNotCreateSecureRandomDirectlyNegativeCases.java").doTest(); - } -} diff --git a/errorprone-checks/src/test/java/org/hyperledger/errorpronechecks/DoNotInvokeMessageDigestDirectlyTest.java b/errorprone-checks/src/test/java/org/hyperledger/errorpronechecks/DoNotInvokeMessageDigestDirectlyTest.java deleted file mode 100644 index 986aedb63c2..00000000000 --- a/errorprone-checks/src/test/java/org/hyperledger/errorpronechecks/DoNotInvokeMessageDigestDirectlyTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.errorpronechecks; - -import com.google.errorprone.CompilationTestHelper; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -public class DoNotInvokeMessageDigestDirectlyTest { - - private CompilationTestHelper compilationHelper; - - @BeforeEach - public void setup() { - compilationHelper = - CompilationTestHelper.newInstance(DoNotInvokeMessageDigestDirectly.class, getClass()); - } - - @Test - public void doNotInvokeMessageDigestDirectlyPositiveCases() { - compilationHelper.addSourceFile("DoNotInvokeMessageDigestDirectlyPositiveCases.java").doTest(); - } - - @Test - public void doNotInvokeMessageDigestDirectlyNegativeCases() { - compilationHelper.addSourceFile("DoNotInvokeMessageDigestDirectlyNegativeCases.java").doTest(); - } -} diff --git a/errorprone-checks/src/test/java/org/hyperledger/errorpronechecks/DoNotReturnNullOptionalsTest.java b/errorprone-checks/src/test/java/org/hyperledger/errorpronechecks/DoNotReturnNullOptionalsTest.java deleted file mode 100644 index 0055c9fc1ed..00000000000 --- a/errorprone-checks/src/test/java/org/hyperledger/errorpronechecks/DoNotReturnNullOptionalsTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.errorpronechecks; - -import com.google.errorprone.CompilationTestHelper; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -public class DoNotReturnNullOptionalsTest { - - private CompilationTestHelper compilationHelper; - - @BeforeEach - public void setup() { - compilationHelper = - CompilationTestHelper.newInstance(DoNotReturnNullOptionals.class, getClass()); - } - - @Test - public void doNotReturnNullPositiveCases() { - compilationHelper.addSourceFile("DoNotReturnNullOptionalsPositiveCases.java").doTest(); - } - - @Test - public void doNotReturnNullNegativeCases() { - compilationHelper.addSourceFile("DoNotReturnNullOptionalsNegativeCases.java").doTest(); - } -} diff --git a/errorprone-checks/src/test/java/org/hyperledger/errorpronechecks/ExperimentalCliOptionMustBeCorrectlyDisplayedTest.java b/errorprone-checks/src/test/java/org/hyperledger/errorpronechecks/ExperimentalCliOptionMustBeCorrectlyDisplayedTest.java deleted file mode 100644 index 159c3905eab..00000000000 --- a/errorprone-checks/src/test/java/org/hyperledger/errorpronechecks/ExperimentalCliOptionMustBeCorrectlyDisplayedTest.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.errorpronechecks; - -import com.google.errorprone.CompilationTestHelper; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -public class ExperimentalCliOptionMustBeCorrectlyDisplayedTest { - - private CompilationTestHelper compilationHelper; - - @BeforeEach - public void setup() { - compilationHelper = - CompilationTestHelper.newInstance( - ExperimentalCliOptionMustBeCorrectlyDisplayed.class, getClass()); - } - - @Test - public void experimentalCliOptionMustBeHiddenPositiveCases() { - compilationHelper - .addSourceFile("ExperimentalCliOptionMustBeCorrectlyDisplayedPositiveCases.java") - .doTest(); - } - - @Test - public void experimentalCliOptionMustBeHiddenNegativeCases() { - compilationHelper - .addSourceFile("ExperimentalCliOptionMustBeCorrectlyDisplayedNegativeCases.java") - .doTest(); - } -} diff --git a/errorprone-checks/src/test/java/org/hyperledger/errorpronechecks/MethodInputParametersMustBeFinalTest.java b/errorprone-checks/src/test/java/org/hyperledger/errorpronechecks/MethodInputParametersMustBeFinalTest.java deleted file mode 100644 index f94fa2d606c..00000000000 --- a/errorprone-checks/src/test/java/org/hyperledger/errorpronechecks/MethodInputParametersMustBeFinalTest.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.errorpronechecks; - -import com.google.errorprone.CompilationTestHelper; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -public class MethodInputParametersMustBeFinalTest { - - private CompilationTestHelper compilationHelper; - - @BeforeEach - public void setup() { - compilationHelper = - CompilationTestHelper.newInstance(MethodInputParametersMustBeFinal.class, getClass()); - } - - @Test - public void methodInputParametersMustBeFinalPositiveCases() { - compilationHelper.addSourceFile("MethodInputParametersMustBeFinalPositiveCases.java").doTest(); - } - - @Test - public void methodInputParametersMustBeFinalInterfacePositiveCases() { - compilationHelper - .addSourceFile("MethodInputParametersMustBeFinalInterfacePositiveCases.java") - .doTest(); - } - - @Test - public void methodInputParametersMustBeFinalNegativeCases() { - compilationHelper.addSourceFile("MethodInputParametersMustBeFinalNegativeCases.java").doTest(); - } - - @Test - public void methodInputParametersMustBeFinalInterfaceNegativeCases() { - compilationHelper - .addSourceFile("MethodInputParametersMustBeFinalInterfaceNegativeCases.java") - .doTest(); - } -} diff --git a/errorprone-checks/src/test/java/org/hyperledger/errorpronechecks/PrivateStaticFinalLoggersTest.java b/errorprone-checks/src/test/java/org/hyperledger/errorpronechecks/PrivateStaticFinalLoggersTest.java deleted file mode 100644 index f15dabca1d6..00000000000 --- a/errorprone-checks/src/test/java/org/hyperledger/errorpronechecks/PrivateStaticFinalLoggersTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.errorpronechecks; - -import com.google.errorprone.CompilationTestHelper; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -public class PrivateStaticFinalLoggersTest { - - private CompilationTestHelper compilationHelper; - - @BeforeEach - public void setup() { - compilationHelper = - CompilationTestHelper.newInstance(PrivateStaticFinalLoggers.class, getClass()); - } - - @Test - public void privateStaticFinalLoggersPositiveCases() { - compilationHelper.addSourceFile("PrivateStaticFinalLoggersPositiveCases.java").doTest(); - } - - @Test - public void privateStaticFinalLoggersNegativeCases() { - compilationHelper.addSourceFile("PrivateStaticFinalLoggersNegativeCases.java").doTest(); - } -} diff --git a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/BannedMethodNegativeCases.java b/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/BannedMethodNegativeCases.java deleted file mode 100644 index 5b105322e54..00000000000 --- a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/BannedMethodNegativeCases.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.errorpronechecks; - -import java.util.Objects; - -public class BannedMethodNegativeCases { - - public void callsObjectsEquals() throws Exception { - Objects.equals("1", "1"); - } - - public void callsObjectsHashCode() throws Exception { - Objects.hash("1", "1"); - } -} diff --git a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/BannedMethodPositiveCases.java b/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/BannedMethodPositiveCases.java deleted file mode 100644 index 0b2208a42e3..00000000000 --- a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/BannedMethodPositiveCases.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.errorpronechecks; - -import com.google.common.base.Objects; - -public class BannedMethodPositiveCases { - - public void callsObjectsEquals() throws Exception { - // BUG: Diagnostic contains: Do not use com.google.common.base.Objects methods, use - // java.util.Objects methods instead. - Objects.equal("1", "1"); - } - - public void callsObjectsHashCode() throws Exception { - // BUG: Diagnostic contains: Do not use com.google.common.base.Objects methods, use - // java.util.Objects methods instead. - Objects.hashCode("1", "1"); - } - - public void usesJUnitAssertions() throws Exception { - // BUG: Diagnostic contains: Do not use junit assertions. Use assertj assertions instead. - org.junit.Assert.assertEquals(1, 1); - // BUG: Diagnostic contains: Do not use junit assertions. Use assertj assertions instead. - org.junit.Assert.assertNotEquals(1, 2); - // BUG: Diagnostic contains: Do not use junit assertions. Use assertj assertions instead. - org.junit.Assert.assertTrue(true); - // BUG: Diagnostic contains: Do not use junit assertions. Use assertj assertions instead. - org.junit.Assert.assertFalse(false); - // BUG: Diagnostic contains: Do not use junit assertions. Use assertj assertions instead. - org.junit.Assert.assertNull(null); - // BUG: Diagnostic contains: Do not use junit assertions. Use assertj assertions instead. - org.junit.Assert.assertNotNull("foo"); - // BUG: Diagnostic contains: Do not use junit assertions. Use assertj assertions instead. - org.junit.Assert.assertArrayEquals(new int[] {1}, new int[] {1}); - } -} diff --git a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/DoNotCreateSecureRandomDirectlyNegativeCases.java b/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/DoNotCreateSecureRandomDirectlyNegativeCases.java deleted file mode 100644 index 704eeffa105..00000000000 --- a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/DoNotCreateSecureRandomDirectlyNegativeCases.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.errorpronechecks; - -import java.security.Provider; -import java.security.SecureRandom; - -public class DoNotCreateSecureRandomDirectlyNegativeCases { - - public void callsNonJRESecureRandomGetInstance() throws Exception { - TestSecureRandom.getInstance(""); - TestSecureRandom.getInstance("", ""); - TestSecureRandom.getInstance("", new Provider("", 0, "") {}); - } - - public void invokesNonJRESecureRandomConstructor() throws Exception { - new TestSecureRandom(); - } - - private class TestSecureRandom extends SecureRandom {} -} diff --git a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/DoNotCreateSecureRandomDirectlyPositiveCases.java b/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/DoNotCreateSecureRandomDirectlyPositiveCases.java deleted file mode 100644 index 8fb932cbfb0..00000000000 --- a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/DoNotCreateSecureRandomDirectlyPositiveCases.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.errorpronechecks; - -import java.security.Provider; -import java.security.SecureRandom; - -public class DoNotCreateSecureRandomDirectlyPositiveCases { - - public void callsSecureRandomGetInstance() throws Exception { - // BUG: Diagnostic contains: Do not create SecureRandom directly. - SecureRandom.getInstance(""); - - // BUG: Diagnostic contains: Do not create SecureRandom directly. - SecureRandom.getInstance("", ""); - - // BUG: Diagnostic contains: Do not create SecureRandom directly. - SecureRandom.getInstance("", new Provider("", 0, "") {}); - } - - public void invokesSecureRandomConstructor() throws Exception { - // BUG: Diagnostic contains: Do not create SecureRandom directly. - new SecureRandom(); - - // BUG: Diagnostic contains: Do not create SecureRandom directly. - new SecureRandom(new byte[] {}); - } -} diff --git a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/DoNotInvokeMessageDigestDirectlyNegativeCases.java b/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/DoNotInvokeMessageDigestDirectlyNegativeCases.java deleted file mode 100644 index 3f84b2fb1a4..00000000000 --- a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/DoNotInvokeMessageDigestDirectlyNegativeCases.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.errorpronechecks; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -public class DoNotInvokeMessageDigestDirectlyNegativeCases { - - public void callsMessageDigestGetInstance() throws NoSuchAlgorithmException { - MessageDigest dig = null; - } -} diff --git a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/DoNotInvokeMessageDigestDirectlyPositiveCases.java b/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/DoNotInvokeMessageDigestDirectlyPositiveCases.java deleted file mode 100644 index 5aac65e16d6..00000000000 --- a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/DoNotInvokeMessageDigestDirectlyPositiveCases.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.errorpronechecks; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -public class DoNotInvokeMessageDigestDirectlyPositiveCases { - - public void callsMessageDigestGetInstance() throws NoSuchAlgorithmException { - // BUG: Diagnostic contains: Do not invoke MessageDigest.getInstance directly. - MessageDigest dig = MessageDigest.getInstance("SHA-256"); - } -} diff --git a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/DoNotReturnNullOptionalsNegativeCases.java b/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/DoNotReturnNullOptionalsNegativeCases.java deleted file mode 100644 index aa9ac813905..00000000000 --- a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/DoNotReturnNullOptionalsNegativeCases.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.errorpronechecks; - -import java.util.Optional; -import javax.annotation.Nullable; - -public class DoNotReturnNullOptionalsNegativeCases { - - public interface allInterfacesAreValid { - public Optional ExpectToBeOverridden(); - } - - public DoNotReturnNullOptionalsNegativeCases() {} - - public Optional doesNotReturnNull() { - return Optional.of(3L); - } - - @Nullable - public Optional returnsNullButAnnotatedWithNullable() { - return Optional.empty(); - } -} diff --git a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/DoNotReturnNullOptionalsPositiveCases.java b/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/DoNotReturnNullOptionalsPositiveCases.java deleted file mode 100644 index cd814d9cf99..00000000000 --- a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/DoNotReturnNullOptionalsPositiveCases.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.errorpronechecks; - -import java.util.Optional; - -public class DoNotReturnNullOptionalsPositiveCases { - - // BUG: Diagnostic contains: Do not return null optionals. - public Optional returnsNull() { - return null; - } - - // BUG: Diagnostic contains: Do not return null optionals. - public Optional sometimesReturnsNull(boolean random) { - if (random) { - - return null; - } - return Optional.of(2L); - } -} diff --git a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/ExperimentalCliOptionMustBeCorrectlyDisplayedNegativeCases.java b/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/ExperimentalCliOptionMustBeCorrectlyDisplayedNegativeCases.java deleted file mode 100644 index 5362bba7935..00000000000 --- a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/ExperimentalCliOptionMustBeCorrectlyDisplayedNegativeCases.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.errorpronechecks; - -import picocli.CommandLine; - -public class ExperimentalCliOptionMustBeCorrectlyDisplayedNegativeCases { - - @CommandLine.Option( - hidden = true, - names = {"--Xexperimental"}) - private String experimental = ""; - - @CommandLine.Option( - hidden = false, - names = {"--notExperimental"}) - private String notExperimental = ""; - - @CommandLine.Option(names = {"--notExperimental2"}) - private String notExperimental2 = ""; - - private class AnotherClass { - @CommandLine.Option(names = {"--notExperimentalInAnotherClass"}) - private String notExperimentalInAnotherClass = ""; - - @CommandLine.Option( - hidden = true, - names = {"--XexperimentalInAnotherClass"}) - private String experimentalInAnotherClass = ""; - } - - private class BesuCommand { - - @CommandLine.Option(names = {"--notExperimentalInBesuCommandClass"}) - private String notExperimentalInBesuCommandClass = ""; - } -} diff --git a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/ExperimentalCliOptionMustBeCorrectlyDisplayedPositiveCases.java b/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/ExperimentalCliOptionMustBeCorrectlyDisplayedPositiveCases.java deleted file mode 100644 index c1721b4ebf8..00000000000 --- a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/ExperimentalCliOptionMustBeCorrectlyDisplayedPositiveCases.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.errorpronechecks; - -import picocli.CommandLine; - -public class ExperimentalCliOptionMustBeCorrectlyDisplayedPositiveCases { - - // BUG: Diagnostic contains: Experimental options must be hidden and not present in the - // BesuCommand class. - @CommandLine.Option( - hidden = false, - names = {"--Xexperimental"}) - private String experimental = ""; - - // BUG: Diagnostic contains: Experimental options must be hidden and not present in the - // BesuCommand class. - @CommandLine.Option(names = {"--Xexperimental2"}) - private String experimental2 = ""; - - private class BesuCommand { - - // BUG: Diagnostic contains: Experimental options must be hidden and not present in the - // BesuCommand class. - @CommandLine.Option(names = {"--XexperimentalInBesuCommandClass"}) - private String experimentalInBesuCommandClass = ""; - } -} diff --git a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/MethodInputParametersMustBeFinalInterfaceNegativeCases.java b/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/MethodInputParametersMustBeFinalInterfaceNegativeCases.java deleted file mode 100644 index 237e3c17372..00000000000 --- a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/MethodInputParametersMustBeFinalInterfaceNegativeCases.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.errorpronechecks; - -import java.util.Observable; -import java.util.Observer; - -public interface MethodInputParametersMustBeFinalInterfaceNegativeCases { - - void parameterCannotBeFinal(int value); - - default void concreteMethod(final long value) {} - - static void anotherConcreteMethod(final double value) {} - - static Observer annonymousClass() { - return new Observer() { - @Override - public void update(final Observable o, final Object arg) {} - }; - } - - void methodAfterAnnonymousClass(int value); - - enum Status {} - - void methodAfterEnum(int value); -} diff --git a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/MethodInputParametersMustBeFinalInterfacePositiveCases.java b/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/MethodInputParametersMustBeFinalInterfacePositiveCases.java deleted file mode 100644 index 759c1859d32..00000000000 --- a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/MethodInputParametersMustBeFinalInterfacePositiveCases.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.errorpronechecks; - -public interface MethodInputParametersMustBeFinalInterfacePositiveCases { - - // BUG: Diagnostic contains: Method input parameters must be final. - default void concreteMethod(int value) {} - - // BUG: Diagnostic contains: Method input parameters must be final. - static void concreteStaticMethodsAreIncluded(int value) {} -} diff --git a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/MethodInputParametersMustBeFinalNegativeCases.java b/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/MethodInputParametersMustBeFinalNegativeCases.java deleted file mode 100644 index a86056cf9f6..00000000000 --- a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/MethodInputParametersMustBeFinalNegativeCases.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.errorpronechecks; - -import javax.annotation.processing.Generated; - -public class MethodInputParametersMustBeFinalNegativeCases { - - public void noInputParameters() {} - - public void onlyPrimativeInputParameters(final long value) {} - - public void onlyObjectInputParameters(final Object value) {} - - public void mixedInputParameters(final Object value, final int anotherValue) {} - - public interface allInterfacesAreValid { - void parameterCannotBeFinal(int value); - } -} - -@Generated( - value = "test", - comments = "Every method is buggy, but ignored because the class has been tagged generated") -class MethodInputParametersMustBeFinalPositiveCasesBugGenerated1 { - - public void primativeInputMethod(int value) {} - - public void objectInputMethod(Object value) {} - - public void mixedInputMethod(Object value, int anotherValue) {} - - @Generated( - value = "test", - comments = "Every method is buggy, but ignored because the class has been tagged generated") - public abstract class abstractClassDefinition { - public void concreteMethodsAreIncluded(int value) {} - } - - public void varArgsInputMethod(String... value) {} -} - -@Generated( - value = "test", - comments = "Every method is buggy, but ignored because the class has been tagged generated") -class MethodInputParametersMustBeFinalPositiveCasesBugGenerated2 { - - public void mixedInputMethodFirstFinal(final Object value, int anotherValue) {} - - public void mixedInputMethodSecondFinal(Object value, final int anotherValue) {} -} diff --git a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/MethodInputParametersMustBeFinalPositiveCases.java b/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/MethodInputParametersMustBeFinalPositiveCases.java deleted file mode 100644 index f18fad65183..00000000000 --- a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/MethodInputParametersMustBeFinalPositiveCases.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.errorpronechecks; - -public class MethodInputParametersMustBeFinalPositiveCases { - - // BUG: Diagnostic contains: Method input parameters must be final. - public void primativeInputMethod(int value) {} - - // BUG: Diagnostic contains: Method input parameters must be final. - public void objectInputMethod(Object value) {} - - // BUG: Diagnostic contains: Method input parameters must be final. - public void mixedInputMethod(Object value, int anotherValue) {} - - // BUG: Diagnostic contains: Method input parameters must be final. - public void mixedInputMethodFirstFinal(final Object value, int anotherValue) {} - - // BUG: Diagnostic contains: Method input parameters must be final. - public void mixedInputMethodSecondFinal(Object value, final int anotherValue) {} - - // BUG: Diagnostic contains: Method input parameters must be final. - public void varArgsInputMethod(String... value) {} - - public abstract class abstractClassDefinition { - // BUG: Diagnostic contains: Method input parameters must be final. - public void concreteMethodsAreIncluded(int value) {} - } -} diff --git a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/PrivateStaticFinalLoggersNegativeCases.java b/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/PrivateStaticFinalLoggersNegativeCases.java deleted file mode 100644 index 99d8986ac74..00000000000 --- a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/PrivateStaticFinalLoggersNegativeCases.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.errorpronechecks; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class PrivateStaticFinalLoggersNegativeCases { - - private static final Logger LOG = - LoggerFactory.getLogger(PrivateStaticFinalLoggersNegativeCases.class); -} diff --git a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/PrivateStaticFinalLoggersPositiveCases.java b/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/PrivateStaticFinalLoggersPositiveCases.java deleted file mode 100644 index cbe6c772d7f..00000000000 --- a/errorprone-checks/src/test/resources/org/hyperledger/errorpronechecks/PrivateStaticFinalLoggersPositiveCases.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.errorpronechecks; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class PrivateStaticFinalLoggersPositiveCases { - - // BUG: Diagnostic contains: Logger classes should be private, static, and final. - private final Logger LOG = LoggerFactory.getLogger(PrivateStaticFinalLoggersPositiveCases.class); -} diff --git a/ethereum/api/build.gradle b/ethereum/api/build.gradle index ed395df8c5d..3269812fec2 100644 --- a/ethereum/api/build.gradle +++ b/ethereum/api/build.gradle @@ -22,7 +22,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/BlockchainImporter.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/BlockchainImporter.java index 22084fa3abd..3e261abf6d7 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/BlockchainImporter.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/BlockchainImporter.java @@ -15,13 +15,16 @@ package org.hyperledger.besu.ethereum.api.jsonrpc; import org.hyperledger.besu.config.GenesisConfigFile; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.GenesisState; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions; import org.hyperledger.besu.ethereum.util.RawBlockIterator; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import java.net.URL; import java.nio.file.Paths; @@ -42,7 +45,11 @@ public class BlockchainImporter { public BlockchainImporter(final URL blocksUrl, final String genesisJson) throws Exception { protocolSchedule = MainnetProtocolSchedule.fromConfig( - GenesisConfigFile.fromConfig(genesisJson).getConfigOptions()); + GenesisConfigFile.fromConfig(genesisJson).getConfigOptions(), + MiningParameters.newDefault(), + new BadBlockManager(), + false, + new NoOpMetricsSystem()); final BlockHeaderFunctions blockHeaderFunctions = ScheduleBasedBlockHeaderFunctions.create(protocolSchedule); blocks = new ArrayList<>(); diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseKey.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseKey.java index a57cb990c81..a62cc06ec26 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseKey.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseKey.java @@ -38,5 +38,5 @@ public enum JsonRpcResponseKey { TRANSACTION_ROOT, BASEFEE, WITHDRAWALS_ROOT, - DEPOSITS_ROOT + REQUESTS_ROOT } diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseUtils.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseUtils.java index 039aac39ff5..4e067e6ec8a 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseUtils.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcResponseUtils.java @@ -16,7 +16,6 @@ import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcResponseKey.BASEFEE; import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcResponseKey.COINBASE; -import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcResponseKey.DEPOSITS_ROOT; import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcResponseKey.DIFFICULTY; import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcResponseKey.EXTRA_DATA; import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcResponseKey.GAS_LIMIT; @@ -28,6 +27,7 @@ import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcResponseKey.OMMERS_HASH; import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcResponseKey.PARENT_HASH; import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcResponseKey.RECEIPTS_ROOT; +import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcResponseKey.REQUESTS_ROOT; import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcResponseKey.SIZE; import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcResponseKey.STATE_ROOT; import static org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcResponseKey.TIMESTAMP; @@ -104,8 +104,8 @@ public JsonRpcResponse response( final int size = unsignedInt(values.get(SIZE)); final Hash withdrawalsRoot = values.containsKey(WITHDRAWALS_ROOT) ? hash(values.get(WITHDRAWALS_ROOT)) : null; - final Hash depositsRoot = - values.containsKey(DEPOSITS_ROOT) ? hash(values.get(DEPOSITS_ROOT)) : null; + final Hash requestsRoot = + values.containsKey(REQUESTS_ROOT) ? hash(values.get(REQUESTS_ROOT)) : null; final List ommers = new ArrayList<>(); final BlockHeader header = @@ -130,7 +130,7 @@ public JsonRpcResponse response( null, // ToDo 4844: set with the value of blob_gas_used field null, // ToDo 4844: set with the value of excess_blob_gas field null, // TODO 4788: set with the value of the parent beacon block root field - depositsRoot, + requestsRoot, blockHeaderFunctions); return new JsonRpcSuccessResponse( diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcTestMethodsFactory.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcTestMethodsFactory.java index f7573d266ba..538060a905b 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcTestMethodsFactory.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcTestMethodsFactory.java @@ -21,6 +21,7 @@ import org.hyperledger.besu.config.StubGenesisConfigOptions; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.ImmutableApiConfiguration; +import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.filter.FilterManager; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.filter.FilterManagerBuilder; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; @@ -28,6 +29,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.blockcreation.PoWMiningCoordinator; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockImporter; @@ -63,7 +65,9 @@ /** Provides a facade to construct the JSON-RPC component. */ public class JsonRpcTestMethodsFactory { - private static final String CLIENT_VERSION = "TestClientVersion/0.1.0"; + private static final String CLIENT_NODE_NAME = "TestClientVersion/0.1.0"; + private static final String CLIENT_VERSION = "0.1.0"; + private static final String CLIENT_COMMIT = "12345678"; private static final BigInteger NETWORK_ID = BigInteger.valueOf(123); private final BlockchainImporter importer; @@ -78,7 +82,7 @@ public JsonRpcTestMethodsFactory(final BlockchainImporter importer) { this.blockchain = createInMemoryBlockchain(importer.getGenesisBlock()); this.stateArchive = createInMemoryWorldStateArchive(); this.importer.getGenesisState().writeStateTo(stateArchive.getMutable()); - this.context = new ProtocolContext(blockchain, stateArchive, null, Optional.empty()); + this.context = new ProtocolContext(blockchain, stateArchive, null, new BadBlockManager()); final ProtocolSchedule protocolSchedule = importer.getProtocolSchedule(); this.synchronizer = mock(Synchronizer.class); @@ -88,7 +92,9 @@ public JsonRpcTestMethodsFactory(final BlockchainImporter importer) { final BlockImporter blockImporter = protocolSpec.getBlockImporter(); blockImporter.importBlock(context, block, HeaderValidationMode.FULL); } - this.blockchainQueries = new BlockchainQueries(blockchain, stateArchive); + this.blockchainQueries = + new BlockchainQueries( + protocolSchedule, blockchain, stateArchive, MiningParameters.newDefault()); } public JsonRpcTestMethodsFactory( @@ -100,7 +106,12 @@ public JsonRpcTestMethodsFactory( this.blockchain = blockchain; this.stateArchive = stateArchive; this.context = context; - this.blockchainQueries = new BlockchainQueries(blockchain, stateArchive); + this.blockchainQueries = + new BlockchainQueries( + importer.getProtocolSchedule(), + blockchain, + stateArchive, + MiningParameters.newDefault()); this.synchronizer = mock(Synchronizer.class); } @@ -115,7 +126,12 @@ public JsonRpcTestMethodsFactory( this.stateArchive = stateArchive; this.context = context; this.synchronizer = synchronizer; - this.blockchainQueries = new BlockchainQueries(blockchain, stateArchive); + this.blockchainQueries = + new BlockchainQueries( + importer.getProtocolSchedule(), + blockchain, + stateArchive, + MiningParameters.newDefault()); } public BlockchainQueries getBlockchainQueries() { @@ -149,6 +165,8 @@ public Map methods() { final JsonRpcConfiguration jsonRpcConfiguration = mock(JsonRpcConfiguration.class); final WebSocketConfiguration webSocketConfiguration = mock(WebSocketConfiguration.class); final MetricsConfiguration metricsConfiguration = mock(MetricsConfiguration.class); + final GraphQLConfiguration graphQLConfiguration = mock(GraphQLConfiguration.class); + final NatService natService = new NatService(Optional.empty()); final List apis = new ArrayList<>(); @@ -162,7 +180,9 @@ public Map methods() { return new JsonRpcMethodsFactory() .methods( + CLIENT_NODE_NAME, CLIENT_VERSION, + CLIENT_COMMIT, NETWORK_ID, new StubGenesisConfigOptions(), peerDiscovery, @@ -183,6 +203,7 @@ public Map methods() { jsonRpcConfiguration, webSocketConfiguration, metricsConfiguration, + graphQLConfiguration, natService, new HashMap<>(), dataDir, diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/DebugTraceTransactionIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/DebugTraceTransactionIntegrationTest.java index d285ef3816d..5b488987169 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/DebugTraceTransactionIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/DebugTraceTransactionIntegrationTest.java @@ -23,9 +23,9 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponseType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.DebugTraceTransactionResult; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; import org.hyperledger.besu.testutil.BlockTestUtil; import java.util.Map; @@ -69,7 +69,7 @@ public void debugTraceTransactionSuccessTest() { new JsonRpcRequestContext(new JsonRpcRequest("2.0", DEBUG_TRACE_TRANSACTION, params)); final JsonRpcResponse response = method.response(request); - assertThat(response.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); + assertThat(response.getType()).isEqualTo(RpcResponseType.SUCCESS); DebugTraceTransactionResult debugTraceTransactionResult = (DebugTraceTransactionResult) ((JsonRpcSuccessResponse) response).getResult(); assertThat(debugTraceTransactionResult.getGas()).isEqualTo(23705L); diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthGetBlockByNumberLatestDesyncIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthGetBlockByNumberLatestDesyncIntegrationTest.java index 6d723844a2d..088eff85007 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthGetBlockByNumberLatestDesyncIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthGetBlockByNumberLatestDesyncIntegrationTest.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.api.jsonrpc.methods; import static org.assertj.core.api.Assertions.assertThat; @@ -30,6 +29,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResult; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockImporter; @@ -67,7 +67,7 @@ public static void setUpOnce() throws Exception { InMemoryKeyValueStorageProvider.createInMemoryBlockchain(importer.getGenesisBlock()); WorldStateArchive state = InMemoryKeyValueStorageProvider.createInMemoryWorldStateArchive(); importer.getGenesisState().writeStateTo(state.getMutable()); - ProtocolContext context = new ProtocolContext(chain, state, null, Optional.empty()); + ProtocolContext context = new ProtocolContext(chain, state, null, new BadBlockManager()); for (final Block block : importer.getBlocks()) { final ProtocolSchedule protocolSchedule = importer.getProtocolSchedule(); diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthCallIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthCallIntegrationTest.java index f80fa0c3a7a..886f7bc0158 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthCallIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthCallIntegrationTest.java @@ -64,18 +64,12 @@ public void setUp() { @Test public void shouldReturnExpectedResultForCallAtLatestBlock() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"), - Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f"), - null, - null, - null, - null, - null, - Bytes.fromHexString("0x12a7b914"), - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) + .withTo(Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f")) + .withInput(Bytes.fromHexString("0x12a7b914")) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse( @@ -89,18 +83,12 @@ public void shouldReturnExpectedResultForCallAtLatestBlock() { @Test public void shouldReturnExpectedResultForCallAtSpecificBlock() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"), - Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f"), - null, - null, - null, - null, - null, - Bytes.fromHexString("0x12a7b914"), - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) + .withTo(Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f")) + .withInput(Bytes.fromHexString("0x12a7b914")) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter, "0x8"); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse( @@ -114,19 +102,13 @@ public void shouldReturnExpectedResultForCallAtSpecificBlock() { @Test public void shouldReturnSuccessWhenCreatingContract() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"), - null, - null, - null, - null, - null, - null, - Bytes.fromHexString( - "0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029"), - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) + .withInput( + Bytes.fromHexString( + "0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029")) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse( @@ -141,18 +123,13 @@ public void shouldReturnSuccessWhenCreatingContract() { @Test public void shouldReturnErrorWithGasLimitTooLow() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"), - Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f"), - 0L, - null, - null, - null, - null, - Bytes.fromHexString("0x12a7b914"), - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) + .withTo(Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f")) + .withGas(0L) + .withInput(Bytes.fromHexString("0x12a7b914")) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.INTRINSIC_GAS_EXCEEDS_LIMIT); @@ -165,18 +142,14 @@ public void shouldReturnErrorWithGasLimitTooLow() { @Test public void shouldReturnErrorWithGasPriceTooHighAndStrict() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"), - Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f"), - null, - Wei.fromHexString("0x10000000000000"), - null, - null, - null, - Bytes.fromHexString("0x12a7b914"), - null, - true, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) + .withTo(Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f")) + .withGasPrice(Wei.fromHexString("0x10000000000000")) + .withInput(Bytes.fromHexString("0x12a7b914")) + .withStrict(true) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE); @@ -189,18 +162,14 @@ public void shouldReturnErrorWithGasPriceTooHighAndStrict() { @Test public void shouldReturnSuccessWithGasPriceTooHighNotStrict() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"), - Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f"), - null, - Wei.fromHexString("0x10000000000000"), - null, - null, - null, - Bytes.fromHexString("0x12a7b914"), - null, - false, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) + .withTo(Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f")) + .withGasPrice(Wei.fromHexString("0x10000000000000")) + .withInput(Bytes.fromHexString("0x12a7b914")) + .withStrict(false) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse( @@ -214,18 +183,13 @@ public void shouldReturnSuccessWithGasPriceTooHighNotStrict() { @Test public void shouldReturnErrorWithGasPriceTooHigh() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"), - Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f"), - null, - Wei.fromHexString("0x10000000000000"), - null, - null, - null, - Bytes.fromHexString("0x12a7b914"), - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) + .withTo(Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f")) + .withGasPrice(Wei.fromHexString("0x10000000000000")) + .withInput(Bytes.fromHexString("0x12a7b914")) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE); @@ -238,18 +202,13 @@ public void shouldReturnErrorWithGasPriceTooHigh() { @Test public void shouldReturnSuccessWithValidGasPrice() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"), - Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f"), - null, - Wei.fromHexString("0x10"), - null, - null, - null, - Bytes.fromHexString("0x12a7b914"), - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) + .withTo(Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f")) + .withGasPrice(Wei.fromHexString("0x10")) + .withInput(Bytes.fromHexString("0x12a7b914")) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse( @@ -263,18 +222,13 @@ public void shouldReturnSuccessWithValidGasPrice() { @Test public void shouldReturnErrorWithGasPriceAndEmptyBalance() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xdeadbeef00000000000000000000000000000000"), - Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f"), - null, - Wei.fromHexString("0x10"), - null, - null, - null, - Bytes.fromHexString("0x12a7b914"), - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xdeadbeef00000000000000000000000000000000")) + .withTo(Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f")) + .withGasPrice(Wei.fromHexString("0x10")) + .withInput(Bytes.fromHexString("0x12a7b914")) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE); @@ -287,18 +241,13 @@ public void shouldReturnErrorWithGasPriceAndEmptyBalance() { @Test public void shouldReturnSuccessWithZeroGasPriceAndEmptyBalance() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xdeadbeef00000000000000000000000000000000"), - Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f"), - null, - Wei.fromHexString("0x0"), - null, - null, - null, - Bytes.fromHexString("0x12a7b914"), - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xdeadbeef00000000000000000000000000000000")) + .withTo(Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f")) + .withGasPrice(Wei.fromHexString("0x0")) + .withInput(Bytes.fromHexString("0x12a7b914")) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse( @@ -312,18 +261,12 @@ public void shouldReturnSuccessWithZeroGasPriceAndEmptyBalance() { @Test public void shouldReturnSuccessWithoutGasPriceAndEmptyBalance() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xdeadbeef00000000000000000000000000000000"), - Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f"), - null, - null, - null, - null, - null, - Bytes.fromHexString("0x12a7b914"), - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xdeadbeef00000000000000000000000000000000")) + .withTo(Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f")) + .withInput(Bytes.fromHexString("0x12a7b914")) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse( @@ -337,18 +280,13 @@ public void shouldReturnSuccessWithoutGasPriceAndEmptyBalance() { @Test public void shouldReturnSuccessWithInvalidGasPricingAndEmptyBalance() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xdeadbeef00000000000000000000000000000000"), - Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f"), - null, - null, - Wei.fromHexString("0x0A"), - null, - null, - Bytes.fromHexString("0x12a7b914"), - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xdeadbeef00000000000000000000000000000000")) + .withTo(Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f")) + .withMaxPriorityFeePerGas(Wei.fromHexString("0x0A")) + .withInput(Bytes.fromHexString("0x12a7b914")) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse( @@ -362,18 +300,10 @@ public void shouldReturnSuccessWithInvalidGasPricingAndEmptyBalance() { @Test public void shouldReturnEmptyHashResultForCallWithOnlyToField() { final JsonCallParameter callParameter = - new JsonCallParameter( - null, - Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f"), - null, - null, - null, - null, - null, - null, - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withTo(Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f")) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, "0x"); @@ -382,6 +312,26 @@ public void shouldReturnEmptyHashResultForCallWithOnlyToField() { assertThat(response).usingRecursiveComparison().isEqualTo(expectedResponse); } + @Test + public void shouldReturnSuccessWithInputAndDataFieldSetToSameValue() { + final JsonCallParameter callParameter = + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) + .withTo(Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f")) + .withInput(Bytes.fromHexString("0x12a7b914")) + .withData(Bytes.fromHexString("0x12a7b914")) + .build(); + + final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); + final JsonRpcResponse expectedResponse = + new JsonRpcSuccessResponse( + null, "0x0000000000000000000000000000000000000000000000000000000000000001"); + + final JsonRpcResponse response = method.response(request); + + assertThat(response).usingRecursiveComparison().isEqualTo(expectedResponse); + } + private JsonRpcRequestContext requestWithParams(final Object... params) { return new JsonRpcRequestContext(new JsonRpcRequest("2.0", "eth_call", params)); } diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthCreateAccessListIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthCreateAccessListIntegrationTest.java index fe4dc6316ec..6a1bbea2608 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthCreateAccessListIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthCreateAccessListIntegrationTest.java @@ -79,10 +79,11 @@ public void shouldSucceedWhenCreateAccessListMultipleReads() { "0x0000000000000000000000000000000000000000000000000000000000000003")))); final JsonCallParameter callParameter = - createAccessListJsonCallParameters( - "0x658bdf435d810c91414ec09147daa6db62406379", - "0xbb00000000000000000000000000000000000000", - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0x658bdf435d810c91414ec09147daa6db62406379")) + .withTo(Address.fromHexString("0xbb00000000000000000000000000000000000000")) + .withAccessList(null) + .build(); assertAccessListExpectedResult(callParameter, expectedAccessListEntryList, expectedGasUsed); } @@ -101,10 +102,11 @@ public void shouldSucceedWhenCreateAccessListMultipleReads_withAccessListParam() "0x0000000000000000000000000000000000000000000000000000000000000003")))); final JsonCallParameter callParameter = - createAccessListJsonCallParameters( - "0x658bdf435d810c91414ec09147daa6db62406379", - "0xbb00000000000000000000000000000000000000", - expectedAccessListEntryList); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0x658bdf435d810c91414ec09147daa6db62406379")) + .withTo(Address.fromHexString("0xbb00000000000000000000000000000000000000")) + .withAccessList(expectedAccessListEntryList) + .build(); assertAccessListExpectedResult(callParameter, expectedAccessListEntryList, expectedGasUsed); } @@ -115,10 +117,11 @@ public void shouldSucceedWhenCreateAccessListSimpleTransfer() { final List expectedAccessListEntryList = new ArrayList<>(); final JsonCallParameter callParameter = - createAccessListJsonCallParameters( - "0x658bdf435d810c91414ec09147daa6db62406379", - "0x0100000000000000000000000000000000000000", - expectedAccessListEntryList); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0x658bdf435d810c91414ec09147daa6db62406379")) + .withTo(Address.fromHexString("0x0100000000000000000000000000000000000000")) + .withAccessList(expectedAccessListEntryList) + .build(); assertAccessListExpectedResult(callParameter, expectedAccessListEntryList, expectedGasUsed); } @@ -129,10 +132,11 @@ public void shouldSucceedWhenCreateAccessListSimpleContract() { final List expectedAccessListEntryList = new ArrayList<>(); final JsonCallParameter callParameter = - createAccessListJsonCallParameters( - "0x658bdf435d810c91414ec09147daa6db62406379", - "0xaa00000000000000000000000000000000000000", - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0x658bdf435d810c91414ec09147daa6db62406379")) + .withTo(Address.fromHexString("0xaa00000000000000000000000000000000000000")) + .withAccessList(null) + .build(); assertAccessListExpectedResult(callParameter, expectedAccessListEntryList, expectedGasUsed); } @@ -140,7 +144,8 @@ public void shouldSucceedWhenCreateAccessListSimpleContract() { @Test public void shouldReturnExpectedValueForEmptyCallParameter() { final JsonCallParameter callParameter = - new JsonCallParameter(null, null, null, null, null, null, null, null, null, null, null); + new JsonCallParameter.JsonCallParameterBuilder().build(); + final JsonRpcRequestContext request = requestWithParams(callParameter); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, new CreateAccessListResult(new ArrayList<>(), 0xcf08)); @@ -153,18 +158,12 @@ public void shouldReturnExpectedValueForEmptyCallParameter() { @Test public void shouldReturnExpectedValueForTransfer() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"), - Address.fromHexString("0x8888f1f195afa192cfee860698584c030f4c9db1"), - null, - null, - null, - null, - Wei.ZERO, - null, - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) + .withTo(Address.fromHexString("0x8888f1f195afa192cfee860698584c030f4c9db1")) + .withValue(Wei.ZERO) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, new CreateAccessListResult(new ArrayList<>(), 0x5208)); @@ -177,19 +176,13 @@ public void shouldReturnExpectedValueForTransfer() { @Test public void shouldReturnExpectedValueForContractDeploy() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"), - null, - null, - null, - null, - null, - null, - Bytes.fromHexString( - "0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029"), - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) + .withInput( + Bytes.fromHexString( + "0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029")) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, new CreateAccessListResult(new ArrayList<>(), 0x1f081)); @@ -202,19 +195,16 @@ public void shouldReturnExpectedValueForContractDeploy() { @Test public void shouldIgnoreSenderBalanceAccountWhenStrictModeDisabledAndReturnExpectedValue() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0x0000000000000000000000000000000000000000"), - null, - 1L, - Wei.fromHexString("0x9999999999"), - null, - null, - null, - Bytes.fromHexString( - "0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029"), - null, - false, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0x0000000000000000000000000000000000000000")) + .withGas(1L) + .withGasPrice(Wei.fromHexString("0x9999999999")) + .withInput( + Bytes.fromHexString( + "0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029")) + .withStrict(false) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, new CreateAccessListResult(new ArrayList<>(), 0x1f081)); @@ -227,7 +217,7 @@ public void shouldIgnoreSenderBalanceAccountWhenStrictModeDisabledAndReturnExpec @Test public void shouldReturnExpectedValueForInsufficientGas() { final JsonCallParameter callParameter = - new JsonCallParameter(null, null, 1L, null, null, null, null, null, null, null, null); + new JsonCallParameter.JsonCallParameterBuilder().withGas(1L).build(); final JsonRpcRequestContext request = requestWithParams(callParameter); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, new CreateAccessListResult(new ArrayList<>(), 0xcf08)); @@ -246,23 +236,12 @@ private void assertAccessListExpectedResult( new JsonRpcSuccessResponse(null, new CreateAccessListResult(accessList, gasUsed)); final JsonRpcResponse response = method.response(request); - assertThat(response).usingRecursiveComparison().isEqualTo(expectedResponse); - } - - private JsonCallParameter createAccessListJsonCallParameters( - final String from, final String to, final List accessList) { - return new JsonCallParameter( - Address.fromHexString(from), - Address.fromHexString(to), - null, - null, - null, - null, - null, - null, - null, - null, - accessList); + assertThat(response) + .usingRecursiveComparison() + // customize the comparison for the type that lazy compute the hashCode + .withEqualsForType(UInt256::equals, UInt256.class) + .withEqualsForType(Address::equals, Address.class) + .isEqualTo(expectedResponse); } private JsonRpcRequestContext requestWithParams(final Object... params) { diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthEstimateGasIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthEstimateGasIntegrationTest.java index 5bc57774c2b..d17a2774e6c 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthEstimateGasIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthEstimateGasIntegrationTest.java @@ -24,10 +24,12 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonCallParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; +import org.hyperledger.besu.ethereum.mainnet.ValidationResult; +import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; import org.hyperledger.besu.testutil.BlockTestUtil; import java.util.Map; @@ -64,7 +66,7 @@ public void setUp() { @Test public void shouldReturnExpectedValueForEmptyCallParameter() { final JsonCallParameter callParameter = - new JsonCallParameter(null, null, null, null, null, null, null, null, null, null, null); + new JsonCallParameter.JsonCallParameterBuilder().build(); final JsonRpcRequestContext request = requestWithParams(callParameter); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, "0x5208"); @@ -76,18 +78,12 @@ public void shouldReturnExpectedValueForEmptyCallParameter() { @Test public void shouldReturnExpectedValueForTransfer() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"), - Address.fromHexString("0x8888f1f195afa192cfee860698584c030f4c9db1"), - null, - null, - null, - null, - Wei.ONE, - null, - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) + .withTo(Address.fromHexString("0x8888f1f195afa192cfee860698584c030f4c9db1")) + .withValue(Wei.ONE) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, "0x5208"); @@ -99,19 +95,13 @@ public void shouldReturnExpectedValueForTransfer() { @Test public void shouldReturnExpectedValueForContractDeploy() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"), - null, - null, - null, - null, - null, - null, - Bytes.fromHexString( - "0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029"), - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) + .withInput( + Bytes.fromHexString( + "0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029")) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, "0x1b551"); @@ -123,19 +113,16 @@ public void shouldReturnExpectedValueForContractDeploy() { @Test public void shouldIgnoreSenderBalanceAccountWhenStrictModeDisabledAndReturnExpectedValue() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0x0000000000000000000000000000000000000000"), - null, - 1L, - Wei.fromHexString("0x9999999999"), - null, - null, - null, - Bytes.fromHexString( - "0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029"), - null, - false, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0x0000000000000000000000000000000000000000")) + .withGas(1L) + .withGasPrice(Wei.fromHexString("0x9999999999")) + .withInput( + Bytes.fromHexString( + "0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029")) + .withStrict(false) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, "0x1b551"); @@ -147,22 +134,23 @@ public void shouldIgnoreSenderBalanceAccountWhenStrictModeDisabledAndReturnExpec @Test public void shouldNotIgnoreSenderBalanceAccountWhenStrictModeDisabledAndThrowError() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f"), - null, - 1L, - Wei.fromHexString("0x9999999999"), - null, - null, - null, - Bytes.fromHexString( - "0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029"), - null, - true, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0x6295ee1b4f6dd65047762f924ecd367c17eabf8f")) + .withGas(1L) + .withGasPrice(Wei.fromHexString("0x9999999999")) + .withInput( + Bytes.fromHexString( + "0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029")) + .withStrict(true) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter); - final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(null, RpcErrorType.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE); + final ValidationResult validationResult = + ValidationResult.invalid( + TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE, + "transaction up-front cost 0x1cc31b3333167018 exceeds transaction sender account balance 0x140"); + final JsonRpcError rpcError = JsonRpcError.from(validationResult); + final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError); final JsonRpcResponse response = method.response(request); assertThat(response).usingRecursiveComparison().isEqualTo(expectedResponse); @@ -171,7 +159,7 @@ public void shouldNotIgnoreSenderBalanceAccountWhenStrictModeDisabledAndThrowErr @Test public void shouldReturnExpectedValueForInsufficientGas() { final JsonCallParameter callParameter = - new JsonCallParameter(null, null, 1L, null, null, null, null, null, null, null, null); + new JsonCallParameter.JsonCallParameterBuilder().withGas(1L).build(); final JsonRpcRequestContext request = requestWithParams(callParameter); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, "0x5208"); diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthGetBlockByNumberIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthGetBlockByNumberIntegrationTest.java index a7c3aa254ea..3798cbe507c 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthGetBlockByNumberIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthGetBlockByNumberIntegrationTest.java @@ -410,7 +410,7 @@ public void missingTagParameterBlockHashes() { assertThat(thrown) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Invalid json rpc parameter at index 0"); + .hasMessageContaining("Invalid block parameter"); } /** The Tag | Quantity is the first parameter, either a String or a number */ @@ -422,7 +422,7 @@ public void missingTagParameterBlockTransactions() { assertThat(thrown) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Invalid json rpc parameter at index 0"); + .hasMessageContaining("Invalid block parameter"); } /** @@ -437,8 +437,7 @@ public void missingHashesOrTransactionParameter() { assertThat(thrown) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasNoCause() - .hasMessage("Missing required json rpc parameter at index 1"); + .hasMessage("Invalid return complete transaction parameter (index 1)"); } /** @@ -453,8 +452,7 @@ public void missingAllParameters() { assertThat(thrown) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasNoCause() - .hasMessage("Missing required json rpc parameter at index 0"); + .hasMessage("Invalid block parameter (index 0)"); } private JsonRpcRequestContext requestWithParams(final Object... params) { diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthGetFilterChangesIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthGetFilterChangesIntegrationTest.java index cff37a0392b..d4de405305b 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthGetFilterChangesIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/frontier/EthGetFilterChangesIntegrationTest.java @@ -18,6 +18,7 @@ import static java.util.Collections.emptyList; import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -44,10 +45,12 @@ import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.ExecutionContextTestFixture; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthPeers; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions; @@ -107,7 +110,7 @@ public void setUp() { blockchain::getChainHeadHeader); final ProtocolContext protocolContext = executionContext.getProtocolContext(); - EthContext ethContext = mock(EthContext.class); + EthContext ethContext = mock(EthContext.class, RETURNS_DEEP_STUBS); EthPeers ethPeers = mock(EthPeers.class); when(ethContext.getEthPeers()).thenReturn(ethPeers); @@ -120,10 +123,14 @@ public void setUp() { ethContext, new TransactionPoolMetrics(metricsSystem), TransactionPoolConfiguration.DEFAULT, - null); + new BlobCache()); transactionPool.setEnabled(); final BlockchainQueries blockchainQueries = - new BlockchainQueries(blockchain, protocolContext.getWorldStateArchive()); + new BlockchainQueries( + executionContext.getProtocolSchedule(), + blockchain, + protocolContext.getWorldStateArchive(), + MiningParameters.newDefault()); filterManager = new FilterManagerBuilder() .blockchainQueries(blockchainQueries) diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthCallIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthCallIntegrationTest.java index f72a452db9d..5f03d7f4d5f 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthCallIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthCallIntegrationTest.java @@ -64,18 +64,12 @@ public void setUp() { @Test public void shouldReturnSuccessWithoutGasPriceAndEmptyBalance() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xdeadbeef00000000000000000000000000000000"), - Address.fromHexString("0x9b8397f1b0fecd3a1a40cdd5e8221fa461898517"), - null, - null, - null, - null, - null, - Bytes.fromHexString("0x2e64cec1"), - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xdeadbeef00000000000000000000000000000000")) + .withTo(Address.fromHexString("0x9b8397f1b0fecd3a1a40cdd5e8221fa461898517")) + .withInput(Bytes.fromHexString("0x2e64cec1")) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse( @@ -89,18 +83,13 @@ public void shouldReturnSuccessWithoutGasPriceAndEmptyBalance() { @Test public void shouldReturnErrorWithGasPriceTooHigh() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"), - Address.fromHexString("0x9b8397f1b0fecd3a1a40cdd5e8221fa461898517"), - null, - Wei.fromHexString("0x10000000000000"), - null, - null, - null, - Bytes.fromHexString("0x2e64cec1"), - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) + .withTo(Address.fromHexString("0x9b8397f1b0fecd3a1a40cdd5e8221fa461898517")) + .withGasPrice(Wei.fromHexString("0x10000000000000")) + .withInput(Bytes.fromHexString("0x2e64cec1")) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE); @@ -113,18 +102,13 @@ public void shouldReturnErrorWithGasPriceTooHigh() { @Test public void shouldReturnSuccessWithValidGasPrice() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"), - Address.fromHexString("0x9b8397f1b0fecd3a1a40cdd5e8221fa461898517"), - null, - Wei.fromHexString("0x3B9ACA01"), - null, - null, - null, - Bytes.fromHexString("0x2e64cec1"), - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) + .withTo(Address.fromHexString("0x9b8397f1b0fecd3a1a40cdd5e8221fa461898517")) + .withGasPrice(Wei.fromHexString("0x3B9ACA01")) + .withInput(Bytes.fromHexString("0x2e64cec1")) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse( @@ -138,18 +122,13 @@ public void shouldReturnSuccessWithValidGasPrice() { @Test public void shouldReturnErrorWithGasPriceLessThanCurrentBaseFee() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"), - Address.fromHexString("0x9b8397f1b0fecd3a1a40cdd5e8221fa461898517"), - null, - Wei.fromHexString("0x0A"), - null, - null, - null, - Bytes.fromHexString("0x2e64cec1"), - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) + .withTo(Address.fromHexString("0x9b8397f1b0fecd3a1a40cdd5e8221fa461898517")) + .withGasPrice(Wei.fromHexString("0x0A")) + .withInput(Bytes.fromHexString("0x2e64cec1")) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.GAS_PRICE_BELOW_CURRENT_BASE_FEE); @@ -162,18 +141,13 @@ public void shouldReturnErrorWithGasPriceLessThanCurrentBaseFee() { @Test public void shouldReturnSuccessWithValidMaxFeePerGas() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"), - Address.fromHexString("0x9b8397f1b0fecd3a1a40cdd5e8221fa461898517"), - null, - null, - null, - Wei.fromHexString("0x3B9ACA01"), - null, - Bytes.fromHexString("0x2e64cec1"), - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) + .withTo(Address.fromHexString("0x9b8397f1b0fecd3a1a40cdd5e8221fa461898517")) + .withMaxFeePerGas(Wei.fromHexString("0x3B9ACA01")) + .withInput(Bytes.fromHexString("0x2e64cec1")) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse( @@ -187,18 +161,14 @@ public void shouldReturnSuccessWithValidMaxFeePerGas() { @Test public void shouldReturnSuccessWithValidMaxFeePerGasAndMaxPriorityFeePerGas() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"), - Address.fromHexString("0x9b8397f1b0fecd3a1a40cdd5e8221fa461898517"), - null, - null, - Wei.fromHexString("0x3B9ACA00"), - Wei.fromHexString("0x3B9ACA01"), - null, - Bytes.fromHexString("0x2e64cec1"), - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) + .withTo(Address.fromHexString("0x9b8397f1b0fecd3a1a40cdd5e8221fa461898517")) + .withMaxPriorityFeePerGas(Wei.fromHexString("0x3B9ACA00")) + .withMaxFeePerGas(Wei.fromHexString("0x3B9ACA01")) + .withInput(Bytes.fromHexString("0x2e64cec1")) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse( @@ -212,18 +182,13 @@ public void shouldReturnSuccessWithValidMaxFeePerGasAndMaxPriorityFeePerGas() { @Test public void shouldReturnErrorWithValidMaxFeePerGasLessThanCurrentBaseFee() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"), - Address.fromHexString("0x9b8397f1b0fecd3a1a40cdd5e8221fa461898517"), - null, - null, - null, - Wei.fromHexString("0x0A"), - null, - Bytes.fromHexString("0x2e64cec1"), - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) + .withTo(Address.fromHexString("0x9b8397f1b0fecd3a1a40cdd5e8221fa461898517")) + .withMaxFeePerGas(Wei.fromHexString("0x0A")) + .withInput(Bytes.fromHexString("0x2e64cec1")) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.GAS_PRICE_BELOW_CURRENT_BASE_FEE); @@ -236,18 +201,14 @@ public void shouldReturnErrorWithValidMaxFeePerGasLessThanCurrentBaseFee() { @Test public void shouldReturnErrorWithValidMaxFeePerGasLessThanMaxPriorityFeePerGas() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"), - Address.fromHexString("0x9b8397f1b0fecd3a1a40cdd5e8221fa461898517"), - null, - null, - Wei.fromHexString("0x3B9ACA02"), - Wei.fromHexString("0x3B9ACA01"), - null, - Bytes.fromHexString("0x2e64cec1"), - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) + .withTo(Address.fromHexString("0x9b8397f1b0fecd3a1a40cdd5e8221fa461898517")) + .withMaxPriorityFeePerGas(Wei.fromHexString("0x3B9ACA02")) + .withMaxFeePerGas(Wei.fromHexString("0x3B9ACA01")) + .withInput(Bytes.fromHexString("0x2e64cec1")) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse( @@ -261,18 +222,13 @@ public void shouldReturnErrorWithValidMaxFeePerGasLessThanMaxPriorityFeePerGas() @Test public void shouldReturnErrorWithMaxFeePerGasAndEmptyBalance() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xdeadbeef00000000000000000000000000000000"), - Address.fromHexString("0x9b8397f1b0fecd3a1a40cdd5e8221fa461898517"), - null, - null, - null, - Wei.fromHexString("0x3B9ACA01"), - null, - Bytes.fromHexString("0x2e64cec1"), - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xdeadbeef00000000000000000000000000000000")) + .withTo(Address.fromHexString("0x9b8397f1b0fecd3a1a40cdd5e8221fa461898517")) + .withMaxFeePerGas(Wei.fromHexString("0x3B9ACA01")) + .withInput(Bytes.fromHexString("0x2e64cec1")) + .build(); + final JsonRpcRequestContext request = requestWithParams(callParameter, "latest"); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE); diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthEstimateGasIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthEstimateGasIntegrationTest.java index e34466a644a..84447dfc9d3 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthEstimateGasIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthEstimateGasIntegrationTest.java @@ -67,18 +67,11 @@ public void setUp() { @Test public void shouldReturnExpectedValueForTransfer() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"), - Address.fromHexString("0x8888f1f195afa192cfee860698584c030f4c9db1"), - null, - null, - null, - null, - Wei.ONE, - null, - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) + .withTo(Address.fromHexString("0x8888f1f195afa192cfee860698584c030f4c9db1")) + .withValue(Wei.ONE) + .build(); final JsonRpcResponse response = method.response(requestWithParams(callParameter)); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, "0x5208"); @@ -87,20 +80,13 @@ public void shouldReturnExpectedValueForTransfer() { @Test public void shouldReturnExpectedValueForTransfer_WithAccessList() { - final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"), - Address.fromHexString("0x8888f1f195afa192cfee860698584c030f4c9db1"), - null, - null, - null, - null, - Wei.ONE, - null, - null, - null, - createAccessList()); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) + .withTo(Address.fromHexString("0x8888f1f195afa192cfee860698584c030f4c9db1")) + .withValue(Wei.ONE) + .withAccessList(createAccessList()) + .build(); final JsonRpcResponse response = method.response(requestWithParams(callParameter)); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, "0x62d4"); @@ -110,19 +96,13 @@ public void shouldReturnExpectedValueForTransfer_WithAccessList() { @Test public void shouldReturnExpectedValueForContractDeploy() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"), - null, - null, - null, - null, - null, - null, - Bytes.fromHexString( - "0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029"), - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) + .withInput( + Bytes.fromHexString( + "0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029")) + .build(); + final JsonRpcResponse response = method.response(requestWithParams(callParameter)); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, "0x1f081"); assertThat(response).usingRecursiveComparison().isEqualTo(expectedResponse); @@ -131,19 +111,13 @@ public void shouldReturnExpectedValueForContractDeploy() { @Test public void shouldReturnExpectedValueForContractDeploy_WithAccessList() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"), - null, - null, - null, - null, - null, - null, - Bytes.fromHexString( - "0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029"), - null, - null, - createAccessList()); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")) + .withInput( + Bytes.fromHexString( + "0x608060405234801561001057600080fd5b50610157806100206000396000f30060806040526004361061004c576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633bdab8bf146100515780639ae97baa14610068575b600080fd5b34801561005d57600080fd5b5061006661007f565b005b34801561007457600080fd5b5061007d6100b9565b005b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60016040518082815260200191505060405180910390a1565b7fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60026040518082815260200191505060405180910390a17fa53887c1eed04528e23301f55ad49a91634ef5021aa83a97d07fd16ed71c039a60036040518082815260200191505060405180910390a15600a165627a7a7230582010ddaa52e73a98c06dbcd22b234b97206c1d7ed64a7c048e10c2043a3d2309cb0029")) + .withAccessList(createAccessList()) + .build(); final JsonRpcResponse response = method.response(requestWithParams(callParameter)); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, "0x2014d"); diff --git a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthGetFilterChangesIntegrationTest.java b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthGetFilterChangesIntegrationTest.java index 0026c56ad84..0c194ad22f2 100644 --- a/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthGetFilterChangesIntegrationTest.java +++ b/ethereum/api/src/integration-test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/fork/london/EthGetFilterChangesIntegrationTest.java @@ -18,6 +18,7 @@ import static java.util.Collections.emptyList; import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -44,10 +45,12 @@ import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.ExecutionContextTestFixture; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthPeers; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions; @@ -107,7 +110,7 @@ public void setUp() { blockchain::getChainHeadHeader); final ProtocolContext protocolContext = executionContext.getProtocolContext(); - EthContext ethContext = mock(EthContext.class); + EthContext ethContext = mock(EthContext.class, RETURNS_DEEP_STUBS); EthPeers ethPeers = mock(EthPeers.class); when(ethContext.getEthPeers()).thenReturn(ethPeers); @@ -120,10 +123,14 @@ public void setUp() { ethContext, new TransactionPoolMetrics(metricsSystem), TransactionPoolConfiguration.DEFAULT, - null); + new BlobCache()); transactionPool.setEnabled(); final BlockchainQueries blockchainQueries = - new BlockchainQueries(blockchain, protocolContext.getWorldStateArchive()); + new BlockchainQueries( + executionContext.getProtocolSchedule(), + blockchain, + protocolContext.getWorldStateArchive(), + MiningParameters.newDefault()); filterManager = new FilterManagerBuilder() .blockchainQueries(blockchainQueries) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/ApiConfiguration.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/ApiConfiguration.java index b88f374aa3f..e317845f841 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/ApiConfiguration.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/ApiConfiguration.java @@ -11,52 +11,134 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.api; -import java.util.function.LongSupplier; +import org.hyperledger.besu.datatypes.Wei; import org.immutables.value.Value; +/** + * The ApiConfiguration class provides configuration for the API. It includes default values for gas + * price, max logs range, gas cap, and other parameters. + */ @Value.Immutable @Value.Style(allParameters = true) public abstract class ApiConfiguration { + /** + * The default lower bound coefficient for gas and priority fee. This value is used as the default + * lower bound when calculating the gas and priority fee. + */ + public static final long DEFAULT_LOWER_BOUND_GAS_AND_PRIORITY_FEE_COEFFICIENT = 0L; + + /** + * The default upper bound coefficient for gas and priority fee. This value is used as the default + * upper bound when calculating the gas and priority fee. + */ + public static final long DEFAULT_UPPER_BOUND_GAS_AND_PRIORITY_FEE_COEFFICIENT = Long.MAX_VALUE; + + /** Constructs a new ApiConfiguration with default values. */ + protected ApiConfiguration() {} + + /** + * Returns the number of blocks to consider for gas price calculations. Default value is 100. + * + * @return the number of blocks for gas price calculations + */ @Value.Default public long getGasPriceBlocks() { return 100; } + /** + * Returns the percentile to use for gas price calculations. Default value is 50.0. + * + * @return the percentile for gas price calculations + */ @Value.Default public double getGasPricePercentile() { return 50.0d; } + /** + * Returns the maximum gas price. Default value is 500 GWei. + * + * @return the maximum gas price + */ @Value.Default - @Value.Auxiliary - public LongSupplier getGasPriceMinSupplier() { - return () -> 1_000_000_000L; // 1 GWei - } - - @Value.Default - public long getGasPriceMax() { - return 500_000_000_000L; // 500 GWei + public Wei getGasPriceMax() { + return Wei.of(500_000_000_000L); // 500 GWei } + /** + * Returns the fraction to use for gas price calculations. This is derived from the gas price + * percentile. + * + * @return the fraction for gas price calculations + */ @Value.Derived public double getGasPriceFraction() { return getGasPricePercentile() / 100.0; } + /** + * Returns the maximum range for logs. Default value is 5000. + * + * @return the maximum range for logs + */ @Value.Default public Long getMaxLogsRange() { return 5000L; } + /** + * Returns the gas cap. Default value is 0. + * + * @return the gas cap + */ @Value.Default public Long getGasCap() { return 0L; } + + /** + * Returns whether gas and priority fee limiting is enabled. Default value is false. + * + * @return true if gas and priority fee limiting is enabled, false otherwise + */ + @Value.Default + public boolean isGasAndPriorityFeeLimitingEnabled() { + return false; + } + + /** + * Returns the lower bound coefficient for gas and priority fee. Default value is 0. + * + * @return the lower bound coefficient for gas and priority fee + */ + @Value.Default + public Long getLowerBoundGasAndPriorityFeeCoefficient() { + return DEFAULT_LOWER_BOUND_GAS_AND_PRIORITY_FEE_COEFFICIENT; + } + + /** + * Returns the upper bound coefficient for gas and priority fee. Default value is Long.MAX_VALUE. + * + * @return the upper bound coefficient for gas and priority fee + */ + @Value.Default + public Long getUpperBoundGasAndPriorityFeeCoefficient() { + return DEFAULT_UPPER_BOUND_GAS_AND_PRIORITY_FEE_COEFFICIENT; + } + + /** + * Returns the maximum range for trace filter. Default value is 1000. + * + * @return the maximum range for trace filter + */ + @Value.Default + public Long getMaxTraceFilterRange() { + return 1000L; + } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLConfiguration.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLConfiguration.java index 8c32b552889..a2829edb071 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLConfiguration.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLConfiguration.java @@ -26,17 +26,33 @@ import com.google.common.base.MoreObjects; +/** + * Represents the configuration for GraphQL. This class is used to set and get the configuration + * details for GraphQL such as enabling GraphQL, setting the port and host, setting the allowed + * domains for CORS, setting the hosts allowlist, and setting the HTTP timeout. + */ public class GraphQLConfiguration { private static final String DEFAULT_GRAPHQL_HTTP_HOST = "127.0.0.1"; + + /** The default port number for the GraphQL HTTP server. */ public static final int DEFAULT_GRAPHQL_HTTP_PORT = 8547; private boolean enabled; private int port; private String host; private List corsAllowedDomains = Collections.emptyList(); - private List hostsAllowlist = Arrays.asList("localhost", "127.0.0.1"); + private List hostsAllowlist = Arrays.asList("localhost", DEFAULT_GRAPHQL_HTTP_HOST); private long httpTimeoutSec = TimeoutOptions.defaultOptions().getTimeoutSeconds(); + /** + * Creates a default configuration for GraphQL. + * + *

This method initializes a new GraphQLConfiguration object with default settings. The default + * settings are: - GraphQL is not enabled - The port is set to the default GraphQL HTTP port - The + * host is set to the default GraphQL HTTP host - The HTTP timeout is set to the default timeout + * + * @return a GraphQLConfiguration object with default settings + */ public static GraphQLConfiguration createDefault() { final GraphQLConfiguration config = new GraphQLConfiguration(); config.setEnabled(false); @@ -48,52 +64,112 @@ public static GraphQLConfiguration createDefault() { private GraphQLConfiguration() {} + /** + * Checks if GraphQL is enabled. + * + * @return true if GraphQL is enabled, false otherwise + */ public boolean isEnabled() { return enabled; } + /** + * Sets the enabled status of GraphQL. + * + * @param enabled the status to set. true to enable GraphQL, false to disable it + */ public void setEnabled(final boolean enabled) { this.enabled = enabled; } + /** + * Retrieves the port number for the GraphQL HTTP server. + * + * @return the port number + */ public int getPort() { return port; } + /** + * Sets the port number for the GraphQL HTTP server. + * + * @param port the port number to set + */ public void setPort(final int port) { this.port = port; } + /** + * Retrieves the host for the GraphQL HTTP server. + * + * @return the host + */ public String getHost() { return host; } + /** + * Sets the host for the GraphQL HTTP server. + * + * @param host the host to set + */ public void setHost(final String host) { this.host = host; } + /** + * Retrieves the allowed domains for CORS. + * + * @return a collection of allowed domains for CORS + */ Collection getCorsAllowedDomains() { return corsAllowedDomains; } + /** + * Sets the allowed domains for CORS. + * + * @param corsAllowedDomains a list of allowed domains for CORS + */ public void setCorsAllowedDomains(final List corsAllowedDomains) { checkNotNull(corsAllowedDomains); this.corsAllowedDomains = corsAllowedDomains; } + /** + * Retrieves the hosts allowlist. + * + * @return a collection of hosts in the allowlist + */ Collection getHostsAllowlist() { return Collections.unmodifiableCollection(this.hostsAllowlist); } + /** + * Sets the hosts allowlist. + * + * @param hostsAllowlist a list of hosts to be added to the allowlist + */ public void setHostsAllowlist(final List hostsAllowlist) { checkNotNull(hostsAllowlist); this.hostsAllowlist = hostsAllowlist; } + /** + * Retrieves the HTTP timeout in seconds. + * + * @return the HTTP timeout in seconds + */ public Long getHttpTimeoutSec() { return httpTimeoutSec; } + /** + * Sets the HTTP timeout in seconds. + * + * @param httpTimeoutSec the HTTP timeout to set in seconds + */ public void setHttpTimeoutSec(final long httpTimeoutSec) { this.httpTimeoutSec = httpTimeoutSec; } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLContextType.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLContextType.java index d7975335657..c12f7578c6c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLContextType.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLContextType.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,14 +14,33 @@ */ package org.hyperledger.besu.ethereum.api.graphql; -/** Internal GraphQL Context */ +/** + * Enum representing various context types for GraphQL. + * + *

These context types are used internally by GraphQL to manage different aspects of the system. + */ public enum GraphQLContextType { + /** Represents blockchain queries context. */ BLOCKCHAIN_QUERIES, + + /** Represents protocol schedule context. */ PROTOCOL_SCHEDULE, + + /** Represents transaction pool context. */ TRANSACTION_POOL, + + /** Represents mining coordinator context. */ MINING_COORDINATOR, + + /** Represents synchronizer context. */ SYNCHRONIZER, + + /** Represents is alive handler context. */ IS_ALIVE_HANDLER, + + /** Represents chain ID context. */ CHAIN_ID, + + /** Represents gas cap context. */ GAS_CAP } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLDataFetcherContext.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLDataFetcherContext.java index 0ccd8d2b1e1..acebe95d119 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLDataFetcherContext.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLDataFetcherContext.java @@ -21,18 +21,56 @@ import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +/** + * Interface representing the context for a GraphQL data fetcher. + * + *

This context provides access to various components of the system such as the transaction pool, + * blockchain queries, mining coordinator, synchronizer, and protocol schedule. + */ public interface GraphQLDataFetcherContext { + /** + * Retrieves the transaction pool. + * + * @return the transaction pool + */ TransactionPool getTransactionPool(); + /** + * Retrieves the blockchain queries. + * + * @return the blockchain queries + */ BlockchainQueries getBlockchainQueries(); + /** + * Retrieves the mining coordinator. + * + * @return the mining coordinator + */ MiningCoordinator getMiningCoordinator(); + /** + * Retrieves the synchronizer. + * + * @return the synchronizer + */ Synchronizer getSynchronizer(); + /** + * Retrieves the protocol schedule. + * + * @return the protocol schedule + */ ProtocolSchedule getProtocolSchedule(); + /** + * Retrieves the is alive handler. + * + *

By default, this method returns a new IsAliveHandler instance with a status of true. + * + * @return the is alive handler + */ default IsAliveHandler getIsAliveHandler() { return new IsAliveHandler(true); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLDataFetcherContextImpl.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLDataFetcherContextImpl.java index 0e426ebf3ab..1f5ed82d749 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLDataFetcherContextImpl.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLDataFetcherContextImpl.java @@ -21,6 +21,12 @@ import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +/** + * Implementation of the GraphQLDataFetcherContext interface. + * + *

This class provides access to various components of the system such as the transaction pool, + * blockchain queries, mining coordinator, synchronizer, and protocol schedule. + */ public class GraphQLDataFetcherContextImpl implements GraphQLDataFetcherContext { private final BlockchainQueries blockchainQueries; @@ -30,6 +36,12 @@ public class GraphQLDataFetcherContextImpl implements GraphQLDataFetcherContext private final TransactionPool transactionPool; private final IsAliveHandler isAliveHandler; + /** + * Constructor that takes a GraphQLDataFetcherContext and an IsAliveHandler. + * + * @param context the GraphQLDataFetcherContext + * @param isAliveHandler the IsAliveHandler + */ public GraphQLDataFetcherContextImpl( final GraphQLDataFetcherContext context, final IsAliveHandler isAliveHandler) { this( @@ -41,6 +53,16 @@ public GraphQLDataFetcherContextImpl( isAliveHandler); } + /** + * Constructor that takes a BlockchainQueries, ProtocolSchedule, TransactionPool, + * MiningCoordinator, and Synchronizer. + * + * @param blockchainQueries the BlockchainQueries + * @param protocolSchedule the ProtocolSchedule + * @param transactionPool the TransactionPool + * @param miningCoordinator the MiningCoordinator + * @param synchronizer the Synchronizer + */ public GraphQLDataFetcherContextImpl( final BlockchainQueries blockchainQueries, final ProtocolSchedule protocolSchedule, @@ -56,6 +78,17 @@ public GraphQLDataFetcherContextImpl( new IsAliveHandler(true)); } + /** + * Constructor that takes a BlockchainQueries, ProtocolSchedule, TransactionPool, + * MiningCoordinator, Synchronizer, and IsAliveHandler. + * + * @param blockchainQueries the BlockchainQueries + * @param protocolSchedule the ProtocolSchedule + * @param transactionPool the TransactionPool + * @param miningCoordinator the MiningCoordinator + * @param synchronizer the Synchronizer + * @param isAliveHandler the IsAliveHandler + */ public GraphQLDataFetcherContextImpl( final BlockchainQueries blockchainQueries, final ProtocolSchedule protocolSchedule, diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLDataFetchers.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLDataFetchers.java index fccb0cb0110..d4338d58c34 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLDataFetchers.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLDataFetchers.java @@ -31,7 +31,6 @@ import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.api.query.LogsQuery; import org.hyperledger.besu.ethereum.api.query.TransactionWithMetadata; -import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator; import org.hyperledger.besu.ethereum.core.LogWithMetadata; import org.hyperledger.besu.ethereum.core.Synchronizer; import org.hyperledger.besu.ethereum.core.Transaction; @@ -62,10 +61,34 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; +/** + * This class contains data fetchers for GraphQL queries. + * + *

Data fetchers are responsible for fetching data for a specific field. Each field in the schema + * is associated with a data fetcher. When the field is being processed during a query, the + * associated data fetcher is invoked to get the data for that field. + * + *

This class contains data fetchers for various fields such as protocol version, syncing state, + * pending state, gas price, chain ID, max priority fee per gas, range block, block, account, logs, + * and transaction. + * + *

Each data fetcher is a method that returns a `DataFetcher` object. The `DataFetcher` object + * defines how to fetch the data for the field. It takes a `DataFetchingEnvironment` object as input + * which contains all the context needed to fetch the data. + */ public class GraphQLDataFetchers { private final Integer highestEthVersion; + /** + * Constructs a new GraphQLDataFetchers instance. + * + *

This constructor takes a set of supported capabilities and determines the highest Ethereum + * protocol version supported by these capabilities. This version is then stored and can be + * fetched using the getProtocolVersionDataFetcher method. + * + * @param supportedCapabilities a set of capabilities supported by the Ethereum node + */ public GraphQLDataFetchers(final Set supportedCapabilities) { final OptionalInt version = supportedCapabilities.stream() @@ -75,10 +98,30 @@ public GraphQLDataFetchers(final Set supportedCapabilities) { highestEthVersion = version.isPresent() ? version.getAsInt() : null; } + /** + * Returns a DataFetcher that fetches the highest Ethereum protocol version supported by the node. + * + *

The DataFetcher is a functional interface. It has a single method that takes a + * DataFetchingEnvironment object as input and returns the highest Ethereum protocol version as an + * Optional. + * + * @return a DataFetcher that fetches the highest Ethereum protocol version + */ DataFetcher> getProtocolVersionDataFetcher() { return dataFetchingEnvironment -> Optional.of(highestEthVersion); } + /** + * Returns a DataFetcher that fetches the result of sending a raw transaction. + * + *

The DataFetcher is a functional interface. It has a single method that takes a + * DataFetchingEnvironment object as input and returns the hash of the transaction if it is valid + * and added to the transaction pool. If the transaction is invalid, it throws a GraphQLException + * with the invalid reason. If the raw transaction data cannot be read, it throws a + * GraphQLException with INVALID_PARAMS error. + * + * @return a DataFetcher that fetches the result of sending a raw transaction + */ DataFetcher> getSendRawTransactionDataFetcher() { return dataFetchingEnvironment -> { try { @@ -100,6 +143,19 @@ DataFetcher> getSendRawTransactionDataFetcher() { }; } + /** + * Returns a DataFetcher that fetches the syncing state of the Ethereum node. + * + *

The DataFetcher is a functional interface. It has a single method that takes a + * DataFetchingEnvironment object as input and returns the syncing state as an + * Optional. + * + *

The SyncStateAdapter is a wrapper around the SyncStatus of the Ethereum node. It provides + * information about the current syncing state of the node such as the current block, highest + * block, and starting block. + * + * @return a DataFetcher that fetches the syncing state of the Ethereum node + */ DataFetcher> getSyncingDataFetcher() { return dataFetchingEnvironment -> { final Synchronizer synchronizer = @@ -117,20 +173,24 @@ DataFetcher> getPendingStateDataFetcher() { }; } - DataFetcher> getGasPriceDataFetcher() { + DataFetcher getGasPriceDataFetcher() { return dataFetchingEnvironment -> { final GraphQLContext graphQLContext = dataFetchingEnvironment.getGraphQlContext(); final BlockchainQueries blockchainQueries = graphQLContext.get(GraphQLContextType.BLOCKCHAIN_QUERIES); - final MiningCoordinator miningCoordinator = - graphQLContext.get(GraphQLContextType.MINING_COORDINATOR); - return blockchainQueries - .gasPrice() - .map(Wei::of) - .or(() -> Optional.of(miningCoordinator.getMinTransactionGasPrice())); + return blockchainQueries.gasPrice(); }; } + /** + * Returns a DataFetcher that fetches the chain ID of the Ethereum node. + * + *

The DataFetcher is a functional interface. It has a single method that takes a + * DataFetchingEnvironment object as input and returns the chain ID as an {@code + * Optional}. + * + * @return a DataFetcher that fetches the chain ID of the Ethereum node + */ public DataFetcher> getChainIdDataFetcher() { return dataFetchingEnvironment -> { final GraphQLContext graphQLContext = dataFetchingEnvironment.getGraphQlContext(); @@ -138,11 +198,20 @@ public DataFetcher> getChainIdDataFetcher() { }; } + /** + * Returns a DataFetcher that fetches the maximum priority fee per gas of the Ethereum node. + * + *

The DataFetcher is a functional interface. It has a single method that takes a + * DataFetchingEnvironment object as input and returns the maximum priority fee per gas as a Wei + * object. + * + * @return a DataFetcher that fetches the maximum priority fee per gas of the Ethereum node + */ public DataFetcher getMaxPriorityFeePerGasDataFetcher() { return dataFetchingEnvironment -> { final BlockchainQueries blockchainQuery = dataFetchingEnvironment.getGraphQlContext().get(GraphQLContextType.BLOCKCHAIN_QUERIES); - return blockchainQuery.gasPriorityFee().orElse(Wei.ZERO); + return blockchainQuery.gasPriorityFee(); }; } @@ -173,6 +242,20 @@ DataFetcher> getRangeBlockDataFetcher() { }; } + /** + * Returns a DataFetcher that fetches a specific block in the Ethereum blockchain. + * + *

The DataFetcher is a functional interface. It has a single method that takes a + * DataFetchingEnvironment object as input. This method fetches a block based on either a block + * number or a block hash. If both a block number and a block hash are provided, it throws a + * GraphQLException with INVALID_PARAMS error. If neither a block number nor a block hash is + * provided, it fetches the latest block. + * + *

The fetched block is then wrapped in a {@link NormalBlockAdapter} and returned as an {@code + * Optional}. + * + * @return a DataFetcher that fetches a specific block in the Ethereum blockchain + */ public DataFetcher> getBlockDataFetcher() { return dataFetchingEnvironment -> { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLHttpService.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLHttpService.java index 03cf1cb7a31..fc763a1e3ba 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLHttpService.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLHttpService.java @@ -15,7 +15,6 @@ package org.hyperledger.besu.ethereum.api.graphql; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.collect.Streams.stream; import static io.vertx.core.http.HttpMethod.GET; import static io.vertx.core.http.HttpMethod.POST; @@ -44,8 +43,6 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Splitter; -import com.google.common.collect.Iterables; import com.google.common.net.MediaType; import graphql.ExecutionInput; import graphql.ExecutionResult; @@ -62,6 +59,7 @@ import io.vertx.core.json.DecodeException; import io.vertx.core.json.Json; import io.vertx.core.json.jackson.JacksonCodec; +import io.vertx.core.net.HostAndPort; import io.vertx.ext.web.Router; import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.handler.BodyHandler; @@ -70,6 +68,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * This class handles the HTTP service for GraphQL. It sets up the server, handles requests and + * responses, and manages the lifecycle of the server. + * + *

It is responsible for processing GraphQL requests, executing them using the provided GraphQL + * engine, and returning the results in the HTTP response. + * + *

It also handles errors and exceptions that may occur during the processing of a request. + */ public class GraphQLHttpService { private static final Logger LOG = LoggerFactory.getLogger(GraphQLHttpService.class); @@ -128,6 +135,15 @@ private void validateConfig(final GraphQLConfiguration config) { checkArgument(config.getHost() != null, "Required host is not configured."); } + /** + * Starts the GraphQL HTTP service. + * + *

This method initializes the HTTP server and sets up the necessary routes for handling + * GraphQL requests. It also validates the configuration and sets up the necessary handlers for + * different types of requests. + * + * @return a CompletableFuture that will be completed when the server is successfully started. + */ public CompletableFuture start() { LOG.info("Starting GraphQL HTTP service on {}:{}", config.getHost(), config.getPort()); // Create the HTTP server and a router object. @@ -143,7 +159,7 @@ public CompletableFuture start() { final Router router = Router.router(vertx); // Verify Host header to avoid rebind attack. - router.route().handler(checkWhitelistHostHeader()); + router.route().handler(checkAllowlistHostHeader()); router .route() @@ -199,7 +215,7 @@ public CompletableFuture start() { return resultFuture; } - private Handler checkWhitelistHostHeader() { + private Handler checkAllowlistHostHeader() { return event -> { final Optional hostHeader = getAndValidateHostHeader(event); if (config.getHostsAllowlist().contains("*") @@ -218,25 +234,13 @@ private Handler checkWhitelistHostHeader() { } private Optional getAndValidateHostHeader(final RoutingContext event) { - String hostname = - event.request().getHeader(HttpHeaders.HOST) != null - ? event.request().getHeader(HttpHeaders.HOST) - : event.request().host(); - final Iterable splitHostHeader = Splitter.on(':').split(hostname); - final long hostPieces = stream(splitHostHeader).count(); - if (hostPieces > 1) { - // If the host contains a colon, verify the host is correctly formed - host [ ":" port ] - if (hostPieces > 2 || !Iterables.get(splitHostHeader, 1).matches("\\d{1,5}+")) { - return Optional.empty(); - } - } - return Optional.ofNullable(Iterables.get(splitHostHeader, 0)); + final HostAndPort hostAndPort = event.request().authority(); + return Optional.ofNullable(hostAndPort).map(HostAndPort::host); } private boolean hostIsInAllowlist(final String hostHeader) { if (config.getHostsAllowlist().stream() - .anyMatch( - allowlistEntry -> allowlistEntry.toLowerCase().equals(hostHeader.toLowerCase()))) { + .anyMatch(allowlistEntry -> allowlistEntry.equalsIgnoreCase(hostHeader))) { return true; } else { LOG.trace("Host not in allowlist: '{}'", hostHeader); @@ -244,6 +248,14 @@ private boolean hostIsInAllowlist(final String hostHeader) { } } + /** + * Stops the GraphQL HTTP service. + * + *

This method stops the HTTP server that was created and started by the start() method. If the + * server is not running, this method will do nothing. + * + * @return a CompletableFuture that will be completed when the server is successfully stopped. + */ public CompletableFuture stop() { if (httpServer == null) { return CompletableFuture.completedFuture(null); @@ -262,6 +274,15 @@ public CompletableFuture stop() { return resultFuture; } + /** + * Returns the socket address of the GraphQL HTTP service. + * + *

This method returns the socket address that the HTTP server is bound to. If the server is + * not running, it returns an empty socket address. + * + * @return the socket address of the HTTP server, or an empty socket address if the server is not + * running. + */ public InetSocketAddress socketAddress() { if (httpServer == null) { return EMPTY_SOCKET_ADDRESS; @@ -269,6 +290,14 @@ public InetSocketAddress socketAddress() { return new InetSocketAddress(config.getHost(), httpServer.actualPort()); } + /** + * Returns the URL of the GraphQL HTTP service. + * + *

This method constructs and returns the URL that the HTTP server is bound to. If the server + * is not running, it returns an empty string. + * + * @return the URL of the HTTP server, or an empty string if the server is not running. + */ @VisibleForTesting public String url() { if (httpServer == null) { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLProvider.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLProvider.java index 7e62e39c057..2a3370572b4 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLProvider.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLProvider.java @@ -31,12 +31,30 @@ import graphql.schema.idl.TypeDefinitionRegistry; import graphql.schema.idl.TypeRuntimeWiring; +/** + * This class provides the GraphQL service. + * + *

It contains a method to build the GraphQL service with the provided data fetchers. + */ public class GraphQLProvider { + /** + * The maximum complexity allowed for a GraphQL query. + * + *

This constant is used to prevent overly complex queries from being executed. A query's + * complexity is calculated based on the number and type of fields it contains. + */ public static final int MAX_COMPLEXITY = 200; private GraphQLProvider() {} + /** + * Builds the GraphQL service with the provided data fetchers. + * + * @param graphQLDataFetchers the data fetchers to be used in the GraphQL service. + * @return the built GraphQL service. + * @throws IOException if there is an error reading the schema file. + */ public static GraphQL buildGraphQL(final GraphQLDataFetchers graphQLDataFetchers) throws IOException { final URL url = Resources.getResource("schema.graphqls"); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/Scalars.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/Scalars.java index bab7a278f15..9fbeae5fde5 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/Scalars.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/Scalars.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.api.graphql.internal; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.VersionedHash; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity; import java.math.BigInteger; @@ -34,6 +35,13 @@ import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; +/** + * The Scalars class provides methods for creating GraphQLScalarType objects. These objects + * represent the scalar types used in GraphQL, such as Address, BigInt, Bytes, Bytes32, and Long. + * Each method in this class returns a GraphQLScalarType object that has been configured with a + * specific Coercing implementation. The Coercing implementation defines how that type is + * serialized, deserialized and validated. + */ public class Scalars { private Scalars() {} @@ -256,6 +264,8 @@ Bytes32 convertImpl(final Object input) { return null; } } + } else if (input instanceof VersionedHash versionedHash) { + return versionedHash.toBytes(); } else { return null; } @@ -327,7 +337,7 @@ public Number parseValue( if (input instanceof Number number) { return number; } else if (input instanceof String string) { - final String value = string.toLowerCase(); + final String value = string.toLowerCase(Locale.ROOT); if (value.startsWith("0x")) { return Bytes.fromHexStringLenient(value).toLong(); } else { @@ -349,7 +359,7 @@ public Number parseLiteral( if (input instanceof IntValue intValue) { return intValue.getValue().longValue(); } else if (input instanceof StringValue stringValue) { - final String value = stringValue.getValue().toLowerCase(); + final String value = stringValue.getValue().toLowerCase(Locale.ROOT); if (value.startsWith("0x")) { return Bytes.fromHexStringLenient(value).toLong(); } else { @@ -363,6 +373,14 @@ public Number parseLiteral( } }; + /** + * Creates a new GraphQLScalarType object for an Address. + * + *

The object is configured with a specific Coercing implementation that defines how the + * Address type is serialized, deserialized and validated. + * + * @return a GraphQLScalarType object for an Address. + */ public static GraphQLScalarType addressScalar() { return GraphQLScalarType.newScalar() .name("Address") @@ -371,6 +389,14 @@ public static GraphQLScalarType addressScalar() { .build(); } + /** + * Creates a new GraphQLScalarType object for a BigInt. + * + *

The object is configured with a specific Coercing implementation that defines how the BigInt + * type is serialized, deserialized and validated. + * + * @return a GraphQLScalarType object for a BigInt. + */ public static GraphQLScalarType bigIntScalar() { return GraphQLScalarType.newScalar() .name("BigInt") @@ -379,6 +405,14 @@ public static GraphQLScalarType bigIntScalar() { .build(); } + /** + * Creates a new GraphQLScalarType object for Bytes. + * + *

The object is configured with a specific Coercing implementation that defines how the Bytes + * type is serialized, deserialized and validated. + * + * @return a GraphQLScalarType object for Bytes. + */ public static GraphQLScalarType bytesScalar() { return GraphQLScalarType.newScalar() .name("Bytes") @@ -387,6 +421,14 @@ public static GraphQLScalarType bytesScalar() { .build(); } + /** + * Creates a new GraphQLScalarType object for Bytes32. + * + *

The object is configured with a specific Coercing implementation that defines how the + * Bytes32 type is serialized, deserialized and validated. + * + * @return a GraphQLScalarType object for Bytes32. + */ public static GraphQLScalarType bytes32Scalar() { return GraphQLScalarType.newScalar() .name("Bytes32") @@ -395,6 +437,14 @@ public static GraphQLScalarType bytes32Scalar() { .build(); } + /** + * Creates a new GraphQLScalarType object for a Long. + * + *

The object is configured with a specific Coercing implementation that defines how the Long + * type is serialized, deserialized and validated. + * + * @return a GraphQLScalarType object for a Long. + */ public static GraphQLScalarType longScalar() { return GraphQLScalarType.newScalar() .name("Long") diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/AccessListEntryAdapter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/AccessListEntryAdapter.java index dca799ce59d..a0dc8743c8e 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/AccessListEntryAdapter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/AccessListEntryAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -23,19 +23,40 @@ import org.apache.tuweni.bytes.Bytes32; +/** + * The AccessListEntryAdapter class extends the AdapterBase class. It provides methods to get the + * storage keys and address from an AccessListEntry. + */ @SuppressWarnings("unused") // reflected by GraphQL public class AccessListEntryAdapter extends AdapterBase { + + /** The AccessListEntry object that this adapter wraps. */ private final AccessListEntry accessListEntry; + /** + * Constructs a new AccessListEntryAdapter with the given AccessListEntry. + * + * @param accessListEntry the AccessListEntry to be adapted + */ public AccessListEntryAdapter(final AccessListEntry accessListEntry) { this.accessListEntry = accessListEntry; } + /** + * Returns the storage keys from the AccessListEntry. + * + * @return a list of storage keys + */ public List getStorageKeys() { final var storage = accessListEntry.storageKeys(); return new ArrayList<>(storage); } + /** + * Returns the address from the AccessListEntry. + * + * @return an Optional containing the address if it exists, otherwise an empty Optional + */ public Optional

getAddress() { return Optional.of(accessListEntry.address()); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/AccountAdapter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/AccountAdapter.java index f9098089e42..1b4c8323b64 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/AccountAdapter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/AccountAdapter.java @@ -17,7 +17,7 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; -import org.hyperledger.besu.ethereum.bonsai.BonsaiAccount; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.BonsaiAccount; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.account.AccountState; @@ -28,6 +28,10 @@ import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; +/** + * The AccountAdapter class extends the AdapterBase class. It provides methods to get the account + * details such as address, balance, transaction count, code, and storage. + */ @SuppressWarnings("unused") // reflected by GraphQL public class AccountAdapter extends AdapterBase { @@ -35,18 +39,42 @@ public class AccountAdapter extends AdapterBase { private final Address address; private final Optional blockNumber; + /** + * Constructs a new AccountAdapter with the given Account. + * + * @param account the Account to be adapted + */ public AccountAdapter(final Account account) { this(account == null ? null : account.getAddress(), account, Optional.empty()); } + /** + * Constructs a new AccountAdapter with the given Account and block number. + * + * @param account the Account to be adapted + * @param blockNumber the block number associated with the account + */ public AccountAdapter(final Account account, final Optional blockNumber) { this(account == null ? null : account.getAddress(), account, blockNumber); } + /** + * Constructs a new AccountAdapter with the given address and Account. + * + * @param address the address of the account + * @param account the Account to be adapted + */ public AccountAdapter(final Address address, final Account account) { this(address, account, Optional.empty()); } + /** + * Constructs a new AccountAdapter with the given address, Account, and block number. + * + * @param address the address of the account + * @param account the Account to be adapted + * @param blockNumber the block number associated with the account + */ public AccountAdapter( final Address address, final Account account, final Optional blockNumber) { this.account = Optional.ofNullable(account); @@ -54,18 +82,39 @@ public AccountAdapter( this.blockNumber = blockNumber; } + /** + * Returns the address of the account. + * + * @return the address of the account + */ public Address getAddress() { return address; } + /** + * Returns the balance of the account. + * + * @return the balance of the account + */ public Wei getBalance() { return account.map(AccountState::getBalance).orElse(Wei.ZERO); } + /** + * Returns the transaction count of the account. + * + * @return the transaction count of the account + */ public Long getTransactionCount() { return account.map(AccountState::getNonce).orElse(0L); } + /** + * Returns the code of the account. + * + * @param environment the DataFetchingEnvironment + * @return the code of the account + */ public Bytes getCode(final DataFetchingEnvironment environment) { if (account.get() instanceof BonsaiAccount) { @@ -80,6 +129,12 @@ public Bytes getCode(final DataFetchingEnvironment environment) { } } + /** + * Returns the storage of the account. + * + * @param environment the DataFetchingEnvironment + * @return the storage of the account + */ public Bytes32 getStorage(final DataFetchingEnvironment environment) { final BlockchainQueries query = getBlockchainQueries(environment); final Bytes32 slot = environment.getArgument("slot"); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/BlockAdapterBase.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/BlockAdapterBase.java index 96434ee3750..6d5b696353a 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/BlockAdapterBase.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/BlockAdapterBase.java @@ -48,15 +48,30 @@ import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; +/** + * The BlockAdapterBase class extends the AdapterBase class. It provides methods to get and + * manipulate block data. + */ @SuppressWarnings("unused") // reflected by GraphQL public class BlockAdapterBase extends AdapterBase { private final BlockHeader header; + /** + * Constructs a new BlockAdapterBase with the given BlockHeader. + * + * @param header the BlockHeader to be adapted + */ BlockAdapterBase(final BlockHeader header) { this.header = header; } + /** + * Returns the parent block of the current block. + * + * @param environment the DataFetchingEnvironment + * @return an Optional containing the parent block if it exists, otherwise an empty Optional + */ public Optional getParent(final DataFetchingEnvironment environment) { final BlockchainQueries query = getBlockchainQueries(environment); final Hash parentHash = header.getParentHash(); @@ -65,28 +80,59 @@ public Optional getParent(final DataFetchingEnvironment envi return block.map(NormalBlockAdapter::new); } + /** + * Returns the hash of the current block. + * + * @return the hash of the block + */ public Bytes32 getHash() { return header.getHash(); } + /** + * Returns the nonce of the current block. + * + * @return the nonce of the block + */ public Bytes getNonce() { final long nonce = header.getNonce(); final byte[] bytes = Longs.toByteArray(nonce); return Bytes.wrap(bytes); } + /** + * Returns the transactions root of the current block. + * + * @return the transactions root of the block + */ public Bytes32 getTransactionsRoot() { return header.getTransactionsRoot(); } + /** + * Returns the state root of the current block. + * + * @return the state root of the block + */ public Bytes32 getStateRoot() { return header.getStateRoot(); } + /** + * Returns the receipts root of the current block. + * + * @return the receipts root of the block + */ public Bytes32 getReceiptsRoot() { return header.getReceiptsRoot(); } + /** + * Returns the miner of the current block. + * + * @param environment the DataFetchingEnvironment + * @return an AccountAdapter instance representing the miner of the block + */ public AccountAdapter getMiner(final DataFetchingEnvironment environment) { final BlockchainQueries query = getBlockchainQueries(environment); @@ -102,46 +148,103 @@ public AccountAdapter getMiner(final DataFetchingEnvironment environment) { .orElseGet(() -> new EmptyAccountAdapter(header.getCoinbase())); } + /** + * Returns the extra data of the current block. + * + * @return the extra data of the block + */ public Bytes getExtraData() { return header.getExtraData(); } + /** + * Returns the base fee per gas of the current block. + * + * @return the base fee per gas of the block + */ public Optional getBaseFeePerGas() { return header.getBaseFee(); } + /** + * Returns the gas limit of the current block. + * + * @return the gas limit of the block + */ public Long getGasLimit() { return header.getGasLimit(); } + /** + * Returns the gas used by the current block. + * + * @return the gas used by the block + */ public Long getGasUsed() { return header.getGasUsed(); } + /** + * Returns the timestamp of the current block. + * + * @return the timestamp of the block + */ public Long getTimestamp() { return header.getTimestamp(); } + /** + * Returns the logs bloom of the current block. + * + * @return the logs bloom of the block + */ public Bytes getLogsBloom() { return header.getLogsBloom(); } + /** + * Returns the mix hash of the current block. + * + * @return the mix hash of the block + */ public Bytes32 getMixHash() { return header.getMixHash(); } + /** + * Returns the difficulty of the current block. + * + * @return the difficulty of the block + */ public Difficulty getDifficulty() { return header.getDifficulty(); } + /** + * Returns the ommer hash of the current block. + * + * @return the ommer hash of the block + */ public Bytes32 getOmmerHash() { return header.getOmmersHash(); } + /** + * Returns the number of the current block. + * + * @return the number of the block + */ public Long getNumber() { return header.getNumber(); } + /** + * Returns an AccountAdapter instance for a given address at the current block. + * + * @param environment the DataFetchingEnvironment + * @return an AccountAdapter instance representing the account of the given address at the current + * block + */ public AccountAdapter getAccount(final DataFetchingEnvironment environment) { final BlockchainQueries query = getBlockchainQueries(environment); final long bn = header.getNumber(); @@ -152,6 +255,13 @@ public AccountAdapter getAccount(final DataFetchingEnvironment environment) { .get(); } + /** + * Returns a list of logs for the current block that match a given filter. + * + * @param environment the DataFetchingEnvironment + * @return a list of LogAdapter instances representing the logs of the current block that match + * the filter + */ public List getLogs(final DataFetchingEnvironment environment) { final Map filter = environment.getArgument("filter"); @@ -183,11 +293,24 @@ public List getLogs(final DataFetchingEnvironment environment) { return results; } + /** + * Estimates the gas used for a call execution. + * + * @param environment the DataFetchingEnvironment + * @return the estimated gas used for the call execution + */ public Long getEstimateGas(final DataFetchingEnvironment environment) { final Optional result = executeCall(environment); return result.map(CallResult::getGasUsed).orElse(0L); } + /** + * Executes a call and returns the result. + * + * @param environment the DataFetchingEnvironment + * @return an Optional containing the result of the call execution if it exists, otherwise an + * empty Optional + */ public Optional getCall(final DataFetchingEnvironment environment) { return executeCall(environment); } @@ -259,12 +382,23 @@ private Optional executeCall(final DataFetchingEnvironment environme header); } + /** + * Returns the raw header of the current block. + * + * @return the raw header of the block + */ Bytes getRawHeader() { final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput(); header.writeTo(rlpOutput); return rlpOutput.encoded(); } + /** + * Returns the raw data of the current block. + * + * @param environment the DataFetchingEnvironment + * @return the raw data of the block + */ Bytes getRaw(final DataFetchingEnvironment environment) { final BlockchainQueries query = getBlockchainQueries(environment); return query @@ -279,10 +413,22 @@ Bytes getRaw(final DataFetchingEnvironment environment) { .orElse(Bytes.EMPTY); } + /** + * Returns the withdrawals root of the current block. + * + * @return an Optional containing the withdrawals root if it exists, otherwise an empty Optional + */ Optional getWithdrawalsRoot() { return header.getWithdrawalsRoot().map(Function.identity()); } + /** + * Returns a list of withdrawals for the current block. + * + * @param environment the DataFetchingEnvironment + * @return an Optional containing a list of WithdrawalAdapter instances representing the + * withdrawals of the current block if they exist, otherwise an empty Optional + */ Optional> getWithdrawals(final DataFetchingEnvironment environment) { final BlockchainQueries query = getBlockchainQueries(environment); return query @@ -295,10 +441,22 @@ Optional> getWithdrawals(final DataFetchingEnvironment e .map(wl -> wl.stream().map(WithdrawalAdapter::new).toList())); } + /** + * Returns the blob gas used by the current block. + * + * @return an Optional containing the blob gas used by the current block if it exists, otherwise + * an empty Optional + */ public Optional getBlobGasUsed() { return header.getBlobGasUsed(); } + /** + * Returns the excess blob gas of the current block. + * + * @return an Optional containing the excess blob gas of the current block if it exists, otherwise + * an empty Optional + */ public Optional getExcessBlobGas() { return header.getExcessBlobGas().map(BlobGas::toLong); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/CallResult.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/CallResult.java index 78fa30d8748..0a5c9a8f38f 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/CallResult.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/CallResult.java @@ -16,26 +16,55 @@ import org.apache.tuweni.bytes.Bytes; +/** + * Represents the result of a call execution. This class is used to encapsulate the status, gas + * used, and data returned by a call execution. It is used in conjunction with the {@link + * org.hyperledger.besu.ethereum.api.graphql.internal.pojoadapter.BlockAdapterBase} class. + * + * @see org.hyperledger.besu.ethereum.api.graphql.internal.pojoadapter.BlockAdapterBase + */ @SuppressWarnings("unused") // reflected by GraphQL public class CallResult { private final Long status; private final Long gasUsed; private final Bytes data; + /** + * Constructs a new CallResult. + * + * @param status the status of the call execution + * @param gasUsed the amount of gas used by the call + * @param data the data returned by the call + */ CallResult(final Long status, final Long gasUsed, final Bytes data) { this.status = status; this.gasUsed = gasUsed; this.data = data; } + /** + * Returns the status of the call execution. + * + * @return the status of the call execution + */ public Long getStatus() { return status; } + /** + * Returns the amount of gas used by the call. + * + * @return the amount of gas used by the call + */ public Long getGasUsed() { return gasUsed; } + /** + * Returns the data returned by the call. + * + * @return the data returned by the call + */ public Bytes getData() { return data; } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/EmptyAccountAdapter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/EmptyAccountAdapter.java index 4c19b997b6e..775934fb1ba 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/EmptyAccountAdapter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/EmptyAccountAdapter.java @@ -21,9 +21,22 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; +/** + * Represents an empty account in the Ethereum blockchain. This class is used when an account does + * not exist at a specific address. It provides default values for the account's properties. It + * extends the {@link org.hyperledger.besu.ethereum.api.graphql.internal.pojoadapter.AccountAdapter} + * class. + * + * @see org.hyperledger.besu.ethereum.api.graphql.internal.pojoadapter.AccountAdapter + */ public class EmptyAccountAdapter extends AccountAdapter { private final Address address; + /** + * Constructs a new EmptyAccountAdapter. + * + * @param address the address of the account + */ public EmptyAccountAdapter(final Address address) { super(null); this.address = address; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/LogAdapter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/LogAdapter.java index 6beab46fdc8..012edbe512a 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/LogAdapter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/LogAdapter.java @@ -28,27 +28,62 @@ import graphql.schema.DataFetchingEnvironment; import org.apache.tuweni.bytes.Bytes; +/** + * This class is an adapter for the LogWithMetadata class. + * + *

It extends the AdapterBase class and provides methods to get the index, topics, data, + * transaction, and account associated with a log. + */ @SuppressWarnings("unused") // reflected by GraphQL public class LogAdapter extends AdapterBase { private final LogWithMetadata logWithMetadata; + /** + * Constructor for LogAdapter. + * + *

It initializes the logWithMetadata field with the provided argument. + * + * @param logWithMetadata the log with metadata to be adapted. + */ public LogAdapter(final LogWithMetadata logWithMetadata) { this.logWithMetadata = logWithMetadata; } + /** + * Returns the index of the log. + * + * @return the index of the log. + */ public Integer getIndex() { return logWithMetadata.getLogIndex(); } + /** + * Returns the topics of the log. + * + * @return a list of topics of the log. + */ public List getTopics() { final List topics = logWithMetadata.getTopics(); return new ArrayList<>(topics); } + /** + * Returns the data of the log. + * + * @return the data of the log. + */ public Bytes getData() { return logWithMetadata.getData(); } + /** + * Returns the transaction associated with the log. + * + * @param environment the data fetching environment. + * @return a TransactionAdapter for the transaction associated with the log. + * @throws java.util.NoSuchElementException if the transaction is not found. + */ public TransactionAdapter getTransaction(final DataFetchingEnvironment environment) { final BlockchainQueries query = getBlockchainQueries(environment); final Hash hash = logWithMetadata.getTransactionHash(); @@ -56,6 +91,12 @@ public TransactionAdapter getTransaction(final DataFetchingEnvironment environme return tran.map(TransactionAdapter::new).orElseThrow(); } + /** + * Returns the account associated with the log. + * + * @param environment the data fetching environment. + * @return an AccountAdapter for the account associated with the log. + */ public AccountAdapter getAccount(final DataFetchingEnvironment environment) { final BlockchainQueries query = getBlockchainQueries(environment); long blockNumber = logWithMetadata.getBlockNumber(); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/NormalBlockAdapter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/NormalBlockAdapter.java index f54b3f0da04..fa75028a383 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/NormalBlockAdapter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/NormalBlockAdapter.java @@ -27,9 +27,24 @@ import graphql.schema.DataFetchingEnvironment; +/** + * This class is an adapter for the BlockWithMetadata class. + * + *

It extends the BlockAdapterBase class and provides methods to get the transaction count, total + * difficulty, ommer count, ommers, transactions, and specific ommer and transaction at a given + * index associated with a block. + */ @SuppressWarnings("unused") // reflected by GraphQL public class NormalBlockAdapter extends BlockAdapterBase { + /** + * Constructor for NormalBlockAdapter. + * + *

It initializes the blockWithMetaData field with the provided argument and calls the parent + * constructor with the header of the provided blockWithMetaData. + * + * @param blockWithMetaData the block with metadata to be adapted. + */ public NormalBlockAdapter( final BlockWithMetadata blockWithMetaData) { super(blockWithMetaData.getHeader()); @@ -38,18 +53,39 @@ public NormalBlockAdapter( private final BlockWithMetadata blockWithMetaData; + /** + * Returns the transaction count of the block. + * + * @return the transaction count of the block. + */ public Optional getTransactionCount() { return Optional.of(blockWithMetaData.getTransactions().size()); } + /** + * Returns the total difficulty of the block. + * + * @return the total difficulty of the block. + */ public Difficulty getTotalDifficulty() { return blockWithMetaData.getTotalDifficulty(); } + /** + * Returns the ommer count of the block. + * + * @return the ommer count of the block. + */ public Optional getOmmerCount() { return Optional.of(blockWithMetaData.getOmmers().size()); } + /** + * Returns the ommers of the block. + * + * @param environment the data fetching environment. + * @return a list of UncleBlockAdapter for the ommers of the block. + */ public List getOmmers(final DataFetchingEnvironment environment) { final BlockchainQueries query = getBlockchainQueries(environment); final List ommers = blockWithMetaData.getOmmers(); @@ -63,6 +99,12 @@ public List getOmmers(final DataFetchingEnvironment environme return results; } + /** + * Returns the ommer at a given index of the block. + * + * @param environment the data fetching environment. + * @return an UncleBlockAdapter for the ommer at the given index of the block. + */ public Optional getOmmerAt(final DataFetchingEnvironment environment) { final BlockchainQueries query = getBlockchainQueries(environment); final int index = ((Number) environment.getArgument("index")).intValue(); @@ -75,6 +117,13 @@ public Optional getOmmerAt(final DataFetchingEnvironment envi return Optional.empty(); } + /** + * Returns a list of TransactionAdapter objects for the transactions in the block. + * + *

Each TransactionAdapter object is created by adapting a TransactionWithMetadata object. + * + * @return a list of TransactionAdapter objects for the transactions in the block. + */ public List getTransactions() { final List trans = blockWithMetaData.getTransactions(); final List results = new ArrayList<>(); @@ -84,6 +133,16 @@ public List getTransactions() { return results; } + /** + * Returns a TransactionAdapter object for the transaction at the given index in the block. + * + *

The index is retrieved from the data fetching environment. If there is no transaction at the + * given index, an empty Optional is returned. + * + * @param environment the data fetching environment. + * @return an Optional containing a TransactionAdapter object for the transaction at the given + * index in the block, or an empty Optional if there is no transaction at the given index. + */ public Optional getTransactionAt(final DataFetchingEnvironment environment) { final int index = ((Number) environment.getArgument("index")).intValue(); final List trans = blockWithMetaData.getTransactions(); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/PendingStateAdapter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/PendingStateAdapter.java index 0202db88f62..49f6bc890f9 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/PendingStateAdapter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/PendingStateAdapter.java @@ -36,19 +36,41 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; +/** + * This class represents the pending state of transactions in the Ethereum network. + * + *

It extends the AdapterBase class and provides methods to interact with the transaction pool. + */ @SuppressWarnings("unused") // reflected by GraphQL public class PendingStateAdapter extends AdapterBase { private final TransactionPool transactionPool; + /** + * Constructor for PendingStateAdapter. + * + *

It initializes the transactionPool field with the provided argument. + * + * @param transactionPool the transaction pool to be used. + */ public PendingStateAdapter(final TransactionPool transactionPool) { this.transactionPool = transactionPool; } + /** + * Returns the count of transactions in the transaction pool. + * + * @return the count of transactions in the transaction pool. + */ public Integer getTransactionCount() { return transactionPool.count(); } + /** + * Returns a list of TransactionAdapter objects for the transactions in the transaction pool. + * + * @return a list of TransactionAdapter objects for the transactions in the transaction pool. + */ public List getTransactions() { return transactionPool.getPendingTransactions().stream() .map(PendingTransaction::getTransaction) @@ -57,9 +79,17 @@ public List getTransactions() { .toList(); } - // until the miner can expose the current "proposed block" we have no - // speculative environment, so estimate against latest. + /** + * Returns an AccountAdapter object for the account associated with the provided address. + * + *

The account state is estimated against the latest block. + * + * @param dataFetchingEnvironment the data fetching environment. + * @return an AccountAdapter object for the account associated with the provided address. + */ public AccountAdapter getAccount(final DataFetchingEnvironment dataFetchingEnvironment) { + // until the miner can expose the current "proposed block" we have no + // speculative environment, so estimate against latest. final BlockchainQueries blockchainQuery = dataFetchingEnvironment.getGraphQlContext().get(GraphQLContextType.BLOCKCHAIN_QUERIES); final Address addr = dataFetchingEnvironment.getArgument("address"); @@ -71,16 +101,39 @@ public AccountAdapter getAccount(final DataFetchingEnvironment dataFetchingEnvir .orElseGet(() -> new AccountAdapter(null)); } - // until the miner can expose the current "proposed block" we have no - // speculative environment, so estimate against latest. + /** + * Estimates the gas required for a transaction. + * + *

This method calls the getCall method to simulate the transaction and then retrieves the gas + * used by the transaction. The gas estimation is done against the latest block as there is + * currently no way to expose the current "proposed block" for a speculative environment. + * + * @param environment the data fetching environment. + * @return an Optional containing the estimated gas for the transaction, or an empty Optional if + * the transaction simulation was not successful. + */ public Optional getEstimateGas(final DataFetchingEnvironment environment) { + // until the miner can expose the current "proposed block" we have no + // speculative environment, so estimate against latest. final Optional result = getCall(environment); return result.map(CallResult::getGasUsed); } - // until the miner can expose the current "proposed block" we have no - // speculative environment, so estimate against latest. + /** + * Simulates the execution of a transaction. + * + *

This method retrieves the transaction parameters from the data fetching environment, creates + * a CallParameter object, and then uses a TransactionSimulator to simulate the execution of the + * transaction. The simulation is done against the latest block as there is currently no way to + * expose the current "proposed block" for a speculative environment. + * + * @param environment the data fetching environment. + * @return an Optional containing the result of the transaction simulation, or an empty Optional + * if the transaction simulation was not successful. + */ public Optional getCall(final DataFetchingEnvironment environment) { + // until the miner can expose the current "proposed block" we have no + // speculative environment, so estimate against latest. final Map callData = environment.getArgument("data"); final Address from = (Address) callData.get("from"); final Address to = (Address) callData.get("to"); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/SyncStateAdapter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/SyncStateAdapter.java index 0a8dcbbf97e..2c3876fe367 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/SyncStateAdapter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/SyncStateAdapter.java @@ -16,22 +16,52 @@ import org.hyperledger.besu.plugin.data.SyncStatus; +/** + * The SyncStateAdapter class provides methods to retrieve the synchronization status of the + * blockchain. + * + *

This class is used to adapt a SyncStatus object into a format that can be used by GraphQL. The + * SyncStatus object is provided at construction time. + * + *

The class provides methods to retrieve the starting block, current block, and highest block of + * the synchronization status. + */ @SuppressWarnings("unused") // reflected by GraphQL public class SyncStateAdapter { private final SyncStatus syncStatus; + /** + * Constructs a new SyncStateAdapter object. + * + * @param syncStatus the SyncStatus object to adapt. + */ public SyncStateAdapter(final SyncStatus syncStatus) { this.syncStatus = syncStatus; } + /** + * Returns the starting block of the synchronization status. + * + * @return the starting block of the synchronization status. + */ public Long getStartingBlock() { return syncStatus.getStartingBlock(); } + /** + * Returns the current block of the synchronization status. + * + * @return the current block of the synchronization status. + */ public Long getCurrentBlock() { return syncStatus.getCurrentBlock(); } + /** + * Returns the highest block of the synchronization status. + * + * @return the highest block of the synchronization status. + */ public Long getHighestBlock() { return syncStatus.getHighestBlock(); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/TransactionAdapter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/TransactionAdapter.java index f3ead13dadb..d5eacf8e357 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/TransactionAdapter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/TransactionAdapter.java @@ -16,6 +16,7 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.VersionedHash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.api.graphql.GraphQLContextType; @@ -37,11 +38,28 @@ import graphql.schema.DataFetchingEnvironment; import org.apache.tuweni.bytes.Bytes; +/** + * The TransactionAdapter class provides methods to retrieve the transaction details. + * + *

This class is used to adapt a TransactionWithMetadata object into a format that can be used by + * GraphQL. The TransactionWithMetadata object is provided at construction time. + * + *

The class provides methods to retrieve the hash, type, nonce, index, from, to, value, gas + * price, max fee per gas, max priority fee per gas, max fee per blob gas, effective tip, gas, input + * data, block, status, gas used, cumulative gas used, effective gas price, blob gas used, blob gas + * price, created contract, logs, R, S, V, Y parity, access list, raw, raw receipt, and blob + * versioned hashes of the transaction. + */ @SuppressWarnings("unused") // reflected by GraphQL public class TransactionAdapter extends AdapterBase { private final TransactionWithMetadata transactionWithMetadata; private Optional transactionReceiptWithMetadata; + /** + * Constructs a new TransactionAdapter object. + * + * @param transactionWithMetadata the TransactionWithMetadata object to adapt. + */ public TransactionAdapter(final @Nonnull TransactionWithMetadata transactionWithMetadata) { this.transactionWithMetadata = transactionWithMetadata; } @@ -64,22 +82,53 @@ private Optional getReceipt( return transactionReceiptWithMetadata; } + /** + * Returns the hash of the transaction. + * + * @return the hash of the transaction. + */ public Hash getHash() { return transactionWithMetadata.getTransaction().getHash(); } + /** + * Returns the type of the transaction. + * + * @return the type of the transaction. + */ public Optional getType() { return Optional.of(transactionWithMetadata.getTransaction().getType().ordinal()); } + /** + * Returns the nonce of the transaction. + * + * @return the nonce of the transaction. + */ public Long getNonce() { return transactionWithMetadata.getTransaction().getNonce(); } + /** + * Returns the index of the transaction. + * + * @return the index of the transaction. + */ public Optional getIndex() { return transactionWithMetadata.getTransactionIndex(); } + /** + * Retrieves the sender of the transaction. + * + *

This method uses the BlockchainQueries to get the block number and then retrieves the sender + * of the transaction. It then uses the getAndMapWorldState method to get the state of the + * sender's account at the given block number. + * + * @param environment the data fetching environment. + * @return an AccountAdapter object representing the sender's account state at the given block + * number. + */ public AccountAdapter getFrom(final DataFetchingEnvironment environment) { final BlockchainQueries query = getBlockchainQueries(environment); final Long blockNumber = @@ -95,6 +144,18 @@ public AccountAdapter getFrom(final DataFetchingEnvironment environment) { .orElse(new EmptyAccountAdapter(addr)); } + /** + * Retrieves the recipient of the transaction. + * + *

This method uses the BlockchainQueries to get the block number and then retrieves the + * recipient of the transaction. It then uses the getAndMapWorldState method to get the state of + * the recipient's account at the given block number. + * + * @param environment the data fetching environment. + * @return an Optional containing an AccountAdapter object representing the recipient's account + * state at the given block number, or an empty Optional if the transaction does not have a + * recipient (i.e., it is a contract creation transaction). + */ public Optional getTo(final DataFetchingEnvironment environment) { final BlockchainQueries query = getBlockchainQueries(environment); final Long blockNumber = @@ -114,39 +175,95 @@ public Optional getTo(final DataFetchingEnvironment environment) .or(() -> Optional.of(new EmptyAccountAdapter(address)))); } + /** + * Retrieves the value of the transaction. + * + * @return a Wei object representing the value of the transaction. + */ public Wei getValue() { return transactionWithMetadata.getTransaction().getValue(); } + /** + * Retrieves the gas price of the transaction. + * + * @return a Wei object representing the gas price of the transaction. If the transaction does not + * specify a gas price, this method returns zero. + */ public Wei getGasPrice() { return transactionWithMetadata.getTransaction().getGasPrice().orElse(Wei.ZERO); } + /** + * Retrieves the maximum fee per gas of the transaction. + * + * @return an Optional containing a Wei object representing the maximum fee per gas of the + * transaction, or an empty Optional if the transaction does not specify a maximum fee per + * gas. + */ public Optional getMaxFeePerGas() { return transactionWithMetadata.getTransaction().getMaxFeePerGas(); } + /** + * Retrieves the maximum priority fee per gas of the transaction. + * + * @return an Optional containing a Wei object representing the maximum priority fee per gas of + * the transaction, or an empty Optional if the transaction does not specify a maximum + * priority fee per gas. + */ public Optional getMaxPriorityFeePerGas() { return transactionWithMetadata.getTransaction().getMaxPriorityFeePerGas(); } + /** + * Retrieves the maximum fee per blob gas of the transaction. + * + * @return an Optional containing a Wei object representing the maximum fee per blob gas of the + * transaction, or an empty Optional if the transaction does not specify a maximum fee per + * blob gas. + */ public Optional getMaxFeePerBlobGas() { return transactionWithMetadata.getTransaction().getMaxFeePerBlobGas(); } + /** + * Retrieves the effective tip of the transaction. + * + * @param environment the data fetching environment. + * @return an Optional containing a Wei object representing the effective tip of the transaction, + * or an empty Optional if the transaction does not specify an effective tip. + */ public Optional getEffectiveTip(final DataFetchingEnvironment environment) { return getReceipt(environment) .map(rwm -> rwm.getTransaction().getEffectivePriorityFeePerGas(rwm.getBaseFee())); } + /** + * Retrieves the gas limit of the transaction. + * + * @return a Long object representing the gas limit of the transaction. + */ public Long getGas() { return transactionWithMetadata.getTransaction().getGasLimit(); } + /** + * Retrieves the input data of the transaction. + * + * @return a Bytes object representing the input data of the transaction. + */ public Bytes getInputData() { return transactionWithMetadata.getTransaction().getPayload(); } + /** + * Retrieves the block of the transaction. + * + * @param environment the data fetching environment. + * @return an Optional containing a NormalBlockAdapter object representing the block of the + * transaction, or an empty Optional if the transaction does not specify a block. + */ public Optional getBlock(final DataFetchingEnvironment environment) { return transactionWithMetadata .getBlockHash() @@ -154,6 +271,17 @@ public Optional getBlock(final DataFetchingEnvironment envir .map(NormalBlockAdapter::new); } + /** + * Retrieves the status of the transaction. + * + *

This method uses the getReceipt method to get the receipt of the transaction. It then checks + * the status of the receipt. If the status is -1, it returns an empty Optional. Otherwise, it + * returns an Optional containing the status of the receipt. + * + * @param environment the data fetching environment. + * @return an Optional containing a Long object representing the status of the transaction, or an + * empty Optional if the status of the receipt is -1. + */ public Optional getStatus(final DataFetchingEnvironment environment) { return getReceipt(environment) .map(TransactionReceiptWithMetadata::getReceipt) @@ -164,27 +292,87 @@ public Optional getStatus(final DataFetchingEnvironment environment) { : Optional.of((long) receipt.getStatus())); } + /** + * Retrieves the gas used by the transaction. + * + *

This method uses the getReceipt method to get the receipt of the transaction. It then + * returns an Optional containing the gas used by the transaction. + * + * @param environment the data fetching environment. + * @return an Optional containing a Long object representing the gas used by the transaction. + */ public Optional getGasUsed(final DataFetchingEnvironment environment) { return getReceipt(environment).map(TransactionReceiptWithMetadata::getGasUsed); } + /** + * Retrieves the cumulative gas used by the transaction. + * + *

This method uses the getReceipt method to get the receipt of the transaction. It then + * returns an Optional containing the cumulative gas used by the transaction. + * + * @param environment the data fetching environment. + * @return an Optional containing a Long object representing the cumulative gas used by the + * transaction. + */ public Optional getCumulativeGasUsed(final DataFetchingEnvironment environment) { return getReceipt(environment).map(rpt -> rpt.getReceipt().getCumulativeGasUsed()); } + /** + * Retrieves the effective gas price of the transaction. + * + *

This method uses the getReceipt method to get the receipt of the transaction. It then + * returns an Optional containing the effective gas price of the transaction. + * + * @param environment the data fetching environment. + * @return an Optional containing a Wei object representing the effective gas price of the + * transaction. + */ public Optional getEffectiveGasPrice(final DataFetchingEnvironment environment) { return getReceipt(environment) .map(rwm -> rwm.getTransaction().getEffectiveGasPrice(rwm.getBaseFee())); } + /** + * Retrieves the blob gas used by the transaction. + * + *

This method uses the getReceipt method to get the receipt of the transaction. It then + * returns an Optional containing the blob gas used by the transaction. + * + * @param environment the data fetching environment. + * @return an Optional containing a Long object representing the blob gas used by the transaction. + */ public Optional getBlobGasUsed(final DataFetchingEnvironment environment) { return getReceipt(environment).flatMap(TransactionReceiptWithMetadata::getBlobGasUsed); } + /** + * Retrieves the blob gas price of the transaction. + * + *

This method uses the getReceipt method to get the receipt of the transaction. It then + * returns an Optional containing the blob gas price of the transaction. + * + * @param environment the data fetching environment. + * @return an Optional containing a Wei object representing the blob gas price of the transaction. + */ public Optional getBlobGasPrice(final DataFetchingEnvironment environment) { return getReceipt(environment).flatMap(TransactionReceiptWithMetadata::getBlobGasPrice); } + /** + * Retrieves the contract created by the transaction. + * + *

This method checks if the transaction is a contract creation transaction. If it is, it + * retrieves the address of the created contract. It then uses the BlockchainQueries to get the + * block number and then retrieves the state of the created contract's account at the given block + * number. + * + * @param environment the data fetching environment. + * @return an Optional containing an AccountAdapter object representing the created contract's + * account state at the given block number, or an empty Optional if the transaction is not a + * contract creation transaction or if the block number is not specified. + */ public Optional getCreatedContract(final DataFetchingEnvironment environment) { final boolean contractCreated = transactionWithMetadata.getTransaction().isContractCreation(); if (contractCreated) { @@ -207,6 +395,17 @@ public Optional getCreatedContract(final DataFetchingEnvironment return Optional.empty(); } + /** + * Retrieves the logs of the transaction. + * + *

This method uses the BlockchainQueries to get the block header and the receipt of the + * transaction. It then retrieves the logs of the transaction and adapts them into a format that + * can be used by GraphQL. + * + * @param environment the data fetching environment. + * @return a List of LogAdapter objects representing the logs of the transaction. If the + * transaction does not have a receipt, this method returns an empty list. + */ public List getLogs(final DataFetchingEnvironment environment) { final BlockchainQueries query = getBlockchainQueries(environment); final ProtocolSchedule protocolSchedule = @@ -239,22 +438,60 @@ public List getLogs(final DataFetchingEnvironment environment) { return results; } + /** + * Retrieves the R component of the transaction's signature. + * + * @return a BigInteger object representing the R component of the transaction's signature. + */ public BigInteger getR() { return transactionWithMetadata.getTransaction().getR(); } + /** + * Retrieves the S component of the transaction's signature. + * + * @return a BigInteger object representing the S component of the transaction's signature. + */ public BigInteger getS() { return transactionWithMetadata.getTransaction().getS(); } + /** + * Retrieves the V component of the transaction's signature. + * + *

If the transaction type is less than the BLOB transaction type and V is null, it returns the + * Y parity of the transaction. Otherwise, it returns V. + * + * @return an Optional containing a BigInteger object representing the V component of the + * transaction's signature, or an Optional containing the Y parity of the transaction if V is + * null and the transaction type is less than the BLOB transaction type. + */ public Optional getV() { - return Optional.ofNullable(transactionWithMetadata.getTransaction().getV()); - } - + BigInteger v = transactionWithMetadata.getTransaction().getV(); + return Optional.ofNullable( + v == null + && (transactionWithMetadata.getTransaction().getType().getEthSerializedType() + < TransactionType.BLOB.getEthSerializedType()) + ? transactionWithMetadata.getTransaction().getYParity() + : v); + } + + /** + * Retrieves the Y parity of the transaction's signature. + * + * @return an Optional containing a BigInteger object representing the Y parity of the + * transaction's signature. + */ public Optional getYParity() { return Optional.ofNullable(transactionWithMetadata.getTransaction().getYParity()); } + /** + * Retrieves the access list of the transaction. + * + * @return a List of AccessListEntryAdapter objects representing the access list of the + * transaction. + */ public List getAccessList() { return transactionWithMetadata .getTransaction() @@ -263,22 +500,45 @@ public List getAccessList() { .orElse(List.of()); } + /** + * Retrieves the raw transaction data. + * + *

This method uses the writeTo method of the transaction to write the transaction data to a + * BytesValueRLPOutput object. It then encodes the BytesValueRLPOutput object and returns it. + * + * @return an Optional containing a Bytes object representing the raw transaction data. + */ public Optional getRaw() { final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput(); transactionWithMetadata.getTransaction().writeTo(rlpOutput); return Optional.of(rlpOutput.encoded()); } + /** + * Retrieves the raw receipt of the transaction. + * + *

This method uses the getReceipt method to get the receipt of the transaction. It then writes + * the receipt data to a BytesValueRLPOutput object using the writeToForNetwork method of the + * receipt. It then encodes the BytesValueRLPOutput object and returns it. + * + * @param environment the data fetching environment. + * @return an Optional containing a Bytes object representing the raw receipt of the transaction. + */ public Optional getRawReceipt(final DataFetchingEnvironment environment) { return getReceipt(environment) .map( receipt -> { final BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput(); - receipt.getReceipt().writeTo(rlpOutput); + receipt.getReceipt().writeToForNetwork(rlpOutput); return rlpOutput.encoded(); }); } + /** + * Retrieves the versioned hashes of the transaction. + * + * @return a List of VersionedHash objects representing the versioned hashes of the transaction. + */ public List getBlobVersionedHashes() { return transactionWithMetadata.getTransaction().getVersionedHashes().orElse(List.of()); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/WithdrawalAdapter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/WithdrawalAdapter.java index 86782b6afcb..85ec69b0ddf 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/WithdrawalAdapter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/pojoadapter/WithdrawalAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,26 +17,60 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.plugin.data.Withdrawal; +/** + * The WithdrawalAdapter class provides methods to retrieve the withdrawal details. + * + *

This class is used to adapt a Withdrawal object into a format that can be used by GraphQL. The + * Withdrawal object is provided at construction time. + * + *

The class provides methods to retrieve the index, validator, address, and amount of the + * withdrawal. + */ public class WithdrawalAdapter { Withdrawal withdrawal; + /** + * Constructs a new WithdrawalAdapter object. + * + * @param withdrawal the Withdrawal object to adapt. + */ public WithdrawalAdapter(final Withdrawal withdrawal) { this.withdrawal = withdrawal; } + /** + * Returns the index of the withdrawal. + * + * @return the index of the withdrawal. + */ public Long getIndex() { return withdrawal.getIndex().toLong(); } + /** + * Returns the validator of the withdrawal. + * + * @return the validator of the withdrawal. + */ public Long getValidator() { return withdrawal.getValidatorIndex().toLong(); } + /** + * Returns the address of the withdrawal. + * + * @return the address of the withdrawal. + */ public Address getAddress() { return withdrawal.getAddress(); } + /** + * Returns the amount of the withdrawal. + * + * @return the amount of the withdrawal. + */ public Long getAmount() { return withdrawal.getAmount().getAsBigInteger().longValue(); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLError.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLError.java index e4aeacd92fd..f370df8dcfb 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLError.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLError.java @@ -19,30 +19,96 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonGetter; +/** + * Enum representing various types of GraphQL errors. + * + *

Each error is associated with a specific code and message. The code is an integer that + * uniquely identifies the error. The message is a string that provides a human-readable description + * of the error. + * + *

This enum includes standard errors such as INVALID_PARAMS and INTERNAL_ERROR, transaction + * validation failures such as NONCE_TOO_LOW and INVALID_TRANSACTION_SIGNATURE, and private + * transaction errors such as PRIVATE_TRANSACTION_FAILED and PRIVATE_NONCE_TOO_LOW. + * + *

The {@code of} method is used to map a {@code TransactionInvalidReason} to a corresponding + * {@code GraphQLError}. + */ @JsonFormat(shape = JsonFormat.Shape.OBJECT) public enum GraphQLError { // Standard errors + /** Error code -32602. This error occurs when the parameters provided are invalid. */ INVALID_PARAMS(-32602, "Invalid params"), + + /** Error code -32603. This error occurs when there is an internal error. */ INTERNAL_ERROR(-32603, "Internal error"), // Transaction validation failures + /** Error code -32001. This error occurs when the nonce value is too low. */ NONCE_TOO_LOW(-32001, "Nonce too low"), + + /** Error code -32002. This error occurs when the transaction signature is invalid. */ INVALID_TRANSACTION_SIGNATURE(-32002, "Invalid signature"), + + /** Error code -32003. This error occurs when the intrinsic gas exceeds the gas limit. */ INTRINSIC_GAS_EXCEEDS_LIMIT(-32003, "Intrinsic gas exceeds gas limit"), + + /** + * Error code -32004. This error occurs when the upfront cost of the transaction exceeds the + * account balance. + */ TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE(-32004, "Upfront cost exceeds account balance"), + + /** + * Error code -32005. This error occurs when the transaction gas limit exceeds the block gas + * limit. + */ EXCEEDS_BLOCK_GAS_LIMIT(-32005, "Transaction gas limit exceeds block gas limit"), + + /** Error code -32006. This error occurs when the nonce value is too high. */ INCORRECT_NONCE(-32006, "Nonce too high"), + + /** + * Error code -32007. This error occurs when the sender account is not authorized to send + * transactions. + */ TX_SENDER_NOT_AUTHORIZED(-32007, "Sender account not authorized to send transactions"), + + /** Error code -32008. This error occurs when the initial sync is still in progress. */ CHAIN_HEAD_WORLD_STATE_NOT_AVAILABLE(-32008, "Initial sync is still in progress"), + + /** + * Error code -32009. This error occurs when the gas price is below the configured minimum gas + * price. + */ GAS_PRICE_TOO_LOW(-32009, "Gas price below configured minimum gas price"), + + /** + * Error code -32000. This error occurs when the Chain ID in the transaction signature is wrong. + */ WRONG_CHAIN_ID(-32000, "Wrong Chain ID in transaction signature"), + + /** + * Error code -32000. This error occurs when signatures with replay protection are not supported. + */ REPLAY_PROTECTED_SIGNATURES_NOT_SUPPORTED( -32000, "Signatures with replay protection are not supported"), + + /** Error code -32000. This error occurs when the transaction fee cap is exceeded. */ TX_FEECAP_EXCEEDED(-32000, "Transaction fee cap exceeded"), // Private Transaction Errors + /** Error code -32000. This error occurs when a private transaction fails. */ PRIVATE_TRANSACTION_FAILED(-32000, "Private transaction failed"), + + /** + * Error code -50100. This error occurs when the nonce value for a private transaction is too low. + */ PRIVATE_NONCE_TOO_LOW(-50100, "Private transaction nonce too low"), + + /** + * Error code -50100. This error occurs when the nonce value for a private transaction is + * incorrect. + */ INCORRECT_PRIVATE_NONCE(-50100, "Private transaction nonce is incorrect"); private final int code; @@ -53,49 +119,52 @@ public enum GraphQLError { this.message = message; } + /** + * Returns the error code associated with this GraphQL error. + * + * @return the error code as an integer. + */ @JsonGetter("code") public int getCode() { return code; } + /** + * Returns the error message associated with this GraphQL error. + * + * @return the error message as a string. + */ @JsonGetter("message") public String getMessage() { return message; } + /** + * Maps a {@code TransactionInvalidReason} to a corresponding {@code GraphQLError}. + * + *

This method is used to convert a transaction invalid reason to a GraphQL error, which can be + * used to provide more detailed error information to the client. + * + * @param transactionInvalidReason the transaction invalid reason to be converted. + * @return the corresponding GraphQL error. + */ public static GraphQLError of(final TransactionInvalidReason transactionInvalidReason) { - switch (transactionInvalidReason) { - case WRONG_CHAIN_ID: - return WRONG_CHAIN_ID; - case REPLAY_PROTECTED_SIGNATURES_NOT_SUPPORTED: - return REPLAY_PROTECTED_SIGNATURES_NOT_SUPPORTED; - case INVALID_SIGNATURE: - return INVALID_TRANSACTION_SIGNATURE; - case UPFRONT_COST_EXCEEDS_BALANCE: - return TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE; - case NONCE_TOO_LOW: - case PRIVATE_NONCE_TOO_LOW: - return NONCE_TOO_LOW; - case NONCE_TOO_HIGH: - case PRIVATE_NONCE_TOO_HIGH: - return INCORRECT_NONCE; - case INTRINSIC_GAS_EXCEEDS_GAS_LIMIT: - return INTRINSIC_GAS_EXCEEDS_LIMIT; - case EXCEEDS_BLOCK_GAS_LIMIT: - return EXCEEDS_BLOCK_GAS_LIMIT; - case TX_SENDER_NOT_AUTHORIZED: - return TX_SENDER_NOT_AUTHORIZED; - case CHAIN_HEAD_WORLD_STATE_NOT_AVAILABLE: - return CHAIN_HEAD_WORLD_STATE_NOT_AVAILABLE; + return switch (transactionInvalidReason) { + case WRONG_CHAIN_ID -> WRONG_CHAIN_ID; + case REPLAY_PROTECTED_SIGNATURES_NOT_SUPPORTED -> REPLAY_PROTECTED_SIGNATURES_NOT_SUPPORTED; + case INVALID_SIGNATURE -> INVALID_TRANSACTION_SIGNATURE; + case UPFRONT_COST_EXCEEDS_BALANCE -> TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE; + case NONCE_TOO_LOW, PRIVATE_NONCE_TOO_LOW -> NONCE_TOO_LOW; + case NONCE_TOO_HIGH, PRIVATE_NONCE_TOO_HIGH -> INCORRECT_NONCE; + case INTRINSIC_GAS_EXCEEDS_GAS_LIMIT -> INTRINSIC_GAS_EXCEEDS_LIMIT; + case EXCEEDS_BLOCK_GAS_LIMIT -> EXCEEDS_BLOCK_GAS_LIMIT; + case TX_SENDER_NOT_AUTHORIZED -> TX_SENDER_NOT_AUTHORIZED; + case CHAIN_HEAD_WORLD_STATE_NOT_AVAILABLE -> CHAIN_HEAD_WORLD_STATE_NOT_AVAILABLE; // Private Transaction Invalid Reasons - case PRIVATE_TRANSACTION_FAILED: - return PRIVATE_TRANSACTION_FAILED; - case GAS_PRICE_TOO_LOW: - return GAS_PRICE_TOO_LOW; - case TX_FEECAP_EXCEEDED: - return TX_FEECAP_EXCEEDED; - default: - return INTERNAL_ERROR; - } + case PRIVATE_TRANSACTION_FAILED -> PRIVATE_TRANSACTION_FAILED; + case GAS_PRICE_TOO_LOW -> GAS_PRICE_TOO_LOW; + case TX_FEECAP_EXCEEDED -> TX_FEECAP_EXCEEDED; + default -> INTERNAL_ERROR; + }; } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLErrorResponse.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLErrorResponse.java index faf129311c1..8204fad0759 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLErrorResponse.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLErrorResponse.java @@ -16,12 +16,26 @@ import com.fasterxml.jackson.annotation.JsonIgnore; +/** + * Represents a GraphQL error response. This class extends the GraphQLResponse class and overrides + * the getType method to return ERROR. + */ public class GraphQLErrorResponse extends GraphQLResponse { + /** + * Constructs a new GraphQLErrorResponse with the specified errors. + * + * @param errors the errors to be included in the response. + */ public GraphQLErrorResponse(final Object errors) { super(errors); } + /** + * Returns the type of this GraphQL response. + * + * @return GraphQLResponseType.ERROR, indicating that this is an error response. + */ @Override @JsonIgnore public GraphQLResponseType getType() { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLJsonRequest.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLJsonRequest.java index 4a0755fad38..899e08a7f08 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLJsonRequest.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLJsonRequest.java @@ -19,36 +19,77 @@ import com.fasterxml.jackson.annotation.JsonGetter; import com.fasterxml.jackson.annotation.JsonSetter; +/** + * This class represents a GraphQL JSON request. + * + *

It contains the query, operation name, and variables for a GraphQL request. The query is a + * string that represents the GraphQL query. The operation name is a string that represents the + * operation to be performed. The variables is a map that contains the variables for the GraphQL + * query. + */ public class GraphQLJsonRequest { private String query; private String operationName; private Map variables; + /** Default constructor for GraphQLJsonRequest. */ + public GraphQLJsonRequest() {} + + /** + * Gets the query of the GraphQL request. + * + * @return the query of the GraphQL request. + */ @JsonGetter public String getQuery() { return query; } + /** + * Sets the query of the GraphQL request. + * + * @param query the query of the GraphQL request. + */ @JsonSetter public void setQuery(final String query) { this.query = query; } + /** + * Gets the operation name of the GraphQL request. + * + * @return the operation name of the GraphQL request. + */ @JsonGetter public String getOperationName() { return operationName; } + /** + * Sets the operation name of the GraphQL request. + * + * @param operationName the operation name of the GraphQL request. + */ @JsonSetter public void setOperationName(final String operationName) { this.operationName = operationName; } + /** + * Gets the variables of the GraphQL request. + * + * @return the variables of the GraphQL request. + */ @JsonGetter public Map getVariables() { return variables; } + /** + * Sets the variables of the GraphQL request. + * + * @param variables the variables of the GraphQL request. + */ @JsonSetter public void setVariables(final Map variables) { this.variables = variables; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLNoResponse.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLNoResponse.java index abc0725107e..2891ed19226 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLNoResponse.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLNoResponse.java @@ -14,8 +14,20 @@ */ package org.hyperledger.besu.ethereum.api.graphql.internal.response; +/** + * This class represents a GraphQL response with no content. + * + *

It extends the GraphQLResponse class and overrides the getType method to return + * GraphQLResponseType.NONE. + */ public class GraphQLNoResponse extends GraphQLResponse { + /** + * Default constructor for GraphQLNoResponse. + * + *

It calls the parent constructor with null as the argument, indicating no content for this + * response. + */ public GraphQLNoResponse() { super(null); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLResponse.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLResponse.java index 21bc17d31a3..66121625493 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLResponse.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLResponse.java @@ -16,15 +16,35 @@ import java.util.Objects; +/** + * Represents a GraphQL response. This abstract class provides a structure for different types of + * GraphQL responses. + */ public abstract class GraphQLResponse { - public abstract GraphQLResponseType getType(); private final Object result; + /** + * Constructs a new GraphQLResponse with the specified result. + * + * @param result the result to be included in the response. + */ GraphQLResponse(final Object result) { this.result = result; } + /** + * Returns the type of this GraphQL response. + * + * @return the type of this GraphQL response as a GraphQLResponseType. + */ + public abstract GraphQLResponseType getType(); + + /** + * Returns the result of this GraphQL response. + * + * @return the result of this GraphQL response as an Object. + */ public Object getResult() { return result; } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLResponseType.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLResponseType.java index ddd5170c371..655d5bc11a5 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLResponseType.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLResponseType.java @@ -16,8 +16,15 @@ /** Various types of responses that the JSON-RPC component may produce. */ public enum GraphQLResponseType { + /** Represents a response type where there is no content. */ NONE, + + /** Represents a successful response type. */ SUCCESS, + + /** Represents an error response type. */ ERROR, + + /** Represents an unauthorized response type. */ UNAUTHORIZED } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLSuccessResponse.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLSuccessResponse.java index c5a709c37a5..0721f7d2769 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLSuccessResponse.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/graphql/internal/response/GraphQLSuccessResponse.java @@ -16,12 +16,33 @@ import com.fasterxml.jackson.annotation.JsonIgnore; +/** + * This class represents a successful GraphQL response. + * + *

It extends the GraphQLResponse class and overrides the getType method to return + * GraphQLResponseType.SUCCESS. + */ public class GraphQLSuccessResponse extends GraphQLResponse { + /** + * Constructor for GraphQLSuccessResponse. + * + *

It calls the parent constructor with the provided data as the argument. + * + * @param data the data to be included in the successful response. + */ public GraphQLSuccessResponse(final Object data) { super(data); } + /** + * Returns the type of the GraphQL response. + * + *

This method is overridden to return GraphQLResponseType.SUCCESS, indicating a successful + * response. + * + * @return GraphQLResponseType.SUCCESS + */ @Override @JsonIgnore public GraphQLResponseType getType() { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/handlers/HandlerFactory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/handlers/HandlerFactory.java index 2d3639d52f6..03465f70f21 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/handlers/HandlerFactory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/handlers/HandlerFactory.java @@ -22,6 +22,7 @@ import java.util.Collection; import java.util.Map; import java.util.Optional; +import java.util.function.Function; import java.util.stream.Collectors; import io.opentelemetry.api.trace.Tracer; @@ -35,7 +36,8 @@ public static Handler timeout( assert methods != null && globalOptions != null; return TimeoutHandler.handler( Optional.of(globalOptions), - methods.keySet().stream().collect(Collectors.toMap(String::new, ignored -> globalOptions))); + methods.keySet().stream() + .collect(Collectors.toMap(Function.identity(), ignored -> globalOptions))); } public static Handler authentication( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/handlers/JsonRpcArrayExecutor.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/handlers/JsonRpcArrayExecutor.java index 5a839d8ad07..72d6c5b6152 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/handlers/JsonRpcArrayExecutor.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/handlers/JsonRpcArrayExecutor.java @@ -22,8 +22,8 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.execution.JsonRpcExecutor; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponseType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; import java.io.IOException; @@ -74,7 +74,7 @@ public void executeRpcRequestBatch( generator.writeStartArray(); for (int i = 0; i < rpcRequestBatch.size(); i++) { JsonRpcResponse response = processMaybeRequest(rpcRequestBatch.getValue(i)); - if (response.getType() != JsonRpcResponseType.NONE) { + if (response.getType() != RpcResponseType.NONE) { generator.writeObject(response); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/handlers/JsonRpcExecutorHandler.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/handlers/JsonRpcExecutorHandler.java index 5e4c4af9c40..278a38c5578 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/handlers/JsonRpcExecutorHandler.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/handlers/JsonRpcExecutorHandler.java @@ -58,11 +58,13 @@ public static Handler handler( } catch (IOException e) { final String method = executor.getRpcMethodName(ctx); LOG.error("{} - Error streaming JSON-RPC response", method, e); - throw new RuntimeException(e); + handleJsonRpcError(ctx, null, RpcErrorType.INTERNAL_ERROR); } }, () -> handleJsonRpcError(ctx, null, RpcErrorType.PARSE_ERROR)); } catch (final RuntimeException e) { + final String method = ctx.get(ContextKey.REQUEST_BODY_AS_JSON_OBJECT.name()); + LOG.error("Unhandled exception in JSON-RPC executor for method {}", method, e); handleJsonRpcError(ctx, null, RpcErrorType.INTERNAL_ERROR); } }; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/handlers/JsonRpcObjectExecutor.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/handlers/JsonRpcObjectExecutor.java index d2a2cb37428..70c78e9ab3e 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/handlers/JsonRpcObjectExecutor.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/handlers/JsonRpcObjectExecutor.java @@ -22,8 +22,8 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.execution.JsonRpcExecutor; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponseType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; import java.io.IOException; @@ -70,7 +70,7 @@ private void handleJsonObjectResponse( final RoutingContext ctx) throws IOException { response.setStatusCode(status(jsonRpcResponse).code()); - if (jsonRpcResponse.getType() == JsonRpcResponseType.NONE) { + if (jsonRpcResponse.getType() == RpcResponseType.NONE) { response.end(); } else { try (final JsonResponseStreamer streamer = diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/EngineJsonRpcService.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/EngineJsonRpcService.java index 42cd1d7c80a..b9f98f3b66c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/EngineJsonRpcService.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/EngineJsonRpcService.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,7 +15,6 @@ package org.hyperledger.besu.ethereum.api.jsonrpc; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.collect.Streams.stream; import static org.apache.tuweni.net.tls.VertxTrustOptions.allowlistClients; import static org.hyperledger.besu.ethereum.api.jsonrpc.authentication.AuthenticationUtils.truncToken; @@ -59,11 +58,10 @@ import java.util.StringJoiner; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicInteger; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Splitter; -import com.google.common.collect.Iterables; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator; import io.opentelemetry.api.trace.Span; @@ -82,13 +80,13 @@ import io.vertx.core.buffer.Buffer; import io.vertx.core.http.ClientAuth; import io.vertx.core.http.HttpConnection; -import io.vertx.core.http.HttpHeaders; import io.vertx.core.http.HttpMethod; import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.http.HttpServerRequest; import io.vertx.core.http.HttpServerResponse; import io.vertx.core.http.ServerWebSocket; +import io.vertx.core.net.HostAndPort; import io.vertx.core.net.PfxOptions; import io.vertx.core.net.SocketAddress; import io.vertx.ext.auth.User; @@ -97,7 +95,6 @@ import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.handler.BodyHandler; import io.vertx.ext.web.handler.CorsHandler; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -152,6 +149,8 @@ public String get(final @Nullable HttpServerRequest carrier, final String key) { private final HealthService livenessService; private final HealthService readinessService; + private final MetricsSystem metricsSystem; + /** * Construct a EngineJsonRpcService to handle either http or websocket clients * @@ -217,6 +216,7 @@ public EngineJsonRpcService( this.livenessService = livenessService; this.readinessService = readinessService; this.maxActiveConnections = config.getMaxActiveConnections(); + this.metricsSystem = metricsSystem; } public CompletableFuture start() { @@ -382,7 +382,7 @@ private Handler webSocketHandler() { }; } - @NotNull + @Nonnull private Handler handlerForUser( final SocketAddress socketAddress, final ServerWebSocket websocket, @@ -428,6 +428,7 @@ private Router buildRouter() { .handler( BodyHandler.create() .setUploadsDirectory(dataDir.resolve("uploads").toString()) + .setBodyLimit(128 * 1024 * 1024) .setDeleteUploadedFilesOnEnd(true)); router.route("/").method(HttpMethod.GET).handler(this::handleEmptyRequest); router @@ -453,7 +454,8 @@ private Router buildRouter() { new JsonRpcExecutor( new AuthenticatedJsonRpcProcessor( new TimedJsonRpcProcessor( - new TracedJsonRpcProcessor(new BaseJsonRpcProcessor()), requestTimer), + new TracedJsonRpcProcessor(new BaseJsonRpcProcessor(), metricsSystem), + requestTimer), authenticationService.get(), config.getNoAuthRpcApis()), rpcMethods), @@ -465,7 +467,8 @@ private Router buildRouter() { HandlerFactory.jsonRpcExecutor( new JsonRpcExecutor( new TimedJsonRpcProcessor( - new TracedJsonRpcProcessor(new BaseJsonRpcProcessor()), requestTimer), + new TracedJsonRpcProcessor(new BaseJsonRpcProcessor(), metricsSystem), + requestTimer), rpcMethods), tracer, config), @@ -615,19 +618,8 @@ private Handler denyRouteToBlockedHost() { } private Optional getAndValidateHostHeader(final RoutingContext event) { - String hostname = - event.request().getHeader(HttpHeaders.HOST) != null - ? event.request().getHeader(HttpHeaders.HOST) - : event.request().host(); - final Iterable splitHostHeader = Splitter.on(':').split(hostname); - final long hostPieces = stream(splitHostHeader).count(); - // If the host contains a colon, verify the host is correctly formed - host [ ":" port ] - if (hostPieces > 1) { - if (hostPieces > 2 || !Iterables.get(splitHostHeader, 1).matches("\\d{1,5}+")) { - return Optional.empty(); - } - } - return Optional.ofNullable(Iterables.get(splitHostHeader, 0)); + final HostAndPort hostAndPort = event.request().authority(); + return Optional.ofNullable(hostAndPort).map(HostAndPort::host); } private boolean hostIsInAllowlist(final String hostHeader) { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/InProcessRpcConfiguration.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/InProcessRpcConfiguration.java new file mode 100644 index 00000000000..e59d06a21fb --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/InProcessRpcConfiguration.java @@ -0,0 +1,36 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc; + +import java.util.HashSet; +import java.util.Set; + +import org.immutables.value.Value; + +@Value.Immutable +public interface InProcessRpcConfiguration { + boolean DEFAULT_IN_PROCESS_RPC_ENABLED = false; + Set DEFAULT_IN_PROCESS_RPC_APIS = new HashSet<>(RpcApis.DEFAULT_RPC_APIS); + + @Value.Default + default boolean isEnabled() { + return DEFAULT_IN_PROCESS_RPC_ENABLED; + } + + @Value.Default + default Set getInProcessRpcApis() { + return DEFAULT_IN_PROCESS_RPC_APIS; + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonResponseStreamer.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonResponseStreamer.java index 563538d22fa..b3b4f9af3f3 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonResponseStreamer.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonResponseStreamer.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcErrorConverter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcErrorConverter.java index 89107497d51..30ad7917a67 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcErrorConverter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcErrorConverter.java @@ -75,10 +75,16 @@ public static RpcErrorType convertTransactionInvalidReason( return RpcErrorType.TOTAL_BLOB_GAS_TOO_HIGH; case TX_POOL_DISABLED: return RpcErrorType.TX_POOL_DISABLED; - case PLUGIN_TX_VALIDATOR: + case PLUGIN_TX_POOL_VALIDATOR: return RpcErrorType.PLUGIN_TX_VALIDATOR; case INVALID_BLOBS: return RpcErrorType.INVALID_BLOBS; + case BLOB_GAS_PRICE_BELOW_CURRENT_BLOB_BASE_FEE: + return RpcErrorType.BLOB_GAS_PRICE_BELOW_CURRENT_BLOB_BASE_FEE; + case EXECUTION_HALTED: + return RpcErrorType.EXECUTION_HALTED; + case BLOCK_NOT_FOUND: + return RpcErrorType.BLOCK_NOT_FOUND; default: return RpcErrorType.INTERNAL_ERROR; } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpService.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpService.java index 12d623bdacb..af50b53bb1c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpService.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpService.java @@ -15,7 +15,6 @@ package org.hyperledger.besu.ethereum.api.jsonrpc; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.collect.Streams.stream; import static org.apache.tuweni.net.tls.VertxTrustOptions.allowlistClients; import org.hyperledger.besu.ethereum.api.handlers.HandlerFactory; @@ -55,8 +54,6 @@ import javax.annotation.Nullable; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Splitter; -import com.google.common.collect.Iterables; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator; import io.opentelemetry.api.trace.Span; @@ -74,12 +71,12 @@ import io.vertx.core.VertxException; import io.vertx.core.http.ClientAuth; import io.vertx.core.http.HttpConnection; -import io.vertx.core.http.HttpHeaders; import io.vertx.core.http.HttpMethod; import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.http.HttpServerRequest; import io.vertx.core.http.HttpServerResponse; +import io.vertx.core.net.HostAndPort; import io.vertx.core.net.PfxOptions; import io.vertx.core.net.SocketAddress; import io.vertx.ext.web.Route; @@ -137,6 +134,7 @@ public String get(final @Nullable HttpServerRequest carrier, final String key) { private HttpServer httpServer; private final HealthService livenessService; private final HealthService readinessService; + private final MetricsSystem metricsSystem; /** * Construct a JsonRpcHttpService handler @@ -207,6 +205,7 @@ public JsonRpcHttpService( if (metricsSystem instanceof OpenTelemetrySystem) { this.tracerProvider = ((OpenTelemetrySystem) metricsSystem).getTracerProvider(); } + this.metricsSystem = metricsSystem; } private void validateConfig(final JsonRpcConfiguration config) { @@ -313,7 +312,8 @@ private Router buildRouter() { router .route() .handler( - CorsHandler.create(buildCorsRegexFromConfig()) + CorsHandler.create() + .addRelativeOrigin(buildCorsRegexFromConfig()) .allowedHeader("*") .allowedHeader("content-type")); router @@ -347,7 +347,8 @@ private Router buildRouter() { new JsonRpcExecutor( new AuthenticatedJsonRpcProcessor( new TimedJsonRpcProcessor( - new TracedJsonRpcProcessor(new BaseJsonRpcProcessor()), requestTimer), + new TracedJsonRpcProcessor(new BaseJsonRpcProcessor(), metricsSystem), + requestTimer), authenticationService.get(), config.getNoAuthRpcApis()), rpcMethods), @@ -359,7 +360,8 @@ private Router buildRouter() { HandlerFactory.jsonRpcExecutor( new JsonRpcExecutor( new TimedJsonRpcProcessor( - new TracedJsonRpcProcessor(new BaseJsonRpcProcessor()), requestTimer), + new TracedJsonRpcProcessor(new BaseJsonRpcProcessor(), metricsSystem), + requestTimer), rpcMethods), tracer, config), @@ -507,25 +509,13 @@ private Handler checkAllowlistHostHeader() { } private Optional getAndValidateHostHeader(final RoutingContext event) { - String hostname = - event.request().getHeader(HttpHeaders.HOST) != null - ? event.request().getHeader(HttpHeaders.HOST) - : event.request().host(); - final Iterable splitHostHeader = Splitter.on(':').split(hostname); - final long hostPieces = stream(splitHostHeader).count(); - if (hostPieces > 1) { - // If the host contains a colon, verify the host is correctly formed - host [ ":" port ] - if (hostPieces > 2 || !Iterables.get(splitHostHeader, 1).matches("\\d{1,5}+")) { - return Optional.empty(); - } - } - return Optional.ofNullable(Iterables.get(splitHostHeader, 0)); + final HostAndPort hostAndPort = event.request().authority(); + return Optional.ofNullable(hostAndPort).map(HostAndPort::host); } private boolean hostIsInAllowlist(final String hostHeader) { if (config.getHostsAllowlist().stream() - .anyMatch( - allowlistEntry -> allowlistEntry.toLowerCase().equals(hostHeader.toLowerCase()))) { + .anyMatch(allowlistEntry -> allowlistEntry.equalsIgnoreCase(hostHeader))) { return true; } else { LOG.trace("Host not in allowlist: '{}'", hostHeader); @@ -580,7 +570,7 @@ private String buildCorsRegexFromConfig() { return ""; } if (config.getCorsAllowedDomains().contains("*")) { - return ".*"; + return ".*://.*|.*"; } else { final StringJoiner stringJoiner = new StringJoiner("|"); config.getCorsAllowedDomains().stream().filter(s -> !s.isEmpty()).forEach(stringJoiner::add); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java index d719e5b48b5..f8bc597ff7c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcMethod.java @@ -51,18 +51,20 @@ public enum RpcMethod { DEBUG_GET_RAW_BLOCK("debug_getRawBlock"), DEBUG_GET_RAW_RECEIPTS("debug_getRawReceipts"), DEBUG_GET_RAW_TRANSACTION("debug_getRawTransaction"), + ENGINE_GET_BLOBS_V1("engine_getBlobsV1"), ENGINE_GET_PAYLOAD_V1("engine_getPayloadV1"), ENGINE_GET_PAYLOAD_V2("engine_getPayloadV2"), ENGINE_GET_PAYLOAD_V3("engine_getPayloadV3"), - ENGINE_GET_PAYLOAD_V6110("engine_getPayloadV6110"), + ENGINE_GET_PAYLOAD_V4("engine_getPayloadV4"), ENGINE_NEW_PAYLOAD_V1("engine_newPayloadV1"), ENGINE_NEW_PAYLOAD_V2("engine_newPayloadV2"), ENGINE_NEW_PAYLOAD_V3("engine_newPayloadV3"), - ENGINE_NEW_PAYLOAD_V6110("engine_newPayloadV6110"), + ENGINE_NEW_PAYLOAD_V4("engine_newPayloadV4"), ENGINE_FORKCHOICE_UPDATED_V1("engine_forkchoiceUpdatedV1"), ENGINE_FORKCHOICE_UPDATED_V2("engine_forkchoiceUpdatedV2"), ENGINE_FORKCHOICE_UPDATED_V3("engine_forkchoiceUpdatedV3"), ENGINE_EXCHANGE_TRANSITION_CONFIGURATION("engine_exchangeTransitionConfigurationV1"), + ENGINE_GET_CLIENT_VERSION_V1("engine_getClientVersionV1"), ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1("engine_getPayloadBodiesByHashV1"), ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1("engine_getPayloadBodiesByRangeV1"), ENGINE_EXCHANGE_CAPABILITIES("engine_exchangeCapabilities"), @@ -86,6 +88,7 @@ public enum RpcMethod { PRIV_GET_FILTER_LOGS("priv_getFilterLogs"), PRIV_SUBSCRIBE("priv_subscribe"), PRIV_UNSUBSCRIBE("priv_unsubscribe"), + PRIV_TRACE_TRANSACTION("priv_traceTransaction"), PRIVX_FIND_PRIVACY_GROUP_OLD("privx_findOnchainPrivacyGroup"), PRIVX_FIND_PRIVACY_GROUP("privx_findFlexiblePrivacyGroup"), EEA_SEND_RAW_TRANSACTION("eea_sendRawTransaction"), @@ -98,6 +101,7 @@ public enum RpcMethod { ETH_CREATE_ACCESS_LIST("eth_createAccessList"), ETH_FEE_HISTORY("eth_feeHistory"), ETH_GAS_PRICE("eth_gasPrice"), + ETH_BLOB_BASE_FEE("eth_blobBaseFee"), ETH_GET_BALANCE("eth_getBalance"), ETH_GET_BLOCK_BY_HASH("eth_getBlockByHash"), ETH_GET_BLOCK_BY_NUMBER("eth_getBlockByNumber"), @@ -108,6 +112,7 @@ public enum RpcMethod { ETH_GET_FILTER_CHANGES("eth_getFilterChanges"), ETH_GET_FILTER_LOGS("eth_getFilterLogs"), ETH_GET_LOGS("eth_getLogs"), + ETH_GET_MAX_PRIORITY_FEE_PER_GAS("eth_maxPriorityFeePerGas"), ETH_GET_MINER_DATA_BY_BLOCK_HASH("eth_getMinerDataByBlockHash"), ETH_GET_MINER_DATA_BY_BLOCK_NUMBER("eth_getMinerDataByBlockNumber"), ETH_GET_PROOF("eth_getProof"), @@ -149,6 +154,7 @@ public enum RpcMethod { QBFT_GET_VALIDATORS_BY_BLOCK_NUMBER("qbft_getValidatorsByBlockNumber"), QBFT_PROPOSE_VALIDATOR_VOTE("qbft_proposeValidatorVote"), QBFT_GET_SIGNER_METRICS("qbft_getSignerMetrics"), + QBFT_GET_REQUEST_TIMEOUT_SECONDS("qbft_getRequestTimeoutSeconds"), MINER_CHANGE_TARGET_GAS_LIMIT("miner_changeTargetGasLimit"), MINER_SET_COINBASE("miner_setCoinbase"), MINER_SET_ETHERBASE("miner_setEtherbase"), @@ -158,6 +164,8 @@ public enum RpcMethod { MINER_SET_MIN_PRIORITY_FEE("miner_setMinPriorityFee"), MINER_GET_MIN_GAS_PRICE("miner_getMinGasPrice"), MINER_SET_MIN_GAS_PRICE("miner_setMinGasPrice"), + MINER_GET_EXTRA_DATA("miner_getExtraData"), + MINER_SET_EXTRA_DATA("miner_setExtraData"), NET_ENODE("net_enode"), NET_LISTENING("net_listening"), NET_PEER_COUNT("net_peerCount"), @@ -172,9 +180,7 @@ public enum RpcMethod { PERM_GET_NODES_WHITELIST("perm_getNodesWhitelist"), PERM_GET_NODES_ALLOWLIST("perm_getNodesAllowlist"), PERM_RELOAD_PERMISSIONS_FROM_FILE("perm_reloadPermissionsFromFile"), - PERM_REMOVE_ACCOUNTS_FROM_WHITELIST("perm_removeAccountsFromWhitelist"), PERM_REMOVE_ACCOUNTS_FROM_ALLOWLIST("perm_removeAccountsFromAllowlist"), - PERM_REMOVE_NODES_FROM_WHITELIST("perm_removeNodesFromWhitelist"), PERM_REMOVE_NODES_FROM_ALLOWLIST("perm_removeNodesFromAllowlist"), RPC_MODULES("rpc_modules"), TRACE_BLOCK("trace_block"), diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java index ae8fd605dec..1e016dbd6e9 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationService.java @@ -1,8 +1,8 @@ /* - * Copyright Hyperledger Besu. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.api.jsonrpc.authentication; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationUtils.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationUtils.java index 9a14b6bf2f1..7ad16b2ed0c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationUtils.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/AuthenticationUtils.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/DefaultAuthenticationService.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/DefaultAuthenticationService.java index 96a1a2d023f..935a46a84db 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/DefaultAuthenticationService.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/DefaultAuthenticationService.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -46,6 +46,7 @@ public class DefaultAuthenticationService implements AuthenticationService { public static final String USERNAME = "username"; + public static final String PASSWORD = "password"; private final JWTAuth jwtAuthProvider; @VisibleForTesting public final JWTAuthOptions jwtAuthOptions; private final Optional credentialAuthProvider; @@ -171,19 +172,21 @@ private void login( final RoutingContext routingContext, final AuthenticationProvider credentialAuthProvider) { final JsonObject requestBody = routingContext.body().asJsonObject(); - if (requestBody == null) { + if (requestBody == null + || requestBody.getValue(USERNAME) == null + || requestBody.getValue(PASSWORD) == null) { routingContext .response() .setStatusCode(HttpResponseStatus.BAD_REQUEST.code()) .setStatusMessage(HttpResponseStatus.BAD_REQUEST.reasonPhrase()) - .end(); + .end("Authentication failed: username and password are required."); return; } // Check user final JsonObject authParams = new JsonObject(); authParams.put(USERNAME, requestBody.getValue(USERNAME)); - authParams.put("password", requestBody.getValue("password")); + authParams.put(PASSWORD, requestBody.getValue(PASSWORD)); final Credentials credentials = new UsernamePasswordCredentials(authParams); credentialAuthProvider.authenticate( @@ -194,7 +197,7 @@ private void login( .response() .setStatusCode(HttpResponseStatus.UNAUTHORIZED.code()) .setStatusMessage(HttpResponseStatus.UNAUTHORIZED.reasonPhrase()) - .end(); + .end("Authentication failed: the username or password is incorrect."); } else { final User user = r.result(); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/EngineAuthService.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/EngineAuthService.java index db388567c2d..942079ab8dd 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/EngineAuthService.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/EngineAuthService.java @@ -1,8 +1,8 @@ /* - * Copyright Hyperledger Besu. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.api.jsonrpc.authentication; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java index 071fc2920ab..854368453a5 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JWTAuthOptionsFactory.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JwtAlgorithm.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JwtAlgorithm.java index 4e5341a28e6..ef5f0826338 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JwtAlgorithm.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/JwtAlgorithm.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/UnsecurableEngineApiException.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/UnsecurableEngineApiException.java index 3dfc371f78b..da19e8d258b 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/UnsecurableEngineApiException.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/UnsecurableEngineApiException.java @@ -1,8 +1,8 @@ /* - * Copyright Hyperledger Besu. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.api.jsonrpc.authentication; /* diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/execution/BaseJsonRpcProcessor.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/execution/BaseJsonRpcProcessor.java index 3d823886484..db6481b46aa 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/execution/BaseJsonRpcProcessor.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/execution/BaseJsonRpcProcessor.java @@ -43,8 +43,12 @@ public JsonRpcResponse process( try { return method.response(request); } catch (final InvalidJsonRpcParameters e) { - LOG.debug("Invalid Params for method: {}", method.getName(), e); - return new JsonRpcErrorResponse(id, RpcErrorType.INVALID_PARAMS); + LOG.debug( + "Invalid Params for method: {}, error: {}", + method.getName(), + e.getRpcErrorType().getMessage(), + e); + return new JsonRpcErrorResponse(id, e.getRpcErrorType()); } catch (final MultiTenancyValidationException e) { return new JsonRpcUnauthorizedResponse(id, RpcErrorType.UNAUTHORIZED); } catch (final RuntimeException e) { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/execution/TracedJsonRpcProcessor.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/execution/TracedJsonRpcProcessor.java index ecde766f8b3..ec0e6aef64a 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/execution/TracedJsonRpcProcessor.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/execution/TracedJsonRpcProcessor.java @@ -19,7 +19,11 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponseType; +import org.hyperledger.besu.metrics.BesuMetricCategory; +import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.metrics.Counter; +import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.StatusCode; @@ -27,9 +31,18 @@ public class TracedJsonRpcProcessor implements JsonRpcProcessor { private final JsonRpcProcessor rpcProcessor; + protected final LabelledMetric rpcErrorsCounter; - public TracedJsonRpcProcessor(final JsonRpcProcessor rpcProcessor) { + public TracedJsonRpcProcessor( + final JsonRpcProcessor rpcProcessor, final MetricsSystem metricsSystem) { this.rpcProcessor = rpcProcessor; + this.rpcErrorsCounter = + metricsSystem.createLabelledCounter( + BesuMetricCategory.RPC, + "errors_count", + "Number of errors per RPC method and RPC error type", + "rpcMethod", + "errorType"); } @Override @@ -39,10 +52,77 @@ public JsonRpcResponse process( final Span metricSpan, final JsonRpcRequestContext request) { JsonRpcResponse jsonRpcResponse = rpcProcessor.process(id, method, metricSpan, request); - if (JsonRpcResponseType.ERROR == jsonRpcResponse.getType()) { + if (RpcResponseType.ERROR == jsonRpcResponse.getType()) { JsonRpcErrorResponse errorResponse = (JsonRpcErrorResponse) jsonRpcResponse; + this.rpcErrorsCounter.labels(method.getName(), errorResponse.getErrorType().name()).inc(); switch (errorResponse.getErrorType()) { case INVALID_PARAMS: + case INVALID_ACCOUNT_PARAMS: + case INVALID_ADDRESS_HASH_PARAMS: + case INVALID_ADDRESS_PARAMS: + case INVALID_BLOB_COUNT: + case INVALID_BLOB_GAS_USED_PARAMS: + case INVALID_BLOCK_PARAMS: + case INVALID_BLOCK_COUNT_PARAMS: + case INVALID_BLOCK_HASH_PARAMS: + case INVALID_BLOCK_INDEX_PARAMS: + case INVALID_BLOCK_NUMBER_PARAMS: + case INVALID_CALL_PARAMS: + case INVALID_CONSOLIDATION_REQUEST_PARAMS: + case INVALID_CREATE_PRIVACY_GROUP_PARAMS: + case INVALID_DATA_PARAMS: + case INVALID_DEPOSIT_REQUEST_PARAMS: + case INVALID_ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_PARAMS: + case INVALID_ENGINE_FORKCHOICE_UPDATED_PARAMS: + case INVALID_ENGINE_FORKCHOICE_UPDATED_PAYLOAD_ATTRIBUTES: + case INVALID_ENGINE_NEW_PAYLOAD_PARAMS: + case INVALID_ENGINE_PREPARE_PAYLOAD_PARAMS: + case INVALID_ENODE_PARAMS: + case INVALID_EXCESS_BLOB_GAS_PARAMS: + case INVALID_EXTRA_DATA_PARAMS: + case INVALID_FILTER_PARAMS: + case INVALID_GAS_PRICE_PARAMS: + case INVALID_HASH_RATE_PARAMS: + case INVALID_ID_PARAMS: + case INVALID_RETURN_COMPLETE_TRANSACTION_PARAMS: + case INVALID_LOG_FILTER_PARAMS: + case INVALID_LOG_LEVEL_PARAMS: + case INVALID_MAX_RESULTS_PARAMS: + case INVALID_METHOD_PARAMS: + case INVALID_MIN_GAS_PRICE_PARAMS: + case INVALID_MIN_PRIORITY_FEE_PARAMS: + case INVALID_MIX_HASH_PARAMS: + case INVALID_NONCE_PARAMS: + case INVALID_PARENT_BEACON_BLOCK_ROOT_PARAMS: + case INVALID_PARAM_COUNT: + case INVALID_PAYLOAD_ID_PARAMS: + case INVALID_PENDING_TRANSACTIONS_PARAMS: + case INVAlID_PLUGIN_NAME_PARAMS: + case INVALID_POSITION_PARAMS: + case INVALID_POW_HASH_PARAMS: + case INVALID_PRIVACY_GROUP_PARAMS: + case INVALID_PRIVATE_FROM_PARAMS: + case INVALID_PRIVATE_FOR_PARAMS: + case INVALID_PROPOSAL_PARAMS: + case INVALID_REMOTE_CAPABILITIES_PARAMS: + case INVALID_REWARD_PERCENTILES_PARAMS: + case INVALID_SEALER_ID_PARAMS: + case INVALID_STORAGE_KEYS_PARAMS: + case INVALID_SUBSCRIPTION_PARAMS: + case INVALID_TARGET_GAS_LIMIT_PARAMS: + case INVALID_TIMESTAMP_PARAMS: + case INVALID_TRACE_CALL_MANY_PARAMS: + case INVALID_TRACE_NUMBERS_PARAMS: + case INVALID_TRACE_TYPE_PARAMS: + case INVALID_TRANSACTION_PARAMS: + case INVALID_TRANSACTION_HASH_PARAMS: + case INVALID_TRANSACTION_ID_PARAMS: + case INVALID_TRANSACTION_INDEX_PARAMS: + case INVALID_TRANSACTION_LIMIT_PARAMS: + case INVALID_TRANSACTION_TRACE_PARAMS: + case INVALID_VERSIONED_HASH_PARAMS: + case INVALID_VOTE_TYPE_PARAMS: + case INVALID_WITHDRAWALS_PARAMS: metricSpan.setStatus(StatusCode.ERROR, "Invalid Params"); break; case UNAUTHORIZED: diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/DebugReplayBlock.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/DebugReplayBlock.java index fc8c3d0656e..ab53e415dd5 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/DebugReplayBlock.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/DebugReplayBlock.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -19,10 +19,13 @@ import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.AbstractBlockParameterMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; @@ -56,7 +59,12 @@ public String getName() { @Override protected BlockParameter blockParameter(final JsonRpcRequestContext request) { - return request.getRequiredParameter(0, BlockParameter.class); + try { + return request.getRequiredParameter(0, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } @Override diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/JsonRpcRequest.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/JsonRpcRequest.java index db7fe131054..a7c1e15e01d 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/JsonRpcRequest.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/JsonRpcRequest.java @@ -16,6 +16,8 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcRequestException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import java.util.Arrays; import java.util.List; @@ -51,7 +53,8 @@ public JsonRpcRequest( this.method = method; this.params = params; if (method == null) { - throw new InvalidJsonRpcRequestException("Field 'method' is required"); + throw new InvalidJsonRpcRequestException( + "Field 'method' is required", RpcErrorType.INVALID_METHOD_PARAMS); } } @@ -130,15 +133,18 @@ public int hashCode() { return Objects.hash(id, method, Arrays.hashCode(params), version, isNotification); } - public T getRequiredParameter(final int index, final Class paramClass) { + public T getRequiredParameter(final int index, final Class paramClass) + throws JsonRpcParameterException { return parameterAccessor.required(params, index, paramClass); } - public Optional getOptionalParameter(final int index, final Class paramClass) { + public Optional getOptionalParameter(final int index, final Class paramClass) + throws JsonRpcParameterException { return parameterAccessor.optional(params, index, paramClass); } - public Optional> getOptionalList(final int index, final Class paramClass) { + public Optional> getOptionalList(final int index, final Class paramClass) + throws JsonRpcParameterException { return parameterAccessor.optionalList(params, index, paramClass); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/JsonRpcRequestContext.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/JsonRpcRequestContext.java index b489259eb82..cc96384005b 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/JsonRpcRequestContext.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/JsonRpcRequestContext.java @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; + import java.util.List; import java.util.Objects; import java.util.Optional; @@ -61,15 +63,18 @@ public Optional getUser() { return user; } - public T getRequiredParameter(final int index, final Class paramClass) { + public T getRequiredParameter(final int index, final Class paramClass) + throws JsonRpcParameterException { return jsonRpcRequest.getRequiredParameter(index, paramClass); } - public Optional getOptionalParameter(final int index, final Class paramClass) { + public Optional getOptionalParameter(final int index, final Class paramClass) + throws JsonRpcParameterException { return jsonRpcRequest.getOptionalParameter(index, paramClass); } - public Optional> getOptionalList(final int index, final Class listOf) { + public Optional> getOptionalList(final int index, final Class listOf) + throws JsonRpcParameterException { return jsonRpcRequest.getOptionalList(index, listOf); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/JsonRpcRequestId.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/JsonRpcRequestId.java index 678eae61b74..8944b0d84c1 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/JsonRpcRequestId.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/JsonRpcRequestId.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcRequestException; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import java.math.BigInteger; import java.util.Objects; @@ -34,7 +35,7 @@ public class JsonRpcRequestId { @JsonCreator public JsonRpcRequestId(final Object id) { if (isRequestTypeInvalid(id)) { - throw new InvalidJsonRpcRequestException("Invalid id"); + throw new InvalidJsonRpcRequestException("Invalid id", RpcErrorType.INVALID_ID_PARAMS); } this.id = id; } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/exception/InvalidJsonRpcParameters.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/exception/InvalidJsonRpcParameters.java index 65fe455bd5c..ea28ca7c4a2 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/exception/InvalidJsonRpcParameters.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/exception/InvalidJsonRpcParameters.java @@ -14,13 +14,24 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; + public class InvalidJsonRpcParameters extends InvalidJsonRpcRequestException { public InvalidJsonRpcParameters(final String s) { super(s); } + public InvalidJsonRpcParameters(final String message, final RpcErrorType rpcErrorType) { + super(message, rpcErrorType); + } + public InvalidJsonRpcParameters(final String message, final Throwable cause) { super(message, cause); } + + public InvalidJsonRpcParameters( + final String message, final RpcErrorType rpcErrorType, final Throwable cause) { + super(message, rpcErrorType, cause); + } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/exception/InvalidJsonRpcRequestException.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/exception/InvalidJsonRpcRequestException.java index 5353439e0b0..7dcda5939c8 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/exception/InvalidJsonRpcRequestException.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/exception/InvalidJsonRpcRequestException.java @@ -14,12 +14,34 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; + public class InvalidJsonRpcRequestException extends IllegalArgumentException { + + private final RpcErrorType rpcErrorType; + public InvalidJsonRpcRequestException(final String message) { super(message); + rpcErrorType = RpcErrorType.INVALID_PARAMS; + } + + public InvalidJsonRpcRequestException(final String message, final RpcErrorType rpcErrorType) { + super(message); + this.rpcErrorType = rpcErrorType; } public InvalidJsonRpcRequestException(final String message, final Throwable cause) { super(message, cause); + rpcErrorType = RpcErrorType.INVALID_PARAMS; + } + + public InvalidJsonRpcRequestException( + final String message, final RpcErrorType rpcErrorType, final Throwable cause) { + super(message, cause); + this.rpcErrorType = rpcErrorType; + } + + public RpcErrorType getRpcErrorType() { + return rpcErrorType; } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/exception/Logging403ErrorHandler.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/exception/Logging403ErrorHandler.java index dcd6d8873df..6cec9294fba 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/exception/Logging403ErrorHandler.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/exception/Logging403ErrorHandler.java @@ -1,8 +1,8 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception; import io.vertx.core.Handler; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractBlockParameterOrBlockHashMethod.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractBlockParameterOrBlockHashMethod.java index 8110bf6c2c4..ba3bcd69c63 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractBlockParameterOrBlockHashMethod.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractBlockParameterOrBlockHashMethod.java @@ -106,7 +106,7 @@ protected Object handleParamTypes(final JsonRpcRequestContext requestContext) { final OptionalLong blockNumber = blockParameterOrBlockHash.getNumber(); if (blockNumber.isEmpty() || blockNumber.getAsLong() < 0) { return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVALID_BLOCK_NUMBER_PARAMS); } else if (blockNumber.getAsLong() > getBlockchainQueries().headBlockNumber()) { return new JsonRpcErrorResponse( requestContext.getRequest().getId(), RpcErrorType.BLOCK_NOT_FOUND); @@ -123,7 +123,7 @@ protected Object handleParamTypes(final JsonRpcRequestContext requestContext) { Optional blockHash = blockParameterOrBlockHash.getHash(); if (blockHash.isEmpty()) { return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVALID_BLOCK_HASH_PARAMS); } // return error if block hash does not find a block diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java index ddfd522c8f7..4cbfb818dbe 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractEstimateGas.java @@ -14,11 +14,15 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonCallParameterUtil.validateAndGetCallParams; + import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcErrorConverter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonCallParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; @@ -34,24 +38,68 @@ import java.util.Optional; -public abstract class AbstractEstimateGas implements JsonRpcMethod { +public abstract class AbstractEstimateGas extends AbstractBlockParameterMethod { private static final double SUB_CALL_REMAINING_GAS_RATIO = 65D / 64D; - protected final BlockchainQueries blockchainQueries; protected final TransactionSimulator transactionSimulator; public AbstractEstimateGas( final BlockchainQueries blockchainQueries, final TransactionSimulator transactionSimulator) { - this.blockchainQueries = blockchainQueries; + super(blockchainQueries); this.transactionSimulator = transactionSimulator; } - protected BlockHeader blockHeader() { - final long headBlockNumber = blockchainQueries.headBlockNumber(); - return blockchainQueries.getBlockchain().getBlockHeader(headBlockNumber).orElse(null); + @Override + protected BlockParameter blockParameter(final JsonRpcRequestContext request) { + try { + return request.getOptionalParameter(1, BlockParameter.class).orElse(BlockParameter.LATEST); + } catch (JsonRpcParameter.JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 1)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } + } + + protected Optional blockHeader(final long blockNumber) { + if (getBlockchainQueries().headBlockNumber() == blockNumber) { + // chain head header if cached, and we can return it form memory + return Optional.of(getBlockchainQueries().getBlockchain().getChainHeadHeader()); + } + return getBlockchainQueries().getBlockHeaderByNumber(blockNumber); + } + + protected Optional validateBlockHeader( + final Optional maybeBlockHeader) { + if (maybeBlockHeader.isEmpty()) { + return Optional.of(RpcErrorType.BLOCK_NOT_FOUND); + } + + final var blockHeader = maybeBlockHeader.get(); + if (!getBlockchainQueries() + .getWorldStateArchive() + .isWorldStateAvailable(blockHeader.getStateRoot(), blockHeader.getHash())) { + return Optional.of(RpcErrorType.WORLD_STATE_UNAVAILABLE); + } + return Optional.empty(); + } + + @Override + protected Object resultByBlockNumber( + final JsonRpcRequestContext requestContext, final long blockNumber) { + final JsonCallParameter jsonCallParameter = validateAndGetCallParams(requestContext); + final Optional maybeBlockHeader = blockHeader(blockNumber); + final Optional jsonRpcError = validateBlockHeader(maybeBlockHeader); + if (jsonRpcError.isPresent()) { + return errorResponse(requestContext, jsonRpcError.get()); + } + return resultByBlockHeader(requestContext, jsonCallParameter, maybeBlockHeader.get()); } + protected abstract Object resultByBlockHeader( + final JsonRpcRequestContext requestContext, + final JsonCallParameter jsonCallParameter, + final BlockHeader blockHeader); + protected CallParameter overrideGasLimitAndPrice( final JsonCallParameter callParams, final long gasLimit) { return new CallParameter( @@ -81,32 +129,25 @@ protected long processEstimateGas( Math.pow(SUB_CALL_REMAINING_GAS_RATIO, operationTracer.getMaxDepth()); // and minimum gas remaining is necessary for some operation (additionalStipend) final long gasStipend = operationTracer.getStipendNeeded(); - final long gasUsedByTransaction = result.getResult().getEstimateGasUsedByTransaction(); + final long gasUsedByTransaction = result.result().getEstimateGasUsedByTransaction(); return ((long) ((gasUsedByTransaction + gasStipend) * subCallMultiplier)); } - protected JsonCallParameter validateAndGetCallParams(final JsonRpcRequestContext request) { - final JsonCallParameter callParams = request.getRequiredParameter(0, JsonCallParameter.class); - if (callParams.getGasPrice() != null - && (callParams.getMaxFeePerGas().isPresent() - || callParams.getMaxPriorityFeePerGas().isPresent())) { - throw new InvalidJsonRpcParameters("gasPrice cannot be used with baseFee or maxFeePerGas"); - } - return callParams; - } - protected JsonRpcErrorResponse errorResponse( final JsonRpcRequestContext request, final TransactionSimulatorResult result) { final ValidationResult validationResult = result.getValidationResult(); if (validationResult != null && !validationResult.isValid()) { + if (validationResult.getErrorMessage().length() > 0) { + return errorResponse(request, JsonRpcError.from(validationResult)); + } return errorResponse( request, JsonRpcErrorConverter.convertTransactionInvalidReason( validationResult.getInvalidReason())); } else { - final TransactionProcessingResult resultTrx = result.getResult(); + final TransactionProcessingResult resultTrx = result.result(); if (resultTrx != null && resultTrx.getRevertReason().isPresent()) { return errorResponse( request, diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractTraceByBlock.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractTraceByBlock.java index 92bfa099c9c..36991796b51 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractTraceByBlock.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractTraceByBlock.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,9 +17,12 @@ import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.TraceTypeParameter.TraceType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.TraceTypeParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTrace; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.TraceCallResult; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.diff.StateDiffGenerator; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.diff.StateDiffTrace; @@ -62,8 +65,13 @@ protected AbstractTraceByBlock( @Override protected BlockParameter blockParameter(final JsonRpcRequestContext request) { - final Optional maybeBlockParameter = - request.getOptionalParameter(2, BlockParameter.class); + final Optional maybeBlockParameter; + try { + maybeBlockParameter = request.getOptionalParameter(2, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 2)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } if (maybeBlockParameter.isPresent()) { return maybeBlockParameter.get(); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractTraceByHash.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractTraceByHash.java index 281d7248108..545d8f76ae3 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractTraceByHash.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractTraceByHash.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -78,19 +78,18 @@ private TransactionTrace getTransactionTrace(final Block block, final Hash trans return Tracer.processTracing( blockchainQueries, Optional.of(block.getHeader()), - mutableWorldState -> { - return blockTracerSupplier - .get() - .trace( - mutableWorldState, - block, - new DebugOperationTracer(new TraceOptions(false, false, true))) - .map(BlockTrace::getTransactionTraces) - .orElse(Collections.emptyList()) - .stream() - .filter(trxTrace -> trxTrace.getTransaction().getHash().equals(transactionHash)) - .findFirst(); - }) + mutableWorldState -> + blockTracerSupplier + .get() + .trace( + mutableWorldState, + block, + new DebugOperationTracer(new TraceOptions(false, false, true), false)) + .map(BlockTrace::getTransactionTraces) + .orElse(Collections.emptyList()) + .stream() + .filter(trxTrace -> trxTrace.getTransaction().getHash().equals(transactionHash)) + .findFirst()) .orElseThrow(); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractTraceCall.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractTraceCall.java index f9a41f353a3..cb691bbaa53 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractTraceCall.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AbstractTraceCall.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -36,11 +36,20 @@ public abstract class AbstractTraceCall extends AbstractTraceByBlock { private static final Logger LOG = LoggerFactory.getLogger(AbstractTraceCall.class); - public AbstractTraceCall( + /** + * A flag to indicate if call operations should trace just the operation cost (false, Geth style, + * debug_ series RPCs) or the operation cost and all gas granted to the child call (true, Parity + * style, trace_ series RPCs) + */ + private final boolean recordChildCallGas; + + protected AbstractTraceCall( final BlockchainQueries blockchainQueries, final ProtocolSchedule protocolSchedule, - final TransactionSimulator transactionSimulator) { + final TransactionSimulator transactionSimulator, + final boolean recordChildCallGas) { super(blockchainQueries, protocolSchedule, transactionSimulator); + this.recordChildCallGas = recordChildCallGas; } @Override @@ -65,7 +74,7 @@ protected Object resultByBlockNumber( return new JsonRpcErrorResponse(requestContext.getRequest().getId(), BLOCK_NOT_FOUND); } - final DebugOperationTracer tracer = new DebugOperationTracer(traceOptions); + final DebugOperationTracer tracer = new DebugOperationTracer(traceOptions, recordChildCallGas); return transactionSimulator .process( callParams, diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminChangeLogLevel.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminChangeLogLevel.java index 45381303319..6081f8f3404 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminChangeLogLevel.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminChangeLogLevel.java @@ -17,6 +17,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -44,13 +45,24 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { try { - final String logLevel = requestContext.getRequiredParameter(0, String.class); + final String logLevel; + try { + logLevel = requestContext.getRequiredParameter(0, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid log level parameter (index 0)", RpcErrorType.INVALID_LOG_LEVEL_PARAMS, e); + } if (!VALID_PARAMS.contains(logLevel)) { return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVALID_LOG_LEVEL_PARAMS); + } + final Optional optionalLogFilters; + try { + optionalLogFilters = requestContext.getOptionalParameter(1, String[].class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid log filter parameters (index 1)", RpcErrorType.INVALID_LOG_FILTER_PARAMS, e); } - final Optional optionalLogFilters = - requestContext.getOptionalParameter(1, String[].class); optionalLogFilters.ifPresentOrElse( logFilters -> Arrays.stream(logFilters).forEach(logFilter -> setLogLevel(logFilter, logLevel)), @@ -58,7 +70,7 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { return new JsonRpcSuccessResponse(requestContext.getRequest().getId()); } catch (InvalidJsonRpcParameters invalidJsonRpcParameters) { return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), invalidJsonRpcParameters.getRpcErrorType()); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminGenerateLogBloomCache.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminGenerateLogBloomCache.java index c9989ced427..cad60660c33 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminGenerateLogBloomCache.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminGenerateLogBloomCache.java @@ -11,16 +11,17 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import java.util.Optional; @@ -40,8 +41,13 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final Optional startBlockParam = - requestContext.getOptionalParameter(0, BlockParameter.class); + final Optional startBlockParam; + try { + startBlockParam = requestContext.getOptionalParameter(0, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid start block parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } final long startBlock; if (startBlockParam.isEmpty() || startBlockParam.get().isEarliest()) { startBlock = 0; @@ -52,8 +58,13 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { startBlock = Long.MAX_VALUE; } - final Optional stopBlockParam = - requestContext.getOptionalParameter(1, BlockParameter.class); + final Optional stopBlockParam; + try { + stopBlockParam = requestContext.getOptionalParameter(1, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid stop block parameter (index 1)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } final long stopBlock; if (stopBlockParam.isEmpty()) { if (startBlockParam.isEmpty()) { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminLogsRemoveCache.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminLogsRemoveCache.java index 01cfb02a1e8..c7b67b28270 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminLogsRemoveCache.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminLogsRemoveCache.java @@ -16,7 +16,9 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -42,10 +44,20 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final Optional startBlockParameter = - requestContext.getOptionalParameter(0, BlockParameter.class); - final Optional stopBlockParameter = - requestContext.getOptionalParameter(1, BlockParameter.class); + final Optional startBlockParameter; + try { + startBlockParameter = requestContext.getOptionalParameter(0, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid start block parameter (index 0)", RpcErrorType.INVALID_BLOCK_NUMBER_PARAMS, e); + } + final Optional stopBlockParameter; + try { + stopBlockParameter = requestContext.getOptionalParameter(1, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid stop block parameter (index 1)", RpcErrorType.INVALID_BLOCK_NUMBER_PARAMS, e); + } final long startBlock; if (startBlockParameter.isEmpty() || startBlockParameter.get().isEarliest()) { @@ -81,7 +93,7 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { if (stopBlock < startBlock) { return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVALID_BLOCK_NUMBER_PARAMS); } final TransactionLogBloomCacher transactionLogBloomCacher = diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminLogsRepairCache.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminLogsRepairCache.java index 757ac2756bb..650a9f74eb4 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminLogsRepairCache.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminLogsRepairCache.java @@ -16,8 +16,11 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.api.query.cache.TransactionLogBloomCacher; @@ -38,7 +41,13 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final Optional blockNumber = requestContext.getOptionalParameter(0, Long.class); + final Optional blockNumber; + try { + blockNumber = requestContext.getOptionalParameter(0, Long.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block number parameter (index 0)", RpcErrorType.INVALID_BLOCK_NUMBER_PARAMS, e); + } if (blockNumber.isPresent() && blockchainQueries.getBlockchain().getBlockByNumber(blockNumber.get()).isEmpty()) { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminModifyPeer.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminModifyPeer.java index 7ad3c9286b9..5b70668c322 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminModifyPeer.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminModifyPeer.java @@ -15,7 +15,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; @@ -40,14 +40,11 @@ protected AdminModifyPeer( public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { if (requestContext.getRequest().getParamLength() != 1) { return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAM_COUNT); } try { final String enodeString = requestContext.getRequiredParameter(0, String.class); return performOperation(requestContext.getRequest().getId(), enodeString); - } catch (final InvalidJsonRpcParameters e) { - return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); } catch (final IllegalArgumentException e) { if (e.getMessage() .endsWith( @@ -69,6 +66,9 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { } catch (final P2PDisabledException e) { return new JsonRpcErrorResponse( requestContext.getRequest().getId(), RpcErrorType.P2P_DISABLED); + } catch (JsonRpcParameterException e) { + return new JsonRpcErrorResponse( + requestContext.getRequest().getId(), RpcErrorType.INVALID_ENODE_PARAMS); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminNodeInfo.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminNodeInfo.java index 8515f9ba1bb..f1633db7841 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminNodeInfo.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminNodeInfo.java @@ -23,6 +23,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.chain.ChainHead; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.p2p.network.P2PNetwork; import org.hyperledger.besu.nat.NatService; import org.hyperledger.besu.nat.core.domain.NatPortMapping; @@ -47,6 +48,7 @@ public class AdminNodeInfo implements JsonRpcMethod { private final P2PNetwork peerNetwork; private final BlockchainQueries blockchainQueries; private final NatService natService; + private final ProtocolSchedule protocolSchedule; public AdminNodeInfo( final String clientVersion, @@ -54,13 +56,15 @@ public AdminNodeInfo( final GenesisConfigOptions genesisConfigOptions, final P2PNetwork peerNetwork, final BlockchainQueries blockchainQueries, - final NatService natService) { + final NatService natService, + final ProtocolSchedule protocolSchedule) { this.peerNetwork = peerNetwork; this.clientVersion = clientVersion; this.genesisConfigOptions = genesisConfigOptions; this.blockchainQueries = blockchainQueries; this.networkId = networkId; this.natService = natService; + this.protocolSchedule = protocolSchedule; } @Override @@ -126,6 +130,9 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { "network", networkId))); + response.put( + "activeFork", protocolSchedule.getByBlockHeader(chainHead.getBlockHeader()).getName()); + return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), response); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugAccountAt.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugAccountAt.java index 89a897df37d..318d8a0b8de 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugAccountAt.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugAccountAt.java @@ -18,7 +18,9 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameterOrBlockHash; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockTrace; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockTracer; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.Tracer; @@ -66,14 +68,33 @@ public String getName() { @Override protected BlockParameterOrBlockHash blockParameterOrBlockHash( final JsonRpcRequestContext requestContext) { - return requestContext.getRequiredParameter(0, BlockParameterOrBlockHash.class); + try { + return requestContext.getRequiredParameter(0, BlockParameterOrBlockHash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block or block hash parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } @Override protected Object resultByBlockHash( final JsonRpcRequestContext requestContext, final Hash blockHash) { - final Integer txIndex = requestContext.getRequiredParameter(1, Integer.class); - final Address address = requestContext.getRequiredParameter(2, Address.class); + final Integer txIndex; + try { + txIndex = requestContext.getRequiredParameter(1, Integer.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid transaction index parameter (index 1)", + RpcErrorType.INVALID_TRANSACTION_INDEX_PARAMS, + e); + } + final Address address; + try { + address = requestContext.getRequiredParameter(2, Address.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid address parameter (index 2)", RpcErrorType.INVALID_ADDRESS_PARAMS, e); + } Optional> block = blockchainQueries.get().blockByHash(blockHash); @@ -85,7 +106,7 @@ protected Object resultByBlockHash( List transactions = block.get().getTransactions(); if (transactions.isEmpty() || txIndex < 0 || txIndex > block.get().getTransactions().size()) { return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVALID_TRANSACTION_PARAMS); } return Tracer.processTracing( @@ -98,7 +119,7 @@ protected Object resultByBlockHash( .trace( mutableWorldState, blockHash, - new DebugOperationTracer(new TraceOptions(false, true, true))) + new DebugOperationTracer(new TraceOptions(false, true, true), false)) .map(BlockTrace::getTransactionTraces) .orElse(Collections.emptyList()) .stream() diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugAccountRange.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugAccountRange.java index 4ca5cb0685f..852972f4d82 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugAccountRange.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugAccountRange.java @@ -17,9 +17,12 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameterOrBlockHash; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.DebugAccountRangeAtResult; import org.hyperledger.besu.ethereum.api.query.BlockWithMetadata; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; @@ -56,10 +59,30 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final BlockParameterOrBlockHash blockParameterOrBlockHash = - requestContext.getRequiredParameter(0, BlockParameterOrBlockHash.class); - final String addressHash = requestContext.getRequiredParameter(2, String.class); - final int maxResults = requestContext.getRequiredParameter(3, Integer.TYPE); + final BlockParameterOrBlockHash blockParameterOrBlockHash; + try { + blockParameterOrBlockHash = + requestContext.getRequiredParameter(0, BlockParameterOrBlockHash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter or block hash parameter (index 0)", + RpcErrorType.INVALID_BLOCK_PARAMS, + e); + } + final String addressHash; + try { + addressHash = requestContext.getRequiredParameter(2, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid address hash parameter (index 2)", RpcErrorType.INVALID_ADDRESS_HASH_PARAMS, e); + } + final int maxResults; + try { + maxResults = requestContext.getRequiredParameter(3, Integer.TYPE); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid max results parameter (index 3)", RpcErrorType.INVALID_MAX_RESULTS_PARAMS, e); + } final Optional blockHashOptional = hashFromParameter(blockParameterOrBlockHash); if (blockHashOptional.isEmpty()) { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugBatchSendRawTransaction.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugBatchSendRawTransaction.java index 83553dc8745..d78f9fb6be9 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugBatchSendRawTransaction.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugBatchSendRawTransaction.java @@ -16,8 +16,11 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.util.DomainObjectDecodeUtils; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; @@ -56,9 +59,15 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { final List executionStatuses = new ArrayList<>(); IntStream.range(0, requestContext.getRequest().getParamLength()) .forEach( - i -> + i -> { + try { executionStatuses.add( - process(i, requestContext.getRequiredParameter(i, String.class)))); + process(i, requestContext.getRequiredParameter(i, String.class))); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid parameter (index " + i + ")", RpcErrorType.INVALID_PARAMS, e); + } + }); return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), executionStatuses); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetBadBlocks.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetBadBlocks.java index 6f049d485e9..cdc2822cb67 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetBadBlocks.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetBadBlocks.java @@ -14,30 +14,25 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; +import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BadBlockResult; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory; -import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import java.util.List; import java.util.stream.Collectors; public class DebugGetBadBlocks implements JsonRpcMethod { - private final BlockchainQueries blockchain; - private final ProtocolSchedule protocolSchedule; + private final ProtocolContext protocolContext; private final BlockResultFactory blockResultFactory; public DebugGetBadBlocks( - final BlockchainQueries blockchain, - final ProtocolSchedule protocolSchedule, - final BlockResultFactory blockResultFactory) { - this.blockchain = blockchain; - this.protocolSchedule = protocolSchedule; + final ProtocolContext protocolContext, final BlockResultFactory blockResultFactory) { + this.protocolContext = protocolContext; this.blockResultFactory = blockResultFactory; } @@ -49,11 +44,7 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { final List response = - protocolSchedule - .getByBlockHeader(blockchain.headBlockHeader()) - .getBadBlocksManager() - .getBadBlocks() - .stream() + protocolContext.getBadBlockManager().getBadBlocks().stream() .map(block -> BadBlockResult.from(blockResultFactory.transactionComplete(block), block)) .collect(Collectors.toList()); return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), response); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawBlock.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawBlock.java index 0dc79dfd419..c9097583e36 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawBlock.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawBlock.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -16,7 +16,9 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; @@ -37,7 +39,12 @@ public String getName() { @Override protected BlockParameter blockParameter(final JsonRpcRequestContext request) { - return request.getRequiredParameter(0, BlockParameter.class); + try { + return request.getRequiredParameter(0, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } @Override diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawHeader.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawHeader.java index cd283d67c81..8e4f9ce6ead 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawHeader.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawHeader.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -16,7 +16,9 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; @@ -37,7 +39,12 @@ public String getName() { @Override protected BlockParameter blockParameter(final JsonRpcRequestContext request) { - return request.getRequiredParameter(0, BlockParameter.class); + try { + return request.getRequiredParameter(0, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } @Override diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawReceipts.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawReceipts.java index 9e879de5170..1c634c3c730 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawReceipts.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawReceipts.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,7 +17,10 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameterOrBlockHash; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.rlp.RLP; @@ -40,7 +43,12 @@ public String getName() { @Override protected BlockParameterOrBlockHash blockParameterOrBlockHash( final JsonRpcRequestContext request) { - return request.getRequiredParameter(0, BlockParameterOrBlockHash.class); + try { + return request.getRequiredParameter(0, BlockParameterOrBlockHash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block or block hash parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } @Override @@ -55,7 +63,7 @@ protected Object resultByBlockHash(final JsonRpcRequestContext request, final Ha private String[] toRLP(final List receipts) { return receipts.stream() - .map(receipt -> RLP.encode(receipt::writeTo).toHexString()) + .map(receipt -> RLP.encode(receipt::writeToForNetwork).toHexString()) .toArray(String[]::new); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawTransaction.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawTransaction.java index c4c40ffcc22..2b1e756d22a 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawTransaction.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetRawTransaction.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,8 +17,11 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; @@ -38,7 +41,15 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final Hash txHash = requestContext.getRequiredParameter(0, Hash.class); + final Hash txHash; + try { + txHash = requestContext.getRequiredParameter(0, Hash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid transaction hash parameter (index 0)", + RpcErrorType.INVALID_TRANSACTION_HASH_PARAMS, + e); + } return blockchainQueries .transactionByHash(txHash) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugMetrics.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugMetrics.java index 89e6e4a2ae7..b5c74245ce4 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugMetrics.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugMetrics.java @@ -71,7 +71,26 @@ private void addLabelledObservation( @SuppressWarnings("unchecked") private Map getNextMapLevel( final Map current, final String name) { + // Use compute to either return the existing map or create a new one return (Map) - current.computeIfAbsent(name, key -> new HashMap()); + current.compute( + name, + (k, v) -> { + if (v instanceof Map) { + // If the value is already a Map, return it as is + return v; + } else { + // If the value is not a Map, create a new Map + Map newMap = new HashMap<>(); + if (v != null) { + // If v is not null and not a Map, we store it as a leaf value + // If the original value was not null, store it under the "value" key + // This handles cases where a metric value (e.g., Double) was previously stored + // directly + newMap.put("value", v); + } + return newMap; + } + }); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugResyncWorldstate.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugResyncWorldstate.java index c7befa050f1..ea4dc4b7fc0 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugResyncWorldstate.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugResyncWorldstate.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,26 +14,22 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; +import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; -import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.Synchronizer; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; public class DebugResyncWorldstate implements JsonRpcMethod { private final Synchronizer synchronizer; - private final ProtocolSchedule protocolSchedule; - private final Blockchain blockchain; + private final BadBlockManager badBlockManager; public DebugResyncWorldstate( - final ProtocolSchedule protocolSchedule, - final Blockchain blockchain, - final Synchronizer synchronizer) { + final ProtocolContext protocolContext, final Synchronizer synchronizer) { this.synchronizer = synchronizer; - this.protocolSchedule = protocolSchedule; - this.blockchain = blockchain; + this.badBlockManager = protocolContext.getBadBlockManager(); } @Override @@ -43,10 +39,7 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext request) { - protocolSchedule - .getByBlockHeader(blockchain.getChainHeadHeader()) - .getBadBlocksManager() - .reset(); + badBlockManager.reset(); return new JsonRpcSuccessResponse( request.getRequest().getId(), synchronizer.resyncWorldState()); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugSetHead.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugSetHead.java index b3d70344318..7958f8e7595 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugSetHead.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugSetHead.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -20,9 +20,12 @@ import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import java.util.Optional; @@ -43,7 +46,12 @@ public String getName() { @Override protected BlockParameter blockParameter(final JsonRpcRequestContext request) { - return request.getRequiredParameter(0, BlockParameter.class); + try { + return request.getRequiredParameter(0, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } @Override diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStandardTraceBadBlockToFile.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStandardTraceBadBlockToFile.java index 0c3299a8d68..b49c94fd6a0 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStandardTraceBadBlockToFile.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStandardTraceBadBlockToFile.java @@ -15,8 +15,11 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.TransactionTraceParams; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTracer; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; @@ -25,9 +28,6 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.chain.BadBlockManager; -import org.hyperledger.besu.ethereum.chain.Blockchain; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import java.nio.file.Path; import java.util.Optional; @@ -36,15 +36,15 @@ public class DebugStandardTraceBadBlockToFile extends DebugStandardTraceBlockToFile implements JsonRpcMethod { - private final ProtocolSchedule protocolSchedule; + private final ProtocolContext protocolContext; public DebugStandardTraceBadBlockToFile( final Supplier transactionTracerSupplier, final BlockchainQueries blockchainQueries, - final ProtocolSchedule protocolSchedule, + final ProtocolContext protocolContext, final Path dataDir) { super(transactionTracerSupplier, blockchainQueries, dataDir); - this.protocolSchedule = protocolSchedule; + this.protocolContext = protocolContext; } @Override @@ -54,14 +54,24 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final Hash blockHash = requestContext.getRequiredParameter(0, Hash.class); - final Optional transactionTraceParams = - requestContext.getOptionalParameter(1, TransactionTraceParams.class); + final Hash blockHash; + try { + blockHash = requestContext.getRequiredParameter(0, Hash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block hash parameter (index 0)", RpcErrorType.INVALID_BLOCK_HASH_PARAMS, e); + } + final Optional transactionTraceParams; + try { + transactionTraceParams = requestContext.getOptionalParameter(1, TransactionTraceParams.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid transaction trace parameters (index 1)", + RpcErrorType.INVALID_TRANSACTION_TRACE_PARAMS, + e); + } - final Blockchain blockchain = blockchainQueries.get().getBlockchain(); - final ProtocolSpec protocolSpec = - protocolSchedule.getByBlockHeader(blockchain.getChainHeadHeader()); - final BadBlockManager badBlockManager = protocolSpec.getBadBlocksManager(); + final BadBlockManager badBlockManager = protocolContext.getBadBlockManager(); return badBlockManager .getBadBlock(blockHash) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStandardTraceBlockToFile.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStandardTraceBlockToFile.java index 306f4c449ed..b3bb32370ee 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStandardTraceBlockToFile.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStandardTraceBlockToFile.java @@ -19,6 +19,8 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.TransactionTraceParams; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.Tracer; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTracer; @@ -59,9 +61,22 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final Hash blockHash = requestContext.getRequiredParameter(0, Hash.class); - final Optional transactionTraceParams = - requestContext.getOptionalParameter(1, TransactionTraceParams.class); + final Hash blockHash; + try { + blockHash = requestContext.getRequiredParameter(0, Hash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block hash parameter (index 0)", RpcErrorType.INVALID_BLOCK_HASH_PARAMS, e); + } + final Optional transactionTraceParams; + try { + transactionTraceParams = requestContext.getOptionalParameter(1, TransactionTraceParams.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid transaction trace parameters (index 1)", + RpcErrorType.INVALID_TRANSACTION_TRACE_PARAMS, + e); + } return blockchainQueries .get() diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStorageRangeAt.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStorageRangeAt.java index 0ea50b41aca..1fde6610ef8 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStorageRangeAt.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStorageRangeAt.java @@ -18,12 +18,15 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameterOrBlockHash; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockReplay; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.Tracer; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.Tracer.TraceableState; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.DebugStorageRangeAtResult; import org.hyperledger.besu.ethereum.api.query.BlockWithMetadata; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; @@ -67,13 +70,44 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final BlockParameterOrBlockHash blockParameterOrBlockHash = - requestContext.getRequiredParameter(0, BlockParameterOrBlockHash.class); - final int transactionIndex = requestContext.getRequiredParameter(1, Integer.class); - final Address accountAddress = requestContext.getRequiredParameter(2, Address.class); - final Hash startKey = - Hash.fromHexStringLenient(requestContext.getRequiredParameter(3, String.class)); - final int limit = requestContext.getRequiredParameter(4, Integer.class); + final BlockParameterOrBlockHash blockParameterOrBlockHash; + try { + blockParameterOrBlockHash = + requestContext.getRequiredParameter(0, BlockParameterOrBlockHash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block or block hash parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } + final int transactionIndex; + try { + transactionIndex = requestContext.getRequiredParameter(1, Integer.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid transaction index parameter (index 1)", + RpcErrorType.INVALID_TRANSACTION_INDEX_PARAMS, + e); + } + final Address accountAddress; + try { + accountAddress = requestContext.getRequiredParameter(2, Address.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid account address parameter (index 2)", RpcErrorType.INVALID_ADDRESS_PARAMS, e); + } + final Hash startKey; + try { + startKey = Hash.fromHexStringLenient(requestContext.getRequiredParameter(3, String.class)); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid data start hash parameter (index 3)", RpcErrorType.INVALID_DATA_HASH_PARAMS, e); + } + final int limit; + try { + limit = requestContext.getRequiredParameter(4, Integer.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid limit parameter (index 4)", RpcErrorType.INVALID_TRANSACTION_LIMIT_PARAMS, e); + } final Optional blockHashOptional = hashFromParameter(blockParameterOrBlockHash); if (blockHashOptional.isEmpty()) { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlock.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlock.java index 270f176c7a8..edfb816bb9d 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlock.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlock.java @@ -16,6 +16,8 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.TransactionTraceParams; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockTrace; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockTracer; @@ -46,15 +48,15 @@ public class DebugTraceBlock implements JsonRpcMethod { private static final Logger LOG = LoggerFactory.getLogger(DebugTraceBlock.class); private final Supplier blockTracerSupplier; private final BlockHeaderFunctions blockHeaderFunctions; - private final BlockchainQueries blockchain; + private final BlockchainQueries blockchainQueries; public DebugTraceBlock( final Supplier blockTracerSupplier, final BlockHeaderFunctions blockHeaderFunctions, - final BlockchainQueries blockchain) { + final BlockchainQueries blockchainQueries) { this.blockTracerSupplier = blockTracerSupplier; this.blockHeaderFunctions = blockHeaderFunctions; - this.blockchain = blockchain; + this.blockchainQueries = blockchainQueries; } @Override @@ -64,33 +66,46 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final String input = requestContext.getRequiredParameter(0, String.class); final Block block; try { + final String input = requestContext.getRequiredParameter(0, String.class); block = Block.readFrom(RLP.input(Bytes.fromHexString(input)), this.blockHeaderFunctions); } catch (final RLPException e) { - LOG.debug("Failed to parse block RLP", e); + LOG.debug("Failed to parse block RLP (index 0)", e); return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVALID_BLOCK_PARAMS); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block params (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } + final TraceOptions traceOptions; + try { + traceOptions = + requestContext + .getOptionalParameter(1, TransactionTraceParams.class) + .map(TransactionTraceParams::traceOptions) + .orElse(TraceOptions.DEFAULT); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid transaction trace parameter (index 1)", + RpcErrorType.INVALID_TRANSACTION_TRACE_PARAMS, + e); } - final TraceOptions traceOptions = - requestContext - .getOptionalParameter(1, TransactionTraceParams.class) - .map(TransactionTraceParams::traceOptions) - .orElse(TraceOptions.DEFAULT); - if (this.blockchain.blockByHash(block.getHeader().getParentHash()).isPresent()) { + if (this.blockchainQueries.blockByHash(block.getHeader().getParentHash()).isPresent()) { final Collection results = Tracer.processTracing( - blockchain, + blockchainQueries, Optional.of(block.getHeader()), - mutableWorldState -> { - return blockTracerSupplier - .get() - .trace(mutableWorldState, block, new DebugOperationTracer(traceOptions)) - .map(BlockTrace::getTransactionTraces) - .map(DebugTraceTransactionResult::of); - }) + mutableWorldState -> + blockTracerSupplier + .get() + .trace( + mutableWorldState, + block, + new DebugOperationTracer(traceOptions, true)) + .map(BlockTrace::getTransactionTraces) + .map(DebugTraceTransactionResult::of)) .orElse(null); return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), results); } else { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockByHash.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockByHash.java index 3b5f5e734f7..d98e8abd8df 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockByHash.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockByHash.java @@ -17,12 +17,15 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.TransactionTraceParams; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockTrace; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockTracer; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.Tracer; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.DebugTraceTransactionResult; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.debug.TraceOptions; @@ -50,12 +53,26 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final Hash blockHash = requestContext.getRequiredParameter(0, Hash.class); - final TraceOptions traceOptions = - requestContext - .getOptionalParameter(1, TransactionTraceParams.class) - .map(TransactionTraceParams::traceOptions) - .orElse(TraceOptions.DEFAULT); + final Hash blockHash; + try { + blockHash = requestContext.getRequiredParameter(0, Hash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block hash parameter (index 0)", RpcErrorType.INVALID_BLOCK_HASH_PARAMS, e); + } + final TraceOptions traceOptions; + try { + traceOptions = + requestContext + .getOptionalParameter(1, TransactionTraceParams.class) + .map(TransactionTraceParams::traceOptions) + .orElse(TraceOptions.DEFAULT); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid transaction trace parameter (index 1)", + RpcErrorType.INVALID_TRANSACTION_TRACE_PARAMS, + e); + } final Collection results = Tracer.processTracing( @@ -64,7 +81,10 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { mutableWorldState -> blockTracerSupplier .get() - .trace(mutableWorldState, blockHash, new DebugOperationTracer(traceOptions)) + .trace( + mutableWorldState, + blockHash, + new DebugOperationTracer(traceOptions, true)) .map(BlockTrace::getTransactionTraces) .map(DebugTraceTransactionResult::of)) .orElse(null); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockByNumber.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockByNumber.java index 977a1d87936..99c96c99be9 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockByNumber.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockByNumber.java @@ -17,11 +17,14 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.TransactionTraceParams; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockTrace; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockTracer; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.Tracer; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.DebugTraceTransactionResult; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.debug.TraceOptions; @@ -47,18 +50,31 @@ public String getName() { @Override protected BlockParameter blockParameter(final JsonRpcRequestContext request) { - return request.getRequiredParameter(0, BlockParameter.class); + try { + return request.getRequiredParameter(0, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } @Override protected Object resultByBlockNumber( final JsonRpcRequestContext request, final long blockNumber) { final Optional blockHash = getBlockchainQueries().getBlockHashByNumber(blockNumber); - final TraceOptions traceOptions = - request - .getOptionalParameter(1, TransactionTraceParams.class) - .map(TransactionTraceParams::traceOptions) - .orElse(TraceOptions.DEFAULT); + final TraceOptions traceOptions; + try { + traceOptions = + request + .getOptionalParameter(1, TransactionTraceParams.class) + .map(TransactionTraceParams::traceOptions) + .orElse(TraceOptions.DEFAULT); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid transaction trace parameter (index 1)", + RpcErrorType.INVALID_TRANSACTION_TRACE_PARAMS, + e); + } return blockHash .flatMap( @@ -66,13 +82,15 @@ protected Object resultByBlockNumber( Tracer.processTracing( blockchainQueriesSupplier.get(), hash, - mutableWorldState -> { - return blockTracerSupplier - .get() - .trace(mutableWorldState, hash, new DebugOperationTracer(traceOptions)) - .map(BlockTrace::getTransactionTraces) - .map(DebugTraceTransactionResult::of); - })) + mutableWorldState -> + blockTracerSupplier + .get() + .trace( + mutableWorldState, + hash, + new DebugOperationTracer(traceOptions, true)) + .map(BlockTrace::getTransactionTraces) + .map(DebugTraceTransactionResult::of))) .orElse(null); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceCall.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceCall.java index d133aa77524..45b06ce6910 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceCall.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceCall.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -18,32 +18,33 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.TransactionTraceParams; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTrace; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.DebugTraceTransactionResult; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.debug.TraceOptions; +import org.hyperledger.besu.ethereum.mainnet.ImmutableTransactionValidationParams; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; import org.hyperledger.besu.ethereum.transaction.PreCloseStateHandler; import org.hyperledger.besu.ethereum.transaction.TransactionSimulator; import org.hyperledger.besu.ethereum.vm.DebugOperationTracer; import java.util.Optional; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - public class DebugTraceCall extends AbstractTraceCall { - private static final Logger LOG = LoggerFactory.getLogger(DebugTraceCall.class); public DebugTraceCall( final BlockchainQueries blockchainQueries, final ProtocolSchedule protocolSchedule, final TransactionSimulator transactionSimulator) { - super(blockchainQueries, protocolSchedule, transactionSimulator); + super(blockchainQueries, protocolSchedule, transactionSimulator, true); } @Override @@ -53,16 +54,28 @@ public String getName() { @Override protected TraceOptions getTraceOptions(final JsonRpcRequestContext requestContext) { - return requestContext - .getOptionalParameter(2, TransactionTraceParams.class) - .map(TransactionTraceParams::traceOptions) - .orElse(TraceOptions.DEFAULT); + try { + return requestContext + .getOptionalParameter(2, TransactionTraceParams.class) + .map(TransactionTraceParams::traceOptions) + .orElse(TraceOptions.DEFAULT); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid transaction trace parameter (index 2)", + RpcErrorType.INVALID_TRANSACTION_TRACE_PARAMS, + e); + } } @Override protected BlockParameter blockParameter(final JsonRpcRequestContext request) { - final Optional maybeBlockParameter = - request.getOptionalParameter(1, BlockParameter.class); + final Optional maybeBlockParameter; + try { + maybeBlockParameter = request.getOptionalParameter(1, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 1)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } return maybeBlockParameter.orElse(BlockParameter.LATEST); } @@ -74,7 +87,6 @@ protected PreCloseStateHandler getSimulatorResultHandler( maybeSimulatorResult.map( result -> { if (result.isInvalid()) { - LOG.error("Invalid simulator result {}", result); final JsonRpcError error = new JsonRpcError( INTERNAL_ERROR, result.getValidationResult().getErrorMessage()); @@ -83,9 +95,18 @@ protected PreCloseStateHandler getSimulatorResultHandler( final TransactionTrace transactionTrace = new TransactionTrace( - result.getTransaction(), result.getResult(), tracer.getTraceFrames()); + result.transaction(), result.result(), tracer.getTraceFrames()); return new DebugTraceTransactionResult(transactionTrace); }); } + + @Override + protected TransactionValidationParams buildTransactionValidationParams() { + return ImmutableTransactionValidationParams.builder() + .from(TransactionValidationParams.transactionSimulator()) + .isAllowExceedingBalance(true) + .allowUnderpriced(true) + .build(); + } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceTransaction.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceTransaction.java index 1ce30e243e5..1e85e6530a4 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceTransaction.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceTransaction.java @@ -17,11 +17,14 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.TransactionTraceParams; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.Tracer; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTracer; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.DebugTraceTransactionResult; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.api.query.TransactionWithMetadata; @@ -48,15 +51,31 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final Hash hash = requestContext.getRequiredParameter(0, Hash.class); + final Hash hash; + try { + hash = requestContext.getRequiredParameter(0, Hash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid transaction hash parameter (index 0)", + RpcErrorType.INVALID_TRANSACTION_HASH_PARAMS, + e); + } final Optional transactionWithMetadata = blockchain.transactionByHash(hash); if (transactionWithMetadata.isPresent()) { - final TraceOptions traceOptions = - requestContext - .getOptionalParameter(1, TransactionTraceParams.class) - .map(TransactionTraceParams::traceOptions) - .orElse(TraceOptions.DEFAULT); + final TraceOptions traceOptions; + try { + traceOptions = + requestContext + .getOptionalParameter(1, TransactionTraceParams.class) + .map(TransactionTraceParams::traceOptions) + .orElse(TraceOptions.DEFAULT); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid transaction trace parameter (index 1)", + RpcErrorType.INVALID_TRANSACTION_TRACE_PARAMS, + e); + } final DebugTraceTransactionResult debugTraceTransactionResult = debugTraceTransactionResult(hash, transactionWithMetadata.get(), traceOptions); @@ -73,7 +92,7 @@ private DebugTraceTransactionResult debugTraceTransactionResult( final TraceOptions traceOptions) { final Hash blockHash = transactionWithMetadata.getBlockHash().get(); - final DebugOperationTracer execTracer = new DebugOperationTracer(traceOptions); + final DebugOperationTracer execTracer = new DebugOperationTracer(traceOptions, true); return Tracer.processTracing( blockchain, diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthBlobBaseFee.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthBlobBaseFee.java new file mode 100644 index 00000000000..eed0476b46c --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthBlobBaseFee.java @@ -0,0 +1,57 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; + +import static org.hyperledger.besu.ethereum.mainnet.feemarket.ExcessBlobGasCalculator.calculateExcessBlobGasForParent; + +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; + +public class EthBlobBaseFee implements JsonRpcMethod { + + final Blockchain blockchain; + + private final ProtocolSchedule protocolSchedule; + + public EthBlobBaseFee(final Blockchain blockchain, final ProtocolSchedule protocolSchedule) { + this.blockchain = blockchain; + this.protocolSchedule = protocolSchedule; + } + + @Override + public String getName() { + return RpcMethod.ETH_BLOB_BASE_FEE.getMethodName(); + } + + @Override + public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { + return new JsonRpcSuccessResponse( + requestContext.getRequest().getId(), Quantity.create(computeNextBlobBaseFee())); + } + + private Wei computeNextBlobBaseFee() { + final BlockHeader header = blockchain.getChainHeadHeader(); + ProtocolSpec spec = protocolSchedule.getForNextBlockHeader(header, header.getTimestamp()); + return spec.getFeeMarket().blobGasPricePerGas(calculateExcessBlobGasForParent(spec, header)); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCall.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCall.java index 569563ca932..0e0318f9c2b 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCall.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCall.java @@ -22,8 +22,10 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcErrorConverter; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameterOrBlockHash; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonCallParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; @@ -57,7 +59,12 @@ public String getName() { @Override protected BlockParameterOrBlockHash blockParameterOrBlockHash( final JsonRpcRequestContext request) { - return request.getRequiredParameter(1, BlockParameterOrBlockHash.class); + try { + return request.getRequiredParameter(1, BlockParameterOrBlockHash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block or block hash parameters (index 1)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } @Override @@ -117,8 +124,9 @@ private JsonRpcErrorResponse errorResponse( JsonRpcErrorConverter.convertTransactionInvalidReason( validationResult.getInvalidReason())); } else { - final TransactionProcessingResult resultTrx = result.getResult(); + final TransactionProcessingResult resultTrx = result.result(); if (resultTrx != null && resultTrx.getRevertReason().isPresent()) { + return errorResponse( request, new JsonRpcError( @@ -164,6 +172,11 @@ private boolean isAllowExceedingBalanceAutoSelection( callParams.getGasPrice() == null || Wei.ZERO.equals(callParams.getGasPrice()); if (header.getBaseFee().isPresent()) { + if (callParams.getBlobVersionedHashes().isPresent() + && (callParams.getMaxFeePerBlobGas().isEmpty() + || callParams.getMaxFeePerBlobGas().get().equals(Wei.ZERO))) { + return true; + } boolean isZeroMaxFeePerGas = callParams.getMaxFeePerGas().orElse(Wei.ZERO).equals(Wei.ZERO); boolean isZeroMaxPriorityFeePerGas = callParams.getMaxPriorityFeePerGas().orElse(Wei.ZERO).equals(Wei.ZERO); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessList.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessList.java index b6b9f5171fa..2b8e79a81e9 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessList.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessList.java @@ -19,8 +19,6 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonCallParameter; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.CreateAccessListResult; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; @@ -50,44 +48,29 @@ public String getName() { } @Override - public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final JsonCallParameter jsonCallParameter = validateAndGetCallParams(requestContext); - final BlockHeader blockHeader = blockHeader(); - final Optional jsonRpcError = validateBlockHeader(blockHeader); - if (jsonRpcError.isPresent()) { - return errorResponse(requestContext, jsonRpcError.get()); - } + protected Object resultByBlockHeader( + final JsonRpcRequestContext requestContext, + final JsonCallParameter jsonCallParameter, + final BlockHeader blockHeader) { final AccessListSimulatorResult maybeResult = processTransaction(jsonCallParameter, blockHeader); // if the call accessList is different from the simulation result, calculate gas and return - if (shouldProcessWithAccessListOverride(jsonCallParameter, maybeResult.getTracer())) { + if (shouldProcessWithAccessListOverride(jsonCallParameter, maybeResult.tracer())) { final AccessListSimulatorResult result = processTransactionWithAccessListOverride( - jsonCallParameter, blockHeader, maybeResult.getTracer().getAccessList()); + jsonCallParameter, blockHeader, maybeResult.tracer().getAccessList()); return createResponse(requestContext, result); } else { return createResponse(requestContext, maybeResult); } } - private Optional validateBlockHeader(final BlockHeader blockHeader) { - if (blockHeader == null) { - return Optional.of(RpcErrorType.INTERNAL_ERROR); - } - if (!blockchainQueries - .getWorldStateArchive() - .isWorldStateAvailable(blockHeader.getStateRoot(), blockHeader.getHash())) { - return Optional.of(RpcErrorType.WORLD_STATE_UNAVAILABLE); - } - return Optional.empty(); - } - - private JsonRpcResponse createResponse( + private Object createResponse( final JsonRpcRequestContext requestContext, final AccessListSimulatorResult result) { return result - .getResult() - .map(createResponse(requestContext, result.getTracer())) - .orElse(errorResponse(requestContext, RpcErrorType.INTERNAL_ERROR)); + .result() + .map(createResponse(requestContext, result.tracer())) + .orElseGet(() -> errorResponse(requestContext, RpcErrorType.INTERNAL_ERROR)); } private TransactionValidationParams transactionValidationParams( @@ -115,14 +98,12 @@ private boolean shouldProcessWithAccessListOverride( return !Objects.equals(tracer.getAccessList(), parameters.getAccessList().get()); } - private Function createResponse( + private Function createResponse( final JsonRpcRequestContext request, final AccessListOperationTracer operationTracer) { return result -> result.isSuccessful() - ? new JsonRpcSuccessResponse( - request.getRequest().getId(), - new CreateAccessListResult( - operationTracer.getAccessList(), processEstimateGas(result, operationTracer))) + ? new CreateAccessListResult( + operationTracer.getAccessList(), processEstimateGas(result, operationTracer)) : errorResponse(request, result); } @@ -136,8 +117,7 @@ private AccessListSimulatorResult processTransaction( final AccessListOperationTracer tracer = AccessListOperationTracer.create(); final Optional result = - transactionSimulator.process( - callParams, transactionValidationParams, tracer, blockHeader.getNumber()); + transactionSimulator.process(callParams, transactionValidationParams, tracer, blockHeader); return new AccessListSimulatorResult(result, tracer); } @@ -154,7 +134,7 @@ private AccessListSimulatorResult processTransactionWithAccessListOverride( final Optional result = transactionSimulator.process( - callParameter, transactionValidationParams, tracer, blockHeader.getNumber()); + callParameter, transactionValidationParams, tracer, blockHeader); return new AccessListSimulatorResult(result, tracer); } @@ -174,22 +154,6 @@ protected CallParameter overrideAccessList( Optional.ofNullable(accessListEntries)); } - private static class AccessListSimulatorResult { - final Optional result; - final AccessListOperationTracer tracer; - - public AccessListSimulatorResult( - final Optional result, final AccessListOperationTracer tracer) { - this.result = result; - this.tracer = tracer; - } - - public Optional getResult() { - return result; - } - - public AccessListOperationTracer getTracer() { - return tracer; - } - } + private record AccessListSimulatorResult( + Optional result, AccessListOperationTracer tracer) {} } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGas.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGas.java index ea69ad9d381..c713ea20a7b 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGas.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGas.java @@ -1,5 +1,5 @@ /* - * Copyright ConsenSys AG. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,8 +17,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonCallParameter; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; @@ -32,7 +31,11 @@ import java.util.Optional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class EthEstimateGas extends AbstractEstimateGas { + private static final Logger LOG = LoggerFactory.getLogger(EthEstimateGas.class); public EthEstimateGas( final BlockchainQueries blockchainQueries, final TransactionSimulator transactionSimulator) { @@ -45,18 +48,10 @@ public String getName() { } @Override - public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final JsonCallParameter callParams = validateAndGetCallParams(requestContext); - - final BlockHeader blockHeader = blockHeader(); - if (blockHeader == null) { - return errorResponse(requestContext, RpcErrorType.INTERNAL_ERROR); - } - if (!blockchainQueries - .getWorldStateArchive() - .isWorldStateAvailable(blockHeader.getStateRoot(), blockHeader.getHash())) { - return errorResponse(requestContext, RpcErrorType.WORLD_STATE_UNAVAILABLE); - } + protected Object resultByBlockHeader( + final JsonRpcRequestContext requestContext, + final JsonCallParameter callParams, + final BlockHeader blockHeader) { final CallParameter modifiedCallParams = overrideGasLimitAndPrice(callParams, blockHeader.getGasLimit()); @@ -64,43 +59,48 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { final boolean isAllowExceedingBalance = !callParams.isMaybeStrict().orElse(Boolean.FALSE); final EstimateGasOperationTracer operationTracer = new EstimateGasOperationTracer(); - - var gasUsed = - executeSimulation( - blockHeader, modifiedCallParams, operationTracer, isAllowExceedingBalance); - - if (gasUsed.isEmpty()) { - return errorResponse(requestContext, RpcErrorType.INTERNAL_ERROR); - } - - // if the transaction is invalid or doesn't have enough gas with the max it never will! - if (gasUsed.get().isInvalid() || !gasUsed.get().isSuccessful()) { - return errorResponse(requestContext, gasUsed.get()); + final var transactionValidationParams = + ImmutableTransactionValidationParams.builder() + .from(TransactionValidationParams.transactionSimulator()) + .isAllowExceedingBalance(isAllowExceedingBalance) + .build(); + + LOG.debug("Processing transaction with params: {}", modifiedCallParams); + final var maybeResult = + transactionSimulator.process( + modifiedCallParams, transactionValidationParams, operationTracer, blockHeader); + + final Optional maybeErrorResponse = + validateSimulationResult(requestContext, maybeResult); + if (maybeErrorResponse.isPresent()) { + return maybeErrorResponse.get(); } - var low = gasUsed.get().getResult().getEstimateGasUsedByTransaction(); - var lowResult = - executeSimulation( - blockHeader, + final var result = maybeResult.get(); + long low = result.result().getEstimateGasUsedByTransaction(); + final var lowResult = + transactionSimulator.process( overrideGasLimitAndPrice(callParams, low), + transactionValidationParams, operationTracer, - isAllowExceedingBalance); + blockHeader); if (lowResult.isPresent() && lowResult.get().isSuccessful()) { - return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), Quantity.create(low)); + return Quantity.create(low); } - var high = processEstimateGas(gasUsed.get(), operationTracer); - var mid = high; - while (low + 1 < high) { - mid = (high + low) / 2; + long high = processEstimateGas(result, operationTracer); + long mid; + while (low + 1 < high) { + mid = (low + high) / 2; var binarySearchResult = - executeSimulation( - blockHeader, + transactionSimulator.process( overrideGasLimitAndPrice(callParams, mid), + transactionValidationParams, operationTracer, - isAllowExceedingBalance); + blockHeader); + if (binarySearchResult.isEmpty() || !binarySearchResult.get().isSuccessful()) { low = mid; } else { @@ -108,21 +108,23 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { } } - return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), Quantity.create(high)); + return Quantity.create(high); } - private Optional executeSimulation( - final BlockHeader blockHeader, - final CallParameter modifiedCallParams, - final EstimateGasOperationTracer operationTracer, - final boolean allowExceedingBalance) { - return transactionSimulator.process( - modifiedCallParams, - ImmutableTransactionValidationParams.builder() - .from(TransactionValidationParams.transactionSimulator()) - .isAllowExceedingBalance(allowExceedingBalance) - .build(), - operationTracer, - blockHeader.getNumber()); + private Optional validateSimulationResult( + final JsonRpcRequestContext requestContext, + final Optional maybeResult) { + if (maybeResult.isEmpty()) { + LOG.error("No result after simulating transaction."); + return Optional.of( + new JsonRpcErrorResponse( + requestContext.getRequest().getId(), RpcErrorType.INTERNAL_ERROR)); + } + + // if the transaction is invalid or doesn't have enough gas with the max it never will! + if (maybeResult.get().isInvalid() || !maybeResult.get().isSuccessful()) { + return Optional.of(errorResponse(requestContext, maybeResult.get())); + } + return Optional.empty(); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthFeeHistory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthFeeHistory.java index 3b155743267..b95746dad98 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthFeeHistory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthFeeHistory.java @@ -15,12 +15,16 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; import static java.util.stream.Collectors.toUnmodifiableList; +import static org.hyperledger.besu.ethereum.mainnet.feemarket.ExcessBlobGasCalculator.calculateExcessBlobGasForParent; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.api.ApiConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.UnsignedIntParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; @@ -28,20 +32,24 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.FeeHistory; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.ImmutableFeeHistory; +import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import java.util.stream.LongStream; import java.util.stream.Stream; @@ -53,14 +61,22 @@ public class EthFeeHistory implements JsonRpcMethod { private final ProtocolSchedule protocolSchedule; private final Blockchain blockchain; + private final MiningCoordinator miningCoordinator; + private final ApiConfiguration apiConfiguration; private final Cache> cache; private static final int MAXIMUM_CACHE_SIZE = 100_000; record RewardCacheKey(Hash blockHash, List rewardPercentiles) {} - public EthFeeHistory(final ProtocolSchedule protocolSchedule, final Blockchain blockchain) { + public EthFeeHistory( + final ProtocolSchedule protocolSchedule, + final Blockchain blockchain, + final MiningCoordinator miningCoordinator, + final ApiConfiguration apiConfiguration) { this.protocolSchedule = protocolSchedule; this.blockchain = blockchain; + this.miningCoordinator = miningCoordinator; + this.apiConfiguration = apiConfiguration; this.cache = Caffeine.newBuilder().maximumSize(MAXIMUM_CACHE_SIZE).build(); } @@ -73,19 +89,39 @@ public String getName() { public JsonRpcResponse response(final JsonRpcRequestContext request) { final Object requestId = request.getRequest().getId(); - final int blockCount = request.getRequiredParameter(0, UnsignedIntParameter.class).getValue(); + final int blockCount; + try { + blockCount = request.getRequiredParameter(0, UnsignedIntParameter.class).getValue(); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block count parameter (index 0)", RpcErrorType.INVALID_BLOCK_COUNT_PARAMS, e); + } if (isInvalidBlockCount(blockCount)) { - return new JsonRpcErrorResponse(requestId, RpcErrorType.INVALID_PARAMS); + return new JsonRpcErrorResponse(requestId, RpcErrorType.INVALID_BLOCK_COUNT_PARAMS); + } + final BlockParameter highestBlock; + try { + highestBlock = request.getRequiredParameter(1, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid highest block parameter (index 1)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } + + final Optional> maybeRewardPercentiles; + try { + maybeRewardPercentiles = request.getOptionalParameter(2, Double[].class).map(Arrays::asList); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid reward percentiles parameter (index 2)", + RpcErrorType.INVALID_REWARD_PERCENTILES_PARAMS, + e); } - final BlockParameter highestBlock = request.getRequiredParameter(1, BlockParameter.class); - final Optional> maybeRewardPercentiles = - request.getOptionalParameter(2, Double[].class).map(Arrays::asList); final BlockHeader chainHeadHeader = blockchain.getChainHeadHeader(); final long chainHeadBlockNumber = chainHeadHeader.getNumber(); final long highestBlockNumber = highestBlock.getNumber().orElse(chainHeadBlockNumber); if (highestBlockNumber > chainHeadBlockNumber) { - return new JsonRpcErrorResponse(requestId, RpcErrorType.INVALID_PARAMS); + return new JsonRpcErrorResponse(requestId, RpcErrorType.INVALID_BLOCK_NUMBER_PARAMS); } final long firstBlock = Math.max(0, highestBlockNumber - (blockCount - 1)); @@ -94,15 +130,23 @@ public JsonRpcResponse response(final JsonRpcRequestContext request) { final List blockHeaderRange = getBlockHeaders(firstBlock, lastBlock); final List requestedBaseFees = getBaseFees(blockHeaderRange); + final List requestedBlobBaseFees = getBlobBaseFees(blockHeaderRange); final Wei nextBaseFee = getNextBaseFee(highestBlockNumber, chainHeadHeader, requestedBaseFees, blockHeaderRange); final List gasUsedRatios = getGasUsedRatios(blockHeaderRange); + final List blobGasUsedRatios = getBlobGasUsedRatios(blockHeaderRange); final Optional>> maybeRewards = maybeRewardPercentiles.map(rewards -> getRewards(rewards, blockHeaderRange)); return new JsonRpcSuccessResponse( requestId, createFeeHistoryResult( - firstBlock, requestedBaseFees, nextBaseFee, gasUsedRatios, maybeRewards)); + firstBlock, + requestedBaseFees, + requestedBlobBaseFees, + nextBaseFee, + gasUsedRatios, + blobGasUsedRatios, + maybeRewards)); } private Wei getNextBaseFee( @@ -203,7 +247,16 @@ public List computeRewards(final List rewardPercentiles, final Bloc final List transactionsGasUsed = calculateTransactionsGasUsed(block); final List transactionsInfo = generateTransactionsInfo(transactions, transactionsGasUsed, baseFee); - return calculateRewards(rewardPercentiles, block, transactionsInfo); + + var realRewards = calculateRewards(rewardPercentiles, block, transactionsInfo); + + // If the priority fee boundary is set, return the bounded rewards. Otherwise, return the real + // rewards. + if (apiConfiguration.isGasAndPriorityFeeLimitingEnabled()) { + return boundRewards(realRewards); + } else { + return realRewards; + } } private List calculateRewards( @@ -235,6 +288,40 @@ private List calculateRewards( return rewards; } + /** + * This method returns a list of bounded rewards. + * + * @param rewards The list of rewards to be bounded. + * @return The list of bounded rewards. + */ + private List boundRewards(final List rewards) { + Wei minPriorityFee = miningCoordinator.getMinPriorityFeePerGas(); + Wei lowerBound = + minPriorityFee + .multiply(apiConfiguration.getLowerBoundGasAndPriorityFeeCoefficient()) + .divide(100); + Wei upperBound = + minPriorityFee + .multiply(apiConfiguration.getUpperBoundGasAndPriorityFeeCoefficient()) + .divide(100); + + return rewards.stream().map(reward -> boundReward(reward, lowerBound, upperBound)).toList(); + } + + /** + * This method bounds the reward between a lower and upper limit. + * + * @param reward The reward to be bounded. + * @param lowerBound The lower limit for the reward. + * @param upperBound The upper limit for the reward. + * @return The bounded reward. + */ + private Wei boundReward(final Wei reward, final Wei lowerBound, final Wei upperBound) { + return reward.compareTo(lowerBound) <= 0 + ? lowerBound + : reward.compareTo(upperBound) >= 0 ? upperBound : reward; + } + private List calculateTransactionsGasUsed(final Block block) { final List transactionsGasUsed = new ArrayList<>(); long cumulativeGasUsed = 0L; @@ -273,23 +360,78 @@ private List getBlockHeaders(final long oldestBlock, final long las } private List getBaseFees(final List blockHeaders) { - // we return the base fees for the blocks requested and 1 more because we can always compute it return blockHeaders.stream() .map(blockHeader -> blockHeader.getBaseFee().orElse(Wei.ZERO)) .toList(); } + private List getBlobBaseFees(final List blockHeaders) { + if (blockHeaders.isEmpty()) { + return Collections.emptyList(); + } + // Calculate the BlobFee for the requested range + List baseFeesPerBlobGas = + blockHeaders.stream().map(this::getBlobGasFee).collect(Collectors.toList()); + + // Calculate the next blob base fee and add it to the list + Wei nextBlobBaseFee = getNextBlobFee(blockHeaders.get(blockHeaders.size() - 1)); + baseFeesPerBlobGas.add(nextBlobBaseFee); + + return baseFeesPerBlobGas; + } + + private Wei getBlobGasFee(final BlockHeader header) { + return blockchain + .getBlockHeader(header.getParentHash()) + .map(parent -> getBlobGasFee(protocolSchedule.getByBlockHeader(header), parent)) + .orElse(Wei.ZERO); + } + + private Wei getBlobGasFee(final ProtocolSpec spec, final BlockHeader parent) { + return spec.getFeeMarket().blobGasPricePerGas(calculateExcessBlobGasForParent(spec, parent)); + } + + private Wei getNextBlobFee(final BlockHeader header) { + // Attempt to retrieve the next header based on the current header's number. + long nextBlockNumber = header.getNumber() + 1; + return blockchain + .getBlockHeader(nextBlockNumber) + .map(nextHeader -> getBlobGasFee(protocolSchedule.getByBlockHeader(nextHeader), header)) + // If the next header is not present, calculate the fee using the current time. + .orElseGet( + () -> + getBlobGasFee( + protocolSchedule.getForNextBlockHeader(header, System.currentTimeMillis()), + header)); + } + private List getGasUsedRatios(final List blockHeaders) { return blockHeaders.stream() .map(blockHeader -> blockHeader.getGasUsed() / (double) blockHeader.getGasLimit()) .toList(); } + private List getBlobGasUsedRatios(final List blockHeaders) { + return blockHeaders.stream().map(this::calculateBlobGasUsedRatio).toList(); + } + + private double calculateBlobGasUsedRatio(final BlockHeader blockHeader) { + ProtocolSpec spec = protocolSchedule.getByBlockHeader(blockHeader); + long blobGasUsed = blockHeader.getBlobGasUsed().orElse(0L); + double currentBlobGasLimit = spec.getGasLimitCalculator().currentBlobGasLimit(); + if (currentBlobGasLimit == 0) { + return 0; + } + return blobGasUsed / currentBlobGasLimit; + } + private FeeHistory.FeeHistoryResult createFeeHistoryResult( final long oldestBlock, final List explicitlyRequestedBaseFees, + final List requestedBlobBaseFees, final Wei nextBaseFee, final List gasUsedRatios, + final List blobGasUsedRatio, final Optional>> maybeRewards) { return FeeHistory.FeeHistoryResult.from( ImmutableFeeHistory.builder() @@ -297,7 +439,9 @@ private FeeHistory.FeeHistoryResult createFeeHistoryResult( .baseFeePerGas( Stream.concat(explicitlyRequestedBaseFees.stream(), Stream.of(nextBaseFee)) .collect(toUnmodifiableList())) + .baseFeePerBlobGas(requestedBlobBaseFees) .gasUsedRatio(gasUsedRatios) + .blobGasUsedRatio(blobGasUsedRatio) .reward(maybeRewards) .build()); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGasPrice.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGasPrice.java index 117fdbdcbb1..1b65de17f1b 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGasPrice.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGasPrice.java @@ -14,30 +14,24 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.api.ApiConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; -import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator; - -import java.util.function.Supplier; public class EthGasPrice implements JsonRpcMethod { - private final Supplier blockchain; - private final MiningCoordinator miningCoordinator; - - public EthGasPrice( - final BlockchainQueries blockchain, final MiningCoordinator miningCoordinator) { - this(() -> blockchain, miningCoordinator); - } + private final BlockchainQueries blockchainQueries; + private final ApiConfiguration apiConfiguration; public EthGasPrice( - final Supplier blockchain, final MiningCoordinator miningCoordinator) { - this.blockchain = blockchain; - this.miningCoordinator = miningCoordinator; + final BlockchainQueries blockchainQueries, final ApiConfiguration apiConfiguration) { + this.blockchainQueries = blockchainQueries; + this.apiConfiguration = apiConfiguration; } @Override @@ -48,11 +42,33 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { return new JsonRpcSuccessResponse( - requestContext.getRequest().getId(), - blockchain - .get() - .gasPrice() - .map(Quantity::create) - .orElseGet(() -> Quantity.create(miningCoordinator.getMinTransactionGasPrice()))); + requestContext.getRequest().getId(), Quantity.create(calculateGasPrice())); + } + + private Wei calculateGasPrice() { + final Wei gasPrice = blockchainQueries.gasPrice(); + return isGasPriceLimitingEnabled() ? limitGasPrice(gasPrice) : gasPrice; + } + + private boolean isGasPriceLimitingEnabled() { + return apiConfiguration.isGasAndPriorityFeeLimitingEnabled(); + } + + private Wei limitGasPrice(final Wei gasPrice) { + final Wei lowerBoundGasPrice = blockchainQueries.gasPriceLowerBound(); + final Wei forcedLowerBound = + calculateBound( + lowerBoundGasPrice, apiConfiguration.getLowerBoundGasAndPriorityFeeCoefficient()); + final Wei forcedUpperBound = + calculateBound( + lowerBoundGasPrice, apiConfiguration.getUpperBoundGasAndPriorityFeeCoefficient()); + + return gasPrice.compareTo(forcedLowerBound) <= 0 + ? forcedLowerBound + : gasPrice.compareTo(forcedUpperBound) >= 0 ? forcedUpperBound : gasPrice; + } + + private Wei calculateBound(final Wei price, final long coefficient) { + return price.multiply(coefficient).divide(100); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBalance.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBalance.java index 8b658d27530..cb1d879a05f 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBalance.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBalance.java @@ -18,7 +18,10 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameterOrBlockHash; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; @@ -41,12 +44,23 @@ public String getName() { @Override protected BlockParameterOrBlockHash blockParameterOrBlockHash( final JsonRpcRequestContext request) { - return request.getRequiredParameter(1, BlockParameterOrBlockHash.class); + try { + return request.getRequiredParameter(1, BlockParameterOrBlockHash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block or block hash parameter (index 1)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } @Override protected String resultByBlockHash(final JsonRpcRequestContext request, final Hash blockHash) { - final Address address = request.getRequiredParameter(0, Address.class); + final Address address; + try { + address = request.getRequiredParameter(0, Address.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid address parameter (index 0)", RpcErrorType.INVALID_ADDRESS_PARAMS, e); + } return blockchainQueries .get() .accountBalance(address, blockHash) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockByHash.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockByHash.java index 04cad96291e..f5b633253ad 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockByHash.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockByHash.java @@ -17,8 +17,11 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResult; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; @@ -59,7 +62,13 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { } private BlockResult blockResult(final JsonRpcRequestContext request) { - final Hash hash = request.getRequiredParameter(0, Hash.class); + final Hash hash; + try { + hash = request.getRequiredParameter(0, Hash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block hash parameter (index 0)", RpcErrorType.INVALID_BLOCK_HASH_PARAMS, e); + } if (isCompleteTransactions(request)) { return transactionComplete(hash); @@ -85,6 +94,13 @@ private BlockResult transactionHash(final Hash hash) { } private boolean isCompleteTransactions(final JsonRpcRequestContext requestContext) { - return requestContext.getRequiredParameter(1, Boolean.class); + try { + return requestContext.getRequiredParameter(1, Boolean.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid return complete transaction parameter (index 1)", + RpcErrorType.INVALID_RETURN_COMPLETE_TRANSACTION_PARAMS, + e); + } } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockByNumber.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockByNumber.java index c1bdcc37dd2..0a26a24ee38 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockByNumber.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockByNumber.java @@ -17,7 +17,10 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResult; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; @@ -62,7 +65,12 @@ public String getName() { @Override protected BlockParameter blockParameter(final JsonRpcRequestContext request) { - return request.getRequiredParameter(0, BlockParameter.class); + try { + return request.getRequiredParameter(0, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } @Override @@ -117,6 +125,13 @@ private BlockResult transactionHash(final long blockNumber) { } private boolean isCompleteTransactions(final JsonRpcRequestContext request) { - return request.getRequiredParameter(1, Boolean.class); + try { + return request.getRequiredParameter(1, Boolean.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid return complete transaction parameter (index 1)", + RpcErrorType.INVALID_RETURN_COMPLETE_TRANSACTION_PARAMS, + e); + } } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockReceipts.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockReceipts.java index 92b3eb54542..a113e4ee8e8 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockReceipts.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockReceipts.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,7 +17,10 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameterOrBlockHash; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockReceiptsResult; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.TransactionReceiptResult; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.TransactionReceiptRootResult; @@ -53,7 +56,12 @@ public String getName() { @Override protected BlockParameterOrBlockHash blockParameterOrBlockHash( final JsonRpcRequestContext request) { - return request.getRequiredParameter(0, BlockParameterOrBlockHash.class); + try { + return request.getRequiredParameter(0, BlockParameterOrBlockHash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block or block hash parameters (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } @Override diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockTransactionCountByHash.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockTransactionCountByHash.java index a9654555953..fc7c3cc70c2 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockTransactionCountByHash.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockTransactionCountByHash.java @@ -17,8 +17,11 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; @@ -37,7 +40,15 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final Hash hash = requestContext.getRequiredParameter(0, Hash.class); + final Hash hash; + try { + hash = requestContext.getRequiredParameter(0, Hash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block header hash parameter (index 0)", + RpcErrorType.INVALID_BLOCK_HASH_PARAMS, + e); + } final Integer count = blockchain.getTransactionCount(hash); if (count == -1) { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockTransactionCountByNumber.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockTransactionCountByNumber.java index b6819c7510d..95bd7cc5070 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockTransactionCountByNumber.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockTransactionCountByNumber.java @@ -16,7 +16,10 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; @@ -33,7 +36,12 @@ public String getName() { @Override protected BlockParameter blockParameter(final JsonRpcRequestContext request) { - return request.getRequiredParameter(0, BlockParameter.class); + try { + return request.getRequiredParameter(0, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameters (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } @Override diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetCode.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetCode.java index 2417b422fa2..0c9194b0b34 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetCode.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetCode.java @@ -18,7 +18,10 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameterOrBlockHash; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import java.util.function.Supplier; @@ -43,12 +46,23 @@ public String getName() { @Override protected BlockParameterOrBlockHash blockParameterOrBlockHash( final JsonRpcRequestContext request) { - return request.getRequiredParameter(1, BlockParameterOrBlockHash.class); + try { + return request.getRequiredParameter(1, BlockParameterOrBlockHash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block or block hash parameter (index 1)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } @Override protected String resultByBlockHash(final JsonRpcRequestContext request, final Hash blockHash) { - final Address address = request.getRequiredParameter(0, Address.class); + final Address address; + try { + address = request.getRequiredParameter(0, Address.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid address parameter (index 0)", RpcErrorType.INVALID_ADDRESS_PARAMS, e); + } return getBlockchainQueries().getCode(address, blockHash).map(Bytes::toString).orElse(null); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetFilterChanges.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetFilterChanges.java index fa9031efe53..b3fa7565d63 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetFilterChanges.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetFilterChanges.java @@ -17,7 +17,9 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.filter.FilterManager; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -43,7 +45,13 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final String filterId = requestContext.getRequiredParameter(0, String.class); + final String filterId; + try { + filterId = requestContext.getRequiredParameter(0, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid filter ID parameter (index 0)", RpcErrorType.INVALID_FILTER_PARAMS, e); + } final List blockHashes = filterManager.blockChanges(filterId); if (blockHashes != null) { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetFilterLogs.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetFilterLogs.java index 2e928c3b548..0fd32455b24 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetFilterLogs.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetFilterLogs.java @@ -16,7 +16,9 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.filter.FilterManager; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -41,7 +43,13 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final String filterId = requestContext.getRequiredParameter(0, String.class); + final String filterId; + try { + filterId = requestContext.getRequiredParameter(0, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid filter ID parameter (index 0)", RpcErrorType.INVALID_FILTER_PARAMS, e); + } final List logs = filterManager.logs(filterId); if (logs != null) { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetLogs.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetLogs.java index c4356ce1c2b..225870ba3a4 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetLogs.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetLogs.java @@ -16,7 +16,9 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.FilterParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -51,12 +53,18 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final FilterParameter filter = requestContext.getRequiredParameter(0, FilterParameter.class); + final FilterParameter filter; + try { + filter = requestContext.getRequiredParameter(0, FilterParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid filter parameter (index 0)", RpcErrorType.INVALID_FILTER_PARAMS, e); + } LOG.atTrace().setMessage("eth_getLogs FilterParameter: {}").addArgument(filter).log(); if (!filter.isValid()) { return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVALID_FILTER_PARAMS); } final AtomicReference ex = new AtomicReference<>(); @@ -78,16 +86,22 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { .getBlockNumber(blockchain) .orElseThrow( () -> - new Exception("fromBlock not found: " + filter.getFromBlock())); + new InvalidJsonRpcParameters( + "fromBlock not found: " + filter.getFromBlock(), + RpcErrorType.INVALID_BLOCK_NUMBER_PARAMS)); toBlockNumber = filter .getToBlock() .getBlockNumber(blockchain) .orElseThrow( - () -> new Exception("toBlock not found: " + filter.getToBlock())); + () -> + new InvalidJsonRpcParameters( + "toBlock not found: " + filter.getToBlock(), + RpcErrorType.INVALID_BLOCK_NUMBER_PARAMS)); if (maxLogRange > 0 && (toBlockNumber - fromBlockNumber) > maxLogRange) { - throw new IllegalArgumentException( - "Requested range exceeds maximum range limit"); + throw new InvalidJsonRpcParameters( + "Requested range exceeds maximum range limit", + RpcErrorType.EXCEEDS_RPC_MAX_BLOCK_RANGE); } } catch (final Exception e) { ex.set(e); @@ -107,12 +121,13 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { .addArgument(requestContext.getRequest()) .setCause(ex.get()) .log(); - if (ex.get() instanceof IllegalArgumentException) { + if (ex.get() instanceof InvalidJsonRpcParameters) { return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.EXCEEDS_RPC_MAX_BLOCK_RANGE); + requestContext.getRequest().getId(), + ((InvalidJsonRpcParameters) ex.get()).getRpcErrorType()); + } else { + throw new RuntimeException(ex.get()); } - return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); } return new JsonRpcSuccessResponse( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetMinerDataByBlockHash.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetMinerDataByBlockHash.java index 1d3d647e3f2..8b54364a999 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetMinerDataByBlockHash.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetMinerDataByBlockHash.java @@ -12,15 +12,17 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.ImmutableMinerDataResult; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.ImmutableUncleRewardResult; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.MinerDataResult; @@ -61,7 +63,13 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final Hash hash = requestContext.getRequest().getRequiredParameter(0, Hash.class); + final Hash hash; + try { + hash = requestContext.getRequest().getRequiredParameter(0, Hash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block hash parameter (index 0)", RpcErrorType.INVALID_BLOCK_HASH_PARAMS, e); + } BlockWithMetadata block = blockchain.get().blockByHash(hash).orElse(null); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetMinerDataByBlockNumber.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetMinerDataByBlockNumber.java index 64a89c89194..e0fe2460626 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetMinerDataByBlockNumber.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetMinerDataByBlockNumber.java @@ -12,13 +12,15 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.MinerDataResult; import org.hyperledger.besu.ethereum.api.query.BlockWithMetadata; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; @@ -41,7 +43,12 @@ public String getName() { @Override protected BlockParameter blockParameter(final JsonRpcRequestContext request) { - return request.getRequiredParameter(0, BlockParameter.class); + try { + return request.getRequiredParameter(0, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } @Override diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetProof.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetProof.java index 96602e23a97..d1920b5c1a2 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetProof.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetProof.java @@ -18,7 +18,9 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameterOrBlockHash; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -48,14 +50,25 @@ public String getName() { @Override protected BlockParameterOrBlockHash blockParameterOrBlockHash( final JsonRpcRequestContext request) { - return request.getRequiredParameter(2, BlockParameterOrBlockHash.class); + try { + return request.getRequiredParameter(2, BlockParameterOrBlockHash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block or block hash parameter (index 2)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } @Override protected Object resultByBlockHash( final JsonRpcRequestContext requestContext, final Hash blockHash) { - final Address address = requestContext.getRequiredParameter(0, Address.class); + final Address address; + try { + address = requestContext.getRequiredParameter(0, Address.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid address parameter (index 0)", RpcErrorType.INVALID_ADDRESS_PARAMS, e); + } final List storageKeys = getStorageKeys(requestContext); final Blockchain blockchain = getBlockchainQueries().getBlockchain(); @@ -94,8 +107,13 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { } private List getStorageKeys(final JsonRpcRequestContext request) { - return Arrays.stream(request.getRequiredParameter(1, String[].class)) - .map(UInt256::fromHexString) - .collect(Collectors.toList()); + try { + return Arrays.stream(request.getRequiredParameter(1, String[].class)) + .map(UInt256::fromHexString) + .collect(Collectors.toList()); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid storage keys parameters (index 1)", RpcErrorType.INVALID_STORAGE_KEYS_PARAMS, e); + } } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetStorageAt.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetStorageAt.java index e2741233f61..a171fc16d1d 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetStorageAt.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetStorageAt.java @@ -18,8 +18,11 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameterOrBlockHash; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.UInt256Parameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.apache.tuweni.units.bigints.UInt256; @@ -37,13 +40,30 @@ public String getName() { @Override protected BlockParameterOrBlockHash blockParameterOrBlockHash( final JsonRpcRequestContext request) { - return request.getRequiredParameter(2, BlockParameterOrBlockHash.class); + try { + return request.getRequiredParameter(2, BlockParameterOrBlockHash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block or block hash parameter (index 2)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } @Override protected String resultByBlockHash(final JsonRpcRequestContext request, final Hash blockHash) { - final Address address = request.getRequiredParameter(0, Address.class); - final UInt256 position = request.getRequiredParameter(1, UInt256Parameter.class).getValue(); + final Address address; + try { + address = request.getRequiredParameter(0, Address.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid address parameter (index 0)", RpcErrorType.INVALID_ADDRESS_PARAMS, e); + } + final UInt256 position; + try { + position = request.getRequiredParameter(1, UInt256Parameter.class).getValue(); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid position parameter (index 1)", RpcErrorType.INVALID_POSITION_PARAMS, e); + } return blockchainQueries .get() .storageAt(address, position, blockHash) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionByBlockHashAndIndex.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionByBlockHashAndIndex.java index 44fb91689d0..917c09a3265 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionByBlockHashAndIndex.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionByBlockHashAndIndex.java @@ -17,9 +17,12 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.UnsignedIntParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.TransactionCompleteResult; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.TransactionResult; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; @@ -42,8 +45,24 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final Hash hash = requestContext.getRequiredParameter(0, Hash.class); - final int index = requestContext.getRequiredParameter(1, UnsignedIntParameter.class).getValue(); + final Hash hash; + try { + hash = requestContext.getRequiredParameter(0, Hash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid transaction hash parameter (index 0)", + RpcErrorType.INVALID_TRANSACTION_HASH_PARAMS, + e); + } + final int index; + try { + index = requestContext.getRequiredParameter(1, UnsignedIntParameter.class).getValue(); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid transaction id parameter (index 1)", + RpcErrorType.INVALID_TRANSACTION_ID_PARAMS, + e); + } final Optional transactionWithMetadata = blockchain.transactionByBlockHashAndIndex(hash, index); final TransactionResult result = diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionByBlockNumberAndIndex.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionByBlockNumberAndIndex.java index ab08127ca79..338a8783688 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionByBlockNumberAndIndex.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionByBlockNumberAndIndex.java @@ -16,8 +16,11 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.UnsignedIntParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.TransactionCompleteResult; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.api.query.TransactionWithMetadata; @@ -37,13 +40,26 @@ public String getName() { @Override protected BlockParameter blockParameter(final JsonRpcRequestContext request) { - return request.getRequiredParameter(0, BlockParameter.class); + try { + return request.getRequiredParameter(0, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } @Override protected Object resultByBlockNumber( final JsonRpcRequestContext request, final long blockNumber) { - final int index = request.getRequiredParameter(1, UnsignedIntParameter.class).getValue(); + final int index; + try { + index = request.getRequiredParameter(1, UnsignedIntParameter.class).getValue(); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid transaction index parameter (index 1)", + RpcErrorType.INVALID_TRANSACTION_INDEX_PARAMS, + e); + } final Optional transactionWithMetadata = getBlockchainQueries().transactionByBlockNumberAndIndex(blockNumber, index); return transactionWithMetadata.map(TransactionCompleteResult::new).orElse(null); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionByHash.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionByHash.java index 2c854239e7b..5d8b0085226 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionByHash.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionByHash.java @@ -17,6 +17,8 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -48,9 +50,17 @@ public String getName() { public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { if (requestContext.getRequest().getParamLength() != 1) { return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAM_COUNT); + } + final Hash hash; + try { + hash = requestContext.getRequiredParameter(0, Hash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid transaction hash parameter (index 0)", + RpcErrorType.INVALID_TRANSACTION_HASH_PARAMS, + e); } - final Hash hash = requestContext.getRequiredParameter(0, Hash.class); final JsonRpcSuccessResponse jsonRpcSuccessResponse = new JsonRpcSuccessResponse(requestContext.getRequest().getId(), getResult(hash)); return jsonRpcSuccessResponse; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionCount.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionCount.java index 053588e89a4..4e26f8ee989 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionCount.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionCount.java @@ -18,7 +18,10 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameterOrBlockHash; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -50,12 +53,23 @@ public String getName() { @Override protected BlockParameterOrBlockHash blockParameterOrBlockHash( final JsonRpcRequestContext request) { - return request.getRequiredParameter(1, BlockParameterOrBlockHash.class); + try { + return request.getRequiredParameter(1, BlockParameterOrBlockHash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block or block hash parameter (index 1)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } @Override protected Object pendingResult(final JsonRpcRequestContext request) { - final Address address = request.getRequiredParameter(0, Address.class); + final Address address; + try { + address = request.getRequiredParameter(0, Address.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid address parameter (index 0)", RpcErrorType.INVALID_ADDRESS_PARAMS, e); + } final long pendingNonce = transactionPoolSupplier.get().getNextNonceForSender(address).orElse(0); final long latestNonce = @@ -72,7 +86,13 @@ protected Object pendingResult(final JsonRpcRequestContext request) { @Override protected String resultByBlockHash(final JsonRpcRequestContext request, final Hash blockHash) { - final Address address = request.getRequiredParameter(0, Address.class); + final Address address; + try { + address = request.getRequiredParameter(0, Address.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid address parameter (index 0)", RpcErrorType.INVALID_ADDRESS_PARAMS, e); + } final long transactionCount = getBlockchainQueries().getTransactionCount(address, blockHash); return Quantity.create(transactionCount); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionReceipt.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionReceipt.java index 78c5f3b6504..44e4cfe0d6c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionReceipt.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionReceipt.java @@ -17,8 +17,11 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.TransactionReceiptResult; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.TransactionReceiptRootResult; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.TransactionReceiptStatusResult; @@ -46,7 +49,15 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final Hash hash = requestContext.getRequiredParameter(0, Hash.class); + final Hash hash; + try { + hash = requestContext.getRequiredParameter(0, Hash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid transaction hash parameter (index 0)", + RpcErrorType.INVALID_TRANSACTION_HASH_PARAMS, + e); + } final TransactionReceiptResult result = blockchainQueries .transactionReceiptByTransactionHash(hash, protocolSchedule) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleByBlockHashAndIndex.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleByBlockHashAndIndex.java index 311da6b45f4..f76b461ceff 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleByBlockHashAndIndex.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleByBlockHashAndIndex.java @@ -17,9 +17,12 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.UnsignedIntParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResult; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.UncleBlockResult; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; @@ -44,8 +47,20 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { } private BlockResult blockResult(final JsonRpcRequestContext requestContext) { - final Hash hash = requestContext.getRequiredParameter(0, Hash.class); - final int index = requestContext.getRequiredParameter(1, UnsignedIntParameter.class).getValue(); + final Hash hash; + try { + hash = requestContext.getRequiredParameter(0, Hash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block hash parameter (index 0)", RpcErrorType.INVALID_BLOCK_HASH_PARAMS, e); + } + final int index; + try { + index = requestContext.getRequiredParameter(1, UnsignedIntParameter.class).getValue(); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block index parameter (index 1)", RpcErrorType.INVALID_BLOCK_INDEX_PARAMS, e); + } return blockchain.getOmmer(hash, index).map(UncleBlockResult::build).orElse(null); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleByBlockNumberAndIndex.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleByBlockNumberAndIndex.java index 2687982b8e3..f7c9ecb0dd9 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleByBlockNumberAndIndex.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleByBlockNumberAndIndex.java @@ -16,8 +16,11 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.UnsignedIntParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResult; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.UncleBlockResult; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; @@ -35,13 +38,24 @@ public String getName() { @Override protected BlockParameter blockParameter(final JsonRpcRequestContext request) { - return request.getRequiredParameter(0, BlockParameter.class); + try { + return request.getRequiredParameter(0, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } @Override protected BlockResult resultByBlockNumber( final JsonRpcRequestContext request, final long blockNumber) { - final int index = request.getRequiredParameter(1, UnsignedIntParameter.class).getValue(); + final int index; + try { + index = request.getRequiredParameter(1, UnsignedIntParameter.class).getValue(); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block index (index 1)", RpcErrorType.INVALID_BLOCK_INDEX_PARAMS, e); + } return getBlockchainQueries() .getOmmer(blockNumber, index) .map(UncleBlockResult::build) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleCountByBlockHash.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleCountByBlockHash.java index c8efa19f309..02cd3c42425 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleCountByBlockHash.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleCountByBlockHash.java @@ -17,8 +17,11 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; @@ -37,7 +40,13 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final Hash hash = requestContext.getRequiredParameter(0, Hash.class); + final Hash hash; + try { + hash = requestContext.getRequiredParameter(0, Hash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block hash parameter (index 0)", RpcErrorType.INVALID_BLOCK_HASH_PARAMS, e); + } final String result = blockchain.getOmmerCount(hash).map(Quantity::create).orElse(null); return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), result); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleCountByBlockNumber.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleCountByBlockNumber.java index ddd130542c6..8d935942add 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleCountByBlockNumber.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleCountByBlockNumber.java @@ -16,7 +16,10 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; @@ -33,7 +36,12 @@ public String getName() { @Override protected BlockParameter blockParameter(final JsonRpcRequestContext request) { - return request.getRequiredParameter(0, BlockParameter.class); + try { + return request.getRequiredParameter(0, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } @Override diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthMaxPriorityFeePerGas.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthMaxPriorityFeePerGas.java new file mode 100644 index 00000000000..71cb5ea3c63 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthMaxPriorityFeePerGas.java @@ -0,0 +1,47 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; + +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity; +import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; + +public class EthMaxPriorityFeePerGas implements JsonRpcMethod { + + private final BlockchainQueries blockchainQueries; + + public EthMaxPriorityFeePerGas(final BlockchainQueries blockchainQueries) { + this.blockchainQueries = blockchainQueries; + } + + @Override + public String getName() { + return RpcMethod.ETH_GET_MAX_PRIORITY_FEE_PER_GAS.getMethodName(); + } + + @Override + public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { + return new JsonRpcSuccessResponse( + requestContext.getRequest().getId(), Quantity.create(fetchAndLimitPriorityFeePerGas())); + } + + private Wei fetchAndLimitPriorityFeePerGas() { + return blockchainQueries.gasPriorityFee(); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthNewFilter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthNewFilter.java index e2e4576d433..d6edccd2e7a 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthNewFilter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthNewFilter.java @@ -16,8 +16,10 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.filter.FilterManager; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.FilterParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -38,11 +40,17 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final FilterParameter filter = requestContext.getRequiredParameter(0, FilterParameter.class); + final FilterParameter filter; + try { + filter = requestContext.getRequiredParameter(0, FilterParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid filter paramters (index 0)", RpcErrorType.INVALID_FILTER_PARAMS, e); + } if (!filter.isValid()) { return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVALID_FILTER_PARAMS); } final String logFilterId = diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthSendRawTransaction.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthSendRawTransaction.java index 6f80a372ffe..10582e7a1ce 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthSendRawTransaction.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthSendRawTransaction.java @@ -14,11 +14,14 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; +import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcErrorConverter; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcRequestException; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; @@ -31,10 +34,11 @@ import org.hyperledger.besu.ethereum.rlp.RLPException; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; +import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; +import javax.annotation.Nonnull; import com.google.common.base.Suppliers; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,18 +68,33 @@ public String getName() { public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { if (requestContext.getRequest().getParamLength() != 1) { return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAM_COUNT); + } + final String rawTransaction; + try { + rawTransaction = requestContext.getRequiredParameter(0, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid transaction parameters (index 0)", RpcErrorType.INVALID_TRANSACTION_PARAMS, e); } - final String rawTransaction = requestContext.getRequiredParameter(0, String.class); final Transaction transaction; try { transaction = DomainObjectDecodeUtils.decodeRawTransaction(rawTransaction); + CompletableFuture.runAsync( + () -> { + Address sender = transaction.getSender(); + LOG.atTrace() + .setMessage("The sender for transaction {} is calculated : {}") + .addArgument(transaction::getHash) + .addArgument(sender) + .log(); + }); LOG.trace("Received local transaction {}", transaction); } catch (final RLPException e) { LOG.debug("RLPException: {} caused by {}", e.getMessage(), e.getCause()); return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVALID_BLOCK_PARAMS); } catch (final InvalidJsonRpcRequestException i) { LOG.debug("InvalidJsonRpcRequestException: {} caused by {}", i.getMessage(), i.getCause()); return new JsonRpcErrorResponse( @@ -95,7 +114,7 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { errorReason -> getJsonRpcResponse(requestContext, errorReason, validationResult)); } - @NotNull + @Nonnull private JsonRpcResponse getJsonRpcResponse( final JsonRpcRequestContext requestContext, final TransactionInvalidReason errorReason, @@ -103,7 +122,7 @@ private JsonRpcResponse getJsonRpcResponse( if (sendEmptyHashOnInvalidBlock) { return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), Hash.EMPTY.toString()); } else { - if (errorReason == TransactionInvalidReason.PLUGIN_TX_VALIDATOR) { + if (errorReason == TransactionInvalidReason.PLUGIN_TX_POOL_VALIDATOR) { final RpcErrorType rpcErrorType = JsonRpcErrorConverter.convertTransactionInvalidReason( validationResult.getInvalidReason()); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthSubmitHashRate.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthSubmitHashRate.java index c2e792b23f0..488f89b3a58 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthSubmitHashRate.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthSubmitHashRate.java @@ -16,8 +16,11 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator; import org.apache.tuweni.bytes.Bytes; @@ -37,8 +40,20 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final String hashRate = requestContext.getRequiredParameter(0, String.class); - final String id = requestContext.getRequiredParameter(1, String.class); + final String hashRate; + try { + hashRate = requestContext.getRequiredParameter(0, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid hash rate parameter (index 0)", RpcErrorType.INVALID_HASH_RATE_PARAMS, e); + } + final String id; + try { + id = requestContext.getRequiredParameter(1, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid sealer ID parameter (index 1)", RpcErrorType.INVALID_SEALER_ID_PARAMS, e); + } return new JsonRpcSuccessResponse( requestContext.getRequest().getId(), miningCoordinator.submitHashRate( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthSubmitWork.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthSubmitWork.java index 370643f203a..9002b2a0ddf 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthSubmitWork.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthSubmitWork.java @@ -17,6 +17,8 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -49,12 +51,29 @@ public String getName() { public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { final Optional solver = miner.getWorkDefinition(); if (solver.isPresent()) { - final PoWSolution solution = - new PoWSolution( - Bytes.fromHexString(requestContext.getRequiredParameter(0, String.class)).getLong(0), - requestContext.getRequiredParameter(2, Hash.class), - null, - Bytes.fromHexString(requestContext.getRequiredParameter(1, String.class))); + long nonce; + try { + nonce = + Bytes.fromHexString(requestContext.getRequiredParameter(0, String.class)).getLong(0); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid nonce parameter (index 0)", RpcErrorType.INVALID_NONCE_PARAMS, e); + } + Hash mixHash; + try { + mixHash = requestContext.getRequiredParameter(2, Hash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid mix hash parameter (index 2)", RpcErrorType.INVALID_MIX_HASH_PARAMS, e); + } + Bytes powHash; + try { + powHash = Bytes.fromHexString(requestContext.getRequiredParameter(1, String.class)); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid PoW hash parameter (index 1)", RpcErrorType.INVALID_POW_HASH_PARAMS, e); + } + final PoWSolution solution = new PoWSolution(nonce, mixHash, null, powHash); final boolean result = miner.submitWork(solution); return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), result); } else { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthUninstallFilter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthUninstallFilter.java index ede4f105d01..8771343ecfc 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthUninstallFilter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthUninstallFilter.java @@ -16,9 +16,12 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.filter.FilterManager; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; public class EthUninstallFilter implements JsonRpcMethod { @@ -35,7 +38,13 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final String filterId = requestContext.getRequiredParameter(0, String.class); + final String filterId; + try { + filterId = requestContext.getRequiredParameter(0, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid filter ID parameter (index 0)", RpcErrorType.INVALID_FILTER_PARAMS, e); + } return new JsonRpcSuccessResponse( requestContext.getRequest().getId(), filterManager.uninstallFilter(filterId)); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/ExecuteTransactionStep.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/ExecuteTransactionStep.java index 6ccdb85cd0d..db062a64d95 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/ExecuteTransactionStep.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/ExecuteTransactionStep.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu.. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -26,9 +26,9 @@ import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; -import org.hyperledger.besu.ethereum.vm.BlockHashLookup; import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup; import org.hyperledger.besu.ethereum.vm.DebugOperationTracer; +import org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; import java.util.List; import java.util.Optional; @@ -98,7 +98,6 @@ public TransactionTrace apply(final TransactionTrace transactionTrace) { final BlockHashLookup blockHashLookup = new CachingBlockHashLookup(header, blockchain); result = transactionProcessor.processTransaction( - blockchain, chainUpdater.getNextUpdater(), header, transactionTrace.getTransaction(), diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/ExecutionEngineJsonRpcMethod.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/ExecutionEngineJsonRpcMethod.java index 42d83a10d13..6e216d46936 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/ExecutionEngineJsonRpcMethod.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/ExecutionEngineJsonRpcMethod.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/JsonCallParameterUtil.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/JsonCallParameterUtil.java index 4af10b63fdf..df593f9fbc3 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/JsonCallParameterUtil.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/JsonCallParameterUtil.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,19 +17,28 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonCallParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; public class JsonCallParameterUtil { private JsonCallParameterUtil() {} public static JsonCallParameter validateAndGetCallParams(final JsonRpcRequestContext request) { - final JsonCallParameter callParams = request.getRequiredParameter(0, JsonCallParameter.class); + final JsonCallParameter callParams; + try { + callParams = request.getRequiredParameter(0, JsonCallParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid call parameters (index 0)", RpcErrorType.INVALID_CALL_PARAMS); + } if (callParams.getGasPrice() != null && (callParams.getMaxFeePerGas().isPresent() || callParams.getMaxPriorityFeePerGas().isPresent())) { throw new InvalidJsonRpcParameters( - "gasPrice cannot be used with maxFeePerGas or maxPriorityFeePerGas"); + "gasPrice cannot be used with maxFeePerGas or maxPriorityFeePerGas", + RpcErrorType.INVALID_GAS_PRICE_PARAMS); } return callParams; } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/NetServices.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/NetServices.java index 335a74846f2..fbc3a88574a 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/NetServices.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/NetServices.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; +import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; @@ -32,16 +33,19 @@ public class NetServices implements JsonRpcMethod { private final WebSocketConfiguration webSocketConfiguration; private final P2PNetwork p2pNetwork; private final MetricsConfiguration metricsConfiguration; + private final GraphQLConfiguration graphQLConfiguration; public NetServices( final JsonRpcConfiguration jsonRpcConfiguration, final WebSocketConfiguration webSocketConfiguration, final P2PNetwork p2pNetwork, - final MetricsConfiguration metricsConfiguration) { + final MetricsConfiguration metricsConfiguration, + final GraphQLConfiguration graphQLConfiguration) { this.jsonRpcConfiguration = jsonRpcConfiguration; this.webSocketConfiguration = webSocketConfiguration; this.p2pNetwork = p2pNetwork; this.metricsConfiguration = metricsConfiguration; + this.graphQLConfiguration = graphQLConfiguration; } @Override @@ -82,6 +86,11 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { createServiceDetailsMap( metricsConfiguration.getHost(), metricsConfiguration.getActualPort())); } + if (graphQLConfiguration.isEnabled()) { + servicesMapBuilder.put( + "graphQL", + createServiceDetailsMap(graphQLConfiguration.getHost(), graphQLConfiguration.getPort())); + } return new JsonRpcSuccessResponse( requestContext.getRequest().getId(), servicesMapBuilder.build()); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/PluginJsonRpcMethod.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/PluginJsonRpcMethod.java index 46f9986b1c2..5b15e9555c2 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/PluginJsonRpcMethod.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/PluginJsonRpcMethod.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,9 +14,6 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; -import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.INTERNAL_ERROR; -import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.PLUGIN_INTERNAL_ERROR; - import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; @@ -53,13 +50,9 @@ public JsonRpcResponse response(final JsonRpcRequestContext request) { final Object result = function.apply(() -> request.getRequest().getParams()); return new JsonRpcSuccessResponse(request.getRequest().getId(), result); } catch (final PluginRpcEndpointException ex) { - final JsonRpcError error = new JsonRpcError(PLUGIN_INTERNAL_ERROR, ex.getMessage()); - LOG.error("Error calling plugin JSON-RPC endpoint", ex); + final JsonRpcError error = new JsonRpcError(ex.getRpcMethodError(), ex.getData()); + LOG.debug("Error calling plugin JSON-RPC endpoint", ex); return new JsonRpcErrorResponse(request.getRequest().getId(), error); - } catch (final Exception ex) { - LOG.error("Error calling plugin JSON-RPC endpoint", ex); - return new JsonRpcErrorResponse( - request.getRequest().getId(), new JsonRpcError(INTERNAL_ERROR)); } } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/PluginsReloadConfiguration.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/PluginsReloadConfiguration.java index 34e9276c829..3761b8114af 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/PluginsReloadConfiguration.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/PluginsReloadConfiguration.java @@ -16,7 +16,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -56,9 +56,9 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { } reloadPluginConfig(namedPlugins.get(pluginName)); return new JsonRpcSuccessResponse(requestContext.getRequest().getId()); - } catch (InvalidJsonRpcParameters invalidJsonRpcParameters) { + } catch (JsonRpcParameterException e) { return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVAlID_PLUGIN_NAME_PARAMS); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceBlock.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceBlock.java index f4e12554738..adacf01e764 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceBlock.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceBlock.java @@ -18,10 +18,13 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.FilterParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.Tracer; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTrace; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.flat.FlatTraceGenerator; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.flat.RewardTraceGenerator; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; @@ -68,7 +71,12 @@ public String getName() { @Override protected BlockParameter blockParameter(final JsonRpcRequestContext request) { - return request.getRequiredParameter(0, BlockParameter.class); + try { + return request.getRequiredParameter(0, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } @Override @@ -116,7 +124,7 @@ protected ArrayNodeWrapper traceBlock( "step", "action"); DebugOperationTracer debugOperationTracer = - new DebugOperationTracer(new TraceOptions(false, false, true)); + new DebugOperationTracer(new TraceOptions(false, false, true), false); ExecuteTransactionStep executeTransactionStep = new ExecuteTransactionStep( chainUpdater, diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceCall.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceCall.java index 724f3f780f9..082283a8158 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceCall.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceCall.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -18,9 +18,12 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.TraceTypeParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTrace; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.debug.TraceOptions; @@ -41,7 +44,7 @@ public TraceCall( final BlockchainQueries blockchainQueries, final ProtocolSchedule protocolSchedule, final TransactionSimulator transactionSimulator) { - super(blockchainQueries, protocolSchedule, transactionSimulator); + super(blockchainQueries, protocolSchedule, transactionSimulator, false); } @Override @@ -56,7 +59,12 @@ protected TraceOptions getTraceOptions(final JsonRpcRequestContext requestContex private Set getTraceTypes( final JsonRpcRequestContext requestContext) { - return requestContext.getRequiredParameter(1, TraceTypeParameter.class).getTraceTypes(); + try { + return requestContext.getRequiredParameter(1, TraceTypeParameter.class).getTraceTypes(); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid trace type parameter (index 1)", RpcErrorType.INVALID_TRACE_TYPE_PARAMS, e); + } } @Override @@ -73,7 +81,7 @@ protected PreCloseStateHandler getSimulatorResultHandler( final TransactionTrace transactionTrace = new TransactionTrace( - result.getTransaction(), result.getResult(), tracer.getTraceFrames()); + result.transaction(), result.result(), tracer.getTraceFrames()); final Block block = blockchainQueriesSupplier.get().getBlockchain().getChainHeadBlock(); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceCallMany.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceCallMany.java index 4ee9ff04e33..10d4018bce2 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceCallMany.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceCallMany.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -19,8 +19,10 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonCallParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.TraceCallManyParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.TraceTypeParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTrace; @@ -63,14 +65,15 @@ public String getName() { @Override protected BlockParameter blockParameter(final JsonRpcRequestContext request) { - final Optional maybeBlockParameter = - request.getOptionalParameter(1, BlockParameter.class); - - if (maybeBlockParameter.isPresent()) { - return maybeBlockParameter.get(); + final Optional maybeBlockParameter; + try { + maybeBlockParameter = request.getOptionalParameter(1, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 1)", RpcErrorType.INVALID_BLOCK_PARAMS, e); } - return BlockParameter.LATEST; + return maybeBlockParameter.orElse(BlockParameter.LATEST); } @Override @@ -79,7 +82,7 @@ protected Object resultByBlockNumber( if (requestContext.getRequest().getParamLength() != 2) { return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAM_COUNT); } final TraceCallManyParameter[] transactionsAndTraceTypeParameters; @@ -96,7 +99,7 @@ protected Object resultByBlockNumber( } catch (final Exception e) { LOG.error("Error parsing trace_callMany parameters: {}", e.getLocalizedMessage()); return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVALID_TRACE_CALL_MANY_PARAMS); } final Optional maybeBlockHeader = @@ -153,7 +156,8 @@ private JsonNode getSingleCallResult( final BlockHeader header, final WorldUpdater worldUpdater) { final Set traceTypes = traceTypeParameter.getTraceTypes(); - final DebugOperationTracer tracer = new DebugOperationTracer(buildTraceOptions(traceTypes)); + final DebugOperationTracer tracer = + new DebugOperationTracer(buildTraceOptions(traceTypes), false); final Optional maybeSimulatorResult = transactionSimulator.processWithWorldUpdater( callParameter, buildTransactionValidationParams(), tracer, header, worldUpdater); @@ -169,7 +173,7 @@ private JsonNode getSingleCallResult( final TransactionTrace transactionTrace = new TransactionTrace( - simulatorResult.getTransaction(), simulatorResult.getResult(), tracer.getTraceFrames()); + simulatorResult.transaction(), simulatorResult.result(), tracer.getTraceFrames()); final Block block = blockchainQueriesSupplier.get().getBlockchain().getChainHeadBlock(); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceFilter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceFilter.java index 8c94f785065..74bfec87c29 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceFilter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceFilter.java @@ -19,13 +19,17 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.FilterParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockTracer; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.Tracer; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTrace; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.flat.FlatTrace; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.flat.RewardTraceGenerator; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; @@ -56,22 +60,25 @@ import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Stream; +import javax.annotation.Nonnull; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.tuweni.bytes.Bytes32; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class TraceFilter extends TraceBlock { private static final Logger LOG = LoggerFactory.getLogger(TraceFilter.class); + private final Long maxRange; public TraceFilter( final Supplier blockTracerSupplier, final ProtocolSchedule protocolSchedule, - final BlockchainQueries blockchainQueries) { + final BlockchainQueries blockchainQueries, + final Long maxRange) { super(protocolSchedule, blockchainQueries); + this.maxRange = maxRange; } @Override @@ -81,13 +88,29 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final FilterParameter filterParameter = - requestContext.getRequiredParameter(0, FilterParameter.class); + final FilterParameter filterParameter; + try { + filterParameter = requestContext.getRequiredParameter(0, FilterParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid filter parameter (index 0)", RpcErrorType.INVALID_FILTER_PARAMS, e); + } final long fromBlock = resolveBlockNumber(filterParameter.getFromBlock()); final long toBlock = resolveBlockNumber(filterParameter.getToBlock()); LOG.trace("Received RPC rpcName={} fromBlock={} toBlock={}", getName(), fromBlock, toBlock); + if (maxRange > 0 && toBlock - fromBlock > maxRange) { + LOG.atDebug() + .setMessage("trace_filter request {} failed:") + .addArgument(requestContext.getRequest()) + .setCause( + new IllegalArgumentException(RpcErrorType.EXCEEDS_RPC_MAX_BLOCK_RANGE.getMessage())) + .log(); + return new JsonRpcErrorResponse( + requestContext.getRequest().getId(), RpcErrorType.EXCEEDS_RPC_MAX_BLOCK_RANGE); + } + final ObjectMapper mapper = new ObjectMapper(); final ArrayNodeWrapper resultArrayNode = new ArrayNodeWrapper( @@ -145,7 +168,7 @@ private JsonRpcResponse traceFilterWithPipeline( "action"); DebugOperationTracer debugOperationTracer = - new DebugOperationTracer(new TraceOptions(false, false, true)); + new DebugOperationTracer(new TraceOptions(false, false, true), false); ExecuteTransactionStep executeTransactionStep = new ExecuteTransactionStep( chainUpdater, @@ -195,7 +218,7 @@ private JsonRpcResponse traceFilterWithPipeline( return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), result.getArrayNode()); } - @NotNull + @Nonnull private List getBlockList( final long fromBlock, final long toBlock, final Optional block) { List blockList = new ArrayList<>(); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceFilterSource.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceFilterSource.java index faee7dce962..c5fb5e7c9b6 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceFilterSource.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceFilterSource.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTrace; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceGet.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceGet.java index f6289e2ce2d..e1c27aed798 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceGet.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceGet.java @@ -17,6 +17,8 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockTracer; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; @@ -51,11 +53,27 @@ public String getName() { public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { if (requestContext.getRequest().getParamLength() != 2) { return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAM_COUNT); } - final Hash transactionHash = requestContext.getRequiredParameter(0, Hash.class); - final List traceNumbersAsStrings = requestContext.getRequiredParameter(1, List.class); + final Hash transactionHash; + try { + transactionHash = requestContext.getRequiredParameter(0, Hash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid transaction hash parameter (index 0)", + RpcErrorType.INVALID_TRANSACTION_HASH_PARAMS, + e); + } + final List traceNumbersAsStrings; + try { + traceNumbersAsStrings = requestContext.getRequiredParameter(1, List.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid trace numbers parameters (index 1)", + RpcErrorType.INVALID_TRACE_NUMBERS_PARAMS, + e); + } final List traceAddress = traceNumbersAsStrings.stream() .map(t -> Integer.parseInt(((String) t).substring(2), 16)) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceRawTransaction.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceRawTransaction.java index 8d1f8f6506e..71c8247784c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceRawTransaction.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceRawTransaction.java @@ -18,6 +18,8 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.TraceTypeParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTrace; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; @@ -67,12 +69,23 @@ protected Object resultByBlockNumber( public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { if (requestContext.getRequest().getParamLength() != 2) { return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAM_COUNT); } - final var rawTransaction = requestContext.getRequiredParameter(0, String.class); - final TraceTypeParameter traceTypeParameter = - requestContext.getRequiredParameter(1, TraceTypeParameter.class); + final String rawTransaction; + try { + rawTransaction = requestContext.getRequiredParameter(0, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid transaction parameters (index 0)", RpcErrorType.INVALID_TRANSACTION_PARAMS, e); + } + final TraceTypeParameter traceTypeParameter; + try { + traceTypeParameter = requestContext.getRequiredParameter(1, TraceTypeParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid trace type parameter (index 1)", RpcErrorType.INVALID_TRACE_TYPE_PARAMS, e); + } LOG.trace( "Received RPC rpcName={} rawTx={} traceType={}", getName(), @@ -85,11 +98,12 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { LOG.trace("rawTx decoded to transaction {}", transaction); } catch (final RLPException | IllegalArgumentException e) { return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVALID_TRANSACTION_PARAMS); } final Set traceTypes = traceTypeParameter.getTraceTypes(); - final DebugOperationTracer tracer = new DebugOperationTracer(buildTraceOptions(traceTypes)); + final DebugOperationTracer tracer = + new DebugOperationTracer(buildTraceOptions(traceTypes), false); final BlockHeader headBlock = blockchainQueriesSupplier.get().headBlockHeader(); return transactionSimulator .process( @@ -101,7 +115,7 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { result -> { final TransactionTrace transactionTrace = new TransactionTrace( - result.getTransaction(), result.getResult(), tracer.getTraceFrames()); + result.transaction(), result.result(), tracer.getTraceFrames()); final Optional maybeBlock = blockchainQueriesSupplier .get() diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceReplayBlockTransactions.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceReplayBlockTransactions.java index f14b88932fc..202d2ddbbbc 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceReplayBlockTransactions.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceReplayBlockTransactions.java @@ -18,11 +18,14 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.TraceBlock.ChainUpdater; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.TraceTypeParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.Tracer; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTrace; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.TraceReplayResult; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.api.util.ArrayNodeWrapper; @@ -70,14 +73,24 @@ public String getName() { @Override protected BlockParameter blockParameter(final JsonRpcRequestContext request) { - return request.getRequiredParameter(0, BlockParameter.class); + try { + return request.getRequiredParameter(0, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } @Override protected ArrayNode resultByBlockNumber( final JsonRpcRequestContext request, final long blockNumber) { - final TraceTypeParameter traceTypeParameter = - request.getRequiredParameter(1, TraceTypeParameter.class); + final TraceTypeParameter traceTypeParameter; + try { + traceTypeParameter = request.getRequiredParameter(1, TraceTypeParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid trace type parameter (index 1)", RpcErrorType.INVALID_TRACE_TYPE_PARAMS, e); + } LOG.trace( "Received RPC rpcName={} block={} traceType={}", @@ -93,7 +106,7 @@ protected ArrayNode resultByBlockNumber( return getBlockchainQueries() .getBlockchain() .getBlockByNumber(blockNumber) - .map((block) -> traceBlock(block, traceTypeParameter)) + .map(block -> traceBlock(block, traceTypeParameter)) .orElse(null); } @@ -127,7 +140,7 @@ private ArrayNode traceBlock(final Block block, final TraceTypeParameter traceTy "step", "action"); final DebugOperationTracer debugOperationTracer = - new DebugOperationTracer(new TraceOptions(false, false, true)); + new DebugOperationTracer(new TraceOptions(false, false, true), false); final ExecuteTransactionStep executeTransactionStep = new ExecuteTransactionStep( chainUpdater, diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceReplayTransactionStep.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceReplayTransactionStep.java index 651e0b4798e..6dc9c1ae512 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceReplayTransactionStep.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceReplayTransactionStep.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceTransaction.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceTransaction.java index 55d1c0569d9..be694236811 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceTransaction.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceTransaction.java @@ -17,9 +17,12 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockTracer; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; @@ -45,7 +48,15 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final Hash transactionHash = requestContext.getRequiredParameter(0, Hash.class); + final Hash transactionHash; + try { + transactionHash = requestContext.getRequiredParameter(0, Hash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid transaction hash parameter (index 0)", + RpcErrorType.INVALID_TRANSACTION_HASH_PARAMS, + e); + } LOG.trace("Received RPC rpcName={} txHash={}", getName(), transactionHash); return new JsonRpcSuccessResponse( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TxPoolBesuPendingTransactions.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TxPoolBesuPendingTransactions.java index 067daa7a11e..81c366926a6 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TxPoolBesuPendingTransactions.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TxPoolBesuPendingTransactions.java @@ -16,13 +16,17 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.PendingTransactionsParams; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.TransactionPendingResult; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool.PendingTransactionFilter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool.PendingTransactionFilter.Filter; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import java.util.Collection; @@ -49,15 +53,34 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final Integer limit = requestContext.getRequiredParameter(0, Integer.class); - final List filters = - requestContext - .getOptionalParameter(1, PendingTransactionsParams.class) - .map(PendingTransactionsParams::filters) - .orElse(Collections.emptyList()); + final Collection pendingTransactions = + transactionPool.getPendingTransactions(); + final int limit; + try { + limit = + requestContext.getOptionalParameter(0, Integer.class).orElse(pendingTransactions.size()); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid transaction limit parameter (index 0)", + RpcErrorType.INVALID_TRANSACTION_LIMIT_PARAMS, + e); + } + final List filters; + try { + filters = + requestContext + .getOptionalParameter(1, PendingTransactionsParams.class) + .map(PendingTransactionsParams::filters) + .orElse(Collections.emptyList()); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid pending transactions parameter (index 1)", + RpcErrorType.INVALID_PENDING_TRANSACTIONS_PARAMS, + e); + } final Collection pendingTransactionsFiltered = - pendingTransactionFilter.reduce(transactionPool.getPendingTransactions(), filters, limit); + pendingTransactionFilter.reduce(pendingTransactions, filters, limit); return new JsonRpcSuccessResponse( requestContext.getRequest().getId(), diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/Web3Sha3.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/Web3Sha3.java index b1d137bb6b7..e34a462800a 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/Web3Sha3.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/Web3Sha3.java @@ -17,6 +17,8 @@ import org.hyperledger.besu.crypto.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -38,14 +40,20 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { if (requestContext.getRequest().getParamLength() != 1) { // Do we want custom messages for each different type of invalid params? return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAM_COUNT); } - final String data = requestContext.getRequiredParameter(0, String.class); + final String data; + try { + data = requestContext.getRequiredParameter(0, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid data parameter (index 0)", RpcErrorType.INVALID_DATA_PARAMS, e); + } if (!data.isEmpty() && !data.startsWith("0x")) { return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVALID_DATA_PARAMS); } try { @@ -54,7 +62,7 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { requestContext.getRequest().getId(), Hash.keccak256(byteData).toString()); } catch (final IllegalArgumentException err) { return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVALID_DATA_PARAMS); } } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineForkchoiceUpdated.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineForkchoiceUpdated.java index b5c38c1e682..11ec3d04c59 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineForkchoiceUpdated.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineForkchoiceUpdated.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; import static java.util.stream.Collectors.toList; +import static org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId.CANCUN; import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.INVALID; import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.SYNCING; import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.VALID; @@ -26,9 +27,11 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EngineForkchoiceUpdatedParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EnginePayloadAttributesParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; @@ -38,21 +41,22 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.mainnet.ScheduledProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import java.util.List; import java.util.Optional; import java.util.function.BiConsumer; +import java.util.stream.Collectors; import io.vertx.core.Vertx; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.spi.LoggingEventBuilder; public abstract class AbstractEngineForkchoiceUpdated extends ExecutionEngineJsonRpcMethod { private static final Logger LOG = LoggerFactory.getLogger(AbstractEngineForkchoiceUpdated.class); private final MergeMiningCoordinator mergeCoordinator; - protected final Long cancunTimestamp; + protected final Optional cancunMilestone; public AbstractEngineForkchoiceUpdated( final Vertx vertx, @@ -63,9 +67,7 @@ public AbstractEngineForkchoiceUpdated( super(vertx, protocolSchedule, protocolContext, engineCallListener); this.mergeCoordinator = mergeCoordinator; - Optional cancun = - protocolSchedule.hardforkFor(s -> s.fork().name().equalsIgnoreCase("Cancun")); - cancunTimestamp = cancun.map(ScheduledProtocolSpec.Hardfork::milestone).orElse(Long.MAX_VALUE); + cancunMilestone = protocolSchedule.milestoneFor(CANCUN); } protected ValidationResult validateParameter( @@ -80,10 +82,25 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) final Object requestId = requestContext.getRequest().getId(); - final EngineForkchoiceUpdatedParameter forkChoice = - requestContext.getRequiredParameter(0, EngineForkchoiceUpdatedParameter.class); - final Optional maybePayloadAttributes = - requestContext.getOptionalParameter(1, EnginePayloadAttributesParameter.class); + final EngineForkchoiceUpdatedParameter forkChoice; + try { + forkChoice = requestContext.getRequiredParameter(0, EngineForkchoiceUpdatedParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid engine forkchoice updated parameter (index 0)", + RpcErrorType.INVALID_ENGINE_FORKCHOICE_UPDATED_PARAMS, + e); + } + final Optional maybePayloadAttributes; + try { + maybePayloadAttributes = + requestContext.getOptionalParameter(1, EnginePayloadAttributesParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid engine payload attributes parameter (index 1)", + RpcErrorType.INVALID_ENGINE_FORKCHOICE_UPDATED_PAYLOAD_ATTRIBUTES, + e); + } LOG.debug("Forkchoice parameters {}", forkChoice); mergeContext @@ -170,7 +187,7 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) if (!getWithdrawalsValidator( protocolSchedule.get(), newHead, maybePayloadAttributes.get().getTimestamp()) .validateWithdrawals(withdrawals)) { - return new JsonRpcErrorResponse(requestId, getInvalidPayloadError()); + return new JsonRpcErrorResponse(requestId, RpcErrorType.INVALID_WITHDRAWALS_PARAMS); } } @@ -239,7 +256,7 @@ protected Optional isPayloadAttributeRelevantToNewHead( if (payloadAttributes.getTimestamp() <= headBlockHeader.getTimestamp()) { LOG.warn( "Payload attributes timestamp is smaller than timestamp of header in fork choice update"); - return Optional.of(new JsonRpcErrorResponse(requestId, getInvalidPayloadError())); + return Optional.of(new JsonRpcErrorResponse(requestId, getInvalidPayloadAttributesError())); } return Optional.empty(); @@ -277,12 +294,31 @@ private JsonRpcResponse handleNonValidForkchoiceUpdate( } private void logPayload(final EnginePayloadAttributesParameter payloadAttributes) { - LOG.atDebug() - .setMessage("timestamp: {}, prevRandao: {}, suggestedFeeRecipient: {}") - .addArgument(payloadAttributes::getTimestamp) - .addArgument(() -> payloadAttributes.getPrevRandao().toHexString()) - .addArgument(() -> payloadAttributes.getSuggestedFeeRecipient().toHexString()) - .log(); + String message = "payloadAttributes: timestamp: {}, prevRandao: {}, suggestedFeeRecipient: {}"; + LoggingEventBuilder builder = + LOG.atDebug() + .setMessage(message) + .addArgument(payloadAttributes::getTimestamp) + .addArgument(() -> payloadAttributes.getPrevRandao().toHexString()) + .addArgument(() -> payloadAttributes.getSuggestedFeeRecipient().toHexString()); + if (payloadAttributes.getWithdrawals() != null) { + message += ", withdrawals: {}"; + builder = + builder + .setMessage(message) + .addArgument( + payloadAttributes.getWithdrawals().stream() + .map(WithdrawalParameter::toString) + .collect(Collectors.joining(", ", "[", "]"))); + } + if (payloadAttributes.getParentBeaconBlockRoot() != null) { + message += ", parentBeaconBlockRoot: {}"; + builder = + builder + .setMessage(message) + .addArgument(() -> payloadAttributes.getParentBeaconBlockRoot().toHexString()); + } + builder.log(); } private boolean isValidForkchoiceState( @@ -343,10 +379,14 @@ protected boolean requireTerminalPoWBlockValidation() { return false; } - protected RpcErrorType getInvalidPayloadError() { + protected RpcErrorType getInvalidParametersError() { return RpcErrorType.INVALID_PARAMS; } + protected RpcErrorType getInvalidPayloadAttributesError() { + return RpcErrorType.INVALID_PAYLOAD_ATTRIBUTES; + } + // fcU calls are synchronous, no need to make volatile private long lastFcuInfoLog = System.currentTimeMillis(); private static final String logMessage = diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineGetPayload.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineGetPayload.java index a714b25d8b4..d95b532e77a 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineGetPayload.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineGetPayload.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,12 +14,14 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; +import org.hyperledger.besu.consensus.merge.PayloadWrapper; import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator; import org.hyperledger.besu.consensus.merge.blockcreation.PayloadIdentifier; -import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; @@ -68,49 +70,55 @@ public AbstractEngineGetPayload( public JsonRpcResponse syncResponse(final JsonRpcRequestContext request) { engineCallListener.executionEngineCalled(); - final PayloadIdentifier payloadId = request.getRequiredParameter(0, PayloadIdentifier.class); + final PayloadIdentifier payloadId; + try { + payloadId = request.getRequiredParameter(0, PayloadIdentifier.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid payload ID parameter (index 0)", RpcErrorType.INVALID_PAYLOAD_ID_PARAMS, e); + } mergeMiningCoordinator.finalizeProposalById(payloadId); - final Optional blockWithReceipts = - mergeContext.get().retrieveBlockById(payloadId); - if (blockWithReceipts.isPresent()) { - final var proposal = blockWithReceipts.get(); - LOG.atDebug().setMessage("assembledBlock {}").addArgument(() -> proposal).log(); + final Optional maybePayload = mergeContext.get().retrievePayloadById(payloadId); + if (maybePayload.isPresent()) { + final BlockWithReceipts proposal = maybePayload.get().blockWithReceipts(); + LOG.atDebug() + .setMessage("assembledBlock for payloadId {}: {}") + .addArgument(() -> payloadId) + .addArgument(() -> proposal.getBlock().toLogString()) + .log(); + LOG.atTrace().setMessage("assembledBlock with receipts {}").addArgument(() -> proposal).log(); ValidationResult forkValidationResult = validateForkSupported(proposal.getHeader().getTimestamp()); if (!forkValidationResult.isValid()) { return new JsonRpcErrorResponse(request.getRequest().getId(), forkValidationResult); } - return createResponse(request, payloadId, proposal); + return createResponse(request, maybePayload.get()); } return new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.UNKNOWN_PAYLOAD); } - protected void logProposal( - final PayloadIdentifier payloadId, - final BlockWithReceipts proposal, - final Optional maybeReward) { - final BlockHeader proposalHeader = proposal.getHeader(); + protected void logProposal(final PayloadWrapper payload) { + final BlockHeader proposalHeader = payload.blockWithReceipts().getHeader(); final float gasUsedPerc = 100.0f * proposalHeader.getGasUsed() / proposalHeader.getGasLimit(); final String message = "Fetch block proposal by identifier: {}, hash: {}, " + "number: {}, coinbase: {}, transaction count: {}, gas used: {}%" - + maybeReward.map(unused -> ", reward: {}").orElse("{}"); + + " reward: {}"; LOG.atInfo() .setMessage(message) - .addArgument(payloadId::toHexString) + .addArgument(payload.payloadIdentifier()::toHexString) .addArgument(proposalHeader::getHash) .addArgument(proposalHeader::getNumber) .addArgument(proposalHeader::getCoinbase) - .addArgument(() -> proposal.getBlock().getBody().getTransactions().size()) + .addArgument( + () -> payload.blockWithReceipts().getBlock().getBody().getTransactions().size()) .addArgument(() -> String.format("%1.2f", gasUsedPerc)) - .addArgument(maybeReward.map(Wei::toHumanReadableString).orElse("")) + .addArgument(payload.blockValue()::toHumanReadableString) .log(); } protected abstract JsonRpcResponse createResponse( - final JsonRpcRequestContext request, - final PayloadIdentifier payloadId, - final BlockWithReceipts blockWithReceipts); + final JsonRpcRequestContext request, final PayloadWrapper payload); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineNewPayload.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineNewPayload.java index a5b8acfc056..78b96796cbd 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineNewPayload.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineNewPayload.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -20,11 +20,13 @@ import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.INVALID_BLOCK_HASH; import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.SYNCING; import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.VALID; -import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.DepositsValidatorProvider.getDepositsValidator; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.RequestValidatorProvider.getConsolidationRequestValidator; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.RequestValidatorProvider.getDepositRequestValidator; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.RequestValidatorProvider.getWithdrawalRequestValidator; import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.WithdrawalsValidatorProvider.getWithdrawalsValidator; -import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.INVALID_PARAMS; import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator; +import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.BlobGas; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.VersionedHash; @@ -32,11 +34,14 @@ import org.hyperledger.besu.ethereum.BlockProcessingResult; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcRequestException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.DepositParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.ConsolidationRequestParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.DepositRequestParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EnginePayloadParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalParameter; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalRequestParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -46,8 +51,8 @@ import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; -import org.hyperledger.besu.ethereum.core.Deposit; import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.core.Request; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.core.encoding.EncodingContext; @@ -59,6 +64,7 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.mainnet.feemarket.ExcessBlobGasCalculator; +import org.hyperledger.besu.ethereum.mainnet.requests.RequestUtil; import org.hyperledger.besu.ethereum.rlp.RLPException; import org.hyperledger.besu.ethereum.trie.MerkleTrieException; import org.hyperledger.besu.plugin.services.exception.StorageException; @@ -101,16 +107,37 @@ public AbstractEngineNewPayload( public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) { engineCallListener.executionEngineCalled(); - final EnginePayloadParameter blockParam = - requestContext.getRequiredParameter(0, EnginePayloadParameter.class); + final EnginePayloadParameter blockParam; + try { + blockParam = requestContext.getRequiredParameter(0, EnginePayloadParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcRequestException( + "Invalid engine payload parameter (index 0)", + RpcErrorType.INVALID_ENGINE_NEW_PAYLOAD_PARAMS, + e); + } - final Optional> maybeVersionedHashParam = - requestContext.getOptionalList(1, String.class); + final Optional> maybeVersionedHashParam; + try { + maybeVersionedHashParam = requestContext.getOptionalList(1, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcRequestException( + "Invalid versioned hash parameters (index 1)", + RpcErrorType.INVALID_VERSIONED_HASH_PARAMS, + e); + } final Object reqId = requestContext.getRequest().getId(); - Optional maybeParentBeaconBlockRootParam = - requestContext.getOptionalParameter(2, String.class); + Optional maybeParentBeaconBlockRootParam; + try { + maybeParentBeaconBlockRootParam = requestContext.getOptionalParameter(2, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcRequestException( + "Invalid parent beacon block root parameters (index 2)", + RpcErrorType.INVALID_PARENT_BEACON_BLOCK_ROOT_PARAMS, + e); + } final Optional maybeParentBeaconBlockRoot = maybeParentBeaconBlockRootParam.map(Bytes32::fromHexString); @@ -153,19 +180,48 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) if (!getWithdrawalsValidator( protocolSchedule.get(), blockParam.getTimestamp(), blockParam.getBlockNumber()) .validateWithdrawals(maybeWithdrawals)) { - return new JsonRpcErrorResponse( - reqId, new JsonRpcError(INVALID_PARAMS, "Invalid withdrawals")); + return new JsonRpcErrorResponse(reqId, RpcErrorType.INVALID_WITHDRAWALS_PARAMS); } - final Optional> maybeDeposits = - Optional.ofNullable(blockParam.getDeposits()) - .map(ds -> ds.stream().map(DepositParameter::toDeposit).collect(toList())); - if (!getDepositsValidator( + final Optional> maybeDepositRequests = + Optional.ofNullable(blockParam.getDepositRequests()) + .map(ds -> ds.stream().map(DepositRequestParameter::toDeposit).collect(toList())); + if (!getDepositRequestValidator( protocolSchedule.get(), blockParam.getTimestamp(), blockParam.getBlockNumber()) - .validateDepositParameter(maybeDeposits)) { - return new JsonRpcErrorResponse(reqId, new JsonRpcError(INVALID_PARAMS, "Invalid deposits")); + .validateParameter(maybeDepositRequests)) { + return new JsonRpcErrorResponse(reqId, RpcErrorType.INVALID_DEPOSIT_REQUEST_PARAMS); } + final Optional> maybeWithdrawalRequests = + Optional.ofNullable(blockParam.getWithdrawalRequests()) + .map( + withdrawalRequest -> + withdrawalRequest.stream() + .map(WithdrawalRequestParameter::toWithdrawalRequest) + .collect(toList())); + if (!getWithdrawalRequestValidator( + protocolSchedule.get(), blockParam.getTimestamp(), blockParam.getBlockNumber()) + .validateParameter(maybeWithdrawalRequests)) { + return new JsonRpcErrorResponse(reqId, RpcErrorType.INVALID_WITHDRAWALS_PARAMS); + } + + final Optional> maybeConsolidationRequests = + Optional.ofNullable(blockParam.getConsolidationRequests()) + .map( + consolidationRequest -> + consolidationRequest.stream() + .map(ConsolidationRequestParameter::toConsolidationRequest) + .collect(toList())); + if (!getConsolidationRequestValidator( + protocolSchedule.get(), blockParam.getTimestamp(), blockParam.getBlockNumber()) + .validateParameter(maybeConsolidationRequests)) { + return new JsonRpcErrorResponse(reqId, RpcErrorType.INVALID_CONSOLIDATION_REQUEST_PARAMS); + } + + Optional> maybeRequests = + RequestUtil.combine( + maybeDepositRequests, maybeWithdrawalRequests, maybeConsolidationRequests); + if (mergeContext.get().isSyncing()) { LOG.debug("We are syncing"); return respondWith(reqId, blockParam, null, SYNCING); @@ -178,6 +234,19 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) .map(Bytes::fromHexString) .map(in -> TransactionDecoder.decodeOpaqueBytes(in, EncodingContext.BLOCK_BODY)) .collect(Collectors.toList()); + transactions.forEach( + transaction -> + mergeCoordinator + .getEthScheduler() + .scheduleTxWorkerTask( + () -> { + Address sender = transaction.getSender(); + LOG.atTrace() + .setMessage("The sender for transaction {} is calculated : {}") + .addArgument(transaction::getHash) + .addArgument(sender) + .log(); + })); } catch (final RLPException | IllegalArgumentException e) { return respondWithInvalid( reqId, @@ -220,7 +289,7 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) ? null : BlobGas.fromHexString(blockParam.getExcessBlobGas()), maybeParentBeaconBlockRoot.orElse(null), - maybeDeposits.map(BodyValidation::depositsRoot).orElse(null), + maybeRequests.map(BodyValidation::requestsRoot).orElse(null), headerFunctions); // ensure the block hash matches the blockParam hash @@ -234,9 +303,12 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) return respondWithInvalid(reqId, blockParam, null, getInvalidBlockHashStatus(), errorMessage); } + final var blobTransactions = + transactions.stream().filter(transaction -> transaction.getType().supportsBlob()).toList(); + ValidationResult blobValidationResult = validateBlobs( - transactions, + blobTransactions, newBlockHeader, maybeParentHeader, maybeVersionedHashes, @@ -280,7 +352,7 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) final var block = new Block( newBlockHeader, - new BlockBody(transactions, Collections.emptyList(), maybeWithdrawals, maybeDeposits)); + new BlockBody(transactions, Collections.emptyList(), maybeWithdrawals, maybeRequests)); if (maybeParentHeader.isEmpty()) { LOG.atDebug() @@ -302,7 +374,14 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) final BlockProcessingResult executionResult = mergeCoordinator.rememberBlock(block); if (executionResult.isSuccessful()) { - logImportedBlockInfo(block, (System.currentTimeMillis() - startTimeMs) / 1000.0); + logImportedBlockInfo( + block, + blobTransactions.stream() + .map(Transaction::getVersionedHashes) + .flatMap(Optional::stream) + .mapToInt(List::size) + .sum(), + (System.currentTimeMillis() - startTimeMs) / 1000.0); return respondWith(reqId, blockParam, newBlockHeader.getHash(), VALID); } else { if (executionResult.causedBy().isPresent()) { @@ -380,10 +459,6 @@ JsonRpcResponse respondWithInvalid( invalidStatus, latestValidHash, Optional.of(validationError))); } - protected boolean requireTerminalPoWBlockValidation() { - return false; - } - protected EngineStatus getInvalidBlockHashStatus() { return INVALID; } @@ -396,36 +471,34 @@ protected ValidationResult validateParameters( } protected ValidationResult validateBlobs( - final List transactions, + final List blobTransactions, final BlockHeader header, final Optional maybeParentHeader, final Optional> maybeVersionedHashes, final ProtocolSpec protocolSpec) { - var blobTransactions = - transactions.stream().filter(transaction -> transaction.getType().supportsBlob()).toList(); - final List transactionVersionedHashes = new ArrayList<>(); for (Transaction transaction : blobTransactions) { var versionedHashes = transaction.getVersionedHashes(); // blob transactions must have at least one blob if (versionedHashes.isEmpty()) { return ValidationResult.invalid( - RpcErrorType.INVALID_PARAMS, "There must be at least one blob"); + RpcErrorType.INVALID_BLOB_COUNT, "There must be at least one blob"); } transactionVersionedHashes.addAll(versionedHashes.get()); } if (maybeVersionedHashes.isEmpty() && !transactionVersionedHashes.isEmpty()) { return ValidationResult.invalid( - RpcErrorType.INVALID_PARAMS, "Payload must contain versioned hashes for transactions"); + RpcErrorType.INVALID_VERSIONED_HASH_PARAMS, + "Payload must contain versioned hashes for transactions"); } // Validate versionedHashesParam if (maybeVersionedHashes.isPresent() && !maybeVersionedHashes.get().equals(transactionVersionedHashes)) { return ValidationResult.invalid( - RpcErrorType.INVALID_PARAMS, + RpcErrorType.INVALID_VERSIONED_HASH_PARAMS, "Versioned hashes from blob transactions do not match expected values"); } @@ -433,7 +506,7 @@ protected ValidationResult validateBlobs( if (maybeParentHeader.isPresent()) { if (!validateExcessBlobGas(header, maybeParentHeader.get(), protocolSpec)) { return ValidationResult.invalid( - RpcErrorType.INVALID_PARAMS, + RpcErrorType.INVALID_EXCESS_BLOB_GAS_PARAMS, "Payload excessBlobGas does not match calculated excessBlobGas"); } } @@ -442,7 +515,7 @@ protected ValidationResult validateBlobs( if (header.getBlobGasUsed().isPresent() && maybeVersionedHashes.isPresent()) { if (!validateBlobGasUsed(header, maybeVersionedHashes.get(), protocolSpec)) { return ValidationResult.invalid( - RpcErrorType.INVALID_PARAMS, + RpcErrorType.INVALID_BLOB_GAS_USED_PARAMS, "Payload BlobGasUsed does not match calculated BlobGasUsed"); } } @@ -450,7 +523,7 @@ protected ValidationResult validateBlobs( if (protocolSpec.getGasCalculator().blobGasCost(transactionVersionedHashes.size()) > protocolSpec.getGasLimitCalculator().currentBlobGasLimit()) { return ValidationResult.invalid( - RpcErrorType.INVALID_PARAMS, + RpcErrorType.INVALID_BLOB_COUNT, String.format("Invalid Blob Count: %d", transactionVersionedHashes.size())); } return ValidationResult.valid(); @@ -489,7 +562,7 @@ private Optional> extractVersionedHashes( .collect(Collectors.toList())); } - private void logImportedBlockInfo(final Block block, final double timeInS) { + private void logImportedBlockInfo(final Block block, final int blobCount, final double timeInS) { final StringBuilder message = new StringBuilder(); message.append("Imported #%,d / %d tx"); final List messageArgs = @@ -499,13 +572,14 @@ private void logImportedBlockInfo(final Block block, final double timeInS) { message.append(" / %d ws"); messageArgs.add(block.getBody().getWithdrawals().get().size()); } - if (block.getBody().getDeposits().isPresent()) { - message.append(" / %d ds"); - messageArgs.add(block.getBody().getDeposits().get().size()); + if (block.getBody().getRequests().isPresent()) { + message.append(" / %d rs"); + messageArgs.add(block.getBody().getRequests().get().size()); } - message.append(" / base fee %s / %,d (%01.1f%%) gas / (%s) in %01.3fs. Peers: %d"); + message.append(" / %d blobs / base fee %s / %,d (%01.1f%%) gas / (%s) in %01.3fs. Peers: %d"); messageArgs.addAll( List.of( + blobCount, block.getHeader().getBaseFee().map(Wei::toHumanReadableString).orElse("N/A"), block.getHeader().getGasUsed(), (block.getHeader().getGasUsed() * 100.0) / block.getHeader().getGasLimit(), diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/DepositsValidatorProvider.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/DepositsValidatorProvider.java deleted file mode 100644 index 36c29bd26a1..00000000000 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/DepositsValidatorProvider.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; - -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; -import org.hyperledger.besu.ethereum.mainnet.DepositsValidator; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; - -import java.util.Optional; - -public class DepositsValidatorProvider { - - static DepositsValidator getDepositsValidator( - final ProtocolSchedule protocolSchedule, final long blockTimestamp, final long blockNumber) { - - final BlockHeader blockHeader = - BlockHeaderBuilder.createDefault() - .timestamp(blockTimestamp) - .number(blockNumber) - .buildBlockHeader(); - return getDepositsValidator(protocolSchedule.getByBlockHeader(blockHeader)); - } - - private static DepositsValidator getDepositsValidator(final ProtocolSpec protocolSchedule) { - return Optional.ofNullable(protocolSchedule) - .map(ProtocolSpec::getDepositsValidator) - .orElseGet(DepositsValidator.ProhibitedDeposits::new); - } -} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineCallListener.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineCallListener.java index 1020bca0dfa..77979bc4893 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineCallListener.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineCallListener.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeCapabilities.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeCapabilities.java index fcc26cad641..2666888ed63 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeCapabilities.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeCapabilities.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -20,9 +20,12 @@ import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import java.util.List; import java.util.stream.Collectors; @@ -55,7 +58,17 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) LOG.atTrace() .setMessage("received remote capabilities: {}") - .addArgument(() -> requestContext.getRequiredParameter(0, String[].class)) + .addArgument( + () -> { + try { + return requestContext.getRequiredParameter(0, String[].class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid remote capabilities parameters (index 0)", + RpcErrorType.INVALID_REMOTE_CAPABILITIES_PARAMS, + e); + } + }) .log(); final List localCapabilities = @@ -63,7 +76,6 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) .filter(e -> e.getMethodName().startsWith("engine_")) .filter(e -> !e.equals(ENGINE_EXCHANGE_CAPABILITIES)) .filter(e -> !e.equals(ENGINE_PREPARE_PAYLOAD_DEBUG)) - .filter(e -> !e.getMethodName().endsWith("6110")) .map(RpcMethod::getMethodName) .collect(Collectors.toList()); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeTransitionConfiguration.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeTransitionConfiguration.java index 92eb2ef715c..924e8ac989c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeTransitionConfiguration.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeTransitionConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -20,10 +20,13 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EngineExchangeTransitionConfigurationParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineExchangeTransitionConfigurationResult; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Difficulty; @@ -65,9 +68,17 @@ public String getName() { public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) { engineCallListener.executionEngineCalled(); - final EngineExchangeTransitionConfigurationParameter remoteTransitionConfiguration = - requestContext.getRequiredParameter( - 0, EngineExchangeTransitionConfigurationParameter.class); + final EngineExchangeTransitionConfigurationParameter remoteTransitionConfiguration; + try { + remoteTransitionConfiguration = + requestContext.getRequiredParameter( + 0, EngineExchangeTransitionConfigurationParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid engine exchange transition configuration parameters (index 0)", + RpcErrorType.INVALID_ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_PARAMS, + e); + } final Object reqId = requestContext.getRequest().getId(); LOG.atTrace() diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV1.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV1.java index 6aa5f0964b2..6397afdda3c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV1.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV1.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -54,7 +54,7 @@ protected boolean requireTerminalPoWBlockValidation() { } @Override - protected RpcErrorType getInvalidPayloadError() { + protected RpcErrorType getInvalidParametersError() { return RpcErrorType.INVALID_PAYLOAD_ATTRIBUTES; } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV2.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV2.java index b6406a3e662..1dedf2919a3 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV2.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV2.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -21,6 +21,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import java.util.Optional; @@ -51,19 +52,22 @@ public String getName() { @Override protected Optional isPayloadAttributesValid( final Object requestId, final EnginePayloadAttributesParameter payloadAttributes) { - if (payloadAttributes.getTimestamp() >= cancunTimestamp) { - if (payloadAttributes.getParentBeaconBlockRoot() == null - || payloadAttributes.getParentBeaconBlockRoot().isEmpty()) { - return Optional.of(new JsonRpcErrorResponse(requestId, RpcErrorType.UNSUPPORTED_FORK)); - } else { - return Optional.of(new JsonRpcErrorResponse(requestId, RpcErrorType.INVALID_PARAMS)); - } - } else if (payloadAttributes.getParentBeaconBlockRoot() != null) { + + if (payloadAttributes.getParentBeaconBlockRoot() != null) { LOG.error( - "Parent beacon block root hash present in payload attributes before cancun hardfork"); - return Optional.of(new JsonRpcErrorResponse(requestId, RpcErrorType.INVALID_PARAMS)); - } else { - return Optional.empty(); + "Parent beacon block root hash present in payload attributes before Cancun hardfork"); + return Optional.of(new JsonRpcErrorResponse(requestId, getInvalidPayloadAttributesError())); } + + return Optional.empty(); + } + + @Override + protected ValidationResult validateForkSupported(final long blockTimestamp) { + if (cancunMilestone.isPresent() && blockTimestamp >= cancunMilestone.get()) { + return ValidationResult.invalid(RpcErrorType.UNSUPPORTED_FORK); + } + + return ValidationResult.valid(); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV3.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV3.java index c070d220854..c06b119328f 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV3.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV3.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; +import static org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId.CANCUN; + import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; @@ -22,7 +24,6 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.mainnet.ScheduledProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import java.util.Optional; @@ -33,7 +34,6 @@ public class EngineForkchoiceUpdatedV3 extends AbstractEngineForkchoiceUpdated { - private final Optional cancun; private static final Logger LOG = LoggerFactory.getLogger(EngineForkchoiceUpdatedV3.class); public EngineForkchoiceUpdatedV3( @@ -43,7 +43,6 @@ public EngineForkchoiceUpdatedV3( final MergeMiningCoordinator mergeCoordinator, final EngineCallListener engineCallListener) { super(vertx, protocolSchedule, protocolContext, mergeCoordinator, engineCallListener); - this.cancun = protocolSchedule.hardforkFor(s -> s.fork().name().equalsIgnoreCase("Cancun")); } @Override @@ -56,16 +55,19 @@ protected ValidationResult validateParameter( final EngineForkchoiceUpdatedParameter fcuParameter, final Optional maybePayloadAttributes) { if (fcuParameter.getHeadBlockHash() == null) { - return ValidationResult.invalid(RpcErrorType.INVALID_PARAMS, "Missing head block hash"); + return ValidationResult.invalid( + getInvalidPayloadAttributesError(), "Missing head block hash"); } else if (fcuParameter.getSafeBlockHash() == null) { - return ValidationResult.invalid(RpcErrorType.INVALID_PARAMS, "Missing safe block hash"); + return ValidationResult.invalid( + getInvalidPayloadAttributesError(), "Missing safe block hash"); } else if (fcuParameter.getFinalizedBlockHash() == null) { - return ValidationResult.invalid(RpcErrorType.INVALID_PARAMS, "Missing finalized block hash"); + return ValidationResult.invalid( + getInvalidPayloadAttributesError(), "Missing finalized block hash"); } if (maybePayloadAttributes.isPresent()) { if (maybePayloadAttributes.get().getParentBeaconBlockRoot() == null) { return ValidationResult.invalid( - RpcErrorType.INVALID_PARAMS, "Missing parent beacon block root hash"); + getInvalidPayloadAttributesError(), "Missing parent beacon block root hash"); } } return ValidationResult.valid(); @@ -73,18 +75,7 @@ protected ValidationResult validateParameter( @Override protected ValidationResult validateForkSupported(final long blockTimestamp) { - if (protocolSchedule.isPresent()) { - if (cancun.isPresent() && blockTimestamp >= cancun.get().milestone()) { - return ValidationResult.valid(); - } else { - return ValidationResult.invalid( - RpcErrorType.UNSUPPORTED_FORK, - "Cancun configured to start at timestamp: " + cancun.get().milestone()); - } - } else { - return ValidationResult.invalid( - RpcErrorType.UNSUPPORTED_FORK, "Configuration error, no schedule for Cancun fork set"); - } + return ForkSupportHelper.validateForkSupported(CANCUN, cancunMilestone, blockTimestamp); } @Override @@ -93,11 +84,17 @@ protected Optional isPayloadAttributesValid( if (payloadAttributes.getParentBeaconBlockRoot() == null) { LOG.error( "Parent beacon block root hash not present in payload attributes after cancun hardfork"); - return Optional.of(new JsonRpcErrorResponse(requestId, RpcErrorType.INVALID_PARAMS)); - } else if (payloadAttributes.getTimestamp() < cancun.get().milestone()) { + return Optional.of(new JsonRpcErrorResponse(requestId, getInvalidPayloadAttributesError())); + } + + if (payloadAttributes.getTimestamp() == 0) { + return Optional.of(new JsonRpcErrorResponse(requestId, getInvalidPayloadAttributesError())); + } + + if (cancunMilestone.isEmpty() || payloadAttributes.getTimestamp() < cancunMilestone.get()) { return Optional.of(new JsonRpcErrorResponse(requestId, RpcErrorType.UNSUPPORTED_FORK)); - } else { - return Optional.empty(); } + + return Optional.empty(); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetBlobsV1.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetBlobsV1.java new file mode 100644 index 00000000000..cc9ba2dce2a --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetBlobsV1.java @@ -0,0 +1,115 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; + +import org.hyperledger.besu.datatypes.BlobsWithCommitments; +import org.hyperledger.besu.datatypes.VersionedHash; +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlobAndProofV1; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; + +import java.util.Arrays; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import io.vertx.core.Vertx; + +/** + * #### Specification + * + *

1. Given an array of blob versioned hashes client software **MUST** respond with an array of + * `BlobAndProofV1` objects with matching versioned hashes, respecting the order of versioned hashes + * in the input array. + * + *

2. Client software **MUST** place responses in the order given in the request, using `null` + * for any missing blobs. For instance, if the request is `[A_versioned_hash, B_versioned_hash, + * C_versioned_hash]` and client software has data for blobs `A` and `C`, but doesn't have data for + * `B`, the response **MUST** be `[A, null, C]`. + * + *

3. Client software **MUST** support request sizes of at least 128 blob versioned hashes. The + * client **MUST** return `-38004: Too large request` error if the number of requested blobs is too + * large. + * + *

4. Client software **MAY** return an array of all `null` entries if syncing or otherwise + * unable to serve blob pool data. + * + *

5. Callers **MUST** consider that execution layer clients may prune old blobs from their pool, + * and will respond with `null` if a blob has been pruned. + */ +public class EngineGetBlobsV1 extends ExecutionEngineJsonRpcMethod { + + private final TransactionPool transactionPool; + + public EngineGetBlobsV1( + final Vertx vertx, + final ProtocolContext protocolContext, + final EngineCallListener engineCallListener, + final TransactionPool transactionPool) { + super(vertx, protocolContext, engineCallListener); + this.transactionPool = transactionPool; + } + + @Override + public String getName() { + return "engine_getBlobsV1"; + } + + @Override + public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) { + final VersionedHash[] versionedHashes; + try { + versionedHashes = requestContext.getRequiredParameter(0, VersionedHash[].class); + } catch (JsonRpcParameter.JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid versioned hashes parameter (index 0)", + RpcErrorType.INVALID_VERSIONED_HASHES_PARAMS, + e); + } + + if (versionedHashes.length > 128) { + return new JsonRpcErrorResponse( + requestContext.getRequest().getId(), + RpcErrorType.INVALID_ENGINE_GET_BLOBS_V1_TOO_LARGE_REQUEST); + } + + final List result = getBlobV1Result(versionedHashes); + + return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), result); + } + + private @Nonnull List getBlobV1Result(final VersionedHash[] versionedHashes) { + return Arrays.stream(versionedHashes) + .map(transactionPool::getBlobQuad) + .map(this::getBlobAndProofV1) + .toList(); + } + + private @Nullable BlobAndProofV1 getBlobAndProofV1(final BlobsWithCommitments.BlobQuad bq) { + if (bq == null) { + return null; + } + return new BlobAndProofV1( + bq.blob().getData().toHexString(), bq.kzgProof().getData().toHexString()); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetClientVersionV1.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetClientVersionV1.java new file mode 100644 index 00000000000..689cb44e6a3 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetClientVersionV1.java @@ -0,0 +1,57 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; + +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineGetClientVersionResultV1; + +import io.vertx.core.Vertx; + +public class EngineGetClientVersionV1 extends ExecutionEngineJsonRpcMethod { + private static final String ENGINE_CLIENT_CODE = "BU"; + private static final String ENGINE_CLIENT_NAME = "Besu"; + + private final String clientVersion; + private final String commit; + + public EngineGetClientVersionV1( + final Vertx vertx, + final ProtocolContext protocolContext, + final EngineCallListener engineCallListener, + final String clientVersion, + final String commit) { + super(vertx, protocolContext, engineCallListener); + this.clientVersion = clientVersion; + this.commit = commit; + } + + @Override + public String getName() { + return RpcMethod.ENGINE_GET_CLIENT_VERSION_V1.getMethodName(); + } + + @Override + public JsonRpcResponse syncResponse(final JsonRpcRequestContext request) { + return new JsonRpcSuccessResponse( + request.getRequest().getId(), + new EngineGetClientVersionResultV1( + ENGINE_CLIENT_CODE, ENGINE_CLIENT_NAME, clientVersion, commit.substring(0, 8))); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadBodiesByHashV1.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadBodiesByHashV1.java index dbbbeaec66c..facbd026d4e 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadBodiesByHashV1.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadBodiesByHashV1.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -18,7 +18,9 @@ import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -60,7 +62,13 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext request) { final Object reqId = request.getRequest().getId(); - final Hash[] blockHashes = request.getRequiredParameter(0, Hash[].class); + final Hash[] blockHashes; + try { + blockHashes = request.getRequiredParameter(0, Hash[].class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block hash parameters (index 0)", RpcErrorType.INVALID_BLOCK_HASH_PARAMS, e); + } LOG.atTrace() .setMessage("{} parameters: blockHashes {}") diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadBodiesByRangeV1.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadBodiesByRangeV1.java index 94576978097..2d21a7282ae 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadBodiesByRangeV1.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadBodiesByRangeV1.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,7 +17,9 @@ import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.UnsignedLongParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; @@ -57,9 +59,22 @@ public String getName() { public JsonRpcResponse syncResponse(final JsonRpcRequestContext request) { engineCallListener.executionEngineCalled(); - final long startBlockNumber = - request.getRequiredParameter(0, UnsignedLongParameter.class).getValue(); - final long count = request.getRequiredParameter(1, UnsignedLongParameter.class).getValue(); + final long startBlockNumber; + try { + startBlockNumber = request.getRequiredParameter(0, UnsignedLongParameter.class).getValue(); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid start block number parameter (index 0)", + RpcErrorType.INVALID_BLOCK_NUMBER_PARAMS, + e); + } + final long count; + try { + count = request.getRequiredParameter(1, UnsignedLongParameter.class).getValue(); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block count params (index 1)", RpcErrorType.INVALID_BLOCK_COUNT_PARAMS, e); + } final Object reqId = request.getRequest().getId(); LOG.atTrace() @@ -70,7 +85,7 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext request) { .log(); if (startBlockNumber < 1 || count < 1) { - return new JsonRpcErrorResponse(reqId, RpcErrorType.INVALID_PARAMS); + return new JsonRpcErrorResponse(reqId, RpcErrorType.INVALID_BLOCK_NUMBER_PARAMS); } if (count > getMaxRequestBlocks()) { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV1.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV1.java index 2484ac961e3..d9365282711 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV1.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV1.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,17 +14,14 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; +import org.hyperledger.besu.consensus.merge.PayloadWrapper; import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator; -import org.hyperledger.besu.consensus.merge.blockcreation.PayloadIdentifier; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory; -import org.hyperledger.besu.ethereum.core.BlockWithReceipts; - -import java.util.Optional; import io.vertx.core.Vertx; @@ -46,12 +43,10 @@ public String getName() { @Override protected JsonRpcResponse createResponse( - final JsonRpcRequestContext request, - final PayloadIdentifier payloadId, - final BlockWithReceipts blockWithReceipts) { + final JsonRpcRequestContext request, final PayloadWrapper payload) { final var result = - blockResultFactory.payloadTransactionCompleteV1(blockWithReceipts.getBlock()); - logProposal(payloadId, blockWithReceipts, Optional.empty()); + blockResultFactory.payloadTransactionCompleteV1(payload.blockWithReceipts().getBlock()); + logProposal(payload); return new JsonRpcSuccessResponse(request.getRequest().getId(), result); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV2.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV2.java index 4749d4233be..ce9cdcbbb3f 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV2.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV2.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,9 +14,10 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; +import static org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId.CANCUN; + +import org.hyperledger.besu.consensus.merge.PayloadWrapper; import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator; -import org.hyperledger.besu.consensus.merge.blockcreation.PayloadIdentifier; -import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; @@ -24,9 +25,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory; -import org.hyperledger.besu.ethereum.core.BlockWithReceipts; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.mainnet.ScheduledProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import java.util.Optional; @@ -35,7 +34,7 @@ public class EngineGetPayloadV2 extends AbstractEngineGetPayload { - private final Optional cancun; + private final Optional cancunMilestone; public EngineGetPayloadV2( final Vertx vertx, @@ -51,7 +50,7 @@ public EngineGetPayloadV2( mergeMiningCoordinator, blockResultFactory, engineCallListener); - this.cancun = schedule.hardforkFor(s -> s.fork().name().equalsIgnoreCase("Cancun")); + cancunMilestone = schedule.milestoneFor(CANCUN); } @Override @@ -61,31 +60,18 @@ public String getName() { @Override protected JsonRpcResponse createResponse( - final JsonRpcRequestContext request, - final PayloadIdentifier payloadId, - final BlockWithReceipts blockWithReceipts) { - final var result = blockResultFactory.payloadTransactionCompleteV2(blockWithReceipts); - logProposal( - payloadId, blockWithReceipts, Optional.of(Wei.fromHexString(result.getBlockValue()))); + final JsonRpcRequestContext request, final PayloadWrapper payload) { + final var result = blockResultFactory.payloadTransactionCompleteV2(payload); + logProposal(payload); return new JsonRpcSuccessResponse(request.getRequest().getId(), result); } @Override protected ValidationResult validateForkSupported(final long blockTimestamp) { - if (protocolSchedule.isPresent()) { - if (cancun.isPresent() && blockTimestamp >= cancun.get().milestone()) { - return ValidationResult.invalid( - RpcErrorType.UNSUPPORTED_FORK, - "Cancun configured to start at timestamp: " - + cancun.get().milestone() - + " please call engine_getPayloadV3"); - } else { - return ValidationResult.valid(); - } - } else { - return ValidationResult.invalid( - RpcErrorType.UNSUPPORTED_FORK, - "Configuration error, no schedule for Cancun fork set, not sure when to stop honoring use of V2"); + if (cancunMilestone.isPresent() && blockTimestamp >= cancunMilestone.get()) { + return ValidationResult.invalid(RpcErrorType.UNSUPPORTED_FORK); } + + return ValidationResult.valid(); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV3.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV3.java index 2e8ed52e4d0..f0b026ef450 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV3.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV3.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,8 +14,10 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; +import static org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId.CANCUN; + +import org.hyperledger.besu.consensus.merge.PayloadWrapper; import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator; -import org.hyperledger.besu.consensus.merge.blockcreation.PayloadIdentifier; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; @@ -23,9 +25,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory; -import org.hyperledger.besu.ethereum.core.BlockWithReceipts; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.mainnet.ScheduledProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import java.util.Optional; @@ -34,7 +34,7 @@ public class EngineGetPayloadV3 extends AbstractEngineGetPayload { - private final Optional cancun; + private final Optional cancunMilestone; public EngineGetPayloadV3( final Vertx vertx, @@ -50,7 +50,7 @@ public EngineGetPayloadV3( mergeMiningCoordinator, blockResultFactory, engineCallListener); - this.cancun = schedule.hardforkFor(s -> s.fork().name().equalsIgnoreCase("Cancun")); + cancunMilestone = schedule.milestoneFor(CANCUN); } @Override @@ -60,28 +60,14 @@ public String getName() { @Override protected JsonRpcResponse createResponse( - final JsonRpcRequestContext request, - final PayloadIdentifier payloadId, - final BlockWithReceipts blockWithReceipts) { + final JsonRpcRequestContext request, final PayloadWrapper payload) { return new JsonRpcSuccessResponse( - request.getRequest().getId(), - blockResultFactory.payloadTransactionCompleteV3(blockWithReceipts)); + request.getRequest().getId(), blockResultFactory.payloadTransactionCompleteV3(payload)); } @Override protected ValidationResult validateForkSupported(final long blockTimestamp) { - if (protocolSchedule.isPresent()) { - if (cancun.isPresent() && blockTimestamp >= cancun.get().milestone()) { - return ValidationResult.valid(); - } else { - return ValidationResult.invalid( - RpcErrorType.UNSUPPORTED_FORK, - "Cancun configured to start at timestamp: " + cancun.get().milestone()); - } - } else { - return ValidationResult.invalid( - RpcErrorType.UNSUPPORTED_FORK, "Configuration error, no schedule for Cancun fork set"); - } + return ForkSupportHelper.validateForkSupported(CANCUN, cancunMilestone, blockTimestamp); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV4.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV4.java new file mode 100644 index 00000000000..ba42aec2ed1 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV4.java @@ -0,0 +1,73 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; + +import static org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId.PRAGUE; + +import org.hyperledger.besu.consensus.merge.PayloadWrapper; +import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator; +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ValidationResult; + +import java.util.Optional; + +import io.vertx.core.Vertx; + +public class EngineGetPayloadV4 extends AbstractEngineGetPayload { + + private final Optional pragueMilestone; + + public EngineGetPayloadV4( + final Vertx vertx, + final ProtocolContext protocolContext, + final MergeMiningCoordinator mergeMiningCoordinator, + final BlockResultFactory blockResultFactory, + final EngineCallListener engineCallListener, + final ProtocolSchedule schedule) { + super( + vertx, + schedule, + protocolContext, + mergeMiningCoordinator, + blockResultFactory, + engineCallListener); + pragueMilestone = schedule.milestoneFor(PRAGUE); + } + + @Override + public String getName() { + return RpcMethod.ENGINE_GET_PAYLOAD_V4.getMethodName(); + } + + @Override + protected JsonRpcResponse createResponse( + final JsonRpcRequestContext request, final PayloadWrapper payload) { + + return new JsonRpcSuccessResponse( + request.getRequest().getId(), blockResultFactory.payloadTransactionCompleteV4(payload)); + } + + @Override + protected ValidationResult validateForkSupported(final long blockTimestamp) { + return ForkSupportHelper.validateForkSupported(PRAGUE, pragueMilestone, blockTimestamp); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV6110.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV6110.java deleted file mode 100644 index c9a9737a90a..00000000000 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV6110.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; - -import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator; -import org.hyperledger.besu.consensus.merge.blockcreation.PayloadIdentifier; -import org.hyperledger.besu.ethereum.ProtocolContext; -import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory; -import org.hyperledger.besu.ethereum.core.BlockWithReceipts; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.mainnet.ScheduledProtocolSpec; -import org.hyperledger.besu.ethereum.mainnet.ValidationResult; - -import java.util.Optional; - -import io.vertx.core.Vertx; - -public class EngineGetPayloadV6110 extends AbstractEngineGetPayload { - - private final Optional eip6110; - - public EngineGetPayloadV6110( - final Vertx vertx, - final ProtocolContext protocolContext, - final MergeMiningCoordinator mergeMiningCoordinator, - final BlockResultFactory blockResultFactory, - final EngineCallListener engineCallListener, - final ProtocolSchedule schedule) { - super( - vertx, - schedule, - protocolContext, - mergeMiningCoordinator, - blockResultFactory, - engineCallListener); - this.eip6110 = schedule.hardforkFor(s -> s.fork().name().equalsIgnoreCase("ExperimentalEips")); - } - - @Override - public String getName() { - return RpcMethod.ENGINE_GET_PAYLOAD_V6110.getMethodName(); - } - - @Override - protected JsonRpcResponse createResponse( - final JsonRpcRequestContext request, - final PayloadIdentifier payloadId, - final BlockWithReceipts blockWithReceipts) { - - return new JsonRpcSuccessResponse( - request.getRequest().getId(), - blockResultFactory.payloadTransactionCompleteV6110(blockWithReceipts)); - } - - @Override - protected ValidationResult validateForkSupported(final long blockTimestamp) { - if (protocolSchedule.isPresent()) { - if (eip6110.isPresent() && blockTimestamp >= eip6110.get().milestone()) { - return ValidationResult.valid(); - } else { - return ValidationResult.invalid( - RpcErrorType.UNSUPPORTED_FORK, - "EIP-6110 configured to start at timestamp: " + eip6110.get().milestone()); - } - } else { - return ValidationResult.invalid( - RpcErrorType.UNSUPPORTED_FORK, "Configuration error, no schedule for EIP-6110 fork set"); - } - } -} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV1.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV1.java index d7019543c02..4230457b6d0 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV1.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV1.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -50,11 +50,6 @@ public String getName() { return RpcMethod.ENGINE_NEW_PAYLOAD_V1.getMethodName(); } - @Override - protected boolean requireTerminalPoWBlockValidation() { - return true; - } - @Override protected EngineStatus getInvalidBlockHashStatus() { return INVALID_BLOCK_HASH; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV2.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV2.java index 65133718ada..acb177c3e03 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV2.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV2.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; +import static org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId.CANCUN; + import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator; import org.hyperledger.besu.datatypes.VersionedHash; import org.hyperledger.besu.ethereum.ProtocolContext; @@ -33,6 +35,7 @@ import io.vertx.core.Vertx; public class EngineNewPayloadV2 extends AbstractEngineNewPayload { + private final Optional cancunMilestone; public EngineNewPayloadV2( final Vertx vertx, @@ -42,6 +45,7 @@ public EngineNewPayloadV2( final EthPeers ethPeers, final EngineCallListener engineCallListener) { super(vertx, protocolSchedule, protocolContext, mergeCoordinator, ethPeers, engineCallListener); + cancunMilestone = protocolSchedule.milestoneFor(CANCUN); } @Override @@ -56,11 +60,11 @@ protected ValidationResult validateParameters( final Optional maybeBeaconBlockRootParam) { if (payloadParameter.getBlobGasUsed() != null) { return ValidationResult.invalid( - RpcErrorType.INVALID_PARAMS, "non-null BlobGasUsed pre-cancun"); + RpcErrorType.INVALID_BLOB_GAS_USED_PARAMS, "Missing blob gas used field"); } if (payloadParameter.getExcessBlobGas() != null) { return ValidationResult.invalid( - RpcErrorType.INVALID_PARAMS, "non-null ExcessBlobGas pre-cancun"); + RpcErrorType.INVALID_EXCESS_BLOB_GAS_PARAMS, "Missing excess blob gas field"); } return ValidationResult.valid(); } @@ -74,4 +78,13 @@ protected ValidationResult validateBlobs( final ProtocolSpec protocolSpec) { return ValidationResult.valid(); } + + @Override + protected ValidationResult validateForkSupported(final long blockTimestamp) { + if (cancunMilestone.isPresent() && blockTimestamp >= cancunMilestone.get()) { + return ValidationResult.invalid(RpcErrorType.UNSUPPORTED_FORK); + } + + return ValidationResult.valid(); + } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV3.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV3.java index dff7174b49d..7d13ecc02c4 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV3.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV3.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; +import static org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId.CANCUN; + import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; @@ -21,7 +23,6 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.eth.manager.EthPeers; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.mainnet.ScheduledProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import java.util.List; @@ -31,7 +32,7 @@ public class EngineNewPayloadV3 extends AbstractEngineNewPayload { - private final Optional cancun; + private final Optional cancunMilestone; public EngineNewPayloadV3( final Vertx vertx, @@ -42,7 +43,7 @@ public EngineNewPayloadV3( final EngineCallListener engineCallListener) { super( vertx, timestampSchedule, protocolContext, mergeCoordinator, ethPeers, engineCallListener); - this.cancun = timestampSchedule.hardforkFor(s -> s.fork().name().equalsIgnoreCase("Cancun")); + this.cancunMilestone = timestampSchedule.milestoneFor(CANCUN); } @Override @@ -55,14 +56,19 @@ protected ValidationResult validateParameters( final EnginePayloadParameter payloadParameter, final Optional> maybeVersionedHashParam, final Optional maybeBeaconBlockRootParam) { - if (payloadParameter.getBlobGasUsed() == null || payloadParameter.getExcessBlobGas() == null) { - return ValidationResult.invalid(RpcErrorType.INVALID_PARAMS, "Missing blob gas fields"); + if (payloadParameter.getBlobGasUsed() == null) { + return ValidationResult.invalid( + RpcErrorType.INVALID_BLOB_GAS_USED_PARAMS, "Missing blob gas used field"); + } else if (payloadParameter.getExcessBlobGas() == null) { + return ValidationResult.invalid( + RpcErrorType.INVALID_EXCESS_BLOB_GAS_PARAMS, "Missing excess blob gas field"); } else if (maybeVersionedHashParam == null || maybeVersionedHashParam.isEmpty()) { return ValidationResult.invalid( - RpcErrorType.INVALID_PARAMS, "Missing versioned hashes field"); + RpcErrorType.INVALID_VERSIONED_HASH_PARAMS, "Missing versioned hashes field"); } else if (maybeBeaconBlockRootParam.isEmpty()) { return ValidationResult.invalid( - RpcErrorType.INVALID_PARAMS, "Missing parent beacon block root field"); + RpcErrorType.INVALID_PARENT_BEACON_BLOCK_ROOT_PARAMS, + "Missing parent beacon block root field"); } else { return ValidationResult.valid(); } @@ -70,18 +76,6 @@ protected ValidationResult validateParameters( @Override protected ValidationResult validateForkSupported(final long blockTimestamp) { - if (protocolSchedule.isPresent()) { - if (cancun.isPresent() - && Long.compareUnsigned(blockTimestamp, cancun.get().milestone()) >= 0) { - return ValidationResult.valid(); - } else { - return ValidationResult.invalid( - RpcErrorType.UNSUPPORTED_FORK, - "Cancun configured to start at timestamp: " + cancun.get().milestone()); - } - } else { - return ValidationResult.invalid( - RpcErrorType.UNSUPPORTED_FORK, "Configuration error, no schedule for Cancun fork set"); - } + return ForkSupportHelper.validateForkSupported(CANCUN, cancunMilestone, blockTimestamp); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV4.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV4.java new file mode 100644 index 00000000000..904ec08e5c2 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV4.java @@ -0,0 +1,83 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; + +import static org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId.PRAGUE; + +import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator; +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EnginePayloadParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; +import org.hyperledger.besu.ethereum.eth.manager.EthPeers; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ValidationResult; + +import java.util.List; +import java.util.Optional; + +import io.vertx.core.Vertx; + +public class EngineNewPayloadV4 extends AbstractEngineNewPayload { + + private final Optional pragueMilestone; + + public EngineNewPayloadV4( + final Vertx vertx, + final ProtocolSchedule timestampSchedule, + final ProtocolContext protocolContext, + final MergeMiningCoordinator mergeCoordinator, + final EthPeers ethPeers, + final EngineCallListener engineCallListener) { + super( + vertx, timestampSchedule, protocolContext, mergeCoordinator, ethPeers, engineCallListener); + pragueMilestone = timestampSchedule.milestoneFor(PRAGUE); + } + + @Override + public String getName() { + return RpcMethod.ENGINE_NEW_PAYLOAD_V4.getMethodName(); + } + + @Override + protected ValidationResult validateParameters( + final EnginePayloadParameter payloadParameter, + final Optional> maybeVersionedHashParam, + final Optional maybeBeaconBlockRootParam) { + if (payloadParameter.getBlobGasUsed() == null) { + return ValidationResult.invalid( + RpcErrorType.INVALID_BLOB_GAS_USED_PARAMS, "Missing blob gas used field"); + } else if (payloadParameter.getExcessBlobGas() == null) { + return ValidationResult.invalid( + RpcErrorType.INVALID_EXCESS_BLOB_GAS_PARAMS, "Missing excess blob gas field"); + } else if (maybeVersionedHashParam == null || maybeVersionedHashParam.isEmpty()) { + return ValidationResult.invalid( + RpcErrorType.INVALID_VERSIONED_HASH_PARAMS, "Missing versioned hashes field"); + } else if (maybeBeaconBlockRootParam.isEmpty()) { + return ValidationResult.invalid( + RpcErrorType.INVALID_PARENT_BEACON_BLOCK_ROOT_PARAMS, + "Missing parent beacon block root field"); + } else if (payloadParameter.getDepositRequests() == null) { + return ValidationResult.invalid(RpcErrorType.INVALID_PARAMS, "Missing deposit field"); + } else { + return ValidationResult.valid(); + } + } + + @Override + protected ValidationResult validateForkSupported(final long blockTimestamp) { + return ForkSupportHelper.validateForkSupported(PRAGUE, pragueMilestone, blockTimestamp); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV6110.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV6110.java deleted file mode 100644 index 84f203c3a73..00000000000 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV6110.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; - -import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator; -import org.hyperledger.besu.ethereum.ProtocolContext; -import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EnginePayloadParameter; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; -import org.hyperledger.besu.ethereum.eth.manager.EthPeers; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.mainnet.ScheduledProtocolSpec; -import org.hyperledger.besu.ethereum.mainnet.ValidationResult; - -import java.util.List; -import java.util.Optional; - -import io.vertx.core.Vertx; - -public class EngineNewPayloadV6110 extends AbstractEngineNewPayload { - - private final Optional eip6110; - - public EngineNewPayloadV6110( - final Vertx vertx, - final ProtocolSchedule timestampSchedule, - final ProtocolContext protocolContext, - final MergeMiningCoordinator mergeCoordinator, - final EthPeers ethPeers, - final EngineCallListener engineCallListener) { - super( - vertx, timestampSchedule, protocolContext, mergeCoordinator, ethPeers, engineCallListener); - this.eip6110 = - timestampSchedule.hardforkFor(s -> s.fork().name().equalsIgnoreCase("ExperimentalEips")); - } - - @Override - public String getName() { - return RpcMethod.ENGINE_NEW_PAYLOAD_V6110.getMethodName(); - } - - @Override - protected ValidationResult validateParameters( - final EnginePayloadParameter payloadParameter, - final Optional> maybeVersionedHashParam, - final Optional maybeBeaconBlockRootParam) { - if (payloadParameter.getBlobGasUsed() == null || payloadParameter.getExcessBlobGas() == null) { - return ValidationResult.invalid(RpcErrorType.INVALID_PARAMS, "Missing blob gas fields"); - } else if (maybeVersionedHashParam == null || maybeVersionedHashParam.isEmpty()) { - return ValidationResult.invalid( - RpcErrorType.INVALID_PARAMS, "Missing versioned hashes field"); - } else if (maybeBeaconBlockRootParam.isEmpty()) { - return ValidationResult.invalid( - RpcErrorType.INVALID_PARAMS, "Missing parent beacon block root field"); - } else if (payloadParameter.getDeposits() == null) { - return ValidationResult.invalid(RpcErrorType.INVALID_PARAMS, "Missing deposit field"); - } else { - return ValidationResult.valid(); - } - } - - @Override - protected ValidationResult validateForkSupported(final long blockTimestamp) { - if (protocolSchedule.isPresent()) { - if (eip6110.isPresent() && blockTimestamp >= eip6110.get().milestone()) { - return ValidationResult.valid(); - } else { - return ValidationResult.invalid( - RpcErrorType.UNSUPPORTED_FORK, - "EIP-6110 configured to start at timestamp: " + eip6110.get().milestone()); - } - } else { - return ValidationResult.invalid( - RpcErrorType.UNSUPPORTED_FORK, "Configuration error, no schedule for EIP-6110 fork set"); - } - } -} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EnginePreparePayloadDebug.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EnginePreparePayloadDebug.java index 7f18250ec27..c4bca218031 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EnginePreparePayloadDebug.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EnginePreparePayloadDebug.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -23,8 +23,10 @@ import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EnginePreparePayloadParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; @@ -58,17 +60,25 @@ public String getName() { @Override public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) { - final EnginePreparePayloadParameter enginePreparePayloadParameter = - requestContext - .getOptionalParameter(0, EnginePreparePayloadParameter.class) - .orElse( - new EnginePreparePayloadParameter( - Optional.empty(), - Optional.empty(), - Optional.empty(), - Optional.empty(), - Optional.empty(), - Optional.empty())); + final EnginePreparePayloadParameter enginePreparePayloadParameter; + try { + enginePreparePayloadParameter = + requestContext + .getOptionalParameter(0, EnginePreparePayloadParameter.class) + .orElse( + new EnginePreparePayloadParameter( + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty())); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid engine prepare payload parameter (index 0)", + RpcErrorType.INVALID_ENGINE_PREPARE_PAYLOAD_PARAMS, + e); + } final var requestId = requestContext.getRequest().getId(); @@ -81,7 +91,10 @@ public JsonRpcResponse syncResponse(final JsonRpcRequestContext requestContext) payloadIdentifier -> new JsonRpcSuccessResponse( requestId, new EnginePreparePayloadResult(VALID, payloadIdentifier))) - .orElseGet(() -> new JsonRpcErrorResponse(requestId, RpcErrorType.INVALID_PARAMS)); + .orElseGet( + () -> + new JsonRpcErrorResponse( + requestId, RpcErrorType.INVALID_ENGINE_PREPARE_PAYLOAD_PARAMS)); } @VisibleForTesting diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineQosTimer.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineQosTimer.java index 6b9c46dddba..e5332b09b4b 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineQosTimer.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineQosTimer.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/ForkSupportHelper.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/ForkSupportHelper.java new file mode 100644 index 00000000000..57b8f549c95 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/ForkSupportHelper.java @@ -0,0 +1,42 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; + +import org.hyperledger.besu.datatypes.HardforkId; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; +import org.hyperledger.besu.ethereum.mainnet.ValidationResult; + +import java.util.Optional; + +public class ForkSupportHelper { + public static ValidationResult validateForkSupported( + final HardforkId hardforkId, + final Optional maybeForkMilestone, + final long blockTimestamp) { + if (maybeForkMilestone.isEmpty()) { + return ValidationResult.invalid( + RpcErrorType.UNSUPPORTED_FORK, + "Configuration error, no schedule for " + hardforkId.name() + " fork set"); + } + + if (blockTimestamp < maybeForkMilestone.get()) { + return ValidationResult.invalid( + RpcErrorType.UNSUPPORTED_FORK, + hardforkId.name() + " configured to start at timestamp: " + maybeForkMilestone.get()); + } + + return ValidationResult.valid(); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/RequestValidatorProvider.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/RequestValidatorProvider.java new file mode 100644 index 00000000000..c3d9c8376b2 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/RequestValidatorProvider.java @@ -0,0 +1,77 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; + +import org.hyperledger.besu.datatypes.RequestType; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.requests.ProhibitedRequestsValidator; +import org.hyperledger.besu.ethereum.mainnet.requests.RequestValidator; +import org.hyperledger.besu.ethereum.mainnet.requests.RequestsValidatorCoordinator; + +import java.util.Optional; + +public class RequestValidatorProvider { + + public static RequestValidator getDepositRequestValidator( + final ProtocolSchedule protocolSchedule, final long blockTimestamp, final long blockNumber) { + return getRequestValidator(protocolSchedule, blockTimestamp, blockNumber, RequestType.DEPOSIT); + } + + public static RequestValidator getWithdrawalRequestValidator( + final ProtocolSchedule protocolSchedule, final long blockTimestamp, final long blockNumber) { + return getRequestValidator( + protocolSchedule, blockTimestamp, blockNumber, RequestType.WITHDRAWAL); + } + + public static RequestValidator getConsolidationRequestValidator( + final ProtocolSchedule protocolSchedule, final long blockTimestamp, final long blockNumber) { + return getRequestValidator( + protocolSchedule, blockTimestamp, blockNumber, RequestType.CONSOLIDATION); + } + + private static RequestValidator getRequestValidator( + final ProtocolSchedule protocolSchedule, + final long blockTimestamp, + final long blockNumber, + final RequestType requestType) { + + RequestsValidatorCoordinator requestsValidatorCoordinator = + getRequestValidatorCoordinator(protocolSchedule, blockTimestamp, blockNumber); + return requestsValidatorCoordinator + .getRequestValidator(requestType) + .orElse(new ProhibitedRequestsValidator()); + } + + private static RequestsValidatorCoordinator getRequestValidatorCoordinator( + final ProtocolSchedule protocolSchedule, final long blockTimestamp, final long blockNumber) { + + final BlockHeader blockHeader = + BlockHeaderBuilder.createDefault() + .timestamp(blockTimestamp) + .number(blockNumber) + .buildBlockHeader(); + return getRequestValidatorCoordinator(protocolSchedule.getByBlockHeader(blockHeader)); + } + + private static RequestsValidatorCoordinator getRequestValidatorCoordinator( + final ProtocolSpec protocolSchedule) { + return Optional.ofNullable(protocolSchedule) + .map(ProtocolSpec::getRequestsValidatorCoordinator) + .orElseGet(RequestsValidatorCoordinator::empty); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/WithdrawalsValidatorProvider.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/WithdrawalsValidatorProvider.java index 1ab4b78204f..a1e83fbde9f 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/WithdrawalsValidatorProvider.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/WithdrawalsValidatorProvider.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; import org.hyperledger.besu.ethereum.core.BlockHeader; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerChangeTargetGasLimit.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerChangeTargetGasLimit.java index 1c530c14cf6..cd1b033dc47 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerChangeTargetGasLimit.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerChangeTargetGasLimit.java @@ -16,7 +16,9 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -43,11 +45,16 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { return new JsonRpcSuccessResponse(requestContext.getRequest().getId()); } catch (final IllegalArgumentException invalidJsonRpcParameters) { return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVALID_TARGET_GAS_LIMIT_PARAMS); } catch (final UnsupportedOperationException unsupportedOperationException) { return new JsonRpcErrorResponse( requestContext.getRequest().getId(), RpcErrorType.TARGET_GAS_LIMIT_MODIFICATION_UNSUPPORTED); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid target gas limit parameter (index 0)", + RpcErrorType.INVALID_TARGET_GAS_LIMIT_PARAMS, + e); } } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerGetExtraData.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerGetExtraData.java new file mode 100644 index 00000000000..b433ed0b6af --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerGetExtraData.java @@ -0,0 +1,41 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.miner; + +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.core.MiningParameters; + +public class MinerGetExtraData implements JsonRpcMethod { + private final MiningParameters miningParameters; + + public MinerGetExtraData(final MiningParameters miningParameters) { + this.miningParameters = miningParameters; + } + + @Override + public String getName() { + return RpcMethod.MINER_GET_EXTRA_DATA.getMethodName(); + } + + @Override + public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { + return new JsonRpcSuccessResponse( + requestContext.getRequest().getId(), miningParameters.getExtraData().toShortHexString()); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerGetMinGasPrice.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerGetMinGasPrice.java index bbd53d2a18b..07e8fdc9b63 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerGetMinGasPrice.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerGetMinGasPrice.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerGetMinPriorityFee.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerGetMinPriorityFee.java index daf134e3d0f..d99d13de2ce 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerGetMinPriorityFee.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerGetMinPriorityFee.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetCoinbase.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetCoinbase.java index eda085d77c4..f30db35e77e 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetCoinbase.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetCoinbase.java @@ -17,7 +17,9 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -46,6 +48,9 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { } catch (final UnsupportedOperationException ex) { return new JsonRpcErrorResponse( requestContext.getRequest().getId(), RpcErrorType.INVALID_REQUEST); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid address parameter (index 0)", RpcErrorType.INVALID_ADDRESS_PARAMS, e); } } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetExtraData.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetExtraData.java new file mode 100644 index 00000000000..51395e24f77 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetExtraData.java @@ -0,0 +1,70 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.miner; + +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; +import org.hyperledger.besu.ethereum.core.MiningParameters; + +import java.nio.charset.StandardCharsets; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MinerSetExtraData implements JsonRpcMethod { + private static final Logger LOG = LoggerFactory.getLogger(MinerSetExtraData.class); + + private final MiningParameters miningParameters; + + public MinerSetExtraData(final MiningParameters miningParameters) { + this.miningParameters = miningParameters; + } + + @Override + public String getName() { + return RpcMethod.MINER_SET_EXTRA_DATA.getMethodName(); + } + + @Override + public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { + try { + final String rawParam = requestContext.getRequiredParameter(0, String.class); + Bytes32.fromHexStringLenient( + rawParam); // done for validation, we want a hex string and max 32 bytes + final var extraData = Bytes.fromHexStringLenient(rawParam); + miningParameters.setExtraData(extraData); + LOG.atDebug() + .setMessage("set extra data, raw=[{}] parsed=[{}], UTF-8=[{}]") + .addArgument(rawParam) + .addArgument(extraData::toHexString) + .addArgument(() -> new String(extraData.toArray(), StandardCharsets.UTF_8)) + .log(); + return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), true); + } catch (IllegalArgumentException | JsonRpcParameterException e) { + return new JsonRpcErrorResponse( + requestContext.getRequest().getId(), + new JsonRpcError(RpcErrorType.INVALID_EXTRA_DATA_PARAMS, e.getMessage())); + } + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetMinGasPrice.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetMinGasPrice.java index 3bb6678d9f0..733ed3a8f3c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetMinGasPrice.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetMinGasPrice.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,7 +17,9 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; @@ -53,7 +55,13 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { } catch (final IllegalArgumentException invalidJsonRpcParameters) { return new JsonRpcErrorResponse( requestContext.getRequest().getId(), - new JsonRpcError(RpcErrorType.INVALID_PARAMS, invalidJsonRpcParameters.getMessage())); + new JsonRpcError( + RpcErrorType.INVALID_MIN_GAS_PRICE_PARAMS, invalidJsonRpcParameters.getMessage())); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid min gas price parameter (index 0)", + RpcErrorType.INVALID_MIN_GAS_PRICE_PARAMS, + e); } } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetMinPriorityFee.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetMinPriorityFee.java index 25a47c0abbf..b1bf4338f77 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetMinPriorityFee.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetMinPriorityFee.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -18,6 +18,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; @@ -51,10 +52,10 @@ public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { LOG.debug( "min priority fee per gas changed to {}", minPriorityFeePerGas.toHumanReadableString()); return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), true); - } catch (final IllegalArgumentException invalidJsonRpcParameters) { + } catch (final IllegalArgumentException | JsonRpcParameterException e) { return new JsonRpcErrorResponse( requestContext.getRequest().getId(), - new JsonRpcError(RpcErrorType.INVALID_PARAMS, invalidJsonRpcParameters.getMessage())); + new JsonRpcError(RpcErrorType.INVALID_MIN_PRIORITY_FEE_PARAMS, e.getMessage())); } } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermAddAccountsToAllowlist.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermAddAccountsToAllowlist.java index 21898c79ede..32459b3b4cb 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermAddAccountsToAllowlist.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermAddAccountsToAllowlist.java @@ -16,7 +16,9 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -44,7 +46,13 @@ public String getName() { @Override @SuppressWarnings("unchecked") public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final List accountsList = requestContext.getRequiredParameter(0, List.class); + final List accountsList; + try { + accountsList = requestContext.getRequiredParameter(0, List.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid accounts list parameter (index 0)", RpcErrorType.INVALID_ACCOUNT_PARAMS, e); + } if (allowlistController.isPresent()) { final AllowlistOperationResult addResult = diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermAddAccountsToWhitelist.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermAddAccountsToWhitelist.java deleted file mode 100644 index 37d530634a8..00000000000 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermAddAccountsToWhitelist.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning; - -import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; -import org.hyperledger.besu.ethereum.permissioning.AccountLocalConfigPermissioningController; - -import java.util.Optional; - -@Deprecated -public class PermAddAccountsToWhitelist extends PermAddAccountsToAllowlist { - - public PermAddAccountsToWhitelist( - final Optional allowlistController) { - super(allowlistController); - } - - @Override - public String getName() { - return RpcMethod.PERM_ADD_ACCOUNTS_TO_WHITELIST.getMethodName(); - } -} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermAddNodesToAllowlist.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermAddNodesToAllowlist.java index 9659f5a776e..5f8374a884e 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermAddNodesToAllowlist.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermAddNodesToAllowlist.java @@ -16,7 +16,9 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.StringListParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; @@ -46,8 +48,13 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final StringListParameter enodeListParam = - requestContext.getRequiredParameter(0, StringListParameter.class); + final StringListParameter enodeListParam; + try { + enodeListParam = requestContext.getRequiredParameter(0, StringListParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid enode list parameter (index 0)", RpcErrorType.INVALID_ENODE_PARAMS, e); + } try { if (nodeAllowlistPermissioningController.isPresent()) { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermAddNodesToWhitelist.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermAddNodesToWhitelist.java deleted file mode 100644 index c75a86e28ca..00000000000 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermAddNodesToWhitelist.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning; - -import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; -import org.hyperledger.besu.ethereum.permissioning.NodeLocalConfigPermissioningController; - -import java.util.Optional; - -@Deprecated -public class PermAddNodesToWhitelist extends PermAddNodesToAllowlist { - - public PermAddNodesToWhitelist( - final Optional nodeAllowlistPermissioningController) { - super(nodeAllowlistPermissioningController); - } - - @Override - public String getName() { - return RpcMethod.PERM_ADD_NODES_TO_WHITELIST.getMethodName(); - } -} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermGetAccountsWhitelist.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermGetAccountsWhitelist.java deleted file mode 100644 index 97281ae707a..00000000000 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermGetAccountsWhitelist.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning; - -import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; -import org.hyperledger.besu.ethereum.permissioning.AccountLocalConfigPermissioningController; - -import java.util.Optional; - -@Deprecated -public class PermGetAccountsWhitelist extends PermGetAccountsAllowlist { - - public PermGetAccountsWhitelist( - final Optional allowlistController) { - super(allowlistController); - } - - @Override - public String getName() { - return RpcMethod.PERM_GET_ACCOUNTS_WHITELIST.getMethodName(); - } -} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermGetNodesWhitelist.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermGetNodesWhitelist.java deleted file mode 100644 index 850872d3fd3..00000000000 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermGetNodesWhitelist.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning; - -import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; -import org.hyperledger.besu.ethereum.permissioning.NodeLocalConfigPermissioningController; - -import java.util.Optional; - -@Deprecated -public class PermGetNodesWhitelist extends PermGetNodesAllowlist { - - public PermGetNodesWhitelist( - final Optional nodeAllowlistPermissioningController) { - super(nodeAllowlistPermissioningController); - } - - @Override - public String getName() { - return RpcMethod.PERM_GET_NODES_WHITELIST.getMethodName(); - } -} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermRemoveAccountsFromAllowlist.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermRemoveAccountsFromAllowlist.java index aefd69141d5..ac7bffdf578 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermRemoveAccountsFromAllowlist.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermRemoveAccountsFromAllowlist.java @@ -16,7 +16,9 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -44,7 +46,14 @@ public String getName() { @Override @SuppressWarnings("unchecked") public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final List accountsList = requestContext.getRequiredParameter(0, List.class); + final List accountsList; + try { + accountsList = requestContext.getRequiredParameter(0, List.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid accounts list parameter (index 0)", RpcErrorType.INVALID_ACCOUNT_PARAMS, e); + } + if (allowlistController.isPresent()) { final AllowlistOperationResult removeResult = allowlistController.get().removeAccounts(accountsList); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermRemoveAccountsFromWhitelist.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermRemoveAccountsFromWhitelist.java deleted file mode 100644 index f72984f03b5..00000000000 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermRemoveAccountsFromWhitelist.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning; - -import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; -import org.hyperledger.besu.ethereum.permissioning.AccountLocalConfigPermissioningController; - -import java.util.Optional; - -@Deprecated -public class PermRemoveAccountsFromWhitelist extends PermRemoveAccountsFromAllowlist { - - public PermRemoveAccountsFromWhitelist( - final Optional allowlistController) { - super(allowlistController); - } - - @Override - public String getName() { - return RpcMethod.PERM_REMOVE_ACCOUNTS_FROM_WHITELIST.getMethodName(); - } -} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermRemoveNodesFromAllowlist.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermRemoveNodesFromAllowlist.java index 876376fab15..16e3bc234ac 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermRemoveNodesFromAllowlist.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermRemoveNodesFromAllowlist.java @@ -16,7 +16,9 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.StringListParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; @@ -46,8 +48,13 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final StringListParameter enodeListParam = - requestContext.getRequiredParameter(0, StringListParameter.class); + final StringListParameter enodeListParam; + try { + enodeListParam = requestContext.getRequiredParameter(0, StringListParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid enode list parameter (index 0)", RpcErrorType.INVALID_ENODE_PARAMS, e); + } try { if (nodeAllowlistPermissioningController.isPresent()) { try { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermRemoveNodesFromWhitelist.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermRemoveNodesFromWhitelist.java deleted file mode 100644 index a0b4de534cf..00000000000 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermRemoveNodesFromWhitelist.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning; - -import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; -import org.hyperledger.besu.ethereum.permissioning.NodeLocalConfigPermissioningController; - -import java.util.Optional; - -@Deprecated -public class PermRemoveNodesFromWhitelist extends PermRemoveNodesFromAllowlist { - - public PermRemoveNodesFromWhitelist( - final Optional nodeAllowlistPermissioningController) { - super(nodeAllowlistPermissioningController); - } - - @Override - public String getName() { - return RpcMethod.PERM_REMOVE_NODES_FROM_WHITELIST.getMethodName(); - } -} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameter.java index ceed60cbc95..c90222ff3d7 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameter.java @@ -18,6 +18,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; +import java.util.Locale; import java.util.Objects; import java.util.Optional; @@ -38,7 +39,7 @@ public class BlockParameter { @JsonCreator public BlockParameter(final String value) { - final String normalizedValue = value.toLowerCase(); + final String normalizedValue = value.toLowerCase(Locale.ROOT); switch (normalizedValue) { case "earliest": diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHash.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHash.java index 172a34d56f6..6cc537e6922 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHash.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterOrBlockHash.java @@ -18,6 +18,7 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.core.BlockHeader; +import java.util.Locale; import java.util.Objects; import java.util.Optional; import java.util.OptionalLong; @@ -44,7 +45,7 @@ public class BlockParameterOrBlockHash { @JsonCreator public BlockParameterOrBlockHash(final Object value) throws JsonProcessingException { if (value instanceof String) { - final String normalizedValue = String.valueOf(value).toLowerCase(); + final String normalizedValue = String.valueOf(value).toLowerCase(Locale.ROOT); if (Objects.equals(normalizedValue, "earliest")) { type = BlockParameterType.EARLIEST; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/ConsolidationRequestParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/ConsolidationRequestParameter.java new file mode 100644 index 00000000000..c6c600329d2 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/ConsolidationRequestParameter.java @@ -0,0 +1,102 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.ethereum.core.ConsolidationRequest; + +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ConsolidationRequestParameter { + + private final String sourceAddress; + private final String sourcePubkey; + private final String targetPubkey; + + @JsonCreator + public ConsolidationRequestParameter( + @JsonProperty("sourceAddress") final String sourceAddress, + @JsonProperty("sourcePubkey") final String sourcePubkey, + @JsonProperty("targetPubkey") final String targetPubkey) { + this.sourceAddress = sourceAddress; + this.sourcePubkey = sourcePubkey; + this.targetPubkey = targetPubkey; + } + + public static ConsolidationRequestParameter fromConsolidationRequest( + final ConsolidationRequest consolidationRequest) { + return new ConsolidationRequestParameter( + consolidationRequest.getSourceAddress().toHexString(), + consolidationRequest.getSourcePubkey().toHexString(), + consolidationRequest.getTargetPubkey().toHexString()); + } + + public ConsolidationRequest toConsolidationRequest() { + return new ConsolidationRequest( + Address.fromHexString(sourceAddress), + BLSPublicKey.fromHexString(sourcePubkey), + BLSPublicKey.fromHexString(targetPubkey)); + } + + @JsonGetter + public String getSourceAddress() { + return sourceAddress; + } + + @JsonGetter + public String getSourcePubkey() { + return sourcePubkey; + } + + @JsonGetter + public String getTargetPubkey() { + return targetPubkey; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final ConsolidationRequestParameter that = (ConsolidationRequestParameter) o; + return Objects.equals(sourceAddress, that.sourceAddress) + && Objects.equals(sourcePubkey, that.sourcePubkey) + && Objects.equals(targetPubkey, that.targetPubkey); + } + + @Override + public int hashCode() { + return Objects.hash(sourceAddress, sourcePubkey, targetPubkey); + } + + @Override + public String toString() { + return "ConsolidationRequestParameter{" + + "sourceAddress='" + + sourceAddress + + '\'' + + ", sourcePubkey='" + + sourcePubkey + + '\'' + + ", targetPubkey='" + + targetPubkey + + '\'' + + '}'; + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/DepositParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/DepositParameter.java deleted file mode 100644 index 288c998977d..00000000000 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/DepositParameter.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright contributors to Hyperledger Besu - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters; - -import org.hyperledger.besu.datatypes.BLSPublicKey; -import org.hyperledger.besu.datatypes.BLSSignature; -import org.hyperledger.besu.datatypes.GWei; -import org.hyperledger.besu.ethereum.core.Deposit; - -import java.util.Objects; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.annotation.JsonProperty; -import io.vertx.core.json.JsonObject; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt64; - -public class DepositParameter { - - private final String pubkey; - - private final String withdrawalCredentials; - private final String amount; - - private final String signature; - private final String index; - - @JsonCreator - public DepositParameter( - @JsonProperty("pubkey") final String pubkey, - @JsonProperty("withdrawalCredentials") final String withdrawalCredentials, - @JsonProperty("amount") final String amount, - @JsonProperty("signature") final String signature, - @JsonProperty("index") final String index) { - this.pubkey = pubkey; - this.withdrawalCredentials = withdrawalCredentials; - this.amount = amount; - this.signature = signature; - this.index = index; - } - - public static DepositParameter fromDeposit(final Deposit deposit) { - return new DepositParameter( - deposit.getPubkey().toString(), - deposit.getWithdrawalCredentials().toString(), - deposit.getAmount().toShortHexString(), - deposit.getSignature().toString(), - deposit.getIndex().toBytes().toQuantityHexString()); - } - - public Deposit toDeposit() { - return new Deposit( - BLSPublicKey.fromHexString(pubkey), - Bytes32.fromHexString(withdrawalCredentials), - GWei.fromHexString(amount), - BLSSignature.fromHexString(signature), - UInt64.fromHexString(index)); - } - - public JsonObject asJsonObject() { - return new JsonObject() - .put("pubkey", pubkey) - .put("withdrawalCredentials", withdrawalCredentials) - .put("amount", amount) - .put("signature", signature) - .put("index", index); - } - - @JsonGetter - public String getPubkey() { - return pubkey; - } - - @JsonGetter - public String getWithdrawalCredentials() { - return withdrawalCredentials; - } - - @JsonGetter - public String getAmount() { - return amount; - } - - @JsonGetter - public String getSignature() { - return signature; - } - - @JsonGetter - public String getIndex() { - return index; - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - final DepositParameter that = (DepositParameter) o; - return Objects.equals(pubkey, that.pubkey) - && Objects.equals(withdrawalCredentials, that.withdrawalCredentials) - && Objects.equals(amount, that.amount) - && Objects.equals(signature, that.signature) - && Objects.equals(index, that.index); - } - - @Override - public int hashCode() { - return Objects.hash(pubkey, withdrawalCredentials, amount, signature, index); - } - - @Override - public String toString() { - return "DepositParameter{" - + "pubKey='" - + pubkey - + '\'' - + ", withdrawalCredentials='" - + withdrawalCredentials - + '\'' - + ", amount='" - + amount - + '\'' - + ", signature='" - + signature - + '\'' - + ", index='" - + index - + '\'' - + '}'; - } -} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/DepositRequestParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/DepositRequestParameter.java new file mode 100644 index 00000000000..39d6476ca46 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/DepositRequestParameter.java @@ -0,0 +1,144 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters; + +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.datatypes.BLSSignature; +import org.hyperledger.besu.datatypes.GWei; +import org.hyperledger.besu.ethereum.core.DepositRequest; + +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import io.vertx.core.json.JsonObject; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt64; + +public class DepositRequestParameter { + + private final String pubkey; + + private final String withdrawalCredentials; + private final String amount; + + private final String signature; + private final String index; + + @JsonCreator + public DepositRequestParameter( + @JsonProperty("pubkey") final String pubkey, + @JsonProperty("withdrawalCredentials") final String withdrawalCredentials, + @JsonProperty("amount") final String amount, + @JsonProperty("signature") final String signature, + @JsonProperty("index") final String index) { + this.pubkey = pubkey; + this.withdrawalCredentials = withdrawalCredentials; + this.amount = amount; + this.signature = signature; + this.index = index; + } + + public static DepositRequestParameter fromDeposit(final DepositRequest depositRequest) { + return new DepositRequestParameter( + depositRequest.getPubkey().toString(), + depositRequest.getWithdrawalCredentials().toString(), + depositRequest.getAmount().toShortHexString(), + depositRequest.getSignature().toString(), + depositRequest.getIndex().toBytes().toQuantityHexString()); + } + + public DepositRequest toDeposit() { + return new DepositRequest( + BLSPublicKey.fromHexString(pubkey), + Bytes32.fromHexString(withdrawalCredentials), + GWei.fromHexString(amount), + BLSSignature.fromHexString(signature), + UInt64.fromHexString(index)); + } + + public JsonObject asJsonObject() { + return new JsonObject() + .put("pubkey", pubkey) + .put("withdrawalCredentials", withdrawalCredentials) + .put("amount", amount) + .put("signature", signature) + .put("index", index); + } + + @JsonGetter + public String getPubkey() { + return pubkey; + } + + @JsonGetter + public String getWithdrawalCredentials() { + return withdrawalCredentials; + } + + @JsonGetter + public String getAmount() { + return amount; + } + + @JsonGetter + public String getSignature() { + return signature; + } + + @JsonGetter + public String getIndex() { + return index; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final DepositRequestParameter that = (DepositRequestParameter) o; + return Objects.equals(pubkey, that.pubkey) + && Objects.equals(withdrawalCredentials, that.withdrawalCredentials) + && Objects.equals(amount, that.amount) + && Objects.equals(signature, that.signature) + && Objects.equals(index, that.index); + } + + @Override + public int hashCode() { + return Objects.hash(pubkey, withdrawalCredentials, amount, signature, index); + } + + @Override + public String toString() { + return "DepositRequestParameter{" + + "pubKey='" + + pubkey + + '\'' + + ", withdrawalCredentials='" + + withdrawalCredentials + + '\'' + + ", amount='" + + amount + + '\'' + + ", signature='" + + signature + + '\'' + + ", index='" + + index + + '\'' + + '}'; + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EngineExchangeTransitionConfigurationParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EngineExchangeTransitionConfigurationParameter.java index 3833fa80c4f..2f8e2be8e00 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EngineExchangeTransitionConfigurationParameter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EngineExchangeTransitionConfigurationParameter.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EngineForkchoiceUpdatedParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EngineForkchoiceUpdatedParameter.java index 7b3e50a07e1..dd3e829d56d 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EngineForkchoiceUpdatedParameter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EngineForkchoiceUpdatedParameter.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadAttributesParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadAttributesParameter.java index 655047573a0..0168531c4f8 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadAttributesParameter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadAttributesParameter.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadParameter.java index 355f7b218c7..fdcec45c6bd 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadParameter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadParameter.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -43,7 +43,9 @@ public class EnginePayloadParameter { private final List withdrawals; private final Long blobGasUsed; private final String excessBlobGas; - private final List deposits; + private final List depositRequests; + private final List withdrawalRequests; + private final List consolidationRequests; /** * Creates an instance of EnginePayloadParameter. @@ -65,7 +67,9 @@ public class EnginePayloadParameter { * @param withdrawals Array of Withdrawal * @param blobGasUsed QUANTITY, 64 Bits * @param excessBlobGas QUANTITY, 64 Bits - * @param deposits List of deposit parameters. + * @param depositRequests List of deposit parameters. + * @param withdrawalRequestParameters List of withdrawal requests parameters. + * @param consolidationRequests List of consolidation requests parameters. */ @JsonCreator public EnginePayloadParameter( @@ -86,7 +90,11 @@ public EnginePayloadParameter( @JsonProperty("withdrawals") final List withdrawals, @JsonProperty("blobGasUsed") final UnsignedLongParameter blobGasUsed, @JsonProperty("excessBlobGas") final String excessBlobGas, - @JsonProperty("depositReceipts") final List deposits) { + @JsonProperty("depositRequests") final List depositRequests, + @JsonProperty("withdrawalRequests") + final List withdrawalRequestParameters, + @JsonProperty("consolidationRequests") + final List consolidationRequests) { this.blockHash = blockHash; this.parentHash = parentHash; this.feeRecipient = feeRecipient; @@ -104,7 +112,9 @@ public EnginePayloadParameter( this.withdrawals = withdrawals; this.blobGasUsed = blobGasUsed == null ? null : blobGasUsed.getValue(); this.excessBlobGas = excessBlobGas; - this.deposits = deposits; + this.depositRequests = depositRequests; + this.withdrawalRequests = withdrawalRequestParameters; + this.consolidationRequests = consolidationRequests; } public Hash getBlockHash() { @@ -175,7 +185,15 @@ public String getExcessBlobGas() { return excessBlobGas; } - public List getDeposits() { - return deposits; + public List getDepositRequests() { + return depositRequests; + } + + public List getWithdrawalRequests() { + return withdrawalRequests; + } + + public List getConsolidationRequests() { + return consolidationRequests; } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePreparePayloadParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePreparePayloadParameter.java index 049ae4608d3..2ce52341028 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePreparePayloadParameter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePreparePayloadParameter.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/JsonCallParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/JsonCallParameter.java index 4141278c883..987b68c2e2f 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/JsonCallParameter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/JsonCallParameter.java @@ -16,8 +16,9 @@ import org.hyperledger.besu.datatypes.AccessListEntry; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.VersionedHash; import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.ethereum.core.json.HexLongDeserializer; +import org.hyperledger.besu.ethereum.core.json.GasDeserializer; import org.hyperledger.besu.ethereum.core.json.HexStringDeserializer; import org.hyperledger.besu.ethereum.transaction.CallParameter; @@ -25,64 +26,338 @@ import java.util.Optional; import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import org.apache.tuweni.bytes.Bytes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * This class is used to deserialize JSON parameters for a call to the JSON-RPC method eth_call. It + * extends {@link CallParameter} by adding support for JSON-specific fields such as strict mode, + * access lists, and blob versioned hashes. It also handles unknown JSON properties gracefully. + * + *

To build an instance of this class, use the {@link JsonCallParameterBuilder}: + * + *

{@code
+ * JsonCallParameter param = new JsonCallParameter.JsonCallParameterBuilder()
+ *     .withFrom(Address.fromHexString("0x..."))
+ *     .withTo(Address.fromHexString("0x..."))
+ *     .withGas(21000L)
+ *     .withGasPrice(Wei.of(1000000000L))
+ *     .withValue(Wei.ZERO)
+ *     .withInput(Bytes.fromHexString("0x..."))
+ *     .withStrict(true) // Optional
+ *     .withAccessList(accessList) // Optional
+ *     .withMaxFeePerGas(Wei.of(2)) // Optional
+ *     .withMaxPriorityFeePerGas(Wei.of(1)) // Optional
+ *     .withMaxFeePerBlobGas(Wei.of(3)) // Optional
+ *     .withBlobVersionedHashes(blobVersionedHashes) // Optional
+ *     .build();
+ * }
+ * + *

Note: Only one of 'data' or 'input' should be provided to the builder. If both are provided + * and their values differ, an {@link IllegalArgumentException} is thrown. + * + *

Unknown JSON properties encountered during deserialization are logged but do not affect the + * deserialization process, allowing for flexibility in JSON formats. + */ @JsonIgnoreProperties(ignoreUnknown = true) +@JsonDeserialize(builder = JsonCallParameter.JsonCallParameterBuilder.class) public class JsonCallParameter extends CallParameter { private static final Logger LOG = LoggerFactory.getLogger(JsonCallParameter.class); private final Optional strict; - @JsonCreator - public JsonCallParameter( - @JsonProperty("from") final Address from, - @JsonProperty("to") final Address to, - @JsonDeserialize(using = HexLongDeserializer.class) @JsonProperty("gas") final Long gasLimit, - @JsonProperty("gasPrice") final Wei gasPrice, - @JsonProperty("maxPriorityFeePerGas") final Wei maxPriorityFeePerGas, - @JsonProperty("maxFeePerGas") final Wei maxFeePerGas, - @JsonProperty("value") final Wei value, - @JsonDeserialize(using = HexStringDeserializer.class) @JsonProperty("data") final Bytes data, - @JsonDeserialize(using = HexStringDeserializer.class) @JsonProperty("input") - final Bytes input, - @JsonProperty("strict") final Boolean strict, - @JsonProperty("accessList") final List accessList) { + private JsonCallParameter( + final Address from, + final Address to, + final Long gasLimit, + final Wei gasPrice, + final Optional maxPriorityFeePerGas, + final Optional maxFeePerGas, + final Wei value, + final Bytes payload, + final Optional strict, + final Optional> accessList, + final Optional maxFeePerBlobGas, + final Optional> blobVersionedHashes) { super( from, to, - gasLimit != null ? gasLimit : -1L, + gasLimit, gasPrice, - Optional.ofNullable(maxPriorityFeePerGas), - Optional.ofNullable(maxFeePerGas), + maxPriorityFeePerGas, + maxFeePerGas, value, - Optional.ofNullable(input != null ? input : data).orElse(null), - Optional.ofNullable(accessList)); + payload, + accessList, + maxFeePerBlobGas, + blobVersionedHashes); - if (input != null && data != null) { - throw new IllegalArgumentException("Only one of 'input' or 'data' should be provided"); - } - - this.strict = Optional.ofNullable(strict); + this.strict = strict; } + /** + * Returns whether the call should be executed in strict mode. + * + * @return Optional strict mode flag + */ public Optional isMaybeStrict() { return strict; } - @JsonAnySetter - public void logUnknownProperties(final String key, final Object value) { - LOG.debug( - "unknown property - {} with value - {} and type - {} caught during serialization", - key, - value, - value.getClass()); + /** + * Builder for {@link JsonCallParameter}. Used by Jackson to deserialize {@code + * JsonCallParameter}. + */ + public static final class JsonCallParameterBuilder { + private Optional strict = Optional.empty(); + private Address from; + private Address to; + private long gas = -1; + private Optional maxPriorityFeePerGas = Optional.empty(); + private Optional maxFeePerGas = Optional.empty(); + private Optional maxFeePerBlobGas = Optional.empty(); + private Wei gasPrice; + private Wei value; + private Bytes data; + private Bytes input; + private Optional> accessList = Optional.empty(); + private Optional> blobVersionedHashes = Optional.empty(); + + /** Default constructor. */ + public JsonCallParameterBuilder() {} + + /** + * Sets the strict mode for the {@link JsonCallParameter}. If strict mode is enabled, the call + * will be executed with stricter validation rules. This is optional and defaults to not being + * in strict mode if not specified. + * + * @param strict the strict mode flag, can be {@code null} to indicate the absence of a strict + * mode preference + * @return the {@link JsonCallParameterBuilder} instance for chaining + */ + public JsonCallParameterBuilder withStrict(final Boolean strict) { + this.strict = Optional.ofNullable(strict); + return this; + } + + /** + * Sets the "from" address for the {@link JsonCallParameter}. This address represents the sender + * of the call. + * + * @param from the sender's address + * @return the {@link JsonCallParameterBuilder} instance for chaining + */ + public JsonCallParameterBuilder withFrom(final Address from) { + this.from = from; + return this; + } + + /** + * Sets the "to" address for the {@link JsonCallParameter}. This address represents the + * recipient of the call. It can be null for contract creation transactions. + * + * @param to the recipient's address + * @return the {@link JsonCallParameterBuilder} instance for chaining + */ + public JsonCallParameterBuilder withTo(final Address to) { + this.to = to; + return this; + } + + /** + * Sets the gas for the {@link JsonCallParameter} used for transaction execution. eth_call uses + * 0 gas but this parameter may be needed by some executions. By default, if not specified, the + * gas is set to -1, indicating that it is not set. + * + * @param gas the gas limit for the call, can be {@code null} to indicate that the gas limit is + * not set + * @return the {@link JsonCallParameterBuilder} instance for chaining + */ + @JsonDeserialize(using = GasDeserializer.class) + public JsonCallParameterBuilder withGas(final Long gas) { + this.gas = Optional.ofNullable(gas).orElse(-1L); + return this; + } + + /** + * Sets the maximum priority fee per gas for the {@link JsonCallParameter}. This fee is used to + * incentivize miners to include the transaction in a block. It is an optional parameter, and if + * not specified, it defaults to an empty {@link Optional}. + * + * @param maxPriorityFeePerGas the maximum priority fee per gas, can be {@code null} to indicate + * no preference + * @return the {@link JsonCallParameterBuilder} instance for chaining + */ + public JsonCallParameterBuilder withMaxPriorityFeePerGas(final Wei maxPriorityFeePerGas) { + this.maxPriorityFeePerGas = Optional.ofNullable(maxPriorityFeePerGas); + return this; + } + + /** + * Sets the maximum fee per gas for the {@link JsonCallParameter}. This fee represents the + * maximum amount of gas that the sender is willing to pay. It is an optional parameter, and if + * not specified, it defaults to an empty {@link Optional}. + * + * @param maxFeePerGas the maximum fee per gas, can be {@code null} to indicate no preference + * @return the {@link JsonCallParameterBuilder} instance for chaining + */ + public JsonCallParameterBuilder withMaxFeePerGas(final Wei maxFeePerGas) { + this.maxFeePerGas = Optional.ofNullable(maxFeePerGas); + return this; + } + + /** + * Sets the maximum fee per blob gas for the {@link JsonCallParameter}. This fee is specific to + * certain types of transactions and is an optional parameter. If not specified, it defaults to + * an empty {@link Optional}. + * + * @param maxFeePerBlobGas the maximum fee per blob gas, can be {@code null} to indicate no + * preference + * @return the {@link JsonCallParameterBuilder} instance for chaining + */ + public JsonCallParameterBuilder withMaxFeePerBlobGas(final Wei maxFeePerBlobGas) { + this.maxFeePerBlobGas = Optional.ofNullable(maxFeePerBlobGas); + return this; + } + + /** + * Sets the gas price for the {@link JsonCallParameter}. The gas price is used to calculate the + * transaction fee as the product of gas price and gas used. It is an optional parameter, and if + * not specified, it defaults to the network's current gas price. + * + * @param gasPrice the gas price, can be {@code null} to indicate that the network's current gas + * price should be used + * @return the {@link JsonCallParameterBuilder} instance for chaining + */ + public JsonCallParameterBuilder withGasPrice(final Wei gasPrice) { + this.gasPrice = gasPrice; + return this; + } + + /** + * Sets the value to be transferred with the call for the {@link JsonCallParameter}. This value + * is the amount of Wei to be transferred from the sender to the recipient. It is an optional + * parameter, and if not specified, it defaults to 0, indicating that no value is transferred. + * + * @param value the value to be transferred, can be {@code null} to indicate no value is + * transferred + * @return the {@link JsonCallParameterBuilder} instance for chaining + */ + public JsonCallParameterBuilder withValue(final Wei value) { + this.value = value; + return this; + } + + /** + * Sets the data for the {@link JsonCallParameter}. This data represents the payload of the + * call. Note: Only one of 'data' or 'input' should be provided. If both are provided and their + * values differ, an exception will be thrown during the build process. + * + * @param data the payload data + * @return the {@link JsonCallParameterBuilder} instance for chaining + */ + @JsonDeserialize(using = HexStringDeserializer.class) + public JsonCallParameterBuilder withData(final Bytes data) { + this.data = data; + return this; + } + + /** + * Sets the input for the {@link JsonCallParameter}. This input is an alternative representation + * of the payload for the call. Note: Only one of 'input' or 'data' should be provided. If both + * are provided and their values differ, an exception will be thrown during the build process. + * + * @param input the payload input + * @return the {@link JsonCallParameterBuilder} instance for chaining + */ + @JsonDeserialize(using = HexStringDeserializer.class) + public JsonCallParameterBuilder withInput(final Bytes input) { + this.input = input; + return this; + } + + /** + * Sets the access list for the {@link JsonCallParameter}. The access list is a list of + * addresses and storage keys that the transaction plans to access. This is an optional + * parameter, and if not specified, it defaults to an empty {@link Optional}. + * + * @param accessList the access list, can be {@code null} to indicate no access list is provided + * @return the {@link JsonCallParameterBuilder} instance for chaining + */ + public JsonCallParameterBuilder withAccessList(final List accessList) { + this.accessList = Optional.ofNullable(accessList); + return this; + } + + /** + * Sets the blob versioned hashes for the {@link JsonCallParameter}. This is a list of versioned + * hashes related to blob transactions, allowing for more complex transaction types. It is an + * optional parameter, and if not specified, it defaults to an empty {@link Optional}. + * + * @param blobVersionedHashes the list of versioned hashes, can be {@code null} to indicate no + * versioned hashes are provided + * @return the {@link JsonCallParameterBuilder} instance for chaining + */ + public JsonCallParameterBuilder withBlobVersionedHashes( + final List blobVersionedHashes) { + this.blobVersionedHashes = Optional.ofNullable(blobVersionedHashes); + return this; + } + + /** + * Handles unknown JSON properties during deserialization. This method is invoked when an + * unknown property is encountered in the JSON being deserialized into a {@link + * JsonCallParameter} object. It logs the unknown property's key, value, and the value's type if + * the value is not null. This allows for flexibility in JSON formats and aids in debugging + * issues related to unexpected properties. + * + * @param key the key of the unknown property + * @param value the value of the unknown property, which can be any type + */ + @JsonAnySetter + public void withUnknownProperties(final String key, final Object value) { + LOG.debug( + "unknown property - {} with value - {} and type - {} caught during serialization", + key, + value, + value != null ? value.getClass() : "NULL"); + } + + /** + * Builds a {@link JsonCallParameter} instance based on the provided parameters. This method + * also validates that only one of 'input' or 'data' is provided. If both are provided and their + * values differ, an {@link IllegalArgumentException} is thrown. This ensures the integrity of + * the payload data for the call. + * + * @return a new {@link JsonCallParameter} instance with the specified configuration + * @throws IllegalArgumentException if both 'input' and 'data' are provided and their values are + * not equal + */ + public JsonCallParameter build() { + if (input != null && data != null && !input.equals(data)) { + throw new IllegalArgumentException("Only one of 'input' or 'data' should be provided"); + } + + final Bytes payload = input != null ? input : data; + + return new JsonCallParameter( + from, + to, + gas, + gasPrice, + maxPriorityFeePerGas, + maxFeePerGas, + value, + payload, + strict, + accessList, + maxFeePerBlobGas, + blobVersionedHashes); + } } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/JsonRpcParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/JsonRpcParameter.java index 6e10b7a2316..22fb511ef4b 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/JsonRpcParameter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/JsonRpcParameter.java @@ -14,8 +14,6 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; - import java.util.List; import java.util.Optional; @@ -40,11 +38,12 @@ public class JsonRpcParameter { * @param The type of parameter. * @return Returns the parameter cast as T if available, otherwise throws exception. */ - public T required(final Object[] params, final int index, final Class paramClass) { + public T required(final Object[] params, final int index, final Class paramClass) + throws JsonRpcParameterException { return optional(params, index, paramClass) .orElseThrow( () -> - new InvalidJsonRpcParameters( + new JsonRpcParameterException( "Missing required json rpc parameter at index " + index)); } @@ -58,9 +57,8 @@ public T required(final Object[] params, final int index, final Class par * @param The type of parameter. * @return Returns the parameter cast as T if available. */ - @SuppressWarnings("unchecked") - public Optional optional( - final Object[] params, final int index, final Class paramClass) { + public Optional optional(final Object[] params, final int index, final Class paramClass) + throws JsonRpcParameterException { if (params == null || params.length <= index || params[index] == null) { return Optional.empty(); } @@ -69,14 +67,14 @@ public Optional optional( final Object rawParam = params[index]; if (paramClass.isAssignableFrom(rawParam.getClass())) { // If we're dealing with a simple type, just cast the value - param = (T) rawParam; + param = paramClass.cast(rawParam); } else { // Otherwise, serialize param back to json and then deserialize to the paramClass type try { final String json = mapper.writeValueAsString(rawParam); param = mapper.readValue(json, paramClass); } catch (final JsonProcessingException e) { - throw new InvalidJsonRpcParameters( + throw new JsonRpcParameterException( String.format( "Invalid json rpc parameter at index %d. Supplied value was: '%s' of type: '%s' - expected type: '%s'", index, rawParam, rawParam.getClass().getName(), paramClass.getName()), @@ -88,7 +86,8 @@ public Optional optional( } public Optional> optionalList( - final Object[] params, final int index, final Class listClass) { + final Object[] params, final int index, final Class listClass) + throws JsonRpcParameterException { if (params == null || params.length <= index || params[index] == null) { return Optional.empty(); } @@ -99,7 +98,7 @@ public Optional> optionalList( List returnedList = mapper.readValue(listJson, new TypeReference>() {}); return Optional.of(returnedList); } catch (JsonProcessingException e) { - throw new InvalidJsonRpcParameters( + throw new JsonRpcParameterException( String.format( "Invalid json rpc parameter at index %d. Supplied value was: '%s' of type: '%s' - expected type: '%s'", index, rawParam, rawParam.getClass().getName(), listClass.getName()), @@ -108,4 +107,14 @@ public Optional> optionalList( } return Optional.empty(); } + + public static class JsonRpcParameterException extends Exception { + public JsonRpcParameterException(final String message) { + super(message); + } + + public JsonRpcParameterException(final String message, final Throwable cause) { + super(message, cause); + } + } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/PendingTransactionsParams.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/PendingTransactionsParams.java index 546fcec97ca..dd2684e8271 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/PendingTransactionsParams.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/PendingTransactionsParams.java @@ -24,11 +24,13 @@ import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool.Predicate.EQ; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool.PendingTransactionFilter.Filter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.transaction.pool.Predicate; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; @@ -78,25 +80,31 @@ public List filters() throws IllegalArgumentException { private Optional getFilter(final String key, final Map map) { if (map != null) { if (map.size() > 1) { - throw new InvalidJsonRpcParameters("Only one operator per filter type allowed"); + throw new InvalidJsonRpcParameters( + "Only one operator per filter type allowed", RpcErrorType.INVALID_FILTER_PARAMS); } else if (!map.isEmpty()) { final Map.Entry foundEntry = map.entrySet().stream().findFirst().get(); final Predicate predicate = - Predicate.fromValue(foundEntry.getKey().toUpperCase()) + Predicate.fromValue(foundEntry.getKey().toUpperCase(Locale.ROOT)) .orElseThrow( () -> new InvalidJsonRpcParameters( - "Unknown field expected one of `eq`, `gt`, `lt`, `action`")); + "Unknown field expected one of `eq`, `gt`, `lt`, `action`", + RpcErrorType.INVALID_FILTER_PARAMS)); final Filter filter = new Filter(key, foundEntry.getValue(), predicate); if (key.equals(FROM_FIELD) && !predicate.equals(EQ)) { - throw new InvalidJsonRpcParameters("The `from` filter only supports the `eq` operator"); + throw new InvalidJsonRpcParameters( + "The `from` filter only supports the `eq` operator", + RpcErrorType.INVALID_FILTER_PARAMS); } else if (key.equals(TO_FIELD) && !predicate.equals(EQ) && !predicate.equals(ACTION)) { throw new InvalidJsonRpcParameters( - "The `to` filter only supports the `eq` or `action` operator"); + "The `to` filter only supports the `eq` or `action` operator", + RpcErrorType.INVALID_FILTER_PARAMS); } else if (!key.equals(TO_FIELD) && predicate.equals(ACTION)) { throw new InvalidJsonRpcParameters( - "The operator `action` is only supported by the `to` filter"); + "The operator `action` is only supported by the `to` filter", + RpcErrorType.INVALID_FILTER_PARAMS); } return Optional.of(filter); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TraceCallManyParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TraceCallManyParameter.java index 4b0b618f7a4..74c5d2e203d 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TraceCallManyParameter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TraceCallManyParameter.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TraceCallParameterTuple.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TraceCallParameterTuple.java index 360a554e5c0..b02c5dc185c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TraceCallParameterTuple.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TraceCallParameterTuple.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TraceTypeParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TraceTypeParameter.java index 80ba8bf6736..1b8e3a19d2d 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TraceTypeParameter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TraceTypeParameter.java @@ -17,6 +17,7 @@ import static java.util.Objects.isNull; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import java.util.List; import java.util.Objects; @@ -73,7 +74,9 @@ private void validateTraceTypes(final List traceTypesInput) .collect(Collectors.joining(", ")); if (!unsupportedTypes.isEmpty()) { - throw new InvalidJsonRpcParameters("Invalid trace types supplied: " + unsupportedTypes); + throw new InvalidJsonRpcParameters( + "Invalid trace types supplied: " + unsupportedTypes, + RpcErrorType.INVALID_TRACE_TYPE_PARAMS); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/WithdrawalParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/WithdrawalParameter.java index 80bce6e31e0..823de897422 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/WithdrawalParameter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/WithdrawalParameter.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/WithdrawalRequestParameter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/WithdrawalRequestParameter.java new file mode 100644 index 00000000000..3561da37a80 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/WithdrawalRequestParameter.java @@ -0,0 +1,103 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.datatypes.GWei; +import org.hyperledger.besu.ethereum.core.WithdrawalRequest; + +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class WithdrawalRequestParameter { + + private final String sourceAddress; + private final String validatorPubkey; + private final String amount; + + @JsonCreator + public WithdrawalRequestParameter( + @JsonProperty("sourceAddress") final String sourceAddress, + @JsonProperty("validatorPubkey") final String validatorPubkey, + @JsonProperty("amount") final String amount) { + this.sourceAddress = sourceAddress; + this.validatorPubkey = validatorPubkey; + this.amount = amount; + } + + public static WithdrawalRequestParameter fromWithdrawalRequest( + final WithdrawalRequest withdrawalRequest) { + return new WithdrawalRequestParameter( + withdrawalRequest.getSourceAddress().toHexString(), + withdrawalRequest.getValidatorPubkey().toHexString(), + withdrawalRequest.getAmount().toShortHexString()); + } + + public WithdrawalRequest toWithdrawalRequest() { + return new WithdrawalRequest( + Address.fromHexString(sourceAddress), + BLSPublicKey.fromHexString(validatorPubkey), + GWei.fromHexString(amount)); + } + + @JsonGetter + public String getSourceAddress() { + return sourceAddress; + } + + @JsonGetter + public String getValidatorPubkey() { + return validatorPubkey; + } + + @JsonGetter + public String getAmount() { + return amount; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final WithdrawalRequestParameter that = (WithdrawalRequestParameter) o; + return Objects.equals(sourceAddress, that.sourceAddress) + && Objects.equals(validatorPubkey, that.validatorPubkey) + && Objects.equals(amount, that.amount); + } + + @Override + public int hashCode() { + return Objects.hash(sourceAddress, validatorPubkey, amount); + } + + @Override + public String toString() { + return "WithdrawalRequestParameter{" + + "sourceAddress='" + + sourceAddress + + '\'' + + ", validatorPubkey='" + + validatorPubkey + + '\'' + + ", amount='" + + amount + + '\'' + + '}'; + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivGetFilterChanges.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivGetFilterChanges.java index 57b13cc423e..4ef7e0aa1c3 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivGetFilterChanges.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivGetFilterChanges.java @@ -16,8 +16,10 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.filter.FilterManager; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -51,8 +53,22 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final String privacyGroupId = requestContext.getRequiredParameter(0, String.class); - final String filterId = requestContext.getRequiredParameter(1, String.class); + final String privacyGroupId; + try { + privacyGroupId = requestContext.getRequiredParameter(0, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid privacy group ID parameter (index 0)", + RpcErrorType.INVALID_PRIVACY_GROUP_PARAMS, + e); + } + final String filterId; + try { + filterId = requestContext.getRequiredParameter(1, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid filter ID parameter (index 1)", RpcErrorType.INVALID_FILTER_PARAMS, e); + } if (privacyController instanceof MultiTenancyPrivacyController) { checkIfPrivacyGroupMatchesAuthenticatedPrivacyUserId(requestContext, privacyGroupId); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivGetFilterLogs.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivGetFilterLogs.java index c687d66a1ab..ea6a5894a33 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivGetFilterLogs.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivGetFilterLogs.java @@ -16,8 +16,10 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.filter.FilterManager; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -51,8 +53,22 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext request) { - final String privacyGroupId = request.getRequiredParameter(0, String.class); - final String filterId = request.getRequiredParameter(1, String.class); + final String privacyGroupId; + try { + privacyGroupId = request.getRequiredParameter(0, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid privacy group ID parameter (index 0)", + RpcErrorType.INVALID_PRIVACY_GROUP_PARAMS, + e); + } + final String filterId; + try { + filterId = request.getRequiredParameter(1, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid filter ID parameter (index 1)", RpcErrorType.INVALID_FILTER_PARAMS, e); + } if (privacyController instanceof MultiTenancyPrivacyController) { checkIfPrivacyGroupMatchesAuthenticatedPrivacyUserId(request, privacyGroupId); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivUninstallFilter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivUninstallFilter.java index 1683c9511c3..eb41a1dad4e 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivUninstallFilter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/PrivUninstallFilter.java @@ -16,10 +16,13 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.filter.FilterManager; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.privacy.MultiTenancyPrivacyController; import org.hyperledger.besu.ethereum.privacy.PrivacyController; @@ -45,8 +48,22 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext request) { - final String privacyGroupId = request.getRequiredParameter(0, String.class); - final String filterId = request.getRequiredParameter(1, String.class); + final String privacyGroupId; + try { + privacyGroupId = request.getRequiredParameter(0, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid privacy group ID parameter (index 0)", + RpcErrorType.INVALID_PRIVACY_GROUP_PARAMS, + e); + } + final String filterId; + try { + filterId = request.getRequiredParameter(1, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid filter ID parameter (index 1)", RpcErrorType.INVALID_FILTER_PARAMS, e); + } if (privacyController instanceof MultiTenancyPrivacyController) { checkIfPrivacyGroupMatchesAuthenticatedEnclaveKey(request, privacyGroupId); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/AbstractEeaSendRawTransaction.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/AbstractEeaSendRawTransaction.java index 7ca3e943993..e96a08eb7d4 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/AbstractEeaSendRawTransaction.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/AbstractEeaSendRawTransaction.java @@ -23,7 +23,9 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.PrivacyIdProvider; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; @@ -73,7 +75,13 @@ public String getName() { public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { final Object id = requestContext.getRequest().getId(); final Optional user = requestContext.getUser(); - final String rawPrivateTransaction = requestContext.getRequiredParameter(0, String.class); + final String rawPrivateTransaction; + try { + rawPrivateTransaction = requestContext.getRequiredParameter(0, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid transaction parameter (index 0)", RpcErrorType.INVALID_TRANSACTION_PARAMS, e); + } try { final PrivateTransaction privateTransaction = diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/AbstractPrivateTraceByHash.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/AbstractPrivateTraceByHash.java new file mode 100644 index 00000000000..01398868624 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/AbstractPrivateTraceByHash.java @@ -0,0 +1,167 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.PrivacyIdProvider; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor.PrivateBlockTrace; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor.PrivateBlockTracer; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor.PrivateTracer; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor.PrivateTransactionTrace; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.privacy.privateTracing.PrivateFlatTrace; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.privacy.privateTracing.PrivateTraceGenerator; +import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; +import org.hyperledger.besu.ethereum.api.query.PrivacyQueries; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.ethereum.debug.TraceOptions; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.privacy.ExecutedPrivateTransaction; +import org.hyperledger.besu.ethereum.privacy.MultiTenancyPrivacyController; +import org.hyperledger.besu.ethereum.privacy.PrivacyController; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateBlockMetadata; +import org.hyperledger.besu.ethereum.vm.DebugOperationTracer; + +import java.util.Collections; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Stream; + +public abstract class AbstractPrivateTraceByHash implements JsonRpcMethod { + + protected final Supplier blockTracerSupplier; + protected final BlockchainQueries blockchainQueries; + protected final PrivacyQueries privacyQueries; + protected final ProtocolSchedule protocolSchedule; + protected final PrivacyController privacyController; + protected final PrivacyParameters privacyParameters; + protected final PrivacyIdProvider privacyIdProvider; + + protected AbstractPrivateTraceByHash( + final Supplier blockTracerSupplier, + final BlockchainQueries blockchainQueries, + final PrivacyQueries privacyQueries, + final ProtocolSchedule protocolSchedule, + final PrivacyController privacyController, + final PrivacyParameters privacyParameters, + final PrivacyIdProvider privacyIdProvider) { + this.blockTracerSupplier = blockTracerSupplier; + this.blockchainQueries = blockchainQueries; + this.privacyQueries = privacyQueries; + this.protocolSchedule = protocolSchedule; + this.privacyController = privacyController; + this.privacyParameters = privacyParameters; + this.privacyIdProvider = privacyIdProvider; + } + + public Stream resultByTransactionHash( + final String privacyGroupId, + final Hash transactionHash, + final JsonRpcRequestContext requestContext) { + + final String enclaveKey = privacyIdProvider.getPrivacyUserId(requestContext.getUser()); + if (privacyController instanceof MultiTenancyPrivacyController) { + verifyPrivacyGroupMatchesAuthenticatedEnclaveKey( + requestContext, privacyGroupId, Optional.empty()); + } + + return privacyController + .findPrivateTransactionByPmtHash(transactionHash, enclaveKey) + .map(ExecutedPrivateTransaction::getBlockNumber) + .flatMap(blockNumber -> blockchainQueries.getBlockchain().getBlockHashByNumber(blockNumber)) + .map(blockHash -> getTraceBlock(blockHash, transactionHash, enclaveKey, privacyGroupId)) + .orElse(Stream.empty()); + } + + private Stream getTraceBlock( + final Hash blockHash, + final Hash transactionHash, + final String enclaveKey, + final String privacyGroupId) { + final Block block = blockchainQueries.getBlockchain().getBlockByHash(blockHash).orElse(null); + final PrivateBlockMetadata privateBlockMetadata = + privacyQueries.getPrivateBlockMetaData(privacyGroupId, blockHash).orElse(null); + + if (privateBlockMetadata == null || block == null) { + return Stream.empty(); + } + return PrivateTracer.processTracing( + blockchainQueries, + Optional.of(block.getHeader()), + privacyGroupId, + enclaveKey, + privacyParameters, + privacyController, + mutableWorldState -> { + final PrivateTransactionTrace privateTransactionTrace = + getTransactionTrace( + block, transactionHash, enclaveKey, privateBlockMetadata, privacyGroupId); + return Optional.ofNullable(getTraceStream(privateTransactionTrace, block)); + }) + .orElse(Stream.empty()); + } + + private PrivateTransactionTrace getTransactionTrace( + final Block block, + final Hash transactionHash, + final String enclaveKey, + final PrivateBlockMetadata privateBlockMetadata, + final String privacyGroupId) { + return PrivateTracer.processTracing( + blockchainQueries, + Optional.of(block.getHeader()), + privacyGroupId, + enclaveKey, + privacyParameters, + privacyController, + mutableWorldState -> + blockTracerSupplier + .get() + .trace( + mutableWorldState, + block, + new DebugOperationTracer(new TraceOptions(false, false, true), false), + enclaveKey, + privacyGroupId, + privateBlockMetadata) + .map(PrivateBlockTrace::getTransactionTraces) + .orElse(Collections.emptyList()) + .stream() + .filter( + trxTrace -> + trxTrace.getPrivateTransaction().getPmtHash().equals(transactionHash)) + .findFirst()) + .orElseThrow(); + } + + private Stream getTraceStream( + final PrivateTransactionTrace transactionTrace, final Block block) { + + return PrivateTraceGenerator.generateFromTransactionTraceAndBlock( + this.protocolSchedule, transactionTrace, block) + .map(PrivateFlatTrace.class::cast); + } + + private void verifyPrivacyGroupMatchesAuthenticatedEnclaveKey( + final JsonRpcRequestContext request, + final String privacyGroupId, + final Optional toBlock) { + final String privacyUserId = privacyIdProvider.getPrivacyUserId(request.getUser()); + privacyController.verifyPrivacyGroupContainsPrivacyUserId( + privacyGroupId, privacyUserId, toBlock); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCall.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCall.java index 31469530b09..5d9edc63b9d 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCall.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCall.java @@ -21,6 +21,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.AbstractBlockParameterMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonCallParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.PrivacyIdProvider; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; @@ -53,14 +54,27 @@ public String getName() { @Override protected BlockParameter blockParameter(final JsonRpcRequestContext request) { - return request.getRequiredParameter(2, BlockParameter.class); + try { + return request.getRequiredParameter(2, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 2)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } @Override protected Object resultByBlockNumber( final JsonRpcRequestContext request, final long blockNumber) { final JsonCallParameter callParams = validateAndGetCallParams(request); - final String privacyGroupId = request.getRequiredParameter(0, String.class); + final String privacyGroupId; + try { + privacyGroupId = request.getRequiredParameter(0, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid privacy group ID parameter (index 0)", + RpcErrorType.INVALID_PRIVACY_GROUP_PARAMS, + e); + } final String privacyUserId = privacyIdProvider.getPrivacyUserId(request.getUser()); @@ -99,9 +113,16 @@ private JsonRpcError errorResponse( } private JsonCallParameter validateAndGetCallParams(final JsonRpcRequestContext request) { - final JsonCallParameter callParams = request.getRequiredParameter(1, JsonCallParameter.class); + final JsonCallParameter callParams; + try { + callParams = request.getRequiredParameter(1, JsonCallParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid call parameters (index 1)", RpcErrorType.INVALID_CALL_PARAMS); + } if (callParams.getTo() == null) { - throw new InvalidJsonRpcParameters("Missing \"to\" field in call arguments"); + throw new InvalidJsonRpcParameters( + "Missing \"to\" field in call arguments", RpcErrorType.INVALID_CALL_PARAMS); } return callParams; } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCreatePrivacyGroup.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCreatePrivacyGroup.java index 3527c02ad5b..568125a2762 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCreatePrivacyGroup.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCreatePrivacyGroup.java @@ -18,12 +18,15 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcEnclaveErrorConverter; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.PrivacyIdProvider; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.parameters.CreatePrivacyGroupParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.privacy.PrivacyController; import org.slf4j.Logger; @@ -50,8 +53,15 @@ public String getName() { public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { LOG.trace("Executing {}", RpcMethod.PRIV_CREATE_PRIVACY_GROUP.getMethodName()); - final CreatePrivacyGroupParameter parameter = - requestContext.getRequiredParameter(0, CreatePrivacyGroupParameter.class); + final CreatePrivacyGroupParameter parameter; + try { + parameter = requestContext.getRequiredParameter(0, CreatePrivacyGroupParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid create privacy group parameter (index 0)", + RpcErrorType.INVALID_CREATE_PRIVACY_GROUP_PARAMS, + e); + } LOG.trace( "Creating a privacy group with name {} and description {}", diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDebugGetStateRoot.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDebugGetStateRoot.java index 6ccc9630f96..dc0fc90f661 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDebugGetStateRoot.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDebugGetStateRoot.java @@ -20,8 +20,10 @@ import org.hyperledger.besu.enclave.types.PrivacyGroup; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.AbstractBlockParameterMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.PrivacyIdProvider; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; @@ -60,13 +62,26 @@ public String getName() { @Override protected BlockParameter blockParameter(final JsonRpcRequestContext request) { - return request.getRequiredParameter(1, BlockParameter.class); + try { + return request.getRequiredParameter(1, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 1)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } @Override protected Object resultByBlockNumber( final JsonRpcRequestContext requestContext, final long blockNumber) { - final String privacyGroupId = requestContext.getRequiredParameter(0, String.class); + final String privacyGroupId; + try { + privacyGroupId = requestContext.getRequiredParameter(0, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid privacy group ID parameter (index 0)", + RpcErrorType.INVALID_PRIVACY_GROUP_PARAMS, + e); + } final String privacyUserId = privacyIdProvider.getPrivacyUserId(requestContext.getUser()); if (LOG.isTraceEnabled()) { LOG.trace("Executing {}", getName()); @@ -91,7 +106,7 @@ protected Object resultByBlockNumber( } } catch (final Exception e) { return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVALID_PRIVACY_GROUP_PARAMS); } if (privacyGroup.isEmpty()) { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDeletePrivacyGroup.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDeletePrivacyGroup.java index db0c11ea5af..61b7791b3c0 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDeletePrivacyGroup.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDeletePrivacyGroup.java @@ -18,11 +18,14 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.PrivacyIdProvider; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException; import org.hyperledger.besu.ethereum.privacy.PrivacyController; @@ -50,7 +53,15 @@ public String getName() { public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { LOG.trace("Executing {}", RpcMethod.PRIV_DELETE_PRIVACY_GROUP.getMethodName()); - final String privacyGroupId = requestContext.getRequiredParameter(0, String.class); + final String privacyGroupId; + try { + privacyGroupId = requestContext.getRequiredParameter(0, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid privacy group ID parameter (index 0)", + RpcErrorType.INVALID_PRIVACY_GROUP_PARAMS, + e); + } final String response; try { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDistributeRawTransaction.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDistributeRawTransaction.java index 62a087dade7..aac4df3d2c9 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDistributeRawTransaction.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDistributeRawTransaction.java @@ -23,7 +23,9 @@ import org.hyperledger.besu.enclave.types.PrivacyGroup; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.PrivacyIdProvider; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; @@ -70,7 +72,15 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { final Object id = requestContext.getRequest().getId(); - final String rawPrivateTransaction = requestContext.getRequiredParameter(0, String.class); + final String rawPrivateTransaction; + try { + rawPrivateTransaction = requestContext.getRequiredParameter(0, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid private transaction parameter (index 0)", + RpcErrorType.INVALID_TRANSACTION_PARAMS, + e); + } try { final PrivateTransaction privateTransaction = diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivFindPrivacyGroup.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivFindPrivacyGroup.java index c09e21dc2a2..7e823232237 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivFindPrivacyGroup.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivFindPrivacyGroup.java @@ -19,11 +19,14 @@ import org.hyperledger.besu.enclave.types.PrivacyGroup; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.PrivacyIdProvider; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException; import org.hyperledger.besu.ethereum.privacy.PrivacyController; @@ -55,7 +58,13 @@ public String getName() { public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { LOG.trace("Executing {}", RpcMethod.PRIV_FIND_PRIVACY_GROUP.getMethodName()); - final String[] addresses = requestContext.getRequiredParameter(0, String[].class); + final String[] addresses; + try { + addresses = requestContext.getRequiredParameter(0, String[].class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid address parameters (index 0)", RpcErrorType.INVALID_ADDRESS_PARAMS, e); + } LOG.trace("Finding a privacy group with members {}", Arrays.toString(addresses)); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetCode.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetCode.java index b7cafd8cabe..7042d357f12 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetCode.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetCode.java @@ -17,9 +17,12 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.AbstractBlockParameterMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.PrivacyIdProvider; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.privacy.PrivacyController; @@ -46,14 +49,33 @@ public String getName() { @Override protected BlockParameter blockParameter(final JsonRpcRequestContext request) { - return request.getRequiredParameter(2, BlockParameter.class); + try { + return request.getRequiredParameter(2, BlockParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 2)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } } @Override protected String resultByBlockNumber( final JsonRpcRequestContext request, final long blockNumber) { - final String privacyGroupId = request.getRequiredParameter(0, String.class); - final Address address = request.getRequiredParameter(1, Address.class); + final String privacyGroupId; + try { + privacyGroupId = request.getRequiredParameter(0, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid privacy group ID parameter (index 0)", + RpcErrorType.INVALID_PRIVACY_GROUP_PARAMS, + e); + } + final Address address; + try { + address = request.getRequiredParameter(1, Address.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid address parameter (index 1)", RpcErrorType.INVALID_ADDRESS_PARAMS, e); + } final String privacyUserId = privacyIdProvider.getPrivacyUserId(request.getUser()); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetEeaTransactionCount.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetEeaTransactionCount.java index 3ec2d87b6aa..9f231f41c06 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetEeaTransactionCount.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetEeaTransactionCount.java @@ -20,7 +20,9 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.PrivacyIdProvider; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; @@ -62,12 +64,30 @@ public String getName() { public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { if (requestContext.getRequest().getParamLength() != 3) { return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAM_COUNT); } - final Address address = requestContext.getRequiredParameter(0, Address.class); - final String privateFrom = requestContext.getRequiredParameter(1, String.class); - final String[] privateFor = requestContext.getRequiredParameter(2, String[].class); + final Address address; + try { + address = requestContext.getRequiredParameter(0, Address.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid address parameter (index 0)", RpcErrorType.INVALID_ADDRESS_PARAMS, e); + } + final String privateFrom; + try { + privateFrom = requestContext.getRequiredParameter(1, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid private from parameter (index 1)", RpcErrorType.INVALID_PRIVATE_FROM_PARAMS, e); + } + final String[] privateFor; + try { + privateFor = requestContext.getRequiredParameter(2, String[].class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid private for parameters (index 2)", RpcErrorType.INVALID_PRIVATE_FOR_PARAMS, e); + } final String privacyUserId = privacyIdProvider.getPrivacyUserId(requestContext.getUser()); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetLogs.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetLogs.java index 5e89386cda9..6947ea5e546 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetLogs.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetLogs.java @@ -17,8 +17,10 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.FilterParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.PrivacyIdProvider; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; @@ -61,12 +63,26 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final String privacyGroupId = requestContext.getRequiredParameter(0, String.class); - final FilterParameter filter = requestContext.getRequiredParameter(1, FilterParameter.class); + final String privacyGroupId; + try { + privacyGroupId = requestContext.getRequiredParameter(0, String.class); + } catch (Exception e) { + throw new InvalidJsonRpcParameters( + "Invalid privacy group ID parameter (index 0)", + RpcErrorType.INVALID_PRIVACY_GROUP_PARAMS, + e); + } + final FilterParameter filter; + try { + filter = requestContext.getRequiredParameter(1, FilterParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid filter parameter (index 1)", RpcErrorType.INVALID_FILTER_PARAMS, e); + } if (!filter.isValid()) { return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVALID_FILTER_PARAMS); } final List matchingLogs = diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetPrivateTransaction.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetPrivateTransaction.java index d260647c843..8779181599e 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetPrivateTransaction.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetPrivateTransaction.java @@ -19,11 +19,14 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcEnclaveErrorConverter; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.PrivacyIdProvider; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.privacy.PrivateTransactionGroupResult; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.privacy.PrivateTransactionLegacyResult; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.privacy.PrivateTransactionResult; @@ -57,7 +60,15 @@ public String getName() { public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { LOG.trace("Executing {}", RpcMethod.PRIV_GET_PRIVATE_TRANSACTION.getMethodName()); - final Hash hash = requestContext.getRequiredParameter(0, Hash.class); + final Hash hash; + try { + hash = requestContext.getRequiredParameter(0, Hash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid transaction hash parameter (index 0)", + RpcErrorType.INVALID_TRANSACTION_HASH_PARAMS, + e); + } final String enclaveKey = privacyIdProvider.getPrivacyUserId(requestContext.getUser()); final Optional maybePrivateTx; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionCount.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionCount.java index 31cac2b8b3d..d8757dc2fbd 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionCount.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionCount.java @@ -19,7 +19,9 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.PrivacyIdProvider; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; @@ -53,11 +55,25 @@ public String getName() { public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { if (requestContext.getRequest().getParamLength() != 2) { return new JsonRpcErrorResponse( - requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAM_COUNT); } - final Address address = requestContext.getRequiredParameter(0, Address.class); - final String privacyGroupId = requestContext.getRequiredParameter(1, String.class); + final Address address; + try { + address = requestContext.getRequiredParameter(0, Address.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid address parameter (index 0)", RpcErrorType.INVALID_ADDRESS_PARAMS, e); + } + final String privacyGroupId; + try { + privacyGroupId = requestContext.getRequiredParameter(1, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid privacy group ID parameter (index 1)", + RpcErrorType.INVALID_PRIVACY_GROUP_PARAMS, + e); + } try { final long nonce = diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionReceipt.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionReceipt.java index f9bc4241048..287793ddc01 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionReceipt.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetTransactionReceipt.java @@ -20,7 +20,9 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcEnclaveErrorConverter; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.PrivacyIdProvider; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -66,7 +68,15 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { LOG.trace("Executing {}", RpcMethod.PRIV_GET_TRANSACTION_RECEIPT.getMethodName()); - final Hash pmtTransactionHash = requestContext.getRequiredParameter(0, Hash.class); + final Hash pmtTransactionHash; + try { + pmtTransactionHash = requestContext.getRequiredParameter(0, Hash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid transaction hash parameter (index 0)", + RpcErrorType.INVALID_TRANSACTION_HASH_PARAMS, + e); + } final String enclaveKey = privacyIdProvider.getPrivacyUserId(requestContext.getUser()); final ExecutedPrivateTransaction privateTransaction; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivNewFilter.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivNewFilter.java index 830e7e7e934..49fc11f4b82 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivNewFilter.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivNewFilter.java @@ -16,9 +16,11 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.filter.FilterManager; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.FilterParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.PrivacyIdProvider; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; @@ -49,8 +51,22 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext request) { - final String privacyGroupId = request.getRequiredParameter(0, String.class); - final FilterParameter filter = request.getRequiredParameter(1, FilterParameter.class); + final String privacyGroupId; + try { + privacyGroupId = request.getRequiredParameter(0, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid privacy group ID parameter (index 0)", + RpcErrorType.INVALID_PRIVACY_GROUP_PARAMS, + e); + } + final FilterParameter filter; + try { + filter = request.getRequiredParameter(1, FilterParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid filter parameter (index 1)", RpcErrorType.INVALID_FILTER_PARAMS, e); + } final String privacyUserId = privacyIdProvider.getPrivacyUserId(request.getUser()); if (privacyController instanceof MultiTenancyPrivacyController) { @@ -60,7 +76,8 @@ public JsonRpcResponse response(final JsonRpcRequestContext request) { } if (!filter.isValid()) { - return new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + return new JsonRpcErrorResponse( + request.getRequest().getId(), RpcErrorType.INVALID_FILTER_PARAMS); } final String logFilterId = diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivTraceTransaction.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivTraceTransaction.java new file mode 100644 index 00000000000..07d663bef55 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivTraceTransaction.java @@ -0,0 +1,111 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.TraceTransaction; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.PrivacyIdProvider; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor.PrivateBlockTracer; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.privacy.privateTracing.PrivateFlatTrace; +import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; +import org.hyperledger.besu.ethereum.api.query.PrivacyQueries; +import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.privacy.PrivacyController; + +import java.util.function.Supplier; +import java.util.stream.Stream; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PrivTraceTransaction extends AbstractPrivateTraceByHash implements JsonRpcMethod { + private static final Logger LOG = LoggerFactory.getLogger(TraceTransaction.class); + + public PrivTraceTransaction( + final Supplier blockTracerSupplier, + final BlockchainQueries blockchainQueries, + final ProtocolSchedule protocolSchedule, + final PrivacyQueries privacyQueries, + final PrivacyController privacyController, + final PrivacyParameters privacyParameters, + final PrivacyIdProvider privacyIdProvider) { + super( + blockTracerSupplier, + blockchainQueries, + privacyQueries, + protocolSchedule, + privacyController, + privacyParameters, + privacyIdProvider); + } + + @Override + public String getName() { + return RpcMethod.PRIV_TRACE_TRANSACTION.getMethodName(); + } + + @Override + public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { + final String privacyGroupId; + try { + privacyGroupId = requestContext.getRequiredParameter(0, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid privacy group ID parameter (index 0)", + RpcErrorType.INVALID_PRIVACY_GROUP_PARAMS, + e); + } + final Hash transactionHash; + try { + transactionHash = requestContext.getRequiredParameter(1, Hash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid transaction hash parameter (index 1)", + RpcErrorType.INVALID_TRANSACTION_HASH_PARAMS, + e); + } + LOG.trace("Received RPC rpcName={} txHash={}", getName(), transactionHash); + + if (privacyGroupId.isEmpty() || transactionHash.isEmpty()) { + return new JsonRpcErrorResponse( + requestContext.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + } + + return new JsonRpcSuccessResponse( + requestContext.getRequest().getId(), + arrayNodeFromTraceStream( + resultByTransactionHash(privacyGroupId, transactionHash, requestContext))); + } + + protected JsonNode arrayNodeFromTraceStream(final Stream traceStream) { + final ObjectMapper mapper = new ObjectMapper(); + final ArrayNode resultArrayNode = mapper.createArrayNode(); + traceStream.forEachOrdered(resultArrayNode::addPOJO); + return resultArrayNode; + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivUtil.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivUtil.java index 08a3e3a04c5..8db4b73bdb4 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivUtil.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivUtil.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/privx/PrivxFindFlexiblePrivacyGroup.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/privx/PrivxFindFlexiblePrivacyGroup.java index 0275626addd..0a66d1aa73b 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/privx/PrivxFindFlexiblePrivacyGroup.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/privx/PrivxFindFlexiblePrivacyGroup.java @@ -19,11 +19,14 @@ import org.hyperledger.besu.enclave.types.PrivacyGroup; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.PrivacyIdProvider; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.privacy.MultiTenancyValidationException; import org.hyperledger.besu.ethereum.privacy.PrivacyController; @@ -54,7 +57,13 @@ public String getName() { public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { LOG.trace("Executing {}", RpcMethod.PRIVX_FIND_PRIVACY_GROUP.getMethodName()); - final String[] addresses = requestContext.getRequiredParameter(0, String[].class); + final String[] addresses; + try { + addresses = requestContext.getRequiredParameter(0, String[].class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid address parameters (index 0)", RpcErrorType.INVALID_ADDRESS_PARAMS, e); + } LOG.trace("Finding a privacy group with members {}", Arrays.toString(addresses)); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/privx/PrivxFindOnchainPrivacyGroup.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/privx/PrivxFindOnchainPrivacyGroup.java index 4df3daeafd1..e09c72464e2 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/privx/PrivxFindOnchainPrivacyGroup.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/privx/PrivxFindOnchainPrivacyGroup.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockReplay.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockReplay.java index 9444c9ee963..b1a9be021e9 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockReplay.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockReplay.java @@ -19,6 +19,7 @@ import org.hyperledger.besu.datatypes.BlobGas; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.Tracer.TraceableState; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.Block; @@ -29,8 +30,8 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; -import org.hyperledger.besu.ethereum.vm.BlockHashLookup; import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup; +import org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; import java.util.List; import java.util.Optional; @@ -39,9 +40,14 @@ public class BlockReplay { private final ProtocolSchedule protocolSchedule; private final Blockchain blockchain; + private final ProtocolContext protocolContext; - public BlockReplay(final ProtocolSchedule protocolSchedule, final Blockchain blockchain) { + public BlockReplay( + final ProtocolSchedule protocolSchedule, + final ProtocolContext protocolContext, + final Blockchain blockchain) { this.protocolSchedule = protocolSchedule; + this.protocolContext = protocolContext; this.blockchain = blockchain; } @@ -101,7 +107,6 @@ public Optional beforeTransactionInBlock( transaction, header, blockchain, transactionProcessor, blobGasPrice)); } else { transactionProcessor.processTransaction( - blockchain, mutableWorldState.updater(), header, transaction, @@ -128,7 +133,6 @@ public Optional afterTransactionInBlock( (transaction, blockHeader, blockchain, transactionProcessor, blobGasPrice) -> { final ProtocolSpec spec = protocolSchedule.getByBlockHeader(blockHeader); transactionProcessor.processTransaction( - blockchain, mutableWorldState.updater(), blockHeader, transaction, @@ -145,7 +149,7 @@ public Optional afterTransactionInBlock( public Optional performActionWithBlock(final Hash blockHash, final BlockAction action) { Optional maybeBlock = getBlock(blockHash); if (maybeBlock.isEmpty()) { - maybeBlock = getBadBlock(blockHash); + maybeBlock = protocolContext.getBadBlockManager().getBadBlock(blockHash); } return maybeBlock.flatMap( block -> performActionWithBlock(block.getHeader(), block.getBody(), action)); @@ -176,12 +180,6 @@ private Optional getBlock(final Hash blockHash) { return Optional.empty(); } - private Optional getBadBlock(final Hash blockHash) { - final ProtocolSpec protocolSpec = - protocolSchedule.getByBlockHeader(blockchain.getChainHeadHeader()); - return protocolSpec.getBadBlocksManager().getBadBlock(blockHash); - } - @FunctionalInterface public interface BlockAction { Optional perform( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockTracer.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockTracer.java index b6651093702..5ede2d4a7b2 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockTracer.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/BlockTracer.java @@ -64,7 +64,6 @@ private BlockReplay.TransactionAction prepareReplayAction( chainedUpdater = chainedUpdater.updater(); final TransactionProcessingResult result = transactionProcessor.processTransaction( - blockchain, chainedUpdater, header, transaction, diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/Tracer.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/Tracer.java index c6bbeceb741..d1ee0872ae5 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/Tracer.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/Tracer.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracer.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracer.java index 5b9cdfc2fcc..6524109ac6f 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracer.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracer.java @@ -187,7 +187,6 @@ private TransactionProcessingResult processTransaction( final OperationTracer tracer, final Wei blobGasPrice) { return transactionProcessor.processTransaction( - blockchain, worldUpdater, header, transaction, diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/privateProcessor/PrivateBlockReplay.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/privateProcessor/PrivateBlockReplay.java new file mode 100644 index 00000000000..de5c11940b5 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/privateProcessor/PrivateBlockReplay.java @@ -0,0 +1,110 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor; + +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockBody; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.privacy.ExecutedPrivateTransaction; +import org.hyperledger.besu.ethereum.privacy.PrivacyController; +import org.hyperledger.besu.ethereum.privacy.PrivateTransactionProcessor; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateBlockMetadata; + +import java.util.List; +import java.util.Optional; + +public class PrivateBlockReplay { + + private final ProtocolSchedule protocolSchedule; + private final Blockchain blockchain; + private final PrivacyController privacyController; + + public PrivateBlockReplay( + final ProtocolSchedule protocolSchedule, + final Blockchain blockchain, + final PrivacyController privacyController) { + this.protocolSchedule = protocolSchedule; + this.blockchain = blockchain; + this.privacyController = privacyController; + } + + public Optional block( + final Block block, + final PrivateBlockMetadata privateBlockMetadata, + final String enclaveKey, + final TransactionAction action) { + return performActionWithBlock( + block.getHeader(), + block.getBody(), + (body, header, blockchain, transactionProcessor, protocolSpec) -> { + final List transactionTraces = + privateBlockMetadata.getPrivateTransactionMetadataList().stream() + .map( + privateTransactionMetadata -> + privacyController + .findPrivateTransactionByPmtHash( + privateTransactionMetadata.getPrivateMarkerTransactionHash(), + enclaveKey) + .map( + executedPrivateTransaction -> + action.performAction( + executedPrivateTransaction, + header, + blockchain, + transactionProcessor)) + .orElse(null)) + .toList(); + + return Optional.of(new PrivateBlockTrace(transactionTraces)); + }); + } + + private Optional performActionWithBlock( + final BlockHeader header, final BlockBody body, final BlockAction action) { + if (header == null) { + return Optional.empty(); + } + if (body == null) { + return Optional.empty(); + } + final ProtocolSpec protocolSpec = protocolSchedule.getByBlockHeader(header); + final PrivateTransactionProcessor transactionProcessor = + protocolSpec.getPrivateTransactionProcessor(); + + return action.perform(body, header, blockchain, transactionProcessor, protocolSpec); + } + + @FunctionalInterface + public interface BlockAction { + Optional perform( + BlockBody body, + BlockHeader blockHeader, + Blockchain blockchain, + PrivateTransactionProcessor transactionProcessor, + ProtocolSpec protocolSpec); + } + + @FunctionalInterface + public interface TransactionAction { + T performAction( + ExecutedPrivateTransaction transaction, + BlockHeader blockHeader, + Blockchain blockchain, + PrivateTransactionProcessor transactionProcessor); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/privateProcessor/PrivateBlockTrace.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/privateProcessor/PrivateBlockTrace.java new file mode 100644 index 00000000000..4deebed2617 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/privateProcessor/PrivateBlockTrace.java @@ -0,0 +1,30 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor; + +import java.util.List; + +public class PrivateBlockTrace { + + private final List transactionTraces; + + public PrivateBlockTrace(final List transactionTraces) { + this.transactionTraces = transactionTraces; + } + + public List getTransactionTraces() { + return transactionTraces; + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/privateProcessor/PrivateBlockTracer.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/privateProcessor/PrivateBlockTracer.java new file mode 100644 index 00000000000..c58af8fc3fc --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/privateProcessor/PrivateBlockTracer.java @@ -0,0 +1,87 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor; + +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.debug.TraceFrame; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateBlockMetadata; +import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; +import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup; +import org.hyperledger.besu.ethereum.vm.DebugOperationTracer; +import org.hyperledger.besu.evm.worldstate.StackedUpdater; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.List; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +public class PrivateBlockTracer { + private final PrivateBlockReplay blockReplay; + // Either the initial block state or the state of the prior TX, including miner rewards. + private WorldUpdater chainedUpdater; + + public PrivateBlockTracer(final PrivateBlockReplay blockReplay) { + this.blockReplay = blockReplay; + } + + public Optional trace( + final PrivateTracer.TraceableState mutableWorldState, + final Block block, + final DebugOperationTracer tracer, + final String enclaveKey, + final String privacyGroupId, + final PrivateBlockMetadata privateBlockMetadata) { + return blockReplay.block( + block, + privateBlockMetadata, + enclaveKey, + prepareReplayAction(mutableWorldState, tracer, privacyGroupId)); + } + + private PrivateBlockReplay.TransactionAction prepareReplayAction( + final PrivateTracer.TraceableState mutableWorldState, + final DebugOperationTracer tracer, + final String privacyGroupId) { + return (transaction, header, blockchain, transactionProcessor) -> { + // if we have no prior updater, it must be the first TX, so use the block's initial state + if (chainedUpdater == null) { + chainedUpdater = mutableWorldState.updater(); + + } else if (chainedUpdater instanceof StackedUpdater stackedUpdater) { + stackedUpdater.markTransactionBoundary(); + } + // create an updater for just this tx + chainedUpdater = chainedUpdater.updater(); + WorldUpdater privateChainedUpdater = mutableWorldState.privateUpdater(); + final TransactionProcessingResult result = + transactionProcessor.processTransaction( + chainedUpdater, + privateChainedUpdater, + header, + transaction.getPmtHash(), + transaction, + header.getCoinbase(), + tracer, + new CachingBlockHashLookup(header, blockchain), + Bytes32.wrap(Bytes.fromBase64String(privacyGroupId))); + + final List traceFrames = tracer.copyTraceFrames(); + tracer.reset(); + return new PrivateTransactionTrace(transaction, result, traceFrames); + }; + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/privateProcessor/PrivateTracer.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/privateProcessor/PrivateTracer.java new file mode 100644 index 00000000000..c79feead167 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/privateProcessor/PrivateTracer.java @@ -0,0 +1,118 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.ethereum.privacy.PrivacyController; +import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; + +import org.apache.tuweni.bytes.Bytes32; + +public class PrivateTracer { + + public static Optional processTracing( + final BlockchainQueries blockchainQueries, + final Optional blockHeader, + final String privacyGroupId, + final String enclaveKey, + final PrivacyParameters privacyParameters, + final PrivacyController privacyController, + final Function> mapper) { + + return blockHeader.flatMap( + header -> { + final long blockNumber = header.getNumber(); + final Hash parentHash = header.getParentHash(); + + final MutableWorldState disposablePrivateState = + privacyParameters + .getPrivateWorldStateArchive() + .getMutable( + privacyController + .getStateRootByBlockNumber(privacyGroupId, enclaveKey, blockNumber) + .get(), + parentHash) + .get(); + + return blockchainQueries.getAndMapWorldState( + parentHash, + mutableWorldState -> + mapper.apply( + new PrivateTracer.TraceableState(mutableWorldState, disposablePrivateState))); + }); + } + + /** + * This class force the use of the processTracing method to do tracing. processTracing allows you + * to cleanly manage the worldstate, to close it etc + */ + public static class TraceableState implements MutableWorldState { + private final MutableWorldState mutableWorldState; + private final MutableWorldState disposableWorldState; + + private TraceableState( + final MutableWorldState mutableWorldState, final MutableWorldState disposableWorldState) { + this.mutableWorldState = mutableWorldState; + this.disposableWorldState = disposableWorldState; + } + + @Override + public WorldUpdater updater() { + return mutableWorldState.updater(); + } + + public WorldUpdater privateUpdater() { + return disposableWorldState.updater(); + } + + @Override + public Hash rootHash() { + return mutableWorldState.rootHash(); + } + + @Override + public Hash frontierRootHash() { + return mutableWorldState.rootHash(); + } + + @Override + public Stream streamAccounts(final Bytes32 startKeyHash, final int limit) { + return mutableWorldState.streamAccounts(startKeyHash, limit); + } + + @Override + public Account get(final Address address) { + return mutableWorldState.get(address); + } + + @Override + public void close() throws Exception { + mutableWorldState.close(); + } + + @Override + public void persist(final BlockHeader blockHeader) {} + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/privateProcessor/PrivateTransactionTrace.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/privateProcessor/PrivateTransactionTrace.java new file mode 100644 index 00000000000..45e41d068b6 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/privateProcessor/PrivateTransactionTrace.java @@ -0,0 +1,91 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor; + +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.debug.TraceFrame; +import org.hyperledger.besu.ethereum.privacy.ExecutedPrivateTransaction; +import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; + +import java.util.List; +import java.util.Optional; + +public class PrivateTransactionTrace { + + private final ExecutedPrivateTransaction privateTransaction; + private final TransactionProcessingResult result; + private final List traceFrames; + private final Optional block; + + public PrivateTransactionTrace(final Optional block) { + this.privateTransaction = null; + this.result = null; + this.traceFrames = null; + this.block = block; + } + + public PrivateTransactionTrace( + final ExecutedPrivateTransaction privateTransaction, + final TransactionProcessingResult result, + final List traceFrames) { + this.privateTransaction = privateTransaction; + this.result = result; + this.traceFrames = traceFrames; + this.block = Optional.empty(); + } + + public PrivateTransactionTrace( + final ExecutedPrivateTransaction privateTransaction, + final TransactionProcessingResult result, + final List traceFrames, + final Optional block) { + this.privateTransaction = privateTransaction; + this.result = result; + this.traceFrames = traceFrames; + this.block = block; + } + + public PrivateTransactionTrace( + final ExecutedPrivateTransaction privateTransaction, final Optional block) { + this.privateTransaction = privateTransaction; + this.result = null; + this.traceFrames = null; + this.block = block; + } + + public ExecutedPrivateTransaction getPrivateTransaction() { + return privateTransaction; + } + + public long getGas() { + return privateTransaction.getGasLimit() - result.getGasRemaining(); + } + + public long getGasLimit() { + return privateTransaction.getGasLimit(); + } + + public TransactionProcessingResult getResult() { + return result; + } + + public List getTraceFrames() { + return traceFrames; + } + + public Optional getBlock() { + return block; + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcError.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcError.java index 73c8a20519f..59bb92c4433 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcError.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcError.java @@ -14,6 +14,11 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.response; +import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcErrorConverter; +import org.hyperledger.besu.ethereum.mainnet.ValidationResult; +import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; +import org.hyperledger.besu.plugin.services.rpc.RpcMethodError; + import java.util.Objects; import com.fasterxml.jackson.annotation.JsonCreator; @@ -21,7 +26,6 @@ import com.fasterxml.jackson.annotation.JsonGetter; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import org.apache.tuweni.bytes.Bytes; @JsonInclude(value = JsonInclude.Include.NON_NULL) @JsonFormat(shape = JsonFormat.Shape.OBJECT) @@ -41,16 +45,11 @@ public JsonRpcError( this.data = data; } - public JsonRpcError(final RpcErrorType errorType, final String data) { + public JsonRpcError(final RpcMethodError errorType, final String data) { this(errorType.getCode(), errorType.getMessage(), data); - // For execution reverted errors decode the data (if present) - if (errorType == RpcErrorType.REVERT_ERROR && data != null) { - JsonRpcErrorResponse.decodeRevertReason(Bytes.fromHexString(data)) - .ifPresent( - (decodedReason) -> { - this.reason = decodedReason; - }); + if (data != null) { + errorType.decodeData(data).ifPresent(decodedData -> this.reason = decodedData); } } @@ -58,6 +57,16 @@ public JsonRpcError(final RpcErrorType errorType) { this(errorType, null); } + public static JsonRpcError from( + final ValidationResult validationResult) { + final var jsonRpcError = + new JsonRpcError( + JsonRpcErrorConverter.convertTransactionInvalidReason( + validationResult.getInvalidReason())); + jsonRpcError.reason = validationResult.getErrorMessage(); + return jsonRpcError; + } + @JsonGetter("code") public int getCode() { return code; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcErrorResponse.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcErrorResponse.java index 3dffff4956a..4cfd7a1df37 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcErrorResponse.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcErrorResponse.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.response; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; import java.util.Arrays; import java.util.Collections; @@ -72,8 +73,8 @@ public JsonRpcError getError() { @Override @JsonIgnore - public JsonRpcResponseType getType() { - return JsonRpcResponseType.ERROR; + public RpcResponseType getType() { + return RpcResponseType.ERROR; } @Override diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcNoResponse.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcNoResponse.java index b7391a867b8..92a63174ac9 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcNoResponse.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcNoResponse.java @@ -14,10 +14,12 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.response; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; + public class JsonRpcNoResponse implements JsonRpcResponse { @Override - public JsonRpcResponseType getType() { - return JsonRpcResponseType.NONE; + public RpcResponseType getType() { + return RpcResponseType.NONE; } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcResponse.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcResponse.java index 3b308ff9d7e..5fb82187b69 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcResponse.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcResponse.java @@ -14,14 +14,14 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.response; +import org.hyperledger.besu.plugin.services.rpc.RpcResponse; + import com.fasterxml.jackson.annotation.JsonGetter; -public interface JsonRpcResponse { +public interface JsonRpcResponse extends RpcResponse { @JsonGetter("jsonrpc") default String getVersion() { return "2.0"; } - - JsonRpcResponseType getType(); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcResponseType.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcResponseType.java deleted file mode 100644 index 9d5be9c4306..00000000000 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcResponseType.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.api.jsonrpc.internal.response; - -/** Various types of responses that the JSON-RPC component may produce. */ -public enum JsonRpcResponseType { - NONE, - SUCCESS, - ERROR, - UNAUTHORIZED -} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcSuccessResponse.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcSuccessResponse.java index 82b1b8835b7..e26e2cf972e 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcSuccessResponse.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcSuccessResponse.java @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.response; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; + import java.util.Objects; import com.fasterxml.jackson.annotation.JsonGetter; @@ -50,8 +52,8 @@ public Object getResult() { @Override @JsonIgnore - public JsonRpcResponseType getType() { - return JsonRpcResponseType.SUCCESS; + public RpcResponseType getType() { + return RpcResponseType.SUCCESS; } @Override diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcUnauthorizedResponse.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcUnauthorizedResponse.java index f59b49a30bd..cdf7661678b 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcUnauthorizedResponse.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/JsonRpcUnauthorizedResponse.java @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.response; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; + import java.util.Arrays; import java.util.Objects; @@ -50,8 +52,8 @@ public JsonRpcError getError() { @Override @JsonIgnore - public JsonRpcResponseType getType() { - return JsonRpcResponseType.UNAUTHORIZED; + public RpcResponseType getType() { + return RpcResponseType.UNAUTHORIZED; } @Override diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/RpcErrorType.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/RpcErrorType.java index f025b4177a1..875eab601ba 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/RpcErrorType.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/RpcErrorType.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,19 +14,115 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.response; -public enum RpcErrorType { +import org.hyperledger.besu.plugin.services.rpc.RpcMethodError; + +import java.util.Optional; +import java.util.function.Function; + +import org.apache.tuweni.bytes.Bytes; + +public enum RpcErrorType implements RpcMethodError { // Standard errors PARSE_ERROR(-32700, "Parse error"), INVALID_REQUEST(-32600, "Invalid Request"), METHOD_NOT_FOUND(-32601, "Method not found"), - INVALID_PARAMS(-32602, "Invalid params"), + + INVALID_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid params"), + INVALID_ACCOUNT_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid account params"), + INVALID_ADDRESS_HASH_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid address hash params"), + INVALID_ADDRESS_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid address params"), + INVALID_BLOB_COUNT( + INVALID_PARAMS_ERROR_CODE, + "Invalid blob count (blob transactions must have at least one blob)"), + INVALID_BLOB_GAS_USED_PARAMS( + INVALID_PARAMS_ERROR_CODE, "Invalid blob gas used param (missing or invalid)"), + INVALID_BLOCK_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid block, unable to parse RLP"), + INVALID_BLOCK_COUNT_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid block count params"), + INVALID_BLOCK_HASH_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid block hash params"), + INVALID_BLOCK_INDEX_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid block index params"), + INVALID_BLOCK_NUMBER_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid block number params"), + INVALID_CALL_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid call params"), + INVALID_CONSOLIDATION_REQUEST_PARAMS( + INVALID_PARAMS_ERROR_CODE, "Invalid consolidation request params"), + INVALID_CREATE_PRIVACY_GROUP_PARAMS( + INVALID_PARAMS_ERROR_CODE, "Invalid create privacy group params"), + INVALID_DATA_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid data params"), + INVALID_DATA_HASH_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid data hash params"), + INVALID_DEPOSIT_REQUEST_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid deposit request"), + INVALID_ENGINE_EXCHANGE_TRANSITION_CONFIGURATION_PARAMS( + INVALID_PARAMS_ERROR_CODE, "Invalid engine exchange transition configuration params"), + INVALID_ENGINE_FORKCHOICE_UPDATED_PARAMS( + INVALID_PARAMS_ERROR_CODE, "Invalid engine forkchoice updated params"), + INVALID_ENGINE_FORKCHOICE_UPDATED_PAYLOAD_ATTRIBUTES( + INVALID_PARAMS_ERROR_CODE, "Invalid engine payload attributes parameter"), + INVALID_ENGINE_NEW_PAYLOAD_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid engine payload parameter"), + INVALID_ENGINE_PREPARE_PAYLOAD_PARAMS( + INVALID_PARAMS_ERROR_CODE, "Invalid engine prepare payload parameter"), + INVALID_ENGINE_GET_BLOBS_V1_TOO_LARGE_REQUEST(-38004, "Too large request"), + INVALID_ENODE_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid enode params"), + INVALID_EXCESS_BLOB_GAS_PARAMS( + INVALID_PARAMS_ERROR_CODE, "Invalid excess blob gas params (missing or invalid)"), + INVALID_EXTRA_DATA_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid extra data params"), + INVALID_FILTER_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid filter params"), + INVALID_GAS_PRICE_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid gas price params"), + INVALID_HASH_RATE_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid hash rate params"), + INVALID_ID_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid ID params"), + INVALID_RETURN_COMPLETE_TRANSACTION_PARAMS( + INVALID_PARAMS_ERROR_CODE, "Invalid return complete transaction params"), + INVALID_LOG_FILTER_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid log filter params"), + INVALID_LOG_LEVEL_PARAMS( + INVALID_PARAMS_ERROR_CODE, "Invalid log level params (missing or incorrect)"), + INVALID_MAX_RESULTS_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid max results params"), + INVALID_METHOD_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid method params"), + INVALID_MIN_GAS_PRICE_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid min gas price params"), + INVALID_MIN_PRIORITY_FEE_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid min priority fee params"), + INVALID_MIX_HASH_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid mix hash params"), + INVALID_NONCE_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid nonce params"), + INVALID_PARENT_BEACON_BLOCK_ROOT_PARAMS( + INVALID_PARAMS_ERROR_CODE, "Invalid parent beacon block root (missing or incorrect)"), + INVALID_PARAM_COUNT(INVALID_PARAMS_ERROR_CODE, "Invalid number of params"), + INVALID_PAYLOAD_ID_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid payload id params"), + INVALID_PENDING_TRANSACTIONS_PARAMS( + INVALID_PARAMS_ERROR_CODE, "Invalid pending transactions params"), + INVAlID_PLUGIN_NAME_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid plug in name params"), + INVALID_POSITION_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid position params"), + INVALID_POW_HASH_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid pow hash params"), + INVALID_PRIVACY_GROUP_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid privacy group params"), + INVALID_PRIVATE_FROM_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid private from params"), + INVALID_PRIVATE_FOR_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid private for params"), + INVALID_PROPOSAL_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid proposal params"), + INVALID_REMOTE_CAPABILITIES_PARAMS( + INVALID_PARAMS_ERROR_CODE, "Invalid remote capabilities params"), + INVALID_REWARD_PERCENTILES_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid reward percentiles params"), + INVALID_SEALER_ID_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid sealer ID params"), + INVALID_STORAGE_KEYS_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid storage keys params"), + INVALID_SUBSCRIPTION_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid subscription params"), + INVALID_TARGET_GAS_LIMIT_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid target gas limit params"), + INVALID_TIMESTAMP_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid timestamp parameter"), + INVALID_TRACE_CALL_MANY_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid trace call many params"), + INVALID_TRACE_NUMBERS_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid trace numbers params"), + INVALID_TRACE_TYPE_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid trace type params"), + INVALID_TRANSACTION_PARAMS( + INVALID_PARAMS_ERROR_CODE, "Invalid transaction params (missing or incorrect)"), + INVALID_TRANSACTION_HASH_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid transaction hash params"), + INVALID_TRANSACTION_ID_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid transaction id params"), + INVALID_TRANSACTION_INDEX_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid transaction index params"), + INVALID_TRANSACTION_LIMIT_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid transaction limit params"), + INVALID_TRANSACTION_TRACE_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid transaction trace params"), + INVALID_VERSIONED_HASH_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid versioned hash params"), + INVALID_VERSIONED_HASHES_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid versioned hashes params"), + INVALID_VOTE_TYPE_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid vote type params"), + INVALID_WITHDRAWALS_PARAMS(INVALID_PARAMS_ERROR_CODE, "Invalid withdrawals"), + INTERNAL_ERROR(-32603, "Internal error"), TIMEOUT_ERROR(-32603, "Timeout expired"), METHOD_NOT_ENABLED(-32604, "Method not enabled"), // Resource unavailable error - TX_POOL_DISABLED(-32002, "Transaction pool not enabled"), + TX_POOL_DISABLED( + -32002, + "Transaction pool not enabled. (Either txpool explicitly disabled, or node not yet in sync)."), // eth_getBlockByNumber specific error message UNKNOWN_BLOCK(-39001, "Unknown block"), @@ -61,11 +157,16 @@ public enum RpcErrorType { CHAIN_HEAD_WORLD_STATE_NOT_AVAILABLE(-32008, "Initial sync is still in progress"), GAS_PRICE_TOO_LOW(-32009, "Gas price below configured minimum gas price"), GAS_PRICE_BELOW_CURRENT_BASE_FEE(-32009, "Gas price below current base fee"), + + BLOB_GAS_PRICE_BELOW_CURRENT_BLOB_BASE_FEE(-32009, "blob gas price below current blob base fee"), WRONG_CHAIN_ID(-32000, "Wrong chainId"), REPLAY_PROTECTED_SIGNATURES_NOT_SUPPORTED(-32000, "ChainId not supported"), REPLAY_PROTECTED_SIGNATURE_REQUIRED(-32000, "ChainId is required"), TX_FEECAP_EXCEEDED(-32000, "Transaction fee cap exceeded"), - REVERT_ERROR(-32000, "Execution reverted"), + REVERT_ERROR( + -32000, + "Execution reverted", + data -> JsonRpcErrorResponse.decodeRevertReason(Bytes.fromHexString(data))), TRANSACTION_NOT_FOUND(-32000, "Transaction not found"), MAX_PRIORITY_FEE_PER_GAS_EXCEEDS_MAX_FEE_PER_GAS( -32000, "Max priority fee per gas exceeds max fee per gas"), @@ -75,6 +176,7 @@ public enum RpcErrorType { -32000, "An invalid transaction with a lower nonce exists"), TOTAL_BLOB_GAS_TOO_HIGH(-32000, "Total blob gas too high"), PLUGIN_TX_VALIDATOR(-32000, "Plugin has marked the transaction as invalid"), + EXECUTION_HALTED(-32000, "Transaction processing could not be completed due to an exception"), // Execution engine failures UNKNOWN_PAYLOAD(-32001, "Payload does not exist / is not available"), @@ -216,20 +318,33 @@ public enum RpcErrorType { UNKNOWN(-32603, "Unknown internal error"), INVALID_BLOBS(-32603, "blobs failed kzg validation"); - private final int code; private final String message; + private final Function> dataDecoder; RpcErrorType(final int code, final String message) { + this(code, message, null); + } + + RpcErrorType( + final int code, final String message, final Function> dataDecoder) { this.code = code; this.message = message; + this.dataDecoder = dataDecoder; } + @Override public int getCode() { return code; } + @Override public String getMessage() { return message; } + + @Override + public Optional decodeData(final String data) { + return dataDecoder == null ? Optional.empty() : dataDecoder.apply(data); + } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlobAndProofV1.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlobAndProofV1.java new file mode 100644 index 00000000000..c8978e00c11 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlobAndProofV1.java @@ -0,0 +1,42 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results; + +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +/** + * The result of the eth_getBlobAndProofV1 JSON-RPC method contains an array of BlobAndProofV1. + * BlobAndProofV1 contains the blob data and the kzg proof for the blob. + */ +@JsonPropertyOrder({"blob", "proof"}) +public class BlobAndProofV1 { + + private final String blob; + + private final String proof; + + public BlobAndProofV1(final String blob, final String proof) { + this.blob = blob; + this.proof = proof; + } + + public String getProof() { + return proof; + } + + public String getBlob() { + return blob; + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlobsBundleV1.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlobsBundleV1.java index 3377aa63cce..c397c7aab38 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlobsBundleV1.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlobsBundleV1.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results; import org.hyperledger.besu.datatypes.Blob; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockReceiptsResult.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockReceiptsResult.java index 2a239bdd3cb..5fe19030bc2 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockReceiptsResult.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockReceiptsResult.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockResultFactory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockResultFactory.java index 9596151bdfe..b8111c00c76 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockResultFactory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlockResultFactory.java @@ -14,16 +14,18 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results; +import static org.hyperledger.besu.ethereum.mainnet.requests.RequestUtil.getConsolidationRequests; +import static org.hyperledger.besu.ethereum.mainnet.requests.RequestUtil.getDepositRequests; +import static org.hyperledger.besu.ethereum.mainnet.requests.RequestUtil.getWithdrawalRequests; + +import org.hyperledger.besu.consensus.merge.PayloadWrapper; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineGetPayloadBodiesResultV1.PayloadBody; import org.hyperledger.besu.ethereum.api.query.BlockWithMetadata; import org.hyperledger.besu.ethereum.api.query.TransactionWithMetadata; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.BlockValueCalculator; -import org.hyperledger.besu.ethereum.core.BlockWithReceipts; import org.hyperledger.besu.ethereum.core.encoding.EncodingContext; import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder; @@ -105,8 +107,8 @@ public EngineGetPayloadResultV1 payloadTransactionCompleteV1(final Block block) return new EngineGetPayloadResultV1(block.getHeader(), txs); } - public EngineGetPayloadResultV2 payloadTransactionCompleteV2( - final BlockWithReceipts blockWithReceipts) { + public EngineGetPayloadResultV2 payloadTransactionCompleteV2(final PayloadWrapper payload) { + final var blockWithReceipts = payload.blockWithReceipts(); final List txs = blockWithReceipts.getBlock().getBody().getTransactions().stream() .map( @@ -115,12 +117,11 @@ public EngineGetPayloadResultV2 payloadTransactionCompleteV2( .map(Bytes::toHexString) .collect(Collectors.toList()); - final Wei blockValue = new BlockValueCalculator().calculateBlockValue(blockWithReceipts); return new EngineGetPayloadResultV2( blockWithReceipts.getHeader(), txs, blockWithReceipts.getBlock().getBody().getWithdrawals(), - Quantity.create(blockValue)); + Quantity.create(payload.blockValue())); } public EngineGetPayloadBodiesResultV1 payloadBodiesCompleteV1( @@ -132,8 +133,8 @@ public EngineGetPayloadBodiesResultV1 payloadBodiesCompleteV1( return new EngineGetPayloadBodiesResultV1(payloadBodies); } - public EngineGetPayloadResultV3 payloadTransactionCompleteV3( - final BlockWithReceipts blockWithReceipts) { + public EngineGetPayloadResultV3 payloadTransactionCompleteV3(final PayloadWrapper payload) { + final var blockWithReceipts = payload.blockWithReceipts(); final List txs = blockWithReceipts.getBlock().getBody().getTransactions().stream() .map( @@ -142,20 +143,18 @@ public EngineGetPayloadResultV3 payloadTransactionCompleteV3( .map(Bytes::toHexString) .collect(Collectors.toList()); - final Wei blockValue = new BlockValueCalculator().calculateBlockValue(blockWithReceipts); - final BlobsBundleV1 blobsBundleV1 = new BlobsBundleV1(blockWithReceipts.getBlock().getBody().getTransactions()); return new EngineGetPayloadResultV3( blockWithReceipts.getHeader(), txs, blockWithReceipts.getBlock().getBody().getWithdrawals(), - Quantity.create(blockValue), + Quantity.create(payload.blockValue()), blobsBundleV1); } - public EngineGetPayloadResultV6110 payloadTransactionCompleteV6110( - final BlockWithReceipts blockWithReceipts) { + public EngineGetPayloadResultV4 payloadTransactionCompleteV4(final PayloadWrapper payload) { + final var blockWithReceipts = payload.blockWithReceipts(); final List txs = blockWithReceipts.getBlock().getBody().getTransactions().stream() .map( @@ -164,16 +163,16 @@ public EngineGetPayloadResultV6110 payloadTransactionCompleteV6110( .map(Bytes::toHexString) .collect(Collectors.toList()); - final Wei blockValue = new BlockValueCalculator().calculateBlockValue(blockWithReceipts); - final BlobsBundleV1 blobsBundleV1 = new BlobsBundleV1(blockWithReceipts.getBlock().getBody().getTransactions()); - return new EngineGetPayloadResultV6110( + return new EngineGetPayloadResultV4( blockWithReceipts.getHeader(), txs, blockWithReceipts.getBlock().getBody().getWithdrawals(), - blockWithReceipts.getBlock().getBody().getDeposits(), - Quantity.create(blockValue), + getDepositRequests(blockWithReceipts.getBlock().getBody().getRequests()), + getWithdrawalRequests(blockWithReceipts.getBlock().getBody().getRequests()), + getConsolidationRequests(blockWithReceipts.getBlock().getBody().getRequests()), + Quantity.create(payload.blockValue()), blobsBundleV1); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineExchangeTransitionConfigurationResult.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineExchangeTransitionConfigurationResult.java index 7f790268715..f25c7c8b2b3 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineExchangeTransitionConfigurationResult.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineExchangeTransitionConfigurationResult.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetClientVersionResultV1.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetClientVersionResultV1.java new file mode 100644 index 00000000000..8251de6dc50 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetClientVersionResultV1.java @@ -0,0 +1,52 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results; + +import com.fasterxml.jackson.annotation.JsonGetter; + +public class EngineGetClientVersionResultV1 { + private final String code; + private final String name; + private final String version; + private final String commit; + + public EngineGetClientVersionResultV1( + final String code, final String name, final String version, final String commit) { + this.code = code; + this.name = name; + this.version = version; + this.commit = commit; + } + + @JsonGetter(value = "code") + public String getCode() { + return code; + } + + @JsonGetter(value = "name") + public String getName() { + return name; + } + + @JsonGetter(value = "version") + public String getVersion() { + return version; + } + + @JsonGetter(value = "commit") + public String getCommit() { + return commit; + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadBodiesResultV1.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadBodiesResultV1.java index f4e9a12e27d..8e2afcd918f 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadBodiesResultV1.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadBodiesResultV1.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV1.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV1.java index 0b21d15ef62..2435283dd3c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV1.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV1.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV2.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV2.java index e3919517f3b..39251120c1a 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV2.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV2.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV3.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV3.java index 6d18cbed0bc..1f1b19d88a1 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV3.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV3.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV4.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV4.java new file mode 100644 index 00000000000..39772ff7dbf --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV4.java @@ -0,0 +1,276 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results; + +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.ConsolidationRequestParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.DepositRequestParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalRequestParameter; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.ConsolidationRequest; +import org.hyperledger.besu.ethereum.core.DepositRequest; +import org.hyperledger.besu.ethereum.core.Withdrawal; +import org.hyperledger.besu.ethereum.core.WithdrawalRequest; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import org.apache.tuweni.bytes.Bytes32; + +@JsonPropertyOrder({"executionPayload", "blockValue", "blobsBundle", "shouldOverrideBuilder"}) +public class EngineGetPayloadResultV4 { + protected final PayloadResult executionPayload; + private final String blockValue; + private final BlobsBundleV1 blobsBundle; + private final boolean shouldOverrideBuilder; + + public EngineGetPayloadResultV4( + final BlockHeader header, + final List transactions, + final Optional> withdrawals, + final Optional> depositRequests, + final Optional> withdrawalRequests, + final Optional> consolidationRequests, + final String blockValue, + final BlobsBundleV1 blobsBundle) { + this.executionPayload = + new PayloadResult( + header, + transactions, + withdrawals, + depositRequests, + withdrawalRequests, + consolidationRequests); + this.blockValue = blockValue; + this.blobsBundle = blobsBundle; + this.shouldOverrideBuilder = false; + } + + @JsonGetter(value = "executionPayload") + public PayloadResult getExecutionPayload() { + return executionPayload; + } + + @JsonGetter(value = "blockValue") + public String getBlockValue() { + return blockValue; + } + + @JsonGetter(value = "blobsBundle") + public BlobsBundleV1 getBlobsBundle() { + return blobsBundle; + } + + @JsonGetter(value = "shouldOverrideBuilder") + public boolean shouldOverrideBuilder() { + return shouldOverrideBuilder; + } + + public static class PayloadResult { + + protected final String blockHash; + private final String parentHash; + private final String feeRecipient; + private final String stateRoot; + private final String receiptsRoot; + private final String logsBloom; + private final String prevRandao; + private final String blockNumber; + private final String gasLimit; + private final String gasUsed; + private final String timestamp; + private final String extraData; + private final String baseFeePerGas; + private final String excessBlobGas; + private final String blobGasUsed; + private final String parentBeaconBlockRoot; + + protected final List transactions; + private final List withdrawals; + private final List depositRequests; + private final List withdrawalRequests; + private final List consolidationRequests; + + public PayloadResult( + final BlockHeader header, + final List transactions, + final Optional> withdrawals, + final Optional> depositRequests, + final Optional> withdrawalRequests, + final Optional> consolidationRequests) { + this.blockNumber = Quantity.create(header.getNumber()); + this.blockHash = header.getHash().toString(); + this.parentHash = header.getParentHash().toString(); + this.logsBloom = header.getLogsBloom().toString(); + this.stateRoot = header.getStateRoot().toString(); + this.receiptsRoot = header.getReceiptsRoot().toString(); + this.extraData = header.getExtraData().toString(); + this.baseFeePerGas = header.getBaseFee().map(Quantity::create).orElse(null); + this.gasLimit = Quantity.create(header.getGasLimit()); + this.gasUsed = Quantity.create(header.getGasUsed()); + this.timestamp = Quantity.create(header.getTimestamp()); + this.transactions = transactions; + this.feeRecipient = header.getCoinbase().toString(); + this.prevRandao = header.getPrevRandao().map(Bytes32::toHexString).orElse(null); + this.withdrawals = + withdrawals + .map( + ws -> + ws.stream() + .map(WithdrawalParameter::fromWithdrawal) + .collect(Collectors.toList())) + .orElse(null); + this.depositRequests = + depositRequests + .map( + ds -> + ds.stream() + .map(DepositRequestParameter::fromDeposit) + .collect(Collectors.toList())) + .orElse(null); + this.withdrawalRequests = + withdrawalRequests + .map( + wr -> + wr.stream() + .map(WithdrawalRequestParameter::fromWithdrawalRequest) + .collect(Collectors.toList())) + .orElse(null); + this.consolidationRequests = + consolidationRequests + .map( + wr -> + wr.stream() + .map(ConsolidationRequestParameter::fromConsolidationRequest) + .collect(Collectors.toList())) + .orElse(null); + this.blobGasUsed = header.getBlobGasUsed().map(Quantity::create).orElse(Quantity.HEX_ZERO); + this.excessBlobGas = + header.getExcessBlobGas().map(Quantity::create).orElse(Quantity.HEX_ZERO); + this.parentBeaconBlockRoot = + header.getParentBeaconBlockRoot().map(Bytes32::toHexString).orElse(null); + } + + @JsonGetter(value = "blockNumber") + public String getNumber() { + return blockNumber; + } + + @JsonGetter(value = "blockHash") + public String getHash() { + return blockHash; + } + + @JsonGetter(value = "parentHash") + public String getParentHash() { + return parentHash; + } + + @JsonGetter(value = "logsBloom") + public String getLogsBloom() { + return logsBloom; + } + + @JsonGetter(value = "prevRandao") + public String getPrevRandao() { + return prevRandao; + } + + @JsonGetter(value = "stateRoot") + public String getStateRoot() { + return stateRoot; + } + + @JsonGetter(value = "receiptsRoot") + public String getReceiptRoot() { + return receiptsRoot; + } + + @JsonGetter(value = "extraData") + public String getExtraData() { + return extraData; + } + + @JsonGetter(value = "baseFeePerGas") + public String getBaseFeePerGas() { + return baseFeePerGas; + } + + @JsonGetter(value = "gasLimit") + public String getGasLimit() { + return gasLimit; + } + + @JsonGetter(value = "gasUsed") + public String getGasUsed() { + return gasUsed; + } + + @JsonGetter(value = "timestamp") + public String getTimestamp() { + return timestamp; + } + + @JsonGetter(value = "transactions") + public List getTransactions() { + return transactions; + } + + @JsonGetter(value = "withdrawals") + public List getWithdrawals() { + return withdrawals; + } + + @JsonGetter(value = "depositRequests") + public List getDepositRequests() { + return depositRequests; + } + + @JsonGetter(value = "withdrawalRequests") + public List getWithdrawalRequests() { + return withdrawalRequests; + } + + @JsonGetter(value = "consolidationRequests") + public List getConsolidationRequests() { + return consolidationRequests; + } + + @JsonGetter(value = "feeRecipient") + @JsonInclude(JsonInclude.Include.NON_NULL) + public String getFeeRecipient() { + return feeRecipient; + } + + @JsonGetter(value = "excessBlobGas") + public String getExcessBlobGas() { + return excessBlobGas; + } + + @JsonGetter(value = "blobGasUsed") + public String getBlobGasUsed() { + return blobGasUsed; + } + + @JsonGetter(value = "parentBeaconBlockRoot") + public String getParentBeaconBlockRoot() { + return parentBeaconBlockRoot; + } + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV6110.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV6110.java deleted file mode 100644 index 4b47101fb64..00000000000 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineGetPayloadResultV6110.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results; - -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.DepositParameter; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalParameter; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.Deposit; -import org.hyperledger.besu.ethereum.core.Withdrawal; - -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import org.apache.tuweni.bytes.Bytes32; - -@JsonPropertyOrder({"executionPayload", "blockValue", "blobsBundle", "shouldOverrideBuilder"}) -public class EngineGetPayloadResultV6110 { - protected final PayloadResult executionPayload; - private final String blockValue; - private final BlobsBundleV1 blobsBundle; - private final boolean shouldOverrideBuilder; - - public EngineGetPayloadResultV6110( - final BlockHeader header, - final List transactions, - final Optional> withdrawals, - final Optional> deposits, - final String blockValue, - final BlobsBundleV1 blobsBundle) { - this.executionPayload = new PayloadResult(header, transactions, withdrawals, deposits); - this.blockValue = blockValue; - this.blobsBundle = blobsBundle; - this.shouldOverrideBuilder = false; - } - - @JsonGetter(value = "executionPayload") - public PayloadResult getExecutionPayload() { - return executionPayload; - } - - @JsonGetter(value = "blockValue") - public String getBlockValue() { - return blockValue; - } - - @JsonGetter(value = "blobsBundle") - public BlobsBundleV1 getBlobsBundle() { - return blobsBundle; - } - - @JsonGetter(value = "shouldOverrideBuilder") - public boolean shouldOverrideBuilder() { - return shouldOverrideBuilder; - } - - public static class PayloadResult { - - protected final String blockHash; - private final String parentHash; - private final String feeRecipient; - private final String stateRoot; - private final String receiptsRoot; - private final String logsBloom; - private final String prevRandao; - private final String blockNumber; - private final String gasLimit; - private final String gasUsed; - private final String timestamp; - private final String extraData; - private final String baseFeePerGas; - private final String excessBlobGas; - private final String blobGasUsed; - private final String parentBeaconBlockRoot; - - protected final List transactions; - private final List withdrawals; - private final List deposits; - - public PayloadResult( - final BlockHeader header, - final List transactions, - final Optional> withdrawals, - final Optional> deposits) { - this.blockNumber = Quantity.create(header.getNumber()); - this.blockHash = header.getHash().toString(); - this.parentHash = header.getParentHash().toString(); - this.logsBloom = header.getLogsBloom().toString(); - this.stateRoot = header.getStateRoot().toString(); - this.receiptsRoot = header.getReceiptsRoot().toString(); - this.extraData = header.getExtraData().toString(); - this.baseFeePerGas = header.getBaseFee().map(Quantity::create).orElse(null); - this.gasLimit = Quantity.create(header.getGasLimit()); - this.gasUsed = Quantity.create(header.getGasUsed()); - this.timestamp = Quantity.create(header.getTimestamp()); - this.transactions = transactions; - this.feeRecipient = header.getCoinbase().toString(); - this.prevRandao = header.getPrevRandao().map(Bytes32::toHexString).orElse(null); - this.withdrawals = - withdrawals - .map( - ws -> - ws.stream() - .map(WithdrawalParameter::fromWithdrawal) - .collect(Collectors.toList())) - .orElse(null); - this.deposits = - deposits - .map( - ds -> ds.stream().map(DepositParameter::fromDeposit).collect(Collectors.toList())) - .orElse(null); - this.blobGasUsed = header.getBlobGasUsed().map(Quantity::create).orElse(Quantity.HEX_ZERO); - this.excessBlobGas = - header.getExcessBlobGas().map(Quantity::create).orElse(Quantity.HEX_ZERO); - this.parentBeaconBlockRoot = - header.getParentBeaconBlockRoot().map(Bytes32::toHexString).orElse(null); - } - - @JsonGetter(value = "blockNumber") - public String getNumber() { - return blockNumber; - } - - @JsonGetter(value = "blockHash") - public String getHash() { - return blockHash; - } - - @JsonGetter(value = "parentHash") - public String getParentHash() { - return parentHash; - } - - @JsonGetter(value = "logsBloom") - public String getLogsBloom() { - return logsBloom; - } - - @JsonGetter(value = "prevRandao") - public String getPrevRandao() { - return prevRandao; - } - - @JsonGetter(value = "stateRoot") - public String getStateRoot() { - return stateRoot; - } - - @JsonGetter(value = "receiptsRoot") - public String getReceiptRoot() { - return receiptsRoot; - } - - @JsonGetter(value = "extraData") - public String getExtraData() { - return extraData; - } - - @JsonGetter(value = "baseFeePerGas") - public String getBaseFeePerGas() { - return baseFeePerGas; - } - - @JsonGetter(value = "gasLimit") - public String getGasLimit() { - return gasLimit; - } - - @JsonGetter(value = "gasUsed") - public String getGasUsed() { - return gasUsed; - } - - @JsonGetter(value = "timestamp") - public String getTimestamp() { - return timestamp; - } - - @JsonGetter(value = "transactions") - public List getTransactions() { - return transactions; - } - - @JsonGetter(value = "withdrawals") - public List getWithdrawals() { - return withdrawals; - } - - @JsonGetter(value = "depositReceipts") - public List getDeposits() { - return deposits; - } - - @JsonGetter(value = "feeRecipient") - @JsonInclude(JsonInclude.Include.NON_NULL) - public String getFeeRecipient() { - return feeRecipient; - } - - @JsonGetter(value = "excessBlobGas") - public String getExcessBlobGas() { - return excessBlobGas; - } - - @JsonGetter(value = "blobGasUsed") - public String getBlobGasUseds() { - return blobGasUsed; - } - - @JsonGetter(value = "parentBeaconBlockRoot") - public String getParentBeaconBlockRoot() { - return parentBeaconBlockRoot; - } - } -} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EnginePayloadStatusResult.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EnginePayloadStatusResult.java index 6ea417ca11e..a9b9a17018a 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EnginePayloadStatusResult.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EnginePayloadStatusResult.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -19,7 +19,9 @@ import java.util.Optional; +import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; @JsonPropertyOrder({"status", "latestValidHash", "validationError"}) @@ -28,10 +30,11 @@ public class EnginePayloadStatusResult { Optional latestValidHash; Optional validationError; + @JsonCreator public EnginePayloadStatusResult( - final EngineStatus status, - final Hash latestValidHash, - final Optional validationError) { + @JsonProperty("status") final EngineStatus status, + @JsonProperty("latestValidHash") final Hash latestValidHash, + @JsonProperty("errorMessage") final Optional validationError) { this.status = status; this.latestValidHash = Optional.ofNullable(latestValidHash); this.validationError = validationError; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EnginePreparePayloadResult.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EnginePreparePayloadResult.java index 650ad973875..510c8ff396e 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EnginePreparePayloadResult.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EnginePreparePayloadResult.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineUpdateForkchoiceResult.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineUpdateForkchoiceResult.java index f560d295094..16ab0077f2c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineUpdateForkchoiceResult.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/EngineUpdateForkchoiceResult.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/FeeHistory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/FeeHistory.java index 08b130c3f5c..0985479ea5f 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/FeeHistory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/FeeHistory.java @@ -33,8 +33,12 @@ public interface FeeHistory { List getBaseFeePerGas(); + List getBaseFeePerBlobGas(); + List getGasUsedRatio(); + List getBlobGasUsedRatio(); + Optional>> getReward(); @Value.Immutable @@ -47,9 +51,15 @@ interface FeeHistoryResult { @JsonProperty("baseFeePerGas") List getBaseFeePerGas(); + @JsonProperty("baseFeePerBlobGas") + List getBaseFeePerBlobGas(); + @JsonProperty("gasUsedRatio") List getGasUsedRatio(); + @JsonProperty("blobGasUsedRatio") + List getBlobGasUsedRatio(); + @Nullable @JsonProperty("reward") List> getReward(); @@ -60,7 +70,9 @@ static FeeHistoryResult from(final FeeHistory feeHistory) { feeHistory.getBaseFeePerGas().stream() .map(Quantity::create) .collect(toUnmodifiableList()), + feeHistory.getBaseFeePerBlobGas().stream().map(Quantity::create).toList(), feeHistory.getGasUsedRatio(), + feeHistory.getBlobGasUsedRatio(), feeHistory .getReward() .map( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/NetworkResult.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/NetworkResult.java index e5176c55b2f..2a48201dcc9 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/NetworkResult.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/NetworkResult.java @@ -24,10 +24,13 @@ public class NetworkResult { private final String localAddress; private final String remoteAddress; + private final boolean inbound; - public NetworkResult(final SocketAddress localAddress, final SocketAddress remoteAddress) { + public NetworkResult( + final SocketAddress localAddress, final SocketAddress remoteAddress, final boolean inbound) { this.localAddress = removeTrailingSlash(localAddress.toString()); this.remoteAddress = removeTrailingSlash(remoteAddress.toString()); + this.inbound = inbound; } @JsonGetter(value = "localAddress") @@ -40,6 +43,11 @@ public String getRemoteAddress() { return remoteAddress; } + @JsonGetter(value = "inbound") + public boolean isInbound() { + return inbound; + } + private String removeTrailingSlash(final String address) { if (address != null && address.startsWith("/")) { return address.substring(1); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/PeerResult.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/PeerResult.java index c420990d65b..1bc2283acc3 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/PeerResult.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/PeerResult.java @@ -45,7 +45,11 @@ static PeerResult fromEthPeer(final EthPeer peer) { .map(Capability::toString) .map(TextNode::new) .collect(Collectors.toList())) - .network(new NetworkResult(connection.getLocalAddress(), connection.getRemoteAddress())) + .network( + new NetworkResult( + connection.getLocalAddress(), + connection.getRemoteAddress(), + connection.inboundInitiated())) .port(Quantity.create(peerInfo.getPort())) .id(peerInfo.getNodeId().toString()) .protocols(Map.of(peer.getProtocolName(), ProtocolsResult.fromEthPeer(peer))) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/ProtocolsResult.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/ProtocolsResult.java index a412f4287b3..8a21424012d 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/ProtocolsResult.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/ProtocolsResult.java @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results; import org.hyperledger.besu.ethereum.eth.manager.ChainState.BestBlock; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TraceCallResult.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TraceCallResult.java index 699c3d4721a..298053d9645 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TraceCallResult.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TraceCallResult.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TraceReplayResult.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TraceReplayResult.java index 67256973c95..cd03d8bca4c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TraceReplayResult.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TraceReplayResult.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionCompleteResult.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionCompleteResult.java index 78c1069801a..e8262021fde 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionCompleteResult.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionCompleteResult.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results; import org.hyperledger.besu.datatypes.AccessListEntry; +import org.hyperledger.besu.datatypes.CodeDelegation; import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.VersionedHash; import org.hyperledger.besu.datatypes.Wei; @@ -30,6 +31,7 @@ @JsonPropertyOrder({ "accessList", + "authorizationList", "blockHash", "blockNumber", "chainId", @@ -91,6 +93,9 @@ public class TransactionCompleteResult implements TransactionResult { @JsonInclude(JsonInclude.Include.NON_NULL) private final List versionedHashes; + @JsonInclude(JsonInclude.Include.NON_NULL) + private final List authorizationList; + public TransactionCompleteResult(final TransactionWithMetadata tx) { final Transaction transaction = tx.getTransaction(); final TransactionType transactionType = transaction.getType(); @@ -125,14 +130,16 @@ public TransactionCompleteResult(final TransactionWithMetadata tx) { this.yParity = Quantity.create(transaction.getYParity()); this.v = (transactionType == TransactionType.ACCESS_LIST - || transactionType == TransactionType.EIP1559) - ? this.yParity + || transactionType == TransactionType.EIP1559) + || transactionType == TransactionType.DELEGATE_CODE + ? Quantity.create(transaction.getYParity()) : null; } this.value = Quantity.create(transaction.getValue()); this.r = Quantity.create(transaction.getR()); this.s = Quantity.create(transaction.getS()); this.versionedHashes = transaction.getVersionedHashes().orElse(null); + this.authorizationList = transaction.getCodeDelegationList().orElse(null); } @JsonGetter(value = "accessList") @@ -246,4 +253,9 @@ public String getS() { public List getVersionedHashes() { return versionedHashes; } + + @JsonGetter(value = "authorizationList") + public List getAuthorizationList() { + return authorizationList; + } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionPendingResult.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionPendingResult.java index 694284d2260..2cbf5dd2255 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionPendingResult.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/TransactionPendingResult.java @@ -116,7 +116,7 @@ public TransactionPendingResult(final Transaction transaction) { this.v = (transactionType == TransactionType.ACCESS_LIST || transactionType == TransactionType.EIP1559) - ? this.yParity + ? Quantity.create(transaction.getYParity()) : null; } this.value = Quantity.create(transaction.getValue()); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/privacy/privateTracing/PrivateFlatTrace.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/privacy/privateTracing/PrivateFlatTrace.java new file mode 100644 index 00000000000..805e1c5ca37 --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/privacy/privateTracing/PrivateFlatTrace.java @@ -0,0 +1,377 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.privacy.privateTracing; + +import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL; + +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor.PrivateTransactionTrace; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.Trace; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.flat.Action; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.flat.Result; +import org.hyperledger.besu.ethereum.debug.TraceFrame; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonPropertyOrder({ + "action", + "blockHash", + "blockNumber", + "result", + "error", + "revertReason", + "subtraces", + "traceAddress", + "transactionHash", + "transactionPosition", + "type" +}) +public class PrivateFlatTrace implements Trace { + + private final Action action; + private final Result result; + private final Long blockNumber; + private final String blockHash; + private final Integer transactionPosition; + private final String transactionHash; + private final Optional error; + private final String revertReason; + private final int subtraces; + private final List traceAddress; + private final String type; + + protected PrivateFlatTrace( + final Action.Builder actionBuilder, + final Result.Builder resultBuilder, + final int subtraces, + final List traceAddress, + final String type, + final Long blockNumber, + final String blockHash, + final Integer transactionPosition, + final String transactionHash, + final Optional error) { + this( + actionBuilder != null ? actionBuilder.build() : null, + resultBuilder != null ? resultBuilder.build() : null, + subtraces, + traceAddress, + type, + blockNumber, + blockHash, + transactionPosition, + transactionHash, + error, + null); + } + + protected PrivateFlatTrace( + final Action.Builder actionBuilder, + final Result.Builder resultBuilder, + final int subtraces, + final List traceAddress, + final String type, + final Long blockNumber, + final String blockHash, + final Integer transactionPosition, + final String transactionHash, + final Optional error, + final String revertReason) { + this( + actionBuilder != null ? actionBuilder.build() : null, + resultBuilder != null ? resultBuilder.build() : null, + subtraces, + traceAddress, + type, + blockNumber, + blockHash, + transactionPosition, + transactionHash, + error, + revertReason); + } + + protected PrivateFlatTrace( + final Action action, + final Result result, + final int subtraces, + final List traceAddress, + final String type, + final Long blockNumber, + final String blockHash, + final Integer transactionPosition, + final String transactionHash, + final Optional error, + final String revertReason) { + this.action = action; + this.result = result; + this.subtraces = subtraces; + this.traceAddress = traceAddress; + this.type = type; + this.blockNumber = blockNumber; + this.blockHash = blockHash; + this.transactionPosition = transactionPosition; + this.transactionHash = transactionHash; + this.error = error; + this.revertReason = revertReason; + } + + static PrivateFlatTrace.Builder freshBuilder(final PrivateTransactionTrace transactionTrace) { + return PrivateFlatTrace.builder() + .resultBuilder(Result.builder()) + .actionBuilder(from(transactionTrace)); + } + + public static Action.Builder from(final PrivateTransactionTrace trace) { + final Action.Builder builder = + Action.builder() + .from(trace.getPrivateTransaction().getSender().toHexString()) + .value(Quantity.create(trace.getPrivateTransaction().getValue())); + if (!trace.getTraceFrames().isEmpty()) { + final TraceFrame traceFrame = trace.getTraceFrames().get(0); + builder.gas( + "0x" + + Long.toHexString( + traceFrame.getGasRemaining() + (traceFrame.getPrecompiledGasCost().orElse(0L)))); + } + return builder; + } + + public Action getAction() { + return action; + } + + @JsonInclude(NON_NULL) + public Long getBlockNumber() { + return blockNumber; + } + + @JsonInclude(NON_NULL) + public String getBlockHash() { + return blockHash; + } + + @JsonInclude(NON_NULL) + public String getTransactionHash() { + return transactionHash; + } + + @JsonInclude(NON_NULL) + public Integer getTransactionPosition() { + return transactionPosition; + } + + @JsonInclude(NON_NULL) + public String getError() { + return error.orElse(null); + } + + @JsonInclude(NON_NULL) + public String getRevertReason() { + return revertReason; + } + + @JsonInclude(NON_NULL) + public AtomicReference getResult() { + return (error.isPresent()) ? null : new AtomicReference<>(result); + } + + public int getSubtraces() { + return subtraces; + } + + public List getTraceAddress() { + return traceAddress; + } + + public String getType() { + return type; + } + + public static PrivateFlatTrace.Builder builder() { + return new PrivateFlatTrace.Builder(); + } + + public static class Context { + + private final PrivateFlatTrace.Builder builder; + private long gasUsed = 0; + private boolean createOp; + + Context(final PrivateFlatTrace.Builder builder) { + this.builder = builder; + } + + public PrivateFlatTrace.Builder getBuilder() { + return builder; + } + + void incGasUsed(final long gas) { + setGasUsed(gasUsed + gas); + } + + void decGasUsed(final long gas) { + setGasUsed(gasUsed - gas); + } + + public long getGasUsed() { + return gasUsed; + } + + public void setGasUsed(final long gasUsed) { + this.gasUsed = gasUsed; + builder.getResultBuilder().gasUsed("0x" + Long.toHexString(gasUsed)); + } + + boolean isCreateOp() { + return createOp; + } + + void setCreateOp(final boolean createOp) { + this.createOp = createOp; + } + } + + public static class Builder { + + private Action.Builder actionBuilder; + private Result.Builder resultBuilder; + private int subtraces; + private List traceAddress = new ArrayList<>(); + private String type = "call"; + private Long blockNumber; + private String blockHash; + private String transactionHash; + private Integer transactionPosition; + private Optional error = Optional.empty(); + private String revertReason; + + protected Builder() {} + + PrivateFlatTrace.Builder resultBuilder(final Result.Builder resultBuilder) { + this.resultBuilder = resultBuilder; + return this; + } + + PrivateFlatTrace.Builder actionBuilder(final Action.Builder actionBuilder) { + this.actionBuilder = actionBuilder; + return this; + } + + public PrivateFlatTrace.Builder traceAddress(final List traceAddress) { + this.traceAddress = traceAddress; + return this; + } + + public PrivateFlatTrace.Builder type(final String type) { + this.type = type; + return this; + } + + public PrivateFlatTrace.Builder blockNumber(final Long blockNumber) { + this.blockNumber = blockNumber; + return this; + } + + public PrivateFlatTrace.Builder blockHash(final String blockHash) { + this.blockHash = blockHash; + return this; + } + + public PrivateFlatTrace.Builder transactionHash(final String transactionHash) { + this.transactionHash = transactionHash; + return this; + } + + public PrivateFlatTrace.Builder error(final Optional error) { + this.error = error; + return this; + } + + public PrivateFlatTrace.Builder revertReason(final String revertReason) { + this.revertReason = revertReason; + return this; + } + + public String getType() { + return type; + } + + public int getSubtraces() { + return subtraces; + } + + public List getTraceAddress() { + return traceAddress; + } + + public Long getBlockNumber() { + return blockNumber; + } + + public String getBlockHash() { + return blockHash; + } + + public String getTransactionHash() { + return transactionHash; + } + + public Integer getTransactionPosition() { + return transactionPosition; + } + + public Optional getError() { + return error; + } + + public String getRevertReason() { + return revertReason; + } + + void incSubTraces() { + this.subtraces++; + } + + public PrivateFlatTrace build() { + return new PrivateFlatTrace( + actionBuilder, + resultBuilder, + subtraces, + traceAddress, + type, + blockNumber, + blockHash, + transactionPosition, + transactionHash, + error, + revertReason); + } + + public Result.Builder getResultBuilder() { + return resultBuilder; + } + + public Action.Builder getActionBuilder() { + return actionBuilder; + } + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/privacy/privateTracing/PrivateTraceGenerator.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/privacy/privateTracing/PrivateTraceGenerator.java new file mode 100644 index 00000000000..3f78ffe592f --- /dev/null +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/privacy/privateTracing/PrivateTraceGenerator.java @@ -0,0 +1,598 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.privacy.privateTracing; + +import static org.hyperledger.besu.evm.internal.Words.toAddress; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor.PrivateTransactionTrace; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.Trace; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.TracingUtils; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.flat.Action; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.flat.Result; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.debug.TraceFrame; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.privacy.ExecutedPrivateTransaction; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.OptionalLong; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.google.common.collect.Streams; +import com.google.common.util.concurrent.Atomics; +import org.apache.tuweni.bytes.Bytes; + +public class PrivateTraceGenerator { + + private static final String ZERO_ADDRESS_STRING = Address.ZERO.toHexString(); + private static final int EIP_150_DIVISOR = 64; + + public static Stream generateFromTransactionTrace( + final ProtocolSchedule protocolSchedule, + final PrivateTransactionTrace transactionTrace, + final Block block, + final AtomicInteger traceCounter, + final Consumer consumer) { + + final PrivateFlatTrace.Builder firstFlatTraceBuilder = + PrivateFlatTrace.freshBuilder(transactionTrace); + + final ExecutedPrivateTransaction tx = transactionTrace.getPrivateTransaction(); + + final Optional smartContractCode = + tx.getInit().map(__ -> transactionTrace.getResult().getOutput().toString()); + final Optional smartContractAddress = + smartContractCode.map( + __ -> Address.contractAddress(tx.getSender(), tx.getNonce()).toHexString()); + final Optional revertReason = transactionTrace.getResult().getRevertReason(); + + // set code field in result node + smartContractCode.ifPresent(firstFlatTraceBuilder.getResultBuilder()::code); + revertReason.ifPresent(r -> firstFlatTraceBuilder.revertReason(r.toHexString())); + + // set init field if transaction is a smart contract deployment + tx.getInit().map(Bytes::toHexString).ifPresent(firstFlatTraceBuilder.getActionBuilder()::init); + + // set to, input and callType fields if not a smart contract + if (tx.getTo().isPresent()) { + final Bytes payload = tx.getPayload(); + firstFlatTraceBuilder + .getActionBuilder() + .to(tx.getTo().map(Bytes::toHexString).orElse(null)) + .callType("call") + .input(payload == null ? "0x" : payload.toHexString()); + + if (!transactionTrace.getTraceFrames().isEmpty() + && hasRevertInSubCall(transactionTrace, transactionTrace.getTraceFrames().get(0))) { + firstFlatTraceBuilder.error(Optional.of("Reverted")); + } + + } else { + firstFlatTraceBuilder + .type("create") + .getResultBuilder() + .address(smartContractAddress.orElse(null)); + } + + if (!transactionTrace.getTraceFrames().isEmpty()) { + final OptionalLong precompiledGasCost = + transactionTrace.getTraceFrames().get(0).getPrecompiledGasCost(); + if (precompiledGasCost.isPresent()) { + firstFlatTraceBuilder + .getResultBuilder() + .gasUsed("0x" + Long.toHexString(precompiledGasCost.getAsLong())); + } + } + + final List flatTraces = new ArrayList<>(); + + // stack of previous contexts + final Deque tracesContexts = new ArrayDeque<>(); + + // add the first transactionTrace context to the queue of transactionTrace contexts + PrivateFlatTrace.Context currentContext = new PrivateFlatTrace.Context(firstFlatTraceBuilder); + tracesContexts.addLast(currentContext); + flatTraces.add(currentContext.getBuilder()); + // declare the first transactionTrace context as the previous transactionTrace context + long cumulativeGasCost = 0; + + final Iterator iter = transactionTrace.getTraceFrames().iterator(); + Optional nextTraceFrame = + iter.hasNext() ? Optional.of(iter.next()) : Optional.empty(); + while (nextTraceFrame.isPresent()) { + final TraceFrame traceFrame = nextTraceFrame.get(); + nextTraceFrame = iter.hasNext() ? Optional.of(iter.next()) : Optional.empty(); + cumulativeGasCost += + traceFrame.getGasCost().orElse(0L) + traceFrame.getPrecompiledGasCost().orElse(0L); + + final String opcodeString = traceFrame.getOpcode(); + if ("CALL".equals(opcodeString) + || "CALLCODE".equals(opcodeString) + || "DELEGATECALL".equals(opcodeString) + || "STATICCALL".equals(opcodeString)) { + + currentContext = + handleCall( + transactionTrace, + traceFrame, + nextTraceFrame, + flatTraces, + cumulativeGasCost, + tracesContexts, + opcodeString.toLowerCase(Locale.US)); + + } else if ("CALLDATALOAD".equals(opcodeString)) { + currentContext = handleCallDataLoad(currentContext, traceFrame); + } else if ("RETURN".equals(opcodeString) || "STOP".equals(opcodeString)) { + currentContext = + handleReturn( + protocolSchedule, + transactionTrace, + block, + traceFrame, + tracesContexts, + currentContext); + } else if ("SELFDESTRUCT".equals(opcodeString)) { + if (traceFrame.getExceptionalHaltReason().isPresent()) { + currentContext = + handleCall( + transactionTrace, + traceFrame, + nextTraceFrame, + flatTraces, + cumulativeGasCost, + tracesContexts, + opcodeString.toLowerCase(Locale.US)); + } else { + currentContext = + handleSelfDestruct(traceFrame, tracesContexts, currentContext, flatTraces); + } + } else if (("CREATE".equals(opcodeString) || "CREATE2".equals(opcodeString)) + && (traceFrame.getExceptionalHaltReason().isEmpty() || traceFrame.getDepth() == 0)) { + currentContext = + handleCreateOperation( + traceFrame, + nextTraceFrame, + flatTraces, + cumulativeGasCost, + tracesContexts, + smartContractAddress); + } else if ("REVERT".equals(opcodeString)) { + currentContext = handleRevert(tracesContexts, currentContext); + } + + if (traceFrame.getExceptionalHaltReason().isPresent()) { + currentContext = handleHalt(flatTraces, tracesContexts, currentContext, traceFrame); + } + + if (currentContext == null) { + break; + } + } + + return flatTraces.stream().peek(consumer).map(PrivateFlatTrace.Builder::build); + } + + public static Stream generateFromTransactionTraceAndBlock( + final ProtocolSchedule protocolSchedule, + final PrivateTransactionTrace transactionTrace, + final Block block) { + return generateFromTransactionTrace( + protocolSchedule, + transactionTrace, + block, + new AtomicInteger(), + builder -> + addAdditionalTransactionInformationToFlatTrace(builder, transactionTrace, block)); + } + + private static long computeGasUsed( + final Deque tracesContexts, + final PrivateFlatTrace.Context currentContext, + final PrivateTransactionTrace transactionTrace, + final TraceFrame traceFrame) { + + final long gasRemainingBeforeProcessed; + final long gasRemainingAfterProcessed; + long gasRefund = 0; + if (tracesContexts.size() == 1) { + gasRemainingBeforeProcessed = transactionTrace.getTraceFrames().get(0).getGasRemaining(); + gasRemainingAfterProcessed = transactionTrace.getResult().getGasRemaining(); + if (gasRemainingAfterProcessed > traceFrame.getGasRemaining()) { + gasRefund = gasRemainingAfterProcessed - traceFrame.getGasRemaining(); + } else { + gasRefund = traceFrame.getGasRefund(); + } + } else { + final Action.Builder actionBuilder = currentContext.getBuilder().getActionBuilder(); + gasRemainingBeforeProcessed = Long.decode(actionBuilder.getGas()); + gasRemainingAfterProcessed = traceFrame.getGasRemaining(); + } + return gasRemainingBeforeProcessed - gasRemainingAfterProcessed + gasRefund; + } + + private static long computeGas( + final TraceFrame traceFrame, final Optional nextTraceFrame) { + if (traceFrame.getGasCost().isPresent()) { + final long gasNeeded = traceFrame.getGasCost().getAsLong(); + final long currentGas = traceFrame.getGasRemaining(); + if (currentGas >= gasNeeded) { + final long gasRemaining = currentGas - gasNeeded; + return gasRemaining - Math.floorDiv(gasRemaining, EIP_150_DIVISOR); + } + } + return nextTraceFrame.map(TraceFrame::getGasRemaining).orElse(0L); + } + + private static String calculateCallingAddress(final PrivateFlatTrace.Context lastContext) { + final PrivateFlatTrace.Builder lastContextBuilder = lastContext.getBuilder(); + final Action.Builder lastActionBuilder = lastContextBuilder.getActionBuilder(); + if (lastActionBuilder.getCallType() == null) { + if ("create".equals(lastContextBuilder.getType())) { + return lastContextBuilder.getResultBuilder().getAddress(); + } else { + return ZERO_ADDRESS_STRING; + } + } + + switch (lastActionBuilder.getCallType()) { + case "call": + case "staticcall": + return lastActionBuilder.getTo(); + case "delegatecall": + case "callcode": + return lastActionBuilder.getFrom(); + case "create": + case "create2": + return lastContextBuilder.getResultBuilder().getAddress(); + default: + return ZERO_ADDRESS_STRING; + } + } + + private static List calculateTraceAddress( + final Deque contexts) { + return contexts.stream() + .map(context -> context.getBuilder().getSubtraces()) + .collect(Collectors.toList()); + } + + private static List calculateSelfDescructAddress( + final Deque contexts) { + return Streams.concat( + contexts.stream() + .map(context -> context.getBuilder().getSubtraces())) // , Stream.of(0)) + .collect(Collectors.toList()); + } + + private static String getActionAddress( + final Action.Builder callingAction, final String recipient) { + if (callingAction.getCallType() != null) { + return callingAction.getCallType().equals("call") + ? callingAction.getTo() + : callingAction.getFrom(); + } + return firstNonNull("", recipient, callingAction.getFrom(), callingAction.getTo()); + } + + private static String firstNonNull(final String defaultValue, final String... values) { + for (final String value : values) { + if (value != null) { + return value; + } + } + return defaultValue; + } + + private static PrivateFlatTrace.Context handleCreateOperation( + final TraceFrame traceFrame, + final Optional nextTraceFrame, + final List flatTraces, + final long cumulativeGasCost, + final Deque tracesContexts, + final Optional smartContractAddress) { + final PrivateFlatTrace.Context lastContext = tracesContexts.peekLast(); + + final String callingAddress = calculateCallingAddress(lastContext); + + final PrivateFlatTrace.Builder subTraceBuilder = + PrivateFlatTrace.builder() + .type("create") + .traceAddress(calculateTraceAddress(tracesContexts)) + .resultBuilder(Result.builder()); + + final Action.Builder subTraceActionBuilder = + Action.builder() + .from(smartContractAddress.orElse(callingAddress)) + .gas("0x" + Long.toHexString(computeGas(traceFrame, nextTraceFrame))) + .value(Quantity.create(nextTraceFrame.map(TraceFrame::getValue).orElse(Wei.ZERO))); + + traceFrame + .getMaybeCode() + .map(Code::getBytes) + .map(Bytes::toHexString) + .ifPresent(subTraceActionBuilder::init); + + final PrivateFlatTrace.Context currentContext = + new PrivateFlatTrace.Context(subTraceBuilder.actionBuilder(subTraceActionBuilder)); + + currentContext + .getBuilder() + .getResultBuilder() + .address(nextTraceFrame.map(TraceFrame::getRecipient).orElse(Address.ZERO).toHexString()); + currentContext.setCreateOp(true); + currentContext.decGasUsed(cumulativeGasCost); + tracesContexts.addLast(currentContext); + flatTraces.add(currentContext.getBuilder()); + return currentContext; + } + + private static PrivateFlatTrace.Context handleHalt( + final List flatTraces, + final Deque tracesContexts, + final PrivateFlatTrace.Context currentContext, + final TraceFrame traceFrame) { + final PrivateFlatTrace.Builder traceFrameBuilder; + if (currentContext == null) { + traceFrameBuilder = flatTraces.get(flatTraces.size() - 1); + } else { + traceFrameBuilder = currentContext.getBuilder(); + } + traceFrameBuilder.error( + traceFrame.getExceptionalHaltReason().map(ExceptionalHaltReason::getDescription)); + if (currentContext != null) { + final Action.Builder actionBuilder = traceFrameBuilder.getActionBuilder(); + actionBuilder.value(Quantity.create(traceFrame.getValue())); + tracesContexts.removeLast(); + final PrivateFlatTrace.Context nextContext = tracesContexts.peekLast(); + if (nextContext != null) { + nextContext.getBuilder().incSubTraces(); + } + return nextContext; + } + return currentContext; + } + + private static PrivateFlatTrace.Context handleRevert( + final Deque tracesContexts, + final PrivateFlatTrace.Context currentContext) { + currentContext.getBuilder().error(Optional.of("Reverted")); + tracesContexts.removeLast(); + final PrivateFlatTrace.Context nextContext = tracesContexts.peekLast(); + if (nextContext != null) { + nextContext.getBuilder().incSubTraces(); + } + return nextContext; + } + + private static PrivateFlatTrace.Context handleSelfDestruct( + final TraceFrame traceFrame, + final Deque tracesContexts, + final PrivateFlatTrace.Context currentContext, + final List flatTraces) { + + final Action.Builder actionBuilder = currentContext.getBuilder().getActionBuilder(); + final long gasUsed = + Long.decode(actionBuilder.getGas()) + - traceFrame.getGasRemaining() + + (traceFrame.getGasCost().orElse(0L)); + + currentContext.setGasUsed(gasUsed); + + final Bytes[] stack = traceFrame.getStack().orElseThrow(); + final Address refundAddress = toAddress(stack[stack.length - 1]); + final PrivateFlatTrace.Builder subTraceBuilder = + PrivateFlatTrace.builder() + .type("suicide") + .traceAddress(calculateSelfDescructAddress(tracesContexts)); + + final AtomicReference weiBalance = Atomics.newReference(Wei.ZERO); + traceFrame + .getMaybeRefunds() + .ifPresent(refunds -> weiBalance.set(refunds.getOrDefault(refundAddress, Wei.ZERO))); + + final Action.Builder callingAction = tracesContexts.peekLast().getBuilder().getActionBuilder(); + final String actionAddress = + getActionAddress(callingAction, traceFrame.getRecipient().toHexString()); + final Action.Builder subTraceActionBuilder = + Action.builder() + .address(actionAddress) + .refundAddress(refundAddress.toString()) + .balance(TracingUtils.weiAsHex(weiBalance.get())); + + flatTraces.add( + new PrivateFlatTrace.Context(subTraceBuilder.actionBuilder(subTraceActionBuilder)) + .getBuilder()); + final PrivateFlatTrace.Context lastContext = tracesContexts.removeLast(); + lastContext.getBuilder().incSubTraces(); + final PrivateFlatTrace.Context nextContext = tracesContexts.peekLast(); + if (nextContext != null) { + nextContext.getBuilder().incSubTraces(); + } + return nextContext; + } + + private static PrivateFlatTrace.Context handleReturn( + final ProtocolSchedule protocolSchedule, + final PrivateTransactionTrace transactionTrace, + final Block block, + final TraceFrame traceFrame, + final Deque tracesContexts, + final PrivateFlatTrace.Context currentContext) { + + final PrivateFlatTrace.Builder traceFrameBuilder = currentContext.getBuilder(); + final Result.Builder resultBuilder = traceFrameBuilder.getResultBuilder(); + final Action.Builder actionBuilder = traceFrameBuilder.getActionBuilder(); + actionBuilder.value(Quantity.create(traceFrame.getValue())); + + currentContext.setGasUsed( + computeGasUsed(tracesContexts, currentContext, transactionTrace, traceFrame)); + + if ("STOP".equals(traceFrame.getOpcode()) && resultBuilder.isGasUsedEmpty()) { + final long callStipend = + protocolSchedule + .getByBlockHeader(block.getHeader()) + .getGasCalculator() + .getAdditionalCallStipend(); + tracesContexts.stream() + .filter( + context -> + !tracesContexts.getFirst().equals(context) + && !tracesContexts.getLast().equals(context)) + .forEach(context -> context.decGasUsed(callStipend)); + } + + final Bytes outputData = traceFrame.getOutputData(); + if (resultBuilder.getCode() == null) { + resultBuilder.output(outputData.toHexString()); + } + + // set value for contract creation TXes, CREATE, and CREATE2 + if (actionBuilder.getCallType() == null && traceFrame.getMaybeCode().isPresent()) { + actionBuilder.init(traceFrame.getMaybeCode().get().getBytes().toHexString()); + resultBuilder.code(outputData.toHexString()); + if (currentContext.isCreateOp()) { + // this is from a CREATE/CREATE2, so add code deposit cost. + currentContext.incGasUsed(outputData.size() * 200L); + } + } + + tracesContexts.removeLast(); + final PrivateFlatTrace.Context nextContext = tracesContexts.peekLast(); + if (nextContext != null) { + nextContext.getBuilder().incSubTraces(); + } + return nextContext; + } + + private static PrivateFlatTrace.Context handleCallDataLoad( + final PrivateFlatTrace.Context currentContext, final TraceFrame traceFrame) { + if (!traceFrame.getValue().isZero()) { + currentContext + .getBuilder() + .getActionBuilder() + .value(traceFrame.getValue().toShortHexString()); + } else { + currentContext.getBuilder().getActionBuilder().value("0x0"); + } + return currentContext; + } + + private static PrivateFlatTrace.Context handleCall( + final PrivateTransactionTrace transactionTrace, + final TraceFrame traceFrame, + final Optional nextTraceFrame, + final List flatTraces, + final long cumulativeGasCost, + final Deque tracesContexts, + final String opcodeString) { + final Bytes[] stack = traceFrame.getStack().orElseThrow(); + final PrivateFlatTrace.Context lastContext = tracesContexts.peekLast(); + + final String callingAddress = calculateCallingAddress(lastContext); + + if (traceFrame.getDepth() >= nextTraceFrame.map(TraceFrame::getDepth).orElse(0)) { + // don't log calls to calls that don't execute, such as insufficient value and precompiles + return tracesContexts.peekLast(); + } + + final PrivateFlatTrace.Builder subTraceBuilder = + PrivateFlatTrace.builder() + .traceAddress(calculateTraceAddress(tracesContexts)) + .resultBuilder(Result.builder()); + final Action.Builder subTraceActionBuilder = + Action.builder() + .from(callingAddress) + .input( + nextTraceFrame.map(TraceFrame::getInputData).map(Bytes::toHexString).orElse(null)) + .gas( + "0x" + Long.toHexString(nextTraceFrame.map(TraceFrame::getGasRemaining).orElse(0L))) + .callType(opcodeString.toLowerCase(Locale.US)) + .value(Quantity.create(traceFrame.getValue())); + + if (stack.length > 1) { + subTraceActionBuilder.to(toAddress(stack[stack.length - 2]).toString()); + } + + nextTraceFrame.ifPresent( + nextFrame -> { + if (hasRevertInSubCall(transactionTrace, nextFrame)) { + subTraceBuilder.error(Optional.of("Reverted")); + } + }); + + final PrivateFlatTrace.Context currentContext = + new PrivateFlatTrace.Context(subTraceBuilder.actionBuilder(subTraceActionBuilder)); + currentContext.decGasUsed(cumulativeGasCost); + + tracesContexts.addLast(currentContext); + flatTraces.add(currentContext.getBuilder()); + return currentContext; + } + + private static boolean hasRevertInSubCall( + final PrivateTransactionTrace transactionTrace, final TraceFrame callFrame) { + return transactionTrace.getTraceFrames().stream() + .filter(traceFrame -> !traceFrame.equals(callFrame)) + .takeWhile(traceFrame -> !traceFrame.getOpcode().equals("RETURN")) + .filter(traceFrame -> traceFrame.getOpcode().equals("REVERT")) + .anyMatch(traceFrame -> traceFrame.getDepth() == callFrame.getDepth()); + } + + private static void addAdditionalTransactionInformationToFlatTrace( + final PrivateFlatTrace.Builder builder, + final PrivateTransactionTrace transactionTrace, + final Block block) { + // add block information (hash and number) + builder.blockHash(block.getHash().toHexString()).blockNumber(block.getHeader().getNumber()); + // add transaction information (position and hash) + builder.transactionHash(transactionTrace.getPrivateTransaction().getPmtHash().toHexString()); + + addContractCreationMethodToTrace(transactionTrace, builder); + } + + private static void addContractCreationMethodToTrace( + final PrivateTransactionTrace transactionTrace, final PrivateFlatTrace.Builder builder) { + // add creationMethod for create action + Optional.ofNullable(builder.getType()) + .filter(type -> type.equals("create")) + .ifPresent( + __ -> + builder + .getActionBuilder() + .creationMethod( + transactionTrace.getTraceFrames().stream() + .filter(frame -> "CREATE2".equals(frame.getOpcode())) + .findFirst() + .map(TraceFrame::getOpcode) + .orElse("CREATE") + .toLowerCase(Locale.US))); + } +} diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/diff/AccountDiff.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/diff/AccountDiff.java index cabf33c8470..44ae5e788bf 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/diff/AccountDiff.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/diff/AccountDiff.java @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.diff; import java.util.Map; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/diff/DiffNode.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/diff/DiffNode.java index 46b4252bb47..e952f9b2912 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/diff/DiffNode.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/diff/DiffNode.java @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.diff; import java.io.IOException; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/diff/StateDiffGenerator.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/diff/StateDiffGenerator.java index ffc816db736..8656c8e0c1b 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/diff/StateDiffGenerator.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/diff/StateDiffGenerator.java @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.diff; import org.hyperledger.besu.datatypes.Address; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/diff/StateDiffTrace.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/diff/StateDiffTrace.java index 1a2ecc3d696..7e4213780ed 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/diff/StateDiffTrace.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/diff/StateDiffTrace.java @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.diff; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.tracing.Trace; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/flat/Action.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/flat/Action.java index 03bafbe859f..de7ae2c6f0e 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/flat/Action.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/flat/Action.java @@ -263,7 +263,7 @@ public Builder balance(final String balance) { return this; } - Builder refundAddress(final String refundAddress) { + public Builder refundAddress(final String refundAddress) { this.refundAddress = refundAddress; return this; } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/flat/FlatTraceGenerator.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/flat/FlatTraceGenerator.java index 3a7fa3951fb..a253c6e323f 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/flat/FlatTraceGenerator.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/flat/FlatTraceGenerator.java @@ -28,6 +28,8 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.operation.ReturnOperation; +import org.hyperledger.besu.evm.operation.RevertOperation; import java.util.ArrayDeque; import java.util.ArrayList; @@ -530,11 +532,19 @@ private static FlatTrace.Context handleCallDataLoad( private static boolean hasRevertInSubCall( final TransactionTrace transactionTrace, final TraceFrame callFrame) { - return transactionTrace.getTraceFrames().stream() - .filter(traceFrame -> !traceFrame.equals(callFrame)) - .takeWhile(traceFrame -> !traceFrame.getOpcode().equals("RETURN")) - .filter(traceFrame -> traceFrame.getOpcode().equals("REVERT")) - .anyMatch(traceFrame -> traceFrame.getDepth() == callFrame.getDepth()); + for (int i = 0; i < transactionTrace.getTraceFrames().size(); i++) { + if (i + 1 < transactionTrace.getTraceFrames().size()) { + final TraceFrame next = transactionTrace.getTraceFrames().get(i + 1); + if (next.getDepth() == callFrame.getDepth()) { + if (next.getOpcodeNumber() == RevertOperation.OPCODE) { + return true; + } else if (next.getOpcodeNumber() == ReturnOperation.OPCODE) { + return false; + } + } + } + } + return false; } private static String calculateCallingAddress(final FlatTrace.Context lastContext) { @@ -614,7 +624,7 @@ private static List calculateSelfDescructAddress( .collect(Collectors.toList()); } - private static void addAdditionalTransactionInformationToFlatTrace( + protected static void addAdditionalTransactionInformationToFlatTrace( final FlatTrace.Builder builder, final TransactionTrace transactionTrace, final Block block) { // add block information (hash and number) builder.blockHash(block.getHash().toHexString()).blockNumber(block.getHeader().getNumber()); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/flat/MixInIgnoreRevertReason.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/flat/MixInIgnoreRevertReason.java index 1d24c6264ee..c967febd6bd 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/flat/MixInIgnoreRevertReason.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/flat/MixInIgnoreRevertReason.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/vm/VmTraceGenerator.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/vm/VmTraceGenerator.java index 9800f7d6bd1..74a524fa733 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/vm/VmTraceGenerator.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/tracing/vm/VmTraceGenerator.java @@ -107,7 +107,8 @@ private boolean mustIgnore(final TraceFrame frame) { } else if (frame.getExceptionalHaltReason().isPresent()) { final Optional haltReason = frame.getExceptionalHaltReason(); return haltReason.get() != ExceptionalHaltReason.INVALID_JUMP_DESTINATION - && haltReason.get() != ExceptionalHaltReason.INSUFFICIENT_GAS; + && haltReason.get() != ExceptionalHaltReason.INSUFFICIENT_GAS + && haltReason.get() != ExceptionalHaltReason.ILLEGAL_STATE_CHANGE; } else { return frame.isVirtualOperation(); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/ipc/JsonRpcIpcService.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/ipc/JsonRpcIpcService.java index 41214d9021a..b980c750d5c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/ipc/JsonRpcIpcService.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/ipc/JsonRpcIpcService.java @@ -20,8 +20,8 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponseType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; import java.io.IOException; import java.nio.file.Files; @@ -155,7 +155,7 @@ public Future start() { .filter( jsonRpcResponse -> jsonRpcResponse.getType() - != JsonRpcResponseType.NONE) + != RpcResponseType.NONE) .toArray(JsonRpcResponse[]::new); socket.write( diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/AdminJsonRpcMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/AdminJsonRpcMethods.java index 286758cb762..437f64f1df2 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/AdminJsonRpcMethods.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/AdminJsonRpcMethods.java @@ -28,6 +28,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.PluginsReloadConfiguration; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.eth.manager.EthPeers; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.p2p.network.P2PNetwork; import org.hyperledger.besu.ethereum.p2p.peers.EnodeDnsConfiguration; import org.hyperledger.besu.nat.NatService; @@ -48,6 +49,7 @@ public class AdminJsonRpcMethods extends ApiGroupJsonRpcMethods { private final Map namedPlugins; private final EthPeers ethPeers; private final Optional enodeDnsConfiguration; + private final ProtocolSchedule protocolSchedule; public AdminJsonRpcMethods( final String clientVersion, @@ -58,7 +60,8 @@ public AdminJsonRpcMethods( final Map namedPlugins, final NatService natService, final EthPeers ethPeers, - final Optional enodeDnsConfiguration) { + final Optional enodeDnsConfiguration, + final ProtocolSchedule protocolSchedule) { this.clientVersion = clientVersion; this.networkId = networkId; this.genesisConfigOptions = genesisConfigOptions; @@ -68,6 +71,7 @@ public AdminJsonRpcMethods( this.natService = natService; this.ethPeers = ethPeers; this.enodeDnsConfiguration = enodeDnsConfiguration; + this.protocolSchedule = protocolSchedule; } @Override @@ -86,7 +90,8 @@ protected Map create() { genesisConfigOptions, p2pNetwork, blockchainQueries, - natService), + natService, + protocolSchedule), new AdminPeers(ethPeers), new AdminChangeLogLevel(), new AdminGenerateLogBloomCache(blockchainQueries), diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/DebugJsonRpcMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/DebugJsonRpcMethods.java index 71212456d38..90ae97c5c4f 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/DebugJsonRpcMethods.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/DebugJsonRpcMethods.java @@ -93,14 +93,14 @@ protected String getApiGroup() { @Override protected Map create() { final BlockReplay blockReplay = - new BlockReplay(protocolSchedule, blockchainQueries.getBlockchain()); + new BlockReplay(protocolSchedule, protocolContext, blockchainQueries.getBlockchain()); return mapOf( new DebugTraceTransaction(blockchainQueries, new TransactionTracer(blockReplay)), new DebugAccountRange(blockchainQueries), new DebugStorageRangeAt(blockchainQueries, blockReplay), new DebugMetrics(metricsSystem), - new DebugResyncWorldstate(protocolSchedule, protocolContext.getBlockchain(), synchronizer), + new DebugResyncWorldstate(protocolContext, synchronizer), new DebugTraceBlock( () -> new BlockTracer(blockReplay), ScheduleBasedBlockHeaderFunctions.create(protocolSchedule), @@ -110,11 +110,11 @@ protected Map create() { new DebugTraceBlockByNumber(() -> new BlockTracer(blockReplay), blockchainQueries), new DebugTraceBlockByHash(() -> new BlockTracer(blockReplay), () -> blockchainQueries), new DebugBatchSendRawTransaction(transactionPool), - new DebugGetBadBlocks(blockchainQueries, protocolSchedule, blockResult), + new DebugGetBadBlocks(protocolContext, blockResult), new DebugStandardTraceBlockToFile( () -> new TransactionTracer(blockReplay), blockchainQueries, dataDir), new DebugStandardTraceBadBlockToFile( - () -> new TransactionTracer(blockReplay), blockchainQueries, protocolSchedule, dataDir), + () -> new TransactionTracer(blockReplay), blockchainQueries, protocolContext, dataDir), new DebugAccountAt(blockchainQueries, () -> new BlockTracer(blockReplay)), new DebugGetRawHeader(blockchainQueries), new DebugGetRawBlock(blockchainQueries), diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthJsonRpcMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthJsonRpcMethods.java index 9dfe2e45772..5baa1106462 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthJsonRpcMethods.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/EthJsonRpcMethods.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -18,6 +18,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.filter.FilterManager; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthAccounts; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthBlobBaseFee; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthBlockNumber; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthCall; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthChainId; @@ -51,6 +52,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthGetUncleCountByBlockNumber; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthGetWork; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthHashrate; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthMaxPriorityFeePerGas; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthMining; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthNewBlockFilter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthNewFilter; @@ -130,7 +132,11 @@ protected Map create() { blockchainQueries.getWorldStateArchive(), protocolSchedule, apiConfiguration.getGasCap())), - new EthFeeHistory(protocolSchedule, blockchainQueries.getBlockchain()), + new EthFeeHistory( + protocolSchedule, + blockchainQueries.getBlockchain(), + miningCoordinator, + apiConfiguration), new EthGetCode(blockchainQueries), new EthGetLogs(blockchainQueries, apiConfiguration.getMaxLogsRange()), new EthGetProof(blockchainQueries), @@ -170,13 +176,15 @@ protected Map create() { new EthMining(miningCoordinator), new EthCoinbase(miningCoordinator), new EthProtocolVersion(supportedCapabilities), - new EthGasPrice(blockchainQueries, miningCoordinator), + new EthGasPrice(blockchainQueries, apiConfiguration), new EthGetWork(miningCoordinator), new EthSubmitWork(miningCoordinator), new EthHashrate(miningCoordinator), new EthSubmitHashRate(miningCoordinator), new EthChainId(protocolSchedule.getChainId()), new EthGetMinerDataByBlockHash(blockchainQueries, protocolSchedule), - new EthGetMinerDataByBlockNumber(blockchainQueries, protocolSchedule)); + new EthGetMinerDataByBlockNumber(blockchainQueries, protocolSchedule), + new EthBlobBaseFee(blockchainQueries.getBlockchain(), protocolSchedule), + new EthMaxPriorityFeePerGas(blockchainQueries)); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.java index 9f4480b82be..d8d04027f6a 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/ExecutionEngineJsonRpcMethods.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -23,21 +23,24 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineForkchoiceUpdatedV1; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineForkchoiceUpdatedV2; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineForkchoiceUpdatedV3; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetBlobsV1; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetClientVersionV1; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetPayloadBodiesByHashV1; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetPayloadBodiesByRangeV1; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetPayloadV1; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetPayloadV2; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetPayloadV3; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetPayloadV6110; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineGetPayloadV4; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineNewPayloadV1; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineNewPayloadV2; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineNewPayloadV3; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineNewPayloadV6110; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineNewPayloadV4; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EnginePreparePayloadDebug; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine.EngineQosTimer; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory; import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator; import org.hyperledger.besu.ethereum.eth.manager.EthPeers; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import java.util.ArrayList; @@ -57,13 +60,19 @@ public class ExecutionEngineJsonRpcMethods extends ApiGroupJsonRpcMethods { private final ProtocolContext protocolContext; private final EthPeers ethPeers; private final Vertx consensusEngineServer; + private final String clientVersion; + private final String commit; + private final TransactionPool transactionPool; ExecutionEngineJsonRpcMethods( final MiningCoordinator miningCoordinator, final ProtocolSchedule protocolSchedule, final ProtocolContext protocolContext, final EthPeers ethPeers, - final Vertx consensusEngineServer) { + final Vertx consensusEngineServer, + final String clientVersion, + final String commit, + final TransactionPool transactionPool) { this.mergeCoordinator = Optional.ofNullable(miningCoordinator) .filter(mc -> mc.isCompatibleWithEngineApi()) @@ -72,6 +81,9 @@ public class ExecutionEngineJsonRpcMethods extends ApiGroupJsonRpcMethods { this.protocolContext = protocolContext; this.ethPeers = ethPeers; this.consensusEngineServer = consensusEngineServer; + this.clientVersion = clientVersion; + this.commit = commit; + this.transactionPool = transactionPool; } @Override @@ -147,7 +159,11 @@ protected Map create() { new EngineExchangeCapabilities( consensusEngineServer, protocolContext, engineQosTimer), new EnginePreparePayloadDebug( - consensusEngineServer, protocolContext, engineQosTimer, mergeCoordinator.get()))); + consensusEngineServer, protocolContext, engineQosTimer, mergeCoordinator.get()), + new EngineGetClientVersionV1( + consensusEngineServer, protocolContext, engineQosTimer, clientVersion, commit), + new EngineGetBlobsV1( + consensusEngineServer, protocolContext, engineQosTimer, transactionPool))); if (protocolSchedule.anyMatch(p -> p.spec().getName().equalsIgnoreCase("cancun"))) { executionEngineApisSupported.add( @@ -160,9 +176,9 @@ protected Map create() { protocolSchedule)); } - if (protocolSchedule.anyMatch(p -> p.spec().getName().equalsIgnoreCase("ExperimentalEips"))) { + if (protocolSchedule.anyMatch(p -> p.spec().getName().equalsIgnoreCase("prague"))) { executionEngineApisSupported.add( - new EngineGetPayloadV6110( + new EngineGetPayloadV4( consensusEngineServer, protocolContext, mergeCoordinator.get(), @@ -171,7 +187,7 @@ protected Map create() { protocolSchedule)); executionEngineApisSupported.add( - new EngineNewPayloadV6110( + new EngineNewPayloadV4( consensusEngineServer, protocolSchedule, protocolContext, diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java index 9dbc1ef98b4..cdf4440bde9 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/JsonRpcMethodsFactory.java @@ -17,6 +17,7 @@ import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.ApiConfiguration; +import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.filter.FilterManager; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; @@ -54,7 +55,9 @@ public class JsonRpcMethodsFactory { public Map methods( + final String clientNodeName, final String clientVersion, + final String commit, final BigInteger networkId, final GenesisConfigOptions genesisConfigOptions, final P2PNetwork p2pNetwork, @@ -75,6 +78,7 @@ public Map methods( final JsonRpcConfiguration jsonRpcConfiguration, final WebSocketConfiguration webSocketConfiguration, final MetricsConfiguration metricsConfiguration, + final GraphQLConfiguration graphQLConfiguration, final NatService natService, final Map namedPlugins, final Path dataDir, @@ -89,7 +93,7 @@ public Map methods( final List availableApiGroups = List.of( new AdminJsonRpcMethods( - clientVersion, + clientNodeName, networkId, genesisConfigOptions, p2pNetwork, @@ -97,7 +101,8 @@ public Map methods( namedPlugins, natService, ethPeers, - enodeDnsConfiguration), + enodeDnsConfiguration, + protocolSchedule), new DebugJsonRpcMethods( blockchainQueries, protocolContext, @@ -114,7 +119,10 @@ public Map methods( protocolSchedule, protocolContext, ethPeers, - consensusEngineServer), + consensusEngineServer, + clientVersion, + commit, + transactionPool), new EthJsonRpcMethods( blockchainQueries, synchronizer, @@ -129,7 +137,8 @@ public Map methods( networkId, jsonRpcConfiguration, webSocketConfiguration, - metricsConfiguration), + metricsConfiguration, + graphQLConfiguration), new MinerJsonRpcMethods(miningParameters, miningCoordinator), new PermJsonRpcMethods(accountsAllowlistController, nodeAllowlistController), new PrivJsonRpcMethods( @@ -140,8 +149,9 @@ public Map methods( filterManager), new PrivxJsonRpcMethods( blockchainQueries, protocolSchedule, transactionPool, privacyParameters), - new Web3JsonRpcMethods(clientVersion), - new TraceJsonRpcMethods(blockchainQueries, protocolSchedule, apiConfiguration), + new Web3JsonRpcMethods(clientNodeName), + new TraceJsonRpcMethods( + blockchainQueries, protocolSchedule, protocolContext, apiConfiguration), new TxPoolJsonRpcMethods(transactionPool), new PluginsJsonRpcMethods(namedPlugins)); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/MinerJsonRpcMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/MinerJsonRpcMethods.java index 8afaf6a5873..c799c23305b 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/MinerJsonRpcMethods.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/MinerJsonRpcMethods.java @@ -17,10 +17,12 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.miner.MinerChangeTargetGasLimit; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.miner.MinerGetExtraData; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.miner.MinerGetMinGasPrice; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.miner.MinerGetMinPriorityFee; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.miner.MinerSetCoinbase; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.miner.MinerSetEtherbase; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.miner.MinerSetExtraData; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.miner.MinerSetMinGasPrice; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.miner.MinerSetMinPriorityFee; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.miner.MinerStart; @@ -58,6 +60,8 @@ protected Map create() { new MinerGetMinPriorityFee(miningParameters), new MinerSetMinPriorityFee(miningParameters), new MinerGetMinGasPrice(miningParameters), - new MinerSetMinGasPrice(miningParameters)); + new MinerSetMinGasPrice(miningParameters), + new MinerGetExtraData(miningParameters), + new MinerSetExtraData(miningParameters)); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/NetJsonRpcMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/NetJsonRpcMethods.java index a3fcc318cf7..9ccfc81fedf 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/NetJsonRpcMethods.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/NetJsonRpcMethods.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.methods; +import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; @@ -37,18 +38,21 @@ public class NetJsonRpcMethods extends ApiGroupJsonRpcMethods { private final JsonRpcConfiguration jsonRpcConfiguration; private final WebSocketConfiguration webSocketConfiguration; private final MetricsConfiguration metricsConfiguration; + private final GraphQLConfiguration graphQLConfiguration; public NetJsonRpcMethods( final P2PNetwork p2pNetwork, final BigInteger networkId, final JsonRpcConfiguration jsonRpcConfiguration, final WebSocketConfiguration webSocketConfiguration, - final MetricsConfiguration metricsConfiguration) { + final MetricsConfiguration metricsConfiguration, + final GraphQLConfiguration graphQLConfiguration) { this.p2pNetwork = p2pNetwork; this.networkId = networkId; this.jsonRpcConfiguration = jsonRpcConfiguration; this.webSocketConfiguration = webSocketConfiguration; this.metricsConfiguration = metricsConfiguration; + this.graphQLConfiguration = graphQLConfiguration; } @Override @@ -64,6 +68,10 @@ protected Map create() { new NetPeerCount(p2pNetwork), new NetEnode(p2pNetwork), new NetServices( - jsonRpcConfiguration, webSocketConfiguration, p2pNetwork, metricsConfiguration)); + jsonRpcConfiguration, + webSocketConfiguration, + p2pNetwork, + metricsConfiguration, + graphQLConfiguration)); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PermJsonRpcMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PermJsonRpcMethods.java index 3a3b6cfb003..96fce83eab6 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PermJsonRpcMethods.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PermJsonRpcMethods.java @@ -17,18 +17,12 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning.PermAddAccountsToAllowlist; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning.PermAddAccountsToWhitelist; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning.PermAddNodesToAllowlist; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning.PermAddNodesToWhitelist; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning.PermGetAccountsAllowlist; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning.PermGetAccountsWhitelist; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning.PermGetNodesAllowlist; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning.PermGetNodesWhitelist; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning.PermReloadPermissionsFromFile; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning.PermRemoveAccountsFromAllowlist; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning.PermRemoveAccountsFromWhitelist; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning.PermRemoveNodesFromAllowlist; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning.PermRemoveNodesFromWhitelist; import org.hyperledger.besu.ethereum.permissioning.AccountLocalConfigPermissioningController; import org.hyperledger.besu.ethereum.permissioning.NodeLocalConfigPermissioningController; @@ -55,17 +49,11 @@ protected String getApiGroup() { @Override protected Map create() { return mapOf( - new PermAddNodesToWhitelist(nodeAllowlistController), new PermAddNodesToAllowlist(nodeAllowlistController), - new PermRemoveNodesFromWhitelist(nodeAllowlistController), new PermRemoveNodesFromAllowlist(nodeAllowlistController), - new PermGetNodesWhitelist(nodeAllowlistController), new PermGetNodesAllowlist(nodeAllowlistController), - new PermGetAccountsWhitelist(accountsAllowlistController), new PermGetAccountsAllowlist(accountsAllowlistController), - new PermAddAccountsToWhitelist(accountsAllowlistController), new PermAddAccountsToAllowlist(accountsAllowlistController), - new PermRemoveAccountsFromWhitelist(accountsAllowlistController), new PermRemoveAccountsFromAllowlist(accountsAllowlistController), new PermReloadPermissionsFromFile(accountsAllowlistController, nodeAllowlistController)); } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivJsonRpcMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivJsonRpcMethods.java index 0f6df40d143..8ae62741b94 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivJsonRpcMethods.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivJsonRpcMethods.java @@ -34,6 +34,9 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv.PrivGetTransactionCount; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv.PrivGetTransactionReceipt; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv.PrivNewFilter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.priv.PrivTraceTransaction; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor.PrivateBlockReplay; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.privateProcessor.PrivateBlockTracer; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -68,6 +71,9 @@ protected Map create( final PrivacyIdProvider privacyIdProvider, final PrivateMarkerTransactionFactory privateMarkerTransactionFactory) { + final PrivateBlockReplay blockReplay = + new PrivateBlockReplay( + getProtocolSchedule(), getBlockchainQueries().getBlockchain(), privacyController); final Map RPC_METHODS = mapOf( new PrivCall(getBlockchainQueries(), privacyController, privacyIdProvider), @@ -89,7 +95,15 @@ protected Map create( new PrivGetFilterLogs(filterManager, privacyController, privacyIdProvider), new PrivGetFilterChanges(filterManager, privacyController, privacyIdProvider), new PrivNewFilter(filterManager, privacyController, privacyIdProvider), - new PrivUninstallFilter(filterManager, privacyController, privacyIdProvider)); + new PrivUninstallFilter(filterManager, privacyController, privacyIdProvider), + new PrivTraceTransaction( + () -> new PrivateBlockTracer(blockReplay), + getBlockchainQueries(), + getProtocolSchedule(), + getPrivacyQueries(), + privacyController, + getPrivacyParameters(), + privacyIdProvider)); if (!getPrivacyParameters().isFlexiblePrivacyGroupsEnabled()) { final Map OFFCHAIN_METHODS = diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/TraceJsonRpcMethods.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/TraceJsonRpcMethods.java index 96faae9fd29..6a36bebfcd5 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/TraceJsonRpcMethods.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/TraceJsonRpcMethods.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.methods; +import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.ApiConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; @@ -39,13 +40,16 @@ public class TraceJsonRpcMethods extends ApiGroupJsonRpcMethods { private final ProtocolSchedule protocolSchedule; private final ApiConfiguration apiConfiguration; + private final ProtocolContext protocolContext; TraceJsonRpcMethods( final BlockchainQueries blockchainQueries, final ProtocolSchedule protocolSchedule, + final ProtocolContext protocolContext, final ApiConfiguration apiConfiguration) { this.blockchainQueries = blockchainQueries; this.protocolSchedule = protocolSchedule; + this.protocolContext = protocolContext; this.apiConfiguration = apiConfiguration; } @@ -57,10 +61,14 @@ protected String getApiGroup() { @Override protected Map create() { final BlockReplay blockReplay = - new BlockReplay(protocolSchedule, blockchainQueries.getBlockchain()); + new BlockReplay(protocolSchedule, protocolContext, blockchainQueries.getBlockchain()); return mapOf( new TraceReplayBlockTransactions(protocolSchedule, blockchainQueries), - new TraceFilter(() -> new BlockTracer(blockReplay), protocolSchedule, blockchainQueries), + new TraceFilter( + () -> new BlockTracer(blockReplay), + protocolSchedule, + blockchainQueries, + apiConfiguration.getMaxTraceFilterRange()), new TraceGet(() -> new BlockTracer(blockReplay), blockchainQueries, protocolSchedule), new TraceTransaction( () -> new BlockTracer(blockReplay), protocolSchedule, blockchainQueries), diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/JsonResponseStreamer.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/JsonResponseStreamer.java index bc216c7fc2a..dbec85a8f7e 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/JsonResponseStreamer.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/JsonResponseStreamer.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketMessageHandler.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketMessageHandler.java index 3f4995d7abd..da7baa5084c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketMessageHandler.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketMessageHandler.java @@ -20,10 +20,10 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.execution.JsonRpcExecutor; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponseType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.methods.WebSocketRpcRequest; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; import java.io.IOException; import java.util.ArrayList; @@ -31,6 +31,7 @@ import java.util.Optional; import com.fasterxml.jackson.core.JsonGenerator.Feature; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; @@ -46,6 +47,10 @@ public class WebSocketMessageHandler { + private static final ObjectMapper jsonObjectMapper = + new ObjectMapper() + .registerModule(new Jdk8Module()); // Handle JDK8 Optionals (de)serialization + private static final Logger LOG = LoggerFactory.getLogger(WebSocketMessageHandler.class); private static final ObjectWriter JSON_OBJECT_WRITER = new ObjectMapper() @@ -146,7 +151,7 @@ public void handle( jsonRpcBatchResponse.stream() .filter( jsonRpcResponse -> - jsonRpcResponse.getType() != JsonRpcResponseType.NONE) + jsonRpcResponse.getType() != RpcResponseType.NONE) .toArray(JsonRpcResponse[]::new); replyToClient(websocket, completed); }) @@ -161,6 +166,7 @@ public void handle( } private void replyToClient(final ServerWebSocket websocket, final Object result) { + traceResponse(result); try { // underlying output stream lifecycle is managed by the json object writer JSON_OBJECT_WRITER.writeValue(new JsonResponseStreamer(websocket), result); @@ -172,4 +178,12 @@ private void replyToClient(final ServerWebSocket websocket, final Object result) private JsonRpcResponse errorResponse(final Object id, final RpcErrorType error) { return new JsonRpcErrorResponse(id, error); } + + private void traceResponse(final Object response) { + try { + LOG.trace(jsonObjectMapper.writeValueAsString(response)); + } catch (JsonProcessingException e) { + LOG.error("Error tracing JSON-RPC response", e); + } + } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketService.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketService.java index 007cc3b4cba..59742836ba8 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketService.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketService.java @@ -14,7 +14,6 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.websocket; -import static com.google.common.collect.Streams.stream; import static org.hyperledger.besu.ethereum.api.jsonrpc.authentication.AuthenticationUtils.truncToken; import org.hyperledger.besu.ethereum.api.jsonrpc.authentication.AuthenticationService; @@ -31,8 +30,6 @@ import java.util.concurrent.atomic.AtomicInteger; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Splitter; -import com.google.common.collect.Iterables; import io.vertx.core.AsyncResult; import io.vertx.core.Handler; import io.vertx.core.Vertx; @@ -43,6 +40,7 @@ import io.vertx.core.http.HttpServerRequest; import io.vertx.core.http.HttpServerResponse; import io.vertx.core.http.ServerWebSocket; +import io.vertx.core.net.HostAndPort; import io.vertx.core.net.SocketAddress; import io.vertx.ext.web.Router; import io.vertx.ext.web.RoutingContext; @@ -115,7 +113,8 @@ public CompletableFuture start() { .setCompressionSupported(true) .addWebSocketSubProtocol("undefined") .setMaxWebSocketFrameSize(configuration.getMaxFrameSize()) - .setMaxWebSocketMessageSize(configuration.getMaxFrameSize() * 4)) + .setMaxWebSocketMessageSize(configuration.getMaxFrameSize() * 4) + .setRegisterWebSocketWriteHandlers(true)) .webSocketHandler(websocketHandler()) .connectionHandler(connectionHandler()) .requestHandler(httpHandler()) @@ -136,7 +135,8 @@ private Handler websocketHandler() { .log(); } - if (!hasAllowedHostnameHeader(Optional.ofNullable(websocket.headers().get("Host")))) { + if (!checkHostInAllowlist( + Optional.ofNullable(websocket.authority()).map(HostAndPort::host))) { websocket.reject(403); } @@ -293,7 +293,8 @@ private String getAuthToken(final ServerWebSocket websocket) { private Handler checkAllowlistHostHeader() { return event -> { - if (hasAllowedHostnameHeader(Optional.ofNullable(event.request().host()))) { + if (checkHostInAllowlist( + Optional.ofNullable(event.request().authority()).map(HostAndPort::host))) { event.next(); } else { final HttpServerResponse response = event.response(); @@ -308,31 +309,12 @@ private Handler checkAllowlistHostHeader() { } @VisibleForTesting - public boolean hasAllowedHostnameHeader(final Optional header) { + boolean checkHostInAllowlist(final Optional host) { return configuration.getHostsAllowlist().contains("*") - || header.map(value -> checkHostInAllowlist(validateHostHeader(value))).orElse(false); - } - - private Optional validateHostHeader(final String header) { - final Iterable splitHostHeader = Splitter.on(':').split(header); - final long hostPieces = stream(splitHostHeader).count(); - if (hostPieces > 1) { - // If the host contains a colon, verify the host is correctly formed - host [ ":" port ] - if (hostPieces > 2 || !Iterables.get(splitHostHeader, 1).matches("\\d{1,5}+")) { - return Optional.empty(); - } - } - return Optional.ofNullable(Iterables.get(splitHostHeader, 0)); - } - - private boolean checkHostInAllowlist(final Optional hostHeader) { - return hostHeader - .map( - header -> - configuration.getHostsAllowlist().stream() - .anyMatch( - allowlistEntry -> - allowlistEntry.toLowerCase().equals(header.toLowerCase()))) - .orElse(false); + || host.map( + header -> + configuration.getHostsAllowlist().stream() + .anyMatch(allowListEntry -> allowListEntry.equalsIgnoreCase(header))) + .orElse(false); } } diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/pending/PendingTransactionDetailResult.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/pending/PendingTransactionDetailResult.java index 6634ded9132..68f75ee4ecf 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/pending/PendingTransactionDetailResult.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/pending/PendingTransactionDetailResult.java @@ -73,7 +73,7 @@ public PendingTransactionDetailResult(final Transaction tx) { this.v = (transactionType == TransactionType.ACCESS_LIST || transactionType == TransactionType.EIP1559) - ? this.yParity + ? Quantity.create(tx.getYParity()) : null; } this.value = Quantity.create(tx.getValue()); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/request/SubscriptionRequestMapper.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/request/SubscriptionRequestMapper.java index b3e76fead87..8bc35798495 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/request/SubscriptionRequestMapper.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/request/SubscriptionRequestMapper.java @@ -15,8 +15,11 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.request; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.FilterParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.UnsignedLongParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.methods.WebSocketRpcRequest; import java.util.Optional; @@ -30,8 +33,15 @@ public SubscribeRequest mapSubscribeRequest(final JsonRpcRequestContext jsonRpcR try { final WebSocketRpcRequest webSocketRpcRequestBody = validateRequest(jsonRpcRequestContext); - final SubscriptionType subscriptionType = - webSocketRpcRequestBody.getRequiredParameter(0, SubscriptionType.class); + final SubscriptionType subscriptionType; + try { + subscriptionType = webSocketRpcRequestBody.getRequiredParameter(0, SubscriptionType.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid subscription type parameter (index 0)", + RpcErrorType.INVALID_SUBSCRIPTION_PARAMS, + e); + } switch (subscriptionType) { case NEW_BLOCK_HEADERS: { @@ -58,8 +68,13 @@ public SubscribeRequest mapSubscribeRequest(final JsonRpcRequestContext jsonRpcR } private boolean includeTransactions(final WebSocketRpcRequest webSocketRpcRequestBody) { - final Optional params = - webSocketRpcRequestBody.getOptionalParameter(1, SubscriptionParam.class); + final Optional params; + try { + params = webSocketRpcRequestBody.getOptionalParameter(1, SubscriptionParam.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid subscription parameter (index 1)", RpcErrorType.INVALID_SUBSCRIPTION_PARAMS, e); + } return params.isPresent() && params.get().includeTransaction(); } @@ -70,7 +85,13 @@ private SubscribeRequest parseNewBlockHeadersRequest( } private SubscribeRequest parseLogsRequest(final WebSocketRpcRequest request) { - final FilterParameter filterParameter = request.getRequiredParameter(1, FilterParameter.class); + final FilterParameter filterParameter; + try { + filterParameter = request.getRequiredParameter(1, FilterParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid filter parameters (index 1)", RpcErrorType.INVALID_FILTER_PARAMS, e); + } return new SubscribeRequest( SubscriptionType.LOGS, filterParameter, null, request.getConnectionId()); } @@ -80,8 +101,16 @@ public UnsubscribeRequest mapUnsubscribeRequest(final JsonRpcRequestContext json try { final WebSocketRpcRequest webSocketRpcRequestBody = validateRequest(jsonRpcRequestContext); - final long subscriptionId = - webSocketRpcRequestBody.getRequiredParameter(0, UnsignedLongParameter.class).getValue(); + final long subscriptionId; + try { + subscriptionId = + webSocketRpcRequestBody.getRequiredParameter(0, UnsignedLongParameter.class).getValue(); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid subscription ID parameter (index 0)", + RpcErrorType.INVALID_SUBSCRIPTION_PARAMS, + e); + } return new UnsubscribeRequest(subscriptionId, webSocketRpcRequestBody.getConnectionId()); } catch (final Exception e) { throw new InvalidSubscriptionRequestException("Error parsing unsubscribe request", e); @@ -94,15 +123,36 @@ public PrivateSubscribeRequest mapPrivateSubscribeRequest( try { final WebSocketRpcRequest webSocketRpcRequestBody = validateRequest(jsonRpcRequestContext); - final String privacyGroupId = webSocketRpcRequestBody.getRequiredParameter(0, String.class); - final SubscriptionType subscriptionType = - webSocketRpcRequestBody.getRequiredParameter(1, SubscriptionType.class); + final String privacyGroupId; + try { + privacyGroupId = webSocketRpcRequestBody.getRequiredParameter(0, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid privacy group ID parameter (index 0)", + RpcErrorType.INVALID_PRIVACY_GROUP_PARAMS, + e); + } + final SubscriptionType subscriptionType; + try { + subscriptionType = webSocketRpcRequestBody.getRequiredParameter(1, SubscriptionType.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid subscription type parameter (index 1)", + RpcErrorType.INVALID_SUBSCRIPTION_PARAMS, + e); + } switch (subscriptionType) { case LOGS: { - final FilterParameter filterParameter = - jsonRpcRequestContext.getRequiredParameter(2, FilterParameter.class); + final FilterParameter filterParameter; + try { + filterParameter = + jsonRpcRequestContext.getRequiredParameter(2, FilterParameter.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid filter parameter (index 2)", RpcErrorType.INVALID_FILTER_PARAMS, e); + } return new PrivateSubscribeRequest( SubscriptionType.LOGS, filterParameter, @@ -128,9 +178,25 @@ public PrivateUnsubscribeRequest mapPrivateUnsubscribeRequest( try { final WebSocketRpcRequest webSocketRpcRequestBody = validateRequest(jsonRpcRequestContext); - final String privacyGroupId = webSocketRpcRequestBody.getRequiredParameter(0, String.class); - final long subscriptionId = - webSocketRpcRequestBody.getRequiredParameter(1, UnsignedLongParameter.class).getValue(); + final String privacyGroupId; + try { + privacyGroupId = webSocketRpcRequestBody.getRequiredParameter(0, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid privacy group ID parameter (index 0)", + RpcErrorType.INVALID_PRIVACY_GROUP_PARAMS, + e); + } + final long subscriptionId; + try { + subscriptionId = + webSocketRpcRequestBody.getRequiredParameter(1, UnsignedLongParameter.class).getValue(); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid subscription ID parameter (index 1)", + RpcErrorType.INVALID_SUBSCRIPTION_PARAMS, + e); + } return new PrivateUnsubscribeRequest( subscriptionId, webSocketRpcRequestBody.getConnectionId(), privacyGroupId); } catch (final Exception e) { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueries.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueries.java index d5228de45e0..c03bf124c47 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueries.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueries.java @@ -30,12 +30,15 @@ import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.LogWithMetadata; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket; +import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.log.LogsBloomFilter; @@ -43,7 +46,6 @@ import java.io.EOFException; import java.io.IOException; import java.io.RandomAccessFile; -import java.math.BigInteger; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -57,52 +59,81 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.LongStream; +import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; +import org.apache.tuweni.units.bigints.UInt256s; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class BlockchainQueries { private static final Logger LOG = LoggerFactory.getLogger(BlockchainQueries.class); + private final ProtocolSchedule protocolSchedule; private final WorldStateArchive worldStateArchive; private final Blockchain blockchain; private final Optional cachePath; private final Optional transactionLogBloomCacher; private final Optional ethScheduler; private final ApiConfiguration apiConfig; + private final MiningParameters miningParameters; - public BlockchainQueries(final Blockchain blockchain, final WorldStateArchive worldStateArchive) { - this(blockchain, worldStateArchive, Optional.empty(), Optional.empty()); + public BlockchainQueries( + final ProtocolSchedule protocolSchedule, + final Blockchain blockchain, + final WorldStateArchive worldStateArchive, + final MiningParameters miningParameters) { + this( + protocolSchedule, + blockchain, + worldStateArchive, + Optional.empty(), + Optional.empty(), + miningParameters); } public BlockchainQueries( + final ProtocolSchedule protocolSchedule, final Blockchain blockchain, final WorldStateArchive worldStateArchive, - final EthScheduler scheduler) { - this(blockchain, worldStateArchive, Optional.empty(), Optional.ofNullable(scheduler)); + final EthScheduler scheduler, + final MiningParameters miningParameters) { + this( + protocolSchedule, + blockchain, + worldStateArchive, + Optional.empty(), + Optional.ofNullable(scheduler), + miningParameters); } public BlockchainQueries( + final ProtocolSchedule protocolSchedule, final Blockchain blockchain, final WorldStateArchive worldStateArchive, final Optional cachePath, - final Optional scheduler) { + final Optional scheduler, + final MiningParameters miningParameters) { this( + protocolSchedule, blockchain, worldStateArchive, cachePath, scheduler, - ImmutableApiConfiguration.builder().build()); + ImmutableApiConfiguration.builder().build(), + miningParameters); } public BlockchainQueries( + final ProtocolSchedule protocolSchedule, final Blockchain blockchain, final WorldStateArchive worldStateArchive, final Optional cachePath, final Optional scheduler, - final ApiConfiguration apiConfig) { + final ApiConfiguration apiConfig, + final MiningParameters miningParameters) { + this.protocolSchedule = protocolSchedule; this.blockchain = blockchain; this.worldStateArchive = worldStateArchive; this.cachePath = cachePath; @@ -113,6 +144,7 @@ public BlockchainQueries( new TransactionLogBloomCacher(blockchain, cachePath.get(), scheduler.get())) : Optional.empty(); this.apiConfig = apiConfig; + this.miningParameters = miningParameters; } public Blockchain getBlockchain() { @@ -943,63 +975,135 @@ public Optional getAndMapWorldState( return getAndMapWorldState(blockHash, mapper); } - public Optional gasPrice() { - final long blockHeight = headBlockNumber(); - final long[] gasCollection = - LongStream.range(Math.max(0, blockHeight - apiConfig.getGasPriceBlocks()), blockHeight) - .mapToObj( - l -> - blockchain - .getBlockByNumber(l) - .map(Block::getBody) - .map(BlockBody::getTransactions) - .orElseThrow( - () -> new IllegalStateException("Could not retrieve block #" + l))) + public Wei gasPrice() { + final Block chainHeadBlock = blockchain.getChainHeadBlock(); + final var chainHeadHeader = chainHeadBlock.getHeader(); + final long blockHeight = chainHeadHeader.getNumber(); + + final var nextBlockProtocolSpec = + protocolSchedule.getForNextBlockHeader(chainHeadHeader, System.currentTimeMillis()); + final var nextBlockFeeMarket = nextBlockProtocolSpec.getFeeMarket(); + + final Wei[] gasCollection = + Stream.concat( + LongStream.range( + Math.max(0, blockHeight - apiConfig.getGasPriceBlocks() + 1), blockHeight) + .mapToObj( + l -> + blockchain + .getBlockByNumber(l) + .orElseThrow( + () -> + new IllegalStateException( + "Could not retrieve block #" + l))), + Stream.of(chainHeadBlock)) + .map(Block::getBody) + .map(BlockBody::getTransactions) .flatMap(Collection::stream) .filter(t -> t.getGasPrice().isPresent()) - .mapToLong(t -> t.getGasPrice().get().toLong()) + .map(t -> t.getGasPrice().get()) .sorted() - .toArray(); - return (gasCollection == null || gasCollection.length == 0) - ? Optional.empty() - : Optional.of( - Math.max( - apiConfig.getGasPriceMinSupplier().getAsLong(), - Math.min( - apiConfig.getGasPriceMax(), - gasCollection[ - Math.min( - gasCollection.length - 1, - (int) ((gasCollection.length) * apiConfig.getGasPriceFraction()))]))); - } - - public Optional gasPriorityFee() { - final long blockHeight = headBlockNumber(); - final BigInteger[] gasCollection = - LongStream.range(Math.max(0, blockHeight - apiConfig.getGasPriceBlocks()), blockHeight) - .mapToObj( - l -> - blockchain - .getBlockByNumber(l) - .map(Block::getBody) - .map(BlockBody::getTransactions) - .orElseThrow( - () -> new IllegalStateException("Could not retrieve block #" + l))) - .flatMap(Collection::stream) - .filter(t -> t.getMaxPriorityFeePerGas().isPresent()) - .map(t -> t.getMaxPriorityFeePerGas().get().toBigInteger()) - .sorted(BigInteger::compareTo) - .toArray(BigInteger[]::new); - return (gasCollection.length == 0) - ? Optional.empty() - : Optional.of( - Wei.of( + .toArray(Wei[]::new); + + return gasCollection.length == 0 + ? gasPriceLowerBound(chainHeadHeader, nextBlockFeeMarket) + : UInt256s.max( + gasPriceLowerBound(chainHeadHeader, nextBlockFeeMarket), + UInt256s.min( + apiConfig.getGasPriceMax(), gasCollection[ Math.min( gasCollection.length - 1, (int) ((gasCollection.length) * apiConfig.getGasPriceFraction()))])); } + /** + * Return the min gas required for a tx to be mineable. On networks with gas price fee market it + * is just the minGasPrice, while on networks with base fee market it is the max between the + * minGasPrice and the baseFee for the next block. + * + * @return the min gas required for a tx to be mineable. + */ + public Wei gasPriceLowerBound() { + final var chainHeadHeader = blockchain.getChainHeadHeader(); + final var nextBlockProtocolSpec = + protocolSchedule.getForNextBlockHeader(chainHeadHeader, System.currentTimeMillis()); + final var nextBlockFeeMarket = nextBlockProtocolSpec.getFeeMarket(); + return gasPriceLowerBound(chainHeadHeader, nextBlockFeeMarket); + } + + private Wei gasPriceLowerBound( + final BlockHeader chainHeadHeader, final FeeMarket nextBlockFeeMarket) { + final var minGasPrice = miningParameters.getMinTransactionGasPrice(); + + if (nextBlockFeeMarket.implementsBaseFee()) { + return UInt256s.max( + getNextBlockBaseFee(chainHeadHeader, (BaseFeeMarket) nextBlockFeeMarket), minGasPrice); + } + + return minGasPrice; + } + + public Wei gasPriorityFee() { + final Block chainHeadBlock = blockchain.getChainHeadBlock(); + final long blockHeight = chainHeadBlock.getHeader().getNumber(); + + final Wei[] gasCollection = + Stream.concat( + LongStream.range( + Math.max(0, blockHeight - apiConfig.getGasPriceBlocks() + 1), blockHeight) + .mapToObj( + l -> + blockchain + .getBlockByNumber(l) + .orElseThrow( + () -> + new IllegalStateException( + "Could not retrieve block #" + l))), + Stream.of(chainHeadBlock)) + .map(Block::getBody) + .map(BlockBody::getTransactions) + .flatMap(Collection::stream) + .filter(t -> t.getMaxPriorityFeePerGas().isPresent()) + .map(t -> t.getMaxPriorityFeePerGas().get()) + .sorted() + .toArray(Wei[]::new); + + return gasCollection.length == 0 + ? miningParameters.getMinPriorityFeePerGas() + : UInt256s.max( + miningParameters.getMinPriorityFeePerGas(), + gasCollection[ + Math.min( + gasCollection.length - 1, + (int) ((gasCollection.length) * apiConfig.getGasPriceFraction()))]); + } + + /** + * Calculate and return the value of the base fee for the next block, if the network has a base + * fee market, otherwise return empty. + * + * @return the optional base fee + */ + public Optional getNextBlockBaseFee() { + final var chainHeadHeader = blockchain.getChainHeadHeader(); + final var nextBlockProtocolSpec = + protocolSchedule.getForNextBlockHeader(chainHeadHeader, System.currentTimeMillis()); + final var nextBlockFeeMarket = nextBlockProtocolSpec.getFeeMarket(); + return nextBlockFeeMarket.implementsBaseFee() + ? Optional.of(getNextBlockBaseFee(chainHeadHeader, (BaseFeeMarket) nextBlockFeeMarket)) + : Optional.empty(); + } + + private Wei getNextBlockBaseFee( + final BlockHeader chainHeadHeader, final BaseFeeMarket nextBlockFeeMarket) { + return nextBlockFeeMarket.computeBaseFee( + chainHeadHeader.getNumber() + 1, + chainHeadHeader.getBaseFee().orElse(Wei.ZERO), + chainHeadHeader.getGasUsed(), + nextBlockFeeMarket.targetGasUsed(chainHeadHeader)); + } + private Optional fromAccount( final Address address, final Hash blockHash, diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/LogsQuery.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/LogsQuery.java index 79d5f0246c5..a604721f832 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/LogsQuery.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/LogsQuery.java @@ -1,5 +1,4 @@ /* - * * Copyright ConsenSys AG. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with @@ -12,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.api.query; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/PrivacyQueries.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/PrivacyQueries.java index 604bad0c9bc..9768ead9dc5 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/PrivacyQueries.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/PrivacyQueries.java @@ -20,6 +20,7 @@ import org.hyperledger.besu.ethereum.core.LogWithMetadata; import org.hyperledger.besu.ethereum.privacy.PrivateTransactionReceipt; import org.hyperledger.besu.ethereum.privacy.PrivateWorldStateReader; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateBlockMetadata; import org.hyperledger.besu.ethereum.privacy.storage.PrivateTransactionMetadata; import java.util.Collection; @@ -43,6 +44,11 @@ public PrivacyQueries( this.privateWorldStateReader = privateWorldStateReader; } + public Optional getPrivateBlockMetaData( + final String privacyGroupId, final Hash blockHash) { + return privateWorldStateReader.getPrivateBlockMetadata(privacyGroupId, blockHash); + } + public List matchingLogs( final String privacyGroupId, final long fromBlockNumber, diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/StateBackupService.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/StateBackupService.java index 6ae5125bc5f..63f51016498 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/StateBackupService.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/StateBackupService.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.api.query; import static com.google.common.base.Preconditions.checkArgument; @@ -31,9 +29,9 @@ import org.hyperledger.besu.ethereum.trie.Node; import org.hyperledger.besu.ethereum.trie.TrieIterator; import org.hyperledger.besu.ethereum.trie.TrieIterator.State; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; import org.hyperledger.besu.util.io.RollingFileWriter; import java.io.IOException; @@ -73,7 +71,7 @@ public class StateBackupService { private final Lock submissionLock = new ReentrantLock(); private final EthScheduler scheduler; private final Blockchain blockchain; - private final WorldStateStorage worldStateStorage; + private final ForestWorldStateKeyValueStorage worldStateKeyValueStorage; private final BackupStatus backupStatus = new BackupStatus(); private Path backupDir; @@ -84,12 +82,12 @@ public StateBackupService( final Blockchain blockchain, final Path backupDir, final EthScheduler scheduler, - final WorldStateStorage worldStateStorage) { + final ForestWorldStateKeyValueStorage worldStateKeyValueStorage) { this.besuVersion = besuVersion; this.blockchain = blockchain; this.backupDir = backupDir; this.scheduler = scheduler; - this.worldStateStorage = worldStateStorage; + this.worldStateKeyValueStorage = worldStateKeyValueStorage; } public Path getBackupDir() { @@ -214,7 +212,7 @@ private void backupLeaves() throws IOException { return; } final Optional worldStateRoot = - worldStateStorage.getAccountStateTrieNode(Bytes.EMPTY, header.get().getStateRoot()); + worldStateKeyValueStorage.getAccountStateTrieNode(header.get().getStateRoot()); if (worldStateRoot.isEmpty()) { backupStatus.currentAccount = null; return; @@ -226,7 +224,7 @@ private void backupLeaves() throws IOException { final StoredMerklePatriciaTrie accountTrie = new StoredMerklePatriciaTrie<>( - worldStateStorage::getAccountStateTrieNode, + (location, hash) -> worldStateKeyValueStorage.getAccountStateTrieNode(hash), header.get().getStateRoot(), Function.identity(), Function.identity()); @@ -246,7 +244,7 @@ private TrieIterator.State visitAccount(final Bytes32 nodeKey, final Node final StateTrieAccountValue account = StateTrieAccountValue.readFrom(new BytesValueRLPInput(nodeValue, false)); - final Bytes code = worldStateStorage.getCode(account.getCodeHash(), null).orElse(Bytes.EMPTY); + final Bytes code = worldStateKeyValueStorage.getCode(account.getCodeHash()).orElse(Bytes.EMPTY); backupStatus.codeSize.addAndGet(code.size()); final BytesValueRLPOutput accountOutput = new BytesValueRLPOutput(); @@ -266,7 +264,7 @@ private TrieIterator.State visitAccount(final Bytes32 nodeKey, final Node // storage is written for each leaf, otherwise the whole trie would have to fit in memory final StoredMerklePatriciaTrie storageTrie = new StoredMerklePatriciaTrie<>( - worldStateStorage::getAccountStateTrieNode, + (location, hash) -> worldStateKeyValueStorage.getAccountStateTrieNode(hash), account.getStorageRoot(), Function.identity(), Function.identity()); @@ -311,7 +309,7 @@ private void backupChainData() throws IOException { bodyWriter.writeBytes(bodyOutput.encoded().toArrayUnsafe()); final BytesValueRLPOutput receiptsOutput = new BytesValueRLPOutput(); - receiptsOutput.writeList(receipts.get(), TransactionReceipt::writeToWithRevertReason); + receiptsOutput.writeList(receipts.get(), (r, rlpOut) -> r.writeToForStorage(rlpOut, false)); receiptsWriter.writeBytes(receiptsOutput.encoded().toArrayUnsafe()); backupStatus.storedBlock = blockNumber; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/TransactionWithMetadata.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/TransactionWithMetadata.java index 637b0f8f5ce..50318b5ac15 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/TransactionWithMetadata.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/TransactionWithMetadata.java @@ -1,5 +1,4 @@ /* - * * Copyright ConsenSys AG. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with @@ -12,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.api.query; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/cache/TransactionLogBloomCacher.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/cache/TransactionLogBloomCacher.java index 31132a4655e..270f4d63de4 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/cache/TransactionLogBloomCacher.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/query/cache/TransactionLogBloomCacher.java @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.api.query.cache; import static com.google.common.base.Preconditions.checkArgument; @@ -159,7 +157,7 @@ void cacheLogsBloomForBlockHeader( return; } final long blockNumber = blockHeader.getNumber(); - LOG.atDebug() + LOG.atTrace() .setMessage("Caching logs bloom for block {}") .addArgument(() -> "0x" + Long.toHexString(blockNumber)) .log(); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/tls/TlsClientAuthConfiguration.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/tls/TlsClientAuthConfiguration.java index c32b6e4c0b8..f765405bcaf 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/tls/TlsClientAuthConfiguration.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/tls/TlsClientAuthConfiguration.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.api.tls; import java.nio.file.Path; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/tls/TlsConfiguration.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/tls/TlsConfiguration.java index 9fa86722e08..9f9122a8b10 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/tls/TlsConfiguration.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/tls/TlsConfiguration.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.api.tls; import static java.util.Objects.requireNonNull; diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/tls/TlsConfigurationException.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/tls/TlsConfigurationException.java index 05cf3b04c6d..3aa8e8ccf34 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/tls/TlsConfigurationException.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/tls/TlsConfigurationException.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.api.tls; public class TlsConfigurationException extends RuntimeException { diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/util/ArrayNodeWrapper.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/util/ArrayNodeWrapper.java index b304d2a8d6e..bc0688d4d7f 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/util/ArrayNodeWrapper.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/util/ArrayNodeWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/util/DomainObjectDecodeUtils.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/util/DomainObjectDecodeUtils.java index 7ea2de0c016..bf9d49d4e9c 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/util/DomainObjectDecodeUtils.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/util/DomainObjectDecodeUtils.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.api.util; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcRequestException; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.encoding.EncodingContext; import org.hyperledger.besu.ethereum.core.encoding.TransactionDecoder; @@ -30,9 +31,11 @@ public static Transaction decodeRawTransaction(final String rawTransaction) Bytes txnBytes = Bytes.fromHexString(rawTransaction); return TransactionDecoder.decodeOpaqueBytes(txnBytes, EncodingContext.POOLED_TRANSACTION); } catch (final IllegalArgumentException e) { - throw new InvalidJsonRpcRequestException("Invalid raw transaction hex", e); + throw new InvalidJsonRpcRequestException( + "Invalid raw transaction hex", RpcErrorType.INVALID_TRANSACTION_PARAMS, e); } catch (final RLPException r) { - throw new InvalidJsonRpcRequestException("Invalid RLP in raw transaction hex", r); + throw new InvalidJsonRpcRequestException( + "Invalid RLP in raw transaction hex", RpcErrorType.INVALID_TRANSACTION_PARAMS, r); } } } diff --git a/ethereum/api/src/test-support/java/org/hyperledger/besu/ethereum/api/tls/KnownClientFileUtil.java b/ethereum/api/src/test-support/java/org/hyperledger/besu/ethereum/api/tls/KnownClientFileUtil.java index cd0ebf78f38..c2aa680d9db 100644 --- a/ethereum/api/src/test-support/java/org/hyperledger/besu/ethereum/api/tls/KnownClientFileUtil.java +++ b/ethereum/api/src/test-support/java/org/hyperledger/besu/ethereum/api/tls/KnownClientFileUtil.java @@ -1,5 +1,4 @@ /* - * * Copyright ConsenSys AG. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with @@ -12,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.api.tls; diff --git a/ethereum/api/src/test-support/java/org/hyperledger/besu/ethereum/api/tls/SelfSignedP12Certificate.java b/ethereum/api/src/test-support/java/org/hyperledger/besu/ethereum/api/tls/SelfSignedP12Certificate.java index c5644037abf..7f104b27068 100644 --- a/ethereum/api/src/test-support/java/org/hyperledger/besu/ethereum/api/tls/SelfSignedP12Certificate.java +++ b/ethereum/api/src/test-support/java/org/hyperledger/besu/ethereum/api/tls/SelfSignedP12Certificate.java @@ -1,5 +1,4 @@ /* - * * Copyright ConsenSys AG. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with @@ -12,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.api.tls; diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/AbstractEthGraphQLHttpServiceTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/AbstractEthGraphQLHttpServiceTest.java index 59da8e18b75..5a1c23d40ee 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/AbstractEthGraphQLHttpServiceTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/AbstractEthGraphQLHttpServiceTest.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.ethereum.api.graphql; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import org.hyperledger.besu.datatypes.TransactionType; @@ -22,9 +23,11 @@ import org.hyperledger.besu.ethereum.api.ImmutableApiConfiguration; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.blockcreation.PoWMiningCoordinator; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; import org.hyperledger.besu.ethereum.core.DefaultSyncStatus; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Synchronizer; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.eth.EthProtocol; @@ -34,8 +37,8 @@ import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Capability; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; import org.hyperledger.besu.plugin.data.SyncStatus; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import java.nio.file.Path; import java.util.Collections; @@ -56,7 +59,7 @@ import org.mockito.Mockito; public abstract class AbstractEthGraphQLHttpServiceTest { - @TempDir private static Path tempDir; + @TempDir private Path tempDir; private static BlockchainSetupUtil blockchainSetupUtil; @@ -79,14 +82,13 @@ public static void setupConstants() { @BeforeEach public void setupTest() throws Exception { - final Synchronizer synchronizerMock = Mockito.mock(Synchronizer.class); + final Synchronizer synchronizerMock = mock(Synchronizer.class); final SyncStatus status = new DefaultSyncStatus(1, 2, 3, Optional.of(4L), Optional.of(5L)); when(synchronizerMock.getSyncStatus()).thenReturn(Optional.of(status)); - final PoWMiningCoordinator miningCoordinatorMock = Mockito.mock(PoWMiningCoordinator.class); - when(miningCoordinatorMock.getMinTransactionGasPrice()).thenReturn(Wei.of(16)); + final PoWMiningCoordinator miningCoordinatorMock = mock(PoWMiningCoordinator.class); - final TransactionPool transactionPoolMock = Mockito.mock(TransactionPool.class); + final TransactionPool transactionPoolMock = mock(TransactionPool.class); when(transactionPoolMock.addTransactionViaApi(ArgumentMatchers.any(Transaction.class))) .thenReturn(ValidationResult.valid()); @@ -108,14 +110,16 @@ public void setupTest() throws Exception { final MutableBlockchain blockchain = blockchainSetupUtil.getBlockchain(); ProtocolContext context = new ProtocolContext( - blockchain, blockchainSetupUtil.getWorldArchive(), null, Optional.empty()); + blockchain, blockchainSetupUtil.getWorldArchive(), null, new BadBlockManager()); final BlockchainQueries blockchainQueries = new BlockchainQueries( + blockchainSetupUtil.getProtocolSchedule(), context.getBlockchain(), context.getWorldStateArchive(), Optional.empty(), Optional.empty(), - ImmutableApiConfiguration.builder().gasPriceMinSupplier(() -> 0).build()); + ImmutableApiConfiguration.builder().build(), + MiningParameters.newDefault().setMinTransactionGasPrice(Wei.ZERO)); final Set supportedCapabilities = new HashSet<>(); supportedCapabilities.add(EthProtocol.ETH62); @@ -146,7 +150,7 @@ public void setupTest() throws Exception { synchronizerMock, GraphQLContextType.GAS_CAP, 0L), - Mockito.mock(EthScheduler.class)); + mock(EthScheduler.class)); service.start().join(); client = new OkHttpClient(); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/EthGraphQLHttpBySpecTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/EthGraphQLHttpBySpecTest.java index 4cd31646007..df620d7c1c0 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/EthGraphQLHttpBySpecTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/EthGraphQLHttpBySpecTest.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.api.graphql; import static com.google.common.base.Preconditions.checkState; +import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; import java.net.URISyntaxException; @@ -31,6 +32,7 @@ import okhttp3.RequestBody; import okhttp3.Response; import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -83,4 +85,11 @@ private void graphQLCall(final String name) throws IOException { Assertions.assertThat(resp.code()).isEqualTo(expectedStatusCode); } } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLHttpServiceCorsTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLHttpServiceCorsTest.java index 1b86c21e130..0e008568dc9 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLHttpServiceCorsTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLHttpServiceCorsTest.java @@ -14,6 +14,10 @@ */ package org.hyperledger.besu.ethereum.api.graphql; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.blockcreation.PoWMiningCoordinator; import org.hyperledger.besu.ethereum.core.Synchronizer; @@ -38,10 +42,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.mockito.Mockito; public class GraphQLHttpServiceCorsTest { - @TempDir private static Path folder; + @TempDir private Path folder; private final Vertx vertx = Vertx.vertx(); private final OkHttpClient client = new OkHttpClient(); @@ -208,10 +211,11 @@ private GraphQLHttpService createGraphQLHttpServiceWithAllowedDomains( config.setCorsAllowedDomains(Lists.newArrayList(corsAllowedDomains)); } - final BlockchainQueries blockchainQueries = Mockito.mock(BlockchainQueries.class); - final Synchronizer synchronizer = Mockito.mock(Synchronizer.class); + final BlockchainQueries blockchainQueries = mock(BlockchainQueries.class); + when(blockchainQueries.gasPriorityFee()).thenReturn(Wei.ONE); + final Synchronizer synchronizer = mock(Synchronizer.class); - final PoWMiningCoordinator miningCoordinatorMock = Mockito.mock(PoWMiningCoordinator.class); + final PoWMiningCoordinator miningCoordinatorMock = mock(PoWMiningCoordinator.class); // mock graphql context final Map graphQLContextMap = @@ -219,7 +223,7 @@ private GraphQLHttpService createGraphQLHttpServiceWithAllowedDomains( GraphQLContextType.BLOCKCHAIN_QUERIES, blockchainQueries, GraphQLContextType.TRANSACTION_POOL, - Mockito.mock(TransactionPool.class), + mock(TransactionPool.class), GraphQLContextType.MINING_COORDINATOR, miningCoordinatorMock, GraphQLContextType.SYNCHRONIZER, @@ -233,7 +237,7 @@ private GraphQLHttpService createGraphQLHttpServiceWithAllowedDomains( final GraphQLHttpService graphQLHttpService = new GraphQLHttpService( - vertx, folder, config, graphQL, graphQLContextMap, Mockito.mock(EthScheduler.class)); + vertx, folder, config, graphQL, graphQLContextMap, mock(EthScheduler.class)); graphQLHttpService.start().join(); return graphQLHttpService; diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLHttpServiceHostWhitelistTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLHttpServiceHostWhitelistTest.java index 5b083a79580..90833c42a5c 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLHttpServiceHostWhitelistTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLHttpServiceHostWhitelistTest.java @@ -14,6 +14,10 @@ */ package org.hyperledger.besu.ethereum.api.graphql; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.blockcreation.PoWMiningCoordinator; import org.hyperledger.besu.ethereum.core.Synchronizer; @@ -42,11 +46,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.mockito.Mockito; public class GraphQLHttpServiceHostWhitelistTest { - @TempDir private static Path folder; + @TempDir private Path folder; protected static Vertx vertx; @@ -69,17 +72,18 @@ public void initServerAndClient() throws Exception { } private GraphQLHttpService createGraphQLHttpService() throws Exception { - final BlockchainQueries blockchainQueries = Mockito.mock(BlockchainQueries.class); - final Synchronizer synchronizer = Mockito.mock(Synchronizer.class); + final BlockchainQueries blockchainQueries = mock(BlockchainQueries.class); + when(blockchainQueries.gasPriorityFee()).thenReturn(Wei.ONE); + final Synchronizer synchronizer = mock(Synchronizer.class); - final PoWMiningCoordinator miningCoordinatorMock = Mockito.mock(PoWMiningCoordinator.class); + final PoWMiningCoordinator miningCoordinatorMock = mock(PoWMiningCoordinator.class); final Map graphQLContextMap = Map.of( GraphQLContextType.BLOCKCHAIN_QUERIES, blockchainQueries, GraphQLContextType.TRANSACTION_POOL, - Mockito.mock(TransactionPool.class), + mock(TransactionPool.class), GraphQLContextType.MINING_COORDINATOR, miningCoordinatorMock, GraphQLContextType.SYNCHRONIZER, @@ -92,7 +96,7 @@ private GraphQLHttpService createGraphQLHttpService() throws Exception { final GraphQL graphQL = GraphQLProvider.buildGraphQL(dataFetchers); return new GraphQLHttpService( - vertx, folder, graphQLConfig, graphQL, graphQLContextMap, Mockito.mock(EthScheduler.class)); + vertx, folder, graphQLConfig, graphQL, graphQLContextMap, mock(EthScheduler.class)); } private static GraphQLConfiguration createGraphQLConfig() { @@ -155,8 +159,8 @@ private int doRequest(final String hostname) throws IOException { @Test public void requestWithMalformedHostIsRejected() throws IOException { graphQLConfig.setHostsAllowlist(hostsWhitelist); - Assertions.assertThat(doRequest("ally:friend")).isEqualTo(403); - Assertions.assertThat(doRequest("ally:123456")).isEqualTo(403); - Assertions.assertThat(doRequest("ally:friend:1234")).isEqualTo(403); + Assertions.assertThat(doRequest("ally:friend")).isEqualTo(400); + Assertions.assertThat(doRequest("ally:123456")).isEqualTo(400); + Assertions.assertThat(doRequest("ally:friend:1234")).isEqualTo(400); } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLHttpServiceTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLHttpServiceTest.java index b5d0463883b..433472af569 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLHttpServiceTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/GraphQLHttpServiceTest.java @@ -59,6 +59,7 @@ public class GraphQLHttpServiceTest { + // this tempDir is deliberately static @TempDir private static Path folder; private static final Vertx vertx = Vertx.vertx(); @@ -215,7 +216,7 @@ public void handleInvalidQuerySchema() throws Exception { @Test public void query_get() throws Exception { final Wei price = Wei.of(16); - Mockito.when(blockchainQueries.gasPrice()).thenReturn(Optional.of(price.toLong())); + Mockito.when(blockchainQueries.gasPrice()).thenReturn(price); Mockito.when(miningCoordinatorMock.getMinTransactionGasPrice()).thenReturn(price); try (final Response resp = client.newCall(buildGetRequest("?query={gasPrice}")).execute()) { diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/TransactionDataFetcherTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/TransactionDataFetcherTest.java index aa780d42ccd..362216b9042 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/TransactionDataFetcherTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/TransactionDataFetcherTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/scalar/Bytes32ScalarTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/scalar/Bytes32ScalarTest.java index 165a8afc422..4f74fd103af 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/scalar/Bytes32ScalarTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/graphql/scalar/Bytes32ScalarTest.java @@ -17,6 +17,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.VersionedHash; import org.hyperledger.besu.ethereum.api.graphql.internal.Scalars; import java.util.Locale; @@ -42,6 +44,8 @@ public class Bytes32ScalarTest { private final StringValue strValue = StringValue.newStringValue(str).build(); private final StringValue invalidStrValue = StringValue.newStringValue("0xgh").build(); + private final VersionedHash versionedHash = new VersionedHash((byte) 1, Hash.hash(value)); + @Test public void pareValueTest() { final var result = @@ -121,6 +125,15 @@ public void parseLiteralErrorTest2() { .isInstanceOf(CoercingParseLiteralException.class); } + @Test + public void parseVersionedHash() { + assertThat( + scalar + .getCoercing() + .serialize(versionedHash, GraphQLContext.newContext().build(), Locale.ENGLISH)) + .isEqualTo(versionedHash.toString()); + } + @BeforeEach public void before() { scalar = Scalars.bytes32Scalar(); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/AbstractJsonRpcHttpServiceTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/AbstractJsonRpcHttpServiceTest.java index e43c39db4df..d504e3bb0d4 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/AbstractJsonRpcHttpServiceTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/AbstractJsonRpcHttpServiceTest.java @@ -23,6 +23,7 @@ import org.hyperledger.besu.config.StubGenesisConfigOptions; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.ApiConfiguration; +import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.health.HealthService; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.filter.FilterIdGenerator; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.filter.FilterManager; @@ -45,10 +46,10 @@ import org.hyperledger.besu.ethereum.p2p.network.P2PNetwork; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Capability; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; import org.hyperledger.besu.nat.NatService; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.testutil.BlockTestUtil.ChainResources; import java.math.BigInteger; @@ -75,7 +76,9 @@ public abstract class AbstractJsonRpcHttpServiceTest { protected BlockchainSetupUtil blockchainSetupUtil; - protected static String CLIENT_VERSION = "TestClientVersion/0.1.0"; + protected static final String CLIENT_NODE_NAME = "TestClientVersion/0.1.0"; + protected static final String CLIENT_VERSION = "0.1.0"; + protected static final String CLIENT_COMMIT = "12345678"; protected static final BigInteger NETWORK_ID = BigInteger.valueOf(123); protected static final Collection JSON_RPC_APIS = Arrays.asList( @@ -145,7 +148,10 @@ protected Map getRpcMethods( final BlockchainQueries blockchainQueries = new BlockchainQueries( - blockchainSetupUtil.getBlockchain(), blockchainSetupUtil.getWorldArchive()); + blockchainSetupUtil.getProtocolSchedule(), + blockchainSetupUtil.getBlockchain(), + blockchainSetupUtil.getWorldArchive(), + miningParameters); final FilterIdGenerator filterIdGenerator = mock(FilterIdGenerator.class); final FilterRepository filterRepository = new FilterRepository(); when(filterIdGenerator.nextId()).thenReturn("0x1"); @@ -165,7 +171,9 @@ protected Map getRpcMethods( return new JsonRpcMethodsFactory() .methods( + CLIENT_NODE_NAME, CLIENT_VERSION, + CLIENT_COMMIT, NETWORK_ID, new StubGenesisConfigOptions(), peerDiscoveryMock, @@ -186,6 +194,7 @@ protected Map getRpcMethods( config, mock(WebSocketConfiguration.class), mock(MetricsConfiguration.class), + mock(GraphQLConfiguration.class), natService, new HashMap<>(), folder, diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/AdminJsonRpcHttpServiceTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/AdminJsonRpcHttpServiceTest.java index 061d2a2d6a3..4a03b2eb938 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/AdminJsonRpcHttpServiceTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/AdminJsonRpcHttpServiceTest.java @@ -58,13 +58,13 @@ public void getPeers() throws Exception { final List peerList = new ArrayList<>(); final PeerInfo info1 = new PeerInfo( - 4, CLIENT_VERSION, caps, 30302, Bytes.fromHexString(String.format("%0128x", 1))); + 4, CLIENT_NODE_NAME, caps, 30302, Bytes.fromHexString(String.format("%0128x", 1))); final PeerInfo info2 = new PeerInfo( - 4, CLIENT_VERSION, caps, 60302, Bytes.fromHexString(String.format("%0128x", 2))); + 4, CLIENT_NODE_NAME, caps, 60302, Bytes.fromHexString(String.format("%0128x", 2))); final PeerInfo info3 = new PeerInfo( - 4, CLIENT_VERSION, caps, 60303, Bytes.fromHexString(String.format("%0128x", 3))); + 4, CLIENT_NODE_NAME, caps, 60303, Bytes.fromHexString(String.format("%0128x", 3))); final InetSocketAddress addr30301 = new InetSocketAddress("localhost", 30301); final InetSocketAddress addr30302 = new InetSocketAddress("localhost", 30302); final InetSocketAddress addr60301 = new InetSocketAddress("localhost", 60301); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonResponseStreamerTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonResponseStreamerTest.java index 67e692de2e7..04863cbd63f 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonResponseStreamerTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonResponseStreamerTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceCorsTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceCorsTest.java index b090869bb5c..8ee1d33b792 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceCorsTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceCorsTest.java @@ -35,7 +35,7 @@ import org.junit.jupiter.api.io.TempDir; public class JsonRpcHttpServiceCorsTest { - @TempDir private static Path folder; + @TempDir private Path folder; private final Vertx vertx = Vertx.vertx(); private final OkHttpClient client = new OkHttpClient(); @@ -166,6 +166,18 @@ public void requestWithAnyOriginShouldSucceedWhenCorsIsStart() throws Exception } } + @Test + public void requestWithAnyOriginAndEmptyActualOriginShouldSucceed() throws Exception { + jsonRpcHttpService = createJsonRpcHttpServiceWithAllowedDomains("*"); + + final Request request = + new Request.Builder().url(jsonRpcHttpService.url()).header("Origin", "").build(); + + try (final Response response = client.newCall(request).execute()) { + assertThat(response.isSuccessful()).isTrue(); + } + } + @Test public void requestFromBrowserExtensionShouldSucceedWhenCorsIsStar() throws Exception { jsonRpcHttpService = createJsonRpcHttpServiceWithAllowedDomains("*"); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceHostAllowlistTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceHostAllowlistTest.java index da7319282fb..022aa244220 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceHostAllowlistTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceHostAllowlistTest.java @@ -17,11 +17,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.DEFAULT_RPC_APIS; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; import org.hyperledger.besu.config.StubGenesisConfigOptions; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.ApiConfiguration; +import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.health.HealthService; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.filter.FilterManager; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; @@ -29,6 +29,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.blockcreation.PoWMiningCoordinator; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.Synchronizer; @@ -79,7 +80,9 @@ public class JsonRpcHttpServiceHostAllowlistTest { private static OkHttpClient client; private static String baseUrl; private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); - private static final String CLIENT_VERSION = "TestClientVersion/0.1.0"; + private static final String CLIENT_NODE_NAME = "TestClientVersion/0.1.0"; + private static final String CLIENT_VERSION = "0.1.0"; + private static final String CLIENT_COMMIT = "12345678"; private static final BigInteger CHAIN_ID = BigInteger.valueOf(123); private final JsonRpcConfiguration jsonRpcConfig = createJsonRpcConfig(); @@ -98,38 +101,44 @@ public void initServerAndClient() throws Exception { supportedCapabilities.add(EthProtocol.ETH63); rpcMethods = - spy( - new JsonRpcMethodsFactory() - .methods( - CLIENT_VERSION, - CHAIN_ID, - new StubGenesisConfigOptions(), - peerDiscoveryMock, - blockchainQueries, - synchronizer, - MainnetProtocolSchedule.fromConfig( - new StubGenesisConfigOptions().constantinopleBlock(0).chainId(CHAIN_ID)), - mock(ProtocolContext.class), - mock(FilterManager.class), - mock(TransactionPool.class), - mock(MiningParameters.class), - mock(PoWMiningCoordinator.class), - new NoOpMetricsSystem(), - supportedCapabilities, - Optional.of(mock(AccountLocalConfigPermissioningController.class)), - Optional.of(mock(NodeLocalConfigPermissioningController.class)), - DEFAULT_RPC_APIS, - mock(PrivacyParameters.class), - mock(JsonRpcConfiguration.class), - mock(WebSocketConfiguration.class), - mock(MetricsConfiguration.class), - natService, - new HashMap<>(), - folder, - mock(EthPeers.class), - vertx, - mock(ApiConfiguration.class), - Optional.empty())); + new JsonRpcMethodsFactory() + .methods( + CLIENT_NODE_NAME, + CLIENT_VERSION, + CLIENT_COMMIT, + CHAIN_ID, + new StubGenesisConfigOptions(), + peerDiscoveryMock, + blockchainQueries, + synchronizer, + MainnetProtocolSchedule.fromConfig( + new StubGenesisConfigOptions().constantinopleBlock(0).chainId(CHAIN_ID), + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()), + mock(ProtocolContext.class), + mock(FilterManager.class), + mock(TransactionPool.class), + mock(MiningParameters.class), + mock(PoWMiningCoordinator.class), + new NoOpMetricsSystem(), + supportedCapabilities, + Optional.of(mock(AccountLocalConfigPermissioningController.class)), + Optional.of(mock(NodeLocalConfigPermissioningController.class)), + DEFAULT_RPC_APIS, + mock(PrivacyParameters.class), + mock(JsonRpcConfiguration.class), + mock(WebSocketConfiguration.class), + mock(MetricsConfiguration.class), + mock(GraphQLConfiguration.class), + natService, + new HashMap<>(), + folder, + mock(EthPeers.class), + vertx, + mock(ApiConfiguration.class), + Optional.empty()); service = createJsonRpcHttpService(); service.start().join(); @@ -205,8 +214,8 @@ private int doRequest(final String hostname) throws IOException { @Test public void requestWithMalformedHostIsRejected() throws IOException { jsonRpcConfig.setHostsAllowlist(hostsAllowlist); - assertThat(doRequest("ally:friend")).isEqualTo(403); - assertThat(doRequest("ally:123456")).isEqualTo(403); - assertThat(doRequest("ally:friend:1234")).isEqualTo(403); + assertThat(doRequest("ally:friend")).isEqualTo(400); + assertThat(doRequest("ally:123456")).isEqualTo(400); + assertThat(doRequest("ally:friend:1234")).isEqualTo(400); } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceLoginTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceLoginTest.java index a00e7ed7cb8..f9dba0bc260 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceLoginTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceLoginTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -19,11 +19,11 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.util.Lists.list; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; import org.hyperledger.besu.config.StubGenesisConfigOptions; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.ApiConfiguration; +import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.health.HealthService; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.filter.FilterManager; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.EthAccounts; @@ -36,6 +36,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.blockcreation.PoWMiningCoordinator; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.Synchronizer; @@ -90,6 +91,7 @@ @ExtendWith(MockitoExtension.class) public class JsonRpcHttpServiceLoginTest { + // this tempDir is deliberately static @TempDir private static Path folder; private static final Vertx vertx = Vertx.vertx(); @@ -99,7 +101,9 @@ public class JsonRpcHttpServiceLoginTest { protected static OkHttpClient client; protected static String baseUrl; protected static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); - protected static final String CLIENT_VERSION = "TestClientVersion/0.1.0"; + protected static final String CLIENT_NODE_NAME = "TestClientVersion/0.1.0"; + protected static final String CLIENT_VERSION = "0.1.0"; + protected static final String CLIENT_COMMIT = "12345678"; protected static final BigInteger CHAIN_ID = BigInteger.valueOf(123); protected static P2PNetwork peerDiscoveryMock; protected static BlockchainQueries blockchainQueries; @@ -128,37 +132,44 @@ public static void initServerAndClient() throws Exception { new StubGenesisConfigOptions().constantinopleBlock(0).chainId(CHAIN_ID); rpcMethods = - spy( - new JsonRpcMethodsFactory() - .methods( - CLIENT_VERSION, - CHAIN_ID, + new JsonRpcMethodsFactory() + .methods( + CLIENT_NODE_NAME, + CLIENT_VERSION, + CLIENT_COMMIT, + CHAIN_ID, + genesisConfigOptions, + peerDiscoveryMock, + blockchainQueries, + synchronizer, + MainnetProtocolSchedule.fromConfig( genesisConfigOptions, - peerDiscoveryMock, - blockchainQueries, - synchronizer, - MainnetProtocolSchedule.fromConfig(genesisConfigOptions), - mock(ProtocolContext.class), - mock(FilterManager.class), - mock(TransactionPool.class), - mock(MiningParameters.class), - mock(PoWMiningCoordinator.class), - new NoOpMetricsSystem(), - supportedCapabilities, - Optional.empty(), - Optional.empty(), - JSON_RPC_APIS, - mock(PrivacyParameters.class), - mock(JsonRpcConfiguration.class), - mock(WebSocketConfiguration.class), - mock(MetricsConfiguration.class), - natService, - new HashMap<>(), - folder, - mock(EthPeers.class), - vertx, - mock(ApiConfiguration.class), - Optional.empty())); + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()), + mock(ProtocolContext.class), + mock(FilterManager.class), + mock(TransactionPool.class), + mock(MiningParameters.class), + mock(PoWMiningCoordinator.class), + new NoOpMetricsSystem(), + supportedCapabilities, + Optional.empty(), + Optional.empty(), + JSON_RPC_APIS, + mock(PrivacyParameters.class), + mock(JsonRpcConfiguration.class), + mock(WebSocketConfiguration.class), + mock(MetricsConfiguration.class), + mock(GraphQLConfiguration.class), + natService, + new HashMap<>(), + folder, + mock(EthPeers.class), + vertx, + mock(ApiConfiguration.class), + Optional.empty()); service = createJsonRpcHttpService(); jwtAuth = service.authenticationService.get().getJwtAuthProvider(); service.start().join(); @@ -203,6 +214,18 @@ public static void shutdownServer() { service.stop().join(); } + @Test + public void loginWithEmptyCredentials() throws IOException { + final RequestBody body = RequestBody.create("{}", JSON); + final Request request = new Request.Builder().post(body).url(baseUrl + "/login").build(); + try (final Response resp = client.newCall(request).execute()) { + assertThat(resp.code()).isEqualTo(400); + assertThat(resp.message()).isEqualTo("Bad Request"); + final String bodyString = resp.body().string(); + assertThat(bodyString).containsIgnoringCase("username and password are required"); + } + } + @Test public void loginWithBadCredentials() throws IOException { final RequestBody body = @@ -211,6 +234,8 @@ public void loginWithBadCredentials() throws IOException { try (final Response resp = client.newCall(request).execute()) { assertThat(resp.code()).isEqualTo(401); assertThat(resp.message()).isEqualTo("Unauthorized"); + final String bodyString = resp.body().string(); + assertThat(bodyString).containsIgnoringCase("the username or password is incorrect"); } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceParameterizedTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceParameterizedTest.java index cbbd478100a..a561079e7cf 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceParameterizedTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceParameterizedTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -27,6 +27,7 @@ import okhttp3.Response; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -60,4 +61,11 @@ public void invalidJsonShouldReturnParseError(final String requestJson) throws E json, null, expectedError.getCode(), expectedError.getMessage()); } } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceRpcApisTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceRpcApisTest.java index b259d9d3ab0..63ae1b8bfb2 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceRpcApisTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceRpcApisTest.java @@ -17,7 +17,6 @@ import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import org.hyperledger.besu.config.StubGenesisConfigOptions; @@ -25,6 +24,7 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.ApiConfiguration; +import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.health.HealthService; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.filter.FilterManager; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; @@ -88,14 +88,16 @@ @ExtendWith(MockitoExtension.class) public class JsonRpcHttpServiceRpcApisTest { - @TempDir private static Path folder; + @TempDir private Path folder; private final Vertx vertx = Vertx.vertx(); private final OkHttpClient client = new OkHttpClient(); private JsonRpcHttpService service; private static String baseUrl; private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); - private static final String CLIENT_VERSION = "TestClientVersion/0.1.0"; + private static final String CLIENT_NODE_NAME = "TestClientVersion/0.1.0"; + private static final String CLIENT_VERSION = "0.1.0"; + private static final String CLIENT_COMMIT = "12345678"; private static final BigInteger NETWORK_ID = BigInteger.valueOf(123); private JsonRpcConfiguration configuration; private static final List netServices = @@ -201,37 +203,39 @@ private JsonRpcHttpService createJsonRpcHttpServiceWithRpcApis(final JsonRpcConf supportedCapabilities.add(EthProtocol.ETH63); final Map rpcMethods = - spy( - new JsonRpcMethodsFactory() - .methods( - CLIENT_VERSION, - NETWORK_ID, - new StubGenesisConfigOptions(), - mock(P2PNetwork.class), - blockchainQueries, - mock(Synchronizer.class), - ProtocolScheduleFixture.MAINNET, - mock(ProtocolContext.class), - mock(FilterManager.class), - mock(TransactionPool.class), - mock(MiningParameters.class), - mock(PoWMiningCoordinator.class), - new NoOpMetricsSystem(), - supportedCapabilities, - Optional.of(mock(AccountLocalConfigPermissioningController.class)), - Optional.of(mock(NodeLocalConfigPermissioningController.class)), - config.getRpcApis(), - mock(PrivacyParameters.class), - mock(JsonRpcConfiguration.class), - mock(WebSocketConfiguration.class), - mock(MetricsConfiguration.class), - natService, - new HashMap<>(), - folder, - mock(EthPeers.class), - vertx, - mock(ApiConfiguration.class), - Optional.empty())); + new JsonRpcMethodsFactory() + .methods( + CLIENT_NODE_NAME, + CLIENT_VERSION, + CLIENT_COMMIT, + NETWORK_ID, + new StubGenesisConfigOptions(), + mock(P2PNetwork.class), + blockchainQueries, + mock(Synchronizer.class), + ProtocolScheduleFixture.MAINNET, + mock(ProtocolContext.class), + mock(FilterManager.class), + mock(TransactionPool.class), + mock(MiningParameters.class), + mock(PoWMiningCoordinator.class), + new NoOpMetricsSystem(), + supportedCapabilities, + Optional.of(mock(AccountLocalConfigPermissioningController.class)), + Optional.of(mock(NodeLocalConfigPermissioningController.class)), + config.getRpcApis(), + mock(PrivacyParameters.class), + mock(JsonRpcConfiguration.class), + mock(WebSocketConfiguration.class), + mock(MetricsConfiguration.class), + mock(GraphQLConfiguration.class), + natService, + new HashMap<>(), + folder, + mock(EthPeers.class), + vertx, + mock(ApiConfiguration.class), + Optional.empty()); final JsonRpcHttpService jsonRpcHttpService = new JsonRpcHttpService( vertx, @@ -302,8 +306,7 @@ private JsonRpcHttpService createJsonRpcHttpService( final WebSocketConfiguration webSocketConfiguration, final P2PNetwork p2pNetwork, final MetricsConfiguration metricsConfiguration, - final NatService natService) - throws Exception { + final NatService natService) { final Set supportedCapabilities = new HashSet<>(); supportedCapabilities.add(EthProtocol.ETH62); supportedCapabilities.add(EthProtocol.ETH63); @@ -311,37 +314,39 @@ private JsonRpcHttpService createJsonRpcHttpService( webSocketConfiguration.setPort(0); final Map rpcMethods = - spy( - new JsonRpcMethodsFactory() - .methods( - CLIENT_VERSION, - NETWORK_ID, - new StubGenesisConfigOptions(), - p2pNetwork, - blockchainQueries, - mock(Synchronizer.class), - ProtocolScheduleFixture.MAINNET, - mock(ProtocolContext.class), - mock(FilterManager.class), - mock(TransactionPool.class), - mock(MiningParameters.class), - mock(PoWMiningCoordinator.class), - new NoOpMetricsSystem(), - supportedCapabilities, - Optional.of(mock(AccountLocalConfigPermissioningController.class)), - Optional.of(mock(NodeLocalConfigPermissioningController.class)), - jsonRpcConfiguration.getRpcApis(), - mock(PrivacyParameters.class), - jsonRpcConfiguration, - webSocketConfiguration, - metricsConfiguration, - natService, - new HashMap<>(), - folder, - mock(EthPeers.class), - vertx, - mock(ApiConfiguration.class), - Optional.empty())); + new JsonRpcMethodsFactory() + .methods( + CLIENT_NODE_NAME, + CLIENT_VERSION, + CLIENT_COMMIT, + NETWORK_ID, + new StubGenesisConfigOptions(), + p2pNetwork, + blockchainQueries, + mock(Synchronizer.class), + ProtocolScheduleFixture.MAINNET, + mock(ProtocolContext.class), + mock(FilterManager.class), + mock(TransactionPool.class), + mock(MiningParameters.class), + mock(PoWMiningCoordinator.class), + new NoOpMetricsSystem(), + supportedCapabilities, + Optional.of(mock(AccountLocalConfigPermissioningController.class)), + Optional.of(mock(NodeLocalConfigPermissioningController.class)), + jsonRpcConfiguration.getRpcApis(), + mock(PrivacyParameters.class), + jsonRpcConfiguration, + webSocketConfiguration, + metricsConfiguration, + mock(GraphQLConfiguration.class), + natService, + new HashMap<>(), + folder, + mock(EthPeers.class), + vertx, + mock(ApiConfiguration.class), + Optional.empty()); final JsonRpcHttpService jsonRpcHttpService = new JsonRpcHttpService( vertx, @@ -425,8 +430,7 @@ public RequestBody createNetServicesRequestBody() { "{\"jsonrpc\":\"2.0\",\"id\":" + Json.encode(id) + ",\"method\":\"net_services\"}", JSON); } - public JsonRpcHttpService getJsonRpcHttpService(final boolean[] enabledNetServices) - throws Exception { + public JsonRpcHttpService getJsonRpcHttpService(final boolean[] enabledNetServices) { JsonRpcConfiguration jsonRpcConfiguration = JsonRpcConfiguration.createDefault(); WebSocketConfiguration webSocketConfiguration = WebSocketConfiguration.createDefault(); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTest.java index f9c9b14ccd0..52f2ee050cd 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTest.java @@ -17,10 +17,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import org.hyperledger.besu.datatypes.Address; @@ -46,6 +43,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -204,7 +202,7 @@ public void web3ClientVersionSuccessful() throws Exception { testHelper.assertValidJsonRpcResult(json, id); // Check result final String result = json.getString("result"); - assertThat(result).isEqualTo(CLIENT_VERSION); + assertThat(result).isEqualTo(CLIENT_NODE_NAME); } } @@ -762,7 +760,7 @@ public void getBlockByHashWithMissingHashParameter() throws Exception { assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); - final RpcErrorType expectedError = RpcErrorType.INVALID_PARAMS; + final RpcErrorType expectedError = RpcErrorType.INVALID_BLOCK_HASH_PARAMS; testHelper.assertValidJsonRpcError( json, id, expectedError.getCode(), expectedError.getMessage()); } @@ -786,7 +784,7 @@ public void getBlockByHashWithMissingBooleanParameter() throws Exception { assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); - final RpcErrorType expectedError = RpcErrorType.INVALID_PARAMS; + final RpcErrorType expectedError = RpcErrorType.INVALID_RETURN_COMPLETE_TRANSACTION_PARAMS; testHelper.assertValidJsonRpcError( json, id, expectedError.getCode(), expectedError.getMessage()); } @@ -809,7 +807,7 @@ public void getBlockByHashWithInvalidHashParameterWithOddLength() throws Excepti assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); - final RpcErrorType expectedError = RpcErrorType.INVALID_PARAMS; + final RpcErrorType expectedError = RpcErrorType.INVALID_BLOCK_HASH_PARAMS; testHelper.assertValidJsonRpcError( json, id, expectedError.getCode(), expectedError.getMessage()); } @@ -832,7 +830,7 @@ public void getBlockByHashWithInvalidHashParameterThatIsTooShort() throws Except assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); - final RpcErrorType expectedError = RpcErrorType.INVALID_PARAMS; + final RpcErrorType expectedError = RpcErrorType.INVALID_BLOCK_HASH_PARAMS; testHelper.assertValidJsonRpcError( json, id, expectedError.getCode(), expectedError.getMessage()); } @@ -856,7 +854,7 @@ public void getBlockByHashWithInvalidBooleanParameter() throws Exception { assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); - final RpcErrorType expectedError = RpcErrorType.INVALID_PARAMS; + final RpcErrorType expectedError = RpcErrorType.INVALID_RETURN_COMPLETE_TRANSACTION_PARAMS; testHelper.assertValidJsonRpcError( json, id, expectedError.getCode(), expectedError.getMessage()); } @@ -876,7 +874,7 @@ public void getBlockByHashWithAllParametersMissing() throws Exception { assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); - final RpcErrorType expectedError = RpcErrorType.INVALID_PARAMS; + final RpcErrorType expectedError = RpcErrorType.INVALID_BLOCK_HASH_PARAMS; testHelper.assertValidJsonRpcError( json, id, expectedError.getCode(), expectedError.getMessage()); } @@ -896,7 +894,7 @@ public void getBlockByHashWithNoParameters() throws Exception { assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); - final RpcErrorType expectedError = RpcErrorType.INVALID_PARAMS; + final RpcErrorType expectedError = RpcErrorType.INVALID_BLOCK_HASH_PARAMS; testHelper.assertValidJsonRpcError( json, id, expectedError.getCode(), expectedError.getMessage()); } @@ -982,7 +980,7 @@ public void getBlockByNumberForInvalidBlockParameter() throws Exception { // Check general format of result final String respBody = resp.body().string(); final JsonObject json = new JsonObject(respBody); - final RpcErrorType expectedError = RpcErrorType.INVALID_PARAMS; + final RpcErrorType expectedError = RpcErrorType.INVALID_BLOCK_PARAMS; testHelper.assertValidJsonRpcError( json, id, expectedError.getCode(), expectedError.getMessage()); } @@ -1129,7 +1127,7 @@ public void extraneousParameters() throws Exception { testHelper.assertValidJsonRpcResult(json, id); // Check result final String result = json.getString("result"); - assertThat(result).isEqualTo(CLIENT_VERSION); + assertThat(result).isEqualTo(CLIENT_NODE_NAME); } } @@ -1145,7 +1143,7 @@ public void requestMissingVersionFieldShouldSucceed() throws Exception { final JsonObject json = new JsonObject(resp.body().string()); testHelper.assertValidJsonRpcResult(json, id); final String result = json.getString("result"); - assertThat(result).isEqualTo(CLIENT_VERSION); + assertThat(result).isEqualTo(CLIENT_NODE_NAME); } } @@ -1177,7 +1175,7 @@ public void nullId() throws Exception { testHelper.assertValidJsonRpcResult(json, null); // Check result final String result = json.getString("result"); - assertThat(result).isEqualTo(CLIENT_VERSION); + assertThat(result).isEqualTo(CLIENT_NODE_NAME); } } @@ -1199,7 +1197,7 @@ public void emptyStringIdField() throws Exception { testHelper.assertValidJsonRpcResult(json, id); // Check result final String result = json.getString("result"); - assertThat(result).isEqualTo(CLIENT_VERSION); + assertThat(result).isEqualTo(CLIENT_NODE_NAME); } } @@ -1220,7 +1218,7 @@ public void negativeNumericId() throws Exception { testHelper.assertValidJsonRpcResult(json, id); // Check result final String result = json.getString("result"); - assertThat(result).isEqualTo(CLIENT_VERSION); + assertThat(result).isEqualTo(CLIENT_NODE_NAME); } } @@ -1244,7 +1242,7 @@ public void largeNumericId() throws Exception { testHelper.assertValidJsonRpcResult(json, id); // Check result final String result = json.getString("result"); - assertThat(result).isEqualTo(CLIENT_VERSION); + assertThat(result).isEqualTo(CLIENT_NODE_NAME); } } @@ -1270,7 +1268,7 @@ public void largeStringId() throws Exception { testHelper.assertValidJsonRpcResult(json, id); // Check result final String result = json.getString("result"); - assertThat(result).isEqualTo(CLIENT_VERSION); + assertThat(result).isEqualTo(CLIENT_NODE_NAME); } } @@ -1291,7 +1289,7 @@ public void fractionalNumericId() throws Exception { testHelper.assertValidJsonRpcResult(json, id); // Check result final String result = json.getString("result"); - assertThat(result).isEqualTo(CLIENT_VERSION); + assertThat(result).isEqualTo(CLIENT_NODE_NAME); } } @@ -1355,7 +1353,7 @@ public void requestWithWrongVersionShouldSucceed() throws Exception { final JsonObject json = new JsonObject(resp.body().string()); testHelper.assertValidJsonRpcResult(json, id); final String result = json.getString("result"); - assertThat(result).isEqualTo(CLIENT_VERSION); + assertThat(result).isEqualTo(CLIENT_NODE_NAME); } } @@ -1389,65 +1387,68 @@ public void disabledMethod() throws Exception { + "\"}", JSON); - when(rpcMethods.get(any(String.class))).thenReturn(null); - when(rpcMethods.containsKey(any(String.class))).thenReturn(false); + try (var unused = disableRpcMethod(methodName)) { - try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { - assertThat(resp.code()).isEqualTo(200); - final JsonObject json = new JsonObject(resp.body().string()); - final RpcErrorType expectedError = RpcErrorType.METHOD_NOT_ENABLED; - testHelper.assertValidJsonRpcError( - json, id, expectedError.getCode(), expectedError.getMessage()); + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { + assertThat(resp.code()).isEqualTo(200); + final JsonObject json = new JsonObject(resp.body().string()); + final RpcErrorType expectedError = RpcErrorType.METHOD_NOT_ENABLED; + testHelper.assertValidJsonRpcError( + json, id, expectedError.getCode(), expectedError.getMessage()); + } } - - verify(rpcMethods).containsKey(methodName); - verify(rpcMethods).get(methodName); - - reset(rpcMethods); } @Test public void exceptionallyHandleJsonSingleRequest() throws Exception { + final String methodName = "foo"; final JsonRpcMethod jsonRpcMethod = mock(JsonRpcMethod.class); - when(jsonRpcMethod.getName()).thenReturn("foo"); + when(jsonRpcMethod.getName()).thenReturn(methodName); when(jsonRpcMethod.response(any())).thenThrow(new RuntimeException("test exception")); - doReturn(jsonRpcMethod).when(rpcMethods).get("foo"); + try (var unused = addRpcMethod(methodName, jsonRpcMethod)) { - final RequestBody body = - RequestBody.create("{\"jsonrpc\":\"2.0\",\"id\":\"666\",\"method\":\"foo\"}", JSON); + final RequestBody body = + RequestBody.create( + "{\"jsonrpc\":\"2.0\",\"id\":\"666\",\"method\":\"" + methodName + "\"}", JSON); - try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { - assertThat(resp.code()).isEqualTo(200); - final JsonObject json = new JsonObject(resp.body().string()); - final RpcErrorType expectedError = RpcErrorType.INTERNAL_ERROR; - testHelper.assertValidJsonRpcError( - json, "666", expectedError.getCode(), expectedError.getMessage()); + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { + assertThat(resp.code()).isEqualTo(200); + final JsonObject json = new JsonObject(resp.body().string()); + final RpcErrorType expectedError = RpcErrorType.INTERNAL_ERROR; + testHelper.assertValidJsonRpcError( + json, "666", expectedError.getCode(), expectedError.getMessage()); + } } } @Test public void exceptionallyHandleJsonBatchRequest() throws Exception { + final String methodName = "foo"; final JsonRpcMethod jsonRpcMethod = mock(JsonRpcMethod.class); - when(jsonRpcMethod.getName()).thenReturn("foo"); + when(jsonRpcMethod.getName()).thenReturn(methodName); when(jsonRpcMethod.response(any())).thenThrow(new RuntimeException("test exception")); - doReturn(jsonRpcMethod).when(rpcMethods).get("foo"); - final RequestBody body = - RequestBody.create( - "[{\"jsonrpc\":\"2.0\",\"id\":\"000\",\"method\":\"web3_clientVersion\"}," - + "{\"jsonrpc\":\"2.0\",\"id\":\"111\",\"method\":\"foo\"}," - + "{\"jsonrpc\":\"2.0\",\"id\":\"222\",\"method\":\"net_version\"}]", - JSON); - - try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { - assertThat(resp.code()).isEqualTo(200); - final JsonArray array = new JsonArray(resp.body().string()); - testHelper.assertValidJsonRpcResult(array.getJsonObject(0), "000"); - final RpcErrorType expectedError = RpcErrorType.INTERNAL_ERROR; - testHelper.assertValidJsonRpcError( - array.getJsonObject(1), "111", expectedError.getCode(), expectedError.getMessage()); - testHelper.assertValidJsonRpcResult(array.getJsonObject(2), "222"); + try (var unused = addRpcMethod(methodName, jsonRpcMethod)) { + + final RequestBody body = + RequestBody.create( + "[{\"jsonrpc\":\"2.0\",\"id\":\"000\",\"method\":\"web3_clientVersion\"}," + + "{\"jsonrpc\":\"2.0\",\"id\":\"111\",\"method\":\"" + + methodName + + "\"}," + + "{\"jsonrpc\":\"2.0\",\"id\":\"222\",\"method\":\"net_version\"}]", + JSON); + + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { + assertThat(resp.code()).isEqualTo(200); + final JsonArray array = new JsonArray(resp.body().string()); + testHelper.assertValidJsonRpcResult(array.getJsonObject(0), "000"); + final RpcErrorType expectedError = RpcErrorType.INTERNAL_ERROR; + testHelper.assertValidJsonRpcError( + array.getJsonObject(1), "111", expectedError.getCode(), expectedError.getMessage()); + testHelper.assertValidJsonRpcResult(array.getJsonObject(2), "222"); + } } } @@ -1484,7 +1485,7 @@ public void batchRequest() throws Exception { // Check result web3_clientVersion final JsonObject jsonClientVersion = responses.get(clientVersionRequestId); testHelper.assertValidJsonRpcResult(jsonClientVersion, clientVersionRequestId); - assertThat(jsonClientVersion.getString("result")).isEqualTo(CLIENT_VERSION); + assertThat(jsonClientVersion.getString("result")).isEqualTo(CLIENT_NODE_NAME); // Check result unknown method final JsonObject jsonError = responses.get(brokenRequestId); @@ -1539,7 +1540,7 @@ public void batchRequestContainingInvalidRequest() throws Exception { // Check result web3_clientVersion final JsonObject jsonClientVersion = responses.get(clientVersionRequestId); testHelper.assertValidJsonRpcResult(jsonClientVersion, clientVersionRequestId); - assertThat(jsonClientVersion.getString("result")).isEqualTo(CLIENT_VERSION); + assertThat(jsonClientVersion.getString("result")).isEqualTo(CLIENT_NODE_NAME); // Check invalid request final JsonObject jsonError = responses.get(invalidId); @@ -1604,7 +1605,7 @@ public void batchRequestWithNotifications() throws Exception { // Check result web3_clientVersion final JsonObject jsonClientVersion = responses.get(clientVersionRequestId); testHelper.assertValidJsonRpcResult(jsonClientVersion, clientVersionRequestId); - assertThat(jsonClientVersion.getString("result")).isEqualTo(CLIENT_VERSION); + assertThat(jsonClientVersion.getString("result")).isEqualTo(CLIENT_NODE_NAME); // Check result net_version final JsonObject jsonNetVersion = responses.get(netVersionRequestId); @@ -1742,7 +1743,7 @@ private void assertBlockResultMatchesBlock(final JsonObject result, final Block assertThat(Long.decode(result.getString("timestamp"))).isEqualTo(header.getTimestamp()); assertThat(Long.decode(result.getString("number"))).isEqualTo(header.getNumber()); // Nonce is a data field and should represent 8 bytes exactly - final String nonceResult = result.getString("nonce").toLowerCase(); + final String nonceResult = result.getString("nonce").toLowerCase(Locale.ROOT); assertThat(nonceResult.length() == 18 && nonceResult.startsWith("0x")).isTrue(); assertThat(Long.parseUnsignedLong(nonceResult.substring(2), 16)).isEqualTo(header.getNonce()); assertThat(Hash.fromHexString(result.getString("hash"))).isEqualTo(header.getHash()); @@ -2032,7 +2033,7 @@ public void ethGetStorageAtInvalidParameterStorageIndex() throws Exception { assertThat(resp.code()).isEqualTo(200); // Check general format of result final JsonObject json = new JsonObject(resp.body().string()); - final RpcErrorType expectedError = RpcErrorType.INVALID_PARAMS; + final RpcErrorType expectedError = RpcErrorType.INVALID_POSITION_PARAMS; testHelper.assertValidJsonRpcError( json, id, expectedError.getCode(), expectedError.getMessage()); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTestBase.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTestBase.java index 1b5e58529b8..5a439fa1947 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTestBase.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTestBase.java @@ -12,15 +12,14 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.api.jsonrpc; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; import org.hyperledger.besu.config.StubGenesisConfigOptions; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.ApiConfiguration; +import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.health.HealthService; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.filter.FilterManager; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; @@ -28,6 +27,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.blockcreation.PoWMiningCoordinator; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.ChainHead; import org.hyperledger.besu.ethereum.core.MiningParameters; @@ -67,17 +67,21 @@ public class JsonRpcHttpServiceTestBase { + // this tempDir is deliberately static @TempDir private static Path folder; protected final JsonRpcTestHelper testHelper = new JsonRpcTestHelper(); private static final Vertx vertx = Vertx.vertx(); - protected static Map rpcMethods; + private static Map disabledRpcMethods; + private static Set addedRpcMethods; protected static JsonRpcHttpService service; protected static OkHttpClient client; protected static String baseUrl; protected static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); - protected static final String CLIENT_VERSION = "TestClientVersion/0.1.0"; + protected static final String CLIENT_NODE_NAME = "TestClientVersion/0.1.0"; + protected static final String CLIENT_VERSION = "0.1.0"; + protected static final String CLIENT_COMMIT = "12345678"; protected static final BigInteger CHAIN_ID = BigInteger.valueOf(123); protected static P2PNetwork peerDiscoveryMock; protected static EthPeers ethPeersMock; @@ -105,39 +109,48 @@ public static void initServerAndClient() throws Exception { supportedCapabilities.add(EthProtocol.ETH63); rpcMethods = - spy( - new JsonRpcMethodsFactory() - .methods( - CLIENT_VERSION, - CHAIN_ID, - new StubGenesisConfigOptions(), - peerDiscoveryMock, - blockchainQueries, - synchronizer, - MainnetProtocolSchedule.fromConfig( - new StubGenesisConfigOptions().constantinopleBlock(0).chainId(CHAIN_ID), - EvmConfiguration.DEFAULT), - mock(ProtocolContext.class), - mock(FilterManager.class), - mock(TransactionPool.class), - mock(MiningParameters.class), - mock(PoWMiningCoordinator.class), - new NoOpMetricsSystem(), - supportedCapabilities, - Optional.of(mock(AccountLocalConfigPermissioningController.class)), - Optional.of(mock(NodeLocalConfigPermissioningController.class)), - JSON_RPC_APIS, - mock(PrivacyParameters.class), - mock(JsonRpcConfiguration.class), - mock(WebSocketConfiguration.class), - mock(MetricsConfiguration.class), - natService, - new HashMap<>(), - folder, - ethPeersMock, - vertx, - mock(ApiConfiguration.class), - Optional.empty())); + new JsonRpcMethodsFactory() + .methods( + CLIENT_NODE_NAME, + CLIENT_VERSION, + CLIENT_COMMIT, + CHAIN_ID, + new StubGenesisConfigOptions(), + peerDiscoveryMock, + blockchainQueries, + synchronizer, + MainnetProtocolSchedule.fromConfig( + new StubGenesisConfigOptions().constantinopleBlock(0).chainId(CHAIN_ID), + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()), + mock(ProtocolContext.class), + mock(FilterManager.class), + mock(TransactionPool.class), + mock(MiningParameters.class), + mock(PoWMiningCoordinator.class), + new NoOpMetricsSystem(), + supportedCapabilities, + Optional.of(mock(AccountLocalConfigPermissioningController.class)), + Optional.of(mock(NodeLocalConfigPermissioningController.class)), + JSON_RPC_APIS, + mock(PrivacyParameters.class), + mock(JsonRpcConfiguration.class), + mock(WebSocketConfiguration.class), + mock(MetricsConfiguration.class), + mock(GraphQLConfiguration.class), + natService, + new HashMap<>(), + folder, + ethPeersMock, + vertx, + mock(ApiConfiguration.class), + Optional.empty()); + disabledRpcMethods = new HashMap<>(); + addedRpcMethods = new HashSet<>(); + service = createJsonRpcHttpService(createLimitedJsonRpcConfig()); service.start().join(); @@ -146,8 +159,7 @@ public static void initServerAndClient() throws Exception { baseUrl = service.url(); } - protected static JsonRpcHttpService createJsonRpcHttpService(final JsonRpcConfiguration config) - throws Exception { + protected static JsonRpcHttpService createJsonRpcHttpService(final JsonRpcConfiguration config) { return new JsonRpcHttpService( vertx, folder, @@ -159,7 +171,7 @@ protected static JsonRpcHttpService createJsonRpcHttpService(final JsonRpcConfig HealthService.ALWAYS_HEALTHY); } - protected static JsonRpcHttpService createJsonRpcHttpService() throws Exception { + protected static JsonRpcHttpService createJsonRpcHttpService() { return new JsonRpcHttpService( vertx, folder, @@ -188,6 +200,22 @@ protected Request buildGetRequest(final String path) { return new Request.Builder().get().url(baseUrl + path).build(); } + protected AutoCloseable disableRpcMethod(final String methodName) { + disabledRpcMethods.put(methodName, rpcMethods.remove(methodName)); + return () -> resetRpcMethods(); + } + + protected AutoCloseable addRpcMethod(final String methodName, final JsonRpcMethod method) { + rpcMethods.put(methodName, method); + addedRpcMethods.add(methodName); + return () -> resetRpcMethods(); + } + + protected void resetRpcMethods() { + disabledRpcMethods.forEach(rpcMethods::put); + addedRpcMethods.forEach(rpcMethods::remove); + } + /** Tears down the HTTP server. */ @AfterAll public static void shutdownServer() { diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTlsClientAuthTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTlsClientAuthTest.java index 46eee17ea6a..28474d3c536 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTlsClientAuthTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTlsClientAuthTest.java @@ -21,11 +21,11 @@ import static org.hyperledger.besu.ethereum.api.tls.TlsClientAuthConfiguration.Builder.aTlsClientAuthConfiguration; import static org.hyperledger.besu.ethereum.api.tls.TlsConfiguration.Builder.aTlsConfiguration; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; import org.hyperledger.besu.config.StubGenesisConfigOptions; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.ApiConfiguration; +import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.health.HealthService; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.filter.FilterManager; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; @@ -36,6 +36,7 @@ import org.hyperledger.besu.ethereum.api.tls.SelfSignedP12Certificate; import org.hyperledger.besu.ethereum.api.tls.TlsConfiguration; import org.hyperledger.besu.ethereum.blockcreation.PoWMiningCoordinator; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.Synchronizer; @@ -79,12 +80,15 @@ import org.junit.jupiter.api.io.TempDir; public class JsonRpcHttpServiceTlsClientAuthTest { + // this tempDir is deliberately static @TempDir private static Path folder; protected static final Vertx vertx = Vertx.vertx(); private static final String JSON_HEADER = "application/json; charset=utf-8"; - private static final String CLIENT_VERSION = "TestClientVersion/0.1.0"; + private static final String CLIENT_NODE_NAME = "TestClientVersion/0.1.0"; + private static final String CLIENT_VERSION = "0.1.0"; + private static final String CLIENT_COMMIT = "12345678"; private static final BigInteger CHAIN_ID = BigInteger.valueOf(123); private static final NatService natService = new NatService(Optional.empty()); @@ -111,38 +115,44 @@ public void initServer() throws Exception { supportedCapabilities.add(EthProtocol.ETH63); rpcMethods = - spy( - new JsonRpcMethodsFactory() - .methods( - CLIENT_VERSION, - CHAIN_ID, - new StubGenesisConfigOptions(), - peerDiscoveryMock, - blockchainQueries, - synchronizer, - MainnetProtocolSchedule.fromConfig( - new StubGenesisConfigOptions().constantinopleBlock(0).chainId(CHAIN_ID)), - mock(ProtocolContext.class), - mock(FilterManager.class), - mock(TransactionPool.class), - mock(MiningParameters.class), - mock(PoWMiningCoordinator.class), - new NoOpMetricsSystem(), - supportedCapabilities, - Optional.of(mock(AccountLocalConfigPermissioningController.class)), - Optional.of(mock(NodeLocalConfigPermissioningController.class)), - DEFAULT_RPC_APIS, - mock(PrivacyParameters.class), - mock(JsonRpcConfiguration.class), - mock(WebSocketConfiguration.class), - mock(MetricsConfiguration.class), - natService, - Collections.emptyMap(), - folder, - mock(EthPeers.class), - vertx, - mock(ApiConfiguration.class), - Optional.empty())); + new JsonRpcMethodsFactory() + .methods( + CLIENT_NODE_NAME, + CLIENT_VERSION, + CLIENT_COMMIT, + CHAIN_ID, + new StubGenesisConfigOptions(), + peerDiscoveryMock, + blockchainQueries, + synchronizer, + MainnetProtocolSchedule.fromConfig( + new StubGenesisConfigOptions().constantinopleBlock(0).chainId(CHAIN_ID), + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()), + mock(ProtocolContext.class), + mock(FilterManager.class), + mock(TransactionPool.class), + mock(MiningParameters.class), + mock(PoWMiningCoordinator.class), + new NoOpMetricsSystem(), + supportedCapabilities, + Optional.of(mock(AccountLocalConfigPermissioningController.class)), + Optional.of(mock(NodeLocalConfigPermissioningController.class)), + DEFAULT_RPC_APIS, + mock(PrivacyParameters.class), + mock(JsonRpcConfiguration.class), + mock(WebSocketConfiguration.class), + mock(MetricsConfiguration.class), + mock(GraphQLConfiguration.class), + natService, + Collections.emptyMap(), + folder, + mock(EthPeers.class), + vertx, + mock(ApiConfiguration.class), + Optional.empty()); System.setProperty("javax.net.ssl.trustStore", CLIENT_AS_CA_CERT.getKeyStoreFile().toString()); System.setProperty( diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTlsMisconfigurationTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTlsMisconfigurationTest.java index 695dec93269..15df60230b6 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTlsMisconfigurationTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTlsMisconfigurationTest.java @@ -20,11 +20,11 @@ import static org.hyperledger.besu.ethereum.api.tls.TlsClientAuthConfiguration.Builder.aTlsClientAuthConfiguration; import static org.hyperledger.besu.ethereum.api.tls.TlsConfiguration.Builder.aTlsConfiguration; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; import org.hyperledger.besu.config.StubGenesisConfigOptions; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.ApiConfiguration; +import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.health.HealthService; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.filter.FilterManager; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; @@ -35,6 +35,7 @@ import org.hyperledger.besu.ethereum.api.tls.SelfSignedP12Certificate; import org.hyperledger.besu.ethereum.api.tls.TlsConfiguration; import org.hyperledger.besu.ethereum.blockcreation.PoWMiningCoordinator; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.Synchronizer; @@ -75,7 +76,9 @@ class JsonRpcHttpServiceTlsMisconfigurationTest { protected static final Vertx vertx = Vertx.vertx(); - private static final String CLIENT_VERSION = "TestClientVersion/0.1.0"; + private static final String CLIENT_NODE_NAME = "TestClientVersion/0.1.0"; + private static final String CLIENT_VERSION = "0.1.0"; + private static final String CLIENT_COMMIT = "12345678"; private static final BigInteger CHAIN_ID = BigInteger.valueOf(123); private static final NatService natService = new NatService(Optional.empty()); private final SelfSignedP12Certificate besuCertificate = SelfSignedP12Certificate.create(); @@ -100,38 +103,44 @@ public void beforeEach() { supportedCapabilities.add(EthProtocol.ETH63); rpcMethods = - spy( - new JsonRpcMethodsFactory() - .methods( - CLIENT_VERSION, - CHAIN_ID, - new StubGenesisConfigOptions(), - peerDiscoveryMock, - blockchainQueries, - synchronizer, - MainnetProtocolSchedule.fromConfig( - new StubGenesisConfigOptions().constantinopleBlock(0).chainId(CHAIN_ID)), - mock(ProtocolContext.class), - mock(FilterManager.class), - mock(TransactionPool.class), - mock(MiningParameters.class), - mock(PoWMiningCoordinator.class), - new NoOpMetricsSystem(), - supportedCapabilities, - Optional.of(mock(AccountLocalConfigPermissioningController.class)), - Optional.of(mock(NodeLocalConfigPermissioningController.class)), - DEFAULT_RPC_APIS, - mock(PrivacyParameters.class), - mock(JsonRpcConfiguration.class), - mock(WebSocketConfiguration.class), - mock(MetricsConfiguration.class), - natService, - Collections.emptyMap(), - tempDir.getRoot(), - mock(EthPeers.class), - vertx, - mock(ApiConfiguration.class), - Optional.empty())); + new JsonRpcMethodsFactory() + .methods( + CLIENT_NODE_NAME, + CLIENT_VERSION, + CLIENT_COMMIT, + CHAIN_ID, + new StubGenesisConfigOptions(), + peerDiscoveryMock, + blockchainQueries, + synchronizer, + MainnetProtocolSchedule.fromConfig( + new StubGenesisConfigOptions().constantinopleBlock(0).chainId(CHAIN_ID), + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()), + mock(ProtocolContext.class), + mock(FilterManager.class), + mock(TransactionPool.class), + mock(MiningParameters.class), + mock(PoWMiningCoordinator.class), + new NoOpMetricsSystem(), + supportedCapabilities, + Optional.of(mock(AccountLocalConfigPermissioningController.class)), + Optional.of(mock(NodeLocalConfigPermissioningController.class)), + DEFAULT_RPC_APIS, + mock(PrivacyParameters.class), + mock(JsonRpcConfiguration.class), + mock(WebSocketConfiguration.class), + mock(MetricsConfiguration.class), + mock(GraphQLConfiguration.class), + natService, + Collections.emptyMap(), + tempDir.getRoot(), + mock(EthPeers.class), + vertx, + mock(ApiConfiguration.class), + Optional.empty()); } @AfterEach diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTlsTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTlsTest.java index 587d142b7f8..6f7ce4e1d66 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTlsTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcHttpServiceTlsTest.java @@ -20,11 +20,11 @@ import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis.DEFAULT_RPC_APIS; import static org.hyperledger.besu.ethereum.api.tls.TlsConfiguration.Builder.aTlsConfiguration; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; import org.hyperledger.besu.config.StubGenesisConfigOptions; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.ApiConfiguration; +import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.health.HealthService; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.filter.FilterManager; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; @@ -35,6 +35,7 @@ import org.hyperledger.besu.ethereum.api.tls.SelfSignedP12Certificate; import org.hyperledger.besu.ethereum.api.tls.TlsConfiguration; import org.hyperledger.besu.ethereum.blockcreation.PoWMiningCoordinator; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.Synchronizer; @@ -81,7 +82,9 @@ public class JsonRpcHttpServiceTlsTest { protected static final Vertx vertx = Vertx.vertx(); private static final String JSON_HEADER = "application/json; charset=utf-8"; - private static final String CLIENT_VERSION = "TestClientVersion/0.1.0"; + private static final String CLIENT_NODE_NAME = "TestClientVersion/0.1.0"; + private static final String CLIENT_VERSION = "0.1.0"; + private static final String CLIENT_COMMIT = "12345678"; private static final BigInteger CHAIN_ID = BigInteger.valueOf(123); private static final NatService natService = new NatService(Optional.empty()); private JsonRpcHttpService service; @@ -101,38 +104,44 @@ public void initServer() throws Exception { supportedCapabilities.add(EthProtocol.ETH63); rpcMethods = - spy( - new JsonRpcMethodsFactory() - .methods( - CLIENT_VERSION, - CHAIN_ID, - new StubGenesisConfigOptions(), - peerDiscoveryMock, - blockchainQueries, - synchronizer, - MainnetProtocolSchedule.fromConfig( - new StubGenesisConfigOptions().constantinopleBlock(0).chainId(CHAIN_ID)), - mock(ProtocolContext.class), - mock(FilterManager.class), - mock(TransactionPool.class), - mock(MiningParameters.class), - mock(PoWMiningCoordinator.class), - new NoOpMetricsSystem(), - supportedCapabilities, - Optional.of(mock(AccountLocalConfigPermissioningController.class)), - Optional.of(mock(NodeLocalConfigPermissioningController.class)), - DEFAULT_RPC_APIS, - mock(PrivacyParameters.class), - mock(JsonRpcConfiguration.class), - mock(WebSocketConfiguration.class), - mock(MetricsConfiguration.class), - natService, - Collections.emptyMap(), - folder, - mock(EthPeers.class), - vertx, - mock(ApiConfiguration.class), - Optional.empty())); + new JsonRpcMethodsFactory() + .methods( + CLIENT_NODE_NAME, + CLIENT_VERSION, + CLIENT_COMMIT, + CHAIN_ID, + new StubGenesisConfigOptions(), + peerDiscoveryMock, + blockchainQueries, + synchronizer, + MainnetProtocolSchedule.fromConfig( + new StubGenesisConfigOptions().constantinopleBlock(0).chainId(CHAIN_ID), + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()), + mock(ProtocolContext.class), + mock(FilterManager.class), + mock(TransactionPool.class), + mock(MiningParameters.class), + mock(PoWMiningCoordinator.class), + new NoOpMetricsSystem(), + supportedCapabilities, + Optional.of(mock(AccountLocalConfigPermissioningController.class)), + Optional.of(mock(NodeLocalConfigPermissioningController.class)), + DEFAULT_RPC_APIS, + mock(PrivacyParameters.class), + mock(JsonRpcConfiguration.class), + mock(WebSocketConfiguration.class), + mock(MetricsConfiguration.class), + mock(GraphQLConfiguration.class), + natService, + Collections.emptyMap(), + folder, + mock(EthPeers.class), + vertx, + mock(ApiConfiguration.class), + Optional.empty()); service = createJsonRpcHttpService(createJsonRpcConfig()); service.start().join(); baseUrl = service.url(); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcTestHelper.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcTestHelper.java index 7e5762d3336..f86476ce3e3 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcTestHelper.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/JsonRpcTestHelper.java @@ -37,8 +37,16 @@ protected void assertValidJsonRpcResult(final JsonObject json, final Object id) } protected void assertValidJsonRpcError( - final JsonObject json, final Object id, final int errorCode, final String errorMessage) - throws Exception { + final JsonObject json, final Object id, final int errorCode, final String errorMessage) { + assertValidJsonRpcError(json, id, errorCode, errorMessage, null); + } + + protected void assertValidJsonRpcError( + final JsonObject json, + final Object id, + final int errorCode, + final String errorMessage, + final String data) { // Check all expected fieldnames are set final Set fieldNames = json.fieldNames(); assertThat(fieldNames.size()).isEqualTo(3); @@ -53,13 +61,19 @@ protected void assertValidJsonRpcError( // Check error format final JsonObject error = json.getJsonObject("error"); final Set errorFieldNames = error.fieldNames(); - assertThat(errorFieldNames.size()).isEqualTo(2); + assertThat(errorFieldNames.size()).isEqualTo(data == null ? 2 : 3); assertThat(errorFieldNames.contains("code")).isTrue(); assertThat(errorFieldNames.contains("message")).isTrue(); + if (data != null) { + assertThat(errorFieldNames.contains("data")).isTrue(); + } // Check error field values assertThat(error.getInteger("code")).isEqualTo(errorCode); assertThat(error.getString("message")).isEqualTo(errorMessage); + if (data != null) { + assertThat(error.getString("data")).isEqualTo(data); + } } protected void assertIdMatches(final JsonObject json, final Object expectedId) { diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/LimitConnectionsJsonRpcHttpServiceTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/LimitConnectionsJsonRpcHttpServiceTest.java index 625e3fe5687..b1618a0dcf3 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/LimitConnectionsJsonRpcHttpServiceTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/LimitConnectionsJsonRpcHttpServiceTest.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.api.jsonrpc; import static org.assertj.core.api.Assertions.assertThat; diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/MaxBatchSizeJsonRpcHttpServiceTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/MaxBatchSizeJsonRpcHttpServiceTest.java index 3c206fa2235..2e283a65b97 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/MaxBatchSizeJsonRpcHttpServiceTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/MaxBatchSizeJsonRpcHttpServiceTest.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.api.jsonrpc; import static org.assertj.core.api.Assertions.assertThat; diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/PluginJsonRpcMethodTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/PluginJsonRpcMethodTest.java new file mode 100644 index 00000000000..5c92a43e57f --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/PluginJsonRpcMethodTest.java @@ -0,0 +1,219 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.PluginJsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; +import org.hyperledger.besu.plugin.services.exception.PluginRpcEndpointException; +import org.hyperledger.besu.plugin.services.rpc.PluginRpcRequest; +import org.hyperledger.besu.plugin.services.rpc.RpcMethodError; + +import java.util.Locale; +import java.util.Optional; + +import io.vertx.core.json.JsonObject; +import okhttp3.RequestBody; +import okhttp3.Response; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class PluginJsonRpcMethodTest extends JsonRpcHttpServiceTestBase { + + @BeforeAll + public static void setup() throws Exception { + initServerAndClient(); + } + + /** Tears down the HTTP server. */ + @AfterAll + public static void shutdownServer() { + service.stop().join(); + } + + @Test + public void happyPath() throws Exception { + final var request = + """ + {"jsonrpc":"2.0","id":1,"method":"plugin_echo","params":["hello"]}"""; + + try (var unused = + addRpcMethod( + "plugin_echo", + new PluginJsonRpcMethod("plugin_echo", PluginJsonRpcMethodTest::echoPluginRpcMethod))) { + final RequestBody body = RequestBody.create(request, JSON); + + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { + assertThat(resp.code()).isEqualTo(200); + final JsonObject json = new JsonObject(resp.body().string()); + testHelper.assertValidJsonRpcResult(json, 1); + assertThat(json.getString("result")).isEqualTo("hello"); + } + } + } + + @Test + public void invalidJsonShouldReturnParseError() throws Exception { + final var malformedRequest = + """ + {"jsonrpc":"2.0","id":1,"method":"plugin_echo","params":}"""; + + try (var unused = + addRpcMethod( + "plugin_echo", + new PluginJsonRpcMethod("plugin_echo", PluginJsonRpcMethodTest::echoPluginRpcMethod))) { + final RequestBody body = RequestBody.create(malformedRequest, JSON); + + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { + assertThat(resp.code()).isEqualTo(400); + final JsonObject json = new JsonObject(resp.body().string()); + final JsonRpcError expectedError = new JsonRpcError(RpcErrorType.PARSE_ERROR); + testHelper.assertValidJsonRpcError( + json, null, expectedError.getCode(), expectedError.getMessage()); + } + } + } + + @Test + public void invalidParamsShouldReturnInvalidParams() throws Exception { + final var missingRequiredParam = + """ + {"jsonrpc":"2.0","id":1,"method":"plugin_echo","params":[]}"""; + try (var unused = + addRpcMethod( + "plugin_echo", + new PluginJsonRpcMethod("plugin_echo", PluginJsonRpcMethodTest::echoPluginRpcMethod))) { + final RequestBody body = RequestBody.create(missingRequiredParam, JSON); + + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { + assertThat(resp.code()).isEqualTo(200); + final JsonObject json = new JsonObject(resp.body().string()); + final JsonRpcError expectedError = new JsonRpcError(RpcErrorType.INVALID_PARAM_COUNT); + testHelper.assertValidJsonRpcError( + json, 1, expectedError.getCode(), expectedError.getMessage()); + } + } + } + + @Test + public void methodErrorShouldReturnErrorResponse() throws Exception { + final var wrongParamContent = + """ + {"jsonrpc":"2.0","id":1,"method":"plugin_echo","params":[" "]}"""; + try (var unused = + addRpcMethod( + "plugin_echo", + new PluginJsonRpcMethod("plugin_echo", PluginJsonRpcMethodTest::echoPluginRpcMethod))) { + final RequestBody body = RequestBody.create(wrongParamContent, JSON); + + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { + assertThat(resp.code()).isEqualTo(200); + final JsonObject json = new JsonObject(resp.body().string()); + testHelper.assertValidJsonRpcError(json, 1, -1, "Blank input not allowed"); + } + } + } + + @Test + public void methodErrorWithDataShouldReturnErrorResponseWithDecodedData() throws Exception { + final var wrongParamContent = + """ + {"jsonrpc":"2.0","id":1,"method":"plugin_echo","params":["data"]}"""; + try (var unused = + addRpcMethod( + "plugin_echo", + new PluginJsonRpcMethod("plugin_echo", PluginJsonRpcMethodTest::echoPluginRpcMethod))) { + final RequestBody body = RequestBody.create(wrongParamContent, JSON); + + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { + assertThat(resp.code()).isEqualTo(200); + final JsonObject json = new JsonObject(resp.body().string()); + testHelper.assertValidJsonRpcError(json, 1, -2, "Error with data: ABC", "abc"); + } + } + } + + @Test + public void unhandledExceptionShouldReturnInternalErrorResponse() throws Exception { + final var nullParam = + """ + {"jsonrpc":"2.0","id":1,"method":"plugin_echo","params":[null]}"""; + try (var unused = + addRpcMethod( + "plugin_echo", + new PluginJsonRpcMethod("plugin_echo", PluginJsonRpcMethodTest::echoPluginRpcMethod))) { + final RequestBody body = RequestBody.create(nullParam, JSON); + + try (final Response resp = client.newCall(buildPostRequest(body)).execute()) { + assertThat(resp.code()).isEqualTo(200); + final JsonObject json = new JsonObject(resp.body().string()); + final JsonRpcError expectedError = new JsonRpcError(RpcErrorType.INTERNAL_ERROR); + testHelper.assertValidJsonRpcError( + json, 1, expectedError.getCode(), expectedError.getMessage()); + } + } + } + + private static Object echoPluginRpcMethod(final PluginRpcRequest request) { + final var params = request.getParams(); + if (params.length == 0) { + throw new InvalidJsonRpcParameters( + "parameter is mandatory", RpcErrorType.INVALID_PARAM_COUNT); + } + final var input = params[0]; + if (input.toString().isBlank()) { + throw new PluginRpcEndpointException( + new RpcMethodError() { + @Override + public int getCode() { + return -1; + } + + @Override + public String getMessage() { + return "Blank input not allowed"; + } + }); + } + + if (input.toString().equals("data")) { + throw new PluginRpcEndpointException( + new RpcMethodError() { + @Override + public int getCode() { + return -2; + } + + @Override + public String getMessage() { + return "Error with data"; + } + + @Override + public Optional decodeData(final String data) { + // just turn everything uppercase + return Optional.of(data.toUpperCase(Locale.US)); + } + }, + "abc"); + } + + return input; + } +} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcErrorTypeConverterTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcErrorTypeConverterTest.java index 6a52b39c853..a54057f8f42 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcErrorTypeConverterTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/RpcErrorTypeConverterTest.java @@ -22,6 +22,7 @@ import java.util.Arrays; import java.util.Collection; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -68,7 +69,13 @@ public static Collection expectedErrorMapping() { { TransactionInvalidReason.TRANSACTION_REPLACEMENT_UNDERPRICED, RpcErrorType.ETH_SEND_TX_REPLACEMENT_UNDERPRICED - } + }, + { + TransactionInvalidReason.BLOB_GAS_PRICE_BELOW_CURRENT_BLOB_BASE_FEE, + RpcErrorType.BLOB_GAS_PRICE_BELOW_CURRENT_BLOB_BASE_FEE + }, + {TransactionInvalidReason.TOTAL_BLOB_GAS_TOO_HIGH, RpcErrorType.TOTAL_BLOB_GAS_TOO_HIGH}, + {TransactionInvalidReason.INVALID_BLOBS, RpcErrorType.INVALID_BLOBS} }); } @@ -79,4 +86,11 @@ public void expectedTransactionValidationToJsonRpcErrorConversion( assertThat(JsonRpcErrorConverter.convertTransactionInvalidReason(txInvalidReason)) .isEqualTo(expectedJsonRpcError); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/EngineAuthServiceTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/EngineAuthServiceTest.java index 4c2fc787ff5..d31ea931bcd 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/EngineAuthServiceTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/authentication/EngineAuthServiceTest.java @@ -1,8 +1,8 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.api.jsonrpc.authentication; import static org.assertj.core.api.Assertions.assertThat; diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/bonsai/DebugJsonRpcHttpBySpecTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/bonsai/DebugJsonRpcHttpBySpecTest.java index 0ee3388f4e9..e1a13f1a802 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/bonsai/DebugJsonRpcHttpBySpecTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/bonsai/DebugJsonRpcHttpBySpecTest.java @@ -14,9 +14,12 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.bonsai; +import static org.assertj.core.api.Assertions.assertThat; + import org.hyperledger.besu.ethereum.api.jsonrpc.AbstractJsonRpcHttpBySpecTest; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class DebugJsonRpcHttpBySpecTest extends AbstractJsonRpcHttpBySpecTest { @@ -33,4 +36,11 @@ public static Object[][] specs() { "debug/account-at", "debug/batch-send-raw-transaction", "debug/trace-transaction" }); // storageRange and accountRange are not working with bonsai trie } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/bonsai/DebugTraceJsonRpcHttpBySpecTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/bonsai/DebugTraceJsonRpcHttpBySpecTest.java index acfbcbaf492..fbe3f32196a 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/bonsai/DebugTraceJsonRpcHttpBySpecTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/bonsai/DebugTraceJsonRpcHttpBySpecTest.java @@ -14,11 +14,14 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.bonsai; +import static org.assertj.core.api.Assertions.assertThat; + import org.hyperledger.besu.ethereum.api.jsonrpc.AbstractJsonRpcHttpBySpecTest; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class DebugTraceJsonRpcHttpBySpecTest extends AbstractJsonRpcHttpBySpecTest { @@ -38,4 +41,11 @@ protected BlockchainSetupUtil getBlockchainSetupUtil(final DataStorageFormat sto public static Object[][] specs() { return AbstractJsonRpcHttpBySpecTest.findSpecFiles(new String[] {"debug/trace-call"}); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/bonsai/EthByzantiumJsonRpcHttpBySpecTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/bonsai/EthByzantiumJsonRpcHttpBySpecTest.java index a5f3ae0ab0c..f1b6b695a86 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/bonsai/EthByzantiumJsonRpcHttpBySpecTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/bonsai/EthByzantiumJsonRpcHttpBySpecTest.java @@ -14,11 +14,14 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.bonsai; +import static org.assertj.core.api.Assertions.assertThat; + import org.hyperledger.besu.ethereum.api.jsonrpc.AbstractJsonRpcHttpBySpecTest; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class EthByzantiumJsonRpcHttpBySpecTest extends AbstractJsonRpcHttpBySpecTest { @@ -38,4 +41,11 @@ protected BlockchainSetupUtil getBlockchainSetupUtil(final DataStorageFormat dat public static Object[][] specs() { return AbstractJsonRpcHttpBySpecTest.findSpecFiles(new String[] {"eth_latest"}); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/bonsai/EthJsonRpcHttpBySpecTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/bonsai/EthJsonRpcHttpBySpecTest.java index 9dc7d193c8b..1bb5f801ebc 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/bonsai/EthJsonRpcHttpBySpecTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/bonsai/EthJsonRpcHttpBySpecTest.java @@ -14,9 +14,12 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.bonsai; +import static org.assertj.core.api.Assertions.assertThat; + import org.hyperledger.besu.ethereum.api.jsonrpc.AbstractJsonRpcHttpBySpecTest; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class EthJsonRpcHttpBySpecTest extends AbstractJsonRpcHttpBySpecTest { @@ -30,4 +33,11 @@ public void setup() throws Exception { public static Object[][] specs() { return findSpecFiles(new String[] {"eth"}); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/bonsai/TraceJsonRpcHttpBySpecTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/bonsai/TraceJsonRpcHttpBySpecTest.java index 127d7d3b696..51d573ddae7 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/bonsai/TraceJsonRpcHttpBySpecTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/bonsai/TraceJsonRpcHttpBySpecTest.java @@ -14,11 +14,14 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.bonsai; +import static org.assertj.core.api.Assertions.assertThat; + import org.hyperledger.besu.ethereum.api.jsonrpc.AbstractJsonRpcHttpBySpecTest; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class TraceJsonRpcHttpBySpecTest extends AbstractJsonRpcHttpBySpecTest { @@ -52,4 +55,11 @@ public static Object[][] specs() { "trace/specs/trace-raw-transaction" }); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/context/ContextKeyTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/context/ContextKeyTest.java index a31bcb96d8e..ed2574170ed 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/context/ContextKeyTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/context/ContextKeyTest.java @@ -26,6 +26,7 @@ import io.vertx.core.json.JsonObject; import io.vertx.ext.web.RoutingContext; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -66,4 +67,11 @@ private static RoutingContext ctx(final T value) { private static Supplier supplier(final T value) { return () -> value; } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/forest/DebugJsonRpcHttpBySpecTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/forest/DebugJsonRpcHttpBySpecTest.java index 562b4c25724..bc8d4178ed1 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/forest/DebugJsonRpcHttpBySpecTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/forest/DebugJsonRpcHttpBySpecTest.java @@ -14,9 +14,12 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.forest; +import static org.assertj.core.api.Assertions.assertThat; + import org.hyperledger.besu.ethereum.api.jsonrpc.AbstractJsonRpcHttpBySpecTest; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class DebugJsonRpcHttpBySpecTest extends AbstractJsonRpcHttpBySpecTest { @@ -37,4 +40,11 @@ public static Object[][] specs() { "debug/storage-range" }); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/forest/DebugTraceJsonRpcHttpBySpecTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/forest/DebugTraceJsonRpcHttpBySpecTest.java index 412a4958c3d..cdedc3438b6 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/forest/DebugTraceJsonRpcHttpBySpecTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/forest/DebugTraceJsonRpcHttpBySpecTest.java @@ -14,11 +14,14 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.forest; +import static org.assertj.core.api.Assertions.assertThat; + import org.hyperledger.besu.ethereum.api.jsonrpc.AbstractJsonRpcHttpBySpecTest; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class DebugTraceJsonRpcHttpBySpecTest extends AbstractJsonRpcHttpBySpecTest { @@ -38,4 +41,11 @@ protected BlockchainSetupUtil getBlockchainSetupUtil(final DataStorageFormat sto public static Object[][] specs() { return AbstractJsonRpcHttpBySpecTest.findSpecFiles(new String[] {"debug/trace-call"}); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/forest/EthByzantiumJsonRpcHttpBySpecTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/forest/EthByzantiumJsonRpcHttpBySpecTest.java index a98aa9b45e5..c7c2d131a10 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/forest/EthByzantiumJsonRpcHttpBySpecTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/forest/EthByzantiumJsonRpcHttpBySpecTest.java @@ -14,11 +14,14 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.forest; +import static org.assertj.core.api.Assertions.assertThat; + import org.hyperledger.besu.ethereum.api.jsonrpc.AbstractJsonRpcHttpBySpecTest; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class EthByzantiumJsonRpcHttpBySpecTest extends AbstractJsonRpcHttpBySpecTest { @@ -38,4 +41,11 @@ protected BlockchainSetupUtil getBlockchainSetupUtil(final DataStorageFormat sto public static Object[][] specs() { return AbstractJsonRpcHttpBySpecTest.findSpecFiles(new String[] {"eth_latest"}); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/forest/EthJsonRpcHttpBySpecTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/forest/EthJsonRpcHttpBySpecTest.java index 277f1b89d03..041c0c711e1 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/forest/EthJsonRpcHttpBySpecTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/forest/EthJsonRpcHttpBySpecTest.java @@ -14,9 +14,12 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.forest; +import static org.assertj.core.api.Assertions.assertThat; + import org.hyperledger.besu.ethereum.api.jsonrpc.AbstractJsonRpcHttpBySpecTest; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class EthJsonRpcHttpBySpecTest extends AbstractJsonRpcHttpBySpecTest { @@ -30,4 +33,11 @@ public void setup() throws Exception { public static Object[][] specs() { return findSpecFiles(new String[] {"eth"}); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/forest/TraceJsonRpcHttpBySpecTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/forest/TraceJsonRpcHttpBySpecTest.java index 36b5b6f17e1..b33be910059 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/forest/TraceJsonRpcHttpBySpecTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/forest/TraceJsonRpcHttpBySpecTest.java @@ -14,11 +14,14 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.forest; +import static org.assertj.core.api.Assertions.assertThat; + import org.hyperledger.besu.ethereum.api.jsonrpc.AbstractJsonRpcHttpBySpecTest; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class TraceJsonRpcHttpBySpecTest extends AbstractJsonRpcHttpBySpecTest { @@ -52,4 +55,11 @@ public static Object[][] specs() { "trace/specs/trace-raw-transaction" }); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/filter/EthJsonRpcHttpServiceTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/filter/EthJsonRpcHttpServiceTest.java index bdef98bb039..38dc9decc57 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/filter/EthJsonRpcHttpServiceTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/filter/EthJsonRpcHttpServiceTest.java @@ -21,7 +21,7 @@ import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; import org.hyperledger.besu.ethereum.core.Transaction; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import java.io.IOException; diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminAddPeerTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminAddPeerTest.java index 25a0899c8c4..f3dcb1c8f51 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminAddPeerTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminAddPeerTest.java @@ -102,7 +102,7 @@ public void requestIsMissingParameter() { final JsonRpcRequestContext request = new JsonRpcRequestContext(new JsonRpcRequest("2.0", "admin_addPeer", new String[] {})); final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAM_COUNT); final JsonRpcResponse actualResponse = method.response(request); @@ -114,7 +114,7 @@ public void requestHasNullObjectParameter() { final JsonRpcRequestContext request = new JsonRpcRequestContext(new JsonRpcRequest("2.0", "admin_addPeer", null)); final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAM_COUNT); final JsonRpcResponse actualResponse = method.response(request); @@ -126,7 +126,7 @@ public void requestHasNullArrayParameter() { final JsonRpcRequestContext request = new JsonRpcRequestContext(new JsonRpcRequest("2.0", "admin_addPeer", new String[] {null})); final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAM_COUNT); final JsonRpcResponse actualResponse = method.response(request); @@ -221,7 +221,7 @@ public void requestRefusesListOfNodes() { new JsonRpcRequest("2.0", "admin_addPeer", new String[] {validEnode, validEnode})); final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAM_COUNT); final JsonRpcResponse actualResponse = method.response(request); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminChangeLogLevelTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminChangeLogLevelTest.java index 120f380cd7a..cf7e3337fd9 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminChangeLogLevelTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminChangeLogLevelTest.java @@ -117,7 +117,8 @@ public void requestHasNullArrayParameter() { new JsonRpcRequestContext( new JsonRpcRequest("2.0", "admin_changeLogLevel", new Object[] {null})); final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + new JsonRpcErrorResponse( + request.getRequest().getId(), RpcErrorType.INVALID_LOG_LEVEL_PARAMS); final JsonRpcResponse actualResponse = adminChangeLogLevel.response(request); assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); @@ -129,7 +130,8 @@ public void requestHasInvalidStringLogLevelParameter() { new JsonRpcRequestContext( new JsonRpcRequest("2.0", "admin_changeLogLevel", new String[] {"INVALID"})); final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + new JsonRpcErrorResponse( + request.getRequest().getId(), RpcErrorType.INVALID_LOG_LEVEL_PARAMS); final JsonRpcResponse actualResponse = adminChangeLogLevel.response(request); assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); @@ -141,7 +143,8 @@ public void requestHasInvalidLogFilterParameter() { new JsonRpcRequestContext( new JsonRpcRequest("2.0", "admin_changeLogLevel", new Object[] {"DEBUG", "INVALID"})); final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + new JsonRpcErrorResponse( + request.getRequest().getId(), RpcErrorType.INVALID_LOG_FILTER_PARAMS); final JsonRpcResponse actualResponse = adminChangeLogLevel.response(request); assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminLogsRemoveCacheTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminLogsRemoveCacheTest.java index fbe27b42fb9..c58556114c7 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminLogsRemoveCacheTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminLogsRemoveCacheTest.java @@ -152,7 +152,8 @@ public void requestBlockRangeInvalidTest() { new JsonRpcRequestContext( new JsonRpcRequest("2.0", "admin_logsRemoveCache", new String[] {"0x20", "0x1"})); final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + new JsonRpcErrorResponse( + request.getRequest().getId(), RpcErrorType.INVALID_BLOCK_NUMBER_PARAMS); when(blockchainQueries.getBlockchain()).thenReturn(blockchain); when(blockchain.getBlockByNumber(anyLong())).thenReturn(Optional.of(block)); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminNodeInfoTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminNodeInfoTest.java index 20284cb5de3..07fc060655b 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminNodeInfoTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminNodeInfoTest.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; @@ -33,6 +34,8 @@ import org.hyperledger.besu.ethereum.chain.ChainHead; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.p2p.network.P2PNetwork; import org.hyperledger.besu.ethereum.p2p.peers.DefaultPeer; import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl; @@ -67,6 +70,8 @@ public class AdminNodeInfoTest { @Mock private BlockchainQueries blockchainQueries; @Mock private NatService natService; @Mock private BlockHeader blockHeader; + @Mock private ProtocolSchedule protocolSchedule; + @Mock private ProtocolSpec protocolSpec; private AdminNodeInfo method; @@ -93,6 +98,8 @@ public void setup() { when(blockchainQueries.getBlockHashByNumber(anyLong())).thenReturn(Optional.of(Hash.EMPTY)); when(blockchain.getChainHead()).thenReturn(testChainHead); when(natService.queryExternalIPAddress(anyString())).thenReturn("1.2.3.4"); + when(protocolSpec.getName()).thenReturn("London"); + when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); method = new AdminNodeInfo( "testnet/1.0/this/that", @@ -100,7 +107,8 @@ public void setup() { genesisConfigOptions, p2pNetwork, blockchainQueries, - natService); + natService, + protocolSchedule); } @Test @@ -110,6 +118,7 @@ public void shouldReturnCorrectResult() { final JsonRpcRequestContext request = adminNodeInfo(); final Map expected = new HashMap<>(); + expected.put("activeFork", "London"); expected.put( "enode", "enode://0f1b319e32017c3fcb221841f0f978701b4e9513fe6a567a2db43d43381a9c7e3dfe7cae13cbc2f56943400bacaf9082576ab087cd51983b17d729ae796f6807@1.2.3.4:30303?discport=7890"); @@ -161,6 +170,7 @@ public void shouldReturnCorrectResultWhenIsNatEnvironment() { final JsonRpcRequestContext request = adminNodeInfo(); final Map expected = new HashMap<>(); + expected.put("activeFork", "London"); expected.put( "enode", "enode://0f1b319e32017c3fcb221841f0f978701b4e9513fe6a567a2db43d43381a9c7e3dfe7cae13cbc2f56943400bacaf9082576ab087cd51983b17d729ae796f6807@3.4.5.6:8081?discport=8080"); @@ -207,6 +217,7 @@ public void handlesLocalEnodeWithListeningAndDiscoveryDisabled() { final JsonRpcRequestContext request = adminNodeInfo(); final Map expected = new HashMap<>(); + expected.put("activeFork", "London"); expected.put( "enode", "enode://0f1b319e32017c3fcb221841f0f978701b4e9513fe6a567a2db43d43381a9c7e3dfe7cae13cbc2f56943400bacaf9082576ab087cd51983b17d729ae796f6807@1.2.3.4:0"); @@ -253,6 +264,7 @@ public void handlesLocalEnodeWithListeningDisabled() { final JsonRpcRequestContext request = adminNodeInfo(); final Map expected = new HashMap<>(); + expected.put("activeFork", "London"); expected.put( "enode", "enode://0f1b319e32017c3fcb221841f0f978701b4e9513fe6a567a2db43d43381a9c7e3dfe7cae13cbc2f56943400bacaf9082576ab087cd51983b17d729ae796f6807@1.2.3.4:0?discport=7890"); @@ -299,6 +311,7 @@ public void handlesLocalEnodeWithDiscoveryDisabled() { final JsonRpcRequestContext request = adminNodeInfo(); final Map expected = new HashMap<>(); + expected.put("activeFork", "London"); expected.put( "enode", "enode://0f1b319e32017c3fcb221841f0f978701b4e9513fe6a567a2db43d43381a9c7e3dfe7cae13cbc2f56943400bacaf9082576ab087cd51983b17d729ae796f6807@1.2.3.4:7890?discport=0"); @@ -387,7 +400,8 @@ public void returnsClassicForkBlocks() { genesisClassicConfigOptions, p2pNetwork, blockchainQueries, - natService); + natService, + protocolSchedule); final JsonRpcRequestContext request = adminNodeInfo(); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminRemovePeerTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminRemovePeerTest.java index b2245b51b86..2ba7c335239 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminRemovePeerTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/AdminRemovePeerTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -102,7 +102,7 @@ public void requestIsMissingParameter() { final JsonRpcRequestContext request = new JsonRpcRequestContext(new JsonRpcRequest("2.0", "admin_removePeer", new String[] {})); final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAM_COUNT); final JsonRpcResponse actualResponse = method.response(request); @@ -114,7 +114,7 @@ public void requestHasNullObjectParameter() { final JsonRpcRequestContext request = new JsonRpcRequestContext(new JsonRpcRequest("2.0", "admin_removePeer", null)); final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAM_COUNT); final JsonRpcResponse actualResponse = method.response(request); @@ -127,7 +127,7 @@ public void requestHasNullArrayParameter() { new JsonRpcRequestContext( new JsonRpcRequest("2.0", "admin_removePeer", new String[] {null})); final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAM_COUNT); final JsonRpcResponse actualResponse = method.response(request); @@ -222,7 +222,7 @@ public void requestRefusesListOfNodes() { new JsonRpcRequest("2.0", "admin_removePeer", new String[] {validEnode, validEnode})); final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAM_COUNT); final JsonRpcResponse actualResponse = method.response(request); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugAccountAtTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugAccountAtTest.java index d7840241d5a..7876693b4cc 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugAccountAtTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugAccountAtTest.java @@ -113,7 +113,7 @@ void testInvalidParamsResponseEmptyList() { Assertions.assertThat(response).isInstanceOf(JsonRpcErrorResponse.class); Assertions.assertThat(((JsonRpcErrorResponse) response).getErrorType()) - .isEqualByComparingTo(RpcErrorType.INVALID_PARAMS); + .isEqualByComparingTo(RpcErrorType.INVALID_TRANSACTION_PARAMS); } @Test @@ -129,7 +129,7 @@ void testInvalidParamsResponseNegative() { Assertions.assertThat(response).isInstanceOf(JsonRpcErrorResponse.class); Assertions.assertThat(((JsonRpcErrorResponse) response).getErrorType()) - .isEqualByComparingTo(RpcErrorType.INVALID_PARAMS); + .isEqualByComparingTo(RpcErrorType.INVALID_TRANSACTION_PARAMS); } @Test @@ -145,7 +145,7 @@ void testInvalidParamsResponseTooHigh() { Assertions.assertThat(response).isInstanceOf(JsonRpcErrorResponse.class); Assertions.assertThat(((JsonRpcErrorResponse) response).getErrorType()) - .isEqualByComparingTo(RpcErrorType.INVALID_PARAMS); + .isEqualByComparingTo(RpcErrorType.INVALID_TRANSACTION_PARAMS); } @Test diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetBadBlockTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetBadBlockTest.java index 685641f111d..12a0e39e18b 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetBadBlockTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugGetBadBlockTest.java @@ -16,32 +16,30 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import org.hyperledger.besu.crypto.KeyPair; import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; +import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BadBlockResult; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory; -import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; +import org.hyperledger.besu.ethereum.chain.BadBlockCause; import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockDataGenerator; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionTestFixture; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @SuppressWarnings("unchecked") @@ -49,13 +47,17 @@ public class DebugGetBadBlockTest { private final TransactionTestFixture transactionTestFixture = new TransactionTestFixture(); - private final ProtocolSchedule protocolSchedule = mock(ProtocolSchedule.class); - private final BlockchainQueries blockchainQueries = mock(BlockchainQueries.class); + private final ProtocolContext protocolContext = mock(ProtocolContext.class); private final BlockResultFactory blockResult = new BlockResultFactory(); private final BadBlockManager badBlockManager = new BadBlockManager(); private final DebugGetBadBlocks debugGetBadBlocks = - new DebugGetBadBlocks(blockchainQueries, protocolSchedule, blockResult); + new DebugGetBadBlocks(protocolContext, blockResult); + + @BeforeEach + public void setup() { + when(protocolContext.getBadBlockManager()).thenReturn(badBlockManager); + } @Test public void nameShouldBeDebugTraceBlock() { @@ -93,11 +95,10 @@ public void shouldReturnCorrectResponse() { .setBlockHeaderFunctions(new MainnetBlockHeaderFunctions()) .setParentHash(parentBlock.getHash())); - badBlockManager.addBadBlock(badBlockWithTransaction, Optional.empty()); - badBlockManager.addBadBlock(badBlockWoTransaction, Optional.empty()); - final ProtocolSpec protocolSpec = mock(ProtocolSpec.class); - when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); - when(protocolSpec.getBadBlocksManager()).thenReturn(badBlockManager); + badBlockManager.addBadBlock( + badBlockWithTransaction, BadBlockCause.fromValidationFailure("failed")); + badBlockManager.addBadBlock( + badBlockWoTransaction, BadBlockCause.fromValidationFailure("failed")); final JsonRpcRequestContext request = new JsonRpcRequestContext(new JsonRpcRequest("2.0", "debug_traceBlock", new Object[] {})); @@ -123,11 +124,6 @@ public void shouldReturnCorrectResponse() { @Test public void shouldReturnCorrectResponseWhenNoInvalidBlockFound() { - final ProtocolSpec protocolSpec = mock(ProtocolSpec.class); - - when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); - when(protocolSpec.getBadBlocksManager()).thenReturn(badBlockManager); - final JsonRpcRequestContext request = new JsonRpcRequestContext(new JsonRpcRequest("2.0", "debug_traceBlock", new Object[] {})); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugMetricsTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugMetricsTest.java index 6e41fc9c1bf..d4830cb85a7 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugMetricsTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugMetricsTest.java @@ -16,6 +16,7 @@ import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.metrics.BesuMetricCategory.BLOCKCHAIN; import static org.hyperledger.besu.metrics.BesuMetricCategory.PEERS; import static org.hyperledger.besu.metrics.BesuMetricCategory.RPC; import static org.mockito.Mockito.mock; @@ -28,6 +29,7 @@ import org.hyperledger.besu.metrics.Observation; import java.util.Collections; +import java.util.List; import java.util.stream.Stream; import com.google.common.collect.ImmutableMap; @@ -84,6 +86,36 @@ public void shouldNestObservationsByLabel() { ImmutableMap.of("label2B", "value3"))))); } + @Test + public void shouldHandleDoubleValuesInNestedStructureWithoutClassCastException() { + // Tests fix for issue# 7383: debug_metrics method error + when(metricsSystem.streamObservations()) + .thenReturn( + Stream.of( + // This creates a double value for "a" + new Observation(BLOCKCHAIN, "nested_metric", 1.0, List.of("a")), + // This attempts to create a nested structure under "a", which was previously a + // double + new Observation(BLOCKCHAIN, "nested_metric", 2.0, asList("a", "b")), + // This adds another level of nesting + new Observation(BLOCKCHAIN, "nested_metric", 3.0, asList("a", "b", "c")))); + + assertResponse( + ImmutableMap.of( + BLOCKCHAIN.getName(), + ImmutableMap.of( + "nested_metric", + ImmutableMap.of( + "a", + ImmutableMap.of( + "value", + 1.0, + "b", + ImmutableMap.of( + "value", 2.0, + "c", 3.0)))))); + } + private void assertResponse(final ImmutableMap expectedResponse) { final JsonRpcSuccessResponse response = (JsonRpcSuccessResponse) method.response(REQUEST); assertThat(response.getResult()).isEqualTo(expectedResponse); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStandardTraceBadBlockToFileTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStandardTraceBadBlockToFileTest.java index 207ebd73f3b..2ded3440c32 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStandardTraceBadBlockToFileTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStandardTraceBadBlockToFileTest.java @@ -21,21 +21,19 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTracer; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; +import org.hyperledger.besu.ethereum.chain.BadBlockCause; import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockDataGenerator; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.MutableWorldState; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import java.nio.file.Path; import java.util.ArrayList; @@ -56,16 +54,18 @@ public class DebugStandardTraceBadBlockToFileTest { private final Blockchain blockchain = mock(Blockchain.class); private final MutableWorldState mutableWorldState = mock(MutableWorldState.class); - private final ProtocolSchedule protocolSchedule = mock(ProtocolSchedule.class); + private final ProtocolContext protocolContext = mock(ProtocolContext.class); private final TransactionTracer transactionTracer = mock(TransactionTracer.class); - private final ProtocolSpec protocolSpec = mock(ProtocolSpec.class); + + private final BadBlockManager badBlockManager = new BadBlockManager(); private final DebugStandardTraceBadBlockToFile debugStandardTraceBadBlockToFile = new DebugStandardTraceBadBlockToFile( - () -> transactionTracer, blockchainQueries, protocolSchedule, folder); + () -> transactionTracer, blockchainQueries, protocolContext, folder); @BeforeEach public void setup() { + when(protocolContext.getBadBlockManager()).thenReturn(badBlockManager); doAnswer( invocation -> invocation @@ -100,14 +100,9 @@ public void shouldTraceTheTransactionUsingTheTransactionTracer() { final List paths = new ArrayList<>(); paths.add("path-1"); - final BadBlockManager badBlockManager = new BadBlockManager(); - badBlockManager.addBadBlock(block, Optional.empty()); + badBlockManager.addBadBlock(block, BadBlockCause.fromValidationFailure("failed")); - final BlockHeader blockHeader = new BlockHeaderTestFixture().buildHeader(); - when(protocolSpec.getBadBlocksManager()).thenReturn(badBlockManager); when(blockchainQueries.getBlockchain()).thenReturn(blockchain); - when(blockchain.getChainHeadHeader()).thenReturn(new BlockHeaderTestFixture().buildHeader()); - when(protocolSchedule.getByBlockHeader(blockHeader)).thenReturn(protocolSpec); when(transactionTracer.traceTransactionToFile( any(MutableWorldState.class), eq(block.getHash()), any(), any())) .thenReturn(paths); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStandardTraceBlockToFileTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStandardTraceBlockToFileTest.java index 073c604c111..2cf7dcbfc27 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStandardTraceBlockToFileTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugStandardTraceBlockToFileTest.java @@ -18,11 +18,12 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockTracer; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.Tracer; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.TransactionTracer; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; @@ -30,27 +31,23 @@ import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockDataGenerator; import org.hyperledger.besu.ethereum.core.MutableWorldState; -import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import java.nio.file.Path; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Optional; +import java.util.function.Function; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -import org.mockito.Answers; public class DebugStandardTraceBlockToFileTest { + // this tempDir is deliberately static @TempDir private static Path folder; - private final WorldStateArchive archive = - mock(WorldStateArchive.class, Answers.RETURNS_DEEP_STUBS); private final Blockchain blockchain = mock(Blockchain.class); - private final BlockchainQueries blockchainQueries = - spy(new BlockchainQueries(blockchain, archive)); + private final BlockchainQueries blockchainQueries = mock(BlockchainQueries.class); private final TransactionTracer transactionTracer = mock(TransactionTracer.class); private final DebugStandardTraceBlockToFile debugStandardTraceBlockToFile = new DebugStandardTraceBlockToFile(() -> transactionTracer, blockchainQueries, folder); @@ -75,20 +72,26 @@ public void shouldTraceTheTransactionUsingTheTransactionTracer() { new JsonRpcRequestContext( new JsonRpcRequest("2.0", "debug_standardTraceBlockToFile", params)); - final List paths = new ArrayList<>(); - paths.add("path-1"); - - when(blockchainQueries.getBlockchain()).thenReturn(blockchain); + final List paths = List.of("path-1"); when(blockchain.getBlockByHash(block.getHash())).thenReturn(Optional.of(block)); when(blockchain.getBlockHeader(genesis.getHash())).thenReturn(Optional.of(genesis.getHeader())); + when(blockchainQueries.getBlockchain()).thenReturn(blockchain); + + when(blockchainQueries.getAndMapWorldState(any(), any())) + .thenAnswer( + invocationOnMock -> { + Function> mapper = + invocationOnMock.getArgument(1); + return mapper.apply(mock(Tracer.TraceableState.class)); + }); when(transactionTracer.traceTransactionToFile( any(MutableWorldState.class), eq(block.getHash()), any(), any())) .thenReturn(paths); final JsonRpcSuccessResponse response = (JsonRpcSuccessResponse) debugStandardTraceBlockToFile.response(request); - final List result = (ArrayList) response.getResult(); + final List result = (List) response.getResult(); assertThat(result.size()).isEqualTo(1); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockByHashTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockByHashTest.java index c6857bec332..75d7ae26a95 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockByHashTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockByHashTest.java @@ -91,6 +91,7 @@ public void shouldReturnCorrectResponse() { new TraceFrame( 12, Optional.of("NONE"), + Integer.MAX_VALUE, 45L, OptionalLong.of(56L), 0L, diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockByNumberTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockByNumberTest.java index 9b23ab12e91..99e599ec110 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockByNumberTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockByNumberTest.java @@ -77,6 +77,7 @@ public void shouldReturnCorrectResponse() { new TraceFrame( 12, Optional.of("NONE"), + Integer.MAX_VALUE, 45L, OptionalLong.of(56L), 0L, diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockTest.java index 60841c24ef4..f35f3d1a16a 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceBlockTest.java @@ -18,9 +18,8 @@ import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doAnswer; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import org.hyperledger.besu.datatypes.Wei; @@ -35,32 +34,25 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.query.BlockWithMetadata; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; -import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockDataGenerator; import org.hyperledger.besu.ethereum.debug.TraceFrame; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; -import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import java.util.Collection; import java.util.Collections; import java.util.Optional; import java.util.OptionalLong; +import java.util.function.Function; import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.Test; -import org.mockito.Answers; -import org.mockito.Mockito; public class DebugTraceBlockTest { private final BlockTracer blockTracer = mock(BlockTracer.class); - private final WorldStateArchive archive = - mock(WorldStateArchive.class, Answers.RETURNS_DEEP_STUBS); - private final Blockchain blockchain = mock(Blockchain.class); - private final BlockchainQueries blockchainQueries = - spy(new BlockchainQueries(blockchain, archive)); + private final BlockchainQueries blockchainQueries = mock(BlockchainQueries.class); private final DebugTraceBlock debugTraceBlock = new DebugTraceBlock(() -> blockTracer, new MainnetBlockHeaderFunctions(), blockchainQueries); @@ -91,6 +83,7 @@ public void shouldReturnCorrectResponse() { new TraceFrame( 12, Optional.of("NONE"), + Integer.MAX_VALUE, 45L, OptionalLong.of(56L), 0L, @@ -127,22 +120,25 @@ public void shouldReturnCorrectResponse() { when(transaction2Trace.getResult()).thenReturn(transaction2Result); when(transaction1Result.getOutput()).thenReturn(Bytes.fromHexString("1234")); when(transaction2Result.getOutput()).thenReturn(Bytes.fromHexString("1234")); - when(blockTracer.trace(any(Tracer.TraceableState.class), Mockito.eq(block), any())) + when(blockTracer.trace(any(Tracer.TraceableState.class), eq(block), any())) .thenReturn(Optional.of(blockTrace)); - when(blockchain.getBlockHeader(parentBlock.getHash())) - .thenReturn(Optional.of(parentBlock.getHeader())); - doAnswer( - invocation -> - Optional.of( - new BlockWithMetadata<>( - parentBlock.getHeader(), - Collections.emptyList(), - Collections.emptyList(), - parentBlock.getHeader().getDifficulty(), - parentBlock.calculateSize()))) - .when(blockchainQueries) - .blockByHash(parentBlock.getHash()); + when(blockchainQueries.blockByHash(parentBlock.getHash())) + .thenReturn( + Optional.of( + new BlockWithMetadata<>( + parentBlock.getHeader(), + Collections.emptyList(), + Collections.emptyList(), + parentBlock.getHeader().getDifficulty(), + parentBlock.calculateSize()))); + when(blockchainQueries.getAndMapWorldState(eq(parentBlock.getHash()), any())) + .thenAnswer( + invocationOnMock -> { + Function> mapper = + invocationOnMock.getArgument(1); + return mapper.apply(mock(Tracer.TraceableState.class)); + }); final JsonRpcSuccessResponse response = (JsonRpcSuccessResponse) debugTraceBlock.response(request); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceTransactionTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceTransactionTest.java index 580897abb8f..f57fd716c06 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceTransactionTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/DebugTraceTransactionTest.java @@ -116,6 +116,7 @@ public void shouldTraceTheTransactionUsingTheTransactionTracer() { new TraceFrame( 12, Optional.of("NONE"), + Integer.MAX_VALUE, 45L, OptionalLong.of(56L), 0L, @@ -183,6 +184,7 @@ public void shouldNotTraceTheTransactionIfNotFound() { new TraceFrame( 12, Optional.of("NONE"), + Integer.MAX_VALUE, 45L, OptionalLong.of(56L), 0L, diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthBlobBaseFeeTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthBlobBaseFeeTest.java new file mode 100644 index 00000000000..61f12c78aa3 --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthBlobBaseFeeTest.java @@ -0,0 +1,87 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryBlockchain; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.chain.MutableBlockchain; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockDataGenerator; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket; +import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; +import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.gascalculator.ShanghaiGasCalculator; + +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class EthBlobBaseFeeTest { + private final BlockDataGenerator blockDataGenerator = new BlockDataGenerator(); + private MutableBlockchain blockchain; + private EthBlobBaseFee method; + private ProtocolSchedule protocolSchedule; + + @BeforeEach + public void setUp() { + protocolSchedule = mock(ProtocolSchedule.class); + Block genesisBlock = blockDataGenerator.genesisBlock(); + blockchain = createInMemoryBlockchain(genesisBlock); + blockDataGenerator + .blockSequence(genesisBlock, 10) + .forEach(block -> blockchain.appendBlock(block, blockDataGenerator.receipts(block))); + method = new EthBlobBaseFee(blockchain, protocolSchedule); + } + + /** Tests that the method returns the expected blob base fee */ + @Test + public void shouldReturnBlobBaseFee() { + configureProtocolSpec(FeeMarket.cancun(5, Optional.empty()), new CancunGasCalculator()); + assertThat(requestBlobBaseFee().getResult()).isEqualTo("0x1"); + } + + /** Tests that the method returns zero for forks that do not support blob transactions */ + @Test + public void shouldReturnZeroForNonBlobForks() { + configureProtocolSpec(FeeMarket.london(5, Optional.empty()), new ShanghaiGasCalculator()); + assertThat(requestBlobBaseFee().getResult()).isEqualTo("0x0"); + } + + private void configureProtocolSpec( + final BaseFeeMarket feeMarket, final GasCalculator gasCalculator) { + ProtocolSpec spec = mock(ProtocolSpec.class); + when(spec.getFeeMarket()).thenReturn(feeMarket); + when(spec.getGasCalculator()).thenReturn(gasCalculator); + when(protocolSchedule.getForNextBlockHeader( + blockchain.getChainHeadHeader(), blockchain.getChainHeadHeader().getTimestamp())) + .thenReturn(spec); + } + + private JsonRpcSuccessResponse requestBlobBaseFee() { + return (JsonRpcSuccessResponse) + method.response( + new JsonRpcRequestContext(new JsonRpcRequest("2.0", "eth_blobBaseFee", null))); + } +} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCallTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCallTest.java index 00c5af2f94c..7570fe342c2 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCallTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCallTest.java @@ -115,8 +115,7 @@ public void shouldReturnInternalErrorWhenProcessorReturnsEmpty() { @Test public void shouldAcceptRequestWhenMissingOptionalFields() { final JsonCallParameter callParameter = - new JsonCallParameter( - null, null, null, null, null, null, null, null, null, Boolean.FALSE, null); + new JsonCallParameter.JsonCallParameterBuilder().withStrict(Boolean.FALSE).build(); final JsonRpcRequestContext request = ethCallRequest(callParameter, "latest"); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, Bytes.of().toString()); @@ -197,7 +196,7 @@ public void shouldReturnBasicExecutionRevertErrorWithoutReason() { final TransactionSimulatorResult result = mock(TransactionSimulatorResult.class); when(result.isSuccessful()).thenReturn(false); when(result.getValidationResult()).thenReturn(ValidationResult.valid()); - when(result.getResult()).thenReturn(processingResult); + when(result.result()).thenReturn(processingResult); verify(transactionSimulator).process(any(), any(), any(), mapperCaptor.capture(), any()); assertThat(mapperCaptor.getValue().apply(mock(MutableWorldState.class), Optional.of(result))) .isEqualTo(Optional.of(expectedResponse)); @@ -236,7 +235,7 @@ public void shouldReturnExecutionRevertErrorWithABIParseError() { final TransactionSimulatorResult result = mock(TransactionSimulatorResult.class); when(result.isSuccessful()).thenReturn(false); when(result.getValidationResult()).thenReturn(ValidationResult.valid()); - when(result.getResult()).thenReturn(processingResult); + when(result.result()).thenReturn(processingResult); verify(transactionSimulator).process(any(), any(), any(), mapperCaptor.capture(), any()); assertThat(mapperCaptor.getValue().apply(mock(MutableWorldState.class), Optional.of(result))) .isEqualTo(Optional.of(expectedResponse)); @@ -277,7 +276,7 @@ public void shouldReturnExecutionRevertErrorWithParsedABI() { final TransactionSimulatorResult result = mock(TransactionSimulatorResult.class); when(result.isSuccessful()).thenReturn(false); when(result.getValidationResult()).thenReturn(ValidationResult.valid()); - when(result.getResult()).thenReturn(processingResult); + when(result.result()).thenReturn(processingResult); verify(transactionSimulator).process(any(), any(), any(), mapperCaptor.capture(), any()); System.out.println(result); @@ -444,18 +443,16 @@ private JsonCallParameter callParameter() { private JsonCallParameter callParameter( final Wei gasPrice, final Wei maxFeesPerGas, final Wei maxPriorityFeesPerGas) { - return new JsonCallParameter( - Address.fromHexString("0x0"), - Address.fromHexString("0x0"), - 0L, - gasPrice, - maxFeesPerGas, - maxPriorityFeesPerGas, - Wei.ZERO, - Bytes.EMPTY, - null, - null, - null); + return new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0x0")) + .withTo(Address.fromHexString("0x0")) + .withGas(0L) + .withGasPrice(gasPrice) + .withMaxFeePerGas(maxFeesPerGas) + .withMaxPriorityFeePerGas(maxPriorityFeesPerGas) + .withValue(Wei.ZERO) + .withInput(Bytes.EMPTY) + .build(); } private JsonRpcRequestContext ethCallRequest( diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessListTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessListTest.java index ec74d82991a..f7c0c31914f 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessListTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthCreateAccessListTest.java @@ -16,7 +16,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -70,7 +69,9 @@ public class EthCreateAccessListTest { private final String METHOD = "eth_createAccessList"; private EthCreateAccessList method; - @Mock private BlockHeader blockHeader; + @Mock private BlockHeader latestBlockHeader; + @Mock private BlockHeader finalizedBlockHeader; + @Mock private BlockHeader genesisBlockHeader; @Mock private Blockchain blockchain; @Mock private BlockchainQueries blockchainQueries; @Mock private TransactionSimulator transactionSimulator; @@ -78,12 +79,20 @@ public class EthCreateAccessListTest { @BeforeEach public void setUp() { - when(blockchainQueries.headBlockNumber()).thenReturn(1L); when(blockchainQueries.getBlockchain()).thenReturn(blockchain); when(blockchainQueries.getWorldStateArchive()).thenReturn(worldStateArchive); - when(blockchain.getBlockHeader(eq(1L))).thenReturn(Optional.of(blockHeader)); - when(blockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); - when(blockHeader.getNumber()).thenReturn(1L); + when(blockchainQueries.headBlockNumber()).thenReturn(2L); + when(blockchainQueries.getBlockHeaderByNumber(0L)).thenReturn(Optional.of(genesisBlockHeader)); + when(blockchainQueries.finalizedBlockHeader()).thenReturn(Optional.of(finalizedBlockHeader)); + when(blockchainQueries.getBlockHeaderByNumber(1L)) + .thenReturn(Optional.of(finalizedBlockHeader)); + when(genesisBlockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); + when(genesisBlockHeader.getNumber()).thenReturn(0L); + when(finalizedBlockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); + when(finalizedBlockHeader.getNumber()).thenReturn(1L); + when(blockchain.getChainHeadHeader()).thenReturn(latestBlockHeader); + when(latestBlockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); + when(latestBlockHeader.getNumber()).thenReturn(2L); when(worldStateArchive.isWorldStateAvailable(any(), any())).thenReturn(true); method = new EthCreateAccessList(blockchainQueries, transactionSimulator); @@ -99,18 +108,22 @@ private JsonRpcRequestContext ethCreateAccessListRequest(final CallParameter cal new JsonRpcRequest("2.0", METHOD, new Object[] {callParameter})); } + private JsonRpcRequestContext ethCreateAccessListRequest( + final CallParameter callParameter, final String blockParam) { + return new JsonRpcRequestContext( + new JsonRpcRequest("2.0", METHOD, new Object[] {callParameter, blockParam})); + } + @Test public void shouldReturnGasEstimateWhenTransientLegacyTransactionProcessorReturnsResultSuccess() { final JsonRpcRequestContext request = ethCreateAccessListRequest(legacyTransactionCallParameter(Wei.ZERO)); - mockTransactionSimulatorResult(true, false, 1L); + mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, new CreateAccessListResult(new ArrayList<>(), 1L)); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test @@ -118,25 +131,23 @@ public void shouldUseGasPriceParameterWhenIsPresent() { final Wei gasPrice = Wei.of(1000); final JsonRpcRequestContext request = ethCreateAccessListRequest(legacyTransactionCallParameter(gasPrice)); - mockTransactionSimulatorResult(true, false, 1L); + mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, new CreateAccessListResult(new ArrayList<>(), 1L)); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test public void shouldReturnGasEstimateErrorWhenGasPricePresentForEip1559Transaction() { final JsonRpcRequestContext request = ethCreateAccessListRequest(eip1559TransactionCallParameter(Optional.of(Wei.of(10)))); - mockTransactionSimulatorResult(false, false, 1L); + mockTransactionSimulatorResult(false, false, 1L, latestBlockHeader); Assertions.assertThatThrownBy(() -> method.response(request)) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("gasPrice cannot be used with baseFee or maxFeePerGas"); + .hasMessageContaining("gasPrice cannot be used with maxFeePerGas or maxPriorityFeePerGas"); } @Test @@ -144,29 +155,25 @@ public void shouldReturnErrorWhenWorldStateIsNotAvailable() { when(worldStateArchive.isWorldStateAvailable(any(), any())).thenReturn(false); final JsonRpcRequestContext request = ethCreateAccessListRequest(legacyTransactionCallParameter(Wei.ZERO)); - mockTransactionSimulatorResult(false, false, 1L); + mockTransactionSimulatorResult(false, false, 1L, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.WORLD_STATE_UNAVAILABLE); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test public void shouldReturnErrorWhenTransactionReverted() { final JsonRpcRequestContext request = ethCreateAccessListRequest(legacyTransactionCallParameter(Wei.ZERO)); - mockTransactionSimulatorResult(false, true, 1L); + mockTransactionSimulatorResult(false, true, 1L, latestBlockHeader); final String errorReason = "0x00"; final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, new JsonRpcError(RpcErrorType.REVERT_ERROR, errorReason)); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test @@ -176,12 +183,10 @@ public void shouldReturnEmptyAccessListIfNoParameterAndWithoutAccessedStorage() new JsonRpcSuccessResponse(null, new CreateAccessListResult(expectedAccessList, 1L)); final JsonRpcRequestContext request = ethCreateAccessListRequest(eip1559TransactionCallParameter()); - mockTransactionSimulatorResult(true, false, 1L); + mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); - verify(transactionSimulator, times(1)).process(any(), any(), any(), anyLong()); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); + verify(transactionSimulator, times(1)).process(any(), any(), any(), eq(latestBlockHeader)); } @Test @@ -198,11 +203,11 @@ public void shouldReturnAccessListIfNoParameterAndWithAccessedStorage() { final AccessListOperationTracer tracer = createMockTracer(expectedAccessList); // Set TransactionSimulator.process response - mockTransactionSimulatorResult(true, false, 1L); - Assertions.assertThat(responseWithMockTracer(request, tracer)) + mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader); + assertThat(responseWithMockTracer(request, tracer)) .usingRecursiveComparison() .isEqualTo(expectedResponse); - verify(transactionSimulator, times(2)).process(any(), any(), any(), anyLong()); + verify(transactionSimulator, times(2)).process(any(), any(), any(), eq(latestBlockHeader)); } @Test @@ -217,11 +222,9 @@ public void shouldReturnEmptyAccessListIfNoAccessedStorage() { ethCreateAccessListRequest(eip1559TransactionCallParameter(accessListParam)); // Set TransactionSimulator.process response - mockTransactionSimulatorResult(true, false, 1L); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); - verify(transactionSimulator, times(1)).process(any(), any(), any(), anyLong()); + mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); + verify(transactionSimulator, times(1)).process(any(), any(), any(), eq(latestBlockHeader)); } @Test @@ -238,11 +241,11 @@ public void shouldReturnAccessListIfParameterAndSameAccessedStorage() { final AccessListOperationTracer tracer = createMockTracer(expectedAccessList); // Set TransactionSimulator.process response - mockTransactionSimulatorResult(true, false, 1L); - Assertions.assertThat(responseWithMockTracer(request, tracer)) + mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader); + assertThat(responseWithMockTracer(request, tracer)) .usingRecursiveComparison() .isEqualTo(expectedResponse); - verify(transactionSimulator, times(1)).process(any(), any(), any(), anyLong()); + verify(transactionSimulator, times(1)).process(any(), any(), any(), eq(latestBlockHeader)); } @Test @@ -262,11 +265,51 @@ public void shouldReturnAccessListIfWithParameterAndDifferentAccessedStorage() { final AccessListOperationTracer tracer = createMockTracer(expectedAccessList); // Set TransactionSimulator.process response - mockTransactionSimulatorResult(true, false, 1L); - Assertions.assertThat(responseWithMockTracer(request, tracer)) + mockTransactionSimulatorResult(true, false, 1L, latestBlockHeader); + assertThat(responseWithMockTracer(request, tracer)) + .usingRecursiveComparison() + .isEqualTo(expectedResponse); + verify(transactionSimulator, times(2)).process(any(), any(), any(), eq(latestBlockHeader)); + } + + @Test + public void shouldReturnAccessListWhenBlockTagParamIsPresent() { + final JsonRpcRequestContext request = + ethCreateAccessListRequest(eip1559TransactionCallParameter(), "finalized"); + // Create a list with one access list entry + final List expectedAccessList = createAccessList(); + + // expect a list with the mocked access list + final JsonRpcResponse expectedResponse = + new JsonRpcSuccessResponse(null, new CreateAccessListResult(expectedAccessList, 1L)); + final AccessListOperationTracer tracer = createMockTracer(expectedAccessList); + + // Set TransactionSimulator.process response + mockTransactionSimulatorResult(true, false, 1L, finalizedBlockHeader); + assertThat(responseWithMockTracer(request, tracer)) + .usingRecursiveComparison() + .isEqualTo(expectedResponse); + verify(transactionSimulator, times(2)).process(any(), any(), any(), eq(finalizedBlockHeader)); + } + + @Test + public void shouldReturnAccessListWhenBlockNumberParamIsPresent() { + final JsonRpcRequestContext request = + ethCreateAccessListRequest(eip1559TransactionCallParameter(), "0x0"); + // Create a list with one access list entry + final List expectedAccessList = createAccessList(); + + // expect a list with the mocked access list + final JsonRpcResponse expectedResponse = + new JsonRpcSuccessResponse(null, new CreateAccessListResult(expectedAccessList, 1L)); + final AccessListOperationTracer tracer = createMockTracer(expectedAccessList); + + // Set TransactionSimulator.process response + mockTransactionSimulatorResult(true, false, 1L, genesisBlockHeader); + assertThat(responseWithMockTracer(request, tracer)) .usingRecursiveComparison() .isEqualTo(expectedResponse); - verify(transactionSimulator, times(2)).process(any(), any(), any(), anyLong()); + verify(transactionSimulator, times(2)).process(any(), any(), any(), eq(genesisBlockHeader)); } private JsonRpcResponse responseWithMockTracer( @@ -286,31 +329,31 @@ private AccessListOperationTracer createMockTracer( } private void mockTransactionSimulatorResult( - final boolean isSuccessful, final boolean isReverted, final long estimateGas) { + final boolean isSuccessful, + final boolean isReverted, + final long estimateGas, + final BlockHeader blockHeader) { final TransactionSimulatorResult mockTxSimResult = mock(TransactionSimulatorResult.class); - when(transactionSimulator.process(any(), any(), any(), anyLong())) + when(transactionSimulator.process(any(), any(), any(), eq(blockHeader))) .thenReturn(Optional.of(mockTxSimResult)); final TransactionProcessingResult mockResult = mock(TransactionProcessingResult.class); when(mockResult.getEstimateGasUsedByTransaction()).thenReturn(estimateGas); when(mockResult.getRevertReason()) .thenReturn(isReverted ? Optional.of(Bytes.of(0)) : Optional.empty()); - when(mockTxSimResult.getResult()).thenReturn(mockResult); + when(mockTxSimResult.result()).thenReturn(mockResult); when(mockTxSimResult.isSuccessful()).thenReturn(isSuccessful); } private JsonCallParameter legacyTransactionCallParameter(final Wei gasPrice) { - return new JsonCallParameter( - Address.fromHexString("0x0"), - Address.fromHexString("0x0"), - 0L, - gasPrice, - null, - null, - Wei.ZERO, - Bytes.EMPTY, - null, - false, - null); + return new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0x0")) + .withTo(Address.fromHexString("0x0")) + .withGas(0L) + .withGasPrice(gasPrice) + .withValue(Wei.ZERO) + .withInput(Bytes.EMPTY) + .withStrict(Boolean.FALSE) + .build(); } private CallParameter eip1559TransactionCallParameter() { @@ -328,18 +371,17 @@ private CallParameter eip1559TransactionCallParameter( private JsonCallParameter eip1559TransactionCallParameter( final Optional gasPrice, final List accessListEntries) { - return new JsonCallParameter( - Address.fromHexString("0x0"), - Address.fromHexString("0x0"), - null, - gasPrice.orElse(null), - Wei.fromHexString("0x10"), - Wei.fromHexString("0x10"), - Wei.ZERO, - Bytes.EMPTY, - null, - false, - accessListEntries); + return new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0x0")) + .withTo(Address.fromHexString("0x0")) + .withGasPrice(gasPrice.orElse(null)) + .withMaxFeePerGas(Wei.fromHexString("0x10")) + .withMaxPriorityFeePerGas(Wei.fromHexString("0x10")) + .withValue(Wei.ZERO) + .withInput(Bytes.EMPTY) + .withStrict(Boolean.FALSE) + .withAccessList(accessListEntries) + .build(); } private List createAccessList() { diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java index 6f1d748030f..f85a0813efd 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthEstimateGasTest.java @@ -65,7 +65,9 @@ public class EthEstimateGasTest { private EthEstimateGas method; - @Mock private BlockHeader blockHeader; + @Mock private BlockHeader latestBlockHeader; + @Mock private BlockHeader finalizedBlockHeader; + @Mock private BlockHeader genesisBlockHeader; @Mock private Blockchain blockchain; @Mock private BlockchainQueries blockchainQueries; @Mock private TransactionSimulator transactionSimulator; @@ -73,12 +75,20 @@ public class EthEstimateGasTest { @BeforeEach public void setUp() { - when(blockchainQueries.headBlockNumber()).thenReturn(1L); when(blockchainQueries.getBlockchain()).thenReturn(blockchain); when(blockchainQueries.getWorldStateArchive()).thenReturn(worldStateArchive); - when(blockchain.getBlockHeader(eq(1L))).thenReturn(Optional.of(blockHeader)); - when(blockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); - when(blockHeader.getNumber()).thenReturn(1L); + when(blockchainQueries.headBlockNumber()).thenReturn(2L); + when(blockchainQueries.getBlockHeaderByNumber(0L)).thenReturn(Optional.of(genesisBlockHeader)); + when(blockchainQueries.finalizedBlockHeader()).thenReturn(Optional.of(finalizedBlockHeader)); + when(blockchainQueries.getBlockHeaderByNumber(1L)) + .thenReturn(Optional.of(finalizedBlockHeader)); + when(genesisBlockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); + when(genesisBlockHeader.getNumber()).thenReturn(0L); + when(finalizedBlockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); + when(finalizedBlockHeader.getNumber()).thenReturn(1L); + when(blockchain.getChainHeadHeader()).thenReturn(latestBlockHeader); + when(latestBlockHeader.getGasLimit()).thenReturn(Long.MAX_VALUE); + when(latestBlockHeader.getNumber()).thenReturn(2L); when(worldStateArchive.isWorldStateAvailable(any(), any())).thenReturn(true); method = new EthEstimateGas(blockchainQueries, transactionSimulator); @@ -97,15 +107,13 @@ public void shouldReturnErrorWhenTransientLegacyTransactionProcessorReturnsEmpty eq(modifiedLegacyTransactionCallParameter(Wei.ZERO)), any(TransactionValidationParams.class), any(OperationTracer.class), - eq(1L))) + eq(latestBlockHeader))) .thenReturn(Optional.empty()); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.INTERNAL_ERROR); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test @@ -115,28 +123,24 @@ public void shouldReturnErrorWhenTransientEip1559TransactionProcessorReturnsEmpt eq(modifiedEip1559TransactionCallParameter()), any(TransactionValidationParams.class), any(OperationTracer.class), - eq(1L))) + eq(latestBlockHeader))) .thenReturn(Optional.empty()); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.INTERNAL_ERROR); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test public void shouldReturnGasEstimateWhenTransientLegacyTransactionProcessorReturnsResultSuccess() { final JsonRpcRequestContext request = ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); - mockTransientProcessorResultGasEstimate(1L, true, false); + mockTransientProcessorResultGasEstimate(1L, true, false, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, Quantity.create(1L)); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test @@ -144,35 +148,32 @@ public void shouldUseGasPriceParameterWhenIsPresent() { final Wei gasPrice = Wei.of(1000); final JsonRpcRequestContext request = ethEstimateGasRequest(defaultLegacyTransactionCallParameter(gasPrice)); - mockTransientProcessorResultGasEstimate(1L, true, gasPrice, Optional.empty()); + mockTransientProcessorResultGasEstimate( + 1L, true, gasPrice, Optional.empty(), latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, Quantity.create(1L)); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test public void shouldReturnGasEstimateErrorWhenGasPricePresentForEip1559Transaction() { final JsonRpcRequestContext request = ethEstimateGasRequest(eip1559TransactionCallParameter(Optional.of(Wei.of(10)))); - mockTransientProcessorResultGasEstimate(1L, false, false); + mockTransientProcessorResultGasEstimate(1L, false, false, latestBlockHeader); Assertions.assertThatThrownBy(() -> method.response(request)) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("gasPrice cannot be used with baseFee or maxFeePerGas"); + .hasMessageContaining("gasPrice cannot be used with maxFeePerGas or maxPriorityFeePerGas"); } @Test public void shouldReturnGasEstimateWhenTransientEip1559TransactionProcessorReturnsResultSuccess() { final JsonRpcRequestContext request = ethEstimateGasRequest(eip1559TransactionCallParameter()); - mockTransientProcessorResultGasEstimate(1L, true, false); + mockTransientProcessorResultGasEstimate(1L, true, false, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, Quantity.create(1L)); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test @@ -180,28 +181,24 @@ public void shouldReturnGasEstimateErrorWhenGasPricePresentForEip1559Transaction shouldReturnGasEstimateErrorWhenTransientLegacyTransactionProcessorReturnsResultFailure() { final JsonRpcRequestContext request = ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); - mockTransientProcessorResultGasEstimate(1L, false, false); + mockTransientProcessorResultGasEstimate(1L, false, false, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.INTERNAL_ERROR); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test public void shouldReturnGasEstimateErrorWhenTransientEip1559TransactionProcessorReturnsResultFailure() { final JsonRpcRequestContext request = ethEstimateGasRequest(eip1559TransactionCallParameter()); - mockTransientProcessorResultGasEstimate(1L, false, false); + mockTransientProcessorResultGasEstimate(1L, false, false, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.INTERNAL_ERROR); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test @@ -209,28 +206,35 @@ public void shouldReturnErrorWhenLegacyTransactionProcessorReturnsTxInvalidReaso final JsonRpcRequestContext request = ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); mockTransientProcessorResultTxInvalidReason( - TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE); - - final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(null, RpcErrorType.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE); - - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE, + "transaction up-front cost 10 exceeds transaction sender account balance 5", + latestBlockHeader); + + final ValidationResult validationResult = + ValidationResult.invalid( + TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE, + "transaction up-front cost 10 exceeds transaction sender account balance 5"); + final JsonRpcError rpcError = JsonRpcError.from(validationResult); + final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError); + + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test public void shouldReturnErrorWhenEip1559TransactionProcessorReturnsTxInvalidReason() { final JsonRpcRequestContext request = ethEstimateGasRequest(eip1559TransactionCallParameter()); mockTransientProcessorResultTxInvalidReason( - TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE); - - final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(null, RpcErrorType.TRANSACTION_UPFRONT_COST_EXCEEDS_BALANCE); - - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE, + "transaction up-front cost 10 exceeds transaction sender account balance 5", + latestBlockHeader); + final ValidationResult validationResult = + ValidationResult.invalid( + TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE, + "transaction up-front cost 10 exceeds transaction sender account balance 5"); + final JsonRpcError rpcError = JsonRpcError.from(validationResult); + final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError); + + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test @@ -238,21 +242,21 @@ public void shouldReturnErrorWhenWorldStateIsNotAvailable() { when(worldStateArchive.isWorldStateAvailable(any(), any())).thenReturn(false); final JsonRpcRequestContext request = ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); - mockTransientProcessorResultGasEstimate(1L, false, false); + mockTransientProcessorResultGasEstimate(1L, false, false, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.WORLD_STATE_UNAVAILABLE); - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); + JsonRpcResponse theResponse = method.response(request); + + assertThat(theResponse).usingRecursiveComparison().isEqualTo(expectedResponse); } @Test public void shouldReturnErrorWhenTransactionReverted() { final JsonRpcRequestContext request = ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); - mockTransientProcessorResultGasEstimate(1L, false, true); + mockTransientProcessorResultGasEstimate(1L, false, true, latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, new JsonRpcError(RpcErrorType.REVERT_ERROR, "0x00")); @@ -262,7 +266,7 @@ public void shouldReturnErrorWhenTransactionReverted() { final JsonRpcResponse actualResponse = method.response(request); - Assertions.assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); + assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); assertThat(((JsonRpcErrorResponse) actualResponse).getError().getMessage()) .isEqualTo("Execution reverted"); @@ -280,7 +284,8 @@ public void shouldReturnErrorReasonWhenTransactionReverted() { + "00002545524332303a207472616e736665722066726f6d20746865207a65726f20" + "61646472657373000000000000000000000000000000000000000000000000000000"; - mockTransientProcessorTxReverted(1L, false, Bytes.fromHexString(executionRevertedReason)); + mockTransientProcessorTxReverted( + 1L, false, Bytes.fromHexString(executionRevertedReason), latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse( @@ -291,7 +296,7 @@ public void shouldReturnErrorReasonWhenTransactionReverted() { final JsonRpcResponse actualResponse = method.response(request); - Assertions.assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); + assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); assertThat(((JsonRpcErrorResponse) actualResponse).getError().getMessage()) .isEqualTo("Execution reverted: ERC20: transfer from the zero address"); @@ -307,7 +312,8 @@ public void shouldReturnABIDecodeErrorReasonWhenInvalidRevertReason() { "0x08c379a000000000000000000000000000000000000000000000000000000000" + "123451234512345123451234512345123451234512345123451234512345123451"; - mockTransientProcessorTxReverted(1L, false, Bytes.fromHexString(invalidRevertReason)); + mockTransientProcessorTxReverted( + 1L, false, Bytes.fromHexString(invalidRevertReason), latestBlockHeader); final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse( @@ -318,7 +324,7 @@ public void shouldReturnABIDecodeErrorReasonWhenInvalidRevertReason() { final JsonRpcResponse actualResponse = method.response(request); - Assertions.assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); + assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); assertThat(((JsonRpcErrorResponse) actualResponse).getError().getMessage()) .isEqualTo("Execution reverted: ABI decode error"); @@ -328,7 +334,7 @@ public void shouldReturnABIDecodeErrorReasonWhenInvalidRevertReason() { public void shouldIgnoreSenderBalanceAccountWhenStrictModeDisabled() { final JsonRpcRequestContext request = ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); - mockTransientProcessorResultGasEstimate(1L, false, true); + mockTransientProcessorResultGasEstimate(1L, false, true, latestBlockHeader); method.response(request); @@ -341,14 +347,14 @@ public void shouldIgnoreSenderBalanceAccountWhenStrictModeDisabled() { .isAllowExceedingBalance(true) .build()), any(OperationTracer.class), - eq(1L)); + eq(latestBlockHeader)); } @Test - public void shouldNotIgnoreSenderBalanceAccountWhenStrictModeDisabled() { + public void shouldNotIgnoreSenderBalanceAccountWhenStrictModeEnabled() { final JsonRpcRequestContext request = ethEstimateGasRequest(legacyTransactionCallParameter(Wei.ZERO, true)); - mockTransientProcessorResultGasEstimate(1L, false, true); + mockTransientProcessorResultGasEstimate(1L, false, true, latestBlockHeader); method.response(request); @@ -361,62 +367,116 @@ public void shouldNotIgnoreSenderBalanceAccountWhenStrictModeDisabled() { .isAllowExceedingBalance(false) .build()), any(OperationTracer.class), - eq(1L)); + eq(latestBlockHeader)); + } + + @Test + public void shouldIncludeHaltReasonWhenExecutionHalts() { + final JsonRpcRequestContext request = + ethEstimateGasRequest(defaultLegacyTransactionCallParameter(Wei.ZERO)); + mockTransientProcessorResultTxInvalidReason( + TransactionInvalidReason.EXECUTION_HALTED, "INVALID_OPERATION", latestBlockHeader); + + final ValidationResult validationResult = + ValidationResult.invalid(TransactionInvalidReason.EXECUTION_HALTED, "INVALID_OPERATION"); + final JsonRpcError rpcError = JsonRpcError.from(validationResult); + final JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, rpcError); + + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); + } + + @Test + public void shouldUseBlockTagParamWhenPresent() { + final JsonRpcRequestContext request = + ethEstimateGasRequest(eip1559TransactionCallParameter(), "finalized"); + mockTransientProcessorResultGasEstimate(1L, true, false, finalizedBlockHeader); + + final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, Quantity.create(1L)); + + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); } - private void mockTransientProcessorResultTxInvalidReason(final TransactionInvalidReason reason) { + @Test + public void shouldUseBlockNumberParamWhenPresent() { + final JsonRpcRequestContext request = + ethEstimateGasRequest(eip1559TransactionCallParameter(), "0x0"); + mockTransientProcessorResultGasEstimate(1L, true, false, genesisBlockHeader); + + final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, Quantity.create(1L)); + + assertThat(method.response(request)).usingRecursiveComparison().isEqualTo(expectedResponse); + } + + private void mockTransientProcessorResultTxInvalidReason( + final TransactionInvalidReason reason, + final String validationFailedErrorMessage, + final BlockHeader blockHeader) { final TransactionSimulatorResult mockTxSimResult = - getMockTransactionSimulatorResult(false, 0, Wei.ZERO, Optional.empty()); - when(mockTxSimResult.getValidationResult()).thenReturn(ValidationResult.invalid(reason)); + getMockTransactionSimulatorResult(false, 0, Wei.ZERO, Optional.empty(), blockHeader); + when(mockTxSimResult.getValidationResult()) + .thenReturn( + validationFailedErrorMessage == null + ? ValidationResult.invalid(reason) + : ValidationResult.invalid(reason, validationFailedErrorMessage)); } private void mockTransientProcessorTxReverted( - final long estimateGas, final boolean isSuccessful, final Bytes revertReason) { + final long estimateGas, + final boolean isSuccessful, + final Bytes revertReason, + final BlockHeader blockHeader) { mockTransientProcessorResultGasEstimate( - estimateGas, isSuccessful, Wei.ZERO, Optional.of(revertReason)); + estimateGas, isSuccessful, Wei.ZERO, Optional.of(revertReason), blockHeader); } private void mockTransientProcessorResultGasEstimate( - final long estimateGas, final boolean isSuccessful, final boolean isReverted) { + final long estimateGas, + final boolean isSuccessful, + final boolean isReverted, + final BlockHeader blockHeader) { mockTransientProcessorResultGasEstimate( estimateGas, isSuccessful, Wei.ZERO, - isReverted ? Optional.of(Bytes.of(0)) : Optional.empty()); + isReverted ? Optional.of(Bytes.of(0)) : Optional.empty(), + blockHeader); } private void mockTransientProcessorResultGasEstimate( final long estimateGas, final boolean isSuccessful, final Wei gasPrice, - final Optional revertReason) { - getMockTransactionSimulatorResult(isSuccessful, estimateGas, gasPrice, revertReason); + final Optional revertReason, + final BlockHeader blockHeader) { + getMockTransactionSimulatorResult( + isSuccessful, estimateGas, gasPrice, revertReason, blockHeader); } private TransactionSimulatorResult getMockTransactionSimulatorResult( final boolean isSuccessful, final long estimateGas, final Wei gasPrice, - final Optional revertReason) { + final Optional revertReason, + final BlockHeader blockHeader) { final TransactionSimulatorResult mockTxSimResult = mock(TransactionSimulatorResult.class); when(transactionSimulator.process( eq(modifiedLegacyTransactionCallParameter(gasPrice)), any(TransactionValidationParams.class), any(OperationTracer.class), - eq(1L))) + eq(blockHeader))) .thenReturn(Optional.of(mockTxSimResult)); when(transactionSimulator.process( eq(modifiedEip1559TransactionCallParameter()), any(TransactionValidationParams.class), any(OperationTracer.class), - eq(1L))) + eq(blockHeader))) .thenReturn(Optional.of(mockTxSimResult)); final TransactionProcessingResult mockResult = mock(TransactionProcessingResult.class); when(mockResult.getEstimateGasUsedByTransaction()).thenReturn(estimateGas); when(mockResult.getRevertReason()).thenReturn(revertReason); - when(mockTxSimResult.getResult()).thenReturn(mockResult); + when(mockTxSimResult.result()).thenReturn(mockResult); when(mockTxSimResult.isSuccessful()).thenReturn(isSuccessful); return mockTxSimResult; } @@ -427,18 +487,15 @@ private JsonCallParameter defaultLegacyTransactionCallParameter(final Wei gasPri private JsonCallParameter legacyTransactionCallParameter( final Wei gasPrice, final boolean isStrict) { - return new JsonCallParameter( - Address.fromHexString("0x0"), - Address.fromHexString("0x0"), - 0L, - gasPrice, - null, - null, - Wei.ZERO, - Bytes.EMPTY, - null, - isStrict, - null); + return new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0x0")) + .withTo(Address.fromHexString("0x0")) + .withGas(0L) + .withGasPrice(gasPrice) + .withValue(Wei.ZERO) + .withInput(Bytes.EMPTY) + .withStrict(isStrict) + .build(); } private CallParameter modifiedLegacyTransactionCallParameter(final Wei gasPrice) { @@ -459,18 +516,16 @@ private CallParameter eip1559TransactionCallParameter() { } private JsonCallParameter eip1559TransactionCallParameter(final Optional gasPrice) { - return new JsonCallParameter( - Address.fromHexString("0x0"), - Address.fromHexString("0x0"), - null, - gasPrice.orElse(null), - Wei.fromHexString("0x10"), - Wei.fromHexString("0x10"), - Wei.ZERO, - Bytes.EMPTY, - null, - false, - null); + return new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0x0")) + .withTo(Address.fromHexString("0x0")) + .withGasPrice(gasPrice.orElse(null)) + .withMaxPriorityFeePerGas(Wei.fromHexString("0x10")) + .withMaxFeePerGas(Wei.fromHexString("0x10")) + .withValue(Wei.ZERO) + .withInput(Bytes.EMPTY) + .withStrict(false) + .build(); } private CallParameter modifiedEip1559TransactionCallParameter() { @@ -490,4 +545,10 @@ private JsonRpcRequestContext ethEstimateGasRequest(final CallParameter callPara return new JsonRpcRequestContext( new JsonRpcRequest("2.0", "eth_estimateGas", new Object[] {callParameter})); } + + private JsonRpcRequestContext ethEstimateGasRequest( + final CallParameter callParameter, final String blockParam) { + return new JsonRpcRequestContext( + new JsonRpcRequest("2.0", "eth_estimateGas", new Object[] {callParameter, blockParam})); + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthFeeHistoryTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthFeeHistoryTest.java index eaf88bbf9e6..a04cb06c67a 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthFeeHistoryTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthFeeHistoryTest.java @@ -23,8 +23,12 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import org.hyperledger.besu.consensus.merge.blockcreation.MergeCoordinator; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.GasLimitCalculator; +import org.hyperledger.besu.ethereum.api.ApiConfiguration; +import org.hyperledger.besu.ethereum.api.ImmutableApiConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; @@ -35,6 +39,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.FeeHistory; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.ImmutableFeeHistory; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.ImmutableFeeHistoryResult; +import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; @@ -43,9 +48,12 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.mainnet.CancunTargetingGasLimitCalculator; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; +import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator; +import org.hyperledger.besu.evm.gascalculator.LondonGasCalculator; import java.util.ArrayList; import java.util.Arrays; @@ -64,6 +72,7 @@ public class EthFeeHistoryTest { private MutableBlockchain blockchain; private EthFeeHistory method; private ProtocolSchedule protocolSchedule; + private MiningCoordinator miningCoordinator; @BeforeEach public void setUp() { @@ -72,14 +81,21 @@ public void setUp() { blockchain = createInMemoryBlockchain(genesisBlock); gen.blockSequence(genesisBlock, 10) .forEach(block -> blockchain.appendBlock(block, gen.receipts(block))); - method = new EthFeeHistory(protocolSchedule, blockchain); + miningCoordinator = mock(MergeCoordinator.class); + when(miningCoordinator.getMinPriorityFeePerGas()).thenReturn(Wei.ONE); + + mockFork(); + + method = + new EthFeeHistory( + protocolSchedule, + blockchain, + miningCoordinator, + ImmutableApiConfiguration.builder().build()); } @Test public void params() { - final ProtocolSpec londonSpec = mock(ProtocolSpec.class); - when(londonSpec.getFeeMarket()).thenReturn(FeeMarket.london(5)); - when(protocolSchedule.getForNextBlockHeader(any(), anyLong())).thenReturn(londonSpec); // should fail because no required params given assertThatThrownBy(this::feeHistoryRequest).isInstanceOf(InvalidJsonRpcParameters.class); // should fail because newestBlock not given @@ -97,12 +113,7 @@ public void params() { @Test public void allFieldsPresentForLatestBlock() { - final ProtocolSpec londonSpec = mock(ProtocolSpec.class); - when(londonSpec.getFeeMarket()).thenReturn(FeeMarket.london(5)); - when(protocolSchedule.getForNextBlockHeader( - eq(blockchain.getChainHeadHeader()), - eq(blockchain.getChainHeadHeader().getTimestamp()))) - .thenReturn(londonSpec); + final Object latest = ((JsonRpcSuccessResponse) feeHistoryRequest("0x1", "latest", new double[] {100.0})) .getResult(); @@ -113,6 +124,8 @@ public void allFieldsPresentForLatestBlock() { .oldestBlock(10) .baseFeePerGas(List.of(Wei.of(25496L), Wei.of(28683L))) .gasUsedRatio(List.of(0.9999999992132459)) + .baseFeePerBlobGas(List.of(Wei.of(0), Wei.of(0))) + .blobGasUsedRatio(List.of(0.0)) .reward(List.of(List.of(Wei.of(1524763764L)))) .build())); } @@ -121,12 +134,63 @@ public void allFieldsPresentForLatestBlock() { public void shouldComputeRewardsCorrectly() { // Define the percentiles of rewards we want to compute List rewardPercentiles = - Arrays.asList(0.0, 5.0, 10.0, 30.0, 31.0, 59.0, 60.0, 61.0, 100.0); + Arrays.asList(0.0, 5.0, 10.0, 27.50, 31.0, 59.0, 60.0, 61.0, 100.0); + + Block block = mock(Block.class); + Blockchain blockchain = mockBlockchainTransactionsWithPriorityFee(block); + + EthFeeHistory ethFeeHistory = + new EthFeeHistory( + null, blockchain, miningCoordinator, ImmutableApiConfiguration.builder().build()); + + List rewards = ethFeeHistory.computeRewards(rewardPercentiles, block); // Define the expected rewards for each percentile // The expected rewards match the fees of the transactions at each percentile in the // rewardPercentiles list - List expectedRewards = Stream.of(1, 1, 2, 5, 5, 6, 6, 7, 7).map(Wei::of).toList(); + List expectedRewards = Stream.of(1, 1, 2, 4, 5, 6, 6, 7, 7).map(Wei::of).toList(); + + // Check that the number of computed rewards is equal to the number of requested percentiles + assertThat(rewards.size()).isEqualTo(rewardPercentiles.size()); + assertThat(expectedRewards).isEqualTo(rewards); + } + + @Test + public void shouldBoundRewardsCorrectly() { + // This test checks that the rewards are correctly bounded by the lower and upper limits. + // The lower and upper limits are defined by the lowerBoundPriorityFeeCoefficient and + // upperBoundPriorityFeeCoefficient in the ApiConfiguration. + // The lower limit is 2.0 (Wei.One * 200L / 100) and the upper limit is 5.0 (Wei.One * 500L / + // 100). + // The rewards are computed for a list of percentiles, and the expected bounded rewards are + // defined for each percentile. + // The test checks that the computed rewards match the expected bounded rewards. + + List rewardPercentiles = + Arrays.asList(0.0, 5.0, 10.0, 27.50, 31.0, 59.0, 60.0, 61.0, 100.0); + + Block block = mock(Block.class); + Blockchain blockchain = mockBlockchainTransactionsWithPriorityFee(block); + + ApiConfiguration apiConfiguration = + ImmutableApiConfiguration.builder() + .isGasAndPriorityFeeLimitingEnabled(true) + .lowerBoundGasAndPriorityFeeCoefficient(200L) // Min reward = Wei.One * 200L / 100 = 2.0 + .upperBoundGasAndPriorityFeeCoefficient(500L) + .build(); // Max reward = Wei.One * 500L / 100 = 5.0 + + EthFeeHistory ethFeeHistory = + new EthFeeHistory(null, blockchain, miningCoordinator, apiConfiguration); + + List rewards = ethFeeHistory.computeRewards(rewardPercentiles, block); + + // Define the expected bounded rewards for each percentile + List expectedBoundedRewards = Stream.of(2, 2, 2, 4, 5, 5, 5, 5, 5).map(Wei::of).toList(); + assertThat(expectedBoundedRewards).isEqualTo(rewards); + } + + private Blockchain mockBlockchainTransactionsWithPriorityFee(final Block block) { + final Blockchain blockchain = mock(Blockchain.class); // Define a list of gas used and fee pairs. Each pair represents a transaction in the block. // The first number is the gas used by the transaction, and the second number the fee. @@ -141,21 +205,6 @@ public void shouldComputeRewardsCorrectly() { gasUsedAndFee.add(new Object[] {800, 7L}); // 100.0% Collections.shuffle(gasUsedAndFee); - Block block = mock(Block.class); - Blockchain blockchain = mockBlockchainTransactionsWithPriorityFee(gasUsedAndFee, block); - EthFeeHistory ethFeeHistory = new EthFeeHistory(null, blockchain); - - List rewards = ethFeeHistory.computeRewards(rewardPercentiles, block); - - // Check that the number of computed rewards is equal to the number of requested percentiles - assertThat(rewards.size()).isEqualTo(rewardPercentiles.size()); - assertThat(rewards).isEqualTo(expectedRewards); - } - - private Blockchain mockBlockchainTransactionsWithPriorityFee( - final List gasUsedAndFee, final Block block) { - final Blockchain blockchain = mock(Blockchain.class); - when(block.getHash()).thenReturn(Hash.wrap(Bytes32.wrap(Bytes.random(32)))); BlockBody body = mock(BlockBody.class); BlockHeader blockHeader = mock(BlockHeader.class); @@ -184,7 +233,7 @@ public void cantGetBlockHigherThanChainHead() { assertThat( ((JsonRpcErrorResponse) feeHistoryRequest("0x2", "11", new double[] {100.0})) .getErrorType()) - .isEqualTo(RpcErrorType.INVALID_PARAMS); + .isEqualTo(RpcErrorType.INVALID_BLOCK_NUMBER_PARAMS); } @Test @@ -192,38 +241,28 @@ public void blockCountBounds() { assertThat( ((JsonRpcErrorResponse) feeHistoryRequest("0x0", "latest", new double[] {100.0})) .getErrorType()) - .isEqualTo(RpcErrorType.INVALID_PARAMS); + .isEqualTo(RpcErrorType.INVALID_BLOCK_COUNT_PARAMS); assertThat( ((JsonRpcErrorResponse) feeHistoryRequest("0x401", "latest", new double[] {100.0})) .getErrorType()) - .isEqualTo(RpcErrorType.INVALID_PARAMS); + .isEqualTo(RpcErrorType.INVALID_BLOCK_COUNT_PARAMS); } @Test public void doesntGoPastChainHeadWithHighBlockCount() { - final ProtocolSpec londonSpec = mock(ProtocolSpec.class); - when(londonSpec.getFeeMarket()).thenReturn(FeeMarket.london(5)); - when(protocolSchedule.getForNextBlockHeader( - eq(blockchain.getChainHeadHeader()), - eq(blockchain.getChainHeadHeader().getTimestamp()))) - .thenReturn(londonSpec); final FeeHistory.FeeHistoryResult result = (ImmutableFeeHistoryResult) ((JsonRpcSuccessResponse) feeHistoryRequest("0x14", "latest")).getResult(); assertThat(Long.decode(result.getOldestBlock())).isEqualTo(0); assertThat(result.getBaseFeePerGas()).hasSize(12); assertThat(result.getGasUsedRatio()).hasSize(11); + assertThat(result.getBaseFeePerBlobGas()).hasSize(12); + assertThat(result.getBlobGasUsedRatio()).hasSize(11); assertThat(result.getReward()).isNull(); } @Test public void feeValuesAreInTheBlockCountAndHighestBlock() { - final ProtocolSpec londonSpec = mock(ProtocolSpec.class); - when(londonSpec.getFeeMarket()).thenReturn(FeeMarket.london(5)); - when(protocolSchedule.getForNextBlockHeader( - eq(blockchain.getChainHeadHeader()), - eq(blockchain.getChainHeadHeader().getTimestamp()))) - .thenReturn(londonSpec); double[] percentile = new double[] {100.0}; final Object ninth = @@ -237,12 +276,6 @@ public void feeValuesAreInTheBlockCountAndHighestBlock() { @Test public void feeValuesDontGoPastHighestBlock() { - final ProtocolSpec londonSpec = mock(ProtocolSpec.class); - when(londonSpec.getFeeMarket()).thenReturn(FeeMarket.london(5)); - when(protocolSchedule.getForNextBlockHeader( - eq(blockchain.getChainHeadHeader()), - eq(blockchain.getChainHeadHeader().getTimestamp()))) - .thenReturn(londonSpec); double[] percentile = new double[] {100.0}; final Object second = @@ -296,6 +329,70 @@ private void assertFeeMetadataSize(final Object feeObject, final int blockCount) assertThat(((ImmutableFeeHistoryResult) feeObject).getReward().size()).isEqualTo(blockCount); assertThat(((ImmutableFeeHistoryResult) feeObject).getGasUsedRatio().size()) .isEqualTo(blockCount); + assertThat(((ImmutableFeeHistoryResult) feeObject).getBaseFeePerBlobGas().size()) + .isEqualTo(blockCount + 1); + assertThat(((ImmutableFeeHistoryResult) feeObject).getBlobGasUsedRatio().size()) + .isEqualTo(blockCount); + } + + @Test + public void shouldCalculateBlobFeeCorrectly_preBlob() { + assertBlobBaseFee(List.of(Wei.ZERO, Wei.ZERO)); + } + + @Test + public void shouldCalculateBlobFeeCorrectly_postBlob() { + mockPostBlobFork(); + assertBlobBaseFee(List.of(Wei.ONE, Wei.ONE)); + } + + @Test + public void shouldCalculateBlobFeeCorrectly_transitionFork() { + mockTransitionBlobFork(); + assertBlobBaseFee(List.of(Wei.ZERO, Wei.ONE)); + } + + private void mockFork() { + final ProtocolSpec londonSpec = mock(ProtocolSpec.class); + when(londonSpec.getGasCalculator()).thenReturn(new LondonGasCalculator()); + when(londonSpec.getFeeMarket()).thenReturn(FeeMarket.london(5)); + when(londonSpec.getGasLimitCalculator()).thenReturn(mock(GasLimitCalculator.class)); + + when(protocolSchedule.getByBlockHeader(any())).thenReturn(londonSpec); + when(protocolSchedule.getForNextBlockHeader(any(), anyLong())).thenReturn(londonSpec); + } + + private void mockPostBlobFork() { + final ProtocolSpec cancunSpec = mock(ProtocolSpec.class); + when(cancunSpec.getGasCalculator()).thenReturn(new CancunGasCalculator()); + when(cancunSpec.getFeeMarket()).thenReturn(FeeMarket.cancun(5, Optional.empty())); + when(cancunSpec.getGasLimitCalculator()) + .thenReturn(mock(CancunTargetingGasLimitCalculator.class)); + when(protocolSchedule.getByBlockHeader(any())).thenReturn(cancunSpec); + when(protocolSchedule.getForNextBlockHeader(any(), anyLong())).thenReturn(cancunSpec); + } + + private void mockTransitionBlobFork() { + final ProtocolSpec cancunSpec = mock(ProtocolSpec.class); + when(cancunSpec.getGasCalculator()).thenReturn(new CancunGasCalculator()); + when(cancunSpec.getFeeMarket()).thenReturn(FeeMarket.cancun(5, Optional.empty())); + when(cancunSpec.getGasLimitCalculator()) + .thenReturn(mock(CancunTargetingGasLimitCalculator.class)); + when(protocolSchedule.getForNextBlockHeader(any(), anyLong())).thenReturn(cancunSpec); + } + + private void assertBlobBaseFee(final List baseFeePerBlobGas) { + final Object latest = ((JsonRpcSuccessResponse) feeHistoryRequest("0x1", "latest")).getResult(); + assertThat(latest) + .isEqualTo( + FeeHistory.FeeHistoryResult.from( + ImmutableFeeHistory.builder() + .oldestBlock(10) + .baseFeePerGas(List.of(Wei.of(25496L), Wei.of(28683L))) + .gasUsedRatio(List.of(0.9999999992132459)) + .baseFeePerBlobGas(baseFeePerBlobGas) + .blobGasUsedRatio(List.of(0.0)) + .build())); } private JsonRpcResponse feeHistoryRequest(final Object... params) { diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGasPriceTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGasPriceTest.java index 27462501010..e4282864447 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGasPriceTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGasPriceTest.java @@ -15,7 +15,11 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; @@ -23,52 +27,64 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.api.ApiConfiguration; import org.hyperledger.besu.ethereum.api.ImmutableApiConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; -import org.hyperledger.besu.ethereum.blockcreation.PoWMiningCoordinator; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.feemarket.CancunFeeMarket; +import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; +import org.hyperledger.besu.ethereum.mainnet.feemarket.LegacyFeeMarket; +import org.hyperledger.besu.ethereum.mainnet.feemarket.LondonFeeMarket; import org.hyperledger.besu.evm.log.LogsBloomFilter; +import java.util.HashMap; import java.util.List; import java.util.Optional; +import java.util.stream.IntStream; +import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mock; -import org.mockito.internal.verification.VerificationModeFactory; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) public class EthGasPriceTest { + private static final String JSON_RPC_VERSION = "2.0"; + private static final String ETH_METHOD = "eth_gasPrice"; + private static final long DEFAULT_BLOCK_GAS_LIMIT = 100_000; + private static final long DEFAULT_BLOCK_GAS_USED = 21_000; + private static final Wei DEFAULT_MIN_GAS_PRICE = Wei.of(1_000); + private static final Wei DEFAULT_BASE_FEE = Wei.of(100_000); - @Mock private PoWMiningCoordinator miningCoordinator; + @Mock private ProtocolSchedule protocolSchedule; @Mock private Blockchain blockchain; private EthGasPrice method; - private final String JSON_RPC_VERSION = "2.0"; - private final String ETH_METHOD = "eth_gasPrice"; + private MiningParameters miningParameters; @BeforeEach public void setUp() { - method = - new EthGasPrice( - new BlockchainQueries( - blockchain, - null, - Optional.empty(), - Optional.empty(), - ImmutableApiConfiguration.builder().gasPriceMinSupplier(() -> 100).build()), - miningCoordinator); + ApiConfiguration apiConfig = createDefaultApiConfiguration(); + miningParameters = + MiningParameters.newDefault().setMinTransactionGasPrice(DEFAULT_MIN_GAS_PRICE); + method = createEthGasPriceMethod(apiConfig); } @Test @@ -79,135 +95,334 @@ public void returnsCorrectMethodName() { @Test public void shouldReturnMinValueWhenNoTransactionsExist() { final JsonRpcRequestContext request = requestWithParams(); - final String expectedWei = "0x4d2"; + final String expectedWei = "0x4d2"; // minGasPrice > nextBlockBaseFee + miningParameters.setMinTransactionGasPrice(Wei.fromHexString(expectedWei)); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(request.getRequest().getId(), expectedWei); - when(miningCoordinator.getMinTransactionGasPrice()).thenReturn(Wei.of(1234)); - when(blockchain.getChainHeadBlockNumber()).thenReturn(1000L); - when(blockchain.getBlockByNumber(anyLong())) - .thenAnswer(invocation -> createEmptyBlock(invocation.getArgument(0, Long.class))); + mockBaseFeeMarket(); + + mockBlockchain(1000, 0); final JsonRpcResponse actualResponse = method.response(request); assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); - verify(miningCoordinator).getMinTransactionGasPrice(); - verifyNoMoreInteractions(miningCoordinator); + verify(blockchain).getChainHeadBlock(); + verify(blockchain, times(99)).getBlockByNumber(anyLong()); + verifyNoMoreInteractions(blockchain); + } + + @Test + public void shouldReturnBaseFeeAsMinValueOnGenesis() { + final JsonRpcRequestContext request = requestWithParams(); + final String expectedWei = + DEFAULT_BASE_FEE.toShortHexString(); // nextBlockBaseFee > minGasPrice + miningParameters.setMinTransactionGasPrice(Wei.fromHexString(expectedWei)); + final JsonRpcResponse expectedResponse = + new JsonRpcSuccessResponse(request.getRequest().getId(), expectedWei); + + mockBaseFeeMarket(); + + mockBlockchain(0, 0); - verify(blockchain).getChainHeadBlockNumber(); - verify(blockchain, VerificationModeFactory.times(100)).getBlockByNumber(anyLong()); + final JsonRpcResponse actualResponse = method.response(request); + assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); + + verify(blockchain).getChainHeadBlock(); verifyNoMoreInteractions(blockchain); } @Test public void shouldReturnMedianWhenTransactionsExist() { final JsonRpcRequestContext request = requestWithParams(); - final String expectedWei = "0x389fd980"; // 950Wei, gas prices are 900-999 wei. + final String expectedWei = "0x911c70"; // 9.51 mwei, gas prices are 9.01-10 mwei. final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(request.getRequest().getId(), expectedWei); - when(blockchain.getChainHeadBlockNumber()).thenReturn(1000L); - when(blockchain.getBlockByNumber(anyLong())) - .thenAnswer(invocation -> createFakeBlock(invocation.getArgument(0, Long.class))); + mockBaseFeeMarket(); + + mockBlockchain(1000L, 1); final JsonRpcResponse actualResponse = method.response(request); assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); - verifyNoMoreInteractions(miningCoordinator); - - verify(blockchain).getChainHeadBlockNumber(); - verify(blockchain, VerificationModeFactory.times(100)).getBlockByNumber(anyLong()); + verify(blockchain).getChainHeadBlock(); + verify(blockchain, times(99)).getBlockByNumber(anyLong()); verifyNoMoreInteractions(blockchain); } @Test public void shortChainQueriesAllBlocks() { final JsonRpcRequestContext request = requestWithParams(); - final String expectedWei = "0x2625a00"; + final String expectedWei = "0x64190"; // 410 kwei final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(request.getRequest().getId(), expectedWei); - when(blockchain.getChainHeadBlockNumber()).thenReturn(80L); - when(blockchain.getBlockByNumber(anyLong())) - .thenAnswer(invocation -> createFakeBlock(invocation.getArgument(0, Long.class))); + mockBaseFeeMarket(); + + mockBlockchain(80L, 1); final JsonRpcResponse actualResponse = method.response(request); assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); - verifyNoMoreInteractions(miningCoordinator); + verify(blockchain).getChainHeadBlock(); + verify(blockchain, times(80)).getBlockByNumber(anyLong()); + verifyNoMoreInteractions(blockchain); + } - verify(blockchain).getChainHeadBlockNumber(); - verify(blockchain, VerificationModeFactory.times(80)).getBlockByNumber(anyLong()); + /** + * Test to verify that the method returns the lower bound gas price when the lower bound parameter + * is present. + */ + @Test + public void shouldReturnLimitedPriceWhenLowerBoundIsPresent() { + long expectedGasPrice = 31_841 * 2; + long lowerBoundCoefficient = 200; + verifyGasPriceLimit(lowerBoundCoefficient, null, expectedGasPrice); + } + + /** + * Test to verify that the method returns the upper bound gas price when the upper bound parameter + * is present. + */ + @Test + public void shouldReturnLimitedPriceWhenUpperBoundIsPresent() { + long expectedGasPrice = (long) (31_841 * 1.5); + long upperBoundCoefficient = 150; + verifyGasPriceLimit(null, upperBoundCoefficient, expectedGasPrice); + } + + /** + * Test to verify that the method returns the actual gas price when the gas price is within the + * bound range. + */ + @Test + public void shouldReturnActualGasPriceWhenWithinBoundRange() { + long gasPrice = 60_000; + long lowerBoundCoefficient = 120; + long upperBoundCoefficient = 200; + verifyGasPriceLimit(lowerBoundCoefficient, upperBoundCoefficient, gasPrice); + } + + private static Stream ethGasPriceAtGenesis() { + return Stream.of( + // base fee > min gas price + Arguments.of( + DEFAULT_MIN_GAS_PRICE.divide(2), + Optional.of(DEFAULT_MIN_GAS_PRICE), + DEFAULT_MIN_GAS_PRICE.subtract( + DEFAULT_MIN_GAS_PRICE + .multiply(125) + .divide(1000)) // expect base fee for the 1st block + ), + // base fee < min gas price + Arguments.of( + DEFAULT_BASE_FEE.multiply(2), + Optional.of(DEFAULT_BASE_FEE), + DEFAULT_BASE_FEE.multiply(2)) // expect min gas price value + , + + // no base fee market + Arguments.of( + DEFAULT_MIN_GAS_PRICE, + Optional.empty(), + DEFAULT_MIN_GAS_PRICE // expect min gas price value + )); + } + + @ParameterizedTest + @MethodSource("ethGasPriceAtGenesis") + public void ethGasPriceAtGenesis( + final Wei minGasPrice, final Optional maybeGenesisBaseFee, final Wei expectedGasPrice) { + miningParameters.setMinTransactionGasPrice(minGasPrice); + + if (maybeGenesisBaseFee.isPresent()) { + mockBaseFeeMarket(); + mockBlockchain(maybeGenesisBaseFee.get(), 0, 0); + } else { + mockGasPriceMarket(); + mockBlockchain(null, 0, 0); + } + + final JsonRpcRequestContext request = requestWithParams(); + final JsonRpcResponse expectedResponse = + new JsonRpcSuccessResponse( + request.getRequest().getId(), expectedGasPrice.toShortHexString()); + + final JsonRpcResponse actualResponse = method.response(request); + assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); + + verify(blockchain).getChainHeadBlock(); verifyNoMoreInteractions(blockchain); } - private Object createFakeBlock(final Long height) { - return Optional.of( - new Block( - new BlockHeader( - Hash.EMPTY, - Hash.EMPTY_TRIE_HASH, - Address.ZERO, - Hash.EMPTY_TRIE_HASH, - Hash.EMPTY_TRIE_HASH, - Hash.EMPTY_TRIE_HASH, - LogsBloomFilter.builder().build(), - Difficulty.ONE, - height, - 0, - 0, - 0, - Bytes.EMPTY, - Wei.ZERO, - Hash.EMPTY, - 0, - null, - null, - null, - null, - null, - null), - new BlockBody( - List.of( - new Transaction.Builder() - .nonce(0) - .gasPrice(Wei.of(height * 1000000L)) - .gasLimit(0) - .value(Wei.ZERO) - .build()), - List.of()))); - } - - private Object createEmptyBlock(final Long height) { - return Optional.of( - new Block( - new BlockHeader( - Hash.EMPTY, - Hash.EMPTY_TRIE_HASH, - Address.ZERO, - Hash.EMPTY_TRIE_HASH, - Hash.EMPTY_TRIE_HASH, - Hash.EMPTY_TRIE_HASH, - LogsBloomFilter.builder().build(), - Difficulty.ONE, - height, - 0, - 0, - 0, - Bytes.EMPTY, - Wei.ZERO, - Hash.EMPTY, - 0, - null, - null, - null, - null, - null, - null), - new BlockBody(List.of(), List.of()))); + /** + * Helper method to verify the gas price limit. + * + * @param lowerBoundCoefficient The lower bound of the gas price. + * @param upperBoundCoefficient The upper bound of the gas price. + * @param expectedGasPrice The expected gas price. + */ + private void verifyGasPriceLimit( + final Long lowerBoundCoefficient, + final Long upperBoundCoefficient, + final long expectedGasPrice) { + miningParameters.setMinTransactionGasPrice(Wei.of(100)); + + mockBaseFeeMarket(); + + var apiConfig = + createApiConfiguration( + Optional.ofNullable(lowerBoundCoefficient), Optional.ofNullable(upperBoundCoefficient)); + method = createEthGasPriceMethod(apiConfig); + + final JsonRpcRequestContext request = requestWithParams(); + final JsonRpcResponse expectedResponse = + new JsonRpcSuccessResponse( + request.getRequest().getId(), Wei.of(expectedGasPrice).toShortHexString()); + + final var chainHeadBlockNumber = 10L; + mockBlockchain(chainHeadBlockNumber, 1); + + final JsonRpcResponse actualResponse = method.response(request); + assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); + } + + private void mockBaseFeeMarket() { + mockFeeMarket(new LondonFeeMarket(0)); + } + + private void mockGasPriceMarket() { + mockFeeMarket(new LegacyFeeMarket()); + } + + private void mockFeeMarket(final FeeMarket feeMarket) { + final var protocolSpec = mock(ProtocolSpec.class); + when(protocolSpec.getFeeMarket()).thenReturn(feeMarket); + when(protocolSchedule.getForNextBlockHeader(any(), anyLong())).thenReturn(protocolSpec); + } + + private void mockBlockchain(final long chainHeadBlockNumber, final int txsNum) { + mockBlockchain(DEFAULT_BASE_FEE, chainHeadBlockNumber, txsNum); + } + + private void mockBlockchain( + final Wei genesisBaseFee, final long chainHeadBlockNumber, final int txsNum) { + final var blocksByNumber = new HashMap(); + + final var genesisBlock = createFakeBlock(0, 0, genesisBaseFee); + blocksByNumber.put(0L, genesisBlock); + + final var baseFeeMarket = new CancunFeeMarket(0, Optional.empty()); + + var baseFee = genesisBaseFee; + for (long i = 1; i <= chainHeadBlockNumber; i++) { + final var parentHeader = blocksByNumber.get(i - 1).getHeader(); + baseFee = + baseFeeMarket.computeBaseFee( + i, + parentHeader.getBaseFee().get(), + parentHeader.getGasUsed(), + parentHeader.getGasLimit()); + blocksByNumber.put(i, createFakeBlock(i, txsNum, baseFee)); + } + + when(blockchain.getChainHeadBlock()).thenReturn(blocksByNumber.get(chainHeadBlockNumber)); + if (chainHeadBlockNumber > 0) { + when(blockchain.getBlockByNumber(anyLong())) + .thenAnswer( + invocation -> Optional.of(blocksByNumber.get(invocation.getArgument(0, Long.class)))); + } + lenient() + .when(blockchain.getChainHeadHeader()) + .thenReturn(blocksByNumber.get(chainHeadBlockNumber).getHeader()); + } + + private Block createFakeBlock(final long height, final int txsNum, final Wei baseFee) { + return createFakeBlock( + height, txsNum, baseFee, DEFAULT_BLOCK_GAS_LIMIT, DEFAULT_BLOCK_GAS_USED * txsNum); + } + + private Block createFakeBlock( + final long height, + final int txsNum, + final Wei baseFee, + final long gasLimit, + final long gasUsed) { + return new Block( + new BlockHeader( + Hash.EMPTY, + Hash.EMPTY_TRIE_HASH, + Address.ZERO, + Hash.EMPTY_TRIE_HASH, + Hash.EMPTY_TRIE_HASH, + Hash.EMPTY_TRIE_HASH, + LogsBloomFilter.builder().build(), + Difficulty.ONE, + height, + gasLimit, + gasUsed, + 0, + Bytes.EMPTY, + baseFee, + Hash.EMPTY, + 0, + null, + null, + null, + null, + null, + null), + new BlockBody( + IntStream.range(0, txsNum) + .mapToObj( + i -> + new Transaction.Builder() + .nonce(i) + .gasPrice(Wei.of(height * 10_000L)) + .gasLimit(gasUsed) + .value(Wei.ZERO) + .build()) + .toList(), + List.of())); } private JsonRpcRequestContext requestWithParams(final Object... params) { return new JsonRpcRequestContext(new JsonRpcRequest(JSON_RPC_VERSION, ETH_METHOD, params)); } + + private ApiConfiguration createDefaultApiConfiguration() { + return createApiConfiguration(Optional.empty(), Optional.empty()); + } + + private ApiConfiguration createApiConfiguration( + final Optional lowerBoundCoefficient, final Optional upperBoundCoefficient) { + ImmutableApiConfiguration.Builder builder = ImmutableApiConfiguration.builder(); + + lowerBoundCoefficient.ifPresent( + value -> + builder + .isGasAndPriorityFeeLimitingEnabled(true) + .lowerBoundGasAndPriorityFeeCoefficient(value)); + upperBoundCoefficient.ifPresent( + value -> + builder + .isGasAndPriorityFeeLimitingEnabled(true) + .upperBoundGasAndPriorityFeeCoefficient(value)); + + return builder.build(); + } + + private EthGasPrice createEthGasPriceMethod(final ApiConfiguration apiConfig) { + return new EthGasPrice( + new BlockchainQueries( + protocolSchedule, + blockchain, + null, + Optional.empty(), + Optional.empty(), + apiConfig, + miningParameters), + apiConfig); + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockByHashTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockByHashTest.java index 1747030ba60..e79fff04f7f 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockByHashTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockByHashTest.java @@ -69,7 +69,7 @@ public void exceptionWhenNoHashSupplied() { public void exceptionWhenNoBoolSupplied() { assertThatThrownBy(() -> method.response(requestWithParams(ZERO_HASH))) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 1"); + .hasMessage("Invalid return complete transaction parameter (index 1)"); verifyNoMoreInteractions(blockchainQueries); } @@ -77,8 +77,7 @@ public void exceptionWhenNoBoolSupplied() { public void exceptionWhenHashParamInvalid() { assertThatThrownBy(() -> method.response(requestWithParams("hash", "true"))) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage( - "Invalid json rpc parameter at index 0. Supplied value was: 'hash' of type: 'java.lang.String' - expected type: 'org.hyperledger.besu.datatypes.Hash'"); + .hasMessage("Invalid block hash parameter (index 0)"); verifyNoMoreInteractions(blockchainQueries); } @@ -86,8 +85,7 @@ public void exceptionWhenHashParamInvalid() { public void exceptionWhenBoolParamInvalid() { assertThatThrownBy(() -> method.response(requestWithParams(ZERO_HASH, "maybe"))) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage( - "Invalid json rpc parameter at index 1. Supplied value was: 'maybe' of type: 'java.lang.String' - expected type: 'java.lang.Boolean'"); + .hasMessage("Invalid return complete transaction parameter (index 1)"); verifyNoMoreInteractions(blockchainQueries); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockByNumberTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockByNumberTest.java index 8eafb35f730..254d0fbe9e8 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockByNumberTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockByNumberTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -26,7 +26,6 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponseType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResult; @@ -36,9 +35,12 @@ import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockDataGenerator; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Synchronizer; import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; import java.util.List; @@ -66,6 +68,7 @@ public class EthGetBlockByNumberTest { private EthGetBlockByNumber method; @Mock private Synchronizer synchronizer; @Mock private WorldStateArchive worldStateArchive; + @Mock private ProtocolSchedule protocolSchedule; @BeforeEach public void setUp() { @@ -87,7 +90,10 @@ public void setUp() { latestHeader.getStateRoot(), latestHeader.getHash())) .thenReturn(Boolean.TRUE); - blockchainQueries = spy(new BlockchainQueries(blockchain, worldStateArchive)); + blockchainQueries = + spy( + new BlockchainQueries( + protocolSchedule, blockchain, worldStateArchive, MiningParameters.newDefault())); method = new EthGetBlockByNumber(blockchainQueries, blockResult, synchronizer); } @@ -115,7 +121,7 @@ public void exceptionWhenNoNumberSupplied() { public void exceptionWhenNoBoolSupplied() { assertThatThrownBy(() -> method.response(requestWithParams("0"))) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 1"); + .hasMessage("Invalid return complete transaction parameter (index 1)"); verifyNoMoreInteractions(blockchainQueries); } @@ -123,8 +129,7 @@ public void exceptionWhenNoBoolSupplied() { public void exceptionWhenNumberParamInvalid() { assertThatThrownBy(() -> method.response(requestWithParams("invalid", "true"))) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage( - "Invalid json rpc parameter at index 0. Supplied value was: 'invalid' of type: 'java.lang.String' - expected type: 'org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter'"); + .hasMessage("Invalid block parameter (index 0)"); verifyNoMoreInteractions(blockchainQueries); } @@ -132,15 +137,14 @@ public void exceptionWhenNumberParamInvalid() { public void exceptionWhenBoolParamInvalid() { assertThatThrownBy(() -> method.response(requestWithParams("0", "maybe"))) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage( - "Invalid json rpc parameter at index 1. Supplied value was: 'maybe' of type: 'java.lang.String' - expected type: 'java.lang.Boolean'"); + .hasMessage("Invalid return complete transaction parameter (index 1)"); verifyNoMoreInteractions(blockchainQueries); } @Test public void errorWhenAskingFinalizedButFinalizedIsNotPresent() { JsonRpcResponse resp = method.response(requestWithParams("finalized", "false")); - assertThat(resp.getType()).isEqualTo(JsonRpcResponseType.ERROR); + assertThat(resp.getType()).isEqualTo(RpcResponseType.ERROR); JsonRpcErrorResponse errorResp = (JsonRpcErrorResponse) resp; assertThat(errorResp.getErrorType()).isEqualTo(RpcErrorType.UNKNOWN_BLOCK); } @@ -148,7 +152,7 @@ public void errorWhenAskingFinalizedButFinalizedIsNotPresent() { @Test public void errorWhenAskingSafeButSafeIsNotPresent() { JsonRpcResponse resp = method.response(requestWithParams("safe", "false")); - assertThat(resp.getType()).isEqualTo(JsonRpcResponseType.ERROR); + assertThat(resp.getType()).isEqualTo(RpcResponseType.ERROR); JsonRpcErrorResponse errorResp = (JsonRpcErrorResponse) resp; assertThat(errorResp.getErrorType()).isEqualTo(RpcErrorType.UNKNOWN_BLOCK); } @@ -175,7 +179,7 @@ public void successWhenAskingSafe() { private void assertSuccess(final String tag, final long height) { JsonRpcResponse resp = method.response(requestWithParams(tag, "false")); - assertThat(resp.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); + assertThat(resp.getType()).isEqualTo(RpcResponseType.SUCCESS); JsonRpcSuccessResponse successResp = (JsonRpcSuccessResponse) resp; BlockResult blockResult = (BlockResult) successResp.getResult(); assertThat(blockResult.getHash()) diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockReceiptsTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockReceiptsTest.java index dad5099ee7c..545b43a6e70 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockReceiptsTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetBlockReceiptsTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -35,6 +35,7 @@ import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockDataGenerator; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; @@ -82,7 +83,10 @@ public void setUp() { blockchain.appendBlock(block, receipts); } - blockchainQueries = spy(new BlockchainQueries(blockchain, worldStateArchive)); + blockchainQueries = + spy( + new BlockchainQueries( + protocolSchedule, blockchain, worldStateArchive, MiningParameters.newDefault())); protocolSchedule = mock(ProtocolSchedule.class); method = new EthGetBlockReceipts(blockchainQueries, protocolSchedule); } @@ -96,7 +100,7 @@ public void returnsCorrectMethodName() { public void exceptionWhenNoParamsSupplied() { assertThatThrownBy(() -> method.response(requestWithParams())) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 0"); + .hasMessage("Invalid block or block hash parameters (index 0)"); verifyNoMoreInteractions(blockchainQueries); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetFilterChangesTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetFilterChangesTest.java index 7bf8e1a9dde..584702d7464 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetFilterChangesTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetFilterChangesTest.java @@ -69,9 +69,8 @@ public void shouldThrowExceptionWhenNoParamsSupplied() { final Throwable thrown = catchThrowable(() -> method.response(request)); assertThat(thrown) - .hasNoCause() .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 0"); + .hasMessage("Invalid filter ID parameter (index 0)"); verifyNoInteractions(filterManager); } @@ -83,7 +82,7 @@ public void shouldThrowExceptionWhenInvalidParamsSupplied() { final Throwable thrown = catchThrowable(() -> method.response(request)); assertThat(thrown) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 0"); + .hasMessage("Invalid filter ID parameter (index 0)"); verifyNoInteractions(filterManager); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetFilterLogsTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetFilterLogsTest.java index ee9723c6672..c7b4ae85071 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetFilterLogsTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetFilterLogsTest.java @@ -70,7 +70,7 @@ public void shouldReturnErrorWhenMissingParams() { final Throwable thrown = catchThrowable(() -> method.response(request)); assertThat(thrown) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 0"); + .hasMessage("Invalid filter ID parameter (index 0)"); verifyNoInteractions(filterManager); } @@ -82,7 +82,7 @@ public void shouldReturnErrorWhenMissingFilterId() { final Throwable thrown = catchThrowable(() -> method.response(request)); assertThat(thrown) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 0"); + .hasMessage("Invalid filter ID parameter (index 0)"); verifyNoInteractions(filterManager); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetLogsTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetLogsTest.java index a33abc71dd1..93cb216011b 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetLogsTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetLogsTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -73,7 +73,7 @@ public void shouldReturnErrorWhenMissingParams() { final Throwable thrown = catchThrowable(() -> method.response(request)); assertThat(thrown) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 0"); + .hasMessage("Invalid filter parameter (index 0)"); verifyNoInteractions(blockchainQueries); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetMinerDataByBlockHashTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetMinerDataByBlockHashTest.java index 653f6835f35..9d93dde85ae 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetMinerDataByBlockHashTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetMinerDataByBlockHashTest.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; import static org.assertj.core.api.Assertions.assertThat; @@ -110,7 +109,7 @@ public void exceptionWhenNoHashSuppliedTest() { JsonRpcRequestContext requestContext = new JsonRpcRequestContext(request); assertThatThrownBy(() -> method.response(requestContext)) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 0"); + .hasMessage("Invalid block hash parameter (index 0)"); verifyNoMoreInteractions(blockchainQueries); } @@ -121,7 +120,7 @@ public void exceptionWhenHashParamInvalidTest() { JsonRpcRequestContext requestContext = new JsonRpcRequestContext(request); assertThatThrownBy(() -> method.response(requestContext)) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Invalid json rpc parameter at index 0"); + .hasMessageContaining("Invalid block hash parameter (index 0)"); verifyNoMoreInteractions(blockchainQueries); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetMinerDataByBlockNumberTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetMinerDataByBlockNumberTest.java index 5c0dc630bc2..9295e64da91 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetMinerDataByBlockNumberTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetMinerDataByBlockNumberTest.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; import static org.assertj.core.api.Assertions.assertThat; @@ -104,7 +103,7 @@ public void exceptionWhenNoNumberSuppliedTest() { JsonRpcRequestContext requestContext = new JsonRpcRequestContext(request); assertThatThrownBy(() -> method.response(requestContext)) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 0"); + .hasMessage("Invalid block parameter (index 0)"); verifyNoMoreInteractions(blockchainQueries); } @@ -115,7 +114,7 @@ public void exceptionWhenNumberParamInvalidTest() { JsonRpcRequestContext requestContext = new JsonRpcRequestContext(request); assertThatThrownBy(() -> method.response(requestContext)) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Invalid json rpc parameter at index 0"); + .hasMessageContaining("Invalid block parameter"); verifyNoMoreInteractions(blockchainQueries); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetProofTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetProofTest.java index 2f7d82d1bf5..06a4b9d0e7c 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetProofTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetProofTest.java @@ -39,6 +39,8 @@ import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.ChainHead; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.proof.WorldStateProof; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; @@ -63,6 +65,7 @@ @MockitoSettings(strictness = Strictness.LENIENT) class EthGetProofTest { @Mock private Blockchain blockchain; + @Mock private ProtocolSchedule protocolSchedule; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private WorldStateArchive archive; @@ -83,7 +86,10 @@ class EthGetProofTest { @BeforeEach public void setUp() { - blockchainQueries = spy(new BlockchainQueries(blockchain, archive)); + blockchainQueries = + spy( + new BlockchainQueries( + protocolSchedule, blockchain, archive, MiningParameters.newDefault())); when(blockchainQueries.getBlockchain()).thenReturn(blockchain); when(blockchainQueries.headBlockNumber()).thenReturn(14L); when(blockchain.getChainHead()).thenReturn(chainHead); @@ -105,7 +111,7 @@ void errorWhenNoAddressAccountSupplied() { Assertions.assertThatThrownBy(() -> method.response(request)) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Missing required json rpc parameter at index 0"); + .hasMessageContaining("Invalid address parameter (index 0)"); } @Test @@ -114,7 +120,7 @@ void errorWhenNoStorageKeysSupplied() { Assertions.assertThatThrownBy(() -> method.response(request)) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Missing required json rpc parameter at index 1"); + .hasMessageContaining("Invalid storage keys parameters (index 1)"); } @Test @@ -123,7 +129,7 @@ void errorWhenNoBlockNumberSupplied() { Assertions.assertThatThrownBy(() -> method.response(request)) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Missing required json rpc parameter at index 2"); + .hasMessageContaining("Invalid block or block hash parameter"); } @Test diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionByHashTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionByHashTest.java index 42018f2aebc..48350ff9d0b 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionByHashTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionByHashTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -77,7 +77,7 @@ void shouldReturnErrorResponseIfMissingRequiredParameter() { final JsonRpcRequestContext context = new JsonRpcRequestContext(request); final JsonRpcErrorResponse expectedResponse = - new JsonRpcErrorResponse(request.getId(), RpcErrorType.INVALID_PARAMS); + new JsonRpcErrorResponse(request.getId(), RpcErrorType.INVALID_PARAM_COUNT); final JsonRpcResponse actualResponse = method.response(context); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionReceiptTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionReceiptTest.java index a773048d965..884cc504d8e 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionReceiptTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetTransactionReceiptTest.java @@ -42,11 +42,13 @@ import org.hyperledger.besu.ethereum.core.BlockDataGenerator; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.mainnet.PoWHasher; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.blockhash.FrontierBlockHashProcessor; import org.hyperledger.besu.ethereum.mainnet.feemarket.CancunFeeMarket; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; @@ -142,11 +144,12 @@ public class EthGetTransactionReceiptTest { null, GasLimitCalculator.constant(), FeeMarket.legacy(), - null, Optional.of(PoWHasher.ETHASH_LIGHT), null, Optional.empty(), null, + Optional.empty(), + new FrontierBlockHashProcessor(), true, true); private final ProtocolSpec statusTransactionTypeSpec = @@ -172,11 +175,12 @@ public class EthGetTransactionReceiptTest { null, GasLimitCalculator.constant(), FeeMarket.legacy(), - null, Optional.of(PoWHasher.ETHASH_LIGHT), null, Optional.empty(), null, + Optional.empty(), + new FrontierBlockHashProcessor(), true, true); @@ -186,7 +190,12 @@ public class EthGetTransactionReceiptTest { private final Blockchain blockchain = mock(Blockchain.class); private final BlockchainQueries blockchainQueries = - spy(new BlockchainQueries(blockchain, mock(WorldStateArchive.class))); + spy( + new BlockchainQueries( + protocolSchedule, + blockchain, + mock(WorldStateArchive.class), + MiningParameters.newDefault())); private final EthGetTransactionReceipt ethGetTransactionReceipt = new EthGetTransactionReceipt(blockchainQueries, protocolSchedule); private final String receiptString = diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleByBlockHashAndIndexTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleByBlockHashAndIndexTest.java index 07f8364615c..6b968380c67 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleByBlockHashAndIndexTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleByBlockHashAndIndexTest.java @@ -79,7 +79,7 @@ public void shouldReturnErrorWhenMissingBlockHashParam() { assertThat(thrown) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 0"); + .hasMessage("Invalid block hash parameter (index 0)"); } @Test @@ -90,7 +90,7 @@ public void shouldReturnErrorWhenMissingIndexParam() { assertThat(thrown) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 1"); + .hasMessage("Invalid block index parameter (index 1)"); } @Test @@ -101,7 +101,7 @@ public void shouldReturnErrorWhenInvalidBlockHashParam() { assertThat(thrown) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Invalid json rpc parameter at index 0"); + .hasMessageContaining("Invalid block hash parameter (index 0)"); } @Test @@ -113,7 +113,7 @@ public void shouldReturnErrorWhenInvalidIndexParam() { assertThat(thrown) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Invalid json rpc parameter at index 1"); + .hasMessageContaining("Invalid block index parameter (index 1)"); } @Test diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleByBlockNumberAndIndexTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleByBlockNumberAndIndexTest.java index 59b20d58466..746f63be75c 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleByBlockNumberAndIndexTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthGetUncleByBlockNumberAndIndexTest.java @@ -79,7 +79,7 @@ public void shouldReturnErrorWhenMissingBlockNumberParam() { assertThat(thrown) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 0"); + .hasMessage("Invalid block parameter (index 0)"); } @Test @@ -90,7 +90,7 @@ public void shouldReturnErrorWhenMissingIndexParam() { assertThat(thrown) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 1"); + .hasMessage("Invalid block index (index 1)"); } @Test diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthMaxPriorityFeePerGasTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthMaxPriorityFeePerGasTest.java new file mode 100644 index 00000000000..886c8a1d038 --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthMaxPriorityFeePerGasTest.java @@ -0,0 +1,239 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.TransactionType; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.api.ImmutableApiConfiguration; +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockBody; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.feemarket.CancunFeeMarket; +import org.hyperledger.besu.evm.log.LogsBloomFilter; + +import java.math.BigInteger; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; +import java.util.stream.IntStream; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class EthMaxPriorityFeePerGasTest { + private static final String JSON_RPC_VERSION = "2.0"; + private static final String ETH_METHOD = + RpcMethod.ETH_GET_MAX_PRIORITY_FEE_PER_GAS.getMethodName(); + private static final Wei DEFAULT_MIN_PRIORITY_FEE_PER_GAS = Wei.ZERO; + private static final long DEFAULT_BLOCK_GAS_LIMIT = 100_000; + private static final long DEFAULT_BLOCK_GAS_USED = 21_000; + private static final Wei DEFAULT_BASE_FEE = Wei.of(100_000); + + private EthMaxPriorityFeePerGas method; + @Mock private ProtocolSchedule protocolSchedule; + @Mock private Blockchain blockchain; + private MiningParameters miningParameters; + + @BeforeEach + public void setUp() { + miningParameters = + MiningParameters.newDefault().setMinPriorityFeePerGas(DEFAULT_MIN_PRIORITY_FEE_PER_GAS); + method = createEthMaxPriorityFeePerGasMethod(); + } + + @Test + public void shouldReturnCorrectMethodName() { + assertThat(method.getName()).isEqualTo(ETH_METHOD); + } + + @Test + public void whenNoTransactionsExistReturnMinPriorityFeePerGasPrice() { + final JsonRpcRequestContext request = requestWithParams(); + final Wei expectedWei = Wei.ONE; + miningParameters.setMinPriorityFeePerGas(expectedWei); + final JsonRpcResponse expectedResponse = + new JsonRpcSuccessResponse(request.getRequest().getId(), expectedWei.toShortHexString()); + + mockBlockchain(10, 0); + final JsonRpcResponse actualResponse = method.response(request); + assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); + } + + @Test + public void whenTransactionsExistReturnMedianMaxPriorityFeePerGasPrice() { + final JsonRpcRequestContext request = requestWithParams(); + final Wei expectedWei = Wei.of(51_000); // max priority fee per gas prices are 1000-100000 wei. + final JsonRpcResponse expectedResponse = + new JsonRpcSuccessResponse(request.getRequest().getId(), expectedWei.toShortHexString()); + + mockBlockchain(100, 1); + + final JsonRpcResponse actualResponse = method.response(request); + assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); + } + + @Test + public void returnMinPriorityFeePerGasWhenMedianValueIsLower() { + final JsonRpcRequestContext request = requestWithParams(); + final Wei expectedWei = Wei.of(100_000); + miningParameters.setMinPriorityFeePerGas(expectedWei); + + mockBlockchain(100, 1); + + // median value is 51000 wei, that is lower than the value this node is willing to accept, + // so the configured min priority fee per gas is returned. + final JsonRpcResponse expectedResponse = + new JsonRpcSuccessResponse(request.getRequest().getId(), expectedWei.toShortHexString()); + + final JsonRpcResponse actualResponse = method.response(request); + assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); + } + + @Test + public void atGenesisReturnMinPriorityFeePerGas() { + final JsonRpcRequestContext request = requestWithParams(); + final Wei expectedWei = Wei.ONE; + miningParameters.setMinPriorityFeePerGas(expectedWei); + final JsonRpcResponse expectedResponse = + new JsonRpcSuccessResponse(request.getRequest().getId(), expectedWei.toShortHexString()); + + mockBlockchain(0, 0); + final JsonRpcResponse actualResponse = method.response(request); + assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); + } + + private JsonRpcRequestContext requestWithParams(final Object... params) { + return new JsonRpcRequestContext(new JsonRpcRequest(JSON_RPC_VERSION, ETH_METHOD, params)); + } + + private void mockBlockchain(final long chainHeadBlockNumber, final int txsNum) { + final var genesisBaseFee = DEFAULT_BASE_FEE; + final var blocksByNumber = new HashMap(); + + final var genesisBlock = createFakeBlock(0, 0, genesisBaseFee); + blocksByNumber.put(0L, genesisBlock); + + final var baseFeeMarket = new CancunFeeMarket(0, Optional.empty()); + + var baseFee = genesisBaseFee; + for (long i = 1; i <= chainHeadBlockNumber; i++) { + final var parentHeader = blocksByNumber.get(i - 1).getHeader(); + baseFee = + baseFeeMarket.computeBaseFee( + i, + parentHeader.getBaseFee().get(), + parentHeader.getGasUsed(), + parentHeader.getGasLimit()); + blocksByNumber.put(i, createFakeBlock(i, txsNum, baseFee)); + } + + when(blockchain.getChainHeadBlock()).thenReturn(blocksByNumber.get(chainHeadBlockNumber)); + if (chainHeadBlockNumber > 0) { + when(blockchain.getBlockByNumber(anyLong())) + .thenAnswer( + invocation -> Optional.of(blocksByNumber.get(invocation.getArgument(0, Long.class)))); + } + lenient() + .when(blockchain.getChainHeadHeader()) + .thenReturn(blocksByNumber.get(chainHeadBlockNumber).getHeader()); + } + + private Block createFakeBlock(final long height, final int txsNum, final Wei baseFee) { + return createFakeBlock( + height, txsNum, baseFee, DEFAULT_BLOCK_GAS_LIMIT, DEFAULT_BLOCK_GAS_USED * txsNum); + } + + private Block createFakeBlock( + final long height, + final int txsNum, + final Wei baseFee, + final long gasLimit, + final long gasUsed) { + return new Block( + new BlockHeader( + Hash.EMPTY, + Hash.EMPTY_TRIE_HASH, + Address.ZERO, + Hash.EMPTY_TRIE_HASH, + Hash.EMPTY_TRIE_HASH, + Hash.EMPTY_TRIE_HASH, + LogsBloomFilter.builder().build(), + Difficulty.ONE, + height, + gasLimit, + gasUsed, + 0, + Bytes.EMPTY, + baseFee, + Hash.EMPTY, + 0, + null, + null, + null, + null, + null, + null), + new BlockBody( + IntStream.range(0, txsNum) + .mapToObj( + i -> + new Transaction.Builder() + .chainId(BigInteger.ONE) + .type(TransactionType.EIP1559) + .nonce(i) + .maxFeePerGas(Wei.of(height * 10_000L)) + .maxPriorityFeePerGas(Wei.of(height * 1_000L)) + .gasLimit(gasUsed) + .value(Wei.ZERO) + .build()) + .toList(), + List.of())); + } + + private EthMaxPriorityFeePerGas createEthMaxPriorityFeePerGasMethod() { + return new EthMaxPriorityFeePerGas( + new BlockchainQueries( + protocolSchedule, + blockchain, + null, + Optional.empty(), + Optional.empty(), + ImmutableApiConfiguration.builder().build(), + miningParameters)); + } +} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthNewFilterTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthNewFilterTest.java index edaf60d765a..c6ae2dc1017 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthNewFilterTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthNewFilterTest.java @@ -182,7 +182,7 @@ public void filterWithInvalidParameters() { final JsonRpcRequestContext request = ethNewFilter(invalidFilter); final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(null, RpcErrorType.INVALID_PARAMS); + new JsonRpcErrorResponse(null, RpcErrorType.INVALID_FILTER_PARAMS); final JsonRpcResponse response = method.response(request); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthSendRawTransactionTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthSendRawTransactionTest.java index 16cbbe3649a..35abfa66ce8 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthSendRawTransactionTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/EthSendRawTransactionTest.java @@ -57,7 +57,7 @@ public void requestIsMissingParameter() { new JsonRpcRequest("2.0", "eth_sendRawTransaction", new String[] {})); final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAM_COUNT); final JsonRpcResponse actualResponse = method.response(request); @@ -70,7 +70,7 @@ public void requestHasNullObjectParameter() { new JsonRpcRequestContext(new JsonRpcRequest("2.0", "eth_sendRawTransaction", null)); final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAM_COUNT); final JsonRpcResponse actualResponse = method.response(request); @@ -84,7 +84,7 @@ public void requestHasNullArrayParameter() { new JsonRpcRequest("2.0", "eth_sendRawTransaction", new String[] {null})); final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAM_COUNT); final JsonRpcResponse actualResponse = method.response(request); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceFilterTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceFilterTest.java new file mode 100644 index 00000000000..c5c03f6b114 --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TraceFilterTest.java @@ -0,0 +1,87 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.BlockParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.FilterParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockTracer; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; +import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; + +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class TraceFilterTest { + + private TraceFilter method; + + @Mock Supplier blockTracerSupplier; + @Mock ProtocolSchedule protocolSchedule; + @Mock BlockchainQueries blockchainQueries; + + @ParameterizedTest + @CsvSource({ + "0, 1001, 1000", "0, 5000, 1000", "1, 1002, 1000", "1, 6002, 1000", "1000, 3000, 1000", + "0, 501, 500", "0, 5000, 500", "1, 502, 500", "1, 6002, 500", "1000, 3000, 500" + }) + public void shouldFailIfParamsExceedMaxRange( + final long fromBlock, final long toBlock, final long maxFilterRange) { + final FilterParameter filterParameter = + new FilterParameter( + new BlockParameter(fromBlock), + new BlockParameter(toBlock), + null, + null, + null, + null, + null, + null, + null); + + JsonRpcRequestContext request = + new JsonRpcRequestContext( + new JsonRpcRequest("2.0", "trace_filter", new Object[] {filterParameter})); + + method = + new TraceFilter(blockTracerSupplier, protocolSchedule, blockchainQueries, maxFilterRange); + + final JsonRpcResponse response = method.response(request); + assertThat(response).isInstanceOf(JsonRpcErrorResponse.class); + + final JsonRpcErrorResponse errorResponse = (JsonRpcErrorResponse) response; + assertThat(errorResponse.getErrorType()).isEqualTo(RpcErrorType.EXCEEDS_RPC_MAX_BLOCK_RANGE); + } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } +} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TxPoolBesuPendingTransactionsTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TxPoolBesuPendingTransactionsTest.java index 738cae30970..1eef04fb93a 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TxPoolBesuPendingTransactionsTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/TxPoolBesuPendingTransactionsTest.java @@ -70,7 +70,7 @@ public void shouldReturnPendingTransactions() { final JsonRpcRequestContext request = new JsonRpcRequestContext( new JsonRpcRequest( - JSON_RPC_VERSION, TXPOOL_PENDING_TRANSACTIONS_METHOD, new Object[] {100})); + JSON_RPC_VERSION, TXPOOL_PENDING_TRANSACTIONS_METHOD, new Object[] {})); final JsonRpcSuccessResponse actualResponse = (JsonRpcSuccessResponse) method.response(request); final Set result = @@ -120,6 +120,36 @@ public void shouldReturnPendingTransactionsWithLimit() { @Test public void shouldReturnPendingTransactionsWithFilter() { + final Map fromFilter = new HashMap<>(); + fromFilter.put( + "eq", listTrx.stream().findAny().get().getTransaction().getSender().toHexString()); + + final JsonRpcRequestContext request = + new JsonRpcRequestContext( + new JsonRpcRequest( + JSON_RPC_VERSION, + TXPOOL_PENDING_TRANSACTIONS_METHOD, + new Object[] { + null, + new PendingTransactionsParams( + fromFilter, + new HashMap<>(), + new HashMap<>(), + new HashMap<>(), + new HashMap<>(), + new HashMap<>()) + })); + + final JsonRpcSuccessResponse actualResponse = (JsonRpcSuccessResponse) method.response(request); + + final Set result = + (Set) actualResponse.getResult(); + assertThat(result.size()).isEqualTo(1); + } + + @Test + public void shouldReturnPendingTransactionsWithLimitAndFilter() { + final Map fromFilter = new HashMap<>(); fromFilter.put( "eq", listTrx.stream().findAny().get().getTransaction().getSender().toHexString()); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/Web3Sha3Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/Web3Sha3Test.java index 83310ead028..d690a1a614e 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/Web3Sha3Test.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/Web3Sha3Test.java @@ -70,7 +70,7 @@ public void shouldReturnErrorOnOddLengthParam() { new JsonRpcRequest("2", "web3_sha3", new Object[] {"0x68656c6c6f20776f726c6"})); final JsonRpcResponse expected = - new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_DATA_PARAMS); final JsonRpcResponse actual = method.response(request); assertThat(actual).usingRecursiveComparison().isEqualTo(expected); @@ -83,7 +83,7 @@ public void shouldReturnErrorOnNonHexParam() { new JsonRpcRequest("2", "web3_sha3", new Object[] {"0x68656c6c6fThisIsNotHex"})); final JsonRpcResponse expected = - new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_DATA_PARAMS); final JsonRpcResponse actual = method.response(request); assertThat(actual).usingRecursiveComparison().isEqualTo(expected); @@ -96,7 +96,7 @@ public void shouldReturnErrorOnNoPrefixParam() { new JsonRpcRequest("2", "web3_sha3", new Object[] {"68656c6c6f20776f726c64"})); final JsonRpcResponse expected = - new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_DATA_PARAMS); final JsonRpcResponse actual = method.response(request); assertThat(actual).usingRecursiveComparison().isEqualTo(expected); @@ -109,7 +109,7 @@ public void shouldReturnErrorOnNoPrefixNonHexParam() { new JsonRpcRequest("2", "web3_sha3", new Object[] {"68656c6c6fThisIsNotHex"})); final JsonRpcResponse expected = - new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_DATA_PARAMS); final JsonRpcResponse actual = method.response(request); assertThat(actual).usingRecursiveComparison().isEqualTo(expected); @@ -123,7 +123,7 @@ public void shouldReturnErrorOnExtraParam() { "2", "web3_sha3", new Object[] {"0x68656c6c6f20776f726c64", "{encode:'hex'}"})); final JsonRpcResponse expected = - new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAM_COUNT); final JsonRpcResponse actual = method.response(request); assertThat(actual).usingRecursiveComparison().isEqualTo(expected); @@ -135,7 +135,7 @@ public void shouldReturnErrorOnNoParam() { new JsonRpcRequestContext(new JsonRpcRequest("2", "web3_sha3", new Object[] {})); final JsonRpcResponse expected = - new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAMS); + new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAM_COUNT); final JsonRpcResponse actual = method.response(request); assertThat(actual).usingRecursiveComparison().isEqualTo(expected); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineForkchoiceUpdatedTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineForkchoiceUpdatedTest.java index 249a12d9f0b..cdca20cb00e 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineForkchoiceUpdatedTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineForkchoiceUpdatedTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -16,6 +16,7 @@ import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId.CANCUN; import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.INVALID; import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.SYNCING; import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.VALID; @@ -44,7 +45,6 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponseType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineUpdateForkchoiceResult; @@ -55,6 +55,7 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.WithdrawalsValidator; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; import java.util.List; import java.util.Optional; @@ -92,6 +93,7 @@ public AbstractEngineForkchoiceUpdatedTest(final MethodFactory methodFactory) { private static final Vertx vertx = Vertx.vertx(); private static final Hash mockHash = Hash.hash(Bytes32.fromHexStringLenient("0x1337deadbeef")); + protected static final long CANCUN_MILESTONE = 1_000_000L; private static final EngineForkchoiceUpdatedParameter mockFcuParam = new EngineForkchoiceUpdatedParameter(mockHash, mockHash, mockHash); @@ -100,14 +102,14 @@ public AbstractEngineForkchoiceUpdatedTest(final MethodFactory methodFactory) { new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE); @Mock private ProtocolSpec protocolSpec; - @Mock private ProtocolSchedule protocolSchedule; + @Mock protected ProtocolSchedule protocolSchedule; @Mock private ProtocolContext protocolContext; @Mock private MergeContext mergeContext; @Mock protected MergeMiningCoordinator mergeCoordinator; - @Mock private MutableBlockchain blockchain; + @Mock protected MutableBlockchain blockchain; @Mock private EngineCallListener engineCallListener; @@ -118,6 +120,7 @@ public void before() { when(protocolSpec.getWithdrawalsValidator()) .thenReturn(new WithdrawalsValidator.ProhibitedWithdrawals()); when(protocolSchedule.getForNextBlockHeader(any(), anyLong())).thenReturn(protocolSpec); + when(protocolSchedule.milestoneFor(CANCUN)).thenReturn(Optional.of(CANCUN_MILESTONE)); this.method = methodFactory.create( vertx, protocolSchedule, protocolContext, mergeCoordinator, engineCallListener); @@ -237,7 +240,7 @@ public void shouldReturnValidWithoutFinalizedWithPayload() { var payloadParams = new EnginePayloadAttributesParameter( - String.valueOf(System.currentTimeMillis()), + String.valueOf(defaultPayloadTimestamp()), Bytes32.fromHexStringLenient("0xDEADBEEF").toHexString(), Address.ECREC.toString(), null, @@ -426,7 +429,7 @@ public void shouldIgnoreUpdateToOldHeadAndNotPreparePayload() { var payloadParams = new EnginePayloadAttributesParameter( - String.valueOf(System.currentTimeMillis()), + String.valueOf(defaultPayloadTimestamp()), Bytes32.fromHexStringLenient("0xDEADBEEF").toHexString(), Address.ECREC.toString(), null, @@ -454,7 +457,7 @@ public void shouldIgnoreUpdateToOldHeadAndNotPreparePayload() { @Test public void shouldReturnInvalidIfPayloadTimestampNotGreaterThanHead() { - BlockHeader mockParent = blockHeaderBuilder.number(9L).buildHeader(); + BlockHeader mockParent = blockHeaderBuilder.timestamp(99).number(9L).buildHeader(); BlockHeader mockHeader = blockHeaderBuilder.number(10L).parentHash(mockParent.getHash()).buildHeader(); setupValidForkchoiceUpdate(mockHeader); @@ -475,7 +478,7 @@ public void shouldReturnInvalidIfPayloadTimestampNotGreaterThanHead() { mockHeader.getHash(), Hash.ZERO, mockParent.getHash()), Optional.of(payloadParams)); - assertInvalidForkchoiceState(resp, expectedInvalidPayloadError()); + assertInvalidForkchoiceState(resp, RpcErrorType.INVALID_PAYLOAD_ATTRIBUTES); verify(engineCallListener, times(1)).executionEngineCalled(); } @@ -488,7 +491,7 @@ public void shouldReturnInvalidIfWithdrawalsIsNotNull_WhenWithdrawalsProhibited( var payloadParams = new EnginePayloadAttributesParameter( - String.valueOf(System.currentTimeMillis()), + String.valueOf(mockHeader.getTimestamp() + 1), Bytes32.fromHexStringLenient("0xDEADBEEF").toHexString(), Address.ECREC.toString(), emptyList(), @@ -513,7 +516,7 @@ public void shouldReturnValidIfWithdrawalsIsNull_WhenWithdrawalsProhibited() { var payloadParams = new EnginePayloadAttributesParameter( - String.valueOf(System.currentTimeMillis()), + String.valueOf(mockHeader.getTimestamp() + 1), Bytes32.fromHexStringLenient("0xDEADBEEF").toHexString(), Address.ECREC.toString(), null, @@ -557,7 +560,7 @@ public void shouldReturnInvalidIfWithdrawalsIsNull_WhenWithdrawalsAllowed() { var payloadParams = new EnginePayloadAttributesParameter( - String.valueOf(System.currentTimeMillis()), + String.valueOf(mockHeader.getTimestamp() + 1), Bytes32.fromHexStringLenient("0xDEADBEEF").toHexString(), Address.ECREC.toString(), null, @@ -593,7 +596,7 @@ public void shouldReturnValidIfWithdrawalsIsNotNull_WhenWithdrawalsAllowed() { var payloadParams = new EnginePayloadAttributesParameter( - String.valueOf(System.currentTimeMillis()), + String.valueOf(mockHeader.getTimestamp() + 1), Bytes32.fromHexStringLenient("0xDEADBEEF").toHexString(), Address.ECREC.toString(), withdrawalParameters, @@ -642,7 +645,7 @@ public void shouldReturnValidIfProtocolScheduleIsEmpty() { var payloadParams = new EnginePayloadAttributesParameter( - String.valueOf(System.currentTimeMillis()), + String.valueOf(mockHeader.getTimestamp() + 1), Bytes32.fromHexStringLenient("0xDEADBEEF").toHexString(), Address.ECREC.toString(), null, @@ -674,7 +677,7 @@ public void shouldReturnValidIfProtocolScheduleIsEmpty() { verify(engineCallListener, times(1)).executionEngineCalled(); } - private void setupValidForkchoiceUpdate(final BlockHeader mockHeader) { + protected void setupValidForkchoiceUpdate(final BlockHeader mockHeader) { when(blockchain.getBlockHeader(any())).thenReturn(Optional.of(mockHeader)); when(mergeCoordinator.getOrSyncHeadByHash(mockHeader.getHash(), Hash.ZERO)) .thenReturn(Optional.of(mockHeader)); @@ -735,10 +738,10 @@ protected EngineUpdateForkchoiceResult assertSuccessWithPayloadForForkchoiceResu } protected RpcErrorType expectedInvalidPayloadError() { - return RpcErrorType.INVALID_PARAMS; + return RpcErrorType.INVALID_WITHDRAWALS_PARAMS; } - private JsonRpcResponse resp( + protected JsonRpcResponse resp( final EngineForkchoiceUpdatedParameter forkchoiceParam, final Optional payloadParam) { return method.response( @@ -752,7 +755,7 @@ private JsonRpcResponse resp( abstract String getMethodName(); private EngineUpdateForkchoiceResult fromSuccessResp(final JsonRpcResponse resp) { - assertThat(resp.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); + assertThat(resp.getType()).isEqualTo(RpcResponseType.SUCCESS); return Optional.of(resp) .map(JsonRpcSuccessResponse.class::cast) .map(JsonRpcSuccessResponse::getResult) @@ -766,10 +769,14 @@ private void assertInvalidForkchoiceState(final JsonRpcResponse resp) { private void assertInvalidForkchoiceState( final JsonRpcResponse resp, final RpcErrorType jsonRpcError) { - assertThat(resp.getType()).isEqualTo(JsonRpcResponseType.ERROR); + assertThat(resp.getType()).isEqualTo(RpcResponseType.ERROR); var errorResp = (JsonRpcErrorResponse) resp; assertThat(errorResp.getErrorType()).isEqualTo(jsonRpcError); assertThat(errorResp.getError().getMessage()).isEqualTo(jsonRpcError.getMessage()); } + + protected long defaultPayloadTimestamp() { + return 1; + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineGetPayloadTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineGetPayloadTest.java index fd2f02ba024..f18b5601cf4 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineGetPayloadTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineGetPayloadTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -20,6 +20,7 @@ import static org.mockito.Mockito.when; import org.hyperledger.besu.consensus.merge.MergeContext; +import org.hyperledger.besu.consensus.merge.PayloadWrapper; import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator; import org.hyperledger.besu.consensus.merge.blockcreation.PayloadIdentifier; import org.hyperledger.besu.crypto.KeyPair; @@ -97,6 +98,8 @@ public AbstractEngineGetPayloadTest() { new Block(mockHeader, new BlockBody(Collections.emptyList(), Collections.emptyList())); protected static final BlockWithReceipts mockBlockWithReceipts = new BlockWithReceipts(mockBlock, Collections.emptyList()); + protected static final PayloadWrapper mockPayload = + new PayloadWrapper(mockPid, mockBlockWithReceipts); private static final Block mockBlockWithWithdrawals = new Block( mockHeader, @@ -105,7 +108,7 @@ public AbstractEngineGetPayloadTest() { Collections.emptyList(), Optional.of(Collections.emptyList()), Optional.empty())); - private static final Block mockBlockWithDeposits = + private static final Block mockBlockWithDepositRequests = new Block( mockHeader, new BlockBody( @@ -115,9 +118,13 @@ public AbstractEngineGetPayloadTest() { Optional.of(Collections.emptyList()))); protected static final BlockWithReceipts mockBlockWithReceiptsAndWithdrawals = new BlockWithReceipts(mockBlockWithWithdrawals, Collections.emptyList()); + protected static final PayloadWrapper mockPayloadWithWithdrawals = + new PayloadWrapper(mockPid, mockBlockWithReceiptsAndWithdrawals); - protected static final BlockWithReceipts mockBlockWithReceiptsAndDeposits = - new BlockWithReceipts(mockBlockWithDeposits, Collections.emptyList()); + protected static final BlockWithReceipts mockBlockWithReceiptsAndDepositRequests = + new BlockWithReceipts(mockBlockWithDepositRequests, Collections.emptyList()); + protected static final PayloadWrapper mockPayloadWithDepositRequests = + new PayloadWrapper(mockPid, mockBlockWithReceiptsAndDepositRequests); @Mock protected ProtocolContext protocolContext; @@ -130,7 +137,7 @@ public AbstractEngineGetPayloadTest() { @Override public void before() { super.before(); - when(mergeContext.retrieveBlockById(mockPid)).thenReturn(Optional.of(mockBlockWithReceipts)); + when(mergeContext.retrievePayloadById(mockPid)).thenReturn(Optional.of(mockPayload)); when(protocolContext.safeConsensusContext(Mockito.any())).thenReturn(Optional.of(mergeContext)); if (methodFactory.isPresent()) { this.method = diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineNewPayloadTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineNewPayloadTest.java index a545883a6c8..9b3e94330eb 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineNewPayloadTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractEngineNewPayloadTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -22,7 +22,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -39,14 +38,15 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.DepositParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.ConsolidationRequestParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.DepositRequestParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EnginePayloadParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.UnsignedLongParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalRequestParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponseType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EnginePayloadStatusResult; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; @@ -54,17 +54,20 @@ import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; -import org.hyperledger.besu.ethereum.core.Deposit; +import org.hyperledger.besu.ethereum.core.DepositRequest; +import org.hyperledger.besu.ethereum.core.Request; import org.hyperledger.besu.ethereum.core.Withdrawal; +import org.hyperledger.besu.ethereum.core.WithdrawalRequest; import org.hyperledger.besu.ethereum.eth.manager.EthPeers; import org.hyperledger.besu.ethereum.mainnet.BodyValidation; -import org.hyperledger.besu.ethereum.mainnet.DepositsValidator; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.WithdrawalsValidator; +import org.hyperledger.besu.ethereum.mainnet.requests.RequestsValidatorCoordinator; import org.hyperledger.besu.ethereum.trie.MerkleTrieException; import org.hyperledger.besu.plugin.services.exception.StorageException; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -82,17 +85,6 @@ @ExtendWith(MockitoExtension.class) public abstract class AbstractEngineNewPayloadTest extends AbstractScheduledApiTest { - @FunctionalInterface - interface MethodFactory { - AbstractEngineNewPayload create( - final Vertx vertx, - final ProtocolSchedule protocolSchedule, - final ProtocolContext protocolContext, - final MergeMiningCoordinator mergeCoordinator, - final EthPeers ethPeers, - final EngineCallListener engineCallListener); - } - protected AbstractEngineNewPayload method; protected Optional maybeParentBeaconBlockRoot = Optional.empty(); @@ -124,9 +116,7 @@ public void before() { lenient() .when(protocolSpec.getWithdrawalsValidator()) .thenReturn(new WithdrawalsValidator.ProhibitedWithdrawals()); - lenient() - .when(protocolSpec.getDepositsValidator()) - .thenReturn(new DepositsValidator.ProhibitedDeposits()); + mockProhibitedRequestsValidator(); lenient().when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); lenient().when(ethPeers.peerCount()).thenReturn(1); } @@ -140,6 +130,7 @@ public void shouldReturnValid() { setupValidPayload( new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))), Optional.empty(), + Optional.empty(), Optional.empty()); lenient() .when(blockchain.getBlockHeader(mockHeader.getParentHash())) @@ -153,7 +144,10 @@ public void shouldReturnValid() { public void shouldReturnInvalidOnBlockExecutionError() { BlockHeader mockHeader = setupValidPayload( - new BlockProcessingResult("error 42"), Optional.empty(), Optional.empty()); + new BlockProcessingResult("error 42"), + Optional.empty(), + Optional.empty(), + Optional.empty()); lenient() .when(blockchain.getBlockHeader(mockHeader.getParentHash())) .thenReturn(Optional.of(mock(BlockHeader.class))); @@ -168,7 +162,8 @@ public void shouldReturnInvalidOnBlockExecutionError() { @Test public void shouldReturnAcceptedOnLatestValidAncestorEmpty() { - BlockHeader mockHeader = createBlockHeader(Optional.empty(), Optional.empty()); + BlockHeader mockHeader = + createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty()); when(blockchain.getBlockByHash(mockHeader.getHash())).thenReturn(Optional.empty()); when(blockchain.getBlockHeader(mockHeader.getParentHash())) .thenReturn(Optional.of(mock(BlockHeader.class))); @@ -186,7 +181,8 @@ public void shouldReturnAcceptedOnLatestValidAncestorEmpty() { @Test public void shouldReturnSuccessOnAlreadyPresent() { - BlockHeader mockHeader = createBlockHeader(Optional.empty(), Optional.empty()); + BlockHeader mockHeader = + createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty()); Block mockBlock = new Block(mockHeader, new BlockBody(Collections.emptyList(), Collections.emptyList())); @@ -199,7 +195,8 @@ public void shouldReturnSuccessOnAlreadyPresent() { @Test public void shouldReturnInvalidWithLatestValidHashIsABadBlock() { - BlockHeader mockHeader = createBlockHeader(Optional.empty(), Optional.empty()); + BlockHeader mockHeader = + createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty()); Hash latestValidHash = Hash.hash(Bytes32.fromHexStringLenient("0xcafebabe")); when(blockchain.getBlockByHash(mockHeader.getHash())).thenReturn(Optional.empty()); @@ -221,6 +218,7 @@ public void shouldNotReturnInvalidOnStorageException() { setupValidPayload( new BlockProcessingResult(Optional.empty(), new StorageException("database bedlam")), Optional.empty(), + Optional.empty(), Optional.empty()); lenient() .when(blockchain.getBlockHeader(mockHeader.getParentHash())) @@ -229,8 +227,6 @@ public void shouldNotReturnInvalidOnStorageException() { fromErrorResp(resp); verify(engineCallListener, times(1)).executionEngineCalled(); - verify(mergeCoordinator, times(0)).addBadBlock(any(), any()); - // verify mainnetBlockValidator does not add to bad block manager } @Test @@ -239,6 +235,7 @@ public void shouldNotReturnInvalidOnHandledMerkleTrieException() { setupValidPayload( new BlockProcessingResult(Optional.empty(), new MerkleTrieException("missing leaf")), Optional.empty(), + Optional.empty(), Optional.empty()); lenient() @@ -247,14 +244,14 @@ public void shouldNotReturnInvalidOnHandledMerkleTrieException() { var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList())); verify(engineCallListener, times(1)).executionEngineCalled(); - verify(mergeCoordinator, times(0)).addBadBlock(any(), any()); fromErrorResp(resp); } @Test public void shouldNotReturnInvalidOnThrownMerkleTrieException() { - BlockHeader mockHeader = createBlockHeader(Optional.empty(), Optional.empty()); + BlockHeader mockHeader = + createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty()); when(blockchain.getBlockByHash(mockHeader.getHash())).thenReturn(Optional.empty()); when(blockchain.getBlockHeader(mockHeader.getParentHash())) .thenReturn(Optional.of(mock(BlockHeader.class))); @@ -265,14 +262,14 @@ public void shouldNotReturnInvalidOnThrownMerkleTrieException() { var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList())); verify(engineCallListener, times(1)).executionEngineCalled(); - verify(mergeCoordinator, never()).addBadBlock(any(), any()); fromErrorResp(resp); } @Test public void shouldReturnInvalidBlockHashOnBadHashParameter() { - BlockHeader mockHeader = spy(createBlockHeader(Optional.empty(), Optional.empty())); + BlockHeader mockHeader = + spy(createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty())); lenient() .when(mergeCoordinator.getLatestValidAncestor(mockHeader.getBlockHash())) .thenReturn(Optional.empty()); @@ -289,7 +286,8 @@ public void shouldReturnInvalidBlockHashOnBadHashParameter() { @Test public void shouldCheckBlockValidityBeforeCheckingByHashForExisting() { - BlockHeader realHeader = createBlockHeader(Optional.empty(), Optional.empty()); + BlockHeader realHeader = + createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty()); BlockHeader paramHeader = spy(realHeader); when(paramHeader.getHash()).thenReturn(Hash.fromHexStringLenient("0x1337")); @@ -303,7 +301,8 @@ public void shouldCheckBlockValidityBeforeCheckingByHashForExisting() { @Test public void shouldReturnInvalidOnMalformedTransactions() { - BlockHeader mockHeader = createBlockHeader(Optional.empty(), Optional.empty()); + BlockHeader mockHeader = + createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty()); when(mergeCoordinator.getLatestValidAncestor(any(Hash.class))) .thenReturn(Optional.of(mockHash)); @@ -318,7 +317,8 @@ public void shouldReturnInvalidOnMalformedTransactions() { @Test public void shouldRespondWithSyncingDuringForwardSync() { - BlockHeader mockHeader = createBlockHeader(Optional.empty(), Optional.empty()); + BlockHeader mockHeader = + createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty()); when(mergeContext.isSyncing()).thenReturn(Boolean.TRUE); var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList())); @@ -331,7 +331,8 @@ public void shouldRespondWithSyncingDuringForwardSync() { @Test public void shouldRespondWithSyncingDuringBackwardsSync() { - BlockHeader mockHeader = createBlockHeader(Optional.empty(), Optional.empty()); + BlockHeader mockHeader = + createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty()); when(mergeCoordinator.appendNewPayloadToSync(any())) .thenReturn(CompletableFuture.completedFuture(null)); var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList())); @@ -345,7 +346,8 @@ public void shouldRespondWithSyncingDuringBackwardsSync() { @Test public void shouldRespondWithInvalidIfExtraDataIsNull() { - BlockHeader realHeader = createBlockHeader(Optional.empty(), Optional.empty()); + BlockHeader realHeader = + createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty()); BlockHeader paramHeader = spy(realHeader); when(paramHeader.getHash()).thenReturn(Hash.fromHexStringLenient("0x1337")); when(paramHeader.getExtraData().toHexString()).thenReturn(null); @@ -362,7 +364,8 @@ public void shouldRespondWithInvalidIfExtraDataIsNull() { @Test public void shouldReturnInvalidWhenBadBlock() { when(mergeCoordinator.isBadBlock(any(Hash.class))).thenReturn(true); - BlockHeader mockHeader = createBlockHeader(Optional.empty(), Optional.empty()); + BlockHeader mockHeader = + createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty()); var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList())); when(protocolSpec.getWithdrawalsValidator()) .thenReturn(new WithdrawalsValidator.AllowedWithdrawals()); @@ -380,6 +383,7 @@ public void shouldReturnValidIfProtocolScheduleIsEmpty() { setupValidPayload( new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))), Optional.empty(), + Optional.empty(), Optional.empty()); lenient() .when(blockchain.getBlockHeader(mockHeader.getParentHash())) @@ -400,14 +404,16 @@ protected JsonRpcResponse resp(final EnginePayloadParameter payload) { protected EnginePayloadParameter mockEnginePayload( final BlockHeader header, final List txs) { - return mockEnginePayload(header, txs, null, null); + return mockEnginePayload(header, txs, null, null, null, null); } protected EnginePayloadParameter mockEnginePayload( final BlockHeader header, final List txs, final List withdrawals, - final List deposits) { + final List depositRequests, + final List withdrawalRequests, + final List consolidationRequests) { return new EnginePayloadParameter( header.getHash(), header.getParentHash(), @@ -426,15 +432,19 @@ protected EnginePayloadParameter mockEnginePayload( withdrawals, header.getBlobGasUsed().map(UnsignedLongParameter::new).orElse(null), header.getExcessBlobGas().map(BlobGas::toHexString).orElse(null), - deposits); + depositRequests, + withdrawalRequests, + consolidationRequests); } protected BlockHeader setupValidPayload( final BlockProcessingResult value, final Optional> maybeWithdrawals, - final Optional> maybeDeposits) { + final Optional> maybeDepositRequests, + final Optional> maybeWithdrawalRequests) { - BlockHeader mockHeader = createBlockHeader(maybeWithdrawals, maybeDeposits); + BlockHeader mockHeader = + createBlockHeader(maybeWithdrawals, maybeDepositRequests, maybeWithdrawalRequests); when(blockchain.getBlockByHash(mockHeader.getHash())).thenReturn(Optional.empty()); // when(blockchain.getBlockHeader(mockHeader.getParentHash())) // .thenReturn(Optional.of(mock(BlockHeader.class))); @@ -449,7 +459,7 @@ protected ExecutionEngineJsonRpcMethod.EngineStatus getExpectedInvalidBlockHashS } protected EnginePayloadStatusResult fromSuccessResp(final JsonRpcResponse resp) { - assertThat(resp.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); + assertThat(resp.getType()).isEqualTo(RpcResponseType.SUCCESS); return Optional.of(resp) .map(JsonRpcSuccessResponse.class::cast) .map(JsonRpcSuccessResponse::getResult) @@ -458,7 +468,7 @@ protected EnginePayloadStatusResult fromSuccessResp(final JsonRpcResponse resp) } protected JsonRpcError fromErrorResp(final JsonRpcResponse resp) { - assertThat(resp.getType()).isEqualTo(JsonRpcResponseType.ERROR); + assertThat(resp.getType()).isEqualTo(RpcResponseType.ERROR); return Optional.of(resp) .map(JsonRpcErrorResponse.class::cast) .map(JsonRpcErrorResponse::getError) @@ -467,13 +477,32 @@ protected JsonRpcError fromErrorResp(final JsonRpcResponse resp) { protected BlockHeader createBlockHeader( final Optional> maybeWithdrawals, - final Optional> maybeDeposits) { - return createBlockHeaderFixture(maybeWithdrawals, maybeDeposits).buildHeader(); + final Optional> maybeDepositRequests, + final Optional> maybeWithdrawalRequests) { + return createBlockHeaderFixture(maybeWithdrawals, maybeDepositRequests, maybeWithdrawalRequests) + .buildHeader(); } protected BlockHeaderTestFixture createBlockHeaderFixture( final Optional> maybeWithdrawals, - final Optional> maybeDeposits) { + final Optional> maybeDepositRequests, + final Optional> maybeWithdrawalRequests) { + + Optional> maybeRequests; + if (maybeDepositRequests.isPresent() || maybeWithdrawalRequests.isPresent()) { + List requests = new ArrayList<>(); + maybeDepositRequests.ifPresent(requests::addAll); + maybeWithdrawalRequests.ifPresent(requests::addAll); + maybeRequests = Optional.of(requests); + } else { + maybeRequests = Optional.empty(); + } + return createBlockHeaderFixture(maybeWithdrawals, maybeRequests); + } + + protected BlockHeaderTestFixture createBlockHeaderFixture( + final Optional> maybeWithdrawals, + final Optional> maybeRequests) { BlockHeader parentBlockHeader = new BlockHeaderTestFixture().baseFeePerGas(Wei.ONE).buildHeader(); return new BlockHeaderTestFixture() @@ -482,8 +511,8 @@ protected BlockHeaderTestFixture createBlockHeaderFixture( .number(parentBlockHeader.getNumber() + 1) .timestamp(parentBlockHeader.getTimestamp() + 1) .withdrawalsRoot(maybeWithdrawals.map(BodyValidation::withdrawalsRoot).orElse(null)) - .depositsRoot(maybeDeposits.map(BodyValidation::depositsRoot).orElse(null)) - .parentBeaconBlockRoot(maybeParentBeaconBlockRoot); + .parentBeaconBlockRoot(maybeParentBeaconBlockRoot) + .requestsRoot(maybeRequests.map(BodyValidation::requestsRoot).orElse(null)); } protected void assertValidResponse(final BlockHeader mockHeader, final JsonRpcResponse resp) { @@ -493,4 +522,9 @@ protected void assertValidResponse(final BlockHeader mockHeader, final JsonRpcRe assertThat(res.getError()).isNull(); verify(engineCallListener, times(1)).executionEngineCalled(); } + + private void mockProhibitedRequestsValidator() { + var validator = RequestsValidatorCoordinator.empty(); + when(protocolSpec.getRequestsValidatorCoordinator()).thenReturn(validator); + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractScheduledApiTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractScheduledApiTest.java index 6d71932ff96..46fe7d3b6ce 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractScheduledApiTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/AbstractScheduledApiTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,10 +12,14 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; -import static org.mockito.ArgumentMatchers.argThat; +import static org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId.CANCUN; +import static org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId.EXPERIMENTAL_EIPS; +import static org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId.LONDON; +import static org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId.PARIS; +import static org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId.PRAGUE; +import static org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId.SHANGHAI; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; @@ -39,8 +43,10 @@ public class AbstractScheduledApiTest { new ScheduledProtocolSpec.Hardfork("Shanghai", 20); protected final ScheduledProtocolSpec.Hardfork cancunHardfork = new ScheduledProtocolSpec.Hardfork("Cancun", 30); + protected final ScheduledProtocolSpec.Hardfork pragueHardfork = + new ScheduledProtocolSpec.Hardfork("Prague", 40); protected final ScheduledProtocolSpec.Hardfork experimentalHardfork = - new ScheduledProtocolSpec.Hardfork("ExperimentalEips", 40); + new ScheduledProtocolSpec.Hardfork("ExperimentalEips", 50); @Mock protected DefaultProtocolSchedule protocolSchedule; @@ -66,19 +72,22 @@ public boolean matches(final Predicate value) { @BeforeEach public void before() { lenient() - .when(protocolSchedule.hardforkFor(argThat(new HardforkMatcher(londonHardfork)))) - .thenReturn(Optional.of(londonHardfork)); + .when(protocolSchedule.milestoneFor((LONDON))) + .thenReturn(Optional.of(londonHardfork.milestone())); + lenient() + .when(protocolSchedule.milestoneFor(PARIS)) + .thenReturn(Optional.of(parisHardfork.milestone())); lenient() - .when(protocolSchedule.hardforkFor(argThat(new HardforkMatcher(parisHardfork)))) - .thenReturn(Optional.of(parisHardfork)); + .when(protocolSchedule.milestoneFor(CANCUN)) + .thenReturn(Optional.of(cancunHardfork.milestone())); lenient() - .when(protocolSchedule.hardforkFor(argThat(new HardforkMatcher(cancunHardfork)))) - .thenReturn(Optional.of(cancunHardfork)); + .when(protocolSchedule.milestoneFor(PRAGUE)) + .thenReturn(Optional.of(pragueHardfork.milestone())); lenient() - .when(protocolSchedule.hardforkFor(argThat(new HardforkMatcher(shanghaiHardfork)))) - .thenReturn(Optional.of(shanghaiHardfork)); + .when(protocolSchedule.milestoneFor(SHANGHAI)) + .thenReturn(Optional.of(shanghaiHardfork.milestone())); lenient() - .when(protocolSchedule.hardforkFor(argThat(new HardforkMatcher(experimentalHardfork)))) - .thenReturn(Optional.of(experimentalHardfork)); + .when(protocolSchedule.milestoneFor(EXPERIMENTAL_EIPS)) + .thenReturn(Optional.of(experimentalHardfork.milestone())); } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeCapabilitiesTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeCapabilitiesTest.java index 41a024c09c6..4ccd9318a25 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeCapabilitiesTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeCapabilitiesTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -23,8 +23,8 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponseType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; import java.util.Collections; import java.util.List; @@ -85,7 +85,7 @@ private JsonRpcResponse resp(final List capabilitiesParam) { @SuppressWarnings("unchecked") private List fromSuccessResp(final JsonRpcResponse resp) { - assertThat(resp.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); + assertThat(resp.getType()).isEqualTo(RpcResponseType.SUCCESS); return Optional.of(resp) .map(JsonRpcSuccessResponse.class::cast) .map(JsonRpcSuccessResponse::getResult) diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeTransitionConfigurationTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeTransitionConfigurationTest.java index 1617afdac30..f1575f81ab4 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeTransitionConfigurationTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineExchangeTransitionConfigurationTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -30,7 +30,6 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EngineExchangeTransitionConfigurationParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.UnsignedLongParameter; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponseType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineExchangeTransitionConfigurationResult; import org.hyperledger.besu.ethereum.core.BlockHeader; @@ -39,6 +38,7 @@ import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.ParsedExtraData; import org.hyperledger.besu.evm.log.LogsBloomFilter; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; import java.math.BigInteger; import java.util.Map; @@ -223,7 +223,7 @@ private JsonRpcResponse resp(final EngineExchangeTransitionConfigurationParamete } private EngineExchangeTransitionConfigurationResult fromSuccessResp(final JsonRpcResponse resp) { - assertThat(resp.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); + assertThat(resp.getType()).isEqualTo(RpcResponseType.SUCCESS); return Optional.of(resp) .map(JsonRpcSuccessResponse.class::cast) .map(JsonRpcSuccessResponse::getResult) diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV1Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV1Test.java index a6f78d145c8..6993be3d7ae 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV1Test.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV1Test.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -46,6 +46,6 @@ protected String getMethodName() { @Override protected RpcErrorType expectedInvalidPayloadError() { - return RpcErrorType.INVALID_PAYLOAD_ATTRIBUTES; + return RpcErrorType.INVALID_WITHDRAWALS_PARAMS; } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV2Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV2Test.java index 77249934113..c3558ef3292 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV2Test.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineForkchoiceUpdatedV2Test.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,9 +15,26 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.UNSUPPORTED_FORK; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import org.hyperledger.besu.consensus.merge.blockcreation.MergeMiningCoordinator; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EngineForkchoiceUpdatedParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EnginePayloadAttributesParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; @@ -38,8 +55,43 @@ public void shouldReturnExpectedMethodName() { assertThat(method.getName()).isEqualTo("engine_forkchoiceUpdatedV2"); } + @Test + public void shouldReturnUnsupportedForkIfBlockTimestampIsAfterCancunMilestone() { + when(mergeCoordinator.updateForkChoice( + any(BlockHeader.class), any(Hash.class), any(Hash.class))) + .thenReturn(mock(MergeMiningCoordinator.ForkchoiceResult.class)); + + BlockHeader mockHeader = blockHeaderBuilder.timestamp(CANCUN_MILESTONE + 1).buildHeader(); + setupValidForkchoiceUpdate(mockHeader); + + final EngineForkchoiceUpdatedParameter param = + new EngineForkchoiceUpdatedParameter(mockHeader.getBlockHash(), Hash.ZERO, Hash.ZERO); + + final EnginePayloadAttributesParameter payloadParams = + new EnginePayloadAttributesParameter( + String.valueOf(mockHeader.getTimestamp()), + Bytes32.fromHexStringLenient("0xDEADBEEF").toHexString(), + Address.ECREC.toString(), + null, + null); + + final JsonRpcResponse resp = resp(param, Optional.of(payloadParams)); + + final JsonRpcError jsonRpcError = + Optional.of(resp) + .map(JsonRpcErrorResponse.class::cast) + .map(JsonRpcErrorResponse::getError) + .get(); + assertThat(jsonRpcError.getCode()).isEqualTo(UNSUPPORTED_FORK.getCode()); + } + @Override protected String getMethodName() { return RpcMethod.ENGINE_FORKCHOICE_UPDATED_V2.getMethodName(); } + + @Override + protected RpcErrorType expectedInvalidPayloadError() { + return RpcErrorType.INVALID_WITHDRAWALS_PARAMS; + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetBlobsV1Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetBlobsV1Test.java new file mode 100644 index 00000000000..29c7ae82ed5 --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetBlobsV1Test.java @@ -0,0 +1,267 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.crypto.KeyPair; +import org.hyperledger.besu.crypto.SECPPrivateKey; +import org.hyperledger.besu.crypto.SignatureAlgorithm; +import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BlobsWithCommitments; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.TransactionType; +import org.hyperledger.besu.datatypes.VersionedHash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlobAndProofV1; +import org.hyperledger.besu.ethereum.chain.MutableBlockchain; +import org.hyperledger.besu.ethereum.core.BlobTestFixture; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.TransactionTestFixture; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; + +import com.google.common.base.Suppliers; +import io.vertx.core.Vertx; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class EngineGetBlobsV1Test { + + private static final Supplier SIGNATURE_ALGORITHM = + Suppliers.memoize(SignatureAlgorithmFactory::getInstance); + private static final SECPPrivateKey PRIVATE_KEY1 = + SIGNATURE_ALGORITHM + .get() + .createPrivateKey( + Bytes32.fromHexString( + "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63")); + private static final KeyPair KEYS1 = + new KeyPair(PRIVATE_KEY1, SIGNATURE_ALGORITHM.get().createPublicKey(PRIVATE_KEY1)); + public static final VersionedHash VERSIONED_HASH_ZERO = new VersionedHash((byte) 1, Hash.ZERO); + + @Mock private ProtocolContext protocolContext; + @Mock private EngineCallListener engineCallListener; + @Mock private MutableBlockchain blockchain; + @Mock private TransactionPool transactionPool; + + private EngineGetBlobsV1 method; + + private static final Vertx vertx = Vertx.vertx(); + + @BeforeEach + public void before() { + when(protocolContext.getBlockchain()).thenReturn(blockchain); + this.method = + spy(new EngineGetBlobsV1(vertx, protocolContext, engineCallListener, transactionPool)); + } + + @Test + public void shouldReturnExpectedMethodName() { + assertThat(method.getName()).isEqualTo("engine_getBlobsV1"); + } + + @Test + public void shouldReturnBlobsAndProofsForKnownVersionedHashesFromMap() { + final Transaction blobTransaction = createBlobTransaction(); + + final BlobsWithCommitments blobsWithCommitments = + blobTransaction.getBlobsWithCommitments().get(); + + mockTransactionPoolMethod(blobsWithCommitments); + + VersionedHash[] versionedHashes = + blobsWithCommitments.getVersionedHashes().toArray(new VersionedHash[0]); + + final JsonRpcResponse jsonRpcResponse = resp(versionedHashes); + + final List blobAndProofV1s = fromSuccessResp(jsonRpcResponse); + + assertThat(blobAndProofV1s.size()).isEqualTo(versionedHashes.length); + // for loop to check each blob and proof + for (int i = 0; i < versionedHashes.length; i++) { + assertThat(Bytes.fromHexString(blobAndProofV1s.get(i).getBlob())) + .isEqualTo(blobsWithCommitments.getBlobQuads().get(i).blob().getData()); + assertThat(Bytes.fromHexString(blobAndProofV1s.get(i).getProof())) + .isEqualTo(blobsWithCommitments.getBlobQuads().get(i).kzgProof().getData()); + } + } + + @Test + public void shouldReturnNullForBlobsAndProofsForUnknownVersionedHashes() { + final Transaction blobTransaction = createBlobTransaction(); + + final BlobsWithCommitments blobsWithCommitments = + blobTransaction.getBlobsWithCommitments().get(); + + mockTransactionPoolMethod(blobsWithCommitments); + + List versionedHashesList = + blobsWithCommitments.getVersionedHashes().stream().toList(); + + final VersionedHash[] hashes = versionedHashesList.toArray(new VersionedHash[0]); + hashes[1] = VERSIONED_HASH_ZERO; + + final JsonRpcResponse jsonRpcResponse = resp(hashes); + + final List blobAndProofV1s = fromSuccessResp(jsonRpcResponse); + + assertThat(blobAndProofV1s.size()).isEqualTo(versionedHashesList.size()); + // for loop to check each blob and proof + for (int i = 0; i < versionedHashesList.size(); i++) { + if (i != 1) { + assertThat(Bytes.fromHexString(blobAndProofV1s.get(i).getBlob())) + .isEqualTo(blobsWithCommitments.getBlobQuads().get(i).blob().getData()); + assertThat(Bytes.fromHexString(blobAndProofV1s.get(i).getProof())) + .isEqualTo(blobsWithCommitments.getBlobQuads().get(i).kzgProof().getData()); + } else { + assertThat(blobAndProofV1s.get(i)).isNull(); + } + } + } + + @Test + public void shouldReturnOnlyNullsForBlobsAndProofsIfAllVersionedHashesUnknown() { + final Transaction blobTransaction = createBlobTransaction(); + + final BlobsWithCommitments blobsWithCommitments = + blobTransaction.getBlobsWithCommitments().get(); + + mockTransactionPoolMethod(blobsWithCommitments); + + List versionedHashesList = + blobsWithCommitments.getVersionedHashes().stream().toList(); + + final VersionedHash[] versionedHashes = new VersionedHash[6]; + Arrays.fill(versionedHashes, VERSIONED_HASH_ZERO); + final JsonRpcResponse jsonRpcResponse = resp(versionedHashes); + + final List blobAndProofV1s = fromSuccessResp(jsonRpcResponse); + + assertThat(blobAndProofV1s.size()).isEqualTo(versionedHashesList.size()); + // for loop to check each blob and proof + for (int i = 0; i < versionedHashes.length; i++) { + assertThat(blobAndProofV1s.get(i)).isNull(); + } + } + + @Test + public void shouldReturnEmptyResponseForEmptyRequest() { + final VersionedHash[] versionedHashes = new VersionedHash[0]; + + final JsonRpcResponse jsonRpcResponse = resp(versionedHashes); + + assertThat(fromSuccessResp(jsonRpcResponse).size()).isEqualTo(0); + } + + @Test + public void shouldFailWhenRequestingMoreThan128() { + final VersionedHash[] versionedHashes = new VersionedHash[129]; + for (int i = 0; i < 129; i++) { + versionedHashes[i] = new VersionedHash((byte) 1, Hash.ZERO); + } + + final JsonRpcResponse jsonRpcResponse = resp(versionedHashes); + + assertThat(fromErrorResp(jsonRpcResponse).getCode()) + .isEqualTo(RpcErrorType.INVALID_ENGINE_GET_BLOBS_V1_TOO_LARGE_REQUEST.getCode()); + assertThat(fromErrorResp(jsonRpcResponse).getMessage()) + .isEqualTo(RpcErrorType.INVALID_ENGINE_GET_BLOBS_V1_TOO_LARGE_REQUEST.getMessage()); + } + + Transaction createBlobTransaction() { + BlobTestFixture blobTestFixture = new BlobTestFixture(); + BlobsWithCommitments bwc = blobTestFixture.createBlobsWithCommitments(6); + TransactionTestFixture ttf = new TransactionTestFixture(); + Transaction fullOfBlobs = + ttf.to(Optional.of(Address.ZERO)) + .type(TransactionType.BLOB) + .chainId(Optional.of(BigInteger.valueOf(42))) + .gasLimit(21000) + .maxFeePerGas(Optional.of(Wei.of(15))) + .maxFeePerBlobGas(Optional.of(Wei.of(128))) + .maxPriorityFeePerGas(Optional.of(Wei.of(1))) + .versionedHashes(Optional.of(bwc.getVersionedHashes())) + .blobsWithCommitments(Optional.of(bwc)) + .createTransaction(KEYS1); + return fullOfBlobs; + } + + private void mockTransactionPoolMethod(final BlobsWithCommitments blobsWithCommitments) { + blobsWithCommitments + .getBlobQuads() + .forEach( + blobQuad -> + when(transactionPool.getBlobQuad(blobQuad.versionedHash())).thenReturn(blobQuad)); + } + + private JsonRpcResponse resp(final VersionedHash[] versionedHashes) { + return method.response( + new JsonRpcRequestContext( + new JsonRpcRequest( + "2.0", + RpcMethod.ENGINE_GET_BLOBS_V1.getMethodName(), + new Object[] {versionedHashes}))); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private List fromSuccessResp(final JsonRpcResponse resp) { + assertThat(resp.getType()).isEqualTo(RpcResponseType.SUCCESS); + final List list = + Optional.of(resp) + .map(JsonRpcSuccessResponse.class::cast) + .map(JsonRpcSuccessResponse::getResult) + .map(List.class::cast) + .get(); + final ArrayList blobAndProofV1s = new ArrayList<>(); + list.forEach(obj -> blobAndProofV1s.add((BlobAndProofV1) obj)); + return blobAndProofV1s; + } + + private RpcErrorType fromErrorResp(final JsonRpcResponse resp) { + assertThat(resp.getType()).isEqualTo(RpcResponseType.ERROR); + return Optional.of(resp) + .map(JsonRpcErrorResponse.class::cast) + .map(JsonRpcErrorResponse::getErrorType) + .get(); + } +} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetClientVersionV1Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetClientVersionV1Test.java new file mode 100644 index 00000000000..1aa0def7e27 --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetClientVersionV1Test.java @@ -0,0 +1,72 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineGetClientVersionResultV1; + +import io.vertx.core.Vertx; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class EngineGetClientVersionV1Test { + + private static final String ENGINE_CLIENT_CODE = "BU"; + private static final String ENGINE_CLIENT_NAME = "Besu"; + + private static final String CLIENT_VERSION = "v25.6.7-dev-abcdef12"; + private static final String COMMIT = "abcdef12"; + + private EngineGetClientVersionV1 getClientVersion; + + @BeforeEach + void before() { + getClientVersion = + new EngineGetClientVersionV1( + Mockito.mock(Vertx.class), + Mockito.mock(ProtocolContext.class), + Mockito.mock(EngineCallListener.class), + CLIENT_VERSION, + COMMIT); + } + + @Test + void testGetName() { + assertThat(getClientVersion.getName()).isEqualTo("engine_getClientVersionV1"); + } + + @Test + void testSyncResponse() { + JsonRpcRequestContext request = new JsonRpcRequestContext(new JsonRpcRequest("v", "m", null)); + JsonRpcResponse actualResult = getClientVersion.syncResponse(request); + + assertThat(actualResult).isInstanceOf(JsonRpcSuccessResponse.class); + JsonRpcSuccessResponse successResponse = (JsonRpcSuccessResponse) actualResult; + assertThat(successResponse.getResult()).isInstanceOf(EngineGetClientVersionResultV1.class); + EngineGetClientVersionResultV1 actualEngineGetClientVersionResultV1 = + (EngineGetClientVersionResultV1) successResponse.getResult(); + assertThat(actualEngineGetClientVersionResultV1.getName()).isEqualTo(ENGINE_CLIENT_NAME); + assertThat(actualEngineGetClientVersionResultV1.getCode()).isEqualTo(ENGINE_CLIENT_CODE); + assertThat(actualEngineGetClientVersionResultV1.getVersion()).isEqualTo(CLIENT_VERSION); + assertThat(actualEngineGetClientVersionResultV1.getCommit()).isEqualTo(COMMIT); + } +} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadBodiesByHashV1Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadBodiesByHashV1Test.java index 9c3cd8f45ff..8a99fa94392 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadBodiesByHashV1Test.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadBodiesByHashV1Test.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -31,7 +31,6 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponseType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory; @@ -40,6 +39,7 @@ import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.TransactionTestFixture; import org.hyperledger.besu.ethereum.core.Withdrawal; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; import java.util.Collections; import java.util.List; @@ -255,7 +255,7 @@ private JsonRpcResponse resp(final Hash[] hashes) { } private EngineGetPayloadBodiesResultV1 fromSuccessResp(final JsonRpcResponse resp) { - assertThat(resp.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); + assertThat(resp.getType()).isEqualTo(RpcResponseType.SUCCESS); return Optional.of(resp) .map(JsonRpcSuccessResponse.class::cast) .map(JsonRpcSuccessResponse::getResult) @@ -264,7 +264,7 @@ private EngineGetPayloadBodiesResultV1 fromSuccessResp(final JsonRpcResponse res } private RpcErrorType fromErrorResp(final JsonRpcResponse resp) { - assertThat(resp.getType()).isEqualTo(JsonRpcResponseType.ERROR); + assertThat(resp.getType()).isEqualTo(RpcResponseType.ERROR); return Optional.of(resp) .map(JsonRpcErrorResponse.class::cast) .map(JsonRpcErrorResponse::getErrorType) diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadBodiesByRangeV1Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadBodiesByRangeV1Test.java index 16964458648..6bc89ba6791 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadBodiesByRangeV1Test.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadBodiesByRangeV1Test.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -33,7 +33,6 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponseType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.BlockResultFactory; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineGetPayloadBodiesResultV1; @@ -41,6 +40,7 @@ import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.TransactionTestFixture; import org.hyperledger.besu.ethereum.core.Withdrawal; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; import java.util.Collections; import java.util.List; @@ -344,7 +344,7 @@ private JsonRpcResponse resp(final String startBlockNumber, final String range) } private EngineGetPayloadBodiesResultV1 fromSuccessResp(final JsonRpcResponse resp) { - assertThat(resp.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); + assertThat(resp.getType()).isEqualTo(RpcResponseType.SUCCESS); return Optional.of(resp) .map(JsonRpcSuccessResponse.class::cast) .map(JsonRpcSuccessResponse::getResult) @@ -353,7 +353,7 @@ private EngineGetPayloadBodiesResultV1 fromSuccessResp(final JsonRpcResponse res } private JsonRpcError fromErrorResp(final JsonRpcResponse resp) { - assertThat(resp.getType()).isEqualTo(JsonRpcResponseType.ERROR); + assertThat(resp.getType()).isEqualTo(RpcResponseType.ERROR); return Optional.of(resp) .map(JsonRpcErrorResponse.class::cast) .map(JsonRpcErrorResponse::getError) diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV1Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV1Test.java index 5fbf89413f2..f0888409620 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV1Test.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV1Test.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV2Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV2Test.java index d9f7f9b4291..5c455222f17 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV2Test.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV2Test.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,16 +15,26 @@ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.UNSUPPORTED_FORK; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import org.hyperledger.besu.consensus.merge.PayloadWrapper; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineGetPayloadResultV2; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockBody; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.core.BlockWithReceipts; +import java.util.Collections; import java.util.Optional; import org.apache.tuweni.bytes.Bytes32; @@ -48,9 +58,7 @@ public EngineGetPayloadV2Test() { @Override public void before() { super.before(); - lenient() - .when(mergeContext.retrieveBlockById(mockPid)) - .thenReturn(Optional.of(mockBlockWithReceipts)); + lenient().when(mergeContext.retrievePayloadById(mockPid)).thenReturn(Optional.of(mockPayload)); when(protocolContext.safeConsensusContext(Mockito.any())).thenReturn(Optional.of(mergeContext)); this.method = new EngineGetPayloadV2( @@ -72,8 +80,8 @@ public void shouldReturnExpectedMethodName() { @Test public void shouldReturnBlockForKnownPayloadId() { // should return withdrawals for a post-Shanghai block - when(mergeContext.retrieveBlockById(mockPid)) - .thenReturn(Optional.of(mockBlockWithReceiptsAndWithdrawals)); + when(mergeContext.retrievePayloadById(mockPid)) + .thenReturn(Optional.of(mockPayloadWithWithdrawals)); final var resp = resp(RpcMethod.ENGINE_GET_PAYLOAD_V2.getMethodName(), mockPid); assertThat(resp).isInstanceOf(JsonRpcSuccessResponse.class); @@ -108,6 +116,29 @@ public void shouldReturnExecutionPayloadWithoutWithdrawals_PreShanghaiBlock() { verify(engineCallListener, times(1)).executionEngineCalled(); } + @Test + public void shouldReturnUnsupportedForkIfBlockTimestampIsAfterCancunMilestone() { + // Cancun starts at timestamp 30 + final BlockHeader mockHeader = new BlockHeaderTestFixture().timestamp(31L).buildHeader(); + final Block mockBlock = + new Block(mockHeader, new BlockBody(Collections.emptyList(), Collections.emptyList())); + final BlockWithReceipts mockBlockWithReceipts = + new BlockWithReceipts(mockBlock, Collections.emptyList()); + final PayloadWrapper mockPayload = new PayloadWrapper(mockPid, mockBlockWithReceipts); + + when(mergeContext.retrievePayloadById(mockPid)).thenReturn(Optional.of(mockPayload)); + + final var resp = resp(RpcMethod.ENGINE_GET_PAYLOAD_V2.getMethodName(), mockPid); + + final JsonRpcError jsonRpcError = + Optional.of(resp) + .map(JsonRpcErrorResponse.class::cast) + .map(JsonRpcErrorResponse::getError) + .get(); + assertThat(jsonRpcError.getCode()).isEqualTo(UNSUPPORTED_FORK.getCode()); + verify(engineCallListener, times(1)).executionEngineCalled(); + } + @Override protected String getMethodName() { return RpcMethod.ENGINE_GET_PAYLOAD_V2.getMethodName(); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV3Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV3Test.java index 1e19bc3fd5d..ac589c11219 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV3Test.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV3Test.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -21,6 +21,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import org.hyperledger.besu.consensus.merge.PayloadWrapper; import org.hyperledger.besu.consensus.merge.blockcreation.PayloadIdentifier; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.BlobGas; @@ -67,9 +68,7 @@ public EngineGetPayloadV3Test() { @Override public void before() { super.before(); - lenient() - .when(mergeContext.retrieveBlockById(mockPid)) - .thenReturn(Optional.of(mockBlockWithReceipts)); + lenient().when(mergeContext.retrievePayloadById(mockPid)).thenReturn(Optional.of(mockPayload)); when(protocolContext.safeConsensusContext(Mockito.any())).thenReturn(Optional.of(mergeContext)); this.method = new EngineGetPayloadV3( @@ -130,10 +129,12 @@ public void shouldReturnBlockForKnownPayloadId() { List.of(blobTx), Collections.emptyList(), Optional.of(Collections.emptyList()), - Optional.empty())), + Optional.of(Collections.emptyList()))), List.of(blobReceipt)); + PayloadWrapper payloadPostCancun = new PayloadWrapper(postCancunPid, postCancunBlock); - when(mergeContext.retrieveBlockById(postCancunPid)).thenReturn(Optional.of(postCancunBlock)); + when(mergeContext.retrievePayloadById(postCancunPid)) + .thenReturn(Optional.of(payloadPostCancun)); final var resp = resp(RpcMethod.ENGINE_GET_PAYLOAD_V3.getMethodName(), postCancunPid); assertThat(resp).isInstanceOf(JsonRpcSuccessResponse.class); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV4Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV4Test.java new file mode 100644 index 00000000000..134c7a2c0c6 --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV4Test.java @@ -0,0 +1,181 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.consensus.merge.PayloadWrapper; +import org.hyperledger.besu.consensus.merge.blockcreation.PayloadIdentifier; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BlobGas; +import org.hyperledger.besu.datatypes.BlobsWithCommitments; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.TransactionType; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineGetPayloadResultV4; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity; +import org.hyperledger.besu.ethereum.core.BlobTestFixture; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockBody; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.core.BlockWithReceipts; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.core.TransactionTestFixture; + +import java.math.BigInteger; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith( + MockitoExtension.class) // mocks in parent class may not be used, throwing unnecessary stubbing +public class EngineGetPayloadV4Test extends AbstractEngineGetPayloadTest { + + public EngineGetPayloadV4Test() { + super(); + } + + @BeforeEach + @Override + public void before() { + super.before(); + lenient() + .when(mergeContext.retrievePayloadById(mockPid)) + .thenReturn(Optional.of(mockPayloadWithDepositRequests)); + when(protocolContext.safeConsensusContext(Mockito.any())).thenReturn(Optional.of(mergeContext)); + this.method = + new EngineGetPayloadV4( + vertx, + protocolContext, + mergeMiningCoordinator, + factory, + engineCallListener, + protocolSchedule); + } + + @Override + @Test + public void shouldReturnExpectedMethodName() { + assertThat(method.getName()).isEqualTo("engine_getPayloadV4"); + } + + @Override + @Test + public void shouldReturnBlockForKnownPayloadId() { + + BlockHeader header = + new BlockHeaderTestFixture() + .prevRandao(Bytes32.random()) + .timestamp(pragueHardfork.milestone() + 1) + .excessBlobGas(BlobGas.of(10L)) + .buildHeader(); + // should return withdrawals, deposits and excessGas for a post-6110 block + PayloadIdentifier payloadIdentifier = + PayloadIdentifier.forPayloadParams( + Hash.ZERO, + pragueHardfork.milestone(), + Bytes32.random(), + Address.fromHexString("0x42"), + Optional.empty(), + Optional.empty()); + + BlobTestFixture blobTestFixture = new BlobTestFixture(); + BlobsWithCommitments bwc = blobTestFixture.createBlobsWithCommitments(1); + Transaction blobTx = + new TransactionTestFixture() + .to(Optional.of(Address.fromHexString("0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"))) + .type(TransactionType.BLOB) + .chainId(Optional.of(BigInteger.ONE)) + .maxFeePerGas(Optional.of(Wei.of(15))) + .maxFeePerBlobGas(Optional.of(Wei.of(128))) + .maxPriorityFeePerGas(Optional.of(Wei.of(1))) + .blobsWithCommitments(Optional.of(bwc)) + .versionedHashes(Optional.of(bwc.getVersionedHashes())) + .createTransaction(senderKeys); + TransactionReceipt blobReceipt = mock(TransactionReceipt.class); + when(blobReceipt.getCumulativeGasUsed()).thenReturn(100L); + BlockWithReceipts block = + new BlockWithReceipts( + new Block( + header, + new BlockBody( + List.of(blobTx), + Collections.emptyList(), + Optional.of(Collections.emptyList()), + Optional.of(Collections.emptyList()))), + List.of(blobReceipt)); + PayloadWrapper payload = new PayloadWrapper(payloadIdentifier, block); + + when(mergeContext.retrievePayloadById(payloadIdentifier)).thenReturn(Optional.of(payload)); + + final var resp = resp(RpcMethod.ENGINE_GET_PAYLOAD_V4.getMethodName(), payloadIdentifier); + assertThat(resp).isInstanceOf(JsonRpcSuccessResponse.class); + Optional.of(resp) + .map(JsonRpcSuccessResponse.class::cast) + .ifPresent( + r -> { + assertThat(r.getResult()).isInstanceOf(EngineGetPayloadResultV4.class); + final EngineGetPayloadResultV4 res = (EngineGetPayloadResultV4) r.getResult(); + assertThat(res.getExecutionPayload().getWithdrawals()).isNotNull(); + assertThat(res.getExecutionPayload().getDepositRequests()).isNotNull(); + assertThat(res.getExecutionPayload().getWithdrawalRequests()).isNotNull(); + assertThat(res.getExecutionPayload().getConsolidationRequests()).isNotNull(); + assertThat(res.getExecutionPayload().getHash()) + .isEqualTo(header.getHash().toString()); + assertThat(res.getBlockValue()).isEqualTo(Quantity.create(0)); + assertThat(res.getExecutionPayload().getPrevRandao()) + .isEqualTo(header.getPrevRandao().map(Bytes32::toString).orElse("")); + // excessBlobGas: QUANTITY, 256 bits + String expectedQuantityOf10 = Bytes32.leftPad(Bytes.of(10)).toQuantityHexString(); + assertThat(res.getExecutionPayload().getExcessBlobGas()).isNotEmpty(); + assertThat(res.getExecutionPayload().getExcessBlobGas()) + .isEqualTo(expectedQuantityOf10); + }); + verify(engineCallListener, times(1)).executionEngineCalled(); + } + + @Test + public void shouldReturnUnsupportedFork() { + final var resp = resp(RpcMethod.ENGINE_GET_PAYLOAD_V4.getMethodName(), mockPid); + + assertThat(resp).isInstanceOf(JsonRpcErrorResponse.class); + assertThat(((JsonRpcErrorResponse) resp).getErrorType()) + .isEqualTo(RpcErrorType.UNSUPPORTED_FORK); + } + + @Override + protected String getMethodName() { + return RpcMethod.ENGINE_GET_PAYLOAD_V4.getMethodName(); + } +} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV6110Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV6110Test.java deleted file mode 100644 index 879f11e724c..00000000000 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineGetPayloadV6110Test.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.lenient; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.consensus.merge.blockcreation.PayloadIdentifier; -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.BlobGas; -import org.hyperledger.besu.datatypes.BlobsWithCommitments; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.datatypes.TransactionType; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EngineGetPayloadResultV6110; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.Quantity; -import org.hyperledger.besu.ethereum.core.BlobTestFixture; -import org.hyperledger.besu.ethereum.core.Block; -import org.hyperledger.besu.ethereum.core.BlockBody; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; -import org.hyperledger.besu.ethereum.core.BlockWithReceipts; -import org.hyperledger.besu.ethereum.core.Transaction; -import org.hyperledger.besu.ethereum.core.TransactionReceipt; -import org.hyperledger.besu.ethereum.core.TransactionTestFixture; - -import java.math.BigInteger; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith( - MockitoExtension.class) // mocks in parent class may not be used, throwing unnecessary stubbing -public class EngineGetPayloadV6110Test extends AbstractEngineGetPayloadTest { - - public EngineGetPayloadV6110Test() { - super(); - } - - @BeforeEach - @Override - public void before() { - super.before(); - lenient() - .when(mergeContext.retrieveBlockById(mockPid)) - .thenReturn(Optional.of(mockBlockWithReceiptsAndDeposits)); - when(protocolContext.safeConsensusContext(Mockito.any())).thenReturn(Optional.of(mergeContext)); - this.method = - new EngineGetPayloadV6110( - vertx, - protocolContext, - mergeMiningCoordinator, - factory, - engineCallListener, - protocolSchedule); - } - - @Override - @Test - public void shouldReturnExpectedMethodName() { - assertThat(method.getName()).isEqualTo("engine_getPayloadV6110"); - } - - @Override - @Test - public void shouldReturnBlockForKnownPayloadId() { - - BlockHeader eip6110Header = - new BlockHeaderTestFixture() - .prevRandao(Bytes32.random()) - .timestamp(experimentalHardfork.milestone() + 1) - .excessBlobGas(BlobGas.of(10L)) - .buildHeader(); - // should return withdrawals, deposits and excessGas for a post-6110 block - PayloadIdentifier postEip6110Pid = - PayloadIdentifier.forPayloadParams( - Hash.ZERO, - experimentalHardfork.milestone(), - Bytes32.random(), - Address.fromHexString("0x42"), - Optional.empty(), - Optional.empty()); - - BlobTestFixture blobTestFixture = new BlobTestFixture(); - BlobsWithCommitments bwc = blobTestFixture.createBlobsWithCommitments(1); - Transaction blobTx = - new TransactionTestFixture() - .to(Optional.of(Address.fromHexString("0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"))) - .type(TransactionType.BLOB) - .chainId(Optional.of(BigInteger.ONE)) - .maxFeePerGas(Optional.of(Wei.of(15))) - .maxFeePerBlobGas(Optional.of(Wei.of(128))) - .maxPriorityFeePerGas(Optional.of(Wei.of(1))) - .blobsWithCommitments(Optional.of(bwc)) - .versionedHashes(Optional.of(bwc.getVersionedHashes())) - .createTransaction(senderKeys); - TransactionReceipt blobReceipt = mock(TransactionReceipt.class); - when(blobReceipt.getCumulativeGasUsed()).thenReturn(100L); - BlockWithReceipts postEip6110Block = - new BlockWithReceipts( - new Block( - eip6110Header, - new BlockBody( - List.of(blobTx), - Collections.emptyList(), - Optional.of(Collections.emptyList()), - Optional.of(Collections.emptyList()))), - List.of(blobReceipt)); - - when(mergeContext.retrieveBlockById(postEip6110Pid)).thenReturn(Optional.of(postEip6110Block)); - - final var resp = resp(RpcMethod.ENGINE_GET_PAYLOAD_V6110.getMethodName(), postEip6110Pid); - assertThat(resp).isInstanceOf(JsonRpcSuccessResponse.class); - Optional.of(resp) - .map(JsonRpcSuccessResponse.class::cast) - .ifPresent( - r -> { - assertThat(r.getResult()).isInstanceOf(EngineGetPayloadResultV6110.class); - final EngineGetPayloadResultV6110 res = (EngineGetPayloadResultV6110) r.getResult(); - assertThat(res.getExecutionPayload().getWithdrawals()).isNotNull(); - assertThat(res.getExecutionPayload().getDeposits()).isNotNull(); - assertThat(res.getExecutionPayload().getHash()) - .isEqualTo(eip6110Header.getHash().toString()); - assertThat(res.getBlockValue()).isEqualTo(Quantity.create(0)); - assertThat(res.getExecutionPayload().getPrevRandao()) - .isEqualTo(eip6110Header.getPrevRandao().map(Bytes32::toString).orElse("")); - // excessBlobGas: QUANTITY, 256 bits - String expectedQuantityOf10 = Bytes32.leftPad(Bytes.of(10)).toQuantityHexString(); - assertThat(res.getExecutionPayload().getExcessBlobGas()).isNotEmpty(); - assertThat(res.getExecutionPayload().getExcessBlobGas()) - .isEqualTo(expectedQuantityOf10); - }); - verify(engineCallListener, times(1)).executionEngineCalled(); - } - - @Test - public void shouldReturnUnsupportedFork() { - final var resp = resp(RpcMethod.ENGINE_GET_PAYLOAD_V6110.getMethodName(), mockPid); - - assertThat(resp).isInstanceOf(JsonRpcErrorResponse.class); - assertThat(((JsonRpcErrorResponse) resp).getErrorType()) - .isEqualTo(RpcErrorType.UNSUPPORTED_FORK); - } - - @Override - protected String getMethodName() { - return RpcMethod.ENGINE_GET_PAYLOAD_V6110.getMethodName(); - } -} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV1Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV1Test.java index 0ad39def5ac..0973c7db4e7 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV1Test.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV1Test.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV2Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV2Test.java index f7f2cba533f..4e68cca76cb 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV2Test.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV2Test.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,7 +17,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.ExecutionEngineJsonRpcMethod.EngineStatus.INVALID; import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalParameterTestFixture.WITHDRAWAL_PARAM_1; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.INVALID_BLOB_GAS_USED_PARAMS; import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.INVALID_PARAMS; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.UNSUPPORTED_FORK; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -81,11 +83,15 @@ public void shouldReturnValidIfWithdrawalsIsNotNull_WhenWithdrawalsAllowed() { setupValidPayload( new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))), Optional.of(withdrawals), + Optional.empty(), Optional.empty()); lenient() .when(blockchain.getBlockHeader(mockHeader.getParentHash())) .thenReturn(Optional.of(mock(BlockHeader.class))); - var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList(), withdrawalsParam, null)); + var resp = + resp( + mockEnginePayload( + mockHeader, Collections.emptyList(), withdrawalsParam, null, null, null)); assertValidResponse(mockHeader, resp); } @@ -99,11 +105,13 @@ public void shouldReturnValidIfWithdrawalsIsNull_WhenWithdrawalsProhibited() { setupValidPayload( new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))), Optional.empty(), + Optional.empty(), Optional.empty()); lenient() .when(blockchain.getBlockHeader(mockHeader.getParentHash())) .thenReturn(Optional.of(mock(BlockHeader.class))); - var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList(), withdrawals, null)); + var resp = + resp(mockEnginePayload(mockHeader, Collections.emptyList(), withdrawals, null, null, null)); assertValidResponse(mockHeader, resp); } @@ -118,9 +126,12 @@ public void shouldReturnInvalidIfWithdrawalsIsNotNull_WhenWithdrawalsProhibited( var resp = resp( mockEnginePayload( - createBlockHeader(Optional.of(Collections.emptyList()), Optional.empty()), + createBlockHeader( + Optional.of(Collections.emptyList()), Optional.empty(), Optional.empty()), Collections.emptyList(), withdrawals, + null, + null, null)); final JsonRpcError jsonRpcError = fromErrorResp(resp); @@ -132,14 +143,16 @@ public void shouldReturnInvalidIfWithdrawalsIsNotNull_WhenWithdrawalsProhibited( public void shouldValidateBlobGasUsedCorrectly() { // V2 should return error if non-null blobGasUsed BlockHeader blockHeader = - createBlockHeaderFixture(Optional.of(Collections.emptyList()), Optional.empty()) + createBlockHeaderFixture( + Optional.of(Collections.emptyList()), Optional.empty(), Optional.empty()) .blobGasUsed(100L) .buildHeader(); - var resp = resp(mockEnginePayload(blockHeader, Collections.emptyList(), List.of(), null)); + var resp = + resp(mockEnginePayload(blockHeader, Collections.emptyList(), List.of(), null, null, null)); final JsonRpcError jsonRpcError = fromErrorResp(resp); - assertThat(jsonRpcError.getCode()).isEqualTo(INVALID_PARAMS.getCode()); - assertThat(jsonRpcError.getData()).isEqualTo("non-null BlobGasUsed pre-cancun"); + assertThat(jsonRpcError.getCode()).isEqualTo(INVALID_BLOB_GAS_USED_PARAMS.getCode()); + assertThat(jsonRpcError.getData()).isEqualTo("Missing blob gas used field"); verify(engineCallListener, times(1)).executionEngineCalled(); } @@ -147,15 +160,17 @@ public void shouldValidateBlobGasUsedCorrectly() { public void shouldValidateExcessBlobGasCorrectly() { // V2 should return error if non-null ExcessBlobGas BlockHeader blockHeader = - createBlockHeaderFixture(Optional.of(Collections.emptyList()), Optional.empty()) + createBlockHeaderFixture( + Optional.of(Collections.emptyList()), Optional.empty(), Optional.empty()) .excessBlobGas(BlobGas.MAX_BLOB_GAS) .buildHeader(); - var resp = resp(mockEnginePayload(blockHeader, Collections.emptyList(), List.of(), null)); + var resp = + resp(mockEnginePayload(blockHeader, Collections.emptyList(), List.of(), null, null, null)); final JsonRpcError jsonRpcError = fromErrorResp(resp); assertThat(jsonRpcError.getCode()).isEqualTo(INVALID_PARAMS.getCode()); - assertThat(jsonRpcError.getData()).isEqualTo("non-null ExcessBlobGas pre-cancun"); + assertThat(jsonRpcError.getData()).isEqualTo("Missing excess blob gas field"); verify(engineCallListener, times(1)).executionEngineCalled(); } @@ -168,15 +183,35 @@ public void shouldReturnInvalidIfWithdrawalsIsNull_WhenWithdrawalsAllowed() { var resp = resp( mockEnginePayload( - createBlockHeader(Optional.empty(), Optional.empty()), + createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty()), Collections.emptyList(), withdrawals, + null, + null, null)); assertThat(fromErrorResp(resp).getCode()).isEqualTo(INVALID_PARAMS.getCode()); verify(engineCallListener, times(1)).executionEngineCalled(); } + @Test + public void shouldReturnUnsupportedForkIfBlockTimestampIsAfterCancunMilestone() { + // Cancun starte at timestamp 30 + final long blockTimestamp = 31L; + BlockHeader blockHeader = + createBlockHeaderFixture( + Optional.of(Collections.emptyList()), Optional.empty(), Optional.empty()) + .timestamp(blockTimestamp) + .buildHeader(); + + var resp = + resp(mockEnginePayload(blockHeader, Collections.emptyList(), List.of(), null, null, null)); + + final JsonRpcError jsonRpcError = fromErrorResp(resp); + assertThat(jsonRpcError.getCode()).isEqualTo(UNSUPPORTED_FORK.getCode()); + verify(engineCallListener, times(1)).executionEngineCalled(); + } + @Override protected ExecutionEngineJsonRpcMethod.EngineStatus getExpectedInvalidBlockHashStatus() { return INVALID; diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV3Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV3Test.java index 8f319c27cf0..d8cf86758f8 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV3Test.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV3Test.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -44,10 +44,11 @@ import org.hyperledger.besu.ethereum.core.BlobTestFixture; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; -import org.hyperledger.besu.ethereum.core.Deposit; +import org.hyperledger.besu.ethereum.core.DepositRequest; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionTestFixture; import org.hyperledger.besu.ethereum.core.Withdrawal; +import org.hyperledger.besu.ethereum.core.WithdrawalRequest; import org.hyperledger.besu.ethereum.core.encoding.EncodingContext; import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder; import org.hyperledger.besu.ethereum.mainnet.BodyValidation; @@ -65,6 +66,7 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; @@ -133,9 +135,10 @@ public void shouldValidVersionedHash_whenListIsEmpty() { setupValidPayload( new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))), Optional.empty(), + Optional.empty(), Optional.empty()); final EnginePayloadParameter payload = - mockEnginePayload(mockHeader, Collections.emptyList(), null, null); + mockEnginePayload(mockHeader, Collections.emptyList(), null, null, null, null); ValidationResult res = method.validateParameters( @@ -148,7 +151,8 @@ public void shouldValidVersionedHash_whenListIsEmpty() { @Override protected BlockHeader createBlockHeader( final Optional> maybeWithdrawals, - final Optional> maybeDeposits) { + final Optional> maybeDepositRequests, + final Optional> maybeWithdrawalRequests) { BlockHeader parentBlockHeader = new BlockHeaderTestFixture() .baseFeePerGas(Wei.ONE) @@ -165,7 +169,6 @@ protected BlockHeader createBlockHeader( .number(parentBlockHeader.getNumber() + 1) .timestamp(parentBlockHeader.getTimestamp() + 12) .withdrawalsRoot(maybeWithdrawals.map(BodyValidation::withdrawalsRoot).orElse(null)) - .depositsRoot(maybeDeposits.map(BodyValidation::depositsRoot).orElse(null)) .excessBlobGas(BlobGas.ZERO) .blobGasUsed(0L) .parentBeaconBlockRoot( @@ -185,16 +188,18 @@ public void shouldReturnValidIfProtocolScheduleIsEmpty() { public void shouldValidateBlobGasUsedCorrectly() { // V3 must return error if null blobGasUsed BlockHeader blockHeader = - createBlockHeaderFixture(Optional.of(Collections.emptyList()), Optional.empty()) + createBlockHeaderFixture( + Optional.of(Collections.emptyList()), Optional.empty(), Optional.empty()) .excessBlobGas(BlobGas.MAX_BLOB_GAS) .blobGasUsed(null) .buildHeader(); - var resp = resp(mockEnginePayload(blockHeader, Collections.emptyList(), List.of(), null)); + var resp = + resp(mockEnginePayload(blockHeader, Collections.emptyList(), List.of(), null, null, null)); final JsonRpcError jsonRpcError = fromErrorResp(resp); assertThat(jsonRpcError.getCode()).isEqualTo(INVALID_PARAMS.getCode()); - assertThat(jsonRpcError.getData()).isEqualTo("Missing blob gas fields"); + assertThat(jsonRpcError.getData()).isEqualTo("Missing blob gas used field"); verify(engineCallListener, times(1)).executionEngineCalled(); } @@ -203,16 +208,18 @@ public void shouldValidateBlobGasUsedCorrectly() { public void shouldValidateExcessBlobGasCorrectly() { // V3 must return error if null excessBlobGas BlockHeader blockHeader = - createBlockHeaderFixture(Optional.of(Collections.emptyList()), Optional.empty()) + createBlockHeaderFixture( + Optional.of(Collections.emptyList()), Optional.empty(), Optional.empty()) .excessBlobGas(null) .blobGasUsed(100L) .buildHeader(); - var resp = resp(mockEnginePayload(blockHeader, Collections.emptyList(), List.of(), null)); + var resp = + resp(mockEnginePayload(blockHeader, Collections.emptyList(), List.of(), null, null, null)); final JsonRpcError jsonRpcError = fromErrorResp(resp); assertThat(jsonRpcError.getCode()).isEqualTo(INVALID_PARAMS.getCode()); - assertThat(jsonRpcError.getData()).isEqualTo("Missing blob gas fields"); + assertThat(jsonRpcError.getData()).isEqualTo("Missing excess blob gas field"); verify(engineCallListener, times(1)).executionEngineCalled(); } @@ -229,6 +236,7 @@ public void shouldRejectTransactionsWithFullBlobs() { setupValidPayload( new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))), Optional.empty(), + Optional.empty(), Optional.empty()); var resp = resp(mockEnginePayload(mockHeader, transactions)); @@ -254,6 +262,12 @@ private Transaction createTransactionWithBlobs() { .createTransaction(senderKeys); } + @Override + @Disabled + public void shouldReturnUnsupportedForkIfBlockTimestampIsAfterCancunMilestone() { + // only relevant for v2 + } + @Override protected JsonRpcResponse resp(final EnginePayloadParameter payload) { Object[] params = diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV4Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV4Test.java new file mode 100644 index 00000000000..9ea17213059 --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV4Test.java @@ -0,0 +1,343 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.DepositParameterTestFixture.DEPOSIT_PARAM_1; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalRequestTestFixture.WITHDRAWAL_REQUEST_PARAMETER_1; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.INVALID_PARAMS; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BlobGas; +import org.hyperledger.besu.datatypes.RequestType; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.BlockProcessingOutputs; +import org.hyperledger.besu.ethereum.BlockProcessingResult; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.DepositRequestParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EnginePayloadParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalRequestParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.core.DepositRequest; +import org.hyperledger.besu.ethereum.core.Request; +import org.hyperledger.besu.ethereum.core.Withdrawal; +import org.hyperledger.besu.ethereum.core.WithdrawalRequest; +import org.hyperledger.besu.ethereum.mainnet.BodyValidation; +import org.hyperledger.besu.ethereum.mainnet.requests.DepositRequestValidator; +import org.hyperledger.besu.ethereum.mainnet.requests.RequestsValidatorCoordinator; +import org.hyperledger.besu.ethereum.mainnet.requests.WithdrawalRequestValidator; +import org.hyperledger.besu.evm.gascalculator.PragueGasCalculator; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class EngineNewPayloadV4Test extends EngineNewPayloadV3Test { + private static final Address depositContractAddress = + Address.fromHexString("0x00000000219ab540356cbb839cbe05303d7705fa"); + + public EngineNewPayloadV4Test() {} + + @BeforeEach + @Override + public void before() { + super.before(); + maybeParentBeaconBlockRoot = Optional.of(Bytes32.ZERO); + // TODO this should be using NewPayloadV4 + this.method = + new EngineNewPayloadV3( + vertx, + protocolSchedule, + protocolContext, + mergeCoordinator, + ethPeers, + engineCallListener); + lenient().when(protocolSchedule.hardforkFor(any())).thenReturn(Optional.of(pragueHardfork)); + lenient().when(protocolSpec.getGasCalculator()).thenReturn(new PragueGasCalculator()); + } + + @Override + public void shouldReturnExpectedMethodName() { + assertThat(method.getName()).isEqualTo("engine_newPayloadV3"); + } + + @Test + public void shouldReturnValidIfDepositRequestsIsNull_WhenDepositRequestsProhibited() { + final List depositRequests = null; + mockProhibitedRequestsValidator(); + + BlockHeader mockHeader = + setupValidPayload( + new BlockProcessingResult( + Optional.of(new BlockProcessingOutputs(null, List.of(), Optional.empty()))), + Optional.empty(), + Optional.empty(), + Optional.empty()); + when(blockchain.getBlockHeader(mockHeader.getParentHash())) + .thenReturn(Optional.of(mock(BlockHeader.class))); + when(mergeCoordinator.getLatestValidAncestor(mockHeader)) + .thenReturn(Optional.of(mockHeader.getHash())); + + var resp = + resp( + mockEnginePayload( + mockHeader, Collections.emptyList(), null, depositRequests, null, null)); + + assertValidResponse(mockHeader, resp); + } + + @Test + public void shouldReturnInvalidIfDepositRequestsIsNull_WhenDepositRequestsAllowed() { + final List depositRequests = null; + mockAllowedDepositRequestsRequestValidator(); + var resp = + resp( + mockEnginePayload( + createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty()), + Collections.emptyList(), + null, + depositRequests, + null, + null)); + + assertThat(fromErrorResp(resp).getCode()).isEqualTo(INVALID_PARAMS.getCode()); + verify(engineCallListener, times(1)).executionEngineCalled(); + } + + @Test + public void shouldReturnValidIfDepositRequestsIsNotNull_WhenDepositRequestsAllowed() { + final List depositRequestsParam = List.of(DEPOSIT_PARAM_1); + final List depositRequests = List.of(DEPOSIT_PARAM_1.toDeposit()); + + mockAllowedDepositRequestsRequestValidator(); + BlockHeader mockHeader = + setupValidPayload( + new BlockProcessingResult( + Optional.of( + new BlockProcessingOutputs(null, List.of(), Optional.of(depositRequests)))), + Optional.empty(), + Optional.of(List.of(DEPOSIT_PARAM_1.toDeposit())), + Optional.empty()); + when(blockchain.getBlockHeader(mockHeader.getParentHash())) + .thenReturn(Optional.of(mock(BlockHeader.class))); + when(mergeCoordinator.getLatestValidAncestor(mockHeader)) + .thenReturn(Optional.of(mockHeader.getHash())); + var resp = + resp( + mockEnginePayload( + mockHeader, Collections.emptyList(), null, depositRequestsParam, null, null)); + + assertValidResponse(mockHeader, resp); + } + + @Test + public void shouldReturnInvalidIfDepositRequestsIsNotNull_WhenDepositRequestsProhibited() { + final List depositRequests = List.of(); + lenient() + .when(protocolSpec.getRequestsValidatorCoordinator()) + .thenReturn(RequestsValidatorCoordinator.empty()); + + var resp = + resp( + mockEnginePayload( + createBlockHeader( + Optional.empty(), Optional.of(Collections.emptyList()), Optional.empty()), + Collections.emptyList(), + null, + depositRequests, + null, + null)); + + final JsonRpcError jsonRpcError = fromErrorResp(resp); + assertThat(jsonRpcError.getCode()).isEqualTo(INVALID_PARAMS.getCode()); + verify(engineCallListener, times(1)).executionEngineCalled(); + } + + @Test + public void shouldReturnValidIfWithdrawalRequestsIsNull_WhenWithdrawalRequestsAreProhibited() { + mockProhibitedRequestsValidator(); + + BlockHeader mockHeader = + setupValidPayload( + new BlockProcessingResult( + Optional.of(new BlockProcessingOutputs(null, List.of(), Optional.empty()))), + Optional.empty(), + Optional.empty(), + Optional.empty()); + when(blockchain.getBlockHeader(mockHeader.getParentHash())) + .thenReturn(Optional.of(mock(BlockHeader.class))); + when(mergeCoordinator.getLatestValidAncestor(mockHeader)) + .thenReturn(Optional.of(mockHeader.getHash())); + + var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList(), null, null, null, null)); + + assertValidResponse(mockHeader, resp); + } + + @Test + public void shouldReturnInvalidIfWithdrawalRequestsIsNull_WhenWithdrawalRequestsAreAllowed() { + mockAllowedWithdrawalsRequestValidator(); + + var resp = + resp( + mockEnginePayload( + createBlockHeader(Optional.empty(), Optional.empty(), Optional.empty()), + Collections.emptyList(), + null, + null, + null, + null)); + + assertThat(fromErrorResp(resp).getCode()).isEqualTo(INVALID_PARAMS.getCode()); + verify(engineCallListener, times(1)).executionEngineCalled(); + } + + @Test + public void shouldReturnValidIfWithdrawalRequestsIsNotNull_WhenWithdrawalRequestsAreAllowed() { + final List withdrawalRequestsParams = + List.of(WITHDRAWAL_REQUEST_PARAMETER_1); + final List withdrawalRequests = + List.of(WITHDRAWAL_REQUEST_PARAMETER_1.toWithdrawalRequest()); + mockAllowedWithdrawalsRequestValidator(); + BlockHeader mockHeader = + setupValidPayload( + new BlockProcessingResult( + Optional.of( + new BlockProcessingOutputs(null, List.of(), Optional.of(withdrawalRequests)))), + Optional.empty(), + Optional.empty(), + Optional.of(List.of(WITHDRAWAL_REQUEST_PARAMETER_1.toWithdrawalRequest()))); + when(blockchain.getBlockHeader(mockHeader.getParentHash())) + .thenReturn(Optional.of(mock(BlockHeader.class))); + when(mergeCoordinator.getLatestValidAncestor(mockHeader)) + .thenReturn(Optional.of(mockHeader.getHash())); + var resp = + resp( + mockEnginePayload( + mockHeader, Collections.emptyList(), null, null, withdrawalRequestsParams, null)); + + assertValidResponse(mockHeader, resp); + } + + @Test + public void + shouldReturnInvalidIfWithdrawalRequestsIsNotNull_WhenWithdrawalRequestsAreProhibited() { + final List withdrawalRequests = List.of(); + mockProhibitedRequestsValidator(); + + var resp = + resp( + mockEnginePayload( + createBlockHeader( + Optional.empty(), Optional.empty(), Optional.of(Collections.emptyList())), + Collections.emptyList(), + null, + null, + withdrawalRequests, + null)); + + final JsonRpcError jsonRpcError = fromErrorResp(resp); + assertThat(jsonRpcError.getCode()).isEqualTo(INVALID_PARAMS.getCode()); + verify(engineCallListener, times(1)).executionEngineCalled(); + } + + @Override + protected BlockHeader createBlockHeader( + final Optional> maybeWithdrawals, + final Optional> maybeDepositRequests, + final Optional> maybeWithdrawalRequests) { + BlockHeader parentBlockHeader = + new BlockHeaderTestFixture() + .baseFeePerGas(Wei.ONE) + .timestamp(pragueHardfork.milestone()) + .excessBlobGas(BlobGas.ZERO) + .blobGasUsed(0L) + .buildHeader(); + + Optional> maybeRequests; + if (maybeDepositRequests.isPresent() || maybeWithdrawalRequests.isPresent()) { + List requests = new ArrayList<>(); + maybeDepositRequests.ifPresent(requests::addAll); + maybeWithdrawalRequests.ifPresent(requests::addAll); + maybeRequests = Optional.of(requests); + } else { + maybeRequests = Optional.empty(); + } + + BlockHeader mockHeader = + new BlockHeaderTestFixture() + .baseFeePerGas(Wei.ONE) + .parentHash(parentBlockHeader.getParentHash()) + .number(parentBlockHeader.getNumber() + 1) + .timestamp(parentBlockHeader.getTimestamp() + 1) + .withdrawalsRoot(maybeWithdrawals.map(BodyValidation::withdrawalsRoot).orElse(null)) + .excessBlobGas(BlobGas.ZERO) + .blobGasUsed(0L) + .requestsRoot(maybeRequests.map(BodyValidation::requestsRoot).orElse(null)) + .parentBeaconBlockRoot( + maybeParentBeaconBlockRoot.isPresent() ? maybeParentBeaconBlockRoot : null) + .buildHeader(); + return mockHeader; + } + + @Override + protected JsonRpcResponse resp(final EnginePayloadParameter payload) { + Object[] params = + maybeParentBeaconBlockRoot + .map(bytes32 -> new Object[] {payload, Collections.emptyList(), bytes32.toHexString()}) + .orElseGet(() -> new Object[] {payload}); + return method.response( + new JsonRpcRequestContext(new JsonRpcRequest("2.0", this.method.getName(), params))); + } + + private void mockProhibitedRequestsValidator() { + var validator = RequestsValidatorCoordinator.empty(); + when(protocolSpec.getRequestsValidatorCoordinator()).thenReturn(validator); + } + + private void mockAllowedDepositRequestsRequestValidator() { + var validator = + new RequestsValidatorCoordinator.Builder() + .addValidator(RequestType.DEPOSIT, new DepositRequestValidator(depositContractAddress)) + .build(); + when(protocolSpec.getRequestsValidatorCoordinator()).thenReturn(validator); + } + + private void mockAllowedWithdrawalsRequestValidator() { + var validator = + new RequestsValidatorCoordinator.Builder() + .addValidator(RequestType.WITHDRAWAL, new WithdrawalRequestValidator()) + .build(); + when(protocolSpec.getRequestsValidatorCoordinator()).thenReturn(validator); + } +} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV6110Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV6110Test.java deleted file mode 100644 index 6390b8716c6..00000000000 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineNewPayloadV6110Test.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.engine; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.DepositParameterTestFixture.DEPOSIT_PARAM_1; -import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType.INVALID_PARAMS; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.lenient; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.BlobGas; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.ethereum.BlockProcessingOutputs; -import org.hyperledger.besu.ethereum.BlockProcessingResult; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.DepositParameter; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EnginePayloadParameter; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; -import org.hyperledger.besu.ethereum.core.Deposit; -import org.hyperledger.besu.ethereum.core.Withdrawal; -import org.hyperledger.besu.ethereum.mainnet.BodyValidation; -import org.hyperledger.besu.ethereum.mainnet.DepositsValidator; -import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator; - -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import org.apache.tuweni.bytes.Bytes32; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -public class EngineNewPayloadV6110Test extends EngineNewPayloadV3Test { - private static final Address depositContractAddress = - Address.fromHexString("0x00000000219ab540356cbb839cbe05303d7705fa"); - - public EngineNewPayloadV6110Test() {} - - @BeforeEach - @Override - public void before() { - super.before(); - maybeParentBeaconBlockRoot = Optional.of(Bytes32.ZERO); - this.method = - new EngineNewPayloadV3( - vertx, - protocolSchedule, - protocolContext, - mergeCoordinator, - ethPeers, - engineCallListener); - lenient() - .when(protocolSchedule.hardforkFor(any())) - .thenReturn(Optional.of(super.cancunHardfork)); - lenient().when(protocolSpec.getGasCalculator()).thenReturn(new CancunGasCalculator()); - } - - @Override - public void shouldReturnExpectedMethodName() { - assertThat(method.getName()).isEqualTo("engine_newPayloadV3"); - } - - @Test - public void shouldReturnValidIfDepositsIsNull_WhenDepositsProhibited() { - final List deposits = null; - when(protocolSpec.getDepositsValidator()) - .thenReturn(new DepositsValidator.ProhibitedDeposits()); - - BlockHeader mockHeader = - setupValidPayload( - new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))), - Optional.empty(), - Optional.empty()); - when(blockchain.getBlockHeader(mockHeader.getParentHash())) - .thenReturn(Optional.of(mock(BlockHeader.class))); - when(mergeCoordinator.getLatestValidAncestor(mockHeader)) - .thenReturn(Optional.of(mockHeader.getHash())); - - var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList(), null, deposits)); - - assertValidResponse(mockHeader, resp); - } - - @Test - public void shouldReturnInvalidIfDepositsIsNull_WhenDepositsAllowed() { - final List deposits = null; - lenient() - .when(protocolSpec.getDepositsValidator()) - .thenReturn(new DepositsValidator.AllowedDeposits(depositContractAddress)); - - var resp = - resp( - mockEnginePayload( - createBlockHeader(Optional.empty(), Optional.empty()), - Collections.emptyList(), - null, - deposits)); - - assertThat(fromErrorResp(resp).getCode()).isEqualTo(INVALID_PARAMS.getCode()); - verify(engineCallListener, times(1)).executionEngineCalled(); - } - - @Test - public void shouldReturnValidIfDepositsIsNotNull_WhenDepositsAllowed() { - final List depositsParam = List.of(DEPOSIT_PARAM_1); - final List deposits = List.of(DEPOSIT_PARAM_1.toDeposit()); - when(protocolSpec.getDepositsValidator()) - .thenReturn(new DepositsValidator.AllowedDeposits(depositContractAddress)); - BlockHeader mockHeader = - setupValidPayload( - new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(null, List.of()))), - Optional.empty(), - Optional.of(deposits)); - when(blockchain.getBlockHeader(mockHeader.getParentHash())) - .thenReturn(Optional.of(mock(BlockHeader.class))); - when(mergeCoordinator.getLatestValidAncestor(mockHeader)) - .thenReturn(Optional.of(mockHeader.getHash())); - var resp = resp(mockEnginePayload(mockHeader, Collections.emptyList(), null, depositsParam)); - - assertValidResponse(mockHeader, resp); - } - - @Test - public void shouldReturnInvalidIfDepositsIsNotNull_WhenDepositsProhibited() { - final List deposits = List.of(); - lenient() - .when(protocolSpec.getDepositsValidator()) - .thenReturn(new DepositsValidator.ProhibitedDeposits()); - - var resp = - resp( - mockEnginePayload( - createBlockHeader(Optional.empty(), Optional.of(Collections.emptyList())), - Collections.emptyList(), - null, - deposits)); - - final JsonRpcError jsonRpcError = fromErrorResp(resp); - assertThat(jsonRpcError.getCode()).isEqualTo(INVALID_PARAMS.getCode()); - verify(engineCallListener, times(1)).executionEngineCalled(); - } - - @Override - protected BlockHeader createBlockHeader( - final Optional> maybeWithdrawals, - final Optional> maybeDeposits) { - BlockHeader parentBlockHeader = - new BlockHeaderTestFixture() - .baseFeePerGas(Wei.ONE) - .timestamp(super.experimentalHardfork.milestone()) - .excessBlobGas(BlobGas.ZERO) - .blobGasUsed(0L) - .buildHeader(); - - BlockHeader mockHeader = - new BlockHeaderTestFixture() - .baseFeePerGas(Wei.ONE) - .parentHash(parentBlockHeader.getParentHash()) - .number(parentBlockHeader.getNumber() + 1) - .timestamp(parentBlockHeader.getTimestamp() + 1) - .withdrawalsRoot(maybeWithdrawals.map(BodyValidation::withdrawalsRoot).orElse(null)) - .excessBlobGas(BlobGas.ZERO) - .blobGasUsed(0L) - .depositsRoot(maybeDeposits.map(BodyValidation::depositsRoot).orElse(null)) - .parentBeaconBlockRoot( - maybeParentBeaconBlockRoot.isPresent() ? maybeParentBeaconBlockRoot : null) - .buildHeader(); - return mockHeader; - } - - @Override - protected JsonRpcResponse resp(final EnginePayloadParameter payload) { - Object[] params = - maybeParentBeaconBlockRoot - .map(bytes32 -> new Object[] {payload, Collections.emptyList(), bytes32.toHexString()}) - .orElseGet(() -> new Object[] {payload}); - return method.response( - new JsonRpcRequestContext(new JsonRpcRequest("2.0", this.method.getName(), params))); - } -} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EnginePreparePayloadDebugTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EnginePreparePayloadDebugTest.java index 0611813696c..9c5067f7a83 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EnginePreparePayloadDebugTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EnginePreparePayloadDebugTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -28,6 +28,7 @@ import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.EnginePreparePayloadParameter; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.results.EnginePreparePayloadResult; @@ -56,7 +57,7 @@ public class EnginePreparePayloadDebugTest { @Mock private EnginePreparePayloadParameter param; @BeforeEach - public void setUp() { + public void setUp() throws JsonRpcParameterException { when(protocolContext.safeConsensusContext(MergeContext.class)) .thenReturn(Optional.of(mergeContext)); when(requestContext.getOptionalParameter(0, EnginePreparePayloadParameter.class)) @@ -84,7 +85,7 @@ public void shouldReturnPayloadId() { } @Test - public void shouldReturnPayloadIdWhenNoParams() { + public void shouldReturnPayloadIdWhenNoParams() throws JsonRpcParameterException { when(requestContext.getOptionalParameter(0, EnginePreparePayloadParameter.class)) .thenReturn(Optional.empty()); checkForPayloadId(); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineQosTimerTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineQosTimerTest.java index b25fd7e3d0b..f344e57c34f 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineQosTimerTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/engine/EngineQosTimerTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerChangeTargetGasLimitTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerChangeTargetGasLimitTest.java index f31ac5e986f..df9d9247c35 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerChangeTargetGasLimitTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerChangeTargetGasLimitTest.java @@ -50,7 +50,8 @@ public void failsWithInvalidValue() { assertThat(minerChangeTargetGasLimit.response(request)) .isEqualTo( - new JsonRpcErrorResponse(request.getRequest().getId(), RpcErrorType.INVALID_PARAMS)); + new JsonRpcErrorResponse( + request.getRequest().getId(), RpcErrorType.INVALID_TARGET_GAS_LIMIT_PARAMS)); } @Test diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerGetExtraDataTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerGetExtraDataTest.java new file mode 100644 index 00000000000..ebaf817a477 --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerGetExtraDataTest.java @@ -0,0 +1,62 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.miner; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters; +import org.hyperledger.besu.ethereum.core.MiningParameters; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; + +public class MinerGetExtraDataTest { + + @Test + public void shouldReturnDefaultExtraData() { + final MiningParameters miningParameters = ImmutableMiningParameters.newDefault(); + final MinerGetExtraData method = new MinerGetExtraData(miningParameters); + final JsonRpcRequestContext request = + new JsonRpcRequestContext(new JsonRpcRequest("2.0", method.getName(), new Object[] {})); + + final JsonRpcResponse expected = new JsonRpcSuccessResponse(request.getRequest().getId(), "0x"); + + final JsonRpcResponse actual = method.response(request); + assertThat(actual).usingRecursiveComparison().isEqualTo(expected); + } + + @Test + public void shouldReturnSetAtRuntimeExtraData() { + final MiningParameters miningParameters = ImmutableMiningParameters.newDefault(); + final MinerGetExtraData method = new MinerGetExtraData(miningParameters); + final var extraData = "0x123456"; + final Bytes extraDataAtRuntime = Bytes.fromHexString(extraData); + + miningParameters.setExtraData(extraDataAtRuntime); + + final JsonRpcRequestContext request = + new JsonRpcRequestContext(new JsonRpcRequest("2.0", method.getName(), new Object[] {})); + + final JsonRpcResponse expected = + new JsonRpcSuccessResponse(request.getRequest().getId(), extraData); + + final JsonRpcResponse actual = method.response(request); + assertThat(actual).usingRecursiveComparison().isEqualTo(expected); + } +} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerGetMinGasPriceTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerGetMinGasPriceTest.java index ebb8035b5f7..f73229f0e0f 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerGetMinGasPriceTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerGetMinGasPriceTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerGetMinPriorityFeeTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerGetMinPriorityFeeTest.java index 4022e10244f..78fb85b94b7 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerGetMinPriorityFeeTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerGetMinPriorityFeeTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetCoinbaseTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetCoinbaseTest.java index a50a9576b08..20abf9e9c1d 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetCoinbaseTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetCoinbaseTest.java @@ -62,7 +62,7 @@ public void shouldFailWhenMissingAddress() { assertThat(thrown) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 0"); + .hasMessage("Invalid address parameter (index 0)"); } @Test diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetExtraDataTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetExtraDataTest.java new file mode 100644 index 00000000000..b3c7b45d076 --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetExtraDataTest.java @@ -0,0 +1,109 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.miner; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; +import org.hyperledger.besu.ethereum.core.MiningParameters; + +import java.nio.charset.StandardCharsets; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class MinerSetExtraDataTest { + MiningParameters miningParameters = MiningParameters.newDefault(); + private MinerSetExtraData method; + + @BeforeEach + public void setUp() { + method = new MinerSetExtraData(miningParameters); + } + + @Test + public void shouldChangeExtraData() { + final String newExtraData = " pippo 🐐 | "; + final var request = + request(Bytes.wrap(newExtraData.getBytes(StandardCharsets.UTF_8)).toHexString()); + final JsonRpcResponse expected = new JsonRpcSuccessResponse(request.getRequest().getId(), true); + + final JsonRpcResponse actual = method.response(request); + assertThat(actual).usingRecursiveComparison().isEqualTo(expected); + final var currExtraData = miningParameters.getExtraData(); + assertThat(new String(currExtraData.toArray(), StandardCharsets.UTF_8)).isEqualTo(newExtraData); + } + + private JsonRpcRequestContext request(final String newExtraData) { + return new JsonRpcRequestContext( + new JsonRpcRequest("2.0", method.getName(), new Object[] {newExtraData})); + } + + @Test + public void shouldNotTrimLeadingZeros() { + final var zeroPrefixedExtraData = "0010203"; + final var request = request(zeroPrefixedExtraData); + + final JsonRpcResponse expected = new JsonRpcSuccessResponse(request.getRequest().getId(), true); + + final JsonRpcResponse actual = method.response(request); + assertThat(actual).usingRecursiveComparison().isEqualTo(expected); + final var currExtraData = miningParameters.getExtraData(); + assertThat(new String(currExtraData.toArray(), StandardCharsets.UTF_8)) + .isEqualTo( + new String( + Bytes.fromHexStringLenient(zeroPrefixedExtraData).toArray(), + StandardCharsets.UTF_8)); + } + + @Test + public void shouldReturnErrorWhenExtraDataNotHex() { + final var request = request("not hex string"); + + final JsonRpcResponse expected = + new JsonRpcErrorResponse( + request.getRequest().getId(), + new JsonRpcError( + RpcErrorType.INVALID_EXTRA_DATA_PARAMS, + "Illegal character 'n' found at index 0 in hex binary representation")); + + final JsonRpcResponse actual = method.response(request); + assertThat(actual).usingRecursiveComparison().isEqualTo(expected); + } + + @Test + public void shouldReturnErrorWhenExtraDataTooLong() { + final var tooLongExtraData = "shouldReturnFalseWhenExtraDataTooLong"; + final var request = + request(Bytes.wrap(tooLongExtraData.getBytes(StandardCharsets.UTF_8)).toHexString()); + + final JsonRpcResponse expected = + new JsonRpcErrorResponse( + request.getRequest().getId(), + new JsonRpcError( + RpcErrorType.INVALID_EXTRA_DATA_PARAMS, + "Hex value is too large: expected at most 32 bytes but got 37")); + + final JsonRpcResponse actual = method.response(request); + assertThat(actual).usingRecursiveComparison().isEqualTo(expected); + } +} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetMinGasPriceTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetMinGasPriceTest.java index 824b8f8fb81..fb56b573075 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetMinGasPriceTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetMinGasPriceTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -48,7 +48,7 @@ public void shouldReturnFalseWhenParameterIsInvalid() { new JsonRpcErrorResponse( request.getRequest().getId(), new JsonRpcError( - RpcErrorType.INVALID_PARAMS, + RpcErrorType.INVALID_MIN_GAS_PRICE_PARAMS, "Illegal character '-' found at index 0 in hex binary representation")); final JsonRpcResponse actual = method.response(request); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetMinPriorityFeeTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetMinPriorityFeeTest.java index 0634274bc75..7e564ac4259 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetMinPriorityFeeTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/miner/MinerSetMinPriorityFeeTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -49,7 +49,7 @@ public void shouldReturnInvalidParamsWhenParameterIsInvalid() { new JsonRpcErrorResponse( request.getRequest().getId(), new JsonRpcError( - RpcErrorType.INVALID_PARAMS, + RpcErrorType.INVALID_MIN_PRIORITY_FEE_PARAMS, "Hex value is too large: expected at most 32 bytes but got 33")); final JsonRpcResponse actual = method.response(request); @@ -65,7 +65,8 @@ public void shouldReturnInvalidParamsWhenParameterIsMissing() { new JsonRpcErrorResponse( request.getRequest().getId(), new JsonRpcError( - RpcErrorType.INVALID_PARAMS, "Missing required json rpc parameter at index 0")); + RpcErrorType.INVALID_MIN_PRIORITY_FEE_PARAMS, + "Missing required json rpc parameter at index 0")); final JsonRpcResponse actual = method.response(request); assertThat(actual).usingRecursiveComparison().isEqualTo(expected); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermAddAccountsToAllowlistTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermAddAccountsToAllowlistTest.java index 0892cc5c647..a87a6487fb5 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermAddAccountsToAllowlistTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermAddAccountsToAllowlistTest.java @@ -43,12 +43,12 @@ @ExtendWith(MockitoExtension.class) public class PermAddAccountsToAllowlistTest { - @Mock private AccountLocalConfigPermissioningController accountWhitelist; + @Mock private AccountLocalConfigPermissioningController accountAllowlist; private PermAddAccountsToAllowlist method; @BeforeEach public void before() { - method = new PermAddAccountsToAllowlist(java.util.Optional.of(accountWhitelist)); + method = new PermAddAccountsToAllowlist(java.util.Optional.of(accountAllowlist)); } @Test @@ -57,10 +57,10 @@ public void getNameShouldReturnExpectedName() { } @Test - public void whenAccountsAreAddedToWhitelistShouldReturnSuccess() { + public void whenAccountsAreAddedToAllowlistShouldReturnSuccess() { List accounts = Arrays.asList("0x0", "0x1"); JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null); - when(accountWhitelist.addAccounts(eq(accounts))).thenReturn(AllowlistOperationResult.SUCCESS); + when(accountAllowlist.addAccounts(eq(accounts))).thenReturn(AllowlistOperationResult.SUCCESS); JsonRpcResponse actualResponse = method.response(request(accounts)); @@ -71,7 +71,7 @@ public void whenAccountsAreAddedToWhitelistShouldReturnSuccess() { public void whenAccountIsInvalidShouldReturnInvalidAccountErrorResponse() { JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.ACCOUNT_ALLOWLIST_INVALID_ENTRY); - when(accountWhitelist.addAccounts(any())) + when(accountAllowlist.addAccounts(any())) .thenReturn(AllowlistOperationResult.ERROR_INVALID_ENTRY); JsonRpcResponse actualResponse = method.response(request(new ArrayList<>())); @@ -83,7 +83,7 @@ public void whenAccountIsInvalidShouldReturnInvalidAccountErrorResponse() { public void whenAccountExistsShouldReturnExistingEntryErrorResponse() { JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.ACCOUNT_ALLOWLIST_EXISTING_ENTRY); - when(accountWhitelist.addAccounts(any())) + when(accountAllowlist.addAccounts(any())) .thenReturn(AllowlistOperationResult.ERROR_EXISTING_ENTRY); JsonRpcResponse actualResponse = method.response(request(new ArrayList<>())); @@ -95,7 +95,7 @@ public void whenAccountExistsShouldReturnExistingEntryErrorResponse() { public void whenInputHasDuplicatedAccountsShouldReturnDuplicatedEntryErrorResponse() { JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.ACCOUNT_ALLOWLIST_DUPLICATED_ENTRY); - when(accountWhitelist.addAccounts(any())) + when(accountAllowlist.addAccounts(any())) .thenReturn(AllowlistOperationResult.ERROR_DUPLICATED_ENTRY); JsonRpcResponse actualResponse = method.response(request(new ArrayList<>())); @@ -108,7 +108,7 @@ public void whenEmptyListOnRequestShouldReturnEmptyEntryErrorResponse() { JsonRpcResponse expectedResponse = new JsonRpcErrorResponse(null, RpcErrorType.ACCOUNT_ALLOWLIST_EMPTY_ENTRY); - when(accountWhitelist.addAccounts(eq(new ArrayList<>()))) + when(accountAllowlist.addAccounts(eq(new ArrayList<>()))) .thenReturn(AllowlistOperationResult.ERROR_EMPTY_ENTRY); JsonRpcResponse actualResponse = method.response(request(new ArrayList<>())); @@ -124,9 +124,8 @@ public void whenEmptyParamOnRequestShouldThrowInvalidJsonRpcException() { final Throwable thrown = catchThrowable(() -> method.response(request)); assertThat(thrown) - .hasNoCause() .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 0"); + .hasMessage("Invalid accounts list parameter (index 0)"); } private JsonRpcRequestContext request(final List accounts) { diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermAddAccountsToWhitelistTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermAddAccountsToWhitelistTest.java deleted file mode 100644 index 0dd4fb73d46..00000000000 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermAddAccountsToWhitelistTest.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.catchThrowable; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; -import org.hyperledger.besu.ethereum.permissioning.AccountLocalConfigPermissioningController; -import org.hyperledger.besu.ethereum.permissioning.AllowlistOperationResult; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@Deprecated -@ExtendWith(MockitoExtension.class) -public class PermAddAccountsToWhitelistTest { - - @Mock private AccountLocalConfigPermissioningController accountWhitelist; - private PermAddAccountsToWhitelist method; - - @BeforeEach - public void before() { - method = new PermAddAccountsToWhitelist(java.util.Optional.of(accountWhitelist)); - } - - @Test - public void getNameShouldReturnExpectedName() { - assertThat(method.getName()).isEqualTo("perm_addAccountsToWhitelist"); - } - - @Test - public void whenAccountsAreAddedToWhitelistShouldReturnSuccess() { - List accounts = Arrays.asList("0x0", "0x1"); - JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null); - when(accountWhitelist.addAccounts(eq(accounts))).thenReturn(AllowlistOperationResult.SUCCESS); - - JsonRpcResponse actualResponse = method.response(request(accounts)); - - assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); - } - - @Test - public void whenAccountIsInvalidShouldReturnInvalidAccountErrorResponse() { - JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(null, RpcErrorType.ACCOUNT_ALLOWLIST_INVALID_ENTRY); - when(accountWhitelist.addAccounts(any())) - .thenReturn(AllowlistOperationResult.ERROR_INVALID_ENTRY); - - JsonRpcResponse actualResponse = method.response(request(new ArrayList<>())); - - assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); - } - - @Test - public void whenAccountExistsShouldReturnExistingEntryErrorResponse() { - JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(null, RpcErrorType.ACCOUNT_ALLOWLIST_EXISTING_ENTRY); - when(accountWhitelist.addAccounts(any())) - .thenReturn(AllowlistOperationResult.ERROR_EXISTING_ENTRY); - - JsonRpcResponse actualResponse = method.response(request(new ArrayList<>())); - - assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); - } - - @Test - public void whenInputHasDuplicatedAccountsShouldReturnDuplicatedEntryErrorResponse() { - JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(null, RpcErrorType.ACCOUNT_ALLOWLIST_DUPLICATED_ENTRY); - when(accountWhitelist.addAccounts(any())) - .thenReturn(AllowlistOperationResult.ERROR_DUPLICATED_ENTRY); - - JsonRpcResponse actualResponse = method.response(request(new ArrayList<>())); - - assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); - } - - @Test - public void whenEmptyListOnRequestShouldReturnEmptyEntryErrorResponse() { - JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(null, RpcErrorType.ACCOUNT_ALLOWLIST_EMPTY_ENTRY); - - when(accountWhitelist.addAccounts(eq(new ArrayList<>()))) - .thenReturn(AllowlistOperationResult.ERROR_EMPTY_ENTRY); - - JsonRpcResponse actualResponse = method.response(request(new ArrayList<>())); - - assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); - } - - @Test - public void whenEmptyParamOnRequestShouldThrowInvalidJsonRpcException() { - JsonRpcRequestContext request = - new JsonRpcRequestContext( - new JsonRpcRequest("2.0", "perm_addAccountsToWhitelist", new Object[] {})); - - final Throwable thrown = catchThrowable(() -> method.response(request)); - assertThat(thrown) - .hasNoCause() - .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 0"); - } - - private JsonRpcRequestContext request(final List accounts) { - return new JsonRpcRequestContext( - new JsonRpcRequest("2.0", "perm_addAccountsToWhitelist", new Object[] {accounts})); - } -} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermAddNodesToWhitelistTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermAddNodesToWhitelistTest.java deleted file mode 100644 index 237094b90c8..00000000000 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermAddNodesToWhitelistTest.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.ethereum.permissioning.NodeLocalConfigPermissioningController.NodesAllowlistResult; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; -import org.hyperledger.besu.ethereum.permissioning.AllowlistOperationResult; -import org.hyperledger.besu.ethereum.permissioning.NodeLocalConfigPermissioningController; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import org.assertj.core.api.Assertions; -import org.assertj.core.util.Lists; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@Deprecated -@ExtendWith(MockitoExtension.class) -public class PermAddNodesToWhitelistTest { - - private PermAddNodesToWhitelist method; - private static final String METHOD_NAME = "perm_addNodesToWhitelist"; - - private final String enode1 = - "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.10:4567"; - private final String enode2 = - "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.10:4567"; - private final String enode3 = - "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.10:4567"; - private final String badEnode = "enod://dog@cat:fish"; - - @Mock private NodeLocalConfigPermissioningController nodeLocalConfigPermissioningController; - - @BeforeEach - public void setUp() { - method = new PermAddNodesToWhitelist(Optional.of(nodeLocalConfigPermissioningController)); - } - - @Test - public void shouldReturnCorrectMethodName() { - assertThat(method.getName()).isEqualTo(METHOD_NAME); - } - - @Test - public void shouldThrowInvalidJsonRpcParametersExceptionWhenOnlyBadEnode() { - final ArrayList enodeList = Lists.newArrayList(badEnode); - final JsonRpcRequestContext request = buildRequest(enodeList); - final JsonRpcResponse expected = - new JsonRpcErrorResponse( - request.getRequest().getId(), RpcErrorType.NODE_ALLOWLIST_INVALID_ENTRY); - - when(nodeLocalConfigPermissioningController.addNodes(eq(enodeList))) - .thenThrow(IllegalArgumentException.class); - - final JsonRpcResponse actual = method.response(request); - - assertThat(actual).usingRecursiveComparison().isEqualTo(expected); - } - - @Test - public void shouldThrowInvalidJsonRpcParametersExceptionWhenBadEnodeInList() { - final ArrayList enodeList = Lists.newArrayList(enode2, badEnode, enode1); - final JsonRpcRequestContext request = buildRequest(enodeList); - final JsonRpcResponse expected = - new JsonRpcErrorResponse( - request.getRequest().getId(), RpcErrorType.NODE_ALLOWLIST_INVALID_ENTRY); - - when(nodeLocalConfigPermissioningController.addNodes(eq(enodeList))) - .thenThrow(IllegalArgumentException.class); - - final JsonRpcResponse actual = method.response(request); - - assertThat(actual).usingRecursiveComparison().isEqualTo(expected); - } - - @Test - public void shouldThrowInvalidJsonRpcParametersExceptionWhenEmptyEnode() { - final JsonRpcRequestContext request = buildRequest(Collections.emptyList()); - final JsonRpcResponse expected = - new JsonRpcErrorResponse( - request.getRequest().getId(), RpcErrorType.NODE_ALLOWLIST_EMPTY_ENTRY); - - when(nodeLocalConfigPermissioningController.addNodes(Collections.emptyList())) - .thenReturn(new NodesAllowlistResult(AllowlistOperationResult.ERROR_EMPTY_ENTRY)); - - final JsonRpcResponse actual = method.response(request); - - assertThat(actual).usingRecursiveComparison().isEqualTo(expected); - } - - @Test - public void whenRequestContainsDuplicatedNodesShouldReturnDuplicatedEntryError() { - final JsonRpcRequestContext request = buildRequest(Lists.newArrayList(enode1, enode1)); - final JsonRpcResponse expected = - new JsonRpcErrorResponse( - request.getRequest().getId(), RpcErrorType.NODE_ALLOWLIST_DUPLICATED_ENTRY); - - when(nodeLocalConfigPermissioningController.addNodes(any())) - .thenReturn(new NodesAllowlistResult(AllowlistOperationResult.ERROR_DUPLICATED_ENTRY)); - - final JsonRpcResponse actual = method.response(request); - - assertThat(actual).usingRecursiveComparison().isEqualTo(expected); - } - - @Test - public void whenRequestContainsEmptyListOfNodesShouldReturnEmptyEntryError() { - final JsonRpcRequestContext request = buildRequest(new ArrayList<>()); - final JsonRpcResponse expected = - new JsonRpcErrorResponse( - request.getRequest().getId(), RpcErrorType.NODE_ALLOWLIST_EMPTY_ENTRY); - - when(nodeLocalConfigPermissioningController.addNodes(eq(new ArrayList<>()))) - .thenReturn(new NodesAllowlistResult(AllowlistOperationResult.ERROR_EMPTY_ENTRY)); - - final JsonRpcResponse actual = method.response(request); - - assertThat(actual).usingRecursiveComparison().isEqualTo(expected); - } - - @Test - public void shouldAddSingleValidNode() { - final JsonRpcRequestContext request = buildRequest(Lists.newArrayList(enode1)); - final JsonRpcResponse expected = new JsonRpcSuccessResponse(request.getRequest().getId()); - - when(nodeLocalConfigPermissioningController.addNodes(any())) - .thenReturn(new NodesAllowlistResult(AllowlistOperationResult.SUCCESS)); - - final JsonRpcResponse actual = method.response(request); - - assertThat(actual).usingRecursiveComparison().isEqualTo(expected); - - verify(nodeLocalConfigPermissioningController, times(1)).addNodes(any()); - verifyNoMoreInteractions(nodeLocalConfigPermissioningController); - } - - @Test - public void shouldAddMultipleValidNodes() { - final JsonRpcRequestContext request = buildRequest(Lists.newArrayList(enode1, enode2, enode3)); - final JsonRpcResponse expected = new JsonRpcSuccessResponse(request.getRequest().getId()); - - when(nodeLocalConfigPermissioningController.addNodes(any())) - .thenReturn(new NodesAllowlistResult(AllowlistOperationResult.SUCCESS)); - - final JsonRpcResponse actual = method.response(request); - - assertThat(actual).usingRecursiveComparison().isEqualTo(expected); - - verify(nodeLocalConfigPermissioningController, times(1)).addNodes(any()); - verifyNoMoreInteractions(nodeLocalConfigPermissioningController); - } - - @Test - public void shouldFailWhenP2pDisabled() { - method = new PermAddNodesToWhitelist(Optional.empty()); - - final JsonRpcRequestContext request = buildRequest(Lists.newArrayList(enode1, enode2, enode3)); - ; - final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse( - request.getRequest().getId(), RpcErrorType.NODE_ALLOWLIST_NOT_ENABLED); - - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); - } - - private JsonRpcRequestContext buildRequest(final List enodeList) { - return new JsonRpcRequestContext( - new JsonRpcRequest("2.0", METHOD_NAME, new Object[] {enodeList})); - } -} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermGetAccountsWhitelistTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermGetAccountsWhitelistTest.java deleted file mode 100644 index 024349d8b23..00000000000 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermGetAccountsWhitelistTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; -import org.hyperledger.besu.ethereum.permissioning.AccountLocalConfigPermissioningController; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@Deprecated -@ExtendWith(MockitoExtension.class) -public class PermGetAccountsWhitelistTest { - - private static final JsonRpcRequestContext request = - new JsonRpcRequestContext(new JsonRpcRequest("2.0", "perm_getAccountsWhitelist", null)); - - @Mock private AccountLocalConfigPermissioningController accountWhitelist; - private PermGetAccountsWhitelist method; - - @BeforeEach - public void before() { - method = new PermGetAccountsWhitelist(java.util.Optional.of(accountWhitelist)); - } - - @Test - public void getNameShouldReturnExpectedName() { - assertThat(method.getName()).isEqualTo("perm_getAccountsWhitelist"); - } - - @Test - public void shouldReturnExpectedListOfAccountsWhenWhitelistHasBeenSet() { - List accountsList = Arrays.asList("0x0", "0x1"); - when(accountWhitelist.getAccountAllowlist()).thenReturn(accountsList); - JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, accountsList); - - JsonRpcResponse actualResponse = method.response(request); - - assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); - } - - @Test - public void shouldReturnEmptyListOfAccountsWhenWhitelistHasBeenSetAndIsEmpty() { - List emptyAccountsList = new ArrayList<>(); - when(accountWhitelist.getAccountAllowlist()).thenReturn(emptyAccountsList); - JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, emptyAccountsList); - - JsonRpcResponse actualResponse = method.response(request); - - assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); - } -} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermGetNodesWhitelistTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermGetNodesWhitelistTest.java deleted file mode 100644 index 9981cb27e74..00000000000 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermGetNodesWhitelistTest.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; -import org.hyperledger.besu.ethereum.permissioning.NodeLocalConfigPermissioningController; - -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import org.assertj.core.api.Assertions; -import org.assertj.core.util.Lists; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@Deprecated -@ExtendWith(MockitoExtension.class) -public class PermGetNodesWhitelistTest { - - private PermGetNodesWhitelist method; - private static final String METHOD_NAME = "perm_getNodesWhitelist"; - - private final String enode1 = - "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.10:4567"; - private final String enode2 = - "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.11:4567"; - private final String enode3 = - "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.12:4567"; - - @Mock private NodeLocalConfigPermissioningController nodeLocalConfigPermissioningController; - - @BeforeEach - public void setUp() { - method = new PermGetNodesWhitelist(Optional.of(nodeLocalConfigPermissioningController)); - } - - @Test - public void shouldReturnCorrectMethodName() { - assertThat(method.getName()).isEqualTo(METHOD_NAME); - } - - @Test - public void shouldReturnSuccessResponseWhenListPopulated() { - final JsonRpcRequestContext request = buildRequest(); - final JsonRpcResponse expected = - new JsonRpcSuccessResponse( - request.getRequest().getId(), Lists.newArrayList(enode1, enode2, enode3)); - - when(nodeLocalConfigPermissioningController.getNodesAllowlist()) - .thenReturn(buildNodesList(enode1, enode2, enode3)); - - final JsonRpcResponse actual = method.response(request); - - assertThat(actual).usingRecursiveComparison().isEqualTo(expected); - - verify(nodeLocalConfigPermissioningController, times(1)).getNodesAllowlist(); - verifyNoMoreInteractions(nodeLocalConfigPermissioningController); - } - - @Test - public void shouldReturnSuccessResponseWhenListSetAndEmpty() { - final JsonRpcRequestContext request = buildRequest(); - final JsonRpcResponse expected = - new JsonRpcSuccessResponse(request.getRequest().getId(), Collections.emptyList()); - - when(nodeLocalConfigPermissioningController.getNodesAllowlist()).thenReturn(buildNodesList()); - - final JsonRpcResponse actual = method.response(request); - - assertThat(actual).usingRecursiveComparison().isEqualTo(expected); - - verify(nodeLocalConfigPermissioningController, times(1)).getNodesAllowlist(); - verifyNoMoreInteractions(nodeLocalConfigPermissioningController); - } - - @Test - public void shouldFailWhenP2pDisabled() { - method = new PermGetNodesWhitelist(Optional.empty()); - - final JsonRpcRequestContext request = buildRequest(); - final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse( - request.getRequest().getId(), RpcErrorType.NODE_ALLOWLIST_NOT_ENABLED); - - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); - } - - private JsonRpcRequestContext buildRequest() { - return new JsonRpcRequestContext(new JsonRpcRequest("2.0", METHOD_NAME, new Object[] {})); - } - - private List buildNodesList(final String... enodes) { - return Lists.newArrayList(enodes); - } -} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermRemoveAccountsFromAllowlistTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermRemoveAccountsFromAllowlistTest.java index a70ac9e4b3a..af3fc2ee6ad 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermRemoveAccountsFromAllowlistTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermRemoveAccountsFromAllowlistTest.java @@ -125,9 +125,8 @@ public void whenEmptyParamOnRequestShouldThrowInvalidJsonRpcException() { final Throwable thrown = catchThrowable(() -> method.response(request)); assertThat(thrown) - .hasNoCause() .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 0"); + .hasMessage("Invalid accounts list parameter (index 0)"); } private JsonRpcRequestContext request(final List accounts) { diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermRemoveAccountsFromWhitelistTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermRemoveAccountsFromWhitelistTest.java deleted file mode 100644 index 934329052dc..00000000000 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermRemoveAccountsFromWhitelistTest.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.catchThrowable; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; -import org.hyperledger.besu.ethereum.permissioning.AccountLocalConfigPermissioningController; -import org.hyperledger.besu.ethereum.permissioning.AllowlistOperationResult; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@Deprecated -@ExtendWith(MockitoExtension.class) -public class PermRemoveAccountsFromWhitelistTest { - - @Mock private AccountLocalConfigPermissioningController accountWhitelist; - private PermRemoveAccountsFromWhitelist method; - - @BeforeEach - public void before() { - method = new PermRemoveAccountsFromWhitelist(java.util.Optional.of(accountWhitelist)); - } - - @Test - public void getNameShouldReturnExpectedName() { - assertThat(method.getName()).isEqualTo("perm_removeAccountsFromWhitelist"); - } - - @Test - public void whenAccountsAreRemovedFromWhitelistShouldReturnSuccess() { - List accounts = Arrays.asList("0x0", "0x1"); - JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null); - when(accountWhitelist.removeAccounts(eq(accounts))) - .thenReturn(AllowlistOperationResult.SUCCESS); - - JsonRpcResponse actualResponse = method.response(request(accounts)); - - assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); - } - - @Test - public void whenAccountIsInvalidShouldReturnInvalidAccountErrorResponse() { - JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(null, RpcErrorType.ACCOUNT_ALLOWLIST_INVALID_ENTRY); - when(accountWhitelist.removeAccounts(any())) - .thenReturn(AllowlistOperationResult.ERROR_INVALID_ENTRY); - - JsonRpcResponse actualResponse = method.response(request(new ArrayList<>())); - - assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); - } - - @Test - public void whenAccountIsAbsentShouldReturnAbsentAccountErrorResponse() { - JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(null, RpcErrorType.ACCOUNT_ALLOWLIST_ABSENT_ENTRY); - when(accountWhitelist.removeAccounts(any())) - .thenReturn(AllowlistOperationResult.ERROR_ABSENT_ENTRY); - - JsonRpcResponse actualResponse = method.response(request(new ArrayList<>())); - - assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); - } - - @Test - public void whenInputHasDuplicatedAccountsShouldReturnDuplicatedEntryErrorResponse() { - JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(null, RpcErrorType.ACCOUNT_ALLOWLIST_DUPLICATED_ENTRY); - when(accountWhitelist.removeAccounts(any())) - .thenReturn(AllowlistOperationResult.ERROR_DUPLICATED_ENTRY); - - JsonRpcResponse actualResponse = method.response(request(new ArrayList<>())); - - assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); - } - - @Test - public void whenEmptyListOnRequestShouldReturnEmptyEntryErrorResponse() { - JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(null, RpcErrorType.ACCOUNT_ALLOWLIST_EMPTY_ENTRY); - - when(accountWhitelist.removeAccounts(eq(new ArrayList<>()))) - .thenReturn(AllowlistOperationResult.ERROR_EMPTY_ENTRY); - - JsonRpcResponse actualResponse = method.response(request(new ArrayList<>())); - - assertThat(actualResponse).usingRecursiveComparison().isEqualTo(expectedResponse); - } - - @Test - public void whenEmptyParamOnRequestShouldThrowInvalidJsonRpcException() { - JsonRpcRequestContext request = - new JsonRpcRequestContext( - new JsonRpcRequest("2.0", "perm_removeAccountsFromWhitelist", new Object[] {})); - - final Throwable thrown = catchThrowable(() -> method.response(request)); - assertThat(thrown) - .hasNoCause() - .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 0"); - } - - private JsonRpcRequestContext request(final List accounts) { - return new JsonRpcRequestContext( - new JsonRpcRequest("2.0", "perm_removeAccountsFromWhitelist", new Object[] {accounts})); - } -} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermRemoveNodesFromWhitelistTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermRemoveNodesFromWhitelistTest.java deleted file mode 100644 index 45630a25ad1..00000000000 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/methods/permissioning/PermRemoveNodesFromWhitelistTest.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.ethereum.permissioning.NodeLocalConfigPermissioningController.NodesAllowlistResult; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; -import org.hyperledger.besu.ethereum.permissioning.AllowlistOperationResult; -import org.hyperledger.besu.ethereum.permissioning.NodeLocalConfigPermissioningController; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import org.assertj.core.api.Assertions; -import org.assertj.core.util.Lists; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@Deprecated -@ExtendWith(MockitoExtension.class) -public class PermRemoveNodesFromWhitelistTest { - - private PermRemoveNodesFromWhitelist method; - private static final String METHOD_NAME = "perm_removeNodesFromWhitelist"; - - private final String enode1 = - "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.10:4567"; - private final String enode2 = - "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.10:4567"; - private final String enode3 = - "enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.10:4567"; - private final String badEnode = "enod://dog@cat:fish"; - - @Mock private NodeLocalConfigPermissioningController nodeLocalConfigPermissioningController; - - @BeforeEach - public void setUp() { - method = new PermRemoveNodesFromWhitelist(Optional.of(nodeLocalConfigPermissioningController)); - } - - @Test - public void shouldReturnCorrectMethodName() { - assertThat(method.getName()).isEqualTo(METHOD_NAME); - } - - @Test - public void shouldThrowInvalidJsonRpcParametersExceptionWhenBadEnode() { - final JsonRpcRequestContext request = buildRequest(Lists.newArrayList(badEnode)); - final JsonRpcResponse expected = - new JsonRpcErrorResponse( - request.getRequest().getId(), RpcErrorType.NODE_ALLOWLIST_INVALID_ENTRY); - - when(nodeLocalConfigPermissioningController.removeNodes(eq(Lists.newArrayList(badEnode)))) - .thenThrow(IllegalArgumentException.class); - - final JsonRpcResponse actual = method.response(request); - - assertThat(actual).usingRecursiveComparison().isEqualTo(expected); - } - - @Test - public void shouldThrowInvalidJsonRpcParametersExceptionWhenEmptyList() { - final JsonRpcRequestContext request = buildRequest(Collections.emptyList()); - final JsonRpcResponse expected = - new JsonRpcErrorResponse( - request.getRequest().getId(), RpcErrorType.NODE_ALLOWLIST_EMPTY_ENTRY); - - when(nodeLocalConfigPermissioningController.removeNodes(Collections.emptyList())) - .thenReturn(new NodesAllowlistResult(AllowlistOperationResult.ERROR_EMPTY_ENTRY)); - - final JsonRpcResponse actual = method.response(request); - - assertThat(actual).usingRecursiveComparison().isEqualTo(expected); - } - - @Test - public void shouldRemoveSingleValidNode() { - final JsonRpcRequestContext request = buildRequest(Lists.newArrayList(enode1)); - final JsonRpcResponse expected = new JsonRpcSuccessResponse(request.getRequest().getId()); - - when(nodeLocalConfigPermissioningController.removeNodes(any())) - .thenReturn(new NodesAllowlistResult(AllowlistOperationResult.SUCCESS)); - - final JsonRpcResponse actual = method.response(request); - - assertThat(actual).usingRecursiveComparison().isEqualTo(expected); - - verify(nodeLocalConfigPermissioningController, times(1)).removeNodes(any()); - verifyNoMoreInteractions(nodeLocalConfigPermissioningController); - } - - @Test - public void shouldRemoveMultipleValidNodes() { - final JsonRpcRequestContext request = buildRequest(Lists.newArrayList(enode1, enode2, enode3)); - final JsonRpcResponse expected = new JsonRpcSuccessResponse(request.getRequest().getId()); - - when(nodeLocalConfigPermissioningController.removeNodes(any())) - .thenReturn(new NodesAllowlistResult(AllowlistOperationResult.SUCCESS)); - - final JsonRpcResponse actual = method.response(request); - - assertThat(actual).usingRecursiveComparison().isEqualTo(expected); - - verify(nodeLocalConfigPermissioningController, times(1)).removeNodes(any()); - verifyNoMoreInteractions(nodeLocalConfigPermissioningController); - } - - @Test - public void shouldFailWhenP2pDisabled() { - method = new PermRemoveNodesFromWhitelist(Optional.empty()); - final JsonRpcRequestContext request = buildRequest(Lists.newArrayList(enode1, enode2, enode3)); - final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse( - request.getRequest().getId(), RpcErrorType.NODE_ALLOWLIST_NOT_ENABLED); - - Assertions.assertThat(method.response(request)) - .usingRecursiveComparison() - .isEqualTo(expectedResponse); - } - - @Test - public void whenRequestContainsDuplicatedNodesShouldReturnDuplicatedEntryError() { - final JsonRpcRequestContext request = buildRequest(Lists.newArrayList(enode1, enode1)); - final JsonRpcResponse expected = - new JsonRpcErrorResponse( - request.getRequest().getId(), RpcErrorType.NODE_ALLOWLIST_DUPLICATED_ENTRY); - - when(nodeLocalConfigPermissioningController.removeNodes(any())) - .thenReturn(new NodesAllowlistResult(AllowlistOperationResult.ERROR_DUPLICATED_ENTRY)); - - final JsonRpcResponse actual = method.response(request); - - assertThat(actual).usingRecursiveComparison().isEqualTo(expected); - } - - @Test - public void whenRequestContainsEmptyListOfNodesShouldReturnEmptyEntryError() { - final JsonRpcRequestContext request = buildRequest(new ArrayList<>()); - final JsonRpcResponse expected = - new JsonRpcErrorResponse( - request.getRequest().getId(), RpcErrorType.NODE_ALLOWLIST_EMPTY_ENTRY); - - when(nodeLocalConfigPermissioningController.removeNodes(eq(new ArrayList<>()))) - .thenReturn(new NodesAllowlistResult(AllowlistOperationResult.ERROR_EMPTY_ENTRY)); - - final JsonRpcResponse actual = method.response(request); - - assertThat(actual).usingRecursiveComparison().isEqualTo(expected); - } - - @Test - public void shouldReturnCantRemoveBootnodeWhenRemovingBootnode() { - final JsonRpcRequestContext request = buildRequest(Lists.newArrayList(enode1)); - final JsonRpcResponse expected = - new JsonRpcErrorResponse( - request.getRequest().getId(), RpcErrorType.NODE_ALLOWLIST_FIXED_NODE_CANNOT_BE_REMOVED); - - when(nodeLocalConfigPermissioningController.removeNodes(any())) - .thenReturn( - new NodesAllowlistResult(AllowlistOperationResult.ERROR_FIXED_NODE_CANNOT_BE_REMOVED)); - - final JsonRpcResponse actual = method.response(request); - - assertThat(actual).usingRecursiveComparison().isEqualTo(expected); - } - - private JsonRpcRequestContext buildRequest(final List enodeList) { - return new JsonRpcRequestContext( - new JsonRpcRequest("2.0", METHOD_NAME, new Object[] {enodeList})); - } -} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterTest.java index 015a217bc28..9e6ee0d37d7 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/BlockParameterTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/DepositParameterTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/DepositParameterTest.java deleted file mode 100644 index 3d59adbf2c1..00000000000 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/DepositParameterTest.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright contributors to Hyperledger Besu - * - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.DepositParameterTestFixture.DEPOSIT_PARAM_1; - -import org.hyperledger.besu.datatypes.BLSPublicKey; -import org.hyperledger.besu.datatypes.BLSSignature; -import org.hyperledger.besu.datatypes.GWei; -import org.hyperledger.besu.ethereum.core.Deposit; - -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt64; -import org.junit.jupiter.api.Test; - -public class DepositParameterTest { - - @Test - public void toDeposit() { - Deposit expected = - new Deposit( - BLSPublicKey.fromHexString( - "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e"), - Bytes32.fromHexString( - "0x0017a7fcf06faf493d30bbe2632ea7c2383cd86825e12797165de7aa35589483"), - GWei.of(32000000000L), - BLSSignature.fromHexString( - "0xa889db8300194050a2636c92a95bc7160515867614b7971a9500cdb62f9c0890217d2901c3241f86fac029428fc106930606154bd9e406d7588934a5f15b837180b17194d6e44bd6de23e43b163dfe12e369dcc75a3852cd997963f158217eb5"), - UInt64.ONE); - assertThat(DEPOSIT_PARAM_1.toDeposit()).isEqualTo(expected); - } - - @Test - public void fromDeposit() { - Deposit deposit = - new Deposit( - BLSPublicKey.fromHexString( - "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e"), - Bytes32.fromHexString( - "0x0017a7fcf06faf493d30bbe2632ea7c2383cd86825e12797165de7aa35589483"), - GWei.of(32000000000L), - BLSSignature.fromHexString( - "0xa889db8300194050a2636c92a95bc7160515867614b7971a9500cdb62f9c0890217d2901c3241f86fac029428fc106930606154bd9e406d7588934a5f15b837180b17194d6e44bd6de23e43b163dfe12e369dcc75a3852cd997963f158217eb5"), - UInt64.ONE); - - assertThat(DepositParameter.fromDeposit(deposit)).isEqualTo(DEPOSIT_PARAM_1); - } -} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/DepositParameterTestFixture.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/DepositParameterTestFixture.java index ef9ef57b832..f3f5f420bbb 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/DepositParameterTestFixture.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/DepositParameterTestFixture.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,19 +12,18 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters; public class DepositParameterTestFixture { - public static final DepositParameter DEPOSIT_PARAM_1 = + public static final DepositRequestParameter DEPOSIT_PARAM_1 = createDeposit( "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e", "0x0017a7fcf06faf493d30bbe2632ea7c2383cd86825e12797165de7aa35589483", "0x773594000", "0xa889db8300194050a2636c92a95bc7160515867614b7971a9500cdb62f9c0890217d2901c3241f86fac029428fc106930606154bd9e406d7588934a5f15b837180b17194d6e44bd6de23e43b163dfe12e369dcc75a3852cd997963f158217eb5", "0x1"); - static final DepositParameter DEPOSIT_PARAM_2 = + static final DepositRequestParameter DEPOSIT_PARAM_2 = createDeposit( "0x8706d19a62f28a6a6549f96c5adaebac9124a61d44868ec94f6d2d707c6a2f82c9162071231dfeb40e24bfde4ffdf243", "0x006a8dc800c6d8dd6977ef53264e2d030350f0145a91bcd167b4f1c3ea21b271", @@ -32,12 +31,12 @@ public class DepositParameterTestFixture { "0x801b08ca107b623eca32ee9f9111b4e50eb9cfe19e38204b72de7dc04c5a5e00f61bab96f10842576f66020ce851083f1583dd9a6b73301bea6c245cf51f27cf96aeb018852c5f70bf485d16b957cfe49ca008913346b431e7653ae3ddb23b07", "0x3"); - private static DepositParameter createDeposit( + private static DepositRequestParameter createDeposit( final String pubKey, final String withdrawalCredentials, final String amount, final String signature, final String index) { - return new DepositParameter(pubKey, withdrawalCredentials, amount, signature, index); + return new DepositRequestParameter(pubKey, withdrawalCredentials, amount, signature, index); } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/DepositRequestRequestParameterTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/DepositRequestRequestParameterTest.java new file mode 100644 index 00000000000..ce5780641be --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/DepositRequestRequestParameterTest.java @@ -0,0 +1,61 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.DepositParameterTestFixture.DEPOSIT_PARAM_1; + +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.datatypes.BLSSignature; +import org.hyperledger.besu.datatypes.GWei; +import org.hyperledger.besu.ethereum.core.DepositRequest; + +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt64; +import org.junit.jupiter.api.Test; + +public class DepositRequestRequestParameterTest { + + @Test + public void toDeposit() { + DepositRequest expected = + new DepositRequest( + BLSPublicKey.fromHexString( + "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e"), + Bytes32.fromHexString( + "0x0017a7fcf06faf493d30bbe2632ea7c2383cd86825e12797165de7aa35589483"), + GWei.of(32000000000L), + BLSSignature.fromHexString( + "0xa889db8300194050a2636c92a95bc7160515867614b7971a9500cdb62f9c0890217d2901c3241f86fac029428fc106930606154bd9e406d7588934a5f15b837180b17194d6e44bd6de23e43b163dfe12e369dcc75a3852cd997963f158217eb5"), + UInt64.ONE); + assertThat(DEPOSIT_PARAM_1.toDeposit()).isEqualTo(expected); + } + + @Test + public void fromDeposit() { + DepositRequest depositRequest = + new DepositRequest( + BLSPublicKey.fromHexString( + "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e"), + Bytes32.fromHexString( + "0x0017a7fcf06faf493d30bbe2632ea7c2383cd86825e12797165de7aa35589483"), + GWei.of(32000000000L), + BLSSignature.fromHexString( + "0xa889db8300194050a2636c92a95bc7160515867614b7971a9500cdb62f9c0890217d2901c3241f86fac029428fc106930606154bd9e406d7588934a5f15b837180b17194d6e44bd6de23e43b163dfe12e369dcc75a3852cd997963f158217eb5"), + UInt64.ONE); + + assertThat(DepositRequestParameter.fromDeposit(depositRequest)).isEqualTo(DEPOSIT_PARAM_1); + } +} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadAttributesParameterTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadAttributesParameterTest.java index 46f46f3bbd4..bec9bd12143 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadAttributesParameterTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/EnginePayloadAttributesParameterTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters; import static org.assertj.core.api.Assertions.assertThat; diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/JsonCallParameterTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/JsonCallParameterTest.java new file mode 100644 index 00000000000..97d7feeeb13 --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/JsonCallParameterTest.java @@ -0,0 +1,118 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; + +public class JsonCallParameterTest { + + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Test + public void acceptsAndCapMaxValueForGas() throws JsonProcessingException { + final String json = + """ + { + "gas": "0xffffffffffffffff" + } + """; + + final JsonCallParameter callParameter = objectMapper.readValue(json, JsonCallParameter.class); + + assertThat(callParameter.getGasLimit()).isEqualTo(Long.MAX_VALUE); + } + + @Test + public void dataAsPayLoad() throws JsonProcessingException { + final String json = + """ + { + "data": "0x1234" + } + """; + + final JsonCallParameter callParameter = objectMapper.readValue(json, JsonCallParameter.class); + + assertThat(callParameter.getPayload()).isEqualTo(Bytes.fromHexString("0x1234")); + } + + @Test + public void inputAsPayLoad() throws JsonProcessingException { + final String json = + """ + { + "input": "0x1234" + } + """; + + final JsonCallParameter callParameter = objectMapper.readValue(json, JsonCallParameter.class); + + assertThat(callParameter.getPayload()).isEqualTo(Bytes.fromHexString("0x1234")); + } + + @Test + public void inputAndDataWithSameValueAsPayLoad() throws JsonProcessingException { + final String json = + """ + { + "input": "0x1234", + "data": "0x1234" + } + """; + + final JsonCallParameter callParameter = objectMapper.readValue(json, JsonCallParameter.class); + + assertThat(callParameter.getPayload()).isEqualTo(Bytes.fromHexString("0x1234")); + } + + @Test + public void inputAndDataWithDifferentValueAsPayLoadCauseException() { + final String json = + """ + { + "input": "0x1234", + "data": "0x1235" + } + """; + + assertThatExceptionOfType(JsonMappingException.class) + .isThrownBy(() -> objectMapper.readValue(json, JsonCallParameter.class)) + .withMessageContaining("problem: Only one of 'input' or 'data' should be provided"); + } + + @Test + public void extraParametersAreIgnored() throws JsonProcessingException { + // 0x96 = 150 + final String json = + """ + { + "gas": "0x96", + "gasLimit": "0xfa", + "extraField": "extra" + } + """; + + final JsonCallParameter callParameter = objectMapper.readValue(json, JsonCallParameter.class); + + assertThat(callParameter.getGasLimit()).isEqualTo(150); + } +} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/WithdrawalParameterTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/WithdrawalParameterTest.java index 80aa67fdea7..3a0e10f15e9 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/WithdrawalParameterTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/WithdrawalParameterTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters; import static org.assertj.core.api.Assertions.assertThat; diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/WithdrawalParameterTestFixture.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/WithdrawalParameterTestFixture.java index 189b09ff8a5..bd51db6226a 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/WithdrawalParameterTestFixture.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/WithdrawalParameterTestFixture.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters; import org.hyperledger.besu.datatypes.GWei; diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/WithdrawalRequestParameterTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/WithdrawalRequestParameterTest.java new file mode 100644 index 00000000000..e3e136cb7a4 --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/WithdrawalRequestParameterTest.java @@ -0,0 +1,52 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.WithdrawalRequestTestFixture.WITHDRAWAL_REQUEST_PARAMETER_1; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.datatypes.GWei; +import org.hyperledger.besu.ethereum.core.WithdrawalRequest; + +import org.junit.jupiter.api.Test; + +public class WithdrawalRequestParameterTest { + + @Test + public void toWithdrawalRequest() { + WithdrawalRequest expected = + new WithdrawalRequest( + Address.fromHexString("0x814FaE9f487206471B6B0D713cD51a2D35980000"), + BLSPublicKey.fromHexString( + "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e"), + GWei.ONE); + assertThat(WITHDRAWAL_REQUEST_PARAMETER_1.toWithdrawalRequest()).isEqualTo(expected); + } + + @Test + public void fromWithdrawalRequest() { + WithdrawalRequest withdrawalRequest = + new WithdrawalRequest( + Address.fromHexString("0x814FaE9f487206471B6B0D713cD51a2D35980000"), + BLSPublicKey.fromHexString( + "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e"), + GWei.ONE); + + assertThat(WithdrawalRequestParameter.fromWithdrawalRequest(withdrawalRequest)) + .isEqualTo(WITHDRAWAL_REQUEST_PARAMETER_1); + } +} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/WithdrawalRequestTestFixture.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/WithdrawalRequestTestFixture.java new file mode 100644 index 00000000000..2256cc31956 --- /dev/null +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/WithdrawalRequestTestFixture.java @@ -0,0 +1,31 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters; + +import org.hyperledger.besu.datatypes.GWei; + +public class WithdrawalRequestTestFixture { + + public static final WithdrawalRequestParameter WITHDRAWAL_REQUEST_PARAMETER_1 = + new WithdrawalRequestParameter( + "0x814fae9f487206471b6b0d713cd51a2d35980000", + "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e", + GWei.ONE.toShortHexString()); + static final WithdrawalRequestParameter WITHDRAWAL_REQUEST_PARAMETER_2 = + new WithdrawalRequestParameter( + "0x758b8178a9a4b7206d1f648c4a77c515cbac7000", + "0x8706d19a62f28a6a6549f96c5adaebac9124a61d44868ec94f6d2d707c6a2f82c9162071231dfeb40e24bfde4ffdf243", + GWei.ONE.toShortHexString()); +} diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/MultiTenancyRpcMethodDecoratorTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/MultiTenancyRpcMethodDecoratorTest.java index 8803540392c..d7979dccc83 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/MultiTenancyRpcMethodDecoratorTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/MultiTenancyRpcMethodDecoratorTest.java @@ -22,10 +22,10 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponseType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcUnauthorizedResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; import io.vertx.core.json.JsonObject; import io.vertx.ext.auth.User; @@ -58,7 +58,7 @@ public void delegatesWhenHasValidToken() { assertThat(tokenRpcDecorator.getName()).isEqualTo("delegate"); final JsonRpcResponse response = tokenRpcDecorator.response(rpcRequestContext); - assertThat(response.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); + assertThat(response.getType()).isEqualTo(RpcResponseType.SUCCESS); final JsonRpcSuccessResponse successResponse = (JsonRpcSuccessResponse) response; assertThat(successResponse.getResult()).isEqualTo("b"); } @@ -73,7 +73,7 @@ public void failsWhenHasNoToken() { assertThat(tokenRpcDecorator.getName()).isEqualTo("delegate"); final JsonRpcResponse response = tokenRpcDecorator.response(rpcRequestContext); - assertThat(response.getType()).isEqualTo(JsonRpcResponseType.UNAUTHORIZED); + assertThat(response.getType()).isEqualTo(RpcResponseType.UNAUTHORIZED); final JsonRpcUnauthorizedResponse errorResponse = (JsonRpcUnauthorizedResponse) response; assertThat(errorResponse.getErrorType()).isEqualTo(RpcErrorType.UNAUTHORIZED); } @@ -89,7 +89,7 @@ public void failsWhenTokenDoesNotHavePrivacyPublicKey() { assertThat(tokenRpcDecorator.getName()).isEqualTo("delegate"); final JsonRpcResponse response = tokenRpcDecorator.response(rpcRequestContext); - assertThat(response.getType()).isEqualTo(JsonRpcResponseType.ERROR); + assertThat(response.getType()).isEqualTo(RpcResponseType.ERROR); final JsonRpcErrorResponse errorResponse = (JsonRpcErrorResponse) response; assertThat(errorResponse.getErrorType()).isEqualTo(RpcErrorType.INVALID_REQUEST); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransactionTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransactionTest.java index 4d209b847ba..b44100ba6c3 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransactionTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/eea/EeaSendRawTransactionTest.java @@ -78,7 +78,7 @@ public void requestIsMissingParameter() { assertThatThrownBy(() -> method.response(request)) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 0"); + .hasMessage("Invalid transaction parameter (index 0)"); } @Test @@ -88,7 +88,7 @@ public void requestHasNullObjectParameter() { assertThatThrownBy(() -> method.response(request)) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 0"); + .hasMessage("Invalid transaction parameter (index 0)"); } @Test @@ -99,7 +99,7 @@ public void requestHasNullArrayParameter() { assertThatThrownBy(() -> method.response(request)) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 0"); + .hasMessage("Invalid transaction parameter (index 0)"); } @Test diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCallTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCallTest.java index 3542ff5100a..cc5169d75d5 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCallTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCallTest.java @@ -75,18 +75,13 @@ public void shouldReturnCorrectMethodName() { @Test public void shouldThrowInvalidJsonRpcParametersExceptionWhenMissingToField() { final JsonCallParameter callParameter = - new JsonCallParameter( - Address.fromHexString("0x0"), - null, - 0L, - Wei.ZERO, - null, - null, - Wei.ZERO, - Bytes.EMPTY, - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0x0")) + .withGas(0L) + .withGasPrice(Wei.ZERO) + .withValue(Wei.ZERO) + .withInput(Bytes.EMPTY) + .build(); final JsonRpcRequestContext request = ethCallRequest(privacyGroupId, callParameter, "latest"); final Throwable thrown = catchThrowable(() -> method.response(request)); @@ -111,18 +106,9 @@ public void shouldReturnNullWhenProcessorReturnsEmpty() { @Test public void shouldAcceptRequestWhenMissingOptionalFields() { final JsonCallParameter callParameter = - new JsonCallParameter( - null, - Address.fromHexString("0x0"), - null, - null, - null, - null, - null, - null, - null, - null, - null); + new JsonCallParameter.JsonCallParameterBuilder() + .withTo(Address.fromHexString("0x0")) + .build(); final JsonRpcRequestContext request = ethCallRequest(privacyGroupId, callParameter, "latest"); final JsonRpcResponse expectedResponse = new JsonRpcSuccessResponse(null, Bytes.of().toString()); @@ -187,23 +173,18 @@ public void shouldThrowCorrectExceptionWhenNoPrivacyGroupSpecified() { assertThat(thrown) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasNoCause() - .hasMessage("Missing required json rpc parameter at index 0"); + .hasMessage("Invalid privacy group ID parameter (index 0)"); } private JsonCallParameter callParameter() { - return new JsonCallParameter( - Address.fromHexString("0x0"), - Address.fromHexString("0x0"), - 0L, - Wei.ZERO, - null, - null, - Wei.ZERO, - Bytes.EMPTY, - null, - null, - null); + return new JsonCallParameter.JsonCallParameterBuilder() + .withFrom(Address.fromHexString("0x0")) + .withTo(Address.fromHexString("0x0")) + .withGas(0L) + .withGasPrice(Wei.ZERO) + .withValue(Wei.ZERO) + .withInput(Bytes.EMPTY) + .build(); } private JsonRpcRequestContext ethCallRequest( diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCreatePrivacyGroupTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCreatePrivacyGroupTest.java index 9be3122c1e4..771c287ac3b 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCreatePrivacyGroupTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivCreatePrivacyGroupTest.java @@ -231,7 +231,7 @@ public String getDescription() { catchThrowableOfType( () -> privCreatePrivacyGroup.response(request), InvalidJsonRpcParameters.class); - assertThat(response.getMessage()).contains("Invalid json rpc parameter at index 0"); + assertThat(response.getMessage()).contains("Invalid create privacy group parameter (index 0)"); } @Test @@ -249,7 +249,7 @@ public void returnsCorrectExceptionMissingParam() { catchThrowableOfType( () -> privCreatePrivacyGroup.response(request), InvalidJsonRpcParameters.class); - assertThat(response.getMessage()).isEqualTo("Missing required json rpc parameter at index 0"); + assertThat(response.getMessage()).isEqualTo("Invalid create privacy group parameter (index 0)"); } @Test diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDebugGetStateRootTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDebugGetStateRootTest.java index 7c2b6162665..c730be1db85 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDebugGetStateRootTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivDebugGetStateRootTest.java @@ -33,11 +33,11 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.PrivacyIdProvider; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponseType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.privacy.PrivacyController; import org.hyperledger.besu.ethereum.privacy.RestrictedDefaultPrivacyController; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; import java.util.Collections; import java.util.Optional; @@ -82,8 +82,7 @@ public void shouldThrowInvalidJsonRpcParametersExceptionWhenNoPrivacyGroup() { assertThat(thrown) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasNoCause() - .hasMessage("Missing required json rpc parameter at index 0"); + .hasMessage("Invalid privacy group ID parameter (index 0)"); } @Test @@ -91,7 +90,7 @@ public void shouldReturnErrorIfInvalidGroupId() { when(privacyController.findPrivacyGroupByGroupId(anyString(), anyString())) .thenReturn(Optional.empty()); final JsonRpcResponse response = method.response(request("not_base64", "latest")); - assertThat(response.getType()).isEqualByComparingTo(JsonRpcResponseType.ERROR); + assertThat(response.getType()).isEqualByComparingTo(RpcResponseType.ERROR); assertThat(((JsonRpcErrorResponse) response).getError().getMessage()) .contains(FIND_PRIVACY_GROUP_ERROR.getMessage()); } @@ -102,7 +101,7 @@ public void shouldReturnErrorIfPrivacyGroupDoesNotExist() { .thenReturn(Optional.empty()); final String invalidGroupId = Base64.toBase64String("invalid_group_id".getBytes(UTF_8)); final JsonRpcResponse response = method.response(request(invalidGroupId, "latest")); - assertThat(response.getType()).isEqualByComparingTo(JsonRpcResponseType.ERROR); + assertThat(response.getType()).isEqualByComparingTo(RpcResponseType.ERROR); assertThat(((JsonRpcErrorResponse) response).getError().getMessage()) .contains(FIND_PRIVACY_GROUP_ERROR.getMessage()); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetFilterChangesTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetFilterChangesTest.java index 5d4d381c1ef..3b9f5c81314 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetFilterChangesTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetFilterChangesTest.java @@ -81,7 +81,7 @@ public void privacyGroupIdIsRequired() { assertThatThrownBy(() -> method.response(request)) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Missing required json rpc parameter at index 0"); + .hasMessageContaining("Invalid privacy group ID parameter (index 0)"); } @Test @@ -107,7 +107,7 @@ public void filterIdIsRequired() { assertThatThrownBy(() -> method.response(request)) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Missing required json rpc parameter at index 1"); + .hasMessageContaining("Invalid filter ID parameter (index 1)"); } @Test diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetFilterLogsTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetFilterLogsTest.java index 1bd983ffd74..c055988271f 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetFilterLogsTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetFilterLogsTest.java @@ -75,7 +75,7 @@ public void privacyGroupIdIsRequired() { assertThatThrownBy(() -> method.response(request)) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Missing required json rpc parameter at index 0"); + .hasMessageContaining("Invalid privacy group ID parameter (index 0)"); } @Test @@ -84,7 +84,7 @@ public void filterIdIsRequired() { assertThatThrownBy(() -> method.response(request)) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Missing required json rpc parameter at index 1"); + .hasMessageContaining("Invalid filter ID parameter (index 1)"); } @Test diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetLogsTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetLogsTest.java index 09eb8b00895..9426f214786 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetLogsTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivGetLogsTest.java @@ -87,7 +87,7 @@ public void privacyGroupIdIsRequired() { assertThatThrownBy(() -> method.response(request)) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Missing required json rpc parameter at index 0"); + .hasMessageContaining("Invalid privacy group ID parameter (index 0)"); } @Test @@ -96,7 +96,7 @@ public void filterParameterIsRequired() { assertThatThrownBy(() -> method.response(request)) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Missing required json rpc parameter at index 1"); + .hasMessageContaining("Invalid filter parameter (index 1)"); } @Test @@ -116,7 +116,7 @@ public void filterWithInvalidParameters() { final JsonRpcRequestContext request = privGetLogRequest(PRIVACY_GROUP_ID, invalidFilter); final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(null, RpcErrorType.INVALID_PARAMS); + new JsonRpcErrorResponse(null, RpcErrorType.INVALID_FILTER_PARAMS); final JsonRpcResponse response = method.response(request); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivNewFilterTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivNewFilterTest.java index f9e9795f1cd..855da64b011 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivNewFilterTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivNewFilterTest.java @@ -78,7 +78,7 @@ public void privacyGroupIdIsRequired() { assertThatThrownBy(() -> method.response(request)) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Missing required json rpc parameter at index 0"); + .hasMessageContaining("Invalid privacy group ID parameter (index 0)"); } @Test @@ -87,7 +87,7 @@ public void filterParameterIsRequired() { assertThatThrownBy(() -> method.response(request)) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Missing required json rpc parameter at index 1"); + .hasMessageContaining("Invalid filter parameter (index 1)"); } @Test @@ -107,7 +107,7 @@ public void filterWithInvalidParameters() { final JsonRpcRequestContext request = privNewFilterRequest(PRIVACY_GROUP_ID, invalidFilter); final JsonRpcResponse expectedResponse = - new JsonRpcErrorResponse(null, RpcErrorType.INVALID_PARAMS); + new JsonRpcErrorResponse(null, RpcErrorType.INVALID_FILTER_PARAMS); final JsonRpcResponse response = method.response(request); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivUninstallFilterTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivUninstallFilterTest.java index a0e28aab163..74935692e8d 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivUninstallFilterTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/privacy/methods/priv/PrivUninstallFilterTest.java @@ -61,7 +61,7 @@ public void privacyGroupIdIsRequired() { assertThatThrownBy(() -> method.response(request)) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Missing required json rpc parameter at index 0"); + .hasMessageContaining("Invalid privacy group ID parameter (index 0)"); } @Test @@ -70,7 +70,7 @@ public void filterIdIsRequired() { assertThatThrownBy(() -> method.response(request)) .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Missing required json rpc parameter at index 1"); + .hasMessageContaining("Invalid filter ID parameter (index 1)"); } @Test diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracerTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracerTest.java index 70c6812c5cd..6626a66bb09 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracerTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/processor/TransactionTracerTest.java @@ -25,6 +25,7 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.ImmutableTransactionTraceParams; import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.Blockchain; @@ -66,9 +67,10 @@ @MockitoSettings(strictness = Strictness.LENIENT) public class TransactionTracerTest { - @TempDir private static Path traceDir; + @TempDir private Path traceDir; @Mock private ProtocolSchedule protocolSchedule; + @Mock private ProtocolContext protocolContext; @Mock private Blockchain blockchain; @Mock private BlockHeader blockHeader; @@ -91,6 +93,7 @@ public class TransactionTracerTest { @Mock private MainnetTransactionProcessor transactionProcessor; private TransactionTracer transactionTracer; + private final BadBlockManager badBlockManager = new BadBlockManager(); private final Hash transactionHash = Hash.fromHexString("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); @@ -107,7 +110,8 @@ public class TransactionTracerTest { @BeforeEach public void setUp() throws Exception { - transactionTracer = new TransactionTracer(new BlockReplay(protocolSchedule, blockchain)); + transactionTracer = + new TransactionTracer(new BlockReplay(protocolSchedule, protocolContext, blockchain)); when(transaction.getHash()).thenReturn(transactionHash); when(otherTransaction.getHash()).thenReturn(otherTransactionHash); when(blockHeader.getNumber()).thenReturn(12L); @@ -118,8 +122,8 @@ public void setUp() throws Exception { when(protocolSpec.getMiningBeneficiaryCalculator()).thenReturn(BlockHeader::getCoinbase); when(protocolSpec.getFeeMarket()).thenReturn(FeeMarket.london(0L)); when(blockchain.getChainHeadHeader()).thenReturn(blockHeader); - when(protocolSpec.getBadBlocksManager()).thenReturn(new BadBlockManager()); when(protocolSpec.getGasCalculator()).thenReturn(gasCalculator); + when(protocolContext.getBadBlockManager()).thenReturn(badBlockManager); lenient().when(gasCalculator.computeExcessBlobGas(anyLong(), anyInt())).thenReturn(0L); } @@ -176,7 +180,6 @@ public void traceTransactionShouldReturnResultFromProcessTransaction() { final WorldUpdater updater = mock(WorldUpdater.class); when(mutableWorldState.updater()).thenReturn(updater); when(transactionProcessor.processTransaction( - eq(blockchain), eq(updater), eq(blockHeader), eq(transaction), @@ -265,7 +268,6 @@ public void traceTransactionToFileShouldReturnResultFromProcessTransaction() thr final WorldUpdater stackedUpdater = mock(StackedUpdater.class); when(updater.updater()).thenReturn(stackedUpdater); when(transactionProcessor.processTransaction( - eq(blockchain), eq(stackedUpdater), eq(blockHeader), eq(transaction), diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/MutableJsonRpcSuccessResponse.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/MutableJsonRpcSuccessResponse.java index fc51f5209e4..a9b9376dc37 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/MutableJsonRpcSuccessResponse.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/response/MutableJsonRpcSuccessResponse.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.internal.response; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; + import java.util.Objects; import com.fasterxml.jackson.annotation.JsonGetter; @@ -74,8 +76,8 @@ public void setVersion(final Object version) { } @JsonIgnore - public JsonRpcResponseType getType() { - return JsonRpcResponseType.SUCCESS; + public RpcResponseType getType() { + return RpcResponseType.SUCCESS; } @Override diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlobsBundleV1Test.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlobsBundleV1Test.java index f38a3265046..86db821262e 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlobsBundleV1Test.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/BlobsBundleV1Test.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results; import static org.assertj.core.api.Assertions.assertThat; diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/NetworkResultTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/NetworkResultTest.java index 876e795468f..63f71c435fd 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/NetworkResultTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/NetworkResultTest.java @@ -26,9 +26,22 @@ public class NetworkResultTest { @Test public void localAndRemoteAddressShouldNotStartWithForwardSlash() { final SocketAddress socketAddress = new InetSocketAddress("1.2.3.4", 7890); - final NetworkResult networkResult = new NetworkResult(socketAddress, socketAddress); + final NetworkResult networkResult = new NetworkResult(socketAddress, socketAddress, true); assertThat(networkResult.getLocalAddress()).isEqualTo("1.2.3.4:7890"); assertThat(networkResult.getRemoteAddress()).isEqualTo("1.2.3.4:7890"); + assertThat(networkResult.isInbound()).isTrue(); + } + + @Test + public void inboundFieldShouldReflectConnectionDirection() { + final SocketAddress localAddress = new InetSocketAddress("192.168.0.1", 30303); + final SocketAddress remoteAddress = new InetSocketAddress("10.0.0.1", 30303); + + final NetworkResult inboundConnection = new NetworkResult(localAddress, remoteAddress, true); + assertThat(inboundConnection.isInbound()).isTrue(); + + final NetworkResult outboundConnection = new NetworkResult(localAddress, remoteAddress, false); + assertThat(outboundConnection.isInbound()).isFalse(); } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/transaction/pool/PendingPermissionTransactionFilterTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/transaction/pool/PendingPermissionTransactionFilterTest.java index b08e5f7e8d2..ada560adb02 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/transaction/pool/PendingPermissionTransactionFilterTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/transaction/pool/PendingPermissionTransactionFilterTest.java @@ -39,6 +39,7 @@ import java.util.Optional; import java.util.Set; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -126,4 +127,11 @@ private Set getPendingTransactions() { } return new LinkedHashSet<>(pendingTransactionList); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PermJsonRpcMethodsTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PermJsonRpcMethodsTest.java index a8f06f52650..7484730a308 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PermJsonRpcMethodsTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PermJsonRpcMethodsTest.java @@ -16,30 +16,19 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod.PERM_ADD_ACCOUNTS_TO_ALLOWLIST; -import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod.PERM_ADD_ACCOUNTS_TO_WHITELIST; import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod.PERM_ADD_NODES_TO_ALLOWLIST; -import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod.PERM_ADD_NODES_TO_WHITELIST; import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod.PERM_GET_ACCOUNTS_ALLOWLIST; -import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod.PERM_GET_ACCOUNTS_WHITELIST; import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod.PERM_GET_NODES_ALLOWLIST; -import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod.PERM_GET_NODES_WHITELIST; import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod.PERM_REMOVE_ACCOUNTS_FROM_ALLOWLIST; -import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod.PERM_REMOVE_ACCOUNTS_FROM_WHITELIST; import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod.PERM_REMOVE_NODES_FROM_ALLOWLIST; -import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod.PERM_REMOVE_NODES_FROM_WHITELIST; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning.PermAddAccountsToAllowlist; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning.PermAddAccountsToWhitelist; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning.PermAddNodesToAllowlist; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning.PermGetAccountsAllowlist; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning.PermGetAccountsWhitelist; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning.PermGetNodesAllowlist; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning.PermGetNodesWhitelist; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning.PermRemoveAccountsFromAllowlist; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning.PermRemoveAccountsFromWhitelist; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning.PermRemoveNodesFromAllowlist; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.permissioning.PermRemoveNodesFromWhitelist; import org.hyperledger.besu.ethereum.permissioning.AccountLocalConfigPermissioningController; import org.hyperledger.besu.ethereum.permissioning.NodeLocalConfigPermissioningController; @@ -88,27 +77,4 @@ public void allowlistMethodsPresent() { assertThat(rpcMethods.get(PERM_REMOVE_NODES_FROM_ALLOWLIST.getMethodName())) .isInstanceOf(PermRemoveNodesFromAllowlist.class); } - - @Deprecated - @Test - public void whitelistMethodsPresent() { - final Map rpcMethods = permJsonRpcMethods.create(); - assertThat(rpcMethods.size()).isEqualTo(13); - - // Account methods x 3 - assertThat(rpcMethods.get(PERM_GET_ACCOUNTS_WHITELIST.getMethodName())) - .isInstanceOf(PermGetAccountsWhitelist.class); - assertThat(rpcMethods.get(PERM_ADD_ACCOUNTS_TO_WHITELIST.getMethodName())) - .isInstanceOf(PermAddAccountsToWhitelist.class); - assertThat(rpcMethods.get(PERM_REMOVE_ACCOUNTS_FROM_WHITELIST.getMethodName())) - .isInstanceOf(PermRemoveAccountsFromWhitelist.class); - - // Node methods x 3 - assertThat(rpcMethods.get(PERM_GET_NODES_WHITELIST.getMethodName())) - .isInstanceOf(PermGetNodesWhitelist.class); - assertThat(rpcMethods.get(PERM_ADD_NODES_TO_WHITELIST.getMethodName())) - .isInstanceOf(PermAddNodesToAllowlist.class); - assertThat(rpcMethods.get(PERM_REMOVE_NODES_FROM_WHITELIST.getMethodName())) - .isInstanceOf(PermRemoveNodesFromWhitelist.class); - } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivJsonRpcMethodsTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivJsonRpcMethodsTest.java index 249b2e7077e..b9024db9984 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivJsonRpcMethodsTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivJsonRpcMethodsTest.java @@ -44,7 +44,6 @@ public class PrivJsonRpcMethodsTest { @Mock private TransactionPool transactionPool; @Mock private PrivacyParameters privacyParameters; @Mock private FilterManager filterManager; - private PrivJsonRpcMethods privJsonRpcMethods; @BeforeEach diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethodsTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethodsTest.java index ab7b90735b5..52a875a4cbd 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethodsTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/methods/PrivacyApiGroupJsonRpcMethodsTest.java @@ -27,7 +27,6 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.privacy.methods.PrivacyIdProvider; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponseType; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -35,6 +34,7 @@ import org.hyperledger.besu.ethereum.privacy.MultiTenancyPrivacyController; import org.hyperledger.besu.ethereum.privacy.PrivacyController; import org.hyperledger.besu.plugin.services.privacy.PrivateMarkerTransactionFactory; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; import java.util.Map; import java.util.Optional; @@ -140,7 +140,7 @@ public void rpcMethodsCreatedWhenPrivacyIsNotEnabledAreDisabled() { final JsonRpcRequestContext request = new JsonRpcRequestContext(new JsonRpcRequest("2.0", "priv_method", null)); final JsonRpcResponse response = privMethod.response(request); - assertThat(response.getType()).isEqualTo(JsonRpcResponseType.ERROR); + assertThat(response.getType()).isEqualTo(RpcResponseType.ERROR); JsonRpcErrorResponse errorResponse = (JsonRpcErrorResponse) response; assertThat(errorResponse.getErrorType()).isEqualTo(PRIVACY_NOT_ENABLED); diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/parameters/TraceCallManyParameterTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/parameters/TraceCallManyParameterTest.java index 4e236a12c1d..d114aec369d 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/parameters/TraceCallManyParameterTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/parameters/TraceCallManyParameterTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/timeout/TimeoutHandlerTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/timeout/TimeoutHandlerTest.java index c8047b806c7..2fd96d6b98c 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/timeout/TimeoutHandlerTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/timeout/TimeoutHandlerTest.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.ethereum.api.jsonrpc.timeout; +import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod.ETH_BLOCK_NUMBER; import static org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod.ETH_GET_LOGS; import static org.mockito.ArgumentMatchers.any; @@ -39,6 +40,7 @@ import io.vertx.core.Vertx; import io.vertx.core.json.JsonObject; import io.vertx.ext.web.RoutingContext; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mockito; @@ -86,4 +88,11 @@ public void test( .setTimer(eq(TimeUnit.SECONDS.toMillis(timeoutSec)), any()); verify(ctx, times(timerMustBeSet ? 1 : 0)).addBodyEndHandler(any()); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/JsonResponseStreamerTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/JsonResponseStreamerTest.java index 4ad036c2952..bbeed01481e 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/JsonResponseStreamerTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/JsonResponseStreamerTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/JsonRpcJWTTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/JsonRpcJWTTest.java index f44b490b82f..55913f18a0d 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/JsonRpcJWTTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/JsonRpcJWTTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketHostAllowlistTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketHostAllowlistTest.java index 879788b26a1..3ac348c01a1 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketHostAllowlistTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketHostAllowlistTest.java @@ -41,12 +41,15 @@ import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpClientOptions; import io.vertx.core.http.HttpMethod; +import io.vertx.core.net.HostAndPort; import io.vertx.junit5.VertxExtension; import io.vertx.junit5.VertxTestContext; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; @ExtendWith(VertxExtension.class) public class WebSocketHostAllowlistTest { @@ -104,49 +107,60 @@ public void after() { @Test public void websocketRequestWithDefaultHeaderAndDefaultConfigIsAccepted() { - boolean result = websocketService.hasAllowedHostnameHeader(Optional.of("localhost:50012")); + final Optional host = + Optional.of(HostAndPort.parseAuthority("localhost", 50012)).map(HostAndPort::host); + boolean result = websocketService.checkHostInAllowlist(host); assertThat(result).isTrue(); } @Test - public void httpRequestWithDefaultHeaderAndDefaultConfigIsAccepted() throws InterruptedException { + public void httpRequestWithDefaultHeaderAndDefaultConfigIsAccepted() throws Throwable { doHttpRequestAndVerify(testContext, "localhost:50012", 400); } @Test public void websocketRequestWithEmptyHeaderAndDefaultConfigIsRejected() { - assertThat(websocketService.hasAllowedHostnameHeader(Optional.of(""))).isFalse(); + final Optional host = + Optional.ofNullable(HostAndPort.parseAuthority("", 50012)).map(HostAndPort::host); + boolean result = websocketService.checkHostInAllowlist(host); + assertThat(result).isFalse(); } @Test - public void httpRequestWithEmptyHeaderAndDefaultConfigIsRejected() throws InterruptedException { + public void httpRequestWithEmptyHeaderAndDefaultConfigIsRejected() throws Throwable { doHttpRequestAndVerify(testContext, "", 403); } - @Test - public void websocketRequestWithAnyHostnameAndWildcardConfigIsAccepted() { + @ParameterizedTest + @ValueSource(strings = {"ally", "foe"}) + public void websocketRequestWithAnyHostnameAndWildcardConfigIsAccepted(final String hostname) { webSocketConfiguration.setHostsAllowlist(Collections.singletonList("*")); - assertThat(websocketService.hasAllowedHostnameHeader(Optional.of("ally"))).isTrue(); - assertThat(websocketService.hasAllowedHostnameHeader(Optional.of("foe"))).isTrue(); + + final Optional host = + Optional.ofNullable(HostAndPort.parseAuthority(hostname, -1)).map(HostAndPort::host); + boolean result = websocketService.checkHostInAllowlist(host); + assertThat(result).isTrue(); } @Test - public void httpRequestWithAnyHostnameAndWildcardConfigIsAccepted() throws InterruptedException { + public void httpRequestWithAnyHostnameAndWildcardConfigIsAccepted() throws Throwable { webSocketConfiguration.setHostsAllowlist(Collections.singletonList("*")); doHttpRequestAndVerify(testContext, "ally", 400); doHttpRequestAndVerify(testContext, "foe", 400); } - @Test - public void websocketRequestWithAllowedHostIsAccepted() { + @ParameterizedTest + @ValueSource(strings = {"ally", "ally:12345", "friend"}) + public void websocketRequestWithAllowedHostIsAccepted(final String hostname) { webSocketConfiguration.setHostsAllowlist(hostsAllowlist); - assertThat(websocketService.hasAllowedHostnameHeader(Optional.of("ally"))).isTrue(); - assertThat(websocketService.hasAllowedHostnameHeader(Optional.of("ally:12345"))).isTrue(); - assertThat(websocketService.hasAllowedHostnameHeader(Optional.of("friend"))).isTrue(); + final Optional host = + Optional.ofNullable(HostAndPort.parseAuthority(hostname, -1)).map(HostAndPort::host); + boolean result = websocketService.checkHostInAllowlist(host); + assertThat(result).isTrue(); } @Test - public void httpRequestWithAllowedHostIsAccepted() throws InterruptedException { + public void httpRequestWithAllowedHostIsAccepted() throws Throwable { webSocketConfiguration.setHostsAllowlist(hostsAllowlist); doHttpRequestAndVerify(testContext, "ally", 400); doHttpRequestAndVerify(testContext, "ally:12345", 400); @@ -156,37 +170,40 @@ public void httpRequestWithAllowedHostIsAccepted() throws InterruptedException { @Test public void websocketRequestWithUnknownHostIsRejected() { webSocketConfiguration.setHostsAllowlist(hostsAllowlist); - assertThat(websocketService.hasAllowedHostnameHeader(Optional.of("foe"))).isFalse(); + final Optional host = + Optional.ofNullable(HostAndPort.parseAuthority("foe", -1)).map(HostAndPort::host); + assertThat(websocketService.checkHostInAllowlist(host)).isFalse(); } @Test - public void httpRequestWithUnknownHostIsRejected() throws InterruptedException { + public void httpRequestWithUnknownHostIsRejected() throws Throwable { webSocketConfiguration.setHostsAllowlist(hostsAllowlist); doHttpRequestAndVerify(testContext, "foe", 403); } - @Test - public void websocketRequestWithMalformedHostIsRejected() { + @ParameterizedTest + @ValueSource(strings = {"ally:friend", "ally:123456", "ally:friend:1234"}) + public void websocketRequestWithMalformedHostIsRejected(final String hostname) { webSocketConfiguration.setAuthenticationEnabled(false); webSocketConfiguration.setHostsAllowlist(hostsAllowlist); - assertThat(websocketService.hasAllowedHostnameHeader(Optional.of("ally:friend"))).isFalse(); - assertThat(websocketService.hasAllowedHostnameHeader(Optional.of("ally:123456"))).isFalse(); - assertThat(websocketService.hasAllowedHostnameHeader(Optional.of("ally:friend:1234"))) - .isFalse(); + final Optional host = + Optional.ofNullable(HostAndPort.parseAuthority(hostname, -1)).map(HostAndPort::host); + final boolean result = websocketService.checkHostInAllowlist(host); + assertThat(result).isFalse(); } @Test - public void httpRequestWithMalformedHostIsRejected() throws InterruptedException { + public void httpRequestWithMalformedHostIsRejected() throws Throwable { webSocketConfiguration.setAuthenticationEnabled(false); webSocketConfiguration.setHostsAllowlist(hostsAllowlist); doHttpRequestAndVerify(testContext, "ally:friend", 400); - doHttpRequestAndVerify(testContext, "ally:123456", 403); - doHttpRequestAndVerify(testContext, "ally:friend:1234", 403); + doHttpRequestAndVerify(testContext, "ally:123456", 400); + doHttpRequestAndVerify(testContext, "ally:friend:1234", 400); } private void doHttpRequestAndVerify( final VertxTestContext testContext, final String hostname, final int expectedResponse) - throws InterruptedException { + throws Throwable { httpClient.request( HttpMethod.POST, @@ -200,10 +217,18 @@ private void doHttpRequestAndVerify( .result() .send( response -> { - assertThat(response.result().statusCode()).isEqualTo(expectedResponse); - testContext.completeNow(); + if (response.succeeded()) { + assertThat(response.result().statusCode()).isEqualTo(expectedResponse); + testContext.completeNow(); + } else { + testContext.failNow(response.cause()); + } }); }); - testContext.awaitCompletion(VERTX_AWAIT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + assertThat(testContext.awaitCompletion(VERTX_AWAIT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) + .isTrue(); + if (testContext.failed()) { + throw testContext.causeOfFailure(); + } } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketServiceLoginTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketServiceLoginTest.java index acf500fa8dd..47a16135ffd 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketServiceLoginTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/WebSocketServiceLoginTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -26,6 +26,7 @@ import org.hyperledger.besu.config.StubGenesisConfigOptions; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.ApiConfiguration; +import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration; import org.hyperledger.besu.ethereum.api.handlers.TimeoutOptions; import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration; import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcHttpService; @@ -42,6 +43,7 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.subscription.SubscriptionManager; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; import org.hyperledger.besu.ethereum.blockcreation.PoWMiningCoordinator; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.Synchronizer; @@ -114,7 +116,9 @@ public class WebSocketServiceLoginTest { protected static OkHttpClient client; protected static String baseUrl; protected static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); - protected static final String CLIENT_VERSION = "TestClientVersion/0.1.0"; + protected static final String CLIENT_NODE_NAME = "TestClientVersion/0.1.0"; + protected static final String CLIENT_VERSION = "0.1.0"; + protected static final String CLIENT_COMMIT = "12345678"; protected static final BigInteger CHAIN_ID = BigInteger.valueOf(123); protected static P2PNetwork peerDiscoveryMock; protected static BlockchainQueries blockchainQueries; @@ -166,13 +170,20 @@ public void before() throws URISyntaxException { spy( new JsonRpcMethodsFactory() .methods( + CLIENT_NODE_NAME, CLIENT_VERSION, + CLIENT_COMMIT, CHAIN_ID, genesisConfigOptions, peerDiscoveryMock, blockchainQueries, synchronizer, - MainnetProtocolSchedule.fromConfig(genesisConfigOptions), + MainnetProtocolSchedule.fromConfig( + genesisConfigOptions, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()), mock(ProtocolContext.class), mock(FilterManager.class), mock(TransactionPool.class), @@ -187,6 +198,7 @@ public void before() throws URISyntaxException { mock(JsonRpcConfiguration.class), mock(WebSocketConfiguration.class), mock(MetricsConfiguration.class), + mock(GraphQLConfiguration.class), natService, new HashMap<>(), folder, diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/blockheaders/NewBlockHeadersSubscriptionServiceTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/blockheaders/NewBlockHeadersSubscriptionServiceTest.java index 4902eb31464..8cb33fa714e 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/blockheaders/NewBlockHeadersSubscriptionServiceTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/blockheaders/NewBlockHeadersSubscriptionServiceTest.java @@ -32,8 +32,10 @@ import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockDataGenerator; import org.hyperledger.besu.ethereum.core.BlockDataGenerator.BlockOptions; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; import org.hyperledger.besu.ethereum.storage.keyvalue.VariablesKeyValueStorage; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; @@ -47,6 +49,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; +import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; @@ -63,7 +66,8 @@ public class NewBlockHeadersSubscriptionServiceTest { new KeyValueStoragePrefixedKeyBlockchainStorage( new InMemoryKeyValueStorage(), new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), - new MainnetBlockHeaderFunctions()); + new MainnetBlockHeaderFunctions(), + false); private final Block genesisBlock = gen.genesisBlock(); private final MutableBlockchain blockchain = DefaultBlockchain.createMutable(genesisBlock, blockchainStorage, new NoOpMetricsSystem(), 0); @@ -72,9 +76,16 @@ public class NewBlockHeadersSubscriptionServiceTest { private final SubscriptionManager subscriptionManagerSpy = new SubscriptionManager(new NoOpMetricsSystem()); + @Mock ProtocolSchedule protocolSchedule; + @Spy private final BlockchainQueries blockchainQueriesSpy = - Mockito.spy(new BlockchainQueries(blockchain, createInMemoryWorldStateArchive())); + Mockito.spy( + new BlockchainQueries( + protocolSchedule, + blockchain, + createInMemoryWorldStateArchive(), + MiningParameters.newDefault())); @BeforeEach public void before() { diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/request/SubscriptionRequestMapperTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/request/SubscriptionRequestMapperTest.java index d71b47421eb..f560dfaa089 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/request/SubscriptionRequestMapperTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/websocket/subscription/request/SubscriptionRequestMapperTest.java @@ -83,7 +83,7 @@ public void mapRequestToUnsubscribeRequestMissingSubscriptionIdFails() { .isInstanceOf(InvalidSubscriptionRequestException.class) .getCause() .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessage("Missing required json rpc parameter at index 0"); + .hasMessage("Invalid subscription type parameter (index 0)"); } @Test @@ -138,7 +138,7 @@ public void mapRequestToNewHeadsWithInvalidSecondParamFails() { .isInstanceOf(InvalidSubscriptionRequestException.class) .getCause() .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Invalid json rpc parameter at index 1"); + .hasMessageContaining("Invalid subscription parameter (index 1)"); } @Test @@ -290,7 +290,7 @@ public void mapRequestToLogsWithInvalidTopicInFilter() { .isInstanceOf(InvalidSubscriptionRequestException.class) .getCause() .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Invalid json rpc parameter at index 1"); + .hasMessageContaining("Invalid filter parameters (index 1)"); } @Test @@ -303,7 +303,7 @@ public void mapRequestToLogsWithInvalidSecondParam() { .isInstanceOf(InvalidSubscriptionRequestException.class) .getCause() .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Invalid json rpc parameter at index 1"); + .hasMessageContaining("Invalid filter parameters (index 1)"); } @Test @@ -372,7 +372,7 @@ public void mapAbsentSubscriptionTypeRequestFails() { .isInstanceOf(InvalidSubscriptionRequestException.class) .getCause() .isInstanceOf(InvalidJsonRpcParameters.class) - .hasMessageContaining("Invalid json rpc parameter at index 0"); + .hasMessageContaining("Invalid subscription type parameter (index 0)"); } @Test diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BackendQueryTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BackendQueryTest.java index 20eaf656ed7..4c5c2679eed 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BackendQueryTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BackendQueryTest.java @@ -20,6 +20,7 @@ import java.util.function.Supplier; import java.util.stream.Stream; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -52,4 +53,11 @@ public void test( assertThat(BackendQuery.runIfAlive(() -> wantReturn, alive)).isEqualTo(wantReturn); } } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueriesLogCacheTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueriesLogCacheTest.java index 77476cd97d7..c0420163de8 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueriesLogCacheTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueriesLogCacheTest.java @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.api.query; import static org.hyperledger.besu.ethereum.api.query.cache.TransactionLogBloomCacher.BLOCKS_PER_BLOOM_CACHE; @@ -30,8 +28,10 @@ import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.evm.log.Log; import org.hyperledger.besu.evm.log.LogsBloomFilter; @@ -58,12 +58,14 @@ @MockitoSettings(strictness = Strictness.LENIENT) public class BlockchainQueriesLogCacheTest { + // this tempDir is deliberately static @TempDir private static Path cacheDir; private static LogsQuery logsQuery; private Hash testHash; private static LogsBloomFilter testLogsBloomFilter; + @Mock ProtocolSchedule protocolSchedule; @Mock MutableBlockchain blockchain; @Mock WorldStateArchive worldStateArchive; @Mock EthScheduler scheduler; @@ -128,7 +130,12 @@ public void setup() { when(blockchain.getBlockBody(any())).thenReturn(Optional.of(fakeBody)); blockchainQueries = new BlockchainQueries( - blockchain, worldStateArchive, Optional.of(cacheDir), Optional.of(scheduler)); + protocolSchedule, + blockchain, + worldStateArchive, + Optional.of(cacheDir), + Optional.of(scheduler), + MiningParameters.newDefault()); } /** diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueriesTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueriesTest.java index e4539b19904..a49b290e4db 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueriesTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/BlockchainQueriesTest.java @@ -1,5 +1,4 @@ /* - * * Copyright ConsenSys AG. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with @@ -12,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.api.query; @@ -30,9 +28,11 @@ import org.hyperledger.besu.ethereum.core.BlockDataGenerator.BlockOptions; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.LogWithMetadata; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.worldstate.WorldState; @@ -48,6 +48,7 @@ import org.apache.tuweni.units.bigints.UInt256; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; public class BlockchainQueriesTest { private BlockDataGenerator gen; @@ -506,6 +507,9 @@ public void getLatestBlockOmmerByIndexShouldReturnExpectedOmmerHeader() { assertThat(retrievedOmmerBlockHeader).isEqualTo(ommerBlockHeader); } + @Test + public void getGasPriceLowerBound() {} + private void assertBlockMatchesResult( final Block targetBlock, final BlockWithMetadata result) { assertThat(result.getHeader()).isEqualTo(targetBlock.getHeader()); @@ -589,17 +593,15 @@ private BlockchainWithData( this.blockchain = blockchain; this.blockData = blockData; this.worldStateArchive = worldStateArchive; - this.blockchainQueries = new BlockchainQueries(blockchain, worldStateArchive, scheduler); + this.blockchainQueries = + new BlockchainQueries( + Mockito.mock(ProtocolSchedule.class), + blockchain, + worldStateArchive, + scheduler, + MiningParameters.newDefault()); } } - private static class BlockData { - final Block block; - final List receipts; - - private BlockData(final Block block, final List receipts) { - this.block = block; - this.receipts = receipts; - } - } + private record BlockData(Block block, List receipts) {} } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/LogsQueryTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/LogsQueryTest.java index 95a5103546e..748222d2feb 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/LogsQueryTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/LogsQueryTest.java @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.api.query; import static java.util.Collections.singletonList; diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/StateBackupServiceTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/StateBackupServiceTest.java index d8103963fc3..ee5d0f347d1 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/StateBackupServiceTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/StateBackupServiceTest.java @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.api.query; import static org.assertj.core.api.Assertions.assertThat; diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/cache/TransactionLogBloomCacherTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/cache/TransactionLogBloomCacherTest.java index 5ff026e62ca..5772a04a49d 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/cache/TransactionLogBloomCacherTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/query/cache/TransactionLogBloomCacherTest.java @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.api.query.cache; import static org.assertj.core.api.Assertions.assertThat; diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/tls/FileBasedPasswordProviderTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/tls/FileBasedPasswordProviderTest.java index dd9d75778e8..7d77f9e430e 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/tls/FileBasedPasswordProviderTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/tls/FileBasedPasswordProviderTest.java @@ -1,5 +1,4 @@ /* - * * Copyright ConsenSys AG. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with @@ -12,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.api.tls; diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/util/DomainObjectDecodeUtilsTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/util/DomainObjectDecodeUtilsTest.java index 763182da5c5..af48e26823c 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/util/DomainObjectDecodeUtilsTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/util/DomainObjectDecodeUtilsTest.java @@ -1,5 +1,4 @@ /* - * * Copyright ConsenSys AG. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with @@ -12,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.api.util; diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/util/TestJsonRpcMethodsUtil.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/util/TestJsonRpcMethodsUtil.java index ada9e87c937..5273192b18f 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/util/TestJsonRpcMethodsUtil.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/util/TestJsonRpcMethodsUtil.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.api.util; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/graphql/eth_gasPrice.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/graphql/eth_gasPrice.json index 40b8bf0fa0d..1f5ad97ff89 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/graphql/eth_gasPrice.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/graphql/eth_gasPrice.json @@ -2,7 +2,7 @@ "request": "{ gasPrice maxPriorityFeePerGas }", "response": { "data": { - "gasPrice": "0x1", + "gasPrice": "0x2dbc88c1", "maxPriorityFeePerGas": "0x3b9aca00" } }, diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/graphql/eth_getBlock_frontier.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/graphql/eth_getBlock_frontier.json new file mode 100644 index 00000000000..335121ac896 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/graphql/eth_getBlock_frontier.json @@ -0,0 +1,29 @@ +{ + "request": "{block (number: 32) { difficulty extraData miner { address } mixHash nonce stateRoot totalDifficulty transactions { gasPrice type yParity v r s} }} ", + "response": { + "data": { + "block": { + "difficulty": "0x207c0", + "extraData": "0x", + "miner": { + "address": "0x8888f1f195afa192cfee860698584c030f4c9db1" + }, + "mixHash": "0x4edd77bfff565659bb0ae09421918e4def65d938a900eb94230eb01f5ce80c99", + "nonce": "0xdb063000b00e8026", + "stateRoot": "0xf65f3dd13f72f5fa5607a5224691419969b4f4bae7a00a6cdb853f2ca9eeb1be", + "totalDifficulty": "0x427c00", + "transactions": [ + { + "gasPrice": "0x1", + "type": "0x0", + "yParity": null, + "v": "0x1b", + "r": "0x705b002a7df60707d33812e0298411721be20ea5a2f533707295140d89263b79", + "s": "0x78024390784f24160739533b3ceea2698289a02afd9cc768581b4aa3d5f4b105" + } + ] + } + } + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/graphql/eth_getBlock_shanghai.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/graphql/eth_getBlock_shanghai.json index 3e98251971b..add2edd6507 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/graphql/eth_getBlock_shanghai.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/graphql/eth_getBlock_shanghai.json @@ -1,5 +1,5 @@ { - "request": "{block (number : 33) { baseFeePerGas difficulty extraData miner { address } mixHash nonce stateRoot totalDifficulty withdrawalsRoot withdrawals { address amount index validator } }} ", + "request": "{block (number : 33) { baseFeePerGas difficulty extraData miner { address } mixHash nonce stateRoot totalDifficulty transactions { r s v yParity } withdrawalsRoot withdrawals { address amount index validator } }} ", "response": { "data": { "block": { @@ -13,6 +13,14 @@ "nonce": "0x0000000000000000", "stateRoot": "0x0d3c456bb68669bad05da3a1a766daab236c9df1da8f74edf5ebe9383f00084c", "totalDifficulty": "0x427c00", + "transactions": [ + { + "r": "0x8abbfbd4c5f2a13a8d5ed394ac50bac7d678f83a23f645818492f76e8ee17ab3", + "s": "0x7bd38c6929235f775d68b45bd7dea7981264f9a265b6bea97b070e15be88389c", + "v": "0x0", + "yParity": "0x0" + } + ], "withdrawalsRoot": "0x37945ab58d2712a26df2a38d217e822694927e29b30d5993d7a53ccea618d1f3", "withdrawals": [ { diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/graphql/eth_getTransaction_type2.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/graphql/eth_getTransaction_type2.json index d88f5f51554..d4c557f4bf1 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/graphql/eth_getTransaction_type2.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/graphql/eth_getTransaction_type2.json @@ -1,5 +1,5 @@ { - "request": "{transaction (hash : \"0x3ecd2ca6cf26c864d0ea5f038a58d4cd4a46a3e242fe92f446f392fdc232dd98\") { accessList { address storageKeys } maxFeePerGas maxPriorityFeePerGas nonce type status } } ", + "request": "{transaction (hash : \"0x3ecd2ca6cf26c864d0ea5f038a58d4cd4a46a3e242fe92f446f392fdc232dd98\") { accessList { address storageKeys } maxFeePerGas maxPriorityFeePerGas nonce type status yParity v} } ", "response": { "data": { "transaction": { @@ -15,7 +15,9 @@ "maxPriorityFeePerGas": "0x3b9aca00", "nonce": "0x20", "type": "0x2", - "status": "0x1" + "status": "0x1", + "yParity": "0x0", + "v": "0x0" } } }, diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-call/debug_traceCall_all.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-call/debug_traceCall_all.json index cc6696ae142..69d19bb5e6d 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-call/debug_traceCall_all.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-call/debug_traceCall_all.json @@ -167,7 +167,7 @@ "pc" : 21, "op" : "CALLCODE", "gas" : 16755865, - "gasCost" : 700, + "gasCost" : 16494066, "depth" : 1, "stack" : null, "memory" : null, diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-call/debug_traceCall_complete.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-call/debug_traceCall_complete.json index ef20e1ae4ce..fb84953c078 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-call/debug_traceCall_complete.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-call/debug_traceCall_complete.json @@ -164,7 +164,7 @@ "pc" : 21, "op" : "CALLCODE", "gas" : 16755865, - "gasCost" : 700, + "gasCost" : 16494066, "depth" : 1, "stack" : [ "0000000000000000000000000000000000000000000000000000000000000020", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000020", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000030000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000ffac99" ], "memory" : [ "f000000000000000000000000000000000000000000000000000000000000001" ], diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-call/debug_traceCall_disableMemory.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-call/debug_traceCall_disableMemory.json index 5f1ab990f96..73b391f022a 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-call/debug_traceCall_disableMemory.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-call/debug_traceCall_disableMemory.json @@ -167,7 +167,7 @@ "pc" : 21, "op" : "CALLCODE", "gas" : 16755865, - "gasCost" : 700, + "gasCost" : 16494066, "depth" : 1, "stack" : [ "0000000000000000000000000000000000000000000000000000000000000020", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000020", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000030000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000ffac99" ], "memory" : null, diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-call/debug_traceCall_disableStack.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-call/debug_traceCall_disableStack.json index 38e3d77b250..226fcb2cd0f 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-call/debug_traceCall_disableStack.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-call/debug_traceCall_disableStack.json @@ -167,7 +167,7 @@ "pc" : 21, "op" : "CALLCODE", "gas" : 16755865, - "gasCost" : 700, + "gasCost" : 16494066, "depth" : 1, "stack" : null, "memory" : [ "f000000000000000000000000000000000000000000000000000000000000001" ], diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-call/debug_traceCall_disableStorage.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-call/debug_traceCall_disableStorage.json index ea6bd930442..a515b46b744 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-call/debug_traceCall_disableStorage.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-call/debug_traceCall_disableStorage.json @@ -167,7 +167,7 @@ "pc" : 21, "op" : "CALLCODE", "gas" : 16755865, - "gasCost" : 700, + "gasCost" : 16494066, "depth" : 1, "stack" : [ "0000000000000000000000000000000000000000000000000000000000000020", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000020", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000030000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000ffac99" ], "memory" : [ "f000000000000000000000000000000000000000000000000000000000000001" ], diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-call/debug_traceCall_noGasPrice.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-call/debug_traceCall_noGasPrice.json new file mode 100644 index 00000000000..18fe53baa16 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/debug/trace-call/debug_traceCall_noGasPrice.json @@ -0,0 +1,42 @@ +{ + "request": { + "jsonrpc": "2.0", + "method": "debug_traceCall", + "params": [ + { + "to": "0x0aae40965e6800cd9b1f4b05ff21581047e3f91e", + "data": "0x000000000000000000000000000000000000000000000000000000000001A00E" + }, + "latest", + { + "disableMemory": true, + "disableStack": true, + "disableStorage": true + } + ], + "id": 1 + }, + "response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "gas": 21164, + "failed": false, + "returnValue": "", + "structLogs": [ + { + "pc": 0, + "op": "STOP", + "gas": 17592186023252, + "gasCost": 0, + "depth": 1, + "stack": null, + "memory": null, + "storage": null, + "reason": null + } + ] + } + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob.json new file mode 100644 index 00000000000..463c57e21e8 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob.json @@ -0,0 +1,23 @@ +{ + "request": { + "id": 4, + "jsonrpc": "2.0", + "method": "eth_call", + "params": [ + { + "to": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "from": "a94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "data": "0x12a7b914", + "blobVersionedHashes" : ["0x0100000051c8833cfbaf272e62da1285b183b0405357f62b052a4894ffcdaa2d"], + "maxFeePerBlobGas": "0x3b9aca00" + }, + "latest" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 4, + "result": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_missing_maxFeePerBlobGas.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_missing_maxFeePerBlobGas.json new file mode 100644 index 00000000000..2f92e43f7ae --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_missing_maxFeePerBlobGas.json @@ -0,0 +1,22 @@ +{ + "request": { + "id": 4, + "jsonrpc": "2.0", + "method": "eth_call", + "params": [ + { + "to": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "from": "a94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "data": "0x12a7b914", + "blobVersionedHashes" : ["0x0100000051c8833cfbaf272e62da1285b183b0405357f62b052a4894ffcdaa2d"] + }, + "latest" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 4, + "result": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_missing_maxFeePerBlobGas_strict.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_missing_maxFeePerBlobGas_strict.json new file mode 100644 index 00000000000..3c16f9c0bf8 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_missing_maxFeePerBlobGas_strict.json @@ -0,0 +1,25 @@ +{ + "request": { + "id": 4, + "jsonrpc": "2.0", + "method": "eth_call", + "params": [ + { + "to": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "from": "a94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "data": "0x12a7b914", + "blobVersionedHashes" : ["0x0100000051c8833cfbaf272e62da1285b183b0405357f62b052a4894ffcdaa2d"], + "gasPrice": "0x000000000000000000000000000000000000000000000000000000003437004a", + "maxFeePerBlobGas": "0x0000000000000000000000000000000000000000000000000000000000000000", + "strict": true + }, + "latest" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 4, + "error":{"code":-32009,"message":"blob gas price below current blob base fee"} + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_too_many_blobs.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_too_many_blobs.json new file mode 100644 index 00000000000..a43edf17f99 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_too_many_blobs.json @@ -0,0 +1,23 @@ +{ + "request": { + "id": 4, + "jsonrpc": "2.0", + "method": "eth_call", + "params": [ + { + "to": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "from": "a94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "data": "0x12a7b914", + "blobVersionedHashes" : ["0x0100000051c8833cfbaf272e62da1285b183b0405357f62b052a4894ffcdaa2d","0x0100000051c8833cfbaf272e62da1285b183b0405357f62b052a4894ffcdaa2d","0x0100000051c8833cfbaf272e62da1285b183b0405357f62b052a4894ffcdaa2d","0x0100000051c8833cfbaf272e62da1285b183b0405357f62b052a4894ffcdaa2d","0x0100000051c8833cfbaf272e62da1285b183b0405357f62b052a4894ffcdaa2d","0x0100000051c8833cfbaf272e62da1285b183b0405357f62b052a4894ffcdaa2d","0x0100000051c8833cfbaf272e62da1285b183b0405357f62b052a4894ffcdaa2d"], + "maxFeePerBlobGas": "0x0" + }, + "latest" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 4, + "error":{"code":-32000,"message":"Total blob gas too high"} + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_without_to.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_without_to.json new file mode 100644 index 00000000000..c73c61c04de --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_without_to.json @@ -0,0 +1,22 @@ +{ + "request": { + "id": 4, + "jsonrpc": "2.0", + "method": "eth_call", + "params": [ + { + "from": "a94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "data": "0x12a7b914", + "blobVersionedHashes" : ["0x0100000051c8833cfbaf272e62da1285b183b0405357f62b052a4894ffcdaa2d"], + "maxFeePerBlobGas": "0x3b9aca00" + }, + "latest" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 4, + "error":{"code":-32602,"message":"Invalid transaction type"} + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_zero_fee.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_zero_fee.json new file mode 100644 index 00000000000..3b5d4a61194 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_zero_fee.json @@ -0,0 +1,23 @@ +{ + "request": { + "id": 4, + "jsonrpc": "2.0", + "method": "eth_call", + "params": [ + { + "to": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "from": "a94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "data": "0x12a7b914", + "blobVersionedHashes" : ["0x0100000051c8833cfbaf272e62da1285b183b0405357f62b052a4894ffcdaa2d"], + "maxFeePerBlobGas": "0x0" + }, + "latest" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 4, + "result": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_zero_versioned_hash.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_zero_versioned_hash.json new file mode 100644 index 00000000000..80c21e6a1b2 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_blob_zero_versioned_hash.json @@ -0,0 +1,23 @@ +{ + "request": { + "id": 4, + "jsonrpc": "2.0", + "method": "eth_call", + "params": [ + { + "to": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "from": "a94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "data": "0x12a7b914", + "blobVersionedHashes" : ["0x0"], + "maxFeePerBlobGas": "0x3b9aca00" + }, + "latest" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 4, + "error":{"code":-32602,"message":"Invalid call params"} + }, + "statusCode": 200 +} diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_callParamsMissing_block_8.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_callParamsMissing_block_8.json index 562e0f33265..70a9be13f5c 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_callParamsMissing_block_8.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_callParamsMissing_block_8.json @@ -12,7 +12,7 @@ "id": 4, "error":{ "code":-32602, - "message":"Invalid params" + "message":"Invalid block, unable to parse RLP" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_invalidBlockhash.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_invalidBlockhash.json index d6a714eb49b..f00531954c3 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_invalidBlockhash.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_invalidBlockhash.json @@ -17,7 +17,7 @@ "id": 3, "error" : { "code" : -32602, - "message" : "Invalid params" + "message" : "Invalid block, unable to parse RLP" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_invalidWithDifferentInputAndDataAttributes.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_invalidWithDifferentInputAndDataAttributes.json new file mode 100644 index 00000000000..e304f0cbbd6 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_invalidWithDifferentInputAndDataAttributes.json @@ -0,0 +1,25 @@ +{ + "request": { + "id": 3, + "jsonrpc": "2.0", + "method": "eth_call", + "params": [ + { + "to": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "from": "a94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "input": "0x12a7b914", + "data": "0x12a7b915" + }, + "latest" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 3, + "error" : { + "code" : -32602, + "message" : "Invalid call params" + } + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_invalidWithInputAndDataAttribute.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_invalidWithInputAndDataAttribute.json deleted file mode 100644 index eda5c99c0bc..00000000000 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_invalidWithInputAndDataAttribute.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "request": { - "id": 3, - "jsonrpc": "2.0", - "method": "eth_call", - "params": [ - { - "to": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", - "from": "a94f5374fce5edbc8e2a8697c15331677e6ebf0b", - "input": "0x12a7b914", - "data": "0x12a7b914" - }, - "0x19" - ] - }, - "response": { - "jsonrpc": "2.0", - "id": 3, - "error" : { - "code" : -32602, - "message" : "Invalid params" - } - }, - "statusCode": 200 -} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_withInputAndDataAttribute.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_withInputAndDataAttribute.json new file mode 100644 index 00000000000..929dbe2ad4f --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_call_withInputAndDataAttribute.json @@ -0,0 +1,22 @@ +{ + "request": { + "id": 3, + "jsonrpc": "2.0", + "method": "eth_call", + "params": [ + { + "to": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "from": "a94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "input": "0x12a7b914", + "data": "0x12a7b914" + }, + "0x19" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 3, + "result": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_from_contract_withBlockNum.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_from_contract_withBlockNum.json new file mode 100644 index 00000000000..aa9731f74f4 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_from_contract_withBlockNum.json @@ -0,0 +1,21 @@ +{ + "request": { + "id": 3, + "jsonrpc": "2.0", + "method": "eth_estimateGas", + "params": [ + { + "to": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "from": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "data": "0x123456" + }, + "0x1" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 3, + "result": "0x52d4" + }, + "statusCode": 200 +} diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_from_contract_withBlockTag.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_from_contract_withBlockTag.json new file mode 100644 index 00000000000..992a7d77283 --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_from_contract_withBlockTag.json @@ -0,0 +1,21 @@ +{ + "request": { + "id": 3, + "jsonrpc": "2.0", + "method": "eth_estimateGas", + "params": [ + { + "to": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "from": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "data": "0x123456" + }, + "latest" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 3, + "result": "0x5238" + }, + "statusCode": 200 +} diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_insufficientGas_withBlockNum.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_insufficientGas_withBlockNum.json new file mode 100644 index 00000000000..6dd337509ef --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_insufficientGas_withBlockNum.json @@ -0,0 +1,21 @@ +{ + "request": { + "id": 3, + "jsonrpc": "2.0", + "method": "eth_estimateGas", + "params": [ + { + "to": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "from": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + "gas": "0x1" + }, + "0xa" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 3, + "result": "0x5208" + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_invalid.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_invalid.json index 21f5ffb3bcb..7c78f47000f 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_invalid.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_invalid.json @@ -14,8 +14,8 @@ "jsonrpc": "2.0", "id": 3, "error": { - "code": -32603, - "message": "Internal error" + "code": -32000, + "message": "Transaction processing could not be completed due to an exception: INVALID_OPERATION" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_transfer_withBlockTag.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_transfer_withBlockTag.json new file mode 100644 index 00000000000..d909af9eedb --- /dev/null +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_estimateGas_transfer_withBlockTag.json @@ -0,0 +1,21 @@ +{ + "request": { + "id": 3, + "jsonrpc": "2.0", + "method": "eth_estimateGas", + "params": [ + { + "from": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "to": "0x8888f1f195afa192cfee860698584c030f4c9db1", + "value": "0x1" + }, + "earliest" + ] + }, + "response": { + "jsonrpc": "2.0", + "id": 3, + "result": "0x5208" + }, + "statusCode": 200 +} \ No newline at end of file diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_feeHistory_fieldNamesWithReward.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_feeHistory_fieldNamesWithReward.json index f5b7baee3f0..ed65aaf5f62 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_feeHistory_fieldNamesWithReward.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_feeHistory_fieldNamesWithReward.json @@ -23,10 +23,12 @@ "0x3437004a", "0x2dbc88c1" ], + "baseFeePerBlobGas" : [ "0x0", "0x1", "0x1" ], "gasUsedRatio": [ 0.004079142040086682, 0.003713085594819442 ], + "blobGasUsedRatio" : [ 0.0, 0.3333333333333333 ], "reward": [ [ "0x3b9aca00", diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_feeHistory_fieldNamesWithoutReward.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_feeHistory_fieldNamesWithoutReward.json index f048553904a..efe182637ee 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_feeHistory_fieldNamesWithoutReward.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_feeHistory_fieldNamesWithoutReward.json @@ -18,10 +18,12 @@ "0x3437004a", "0x2dbc88c1" ], + "baseFeePerBlobGas" : [ "0x0", "0x1", "0x1" ], "gasUsedRatio": [ 0.004079142040086682, 0.003713085594819442 - ] + ], + "blobGasUsedRatio" : [ 0.0, 0.3333333333333333 ] } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBalance_illegalRangeLessThan.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBalance_illegalRangeLessThan.json index 146756fc552..2a59465e88f 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBalance_illegalRangeLessThan.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBalance_illegalRangeLessThan.json @@ -13,7 +13,7 @@ "id": 28, "error": { "code": -32602, - "message": "Invalid params" + "message": "Invalid block number params" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBalance_invalidBlockHash.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBalance_invalidBlockHash.json index fa6fab63065..7e56b0a5669 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBalance_invalidBlockHash.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBalance_invalidBlockHash.json @@ -13,7 +13,7 @@ "id": 28, "error" : { "code" : -32602, - "message" : "Invalid params" + "message" : "Invalid block, unable to parse RLP" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBalance_invalidParams.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBalance_invalidParams.json index e84f3045a33..0f07f4a3bbe 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBalance_invalidParams.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBalance_invalidParams.json @@ -10,7 +10,7 @@ "id": 32, "error" : { "code" : -32602, - "message" : "Invalid params" + "message" : "Invalid block, unable to parse RLP" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceipts_invalidParams_blockNumber.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceipts_invalidParams_blockNumber.json index 91f6a6af8f7..4dca011466b 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceipts_invalidParams_blockNumber.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceipts_invalidParams_blockNumber.json @@ -12,7 +12,7 @@ "id": 300, "error" : { "code" : -32602, - "message" : "Invalid params" + "message" : "Invalid block, unable to parse RLP" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceipts_invalidParams_tag.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceipts_invalidParams_tag.json index 655ff6013ae..8fc9c2d805a 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceipts_invalidParams_tag.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockReceipts_invalidParams_tag.json @@ -12,7 +12,7 @@ "id": 301, "error" : { "code" : -32602, - "message" : "Invalid params" + "message" : "Invalid block, unable to parse RLP" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockTransactionCountByHash_invalidParams.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockTransactionCountByHash_invalidParams.json index e05ba50cc6e..07a35abf6d9 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockTransactionCountByHash_invalidParams.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockTransactionCountByHash_invalidParams.json @@ -10,7 +10,7 @@ "id": 209, "error": { "code" : -32602, - "message" : "Invalid params" + "message" : "Invalid block hash params" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockTransactionCountByNumber_invalidParams.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockTransactionCountByNumber_invalidParams.json index 7e40d417e76..1a56a83a607 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockTransactionCountByNumber_invalidParams.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getBlockTransactionCountByNumber_invalidParams.json @@ -10,7 +10,7 @@ "id": 248, "error": { "code" : -32602, - "message" : "Invalid params" + "message" : "Invalid block, unable to parse RLP" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getCode_illegalRangeLessThan.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getCode_illegalRangeLessThan.json index 6a336a96667..e2011bb4d05 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getCode_illegalRangeLessThan.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getCode_illegalRangeLessThan.json @@ -13,7 +13,7 @@ "id": 13, "error": { "code": -32602, - "message": "Invalid params" + "message": "Invalid block number params" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getCode_invalidBlockHash.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getCode_invalidBlockHash.json index a5e1e004af8..9e715fca1da 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getCode_invalidBlockHash.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getCode_invalidBlockHash.json @@ -13,7 +13,7 @@ "id": 0, "error" : { "code" : -32602, - "message" : "Invalid params" + "message" : "Invalid block, unable to parse RLP" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getCode_invalidParams.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getCode_invalidParams.json index 5a65b4d17c5..929a0f3f358 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getCode_invalidParams.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getCode_invalidParams.json @@ -10,7 +10,7 @@ "id": 255, "error": { "code": -32602, - "message": "Invalid params" + "message": "Invalid block, unable to parse RLP" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getLogs_invalidInput.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getLogs_invalidInput.json index 5a3dcb038a3..c2c23fdfec3 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getLogs_invalidInput.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getLogs_invalidInput.json @@ -16,7 +16,7 @@ "id" : 406, "error" : { "code": -32602, - "message": "Invalid params" + "message": "Invalid filter params" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getNewFilter_invalidFilter.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getNewFilter_invalidFilter.json index be517fc357c..52d5bd7966b 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getNewFilter_invalidFilter.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getNewFilter_invalidFilter.json @@ -12,7 +12,7 @@ "id": 406, "error" : { "code" : -32602, - "message" : "Invalid params" + "message" : "Invalid filter params" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getProof_illegalRangeLessThan.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getProof_illegalRangeLessThan.json index de1c3ccb57b..f0cf31f79dd 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getProof_illegalRangeLessThan.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getProof_illegalRangeLessThan.json @@ -14,7 +14,7 @@ "id": 28, "error": { "code": -32602, - "message": "Invalid params" + "message": "Invalid block number params" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getProof_invalidBlockHash.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getProof_invalidBlockHash.json index 257380fdac1..4fb69d437c9 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getProof_invalidBlockHash.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getProof_invalidBlockHash.json @@ -14,7 +14,7 @@ "id": 28, "error" : { "code" : -32602, - "message" : "Invalid params" + "message" : "Invalid block, unable to parse RLP" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getProof_invalidParams.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getProof_invalidParams.json index ba169f79dea..095ebd2a910 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getProof_invalidParams.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getProof_invalidParams.json @@ -10,7 +10,7 @@ "id": 28, "error" : { "code" : -32602, - "message" : "Invalid params" + "message" : "Invalid block, unable to parse RLP" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getStorageAt_illegalRangeLessThan.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getStorageAt_illegalRangeLessThan.json index 67892401ce3..1f46003c8f6 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getStorageAt_illegalRangeLessThan.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getStorageAt_illegalRangeLessThan.json @@ -14,7 +14,7 @@ "id": 337, "error": { "code": -32602, - "message": "Invalid params" + "message": "Invalid block number params" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getStorageAt_invalidBlockHash.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getStorageAt_invalidBlockHash.json index 3ab3afb25ea..e8fecda1030 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getStorageAt_invalidBlockHash.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getStorageAt_invalidBlockHash.json @@ -14,7 +14,7 @@ "id": 341, "error" : { "code" : -32602, - "message" : "Invalid params" + "message" : "Invalid block, unable to parse RLP" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getStorageAt_invalidParams.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getStorageAt_invalidParams.json index 99af70906a3..9ad533c0c68 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getStorageAt_invalidParams.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getStorageAt_invalidParams.json @@ -10,7 +10,7 @@ "id": 342, "error" : { "code" : -32602, - "message" : "Invalid params" + "message" : "Invalid block, unable to parse RLP" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByBlockHashAndIndex_intOverflow.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByBlockHashAndIndex_intOverflow.json index 7f804359ab5..aac1138b583 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByBlockHashAndIndex_intOverflow.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByBlockHashAndIndex_intOverflow.json @@ -13,7 +13,7 @@ "id": 448, "error": { "code": -32602, - "message" : "Invalid params" + "message" : "Invalid transaction id params" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByBlockHashAndIndex_missingParam_00.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByBlockHashAndIndex_missingParam_00.json index 97240170b07..b6c8becdb2e 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByBlockHashAndIndex_missingParam_00.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByBlockHashAndIndex_missingParam_00.json @@ -12,7 +12,7 @@ "id": 448, "error": { "code": -32602, - "message" : "Invalid params" + "message" : "Invalid transaction hash params" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByBlockHashAndIndex_missingParam_01.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByBlockHashAndIndex_missingParam_01.json index 6d12441ac7e..5984be529b7 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByBlockHashAndIndex_missingParam_01.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByBlockHashAndIndex_missingParam_01.json @@ -12,7 +12,7 @@ "id": 448, "error": { "code": -32602, - "message" : "Invalid params" + "message" : "Invalid transaction id params" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByBlockHashAndIndex_missingParams.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByBlockHashAndIndex_missingParams.json index 64f1cdef765..f3c8fca4e56 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByBlockHashAndIndex_missingParams.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByBlockHashAndIndex_missingParams.json @@ -10,7 +10,7 @@ "id": 448, "error": { "code": -32602, - "message" : "Invalid params" + "message" : "Invalid transaction hash params" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByBlockHashAndIndex_wrongParamType.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByBlockHashAndIndex_wrongParamType.json index 977ea64f158..f4e3dea7753 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByBlockHashAndIndex_wrongParamType.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByBlockHashAndIndex_wrongParamType.json @@ -13,7 +13,7 @@ "id": 448, "error": { "code": -32602, - "message" : "Invalid params" + "message" : "Invalid transaction id params" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByBlockNumberAndIndex_invalidParams.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByBlockNumberAndIndex_invalidParams.json index 8ba25d9c33b..c88d01904db 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByBlockNumberAndIndex_invalidParams.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByBlockNumberAndIndex_invalidParams.json @@ -10,7 +10,7 @@ "id": 486, "error": { "code" : -32602, - "message" : "Invalid params" + "message" : "Invalid block, unable to parse RLP" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByHash_invalidHashAndIndex.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByHash_invalidHashAndIndex.json index 03756bd7bb6..424c31ebf02 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByHash_invalidHashAndIndex.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByHash_invalidHashAndIndex.json @@ -13,7 +13,7 @@ "id" : 406, "error" : { "code" : -32602, - "message" : "Invalid params" + "message" : "Invalid number of params" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByHash_invalidParams.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByHash_invalidParams.json index 7791942380f..a0a7fea74bb 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByHash_invalidParams.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByHash_invalidParams.json @@ -10,7 +10,7 @@ "id" : 412, "error" : { "code" : -32602, - "message" : "Invalid params" + "message" : "Invalid number of params" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByHash_typeMismatch.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByHash_typeMismatch.json index 2263de69d16..0abcb4da7e9 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByHash_typeMismatch.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionByHash_typeMismatch.json @@ -12,7 +12,7 @@ "id" : 406, "error" : { "code" : -32602, - "message" : "Invalid params" + "message" : "Invalid transaction hash params" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionCount_invalidBlockHash.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionCount_invalidBlockHash.json index 52651090e96..f98c40c0d2c 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionCount_invalidBlockHash.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionCount_invalidBlockHash.json @@ -13,7 +13,7 @@ "id": 487, "error" : { "code" : -32602, - "message" : "Invalid params" + "message" : "Invalid block, unable to parse RLP" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionCount_invalidBlockNumber.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionCount_invalidBlockNumber.json index 0ad4a0d3cb4..c25d81a1abb 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionCount_invalidBlockNumber.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionCount_invalidBlockNumber.json @@ -13,7 +13,7 @@ "id": 487, "error" : { "code" : -32602, - "message" : "Invalid params" + "message" : "Invalid block, unable to parse RLP" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionCount_missingArgument.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionCount_missingArgument.json index 75437d4db8a..75ca3aed1aa 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionCount_missingArgument.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/eth/eth_getTransactionCount_missingArgument.json @@ -10,7 +10,7 @@ "id": 489, "error": { "code": -32602, - "message": "Invalid params" + "message": "Invalid block, unable to parse RLP" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/trace/specs/replay-trace-transaction/flat/trace_replayBlockTransactions_invalidBlockParam.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/trace/specs/replay-trace-transaction/flat/trace_replayBlockTransactions_invalidBlockParam.json index b67f6eb872a..42967740d90 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/trace/specs/replay-trace-transaction/flat/trace_replayBlockTransactions_invalidBlockParam.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/trace/specs/replay-trace-transaction/flat/trace_replayBlockTransactions_invalidBlockParam.json @@ -13,7 +13,7 @@ "id": 415, "error": { "code": -32602, - "message": "Invalid params" + "message": "Invalid block, unable to parse RLP" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/trace/specs/replay-trace-transaction/flat/trace_replayBlockTransactions_invalidTraceOptions.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/trace/specs/replay-trace-transaction/flat/trace_replayBlockTransactions_invalidTraceOptions.json index 47f13b7287c..eb1bfbd30b1 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/trace/specs/replay-trace-transaction/flat/trace_replayBlockTransactions_invalidTraceOptions.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/trace/specs/replay-trace-transaction/flat/trace_replayBlockTransactions_invalidTraceOptions.json @@ -13,7 +13,7 @@ "id": 415, "error": { "code": -32602, - "message": "Invalid params" + "message": "Invalid trace type params" } }, "statusCode": 200 diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/trace/specs/trace-call/trace_call_14_1_trace.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/trace/specs/trace-call/trace_call_14_1_trace.json index ca583b57783..935a137298c 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/trace/specs/trace-call/trace_call_14_1_trace.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/trace/specs/trace-call/trace_call_14_1_trace.json @@ -41,7 +41,7 @@ "init" : "0x600160015560015460025560ff60005360016000f3", "value" : "0x0" }, - "error" : "Out of gas", + "error" : "Illegal state change", "subtraces" : 0, "traceAddress" : [ 0 ], "type" : "create" diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/trace/specs/trace-callMany/trace_callMany_14_trace.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/trace/specs/trace-callMany/trace_callMany_14_trace.json index 01331d70917..019f701e490 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/trace/specs/trace-callMany/trace_callMany_14_trace.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/trace/specs/trace-callMany/trace_callMany_14_trace.json @@ -82,7 +82,7 @@ "init" : "0x600160015560015460025560ff60005360016000f3", "value" : "0x0" }, - "error" : "Out of gas", + "error" : "Illegal state change", "subtraces" : 0, "traceAddress" : [ 0 ], "type" : "create" diff --git a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/trace/specs/trace-raw-transaction/trace_rawTransaction_14_1_trace.json b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/trace/specs/trace-raw-transaction/trace_rawTransaction_14_1_trace.json index 5dbd9ac84f0..9fc05d054e3 100644 --- a/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/trace/specs/trace-raw-transaction/trace_rawTransaction_14_1_trace.json +++ b/ethereum/api/src/test/resources/org/hyperledger/besu/ethereum/api/jsonrpc/trace/specs/trace-raw-transaction/trace_rawTransaction_14_1_trace.json @@ -34,7 +34,7 @@ "init" : "0x600160015560015460025560ff60005360016000f3", "value" : "0x0" }, - "error" : "Out of gas", + "error" : "Illegal state change", "subtraces" : 0, "traceAddress" : [ 0 ], "type" : "create" diff --git a/ethereum/blockcreation/build.gradle b/ethereum/blockcreation/build.gradle index 7fc35a6ec10..753cc12d185 100644 --- a/ethereum/blockcreation/build.gradle +++ b/ethereum/blockcreation/build.gradle @@ -7,7 +7,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } @@ -27,6 +28,7 @@ dependencies { implementation 'io.tmio:tuweni-bytes' implementation 'io.tmio:tuweni-units' + testImplementation project(':besu') testImplementation project(path: ':config', configuration: 'testSupportArtifacts') testImplementation project(path: ':ethereum:core', configuration: 'testArtifacts') testImplementation project(path: ':ethereum:core', configuration: 'testSupportArtifacts') diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java index deac72bc5da..b843389c0d3 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -24,29 +24,25 @@ import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockTransactionSelector; import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults; -import org.hyperledger.besu.ethereum.blockcreation.txselection.selectors.AllAcceptingTransactionSelector; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; -import org.hyperledger.besu.ethereum.core.Deposit; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; +import org.hyperledger.besu.ethereum.core.Request; import org.hyperledger.besu.ethereum.core.SealableBlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.Withdrawal; -import org.hyperledger.besu.ethereum.core.encoding.DepositDecoder; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.AbstractBlockProcessor; import org.hyperledger.besu.ethereum.mainnet.BodyValidation; -import org.hyperledger.besu.ethereum.mainnet.DepositsValidator; import org.hyperledger.besu.ethereum.mainnet.DifficultyCalculator; import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor; -import org.hyperledger.besu.ethereum.mainnet.ParentBeaconBlockRootHelper; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions; @@ -54,13 +50,15 @@ import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket; import org.hyperledger.besu.ethereum.mainnet.feemarket.ExcessBlobGasCalculator; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; +import org.hyperledger.besu.ethereum.mainnet.requests.ProcessRequestContext; +import org.hyperledger.besu.ethereum.mainnet.requests.RequestProcessorCoordinator; +import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup; import org.hyperledger.besu.evm.account.MutableAccount; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.hyperledger.besu.plugin.services.exception.StorageException; import org.hyperledger.besu.plugin.services.securitymodule.SecurityModuleException; import org.hyperledger.besu.plugin.services.tracer.BlockAwareOperationTracer; import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelector; -import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelectorFactory; import java.math.BigInteger; import java.util.List; @@ -68,7 +66,6 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.atomic.AtomicBoolean; -import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; @@ -91,8 +88,6 @@ public interface ExtraDataCalculator { protected final ProtocolContext protocolContext; protected final ProtocolSchedule protocolSchedule; protected final BlockHeaderFunctions blockHeaderFunctions; - protected final BlockHeader parentHeader; - private final Optional

depositContractAddress; private final EthScheduler ethScheduler; private final AtomicBoolean isCancelled = new AtomicBoolean(false); @@ -103,8 +98,6 @@ protected AbstractBlockCreator( final TransactionPool transactionPool, final ProtocolContext protocolContext, final ProtocolSchedule protocolSchedule, - final BlockHeader parentHeader, - final Optional
depositContractAddress, final EthScheduler ethScheduler) { this.miningParameters = miningParameters; this.miningBeneficiaryCalculator = miningBeneficiaryCalculator; @@ -112,8 +105,6 @@ protected AbstractBlockCreator( this.transactionPool = transactionPool; this.protocolContext = protocolContext; this.protocolSchedule = protocolSchedule; - this.parentHeader = parentHeader; - this.depositContractAddress = depositContractAddress; this.ethScheduler = ethScheduler; blockHeaderFunctions = ScheduleBasedBlockHeaderFunctions.create(protocolSchedule); } @@ -126,7 +117,7 @@ protected AbstractBlockCreator( * body, and will supply an empty Ommers list. * *

Once transactions have been selected and applied to a disposable/temporary world state, the - * block reward is paid to the relevant coinbase, and a sealable header is constucted. + * block reward is paid to the relevant coinbase, and a sealable header is constructed. * *

The sealableHeader is then provided to child instances for sealing (i.e. proof of work or * otherwise). @@ -136,21 +127,25 @@ protected AbstractBlockCreator( * @return a block with appropriately selected transactions, seals and ommers. */ @Override - public BlockCreationResult createBlock(final long timestamp) { - return createBlock(Optional.empty(), Optional.empty(), timestamp); + public BlockCreationResult createBlock(final long timestamp, final BlockHeader parentHeader) { + return createBlock(Optional.empty(), Optional.empty(), timestamp, parentHeader); } @Override public BlockCreationResult createBlock( - final List transactions, final List ommers, final long timestamp) { - return createBlock(Optional.of(transactions), Optional.of(ommers), timestamp); + final List transactions, + final List ommers, + final long timestamp, + final BlockHeader parentHeader) { + return createBlock(Optional.of(transactions), Optional.of(ommers), timestamp, parentHeader); } @Override public BlockCreationResult createBlock( final Optional> maybeTransactions, final Optional> maybeOmmers, - final long timestamp) { + final long timestamp, + final BlockHeader parentHeader) { return createBlock( maybeTransactions, maybeOmmers, @@ -158,7 +153,31 @@ public BlockCreationResult createBlock( Optional.empty(), Optional.empty(), timestamp, - true); + true, + parentHeader); + } + + @Override + public BlockCreationResult createEmptyWithdrawalsBlock( + final long timestamp, final BlockHeader parentHeader) { + throw new UnsupportedOperationException("Only used by BFT block creators"); + } + + public BlockCreationResult createBlock( + final Optional> maybeTransactions, + final Optional> maybeOmmers, + final Optional> maybeWithdrawals, + final long timestamp, + final BlockHeader parentHeader) { + return createBlock( + maybeTransactions, + maybeOmmers, + maybeWithdrawals, + Optional.empty(), + Optional.empty(), + timestamp, + true, + parentHeader); } protected BlockCreationResult createBlock( @@ -168,15 +187,23 @@ protected BlockCreationResult createBlock( final Optional maybePrevRandao, final Optional maybeParentBeaconBlockRoot, final long timestamp, - boolean rewardCoinbase) { + boolean rewardCoinbase, + final BlockHeader parentHeader) { + + final var timings = new BlockCreationTiming(); - try (final MutableWorldState disposableWorldState = duplicateWorldStateAtParent()) { + try (final MutableWorldState disposableWorldState = duplicateWorldStateAtParent(parentHeader)) { + timings.register("duplicateWorldState"); final ProtocolSpec newProtocolSpec = protocolSchedule.getForNextBlockHeader(parentHeader, timestamp); final ProcessableBlockHeader processableBlockHeader = createPendingBlockHeader( - timestamp, maybePrevRandao, maybeParentBeaconBlockRoot, newProtocolSpec); + timestamp, + maybePrevRandao, + maybeParentBeaconBlockRoot, + newProtocolSpec, + parentHeader); final Address miningBeneficiary = miningBeneficiaryCalculator.getMiningBeneficiary(processableBlockHeader.getNumber()); @@ -184,24 +211,21 @@ protected BlockCreationResult createBlock( final List ommers = maybeOmmers.orElse(selectOmmers()); - maybeParentBeaconBlockRoot.ifPresent( - bytes32 -> - ParentBeaconBlockRootHelper.storeParentBeaconBlockRoot( - disposableWorldState.updater(), timestamp, bytes32)); + newProtocolSpec + .getBlockHashProcessor() + .processBlockHashes( + protocolContext.getBlockchain(), disposableWorldState, processableBlockHeader); throwIfStopped(); final PluginTransactionSelector pluginTransactionSelector = - protocolContext - .getTransactionSelectorFactory() - .map(PluginTransactionSelectorFactory::create) - .orElseGet(() -> AllAcceptingTransactionSelector.INSTANCE); + miningParameters.getTransactionSelectionService().createPluginTransactionSelector(); final BlockAwareOperationTracer operationTracer = pluginTransactionSelector.getOperationTracer(); operationTracer.traceStartBlock(processableBlockHeader); - + timings.register("preTxsSelection"); final TransactionSelectionResults transactionResults = selectTransactions( processableBlockHeader, @@ -209,10 +233,10 @@ protected BlockCreationResult createBlock( maybeTransactions, miningBeneficiary, newProtocolSpec, - pluginTransactionSelector); - + pluginTransactionSelector, + parentHeader); transactionResults.logSelectionStats(); - + timings.register("txsSelection"); throwIfStopped(); final Optional maybeWithdrawalsProcessor = @@ -227,12 +251,21 @@ protected BlockCreationResult createBlock( throwIfStopped(); - final DepositsValidator depositsValidator = newProtocolSpec.getDepositsValidator(); - Optional> maybeDeposits = Optional.empty(); - if (depositsValidator instanceof DepositsValidator.AllowedDeposits - && depositContractAddress.isPresent()) { - maybeDeposits = Optional.of(findDepositsFromReceipts(transactionResults)); - } + // EIP-7685: process EL requests + final Optional requestProcessor = + newProtocolSpec.getRequestProcessorCoordinator(); + + ProcessRequestContext context = + new ProcessRequestContext( + processableBlockHeader, + disposableWorldState, + newProtocolSpec, + transactionResults.getReceipts(), + new CachingBlockHashLookup(processableBlockHeader, protocolContext.getBlockchain()), + operationTracer); + + Optional> maybeRequests = + requestProcessor.flatMap(processor -> processor.process(context)); throwIfStopped(); @@ -251,7 +284,8 @@ protected BlockCreationResult createBlock( throwIfStopped(); - final GasUsage usage = computeExcessBlobGas(transactionResults, newProtocolSpec); + final GasUsage usage = + computeExcessBlobGas(transactionResults, newProtocolSpec, parentHeader); throwIfStopped(); @@ -270,7 +304,7 @@ protected BlockCreationResult createBlock( withdrawalsCanBeProcessed ? BodyValidation.withdrawalsRoot(maybeWithdrawals.get()) : null) - .depositsRoot(maybeDeposits.map(BodyValidation::depositsRoot).orElse(null)); + .requestsRoot(maybeRequests.map(BodyValidation::requestsRoot).orElse(null)); if (usage != null) { builder.blobGasUsed(usage.used.toLong()).excessBlobGas(usage.excessBlobGas); } @@ -283,12 +317,12 @@ protected BlockCreationResult createBlock( withdrawalsCanBeProcessed ? maybeWithdrawals : Optional.empty(); final BlockBody blockBody = new BlockBody( - transactionResults.getSelectedTransactions(), ommers, withdrawals, maybeDeposits); + transactionResults.getSelectedTransactions(), ommers, withdrawals, maybeRequests); final Block block = new Block(blockHeader, blockBody); operationTracer.traceEndBlock(blockHeader, blockBody); - - return new BlockCreationResult(block, transactionResults); + timings.register("blockAssembled"); + return new BlockCreationResult(block, transactionResults, timings); } catch (final SecurityModuleException ex) { throw new IllegalStateException("Failed to create block signature", ex); } catch (final CancellationException | StorageException ex) { @@ -299,19 +333,12 @@ protected BlockCreationResult createBlock( } } - @VisibleForTesting - List findDepositsFromReceipts(final TransactionSelectionResults transactionResults) { - return transactionResults.getReceipts().stream() - .flatMap(receipt -> receipt.getLogsList().stream()) - .filter(log -> depositContractAddress.get().equals(log.getLogger())) - .map(DepositDecoder::decodeFromLog) - .toList(); - } - record GasUsage(BlobGas excessBlobGas, BlobGas used) {} private GasUsage computeExcessBlobGas( - final TransactionSelectionResults transactionResults, final ProtocolSpec newProtocolSpec) { + final TransactionSelectionResults transactionResults, + final ProtocolSpec newProtocolSpec, + final BlockHeader parentHeader) { if (newProtocolSpec.getFeeMarket().implementsDataFee()) { final var gasCalculator = newProtocolSpec.getGasCalculator(); @@ -336,7 +363,8 @@ private TransactionSelectionResults selectTransactions( final Optional> transactions, final Address miningBeneficiary, final ProtocolSpec protocolSpec, - final PluginTransactionSelector pluginTransactionSelector) + final PluginTransactionSelector pluginTransactionSelector, + final BlockHeader parentHeader) throws RuntimeException { final MainnetTransactionProcessor transactionProcessor = protocolSpec.getTransactionProcessor(); @@ -363,6 +391,7 @@ private TransactionSelectionResults selectTransactions( protocolSpec.getFeeMarket(), protocolSpec.getGasCalculator(), protocolSpec.getGasLimitCalculator(), + protocolSpec.getBlockHashProcessor(), pluginTransactionSelector, ethScheduler); @@ -373,7 +402,7 @@ private TransactionSelectionResults selectTransactions( } } - private MutableWorldState duplicateWorldStateAtParent() { + private MutableWorldState duplicateWorldStateAtParent(final BlockHeader parentHeader) { final Hash parentStateRoot = parentHeader.getStateRoot(); return protocolContext .getWorldStateArchive() @@ -397,7 +426,8 @@ private ProcessableBlockHeader createPendingBlockHeader( final long timestamp, final Optional maybePrevRandao, final Optional maybeParentBeaconBlockRoot, - final ProtocolSpec protocolSpec) { + final ProtocolSpec protocolSpec, + final BlockHeader parentHeader) { final long newBlockNumber = parentHeader.getNumber() + 1; long gasLimit = protocolSpec diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockScheduler.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockScheduler.java index bce8d480e5b..12246be0016 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockScheduler.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockScheduler.java @@ -37,23 +37,5 @@ public long waitUntilNextBlockCanBeMined(final BlockHeader parentHeader) public abstract BlockCreationTimeResult getNextTimestamp(final BlockHeader parentHeader); - public static class BlockCreationTimeResult { - - private final long timestampForHeader; - private final long millisecondsUntilValid; - - public BlockCreationTimeResult( - final long timestampForHeader, final long millisecondsUntilValid) { - this.timestampForHeader = timestampForHeader; - this.millisecondsUntilValid = millisecondsUntilValid; - } - - public long getTimestampForHeader() { - return timestampForHeader; - } - - public long getMillisecondsUntilValid() { - return millisecondsUntilValid; - } - } + public record BlockCreationTimeResult(long timestampForHeader, long millisecondsUntilValid) {} } diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractMinerExecutor.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractMinerExecutor.java index 451af2df12b..af71938d1d5 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractMinerExecutor.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractMinerExecutor.java @@ -34,7 +34,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import org.apache.tuweni.bytes.Bytes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -98,10 +97,6 @@ public abstract M createMiner( final Subscribers ethHashObservers, final BlockHeader parentHeader); - public void setExtraData(final Bytes extraData) { - miningParameters.setExtraData(extraData.copy()); - } - public void setMinTransactionGasPrice(final Wei minTransactionGasPrice) { miningParameters.setMinTransactionGasPrice(minTransactionGasPrice); } @@ -110,6 +105,10 @@ public Wei getMinTransactionGasPrice() { return miningParameters.getMinTransactionGasPrice(); } + public Wei getMinPriorityFeePerGas() { + return miningParameters.getMinPriorityFeePerGas(); + } + public abstract Optional

getCoinbase(); public void changeTargetGasLimit(final Long newTargetGasLimit) { diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractMiningCoordinator.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractMiningCoordinator.java index 43475bc58b6..9c939b91bc8 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractMiningCoordinator.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractMiningCoordinator.java @@ -32,8 +32,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -import org.apache.tuweni.bytes.Bytes; - public abstract class AbstractMiningCoordinator< M extends BlockMiner> implements BlockAddedObserver, MiningCoordinator { @@ -208,8 +206,8 @@ public Wei getMinTransactionGasPrice() { } @Override - public void setExtraData(final Bytes extraData) { - executor.setExtraData(extraData); + public Wei getMinPriorityFeePerGas() { + return executor.getMinPriorityFeePerGas(); } @Override diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockCreationTiming.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockCreationTiming.java new file mode 100644 index 00000000000..e5fe3e679d1 --- /dev/null +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockCreationTiming.java @@ -0,0 +1,66 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.blockcreation; + +import java.time.Duration; +import java.time.Instant; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.google.common.base.Stopwatch; + +public class BlockCreationTiming { + private final Map timing = new LinkedHashMap<>(); + private final Stopwatch stopwatch; + private final Instant startedAt = Instant.now(); + + public BlockCreationTiming() { + this.stopwatch = Stopwatch.createStarted(); + } + + public void register(final String step) { + timing.put(step, stopwatch.elapsed()); + } + + public void registerAll(final BlockCreationTiming subTiming) { + final var offset = Duration.between(startedAt, subTiming.startedAt); + for (final var entry : subTiming.timing.entrySet()) { + timing.put(entry.getKey(), offset.plus(entry.getValue())); + } + } + + public Duration end(final String step) { + final var elapsed = stopwatch.stop().elapsed(); + timing.put(step, elapsed); + return elapsed; + } + + @Override + public String toString() { + final var sb = new StringBuilder("started at " + startedAt + ", "); + + var prevDuration = Duration.ZERO; + for (final var entry : timing.entrySet()) { + sb.append(entry.getKey()) + .append("=") + .append(entry.getValue().minus(prevDuration).toMillis()) + .append("ms, "); + prevDuration = entry.getValue(); + } + sb.delete(sb.length() - 2, sb.length()); + + return sb.toString(); + } +} diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockCreator.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockCreator.java index 5931d49f369..988cc7e7635 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockCreator.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockCreator.java @@ -26,11 +26,15 @@ public interface BlockCreator { class BlockCreationResult { private final Block block; private final TransactionSelectionResults transactionSelectionResults; + private final BlockCreationTiming blockCreationTiming; public BlockCreationResult( - final Block block, final TransactionSelectionResults transactionSelectionResults) { + final Block block, + final TransactionSelectionResults transactionSelectionResults, + final BlockCreationTiming timings) { this.block = block; this.transactionSelectionResults = transactionSelectionResults; + this.blockCreationTiming = timings; } public Block getBlock() { @@ -40,15 +44,26 @@ public Block getBlock() { public TransactionSelectionResults getTransactionSelectionResults() { return transactionSelectionResults; } + + public BlockCreationTiming getBlockCreationTimings() { + return blockCreationTiming; + } } - BlockCreationResult createBlock(final long timestamp); + BlockCreationResult createBlock(final long timestamp, final BlockHeader parentHeader); + + BlockCreationResult createEmptyWithdrawalsBlock( + final long timestamp, final BlockHeader parentHeader); BlockCreationResult createBlock( - final List transactions, final List ommers, final long timestamp); + final List transactions, + final List ommers, + final long timestamp, + final BlockHeader parentHeader); BlockCreationResult createBlock( final Optional> maybeTransactions, final Optional> maybeOmmers, - final long timestamp); + final long timestamp, + final BlockHeader parentHeader); } diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockMiner.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockMiner.java index 34ac04bf880..f51451f2930 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockMiner.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockMiner.java @@ -29,10 +29,8 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.CancellationException; -import java.util.concurrent.TimeUnit; import java.util.function.Function; -import com.google.common.base.Stopwatch; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -110,8 +108,8 @@ public BlockCreationResult createBlock( final List transactions, final List ommers) { final BlockCreator blockCreator = this.blockCreatorFactory.apply(parentHeader); - final long timestamp = scheduler.getNextTimestamp(parentHeader).getTimestampForHeader(); - return blockCreator.createBlock(transactions, ommers, timestamp); + final long timestamp = scheduler.getNextTimestamp(parentHeader).timestampForHeader(); + return blockCreator.createBlock(transactions, ommers, timestamp, parentHeader); } /** @@ -123,7 +121,7 @@ public BlockCreationResult createBlock( */ public BlockCreationResult createBlock(final BlockHeader parentHeader, final long timestamp) { final BlockCreator blockCreator = this.blockCreatorFactory.apply(parentHeader); - return blockCreator.createBlock(Optional.empty(), Optional.empty(), timestamp); + return blockCreator.createBlock(Optional.empty(), Optional.empty(), timestamp, parentHeader); } protected boolean shouldImportBlock(final Block block) throws InterruptedException { @@ -133,13 +131,19 @@ protected boolean shouldImportBlock(final Block block) throws InterruptedExcepti protected boolean mineBlock() throws InterruptedException { // Ensure the block is allowed to be mined - i.e. the timestamp on the new block is sufficiently // ahead of the parent, and still within allowable clock tolerance. + final var timing = new BlockCreationTiming(); + LOG.trace("Started a mining operation."); final long newBlockTimestamp = scheduler.waitUntilNextBlockCanBeMined(parentHeader); + timing.register("protocolWait"); - final Stopwatch stopwatch = Stopwatch.createStarted(); LOG.trace("Mining a new block with timestamp {}", newBlockTimestamp); - final Block block = minerBlockCreator.createBlock(newBlockTimestamp).getBlock(); + + final var blockCreationResult = minerBlockCreator.createBlock(newBlockTimestamp, parentHeader); + timing.registerAll(blockCreationResult.getBlockCreationTimings()); + + final Block block = blockCreationResult.getBlock(); LOG.trace( "Block created, importing to local chain, block includes {} transactions", block.getBody().getTransactions().size()); @@ -152,26 +156,31 @@ protected boolean mineBlock() throws InterruptedException { protocolSchedule.getByBlockHeader(block.getHeader()).getBlockImporter(); final BlockImportResult blockImportResult = importer.importBlock(protocolContext, block, HeaderValidationMode.FULL); + timing.register("importingBlock"); if (blockImportResult.isImported()) { notifyNewBlockListeners(block); - final double taskTimeInSec = stopwatch.elapsed(TimeUnit.MILLISECONDS) / 1000.0; - LOG.info( - String.format( - "Produced #%,d / %d tx / %d om / %,d (%01.1f%%) gas / (%s) in %01.3fs", - block.getHeader().getNumber(), - block.getBody().getTransactions().size(), - block.getBody().getOmmers().size(), - block.getHeader().getGasUsed(), - (block.getHeader().getGasUsed() * 100.0) / block.getHeader().getGasLimit(), - block.getHash(), - taskTimeInSec)); + timing.register("notifyListeners"); + logProducedBlock(block, timing); } else { LOG.error("Illegal block mined, could not be imported to local chain."); } - return blockImportResult.isImported(); } + private void logProducedBlock(final Block block, final BlockCreationTiming blockCreationTiming) { + LOG.info( + String.format( + "Produced #%,d / %d tx / %d om / %,d (%01.1f%%) gas / (%s) in %01.3fs / Timing(%s)", + block.getHeader().getNumber(), + block.getBody().getTransactions().size(), + block.getBody().getOmmers().size(), + block.getHeader().getGasUsed(), + (block.getHeader().getGasUsed() * 100.0) / block.getHeader().getGasLimit(), + block.getHash(), + blockCreationTiming.end("log").toMillis() / 1000.0, + blockCreationTiming)); + } + public void cancel() { minerBlockCreator.cancel(); } diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/DefaultBlockScheduler.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/DefaultBlockScheduler.java index becde4a9811..327cba4a85a 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/DefaultBlockScheduler.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/DefaultBlockScheduler.java @@ -18,21 +18,29 @@ import java.time.Clock; import java.util.concurrent.TimeUnit; +import java.util.function.ToLongFunction; import com.google.common.annotations.VisibleForTesting; public class DefaultBlockScheduler extends AbstractBlockScheduler { private final long acceptableClockDriftSeconds; - private final long minimumSecondsSinceParent; + private final ToLongFunction calcMinimumSecondsSinceParent; public DefaultBlockScheduler( - final long minimumSecondsSinceParent, + final long calcMinimumSecondsSinceParent, + final long acceptableClockDriftSeconds, + final Clock clock) { + this((bh) -> calcMinimumSecondsSinceParent, acceptableClockDriftSeconds, clock); + } + + protected DefaultBlockScheduler( + final ToLongFunction calcMinimumSecondsSinceParent, final long acceptableClockDriftSeconds, final Clock clock) { super(clock); this.acceptableClockDriftSeconds = acceptableClockDriftSeconds; - this.minimumSecondsSinceParent = minimumSecondsSinceParent; + this.calcMinimumSecondsSinceParent = calcMinimumSecondsSinceParent; } @Override @@ -42,11 +50,12 @@ public BlockCreationTimeResult getNextTimestamp(final BlockHeader parentHeader) final long now = TimeUnit.SECONDS.convert(msSinceEpoch, TimeUnit.MILLISECONDS); final long parentTimestamp = parentHeader.getTimestamp(); - final long nextHeaderTimestamp = Long.max(parentTimestamp + minimumSecondsSinceParent, now); + final long minSecondsSinceParent = calcMinimumSecondsSinceParent.applyAsLong(parentHeader); + final long nextHeaderTimestamp = Long.max(parentTimestamp + minSecondsSinceParent, now); final long earliestBlockTransmissionTime = nextHeaderTimestamp - acceptableClockDriftSeconds; - final long msUntilBlocKTransmission = (earliestBlockTransmissionTime * 1000) - msSinceEpoch; + final long msUntilBlockTransmission = (earliestBlockTransmissionTime * 1000) - msSinceEpoch; - return new BlockCreationTimeResult(nextHeaderTimestamp, Math.max(0, msUntilBlocKTransmission)); + return new BlockCreationTimeResult(nextHeaderTimestamp, Math.max(0, msUntilBlockTransmission)); } } diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/MiningCoordinator.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/MiningCoordinator.java index 68353f7ae7e..d1a91083b63 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/MiningCoordinator.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/MiningCoordinator.java @@ -26,8 +26,6 @@ import java.util.List; import java.util.Optional; -import org.apache.tuweni.bytes.Bytes; - public interface MiningCoordinator { void start(); @@ -58,7 +56,7 @@ default void onPauseMining() {} Wei getMinTransactionGasPrice(); - void setExtraData(Bytes extraData); + Wei getMinPriorityFeePerGas(); default void setCoinbase(final Address coinbase) { throw new UnsupportedOperationException( diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/NoopMiningCoordinator.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/NoopMiningCoordinator.java index ab0a5c3d717..6eb4927226c 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/NoopMiningCoordinator.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/NoopMiningCoordinator.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.blockcreation; import org.hyperledger.besu.datatypes.Address; @@ -25,8 +24,6 @@ import java.util.List; import java.util.Optional; -import org.apache.tuweni.bytes.Bytes; - public class NoopMiningCoordinator implements MiningCoordinator { private final MiningParameters miningParameters; @@ -65,7 +62,9 @@ public Wei getMinTransactionGasPrice() { } @Override - public void setExtraData(final Bytes extraData) {} + public Wei getMinPriorityFeePerGas() { + return miningParameters.getMinPriorityFeePerGas(); + } @Override public Optional
getCoinbase() { diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreator.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreator.java index 9cf12920701..d0042a5ad96 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreator.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreator.java @@ -45,7 +45,6 @@ public PoWBlockCreator( final ProtocolContext protocolContext, final ProtocolSchedule protocolSchedule, final PoWSolver nonceSolver, - final BlockHeader parentHeader, final EthScheduler ethScheduler) { super( miningParameters, @@ -54,8 +53,6 @@ public PoWBlockCreator( transactionPool, protocolContext, protocolSchedule, - parentHeader, - Optional.empty(), ethScheduler); this.nonceSolver = nonceSolver; diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/PoWMinerExecutor.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/PoWMinerExecutor.java index 78524e3035e..b14eb193405 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/PoWMinerExecutor.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/PoWMinerExecutor.java @@ -93,7 +93,6 @@ public PoWBlockMiner createMiner( protocolContext, protocolSchedule, solver, - parentHeader, ethScheduler); return new PoWBlockMiner( diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockSelectionContext.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockSelectionContext.java index 8204b3d9e43..f8bf6d50c5c 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockSelectionContext.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockSelectionContext.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -20,6 +20,7 @@ import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; +import org.hyperledger.besu.ethereum.mainnet.blockhash.BlockHashProcessor; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -27,7 +28,8 @@ public record BlockSelectionContext( MiningParameters miningParameters, GasCalculator gasCalculator, GasLimitCalculator gasLimitCalculator, - ProcessableBlockHeader processableBlockHeader, + BlockHashProcessor blockHashProcessor, + ProcessableBlockHeader pendingBlockHeader, FeeMarket feeMarket, Wei blobGasPrice, Address miningBeneficiary, diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockTransactionSelector.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockTransactionSelector.java index 4a0707e76f7..3d56c1fc07b 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockTransactionSelector.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockTransactionSelector.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.blockcreation.txselection; import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.BLOCK_SELECTION_TIMEOUT; +import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.INVALID_TX_EVALUATION_TOO_LONG; import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.SELECTED; import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.TX_EVALUATION_TOO_LONG; @@ -23,6 +24,7 @@ import org.hyperledger.besu.ethereum.GasLimitCalculator; import org.hyperledger.besu.ethereum.blockcreation.txselection.selectors.AbstractTransactionSelector; import org.hyperledger.besu.ethereum.blockcreation.txselection.selectors.BlobPriceTransactionSelector; +import org.hyperledger.besu.ethereum.blockcreation.txselection.selectors.BlobSizeTransactionSelector; import org.hyperledger.besu.ethereum.blockcreation.txselection.selectors.BlockSizeTransactionSelector; import org.hyperledger.besu.ethereum.blockcreation.txselection.selectors.MinPriorityFeePerGasTransactionSelector; import org.hyperledger.besu.ethereum.blockcreation.txselection.selectors.PriceTransactionSelector; @@ -39,11 +41,12 @@ import org.hyperledger.besu.ethereum.mainnet.AbstractBlockProcessor; import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor; import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; +import org.hyperledger.besu.ethereum.mainnet.blockhash.BlockHashProcessor; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; -import org.hyperledger.besu.ethereum.vm.BlockHashLookup; import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup; import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.hyperledger.besu.plugin.data.TransactionSelectionResult; import org.hyperledger.besu.plugin.services.tracer.BlockAwareOperationTracer; @@ -114,6 +117,7 @@ public BlockTransactionSelector( final FeeMarket feeMarket, final GasCalculator gasCalculator, final GasLimitCalculator gasLimitCalculator, + final BlockHashProcessor blockHashProcessor, final PluginTransactionSelector pluginTransactionSelector, final EthScheduler ethScheduler) { this.transactionProcessor = transactionProcessor; @@ -127,6 +131,7 @@ public BlockTransactionSelector( miningParameters, gasCalculator, gasLimitCalculator, + blockHashProcessor, processableBlockHeader, feeMarket, blobGasPrice, @@ -136,13 +141,14 @@ public BlockTransactionSelector( this.pluginTransactionSelector = pluginTransactionSelector; this.pluginOperationTracer = pluginTransactionSelector.getOperationTracer(); blockWorldStateUpdater = worldState.updater(); - blockTxsSelectionMaxTime = miningParameters.getUnstable().getBlockTxsSelectionMaxTime(); + blockTxsSelectionMaxTime = miningParameters.getBlockTxsSelectionMaxTime(); } private List createTransactionSelectors( final BlockSelectionContext context) { return List.of( new BlockSizeTransactionSelector(context), + new BlobSizeTransactionSelector(context), new PriceTransactionSelector(context), new BlobPriceTransactionSelector(context), new MinPriorityFeePerGasTransactionSelector(context), @@ -161,7 +167,7 @@ private List createTransactionSelectors( public TransactionSelectionResults buildTransactionListForBlock() { LOG.atDebug() .setMessage("Transaction pool stats {}") - .addArgument(blockSelectionContext.transactionPool().logStats()) + .addArgument(blockSelectionContext.transactionPool()::logStats) .log(); timeLimitedSelection(); LOG.atTrace() @@ -192,7 +198,7 @@ private void timeLimitedSelection() { isTimeout.set(true); } LOG.warn( - "Interrupting transaction selection since it is taking more than the max configured time of " + "Interrupting the selection of transactions for block inclusion as it exceeds the maximum configured duration of " + blockTxsSelectionMaxTime + "ms", e); @@ -228,26 +234,43 @@ private TransactionSelectionResult evaluateTransaction( final PendingTransaction pendingTransaction) { checkCancellation(); - final Stopwatch evaluationTimer = Stopwatch.createStarted(); + final TransactionEvaluationContext evaluationContext = + createTransactionEvaluationContext(pendingTransaction); - TransactionSelectionResult selectionResult = evaluatePreProcessing(pendingTransaction); + TransactionSelectionResult selectionResult = evaluatePreProcessing(evaluationContext); if (!selectionResult.selected()) { - return handleTransactionNotSelected(pendingTransaction, selectionResult, evaluationTimer); + return handleTransactionNotSelected(evaluationContext, selectionResult); } final WorldUpdater txWorldStateUpdater = blockWorldStateUpdater.updater(); final TransactionProcessingResult processingResult = processTransaction(pendingTransaction, txWorldStateUpdater); - var postProcessingSelectionResult = - evaluatePostProcessing(pendingTransaction, processingResult); + var postProcessingSelectionResult = evaluatePostProcessing(evaluationContext, processingResult); if (postProcessingSelectionResult.selected()) { - return handleTransactionSelected( - pendingTransaction, processingResult, txWorldStateUpdater, evaluationTimer); + return handleTransactionSelected(evaluationContext, processingResult, txWorldStateUpdater); } return handleTransactionNotSelected( - pendingTransaction, postProcessingSelectionResult, txWorldStateUpdater, evaluationTimer); + evaluationContext, postProcessingSelectionResult, txWorldStateUpdater); + } + + private TransactionEvaluationContext createTransactionEvaluationContext( + final PendingTransaction pendingTransaction) { + final Wei transactionGasPriceInBlock = + blockSelectionContext + .feeMarket() + .getTransactionPriceCalculator() + .price( + pendingTransaction.getTransaction(), + blockSelectionContext.pendingBlockHeader().getBaseFee()); + + return new TransactionEvaluationContext( + blockSelectionContext.pendingBlockHeader(), + pendingTransaction, + Stopwatch.createStarted(), + transactionGasPriceInBlock, + blockSelectionContext.miningParameters().getMinTransactionGasPrice()); } /** @@ -256,21 +279,20 @@ private TransactionSelectionResult evaluateTransaction( * it then processes it through external selectors. If the transaction is selected by all * selectors, it returns SELECTED. * - * @param pendingTransaction The transaction to be evaluated. + * @param evaluationContext The current selection session data. * @return The result of the transaction selection process. */ private TransactionSelectionResult evaluatePreProcessing( - final PendingTransaction pendingTransaction) { + final TransactionEvaluationContext evaluationContext) { for (var selector : transactionSelectors) { TransactionSelectionResult result = - selector.evaluateTransactionPreProcessing( - pendingTransaction, transactionSelectionResults); + selector.evaluateTransactionPreProcessing(evaluationContext, transactionSelectionResults); if (!result.equals(SELECTED)) { return result; } } - return pluginTransactionSelector.evaluateTransactionPreProcessing(pendingTransaction); + return pluginTransactionSelector.evaluateTransactionPreProcessing(evaluationContext); } /** @@ -279,24 +301,24 @@ private TransactionSelectionResult evaluatePreProcessing( * whether the transaction should be included in a block. If the transaction is selected by all * selectors, it returns SELECTED. * - * @param pendingTransaction The transaction to be evaluated. + * @param evaluationContext The current selection session data. * @param processingResult The result of the transaction processing. * @return The result of the transaction selection process. */ private TransactionSelectionResult evaluatePostProcessing( - final PendingTransaction pendingTransaction, + final TransactionEvaluationContext evaluationContext, final TransactionProcessingResult processingResult) { for (var selector : transactionSelectors) { TransactionSelectionResult result = selector.evaluateTransactionPostProcessing( - pendingTransaction, transactionSelectionResults, processingResult); + evaluationContext, transactionSelectionResults, processingResult); if (!result.equals(SELECTED)) { return result; } } return pluginTransactionSelector.evaluateTransactionPostProcessing( - pendingTransaction, processingResult); + evaluationContext, processingResult); } /** @@ -309,11 +331,10 @@ private TransactionSelectionResult evaluatePostProcessing( private TransactionProcessingResult processTransaction( final PendingTransaction pendingTransaction, final WorldUpdater worldStateUpdater) { final BlockHashLookup blockHashLookup = - new CachingBlockHashLookup(blockSelectionContext.processableBlockHeader(), blockchain); + new CachingBlockHashLookup(blockSelectionContext.pendingBlockHeader(), blockchain); return transactionProcessor.processTransaction( - blockchain, worldStateUpdater, - blockSelectionContext.processableBlockHeader(), + blockSelectionContext.pendingBlockHeader(), pendingTransaction.getTransaction(), blockSelectionContext.miningBeneficiary(), pluginOperationTracer, @@ -328,18 +349,16 @@ private TransactionProcessingResult processTransaction( * receipt, updating the TransactionSelectionResults with the selected transaction, and notifying * the external transaction selector. * - * @param pendingTransaction The pending transaction. + * @param evaluationContext The current selection session data. * @param processingResult The result of the transaction processing. * @param txWorldStateUpdater The world state updater. - * @param evaluationTimer tracks the evaluation elapsed time * @return The result of the transaction selection process. */ private TransactionSelectionResult handleTransactionSelected( - final PendingTransaction pendingTransaction, + final TransactionEvaluationContext evaluationContext, final TransactionProcessingResult processingResult, - final WorldUpdater txWorldStateUpdater, - final Stopwatch evaluationTimer) { - final Transaction transaction = pendingTransaction.getTransaction(); + final WorldUpdater txWorldStateUpdater) { + final Transaction transaction = evaluationContext.getTransaction(); final long gasUsedByTransaction = transaction.getGasLimit() - processingResult.getGasRemaining(); @@ -363,7 +382,7 @@ private TransactionSelectionResult handleTransactionSelected( transaction.getType(), processingResult, worldState, cumulativeGasUsed); transactionSelectionResults.updateSelected( - pendingTransaction.getTransaction(), receipt, gasUsedByTransaction, blobGasUsed); + transaction, receipt, gasUsedByTransaction, blobGasUsed); } } @@ -371,39 +390,18 @@ private TransactionSelectionResult handleTransactionSelected( // even if this tx passed all the checks, it is too late to include it in this block, // so we need to treat it as not selected - // check if this tx took too much to evaluate, and in case remove it from the pool - final TransactionSelectionResult timeoutSelectionResult; - if (evaluationTimer.elapsed(TimeUnit.MILLISECONDS) > blockTxsSelectionMaxTime) { - LOG.atWarn() - .setMessage( - "Transaction {} is too late for inclusion, evaluated in {} that is over the max limit of {}ms" - + ", removing it from the pool") - .addArgument(transaction::toTraceLog) - .addArgument(evaluationTimer) - .addArgument(blockTxsSelectionMaxTime) - .log(); - timeoutSelectionResult = TX_EVALUATION_TOO_LONG; - } else { - LOG.atTrace() - .setMessage("Transaction {} is too late for inclusion") - .addArgument(transaction::toTraceLog) - .addArgument(evaluationTimer) - .log(); - timeoutSelectionResult = BLOCK_SELECTION_TIMEOUT; - } - // do not rely on the presence of this result, since by the time it is added, the code // reading it could have been already executed by another thread return handleTransactionNotSelected( - pendingTransaction, timeoutSelectionResult, txWorldStateUpdater, evaluationTimer); + evaluationContext, BLOCK_SELECTION_TIMEOUT, txWorldStateUpdater); } - pluginTransactionSelector.onTransactionSelected(pendingTransaction, processingResult); + pluginTransactionSelector.onTransactionSelected(evaluationContext, processingResult); blockWorldStateUpdater = worldState.updater(); LOG.atTrace() .setMessage("Selected {} for block creation, evaluated in {}") .addArgument(transaction::toTraceLog) - .addArgument(evaluationTimer) + .addArgument(evaluationContext.getPendingTransaction()) .log(); return SELECTED; } @@ -413,36 +411,73 @@ private TransactionSelectionResult handleTransactionSelected( * TransactionSelectionResults with the unselected transaction, and notifies the external * transaction selector. * - * @param pendingTransaction The unselected pending transaction. + * @param evaluationContext The current selection session data. * @param selectionResult The result of the transaction selection process. - * @param evaluationTimer tracks the evaluation elapsed time * @return The result of the transaction selection process. */ private TransactionSelectionResult handleTransactionNotSelected( - final PendingTransaction pendingTransaction, - final TransactionSelectionResult selectionResult, - final Stopwatch evaluationTimer) { - - transactionSelectionResults.updateNotSelected( - pendingTransaction.getTransaction(), selectionResult); - pluginTransactionSelector.onTransactionNotSelected(pendingTransaction, selectionResult); + final TransactionEvaluationContext evaluationContext, + final TransactionSelectionResult selectionResult) { + + final var pendingTransaction = evaluationContext.getPendingTransaction(); + + // check if this tx took too much to evaluate, and in case it was invalid remove it from the + // pool, otherwise penalize it. + final TransactionSelectionResult actualResult = + isTimeout.get() + ? transactionTookTooLong(evaluationContext, selectionResult) + ? selectionResult.discard() + ? INVALID_TX_EVALUATION_TOO_LONG + : TX_EVALUATION_TOO_LONG + : BLOCK_SELECTION_TIMEOUT + : selectionResult; + + transactionSelectionResults.updateNotSelected(evaluationContext.getTransaction(), actualResult); + pluginTransactionSelector.onTransactionNotSelected(evaluationContext, actualResult); LOG.atTrace() - .setMessage("Not selected {} for block creation with result {}, evaluated in {}") + .setMessage( + "Not selected {} for block creation with result {} (original result {}), evaluated in {}") .addArgument(pendingTransaction::toTraceLog) + .addArgument(actualResult) .addArgument(selectionResult) - .addArgument(evaluationTimer) + .addArgument(evaluationContext.getEvaluationTimer()) + .log(); + + return actualResult; + } + + private boolean transactionTookTooLong( + final TransactionEvaluationContext evaluationContext, + final TransactionSelectionResult selectionResult) { + final var evaluationTimer = evaluationContext.getEvaluationTimer(); + if (evaluationTimer.elapsed(TimeUnit.MILLISECONDS) > blockTxsSelectionMaxTime) { + LOG.atWarn() + .setMessage( + "Transaction {} is too late for inclusion, with result {}, evaluated in {} that is over the max limit of {}ms" + + ", {}") + .addArgument(evaluationContext.getPendingTransaction()::getHash) + .addArgument(selectionResult) + .addArgument(evaluationTimer) + .addArgument(blockTxsSelectionMaxTime) + .addArgument( + selectionResult.discard() ? "removing it from the pool" : "penalizing it in the pool") + .log(); + return true; + } + LOG.atTrace() + .setMessage("Transaction {} is too late for inclusion") + .addArgument(evaluationContext.getPendingTransaction()::toTraceLog) .log(); - return selectionResult; + return false; } private TransactionSelectionResult handleTransactionNotSelected( - final PendingTransaction pendingTransaction, + final TransactionEvaluationContext evaluationContext, final TransactionSelectionResult selectionResult, - final WorldUpdater txWorldStateUpdater, - final Stopwatch evaluationTimer) { + final WorldUpdater txWorldStateUpdater) { txWorldStateUpdater.revert(); - return handleTransactionNotSelected(pendingTransaction, selectionResult, evaluationTimer); + return handleTransactionNotSelected(evaluationContext, selectionResult); } private void checkCancellation() { diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/TransactionEvaluationContext.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/TransactionEvaluationContext.java new file mode 100644 index 00000000000..7fce600a1a6 --- /dev/null +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/TransactionEvaluationContext.java @@ -0,0 +1,74 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.blockcreation.txselection; + +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; +import org.hyperledger.besu.plugin.data.ProcessableBlockHeader; + +import com.google.common.base.Stopwatch; + +public class TransactionEvaluationContext + implements org.hyperledger.besu.plugin.services.txselection.TransactionEvaluationContext< + PendingTransaction> { + private final ProcessableBlockHeader pendingBlockHeader; + private final PendingTransaction pendingTransaction; + private final Stopwatch evaluationTimer; + private final Wei transactionGasPrice; + private final Wei minGasPrice; + + public TransactionEvaluationContext( + final ProcessableBlockHeader pendingBlockHeader, + final PendingTransaction pendingTransaction, + final Stopwatch evaluationTimer, + final Wei transactionGasPrice, + final Wei minGasPrice) { + this.pendingBlockHeader = pendingBlockHeader; + this.pendingTransaction = pendingTransaction; + this.evaluationTimer = evaluationTimer; + this.transactionGasPrice = transactionGasPrice; + this.minGasPrice = minGasPrice; + } + + public Transaction getTransaction() { + return pendingTransaction.getTransaction(); + } + + @Override + public ProcessableBlockHeader getPendingBlockHeader() { + return pendingBlockHeader; + } + + @Override + public PendingTransaction getPendingTransaction() { + return pendingTransaction; + } + + @Override + public Stopwatch getEvaluationTimer() { + return evaluationTimer; + } + + @Override + public Wei getTransactionGasPrice() { + return transactionGasPrice; + } + + @Override + public Wei getMinGasPrice() { + return minGasPrice; + } +} diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/TransactionSelectionResults.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/TransactionSelectionResults.java index dcbd871aaec..5977b2f7b3e 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/TransactionSelectionResults.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/TransactionSelectionResults.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -39,6 +39,7 @@ public class TransactionSelectionResults { private final Map> transactionsByType = new EnumMap<>(TransactionType.class); private final List receipts = Lists.newArrayList(); + /** * Access to this field needs to be guarded, since it is possible to read it while another * processing thread is writing, when the selection time is over. diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/AbstractTransactionSelector.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/AbstractTransactionSelector.java index 205e803e820..afb426e90b6 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/AbstractTransactionSelector.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/AbstractTransactionSelector.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,8 +15,8 @@ package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors; import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext; +import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionEvaluationContext; import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults; -import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.plugin.data.TransactionSelectionResult; @@ -34,25 +34,25 @@ public AbstractTransactionSelector(final BlockSelectionContext context) { /** * Evaluates a transaction in the context of other transactions in the same block. * - * @param pendingTransaction The transaction to be evaluated within a block. + * @param evaluationContext The current selection session data. * @param blockTransactionResults The results of other transaction evaluations in the same block. * @return The result of the transaction evaluation */ public abstract TransactionSelectionResult evaluateTransactionPreProcessing( - final PendingTransaction pendingTransaction, + final TransactionEvaluationContext evaluationContext, final TransactionSelectionResults blockTransactionResults); /** * Evaluates a transaction considering other transactions in the same block and a transaction * processing result. * - * @param pendingTransaction The transaction to be evaluated. + * @param evaluationContext The current selection session data. * @param blockTransactionResults The results of other transaction evaluations in the same block. * @param processingResult The result of transaction processing. * @return The result of the transaction evaluation */ public abstract TransactionSelectionResult evaluateTransactionPostProcessing( - final PendingTransaction pendingTransaction, + final TransactionEvaluationContext evaluationContext, final TransactionSelectionResults blockTransactionResults, final TransactionProcessingResult processingResult); } diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/AllAcceptingTransactionSelector.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/AllAcceptingTransactionSelector.java deleted file mode 100644 index 9032ddca575..00000000000 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/AllAcceptingTransactionSelector.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors; - -import org.hyperledger.besu.datatypes.PendingTransaction; -import org.hyperledger.besu.plugin.data.TransactionProcessingResult; -import org.hyperledger.besu.plugin.data.TransactionSelectionResult; -import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelector; - -/** A TransactionSelector that unconditionally selects all transactions. */ -public class AllAcceptingTransactionSelector implements PluginTransactionSelector { - public static final AllAcceptingTransactionSelector INSTANCE = - new AllAcceptingTransactionSelector(); - - private AllAcceptingTransactionSelector() {} - - /** - * Always selects the transaction in the pre-processing stage. - * - * @param pendingTransaction The transaction to be evaluated. - * @return Always SELECTED. - */ - @Override - public TransactionSelectionResult evaluateTransactionPreProcessing( - final PendingTransaction pendingTransaction) { - return TransactionSelectionResult.SELECTED; - } - - /** - * Always selects the transaction in the post-processing stage. - * - * @param pendingTransaction The transaction to be evaluated. - * @param processingResult The result of the transaction processing. - * @return Always SELECTED. - */ - @Override - public TransactionSelectionResult evaluateTransactionPostProcessing( - final PendingTransaction pendingTransaction, - final TransactionProcessingResult processingResult) { - return TransactionSelectionResult.SELECTED; - } -} diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlobPriceTransactionSelector.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlobPriceTransactionSelector.java index 56a17c28434..57d4e7dcafa 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlobPriceTransactionSelector.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlobPriceTransactionSelector.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,9 +15,9 @@ package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors; import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext; +import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionEvaluationContext; import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults; import org.hyperledger.besu.ethereum.core.Transaction; -import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.plugin.data.TransactionSelectionResult; @@ -39,14 +39,15 @@ public BlobPriceTransactionSelector(final BlockSelectionContext context) { /** * Evaluates a transaction considering its blob price. * - * @param pendingTransaction The transaction to be evaluated. + * @param evaluationContext The current selection session data. * @param ignored The results of other transaction evaluations in the same block. * @return The result of the transaction selection. */ @Override public TransactionSelectionResult evaluateTransactionPreProcessing( - final PendingTransaction pendingTransaction, final TransactionSelectionResults ignored) { - if (transactionBlobPriceBelowMin(pendingTransaction.getTransaction())) { + final TransactionEvaluationContext evaluationContext, + final TransactionSelectionResults ignored) { + if (transactionBlobPriceBelowMin(evaluationContext.getTransaction())) { return TransactionSelectionResult.BLOB_PRICE_BELOW_CURRENT_MIN; } return TransactionSelectionResult.SELECTED; @@ -54,7 +55,7 @@ public TransactionSelectionResult evaluateTransactionPreProcessing( @Override public TransactionSelectionResult evaluateTransactionPostProcessing( - final PendingTransaction pendingTransaction, + final TransactionEvaluationContext evaluationContext, final TransactionSelectionResults blockTransactionResults, final TransactionProcessingResult processingResult) { // All necessary checks were done in the pre-processing method, so nothing to do here. diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlobSizeTransactionSelector.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlobSizeTransactionSelector.java new file mode 100644 index 00000000000..9a4c83e9625 --- /dev/null +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlobSizeTransactionSelector.java @@ -0,0 +1,95 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors; + +import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext; +import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionEvaluationContext; +import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults; +import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; +import org.hyperledger.besu.plugin.data.TransactionSelectionResult; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class extends AbstractTransactionSelector and provides a specific implementation for + * evaluating transactions based on blobs size. It checks if a transaction supports blobs, and if + * so, checks that there is enough remaining blob gas in the block to fit the blobs of the tx. + */ +public class BlobSizeTransactionSelector extends AbstractTransactionSelector { + private static final Logger LOG = LoggerFactory.getLogger(BlobSizeTransactionSelector.class); + + public BlobSizeTransactionSelector(final BlockSelectionContext context) { + super(context); + } + + /** + * Evaluates a transaction considering other transactions in the same block. If the tx does not + * support blobs, no check is performed, and SELECTED is returned, otherwise SELECTED is returned + * only if there is enough remaining blob gas to fit the blobs of the tx, otherwise a specific not + * selected result is returned, depending on the fact that the block already contains the max + * number of blobs or not. + * + * @param evaluationContext The current selection session data. + * @param transactionSelectionResults The results of other transaction evaluations in the same + * block. + * @return The result of the transaction selection. + */ + @Override + public TransactionSelectionResult evaluateTransactionPreProcessing( + final TransactionEvaluationContext evaluationContext, + final TransactionSelectionResults transactionSelectionResults) { + + final var tx = evaluationContext.getTransaction(); + if (tx.getType().supportsBlob()) { + + final var remainingBlobGas = + context.gasLimitCalculator().currentBlobGasLimit() + - transactionSelectionResults.getCumulativeBlobGasUsed(); + + if (remainingBlobGas == 0) { + LOG.atTrace() + .setMessage("The block already contains the max number of allowed blobs") + .addArgument(evaluationContext.getPendingTransaction()::toTraceLog) + .log(); + return TransactionSelectionResult.BLOBS_FULL; + } + + final long requestedBlobGas = context.gasCalculator().blobGasCost(tx.getBlobCount()); + + if (requestedBlobGas > remainingBlobGas) { + LOG.atTrace() + .setMessage( + "There is not enough blob gas available to fit the blobs of the transaction {} in the block." + + " Available {} / Requested {}") + .addArgument(evaluationContext.getPendingTransaction()::toTraceLog) + .addArgument(remainingBlobGas) + .addArgument(requestedBlobGas) + .log(); + return TransactionSelectionResult.TX_TOO_LARGE_FOR_REMAINING_BLOB_GAS; + } + } + return TransactionSelectionResult.SELECTED; + } + + @Override + public TransactionSelectionResult evaluateTransactionPostProcessing( + final TransactionEvaluationContext evaluationContext, + final TransactionSelectionResults blockTransactionResults, + final TransactionProcessingResult processingResult) { + // All necessary checks were done in the pre-processing method, so nothing to do here. + return TransactionSelectionResult.SELECTED; + } +} diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlockSizeTransactionSelector.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlockSizeTransactionSelector.java index 96f357546f3..2877c4ce139 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlockSizeTransactionSelector.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlockSizeTransactionSelector.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,9 +15,9 @@ package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors; import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext; +import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionEvaluationContext; import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults; import org.hyperledger.besu.ethereum.core.Transaction; -import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.plugin.data.TransactionSelectionResult; @@ -40,20 +40,21 @@ public BlockSizeTransactionSelector(final BlockSelectionContext context) { * Evaluates a transaction considering other transactions in the same block. If the transaction is * too large for the block returns a selection result based on block occupancy. * - * @param pendingTransaction The transaction to be evaluated. + * @param evaluationContext The current selection session data. * @param transactionSelectionResults The results of other transaction evaluations in the same * block. * @return The result of the transaction selection. */ @Override public TransactionSelectionResult evaluateTransactionPreProcessing( - final PendingTransaction pendingTransaction, + final TransactionEvaluationContext evaluationContext, final TransactionSelectionResults transactionSelectionResults) { + if (transactionTooLargeForBlock( - pendingTransaction.getTransaction(), transactionSelectionResults)) { + evaluationContext.getTransaction(), transactionSelectionResults)) { LOG.atTrace() .setMessage("Transaction {} too large to select for block creation") - .addArgument(pendingTransaction::toTraceLog) + .addArgument(evaluationContext.getPendingTransaction()::toTraceLog) .log(); if (blockOccupancyAboveThreshold(transactionSelectionResults)) { LOG.trace("Block occupancy above threshold, completing operation"); @@ -70,7 +71,7 @@ public TransactionSelectionResult evaluateTransactionPreProcessing( @Override public TransactionSelectionResult evaluateTransactionPostProcessing( - final PendingTransaction pendingTransaction, + final TransactionEvaluationContext evaluationContext, final TransactionSelectionResults blockTransactionResults, final TransactionProcessingResult processingResult) { // All necessary checks were done in the pre-processing method, so nothing to do here. @@ -88,16 +89,9 @@ public TransactionSelectionResult evaluateTransactionPostProcessing( private boolean transactionTooLargeForBlock( final Transaction transaction, final TransactionSelectionResults transactionSelectionResults) { - final long blobGasUsed = context.gasCalculator().blobGasCost(transaction.getBlobCount()); - - if (blobGasUsed - > context.gasLimitCalculator().currentBlobGasLimit() - - transactionSelectionResults.getCumulativeBlobGasUsed()) { - return true; - } - return transaction.getGasLimit() + blobGasUsed - > context.processableBlockHeader().getGasLimit() + return transaction.getGasLimit() + > context.pendingBlockHeader().getGasLimit() - transactionSelectionResults.getCumulativeGasUsed(); } @@ -110,7 +104,7 @@ private boolean transactionTooLargeForBlock( */ private boolean blockOccupancyAboveThreshold( final TransactionSelectionResults transactionSelectionResults) { - final long gasAvailable = context.processableBlockHeader().getGasLimit(); + final long gasAvailable = context.pendingBlockHeader().getGasLimit(); final long gasUsed = transactionSelectionResults.getCumulativeGasUsed(); final long gasRemaining = gasAvailable - gasUsed; @@ -135,7 +129,7 @@ private boolean blockOccupancyAboveThreshold( * @return True if the block is full, false otherwise. */ private boolean blockFull(final TransactionSelectionResults transactionSelectionResults) { - final long gasAvailable = context.processableBlockHeader().getGasLimit(); + final long gasAvailable = context.pendingBlockHeader().getGasLimit(); final long gasUsed = transactionSelectionResults.getCumulativeGasUsed(); final long gasRemaining = gasAvailable - gasUsed; diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/MinPriorityFeePerGasTransactionSelector.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/MinPriorityFeePerGasTransactionSelector.java index 91085f4f020..5783c7e5007 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/MinPriorityFeePerGasTransactionSelector.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/MinPriorityFeePerGasTransactionSelector.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -16,6 +16,7 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext; +import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionEvaluationContext; import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; @@ -36,7 +37,7 @@ public MinPriorityFeePerGasTransactionSelector(final BlockSelectionContext conte /** * Evaluates a transaction before processing. * - * @param pendingTransaction The transaction to be evaluated. + * @param evaluationContext The current selection session data. * @param transactionSelectionResults The results of other transaction evaluations in the same * block. * @return TransactionSelectionResult. If the priority fee is below the minimum, it returns an @@ -44,9 +45,9 @@ public MinPriorityFeePerGasTransactionSelector(final BlockSelectionContext conte */ @Override public TransactionSelectionResult evaluateTransactionPreProcessing( - final PendingTransaction pendingTransaction, + final TransactionEvaluationContext evaluationContext, final TransactionSelectionResults transactionSelectionResults) { - if (isPriorityFeePriceBelowMinimum(pendingTransaction)) { + if (isPriorityFeePriceBelowMinimum(evaluationContext.getPendingTransaction())) { return TransactionSelectionResult.PRIORITY_FEE_PER_GAS_BELOW_CURRENT_MIN; } return TransactionSelectionResult.SELECTED; @@ -67,20 +68,20 @@ private boolean isPriorityFeePriceBelowMinimum(final PendingTransaction pendingT Wei priorityFeePerGas = pendingTransaction .getTransaction() - .getEffectivePriorityFeePerGas(context.processableBlockHeader().getBaseFee()); + .getEffectivePriorityFeePerGas(context.pendingBlockHeader().getBaseFee()); return priorityFeePerGas.lessThan(context.miningParameters().getMinPriorityFeePerGas()); } /** * No evaluation is performed post-processing. * - * @param pendingTransaction The processed transaction. + * @param evaluationContext The current selection session data. * @param processingResult The result of the transaction processing. * @return Always returns SELECTED. */ @Override public TransactionSelectionResult evaluateTransactionPostProcessing( - final PendingTransaction pendingTransaction, + final TransactionEvaluationContext evaluationContext, final TransactionSelectionResults blockTransactionResults, final TransactionProcessingResult processingResult) { return TransactionSelectionResult.SELECTED; diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/PriceTransactionSelector.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/PriceTransactionSelector.java index defd75cb77c..707b1c48633 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/PriceTransactionSelector.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/PriceTransactionSelector.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,10 +14,9 @@ */ package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors; -import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext; +import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionEvaluationContext; import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults; -import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.plugin.data.TransactionSelectionResult; @@ -41,14 +40,15 @@ public PriceTransactionSelector(final BlockSelectionContext context) { * Evaluates a transaction considering its price. If the transaction's current price is below the * minimum, it returns a selection result indicating the reason. * - * @param pendingTransaction The transaction to be evaluated. + * @param evaluationContext The current selection session data. * @param ignored The results of other transaction evaluations in the same block. * @return The result of the transaction selection. */ @Override public TransactionSelectionResult evaluateTransactionPreProcessing( - final PendingTransaction pendingTransaction, final TransactionSelectionResults ignored) { - if (transactionCurrentPriceBelowMin(pendingTransaction)) { + final TransactionEvaluationContext evaluationContext, + final TransactionSelectionResults ignored) { + if (transactionCurrentPriceBelowMin(evaluationContext)) { return TransactionSelectionResult.CURRENT_TX_PRICE_BELOW_MIN; } return TransactionSelectionResult.SELECTED; @@ -56,7 +56,7 @@ public TransactionSelectionResult evaluateTransactionPreProcessing( @Override public TransactionSelectionResult evaluateTransactionPostProcessing( - final PendingTransaction pendingTransaction, + final TransactionEvaluationContext evaluationContext, final TransactionSelectionResults blockTransactionResults, final TransactionProcessingResult processingResult) { // All necessary checks were done in the pre-processing method, so nothing to do here. @@ -66,30 +66,25 @@ public TransactionSelectionResult evaluateTransactionPostProcessing( /** * Checks if the transaction's current price is below the minimum. * - * @param pendingTransaction The transaction to be checked. + * @param evaluationContext The current selection session data. * @return True if the transaction's current price is below the minimum, false otherwise. */ - private boolean transactionCurrentPriceBelowMin(final PendingTransaction pendingTransaction) { - final Transaction transaction = pendingTransaction.getTransaction(); + private boolean transactionCurrentPriceBelowMin( + final TransactionEvaluationContext evaluationContext) { + final PendingTransaction pendingTransaction = evaluationContext.getPendingTransaction(); // Priority txs are exempt from this check if (!pendingTransaction.hasPriority()) { - // since the minGasPrice can change at runtime, we need to recheck it everytime - final Wei transactionGasPriceInBlock = - context - .feeMarket() - .getTransactionPriceCalculator() - .price(transaction, context.processableBlockHeader().getBaseFee()); if (context .miningParameters() .getMinTransactionGasPrice() - .compareTo(transactionGasPriceInBlock) + .compareTo(evaluationContext.getTransactionGasPrice()) > 0) { LOG.atTrace() .setMessage( "Current gas price of {} is {} and lower than the configured minimum {}, skipping") .addArgument(pendingTransaction::toTraceLog) - .addArgument(transactionGasPriceInBlock::toHumanReadableString) + .addArgument(evaluationContext.getTransactionGasPrice()::toHumanReadableString) .addArgument( context.miningParameters().getMinTransactionGasPrice()::toHumanReadableString) .log(); diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/ProcessingResultTransactionSelector.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/ProcessingResultTransactionSelector.java index 8a2778eda03..9eed2bff09e 100644 --- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/ProcessingResultTransactionSelector.java +++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/ProcessingResultTransactionSelector.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,9 +15,9 @@ package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors; import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext; +import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionEvaluationContext; import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults; import org.hyperledger.besu.ethereum.core.Transaction; -import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; @@ -41,7 +41,7 @@ public ProcessingResultTransactionSelector(final BlockSelectionContext context) @Override public TransactionSelectionResult evaluateTransactionPreProcessing( - final PendingTransaction pendingTransaction, + final TransactionEvaluationContext evaluationContext, final TransactionSelectionResults blockTransactionResults) { // All checks depend on processingResult and will be done in the post-processing method, so // nothing to do here. @@ -53,20 +53,20 @@ public TransactionSelectionResult evaluateTransactionPreProcessing( * result. If the processing result is invalid, it determines the selection result for the invalid * result. * - * @param pendingTransaction The transaction to be evaluated. + * @param evaluationContext The current selection session data. * @param blockTransactionResults The results of other transaction evaluations in the same block. * @param processingResult The processing result of the transaction. * @return The result of the transaction selection. */ @Override public TransactionSelectionResult evaluateTransactionPostProcessing( - final PendingTransaction pendingTransaction, + final TransactionEvaluationContext evaluationContext, final TransactionSelectionResults blockTransactionResults, final TransactionProcessingResult processingResult) { if (processingResult.isInvalid()) { return transactionSelectionResultForInvalidResult( - pendingTransaction.getTransaction(), processingResult.getValidationResult()); + evaluationContext.getTransaction(), processingResult.getValidationResult()); } return TransactionSelectionResult.SELECTED; } @@ -110,7 +110,8 @@ private TransactionSelectionResult transactionSelectionResultForInvalidResult( * @return True if the invalid reason is transient, false otherwise. */ private boolean isTransientValidationError(final TransactionInvalidReason invalidReason) { - return invalidReason.equals(TransactionInvalidReason.GAS_PRICE_BELOW_CURRENT_BASE_FEE) + return invalidReason.equals(TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE) + || invalidReason.equals(TransactionInvalidReason.GAS_PRICE_BELOW_CURRENT_BASE_FEE) || invalidReason.equals(TransactionInvalidReason.NONCE_TOO_HIGH); } } diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreatorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreatorTest.java index 953fdeb7f42..ffab847bbdc 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreatorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -16,7 +16,7 @@ import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSpecs.DEFAULT_DEPOSIT_CONTRACT_ADDRESS; +import static org.hyperledger.besu.ethereum.mainnet.requests.DepositRequestProcessor.DEFAULT_DEPOSIT_CONTRACT_ADDRESS; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; @@ -26,8 +26,9 @@ import static org.mockito.Mockito.when; import org.hyperledger.besu.config.GenesisConfigFile; -import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.crypto.KeyPair; +import org.hyperledger.besu.crypto.SECPPrivateKey; +import org.hyperledger.besu.crypto.SignatureAlgorithm; import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.BLSPublicKey; @@ -36,23 +37,25 @@ import org.hyperledger.besu.datatypes.BlobsWithCommitments; import org.hyperledger.besu.datatypes.GWei; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.RequestType; import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.blockcreation.BlockCreator.BlockCreationResult; -import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.BlobTestFixture; import org.hyperledger.besu.ethereum.core.BlockDataGenerator; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; -import org.hyperledger.besu.ethereum.core.Deposit; +import org.hyperledger.besu.ethereum.core.DepositRequest; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.ExecutionContextTestFixture; import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters; import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters.MutableInitValues; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.ethereum.core.Request; import org.hyperledger.besu.ethereum.core.SealableBlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; @@ -60,6 +63,7 @@ import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -68,12 +72,21 @@ import org.hyperledger.besu.ethereum.eth.transactions.sorter.AbstractPendingTransactionsSorter; import org.hyperledger.besu.ethereum.eth.transactions.sorter.GasPricePendingTransactionsSorter; import org.hyperledger.besu.ethereum.mainnet.BodyValidation; -import org.hyperledger.besu.ethereum.mainnet.DepositsValidator; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; +import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; +import org.hyperledger.besu.ethereum.mainnet.TransactionValidator; +import org.hyperledger.besu.ethereum.mainnet.TransactionValidatorFactory; +import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.mainnet.WithdrawalsProcessor; -import org.hyperledger.besu.ethereum.mainnet.feemarket.CancunFeeMarket; +import org.hyperledger.besu.ethereum.mainnet.requests.DepositRequestProcessor; +import org.hyperledger.besu.ethereum.mainnet.requests.DepositRequestValidator; +import org.hyperledger.besu.ethereum.mainnet.requests.ProcessRequestContext; +import org.hyperledger.besu.ethereum.mainnet.requests.RequestProcessorCoordinator; +import org.hyperledger.besu.ethereum.mainnet.requests.RequestsValidatorCoordinator; +import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; +import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.log.Log; import org.hyperledger.besu.evm.log.LogTopic; @@ -83,13 +96,13 @@ import java.math.BigInteger; import java.time.Clock; import java.util.List; -import java.util.Map; import java.util.Optional; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt64; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -97,15 +110,22 @@ @ExtendWith(MockitoExtension.class) abstract class AbstractBlockCreatorTest { - private static final Optional
EMPTY_DEPOSIT_CONTRACT_ADDRESS = Optional.empty(); + private static final Supplier SIGNATURE_ALGORITHM = + Suppliers.memoize(SignatureAlgorithmFactory::getInstance); + private static final SECPPrivateKey PRIVATE_KEY1 = + SIGNATURE_ALGORITHM + .get() + .createPrivateKey( + Bytes32.fromHexString( + "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63")); + private static final KeyPair KEYS1 = + new KeyPair(PRIVATE_KEY1, SIGNATURE_ALGORITHM.get().createPublicKey(PRIVATE_KEY1)); + @Mock private WithdrawalsProcessor withdrawalsProcessor; protected EthScheduler ethScheduler = new DeterministicEthScheduler(); @Test - void findDepositsFromReceipts() { - final AbstractBlockCreator blockCreator = - blockCreatorWithAllowedDeposits(Optional.of(DEFAULT_DEPOSIT_CONTRACT_ADDRESS)); - final TransactionSelectionResults transactionResults = mock(TransactionSelectionResults.class); + void findDepositRequestsFromReceipts() { BlockDataGenerator blockDataGenerator = new BlockDataGenerator(); TransactionReceipt receiptWithoutDeposit1 = blockDataGenerator.receipt(); TransactionReceipt receiptWithoutDeposit2 = blockDataGenerator.receipt(); @@ -118,11 +138,11 @@ void findDepositsFromReceipts() { LogTopic.fromHexString( "0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"))); final TransactionReceipt receiptWithDeposit = blockDataGenerator.receipt(List.of(depositLog)); - when(transactionResults.getReceipts()) - .thenReturn(List.of(receiptWithoutDeposit1, receiptWithDeposit, receiptWithoutDeposit2)); + List receipts = + List.of(receiptWithoutDeposit1, receiptWithDeposit, receiptWithoutDeposit2); - Deposit expectedDeposit = - new Deposit( + DepositRequest expectedDepositRequest = + new DepositRequest( BLSPublicKey.fromHexString( "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e"), Bytes32.fromHexString( @@ -131,94 +151,79 @@ void findDepositsFromReceipts() { BLSSignature.fromHexString( "0xa889db8300194050a2636c92a95bc7160515867614b7971a9500cdb62f9c0890217d2901c3241f86fac029428fc106930606154bd9e406d7588934a5f15b837180b17194d6e44bd6de23e43b163dfe12e369dcc75a3852cd997963f158217eb5"), UInt64.valueOf(539967)); - final List expectedDeposits = List.of(expectedDeposit); - - final List depositsFromReceipts = - blockCreator.findDepositsFromReceipts(transactionResults); + final List expectedDepositRequests = List.of(expectedDepositRequest); - assertThat(depositsFromReceipts).isEqualTo(expectedDeposits); + var depositRequestsFromReceipts = + new DepositRequestProcessor(DEFAULT_DEPOSIT_CONTRACT_ADDRESS) + .process(new ProcessRequestContext(null, null, null, receipts, null, null)); + assertThat(depositRequestsFromReceipts.get()).isEqualTo(expectedDepositRequests); } @Test - void withAllowedDepositsAndContractAddress_DepositsAreParsed() { - final AbstractBlockCreator blockCreator = - blockCreatorWithAllowedDeposits(Optional.of(DEFAULT_DEPOSIT_CONTRACT_ADDRESS)); + void withAllowedDepositRequestsAndContractAddress_DepositRequestsAreParsed() { + final CreateOn miningOn = + blockCreatorWithAllowedDepositRequests(DEFAULT_DEPOSIT_CONTRACT_ADDRESS); final BlockCreationResult blockCreationResult = - blockCreator.createBlock( + miningOn.blockCreator.createBlock( Optional.empty(), Optional.empty(), Optional.of(emptyList()), Optional.empty(), Optional.empty(), 1L, - false); + false, + miningOn.parentHeader); - List deposits = emptyList(); - final Hash depositsRoot = BodyValidation.depositsRoot(deposits); - assertThat(blockCreationResult.getBlock().getHeader().getDepositsRoot()).hasValue(depositsRoot); - assertThat(blockCreationResult.getBlock().getBody().getDeposits()).hasValue(deposits); + List depositRequests = emptyList(); + final Hash requestsRoot = BodyValidation.requestsRoot(depositRequests); + assertThat(blockCreationResult.getBlock().getHeader().getRequestsRoot()).hasValue(requestsRoot); + assertThat(blockCreationResult.getBlock().getBody().getRequests()).hasValue(depositRequests); } @Test - void withAllowedDepositsAndNoContractAddress_DepositsAreNotParsed() { - final AbstractBlockCreator blockCreator = blockCreatorWithAllowedDeposits(Optional.empty()); + void withAllowedDepositRequestsAndNoContractAddress_DepositRequestsAreNotParsed() { + final CreateOn miningOn = blockCreatorWithAllowedDepositRequests(null); final BlockCreationResult blockCreationResult = - blockCreator.createBlock( + miningOn.blockCreator.createBlock( Optional.empty(), Optional.empty(), Optional.of(emptyList()), Optional.empty(), Optional.empty(), 1L, - false); + false, + miningOn.parentHeader); - assertThat(blockCreationResult.getBlock().getHeader().getDepositsRoot()).isEmpty(); - assertThat(blockCreationResult.getBlock().getBody().getDeposits()).isEmpty(); + assertThat(blockCreationResult.getBlock().getHeader().getRequestsRoot()).isEmpty(); + assertThat(blockCreationResult.getBlock().getBody().getRequests()).isEmpty(); } @Test - void withProhibitedDeposits_DepositsAreNotParsed() { - final AbstractBlockCreator blockCreator = blockCreatorWithProhibitedDeposits(); + void withProhibitedDepositRequests_DepositRequestsAreNotParsed() { + + final CreateOn miningOn = blockCreatorWithProhibitedDepositRequests(); final BlockCreationResult blockCreationResult = - blockCreator.createBlock( + miningOn.blockCreator.createBlock( Optional.empty(), Optional.empty(), Optional.of(emptyList()), Optional.empty(), Optional.empty(), 1L, - false); + false, + miningOn.parentHeader); - assertThat(blockCreationResult.getBlock().getHeader().getDepositsRoot()).isEmpty(); - assertThat(blockCreationResult.getBlock().getBody().getDeposits()).isEmpty(); - } - - private AbstractBlockCreator blockCreatorWithAllowedDeposits( - final Optional
depositContractAddress) { - final ProtocolSpecAdapters protocolSpecAdapters = - ProtocolSpecAdapters.create( - 0, - specBuilder -> - specBuilder.depositsValidator( - new DepositsValidator.AllowedDeposits(depositContractAddress.orElse(null)))); - return createBlockCreator(protocolSpecAdapters, depositContractAddress); - } - - private AbstractBlockCreator blockCreatorWithProhibitedDeposits() { - final ProtocolSpecAdapters protocolSpecAdapters = - ProtocolSpecAdapters.create( - 0, - specBuilder -> - specBuilder.depositsValidator(new DepositsValidator.ProhibitedDeposits())); - return createBlockCreator(protocolSpecAdapters, Optional.of(DEFAULT_DEPOSIT_CONTRACT_ADDRESS)); + assertThat(blockCreationResult.getBlock().getHeader().getRequestsRoot()).isEmpty(); + assertThat(blockCreationResult.getBlock().getBody().getRequests()).isEmpty(); } @Test void withProcessorAndEmptyWithdrawals_NoWithdrawalsAreProcessed() { - final AbstractBlockCreator blockCreator = blockCreatorWithWithdrawalsProcessor(); + final CreateOn miningOn = blockCreatorWithWithdrawalsProcessor(); + final AbstractBlockCreator blockCreator = miningOn.blockCreator; final BlockCreationResult blockCreationResult = blockCreator.createBlock( Optional.empty(), @@ -227,7 +232,8 @@ void withProcessorAndEmptyWithdrawals_NoWithdrawalsAreProcessed() { Optional.empty(), Optional.empty(), 1L, - false); + false, + miningOn.parentHeader); verify(withdrawalsProcessor, never()).processWithdrawals(any(), any()); assertThat(blockCreationResult.getBlock().getHeader().getWithdrawalsRoot()).isEmpty(); assertThat(blockCreationResult.getBlock().getBody().getWithdrawals()).isEmpty(); @@ -235,7 +241,8 @@ void withProcessorAndEmptyWithdrawals_NoWithdrawalsAreProcessed() { @Test void withNoProcessorAndEmptyWithdrawals_NoWithdrawalsAreNotProcessed() { - final AbstractBlockCreator blockCreator = blockCreatorWithoutWithdrawalsProcessor(); + final CreateOn miningOn = blockCreatorWithoutWithdrawalsProcessor(); + final AbstractBlockCreator blockCreator = miningOn.blockCreator; final BlockCreationResult blockCreationResult = blockCreator.createBlock( Optional.empty(), @@ -244,7 +251,8 @@ void withNoProcessorAndEmptyWithdrawals_NoWithdrawalsAreNotProcessed() { Optional.empty(), Optional.empty(), 1L, - false); + false, + miningOn.parentHeader); verify(withdrawalsProcessor, never()).processWithdrawals(any(), any()); assertThat(blockCreationResult.getBlock().getHeader().getWithdrawalsRoot()).isEmpty(); assertThat(blockCreationResult.getBlock().getBody().getWithdrawals()).isEmpty(); @@ -252,7 +260,8 @@ void withNoProcessorAndEmptyWithdrawals_NoWithdrawalsAreNotProcessed() { @Test void withProcessorAndWithdrawals_WithdrawalsAreProcessed() { - final AbstractBlockCreator blockCreator = blockCreatorWithWithdrawalsProcessor(); + final CreateOn miningOn = blockCreatorWithWithdrawalsProcessor(); + final AbstractBlockCreator blockCreator = miningOn.blockCreator; final List withdrawals = List.of(new Withdrawal(UInt64.ONE, UInt64.ONE, Address.fromHexString("0x1"), GWei.ONE)); final BlockCreationResult blockCreationResult = @@ -263,7 +272,8 @@ void withProcessorAndWithdrawals_WithdrawalsAreProcessed() { Optional.empty(), Optional.empty(), 1L, - false); + false, + miningOn.parentHeader); final Hash withdrawalsRoot = BodyValidation.withdrawalsRoot(withdrawals); verify(withdrawalsProcessor).processWithdrawals(eq(withdrawals), any()); @@ -274,7 +284,8 @@ void withProcessorAndWithdrawals_WithdrawalsAreProcessed() { @Test void withNoProcessorAndWithdrawals_WithdrawalsAreNotProcessed() { - final AbstractBlockCreator blockCreator = blockCreatorWithoutWithdrawalsProcessor(); + final CreateOn miningOn = blockCreatorWithoutWithdrawalsProcessor(); + final AbstractBlockCreator blockCreator = miningOn.blockCreator; final List withdrawals = List.of(new Withdrawal(UInt64.ONE, UInt64.ONE, Address.fromHexString("0x1"), GWei.ONE)); final BlockCreationResult blockCreationResult = @@ -285,17 +296,17 @@ void withNoProcessorAndWithdrawals_WithdrawalsAreNotProcessed() { Optional.empty(), Optional.empty(), 1L, - false); + false, + miningOn.parentHeader); verify(withdrawalsProcessor, never()).processWithdrawals(any(), any()); assertThat(blockCreationResult.getBlock().getHeader().getWithdrawalsRoot()).isEmpty(); assertThat(blockCreationResult.getBlock().getBody().getWithdrawals()).isEmpty(); } - @Disabled @Test public void computesGasUsageFromIncludedTransactions() { - final KeyPair senderKeys = SignatureAlgorithmFactory.getInstance().generateKeyPair(); - final AbstractBlockCreator blockCreator = blockCreatorWithBlobGasSupport(); + final CreateOn miningOn = blockCreatorWithBlobGasSupport(); + final AbstractBlockCreator blockCreator = miningOn.blockCreator; BlobTestFixture blobTestFixture = new BlobTestFixture(); BlobsWithCommitments bwc = blobTestFixture.createBlobsWithCommitments(6); TransactionTestFixture ttf = new TransactionTestFixture(); @@ -303,13 +314,14 @@ public void computesGasUsageFromIncludedTransactions() { ttf.to(Optional.of(Address.ZERO)) .type(TransactionType.BLOB) .chainId(Optional.of(BigInteger.valueOf(42))) + .gasLimit(21000) .maxFeePerGas(Optional.of(Wei.of(15))) .maxFeePerBlobGas(Optional.of(Wei.of(128))) .maxPriorityFeePerGas(Optional.of(Wei.of(1))) .versionedHashes(Optional.of(bwc.getVersionedHashes())) - .createTransaction(senderKeys); + .blobsWithCommitments(Optional.of(bwc)) + .createTransaction(KEYS1); - ttf.blobsWithCommitments(Optional.of(bwc)); final BlockCreationResult blockCreationResult = blockCreator.createBlock( Optional.of(List.of(fullOfBlobs)), @@ -318,60 +330,103 @@ public void computesGasUsageFromIncludedTransactions() { Optional.empty(), Optional.empty(), 1L, - false); + false, + miningOn.parentHeader); long blobGasUsage = blockCreationResult.getBlock().getHeader().getGasUsed(); assertThat(blobGasUsage).isNotZero(); BlobGas excessBlobGas = blockCreationResult.getBlock().getHeader().getExcessBlobGas().get(); assertThat(excessBlobGas).isNotNull(); } - private AbstractBlockCreator blockCreatorWithBlobGasSupport() { + private CreateOn blockCreatorWithBlobGasSupport() { + final var alwaysValidTransactionValidatorFactory = mock(TransactionValidatorFactory.class); + when(alwaysValidTransactionValidatorFactory.get()) + .thenReturn(new AlwaysValidTransactionValidator()); final ProtocolSpecAdapters protocolSpecAdapters = ProtocolSpecAdapters.create( 0, specBuilder -> { - specBuilder.feeMarket(new CancunFeeMarket(0, Optional.empty())); specBuilder.isReplayProtectionSupported(true); specBuilder.withdrawalsProcessor(withdrawalsProcessor); + specBuilder.transactionValidatorFactoryBuilder( + (evm, gasLimitCalculator, feeMarket) -> alwaysValidTransactionValidatorFactory); return specBuilder; }); - return createBlockCreator(protocolSpecAdapters, EMPTY_DEPOSIT_CONTRACT_ADDRESS); + return createBlockCreator(protocolSpecAdapters); + } + + private CreateOn blockCreatorWithProhibitedDepositRequests() { + final ProtocolSpecAdapters protocolSpecAdapters = + ProtocolSpecAdapters.create(0, specBuilder -> specBuilder); + return createBlockCreator(protocolSpecAdapters); } - private AbstractBlockCreator blockCreatorWithWithdrawalsProcessor() { + private CreateOn blockCreatorWithWithdrawalsProcessor() { final ProtocolSpecAdapters protocolSpecAdapters = ProtocolSpecAdapters.create( 0, specBuilder -> specBuilder.withdrawalsProcessor(withdrawalsProcessor)); - return createBlockCreator(protocolSpecAdapters, EMPTY_DEPOSIT_CONTRACT_ADDRESS); + return createBlockCreator(protocolSpecAdapters); } - private AbstractBlockCreator blockCreatorWithoutWithdrawalsProcessor() { - return createBlockCreator(new ProtocolSpecAdapters(Map.of()), EMPTY_DEPOSIT_CONTRACT_ADDRESS); + private CreateOn blockCreatorWithoutWithdrawalsProcessor() { + final ProtocolSpecAdapters protocolSpecAdapters = + ProtocolSpecAdapters.create(0, specBuilder -> specBuilder.withdrawalsProcessor(null)); + return createBlockCreator(protocolSpecAdapters); } - private AbstractBlockCreator createBlockCreator( - final ProtocolSpecAdapters protocolSpecAdapters, - final Optional
depositContractAddress) { - final GenesisConfigOptions genesisConfigOptions = GenesisConfigFile.DEFAULT.getConfigOptions(); + private CreateOn blockCreatorWithAllowedDepositRequests(final Address depositContractAddress) { + final ProtocolSpecAdapters protocolSpecAdapters = + ProtocolSpecAdapters.create( + 0, + specBuilder -> + specBuilder + .requestsValidator( + new RequestsValidatorCoordinator.Builder() + .addValidator( + RequestType.DEPOSIT, + new DepositRequestValidator((depositContractAddress))) + .build()) + .requestProcessorCoordinator( + new RequestProcessorCoordinator.Builder() + .addProcessor( + RequestType.DEPOSIT, + new DepositRequestProcessor(depositContractAddress)) + .build())); + return createBlockCreator(protocolSpecAdapters); + } + + record CreateOn(AbstractBlockCreator blockCreator, BlockHeader parentHeader) {} + + private CreateOn createBlockCreator(final ProtocolSpecAdapters protocolSpecAdapters) { + + final var genesisConfigFile = GenesisConfigFile.fromResource("/block-creation-genesis.json"); final ExecutionContextTestFixture executionContextTestFixture = - ExecutionContextTestFixture.builder() + ExecutionContextTestFixture.builder(genesisConfigFile) .protocolSchedule( new ProtocolScheduleBuilder( - genesisConfigOptions, + genesisConfigFile.getConfigOptions(), BigInteger.valueOf(42), protocolSpecAdapters, PrivacyParameters.DEFAULT, false, - EvmConfiguration.DEFAULT) + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()) .createProtocolSchedule()) .build(); final MutableBlockchain blockchain = executionContextTestFixture.getBlockchain(); + BlockHeader parentHeader = blockchain.getChainHeadHeader(); final TransactionPoolConfiguration poolConf = ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(100).build(); final AbstractPendingTransactionsSorter sorter = new GasPricePendingTransactionsSorter( - poolConf, Clock.systemUTC(), new NoOpMetricsSystem(), blockchain::getChainHeadHeader); + poolConf, + Clock.systemUTC(), + new NoOpMetricsSystem(), + Suppliers.ofInstance(parentHeader)); final EthContext ethContext = mock(EthContext.class, RETURNS_DEEP_STUBS); when(ethContext.getEthPeers().subscribeConnect(any())).thenReturn(1L); @@ -385,7 +440,7 @@ private AbstractBlockCreator createBlockCreator( ethContext, new TransactionPoolMetrics(new NoOpMetricsSystem()), poolConf, - null); + new BlobCache()); transactionPool.setEnabled(); final MiningParameters miningParameters = @@ -399,16 +454,16 @@ private AbstractBlockCreator createBlockCreator( .build()) .build(); - return new TestBlockCreator( - miningParameters, - __ -> Address.ZERO, - __ -> Bytes.fromHexString("deadbeef"), - transactionPool, - executionContextTestFixture.getProtocolContext(), - executionContextTestFixture.getProtocolSchedule(), - blockchain.getChainHeadHeader(), - depositContractAddress, - ethScheduler); + return new CreateOn( + new TestBlockCreator( + miningParameters, + __ -> Address.ZERO, + __ -> Bytes.fromHexString("deadbeef"), + transactionPool, + executionContextTestFixture.getProtocolContext(), + executionContextTestFixture.getProtocolSchedule(), + ethScheduler), + parentHeader); } static class TestBlockCreator extends AbstractBlockCreator { @@ -420,8 +475,6 @@ protected TestBlockCreator( final TransactionPool transactionPool, final ProtocolContext protocolContext, final ProtocolSchedule protocolSchedule, - final BlockHeader parentHeader, - final Optional
depositContractAddress, final EthScheduler ethScheduler) { super( miningParameters, @@ -430,8 +483,6 @@ protected TestBlockCreator( transactionPool, protocolContext, protocolSchedule, - parentHeader, - depositContractAddress, ethScheduler); } @@ -446,4 +497,24 @@ protected BlockHeader createFinalBlockHeader(final SealableBlockHeader sealableB .buildBlockHeader(); } } + + static class AlwaysValidTransactionValidator implements TransactionValidator { + + @Override + public ValidationResult validate( + final Transaction transaction, + final Optional baseFee, + final Optional blobBaseFee, + final TransactionValidationParams transactionValidationParams) { + return ValidationResult.valid(); + } + + @Override + public ValidationResult validateForSender( + final Transaction transaction, + final Account sender, + final TransactionValidationParams validationParams) { + return ValidationResult.valid(); + } + } } diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockTransactionSelectorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockTransactionSelectorTest.java index 4e7b3eae5eb..adcc3ee1e27 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockTransactionSelectorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockTransactionSelectorTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,8 +17,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; import static org.awaitility.Awaitility.await; -import static org.hyperledger.besu.ethereum.core.MiningParameters.Unstable.DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME; +import static org.hyperledger.besu.ethereum.core.MiningParameters.DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME; +import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.NONCE_TOO_LOW; import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.BLOCK_SELECTION_TIMEOUT; +import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.INVALID_TX_EVALUATION_TOO_LONG; import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.PRIORITY_FEE_PER_GAS_BELOW_CURRENT_MIN; import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.SELECTED; import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.TX_EVALUATION_TOO_LONG; @@ -26,7 +28,6 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -42,7 +43,7 @@ import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockTransactionSelector; import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults; -import org.hyperledger.besu.ethereum.blockcreation.txselection.selectors.AllAcceptingTransactionSelector; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.DefaultBlockchain; import org.hyperledger.besu.ethereum.chain.GenesisState; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; @@ -54,7 +55,6 @@ import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters; import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters.MutableInitValues; -import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters.Unstable; import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.MutableWorldState; @@ -81,15 +81,19 @@ import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.data.TransactionSelectionResult; import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.TransactionSelectionService; import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelector; import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelectorFactory; +import org.hyperledger.besu.plugin.services.txselection.TransactionEvaluationContext; +import org.hyperledger.besu.services.TransactionSelectionServiceImpl; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; -import org.hyperledger.besu.util.number.Percentage; +import org.hyperledger.besu.util.number.PositiveNumber; import java.math.BigInteger; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -118,8 +122,6 @@ public abstract class AbstractBlockTransactionSelectorTest { protected static final double MIN_OCCUPANCY_80_PERCENT = 0.8; protected static final double MIN_OCCUPANCY_100_PERCENT = 1; - protected static final PluginTransactionSelectorFactory NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY = - () -> AllAcceptingTransactionSelector.INSTANCE; protected static final BigInteger CHAIN_ID = BigInteger.valueOf(42L); protected static final KeyPair keyPair = SignatureAlgorithmFactory.getInstance().generateKeyPair(); @@ -132,9 +134,8 @@ public abstract class AbstractBlockTransactionSelectorTest { protected TransactionPool transactionPool; protected MutableWorldState worldState; protected ProtocolSchedule protocolSchedule; - protected final MiningParameters defaultTestMiningParameters = - createMiningParameters( - Wei.ZERO, MIN_OCCUPANCY_80_PERCENT, DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME); + protected TransactionSelectionService transactionSelectionService; + protected MiningParameters defaultTestMiningParameters; @Mock protected EthScheduler ethScheduler; @@ -150,6 +151,14 @@ public abstract class AbstractBlockTransactionSelectorTest { public void setup() { genesisConfigFile = getGenesisConfigFile(); protocolSchedule = createProtocolSchedule(); + transactionSelectionService = new TransactionSelectionServiceImpl(); + defaultTestMiningParameters = + createMiningParameters( + transactionSelectionService, + Wei.ZERO, + MIN_OCCUPANCY_80_PERCENT, + DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME); + final Block genesisBlock = GenesisState.fromConfig(genesisConfigFile, protocolSchedule).getBlock(); @@ -159,7 +168,8 @@ public void setup() { new KeyValueStoragePrefixedKeyBlockchainStorage( new InMemoryKeyValueStorage(), new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), - new MainnetBlockHeaderFunctions()), + new MainnetBlockHeaderFunctions(), + false), new NoOpMetricsSystem(), 0); @@ -187,6 +197,10 @@ private Boolean isCancelled() { return false; } + protected Wei getMinGasPrice() { + return Wei.ONE; + } + protected ProcessableBlockHeader createBlock(final long gasLimit) { return createBlock(gasLimit, Wei.ONE); } @@ -207,7 +221,12 @@ protected ProcessableBlockHeader createBlock(final long gasLimit, final Wei base public void emptyPendingTransactionsResultsInEmptyVettingResult() { final ProtocolSchedule protocolSchedule = FixedDifficultyProtocolSchedule.create( - GenesisConfigFile.development().getConfigOptions(), EvmConfiguration.DEFAULT); + GenesisConfigFile.fromResource("/dev.json").getConfigOptions(), + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); final MainnetTransactionProcessor mainnetTransactionProcessor = protocolSchedule.getByBlockHeader(blockHeader(0)).getTransactionProcessor(); @@ -223,7 +242,7 @@ public void emptyPendingTransactionsResultsInEmptyVettingResult() { blockHeader, miningBeneficiary, Wei.ZERO, - NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + transactionSelectionService); final TransactionSelectionResults results = selector.buildTransactionListForBlock(); @@ -244,7 +263,7 @@ public void validPendingTransactionIsIncludedInTheBlock() { blockHeader, miningBeneficiary, Wei.ZERO, - NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + transactionSelectionService); final Transaction transaction = createTransaction(1, Wei.of(7L), 100_000); transactionPool.addRemoteTransactions(List.of(transaction)); @@ -271,14 +290,14 @@ public void invalidTransactionsAreSkippedButBlockStillFills() { blockHeader, miningBeneficiary, Wei.ZERO, - NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + transactionSelectionService); final List transactionsToInject = Lists.newArrayList(); for (int i = 0; i < 5; i++) { final Transaction tx = createTransaction(i, Wei.of(7), 100_000); transactionsToInject.add(tx); if (i == 1) { - ensureTransactionIsInvalid(tx, TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE); + ensureTransactionIsInvalid(tx, TransactionInvalidReason.NONCE_TOO_LOW); } else { ensureTransactionIsValid(tx); } @@ -293,8 +312,7 @@ public void invalidTransactionsAreSkippedButBlockStillFills() { .containsOnly( entry( invalidTx, - TransactionSelectionResult.invalid( - TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE.name()))); + TransactionSelectionResult.invalid(TransactionInvalidReason.NONCE_TOO_LOW.name()))); assertThat(results.getSelectedTransactions().size()).isEqualTo(4); assertThat(results.getSelectedTransactions().contains(invalidTx)).isFalse(); assertThat(results.getReceipts().size()).isEqualTo(4); @@ -312,7 +330,7 @@ public void subsetOfPendingTransactionsIncludedWhenBlockGasLimitHit() { blockHeader, miningBeneficiary, Wei.ZERO, - NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + transactionSelectionService); final List transactionsToInject = Lists.newArrayList(); for (int i = 0; i < 5; i++) { @@ -353,7 +371,7 @@ public void transactionTooLargeForBlockDoesNotPreventMoreBeingAddedIfBlockOccupa blockHeader, miningBeneficiary, Wei.ZERO, - NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + transactionSelectionService); // Add 3 transactions to the Pending Transactions, 79% of block, 100% of block and 10% of block // should end up selecting the first and third only. @@ -388,7 +406,7 @@ public void transactionSelectionStopsWhenSufficientBlockOccupancyIsReached() { blockHeader, miningBeneficiary, Wei.ZERO, - NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + transactionSelectionService); // Add 4 transactions to the Pending Transactions 15% (ok), 79% (ok), 25% (too large), 10% // (not included, it would fit, however previous transaction was too large and block was @@ -422,12 +440,15 @@ public void transactionSelectionStopsWhenBlockIsFull() { final BlockTransactionSelector selector = createBlockSelectorAndSetupTxPool( createMiningParameters( - Wei.ZERO, MIN_OCCUPANCY_100_PERCENT, DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME), + transactionSelectionService, + Wei.ZERO, + MIN_OCCUPANCY_100_PERCENT, + DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME), transactionProcessor, blockHeader, miningBeneficiary, Wei.ZERO, - NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + transactionSelectionService); final long minTxGasCost = getGasCalculator().getMinimumTransactionCost(); @@ -481,12 +502,15 @@ public void transactionSelectionStopsWhenRemainingGasIsNotEnoughForAnyMoreTransa final BlockTransactionSelector selector = createBlockSelectorAndSetupTxPool( createMiningParameters( - Wei.ZERO, MIN_OCCUPANCY_100_PERCENT, DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME), + transactionSelectionService, + Wei.ZERO, + MIN_OCCUPANCY_100_PERCENT, + DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME), transactionProcessor, blockHeader, miningBeneficiary, Wei.ZERO, - NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + transactionSelectionService); final long minTxGasCost = getGasCalculator().getMinimumTransactionCost(); @@ -538,14 +562,13 @@ public void shouldDiscardTransactionsThatFailValidation() { blockHeader, miningBeneficiary, Wei.ZERO, - NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + transactionSelectionService); final Transaction validTransaction = createTransaction(0, Wei.of(10), 21_000); ensureTransactionIsValid(validTransaction, 21_000, 0); final Transaction invalidTransaction = createTransaction(3, Wei.of(10), 21_000); - ensureTransactionIsInvalid( - invalidTransaction, TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE); + ensureTransactionIsInvalid(invalidTransaction, TransactionInvalidReason.NONCE_TOO_LOW); transactionPool.addRemoteTransactions(List.of(validTransaction, invalidTransaction)); @@ -558,8 +581,7 @@ public void shouldDiscardTransactionsThatFailValidation() { .containsOnly( entry( invalidTransaction, - TransactionSelectionResult.invalid( - TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE.name()))); + TransactionSelectionResult.invalid(TransactionInvalidReason.NONCE_TOO_LOW.name()))); } @Test @@ -581,22 +603,32 @@ public void transactionSelectionPluginShouldWork_PreProcessing() { new PluginTransactionSelector() { @Override public TransactionSelectionResult evaluateTransactionPreProcessing( - final PendingTransaction pendingTransaction) { - if (pendingTransaction.getTransaction().equals(notSelectedTransient)) + final TransactionEvaluationContext + evaluationContext) { + if (evaluationContext + .getPendingTransaction() + .getTransaction() + .equals(notSelectedTransient)) return PluginTransactionSelectionResult.GENERIC_PLUGIN_INVALID_TRANSIENT; - if (pendingTransaction.getTransaction().equals(notSelectedInvalid)) + if (evaluationContext + .getPendingTransaction() + .getTransaction() + .equals(notSelectedInvalid)) return PluginTransactionSelectionResult.GENERIC_PLUGIN_INVALID; return SELECTED; } @Override public TransactionSelectionResult evaluateTransactionPostProcessing( - final PendingTransaction pendingTransaction, + final TransactionEvaluationContext + evaluationContext, final org.hyperledger.besu.plugin.data.TransactionProcessingResult processingResult) { return SELECTED; } }; + transactionSelectionService.registerPluginTransactionSelectorFactory( + transactionSelectorFactory); final BlockTransactionSelector selector = createBlockSelectorAndSetupTxPool( @@ -605,7 +637,7 @@ public TransactionSelectionResult evaluateTransactionPostProcessing( blockHeader, miningBeneficiary, Wei.ZERO, - transactionSelectorFactory); + transactionSelectionService); transactionPool.addRemoteTransactions( List.of(selected, notSelectedTransient, notSelectedInvalid)); @@ -645,13 +677,15 @@ public void transactionSelectionPluginShouldWork_PostProcessing() { new PluginTransactionSelector() { @Override public TransactionSelectionResult evaluateTransactionPreProcessing( - final PendingTransaction pendingTransaction) { + final TransactionEvaluationContext + evaluationContext) { return SELECTED; } @Override public TransactionSelectionResult evaluateTransactionPostProcessing( - final PendingTransaction pendingTransaction, + final TransactionEvaluationContext + evaluationContext, final org.hyperledger.besu.plugin.data.TransactionProcessingResult processingResult) { // the transaction with max gas +1 should fail @@ -661,17 +695,22 @@ public TransactionSelectionResult evaluateTransactionPostProcessing( return SELECTED; } }; + transactionSelectionService.registerPluginTransactionSelectorFactory( + transactionSelectorFactory); final Address miningBeneficiary = AddressHelpers.ofValue(1); final BlockTransactionSelector selector = createBlockSelectorAndSetupTxPool( createMiningParameters( - Wei.ZERO, MIN_OCCUPANCY_80_PERCENT, DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME), + transactionSelectionService, + Wei.ZERO, + MIN_OCCUPANCY_80_PERCENT, + DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME), transactionProcessor, blockHeader, miningBeneficiary, Wei.ZERO, - transactionSelectorFactory); + transactionSelectionService); transactionPool.addRemoteTransactions(List.of(selected, notSelected, selected3)); @@ -688,15 +727,22 @@ public TransactionSelectionResult evaluateTransactionPostProcessing( public void transactionSelectionPluginShouldBeNotifiedWhenTransactionSelectionCompletes() { final PluginTransactionSelectorFactory transactionSelectorFactory = mock(PluginTransactionSelectorFactory.class); - PluginTransactionSelector transactionSelector = spy(AllAcceptingTransactionSelector.INSTANCE); + PluginTransactionSelector transactionSelector = mock(PluginTransactionSelector.class); + when(transactionSelector.evaluateTransactionPreProcessing(any())).thenReturn(SELECTED); + when(transactionSelector.evaluateTransactionPostProcessing(any(), any())).thenReturn(SELECTED); when(transactionSelectorFactory.create()).thenReturn(transactionSelector); + transactionSelectionService.registerPluginTransactionSelectorFactory( + transactionSelectorFactory); + final Transaction transaction = createTransaction(0, Wei.of(10), 21_000); ensureTransactionIsValid(transaction, 21_000, 0); - final TransactionInvalidReason invalidReason = TransactionInvalidReason.PLUGIN_TX_VALIDATOR; + final TransactionInvalidReason invalidReason = + TransactionInvalidReason.PLUGIN_TX_POOL_VALIDATOR; final Transaction invalidTransaction = createTransaction(1, Wei.of(10), 21_000); - ensureTransactionIsInvalid(invalidTransaction, TransactionInvalidReason.PLUGIN_TX_VALIDATOR); + ensureTransactionIsInvalid( + invalidTransaction, TransactionInvalidReason.PLUGIN_TX_POOL_VALIDATOR); final BlockTransactionSelector selector = createBlockSelectorAndSetupTxPool( @@ -705,19 +751,20 @@ public void transactionSelectionPluginShouldBeNotifiedWhenTransactionSelectionCo createBlock(300_000), AddressHelpers.ofValue(1), Wei.ZERO, - transactionSelectorFactory); + transactionSelectionService); transactionPool.addRemoteTransactions(List.of(transaction, invalidTransaction)); selector.buildTransactionListForBlock(); - ArgumentCaptor argumentCaptor = - ArgumentCaptor.forClass(PendingTransaction.class); + @SuppressWarnings("unchecked") + ArgumentCaptor> argumentCaptor = + ArgumentCaptor.forClass(TransactionEvaluationContext.class); // selected transaction must be notified to the selector verify(transactionSelector) .onTransactionSelected(argumentCaptor.capture(), any(TransactionProcessingResult.class)); - PendingTransaction selected = argumentCaptor.getValue(); + PendingTransaction selected = argumentCaptor.getValue().getPendingTransaction(); assertThat(selected.getTransaction()).isEqualTo(transaction); // unselected transaction must be notified to the selector with correct reason @@ -725,7 +772,7 @@ public void transactionSelectionPluginShouldBeNotifiedWhenTransactionSelectionCo .onTransactionNotSelected( argumentCaptor.capture(), eq(TransactionSelectionResult.invalid(invalidReason.toString()))); - PendingTransaction rejectedTransaction = argumentCaptor.getValue(); + PendingTransaction rejectedTransaction = argumentCaptor.getValue().getPendingTransaction(); assertThat(rejectedTransaction.getTransaction()).isEqualTo(invalidTransaction); } @@ -740,7 +787,7 @@ public void transactionWithIncorrectNonceRemainsInPoolAndNotSelected() { blockHeader, miningBeneficiary, Wei.ZERO, - NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + transactionSelectionService); final Transaction futureTransaction = createTransaction(4, Wei.of(10), 100_000); @@ -776,7 +823,7 @@ public void increaseOfMinGasPriceAtRuntimeExcludeTxFromBeingSelected() { blockHeader, miningBeneficiary, Wei.ZERO, - NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + transactionSelectionService); transactionPool.addRemoteTransactions(List.of(transaction)); @@ -812,7 +859,7 @@ public void decreaseOfMinGasPriceAtRuntimeIncludeTxThatWasPreviouslyNotSelected( blockHeader, miningBeneficiary, Wei.ZERO, - NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + transactionSelectionService); transactionPool.addRemoteTransactions(List.of(transaction)); ensureTransactionIsValid(transaction, 0, 5); @@ -840,7 +887,7 @@ public void decreaseOfMinGasPriceAtRuntimeIncludeTxThatWasPreviouslyNotSelected( blockHeader, miningBeneficiary, Wei.ZERO, - NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + transactionSelectionService); final TransactionSelectionResults results2 = selector2.buildTransactionListForBlock(); @@ -868,7 +915,7 @@ public void shouldNotSelectTransactionsWithPriorityFeeLessThanConfig() { blockHeader, AddressHelpers.ofValue(1), Wei.ZERO, - NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + transactionSelectionService); transactionPool.addRemoteTransactions(List.of(txSelected, txNotSelected)); @@ -899,7 +946,7 @@ public void subsetOfPendingTransactionsIncludedWhenTxSelectionMaxTimeIsOver( @ParameterizedTest @MethodSource("subsetOfPendingTransactionsIncludedWhenTxSelectionMaxTimeIsOver") - public void pendingTransactionsThatTakesTooLongToEvaluateIsDroppedFromThePool( + public void pendingTransactionsThatTakesTooLongToEvaluateIsPenalized( final boolean isPoa, final boolean preProcessingTooLate, final boolean processingTooLate, @@ -912,7 +959,7 @@ public void pendingTransactionsThatTakesTooLongToEvaluateIsDroppedFromThePool( postProcessingTooLate, 900, TX_EVALUATION_TOO_LONG, - true); + false); } private void internalBlockSelectionTimeoutSimulation( @@ -931,9 +978,10 @@ private void internalBlockSelectionTimeoutSimulation( final BiFunction> tooLate = (p, t) -> invocation -> { - final org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction ptx = - invocation.getArgument(0); - if (ptx.getTransaction().equals(p)) { + final org.hyperledger.besu.ethereum.blockcreation.txselection + .TransactionEvaluationContext + ctx = invocation.getArgument(0); + if (ctx.getTransaction().equals(p)) { Thread.sleep(t); } else { Thread.sleep(fastProcessingTxTime); @@ -943,8 +991,8 @@ private void internalBlockSelectionTimeoutSimulation( final ProcessableBlockHeader blockHeader = createBlock(301_000); final Address miningBeneficiary = AddressHelpers.ofValue(1); - final int poaMinBlockTime = 1; - final long blockTxsSelectionMaxTime = 750; + final int poaGenesisBlockPeriod = 1; + final int blockTxsSelectionMaxTime = 750; final List transactionsToInject = new ArrayList<>(3); for (int i = 0; i < 2; i++) { @@ -970,18 +1018,28 @@ private void internalBlockSelectionTimeoutSimulation( mock(PluginTransactionSelectorFactory.class); when(transactionSelectorFactory.create()).thenReturn(transactionSelector); + transactionSelectionService.registerPluginTransactionSelectorFactory( + transactionSelectorFactory); + final BlockTransactionSelector selector = createBlockSelectorAndSetupTxPool( isPoa ? createMiningParameters( - Wei.ZERO, MIN_OCCUPANCY_100_PERCENT, poaMinBlockTime, Percentage.fromInt(75)) + transactionSelectionService, + Wei.ZERO, + MIN_OCCUPANCY_100_PERCENT, + poaGenesisBlockPeriod, + PositiveNumber.fromInt(75)) : createMiningParameters( - Wei.ZERO, MIN_OCCUPANCY_100_PERCENT, blockTxsSelectionMaxTime), + transactionSelectionService, + Wei.ZERO, + MIN_OCCUPANCY_100_PERCENT, + PositiveNumber.fromInt(blockTxsSelectionMaxTime)), transactionProcessor, blockHeader, miningBeneficiary, Wei.ZERO, - transactionSelectorFactory); + transactionSelectionService); transactionPool.addRemoteTransactions(transactionsToInject); @@ -1009,6 +1067,157 @@ private void internalBlockSelectionTimeoutSimulation( .isEqualTo(isLongProcessingTxDropped ? true : false); } + @ParameterizedTest + @MethodSource("subsetOfPendingTransactionsIncludedWhenTxSelectionMaxTimeIsOver") + public void subsetOfInvalidPendingTransactionsIncludedWhenTxSelectionMaxTimeIsOver( + final boolean isPoa, + final boolean preProcessingTooLate, + final boolean processingTooLate, + final boolean postProcessingTooLate) { + + internalBlockSelectionTimeoutSimulationInvalidTxs( + isPoa, + preProcessingTooLate, + processingTooLate, + postProcessingTooLate, + 500, + BLOCK_SELECTION_TIMEOUT, + false, + NONCE_TOO_LOW); + } + + @ParameterizedTest + @MethodSource("subsetOfPendingTransactionsIncludedWhenTxSelectionMaxTimeIsOver") + public void invalidPendingTransactionsThatTakesTooLongToEvaluateIsDroppedFromThePool( + final boolean isPoa, + final boolean preProcessingTooLate, + final boolean processingTooLate, + final boolean postProcessingTooLate) { + + internalBlockSelectionTimeoutSimulationInvalidTxs( + isPoa, + preProcessingTooLate, + processingTooLate, + postProcessingTooLate, + 900, + INVALID_TX_EVALUATION_TOO_LONG, + true, + NONCE_TOO_LOW); + } + + private void internalBlockSelectionTimeoutSimulationInvalidTxs( + final boolean isPoa, + final boolean preProcessingTooLate, + final boolean processingTooLate, + final boolean postProcessingTooLate, + final long longProcessingTxTime, + final TransactionSelectionResult longProcessingTxResult, + final boolean isLongProcessingTxDropped, + final TransactionInvalidReason txInvalidReason) { + + final int txCount = 3; + final long fastProcessingTxTime = 200; + final var invalidSelectionResult = TransactionSelectionResult.invalid(txInvalidReason.name()); + + final Supplier> inTime = () -> invocation -> SELECTED; + + final BiFunction> tooLate = + (p, t) -> + invocation -> { + final org.hyperledger.besu.ethereum.blockcreation.txselection + .TransactionEvaluationContext + ctx = invocation.getArgument(0); + if (ctx.getTransaction().equals(p)) { + Thread.sleep(t); + } else { + Thread.sleep(fastProcessingTxTime); + } + return invalidSelectionResult; + }; + + final ProcessableBlockHeader blockHeader = createBlock(301_000); + final Address miningBeneficiary = AddressHelpers.ofValue(1); + final int poaGenesisBlockPeriod = 1; + final int blockTxsSelectionMaxTime = 750; + + final List transactionsToInject = new ArrayList<>(txCount); + for (int i = 0; i < txCount - 1; i++) { + final Transaction tx = createTransaction(i, Wei.of(7), 100_000); + transactionsToInject.add(tx); + if (processingTooLate) { + ensureTransactionIsInvalid(tx, txInvalidReason, fastProcessingTxTime); + } else { + ensureTransactionIsValid(tx); + } + } + + final Transaction lateTx = createTransaction(2, Wei.of(7), 100_000); + transactionsToInject.add(lateTx); + if (processingTooLate) { + ensureTransactionIsInvalid(lateTx, txInvalidReason, longProcessingTxTime); + } else { + ensureTransactionIsValid(lateTx); + } + + PluginTransactionSelector transactionSelector = mock(PluginTransactionSelector.class); + when(transactionSelector.evaluateTransactionPreProcessing(any())) + .thenAnswer( + preProcessingTooLate ? tooLate.apply(lateTx, longProcessingTxTime) : inTime.get()); + + when(transactionSelector.evaluateTransactionPostProcessing(any(), any())) + .thenAnswer( + postProcessingTooLate ? tooLate.apply(lateTx, longProcessingTxTime) : inTime.get()); + + final PluginTransactionSelectorFactory transactionSelectorFactory = + mock(PluginTransactionSelectorFactory.class); + when(transactionSelectorFactory.create()).thenReturn(transactionSelector); + + transactionSelectionService.registerPluginTransactionSelectorFactory( + transactionSelectorFactory); + + final BlockTransactionSelector selector = + createBlockSelectorAndSetupTxPool( + isPoa + ? createMiningParameters( + transactionSelectionService, + Wei.ZERO, + MIN_OCCUPANCY_100_PERCENT, + poaGenesisBlockPeriod, + PositiveNumber.fromInt(75)) + : createMiningParameters( + transactionSelectionService, + Wei.ZERO, + MIN_OCCUPANCY_100_PERCENT, + PositiveNumber.fromInt(blockTxsSelectionMaxTime)), + transactionProcessor, + blockHeader, + miningBeneficiary, + Wei.ZERO, + transactionSelectionService); + + transactionPool.addRemoteTransactions(transactionsToInject); + + final TransactionSelectionResults results = selector.buildTransactionListForBlock(); + + // no tx is selected since all are invalid + assertThat(results.getSelectedTransactions()).isEmpty(); + + // all txs are not selected so wait until all are evaluated + // before checking the results + await().until(() -> results.getNotSelectedTransactions().size() == transactionsToInject.size()); + final var expectedEntries = new HashMap(); + for (int i = 0; i < txCount - 1; i++) { + expectedEntries.put( + transactionsToInject.get(i), TransactionSelectionResult.invalid(txInvalidReason.name())); + } + expectedEntries.put(lateTx, longProcessingTxResult); + assertThat(results.getNotSelectedTransactions()) + .containsExactlyInAnyOrderEntriesOf(expectedEntries); + + assertThat(transactionPool.getTransactionByHash(lateTx.getHash()).isEmpty()) + .isEqualTo(isLongProcessingTxDropped ? true : false); + } + private static Stream subsetOfPendingTransactionsIncludedWhenTxSelectionMaxTimeIsOver() { @@ -1027,7 +1236,7 @@ protected BlockTransactionSelector createBlockSelectorAndSetupTxPool( final ProcessableBlockHeader blockHeader, final Address miningBeneficiary, final Wei blobGasPrice, - final PluginTransactionSelectorFactory transactionSelectorFactory) { + final TransactionSelectionService transactionSelectionService) { transactionPool = createTransactionPool(); @@ -1037,7 +1246,7 @@ protected BlockTransactionSelector createBlockSelectorAndSetupTxPool( blockHeader, miningBeneficiary, blobGasPrice, - transactionSelectorFactory); + transactionSelectionService); } protected BlockTransactionSelector createBlockSelector( @@ -1046,7 +1255,7 @@ protected BlockTransactionSelector createBlockSelector( final ProcessableBlockHeader blockHeader, final Address miningBeneficiary, final Wei blobGasPrice, - final PluginTransactionSelectorFactory transactionSelectorFactory) { + final TransactionSelectionService transactionSelectionService) { final BlockTransactionSelector selector = new BlockTransactionSelector( @@ -1063,7 +1272,8 @@ protected BlockTransactionSelector createBlockSelector( getFeeMarket(), new LondonGasCalculator(), GasLimitCalculator.constant(), - transactionSelectorFactory.create(), + protocolSchedule.getByBlockHeader(blockHeader).getBlockHashProcessor(), + transactionSelectionService.createPluginTransactionSelector(), ethScheduler); return selector; @@ -1136,7 +1346,7 @@ protected void ensureTransactionIsValid( final long gasRemaining, final long processingTime) { when(transactionProcessor.processTransaction( - any(), any(), any(), eq(tx), any(), any(), any(), anyBoolean(), any(), any())) + any(), any(), eq(tx), any(), any(), any(), anyBoolean(), any(), any())) .thenAnswer( invocation -> { if (processingTime > 0) { @@ -1153,9 +1363,22 @@ protected void ensureTransactionIsValid( protected void ensureTransactionIsInvalid( final Transaction tx, final TransactionInvalidReason invalidReason) { + ensureTransactionIsInvalid(tx, invalidReason, 0); + } + + protected void ensureTransactionIsInvalid( + final Transaction tx, + final TransactionInvalidReason invalidReason, + final long processingTime) { when(transactionProcessor.processTransaction( - any(), any(), any(), eq(tx), any(), any(), any(), anyBoolean(), any(), any())) - .thenReturn(TransactionProcessingResult.invalid(ValidationResult.invalid(invalidReason))); + any(), any(), eq(tx), any(), any(), any(), anyBoolean(), any(), any())) + .thenAnswer( + invocation -> { + if (processingTime > 0) { + Thread.sleep(processingTime); + } + return TransactionProcessingResult.invalid(ValidationResult.invalid(invalidReason)); + }); } private BlockHeader blockHeader(final long number) { @@ -1163,47 +1386,52 @@ private BlockHeader blockHeader(final long number) { } protected MiningParameters createMiningParameters( - final Wei minGasPrice, final double minBlockOccupancyRatio, final long txsSelectionMaxTime) { + final TransactionSelectionService transactionSelectionService, + final Wei minGasPrice, + final double minBlockOccupancyRatio, + final PositiveNumber txsSelectionMaxTime) { return ImmutableMiningParameters.builder() .mutableInitValues( MutableInitValues.builder() .minTransactionGasPrice(minGasPrice) .minBlockOccupancyRatio(minBlockOccupancyRatio) .build()) - .unstable(Unstable.builder().nonPoaBlockTxsSelectionMaxTime(txsSelectionMaxTime).build()) + .transactionSelectionService(transactionSelectionService) + .nonPoaBlockTxsSelectionMaxTime(txsSelectionMaxTime) .build(); } protected MiningParameters createMiningParameters( + final TransactionSelectionService transactionSelectionService, final Wei minGasPrice, final double minBlockOccupancyRatio, - final int minBlockTime, - final Percentage minBlockTimePercentage) { + final int genesisBlockPeriodSeconds, + final PositiveNumber minBlockTimePercentage) { return ImmutableMiningParameters.builder() .mutableInitValues( MutableInitValues.builder() .minTransactionGasPrice(minGasPrice) .minBlockOccupancyRatio(minBlockOccupancyRatio) + .blockPeriodSeconds(genesisBlockPeriodSeconds) .build()) - .unstable( - Unstable.builder() - .minBlockTime(minBlockTime) - .poaBlockTxsSelectionMaxTime(minBlockTimePercentage) - .build()) + .transactionSelectionService(transactionSelectionService) + .poaBlockTxsSelectionMaxTime(minBlockTimePercentage) .build(); } private static class PluginTransactionSelectionResult extends TransactionSelectionResult { private enum PluginStatus implements Status { - PLUGIN_INVALID(false, true), - PLUGIN_INVALID_TRANSIENT(false, false); + PLUGIN_INVALID(false, true, false), + PLUGIN_INVALID_TRANSIENT(false, false, true); private final boolean stop; private final boolean discard; + private final boolean penalize; - PluginStatus(final boolean stop, final boolean discard) { + PluginStatus(final boolean stop, final boolean discard, final boolean penalize) { this.stop = stop; this.discard = discard; + this.penalize = penalize; } @Override @@ -1215,6 +1443,11 @@ public boolean stop() { public boolean discard() { return discard; } + + @Override + public boolean penalize() { + return penalize; + } } public static final TransactionSelectionResult GENERIC_PLUGIN_INVALID_TRANSIENT = diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/BlockMinerTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/BlockMinerTest.java index 681991ee75c..e5b46925aad 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/BlockMinerTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/BlockMinerTest.java @@ -25,6 +25,7 @@ import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.blockcreation.BlockCreator.BlockCreationResult; import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MinedBlockObserver; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockBody; @@ -56,13 +57,16 @@ public void blockCreatedIsAddedToBlockChain() throws InterruptedException { new Block( headerBuilder.buildHeader(), new BlockBody(Lists.newArrayList(), Lists.newArrayList())); - final ProtocolContext protocolContext = new ProtocolContext(null, null, null, Optional.empty()); + final ProtocolContext protocolContext = + new ProtocolContext(null, null, null, new BadBlockManager()); final PoWBlockCreator blockCreator = mock(PoWBlockCreator.class); final Function blockCreatorSupplier = (parentHeader) -> blockCreator; - when(blockCreator.createBlock(anyLong())) - .thenReturn(new BlockCreationResult(blockToCreate, new TransactionSelectionResults())); + when(blockCreator.createBlock(anyLong(), any())) + .thenReturn( + new BlockCreationResult( + blockToCreate, new TransactionSelectionResults(), new BlockCreationTiming())); final BlockImporter blockImporter = mock(BlockImporter.class); final ProtocolSpec protocolSpec = mock(ProtocolSpec.class); @@ -97,13 +101,16 @@ public void failureToImportDoesNotTriggerObservers() throws InterruptedException new Block( headerBuilder.buildHeader(), new BlockBody(Lists.newArrayList(), Lists.newArrayList())); - final ProtocolContext protocolContext = new ProtocolContext(null, null, null, Optional.empty()); + final ProtocolContext protocolContext = + new ProtocolContext(null, null, null, new BadBlockManager()); final PoWBlockCreator blockCreator = mock(PoWBlockCreator.class); final Function blockCreatorSupplier = (parentHeader) -> blockCreator; - when(blockCreator.createBlock(anyLong())) - .thenReturn(new BlockCreationResult(blockToCreate, new TransactionSelectionResults())); + when(blockCreator.createBlock(anyLong(), any())) + .thenReturn( + new BlockCreationResult( + blockToCreate, new TransactionSelectionResults(), new BlockCreationTiming())); final BlockImporter blockImporter = mock(BlockImporter.class); final ProtocolSpec protocolSpec = mock(ProtocolSpec.class); @@ -142,13 +149,16 @@ public void blockValidationFailureBeforeImportDoesNotImportBlock() throws Interr new Block( headerBuilder.buildHeader(), new BlockBody(Lists.newArrayList(), Lists.newArrayList())); - final ProtocolContext protocolContext = new ProtocolContext(null, null, null, Optional.empty()); + final ProtocolContext protocolContext = + new ProtocolContext(null, null, null, new BadBlockManager()); final PoWBlockCreator blockCreator = mock(PoWBlockCreator.class); final Function blockCreatorSupplier = (parentHeader) -> blockCreator; - when(blockCreator.createBlock(anyLong())) - .thenReturn(new BlockCreationResult(blockToCreate, new TransactionSelectionResults())); + when(blockCreator.createBlock(anyLong(), any())) + .thenReturn( + new BlockCreationResult( + blockToCreate, new TransactionSelectionResults(), new BlockCreationTiming())); final BlockImporter blockImporter = mock(BlockImporter.class); final ProtocolSpec protocolSpec = mock(ProtocolSpec.class); diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/DefaultBlockSchedulerTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/DefaultBlockSchedulerTest.java index 0bb0ba1dd1a..a31f1cacf94 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/DefaultBlockSchedulerTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/DefaultBlockSchedulerTest.java @@ -47,8 +47,8 @@ public void canMineBlockOnLimitOfClockDrift() { final AbstractBlockScheduler.BlockCreationTimeResult result = scheduler.getNextTimestamp(parentBlock); - assertThat(result.getTimestampForHeader()).isEqualTo(parentTimeStamp + interBlockSeconds); - assertThat(result.getMillisecondsUntilValid()).isEqualTo(0); + assertThat(result.timestampForHeader()).isEqualTo(parentTimeStamp + interBlockSeconds); + assertThat(result.millisecondsUntilValid()).isEqualTo(0); } @Test @@ -64,7 +64,7 @@ public void childBlockWithinClockDriftReportsTimeToValidOfZero() { final AbstractBlockScheduler.BlockCreationTimeResult result = scheduler.getNextTimestamp(parentBlock); - assertThat(result.getMillisecondsUntilValid()).isEqualTo(0); + assertThat(result.millisecondsUntilValid()).isEqualTo(0); } @Test @@ -81,7 +81,7 @@ public void mustWaitForNextBlockIfTooFarAheadOfSystemTime() { final AbstractBlockScheduler.BlockCreationTimeResult result = scheduler.getNextTimestamp(parentBlock); - assertThat(result.getMillisecondsUntilValid()).isEqualTo(interBlockSeconds * 1000); + assertThat(result.millisecondsUntilValid()).isEqualTo(interBlockSeconds * 1000); } @Test @@ -99,6 +99,6 @@ public void ifParentTimestampIsBehindCurrentTimeChildUsesCurrentTime() { final AbstractBlockScheduler.BlockCreationTimeResult result = scheduler.getNextTimestamp(parentBlock); - assertThat(result.getTimestampForHeader()).isEqualTo(secondsSinceEpoch); + assertThat(result.timestampForHeader()).isEqualTo(secondsSinceEpoch); } } diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/HashRateMiningCoordinatorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/HashRateMiningCoordinatorTest.java index 985870c6fba..73e122b6203 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/HashRateMiningCoordinatorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/HashRateMiningCoordinatorTest.java @@ -24,6 +24,7 @@ import java.util.Collection; import java.util.UUID; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -62,4 +63,11 @@ public void test( assertThat(miningCoordinator.hashesPerSecond()).contains(wantTotalHashrate); } } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LegacyFeeMarketBlockTransactionSelectorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LegacyFeeMarketBlockTransactionSelectorTest.java index fdda1b7f4d2..940d076bbc3 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LegacyFeeMarketBlockTransactionSelectorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LegacyFeeMarketBlockTransactionSelectorTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -21,8 +21,11 @@ import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; @@ -34,6 +37,7 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.testutil.TestClock; import org.hyperledger.besu.util.number.Fraction; @@ -45,8 +49,7 @@ public class LegacyFeeMarketBlockTransactionSelectorTest @Override protected GenesisConfigFile getGenesisConfigFile() { - return GenesisConfigFile.genesisFileFromResources( - "/block-transaction-selector/gas-price-genesis.json"); + return GenesisConfigFile.fromResource("/block-transaction-selector/gas-price-genesis.json"); } @Override @@ -57,7 +60,11 @@ protected ProtocolSchedule createProtocolSchedule() { ProtocolSpecAdapters.create(0, Function.identity()), new PrivacyParameters(), false, - EvmConfiguration.DEFAULT) + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()) .createProtocolSchedule(); } @@ -89,7 +96,7 @@ protected TransactionPool createTransactionPool() { ethContext, new TransactionPoolMetrics(metricsSystem), poolConf, - null); + new BlobCache()); transactionPool.setEnabled(); return transactionPool; } diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LondonFeeMarketBlockTransactionSelectorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LondonFeeMarketBlockTransactionSelectorTest.java index 63e0d54ecd7..68d9a71de6f 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LondonFeeMarketBlockTransactionSelectorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LondonFeeMarketBlockTransactionSelectorTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -16,7 +16,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; -import static org.hyperledger.besu.ethereum.core.MiningParameters.Unstable.DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME; +import static org.hyperledger.besu.ethereum.core.MiningParameters.DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME; import static org.mockito.Mockito.mock; import org.hyperledger.besu.config.GenesisConfigFile; @@ -24,12 +24,14 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockTransactionSelector; import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.AddressHelpers; import org.hyperledger.besu.ethereum.core.ImmutableMiningParameters; import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; @@ -42,6 +44,7 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.data.TransactionSelectionResult; import org.hyperledger.besu.testutil.TestClock; import org.hyperledger.besu.util.number.Fraction; @@ -57,8 +60,7 @@ public class LondonFeeMarketBlockTransactionSelectorTest @Override protected GenesisConfigFile getGenesisConfigFile() { - return GenesisConfigFile.genesisFileFromResources( - "/block-transaction-selector/london-genesis.json"); + return GenesisConfigFile.fromResource("/block-transaction-selector/london-genesis.json"); } @Override @@ -69,7 +71,11 @@ protected ProtocolSchedule createProtocolSchedule() { ProtocolSpecAdapters.create(0, Function.identity()), new PrivacyParameters(), false, - EvmConfiguration.DEFAULT) + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()) .createProtocolSchedule(); } @@ -97,7 +103,7 @@ protected TransactionPool createTransactionPool() { ethContext, new TransactionPoolMetrics(metricsSystem), poolConf, - null); + new BlobCache()); transactionPool.setEnabled(); return transactionPool; } @@ -110,12 +116,15 @@ public void eip1559TransactionCurrentGasPriceLessThanMinimumIsSkippedAndKeptInTh final BlockTransactionSelector selector = createBlockSelectorAndSetupTxPool( createMiningParameters( - Wei.of(6), MIN_OCCUPANCY_80_PERCENT, DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME), + transactionSelectionService, + Wei.of(6), + MIN_OCCUPANCY_80_PERCENT, + DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME), transactionProcessor, blockHeader, miningBeneficiary, Wei.ZERO, - NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + transactionSelectionService); // tx is willing to pay max 7 wei for gas, but current network condition (baseFee == 1) // result in it paying 2 wei, that is below the minimum accepted by the node, so it is skipped @@ -139,12 +148,15 @@ public void eip1559TransactionCurrentGasPriceGreaterThanMinimumIsSelected() { final BlockTransactionSelector selector = createBlockSelectorAndSetupTxPool( createMiningParameters( - Wei.of(6), MIN_OCCUPANCY_80_PERCENT, DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME), + transactionSelectionService, + Wei.of(6), + MIN_OCCUPANCY_80_PERCENT, + DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME), transactionProcessor, blockHeader, miningBeneficiary, Wei.ZERO, - NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + transactionSelectionService); // tx is willing to pay max 7 wei for gas, and current network condition (baseFee == 5) // result in it paying the max, that is >= the minimum accepted by the node, so it is selected @@ -167,12 +179,15 @@ public void eip1559PriorityTransactionCurrentGasPriceLessThanMinimumIsSelected() final BlockTransactionSelector selector = createBlockSelectorAndSetupTxPool( createMiningParameters( - Wei.of(6), MIN_OCCUPANCY_80_PERCENT, DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME), + transactionSelectionService, + Wei.of(6), + MIN_OCCUPANCY_80_PERCENT, + DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME), transactionProcessor, blockHeader, miningBeneficiary, Wei.ZERO, - NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + transactionSelectionService); // tx is willing to pay max 7 wei for gas, but current network condition (baseFee == 1) // result in it paying 2 wei, that is below the minimum accepted by the node, but since it is @@ -211,7 +226,7 @@ public void transactionFromSameSenderWithMixedTypes() { blockHeader, miningBeneficiary, Wei.ZERO, - NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + transactionSelectionService); transactionPool.addRemoteTransactions(List.of(txFrontier1, txLondon1, txFrontier2, txLondon2)); @@ -252,7 +267,7 @@ public void shouldNotSelectTransactionsWithPriorityFeeLessThanConfig() { blockHeader, AddressHelpers.ofValue(1), Wei.ZERO, - NO_PLUGIN_TRANSACTION_SELECTOR_FACTORY); + transactionSelectionService); transactionPool.addRemoteTransactions( List.of(txSelected1, txNotSelected1, txSelected2, txNotSelected2)); diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/MinPriorityFeePerGasTransactionSelectorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/MinPriorityFeePerGasTransactionSelectorTest.java index a86dc73ee46..6f81f4df2c6 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/MinPriorityFeePerGasTransactionSelectorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/MinPriorityFeePerGasTransactionSelectorTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -21,6 +21,7 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext; +import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionEvaluationContext; import org.hyperledger.besu.ethereum.blockcreation.txselection.selectors.AbstractTransactionSelector; import org.hyperledger.besu.ethereum.blockcreation.txselection.selectors.MinPriorityFeePerGasTransactionSelector; import org.hyperledger.besu.ethereum.core.MiningParameters; @@ -29,13 +30,19 @@ import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.plugin.data.TransactionSelectionResult; +import com.google.common.base.Stopwatch; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +@ExtendWith(MockitoExtension.class) public class MinPriorityFeePerGasTransactionSelectorTest { private AbstractTransactionSelector transactionSelector; private final int minPriorityFeeParameter = 7; + @Mock private ProcessableBlockHeader pendingBlockHeader; @BeforeEach public void initialize() { @@ -43,57 +50,53 @@ public void initialize() { MiningParameters.newDefault().setMinPriorityFeePerGas(Wei.of(minPriorityFeeParameter)); BlockSelectionContext context = new BlockSelectionContext( - miningParameters, - null, - null, - mock(ProcessableBlockHeader.class), - null, - null, - null, - null); + miningParameters, null, null, null, pendingBlockHeader, null, null, null, null); transactionSelector = new MinPriorityFeePerGasTransactionSelector(context); } @Test public void shouldNotSelectWhen_PriorityFeePerGas_IsLessThan_MinPriorityFeePerGas() { - var transaction = mockTransactionWithPriorityFee(minPriorityFeeParameter - 1); + var transaction = mockTransactionEvaluationContext(minPriorityFeeParameter - 1); assertSelectionResult( transaction, TransactionSelectionResult.PRIORITY_FEE_PER_GAS_BELOW_CURRENT_MIN); } @Test public void shouldSelectWhen_PriorityFeePerGas_IsEqual_MinPriorityFeePerGas() { - var transaction = mockTransactionWithPriorityFee(minPriorityFeeParameter); + var transaction = mockTransactionEvaluationContext(minPriorityFeeParameter); assertSelectionResult(transaction, TransactionSelectionResult.SELECTED); } @Test public void shouldSelectWhen_PriorityFeePerGas_IsGreaterThan_MinPriorityFeePerGas() { - var transaction = mockTransactionWithPriorityFee(minPriorityFeeParameter + 1); + var transaction = mockTransactionEvaluationContext(minPriorityFeeParameter + 1); assertSelectionResult(transaction, TransactionSelectionResult.SELECTED); } @Test public void shouldSelectWhenPrioritySender() { - var prioritySenderTransaction = mockTransactionWithPriorityFee(minPriorityFeeParameter - 1); + final var evaluationContext = mockTransactionEvaluationContext(minPriorityFeeParameter - 1); assertSelectionResult( - prioritySenderTransaction, - TransactionSelectionResult.PRIORITY_FEE_PER_GAS_BELOW_CURRENT_MIN); - when(prioritySenderTransaction.hasPriority()).thenReturn(true); - assertSelectionResult(prioritySenderTransaction, TransactionSelectionResult.SELECTED); + evaluationContext, TransactionSelectionResult.PRIORITY_FEE_PER_GAS_BELOW_CURRENT_MIN); + when(evaluationContext.getPendingTransaction().hasPriority()).thenReturn(true); + assertSelectionResult(evaluationContext, TransactionSelectionResult.SELECTED); } private void assertSelectionResult( - final PendingTransaction transaction, final TransactionSelectionResult expectedResult) { - var actualResult = transactionSelector.evaluateTransactionPreProcessing(transaction, null); + final TransactionEvaluationContext evaluationContext, + final TransactionSelectionResult expectedResult) { + var actualResult = + transactionSelector.evaluateTransactionPreProcessing(evaluationContext, null); assertThat(actualResult).isEqualTo(expectedResult); } - private PendingTransaction mockTransactionWithPriorityFee(final int priorityFeePerGas) { - PendingTransaction mockTransaction = mock(PendingTransaction.class); + private TransactionEvaluationContext mockTransactionEvaluationContext( + final int priorityFeePerGas) { + PendingTransaction pendingTransaction = mock(PendingTransaction.class); Transaction transaction = mock(Transaction.class); - when(mockTransaction.getTransaction()).thenReturn(transaction); + when(pendingTransaction.getTransaction()).thenReturn(transaction); when(transaction.getEffectivePriorityFeePerGas(any())).thenReturn(Wei.of(priorityFeePerGas)); - return mockTransaction; + return new TransactionEvaluationContext( + pendingBlockHeader, pendingTransaction, Stopwatch.createStarted(), Wei.ONE, Wei.ONE); } } diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreatorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreatorTest.java index 3e50328dd43..509efd7b19b 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreatorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreatorTest.java @@ -21,12 +21,12 @@ import static org.mockito.Mockito.when; import org.hyperledger.besu.config.GenesisConfigFile; -import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.blockcreation.BlockCreator.BlockCreationResult; import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; import org.hyperledger.besu.ethereum.core.Difficulty; @@ -37,7 +37,9 @@ import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; +import org.hyperledger.besu.ethereum.difficulty.fixed.FixedDifficultyCalculators; import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -74,6 +76,7 @@ class PoWBlockCreatorTest extends AbstractBlockCreatorTest { private static final long BLOCK_1_TIMESTAMP = Long.parseUnsignedLong("55ba4224", 16); private static final long BLOCK_1_NONCE = Long.parseLong("539bd4979fef1ec4", 16); + private static final long FIXED_DIFFICULTY_NONCE = 26; private static final Bytes BLOCK_1_EXTRA_DATA = Bytes.fromHexString("0x476574682f76312e302e302f6c696e75782f676f312e342e32"); @@ -81,22 +84,27 @@ class PoWBlockCreatorTest extends AbstractBlockCreatorTest { @Test void createMainnetBlock1() throws IOException { - final GenesisConfigOptions genesisConfigOptions = GenesisConfigFile.DEFAULT.getConfigOptions(); + final var genesisConfigFile = GenesisConfigFile.mainnet(); + + final MiningParameters miningParameters = createMiningParameters(BLOCK_1_NONCE); + final ExecutionContextTestFixture executionContextTestFixture = - ExecutionContextTestFixture.builder() + ExecutionContextTestFixture.builder(genesisConfigFile) .protocolSchedule( new ProtocolScheduleBuilder( - genesisConfigOptions, + genesisConfigFile.getConfigOptions(), BigInteger.valueOf(42), ProtocolSpecAdapters.create(0, Function.identity()), PrivacyParameters.DEFAULT, false, - EvmConfiguration.DEFAULT) + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()) .createProtocolSchedule()) .build(); - final MiningParameters miningParameters = createMiningParameters(); - final PoWSolver solver = new PoWSolver( miningParameters, @@ -115,13 +123,14 @@ void createMainnetBlock1() throws IOException { executionContextTestFixture.getProtocolContext(), executionContextTestFixture.getProtocolSchedule(), solver, - executionContextTestFixture.getBlockchain().getChainHeadHeader(), ethScheduler); // A Hashrate should not exist in the block creator prior to creating a block assertThat(blockCreator.getHashesPerSecond()).isNotPresent(); - final BlockCreationResult blockResult = blockCreator.createBlock(BLOCK_1_TIMESTAMP); + final BlockCreationResult blockResult = + blockCreator.createBlock( + BLOCK_1_TIMESTAMP, executionContextTestFixture.getBlockchain().getChainHeadHeader()); final Block actualBlock = blockResult.getBlock(); final Block expectedBlock = ValidationTestUtils.readBlock(1); @@ -133,24 +142,33 @@ void createMainnetBlock1() throws IOException { @Test void createMainnetBlock1_fixedDifficulty1() { - final GenesisConfigOptions genesisConfigOptions = - GenesisConfigFile.fromConfig("{\"config\": {\"ethash\": {\"fixeddifficulty\":1}}}") - .getConfigOptions(); + final var genesisConfigFile = + GenesisConfigFile.fromResource("/block-creation-fixed-difficulty-genesis.json"); + + final MiningParameters miningParameters = createMiningParameters(FIXED_DIFFICULTY_NONCE); + final ExecutionContextTestFixture executionContextTestFixture = - ExecutionContextTestFixture.builder() + ExecutionContextTestFixture.builder(genesisConfigFile) .protocolSchedule( new ProtocolScheduleBuilder( - genesisConfigOptions, + genesisConfigFile.getConfigOptions(), BigInteger.valueOf(42), - ProtocolSpecAdapters.create(0, Function.identity()), + ProtocolSpecAdapters.create( + 0, + specBuilder -> + specBuilder.difficultyCalculator( + FixedDifficultyCalculators.calculator( + genesisConfigFile.getConfigOptions()))), PrivacyParameters.DEFAULT, false, - EvmConfiguration.DEFAULT) + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()) .createProtocolSchedule()) .build(); - final MiningParameters miningParameters = createMiningParameters(); - final PoWSolver solver = new PoWSolver( miningParameters, @@ -169,32 +187,46 @@ void createMainnetBlock1_fixedDifficulty1() { executionContextTestFixture.getProtocolContext(), executionContextTestFixture.getProtocolSchedule(), solver, - executionContextTestFixture.getBlockchain().getChainHeadHeader(), ethScheduler); - assertThat(blockCreator.createBlock(BLOCK_1_TIMESTAMP)).isNotNull(); + assertThat( + blockCreator.createBlock( + BLOCK_1_TIMESTAMP, + executionContextTestFixture.getBlockchain().getChainHeadHeader())) + .isNotNull(); // If we weren't setting difficulty to 2^256-1 a difficulty of 1 would have caused a // IllegalArgumentException at the previous line, as 2^256 is 33 bytes. } @Test void rewardBeneficiary_zeroReward_skipZeroRewardsFalse() { - final GenesisConfigOptions genesisConfigOptions = - GenesisConfigFile.fromConfig("{\"config\": {\"ethash\": {\"fixeddifficulty\":1}}}") - .getConfigOptions(); + final var genesisConfigFile = + GenesisConfigFile.fromResource("/block-creation-fixed-difficulty-genesis.json"); + + final MiningParameters miningParameters = createMiningParameters(FIXED_DIFFICULTY_NONCE); + ProtocolSchedule protocolSchedule = new ProtocolScheduleBuilder( - genesisConfigOptions, + genesisConfigFile.getConfigOptions(), BigInteger.valueOf(42), - ProtocolSpecAdapters.create(0, Function.identity()), + ProtocolSpecAdapters.create( + 0, + specBuilder -> + specBuilder.difficultyCalculator( + FixedDifficultyCalculators.calculator( + genesisConfigFile.getConfigOptions()))), PrivacyParameters.DEFAULT, false, - EvmConfiguration.DEFAULT) + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()) .createProtocolSchedule(); final ExecutionContextTestFixture executionContextTestFixture = - ExecutionContextTestFixture.builder().protocolSchedule(protocolSchedule).build(); - - final MiningParameters miningParameters = createMiningParameters(); + ExecutionContextTestFixture.builder(genesisConfigFile) + .protocolSchedule(protocolSchedule) + .build(); final PoWSolver solver = new PoWSolver( @@ -214,7 +246,6 @@ void rewardBeneficiary_zeroReward_skipZeroRewardsFalse() { executionContextTestFixture.getProtocolContext(), executionContextTestFixture.getProtocolSchedule(), solver, - executionContextTestFixture.getBlockchain().getChainHeadHeader(), ethScheduler); final MutableWorldState mutableWorldState = @@ -246,22 +277,33 @@ void rewardBeneficiary_zeroReward_skipZeroRewardsFalse() { @Test void rewardBeneficiary_zeroReward_skipZeroRewardsTrue() { - final GenesisConfigOptions genesisConfigOptions = - GenesisConfigFile.fromConfig("{\"config\": {\"ethash\": {\"fixeddifficulty\":1}}}") - .getConfigOptions(); + final var genesisConfigFile = + GenesisConfigFile.fromResource("/block-creation-fixed-difficulty-genesis.json"); + + final MiningParameters miningParameters = createMiningParameters(FIXED_DIFFICULTY_NONCE); + ProtocolSchedule protocolSchedule = new ProtocolScheduleBuilder( - genesisConfigOptions, + genesisConfigFile.getConfigOptions(), BigInteger.valueOf(42), - ProtocolSpecAdapters.create(0, Function.identity()), + ProtocolSpecAdapters.create( + 0, + specBuilder -> + specBuilder.difficultyCalculator( + FixedDifficultyCalculators.calculator( + genesisConfigFile.getConfigOptions()))), PrivacyParameters.DEFAULT, false, - EvmConfiguration.DEFAULT) + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()) .createProtocolSchedule(); final ExecutionContextTestFixture executionContextTestFixture = - ExecutionContextTestFixture.builder().protocolSchedule(protocolSchedule).build(); - - final MiningParameters miningParameters = createMiningParameters(); + ExecutionContextTestFixture.builder(genesisConfigFile) + .protocolSchedule(protocolSchedule) + .build(); final PoWSolver solver = new PoWSolver( @@ -281,7 +323,6 @@ void rewardBeneficiary_zeroReward_skipZeroRewardsTrue() { executionContextTestFixture.getProtocolContext(), executionContextTestFixture.getProtocolSchedule(), solver, - executionContextTestFixture.getBlockchain().getChainHeadHeader(), ethScheduler); final MutableWorldState mutableWorldState = @@ -334,17 +375,18 @@ private TransactionPool createTransactionPool( ethContext, new TransactionPoolMetrics(metricsSystem), poolConf, - null); + new BlobCache()); transactionPool.setEnabled(); return transactionPool; } - private MiningParameters createMiningParameters() { + private MiningParameters createMiningParameters(final long nonce) { return ImmutableMiningParameters.builder() .mutableInitValues( MutableInitValues.builder() - .nonceGenerator(Lists.newArrayList(BLOCK_1_NONCE)) + .nonceGenerator(Lists.newArrayList(nonce)) + // .nonceGenerator(new IncrementingNonceGenerator(0)) .extraData(BLOCK_1_EXTRA_DATA) .minTransactionGasPrice(Wei.ONE) .coinbase(BLOCK_1_COINBASE) diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWMinerExecutorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWMinerExecutorTest.java index 84c46ea6afb..8c1e217d759 100644 --- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWMinerExecutorTest.java +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWMinerExecutorTest.java @@ -25,6 +25,7 @@ import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; +import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionBroadcaster; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -60,7 +61,7 @@ public void startingMiningWithoutCoinbaseThrowsException() { null, transactionPool, miningParameters, - new DefaultBlockScheduler(1, 10, TestClock.fixed()), + new DefaultBlockScheduler(1L, 10, TestClock.fixed()), new EpochCalculator.DefaultEpochCalculator(), ethScheduler); @@ -118,7 +119,7 @@ private TransactionPool createTransactionPool() { ethContext, new TransactionPoolMetrics(new NoOpMetricsSystem()), poolConf, - null); + new BlobCache()); transactionPool.setEnabled(); return transactionPool; diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlobSizeTransactionSelectorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlobSizeTransactionSelectorTest.java new file mode 100644 index 00000000000..123faf2d18a --- /dev/null +++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlobSizeTransactionSelectorTest.java @@ -0,0 +1,196 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.Answers.RETURNS_DEEP_STUBS; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.crypto.KeyPair; +import org.hyperledger.besu.crypto.SignatureAlgorithm; +import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Blob; +import org.hyperledger.besu.datatypes.BlobsWithCommitments; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.KZGCommitment; +import org.hyperledger.besu.datatypes.KZGProof; +import org.hyperledger.besu.datatypes.TransactionType; +import org.hyperledger.besu.datatypes.VersionedHash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext; +import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionEvaluationContext; +import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.TransactionTestFixture; +import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; +import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator; +import org.hyperledger.besu.plugin.data.TransactionSelectionResult; + +import java.util.Optional; +import java.util.stream.IntStream; + +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes48; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class BlobSizeTransactionSelectorTest { + private static final Supplier SIGNATURE_ALGORITHM = + Suppliers.memoize(SignatureAlgorithmFactory::getInstance); + private static final KeyPair KEYS = SIGNATURE_ALGORITHM.get().generateKeyPair(); + + private static final long BLOB_GAS_PER_BLOB = CancunGasCalculator.BLOB_GAS_PER_BLOB; + private static final int MAX_BLOBS = 6; + private static final long MAX_BLOB_GAS = BLOB_GAS_PER_BLOB * MAX_BLOBS; + + @Mock(answer = RETURNS_DEEP_STUBS) + BlockSelectionContext blockSelectionContext; + + @Mock TransactionSelectionResults selectionResults; + + @Test + void notBlobTransactionsAreSelectedWithoutAnyCheck() { + final var selector = new BlobSizeTransactionSelector(blockSelectionContext); + + final var nonBlobTx = createEIP1559PendingTransaction(); + + final var txEvaluationContext = + new TransactionEvaluationContext( + blockSelectionContext.pendingBlockHeader(), nonBlobTx, null, null, null); + + final var result = + selector.evaluateTransactionPreProcessing(txEvaluationContext, selectionResults); + assertThat(result).isEqualTo(TransactionSelectionResult.SELECTED); + verifyNoInteractions(selectionResults); + } + + @Test + void firstBlobTransactionIsSelected() { + when(blockSelectionContext.gasLimitCalculator().currentBlobGasLimit()).thenReturn(MAX_BLOB_GAS); + when(blockSelectionContext.gasCalculator().blobGasCost(anyInt())) + .thenAnswer(iom -> BLOB_GAS_PER_BLOB * iom.getArgument(0, Integer.class)); + + final var selector = new BlobSizeTransactionSelector(blockSelectionContext); + + final var firstBlobTx = createBlobPendingTransaction(MAX_BLOBS); + + final var txEvaluationContext = + new TransactionEvaluationContext( + blockSelectionContext.pendingBlockHeader(), firstBlobTx, null, null, null); + + when(selectionResults.getCumulativeBlobGasUsed()).thenReturn(0L); + + final var result = + selector.evaluateTransactionPreProcessing(txEvaluationContext, selectionResults); + assertThat(result).isEqualTo(TransactionSelectionResult.SELECTED); + verify(selectionResults).getCumulativeBlobGasUsed(); + } + + @Test + void returnsBlobsFullWhenMaxNumberOfBlobsAlreadyPresent() { + when(blockSelectionContext.gasLimitCalculator().currentBlobGasLimit()).thenReturn(MAX_BLOB_GAS); + + final var selector = new BlobSizeTransactionSelector(blockSelectionContext); + + final var firstBlobTx = createBlobPendingTransaction(1); + + final var txEvaluationContext = + new TransactionEvaluationContext( + blockSelectionContext.pendingBlockHeader(), firstBlobTx, null, null, null); + + when(selectionResults.getCumulativeBlobGasUsed()).thenReturn(MAX_BLOB_GAS); + + final var result = + selector.evaluateTransactionPreProcessing(txEvaluationContext, selectionResults); + assertThat(result).isEqualTo(TransactionSelectionResult.BLOBS_FULL); + verify(selectionResults).getCumulativeBlobGasUsed(); + } + + @Test + void returnsTooLargeForRemainingBlobGas() { + when(blockSelectionContext.gasLimitCalculator().currentBlobGasLimit()).thenReturn(MAX_BLOB_GAS); + when(blockSelectionContext.gasCalculator().blobGasCost(anyInt())) + .thenAnswer(iom -> BLOB_GAS_PER_BLOB * iom.getArgument(0, Integer.class)); + + final var selector = new BlobSizeTransactionSelector(blockSelectionContext); + + final var firstBlobTx = createBlobPendingTransaction(MAX_BLOBS); + + final var txEvaluationContext = + new TransactionEvaluationContext( + blockSelectionContext.pendingBlockHeader(), firstBlobTx, null, null, null); + + when(selectionResults.getCumulativeBlobGasUsed()).thenReturn(MAX_BLOB_GAS - 1); + + final var result = + selector.evaluateTransactionPreProcessing(txEvaluationContext, selectionResults); + assertThat(result).isEqualTo(TransactionSelectionResult.TX_TOO_LARGE_FOR_REMAINING_BLOB_GAS); + verify(selectionResults).getCumulativeBlobGasUsed(); + } + + private PendingTransaction createEIP1559PendingTransaction() { + return PendingTransaction.newPendingTransaction( + createTransaction(TransactionType.EIP1559, 0), false, false); + } + + private PendingTransaction createBlobPendingTransaction(final int blobCount) { + return PendingTransaction.newPendingTransaction( + createTransaction(TransactionType.BLOB, blobCount), false, false); + } + + private Transaction createTransaction(final TransactionType type, final int blobCount) { + + var tx = + new TransactionTestFixture() + .to(Optional.of(Address.fromHexString("0x634316eA0EE79c701c6F67C53A4C54cBAfd2316d"))) + .nonce(0) + .type(type); + tx.maxFeePerGas(Optional.of(Wei.of(1000))).maxPriorityFeePerGas(Optional.of(Wei.of(100))); + if (type.supportsBlob()) { + if (blobCount > 0) { + tx.maxFeePerBlobGas(Optional.of(Wei.of(10))); + final var versionHashes = + IntStream.range(0, blobCount) + .mapToObj(i -> new VersionedHash((byte) 1, Hash.ZERO)) + .toList(); + final var kgzCommitments = + IntStream.range(0, blobCount) + .mapToObj(i -> new KZGCommitment(Bytes48.random())) + .toList(); + final var kzgProofs = + IntStream.range(0, blobCount).mapToObj(i -> new KZGProof(Bytes48.random())).toList(); + final var blobs = + IntStream.range(0, blobCount).mapToObj(i -> new Blob(Bytes.random(32 * 4096))).toList(); + tx.versionedHashes(Optional.of(versionHashes)); + final var blobsWithCommitments = + new BlobsWithCommitments(kgzCommitments, blobs, kzgProofs, versionHashes); + tx.blobsWithCommitments(Optional.of(blobsWithCommitments)); + } else { + fail("At least 1 blob is required for blob tx type"); + } + } + return tx.createTransaction(KEYS); + } +} diff --git a/ethereum/blockcreation/src/test/resources/block-creation-fixed-difficulty-genesis.json b/ethereum/blockcreation/src/test/resources/block-creation-fixed-difficulty-genesis.json new file mode 100644 index 00000000000..5f9a821a8e1 --- /dev/null +++ b/ethereum/blockcreation/src/test/resources/block-creation-fixed-difficulty-genesis.json @@ -0,0 +1,34 @@ +{ + "config": { + "chainId": 42, + "frontierBlock": 0, + "ethash": { + "fixeddifficulty":10 + } + }, + "nonce": "0x42", + "timestamp": "0x0", + "extraData": "", + "gasLimit": "0x1fffffffffffff", + "difficulty": "0x1", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "baseFeePerGas": "0x7", + "alloc": { + "fe3b557e8fb62b89f4916b721be55ceb828dbd73": { + "privateKey": "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63", + "comment": "private key and this comment are ignored. In a real chain, the private key should NOT be stored", + "balance": "0xad78ebc5ac6200000" + }, + "627306090abaB3A6e1400e9345bC60c78a8BEf57": { + "privateKey": "c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3", + "comment": "private key and this comment are ignored. In a real chain, the private key should NOT be stored", + "balance": "90000000000000000000000" + }, + "f17f52151EbEF6C7334FAD080c5704D77216b732": { + "privateKey": "ae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f", + "comment": "private key and this comment are ignored. In a real chain, the private key should NOT be stored", + "balance": "90000000000000000000000" + } + } +} diff --git a/ethereum/blockcreation/src/test/resources/block-creation-genesis.json b/ethereum/blockcreation/src/test/resources/block-creation-genesis.json new file mode 100644 index 00000000000..4a26c5ae025 --- /dev/null +++ b/ethereum/blockcreation/src/test/resources/block-creation-genesis.json @@ -0,0 +1,32 @@ +{ + "config": { + "chainId": 42, + "cancunTime": 0, + "terminalTotalDifficulty": 0 + }, + "nonce": "0x42", + "timestamp": "0x0", + "extraData": "", + "gasLimit": "0x1fffffffffffff", + "difficulty": "0x10000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "baseFeePerGas": "0x7", + "alloc": { + "fe3b557e8fb62b89f4916b721be55ceb828dbd73": { + "privateKey": "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63", + "comment": "private key and this comment are ignored. In a real chain, the private key should NOT be stored", + "balance": "0xad78ebc5ac6200000" + }, + "627306090abaB3A6e1400e9345bC60c78a8BEf57": { + "privateKey": "c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3", + "comment": "private key and this comment are ignored. In a real chain, the private key should NOT be stored", + "balance": "90000000000000000000000" + }, + "f17f52151EbEF6C7334FAD080c5704D77216b732": { + "privateKey": "ae6ae8e5ccbfb04590405997ee2d52d2b330726137b875053c36d94e974d162f", + "comment": "private key and this comment are ignored. In a real chain, the private key should NOT be stored", + "balance": "90000000000000000000000" + } + } +} diff --git a/ethereum/core/build.gradle b/ethereum/core/build.gradle index b616413dd22..24c794e298e 100644 --- a/ethereum/core/build.gradle +++ b/ethereum/core/build.gradle @@ -22,7 +22,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } @@ -32,6 +33,7 @@ dependencies { api 'org.web3j:core' annotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess' + annotationProcessor 'org.hyperledger.besu:besu-errorprone-checks' implementation project(':config') implementation project(':crypto:algorithms') @@ -47,7 +49,9 @@ dependencies { implementation 'com.fasterxml.jackson.core:jackson-databind' implementation 'com.google.guava:guava' + implementation 'com.github.ben-manes.caffeine:caffeine' implementation 'com.google.dagger:dagger' + implementation 'org.apache.maven:maven-artifact' annotationProcessor 'com.google.dagger:dagger-compiler' implementation 'io.opentelemetry:opentelemetry-api' implementation 'io.vertx:vertx-core' @@ -58,7 +62,6 @@ dependencies { implementation 'io.tmio:tuweni-concurrent' implementation 'io.tmio:tuweni-units' implementation 'io.tmio:tuweni-rlp' - implementation 'org.hyperledger.besu:bls12-381' implementation 'org.immutables:value-annotations' implementation 'tech.pegasys:jc-kzg-4844' diff --git a/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractIntegrationTest.java b/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractIntegrationTest.java index 2c6e07433f0..a5ee5f068ee 100644 --- a/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractIntegrationTest.java +++ b/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractIntegrationTest.java @@ -43,10 +43,10 @@ import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; -import org.hyperledger.besu.ethereum.vm.BlockHashLookup; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.SpuriousDragonGasCalculator; +import org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; import org.hyperledger.besu.evm.precompile.PrecompiledContract; import org.hyperledger.besu.evm.tracing.OperationTracer; import org.hyperledger.besu.evm.worldstate.WorldUpdater; @@ -72,6 +72,7 @@ public class PrivacyPrecompiledContractIntegrationTest { + // this tempDir is deliberately static @TempDir private static Path folder; private static final Bytes VALID_PRIVATE_TRANSACTION_RLP = @@ -204,6 +205,7 @@ public void testSendAndReceive() { new PrivateStateRootResolver(privateStateStorage), new PrivateStateGenesisAllocator( false, (privacyGroupId, blockNumber) -> Collections::emptyList), + false, "IntegrationTest"); privacyPrecompiledContract.setPrivateTransactionProcessor(mockPrivateTxProcessor()); diff --git a/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/vm/TraceTransactionIntegrationTest.java b/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/vm/TraceTransactionIntegrationTest.java index 972da935f35..a029c4b4a49 100644 --- a/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/vm/TraceTransactionIntegrationTest.java +++ b/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/vm/TraceTransactionIntegrationTest.java @@ -16,7 +16,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; +import static org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; +import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.crypto.KeyPair; import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; import org.hyperledger.besu.datatypes.TransactionType; @@ -67,7 +69,9 @@ public class TraceTransactionIntegrationTest { @BeforeEach public void setUp() { - final ExecutionContextTestFixture contextTestFixture = ExecutionContextTestFixture.create(); + final ExecutionContextTestFixture contextTestFixture = + ExecutionContextTestFixture.builder(GenesisConfigFile.fromResource("/genesis-it.json")) + .build(); genesisBlock = contextTestFixture.getGenesis(); blockchain = contextTestFixture.getBlockchain(); worldStateArchive = contextTestFixture.getStateArchive(); @@ -100,7 +104,6 @@ public void shouldTraceSStoreOperation() { final WorldUpdater createTransactionUpdater = worldState.updater(); TransactionProcessingResult result = transactionProcessor.processTransaction( - blockchain, createTransactionUpdater, genesisBlockHeader, createTransaction, @@ -119,7 +122,7 @@ public void shouldTraceSStoreOperation() { // Now call the transaction to execute the SSTORE. final DebugOperationTracer tracer = - new DebugOperationTracer(new TraceOptions(true, true, true)); + new DebugOperationTracer(new TraceOptions(true, true, true), false); final Transaction executeTransaction = Transaction.builder() .type(TransactionType.FRONTIER) @@ -133,7 +136,6 @@ public void shouldTraceSStoreOperation() { final WorldUpdater storeUpdater = worldState.updater(); result = transactionProcessor.processTransaction( - blockchain, storeUpdater, genesisBlockHeader, executeTransaction, @@ -166,13 +168,12 @@ public void shouldTraceSStoreOperation() { @Test public void shouldTraceContractCreation() { final DebugOperationTracer tracer = - new DebugOperationTracer(new TraceOptions(true, true, true)); + new DebugOperationTracer(new TraceOptions(true, true, true), false); final Transaction transaction = Transaction.readFrom( new BytesValueRLPInput(Bytes.fromHexString(CONTRACT_CREATION_TX), false)); final BlockHeader genesisBlockHeader = genesisBlock.getHeader(); transactionProcessor.processTransaction( - blockchain, worldStateArchive .getMutable(genesisBlockHeader.getStateRoot(), genesisBlockHeader.getHash()) .get() @@ -181,7 +182,7 @@ public void shouldTraceContractCreation() { transaction, genesisBlockHeader.getCoinbase(), tracer, - new CachingBlockHashLookup(genesisBlockHeader, blockchain), + blockHashLookup, false, Wei.ZERO); diff --git a/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/worldstate/PrunerIntegrationTest.java b/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/worldstate/PrunerIntegrationTest.java deleted file mode 100644 index 77f871f1002..00000000000 --- a/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/worldstate/PrunerIntegrationTest.java +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.worldstate; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryBlockchain; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.chain.MutableBlockchain; -import org.hyperledger.besu.ethereum.core.Block; -import org.hyperledger.besu.ethereum.core.BlockDataGenerator; -import org.hyperledger.besu.ethereum.core.BlockDataGenerator.BlockOptions; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.MutableWorldState; -import org.hyperledger.besu.ethereum.core.TransactionReceipt; -import org.hyperledger.besu.ethereum.rlp.RLP; -import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStatePreimageKeyValueStorage; -import org.hyperledger.besu.ethereum.trie.MerkleTrie; -import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; -import org.hyperledger.besu.ethereum.worldstate.Pruner.PruningPhase; -import org.hyperledger.besu.evm.internal.EvmConfiguration; -import org.hyperledger.besu.evm.worldstate.WorldState; -import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; -import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; -import org.hyperledger.besu.testutil.MockExecutorService; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.junit.jupiter.api.Test; - -public class PrunerIntegrationTest { - - private final BlockDataGenerator gen = new BlockDataGenerator(); - private final NoOpMetricsSystem metricsSystem = new NoOpMetricsSystem(); - private final Map> hashValueStore = new HashMap<>(); - private final InMemoryKeyValueStorage stateStorage = new TestInMemoryStorage(hashValueStore); - private final WorldStateStorage worldStateStorage = new WorldStateKeyValueStorage(stateStorage); - private final WorldStateArchive worldStateArchive = - new DefaultWorldStateArchive( - worldStateStorage, - new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage()), - EvmConfiguration.DEFAULT); - private final InMemoryKeyValueStorage markStorage = new InMemoryKeyValueStorage(); - private final Block genesisBlock = gen.genesisBlock(); - private final MutableBlockchain blockchain = createInMemoryBlockchain(genesisBlock); - - @Test - public void pruner_smallState_manyOpsPerTx() { - testPruner(3, 1, 1, 4, 100_000); - } - - @Test - public void pruner_largeState_fewOpsPerTx() { - testPruner(2, 5, 5, 6, 5); - } - - @Test - public void pruner_emptyBlocks() { - testPruner(5, 0, 2, 5, 10); - } - - @Test - public void pruner_markChainhead() { - testPruner(4, 2, 1, 10, 20); - } - - @Test - public void pruner_lowRelativeBlockConfirmations() { - testPruner(3, 2, 1, 4, 20); - } - - @Test - public void pruner_highRelativeBlockConfirmations() { - testPruner(3, 2, 9, 10, 20); - } - - private void testPruner( - final int numCycles, - final int accountsPerBlock, - final int blockConfirmations, - final int numBlocksToKeep, - final int opsPerTransaction) { - - final var markSweepPruner = - new MarkSweepPruner( - worldStateStorage, blockchain, markStorage, metricsSystem, opsPerTransaction); - final var pruner = - new Pruner( - markSweepPruner, - blockchain, - new PrunerConfiguration(blockConfirmations, numBlocksToKeep), - new MockExecutorService()); - - pruner.start(); - - for (int cycle = 0; cycle < numCycles; ++cycle) { - int numBlockInCycle = - numBlocksToKeep - + 1; // +1 to get it to switch from MARKING_COMPLETE TO SWEEPING on each cycle - var fullyMarkedBlockNum = cycle * numBlockInCycle + 1; - - // This should cause a full mark and sweep cycle - assertThat(pruner.getPruningPhase()).isEqualByComparingTo(PruningPhase.IDLE); - generateBlockchainData(numBlockInCycle, accountsPerBlock); - assertThat(pruner.getPruningPhase()).isEqualByComparingTo(PruningPhase.IDLE); - - // Collect the nodes we expect to keep - final Set expectedNodes = new HashSet<>(); - for (int i = fullyMarkedBlockNum; i <= blockchain.getChainHeadBlockNumber(); i++) { - final Hash stateRoot = blockchain.getBlockHeader(i).get().getStateRoot(); - collectWorldStateNodes(stateRoot, expectedNodes); - } - - if (accountsPerBlock != 0) { - assertThat(hashValueStore.size()) - .isGreaterThanOrEqualTo(expectedNodes.size()); // Sanity check - } - - // Assert that blocks from mark point onward are still accessible - for (int i = fullyMarkedBlockNum; i <= blockchain.getChainHeadBlockNumber(); i++) { - final BlockHeader blockHeader = blockchain.getBlockHeader(i).get(); - final Hash stateRoot = blockHeader.getStateRoot(); - assertThat(worldStateArchive.get(stateRoot, blockHeader.getHash())).isPresent(); - final WorldState markedState = - worldStateArchive.get(stateRoot, blockHeader.getHash()).get(); - // Traverse accounts and make sure all are accessible - final int expectedAccounts = accountsPerBlock * i; - final long accounts = - markedState.streamAccounts(Bytes32.ZERO, expectedAccounts * 2).count(); - assertThat(accounts).isEqualTo(expectedAccounts); - // Traverse storage to ensure that all storage is accessible - markedState - .streamAccounts(Bytes32.ZERO, expectedAccounts * 2) - .forEach(a -> a.storageEntriesFrom(Bytes32.ZERO, 1000)); - } - - // All other state roots should have been removed - for (int i = 0; i < fullyMarkedBlockNum; i++) { - final BlockHeader curHeader = blockchain.getBlockHeader(i).get(); - if (!curHeader.getStateRoot().equals(Hash.EMPTY_TRIE_HASH)) { - assertThat(worldStateArchive.get(curHeader.getStateRoot(), curHeader.getHash())) - .isEmpty(); - } - } - - // Check that storage contains only the values we expect - assertThat(hashValueStore.size()).isEqualTo(expectedNodes.size()); - assertThat(hashValueStore.values().stream().map(Optional::get)) - .containsExactlyInAnyOrderElementsOf( - expectedNodes.stream().map(Bytes::toArrayUnsafe).collect(Collectors.toSet())); - } - - pruner.stop(); - } - - private void generateBlockchainData(final int numBlocks, final int numAccounts) { - Block parentBlock = blockchain.getChainHeadBlock(); - for (int i = 0; i < numBlocks; i++) { - final BlockHeader parentHeader = parentBlock.getHeader(); - final Hash parentHash = parentBlock.getHash(); - final MutableWorldState worldState = - worldStateArchive.getMutable(parentHeader.getStateRoot(), parentHash).get(); - gen.createRandomContractAccountsWithNonEmptyStorage(worldState, numAccounts); - final Hash stateRoot = worldState.rootHash(); - - final Block block = - gen.block( - BlockOptions.create() - .setStateRoot(stateRoot) - .setBlockNumber(parentHeader.getNumber() + 1L) - .setParentHash(parentHash)); - final List receipts = gen.receipts(block); - blockchain.appendBlock(block, receipts); - parentBlock = block; - } - } - - private Set collectWorldStateNodes(final Hash stateRootHash, final Set collector) { - final List storageRoots = new ArrayList<>(); - final MerkleTrie stateTrie = createStateTrie(stateRootHash); - - // Collect storage roots and code - stateTrie - .entriesFrom(Bytes32.ZERO, 1000) - .forEach( - (key, val) -> { - final StateTrieAccountValue accountValue = - StateTrieAccountValue.readFrom(RLP.input(val)); - stateStorage - .get(accountValue.getCodeHash().toArray()) - .ifPresent(v -> collector.add(Bytes.wrap(v))); - storageRoots.add(accountValue.getStorageRoot()); - }); - - // Collect state nodes - collectTrieNodes(stateTrie, collector); - // Collect storage nodes - for (Hash storageRoot : storageRoots) { - final MerkleTrie storageTrie = createStorageTrie(storageRoot); - collectTrieNodes(storageTrie, collector); - } - - return collector; - } - - private void collectTrieNodes(final MerkleTrie trie, final Set collector) { - final Bytes32 rootHash = trie.getRootHash(); - trie.visitAll( - node -> { - if (node.isReferencedByHash() || node.getHash().equals(rootHash)) { - collector.add(node.getEncodedBytes()); - } - }); - } - - private MerkleTrie createStateTrie(final Bytes32 rootHash) { - return new StoredMerklePatriciaTrie<>( - worldStateStorage::getAccountStateTrieNode, - rootHash, - Function.identity(), - Function.identity()); - } - - private MerkleTrie createStorageTrie(final Bytes32 rootHash) { - return new StoredMerklePatriciaTrie<>( - (location, hash) -> worldStateStorage.getAccountStorageTrieNode(null, location, hash), - rootHash, - Function.identity(), - Function.identity()); - } - - // Proxy class so that we have access to the constructor that takes our own map - private static class TestInMemoryStorage extends InMemoryKeyValueStorage { - - public TestInMemoryStorage(final Map> hashValueStore) { - super(hashValueStore); - } - } -} diff --git a/ethereum/core/src/integration-test/resources/genesis-it.json b/ethereum/core/src/integration-test/resources/genesis-it.json new file mode 100644 index 00000000000..70c42b39fe5 --- /dev/null +++ b/ethereum/core/src/integration-test/resources/genesis-it.json @@ -0,0 +1,13 @@ +{ + "config": { + "petersburgBlock": 0 + }, + "coinbase": "0x0000000000000000000000000000000000000000", + "difficulty": "0x0000001", + "extraData": "", + "gasLimit": "0x2fefd8", + "nonce": "0x0000000000000107", + "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": "0x00" +} diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OperationBenchmarkHelper.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OperationBenchmarkHelper.java index 8307a8fef4a..4289a91a3c8 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OperationBenchmarkHelper.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/OperationBenchmarkHelper.java @@ -16,6 +16,7 @@ import static java.util.Collections.emptyList; +import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; @@ -74,7 +75,9 @@ public static OperationBenchmarkHelper create() throws IOException { KeyValueSegmentIdentifier.BLOCKCHAIN, optimisticRocksDBColumnarKeyValueStorage); final ExecutionContextTestFixture executionContext = - ExecutionContextTestFixture.builder().blockchainKeyValueStorage(keyValueStorage).build(); + ExecutionContextTestFixture.builder(GenesisConfigFile.fromResource("/genesis-jmh.json")) + .blockchainKeyValueStorage(keyValueStorage) + .build(); final MutableBlockchain blockchain = executionContext.getBlockchain(); for (int i = 1; i < 256; i++) { diff --git a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/TransientStorageOperationBenchmark.java b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/TransientStorageOperationBenchmark.java index 19d3c8b45ca..aa7ce37313b 100644 --- a/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/TransientStorageOperationBenchmark.java +++ b/ethereum/core/src/jmh/java/org/hyperledger/besu/ethereum/vm/operations/TransientStorageOperationBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/jmh/resources/genesis-jmh.json b/ethereum/core/src/jmh/resources/genesis-jmh.json new file mode 100644 index 00000000000..70c42b39fe5 --- /dev/null +++ b/ethereum/core/src/jmh/resources/genesis-jmh.json @@ -0,0 +1,13 @@ +{ + "config": { + "petersburgBlock": 0 + }, + "coinbase": "0x0000000000000000000000000000000000000000", + "difficulty": "0x0000001", + "extraData": "", + "gasLimit": "0x2fefd8", + "nonce": "0x0000000000000107", + "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": "0x00" +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/BlockProcessingOutputs.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/BlockProcessingOutputs.java index 9e062b4edeb..92d97697684 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/BlockProcessingOutputs.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/BlockProcessingOutputs.java @@ -1,8 +1,8 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * @@ -11,37 +11,74 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum; import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.core.Request; import org.hyperledger.besu.ethereum.core.TransactionReceipt; -import java.util.ArrayList; import java.util.List; +import java.util.Optional; +/** Contains the outputs of processing a block. */ public class BlockProcessingOutputs { private final MutableWorldState worldState; private final List receipts; + private final Optional> maybeRequests; + /** + * Creates a new instance. + * + * @param worldState the world state after processing the block + * @param receipts the receipts produced by processing the block + */ public BlockProcessingOutputs( final MutableWorldState worldState, final List receipts) { - this.worldState = worldState; - this.receipts = receipts; + this(worldState, receipts, Optional.empty()); } - public static BlockProcessingOutputs empty() { - return new BlockProcessingOutputs(null, new ArrayList<>()); + /** + * Creates a new instance. + * + * @param worldState the world state after processing the block + * @param receipts the receipts produced by processing the block + * @param maybeRequests the requests produced by processing the block + */ + public BlockProcessingOutputs( + final MutableWorldState worldState, + final List receipts, + final Optional> maybeRequests) { + this.worldState = worldState; + this.receipts = receipts; + this.maybeRequests = maybeRequests; } + /** + * Returns the world state after processing the block. + * + * @return the world state after processing the block + */ public MutableWorldState getWorldState() { return worldState; } + /** + * Returns the receipts produced by processing the block. + * + * @return the receipts produced by processing the block + */ public List getReceipts() { return receipts; } + + /** + * Returns the requests produced by processing the block. + * + * @return the requests produced by processing the block + */ + public Optional> getRequests() { + return maybeRequests; + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/BlockProcessingResult.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/BlockProcessingResult.java index 657a801455f..926dd13abdf 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/BlockProcessingResult.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/BlockProcessingResult.java @@ -1,8 +1,8 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum; import org.hyperledger.besu.ethereum.core.TransactionReceipt; @@ -22,24 +20,43 @@ import java.util.List; import java.util.Optional; +/** Contains the outputs of processing a block. */ public class BlockProcessingResult extends BlockValidationResult { private final Optional yield; private final boolean isPartial; + /** A result indicating that processing failed. */ public static final BlockProcessingResult FAILED = new BlockProcessingResult("processing failed"); + /** + * A result indicating that processing was successful but incomplete. + * + * @param yield the outputs of processing a block + */ public BlockProcessingResult(final Optional yield) { this.yield = yield; this.isPartial = false; } + /** + * A result indicating that processing was successful but incomplete. + * + * @param yield the outputs of processing a block + * @param isPartial whether the processing was incomplete + */ public BlockProcessingResult( final Optional yield, final boolean isPartial) { this.yield = yield; this.isPartial = isPartial; } + /** + * A result indicating that processing was successful but incomplete. + * + * @param yield the outputs of processing a block + * @param errorMessage the error message if any + */ public BlockProcessingResult( final Optional yield, final String errorMessage) { super(errorMessage); @@ -47,6 +64,12 @@ public BlockProcessingResult( this.isPartial = false; } + /** + * A result indicating that processing was successful but incomplete. + * + * @param yield the outputs of processing a block + * @param cause the cause of the error if any + */ public BlockProcessingResult( final Optional yield, final Throwable cause) { super(cause.getLocalizedMessage(), cause); @@ -54,6 +77,13 @@ public BlockProcessingResult( this.isPartial = false; } + /** + * A result indicating that processing was successful but incomplete. + * + * @param yield the outputs of processing a block + * @param errorMessage the error message if any + * @param isPartial whether the processing was incomplete + */ public BlockProcessingResult( final Optional yield, final String errorMessage, @@ -63,20 +93,40 @@ public BlockProcessingResult( this.isPartial = isPartial; } + /** + * A result indicating that processing failed. + * + * @param errorMessage the error message + */ public BlockProcessingResult(final String errorMessage) { super(errorMessage); this.isPartial = false; this.yield = Optional.empty(); } + /** + * Gets the block processing outputs of the result. + * + * @return the block processing outputs of the result + */ public Optional getYield() { return yield; } + /** + * Checks if the processing was incomplete. + * + * @return true if the processing was incomplete, false otherwise + */ public boolean isPartial() { return isPartial; } + /** + * Gets the transaction receipts of the result. + * + * @return the transaction receipts of the result + */ public List getReceipts() { if (yield.isEmpty()) { return new ArrayList<>(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/BlockValidationResult.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/BlockValidationResult.java index 63d786acdff..83734e29c06 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/BlockValidationResult.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/BlockValidationResult.java @@ -1,8 +1,8 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * @@ -11,45 +11,83 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum; import java.util.Optional; +/** + * Represents the result of a block validation. This class holds the success status, error message, + * and cause of the validation. + */ public class BlockValidationResult { + /** The error message of the failed validation, if any. */ public final Optional errorMessage; + + /** The cause of the failed validation, if any. */ public final Optional cause; + + /** + * The success status of the validation. True if the validation was successful, false otherwise. + */ public final boolean success; + /** Constructs a new BlockValidationResult indicating a successful validation. */ public BlockValidationResult() { this.success = true; this.errorMessage = Optional.empty(); this.cause = Optional.empty(); } + /** + * Constructs a new BlockValidationResult indicating a failed validation with the given error + * message. + * + * @param errorMessage the error message of the failed validation + */ public BlockValidationResult(final String errorMessage) { this.success = false; this.errorMessage = Optional.of(errorMessage); this.cause = Optional.empty(); } + /** + * Constructs a new BlockValidationResult indicating a failed validation with the given error + * message and cause. + * + * @param errorMessage the error message of the failed validation + * @param cause the cause of the failed validation + */ public BlockValidationResult(final String errorMessage, final Throwable cause) { this.success = false; this.errorMessage = Optional.of(errorMessage); this.cause = Optional.of(cause); } + /** + * Checks if the validation was successful. + * + * @return true if the validation was successful, false otherwise + */ public boolean isSuccessful() { return this.success; } + /** + * Checks if the validation failed. + * + * @return true if the validation failed, false otherwise + */ public boolean isFailed() { return !isSuccessful(); } + /** + * Gets the cause of the failed validation. + * + * @return the cause of the failed validation + */ public Optional causedBy() { return cause; } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/BlockValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/BlockValidator.java index bed189c4935..e8136062bbd 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/BlockValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/BlockValidator.java @@ -15,19 +15,46 @@ package org.hyperledger.besu.ethereum; import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.Request; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; import java.util.List; +import java.util.Optional; +/** + * The BlockValidator interface defines the methods for validating and processing blocks in the + * Ethereum protocol. + */ public interface BlockValidator { + /** + * Validates and processes a block with the given context, block, header validation mode, and + * ommer validation mode. + * + * @param context the protocol context + * @param block the block to validate and process + * @param headerValidationMode the header validation mode + * @param ommerValidationMode the ommer validation mode + * @return the result of the block processing + */ BlockProcessingResult validateAndProcessBlock( final ProtocolContext context, final Block block, final HeaderValidationMode headerValidationMode, final HeaderValidationMode ommerValidationMode); + /** + * Validates and processes a block with the given context, block, header validation mode, ommer + * validation mode, and persistence flag. + * + * @param context the protocol context + * @param block the block to validate and process + * @param headerValidationMode the header validation mode + * @param ommerValidationMode the ommer validation mode + * @param shouldPersist flag indicating whether the block should be persisted + * @return the result of the block processing + */ BlockProcessingResult validateAndProcessBlock( final ProtocolContext context, final Block block, @@ -35,6 +62,18 @@ BlockProcessingResult validateAndProcessBlock( final HeaderValidationMode ommerValidationMode, final boolean shouldPersist); + /** + * Validates and processes a block with the given context, block, header validation mode, ommer + * validation mode, persistence flag, and bad block recording flag. + * + * @param context the protocol context + * @param block the block to validate and process + * @param headerValidationMode the header validation mode + * @param ommerValidationMode the ommer validation mode + * @param shouldPersist flag indicating whether the block should be persisted + * @param shouldRecordBadBlock flag indicating whether bad blocks should be recorded + * @return the result of the block processing + */ BlockProcessingResult validateAndProcessBlock( final ProtocolContext context, final Block block, @@ -43,10 +82,23 @@ BlockProcessingResult validateAndProcessBlock( final boolean shouldPersist, final boolean shouldRecordBadBlock); + /** + * Performs fast block validation with the given context, block, transaction receipts, requests, + * header validation mode, and ommer validation mode. + * + * @param context the protocol context + * @param block the block to validate + * @param receipts the transaction receipts + * @param requests the requests + * @param headerValidationMode the header validation mode + * @param ommerValidationMode the ommer validation mode + * @return true if the block is valid, false otherwise + */ boolean fastBlockValidation( final ProtocolContext context, final Block block, final List receipts, + final Optional> requests, final HeaderValidationMode headerValidationMode, final HeaderValidationMode ommerValidationMode); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ConsensusContext.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ConsensusContext.java index 377d55953d8..33ade39ab30 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ConsensusContext.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ConsensusContext.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,7 +14,19 @@ */ package org.hyperledger.besu.ethereum; +/** + * The ConsensusContext interface defines a method for casting the consensus context to a specific + * class. + */ @FunctionalInterface public interface ConsensusContext { + + /** + * Casts the consensus context to the specified class. + * + * @param the type of the class to cast the consensus context to + * @param klass the class to cast the consensus context to + * @return the consensus context cast to the specified class + */ C as(final Class klass); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ConsensusContextFactory.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ConsensusContextFactory.java index 848d861197b..a9381fa199f 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ConsensusContextFactory.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ConsensusContextFactory.java @@ -18,9 +18,19 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +/** The ConsensusContextFactory interface defines a method for creating a consensus context. */ @FunctionalInterface public interface ConsensusContextFactory { + /** + * Creates a consensus context with the given blockchain, world state archive, and protocol + * schedule. + * + * @param blockchain the blockchain + * @param worldStateArchive the world state archive + * @param protocolSchedule the protocol schedule + * @return the created consensus context + */ ConsensusContext create( Blockchain blockchain, WorldStateArchive worldStateArchive, diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/GasLimitCalculator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/GasLimitCalculator.java index 3d5375c4012..7c15d6229c0 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/GasLimitCalculator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/GasLimitCalculator.java @@ -14,16 +14,37 @@ */ package org.hyperledger.besu.ethereum; +/** The GasLimitCalculator interface defines methods for calculating the gas limit. */ public interface GasLimitCalculator { - static final long BLOB_GAS_LIMIT = 786432; + /** The constant BLOB_GAS_LIMIT represents the gas limit for blob data. */ + long BLOB_GAS_LIMIT = 786432; + /** + * Calculates the next gas limit based on the current gas limit, target gas limit, and new block + * number. + * + * @param currentGasLimit the current gas limit + * @param targetGasLimit the target gas limit + * @param newBlockNumber the new block number + * @return the calculated next gas limit + */ long nextGasLimit(long currentGasLimit, long targetGasLimit, long newBlockNumber); + /** + * Returns a GasLimitCalculator that always returns the current gas limit. + * + * @return a GasLimitCalculator that always returns the current gas limit + */ static GasLimitCalculator constant() { return (currentGasLimit, targetGasLimit, newBlockNumber) -> currentGasLimit; } + /** + * Returns the current blob gas limit. + * + * @return the current blob gas limit + */ default long currentBlobGasLimit() { return BLOB_GAS_LIMIT; } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/MainnetBlockValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/MainnetBlockValidator.java index 3d711618cec..5d80a951505 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/MainnetBlockValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/MainnetBlockValidator.java @@ -14,11 +14,13 @@ */ package org.hyperledger.besu.ethereum; +import org.hyperledger.besu.ethereum.chain.BadBlockCause; import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.core.Request; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.mainnet.BlockBodyValidator; import org.hyperledger.besu.ethereum.mainnet.BlockHeaderValidator; @@ -34,14 +36,36 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * The MainnetBlockValidator class implements the BlockValidator interface for the Mainnet Ethereum + * network. It validates and processes blocks according to the rules of the Mainnet Ethereum + * network. + */ public class MainnetBlockValidator implements BlockValidator { private static final Logger LOG = LoggerFactory.getLogger(MainnetBlockValidator.class); + + /** The BlockHeaderValidator used to validate block headers. */ protected final BlockHeaderValidator blockHeaderValidator; + + /** The BlockBodyValidator used to validate block bodies. */ protected final BlockBodyValidator blockBodyValidator; + + /** The BlockProcessor used to process blocks. */ protected final BlockProcessor blockProcessor; + + /** The BadBlockManager used to manage bad blocks. */ protected final BadBlockManager badBlockManager; + /** + * Constructs a new MainnetBlockValidator with the given BlockHeaderValidator, BlockBodyValidator, + * BlockProcessor, and BadBlockManager. + * + * @param blockHeaderValidator the BlockHeaderValidator used to validate block headers + * @param blockBodyValidator the BlockBodyValidator used to validate block bodies + * @param blockProcessor the BlockProcessor used to process blocks + * @param badBlockManager the BadBlockManager used to manage bad blocks + */ public MainnetBlockValidator( final BlockHeaderValidator blockHeaderValidator, final BlockBodyValidator blockBodyValidator, @@ -104,20 +128,23 @@ public BlockProcessingResult validateAndProcessBlock( var retval = new BlockProcessingResult( "Parent block with hash " + header.getParentHash() + " not present"); - handleAndLogImportFailure(block, retval, shouldRecordBadBlock); + // Blocks should not be marked bad due to missing data + handleFailedBlockProcessing(block, retval, false); return retval; } parentHeader = maybeParentHeader.get(); if (!blockHeaderValidator.validateHeader( header, parentHeader, context, headerValidationMode)) { - var retval = new BlockProcessingResult("header validation rule violated, see logs"); - handleAndLogImportFailure(block, retval, shouldRecordBadBlock); + final String error = String.format("Header validation failed (%s)", headerValidationMode); + var retval = new BlockProcessingResult(error); + handleFailedBlockProcessing(block, retval, shouldRecordBadBlock); return retval; } } catch (StorageException ex) { var retval = new BlockProcessingResult(Optional.empty(), ex); - handleAndLogImportFailure(block, retval, shouldRecordBadBlock); + // Blocks should not be marked bad due to a local storage failure + handleFailedBlockProcessing(block, retval, false); return retval; } try (final var worldState = @@ -129,24 +156,28 @@ public BlockProcessingResult validateAndProcessBlock( "Unable to process block because parent world state " + parentHeader.getStateRoot() + " is not available"); - handleAndLogImportFailure(block, retval, shouldRecordBadBlock); + // Blocks should not be marked bad due to missing data + handleFailedBlockProcessing(block, retval, false); return retval; } var result = processBlock(context, worldState, block); if (result.isFailed()) { - handleAndLogImportFailure(block, result, shouldRecordBadBlock); + handleFailedBlockProcessing(block, result, shouldRecordBadBlock); return result; } else { List receipts = result.getYield().map(BlockProcessingOutputs::getReceipts).orElse(new ArrayList<>()); + Optional> maybeRequests = + result.getYield().flatMap(BlockProcessingOutputs::getRequests); if (!blockBodyValidator.validateBody( - context, block, receipts, worldState.rootHash(), ommerValidationMode)) { - handleAndLogImportFailure(block, result, shouldRecordBadBlock); - return new BlockProcessingResult("failed to validate output of imported block"); + context, block, receipts, maybeRequests, worldState.rootHash(), ommerValidationMode)) { + result = new BlockProcessingResult("failed to validate output of imported block"); + handleFailedBlockProcessing(block, result, shouldRecordBadBlock); + return result; } return new BlockProcessingResult( - Optional.of(new BlockProcessingOutputs(worldState, receipts))); + Optional.of(new BlockProcessingOutputs(worldState, receipts, maybeRequests))); } } catch (MerkleTrieException ex) { context @@ -154,42 +185,50 @@ public BlockProcessingResult validateAndProcessBlock( .ifPresentOrElse( synchronizer -> synchronizer.healWorldState(ex.getMaybeAddress(), ex.getLocation()), () -> - handleAndLogImportFailure( + handleFailedBlockProcessing( block, new BlockProcessingResult(Optional.empty(), ex), - shouldRecordBadBlock)); + // Do not record bad black due to missing data + false)); return new BlockProcessingResult(Optional.empty(), ex); } catch (StorageException ex) { var retval = new BlockProcessingResult(Optional.empty(), ex); - handleAndLogImportFailure(block, retval, shouldRecordBadBlock); + // Do not record bad block due to a local storage issue + handleFailedBlockProcessing(block, retval, false); return retval; } catch (Exception ex) { + // Wrap checked autocloseable exception from try-with-resources throw new RuntimeException(ex); } } - private void handleAndLogImportFailure( - final Block invalidBlock, + private void handleFailedBlockProcessing( + final Block failedBlock, final BlockValidationResult result, final boolean shouldRecordBadBlock) { if (result.causedBy().isPresent()) { + // Block processing failed exceptionally, we cannot assume the block was intrinsically invalid LOG.info( - "Invalid block {}: {}, caused by {}", - invalidBlock.toLogString(), + "Failed to process block {}: {}, caused by {}", + failedBlock.toLogString(), result.errorMessage, result.causedBy().get()); LOG.debug("with stack", result.causedBy().get()); } else { if (result.errorMessage.isPresent()) { - LOG.info("Invalid block {}: {}", invalidBlock.toLogString(), result.errorMessage); + LOG.info("Invalid block {}: {}", failedBlock.toLogString(), result.errorMessage); } else { - LOG.info("Invalid block {}", invalidBlock.toLogString()); + LOG.info("Invalid block {}", failedBlock.toLogString()); + } + + if (shouldRecordBadBlock) { + // Result.errorMessage should not be empty on failure, but add a default to be safe + String description = result.errorMessage.orElse("Unknown cause"); + final BadBlockCause cause = BadBlockCause.fromValidationFailure(description); + badBlockManager.addBadBlock(failedBlock, cause); + } else { + LOG.debug("Invalid block {} not added to badBlockManager ", failedBlock.toLogString()); } - } - if (shouldRecordBadBlock) { - badBlockManager.addBadBlock(invalidBlock, result.causedBy()); - } else { - LOG.debug("Invalid block {} not added to badBlockManager ", invalidBlock.toLogString()); } } @@ -212,16 +251,20 @@ public boolean fastBlockValidation( final ProtocolContext context, final Block block, final List receipts, + final Optional> requests, final HeaderValidationMode headerValidationMode, final HeaderValidationMode ommerValidationMode) { final BlockHeader header = block.getHeader(); if (!blockHeaderValidator.validateHeader(header, context, headerValidationMode)) { - badBlockManager.addBadBlock(block, Optional.empty()); + String description = String.format("Failed header validation (%s)", headerValidationMode); + badBlockManager.addBadBlock(block, BadBlockCause.fromValidationFailure(description)); return false; } - if (!blockBodyValidator.validateBodyLight(context, block, receipts, ommerValidationMode)) { - badBlockManager.addBadBlock(block, Optional.empty()); + if (!blockBodyValidator.validateBodyLight( + context, block, receipts, requests, ommerValidationMode)) { + badBlockManager.addBadBlock( + block, BadBlockCause.fromValidationFailure("Failed body validation (light)")); return false; } return true; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ProtocolContext.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ProtocolContext.java index 745c5640689..e5ec5ae0921 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ProtocolContext.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/ProtocolContext.java @@ -14,11 +14,11 @@ */ package org.hyperledger.besu.ethereum; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Synchronizer; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; -import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelectorFactory; import java.util.Optional; @@ -30,70 +30,122 @@ public class ProtocolContext { private final MutableBlockchain blockchain; private final WorldStateArchive worldStateArchive; + private final BadBlockManager badBlockManager; private final ConsensusContext consensusContext; - private final Optional transactionSelectorFactory; private Optional synchronizer; - public ProtocolContext( - final MutableBlockchain blockchain, - final WorldStateArchive worldStateArchive, - final ConsensusContext consensusContext) { - this(blockchain, worldStateArchive, consensusContext, Optional.empty()); - } - + /** + * Constructs a new ProtocolContext with the given blockchain, world state archive, consensus + * context, and bad block manager. + * + * @param blockchain the blockchain of the protocol context + * @param worldStateArchive the world state archive of the protocol context + * @param consensusContext the consensus context of the protocol context + * @param badBlockManager the bad block manager of the protocol context + */ public ProtocolContext( final MutableBlockchain blockchain, final WorldStateArchive worldStateArchive, final ConsensusContext consensusContext, - final Optional transactionSelectorFactory) { + final BadBlockManager badBlockManager) { this.blockchain = blockchain; this.worldStateArchive = worldStateArchive; this.consensusContext = consensusContext; this.synchronizer = Optional.empty(); - this.transactionSelectorFactory = transactionSelectorFactory; + this.badBlockManager = badBlockManager; } + /** + * Initializes a new ProtocolContext with the given blockchain, world state archive, protocol + * schedule, consensus context factory, and bad block manager. + * + * @param blockchain the blockchain of the protocol context + * @param worldStateArchive the world state archive of the protocol context + * @param protocolSchedule the protocol schedule of the protocol context + * @param consensusContextFactory the consensus context factory of the protocol context + * @param badBlockManager the bad block manager of the protocol context + * @return the initialized ProtocolContext + */ public static ProtocolContext init( final MutableBlockchain blockchain, final WorldStateArchive worldStateArchive, final ProtocolSchedule protocolSchedule, final ConsensusContextFactory consensusContextFactory, - final Optional transactionSelectorFactory) { + final BadBlockManager badBlockManager) { return new ProtocolContext( blockchain, worldStateArchive, consensusContextFactory.create(blockchain, worldStateArchive, protocolSchedule), - transactionSelectorFactory); + badBlockManager); } + /** + * Gets the synchronizer of the protocol context. + * + * @return the synchronizer of the protocol context + */ public Optional getSynchronizer() { return synchronizer; } + /** + * Sets the synchronizer of the protocol context. + * + * @param synchronizer the synchronizer to set + */ public void setSynchronizer(final Optional synchronizer) { this.synchronizer = synchronizer; } + /** + * Gets the blockchain of the protocol context. + * + * @return the blockchain of the protocol context + */ public MutableBlockchain getBlockchain() { return blockchain; } + /** + * Gets the world state archive of the protocol context. + * + * @return the world state archive of the protocol context + */ public WorldStateArchive getWorldStateArchive() { return worldStateArchive; } + /** + * Gets the bad block manager of the protocol context. + * + * @return the bad block manager of the protocol context + */ + public BadBlockManager getBadBlockManager() { + return badBlockManager; + } + + /** + * Gets the consensus context of the protocol context. + * + * @param the type of the consensus context + * @param klass the klass + * @return the consensus context of the protocol context + */ public C getConsensusContext(final Class klass) { return consensusContext.as(klass); } + /** + * Gets the safe consensus context of the protocol context. + * + * @param the type of the consensus context + * @param klass the klass + * @return the consensus context of the protocol context + */ public Optional safeConsensusContext(final Class klass) { return Optional.ofNullable(consensusContext) .filter(c -> klass.isAssignableFrom(c.getClass())) .map(klass::cast); } - - public Optional getTransactionSelectorFactory() { - return transactionSelectorFactory; - } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiAccount.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiAccount.java deleted file mode 100644 index 476a0ab22e1..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiAccount.java +++ /dev/null @@ -1,318 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * - */ - -package org.hyperledger.besu.ethereum.bonsai; - -import org.hyperledger.besu.datatypes.AccountValue; -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldView; -import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; -import org.hyperledger.besu.ethereum.rlp.RLP; -import org.hyperledger.besu.ethereum.rlp.RLPException; -import org.hyperledger.besu.ethereum.rlp.RLPInput; -import org.hyperledger.besu.ethereum.rlp.RLPOutput; -import org.hyperledger.besu.evm.ModificationNotAllowedException; -import org.hyperledger.besu.evm.account.AccountStorageEntry; -import org.hyperledger.besu.evm.account.MutableAccount; -import org.hyperledger.besu.evm.worldstate.UpdateTrackingAccount; - -import java.util.HashMap; -import java.util.Map; -import java.util.NavigableMap; -import java.util.Objects; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt256; - -public class BonsaiAccount implements MutableAccount, AccountValue { - private final BonsaiWorldView context; - private boolean immutable; - - private final Address address; - private final Hash addressHash; - private Hash codeHash; - private long nonce; - private Wei balance; - private Hash storageRoot; - private Bytes code; - - private final Map updatedStorage = new HashMap<>(); - - public BonsaiAccount( - final BonsaiWorldView context, - final Address address, - final Hash addressHash, - final long nonce, - final Wei balance, - final Hash storageRoot, - final Hash codeHash, - final boolean mutable) { - this.context = context; - this.address = address; - this.addressHash = addressHash; - this.nonce = nonce; - this.balance = balance; - this.storageRoot = storageRoot; - this.codeHash = codeHash; - - this.immutable = !mutable; - } - - public BonsaiAccount( - final BonsaiWorldView context, - final Address address, - final AccountValue stateTrieAccount, - final boolean mutable) { - this( - context, - address, - address.addressHash(), - stateTrieAccount.getNonce(), - stateTrieAccount.getBalance(), - stateTrieAccount.getStorageRoot(), - stateTrieAccount.getCodeHash(), - mutable); - } - - public BonsaiAccount(final BonsaiAccount toCopy) { - this(toCopy, toCopy.context, false); - } - - public BonsaiAccount( - final BonsaiAccount toCopy, final BonsaiWorldView context, final boolean mutable) { - this.context = context; - this.address = toCopy.address; - this.addressHash = toCopy.addressHash; - this.nonce = toCopy.nonce; - this.balance = toCopy.balance; - this.storageRoot = toCopy.storageRoot; - this.codeHash = toCopy.codeHash; - this.code = toCopy.code; - updatedStorage.putAll(toCopy.updatedStorage); - - this.immutable = !mutable; - } - - public BonsaiAccount( - final BonsaiWorldView context, final UpdateTrackingAccount tracked) { - this.context = context; - this.address = tracked.getAddress(); - this.addressHash = tracked.getAddressHash(); - this.nonce = tracked.getNonce(); - this.balance = tracked.getBalance(); - this.storageRoot = Hash.EMPTY_TRIE_HASH; - this.codeHash = tracked.getCodeHash(); - this.code = tracked.getCode(); - updatedStorage.putAll(tracked.getUpdatedStorage()); - - this.immutable = false; - } - - public static BonsaiAccount fromRLP( - final BonsaiWorldView context, - final Address address, - final Bytes encoded, - final boolean mutable) - throws RLPException { - final RLPInput in = RLP.input(encoded); - in.enterList(); - - final long nonce = in.readLongScalar(); - final Wei balance = Wei.of(in.readUInt256Scalar()); - final Hash storageRoot = Hash.wrap(in.readBytes32()); - final Hash codeHash = Hash.wrap(in.readBytes32()); - - in.leaveList(); - - return new BonsaiAccount( - context, address, address.addressHash(), nonce, balance, storageRoot, codeHash, mutable); - } - - @Override - public Address getAddress() { - return address; - } - - @Override - public Hash getAddressHash() { - return addressHash; - } - - @Override - public long getNonce() { - return nonce; - } - - @Override - public void setNonce(final long value) { - if (immutable) { - throw new ModificationNotAllowedException(); - } - nonce = value; - } - - @Override - public Wei getBalance() { - return balance; - } - - @Override - public void setBalance(final Wei value) { - if (immutable) { - throw new ModificationNotAllowedException(); - } - balance = value; - } - - @Override - public Bytes getCode() { - if (code == null) { - code = context.getCode(address, codeHash).orElse(Bytes.EMPTY); - } - return code; - } - - @Override - public void setCode(final Bytes code) { - if (immutable) { - throw new ModificationNotAllowedException(); - } - this.code = code; - if (code == null || code.isEmpty()) { - this.codeHash = Hash.EMPTY; - } else { - this.codeHash = Hash.hash(code); - } - } - - @Override - public Hash getCodeHash() { - return codeHash; - } - - @Override - public UInt256 getStorageValue(final UInt256 key) { - return context.getStorageValue(address, key); - } - - @Override - public UInt256 getOriginalStorageValue(final UInt256 key) { - return context.getPriorStorageValue(address, key); - } - - @Override - public NavigableMap storageEntriesFrom( - final Bytes32 startKeyHash, final int limit) { - return context.getWorldStateStorage().storageEntriesFrom(this.addressHash, startKeyHash, limit); - } - - public Bytes serializeAccount() { - final BytesValueRLPOutput out = new BytesValueRLPOutput(); - writeTo(out); - return out.encoded(); - } - - @Override - public void writeTo(final RLPOutput out) { - out.startList(); - - out.writeLongScalar(nonce); - out.writeUInt256Scalar(balance); - out.writeBytes(storageRoot); - out.writeBytes(codeHash); - - out.endList(); - } - - @Override - public void setStorageValue(final UInt256 key, final UInt256 value) { - if (immutable) { - throw new ModificationNotAllowedException(); - } - updatedStorage.put(key, value); - } - - @Override - public void clearStorage() { - updatedStorage.clear(); - } - - @Override - public Map getUpdatedStorage() { - return updatedStorage; - } - - @Override - public Hash getStorageRoot() { - return storageRoot; - } - - public void setStorageRoot(final Hash storageRoot) { - if (immutable) { - throw new ModificationNotAllowedException(); - } - this.storageRoot = storageRoot; - } - - @Override - public void becomeImmutable() { - immutable = true; - } - - @Override - public String toString() { - return "AccountState{" - + "address=" - + address - + ", nonce=" - + nonce - + ", balance=" - + balance - + ", storageRoot=" - + storageRoot - + ", codeHash=" - + codeHash - + '}'; - } - - /** - * Throws an exception if the two accounts represent different stored states - * - * @param source The bonsai account to compare - * @param account The State Trie account to compare - * @param context a description to be added to the thrown exceptions - * @throws IllegalStateException if the stored values differ - */ - public static void assertCloseEnoughForDiffing( - final BonsaiAccount source, final AccountValue account, final String context) { - if (source == null) { - throw new IllegalStateException(context + ": source is null but target isn't"); - } else { - if (source.nonce != account.getNonce()) { - throw new IllegalStateException(context + ": nonces differ"); - } - if (!Objects.equals(source.balance, account.getBalance())) { - throw new IllegalStateException(context + ": balances differ"); - } - if (!Objects.equals(source.storageRoot, account.getStorageRoot())) { - throw new IllegalStateException(context + ": Storage Roots differ"); - } - } - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiValue.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiValue.java deleted file mode 100644 index 7d862c7fb95..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiValue.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * - */ - -package org.hyperledger.besu.ethereum.bonsai; - -import org.hyperledger.besu.plugin.services.trielogs.TrieLog; - -import org.apache.commons.lang3.builder.EqualsBuilder; -import org.apache.commons.lang3.builder.HashCodeBuilder; - -public class BonsaiValue implements TrieLog.LogTuple { - private T prior; - private T updated; - private boolean cleared; - - public BonsaiValue(final T prior, final T updated) { - this.prior = prior; - this.updated = updated; - this.cleared = false; - } - - public BonsaiValue(final T prior, final T updated, final boolean cleared) { - this.prior = prior; - this.updated = updated; - this.cleared = cleared; - } - - @Override - public T getPrior() { - return prior; - } - - @Override - public T getUpdated() { - return updated; - } - - public BonsaiValue setPrior(final T prior) { - this.prior = prior; - return this; - } - - public BonsaiValue setUpdated(final T updated) { - this.cleared = updated == null; - this.updated = updated; - return this; - } - - public void setCleared() { - this.cleared = true; - } - - @Override - public boolean isCleared() { - return cleared; - } - - @Override - public String toString() { - return "BonsaiValue{" - + "prior=" - + prior - + ", updated=" - + updated - + ", cleared=" - + cleared - + '}'; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - BonsaiValue that = (BonsaiValue) o; - return new EqualsBuilder() - .append(cleared, that.cleared) - .append(prior, that.prior) - .append(updated, that.updated) - .isEquals(); - } - - @Override - public int hashCode() { - return new HashCodeBuilder(17, 37).append(prior).append(updated).append(cleared).toHashCode(); - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java deleted file mode 100644 index 5313977c037..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateProvider.java +++ /dev/null @@ -1,393 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * - */ - -package org.hyperledger.besu.ethereum.bonsai; - -import static org.hyperledger.besu.ethereum.bonsai.cache.CachedWorldStorageManager.RETAINED_LAYERS; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; -import org.hyperledger.besu.ethereum.bonsai.cache.CachedWorldStorageManager; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogManager; -import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogPruner; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; -import org.hyperledger.besu.ethereum.chain.Blockchain; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.MutableWorldState; -import org.hyperledger.besu.ethereum.proof.WorldStateProof; -import org.hyperledger.besu.ethereum.proof.WorldStateProofProvider; -import org.hyperledger.besu.ethereum.rlp.RLP; -import org.hyperledger.besu.ethereum.trie.MerkleTrieException; -import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; -import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; -import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; -import org.hyperledger.besu.evm.internal.EvmConfiguration; -import org.hyperledger.besu.evm.worldstate.WorldState; -import org.hyperledger.besu.metrics.ObservableMetricsSystem; -import org.hyperledger.besu.plugin.BesuContext; -import org.hyperledger.besu.plugin.services.trielogs.TrieLog; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; - -import com.google.common.annotations.VisibleForTesting; -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.units.bigints.UInt256; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class BonsaiWorldStateProvider implements WorldStateArchive { - - private static final Logger LOG = LoggerFactory.getLogger(BonsaiWorldStateProvider.class); - - private final Blockchain blockchain; - - private final CachedWorldStorageManager cachedWorldStorageManager; - private final TrieLogManager trieLogManager; - private final BonsaiWorldState persistedState; - private final BonsaiWorldStateKeyValueStorage worldStateStorage; - private final CachedMerkleTrieLoader cachedMerkleTrieLoader; - - public BonsaiWorldStateProvider( - final BonsaiWorldStateKeyValueStorage worldStateStorage, - final Blockchain blockchain, - final Optional maxLayersToLoad, - final CachedMerkleTrieLoader cachedMerkleTrieLoader, - final ObservableMetricsSystem metricsSystem, - final BesuContext pluginContext, - final EvmConfiguration evmConfiguration, - final TrieLogPruner trieLogPruner) { - - this.cachedWorldStorageManager = - new CachedWorldStorageManager(this, worldStateStorage, metricsSystem); - // TODO: de-dup constructors - this.trieLogManager = - new TrieLogManager( - blockchain, - worldStateStorage, - maxLayersToLoad.orElse(RETAINED_LAYERS), - pluginContext, - trieLogPruner); - this.blockchain = blockchain; - this.worldStateStorage = worldStateStorage; - this.cachedMerkleTrieLoader = cachedMerkleTrieLoader; - this.persistedState = new BonsaiWorldState(this, worldStateStorage, evmConfiguration); - blockchain - .getBlockHeader(persistedState.getWorldStateBlockHash()) - .ifPresent( - blockHeader -> - this.cachedWorldStorageManager.addCachedLayer( - blockHeader, persistedState.getWorldStateRootHash(), persistedState)); - } - - @VisibleForTesting - BonsaiWorldStateProvider( - final CachedWorldStorageManager cachedWorldStorageManager, - final TrieLogManager trieLogManager, - final BonsaiWorldStateKeyValueStorage worldStateStorage, - final Blockchain blockchain, - final CachedMerkleTrieLoader cachedMerkleTrieLoader, - final EvmConfiguration evmConfiguration) { - this.cachedWorldStorageManager = cachedWorldStorageManager; - this.trieLogManager = trieLogManager; - this.blockchain = blockchain; - this.worldStateStorage = worldStateStorage; - this.persistedState = new BonsaiWorldState(this, worldStateStorage, evmConfiguration); - this.cachedMerkleTrieLoader = cachedMerkleTrieLoader; - blockchain - .getBlockHeader(persistedState.getWorldStateBlockHash()) - .ifPresent( - blockHeader -> - this.cachedWorldStorageManager.addCachedLayer( - blockHeader, persistedState.getWorldStateRootHash(), persistedState)); - } - - @Override - public Optional get(final Hash rootHash, final Hash blockHash) { - return cachedWorldStorageManager - .getWorldState(blockHash) - .or( - () -> { - if (blockHash.equals(persistedState.blockHash())) { - return Optional.of(persistedState); - } else { - return Optional.empty(); - } - }) - .map(WorldState.class::cast); - } - - @Override - public boolean isWorldStateAvailable(final Hash rootHash, final Hash blockHash) { - return cachedWorldStorageManager.containWorldStateStorage(blockHash) - || persistedState.blockHash().equals(blockHash) - || worldStateStorage.isWorldStateAvailable(rootHash, blockHash); - } - - @Override - public Optional getMutable( - final BlockHeader blockHeader, final boolean shouldPersistState) { - if (shouldPersistState) { - return getMutable(blockHeader.getStateRoot(), blockHeader.getHash()); - } else { - final BlockHeader chainHeadBlockHeader = blockchain.getChainHeadHeader(); - if (chainHeadBlockHeader.getNumber() - blockHeader.getNumber() - >= trieLogManager.getMaxLayersToLoad()) { - LOG.warn( - "Exceeded the limit of back layers that can be loaded ({})", - trieLogManager.getMaxLayersToLoad()); - return Optional.empty(); - } - return cachedWorldStorageManager - .getWorldState(blockHeader.getHash()) - .or(() -> cachedWorldStorageManager.getNearestWorldState(blockHeader)) - .or(() -> cachedWorldStorageManager.getHeadWorldState(blockchain::getBlockHeader)) - .flatMap( - bonsaiWorldState -> - rollMutableStateToBlockHash(bonsaiWorldState, blockHeader.getHash())) - .map(MutableWorldState::freeze); - } - } - - @Override - public synchronized Optional getMutable( - final Hash rootHash, final Hash blockHash) { - return rollMutableStateToBlockHash(persistedState, blockHash); - } - - Optional rollMutableStateToBlockHash( - final BonsaiWorldState mutableState, final Hash blockHash) { - if (blockHash.equals(mutableState.blockHash())) { - return Optional.of(mutableState); - } else { - try { - - final Optional maybePersistedHeader = - blockchain.getBlockHeader(mutableState.blockHash()).map(BlockHeader.class::cast); - - final List rollBacks = new ArrayList<>(); - final List rollForwards = new ArrayList<>(); - if (maybePersistedHeader.isEmpty()) { - trieLogManager.getTrieLogLayer(mutableState.blockHash()).ifPresent(rollBacks::add); - } else { - BlockHeader targetHeader = blockchain.getBlockHeader(blockHash).get(); - BlockHeader persistedHeader = maybePersistedHeader.get(); - // roll back from persisted to even with target - Hash persistedBlockHash = persistedHeader.getBlockHash(); - while (persistedHeader.getNumber() > targetHeader.getNumber()) { - LOG.debug("Rollback {}", persistedBlockHash); - rollBacks.add(trieLogManager.getTrieLogLayer(persistedBlockHash).get()); - persistedHeader = blockchain.getBlockHeader(persistedHeader.getParentHash()).get(); - persistedBlockHash = persistedHeader.getBlockHash(); - } - // roll forward to target - Hash targetBlockHash = targetHeader.getBlockHash(); - while (persistedHeader.getNumber() < targetHeader.getNumber()) { - LOG.debug("Rollforward {}", targetBlockHash); - rollForwards.add(trieLogManager.getTrieLogLayer(targetBlockHash).get()); - targetHeader = blockchain.getBlockHeader(targetHeader.getParentHash()).get(); - targetBlockHash = targetHeader.getBlockHash(); - } - - // roll back in tandem until we hit a shared state - while (!persistedBlockHash.equals(targetBlockHash)) { - LOG.debug("Paired Rollback {}", persistedBlockHash); - LOG.debug("Paired Rollforward {}", targetBlockHash); - rollForwards.add(trieLogManager.getTrieLogLayer(targetBlockHash).get()); - targetHeader = blockchain.getBlockHeader(targetHeader.getParentHash()).get(); - - rollBacks.add(trieLogManager.getTrieLogLayer(persistedBlockHash).get()); - persistedHeader = blockchain.getBlockHeader(persistedHeader.getParentHash()).get(); - - targetBlockHash = targetHeader.getBlockHash(); - persistedBlockHash = persistedHeader.getBlockHash(); - } - } - - // attempt the state rolling - final BonsaiWorldStateUpdateAccumulator bonsaiUpdater = - (BonsaiWorldStateUpdateAccumulator) mutableState.updater(); - try { - for (final TrieLog rollBack : rollBacks) { - LOG.debug("Attempting Rollback of {}", rollBack.getBlockHash()); - bonsaiUpdater.rollBack(rollBack); - } - for (int i = rollForwards.size() - 1; i >= 0; i--) { - final var forward = rollForwards.get(i); - LOG.debug("Attempting Rollforward of {}", rollForwards.get(i).getBlockHash()); - bonsaiUpdater.rollForward(forward); - } - bonsaiUpdater.commit(); - - mutableState.persist(blockchain.getBlockHeader(blockHash).get()); - - LOG.debug( - "Archive rolling finished, {} now at {}", - mutableState.getWorldStateStorage().getClass().getSimpleName(), - blockHash); - return Optional.of(mutableState); - } catch (final MerkleTrieException re) { - // need to throw to trigger the heal - throw re; - } catch (final Exception e) { - // if we fail we must clean up the updater - bonsaiUpdater.reset(); - LOG.debug( - "State rolling failed on " - + mutableState.getWorldStateStorage().getClass().getSimpleName() - + " for block hash " - + blockHash, - e); - - return Optional.empty(); - } - } catch (final RuntimeException re) { - LOG.info("Archive rolling failed for block hash " + blockHash, re); - if (re instanceof MerkleTrieException) { - // need to throw to trigger the heal - throw re; - } - throw new MerkleTrieException( - "invalid", Optional.of(Address.ZERO), Hash.EMPTY, Bytes.EMPTY); - } - } - } - - public CachedMerkleTrieLoader getCachedMerkleTrieLoader() { - return cachedMerkleTrieLoader; - } - - @Override - public MutableWorldState getMutable() { - return persistedState; - } - - /** - * Prepares the state healing process for a given address and location. It prepares the state - * healing, including retrieving data from storage, identifying invalid slots or nodes, removing - * account and slot from the state trie, and committing the changes. Finally, it downgrades the - * world state storage to partial flat database mode. - */ - public void prepareStateHealing(final Address address, final Bytes location) { - final Set keysToDelete = new HashSet<>(); - final BonsaiWorldStateKeyValueStorage.BonsaiUpdater updater = worldStateStorage.updater(); - final Hash accountHash = address.addressHash(); - final StoredMerklePatriciaTrie accountTrie = - new StoredMerklePatriciaTrie<>( - (l, h) -> { - final Optional node = worldStateStorage.getAccountStateTrieNode(l, h); - if (node.isPresent()) { - keysToDelete.add(l); - } - return node; - }, - persistedState.getWorldStateRootHash(), - Function.identity(), - Function.identity()); - try { - accountTrie - .get(accountHash) - .map(RLP::input) - .map(StateTrieAccountValue::readFrom) - .ifPresent( - account -> { - final StoredMerklePatriciaTrie storageTrie = - new StoredMerklePatriciaTrie<>( - (l, h) -> { - Optional node = - worldStateStorage.getAccountStorageTrieNode(accountHash, l, h); - if (node.isPresent()) { - keysToDelete.add(Bytes.concatenate(accountHash, l)); - } - return node; - }, - account.getStorageRoot(), - Function.identity(), - Function.identity()); - try { - storageTrie.getPath(location); - } catch (Exception eA) { - LOG.warn("Invalid slot found for account {} at location {}", address, location); - // ignore - } - }); - } catch (Exception eA) { - LOG.warn("Invalid node for account {} at location {}", address, location); - // ignore - } - keysToDelete.forEach(bytes -> updater.removeAccountStateTrieNode(bytes, null)); - updater.commit(); - - worldStateStorage.downgradeToPartialFlatDbMode(); - } - - public TrieLogManager getTrieLogManager() { - return trieLogManager; - } - - public CachedWorldStorageManager getCachedWorldStorageManager() { - return cachedWorldStorageManager; - } - - @Override - public void resetArchiveStateTo(final BlockHeader blockHeader) { - persistedState.resetWorldStateTo(blockHeader); - this.cachedWorldStorageManager.reset(); - this.cachedWorldStorageManager.addCachedLayer( - blockHeader, persistedState.getWorldStateRootHash(), persistedState); - } - - @Override - public Optional getAccountProof( - final BlockHeader blockHeader, - final Address accountAddress, - final List accountStorageKeys, - final Function, ? extends Optional> mapper) { - try (BonsaiWorldState ws = (BonsaiWorldState) getMutable(blockHeader, false).orElse(null)) { - if (ws != null) { - final WorldStateProofProvider worldStateProofProvider = - new WorldStateProofProvider(ws.getWorldStateStorage()); - return mapper.apply( - worldStateProofProvider.getAccountProof( - ws.getWorldStateRootHash(), accountAddress, accountStorageKeys)); - } - } catch (Exception ex) { - LOG.error("failed proof query for " + blockHeader.getBlockHash().toShortHexString(), ex); - } - return Optional.empty(); - } - - @Override - public Optional getNodeData(final Hash hash) { - return Optional.empty(); - } - - @Override - public void close() { - try { - worldStateStorage.close(); - } catch (Exception e) { - // no op - } - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedBonsaiWorldView.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedBonsaiWorldView.java deleted file mode 100644 index 473933b3959..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedBonsaiWorldView.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright Hyperledger Besu contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * - */ -package org.hyperledger.besu.ethereum.bonsai.cache; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.BonsaiStorageSubscriber; -import org.hyperledger.besu.ethereum.core.BlockHeader; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class CachedBonsaiWorldView implements BonsaiStorageSubscriber { - private BonsaiWorldStateKeyValueStorage worldStateStorage; - private final BlockHeader blockHeader; - private long worldViewSubscriberId; - private static final Logger LOG = LoggerFactory.getLogger(CachedBonsaiWorldView.class); - - public CachedBonsaiWorldView( - final BlockHeader blockHeader, final BonsaiWorldStateKeyValueStorage worldView) { - this.blockHeader = blockHeader; - this.worldStateStorage = worldView; - this.worldViewSubscriberId = worldStateStorage.subscribe(this); - } - - public BonsaiWorldStateKeyValueStorage getWorldStateStorage() { - return worldStateStorage; - } - - public long getBlockNumber() { - return blockHeader.getNumber(); - } - - public Hash getBlockHash() { - return blockHeader.getHash(); - } - - public synchronized void close() { - worldStateStorage.unSubscribe(this.worldViewSubscriberId); - try { - worldStateStorage.close(); - } catch (final Exception e) { - LOG.warn("Failed to close worldstate storage for block " + blockHeader.toLogString(), e); - } - } - - public synchronized void updateWorldStateStorage( - final BonsaiWorldStateKeyValueStorage newWorldStateStorage) { - long newSubscriberId = newWorldStateStorage.subscribe(this); - this.worldStateStorage.unSubscribe(this.worldViewSubscriberId); - BonsaiWorldStateKeyValueStorage oldWorldStateStorage = this.worldStateStorage; - this.worldStateStorage = newWorldStateStorage; - this.worldViewSubscriberId = newSubscriberId; - try { - oldWorldStateStorage.close(); - } catch (final Exception e) { - LOG.warn( - "During update, failed to close prior worldstate storage for block " - + blockHeader.toLogString(), - e); - } - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedMerkleTrieLoader.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedMerkleTrieLoader.java deleted file mode 100644 index f53342d6317..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedMerkleTrieLoader.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * - */ -package org.hyperledger.besu.ethereum.bonsai.cache; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.datatypes.StorageSlotKey; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.BonsaiStorageSubscriber; -import org.hyperledger.besu.ethereum.trie.MerkleTrie; -import org.hyperledger.besu.ethereum.trie.MerkleTrieException; -import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; -import org.hyperledger.besu.metrics.BesuMetricCategory; -import org.hyperledger.besu.metrics.ObservableMetricsSystem; -import org.hyperledger.besu.metrics.prometheus.PrometheusMetricsSystem; - -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.function.Function; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import io.prometheus.client.guava.cache.CacheMetricsCollector; -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; - -public class CachedMerkleTrieLoader implements BonsaiStorageSubscriber { - - private static final int ACCOUNT_CACHE_SIZE = 100_000; - private static final int STORAGE_CACHE_SIZE = 200_000; - private final Cache accountNodes = - CacheBuilder.newBuilder().recordStats().maximumSize(ACCOUNT_CACHE_SIZE).build(); - private final Cache storageNodes = - CacheBuilder.newBuilder().recordStats().maximumSize(STORAGE_CACHE_SIZE).build(); - - public CachedMerkleTrieLoader(final ObservableMetricsSystem metricsSystem) { - - CacheMetricsCollector cacheMetrics = new CacheMetricsCollector(); - cacheMetrics.addCache("accountsNodes", accountNodes); - cacheMetrics.addCache("storageNodes", storageNodes); - if (metricsSystem instanceof PrometheusMetricsSystem prometheusMetricsSystem) - prometheusMetricsSystem.addCollector(BesuMetricCategory.BLOCKCHAIN, () -> cacheMetrics); - } - - public void preLoadAccount( - final BonsaiWorldStateKeyValueStorage worldStateStorage, - final Hash worldStateRootHash, - final Address account) { - CompletableFuture.runAsync( - () -> cacheAccountNodes(worldStateStorage, worldStateRootHash, account)); - } - - @VisibleForTesting - public void cacheAccountNodes( - final BonsaiWorldStateKeyValueStorage worldStateStorage, - final Hash worldStateRootHash, - final Address account) { - final long storageSubscriberId = worldStateStorage.subscribe(this); - try { - final StoredMerklePatriciaTrie accountTrie = - new StoredMerklePatriciaTrie<>( - (location, hash) -> { - Optional node = getAccountStateTrieNode(worldStateStorage, location, hash); - node.ifPresent(bytes -> accountNodes.put(Hash.hash(bytes), bytes)); - return node; - }, - worldStateRootHash, - Function.identity(), - Function.identity()); - accountTrie.get(account.addressHash()); - } catch (MerkleTrieException e) { - // ignore exception for the cache - } finally { - worldStateStorage.unSubscribe(storageSubscriberId); - } - } - - public void preLoadStorageSlot( - final BonsaiWorldStateKeyValueStorage worldStateStorage, - final Address account, - final StorageSlotKey slotKey) { - CompletableFuture.runAsync(() -> cacheStorageNodes(worldStateStorage, account, slotKey)); - } - - @VisibleForTesting - public void cacheStorageNodes( - final BonsaiWorldStateKeyValueStorage worldStateStorage, - final Address account, - final StorageSlotKey slotKey) { - final Hash accountHash = account.addressHash(); - final long storageSubscriberId = worldStateStorage.subscribe(this); - try { - worldStateStorage - .getStateTrieNode(Bytes.concatenate(accountHash, Bytes.EMPTY)) - .ifPresent( - storageRoot -> { - try { - final StoredMerklePatriciaTrie storageTrie = - new StoredMerklePatriciaTrie<>( - (location, hash) -> { - Optional node = - getAccountStorageTrieNode( - worldStateStorage, accountHash, location, hash); - node.ifPresent(bytes -> storageNodes.put(Hash.hash(bytes), bytes)); - return node; - }, - Hash.hash(storageRoot), - Function.identity(), - Function.identity()); - storageTrie.get(slotKey.getSlotHash()); - } catch (MerkleTrieException e) { - // ignore exception for the cache - } - }); - } finally { - worldStateStorage.unSubscribe(storageSubscriberId); - } - } - - public Optional getAccountStateTrieNode( - final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage, - final Bytes location, - final Bytes32 nodeHash) { - if (nodeHash.equals(MerkleTrie.EMPTY_TRIE_NODE_HASH)) { - return Optional.of(MerkleTrie.EMPTY_TRIE_NODE); - } else { - return Optional.ofNullable(accountNodes.getIfPresent(nodeHash)) - .or(() -> worldStateKeyValueStorage.getAccountStateTrieNode(location, nodeHash)); - } - } - - public Optional getAccountStorageTrieNode( - final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage, - final Hash accountHash, - final Bytes location, - final Bytes32 nodeHash) { - if (nodeHash.equals(MerkleTrie.EMPTY_TRIE_NODE_HASH)) { - return Optional.of(MerkleTrie.EMPTY_TRIE_NODE); - } else { - return Optional.ofNullable(storageNodes.getIfPresent(nodeHash)) - .or( - () -> - worldStateKeyValueStorage.getAccountStorageTrieNode( - accountHash, location, nodeHash)); - } - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedMerkleTrieLoaderModule.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedMerkleTrieLoaderModule.java deleted file mode 100644 index 51cfdd8707e..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedMerkleTrieLoaderModule.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.besu.ethereum.bonsai.cache; - -import org.hyperledger.besu.metrics.ObservableMetricsSystem; - -import dagger.Module; -import dagger.Provides; - -@Module -public class CachedMerkleTrieLoaderModule { - - @Provides - CachedMerkleTrieLoader provideCachedMerkleTrieLoaderModule( - final ObservableMetricsSystem metricsSystem) { - return new CachedMerkleTrieLoader(metricsSystem); - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedWorldStorageManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedWorldStorageManager.java deleted file mode 100644 index bf205f05b72..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/cache/CachedWorldStorageManager.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.bonsai.cache; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateProvider; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiSnapshotWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.BonsaiStorageSubscriber; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateLayerStorage; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.evm.internal.EvmConfiguration; -import org.hyperledger.besu.metrics.ObservableMetricsSystem; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; - -import org.apache.tuweni.bytes.Bytes32; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class CachedWorldStorageManager implements BonsaiStorageSubscriber { - public static final long RETAINED_LAYERS = 512; // at least 256 + typical rollbacks - private static final Logger LOG = LoggerFactory.getLogger(CachedWorldStorageManager.class); - private final BonsaiWorldStateProvider archive; - private final ObservableMetricsSystem metricsSystem; - private final EvmConfiguration evmConfiguration; - - private final BonsaiWorldStateKeyValueStorage rootWorldStateStorage; - private final Map cachedWorldStatesByHash; - - private CachedWorldStorageManager( - final BonsaiWorldStateProvider archive, - final BonsaiWorldStateKeyValueStorage worldStateStorage, - final Map cachedWorldStatesByHash, - final ObservableMetricsSystem metricsSystem, - final EvmConfiguration evmConfiguration) { - worldStateStorage.subscribe(this); - this.rootWorldStateStorage = worldStateStorage; - this.cachedWorldStatesByHash = cachedWorldStatesByHash; - this.archive = archive; - this.metricsSystem = metricsSystem; - this.evmConfiguration = evmConfiguration; - } - - public CachedWorldStorageManager( - final BonsaiWorldStateProvider archive, - final BonsaiWorldStateKeyValueStorage worldStateStorage, - final ObservableMetricsSystem metricsSystem) { - this( - archive, - worldStateStorage, - new ConcurrentHashMap<>(), - metricsSystem, - EvmConfiguration.DEFAULT); - } - - public synchronized void addCachedLayer( - final BlockHeader blockHeader, - final Hash worldStateRootHash, - final BonsaiWorldState forWorldState) { - final Optional cachedBonsaiWorldView = - Optional.ofNullable(this.cachedWorldStatesByHash.get(blockHeader.getBlockHash())); - if (cachedBonsaiWorldView.isPresent()) { - // only replace if it is a layered storage - if (forWorldState.isPersisted() - && cachedBonsaiWorldView.get().getWorldStateStorage() - instanceof BonsaiWorldStateLayerStorage) { - LOG.atDebug() - .setMessage("updating layered world state for block {}, state root hash {}") - .addArgument(blockHeader::toLogString) - .addArgument(worldStateRootHash::toShortHexString) - .log(); - cachedBonsaiWorldView - .get() - .updateWorldStateStorage( - new BonsaiSnapshotWorldStateKeyValueStorage( - forWorldState.getWorldStateStorage(), metricsSystem)); - } - } else { - LOG.atDebug() - .setMessage("adding layered world state for block {}, state root hash {}") - .addArgument(blockHeader::toLogString) - .addArgument(worldStateRootHash::toShortHexString) - .log(); - if (forWorldState.isPersisted()) { - cachedWorldStatesByHash.put( - blockHeader.getHash(), - new CachedBonsaiWorldView( - blockHeader, - new BonsaiSnapshotWorldStateKeyValueStorage( - forWorldState.getWorldStateStorage(), metricsSystem))); - } else { - // otherwise, add the layer to the cache - cachedWorldStatesByHash.put( - blockHeader.getHash(), - new CachedBonsaiWorldView( - blockHeader, - ((BonsaiWorldStateLayerStorage) forWorldState.getWorldStateStorage()).clone())); - } - } - scrubCachedLayers(blockHeader.getNumber()); - } - - private synchronized void scrubCachedLayers(final long newMaxHeight) { - if (cachedWorldStatesByHash.size() > RETAINED_LAYERS) { - final long waterline = newMaxHeight - RETAINED_LAYERS; - cachedWorldStatesByHash.values().stream() - .filter(layer -> layer.getBlockNumber() < waterline) - .toList() - .forEach( - layer -> { - cachedWorldStatesByHash.remove(layer.getBlockHash()); - layer.close(); - }); - } - } - - public Optional getWorldState(final Hash blockHash) { - if (cachedWorldStatesByHash.containsKey(blockHash)) { - // return a new worldstate using worldstate storage and an isolated copy of the updater - return Optional.ofNullable(cachedWorldStatesByHash.get(blockHash)) - .map( - cached -> - new BonsaiWorldState( - archive, - new BonsaiWorldStateLayerStorage(cached.getWorldStateStorage()), - evmConfiguration)); - } - LOG.atDebug() - .setMessage("did not find worldstate in cache for {}") - .addArgument(blockHash.toShortHexString()) - .log(); - - return Optional.empty(); - } - - public Optional getNearestWorldState(final BlockHeader blockHeader) { - LOG.atDebug() - .setMessage("getting nearest worldstate for {}") - .addArgument(blockHeader.toLogString()) - .log(); - - return Optional.ofNullable( - cachedWorldStatesByHash.get(blockHeader.getParentHash())) // search parent block - .map(CachedBonsaiWorldView::getWorldStateStorage) - .or( - () -> { - // or else search the nearest state in the cache - LOG.atDebug() - .setMessage("searching cache for nearest worldstate for {}") - .addArgument(blockHeader.toLogString()) - .log(); - - final List cachedBonsaiWorldViews = - new ArrayList<>(cachedWorldStatesByHash.values()); - return cachedBonsaiWorldViews.stream() - .sorted( - Comparator.comparingLong( - view -> Math.abs(blockHeader.getNumber() - view.getBlockNumber()))) - .map(CachedBonsaiWorldView::getWorldStateStorage) - .findFirst(); - }) - .map( - storage -> - new BonsaiWorldState( // wrap the state in a layered worldstate - archive, new BonsaiWorldStateLayerStorage(storage), evmConfiguration)); - } - - public Optional getHeadWorldState( - final Function> hashBlockHeaderFunction) { - - LOG.atDebug().setMessage("getting head worldstate").log(); - - return rootWorldStateStorage - .getWorldStateBlockHash() - .flatMap(hashBlockHeaderFunction) - .flatMap( - blockHeader -> { - // add the head to the cache - addCachedLayer( - blockHeader, - blockHeader.getStateRoot(), - new BonsaiWorldState(archive, rootWorldStateStorage, evmConfiguration)); - return getWorldState(blockHeader.getHash()); - }); - } - - public boolean containWorldStateStorage(final Hash blockHash) { - return cachedWorldStatesByHash.containsKey(blockHash); - } - - public void reset() { - this.cachedWorldStatesByHash.clear(); - } - - @Override - public void onClearStorage() { - this.cachedWorldStatesByHash.clear(); - } - - @Override - public void onClearFlatDatabaseStorage() { - this.cachedWorldStatesByHash.clear(); - } - - @Override - public void onClearTrieLog() { - this.cachedWorldStatesByHash.clear(); - } - - @Override - public void onCloseStorage() { - this.cachedWorldStatesByHash.clear(); - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiWorldStateKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiWorldStateKeyValueStorage.java deleted file mode 100644 index 28e2c5bd7aa..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiWorldStateKeyValueStorage.java +++ /dev/null @@ -1,565 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.bonsai.storage; - -import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE; -import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_STORAGE; -import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.CODE_STORAGE; -import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.datatypes.StorageSlotKey; -import org.hyperledger.besu.ethereum.bonsai.storage.flat.FlatDbStrategy; -import org.hyperledger.besu.ethereum.bonsai.storage.flat.FullFlatDbStrategy; -import org.hyperledger.besu.ethereum.bonsai.storage.flat.PartialFlatDbStrategy; -import org.hyperledger.besu.ethereum.storage.StorageProvider; -import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; -import org.hyperledger.besu.ethereum.trie.MerkleTrie; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; -import org.hyperledger.besu.ethereum.worldstate.FlatDbMode; -import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; -import org.hyperledger.besu.evm.account.AccountStorageEntry; -import org.hyperledger.besu.metrics.ObservableMetricsSystem; -import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; -import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; -import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; -import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; -import org.hyperledger.besu.util.Subscribers; - -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Map; -import java.util.NavigableMap; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Predicate; -import java.util.function.Supplier; -import java.util.stream.Stream; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@SuppressWarnings("unused") -public class BonsaiWorldStateKeyValueStorage implements WorldStateStorage, AutoCloseable { - private static final Logger LOG = LoggerFactory.getLogger(BonsaiWorldStateKeyValueStorage.class); - - // 0x776f726c64526f6f74 - public static final byte[] WORLD_ROOT_HASH_KEY = "worldRoot".getBytes(StandardCharsets.UTF_8); - // 0x776f726c64426c6f636b48617368 - public static final byte[] WORLD_BLOCK_HASH_KEY = - "worldBlockHash".getBytes(StandardCharsets.UTF_8); - - // 0x666C61744462537461747573 - public static final byte[] FLAT_DB_MODE = "flatDbStatus".getBytes(StandardCharsets.UTF_8); - - protected FlatDbMode flatDbMode; - protected FlatDbStrategy flatDbStrategy; - - protected final SegmentedKeyValueStorage composedWorldStateStorage; - protected final KeyValueStorage trieLogStorage; - - protected final ObservableMetricsSystem metricsSystem; - - private final AtomicBoolean shouldClose = new AtomicBoolean(false); - - protected final AtomicBoolean isClosed = new AtomicBoolean(false); - - protected final Subscribers subscribers = Subscribers.create(); - - public BonsaiWorldStateKeyValueStorage( - final StorageProvider provider, final ObservableMetricsSystem metricsSystem) { - this.composedWorldStateStorage = - provider.getStorageBySegmentIdentifiers( - List.of( - ACCOUNT_INFO_STATE, CODE_STORAGE, ACCOUNT_STORAGE_STORAGE, TRIE_BRANCH_STORAGE)); - this.trieLogStorage = - provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.TRIE_LOG_STORAGE); - this.metricsSystem = metricsSystem; - loadFlatDbStrategy(); - } - - public BonsaiWorldStateKeyValueStorage( - final FlatDbMode flatDbMode, - final FlatDbStrategy flatDbStrategy, - final SegmentedKeyValueStorage composedWorldStateStorage, - final KeyValueStorage trieLogStorage, - final ObservableMetricsSystem metricsSystem) { - this.flatDbMode = flatDbMode; - this.flatDbStrategy = flatDbStrategy; - this.composedWorldStateStorage = composedWorldStateStorage; - this.trieLogStorage = trieLogStorage; - this.metricsSystem = metricsSystem; - } - - private void loadFlatDbStrategy() { - // derive our flatdb strategy from db or default: - var newFlatDbMode = deriveFlatDbStrategy(); - - // if flatDbMode is not loaded or has changed, reload flatDbStrategy - if (this.flatDbMode == null || !this.flatDbMode.equals(newFlatDbMode)) { - this.flatDbMode = newFlatDbMode; - if (flatDbMode == FlatDbMode.FULL) { - this.flatDbStrategy = new FullFlatDbStrategy(metricsSystem); - } else { - this.flatDbStrategy = new PartialFlatDbStrategy(metricsSystem); - } - } - } - - public FlatDbMode deriveFlatDbStrategy() { - var flatDbMode = - FlatDbMode.fromVersion( - composedWorldStateStorage - .get(TRIE_BRANCH_STORAGE, FLAT_DB_MODE) - .map(Bytes::wrap) - .orElse(FlatDbMode.PARTIAL.getVersion())); - LOG.info("Bonsai flat db mode found {}", flatDbMode); - - return flatDbMode; - } - - public FlatDbStrategy getFlatDbStrategy() { - if (flatDbStrategy == null) { - loadFlatDbStrategy(); - } - return flatDbStrategy; - } - - @Override - public DataStorageFormat getDataStorageFormat() { - return DataStorageFormat.BONSAI; - } - - @Override - public FlatDbMode getFlatDbMode() { - return flatDbMode; - } - - @Override - public Optional getCode(final Bytes32 codeHash, final Hash accountHash) { - if (codeHash.equals(Hash.EMPTY)) { - return Optional.of(Bytes.EMPTY); - } else { - return getFlatDbStrategy().getFlatCode(codeHash, accountHash, composedWorldStateStorage); - } - } - - public Optional getAccount(final Hash accountHash) { - return getFlatDbStrategy() - .getFlatAccount( - this::getWorldStateRootHash, - this::getAccountStateTrieNode, - accountHash, - composedWorldStateStorage); - } - - @Override - public Optional getAccountStateTrieNode(final Bytes location, final Bytes32 nodeHash) { - if (nodeHash.equals(MerkleTrie.EMPTY_TRIE_NODE_HASH)) { - return Optional.of(MerkleTrie.EMPTY_TRIE_NODE); - } else { - return composedWorldStateStorage - .get(TRIE_BRANCH_STORAGE, location.toArrayUnsafe()) - .map(Bytes::wrap) - .filter(b -> Hash.hash(b).equals(nodeHash)); - } - } - - @Override - public Optional getAccountStorageTrieNode( - final Hash accountHash, final Bytes location, final Bytes32 nodeHash) { - if (nodeHash.equals(MerkleTrie.EMPTY_TRIE_NODE_HASH)) { - return Optional.of(MerkleTrie.EMPTY_TRIE_NODE); - } else { - return composedWorldStateStorage - .get(TRIE_BRANCH_STORAGE, Bytes.concatenate(accountHash, location).toArrayUnsafe()) - .map(Bytes::wrap) - .filter(b -> Hash.hash(b).equals(nodeHash)); - } - } - - @Override - public Optional getTrieNodeUnsafe(final Bytes key) { - return composedWorldStateStorage - .get(TRIE_BRANCH_STORAGE, Bytes.concatenate(key).toArrayUnsafe()) - .map(Bytes::wrap); - } - - public Optional getTrieLog(final Hash blockHash) { - return trieLogStorage.get(blockHash.toArrayUnsafe()); - } - - public Stream streamTrieLogKeys(final int limit) { - return trieLogStorage.streamKeys().limit(limit); - } - - public Optional getStateTrieNode(final Bytes location) { - return composedWorldStateStorage - .get(TRIE_BRANCH_STORAGE, location.toArrayUnsafe()) - .map(Bytes::wrap); - } - - public Optional getWorldStateRootHash() { - return composedWorldStateStorage.get(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY).map(Bytes::wrap); - } - - public Optional getWorldStateBlockHash() { - return composedWorldStateStorage - .get(TRIE_BRANCH_STORAGE, WORLD_BLOCK_HASH_KEY) - .map(Bytes32::wrap) - .map(Hash::wrap); - } - - public Optional getStorageValueByStorageSlotKey( - final Hash accountHash, final StorageSlotKey storageSlotKey) { - return getStorageValueByStorageSlotKey( - () -> - getAccount(accountHash) - .map( - b -> - StateTrieAccountValue.readFrom( - org.hyperledger.besu.ethereum.rlp.RLP.input(b)) - .getStorageRoot()), - accountHash, - storageSlotKey); - } - - public Optional getStorageValueByStorageSlotKey( - final Supplier> storageRootSupplier, - final Hash accountHash, - final StorageSlotKey storageSlotKey) { - return getFlatDbStrategy() - .getFlatStorageValueByStorageSlotKey( - this::getWorldStateRootHash, - storageRootSupplier, - (location, hash) -> getAccountStorageTrieNode(accountHash, location, hash), - accountHash, - storageSlotKey, - composedWorldStateStorage); - } - - @Override - public Map streamFlatAccounts( - final Bytes startKeyHash, final Bytes32 endKeyHash, final long max) { - return getFlatDbStrategy() - .streamAccountFlatDatabase(composedWorldStateStorage, startKeyHash, endKeyHash, max); - } - - @Override - public Map streamFlatStorages( - final Hash accountHash, final Bytes startKeyHash, final Bytes32 endKeyHash, final long max) { - return getFlatDbStrategy() - .streamStorageFlatDatabase( - composedWorldStateStorage, accountHash, startKeyHash, endKeyHash, max); - } - - public NavigableMap storageEntriesFrom( - final Hash addressHash, final Bytes32 startKeyHash, final int limit) { - throw new RuntimeException("Bonsai Tries does not currently support enumerating storage"); - } - - @Override - public Optional getNodeData(final Bytes location, final Bytes32 hash) { - return Optional.empty(); - } - - @Override - public boolean isWorldStateAvailable(final Bytes32 rootHash, final Hash blockHash) { - return composedWorldStateStorage - .get(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY) - .map(Bytes32::wrap) - .map(hash -> hash.equals(rootHash) || trieLogStorage.containsKey(blockHash.toArrayUnsafe())) - .orElse(false); - } - - public void upgradeToFullFlatDbMode() { - final SegmentedKeyValueStorageTransaction transaction = - composedWorldStateStorage.startTransaction(); - // TODO: consider ARCHIVE mode - transaction.put( - TRIE_BRANCH_STORAGE, FLAT_DB_MODE, FlatDbMode.FULL.getVersion().toArrayUnsafe()); - transaction.commit(); - loadFlatDbStrategy(); // force reload of flat db reader strategy - } - - public void downgradeToPartialFlatDbMode() { - final SegmentedKeyValueStorageTransaction transaction = - composedWorldStateStorage.startTransaction(); - transaction.put( - TRIE_BRANCH_STORAGE, FLAT_DB_MODE, FlatDbMode.PARTIAL.getVersion().toArrayUnsafe()); - transaction.commit(); - loadFlatDbStrategy(); // force reload of flat db reader strategy - } - - @Override - public void clear() { - subscribers.forEach(BonsaiStorageSubscriber::onClearStorage); - getFlatDbStrategy().clearAll(composedWorldStateStorage); - composedWorldStateStorage.clear(TRIE_BRANCH_STORAGE); - trieLogStorage.clear(); - loadFlatDbStrategy(); // force reload of flat db reader strategy - } - - @Override - public void clearTrieLog() { - subscribers.forEach(BonsaiStorageSubscriber::onClearTrieLog); - trieLogStorage.clear(); - } - - @Override - public void clearFlatDatabase() { - subscribers.forEach(BonsaiStorageSubscriber::onClearFlatDatabaseStorage); - getFlatDbStrategy().resetOnResync(composedWorldStateStorage); - } - - @Override - public BonsaiUpdater updater() { - return new Updater( - composedWorldStateStorage.startTransaction(), - trieLogStorage.startTransaction(), - flatDbStrategy); - } - - @Override - public long prune(final Predicate inUseCheck) { - throw new RuntimeException("Bonsai Tries do not work with pruning."); - } - - public boolean pruneTrieLog(final Hash blockHash) { - try { - return trieLogStorage.tryDelete(blockHash.toArrayUnsafe()); - } catch (Exception e) { - LOG.error("Error pruning trie log for block hash {}", blockHash, e); - return false; - } - } - - @Override - public long addNodeAddedListener(final NodesAddedListener listener) { - throw new RuntimeException("addNodeAddedListener not available"); - } - - @Override - public void removeNodeAddedListener(final long id) { - throw new RuntimeException("removeNodeAddedListener not available"); - } - - public interface BonsaiUpdater extends WorldStateStorage.Updater { - BonsaiUpdater removeCode(final Hash accountHash); - - BonsaiUpdater removeAccountInfoState(final Hash accountHash); - - BonsaiUpdater putAccountInfoState(final Hash accountHash, final Bytes accountValue); - - BonsaiUpdater putStorageValueBySlotHash( - final Hash accountHash, final Hash slotHash, final Bytes storage); - - void removeStorageValueBySlotHash(final Hash accountHash, final Hash slotHash); - - SegmentedKeyValueStorageTransaction getWorldStateTransaction(); - - KeyValueStorageTransaction getTrieLogStorageTransaction(); - } - - public static class Updater implements BonsaiUpdater { - - private final SegmentedKeyValueStorageTransaction composedWorldStateTransaction; - private final KeyValueStorageTransaction trieLogStorageTransaction; - private final FlatDbStrategy flatDbStrategy; - - public Updater( - final SegmentedKeyValueStorageTransaction composedWorldStateTransaction, - final KeyValueStorageTransaction trieLogStorageTransaction, - final FlatDbStrategy flatDbStrategy) { - - this.composedWorldStateTransaction = composedWorldStateTransaction; - this.trieLogStorageTransaction = trieLogStorageTransaction; - this.flatDbStrategy = flatDbStrategy; - } - - @Override - public BonsaiUpdater removeCode(final Hash accountHash) { - flatDbStrategy.removeFlatCode(composedWorldStateTransaction, accountHash); - return this; - } - - @Override - public BonsaiUpdater putCode(final Hash accountHash, final Bytes32 codeHash, final Bytes code) { - if (code.size() == 0) { - // Don't save empty values - return this; - } - flatDbStrategy.putFlatCode(composedWorldStateTransaction, accountHash, codeHash, code); - return this; - } - - @Override - public BonsaiUpdater removeAccountInfoState(final Hash accountHash) { - flatDbStrategy.removeFlatAccount(composedWorldStateTransaction, accountHash); - return this; - } - - @Override - public BonsaiUpdater putAccountInfoState(final Hash accountHash, final Bytes accountValue) { - if (accountValue.size() == 0) { - // Don't save empty values - return this; - } - flatDbStrategy.putFlatAccount(composedWorldStateTransaction, accountHash, accountValue); - return this; - } - - @Override - public WorldStateStorage.Updater saveWorldState( - final Bytes blockHash, final Bytes32 nodeHash, final Bytes node) { - composedWorldStateTransaction.put( - TRIE_BRANCH_STORAGE, Bytes.EMPTY.toArrayUnsafe(), node.toArrayUnsafe()); - composedWorldStateTransaction.put( - TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY, nodeHash.toArrayUnsafe()); - composedWorldStateTransaction.put( - TRIE_BRANCH_STORAGE, WORLD_BLOCK_HASH_KEY, blockHash.toArrayUnsafe()); - return this; - } - - @Override - public BonsaiUpdater putAccountStateTrieNode( - final Bytes location, final Bytes32 nodeHash, final Bytes node) { - if (nodeHash.equals(MerkleTrie.EMPTY_TRIE_NODE_HASH)) { - // Don't save empty nodes - return this; - } - composedWorldStateTransaction.put( - TRIE_BRANCH_STORAGE, location.toArrayUnsafe(), node.toArrayUnsafe()); - return this; - } - - @Override - public BonsaiUpdater removeAccountStateTrieNode(final Bytes location, final Bytes32 nodeHash) { - composedWorldStateTransaction.remove(TRIE_BRANCH_STORAGE, location.toArrayUnsafe()); - return this; - } - - @Override - public synchronized BonsaiUpdater putAccountStorageTrieNode( - final Hash accountHash, final Bytes location, final Bytes32 nodeHash, final Bytes node) { - if (nodeHash.equals(MerkleTrie.EMPTY_TRIE_NODE_HASH)) { - // Don't save empty nodes - return this; - } - composedWorldStateTransaction.put( - TRIE_BRANCH_STORAGE, - Bytes.concatenate(accountHash, location).toArrayUnsafe(), - node.toArrayUnsafe()); - return this; - } - - @Override - public synchronized BonsaiUpdater putStorageValueBySlotHash( - final Hash accountHash, final Hash slotHash, final Bytes storage) { - flatDbStrategy.putFlatAccountStorageValueByStorageSlotHash( - composedWorldStateTransaction, accountHash, slotHash, storage); - return this; - } - - @Override - public synchronized void removeStorageValueBySlotHash( - final Hash accountHash, final Hash slotHash) { - flatDbStrategy.removeFlatAccountStorageValueByStorageSlotHash( - composedWorldStateTransaction, accountHash, slotHash); - } - - @Override - public SegmentedKeyValueStorageTransaction getWorldStateTransaction() { - return composedWorldStateTransaction; - } - - @Override - public KeyValueStorageTransaction getTrieLogStorageTransaction() { - return trieLogStorageTransaction; - } - - @Override - public void commit() { - // write the log ahead, then the worldstate - trieLogStorageTransaction.commit(); - composedWorldStateTransaction.commit(); - } - - @Override - public void rollback() { - composedWorldStateTransaction.rollback(); - trieLogStorageTransaction.rollback(); - } - } - - @Override - public synchronized void close() throws Exception { - // when the storage clears, close - shouldClose.set(true); - tryClose(); - } - - public synchronized long subscribe(final BonsaiStorageSubscriber sub) { - if (isClosed.get()) { - throw new RuntimeException("Storage is marked to close or has already closed"); - } - return subscribers.subscribe(sub); - } - - public synchronized void unSubscribe(final long id) { - subscribers.unsubscribe(id); - try { - tryClose(); - } catch (Exception e) { - LOG.atWarn() - .setMessage("exception while trying to close : {}") - .addArgument(e::getMessage) - .log(); - } - } - - protected synchronized void tryClose() throws Exception { - if (shouldClose.get() && subscribers.getSubscriberCount() < 1) { - doClose(); - } - } - - protected synchronized void doClose() throws Exception { - if (!isClosed.get()) { - // alert any subscribers we are closing: - subscribers.forEach(BonsaiStorageSubscriber::onCloseStorage); - - // close all of the KeyValueStorages: - composedWorldStateStorage.close(); - trieLogStorage.close(); - - // set storage closed - isClosed.set(true); - } - } - - public interface BonsaiStorageSubscriber { - default void onClearStorage() {} - - default void onClearFlatDatabaseStorage() {} - - default void onClearTrieLog() {} - - default void onCloseStorage() {} - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiWorldStateLayerStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiWorldStateLayerStorage.java deleted file mode 100644 index aa96354789a..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiWorldStateLayerStorage.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * - */ -package org.hyperledger.besu.ethereum.bonsai.storage; - -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.BonsaiStorageSubscriber; -import org.hyperledger.besu.ethereum.worldstate.FlatDbMode; -import org.hyperledger.besu.metrics.ObservableMetricsSystem; -import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; -import org.hyperledger.besu.plugin.services.storage.SnappedKeyValueStorage; -import org.hyperledger.besu.services.kvstore.LayeredKeyValueStorage; - -public class BonsaiWorldStateLayerStorage extends BonsaiSnapshotWorldStateKeyValueStorage - implements BonsaiStorageSubscriber { - - public BonsaiWorldStateLayerStorage(final BonsaiWorldStateKeyValueStorage parent) { - this( - new LayeredKeyValueStorage(parent.composedWorldStateStorage), - parent.trieLogStorage, - parent, - parent.metricsSystem); - } - - public BonsaiWorldStateLayerStorage( - final SnappedKeyValueStorage composedWorldStateStorage, - final KeyValueStorage trieLogStorage, - final BonsaiWorldStateKeyValueStorage parent, - final ObservableMetricsSystem metricsSystem) { - super(parent, composedWorldStateStorage, trieLogStorage, metricsSystem); - } - - @Override - public FlatDbMode getFlatDbMode() { - return parentWorldStateStorage.getFlatDbMode(); - } - - @Override - public BonsaiWorldStateLayerStorage clone() { - return new BonsaiWorldStateLayerStorage( - ((LayeredKeyValueStorage) composedWorldStateStorage).clone(), - trieLogStorage, - parentWorldStateStorage, - metricsSystem); - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/FlatDbStrategy.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/FlatDbStrategy.java deleted file mode 100644 index c561dcd8609..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/FlatDbStrategy.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * - */ -package org.hyperledger.besu.ethereum.bonsai.storage.flat; - -import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE; -import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_STORAGE; -import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.CODE_STORAGE; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.datatypes.StorageSlotKey; -import org.hyperledger.besu.ethereum.trie.NodeLoader; -import org.hyperledger.besu.metrics.BesuMetricCategory; -import org.hyperledger.besu.plugin.services.MetricsSystem; -import org.hyperledger.besu.plugin.services.metrics.Counter; -import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; -import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; - -import java.util.Map; -import java.util.Optional; -import java.util.TreeMap; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import kotlin.Pair; -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.rlp.RLP; - -/** - * This class represents a FlatDbReaderStrategy, which is responsible for reading and writing data - * from flat databases. It implements various methods for storing and retrieving account data, code - * data, and storage data from the corresponding KeyValueStorage. - */ -public abstract class FlatDbStrategy { - - protected final MetricsSystem metricsSystem; - protected final Counter getAccountCounter; - protected final Counter getAccountFoundInFlatDatabaseCounter; - - protected final Counter getStorageValueCounter; - protected final Counter getStorageValueFlatDatabaseCounter; - - public FlatDbStrategy(final MetricsSystem metricsSystem) { - this.metricsSystem = metricsSystem; - - getAccountCounter = - metricsSystem.createCounter( - BesuMetricCategory.BLOCKCHAIN, - "get_account_total", - "Total number of calls to getAccount"); - - getAccountFoundInFlatDatabaseCounter = - metricsSystem.createCounter( - BesuMetricCategory.BLOCKCHAIN, - "get_account_flat_database", - "Number of accounts found in the flat database"); - - getStorageValueCounter = - metricsSystem.createCounter( - BesuMetricCategory.BLOCKCHAIN, - "get_storagevalue_total", - "Total number of calls to getStorageValueBySlotHash"); - - getStorageValueFlatDatabaseCounter = - metricsSystem.createCounter( - BesuMetricCategory.BLOCKCHAIN, - "get_storagevalue_flat_database", - "Number of storage slots found in the flat database"); - } - - /* - * Retrieves the account data for the given account hash, using the world state root hash supplier and node loader. - */ - public abstract Optional getFlatAccount( - Supplier> worldStateRootHashSupplier, - NodeLoader nodeLoader, - Hash accountHash, - SegmentedKeyValueStorage storage); - - /* - * Retrieves the storage value for the given account hash and storage slot key, using the world state root hash supplier, storage root supplier, and node loader. - */ - - public abstract Optional getFlatStorageValueByStorageSlotKey( - Supplier> worldStateRootHashSupplier, - Supplier> storageRootSupplier, - NodeLoader nodeLoader, - Hash accountHash, - StorageSlotKey storageSlotKey, - SegmentedKeyValueStorage storageStorage); - - /* - * Retrieves the code data for the given code hash and account hash. - */ - public Optional getFlatCode( - final Bytes32 codeHash, final Hash accountHash, final SegmentedKeyValueStorage storage) { - if (codeHash.equals(Hash.EMPTY)) { - return Optional.of(Bytes.EMPTY); - } else { - return storage - .get(CODE_STORAGE, accountHash.toArrayUnsafe()) - .map(Bytes::wrap) - .filter(b -> Hash.hash(b).equals(codeHash)); - } - } - - /* - * Puts the account data for the given account hash, using the world state root hash supplier and node loader. - */ - public void putFlatAccount( - final SegmentedKeyValueStorageTransaction transaction, - final Hash accountHash, - final Bytes accountValue) { - transaction.put(ACCOUNT_INFO_STATE, accountHash.toArrayUnsafe(), accountValue.toArrayUnsafe()); - } - - public void removeFlatAccount( - final SegmentedKeyValueStorageTransaction transaction, final Hash accountHash) { - transaction.remove(ACCOUNT_INFO_STATE, accountHash.toArrayUnsafe()); - } - - /* - * Puts the storage value for the given account hash and storage slot key, using the world state root hash supplier, storage root supplier, and node loader. - */ - public void putFlatAccountStorageValueByStorageSlotHash( - final SegmentedKeyValueStorageTransaction transaction, - final Hash accountHash, - final Hash slotHash, - final Bytes storage) { - transaction.put( - ACCOUNT_STORAGE_STORAGE, - Bytes.concatenate(accountHash, slotHash).toArrayUnsafe(), - storage.toArrayUnsafe()); - } - - /* - * Removes the storage value for the given account hash and storage slot key, using the world state root hash supplier, storage root supplier, and node loader. - */ - public void removeFlatAccountStorageValueByStorageSlotHash( - final SegmentedKeyValueStorageTransaction transaction, - final Hash accountHash, - final Hash slotHash) { - transaction.remove( - ACCOUNT_STORAGE_STORAGE, Bytes.concatenate(accountHash, slotHash).toArrayUnsafe()); - } - - /* - * Removes code for the given account hash. - */ - public void removeFlatCode( - final SegmentedKeyValueStorageTransaction transaction, final Hash accountHash) { - transaction.remove(CODE_STORAGE, accountHash.toArrayUnsafe()); - } - - /* - * Puts the code data for the given code hash and account hash. - */ - public void putFlatCode( - final SegmentedKeyValueStorageTransaction transaction, - final Hash accountHash, - final Bytes32 codeHash, - final Bytes code) { - transaction.put(CODE_STORAGE, accountHash.toArrayUnsafe(), code.toArrayUnsafe()); - } - - public void clearAll(final SegmentedKeyValueStorage storage) { - storage.clear(ACCOUNT_INFO_STATE); - storage.clear(ACCOUNT_STORAGE_STORAGE); - storage.clear(CODE_STORAGE); - } - - public void resetOnResync(final SegmentedKeyValueStorage storage) { - storage.clear(ACCOUNT_INFO_STATE); - storage.clear(ACCOUNT_STORAGE_STORAGE); - } - - public Map streamAccountFlatDatabase( - final SegmentedKeyValueStorage storage, - final Bytes startKeyHash, - final Bytes32 endKeyHash, - final long max) { - final Stream> pairStream = - storage - .streamFromKey( - ACCOUNT_INFO_STATE, startKeyHash.toArrayUnsafe(), endKeyHash.toArrayUnsafe()) - .limit(max) - .map(pair -> new Pair<>(Bytes32.wrap(pair.getKey()), Bytes.wrap(pair.getValue()))); - - final TreeMap collected = - pairStream.collect( - Collectors.toMap(Pair::getFirst, Pair::getSecond, (v1, v2) -> v1, TreeMap::new)); - pairStream.close(); - return collected; - } - - public Map streamStorageFlatDatabase( - final SegmentedKeyValueStorage storage, - final Hash accountHash, - final Bytes startKeyHash, - final Bytes32 endKeyHash, - final long max) { - final Stream> pairStream = - storage - .streamFromKey( - ACCOUNT_STORAGE_STORAGE, - Bytes.concatenate(accountHash, startKeyHash).toArrayUnsafe(), - Bytes.concatenate(accountHash, endKeyHash).toArrayUnsafe()) - .limit(max) - .map( - pair -> - new Pair<>( - Bytes32.wrap(Bytes.wrap(pair.getKey()).slice(Hash.SIZE)), - RLP.encodeValue(Bytes.wrap(pair.getValue()).trimLeadingZeros()))); - - final TreeMap collected = - pairStream.collect( - Collectors.toMap(Pair::getFirst, Pair::getSecond, (v1, v2) -> v1, TreeMap::new)); - pairStream.close(); - return collected; - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManager.java deleted file mode 100644 index 3a874de6b67..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManager.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * - */ -package org.hyperledger.besu.ethereum.bonsai.trielog; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; -import org.hyperledger.besu.ethereum.chain.Blockchain; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.plugin.BesuContext; -import org.hyperledger.besu.plugin.services.TrieLogService; -import org.hyperledger.besu.plugin.services.trielogs.TrieLog; -import org.hyperledger.besu.plugin.services.trielogs.TrieLogEvent; -import org.hyperledger.besu.plugin.services.trielogs.TrieLogFactory; -import org.hyperledger.besu.plugin.services.trielogs.TrieLogProvider; -import org.hyperledger.besu.util.Subscribers; - -import java.util.List; -import java.util.Optional; -import java.util.stream.LongStream; -import java.util.stream.Stream; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class TrieLogManager { - private static final Logger LOG = LoggerFactory.getLogger(TrieLogManager.class); - public static final long LOG_RANGE_LIMIT = 1000; // restrict trielog range queries to 1k logs - protected final Blockchain blockchain; - protected final BonsaiWorldStateKeyValueStorage rootWorldStateStorage; - - protected final long maxLayersToLoad; - protected final Subscribers trieLogObservers = Subscribers.create(); - - protected final TrieLogFactory trieLogFactory; - private final TrieLogPruner trieLogPruner; - - public TrieLogManager( - final Blockchain blockchain, - final BonsaiWorldStateKeyValueStorage worldStateStorage, - final long maxLayersToLoad, - final BesuContext pluginContext, - final TrieLogPruner trieLogPruner) { - this.blockchain = blockchain; - this.rootWorldStateStorage = worldStateStorage; - this.maxLayersToLoad = maxLayersToLoad; - this.trieLogFactory = setupTrieLogFactory(pluginContext); - this.trieLogPruner = trieLogPruner; - } - - public synchronized void saveTrieLog( - final BonsaiWorldStateUpdateAccumulator localUpdater, - final Hash forWorldStateRootHash, - final BlockHeader forBlockHeader, - final BonsaiWorldState forWorldState) { - // do not overwrite a trielog layer that already exists in the database. - // if it's only in memory we need to save it - // for example, in case of reorg we don't replace a trielog layer - if (rootWorldStateStorage.getTrieLog(forBlockHeader.getHash()).isEmpty()) { - final BonsaiWorldStateKeyValueStorage.BonsaiUpdater stateUpdater = - forWorldState.getWorldStateStorage().updater(); - boolean success = false; - try { - final TrieLog trieLog = prepareTrieLog(forBlockHeader, localUpdater); - persistTrieLog(forBlockHeader, forWorldStateRootHash, trieLog, stateUpdater); - - // notify trie log added observers, synchronously - trieLogObservers.forEach(o -> o.onTrieLogAdded(new TrieLogAddedEvent(trieLog))); - - success = true; - } finally { - if (success) { - stateUpdater.commit(); - trieLogPruner.addToPruneQueue(forBlockHeader.getNumber(), forBlockHeader.getBlockHash()); - trieLogPruner.pruneFromQueue(); - } else { - stateUpdater.rollback(); - } - } - } - } - - private TrieLog prepareTrieLog( - final BlockHeader blockHeader, final BonsaiWorldStateUpdateAccumulator localUpdater) { - LOG.atDebug() - .setMessage("Adding layered world state for {}") - .addArgument(blockHeader::toLogString) - .log(); - final TrieLog trieLog = trieLogFactory.create(localUpdater, blockHeader); - trieLog.freeze(); - return trieLog; - } - - private void persistTrieLog( - final BlockHeader blockHeader, - final Hash worldStateRootHash, - final TrieLog trieLog, - final BonsaiWorldStateKeyValueStorage.BonsaiUpdater stateUpdater) { - LOG.atDebug() - .setMessage("Persisting trie log for block hash {} and world state root {}") - .addArgument(blockHeader::toLogString) - .addArgument(worldStateRootHash::toHexString) - .log(); - - stateUpdater - .getTrieLogStorageTransaction() - .put(blockHeader.getHash().toArrayUnsafe(), trieLogFactory.serialize(trieLog)); - } - - public long getMaxLayersToLoad() { - return maxLayersToLoad; - } - - public Optional getTrieLogLayer(final Hash blockHash) { - return rootWorldStateStorage.getTrieLog(blockHash).map(trieLogFactory::deserialize); - } - - public synchronized long subscribe(final TrieLogEvent.TrieLogObserver sub) { - return trieLogObservers.subscribe(sub); - } - - public synchronized void unsubscribe(final long id) { - trieLogObservers.unsubscribe(id); - } - - private TrieLogFactory setupTrieLogFactory(final BesuContext pluginContext) { - // if we have a TrieLogService from pluginContext, use it. - var trieLogServicez = - Optional.ofNullable(pluginContext) - .flatMap(context -> context.getService(TrieLogService.class)); - - if (trieLogServicez.isPresent()) { - var trieLogService = trieLogServicez.get(); - // push the TrieLogProvider into the TrieLogService - trieLogService.configureTrieLogProvider(getTrieLogProvider()); - - // configure plugin observers: - trieLogService.getObservers().forEach(trieLogObservers::subscribe); - - // return the TrieLogFactory implementation from the TrieLogService - return trieLogService.getTrieLogFactory(); - } else { - // Otherwise default to TrieLogFactoryImpl - return new TrieLogFactoryImpl(); - } - } - - private TrieLogProvider getTrieLogProvider() { - return new TrieLogProvider() { - @Override - public Optional getTrieLogLayer(final Hash blockHash) { - return TrieLogManager.this.getTrieLogLayer(blockHash); - } - - @Override - public Optional getTrieLogLayer(final long blockNumber) { - return TrieLogManager.this - .blockchain - .getBlockHeader(blockNumber) - .map(BlockHeader::getHash) - .flatMap(TrieLogManager.this::getTrieLogLayer); - } - - @Override - public List getTrieLogsByRange( - final long fromBlockNumber, final long toBlockNumber) { - return rangeAsStream(fromBlockNumber, toBlockNumber) - .map(blockchain::getBlockHeader) - .map( - headerOpt -> - headerOpt.flatMap( - header -> - TrieLogManager.this - .getTrieLogLayer(header.getBlockHash()) - .map( - layer -> - new TrieLogRangeTuple( - header.getBlockHash(), header.getNumber(), layer)))) - .filter(Optional::isPresent) - .map(Optional::get) - .toList(); - } - - Stream rangeAsStream(final long fromBlockNumber, final long toBlockNumber) { - if (Math.abs(toBlockNumber - fromBlockNumber) > LOG_RANGE_LIMIT) { - throw new IllegalArgumentException("Requested Range too large"); - } - long left = Math.min(fromBlockNumber, toBlockNumber); - long right = Math.max(fromBlockNumber, toBlockNumber); - return LongStream.range(left, right).boxed(); - } - }; - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPruner.java deleted file mode 100644 index 6ba88170742..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPruner.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright contributors to Hyperledger Besu. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.besu.ethereum.bonsai.trielog; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.chain.Blockchain; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; - -import java.util.Comparator; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicLong; -import java.util.stream.Stream; - -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Multimap; -import com.google.common.collect.TreeMultimap; -import org.apache.tuweni.bytes.Bytes32; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class TrieLogPruner { - - private static final Logger LOG = LoggerFactory.getLogger(TrieLogPruner.class); - - private final int pruningLimit; - private final int loadingLimit; - private final BonsaiWorldStateKeyValueStorage rootWorldStateStorage; - private final Blockchain blockchain; - private final long numBlocksToRetain; - private final boolean requireFinalizedBlock; - - private final Multimap trieLogBlocksAndForksByDescendingBlockNumber = - TreeMultimap.create(Comparator.reverseOrder(), Comparator.naturalOrder()); - - public TrieLogPruner( - final BonsaiWorldStateKeyValueStorage rootWorldStateStorage, - final Blockchain blockchain, - final long numBlocksToRetain, - final int pruningLimit, - final boolean requireFinalizedBlock) { - this.rootWorldStateStorage = rootWorldStateStorage; - this.blockchain = blockchain; - this.numBlocksToRetain = numBlocksToRetain; - this.pruningLimit = pruningLimit; - this.loadingLimit = pruningLimit; // same as pruningLimit for now - this.requireFinalizedBlock = requireFinalizedBlock; - } - - public void initialize() { - preloadQueue(); - } - - private void preloadQueue() { - LOG.atInfo() - .setMessage("Loading first {} trie logs from database...") - .addArgument(loadingLimit) - .log(); - try (final Stream trieLogKeys = rootWorldStateStorage.streamTrieLogKeys(loadingLimit)) { - final AtomicLong count = new AtomicLong(); - trieLogKeys.forEach( - blockHashAsBytes -> { - final Hash blockHash = Hash.wrap(Bytes32.wrap(blockHashAsBytes)); - final Optional header = blockchain.getBlockHeader(blockHash); - if (header.isPresent()) { - trieLogBlocksAndForksByDescendingBlockNumber.put(header.get().getNumber(), blockHash); - count.getAndIncrement(); - } else { - // prune orphaned blocks (sometimes created during block production) - rootWorldStateStorage.pruneTrieLog(blockHash); - } - }); - LOG.atInfo().log("Loaded {} trie logs from database", count); - pruneFromQueue(); - } catch (Exception e) { - LOG.error("Error loading trie logs from database, nothing pruned", e); - } - } - - void addToPruneQueue(final long blockNumber, final Hash blockHash) { - LOG.atTrace() - .setMessage("adding trie log to queue for later pruning blockNumber {}; blockHash {}") - .addArgument(blockNumber) - .addArgument(blockHash) - .log(); - trieLogBlocksAndForksByDescendingBlockNumber.put(blockNumber, blockHash); - } - - int pruneFromQueue() { - final long retainAboveThisBlock = blockchain.getChainHeadBlockNumber() - numBlocksToRetain; - final Optional finalized = blockchain.getFinalized(); - if (requireFinalizedBlock && finalized.isEmpty()) { - LOG.debug("No finalized block present, skipping pruning"); - return 0; - } - - final long retainAboveThisBlockOrFinalized = - finalized - .flatMap(blockchain::getBlockHeader) - .map(ProcessableBlockHeader::getNumber) - .map(finalizedBlock -> Math.min(finalizedBlock, retainAboveThisBlock)) - .orElse(retainAboveThisBlock); - - LOG.atTrace() - .setMessage( - "min((chainHeadNumber: {} - numBlocksToRetain: {}) = {}, finalized: {})) = retainAboveThisBlockOrFinalized: {}") - .addArgument(blockchain::getChainHeadBlockNumber) - .addArgument(numBlocksToRetain) - .addArgument(retainAboveThisBlock) - .addArgument( - () -> - finalized - .flatMap(blockchain::getBlockHeader) - .map(ProcessableBlockHeader::getNumber) - .orElse(null)) - .addArgument(retainAboveThisBlockOrFinalized) - .log(); - - final var pruneWindowEntries = - trieLogBlocksAndForksByDescendingBlockNumber.asMap().entrySet().stream() - .dropWhile((e) -> e.getKey() > retainAboveThisBlockOrFinalized) - .limit(pruningLimit); - - final Multimap wasPruned = ArrayListMultimap.create(); - - pruneWindowEntries.forEach( - (e) -> { - for (Hash blockHash : e.getValue()) { - if (rootWorldStateStorage.pruneTrieLog(blockHash)) { - wasPruned.put(e.getKey(), blockHash); - } - } - }); - - wasPruned.keySet().forEach(trieLogBlocksAndForksByDescendingBlockNumber::removeAll); - - LOG.atTrace() - .setMessage("pruned {} trie logs for blocks {}") - .addArgument(wasPruned::size) - .addArgument(wasPruned) - .log(); - LOG.atDebug() - .setMessage("pruned {} trie logs from {} blocks") - .addArgument(wasPruned::size) - .addArgument(() -> wasPruned.keySet().size()) - .log(); - - return wasPruned.size(); - } - - public static TrieLogPruner noOpTrieLogPruner() { - return new NoOpTrieLogPruner(null, null, 0, 0); - } - - public static class NoOpTrieLogPruner extends TrieLogPruner { - private NoOpTrieLogPruner( - final BonsaiWorldStateKeyValueStorage rootWorldStateStorage, - final Blockchain blockchain, - final long numBlocksToRetain, - final int pruningLimit) { - super(rootWorldStateStorage, blockchain, numBlocksToRetain, pruningLimit, true); - } - - @Override - public void initialize() { - // no-op - } - - @Override - void addToPruneQueue(final long blockNumber, final Hash blockHash) { - // no-op - } - - @Override - int pruneFromQueue() { - // no-op - return -1; - } - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldState.java deleted file mode 100644 index 7c7f7fd5516..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldState.java +++ /dev/null @@ -1,646 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * - */ - -package org.hyperledger.besu.ethereum.bonsai.worldview; - -import static org.hyperledger.besu.ethereum.bonsai.BonsaiAccount.fromRLP; -import static org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.WORLD_BLOCK_HASH_KEY; -import static org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.WORLD_ROOT_HASH_KEY; -import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.datatypes.StorageSlotKey; -import org.hyperledger.besu.ethereum.bonsai.BonsaiAccount; -import org.hyperledger.besu.ethereum.bonsai.BonsaiValue; -import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateProvider; -import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; -import org.hyperledger.besu.ethereum.bonsai.cache.CachedWorldStorageManager; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiSnapshotWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.BonsaiStorageSubscriber; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateLayerStorage; -import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogManager; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator.StorageConsumingMap; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.MutableWorldState; -import org.hyperledger.besu.ethereum.trie.MerkleTrie; -import org.hyperledger.besu.ethereum.trie.MerkleTrieException; -import org.hyperledger.besu.ethereum.trie.NodeLoader; -import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; -import org.hyperledger.besu.evm.account.Account; -import org.hyperledger.besu.evm.internal.EvmConfiguration; -import org.hyperledger.besu.evm.worldstate.WorldUpdater; -import org.hyperledger.besu.plugin.services.exception.StorageException; -import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; -import org.hyperledger.besu.plugin.services.storage.SegmentIdentifier; -import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; - -import java.util.Map; -import java.util.Optional; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Stream; -import javax.annotation.Nonnull; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt256; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class BonsaiWorldState - implements MutableWorldState, BonsaiWorldView, BonsaiStorageSubscriber { - - private static final Logger LOG = LoggerFactory.getLogger(BonsaiWorldState.class); - - protected BonsaiWorldStateKeyValueStorage worldStateStorage; - - protected final CachedMerkleTrieLoader cachedMerkleTrieLoader; - protected final CachedWorldStorageManager cachedWorldStorageManager; - protected final TrieLogManager trieLogManager; - private BonsaiWorldStateUpdateAccumulator accumulator; - - protected Hash worldStateRootHash; - Hash worldStateBlockHash; - private boolean isFrozen; - - public BonsaiWorldState( - final BonsaiWorldStateProvider archive, - final BonsaiWorldStateKeyValueStorage worldStateStorage, - final EvmConfiguration evmConfiguration) { - this( - worldStateStorage, - archive.getCachedMerkleTrieLoader(), - archive.getCachedWorldStorageManager(), - archive.getTrieLogManager(), - evmConfiguration); - } - - protected BonsaiWorldState( - final BonsaiWorldStateKeyValueStorage worldStateStorage, - final CachedMerkleTrieLoader cachedMerkleTrieLoader, - final CachedWorldStorageManager cachedWorldStorageManager, - final TrieLogManager trieLogManager, - final EvmConfiguration evmConfiguration) { - this.worldStateStorage = worldStateStorage; - this.worldStateRootHash = - Hash.wrap( - Bytes32.wrap(worldStateStorage.getWorldStateRootHash().orElse(Hash.EMPTY_TRIE_HASH))); - this.worldStateBlockHash = - Hash.wrap(Bytes32.wrap(worldStateStorage.getWorldStateBlockHash().orElse(Hash.ZERO))); - this.accumulator = - new BonsaiWorldStateUpdateAccumulator( - this, - (addr, value) -> - cachedMerkleTrieLoader.preLoadAccount( - getWorldStateStorage(), worldStateRootHash, addr), - (addr, value) -> - cachedMerkleTrieLoader.preLoadStorageSlot(getWorldStateStorage(), addr, value), - evmConfiguration); - this.cachedMerkleTrieLoader = cachedMerkleTrieLoader; - this.cachedWorldStorageManager = cachedWorldStorageManager; - this.trieLogManager = trieLogManager; - } - - /** - * Having a protected method to override the accumulator solves the chicken-egg problem of needing - * a worldstate reference (this) when construction the Accumulator. - * - * @param accumulator accumulator to use. - */ - protected void setAccumulator(final BonsaiWorldStateUpdateAccumulator accumulator) { - this.accumulator = accumulator; - } - - /** - * Returns the world state block hash of this world state - * - * @return the world state block hash. - */ - public Hash getWorldStateBlockHash() { - return worldStateBlockHash; - } - - /** - * Returns the world state root hash of this world state - * - * @return the world state root hash. - */ - public Hash getWorldStateRootHash() { - return worldStateRootHash; - } - - @Override - public boolean isPersisted() { - return isPersisted(worldStateStorage); - } - - private boolean isPersisted(final WorldStateStorage worldStateStorage) { - return !(worldStateStorage instanceof BonsaiSnapshotWorldStateKeyValueStorage); - } - - @Override - public Optional getCode(@Nonnull final Address address, final Hash codeHash) { - return worldStateStorage.getCode(codeHash, address.addressHash()); - } - - /** - * Reset the worldState to this block header - * - * @param blockHeader block to use - */ - public void resetWorldStateTo(final BlockHeader blockHeader) { - worldStateBlockHash = blockHeader.getBlockHash(); - worldStateRootHash = blockHeader.getStateRoot(); - } - - @Override - public BonsaiWorldStateKeyValueStorage getWorldStateStorage() { - return worldStateStorage; - } - - private Hash calculateRootHash( - final Optional maybeStateUpdater, - final BonsaiWorldStateUpdateAccumulator worldStateUpdater) { - - clearStorage(maybeStateUpdater, worldStateUpdater); - - // This must be done before updating the accounts so - // that we can get the storage state hash - Stream>>> - storageStream = worldStateUpdater.getStorageToUpdate().entrySet().stream(); - if (maybeStateUpdater.isEmpty()) { - storageStream = - storageStream - .parallel(); // if we are not updating the state updater we can use parallel stream - } - storageStream.forEach( - addressMapEntry -> - updateAccountStorageState(maybeStateUpdater, worldStateUpdater, addressMapEntry)); - - // Third update the code. This has the side effect of ensuring a code hash is calculated. - updateCode(maybeStateUpdater, worldStateUpdater); - - // next walk the account trie - final StoredMerklePatriciaTrie accountTrie = - createTrie( - (location, hash) -> - cachedMerkleTrieLoader.getAccountStateTrieNode(worldStateStorage, location, hash), - worldStateRootHash); - - // for manicured tries and composting, collect branches here (not implemented) - updateTheAccounts(maybeStateUpdater, worldStateUpdater, accountTrie); - - // TODO write to a cache and then generate a layer update from that and the - // DB tx updates. Right now it is just DB updates. - maybeStateUpdater.ifPresent( - bonsaiUpdater -> - accountTrie.commit( - (location, hash, value) -> - writeTrieNode( - TRIE_BRANCH_STORAGE, - bonsaiUpdater.getWorldStateTransaction(), - location, - value))); - final Bytes32 rootHash = accountTrie.getRootHash(); - return Hash.wrap(rootHash); - } - - private void updateTheAccounts( - final Optional maybeStateUpdater, - final BonsaiWorldStateUpdateAccumulator worldStateUpdater, - final StoredMerklePatriciaTrie accountTrie) { - for (final Map.Entry> accountUpdate : - worldStateUpdater.getAccountsToUpdate().entrySet()) { - final Bytes accountKey = accountUpdate.getKey(); - final BonsaiValue bonsaiValue = accountUpdate.getValue(); - final BonsaiAccount updatedAccount = bonsaiValue.getUpdated(); - try { - if (updatedAccount == null) { - final Hash addressHash = hashAndSavePreImage(accountKey); - accountTrie.remove(addressHash); - maybeStateUpdater.ifPresent( - bonsaiUpdater -> bonsaiUpdater.removeAccountInfoState(addressHash)); - } else { - final Hash addressHash = updatedAccount.getAddressHash(); - final Bytes accountValue = updatedAccount.serializeAccount(); - maybeStateUpdater.ifPresent( - bonsaiUpdater -> - bonsaiUpdater.putAccountInfoState(hashAndSavePreImage(accountKey), accountValue)); - accountTrie.put(addressHash, accountValue); - } - } catch (MerkleTrieException e) { - // need to throw to trigger the heal - throw new MerkleTrieException( - e.getMessage(), Optional.of(Address.wrap(accountKey)), e.getHash(), e.getLocation()); - } - } - } - - private void updateCode( - final Optional maybeStateUpdater, - final BonsaiWorldStateUpdateAccumulator worldStateUpdater) { - maybeStateUpdater.ifPresent( - bonsaiUpdater -> { - for (final Map.Entry> codeUpdate : - worldStateUpdater.getCodeToUpdate().entrySet()) { - final Bytes updatedCode = codeUpdate.getValue().getUpdated(); - final Hash accountHash = codeUpdate.getKey().addressHash(); - if (updatedCode == null || updatedCode.isEmpty()) { - bonsaiUpdater.removeCode(accountHash); - } else { - bonsaiUpdater.putCode(accountHash, null, updatedCode); - } - } - }); - } - - private void updateAccountStorageState( - final Optional maybeStateUpdater, - final BonsaiWorldStateUpdateAccumulator worldStateUpdater, - final Map.Entry>> - storageAccountUpdate) { - final Address updatedAddress = storageAccountUpdate.getKey(); - final Hash updatedAddressHash = updatedAddress.addressHash(); - if (worldStateUpdater.getAccountsToUpdate().containsKey(updatedAddress)) { - final BonsaiValue accountValue = - worldStateUpdater.getAccountsToUpdate().get(updatedAddress); - final BonsaiAccount accountOriginal = accountValue.getPrior(); - final Hash storageRoot = - (accountOriginal == null) ? Hash.EMPTY_TRIE_HASH : accountOriginal.getStorageRoot(); - final StoredMerklePatriciaTrie storageTrie = - createTrie( - (location, key) -> - cachedMerkleTrieLoader.getAccountStorageTrieNode( - worldStateStorage, updatedAddressHash, location, key), - storageRoot); - - // for manicured tries and composting, collect branches here (not implemented) - for (final Map.Entry> storageUpdate : - storageAccountUpdate.getValue().entrySet()) { - final Hash slotHash = storageUpdate.getKey().getSlotHash(); - final UInt256 updatedStorage = storageUpdate.getValue().getUpdated(); - try { - if (updatedStorage == null || updatedStorage.equals(UInt256.ZERO)) { - maybeStateUpdater.ifPresent( - bonsaiUpdater -> - bonsaiUpdater.removeStorageValueBySlotHash(updatedAddressHash, slotHash)); - storageTrie.remove(slotHash); - } else { - maybeStateUpdater.ifPresent( - bonsaiUpdater -> - bonsaiUpdater.putStorageValueBySlotHash( - updatedAddressHash, slotHash, updatedStorage)); - storageTrie.put(slotHash, BonsaiWorldView.encodeTrieValue(updatedStorage)); - } - } catch (MerkleTrieException e) { - // need to throw to trigger the heal - throw new MerkleTrieException( - e.getMessage(), - Optional.of(Address.wrap(updatedAddress)), - e.getHash(), - e.getLocation()); - } - } - - final BonsaiAccount accountUpdated = accountValue.getUpdated(); - if (accountUpdated != null) { - maybeStateUpdater.ifPresent( - bonsaiUpdater -> - storageTrie.commit( - (location, key, value) -> - writeStorageTrieNode( - bonsaiUpdater, updatedAddressHash, location, key, value))); - final Hash newStorageRoot = Hash.wrap(storageTrie.getRootHash()); - accountUpdated.setStorageRoot(newStorageRoot); - } - } - // for manicured tries and composting, trim and compost here - } - - private void clearStorage( - final Optional maybeStateUpdater, - final BonsaiWorldStateUpdateAccumulator worldStateUpdater) { - - maybeStateUpdater.ifPresent( - bonsaiUpdater -> { - for (final Address address : worldStateUpdater.getStorageToClear()) { - // because we are clearing persisted values we need the account root as persisted - final BonsaiAccount oldAccount = - worldStateStorage - .getAccount(address.addressHash()) - .map(bytes -> fromRLP(BonsaiWorldState.this, address, bytes, true)) - .orElse(null); - if (oldAccount == null) { - // This is when an account is both created and deleted within the scope of the same - // block. A not-uncommon DeFi bot pattern. - continue; - } - final Hash addressHash = address.addressHash(); - final MerkleTrie storageTrie = - createTrie( - (location, key) -> getStorageTrieNode(addressHash, location, key), - oldAccount.getStorageRoot()); - try { - Map entriesToDelete = storageTrie.entriesFrom(Bytes32.ZERO, 256); - while (!entriesToDelete.isEmpty()) { - entriesToDelete - .keySet() - .forEach( - k -> - bonsaiUpdater.removeStorageValueBySlotHash( - address.addressHash(), Hash.wrap(k))); - entriesToDelete.keySet().forEach(storageTrie::remove); - if (entriesToDelete.size() == 256) { - entriesToDelete = storageTrie.entriesFrom(Bytes32.ZERO, 256); - } else { - break; - } - } - } catch (MerkleTrieException e) { - // need to throw to trigger the heal - throw new MerkleTrieException( - e.getMessage(), Optional.of(Address.wrap(address)), e.getHash(), e.getLocation()); - } - } - }); - } - - @Override - public void persist(final BlockHeader blockHeader) { - final Optional maybeBlockHeader = Optional.ofNullable(blockHeader); - LOG.atDebug() - .setMessage("Persist world state for block {}") - .addArgument(maybeBlockHeader) - .log(); - - final BonsaiWorldStateUpdateAccumulator localCopy = accumulator.copy(); - - boolean success = false; - - final BonsaiWorldStateKeyValueStorage.BonsaiUpdater stateUpdater = worldStateStorage.updater(); - Runnable saveTrieLog = () -> {}; - - try { - final Hash newWorldStateRootHash = - calculateRootHash(isFrozen ? Optional.empty() : Optional.of(stateUpdater), accumulator); - // if we are persisted with a block header, and the prior state is the parent - // then persist the TrieLog for that transition. - // If specified but not a direct descendant simply store the new block hash. - if (blockHeader != null) { - verifyWorldStateRoot(newWorldStateRootHash, blockHeader); - saveTrieLog = - () -> { - trieLogManager.saveTrieLog(localCopy, newWorldStateRootHash, blockHeader, this); - // not save a frozen state in the cache - if (!isFrozen) { - cachedWorldStorageManager.addCachedLayer(blockHeader, newWorldStateRootHash, this); - } - }; - - stateUpdater - .getWorldStateTransaction() - .put(TRIE_BRANCH_STORAGE, WORLD_BLOCK_HASH_KEY, blockHeader.getHash().toArrayUnsafe()); - worldStateBlockHash = blockHeader.getHash(); - } else { - stateUpdater.getWorldStateTransaction().remove(TRIE_BRANCH_STORAGE, WORLD_BLOCK_HASH_KEY); - worldStateBlockHash = null; - } - - stateUpdater - .getWorldStateTransaction() - .put(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY, newWorldStateRootHash.toArrayUnsafe()); - worldStateRootHash = newWorldStateRootHash; - success = true; - } finally { - if (success) { - stateUpdater.commit(); - accumulator.reset(); - saveTrieLog.run(); - } else { - stateUpdater.rollback(); - accumulator.reset(); - } - } - } - - protected void verifyWorldStateRoot(final Hash calculatedStateRoot, final BlockHeader header) { - if (!calculatedStateRoot.equals(header.getStateRoot())) { - throw new RuntimeException( - "World State Root does not match expected value, header " - + header.getStateRoot().toHexString() - + " calculated " - + calculatedStateRoot.toHexString()); - } - } - - @Override - public WorldUpdater updater() { - return accumulator; - } - - @Override - public Hash rootHash() { - if (isFrozen && accumulator.isAccumulatorStateChanged()) { - worldStateRootHash = calculateRootHash(Optional.empty(), accumulator.copy()); - accumulator.resetAccumulatorStateChanged(); - } - return Hash.wrap(worldStateRootHash); - } - - static final KeyValueStorageTransaction noOpTx = - new KeyValueStorageTransaction() { - - @Override - public void put(final byte[] key, final byte[] value) { - // no-op - } - - @Override - public void remove(final byte[] key) { - // no-op - } - - @Override - public void commit() throws StorageException { - // no-op - } - - @Override - public void rollback() { - // no-op - } - }; - - static final SegmentedKeyValueStorageTransaction noOpSegmentedTx = - new SegmentedKeyValueStorageTransaction() { - - @Override - public void put( - final SegmentIdentifier segmentIdentifier, final byte[] key, final byte[] value) { - // no-op - } - - @Override - public void remove(final SegmentIdentifier segmentIdentifier, final byte[] key) { - // no-op - } - - @Override - public void commit() throws StorageException { - // no-op - } - - @Override - public void rollback() { - // no-op - } - }; - - @Override - public Hash frontierRootHash() { - return calculateRootHash( - Optional.of( - new BonsaiWorldStateKeyValueStorage.Updater( - noOpSegmentedTx, noOpTx, worldStateStorage.getFlatDbStrategy())), - accumulator.copy()); - } - - public Hash blockHash() { - return worldStateBlockHash; - } - - @Override - public Stream streamAccounts(final Bytes32 startKeyHash, final int limit) { - throw new RuntimeException("Bonsai Tries do not provide account streaming."); - } - - @Override - public Account get(final Address address) { - return worldStateStorage - .getAccount(address.addressHash()) - .map(bytes -> fromRLP(accumulator, address, bytes, true)) - .orElse(null); - } - - protected Optional getAccountStateTrieNode(final Bytes location, final Bytes32 nodeHash) { - return worldStateStorage.getAccountStateTrieNode(location, nodeHash); - } - - private void writeTrieNode( - final SegmentIdentifier segmentId, - final SegmentedKeyValueStorageTransaction tx, - final Bytes location, - final Bytes value) { - tx.put(segmentId, location.toArrayUnsafe(), value.toArrayUnsafe()); - } - - protected Optional getStorageTrieNode( - final Hash accountHash, final Bytes location, final Bytes32 nodeHash) { - return worldStateStorage.getAccountStorageTrieNode(accountHash, location, nodeHash); - } - - private void writeStorageTrieNode( - final WorldStateStorage.Updater stateUpdater, - final Hash accountHash, - final Bytes location, - final Bytes32 nodeHash, - final Bytes value) { - stateUpdater.putAccountStorageTrieNode(accountHash, location, nodeHash, value); - } - - @Override - public UInt256 getStorageValue(final Address address, final UInt256 storageKey) { - return getStorageValueByStorageSlotKey(address, new StorageSlotKey(storageKey)) - .orElse(UInt256.ZERO); - } - - @Override - public Optional getStorageValueByStorageSlotKey( - final Address address, final StorageSlotKey storageSlotKey) { - return worldStateStorage - .getStorageValueByStorageSlotKey(address.addressHash(), storageSlotKey) - .map(UInt256::fromBytes); - } - - public Optional getStorageValueByStorageSlotKey( - final Supplier> storageRootSupplier, - final Address address, - final StorageSlotKey storageSlotKey) { - return worldStateStorage - .getStorageValueByStorageSlotKey(storageRootSupplier, address.addressHash(), storageSlotKey) - .map(UInt256::fromBytes); - } - - @Override - public UInt256 getPriorStorageValue(final Address address, final UInt256 storageKey) { - return getStorageValue(address, storageKey); - } - - @Override - public Map getAllAccountStorage(final Address address, final Hash rootHash) { - final StoredMerklePatriciaTrie storageTrie = - createTrie( - (location, key) -> getStorageTrieNode(address.addressHash(), location, key), rootHash); - return storageTrie.entriesFrom(Bytes32.ZERO, Integer.MAX_VALUE); - } - - @Override - public MutableWorldState freeze() { - this.isFrozen = true; - this.worldStateStorage = new BonsaiWorldStateLayerStorage(worldStateStorage); - return this; - } - - private StoredMerklePatriciaTrie createTrie( - final NodeLoader nodeLoader, final Bytes32 rootHash) { - return new StoredMerklePatriciaTrie<>( - nodeLoader, rootHash, Function.identity(), Function.identity()); - } - - @Override - public void close() { - try { - if (!isPersisted()) { - this.worldStateStorage.close(); - if (isFrozen) { - closeFrozenStorage(); - } - } - } catch (Exception e) { - // no op - } - } - - private void closeFrozenStorage() { - try { - final BonsaiWorldStateLayerStorage worldStateLayerStorage = - (BonsaiWorldStateLayerStorage) worldStateStorage; - if (!isPersisted(worldStateLayerStorage.getParentWorldStateStorage())) { - worldStateLayerStorage.getParentWorldStateStorage().close(); - } - } catch (Exception e) { - // no op - } - } - - protected Hash hashAndSavePreImage(final Bytes value) { - // by default do not save has preImages - return Hash.hash(value); - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldStateUpdateAccumulator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldStateUpdateAccumulator.java deleted file mode 100644 index f66fcd0f56f..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldStateUpdateAccumulator.java +++ /dev/null @@ -1,846 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * - */ - -package org.hyperledger.besu.ethereum.bonsai.worldview; - -import org.hyperledger.besu.datatypes.AccountValue; -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.datatypes.StorageSlotKey; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.ethereum.bonsai.BonsaiAccount; -import org.hyperledger.besu.ethereum.bonsai.BonsaiValue; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.rlp.RLP; -import org.hyperledger.besu.ethereum.trie.MerkleTrieException; -import org.hyperledger.besu.evm.account.Account; -import org.hyperledger.besu.evm.account.MutableAccount; -import org.hyperledger.besu.evm.internal.EvmConfiguration; -import org.hyperledger.besu.evm.worldstate.AbstractWorldUpdater; -import org.hyperledger.besu.evm.worldstate.UpdateTrackingAccount; -import org.hyperledger.besu.plugin.services.trielogs.TrieLog; -import org.hyperledger.besu.plugin.services.trielogs.TrieLogAccumulator; - -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.TreeSet; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.function.Function; - -import com.google.common.collect.ForwardingMap; -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt256; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class BonsaiWorldStateUpdateAccumulator - extends AbstractWorldUpdater - implements BonsaiWorldView, TrieLogAccumulator { - private static final Logger LOG = - LoggerFactory.getLogger(BonsaiWorldStateUpdateAccumulator.class); - private final Consumer> accountPreloader; - private final Consumer storagePreloader; - - private final AccountConsumingMap> accountsToUpdate; - private final Map> codeToUpdate = new ConcurrentHashMap<>(); - private final Set
storageToClear = Collections.synchronizedSet(new HashSet<>()); - private final EvmConfiguration evmConfiguration; - - // storage sub mapped by _hashed_ key. This is because in self_destruct calls we need to - // enumerate the old storage and delete it. Those are trie stored by hashed key by spec and the - // alternative was to keep a giant pre-image cache of the entire trie. - private final Map>> - storageToUpdate = new ConcurrentHashMap<>(); - - private boolean isAccumulatorStateChanged; - - public BonsaiWorldStateUpdateAccumulator( - final BonsaiWorldView world, - final Consumer> accountPreloader, - final Consumer storagePreloader, - final EvmConfiguration evmConfiguration) { - super(world, evmConfiguration); - this.accountsToUpdate = new AccountConsumingMap<>(new ConcurrentHashMap<>(), accountPreloader); - this.accountPreloader = accountPreloader; - this.storagePreloader = storagePreloader; - this.isAccumulatorStateChanged = false; - this.evmConfiguration = evmConfiguration; - } - - public BonsaiWorldStateUpdateAccumulator copy() { - final BonsaiWorldStateUpdateAccumulator copy = - new BonsaiWorldStateUpdateAccumulator( - wrappedWorldView(), accountPreloader, storagePreloader, evmConfiguration); - copy.cloneFromUpdater(this); - return copy; - } - - void cloneFromUpdater(final BonsaiWorldStateUpdateAccumulator source) { - accountsToUpdate.putAll(source.getAccountsToUpdate()); - codeToUpdate.putAll(source.codeToUpdate); - storageToClear.addAll(source.storageToClear); - storageToUpdate.putAll(source.storageToUpdate); - updatedAccounts.putAll(source.updatedAccounts); - deletedAccounts.addAll(source.deletedAccounts); - this.isAccumulatorStateChanged = true; - } - - @Override - public Account get(final Address address) { - return super.get(address); - } - - @Override - protected UpdateTrackingAccount track( - final UpdateTrackingAccount account) { - return super.track(account); - } - - @Override - public MutableAccount getAccount(final Address address) { - return super.getAccount(address); - } - - @Override - public MutableAccount createAccount(final Address address, final long nonce, final Wei balance) { - BonsaiValue bonsaiValue = accountsToUpdate.get(address); - - if (bonsaiValue == null) { - bonsaiValue = new BonsaiValue<>(null, null); - accountsToUpdate.put(address, bonsaiValue); - } else if (bonsaiValue.getUpdated() != null) { - if (bonsaiValue.getUpdated().isEmpty()) { - return track(new UpdateTrackingAccount<>(bonsaiValue.getUpdated())); - } else { - throw new IllegalStateException("Cannot create an account when one already exists"); - } - } - - final BonsaiAccount newAccount = - new BonsaiAccount( - this, - address, - hashAndSavePreImage(address), - nonce, - balance, - Hash.EMPTY_TRIE_HASH, - Hash.EMPTY, - true); - bonsaiValue.setUpdated(newAccount); - return track(new UpdateTrackingAccount<>(newAccount)); - } - - @Override - public Map> getAccountsToUpdate() { - return accountsToUpdate; - } - - @Override - public Map> getCodeToUpdate() { - return codeToUpdate; - } - - public Set
getStorageToClear() { - return storageToClear; - } - - @Override - public Map>> - getStorageToUpdate() { - return storageToUpdate; - } - - @Override - protected BonsaiAccount getForMutation(final Address address) { - return loadAccount(address, BonsaiValue::getUpdated); - } - - protected BonsaiAccount loadAccount( - final Address address, - final Function, BonsaiAccount> bonsaiAccountFunction) { - try { - final BonsaiValue bonsaiValue = accountsToUpdate.get(address); - if (bonsaiValue == null) { - final Account account; - if (wrappedWorldView() - instanceof BonsaiWorldStateUpdateAccumulator bonsaiWorldStateUpdateAccumulator) { - account = bonsaiWorldStateUpdateAccumulator.loadAccount(address, bonsaiAccountFunction); - } else { - account = wrappedWorldView().get(address); - } - if (account instanceof BonsaiAccount bonsaiAccount) { - BonsaiAccount mutableAccount = new BonsaiAccount(bonsaiAccount, this, true); - accountsToUpdate.put(address, new BonsaiValue<>(bonsaiAccount, mutableAccount)); - return mutableAccount; - } else { - // add the empty read in accountsToUpdate - accountsToUpdate.put(address, new BonsaiValue<>(null, null)); - return null; - } - } else { - return bonsaiAccountFunction.apply(bonsaiValue); - } - } catch (MerkleTrieException e) { - // need to throw to trigger the heal - throw new MerkleTrieException( - e.getMessage(), Optional.of(address), e.getHash(), e.getLocation()); - } - } - - @Override - public Collection getTouchedAccounts() { - return getUpdatedAccounts(); - } - - @Override - public Collection
getDeletedAccountAddresses() { - return getDeletedAccounts(); - } - - @Override - public void revert() { - super.reset(); - } - - @Override - public void commit() { - this.isAccumulatorStateChanged = true; - for (final Address deletedAddress : getDeletedAccounts()) { - final BonsaiValue accountValue = - accountsToUpdate.computeIfAbsent( - deletedAddress, - __ -> loadAccountFromParent(deletedAddress, new BonsaiValue<>(null, null, true))); - storageToClear.add(deletedAddress); - final BonsaiValue codeValue = codeToUpdate.get(deletedAddress); - if (codeValue != null) { - codeValue.setUpdated(null).setCleared(); - } else { - wrappedWorldView() - .getCode( - deletedAddress, - Optional.ofNullable(accountValue) - .map(BonsaiValue::getPrior) - .map(BonsaiAccount::getCodeHash) - .orElse(Hash.EMPTY)) - .ifPresent( - deletedCode -> - codeToUpdate.put(deletedAddress, new BonsaiValue<>(deletedCode, null, true))); - } - - // mark all updated storage as to be cleared - final Map> deletedStorageUpdates = - storageToUpdate.computeIfAbsent( - deletedAddress, - k -> - new StorageConsumingMap<>( - deletedAddress, new ConcurrentHashMap<>(), storagePreloader)); - final Iterator>> iter = - deletedStorageUpdates.entrySet().iterator(); - while (iter.hasNext()) { - final Map.Entry> updateEntry = iter.next(); - final BonsaiValue updatedSlot = updateEntry.getValue(); - if (updatedSlot.getPrior() == null || updatedSlot.getPrior().isZero()) { - iter.remove(); - } else { - updatedSlot.setUpdated(null).setCleared(); - } - } - - final BonsaiAccount originalValue = accountValue.getPrior(); - if (originalValue != null) { - // Enumerate and delete addresses not updated - wrappedWorldView() - .getAllAccountStorage(deletedAddress, originalValue.getStorageRoot()) - .forEach( - (keyHash, entryValue) -> { - final StorageSlotKey storageSlotKey = - new StorageSlotKey(Hash.wrap(keyHash), Optional.empty()); - if (!deletedStorageUpdates.containsKey(storageSlotKey)) { - final UInt256 value = UInt256.fromBytes(RLP.decodeOne(entryValue)); - deletedStorageUpdates.put(storageSlotKey, new BonsaiValue<>(value, null, true)); - } - }); - } - if (deletedStorageUpdates.isEmpty()) { - storageToUpdate.remove(deletedAddress); - } - accountValue.setUpdated(null); - } - - getUpdatedAccounts().parallelStream() - .forEach( - tracked -> { - final Address updatedAddress = tracked.getAddress(); - final BonsaiAccount updatedAccount; - final BonsaiValue updatedAccountValue = - accountsToUpdate.get(updatedAddress); - - final Map> pendingStorageUpdates = - storageToUpdate.computeIfAbsent( - updatedAddress, - k -> - new StorageConsumingMap<>( - updatedAddress, new ConcurrentHashMap<>(), storagePreloader)); - - if (tracked.getStorageWasCleared()) { - storageToClear.add(updatedAddress); - pendingStorageUpdates.clear(); - } - - if (tracked.getWrappedAccount() == null) { - updatedAccount = new BonsaiAccount(this, tracked); - tracked.setWrappedAccount(updatedAccount); - if (updatedAccountValue == null) { - accountsToUpdate.put(updatedAddress, new BonsaiValue<>(null, updatedAccount)); - codeToUpdate.put( - updatedAddress, new BonsaiValue<>(null, updatedAccount.getCode())); - } else { - updatedAccountValue.setUpdated(updatedAccount); - } - } else { - updatedAccount = tracked.getWrappedAccount(); - updatedAccount.setBalance(tracked.getBalance()); - updatedAccount.setNonce(tracked.getNonce()); - if (tracked.codeWasUpdated()) { - updatedAccount.setCode(tracked.getCode()); - } - if (tracked.getStorageWasCleared()) { - updatedAccount.clearStorage(); - wrappedWorldView() - .getAllAccountStorage(updatedAddress, updatedAccount.getStorageRoot()) - .forEach( - (keyHash, entryValue) -> { - final StorageSlotKey storageSlotKey = - new StorageSlotKey(Hash.wrap(keyHash), Optional.empty()); - final UInt256 value = UInt256.fromBytes(RLP.decodeOne(entryValue)); - pendingStorageUpdates.put( - storageSlotKey, new BonsaiValue<>(value, null, true)); - }); - updatedAccount.setStorageRoot(Hash.EMPTY_TRIE_HASH); - } - tracked.getUpdatedStorage().forEach(updatedAccount::setStorageValue); - } - - if (tracked.codeWasUpdated()) { - final BonsaiValue pendingCode = - codeToUpdate.computeIfAbsent( - updatedAddress, - addr -> - new BonsaiValue<>( - wrappedWorldView() - .getCode( - addr, - Optional.ofNullable(updatedAccountValue) - .map(BonsaiValue::getPrior) - .map(BonsaiAccount::getCodeHash) - .orElse(Hash.EMPTY)) - .orElse(null), - null)); - pendingCode.setUpdated(updatedAccount.getCode()); - } - - // This is especially to avoid unnecessary computation for withdrawals and - // self-destruct beneficiaries - if (updatedAccount.getUpdatedStorage().isEmpty()) { - return; - } - - final TreeSet> entries = - new TreeSet<>(Map.Entry.comparingByKey()); - entries.addAll(updatedAccount.getUpdatedStorage().entrySet()); - - // parallel stream here may cause database corruption - entries.forEach( - storageUpdate -> { - final UInt256 keyUInt = storageUpdate.getKey(); - final Hash slotHash = hashAndSavePreImage(keyUInt); - final StorageSlotKey slotKey = - new StorageSlotKey(slotHash, Optional.of(keyUInt)); - final UInt256 value = storageUpdate.getValue(); - final BonsaiValue pendingValue = pendingStorageUpdates.get(slotKey); - if (pendingValue == null) { - pendingStorageUpdates.put( - slotKey, - new BonsaiValue<>( - updatedAccount.getOriginalStorageValue(keyUInt), value)); - } else { - pendingValue.setUpdated(value); - } - }); - - updatedAccount.getUpdatedStorage().clear(); - - if (pendingStorageUpdates.isEmpty()) { - storageToUpdate.remove(updatedAddress); - } - - if (tracked.getStorageWasCleared()) { - tracked.setStorageWasCleared(false); // storage already cleared for this transaction - } - }); - } - - @Override - public Optional getCode(final Address address, final Hash codeHash) { - final BonsaiValue localCode = codeToUpdate.get(address); - if (localCode == null) { - final Optional code = wrappedWorldView().getCode(address, codeHash); - if (code.isEmpty() && !codeHash.equals(Hash.EMPTY)) { - throw new MerkleTrieException( - "invalid account code", Optional.of(address), codeHash, Bytes.EMPTY); - } - return code; - } else { - return Optional.ofNullable(localCode.getUpdated()); - } - } - - @Override - public UInt256 getStorageValue(final Address address, final UInt256 slotKey) { - StorageSlotKey storageSlotKey = - new StorageSlotKey(hashAndSavePreImage(slotKey), Optional.of(slotKey)); - return getStorageValueByStorageSlotKey(address, storageSlotKey).orElse(UInt256.ZERO); - } - - @Override - public Optional getStorageValueByStorageSlotKey( - final Address address, final StorageSlotKey storageSlotKey) { - final Map> localAccountStorage = - storageToUpdate.get(address); - if (localAccountStorage != null) { - final BonsaiValue value = localAccountStorage.get(storageSlotKey); - if (value != null) { - return Optional.ofNullable(value.getUpdated()); - } - } - try { - final Optional valueUInt = - (wrappedWorldView() instanceof BonsaiWorldState bonsaiWorldState) - ? bonsaiWorldState.getStorageValueByStorageSlotKey( - () -> - Optional.ofNullable(loadAccount(address, BonsaiValue::getPrior)) - .map(BonsaiAccount::getStorageRoot), - address, - storageSlotKey) - : wrappedWorldView().getStorageValueByStorageSlotKey(address, storageSlotKey); - storageToUpdate - .computeIfAbsent( - address, - key -> - new StorageConsumingMap<>(address, new ConcurrentHashMap<>(), storagePreloader)) - .put(storageSlotKey, new BonsaiValue<>(valueUInt.orElse(null), valueUInt.orElse(null))); - - return valueUInt; - } catch (MerkleTrieException e) { - // need to throw to trigger the heal - throw new MerkleTrieException( - e.getMessage(), Optional.of(address), e.getHash(), e.getLocation()); - } - } - - @Override - public UInt256 getPriorStorageValue(final Address address, final UInt256 storageKey) { - // TODO maybe log the read into the trie layer? - StorageSlotKey storageSlotKey = - new StorageSlotKey(hashAndSavePreImage(storageKey), Optional.of(storageKey)); - final Map> localAccountStorage = - storageToUpdate.get(address); - if (localAccountStorage != null) { - final BonsaiValue value = localAccountStorage.get(storageSlotKey); - if (value != null) { - if (value.isCleared()) { - return UInt256.ZERO; - } - final UInt256 updated = value.getUpdated(); - if (updated != null) { - return updated; - } - final UInt256 original = value.getPrior(); - if (original != null) { - return original; - } - } - } - if (storageToClear.contains(address)) { - return UInt256.ZERO; - } - return getStorageValue(address, storageKey); - } - - @Override - public Map getAllAccountStorage(final Address address, final Hash rootHash) { - final Map results = wrappedWorldView().getAllAccountStorage(address, rootHash); - final StorageConsumingMap> bonsaiValueStorage = - storageToUpdate.get(address); - if (bonsaiValueStorage != null) { - // hash the key to match the implied storage interface of hashed slotKey - bonsaiValueStorage.forEach( - (key, value) -> results.put(key.getSlotHash(), value.getUpdated())); - } - return results; - } - - @Override - public boolean isPersisted() { - return true; - } - - @Override - public BonsaiWorldStateKeyValueStorage getWorldStateStorage() { - return wrappedWorldView().getWorldStateStorage(); - } - - public void rollForward(final TrieLog layer) { - layer - .getAccountChanges() - .forEach( - (address, change) -> - rollAccountChange(address, change.getPrior(), change.getUpdated())); - layer - .getCodeChanges() - .forEach( - (address, change) -> rollCodeChange(address, change.getPrior(), change.getUpdated())); - layer - .getStorageChanges() - .forEach( - (address, storage) -> - storage.forEach( - (storageSlotKey, value) -> - rollStorageChange( - address, storageSlotKey, value.getPrior(), value.getUpdated()))); - } - - public void rollBack(final TrieLog layer) { - layer - .getAccountChanges() - .forEach( - (address, change) -> - rollAccountChange(address, change.getUpdated(), change.getPrior())); - layer - .getCodeChanges() - .forEach( - (address, change) -> rollCodeChange(address, change.getUpdated(), change.getPrior())); - layer - .getStorageChanges() - .forEach( - (address, storage) -> - storage.forEach( - (storageSlotKey, value) -> - rollStorageChange( - address, storageSlotKey, value.getUpdated(), value.getPrior()))); - } - - private void rollAccountChange( - final Address address, - final AccountValue expectedValue, - final AccountValue replacementValue) { - if (Objects.equals(expectedValue, replacementValue)) { - // non-change, a cached read. - return; - } - BonsaiValue accountValue = accountsToUpdate.get(address); - if (accountValue == null) { - accountValue = loadAccountFromParent(address, accountValue); - } - if (accountValue == null) { - if (expectedValue == null && replacementValue != null) { - accountsToUpdate.put( - address, - new BonsaiValue<>(null, new BonsaiAccount(this, address, replacementValue, true))); - } else { - throw new IllegalStateException( - String.format( - "Expected to update account, but the account does not exist. Address=%s", address)); - } - } else { - if (expectedValue == null) { - if (accountValue.getUpdated() != null) { - throw new IllegalStateException( - String.format( - "Expected to create account, but the account exists. Address=%s", address)); - } - } else { - BonsaiAccount.assertCloseEnoughForDiffing( - accountValue.getUpdated(), - expectedValue, - "Address=" + address + " Prior Value in Rolling Change"); - } - if (replacementValue == null) { - if (accountValue.getPrior() == null) { - // TODO: should we remove from the parent accumulated change also? only if it is a - // private copy - accountsToUpdate.remove(address); - } else { - accountValue.setUpdated(null); - } - } else { - accountValue.setUpdated( - new BonsaiAccount(wrappedWorldView(), address, replacementValue, true)); - } - } - } - - private BonsaiValue loadAccountFromParent( - final Address address, final BonsaiValue defaultValue) { - try { - final Account parentAccount = wrappedWorldView().get(address); - if (parentAccount instanceof BonsaiAccount account) { - final BonsaiValue loadedAccountValue = - new BonsaiValue<>(new BonsaiAccount(account), account); - accountsToUpdate.put(address, loadedAccountValue); - return loadedAccountValue; - } else { - return defaultValue; - } - } catch (MerkleTrieException e) { - // need to throw to trigger the heal - throw new MerkleTrieException( - e.getMessage(), Optional.of(address), e.getHash(), e.getLocation()); - } - } - - private void rollCodeChange( - final Address address, final Bytes expectedCode, final Bytes replacementCode) { - if (Objects.equals(expectedCode, replacementCode)) { - // non-change, a cached read. - return; - } - BonsaiValue codeValue = codeToUpdate.get(address); - if (codeValue == null) { - final Bytes storedCode = - wrappedWorldView() - .getCode( - address, Optional.ofNullable(expectedCode).map(Hash::hash).orElse(Hash.EMPTY)) - .orElse(Bytes.EMPTY); - if (!storedCode.isEmpty()) { - codeValue = new BonsaiValue<>(storedCode, storedCode); - codeToUpdate.put(address, codeValue); - } - } - - if (codeValue == null) { - if ((expectedCode == null || expectedCode.isEmpty()) && replacementCode != null) { - codeToUpdate.put(address, new BonsaiValue<>(null, replacementCode)); - } else { - throw new IllegalStateException( - String.format( - "Expected to update code, but the code does not exist. Address=%s", address)); - } - } else { - final Bytes existingCode = codeValue.getUpdated(); - if ((expectedCode == null || expectedCode.isEmpty()) - && existingCode != null - && !existingCode.isEmpty()) { - LOG.warn("At Address={}, expected to create code, but code exists. Overwriting.", address); - } else if (!Objects.equals(expectedCode, existingCode)) { - throw new IllegalStateException( - String.format( - "Old value of code does not match expected value. Address=%s ExpectedHash=%s ActualHash=%s", - address, - expectedCode == null ? "null" : Hash.hash(expectedCode), - Hash.hash(codeValue.getUpdated()))); - } - if (replacementCode == null && codeValue.getPrior() == null) { - codeToUpdate.remove(address); - } else { - codeValue.setUpdated(replacementCode); - } - } - } - - private Map> maybeCreateStorageMap( - final Map> storageMap, final Address address) { - if (storageMap == null) { - final StorageConsumingMap> newMap = - new StorageConsumingMap<>(address, new ConcurrentHashMap<>(), storagePreloader); - storageToUpdate.put(address, newMap); - return newMap; - } else { - return storageMap; - } - } - - private void rollStorageChange( - final Address address, - final StorageSlotKey storageSlotKey, - final UInt256 expectedValue, - final UInt256 replacementValue) { - if (Objects.equals(expectedValue, replacementValue)) { - // non-change, a cached read. - return; - } - if (replacementValue == null && expectedValue != null && expectedValue.isZero()) { - // corner case on deletes, non-change - return; - } - final Map> storageMap = storageToUpdate.get(address); - BonsaiValue slotValue = storageMap == null ? null : storageMap.get(storageSlotKey); - if (slotValue == null) { - final Optional storageValue = - wrappedWorldView().getStorageValueByStorageSlotKey(address, storageSlotKey); - if (storageValue.isPresent()) { - slotValue = new BonsaiValue<>(storageValue.get(), storageValue.get()); - storageToUpdate - .computeIfAbsent( - address, - k -> - new StorageConsumingMap<>(address, new ConcurrentHashMap<>(), storagePreloader)) - .put(storageSlotKey, slotValue); - } - } - if (slotValue == null) { - if ((expectedValue == null || expectedValue.isZero()) && replacementValue != null) { - maybeCreateStorageMap(storageMap, address) - .put(storageSlotKey, new BonsaiValue<>(null, replacementValue)); - } else { - throw new IllegalStateException( - String.format( - "Expected to update storage value, but the slot does not exist. Account=%s SlotKey=%s", - address, storageSlotKey)); - } - } else { - final UInt256 existingSlotValue = slotValue.getUpdated(); - if ((expectedValue == null || expectedValue.isZero()) - && existingSlotValue != null - && !existingSlotValue.isZero()) { - throw new IllegalStateException( - String.format( - "Expected to create slot, but the slot exists. Account=%s SlotKey=%s expectedValue=%s existingValue=%s", - address, storageSlotKey, expectedValue, existingSlotValue)); - } - if (!isSlotEquals(expectedValue, existingSlotValue)) { - throw new IllegalStateException( - String.format( - "Old value of slot does not match expected value. Account=%s SlotKey=%s Expected=%s Actual=%s", - address, - storageSlotKey, - expectedValue == null ? "null" : expectedValue.toShortHexString(), - existingSlotValue == null ? "null" : existingSlotValue.toShortHexString())); - } - if (replacementValue == null && slotValue.getPrior() == null) { - final Map> thisStorageUpdate = - maybeCreateStorageMap(storageMap, address); - thisStorageUpdate.remove(storageSlotKey); - if (thisStorageUpdate.isEmpty()) { - storageToUpdate.remove(address); - } - } else { - slotValue.setUpdated(replacementValue); - } - } - } - - private boolean isSlotEquals(final UInt256 expectedValue, final UInt256 existingSlotValue) { - final UInt256 sanitizedExpectedValue = (expectedValue == null) ? UInt256.ZERO : expectedValue; - final UInt256 sanitizedExistingSlotValue = - (existingSlotValue == null) ? UInt256.ZERO : existingSlotValue; - return Objects.equals(sanitizedExpectedValue, sanitizedExistingSlotValue); - } - - public boolean isAccumulatorStateChanged() { - return isAccumulatorStateChanged; - } - - public void resetAccumulatorStateChanged() { - isAccumulatorStateChanged = false; - } - - @Override - public void reset() { - storageToClear.clear(); - storageToUpdate.clear(); - codeToUpdate.clear(); - accountsToUpdate.clear(); - resetAccumulatorStateChanged(); - updatedAccounts.clear(); - deletedAccounts.clear(); - } - - public static class AccountConsumingMap extends ForwardingMap { - - private final ConcurrentMap accounts; - private final Consumer consumer; - - public AccountConsumingMap( - final ConcurrentMap accounts, final Consumer consumer) { - this.accounts = accounts; - this.consumer = consumer; - } - - @Override - public T put(@NotNull final Address address, @NotNull final T value) { - consumer.process(address, value); - return accounts.put(address, value); - } - - public Consumer getConsumer() { - return consumer; - } - - @Override - protected Map delegate() { - return accounts; - } - } - - public static class StorageConsumingMap extends ForwardingMap { - - private final Address address; - - private final ConcurrentMap storages; - private final Consumer consumer; - - public StorageConsumingMap( - final Address address, final ConcurrentMap storages, final Consumer consumer) { - this.address = address; - this.storages = storages; - this.consumer = consumer; - } - - @Override - public T put(@NotNull final K slotKey, @NotNull final T value) { - consumer.process(address, slotKey); - return storages.put(slotKey, value); - } - - public Consumer getConsumer() { - return consumer; - } - - @Override - protected Map delegate() { - return storages; - } - } - - public interface Consumer { - void process(final Address address, T value); - } - - protected Hash hashAndSavePreImage(final Bytes bytes) { - // by default do not save hash preImages - return Hash.hash(bytes); - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldView.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldView.java deleted file mode 100644 index 7a307ec3f40..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/worldview/BonsaiWorldView.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * - */ - -package org.hyperledger.besu.ethereum.bonsai.worldview; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.datatypes.StorageSlotKey; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; -import org.hyperledger.besu.evm.worldstate.WorldUpdater; -import org.hyperledger.besu.evm.worldstate.WorldView; - -import java.util.Map; -import java.util.Optional; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt256; - -public interface BonsaiWorldView extends WorldView { - - Optional getCode(Address address, final Hash codeHash); - - UInt256 getStorageValue(Address address, UInt256 key); - - Optional getStorageValueByStorageSlotKey(Address address, StorageSlotKey storageSlotKey); - - UInt256 getPriorStorageValue(Address address, UInt256 key); - - /** - * Retrieve all the storage values of an account. - * - * @param address the account to stream - * @param rootHash the root hash of the account storage trie - * @return A map that is a copy of the entries. The key is the hashed slot number, and the value - * is the Bytes representation of the storage value. - */ - Map getAllAccountStorage(final Address address, final Hash rootHash); - - static Bytes encodeTrieValue(final Bytes bytes) { - final BytesValueRLPOutput out = new BytesValueRLPOutput(); - out.writeBytes(bytes.trimLeadingZeros()); - return out.encoded(); - } - - boolean isPersisted(); - - BonsaiWorldStateKeyValueStorage getWorldStateStorage(); - - WorldUpdater updater(); -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockCause.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockCause.java new file mode 100644 index 00000000000..e110876afad --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockCause.java @@ -0,0 +1,61 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.chain; + +import static org.hyperledger.besu.plugin.data.BadBlockCause.BadBlockReason.DESCENDS_FROM_BAD_BLOCK; +import static org.hyperledger.besu.plugin.data.BadBlockCause.BadBlockReason.SPEC_VALIDATION_FAILURE; + +import org.hyperledger.besu.ethereum.core.Block; + +import com.google.common.base.MoreObjects; + +public class BadBlockCause implements org.hyperledger.besu.plugin.data.BadBlockCause { + + private final BadBlockReason reason; + private final String description; + + public static BadBlockCause fromBadAncestorBlock(final Block badAncestor) { + final String description = + String.format("Descends from bad block %s", badAncestor.toLogString()); + return new BadBlockCause(DESCENDS_FROM_BAD_BLOCK, description); + } + + public static BadBlockCause fromValidationFailure(final String failureMessage) { + return new BadBlockCause(SPEC_VALIDATION_FAILURE, failureMessage); + } + + private BadBlockCause(final BadBlockReason reason, final String description) { + this.reason = reason; + this.description = description; + } + + @Override + public BadBlockReason getReason() { + return reason; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("reason", reason) + .add("description", description) + .toString(); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockManager.java index 8d5d57a9975..211d28c3428 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockManager.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BadBlockManager.java @@ -17,16 +17,20 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.trie.MerkleTrieException; -import org.hyperledger.besu.plugin.services.exception.StorageException; +import org.hyperledger.besu.plugin.services.BesuEvents.BadBlockListener; +import org.hyperledger.besu.util.Subscribers; import java.util.Collection; import java.util.Optional; +import com.google.common.annotations.VisibleForTesting; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class BadBlockManager { + private static final Logger LOG = LoggerFactory.getLogger(BadBlockManager.class); public static final int MAX_BAD_BLOCKS_SIZE = 100; private final Cache badBlocks = @@ -35,19 +39,18 @@ public class BadBlockManager { CacheBuilder.newBuilder().maximumSize(MAX_BAD_BLOCKS_SIZE).concurrencyLevel(1).build(); private final Cache latestValidHashes = CacheBuilder.newBuilder().maximumSize(MAX_BAD_BLOCKS_SIZE).concurrencyLevel(1).build(); + private final Subscribers badBlockSubscribers = Subscribers.create(true); /** * Add a new invalid block. * * @param badBlock the invalid block - * @param cause optional exception causing the block to be considered invalid + * @param cause the cause detailing why the block is considered invalid */ - public void addBadBlock(final Block badBlock, final Optional cause) { - if (badBlock != null) { - if (cause.isEmpty() || !isInternalError(cause.get())) { - this.badBlocks.put(badBlock.getHash(), badBlock); - } - } + public void addBadBlock(final Block badBlock, final BadBlockCause cause) { + LOG.debug("Register bad block {} with cause: {}", badBlock.toLogString(), cause); + this.badBlocks.put(badBlock.getHash(), badBlock); + badBlockSubscribers.forEach(s -> s.onBadBlockAdded(badBlock.getHeader(), cause)); } public void reset() { @@ -65,6 +68,11 @@ public Collection getBadBlocks() { return badBlocks.asMap().values(); } + @VisibleForTesting + public Collection getBadHeaders() { + return badHeaders.asMap().values(); + } + /** * Return an invalid block based on the hash * @@ -75,12 +83,14 @@ public Optional getBadBlock(final Hash hash) { return Optional.ofNullable(badBlocks.getIfPresent(hash)); } - public void addBadHeader(final BlockHeader header) { + public void addBadHeader(final BlockHeader header, final BadBlockCause cause) { + LOG.debug("Register bad block header {} with cause: {}", header.toLogString(), cause); badHeaders.put(header.getHash(), header); + badBlockSubscribers.forEach(s -> s.onBadBlockAdded(header, cause)); } - public Optional getBadHash(final Hash blockHash) { - return Optional.ofNullable(badHeaders.getIfPresent(blockHash)); + public boolean isBadBlock(final Hash blockHash) { + return badBlocks.asMap().containsKey(blockHash) || badHeaders.asMap().containsKey(blockHash); } public void addLatestValidHash(final Hash blockHash, final Hash latestValidHash) { @@ -91,11 +101,11 @@ public Optional getLatestValidHash(final Hash blockHash) { return Optional.ofNullable(latestValidHashes.getIfPresent(blockHash)); } - private boolean isInternalError(final Throwable causedBy) { - // As new "internal only" types of exception are discovered, add them here. - if (causedBy instanceof StorageException || causedBy instanceof MerkleTrieException) { - return true; - } - return false; + public long subscribeToBadBlocks(final BadBlockListener listener) { + return badBlockSubscribers.subscribe(listener); + } + + public void unsubscribeFromBadBlocks(final long id) { + badBlockSubscribers.unsubscribe(id); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/Blockchain.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/Blockchain.java index 84e92ff019b..49b6fea33e1 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/Blockchain.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/Blockchain.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -167,6 +167,15 @@ default boolean contains(final Hash blockHash) { */ Optional getBlockBody(Hash blockHeaderHash); + /** + * Safe version of {@code getBlockBody} (it should take any locks necessary to ensure any block + * updates that might be taking place have been completed first) + * + * @param blockHeaderHash The hash of the block whose header we want to retrieve. + * @return The block body corresponding to this block hash. + */ + Optional getBlockBodySafe(Hash blockHeaderHash); + /** * Given a block's hash, returns the list of transaction receipts associated with this block's * transactions. Associated block is not necessarily on the canonical chain. diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BlockchainStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BlockchainStorage.java index c610a3e0d2b..2c0cbf1a5c2 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BlockchainStorage.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/BlockchainStorage.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java index 7807786948d..37025feca7e 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPruner.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.chain; @@ -77,7 +76,7 @@ public void onBlockAdded(final BlockAddedEvent event) { if (event.isNewCanonicalHead() && blocksToBePruned >= pruningFrequency) { long currentRetainedBlock = blockNumber - currentPruningMark + 1; while (currentRetainedBlock > blocksToRetain) { - LOG.debug("Pruning chain data with block height of " + currentPruningMark); + LOG.debug("Pruning chain data with block height of {}", currentPruningMark); pruneChainDataAtBlock(pruningTransaction, currentPruningMark); currentPruningMark++; currentRetainedBlock = blockNumber - currentPruningMark; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerStorage.java index 8b50ec83d52..52a37cdba3a 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerStorage.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerStorage.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.chain; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainPrunerConfiguration.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainPrunerConfiguration.java index 99ac9bc486d..67ad7f0a277 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainPrunerConfiguration.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/ChainPrunerConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,21 +11,25 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.chain; public class ChainPrunerConfiguration { public static final ChainPrunerConfiguration DEFAULT = - new ChainPrunerConfiguration(false, 7200, 256); + new ChainPrunerConfiguration(false, 7200, 7200, 256); private final boolean enabled; private final long blocksRetained; private final long blocksFrequency; + private final long blocksRetainedLimit; public ChainPrunerConfiguration( - final boolean enabled, final long blocksRetained, final long blocksFrequency) { + final boolean enabled, + final long blocksRetained, + final long blocksRetainedLimit, + final long blocksFrequency) { this.enabled = enabled; this.blocksRetained = blocksRetained; + this.blocksRetainedLimit = blocksRetainedLimit; this.blocksFrequency = blocksFrequency; } @@ -33,6 +37,10 @@ public long getChainPruningBlocksRetained() { return blocksRetained; } + public long getBlocksRetainedLimit() { + return blocksRetainedLimit; + } + public boolean getChainPruningEnabled() { return enabled; } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/DefaultBlockchain.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/DefaultBlockchain.java index a92be73bf7c..bce94a19153 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/DefaultBlockchain.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/DefaultBlockchain.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -31,8 +31,10 @@ import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.metrics.BesuMetricCategory; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.metrics.prometheus.PrometheusMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.metrics.Counter; import org.hyperledger.besu.util.InvalidConfigurationException; import org.hyperledger.besu.util.Subscribers; @@ -83,6 +85,9 @@ public class DefaultBlockchain implements MutableBlockchain { private final Optional>> transactionReceiptsCache; private final Optional> totalDifficultyCache; + private Counter gasUsedCounter = NoOpMetricsSystem.NO_OP_COUNTER; + private Counter numberOfTransactionsCounter = NoOpMetricsSystem.NO_OP_COUNTER; + private DefaultBlockchain( final Optional genesisBlock, final BlockchainStorage blockchainStorage, @@ -112,11 +117,72 @@ private DefaultBlockchain( chainHeadTransactionCount = chainHeadBody.getTransactions().size(); chainHeadOmmerCount = chainHeadBody.getOmmers().size(); + this.reorgLoggingThreshold = reorgLoggingThreshold; + this.blockChoiceRule = heaviestChainBlockChoiceRule; + this.numberOfBlocksToCache = numberOfBlocksToCache; + + if (numberOfBlocksToCache != 0) { + blockHeadersCache = + Optional.of( + CacheBuilder.newBuilder().recordStats().maximumSize(numberOfBlocksToCache).build()); + blockBodiesCache = + Optional.of( + CacheBuilder.newBuilder().recordStats().maximumSize(numberOfBlocksToCache).build()); + transactionReceiptsCache = + Optional.of( + CacheBuilder.newBuilder().recordStats().maximumSize(numberOfBlocksToCache).build()); + totalDifficultyCache = + Optional.of( + CacheBuilder.newBuilder().recordStats().maximumSize(numberOfBlocksToCache).build()); + CacheMetricsCollector cacheMetrics = new CacheMetricsCollector(); + cacheMetrics.addCache("blockHeaders", blockHeadersCache.get()); + cacheMetrics.addCache("blockBodies", blockBodiesCache.get()); + cacheMetrics.addCache("transactionReceipts", transactionReceiptsCache.get()); + cacheMetrics.addCache("totalDifficulty", totalDifficultyCache.get()); + if (metricsSystem instanceof PrometheusMetricsSystem prometheusMetricsSystem) + prometheusMetricsSystem.addCollector(BesuMetricCategory.BLOCKCHAIN, () -> cacheMetrics); + } else { + blockHeadersCache = Optional.empty(); + blockBodiesCache = Optional.empty(); + transactionReceiptsCache = Optional.empty(); + totalDifficultyCache = Optional.empty(); + } + + createCounters(metricsSystem); + createGauges(metricsSystem); + } + + private void createCounters(final MetricsSystem metricsSystem) { + gasUsedCounter = + metricsSystem.createCounter( + BesuMetricCategory.BLOCKCHAIN, "chain_head_gas_used_counter", "Counter for Gas used"); + + numberOfTransactionsCounter = + metricsSystem.createCounter( + BesuMetricCategory.BLOCKCHAIN, + "chain_head_transaction_count_counter", + "Counter for the number of transactions"); + } + + private void createGauges(final MetricsSystem metricsSystem) { metricsSystem.createLongGauge( BesuMetricCategory.ETHEREUM, "blockchain_height", "The current height of the canonical chain", this::getChainHeadBlockNumber); + + metricsSystem.createLongGauge( + BesuMetricCategory.ETHEREUM, + "blockchain_finalized_block", + "The current finalized block number", + this::getFinalizedBlockNumber); + + metricsSystem.createLongGauge( + BesuMetricCategory.ETHEREUM, + "blockchain_safe_block", + "The current safe block number", + this::getSafeBlockNumber); + metricsSystem.createGauge( BesuMetricCategory.BLOCKCHAIN, "difficulty_total", @@ -152,37 +218,6 @@ private DefaultBlockchain( "chain_head_ommer_count", "Number of ommers in the current chain head block", () -> chainHeadOmmerCount); - - this.reorgLoggingThreshold = reorgLoggingThreshold; - this.blockChoiceRule = heaviestChainBlockChoiceRule; - this.numberOfBlocksToCache = numberOfBlocksToCache; - - if (numberOfBlocksToCache != 0) { - blockHeadersCache = - Optional.of( - CacheBuilder.newBuilder().recordStats().maximumSize(numberOfBlocksToCache).build()); - blockBodiesCache = - Optional.of( - CacheBuilder.newBuilder().recordStats().maximumSize(numberOfBlocksToCache).build()); - transactionReceiptsCache = - Optional.of( - CacheBuilder.newBuilder().recordStats().maximumSize(numberOfBlocksToCache).build()); - totalDifficultyCache = - Optional.of( - CacheBuilder.newBuilder().recordStats().maximumSize(numberOfBlocksToCache).build()); - CacheMetricsCollector cacheMetrics = new CacheMetricsCollector(); - cacheMetrics.addCache("blockHeaders", blockHeadersCache.get()); - cacheMetrics.addCache("blockBodies", blockBodiesCache.get()); - cacheMetrics.addCache("transactionReceipts", transactionReceiptsCache.get()); - cacheMetrics.addCache("totalDifficulty", totalDifficultyCache.get()); - if (metricsSystem instanceof PrometheusMetricsSystem prometheusMetricsSystem) - prometheusMetricsSystem.addCollector(BesuMetricCategory.BLOCKCHAIN, () -> cacheMetrics); - } else { - blockHeadersCache = Optional.empty(); - blockBodiesCache = Optional.empty(); - transactionReceiptsCache = Optional.empty(); - totalDifficultyCache = Optional.empty(); - } } public static MutableBlockchain createMutable( @@ -284,7 +319,10 @@ public BlockHeader getChainHeadHeader() { @Override public Block getChainHeadBlock() { - return new Block(chainHeader, blockchainStorage.getBlockBody(chainHeader.getHash()).get()); + return new Block( + chainHeader, + getBlockBody(chainHeader.getHash()) + .orElseGet(() -> getBlockBodySafe(chainHeader.getHash()).get())); } @Override @@ -322,6 +360,11 @@ public Optional getBlockBody(final Hash blockHeaderHash) { .orElseGet(() -> blockchainStorage.getBlockBody(blockHeaderHash)); } + @Override + public synchronized Optional getBlockBodySafe(final Hash blockHeaderHash) { + return getBlockBody(blockHeaderHash); + } + @Override public Optional> getTxReceipts(final Hash blockHeaderHash) { return transactionReceiptsCache @@ -469,7 +512,8 @@ public synchronized void unsafeSetChainHead( updater.commit(); } - private Difficulty calculateTotalDifficulty(final BlockHeader blockHeader) { + @Override + public Difficulty calculateTotalDifficulty(final BlockHeader blockHeader) { if (blockHeader.getNumber() == BlockHeader.GENESIS_BLOCK_NUMBER) { return blockHeader.getDifficulty(); } @@ -523,6 +567,10 @@ private BlockAddedEvent handleNewHead( updater.setChainHead(newBlockHash); indexTransactionForBlock( updater, newBlockHash, blockWithReceipts.getBlock().getBody().getTransactions()); + gasUsedCounter.inc(blockWithReceipts.getHeader().getGasUsed()); + numberOfTransactionsCounter.inc( + blockWithReceipts.getBlock().getBody().getTransactions().size()); + return BlockAddedEvent.createForHeadAdvancement( blockWithReceipts.getBlock(), LogWithMetadata.generate( @@ -731,6 +779,14 @@ public void setSafeBlock(final Hash blockHash) { updater.commit(); } + private long getFinalizedBlockNumber() { + return this.getFinalized().flatMap(this::getBlockHeader).map(BlockHeader::getNumber).orElse(0L); + } + + private long getSafeBlockNumber() { + return this.getSafeBlock().flatMap(this::getBlockHeader).map(BlockHeader::getNumber).orElse(0L); + } + private void updateCacheForNewCanonicalHead(final Block block, final Difficulty uInt256) { chainHeader = block.getHeader(); totalDifficulty = uInt256; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java index a69e920dd20..00b20a0170f 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/GenesisState.java @@ -15,55 +15,49 @@ package org.hyperledger.besu.ethereum.chain; import static java.util.Collections.emptyList; +import static org.hyperledger.besu.ethereum.trie.common.GenesisWorldStateProvider.createGenesisWorldState; -import org.hyperledger.besu.config.GenesisAllocation; +import org.hyperledger.besu.config.GenesisAccount; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.BlobGas; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; -import org.hyperledger.besu.ethereum.core.Deposit; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.core.Request; import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions; -import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStatePreimageKeyValueStorage; -import org.hyperledger.besu.ethereum.worldstate.DefaultMutableWorldState; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.evm.account.MutableAccount; -import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.log.LogsBloomFilter; import org.hyperledger.besu.evm.worldstate.WorldUpdater; -import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; -import java.math.BigInteger; -import java.util.HashMap; +import java.net.URL; import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.Optional; import java.util.OptionalLong; import java.util.function.Function; import java.util.stream.Stream; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt256; public final class GenesisState { private final Block block; - private final List genesisAccounts; + private final GenesisConfigFile genesisConfigFile; - private GenesisState(final Block block, final List genesisAccounts) { + private GenesisState(final Block block, final GenesisConfigFile genesisConfigFile) { this.block = block; - this.genesisAccounts = genesisAccounts; + this.genesisConfigFile = genesisConfigFile; } /** @@ -78,7 +72,25 @@ public static GenesisState fromJson(final String json, final ProtocolSchedule pr } /** - * Construct a {@link GenesisState} from a JSON object. + * Construct a {@link GenesisState} from a URL + * + * @param dataStorageConfiguration A {@link DataStorageConfiguration} describing the storage + * configuration + * @param jsonSource A URL pointing to JSON genesis file + * @param protocolSchedule A protocol Schedule associated with + * @return A new {@link GenesisState}. + */ + @VisibleForTesting + static GenesisState fromJsonSource( + final DataStorageConfiguration dataStorageConfiguration, + final URL jsonSource, + final ProtocolSchedule protocolSchedule) { + return fromConfig( + dataStorageConfiguration, GenesisConfigFile.fromConfig(jsonSource), protocolSchedule); + } + + /** + * Construct a {@link GenesisState} from a genesis file object. * * @param config A {@link GenesisConfigFile} describing the genesis block. * @param protocolSchedule A protocol Schedule associated with @@ -86,21 +98,57 @@ public static GenesisState fromJson(final String json, final ProtocolSchedule pr */ public static GenesisState fromConfig( final GenesisConfigFile config, final ProtocolSchedule protocolSchedule) { - final List genesisAccounts = parseAllocations(config).toList(); + return fromConfig(DataStorageConfiguration.DEFAULT_CONFIG, config, protocolSchedule); + } + + /** + * Construct a {@link GenesisState} from a JSON object. + * + * @param dataStorageConfiguration A {@link DataStorageConfiguration} describing the storage + * configuration + * @param genesisConfigFile A {@link GenesisConfigFile} describing the genesis block. + * @param protocolSchedule A protocol Schedule associated with + * @return A new {@link GenesisState}. + */ + public static GenesisState fromConfig( + final DataStorageConfiguration dataStorageConfiguration, + final GenesisConfigFile genesisConfigFile, + final ProtocolSchedule protocolSchedule) { + final var genesisStateRoot = + calculateGenesisStateRoot(dataStorageConfiguration, genesisConfigFile); final Block block = new Block( - buildHeader(config, calculateGenesisStateHash(genesisAccounts), protocolSchedule), - buildBody(config)); - return new GenesisState(block, genesisAccounts); + buildHeader(genesisConfigFile, genesisStateRoot, protocolSchedule), + buildBody(genesisConfigFile)); + return new GenesisState(block, genesisConfigFile); + } + + /** + * Construct a {@link GenesisState} from a JSON object. + * + * @param genesisStateRoot The root of the genesis state. + * @param genesisConfigFile A {@link GenesisConfigFile} describing the genesis block. + * @param protocolSchedule A protocol Schedule associated with + * @return A new {@link GenesisState}. + */ + public static GenesisState fromStorage( + final Hash genesisStateRoot, + final GenesisConfigFile genesisConfigFile, + final ProtocolSchedule protocolSchedule) { + final Block block = + new Block( + buildHeader(genesisConfigFile, genesisStateRoot, protocolSchedule), + buildBody(genesisConfigFile)); + return new GenesisState(block, genesisConfigFile); } private static BlockBody buildBody(final GenesisConfigFile config) { final Optional> withdrawals = isShanghaiAtGenesis(config) ? Optional.of(emptyList()) : Optional.empty(); - final Optional> deposits = - isExperimentalEipsTimeAtGenesis(config) ? Optional.of(emptyList()) : Optional.empty(); + final Optional> requests = + isPragueAtGenesis(config) ? Optional.of(emptyList()) : Optional.empty(); - return new BlockBody(emptyList(), emptyList(), withdrawals, deposits); + return new BlockBody(emptyList(), emptyList(), withdrawals, requests); } public Block getBlock() { @@ -113,35 +161,35 @@ public Block getBlock() { * @param target WorldView to write genesis state to */ public void writeStateTo(final MutableWorldState target) { - writeAccountsTo(target, genesisAccounts, block.getHeader()); + writeAccountsTo(target, genesisConfigFile.streamAllocations(), block.getHeader()); } private static void writeAccountsTo( final MutableWorldState target, - final List genesisAccounts, + final Stream genesisAccounts, final BlockHeader rootHeader) { final WorldUpdater updater = target.updater(); genesisAccounts.forEach( genesisAccount -> { - final MutableAccount account = updater.getOrCreate(genesisAccount.address); - account.setNonce(genesisAccount.nonce); - account.setBalance(genesisAccount.balance); - account.setCode(genesisAccount.code); - genesisAccount.storage.forEach(account::setStorageValue); + final MutableAccount account = updater.createAccount(genesisAccount.address()); + account.setNonce(genesisAccount.nonce()); + account.setBalance(genesisAccount.balance()); + account.setCode(genesisAccount.code()); + genesisAccount.storage().forEach(account::setStorageValue); }); updater.commit(); target.persist(rootHeader); } - private static Hash calculateGenesisStateHash(final List genesisAccounts) { - final WorldStateKeyValueStorage stateStorage = - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); - final WorldStatePreimageKeyValueStorage preimageStorage = - new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage()); - final MutableWorldState worldState = - new DefaultMutableWorldState(stateStorage, preimageStorage, EvmConfiguration.DEFAULT); - writeAccountsTo(worldState, genesisAccounts, null); - return worldState.rootHash(); + private static Hash calculateGenesisStateRoot( + final DataStorageConfiguration dataStorageConfiguration, + final GenesisConfigFile genesisConfigFile) { + try (var worldState = createGenesisWorldState(dataStorageConfiguration)) { + writeAccountsTo(worldState, genesisConfigFile.streamAllocations(), null); + return worldState.rootHash(); + } catch (Exception e) { + throw new RuntimeException(e); + } } private static BlockHeader buildHeader( @@ -172,7 +220,7 @@ private static BlockHeader buildHeader( .excessBlobGas(isCancunAtGenesis(genesis) ? parseExcessBlobGas(genesis) : null) .parentBeaconBlockRoot( (isCancunAtGenesis(genesis) ? parseParentBeaconBlockRoot(genesis) : null)) - .depositsRoot(isExperimentalEipsTimeAtGenesis(genesis) ? Hash.EMPTY_TRIE_HASH : null) + .requestsRoot(isPragueAtGenesis(genesis) ? Hash.EMPTY_TRIE_HASH : null) .buildBlockHeader(); } @@ -214,10 +262,6 @@ private static Hash parseMixHash(final GenesisConfigFile genesis) { return withNiceErrorMessage("mixHash", genesis.getMixHash(), Hash::fromHexStringLenient); } - private static Stream parseAllocations(final GenesisConfigFile genesis) { - return genesis.streamAllocations().map(GenesisAccount::fromAllocation); - } - private static long parseNonce(final GenesisConfigFile genesis) { return withNiceErrorMessage("nonce", genesis.getNonce(), GenesisState::parseUnsignedLong); } @@ -252,7 +296,7 @@ private static boolean isShanghaiAtGenesis(final GenesisConfigFile genesis) { if (shanghaiTimestamp.isPresent()) { return genesis.getTimestamp() >= shanghaiTimestamp.getAsLong(); } - return false; + return isCancunAtGenesis(genesis); } private static boolean isCancunAtGenesis(final GenesisConfigFile genesis) { @@ -260,88 +304,51 @@ private static boolean isCancunAtGenesis(final GenesisConfigFile genesis) { if (cancunTimestamp.isPresent()) { return genesis.getTimestamp() >= cancunTimestamp.getAsLong(); } - return false; + return isPragueAtGenesis(genesis) || isCancunEOFAtGenesis(genesis); } - private static boolean isExperimentalEipsTimeAtGenesis(final GenesisConfigFile genesis) { - final OptionalLong experimentalEipsTime = genesis.getConfigOptions().getExperimentalEipsTime(); - if (experimentalEipsTime.isPresent()) { - return genesis.getTimestamp() >= experimentalEipsTime.getAsLong(); + private static boolean isCancunEOFAtGenesis(final GenesisConfigFile genesis) { + final OptionalLong cancunEOFTimestamp = genesis.getConfigOptions().getCancunEOFTime(); + if (cancunEOFTimestamp.isPresent()) { + return genesis.getTimestamp() >= cancunEOFTimestamp.getAsLong(); } - return false; + return isPragueEOFAtGenesis(genesis); } - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("block", block) - .add("genesisAccounts", genesisAccounts) - .toString(); - } - - private static final class GenesisAccount { - - final long nonce; - final Address address; - final Wei balance; - final Map storage; - final Bytes code; - - static GenesisAccount fromAllocation(final GenesisAllocation allocation) { - return new GenesisAccount( - allocation.getNonce(), - allocation.getAddress(), - allocation.getBalance(), - allocation.getStorage(), - allocation.getCode()); + private static boolean isPragueAtGenesis(final GenesisConfigFile genesis) { + final OptionalLong pragueTimestamp = genesis.getConfigOptions().getPragueTime(); + if (pragueTimestamp.isPresent()) { + return genesis.getTimestamp() >= pragueTimestamp.getAsLong(); } + return isPragueEOFAtGenesis(genesis); + } - private GenesisAccount( - final String hexNonce, - final String hexAddress, - final String balance, - final Map storage, - final String hexCode) { - this.nonce = withNiceErrorMessage("nonce", hexNonce, GenesisState::parseUnsignedLong); - this.address = withNiceErrorMessage("address", hexAddress, Address::fromHexString); - this.balance = withNiceErrorMessage("balance", balance, this::parseBalance); - this.code = hexCode != null ? Bytes.fromHexString(hexCode) : null; - this.storage = parseStorage(storage); + private static boolean isPragueEOFAtGenesis(final GenesisConfigFile genesis) { + final OptionalLong pragueEOFTimestamp = genesis.getConfigOptions().getPragueEOFTime(); + if (pragueEOFTimestamp.isPresent()) { + return genesis.getTimestamp() >= pragueEOFTimestamp.getAsLong(); } + return isFutureEipsTimeAtGenesis(genesis); + } - private Wei parseBalance(final String balance) { - final BigInteger val; - if (balance.startsWith("0x")) { - val = new BigInteger(1, Bytes.fromHexStringLenient(balance).toArrayUnsafe()); - } else { - val = new BigInteger(balance); - } - - return Wei.of(val); + private static boolean isFutureEipsTimeAtGenesis(final GenesisConfigFile genesis) { + final OptionalLong futureEipsTime = genesis.getConfigOptions().getFutureEipsTime(); + if (futureEipsTime.isPresent()) { + return genesis.getTimestamp() >= futureEipsTime.getAsLong(); } + return isExperimentalEipsTimeAtGenesis(genesis); + } - private Map parseStorage(final Map storage) { - final Map parsedStorage = new HashMap<>(); - storage.forEach( - (key1, value1) -> { - final UInt256 key = withNiceErrorMessage("storage key", key1, UInt256::fromHexString); - final UInt256 value = - withNiceErrorMessage("storage value", value1, UInt256::fromHexString); - parsedStorage.put(key, value); - }); - - return parsedStorage; + private static boolean isExperimentalEipsTimeAtGenesis(final GenesisConfigFile genesis) { + final OptionalLong experimentalEipsTime = genesis.getConfigOptions().getExperimentalEipsTime(); + if (experimentalEipsTime.isPresent()) { + return genesis.getTimestamp() >= experimentalEipsTime.getAsLong(); } + return false; + } - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("address", address) - .add("nonce", nonce) - .add("balance", balance) - .add("storage", storage) - .add("code", code) - .toString(); - } + @Override + public String toString() { + return MoreObjects.toStringHelper(this).add("block", block).toString(); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/MutableBlockchain.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/MutableBlockchain.java index 9ec7c8a9d9d..5213974be31 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/MutableBlockchain.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/MutableBlockchain.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -56,6 +56,8 @@ void unsafeImportBlock( void unsafeSetChainHead(final BlockHeader blockHeader, final Difficulty totalDifficulty); + Difficulty calculateTotalDifficulty(final BlockHeader blockHeader); + /** * Rolls back the canonical chainhead to the specified block number. * diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/VariablesStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/VariablesStorage.java index 43a4361a0e4..9d63e6b30a4 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/VariablesStorage.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/chain/VariablesStorage.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -29,7 +29,8 @@ enum Keys { FORK_HEADS("forkHeads"), FINALIZED_BLOCK_HASH("finalizedBlockHash"), SAFE_BLOCK_HASH("safeBlockHash"), - SEQ_NO_STORE("local-enr-seqno"); + SEQ_NO_STORE("local-enr-seqno"), + GENESIS_STATE_HASH("genesisStateHash"); private final String key; private final byte[] byteArray; @@ -65,6 +66,8 @@ public String toString() { Optional getLocalEnrSeqno(); + Optional getGenesisStateHash(); + Updater updater(); interface Updater { @@ -79,6 +82,8 @@ interface Updater { void setLocalEnrSeqno(Bytes nodeRecord); + void setGenesisStateHash(Hash genesisStateHash); + void removeAll(); void commit(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Block.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Block.java index 32313820cc8..3c90f77d320 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Block.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Block.java @@ -62,7 +62,7 @@ public void writeTo(final RLPOutput out) { out.writeList(body.getTransactions(), Transaction::writeTo); out.writeList(body.getOmmers(), BlockHeader::writeTo); body.getWithdrawals().ifPresent(withdrawals -> out.writeList(withdrawals, Withdrawal::writeTo)); - body.getDeposits().ifPresent(deposits -> out.writeList(deposits, Deposit::writeTo)); + body.getRequests().ifPresent(requests -> out.writeList(requests, Request::writeTo)); out.endList(); } @@ -74,11 +74,11 @@ public static Block readFrom(final RLPInput in, final BlockHeaderFunctions hashF final List ommers = in.readList(rlp -> BlockHeader.readFrom(rlp, hashFunction)); final Optional> withdrawals = in.isEndOfCurrentList() ? Optional.empty() : Optional.of(in.readList(Withdrawal::readFrom)); - final Optional> deposits = - in.isEndOfCurrentList() ? Optional.empty() : Optional.of(in.readList(Deposit::readFrom)); + final Optional> requests = + in.isEndOfCurrentList() ? Optional.empty() : Optional.of(in.readList(Request::readFrom)); in.leaveList(); - return new Block(header, new BlockBody(transactions, ommers, withdrawals, deposits)); + return new Block(header, new BlockBody(transactions, ommers, withdrawals, requests)); } @Override diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockBody.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockBody.java index 7defb8eab29..4b8ab926137 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockBody.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockBody.java @@ -26,6 +26,7 @@ public class BlockBody implements org.hyperledger.besu.plugin.data.BlockBody { private static final BlockBody EMPTY = new BlockBody(Collections.emptyList(), Collections.emptyList()); + /** * Adding a new field with a corresponding root hash in the block header will require a change in * {@link org.hyperledger.besu.ethereum.eth.manager.task.GetBodiesFromPeerTask.BodyIdentifier} @@ -36,24 +37,24 @@ public class BlockBody implements org.hyperledger.besu.plugin.data.BlockBody { private final List ommers; private final Optional> withdrawals; - private final Optional> deposits; + private final Optional> requests; public BlockBody(final List transactions, final List ommers) { this.transactions = transactions; this.ommers = ommers; this.withdrawals = Optional.empty(); - this.deposits = Optional.empty(); + this.requests = Optional.empty(); } public BlockBody( final List transactions, final List ommers, final Optional> withdrawals, - final Optional> deposits) { + final Optional> requests) { this.transactions = transactions; this.ommers = ommers; this.withdrawals = withdrawals; - this.deposits = deposits; + this.requests = requests; } public static BlockBody empty() { @@ -87,13 +88,13 @@ public Optional> getWithdrawals() { } /** - * Returns the deposits of the block. + * Returns the withdrawal requests of the block. * - * @return The optional list of deposits included in the block. + * @return The optional list of withdrawal requests included in the block. */ @Override - public Optional> getDeposits() { - return deposits; + public Optional> getRequests() { + return requests; } /** @@ -111,7 +112,7 @@ public void writeTo(final RLPOutput output) { output.writeList(getTransactions(), Transaction::writeTo); output.writeList(getOmmers(), BlockHeader::writeTo); withdrawals.ifPresent(withdrawals -> output.writeList(withdrawals, Withdrawal::writeTo)); - deposits.ifPresent(deposits -> output.writeList(deposits, Deposit::writeTo)); + requests.ifPresent(requests -> output.writeList(requests, Request::writeTo)); } public static BlockBody readWrappedBodyFrom( @@ -146,7 +147,7 @@ public static BlockBody readWrappedBodyFrom( /** * Read all fields from the block body expecting no list wrapping them. An example of a valid body - * would be: [txs],[ommers],[withdrawals],[deposits] this method is called directly when importing + * would be: [txs],[ommers],[withdrawals],[requests] this method is called directly when importing * a single block * * @param input The RLP-encoded input @@ -163,7 +164,7 @@ public static BlockBody readFrom( : Optional.of(input.readList(Withdrawal::readFrom)), input.isEndOfCurrentList() ? Optional.empty() - : Optional.of(input.readList(Deposit::readFrom))); + : Optional.of(input.readList(Request::readFrom))); } @Override @@ -174,19 +175,19 @@ public boolean equals(final Object o) { return Objects.equals(transactions, blockBody.transactions) && Objects.equals(ommers, blockBody.ommers) && Objects.equals(withdrawals, blockBody.withdrawals) - && Objects.equals(deposits, blockBody.deposits); + && Objects.equals(requests, blockBody.requests); } @Override public int hashCode() { - return Objects.hash(transactions, ommers, withdrawals, deposits); + return Objects.hash(transactions, ommers, withdrawals, requests); } public boolean isEmpty() { return transactions.isEmpty() && ommers.isEmpty() && withdrawals.isEmpty() - && deposits.isEmpty(); + && requests.isEmpty(); } @Override @@ -198,8 +199,8 @@ public String toString() { + ommers + ", withdrawals=" + withdrawals - + ", deposits=" - + deposits + + ", withdrawal_requests=" + + requests + '}'; } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeader.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeader.java index 7aa5770f3fc..42f99fafb69 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeader.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeader.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -64,7 +64,7 @@ public BlockHeader( final Long blobGasUsed, final BlobGas excessBlobGas, final Bytes32 parentBeaconBlockRoot, - final Hash depositsRoot, + final Hash requestsRoot, final BlockHeaderFunctions blockHeaderFunctions) { super( parentHash, @@ -86,7 +86,7 @@ public BlockHeader( blobGasUsed, excessBlobGas, parentBeaconBlockRoot, - depositsRoot); + requestsRoot); this.nonce = nonce; this.hash = Suppliers.memoize(() -> blockHeaderFunctions.hash(this)); this.parsedExtraData = Suppliers.memoize(() -> blockHeaderFunctions.parseExtraData(this)); @@ -102,11 +102,6 @@ public Hash getMixHash() { return Hash.wrap(mixHashOrPrevRandao); } - @Override - public Bytes32 getMixHashOrPrevRandao() { - return mixHashOrPrevRandao; - } - /** * Returns the block nonce. * @@ -163,22 +158,23 @@ public void writeTo(final RLPOutput out) { out.writeBytes(extraData); out.writeBytes(mixHashOrPrevRandao); out.writeLong(nonce); - if (baseFee != null) { + do { + if (baseFee == null) break; out.writeUInt256Scalar(baseFee); - } - if (withdrawalsRoot != null) { + + if (withdrawalsRoot == null) break; out.writeBytes(withdrawalsRoot); - } - if (excessBlobGas != null && blobGasUsed != null) { + + if (excessBlobGas == null || blobGasUsed == null) break; out.writeLongScalar(blobGasUsed); out.writeUInt64Scalar(excessBlobGas); - } - if (parentBeaconBlockRoot != null) { + + if (parentBeaconBlockRoot == null) break; out.writeBytes(parentBeaconBlockRoot); - } - if (depositsRoot != null) { - out.writeBytes(depositsRoot); - } + + if (requestsRoot == null) break; + out.writeBytes(requestsRoot); + } while (false); out.endList(); } @@ -207,10 +203,9 @@ public static BlockHeader readFrom( : null; final Long blobGasUsed = !input.isEndOfCurrentList() ? input.readLongScalar() : null; final BlobGas excessBlobGas = - !input.isEndOfCurrentList() ? BlobGas.of(input.readLongScalar()) : null; + !input.isEndOfCurrentList() ? BlobGas.of(input.readUInt64Scalar()) : null; final Bytes32 parentBeaconBlockRoot = !input.isEndOfCurrentList() ? input.readBytes32() : null; - final Hash depositHashRoot = - !input.isEndOfCurrentList() ? Hash.wrap(input.readBytes32()) : null; + final Hash requestsRoot = !input.isEndOfCurrentList() ? Hash.wrap(input.readBytes32()) : null; input.leaveList(); return new BlockHeader( parentHash, @@ -233,7 +228,7 @@ public static BlockHeader readFrom( blobGasUsed, excessBlobGas, parentBeaconBlockRoot, - depositHashRoot, + requestsRoot, blockHeaderFunctions); } @@ -242,10 +237,9 @@ public boolean equals(final Object obj) { if (obj == this) { return true; } - if (!(obj instanceof BlockHeader)) { + if (!(obj instanceof BlockHeader other)) { return false; } - final BlockHeader other = (BlockHeader) obj; return getHash().equals(other.getHash()); } @@ -258,6 +252,7 @@ public int hashCode() { public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("BlockHeader{"); + sb.append("number=").append(number).append(", "); sb.append("hash=").append(getHash()).append(", "); sb.append("parentHash=").append(parentHash).append(", "); sb.append("ommersHash=").append(ommersHash).append(", "); @@ -267,7 +262,6 @@ public String toString() { sb.append("receiptsRoot=").append(receiptsRoot).append(", "); sb.append("logsBloom=").append(logsBloom).append(", "); sb.append("difficulty=").append(difficulty).append(", "); - sb.append("number=").append(number).append(", "); sb.append("gasLimit=").append(gasLimit).append(", "); sb.append("gasUsed=").append(gasUsed).append(", "); sb.append("timestamp=").append(timestamp).append(", "); @@ -285,8 +279,8 @@ public String toString() { if (parentBeaconBlockRoot != null) { sb.append("parentBeaconBlockRoot=").append(parentBeaconBlockRoot).append(", "); } - if (depositsRoot != null) { - sb.append("depositsRoot=").append(depositsRoot); + if (requestsRoot != null) { + sb.append("requestsRoot=").append(requestsRoot); } return sb.append("}").toString(); } @@ -316,10 +310,10 @@ public static org.hyperledger.besu.ethereum.core.BlockHeader convertPluginBlockH .map(h -> Hash.fromHexString(h.toHexString())) .orElse(null), pluginBlockHeader.getBlobGasUsed().map(Long::longValue).orElse(null), - pluginBlockHeader.getExcessBlobGas().map(BlobGas::fromQuantity).orElse(null), + pluginBlockHeader.getExcessBlobGas().map(BlobGas.class::cast).orElse(null), pluginBlockHeader.getParentBeaconBlockRoot().orElse(null), pluginBlockHeader - .getDepositsRoot() + .getRequestsRoot() .map(h -> Hash.fromHexString(h.toHexString())) .orElse(null), blockHeaderFunctions); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeaderBuilder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeaderBuilder.java index b23c7a46c78..6f485d19ced 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeaderBuilder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockHeaderBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -45,7 +45,7 @@ public class BlockHeaderBuilder { private Hash transactionsRoot; private Hash withdrawalsRoot = null; - private Hash depositsRoot = null; + private Hash requestsRoot = null; private Hash receiptsRoot; @@ -124,7 +124,7 @@ public static BlockHeaderBuilder fromHeader(final BlockHeader header) { .blobGasUsed(header.getBlobGasUsed().orElse(null)) .excessBlobGas(header.getExcessBlobGas().orElse(null)) .parentBeaconBlockRoot(header.getParentBeaconBlockRoot().orElse(null)) - .depositsRoot(header.getDepositsRoot().orElse(null)); + .requestsRoot(header.getRequestsRoot().orElse(null)); } public static BlockHeaderBuilder fromBuilder(final BlockHeaderBuilder fromBuilder) { @@ -148,7 +148,7 @@ public static BlockHeaderBuilder fromBuilder(final BlockHeaderBuilder fromBuilde .withdrawalsRoot(fromBuilder.withdrawalsRoot) .excessBlobGas(fromBuilder.excessBlobGas) .parentBeaconBlockRoot(fromBuilder.parentBeaconBlockRoot) - .depositsRoot(fromBuilder.depositsRoot) + .requestsRoot(fromBuilder.requestsRoot) .blockHeaderFunctions(fromBuilder.blockHeaderFunctions); toBuilder.nonce = fromBuilder.nonce; return toBuilder; @@ -178,7 +178,7 @@ public BlockHeader buildBlockHeader() { blobGasUsed, excessBlobGas, parentBeaconBlockRoot, - depositsRoot, + requestsRoot, blockHeaderFunctions); } @@ -220,7 +220,7 @@ public SealableBlockHeader buildSealableBlockHeader() { blobGasUsed, excessBlobGas, parentBeaconBlockRoot, - depositsRoot); + requestsRoot); } private void validateBlockHeader() { @@ -284,7 +284,7 @@ public BlockHeaderBuilder populateFrom(final SealableBlockHeader sealableBlockHe sealableBlockHeader.getBlobGasUsed().ifPresent(this::blobGasUsed); sealableBlockHeader.getExcessBlobGas().ifPresent(this::excessBlobGas); sealableBlockHeader.getParentBeaconBlockRoot().ifPresent(this::parentBeaconBlockRoot); - depositsRoot(sealableBlockHeader.getDepositsRoot().orElse(null)); + requestsRoot(sealableBlockHeader.getRequestsRoot().orElse(null)); return this; } @@ -399,8 +399,8 @@ public BlockHeaderBuilder withdrawalsRoot(final Hash hash) { return this; } - public BlockHeaderBuilder depositsRoot(final Hash hash) { - this.depositsRoot = hash; + public BlockHeaderBuilder requestsRoot(final Hash hash) { + this.requestsRoot = hash; return this; } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockValueCalculator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockValueCalculator.java index cbf6a7c6941..a0db4717278 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockValueCalculator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/BlockValueCalculator.java @@ -1,5 +1,5 @@ /* - * Copyright Contributors to Hyperledger Besu. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -20,7 +20,7 @@ public class BlockValueCalculator { - public Wei calculateBlockValue(final BlockWithReceipts blockWithReceipts) { + public static Wei calculateBlockValue(final BlockWithReceipts blockWithReceipts) { final Block block = blockWithReceipts.getBlock(); final List txs = block.getBody().getTransactions(); final List receipts = blockWithReceipts.getReceipts(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/CodeDelegation.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/CodeDelegation.java new file mode 100644 index 00000000000..68fa958a8d5 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/CodeDelegation.java @@ -0,0 +1,266 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core; + +import org.hyperledger.besu.crypto.KeyPair; +import org.hyperledger.besu.crypto.SECPSignature; +import org.hyperledger.besu.crypto.SignatureAlgorithm; +import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.core.encoding.CodeDelegationEncoder; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; + +import java.math.BigInteger; +import java.util.Optional; +import java.util.function.Supplier; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Suppliers; +import org.apache.tuweni.bytes.Bytes; + +public class CodeDelegation implements org.hyperledger.besu.datatypes.CodeDelegation { + private static final Supplier SIGNATURE_ALGORITHM = + Suppliers.memoize(SignatureAlgorithmFactory::getInstance); + + public static final Bytes MAGIC = Bytes.fromHexString("05"); + + private final BigInteger chainId; + private final Address address; + private final long nonce; + private final SECPSignature signature; + private Optional
authorizer = Optional.empty(); + private boolean isAuthorityComputed = false; + + /** + * An access list entry as defined in EIP-7702 + * + * @param chainId can be either the current chain id or zero + * @param address the address from which the code will be set into the EOA account + * @param nonce the nonce after which this auth expires + * @param signature the signature of the EOA account which will be used to set the code + */ + public CodeDelegation( + final BigInteger chainId, + final Address address, + final long nonce, + final SECPSignature signature) { + this.chainId = chainId; + this.address = address; + this.nonce = nonce; + this.signature = signature; + } + + /** + * Create code delegation. + * + * @param chainId can be either the current chain id or zero + * @param address the address from which the code will be set into the EOA account + * @param nonce the nonce + * @param v the recovery id + * @param r the r value of the signature + * @param s the s value of the signature + * @return CodeDelegation + */ + @JsonCreator + public static org.hyperledger.besu.datatypes.CodeDelegation createCodeDelegation( + @JsonProperty("chainId") final BigInteger chainId, + @JsonProperty("address") final Address address, + @JsonProperty("nonce") final long nonce, + @JsonProperty("v") final byte v, + @JsonProperty("r") final BigInteger r, + @JsonProperty("s") final BigInteger s) { + return new CodeDelegation( + chainId, address, nonce, SIGNATURE_ALGORITHM.get().createSignature(r, s, v)); + } + + @JsonProperty("chainId") + @Override + public BigInteger chainId() { + return chainId; + } + + @JsonProperty("address") + @Override + public Address address() { + return address; + } + + @JsonProperty("signature") + @Override + public SECPSignature signature() { + return signature; + } + + @Override + public Optional
authorizer() { + if (!isAuthorityComputed) { + authorizer = computeAuthority(); + isAuthorityComputed = true; + } + + return authorizer; + } + + @Override + public long nonce() { + return nonce; + } + + @JsonProperty("v") + @Override + public byte v() { + return signature.getRecId(); + } + + @JsonProperty("r") + @Override + public BigInteger r() { + return signature.getR(); + } + + @JsonProperty("s") + @Override + public BigInteger s() { + return signature.getS(); + } + + private Optional
computeAuthority() { + BytesValueRLPOutput rlpOutput = new BytesValueRLPOutput(); + CodeDelegationEncoder.encodeSingleCodeDelegationWithoutSignature(this, rlpOutput); + + final Hash hash = Hash.hash(Bytes.concatenate(MAGIC, rlpOutput.encoded())); + + Optional
authorityAddress; + try { + authorityAddress = + SIGNATURE_ALGORITHM + .get() + .recoverPublicKeyFromSignature(hash, signature) + .map(Address::extract); + } catch (final IllegalArgumentException e) { + authorityAddress = Optional.empty(); + } + + return authorityAddress; + } + + /** + * Create a code delegation authorization with a builder. + * + * @return CodeDelegation.Builder + */ + public static Builder builder() { + return new Builder(); + } + + /** Builder for CodeDelegation authorizations. */ + public static class Builder { + private BigInteger chainId = BigInteger.ZERO; + private Address address; + private Long nonce; + private SECPSignature signature; + + /** Create a new builder. */ + protected Builder() {} + + /** + * Set the optional chain id. + * + * @param chainId the chain id + * @return this builder + */ + public Builder chainId(final BigInteger chainId) { + this.chainId = chainId; + return this; + } + + /** + * Set the address of the authorized smart contract. + * + * @param address the address + * @return this builder + */ + public Builder address(final Address address) { + this.address = address; + return this; + } + + /** + * Set the nonce. + * + * @param nonce the nonce. + * @return this builder + */ + public Builder nonce(final long nonce) { + this.nonce = nonce; + return this; + } + + /** + * Set the signature of the authorizer account. + * + * @param signature the signature + * @return this builder + */ + public Builder signature(final SECPSignature signature) { + this.signature = signature; + return this; + } + + /** + * Sign the authorization with the given key pair and return the authorization. + * + * @param keyPair the key pair + * @return CodeDelegation + */ + public org.hyperledger.besu.datatypes.CodeDelegation signAndBuild(final KeyPair keyPair) { + final BytesValueRLPOutput output = new BytesValueRLPOutput(); + output.startList(); + output.writeBigIntegerScalar(chainId); + output.writeBytes(address); + output.writeLongScalar(nonce); + output.endList(); + + signature( + SIGNATURE_ALGORITHM + .get() + .sign(Hash.hash(Bytes.concatenate(MAGIC, output.encoded())), keyPair)); + return build(); + } + + /** + * Build the authorization. + * + * @return CodeDelegation + */ + public org.hyperledger.besu.datatypes.CodeDelegation build() { + if (address == null) { + throw new IllegalStateException("Address must be set"); + } + + if (nonce == null) { + throw new IllegalStateException("Nonce must be set"); + } + + if (signature == null) { + throw new IllegalStateException("Signature must be set"); + } + + return new CodeDelegation(chainId, address, nonce, signature); + } + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ConsolidationRequest.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ConsolidationRequest.java new file mode 100644 index 00000000000..38345d1a31e --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ConsolidationRequest.java @@ -0,0 +1,90 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.datatypes.PublicKey; +import org.hyperledger.besu.datatypes.RequestType; + +import java.util.Objects; + +public class ConsolidationRequest extends Request + implements org.hyperledger.besu.plugin.data.ConsolidationRequest { + + private final Address sourceAddress; + private final BLSPublicKey sourcePubkey; + private final BLSPublicKey targetPubkey; + + public ConsolidationRequest( + final Address sourceAddress, + final BLSPublicKey sourcePubkey, + final BLSPublicKey targetPubkey) { + this.sourceAddress = sourceAddress; + this.sourcePubkey = sourcePubkey; + this.targetPubkey = targetPubkey; + } + + @Override + public RequestType getType() { + return RequestType.CONSOLIDATION; + } + + @Override + public Address getSourceAddress() { + return sourceAddress; + } + + @Override + public PublicKey getSourcePubkey() { + return sourcePubkey; + } + + @Override + public PublicKey getTargetPubkey() { + return targetPubkey; + } + + @Override + public String toString() { + return "ConsolidationRequest{" + + "sourceAddress=" + + sourceAddress + + " sourcePubkey=" + + sourcePubkey + + " targetPubkey=" + + targetPubkey + + '}'; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final ConsolidationRequest that = (ConsolidationRequest) o; + return Objects.equals(sourceAddress, that.sourceAddress) + && Objects.equals(sourcePubkey, that.sourcePubkey) + && Objects.equals(targetPubkey, that.targetPubkey); + } + + @Override + public int hashCode() { + return Objects.hash(sourceAddress, sourcePubkey, targetPubkey); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Deposit.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Deposit.java deleted file mode 100644 index ed362199b23..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Deposit.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright contributors to Hyperledger Besu - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.core; - -import org.hyperledger.besu.datatypes.BLSPublicKey; -import org.hyperledger.besu.datatypes.BLSSignature; -import org.hyperledger.besu.datatypes.GWei; -import org.hyperledger.besu.datatypes.PublicKey; -import org.hyperledger.besu.ethereum.core.encoding.DepositDecoder; -import org.hyperledger.besu.ethereum.core.encoding.DepositEncoder; -import org.hyperledger.besu.ethereum.rlp.RLP; -import org.hyperledger.besu.ethereum.rlp.RLPInput; -import org.hyperledger.besu.ethereum.rlp.RLPOutput; - -import java.util.Objects; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt64; - -public class Deposit implements org.hyperledger.besu.plugin.data.Deposit { - - private final BLSPublicKey pubkey; - private final Bytes32 depositWithdrawalCredentials; - private final GWei amount; - private final BLSSignature signature; - private final UInt64 index; - - public Deposit( - final BLSPublicKey pubkey, - final Bytes32 depositWithdrawalCredentials, - final GWei amount, - final BLSSignature signature, - final UInt64 index) { - this.pubkey = pubkey; - this.depositWithdrawalCredentials = depositWithdrawalCredentials; - this.amount = amount; - this.signature = signature; - this.index = index; - } - - public static Deposit readFrom(final Bytes rlpBytes) { - return readFrom(RLP.input(rlpBytes)); - } - - public static Deposit readFrom(final RLPInput rlpInput) { - return DepositDecoder.decode(rlpInput); - } - - public void writeTo(final RLPOutput out) { - DepositEncoder.encode(this, out); - } - - @Override - public PublicKey getPubkey() { - return pubkey; - } - - @Override - public Bytes32 getWithdrawalCredentials() { - return depositWithdrawalCredentials; - } - - @Override - public GWei getAmount() { - return amount; - } - - @Override - public BLSSignature getSignature() { - return signature; - } - - @Override - public UInt64 getIndex() { - return index; - } - - @Override - public String toString() { - return "Deposit{" - + "pubKey=" - + pubkey - + ", withdrawalCredentials=" - + depositWithdrawalCredentials - + ", amount=" - + amount - + ", signature=" - + signature - + ", index=" - + index - + '}'; - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - final Deposit that = (Deposit) o; - return Objects.equals(pubkey, that.pubkey) - && Objects.equals(depositWithdrawalCredentials, that.depositWithdrawalCredentials) - && Objects.equals(amount, that.amount) - && Objects.equals(signature, that.signature) - && Objects.equals(index, that.index); - } - - @Override - public int hashCode() { - return Objects.hash(pubkey, depositWithdrawalCredentials, amount, signature, index); - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/DepositContract.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/DepositContract.java index 9f36df7ca97..a49f25fb008 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/DepositContract.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/DepositContract.java @@ -1,6 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu - * + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -13,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.core; import org.hyperledger.besu.evm.log.LogTopic; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/DepositRequest.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/DepositRequest.java new file mode 100644 index 00000000000..a005fa831e0 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/DepositRequest.java @@ -0,0 +1,112 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core; + +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.datatypes.BLSSignature; +import org.hyperledger.besu.datatypes.GWei; +import org.hyperledger.besu.datatypes.PublicKey; +import org.hyperledger.besu.datatypes.RequestType; + +import java.util.Objects; + +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt64; + +public class DepositRequest extends Request + implements org.hyperledger.besu.plugin.data.DepositRequest { + + private final BLSPublicKey pubkey; + private final Bytes32 depositWithdrawalCredentials; + private final GWei amount; + private final BLSSignature signature; + private final UInt64 index; + + public DepositRequest( + final BLSPublicKey pubkey, + final Bytes32 depositWithdrawalCredentials, + final GWei amount, + final BLSSignature signature, + final UInt64 index) { + this.pubkey = pubkey; + this.depositWithdrawalCredentials = depositWithdrawalCredentials; + this.amount = amount; + this.signature = signature; + this.index = index; + } + + @Override + public RequestType getType() { + return RequestType.DEPOSIT; + } + + @Override + public PublicKey getPubkey() { + return pubkey; + } + + @Override + public Bytes32 getWithdrawalCredentials() { + return depositWithdrawalCredentials; + } + + @Override + public GWei getAmount() { + return amount; + } + + @Override + public BLSSignature getSignature() { + return signature; + } + + @Override + public UInt64 getIndex() { + return index; + } + + @Override + public String toString() { + return "Deposit{" + + "pubKey=" + + pubkey + + ", withdrawalCredentials=" + + depositWithdrawalCredentials + + ", amount=" + + amount + + ", signature=" + + signature + + ", index=" + + index + + '}'; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final DepositRequest that = (DepositRequest) o; + return Objects.equals(pubkey, that.pubkey) + && Objects.equals(depositWithdrawalCredentials, that.depositWithdrawalCredentials) + && Objects.equals(amount, that.amount) + && Objects.equals(signature, that.signature) + && Objects.equals(index, that.index); + } + + @Override + public int hashCode() { + return Objects.hash(pubkey, depositWithdrawalCredentials, amount, signature, index); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/GasAndAccessedState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/GasAndAccessedState.java index 3ef9dcd4053..c7a834ce263 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/GasAndAccessedState.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/GasAndAccessedState.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.core; import static java.util.Collections.emptySet; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/LogWithMetadata.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/LogWithMetadata.java index 57721af98b0..b4b8baa6aaf 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/LogWithMetadata.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/LogWithMetadata.java @@ -1,5 +1,4 @@ /* - * * Copyright ConsenSys AG. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with @@ -12,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.core; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/LogsWrapper.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/LogsWrapper.java index 7ff9af5374d..0e1f3df23f7 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/LogsWrapper.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/LogsWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.core; import org.hyperledger.besu.datatypes.Address; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/MiningParameters.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/MiningParameters.java index 32ac5ee926a..20a8a31084d 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/MiningParameters.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/MiningParameters.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -16,7 +16,10 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.util.number.Percentage; +import org.hyperledger.besu.plugin.services.TransactionSelectionService; +import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelector; +import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelectorFactory; +import org.hyperledger.besu.util.number.PositiveNumber; import java.time.Duration; import java.util.Objects; @@ -32,6 +35,10 @@ @Value.Immutable @Value.Enclosing public abstract class MiningParameters { + public static final PositiveNumber DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME = + PositiveNumber.fromInt((int) Duration.ofSeconds(5).toMillis()); + public static final PositiveNumber DEFAULT_POA_BLOCK_TXS_SELECTION_MAX_TIME = + PositiveNumber.fromInt(75); public static final MiningParameters MINING_DISABLED = ImmutableMiningParameters.builder() .mutableInitValues( @@ -115,6 +122,15 @@ public MiningParameters setNonceGenerator(final Iterable nonceGenerator) { return this; } + public OptionalInt getBlockPeriodSeconds() { + return getMutableRuntimeValues().blockPeriodSeconds; + } + + public MiningParameters setBlockPeriodSeconds(final int blockPeriodSeconds) { + getMutableRuntimeValues().blockPeriodSeconds = OptionalInt.of(blockPeriodSeconds); + return this; + } + @Value.Default public boolean isStratumMiningEnabled() { return false; @@ -130,6 +146,40 @@ public int getStratumPort() { return 8008; } + @Value.Default + public PositiveNumber getNonPoaBlockTxsSelectionMaxTime() { + return DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME; + } + + @Value.Default + public PositiveNumber getPoaBlockTxsSelectionMaxTime() { + return DEFAULT_POA_BLOCK_TXS_SELECTION_MAX_TIME; + } + + @Value.Default + public TransactionSelectionService getTransactionSelectionService() { + return new TransactionSelectionService() { + @Override + public PluginTransactionSelector createPluginTransactionSelector() { + return PluginTransactionSelector.ACCEPT_ALL; + } + + @Override + public void registerPluginTransactionSelectorFactory( + final PluginTransactionSelectorFactory transactionSelectorFactory) {} + }; + } + + public long getBlockTxsSelectionMaxTime() { + final var maybeBlockPeriodSeconds = getMutableRuntimeValues().blockPeriodSeconds; + if (maybeBlockPeriodSeconds.isPresent()) { + return (TimeUnit.SECONDS.toMillis(maybeBlockPeriodSeconds.getAsInt()) + * getPoaBlockTxsSelectionMaxTime().getValue()) + / 100; + } + return getNonPoaBlockTxsSelectionMaxTime().getValue(); + } + @Value.Default protected MutableRuntimeValues getMutableRuntimeValues() { return new MutableRuntimeValues(getMutableInitValues()); @@ -179,6 +229,8 @@ default double getMinBlockOccupancyRatio() { return DEFAULT_MIN_BLOCK_OCCUPANCY_RATIO; } + OptionalInt getBlockPeriodSeconds(); + Optional
getCoinbase(); OptionalLong getTargetGasLimit(); @@ -195,6 +247,7 @@ static class MutableRuntimeValues { private volatile Optional
coinbase; private volatile OptionalLong targetGasLimit; private volatile Optional> nonceGenerator; + private volatile OptionalInt blockPeriodSeconds; private MutableRuntimeValues(final MutableInitValues initValues) { miningEnabled = initValues.isMiningEnabled(); @@ -205,6 +258,7 @@ private MutableRuntimeValues(final MutableInitValues initValues) { coinbase = initValues.getCoinbase(); targetGasLimit = initValues.getTargetGasLimit(); nonceGenerator = initValues.nonceGenerator(); + blockPeriodSeconds = initValues.getBlockPeriodSeconds(); } @Override @@ -219,7 +273,8 @@ public boolean equals(final Object o) { && Objects.equals(coinbase, that.coinbase) && Objects.equals(minPriorityFeePerGas, that.minPriorityFeePerGas) && Objects.equals(targetGasLimit, that.targetGasLimit) - && Objects.equals(nonceGenerator, that.nonceGenerator); + && Objects.equals(nonceGenerator, that.nonceGenerator) + && Objects.equals(blockPeriodSeconds, that.blockPeriodSeconds); } @Override @@ -232,7 +287,8 @@ public int hashCode() { minBlockOccupancyRatio, coinbase, targetGasLimit, - nonceGenerator); + nonceGenerator, + blockPeriodSeconds); } @Override @@ -254,6 +310,8 @@ public String toString() { + targetGasLimit + ", nonceGenerator=" + nonceGenerator + + ", blockPeriodSeconds=" + + blockPeriodSeconds + '}'; } } @@ -266,8 +324,6 @@ public interface Unstable { int DEFAULT_MAX_OMMERS_DEPTH = 8; long DEFAULT_POS_BLOCK_CREATION_MAX_TIME = Duration.ofSeconds(12).toMillis(); long DEFAULT_POS_BLOCK_CREATION_REPETITION_MIN_DURATION = Duration.ofMillis(500).toMillis(); - long DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME = Duration.ofSeconds(5).toMillis(); - Percentage DEFAULT_POA_BLOCK_TXS_SELECTION_MAX_TIME = Percentage.fromInt(75); MiningParameters.Unstable DEFAULT = ImmutableMiningParameters.Unstable.builder().build(); @@ -305,27 +361,5 @@ default long getPosBlockCreationRepetitionMinDuration() { default String getStratumExtranonce() { return "080c"; } - - @Value.Default - default long getNonPoaBlockTxsSelectionMaxTime() { - return DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME; - } - - @Value.Default - default Percentage getPoaBlockTxsSelectionMaxTime() { - return DEFAULT_POA_BLOCK_TXS_SELECTION_MAX_TIME; - } - - OptionalInt getMinBlockTime(); - - @Value.Derived - default long getBlockTxsSelectionMaxTime() { - if (getMinBlockTime().isPresent()) { - return (TimeUnit.SECONDS.toMillis(getMinBlockTime().getAsInt()) - * getPoaBlockTxsSelectionMaxTime().getValue()) - / 100; - } - return getNonPoaBlockTxsSelectionMaxTime(); - } } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/MiningParametersMetrics.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/MiningParametersMetrics.java new file mode 100644 index 00000000000..3b3e3a28dcb --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/MiningParametersMetrics.java @@ -0,0 +1,39 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core; + +import org.hyperledger.besu.metrics.BesuMetricCategory; +import org.hyperledger.besu.plugin.services.MetricsSystem; + +public class MiningParametersMetrics { + public static final String MIN_GAS_PRICE_GAUGE = "min_gas_price"; + public static final String MIN_PRIORITY_FEE_GAUGE = "min_priority_fee"; + + public MiningParametersMetrics( + final MetricsSystem metricsSystem, final MiningParameters miningParameters) { + + metricsSystem.createGauge( + BesuMetricCategory.ETHEREUM, + MIN_GAS_PRICE_GAUGE, + "Gauge to measure the runtime value of min-gas-price", + () -> miningParameters.getMinTransactionGasPrice().toBigInteger().doubleValue()); + + metricsSystem.createGauge( + BesuMetricCategory.ETHEREUM, + MIN_PRIORITY_FEE_GAUGE, + "Gauge to measure the runtime value of min-priority-fee", + () -> miningParameters.getMinPriorityFeePerGas().toBigInteger().doubleValue()); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/MutableWorldState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/MutableWorldState.java index 409003a3218..973774ae5da 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/MutableWorldState.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/MutableWorldState.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/PermissionTransactionFilter.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/PermissionTransactionFilter.java index bb2784e9805..9ad4d3c4f53 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/PermissionTransactionFilter.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/PermissionTransactionFilter.java @@ -1,6 +1,5 @@ /* - * - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.core; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/PrivacyParameters.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/PrivacyParameters.java index 1d4cfdf6c1b..775634a41a1 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/PrivacyParameters.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/PrivacyParameters.java @@ -26,10 +26,10 @@ import org.hyperledger.besu.ethereum.privacy.PrivateWorldStateReader; import org.hyperledger.besu.ethereum.privacy.storage.PrivacyStorageProvider; import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; -import org.hyperledger.besu.ethereum.worldstate.DefaultWorldStateArchive; +import org.hyperledger.besu.ethereum.trie.forest.ForestWorldStateArchive; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.ethereum.worldstate.WorldStatePreimageStorage; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.plugin.services.PrivacyPluginService; import org.hyperledger.besu.plugin.services.privacy.PrivacyGroupGenesisProvider; @@ -77,6 +77,7 @@ public class PrivacyParameters { private PrivateStateRootResolver privateStateRootResolver; private PrivateWorldStateReader privateWorldStateReader; private PrivacyPluginService privacyPluginService; + private boolean privateNonceAlwaysIncrementsEnabled; public Address getPrivacyAddress() { if (isPrivacyPluginEnabled()) { @@ -228,6 +229,15 @@ private PrivacyGroupGenesisProvider createPrivateGenesisProvider() { } } + public boolean isPrivateNonceAlwaysIncrementsEnabled() { + return privateNonceAlwaysIncrementsEnabled; + } + + public void setPrivateNonceAlwaysIncrementsEnabled( + final boolean privateNonceAlwaysIncrementsEnabled) { + this.privateNonceAlwaysIncrementsEnabled = privateNonceAlwaysIncrementsEnabled; + } + @Override public String toString() { return "PrivacyParameters{" @@ -263,6 +273,7 @@ public static class Builder { private boolean flexiblePrivacyGroupsEnabled; private boolean privacyPluginEnabled; private PrivacyPluginService privacyPluginService; + private boolean privateNonceAlwaysIncrementsEnabled; public Builder setEnclaveUrl(final URI enclaveUrl) { this.enclaveUrl = enclaveUrl; @@ -314,6 +325,12 @@ public Builder setFlexiblePrivacyGroupsEnabled(final boolean flexiblePrivacyGrou return this; } + public Builder setPrivateNonceAlwaysIncrementsEnabled( + final boolean isPrivateNonceAlwaysIncrementsEnabled) { + this.privateNonceAlwaysIncrementsEnabled = isPrivateNonceAlwaysIncrementsEnabled; + return this; + } + public Builder setPrivacyPluginEnabled(final boolean privacyPluginEnabled) { this.privacyPluginEnabled = privacyPluginEnabled; return this; @@ -335,12 +352,12 @@ public Builder setPrivacyService(final PrivacyPluginService privacyPluginService public PrivacyParameters build() { final PrivacyParameters config = new PrivacyParameters(); if (enabled) { - final WorldStateStorage privateWorldStateStorage = - storageProvider.createWorldStateStorage(); + final WorldStateStorageCoordinator privateWorldStateStorage = + storageProvider.createWorldStateStorageCoordinator(); final WorldStatePreimageStorage privatePreimageStorage = storageProvider.createWorldStatePreimageStorage(); final WorldStateArchive privateWorldStateArchive = - new DefaultWorldStateArchive( + new ForestWorldStateArchive( privateWorldStateStorage, privatePreimageStorage, EvmConfiguration.DEFAULT); final PrivateStateStorage privateStateStorage = storageProvider.createPrivateStateStorage(); @@ -382,6 +399,7 @@ public PrivacyParameters build() { config.setMultiTenancyEnabled(multiTenancyEnabled); config.setFlexiblePrivacyGroupsEnabled(flexiblePrivacyGroupsEnabled); config.setPrivacyPluginEnabled(privacyPluginEnabled); + config.setPrivateNonceAlwaysIncrementsEnabled(privateNonceAlwaysIncrementsEnabled); return config; } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java index 75393315428..036a33af97a 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/ProcessableBlockHeader.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Request.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Request.java new file mode 100644 index 00000000000..8f4ea64e314 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Request.java @@ -0,0 +1,41 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core; + +import org.hyperledger.besu.datatypes.RequestType; +import org.hyperledger.besu.ethereum.core.encoding.RequestDecoder; +import org.hyperledger.besu.ethereum.core.encoding.RequestEncoder; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.rlp.RLPInput; +import org.hyperledger.besu.ethereum.rlp.RLPOutput; + +import org.apache.tuweni.bytes.Bytes; + +public abstract class Request implements org.hyperledger.besu.plugin.data.Request { + @Override + public abstract RequestType getType(); + + public static Request readFrom(final Bytes rlpBytes) { + return readFrom(RLP.input(rlpBytes)); + } + + public static Request readFrom(final RLPInput rlpInput) { + return RequestDecoder.decode(rlpInput); + } + + public void writeTo(final RLPOutput out) { + RequestEncoder.encode(this, out); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/SealableBlockHeader.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/SealableBlockHeader.java index 68855cfafbd..df3859f2b7c 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/SealableBlockHeader.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/SealableBlockHeader.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -43,7 +43,7 @@ public class SealableBlockHeader extends ProcessableBlockHeader { protected final Hash withdrawalsRoot; - protected final Hash depositsRoot; + protected final Hash requestsRoot; protected final Long blobGasUsed; @@ -69,7 +69,7 @@ protected SealableBlockHeader( final Long blobGasUsed, final BlobGas excessBlobGas, final Bytes32 parentBeaconBlockRoot, - final Hash depositsRoot) { + final Hash requestsRoot) { super( parentHash, coinbase, @@ -84,8 +84,8 @@ protected SealableBlockHeader( this.stateRoot = stateRoot; this.transactionsRoot = transactionsRoot; this.withdrawalsRoot = withdrawalsRoot; - this.depositsRoot = depositsRoot; this.receiptsRoot = receiptsRoot; + this.requestsRoot = requestsRoot; this.logsBloom = logsBloom; this.gasUsed = gasUsed; this.extraData = extraData; @@ -166,12 +166,12 @@ public Optional getWithdrawalsRoot() { } /** - * Returns the block deposits root hash. + * Returns the block requests root hash. * - * @return the block deposits root hash + * @return the block requests root hash */ - public Optional getDepositsRoot() { - return Optional.ofNullable(depositsRoot); + public Optional getRequestsRoot() { + return Optional.ofNullable(requestsRoot); } /** diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java index 79261159498..0da8f8f28a2 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Transaction.java @@ -28,6 +28,7 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Blob; import org.hyperledger.besu.datatypes.BlobsWithCommitments; +import org.hyperledger.besu.datatypes.CodeDelegation; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.KZGCommitment; import org.hyperledger.besu.datatypes.KZGProof; @@ -37,6 +38,7 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.encoding.AccessListTransactionEncoder; import org.hyperledger.besu.ethereum.core.encoding.BlobTransactionEncoder; +import org.hyperledger.besu.ethereum.core.encoding.CodeDelegationEncoder; import org.hyperledger.besu.ethereum.core.encoding.EncodingContext; import org.hyperledger.besu.ethereum.core.encoding.TransactionDecoder; import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder; @@ -51,6 +53,8 @@ import java.util.Objects; import java.util.Optional; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; import com.google.common.primitives.Longs; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; @@ -75,6 +79,9 @@ public class Transaction public static final BigInteger TWO = BigInteger.valueOf(2); + private static final Cache senderCache = + CacheBuilder.newBuilder().recordStats().maximumSize(100_000L).build(); + private final long nonce; private final Optional gasPrice; @@ -117,6 +124,7 @@ public class Transaction private final Optional> versionedHashes; private final Optional blobsWithCommitments; + private final Optional> maybeCodeDelegationList; public static Builder builder() { return new Builder(); @@ -172,7 +180,8 @@ private Transaction( final Address sender, final Optional chainId, final Optional> versionedHashes, - final Optional blobsWithCommitments) { + final Optional blobsWithCommitments, + final Optional> maybeCodeDelegationList) { if (!forCopy) { if (transactionType.requiresChainId()) { @@ -208,6 +217,12 @@ private Transaction( checkArgument( maxFeePerBlobGas.isPresent(), "Must specify max fee per blob gas for blob transaction"); } + + if (transactionType.requiresCodeDelegation()) { + checkArgument( + maybeCodeDelegationList.isPresent(), + "Must specify code delegation authorizations for code delegation transaction"); + } } this.transactionType = transactionType; @@ -226,6 +241,7 @@ private Transaction( this.chainId = chainId; this.versionedHashes = versionedHashes; this.blobsWithCommitments = blobsWithCommitments; + this.maybeCodeDelegationList = maybeCodeDelegationList; } /** @@ -411,18 +427,25 @@ public Optional getChainId() { @Override public Address getSender() { if (sender == null) { - final SECPPublicKey publicKey = - signatureAlgorithm - .recoverPublicKeyFromSignature(getOrComputeSenderRecoveryHash(), signature) - .orElseThrow( - () -> - new IllegalStateException( - "Cannot recover public key from signature for " + this)); - sender = Address.extract(Hash.hash(publicKey.getEncodedBytes())); + Optional
cachedSender = Optional.ofNullable(senderCache.getIfPresent(getHash())); + sender = cachedSender.orElseGet(this::computeSender); } return sender; } + private Address computeSender() { + final SECPPublicKey publicKey = + signatureAlgorithm + .recoverPublicKeyFromSignature(getOrComputeSenderRecoveryHash(), signature) + .orElseThrow( + () -> + new IllegalStateException( + "Cannot recover public key from signature for " + this)); + final Address calculatedSender = Address.extract(Hash.hash(publicKey.getEncodedBytes())); + senderCache.put(this.hash, calculatedSender); + return calculatedSender; + } + /** * Returns the public key extracted from the signature. * @@ -450,6 +473,7 @@ private Bytes32 getOrComputeSenderRecoveryHash() { payload, maybeAccessList, versionedHashes.orElse(null), + maybeCodeDelegationList, chainId); } return hashNoSignature; @@ -656,6 +680,16 @@ public Optional getBlobsWithCommitments() { return blobsWithCommitments; } + @Override + public Optional> getCodeDelegationList() { + return maybeCodeDelegationList; + } + + @Override + public int codeDelegationListSize() { + return maybeCodeDelegationList.map(List::size).orElse(0); + } + /** * Return the list of transaction hashes extracted from the collection of Transaction passed as * argument @@ -680,6 +714,7 @@ private static Bytes32 computeSenderRecoveryHash( final Bytes payload, final Optional> accessList, final List versionedHashes, + final Optional> codeDelegationList, final Optional chainId) { if (transactionType.requiresChainId()) { checkArgument(chainId.isPresent(), "Transaction type %s requires chainId", transactionType); @@ -687,40 +722,58 @@ private static Bytes32 computeSenderRecoveryHash( final Bytes preimage = switch (transactionType) { case FRONTIER -> frontierPreimage(nonce, gasPrice, gasLimit, to, value, payload, chainId); - case EIP1559 -> eip1559Preimage( - nonce, - maxPriorityFeePerGas, - maxFeePerGas, - gasLimit, - to, - value, - payload, - chainId, - accessList); - case BLOB -> blobPreimage( - nonce, - maxPriorityFeePerGas, - maxFeePerGas, - maxFeePerBlobGas, - gasLimit, - to, - value, - payload, - chainId, - accessList, - versionedHashes); - case ACCESS_LIST -> accessListPreimage( - nonce, - gasPrice, - gasLimit, - to, - value, - payload, - accessList.orElseThrow( - () -> - new IllegalStateException( - "Developer error: the transaction should be guaranteed to have an access list here")), - chainId); + case EIP1559 -> + eip1559Preimage( + nonce, + maxPriorityFeePerGas, + maxFeePerGas, + gasLimit, + to, + value, + payload, + chainId, + accessList); + case BLOB -> + blobPreimage( + nonce, + maxPriorityFeePerGas, + maxFeePerGas, + maxFeePerBlobGas, + gasLimit, + to, + value, + payload, + chainId, + accessList, + versionedHashes); + case ACCESS_LIST -> + accessListPreimage( + nonce, + gasPrice, + gasLimit, + to, + value, + payload, + accessList.orElseThrow( + () -> + new IllegalStateException( + "Developer error: the transaction should be guaranteed to have an access list here")), + chainId); + case DELEGATE_CODE -> + codeDelegationPreimage( + nonce, + maxPriorityFeePerGas, + maxFeePerGas, + gasLimit, + to, + value, + payload, + chainId, + accessList, + codeDelegationList.orElseThrow( + () -> + new IllegalStateException( + "Developer error: the transaction should be guaranteed to have a code delegations here"))); }; return keccak256(preimage); } @@ -858,6 +911,38 @@ private static Bytes accessListPreimage( return Bytes.concatenate(Bytes.of(TransactionType.ACCESS_LIST.getSerializedType()), encode); } + private static Bytes codeDelegationPreimage( + final long nonce, + final Wei maxPriorityFeePerGas, + final Wei maxFeePerGas, + final long gasLimit, + final Optional
to, + final Wei value, + final Bytes payload, + final Optional chainId, + final Optional> accessList, + final List authorizationList) { + final Bytes encoded = + RLP.encode( + rlpOutput -> { + rlpOutput.startList(); + eip1559PreimageFields( + nonce, + maxPriorityFeePerGas, + maxFeePerGas, + gasLimit, + to, + value, + payload, + chainId, + accessList, + rlpOutput); + CodeDelegationEncoder.encodeCodeDelegationInner(authorizationList, rlpOutput); + rlpOutput.endList(); + }); + return Bytes.concatenate(Bytes.of(TransactionType.DELEGATE_CODE.getSerializedType()), encoded); + } + @Override public boolean equals(final Object other) { if (!(other instanceof Transaction that)) { @@ -1007,24 +1092,34 @@ public Transaction detachedCopy() { withCommitments -> blobsWithCommitmentsDetachedCopy(withCommitments, detachedVersionedHashes.get())); - return new Transaction( - true, - transactionType, - nonce, - gasPrice, - maxPriorityFeePerGas, - maxFeePerGas, - maxFeePerBlobGas, - gasLimit, - detachedTo, - value, - signature, - payload.copy(), - detachedAccessList, - sender, - chainId, - detachedVersionedHashes, - detachedBlobsWithCommitments); + final var copiedTx = + new Transaction( + true, + transactionType, + nonce, + gasPrice, + maxPriorityFeePerGas, + maxFeePerGas, + maxFeePerBlobGas, + gasLimit, + detachedTo, + value, + signature, + payload.copy(), + detachedAccessList, + sender, + chainId, + detachedVersionedHashes, + detachedBlobsWithCommitments, + maybeCodeDelegationList); + + // copy also the computed fields, to avoid to recompute them + copiedTx.sender = this.sender; + copiedTx.hash = this.hash; + copiedTx.hashNoSignature = this.hashNoSignature; + copiedTx.size = this.size; + + return copiedTx; } private AccessListEntry accessListDetachedCopy(final AccessListEntry accessListEntry) { @@ -1084,6 +1179,7 @@ public static class Builder { protected Optional v = Optional.empty(); protected List versionedHashes = null; private BlobsWithCommitments blobsWithCommitments; + protected Optional> codeDelegationAuthorizations = Optional.empty(); public Builder copiedFrom(final Transaction toCopy) { this.transactionType = toCopy.transactionType; @@ -1102,6 +1198,7 @@ public Builder copiedFrom(final Transaction toCopy) { this.chainId = toCopy.chainId; this.versionedHashes = toCopy.versionedHashes.orElse(null); this.blobsWithCommitments = toCopy.blobsWithCommitments.orElse(null); + this.codeDelegationAuthorizations = toCopy.maybeCodeDelegationList; return this; } @@ -1195,6 +1292,8 @@ public Builder guessType() { transactionType = TransactionType.EIP1559; } else if (accessList.isPresent()) { transactionType = TransactionType.ACCESS_LIST; + } else if (codeDelegationAuthorizations.isPresent()) { + transactionType = TransactionType.DELEGATE_CODE; } else { transactionType = TransactionType.FRONTIER; } @@ -1224,7 +1323,8 @@ public Transaction build() { sender, chainId, Optional.ofNullable(versionedHashes), - Optional.ofNullable(blobsWithCommitments)); + Optional.ofNullable(blobsWithCommitments), + codeDelegationAuthorizations); } public Transaction signAndBuild(final KeyPair keys) { @@ -1251,6 +1351,7 @@ SECPSignature computeSignature(final KeyPair keys) { payload, accessList, versionedHashes, + codeDelegationAuthorizations, chainId), keys); } @@ -1274,5 +1375,10 @@ public Builder blobsWithCommitments(final BlobsWithCommitments blobsWithCommitme this.blobsWithCommitments = blobsWithCommitments; return this; } + + public Builder codeDelegations(final List codeDelegations) { + this.codeDelegationAuthorizations = Optional.ofNullable(codeDelegations); + return this; + } } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/TransactionReceipt.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/TransactionReceipt.java index b7419b1572d..79fb1a50149 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/TransactionReceipt.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/TransactionReceipt.java @@ -30,6 +30,7 @@ import java.util.Optional; import java.util.stream.Collectors; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; import org.apache.tuweni.bytes.Bytes; @@ -169,23 +170,26 @@ private TransactionReceipt( * * @param out The RLP output to write to */ - public void writeTo(final RLPOutput out) { - writeTo(out, false); + public void writeToForNetwork(final RLPOutput out) { + writeTo(out, false, false); } - public void writeToWithRevertReason(final RLPOutput out) { - writeTo(out, true); + public void writeToForStorage(final RLPOutput out, final boolean compacted) { + writeTo(out, true, compacted); } - private void writeTo(final RLPOutput rlpOutput, final boolean withRevertReason) { + @VisibleForTesting + void writeTo(final RLPOutput rlpOutput, final boolean withRevertReason, final boolean compacted) { if (transactionType.equals(TransactionType.FRONTIER)) { - writeToForReceiptTrie(rlpOutput, withRevertReason); + writeToForReceiptTrie(rlpOutput, withRevertReason, compacted); } else { - rlpOutput.writeBytes(RLP.encode(out -> writeToForReceiptTrie(out, withRevertReason))); + rlpOutput.writeBytes( + RLP.encode(out -> writeToForReceiptTrie(out, withRevertReason, compacted))); } } - public void writeToForReceiptTrie(final RLPOutput rlpOutput, final boolean withRevertReason) { + public void writeToForReceiptTrie( + final RLPOutput rlpOutput, final boolean withRevertReason, final boolean compacted) { if (!transactionType.equals(TransactionType.FRONTIER)) { rlpOutput.writeIntScalar(transactionType.getSerializedType()); } @@ -200,8 +204,10 @@ public void writeToForReceiptTrie(final RLPOutput rlpOutput, final boolean withR rlpOutput.writeLongScalar(status); } rlpOutput.writeLongScalar(cumulativeGasUsed); - rlpOutput.writeBytes(bloomFilter); - rlpOutput.writeList(logs, Log::writeTo); + if (!compacted) { + rlpOutput.writeBytes(bloomFilter); + } + rlpOutput.writeList(logs, (log, logOutput) -> log.writeTo(logOutput, compacted)); if (withRevertReason && revertReason.isPresent()) { rlpOutput.writeBytes(revertReason.get()); } @@ -240,10 +246,21 @@ public static TransactionReceipt readFrom( // correct transaction receipt encoding to use. final RLPInput firstElement = input.readAsRlp(); final long cumulativeGas = input.readLongScalar(); - // The logs below will populate the bloom filter upon construction. + + LogsBloomFilter bloomFilter = null; + + final boolean hasLogs = !input.nextIsList() && input.nextSize() == LogsBloomFilter.BYTE_SIZE; + if (hasLogs) { + // The logs below will populate the bloom filter upon construction. + bloomFilter = LogsBloomFilter.readFrom(input); + } // TODO consider validating that the logs and bloom filter match. - final LogsBloomFilter bloomFilter = LogsBloomFilter.readFrom(input); - final List logs = input.readList(Log::readFrom); + final boolean compacted = !hasLogs; + final List logs = input.readList(logInput -> Log.readFrom(logInput, compacted)); + if (compacted) { + bloomFilter = LogsBloomFilter.builder().insertLogs(logs).build(); + } + final Optional revertReason; if (input.isEndOfCurrentList()) { revertReason = Optional.empty(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/VersionMetadata.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/VersionMetadata.java new file mode 100644 index 00000000000..55fc16a99fa --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/VersionMetadata.java @@ -0,0 +1,164 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Path; +import javax.annotation.Nonnull; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.maven.artifact.versioning.ComparableVersion; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class VersionMetadata implements Comparable { + private static final Logger LOG = LoggerFactory.getLogger(VersionMetadata.class); + + /** Represents an unknown Besu version in the version metadata file */ + public static final String BESU_VERSION_UNKNOWN = "UNKNOWN"; + + private static final String METADATA_FILENAME = "VERSION_METADATA.json"; + private static final ObjectMapper MAPPER = new ObjectMapper(); + private final String besuVersion; + + /** + * Get the version of Besu that is running. + * + * @return the version of Besu + */ + public static String getRuntimeVersionString() { + return VersionMetadata.class.getPackage().getImplementationVersion() == null + ? BESU_VERSION_UNKNOWN + : VersionMetadata.class.getPackage().getImplementationVersion(); + } + + public static VersionMetadata getRuntimeVersion() { + return new VersionMetadata(getRuntimeVersionString()); + } + + @JsonCreator + public VersionMetadata(@JsonProperty("besuVersion") final String besuVersion) { + this.besuVersion = besuVersion; + } + + public String getBesuVersion() { + return besuVersion; + } + + public static VersionMetadata lookUpFrom(final Path dataDir) throws IOException { + LOG.info("Lookup version metadata file in data directory: {}", dataDir); + return resolveVersionMetadata(getDefaultMetadataFile(dataDir)); + } + + public void writeToDirectory(final Path dataDir) throws IOException { + MAPPER.writeValue(getDefaultMetadataFile(dataDir), this); + } + + private static File getDefaultMetadataFile(final Path dataDir) { + File metaDataFile = dataDir.resolve(METADATA_FILENAME).toFile(); + + // Create the data dir here if it doesn't exist yet + if (!metaDataFile.getParentFile().exists()) { + LOG.info("Data directory {} does not exist - creating it", dataDir); + metaDataFile.getParentFile().mkdirs(); + } + return metaDataFile; + } + + private static VersionMetadata resolveVersionMetadata(final File metadataFile) + throws IOException { + VersionMetadata versionMetadata; + try { + versionMetadata = MAPPER.readValue(metadataFile, VersionMetadata.class); + LOG.info("Existing version data detected. Besu version {}", versionMetadata.besuVersion); + } catch (FileNotFoundException fnfe) { + versionMetadata = new VersionMetadata(BESU_VERSION_UNKNOWN); + } catch (JsonProcessingException jpe) { + throw new IllegalStateException( + String.format("Invalid metadata file %s", metadataFile.getAbsolutePath()), jpe); + } + return versionMetadata; + } + + /** + * This function is designed to protect a Besu instance from being unintentionally started at a + * version of Besu that might be incompatible with the version that last modified the specified + * data directory. Currently this check is limited to checking that the version is >= the previous + * version, to avoid accidentally running a lower version of Besu and potentially corrupting data, + * but the method could be extended to perform any other version-to-version compatibility checks + * necessary. If the --version-compatibility-protection flag is set to true and the compatibility + * checks pass, the version metadata is updated to the current version of Besu. + */ + public static void versionCompatibilityChecks( + final boolean enforceCompatibilityProtection, final Path dataDir) throws IOException { + final VersionMetadata metadataVersion = VersionMetadata.lookUpFrom(dataDir); + final VersionMetadata runtimeVersion = getRuntimeVersion(); + if (metadataVersion.getBesuVersion().equals(VersionMetadata.BESU_VERSION_UNKNOWN)) { + // The version isn't known, potentially because the file doesn't exist. Write the latest + // version to the metadata file. + LOG.info( + "No version data detected. Writing Besu version {} to metadata file", + runtimeVersion.getBesuVersion()); + runtimeVersion.writeToDirectory(dataDir); + } else { + // Check the runtime version against the most recent version as recorded in the version + // metadata file + final int versionComparison = runtimeVersion.compareTo(metadataVersion); + if (versionComparison == 0) { + // Versions match - no-op + } else if (versionComparison < 0) { + if (!enforceCompatibilityProtection) { + LOG.warn( + "Besu version {} is lower than version {} that last started. Allowing startup because --version-compatibility-protection has been disabled.", + runtimeVersion.getBesuVersion(), + metadataVersion.getBesuVersion()); + // We've allowed startup at an older version of Besu. Since the version in the metadata + // file records the latest version of + // Besu to write to the database we'll update the metadata version to this + // downgraded-version. + runtimeVersion.writeToDirectory(dataDir); + } else { + final String message = + "Besu version " + + runtimeVersion.getBesuVersion() + + " is lower than version " + + metadataVersion.getBesuVersion() + + " that last started. Remove --version-compatibility-protection option to allow Besu to start at " + + " the lower version (warning - this may have unrecoverable effects on the database)."; + LOG.error(message); + throw new IllegalStateException(message); + } + } else { + LOG.info( + "Besu version {} is higher than version {} that last started. Updating version metadata.", + runtimeVersion.getBesuVersion(), + metadataVersion.getBesuVersion()); + runtimeVersion.writeToDirectory(dataDir); + } + } + } + + @Override + public int compareTo(@Nonnull final VersionMetadata versionMetadata) { + final String thisVersion = this.getBesuVersion().split("-", 2)[0]; + final String metadataVersion = versionMetadata.getBesuVersion().split("-", 2)[0]; + return new ComparableVersion(thisVersion).compareTo(new ComparableVersion(metadataVersion)); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Withdrawal.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Withdrawal.java index 4fe64ec2016..b82eda72481 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Withdrawal.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/Withdrawal.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/WithdrawalRequest.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/WithdrawalRequest.java new file mode 100644 index 00000000000..0e7afc1595b --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/WithdrawalRequest.java @@ -0,0 +1,89 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.datatypes.GWei; +import org.hyperledger.besu.datatypes.PublicKey; +import org.hyperledger.besu.datatypes.RequestType; + +import java.util.Objects; + +public class WithdrawalRequest extends Request + implements org.hyperledger.besu.plugin.data.WithdrawalRequest { + + private final Address sourceAddress; + private final BLSPublicKey validatorPubkey; + private final GWei amount; + + public WithdrawalRequest( + final Address sourceAddress, final BLSPublicKey validatorPubkey, final GWei amount) { + this.sourceAddress = sourceAddress; + this.validatorPubkey = validatorPubkey; + this.amount = amount; + } + + @Override + public RequestType getType() { + return RequestType.WITHDRAWAL; + } + + @Override + public Address getSourceAddress() { + return sourceAddress; + } + + @Override + public PublicKey getValidatorPubkey() { + return validatorPubkey; + } + + @Override + public GWei getAmount() { + return amount; + } + + @Override + public String toString() { + return "WithdrawalRequest{" + + "sourceAddress=" + + sourceAddress + + " validatorPubkey=" + + validatorPubkey + + " amount=" + + amount + + '}'; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final WithdrawalRequest that = (WithdrawalRequest) o; + return Objects.equals(sourceAddress, that.sourceAddress) + && Objects.equals(validatorPubkey, that.validatorPubkey) + && Objects.equals(amount, that.amount); + } + + @Override + public int hashCode() { + return Objects.hash(sourceAddress, validatorPubkey, amount); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/AccessListTransactionDecoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/AccessListTransactionDecoder.java index e9fbf828174..b5a06042406 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/AccessListTransactionDecoder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/AccessListTransactionDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/AccessListTransactionEncoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/AccessListTransactionEncoder.java index b539ee931cc..54bf8cc41ec 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/AccessListTransactionEncoder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/AccessListTransactionEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/BlobPooledTransactionDecoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/BlobPooledTransactionDecoder.java index bac3d472aca..dd786ff7238 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/BlobPooledTransactionDecoder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/BlobPooledTransactionDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/BlobPooledTransactionEncoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/BlobPooledTransactionEncoder.java index b47a406102d..44981a30ad5 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/BlobPooledTransactionEncoder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/BlobPooledTransactionEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/BlobTransactionDecoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/BlobTransactionDecoder.java index 13240930b94..fdb467887da 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/BlobTransactionDecoder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/BlobTransactionDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/BlobTransactionEncoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/BlobTransactionEncoder.java index 73b7593d9b8..2b918b4f260 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/BlobTransactionEncoder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/BlobTransactionEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationEncoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationEncoder.java new file mode 100644 index 00000000000..4cedf93adc2 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationEncoder.java @@ -0,0 +1,87 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core.encoding; + +import static org.hyperledger.besu.ethereum.core.encoding.AccessListTransactionEncoder.writeAccessList; +import static org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder.writeSignatureAndRecoveryId; + +import org.hyperledger.besu.datatypes.CodeDelegation; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.rlp.RLPOutput; + +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; + +public class CodeDelegationEncoder { + + private CodeDelegationEncoder() { + // private constructor + } + + public static void encodeCodeDelegationInner( + final List payloads, final RLPOutput rlpOutput) { + rlpOutput.startList(); + payloads.forEach(payload -> encodeSingleCodeDelegation(payload, rlpOutput)); + rlpOutput.endList(); + } + + public static void encodeSingleCodeDelegationWithoutSignature( + final CodeDelegation payload, final RLPOutput rlpOutput) { + rlpOutput.startList(); + encodeAuthorizationDetails(payload, rlpOutput); + rlpOutput.endList(); + } + + public static void encodeSingleCodeDelegation( + final CodeDelegation payload, final RLPOutput rlpOutput) { + rlpOutput.startList(); + encodeAuthorizationDetails(payload, rlpOutput); + rlpOutput.writeIntScalar(payload.signature().getRecId()); + rlpOutput.writeBigIntegerScalar(payload.signature().getR()); + rlpOutput.writeBigIntegerScalar(payload.signature().getS()); + rlpOutput.endList(); + } + + private static void encodeAuthorizationDetails( + final CodeDelegation payload, final RLPOutput rlpOutput) { + rlpOutput.writeBigIntegerScalar(payload.chainId()); + rlpOutput.writeBytes(payload.address().copy()); + rlpOutput.writeLongScalar(payload.nonce()); + } + + public static void encode(final Transaction transaction, final RLPOutput out) { + out.startList(); + out.writeBigIntegerScalar(transaction.getChainId().orElseThrow()); + out.writeLongScalar(transaction.getNonce()); + out.writeUInt256Scalar(transaction.getMaxPriorityFeePerGas().orElseThrow()); + out.writeUInt256Scalar(transaction.getMaxFeePerGas().orElseThrow()); + out.writeLongScalar(transaction.getGasLimit()); + out.writeBytes(transaction.getTo().map(Bytes::copy).orElse(Bytes.EMPTY)); + out.writeUInt256Scalar(transaction.getValue()); + out.writeBytes(transaction.getPayload()); + writeAccessList(out, transaction.getAccessList()); + encodeCodeDelegationInner( + transaction + .getCodeDelegationList() + .orElseThrow( + () -> + new IllegalStateException( + "Developer error: the transaction should be guaranteed to have a code delegation authorizations here")), + out); + writeSignatureAndRecoveryId(transaction, out); + out.endList(); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationTransactionDecoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationTransactionDecoder.java new file mode 100644 index 00000000000..8961431c9cd --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationTransactionDecoder.java @@ -0,0 +1,95 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core.encoding; + +import org.hyperledger.besu.crypto.SECPSignature; +import org.hyperledger.besu.crypto.SignatureAlgorithm; +import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; +import org.hyperledger.besu.datatypes.AccessListEntry; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.CodeDelegation; +import org.hyperledger.besu.datatypes.TransactionType; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.rlp.RLPInput; + +import java.math.BigInteger; +import java.util.function.Supplier; + +import com.google.common.base.Suppliers; + +public class CodeDelegationTransactionDecoder { + private static final Supplier SIGNATURE_ALGORITHM = + Suppliers.memoize(SignatureAlgorithmFactory::getInstance); + + private CodeDelegationTransactionDecoder() { + // private constructor + } + + public static Transaction decode(final RLPInput input) { + input.enterList(); + final BigInteger chainId = input.readBigIntegerScalar(); + final Transaction.Builder builder = + Transaction.builder() + .type(TransactionType.DELEGATE_CODE) + .chainId(chainId) + .nonce(input.readLongScalar()) + .maxPriorityFeePerGas(Wei.of(input.readUInt256Scalar())) + .maxFeePerGas(Wei.of(input.readUInt256Scalar())) + .gasLimit(input.readLongScalar()) + .to(input.readBytes(v -> v.isEmpty() ? null : Address.wrap(v))) + .value(Wei.of(input.readUInt256Scalar())) + .payload(input.readBytes()) + .accessList( + input.readList( + accessListEntryRLPInput -> { + accessListEntryRLPInput.enterList(); + final AccessListEntry accessListEntry = + new AccessListEntry( + Address.wrap(accessListEntryRLPInput.readBytes()), + accessListEntryRLPInput.readList(RLPInput::readBytes32)); + accessListEntryRLPInput.leaveList(); + return accessListEntry; + })) + .codeDelegations(input.readList(CodeDelegationTransactionDecoder::decodeInnerPayload)); + + final byte recId = (byte) input.readUnsignedByteScalar(); + final BigInteger r = input.readUInt256Scalar().toUnsignedBigInteger(); + final BigInteger s = input.readUInt256Scalar().toUnsignedBigInteger(); + + input.leaveList(); + + return builder.signature(SIGNATURE_ALGORITHM.get().createSignature(r, s, recId)).build(); + } + + public static CodeDelegation decodeInnerPayload(final RLPInput input) { + input.enterList(); + + final BigInteger chainId = input.readBigIntegerScalar(); + final Address address = Address.wrap(input.readBytes()); + final long nonce = input.readLongScalar(); + + final byte yParity = (byte) input.readUnsignedByteScalar(); + final BigInteger r = input.readUInt256Scalar().toUnsignedBigInteger(); + final BigInteger s = input.readUInt256Scalar().toUnsignedBigInteger(); + + input.leaveList(); + + final SECPSignature signature = SIGNATURE_ALGORITHM.get().createSignature(r, s, yParity); + + return new org.hyperledger.besu.ethereum.core.CodeDelegation( + chainId, address, nonce, signature); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/ConsolidationRequestDecoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/ConsolidationRequestDecoder.java new file mode 100644 index 00000000000..29be31f46de --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/ConsolidationRequestDecoder.java @@ -0,0 +1,40 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core.encoding; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.ethereum.core.ConsolidationRequest; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.rlp.RLPInput; + +import org.apache.tuweni.bytes.Bytes; + +public class ConsolidationRequestDecoder { + + public static ConsolidationRequest decode(final RLPInput rlpInput) { + rlpInput.enterList(); + final Address sourceAddress = Address.readFrom(rlpInput); + final BLSPublicKey sourcePublicKey = BLSPublicKey.readFrom(rlpInput); + final BLSPublicKey targetPublicKey = BLSPublicKey.readFrom(rlpInput); + rlpInput.leaveList(); + + return new ConsolidationRequest(sourceAddress, sourcePublicKey, targetPublicKey); + } + + public static ConsolidationRequest decodeOpaqueBytes(final Bytes input) { + return decode(RLP.input(input)); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/ConsolidationRequestEncoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/ConsolidationRequestEncoder.java new file mode 100644 index 00000000000..9551f7b9740 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/ConsolidationRequestEncoder.java @@ -0,0 +1,60 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core.encoding; + +import org.hyperledger.besu.datatypes.RequestType; +import org.hyperledger.besu.ethereum.core.ConsolidationRequest; +import org.hyperledger.besu.ethereum.core.Request; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.rlp.RLPOutput; + +import org.apache.tuweni.bytes.Bytes; + +public class ConsolidationRequestEncoder { + + /** + * Encodes a Request into RLP format if it is a ConsolidationRequest. + * + * @param request The Request to encode, which must be a ConsolidationRequest. + * @param rlpOutput The RLPOutput to write the encoded data to. + * @throws IllegalArgumentException if the provided request is not a ConsolidationRequest. + */ + public static void encode(final Request request, final RLPOutput rlpOutput) { + if (!request.getType().equals(RequestType.CONSOLIDATION)) { + throw new IllegalArgumentException( + "The provided request is not of type ConsolidationRequest."); + } + encodeConsolidationRequest((ConsolidationRequest) request, rlpOutput); + } + + /** + * Encodes the details of a ConsolidationRequest into RLP format. + * + * @param consolidationRequest The ConsolidationRequest to encode. + * @param rlpOutput The RLPOutput to write the encoded data to. + */ + private static void encodeConsolidationRequest( + final ConsolidationRequest consolidationRequest, final RLPOutput rlpOutput) { + rlpOutput.startList(); + rlpOutput.writeBytes(consolidationRequest.getSourceAddress()); + rlpOutput.writeBytes(consolidationRequest.getSourcePubkey()); + rlpOutput.writeBytes(consolidationRequest.getTargetPubkey()); + rlpOutput.endList(); + } + + public static Bytes encodeOpaqueBytes(final Request consolidationRequest) { + return RLP.encode(rlpOutput -> encode(consolidationRequest, rlpOutput)); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/DepositDecoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/DepositDecoder.java deleted file mode 100644 index cb8410d3d28..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/DepositDecoder.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright contributors to Hyperledger Besu - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.core.encoding; - -import org.hyperledger.besu.datatypes.BLSPublicKey; -import org.hyperledger.besu.datatypes.BLSSignature; -import org.hyperledger.besu.datatypes.GWei; -import org.hyperledger.besu.ethereum.core.Deposit; -import org.hyperledger.besu.ethereum.core.DepositContract; -import org.hyperledger.besu.ethereum.rlp.RLP; -import org.hyperledger.besu.ethereum.rlp.RLPInput; -import org.hyperledger.besu.evm.log.Log; - -import java.nio.ByteOrder; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt64; -import org.web3j.tx.Contract; - -public class DepositDecoder { - - public static Deposit decode(final RLPInput rlpInput) { - rlpInput.enterList(); - final BLSPublicKey publicKey = BLSPublicKey.readFrom(rlpInput); - final Bytes32 depositWithdrawalCredential = Bytes32.wrap(rlpInput.readBytes()); - final GWei amount = GWei.of(rlpInput.readUInt64Scalar()); - final BLSSignature signature = BLSSignature.readFrom(rlpInput); - final UInt64 index = UInt64.valueOf(rlpInput.readBigIntegerScalar()); - rlpInput.leaveList(); - - return new Deposit(publicKey, depositWithdrawalCredential, amount, signature, index); - } - - public static Deposit decodeFromLog(final Log log) { - Contract.EventValuesWithLog eventValues = DepositContract.staticExtractDepositEventWithLog(log); - final byte[] rawPublicKey = (byte[]) eventValues.getNonIndexedValues().get(0).getValue(); - final byte[] rawWithdrawalCredential = - (byte[]) eventValues.getNonIndexedValues().get(1).getValue(); - final byte[] rawAmount = (byte[]) eventValues.getNonIndexedValues().get(2).getValue(); - final byte[] rawSignature = (byte[]) eventValues.getNonIndexedValues().get(3).getValue(); - final byte[] rawIndex = (byte[]) eventValues.getNonIndexedValues().get(4).getValue(); - - return new Deposit( - BLSPublicKey.wrap(Bytes.wrap(rawPublicKey)), - Bytes32.wrap(Bytes.wrap(rawWithdrawalCredential)), - GWei.of( - Bytes.wrap(rawAmount) - .toLong( - ByteOrder.LITTLE_ENDIAN)), // Amount is little endian as per Deposit Contract - BLSSignature.wrap(Bytes.wrap(rawSignature)), - UInt64.valueOf(Bytes.wrap(rawIndex).reverse().toLong())); - } - - public static Deposit decodeOpaqueBytes(final Bytes input) { - return decode(RLP.input(input)); - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/DepositEncoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/DepositEncoder.java deleted file mode 100644 index 15f825e0757..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/DepositEncoder.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright contributors to Hyperledger Besu - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.core.encoding; - -import org.hyperledger.besu.ethereum.core.Deposit; -import org.hyperledger.besu.ethereum.rlp.RLP; -import org.hyperledger.besu.ethereum.rlp.RLPOutput; - -import org.apache.tuweni.bytes.Bytes; - -public class DepositEncoder { - - public static void encode(final Deposit deposit, final RLPOutput rlpOutput) { - rlpOutput.startList(); - rlpOutput.writeBytes(deposit.getPubkey()); - rlpOutput.writeBytes(deposit.getWithdrawalCredentials()); - rlpOutput.writeUInt64Scalar(deposit.getAmount()); - rlpOutput.writeBytes(deposit.getSignature()); - rlpOutput.writeUInt64Scalar(deposit.getIndex()); - rlpOutput.endList(); - } - - public static Bytes encodeOpaqueBytes(final Deposit deposit) { - return RLP.encode(rlpOutput -> encode(deposit, rlpOutput)); - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/DepositRequestDecoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/DepositRequestDecoder.java new file mode 100644 index 00000000000..85b56e1a573 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/DepositRequestDecoder.java @@ -0,0 +1,70 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core.encoding; + +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.datatypes.BLSSignature; +import org.hyperledger.besu.datatypes.GWei; +import org.hyperledger.besu.ethereum.core.DepositContract; +import org.hyperledger.besu.ethereum.core.DepositRequest; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.rlp.RLPInput; +import org.hyperledger.besu.evm.log.Log; + +import java.nio.ByteOrder; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt64; +import org.web3j.tx.Contract; + +public class DepositRequestDecoder { + + public static DepositRequest decode(final RLPInput rlpInput) { + rlpInput.enterList(); + final BLSPublicKey publicKey = BLSPublicKey.readFrom(rlpInput); + final Bytes32 depositWithdrawalCredential = Bytes32.wrap(rlpInput.readBytes()); + final GWei amount = GWei.of(rlpInput.readUInt64Scalar()); + final BLSSignature signature = BLSSignature.readFrom(rlpInput); + final UInt64 index = UInt64.valueOf(rlpInput.readBigIntegerScalar()); + rlpInput.leaveList(); + + return new DepositRequest(publicKey, depositWithdrawalCredential, amount, signature, index); + } + + public static DepositRequest decodeFromLog(final Log log) { + Contract.EventValuesWithLog eventValues = DepositContract.staticExtractDepositEventWithLog(log); + final byte[] rawPublicKey = (byte[]) eventValues.getNonIndexedValues().get(0).getValue(); + final byte[] rawWithdrawalCredential = + (byte[]) eventValues.getNonIndexedValues().get(1).getValue(); + final byte[] rawAmount = (byte[]) eventValues.getNonIndexedValues().get(2).getValue(); + final byte[] rawSignature = (byte[]) eventValues.getNonIndexedValues().get(3).getValue(); + final byte[] rawIndex = (byte[]) eventValues.getNonIndexedValues().get(4).getValue(); + + return new DepositRequest( + BLSPublicKey.wrap(Bytes.wrap(rawPublicKey)), + Bytes32.wrap(Bytes.wrap(rawWithdrawalCredential)), + GWei.of( + Bytes.wrap(rawAmount) + .toLong( + ByteOrder.LITTLE_ENDIAN)), // Amount is little endian as per Deposit Contract + BLSSignature.wrap(Bytes.wrap(rawSignature)), + UInt64.valueOf(Bytes.wrap(rawIndex).reverse().toLong())); + } + + public static DepositRequest decodeOpaqueBytes(final Bytes input) { + return decode(RLP.input(input)); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/DepositRequestEncoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/DepositRequestEncoder.java new file mode 100644 index 00000000000..a99b0b761ce --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/DepositRequestEncoder.java @@ -0,0 +1,42 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core.encoding; + +import org.hyperledger.besu.ethereum.core.DepositRequest; +import org.hyperledger.besu.ethereum.core.Request; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.rlp.RLPOutput; + +import org.apache.tuweni.bytes.Bytes; + +public class DepositRequestEncoder { + + public static void encode(final Request request, final RLPOutput rlpOutput) { + if (!(request instanceof DepositRequest depositRequest)) { + throw new IllegalArgumentException("The provided request is not of type deposit."); + } + rlpOutput.startList(); + rlpOutput.writeBytes(depositRequest.getPubkey()); + rlpOutput.writeBytes(depositRequest.getWithdrawalCredentials()); + rlpOutput.writeUInt64Scalar(depositRequest.getAmount()); + rlpOutput.writeBytes(depositRequest.getSignature()); + rlpOutput.writeUInt64Scalar(depositRequest.getIndex()); + rlpOutput.endList(); + } + + public static Bytes encodeOpaqueBytes(final Request deposit) { + return RLP.encode(rlpOutput -> encode(deposit, rlpOutput)); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/EIP1559TransactionDecoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/EIP1559TransactionDecoder.java index 6e419a9ed22..b99f37468e5 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/EIP1559TransactionDecoder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/EIP1559TransactionDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/EIP1559TransactionEncoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/EIP1559TransactionEncoder.java index 3d061beff84..bc8e41da820 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/EIP1559TransactionEncoder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/EIP1559TransactionEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/EncodingContext.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/EncodingContext.java index b94289aeb9f..96104d6e2a3 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/EncodingContext.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/EncodingContext.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/FrontierTransactionDecoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/FrontierTransactionDecoder.java index 8cfd8b93b51..e218eb0ac4d 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/FrontierTransactionDecoder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/FrontierTransactionDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/FrontierTransactionEncoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/FrontierTransactionEncoder.java index 0c5f61f5454..039c3871040 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/FrontierTransactionEncoder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/FrontierTransactionEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/RequestDecoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/RequestDecoder.java new file mode 100644 index 00000000000..64191e104b2 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/RequestDecoder.java @@ -0,0 +1,106 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core.encoding; + +import org.hyperledger.besu.datatypes.RequestType; +import org.hyperledger.besu.ethereum.core.Request; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.rlp.RLPInput; + +import java.util.Optional; + +import com.google.common.collect.ImmutableMap; +import org.apache.tuweni.bytes.Bytes; + +/** + * Decodes a request from its RLP encoded form. + * + *

This class provides functionality to decode requests based on their type. + */ +public class RequestDecoder { + + @FunctionalInterface + interface Decoder { + Request decode(RLPInput input); + } + + private static final ImmutableMap DECODERS = + ImmutableMap.of( + RequestType.WITHDRAWAL, + WithdrawalRequestDecoder::decode, + RequestType.DEPOSIT, + DepositRequestDecoder::decode, + RequestType.CONSOLIDATION, + ConsolidationRequestDecoder::decode); + + /** + * Decodes a request from its RLP encoded bytes. + * + *

This method first determines the type of the request and then decodes the request data + * according to the request type. + * + * @param rlpInput The RLP encoded request. + * @return The decoded Request object. + * @throws IllegalArgumentException if the request type is unsupported or invalid. + */ + public static Request decode(final RLPInput rlpInput) { + final Bytes requestBytes = rlpInput.readBytes(); + return getRequestType(requestBytes) + .map(type -> decodeRequest(requestBytes, type)) + .orElseThrow(() -> new IllegalArgumentException("Unsupported or invalid request type")); + } + + /** + * Decodes the request data according to the request type. + * + * @param requestBytes The bytes representing the request, including the request type byte. + * @param requestType The type of the request to decode. + * @return The decoded Request. + * @throws IllegalStateException if no decoder is found for the specified request type. + */ + private static Request decodeRequest(final Bytes requestBytes, final RequestType requestType) { + // Skip the first byte which is the request type + RLPInput requestInput = RLP.input(requestBytes.slice(1)); + Decoder decoder = + Optional.ofNullable(DECODERS.get(requestType)) + .orElseThrow( + () -> + new IllegalStateException( + "Decoder not found for request type: " + requestType)); + return decoder.decode(requestInput); + } + + /** + * Extracts the request type from the given bytes. + * + * @param bytes The bytes from which to extract the request type. + * @return An Optional containing the RequestType if it could be determined, or an empty Optional + * otherwise. + */ + private static Optional getRequestType(final Bytes bytes) { + try { + byte typeByte = bytes.get(0); + return Optional.of(RequestType.of(typeByte)); + } catch (IllegalArgumentException ex) { + return Optional.empty(); + } + } + + public static Request decodeOpaqueBytes(final Bytes input) { + + RequestType type = getRequestType(input).orElseThrow(); + return decodeRequest(input.slice(1), type); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/RequestEncoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/RequestEncoder.java new file mode 100644 index 00000000000..3c61a77ac03 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/RequestEncoder.java @@ -0,0 +1,83 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core.encoding; + +import static com.google.common.base.Preconditions.checkNotNull; + +import org.hyperledger.besu.datatypes.RequestType; +import org.hyperledger.besu.ethereum.core.Request; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.rlp.RLPOutput; + +import com.google.common.collect.ImmutableMap; +import org.apache.tuweni.bytes.Bytes; + +/** Encodes Request objects into RLP format. */ +public class RequestEncoder { + + @FunctionalInterface + interface Encoder { + void encode(Request request, RLPOutput output); + } + + private static final ImmutableMap ENCODERS = + ImmutableMap.of( + RequestType.WITHDRAWAL, + WithdrawalRequestEncoder::encode, + RequestType.DEPOSIT, + DepositRequestEncoder::encode, + RequestType.CONSOLIDATION, + ConsolidationRequestEncoder::encode); + + /** + * Encodes a Request into the provided RLPOutput. + * + * @param request The Request to encode. + * @param rlpOutput The RLPOutput to write the encoded data to. + */ + public static void encode(final Request request, final RLPOutput rlpOutput) { + final RequestEncoder.Encoder encoder = getEncoder(request.getType()); + Bytes requestBytes = RLP.encode(out -> encoder.encode(request, out)); + rlpOutput.writeBytes( + Bytes.concatenate(Bytes.of(request.getType().getSerializedType()), requestBytes)); + } + + /** + * Encodes a Request into a Bytes object representing the RLP-encoded data. + * + * @param request The Request to encode. + * @return The RLP-encoded data as a Bytes object. + */ + public static Bytes encodeOpaqueBytes(final Request request) { + final RequestEncoder.Encoder encoder = getEncoder(request.getType()); + final BytesValueRLPOutput out = new BytesValueRLPOutput(); + out.writeByte(request.getType().getSerializedType()); + encoder.encode(request, out); + return out.encoded(); + } + + /** + * Retrieves the encoder for the specified RequestType. + * + * @param requestType The type of the request. + * @return The encoder for the specified type. + * @throws NullPointerException if no encoder is found for the specified type. + */ + private static RequestEncoder.Encoder getEncoder(final RequestType requestType) { + return checkNotNull( + ENCODERS.get(requestType), "Encoder not found for request type: %s", requestType); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionDecoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionDecoder.java index fe6687cdb5c..7261aecbeac 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionDecoder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionDecoder.java @@ -40,7 +40,9 @@ interface Decoder { TransactionType.EIP1559, EIP1559TransactionDecoder::decode, TransactionType.BLOB, - BlobTransactionDecoder::decode); + BlobTransactionDecoder::decode, + TransactionType.DELEGATE_CODE, + CodeDelegationTransactionDecoder::decode); private static final ImmutableMap POOLED_TRANSACTION_DECODERS = ImmutableMap.of(TransactionType.BLOB, BlobPooledTransactionDecoder::decode); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionEncoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionEncoder.java index 20fe75885e4..26bad56c6da 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionEncoder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/TransactionEncoder.java @@ -39,7 +39,9 @@ interface Encoder { TransactionType.EIP1559, EIP1559TransactionEncoder::encode, TransactionType.BLOB, - BlobTransactionEncoder::encode); + BlobTransactionEncoder::encode, + TransactionType.DELEGATE_CODE, + CodeDelegationEncoder::encode); private static final ImmutableMap POOLED_TRANSACTION_ENCODERS = ImmutableMap.of(TransactionType.BLOB, BlobPooledTransactionEncoder::encode); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalDecoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalDecoder.java index 77ec6ad0200..c2e98215b73 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalDecoder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalEncoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalEncoder.java index 9dcac154501..27d6b805cb8 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalEncoder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalRequestDecoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalRequestDecoder.java new file mode 100644 index 00000000000..031209284e8 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalRequestDecoder.java @@ -0,0 +1,41 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core.encoding; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.datatypes.GWei; +import org.hyperledger.besu.ethereum.core.WithdrawalRequest; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.rlp.RLPInput; + +import org.apache.tuweni.bytes.Bytes; + +public class WithdrawalRequestDecoder { + + public static WithdrawalRequest decode(final RLPInput rlpInput) { + rlpInput.enterList(); + final Address sourceAddress = Address.readFrom(rlpInput); + final BLSPublicKey validatorPubkey = BLSPublicKey.readFrom(rlpInput); + final GWei amount = GWei.of(rlpInput.readUInt64Scalar()); + rlpInput.leaveList(); + + return new WithdrawalRequest(sourceAddress, validatorPubkey, amount); + } + + public static WithdrawalRequest decodeOpaqueBytes(final Bytes input) { + return decode(RLP.input(input)); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalRequestEncoder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalRequestEncoder.java new file mode 100644 index 00000000000..c9fdb37fcea --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalRequestEncoder.java @@ -0,0 +1,59 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core.encoding; + +import org.hyperledger.besu.datatypes.RequestType; +import org.hyperledger.besu.ethereum.core.Request; +import org.hyperledger.besu.ethereum.core.WithdrawalRequest; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.rlp.RLPOutput; + +import org.apache.tuweni.bytes.Bytes; + +public class WithdrawalRequestEncoder { + + /** + * Encodes a Request into RLP format if it is a WithdrawalRequest. + * + * @param request The Request to encode, which must be a WithdrawalRequest. + * @param rlpOutput The RLPOutput to write the encoded data to. + * @throws IllegalArgumentException if the provided request is not a WithdrawalRequest. + */ + public static void encode(final Request request, final RLPOutput rlpOutput) { + if (!request.getType().equals(RequestType.WITHDRAWAL)) { + throw new IllegalArgumentException("The provided request is not of type WithdrawalRequest."); + } + encodeWithdrawalRequest((WithdrawalRequest) request, rlpOutput); + } + + /** + * Encodes the details of a WithdrawalRequest into RLP format. + * + * @param withdrawalRequest The WithdrawalRequest to encode. + * @param rlpOutput The RLPOutput to write the encoded data to. + */ + private static void encodeWithdrawalRequest( + final WithdrawalRequest withdrawalRequest, final RLPOutput rlpOutput) { + rlpOutput.startList(); + rlpOutput.writeBytes(withdrawalRequest.getSourceAddress()); + rlpOutput.writeBytes(withdrawalRequest.getValidatorPubkey()); + rlpOutput.writeUInt64Scalar(withdrawalRequest.getAmount()); + rlpOutput.endList(); + } + + public static Bytes encodeOpaqueBytes(final Request withdrawalRequest) { + return RLP.encode(rlpOutput -> encode(withdrawalRequest, rlpOutput)); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/feemarket/BaseFee.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/feemarket/BaseFee.java deleted file mode 100644 index 010d94a174c..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/feemarket/BaseFee.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.core.feemarket; - -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket; - -public class BaseFee { - private final Wei value; - private final Wei delta; - private final Wei minNextValue; - private final Wei maxNextValue; - - public BaseFee(final BaseFeeMarket feeMarket, final Wei value) { - this.value = value; - this.delta = value.divide(feeMarket.getBasefeeMaxChangeDenominator()); - this.minNextValue = value.subtract(delta); - this.maxNextValue = value.add(delta); - } - - public Wei getValue() { - return value; - } - - public Wei getDelta() { - return delta; - } - - public Wei getMinNextValue() { - return minNextValue; - } - - public Wei getMaxNextValue() { - return maxNextValue; - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/feemarket/TransactionPriceCalculator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/feemarket/TransactionPriceCalculator.java index 35e2acc1464..774063987c4 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/feemarket/TransactionPriceCalculator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/feemarket/TransactionPriceCalculator.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,11 +14,9 @@ */ package org.hyperledger.besu.ethereum.core.feemarket; -import org.hyperledger.besu.datatypes.BlobGas; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.Transaction; -import java.math.BigInteger; import java.util.Optional; @FunctionalInterface @@ -44,36 +42,4 @@ static TransactionPriceCalculator eip1559() { return price; }; } - - // curiously named as in the spec - // https://eips.ethereum.org/EIPS/eip-4844#cryptographic-helpers - private static BigInteger fakeExponential( - final BigInteger factor, final BigInteger numerator, final BigInteger denominator) { - int i = 1; - BigInteger output = BigInteger.ZERO; - BigInteger numeratorAccumulator = factor.multiply(denominator); - while (numeratorAccumulator.signum() > 0) { - output = output.add(numeratorAccumulator); - numeratorAccumulator = - (numeratorAccumulator.multiply(numerator)) - .divide(denominator.multiply(BigInteger.valueOf(i))); - ++i; - } - return output.divide(denominator); - } - - static TransactionPriceCalculator blobGas( - final int minBlobGasPrice, - final int blobGasPriceUpdateFraction, - final BlobGas excessBlobGas) { - return ((transaction, baseFee) -> { - final var blobGasPrice = - Wei.of( - fakeExponential( - BigInteger.valueOf(minBlobGasPrice), - excessBlobGas.toBigInteger(), - BigInteger.valueOf(blobGasPriceUpdateFraction))); - return blobGasPrice; - }); - } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/json/GasDeserializer.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/json/GasDeserializer.java new file mode 100644 index 00000000000..dd0b8a3f6ca --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/json/GasDeserializer.java @@ -0,0 +1,45 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core.json; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import org.apache.tuweni.units.bigints.UInt64; +import org.apache.tuweni.units.bigints.UInt64s; + +public class GasDeserializer extends StdDeserializer { + private static final UInt64 GAS_MAX_VALUE = UInt64.valueOf(Long.MAX_VALUE); + + public GasDeserializer() { + this(null); + } + + public GasDeserializer(final Class vc) { + super(vc); + } + + @Override + public Long deserialize(final JsonParser jsonparser, final DeserializationContext context) + throws IOException { + final var uint64 = + UInt64.fromHexString(jsonparser.getCodec().readValue(jsonparser, String.class)); + // we can safely cap the value to Long.MAX_VALUE since gas is not expected to reach these value + // anytime soon if ever + return UInt64s.min(uint64, GAS_MAX_VALUE).toLong(); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/json/HexLongDeserializer.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/json/HexLongDeserializer.java deleted file mode 100644 index 79d81905059..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/json/HexLongDeserializer.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.core.json; - -import java.io.IOException; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; - -public class HexLongDeserializer extends StdDeserializer { - public HexLongDeserializer() { - this(null); - } - - public HexLongDeserializer(final Class vc) { - super(vc); - } - - @Override - public Long deserialize(final JsonParser jsonparser, final DeserializationContext context) - throws IOException { - return Long.decode(jsonparser.getCodec().readValue(jsonparser, String.class)); - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/plugins/PluginConfiguration.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/plugins/PluginConfiguration.java new file mode 100644 index 00000000000..ebc99f647af --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/plugins/PluginConfiguration.java @@ -0,0 +1,88 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core.plugins; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.List; + +public class PluginConfiguration { + private final List requestedPlugins; + private final Path pluginsDir; + private final boolean externalPluginsEnabled; + + public PluginConfiguration( + final List requestedPlugins, + final Path pluginsDir, + final boolean externalPluginsEnabled) { + this.requestedPlugins = requestedPlugins; + this.pluginsDir = pluginsDir; + this.externalPluginsEnabled = externalPluginsEnabled; + } + + public List getRequestedPlugins() { + return requestedPlugins == null + ? Collections.emptyList() + : requestedPlugins.stream().map(PluginInfo::name).toList(); + } + + public Path getPluginsDir() { + return pluginsDir; + } + + public boolean isExternalPluginsEnabled() { + return externalPluginsEnabled; + } + + public static Path defaultPluginsDir() { + String pluginsDirProperty = System.getProperty("besu.plugins.dir"); + return pluginsDirProperty == null + ? Paths.get(System.getProperty("besu.home", "."), "plugins") + : Paths.get(pluginsDirProperty); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private List requestedPlugins; + private Path pluginsDir; + private boolean externalPluginsEnabled = true; + + public Builder requestedPlugins(final List requestedPlugins) { + this.requestedPlugins = requestedPlugins; + return this; + } + + public Builder pluginsDir(final Path pluginsDir) { + this.pluginsDir = pluginsDir; + return this; + } + + public Builder externalPluginsEnabled(final boolean externalPluginsEnabled) { + this.externalPluginsEnabled = externalPluginsEnabled; + return this; + } + + public PluginConfiguration build() { + if (pluginsDir == null) { + pluginsDir = PluginConfiguration.defaultPluginsDir(); + } + return new PluginConfiguration(requestedPlugins, pluginsDir, externalPluginsEnabled); + } + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/plugins/PluginInfo.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/plugins/PluginInfo.java new file mode 100644 index 00000000000..2e3a771bafd --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/core/plugins/PluginInfo.java @@ -0,0 +1,37 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core.plugins; + +/** Represents information about a plugin, including its name. */ +public final class PluginInfo { + private final String name; + + /** + * Constructs a new PluginInfo instance with the specified name. + * + * @param name The name of the plugin. Cannot be null or empty. + * @throws IllegalArgumentException if the name is null or empty. + */ + public PluginInfo(final String name) { + if (name == null || name.isBlank()) { + throw new IllegalArgumentException("Plugin name cannot be null or empty."); + } + this.name = name; + } + + public String name() { + return name; + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/debug/TraceFrame.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/debug/TraceFrame.java index 33118bef8e5..cd6a9c3584f 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/debug/TraceFrame.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/debug/TraceFrame.java @@ -34,6 +34,7 @@ public class TraceFrame { private final int pc; private final Optional opcode; + private final int opcodeNumber; private final long gasRemaining; private final OptionalLong gasCost; private final long gasRefund; @@ -63,6 +64,7 @@ public class TraceFrame { public TraceFrame( final int pc, final Optional opcode, + final int opcodeNumber, final long gasRemaining, final OptionalLong gasCost, final long gasRefund, @@ -86,6 +88,7 @@ public TraceFrame( final Optional maybeUpdatedStorage) { this.pc = pc; this.opcode = opcode; + this.opcodeNumber = opcodeNumber; this.gasRemaining = gasRemaining; this.gasCost = gasCost; this.gasRefund = gasRefund; @@ -118,6 +121,10 @@ public String getOpcode() { return opcode.orElse(""); } + public int getOpcodeNumber() { + return opcodeNumber; + } + public long getGasRemaining() { return gasRemaining; } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/difficulty/fixed/FixedDifficultyProtocolSchedule.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/difficulty/fixed/FixedDifficultyProtocolSchedule.java index a79f8f7e642..b86b2b0de22 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/difficulty/fixed/FixedDifficultyProtocolSchedule.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/difficulty/fixed/FixedDifficultyProtocolSchedule.java @@ -15,11 +15,14 @@ package org.hyperledger.besu.ethereum.difficulty.fixed; import org.hyperledger.besu.config.GenesisConfigOptions; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.plugin.services.MetricsSystem; /** A ProtocolSchedule which behaves similarly to MainNet, but with a much reduced difficulty. */ public class FixedDifficultyProtocolSchedule { @@ -28,7 +31,11 @@ public static ProtocolSchedule create( final GenesisConfigOptions config, final PrivacyParameters privacyParameters, final boolean isRevertReasonEnabled, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final BadBlockManager badBlockManager, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return new ProtocolScheduleBuilder( config, ProtocolSpecAdapters.create( @@ -37,19 +44,48 @@ public static ProtocolSchedule create( builder.difficultyCalculator(FixedDifficultyCalculators.calculator(config))), privacyParameters, isRevertReasonEnabled, - evmConfiguration) + evmConfiguration, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem) .createProtocolSchedule(); } public static ProtocolSchedule create( final GenesisConfigOptions config, final boolean isRevertReasonEnabled, - final EvmConfiguration evmConfiguration) { - return create(config, PrivacyParameters.DEFAULT, isRevertReasonEnabled, evmConfiguration); + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final BadBlockManager badBlockManager, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { + return create( + config, + PrivacyParameters.DEFAULT, + isRevertReasonEnabled, + evmConfiguration, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem); } public static ProtocolSchedule create( - final GenesisConfigOptions config, final EvmConfiguration evmConfiguration) { - return create(config, PrivacyParameters.DEFAULT, false, evmConfiguration); + final GenesisConfigOptions config, + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final BadBlockManager badBlockManager, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { + return create( + config, + PrivacyParameters.DEFAULT, + false, + evmConfiguration, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/forkid/ForkIdManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/forkid/ForkIdManager.java index bae8cfc339e..407e5149d67 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/forkid/ForkIdManager.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/forkid/ForkIdManager.java @@ -58,7 +58,11 @@ public ForkIdManager( checkNotNull(blockchain); checkNotNull(blockNumberForks); this.chainHeadSupplier = blockchain::getChainHeadHeader; - this.genesisHash = blockchain.getGenesisBlock().getHash(); + try { + this.genesisHash = blockchain.getGenesisBlock().getHash(); + } catch (Exception e) { + throw new RuntimeException(e); + } this.blockNumbersForkIds = new ArrayList<>(); this.timestampsForkIds = new ArrayList<>(); this.legacyEth64 = legacyEth64; @@ -75,8 +79,7 @@ public ForkIdManager( .sorted() .collect(Collectors.toUnmodifiableList()); final List allForkNumbers = - Stream.concat(blockNumberForks.stream(), timestampForks.stream()) - .collect(Collectors.toList()); + Stream.concat(blockNumberForks.stream(), timestampForks.stream()).toList(); this.forkNext = createForkIds(); this.allForkIds = Stream.concat(blockNumbersForkIds.stream(), timestampsForkIds.stream()) @@ -89,7 +92,7 @@ public ForkIdManager( public ForkId getForkIdForChainHead() { if (legacyEth64) { return blockNumbersForkIds.isEmpty() - ? null + ? new ForkId(genesisHashCrc, 0) : blockNumbersForkIds.get(blockNumbersForkIds.size() - 1); } final BlockHeader header = chainHeadSupplier.get(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java index 103fb3782d1..480a1d8d7ab 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessor.java @@ -15,26 +15,30 @@ package org.hyperledger.besu.ethereum.mainnet; import static org.hyperledger.besu.ethereum.mainnet.feemarket.ExcessBlobGasCalculator.calculateExcessBlobGasForParent; +import static org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.BlockProcessingOutputs; import org.hyperledger.besu.ethereum.BlockProcessingResult; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.Deposit; import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.core.Request; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.core.Withdrawal; +import org.hyperledger.besu.ethereum.mainnet.requests.ProcessRequestContext; +import org.hyperledger.besu.ethereum.mainnet.requests.RequestProcessorCoordinator; import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.trie.MerkleTrieException; -import org.hyperledger.besu.ethereum.vm.BlockHashLookup; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldState; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup; +import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator; +import org.hyperledger.besu.evm.operation.BlockHashOperation; import org.hyperledger.besu.evm.tracing.OperationTracer; import org.hyperledger.besu.evm.worldstate.WorldState; import org.hyperledger.besu.evm.worldstate.WorldUpdater; @@ -97,78 +101,96 @@ public BlockProcessingResult processBlock( final List transactions, final List ommers, final Optional> maybeWithdrawals, - final Optional> maybeDeposits, final PrivateMetadataUpdater privateMetadataUpdater) { final List receipts = new ArrayList<>(); long currentGasUsed = 0; + long currentBlobGasUsed = 0; final ProtocolSpec protocolSpec = protocolSchedule.getByBlockHeader(blockHeader); - if (blockHeader.getParentBeaconBlockRoot().isPresent()) { - final WorldUpdater updater = worldState.updater(); - ParentBeaconBlockRootHelper.storeParentBeaconBlockRoot( - updater, blockHeader.getTimestamp(), blockHeader.getParentBeaconBlockRoot().get()); - } - - for (final Transaction transaction : transactions) { + protocolSpec.getBlockHashProcessor().processBlockHashes(blockchain, worldState, blockHeader); + final BlockHashLookup blockHashLookup = new CachingBlockHashLookup(blockHeader, blockchain); + + final Address miningBeneficiary = miningBeneficiaryCalculator.calculateBeneficiary(blockHeader); + + Optional maybeParentHeader = + blockchain.getBlockHeader(blockHeader.getParentHash()); + + Wei blobGasPrice = + maybeParentHeader + .map( + parentHeader -> + protocolSpec + .getFeeMarket() + .blobGasPricePerGas( + calculateExcessBlobGasForParent(protocolSpec, parentHeader))) + .orElse(Wei.ZERO); + + final Optional preProcessingContext = + runBlockPreProcessing( + worldState, + privateMetadataUpdater, + blockHeader, + transactions, + miningBeneficiary, + blockHashLookup, + blobGasPrice); + + for (int i = 0; i < transactions.size(); i++) { + final Transaction transaction = transactions.get(i); if (!hasAvailableBlockBudget(blockHeader, transaction, currentGasUsed)) { return new BlockProcessingResult(Optional.empty(), "provided gas insufficient"); } + final WorldUpdater blockUpdater = worldState.updater(); - final WorldUpdater worldStateUpdater = worldState.updater(); - - final BlockHashLookup blockHashLookup = new CachingBlockHashLookup(blockHeader, blockchain); - final Address miningBeneficiary = - miningBeneficiaryCalculator.calculateBeneficiary(blockHeader); - - Optional maybeParentHeader = - blockchain.getBlockHeader(blockHeader.getParentHash()); - - Wei blobGasPrice = - maybeParentHeader - .map( - (parentHeader) -> - protocolSpec - .getFeeMarket() - .blobGasPricePerGas( - calculateExcessBlobGasForParent(protocolSpec, parentHeader))) - .orElse(Wei.ZERO); - - final TransactionProcessingResult result = - transactionProcessor.processTransaction( - blockchain, - worldStateUpdater, + TransactionProcessingResult transactionProcessingResult = + getTransactionProcessingResult( + preProcessingContext, + worldState, + blockUpdater, + privateMetadataUpdater, blockHeader, - transaction, + blobGasPrice, miningBeneficiary, - OperationTracer.NO_TRACING, - blockHashLookup, - true, - TransactionValidationParams.processingBlock(), - privateMetadataUpdater, - blobGasPrice); - if (result.isInvalid()) { + transaction, + i, + blockHashLookup); + if (transactionProcessingResult.isInvalid()) { String errorMessage = MessageFormat.format( "Block processing error: transaction invalid {0}. Block {1} Transaction {2}", - result.getValidationResult().getErrorMessage(), + transactionProcessingResult.getValidationResult().getErrorMessage(), blockHeader.getHash().toHexString(), transaction.getHash().toHexString()); LOG.info(errorMessage); if (worldState instanceof BonsaiWorldState) { - ((BonsaiWorldStateUpdateAccumulator) worldStateUpdater).reset(); + ((BonsaiWorldStateUpdateAccumulator) blockUpdater).reset(); } return new BlockProcessingResult(Optional.empty(), errorMessage); } - worldStateUpdater.commit(); - currentGasUsed += transaction.getGasLimit() - result.getGasRemaining(); + blockUpdater.commit(); + + currentGasUsed += transaction.getGasLimit() - transactionProcessingResult.getGasRemaining(); + if (transaction.getVersionedHashes().isPresent()) { + currentBlobGasUsed += + (transaction.getVersionedHashes().get().size() * CancunGasCalculator.BLOB_GAS_PER_BLOB); + } + final TransactionReceipt transactionReceipt = transactionReceiptFactory.create( - transaction.getType(), result, worldState, currentGasUsed); + transaction.getType(), transactionProcessingResult, worldState, currentGasUsed); receipts.add(transactionReceipt); } - + if (blockHeader.getBlobGasUsed().isPresent() + && currentBlobGasUsed != blockHeader.getBlobGasUsed().get()) { + String errorMessage = + String.format( + "block did not consume expected blob gas: header %d, transactions %d", + blockHeader.getBlobGasUsed().get(), currentBlobGasUsed); + LOG.error(errorMessage); + return new BlockProcessingResult(Optional.empty(), errorMessage); + } final Optional maybeWithdrawalsProcessor = protocolSpec.getWithdrawalsProcessor(); if (maybeWithdrawalsProcessor.isPresent() && maybeWithdrawals.isPresent()) { @@ -182,6 +204,23 @@ public BlockProcessingResult processBlock( } } + // EIP-7685: process EL requests + final Optional requestProcessor = + protocolSpec.getRequestProcessorCoordinator(); + Optional> maybeRequests = Optional.empty(); + if (requestProcessor.isPresent()) { + ProcessRequestContext context = + new ProcessRequestContext( + blockHeader, + worldState, + protocolSpec, + receipts, + blockHashLookup, + OperationTracer.NO_TRACING); + + maybeRequests = requestProcessor.get().process(context); + } + if (!rewardCoinbase(worldState, blockHeader, ommers, skipZeroBlockRewards)) { // no need to log, rewardCoinbase logs the error. if (worldState instanceof BonsaiWorldState) { @@ -203,7 +242,43 @@ public BlockProcessingResult processBlock( return new BlockProcessingResult(Optional.empty(), e); } - return new BlockProcessingResult(Optional.of(new BlockProcessingOutputs(worldState, receipts))); + return new BlockProcessingResult( + Optional.of(new BlockProcessingOutputs(worldState, receipts, maybeRequests))); + } + + protected Optional runBlockPreProcessing( + final MutableWorldState worldState, + final PrivateMetadataUpdater privateMetadataUpdater, + final BlockHeader blockHeader, + final List transactions, + final Address miningBeneficiary, + final BlockHashOperation.BlockHashLookup blockHashLookup, + final Wei blobGasPrice) { + return Optional.empty(); + } + + protected TransactionProcessingResult getTransactionProcessingResult( + final Optional preProcessingContext, + final MutableWorldState worldState, + final WorldUpdater blockUpdater, + final PrivateMetadataUpdater privateMetadataUpdater, + final BlockHeader blockHeader, + final Wei blobGasPrice, + final Address miningBeneficiary, + final Transaction transaction, + final int location, + final BlockHashLookup blockHashLookup) { + return transactionProcessor.processTransaction( + blockUpdater, + blockHeader, + transaction, + miningBeneficiary, + OperationTracer.NO_TRACING, + blockHashLookup, + true, + TransactionValidationParams.processingBlock(), + privateMetadataUpdater, + blobGasPrice); } protected boolean hasAvailableBlockBudget( @@ -232,4 +307,7 @@ abstract boolean rewardCoinbase( final BlockHeader header, final List ommers, final boolean skipZeroBlockRewards); + + public interface PreprocessingContext {} + ; } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractGasLimitSpecification.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractGasLimitSpecification.java index 70e80527b78..91cf73c3df8 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractGasLimitSpecification.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/AbstractGasLimitSpecification.java @@ -19,7 +19,6 @@ /** Specification for the block gasLimit. */ public abstract class AbstractGasLimitSpecification { - public static final long DEFAULT_MAX_CONSTANT_ADMUSTMENT_INCREMENT = 1024L; public static final long DEFAULT_MIN_GAS_LIMIT = 5000L; public static final long DEFAULT_MAX_GAS_LIMIT = Long.MAX_VALUE; @@ -41,6 +40,13 @@ public static long deltaBound(final long currentGasLimit) { return Long.divideUnsigned(currentGasLimit, GAS_LIMIT_BOUND_DIVISOR); } + /** + * Verify that the target gas limit is within the allowed bounds. + * + * @param targetGasLimit the target gas limit to validate + * @return true if within bounds + */ + @SuppressWarnings("ComparisonOutOfRange") public static boolean isValidTargetGasLimit(final long targetGasLimit) { return DEFAULT_MIN_GAS_LIMIT <= targetGasLimit && DEFAULT_MAX_GAS_LIMIT >= targetGasLimit; } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BaseFeeBlockBodyValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BaseFeeBlockBodyValidator.java index 62629dde665..0e288185cfc 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BaseFeeBlockBodyValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BaseFeeBlockBodyValidator.java @@ -18,6 +18,7 @@ import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockBody; +import org.hyperledger.besu.ethereum.core.Request; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.core.feemarket.TransactionPriceCalculator; @@ -41,9 +42,10 @@ public boolean validateBodyLight( final ProtocolContext context, final Block block, final List receipts, + final Optional> requests, final HeaderValidationMode ommerValidationMode) { - return super.validateBodyLight(context, block, receipts, ommerValidationMode) + return super.validateBodyLight(context, block, receipts, requests, ommerValidationMode) && validateTransactionGasPrice(block); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BlockBodyValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BlockBodyValidator.java index e063c584fd2..62b4985f5ca 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BlockBodyValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BlockBodyValidator.java @@ -17,9 +17,11 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.Request; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import java.util.List; +import java.util.Optional; /** Validates block bodies. */ public interface BlockBodyValidator { @@ -39,6 +41,7 @@ boolean validateBody( ProtocolContext context, Block block, List receipts, + Optional> requests, Hash worldStateRootHash, final HeaderValidationMode ommerValidationMode); @@ -55,5 +58,6 @@ boolean validateBodyLight( ProtocolContext context, Block block, List receipts, + final Optional> requests, final HeaderValidationMode ommerValidationMode); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BlockHeaderValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BlockHeaderValidator.java index 56b1d4f0dbf..fe1537905da 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BlockHeaderValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BlockHeaderValidator.java @@ -43,20 +43,22 @@ public boolean validateHeader( final HeaderValidationMode mode) { return switch (mode) { case NONE -> true; - case LIGHT_DETACHED_ONLY -> applyRules( - header, - parent, - protocolContext, - rule -> rule.includeInLightValidation() && rule.isDetachedSupported()); - case LIGHT_SKIP_DETACHED -> applyRules( - header, - parent, - protocolContext, - rule -> rule.includeInLightValidation() && !rule.isDetachedSupported()); + case LIGHT_DETACHED_ONLY -> + applyRules( + header, + parent, + protocolContext, + rule -> rule.includeInLightValidation() && rule.isDetachedSupported()); + case LIGHT_SKIP_DETACHED -> + applyRules( + header, + parent, + protocolContext, + rule -> rule.includeInLightValidation() && !rule.isDetachedSupported()); case LIGHT -> applyRules(header, parent, protocolContext, Rule::includeInLightValidation); case DETACHED_ONLY -> applyRules(header, parent, protocolContext, Rule::isDetachedSupported); - case SKIP_DETACHED -> applyRules( - header, parent, protocolContext, rule -> !rule.isDetachedSupported()); + case SKIP_DETACHED -> + applyRules(header, parent, protocolContext, rule -> !rule.isDetachedSupported()); case FULL -> applyRules(header, parent, protocolContext, rule -> true); }; } @@ -84,7 +86,10 @@ private boolean applyRules( rule -> { boolean worked = rule.validate(header, parent, protocolContext); if (!worked) { - LOG.debug("{} rule failed", rule.innerRuleClass().getCanonicalName()); + String canonicalName = rule.innerRuleClass().getCanonicalName(); + LOG.debug( + "{} rule failed", + canonicalName == null ? rule.innerRuleClass().getName() : canonicalName); } return worked; }); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BlockProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BlockProcessor.java index 060bce5bd77..d9f2e7874cc 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BlockProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BlockProcessor.java @@ -19,7 +19,6 @@ import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.Deposit; import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; @@ -83,7 +82,6 @@ default BlockProcessingResult processBlock( block.getBody().getTransactions(), block.getBody().getOmmers(), block.getBody().getWithdrawals(), - block.getBody().getDeposits(), null); } @@ -104,14 +102,7 @@ default BlockProcessingResult processBlock( final List transactions, final List ommers) { return processBlock( - blockchain, - worldState, - blockHeader, - transactions, - ommers, - Optional.empty(), - Optional.empty(), - null); + blockchain, worldState, blockHeader, transactions, ommers, Optional.empty(), null); } /** @@ -123,7 +114,6 @@ default BlockProcessingResult processBlock( * @param transactions the transactions in the block * @param ommers the block ommers * @param withdrawals the withdrawals for the block - * @param deposits the deposits for the block * @param privateMetadataUpdater the updater used to update the private metadata for the block * @return the block processing result */ @@ -134,7 +124,6 @@ BlockProcessingResult processBlock( List transactions, List ommers, Optional> withdrawals, - Optional> deposits, PrivateMetadataUpdater privateMetadataUpdater); /** diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BodyValidation.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BodyValidation.java index f806c8d4cd1..15e8c3c804f 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BodyValidation.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/BodyValidation.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -18,12 +18,12 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.Deposit; +import org.hyperledger.besu.ethereum.core.Request; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.core.Withdrawal; -import org.hyperledger.besu.ethereum.core.encoding.DepositEncoder; import org.hyperledger.besu.ethereum.core.encoding.EncodingContext; +import org.hyperledger.besu.ethereum.core.encoding.RequestEncoder; import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder; import org.hyperledger.besu.ethereum.core.encoding.WithdrawalEncoder; import org.hyperledger.besu.ethereum.rlp.RLP; @@ -89,17 +89,15 @@ public static Hash withdrawalsRoot(final List withdrawals) { } /** - * Generates the deposits root for a list of deposits + * Generates the requests root for a list of requests * - * @param deposits the transactions - * @return the transaction root + * @param requests list of request + * @return the requests root */ - public static Hash depositsRoot(final List deposits) { + public static Hash requestsRoot(final List requests) { final MerkleTrie trie = trie(); - - IntStream.range(0, deposits.size()) - .forEach(i -> trie.put(indexKey(i), DepositEncoder.encodeOpaqueBytes(deposits.get(i)))); - + IntStream.range(0, requests.size()) + .forEach(i -> trie.put(indexKey(i), RequestEncoder.encodeOpaqueBytes(requests.get(i)))); return Hash.wrap(trie.getRootHash()); } @@ -118,7 +116,8 @@ public static Hash receiptsRoot(final List receipts) { trie.put( indexKey(i), RLP.encode( - rlpOutput -> receipts.get(i).writeToForReceiptTrie(rlpOutput, false)))); + rlpOutput -> + receipts.get(i).writeToForReceiptTrie(rlpOutput, false, false)))); return Hash.wrap(trie.getRootHash()); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ClassicProtocolSpecs.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ClassicProtocolSpecs.java index 5d193168b31..47c1747dc95 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ClassicProtocolSpecs.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ClassicProtocolSpecs.java @@ -22,10 +22,10 @@ import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.core.feemarket.CoinbaseFeePriceCalculator; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; +import org.hyperledger.besu.evm.ClassicEVMs; import org.hyperledger.besu.evm.MainnetEVMs; import org.hyperledger.besu.evm.contractvalidation.MaxCodeSizeRule; import org.hyperledger.besu.evm.contractvalidation.PrefixCodeRule; -import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.BerlinGasCalculator; import org.hyperledger.besu.evm.gascalculator.DieHardGasCalculator; import org.hyperledger.besu.evm.gascalculator.IstanbulGasCalculator; @@ -38,12 +38,12 @@ import org.hyperledger.besu.evm.processor.ContractCreationProcessor; import org.hyperledger.besu.evm.processor.MessageCallProcessor; import org.hyperledger.besu.evm.worldstate.WorldState; +import org.hyperledger.besu.plugin.services.MetricsSystem; import java.math.BigInteger; import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.OptionalInt; import java.util.OptionalLong; import java.util.Set; @@ -55,11 +55,11 @@ private ClassicProtocolSpecs() { } public static ProtocolSpecBuilder classicRecoveryInitDefinition( - final OptionalInt contractSizeLimit, - final OptionalInt configStackSizeLimit, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return MainnetProtocolSpecs.homesteadDefinition( - contractSizeLimit, configStackSizeLimit, evmConfiguration) + evmConfiguration, isParallelTxProcessingEnabled, metricsSystem) .blockHeaderValidatorBuilder( feeMarket -> MainnetBlockHeaderValidator.createClassicValidator()) .name("ClassicRecoveryInit"); @@ -67,26 +67,27 @@ public static ProtocolSpecBuilder classicRecoveryInitDefinition( public static ProtocolSpecBuilder tangerineWhistleDefinition( final Optional chainId, - final OptionalInt contractSizeLimit, - final OptionalInt configStackSizeLimit, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return MainnetProtocolSpecs.homesteadDefinition( - contractSizeLimit, configStackSizeLimit, evmConfiguration) + evmConfiguration, isParallelTxProcessingEnabled, metricsSystem) .isReplayProtectionSupported(true) .gasCalculator(TangerineWhistleGasCalculator::new) .transactionValidatorFactoryBuilder( - (gasCalculator, gasLimitCalculator, feeMarket) -> - new TransactionValidatorFactory(gasCalculator, gasLimitCalculator, true, chainId)) + (evm, gasLimitCalculator, feeMarket) -> + new TransactionValidatorFactory( + evm.getGasCalculator(), gasLimitCalculator, true, chainId)) .name("ClassicTangerineWhistle"); } public static ProtocolSpecBuilder dieHardDefinition( final Optional chainId, - final OptionalInt ignoredConfigContractSizeLimit, - final OptionalInt configStackSizeLimit, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return tangerineWhistleDefinition( - chainId, OptionalInt.empty(), configStackSizeLimit, evmConfiguration) + chainId, evmConfiguration, isParallelTxProcessingEnabled, metricsSystem) .gasCalculator(DieHardGasCalculator::new) .difficultyCalculator(ClassicDifficultyCalculators.DIFFICULTY_BOMB_PAUSED) .name("DieHard"); @@ -94,11 +95,12 @@ public static ProtocolSpecBuilder dieHardDefinition( public static ProtocolSpecBuilder gothamDefinition( final Optional chainId, - final OptionalInt contractSizeLimit, - final OptionalInt configStackSizeLimit, final OptionalLong ecip1017EraRounds, - final EvmConfiguration evmConfiguration) { - return dieHardDefinition(chainId, contractSizeLimit, configStackSizeLimit, evmConfiguration) + final EvmConfiguration evmConfiguration, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { + return dieHardDefinition( + chainId, evmConfiguration, isParallelTxProcessingEnabled, metricsSystem) .blockReward(MAX_BLOCK_REWARD) .difficultyCalculator(ClassicDifficultyCalculators.DIFFICULTY_BOMB_DELAYED) .blockProcessorBuilder( @@ -121,35 +123,37 @@ public static ProtocolSpecBuilder gothamDefinition( public static ProtocolSpecBuilder defuseDifficultyBombDefinition( final Optional chainId, - final OptionalInt contractSizeLimit, - final OptionalInt configStackSizeLimit, final OptionalLong ecip1017EraRounds, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return gothamDefinition( - chainId, contractSizeLimit, configStackSizeLimit, ecip1017EraRounds, evmConfiguration) + chainId, + ecip1017EraRounds, + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem) .difficultyCalculator(ClassicDifficultyCalculators.DIFFICULTY_BOMB_REMOVED) .transactionValidatorFactoryBuilder( - (gasCalculator, gasLimitCalculator, feeMarket) -> - new TransactionValidatorFactory(gasCalculator, gasLimitCalculator, true, chainId)) + (evm, gasLimitCalculator, feeMarket) -> + new TransactionValidatorFactory( + evm.getGasCalculator(), gasLimitCalculator, true, chainId)) .name("DefuseDifficultyBomb"); } public static ProtocolSpecBuilder atlantisDefinition( final Optional chainId, - final OptionalInt configContractSizeLimit, - final OptionalInt configStackSizeLimit, final boolean enableRevertReason, final OptionalLong ecip1017EraRounds, - final EvmConfiguration evmConfiguration) { - final int contractSizeLimit = - configContractSizeLimit.orElse(MainnetProtocolSpecs.SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT); - final int stackSizeLimit = configStackSizeLimit.orElse(MessageFrame.DEFAULT_MAX_STACK_SIZE); + final EvmConfiguration evmConfiguration, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return gothamDefinition( chainId, - configContractSizeLimit, - configStackSizeLimit, ecip1017EraRounds, - evmConfiguration) + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem) .evmBuilder(MainnetEVMs::byzantium) .evmConfiguration(evmConfiguration) .gasCalculator(SpuriousDragonGasCalculator::new) @@ -162,13 +166,9 @@ public static ProtocolSpecBuilder atlantisDefinition( ? ClassicProtocolSpecs::byzantiumTransactionReceiptFactoryWithReasonEnabled : ClassicProtocolSpecs::byzantiumTransactionReceiptFactory) .contractCreationProcessorBuilder( - (gasCalculator, evm) -> + evm -> new ContractCreationProcessor( - gasCalculator, - evm, - true, - Collections.singletonList(MaxCodeSizeRule.of(contractSizeLimit)), - 1)) + evm, true, Collections.singletonList(MaxCodeSizeRule.from(evm)), 1)) .transactionProcessorBuilder( (gasCalculator, feeMarket, @@ -182,7 +182,7 @@ public static ProtocolSpecBuilder atlantisDefinition( messageCallProcessor, true, false, - stackSizeLimit, + evmConfiguration.evmStackSize(), feeMarket, CoinbaseFeePriceCalculator.frontier())) .name("Atlantis"); @@ -190,18 +190,18 @@ public static ProtocolSpecBuilder atlantisDefinition( public static ProtocolSpecBuilder aghartaDefinition( final Optional chainId, - final OptionalInt configContractSizeLimit, - final OptionalInt configStackSizeLimit, final boolean enableRevertReason, final OptionalLong ecip1017EraRounds, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return atlantisDefinition( chainId, - configContractSizeLimit, - configStackSizeLimit, enableRevertReason, ecip1017EraRounds, - evmConfiguration) + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem) .evmBuilder(MainnetEVMs::constantinople) .gasCalculator(PetersburgGasCalculator::new) .evmBuilder(MainnetEVMs::constantinople) @@ -211,18 +211,18 @@ public static ProtocolSpecBuilder aghartaDefinition( public static ProtocolSpecBuilder phoenixDefinition( final Optional chainId, - final OptionalInt configContractSizeLimit, - final OptionalInt configStackSizeLimit, final boolean enableRevertReason, final OptionalLong ecip1017EraRounds, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return aghartaDefinition( chainId, - configContractSizeLimit, - configStackSizeLimit, enableRevertReason, ecip1017EraRounds, - evmConfiguration) + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem) .gasCalculator(IstanbulGasCalculator::new) .evmBuilder( (gasCalculator, evmConfig) -> @@ -234,18 +234,18 @@ public static ProtocolSpecBuilder phoenixDefinition( public static ProtocolSpecBuilder thanosDefinition( final Optional chainId, - final OptionalInt configContractSizeLimit, - final OptionalInt configStackSizeLimit, final boolean enableRevertReason, final OptionalLong ecip1017EraRounds, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return phoenixDefinition( chainId, - configContractSizeLimit, - configStackSizeLimit, enableRevertReason, ecip1017EraRounds, - evmConfiguration) + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem) .blockHeaderValidatorBuilder( feeMarket -> MainnetBlockHeaderValidator.createPgaBlockHeaderValidator( @@ -279,23 +279,23 @@ private static TransactionReceipt byzantiumTransactionReceiptFactoryWithReasonEn public static ProtocolSpecBuilder magnetoDefinition( final Optional chainId, - final OptionalInt configContractSizeLimit, - final OptionalInt configStackSizeLimit, final boolean enableRevertReason, final OptionalLong ecip1017EraRounds, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return thanosDefinition( chainId, - configContractSizeLimit, - configStackSizeLimit, enableRevertReason, ecip1017EraRounds, - evmConfiguration) + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem) .gasCalculator(BerlinGasCalculator::new) .transactionValidatorFactoryBuilder( - (gasCalculator, gasLimitCalculator, feeMarket) -> + (evm, gasLimitCalculator, feeMarket) -> new TransactionValidatorFactory( - gasCalculator, + evm.getGasCalculator(), gasLimitCalculator, true, chainId, @@ -309,53 +309,46 @@ public static ProtocolSpecBuilder magnetoDefinition( public static ProtocolSpecBuilder mystiqueDefinition( final Optional chainId, - final OptionalInt configContractSizeLimit, - final OptionalInt configStackSizeLimit, final boolean enableRevertReason, final OptionalLong ecip1017EraRounds, - final EvmConfiguration evmConfiguration) { - final int contractSizeLimit = - configContractSizeLimit.orElse(MainnetProtocolSpecs.SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT); + final EvmConfiguration evmConfiguration, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return magnetoDefinition( chainId, - configContractSizeLimit, - configStackSizeLimit, enableRevertReason, ecip1017EraRounds, - evmConfiguration) + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem) .gasCalculator(LondonGasCalculator::new) .contractCreationProcessorBuilder( - (gasCalculator, evm) -> + evm -> new ContractCreationProcessor( - gasCalculator, - evm, - true, - List.of(MaxCodeSizeRule.of(contractSizeLimit), PrefixCodeRule.of()), - 1)) + evm, true, List.of(MaxCodeSizeRule.from(evm), PrefixCodeRule.of()), 1)) .name("Mystique"); } public static ProtocolSpecBuilder spiralDefinition( final Optional chainId, - final OptionalInt configContractSizeLimit, - final OptionalInt configStackSizeLimit, final boolean enableRevertReason, final OptionalLong ecip1017EraRounds, - final EvmConfiguration evmConfiguration) { - final int stackSizeLimit = configStackSizeLimit.orElse(MessageFrame.DEFAULT_MAX_STACK_SIZE); + final EvmConfiguration evmConfiguration, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return mystiqueDefinition( chainId, - configContractSizeLimit, - configStackSizeLimit, enableRevertReason, ecip1017EraRounds, - evmConfiguration) + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem) // EIP-3860 .gasCalculator(ShanghaiGasCalculator::new) // EIP-3855 .evmBuilder( (gasCalculator, jdCacheConfig) -> - MainnetEVMs.shanghai( + ClassicEVMs.spiral( gasCalculator, chainId.orElse(BigInteger.ZERO), evmConfiguration)) // EIP-3651 .transactionProcessorBuilder( @@ -371,7 +364,7 @@ public static ProtocolSpecBuilder spiralDefinition( messageCallProcessor, true, true, - stackSizeLimit, + evmConfiguration.evmStackSize(), feeMarket, CoinbaseFeePriceCalculator.frontier())) .name("Spiral"); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CodeDelegationProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CodeDelegationProcessor.java new file mode 100644 index 00000000000..58f66b6afb4 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CodeDelegationProcessor.java @@ -0,0 +1,134 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.core.CodeDelegation; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.worldstate.EVMWorldUpdater; + +import java.math.BigInteger; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CodeDelegationProcessor { + private static final Logger LOG = LoggerFactory.getLogger(CodeDelegationProcessor.class); + + private final Optional maybeChainId; + + public CodeDelegationProcessor(final Optional maybeChainId) { + this.maybeChainId = maybeChainId; + } + + /** + * At the start of executing the transaction, after incrementing the sender’s nonce, for each + * authorization we do the following: + * + *

    + *
  1. Verify the chain id is either 0 or the chain's current ID. + *
  2. `authority = ecrecover(keccak(MAGIC || rlp([chain_id, address, nonce])), y_parity, r, s]` + *
  3. Add `authority` to `accessed_addresses` (as defined in [EIP-2929](./eip-2929.md).) + *
  4. Verify the code of `authority` is either empty or already delegated. + *
  5. Verify the nonce of `authority` is equal to `nonce`. + *
  6. Add `PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST` gas to the global refund counter if + * `authority` exists in the trie. + *
  7. Set the code of `authority` to be `0xef0100 || address`. This is a delegation + * designation. + *
  8. Increase the nonce of `authority` by one. + *
+ * + * @param evmWorldUpdater The world state updater which is aware of code delegation. + * @param transaction The transaction being processed. + * @return The result of the code delegation processing. + */ + public CodeDelegationResult process( + final EVMWorldUpdater evmWorldUpdater, final Transaction transaction) { + final CodeDelegationResult result = new CodeDelegationResult(); + + transaction + .getCodeDelegationList() + .get() + .forEach( + codeDelegation -> + processAuthorization( + evmWorldUpdater, + (org.hyperledger.besu.ethereum.core.CodeDelegation) codeDelegation, + result)); + + return result; + } + + private void processAuthorization( + final EVMWorldUpdater evmWorldUpdater, + final CodeDelegation codeDelegation, + final CodeDelegationResult result) { + LOG.trace("Processing code delegation: {}", codeDelegation); + + if (maybeChainId.isPresent() + && !codeDelegation.chainId().equals(BigInteger.ZERO) + && !maybeChainId.get().equals(codeDelegation.chainId())) { + LOG.trace( + "Invalid chain id for code delegation. Expected: {}, Actual: {}", + maybeChainId.get(), + codeDelegation.chainId()); + return; + } + + final Optional
authorizer = codeDelegation.authorizer(); + if (authorizer.isEmpty()) { + LOG.trace("Invalid signature for code delegation"); + return; + } + + LOG.trace("Set code delegation for authority: {}", authorizer.get()); + + final Optional maybeAuthorityAccount = + Optional.ofNullable(evmWorldUpdater.getAccount(authorizer.get())); + + result.addAccessedDelegatorAddress(authorizer.get()); + + MutableAccount authority; + boolean authorityDoesAlreadyExist = false; + if (maybeAuthorityAccount.isEmpty()) { + authority = evmWorldUpdater.createAccount(authorizer.get()); + } else { + authority = maybeAuthorityAccount.get(); + + if (!evmWorldUpdater.authorizedCodeService().canSetDelegatedCode(authority)) { + return; + } + + authorityDoesAlreadyExist = true; + } + + if (codeDelegation.nonce() != authority.getNonce()) { + LOG.trace( + "Invalid nonce for code delegation. Expected: {}, Actual: {}", + authority.getNonce(), + codeDelegation.nonce()); + return; + } + + if (authorityDoesAlreadyExist) { + result.incremenentAlreadyExistingDelegators(); + } + + evmWorldUpdater.authorizedCodeService().addDelegatedCode(authority, codeDelegation.address()); + authority.incrementNonce(); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CodeDelegationResult.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CodeDelegationResult.java new file mode 100644 index 00000000000..20d75a67577 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/CodeDelegationResult.java @@ -0,0 +1,41 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet; + +import org.hyperledger.besu.collections.trie.BytesTrieSet; +import org.hyperledger.besu.datatypes.Address; + +import java.util.Set; + +public class CodeDelegationResult { + private final Set
accessedDelegatorAddresses = new BytesTrieSet<>(Address.SIZE); + private long alreadyExistingDelegators = 0L; + + public void addAccessedDelegatorAddress(final Address address) { + accessedDelegatorAddresses.add(address); + } + + public void incremenentAlreadyExistingDelegators() { + alreadyExistingDelegators += 1; + } + + public Set
accessedDelegatorAddresses() { + return accessedDelegatorAddresses; + } + + public long alreadyExistingDelegators() { + return alreadyExistingDelegators; + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/DefaultProtocolSchedule.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/DefaultProtocolSchedule.java index 9499a7a8064..4c67466b9b0 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/DefaultProtocolSchedule.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/DefaultProtocolSchedule.java @@ -1,24 +1,22 @@ /* + * Copyright contributors to Hyperledger Besu. * - * * Copyright Hyperledger Besu Contributors. - * * - * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * * the License. You may obtain a copy of the License at - * * - * * http://www.apache.org/licenses/LICENSE-2.0 - * * - * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * * specific language governing permissions and limitations under the License. - * * - * * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.mainnet; import static com.google.common.base.Preconditions.checkArgument; +import org.hyperledger.besu.datatypes.HardforkId; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.PermissionTransactionFilter; import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; @@ -28,6 +26,8 @@ import java.math.BigInteger; import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; import java.util.NavigableSet; import java.util.Optional; import java.util.TreeSet; @@ -42,6 +42,8 @@ public class DefaultProtocolSchedule implements ProtocolSchedule { protected NavigableSet protocolSpecs = new TreeSet<>(Comparator.comparing(ScheduledProtocolSpec::fork).reversed()); + private final Map milestones = new HashMap<>(); + private final Optional chainId; public DefaultProtocolSchedule(final Optional chainId) { @@ -54,16 +56,6 @@ protected DefaultProtocolSchedule(final DefaultProtocolSchedule protocolSchedule this.protocolSpecs = protocolSchedule.protocolSpecs; } - public ScheduledProtocolSpec specScheduledForBlock(final ProcessableBlockHeader blockHeader) { - return protocolSpecs.stream() - .filter(s -> s.isOnOrAfterMilestoneBoundary(blockHeader)) - .findFirst() - .orElseThrow( - () -> - new IllegalStateException( - "No protocol spec found for block " + blockHeader.getNumber())); - } - @Override public ProtocolSpec getByBlockHeader(final ProcessableBlockHeader blockHeader) { checkArgument( @@ -110,6 +102,12 @@ public void putTimestampMilestone(final long timestamp, final ProtocolSpec proto putMilestone(TimestampProtocolSpec.create(timestamp, protocolSpec)); } + @Override + public void setMilestones(final Map milestones) { + this.milestones.clear(); + this.milestones.putAll(milestones); + } + private void putMilestone(final ScheduledProtocolSpec scheduledProtocolSpec) { // Ensure this replaces any existing spec at the same block number. protocolSpecs.remove(scheduledProtocolSpec); @@ -130,6 +128,11 @@ public Optional hardforkFor( .map(ScheduledProtocolSpec::fork); } + @Override + public Optional milestoneFor(final HardforkId hardforkId) { + return Optional.ofNullable(milestones.get(hardforkId)); + } + @Override public void setPermissionTransactionFilter( final PermissionTransactionFilter permissionTransactionFilter) { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/DepositsValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/DepositsValidator.java deleted file mode 100644 index befa2231083..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/DepositsValidator.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright contributors to Hyperledger Besu - * - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.besu.ethereum.mainnet; - -import static com.google.common.base.Preconditions.checkArgument; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.core.Block; -import org.hyperledger.besu.ethereum.core.Deposit; -import org.hyperledger.besu.ethereum.core.TransactionReceipt; -import org.hyperledger.besu.ethereum.core.encoding.DepositDecoder; -import org.hyperledger.besu.evm.log.Log; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public interface DepositsValidator { - - boolean validateDepositParameter(Optional> deposits); - - boolean validateDeposits(Block block, List receipts); - - boolean validateDepositsRoot(Block block); - - class ProhibitedDeposits implements DepositsValidator { - - private static final Logger LOG = LoggerFactory.getLogger(ProhibitedDeposits.class); - - @Override - public boolean validateDepositParameter(final Optional> deposits) { - return deposits.isEmpty(); - } - - @Override - public boolean validateDeposits(final Block block, final List receipts) { - Optional> deposits = block.getBody().getDeposits(); - final boolean isValid = deposits.isEmpty(); - if (!isValid) { - LOG.warn("Deposits must be empty when Deposits are prohibited but were: {}", deposits); - } - return isValid; - } - - @Override - public boolean validateDepositsRoot(final Block block) { - final Optional depositsRoot = block.getHeader().getDepositsRoot(); - if (depositsRoot.isPresent()) { - LOG.warn( - "DepositsRoot must be null when Deposits are prohibited but was: {}", - depositsRoot.get()); - return false; - } - - return true; - } - } - - class AllowedDeposits implements DepositsValidator { - - private static final Logger LOG = LoggerFactory.getLogger(AllowedDeposits.class); - private final Address depositContractAddress; - - public AllowedDeposits(final Address depositContractAddress) { - this.depositContractAddress = depositContractAddress; - } - - @Override - public boolean validateDepositParameter(final Optional> deposits) { - return deposits.isPresent(); - } - - @Override - public boolean validateDeposits(final Block block, final List receipts) { - if (block.getBody().getDeposits().isEmpty()) { - LOG.warn("Deposits must not be empty when Deposits are activated"); - return false; - } - - List actualDeposits = new ArrayList<>(block.getBody().getDeposits().get()); - List expectedDeposits = new ArrayList<>(); - - for (TransactionReceipt receipt : receipts) { - for (Log log : receipt.getLogsList()) { - if (depositContractAddress.equals(log.getLogger())) { - Deposit deposit = DepositDecoder.decodeFromLog(log); - expectedDeposits.add(deposit); - } - } - } - - boolean isValid = actualDeposits.equals(expectedDeposits); - - if (!isValid) { - LOG.warn( - "Deposits validation failed. Deposits from block body do not match deposits from logs. Block hash: {}", - block.getHash()); - LOG.debug( - "Deposits from logs: {}, deposits from block body: {}", - expectedDeposits, - actualDeposits); - } - - return isValid; - } - - @Override - public boolean validateDepositsRoot(final Block block) { - checkArgument(block.getBody().getDeposits().isPresent(), "Block body must contain deposits"); - final Optional depositsRoot = block.getHeader().getDepositsRoot(); - if (depositsRoot.isEmpty()) { - LOG.warn("depositsRoot must not be null when Deposits are activated"); - return false; - } - - final List deposits = block.getBody().getDeposits().get(); - final Hash expectedDepositsRoot = BodyValidation.depositsRoot(deposits); - if (!expectedDepositsRoot.equals(depositsRoot.get())) { - LOG.info( - "Invalid block: depositsRoot mismatch (expected={}, actual={})", - expectedDepositsRoot, - depositsRoot.get()); - return false; - } - - return true; - } - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/FrontierTargetingGasLimitCalculator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/FrontierTargetingGasLimitCalculator.java index 49295e39468..ed067731b5a 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/FrontierTargetingGasLimitCalculator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/FrontierTargetingGasLimitCalculator.java @@ -23,16 +23,13 @@ public class FrontierTargetingGasLimitCalculator extends AbstractGasLimitSpecifi implements GasLimitCalculator { private static final Logger LOG = LoggerFactory.getLogger(FrontierTargetingGasLimitCalculator.class); - private final long maxConstantAdjustmentIncrement; public FrontierTargetingGasLimitCalculator() { - this(DEFAULT_MAX_CONSTANT_ADMUSTMENT_INCREMENT, DEFAULT_MIN_GAS_LIMIT, DEFAULT_MAX_GAS_LIMIT); + this(DEFAULT_MIN_GAS_LIMIT, DEFAULT_MAX_GAS_LIMIT); } - public FrontierTargetingGasLimitCalculator( - final long maxConstantAdjustmentIncrement, final long minGasLimit, final long maxGasLimit) { + public FrontierTargetingGasLimitCalculator(final long minGasLimit, final long maxGasLimit) { super(minGasLimit, maxGasLimit); - this.maxConstantAdjustmentIncrement = maxConstantAdjustmentIncrement; } @Override @@ -55,8 +52,7 @@ public long nextGasLimit( } private long adjustAmount(final long currentGasLimit) { - final long maxProportionalAdjustmentLimit = Math.max(deltaBound(currentGasLimit) - 1, 0); - return Math.min(maxConstantAdjustmentIncrement, maxProportionalAdjustmentLimit); + return Math.max(deltaBound(currentGasLimit) - 1, 0); } protected long safeAddAtMost(final long gasLimit) { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/LondonTargetingGasLimitCalculator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/LondonTargetingGasLimitCalculator.java index 6e03a2523ea..3c86f770739 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/LondonTargetingGasLimitCalculator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/LondonTargetingGasLimitCalculator.java @@ -27,21 +27,15 @@ public class LondonTargetingGasLimitCalculator extends FrontierTargetingGasLimit public LondonTargetingGasLimitCalculator( final long londonForkBlock, final BaseFeeMarket feeMarket) { - this( - DEFAULT_MAX_CONSTANT_ADMUSTMENT_INCREMENT, - DEFAULT_MIN_GAS_LIMIT, - DEFAULT_MAX_GAS_LIMIT, - londonForkBlock, - feeMarket); + this(DEFAULT_MIN_GAS_LIMIT, DEFAULT_MAX_GAS_LIMIT, londonForkBlock, feeMarket); } public LondonTargetingGasLimitCalculator( - final long maxConstantAdjustmentIncrement, final long minGasLimit, final long maxGasLimit, final long londonForkBlock, final BaseFeeMarket feeMarket) { - super(maxConstantAdjustmentIncrement, minGasLimit, maxGasLimit); + super(minGasLimit, maxGasLimit); this.londonForkBlock = londonForkBlock; this.feeMarket = feeMarket; } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockBodyValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockBodyValidator.java index abaab920666..3202440dd89 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockBodyValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockBodyValidator.java @@ -19,11 +19,14 @@ import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.Request; import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.mainnet.requests.RequestsValidatorCoordinator; import org.hyperledger.besu.evm.log.LogsBloomFilter; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import org.apache.tuweni.bytes.Bytes32; @@ -48,10 +51,11 @@ public boolean validateBody( final ProtocolContext context, final Block block, final List receipts, + final Optional> requests, final Hash worldStateRootHash, final HeaderValidationMode ommerValidationMode) { - if (!validateBodyLight(context, block, receipts, ommerValidationMode)) { + if (!validateBodyLight(context, block, receipts, requests, ommerValidationMode)) { return false; } @@ -72,6 +76,7 @@ public boolean validateBodyLight( final ProtocolContext context, final Block block, final List receipts, + final Optional> requests, final HeaderValidationMode ommerValidationMode) { final BlockHeader header = block.getHeader(); final BlockBody body = block.getBody(); @@ -104,10 +109,9 @@ public boolean validateBodyLight( return false; } - if (!validateDeposits(block, receipts)) { + if (!validateRequests(block, requests, receipts)) { return false; } - return true; } @@ -309,18 +313,12 @@ private boolean validateWithdrawals(final Block block) { return true; } - private boolean validateDeposits(final Block block, final List receipts) { - final DepositsValidator depositsValidator = - protocolSchedule.getByBlockHeader(block.getHeader()).getDepositsValidator(); - - if (!depositsValidator.validateDeposits(block, receipts)) { - return false; - } - - if (!depositsValidator.validateDepositsRoot(block)) { - return false; - } - - return true; + private boolean validateRequests( + final Block block, + final Optional> requests, + final List receipts) { + final RequestsValidatorCoordinator requestValidator = + protocolSchedule.getByBlockHeader(block.getHeader()).getRequestsValidatorCoordinator(); + return requestValidator.validate(block, requests, receipts); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockImporter.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockImporter.java index 7af94d491de..62b708d33e6 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockImporter.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockImporter.java @@ -65,7 +65,12 @@ public BlockImportResult fastImportBlock( final HeaderValidationMode ommerValidationMode) { if (blockValidator.fastBlockValidation( - context, block, receipts, headerValidationMode, ommerValidationMode)) { + context, + block, + receipts, + block.getBody().getRequests(), + headerValidationMode, + ommerValidationMode)) { context.getBlockchain().appendBlock(block, receipts); return new BlockImportResult(true); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetPrecompiledContractRegistries.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetPrecompiledContractRegistries.java index a70e20cf243..abee2a5dcb4 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetPrecompiledContractRegistries.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetPrecompiledContractRegistries.java @@ -22,6 +22,7 @@ import static org.hyperledger.besu.evm.precompile.MainnetPrecompiledContracts.populateForFrontier; import static org.hyperledger.besu.evm.precompile.MainnetPrecompiledContracts.populateForFutureEIPs; import static org.hyperledger.besu.evm.precompile.MainnetPrecompiledContracts.populateForIstanbul; +import static org.hyperledger.besu.evm.precompile.MainnetPrecompiledContracts.populateForPrague; import org.hyperledger.besu.ethereum.mainnet.precompiles.privacy.FlexiblePrivacyPrecompiledContract; import org.hyperledger.besu.ethereum.mainnet.precompiles.privacy.PrivacyPluginPrecompiledContract; @@ -59,6 +60,13 @@ static PrecompileContractRegistry cancun( return registry; } + static PrecompileContractRegistry prague( + final PrecompiledContractConfiguration precompiledContractConfiguration) { + final PrecompileContractRegistry registry = new PrecompileContractRegistry(); + populateForPrague(registry, precompiledContractConfiguration.getGasCalculator()); + return registry; + } + static PrecompileContractRegistry futureEips( final PrecompiledContractConfiguration precompiledContractConfiguration) { final PrecompileContractRegistry registry = new PrecompileContractRegistry(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSchedule.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSchedule.java index 921a0a16dca..88b6a5ae1be 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSchedule.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSchedule.java @@ -15,10 +15,13 @@ package org.hyperledger.besu.ethereum.mainnet; import org.hyperledger.besu.config.GenesisConfigOptions; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.difficulty.fixed.FixedDifficultyCalculators; import org.hyperledger.besu.ethereum.difficulty.fixed.FixedDifficultyProtocolSchedule; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.plugin.services.MetricsSystem; import java.math.BigInteger; import java.util.function.Function; @@ -36,16 +39,31 @@ public class MainnetProtocolSchedule { * @param privacyParameters the parameters set for private transactions * @param isRevertReasonEnabled whether storing the revert reason is for failed transactions * @param evmConfiguration how to configure the EVMs jumpdest cache + * @param miningParameters the mining parameters + * @param badBlockManager the cache to use to keep invalid blocks + * @param isParallelTxProcessingEnabled indicates whether parallel transaction is enabled + * @param metricsSystem A metricSystem instance to expose metrics in the underlying calls * @return A configured mainnet protocol schedule */ public static ProtocolSchedule fromConfig( final GenesisConfigOptions config, final PrivacyParameters privacyParameters, final boolean isRevertReasonEnabled, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final BadBlockManager badBlockManager, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { if (FixedDifficultyCalculators.isFixedDifficultyInConfig(config)) { return FixedDifficultyProtocolSchedule.create( - config, privacyParameters, isRevertReasonEnabled, evmConfiguration); + config, + privacyParameters, + isRevertReasonEnabled, + evmConfiguration, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem); } return new ProtocolScheduleBuilder( config, @@ -53,7 +71,11 @@ public static ProtocolSchedule fromConfig( ProtocolSpecAdapters.create(0, Function.identity()), privacyParameters, isRevertReasonEnabled, - evmConfiguration) + evmConfiguration, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem) .createProtocolSchedule(); } @@ -64,13 +86,28 @@ public static ProtocolSchedule fromConfig( * starting points * @param isRevertReasonEnabled whether storing the revert reason is for failed transactions * @param evmConfiguration how to configure the EVMs jumpdest cache + * @param miningParameters the mining parameters + * @param badBlockManager the cache to use to keep invalid blocks + * @param isParallelTxProcessingEnabled indicates whether parallel transaction is enabled. * @return A configured mainnet protocol schedule */ public static ProtocolSchedule fromConfig( final GenesisConfigOptions config, final boolean isRevertReasonEnabled, - final EvmConfiguration evmConfiguration) { - return fromConfig(config, PrivacyParameters.DEFAULT, isRevertReasonEnabled, evmConfiguration); + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final BadBlockManager badBlockManager, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { + return fromConfig( + config, + PrivacyParameters.DEFAULT, + isRevertReasonEnabled, + evmConfiguration, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem); } /** @@ -79,11 +116,27 @@ public static ProtocolSchedule fromConfig( * @param config {@link GenesisConfigOptions} containing the config options for the milestone * starting points * @param evmConfiguration size of + * @param miningParameters the mining parameters + * @param badBlockManager the cache to use to keep invalid blocks + * @param isParallelTxProcessingEnabled indicates whether parallel transaction is enabled. * @return A configured mainnet protocol schedule */ public static ProtocolSchedule fromConfig( - final GenesisConfigOptions config, final EvmConfiguration evmConfiguration) { - return fromConfig(config, PrivacyParameters.DEFAULT, false, evmConfiguration); + final GenesisConfigOptions config, + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final BadBlockManager badBlockManager, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { + return fromConfig( + config, + PrivacyParameters.DEFAULT, + false, + evmConfiguration, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem); } /** @@ -91,9 +144,25 @@ public static ProtocolSchedule fromConfig( * * @param config {@link GenesisConfigOptions} containing the config options for the milestone * starting points + * @param miningParameters the mining parameters + * @param badBlockManager the cache to use to keep invalid blocks + * @param isParallelTxProcessingEnabled indicates whether parallel transaction is enabled. * @return A configured mainnet protocol schedule */ - public static ProtocolSchedule fromConfig(final GenesisConfigOptions config) { - return fromConfig(config, PrivacyParameters.DEFAULT, false, EvmConfiguration.DEFAULT); + public static ProtocolSchedule fromConfig( + final GenesisConfigOptions config, + final MiningParameters miningParameters, + final BadBlockManager badBlockManager, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { + return fromConfig( + config, + PrivacyParameters.DEFAULT, + false, + EvmConfiguration.DEFAULT, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecFactory.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecFactory.java index bcf5cdcc0b4..55bfb363baa 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecFactory.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecFactory.java @@ -15,157 +15,224 @@ package org.hyperledger.besu.ethereum.mainnet; import org.hyperledger.besu.config.GenesisConfigOptions; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.plugin.services.MetricsSystem; import java.math.BigInteger; import java.util.Optional; -import java.util.OptionalInt; import java.util.OptionalLong; public class MainnetProtocolSpecFactory { private final Optional chainId; - private final OptionalInt contractSizeLimit; - private final OptionalInt evmStackSize; private final boolean isRevertReasonEnabled; private final OptionalLong ecip1017EraRounds; private final EvmConfiguration evmConfiguration; + private final MiningParameters miningParameters; + private final boolean isParallelTxProcessingEnabled; + private final MetricsSystem metricsSystem; public MainnetProtocolSpecFactory( final Optional chainId, - final OptionalInt contractSizeLimit, - final OptionalInt evmStackSize, final boolean isRevertReasonEnabled, final OptionalLong ecip1017EraRounds, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { this.chainId = chainId; - this.contractSizeLimit = contractSizeLimit; - this.evmStackSize = evmStackSize; this.isRevertReasonEnabled = isRevertReasonEnabled; this.ecip1017EraRounds = ecip1017EraRounds; this.evmConfiguration = evmConfiguration; + this.miningParameters = miningParameters; + this.isParallelTxProcessingEnabled = isParallelTxProcessingEnabled; + this.metricsSystem = metricsSystem; } public ProtocolSpecBuilder frontierDefinition() { return MainnetProtocolSpecs.frontierDefinition( - contractSizeLimit, evmStackSize, evmConfiguration); + evmConfiguration, isParallelTxProcessingEnabled, metricsSystem); } public ProtocolSpecBuilder homesteadDefinition() { return MainnetProtocolSpecs.homesteadDefinition( - contractSizeLimit, evmStackSize, evmConfiguration); + evmConfiguration, isParallelTxProcessingEnabled, metricsSystem); } public ProtocolSpecBuilder daoRecoveryInitDefinition() { return MainnetProtocolSpecs.daoRecoveryInitDefinition( - contractSizeLimit, evmStackSize, evmConfiguration); + evmConfiguration, isParallelTxProcessingEnabled, metricsSystem); } public ProtocolSpecBuilder daoRecoveryTransitionDefinition() { return MainnetProtocolSpecs.daoRecoveryTransitionDefinition( - contractSizeLimit, evmStackSize, evmConfiguration); + evmConfiguration, isParallelTxProcessingEnabled, metricsSystem); } public ProtocolSpecBuilder tangerineWhistleDefinition() { return MainnetProtocolSpecs.tangerineWhistleDefinition( - contractSizeLimit, evmStackSize, evmConfiguration); + evmConfiguration, isParallelTxProcessingEnabled, metricsSystem); } public ProtocolSpecBuilder spuriousDragonDefinition() { return MainnetProtocolSpecs.spuriousDragonDefinition( - chainId, contractSizeLimit, evmStackSize, evmConfiguration); + chainId, evmConfiguration, isParallelTxProcessingEnabled, metricsSystem); } public ProtocolSpecBuilder byzantiumDefinition() { return MainnetProtocolSpecs.byzantiumDefinition( - chainId, contractSizeLimit, evmStackSize, isRevertReasonEnabled, evmConfiguration); + chainId, + isRevertReasonEnabled, + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem); } public ProtocolSpecBuilder constantinopleDefinition() { return MainnetProtocolSpecs.constantinopleDefinition( - chainId, contractSizeLimit, evmStackSize, isRevertReasonEnabled, evmConfiguration); + chainId, + isRevertReasonEnabled, + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem); } public ProtocolSpecBuilder petersburgDefinition() { return MainnetProtocolSpecs.petersburgDefinition( - chainId, contractSizeLimit, evmStackSize, isRevertReasonEnabled, evmConfiguration); + chainId, + isRevertReasonEnabled, + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem); } public ProtocolSpecBuilder istanbulDefinition() { return MainnetProtocolSpecs.istanbulDefinition( - chainId, contractSizeLimit, evmStackSize, isRevertReasonEnabled, evmConfiguration); + chainId, + isRevertReasonEnabled, + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem); } public ProtocolSpecBuilder muirGlacierDefinition() { return MainnetProtocolSpecs.muirGlacierDefinition( - chainId, contractSizeLimit, evmStackSize, isRevertReasonEnabled, evmConfiguration); + chainId, + isRevertReasonEnabled, + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem); } public ProtocolSpecBuilder berlinDefinition() { return MainnetProtocolSpecs.berlinDefinition( - chainId, contractSizeLimit, evmStackSize, isRevertReasonEnabled, evmConfiguration); + chainId, + isRevertReasonEnabled, + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem); } public ProtocolSpecBuilder londonDefinition(final GenesisConfigOptions genesisConfigOptions) { return MainnetProtocolSpecs.londonDefinition( chainId, - contractSizeLimit, - evmStackSize, isRevertReasonEnabled, genesisConfigOptions, - evmConfiguration); + evmConfiguration, + miningParameters, + isParallelTxProcessingEnabled, + metricsSystem); } public ProtocolSpecBuilder arrowGlacierDefinition( final GenesisConfigOptions genesisConfigOptions) { return MainnetProtocolSpecs.arrowGlacierDefinition( chainId, - contractSizeLimit, - evmStackSize, isRevertReasonEnabled, genesisConfigOptions, - evmConfiguration); + evmConfiguration, + miningParameters, + isParallelTxProcessingEnabled, + metricsSystem); } public ProtocolSpecBuilder grayGlacierDefinition( final GenesisConfigOptions genesisConfigOptions) { return MainnetProtocolSpecs.grayGlacierDefinition( chainId, - contractSizeLimit, - evmStackSize, isRevertReasonEnabled, genesisConfigOptions, - evmConfiguration); + evmConfiguration, + miningParameters, + isParallelTxProcessingEnabled, + metricsSystem); } public ProtocolSpecBuilder parisDefinition(final GenesisConfigOptions genesisConfigOptions) { return MainnetProtocolSpecs.parisDefinition( chainId, - contractSizeLimit, - evmStackSize, isRevertReasonEnabled, genesisConfigOptions, - evmConfiguration); + evmConfiguration, + miningParameters, + isParallelTxProcessingEnabled, + metricsSystem); } public ProtocolSpecBuilder shanghaiDefinition(final GenesisConfigOptions genesisConfigOptions) { return MainnetProtocolSpecs.shanghaiDefinition( chainId, - contractSizeLimit, - evmStackSize, isRevertReasonEnabled, genesisConfigOptions, - evmConfiguration); + evmConfiguration, + miningParameters, + isParallelTxProcessingEnabled, + metricsSystem); } public ProtocolSpecBuilder cancunDefinition(final GenesisConfigOptions genesisConfigOptions) { return MainnetProtocolSpecs.cancunDefinition( chainId, - contractSizeLimit, - evmStackSize, isRevertReasonEnabled, genesisConfigOptions, - evmConfiguration); + evmConfiguration, + miningParameters, + isParallelTxProcessingEnabled, + metricsSystem); + } + + public ProtocolSpecBuilder cancunEOFDefinition(final GenesisConfigOptions genesisConfigOptions) { + return MainnetProtocolSpecs.cancunEOFDefinition( + chainId, + isRevertReasonEnabled, + genesisConfigOptions, + evmConfiguration, + miningParameters, + isParallelTxProcessingEnabled, + metricsSystem); + } + + public ProtocolSpecBuilder pragueDefinition(final GenesisConfigOptions genesisConfigOptions) { + return MainnetProtocolSpecs.pragueDefinition( + chainId, + isRevertReasonEnabled, + genesisConfigOptions, + evmConfiguration, + miningParameters, + isParallelTxProcessingEnabled, + metricsSystem); + } + + public ProtocolSpecBuilder pragueEOFDefinition(final GenesisConfigOptions genesisConfigOptions) { + return MainnetProtocolSpecs.pragueEOFDefinition( + chainId, + isRevertReasonEnabled, + genesisConfigOptions, + evmConfiguration, + miningParameters, + isParallelTxProcessingEnabled, + metricsSystem); } /** @@ -182,11 +249,12 @@ public ProtocolSpecBuilder cancunDefinition(final GenesisConfigOptions genesisCo public ProtocolSpecBuilder futureEipsDefinition(final GenesisConfigOptions genesisConfigOptions) { return MainnetProtocolSpecs.futureEipsDefinition( chainId, - contractSizeLimit, - evmStackSize, isRevertReasonEnabled, genesisConfigOptions, - evmConfiguration); + evmConfiguration, + miningParameters, + isParallelTxProcessingEnabled, + metricsSystem); } /** @@ -203,11 +271,12 @@ public ProtocolSpecBuilder experimentalEipsDefinition( final GenesisConfigOptions genesisConfigOptions) { return MainnetProtocolSpecs.experimentalEipsDefinition( chainId, - contractSizeLimit, - evmStackSize, isRevertReasonEnabled, genesisConfigOptions, - evmConfiguration); + evmConfiguration, + miningParameters, + isParallelTxProcessingEnabled, + metricsSystem); } //////////////////////////////////////////////////////////////////////////////////////////////// @@ -215,86 +284,86 @@ public ProtocolSpecBuilder experimentalEipsDefinition( // Classic Protocol Specs public ProtocolSpecBuilder dieHardDefinition() { return ClassicProtocolSpecs.dieHardDefinition( - chainId, contractSizeLimit, evmStackSize, evmConfiguration); + chainId, evmConfiguration, isParallelTxProcessingEnabled, metricsSystem); } public ProtocolSpecBuilder gothamDefinition() { return ClassicProtocolSpecs.gothamDefinition( - chainId, contractSizeLimit, evmStackSize, ecip1017EraRounds, evmConfiguration); + chainId, ecip1017EraRounds, evmConfiguration, isParallelTxProcessingEnabled, metricsSystem); } public ProtocolSpecBuilder defuseDifficultyBombDefinition() { return ClassicProtocolSpecs.defuseDifficultyBombDefinition( - chainId, contractSizeLimit, evmStackSize, ecip1017EraRounds, evmConfiguration); + chainId, ecip1017EraRounds, evmConfiguration, isParallelTxProcessingEnabled, metricsSystem); } public ProtocolSpecBuilder atlantisDefinition() { return ClassicProtocolSpecs.atlantisDefinition( chainId, - contractSizeLimit, - evmStackSize, isRevertReasonEnabled, ecip1017EraRounds, - evmConfiguration); + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem); } public ProtocolSpecBuilder aghartaDefinition() { return ClassicProtocolSpecs.aghartaDefinition( chainId, - contractSizeLimit, - evmStackSize, isRevertReasonEnabled, ecip1017EraRounds, - evmConfiguration); + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem); } public ProtocolSpecBuilder phoenixDefinition() { return ClassicProtocolSpecs.phoenixDefinition( chainId, - contractSizeLimit, - evmStackSize, isRevertReasonEnabled, ecip1017EraRounds, - evmConfiguration); + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem); } public ProtocolSpecBuilder thanosDefinition() { return ClassicProtocolSpecs.thanosDefinition( chainId, - contractSizeLimit, - evmStackSize, isRevertReasonEnabled, ecip1017EraRounds, - evmConfiguration); + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem); } public ProtocolSpecBuilder magnetoDefinition() { return ClassicProtocolSpecs.magnetoDefinition( chainId, - contractSizeLimit, - evmStackSize, isRevertReasonEnabled, ecip1017EraRounds, - evmConfiguration); + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem); } public ProtocolSpecBuilder mystiqueDefinition() { return ClassicProtocolSpecs.mystiqueDefinition( chainId, - contractSizeLimit, - evmStackSize, isRevertReasonEnabled, ecip1017EraRounds, - evmConfiguration); + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem); } public ProtocolSpecBuilder spiralDefinition() { return ClassicProtocolSpecs.spiralDefinition( chainId, - contractSizeLimit, - evmStackSize, isRevertReasonEnabled, ecip1017EraRounds, - evmConfiguration); + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java index e477bf6f75e..e6d7a88cbdf 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolSpecs.java @@ -14,6 +14,11 @@ */ package org.hyperledger.besu.ethereum.mainnet; +import static org.hyperledger.besu.ethereum.mainnet.requests.DepositRequestProcessor.DEFAULT_DEPOSIT_CONTRACT_ADDRESS; +import static org.hyperledger.besu.ethereum.mainnet.requests.MainnetRequestsValidator.pragueRequestsProcessors; +import static org.hyperledger.besu.ethereum.mainnet.requests.MainnetRequestsValidator.pragueRequestsValidator; +import static org.hyperledger.besu.ethereum.mainnet.requests.WithdrawalRequestProcessor.DEFAULT_WITHDRAWAL_REQUEST_CONTRACT_ADDRESS; + import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.config.PowAlgorithm; import org.hyperledger.besu.datatypes.Address; @@ -23,15 +28,19 @@ import org.hyperledger.besu.ethereum.MainnetBlockValidator; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.Deposit; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.core.feemarket.CoinbaseFeePriceCalculator; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecBuilder.BlockValidatorBuilder; +import org.hyperledger.besu.ethereum.mainnet.blockhash.CancunBlockHashProcessor; +import org.hyperledger.besu.ethereum.mainnet.blockhash.FrontierBlockHashProcessor; +import org.hyperledger.besu.ethereum.mainnet.blockhash.PragueBlockHashProcessor; import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; +import org.hyperledger.besu.ethereum.mainnet.parallelization.MainnetParallelBlockProcessor; import org.hyperledger.besu.ethereum.privacy.PrivateTransactionProcessor; import org.hyperledger.besu.ethereum.privacy.PrivateTransactionValidator; import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater; @@ -41,7 +50,6 @@ import org.hyperledger.besu.evm.contractvalidation.EOFValidationCodeRule; import org.hyperledger.besu.evm.contractvalidation.MaxCodeSizeRule; import org.hyperledger.besu.evm.contractvalidation.PrefixCodeRule; -import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.BerlinGasCalculator; import org.hyperledger.besu.evm.gascalculator.ByzantiumGasCalculator; import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator; @@ -51,6 +59,8 @@ import org.hyperledger.besu.evm.gascalculator.IstanbulGasCalculator; import org.hyperledger.besu.evm.gascalculator.LondonGasCalculator; import org.hyperledger.besu.evm.gascalculator.PetersburgGasCalculator; +import org.hyperledger.besu.evm.gascalculator.PragueEOFGasCalculator; +import org.hyperledger.besu.evm.gascalculator.PragueGasCalculator; import org.hyperledger.besu.evm.gascalculator.ShanghaiGasCalculator; import org.hyperledger.besu.evm.gascalculator.SpuriousDragonGasCalculator; import org.hyperledger.besu.evm.gascalculator.TangerineWhistleGasCalculator; @@ -59,14 +69,15 @@ import org.hyperledger.besu.evm.processor.MessageCallProcessor; import org.hyperledger.besu.evm.worldstate.WorldState; import org.hyperledger.besu.evm.worldstate.WorldUpdater; +import org.hyperledger.besu.plugin.services.MetricsSystem; import java.io.IOException; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.Optional; -import java.util.OptionalInt; import java.util.Set; import java.util.stream.IntStream; @@ -76,11 +87,6 @@ /** Provides the various {@link ProtocolSpec}s on mainnet hard forks. */ public abstract class MainnetProtocolSpecs { - public static final int FRONTIER_CONTRACT_SIZE_LIMIT = Integer.MAX_VALUE; - - public static final int SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT = 24576; - public static final int SHANGHAI_INIT_CODE_SIZE_LIMIT = 2 * SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT; - private static final Address RIPEMD160_PRECOMPILE = Address.fromHexString("0x0000000000000000000000000000000000000003"); @@ -96,17 +102,12 @@ public abstract class MainnetProtocolSpecs { private static final Wei CONSTANTINOPLE_BLOCK_REWARD = Wei.fromEth(2); - public static final Address DEFAULT_DEPOSIT_CONTRACT_ADDRESS = - Address.fromHexString("0x00000000219ab540356cbb839cbe05303d7705fa"); - private MainnetProtocolSpecs() {} public static ProtocolSpecBuilder frontierDefinition( - final OptionalInt configContractSizeLimit, - final OptionalInt configStackSizeLimit, - final EvmConfiguration evmConfiguration) { - final int contractSizeLimit = configContractSizeLimit.orElse(FRONTIER_CONTRACT_SIZE_LIMIT); - final int stackSizeLimit = configStackSizeLimit.orElse(MessageFrame.DEFAULT_MAX_STACK_SIZE); + final EvmConfiguration evmConfiguration, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return new ProtocolSpecBuilder() .gasCalculator(FrontierGasCalculator::new) .gasLimitCalculatorBuilder(feeMarket -> new FrontierTargetingGasLimitCalculator()) @@ -114,17 +115,13 @@ public static ProtocolSpecBuilder frontierDefinition( .precompileContractRegistryBuilder(MainnetPrecompiledContractRegistries::frontier) .messageCallProcessorBuilder(MessageCallProcessor::new) .contractCreationProcessorBuilder( - (gasCalculator, evm) -> + evm -> new ContractCreationProcessor( - gasCalculator, - evm, - false, - Collections.singletonList(MaxCodeSizeRule.of(contractSizeLimit)), - 0)) + evm, false, Collections.singletonList(MaxCodeSizeRule.from(evm)), 0)) .transactionValidatorFactoryBuilder( - (gasCalculator, gasLimitCalculator, feeMarket) -> + (evm, gasLimitCalculator, feeMarket) -> new TransactionValidatorFactory( - gasCalculator, gasLimitCalculator, false, Optional.empty())) + evm.getGasCalculator(), gasLimitCalculator, false, Optional.empty())) .transactionProcessorBuilder( (gasCalculator, feeMarket, @@ -138,7 +135,7 @@ public static ProtocolSpecBuilder frontierDefinition( messageCallProcessor, false, false, - stackSizeLimit, + evmConfiguration.evmStackSize(), FeeMarket.legacy(), CoinbaseFeePriceCalculator.frontier())) .privateTransactionProcessorBuilder( @@ -151,7 +148,7 @@ public static ProtocolSpecBuilder frontierDefinition( contractCreationProcessor, messageCallProcessor, false, - stackSizeLimit, + evmConfiguration.evmStackSize(), new PrivateTransactionValidator(Optional.empty()))) .difficultyCalculator(MainnetDifficultyCalculators.FRONTIER) .blockHeaderValidatorBuilder(feeMarket -> MainnetBlockHeaderValidator.create()) @@ -161,12 +158,16 @@ public static ProtocolSpecBuilder frontierDefinition( .transactionReceiptFactory(MainnetProtocolSpecs::frontierTransactionReceiptFactory) .blockReward(FRONTIER_BLOCK_REWARD) .skipZeroBlockRewards(false) - .blockProcessorBuilder(MainnetBlockProcessor::new) + .blockProcessorBuilder( + isParallelTxProcessingEnabled + ? new MainnetParallelBlockProcessor.ParallelBlockProcessorBuilder(metricsSystem) + : MainnetBlockProcessor::new) .blockValidatorBuilder(MainnetProtocolSpecs.blockValidatorBuilder()) .blockImporterBuilder(MainnetBlockImporter::new) .blockHeaderFunctions(new MainnetBlockHeaderFunctions()) .miningBeneficiaryCalculator(BlockHeader::getCoinbase) .evmConfiguration(evmConfiguration) + .blockHashProcessor(new FrontierBlockHashProcessor()) .name("Frontier"); } @@ -182,34 +183,29 @@ public static BlockValidatorBuilder blockValidatorBuilder() { } public static ProtocolSpecBuilder homesteadDefinition( - final OptionalInt configContractSizeLimit, - final OptionalInt configStackSizeLimit, - final EvmConfiguration evmConfiguration) { - final int contractSizeLimit = configContractSizeLimit.orElse(FRONTIER_CONTRACT_SIZE_LIMIT); - return frontierDefinition(configContractSizeLimit, configStackSizeLimit, evmConfiguration) + final EvmConfiguration evmConfiguration, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { + return frontierDefinition(evmConfiguration, isParallelTxProcessingEnabled, metricsSystem) .gasCalculator(HomesteadGasCalculator::new) .evmBuilder(MainnetEVMs::homestead) .contractCreationProcessorBuilder( - (gasCalculator, evm) -> + evm -> new ContractCreationProcessor( - gasCalculator, - evm, - true, - Collections.singletonList(MaxCodeSizeRule.of(contractSizeLimit)), - 0)) + evm, true, Collections.singletonList(MaxCodeSizeRule.from(evm)), 0)) .transactionValidatorFactoryBuilder( - (gasCalculator, gasLimitCalculator, feeMarket) -> + (evm, gasLimitCalculator, feeMarket) -> new TransactionValidatorFactory( - gasCalculator, gasLimitCalculator, true, Optional.empty())) + evm.getGasCalculator(), gasLimitCalculator, true, Optional.empty())) .difficultyCalculator(MainnetDifficultyCalculators.HOMESTEAD) .name("Homestead"); } public static ProtocolSpecBuilder daoRecoveryInitDefinition( - final OptionalInt contractSizeLimit, - final OptionalInt configStackSizeLimit, - final EvmConfiguration evmConfiguration) { - return homesteadDefinition(contractSizeLimit, configStackSizeLimit, evmConfiguration) + final EvmConfiguration evmConfiguration, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { + return homesteadDefinition(evmConfiguration, isParallelTxProcessingEnabled, metricsSystem) .blockHeaderValidatorBuilder(feeMarket -> MainnetBlockHeaderValidator.createDaoValidator()) .blockProcessorBuilder( (transactionProcessor, @@ -219,44 +215,53 @@ public static ProtocolSpecBuilder daoRecoveryInitDefinition( skipZeroBlockRewards, protocolSchedule) -> new DaoBlockProcessor( - new MainnetBlockProcessor( - transactionProcessor, - transactionReceiptFactory, - blockReward, - miningBeneficiaryCalculator, - skipZeroBlockRewards, - protocolSchedule))) + isParallelTxProcessingEnabled + ? new MainnetParallelBlockProcessor( + transactionProcessor, + transactionReceiptFactory, + blockReward, + miningBeneficiaryCalculator, + skipZeroBlockRewards, + protocolSchedule, + metricsSystem) + : new MainnetBlockProcessor( + transactionProcessor, + transactionReceiptFactory, + blockReward, + miningBeneficiaryCalculator, + skipZeroBlockRewards, + protocolSchedule))) .name("DaoRecoveryInit"); } public static ProtocolSpecBuilder daoRecoveryTransitionDefinition( - final OptionalInt contractSizeLimit, - final OptionalInt configStackSizeLimit, - final EvmConfiguration evmConfiguration) { - return daoRecoveryInitDefinition(contractSizeLimit, configStackSizeLimit, evmConfiguration) - .blockProcessorBuilder(MainnetBlockProcessor::new) + final EvmConfiguration evmConfiguration, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { + return daoRecoveryInitDefinition(evmConfiguration, isParallelTxProcessingEnabled, metricsSystem) + .blockProcessorBuilder( + isParallelTxProcessingEnabled + ? new MainnetParallelBlockProcessor.ParallelBlockProcessorBuilder(metricsSystem) + : MainnetBlockProcessor::new) .name("DaoRecoveryTransition"); } public static ProtocolSpecBuilder tangerineWhistleDefinition( - final OptionalInt contractSizeLimit, - final OptionalInt configStackSizeLimit, - final EvmConfiguration evmConfiguration) { - return homesteadDefinition(contractSizeLimit, configStackSizeLimit, evmConfiguration) + final EvmConfiguration evmConfiguration, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { + return homesteadDefinition(evmConfiguration, isParallelTxProcessingEnabled, metricsSystem) .gasCalculator(TangerineWhistleGasCalculator::new) .name("TangerineWhistle"); } public static ProtocolSpecBuilder spuriousDragonDefinition( final Optional chainId, - final OptionalInt configContractSizeLimit, - final OptionalInt configStackSizeLimit, - final EvmConfiguration evmConfiguration) { - final int contractSizeLimit = - configContractSizeLimit.orElse(SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT); - final int stackSizeLimit = configStackSizeLimit.orElse(MessageFrame.DEFAULT_MAX_STACK_SIZE); - - return tangerineWhistleDefinition(OptionalInt.empty(), configStackSizeLimit, evmConfiguration) + final EvmConfiguration evmConfiguration, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { + return tangerineWhistleDefinition( + evmConfiguration, isParallelTxProcessingEnabled, metricsSystem) .isReplayProtectionSupported(true) .gasCalculator(SpuriousDragonGasCalculator::new) .skipZeroBlockRewards(true) @@ -267,17 +272,17 @@ public static ProtocolSpecBuilder spuriousDragonDefinition( precompileContractRegistry, SPURIOUS_DRAGON_FORCE_DELETE_WHEN_EMPTY_ADDRESSES)) .contractCreationProcessorBuilder( - (gasCalculator, evm) -> + evm -> new ContractCreationProcessor( - gasCalculator, evm, true, - Collections.singletonList(MaxCodeSizeRule.of(contractSizeLimit)), + Collections.singletonList(MaxCodeSizeRule.from(evm)), 1, SPURIOUS_DRAGON_FORCE_DELETE_WHEN_EMPTY_ADDRESSES)) .transactionValidatorFactoryBuilder( - (gasCalculator, gasLimitCalculator, feeMarket) -> - new TransactionValidatorFactory(gasCalculator, gasLimitCalculator, true, chainId)) + (evm, gasLimitCalculator, feeMarket) -> + new TransactionValidatorFactory( + evm.getGasCalculator(), gasLimitCalculator, true, chainId)) .transactionProcessorBuilder( (gasCalculator, feeMarket, @@ -291,7 +296,7 @@ public static ProtocolSpecBuilder spuriousDragonDefinition( messageCallProcessor, true, false, - stackSizeLimit, + evmConfiguration.evmStackSize(), feeMarket, CoinbaseFeePriceCalculator.frontier())) .name("SpuriousDragon"); @@ -299,13 +304,12 @@ public static ProtocolSpecBuilder spuriousDragonDefinition( public static ProtocolSpecBuilder byzantiumDefinition( final Optional chainId, - final OptionalInt contractSizeLimit, - final OptionalInt configStackSizeLimit, final boolean enableRevertReason, - final EvmConfiguration evmConfiguration) { - final int stackSizeLimit = configStackSizeLimit.orElse(MessageFrame.DEFAULT_MAX_STACK_SIZE); + final EvmConfiguration evmConfiguration, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return spuriousDragonDefinition( - chainId, contractSizeLimit, configStackSizeLimit, evmConfiguration) + chainId, evmConfiguration, isParallelTxProcessingEnabled, metricsSystem) .gasCalculator(ByzantiumGasCalculator::new) .evmBuilder(MainnetEVMs::byzantium) .precompileContractRegistryBuilder(MainnetPrecompiledContractRegistries::byzantium) @@ -326,19 +330,23 @@ public static ProtocolSpecBuilder byzantiumDefinition( contractCreationProcessor, messageCallProcessor, false, - stackSizeLimit, + evmConfiguration.evmStackSize(), privateTransactionValidator)) .name("Byzantium"); } public static ProtocolSpecBuilder constantinopleDefinition( final Optional chainId, - final OptionalInt contractSizeLimit, - final OptionalInt configStackSizeLimit, final boolean enableRevertReason, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return byzantiumDefinition( - chainId, contractSizeLimit, configStackSizeLimit, enableRevertReason, evmConfiguration) + chainId, + enableRevertReason, + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem) .difficultyCalculator(MainnetDifficultyCalculators.CONSTANTINOPLE) .gasCalculator(ConstantinopleGasCalculator::new) .evmBuilder(MainnetEVMs::constantinople) @@ -348,30 +356,32 @@ public static ProtocolSpecBuilder constantinopleDefinition( public static ProtocolSpecBuilder petersburgDefinition( final Optional chainId, - final OptionalInt contractSizeLimit, - final OptionalInt configStackSizeLimit, final boolean enableRevertReason, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return constantinopleDefinition( - chainId, contractSizeLimit, configStackSizeLimit, enableRevertReason, evmConfiguration) + chainId, + enableRevertReason, + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem) .gasCalculator(PetersburgGasCalculator::new) .name("Petersburg"); } public static ProtocolSpecBuilder istanbulDefinition( final Optional chainId, - final OptionalInt configContractSizeLimit, - final OptionalInt configStackSizeLimit, final boolean enableRevertReason, - final EvmConfiguration evmConfiguration) { - final int contractSizeLimit = - configContractSizeLimit.orElse(SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT); + final EvmConfiguration evmConfiguration, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return petersburgDefinition( chainId, - configContractSizeLimit, - configStackSizeLimit, enableRevertReason, - evmConfiguration) + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem) .gasCalculator(IstanbulGasCalculator::new) .evmBuilder( (gasCalculator, jdCacheConfig) -> @@ -379,12 +389,11 @@ public static ProtocolSpecBuilder istanbulDefinition( gasCalculator, chainId.orElse(BigInteger.ZERO), evmConfiguration)) .precompileContractRegistryBuilder(MainnetPrecompiledContractRegistries::istanbul) .contractCreationProcessorBuilder( - (gasCalculator, evm) -> + evm -> new ContractCreationProcessor( - gasCalculator, evm, true, - Collections.singletonList(MaxCodeSizeRule.of(contractSizeLimit)), + Collections.singletonList(MaxCodeSizeRule.from(evm)), 1, SPURIOUS_DRAGON_FORCE_DELETE_WHEN_EMPTY_ADDRESSES)) .name("Istanbul"); @@ -392,29 +401,37 @@ public static ProtocolSpecBuilder istanbulDefinition( static ProtocolSpecBuilder muirGlacierDefinition( final Optional chainId, - final OptionalInt contractSizeLimit, - final OptionalInt configStackSizeLimit, final boolean enableRevertReason, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return istanbulDefinition( - chainId, contractSizeLimit, configStackSizeLimit, enableRevertReason, evmConfiguration) + chainId, + enableRevertReason, + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem) .difficultyCalculator(MainnetDifficultyCalculators.MUIR_GLACIER) .name("MuirGlacier"); } static ProtocolSpecBuilder berlinDefinition( final Optional chainId, - final OptionalInt contractSizeLimit, - final OptionalInt configStackSizeLimit, final boolean enableRevertReason, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return muirGlacierDefinition( - chainId, contractSizeLimit, configStackSizeLimit, enableRevertReason, evmConfiguration) + chainId, + enableRevertReason, + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem) .gasCalculator(BerlinGasCalculator::new) .transactionValidatorFactoryBuilder( - (gasCalculator, gasLimitCalculator, feeMarket) -> + (evm, gasLimitCalculator, feeMarket) -> new TransactionValidatorFactory( - gasCalculator, + evm.getGasCalculator(), gasLimitCalculator, true, chainId, @@ -428,26 +445,31 @@ static ProtocolSpecBuilder berlinDefinition( static ProtocolSpecBuilder londonDefinition( final Optional chainId, - final OptionalInt configContractSizeLimit, - final OptionalInt configStackSizeLimit, final boolean enableRevertReason, final GenesisConfigOptions genesisConfigOptions, - final EvmConfiguration evmConfiguration) { - final int contractSizeLimit = - configContractSizeLimit.orElse(SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT); - final int stackSizeLimit = configStackSizeLimit.orElse(MessageFrame.DEFAULT_MAX_STACK_SIZE); + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { final long londonForkBlockNumber = genesisConfigOptions.getLondonBlockNumber().orElse(Long.MAX_VALUE); - final BaseFeeMarket londonFeeMarket = - genesisConfigOptions.isZeroBaseFee() - ? FeeMarket.zeroBaseFee(londonForkBlockNumber) - : FeeMarket.london(londonForkBlockNumber, genesisConfigOptions.getBaseFeePerGas()); + final BaseFeeMarket londonFeeMarket; + if (genesisConfigOptions.isZeroBaseFee()) { + londonFeeMarket = FeeMarket.zeroBaseFee(londonForkBlockNumber); + } else if (genesisConfigOptions.isFixedBaseFee()) { + londonFeeMarket = + FeeMarket.fixedBaseFee( + londonForkBlockNumber, miningParameters.getMinTransactionGasPrice()); + } else { + londonFeeMarket = + FeeMarket.london(londonForkBlockNumber, genesisConfigOptions.getBaseFeePerGas()); + } return berlinDefinition( chainId, - configContractSizeLimit, - configStackSizeLimit, enableRevertReason, - evmConfiguration) + evmConfiguration, + isParallelTxProcessingEnabled, + metricsSystem) .feeMarket(londonFeeMarket) .gasCalculator(LondonGasCalculator::new) .gasLimitCalculatorBuilder( @@ -455,9 +477,9 @@ static ProtocolSpecBuilder londonDefinition( new LondonTargetingGasLimitCalculator( londonForkBlockNumber, (BaseFeeMarket) feeMarket)) .transactionValidatorFactoryBuilder( - (gasCalculator, gasLimitCalculator, feeMarket) -> + (evm, gasLimitCalculator, feeMarket) -> new TransactionValidatorFactory( - gasCalculator, + evm.getGasCalculator(), gasLimitCalculator, feeMarket, true, @@ -480,16 +502,15 @@ static ProtocolSpecBuilder londonDefinition( messageCallProcessor, true, false, - stackSizeLimit, + evmConfiguration.evmStackSize(), feeMarket, CoinbaseFeePriceCalculator.eip1559())) .contractCreationProcessorBuilder( - (gasCalculator, evm) -> + evm -> new ContractCreationProcessor( - gasCalculator, evm, true, - List.of(MaxCodeSizeRule.of(contractSizeLimit), PrefixCodeRule.of()), + List.of(MaxCodeSizeRule.from(evm), PrefixCodeRule.of()), 1, SPURIOUS_DRAGON_FORCE_DELETE_WHEN_EMPTY_ADDRESSES)) .evmBuilder( @@ -510,55 +531,61 @@ static ProtocolSpecBuilder londonDefinition( static ProtocolSpecBuilder arrowGlacierDefinition( final Optional chainId, - final OptionalInt configContractSizeLimit, - final OptionalInt configStackSizeLimit, final boolean enableRevertReason, final GenesisConfigOptions genesisConfigOptions, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return londonDefinition( chainId, - configContractSizeLimit, - configStackSizeLimit, enableRevertReason, genesisConfigOptions, - evmConfiguration) + evmConfiguration, + miningParameters, + isParallelTxProcessingEnabled, + metricsSystem) .difficultyCalculator(MainnetDifficultyCalculators.ARROW_GLACIER) .name("ArrowGlacier"); } static ProtocolSpecBuilder grayGlacierDefinition( final Optional chainId, - final OptionalInt configContractSizeLimit, - final OptionalInt configStackSizeLimit, final boolean enableRevertReason, final GenesisConfigOptions genesisConfigOptions, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return arrowGlacierDefinition( chainId, - configContractSizeLimit, - configStackSizeLimit, enableRevertReason, genesisConfigOptions, - evmConfiguration) + evmConfiguration, + miningParameters, + isParallelTxProcessingEnabled, + metricsSystem) .difficultyCalculator(MainnetDifficultyCalculators.GRAY_GLACIER) .name("GrayGlacier"); } static ProtocolSpecBuilder parisDefinition( final Optional chainId, - final OptionalInt configContractSizeLimit, - final OptionalInt configStackSizeLimit, final boolean enableRevertReason, final GenesisConfigOptions genesisConfigOptions, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return grayGlacierDefinition( chainId, - configContractSizeLimit, - configStackSizeLimit, enableRevertReason, genesisConfigOptions, - evmConfiguration) + evmConfiguration, + miningParameters, + isParallelTxProcessingEnabled, + metricsSystem) .evmBuilder( (gasCalculator, jdCacheConfig) -> MainnetEVMs.paris(gasCalculator, chainId.orElse(BigInteger.ZERO), evmConfiguration)) @@ -572,22 +599,20 @@ static ProtocolSpecBuilder parisDefinition( static ProtocolSpecBuilder shanghaiDefinition( final Optional chainId, - final OptionalInt configContractSizeLimit, - final OptionalInt configStackSizeLimit, final boolean enableRevertReason, final GenesisConfigOptions genesisConfigOptions, - final EvmConfiguration evmConfiguration) { - - // extra variables need to support flipping the warm coinbase flag. - final int stackSizeLimit = configStackSizeLimit.orElse(MessageFrame.DEFAULT_MAX_STACK_SIZE); - + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return parisDefinition( chainId, - configContractSizeLimit, - configStackSizeLimit, enableRevertReason, genesisConfigOptions, - evmConfiguration) + evmConfiguration, + miningParameters, + isParallelTxProcessingEnabled, + metricsSystem) // gas calculator has new code to support EIP-3860 limit and meter initcode .gasCalculator(ShanghaiGasCalculator::new) // EVM has a new operation for EIP-3855 PUSH0 instruction @@ -609,14 +634,14 @@ static ProtocolSpecBuilder shanghaiDefinition( messageCallProcessor, true, true, - stackSizeLimit, + evmConfiguration.evmStackSize(), feeMarket, CoinbaseFeePriceCalculator.eip1559())) // Contract creation rules for EIP-3860 Limit and meter intitcode .transactionValidatorFactoryBuilder( - (gasCalculator, gasLimitCalculator, feeMarket) -> + (evm, gasLimitCalculator, feeMarket) -> new TransactionValidatorFactory( - gasCalculator, + evm.getGasCalculator(), gasLimitCalculator, feeMarket, true, @@ -625,7 +650,7 @@ static ProtocolSpecBuilder shanghaiDefinition( TransactionType.FRONTIER, TransactionType.ACCESS_LIST, TransactionType.EIP1559), - SHANGHAI_INIT_CODE_SIZE_LIMIT)) + evm.getMaxInitcodeSize())) .withdrawalsProcessor(new WithdrawalsProcessor()) .withdrawalsValidator(new WithdrawalsValidator.AllowedWithdrawals()) .name("Shanghai"); @@ -633,26 +658,33 @@ static ProtocolSpecBuilder shanghaiDefinition( static ProtocolSpecBuilder cancunDefinition( final Optional chainId, - final OptionalInt configContractSizeLimit, - final OptionalInt configStackSizeLimit, final boolean enableRevertReason, final GenesisConfigOptions genesisConfigOptions, - final EvmConfiguration evmConfiguration) { - - final int stackSizeLimit = configStackSizeLimit.orElse(MessageFrame.DEFAULT_MAX_STACK_SIZE); + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { final long londonForkBlockNumber = genesisConfigOptions.getLondonBlockNumber().orElse(0L); - final BaseFeeMarket cancunFeeMarket = - genesisConfigOptions.isZeroBaseFee() - ? FeeMarket.zeroBaseFee(londonForkBlockNumber) - : FeeMarket.cancun(londonForkBlockNumber, genesisConfigOptions.getBaseFeePerGas()); + final BaseFeeMarket cancunFeeMarket; + if (genesisConfigOptions.isZeroBaseFee()) { + cancunFeeMarket = FeeMarket.zeroBaseFee(londonForkBlockNumber); + } else if (genesisConfigOptions.isFixedBaseFee()) { + cancunFeeMarket = + FeeMarket.fixedBaseFee( + londonForkBlockNumber, miningParameters.getMinTransactionGasPrice()); + } else { + cancunFeeMarket = + FeeMarket.cancun(londonForkBlockNumber, genesisConfigOptions.getBaseFeePerGas()); + } return shanghaiDefinition( chainId, - configContractSizeLimit, - configStackSizeLimit, enableRevertReason, genesisConfigOptions, - evmConfiguration) + evmConfiguration, + miningParameters, + isParallelTxProcessingEnabled, + metricsSystem) .feeMarket(cancunFeeMarket) // gas calculator for EIP-4844 blob gas .gasCalculator(CancunGasCalculator::new) @@ -661,7 +693,7 @@ static ProtocolSpecBuilder cancunDefinition( feeMarket -> new CancunTargetingGasLimitCalculator( londonForkBlockNumber, (BaseFeeMarket) feeMarket)) - // EVM changes to support EOF EIPs (3670, 4200, 4750, 5450) + // EVM changes to support EIP-1153: TSTORE and EIP-5656: MCOPY .evmBuilder( (gasCalculator, jdCacheConfig) -> MainnetEVMs.cancun( @@ -680,14 +712,15 @@ static ProtocolSpecBuilder cancunDefinition( messageCallProcessor, true, true, - stackSizeLimit, + evmConfiguration.evmStackSize(), feeMarket, - CoinbaseFeePriceCalculator.eip1559())) + CoinbaseFeePriceCalculator.eip1559(), + new CodeDelegationProcessor(chainId))) // change to check for max blob gas per block for EIP-4844 .transactionValidatorFactoryBuilder( - (gasCalculator, gasLimitCalculator, feeMarket) -> + (evm, gasLimitCalculator, feeMarket) -> new TransactionValidatorFactory( - gasCalculator, + evm.getGasCalculator(), gasLimitCalculator, feeMarket, true, @@ -697,28 +730,157 @@ static ProtocolSpecBuilder cancunDefinition( TransactionType.ACCESS_LIST, TransactionType.EIP1559, TransactionType.BLOB), - SHANGHAI_INIT_CODE_SIZE_LIMIT)) + evm.getMaxInitcodeSize())) .precompileContractRegistryBuilder(MainnetPrecompiledContractRegistries::cancun) .blockHeaderValidatorBuilder(MainnetBlockHeaderValidator::cancunBlockHeaderValidator) + .blockHashProcessor(new CancunBlockHashProcessor()) .name("Cancun"); } - static ProtocolSpecBuilder futureEipsDefinition( + static ProtocolSpecBuilder cancunEOFDefinition( final Optional chainId, - final OptionalInt configContractSizeLimit, - final OptionalInt configStackSizeLimit, final boolean enableRevertReason, final GenesisConfigOptions genesisConfigOptions, - final EvmConfiguration evmConfiguration) { - final int contractSizeLimit = - configContractSizeLimit.orElse(SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT); + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { + + ProtocolSpecBuilder protocolSpecBuilder = + cancunDefinition( + chainId, + enableRevertReason, + genesisConfigOptions, + evmConfiguration, + miningParameters, + isParallelTxProcessingEnabled, + metricsSystem); + return addEOF(chainId, evmConfiguration, protocolSpecBuilder).name("CancunEOF"); + } + + static ProtocolSpecBuilder pragueDefinition( + final Optional chainId, + final boolean enableRevertReason, + final GenesisConfigOptions genesisConfigOptions, + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { + + final Address withdrawalRequestContractAddress = + genesisConfigOptions + .getWithdrawalRequestContractAddress() + .orElse(DEFAULT_WITHDRAWAL_REQUEST_CONTRACT_ADDRESS); + final Address depositContractAddress = + genesisConfigOptions.getDepositContractAddress().orElse(DEFAULT_DEPOSIT_CONTRACT_ADDRESS); + return cancunDefinition( chainId, - configContractSizeLimit, - configStackSizeLimit, enableRevertReason, genesisConfigOptions, - evmConfiguration) + evmConfiguration, + miningParameters, + isParallelTxProcessingEnabled, + metricsSystem) + // EIP-3074 AUTH and AUTCALL gas + .gasCalculator(PragueGasCalculator::new) + // EIP-3074 AUTH and AUTHCALL + .evmBuilder( + (gasCalculator, jdCacheConfig) -> + MainnetEVMs.prague( + gasCalculator, chainId.orElse(BigInteger.ZERO), evmConfiguration)) + + // EIP-2537 BLS12-381 precompiles + .precompileContractRegistryBuilder(MainnetPrecompiledContractRegistries::prague) + + // EIP-7002 Withdrawals / EIP-6610 Deposits / EIP-7685 Requests + .requestsValidator(pragueRequestsValidator(depositContractAddress)) + // EIP-7002 Withdrawals / EIP-6610 Deposits / EIP-7685 Requests + .requestProcessorCoordinator( + pragueRequestsProcessors(withdrawalRequestContractAddress, depositContractAddress)) + + // change to accept EIP-7702 transactions + .transactionValidatorFactoryBuilder( + (evm, gasLimitCalculator, feeMarket) -> + new TransactionValidatorFactory( + evm.getGasCalculator(), + gasLimitCalculator, + feeMarket, + true, + chainId, + Set.of( + TransactionType.FRONTIER, + TransactionType.ACCESS_LIST, + TransactionType.EIP1559, + TransactionType.BLOB, + TransactionType.DELEGATE_CODE), + evm.getMaxInitcodeSize())) + + // EIP-2935 Blockhash processor + .blockHashProcessor(new PragueBlockHashProcessor()) + .name("Prague"); + } + + static ProtocolSpecBuilder pragueEOFDefinition( + final Optional chainId, + final boolean enableRevertReason, + final GenesisConfigOptions genesisConfigOptions, + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { + + ProtocolSpecBuilder protocolSpecBuilder = + pragueDefinition( + chainId, + enableRevertReason, + genesisConfigOptions, + evmConfiguration, + miningParameters, + isParallelTxProcessingEnabled, + metricsSystem); + return addEOF(chainId, evmConfiguration, protocolSpecBuilder).name("PragueEOF"); + } + + private static ProtocolSpecBuilder addEOF( + final Optional chainId, + final EvmConfiguration evmConfiguration, + final ProtocolSpecBuilder protocolSpecBuilder) { + return protocolSpecBuilder + // EIP-7692 EOF v1 Gas calculator + .gasCalculator(PragueEOFGasCalculator::new) + // EIP-7692 EOF v1 EVM and opcodes + .evmBuilder( + (gasCalculator, jdCacheConfig) -> + MainnetEVMs.pragueEOF( + gasCalculator, chainId.orElse(BigInteger.ZERO), evmConfiguration)) + // EIP-7698 EOF v1 creation transaction + .contractCreationProcessorBuilder( + evm -> + new ContractCreationProcessor( + evm, + true, + List.of(MaxCodeSizeRule.from(evm), EOFValidationCodeRule.from(evm)), + 1, + SPURIOUS_DRAGON_FORCE_DELETE_WHEN_EMPTY_ADDRESSES)); + } + + static ProtocolSpecBuilder futureEipsDefinition( + final Optional chainId, + final boolean enableRevertReason, + final GenesisConfigOptions genesisConfigOptions, + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { + return pragueEOFDefinition( + chainId, + enableRevertReason, + genesisConfigOptions, + evmConfiguration, + miningParameters, + isParallelTxProcessingEnabled, + metricsSystem) // Use Future EIP configured EVM .evmBuilder( (gasCalculator, jdCacheConfig) -> @@ -726,13 +888,11 @@ static ProtocolSpecBuilder futureEipsDefinition( gasCalculator, chainId.orElse(BigInteger.ZERO), evmConfiguration)) // change contract call creator to accept EOF code .contractCreationProcessorBuilder( - (gasCalculator, evm) -> + evm -> new ContractCreationProcessor( - gasCalculator, evm, true, - List.of( - MaxCodeSizeRule.of(contractSizeLimit), EOFValidationCodeRule.of(1, false)), + List.of(MaxCodeSizeRule.from(evm), EOFValidationCodeRule.from(evm)), 1, SPURIOUS_DRAGON_FORCE_DELETE_WHEN_EMPTY_ADDRESSES)) // use future configured precompiled contracts @@ -742,27 +902,25 @@ static ProtocolSpecBuilder futureEipsDefinition( static ProtocolSpecBuilder experimentalEipsDefinition( final Optional chainId, - final OptionalInt configContractSizeLimit, - final OptionalInt configStackSizeLimit, final boolean enableRevertReason, final GenesisConfigOptions genesisConfigOptions, - final EvmConfiguration evmConfiguration) { - - final Address depositContractAddress = - genesisConfigOptions.getDepositContractAddress().orElse(DEFAULT_DEPOSIT_CONTRACT_ADDRESS); + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { return futureEipsDefinition( chainId, - configContractSizeLimit, - configStackSizeLimit, enableRevertReason, genesisConfigOptions, - evmConfiguration) + evmConfiguration, + miningParameters, + isParallelTxProcessingEnabled, + metricsSystem) .evmBuilder( (gasCalculator, jdCacheConfig) -> MainnetEVMs.experimentalEips( gasCalculator, chainId.orElse(BigInteger.ZERO), evmConfiguration)) - .depositsValidator(new DepositsValidator.AllowedDeposits(depositContractAddress)) .name("ExperimentalEips"); } @@ -825,13 +983,7 @@ static TransactionReceipt berlinTransactionReceiptFactoryWithReasonEnabled( transactionProcessingResult.getRevertReason()); } - private static class DaoBlockProcessor implements BlockProcessor { - - private final BlockProcessor wrapped; - - public DaoBlockProcessor(final BlockProcessor wrapped) { - this.wrapped = wrapped; - } + private record DaoBlockProcessor(BlockProcessor wrapped) implements BlockProcessor { @Override public BlockProcessingResult processBlock( @@ -841,7 +993,6 @@ public BlockProcessingResult processBlock( final List transactions, final List ommers, final Optional> withdrawals, - final Optional> deposits, final PrivateMetadataUpdater privateMetadataUpdater) { updateWorldStateForDao(worldState); return wrapped.processBlock( @@ -851,7 +1002,6 @@ public BlockProcessingResult processBlock( transactions, ommers, withdrawals, - deposits, privateMetadataUpdater); } @@ -863,7 +1013,8 @@ private void updateWorldStateForDao(final MutableWorldState worldState) { final JsonArray json = new JsonArray( Resources.toString( - this.getClass().getResource("/daoAddresses.json"), StandardCharsets.UTF_8)); + Objects.requireNonNull(this.getClass().getResource("/daoAddresses.json")), + StandardCharsets.UTF_8)); final List
addresses = IntStream.range(0, json.size()) .mapToObj(json::getString) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java index 7e0a0da76b5..91c964525e0 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessor.java @@ -18,12 +18,12 @@ import static org.hyperledger.besu.ethereum.mainnet.PrivateStateUtils.KEY_PRIVATE_METADATA_UPDATER; import static org.hyperledger.besu.ethereum.mainnet.PrivateStateUtils.KEY_TRANSACTION; import static org.hyperledger.besu.ethereum.mainnet.PrivateStateUtils.KEY_TRANSACTION_HASH; +import static org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; import org.hyperledger.besu.collections.trie.BytesTrieSet; import org.hyperledger.besu.datatypes.AccessListEntry; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.feemarket.CoinbaseFeePriceCalculator; @@ -32,15 +32,17 @@ import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; import org.hyperledger.besu.ethereum.trie.MerkleTrieException; -import org.hyperledger.besu.ethereum.vm.BlockHashLookup; +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.code.CodeInvalid; import org.hyperledger.besu.evm.code.CodeV0; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.processor.AbstractMessageProcessor; import org.hyperledger.besu.evm.tracing.OperationTracer; +import org.hyperledger.besu.evm.worldstate.EVMWorldUpdater; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.util.Deque; @@ -60,6 +62,8 @@ public class MainnetTransactionProcessor { private static final Logger LOG = LoggerFactory.getLogger(MainnetTransactionProcessor.class); + private static final Set
EMPTY_ADDRESS_SET = Set.of(); + protected final GasCalculator gasCalculator; protected final TransactionValidatorFactory transactionValidatorFactory; @@ -77,6 +81,8 @@ public class MainnetTransactionProcessor { protected final FeeMarket feeMarket; private final CoinbaseFeePriceCalculator coinbaseFeePriceCalculator; + private final Optional maybeCodeDelegationProcessor; + public MainnetTransactionProcessor( final GasCalculator gasCalculator, final TransactionValidatorFactory transactionValidatorFactory, @@ -87,6 +93,30 @@ public MainnetTransactionProcessor( final int maxStackSize, final FeeMarket feeMarket, final CoinbaseFeePriceCalculator coinbaseFeePriceCalculator) { + this( + gasCalculator, + transactionValidatorFactory, + contractCreationProcessor, + messageCallProcessor, + clearEmptyAccounts, + warmCoinbase, + maxStackSize, + feeMarket, + coinbaseFeePriceCalculator, + null); + } + + public MainnetTransactionProcessor( + final GasCalculator gasCalculator, + final TransactionValidatorFactory transactionValidatorFactory, + final AbstractMessageProcessor contractCreationProcessor, + final AbstractMessageProcessor messageCallProcessor, + final boolean clearEmptyAccounts, + final boolean warmCoinbase, + final int maxStackSize, + final FeeMarket feeMarket, + final CoinbaseFeePriceCalculator coinbaseFeePriceCalculator, + final CodeDelegationProcessor maybeCodeDelegationProcessor) { this.gasCalculator = gasCalculator; this.transactionValidatorFactory = transactionValidatorFactory; this.contractCreationProcessor = contractCreationProcessor; @@ -96,12 +126,12 @@ public MainnetTransactionProcessor( this.maxStackSize = maxStackSize; this.feeMarket = feeMarket; this.coinbaseFeePriceCalculator = coinbaseFeePriceCalculator; + this.maybeCodeDelegationProcessor = Optional.ofNullable(maybeCodeDelegationProcessor); } /** * Applies a transaction to the current system state. * - * @param blockchain The current blockchain * @param worldState The current world state * @param blockHeader The current block header * @param transaction The transaction to process @@ -115,7 +145,6 @@ public MainnetTransactionProcessor( * @see TransactionValidationParams */ public TransactionProcessingResult processTransaction( - final Blockchain blockchain, final WorldUpdater worldState, final ProcessableBlockHeader blockHeader, final Transaction transaction, @@ -125,7 +154,6 @@ public TransactionProcessingResult processTransaction( final TransactionValidationParams transactionValidationParams, final Wei blobGasPrice) { return processTransaction( - blockchain, worldState, blockHeader, transaction, @@ -141,7 +169,6 @@ public TransactionProcessingResult processTransaction( /** * Applies a transaction to the current system state. * - * @param blockchain The current blockchain * @param worldState The current world state * @param blockHeader The current block header * @param transaction The transaction to process @@ -156,7 +183,6 @@ public TransactionProcessingResult processTransaction( * @see TransactionValidationParams */ public TransactionProcessingResult processTransaction( - final Blockchain blockchain, final WorldUpdater worldState, final ProcessableBlockHeader blockHeader, final Transaction transaction, @@ -167,7 +193,6 @@ public TransactionProcessingResult processTransaction( final OperationTracer operationTracer, final Wei blobGasPrice) { return processTransaction( - blockchain, worldState, blockHeader, transaction, @@ -183,18 +208,16 @@ public TransactionProcessingResult processTransaction( /** * Applies a transaction to the current system state. * - * @param blockchain The current blockchain * @param worldState The current world state * @param blockHeader The current block header * @param transaction The transaction to process - * @param operationTracer The tracer to record results of each EVM operation * @param miningBeneficiary The address which is to receive the transaction fee + * @param operationTracer The tracer to record results of each EVM operation * @param blockHashLookup The {@link BlockHashLookup} to use for BLOCKHASH operations * @param isPersistingPrivateState Whether the resulting private state will be persisted * @return the transaction result */ public TransactionProcessingResult processTransaction( - final Blockchain blockchain, final WorldUpdater worldState, final ProcessableBlockHeader blockHeader, final Transaction transaction, @@ -204,7 +227,6 @@ public TransactionProcessingResult processTransaction( final Boolean isPersistingPrivateState, final Wei blobGasPrice) { return processTransaction( - blockchain, worldState, blockHeader, transaction, @@ -220,19 +242,17 @@ public TransactionProcessingResult processTransaction( /** * Applies a transaction to the current system state. * - * @param blockchain The current blockchain * @param worldState The current world state * @param blockHeader The current block header * @param transaction The transaction to process - * @param operationTracer The tracer to record results of each EVM operation * @param miningBeneficiary The address which is to receive the transaction fee + * @param operationTracer The tracer to record results of each EVM operation * @param blockHashLookup The {@link BlockHashLookup} to use for BLOCKHASH operations * @param isPersistingPrivateState Whether the resulting private state will be persisted * @param transactionValidationParams The transaction validation parameters to use * @return the transaction result */ public TransactionProcessingResult processTransaction( - final Blockchain blockchain, final WorldUpdater worldState, final ProcessableBlockHeader blockHeader, final Transaction transaction, @@ -243,7 +263,6 @@ public TransactionProcessingResult processTransaction( final TransactionValidationParams transactionValidationParams, final Wei blobGasPrice) { return processTransaction( - blockchain, worldState, blockHeader, transaction, @@ -257,7 +276,6 @@ public TransactionProcessingResult processTransaction( } public TransactionProcessingResult processTransaction( - final Blockchain ignoredBlockchain, final WorldUpdater worldState, final ProcessableBlockHeader blockHeader, final Transaction transaction, @@ -268,12 +286,16 @@ public TransactionProcessingResult processTransaction( final TransactionValidationParams transactionValidationParams, final PrivateMetadataUpdater privateMetadataUpdater, final Wei blobGasPrice) { + final EVMWorldUpdater evmWorldUpdater = new EVMWorldUpdater(worldState); try { final var transactionValidator = transactionValidatorFactory.get(); LOG.trace("Starting execution of {}", transaction); ValidationResult validationResult = transactionValidator.validate( - transaction, blockHeader.getBaseFee(), transactionValidationParams); + transaction, + blockHeader.getBaseFee(), + Optional.ofNullable(blobGasPrice), + transactionValidationParams); // Make sure the transaction is intrinsically valid before trying to // compare against a sender account (because the transaction may not // be signed correctly to extract the sender). @@ -283,8 +305,7 @@ public TransactionProcessingResult processTransaction( } final Address senderAddress = transaction.getSender(); - - final MutableAccount sender = worldState.getOrCreateSenderAccount(senderAddress); + final MutableAccount sender = evmWorldUpdater.getOrCreateSenderAccount(senderAddress); validationResult = transactionValidator.validateForSender(transaction, sender, transactionValidationParams); @@ -293,6 +314,10 @@ public TransactionProcessingResult processTransaction( return TransactionProcessingResult.invalid(validationResult); } + operationTracer.tracePrepareTransaction(evmWorldUpdater, transaction); + + final Set
warmAddressList = new BytesTrieSet<>(Address.SIZE); + final long previousNonce = sender.incrementNonce(); LOG.trace( "Incremented sender {} nonce ({} -> {})", @@ -315,21 +340,34 @@ public TransactionProcessingResult processTransaction( previousBalance, sender.getBalance()); + long codeDelegationRefund = 0L; + if (transaction.getCodeDelegationList().isPresent()) { + if (maybeCodeDelegationProcessor.isEmpty()) { + throw new RuntimeException("Code delegation processor is required for 7702 transactions"); + } + + final CodeDelegationResult codeDelegationResult = + maybeCodeDelegationProcessor.get().process(evmWorldUpdater, transaction); + warmAddressList.addAll(codeDelegationResult.accessedDelegatorAddresses()); + codeDelegationRefund = + gasCalculator.calculateDelegateCodeGasRefund( + (codeDelegationResult.alreadyExistingDelegators())); + } + final List accessListEntries = transaction.getAccessList().orElse(List.of()); // we need to keep a separate hash set of addresses in case they specify no storage. // No-storage is a common pattern, especially for Externally Owned Accounts - final Set
addressList = new BytesTrieSet<>(Address.SIZE); final Multimap storageList = HashMultimap.create(); int accessListStorageCount = 0; for (final var entry : accessListEntries) { final Address address = entry.address(); - addressList.add(address); + warmAddressList.add(address); final List storageKeys = entry.storageKeys(); storageList.putAll(address, storageKeys); accessListStorageCount += storageKeys.size(); } if (warmCoinbase) { - addressList.add(miningBeneficiary); + warmAddressList.add(miningBeneficiary); } final long intrinsicGas = @@ -337,15 +375,19 @@ public TransactionProcessingResult processTransaction( transaction.getPayload(), transaction.isContractCreation()); final long accessListGas = gasCalculator.accessListGasCost(accessListEntries.size(), accessListStorageCount); - final long gasAvailable = transaction.getGasLimit() - intrinsicGas - accessListGas; + final long codeDelegationGas = + gasCalculator.delegateCodeGasCost(transaction.codeDelegationListSize()); + final long gasAvailable = + transaction.getGasLimit() - intrinsicGas - accessListGas - codeDelegationGas; LOG.trace( - "Gas available for execution {} = {} - {} - {} (limit - intrinsic - accessList)", + "Gas available for execution {} = {} - {} - {} - {} (limit - intrinsic - accessList - codeDelegation)", gasAvailable, transaction.getGasLimit(), intrinsicGas, - accessListGas); + accessListGas, + codeDelegationGas); - final WorldUpdater worldUpdater = worldState.updater(); + final WorldUpdater worldUpdater = evmWorldUpdater.updater(); final ImmutableMap.Builder contextVariablesBuilder = ImmutableMap.builder() .put(KEY_IS_PERSISTING_PRIVATE_STATE, isPersistingPrivateState) @@ -373,7 +415,7 @@ public TransactionProcessingResult processTransaction( .miningBeneficiary(miningBeneficiary) .blockHashLookup(blockHashLookup) .contextVariables(contextVariablesBuilder.build()) - .accessListWarmAddresses(addressList) + .accessListWarmAddresses(warmAddressList) .accessListWarmStorage(storageList); if (transaction.getVersionedHashes().isPresent()) { @@ -389,18 +431,19 @@ public TransactionProcessingResult processTransaction( Address.contractAddress(senderAddress, sender.getNonce() - 1L); final Bytes initCodeBytes = transaction.getPayload(); + Code code = contractCreationProcessor.getCodeFromEVMForCreation(initCodeBytes); initialFrame = commonMessageFrameBuilder .type(MessageFrame.Type.CONTRACT_CREATION) .address(contractAddress) .contract(contractAddress) - .inputData(Bytes.EMPTY) - .code(contractCreationProcessor.getCodeFromEVM(null, initCodeBytes)) + .inputData(initCodeBytes.slice(code.getSize())) + .code(code) .build(); } else { @SuppressWarnings("OptionalGetWithoutIsPresent") // isContractCall tests isPresent final Address to = transaction.getTo().get(); - final Optional maybeContract = Optional.ofNullable(worldState.get(to)); + final Optional maybeContract = Optional.ofNullable(evmWorldUpdater.get(to)); initialFrame = commonMessageFrameBuilder .type(MessageFrame.Type.MESSAGE_CALL) @@ -422,10 +465,22 @@ public TransactionProcessingResult processTransaction( } else { initialFrame.setState(MessageFrame.State.EXCEPTIONAL_HALT); initialFrame.setExceptionalHaltReason(Optional.of(ExceptionalHaltReason.INVALID_CODE)); + validationResult = + ValidationResult.invalid( + TransactionInvalidReason.EOF_CODE_INVALID, + ((CodeInvalid) initialFrame.getCode()).getInvalidReason()); } if (initialFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { worldUpdater.commit(); + } else { + if (initialFrame.getExceptionalHaltReason().isPresent() + && initialFrame.getCode().isValid()) { + validationResult = + ValidationResult.invalid( + TransactionInvalidReason.EXECUTION_HALTED, + initialFrame.getExceptionalHaltReason().get().toString()); + } } if (LOG.isTraceEnabled()) { @@ -439,7 +494,8 @@ public TransactionProcessingResult processTransaction( // after the other so that if it is the same account somehow, we end up with the right result) final long selfDestructRefund = gasCalculator.getSelfDestructRefundAmount() * initialFrame.getSelfDestructs().size(); - final long baseRefundGas = initialFrame.getGasRefund() + selfDestructRefund; + final long baseRefundGas = + initialFrame.getGasRefund() + selfDestructRefund + codeDelegationRefund; final long refundedGas = refunded(transaction, initialFrame.getRemainingGas(), baseRefundGas); final Wei refundedWei = transactionGasPrice.multiply(refundedGas); final Wei balancePriorToRefund = sender.getBalance(); @@ -453,17 +509,7 @@ public TransactionProcessingResult processTransaction( .log(); final long gasUsedByTransaction = transaction.getGasLimit() - initialFrame.getRemainingGas(); - operationTracer.traceEndTransaction( - worldUpdater, - transaction, - initialFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS, - initialFrame.getOutputData(), - initialFrame.getLogs(), - gasUsedByTransaction, - 0L); - // update the coinbase - final var coinbase = worldState.getOrCreate(miningBeneficiary); final long usedGas = transaction.getGasLimit() - refundedGas; final CoinbaseFeePriceCalculator coinbaseCalculator; if (blockHeader.getBaseFee().isPresent()) { @@ -485,12 +531,25 @@ public TransactionProcessingResult processTransaction( final Wei coinbaseWeiDelta = coinbaseCalculator.price(usedGas, transactionGasPrice, blockHeader.getBaseFee()); + operationTracer.traceBeforeRewardTransaction(worldUpdater, transaction, coinbaseWeiDelta); + + final var coinbase = evmWorldUpdater.getOrCreate(miningBeneficiary); coinbase.incrementBalance(coinbaseWeiDelta); - initialFrame.getSelfDestructs().forEach(worldState::deleteAccount); + operationTracer.traceEndTransaction( + worldUpdater, + transaction, + initialFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS, + initialFrame.getOutputData(), + initialFrame.getLogs(), + gasUsedByTransaction, + initialFrame.getSelfDestructs(), + 0L); + + initialFrame.getSelfDestructs().forEach(evmWorldUpdater::deleteAccount); if (clearEmptyAccounts) { - worldState.clearAccountsThatAreEmpty(); + evmWorldUpdater.clearAccountsThatAreEmpty(); } if (initialFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { @@ -501,13 +560,45 @@ public TransactionProcessingResult processTransaction( initialFrame.getOutputData(), validationResult); } else { + if (initialFrame.getExceptionalHaltReason().isPresent()) { + LOG.debug( + "Transaction {} processing halted: {}", + transaction.getHash(), + initialFrame.getExceptionalHaltReason().get()); + } + if (initialFrame.getRevertReason().isPresent()) { + LOG.debug( + "Transaction {} reverted: {}", + transaction.getHash(), + initialFrame.getRevertReason().get()); + } return TransactionProcessingResult.failed( gasUsedByTransaction, refundedGas, validationResult, initialFrame.getRevertReason()); } } catch (final MerkleTrieException re) { + operationTracer.traceEndTransaction( + evmWorldUpdater.updater(), + transaction, + false, + Bytes.EMPTY, + List.of(), + 0, + EMPTY_ADDRESS_SET, + 0L); + // need to throw to trigger the heal throw re; } catch (final RuntimeException re) { + operationTracer.traceEndTransaction( + evmWorldUpdater.updater(), + transaction, + false, + Bytes.EMPTY, + List.of(), + 0, + EMPTY_ADDRESS_SET, + 0L); + LOG.error("Critical Exception Processing Transaction", re); return TransactionProcessingResult.invalid( ValidationResult.invalid( @@ -522,7 +613,7 @@ public void process(final MessageFrame frame, final OperationTracer operationTra executor.process(frame, operationTracer); } - private AbstractMessageProcessor getMessageProcessor(final MessageFrame.Type type) { + public AbstractMessageProcessor getMessageProcessor(final MessageFrame.Type type) { return switch (type) { case MESSAGE_CALL -> messageCallProcessor; case CONTRACT_CREATION -> contractCreationProcessor; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java index 5fd841c1c39..e67ccbb4c62 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -20,6 +20,7 @@ import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; import org.hyperledger.besu.datatypes.Blob; import org.hyperledger.besu.datatypes.BlobsWithCommitments; +import org.hyperledger.besu.datatypes.CodeDelegation; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.KZGCommitment; import org.hyperledger.besu.datatypes.TransactionType; @@ -31,6 +32,7 @@ import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.worldstate.DelegatedCodeService; import java.math.BigInteger; import java.util.List; @@ -83,6 +85,7 @@ public MainnetTransactionValidator( public ValidationResult validate( final Transaction transaction, final Optional baseFee, + final Optional blobFee, final TransactionValidationParams transactionValidationParams) { final ValidationResult signatureResult = validateTransactionSignature(transaction); @@ -128,17 +131,76 @@ public ValidationResult validate( transaction.getPayload().size(), maxInitcodeSize)); } - return validateCostAndFee(transaction, baseFee, transactionValidationParams); + if (transactionType == TransactionType.DELEGATE_CODE) { + ValidationResult codeDelegationValidation = + validateCodeDelegation(transaction); + if (!codeDelegationValidation.isValid()) { + return codeDelegationValidation; + } + } + + return validateCostAndFee(transaction, baseFee, blobFee, transactionValidationParams); + } + + private static ValidationResult validateCodeDelegation( + final Transaction transaction) { + if (isDelegateCodeEmpty(transaction)) { + return ValidationResult.invalid( + TransactionInvalidReason.EMPTY_CODE_DELEGATION, + "transaction code delegation transactions must have a non-empty code delegation list"); + } + + if (transaction.getTo().isEmpty()) { + return ValidationResult.invalid( + TransactionInvalidReason.INVALID_TRANSACTION_FORMAT, + "transaction code delegation transactions must have a to address"); + } + + final BigInteger halfCurveOrder = SignatureAlgorithmFactory.getInstance().getHalfCurveOrder(); + final Optional> validationResult = + transaction + .getCodeDelegationList() + .map( + codeDelegations -> { + for (CodeDelegation codeDelegation : codeDelegations) { + if (codeDelegation.signature().getS().compareTo(halfCurveOrder) > 0) { + return ValidationResult.invalid( + TransactionInvalidReason.INVALID_SIGNATURE, + "Invalid signature for code delegation. S value must be less or equal than the half curve order."); + } + + if (codeDelegation.signature().getRecId() != 0 + && codeDelegation.signature().getRecId() != 1) { + return ValidationResult.invalid( + TransactionInvalidReason.INVALID_SIGNATURE, + "Invalid signature for code delegation. RecId value must be 0 or 1."); + } + } + + return ValidationResult.valid(); + }); + + if (validationResult.isPresent() && !validationResult.get().isValid()) { + return validationResult.get(); + } + + return ValidationResult.valid(); + } + + private static boolean isDelegateCodeEmpty(final Transaction transaction) { + return transaction.getCodeDelegationList().isEmpty() + || transaction.getCodeDelegationList().get().isEmpty(); } private ValidationResult validateCostAndFee( final Transaction transaction, final Optional maybeBaseFee, + final Optional maybeBlobFee, final TransactionValidationParams transactionValidationParams) { if (maybeBaseFee.isPresent()) { final Wei price = feeMarket.getTransactionPriceCalculator().price(transaction, maybeBaseFee); - if (!transactionValidationParams.isAllowMaxFeeGasBelowBaseFee() + if (!transactionValidationParams.allowUnderpriced() && price.compareTo(maybeBaseFee.orElseThrow()) < 0) { return ValidationResult.invalid( TransactionInvalidReason.GAS_PRICE_BELOW_CURRENT_BASE_FEE, @@ -168,12 +230,27 @@ private ValidationResult validateCostAndFee( "total blob gas %d exceeds max blob gas per block %d", txTotalBlobGas, gasLimitCalculator.currentBlobGasLimit())); } + if (maybeBlobFee.isEmpty()) { + throw new IllegalArgumentException( + "blob fee must be provided from blocks containing blobs"); + // tx.getMaxFeePerBlobGas can be empty for eth_call + } else if (!transactionValidationParams.allowUnderpriced() + && maybeBlobFee.get().compareTo(transaction.getMaxFeePerBlobGas().get()) > 0) { + return ValidationResult.invalid( + TransactionInvalidReason.BLOB_GAS_PRICE_BELOW_CURRENT_BLOB_BASE_FEE, + String.format( + "tx max fee per blob gas less than block blob gas fee: address %s blobGasFeeCap: %s, blobBaseFee: %s", + transaction.getSender().toHexString(), + transaction.getMaxFeePerBlobGas().get().toHumanReadableString(), + maybeBlobFee.get().toHumanReadableString())); + } } final long intrinsicGasCost = gasCalculator.transactionIntrinsicGasCost( transaction.getPayload(), transaction.isContractCreation()) - + (transaction.getAccessList().map(gasCalculator::accessListGasCost).orElse(0L)); + + (transaction.getAccessList().map(gasCalculator::accessListGasCost).orElse(0L)) + + gasCalculator.delegateCodeGasCost(transaction.codeDelegationListSize()); if (Long.compareUnsigned(intrinsicGasCost, transaction.getGasLimit()) > 0) { return ValidationResult.invalid( TransactionInvalidReason.INTRINSIC_GAS_EXCEEDS_GAS_LIMIT, @@ -217,7 +294,7 @@ public ValidationResult validateForSender( upfrontCost.toQuantityHexString(), senderBalance.toQuantityHexString())); } - if (transaction.getNonce() < senderNonce) { + if (Long.compareUnsigned(transaction.getNonce(), senderNonce) < 0) { return ValidationResult.invalid( TransactionInvalidReason.NONCE_TOO_LOW, String.format( @@ -233,7 +310,8 @@ public ValidationResult validateForSender( transaction.getNonce(), senderNonce)); } - if (!validationParams.isAllowContractAddressAsSender() && !codeHash.equals(Hash.EMPTY)) { + if (!validationParams.isAllowContractAddressAsSender() + && !canSendTransaction(sender, codeHash)) { return ValidationResult.invalid( TransactionInvalidReason.TX_SENDER_NOT_AUTHORIZED, String.format( @@ -244,6 +322,11 @@ public ValidationResult validateForSender( return ValidationResult.valid(); } + private static boolean canSendTransaction(final Account sender, final Hash codeHash) { + return codeHash.equals(Hash.EMPTY) + || DelegatedCodeService.hasDelegatedCode(sender.getUnprocessedCode()); + } + private ValidationResult validateTransactionSignature( final Transaction transaction) { if (chainId.isPresent() @@ -289,7 +372,7 @@ public ValidationResult validateBlobTransaction( if (transaction.getType().supportsBlob() && transaction.getTo().isEmpty()) { return ValidationResult.invalid( TransactionInvalidReason.INVALID_TRANSACTION_FORMAT, - "transaction blob transactions cannot have a to address"); + "transaction blob transactions must have a to address"); } if (transaction.getVersionedHashes().isEmpty()) { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ParentBeaconBlockRootHelper.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ParentBeaconBlockRootHelper.java index 3715263fd2b..acd7dce24cd 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ParentBeaconBlockRootHelper.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ParentBeaconBlockRootHelper.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -37,8 +37,8 @@ static void storeParentBeaconBlockRoot( see EIP-4788: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-4788.md */ // If code is not deployed don't do anything - final MutableAccount account = worldUpdater.getOrCreate(BEACON_ROOTS_ADDRESS); - if (Hash.EMPTY.equals(account.getCodeHash())) { + final MutableAccount account = worldUpdater.getAccount(BEACON_ROOTS_ADDRESS); + if (account == null || Hash.EMPTY.equals(account.getCodeHash())) { return; } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PermissionTransactionValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PermissionTransactionValidator.java index 5325567d0f9..bc3a72ceabe 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PermissionTransactionValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PermissionTransactionValidator.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -43,8 +43,9 @@ public PermissionTransactionValidator( public ValidationResult validate( final Transaction transaction, final Optional baseFee, + final Optional blobBaseFee, final TransactionValidationParams transactionValidationParams) { - return delegate.validate(transaction, baseFee, transactionValidationParams); + return delegate.validate(transaction, baseFee, blobBaseFee, transactionValidationParams); } @Override diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessor.java index 0db16f0c4db..abfdf3652e6 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessor.java @@ -23,7 +23,6 @@ import org.hyperledger.besu.ethereum.BlockProcessingResult; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.Deposit; import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.Withdrawal; @@ -90,7 +89,6 @@ public BlockProcessingResult processBlock( final List transactions, final List ommers, final Optional> withdrawals, - final Optional> deposits, final PrivateMetadataUpdater privateMetadataUpdater) { if (privateMetadataUpdater != null) { @@ -110,7 +108,6 @@ public BlockProcessingResult processBlock( transactions, ommers, withdrawals, - deposits, metadataUpdater); metadataUpdater.commit(); return result; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PrivacySupportingProtocolSchedule.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PrivacySupportingProtocolSchedule.java index e882c672fc6..eea6fe9f6fb 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PrivacySupportingProtocolSchedule.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PrivacySupportingProtocolSchedule.java @@ -1,20 +1,17 @@ /* + * Copyright contributors to Hyperledger Besu. * - * * Copyright Hyperledger Besu Contributors. - * * - * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * * the License. You may obtain a copy of the License at - * * - * * http://www.apache.org/licenses/LICENSE-2.0 - * * - * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * * specific language governing permissions and limitations under the License. - * * - * * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.mainnet; import org.hyperledger.besu.ethereum.core.PermissionTransactionFilter; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PrivateStateUtils.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PrivateStateUtils.java index 78e33790d56..e44ed2a6974 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PrivateStateUtils.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/PrivateStateUtils.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSchedule.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSchedule.java index a249a6b5285..f97d8f27eae 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSchedule.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSchedule.java @@ -12,14 +12,15 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.mainnet; +import org.hyperledger.besu.datatypes.HardforkId; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import java.math.BigInteger; +import java.util.Map; import java.util.Optional; import java.util.function.Predicate; @@ -47,11 +48,19 @@ default ProtocolSpec getForNextBlockHeader( void putTimestampMilestone(final long timestamp, final ProtocolSpec protocolSpec); + default void setMilestones(final Map milestoneList) { + throw new UnsupportedOperationException("Not implemented"); + } + default Optional hardforkFor( final Predicate predicate) { throw new UnsupportedOperationException("Not implemented"); } + default Optional milestoneFor(final HardforkId hardforkId) { + throw new UnsupportedOperationException("Not implemented"); + } + boolean isOnMilestoneBoundary(final BlockHeader blockHeader); boolean anyMatch(Predicate predicate); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilder.java index 519450b42fc..beac906be55 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilder.java @@ -15,13 +15,20 @@ package org.hyperledger.besu.ethereum.mainnet; import org.hyperledger.besu.config.GenesisConfigOptions; +import org.hyperledger.besu.datatypes.HardforkId; import org.hyperledger.besu.ethereum.chain.BadBlockManager; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.privacy.PrivateTransactionValidator; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.plugin.services.MetricsSystem; import java.math.BigInteger; +import java.util.List; +import java.util.Map; +import java.util.NavigableMap; import java.util.Optional; +import java.util.OptionalInt; import java.util.OptionalLong; import java.util.TreeMap; import java.util.function.Function; @@ -40,9 +47,10 @@ public class ProtocolScheduleBuilder { private final PrivacyParameters privacyParameters; private final boolean isRevertReasonEnabled; private final EvmConfiguration evmConfiguration; - private final BadBlockManager badBlockManager = new BadBlockManager(); - - private DefaultProtocolSchedule protocolSchedule; + private final MiningParameters miningParameters; + private final BadBlockManager badBlockManager; + private final boolean isParallelTxProcessingEnabled; + private final MetricsSystem metricsSystem; public ProtocolScheduleBuilder( final GenesisConfigOptions config, @@ -50,14 +58,22 @@ public ProtocolScheduleBuilder( final ProtocolSpecAdapters protocolSpecAdapters, final PrivacyParameters privacyParameters, final boolean isRevertReasonEnabled, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final BadBlockManager badBlockManager, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { this( config, Optional.of(defaultChainId), protocolSpecAdapters, privacyParameters, isRevertReasonEnabled, - evmConfiguration); + evmConfiguration, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem); } public ProtocolScheduleBuilder( @@ -65,14 +81,22 @@ public ProtocolScheduleBuilder( final ProtocolSpecAdapters protocolSpecAdapters, final PrivacyParameters privacyParameters, final boolean isRevertReasonEnabled, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final BadBlockManager badBlockManager, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { this( config, Optional.empty(), protocolSpecAdapters, privacyParameters, isRevertReasonEnabled, - evmConfiguration); + evmConfiguration, + miningParameters, + badBlockManager, + isParallelTxProcessingEnabled, + metricsSystem); } private ProtocolScheduleBuilder( @@ -81,18 +105,26 @@ private ProtocolScheduleBuilder( final ProtocolSpecAdapters protocolSpecAdapters, final PrivacyParameters privacyParameters, final boolean isRevertReasonEnabled, - final EvmConfiguration evmConfiguration) { + final EvmConfiguration evmConfiguration, + final MiningParameters miningParameters, + final BadBlockManager badBlockManager, + final boolean isParallelTxProcessingEnabled, + final MetricsSystem metricsSystem) { this.config = config; this.protocolSpecAdapters = protocolSpecAdapters; this.privacyParameters = privacyParameters; this.isRevertReasonEnabled = isRevertReasonEnabled; this.evmConfiguration = evmConfiguration; this.defaultChainId = defaultChainId; + this.miningParameters = miningParameters; + this.badBlockManager = badBlockManager; + this.isParallelTxProcessingEnabled = isParallelTxProcessingEnabled; + this.metricsSystem = metricsSystem; } public ProtocolSchedule createProtocolSchedule() { final Optional chainId = config.getChainId().or(() -> defaultChainId); - protocolSchedule = new DefaultProtocolSchedule(chainId); + DefaultProtocolSchedule protocolSchedule = new DefaultProtocolSchedule(chainId); initSchedule(protocolSchedule, chainId); return protocolSchedule; } @@ -103,17 +135,24 @@ private void initSchedule( final MainnetProtocolSpecFactory specFactory = new MainnetProtocolSpecFactory( chainId, - config.getContractSizeLimit(), - config.getEvmStackSize(), isRevertReasonEnabled, config.getEcip1017EraRounds(), - evmConfiguration); + evmConfiguration.overrides( + config.getContractSizeLimit(), OptionalInt.empty(), config.getEvmStackSize()), + miningParameters, + isParallelTxProcessingEnabled, + metricsSystem); validateForkOrdering(); - final TreeMap builders = buildMilestoneMap(specFactory); + final List mileStones = createMilestones(specFactory); + final Map completeMileStoneList = buildFullMilestoneMap(mileStones); + protocolSchedule.setMilestones(completeMileStoneList); + + final NavigableMap builders = buildFlattenedMilestoneMap(mileStones); - // At this stage, all milestones are flagged with correct modifier, but ProtocolSpecs must be + // At this stage, all milestones are flagged with the correct modifier, but ProtocolSpecs must + // be // inserted _AT_ the modifier block entry. if (!builders.isEmpty()) { protocolSpecAdapters.stream() @@ -127,9 +166,10 @@ private void initSchedule( builders.put( modifierBlock, new BuilderMapEntry( + parent.hardforkId, parent.milestoneType, modifierBlock, - parent.getBuilder(), + parent.builder(), entry.getValue())); }); } @@ -142,8 +182,8 @@ private void initSchedule( addProtocolSpec( protocolSchedule, e.milestoneType, - e.getBlockIdentifier(), - e.getBuilder(), + e.blockIdentifier(), + e.builder(), e.modifier)); // NOTE: It is assumed that Daofork blocks will not be used for private networks @@ -157,8 +197,8 @@ private void initSchedule( final ProtocolSpec originalProtocolSpec = getProtocolSpec( protocolSchedule, - previousSpecBuilder.getBuilder(), - previousSpecBuilder.getModifier()); + previousSpecBuilder.builder(), + previousSpecBuilder.modifier()); addProtocolSpec( protocolSchedule, BuilderMapEntry.MilestoneType.BLOCK_NUMBER, @@ -185,14 +225,14 @@ private void initSchedule( final ProtocolSpec originalProtocolSpec = getProtocolSpec( protocolSchedule, - previousSpecBuilder.getBuilder(), - previousSpecBuilder.getModifier()); + previousSpecBuilder.builder(), + previousSpecBuilder.modifier()); addProtocolSpec( protocolSchedule, BuilderMapEntry.MilestoneType.BLOCK_NUMBER, classicBlockNumber, ClassicProtocolSpecs.classicRecoveryInitDefinition( - config.getContractSizeLimit(), config.getEvmStackSize(), evmConfiguration), + evmConfiguration, isParallelTxProcessingEnabled, metricsSystem), Function.identity()); protocolSchedule.putBlockNumberMilestone( classicBlockNumber + 1, originalProtocolSpec); @@ -235,6 +275,9 @@ private void validateEthereumForkOrdering() { // Begin timestamp forks lastForkBlock = validateForkOrder("Shanghai", config.getShanghaiTime(), lastForkBlock); lastForkBlock = validateForkOrder("Cancun", config.getCancunTime(), lastForkBlock); + lastForkBlock = validateForkOrder("CancunEOF", config.getCancunEOFTime(), lastForkBlock); + lastForkBlock = validateForkOrder("Prague", config.getPragueTime(), lastForkBlock); + lastForkBlock = validateForkOrder("PragueEOF", config.getPragueEOFTime(), lastForkBlock); lastForkBlock = validateForkOrder("FutureEips", config.getFutureEipsTime(), lastForkBlock); lastForkBlock = validateForkOrder("ExperimentalEips", config.getExperimentalEipsTime(), lastForkBlock); @@ -274,77 +317,181 @@ private long validateForkOrder( return referenceForkBlock; } - private TreeMap buildMilestoneMap( - final MainnetProtocolSpecFactory specFactory) { - return createMilestones(specFactory) - .flatMap(Optional::stream) + private NavigableMap buildFlattenedMilestoneMap( + final List mileStones) { + return mileStones.stream() .collect( Collectors.toMap( - BuilderMapEntry::getBlockIdentifier, + BuilderMapEntry::blockIdentifier, b -> b, (existing, replacement) -> replacement, TreeMap::new)); } - private Stream> createMilestones( - final MainnetProtocolSpecFactory specFactory) { + private Map buildFullMilestoneMap(final List mileStones) { + return mileStones.stream() + .collect( + Collectors.toMap( + b -> b.hardforkId, + BuilderMapEntry::blockIdentifier, + (existing, replacement) -> existing)); + } + + private List createMilestones(final MainnetProtocolSpecFactory specFactory) { return Stream.of( - blockNumberMilestone(OptionalLong.of(0), specFactory.frontierDefinition()), - blockNumberMilestone(config.getHomesteadBlockNumber(), specFactory.homesteadDefinition()), - blockNumberMilestone( - config.getTangerineWhistleBlockNumber(), specFactory.tangerineWhistleDefinition()), - blockNumberMilestone( - config.getSpuriousDragonBlockNumber(), specFactory.spuriousDragonDefinition()), - blockNumberMilestone(config.getByzantiumBlockNumber(), specFactory.byzantiumDefinition()), - blockNumberMilestone( - config.getConstantinopleBlockNumber(), specFactory.constantinopleDefinition()), - blockNumberMilestone(config.getPetersburgBlockNumber(), specFactory.petersburgDefinition()), - blockNumberMilestone(config.getIstanbulBlockNumber(), specFactory.istanbulDefinition()), - blockNumberMilestone( - config.getMuirGlacierBlockNumber(), specFactory.muirGlacierDefinition()), - blockNumberMilestone(config.getBerlinBlockNumber(), specFactory.berlinDefinition()), - blockNumberMilestone(config.getLondonBlockNumber(), specFactory.londonDefinition(config)), - blockNumberMilestone( - config.getArrowGlacierBlockNumber(), specFactory.arrowGlacierDefinition(config)), - blockNumberMilestone( - config.getGrayGlacierBlockNumber(), specFactory.grayGlacierDefinition(config)), - blockNumberMilestone( - config.getMergeNetSplitBlockNumber(), specFactory.parisDefinition(config)), - // Timestamp Forks - timestampMilestone(config.getShanghaiTime(), specFactory.shanghaiDefinition(config)), - timestampMilestone(config.getCancunTime(), specFactory.cancunDefinition(config)), - timestampMilestone(config.getFutureEipsTime(), specFactory.futureEipsDefinition(config)), - timestampMilestone( - config.getExperimentalEipsTime(), specFactory.experimentalEipsDefinition(config)), - - // Classic Milestones - blockNumberMilestone( - config.getEcip1015BlockNumber(), specFactory.tangerineWhistleDefinition()), - blockNumberMilestone(config.getDieHardBlockNumber(), specFactory.dieHardDefinition()), - blockNumberMilestone(config.getGothamBlockNumber(), specFactory.gothamDefinition()), - blockNumberMilestone( - config.getDefuseDifficultyBombBlockNumber(), - specFactory.defuseDifficultyBombDefinition()), - blockNumberMilestone(config.getAtlantisBlockNumber(), specFactory.atlantisDefinition()), - blockNumberMilestone(config.getAghartaBlockNumber(), specFactory.aghartaDefinition()), - blockNumberMilestone(config.getPhoenixBlockNumber(), specFactory.phoenixDefinition()), - blockNumberMilestone(config.getThanosBlockNumber(), specFactory.thanosDefinition()), - blockNumberMilestone(config.getMagnetoBlockNumber(), specFactory.magnetoDefinition()), - blockNumberMilestone(config.getMystiqueBlockNumber(), specFactory.mystiqueDefinition()), - blockNumberMilestone(config.getSpiralBlockNumber(), specFactory.spiralDefinition())); + blockNumberMilestone( + HardforkId.MainnetHardforkId.FRONTIER, + OptionalLong.of(0), + specFactory.frontierDefinition()), + blockNumberMilestone( + HardforkId.MainnetHardforkId.HOMESTEAD, + config.getHomesteadBlockNumber(), + specFactory.homesteadDefinition()), + blockNumberMilestone( + HardforkId.MainnetHardforkId.TANGERINE_WHISTLE, + config.getTangerineWhistleBlockNumber(), + specFactory.tangerineWhistleDefinition()), + blockNumberMilestone( + HardforkId.MainnetHardforkId.SPURIOUS_DRAGON, + config.getSpuriousDragonBlockNumber(), + specFactory.spuriousDragonDefinition()), + blockNumberMilestone( + HardforkId.MainnetHardforkId.BYZANTIUM, + config.getByzantiumBlockNumber(), + specFactory.byzantiumDefinition()), + blockNumberMilestone( + HardforkId.MainnetHardforkId.CONSTANTINOPLE, + config.getConstantinopleBlockNumber(), + specFactory.constantinopleDefinition()), + blockNumberMilestone( + HardforkId.MainnetHardforkId.PETERSBURG, + config.getPetersburgBlockNumber(), + specFactory.petersburgDefinition()), + blockNumberMilestone( + HardforkId.MainnetHardforkId.ISTANBUL, + config.getIstanbulBlockNumber(), + specFactory.istanbulDefinition()), + blockNumberMilestone( + HardforkId.MainnetHardforkId.MUIR_GLACIER, + config.getMuirGlacierBlockNumber(), + specFactory.muirGlacierDefinition()), + blockNumberMilestone( + HardforkId.MainnetHardforkId.BERLIN, + config.getBerlinBlockNumber(), + specFactory.berlinDefinition()), + blockNumberMilestone( + HardforkId.MainnetHardforkId.LONDON, + config.getLondonBlockNumber(), + specFactory.londonDefinition(config)), + blockNumberMilestone( + HardforkId.MainnetHardforkId.ARROW_GLACIER, + config.getArrowGlacierBlockNumber(), + specFactory.arrowGlacierDefinition(config)), + blockNumberMilestone( + HardforkId.MainnetHardforkId.GRAY_GLACIER, + config.getGrayGlacierBlockNumber(), + specFactory.grayGlacierDefinition(config)), + blockNumberMilestone( + HardforkId.MainnetHardforkId.PARIS, + config.getMergeNetSplitBlockNumber(), + specFactory.parisDefinition(config)), + // Timestamp Forks + timestampMilestone( + HardforkId.MainnetHardforkId.SHANGHAI, + config.getShanghaiTime(), + specFactory.shanghaiDefinition(config)), + timestampMilestone( + HardforkId.MainnetHardforkId.CANCUN, + config.getCancunTime(), + specFactory.cancunDefinition(config)), + timestampMilestone( + HardforkId.MainnetHardforkId.CANCUN_EOF, + config.getCancunEOFTime(), + specFactory.cancunEOFDefinition(config)), + timestampMilestone( + HardforkId.MainnetHardforkId.PRAGUE, + config.getPragueTime(), + specFactory.pragueDefinition(config)), + timestampMilestone( + HardforkId.MainnetHardforkId.PRAGUE_EOF, + config.getPragueEOFTime(), + specFactory.pragueEOFDefinition(config)), + timestampMilestone( + HardforkId.MainnetHardforkId.FUTURE_EIPS, + config.getFutureEipsTime(), + specFactory.futureEipsDefinition(config)), + timestampMilestone( + HardforkId.MainnetHardforkId.EXPERIMENTAL_EIPS, + config.getExperimentalEipsTime(), + specFactory.experimentalEipsDefinition(config)), + + // Classic Milestones + blockNumberMilestone( + HardforkId.ClassicHardforkId.CLASSIC_TANGERINE_WHISTLE, + config.getEcip1015BlockNumber(), + specFactory.tangerineWhistleDefinition()), + blockNumberMilestone( + HardforkId.ClassicHardforkId.DIE_HARD, + config.getDieHardBlockNumber(), + specFactory.dieHardDefinition()), + blockNumberMilestone( + HardforkId.ClassicHardforkId.GOTHAM, + config.getGothamBlockNumber(), + specFactory.gothamDefinition()), + blockNumberMilestone( + HardforkId.ClassicHardforkId.DEFUSE_DIFFICULTY_BOMB, + config.getDefuseDifficultyBombBlockNumber(), + specFactory.defuseDifficultyBombDefinition()), + blockNumberMilestone( + HardforkId.ClassicHardforkId.ATLANTIS, + config.getAtlantisBlockNumber(), + specFactory.atlantisDefinition()), + blockNumberMilestone( + HardforkId.ClassicHardforkId.AGHARTA, + config.getAghartaBlockNumber(), + specFactory.aghartaDefinition()), + blockNumberMilestone( + HardforkId.ClassicHardforkId.PHOENIX, + config.getPhoenixBlockNumber(), + specFactory.phoenixDefinition()), + blockNumberMilestone( + HardforkId.ClassicHardforkId.THANOS, + config.getThanosBlockNumber(), + specFactory.thanosDefinition()), + blockNumberMilestone( + HardforkId.ClassicHardforkId.MAGNETO, + config.getMagnetoBlockNumber(), + specFactory.magnetoDefinition()), + blockNumberMilestone( + HardforkId.ClassicHardforkId.MYSTIQUE, + config.getMystiqueBlockNumber(), + specFactory.mystiqueDefinition()), + blockNumberMilestone( + HardforkId.ClassicHardforkId.SPIRAL, + config.getSpiralBlockNumber(), + specFactory.spiralDefinition())) + .flatMap(Optional::stream) + .toList(); } private Optional timestampMilestone( - final OptionalLong blockIdentifier, final ProtocolSpecBuilder builder) { - return createMilestone(blockIdentifier, builder, BuilderMapEntry.MilestoneType.TIMESTAMP); + final HardforkId hardforkId, + final OptionalLong blockIdentifier, + final ProtocolSpecBuilder builder) { + return createMilestone( + hardforkId, blockIdentifier, builder, BuilderMapEntry.MilestoneType.TIMESTAMP); } private Optional blockNumberMilestone( - final OptionalLong blockIdentifier, final ProtocolSpecBuilder builder) { - return createMilestone(blockIdentifier, builder, BuilderMapEntry.MilestoneType.BLOCK_NUMBER); + final HardforkId hardforkId, + final OptionalLong blockIdentifier, + final ProtocolSpecBuilder builder) { + return createMilestone( + hardforkId, blockIdentifier, builder, BuilderMapEntry.MilestoneType.BLOCK_NUMBER); } private Optional createMilestone( + final HardforkId hardforkId, final OptionalLong blockIdentifier, final ProtocolSpecBuilder builder, final BuilderMapEntry.MilestoneType milestoneType) { @@ -354,7 +501,11 @@ private Optional createMilestone( final long blockVal = blockIdentifier.getAsLong(); return Optional.of( new BuilderMapEntry( - milestoneType, blockVal, builder, protocolSpecAdapters.getModifierForBlock(blockVal))); + hardforkId, + milestoneType, + blockVal, + builder, + protocolSpecAdapters.getModifierForBlock(blockVal))); } private ProtocolSpec getProtocolSpec( @@ -378,46 +529,27 @@ private void addProtocolSpec( final Function modifier) { switch (milestoneType) { - case BLOCK_NUMBER -> protocolSchedule.putBlockNumberMilestone( - blockNumberOrTimestamp, getProtocolSpec(protocolSchedule, definition, modifier)); - case TIMESTAMP -> protocolSchedule.putTimestampMilestone( - blockNumberOrTimestamp, getProtocolSpec(protocolSchedule, definition, modifier)); - default -> throw new IllegalStateException( - "Unexpected milestoneType: " - + milestoneType - + " for milestone: " - + blockNumberOrTimestamp); + case BLOCK_NUMBER -> + protocolSchedule.putBlockNumberMilestone( + blockNumberOrTimestamp, getProtocolSpec(protocolSchedule, definition, modifier)); + case TIMESTAMP -> + protocolSchedule.putTimestampMilestone( + blockNumberOrTimestamp, getProtocolSpec(protocolSchedule, definition, modifier)); + default -> + throw new IllegalStateException( + "Unexpected milestoneType: " + + milestoneType + + " for milestone: " + + blockNumberOrTimestamp); } } - private static class BuilderMapEntry { - private final MilestoneType milestoneType; - private final long blockIdentifier; - private final ProtocolSpecBuilder builder; - private final Function modifier; - - public BuilderMapEntry( - final MilestoneType milestoneType, - final long blockIdentifier, - final ProtocolSpecBuilder builder, - final Function modifier) { - this.milestoneType = milestoneType; - this.blockIdentifier = blockIdentifier; - this.builder = builder; - this.modifier = modifier; - } - - public long getBlockIdentifier() { - return blockIdentifier; - } - - public ProtocolSpecBuilder getBuilder() { - return builder; - } - - public Function getModifier() { - return modifier; - } + private record BuilderMapEntry( + HardforkId hardforkId, + ProtocolScheduleBuilder.BuilderMapEntry.MilestoneType milestoneType, + long blockIdentifier, + ProtocolSpecBuilder builder, + Function modifier) { private enum MilestoneType { BLOCK_NUMBER, diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpec.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpec.java index 3c69f12d648..b364679aa8b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpec.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpec.java @@ -17,10 +17,12 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.BlockValidator; import org.hyperledger.besu.ethereum.GasLimitCalculator; -import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; import org.hyperledger.besu.ethereum.core.BlockImporter; +import org.hyperledger.besu.ethereum.mainnet.blockhash.BlockHashProcessor; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; +import org.hyperledger.besu.ethereum.mainnet.requests.RequestProcessorCoordinator; +import org.hyperledger.besu.ethereum.mainnet.requests.RequestsValidatorCoordinator; import org.hyperledger.besu.ethereum.privacy.PrivateTransactionProcessor; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -72,16 +74,16 @@ public class ProtocolSpec { private final FeeMarket feeMarket; - private final BadBlockManager badBlockManager; - private final Optional powHasher; private final WithdrawalsValidator withdrawalsValidator; private final Optional withdrawalsProcessor; - private final DepositsValidator depositsValidator; - + private final RequestsValidatorCoordinator requestsValidatorCoordinator; + private final Optional requestProcessorCoordinator; + private final BlockHashProcessor blockHashProcessor; private final boolean isPoS; private final boolean isReplayProtectionSupported; + /** * Creates a new protocol specification instance. * @@ -106,11 +108,11 @@ public class ProtocolSpec { * @param gasCalculator the gas calculator to use. * @param gasLimitCalculator the gas limit calculator to use. * @param feeMarket an {@link Optional} wrapping {@link FeeMarket} class if appropriate. - * @param badBlockManager the cache to use to keep invalid blocks * @param powHasher the proof-of-work hasher - * @param withdrawalsValidator the withdrawals validator to use * @param withdrawalsProcessor the Withdrawals processor to use - * @param depositsValidator the withdrawals validator to use + * @param requestsValidatorCoordinator the request validator to use + * @param requestProcessorCoordinator the request processor to use + * @param blockHashProcessor the blockHash processor to use * @param isPoS indicates whether the current spec is PoS * @param isReplayProtectionSupported indicates whether the current spec supports replay * protection @@ -137,11 +139,12 @@ public ProtocolSpec( final GasCalculator gasCalculator, final GasLimitCalculator gasLimitCalculator, final FeeMarket feeMarket, - final BadBlockManager badBlockManager, final Optional powHasher, final WithdrawalsValidator withdrawalsValidator, final Optional withdrawalsProcessor, - final DepositsValidator depositsValidator, + final RequestsValidatorCoordinator requestsValidatorCoordinator, + final Optional requestProcessorCoordinator, + final BlockHashProcessor blockHashProcessor, final boolean isPoS, final boolean isReplayProtectionSupported) { this.name = name; @@ -165,11 +168,12 @@ public ProtocolSpec( this.gasCalculator = gasCalculator; this.gasLimitCalculator = gasLimitCalculator; this.feeMarket = feeMarket; - this.badBlockManager = badBlockManager; this.powHasher = powHasher; this.withdrawalsValidator = withdrawalsValidator; this.withdrawalsProcessor = withdrawalsProcessor; - this.depositsValidator = depositsValidator; + this.requestsValidatorCoordinator = requestsValidatorCoordinator; + this.requestProcessorCoordinator = requestProcessorCoordinator; + this.blockHashProcessor = blockHashProcessor; this.isPoS = isPoS; this.isReplayProtectionSupported = isReplayProtectionSupported; } @@ -354,15 +358,6 @@ public FeeMarket getFeeMarket() { return feeMarket; } - /** - * Returns the bad blocks manager - * - * @return the bad blocks manager - */ - public BadBlockManager getBadBlocksManager() { - return badBlockManager; - } - /** * Returns the Proof-of-Work hasher * @@ -380,8 +375,16 @@ public Optional getWithdrawalsProcessor() { return withdrawalsProcessor; } - public DepositsValidator getDepositsValidator() { - return depositsValidator; + public RequestsValidatorCoordinator getRequestsValidatorCoordinator() { + return requestsValidatorCoordinator; + } + + public Optional getRequestProcessorCoordinator() { + return requestProcessorCoordinator; + } + + public BlockHashProcessor getBlockHashProcessor() { + return blockHashProcessor; } /** diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java index 7b39ebe2f5f..0d792b7870c 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/ProtocolSpecBuilder.java @@ -26,10 +26,13 @@ import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; import org.hyperledger.besu.ethereum.core.BlockImporter; import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.ethereum.mainnet.blockhash.BlockHashProcessor; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.ethereum.mainnet.precompiles.privacy.FlexiblePrivacyPrecompiledContract; import org.hyperledger.besu.ethereum.mainnet.precompiles.privacy.PrivacyPluginPrecompiledContract; import org.hyperledger.besu.ethereum.mainnet.precompiles.privacy.PrivacyPrecompiledContract; +import org.hyperledger.besu.ethereum.mainnet.requests.RequestProcessorCoordinator; +import org.hyperledger.besu.ethereum.mainnet.requests.RequestsValidatorCoordinator; import org.hyperledger.besu.ethereum.privacy.PrivateTransactionProcessor; import org.hyperledger.besu.ethereum.privacy.PrivateTransactionValidator; import org.hyperledger.besu.evm.EVM; @@ -48,6 +51,7 @@ public class ProtocolSpecBuilder { private Function gasLimitCalculatorBuilder; private Wei blockReward; private boolean skipZeroBlockRewards; + private BlockHeaderFunctions blockHeaderFunctions; private AbstractBlockProcessor.TransactionReceiptFactory transactionReceiptFactory; private DifficultyCalculator difficultyCalculator; @@ -57,15 +61,17 @@ public class ProtocolSpecBuilder { private Function blockHeaderValidatorBuilder; private Function ommerHeaderValidatorBuilder; private Function blockBodyValidatorBuilder; - private BiFunction contractCreationProcessorBuilder; + private Function contractCreationProcessorBuilder; private Function precompileContractRegistryBuilder; private BiFunction messageCallProcessorBuilder; private TransactionProcessorBuilder transactionProcessorBuilder; + private BlockProcessorBuilder blockProcessorBuilder; private BlockValidatorBuilder blockValidatorBuilder; private BlockImporterBuilder blockImporterBuilder; + private String name; private MiningBeneficiaryCalculator miningBeneficiaryCalculator; private PrivacyParameters privacyParameters; @@ -74,8 +80,10 @@ public class ProtocolSpecBuilder { private WithdrawalsValidator withdrawalsValidator = new WithdrawalsValidator.ProhibitedWithdrawals(); private WithdrawalsProcessor withdrawalsProcessor; - - private DepositsValidator depositsValidator = new DepositsValidator.ProhibitedDeposits(); + private RequestsValidatorCoordinator requestsValidatorCoordinator = + RequestsValidatorCoordinator.empty(); + private RequestProcessorCoordinator requestProcessorCoordinator; + protected BlockHashProcessor blockHashProcessor; private FeeMarket feeMarket = FeeMarket.legacy(); private BadBlockManager badBlockManager; private PoWHasher powHasher = PoWHasher.ETHASH_LIGHT; @@ -150,8 +158,7 @@ public ProtocolSpecBuilder blockBodyValidatorBuilder( } public ProtocolSpecBuilder contractCreationProcessorBuilder( - final BiFunction - contractCreationProcessorBuilder) { + final Function contractCreationProcessorBuilder) { this.contractCreationProcessorBuilder = contractCreationProcessorBuilder; return this; } @@ -160,7 +167,7 @@ public ProtocolSpecBuilder precompileContractRegistryBuilder( final Function precompileContractRegistryBuilder) { this.precompileContractRegistryBuilder = - (precompiledContractConfiguration) -> { + precompiledContractConfiguration -> { final PrecompileContractRegistry registry = precompileContractRegistryBuilder.apply(precompiledContractConfiguration); if (precompiledContractConfiguration.getPrivacyParameters().isEnabled()) { @@ -260,8 +267,20 @@ public ProtocolSpecBuilder withdrawalsProcessor(final WithdrawalsProcessor withd return this; } - public ProtocolSpecBuilder depositsValidator(final DepositsValidator depositsValidator) { - this.depositsValidator = depositsValidator; + public ProtocolSpecBuilder requestsValidator( + final RequestsValidatorCoordinator requestsValidatorCoordinator) { + this.requestsValidatorCoordinator = requestsValidatorCoordinator; + return this; + } + + public ProtocolSpecBuilder requestProcessorCoordinator( + final RequestProcessorCoordinator requestProcessorCoordinator) { + this.requestProcessorCoordinator = requestProcessorCoordinator; + return this; + } + + public ProtocolSpecBuilder blockHashProcessor(final BlockHashProcessor blockHashProcessor) { + this.blockHashProcessor = blockHashProcessor; return this; } @@ -310,9 +329,9 @@ public ProtocolSpec build(final ProtocolSchedule protocolSchedule) { final PrecompiledContractConfiguration precompiledContractConfiguration = new PrecompiledContractConfiguration(gasCalculator, privacyParameters); final TransactionValidatorFactory transactionValidatorFactory = - transactionValidatorFactoryBuilder.apply(gasCalculator, gasLimitCalculator, feeMarket); + transactionValidatorFactoryBuilder.apply(evm, gasLimitCalculator, feeMarket); final AbstractMessageProcessor contractCreationProcessor = - contractCreationProcessorBuilder.apply(gasCalculator, evm); + contractCreationProcessorBuilder.apply(evm); final PrecompileContractRegistry precompileContractRegistry = precompileContractRegistryBuilder.apply(precompiledContractConfiguration); final AbstractMessageProcessor messageCallProcessor = @@ -380,11 +399,12 @@ public ProtocolSpec build(final ProtocolSchedule protocolSchedule) { gasCalculator, gasLimitCalculator, feeMarket, - badBlockManager, Optional.ofNullable(powHasher), withdrawalsValidator, Optional.ofNullable(withdrawalsProcessor), - depositsValidator, + requestsValidatorCoordinator, + Optional.ofNullable(requestProcessorCoordinator), + blockHashProcessor, isPoS, isReplayProtectionSupported); } @@ -489,6 +509,6 @@ public interface BlockImporterBuilder { public interface TransactionValidatorFactoryBuilder { TransactionValidatorFactory apply( - GasCalculator gasCalculator, GasLimitCalculator gasLimitCalculator, FeeMarket feeMarket); + EVM evm, GasLimitCalculator gasLimitCalculator, FeeMarket feeMarket); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java new file mode 100644 index 00000000000..f74de79442f --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessor.java @@ -0,0 +1,141 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet; + +import static org.hyperledger.besu.evm.frame.MessageFrame.DEFAULT_MAX_STACK_SIZE; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; +import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.code.CodeV0; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.BlockHashOperation; +import org.hyperledger.besu.evm.processor.AbstractMessageProcessor; +import org.hyperledger.besu.evm.tracing.OperationTracer; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.Deque; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SystemCallProcessor { + private static final Logger LOG = LoggerFactory.getLogger(SystemCallProcessor.class); + + /** The system address */ + static final Address SYSTEM_ADDRESS = + Address.fromHexString("0xfffffffffffffffffffffffffffffffffffffffe"); + + private final MainnetTransactionProcessor mainnetTransactionProcessor; + + public SystemCallProcessor(final MainnetTransactionProcessor mainnetTransactionProcessor) { + this.mainnetTransactionProcessor = mainnetTransactionProcessor; + } + + /** + * Processes a system call to a specified address, using the provided world state, block header, + * operation tracer, and block hash lookup. + * + * @param callAddress the address to call. + * @param worldState the current world state. + * @param blockHeader the current block header. + * @param operationTracer the operation tracer for tracing EVM operations. + * @param blockHashLookup the block hash lookup function. + * @return the output data from the call + */ + public Bytes process( + final Address callAddress, + final WorldUpdater worldState, + final ProcessableBlockHeader blockHeader, + final OperationTracer operationTracer, + final BlockHashOperation.BlockHashLookup blockHashLookup) { + + // if no code exists at CALL_ADDRESS, the call must fail silently + final Account maybeContract = worldState.get(callAddress); + if (maybeContract == null) { + LOG.trace("System call address not found {}", callAddress); + return null; + } + + final AbstractMessageProcessor messageProcessor = + mainnetTransactionProcessor.getMessageProcessor(MessageFrame.Type.MESSAGE_CALL); + final MessageFrame initialFrame = + createCallFrame(callAddress, worldState, blockHeader, blockHashLookup); + + return processFrame(initialFrame, messageProcessor, operationTracer, worldState); + } + + private Bytes processFrame( + final MessageFrame frame, + final AbstractMessageProcessor processor, + final OperationTracer tracer, + final WorldUpdater updater) { + + if (!frame.getCode().isValid()) { + throw new RuntimeException("System call did not execute to completion - opcode invalid"); + } + + Deque stack = frame.getMessageFrameStack(); + while (!stack.isEmpty()) { + processor.process(stack.peekFirst(), tracer); + } + + if (frame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { + updater.commit(); + return frame.getOutputData(); + } + + // the call must execute to completion + throw new RuntimeException("System call did not execute to completion"); + } + + private MessageFrame createCallFrame( + final Address callAddress, + final WorldUpdater worldUpdater, + final ProcessableBlockHeader blockHeader, + final BlockHashOperation.BlockHashLookup blockHashLookup) { + + final Optional maybeContract = Optional.ofNullable(worldUpdater.get(callAddress)); + final AbstractMessageProcessor processor = + mainnetTransactionProcessor.getMessageProcessor(MessageFrame.Type.MESSAGE_CALL); + + return MessageFrame.builder() + .maxStackSize(DEFAULT_MAX_STACK_SIZE) + .worldUpdater(worldUpdater) + .initialGas(30_000_000L) + .originator(SYSTEM_ADDRESS) + .gasPrice(Wei.ZERO) + .blobGasPrice(Wei.ZERO) + .value(Wei.ZERO) + .apparentValue(Wei.ZERO) + .blockValues(blockHeader) + .completer(__ -> {}) + .miningBeneficiary(Address.ZERO) // Confirm this + .type(MessageFrame.Type.MESSAGE_CALL) + .address(callAddress) + .contract(callAddress) + .inputData(Bytes.EMPTY) + .sender(SYSTEM_ADDRESS) + .blockHashLookup(blockHashLookup) + .code( + maybeContract + .map(c -> processor.getCodeFromEVM(c.getCodeHash(), c.getCode())) + .orElse(CodeV0.EMPTY_CODE)) + .build(); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidationParams.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidationParams.java index c60f376907d..be2185b09a7 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidationParams.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidationParams.java @@ -46,7 +46,7 @@ default boolean isAllowExceedingBalance() { } @Value.Default - default boolean isAllowMaxFeeGasBelowBaseFee() { + default boolean allowUnderpriced() { return false; } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidator.java index a70de0bcd7f..f23a0a98ee6 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidator.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -35,6 +35,7 @@ public interface TransactionValidator { ValidationResult validate( Transaction transaction, Optional baseFee, + Optional blobBaseFee, TransactionValidationParams transactionValidationParams); /** diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidatorFactory.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidatorFactory.java index e89ca469a8a..ff2bb53dace 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidatorFactory.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidatorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalsProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalsProcessor.java index d2b0f8edf78..81765a78ce5 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalsProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalsProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalsValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalsValidator.java index 2adf0f7f719..2adda62054b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalsValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalsValidator.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.mainnet; import static com.google.common.base.Preconditions.checkArgument; @@ -97,4 +96,17 @@ public boolean validateWithdrawalsRoot(final Block block) { return true; } } + + class NotApplicableWithdrawals implements WithdrawalsValidator { + + @Override + public boolean validateWithdrawals(final Optional> withdrawals) { + return true; + } + + @Override + public boolean validateWithdrawalsRoot(final Block block) { + return true; + } + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/blockhash/BlockHashProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/blockhash/BlockHashProcessor.java new file mode 100644 index 00000000000..e64b51afa41 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/blockhash/BlockHashProcessor.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.blockhash; + +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.plugin.data.ProcessableBlockHeader; + +public interface BlockHashProcessor { + + void processBlockHashes( + Blockchain blockchain, + MutableWorldState worldState, + ProcessableBlockHeader currentBlockHeader); +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/blockhash/CancunBlockHashProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/blockhash/CancunBlockHashProcessor.java new file mode 100644 index 00000000000..0e8b1ae8afa --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/blockhash/CancunBlockHashProcessor.java @@ -0,0 +1,43 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.blockhash; + +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.mainnet.ParentBeaconBlockRootHelper; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; +import org.hyperledger.besu.plugin.data.ProcessableBlockHeader; + +/** Processes the beacon block storage if it is present in the block header. */ +public class CancunBlockHashProcessor implements BlockHashProcessor { + + @Override + public void processBlockHashes( + final Blockchain blockchain, + final MutableWorldState mutableWorldState, + final ProcessableBlockHeader currentBlockHeader) { + currentBlockHeader + .getParentBeaconBlockRoot() + .ifPresent( + beaconBlockRoot -> { + if (!beaconBlockRoot.isEmpty()) { + WorldUpdater worldUpdater = mutableWorldState.updater(); + ParentBeaconBlockRootHelper.storeParentBeaconBlockRoot( + worldUpdater, currentBlockHeader.getTimestamp(), beaconBlockRoot); + worldUpdater.commit(); + } + }); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/blockhash/FrontierBlockHashProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/blockhash/FrontierBlockHashProcessor.java new file mode 100644 index 00000000000..f1722936b58 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/blockhash/FrontierBlockHashProcessor.java @@ -0,0 +1,29 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.blockhash; + +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.plugin.data.ProcessableBlockHeader; + +public class FrontierBlockHashProcessor implements BlockHashProcessor { + @Override + public void processBlockHashes( + final Blockchain blockchain, + final MutableWorldState mutableWorldState, + final ProcessableBlockHeader currentBlockHeader) { + // do nothing + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/blockhash/PragueBlockHashProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/blockhash/PragueBlockHashProcessor.java new file mode 100644 index 00000000000..b6b38b37d57 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/blockhash/PragueBlockHashProcessor.java @@ -0,0 +1,106 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.blockhash; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; +import org.hyperledger.besu.plugin.data.ProcessableBlockHeader; + +import com.google.common.annotations.VisibleForTesting; +import org.apache.tuweni.units.bigints.UInt256; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Processes and stores historical block hashes in accordance with EIP-2935. This class is + * responsible for managing the storage of block hashes to support EIP-2935, which introduces + * historical block hash access in smart contracts. + */ +public class PragueBlockHashProcessor extends CancunBlockHashProcessor { + private static final Logger LOG = LoggerFactory.getLogger(PragueBlockHashProcessor.class); + + public static final Address HISTORY_STORAGE_ADDRESS = + Address.fromHexString("0x0aae40965e6800cd9b1f4b05ff21581047e3f91e"); + + /** The HISTORY_SERVE_WINDOW */ + public static final long HISTORY_SERVE_WINDOW = 8192; + + private final long historySaveWindow; + private final Address historyStorageAddress; + + /** Constructs a BlockHashProcessor. */ + public PragueBlockHashProcessor() { + this(HISTORY_STORAGE_ADDRESS, HISTORY_SERVE_WINDOW); + } + + /** + * Constructs a BlockHashProcessor with a specified history save window. This constructor is + * primarily used for testing. + * + * @param historyStorageAddress the address of the contract storing the history + * @param historySaveWindow The number of blocks for which history should be saved. + */ + @VisibleForTesting + public PragueBlockHashProcessor( + final Address historyStorageAddress, final long historySaveWindow) { + this.historyStorageAddress = historyStorageAddress; + this.historySaveWindow = historySaveWindow; + } + + @Override + public void processBlockHashes( + final Blockchain blockchain, + final MutableWorldState mutableWorldState, + final ProcessableBlockHeader currentBlockHeader) { + super.processBlockHashes(blockchain, mutableWorldState, currentBlockHeader); + + WorldUpdater worldUpdater = mutableWorldState.updater(); + final MutableAccount historyStorageAccount = worldUpdater.getOrCreate(historyStorageAddress); + + if (currentBlockHeader.getNumber() > 0) { + storeParentHash(historyStorageAccount, currentBlockHeader); + } + worldUpdater.commit(); + } + + /** + * Stores the hash of the parent block in the world state. + * + * @param account The account associated with the historical block hash storage. + * @param header The current block header being processed. + */ + private void storeParentHash(final MutableAccount account, final ProcessableBlockHeader header) { + storeHash(account, header.getNumber() - 1, header.getParentHash()); + } + + /** + * Stores the hash in the world state. + * + * @param account The account associated with the historical block hash storage. + * @param number The slot to store. + * @param hash The hash to be stored. + */ + private void storeHash(final MutableAccount account, final long number, final Hash hash) { + UInt256 slot = UInt256.valueOf(number % historySaveWindow); + UInt256 value = UInt256.fromBytes(hash); + LOG.trace( + "Writing to {} {}=%{}", account.getAddress(), slot.toDecimalString(), value.toHexString()); + account.setStorageValue(slot, value); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/FeeMarket.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/FeeMarket.java index 637ffb471ec..fc5defbd105 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/FeeMarket.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/FeeMarket.java @@ -53,6 +53,10 @@ static BaseFeeMarket zeroBaseFee(final long londonForkBlockNumber) { return new ZeroBaseFeeMarket(londonForkBlockNumber); } + static BaseFeeMarket fixedBaseFee(final long londonForkBlockNumber, final Wei fixedBaseFee) { + return new FixedBaseFeeMarket(londonForkBlockNumber, fixedBaseFee); + } + static FeeMarket legacy() { return new LegacyFeeMarket(); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/FixedBaseFeeMarket.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/FixedBaseFeeMarket.java new file mode 100644 index 00000000000..e3155a74345 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/FixedBaseFeeMarket.java @@ -0,0 +1,41 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.feemarket; + +import org.hyperledger.besu.datatypes.Wei; + +import java.util.Optional; + +public class FixedBaseFeeMarket extends LondonFeeMarket { + + public FixedBaseFeeMarket(final long londonForkBlockNumber, final Wei fixedBaseFee) { + super(londonForkBlockNumber, Optional.of(fixedBaseFee)); + } + + @Override + public Wei computeBaseFee( + final long blockNumber, + final Wei parentBaseFee, + final long parentBlockGasUsed, + final long targetGasUsed) { + + return baseFeeInitialValue; + } + + @Override + public ValidationMode baseFeeValidationMode(final long blockNumber) { + return ValidationMode.NONE; + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LondonFeeMarket.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LondonFeeMarket.java index 2fff3a629fa..d1a148ceedf 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LondonFeeMarket.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LondonFeeMarket.java @@ -35,7 +35,7 @@ public class LondonFeeMarket implements BaseFeeMarket { private static final Wei DEFAULT_BASEFEE_FLOOR = Wei.of(7L); - private final Wei baseFeeInitialValue; + protected final Wei baseFeeInitialValue; private final long londonForkBlockNumber; private final TransactionPriceCalculator txPriceCalculator; private final Wei baseFeeFloor; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/ZeroBaseFeeMarket.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/ZeroBaseFeeMarket.java index c2f406cad04..156d1d9926b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/ZeroBaseFeeMarket.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/feemarket/ZeroBaseFeeMarket.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -16,22 +16,10 @@ import org.hyperledger.besu.datatypes.Wei; -import java.util.Optional; - -public class ZeroBaseFeeMarket extends LondonFeeMarket { +public class ZeroBaseFeeMarket extends FixedBaseFeeMarket { public ZeroBaseFeeMarket(final long londonForkBlockNumber) { - super(londonForkBlockNumber, Optional.of(Wei.ZERO)); - } - - @Override - public Wei computeBaseFee( - final long blockNumber, - final Wei parentBaseFee, - final long parentBlockGasUsed, - final long targetGasUsed) { - - return Wei.ZERO; + super(londonForkBlockNumber, Wei.ZERO); } @Override diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/AttachedComposedFromDetachedRule.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/AttachedComposedFromDetachedRule.java index 362cfba0e67..d5f83e4a1ec 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/AttachedComposedFromDetachedRule.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/AttachedComposedFromDetachedRule.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/BlobGasValidationRule.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/BlobGasValidationRule.java index e58d378562f..3a34d7be307 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/BlobGasValidationRule.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/BlobGasValidationRule.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,6 +17,7 @@ import org.hyperledger.besu.datatypes.BlobGas; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.mainnet.DetachedBlockHeaderValidationRule; +import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.slf4j.Logger; @@ -35,7 +36,7 @@ public BlobGasValidationRule(final GasCalculator gasCalculator) { /** * Validates the block header by checking if the header's excess blob gas matches the calculated - * value based on the parent header. + * value based on the parent header, as well that the used blobGas is a multiple of GAS_PER_BLOB. */ @Override public boolean validate(final BlockHeader header, final BlockHeader parent) { @@ -53,6 +54,13 @@ public boolean validate(final BlockHeader header, final BlockHeader parent) { calculatedExcessBlobGas); return false; } + long headerBlobGasUsed = header.getBlobGasUsed().orElse(0L); + if (headerBlobGasUsed % CancunGasCalculator.BLOB_GAS_PER_BLOB != 0) { + LOG.info( + "blob gas used must be multiple of GAS_PER_BLOB ({})", + CancunGasCalculator.BLOB_GAS_PER_BLOB); + return false; + } return true; } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/ConstantOmmersHashRule.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/ConstantOmmersHashRule.java index 61eb038f65f..aef185cbc6a 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/ConstantOmmersHashRule.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/ConstantOmmersHashRule.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.mainnet.headervalidationrules; import org.hyperledger.besu.datatypes.Hash; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/IncrementalTimestampRule.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/IncrementalTimestampRule.java index 72b5161a775..5a9b42b58e0 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/IncrementalTimestampRule.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/IncrementalTimestampRule.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.mainnet.headervalidationrules; import org.hyperledger.besu.ethereum.ProtocolContext; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/NoDifficultyRule.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/NoDifficultyRule.java index 51828cc3678..5795012937c 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/NoDifficultyRule.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/NoDifficultyRule.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.mainnet.headervalidationrules; import org.hyperledger.besu.ethereum.ProtocolContext; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/NoNonceRule.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/NoNonceRule.java index 400565fe868..976b6effce8 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/NoNonceRule.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/NoNonceRule.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.mainnet.headervalidationrules; import org.hyperledger.besu.ethereum.ProtocolContext; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/parallelization/MainnetParallelBlockProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/parallelization/MainnetParallelBlockProcessor.java new file mode 100644 index 00000000000..d1f0d9c5127 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/parallelization/MainnetParallelBlockProcessor.java @@ -0,0 +1,199 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.parallelization; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.mainnet.BlockProcessor; +import org.hyperledger.besu.ethereum.mainnet.MainnetBlockProcessor; +import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor; +import org.hyperledger.besu.ethereum.mainnet.MiningBeneficiaryCalculator; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecBuilder; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater; +import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldState; +import org.hyperledger.besu.evm.operation.BlockHashOperation; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; +import org.hyperledger.besu.metrics.BesuMetricCategory; +import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.metrics.Counter; + +import java.util.List; +import java.util.Optional; + +public class MainnetParallelBlockProcessor extends MainnetBlockProcessor { + + private final Optional metricsSystem; + private final Optional confirmedParallelizedTransactionCounter; + private final Optional conflictingButCachedTransactionCounter; + + public MainnetParallelBlockProcessor( + final MainnetTransactionProcessor transactionProcessor, + final TransactionReceiptFactory transactionReceiptFactory, + final Wei blockReward, + final MiningBeneficiaryCalculator miningBeneficiaryCalculator, + final boolean skipZeroBlockRewards, + final ProtocolSchedule protocolSchedule, + final MetricsSystem metricsSystem) { + super( + transactionProcessor, + transactionReceiptFactory, + blockReward, + miningBeneficiaryCalculator, + skipZeroBlockRewards, + protocolSchedule); + this.metricsSystem = Optional.of(metricsSystem); + this.confirmedParallelizedTransactionCounter = + Optional.of( + this.metricsSystem + .get() + .createCounter( + BesuMetricCategory.BLOCK_PROCESSING, + "parallelized_transactions_counter", + "Counter for the number of parallelized transactions during block processing")); + + this.conflictingButCachedTransactionCounter = + Optional.of( + this.metricsSystem + .get() + .createCounter( + BesuMetricCategory.BLOCK_PROCESSING, + "conflicted_transactions_counter", + "Counter for the number of conflicted transactions during block processing")); + } + + @Override + protected Optional runBlockPreProcessing( + final MutableWorldState worldState, + final PrivateMetadataUpdater privateMetadataUpdater, + final BlockHeader blockHeader, + final List transactions, + final Address miningBeneficiary, + final BlockHashOperation.BlockHashLookup blockHashLookup, + final Wei blobGasPrice) { + if ((worldState instanceof DiffBasedWorldState)) { + ParallelizedConcurrentTransactionProcessor parallelizedConcurrentTransactionProcessor = + new ParallelizedConcurrentTransactionProcessor(transactionProcessor); + // runAsyncBlock, if activated, facilitates the non-blocking parallel execution of + // transactions in the background through an optimistic strategy. + parallelizedConcurrentTransactionProcessor.runAsyncBlock( + worldState, + blockHeader, + transactions, + miningBeneficiary, + blockHashLookup, + blobGasPrice, + privateMetadataUpdater); + return Optional.of( + new ParallelizedPreProcessingContext(parallelizedConcurrentTransactionProcessor)); + } + return Optional.empty(); + } + + @Override + protected TransactionProcessingResult getTransactionProcessingResult( + final Optional preProcessingContext, + final MutableWorldState worldState, + final WorldUpdater blockUpdater, + final PrivateMetadataUpdater privateMetadataUpdater, + final BlockHeader blockHeader, + final Wei blobGasPrice, + final Address miningBeneficiary, + final Transaction transaction, + final int location, + final BlockHashOperation.BlockHashLookup blockHashLookup) { + + TransactionProcessingResult transactionProcessingResult = null; + + if (preProcessingContext.isPresent()) { + final ParallelizedPreProcessingContext parallelizedPreProcessingContext = + (ParallelizedPreProcessingContext) preProcessingContext.get(); + transactionProcessingResult = + parallelizedPreProcessingContext + .getParallelizedConcurrentTransactionProcessor() + .applyParallelizedTransactionResult( + worldState, + miningBeneficiary, + transaction, + location, + confirmedParallelizedTransactionCounter, + conflictingButCachedTransactionCounter) + .orElse(null); + } + + if (transactionProcessingResult == null) { + return super.getTransactionProcessingResult( + preProcessingContext, + worldState, + blockUpdater, + privateMetadataUpdater, + blockHeader, + blobGasPrice, + miningBeneficiary, + transaction, + location, + blockHashLookup); + } else { + return transactionProcessingResult; + } + } + + static class ParallelizedPreProcessingContext implements PreprocessingContext { + final ParallelizedConcurrentTransactionProcessor parallelizedConcurrentTransactionProcessor; + + public ParallelizedPreProcessingContext( + final ParallelizedConcurrentTransactionProcessor + parallelizedConcurrentTransactionProcessor) { + this.parallelizedConcurrentTransactionProcessor = parallelizedConcurrentTransactionProcessor; + } + + public ParallelizedConcurrentTransactionProcessor + getParallelizedConcurrentTransactionProcessor() { + return parallelizedConcurrentTransactionProcessor; + } + } + + public static class ParallelBlockProcessorBuilder + implements ProtocolSpecBuilder.BlockProcessorBuilder { + + final MetricsSystem metricsSystem; + + public ParallelBlockProcessorBuilder(final MetricsSystem metricsSystem) { + this.metricsSystem = metricsSystem; + } + + @Override + public BlockProcessor apply( + final MainnetTransactionProcessor transactionProcessor, + final TransactionReceiptFactory transactionReceiptFactory, + final Wei blockReward, + final MiningBeneficiaryCalculator miningBeneficiaryCalculator, + final boolean skipZeroBlockRewards, + final ProtocolSchedule protocolSchedule) { + return new MainnetParallelBlockProcessor( + transactionProcessor, + transactionReceiptFactory, + blockReward, + miningBeneficiaryCalculator, + skipZeroBlockRewards, + protocolSchedule, + metricsSystem); + } + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/parallelization/ParallelizedConcurrentTransactionProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/parallelization/ParallelizedConcurrentTransactionProcessor.java new file mode 100644 index 00000000000..c6beaa2f40e --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/parallelization/ParallelizedConcurrentTransactionProcessor.java @@ -0,0 +1,268 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.parallelization; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor; +import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater; +import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.NoopBonsaiCachedMerkleTrieLoader; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldState; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldState; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.DiffBasedWorldStateUpdateAccumulator; +import org.hyperledger.besu.evm.operation.BlockHashOperation; +import org.hyperledger.besu.evm.tracing.OperationTracer; +import org.hyperledger.besu.evm.worldstate.WorldView; +import org.hyperledger.besu.plugin.services.metrics.Counter; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +import com.google.common.annotations.VisibleForTesting; + +/** + * Optimizes transaction processing by executing transactions in parallel within a given block. + * Transactions are executed optimistically in a non-blocking manner. After execution, the class + * checks for potential conflicts among transactions to ensure data integrity before applying the + * results to the world state. + */ +@SuppressWarnings({"unchecked", "rawtypes"}) +public class ParallelizedConcurrentTransactionProcessor { + + private static final int NCPU = Runtime.getRuntime().availableProcessors(); + private static final Executor executor = Executors.newFixedThreadPool(NCPU); + + private final MainnetTransactionProcessor transactionProcessor; + + private final TransactionCollisionDetector transactionCollisionDetector; + + private final Map + parallelizedTransactionContextByLocation = new ConcurrentHashMap<>(); + + /** + * Constructs a PreloadConcurrentTransactionProcessor with a specified transaction processor. This + * processor is responsible for the individual processing of transactions. + * + * @param transactionProcessor The transaction processor for processing individual transactions. + */ + public ParallelizedConcurrentTransactionProcessor( + final MainnetTransactionProcessor transactionProcessor) { + this.transactionProcessor = transactionProcessor; + this.transactionCollisionDetector = new TransactionCollisionDetector(); + } + + @VisibleForTesting + public ParallelizedConcurrentTransactionProcessor( + final MainnetTransactionProcessor transactionProcessor, + final TransactionCollisionDetector transactionCollisionDetector) { + this.transactionProcessor = transactionProcessor; + this.transactionCollisionDetector = transactionCollisionDetector; + } + + /** + * Initiates the parallel and optimistic execution of transactions within a block by creating a + * copy of the world state for each transaction. This method processes transactions in a + * non-blocking manner. Transactions are executed against their respective copies of the world + * state, ensuring that the original world state passed as a parameter remains unmodified during + * this process. + * + * @param worldState Mutable world state intended for applying transaction results. This world + * state is not modified directly; instead, copies are made for transaction execution. + * @param blockHeader Header of the current block containing the transactions. + * @param transactions List of transactions to be processed. + * @param miningBeneficiary Address of the beneficiary to receive mining rewards. + * @param blockHashLookup Function for block hash lookup. + * @param blobGasPrice Gas price for blob transactions. + * @param privateMetadataUpdater Updater for private transaction metadata. + */ + public void runAsyncBlock( + final MutableWorldState worldState, + final BlockHeader blockHeader, + final List transactions, + final Address miningBeneficiary, + final BlockHashOperation.BlockHashLookup blockHashLookup, + final Wei blobGasPrice, + final PrivateMetadataUpdater privateMetadataUpdater) { + for (int i = 0; i < transactions.size(); i++) { + final Transaction transaction = transactions.get(i); + final int transactionLocation = i; + /* + * All transactions are executed in the background by copying the world state of the block on which the transactions need to be executed, ensuring that each one has its own accumulator. + */ + CompletableFuture.runAsync( + () -> + runTransaction( + worldState, + blockHeader, + transactionLocation, + transaction, + miningBeneficiary, + blockHashLookup, + blobGasPrice, + privateMetadataUpdater), + executor); + } + } + + @VisibleForTesting + public void runTransaction( + final MutableWorldState worldState, + final BlockHeader blockHeader, + final int transactionLocation, + final Transaction transaction, + final Address miningBeneficiary, + final BlockHashOperation.BlockHashLookup blockHashLookup, + final Wei blobGasPrice, + final PrivateMetadataUpdater privateMetadataUpdater) { + try (final DiffBasedWorldState roundWorldState = + new BonsaiWorldState( + (BonsaiWorldState) worldState, new NoopBonsaiCachedMerkleTrieLoader())) { + roundWorldState.freeze(); // make the clone frozen + final ParallelizedTransactionContext.Builder contextBuilder = + new ParallelizedTransactionContext.Builder(); + final DiffBasedWorldStateUpdateAccumulator roundWorldStateUpdater = + (DiffBasedWorldStateUpdateAccumulator) roundWorldState.updater(); + final TransactionProcessingResult result = + transactionProcessor.processTransaction( + roundWorldStateUpdater, + blockHeader, + transaction, + miningBeneficiary, + new OperationTracer() { + @Override + public void traceBeforeRewardTransaction( + final WorldView worldView, + final org.hyperledger.besu.datatypes.Transaction tx, + final Wei miningReward) { + /* + * This part checks if the mining beneficiary's account was accessed before increasing its balance for rewards. + * Indeed, if the transaction has interacted with the address to read or modify it, + * it means that the value is necessary for the proper execution of the transaction and will therefore be considered in collision detection. + * If this is not the case, we can ignore this address during conflict detection. + */ + if (transactionCollisionDetector + .getAddressesTouchedByTransaction( + transaction, Optional.of(roundWorldStateUpdater)) + .contains(miningBeneficiary)) { + contextBuilder.isMiningBeneficiaryTouchedPreRewardByTransaction(true); + } + contextBuilder.miningBeneficiaryReward(miningReward); + } + }, + blockHashLookup, + true, + TransactionValidationParams.processingBlock(), + privateMetadataUpdater, + blobGasPrice); + + // commit the accumulator in order to apply all the modifications + roundWorldState.getAccumulator().commit(); + + contextBuilder + .transactionAccumulator(roundWorldState.getAccumulator()) + .transactionProcessingResult(result); + + final ParallelizedTransactionContext parallelizedTransactionContext = contextBuilder.build(); + if (!parallelizedTransactionContext.isMiningBeneficiaryTouchedPreRewardByTransaction()) { + /* + * If the address of the mining beneficiary has been touched only for adding rewards, + * we remove it from the accumulator to avoid a false positive collision. + * The balance will be increased during the sequential processing. + */ + roundWorldStateUpdater.getAccountsToUpdate().remove(miningBeneficiary); + } + parallelizedTransactionContextByLocation.put( + transactionLocation, parallelizedTransactionContext); + } + } + + /** + * Applies the results of parallelized transactions to the world state after checking for + * conflicts. + * + *

If a transaction was executed optimistically without any detected conflicts, its result is + * directly applied to the world state. If there is a conflict, this method does not apply the + * transaction's modifications directly to the world state. Instead, it caches the data read from + * the database during the transaction's execution. This cached data is then used to optimize the + * replay of the transaction by reducing the need for additional reads from the disk, thereby + * making the replay process faster. This approach ensures that the integrity of the world state + * is maintained while optimizing the performance of transaction processing. + * + * @param worldState Mutable world state intended for applying transaction results. + * @param miningBeneficiary Address of the beneficiary for mining rewards. + * @param transaction Transaction for which the result is to be applied. + * @param transactionLocation Index of the transaction within the block. + * @param confirmedParallelizedTransactionCounter Metric counter for confirmed parallelized + * transactions + * @param conflictingButCachedTransactionCounter Metric counter for conflicting but cached + * transactions + * @return Optional containing the transaction processing result if applied, or empty if the + * transaction needs to be replayed due to a conflict. + */ + public Optional applyParallelizedTransactionResult( + final MutableWorldState worldState, + final Address miningBeneficiary, + final Transaction transaction, + final int transactionLocation, + final Optional confirmedParallelizedTransactionCounter, + final Optional conflictingButCachedTransactionCounter) { + final DiffBasedWorldState diffBasedWorldState = (DiffBasedWorldState) worldState; + final DiffBasedWorldStateUpdateAccumulator blockAccumulator = + (DiffBasedWorldStateUpdateAccumulator) diffBasedWorldState.updater(); + final ParallelizedTransactionContext parallelizedTransactionContext = + parallelizedTransactionContextByLocation.remove(transactionLocation); + /* + * If `parallelizedTransactionContext` is not null, it means that the transaction had time to complete in the background. + */ + if (parallelizedTransactionContext != null) { + final DiffBasedWorldStateUpdateAccumulator transactionAccumulator = + parallelizedTransactionContext.transactionAccumulator(); + final TransactionProcessingResult transactionProcessingResult = + parallelizedTransactionContext.transactionProcessingResult(); + final boolean hasCollision = + transactionCollisionDetector.hasCollision( + transaction, miningBeneficiary, parallelizedTransactionContext, blockAccumulator); + if (transactionProcessingResult.isSuccessful() && !hasCollision) { + blockAccumulator + .getOrCreate(miningBeneficiary) + .incrementBalance(parallelizedTransactionContext.miningBeneficiaryReward()); + + blockAccumulator.importStateChangesFromSource(transactionAccumulator); + + if (confirmedParallelizedTransactionCounter.isPresent()) + confirmedParallelizedTransactionCounter.get().inc(); + return Optional.of(transactionProcessingResult); + } else { + blockAccumulator.importPriorStateFromSource(transactionAccumulator); + if (conflictingButCachedTransactionCounter.isPresent()) + conflictingButCachedTransactionCounter.get().inc(); + // If there is a conflict, we return an empty result to signal the block processor to + // re-execute the transaction. + return Optional.empty(); + } + } + return Optional.empty(); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/parallelization/ParallelizedTransactionContext.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/parallelization/ParallelizedTransactionContext.java new file mode 100644 index 00000000000..30305ce00b5 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/parallelization/ParallelizedTransactionContext.java @@ -0,0 +1,133 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.parallelization; + +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.DiffBasedWorldStateUpdateAccumulator; + +import java.util.Objects; + +public final class ParallelizedTransactionContext { + private final DiffBasedWorldStateUpdateAccumulator transactionAccumulator; + private final TransactionProcessingResult transactionProcessingResult; + private final boolean isMiningBeneficiaryTouchedPreRewardByTransaction; + private final Wei miningBeneficiaryReward; + + public ParallelizedTransactionContext( + final DiffBasedWorldStateUpdateAccumulator transactionAccumulator, + final TransactionProcessingResult transactionProcessingResult, + final boolean isMiningBeneficiaryTouchedPreRewardByTransaction, + final Wei miningBeneficiaryReward) { + this.transactionAccumulator = transactionAccumulator; + this.transactionProcessingResult = transactionProcessingResult; + this.isMiningBeneficiaryTouchedPreRewardByTransaction = + isMiningBeneficiaryTouchedPreRewardByTransaction; + this.miningBeneficiaryReward = miningBeneficiaryReward; + } + + public DiffBasedWorldStateUpdateAccumulator transactionAccumulator() { + return transactionAccumulator; + } + + public TransactionProcessingResult transactionProcessingResult() { + return transactionProcessingResult; + } + + public boolean isMiningBeneficiaryTouchedPreRewardByTransaction() { + return isMiningBeneficiaryTouchedPreRewardByTransaction; + } + + public Wei miningBeneficiaryReward() { + return miningBeneficiaryReward; + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (ParallelizedTransactionContext) obj; + return Objects.equals(this.transactionAccumulator, that.transactionAccumulator) + && Objects.equals(this.transactionProcessingResult, that.transactionProcessingResult) + && this.isMiningBeneficiaryTouchedPreRewardByTransaction + == that.isMiningBeneficiaryTouchedPreRewardByTransaction + && Objects.equals(this.miningBeneficiaryReward, that.miningBeneficiaryReward); + } + + @Override + public int hashCode() { + return Objects.hash( + transactionAccumulator, + transactionProcessingResult, + isMiningBeneficiaryTouchedPreRewardByTransaction, + miningBeneficiaryReward); + } + + @Override + public String toString() { + return "ParallelizedTransactionContext[" + + "transactionAccumulator=" + + transactionAccumulator + + ", " + + "transactionProcessingResult=" + + transactionProcessingResult + + ", " + + "isMiningBeneficiaryTouchedPreRewardByTransaction=" + + isMiningBeneficiaryTouchedPreRewardByTransaction + + ", " + + "miningBeneficiaryReward=" + + miningBeneficiaryReward + + ']'; + } + + public static class Builder { + private DiffBasedWorldStateUpdateAccumulator transactionAccumulator; + private TransactionProcessingResult transactionProcessingResult; + private boolean isMiningBeneficiaryTouchedPreRewardByTransaction; + private Wei miningBeneficiaryReward = Wei.ZERO; + + public Builder transactionAccumulator( + final DiffBasedWorldStateUpdateAccumulator transactionAccumulator) { + this.transactionAccumulator = transactionAccumulator; + return this; + } + + public Builder transactionProcessingResult( + final TransactionProcessingResult transactionProcessingResult) { + this.transactionProcessingResult = transactionProcessingResult; + return this; + } + + public Builder isMiningBeneficiaryTouchedPreRewardByTransaction( + final boolean isMiningBeneficiaryTouchedPreRewardByTransaction) { + this.isMiningBeneficiaryTouchedPreRewardByTransaction = + isMiningBeneficiaryTouchedPreRewardByTransaction; + return this; + } + + public Builder miningBeneficiaryReward(final Wei miningBeneficiaryReward) { + this.miningBeneficiaryReward = miningBeneficiaryReward; + return this; + } + + public ParallelizedTransactionContext build() { + return new ParallelizedTransactionContext( + transactionAccumulator, + transactionProcessingResult, + isMiningBeneficiaryTouchedPreRewardByTransaction, + miningBeneficiaryReward); + } + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/parallelization/TransactionCollisionDetector.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/parallelization/TransactionCollisionDetector.java new file mode 100644 index 00000000000..4120c0edb1e --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/parallelization/TransactionCollisionDetector.java @@ -0,0 +1,114 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.parallelization; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.DiffBasedWorldStateUpdateAccumulator; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Optional; +import java.util.Set; + +public class TransactionCollisionDetector { + + /** + * Determines if a transaction has a collision based on the addresses it touches. A collision + * occurs if the transaction touches the mining beneficiary address or if there are common + * addresses touched by both the transaction and other transactions within the same block. + * + * @param transaction The transaction to check for collisions. + * @param miningBeneficiary The address of the mining beneficiary. + * @param parallelizedTransactionContext The context containing the accumulator for the + * transaction. + * @param blockAccumulator The accumulator for the block. + * @return true if there is a collision; false otherwise. + */ + public boolean hasCollision( + final Transaction transaction, + final Address miningBeneficiary, + final ParallelizedTransactionContext parallelizedTransactionContext, + final DiffBasedWorldStateUpdateAccumulator blockAccumulator) { + final Set

addressesTouchedByTransaction = + getAddressesTouchedByTransaction( + transaction, Optional.of(parallelizedTransactionContext.transactionAccumulator())); + if (addressesTouchedByTransaction.contains(miningBeneficiary)) { + return true; + } + final Set
addressesTouchedByBlock = + getAddressesTouchedByBlock(Optional.of(blockAccumulator)); + final Iterator
it = addressesTouchedByTransaction.iterator(); + boolean commonAddressFound = false; + while (it.hasNext() && !commonAddressFound) { + if (addressesTouchedByBlock.contains(it.next())) { + commonAddressFound = true; + } + } + return commonAddressFound; + } + + /** + * Retrieves the set of addresses that were touched by a transaction. This includes the sender and + * recipient of the transaction, as well as any addresses that were read from or written to by the + * transaction's execution. + * + * @param transaction The transaction to analyze. + * @param accumulator An optional accumulator containing state changes made by the transaction. + * @return A set of addresses touched by the transaction. + */ + public Set
getAddressesTouchedByTransaction( + final Transaction transaction, + final Optional> accumulator) { + HashSet
addresses = new HashSet<>(); + addresses.add(transaction.getSender()); + if (transaction.getTo().isPresent()) { + addresses.add(transaction.getTo().get()); + } + accumulator.ifPresent( + diffBasedWorldStateUpdateAccumulator -> { + diffBasedWorldStateUpdateAccumulator + .getAccountsToUpdate() + .forEach((address, diffBasedValue) -> addresses.add(address)); + addresses.addAll(diffBasedWorldStateUpdateAccumulator.getDeletedAccountAddresses()); + }); + return addresses; + } + + /** + * Retrieves the set of addresses that were touched by all transactions within a block. This + * method filters out addresses that were only read and not modified. + * + * @param accumulator An optional accumulator containing state changes made by the block. + * @return A set of addresses that were modified by the block's transactions. + */ + private Set
getAddressesTouchedByBlock( + final Optional> accumulator) { + HashSet
addresses = new HashSet<>(); + accumulator.ifPresent( + diffBasedWorldStateUpdateAccumulator -> { + diffBasedWorldStateUpdateAccumulator + .getAccountsToUpdate() + .forEach( + (address, diffBasedValue) -> { + if (!diffBasedValue.isUnchanged()) { + addresses.add(address); + } + }); + addresses.addAll(diffBasedWorldStateUpdateAccumulator.getDeletedAccountAddresses()); + }); + return addresses; + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/FlexiblePrivacyPrecompiledContract.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/FlexiblePrivacyPrecompiledContract.java index 0dd6863f94e..98709a4eaad 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/FlexiblePrivacyPrecompiledContract.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/FlexiblePrivacyPrecompiledContract.java @@ -70,13 +70,15 @@ public FlexiblePrivacyPrecompiledContract( final Enclave enclave, final WorldStateArchive worldStateArchive, final PrivateStateRootResolver privateStateRootResolver, - final PrivateStateGenesisAllocator privateStateGenesisAllocator) { + final PrivateStateGenesisAllocator privateStateGenesisAllocator, + final boolean alwaysIncrementPrivateNonce) { super( gasCalculator, enclave, worldStateArchive, privateStateRootResolver, privateStateGenesisAllocator, + alwaysIncrementPrivateNonce, "FlexiblePrivacy"); } @@ -87,7 +89,8 @@ public FlexiblePrivacyPrecompiledContract( privacyParameters.getEnclave(), privacyParameters.getPrivateWorldStateArchive(), privacyParameters.getPrivateStateRootResolver(), - privacyParameters.getPrivateStateGenesisAllocator()); + privacyParameters.getPrivateStateGenesisAllocator(), + privacyParameters.isPrivateNonceAlwaysIncrementsEnabled()); } public long addPrivateTransactionObserver(final PrivateTransactionObserver observer) { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java index b1a754c9fe2..7e9bfa1ac4b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContract.java @@ -19,6 +19,7 @@ import static org.hyperledger.besu.ethereum.mainnet.PrivateStateUtils.KEY_TRANSACTION_HASH; import static org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver.EMPTY_ROOT_HASH; +import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.enclave.Enclave; import org.hyperledger.besu.enclave.EnclaveClientException; @@ -40,6 +41,7 @@ import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.evm.account.MutableAccount; import org.hyperledger.besu.evm.frame.BlockValues; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -61,6 +63,7 @@ public class PrivacyPrecompiledContract extends AbstractPrecompiledContract { final WorldStateArchive privateWorldStateArchive; final PrivateStateRootResolver privateStateRootResolver; private final PrivateStateGenesisAllocator privateStateGenesisAllocator; + final boolean alwaysIncrementPrivateNonce; PrivateTransactionProcessor privateTransactionProcessor; private static final Logger LOG = LoggerFactory.getLogger(PrivacyPrecompiledContract.class); @@ -79,6 +82,7 @@ public PrivacyPrecompiledContract( privacyParameters.getPrivateWorldStateArchive(), privacyParameters.getPrivateStateRootResolver(), privacyParameters.getPrivateStateGenesisAllocator(), + privacyParameters.isPrivateNonceAlwaysIncrementsEnabled(), name); } @@ -88,12 +92,14 @@ protected PrivacyPrecompiledContract( final WorldStateArchive worldStateArchive, final PrivateStateRootResolver privateStateRootResolver, final PrivateStateGenesisAllocator privateStateGenesisAllocator, + final boolean alwaysIncrementPrivateNonce, final String name) { super(name, gasCalculator); this.enclave = enclave; this.privateWorldStateArchive = worldStateArchive; this.privateStateRootResolver = privateStateRootResolver; this.privateStateGenesisAllocator = privateStateGenesisAllocator; + this.alwaysIncrementPrivateNonce = alwaysIncrementPrivateNonce; } public void setPrivateTransactionProcessor( @@ -181,18 +187,31 @@ public PrecompileContractResult computePrecompile( processPrivateTransaction( messageFrame, privateTransaction, privacyGroupId, privateWorldStateUpdater); - if (result.isInvalid() || !result.isSuccessful()) { + final Boolean isPersistingPrivateState = + messageFrame.getContextVariable(KEY_IS_PERSISTING_PRIVATE_STATE, false); + + if (!result.isSuccessful()) { LOG.error( "Failed to process private transaction {}: {}", pmtHash, result.getValidationResult().getErrorMessage()); - - privateMetadataUpdater.putTransactionReceipt(pmtHash, new PrivateTransactionReceipt(result)); - + if (isPersistingPrivateState && alwaysIncrementPrivateNonce) { + final Address senderAddress = privateTransaction.getSender(); + final MutableAccount senderAccount = privateWorldStateUpdater.getOrCreate(senderAddress); + senderAccount.incrementNonce(); + // we can safely commit the updater here, because it is only changed if the transaction is + // successful, + // so we can be sure that the only change is the incremented nonce + privateWorldStateUpdater.commit(); + disposablePrivateState.persist(null); + + storePrivateMetadata( + pmtHash, privacyGroupId, disposablePrivateState, privateMetadataUpdater, result); + } return NO_RESULT; } - if (messageFrame.getContextVariable(KEY_IS_PERSISTING_PRIVATE_STATE, false)) { + if (isPersistingPrivateState) { privateWorldStateUpdater.commit(); disposablePrivateState.persist(null); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/AbstractSystemCallRequestProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/AbstractSystemCallRequestProcessor.java new file mode 100644 index 00000000000..a7d959f4b98 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/AbstractSystemCallRequestProcessor.java @@ -0,0 +1,102 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.requests; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.core.Request; +import org.hyperledger.besu.ethereum.mainnet.SystemCallProcessor; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; + +/** + * Abstract base class for processing system call requests. + * + * @param The type of request to be processed. + */ +public abstract class AbstractSystemCallRequestProcessor + implements RequestProcessor { + + /** + * Processes a system call and converts the result into requests of type T. + * + * @param context The request context being processed. + * @return An {@link Optional} containing a list of {@link T} objects if any are found + */ + @Override + public Optional> process(final ProcessRequestContext context) { + + SystemCallProcessor systemCallProcessor = + new SystemCallProcessor(context.protocolSpec().getTransactionProcessor()); + + Bytes systemCallOutput = + systemCallProcessor.process( + getCallAddress(), + context.mutableWorldState().updater(), + context.blockHeader(), + context.operationTracer(), + context.blockHashLookup()); + + List requests = parseRequests(systemCallOutput); + return Optional.ofNullable(requests); + } + + /** + * Parses the provided bytes into a list of {@link T} objects. + * + * @param bytes The bytes representing requests. + * @return A list of parsed {@link T} objects. + */ + protected List parseRequests(final Bytes bytes) { + if (bytes == null) { + return null; + } + final List requests = new ArrayList<>(); + if (bytes.isEmpty()) { + return requests; + } + int count = bytes.size() / getRequestBytesSize(); + for (int i = 0; i < count; i++) { + Bytes requestBytes = bytes.slice(i * getRequestBytesSize(), getRequestBytesSize()); + requests.add(parseRequest(requestBytes)); + } + return requests; + } + + /** + * Parses a single request from the provided bytes. + * + * @param requestBytes The bytes representing a single request. + * @return A parsed {@link T} object. + */ + protected abstract T parseRequest(final Bytes requestBytes); + + /** + * Gets the call address for the specific request type. + * + * @return The call address. + */ + protected abstract Address getCallAddress(); + + /** + * Gets the size of the bytes representing a single request. + * + * @return The size of the bytes representing a single request. + */ + protected abstract int getRequestBytesSize(); +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/ConsolidationRequestProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/ConsolidationRequestProcessor.java new file mode 100644 index 00000000000..0a48d8278c9 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/ConsolidationRequestProcessor.java @@ -0,0 +1,68 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.requests; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.ethereum.core.ConsolidationRequest; + +import org.apache.tuweni.bytes.Bytes; + +public class ConsolidationRequestProcessor + extends AbstractSystemCallRequestProcessor { + public static final Address CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS = + Address.fromHexString("0x00b42dbF2194e931E80326D950320f7d9Dbeac02"); + + private static final int ADDRESS_BYTES = 20; + private static final int PUBLIC_KEY_BYTES = 48; + private static final int CONSOLIDATION_REQUEST_BYTES_SIZE = + ADDRESS_BYTES + PUBLIC_KEY_BYTES + PUBLIC_KEY_BYTES; + + /** + * Gets the call address for consolidation requests. + * + * @return The call address. + */ + @Override + protected Address getCallAddress() { + return CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS; + } + + /** + * Gets the size of the bytes representing a single consolidation request. + * + * @return The size of the bytes representing a single consolidation request. + */ + @Override + protected int getRequestBytesSize() { + return CONSOLIDATION_REQUEST_BYTES_SIZE; + } + + /** + * Parses a single consolidation request from the provided bytes. + * + * @param requestBytes The bytes representing a single consolidation request. + * @return A parsed {@link ConsolidationRequest} object. + */ + @Override + protected ConsolidationRequest parseRequest(final Bytes requestBytes) { + final Address sourceAddress = Address.wrap(requestBytes.slice(0, ADDRESS_BYTES)); + final BLSPublicKey sourcePublicKey = + BLSPublicKey.wrap(requestBytes.slice(ADDRESS_BYTES, PUBLIC_KEY_BYTES)); + final BLSPublicKey targetPublicKey = + BLSPublicKey.wrap(requestBytes.slice(ADDRESS_BYTES + PUBLIC_KEY_BYTES, PUBLIC_KEY_BYTES)); + return new ConsolidationRequest(sourceAddress, sourcePublicKey, targetPublicKey); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/ConsolidationRequestValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/ConsolidationRequestValidator.java new file mode 100644 index 00000000000..f03de5e6b53 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/ConsolidationRequestValidator.java @@ -0,0 +1,92 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.requests; + +import static org.hyperledger.besu.ethereum.mainnet.requests.RequestUtil.getConsolidationRequests; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.ConsolidationRequest; +import org.hyperledger.besu.ethereum.core.Request; +import org.hyperledger.besu.ethereum.core.TransactionReceipt; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ConsolidationRequestValidator implements RequestValidator { + + private static final Logger LOG = LoggerFactory.getLogger(ConsolidationRequestValidator.class); + + public static final int MAX_CONSOLIDATION_REQUESTS_PER_BLOCK = 1; + + private boolean validateConsolidationRequestParameter( + final Optional> consolidationRequests) { + return consolidationRequests.isPresent(); + } + + private boolean validateConsolidationRequestsInBlock( + final Block block, final List consolidationRequests) { + final Hash blockHash = block.getHash(); + + final List consolidationRequestsInBlock = + block + .getBody() + .getRequests() + .flatMap(requests -> getConsolidationRequests(Optional.of(requests))) + .orElse(Collections.emptyList()); + + if (consolidationRequestsInBlock.size() > MAX_CONSOLIDATION_REQUESTS_PER_BLOCK) { + LOG.warn( + "Block {} has more than the allowed maximum number of consolidation requests", blockHash); + return false; + } + + // Validate ConsolidationRequests + final boolean expectedConsolidationRequestMatch = + consolidationRequests.equals(consolidationRequestsInBlock); + if (!expectedConsolidationRequestMatch) { + LOG.warn( + "Block {} has a mismatch between block consolidations and RPC consolidation requests (in_block = {}, " + + "expected = {})", + blockHash, + consolidationRequestsInBlock, + consolidationRequests); + return false; + } + return true; + } + + @Override + public boolean validate( + final Block block, final List requests, final List receipts) { + var consolidationRequests = + getConsolidationRequests(Optional.of(requests)).orElse(Collections.emptyList()); + return validateConsolidationRequestsInBlock(block, consolidationRequests); + } + + @Override + public boolean validateParameter(final Optional> request) { + if (request.isEmpty()) { + return true; + } + var consolidationRequests = + RequestUtil.filterRequestsOfType(request.get(), ConsolidationRequest.class); + return validateConsolidationRequestParameter(Optional.of(consolidationRequests)); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/DepositRequestProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/DepositRequestProcessor.java new file mode 100644 index 00000000000..8902ecc510a --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/DepositRequestProcessor.java @@ -0,0 +1,63 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.requests; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.core.DepositRequest; +import org.hyperledger.besu.ethereum.core.Request; +import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.core.encoding.DepositRequestDecoder; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import com.google.common.annotations.VisibleForTesting; + +public class DepositRequestProcessor implements RequestProcessor { + + public static final Address DEFAULT_DEPOSIT_CONTRACT_ADDRESS = + Address.fromHexString("0x00000000219ab540356cbb839cbe05303d7705fa"); + + private final Optional
depositContractAddress; + + public DepositRequestProcessor(final Address depositContractAddress) { + this.depositContractAddress = Optional.ofNullable(depositContractAddress); + } + + @Override + public Optional> process(final ProcessRequestContext context) { + if (depositContractAddress.isEmpty()) { + return Optional.empty(); + } + List depositRequests = + findDepositRequestsFromReceipts(context.transactionReceipts()); + return Optional.of(depositRequests); + } + + @VisibleForTesting + List findDepositRequestsFromReceipts( + final List transactionReceipts) { + return depositContractAddress + .map( + address -> + transactionReceipts.stream() + .flatMap(receipt -> receipt.getLogsList().stream()) + .filter(log -> address.equals(log.getLogger())) + .map(DepositRequestDecoder::decodeFromLog) + .toList()) + .orElse(Collections.emptyList()); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/DepositRequestValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/DepositRequestValidator.java new file mode 100644 index 00000000000..6c9eedf305d --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/DepositRequestValidator.java @@ -0,0 +1,86 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.requests; + +import static org.hyperledger.besu.ethereum.mainnet.requests.RequestUtil.getDepositRequests; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.DepositRequest; +import org.hyperledger.besu.ethereum.core.Request; +import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.core.encoding.DepositRequestDecoder; +import org.hyperledger.besu.evm.log.Log; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DepositRequestValidator implements RequestValidator { + + private static final Logger LOG = LoggerFactory.getLogger(DepositRequestValidator.class); + private final Address depositContractAddress; + + public DepositRequestValidator(final Address depositContractAddress) { + this.depositContractAddress = depositContractAddress; + } + + @Override + public boolean validateParameter(final Optional> depositRequests) { + return depositRequests.isPresent(); + } + + public boolean validateDepositRequests( + final Block block, + final List actualDepositRequests, + final List receipts) { + + List expectedDepositRequests = new ArrayList<>(); + + for (TransactionReceipt receipt : receipts) { + for (Log log : receipt.getLogsList()) { + if (depositContractAddress.equals(log.getLogger())) { + DepositRequest depositRequest = DepositRequestDecoder.decodeFromLog(log); + expectedDepositRequests.add(depositRequest); + } + } + } + + boolean isValid = actualDepositRequests.equals(expectedDepositRequests); + + if (!isValid) { + LOG.warn( + "Deposits validation failed. Deposits from block body do not match deposits from logs. Block hash: {}", + block.getHash()); + LOG.debug( + "Deposits from logs: {}, deposits from block body: {}", + expectedDepositRequests, + actualDepositRequests); + } + + return isValid; + } + + @Override + public boolean validate( + final Block block, final List requests, final List receipts) { + var depositRequests = getDepositRequests(Optional.of(requests)).orElse(Collections.emptyList()); + return validateDepositRequests(block, depositRequests, receipts); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/MainnetRequestsValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/MainnetRequestsValidator.java new file mode 100644 index 00000000000..6e61a0343c3 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/MainnetRequestsValidator.java @@ -0,0 +1,40 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.requests; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.RequestType; + +public class MainnetRequestsValidator { + public static RequestsValidatorCoordinator pragueRequestsValidator( + final Address depositContractAddress) { + return new RequestsValidatorCoordinator.Builder() + .addValidator(RequestType.WITHDRAWAL, new WithdrawalRequestValidator()) + .addValidator(RequestType.CONSOLIDATION, new ConsolidationRequestValidator()) + .addValidator(RequestType.DEPOSIT, new DepositRequestValidator(depositContractAddress)) + .build(); + } + + public static RequestProcessorCoordinator pragueRequestsProcessors( + final Address withdrawalRequestContractAddress, final Address depositContractAddress) { + return new RequestProcessorCoordinator.Builder() + .addProcessor( + RequestType.WITHDRAWAL, + new WithdrawalRequestProcessor(withdrawalRequestContractAddress)) + .addProcessor(RequestType.CONSOLIDATION, new ConsolidationRequestProcessor()) + .addProcessor(RequestType.DEPOSIT, new DepositRequestProcessor(depositContractAddress)) + .build(); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/ProcessRequestContext.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/ProcessRequestContext.java new file mode 100644 index 00000000000..63f4a8d5144 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/ProcessRequestContext.java @@ -0,0 +1,32 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.requests; + +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; +import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; +import org.hyperledger.besu.evm.tracing.OperationTracer; + +import java.util.List; + +public record ProcessRequestContext( + ProcessableBlockHeader blockHeader, + MutableWorldState mutableWorldState, + ProtocolSpec protocolSpec, + List transactionReceipts, + BlockHashLookup blockHashLookup, + OperationTracer operationTracer) {} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/ProhibitedRequestsValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/ProhibitedRequestsValidator.java new file mode 100644 index 00000000000..362da527283 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/ProhibitedRequestsValidator.java @@ -0,0 +1,53 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.requests; + +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.Request; +import org.hyperledger.besu.ethereum.core.TransactionReceipt; + +import java.util.List; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Validates that a block does not contain any prohibited requests. */ +public class ProhibitedRequestsValidator implements RequestValidator { + private static final Logger LOG = LoggerFactory.getLogger(ProhibitedRequestsValidator.class); + + @Override + public boolean validate( + final Block block, final List request, final List receipts) { + boolean hasRequests = block.getBody().getRequests().isPresent(); + boolean hasRequestsRoot = block.getHeader().getRequestsRoot().isPresent(); + + if (hasRequests) { + LOG.warn("Block {} contains requests but requests are prohibited", block.getHash()); + } + + if (hasRequestsRoot) { + LOG.warn( + "Block {} header contains requests_root but requests are prohibited", block.getHash()); + } + + return !(hasRequests || hasRequestsRoot); + } + + @Override + public boolean validateParameter(final Optional> request) { + return request.isEmpty(); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestProcessor.java new file mode 100644 index 00000000000..55f3cd41788 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestProcessor.java @@ -0,0 +1,24 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.requests; + +import org.hyperledger.besu.ethereum.core.Request; + +import java.util.List; +import java.util.Optional; + +public interface RequestProcessor { + Optional> process(final ProcessRequestContext context); +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestProcessorCoordinator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestProcessorCoordinator.java new file mode 100644 index 00000000000..b98274729d5 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestProcessorCoordinator.java @@ -0,0 +1,68 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.requests; + +import org.hyperledger.besu.datatypes.RequestType; +import org.hyperledger.besu.ethereum.core.Request; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import com.google.common.collect.ImmutableSortedMap; + +/** Processes various types of requests based on their RequestType. */ +public class RequestProcessorCoordinator { + private final ImmutableSortedMap processors; + + /** + * Constructs a RequestsProcessor with a given map of processors. + * + * @param processors A map associating RequestType with their corresponding RequestProcessor. + */ + private RequestProcessorCoordinator( + final ImmutableSortedMap processors) { + this.processors = processors; + } + + public Optional> process(final ProcessRequestContext context) { + List requests = null; + for (RequestProcessor requestProcessor : processors.values()) { + var r = requestProcessor.process(context); + if (r.isPresent()) { + if (requests == null) { + requests = new ArrayList<>(); + } + requests.addAll(r.get()); + } + } + return Optional.ofNullable(requests); + } + + public static class Builder { + private final ImmutableSortedMap.Builder + requestProcessorBuilder = ImmutableSortedMap.naturalOrder(); + + public RequestProcessorCoordinator.Builder addProcessor( + final RequestType type, final RequestProcessor processor) { + this.requestProcessorBuilder.put(type, processor); + return this; + } + + public RequestProcessorCoordinator build() { + return new RequestProcessorCoordinator(requestProcessorBuilder.build()); + } + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestUtil.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestUtil.java new file mode 100644 index 00000000000..f3a10161a49 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestUtil.java @@ -0,0 +1,85 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.requests; + +import org.hyperledger.besu.ethereum.core.ConsolidationRequest; +import org.hyperledger.besu.ethereum.core.DepositRequest; +import org.hyperledger.besu.ethereum.core.Request; +import org.hyperledger.besu.ethereum.core.WithdrawalRequest; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +public class RequestUtil { + + /** + * Filters and returns a list of requests of a specific type from a given list of requests. + * + * @param The type of the request to filter by, extending Request. + * @param requests The list of requests to filter. + * @param requestType The class of the request type to filter for. + * @return A List containing only requests of the specified type, or an empty list if the input + * list is null or contains no requests of the specified type. + */ + public static List filterRequestsOfType( + final List requests, final Class requestType) { + if (requests == null) { + return Collections.emptyList(); + } + return requests.stream().filter(requestType::isInstance).map(requestType::cast).toList(); + } + + public static Optional> getDepositRequests( + final Optional> requests) { + return requests.map(r -> filterRequestsOfType(r, DepositRequest.class)); + } + + public static Optional> getWithdrawalRequests( + final Optional> requests) { + return requests.map(r -> filterRequestsOfType(r, WithdrawalRequest.class)); + } + + public static Optional> getConsolidationRequests( + final Optional> requests) { + return requests.map(r -> filterRequestsOfType(r, ConsolidationRequest.class)); + } + + /** + * Combines multiple optional lists of requests into a single optional list. + * + * @param maybeDepositRequests Optional list of deposit requests. + * @param maybeWithdrawalRequest Optional list of withdrawal requests. + * @param maybeConsolidationRequest Optional list of withdrawal requests. + * @return An Optional containing the combined list of requests, or an empty Optional if all input + * lists are empty. + */ + public static Optional> combine( + final Optional> maybeDepositRequests, + final Optional> maybeWithdrawalRequest, + final Optional> maybeConsolidationRequest) { + if (maybeDepositRequests.isEmpty() + && maybeWithdrawalRequest.isEmpty() + && maybeConsolidationRequest.isEmpty()) { + return Optional.empty(); + } + List requests = new ArrayList<>(); + maybeDepositRequests.ifPresent(requests::addAll); + maybeWithdrawalRequest.ifPresent(requests::addAll); + maybeConsolidationRequest.ifPresent(requests::addAll); + return Optional.of(requests); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestValidator.java new file mode 100644 index 00000000000..74fd8cd7ebc --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestValidator.java @@ -0,0 +1,30 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.requests; + +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.Request; +import org.hyperledger.besu.ethereum.core.TransactionReceipt; + +import java.util.List; +import java.util.Optional; + +/** Interface for request validation logic. */ +public interface RequestValidator { + boolean validate( + final Block block, final List request, final List receipts); + + boolean validateParameter(final Optional> request); +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestsValidatorCoordinator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestsValidatorCoordinator.java new file mode 100644 index 00000000000..7b8f5cf4973 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/RequestsValidatorCoordinator.java @@ -0,0 +1,202 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.requests; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.RequestType; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.Request; +import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.mainnet.BodyValidation; + +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import com.google.common.collect.ImmutableSortedMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Validates requests within a block against a set of predefined validators. This class delegates + * the validation of requests of specific types to corresponding validators. It ensures that + * requests are properly ordered, have a valid root, and meet the criteria defined by their + * validators. + */ +public class RequestsValidatorCoordinator { + private static final Logger LOG = LoggerFactory.getLogger(RequestsValidatorCoordinator.class); + private final ImmutableSortedMap validators; + + public static RequestsValidatorCoordinator empty() { + return new Builder().build(); + } + + /** + * Constructs a new RequestsDelegateValidator with a mapping of request types to their respective + * validators. + * + * @param validators An immutable map of request types to their corresponding validators. + */ + private RequestsValidatorCoordinator( + final ImmutableSortedMap validators) { + this.validators = validators; + } + + /** + * Validates a block's requests by ensuring they are correctly ordered, have a valid root, and + * pass their respective type-specific validations. + * + * @param block The block containing the requests to be validated. + * @param maybeRequests The list of requests contained within the block. + * @param receipts The list of transaction receipts corresponding to the requests. + * @return true if all validations pass; false otherwise. + */ + public boolean validate( + final Block block, + final Optional> maybeRequests, + final List receipts) { + + if (validators.isEmpty()) { + return isRequestsEmpty(block, maybeRequests); + } + + if (!isRequestsRootValid(block, maybeRequests)) { + return false; + } + + if (!isRequestOrderValid(maybeRequests.get())) { + final Hash blockHash = block.getHash(); + LOG.warn("Block {} the ordering across requests must be ascending by type", blockHash); + return false; + } + return validateRequests(block, maybeRequests.get(), receipts); + } + + /** + * Validates the requests contained within a block against their respective type-specific + * validators. + * + * @param block The block containing the requests. + * @param requests The list of requests to be validated. + * @param receipts The list of transaction receipts corresponding to the requests. + * @return true if all requests pass their type-specific validations; false otherwise. + */ + private boolean validateRequests( + final Block block, final List requests, final List receipts) { + return requestTypes(requests).stream() + .allMatch(type -> validateRequestOfType(type, block, requests, receipts)); + } + + private boolean isRequestsRootValid(final Block block, final Optional> requests) { + final Hash blockHash = block.getHash(); + final Optional maybeRequestsRoot = block.getHeader().getRequestsRoot(); + + if (maybeRequestsRoot.isEmpty()) { + LOG.warn("Block {} must contain requests root", blockHash); + return false; + } + + if (block.getBody().getRequests().isEmpty()) { + LOG.warn("Block body {} must contain requests (even if empty list)", blockHash); + return false; + } + + if (requests.isEmpty()) { + LOG.warn("Block {} must contain requests (even if empty list)", blockHash); + return false; + } + + final Hash expectedRequestsRoot = BodyValidation.requestsRoot(requests.get()); + if (!expectedRequestsRoot.equals(maybeRequestsRoot.get())) { + LOG.warn( + "Block {} requests root does not match expected hash root for requests in block", + blockHash); + return false; + } + return true; + } + + private boolean isRequestsEmpty(final Block block, final Optional> requests) { + final Hash blockHash = block.getHash(); + final Optional maybeRequestsRoot = block.getHeader().getRequestsRoot(); + + if (maybeRequestsRoot.isPresent()) { + LOG.warn("Block {} must not contain requests root", blockHash); + return false; + } + + if (block.getBody().getRequests().isPresent()) { + LOG.warn("Block body {} must not contain requests", blockHash); + return false; + } + + if (requests.isPresent()) { + LOG.warn("Block {} must not contain requests", blockHash); + return false; + } + return true; + } + + private boolean validateRequestOfType( + final RequestType type, + final Block block, + final List requests, + final List receipts) { + + Optional requestValidator = getRequestValidator(type); + if (requestValidator.isEmpty()) { + LOG.warn("Block {} contains prohibited requests of type: {}", block.getHash(), type); + return false; + } + List typedRequests = filterRequestsOfType(requests, type); + return requestValidator.get().validate(block, typedRequests, receipts); + } + + public Optional getRequestValidator(final RequestType requestType) { + return Optional.ofNullable(validators.get(requestType)); + } + + private static Set requestTypes(final List requests) { + return requests.stream().map(Request::getType).collect(Collectors.toSet()); + } + + private static boolean isRequestOrderValid(final List requests) { + return IntStream.range(0, requests.size() - 1) + .allMatch(i -> requests.get(i).getType().compareTo(requests.get(i + 1).getType()) <= 0); + } + + private static List filterRequestsOfType( + final List requests, final RequestType type) { + return requests.stream() + .filter(request -> request.getType() == type) + .collect(Collectors.toList()); + } + + public static class Builder { + private final ImmutableSortedMap.Builder validatorsBuilder = + ImmutableSortedMap.naturalOrder(); + + public Builder addValidator(final RequestType type, final RequestValidator validator) { + this.validatorsBuilder.put(type, validator); + return this; + } + + public RequestsValidatorCoordinator build() { + return new RequestsValidatorCoordinator(validatorsBuilder.build()); + } + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/WithdrawalRequestProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/WithdrawalRequestProcessor.java new file mode 100644 index 00000000000..d4021c9a104 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/WithdrawalRequestProcessor.java @@ -0,0 +1,79 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.requests; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.datatypes.GWei; +import org.hyperledger.besu.ethereum.core.WithdrawalRequest; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.units.bigints.UInt64; + +/** Processor for handling withdrawal requests. */ +public class WithdrawalRequestProcessor + extends AbstractSystemCallRequestProcessor { + + public static final Address DEFAULT_WITHDRAWAL_REQUEST_CONTRACT_ADDRESS = + Address.fromHexString("0x00A3ca265EBcb825B45F985A16CEFB49958cE017"); + + private static final int ADDRESS_BYTES = 20; + private static final int PUBLIC_KEY_BYTES = 48; + private static final int AMOUNT_BYTES = 8; + private static final int WITHDRAWAL_REQUEST_BYTES_SIZE = + ADDRESS_BYTES + PUBLIC_KEY_BYTES + AMOUNT_BYTES; + + private final Address withdrawalRequestContractAddress; + + public WithdrawalRequestProcessor(final Address withdrawalRequestContractAddress) { + this.withdrawalRequestContractAddress = withdrawalRequestContractAddress; + } + + /** + * Gets the call address for withdrawal requests. + * + * @return The call address. + */ + @Override + protected Address getCallAddress() { + return withdrawalRequestContractAddress; + } + + /** + * Gets the size of the bytes representing a single withdrawal request. + * + * @return The size of the bytes representing a single withdrawal request. + */ + @Override + protected int getRequestBytesSize() { + return WITHDRAWAL_REQUEST_BYTES_SIZE; + } + + /** + * Parses a single withdrawal request from the provided bytes. + * + * @param requestBytes The bytes representing a single withdrawal request. + * @return A parsed {@link WithdrawalRequest} object. + */ + @Override + protected WithdrawalRequest parseRequest(final Bytes requestBytes) { + final Address sourceAddress = Address.wrap(requestBytes.slice(0, ADDRESS_BYTES)); + final BLSPublicKey validatorPublicKey = + BLSPublicKey.wrap(requestBytes.slice(ADDRESS_BYTES, PUBLIC_KEY_BYTES)); + final UInt64 amount = + UInt64.fromBytes(requestBytes.slice(ADDRESS_BYTES + PUBLIC_KEY_BYTES, AMOUNT_BYTES)); + return new WithdrawalRequest(sourceAddress, validatorPublicKey, GWei.of(amount)); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/WithdrawalRequestValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/WithdrawalRequestValidator.java new file mode 100644 index 00000000000..fc108b798f3 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/requests/WithdrawalRequestValidator.java @@ -0,0 +1,93 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.requests; + +import static org.hyperledger.besu.ethereum.mainnet.requests.RequestUtil.getWithdrawalRequests; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.Request; +import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.core.WithdrawalRequest; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class WithdrawalRequestValidator implements RequestValidator { + + public static final int MAX_WITHDRAWAL_REQUESTS_PER_BLOCK = 16; + + private static final Logger LOG = LoggerFactory.getLogger(WithdrawalRequestValidator.class); + + private boolean validateWithdrawalRequestParameter( + final Optional> withdrawalRequests) { + return withdrawalRequests.isPresent(); + } + + private boolean validateWithdrawalRequestsInBlock( + final Block block, final List withdrawalRequests) { + final Hash blockHash = block.getHash(); + + final List withdrawalRequestsInBlock = + block + .getBody() + .getRequests() + .flatMap(requests -> getWithdrawalRequests(Optional.of(requests))) + .orElse(Collections.emptyList()); + + // TODO Do we need to allow for customization? (e.g. if the value changes in the next fork) + if (withdrawalRequestsInBlock.size() > MAX_WITHDRAWAL_REQUESTS_PER_BLOCK) { + LOG.warn( + "Block {} has more than the allowed maximum number of withdrawal requests", blockHash); + return false; + } + + // Validate WithdrawalRequests + final boolean expectedWithdrawalRequestMatch = + withdrawalRequests.equals(withdrawalRequestsInBlock); + if (!expectedWithdrawalRequestMatch) { + LOG.warn( + "Block {} has a mismatch between its withdrawal requests and expected withdrawal requests (in_block = {}, " + + "expected = {})", + blockHash, + withdrawalRequestsInBlock, + withdrawalRequests); + return false; + } + return true; + } + + @Override + public boolean validate( + final Block block, final List requests, final List receipts) { + var withdrawalRequests = + getWithdrawalRequests(Optional.of(requests)).orElse(Collections.emptyList()); + return validateWithdrawalRequestsInBlock(block, withdrawalRequests); + } + + @Override + public boolean validateParameter(final Optional> request) { + if (request.isEmpty()) { + return false; + } + var withdrawalRequests = + RequestUtil.filterRequestsOfType(request.get(), WithdrawalRequest.class); + return validateWithdrawalRequestParameter(Optional.of(withdrawalRequests)); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/AbstractPrivacyController.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/AbstractPrivacyController.java index dd8d171c818..09c2a63dc78 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/AbstractPrivacyController.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/AbstractPrivacyController.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/AbstractRestrictedPrivacyController.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/AbstractRestrictedPrivacyController.java index 75d0df629cd..480df6dd0f4 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/AbstractRestrictedPrivacyController.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/AbstractRestrictedPrivacyController.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/FlexiblePrivacyController.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/FlexiblePrivacyController.java index 595727752b5..458dfa1b317 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/FlexiblePrivacyController.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/FlexiblePrivacyController.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/FlexibleUtil.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/FlexibleUtil.java index 1425fdb114e..f9a71675201 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/FlexibleUtil.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/FlexibleUtil.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyController.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyController.java index 192c376a0e5..4d01ec152a8 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyController.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/MultiTenancyPrivacyController.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PluginPrivacyController.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PluginPrivacyController.java index 22cb76d4503..93c04c4954b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PluginPrivacyController.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PluginPrivacyController.java @@ -124,7 +124,7 @@ public String deletePrivacyGroup(final String privacyGroupId, final String priva @Override public Optional findPrivacyGroupByGroupId( final String privacyGroupId, final String privacyUserId) { - verifyPrivacyGroupContainsPrivacyUserId(privacyUserId, privacyGroupId); + verifyPrivacyGroupContainsPrivacyUserId(privacyGroupId, privacyUserId); return Optional.of( new PrivacyGroup( @@ -155,7 +155,7 @@ public void verifyPrivacyGroupContainsPrivacyUserId( @Override public void verifyPrivacyGroupContainsPrivacyUserId( - final String privacyUserId, final String privacyGroupId) { - verifyPrivacyGroupContainsPrivacyUserId(privacyUserId, privacyGroupId, Optional.empty()); + final String privacyGroupId, final String privacyUserId) { + verifyPrivacyGroupContainsPrivacyUserId(privacyGroupId, privacyUserId, Optional.empty()); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateGroupRehydrationBlockProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateGroupRehydrationBlockProcessor.java index 462bd9c6619..307a1d1e6fa 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateGroupRehydrationBlockProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateGroupRehydrationBlockProcessor.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.privacy; import static org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver.EMPTY_ROOT_HASH; +import static org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; @@ -36,8 +37,6 @@ import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; import org.hyperledger.besu.ethereum.privacy.storage.PrivateTransactionMetadata; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; -import org.hyperledger.besu.ethereum.vm.BlockHashLookup; -import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.evm.account.MutableAccount; import org.hyperledger.besu.evm.tracing.OperationTracer; @@ -91,6 +90,7 @@ public BlockProcessingResult processBlock( final PrivateStateStorage privateStateStorage, final PrivateStateRootResolver privateStateRootResolver, final Block block, + final BlockHashLookup blockHashLookup, final Map forExecution, final List ommers) { long gasUsed = 0; @@ -114,7 +114,6 @@ public BlockProcessingResult processBlock( } final WorldUpdater worldStateUpdater = worldState.updater(); - final BlockHashLookup blockHashLookup = new CachingBlockHashLookup(blockHeader, blockchain); final Address miningBeneficiary = miningBeneficiaryCalculator.calculateBeneficiary(blockHeader); @@ -151,7 +150,7 @@ public BlockProcessingResult processBlock( privateTransaction, miningBeneficiary, OperationTracer.NO_TRACING, - new CachingBlockHashLookup(blockHeader, blockchain), + blockHashLookup, privateTransaction.getPrivacyGroupId().get()); privateWorldStateUpdater.commit(); @@ -171,7 +170,6 @@ public BlockProcessingResult processBlock( // depend on public state final TransactionProcessingResult result = transactionProcessor.processTransaction( - blockchain, worldStateUpdater, blockHeader, transaction, diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateMutableWorldStateUpdater.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateMutableWorldStateUpdater.java new file mode 100644 index 00000000000..fc20db7f813 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateMutableWorldStateUpdater.java @@ -0,0 +1,107 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.privacy; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.Collection; +import java.util.Optional; + +// This class uses a public WorldUpdater and a private WorldUpdater to provide a +// MutableWorldStateUpdater that can read and write from the private world state and can read from +// the public world state, but cannot write to it. +public class PrivateMutableWorldStateUpdater implements WorldUpdater { + + protected final WorldUpdater publicWorldUpdater; + protected final WorldUpdater privateWorldUpdater; + + public PrivateMutableWorldStateUpdater( + final WorldUpdater publicWorldUpdater, final WorldUpdater privateWorldUpdater) { + this.publicWorldUpdater = publicWorldUpdater; + this.privateWorldUpdater = privateWorldUpdater; + } + + @Override + public MutableAccount createAccount(final Address address, final long nonce, final Wei balance) { + return privateWorldUpdater.createAccount(address, nonce, balance); + } + + @Override + public MutableAccount createAccount(final Address address) { + return privateWorldUpdater.createAccount(address); + } + + @Override + public MutableAccount getAccount(final Address address) { + final MutableAccount privateAccount = privateWorldUpdater.getAccount(address); + if (privateAccount != null && !privateAccount.isEmpty()) { + return privateAccount; + } + final MutableAccount publicAccount = publicWorldUpdater.getAccount(address); + if (publicAccount != null && !publicAccount.isEmpty()) { + publicAccount.becomeImmutable(); + return publicAccount; + } + return privateAccount; + } + + @Override + public void deleteAccount(final Address address) { + privateWorldUpdater.deleteAccount(address); + } + + @Override + public Collection getTouchedAccounts() { + return privateWorldUpdater.getTouchedAccounts(); + } + + @Override + public Collection
getDeletedAccountAddresses() { + return privateWorldUpdater.getDeletedAccountAddresses(); + } + + @Override + public void revert() { + privateWorldUpdater.revert(); + } + + @Override + public void commit() { + privateWorldUpdater.commit(); + } + + @Override + public Account get(final Address address) { + final Account privateAccount = privateWorldUpdater.get(address); + if (privateAccount != null && !privateAccount.isEmpty()) { + return privateAccount; + } + return publicWorldUpdater.get(address); + } + + @Override + public WorldUpdater updater() { + return this; + } + + @Override + public Optional parentUpdater() { + return privateWorldUpdater.parentUpdater(); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateStateGenesisAllocator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateStateGenesisAllocator.java index f1a022fa7c2..e011329c071 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateStateGenesisAllocator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateStateGenesisAllocator.java @@ -95,13 +95,13 @@ public void applyGenesisToPrivateWorldState( managementContract.setCode(FlexibleGroupManagement.DEFAULT_GROUP_MANAGEMENT_RUNTIME_BYTECODE); // inject proxy - final MutableAccount procyContract = + final MutableAccount proxyContract = privateWorldStateUpdater.createAccount(FLEXIBLE_PRIVACY_PROXY); // this is the code for the proxy contract - procyContract.setCode(FlexibleGroupManagement.PROXY_RUNTIME_BYTECODE); + proxyContract.setCode(FlexibleGroupManagement.PROXY_RUNTIME_BYTECODE); // manually set the management contract address so the proxy can trust it - procyContract.setStorageValue( + proxyContract.setStorageValue( UInt256.ZERO, UInt256.fromBytes(Bytes32.leftPad(DEFAULT_FLEXIBLE_PRIVACY_MANAGEMENT))); } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateStateRehydration.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateStateRehydration.java index 51953e225e9..6d4811a4bca 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateStateRehydration.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateStateRehydration.java @@ -25,6 +25,7 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap; import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; +import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import java.util.LinkedHashMap; @@ -166,6 +167,7 @@ public void rehydrate( privateStateStorage, privateStateRootResolver, block, + new CachingBlockHashLookup(blockHeader, blockchain), pmtHashToPrivateTransactionMap, block.getBody().getOmmers()); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateStateRootResolver.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateStateRootResolver.java index 747e31d1b05..db0750a410a 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateStateRootResolver.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateStateRootResolver.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransaction.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransaction.java index c108d27626f..42672ddbf06 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransaction.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransaction.java @@ -355,6 +355,16 @@ public Bytes getPayload() { return payload; } + /** + * Returns the payload if this is a contract creation transaction. + * + * @return if present the init code + */ + @Override + public Optional getInit() { + return getTo().isPresent() ? Optional.empty() : Optional.of(payload); + } + /** * Return the transaction chain id (if it exists) * diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionEvent.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionEvent.java index aa28a3e8baf..1ea854ebbfd 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionEvent.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionEvent.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.privacy; public class PrivateTransactionEvent { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionObserver.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionObserver.java index 9168d45e735..8d3ca436839 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionObserver.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionObserver.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.privacy; @FunctionalInterface diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionProcessor.java index c953ed93d1d..ad7de59aee7 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionProcessor.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.privacy; import static org.hyperledger.besu.ethereum.mainnet.PrivateStateUtils.KEY_TRANSACTION_HASH; +import static org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; @@ -24,7 +25,7 @@ import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; -import org.hyperledger.besu.ethereum.worldstate.DefaultMutablePrivateWorldStateUpdater; +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.account.MutableAccount; import org.hyperledger.besu.evm.code.CodeV0; @@ -36,7 +37,6 @@ import java.util.Deque; import java.util.Map; import java.util.Optional; -import java.util.function.Function; import org.apache.tuweni.bytes.Bytes; import org.slf4j.Logger; @@ -83,7 +83,7 @@ public TransactionProcessingResult processTransaction( final PrivateTransaction transaction, final Address miningBeneficiary, final OperationTracer operationTracer, - final Function blockHashLookup, + final BlockHashLookup blockHashLookup, final Bytes privacyGroupId) { try { LOG.trace("Starting private execution of {}", transaction); @@ -109,7 +109,7 @@ public TransactionProcessingResult processTransaction( sender.getNonce()); final WorldUpdater mutablePrivateWorldStateUpdater = - new DefaultMutablePrivateWorldStateUpdater(publicWorldState, privateWorldState); + new PrivateMutableWorldStateUpdater(publicWorldState, privateWorldState); final MessageFrame.Builder commonMessageFrameBuilder = MessageFrame.builder() .maxStackSize(maxStackSize) @@ -139,13 +139,14 @@ public TransactionProcessingResult processTransaction( privacyGroupId); final Bytes initCodeBytes = transaction.getPayload(); + Code code = contractCreationProcessor.getCodeFromEVMForCreation(initCodeBytes); initialFrame = commonMessageFrameBuilder .type(MessageFrame.Type.CONTRACT_CREATION) .address(privateContractAddress) .contract(privateContractAddress) - .inputData(Bytes.EMPTY) - .code(contractCreationProcessor.getCodeFromEVM(null, initCodeBytes)) + .inputData(initCodeBytes.slice(code.getSize())) + .code(code) .build(); } else { final Address to = transaction.getTo().get(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionReceipt.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionReceipt.java index 34ae107af92..443c5077c1f 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionReceipt.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionReceipt.java @@ -168,16 +168,14 @@ public boolean equals(final Object obj) { if (obj == this) { return true; } - if (!(obj instanceof PrivateTransactionReceipt)) { + if (!(obj instanceof PrivateTransactionReceipt other)) { return false; } - final PrivateTransactionReceipt other = (PrivateTransactionReceipt) obj; - return logs.equals(other.getLogs()) - && status == other.status - && output.equals(other.output) - && revertReason.isPresent() - ? revertReason.get().equals(other.revertReason.get()) - : true; + return !logs.equals(other.getLogs()) + || status != other.status + || !output.equals(other.output) + || revertReason.isEmpty() + || revertReason.get().equals(other.revertReason.get()); } @Override diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionValidator.java index 9bbfa6194be..11fe74d02e6 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionValidator.java @@ -61,7 +61,7 @@ public ValidationResult validate( LOG.debug("Validating actual nonce {}, with expected nonce {}", transactionNonce, accountNonce); - if (accountNonce > transactionNonce) { + if (Long.compareUnsigned(accountNonce, transactionNonce) > 0) { final String errorMessage = String.format( "Private Transaction nonce %s, is lower than sender account nonce %s.", diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateWorldStateReader.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateWorldStateReader.java index af32e76513c..255f80b5871 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateWorldStateReader.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/PrivateWorldStateReader.java @@ -66,6 +66,12 @@ public List getPrivateTransactionMetadataList( .orElse(Collections.emptyList()); } + public Optional getPrivateBlockMetadata( + final String privacyGroupId, final Hash blockHash) { + final Bytes32 privacyGroupIdBytes = Bytes32.wrap(Bytes.fromBase64String(privacyGroupId)); + return privateStateStorage.getPrivateBlockMetadata(blockHash, privacyGroupIdBytes); + } + public Optional getPrivateTransactionReceipt( final Hash blockHash, final Hash transactionHash) { return privateStateStorage.getTransactionReceipt(blockHash, transactionHash); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/PrivacyStorageProvider.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/PrivacyStorageProvider.java index 2dc167a884b..ea46ffcc6ab 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/PrivacyStorageProvider.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/PrivacyStorageProvider.java @@ -14,14 +14,17 @@ */ package org.hyperledger.besu.ethereum.privacy.storage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.worldstate.WorldStatePreimageStorage; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import java.io.Closeable; public interface PrivacyStorageProvider extends Closeable { - WorldStateStorage createWorldStateStorage(); + WorldStateKeyValueStorage createWorldStateStorage(); + + WorldStateStorageCoordinator createWorldStateStorageCoordinator(); WorldStatePreimageStorage createWorldStatePreimageStorage(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/keyvalue/PrivacyKeyValueStorageProvider.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/keyvalue/PrivacyKeyValueStorageProvider.java index d54a4ba164f..bb72aeda467 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/keyvalue/PrivacyKeyValueStorageProvider.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/keyvalue/PrivacyKeyValueStorageProvider.java @@ -19,10 +19,11 @@ import org.hyperledger.besu.ethereum.privacy.storage.PrivacyStorageProvider; import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateKeyValueStorage; import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; -import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStatePreimageKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.worldstate.WorldStatePreimageStorage; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; import java.io.IOException; @@ -47,8 +48,13 @@ public PrivacyKeyValueStorageProvider( } @Override - public WorldStateStorage createWorldStateStorage() { - return new WorldStateKeyValueStorage(privateWorldStateKeyValueStorage); + public WorldStateKeyValueStorage createWorldStateStorage() { + return new ForestWorldStateKeyValueStorage(privateWorldStateKeyValueStorage); + } + + @Override + public WorldStateStorageCoordinator createWorldStateStorageCoordinator() { + return new WorldStateStorageCoordinator(createWorldStateStorage()); } @Override diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/migration/PrivateMigrationBlockProcessor.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/migration/PrivateMigrationBlockProcessor.java index 0310700e1f5..7c51ac01cb2 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/migration/PrivateMigrationBlockProcessor.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/migration/PrivateMigrationBlockProcessor.java @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.ethereum.privacy.storage.migration; +import static org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; + import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.BlockProcessingOutputs; @@ -30,8 +32,6 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; -import org.hyperledger.besu.ethereum.vm.BlockHashLookup; -import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup; import org.hyperledger.besu.evm.account.MutableAccount; import org.hyperledger.besu.evm.worldstate.WorldUpdater; @@ -79,6 +79,7 @@ public PrivateMigrationBlockProcessor(final ProtocolSpec protocolSpec) { public BlockProcessingResult processBlock( final Blockchain blockchain, final MutableWorldState worldState, + final BlockHashLookup blockHashLookup, final BlockHeader blockHeader, final List transactions, final List ommers) { @@ -97,13 +98,11 @@ public BlockProcessingResult processBlock( } final WorldUpdater worldStateUpdater = worldState.updater(); - final BlockHashLookup blockHashLookup = new CachingBlockHashLookup(blockHeader, blockchain); final Address miningBeneficiary = miningBeneficiaryCalculator.calculateBeneficiary(blockHeader); final TransactionProcessingResult result = transactionProcessor.processTransaction( - blockchain, worldStateUpdater, blockHeader, transaction, diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/migration/PrivateStorageMigration.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/migration/PrivateStorageMigration.java index af9fc214b8e..834633a997d 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/migration/PrivateStorageMigration.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/privacy/storage/migration/PrivateStorageMigration.java @@ -29,6 +29,7 @@ import org.hyperledger.besu.ethereum.privacy.storage.LegacyPrivateStateStorage; import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap; import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; +import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import java.util.List; @@ -110,7 +111,12 @@ public void migratePrivateStorage() { final List ommers = block.getBody().getOmmers(); privateMigrationBlockProcessor.processBlock( - blockchain, publicWorldState, blockHeader, transactionsToProcess, ommers); + blockchain, + publicWorldState, + new CachingBlockHashLookup(blockHeader, blockchain), + blockHeader, + transactionsToProcess, + ommers); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/processing/TransactionProcessingResult.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/processing/TransactionProcessingResult.java index c053e1107b9..eca28927bdd 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/processing/TransactionProcessingResult.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/processing/TransactionProcessingResult.java @@ -18,7 +18,6 @@ import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; import org.hyperledger.besu.evm.log.Log; -import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -56,7 +55,7 @@ public enum Status { public static TransactionProcessingResult invalid( final ValidationResult validationResult) { return new TransactionProcessingResult( - Status.INVALID, new ArrayList<>(), -1, -1, Bytes.EMPTY, validationResult, Optional.empty()); + Status.INVALID, List.of(), -1, -1, Bytes.EMPTY, validationResult, Optional.empty()); } public static TransactionProcessingResult failed( @@ -66,7 +65,7 @@ public static TransactionProcessingResult failed( final Optional revertReason) { return new TransactionProcessingResult( Status.FAILED, - new ArrayList<>(), + List.of(), gasUsedByTransaction, gasRemaining, Bytes.EMPTY, @@ -204,4 +203,31 @@ public ValidationResult getValidationResult() { public Optional getRevertReason() { return revertReason; } + + @Override + public Optional getInvalidReason() { + return (validationResult.isValid() + ? Optional.empty() + : Optional.of(validationResult.getErrorMessage())); + } + + @Override + public String toString() { + return "TransactionProcessingResult{" + + "status=" + + status + + ", estimateGasUsedByTransaction=" + + estimateGasUsedByTransaction + + ", gasRemaining=" + + gasRemaining + + ", logs=" + + logs + + ", output=" + + output + + ", validationResult=" + + validationResult + + ", revertReason=" + + revertReason + + '}'; + } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/proof/WorldStateProofProvider.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/proof/WorldStateProofProvider.java index 16023264b7d..1f49dea273c 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/proof/WorldStateProofProvider.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/proof/WorldStateProofProvider.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -26,8 +26,9 @@ import org.hyperledger.besu.ethereum.trie.patricia.SimpleMerklePatriciaTrie; import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -41,6 +42,8 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The WorldStateProofProvider class is responsible for providing proofs for world state entries. It @@ -48,10 +51,11 @@ */ public class WorldStateProofProvider { - private final WorldStateStorage worldStateStorage; + private final WorldStateStorageCoordinator worldStateStorageCoordinator; + private static final Logger LOG = LoggerFactory.getLogger(WorldStateProofProvider.class); - public WorldStateProofProvider(final WorldStateStorage worldStateStorage) { - this.worldStateStorage = worldStateStorage; + public WorldStateProofProvider(final WorldStateStorageCoordinator worldStateStorageCoordinator) { + this.worldStateStorageCoordinator = worldStateStorageCoordinator; } public Optional getAccountProof( @@ -59,7 +63,7 @@ public Optional getAccountProof( final Address accountAddress, final List accountStorageKeys) { - if (!worldStateStorage.isWorldStateAvailable(worldStateRoot, null)) { + if (!worldStateStorageCoordinator.isWorldStateAvailable(worldStateRoot, null)) { return Optional.empty(); } else { final Hash accountHash = accountAddress.addressHash(); @@ -85,7 +89,8 @@ private SortedMap> getStorageProofs( final List accountStorageKeys) { final MerkleTrie storageTrie = newAccountStorageTrie(accountHash, account.getStorageRoot()); - final NavigableMap> storageProofs = new TreeMap<>(); + final NavigableMap> storageProofs = + new TreeMap<>(Comparator.comparing(Bytes32::toHexString)); accountStorageKeys.forEach( key -> storageProofs.put(key, storageTrie.getValueWithProof(Hash.hash(key)))); return storageProofs; @@ -122,14 +127,14 @@ public List getStorageProofRelatedNodes( private MerkleTrie newAccountStateTrie(final Bytes32 rootHash) { return new StoredMerklePatriciaTrie<>( - worldStateStorage::getAccountStateTrieNode, rootHash, b -> b, b -> b); + worldStateStorageCoordinator::getAccountStateTrieNode, rootHash, b -> b, b -> b); } private MerkleTrie newAccountStorageTrie( final Hash accountHash, final Bytes32 rootHash) { return new StoredMerklePatriciaTrie<>( (location, hash) -> - worldStateStorage.getAccountStorageTrieNode(accountHash, location, hash), + worldStateStorageCoordinator.getAccountStorageTrieNode(accountHash, location, hash), rootHash, b -> b, b -> b); @@ -153,19 +158,26 @@ public boolean isValidRangeProof( final SortedMap keys) { // check if it's monotonic increasing - if (!Ordering.natural().isOrdered(keys.keySet())) { + if (keys.size() > 1 && !Ordering.natural().isOrdered(keys.keySet())) { return false; } - // when proof is empty we need to have all the keys to reconstruct the trie + // when proof is empty and we requested the full range, we should + // have all the keys to reconstruct the trie if (proofs.isEmpty()) { - final MerkleTrie trie = new SimpleMerklePatriciaTrie<>(Function.identity()); - // add the received keys in the trie - for (Map.Entry key : keys.entrySet()) { - trie.put(key.getKey(), key.getValue()); + if (startKeyHash.equals(Bytes32.ZERO)) { + final MerkleTrie trie = new SimpleMerklePatriciaTrie<>(Function.identity()); + // add the received keys in the trie + for (Map.Entry key : keys.entrySet()) { + trie.put(key.getKey(), key.getValue()); + } + return rootHash.equals(trie.getRootHash()); + } else { + // TODO: possibly accept a node loader so we can verify this with already + // completed partial storage requests + LOG.info("failing proof due to incomplete range without proofs"); + return false; } - - return rootHash.equals(trie.getRootHash()); } // reconstruct a part of the trie with the proof diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/StorageProvider.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/StorageProvider.java index 6cc9741cb22..68c71156cc9 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/StorageProvider.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/StorageProvider.java @@ -17,9 +17,10 @@ import org.hyperledger.besu.ethereum.chain.BlockchainStorage; import org.hyperledger.besu.ethereum.chain.VariablesStorage; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.worldstate.WorldStatePreimageStorage; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; import org.hyperledger.besu.plugin.services.storage.SegmentIdentifier; import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; @@ -32,9 +33,15 @@ public interface StorageProvider extends Closeable { VariablesStorage createVariablesStorage(); BlockchainStorage createBlockchainStorage( - ProtocolSchedule protocolSchedule, VariablesStorage variablesStorage); + ProtocolSchedule protocolSchedule, + VariablesStorage variablesStorage, + DataStorageConfiguration storageConfiguration); - WorldStateStorage createWorldStateStorage(DataStorageFormat dataStorageFormat); + WorldStateKeyValueStorage createWorldStateStorage( + DataStorageConfiguration dataStorageConfiguration); + + WorldStateStorageCoordinator createWorldStateStorageCoordinator( + DataStorageConfiguration dataStorageConfiguration); WorldStatePreimageStorage createWorldStatePreimageStorage(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueSegmentIdentifier.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueSegmentIdentifier.java index 9cce0230cdb..32e372cdecf 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueSegmentIdentifier.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueSegmentIdentifier.java @@ -14,21 +14,27 @@ */ package org.hyperledger.besu.ethereum.storage.keyvalue; +import static org.hyperledger.besu.plugin.services.storage.DataStorageFormat.BONSAI; +import static org.hyperledger.besu.plugin.services.storage.DataStorageFormat.FOREST; + +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.plugin.services.storage.SegmentIdentifier; -import org.bouncycastle.util.Arrays; +import java.nio.charset.StandardCharsets; +import java.util.EnumSet; public enum KeyValueSegmentIdentifier implements SegmentIdentifier { - BLOCKCHAIN(new byte[] {1}, true), - WORLD_STATE(new byte[] {2}, new int[] {0, 1}), + DEFAULT("default".getBytes(StandardCharsets.UTF_8)), + BLOCKCHAIN(new byte[] {1}, true, true), + WORLD_STATE(new byte[] {2}, EnumSet.of(FOREST), false, true, false), PRIVATE_TRANSACTIONS(new byte[] {3}), PRIVATE_STATE(new byte[] {4}), - PRUNING_STATE(new byte[] {5}, new int[] {0, 1}), - ACCOUNT_INFO_STATE(new byte[] {6}, new int[] {2}), - CODE_STORAGE(new byte[] {7}, new int[] {2}), - ACCOUNT_STORAGE_STORAGE(new byte[] {8}, new int[] {2}), - TRIE_BRANCH_STORAGE(new byte[] {9}, new int[] {2}), - TRIE_LOG_STORAGE(new byte[] {10}, new int[] {2}), + PRUNING_STATE(new byte[] {5}, EnumSet.of(FOREST)), + ACCOUNT_INFO_STATE(new byte[] {6}, EnumSet.of(BONSAI), false, true, false), + CODE_STORAGE(new byte[] {7}, EnumSet.of(BONSAI)), + ACCOUNT_STORAGE_STORAGE(new byte[] {8}, EnumSet.of(BONSAI), false, true, false), + TRIE_BRANCH_STORAGE(new byte[] {9}, EnumSet.of(BONSAI), false, true, false), + TRIE_LOG_STORAGE(new byte[] {10}, EnumSet.of(BONSAI), true, false, true), VARIABLES(new byte[] {11}), // formerly GOQUORUM_PRIVATE_WORLD_STATE // previously supported GoQuorum private states @@ -43,26 +49,40 @@ public enum KeyValueSegmentIdentifier implements SegmentIdentifier { CHAIN_PRUNER_STATE(new byte[] {18}); private final byte[] id; - private final int[] versionList; + private final EnumSet formats; private final boolean containsStaticData; + private final boolean eligibleToHighSpecFlag; + private final boolean staticDataGarbageCollectionEnabled; KeyValueSegmentIdentifier(final byte[] id) { - this(id, new int[] {0, 1, 2}); + this(id, EnumSet.allOf(DataStorageFormat.class)); } - KeyValueSegmentIdentifier(final byte[] id, final boolean containsStaticData) { - this(id, new int[] {0, 1, 2}, containsStaticData); + KeyValueSegmentIdentifier( + final byte[] id, final boolean containsStaticData, final boolean eligibleToHighSpecFlag) { + this( + id, + EnumSet.allOf(DataStorageFormat.class), + containsStaticData, + eligibleToHighSpecFlag, + false); } - KeyValueSegmentIdentifier(final byte[] id, final int[] versionList) { - this(id, versionList, false); + KeyValueSegmentIdentifier(final byte[] id, final EnumSet formats) { + this(id, formats, false, false, false); } KeyValueSegmentIdentifier( - final byte[] id, final int[] versionList, final boolean containsStaticData) { + final byte[] id, + final EnumSet formats, + final boolean containsStaticData, + final boolean eligibleToHighSpecFlag, + final boolean staticDataGarbageCollectionEnabled) { this.id = id; - this.versionList = versionList; + this.formats = formats; this.containsStaticData = containsStaticData; + this.eligibleToHighSpecFlag = eligibleToHighSpecFlag; + this.staticDataGarbageCollectionEnabled = staticDataGarbageCollectionEnabled; } @Override @@ -81,7 +101,17 @@ public boolean containsStaticData() { } @Override - public boolean includeInDatabaseVersion(final int version) { - return Arrays.contains(versionList, version); + public boolean isEligibleToHighSpecFlag() { + return eligibleToHighSpecFlag; + } + + @Override + public boolean isStaticDataGarbageCollectionEnabled() { + return staticDataGarbageCollectionEnabled; + } + + @Override + public boolean includeInDatabaseFormat(final DataStorageFormat format) { + return formats.contains(format); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStoragePrefixedKeyBlockchainStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStoragePrefixedKeyBlockchainStorage.java index 9134ca91c01..73d5c54b348 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStoragePrefixedKeyBlockchainStorage.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStoragePrefixedKeyBlockchainStorage.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -59,14 +59,17 @@ public class KeyValueStoragePrefixedKeyBlockchainStorage implements BlockchainSt final KeyValueStorage blockchainStorage; final VariablesStorage variablesStorage; final BlockHeaderFunctions blockHeaderFunctions; + final boolean receiptCompaction; public KeyValueStoragePrefixedKeyBlockchainStorage( final KeyValueStorage blockchainStorage, final VariablesStorage variablesStorage, - final BlockHeaderFunctions blockHeaderFunctions) { + final BlockHeaderFunctions blockHeaderFunctions, + final boolean receiptCompaction) { this.blockchainStorage = blockchainStorage; this.variablesStorage = variablesStorage; this.blockHeaderFunctions = blockHeaderFunctions; + this.receiptCompaction = receiptCompaction; migrateVariables(); } @@ -125,7 +128,8 @@ public Optional getTransactionLocation(final Hash transacti @Override public Updater updater() { - return new Updater(blockchainStorage.startTransaction(), variablesStorage.updater()); + return new Updater( + blockchainStorage.startTransaction(), variablesStorage.updater(), receiptCompaction); } private List rlpDecodeTransactionReceipts(final Bytes bytes) { @@ -253,12 +257,15 @@ public static class Updater implements BlockchainStorage.Updater { private final KeyValueStorageTransaction blockchainTransaction; private final VariablesStorage.Updater variablesUpdater; + private final boolean receiptCompaction; Updater( final KeyValueStorageTransaction blockchainTransaction, - final VariablesStorage.Updater variablesUpdater) { + final VariablesStorage.Updater variablesUpdater, + final boolean receiptCompaction) { this.blockchainTransaction = blockchainTransaction; this.variablesUpdater = variablesUpdater; + this.receiptCompaction = receiptCompaction; } @Override @@ -365,7 +372,10 @@ private void remove(final Bytes prefix, final Bytes key) { } private Bytes rlpEncode(final List receipts) { - return RLP.encode(o -> o.writeList(receipts, TransactionReceipt::writeToWithRevertReason)); + return RLP.encode( + o -> + o.writeList( + receipts, (r, rlpOutput) -> r.writeToForStorage(rlpOutput, receiptCompaction))); } private void removeVariables() { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStorageProvider.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStorageProvider.java index 8a7eef8ee5d..944f668cd68 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStorageProvider.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStorageProvider.java @@ -14,16 +14,19 @@ */ package org.hyperledger.besu.ethereum.storage.keyvalue; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.chain.BlockchainStorage; import org.hyperledger.besu.ethereum.chain.VariablesStorage; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions; import org.hyperledger.besu.ethereum.storage.StorageProvider; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.worldstate.WorldStatePreimageStorage; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.metrics.ObservableMetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; import org.hyperledger.besu.plugin.services.storage.SegmentIdentifier; import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; @@ -66,23 +69,34 @@ public VariablesStorage createVariablesStorage() { @Override public BlockchainStorage createBlockchainStorage( - final ProtocolSchedule protocolSchedule, final VariablesStorage variablesStorage) { + final ProtocolSchedule protocolSchedule, + final VariablesStorage variablesStorage, + final DataStorageConfiguration dataStorageConfiguration) { return new KeyValueStoragePrefixedKeyBlockchainStorage( getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.BLOCKCHAIN), variablesStorage, - ScheduleBasedBlockHeaderFunctions.create(protocolSchedule)); + ScheduleBasedBlockHeaderFunctions.create(protocolSchedule), + dataStorageConfiguration.getReceiptCompactionEnabled()); } @Override - public WorldStateStorage createWorldStateStorage(final DataStorageFormat dataStorageFormat) { - if (dataStorageFormat.equals(DataStorageFormat.BONSAI)) { - return new BonsaiWorldStateKeyValueStorage(this, metricsSystem); + public WorldStateKeyValueStorage createWorldStateStorage( + final DataStorageConfiguration dataStorageConfiguration) { + if (dataStorageConfiguration.getDataStorageFormat().equals(DataStorageFormat.BONSAI)) { + return new BonsaiWorldStateKeyValueStorage(this, metricsSystem, dataStorageConfiguration); } else { - return new WorldStateKeyValueStorage( + return new ForestWorldStateKeyValueStorage( getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.WORLD_STATE)); } } + @Override + public WorldStateStorageCoordinator createWorldStateStorageCoordinator( + final DataStorageConfiguration dataStorageFormatConfiguration) { + return new WorldStateStorageCoordinator( + createWorldStateStorage(dataStorageFormatConfiguration)); + } + @Override public WorldStatePreimageStorage createWorldStatePreimageStorage() { return new WorldStatePreimageKeyValueStorage(worldStatePreimageStorage); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/VariablesKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/VariablesKeyValueStorage.java index 28e6648a82d..92c665f9301 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/VariablesKeyValueStorage.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/VariablesKeyValueStorage.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -67,6 +67,11 @@ public Optional getLocalEnrSeqno() { return getVariable(SEQ_NO_STORE).map(Bytes::wrap); } + @Override + public Optional getGenesisStateHash() { + return getVariable(Keys.GENESIS_STATE_HASH).map(this::bytesToHash); + } + @Override public Updater updater() { return new Updater(variables.startTransaction()); @@ -115,6 +120,11 @@ public void setLocalEnrSeqno(final Bytes nodeRecord) { setVariable(SEQ_NO_STORE, nodeRecord); } + @Override + public void setGenesisStateHash(final Hash genesisStateHash) { + setVariable(Keys.GENESIS_STATE_HASH, genesisStateHash); + } + @Override public void removeAll() { removeVariable(CHAIN_HEAD_HASH); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/WorldStateKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/WorldStateKeyValueStorage.java deleted file mode 100644 index 74051b5e9b7..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/storage/keyvalue/WorldStateKeyValueStorage.java +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.storage.keyvalue; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.trie.MerkleTrie; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; -import org.hyperledger.besu.ethereum.worldstate.FlatDbMode; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; -import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; -import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; -import org.hyperledger.besu.util.Subscribers; - -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Predicate; -import java.util.stream.Stream; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; - -public class WorldStateKeyValueStorage implements WorldStateStorage { - - private final Subscribers nodeAddedListeners = Subscribers.create(); - private final KeyValueStorage keyValueStorage; - private final ReentrantLock lock = new ReentrantLock(); - - public WorldStateKeyValueStorage(final KeyValueStorage keyValueStorage) { - this.keyValueStorage = keyValueStorage; - } - - @Override - public DataStorageFormat getDataStorageFormat() { - return DataStorageFormat.FOREST; - } - - @Override - public Optional getCode(final Bytes32 codeHash, final Hash accountHash) { - if (codeHash.equals(Hash.EMPTY)) { - return Optional.of(Bytes.EMPTY); - } else { - return keyValueStorage.get(codeHash.toArrayUnsafe()).map(Bytes::wrap); - } - } - - @Override - public Optional getAccountStateTrieNode(final Bytes location, final Bytes32 nodeHash) { - return getTrieNode(nodeHash); - } - - @Override - public Optional getAccountStorageTrieNode( - final Hash accountHash, final Bytes location, final Bytes32 nodeHash) { - return getTrieNode(nodeHash); - } - - private Optional getTrieNode(final Bytes32 nodeHash) { - if (nodeHash.equals(MerkleTrie.EMPTY_TRIE_NODE_HASH)) { - return Optional.of(MerkleTrie.EMPTY_TRIE_NODE); - } else { - return keyValueStorage.get(nodeHash.toArrayUnsafe()).map(Bytes::wrap); - } - } - - @Override - public Optional getTrieNodeUnsafe(final Bytes key) { - return keyValueStorage.get(key.toArrayUnsafe()).map(Bytes::wrap); - } - - @Override - public FlatDbMode getFlatDbMode() { - return FlatDbMode.NO_FLATTENED; - } - - @Override - public Optional getNodeData(final Bytes location, final Bytes32 hash) { - if (hash.equals(MerkleTrie.EMPTY_TRIE_NODE_HASH)) { - return Optional.of(MerkleTrie.EMPTY_TRIE_NODE); - } else if (hash.equals(Hash.EMPTY)) { - return Optional.of(Bytes.EMPTY); - } else { - return keyValueStorage.get(hash.toArrayUnsafe()).map(Bytes::wrap); - } - } - - @Override - public boolean isWorldStateAvailable(final Bytes32 rootHash, final Hash blockHash) { - return getAccountStateTrieNode(Bytes.EMPTY, rootHash).isPresent(); - } - - @Override - public void clear() { - keyValueStorage.clear(); - } - - @Override - public void clearTrieLog() { - // nothing to do for forest - } - - @Override - public void clearFlatDatabase() { - // nothing to do for forest - } - - @Override - public Updater updater() { - return new Updater(lock, keyValueStorage.startTransaction(), nodeAddedListeners); - } - - @Override - public long prune(final Predicate inUseCheck) { - final AtomicInteger prunedKeys = new AtomicInteger(0); - try (final Stream entry = keyValueStorage.streamKeys()) { - entry.forEach( - key -> { - lock.lock(); - try { - if (!inUseCheck.test(key) && keyValueStorage.tryDelete(key)) { - prunedKeys.incrementAndGet(); - } - } finally { - lock.unlock(); - } - }); - } - - return prunedKeys.get(); - } - - @Override - public long addNodeAddedListener(final NodesAddedListener listener) { - return nodeAddedListeners.subscribe(listener); - } - - @Override - public void removeNodeAddedListener(final long id) { - nodeAddedListeners.unsubscribe(id); - } - - public static class Updater implements WorldStateStorage.Updater { - - private final KeyValueStorageTransaction transaction; - private final Subscribers nodeAddedListeners; - private final Set addedNodes = new HashSet<>(); - private final Lock lock; - - public Updater( - final Lock lock, - final KeyValueStorageTransaction transaction, - final Subscribers nodeAddedListeners) { - this.lock = lock; - this.transaction = transaction; - this.nodeAddedListeners = nodeAddedListeners; - } - - @Override - public WorldStateStorage.Updater putCode( - final Hash accountHash, final Bytes32 codeHash, final Bytes code) { - if (code.size() == 0) { - // Don't save empty values - return this; - } - - addedNodes.add(codeHash); - transaction.put(codeHash.toArrayUnsafe(), code.toArrayUnsafe()); - return this; - } - - @Override - public WorldStateStorage.Updater saveWorldState( - final Bytes blockHash, final Bytes32 nodeHash, final Bytes node) { - return putAccountStateTrieNode(null, nodeHash, node); - } - - @Override - public Updater putAccountStateTrieNode( - final Bytes location, final Bytes32 nodeHash, final Bytes node) { - if (nodeHash.equals(MerkleTrie.EMPTY_TRIE_NODE_HASH)) { - // Don't save empty nodes - return this; - } - addedNodes.add(nodeHash); - transaction.put(nodeHash.toArrayUnsafe(), node.toArrayUnsafe()); - return this; - } - - @Override - public WorldStateStorage.Updater removeAccountStateTrieNode( - final Bytes location, final Bytes32 nodeHash) { - transaction.remove(nodeHash.toArrayUnsafe()); - return this; - } - - @Override - public Updater putAccountStorageTrieNode( - final Hash accountHash, final Bytes location, final Bytes32 nodeHash, final Bytes node) { - if (nodeHash.equals(MerkleTrie.EMPTY_TRIE_NODE_HASH)) { - // Don't save empty nodes - return this; - } - addedNodes.add(nodeHash); - transaction.put(nodeHash.toArrayUnsafe(), node.toArrayUnsafe()); - return this; - } - - @Override - public void commit() { - lock.lock(); - try { - nodeAddedListeners.forEach(listener -> listener.onNodesAdded(addedNodes)); - transaction.commit(); - } finally { - lock.unlock(); - } - } - - @Override - public void rollback() { - addedNodes.clear(); - transaction.rollback(); - } - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/CallParameter.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/CallParameter.java index 433be4673a3..292c65cac61 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/CallParameter.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/CallParameter.java @@ -16,6 +16,7 @@ import org.hyperledger.besu.datatypes.AccessListEntry; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.VersionedHash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.Transaction; @@ -25,7 +26,7 @@ import org.apache.tuweni.bytes.Bytes; -// Represents parameters for a eth_call or eth_estimateGas JSON-RPC methods. +// Represents parameters for eth_call and eth_estimateGas JSON-RPC methods. public class CallParameter { private final Address from; @@ -37,6 +38,7 @@ public class CallParameter { private final Optional maxPriorityFeePerGas; private final Optional maxFeePerGas; + private final Optional maxFeePerBlobGas; private final Wei gasPrice; @@ -45,6 +47,7 @@ public class CallParameter { private final Bytes payload; private final Optional> accessList; + private final Optional> blobVersionedHashes; public CallParameter( final Address from, @@ -62,6 +65,8 @@ public CallParameter( this.gasPrice = gasPrice; this.value = value; this.payload = payload; + this.maxFeePerBlobGas = Optional.empty(); + this.blobVersionedHashes = Optional.empty(); } public CallParameter( @@ -83,6 +88,33 @@ public CallParameter( this.value = value; this.payload = payload; this.accessList = accessList; + this.maxFeePerBlobGas = Optional.empty(); + this.blobVersionedHashes = Optional.empty(); + } + + public CallParameter( + final Address from, + final Address to, + final long gasLimit, + final Wei gasPrice, + final Optional maxPriorityFeePerGas, + final Optional maxFeePerGas, + final Wei value, + final Bytes payload, + final Optional> accessList, + final Optional maxFeePerBlobGas, + final Optional> blobVersionedHashes) { + this.from = from; + this.to = to; + this.gasLimit = gasLimit; + this.maxPriorityFeePerGas = maxPriorityFeePerGas; + this.maxFeePerGas = maxFeePerGas; + this.gasPrice = gasPrice; + this.value = value; + this.payload = payload; + this.accessList = accessList; + this.maxFeePerBlobGas = maxFeePerBlobGas; + this.blobVersionedHashes = blobVersionedHashes; } public Address getFrom() { @@ -121,6 +153,14 @@ public Optional> getAccessList() { return accessList; } + public Optional getMaxFeePerBlobGas() { + return maxFeePerBlobGas; + } + + public Optional> getBlobVersionedHashes() { + return blobVersionedHashes; + } + @Override public boolean equals(final Object o) { if (this == o) { @@ -137,25 +177,83 @@ public boolean equals(final Object o) { && Objects.equals(maxPriorityFeePerGas, that.maxPriorityFeePerGas) && Objects.equals(maxFeePerGas, that.maxFeePerGas) && Objects.equals(value, that.value) - && Objects.equals(payload, that.payload); + && Objects.equals(payload, that.payload) + && Objects.equals(accessList, that.accessList) + && Objects.equals(maxFeePerBlobGas, that.maxFeePerBlobGas) + && Objects.equals(blobVersionedHashes, that.blobVersionedHashes); } @Override public int hashCode() { return Objects.hash( - from, to, gasLimit, gasPrice, maxPriorityFeePerGas, maxFeePerGas, value, payload); + from, + to, + gasLimit, + gasPrice, + maxPriorityFeePerGas, + maxFeePerGas, + value, + payload, + accessList, + maxFeePerBlobGas, + blobVersionedHashes); + } + + @Override + public String toString() { + return "CallParameter{" + + "from=" + + from + + ", to=" + + to + + ", gasLimit=" + + gasLimit + + ", maxPriorityFeePerGas=" + + maxPriorityFeePerGas.map(Wei::toHumanReadableString).orElse("N/A") + + ", maxFeePerGas=" + + maxFeePerGas.map(Wei::toHumanReadableString).orElse("N/A") + + ", maxFeePerBlobGas=" + + maxFeePerBlobGas.map(Wei::toHumanReadableString).orElse("N/A") + + ", gasPrice=" + + (gasPrice != null ? gasPrice.toHumanReadableString() : "N/A") + + ", value=" + + (value != null ? value.toHumanReadableString() : "N/A") + + ", payloadSize=" + + (payload != null ? payload.size() : "null") + + ", accessListSize=" + + accessList.map(List::size) + + ", blobVersionedHashesSize=" + + blobVersionedHashes.map(List::size) + + '}'; } public static CallParameter fromTransaction(final Transaction tx) { return new CallParameter( tx.getSender(), - tx.getTo().orElseGet(() -> null), + tx.getTo().orElse(null), tx.getGasLimit(), - Wei.fromQuantity(tx.getGasPrice().orElseGet(() -> Wei.ZERO)), - Optional.of(Wei.fromQuantity(tx.getMaxPriorityFeePerGas().orElseGet(() -> Wei.ZERO))), + tx.getGasPrice().orElse(Wei.ZERO), + tx.getMaxPriorityFeePerGas(), tx.getMaxFeePerGas(), + tx.getValue(), + tx.getPayload(), + tx.getAccessList(), + tx.getMaxFeePerBlobGas(), + tx.getVersionedHashes()); + } + + public static CallParameter fromTransaction(final org.hyperledger.besu.datatypes.Transaction tx) { + return new CallParameter( + tx.getSender(), + tx.getTo().orElse(null), + tx.getGasLimit(), + tx.getGasPrice().map(Wei::fromQuantity).orElse(Wei.ZERO), + tx.getMaxPriorityFeePerGas().map(Wei::fromQuantity), + tx.getMaxFeePerGas().map(Wei::fromQuantity), Wei.fromQuantity(tx.getValue()), tx.getPayload(), - tx.getAccessList()); + tx.getAccessList(), + tx.getMaxFeePerBlobGas().map(Wei::fromQuantity), + tx.getVersionedHashes()); } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/PreCloseStateHandler.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/PreCloseStateHandler.java index 7bae8c100ea..879d7dd2a76 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/PreCloseStateHandler.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/PreCloseStateHandler.java @@ -1,6 +1,5 @@ /* - * - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.transaction; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java index aef98171b42..8273c556d91 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java @@ -1,20 +1,17 @@ /* + * Copyright ConsenSys AG. * - * * Copyright ConsenSys AG. - * * - * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * * the License. You may obtain a copy of the License at - * * - * * http://www.apache.org/licenses/LICENSE-2.0 - * * - * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * * specific language governing permissions and limitations under the License. - * * - * * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.transaction; public enum TransactionInvalidReason { @@ -32,6 +29,7 @@ public enum TransactionInvalidReason { TX_SENDER_NOT_AUTHORIZED, CHAIN_HEAD_NOT_AVAILABLE, CHAIN_HEAD_WORLD_STATE_NOT_AVAILABLE, + BLOCK_NOT_FOUND, EXCEEDS_PER_TRANSACTION_GAS_LIMIT, INVALID_TRANSACTION_FORMAT, TRANSACTION_PRICE_TOO_LOW, @@ -43,12 +41,16 @@ public enum TransactionInvalidReason { TOTAL_BLOB_GAS_TOO_HIGH, GAS_PRICE_TOO_LOW, GAS_PRICE_BELOW_CURRENT_BASE_FEE, + BLOB_GAS_PRICE_BELOW_CURRENT_BLOB_BASE_FEE, MAX_FEE_PER_GAS_BELOW_CURRENT_BASE_FEE, TX_FEECAP_EXCEEDED, INTERNAL_ERROR, TX_POOL_DISABLED, INVALID_BLOBS, - PLUGIN_TX_VALIDATOR, + PLUGIN_TX_POOL_VALIDATOR, + EXECUTION_HALTED, + EOF_CODE_INVALID, + EMPTY_CODE_DELEGATION, // Private Transaction Invalid Reasons PRIVATE_TRANSACTION_INVALID, PRIVATE_TRANSACTION_FAILED, diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java index 3b94f888179..070c1dcf771 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulator.java @@ -106,7 +106,21 @@ public Optional process( header); } + public Optional process( + final CallParameter callParams, + final TransactionValidationParams transactionValidationParams, + final OperationTracer operationTracer, + final BlockHeader blockHeader) { + return process( + callParams, + transactionValidationParams, + operationTracer, + (mutableWorldState, transactionSimulatorResult) -> transactionSimulatorResult, + blockHeader); + } + public Optional processAtHead(final CallParameter callParams) { + final var chainHeadHash = blockchain.getChainHeadHash(); return process( callParams, ImmutableTransactionValidationParams.builder() @@ -115,7 +129,10 @@ public Optional processAtHead(final CallParameter ca .build(), OperationTracer.NO_TRACING, (mutableWorldState, transactionSimulatorResult) -> transactionSimulatorResult, - blockchain.getChainHeadHeader()); + blockchain + .getBlockHeader(chainHeadHash) + .or(() -> blockchain.getBlockHeaderSafe(chainHeadHash)) + .orElse(null)); } /** @@ -218,11 +235,8 @@ public Optional processWithWorldUpdater( ? callParams.getGasLimit() : blockHeaderToProcess.getGasLimit(); if (rpcGasCap > 0) { - final long gasCap = rpcGasCap; - if (gasCap < gasLimit) { - gasLimit = gasCap; - LOG.info("Capping gasLimit to " + gasCap); - } + gasLimit = rpcGasCap; + LOG.info("Capping gasLimit to " + rpcGasCap); } final Wei value = callParams.getValue() != null ? callParams.getValue() : Wei.ZERO; final Bytes payload = callParams.getPayload() != null ? callParams.getPayload() : Bytes.EMPTY; @@ -230,6 +244,18 @@ public Optional processWithWorldUpdater( final MainnetTransactionProcessor transactionProcessor = protocolSchedule.getByBlockHeader(blockHeaderToProcess).getTransactionProcessor(); + final Optional maybeParentHeader = + blockchain.getBlockHeader(blockHeaderToProcess.getParentHash()); + final Wei blobGasPrice = + transactionValidationParams.isAllowExceedingBalance() + ? Wei.ZERO + : protocolSpec + .getFeeMarket() + .blobGasPricePerGas( + maybeParentHeader + .map(parent -> calculateExcessBlobGasForParent(protocolSpec, parent)) + .orElse(BlobGas.ZERO)); + final Optional maybeTransaction = buildTransaction( callParams, @@ -239,25 +265,15 @@ public Optional processWithWorldUpdater( nonce, gasLimit, value, - payload); + payload, + blobGasPrice); if (maybeTransaction.isEmpty()) { return Optional.empty(); } - final Optional maybeParentHeader = - blockchain.getBlockHeader(blockHeaderToProcess.getParentHash()); - final Wei blobGasPrice = - protocolSpec - .getFeeMarket() - .blobGasPricePerGas( - maybeParentHeader - .map(parent -> calculateExcessBlobGasForParent(protocolSpec, parent)) - .orElse(BlobGas.ZERO)); - final Transaction transaction = maybeTransaction.get(); final TransactionProcessingResult result = transactionProcessor.processTransaction( - blockchain, updater, blockHeaderToProcess, transaction, @@ -281,7 +297,8 @@ private Optional buildTransaction( final long nonce, final long gasLimit, final Wei value, - final Bytes payload) { + final Bytes payload, + final Wei blobGasPrice) { final Transaction.Builder transactionBuilder = Transaction.builder() .nonce(nonce) @@ -294,18 +311,23 @@ private Optional buildTransaction( // Set access list if present callParams.getAccessList().ifPresent(transactionBuilder::accessList); + // Set versioned hashes if present + callParams.getBlobVersionedHashes().ifPresent(transactionBuilder::versionedHashes); final Wei gasPrice; final Wei maxFeePerGas; final Wei maxPriorityFeePerGas; + final Wei maxFeePerBlobGas; if (transactionValidationParams.isAllowExceedingBalance()) { gasPrice = Wei.ZERO; maxFeePerGas = Wei.ZERO; maxPriorityFeePerGas = Wei.ZERO; + maxFeePerBlobGas = Wei.ZERO; } else { gasPrice = callParams.getGasPrice() != null ? callParams.getGasPrice() : Wei.ZERO; maxFeePerGas = callParams.getMaxFeePerGas().orElse(gasPrice); maxPriorityFeePerGas = callParams.getMaxPriorityFeePerGas().orElse(gasPrice); + maxFeePerBlobGas = callParams.getMaxFeePerBlobGas().orElse(blobGasPrice); } if (header.getBaseFee().isEmpty()) { transactionBuilder.gasPrice(gasPrice); @@ -316,6 +338,9 @@ private Optional buildTransaction( } transactionBuilder.guessType(); + if (transactionBuilder.getTransactionType().supportsBlob()) { + transactionBuilder.maxFeePerBlobGas(maxFeePerBlobGas); + } if (transactionBuilder.getTransactionType().requiresChainId()) { transactionBuilder.chainId( protocolSchedule diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorResult.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorResult.java index 0627ca14fd2..853bc4611a3 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorResult.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorResult.java @@ -18,22 +18,10 @@ import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; -import java.util.Objects; - -import com.google.common.annotations.VisibleForTesting; import org.apache.tuweni.bytes.Bytes; -public class TransactionSimulatorResult { - - private final Transaction transaction; - private final TransactionProcessingResult result; - - @VisibleForTesting - public TransactionSimulatorResult( - final Transaction transaction, final TransactionProcessingResult result) { - this.transaction = transaction; - this.result = result; - } +public record TransactionSimulatorResult( + Transaction transaction, TransactionProcessingResult result) { public boolean isSuccessful() { return result.isSuccessful(); @@ -54,40 +42,4 @@ public Bytes getOutput() { public ValidationResult getValidationResult() { return result.getValidationResult(); } - - public TransactionProcessingResult getResult() { - return result; - } - - public Transaction getTransaction() { - return transaction; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - final TransactionSimulatorResult that = (TransactionSimulatorResult) o; - return Objects.equals(transaction, that.transaction) && Objects.equals(result, that.result); - } - - @Override - public int hashCode() { - return Objects.hash(transaction, result); - } - - @Override - public String toString() { - return "TransactionSimulatorResult{" - + "transaction=" - + transaction - + ", " - + "result=" - + result - + "}"; - } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/common/GenesisWorldStateProvider.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/common/GenesisWorldStateProvider.java new file mode 100644 index 00000000000..78bcb417e37 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/common/GenesisWorldStateProvider.java @@ -0,0 +1,92 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.common; + +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStorageProvider; +import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStatePreimageKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.BonsaiCachedMerkleTrieLoader; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.NoOpBonsaiCachedWorldStorageManager; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldState; +import org.hyperledger.besu.ethereum.trie.diffbased.common.trielog.NoOpTrieLogManager; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldStateConfig; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.forest.worldview.ForestMutableWorldState; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; +import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; +import org.hyperledger.besu.services.kvstore.SegmentedInMemoryKeyValueStorage; + +import java.util.Objects; + +public class GenesisWorldStateProvider { + + /** + * Creates a Genesis world state based on the provided data storage format. + * + * @param dataStorageConfiguration the data storage configuration to use + * @return a mutable world state for the Genesis block + */ + public static MutableWorldState createGenesisWorldState( + final DataStorageConfiguration dataStorageConfiguration) { + if (Objects.requireNonNull(dataStorageConfiguration).getDataStorageFormat() + == DataStorageFormat.BONSAI) { + return createGenesisBonsaiWorldState(); + } else { + return createGenesisForestWorldState(); + } + } + + /** + * Creates a Genesis world state using the Bonsai data storage format. + * + * @return a mutable world state for the Genesis block + */ + private static MutableWorldState createGenesisBonsaiWorldState() { + final BonsaiCachedMerkleTrieLoader bonsaiCachedMerkleTrieLoader = + new BonsaiCachedMerkleTrieLoader(new NoOpMetricsSystem()); + final BonsaiWorldStateKeyValueStorage bonsaiWorldStateKeyValueStorage = + new BonsaiWorldStateKeyValueStorage( + new KeyValueStorageProvider( + segmentIdentifiers -> new SegmentedInMemoryKeyValueStorage(), + new InMemoryKeyValueStorage(), + new NoOpMetricsSystem()), + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG); + return new BonsaiWorldState( + bonsaiWorldStateKeyValueStorage, + bonsaiCachedMerkleTrieLoader, + new NoOpBonsaiCachedWorldStorageManager(bonsaiWorldStateKeyValueStorage), + new NoOpTrieLogManager(), + EvmConfiguration.DEFAULT, + new DiffBasedWorldStateConfig()); + } + + /** + * Creates a Genesis world state using the Forest data storage format. + * + * @return a mutable world state for the Genesis block + */ + private static MutableWorldState createGenesisForestWorldState() { + final ForestWorldStateKeyValueStorage stateStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final WorldStatePreimageKeyValueStorage preimageStorage = + new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage()); + return new ForestMutableWorldState(stateStorage, preimageStorage, EvmConfiguration.DEFAULT); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/BonsaiAccount.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/BonsaiAccount.java new file mode 100644 index 00000000000..a9f85cce9ee --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/BonsaiAccount.java @@ -0,0 +1,200 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai; + +import org.hyperledger.besu.datatypes.AccountValue; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.rlp.RLPException; +import org.hyperledger.besu.ethereum.rlp.RLPInput; +import org.hyperledger.besu.ethereum.rlp.RLPOutput; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.common.DiffBasedAccount; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldView; +import org.hyperledger.besu.evm.ModificationNotAllowedException; +import org.hyperledger.besu.evm.account.AccountStorageEntry; +import org.hyperledger.besu.evm.worldstate.UpdateTrackingAccount; + +import java.util.NavigableMap; +import java.util.Objects; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +public class BonsaiAccount extends DiffBasedAccount { + private Hash storageRoot; + + public BonsaiAccount( + final DiffBasedWorldView context, + final Address address, + final Hash addressHash, + final long nonce, + final Wei balance, + final Hash storageRoot, + final Hash codeHash, + final boolean mutable) { + super(context, address, addressHash, nonce, balance, codeHash, mutable); + this.storageRoot = storageRoot; + } + + public BonsaiAccount( + final DiffBasedWorldView context, + final Address address, + final AccountValue stateTrieAccount, + final boolean mutable) { + super( + context, + address, + address.addressHash(), + stateTrieAccount.getNonce(), + stateTrieAccount.getBalance(), + stateTrieAccount.getCodeHash(), + mutable); + this.storageRoot = stateTrieAccount.getStorageRoot(); + } + + public BonsaiAccount(final BonsaiAccount toCopy) { + this(toCopy, toCopy.context, false); + } + + public BonsaiAccount( + final BonsaiAccount toCopy, final DiffBasedWorldView context, final boolean mutable) { + super( + context, + toCopy.address, + toCopy.addressHash, + toCopy.nonce, + toCopy.balance, + toCopy.codeHash, + toCopy.code, + mutable); + this.storageRoot = toCopy.storageRoot; + updatedStorage.putAll(toCopy.updatedStorage); + } + + public BonsaiAccount( + final DiffBasedWorldView context, final UpdateTrackingAccount tracked) { + super( + context, + tracked.getAddress(), + tracked.getAddressHash(), + tracked.getNonce(), + tracked.getBalance(), + tracked.getCodeHash(), + tracked.getCode(), + true); + this.storageRoot = Hash.EMPTY_TRIE_HASH; + updatedStorage.putAll(tracked.getUpdatedStorage()); + } + + public static BonsaiAccount fromRLP( + final DiffBasedWorldView context, + final Address address, + final Bytes encoded, + final boolean mutable) + throws RLPException { + final RLPInput in = RLP.input(encoded); + in.enterList(); + + final long nonce = in.readLongScalar(); + final Wei balance = Wei.of(in.readUInt256Scalar()); + final Hash storageRoot = Hash.wrap(in.readBytes32()); + final Hash codeHash = Hash.wrap(in.readBytes32()); + + in.leaveList(); + + return new BonsaiAccount( + context, address, address.addressHash(), nonce, balance, storageRoot, codeHash, mutable); + } + + @Override + public boolean isStorageEmpty() { + return Hash.EMPTY_TRIE_HASH.equals(storageRoot); + } + + @Override + public NavigableMap storageEntriesFrom( + final Bytes32 startKeyHash, final int limit) { + return ((BonsaiWorldStateKeyValueStorage) context.getWorldStateStorage()) + .storageEntriesFrom(this.addressHash, startKeyHash, limit); + } + + @Override + public void writeTo(final RLPOutput out) { + out.startList(); + + out.writeLongScalar(nonce); + out.writeUInt256Scalar(balance); + out.writeBytes(storageRoot); + out.writeBytes(codeHash); + + out.endList(); + } + + @Override + public Hash getStorageRoot() { + return storageRoot; + } + + public void setStorageRoot(final Hash storageRoot) { + if (immutable) { + throw new ModificationNotAllowedException(); + } + this.storageRoot = storageRoot; + } + + @Override + public String toString() { + return "AccountState{" + + "address=" + + address + + ", nonce=" + + nonce + + ", balance=" + + balance + + ", storageRoot=" + + storageRoot + + ", codeHash=" + + codeHash + + '}'; + } + + /** + * Throws an exception if the two accounts represent different stored states + * + * @param source The bonsai account to compare + * @param account The State Trie account to compare + * @param context a description to be added to the thrown exceptions + * @throws IllegalStateException if the stored values differ + */ + public static void assertCloseEnoughForDiffing( + final BonsaiAccount source, final AccountValue account, final String context) { + if (source == null) { + throw new IllegalStateException(context + ": source is null but target isn't"); + } else { + if (source.nonce != account.getNonce()) { + throw new IllegalStateException(context + ": nonces differ"); + } + if (!Objects.equals(source.balance, account.getBalance())) { + throw new IllegalStateException(context + ": balances differ"); + } + if (!Objects.equals(source.storageRoot, account.getStorageRoot())) { + throw new IllegalStateException(context + ": Storage Roots differ"); + } + } + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/BonsaiWorldStateProvider.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/BonsaiWorldStateProvider.java new file mode 100644 index 00000000000..e4b7ea991f3 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/BonsaiWorldStateProvider.java @@ -0,0 +1,154 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.BonsaiCachedMerkleTrieLoader; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.BonsaiCachedWorldStorageManager; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldState; +import org.hyperledger.besu.ethereum.trie.diffbased.common.DiffBasedWorldStateProvider; +import org.hyperledger.besu.ethereum.trie.diffbased.common.trielog.TrieLogManager; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldStateConfig; +import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; +import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.plugin.BesuContext; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +import com.google.common.annotations.VisibleForTesting; +import org.apache.tuweni.bytes.Bytes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BonsaiWorldStateProvider extends DiffBasedWorldStateProvider { + + private static final Logger LOG = LoggerFactory.getLogger(BonsaiWorldStateProvider.class); + private final BonsaiCachedMerkleTrieLoader bonsaiCachedMerkleTrieLoader; + + public BonsaiWorldStateProvider( + final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage, + final Blockchain blockchain, + final Optional maxLayersToLoad, + final BonsaiCachedMerkleTrieLoader bonsaiCachedMerkleTrieLoader, + final BesuContext pluginContext, + final EvmConfiguration evmConfiguration) { + super(worldStateKeyValueStorage, blockchain, maxLayersToLoad, pluginContext); + this.bonsaiCachedMerkleTrieLoader = bonsaiCachedMerkleTrieLoader; + provideCachedWorldStorageManager( + new BonsaiCachedWorldStorageManager( + this, worldStateKeyValueStorage, this::cloneBonsaiWorldStateConfig)); + loadPersistedState( + new BonsaiWorldState( + this, worldStateKeyValueStorage, evmConfiguration, defaultWorldStateConfig)); + } + + @VisibleForTesting + BonsaiWorldStateProvider( + final BonsaiCachedWorldStorageManager bonsaiCachedWorldStorageManager, + final TrieLogManager trieLogManager, + final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage, + final Blockchain blockchain, + final BonsaiCachedMerkleTrieLoader bonsaiCachedMerkleTrieLoader, + final EvmConfiguration evmConfiguration) { + super(worldStateKeyValueStorage, blockchain, trieLogManager); + this.bonsaiCachedMerkleTrieLoader = bonsaiCachedMerkleTrieLoader; + provideCachedWorldStorageManager(bonsaiCachedWorldStorageManager); + loadPersistedState( + new BonsaiWorldState( + this, worldStateKeyValueStorage, evmConfiguration, defaultWorldStateConfig)); + } + + public BonsaiCachedMerkleTrieLoader getCachedMerkleTrieLoader() { + return bonsaiCachedMerkleTrieLoader; + } + + private BonsaiWorldStateKeyValueStorage getBonsaiWorldStateKeyValueStorage() { + return (BonsaiWorldStateKeyValueStorage) worldStateKeyValueStorage; + } + + /** + * Prepares the state healing process for a given address and location. It prepares the state + * healing, including retrieving data from storage, identifying invalid slots or nodes, removing + * account and slot from the state trie, and committing the changes. Finally, it downgrades the + * world state storage to partial flat database mode. + */ + public void prepareStateHealing(final Address address, final Bytes location) { + final Set keysToDelete = new HashSet<>(); + final BonsaiWorldStateKeyValueStorage.Updater updater = + getBonsaiWorldStateKeyValueStorage().updater(); + final Hash accountHash = address.addressHash(); + final StoredMerklePatriciaTrie accountTrie = + new StoredMerklePatriciaTrie<>( + (l, h) -> { + final Optional node = + getBonsaiWorldStateKeyValueStorage().getAccountStateTrieNode(l, h); + if (node.isPresent()) { + keysToDelete.add(l); + } + return node; + }, + persistedState.getWorldStateRootHash(), + Function.identity(), + Function.identity()); + try { + accountTrie + .get(accountHash) + .map(RLP::input) + .map(StateTrieAccountValue::readFrom) + .ifPresent( + account -> { + final StoredMerklePatriciaTrie storageTrie = + new StoredMerklePatriciaTrie<>( + (l, h) -> { + Optional node = + getBonsaiWorldStateKeyValueStorage() + .getAccountStorageTrieNode(accountHash, l, h); + if (node.isPresent()) { + keysToDelete.add(Bytes.concatenate(accountHash, l)); + } + return node; + }, + account.getStorageRoot(), + Function.identity(), + Function.identity()); + try { + storageTrie.getPath(location); + } catch (Exception eA) { + LOG.warn("Invalid slot found for account {} at location {}", address, location); + // ignore + } + }); + } catch (Exception eA) { + LOG.warn("Invalid node for account {} at location {}", address, location); + // ignore + } + keysToDelete.forEach(updater::removeAccountStateTrieNode); + updater.commit(); + + getBonsaiWorldStateKeyValueStorage().downgradeToPartialFlatDbMode(); + } + + private DiffBasedWorldStateConfig cloneBonsaiWorldStateConfig() { + return new DiffBasedWorldStateConfig(defaultWorldStateConfig); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/cache/BonsaiCachedMerkleTrieLoader.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/cache/BonsaiCachedMerkleTrieLoader.java new file mode 100644 index 00000000000..9b9960b5c67 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/cache/BonsaiCachedMerkleTrieLoader.java @@ -0,0 +1,162 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.StorageSlotKey; +import org.hyperledger.besu.ethereum.trie.MerkleTrie; +import org.hyperledger.besu.ethereum.trie.MerkleTrieException; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.common.StorageSubscriber; +import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; +import org.hyperledger.besu.metrics.BesuMetricCategory; +import org.hyperledger.besu.metrics.ObservableMetricsSystem; +import org.hyperledger.besu.metrics.prometheus.PrometheusMetricsSystem; + +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import io.prometheus.client.guava.cache.CacheMetricsCollector; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +public class BonsaiCachedMerkleTrieLoader implements StorageSubscriber { + + private static final int ACCOUNT_CACHE_SIZE = 100_000; + private static final int STORAGE_CACHE_SIZE = 200_000; + private final Cache accountNodes = + CacheBuilder.newBuilder().recordStats().maximumSize(ACCOUNT_CACHE_SIZE).build(); + private final Cache storageNodes = + CacheBuilder.newBuilder().recordStats().maximumSize(STORAGE_CACHE_SIZE).build(); + + public BonsaiCachedMerkleTrieLoader(final ObservableMetricsSystem metricsSystem) { + + CacheMetricsCollector cacheMetrics = new CacheMetricsCollector(); + cacheMetrics.addCache("accountsNodes", accountNodes); + cacheMetrics.addCache("storageNodes", storageNodes); + if (metricsSystem instanceof PrometheusMetricsSystem prometheusMetricsSystem) + prometheusMetricsSystem.addCollector(BesuMetricCategory.BLOCKCHAIN, () -> cacheMetrics); + } + + public void preLoadAccount( + final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage, + final Hash worldStateRootHash, + final Address account) { + CompletableFuture.runAsync( + () -> cacheAccountNodes(worldStateKeyValueStorage, worldStateRootHash, account)); + } + + @VisibleForTesting + public void cacheAccountNodes( + final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage, + final Hash worldStateRootHash, + final Address account) { + final long storageSubscriberId = worldStateKeyValueStorage.subscribe(this); + try { + final StoredMerklePatriciaTrie accountTrie = + new StoredMerklePatriciaTrie<>( + (location, hash) -> { + Optional node = + getAccountStateTrieNode(worldStateKeyValueStorage, location, hash); + node.ifPresent(bytes -> accountNodes.put(Hash.hash(bytes), bytes)); + return node; + }, + worldStateRootHash, + Function.identity(), + Function.identity()); + accountTrie.get(account.addressHash()); + } catch (MerkleTrieException e) { + // ignore exception for the cache + } finally { + worldStateKeyValueStorage.unSubscribe(storageSubscriberId); + } + } + + public void preLoadStorageSlot( + final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage, + final Address account, + final StorageSlotKey slotKey) { + CompletableFuture.runAsync( + () -> cacheStorageNodes(worldStateKeyValueStorage, account, slotKey)); + } + + @VisibleForTesting + public void cacheStorageNodes( + final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage, + final Address account, + final StorageSlotKey slotKey) { + final Hash accountHash = account.addressHash(); + final long storageSubscriberId = worldStateKeyValueStorage.subscribe(this); + try { + worldStateKeyValueStorage + .getStateTrieNode(Bytes.concatenate(accountHash, Bytes.EMPTY)) + .ifPresent( + storageRoot -> { + try { + final StoredMerklePatriciaTrie storageTrie = + new StoredMerklePatriciaTrie<>( + (location, hash) -> { + Optional node = + getAccountStorageTrieNode( + worldStateKeyValueStorage, accountHash, location, hash); + node.ifPresent(bytes -> storageNodes.put(Hash.hash(bytes), bytes)); + return node; + }, + Hash.hash(storageRoot), + Function.identity(), + Function.identity()); + storageTrie.get(slotKey.getSlotHash()); + } catch (MerkleTrieException e) { + // ignore exception for the cache + } + }); + } finally { + worldStateKeyValueStorage.unSubscribe(storageSubscriberId); + } + } + + public Optional getAccountStateTrieNode( + final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage, + final Bytes location, + final Bytes32 nodeHash) { + if (nodeHash.equals(MerkleTrie.EMPTY_TRIE_NODE_HASH)) { + return Optional.of(MerkleTrie.EMPTY_TRIE_NODE); + } else { + return Optional.ofNullable(accountNodes.getIfPresent(nodeHash)) + .or(() -> worldStateKeyValueStorage.getAccountStateTrieNode(location, nodeHash)); + } + } + + public Optional getAccountStorageTrieNode( + final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage, + final Hash accountHash, + final Bytes location, + final Bytes32 nodeHash) { + if (nodeHash.equals(MerkleTrie.EMPTY_TRIE_NODE_HASH)) { + return Optional.of(MerkleTrie.EMPTY_TRIE_NODE); + } else { + return Optional.ofNullable(storageNodes.getIfPresent(nodeHash)) + .or( + () -> + worldStateKeyValueStorage.getAccountStorageTrieNode( + accountHash, location, nodeHash)); + } + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/cache/BonsaiCachedMerkleTrieLoaderModule.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/cache/BonsaiCachedMerkleTrieLoaderModule.java new file mode 100644 index 00000000000..8ed7daa35f1 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/cache/BonsaiCachedMerkleTrieLoaderModule.java @@ -0,0 +1,30 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache; + +import org.hyperledger.besu.metrics.ObservableMetricsSystem; + +import dagger.Module; +import dagger.Provides; + +@Module +public class BonsaiCachedMerkleTrieLoaderModule { + + @Provides + BonsaiCachedMerkleTrieLoader provideCachedMerkleTrieLoaderModule( + final ObservableMetricsSystem metricsSystem) { + return new BonsaiCachedMerkleTrieLoader(metricsSystem); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/cache/BonsaiCachedWorldStorageManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/cache/BonsaiCachedWorldStorageManager.java new file mode 100644 index 00000000000..60cf25e880c --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/cache/BonsaiCachedWorldStorageManager.java @@ -0,0 +1,65 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache; + +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.BonsaiWorldStateProvider; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiSnapshotWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateLayerStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldState; +import org.hyperledger.besu.ethereum.trie.diffbased.common.DiffBasedWorldStateProvider; +import org.hyperledger.besu.ethereum.trie.diffbased.common.cache.DiffBasedCachedWorldStorageManager; +import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldState; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldStateConfig; +import org.hyperledger.besu.evm.internal.EvmConfiguration; + +import java.util.function.Supplier; + +public class BonsaiCachedWorldStorageManager extends DiffBasedCachedWorldStorageManager { + + public BonsaiCachedWorldStorageManager( + final BonsaiWorldStateProvider archive, + final DiffBasedWorldStateKeyValueStorage worldStateKeyValueStorage, + final Supplier defaultBonsaiWorldStateConfigSupplier) { + super(archive, worldStateKeyValueStorage, defaultBonsaiWorldStateConfigSupplier); + } + + @Override + public DiffBasedWorldState createWorldState( + final DiffBasedWorldStateProvider archive, + final DiffBasedWorldStateKeyValueStorage worldStateKeyValueStorage, + final EvmConfiguration evmConfiguration) { + return new BonsaiWorldState( + (BonsaiWorldStateProvider) archive, + (BonsaiWorldStateKeyValueStorage) worldStateKeyValueStorage, + evmConfiguration, + defaultBonsaiWorldStateConfigSupplier.get()); + } + + @Override + public DiffBasedWorldStateKeyValueStorage createLayeredKeyValueStorage( + final DiffBasedWorldStateKeyValueStorage worldStateKeyValueStorage) { + return new BonsaiWorldStateLayerStorage( + (BonsaiWorldStateKeyValueStorage) worldStateKeyValueStorage); + } + + @Override + public DiffBasedWorldStateKeyValueStorage createSnapshotKeyValueStorage( + final DiffBasedWorldStateKeyValueStorage worldStateKeyValueStorage) { + return new BonsaiSnapshotWorldStateKeyValueStorage( + (BonsaiWorldStateKeyValueStorage) worldStateKeyValueStorage); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/cache/NoOpBonsaiCachedWorldStorageManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/cache/NoOpBonsaiCachedWorldStorageManager.java new file mode 100644 index 00000000000..a054b437037 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/cache/NoOpBonsaiCachedWorldStorageManager.java @@ -0,0 +1,66 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldState; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldStateConfig; + +import java.util.Optional; +import java.util.function.Function; + +public class NoOpBonsaiCachedWorldStorageManager extends BonsaiCachedWorldStorageManager { + + public NoOpBonsaiCachedWorldStorageManager( + final BonsaiWorldStateKeyValueStorage bonsaiWorldStateKeyValueStorage) { + super(null, bonsaiWorldStateKeyValueStorage, DiffBasedWorldStateConfig::new); + } + + @Override + public synchronized void addCachedLayer( + final BlockHeader blockHeader, + final Hash worldStateRootHash, + final DiffBasedWorldState forWorldState) { + // no cache + } + + @Override + public boolean contains(final Hash blockHash) { + return false; + } + + @Override + public Optional getWorldState(final Hash blockHash) { + return Optional.empty(); + } + + @Override + public Optional getNearestWorldState(final BlockHeader blockHeader) { + return Optional.empty(); + } + + @Override + public Optional getHeadWorldState( + final Function> hashBlockHeaderFunction) { + return Optional.empty(); + } + + @Override + public void reset() { + // world states are not re-used + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/cache/NoopBonsaiCachedMerkleTrieLoader.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/cache/NoopBonsaiCachedMerkleTrieLoader.java new file mode 100644 index 00000000000..8f58e697fee --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/cache/NoopBonsaiCachedMerkleTrieLoader.java @@ -0,0 +1,44 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.StorageSlotKey; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; + +public class NoopBonsaiCachedMerkleTrieLoader extends BonsaiCachedMerkleTrieLoader { + + public NoopBonsaiCachedMerkleTrieLoader() { + super(new NoOpMetricsSystem()); + } + + @Override + public void preLoadAccount( + final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage, + final Hash worldStateRootHash, + final Address account) { + // noop + } + + @Override + public void preLoadStorageSlot( + final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage, + final Address account, + final StorageSlotKey slotKey) { + // noop + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiPreImageProxy.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/storage/BonsaiPreImageProxy.java similarity index 95% rename from ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiPreImageProxy.java rename to ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/storage/BonsaiPreImageProxy.java index 248e5d895fd..070e797a530 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiPreImageProxy.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/storage/BonsaiPreImageProxy.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,9 +11,8 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ -package org.hyperledger.besu.ethereum.bonsai.storage; +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiSnapshotWorldStateKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/storage/BonsaiSnapshotWorldStateKeyValueStorage.java similarity index 84% rename from ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiSnapshotWorldStateKeyValueStorage.java rename to ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/storage/BonsaiSnapshotWorldStateKeyValueStorage.java index 80bd2ce86eb..2437118d640 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiSnapshotWorldStateKeyValueStorage.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/storage/BonsaiSnapshotWorldStateKeyValueStorage.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,14 +11,13 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ -package org.hyperledger.besu.ethereum.bonsai.storage; +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.StorageSlotKey; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.BonsaiStorageSubscriber; -import org.hyperledger.besu.metrics.ObservableMetricsSystem; +import org.hyperledger.besu.ethereum.trie.diffbased.common.StorageSubscriber; +import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedSnapshotWorldStateKeyValueStorage; import org.hyperledger.besu.plugin.services.exception.StorageException; import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; import org.hyperledger.besu.plugin.services.storage.SnappableKeyValueStorage; @@ -33,7 +32,7 @@ import org.slf4j.LoggerFactory; public class BonsaiSnapshotWorldStateKeyValueStorage extends BonsaiWorldStateKeyValueStorage - implements BonsaiStorageSubscriber { + implements DiffBasedSnapshotWorldStateKeyValueStorage, StorageSubscriber { protected final BonsaiWorldStateKeyValueStorage parentWorldStateStorage; private static final Logger LOG = @@ -43,26 +42,20 @@ public class BonsaiSnapshotWorldStateKeyValueStorage extends BonsaiWorldStateKey public BonsaiSnapshotWorldStateKeyValueStorage( final BonsaiWorldStateKeyValueStorage parentWorldStateStorage, final SnappedKeyValueStorage segmentedWorldStateStorage, - final KeyValueStorage trieLogStorage, - final ObservableMetricsSystem metricsSystem) { + final KeyValueStorage trieLogStorage) { super( - parentWorldStateStorage.flatDbMode, - parentWorldStateStorage.flatDbStrategy, - segmentedWorldStateStorage, - trieLogStorage, - metricsSystem); + parentWorldStateStorage.flatDbStrategyProvider, segmentedWorldStateStorage, trieLogStorage); this.parentWorldStateStorage = parentWorldStateStorage; this.subscribeParentId = parentWorldStateStorage.subscribe(this); } public BonsaiSnapshotWorldStateKeyValueStorage( - final BonsaiWorldStateKeyValueStorage worldStateStorage, - final ObservableMetricsSystem metricsSystem) { + final BonsaiWorldStateKeyValueStorage worldStateStorageKeyValueStorage) { this( - worldStateStorage, - ((SnappableKeyValueStorage) worldStateStorage.composedWorldStateStorage).takeSnapshot(), - worldStateStorage.trieLogStorage, - metricsSystem); + worldStateStorageKeyValueStorage, + ((SnappableKeyValueStorage) worldStateStorageKeyValueStorage.getComposedWorldStateStorage()) + .takeSnapshot(), + worldStateStorageKeyValueStorage.getTrieLogStorage()); } private boolean isClosedGet() { @@ -74,11 +67,11 @@ private boolean isClosedGet() { } @Override - public BonsaiUpdater updater() { + public Updater updater() { return new Updater( ((SnappedKeyValueStorage) composedWorldStateStorage).getSnapshotTransaction(), trieLogStorage.startTransaction(), - flatDbStrategy); + getFlatDbStrategy()); } @Override @@ -87,7 +80,7 @@ public Optional getAccount(final Hash accountHash) { } @Override - public Optional getCode(final Bytes32 codeHash, final Hash accountHash) { + public Optional getCode(final Hash codeHash, final Hash accountHash) { return isClosedGet() ? Optional.empty() : super.getCode(codeHash, accountHash); } @@ -210,11 +203,20 @@ public void onClearTrieLog() { } } + @Override + public void onClearTrie() { + try { + doClose(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + @Override protected synchronized void doClose() throws Exception { if (!isClosedGet()) { // alert any subscribers we are closing: - subscribers.forEach(BonsaiStorageSubscriber::onCloseStorage); + subscribers.forEach(StorageSubscriber::onCloseStorage); // close all of the SnappedKeyValueStorages: composedWorldStateStorage.close(); @@ -227,6 +229,7 @@ protected synchronized void doClose() throws Exception { } } + @Override public BonsaiWorldStateKeyValueStorage getParentWorldStateStorage() { return parentWorldStateStorage; } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/storage/BonsaiWorldStateKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/storage/BonsaiWorldStateKeyValueStorage.java new file mode 100644 index 00000000000..9f40d2bd350 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/storage/BonsaiWorldStateKeyValueStorage.java @@ -0,0 +1,321 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage; + +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_STORAGE; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.CODE_STORAGE; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.StorageSlotKey; +import org.hyperledger.besu.ethereum.storage.StorageProvider; +import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; +import org.hyperledger.besu.ethereum.trie.MerkleTrie; +import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.flat.FlatDbStrategy; +import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.flat.FlatDbStrategyProvider; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.ethereum.worldstate.FlatDbMode; +import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.evm.account.AccountStorageEntry; +import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; +import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; +import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; + +import java.util.List; +import java.util.NavigableMap; +import java.util.Optional; +import java.util.function.Supplier; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +public class BonsaiWorldStateKeyValueStorage extends DiffBasedWorldStateKeyValueStorage + implements WorldStateKeyValueStorage { + protected final FlatDbStrategyProvider flatDbStrategyProvider; + + public BonsaiWorldStateKeyValueStorage( + final StorageProvider provider, + final MetricsSystem metricsSystem, + final DataStorageConfiguration dataStorageConfiguration) { + super( + provider.getStorageBySegmentIdentifiers( + List.of( + ACCOUNT_INFO_STATE, CODE_STORAGE, ACCOUNT_STORAGE_STORAGE, TRIE_BRANCH_STORAGE)), + provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.TRIE_LOG_STORAGE)); + this.flatDbStrategyProvider = + new FlatDbStrategyProvider(metricsSystem, dataStorageConfiguration); + flatDbStrategyProvider.loadFlatDbStrategy(composedWorldStateStorage); + } + + public BonsaiWorldStateKeyValueStorage( + final FlatDbStrategyProvider flatDbStrategyProvider, + final SegmentedKeyValueStorage composedWorldStateStorage, + final KeyValueStorage trieLogStorage) { + super(composedWorldStateStorage, trieLogStorage); + this.flatDbStrategyProvider = flatDbStrategyProvider; + } + + @Override + public DataStorageFormat getDataStorageFormat() { + return DataStorageFormat.BONSAI; + } + + @Override + public FlatDbMode getFlatDbMode() { + return flatDbStrategyProvider.getFlatDbMode(); + } + + public Optional getCode(final Hash codeHash, final Hash accountHash) { + if (codeHash.equals(Hash.EMPTY)) { + return Optional.of(Bytes.EMPTY); + } else { + return flatDbStrategyProvider + .getFlatDbStrategy(composedWorldStateStorage) + .getFlatCode(codeHash, accountHash, composedWorldStateStorage); + } + } + + public Optional getAccount(final Hash accountHash) { + return flatDbStrategyProvider + .getFlatDbStrategy(composedWorldStateStorage) + .getFlatAccount( + this::getWorldStateRootHash, + this::getAccountStateTrieNode, + accountHash, + composedWorldStateStorage); + } + + public Optional getAccountStateTrieNode(final Bytes location, final Bytes32 nodeHash) { + if (nodeHash.equals(MerkleTrie.EMPTY_TRIE_NODE_HASH)) { + return Optional.of(MerkleTrie.EMPTY_TRIE_NODE); + } else { + return composedWorldStateStorage + .get(TRIE_BRANCH_STORAGE, location.toArrayUnsafe()) + .map(Bytes::wrap) + .filter(b -> Hash.hash(b).equals(nodeHash)); + } + } + + public Optional getAccountStorageTrieNode( + final Hash accountHash, final Bytes location, final Bytes32 nodeHash) { + if (nodeHash.equals(MerkleTrie.EMPTY_TRIE_NODE_HASH)) { + return Optional.of(MerkleTrie.EMPTY_TRIE_NODE); + } else { + return composedWorldStateStorage + .get(TRIE_BRANCH_STORAGE, Bytes.concatenate(accountHash, location).toArrayUnsafe()) + .map(Bytes::wrap) + .filter(b -> Hash.hash(b).equals(nodeHash)); + } + } + + public Optional getTrieNodeUnsafe(final Bytes key) { + return composedWorldStateStorage.get(TRIE_BRANCH_STORAGE, key.toArrayUnsafe()).map(Bytes::wrap); + } + + public Optional getStorageValueByStorageSlotKey( + final Hash accountHash, final StorageSlotKey storageSlotKey) { + return getStorageValueByStorageSlotKey( + () -> + getAccount(accountHash) + .map( + b -> + StateTrieAccountValue.readFrom( + org.hyperledger.besu.ethereum.rlp.RLP.input(b)) + .getStorageRoot()), + accountHash, + storageSlotKey); + } + + public Optional getStorageValueByStorageSlotKey( + final Supplier> storageRootSupplier, + final Hash accountHash, + final StorageSlotKey storageSlotKey) { + return flatDbStrategyProvider + .getFlatDbStrategy(composedWorldStateStorage) + .getFlatStorageValueByStorageSlotKey( + this::getWorldStateRootHash, + storageRootSupplier, + (location, hash) -> getAccountStorageTrieNode(accountHash, location, hash), + accountHash, + storageSlotKey, + composedWorldStateStorage); + } + + public NavigableMap storageEntriesFrom( + final Hash addressHash, final Bytes32 startKeyHash, final int limit) { + throw new RuntimeException("Bonsai Tries does not currently support enumerating storage"); + } + + public void upgradeToFullFlatDbMode() { + flatDbStrategyProvider.upgradeToFullFlatDbMode(composedWorldStateStorage); + } + + public void downgradeToPartialFlatDbMode() { + flatDbStrategyProvider.downgradeToPartialFlatDbMode(composedWorldStateStorage); + } + + @Override + public void clear() { + super.clear(); + flatDbStrategyProvider.loadFlatDbStrategy( + composedWorldStateStorage); // force reload of flat db reader strategy + } + + @Override + public FlatDbStrategy getFlatDbStrategy() { + return flatDbStrategyProvider.getFlatDbStrategy(composedWorldStateStorage); + } + + @Override + public Updater updater() { + return new Updater( + composedWorldStateStorage.startTransaction(), + trieLogStorage.startTransaction(), + flatDbStrategyProvider.getFlatDbStrategy(composedWorldStateStorage)); + } + + public static class Updater implements DiffBasedWorldStateKeyValueStorage.Updater { + + private final SegmentedKeyValueStorageTransaction composedWorldStateTransaction; + private final KeyValueStorageTransaction trieLogStorageTransaction; + private final FlatDbStrategy flatDbStrategy; + + public Updater( + final SegmentedKeyValueStorageTransaction composedWorldStateTransaction, + final KeyValueStorageTransaction trieLogStorageTransaction, + final FlatDbStrategy flatDbStrategy) { + + this.composedWorldStateTransaction = composedWorldStateTransaction; + this.trieLogStorageTransaction = trieLogStorageTransaction; + this.flatDbStrategy = flatDbStrategy; + } + + public Updater removeCode(final Hash accountHash, final Hash codeHash) { + flatDbStrategy.removeFlatCode(composedWorldStateTransaction, accountHash, codeHash); + return this; + } + + public Updater putCode(final Hash accountHash, final Bytes code) { + // Skip the hash calculation for empty code + final Hash codeHash = code.size() == 0 ? Hash.EMPTY : Hash.hash(code); + return putCode(accountHash, codeHash, code); + } + + public Updater putCode(final Hash accountHash, final Hash codeHash, final Bytes code) { + if (code.isEmpty()) { + // Don't save empty values + return this; + } + flatDbStrategy.putFlatCode(composedWorldStateTransaction, accountHash, codeHash, code); + return this; + } + + public Updater removeAccountInfoState(final Hash accountHash) { + flatDbStrategy.removeFlatAccount(composedWorldStateTransaction, accountHash); + return this; + } + + public Updater putAccountInfoState(final Hash accountHash, final Bytes accountValue) { + if (accountValue.isEmpty()) { + // Don't save empty values + return this; + } + flatDbStrategy.putFlatAccount(composedWorldStateTransaction, accountHash, accountValue); + return this; + } + + @Override + public Updater saveWorldState(final Bytes blockHash, final Bytes32 nodeHash, final Bytes node) { + composedWorldStateTransaction.put( + TRIE_BRANCH_STORAGE, Bytes.EMPTY.toArrayUnsafe(), node.toArrayUnsafe()); + composedWorldStateTransaction.put( + TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY, nodeHash.toArrayUnsafe()); + composedWorldStateTransaction.put( + TRIE_BRANCH_STORAGE, WORLD_BLOCK_HASH_KEY, blockHash.toArrayUnsafe()); + return this; + } + + public Updater putAccountStateTrieNode( + final Bytes location, final Bytes32 nodeHash, final Bytes node) { + if (nodeHash.equals(MerkleTrie.EMPTY_TRIE_NODE_HASH)) { + // Don't save empty nodes + return this; + } + composedWorldStateTransaction.put( + TRIE_BRANCH_STORAGE, location.toArrayUnsafe(), node.toArrayUnsafe()); + return this; + } + + public Updater removeAccountStateTrieNode(final Bytes location) { + composedWorldStateTransaction.remove(TRIE_BRANCH_STORAGE, location.toArrayUnsafe()); + return this; + } + + public synchronized Updater putAccountStorageTrieNode( + final Hash accountHash, final Bytes location, final Bytes32 nodeHash, final Bytes node) { + if (nodeHash.equals(MerkleTrie.EMPTY_TRIE_NODE_HASH)) { + // Don't save empty nodes + return this; + } + composedWorldStateTransaction.put( + TRIE_BRANCH_STORAGE, + Bytes.concatenate(accountHash, location).toArrayUnsafe(), + node.toArrayUnsafe()); + return this; + } + + public synchronized Updater putStorageValueBySlotHash( + final Hash accountHash, final Hash slotHash, final Bytes storage) { + flatDbStrategy.putFlatAccountStorageValueByStorageSlotHash( + composedWorldStateTransaction, accountHash, slotHash, storage); + return this; + } + + public synchronized void removeStorageValueBySlotHash( + final Hash accountHash, final Hash slotHash) { + flatDbStrategy.removeFlatAccountStorageValueByStorageSlotHash( + composedWorldStateTransaction, accountHash, slotHash); + } + + @Override + public SegmentedKeyValueStorageTransaction getWorldStateTransaction() { + return composedWorldStateTransaction; + } + + @Override + public KeyValueStorageTransaction getTrieLogStorageTransaction() { + return trieLogStorageTransaction; + } + + @Override + public void commit() { + // write the log ahead, then the worldstate + trieLogStorageTransaction.commit(); + composedWorldStateTransaction.commit(); + } + + @Override + public void rollback() { + composedWorldStateTransaction.rollback(); + trieLogStorageTransaction.rollback(); + } + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/storage/BonsaiWorldStateLayerStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/storage/BonsaiWorldStateLayerStorage.java new file mode 100644 index 00000000000..f798dfad739 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/storage/BonsaiWorldStateLayerStorage.java @@ -0,0 +1,53 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage; + +import org.hyperledger.besu.ethereum.trie.diffbased.common.StorageSubscriber; +import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedLayeredWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.FlatDbMode; +import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; +import org.hyperledger.besu.plugin.services.storage.SnappedKeyValueStorage; +import org.hyperledger.besu.services.kvstore.LayeredKeyValueStorage; + +public class BonsaiWorldStateLayerStorage extends BonsaiSnapshotWorldStateKeyValueStorage + implements DiffBasedLayeredWorldStateKeyValueStorage, StorageSubscriber { + + public BonsaiWorldStateLayerStorage(final BonsaiWorldStateKeyValueStorage parent) { + this( + new LayeredKeyValueStorage(parent.getComposedWorldStateStorage()), + parent.getTrieLogStorage(), + parent); + } + + public BonsaiWorldStateLayerStorage( + final SnappedKeyValueStorage composedWorldStateStorage, + final KeyValueStorage trieLogStorage, + final BonsaiWorldStateKeyValueStorage parent) { + super(parent, composedWorldStateStorage, trieLogStorage); + } + + @Override + public FlatDbMode getFlatDbMode() { + return parentWorldStateStorage.getFlatDbMode(); + } + + @Override + public BonsaiWorldStateLayerStorage clone() { + return new BonsaiWorldStateLayerStorage( + ((LayeredKeyValueStorage) composedWorldStateStorage).clone(), + trieLogStorage, + parentWorldStateStorage); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/FullFlatDbStrategy.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/storage/flat/FullFlatDbStrategy.java similarity index 88% rename from ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/FullFlatDbStrategy.java rename to ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/storage/flat/FullFlatDbStrategy.java index de42ba50979..dd4dac5563b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/FullFlatDbStrategy.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/storage/flat/FullFlatDbStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,9 +11,8 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ -package org.hyperledger.besu.ethereum.bonsai.storage.flat; +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.flat; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_STORAGE; @@ -21,6 +20,8 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.StorageSlotKey; import org.hyperledger.besu.ethereum.trie.NodeLoader; +import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.flat.CodeStorageStrategy; +import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.flat.FlatDbStrategy; import org.hyperledger.besu.metrics.BesuMetricCategory; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.plugin.services.metrics.Counter; @@ -37,8 +38,9 @@ public class FullFlatDbStrategy extends FlatDbStrategy { protected final Counter getStorageValueNotFoundInFlatDatabaseCounter; - public FullFlatDbStrategy(final MetricsSystem metricsSystem) { - super(metricsSystem); + public FullFlatDbStrategy( + final MetricsSystem metricsSystem, final CodeStorageStrategy codeStorageStrategy) { + super(metricsSystem, codeStorageStrategy); getAccountNotFoundInFlatDatabaseCounter = metricsSystem.createCounter( diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/PartialFlatDbStrategy.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/storage/flat/PartialFlatDbStrategy.java similarity index 92% rename from ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/PartialFlatDbStrategy.java rename to ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/storage/flat/PartialFlatDbStrategy.java index 1dc4288b21a..250dc7505cb 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/storage/flat/PartialFlatDbStrategy.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/storage/flat/PartialFlatDbStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,9 +11,8 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ -package org.hyperledger.besu.ethereum.bonsai.storage.flat; +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.flat; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_STORAGE; @@ -21,6 +20,8 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.StorageSlotKey; import org.hyperledger.besu.ethereum.trie.NodeLoader; +import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.flat.CodeStorageStrategy; +import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.flat.FlatDbStrategy; import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; import org.hyperledger.besu.ethereum.trie.patricia.StoredNodeFactory; import org.hyperledger.besu.metrics.BesuMetricCategory; @@ -52,8 +53,9 @@ public class PartialFlatDbStrategy extends FlatDbStrategy { protected final Counter getStorageValueMerkleTrieCounter; protected final Counter getStorageValueMissingMerkleTrieCounter; - public PartialFlatDbStrategy(final MetricsSystem metricsSystem) { - super(metricsSystem); + public PartialFlatDbStrategy( + final MetricsSystem metricsSystem, final CodeStorageStrategy codeStorageStrategy) { + super(metricsSystem, codeStorageStrategy); getAccountMerkleTrieCounter = metricsSystem.createCounter( BesuMetricCategory.BLOCKCHAIN, diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogFactoryImpl.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/trielog/TrieLogFactoryImpl.java similarity index 90% rename from ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogFactoryImpl.java rename to ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/trielog/TrieLogFactoryImpl.java index ebac500061c..e99b5109649 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogFactoryImpl.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/trielog/TrieLogFactoryImpl.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,19 +11,19 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ -package org.hyperledger.besu.ethereum.bonsai.trielog; +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai.trielog; import org.hyperledger.besu.datatypes.AccountValue; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.StorageSlotKey; -import org.hyperledger.besu.ethereum.bonsai.BonsaiValue; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; import org.hyperledger.besu.ethereum.rlp.RLPInput; import org.hyperledger.besu.ethereum.rlp.RLPOutput; +import org.hyperledger.besu.ethereum.trie.diffbased.common.DiffBasedValue; +import org.hyperledger.besu.ethereum.trie.diffbased.common.trielog.TrieLogLayer; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; import org.hyperledger.besu.plugin.data.BlockHeader; import org.hyperledger.besu.plugin.services.trielogs.TrieLog; @@ -150,7 +150,7 @@ public static TrieLogLayer readFrom(final RLPInput input) { final TrieLogLayer newLayer = new TrieLogLayer(); input.enterList(); - newLayer.blockHash = Hash.wrap(input.readBytes32()); + newLayer.setBlockHash(Hash.wrap(input.readBytes32())); while (!input.isEndOfCurrentList()) { input.enterList(); @@ -164,7 +164,9 @@ public static TrieLogLayer readFrom(final RLPInput input) { final StateTrieAccountValue newValue = nullOrValue(input, StateTrieAccountValue::readFrom); final boolean isCleared = getOptionalIsCleared(input); input.leaveList(); - newLayer.accounts.put(address, new BonsaiValue<>(oldValue, newValue, isCleared)); + newLayer + .getAccountChanges() + .put(address, new DiffBasedValue<>(oldValue, newValue, isCleared)); } if (input.nextIsNull()) { @@ -175,13 +177,13 @@ public static TrieLogLayer readFrom(final RLPInput input) { final Bytes newCode = nullOrValue(input, RLPInput::readBytes); final boolean isCleared = getOptionalIsCleared(input); input.leaveList(); - newLayer.code.put(address, new BonsaiValue<>(oldCode, newCode, isCleared)); + newLayer.getCodeChanges().put(address, new DiffBasedValue<>(oldCode, newCode, isCleared)); } if (input.nextIsNull()) { input.skipNext(); } else { - final Map> storageChanges = new TreeMap<>(); + final Map> storageChanges = new TreeMap<>(); input.enterList(); while (!input.isEndOfCurrentList()) { input.enterList(); @@ -190,11 +192,11 @@ public static TrieLogLayer readFrom(final RLPInput input) { final UInt256 oldValue = nullOrValue(input, RLPInput::readUInt256Scalar); final UInt256 newValue = nullOrValue(input, RLPInput::readUInt256Scalar); final boolean isCleared = getOptionalIsCleared(input); - storageChanges.put(storageSlotKey, new BonsaiValue<>(oldValue, newValue, isCleared)); + storageChanges.put(storageSlotKey, new DiffBasedValue<>(oldValue, newValue, isCleared)); input.leaveList(); } input.leaveList(); - newLayer.storage.put(address, storageChanges); + newLayer.getStorageChanges().put(address, storageChanges); } // TODO add trie nodes @@ -248,7 +250,7 @@ public static void writeInnerRlp( } else { writer.accept(output, value.getUpdated()); } - if (!value.isCleared()) { + if (!value.isLastStepCleared()) { output.writeNull(); } else { output.writeInt(1); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/worldview/BonsaiWorldState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/worldview/BonsaiWorldState.java new file mode 100644 index 00000000000..b62805c1fda --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/worldview/BonsaiWorldState.java @@ -0,0 +1,475 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview; + +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE; +import static org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldView.encodeTrieValue; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.StorageSlotKey; +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.trie.MerkleTrie; +import org.hyperledger.besu.ethereum.trie.MerkleTrieException; +import org.hyperledger.besu.ethereum.trie.NoOpMerkleTrie; +import org.hyperledger.besu.ethereum.trie.NodeLoader; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.BonsaiAccount; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.BonsaiWorldStateProvider; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.BonsaiCachedMerkleTrieLoader; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateLayerStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.common.DiffBasedValue; +import org.hyperledger.besu.ethereum.trie.diffbased.common.cache.DiffBasedCachedWorldStorageManager; +import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.common.trielog.TrieLogManager; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldState; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldStateConfig; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.DiffBasedWorldStateUpdateAccumulator; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.preload.StorageConsumingMap; +import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; +import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.plugin.services.storage.SegmentIdentifier; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; + +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +import com.google.common.annotations.VisibleForTesting; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.rlp.RLP; +import org.apache.tuweni.units.bigints.UInt256; + +public class BonsaiWorldState extends DiffBasedWorldState { + + protected final BonsaiCachedMerkleTrieLoader bonsaiCachedMerkleTrieLoader; + + public BonsaiWorldState( + final BonsaiWorldStateProvider archive, + final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage, + final EvmConfiguration evmConfiguration, + final DiffBasedWorldStateConfig diffBasedWorldStateConfig) { + this( + worldStateKeyValueStorage, + archive.getCachedMerkleTrieLoader(), + archive.getCachedWorldStorageManager(), + archive.getTrieLogManager(), + evmConfiguration, + diffBasedWorldStateConfig); + } + + public BonsaiWorldState( + final BonsaiWorldState worldState, + final BonsaiCachedMerkleTrieLoader cachedMerkleTrieLoader) { + this( + new BonsaiWorldStateLayerStorage(worldState.getWorldStateStorage()), + cachedMerkleTrieLoader, + worldState.cachedWorldStorageManager, + worldState.trieLogManager, + worldState.accumulator.getEvmConfiguration(), + new DiffBasedWorldStateConfig(worldState.worldStateConfig)); + } + + public BonsaiWorldState( + final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage, + final BonsaiCachedMerkleTrieLoader bonsaiCachedMerkleTrieLoader, + final DiffBasedCachedWorldStorageManager cachedWorldStorageManager, + final TrieLogManager trieLogManager, + final EvmConfiguration evmConfiguration, + final DiffBasedWorldStateConfig diffBasedWorldStateConfig) { + super( + worldStateKeyValueStorage, + cachedWorldStorageManager, + trieLogManager, + diffBasedWorldStateConfig); + this.bonsaiCachedMerkleTrieLoader = bonsaiCachedMerkleTrieLoader; + this.worldStateKeyValueStorage = worldStateKeyValueStorage; + this.setAccumulator( + new BonsaiWorldStateUpdateAccumulator( + this, + (addr, value) -> + bonsaiCachedMerkleTrieLoader.preLoadAccount( + getWorldStateStorage(), worldStateRootHash, addr), + (addr, value) -> + this.bonsaiCachedMerkleTrieLoader.preLoadStorageSlot( + getWorldStateStorage(), addr, value), + evmConfiguration)); + } + + @Override + public Optional getCode(@Nonnull final Address address, final Hash codeHash) { + return getWorldStateStorage().getCode(codeHash, address.addressHash()); + } + + @Override + public BonsaiWorldStateKeyValueStorage getWorldStateStorage() { + return (BonsaiWorldStateKeyValueStorage) worldStateKeyValueStorage; + } + + @Override + protected Hash calculateRootHash( + final Optional maybeStateUpdater, + final DiffBasedWorldStateUpdateAccumulator worldStateUpdater) { + return internalCalculateRootHash( + maybeStateUpdater.map(BonsaiWorldStateKeyValueStorage.Updater.class::cast), + (BonsaiWorldStateUpdateAccumulator) worldStateUpdater); + } + + private Hash internalCalculateRootHash( + final Optional maybeStateUpdater, + final BonsaiWorldStateUpdateAccumulator worldStateUpdater) { + + clearStorage(maybeStateUpdater, worldStateUpdater); + + // This must be done before updating the accounts so + // that we can get the storage state hash + Stream>>> + storageStream = worldStateUpdater.getStorageToUpdate().entrySet().stream(); + if (maybeStateUpdater.isEmpty()) { + storageStream = + storageStream + .parallel(); // if we are not updating the state updater we can use parallel stream + } + storageStream.forEach( + addressMapEntry -> + updateAccountStorageState(maybeStateUpdater, worldStateUpdater, addressMapEntry)); + + // Third update the code. This has the side effect of ensuring a code hash is calculated. + updateCode(maybeStateUpdater, worldStateUpdater); + + // next walk the account trie + final MerkleTrie accountTrie = + createTrie( + (location, hash) -> + bonsaiCachedMerkleTrieLoader.getAccountStateTrieNode( + getWorldStateStorage(), location, hash), + worldStateRootHash); + + // for manicured tries and composting, collect branches here (not implemented) + updateTheAccounts(maybeStateUpdater, worldStateUpdater, accountTrie); + + // TODO write to a cache and then generate a layer update from that and the + // DB tx updates. Right now it is just DB updates. + maybeStateUpdater.ifPresent( + bonsaiUpdater -> + accountTrie.commit( + (location, hash, value) -> + writeTrieNode( + TRIE_BRANCH_STORAGE, + bonsaiUpdater.getWorldStateTransaction(), + location, + value))); + final Bytes32 rootHash = accountTrie.getRootHash(); + return Hash.wrap(rootHash); + } + + private void updateTheAccounts( + final Optional maybeStateUpdater, + final BonsaiWorldStateUpdateAccumulator worldStateUpdater, + final MerkleTrie accountTrie) { + for (final Map.Entry> accountUpdate : + worldStateUpdater.getAccountsToUpdate().entrySet()) { + final Bytes accountKey = accountUpdate.getKey(); + final DiffBasedValue bonsaiValue = accountUpdate.getValue(); + final BonsaiAccount updatedAccount = bonsaiValue.getUpdated(); + try { + if (updatedAccount == null) { + final Hash addressHash = hashAndSavePreImage(accountKey); + accountTrie.remove(addressHash); + maybeStateUpdater.ifPresent( + bonsaiUpdater -> bonsaiUpdater.removeAccountInfoState(addressHash)); + } else { + final Hash addressHash = updatedAccount.getAddressHash(); + final Bytes accountValue = updatedAccount.serializeAccount(); + maybeStateUpdater.ifPresent( + bonsaiUpdater -> + bonsaiUpdater.putAccountInfoState(hashAndSavePreImage(accountKey), accountValue)); + accountTrie.put(addressHash, accountValue); + } + } catch (MerkleTrieException e) { + // need to throw to trigger the heal + throw new MerkleTrieException( + e.getMessage(), Optional.of(Address.wrap(accountKey)), e.getHash(), e.getLocation()); + } + } + } + + @VisibleForTesting + protected void updateCode( + final Optional maybeStateUpdater, + final BonsaiWorldStateUpdateAccumulator worldStateUpdater) { + maybeStateUpdater.ifPresent( + bonsaiUpdater -> { + for (final Map.Entry> codeUpdate : + worldStateUpdater.getCodeToUpdate().entrySet()) { + final Bytes updatedCode = codeUpdate.getValue().getUpdated(); + final Hash accountHash = codeUpdate.getKey().addressHash(); + final Bytes priorCode = codeUpdate.getValue().getPrior(); + + // code hasn't changed then do nothing + if (Objects.equals(priorCode, updatedCode) + || (codeIsEmpty(priorCode) && codeIsEmpty(updatedCode))) { + continue; + } + + if (codeIsEmpty(updatedCode)) { + final Hash priorCodeHash = Hash.hash(priorCode); + bonsaiUpdater.removeCode(accountHash, priorCodeHash); + } else { + final Hash codeHash = Hash.hash(codeUpdate.getValue().getUpdated()); + bonsaiUpdater.putCode(accountHash, codeHash, updatedCode); + } + } + }); + } + + private boolean codeIsEmpty(final Bytes value) { + return value == null || value.isEmpty(); + } + + private void updateAccountStorageState( + final Optional maybeStateUpdater, + final BonsaiWorldStateUpdateAccumulator worldStateUpdater, + final Map.Entry>> + storageAccountUpdate) { + final Address updatedAddress = storageAccountUpdate.getKey(); + final Hash updatedAddressHash = updatedAddress.addressHash(); + if (worldStateUpdater.getAccountsToUpdate().containsKey(updatedAddress)) { + final DiffBasedValue accountValue = + worldStateUpdater.getAccountsToUpdate().get(updatedAddress); + final BonsaiAccount accountOriginal = accountValue.getPrior(); + final Hash storageRoot = + (accountOriginal == null + || worldStateUpdater.getStorageToClear().contains(updatedAddress)) + ? Hash.EMPTY_TRIE_HASH + : accountOriginal.getStorageRoot(); + final MerkleTrie storageTrie = + createTrie( + (location, key) -> + bonsaiCachedMerkleTrieLoader.getAccountStorageTrieNode( + getWorldStateStorage(), updatedAddressHash, location, key), + storageRoot); + + // for manicured tries and composting, collect branches here (not implemented) + for (final Map.Entry> storageUpdate : + storageAccountUpdate.getValue().entrySet()) { + final Hash slotHash = storageUpdate.getKey().getSlotHash(); + final UInt256 updatedStorage = storageUpdate.getValue().getUpdated(); + try { + if (updatedStorage == null || updatedStorage.equals(UInt256.ZERO)) { + maybeStateUpdater.ifPresent( + bonsaiUpdater -> + bonsaiUpdater.removeStorageValueBySlotHash(updatedAddressHash, slotHash)); + storageTrie.remove(slotHash); + } else { + maybeStateUpdater.ifPresent( + bonsaiUpdater -> + bonsaiUpdater.putStorageValueBySlotHash( + updatedAddressHash, slotHash, updatedStorage)); + storageTrie.put(slotHash, encodeTrieValue(updatedStorage)); + } + } catch (MerkleTrieException e) { + // need to throw to trigger the heal + throw new MerkleTrieException( + e.getMessage(), + Optional.of(Address.wrap(updatedAddress)), + e.getHash(), + e.getLocation()); + } + } + + final BonsaiAccount accountUpdated = accountValue.getUpdated(); + if (accountUpdated != null) { + maybeStateUpdater.ifPresent( + bonsaiUpdater -> + storageTrie.commit( + (location, key, value) -> + writeStorageTrieNode( + bonsaiUpdater, updatedAddressHash, location, key, value))); + // only use storage root of the trie when trie is enabled + if (!worldStateConfig.isTrieDisabled()) { + final Hash newStorageRoot = Hash.wrap(storageTrie.getRootHash()); + accountUpdated.setStorageRoot(newStorageRoot); + } + } + } + // for manicured tries and composting, trim and compost here + } + + private void clearStorage( + final Optional maybeStateUpdater, + final BonsaiWorldStateUpdateAccumulator worldStateUpdater) { + for (final Address address : worldStateUpdater.getStorageToClear()) { + // because we are clearing persisted values we need the account root as persisted + final BonsaiAccount oldAccount = + getWorldStateStorage() + .getAccount(address.addressHash()) + .map(bytes -> BonsaiAccount.fromRLP(BonsaiWorldState.this, address, bytes, true)) + .orElse(null); + if (oldAccount == null) { + // This is when an account is both created and deleted within the scope of the same + // block. A not-uncommon DeFi bot pattern. + continue; + } + final Hash addressHash = address.addressHash(); + final MerkleTrie storageTrie = + createTrie( + (location, key) -> getStorageTrieNode(addressHash, location, key), + oldAccount.getStorageRoot()); + try { + final StorageConsumingMap> storageToDelete = + worldStateUpdater.getStorageToUpdate().get(address); + Map entriesToDelete = storageTrie.entriesFrom(Bytes32.ZERO, 256); + while (!entriesToDelete.isEmpty()) { + entriesToDelete.forEach( + (k, v) -> { + final StorageSlotKey storageSlotKey = + new StorageSlotKey(Hash.wrap(k), Optional.empty()); + final UInt256 slotValue = UInt256.fromBytes(Bytes32.leftPad(RLP.decodeValue(v))); + maybeStateUpdater.ifPresent( + bonsaiUpdater -> + bonsaiUpdater.removeStorageValueBySlotHash( + address.addressHash(), storageSlotKey.getSlotHash())); + storageToDelete + .computeIfAbsent( + storageSlotKey, key -> new DiffBasedValue<>(slotValue, null, true)) + .setPrior(slotValue); + }); + entriesToDelete.keySet().forEach(storageTrie::remove); + if (entriesToDelete.size() == 256) { + entriesToDelete = storageTrie.entriesFrom(Bytes32.ZERO, 256); + } else { + break; + } + } + } catch (MerkleTrieException e) { + // need to throw to trigger the heal + throw new MerkleTrieException( + e.getMessage(), Optional.of(Address.wrap(address)), e.getHash(), e.getLocation()); + } + } + } + + @Override + public Hash frontierRootHash() { + return calculateRootHash( + Optional.of( + new BonsaiWorldStateKeyValueStorage.Updater( + noOpSegmentedTx, noOpTx, worldStateKeyValueStorage.getFlatDbStrategy())), + accumulator.copy()); + } + + @Override + public Account get(final Address address) { + return getWorldStateStorage() + .getAccount(address.addressHash()) + .map(bytes -> BonsaiAccount.fromRLP(accumulator, address, bytes, true)) + .orElse(null); + } + + protected Optional getAccountStateTrieNode(final Bytes location, final Bytes32 nodeHash) { + return getWorldStateStorage().getAccountStateTrieNode(location, nodeHash); + } + + private void writeTrieNode( + final SegmentIdentifier segmentId, + final SegmentedKeyValueStorageTransaction tx, + final Bytes location, + final Bytes value) { + tx.put(segmentId, location.toArrayUnsafe(), value.toArrayUnsafe()); + } + + protected Optional getStorageTrieNode( + final Hash accountHash, final Bytes location, final Bytes32 nodeHash) { + return getWorldStateStorage().getAccountStorageTrieNode(accountHash, location, nodeHash); + } + + private void writeStorageTrieNode( + final BonsaiWorldStateKeyValueStorage.Updater stateUpdater, + final Hash accountHash, + final Bytes location, + final Bytes32 nodeHash, + final Bytes value) { + stateUpdater.putAccountStorageTrieNode(accountHash, location, nodeHash, value); + } + + @Override + public UInt256 getStorageValue(final Address address, final UInt256 storageKey) { + return getStorageValueByStorageSlotKey(address, new StorageSlotKey(storageKey)) + .orElse(UInt256.ZERO); + } + + @Override + public Optional getStorageValueByStorageSlotKey( + final Address address, final StorageSlotKey storageSlotKey) { + return getWorldStateStorage() + .getStorageValueByStorageSlotKey(address.addressHash(), storageSlotKey) + .map(UInt256::fromBytes); + } + + public Optional getStorageValueByStorageSlotKey( + final Supplier> storageRootSupplier, + final Address address, + final StorageSlotKey storageSlotKey) { + return getWorldStateStorage() + .getStorageValueByStorageSlotKey(storageRootSupplier, address.addressHash(), storageSlotKey) + .map(UInt256::fromBytes); + } + + @Override + public UInt256 getPriorStorageValue(final Address address, final UInt256 storageKey) { + return getStorageValue(address, storageKey); + } + + @Override + public Map getAllAccountStorage(final Address address, final Hash rootHash) { + final MerkleTrie storageTrie = + createTrie( + (location, key) -> getStorageTrieNode(address.addressHash(), location, key), rootHash); + return storageTrie.entriesFrom(Bytes32.ZERO, Integer.MAX_VALUE); + } + + @Override + public MutableWorldState freeze() { + this.worldStateConfig.setFrozen(true); + this.worldStateKeyValueStorage = new BonsaiWorldStateLayerStorage(getWorldStateStorage()); + return this; + } + + private MerkleTrie createTrie(final NodeLoader nodeLoader, final Bytes32 rootHash) { + if (worldStateConfig.isTrieDisabled()) { + return new NoOpMerkleTrie<>(); + } else { + return new StoredMerklePatriciaTrie<>( + nodeLoader, rootHash, Function.identity(), Function.identity()); + } + } + + protected Hash hashAndSavePreImage(final Bytes value) { + // by default do not save has preImages + return Hash.hash(value); + } + + @Override + protected Hash getEmptyTrieHash() { + return Hash.EMPTY_TRIE_HASH; + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/worldview/BonsaiWorldStateUpdateAccumulator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/worldview/BonsaiWorldStateUpdateAccumulator.java new file mode 100644 index 00000000000..49e8fe03dc1 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/worldview/BonsaiWorldStateUpdateAccumulator.java @@ -0,0 +1,97 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview; + +import org.hyperledger.besu.datatypes.AccountValue; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.StorageSlotKey; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.BonsaiAccount; +import org.hyperledger.besu.ethereum.trie.diffbased.common.DiffBasedValue; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldView; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.DiffBasedWorldStateUpdateAccumulator; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.preload.Consumer; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.worldstate.UpdateTrackingAccount; + +public class BonsaiWorldStateUpdateAccumulator + extends DiffBasedWorldStateUpdateAccumulator { + public BonsaiWorldStateUpdateAccumulator( + final DiffBasedWorldView world, + final Consumer> accountPreloader, + final Consumer storagePreloader, + final EvmConfiguration evmConfiguration) { + super(world, accountPreloader, storagePreloader, evmConfiguration); + } + + @Override + public DiffBasedWorldStateUpdateAccumulator copy() { + final BonsaiWorldStateUpdateAccumulator copy = + new BonsaiWorldStateUpdateAccumulator( + wrappedWorldView(), + getAccountPreloader(), + getStoragePreloader(), + getEvmConfiguration()); + copy.cloneFromUpdater(this); + return copy; + } + + @Override + protected BonsaiAccount copyAccount(final BonsaiAccount account) { + return new BonsaiAccount(account); + } + + @Override + protected BonsaiAccount copyAccount( + final BonsaiAccount toCopy, final DiffBasedWorldView context, final boolean mutable) { + return new BonsaiAccount(toCopy, context, mutable); + } + + @Override + protected BonsaiAccount createAccount( + final DiffBasedWorldView context, + final Address address, + final AccountValue stateTrieAccount, + final boolean mutable) { + return new BonsaiAccount(context, address, stateTrieAccount, mutable); + } + + @Override + protected BonsaiAccount createAccount( + final DiffBasedWorldView context, + final Address address, + final Hash addressHash, + final long nonce, + final Wei balance, + final Hash storageRoot, + final Hash codeHash, + final boolean mutable) { + return new BonsaiAccount( + context, address, addressHash, nonce, balance, storageRoot, codeHash, mutable); + } + + @Override + protected BonsaiAccount createAccount( + final DiffBasedWorldView context, final UpdateTrackingAccount tracked) { + return new BonsaiAccount(context, tracked); + } + + @Override + protected void assertCloseEnoughForDiffing( + final BonsaiAccount source, final AccountValue account, final String context) { + BonsaiAccount.assertCloseEnoughForDiffing(source, account, context); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/DiffBasedAccount.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/DiffBasedAccount.java new file mode 100644 index 00000000000..2b81ebfbab1 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/DiffBasedAccount.java @@ -0,0 +1,229 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.common; + +import org.hyperledger.besu.datatypes.AccountValue; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldView; +import org.hyperledger.besu.evm.ModificationNotAllowedException; +import org.hyperledger.besu.evm.account.MutableAccount; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.units.bigints.UInt256; + +public abstract class DiffBasedAccount implements MutableAccount, AccountValue { + protected final DiffBasedWorldView context; + protected boolean immutable; + protected final Address address; + protected final Hash addressHash; + protected Hash codeHash; + protected long nonce; + protected Wei balance; + protected Bytes code; + + protected final Map updatedStorage = new HashMap<>(); + + /** + * Constructs a new DiffBasedAccount instance without the account's code. This constructor is used + * when the account's code is not required or will not be read from the database. It initializes + * the account with its context, address, address hash, nonce, balance, code hash, and mutability + * status. + * + * @param context The DiffBasedWorldView context in which this account exists. + * @param address The Ethereum address of this account. + * @param addressHash The hash of the account's address. + * @param nonce The nonce of the account, representing the number of transactions sent from this + * account. + * @param balance The balance of the account in Wei. + * @param codeHash The hash of the account's code. + * @param mutable A boolean indicating if the account is mutable. If false, the account is + * considered immutable. + */ + public DiffBasedAccount( + final DiffBasedWorldView context, + final Address address, + final Hash addressHash, + final long nonce, + final Wei balance, + final Hash codeHash, + final boolean mutable) { + this.context = context; + this.address = address; + this.addressHash = addressHash; + this.nonce = nonce; + this.balance = balance; + this.codeHash = codeHash; + + this.immutable = !mutable; + } + + /** + * Constructs a new DiffBasedAccount instance with the account's code. This constructor is used + * when all account information, including its code, are available. It initializes the account + * with its context, address, address hash, nonce, balance, code hash, the actual code, and + * mutability status. + * + * @param context The DiffBasedWorldView context in which this account exists. + * @param address The Ethereum address of this account. + * @param addressHash The hash of the account's address. + * @param nonce The nonce of the account, representing the number of transactions sent from this + * account. + * @param balance The balance of the account in Wei. + * @param codeHash The hash of the account's code. + * @param code The actual bytecode of the account's smart contract. This is provided when the code + * is known and needs to be associated with the account. + * @param mutable A boolean indicating if the account is mutable. If false, the account is + * considered immutable. + */ + public DiffBasedAccount( + final DiffBasedWorldView context, + final Address address, + final Hash addressHash, + final long nonce, + final Wei balance, + final Hash codeHash, + final Bytes code, + final boolean mutable) { + this.context = context; + this.address = address; + this.addressHash = addressHash; + this.nonce = nonce; + this.balance = balance; + this.codeHash = codeHash; + this.code = code; + this.immutable = !mutable; + } + + @Override + public Address getAddress() { + return address; + } + + @Override + public Hash getAddressHash() { + return addressHash; + } + + @Override + public long getNonce() { + return nonce; + } + + @Override + public void setNonce(final long value) { + if (immutable) { + throw new ModificationNotAllowedException(); + } + nonce = value; + } + + @Override + public Wei getBalance() { + return balance; + } + + @Override + public void setBalance(final Wei value) { + if (immutable) { + throw new ModificationNotAllowedException(); + } + balance = value; + } + + @Override + public Bytes getCode() { + if (code == null) { + code = context.getCode(address, codeHash).orElse(Bytes.EMPTY); + } + return code; + } + + @Override + public void setCode(final Bytes code) { + if (immutable) { + throw new ModificationNotAllowedException(); + } + this.code = code; + if (code == null || code.isEmpty()) { + this.codeHash = Hash.EMPTY; + } else { + this.codeHash = Hash.hash(code); + } + } + + @Override + public Hash getCodeHash() { + return codeHash; + } + + @Override + public UInt256 getStorageValue(final UInt256 key) { + return context.getStorageValue(address, key); + } + + @Override + public UInt256 getOriginalStorageValue(final UInt256 key) { + return context.getPriorStorageValue(address, key); + } + + public Bytes serializeAccount() { + final BytesValueRLPOutput out = new BytesValueRLPOutput(); + writeTo(out); + return out.encoded(); + } + + @Override + public void setStorageValue(final UInt256 key, final UInt256 value) { + if (immutable) { + throw new ModificationNotAllowedException(); + } + updatedStorage.put(key, value); + } + + @Override + public void clearStorage() { + updatedStorage.clear(); + } + + @Override + public Map getUpdatedStorage() { + return updatedStorage; + } + + @Override + public void becomeImmutable() { + immutable = true; + } + + @Override + public String toString() { + return "AccountState{" + + "address=" + + address + + ", nonce=" + + nonce + + ", balance=" + + balance + + ", codeHash=" + + codeHash + + '}'; + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/DiffBasedValue.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/DiffBasedValue.java new file mode 100644 index 00000000000..b2b6134550a --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/DiffBasedValue.java @@ -0,0 +1,133 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.common; + +import org.hyperledger.besu.plugin.services.trielogs.TrieLog; + +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; + +public class DiffBasedValue implements TrieLog.LogTuple { + private T prior; + private T updated; + private boolean lastStepCleared; + + private boolean clearedAtLeastOnce; + + public DiffBasedValue(final T prior, final T updated) { + this.prior = prior; + this.updated = updated; + this.lastStepCleared = false; + this.clearedAtLeastOnce = false; + } + + public DiffBasedValue(final T prior, final T updated, final boolean lastStepCleared) { + this.prior = prior; + this.updated = updated; + this.lastStepCleared = lastStepCleared; + this.clearedAtLeastOnce = lastStepCleared; + } + + public DiffBasedValue( + final T prior, + final T updated, + final boolean lastStepCleared, + final boolean clearedAtLeastOnce) { + this.prior = prior; + this.updated = updated; + this.lastStepCleared = lastStepCleared; + this.clearedAtLeastOnce = clearedAtLeastOnce; + } + + @Override + public T getPrior() { + return prior; + } + + @Override + public T getUpdated() { + return updated; + } + + public DiffBasedValue setPrior(final T prior) { + this.prior = prior; + return this; + } + + public DiffBasedValue setUpdated(final T updated) { + this.lastStepCleared = updated == null; + if (lastStepCleared) { + this.clearedAtLeastOnce = true; + } + this.updated = updated; + return this; + } + + public void setCleared() { + this.lastStepCleared = true; + this.clearedAtLeastOnce = true; + } + + @Override + public boolean isLastStepCleared() { + return lastStepCleared; + } + + @Override + public boolean isClearedAtLeastOnce() { + return clearedAtLeastOnce; + } + + @Override + public String toString() { + return "DiffBasedValue{" + + "prior=" + + prior + + ", updated=" + + updated + + ", cleared=" + + lastStepCleared + + '}'; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DiffBasedValue that = (DiffBasedValue) o; + return new EqualsBuilder() + .append(lastStepCleared, that.lastStepCleared) + .append(prior, that.prior) + .append(updated, that.updated) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(prior) + .append(updated) + .append(lastStepCleared) + .toHashCode(); + } + + public DiffBasedValue copy() { + return new DiffBasedValue(prior, updated, lastStepCleared, clearedAtLeastOnce); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/DiffBasedWorldStateProvider.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/DiffBasedWorldStateProvider.java new file mode 100644 index 00000000000..75b370c1cd0 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/DiffBasedWorldStateProvider.java @@ -0,0 +1,323 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.common; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.proof.WorldStateProof; +import org.hyperledger.besu.ethereum.proof.WorldStateProofProvider; +import org.hyperledger.besu.ethereum.trie.MerkleTrieException; +import org.hyperledger.besu.ethereum.trie.diffbased.common.cache.DiffBasedCachedWorldStorageManager; +import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.common.trielog.TrieLogManager; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldState; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldStateConfig; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.DiffBasedWorldStateUpdateAccumulator; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.evm.worldstate.WorldState; +import org.hyperledger.besu.plugin.BesuContext; +import org.hyperledger.besu.plugin.services.trielogs.TrieLog; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.units.bigints.UInt256; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class DiffBasedWorldStateProvider implements WorldStateArchive { + + private static final Logger LOG = LoggerFactory.getLogger(DiffBasedWorldStateProvider.class); + + protected final Blockchain blockchain; + + protected final TrieLogManager trieLogManager; + protected DiffBasedCachedWorldStorageManager cachedWorldStorageManager; + protected DiffBasedWorldState persistedState; + + protected final DiffBasedWorldStateKeyValueStorage worldStateKeyValueStorage; + protected final DiffBasedWorldStateConfig defaultWorldStateConfig; + + public DiffBasedWorldStateProvider( + final DiffBasedWorldStateKeyValueStorage worldStateKeyValueStorage, + final Blockchain blockchain, + final Optional maxLayersToLoad, + final BesuContext pluginContext) { + + this.worldStateKeyValueStorage = worldStateKeyValueStorage; + // TODO: de-dup constructors + this.trieLogManager = + new TrieLogManager( + blockchain, + worldStateKeyValueStorage, + maxLayersToLoad.orElse(DiffBasedCachedWorldStorageManager.RETAINED_LAYERS), + pluginContext); + this.blockchain = blockchain; + this.defaultWorldStateConfig = new DiffBasedWorldStateConfig(); + } + + public DiffBasedWorldStateProvider( + final DiffBasedWorldStateKeyValueStorage worldStateKeyValueStorage, + final Blockchain blockchain, + final TrieLogManager trieLogManager) { + + this.worldStateKeyValueStorage = worldStateKeyValueStorage; + // TODO: de-dup constructors + this.trieLogManager = trieLogManager; + this.blockchain = blockchain; + this.defaultWorldStateConfig = new DiffBasedWorldStateConfig(); + } + + protected void provideCachedWorldStorageManager( + final DiffBasedCachedWorldStorageManager cachedWorldStorageManager) { + this.cachedWorldStorageManager = cachedWorldStorageManager; + } + + protected void loadPersistedState(final DiffBasedWorldState persistedState) { + this.persistedState = persistedState; + blockchain + .getBlockHeader(persistedState.getWorldStateBlockHash()) + .ifPresent( + blockHeader -> + this.cachedWorldStorageManager.addCachedLayer( + blockHeader, persistedState.getWorldStateRootHash(), persistedState)); + } + + @Override + public Optional get(final Hash rootHash, final Hash blockHash) { + return cachedWorldStorageManager + .getWorldState(blockHash) + .or( + () -> { + if (blockHash.equals(persistedState.blockHash())) { + return Optional.of(persistedState); + } else { + return Optional.empty(); + } + }) + .map(WorldState.class::cast); + } + + @Override + public boolean isWorldStateAvailable(final Hash rootHash, final Hash blockHash) { + return cachedWorldStorageManager.contains(blockHash) + || persistedState.blockHash().equals(blockHash) + || worldStateKeyValueStorage.isWorldStateAvailable(rootHash, blockHash); + } + + @Override + public Optional getMutable( + final BlockHeader blockHeader, final boolean shouldPersistState) { + if (shouldPersistState) { + return getMutable(blockHeader.getStateRoot(), blockHeader.getHash()); + } else { + final BlockHeader chainHeadBlockHeader = blockchain.getChainHeadHeader(); + if (chainHeadBlockHeader.getNumber() - blockHeader.getNumber() + >= trieLogManager.getMaxLayersToLoad()) { + LOG.warn( + "Exceeded the limit of historical blocks that can be loaded ({}). If you need to make older historical queries, configure your `--bonsai-historical-block-limit`.", + trieLogManager.getMaxLayersToLoad()); + return Optional.empty(); + } + return cachedWorldStorageManager + .getWorldState(blockHeader.getHash()) + .or(() -> cachedWorldStorageManager.getNearestWorldState(blockHeader)) + .or(() -> cachedWorldStorageManager.getHeadWorldState(blockchain::getBlockHeader)) + .flatMap(worldState -> rollMutableStateToBlockHash(worldState, blockHeader.getHash())) + .map(MutableWorldState::freeze); + } + } + + @Override + public synchronized Optional getMutable( + final Hash rootHash, final Hash blockHash) { + return rollMutableStateToBlockHash(persistedState, blockHash); + } + + Optional rollMutableStateToBlockHash( + final DiffBasedWorldState mutableState, final Hash blockHash) { + if (blockHash.equals(mutableState.blockHash())) { + return Optional.of(mutableState); + } else { + try { + + final Optional maybePersistedHeader = + blockchain.getBlockHeader(mutableState.blockHash()).map(BlockHeader.class::cast); + + final List rollBacks = new ArrayList<>(); + final List rollForwards = new ArrayList<>(); + if (maybePersistedHeader.isEmpty()) { + trieLogManager.getTrieLogLayer(mutableState.blockHash()).ifPresent(rollBacks::add); + } else { + BlockHeader targetHeader = blockchain.getBlockHeader(blockHash).get(); + BlockHeader persistedHeader = maybePersistedHeader.get(); + // roll back from persisted to even with target + Hash persistedBlockHash = persistedHeader.getBlockHash(); + while (persistedHeader.getNumber() > targetHeader.getNumber()) { + LOG.debug("Rollback {}", persistedBlockHash); + rollBacks.add(trieLogManager.getTrieLogLayer(persistedBlockHash).get()); + persistedHeader = blockchain.getBlockHeader(persistedHeader.getParentHash()).get(); + persistedBlockHash = persistedHeader.getBlockHash(); + } + // roll forward to target + Hash targetBlockHash = targetHeader.getBlockHash(); + while (persistedHeader.getNumber() < targetHeader.getNumber()) { + LOG.debug("Rollforward {}", targetBlockHash); + rollForwards.add(trieLogManager.getTrieLogLayer(targetBlockHash).get()); + targetHeader = blockchain.getBlockHeader(targetHeader.getParentHash()).get(); + targetBlockHash = targetHeader.getBlockHash(); + } + + // roll back in tandem until we hit a shared state + while (!persistedBlockHash.equals(targetBlockHash)) { + LOG.debug("Paired Rollback {}", persistedBlockHash); + LOG.debug("Paired Rollforward {}", targetBlockHash); + rollForwards.add(trieLogManager.getTrieLogLayer(targetBlockHash).get()); + targetHeader = blockchain.getBlockHeader(targetHeader.getParentHash()).get(); + + rollBacks.add(trieLogManager.getTrieLogLayer(persistedBlockHash).get()); + persistedHeader = blockchain.getBlockHeader(persistedHeader.getParentHash()).get(); + + targetBlockHash = targetHeader.getBlockHash(); + persistedBlockHash = persistedHeader.getBlockHash(); + } + } + + // attempt the state rolling + final DiffBasedWorldStateUpdateAccumulator diffBasedUpdater = + (DiffBasedWorldStateUpdateAccumulator) mutableState.updater(); + try { + for (final TrieLog rollBack : rollBacks) { + LOG.debug("Attempting Rollback of {}", rollBack.getBlockHash()); + diffBasedUpdater.rollBack(rollBack); + } + for (int i = rollForwards.size() - 1; i >= 0; i--) { + final var forward = rollForwards.get(i); + LOG.debug("Attempting Rollforward of {}", rollForwards.get(i).getBlockHash()); + diffBasedUpdater.rollForward(forward); + } + diffBasedUpdater.commit(); + + mutableState.persist(blockchain.getBlockHeader(blockHash).get()); + + LOG.debug( + "Archive rolling finished, {} now at {}", + mutableState.getWorldStateStorage().getClass().getSimpleName(), + blockHash); + return Optional.of(mutableState); + } catch (final MerkleTrieException re) { + // need to throw to trigger the heal + throw re; + } catch (final Exception e) { + // if we fail we must clean up the updater + diffBasedUpdater.reset(); + LOG.atDebug() + .setMessage("State rolling failed on {} for block hash {}") + .addArgument(mutableState.getWorldStateStorage().getClass().getSimpleName()) + .addArgument(blockHash) + .addArgument(e) + .log(); + + return Optional.empty(); + } + } catch (final RuntimeException re) { + LOG.info("Archive rolling failed for block hash " + blockHash, re); + if (re instanceof MerkleTrieException) { + // need to throw to trigger the heal + throw re; + } + throw new MerkleTrieException( + "invalid", Optional.of(Address.ZERO), Hash.EMPTY, Bytes.EMPTY); + } + } + } + + @Override + public MutableWorldState getMutable() { + return persistedState; + } + + public DiffBasedWorldStateConfig getDefaultWorldStateConfig() { + return defaultWorldStateConfig; + } + + public void disableTrie() { + defaultWorldStateConfig.setTrieDisabled(true); + worldStateKeyValueStorage.clearTrie(); + } + + public DiffBasedWorldStateKeyValueStorage getWorldStateKeyValueStorage() { + return worldStateKeyValueStorage; + } + + public TrieLogManager getTrieLogManager() { + return trieLogManager; + } + + public DiffBasedCachedWorldStorageManager getCachedWorldStorageManager() { + return cachedWorldStorageManager; + } + + @Override + public void resetArchiveStateTo(final BlockHeader blockHeader) { + persistedState.resetWorldStateTo(blockHeader); + this.cachedWorldStorageManager.reset(); + this.cachedWorldStorageManager.addCachedLayer( + blockHeader, persistedState.getWorldStateRootHash(), persistedState); + } + + @Override + public Optional getAccountProof( + final BlockHeader blockHeader, + final Address accountAddress, + final List accountStorageKeys, + final Function, ? extends Optional> mapper) { + try (DiffBasedWorldState ws = + (DiffBasedWorldState) getMutable(blockHeader, false).orElse(null)) { + if (ws != null) { + final WorldStateProofProvider worldStateProofProvider = + new WorldStateProofProvider( + new WorldStateStorageCoordinator(ws.getWorldStateStorage())); + return mapper.apply( + worldStateProofProvider.getAccountProof( + ws.getWorldStateRootHash(), accountAddress, accountStorageKeys)); + } + } catch (Exception ex) { + LOG.error("failed proof query for " + blockHeader.getBlockHash().toShortHexString(), ex); + } + return Optional.empty(); + } + + @Override + public Optional getNodeData(final Hash hash) { + return Optional.empty(); + } + + @Override + public void close() { + try { + worldStateKeyValueStorage.close(); + } catch (Exception e) { + // no op + } + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/StorageSubscriber.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/StorageSubscriber.java new file mode 100644 index 00000000000..258df5c5b9c --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/StorageSubscriber.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.common; + +public interface StorageSubscriber { + default void onClearStorage() {} + + default void onClearFlatDatabaseStorage() {} + + default void onClearTrieLog() {} + + default void onClearTrie() {} + + default void onCloseStorage() {} +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/cache/DiffBasedCachedWorldStorageManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/cache/DiffBasedCachedWorldStorageManager.java new file mode 100644 index 00000000000..160c7ac1e3e --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/cache/DiffBasedCachedWorldStorageManager.java @@ -0,0 +1,297 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.common.cache; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldState; +import org.hyperledger.besu.ethereum.trie.diffbased.common.DiffBasedWorldStateProvider; +import org.hyperledger.besu.ethereum.trie.diffbased.common.StorageSubscriber; +import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedLayeredWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldState; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldStateConfig; +import org.hyperledger.besu.evm.internal.EvmConfiguration; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.function.Supplier; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import org.apache.tuweni.bytes.Bytes32; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class DiffBasedCachedWorldStorageManager implements StorageSubscriber { + public static final long RETAINED_LAYERS = 512; // at least 256 + typical rollbacks + private static final Logger LOG = + LoggerFactory.getLogger(DiffBasedCachedWorldStorageManager.class); + private final DiffBasedWorldStateProvider archive; + private final EvmConfiguration evmConfiguration; + protected final Supplier defaultBonsaiWorldStateConfigSupplier; + private final Cache stateRootToBlockHeaderCache = + Caffeine.newBuilder() + .maximumSize(RETAINED_LAYERS) + .expireAfterWrite(100, TimeUnit.MINUTES) + .build(); + + private final DiffBasedWorldStateKeyValueStorage rootWorldStateStorage; + private final Map cachedWorldStatesByHash; + + private DiffBasedCachedWorldStorageManager( + final DiffBasedWorldStateProvider archive, + final DiffBasedWorldStateKeyValueStorage worldStateKeyValueStorage, + final Map cachedWorldStatesByHash, + final EvmConfiguration evmConfiguration, + final Supplier defaultBonsaiWorldStateConfigSupplier) { + worldStateKeyValueStorage.subscribe(this); + this.rootWorldStateStorage = worldStateKeyValueStorage; + this.cachedWorldStatesByHash = cachedWorldStatesByHash; + this.archive = archive; + this.evmConfiguration = evmConfiguration; + this.defaultBonsaiWorldStateConfigSupplier = defaultBonsaiWorldStateConfigSupplier; + } + + public DiffBasedCachedWorldStorageManager( + final DiffBasedWorldStateProvider archive, + final DiffBasedWorldStateKeyValueStorage worldStateKeyValueStorage, + final Supplier defaultBonsaiWorldStateConfigSupplier) { + this( + archive, + worldStateKeyValueStorage, + new ConcurrentHashMap<>(), + EvmConfiguration.DEFAULT, + defaultBonsaiWorldStateConfigSupplier); + } + + public synchronized void addCachedLayer( + final BlockHeader blockHeader, + final Hash worldStateRootHash, + final DiffBasedWorldState forWorldState) { + final Optional cachedDiffBasedWorldView = + Optional.ofNullable(this.cachedWorldStatesByHash.get(blockHeader.getBlockHash())); + if (cachedDiffBasedWorldView.isPresent()) { + // only replace if it is a layered storage + if (forWorldState.isPersisted() + && cachedDiffBasedWorldView.get().getWorldStateStorage() + instanceof DiffBasedLayeredWorldStateKeyValueStorage) { + LOG.atDebug() + .setMessage("updating layered world state for block {}, state root hash {}") + .addArgument(blockHeader::toLogString) + .addArgument(worldStateRootHash::toShortHexString) + .log(); + cachedDiffBasedWorldView + .get() + .updateWorldStateStorage( + createSnapshotKeyValueStorage(forWorldState.getWorldStateStorage())); + } + } else { + LOG.atDebug() + .setMessage("adding layered world state for block {}, state root hash {}") + .addArgument(blockHeader::toLogString) + .addArgument(worldStateRootHash::toShortHexString) + .log(); + if (forWorldState.isPersisted()) { + cachedWorldStatesByHash.put( + blockHeader.getHash(), + new DiffBasedCachedWorldView( + blockHeader, createSnapshotKeyValueStorage(forWorldState.getWorldStateStorage()))); + } else { + // otherwise, add the layer to the cache + cachedWorldStatesByHash.put( + blockHeader.getHash(), + new DiffBasedCachedWorldView( + blockHeader, + ((DiffBasedLayeredWorldStateKeyValueStorage) forWorldState.getWorldStateStorage()) + .clone())); + } + // add stateroot -> blockHeader cache entry + stateRootToBlockHeaderCache.put(blockHeader.getStateRoot(), blockHeader); + } + scrubCachedLayers(blockHeader.getNumber()); + } + + private synchronized void scrubCachedLayers(final long newMaxHeight) { + if (cachedWorldStatesByHash.size() > RETAINED_LAYERS) { + final long waterline = newMaxHeight - RETAINED_LAYERS; + cachedWorldStatesByHash.values().stream() + .filter(layer -> layer.getBlockNumber() < waterline) + .toList() + .forEach( + layer -> { + cachedWorldStatesByHash.remove(layer.getBlockHash()); + layer.close(); + }); + } + } + + public Optional getWorldState(final Hash blockHash) { + if (cachedWorldStatesByHash.containsKey(blockHash)) { + // return a new worldstate using worldstate storage and an isolated copy of the updater + return Optional.ofNullable(cachedWorldStatesByHash.get(blockHash)) + .map( + cached -> + createWorldState( + archive, + createLayeredKeyValueStorage(cached.getWorldStateStorage()), + evmConfiguration)); + } + LOG.atDebug() + .setMessage("did not find worldstate in cache for {}") + .addArgument(blockHash.toShortHexString()) + .log(); + + return Optional.empty(); + } + + public Optional getNearestWorldState(final BlockHeader blockHeader) { + LOG.atDebug() + .setMessage("getting nearest worldstate for {}") + .addArgument(blockHeader.toLogString()) + .log(); + + return Optional.ofNullable( + cachedWorldStatesByHash.get(blockHeader.getParentHash())) // search parent block + .map(DiffBasedCachedWorldView::getWorldStateStorage) + .or( + () -> { + // or else search the nearest state in the cache + LOG.atDebug() + .setMessage("searching cache for nearest worldstate for {}") + .addArgument(blockHeader.toLogString()) + .log(); + + final List cachedDiffBasedWorldViews = + new ArrayList<>(cachedWorldStatesByHash.values()); + return cachedDiffBasedWorldViews.stream() + .sorted( + Comparator.comparingLong( + view -> Math.abs(blockHeader.getNumber() - view.getBlockNumber()))) + .map(DiffBasedCachedWorldView::getWorldStateStorage) + .findFirst(); + }) + .map( + storage -> + createWorldState( // wrap the state in a layered worldstate + archive, createLayeredKeyValueStorage(storage), evmConfiguration)); + } + + public Optional getHeadWorldState( + final Function> hashBlockHeaderFunction) { + + LOG.atDebug().setMessage("getting head worldstate").log(); + + return rootWorldStateStorage + .getWorldStateBlockHash() + .flatMap(hashBlockHeaderFunction) + .flatMap( + blockHeader -> { + // add the head to the cache + addCachedLayer( + blockHeader, + blockHeader.getStateRoot(), + createWorldState(archive, rootWorldStateStorage, evmConfiguration)); + return getWorldState(blockHeader.getHash()); + }); + } + + public boolean contains(final Hash blockHash) { + return cachedWorldStatesByHash.containsKey(blockHash); + } + + public void reset() { + this.cachedWorldStatesByHash.clear(); + } + + public void primeRootToBlockHashCache(final Blockchain blockchain, final int numEntries) { + // prime the stateroot-to-blockhash cache + long head = blockchain.getChainHeadHeader().getNumber(); + for (long i = head; i > Math.max(0, head - numEntries); i--) { + blockchain + .getBlockHeader(i) + .ifPresent(header -> stateRootToBlockHeaderCache.put(header.getStateRoot(), header)); + } + } + + /** + * Returns the worldstate for the supplied root hash. If the worldstate is not already in cache, + * this method will attempt to fetch it and add it to the cache. synchronized to prevent + * concurrent loads/adds to the cache of the same root hash. + * + * @param rootHash rootHash to supply worldstate storage for + * @return Optional worldstate storage + */ + public synchronized Optional getStorageByRootHash( + final Hash rootHash) { + return Optional.ofNullable(stateRootToBlockHeaderCache.getIfPresent(rootHash)) + .flatMap( + header -> + Optional.ofNullable(cachedWorldStatesByHash.get(header.getHash())) + .map(DiffBasedCachedWorldView::getWorldStateStorage) + .or( + () -> { + // if not cached already, maybe fetch and cache this worldstate + var maybeWorldState = + archive.getMutable(header, false).map(BonsaiWorldState.class::cast); + maybeWorldState.ifPresent( + ws -> addCachedLayer(header, header.getStateRoot(), ws)); + return maybeWorldState.map(BonsaiWorldState::getWorldStateStorage); + })); + } + + @Override + public void onClearStorage() { + this.cachedWorldStatesByHash.clear(); + } + + @Override + public void onClearFlatDatabaseStorage() { + this.cachedWorldStatesByHash.clear(); + } + + @Override + public void onClearTrieLog() { + this.cachedWorldStatesByHash.clear(); + } + + @Override + public void onClearTrie() { + this.cachedWorldStatesByHash.clear(); + } + + @Override + public void onCloseStorage() { + this.cachedWorldStatesByHash.clear(); + } + + public abstract DiffBasedWorldState createWorldState( + final DiffBasedWorldStateProvider archive, + final DiffBasedWorldStateKeyValueStorage worldStateKeyValueStorage, + final EvmConfiguration evmConfiguration); + + public abstract DiffBasedWorldStateKeyValueStorage createLayeredKeyValueStorage( + final DiffBasedWorldStateKeyValueStorage worldStateKeyValueStorage); + + public abstract DiffBasedWorldStateKeyValueStorage createSnapshotKeyValueStorage( + final DiffBasedWorldStateKeyValueStorage worldStateKeyValueStorage); +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/cache/DiffBasedCachedWorldView.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/cache/DiffBasedCachedWorldView.java new file mode 100644 index 00000000000..0d685f63d91 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/cache/DiffBasedCachedWorldView.java @@ -0,0 +1,75 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.common.cache; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.trie.diffbased.common.StorageSubscriber; +import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedWorldStateKeyValueStorage; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DiffBasedCachedWorldView implements StorageSubscriber { + private DiffBasedWorldStateKeyValueStorage worldStateKeyValueStorage; + private final BlockHeader blockHeader; + private long worldViewSubscriberId; + private static final Logger LOG = LoggerFactory.getLogger(DiffBasedCachedWorldView.class); + + public DiffBasedCachedWorldView( + final BlockHeader blockHeader, final DiffBasedWorldStateKeyValueStorage worldView) { + this.blockHeader = blockHeader; + this.worldStateKeyValueStorage = worldView; + this.worldViewSubscriberId = worldStateKeyValueStorage.subscribe(this); + } + + public DiffBasedWorldStateKeyValueStorage getWorldStateStorage() { + return worldStateKeyValueStorage; + } + + public long getBlockNumber() { + return blockHeader.getNumber(); + } + + public Hash getBlockHash() { + return blockHeader.getHash(); + } + + public synchronized void close() { + worldStateKeyValueStorage.unSubscribe(this.worldViewSubscriberId); + try { + worldStateKeyValueStorage.close(); + } catch (final Exception e) { + LOG.warn("Failed to close worldstate storage for block " + blockHeader.toLogString(), e); + } + } + + public synchronized void updateWorldStateStorage( + final DiffBasedWorldStateKeyValueStorage newWorldStateStorage) { + long newSubscriberId = newWorldStateStorage.subscribe(this); + this.worldStateKeyValueStorage.unSubscribe(this.worldViewSubscriberId); + final DiffBasedWorldStateKeyValueStorage oldWorldStateStorage = this.worldStateKeyValueStorage; + this.worldStateKeyValueStorage = newWorldStateStorage; + this.worldViewSubscriberId = newSubscriberId; + try { + oldWorldStateStorage.close(); + } catch (final Exception e) { + LOG.warn( + "During update, failed to close prior worldstate storage for block " + + blockHeader.toLogString(), + e); + } + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/DiffBasedLayeredWorldStateKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/DiffBasedLayeredWorldStateKeyValueStorage.java new file mode 100644 index 00000000000..49f0b6e4b1d --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/DiffBasedLayeredWorldStateKeyValueStorage.java @@ -0,0 +1,21 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.common.storage; + +public interface DiffBasedLayeredWorldStateKeyValueStorage + extends DiffBasedSnapshotWorldStateKeyValueStorage { + + DiffBasedWorldStateKeyValueStorage clone(); +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/DiffBasedSnapshotWorldStateKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/DiffBasedSnapshotWorldStateKeyValueStorage.java new file mode 100644 index 00000000000..032d4a83b03 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/DiffBasedSnapshotWorldStateKeyValueStorage.java @@ -0,0 +1,20 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.common.storage; + +public interface DiffBasedSnapshotWorldStateKeyValueStorage { + + DiffBasedWorldStateKeyValueStorage getParentWorldStateStorage(); +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/DiffBasedWorldStateKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/DiffBasedWorldStateKeyValueStorage.java new file mode 100644 index 00000000000..50adf7b34fe --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/DiffBasedWorldStateKeyValueStorage.java @@ -0,0 +1,255 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.common.storage; + +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_STORAGE; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.CODE_STORAGE; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.storage.StorageProvider; +import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; +import org.hyperledger.besu.ethereum.trie.diffbased.common.StorageSubscriber; +import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.flat.FlatDbStrategy; +import org.hyperledger.besu.ethereum.worldstate.FlatDbMode; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; +import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; +import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; +import org.hyperledger.besu.util.Subscribers; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.NavigableMap; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import kotlin.Pair; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class DiffBasedWorldStateKeyValueStorage + implements WorldStateKeyValueStorage, AutoCloseable { + private static final Logger LOG = + LoggerFactory.getLogger(DiffBasedWorldStateKeyValueStorage.class); + + // 0x776f726c64526f6f74 + public static final byte[] WORLD_ROOT_HASH_KEY = "worldRoot".getBytes(StandardCharsets.UTF_8); + // 0x776f726c64426c6f636b48617368 + public static final byte[] WORLD_BLOCK_HASH_KEY = + "worldBlockHash".getBytes(StandardCharsets.UTF_8); + + private final AtomicBoolean shouldClose = new AtomicBoolean(false); + + protected final AtomicBoolean isClosed = new AtomicBoolean(false); + + protected final Subscribers subscribers = Subscribers.create(); + protected final SegmentedKeyValueStorage composedWorldStateStorage; + protected final KeyValueStorage trieLogStorage; + + public DiffBasedWorldStateKeyValueStorage(final StorageProvider provider) { + this.composedWorldStateStorage = + provider.getStorageBySegmentIdentifiers( + List.of( + ACCOUNT_INFO_STATE, CODE_STORAGE, ACCOUNT_STORAGE_STORAGE, TRIE_BRANCH_STORAGE)); + this.trieLogStorage = + provider.getStorageBySegmentIdentifier(KeyValueSegmentIdentifier.TRIE_LOG_STORAGE); + } + + public DiffBasedWorldStateKeyValueStorage( + final SegmentedKeyValueStorage composedWorldStateStorage, + final KeyValueStorage trieLogStorage) { + this.composedWorldStateStorage = composedWorldStateStorage; + this.trieLogStorage = trieLogStorage; + } + + public abstract FlatDbMode getFlatDbMode(); + + public abstract FlatDbStrategy getFlatDbStrategy(); + + @Override + public abstract DataStorageFormat getDataStorageFormat(); + + public SegmentedKeyValueStorage getComposedWorldStateStorage() { + return composedWorldStateStorage; + } + + public KeyValueStorage getTrieLogStorage() { + return trieLogStorage; + } + + public Optional getTrieLog(final Hash blockHash) { + return trieLogStorage.get(blockHash.toArrayUnsafe()); + } + + public Stream streamTrieLogKeys(final long limit) { + return trieLogStorage.streamKeys().limit(limit); + } + + public Optional getStateTrieNode(final Bytes location) { + return composedWorldStateStorage + .get(TRIE_BRANCH_STORAGE, location.toArrayUnsafe()) + .map(Bytes::wrap); + } + + public Optional getWorldStateRootHash() { + return composedWorldStateStorage.get(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY).map(Bytes::wrap); + } + + public Optional getWorldStateBlockHash() { + return composedWorldStateStorage + .get(TRIE_BRANCH_STORAGE, WORLD_BLOCK_HASH_KEY) + .map(Bytes32::wrap) + .map(Hash::wrap); + } + + public NavigableMap streamFlatAccounts( + final Bytes startKeyHash, final Bytes32 endKeyHash, final long max) { + return getFlatDbStrategy() + .streamAccountFlatDatabase(composedWorldStateStorage, startKeyHash, endKeyHash, max); + } + + public NavigableMap streamFlatAccounts( + final Bytes startKeyHash, final Predicate> takeWhile) { + return getFlatDbStrategy() + .streamAccountFlatDatabase(composedWorldStateStorage, startKeyHash, takeWhile); + } + + public NavigableMap streamFlatStorages( + final Hash accountHash, final Bytes startKeyHash, final Bytes32 endKeyHash, final long max) { + return getFlatDbStrategy() + .streamStorageFlatDatabase( + composedWorldStateStorage, accountHash, startKeyHash, endKeyHash, max); + } + + public NavigableMap streamFlatStorages( + final Hash accountHash, + final Bytes startKeyHash, + final Predicate> takeWhile) { + return getFlatDbStrategy() + .streamStorageFlatDatabase(composedWorldStateStorage, accountHash, startKeyHash, takeWhile); + } + + public boolean isWorldStateAvailable(final Bytes32 rootHash, final Hash blockHash) { + return composedWorldStateStorage + .get(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY) + .map(Bytes32::wrap) + .map(hash -> hash.equals(rootHash) || trieLogStorage.containsKey(blockHash.toArrayUnsafe())) + .orElse(false); + } + + @Override + public void clear() { + subscribers.forEach(StorageSubscriber::onClearStorage); + getFlatDbStrategy().clearAll(composedWorldStateStorage); + composedWorldStateStorage.clear(TRIE_BRANCH_STORAGE); + trieLogStorage.clear(); + } + + public void clearTrieLog() { + subscribers.forEach(StorageSubscriber::onClearTrieLog); + trieLogStorage.clear(); + } + + public void clearTrie() { + subscribers.forEach(StorageSubscriber::onClearTrie); + composedWorldStateStorage.clear(TRIE_BRANCH_STORAGE); + } + + public void clearFlatDatabase() { + subscribers.forEach(StorageSubscriber::onClearFlatDatabaseStorage); + getFlatDbStrategy().resetOnResync(composedWorldStateStorage); + } + + @Override + public abstract Updater updater(); + + public boolean pruneTrieLog(final Hash blockHash) { + try { + return trieLogStorage.tryDelete(blockHash.toArrayUnsafe()); + } catch (Exception e) { + LOG.error("Error pruning trie log for block hash {}", blockHash, e); + return false; + } + } + + @Override + public synchronized void close() throws Exception { + // when the storage clears, close + shouldClose.set(true); + tryClose(); + } + + public synchronized long subscribe(final StorageSubscriber sub) { + if (isClosed.get()) { + throw new RuntimeException("Storage is marked to close or has already closed"); + } + return subscribers.subscribe(sub); + } + + public synchronized void unSubscribe(final long id) { + subscribers.unsubscribe(id); + try { + tryClose(); + } catch (Exception e) { + LOG.atWarn() + .setMessage("exception while trying to close : {}") + .addArgument(e::getMessage) + .log(); + } + } + + protected synchronized void tryClose() throws Exception { + if (shouldClose.get() && subscribers.getSubscriberCount() < 1) { + doClose(); + } + } + + protected synchronized void doClose() throws Exception { + if (!isClosed.get()) { + // alert any subscribers we are closing: + subscribers.forEach(StorageSubscriber::onCloseStorage); + + // close all of the KeyValueStorages: + composedWorldStateStorage.close(); + trieLogStorage.close(); + + // set storage closed + isClosed.set(true); + } + } + + public interface Updater extends WorldStateKeyValueStorage.Updater { + + DiffBasedWorldStateKeyValueStorage.Updater saveWorldState( + final Bytes blockHash, final Bytes32 nodeHash, final Bytes node); + + SegmentedKeyValueStorageTransaction getWorldStateTransaction(); + + KeyValueStorageTransaction getTrieLogStorageTransaction(); + + @Override + void commit(); + + void rollback(); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/flat/AccountHashCodeStorageStrategy.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/flat/AccountHashCodeStorageStrategy.java new file mode 100644 index 00000000000..58eb18b26d4 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/flat/AccountHashCodeStorageStrategy.java @@ -0,0 +1,53 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.common.storage.flat; + +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.CODE_STORAGE; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; + +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; + +public class AccountHashCodeStorageStrategy implements CodeStorageStrategy { + @Override + public Optional getFlatCode( + final Hash codeHash, final Hash accountHash, final SegmentedKeyValueStorage storage) { + return storage + .get(CODE_STORAGE, accountHash.toArrayUnsafe()) + .map(Bytes::wrap) + .filter(b -> Hash.hash(b).equals(codeHash)); + } + + @Override + public void putFlatCode( + final SegmentedKeyValueStorageTransaction transaction, + final Hash accountHash, + final Hash codeHash, + final Bytes code) { + transaction.put(CODE_STORAGE, accountHash.toArrayUnsafe(), code.toArrayUnsafe()); + } + + @Override + public void removeFlatCode( + final SegmentedKeyValueStorageTransaction transaction, + final Hash accountHash, + final Hash codeHash) { + transaction.remove(CODE_STORAGE, accountHash.toArrayUnsafe()); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/flat/CodeHashCodeStorageStrategy.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/flat/CodeHashCodeStorageStrategy.java new file mode 100644 index 00000000000..3d5516be00a --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/flat/CodeHashCodeStorageStrategy.java @@ -0,0 +1,54 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.common.storage.flat; + +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.CODE_STORAGE; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; + +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; + +public class CodeHashCodeStorageStrategy implements CodeStorageStrategy { + + @Override + public Optional getFlatCode( + final Hash codeHash, final Hash accountHash, final SegmentedKeyValueStorage storage) { + return storage.get(CODE_STORAGE, codeHash.toArrayUnsafe()).map(Bytes::wrap); + } + + @Override + public void putFlatCode( + final SegmentedKeyValueStorageTransaction transaction, + final Hash accountHash, + final Hash codeHash, + final Bytes code) { + transaction.put(CODE_STORAGE, codeHash.toArrayUnsafe(), code.toArrayUnsafe()); + } + + @Override + public void removeFlatCode( + final SegmentedKeyValueStorageTransaction transaction, + final Hash accountHash, + final Hash codeHash) {} + + public static boolean isCodeHashValue(final byte[] key, final byte[] value) { + final Hash valueHash = Hash.hash(Bytes.wrap(value)); + return Bytes.wrap(key).equals(valueHash); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/flat/CodeStorageStrategy.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/flat/CodeStorageStrategy.java new file mode 100644 index 00000000000..8374a8774e6 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/flat/CodeStorageStrategy.java @@ -0,0 +1,40 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.common.storage.flat; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; + +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; + +public interface CodeStorageStrategy { + + Optional getFlatCode( + final Hash codeHash, final Hash accountHash, final SegmentedKeyValueStorage storage); + + void putFlatCode( + final SegmentedKeyValueStorageTransaction transaction, + final Hash accountHash, + final Hash codeHash, + final Bytes code); + + void removeFlatCode( + final SegmentedKeyValueStorageTransaction transaction, + final Hash accountHash, + final Hash codeHash); +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/flat/FlatDbStrategy.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/flat/FlatDbStrategy.java new file mode 100644 index 00000000000..ad631b8da91 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/flat/FlatDbStrategy.java @@ -0,0 +1,314 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.common.storage.flat; + +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_STORAGE_STORAGE; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.CODE_STORAGE; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.StorageSlotKey; +import org.hyperledger.besu.ethereum.trie.NodeLoader; +import org.hyperledger.besu.metrics.BesuMetricCategory; +import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.metrics.Counter; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; + +import java.util.Comparator; +import java.util.NavigableMap; +import java.util.Optional; +import java.util.TreeMap; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import kotlin.Pair; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.rlp.RLP; + +/** + * This class represents a FlatDbReaderStrategy, which is responsible for reading and writing data + * from flat databases. It implements various methods for storing and retrieving account data, code + * data, and storage data from the corresponding KeyValueStorage. + */ +public abstract class FlatDbStrategy { + protected final MetricsSystem metricsSystem; + protected final Counter getAccountCounter; + protected final Counter getAccountFoundInFlatDatabaseCounter; + + protected final Counter getStorageValueCounter; + protected final Counter getStorageValueFlatDatabaseCounter; + protected final CodeStorageStrategy codeStorageStrategy; + + public FlatDbStrategy( + final MetricsSystem metricsSystem, final CodeStorageStrategy codeStorageStrategy) { + this.metricsSystem = metricsSystem; + this.codeStorageStrategy = codeStorageStrategy; + + getAccountCounter = + metricsSystem.createCounter( + BesuMetricCategory.BLOCKCHAIN, + "get_account_total", + "Total number of calls to getAccount"); + + getAccountFoundInFlatDatabaseCounter = + metricsSystem.createCounter( + BesuMetricCategory.BLOCKCHAIN, + "get_account_flat_database", + "Number of accounts found in the flat database"); + + getStorageValueCounter = + metricsSystem.createCounter( + BesuMetricCategory.BLOCKCHAIN, + "get_storagevalue_total", + "Total number of calls to getStorageValueBySlotHash"); + + getStorageValueFlatDatabaseCounter = + metricsSystem.createCounter( + BesuMetricCategory.BLOCKCHAIN, + "get_storagevalue_flat_database", + "Number of storage slots found in the flat database"); + } + + /* + * Retrieves the account data for the given account hash, using the world state root hash supplier and node loader. + */ + public abstract Optional getFlatAccount( + Supplier> worldStateRootHashSupplier, + NodeLoader nodeLoader, + Hash accountHash, + SegmentedKeyValueStorage storage); + + /* + * Retrieves the storage value for the given account hash and storage slot key, using the world state root hash supplier, storage root supplier, and node loader. + */ + + public abstract Optional getFlatStorageValueByStorageSlotKey( + Supplier> worldStateRootHashSupplier, + Supplier> storageRootSupplier, + NodeLoader nodeLoader, + Hash accountHash, + StorageSlotKey storageSlotKey, + SegmentedKeyValueStorage storageStorage); + + public boolean isCodeByCodeHash() { + return codeStorageStrategy instanceof CodeHashCodeStorageStrategy; + } + + /* + * Retrieves the code data for the given code hash and account hash. + */ + public Optional getFlatCode( + final Hash codeHash, final Hash accountHash, final SegmentedKeyValueStorage storage) { + if (codeHash.equals(Hash.EMPTY)) { + return Optional.of(Bytes.EMPTY); + } else { + return codeStorageStrategy.getFlatCode(codeHash, accountHash, storage); + } + } + + /* + * Puts the account data for the given account hash, using the world state root hash supplier and node loader. + */ + public void putFlatAccount( + final SegmentedKeyValueStorageTransaction transaction, + final Hash accountHash, + final Bytes accountValue) { + transaction.put(ACCOUNT_INFO_STATE, accountHash.toArrayUnsafe(), accountValue.toArrayUnsafe()); + } + + public void removeFlatAccount( + final SegmentedKeyValueStorageTransaction transaction, final Hash accountHash) { + transaction.remove(ACCOUNT_INFO_STATE, accountHash.toArrayUnsafe()); + } + + /* + * Puts the storage value for the given account hash and storage slot key, using the world state root hash supplier, storage root supplier, and node loader. + */ + public void putFlatAccountStorageValueByStorageSlotHash( + final SegmentedKeyValueStorageTransaction transaction, + final Hash accountHash, + final Hash slotHash, + final Bytes storage) { + transaction.put( + ACCOUNT_STORAGE_STORAGE, + Bytes.concatenate(accountHash, slotHash).toArrayUnsafe(), + storage.toArrayUnsafe()); + } + + /* + * Removes the storage value for the given account hash and storage slot key, using the world state root hash supplier, storage root supplier, and node loader. + */ + public void removeFlatAccountStorageValueByStorageSlotHash( + final SegmentedKeyValueStorageTransaction transaction, + final Hash accountHash, + final Hash slotHash) { + transaction.remove( + ACCOUNT_STORAGE_STORAGE, Bytes.concatenate(accountHash, slotHash).toArrayUnsafe()); + } + + /* + * Removes code for the given account hash. + */ + public void removeFlatCode( + final SegmentedKeyValueStorageTransaction transaction, + final Hash accountHash, + final Hash codeHash) { + codeStorageStrategy.removeFlatCode(transaction, accountHash, codeHash); + } + + /* + * Puts the code data for the given code hash and account hash. + */ + public void putFlatCode( + final SegmentedKeyValueStorageTransaction transaction, + final Hash accountHash, + final Hash codeHash, + final Bytes code) { + codeStorageStrategy.putFlatCode(transaction, accountHash, codeHash, code); + } + + public void clearAll(final SegmentedKeyValueStorage storage) { + storage.clear(ACCOUNT_INFO_STATE); + storage.clear(ACCOUNT_STORAGE_STORAGE); + storage.clear(CODE_STORAGE); + } + + public void resetOnResync(final SegmentedKeyValueStorage storage) { + storage.clear(ACCOUNT_INFO_STATE); + storage.clear(ACCOUNT_STORAGE_STORAGE); + } + + public NavigableMap streamAccountFlatDatabase( + final SegmentedKeyValueStorage storage, + final Bytes startKeyHash, + final Bytes32 endKeyHash, + final long max) { + + return toNavigableMap(accountsToPairStream(storage, startKeyHash, endKeyHash).limit(max)); + } + + public NavigableMap streamAccountFlatDatabase( + final SegmentedKeyValueStorage storage, + final Bytes startKeyHash, + final Predicate> takeWhile) { + + return toNavigableMap(accountsToPairStream(storage, startKeyHash).takeWhile(takeWhile)); + } + + /** streams RLP encoded storage values using a specified stream limit. */ + public NavigableMap streamStorageFlatDatabase( + final SegmentedKeyValueStorage storage, + final Hash accountHash, + final Bytes startKeyHash, + final Bytes32 endKeyHash, + final long max) { + + return toNavigableMap( + storageToPairStream(storage, accountHash, startKeyHash, endKeyHash, RLP::encodeValue) + .limit(max)); + } + + /** streams raw storage Bytes using a specified predicate filter and value mapper. */ + public NavigableMap streamStorageFlatDatabase( + final SegmentedKeyValueStorage storage, + final Hash accountHash, + final Bytes startKeyHash, + final Bytes32 endKeyHash, + final Predicate> takeWhile) { + + return toNavigableMap( + storageToPairStream(storage, accountHash, startKeyHash, endKeyHash, RLP::encodeValue) + .takeWhile(takeWhile)); + } + + /** streams raw storage Bytes using a specified predicate filter and value mapper. */ + public NavigableMap streamStorageFlatDatabase( + final SegmentedKeyValueStorage storage, + final Hash accountHash, + final Bytes startKeyHash, + final Predicate> takeWhile) { + return toNavigableMap( + storageToPairStream(storage, accountHash, startKeyHash, RLP::encodeValue) + .takeWhile(takeWhile)); + } + + private static Stream> storageToPairStream( + final SegmentedKeyValueStorage storage, + final Hash accountHash, + final Bytes startKeyHash, + final Function valueMapper) { + + return storage + .streamFromKey( + ACCOUNT_STORAGE_STORAGE, Bytes.concatenate(accountHash, startKeyHash).toArrayUnsafe()) + .takeWhile(pair -> Bytes.wrap(pair.getKey()).slice(0, Hash.SIZE).equals(accountHash)) + .map( + pair -> + new Pair<>( + Bytes32.wrap(Bytes.wrap(pair.getKey()).slice(Hash.SIZE)), + valueMapper.apply(Bytes.wrap(pair.getValue()).trimLeadingZeros()))); + } + + private static Stream> storageToPairStream( + final SegmentedKeyValueStorage storage, + final Hash accountHash, + final Bytes startKeyHash, + final Bytes32 endKeyHash, + final Function valueMapper) { + + return storage + .streamFromKey( + ACCOUNT_STORAGE_STORAGE, + Bytes.concatenate(accountHash, startKeyHash).toArrayUnsafe(), + Bytes.concatenate(accountHash, endKeyHash).toArrayUnsafe()) + .map( + pair -> + new Pair<>( + Bytes32.wrap(Bytes.wrap(pair.getKey()).slice(Hash.SIZE)), + valueMapper.apply(Bytes.wrap(pair.getValue()).trimLeadingZeros()))); + } + + private static Stream> accountsToPairStream( + final SegmentedKeyValueStorage storage, final Bytes startKeyHash, final Bytes32 endKeyHash) { + return storage + .streamFromKey(ACCOUNT_INFO_STATE, startKeyHash.toArrayUnsafe(), endKeyHash.toArrayUnsafe()) + .map(pair -> new Pair<>(Bytes32.wrap(pair.getKey()), Bytes.wrap(pair.getValue()))); + } + + private static Stream> accountsToPairStream( + final SegmentedKeyValueStorage storage, final Bytes startKeyHash) { + return storage + .streamFromKey(ACCOUNT_INFO_STATE, startKeyHash.toArrayUnsafe()) + .map(pair -> new Pair<>(Bytes32.wrap(pair.getKey()), Bytes.wrap(pair.getValue()))); + } + + private static NavigableMap toNavigableMap( + final Stream> pairStream) { + final TreeMap collected = + pairStream.collect( + Collectors.toMap( + Pair::getFirst, + Pair::getSecond, + (v1, v2) -> v1, + () -> new TreeMap<>(Comparator.comparing(Bytes::toHexString)))); + pairStream.close(); + return collected; + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/flat/FlatDbStrategyProvider.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/flat/FlatDbStrategyProvider.java new file mode 100644 index 00000000000..3ce51e793a4 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/flat/FlatDbStrategyProvider.java @@ -0,0 +1,172 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.common.storage.flat; + +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.CODE_STORAGE; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE; +import static org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedWorldStateKeyValueStorage.WORLD_ROOT_HASH_KEY; + +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.flat.FullFlatDbStrategy; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.flat.PartialFlatDbStrategy; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.ethereum.worldstate.FlatDbMode; +import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; + +import java.nio.charset.StandardCharsets; +import java.util.Optional; + +import com.google.common.annotations.VisibleForTesting; +import org.apache.tuweni.bytes.Bytes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class FlatDbStrategyProvider { + private static final Logger LOG = LoggerFactory.getLogger(FlatDbStrategyProvider.class); + + // 0x666C61744462537461747573 + public static final byte[] FLAT_DB_MODE = "flatDbStatus".getBytes(StandardCharsets.UTF_8); + private final MetricsSystem metricsSystem; + private final DataStorageConfiguration dataStorageConfiguration; + protected FlatDbMode flatDbMode; + protected FlatDbStrategy flatDbStrategy; + + public FlatDbStrategyProvider( + final MetricsSystem metricsSystem, final DataStorageConfiguration dataStorageConfiguration) { + this.metricsSystem = metricsSystem; + this.dataStorageConfiguration = dataStorageConfiguration; + } + + public void loadFlatDbStrategy(final SegmentedKeyValueStorage composedWorldStateStorage) { + // derive our flatdb strategy from db or default: + var newFlatDbMode = deriveFlatDbStrategy(composedWorldStateStorage); + + // if flatDbMode is not loaded or has changed, reload flatDbStrategy + if (this.flatDbMode == null || !this.flatDbMode.equals(newFlatDbMode)) { + this.flatDbMode = newFlatDbMode; + final CodeStorageStrategy codeStorageStrategy = + deriveUseCodeStorageByHash(composedWorldStateStorage) + ? new CodeHashCodeStorageStrategy() + : new AccountHashCodeStorageStrategy(); + if (flatDbMode == FlatDbMode.FULL) { + this.flatDbStrategy = new FullFlatDbStrategy(metricsSystem, codeStorageStrategy); + } else { + this.flatDbStrategy = new PartialFlatDbStrategy(metricsSystem, codeStorageStrategy); + } + } + } + + @VisibleForTesting + FlatDbMode deriveFlatDbStrategy(final SegmentedKeyValueStorage composedWorldStateStorage) { + final FlatDbMode requestedFlatDbMode = + dataStorageConfiguration.getUnstable().getBonsaiFullFlatDbEnabled() + ? FlatDbMode.FULL + : FlatDbMode.PARTIAL; + + final var existingTrieData = + composedWorldStateStorage.get(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY).isPresent(); + + var flatDbMode = + FlatDbMode.fromVersion( + composedWorldStateStorage + .get(TRIE_BRANCH_STORAGE, FLAT_DB_MODE) + .map(Bytes::wrap) + .orElseGet( + () -> { + // if we do not have a db-supplied config for flatdb, derive it: + // default to partial if trie data exists, but the flat config does not, + // and default to the storage config otherwise + var flatDbModeVal = + existingTrieData + ? FlatDbMode.PARTIAL.getVersion() + : requestedFlatDbMode.getVersion(); + // persist this config in the db + var setDbModeTx = composedWorldStateStorage.startTransaction(); + setDbModeTx.put( + TRIE_BRANCH_STORAGE, FLAT_DB_MODE, flatDbModeVal.toArrayUnsafe()); + setDbModeTx.commit(); + + return flatDbModeVal; + })); + LOG.info("Bonsai flat db mode found {}", flatDbMode); + + return flatDbMode; + } + + protected boolean deriveUseCodeStorageByHash( + final SegmentedKeyValueStorage composedWorldStateStorage) { + final boolean configCodeUsingHash = + dataStorageConfiguration.getUnstable().getBonsaiCodeStoredByCodeHashEnabled(); + boolean codeUsingCodeByHash = + detectCodeStorageByHash(composedWorldStateStorage) + .map( + dbCodeUsingHash -> { + if (dbCodeUsingHash != configCodeUsingHash) { + LOG.warn( + "Bonsai db is using code storage mode {} but config specifies mode {}. Using mode from database", + dbCodeUsingHash, + configCodeUsingHash); + } + return dbCodeUsingHash; + }) + .orElse(configCodeUsingHash); + LOG.info("Bonsai db mode with code stored using code hash enabled = {}", codeUsingCodeByHash); + return codeUsingCodeByHash; + } + + private Optional detectCodeStorageByHash( + final SegmentedKeyValueStorage composedWorldStateStorage) { + return composedWorldStateStorage.stream(CODE_STORAGE) + .limit(1) + .findFirst() + .map( + keypair -> + CodeHashCodeStorageStrategy.isCodeHashValue(keypair.getKey(), keypair.getValue())); + } + + public FlatDbStrategy getFlatDbStrategy( + final SegmentedKeyValueStorage composedWorldStateStorage) { + if (flatDbStrategy == null) { + loadFlatDbStrategy(composedWorldStateStorage); + } + return flatDbStrategy; + } + + public void upgradeToFullFlatDbMode(final SegmentedKeyValueStorage composedWorldStateStorage) { + final SegmentedKeyValueStorageTransaction transaction = + composedWorldStateStorage.startTransaction(); + LOG.info("setting FlatDbStrategy to FULL"); + transaction.put( + TRIE_BRANCH_STORAGE, FLAT_DB_MODE, FlatDbMode.FULL.getVersion().toArrayUnsafe()); + transaction.commit(); + loadFlatDbStrategy(composedWorldStateStorage); // force reload of flat db reader strategy + } + + public void downgradeToPartialFlatDbMode( + final SegmentedKeyValueStorage composedWorldStateStorage) { + final SegmentedKeyValueStorageTransaction transaction = + composedWorldStateStorage.startTransaction(); + LOG.info("setting FlatDbStrategy to PARTIAL"); + transaction.put( + TRIE_BRANCH_STORAGE, FLAT_DB_MODE, FlatDbMode.PARTIAL.getVersion().toArrayUnsafe()); + transaction.commit(); + loadFlatDbStrategy(composedWorldStateStorage); // force reload of flat db reader strategy + } + + public FlatDbMode getFlatDbMode() { + return flatDbMode; + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/trielog/NoOpTrieLogManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/trielog/NoOpTrieLogManager.java new file mode 100644 index 00000000000..8c8c2acbfc2 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/trielog/NoOpTrieLogManager.java @@ -0,0 +1,51 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.common.trielog; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldState; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.DiffBasedWorldStateUpdateAccumulator; +import org.hyperledger.besu.plugin.services.trielogs.TrieLog; + +import java.util.Optional; + +public class NoOpTrieLogManager extends TrieLogManager { + + public NoOpTrieLogManager() { + super(null, null, 0, null); + } + + @Override + public synchronized void saveTrieLog( + final DiffBasedWorldStateUpdateAccumulator localUpdater, + final Hash forWorldStateRootHash, + final BlockHeader forBlockHeader, + final DiffBasedWorldState forWorldState) { + // notify trie log added observers, synchronously + TrieLog trieLog = trieLogFactory.create(localUpdater, forBlockHeader); + trieLogObservers.forEach(o -> o.onTrieLogAdded(new TrieLogAddedEvent(trieLog))); + } + + @Override + public long getMaxLayersToLoad() { + return 0; + } + + @Override + public Optional getTrieLogLayer(final Hash blockHash) { + return Optional.empty(); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogAddedEvent.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/trielog/TrieLogAddedEvent.java similarity index 92% rename from ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogAddedEvent.java rename to ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/trielog/TrieLogAddedEvent.java index c9df72f0040..8e69df24e4c 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogAddedEvent.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/trielog/TrieLogAddedEvent.java @@ -11,9 +11,8 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ -package org.hyperledger.besu.ethereum.bonsai.trielog; +package org.hyperledger.besu.ethereum.trie.diffbased.common.trielog; import org.hyperledger.besu.plugin.services.trielogs.TrieLog; import org.hyperledger.besu.plugin.services.trielogs.TrieLogEvent; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogLayer.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/trielog/TrieLogLayer.java similarity index 79% rename from ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogLayer.java rename to ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/trielog/TrieLogLayer.java index 7d61e501266..fffbe5c373a 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogLayer.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/trielog/TrieLogLayer.java @@ -11,10 +11,8 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - -package org.hyperledger.besu.ethereum.bonsai.trielog; +package org.hyperledger.besu.ethereum.trie.diffbased.common.trielog; import static com.google.common.base.Preconditions.checkState; @@ -22,7 +20,7 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.StorageSlotKey; -import org.hyperledger.besu.ethereum.bonsai.BonsaiValue; +import org.hyperledger.besu.ethereum.trie.diffbased.common.DiffBasedValue; import org.hyperledger.besu.plugin.services.trielogs.TrieLog; import java.util.HashMap; @@ -49,21 +47,21 @@ public class TrieLogLayer implements TrieLog { protected Hash blockHash; protected Optional blockNumber = Optional.empty(); - Map> getAccounts() { + Map> getAccounts() { return accounts; } - Map> getCode() { + Map> getCode() { return code; } - Map>> getStorage() { + Map>> getStorage() { return storage; } - protected final Map> accounts; - protected final Map> code; - protected final Map>> storage; + protected final Map> accounts; + protected final Map> code; + protected final Map>> storage; protected boolean frozen = false; public TrieLogLayer() { @@ -104,14 +102,14 @@ public TrieLogLayer setBlockNumber(final long blockNumber) { public TrieLogLayer addAccountChange( final Address address, final AccountValue oldValue, final AccountValue newValue) { checkState(!frozen, "Layer is Frozen"); - accounts.put(address, new BonsaiValue<>(oldValue, newValue)); + accounts.put(address, new DiffBasedValue<>(oldValue, newValue)); return this; } public TrieLogLayer addCodeChange( final Address address, final Bytes oldValue, final Bytes newValue, final Hash blockHash) { checkState(!frozen, "Layer is Frozen"); - code.put(address, new BonsaiValue<>(oldValue, newValue, newValue == null)); + code.put(address, new DiffBasedValue<>(oldValue, newValue, newValue == null)); return this; } @@ -123,22 +121,22 @@ public TrieLogLayer addStorageChange( checkState(!frozen, "Layer is Frozen"); storage .computeIfAbsent(address, a -> new TreeMap<>()) - .put(slot, new BonsaiValue<>(oldValue, newValue)); + .put(slot, new DiffBasedValue<>(oldValue, newValue)); return this; } @Override - public Map> getAccountChanges() { + public Map> getAccountChanges() { return accounts; } @Override - public Map> getCodeChanges() { + public Map> getCodeChanges() { return code; } @Override - public Map>> getStorageChanges() { + public Map>> getStorageChanges() { return storage; } @@ -147,18 +145,18 @@ public boolean hasStorageChanges(final Address address) { } @Override - public Map> getStorageChanges(final Address address) { + public Map> getStorageChanges(final Address address) { return storage.getOrDefault(address, Map.of()); } @Override public Optional getPriorCode(final Address address) { - return Optional.ofNullable(code.get(address)).map(BonsaiValue::getPrior); + return Optional.ofNullable(code.get(address)).map(DiffBasedValue::getPrior); } @Override public Optional getCode(final Address address) { - return Optional.ofNullable(code.get(address)).map(BonsaiValue::getUpdated); + return Optional.ofNullable(code.get(address)).map(DiffBasedValue::getUpdated); } @Override @@ -166,7 +164,7 @@ public Optional getPriorStorageByStorageSlotKey( final Address address, final StorageSlotKey storageSlotKey) { return Optional.ofNullable(storage.get(address)) .map(i -> i.get(storageSlotKey)) - .map(BonsaiValue::getPrior); + .map(DiffBasedValue::getPrior); } @Override @@ -174,24 +172,24 @@ public Optional getStorageByStorageSlotKey( final Address address, final StorageSlotKey storageSlotKey) { return Optional.ofNullable(storage.get(address)) .map(i -> i.get(storageSlotKey)) - .map(BonsaiValue::getUpdated); + .map(DiffBasedValue::getUpdated); } @Override public Optional getPriorAccount(final Address address) { - return Optional.ofNullable(accounts.get(address)).map(BonsaiValue::getPrior); + return Optional.ofNullable(accounts.get(address)).map(DiffBasedValue::getPrior); } @Override public Optional getAccount(final Address address) { - return Optional.ofNullable(accounts.get(address)).map(BonsaiValue::getUpdated); + return Optional.ofNullable(accounts.get(address)).map(DiffBasedValue::getUpdated); } public String dump() { final StringBuilder sb = new StringBuilder(); sb.append("TrieLog{" + "blockHash=").append(blockHash).append(frozen).append('}'); sb.append("accounts\n"); - for (final Map.Entry> account : accounts.entrySet()) { + for (final Map.Entry> account : accounts.entrySet()) { sb.append(" : ").append(account.getKey()).append("\n"); if (Objects.equals(account.getValue().getPrior(), account.getValue().getUpdated())) { sb.append(" = ").append(account.getValue().getUpdated()).append("\n"); @@ -201,7 +199,7 @@ public String dump() { } } sb.append("code").append("\n"); - for (final Map.Entry> code : code.entrySet()) { + for (final Map.Entry> code : code.entrySet()) { sb.append(" : ").append(code.getKey()).append("\n"); if (Objects.equals(code.getValue().getPrior(), code.getValue().getUpdated())) { sb.append(" = ").append(code.getValue().getPrior()).append("\n"); @@ -211,10 +209,10 @@ public String dump() { } } sb.append("Storage").append("\n"); - for (final Map.Entry>> storage : + for (final Map.Entry>> storage : storage.entrySet()) { sb.append(" : ").append(storage.getKey()).append("\n"); - for (final Map.Entry> slot : + for (final Map.Entry> slot : storage.getValue().entrySet()) { final UInt256 originalValue = slot.getValue().getPrior(); final UInt256 updatedValue = slot.getValue().getUpdated(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/trielog/TrieLogManager.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/trielog/TrieLogManager.java new file mode 100644 index 00000000000..70d45f546db --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/trielog/TrieLogManager.java @@ -0,0 +1,235 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.common.trielog; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.trielog.TrieLogFactoryImpl; +import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldState; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.DiffBasedWorldStateUpdateAccumulator; +import org.hyperledger.besu.plugin.BesuContext; +import org.hyperledger.besu.plugin.services.TrieLogService; +import org.hyperledger.besu.plugin.services.trielogs.TrieLog; +import org.hyperledger.besu.plugin.services.trielogs.TrieLogEvent; +import org.hyperledger.besu.plugin.services.trielogs.TrieLogFactory; +import org.hyperledger.besu.plugin.services.trielogs.TrieLogProvider; +import org.hyperledger.besu.util.Subscribers; + +import java.util.List; +import java.util.Optional; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +import org.apache.tuweni.bytes.Bytes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TrieLogManager { + private static final Logger LOG = LoggerFactory.getLogger(TrieLogManager.class); + public static final long LOG_RANGE_LIMIT = 1000; // restrict trielog range queries to 1k logs + protected final Blockchain blockchain; + protected final DiffBasedWorldStateKeyValueStorage rootWorldStateStorage; + + protected final long maxLayersToLoad; + protected final Subscribers trieLogObservers = Subscribers.create(); + + protected final TrieLogFactory trieLogFactory; + + public TrieLogManager( + final Blockchain blockchain, + final DiffBasedWorldStateKeyValueStorage worldStateKeyValueStorage, + final long maxLayersToLoad, + final BesuContext pluginContext) { + this.blockchain = blockchain; + this.rootWorldStateStorage = worldStateKeyValueStorage; + this.maxLayersToLoad = maxLayersToLoad; + this.trieLogFactory = setupTrieLogFactory(pluginContext); + } + + public synchronized void saveTrieLog( + final DiffBasedWorldStateUpdateAccumulator localUpdater, + final Hash forWorldStateRootHash, + final BlockHeader forBlockHeader, + final DiffBasedWorldState forWorldState) { + // do not overwrite a trielog layer that already exists in the database. + // if it's only in memory we need to save it + // for example, in case of reorg we don't replace a trielog layer + if (rootWorldStateStorage.getTrieLog(forBlockHeader.getHash()).isEmpty()) { + final DiffBasedWorldStateKeyValueStorage.Updater stateUpdater = + forWorldState.getWorldStateStorage().updater(); + boolean success = false; + try { + final TrieLog trieLog = prepareTrieLog(forBlockHeader, localUpdater); + persistTrieLog(forBlockHeader, forWorldStateRootHash, trieLog, stateUpdater); + + // notify trie log added observers, synchronously + trieLogObservers.forEach(o -> o.onTrieLogAdded(new TrieLogAddedEvent(trieLog))); + + success = true; + } finally { + if (success) { + stateUpdater.commit(); + } else { + stateUpdater.rollback(); + } + } + } + } + + private TrieLog prepareTrieLog( + final BlockHeader blockHeader, final DiffBasedWorldStateUpdateAccumulator localUpdater) { + LOG.atDebug() + .setMessage("Adding layered world state for {}") + .addArgument(blockHeader::toLogString) + .log(); + final TrieLog trieLog = trieLogFactory.create(localUpdater, blockHeader); + trieLog.freeze(); + return trieLog; + } + + private void persistTrieLog( + final BlockHeader blockHeader, + final Hash worldStateRootHash, + final TrieLog trieLog, + final DiffBasedWorldStateKeyValueStorage.Updater stateUpdater) { + LOG.atDebug() + .setMessage("Persisting trie log for block hash {} and world state root {}") + .addArgument(blockHeader::toLogString) + .addArgument(worldStateRootHash::toHexString) + .log(); + + stateUpdater + .getTrieLogStorageTransaction() + .put(blockHeader.getHash().toArrayUnsafe(), trieLogFactory.serialize(trieLog)); + } + + public long getMaxLayersToLoad() { + return maxLayersToLoad; + } + + public Optional getTrieLogLayer(final Hash blockHash) { + return rootWorldStateStorage.getTrieLog(blockHash).map(trieLogFactory::deserialize); + } + + public synchronized long subscribe(final TrieLogEvent.TrieLogObserver sub) { + return trieLogObservers.subscribe(sub); + } + + public synchronized void unsubscribe(final long id) { + trieLogObservers.unsubscribe(id); + } + + private TrieLogFactory setupTrieLogFactory(final BesuContext pluginContext) { + // if we have a TrieLogService from pluginContext, use it. + var trieLogServicez = + Optional.ofNullable(pluginContext) + .flatMap(context -> context.getService(TrieLogService.class)); + + if (trieLogServicez.isPresent()) { + var trieLogService = trieLogServicez.get(); + // push the TrieLogProvider into the TrieLogService + trieLogService.configureTrieLogProvider(getTrieLogProvider()); + + // configure plugin observers: + trieLogService.getObservers().forEach(trieLogObservers::subscribe); + + // return the TrieLogFactory implementation from the TrieLogService + if (trieLogService.getTrieLogFactory().isPresent()) { + return trieLogService.getTrieLogFactory().get(); + } + } + // Otherwise default to TrieLogFactoryImpl + return new TrieLogFactoryImpl(); + } + + private TrieLogProvider getTrieLogProvider() { + return new TrieLogProvider() { + @Override + public Optional getRawTrieLogLayer(final Hash blockHash) { + return rootWorldStateStorage.getTrieLog(blockHash).map(Bytes::wrap); + } + + @Override + public Optional getRawTrieLogLayer(final long blockNumber) { + return TrieLogManager.this + .blockchain + .getBlockHeader(blockNumber) + .map(BlockHeader::getHash) + .flatMap(this::getRawTrieLogLayer); + } + + @Override + public void saveRawTrieLogLayer( + final Hash blockHash, final long blockNumber, final Bytes trieLog) { + final DiffBasedWorldStateKeyValueStorage.Updater updater = rootWorldStateStorage.updater(); + updater + .getTrieLogStorageTransaction() + .put(blockHash.toArrayUnsafe(), trieLog.toArrayUnsafe()); + updater.commit(); + // TODO maybe find a way to have a clean and complete trielog for observers + trieLogObservers.forEach( + o -> + o.onTrieLogAdded( + new TrieLogAddedEvent( + new TrieLogLayer().setBlockHash(blockHash).setBlockNumber(blockNumber)))); + } + + @Override + public Optional getTrieLogLayer(final Hash blockHash) { + return TrieLogManager.this.getTrieLogLayer(blockHash); + } + + @Override + public Optional getTrieLogLayer(final long blockNumber) { + return TrieLogManager.this + .blockchain + .getBlockHeader(blockNumber) + .map(BlockHeader::getHash) + .flatMap(TrieLogManager.this::getTrieLogLayer); + } + + @Override + public List getTrieLogsByRange( + final long fromBlockNumber, final long toBlockNumber) { + return rangeAsStream(fromBlockNumber, toBlockNumber) + .map(blockchain::getBlockHeader) + .map( + headerOpt -> + headerOpt.flatMap( + header -> + TrieLogManager.this + .getTrieLogLayer(header.getBlockHash()) + .map( + layer -> + new TrieLogRangeTuple( + header.getBlockHash(), header.getNumber(), layer)))) + .filter(Optional::isPresent) + .map(Optional::get) + .toList(); + } + + Stream rangeAsStream(final long fromBlockNumber, final long toBlockNumber) { + if (Math.abs(toBlockNumber - fromBlockNumber) > LOG_RANGE_LIMIT) { + throw new IllegalArgumentException("Requested Range too large"); + } + long left = Math.min(fromBlockNumber, toBlockNumber); + long right = Math.max(fromBlockNumber, toBlockNumber); + return LongStream.range(left, right).boxed(); + } + }; + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/trielog/TrieLogPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/trielog/TrieLogPruner.java new file mode 100644 index 00000000000..02ff553fe11 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/trielog/TrieLogPruner.java @@ -0,0 +1,263 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.common.trielog; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; +import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedWorldStateKeyValueStorage; +import org.hyperledger.besu.metrics.BesuMetricCategory; +import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.metrics.Counter; +import org.hyperledger.besu.plugin.services.trielogs.TrieLogEvent; + +import java.util.Comparator; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; +import com.google.common.collect.TreeMultimap; +import org.apache.tuweni.bytes.Bytes32; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TrieLogPruner implements TrieLogEvent.TrieLogObserver { + + private static final Logger LOG = LoggerFactory.getLogger(TrieLogPruner.class); + private static final int PRELOAD_TIMEOUT_IN_SECONDS = 30; + + private final int pruningLimit; + private final int loadingLimit; + private final DiffBasedWorldStateKeyValueStorage rootWorldStateStorage; + private final Blockchain blockchain; + private final Consumer executeAsync; + private final long numBlocksToRetain; + private final boolean requireFinalizedBlock; + private final Counter addedToPruneQueueCounter; + private final Counter prunedFromQueueCounter; + private final Counter prunedOrphanCounter; + + private final Multimap trieLogBlocksAndForksByDescendingBlockNumber = + TreeMultimap.create(Comparator.reverseOrder(), Comparator.naturalOrder()); + + public TrieLogPruner( + final DiffBasedWorldStateKeyValueStorage rootWorldStateStorage, + final Blockchain blockchain, + final Consumer executeAsync, + final long numBlocksToRetain, + final int pruningLimit, + final boolean requireFinalizedBlock, + final MetricsSystem metricsSystem) { + this.rootWorldStateStorage = rootWorldStateStorage; + this.blockchain = blockchain; + this.executeAsync = executeAsync; + this.numBlocksToRetain = numBlocksToRetain; + this.pruningLimit = pruningLimit; + this.loadingLimit = pruningLimit; // same as pruningLimit for now + this.requireFinalizedBlock = requireFinalizedBlock; + this.addedToPruneQueueCounter = + metricsSystem.createCounter( + BesuMetricCategory.PRUNER, + "trie_log_added_to_prune_queue", + "trie log added to prune queue"); + this.prunedFromQueueCounter = + metricsSystem.createCounter( + BesuMetricCategory.PRUNER, "trie_log_pruned_from_queue", "trie log pruned from queue"); + this.prunedOrphanCounter = + metricsSystem.createCounter( + BesuMetricCategory.PRUNER, "trie_log_pruned_orphan", "trie log pruned orphan"); + } + + public void initialize() { + preloadQueueWithTimeout(PRELOAD_TIMEOUT_IN_SECONDS); + } + + @VisibleForTesting + void preloadQueueWithTimeout(final int timeoutInSeconds) { + + LOG.info("Trie log pruner queue preload starting..."); + LOG.atInfo() + .setMessage("Attempting to load first {} trie logs from database...") + .addArgument(loadingLimit) + .log(); + + try (final ExecutorService preloadExecutor = Executors.newSingleThreadExecutor()) { + final Future future = preloadExecutor.submit(this::preloadQueue); + + LOG.atInfo() + .setMessage( + "Trie log pruning will timeout after {} seconds. If this is timing out, consider using `besu storage trie-log prune` subcommand, see https://besu.hyperledger.org/public-networks/how-to/bonsai-limit-trie-logs") + .addArgument(timeoutInSeconds) + .log(); + + try { + future.get(timeoutInSeconds, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException e) { + LOG.error("Error loading trie logs from database", e); + future.cancel(true); + } catch (TimeoutException e) { + future.cancel(true); + LOG.atWarn() + .setMessage("Timeout occurred while loading and processing {} trie logs from database") + .addArgument(loadingLimit) + .log(); + } + } + LOG.info("Trie log pruner queue preload complete."); + } + + private void preloadQueue() { + + try (final Stream trieLogKeys = rootWorldStateStorage.streamTrieLogKeys(loadingLimit)) { + + final AtomicLong addToPruneQueueCount = new AtomicLong(); + final AtomicLong orphansPruned = new AtomicLong(); + trieLogKeys.forEach( + blockHashAsBytes -> { + if (Thread.currentThread().isInterrupted()) { + throw new RuntimeException( + new InterruptedException("Thread interrupted during trie log processing.")); + } + final Hash blockHash = Hash.wrap(Bytes32.wrap(blockHashAsBytes)); + final Optional header = blockchain.getBlockHeader(blockHash); + if (header.isPresent()) { + addToPruneQueue(header.get().getNumber(), blockHash); + addToPruneQueueCount.getAndIncrement(); + } else { + // prune orphaned blocks (sometimes created during block production) + rootWorldStateStorage.pruneTrieLog(blockHash); + orphansPruned.getAndIncrement(); + prunedOrphanCounter.inc(); + } + }); + + LOG.atDebug().log("Pruned {} orphaned trie logs from database...", orphansPruned.intValue()); + LOG.atInfo().log( + "Added {} trie logs to prune queue. Commencing pruning of eligible trie logs...", + addToPruneQueueCount.intValue()); + int prunedCount = pruneFromQueue(); + LOG.atInfo().log("Pruned {} trie logs", prunedCount); + } catch (Exception e) { + if (e instanceof InterruptedException + || (e.getCause() != null && e.getCause() instanceof InterruptedException)) { + LOG.info("Operation interrupted, but will attempt to prune what's in the queue so far..."); + int prunedCount = pruneFromQueue(); + LOG.atInfo().log("...pruned {} trie logs", prunedCount); + Thread.currentThread().interrupt(); // Preserve interrupt status + } else { + LOG.error("Error loading trie logs from database, nothing pruned", e); + } + } + } + + public synchronized void addToPruneQueue(final long blockNumber, final Hash blockHash) { + LOG.atTrace() + .setMessage("adding trie log to queue for later pruning blockNumber {}; blockHash {}") + .addArgument(blockNumber) + .addArgument(blockHash) + .log(); + trieLogBlocksAndForksByDescendingBlockNumber.put(blockNumber, blockHash); + addedToPruneQueueCounter.inc(); + } + + public synchronized int pruneFromQueue() { + final long retainAboveThisBlock = blockchain.getChainHeadBlockNumber() - numBlocksToRetain; + final Optional finalized = blockchain.getFinalized(); + if (requireFinalizedBlock && finalized.isEmpty()) { + LOG.debug("No finalized block present, skipping pruning"); + return 0; + } + + final long retainAboveThisBlockOrFinalized = + finalized + .flatMap(blockchain::getBlockHeader) + .map(ProcessableBlockHeader::getNumber) + .map(finalizedBlock -> Math.min(finalizedBlock, retainAboveThisBlock)) + .orElse(retainAboveThisBlock); + + LOG.atTrace() + .setMessage( + "min((chainHeadNumber: {} - numBlocksToRetain: {}) = {}, finalized: {})) = retainAboveThisBlockOrFinalized: {}") + .addArgument(blockchain::getChainHeadBlockNumber) + .addArgument(numBlocksToRetain) + .addArgument(retainAboveThisBlock) + .addArgument( + () -> + finalized + .flatMap(blockchain::getBlockHeader) + .map(ProcessableBlockHeader::getNumber) + .orElse(null)) + .addArgument(retainAboveThisBlockOrFinalized) + .log(); + + final var pruneWindowEntries = + trieLogBlocksAndForksByDescendingBlockNumber.asMap().entrySet().stream() + .dropWhile((e) -> e.getKey() > retainAboveThisBlockOrFinalized) + .limit(pruningLimit); + + final Multimap wasPruned = ArrayListMultimap.create(); + + pruneWindowEntries.forEach( + (e) -> { + for (Hash blockHash : e.getValue()) { + if (rootWorldStateStorage.pruneTrieLog(blockHash)) { + wasPruned.put(e.getKey(), blockHash); + } + } + }); + + wasPruned.keySet().forEach(trieLogBlocksAndForksByDescendingBlockNumber::removeAll); + prunedFromQueueCounter.inc(wasPruned.size()); + + LOG.atTrace() + .setMessage("pruned {} trie logs for blocks {}") + .addArgument(wasPruned::size) + .addArgument(wasPruned) + .log(); + LOG.atDebug() + .setMessage("pruned {} trie logs from {} blocks") + .addArgument(wasPruned::size) + .addArgument(() -> wasPruned.keySet().size()) + .log(); + + return wasPruned.size(); + } + + @Override + public void onTrieLogAdded(final TrieLogEvent event) { + if (TrieLogEvent.Type.ADDED.equals(event.getType())) { + final Hash blockHash = event.layer().getBlockHash(); + final Optional blockNumber = event.layer().getBlockNumber(); + blockNumber.ifPresent( + blockNum -> + executeAsync.accept( + () -> { + addToPruneQueue(blockNum, blockHash); + pruneFromQueue(); + })); + } + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/worldview/DiffBasedWorldState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/worldview/DiffBasedWorldState.java new file mode 100644 index 00000000000..76a8fbd6377 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/worldview/DiffBasedWorldState.java @@ -0,0 +1,357 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.common.worldview; + +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE; +import static org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedWorldStateKeyValueStorage.WORLD_BLOCK_HASH_KEY; +import static org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedWorldStateKeyValueStorage.WORLD_ROOT_HASH_KEY; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.StorageSlotKey; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.trie.diffbased.common.StorageSubscriber; +import org.hyperledger.besu.ethereum.trie.diffbased.common.cache.DiffBasedCachedWorldStorageManager; +import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedLayeredWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedSnapshotWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.common.trielog.TrieLogManager; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.DiffBasedWorldStateUpdateAccumulator; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; +import org.hyperledger.besu.plugin.services.exception.StorageException; +import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; +import org.hyperledger.besu.plugin.services.storage.SegmentIdentifier; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; + +import java.util.Optional; +import java.util.stream.Stream; +import javax.annotation.Nonnull; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class DiffBasedWorldState + implements MutableWorldState, DiffBasedWorldView, StorageSubscriber { + + private static final Logger LOG = LoggerFactory.getLogger(DiffBasedWorldState.class); + + protected DiffBasedWorldStateKeyValueStorage worldStateKeyValueStorage; + protected final DiffBasedCachedWorldStorageManager cachedWorldStorageManager; + protected final TrieLogManager trieLogManager; + protected DiffBasedWorldStateUpdateAccumulator accumulator; + + protected Hash worldStateRootHash; + protected Hash worldStateBlockHash; + protected DiffBasedWorldStateConfig worldStateConfig; + + protected DiffBasedWorldState( + final DiffBasedWorldStateKeyValueStorage worldStateKeyValueStorage, + final DiffBasedCachedWorldStorageManager cachedWorldStorageManager, + final TrieLogManager trieLogManager, + final DiffBasedWorldStateConfig diffBasedWorldStateConfig) { + this.worldStateKeyValueStorage = worldStateKeyValueStorage; + this.worldStateRootHash = + Hash.wrap( + Bytes32.wrap( + worldStateKeyValueStorage.getWorldStateRootHash().orElse(getEmptyTrieHash()))); + this.worldStateBlockHash = + Hash.wrap( + Bytes32.wrap(worldStateKeyValueStorage.getWorldStateBlockHash().orElse(Hash.ZERO))); + this.cachedWorldStorageManager = cachedWorldStorageManager; + this.trieLogManager = trieLogManager; + this.worldStateConfig = diffBasedWorldStateConfig; + } + + /** + * Having a protected method to override the accumulator solves the chicken-egg problem of needing + * a worldstate reference (this) when constructing the Accumulator. + * + * @param accumulator accumulator to use. + */ + public void setAccumulator(final DiffBasedWorldStateUpdateAccumulator accumulator) { + this.accumulator = accumulator; + } + + /** + * Returns the world state block hash of this world state + * + * @return the world state block hash. + */ + public Hash getWorldStateBlockHash() { + return worldStateBlockHash; + } + + /** + * Returns the world state root hash of this world state + * + * @return the world state root hash. + */ + public Hash getWorldStateRootHash() { + return worldStateRootHash; + } + + @Override + public boolean isPersisted() { + return isPersisted(worldStateKeyValueStorage); + } + + private boolean isPersisted(final WorldStateKeyValueStorage worldStateKeyValueStorage) { + return !(worldStateKeyValueStorage instanceof DiffBasedSnapshotWorldStateKeyValueStorage); + } + + /** + * Reset the worldState to this block header + * + * @param blockHeader block to use + */ + public void resetWorldStateTo(final BlockHeader blockHeader) { + worldStateBlockHash = blockHeader.getBlockHash(); + worldStateRootHash = blockHeader.getStateRoot(); + } + + @Override + public DiffBasedWorldStateKeyValueStorage getWorldStateStorage() { + return worldStateKeyValueStorage; + } + + public DiffBasedWorldStateUpdateAccumulator getAccumulator() { + return accumulator; + } + + protected Hash unsafeRootHashUpdate( + final BlockHeader blockHeader, + final DiffBasedWorldStateKeyValueStorage.Updater stateUpdater) { + // calling calculateRootHash in order to update the state + calculateRootHash( + worldStateConfig.isFrozen() ? Optional.empty() : Optional.of(stateUpdater), accumulator); + return blockHeader.getStateRoot(); + } + + @Override + public void persist(final BlockHeader blockHeader) { + final Optional maybeBlockHeader = Optional.ofNullable(blockHeader); + LOG.atDebug() + .setMessage("Persist world state for block {}") + .addArgument(maybeBlockHeader) + .log(); + + final DiffBasedWorldStateUpdateAccumulator localCopy = accumulator.copy(); + + boolean success = false; + + final DiffBasedWorldStateKeyValueStorage.Updater stateUpdater = + worldStateKeyValueStorage.updater(); + Runnable saveTrieLog = () -> {}; + + try { + final Hash calculatedRootHash; + + if (blockHeader == null || !worldStateConfig.isTrieDisabled()) { + calculatedRootHash = + calculateRootHash( + worldStateConfig.isFrozen() ? Optional.empty() : Optional.of(stateUpdater), + accumulator); + } else { + // if the trie is disabled, we cannot calculate the state root, so we directly use the root + // of the block. It's important to understand that in all networks, + // the state root must be validated independently and the block should not be trusted + // implicitly. This mode + // can be used in cases where Besu would just be a follower of another trusted client. + calculatedRootHash = unsafeRootHashUpdate(blockHeader, stateUpdater); + } + // if we are persisted with a block header, and the prior state is the parent + // then persist the TrieLog for that transition. + // If specified but not a direct descendant simply store the new block hash. + if (blockHeader != null) { + verifyWorldStateRoot(calculatedRootHash, blockHeader); + saveTrieLog = + () -> { + trieLogManager.saveTrieLog(localCopy, calculatedRootHash, blockHeader, this); + // not save a frozen state in the cache + if (!worldStateConfig.isFrozen()) { + cachedWorldStorageManager.addCachedLayer(blockHeader, calculatedRootHash, this); + } + }; + + stateUpdater + .getWorldStateTransaction() + .put(TRIE_BRANCH_STORAGE, WORLD_BLOCK_HASH_KEY, blockHeader.getHash().toArrayUnsafe()); + worldStateBlockHash = blockHeader.getHash(); + } else { + stateUpdater.getWorldStateTransaction().remove(TRIE_BRANCH_STORAGE, WORLD_BLOCK_HASH_KEY); + worldStateBlockHash = null; + } + + stateUpdater + .getWorldStateTransaction() + .put(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY, calculatedRootHash.toArrayUnsafe()); + worldStateRootHash = calculatedRootHash; + success = true; + } finally { + if (success) { + stateUpdater.commit(); + accumulator.reset(); + saveTrieLog.run(); + } else { + stateUpdater.rollback(); + accumulator.reset(); + } + } + } + + protected void verifyWorldStateRoot(final Hash calculatedStateRoot, final BlockHeader header) { + if (!worldStateConfig.isTrieDisabled() && !calculatedStateRoot.equals(header.getStateRoot())) { + throw new RuntimeException( + "World State Root does not match expected value, header " + + header.getStateRoot().toHexString() + + " calculated " + + calculatedStateRoot.toHexString()); + } + } + + @Override + public WorldUpdater updater() { + return accumulator; + } + + @Override + public Hash rootHash() { + if (worldStateConfig.isFrozen() && accumulator.isAccumulatorStateChanged()) { + worldStateRootHash = calculateRootHash(Optional.empty(), accumulator.copy()); + accumulator.resetAccumulatorStateChanged(); + } + return Hash.wrap(worldStateRootHash); + } + + protected static final KeyValueStorageTransaction noOpTx = + new KeyValueStorageTransaction() { + + @Override + public void put(final byte[] key, final byte[] value) { + // no-op + } + + @Override + public void remove(final byte[] key) { + // no-op + } + + @Override + public void commit() throws StorageException { + // no-op + } + + @Override + public void rollback() { + // no-op + } + }; + + protected static final SegmentedKeyValueStorageTransaction noOpSegmentedTx = + new SegmentedKeyValueStorageTransaction() { + + @Override + public void put( + final SegmentIdentifier segmentIdentifier, final byte[] key, final byte[] value) { + // no-op + } + + @Override + public void remove(final SegmentIdentifier segmentIdentifier, final byte[] key) { + // no-op + } + + @Override + public void commit() throws StorageException { + // no-op + } + + @Override + public void rollback() { + // no-op + } + }; + + public Hash blockHash() { + return worldStateBlockHash; + } + + @Override + public Stream streamAccounts(final Bytes32 startKeyHash, final int limit) { + throw new RuntimeException("storage format do not provide account streaming."); + } + + @Override + public UInt256 getPriorStorageValue(final Address address, final UInt256 storageKey) { + return getStorageValue(address, storageKey); + } + + @Override + public void close() { + try { + if (!isPersisted()) { + this.worldStateKeyValueStorage.close(); + if (worldStateConfig.isFrozen()) { + closeFrozenStorage(); + } + } + } catch (Exception e) { + // no op + } + } + + private void closeFrozenStorage() { + try { + final DiffBasedLayeredWorldStateKeyValueStorage worldStateLayerStorage = + (DiffBasedLayeredWorldStateKeyValueStorage) worldStateKeyValueStorage; + if (!isPersisted(worldStateLayerStorage.getParentWorldStateStorage())) { + worldStateLayerStorage.getParentWorldStateStorage().close(); + } + } catch (Exception e) { + // no op + } + } + + @Override + public abstract Hash frontierRootHash(); + + @Override + public abstract MutableWorldState freeze(); + + @Override + public abstract Account get(final Address address); + + @Override + public abstract UInt256 getStorageValue(final Address address, final UInt256 storageKey); + + @Override + public abstract Optional getStorageValueByStorageSlotKey( + final Address address, final StorageSlotKey storageSlotKey); + + @Override + public abstract Optional getCode(@Nonnull final Address address, final Hash codeHash); + + protected abstract Hash calculateRootHash( + final Optional maybeStateUpdater, + final DiffBasedWorldStateUpdateAccumulator worldStateUpdater); + + protected abstract Hash getEmptyTrieHash(); +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/worldview/DiffBasedWorldStateConfig.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/worldview/DiffBasedWorldStateConfig.java new file mode 100644 index 00000000000..cab7f3f8736 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/worldview/DiffBasedWorldStateConfig.java @@ -0,0 +1,79 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.common.worldview; + +public class DiffBasedWorldStateConfig { + + private boolean isFrozen; + + private boolean isTrieDisabled; + + public DiffBasedWorldStateConfig() { + this(false, false); + } + + public DiffBasedWorldStateConfig(final boolean isTrieDisabled) { + this(false, isTrieDisabled); + } + + public DiffBasedWorldStateConfig(final DiffBasedWorldStateConfig config) { + this(config.isFrozen(), config.isTrieDisabled()); + } + + public DiffBasedWorldStateConfig(final boolean isFrozen, final boolean isTrieDisabled) { + this.isFrozen = isFrozen; + this.isTrieDisabled = isTrieDisabled; + } + + /** + * Checks if the world state is frozen. When the world state is frozen, it cannot mutate. + * + * @return true if the world state is frozen, false otherwise. + */ + public boolean isFrozen() { + return isFrozen; + } + + /** + * Sets the frozen status of the world state. When the world state is frozen, it cannot mutate. + * + * @param frozen the new frozen status to set. + */ + public void setFrozen(final boolean frozen) { + isFrozen = frozen; + } + + /** + * Checks if the trie is disabled for the world state. When the trie is disabled, the world state + * will only work with the flat database and not the trie. In this mode, it's impossible to verify + * the state root. + * + * @return true if the trie is disabled, false otherwise. + */ + public boolean isTrieDisabled() { + return isTrieDisabled; + } + + /** + * Sets the disabled status of the trie for the world state. When the trie is disabled, the world + * state will only work with the flat database and not the trie. In this mode, it's impossible to + * verify the state root. + * + * @param trieDisabled the new disabled status to set for the trie. + */ + public void setTrieDisabled(final boolean trieDisabled) { + isTrieDisabled = trieDisabled; + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/worldview/DiffBasedWorldView.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/worldview/DiffBasedWorldView.java new file mode 100644 index 00000000000..7ecc49e2863 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/worldview/DiffBasedWorldView.java @@ -0,0 +1,63 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.common.worldview; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.StorageSlotKey; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedWorldStateKeyValueStorage; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; +import org.hyperledger.besu.evm.worldstate.WorldView; + +import java.util.Map; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; + +public interface DiffBasedWorldView extends WorldView { + + Optional getCode(Address address, final Hash codeHash); + + UInt256 getStorageValue(Address address, UInt256 key); + + Optional getStorageValueByStorageSlotKey(Address address, StorageSlotKey storageSlotKey); + + UInt256 getPriorStorageValue(Address address, UInt256 key); + + /** + * Retrieve all the storage values of an account. + * + * @param address the account to stream + * @param rootHash the root hash of the account storage trie + * @return A map that is a copy of the entries. The key is the hashed slot number, and the value + * is the Bytes representation of the storage value. + */ + Map getAllAccountStorage(final Address address, final Hash rootHash); + + static Bytes encodeTrieValue(final Bytes bytes) { + final BytesValueRLPOutput out = new BytesValueRLPOutput(); + out.writeBytes(bytes.trimLeadingZeros()); + return out.encoded(); + } + + boolean isPersisted(); + + DiffBasedWorldStateKeyValueStorage getWorldStateStorage(); + + WorldUpdater updater(); +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/worldview/accumulator/DiffBasedWorldStateUpdateAccumulator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/worldview/accumulator/DiffBasedWorldStateUpdateAccumulator.java new file mode 100644 index 00000000000..c28bb731b5b --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/worldview/accumulator/DiffBasedWorldStateUpdateAccumulator.java @@ -0,0 +1,922 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator; + +import org.hyperledger.besu.datatypes.AccountValue; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.StorageSlotKey; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.trie.MerkleTrieException; +import org.hyperledger.besu.ethereum.trie.diffbased.common.DiffBasedAccount; +import org.hyperledger.besu.ethereum.trie.diffbased.common.DiffBasedValue; +import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldState; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldView; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.preload.AccountConsumingMap; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.preload.Consumer; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.preload.StorageConsumingMap; +import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.worldstate.AbstractWorldUpdater; +import org.hyperledger.besu.evm.worldstate.UpdateTrackingAccount; +import org.hyperledger.besu.plugin.services.trielogs.TrieLog; +import org.hyperledger.besu.plugin.services.trielogs.TrieLogAccumulator; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@SuppressWarnings("unchecked") +public abstract class DiffBasedWorldStateUpdateAccumulator + extends AbstractWorldUpdater + implements DiffBasedWorldView, TrieLogAccumulator { + private static final Logger LOG = + LoggerFactory.getLogger(DiffBasedWorldStateUpdateAccumulator.class); + protected final Consumer> accountPreloader; + protected final Consumer storagePreloader; + + private final AccountConsumingMap> accountsToUpdate; + private final Map> codeToUpdate = new ConcurrentHashMap<>(); + private final Set
storageToClear = Collections.synchronizedSet(new HashSet<>()); + protected final EvmConfiguration evmConfiguration; + + // storage sub mapped by _hashed_ key. This is because in self_destruct calls we need to + // enumerate the old storage and delete it. Those are trie stored by hashed key by spec and the + // alternative was to keep a giant pre-image cache of the entire trie. + private final Map>> + storageToUpdate = new ConcurrentHashMap<>(); + + private final Map storageKeyHashLookup = new ConcurrentHashMap<>(); + protected boolean isAccumulatorStateChanged; + + public DiffBasedWorldStateUpdateAccumulator( + final DiffBasedWorldView world, + final Consumer> accountPreloader, + final Consumer storagePreloader, + final EvmConfiguration evmConfiguration) { + super(world, evmConfiguration); + this.accountsToUpdate = new AccountConsumingMap<>(new ConcurrentHashMap<>(), accountPreloader); + this.accountPreloader = accountPreloader; + this.storagePreloader = storagePreloader; + this.isAccumulatorStateChanged = false; + this.evmConfiguration = evmConfiguration; + } + + public void cloneFromUpdater(final DiffBasedWorldStateUpdateAccumulator source) { + accountsToUpdate.putAll(source.getAccountsToUpdate()); + codeToUpdate.putAll(source.codeToUpdate); + storageToClear.addAll(source.storageToClear); + storageToUpdate.putAll(source.storageToUpdate); + updatedAccounts.putAll(source.updatedAccounts); + deletedAccounts.addAll(source.deletedAccounts); + this.isAccumulatorStateChanged = true; + } + + /** + * Integrates prior state changes from an external source into the current state. This method + * retrieves state modifications from the specified source and adds them to the current state's + * list of modifications. It does not remove any existing elements in the current state's + * modification list. If a modification has been made in both the current state and the source, + * the modification from the source will be taken. This approach ensures that the source's state + * changes are prioritized and overrides any conflicting changes in the current state. + * + * @param source The source accumulator + */ + public void importStateChangesFromSource( + final DiffBasedWorldStateUpdateAccumulator source) { + source + .getAccountsToUpdate() + .forEach( + (address, diffBasedValue) -> { + ACCOUNT copyPrior = + diffBasedValue.getPrior() != null + ? copyAccount(diffBasedValue.getPrior(), this, false) + : null; + ACCOUNT copyUpdated = + diffBasedValue.getUpdated() != null + ? copyAccount(diffBasedValue.getUpdated(), this, true) + : null; + accountsToUpdate.put(address, new DiffBasedValue<>(copyPrior, copyUpdated)); + }); + source + .getCodeToUpdate() + .forEach( + (address, diffBasedValue) -> { + codeToUpdate.put( + address, + new DiffBasedValue<>(diffBasedValue.getPrior(), diffBasedValue.getUpdated())); + }); + source + .getStorageToUpdate() + .forEach( + (address, slots) -> { + StorageConsumingMap> storageConsumingMap = + storageToUpdate.computeIfAbsent( + address, + k -> + new StorageConsumingMap<>( + address, new ConcurrentHashMap<>(), storagePreloader)); + slots.forEach( + (storageSlotKey, uInt256DiffBasedValue) -> { + storageConsumingMap.put( + storageSlotKey, + new DiffBasedValue<>( + uInt256DiffBasedValue.getPrior(), uInt256DiffBasedValue.getUpdated())); + }); + }); + storageToClear.addAll(source.storageToClear); + + this.isAccumulatorStateChanged = true; + } + + /** + * Imports unchanged state data from an external source into the current state. This method + * focuses on integrating state data from the specified source that has been read but not + * modified. + * + *

The method ensures that only new, unmodified data from the source is added to the current + * state. If a state data has already been read or modified in the current state, it will not be + * added again to avoid overwriting any existing modifications. + * + * @param source The source accumulator + */ + public void importPriorStateFromSource( + final DiffBasedWorldStateUpdateAccumulator source) { + + source + .getAccountsToUpdate() + .forEach( + (address, diffBasedValue) -> { + ACCOUNT copyPrior = + diffBasedValue.getPrior() != null + ? copyAccount(diffBasedValue.getPrior(), this, false) + : null; + ACCOUNT copyUpdated = + diffBasedValue.getPrior() != null + ? copyAccount(diffBasedValue.getPrior(), this, true) + : null; + accountsToUpdate.putIfAbsent(address, new DiffBasedValue<>(copyPrior, copyUpdated)); + }); + source + .getCodeToUpdate() + .forEach( + (address, diffBasedValue) -> { + codeToUpdate.putIfAbsent( + address, + new DiffBasedValue<>(diffBasedValue.getPrior(), diffBasedValue.getPrior())); + }); + source + .getStorageToUpdate() + .forEach( + (address, slots) -> { + StorageConsumingMap> storageConsumingMap = + storageToUpdate.computeIfAbsent( + address, + k -> + new StorageConsumingMap<>( + address, new ConcurrentHashMap<>(), storagePreloader)); + slots.forEach( + (storageSlotKey, uInt256DiffBasedValue) -> { + storageConsumingMap.putIfAbsent( + storageSlotKey, + new DiffBasedValue<>( + uInt256DiffBasedValue.getPrior(), uInt256DiffBasedValue.getPrior())); + }); + }); + this.isAccumulatorStateChanged = true; + } + + protected Consumer> getAccountPreloader() { + return accountPreloader; + } + + protected Consumer getStoragePreloader() { + return storagePreloader; + } + + public EvmConfiguration getEvmConfiguration() { + return evmConfiguration; + } + + @Override + public Account get(final Address address) { + return super.get(address); + } + + @Override + protected UpdateTrackingAccount track(final UpdateTrackingAccount account) { + return super.track(account); + } + + @Override + public MutableAccount getAccount(final Address address) { + return super.getAccount(address); + } + + @Override + public MutableAccount createAccount(final Address address, final long nonce, final Wei balance) { + DiffBasedValue diffBasedValue = accountsToUpdate.get(address); + + if (diffBasedValue == null) { + diffBasedValue = new DiffBasedValue<>(null, null); + accountsToUpdate.put(address, diffBasedValue); + } else if (diffBasedValue.getUpdated() != null) { + if (diffBasedValue.getUpdated().isEmpty()) { + return track(new UpdateTrackingAccount<>(diffBasedValue.getUpdated())); + } else { + throw new IllegalStateException("Cannot create an account when one already exists"); + } + } + + final ACCOUNT newAccount = + createAccount( + this, + address, + hashAndSaveAccountPreImage(address), + nonce, + balance, + Hash.EMPTY_TRIE_HASH, + Hash.EMPTY, + true); + diffBasedValue.setUpdated(newAccount); + return track(new UpdateTrackingAccount<>(newAccount)); + } + + @Override + public Map> getAccountsToUpdate() { + return accountsToUpdate; + } + + @Override + public Map> getCodeToUpdate() { + return codeToUpdate; + } + + public Set

getStorageToClear() { + return storageToClear; + } + + @Override + public Map>> + getStorageToUpdate() { + return storageToUpdate; + } + + @Override + protected ACCOUNT getForMutation(final Address address) { + return loadAccount(address, DiffBasedValue::getUpdated); + } + + protected ACCOUNT loadAccount( + final Address address, final Function, ACCOUNT> accountFunction) { + try { + final DiffBasedValue diffBasedValue = accountsToUpdate.get(address); + if (diffBasedValue == null) { + final Account account; + if (wrappedWorldView() instanceof DiffBasedWorldStateUpdateAccumulator) { + final DiffBasedWorldStateUpdateAccumulator worldStateUpdateAccumulator = + (DiffBasedWorldStateUpdateAccumulator) wrappedWorldView(); + account = worldStateUpdateAccumulator.loadAccount(address, accountFunction); + } else { + account = wrappedWorldView().get(address); + } + if (account instanceof DiffBasedAccount diffBasedAccount) { + ACCOUNT mutableAccount = copyAccount((ACCOUNT) diffBasedAccount, this, true); + accountsToUpdate.put( + address, new DiffBasedValue<>((ACCOUNT) diffBasedAccount, mutableAccount)); + return mutableAccount; + } else { + // add the empty read in accountsToUpdate + accountsToUpdate.put(address, new DiffBasedValue<>(null, null)); + return null; + } + } else { + return accountFunction.apply(diffBasedValue); + } + } catch (MerkleTrieException e) { + // need to throw to trigger the heal + throw new MerkleTrieException( + e.getMessage(), Optional.of(address), e.getHash(), e.getLocation()); + } + } + + @Override + public Collection getTouchedAccounts() { + return getUpdatedAccounts(); + } + + @Override + public Collection
getDeletedAccountAddresses() { + return getDeletedAccounts(); + } + + @Override + public void revert() { + super.reset(); + } + + @Override + public void commit() { + this.isAccumulatorStateChanged = true; + + for (final Address deletedAddress : getDeletedAccounts()) { + final DiffBasedValue accountValue = + accountsToUpdate.computeIfAbsent( + deletedAddress, + __ -> loadAccountFromParent(deletedAddress, new DiffBasedValue<>(null, null, true))); + storageToClear.add(deletedAddress); + final DiffBasedValue codeValue = codeToUpdate.get(deletedAddress); + if (codeValue != null) { + codeValue.setUpdated(null).setCleared(); + } else { + wrappedWorldView() + .getCode( + deletedAddress, + Optional.ofNullable(accountValue) + .map(DiffBasedValue::getPrior) + .map(DiffBasedAccount::getCodeHash) + .orElse(Hash.EMPTY)) + .ifPresent( + deletedCode -> + codeToUpdate.put( + deletedAddress, new DiffBasedValue<>(deletedCode, null, true))); + } + + // mark all updated storage as to be cleared + final Map> deletedStorageUpdates = + storageToUpdate.computeIfAbsent( + deletedAddress, + k -> + new StorageConsumingMap<>( + deletedAddress, new ConcurrentHashMap<>(), storagePreloader)); + final Iterator>> iter = + deletedStorageUpdates.entrySet().iterator(); + while (iter.hasNext()) { + final Map.Entry> updateEntry = iter.next(); + final DiffBasedValue updatedSlot = updateEntry.getValue(); + if (updatedSlot.getPrior() == null || updatedSlot.getPrior().isZero()) { + iter.remove(); + } else { + updatedSlot.setUpdated(null).setCleared(); + } + } + + final ACCOUNT originalValue = accountValue.getPrior(); + if (originalValue != null) { + // Enumerate and delete addresses not updated + wrappedWorldView() + .getAllAccountStorage(deletedAddress, originalValue.getStorageRoot()) + .forEach( + (keyHash, entryValue) -> { + final StorageSlotKey storageSlotKey = + new StorageSlotKey(Hash.wrap(keyHash), Optional.empty()); + if (!deletedStorageUpdates.containsKey(storageSlotKey)) { + final UInt256 value = UInt256.fromBytes(RLP.decodeOne(entryValue)); + deletedStorageUpdates.put( + storageSlotKey, new DiffBasedValue<>(value, null, true)); + } + }); + } + if (deletedStorageUpdates.isEmpty()) { + storageToUpdate.remove(deletedAddress); + } + accountValue.setUpdated(null); + } + + getUpdatedAccounts().parallelStream() + .forEach( + tracked -> { + final Address updatedAddress = tracked.getAddress(); + final ACCOUNT updatedAccount; + final DiffBasedValue updatedAccountValue = + accountsToUpdate.get(updatedAddress); + final Map> pendingStorageUpdates = + storageToUpdate.computeIfAbsent( + updatedAddress, + k -> + new StorageConsumingMap<>( + updatedAddress, new ConcurrentHashMap<>(), storagePreloader)); + + if (tracked.getWrappedAccount() == null) { + updatedAccount = createAccount(this, tracked); + tracked.setWrappedAccount(updatedAccount); + if (updatedAccountValue == null) { + accountsToUpdate.put(updatedAddress, new DiffBasedValue<>(null, updatedAccount)); + codeToUpdate.put( + updatedAddress, new DiffBasedValue<>(null, updatedAccount.getCode())); + } else { + updatedAccountValue.setUpdated(updatedAccount); + } + } else { + updatedAccount = tracked.getWrappedAccount(); + updatedAccount.setBalance(tracked.getBalance()); + updatedAccount.setNonce(tracked.getNonce()); + if (tracked.codeWasUpdated()) { + updatedAccount.setCode(tracked.getCode()); + } + if (tracked.getStorageWasCleared()) { + updatedAccount.clearStorage(); + } + tracked.getUpdatedStorage().forEach(updatedAccount::setStorageValue); + } + + if (tracked.codeWasUpdated()) { + final DiffBasedValue pendingCode = + codeToUpdate.computeIfAbsent( + updatedAddress, + addr -> + new DiffBasedValue<>( + wrappedWorldView() + .getCode( + addr, + Optional.ofNullable(updatedAccountValue) + .map(DiffBasedValue::getPrior) + .map(DiffBasedAccount::getCodeHash) + .orElse(Hash.EMPTY)) + .orElse(null), + null)); + pendingCode.setUpdated(updatedAccount.getCode()); + } + + if (tracked.getStorageWasCleared()) { + storageToClear.add(updatedAddress); + pendingStorageUpdates.clear(); + } + + // parallel stream here may cause database corruption + updatedAccount + .getUpdatedStorage() + .entrySet() + .forEach( + storageUpdate -> { + final UInt256 keyUInt = storageUpdate.getKey(); + final StorageSlotKey slotKey = + new StorageSlotKey( + hashAndSaveSlotPreImage(keyUInt), Optional.of(keyUInt)); + final UInt256 value = storageUpdate.getValue(); + final DiffBasedValue pendingValue = + pendingStorageUpdates.get(slotKey); + if (pendingValue == null) { + pendingStorageUpdates.put( + slotKey, + new DiffBasedValue<>( + updatedAccount.getOriginalStorageValue(keyUInt), value)); + } else { + pendingValue.setUpdated(value); + } + }); + + updatedAccount.getUpdatedStorage().clear(); + + if (pendingStorageUpdates.isEmpty()) { + storageToUpdate.remove(updatedAddress); + } + + if (tracked.getStorageWasCleared()) { + tracked.setStorageWasCleared(false); // storage already cleared for this transaction + } + }); + } + + @Override + public Optional getCode(final Address address, final Hash codeHash) { + final DiffBasedValue localCode = codeToUpdate.get(address); + if (localCode == null) { + final Optional code = wrappedWorldView().getCode(address, codeHash); + if (code.isEmpty() && !codeHash.equals(Hash.EMPTY)) { + throw new MerkleTrieException( + "invalid account code", Optional.of(address), codeHash, Bytes.EMPTY); + } + return code; + } else { + return Optional.ofNullable(localCode.getUpdated()); + } + } + + @Override + public UInt256 getStorageValue(final Address address, final UInt256 slotKey) { + StorageSlotKey storageSlotKey = + new StorageSlotKey(hashAndSaveSlotPreImage(slotKey), Optional.of(slotKey)); + return getStorageValueByStorageSlotKey(address, storageSlotKey).orElse(UInt256.ZERO); + } + + @Override + public Optional getStorageValueByStorageSlotKey( + final Address address, final StorageSlotKey storageSlotKey) { + final Map> localAccountStorage = + storageToUpdate.get(address); + if (localAccountStorage != null) { + final DiffBasedValue value = localAccountStorage.get(storageSlotKey); + if (value != null) { + return Optional.ofNullable(value.getUpdated()); + } + } + try { + final Optional valueUInt = + (wrappedWorldView() instanceof DiffBasedWorldState worldState) + ? worldState.getStorageValueByStorageSlotKey(address, storageSlotKey) + : wrappedWorldView().getStorageValueByStorageSlotKey(address, storageSlotKey); + storageToUpdate + .computeIfAbsent( + address, + key -> + new StorageConsumingMap<>(address, new ConcurrentHashMap<>(), storagePreloader)) + .put( + storageSlotKey, new DiffBasedValue<>(valueUInt.orElse(null), valueUInt.orElse(null))); + return valueUInt; + } catch (MerkleTrieException e) { + // need to throw to trigger the heal + throw new MerkleTrieException( + e.getMessage(), Optional.of(address), e.getHash(), e.getLocation()); + } + } + + @Override + public UInt256 getPriorStorageValue(final Address address, final UInt256 storageKey) { + // TODO maybe log the read into the trie layer? + StorageSlotKey storageSlotKey = + new StorageSlotKey(hashAndSaveSlotPreImage(storageKey), Optional.of(storageKey)); + final Map> localAccountStorage = + storageToUpdate.get(address); + if (localAccountStorage != null) { + final DiffBasedValue value = localAccountStorage.get(storageSlotKey); + if (value != null) { + if (value.isLastStepCleared()) { + return UInt256.ZERO; + } + final UInt256 updated = value.getUpdated(); + if (updated != null) { + return updated; + } + final UInt256 original = value.getPrior(); + if (original != null) { + return original; + } + } + } + if (storageToClear.contains(address)) { + return UInt256.ZERO; + } + return getStorageValue(address, storageKey); + } + + @Override + public Map getAllAccountStorage(final Address address, final Hash rootHash) { + final Map results = wrappedWorldView().getAllAccountStorage(address, rootHash); + final StorageConsumingMap> diffBasedValueStorage = + storageToUpdate.get(address); + if (diffBasedValueStorage != null) { + // hash the key to match the implied storage interface of hashed slotKey + diffBasedValueStorage.forEach( + (key, value) -> results.put(key.getSlotHash(), value.getUpdated())); + } + return results; + } + + @Override + public boolean isPersisted() { + return true; + } + + @Override + public DiffBasedWorldStateKeyValueStorage getWorldStateStorage() { + return wrappedWorldView().getWorldStateStorage(); + } + + public void rollForward(final TrieLog layer) { + layer + .getAccountChanges() + .forEach( + (address, change) -> + rollAccountChange(address, change.getPrior(), change.getUpdated())); + layer + .getCodeChanges() + .forEach( + (address, change) -> rollCodeChange(address, change.getPrior(), change.getUpdated())); + layer + .getStorageChanges() + .forEach( + (address, storage) -> + storage.forEach( + (storageSlotKey, value) -> + rollStorageChange( + address, storageSlotKey, value.getPrior(), value.getUpdated()))); + } + + public void rollBack(final TrieLog layer) { + layer + .getAccountChanges() + .forEach( + (address, change) -> + rollAccountChange(address, change.getUpdated(), change.getPrior())); + layer + .getCodeChanges() + .forEach( + (address, change) -> rollCodeChange(address, change.getUpdated(), change.getPrior())); + layer + .getStorageChanges() + .forEach( + (address, storage) -> + storage.forEach( + (storageSlotKey, value) -> + rollStorageChange( + address, storageSlotKey, value.getUpdated(), value.getPrior()))); + } + + private void rollAccountChange( + final Address address, + final AccountValue expectedValue, + final AccountValue replacementValue) { + if (Objects.equals(expectedValue, replacementValue)) { + // non-change, a cached read. + return; + } + DiffBasedValue accountValue = accountsToUpdate.get(address); + if (accountValue == null) { + accountValue = loadAccountFromParent(address, accountValue); + } + if (accountValue == null) { + if (expectedValue == null && replacementValue != null) { + accountsToUpdate.put( + address, + new DiffBasedValue<>(null, createAccount(this, address, replacementValue, true))); + } else { + throw new IllegalStateException( + String.format( + "Expected to update account, but the account does not exist. Address=%s", address)); + } + } else { + if (expectedValue == null) { + if (accountValue.getUpdated() != null) { + throw new IllegalStateException( + String.format( + "Expected to create account, but the account exists. Address=%s", address)); + } + } else { + assertCloseEnoughForDiffing( + accountValue.getUpdated(), + expectedValue, + "Address=" + address + " Prior Value in Rolling Change"); + } + if (replacementValue == null) { + if (accountValue.getPrior() == null) { + // TODO: should we remove from the parent accumulated change also? only if it is a + // private copy + accountsToUpdate.remove(address); + } else { + accountValue.setUpdated(null); + } + } else { + accountValue.setUpdated(createAccount(wrappedWorldView(), address, replacementValue, true)); + } + } + } + + private DiffBasedValue loadAccountFromParent( + final Address address, final DiffBasedValue defaultValue) { + try { + final Account parentAccount = wrappedWorldView().get(address); + if (parentAccount instanceof DiffBasedAccount account) { + final DiffBasedValue loadedAccountValue = + new DiffBasedValue<>(copyAccount((ACCOUNT) account), ((ACCOUNT) account)); + accountsToUpdate.put(address, loadedAccountValue); + return loadedAccountValue; + } else { + return defaultValue; + } + } catch (MerkleTrieException e) { + // need to throw to trigger the heal + throw new MerkleTrieException( + e.getMessage(), Optional.of(address), e.getHash(), e.getLocation()); + } + } + + private void rollCodeChange( + final Address address, final Bytes expectedCode, final Bytes replacementCode) { + if (Objects.equals(expectedCode, replacementCode)) { + // non-change, a cached read. + return; + } + DiffBasedValue codeValue = codeToUpdate.get(address); + if (codeValue == null) { + final Bytes storedCode = + wrappedWorldView() + .getCode( + address, Optional.ofNullable(expectedCode).map(Hash::hash).orElse(Hash.EMPTY)) + .orElse(Bytes.EMPTY); + if (!storedCode.isEmpty()) { + codeValue = new DiffBasedValue<>(storedCode, storedCode); + codeToUpdate.put(address, codeValue); + } + } + + if (codeValue == null) { + if ((expectedCode == null || expectedCode.isEmpty()) && replacementCode != null) { + codeToUpdate.put(address, new DiffBasedValue<>(null, replacementCode)); + } else { + throw new IllegalStateException( + String.format( + "Expected to update code, but the code does not exist. Address=%s", address)); + } + } else { + final Bytes existingCode = codeValue.getUpdated(); + if ((expectedCode == null || expectedCode.isEmpty()) + && existingCode != null + && !existingCode.isEmpty()) { + LOG.warn("At Address={}, expected to create code, but code exists. Overwriting.", address); + } else if (!Objects.equals(expectedCode, existingCode)) { + throw new IllegalStateException( + String.format( + "Old value of code does not match expected value. Address=%s ExpectedHash=%s ActualHash=%s", + address, + expectedCode == null ? "null" : Hash.hash(expectedCode), + Hash.hash(codeValue.getUpdated()))); + } + if (replacementCode == null && codeValue.getPrior() == null) { + codeToUpdate.remove(address); + } else { + codeValue.setUpdated(replacementCode); + } + } + } + + private Map> maybeCreateStorageMap( + final Map> storageMap, final Address address) { + if (storageMap == null) { + final StorageConsumingMap> newMap = + new StorageConsumingMap<>(address, new ConcurrentHashMap<>(), storagePreloader); + storageToUpdate.put(address, newMap); + return newMap; + } else { + return storageMap; + } + } + + private void rollStorageChange( + final Address address, + final StorageSlotKey storageSlotKey, + final UInt256 expectedValue, + final UInt256 replacementValue) { + if (Objects.equals(expectedValue, replacementValue)) { + // non-change, a cached read. + return; + } + if (replacementValue == null && expectedValue != null && expectedValue.isZero()) { + // corner case on deletes, non-change + return; + } + final Map> storageMap = storageToUpdate.get(address); + DiffBasedValue slotValue = storageMap == null ? null : storageMap.get(storageSlotKey); + if (slotValue == null) { + final Optional storageValue = + wrappedWorldView().getStorageValueByStorageSlotKey(address, storageSlotKey); + if (storageValue.isPresent()) { + slotValue = new DiffBasedValue<>(storageValue.get(), storageValue.get()); + storageToUpdate + .computeIfAbsent( + address, + k -> + new StorageConsumingMap<>(address, new ConcurrentHashMap<>(), storagePreloader)) + .put(storageSlotKey, slotValue); + } + } + if (slotValue == null) { + if ((expectedValue == null || expectedValue.isZero()) && replacementValue != null) { + maybeCreateStorageMap(storageMap, address) + .put(storageSlotKey, new DiffBasedValue<>(null, replacementValue)); + } else { + throw new IllegalStateException( + String.format( + "Expected to update storage value, but the slot does not exist. Account=%s SlotKey=%s", + address, storageSlotKey)); + } + } else { + final UInt256 existingSlotValue = slotValue.getUpdated(); + if ((expectedValue == null || expectedValue.isZero()) + && existingSlotValue != null + && !existingSlotValue.isZero()) { + throw new IllegalStateException( + String.format( + "Expected to create slot, but the slot exists. Account=%s SlotKey=%s expectedValue=%s existingValue=%s", + address, storageSlotKey, expectedValue, existingSlotValue)); + } + if (!isSlotEquals(expectedValue, existingSlotValue)) { + throw new IllegalStateException( + String.format( + "Old value of slot does not match expected value. Account=%s SlotKey=%s Expected=%s Actual=%s", + address, + storageSlotKey, + expectedValue == null ? "null" : expectedValue.toShortHexString(), + existingSlotValue == null ? "null" : existingSlotValue.toShortHexString())); + } + if (replacementValue == null && slotValue.getPrior() == null) { + final Map> thisStorageUpdate = + maybeCreateStorageMap(storageMap, address); + thisStorageUpdate.remove(storageSlotKey); + if (thisStorageUpdate.isEmpty()) { + storageToUpdate.remove(address); + } + } else { + slotValue.setUpdated(replacementValue); + } + } + } + + private boolean isSlotEquals(final UInt256 expectedValue, final UInt256 existingSlotValue) { + final UInt256 sanitizedExpectedValue = (expectedValue == null) ? UInt256.ZERO : expectedValue; + final UInt256 sanitizedExistingSlotValue = + (existingSlotValue == null) ? UInt256.ZERO : existingSlotValue; + return Objects.equals(sanitizedExpectedValue, sanitizedExistingSlotValue); + } + + public boolean isAccumulatorStateChanged() { + return isAccumulatorStateChanged; + } + + public void resetAccumulatorStateChanged() { + isAccumulatorStateChanged = false; + } + + @Override + public void reset() { + storageToClear.clear(); + storageToUpdate.clear(); + codeToUpdate.clear(); + accountsToUpdate.clear(); + resetAccumulatorStateChanged(); + updatedAccounts.clear(); + deletedAccounts.clear(); + storageKeyHashLookup.clear(); + } + + protected Hash hashAndSaveAccountPreImage(final Address address) { + // no need to save account preimage by default + return Hash.hash(address); + } + + protected Hash hashAndSaveSlotPreImage(final UInt256 slotKey) { + Hash hash = storageKeyHashLookup.get(slotKey); + if (hash == null) { + hash = Hash.hash(slotKey); + storageKeyHashLookup.put(slotKey, hash); + } + return hash; + } + + public abstract DiffBasedWorldStateUpdateAccumulator copy(); + + protected abstract ACCOUNT copyAccount(final ACCOUNT account); + + protected abstract ACCOUNT copyAccount( + final ACCOUNT toCopy, final DiffBasedWorldView context, final boolean mutable); + + protected abstract ACCOUNT createAccount( + final DiffBasedWorldView context, + final Address address, + final AccountValue stateTrieAccount, + final boolean mutable); + + protected abstract ACCOUNT createAccount( + final DiffBasedWorldView context, + final Address address, + final Hash addressHash, + final long nonce, + final Wei balance, + final Hash storageRoot, + final Hash codeHash, + final boolean mutable); + + protected abstract ACCOUNT createAccount( + final DiffBasedWorldView context, final UpdateTrackingAccount tracked); + + protected abstract void assertCloseEnoughForDiffing( + final ACCOUNT source, final AccountValue account, final String context); +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/worldview/accumulator/preload/AccountConsumingMap.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/worldview/accumulator/preload/AccountConsumingMap.java new file mode 100644 index 00000000000..1a69b655b68 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/worldview/accumulator/preload/AccountConsumingMap.java @@ -0,0 +1,49 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.preload; + +import org.hyperledger.besu.datatypes.Address; + +import java.util.Map; +import java.util.concurrent.ConcurrentMap; +import javax.annotation.Nonnull; + +import com.google.common.collect.ForwardingMap; + +public class AccountConsumingMap extends ForwardingMap { + + private final ConcurrentMap accounts; + private final Consumer consumer; + + public AccountConsumingMap(final ConcurrentMap accounts, final Consumer consumer) { + this.accounts = accounts; + this.consumer = consumer; + } + + @Override + public T put(@Nonnull final Address address, @Nonnull final T value) { + consumer.process(address, value); + return accounts.put(address, value); + } + + public Consumer getConsumer() { + return consumer; + } + + @Override + protected Map delegate() { + return accounts; + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/worldview/accumulator/preload/Consumer.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/worldview/accumulator/preload/Consumer.java new file mode 100644 index 00000000000..073deb3654a --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/worldview/accumulator/preload/Consumer.java @@ -0,0 +1,21 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.preload; + +import org.hyperledger.besu.datatypes.Address; + +public interface Consumer { + void process(final Address address, T value); +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/worldview/accumulator/preload/StorageConsumingMap.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/worldview/accumulator/preload/StorageConsumingMap.java new file mode 100644 index 00000000000..f48eab5c870 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/diffbased/common/worldview/accumulator/preload/StorageConsumingMap.java @@ -0,0 +1,53 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.preload; + +import org.hyperledger.besu.datatypes.Address; + +import java.util.Map; +import java.util.concurrent.ConcurrentMap; +import javax.annotation.Nonnull; + +import com.google.common.collect.ForwardingMap; + +public class StorageConsumingMap extends ForwardingMap { + + private final Address address; + + private final ConcurrentMap storages; + private final Consumer consumer; + + public StorageConsumingMap( + final Address address, final ConcurrentMap storages, final Consumer consumer) { + this.address = address; + this.storages = storages; + this.consumer = consumer; + } + + @Override + public T put(@Nonnull final K slotKey, @Nonnull final T value) { + consumer.process(address, slotKey); + return storages.put(slotKey, value); + } + + public Consumer getConsumer() { + return consumer; + } + + @Override + protected Map delegate() { + return storages; + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/forest/ForestWorldStateArchive.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/forest/ForestWorldStateArchive.java new file mode 100644 index 00000000000..1cdd079e15a --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/forest/ForestWorldStateArchive.java @@ -0,0 +1,119 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.forest; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.proof.WorldStateProof; +import org.hyperledger.besu.ethereum.proof.WorldStateProofProvider; +import org.hyperledger.besu.ethereum.trie.MerkleTrie; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.forest.worldview.ForestMutableWorldState; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.ethereum.worldstate.WorldStatePreimageStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.worldstate.WorldState; + +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.units.bigints.UInt256; + +public class ForestWorldStateArchive implements WorldStateArchive { + private final ForestWorldStateKeyValueStorage worldStateKeyValueStorage; + private final WorldStatePreimageStorage preimageStorage; + private final WorldStateProofProvider worldStateProof; + private final EvmConfiguration evmConfiguration; + + private static final Hash EMPTY_ROOT_HASH = Hash.wrap(MerkleTrie.EMPTY_TRIE_NODE_HASH); + + public ForestWorldStateArchive( + final WorldStateStorageCoordinator worldStateStorageCoordinator, + final WorldStatePreimageStorage preimageStorage, + final EvmConfiguration evmConfiguration) { + this.worldStateKeyValueStorage = + worldStateStorageCoordinator.getStrategy(ForestWorldStateKeyValueStorage.class); + this.preimageStorage = preimageStorage; + this.worldStateProof = new WorldStateProofProvider(worldStateStorageCoordinator); + this.evmConfiguration = evmConfiguration; + } + + @Override + public Optional get(final Hash rootHash, final Hash blockHash) { + return getMutable(rootHash, blockHash).map(state -> state); + } + + @Override + public boolean isWorldStateAvailable(final Hash rootHash, final Hash blockHash) { + return worldStateKeyValueStorage.isWorldStateAvailable(rootHash); + } + + @Override + public Optional getMutable( + final BlockHeader blockHeader, final boolean isPersistingState) { + return getMutable(blockHeader.getStateRoot(), blockHeader.getHash()); + } + + @Override + public Optional getMutable(final Hash rootHash, final Hash blockHash) { + if (!worldStateKeyValueStorage.isWorldStateAvailable(rootHash)) { + return Optional.empty(); + } + return Optional.of( + new ForestMutableWorldState( + rootHash, worldStateKeyValueStorage, preimageStorage, evmConfiguration)); + } + + @Override + public MutableWorldState getMutable() { + return getMutable(EMPTY_ROOT_HASH, null).get(); + } + + @Override + public void resetArchiveStateTo(final BlockHeader blockHeader) { + // ignore for forest + } + + @Override + public Optional getNodeData(final Hash hash) { + // query by location is not supported, only query by content + return worldStateKeyValueStorage.getNodeData(hash); + } + + public ForestWorldStateKeyValueStorage getWorldStateStorage() { + return worldStateKeyValueStorage; + } + + @Override + public Optional getAccountProof( + final BlockHeader blockHeader, + final Address accountAddress, + final List accountStorageKeys, + final Function, ? extends Optional> mapper) { + return mapper.apply( + worldStateProof.getAccountProof( + blockHeader.getStateRoot(), accountAddress, accountStorageKeys)); + } + + @Override + public void close() { + // no op + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/forest/storage/ForestWorldStateKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/forest/storage/ForestWorldStateKeyValueStorage.java new file mode 100644 index 00000000000..eeec7835b62 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/forest/storage/ForestWorldStateKeyValueStorage.java @@ -0,0 +1,210 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.forest.storage; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.trie.MerkleTrie; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; +import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; +import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; +import org.hyperledger.besu.util.Subscribers; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +public class ForestWorldStateKeyValueStorage implements WorldStateKeyValueStorage { + + private final Subscribers nodeAddedListeners = Subscribers.create(); + private final KeyValueStorage keyValueStorage; + private final ReentrantLock lock = new ReentrantLock(); + + public ForestWorldStateKeyValueStorage(final KeyValueStorage keyValueStorage) { + this.keyValueStorage = keyValueStorage; + } + + @Override + public DataStorageFormat getDataStorageFormat() { + return DataStorageFormat.FOREST; + } + + public Optional getCode(final Hash codeHash) { + if (codeHash.equals(Hash.EMPTY)) { + return Optional.of(Bytes.EMPTY); + } else { + return keyValueStorage.get(codeHash.toArrayUnsafe()).map(Bytes::wrap); + } + } + + public Optional getAccountStateTrieNode(final Bytes32 nodeHash) { + return getTrieNode(nodeHash); + } + + public Optional getAccountStorageTrieNode(final Bytes32 nodeHash) { + return getTrieNode(nodeHash); + } + + private Optional getTrieNode(final Bytes32 nodeHash) { + if (nodeHash.equals(MerkleTrie.EMPTY_TRIE_NODE_HASH)) { + return Optional.of(MerkleTrie.EMPTY_TRIE_NODE); + } else { + return keyValueStorage.get(nodeHash.toArrayUnsafe()).map(Bytes::wrap); + } + } + + public boolean contains(final Bytes32 hash) { + // we don't have location info + return getNodeData(hash).isPresent(); + } + + public Optional getNodeData(final Bytes32 hash) { + if (hash.equals(MerkleTrie.EMPTY_TRIE_NODE_HASH)) { + return Optional.of(MerkleTrie.EMPTY_TRIE_NODE); + } else if (hash.equals(Hash.EMPTY)) { + return Optional.of(Bytes.EMPTY); + } else { + return keyValueStorage.get(hash.toArrayUnsafe()).map(Bytes::wrap); + } + } + + public boolean isWorldStateAvailable(final Bytes32 rootHash) { + return getAccountStateTrieNode(rootHash).isPresent(); + } + + @Override + public void clear() { + keyValueStorage.clear(); + } + + @Override + public Updater updater() { + return new Updater(lock, keyValueStorage.startTransaction(), nodeAddedListeners); + } + + public long prune(final Predicate inUseCheck) { + final AtomicInteger prunedKeys = new AtomicInteger(0); + try (final Stream entry = keyValueStorage.streamKeys()) { + entry.forEach( + key -> { + lock.lock(); + try { + if (!inUseCheck.test(key) && keyValueStorage.tryDelete(key)) { + prunedKeys.incrementAndGet(); + } + } finally { + lock.unlock(); + } + }); + } + + return prunedKeys.get(); + } + + public long addNodeAddedListener(final NodesAddedListener listener) { + return nodeAddedListeners.subscribe(listener); + } + + public void removeNodeAddedListener(final long id) { + nodeAddedListeners.unsubscribe(id); + } + + public static class Updater implements WorldStateKeyValueStorage.Updater { + + private final KeyValueStorageTransaction transaction; + private final Subscribers nodeAddedListeners; + private final Set addedNodes = new HashSet<>(); + private final Lock lock; + + public Updater( + final Lock lock, + final KeyValueStorageTransaction transaction, + final Subscribers nodeAddedListeners) { + this.lock = lock; + this.transaction = transaction; + this.nodeAddedListeners = nodeAddedListeners; + } + + public Updater putCode(final Bytes code) { + // Skip the hash calculation for empty code + final Hash codeHash = code.size() == 0 ? Hash.EMPTY : Hash.hash(code); + return putCode(codeHash, code); + } + + public Updater putCode(final Bytes32 codeHash, final Bytes code) { + if (code.size() == 0) { + // Don't save empty values + return this; + } + + addedNodes.add(codeHash); + transaction.put(codeHash.toArrayUnsafe(), code.toArrayUnsafe()); + return this; + } + + public Updater saveWorldState(final Bytes32 nodeHash, final Bytes node) { + return putAccountStateTrieNode(nodeHash, node); + } + + public Updater putAccountStateTrieNode(final Bytes32 nodeHash, final Bytes node) { + if (nodeHash.equals(MerkleTrie.EMPTY_TRIE_NODE_HASH)) { + // Don't save empty nodes + return this; + } + addedNodes.add(nodeHash); + transaction.put(nodeHash.toArrayUnsafe(), node.toArrayUnsafe()); + return this; + } + + public WorldStateKeyValueStorage.Updater removeAccountStateTrieNode(final Bytes32 nodeHash) { + transaction.remove(nodeHash.toArrayUnsafe()); + return this; + } + + public Updater putAccountStorageTrieNode(final Bytes32 nodeHash, final Bytes node) { + if (nodeHash.equals(MerkleTrie.EMPTY_TRIE_NODE_HASH)) { + // Don't save empty nodes + return this; + } + addedNodes.add(nodeHash); + transaction.put(nodeHash.toArrayUnsafe(), node.toArrayUnsafe()); + return this; + } + + @Override + public void commit() { + lock.lock(); + try { + nodeAddedListeners.forEach(listener -> listener.onNodesAdded(addedNodes)); + transaction.commit(); + } finally { + lock.unlock(); + } + } + + public void rollback() { + addedNodes.clear(); + transaction.rollback(); + } + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/forest/worldview/ForestMutableWorldState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/forest/worldview/ForestMutableWorldState.java new file mode 100644 index 00000000000..d94f3564126 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/trie/forest/worldview/ForestMutableWorldState.java @@ -0,0 +1,462 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.forest.worldview; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.rlp.RLPException; +import org.hyperledger.besu.ethereum.rlp.RLPInput; +import org.hyperledger.besu.ethereum.trie.MerkleTrie; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; +import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStatePreimageStorage; +import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.account.AccountStorageEntry; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.worldstate.AbstractWorldUpdater; +import org.hyperledger.besu.evm.worldstate.UpdateTrackingAccount; +import org.hyperledger.besu.evm.worldstate.WorldState; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.NavigableMap; +import java.util.Objects; +import java.util.Optional; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.stream.Stream; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; + +public class ForestMutableWorldState implements MutableWorldState { + + private final EvmConfiguration evmConfiguration; + private final ForestWorldStateKeyValueStorage worldStateKeyValueStorage; + private final WorldStatePreimageStorage preimageStorage; + + private final MerkleTrie accountStateTrie; + private final Map> updatedStorageTries = new HashMap<>(); + private final Map updatedAccountCode = new HashMap<>(); + private final Map newStorageKeyPreimages = new HashMap<>(); + private final Map newAccountKeyPreimages = new HashMap<>(); + + public ForestMutableWorldState( + final WorldStateKeyValueStorage worldStateKeyValueStorage, + final WorldStatePreimageStorage preimageStorage, + final EvmConfiguration evmConfiguration) { + this( + MerkleTrie.EMPTY_TRIE_NODE_HASH, + worldStateKeyValueStorage, + preimageStorage, + evmConfiguration); + } + + public ForestMutableWorldState( + final Bytes32 rootHash, + final WorldStateKeyValueStorage worldStateKeyValueStorage, + final WorldStatePreimageStorage preimageStorage, + final EvmConfiguration evmConfiguration) { + this.worldStateKeyValueStorage = (ForestWorldStateKeyValueStorage) worldStateKeyValueStorage; + this.accountStateTrie = newAccountStateTrie(rootHash); + this.preimageStorage = preimageStorage; + this.evmConfiguration = evmConfiguration; + } + + public ForestMutableWorldState( + final WorldState worldState, final EvmConfiguration evmConfiguration) { + // TODO: this is an abstraction leak (and kind of incorrect in that we reuse the underlying + // storage), but the reason for this is that the accounts() method is unimplemented below and + // can't be until NC-754. + if (!(worldState instanceof ForestMutableWorldState other)) { + throw new UnsupportedOperationException(); + } + this.worldStateKeyValueStorage = other.worldStateKeyValueStorage; + this.preimageStorage = other.preimageStorage; + this.accountStateTrie = newAccountStateTrie(other.accountStateTrie.getRootHash()); + this.evmConfiguration = evmConfiguration; + } + + private MerkleTrie newAccountStateTrie(final Bytes32 rootHash) { + return new StoredMerklePatriciaTrie<>( + (location, hash) -> worldStateKeyValueStorage.getAccountStateTrieNode(hash), + rootHash, + b -> b, + b -> b); + } + + private MerkleTrie newAccountStorageTrie(final Bytes32 rootHash) { + return new StoredMerklePatriciaTrie<>( + (location, hash) -> worldStateKeyValueStorage.getAccountStorageTrieNode(hash), + rootHash, + b -> b, + b -> b); + } + + @Override + public Hash rootHash() { + return Hash.wrap(accountStateTrie.getRootHash()); + } + + @Override + public Hash frontierRootHash() { + return rootHash(); + } + + @Override + public Account get(final Address address) { + final Hash addressHash = address.addressHash(); + return accountStateTrie + .get(addressHash) + .map(bytes -> deserializeAccount(address, addressHash, bytes)) + .orElse(null); + } + + private WorldStateAccount deserializeAccount( + final Address address, final Hash addressHash, final Bytes encoded) throws RLPException { + final RLPInput in = RLP.input(encoded); + final StateTrieAccountValue accountValue = StateTrieAccountValue.readFrom(in); + return new WorldStateAccount(address, addressHash, accountValue); + } + + @Override + public WorldUpdater updater() { + return new Updater(this, evmConfiguration); + } + + @Override + public Stream streamAccounts(final Bytes32 startKeyHash, final int limit) { + return accountStateTrie.entriesFrom(startKeyHash, limit).entrySet().stream() + .map( + entry -> { + final Optional
address = getAccountTrieKeyPreimage(entry.getKey()); + final WorldStateAccount account = + deserializeAccount( + address.orElse(Address.ZERO), Hash.wrap(entry.getKey()), entry.getValue()); + return new StreamableAccount(address, account); + }); + } + + @Override + public int hashCode() { + return Objects.hashCode(rootHash()); + } + + @Override + public final boolean equals(final Object other) { + if (!(other instanceof ForestMutableWorldState that)) { + return false; + } + + return this.rootHash().equals(that.rootHash()); + } + + @Override + public void persist(final BlockHeader blockHeader) { + final ForestWorldStateKeyValueStorage.Updater stateUpdater = + worldStateKeyValueStorage.updater(); + // Store updated code + for (final Bytes code : updatedAccountCode.values()) { + stateUpdater.putCode(code); + } + // Commit account storage tries + for (final MerkleTrie updatedStorage : updatedStorageTries.values()) { + updatedStorage.commit( + (location, hash, value) -> stateUpdater.putAccountStorageTrieNode(hash, value)); + } + // Commit account updates + accountStateTrie.commit( + (location, hash, value) -> stateUpdater.putAccountStateTrieNode(hash, value)); + + // Persist preimages + final WorldStatePreimageStorage.Updater preimageUpdater = preimageStorage.updater(); + newStorageKeyPreimages.forEach(preimageUpdater::putStorageTrieKeyPreimage); + newAccountKeyPreimages.forEach(preimageUpdater::putAccountTrieKeyPreimage); + + // Clear pending changes that we just flushed + updatedStorageTries.clear(); + updatedAccountCode.clear(); + newStorageKeyPreimages.clear(); + + // Push changes to underlying storage + preimageUpdater.commit(); + stateUpdater.commit(); + } + + private static UInt256 convertToUInt256(final Bytes value) { + // TODO: we could probably have an optimized method to decode a single scalar since it's used + // pretty often. + final RLPInput in = RLP.input(value); + return in.readUInt256Scalar(); + } + + private Optional
getAccountTrieKeyPreimage(final Bytes32 trieKey) { + return Optional.ofNullable(newAccountKeyPreimages.get(trieKey)) + .or(() -> preimageStorage.getAccountTrieKeyPreimage(trieKey)); + } + + // An immutable class that represents an individual account as stored in + // the world state's underlying merkle patricia trie. + protected class WorldStateAccount implements Account { + + private final Address address; + private final Hash addressHash; + + final StateTrieAccountValue accountValue; + + // Lazily initialized since we don't always access storage. + private volatile MerkleTrie storageTrie; + + private WorldStateAccount( + final Address address, final Hash addressHash, final StateTrieAccountValue accountValue) { + + this.address = address; + this.addressHash = addressHash; + this.accountValue = accountValue; + } + + private MerkleTrie storageTrie() { + final MerkleTrie updatedTrie = updatedStorageTries.get(address); + if (updatedTrie != null) { + storageTrie = updatedTrie; + } + if (storageTrie == null) { + storageTrie = newAccountStorageTrie(getStorageRoot()); + } + return storageTrie; + } + + @Override + public Address getAddress() { + return address; + } + + @Override + public Hash getAddressHash() { + return addressHash; + } + + @Override + public long getNonce() { + return accountValue.getNonce(); + } + + @Override + public Wei getBalance() { + return accountValue.getBalance(); + } + + Hash getStorageRoot() { + return accountValue.getStorageRoot(); + } + + @Override + public Bytes getCode() { + final Bytes updatedCode = updatedAccountCode.get(address); + if (updatedCode != null) { + return updatedCode; + } + // No code is common, save the KV-store lookup. + final Hash codeHash = getCodeHash(); + if (codeHash.equals(Hash.EMPTY)) { + return Bytes.EMPTY; + } + return worldStateKeyValueStorage.getCode(codeHash).orElse(Bytes.EMPTY); + } + + @Override + public boolean hasCode() { + return !getCode().isEmpty(); + } + + @Override + public Hash getCodeHash() { + return accountValue.getCodeHash(); + } + + @Override + public UInt256 getStorageValue(final UInt256 key) { + return storageTrie() + .get(Hash.hash(key)) + .map(ForestMutableWorldState::convertToUInt256) + .orElse(UInt256.ZERO); + } + + @Override + public UInt256 getOriginalStorageValue(final UInt256 key) { + return getStorageValue(key); + } + + @Override + public NavigableMap storageEntriesFrom( + final Bytes32 startKeyHash, final int limit) { + final NavigableMap storageEntries = new TreeMap<>(); + storageTrie() + .entriesFrom(startKeyHash, limit) + .forEach( + (key, value) -> { + final AccountStorageEntry entry = + AccountStorageEntry.create( + convertToUInt256(value), key, getStorageTrieKeyPreimage(key)); + storageEntries.put(key, entry); + }); + return storageEntries; + } + + /** + * Does this account have any storage slots that are set to non-zero values? + * + * @return true if the account has no storage values set to non-zero values. False if any + * storage is set. + */ + @Override + public boolean isStorageEmpty() { + return Hash.EMPTY_TRIE_HASH.equals( + storageTrie == null ? getStorageRoot() : storageTrie.getRootHash()); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("AccountState").append("{"); + builder.append("address=").append(getAddress()).append(", "); + builder.append("nonce=").append(getNonce()).append(", "); + builder.append("balance=").append(getBalance()).append(", "); + builder.append("storageRoot=").append(getStorageRoot()).append(", "); + builder.append("codeHash=").append(getCodeHash()).append(", "); + return builder.append("}").toString(); + } + + private Optional getStorageTrieKeyPreimage(final Bytes32 trieKey) { + return Optional.ofNullable(newStorageKeyPreimages.get(trieKey)) + .or(() -> preimageStorage.getStorageTrieKeyPreimage(trieKey)); + } + } + + protected static class Updater + extends AbstractWorldUpdater { + + protected Updater( + final ForestMutableWorldState world, final EvmConfiguration evmConfiguration) { + super(world, evmConfiguration); + } + + @Override + protected WorldStateAccount getForMutation(final Address address) { + final ForestMutableWorldState wrapped = wrappedWorldView(); + final Hash addressHash = address.addressHash(); + return wrapped + .accountStateTrie + .get(addressHash) + .map(bytes -> wrapped.deserializeAccount(address, addressHash, bytes)) + .orElse(null); + } + + @Override + public Collection getTouchedAccounts() { + return new ArrayList<>(getUpdatedAccounts()); + } + + @Override + public Collection
getDeletedAccountAddresses() { + return new ArrayList<>(getDeletedAccounts()); + } + + @Override + public void revert() { + getDeletedAccounts().clear(); + getUpdatedAccounts().clear(); + } + + @Override + public void commit() { + final ForestMutableWorldState wrapped = wrappedWorldView(); + + for (final Address address : getDeletedAccounts()) { + final Hash addressHash = address.addressHash(); + wrapped.accountStateTrie.remove(addressHash); + wrapped.updatedStorageTries.remove(address); + wrapped.updatedAccountCode.remove(address); + } + + for (final UpdateTrackingAccount updated : getUpdatedAccounts()) { + final WorldStateAccount origin = updated.getWrappedAccount(); + + // Save the code in key-value storage ... + Hash codeHash = origin == null ? Hash.EMPTY : origin.getCodeHash(); + if (updated.codeWasUpdated()) { + codeHash = Hash.hash(updated.getCode()); + wrapped.updatedAccountCode.put(updated.getAddress(), updated.getCode()); + } + // ...and storage in the account trie first. + final boolean freshState = origin == null || updated.getStorageWasCleared(); + Hash storageRoot = freshState ? Hash.EMPTY_TRIE_HASH : origin.getStorageRoot(); + if (freshState) { + wrapped.updatedStorageTries.remove(updated.getAddress()); + } + final Map updatedStorage = updated.getUpdatedStorage(); + if (!updatedStorage.isEmpty()) { + // Apply any storage updates + final MerkleTrie storageTrie = + freshState + ? wrapped.newAccountStorageTrie(Hash.EMPTY_TRIE_HASH) + : origin.storageTrie(); + wrapped.updatedStorageTries.put(updated.getAddress(), storageTrie); + final TreeSet> entries = + new TreeSet<>(Map.Entry.comparingByKey()); + entries.addAll(updatedStorage.entrySet()); + + for (final Map.Entry entry : entries) { + final UInt256 value = entry.getValue(); + final Hash keyHash = Hash.hash(entry.getKey()); + if (value.isZero()) { + storageTrie.remove(keyHash); + } else { + wrapped.newStorageKeyPreimages.put(keyHash, entry.getKey()); + storageTrie.put( + keyHash, RLP.encode(out -> out.writeBytes(entry.getValue().toMinimalBytes()))); + } + } + storageRoot = Hash.wrap(storageTrie.getRootHash()); + } + + // Save address preimage + wrapped.newAccountKeyPreimages.put(updated.getAddressHash(), updated.getAddress()); + // Lastly, save the new account. + final Bytes account = + serializeAccount(updated.getNonce(), updated.getBalance(), storageRoot, codeHash); + + wrapped.accountStateTrie.put(updated.getAddressHash(), account); + } + } + + private static Bytes serializeAccount( + final long nonce, final Wei balance, final Hash storageRoot, final Hash codeHash) { + final StateTrieAccountValue accountValue = + new StateTrieAccountValue(nonce, balance, storageRoot, codeHash); + return RLP.encode(accountValue::writeTo); + } + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/util/LogUtil.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/util/LogUtil.java deleted file mode 100644 index 9f1a9a424b6..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/util/LogUtil.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.util; - -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; - -public class LogUtil { - static ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); - - public static void throttledLog( - final Consumer logger, - final String logMessage, - final AtomicBoolean shouldLog, - final int logRepeatDelay) { - - if (shouldLog.compareAndSet(true, false)) { - logger.accept(logMessage); - - final Runnable runnable = () -> shouldLog.set(true); - executor.schedule(runnable, logRepeatDelay, TimeUnit.SECONDS); - } - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/BlockHashLookup.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/BlockHashLookup.java deleted file mode 100644 index b8f09783cf2..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/BlockHashLookup.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.vm; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.evm.operation.BlockHashOperation; - -import java.util.function.Function; - -/** - * Calculates and caches block hashes by number following the chain for a specific branch. This is - * used by {@link BlockHashOperation} and ensures that the correct block hash is returned even when - * the block being imported is on a fork. - * - *

A new BlockHashCache must be created for each block being processed but should be reused for - * all transactions within that block. - */ -public interface BlockHashLookup extends Function {} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/CachingBlockHashLookup.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/CachingBlockHashLookup.java index 1e1e47ece4a..c70af6b836b 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/CachingBlockHashLookup.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/CachingBlockHashLookup.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -20,6 +20,7 @@ import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.evm.operation.BlockHashOperation; +import org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; import java.util.HashMap; import java.util.Map; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java index 711f83e3d9a..485420df772 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java @@ -21,6 +21,7 @@ import org.hyperledger.besu.evm.ModificationNotAllowedException; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.AbstractCallOperation; import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.operation.Operation.OperationResult; import org.hyperledger.besu.evm.tracing.OperationTracer; @@ -39,6 +40,14 @@ public class DebugOperationTracer implements OperationTracer { private final TraceOptions options; + + /** + * A flag to indicate if call operations should trace just the operation cost (false, Geth style, + * debug_ series RPCs) or the operation cost and all gas granted to the child call (true, Parity + * style, trace_ series RPCs) + */ + private final boolean recordChildCallGas; + private List traceFrames = new ArrayList<>(); private TraceFrame lastFrame; @@ -48,8 +57,16 @@ public class DebugOperationTracer implements OperationTracer { private int pc; private int depth; - public DebugOperationTracer(final TraceOptions options) { + /** + * Creates the operation tracer. + * + * @param options The options, as passed in through the RPC + * @param recordChildCallGas A flag on whether to produce geth style (true) or parity style + * (false) gas amounts for call operations + */ + public DebugOperationTracer(final TraceOptions options, final boolean recordChildCallGas) { this.options = options; + this.recordChildCallGas = recordChildCallGas; } @Override @@ -67,6 +84,7 @@ public void tracePreExecution(final MessageFrame frame) { public void tracePostExecution(final MessageFrame frame, final OperationResult operationResult) { final Operation currentOperation = frame.getCurrentOperation(); final String opcode = currentOperation.getName(); + final int opcodeNumber = (opcode != null) ? currentOperation.getOpcode() : Integer.MAX_VALUE; final WorldUpdater worldUpdater = frame.getWorldUpdater(); final Bytes outputData = frame.getOutputData(); final Optional memory = captureMemory(frame); @@ -78,17 +96,21 @@ public void tracePostExecution(final MessageFrame frame, final OperationResult o final Optional> storage = captureStorage(frame); final Optional> maybeRefunds = frame.getRefunds().isEmpty() ? Optional.empty() : Optional.of(frame.getRefunds()); + long thisGasCost = operationResult.getGasCost(); + if (recordChildCallGas && currentOperation instanceof AbstractCallOperation) { + thisGasCost += frame.getMessageFrameStack().getFirst().getRemainingGas(); + } lastFrame = new TraceFrame( pc, Optional.of(opcode), + opcodeNumber, gasRemaining, - operationResult.getGasCost() == 0 - ? OptionalLong.empty() - : OptionalLong.of(operationResult.getGasCost()), + thisGasCost == 0 ? OptionalLong.empty() : OptionalLong.of(thisGasCost), frame.getGasRefund(), depth, - Optional.ofNullable(operationResult.getHaltReason()), + Optional.ofNullable(operationResult.getHaltReason()) + .or(frame::getExceptionalHaltReason), frame.getRecipientAddress(), frame.getApparentValue(), inputData, @@ -117,6 +139,7 @@ public void tracePrecompileCall( new TraceFrame( frame.getPC(), Optional.empty(), + Integer.MAX_VALUE, frame.getRemainingGas(), OptionalLong.empty(), frame.getGasRefund(), @@ -163,6 +186,7 @@ public void traceAccountCreationResult( new TraceFrame( frame.getPC(), Optional.empty(), + Integer.MAX_VALUE, frame.getRemainingGas(), OptionalLong.empty(), frame.getGasRefund(), diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DataStorageConfiguration.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DataStorageConfiguration.java index a36b150337a..320e38733d4 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DataStorageConfiguration.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DataStorageConfiguration.java @@ -11,11 +11,11 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.worldstate; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; + import org.immutables.value.Value; @Value.Immutable @@ -23,8 +23,32 @@ public interface DataStorageConfiguration { long DEFAULT_BONSAI_MAX_LAYERS_TO_LOAD = 512; + boolean DEFAULT_BONSAI_LIMIT_TRIE_LOGS_ENABLED = true; + long MINIMUM_BONSAI_TRIE_LOG_RETENTION_LIMIT = DEFAULT_BONSAI_MAX_LAYERS_TO_LOAD; + int DEFAULT_BONSAI_TRIE_LOG_PRUNING_WINDOW_SIZE = 5_000; + boolean DEFAULT_RECEIPT_COMPACTION_ENABLED = true; DataStorageConfiguration DEFAULT_CONFIG = + ImmutableDataStorageConfiguration.builder() + .dataStorageFormat(DataStorageFormat.BONSAI) + .bonsaiMaxLayersToLoad(DEFAULT_BONSAI_MAX_LAYERS_TO_LOAD) + .unstable(Unstable.DEFAULT) + .build(); + + DataStorageConfiguration DEFAULT_BONSAI_CONFIG = + ImmutableDataStorageConfiguration.builder() + .dataStorageFormat(DataStorageFormat.BONSAI) + .bonsaiMaxLayersToLoad(DEFAULT_BONSAI_MAX_LAYERS_TO_LOAD) + .build(); + + DataStorageConfiguration DEFAULT_BONSAI_PARTIAL_DB_CONFIG = + ImmutableDataStorageConfiguration.builder() + .dataStorageFormat(DataStorageFormat.BONSAI) + .bonsaiMaxLayersToLoad(DEFAULT_BONSAI_MAX_LAYERS_TO_LOAD) + .unstable(Unstable.DEFAULT_PARTIAL) + .build(); + + DataStorageConfiguration DEFAULT_FOREST_CONFIG = ImmutableDataStorageConfiguration.builder() .dataStorageFormat(DataStorageFormat.FOREST) .bonsaiMaxLayersToLoad(DEFAULT_BONSAI_MAX_LAYERS_TO_LOAD) @@ -35,6 +59,21 @@ public interface DataStorageConfiguration { Long getBonsaiMaxLayersToLoad(); + @Value.Default + default boolean getBonsaiLimitTrieLogsEnabled() { + return DEFAULT_BONSAI_LIMIT_TRIE_LOGS_ENABLED; + } + + @Value.Default + default int getBonsaiTrieLogPruningWindowSize() { + return DEFAULT_BONSAI_TRIE_LOG_PRUNING_WINDOW_SIZE; + } + + @Value.Default + default boolean getReceiptCompactionEnabled() { + return DEFAULT_RECEIPT_COMPACTION_ENABLED; + } + @Value.Default default Unstable getUnstable() { return Unstable.DEFAULT; @@ -43,27 +82,30 @@ default Unstable getUnstable() { @Value.Immutable interface Unstable { - boolean DEFAULT_BONSAI_TRIE_LOG_PRUNING_ENABLED = false; - long DEFAULT_BONSAI_TRIE_LOG_RETENTION_THRESHOLD = 512L; - long MINIMUM_BONSAI_TRIE_LOG_RETENTION_THRESHOLD = DEFAULT_BONSAI_TRIE_LOG_RETENTION_THRESHOLD; - int DEFAULT_BONSAI_TRIE_LOG_PRUNING_LIMIT = 30_000; + boolean DEFAULT_BONSAI_FULL_FLAT_DB_ENABLED = true; + boolean DEFAULT_BONSAI_CODE_USING_CODE_HASH_ENABLED = true; + + boolean DEFAULT_PARALLEL_TRX_ENABLED = false; DataStorageConfiguration.Unstable DEFAULT = ImmutableDataStorageConfiguration.Unstable.builder().build(); + DataStorageConfiguration.Unstable DEFAULT_PARTIAL = + ImmutableDataStorageConfiguration.Unstable.builder().bonsaiFullFlatDbEnabled(false).build(); + @Value.Default - default boolean getBonsaiTrieLogPruningEnabled() { - return DEFAULT_BONSAI_TRIE_LOG_PRUNING_ENABLED; + default boolean getBonsaiFullFlatDbEnabled() { + return DEFAULT_BONSAI_FULL_FLAT_DB_ENABLED; } @Value.Default - default long getBonsaiTrieLogRetentionThreshold() { - return DEFAULT_BONSAI_TRIE_LOG_RETENTION_THRESHOLD; + default boolean getBonsaiCodeStoredByCodeHashEnabled() { + return DEFAULT_BONSAI_CODE_USING_CODE_HASH_ENABLED; } @Value.Default - default int getBonsaiTrieLogPruningLimit() { - return DEFAULT_BONSAI_TRIE_LOG_PRUNING_LIMIT; + default boolean isParallelTxProcessingEnabled() { + return DEFAULT_PARALLEL_TRX_ENABLED; } } } diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DataStorageFormat.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DataStorageFormat.java deleted file mode 100644 index b97f79006ae..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DataStorageFormat.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * - */ - -package org.hyperledger.besu.ethereum.worldstate; - -public enum DataStorageFormat { - FOREST(1), // Original format. Store all tries - BONSAI(2); // New format. Store one trie, and trie logs to roll forward and backward. - - private final int databaseVersion; - - DataStorageFormat(final int databaseVersion) { - this.databaseVersion = databaseVersion; - } - - public int getDatabaseVersion() { - return databaseVersion; - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutablePrivateWorldStateUpdater.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutablePrivateWorldStateUpdater.java deleted file mode 100644 index e52319463cc..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutablePrivateWorldStateUpdater.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.worldstate; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.evm.account.Account; -import org.hyperledger.besu.evm.account.MutableAccount; -import org.hyperledger.besu.evm.worldstate.WorldUpdater; - -import java.util.Collection; -import java.util.Optional; - -// This class uses a public WorldUpdater and a private WorldUpdater to provide a -// MutableWorldStateUpdater that can read and write from the private world state and can read from -// the public world state, but cannot write to it. -public class DefaultMutablePrivateWorldStateUpdater implements WorldUpdater { - - protected final WorldUpdater publicWorldUpdater; - protected final WorldUpdater privateWorldUpdater; - - public DefaultMutablePrivateWorldStateUpdater( - final WorldUpdater publicWorldUpdater, final WorldUpdater privateWorldUpdater) { - this.publicWorldUpdater = publicWorldUpdater; - this.privateWorldUpdater = privateWorldUpdater; - } - - @Override - public MutableAccount createAccount(final Address address, final long nonce, final Wei balance) { - return privateWorldUpdater.createAccount(address, nonce, balance); - } - - @Override - public MutableAccount createAccount(final Address address) { - return privateWorldUpdater.createAccount(address); - } - - @Override - public MutableAccount getAccount(final Address address) { - final MutableAccount privateAccount = privateWorldUpdater.getAccount(address); - if (privateAccount != null && !privateAccount.isEmpty()) { - return privateAccount; - } - final MutableAccount publicAccount = publicWorldUpdater.getAccount(address); - if (publicAccount != null && !publicAccount.isEmpty()) { - publicAccount.becomeImmutable(); - return publicAccount; - } - return privateAccount; - } - - @Override - public void deleteAccount(final Address address) { - privateWorldUpdater.deleteAccount(address); - } - - @Override - public Collection getTouchedAccounts() { - return privateWorldUpdater.getTouchedAccounts(); - } - - @Override - public Collection

getDeletedAccountAddresses() { - return privateWorldUpdater.getDeletedAccountAddresses(); - } - - @Override - public void revert() { - privateWorldUpdater.revert(); - } - - @Override - public void commit() { - privateWorldUpdater.commit(); - } - - @Override - public Account get(final Address address) { - final Account privateAccount = privateWorldUpdater.get(address); - if (privateAccount != null && !privateAccount.isEmpty()) { - return privateAccount; - } - return publicWorldUpdater.get(address); - } - - @Override - public WorldUpdater updater() { - return this; - } - - @Override - public Optional parentUpdater() { - return privateWorldUpdater.parentUpdater(); - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutableWorldState.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutableWorldState.java deleted file mode 100644 index d50f702af31..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutableWorldState.java +++ /dev/null @@ -1,438 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.worldstate; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.MutableWorldState; -import org.hyperledger.besu.ethereum.rlp.RLP; -import org.hyperledger.besu.ethereum.rlp.RLPException; -import org.hyperledger.besu.ethereum.rlp.RLPInput; -import org.hyperledger.besu.ethereum.trie.MerkleTrie; -import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; -import org.hyperledger.besu.evm.account.Account; -import org.hyperledger.besu.evm.account.AccountStorageEntry; -import org.hyperledger.besu.evm.internal.EvmConfiguration; -import org.hyperledger.besu.evm.worldstate.AbstractWorldUpdater; -import org.hyperledger.besu.evm.worldstate.UpdateTrackingAccount; -import org.hyperledger.besu.evm.worldstate.WorldState; -import org.hyperledger.besu.evm.worldstate.WorldUpdater; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.NavigableMap; -import java.util.Objects; -import java.util.Optional; -import java.util.TreeMap; -import java.util.TreeSet; -import java.util.stream.Stream; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt256; - -public class DefaultMutableWorldState implements MutableWorldState { - - private final EvmConfiguration evmConfiguration; - private final WorldStateStorage worldStateStorage; - private final WorldStatePreimageStorage preimageStorage; - - private final MerkleTrie accountStateTrie; - private final Map> updatedStorageTries = new HashMap<>(); - private final Map updatedAccountCode = new HashMap<>(); - private final Map newStorageKeyPreimages = new HashMap<>(); - private final Map newAccountKeyPreimages = new HashMap<>(); - - public DefaultMutableWorldState( - final WorldStateStorage storage, - final WorldStatePreimageStorage preimageStorage, - final EvmConfiguration evmConfiguration) { - this(MerkleTrie.EMPTY_TRIE_NODE_HASH, storage, preimageStorage, evmConfiguration); - } - - public DefaultMutableWorldState( - final Bytes32 rootHash, - final WorldStateStorage worldStateStorage, - final WorldStatePreimageStorage preimageStorage, - final EvmConfiguration evmConfiguration) { - this.worldStateStorage = worldStateStorage; - this.accountStateTrie = newAccountStateTrie(rootHash); - this.preimageStorage = preimageStorage; - this.evmConfiguration = evmConfiguration; - } - - public DefaultMutableWorldState( - final WorldState worldState, final EvmConfiguration evmConfiguration) { - // TODO: this is an abstraction leak (and kind of incorrect in that we reuse the underlying - // storage), but the reason for this is that the accounts() method is unimplemented below and - // can't be until NC-754. - if (!(worldState instanceof DefaultMutableWorldState other)) { - throw new UnsupportedOperationException(); - } - - this.worldStateStorage = other.worldStateStorage; - this.preimageStorage = other.preimageStorage; - this.accountStateTrie = newAccountStateTrie(other.accountStateTrie.getRootHash()); - this.evmConfiguration = evmConfiguration; - } - - private MerkleTrie newAccountStateTrie(final Bytes32 rootHash) { - return new StoredMerklePatriciaTrie<>( - worldStateStorage::getAccountStateTrieNode, rootHash, b -> b, b -> b); - } - - private MerkleTrie newAccountStorageTrie(final Bytes32 rootHash) { - return new StoredMerklePatriciaTrie<>( - (location, hash) -> worldStateStorage.getAccountStorageTrieNode(null, location, hash), - rootHash, - b -> b, - b -> b); - } - - @Override - public Hash rootHash() { - return Hash.wrap(accountStateTrie.getRootHash()); - } - - @Override - public Hash frontierRootHash() { - return rootHash(); - } - - @Override - public Account get(final Address address) { - final Hash addressHash = address.addressHash(); - return accountStateTrie - .get(addressHash) - .map(bytes -> deserializeAccount(address, addressHash, bytes)) - .orElse(null); - } - - private WorldStateAccount deserializeAccount( - final Address address, final Hash addressHash, final Bytes encoded) throws RLPException { - final RLPInput in = RLP.input(encoded); - final StateTrieAccountValue accountValue = StateTrieAccountValue.readFrom(in); - return new WorldStateAccount(address, addressHash, accountValue); - } - - @Override - public WorldUpdater updater() { - return new Updater(this, evmConfiguration); - } - - @Override - public Stream streamAccounts(final Bytes32 startKeyHash, final int limit) { - return accountStateTrie.entriesFrom(startKeyHash, limit).entrySet().stream() - .map( - entry -> { - final Optional
address = getAccountTrieKeyPreimage(entry.getKey()); - final WorldStateAccount account = - deserializeAccount( - address.orElse(Address.ZERO), Hash.wrap(entry.getKey()), entry.getValue()); - return new StreamableAccount(address, account); - }); - } - - @Override - public int hashCode() { - return Objects.hashCode(rootHash()); - } - - @Override - public final boolean equals(final Object other) { - if (!(other instanceof DefaultMutableWorldState that)) { - return false; - } - - return this.rootHash().equals(that.rootHash()); - } - - @Override - public void persist(final BlockHeader blockHeader) { - final WorldStateStorage.Updater stateUpdater = worldStateStorage.updater(); - // Store updated code - for (final Bytes code : updatedAccountCode.values()) { - stateUpdater.putCode(null, code); - } - // Commit account storage tries - for (final MerkleTrie updatedStorage : updatedStorageTries.values()) { - updatedStorage.commit( - (location, hash, value) -> - stateUpdater.putAccountStorageTrieNode(null, location, hash, value)); - } - // Commit account updates - accountStateTrie.commit(stateUpdater::putAccountStateTrieNode); - - // Persist preimages - final WorldStatePreimageStorage.Updater preimageUpdater = preimageStorage.updater(); - newStorageKeyPreimages.forEach(preimageUpdater::putStorageTrieKeyPreimage); - newAccountKeyPreimages.forEach(preimageUpdater::putAccountTrieKeyPreimage); - - // Clear pending changes that we just flushed - updatedStorageTries.clear(); - updatedAccountCode.clear(); - newStorageKeyPreimages.clear(); - - // Push changes to underlying storage - preimageUpdater.commit(); - stateUpdater.commit(); - } - - private static UInt256 convertToUInt256(final Bytes value) { - // TODO: we could probably have an optimized method to decode a single scalar since it's used - // pretty often. - final RLPInput in = RLP.input(value); - return in.readUInt256Scalar(); - } - - private Optional
getAccountTrieKeyPreimage(final Bytes32 trieKey) { - return Optional.ofNullable(newAccountKeyPreimages.get(trieKey)) - .or(() -> preimageStorage.getAccountTrieKeyPreimage(trieKey)); - } - // An immutable class that represents an individual account as stored in - // the world state's underlying merkle patricia trie. - protected class WorldStateAccount implements Account { - - private final Address address; - private final Hash addressHash; - - final StateTrieAccountValue accountValue; - - // Lazily initialized since we don't always access storage. - private volatile MerkleTrie storageTrie; - - private WorldStateAccount( - final Address address, final Hash addressHash, final StateTrieAccountValue accountValue) { - - this.address = address; - this.addressHash = addressHash; - this.accountValue = accountValue; - } - - private MerkleTrie storageTrie() { - final MerkleTrie updatedTrie = updatedStorageTries.get(address); - if (updatedTrie != null) { - storageTrie = updatedTrie; - } - if (storageTrie == null) { - storageTrie = newAccountStorageTrie(getStorageRoot()); - } - return storageTrie; - } - - @Override - public Address getAddress() { - return address; - } - - @Override - public Hash getAddressHash() { - return addressHash; - } - - @Override - public long getNonce() { - return accountValue.getNonce(); - } - - @Override - public Wei getBalance() { - return accountValue.getBalance(); - } - - Hash getStorageRoot() { - return accountValue.getStorageRoot(); - } - - @Override - public Bytes getCode() { - final Bytes updatedCode = updatedAccountCode.get(address); - if (updatedCode != null) { - return updatedCode; - } - // No code is common, save the KV-store lookup. - final Hash codeHash = getCodeHash(); - if (codeHash.equals(Hash.EMPTY)) { - return Bytes.EMPTY; - } - return worldStateStorage.getCode(codeHash, null).orElse(Bytes.EMPTY); - } - - @Override - public boolean hasCode() { - return !getCode().isEmpty(); - } - - @Override - public Hash getCodeHash() { - return accountValue.getCodeHash(); - } - - @Override - public UInt256 getStorageValue(final UInt256 key) { - return storageTrie() - .get(Hash.hash(key)) - .map(DefaultMutableWorldState::convertToUInt256) - .orElse(UInt256.ZERO); - } - - @Override - public UInt256 getOriginalStorageValue(final UInt256 key) { - return getStorageValue(key); - } - - @Override - public NavigableMap storageEntriesFrom( - final Bytes32 startKeyHash, final int limit) { - final NavigableMap storageEntries = new TreeMap<>(); - storageTrie() - .entriesFrom(startKeyHash, limit) - .forEach( - (key, value) -> { - final AccountStorageEntry entry = - AccountStorageEntry.create( - convertToUInt256(value), key, getStorageTrieKeyPreimage(key)); - storageEntries.put(key, entry); - }); - return storageEntries; - } - - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append("AccountState").append("{"); - builder.append("address=").append(getAddress()).append(", "); - builder.append("nonce=").append(getNonce()).append(", "); - builder.append("balance=").append(getBalance()).append(", "); - builder.append("storageRoot=").append(getStorageRoot()).append(", "); - builder.append("codeHash=").append(getCodeHash()).append(", "); - return builder.append("}").toString(); - } - - private Optional getStorageTrieKeyPreimage(final Bytes32 trieKey) { - return Optional.ofNullable(newStorageKeyPreimages.get(trieKey)) - .or(() -> preimageStorage.getStorageTrieKeyPreimage(trieKey)); - } - } - - protected static class Updater - extends AbstractWorldUpdater { - - protected Updater( - final DefaultMutableWorldState world, final EvmConfiguration evmConfiguration) { - super(world, evmConfiguration); - } - - @Override - protected WorldStateAccount getForMutation(final Address address) { - final DefaultMutableWorldState wrapped = wrappedWorldView(); - final Hash addressHash = address.addressHash(); - return wrapped - .accountStateTrie - .get(addressHash) - .map(bytes -> wrapped.deserializeAccount(address, addressHash, bytes)) - .orElse(null); - } - - @Override - public Collection getTouchedAccounts() { - return new ArrayList<>(getUpdatedAccounts()); - } - - @Override - public Collection
getDeletedAccountAddresses() { - return new ArrayList<>(getDeletedAccounts()); - } - - @Override - public void revert() { - getDeletedAccounts().clear(); - getUpdatedAccounts().clear(); - } - - @Override - public void commit() { - final DefaultMutableWorldState wrapped = wrappedWorldView(); - - for (final Address address : getDeletedAccounts()) { - final Hash addressHash = address.addressHash(); - wrapped.accountStateTrie.remove(addressHash); - wrapped.updatedStorageTries.remove(address); - wrapped.updatedAccountCode.remove(address); - } - - for (final UpdateTrackingAccount updated : getUpdatedAccounts()) { - final WorldStateAccount origin = updated.getWrappedAccount(); - - // Save the code in key-value storage ... - Hash codeHash = origin == null ? Hash.EMPTY : origin.getCodeHash(); - if (updated.codeWasUpdated()) { - codeHash = Hash.hash(updated.getCode()); - wrapped.updatedAccountCode.put(updated.getAddress(), updated.getCode()); - } - // ...and storage in the account trie first. - final boolean freshState = origin == null || updated.getStorageWasCleared(); - Hash storageRoot = freshState ? Hash.EMPTY_TRIE_HASH : origin.getStorageRoot(); - if (freshState) { - wrapped.updatedStorageTries.remove(updated.getAddress()); - } - final Map updatedStorage = updated.getUpdatedStorage(); - if (!updatedStorage.isEmpty()) { - // Apply any storage updates - final MerkleTrie storageTrie = - freshState - ? wrapped.newAccountStorageTrie(Hash.EMPTY_TRIE_HASH) - : origin.storageTrie(); - wrapped.updatedStorageTries.put(updated.getAddress(), storageTrie); - final TreeSet> entries = - new TreeSet<>(Map.Entry.comparingByKey()); - entries.addAll(updatedStorage.entrySet()); - - for (final Map.Entry entry : entries) { - final UInt256 value = entry.getValue(); - final Hash keyHash = Hash.hash(entry.getKey()); - if (value.isZero()) { - storageTrie.remove(keyHash); - } else { - wrapped.newStorageKeyPreimages.put(keyHash, entry.getKey()); - storageTrie.put( - keyHash, RLP.encode(out -> out.writeBytes(entry.getValue().toMinimalBytes()))); - } - } - storageRoot = Hash.wrap(storageTrie.getRootHash()); - } - - // Save address preimage - wrapped.newAccountKeyPreimages.put(updated.getAddressHash(), updated.getAddress()); - // Lastly, save the new account. - final Bytes account = - serializeAccount(updated.getNonce(), updated.getBalance(), storageRoot, codeHash); - - wrapped.accountStateTrie.put(updated.getAddressHash(), account); - } - } - - private static Bytes serializeAccount( - final long nonce, final Wei balance, final Hash storageRoot, final Hash codeHash) { - final StateTrieAccountValue accountValue = - new StateTrieAccountValue(nonce, balance, storageRoot, codeHash); - return RLP.encode(accountValue::writeTo); - } - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DefaultWorldStateArchive.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DefaultWorldStateArchive.java deleted file mode 100644 index a29bd9b2ff4..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/DefaultWorldStateArchive.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * - */ -package org.hyperledger.besu.ethereum.worldstate; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.MutableWorldState; -import org.hyperledger.besu.ethereum.proof.WorldStateProof; -import org.hyperledger.besu.ethereum.proof.WorldStateProofProvider; -import org.hyperledger.besu.ethereum.trie.MerkleTrie; -import org.hyperledger.besu.evm.internal.EvmConfiguration; -import org.hyperledger.besu.evm.worldstate.WorldState; - -import java.util.List; -import java.util.Optional; -import java.util.function.Function; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.units.bigints.UInt256; - -public class DefaultWorldStateArchive implements WorldStateArchive { - private final WorldStateStorage worldStateStorage; - private final WorldStatePreimageStorage preimageStorage; - private final WorldStateProofProvider worldStateProof; - private final EvmConfiguration evmConfiguration; - - private static final Hash EMPTY_ROOT_HASH = Hash.wrap(MerkleTrie.EMPTY_TRIE_NODE_HASH); - - public DefaultWorldStateArchive( - final WorldStateStorage worldStateStorage, - final WorldStatePreimageStorage preimageStorage, - final EvmConfiguration evmConfiguration) { - this.worldStateStorage = worldStateStorage; - this.preimageStorage = preimageStorage; - this.worldStateProof = new WorldStateProofProvider(worldStateStorage); - this.evmConfiguration = evmConfiguration; - } - - @Override - public Optional get(final Hash rootHash, final Hash blockHash) { - return getMutable(rootHash, blockHash).map(state -> state); - } - - @Override - public boolean isWorldStateAvailable(final Hash rootHash, final Hash blockHash) { - return worldStateStorage.isWorldStateAvailable(rootHash, blockHash); - } - - @Override - public Optional getMutable( - final BlockHeader blockHeader, final boolean isPersistingState) { - return getMutable(blockHeader.getStateRoot(), blockHeader.getHash()); - } - - @Override - public Optional getMutable(final Hash rootHash, final Hash blockHash) { - if (!worldStateStorage.isWorldStateAvailable(rootHash, blockHash)) { - return Optional.empty(); - } - return Optional.of( - new DefaultMutableWorldState( - rootHash, worldStateStorage, preimageStorage, evmConfiguration)); - } - - @Override - public MutableWorldState getMutable() { - return getMutable(EMPTY_ROOT_HASH, null).get(); - } - - @Override - public void resetArchiveStateTo(final BlockHeader blockHeader) { - // ignore for forest - } - - @Override - public Optional getNodeData(final Hash hash) { - // query by location is not supported, only query by content - return worldStateStorage.getNodeData(null, hash); - } - - public WorldStateStorage getWorldStateStorage() { - return worldStateStorage; - } - - @Override - public Optional getAccountProof( - final BlockHeader blockHeader, - final Address accountAddress, - final List accountStorageKeys, - final Function, ? extends Optional> mapper) { - return mapper.apply( - worldStateProof.getAccountProof( - blockHeader.getStateRoot(), accountAddress, accountStorageKeys)); - } - - @Override - public void close() { - // no op - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/FlatDbMode.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/FlatDbMode.java index 89be8acf229..4415ccb8251 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/FlatDbMode.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/FlatDbMode.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.worldstate; diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/MarkSweepPruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/MarkSweepPruner.java deleted file mode 100644 index 136e8b01445..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/MarkSweepPruner.java +++ /dev/null @@ -1,310 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.worldstate; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.chain.MutableBlockchain; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.rlp.RLP; -import org.hyperledger.besu.ethereum.trie.MerkleTrie; -import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; -import org.hyperledger.besu.metrics.BesuMetricCategory; -import org.hyperledger.besu.metrics.ObservableMetricsSystem; -import org.hyperledger.besu.plugin.services.metrics.Counter; -import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; -import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; - -import java.util.Collection; -import java.util.Collections; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.LinkedBlockingDeque; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.function.Function; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Stopwatch; -import com.google.common.util.concurrent.ThreadFactoryBuilder; -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class MarkSweepPruner { - - private static final Logger LOG = LoggerFactory.getLogger(MarkSweepPruner.class); - private static final byte[] IN_USE = Bytes.of(1).toArrayUnsafe(); - - private static final int DEFAULT_OPS_PER_TRANSACTION = 10_000; - private static final int MAX_MARKING_THREAD_POOL_SIZE = 2; - - private final int operationsPerTransaction; - private final WorldStateStorage worldStateStorage; - private final MutableBlockchain blockchain; - private final KeyValueStorage markStorage; - private final Counter markedNodesCounter; - private final Counter markOperationCounter; - private final Counter sweepOperationCounter; - private final Counter sweptNodesCounter; - private final Stopwatch markStopwatch; - private volatile long nodeAddedListenerId; - private final ReadWriteLock pendingMarksLock = new ReentrantReadWriteLock(); - private final Set pendingMarks = Collections.newSetFromMap(new ConcurrentHashMap<>()); - - public MarkSweepPruner( - final WorldStateStorage worldStateStorage, - final MutableBlockchain blockchain, - final KeyValueStorage markStorage, - final ObservableMetricsSystem metricsSystem) { - this(worldStateStorage, blockchain, markStorage, metricsSystem, DEFAULT_OPS_PER_TRANSACTION); - } - - public MarkSweepPruner( - final WorldStateStorage worldStateStorage, - final MutableBlockchain blockchain, - final KeyValueStorage markStorage, - final ObservableMetricsSystem metricsSystem, - final int operationsPerTransaction) { - this.worldStateStorage = worldStateStorage; - this.markStorage = markStorage; - this.blockchain = blockchain; - this.operationsPerTransaction = operationsPerTransaction; - - markedNodesCounter = - metricsSystem.createCounter( - BesuMetricCategory.PRUNER, - "marked_nodes_total", - "Total number of nodes marked as in use"); - markOperationCounter = - metricsSystem.createCounter( - BesuMetricCategory.PRUNER, - "mark_operations_total", - "Total number of mark operations performed"); - - sweptNodesCounter = - metricsSystem.createCounter( - BesuMetricCategory.PRUNER, "swept_nodes_total", "Total number of unused nodes removed"); - sweepOperationCounter = - metricsSystem.createCounter( - BesuMetricCategory.PRUNER, - "sweep_operations_total", - "Total number of sweep operations performed"); - - markStopwatch = Stopwatch.createUnstarted(); - metricsSystem.createLongGauge( - BesuMetricCategory.PRUNER, - "mark_time_duration", - "Cumulative number of seconds spent marking the state trie across all pruning cycles", - () -> markStopwatch.elapsed(TimeUnit.SECONDS)); - - LOG.debug("Using {} pruner threads", MAX_MARKING_THREAD_POOL_SIZE); - } - - public void prepare() { - // Optimization for the case where the previous cycle was interrupted (like the node was shut - // down). If the previous cycle was interrupted, there will be marks in the mark storage from - // last time, causing the first sweep to be smaller than it needs to be. - clearMarks(); - - nodeAddedListenerId = worldStateStorage.addNodeAddedListener(this::markNodes); - } - - /** - * This is a parallel mark implementation. - * - *

The parallel task production is by sub-trie, so calling `visitAll` on a root node will - * eventually spawn up to 16 tasks (for a hexary trie). - * - *

If we marked each sub-trie in its own thread, with no common queue of tasks, our mark speed - * would be limited by the sub-trie with the maximum number of nodes. In practice for the Ethereum - * mainnet, we see a large imbalance in sub-trie size so without a common task pool the time in - * which there is only 1 thread left marking its big sub-trie would be substantial. - * - *

If we were to leave all threads to produce mark tasks before starting to mark, we would run - * out of memory quickly. - * - *

If we were to have a constant number of threads producing the mark tasks with the others - * consuming them, we would have to optimize the production/consumption balance. - * - *

To get the best of both worlds, the marking executor has a {@link - * ThreadPoolExecutor.CallerRunsPolicy} which causes the producing tasks to essentially consume - * their own mark task immediately when the task queue is full. The resulting behavior is threads - * that mark their own sub-trie until they finish that sub-trie, at which point they switch to - * marking the sub-trie tasks produced by another thread. - * - * @param rootHash The root hash of the whole state trie. Roots of storage tries will be - * discovered though traversal. - */ - public void mark(final Hash rootHash) { - markOperationCounter.inc(); - markStopwatch.start(); - final ExecutorService markingExecutorService = - new ThreadPoolExecutor( - 0, - MAX_MARKING_THREAD_POOL_SIZE, - 5L, - TimeUnit.SECONDS, - new LinkedBlockingDeque<>(16), - new ThreadFactoryBuilder() - .setDaemon(true) - .setPriority(Thread.MIN_PRIORITY) - .setNameFormat(this.getClass().getSimpleName() + "-%d") - .build(), - new ThreadPoolExecutor.CallerRunsPolicy()); - createStateTrie(rootHash) - .visitAll( - node -> { - markNode(node.getHash()); - node.getValue() - .ifPresent(value -> processAccountState(value, markingExecutorService)); - }, - markingExecutorService) - .join() /* This will block on all the marking tasks to be _produced_ but doesn't guarantee that the marking tasks have been completed. */; - markingExecutorService.shutdown(); - try { - // This ensures that the marking tasks complete. - markingExecutorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); - } catch (InterruptedException e) { - LOG.info("Interrupted while marking", e); - } - markStopwatch.stop(); - LOG.debug("Completed marking used nodes for pruning"); - } - - public void sweepBefore(final long markedBlockNumber) { - sweepOperationCounter.inc(); - LOG.debug("Sweeping unused nodes"); - // Sweep state roots first, walking backwards until we get to a state root that isn't in the - // storage - long prunedNodeCount = 0; - WorldStateStorage.Updater updater = worldStateStorage.updater(); - for (long blockNumber = markedBlockNumber - 1; blockNumber >= 0; blockNumber--) { - final BlockHeader blockHeader = blockchain.getBlockHeader(blockNumber).get(); - final Hash candidateStateRootHash = blockHeader.getStateRoot(); - if (!worldStateStorage.isWorldStateAvailable(candidateStateRootHash, null)) { - break; - } - - if (!isMarked(candidateStateRootHash)) { - updater.removeAccountStateTrieNode(null, candidateStateRootHash); - prunedNodeCount++; - if (prunedNodeCount % operationsPerTransaction == 0) { - updater.commit(); - updater = worldStateStorage.updater(); - } - } - } - - updater.commit(); - // Sweep non-state-root nodes - prunedNodeCount += worldStateStorage.prune(this::isMarked); - sweptNodesCounter.inc(prunedNodeCount); - clearMarks(); - LOG.debug("Completed sweeping unused nodes"); - } - - public void cleanup() { - worldStateStorage.removeNodeAddedListener(nodeAddedListenerId); - clearMarks(); - } - - public void clearMarks() { - markStorage.clear(); - pendingMarks.clear(); - } - - private boolean isMarked(final Bytes32 key) { - return pendingMarks.contains(key) || markStorage.containsKey(key.toArrayUnsafe()); - } - - private boolean isMarked(final byte[] key) { - return pendingMarks.contains(Bytes32.wrap(key)) || markStorage.containsKey(key); - } - - private MerkleTrie createStateTrie(final Bytes32 rootHash) { - return new StoredMerklePatriciaTrie<>( - worldStateStorage::getAccountStateTrieNode, - rootHash, - Function.identity(), - Function.identity()); - } - - private MerkleTrie createStorageTrie(final Bytes32 rootHash) { - return new StoredMerklePatriciaTrie<>( - (location, hash) -> worldStateStorage.getAccountStorageTrieNode(null, location, hash), - rootHash, - Function.identity(), - Function.identity()); - } - - private void processAccountState(final Bytes value, final ExecutorService executorService) { - final StateTrieAccountValue accountValue = StateTrieAccountValue.readFrom(RLP.input(value)); - markNode(accountValue.getCodeHash()); - - createStorageTrie(accountValue.getStorageRoot()) - .visitAll(storageNode -> markNode(storageNode.getHash()), executorService); - } - - @VisibleForTesting - void markNode(final Bytes32 hash) { - markThenMaybeFlush(() -> pendingMarks.add(hash), 1); - } - - private void markNodes(final Collection nodeHashes) { - markThenMaybeFlush(() -> pendingMarks.addAll(nodeHashes), nodeHashes.size()); - } - - private void markThenMaybeFlush(final Runnable nodeMarker, final int numberOfNodes) { - // We use the read lock here because pendingMarks is threadsafe and we want to allow all the - // marking threads access simultaneously. - final Lock markLock = pendingMarksLock.readLock(); - markLock.lock(); - try { - nodeMarker.run(); - } finally { - markLock.unlock(); - } - markedNodesCounter.inc(numberOfNodes); - - // However, when the size of pendingMarks grows too large, we want all the threads to stop - // adding because we're going to clear the set. - // Therefore, we need to take out a write lock. - if (pendingMarks.size() >= operationsPerTransaction) { - final Lock flushLock = pendingMarksLock.writeLock(); - flushLock.lock(); - try { - // Check once again that the condition holds. If it doesn't, that means another thread - // already flushed them. - if (pendingMarks.size() >= operationsPerTransaction) { - flushPendingMarks(); - } - } finally { - flushLock.unlock(); - } - } - } - - private void flushPendingMarks() { - final KeyValueStorageTransaction transaction = markStorage.startTransaction(); - pendingMarks.forEach(node -> transaction.put(node.toArrayUnsafe(), IN_USE)); - transaction.commit(); - pendingMarks.clear(); - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/PeerTrieNodeFinder.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/PeerTrieNodeFinder.java index 549157860bd..8d1b340f0c2 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/PeerTrieNodeFinder.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/PeerTrieNodeFinder.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/Pruner.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/Pruner.java deleted file mode 100644 index 04949b55e12..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/Pruner.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.worldstate; - -import static com.google.common.base.Preconditions.checkArgument; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.chain.BlockAddedEvent; -import org.hyperledger.besu.ethereum.chain.Blockchain; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.trie.MerkleTrieException; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.util.concurrent.ThreadFactoryBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class Pruner { - - private static final Logger LOG = LoggerFactory.getLogger(Pruner.class); - - private final MarkSweepPruner pruningStrategy; - private final Blockchain blockchain; - private Long blockAddedObserverId; - private final long blocksRetained; - private final AtomicReference pruningPhase = - new AtomicReference<>(PruningPhase.IDLE); - private volatile long markBlockNumber = 0; - private volatile BlockHeader markedBlockHeader; - private final long blockConfirmations; - - private final AtomicReference state = new AtomicReference<>(State.IDLE); - private final ExecutorService executorService; - - @VisibleForTesting - Pruner( - final MarkSweepPruner pruningStrategy, - final Blockchain blockchain, - final PrunerConfiguration prunerConfiguration, - final ExecutorService executorService) { - this.pruningStrategy = pruningStrategy; - this.blockchain = blockchain; - this.executorService = executorService; - this.blocksRetained = prunerConfiguration.getBlocksRetained(); - this.blockConfirmations = prunerConfiguration.getBlockConfirmations(); - checkArgument( - blockConfirmations >= 0 && blockConfirmations < blocksRetained, - "blockConfirmations and blocksRetained must be non-negative. blockConfirmations must be less than blockRetained."); - } - - public Pruner( - final MarkSweepPruner pruningStrategy, - final Blockchain blockchain, - final PrunerConfiguration prunerConfiguration) { - this( - pruningStrategy, - blockchain, - prunerConfiguration, - // This is basically the out-of-the-box `Executors.newSingleThreadExecutor` except we want - // the `corePoolSize` to be 0 - new ThreadPoolExecutor( - 0, - 1, - 0L, - TimeUnit.MILLISECONDS, - new LinkedBlockingQueue<>(), - new ThreadFactoryBuilder() - .setDaemon(true) - .setPriority(Thread.MIN_PRIORITY) - .setNameFormat("StatePruning-%d") - .build())); - } - - public void start() { - execute( - () -> { - if (state.compareAndSet(State.IDLE, State.RUNNING)) { - LOG.info("Starting Pruner."); - pruningStrategy.prepare(); - blockAddedObserverId = blockchain.observeBlockAdded(this::handleNewBlock); - } - }); - } - - public void stop() { - if (state.compareAndSet(State.RUNNING, State.STOPPED)) { - LOG.info("Stopping Pruner."); - pruningStrategy.cleanup(); - blockchain.removeObserver(blockAddedObserverId); - executorService.shutdownNow(); - } - } - - public void awaitStop() throws InterruptedException { - if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) { - LOG.error("Failed to shutdown Pruner executor service."); - } - } - - private void handleNewBlock(final BlockAddedEvent event) { - if (!event.isNewCanonicalHead()) { - return; - } - - final long blockNumber = event.getBlock().getHeader().getNumber(); - if (pruningPhase.compareAndSet( - PruningPhase.IDLE, PruningPhase.MARK_BLOCK_CONFIRMATIONS_AWAITING)) { - markBlockNumber = blockNumber; - } else if (blockNumber >= markBlockNumber + blockConfirmations - && pruningPhase.compareAndSet( - PruningPhase.MARK_BLOCK_CONFIRMATIONS_AWAITING, PruningPhase.MARKING)) { - markedBlockHeader = blockchain.getBlockHeader(markBlockNumber).get(); - mark(markedBlockHeader); - } else if (blockNumber >= markBlockNumber + blocksRetained - && blockchain.blockIsOnCanonicalChain(markedBlockHeader.getHash()) - && pruningPhase.compareAndSet(PruningPhase.MARKING_COMPLETE, PruningPhase.SWEEPING)) { - sweep(); - } - } - - private void mark(final BlockHeader header) { - final Hash stateRoot = header.getStateRoot(); - LOG.info( - "Begin marking used nodes for pruning. Block number: {} State root: {}", - markBlockNumber, - stateRoot); - execute( - () -> { - pruningStrategy.mark(stateRoot); - pruningPhase.compareAndSet(PruningPhase.MARKING, PruningPhase.MARKING_COMPLETE); - }); - } - - private void sweep() { - LOG.info( - "Begin sweeping unused nodes for pruning. Keeping full state for blocks {} to {}", - markBlockNumber, - markBlockNumber + blocksRetained); - execute( - () -> { - pruningStrategy.sweepBefore(markBlockNumber); - pruningPhase.compareAndSet(PruningPhase.SWEEPING, PruningPhase.IDLE); - }); - } - - private void execute(final Runnable action) { - try { - executorService.execute(action); - } catch (final MerkleTrieException mte) { - LOG.error( - "An unrecoverable error occurred while pruning. The database directory must be deleted and resynced.", - mte); - System.exit(1); - } catch (final Exception e) { - LOG.error( - "An unexpected error occurred in the {} pruning phase: {}. Reattempting.", - getPruningPhase(), - e.getMessage()); - pruningStrategy.clearMarks(); - pruningPhase.set(PruningPhase.IDLE); - } - } - - PruningPhase getPruningPhase() { - return pruningPhase.get(); - } - - enum PruningPhase { - IDLE, - MARK_BLOCK_CONFIRMATIONS_AWAITING, - MARKING, - MARKING_COMPLETE, - SWEEPING; - } - - private enum State { - IDLE, - RUNNING, - STOPPED - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/PrunerConfiguration.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/PrunerConfiguration.java deleted file mode 100644 index f2336ee9a3f..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/PrunerConfiguration.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.worldstate; - -public class PrunerConfiguration { - public static final int DEFAULT_PRUNING_BLOCKS_RETAINED = 1024; - public static final int DEFAULT_PRUNING_BLOCK_CONFIRMATIONS = 10; - - private final int blocksRetainedBeforeSweeping; - private final int blockConfirmationsBeforeMarking; - - public PrunerConfiguration( - final int blockConfirmationsBeforeMarking, final int blocksRetainedBeforeSweeping) { - this.blockConfirmationsBeforeMarking = blockConfirmationsBeforeMarking; - this.blocksRetainedBeforeSweeping = blocksRetainedBeforeSweeping; - } - - public static PrunerConfiguration getDefault() { - return new PrunerConfiguration( - DEFAULT_PRUNING_BLOCK_CONFIRMATIONS, DEFAULT_PRUNING_BLOCKS_RETAINED); - } - - public int getBlocksRetained() { - return blocksRetainedBeforeSweeping; - } - - public int getBlockConfirmations() { - return blockConfirmationsBeforeMarking; - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/WorldStateArchive.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/WorldStateArchive.java index 1816063243e..23859ac3961 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/WorldStateArchive.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/WorldStateArchive.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -19,7 +19,6 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.proof.WorldStateProof; -import org.hyperledger.besu.ethereum.trie.MerkleTrie; import org.hyperledger.besu.evm.worldstate.WorldState; import java.io.Closeable; @@ -31,8 +30,6 @@ import org.apache.tuweni.units.bigints.UInt256; public interface WorldStateArchive extends Closeable { - Hash EMPTY_ROOT_HASH = Hash.wrap(MerkleTrie.EMPTY_TRIE_NODE_HASH); - Optional get(Hash rootHash, Hash blockHash); boolean isWorldStateAvailable(Hash rootHash, Hash blockHash); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/WorldStateKeyValueStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/WorldStateKeyValueStorage.java new file mode 100644 index 00000000000..07fa922c126 --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/WorldStateKeyValueStorage.java @@ -0,0 +1,38 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.worldstate; + +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; + +import java.util.Collection; + +import org.apache.tuweni.bytes.Bytes32; + +public interface WorldStateKeyValueStorage { + + DataStorageFormat getDataStorageFormat(); + + Updater updater(); + + void clear(); + + interface NodesAddedListener { + void onNodesAdded(Collection nodeHash); + } + + interface Updater { + void commit(); + } +} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/WorldStateStorage.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/WorldStateStorage.java deleted file mode 100644 index 3bb6a0dfe78..00000000000 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/WorldStateStorage.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.worldstate; - -import org.hyperledger.besu.datatypes.Hash; - -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.Optional; -import java.util.function.Predicate; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; - -public interface WorldStateStorage { - - Optional getCode(Bytes32 codeHash, Hash accountHash); - - Optional getAccountStateTrieNode(Bytes location, Bytes32 nodeHash); - - Optional getAccountStorageTrieNode(Hash accountHash, Bytes location, Bytes32 nodeHash); - - /** - * This method allows obtaining a TrieNode in an unsafe manner, without verifying the consistency - * of the obtained node. Checks such as node hash verification are not performed here. - * - * @param key of the trie node - * @return value of the trie node - */ - Optional getTrieNodeUnsafe(Bytes key); - - Optional getNodeData(Bytes location, Bytes32 hash); - - FlatDbMode getFlatDbMode(); - - boolean isWorldStateAvailable(Bytes32 rootHash, Hash blockHash); - - default boolean contains(final Bytes32 hash) { - // we don't have location info - return getNodeData(null, hash).isPresent(); - } - - /** - * Streams flat accounts within a specified range. - * - * @param startKeyHash The start key hash of the range. - * @param endKeyHash The end key hash of the range. - * @param max The maximum number of entries to stream. - * @return A map of flat accounts. (Empty map in this default implementation) - */ - default Map streamFlatAccounts( - final Bytes startKeyHash, final Bytes32 endKeyHash, final long max) { - return Collections.emptyMap(); - } - - /** - * Streams flat storages within a specified range. - * - * @param accountHash The account hash. - * @param startKeyHash The start key hash of the range. - * @param endKeyHash The end key hash of the range. - * @param max The maximum number of entries to stream. - * @return A map of flat storages. (Empty map in this default implementation) - */ - default Map streamFlatStorages( - final Hash accountHash, final Bytes startKeyHash, final Bytes32 endKeyHash, final long max) { - return Collections.emptyMap(); - } - - DataStorageFormat getDataStorageFormat(); - - void clear(); - - void clearTrieLog(); - - void clearFlatDatabase(); - - Updater updater(); - - long prune(Predicate inUseCheck); - - long addNodeAddedListener(NodesAddedListener listener); - - void removeNodeAddedListener(long id); - - interface Updater { - - Updater putCode(Hash accountHash, Bytes32 nodeHash, Bytes code); - - default Updater putCode(final Hash accountHash, final Bytes code) { - // Skip the hash calculation for empty code - final Hash codeHash = code.size() == 0 ? Hash.EMPTY : Hash.hash(code); - return putCode(accountHash, codeHash, code); - } - - Updater saveWorldState(Bytes blockHash, Bytes32 nodeHash, Bytes node); - - Updater putAccountStateTrieNode(Bytes location, Bytes32 nodeHash, Bytes node); - - Updater removeAccountStateTrieNode(Bytes location, Bytes32 nodeHash); - - Updater putAccountStorageTrieNode( - Hash accountHash, Bytes location, Bytes32 nodeHash, Bytes node); - - void commit(); - - void rollback(); - } - - interface NodesAddedListener { - void onNodesAdded(Collection nodeHash); - } -} diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/WorldStateStorageCoordinator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/WorldStateStorageCoordinator.java new file mode 100644 index 00000000000..710d5ea15df --- /dev/null +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/worldstate/WorldStateStorageCoordinator.java @@ -0,0 +1,160 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.worldstate; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; + +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +public class WorldStateStorageCoordinator { + private final WorldStateKeyValueStorage worldStateKeyValueStorage; + + public WorldStateStorageCoordinator(final WorldStateKeyValueStorage worldStateKeyValueStorage) { + this.worldStateKeyValueStorage = worldStateKeyValueStorage; + } + + public DataStorageFormat getDataStorageFormat() { + return worldStateKeyValueStorage.getDataStorageFormat(); + } + + public boolean isWorldStateAvailable(final Bytes32 nodeHash, final Hash blockHash) { + return applyForStrategy( + bonsai -> bonsai.isWorldStateAvailable(nodeHash, blockHash), + forest -> forest.isWorldStateAvailable(nodeHash)); + } + + public Optional getTrieNodeUnsafe(final Bytes key) { + return applyForStrategy( + bonsai -> bonsai.getTrieNodeUnsafe(key), + forest -> forest.getAccountStateTrieNode(Bytes32.wrap(key))); + } + + public Optional getAccountStateTrieNode(final Bytes location, final Bytes32 nodeHash) { + return applyForStrategy( + bonsai -> bonsai.getAccountStateTrieNode(location, nodeHash), + forest -> forest.getAccountStateTrieNode(nodeHash)); + } + + public Optional getAccountStorageTrieNode( + final Hash accountHash, final Bytes location, final Bytes32 nodeHash) { + return applyForStrategy( + bonsai -> bonsai.getAccountStorageTrieNode(accountHash, location, nodeHash), + forest -> forest.getAccountStorageTrieNode(nodeHash)); + } + + public Optional getCode(final Hash codeHash, final Hash accountHash) { + return applyForStrategy( + bonsai -> bonsai.getCode(codeHash, accountHash), forest -> forest.getCode(codeHash)); + } + + @SuppressWarnings("unchecked") + public STRATEGY getStrategy( + final Class strategyClass) { + return (STRATEGY) worldStateKeyValueStorage; + } + + public boolean isMatchingFlatMode(final FlatDbMode flatDbMode) { + if (getDataStorageFormat().equals(DataStorageFormat.BONSAI)) { + final BonsaiWorldStateKeyValueStorage bonsaiWorldStateStorageStrategy = + (BonsaiWorldStateKeyValueStorage) worldStateKeyValueStorage(); + return bonsaiWorldStateStorageStrategy.getFlatDbMode().equals(flatDbMode); + } + return false; + } + + public void applyOnMatchingFlatMode( + final FlatDbMode flatDbMode, final Consumer onStrategy) { + applyOnMatchingStrategy( + DataStorageFormat.BONSAI, + worldStateKeyValueStorage -> { + final BonsaiWorldStateKeyValueStorage bonsaiWorldStateStorageStrategy = + (BonsaiWorldStateKeyValueStorage) worldStateKeyValueStorage(); + if (bonsaiWorldStateStorageStrategy.getFlatDbMode().equals(flatDbMode)) { + onStrategy.accept(bonsaiWorldStateStorageStrategy); + } + }); + } + + public void applyWhenFlatModeEnabled(final Consumer onStrategy) { + applyOnMatchingStrategy( + DataStorageFormat.BONSAI, + worldStateKeyValueStorage -> { + final BonsaiWorldStateKeyValueStorage bonsaiWorldStateStorageStrategy = + (BonsaiWorldStateKeyValueStorage) worldStateKeyValueStorage(); + if (!bonsaiWorldStateStorageStrategy.getFlatDbMode().equals(FlatDbMode.NO_FLATTENED)) { + onStrategy.accept(bonsaiWorldStateStorageStrategy); + } + }); + } + + public void applyOnMatchingStrategy( + final DataStorageFormat dataStorageFormat, + final Consumer onStrategy) { + if (getDataStorageFormat().equals(dataStorageFormat)) { + onStrategy.accept(worldStateKeyValueStorage()); + } + } + + public RESPONSE applyForStrategy( + final Function onBonsai, + final Function onForest) { + if (getDataStorageFormat().equals(DataStorageFormat.BONSAI)) { + return onBonsai.apply(((BonsaiWorldStateKeyValueStorage) worldStateKeyValueStorage())); + } else { + return onForest.apply(((ForestWorldStateKeyValueStorage) worldStateKeyValueStorage())); + } + } + + public void consumeForStrategy( + final Consumer onBonsai, + final Consumer onForest) { + if (getDataStorageFormat().equals(DataStorageFormat.BONSAI)) { + onBonsai.accept(((BonsaiWorldStateKeyValueStorage) worldStateKeyValueStorage())); + } else { + onForest.accept(((ForestWorldStateKeyValueStorage) worldStateKeyValueStorage())); + } + } + + public static void applyForStrategy( + final WorldStateKeyValueStorage.Updater updater, + final Consumer onBonsai, + final Consumer onForest) { + if (updater instanceof BonsaiWorldStateKeyValueStorage.Updater) { + onBonsai.accept(((BonsaiWorldStateKeyValueStorage.Updater) updater)); + } else if (updater instanceof ForestWorldStateKeyValueStorage.Updater) { + onForest.accept(((ForestWorldStateKeyValueStorage.Updater) updater)); + } + } + + public WorldStateKeyValueStorage.Updater updater() { + return worldStateKeyValueStorage().updater(); + } + + public void clear() { + worldStateKeyValueStorage.clear(); + } + + public WorldStateKeyValueStorage worldStateKeyValueStorage() { + return worldStateKeyValueStorage; + } +} diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlobTestFixture.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlobTestFixture.java index c4fa3b2852b..21e8630c84f 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlobTestFixture.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlobTestFixture.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.core; import static org.assertj.core.api.Assertions.fail; diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java index 3b2b8e30958..ea62d7f1fa1 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockDataGenerator.java @@ -303,7 +303,7 @@ public BlockHeader header(final long number, final BlockBody body, final BlockOp .mixHash(hash()) .nonce(blockNonce) .withdrawalsRoot(options.getWithdrawalsRoot(null)) - .depositsRoot(options.getDepositsRoot(null)) + .requestsRoot(options.getRequestsRoot(null)) .blockHeaderFunctions( options.getBlockHeaderFunctions(new MainnetBlockHeaderFunctions())); options.getBaseFee(Optional.of(Wei.of(uint256(2)))).ifPresent(blockHeaderBuilder::baseFee); @@ -332,7 +332,7 @@ public BlockBody body(final BlockOptions options) { options.getTransactions(defaultTxs), ommers, options.getWithdrawals(Optional.empty()), - options.getDeposits(Optional.empty())); + options.getRequests(Optional.empty())); } private BlockHeader ommer() { @@ -376,6 +376,7 @@ public Transaction transaction( case EIP1559 -> eip1559Transaction(payload, to); case ACCESS_LIST -> accessListTransaction(payload, to); case BLOB -> blobTransaction(payload, to); + case DELEGATE_CODE -> null; // no default, all types accounted for. }; } @@ -638,7 +639,7 @@ public static class BlockOptions { private final List ommers = new ArrayList<>(); private Optional>> withdrawals = Optional.empty(); - private Optional>> deposits = Optional.empty(); + private Optional>> requests = Optional.empty(); private Optional extraData = Optional.empty(); private Optional blockHeaderFunctions = Optional.empty(); private Optional receiptsRoot = Optional.empty(); @@ -654,7 +655,7 @@ public static class BlockOptions { private Optional> maybeBaseFee = Optional.empty(); private Optional withdrawalsRoot = Optional.empty(); - private Optional depositsRoot = Optional.empty(); + private Optional requestsRoot = Optional.empty(); private Optional> maybeMaxFeePerBlobGas = Optional.empty(); @@ -723,12 +724,12 @@ public Optional> getWithdrawals( return withdrawals.orElse(defaultValue); } - public Hash getDepositsRoot(final Hash defaultValue) { - return depositsRoot.orElse(defaultValue); + public Hash getRequestsRoot(final Hash defaultValue) { + return requestsRoot.orElse(defaultValue); } - public Optional> getDeposits(final Optional> defaultValue) { - return deposits.orElse(defaultValue); + public Optional> getRequests(final Optional> defaultValue) { + return requests.orElse(defaultValue); } public boolean hasTransactions() { @@ -758,8 +759,8 @@ public BlockOptions setWithdrawals(final Optional> withdrawals) return this; } - public BlockOptions setDeposits(final Optional> deposits) { - this.deposits = Optional.of(deposits); + public BlockOptions setRequests(final Optional> requests) { + this.requests = Optional.of(requests); return this; } @@ -851,8 +852,8 @@ public BlockOptions setWithdrawalsRoot(final Hash withdrawalsRoot) { return this; } - public BlockOptions setDepositsRoot(final Hash depositsRoot) { - this.depositsRoot = Optional.of(depositsRoot); + public BlockOptions setRequestsRoot(final Hash requestsRoot) { + this.requestsRoot = Optional.of(requestsRoot); return this; } diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockHeaderTestFixture.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockHeaderTestFixture.java index 0a379c9d51e..44a4ffd0603 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockHeaderTestFixture.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockHeaderTestFixture.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -50,7 +50,7 @@ public class BlockHeaderTestFixture { private Hash mixHash = Hash.EMPTY; private long nonce = 0; private Optional withdrawalsRoot = Optional.empty(); - private Optional depositsRoot = Optional.empty(); + private Optional requestsRoot = Optional.empty(); private BlockHeaderFunctions blockHeaderFunctions = new MainnetBlockHeaderFunctions(); private Optional excessBlobGas = Optional.empty(); private Optional blobGasUsed = Optional.empty(); @@ -78,7 +78,7 @@ public BlockHeader buildHeader() { withdrawalsRoot.ifPresent(builder::withdrawalsRoot); excessBlobGas.ifPresent(builder::excessBlobGas); blobGasUsed.ifPresent(builder::blobGasUsed); - depositsRoot.ifPresent(builder::depositsRoot); + requestsRoot.ifPresent(builder::requestsRoot); parentBeaconBlockRoot.ifPresent(builder::parentBeaconBlockRoot); builder.blockHeaderFunctions(blockHeaderFunctions); @@ -175,8 +175,8 @@ public BlockHeaderTestFixture withdrawalsRoot(final Hash withdrawalsRoot) { return this; } - public BlockHeaderTestFixture depositsRoot(final Hash depositsRoot) { - this.depositsRoot = Optional.ofNullable(depositsRoot); + public BlockHeaderTestFixture requestsRoot(final Hash requestsRoot) { + this.requestsRoot = Optional.ofNullable(requestsRoot); return this; } diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockchainSetupUtil.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockchainSetupUtil.java index 5e43198e25e..551f593e825 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockchainSetupUtil.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/BlockchainSetupUtil.java @@ -23,6 +23,7 @@ import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.ethereum.ConsensusContext; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.GenesisState; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; @@ -35,23 +36,19 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions; import org.hyperledger.besu.ethereum.util.RawBlockIterator; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.testutil.BlockTestUtil; import org.hyperledger.besu.testutil.BlockTestUtil.ChainResources; import java.io.IOException; import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Optional; - -import com.google.common.io.Resources; public class BlockchainSetupUtil { private final GenesisState genesisState; @@ -142,7 +139,12 @@ public static BlockchainSetupUtil createForEthashChain( private static ProtocolSchedule mainnetProtocolScheduleProvider( final GenesisConfigFile genesisConfigFile) { return MainnetProtocolSchedule.fromConfig( - genesisConfigFile.getConfigOptions(), EvmConfiguration.DEFAULT); + genesisConfigFile.getConfigOptions(), + EvmConfiguration.DEFAULT, + MiningParameters.newDefault(), + new BadBlockManager(), + false, + new NoOpMetricsSystem()); } private static ProtocolContext mainnetProtocolContextProvider( @@ -156,7 +158,7 @@ public C as(final Class klass) { return null; } }, - Optional.empty()); + new BadBlockManager()); } private static BlockchainSetupUtil create( @@ -166,13 +168,12 @@ private static BlockchainSetupUtil create( final ProtocolContextProvider protocolContextProvider, final EthScheduler scheduler) { try { - final String genesisJson = - Resources.toString(chainResources.getGenesisURL(), StandardCharsets.UTF_8); - - final GenesisConfigFile genesisConfigFile = GenesisConfigFile.fromConfig(genesisJson); + final GenesisConfigFile genesisConfigFile = + GenesisConfigFile.fromSource(chainResources.getGenesisURL()); final ProtocolSchedule protocolSchedule = protocolScheduleProvider.get(genesisConfigFile); - final GenesisState genesisState = GenesisState.fromJson(genesisJson, protocolSchedule); + final GenesisState genesisState = + GenesisState.fromConfig(genesisConfigFile, protocolSchedule); final MutableBlockchain blockchain = createInMemoryBlockchain(genesisState.getBlock()); final WorldStateArchive worldArchive = storageFormat == DataStorageFormat.BONSAI diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/ExecutionContextTestFixture.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/ExecutionContextTestFixture.java index bd3b77798ff..511b94d3ae5 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/ExecutionContextTestFixture.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/ExecutionContextTestFixture.java @@ -14,11 +14,12 @@ */ package org.hyperledger.besu.ethereum.core; +import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createBonsaiInMemoryWorldStateArchive; import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryWorldStateArchive; import org.hyperledger.besu.config.GenesisConfigFile; -import org.hyperledger.besu.config.StubGenesisConfigOptions; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.DefaultBlockchain; import org.hyperledger.besu.ethereum.chain.GenesisState; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; @@ -31,6 +32,7 @@ import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; @@ -48,12 +50,13 @@ public class ExecutionContextTestFixture { private final ProtocolSchedule protocolSchedule; private final ProtocolContext protocolContext; - private static final GenesisConfigFile genesisConfigFile = GenesisConfigFile.mainnet(); private ExecutionContextTestFixture( + final GenesisConfigFile genesisConfigFile, final ProtocolSchedule protocolSchedule, final KeyValueStorage blockchainKeyValueStorage, - final KeyValueStorage variablesKeyValueStorage) { + final KeyValueStorage variablesKeyValueStorage, + final Optional dataStorageFormat) { final GenesisState genesisState = GenesisState.fromConfig(genesisConfigFile, protocolSchedule); this.genesis = genesisState.getBlock(); this.blockchainKeyValueStorage = blockchainKeyValueStorage; @@ -64,21 +67,25 @@ private ExecutionContextTestFixture( new KeyValueStoragePrefixedKeyBlockchainStorage( blockchainKeyValueStorage, new VariablesKeyValueStorage(variablesKeyValueStorage), - new MainnetBlockHeaderFunctions()), + new MainnetBlockHeaderFunctions(), + false), new NoOpMetricsSystem(), 0); - this.stateArchive = createInMemoryWorldStateArchive(); + if (dataStorageFormat.isPresent() && dataStorageFormat.get().equals(DataStorageFormat.BONSAI)) + this.stateArchive = createBonsaiInMemoryWorldStateArchive(blockchain); + else this.stateArchive = createInMemoryWorldStateArchive(); this.protocolSchedule = protocolSchedule; - this.protocolContext = new ProtocolContext(blockchain, stateArchive, null, Optional.empty()); + this.protocolContext = + new ProtocolContext(blockchain, stateArchive, null, new BadBlockManager()); genesisState.writeStateTo(stateArchive.getMutable()); } public static ExecutionContextTestFixture create() { - return new Builder().build(); + return new Builder(GenesisConfigFile.mainnet()).build(); } - public static Builder builder() { - return new Builder(); + public static Builder builder(final GenesisConfigFile genesisConfigFile) { + return new Builder(genesisConfigFile); } public Block getGenesis() { @@ -110,9 +117,15 @@ public ProtocolContext getProtocolContext() { } public static class Builder { + private final GenesisConfigFile genesisConfigFile; private KeyValueStorage variablesKeyValueStorage; private KeyValueStorage blockchainKeyValueStorage; private ProtocolSchedule protocolSchedule; + private Optional dataStorageFormat = Optional.empty(); + + public Builder(final GenesisConfigFile genesisConfigFile) { + this.genesisConfigFile = genesisConfigFile; + } public Builder variablesKeyValueStorage(final KeyValueStorage keyValueStorage) { this.variablesKeyValueStorage = keyValueStorage; @@ -129,16 +142,25 @@ public Builder protocolSchedule(final ProtocolSchedule protocolSchedule) { return this; } + public Builder dataStorageFormat(final DataStorageFormat dataStorageFormat) { + this.dataStorageFormat = Optional.of(dataStorageFormat); + return this; + } + public ExecutionContextTestFixture build() { if (protocolSchedule == null) { protocolSchedule = new ProtocolScheduleBuilder( - new StubGenesisConfigOptions().petersburgBlock(0), + genesisConfigFile.getConfigOptions(), BigInteger.valueOf(42), ProtocolSpecAdapters.create(0, Function.identity()), new PrivacyParameters(), false, - EvmConfiguration.DEFAULT) + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()) .createProtocolSchedule(); } if (blockchainKeyValueStorage == null) { @@ -147,8 +169,13 @@ public ExecutionContextTestFixture build() { if (variablesKeyValueStorage == null) { variablesKeyValueStorage = new InMemoryKeyValueStorage(); } + return new ExecutionContextTestFixture( - protocolSchedule, variablesKeyValueStorage, blockchainKeyValueStorage); + genesisConfigFile, + protocolSchedule, + variablesKeyValueStorage, + blockchainKeyValueStorage, + dataStorageFormat); } } } diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryKeyValueStorageProvider.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryKeyValueStorageProvider.java index fc8bfe77fba..9bac9254940 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryKeyValueStorageProvider.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryKeyValueStorageProvider.java @@ -14,10 +14,6 @@ */ package org.hyperledger.besu.ethereum.core; -import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateProvider; -import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogPruner; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.DefaultBlockchain; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; @@ -28,11 +24,15 @@ import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStorageProvider; import org.hyperledger.besu.ethereum.storage.keyvalue.VariablesKeyValueStorage; -import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStatePreimageKeyValueStorage; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; -import org.hyperledger.besu.ethereum.worldstate.DefaultMutableWorldState; -import org.hyperledger.besu.ethereum.worldstate.DefaultWorldStateArchive; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.BonsaiWorldStateProvider; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.BonsaiCachedMerkleTrieLoader; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.forest.ForestWorldStateArchive; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.forest.worldview.ForestMutableWorldState; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; @@ -73,14 +73,15 @@ public static MutableBlockchain createInMemoryBlockchain( return DefaultBlockchain.createMutable( genesisBlock, new KeyValueStoragePrefixedKeyBlockchainStorage( - keyValueStorage, variablesStorage, blockHeaderFunctions), + keyValueStorage, variablesStorage, blockHeaderFunctions, false), new NoOpMetricsSystem(), 0); } - public static DefaultWorldStateArchive createInMemoryWorldStateArchive() { - return new DefaultWorldStateArchive( - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()), + public static ForestWorldStateArchive createInMemoryWorldStateArchive() { + return new ForestWorldStateArchive( + new WorldStateStorageCoordinator( + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage())), new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage()), EvmConfiguration.DEFAULT); } @@ -94,24 +95,23 @@ public static BonsaiWorldStateProvider createBonsaiInMemoryWorldStateArchive( final Blockchain blockchain, final EvmConfiguration evmConfiguration) { final InMemoryKeyValueStorageProvider inMemoryKeyValueStorageProvider = new InMemoryKeyValueStorageProvider(); - final CachedMerkleTrieLoader cachedMerkleTrieLoader = - new CachedMerkleTrieLoader(new NoOpMetricsSystem()); + final BonsaiCachedMerkleTrieLoader bonsaiCachedMerkleTrieLoader = + new BonsaiCachedMerkleTrieLoader(new NoOpMetricsSystem()); return new BonsaiWorldStateProvider( (BonsaiWorldStateKeyValueStorage) - inMemoryKeyValueStorageProvider.createWorldStateStorage(DataStorageFormat.BONSAI), + inMemoryKeyValueStorageProvider.createWorldStateStorage( + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG), blockchain, Optional.empty(), - cachedMerkleTrieLoader, - new NoOpMetricsSystem(), + bonsaiCachedMerkleTrieLoader, null, - evmConfiguration, - TrieLogPruner.noOpTrieLogPruner()); + evmConfiguration); } public static MutableWorldState createInMemoryWorldState() { final InMemoryKeyValueStorageProvider provider = new InMemoryKeyValueStorageProvider(); - return new DefaultMutableWorldState( - provider.createWorldStateStorage(DataStorageFormat.FOREST), + return new ForestMutableWorldState( + provider.createWorldStateStorage(DataStorageConfiguration.DEFAULT_FOREST_CONFIG), provider.createWorldStatePreimageStorage(), EvmConfiguration.DEFAULT); } diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryPrivacyStorageProvider.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryPrivacyStorageProvider.java index 91e115cffab..d2521663b88 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryPrivacyStorageProvider.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/InMemoryPrivacyStorageProvider.java @@ -19,36 +19,43 @@ import org.hyperledger.besu.ethereum.privacy.storage.PrivacyStorageProvider; import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateKeyValueStorage; import org.hyperledger.besu.ethereum.privacy.storage.PrivateStateStorage; -import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStatePreimageKeyValueStorage; -import org.hyperledger.besu.ethereum.worldstate.DefaultMutableWorldState; -import org.hyperledger.besu.ethereum.worldstate.DefaultWorldStateArchive; +import org.hyperledger.besu.ethereum.trie.forest.ForestWorldStateArchive; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.forest.worldview.ForestMutableWorldState; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.worldstate.WorldStatePreimageStorage; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; public class InMemoryPrivacyStorageProvider implements PrivacyStorageProvider { public static WorldStateArchive createInMemoryWorldStateArchive() { - return new DefaultWorldStateArchive( - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()), + return new ForestWorldStateArchive( + new WorldStateStorageCoordinator( + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage())), new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage()), EvmConfiguration.DEFAULT); } public static MutableWorldState createInMemoryWorldState() { final InMemoryPrivacyStorageProvider provider = new InMemoryPrivacyStorageProvider(); - return new DefaultMutableWorldState( + return new ForestMutableWorldState( provider.createWorldStateStorage(), provider.createWorldStatePreimageStorage(), EvmConfiguration.DEFAULT); } @Override - public WorldStateStorage createWorldStateStorage() { - return new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + public WorldStateKeyValueStorage createWorldStateStorage() { + return new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + } + + @Override + public WorldStateStorageCoordinator createWorldStateStorageCoordinator() { + return new WorldStateStorageCoordinator(createWorldStateStorage()); } @Override diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/MessageFrameTestFixture.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/MessageFrameTestFixture.java index 262657872f5..800af844fb0 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/MessageFrameTestFixture.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/MessageFrameTestFixture.java @@ -15,11 +15,11 @@ package org.hyperledger.besu.ethereum.core; import static org.hyperledger.besu.evm.frame.MessageFrame.DEFAULT_MAX_STACK_SIZE; +import static org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.chain.Blockchain; -import org.hyperledger.besu.ethereum.vm.BlockHashLookup; import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup; import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.code.CodeV0; diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/MilestoneStreamingProtocolSchedule.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/MilestoneStreamingProtocolSchedule.java index 4f50d83476f..ad86866c666 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/MilestoneStreamingProtocolSchedule.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/MilestoneStreamingProtocolSchedule.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.core; import org.hyperledger.besu.ethereum.mainnet.DefaultProtocolSchedule; diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/NonBesuBlockHeader.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/NonBesuBlockHeader.java index d0c7cb8bbe1..000e727db75 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/NonBesuBlockHeader.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/NonBesuBlockHeader.java @@ -115,7 +115,7 @@ public Optional getWithdrawalsRoot() { } @Override - public Optional getDepositsRoot() { + public Optional getRequestsRoot() { return Optional.empty(); } diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/ProtocolScheduleFixture.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/ProtocolScheduleFixture.java index bd20bfa6ad0..e94c6b49fd0 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/ProtocolScheduleFixture.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/ProtocolScheduleFixture.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.core; import static org.hyperledger.besu.config.JsonUtil.normalizeKeys; @@ -20,9 +19,11 @@ import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.config.JsonGenesisConfigOptions; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import java.io.IOException; @@ -34,7 +35,14 @@ public class ProtocolScheduleFixture { public static final ProtocolSchedule MAINNET = MainnetProtocolSchedule.fromConfig( - getMainnetConfigOptions(), PrivacyParameters.DEFAULT, false, EvmConfiguration.DEFAULT); + getMainnetConfigOptions(), + PrivacyParameters.DEFAULT, + false, + EvmConfiguration.DEFAULT, + MiningParameters.newDefault(), + new BadBlockManager(), + false, + new NoOpMetricsSystem()); private static GenesisConfigOptions getMainnetConfigOptions() { // this method avoids reading all the alloc accounts when all we want is the "config" section diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TransactionTestFixture.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TransactionTestFixture.java index d016b7f4e5e..602116749b6 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TransactionTestFixture.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TransactionTestFixture.java @@ -92,6 +92,8 @@ public Transaction createTransaction(final KeyPair keys) { builder.versionedHashes(versionedHashes.get()); } break; + case DELEGATE_CODE: + break; } to.ifPresent(builder::to); diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TrieGenerator.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TrieGenerator.java index 9ee15e0775b..9a9108aa042 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TrieGenerator.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/TrieGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,14 +14,17 @@ */ package org.hyperledger.besu.ethereum.core; +import static org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator.applyForStrategy; + import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.ethereum.trie.MerkleTrie; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import java.util.List; import java.util.stream.Collectors; @@ -34,22 +37,23 @@ public class TrieGenerator { public static MerkleTrie generateTrie( - final WorldStateStorage worldStateStorage, final int nbAccounts) { + final WorldStateStorageCoordinator worldStateStorageCoordinator, final int nbAccounts) { return generateTrie( - worldStateStorage, + worldStateStorageCoordinator, IntStream.range(0, nbAccounts) .mapToObj(operand -> Hash.wrap(Bytes32.leftPad(Bytes.of(operand + 1)))) .collect(Collectors.toList())); } public static MerkleTrie generateTrie( - final WorldStateStorage worldStateStorage, final List accounts) { - final MerkleTrie accountStateTrie = emptyAccountStateTrie(worldStateStorage); + final WorldStateStorageCoordinator worldStateStorageCoordinator, final List accounts) { + final MerkleTrie accountStateTrie = + emptyAccountStateTrie(worldStateStorageCoordinator); // Add some storage values for (int i = 0; i < accounts.size(); i++) { - final WorldStateStorage.Updater updater = worldStateStorage.updater(); + final WorldStateKeyValueStorage.Updater updater = worldStateStorageCoordinator.updater(); final MerkleTrie storageTrie = - emptyStorageTrie(worldStateStorage, accounts.get(i)); + emptyStorageTrie(worldStateStorageCoordinator, accounts.get(i)); writeStorageValue(updater, storageTrie, accounts.get(i), UInt256.ONE, UInt256.valueOf(2L)); writeStorageValue( updater, storageTrie, accounts.get(i), UInt256.valueOf(2L), UInt256.valueOf(4L)); @@ -57,20 +61,36 @@ public static MerkleTrie generateTrie( updater, storageTrie, accounts.get(i), UInt256.valueOf(3L), UInt256.valueOf(6L)); int accountIndex = i; storageTrie.commit( - (location, hash, value) -> - updater.putAccountStorageTrieNode(accounts.get(accountIndex), location, hash, value)); + (location, hash, value) -> { + applyForStrategy( + updater, + onBonsai -> { + onBonsai.putAccountStorageTrieNode( + accounts.get(accountIndex), location, hash, value); + }, + onForest -> { + onForest.putAccountStorageTrieNode(hash, value); + }); + }); final Bytes code = Bytes32.leftPad(Bytes.of(i + 10)); final Hash codeHash = Hash.hash(code); final StateTrieAccountValue accountValue = new StateTrieAccountValue(1L, Wei.of(2L), Hash.wrap(storageTrie.getRootHash()), codeHash); accountStateTrie.put(accounts.get(i), RLP.encode(accountValue::writeTo)); - if (worldStateStorage instanceof BonsaiWorldStateKeyValueStorage) { - ((BonsaiWorldStateKeyValueStorage.Updater) updater) - .putAccountInfoState(accounts.get(i), RLP.encode(accountValue::writeTo)); - updater.putCode(accounts.get(i), code); - } - accountStateTrie.commit(updater::putAccountStateTrieNode); - updater.putCode(codeHash, code); + applyForStrategy( + updater, + onBonsai -> { + onBonsai.putAccountInfoState( + accounts.get(accountIndex), RLP.encode(accountValue::writeTo)); + accountStateTrie.commit(onBonsai::putAccountStateTrieNode); + onBonsai.putCode(accounts.get(accountIndex), codeHash, code); + }, + onForest -> { + accountStateTrie.commit( + (location, hash, value) -> onForest.putAccountStateTrieNode(hash, value)); + onForest.putCode(code); + }); + // Persist updates updater.commit(); } @@ -78,7 +98,7 @@ public static MerkleTrie generateTrie( } private static void writeStorageValue( - final WorldStateStorage.Updater updater, + final WorldStateKeyValueStorage.Updater updater, final MerkleTrie storageTrie, final Hash hash, final UInt256 key, @@ -101,17 +121,17 @@ private static Bytes encodeStorageValue(final UInt256 storageValue) { } public static MerkleTrie emptyStorageTrie( - final WorldStateStorage worldStateStorage, final Hash accountHash) { + final WorldStateStorageCoordinator worldStateStorageCoordinator, final Hash accountHash) { return new StoredMerklePatriciaTrie<>( (location, hash) -> - worldStateStorage.getAccountStorageTrieNode(accountHash, location, hash), + worldStateStorageCoordinator.getAccountStorageTrieNode(accountHash, location, hash), b -> b, b -> b); } public static MerkleTrie emptyAccountStateTrie( - final WorldStateStorage worldStateStorage) { + final WorldStateStorageCoordinator worldStateStorageCoordinator) { return new StoredMerklePatriciaTrie<>( - worldStateStorage::getAccountStateTrieNode, b -> b, b -> b); + worldStateStorageCoordinator::getAccountStateTrieNode, b -> b, b -> b); } } diff --git a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/VariablesStorageHelper.java b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/VariablesStorageHelper.java index 9744748ef1f..268c7f01350 100644 --- a/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/VariablesStorageHelper.java +++ b/ethereum/core/src/test-support/java/org/hyperledger/besu/ethereum/core/VariablesStorageHelper.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/BlockImportExceptionHandlingTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/BlockImportExceptionHandlingTest.java index a33f6a2b04e..d3a261803fb 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/BlockImportExceptionHandlingTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/BlockImportExceptionHandlingTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -23,9 +23,6 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateProvider; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; @@ -43,9 +40,18 @@ import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.blockhash.FrontierBlockHashProcessor; +import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; +import org.hyperledger.besu.ethereum.mainnet.requests.RequestsValidatorCoordinator; import org.hyperledger.besu.ethereum.storage.StorageProvider; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.BonsaiWorldStateProvider; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldState; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldStateConfig; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.exception.StorageException; @@ -76,11 +82,17 @@ class BlockImportExceptionHandlingTest { private final BlockBodyValidator blockBodyValidator = mock(BlockBodyValidator.class); private final ProtocolContext protocolContext = mock(ProtocolContext.class); private final ProtocolSpec protocolSpec = mock(ProtocolSpec.class); + private final GasCalculator gasCalculator = mock(GasCalculator.class); + private final FeeMarket feeMarket = mock(FeeMarket.class); protected final MutableBlockchain blockchain = mock(MutableBlockchain.class); private final StorageProvider storageProvider = new InMemoryKeyValueStorageProvider(); - private final WorldStateStorage worldStateStorage = - new BonsaiWorldStateKeyValueStorage(storageProvider, new NoOpMetricsSystem()); + private final WorldStateStorageCoordinator worldStateStorageCoordinator = + new WorldStateStorageCoordinator( + new BonsaiWorldStateKeyValueStorage( + storageProvider, + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG)); private final WorldStateArchive worldStateArchive = // contains a BonsaiWorldState which we need to spy on. @@ -91,8 +103,10 @@ class BlockImportExceptionHandlingTest { spy( new BonsaiWorldState( (BonsaiWorldStateProvider) worldStateArchive, - (BonsaiWorldStateKeyValueStorage) worldStateStorage, - EvmConfiguration.DEFAULT)); + (BonsaiWorldStateKeyValueStorage) + worldStateStorageCoordinator.worldStateKeyValueStorage(), + EvmConfiguration.DEFAULT, + new DiffBasedWorldStateConfig())); private final BadBlockManager badBlockManager = new BadBlockManager(); @@ -103,6 +117,11 @@ public void setup() { when(protocolContext.getBlockchain()).thenReturn(blockchain); when(protocolContext.getWorldStateArchive()).thenReturn(worldStateArchive); when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); + when(protocolSpec.getRequestsValidatorCoordinator()) + .thenReturn(RequestsValidatorCoordinator.empty()); + when(protocolSpec.getBlockHashProcessor()).thenReturn(new FrontierBlockHashProcessor()); + when(protocolSpec.getGasCalculator()).thenReturn(gasCalculator); + when(protocolSpec.getFeeMarket()).thenReturn(feeMarket); mainnetBlockValidator = new MainnetBlockValidator( blockHeaderValidator, blockBodyValidator, blockProcessor, badBlockManager); @@ -137,6 +156,7 @@ void shouldNotBadBlockWhenInternalErrorDuringPersisting() { eq(goodBlock), any(), any(), + any(), eq(HeaderValidationMode.DETACHED_ONLY))) .thenReturn(true); assertThat(badBlockManager.getBadBlocks()).isEmpty(); @@ -173,6 +193,7 @@ void shouldNotBadBlockWhenInternalErrorOnBlockLookup() { eq(goodBlock), any(), any(), + any(), eq(HeaderValidationMode.DETACHED_ONLY))) .thenReturn(true); assertThat(badBlockManager.getBadBlocks()).isEmpty(); @@ -241,6 +262,7 @@ void shouldNotBadBlockWhenInternalErrorDuringValidateBody() { eq(goodBlock), any(), any(), + any(), eq(HeaderValidationMode.DETACHED_ONLY))) .thenThrow(new StorageException("database problem")); assertThat(badBlockManager.getBadBlocks()).isEmpty(); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/MainnetBlockValidatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/MainnetBlockValidatorTest.java index 9973fe8212c..361353ee2b2 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/MainnetBlockValidatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/MainnetBlockValidatorTest.java @@ -17,76 +17,131 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; -import org.hyperledger.besu.ethereum.core.BlockDataGenerator; import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.mainnet.BlockBodyValidator; import org.hyperledger.besu.ethereum.mainnet.BlockHeaderValidator; import org.hyperledger.besu.ethereum.mainnet.BlockProcessor; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; -import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; +import org.hyperledger.besu.ethereum.trie.MerkleTrieException; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.plugin.services.exception.StorageException; +import java.util.Collections; import java.util.Optional; +import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; public class MainnetBlockValidatorTest { - private final BlockHeaderValidator blockHeaderValidator = mock(BlockHeaderValidator.class); - private final BlockBodyValidator blockBodyValidator = mock(BlockBodyValidator.class); - private final BlockProcessor blockProcessor = mock(BlockProcessor.class); + private final BlockchainSetupUtil chainUtil = BlockchainSetupUtil.forMainnet(); + private final Block block = chainUtil.getBlock(3); + private final Block blockParent = chainUtil.getBlock(2); + + private final MutableBlockchain blockchain = spy(chainUtil.getBlockchain()); private final ProtocolContext protocolContext = mock(ProtocolContext.class); - protected final MutableBlockchain blockchain = mock(MutableBlockchain.class); - protected final WorldStateArchive worldStateArchive = mock(WorldStateArchive.class); + private final WorldStateArchive worldStateArchive = mock(WorldStateArchive.class); + private final MutableWorldState worldState = mock(MutableWorldState.class); private final BadBlockManager badBlockManager = new BadBlockManager(); + private final BlockProcessor blockProcessor = mock(BlockProcessor.class); + private final BlockHeaderValidator blockHeaderValidator = mock(BlockHeaderValidator.class); + private final BlockBodyValidator blockBodyValidator = mock(BlockBodyValidator.class); - private MainnetBlockValidator mainnetBlockValidator; - private Block badBlock; + private final MainnetBlockValidator mainnetBlockValidator = + new MainnetBlockValidator( + blockHeaderValidator, blockBodyValidator, blockProcessor, badBlockManager); + + public static Stream getStorageExceptions() { + return Stream.of( + Arguments.of("StorageException", new StorageException("Database closed")), + Arguments.of("MerkleTrieException", new MerkleTrieException("Missing trie node"))); + } + + public static Stream getBlockProcessingErrors() { + return Stream.of( + Arguments.of("StorageException", new StorageException("Database closed")), + Arguments.of("MerkleTrieException", new MerkleTrieException("Missing trie node")), + Arguments.of("RuntimeException", new RuntimeException("Oops"))); + } @BeforeEach public void setup() { + chainUtil.importFirstBlocks(4); + final BlockProcessingResult successfulProcessingResult = + new BlockProcessingResult(Optional.empty(), false); + when(protocolContext.getBlockchain()).thenReturn(blockchain); when(protocolContext.getWorldStateArchive()).thenReturn(worldStateArchive); - mainnetBlockValidator = - new MainnetBlockValidator( - blockHeaderValidator, blockBodyValidator, blockProcessor, badBlockManager); - badBlock = - new BlockDataGenerator() - .block( - BlockDataGenerator.BlockOptions.create() - .setBlockNumber(2) - .hasTransactions(false) - .setBlockHeaderFunctions(new MainnetBlockHeaderFunctions())); + when(worldStateArchive.getMutable(any(BlockHeader.class), anyBoolean())) + .thenReturn(Optional.of(worldState)); + when(worldStateArchive.getMutable(any(Hash.class), any(Hash.class))) + .thenReturn(Optional.of(worldState)); + when(worldStateArchive.getMutable()).thenReturn(worldState); + when(blockHeaderValidator.validateHeader(any(), any(), any())).thenReturn(true); + when(blockHeaderValidator.validateHeader(any(), any(), any(), any())).thenReturn(true); + when(blockBodyValidator.validateBody(any(), any(), any(), any(), any(), any())) + .thenReturn(true); + when(blockBodyValidator.validateBodyLight(any(), any(), any(), any(), any())).thenReturn(true); + when(blockProcessor.processBlock(any(), any(), any())).thenReturn(successfulProcessingResult); + when(blockProcessor.processBlock(any(), any(), any(), any())) + .thenReturn(successfulProcessingResult); + when(blockProcessor.processBlock(any(), any(), any(), any(), any())) + .thenReturn(successfulProcessingResult); + when(blockProcessor.processBlock(any(), any(), any(), any(), any(), any(), any())) + .thenReturn(successfulProcessingResult); + + assertNoBadBlocks(); } @Test - public void shouldDetectAndCacheInvalidBlocksWhenParentBlockNotPresent() { - when(blockchain.getBlockHeader(anyLong())).thenReturn(Optional.empty()); - assertThat(badBlockManager.getBadBlocks().size()).isEqualTo(0); - mainnetBlockValidator.validateAndProcessBlock( - protocolContext, - badBlock, - HeaderValidationMode.DETACHED_ONLY, - HeaderValidationMode.DETACHED_ONLY); - assertThat(badBlockManager.getBadBlocks().size()).isEqualTo(1); + public void validateAndProcessBlock_onSuccess() { + BlockProcessingResult result = + mainnetBlockValidator.validateAndProcessBlock( + protocolContext, + block, + HeaderValidationMode.DETACHED_ONLY, + HeaderValidationMode.DETACHED_ONLY); + + assertThat(result.isSuccessful()).isTrue(); + assertNoBadBlocks(); } @Test - public void shouldDetectAndCacheInvalidBlocksWhenHeaderInvalid() { - when(blockchain.getBlockHeader(any(Hash.class))) - .thenReturn(Optional.of(new BlockHeaderTestFixture().buildHeader())); + public void validateAndProcessBlock_whenParentBlockNotPresent() { + final Hash parentHash = blockParent.getHash(); + doReturn(Optional.empty()).when(blockchain).getBlockHeader(eq(parentHash)); + + BlockProcessingResult result = + mainnetBlockValidator.validateAndProcessBlock( + protocolContext, + block, + HeaderValidationMode.DETACHED_ONLY, + HeaderValidationMode.DETACHED_ONLY); + + final String expectedError = "Parent block with hash " + parentHash + " not present"; + assertValidationFailed(result, expectedError); + assertNoBadBlocks(); + } + + @Test + public void validateAndProcessBlock_whenHeaderInvalid() { when(blockHeaderValidator.validateHeader( any(BlockHeader.class), any(BlockHeader.class), @@ -94,177 +149,276 @@ public void shouldDetectAndCacheInvalidBlocksWhenHeaderInvalid() { eq(HeaderValidationMode.DETACHED_ONLY))) .thenReturn(false); - assertThat(badBlockManager.getBadBlocks().size()).isEqualTo(0); - mainnetBlockValidator.validateAndProcessBlock( - protocolContext, - badBlock, - HeaderValidationMode.DETACHED_ONLY, - HeaderValidationMode.DETACHED_ONLY); - assertThat(badBlockManager.getBadBlocks().size()).isEqualTo(1); + BlockProcessingResult result = + mainnetBlockValidator.validateAndProcessBlock( + protocolContext, + block, + HeaderValidationMode.DETACHED_ONLY, + HeaderValidationMode.DETACHED_ONLY); + + final String expectedError = "Header validation failed (DETACHED_ONLY)"; + assertValidationFailed(result, expectedError); + assertBadBlockIsTracked(block); } @Test - public void shouldDetectAndCacheInvalidBlocksWhenParentWorldStateNotAvailable() { + public void validateAndProcessBlock_whenBlockBodyInvalid() { + when(blockBodyValidator.validateBody(any(), eq(block), any(), any(), any(), any())) + .thenReturn(false); - final BlockHeader blockHeader = new BlockHeaderTestFixture().buildHeader(); - when(blockchain.getBlockHeader(any(Hash.class))).thenReturn(Optional.of(blockHeader)); - when(blockHeaderValidator.validateHeader( - any(BlockHeader.class), - any(BlockHeader.class), - eq(protocolContext), - eq(HeaderValidationMode.DETACHED_ONLY))) - .thenReturn(true); - when(worldStateArchive.getMutable(eq(blockHeader), anyBoolean())).thenReturn(Optional.empty()); - - assertThat(badBlockManager.getBadBlocks().size()).isEqualTo(0); - mainnetBlockValidator.validateAndProcessBlock( - protocolContext, - badBlock, - HeaderValidationMode.DETACHED_ONLY, - HeaderValidationMode.DETACHED_ONLY); - assertThat(badBlockManager.getBadBlocks().size()).isEqualTo(1); + BlockProcessingResult result = + mainnetBlockValidator.validateAndProcessBlock( + protocolContext, + block, + HeaderValidationMode.DETACHED_ONLY, + HeaderValidationMode.DETACHED_ONLY); + + final String expectedError = "failed to validate output of imported block"; + assertValidationFailed(result, expectedError); + assertBadBlockIsTracked(block); } @Test - public void shouldDetectAndCacheInvalidBlocksWhenProcessBlockFailed() { - final BlockHeader blockHeader = new BlockHeaderTestFixture().buildHeader(); - when(blockchain.getBlockHeader(any(Hash.class))).thenReturn(Optional.of(blockHeader)); - when(blockHeaderValidator.validateHeader( - any(BlockHeader.class), - any(BlockHeader.class), - eq(protocolContext), - eq(HeaderValidationMode.DETACHED_ONLY))) - .thenReturn(true); - when(worldStateArchive.getMutable(eq(blockHeader), anyBoolean())) - .thenReturn(Optional.of(mock(MutableWorldState.class))); - when(blockProcessor.processBlock(eq(blockchain), any(MutableWorldState.class), eq(badBlock))) - .thenReturn(new BlockProcessingResult(Optional.empty())); - assertThat(badBlockManager.getBadBlocks().size()).isEqualTo(0); - mainnetBlockValidator.validateAndProcessBlock( - protocolContext, - badBlock, - HeaderValidationMode.DETACHED_ONLY, - HeaderValidationMode.DETACHED_ONLY); - assertThat(badBlockManager.getBadBlocks().size()).isEqualTo(1); + public void validateAndProcessBlock_whenParentWorldStateNotAvailable() { + when(worldStateArchive.getMutable(eq(blockParent.getHeader()), anyBoolean())) + .thenReturn(Optional.empty()); + + BlockProcessingResult result = + mainnetBlockValidator.validateAndProcessBlock( + protocolContext, + block, + HeaderValidationMode.DETACHED_ONLY, + HeaderValidationMode.DETACHED_ONLY); + + final String expectedError = + "Unable to process block because parent world state " + + blockParent.getHeader().getStateRoot() + + " is not available"; + assertValidationFailed(result, expectedError); + assertNoBadBlocks(); } @Test - public void shouldDetectAndCacheInvalidBlocksWhenBodyInvalid() { - final BlockHeader blockHeader = new BlockHeaderTestFixture().buildHeader(); - when(blockchain.getBlockHeader(any(Hash.class))).thenReturn(Optional.of(blockHeader)); - when(blockHeaderValidator.validateHeader( - any(BlockHeader.class), - any(BlockHeader.class), - eq(protocolContext), - eq(HeaderValidationMode.DETACHED_ONLY))) - .thenReturn(true); - when(worldStateArchive.getMutable(eq(blockHeader), anyBoolean())) - .thenReturn(Optional.of(mock(MutableWorldState.class))); - when(blockProcessor.processBlock(eq(blockchain), any(MutableWorldState.class), eq(badBlock))) - .thenReturn(new BlockProcessingResult(Optional.empty())); - assertThat(badBlockManager.getBadBlocks().size()).isEqualTo(0); - mainnetBlockValidator.validateAndProcessBlock( - protocolContext, - badBlock, - HeaderValidationMode.DETACHED_ONLY, - HeaderValidationMode.DETACHED_ONLY); - assertThat(badBlockManager.getBadBlocks().size()).isEqualTo(1); + public void validateAndProcessBlock_whenProcessBlockFails() { + when(blockProcessor.processBlock(eq(blockchain), any(MutableWorldState.class), eq(block))) + .thenReturn(BlockProcessingResult.FAILED); + + BlockProcessingResult result = + mainnetBlockValidator.validateAndProcessBlock( + protocolContext, + block, + HeaderValidationMode.DETACHED_ONLY, + HeaderValidationMode.DETACHED_ONLY); + + final String expectedError = "processing failed"; + assertValidationFailed(result, expectedError); + assertBadBlockIsTracked(block); } @Test - public void shouldNotCacheWhenValidBlocks() { + public void validateAndProcessBlock_whenStorageExceptionThrownGettingParent() { + final Throwable storageException = new StorageException("Database closed"); + final Hash parentHash = blockParent.getHash(); + doThrow(storageException).when(blockchain).getBlockHeader(eq(parentHash)); - MutableWorldState mockWorldState = mock(MutableWorldState.class); + BlockProcessingResult result = + mainnetBlockValidator.validateAndProcessBlock( + protocolContext, + block, + HeaderValidationMode.DETACHED_ONLY, + HeaderValidationMode.DETACHED_ONLY); - final BlockHeader blockHeader = new BlockHeaderTestFixture().buildHeader(); - when(blockchain.getBlockHeader(any(Hash.class))).thenReturn(Optional.of(blockHeader)); - when(blockHeaderValidator.validateHeader( - any(BlockHeader.class), - any(BlockHeader.class), - eq(protocolContext), - eq(HeaderValidationMode.DETACHED_ONLY))) - .thenReturn(true); - when(worldStateArchive.getMutable(eq(blockHeader), anyBoolean())) - .thenReturn(Optional.of(mockWorldState)); - when(worldStateArchive.getMutable(any(Hash.class), any(Hash.class))) - .thenReturn(Optional.of(mockWorldState)); - when(blockProcessor.processBlock(eq(blockchain), any(MutableWorldState.class), eq(badBlock))) - .thenReturn(new BlockProcessingResult(Optional.empty())); - when(blockBodyValidator.validateBody( - eq(protocolContext), - eq(badBlock), - any(), - any(), - eq(HeaderValidationMode.DETACHED_ONLY))) - .thenReturn(true); - assertThat(badBlockManager.getBadBlocks().size()).isEqualTo(0); - mainnetBlockValidator.validateAndProcessBlock( - protocolContext, - badBlock, - HeaderValidationMode.DETACHED_ONLY, - HeaderValidationMode.DETACHED_ONLY); - assertThat(badBlockManager.getBadBlocks()).isEmpty(); + assertValidationFailedExceptionally(result, storageException); + assertNoBadBlocks(); + } + + @ParameterizedTest(name = "[{index}] {0}") + @MethodSource("getStorageExceptions") + public void validateAndProcessBlock_whenStorageExceptionThrownProcessingBlock( + final String caseName, final Exception storageException) { + doThrow(storageException) + .when(blockProcessor) + .processBlock(eq(blockchain), any(MutableWorldState.class), eq(block)); + + BlockProcessingResult result = + mainnetBlockValidator.validateAndProcessBlock( + protocolContext, + block, + HeaderValidationMode.DETACHED_ONLY, + HeaderValidationMode.DETACHED_ONLY); + + assertValidationFailedExceptionally(result, storageException); + assertNoBadBlocks(); + } + + @ParameterizedTest(name = "[{index}] {0}") + @MethodSource("getStorageExceptions") + public void validateAndProcessBlock_whenStorageExceptionThrownGettingWorldState( + final String caseName, final Exception storageException) { + final BlockHeader parentHeader = blockParent.getHeader(); + doThrow(storageException).when(worldStateArchive).getMutable(eq(parentHeader), anyBoolean()); + + BlockProcessingResult result = + mainnetBlockValidator.validateAndProcessBlock( + protocolContext, + block, + HeaderValidationMode.DETACHED_ONLY, + HeaderValidationMode.DETACHED_ONLY); + + assertValidationFailedExceptionally(result, storageException); + assertNoBadBlocks(); + } + + @ParameterizedTest(name = "[{index}] {0}") + @MethodSource("getBlockProcessingErrors") + public void validateAndProcessBlock_whenProcessBlockYieldsExceptionalResult( + final String caseName, final Exception cause) { + final BlockProcessingResult exceptionalResult = + new BlockProcessingResult(Optional.empty(), cause); + when(blockProcessor.processBlock(eq(blockchain), any(MutableWorldState.class), eq(block))) + .thenReturn(exceptionalResult); + + BlockProcessingResult result = + mainnetBlockValidator.validateAndProcessBlock( + protocolContext, + block, + HeaderValidationMode.DETACHED_ONLY, + HeaderValidationMode.DETACHED_ONLY); + + assertValidationFailedExceptionally(result, cause); + assertNoBadBlocks(); } @Test - public void shouldReturnBadBlockBasedOnTheHash() { - when(blockchain.getBlockHeader(any(Hash.class))) - .thenReturn(Optional.of(new BlockHeaderTestFixture().buildHeader())); - when(blockHeaderValidator.validateHeader( - any(BlockHeader.class), - any(BlockHeader.class), - eq(protocolContext), - eq(HeaderValidationMode.DETACHED_ONLY))) - .thenReturn(false); - mainnetBlockValidator.validateAndProcessBlock( - protocolContext, - badBlock, - HeaderValidationMode.DETACHED_ONLY, - HeaderValidationMode.DETACHED_ONLY); - assertThat(badBlockManager.getBadBlock(badBlock.getHash())).containsSame(badBlock); + public void validateAndProcessBlock_withShouldRecordBadBlockFalse() { + when(blockProcessor.processBlock(eq(blockchain), any(MutableWorldState.class), eq(block))) + .thenReturn(BlockProcessingResult.FAILED); + + BlockProcessingResult result = + mainnetBlockValidator.validateAndProcessBlock( + protocolContext, + block, + HeaderValidationMode.DETACHED_ONLY, + HeaderValidationMode.DETACHED_ONLY, + false, + false); + + assertThat(result.isFailed()).isTrue(); + assertNoBadBlocks(); + } + + @Test + public void validateAndProcessBlock_withShouldRecordBadBlockTrue() { + when(blockProcessor.processBlock(eq(blockchain), any(MutableWorldState.class), eq(block))) + .thenReturn(BlockProcessingResult.FAILED); + + BlockProcessingResult result = + mainnetBlockValidator.validateAndProcessBlock( + protocolContext, + block, + HeaderValidationMode.DETACHED_ONLY, + HeaderValidationMode.DETACHED_ONLY, + false, + true); + + assertThat(result.isFailed()).isTrue(); + assertBadBlockIsTracked(block); } @Test - public void when_shouldRecordBadBlockIsFalse_Expect_BlockNotAddedToBadBlockManager() { - when(blockchain.getBlockHeader(any(Hash.class))) - .thenReturn(Optional.of(new BlockHeaderTestFixture().buildHeader())); - mainnetBlockValidator.validateAndProcessBlock( - protocolContext, - badBlock, - HeaderValidationMode.DETACHED_ONLY, - HeaderValidationMode.DETACHED_ONLY, - false, - false); - - assertThat(badBlockManager.getBadBlocks().size()).isEqualTo(0); + public void validateAndProcessBlock_withShouldRecordBadBlockNotSet() { + when(blockProcessor.processBlock(eq(blockchain), any(MutableWorldState.class), eq(block))) + .thenReturn(BlockProcessingResult.FAILED); + + BlockProcessingResult result = + mainnetBlockValidator.validateAndProcessBlock( + protocolContext, + block, + HeaderValidationMode.DETACHED_ONLY, + HeaderValidationMode.DETACHED_ONLY, + false); + + assertThat(result.isFailed()).isTrue(); + assertBadBlockIsTracked(block); } @Test - public void when_shouldRecordBadBlockIsTrue_Expect_BlockAddedToBadBlockManager() { - when(blockchain.getBlockHeader(any(Hash.class))) - .thenReturn(Optional.of(new BlockHeaderTestFixture().buildHeader())); - mainnetBlockValidator.validateAndProcessBlock( - protocolContext, - badBlock, - HeaderValidationMode.DETACHED_ONLY, - HeaderValidationMode.DETACHED_ONLY, - false, - true); - - assertThat(badBlockManager.getBadBlocks().size()).isEqualTo(1); + public void fastBlockValidation_onSuccess() { + final boolean isValid = + mainnetBlockValidator.fastBlockValidation( + protocolContext, + block, + Collections.emptyList(), + block.getBody().getRequests(), + HeaderValidationMode.FULL, + HeaderValidationMode.FULL); + + assertThat(isValid).isTrue(); + assertNoBadBlocks(); + } + + @Test + public void fastBlockValidation_onFailedHeaderValidation() { + final HeaderValidationMode validationMode = HeaderValidationMode.FULL; + when(blockHeaderValidator.validateHeader( + any(BlockHeader.class), eq(protocolContext), eq(validationMode))) + .thenReturn(false); + + final boolean isValid = + mainnetBlockValidator.fastBlockValidation( + protocolContext, + block, + Collections.emptyList(), + block.getBody().getRequests(), + validationMode, + validationMode); + + assertThat(isValid).isFalse(); + assertBadBlockIsTracked(block); } @Test - public void when_shouldRecordBadBlockIsNotSet_Expect_BlockAddedToBadBlockManager() { - when(blockchain.getBlockHeader(any(Hash.class))) - .thenReturn(Optional.of(new BlockHeaderTestFixture().buildHeader())); - mainnetBlockValidator.validateAndProcessBlock( - protocolContext, - badBlock, - HeaderValidationMode.DETACHED_ONLY, - HeaderValidationMode.DETACHED_ONLY, - false); - - assertThat(badBlockManager.getBadBlocks().size()).isEqualTo(1); + public void fastBlockValidation_onFailedBodyValidation() { + final HeaderValidationMode validationMode = HeaderValidationMode.FULL; + when(blockBodyValidator.validateBodyLight( + eq(protocolContext), eq(block), any(), any(), eq(validationMode))) + .thenReturn(false); + + final boolean isValid = + mainnetBlockValidator.fastBlockValidation( + protocolContext, + block, + Collections.emptyList(), + block.getBody().getRequests(), + validationMode, + validationMode); + + assertThat(isValid).isFalse(); + assertBadBlockIsTracked(block); + } + + private void assertNoBadBlocks() { + assertThat(badBlockManager.getBadBlocks()).isEmpty(); + } + + private void assertBadBlockIsTracked(final Block badBlock) { + assertThat(badBlockManager.getBadBlocks()).containsExactly(badBlock); + assertThat(badBlockManager.getBadBlock(badBlock.getHash())).contains(block); + } + + private void assertValidationFailed( + final BlockProcessingResult result, final String expectedError) { + assertThat(result.isFailed()).isTrue(); + assertThat(result.errorMessage).isPresent(); + assertThat(result.errorMessage.get()).containsIgnoringWhitespaces(expectedError); + } + + private void assertValidationFailedExceptionally( + final BlockProcessingResult result, final Throwable exception) { + assertThat(result.isFailed()).isTrue(); + assertThat(result.causedBy()).containsSame(exception); + assertThat(result.errorMessage).isPresent(); + assertThat(result.errorMessage.get()) + .containsIgnoringWhitespaces(exception.getLocalizedMessage()); } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchiveTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchiveTest.java deleted file mode 100644 index bfe69546295..00000000000 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiWorldStateArchiveTest.java +++ /dev/null @@ -1,307 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * - */ -package org.hyperledger.besu.ethereum.bonsai; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.WORLD_BLOCK_HASH_KEY; -import static org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.WORLD_ROOT_HASH_KEY; -import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.BLOCKCHAIN; -import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; -import org.hyperledger.besu.ethereum.bonsai.cache.CachedWorldStorageManager; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogFactoryImpl; -import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogLayer; -import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogManager; -import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogPruner; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; -import org.hyperledger.besu.ethereum.chain.Blockchain; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; -import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; -import org.hyperledger.besu.ethereum.storage.StorageProvider; -import org.hyperledger.besu.evm.internal.EvmConfiguration; -import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; -import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; -import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; -import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; -import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; - -import java.util.Optional; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.quality.Strictness; - -@ExtendWith(MockitoExtension.class) -@MockitoSettings(strictness = Strictness.LENIENT) -class BonsaiWorldStateArchiveTest { - final BlockHeaderTestFixture blockBuilder = new BlockHeaderTestFixture(); - - @Mock Blockchain blockchain; - - @Mock StorageProvider storageProvider; - - @Mock SegmentedKeyValueStorage segmentedKeyValueStorage; - @Mock KeyValueStorage trieLogStorage; - @Mock SegmentedKeyValueStorageTransaction segmentedKeyValueStorageTransaction; - BonsaiWorldStateProvider bonsaiWorldStateArchive; - - @Mock CachedWorldStorageManager cachedWorldStorageManager; - @Mock TrieLogManager trieLogManager; - - @BeforeEach - public void setUp() { - when(storageProvider.getStorageBySegmentIdentifiers(anyList())) - .thenReturn(segmentedKeyValueStorage); - when(segmentedKeyValueStorage.startTransaction()) - .thenReturn(segmentedKeyValueStorageTransaction); - when(storageProvider.getStorageBySegmentIdentifier(any())).thenReturn(trieLogStorage); - when(trieLogStorage.startTransaction()).thenReturn(mock(KeyValueStorageTransaction.class)); - } - - @Test - void testGetMutableReturnPersistedStateWhenNeeded() { - final BlockHeader chainHead = blockBuilder.number(0).buildHeader(); - - when(segmentedKeyValueStorage.get(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY)) - .thenReturn(Optional.of(chainHead.getStateRoot().toArrayUnsafe())); - when(segmentedKeyValueStorage.get(TRIE_BRANCH_STORAGE, WORLD_BLOCK_HASH_KEY)) - .thenReturn(Optional.of(chainHead.getHash().toArrayUnsafe())); - when(segmentedKeyValueStorage.get(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY)) - .thenReturn(Optional.of(chainHead.getStateRoot().toArrayUnsafe())); - when(segmentedKeyValueStorage.get(TRIE_BRANCH_STORAGE, WORLD_BLOCK_HASH_KEY)) - .thenReturn(Optional.of(chainHead.getHash().toArrayUnsafe())); - bonsaiWorldStateArchive = - new BonsaiWorldStateProvider( - cachedWorldStorageManager, - trieLogManager, - new BonsaiWorldStateKeyValueStorage(storageProvider, new NoOpMetricsSystem()), - blockchain, - new CachedMerkleTrieLoader(new NoOpMetricsSystem()), - EvmConfiguration.DEFAULT); - - assertThat(bonsaiWorldStateArchive.getMutable(chainHead, true)) - .containsInstanceOf(BonsaiWorldState.class); - } - - @Test - void testGetMutableReturnEmptyWhenLoadMoreThanLimitLayersBack() { - bonsaiWorldStateArchive = - new BonsaiWorldStateProvider( - new BonsaiWorldStateKeyValueStorage(storageProvider, new NoOpMetricsSystem()), - blockchain, - Optional.of(512L), - new CachedMerkleTrieLoader(new NoOpMetricsSystem()), - new NoOpMetricsSystem(), - null, - EvmConfiguration.DEFAULT, - TrieLogPruner.noOpTrieLogPruner()); - final BlockHeader blockHeader = blockBuilder.number(0).buildHeader(); - final BlockHeader chainHead = blockBuilder.number(512).buildHeader(); - when(blockchain.getChainHeadHeader()).thenReturn(chainHead); - assertThat(bonsaiWorldStateArchive.getMutable(blockHeader, false)).isEmpty(); - verify(cachedWorldStorageManager, Mockito.never()).getWorldState(any(Hash.class)); - } - - @Test - void testGetMutableWhenLoadLessThanLimitLayersBack() { - - bonsaiWorldStateArchive = - new BonsaiWorldStateProvider( - cachedWorldStorageManager, - trieLogManager, - new BonsaiWorldStateKeyValueStorage(storageProvider, new NoOpMetricsSystem()), - blockchain, - new CachedMerkleTrieLoader(new NoOpMetricsSystem()), - EvmConfiguration.DEFAULT); - final BlockHeader blockHeader = blockBuilder.number(0).buildHeader(); - final BlockHeader chainHead = blockBuilder.number(511).buildHeader(); - final BonsaiWorldState mockWorldState = mock(BonsaiWorldState.class); - when(mockWorldState.blockHash()).thenReturn(blockHeader.getHash()); - when(mockWorldState.freeze()).thenReturn(mockWorldState); - - when(trieLogManager.getMaxLayersToLoad()).thenReturn(Long.valueOf(512)); - when(cachedWorldStorageManager.getWorldState(blockHeader.getHash())) - .thenReturn(Optional.of(mockWorldState)); - when(blockchain.getChainHeadHeader()).thenReturn(chainHead); - assertThat(bonsaiWorldStateArchive.getMutable(blockHeader, false)) - .containsInstanceOf(BonsaiWorldState.class); - } - - @Test - void testGetMutableWithStorageInconsistencyRollbackTheState() { - - doAnswer(__ -> Optional.of(mock(TrieLogLayer.class))) - .when(trieLogManager) - .getTrieLogLayer(any(Hash.class)); - - var worldStateStorage = - new BonsaiWorldStateKeyValueStorage(storageProvider, new NoOpMetricsSystem()); - bonsaiWorldStateArchive = - spy( - new BonsaiWorldStateProvider( - cachedWorldStorageManager, - trieLogManager, - worldStateStorage, - blockchain, - new CachedMerkleTrieLoader(new NoOpMetricsSystem()), - EvmConfiguration.DEFAULT)); - final BlockHeader blockHeader = blockBuilder.number(0).buildHeader(); - - when(blockchain.getBlockHeader(blockHeader.getHash())).thenReturn(Optional.of(blockHeader)); - - assertThat(bonsaiWorldStateArchive.getMutable(null, blockHeader.getHash())) - .containsInstanceOf(BonsaiWorldState.class); - - // verify is trying to get the trie log layer to rollback - verify(trieLogManager).getTrieLogLayer(Hash.ZERO); - } - - // @SuppressWarnings({"unchecked", "rawtypes"}) - @Test - void testGetMutableWithStorageConsistencyNotRollbackTheState() { - - var worldStateStorage = - new BonsaiWorldStateKeyValueStorage(storageProvider, new NoOpMetricsSystem()); - bonsaiWorldStateArchive = - spy( - new BonsaiWorldStateProvider( - cachedWorldStorageManager, - trieLogManager, - worldStateStorage, - blockchain, - new CachedMerkleTrieLoader(new NoOpMetricsSystem()), - EvmConfiguration.DEFAULT)); - - final BlockHeader blockHeader = blockBuilder.number(0).buildHeader(); - - when(blockchain.getBlockHeader(blockHeader.getHash())).thenReturn(Optional.of(blockHeader)); - when(blockchain.getBlockHeader(Hash.ZERO)).thenReturn(Optional.of(blockHeader)); - - assertThat(bonsaiWorldStateArchive.getMutable(null, blockHeader.getHash())) - .containsInstanceOf(BonsaiWorldState.class); - - // verify is not trying to get the trie log layer to rollback when block is present - verify(trieLogManager, Mockito.never()).getTrieLogLayer(any()); - } - - @Test - void testGetMutableWithStorageConsistencyToRollbackAndRollForwardTheState() { - final BlockHeader genesis = blockBuilder.number(0).buildHeader(); - final BlockHeader blockHeaderChainA = - blockBuilder.number(1).timestamp(1).parentHash(genesis.getHash()).buildHeader(); - final BlockHeader blockHeaderChainB = - blockBuilder.number(1).timestamp(2).parentHash(genesis.getHash()).buildHeader(); - - doAnswer(__ -> Optional.of(mock(TrieLogLayer.class))) - .when(trieLogManager) - .getTrieLogLayer(any(Hash.class)); - - var worldStateStorage = - new BonsaiWorldStateKeyValueStorage(storageProvider, new NoOpMetricsSystem()); - - bonsaiWorldStateArchive = - spy( - new BonsaiWorldStateProvider( - cachedWorldStorageManager, - trieLogManager, - worldStateStorage, - blockchain, - new CachedMerkleTrieLoader(new NoOpMetricsSystem()), - EvmConfiguration.DEFAULT)); - - // initial persisted state hash key - when(blockchain.getBlockHeader(Hash.ZERO)).thenReturn(Optional.of(blockHeaderChainA)); - when(blockchain.getBlockHeader(blockHeaderChainB.getHash())) - .thenReturn(Optional.of(blockHeaderChainB)); - when(blockchain.getBlockHeader(genesis.getHash())).thenReturn(Optional.of(genesis)); - - assertThat(bonsaiWorldStateArchive.getMutable(null, blockHeaderChainB.getHash())) - .containsInstanceOf(BonsaiWorldState.class); - - // verify is trying to get the trie log layers to rollback and roll forward - verify(trieLogManager).getTrieLogLayer(blockHeaderChainA.getHash()); - verify(trieLogManager).getTrieLogLayer(blockHeaderChainB.getHash()); - } - - @Test - // TODO: refactor to test original intent - @Disabled("needs refactor, getMutable(hash, hash) cannot trigger saveTrieLog") - void testGetMutableWithRollbackNotOverrideTrieLogLayer() { - when(segmentedKeyValueStorage.startTransaction()) - .thenReturn(segmentedKeyValueStorageTransaction); - final BlockHeader genesis = blockBuilder.number(0).buildHeader(); - final BlockHeader blockHeaderChainA = - blockBuilder.number(1).timestamp(1).parentHash(genesis.getHash()).buildHeader(); - final BlockHeader blockHeaderChainB = - blockBuilder.number(1).timestamp(2).parentHash(genesis.getHash()).buildHeader(); - - doAnswer(__ -> Optional.of(mock(TrieLogLayer.class))) - .when(trieLogManager) - .getTrieLogLayer(any(Hash.class)); - - bonsaiWorldStateArchive = - spy( - new BonsaiWorldStateProvider( - cachedWorldStorageManager, - trieLogManager, - new BonsaiWorldStateKeyValueStorage(storageProvider, new NoOpMetricsSystem()), - blockchain, - new CachedMerkleTrieLoader(new NoOpMetricsSystem()), - EvmConfiguration.DEFAULT)); - - // initial persisted state hash key - when(blockchain.getBlockHeader(Hash.ZERO)).thenReturn(Optional.of(blockHeaderChainA)); - // fake trie log layer - final BytesValueRLPOutput rlpLogBlockB = new BytesValueRLPOutput(); - final TrieLogLayer trieLogLayerBlockB = new TrieLogLayer(); - trieLogLayerBlockB.setBlockHash(blockHeaderChainB.getHash()); - TrieLogFactoryImpl.writeTo(trieLogLayerBlockB, rlpLogBlockB); - when(segmentedKeyValueStorage.get(BLOCKCHAIN, blockHeaderChainB.getHash().toArrayUnsafe())) - .thenReturn(Optional.of(rlpLogBlockB.encoded().toArrayUnsafe())); - - when(blockchain.getBlockHeader(blockHeaderChainB.getHash())) - .thenReturn(Optional.of(blockHeaderChainB)); - when(blockchain.getBlockHeader(genesis.getHash())).thenReturn(Optional.of(genesis)); - - assertThat(bonsaiWorldStateArchive.getMutable(null, blockHeaderChainB.getHash())) - .containsInstanceOf(BonsaiWorldState.class); - - // verify is not persisting if already present - verify(segmentedKeyValueStorageTransaction, never()) - .put(BLOCKCHAIN, eq(blockHeaderChainA.getHash().toArrayUnsafe()), any()); - verify(segmentedKeyValueStorageTransaction, never()) - .put(BLOCKCHAIN, eq(blockHeaderChainB.getHash().toArrayUnsafe()), any()); - } -} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/CachedMerkleTrieLoaderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/CachedMerkleTrieLoaderTest.java deleted file mode 100644 index dfb247ee29b..00000000000 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/CachedMerkleTrieLoaderTest.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * - */ -package org.hyperledger.besu.ethereum.bonsai; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.datatypes.StorageSlotKey; -import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; -import org.hyperledger.besu.ethereum.core.TrieGenerator; -import org.hyperledger.besu.ethereum.rlp.RLP; -import org.hyperledger.besu.ethereum.storage.StorageProvider; -import org.hyperledger.besu.ethereum.trie.MerkleTrie; -import org.hyperledger.besu.ethereum.trie.TrieIterator; -import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; -import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; -import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.function.Function; -import java.util.stream.Collectors; - -import org.apache.tuweni.bytes.Bytes; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -class CachedMerkleTrieLoaderTest { - - private CachedMerkleTrieLoader merkleTrieLoader; - private final StorageProvider storageProvider = new InMemoryKeyValueStorageProvider(); - private final BonsaiWorldStateKeyValueStorage inMemoryWorldState = - Mockito.spy(new BonsaiWorldStateKeyValueStorage(storageProvider, new NoOpMetricsSystem())); - - final List

accounts = - List.of(Address.fromHexString("0xdeadbeef"), Address.fromHexString("0xdeadbeee")); - - private MerkleTrie trie; - - @BeforeEach - public void setup() { - trie = - TrieGenerator.generateTrie( - inMemoryWorldState, - accounts.stream().map(Address::addressHash).collect(Collectors.toList())); - merkleTrieLoader = new CachedMerkleTrieLoader(new NoOpMetricsSystem()); - } - - @Test - void shouldAddAccountNodesInCacheDuringPreload() { - merkleTrieLoader.cacheAccountNodes( - inMemoryWorldState, Hash.wrap(trie.getRootHash()), accounts.get(0)); - - final BonsaiWorldStateKeyValueStorage emptyStorage = - new BonsaiWorldStateKeyValueStorage( - new InMemoryKeyValueStorageProvider(), new NoOpMetricsSystem()); - StoredMerklePatriciaTrie cachedTrie = - new StoredMerklePatriciaTrie<>( - (location, hash) -> - merkleTrieLoader.getAccountStateTrieNode(emptyStorage, location, hash), - trie.getRootHash(), - Function.identity(), - Function.identity()); - - final Hash hashAccountZero = accounts.get(0).addressHash(); - assertThat(cachedTrie.get(hashAccountZero)).isEqualTo(trie.get(hashAccountZero)); - } - - @Test - void shouldAddStorageNodesInCacheDuringPreload() { - final Hash hashAccountZero = accounts.get(0).addressHash(); - final StateTrieAccountValue stateTrieAccountValue = - StateTrieAccountValue.readFrom(RLP.input(trie.get(hashAccountZero).orElseThrow())); - final StoredMerklePatriciaTrie storageTrie = - new StoredMerklePatriciaTrie<>( - (location, hash) -> - inMemoryWorldState.getAccountStorageTrieNode(hashAccountZero, location, hash), - stateTrieAccountValue.getStorageRoot(), - Function.identity(), - Function.identity()); - final List originalSlots = new ArrayList<>(); - storageTrie.visitLeafs( - (keyHash, node) -> { - merkleTrieLoader.cacheStorageNodes( - inMemoryWorldState, - accounts.get(0), - new StorageSlotKey(Hash.wrap(keyHash), Optional.empty())); - originalSlots.add(node.getEncodedBytes()); - return TrieIterator.State.CONTINUE; - }); - - final List cachedSlots = new ArrayList<>(); - final BonsaiWorldStateKeyValueStorage emptyStorage = - new BonsaiWorldStateKeyValueStorage( - new InMemoryKeyValueStorageProvider(), new NoOpMetricsSystem()); - final StoredMerklePatriciaTrie cachedTrie = - new StoredMerklePatriciaTrie<>( - (location, hash) -> - merkleTrieLoader.getAccountStorageTrieNode( - emptyStorage, hashAccountZero, location, hash), - stateTrieAccountValue.getStorageRoot(), - Function.identity(), - Function.identity()); - cachedTrie.visitLeafs( - (keyHash, node) -> { - cachedSlots.add(node.getEncodedBytes()); - return TrieIterator.State.CONTINUE; - }); - assertThat(originalSlots).isNotEmpty().isEqualTo(cachedSlots); - } - - @Test - void shouldFallbackWhenAccountNodesIsNotInCache() { - final StoredMerklePatriciaTrie cachedTrie = - new StoredMerklePatriciaTrie<>( - (location, hash) -> - merkleTrieLoader.getAccountStateTrieNode(inMemoryWorldState, location, hash), - trie.getRootHash(), - Function.identity(), - Function.identity()); - final Hash hashAccountZero = accounts.get(0).addressHash(); - assertThat(cachedTrie.get(hashAccountZero)).isEqualTo(trie.get(hashAccountZero)); - } - - @Test - void shouldFallbackWhenStorageNodesIsNotInCache() { - final Hash hashAccountZero = accounts.get(0).addressHash(); - final StateTrieAccountValue stateTrieAccountValue = - StateTrieAccountValue.readFrom(RLP.input(trie.get(hashAccountZero).orElseThrow())); - final StoredMerklePatriciaTrie storageTrie = - new StoredMerklePatriciaTrie<>( - (location, hash) -> - inMemoryWorldState.getAccountStorageTrieNode(hashAccountZero, location, hash), - stateTrieAccountValue.getStorageRoot(), - Function.identity(), - Function.identity()); - final List originalSlots = new ArrayList<>(); - storageTrie.visitLeafs( - (keyHash, node) -> { - originalSlots.add(node.getEncodedBytes()); - return TrieIterator.State.CONTINUE; - }); - - final List cachedSlots = new ArrayList<>(); - final StoredMerklePatriciaTrie cachedTrie = - new StoredMerklePatriciaTrie<>( - (location, hash) -> - merkleTrieLoader.getAccountStorageTrieNode( - inMemoryWorldState, hashAccountZero, location, hash), - stateTrieAccountValue.getStorageRoot(), - Function.identity(), - Function.identity()); - cachedTrie.visitLeafs( - (keyHash, node) -> { - cachedSlots.add(node.getEncodedBytes()); - return TrieIterator.State.CONTINUE; - }); - assertThat(originalSlots).isNotEmpty().isEqualTo(cachedSlots); - } -} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java deleted file mode 100644 index 13dbe22a403..00000000000 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java +++ /dev/null @@ -1,492 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.bonsai.storage; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage.WORLD_ROOT_HASH_KEY; -import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.datatypes.StorageSlotKey; -import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; -import org.hyperledger.besu.ethereum.core.TrieGenerator; -import org.hyperledger.besu.ethereum.rlp.RLP; -import org.hyperledger.besu.ethereum.storage.StorageProvider; -import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; -import org.hyperledger.besu.ethereum.trie.MerkleTrie; -import org.hyperledger.besu.ethereum.trie.StorageEntriesCollector; -import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; -import org.hyperledger.besu.ethereum.worldstate.FlatDbMode; -import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; -import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; -import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; -import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Optional; -import java.util.TreeMap; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt256; -import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; -import org.mockito.Mockito; - -public class BonsaiWorldStateKeyValueStorageTest { - - public static Collection data() { - return Arrays.asList(new Object[][] {{FlatDbMode.FULL}, {FlatDbMode.PARTIAL}}); - } - - final BonsaiWorldStateKeyValueStorage storage = emptyStorage(); - - public void setUp(final FlatDbMode flatDbMode) { - if (flatDbMode.equals(FlatDbMode.FULL)) { - storage.upgradeToFullFlatDbMode(); - } - } - - @ParameterizedTest - @MethodSource("data") - void getCode_returnsEmpty(final FlatDbMode flatDbMode) { - setUp(flatDbMode); - assertThat(storage.getCode(Hash.EMPTY, Hash.EMPTY)).contains(Bytes.EMPTY); - } - - @ParameterizedTest - @MethodSource("data") - void getAccountStateTrieNode_returnsEmptyNode(final FlatDbMode flatDbMode) { - setUp(flatDbMode); - assertThat(storage.getAccountStateTrieNode(Bytes.EMPTY, MerkleTrie.EMPTY_TRIE_NODE_HASH)) - .contains(MerkleTrie.EMPTY_TRIE_NODE); - } - - @ParameterizedTest - @MethodSource("data") - void getAccountStorageTrieNode_returnsEmptyNode(final FlatDbMode flatDbMode) { - setUp(flatDbMode); - assertThat( - storage.getAccountStorageTrieNode( - Hash.EMPTY, Bytes.EMPTY, MerkleTrie.EMPTY_TRIE_NODE_HASH)) - .contains(MerkleTrie.EMPTY_TRIE_NODE); - } - - @ParameterizedTest - @MethodSource("data") - void getNodeData_returnsEmptyValue(final FlatDbMode flatDbMode) { - setUp(flatDbMode); - assertThat(storage.getNodeData(null, null)).isEmpty(); - } - - @ParameterizedTest - @MethodSource("data") - void getNodeData_returnsEmptyNode(final FlatDbMode flatDbMode) { - setUp(flatDbMode); - assertThat(storage.getNodeData(Bytes.EMPTY, MerkleTrie.EMPTY_TRIE_NODE_HASH)).isEmpty(); - } - - @ParameterizedTest - @MethodSource("data") - void getCode_saveAndGetSpecialValues(final FlatDbMode flatDbMode) { - setUp(flatDbMode); - storage - .updater() - .putCode(Hash.EMPTY, MerkleTrie.EMPTY_TRIE_NODE) - .putCode(Hash.EMPTY, Bytes.EMPTY) - .commit(); - - assertThat(storage.getCode(Hash.hash(MerkleTrie.EMPTY_TRIE_NODE), Hash.EMPTY)) - .contains(MerkleTrie.EMPTY_TRIE_NODE); - } - - @ParameterizedTest - @MethodSource("data") - void getCode_saveAndGetRegularValue(final FlatDbMode flatDbMode) { - setUp(flatDbMode); - final Bytes bytes = Bytes.fromHexString("0x123456"); - storage.updater().putCode(Hash.EMPTY, bytes).commit(); - - assertThat(storage.getCode(Hash.hash(bytes), Hash.EMPTY)).contains(bytes); - } - - @ParameterizedTest - @MethodSource("data") - void getAccountStateTrieNode_saveAndGetSpecialValues(final FlatDbMode flatDbMode) { - setUp(flatDbMode); - storage - .updater() - .putAccountStateTrieNode( - Bytes.EMPTY, Hash.hash(MerkleTrie.EMPTY_TRIE_NODE), MerkleTrie.EMPTY_TRIE_NODE) - .putAccountStateTrieNode(Bytes.EMPTY, Hash.hash(Bytes.EMPTY), Bytes.EMPTY) - .commit(); - - assertThat(storage.getAccountStateTrieNode(Bytes.EMPTY, MerkleTrie.EMPTY_TRIE_NODE_HASH)) - .contains(MerkleTrie.EMPTY_TRIE_NODE); - assertThat(storage.getAccountStateTrieNode(Bytes.EMPTY, Hash.EMPTY)).contains(Bytes.EMPTY); - } - - @ParameterizedTest - @MethodSource("data") - void getAccountStateTrieNode_saveAndGetRegularValue(final FlatDbMode flatDbMode) { - setUp(flatDbMode); - final Bytes location = Bytes.fromHexString("0x01"); - final Bytes bytes = Bytes.fromHexString("0x123456"); - - storage.updater().putAccountStateTrieNode(location, Hash.hash(bytes), bytes).commit(); - - assertThat(storage.getAccountStateTrieNode(location, Hash.hash(bytes))).contains(bytes); - } - - @ParameterizedTest - @MethodSource("data") - void getAccountStorageTrieNode_saveAndGetSpecialValues(final FlatDbMode flatDbMode) { - setUp(flatDbMode); - - storage - .updater() - .putAccountStorageTrieNode( - Hash.EMPTY, - Bytes.EMPTY, - Hash.hash(MerkleTrie.EMPTY_TRIE_NODE), - MerkleTrie.EMPTY_TRIE_NODE) - .putAccountStorageTrieNode(Hash.EMPTY, Bytes.EMPTY, Hash.hash(Bytes.EMPTY), Bytes.EMPTY) - .commit(); - - assertThat( - storage.getAccountStorageTrieNode( - Hash.EMPTY, Bytes.EMPTY, Hash.hash(MerkleTrie.EMPTY_TRIE_NODE))) - .contains(MerkleTrie.EMPTY_TRIE_NODE); - assertThat(storage.getAccountStorageTrieNode(Hash.EMPTY, Bytes.EMPTY, Hash.EMPTY)) - .contains(Bytes.EMPTY); - } - - @ParameterizedTest - @MethodSource("data") - void getAccountStorageTrieNode_saveAndGetRegularValue(final FlatDbMode flatDbMode) { - setUp(flatDbMode); - final Hash accountHash = Address.fromHexString("0x1").addressHash(); - final Bytes location = Bytes.fromHexString("0x01"); - final Bytes bytes = Bytes.fromHexString("0x123456"); - - storage - .updater() - .putAccountStorageTrieNode(accountHash, location, Hash.hash(bytes), bytes) - .commit(); - - assertThat(storage.getAccountStorageTrieNode(accountHash, location, Hash.hash(bytes))) - .contains(bytes); - } - - @ParameterizedTest - @MethodSource("data") - void getAccount_notLoadFromTrieWhenEmptyAndFlatDbFullMode(final FlatDbMode flatDbMode) { - setUp(flatDbMode); - Assumptions.assumeTrue(flatDbMode == FlatDbMode.FULL); - final BonsaiWorldStateKeyValueStorage storage = spy(emptyStorage()); - - final MerkleTrie trie = TrieGenerator.generateTrie(storage, 1); - - final TreeMap accounts = - (TreeMap) - trie.entriesFrom(root -> StorageEntriesCollector.collectEntries(root, Hash.ZERO, 1)); - - // save world state root hash - final BonsaiWorldStateKeyValueStorage.BonsaiUpdater updater = storage.updater(); - updater - .getWorldStateTransaction() - .put(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY, trie.getRootHash().toArrayUnsafe()); - updater.commit(); - - // remove flat database - storage.clearFlatDatabase(); - - storage.upgradeToFullFlatDbMode(); - - Mockito.reset(storage); - - assertThat(storage.getAccount(Hash.wrap(accounts.firstKey()))).isEmpty(); - - verify(storage, times(0)).getAccountStateTrieNode(any(), eq(trie.getRootHash())); - } - - @ParameterizedTest - @MethodSource("data") - void getAccount_loadFromTrieWhenEmptyAndFlatDbPartialMode(final FlatDbMode flatDbMode) { - setUp(flatDbMode); - Assumptions.assumeTrue(flatDbMode == FlatDbMode.PARTIAL); - final BonsaiWorldStateKeyValueStorage storage = spy(emptyStorage()); - final MerkleTrie trie = TrieGenerator.generateTrie(storage, 1); - final TreeMap accounts = - (TreeMap) - trie.entriesFrom(root -> StorageEntriesCollector.collectEntries(root, Hash.ZERO, 1)); - - // save world state root hash - final BonsaiWorldStateKeyValueStorage.BonsaiUpdater updater = storage.updater(); - updater - .getWorldStateTransaction() - .put(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY, trie.getRootHash().toArrayUnsafe()); - updater.commit(); - - // remove flat database - storage.clearFlatDatabase(); - - Mockito.reset(storage); - - assertThat(storage.getAccount(Hash.wrap(accounts.firstKey()))) - .contains(accounts.firstEntry().getValue()); - - verify(storage, times(1)).getAccountStateTrieNode(any(), eq(trie.getRootHash())); - } - - @ParameterizedTest - @MethodSource("data") - void shouldUsePartialDBStrategyAfterDowngradingMode(final FlatDbMode flatDbMode) { - setUp(flatDbMode); - Assumptions.assumeTrue(flatDbMode == FlatDbMode.PARTIAL); - final BonsaiWorldStateKeyValueStorage storage = spy(emptyStorage()); - - final MerkleTrie trie = TrieGenerator.generateTrie(storage, 1); - final TreeMap accounts = - (TreeMap) - trie.entriesFrom(root -> StorageEntriesCollector.collectEntries(root, Hash.ZERO, 1)); - - // save world state root hash - final BonsaiWorldStateKeyValueStorage.BonsaiUpdater updater = storage.updater(); - updater - .getWorldStateTransaction() - .put(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY, trie.getRootHash().toArrayUnsafe()); - updater.commit(); - - Mockito.reset(storage); - - // remove flat database - storage.clearFlatDatabase(); - - storage.upgradeToFullFlatDbMode(); - assertThat(storage.getAccount(Hash.wrap(accounts.firstKey()))).isEmpty(); - - storage.downgradeToPartialFlatDbMode(); - assertThat(storage.getAccount(Hash.wrap(accounts.firstKey()))) - .contains(accounts.firstEntry().getValue()); - } - - @ParameterizedTest - @MethodSource("data") - void getStorage_loadFromTrieWhenEmptyWithPartialMode(final FlatDbMode flatDbMode) { - setUp(flatDbMode); - Assumptions.assumeTrue(flatDbMode == FlatDbMode.PARTIAL); - final BonsaiWorldStateKeyValueStorage storage = spy(emptyStorage()); - final MerkleTrie trie = TrieGenerator.generateTrie(storage, 1); - final TreeMap accounts = - (TreeMap) - trie.entriesFrom(root -> StorageEntriesCollector.collectEntries(root, Hash.ZERO, 1)); - - final StateTrieAccountValue stateTrieAccountValue = - StateTrieAccountValue.readFrom(RLP.input(accounts.firstEntry().getValue())); - - final StoredMerklePatriciaTrie storageTrie = - new StoredMerklePatriciaTrie<>( - (location, hash) -> - storage.getAccountStorageTrieNode(Hash.wrap(accounts.firstKey()), location, hash), - stateTrieAccountValue.getStorageRoot(), - b -> b, - b -> b); - - final TreeMap slots = - (TreeMap) - storageTrie.entriesFrom( - root -> StorageEntriesCollector.collectEntries(root, Hash.ZERO, 1)); - - // save world state root hash - final BonsaiWorldStateKeyValueStorage.BonsaiUpdater updater = storage.updater(); - updater - .getWorldStateTransaction() - .put(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY, trie.getRootHash().toArrayUnsafe()); - updater.commit(); - - // remove flat database - storage.clearFlatDatabase(); - - assertThat( - storage.getStorageValueByStorageSlotKey( - Hash.wrap(accounts.firstKey()), - new StorageSlotKey(Hash.wrap(slots.firstKey()), Optional.empty()))) - .map(Bytes::toShortHexString) - .contains(slots.firstEntry().getValue().toShortHexString()); - - verify(storage, times(2)) - .getAccountStorageTrieNode( - eq(Hash.wrap(accounts.firstKey())), any(), eq(storageTrie.getRootHash())); - } - - @ParameterizedTest - @MethodSource("data") - void getStorage_loadFromTrieWhenEmptyWithFullMode(final FlatDbMode flatDbMode) { - setUp(flatDbMode); - Assumptions.assumeTrue(flatDbMode == FlatDbMode.FULL); - final BonsaiWorldStateKeyValueStorage storage = spy(emptyStorage()); - storage.upgradeToFullFlatDbMode(); - final MerkleTrie trie = TrieGenerator.generateTrie(storage, 1); - - // save world state root hash - final BonsaiWorldStateKeyValueStorage.BonsaiUpdater updater = storage.updater(); - updater - .getWorldStateTransaction() - .put(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY, trie.getRootHash().toArrayUnsafe()); - updater.commit(); - - // remove flat database - storage.clearFlatDatabase(); - } - - @ParameterizedTest - @MethodSource("data") - void clear_reloadFlatDbStrategy(final FlatDbMode flatDbMode) { - setUp(flatDbMode); - final BonsaiWorldStateKeyValueStorage storage = spy(emptyStorage()); - - // save world state root hash - final BonsaiWorldStateKeyValueStorage.BonsaiUpdater updater = storage.updater(); - updater.putAccountInfoState(Hash.ZERO, Bytes32.random()).commit(); - - assertThat(storage.getAccount(Hash.ZERO)).isNotEmpty(); - - // clear - storage.clear(); - - assertThat(storage.getFlatDbStrategy()).isNotNull(); - - assertThat(storage.getAccount(Hash.ZERO)).isEmpty(); - } - - @ParameterizedTest - @MethodSource("data") - void reconcilesNonConflictingUpdaters(final FlatDbMode flatDbMode) { - setUp(flatDbMode); - final Hash accountHashA = Address.fromHexString("0x1").addressHash(); - final Hash accountHashB = Address.fromHexString("0x2").addressHash(); - final Hash accountHashD = Address.fromHexString("0x4").addressHash(); - final Bytes bytesA = Bytes.fromHexString("0x12"); - final Bytes bytesB = Bytes.fromHexString("0x1234"); - final Bytes bytesC = Bytes.fromHexString("0x123456"); - - final BonsaiWorldStateKeyValueStorage.BonsaiUpdater updaterA = storage.updater(); - final BonsaiWorldStateKeyValueStorage.BonsaiUpdater updaterB = storage.updater(); - - updaterA.putCode(accountHashA, bytesA); - updaterB.putCode(accountHashB, bytesA); - updaterB.putCode(accountHashB, bytesB); - updaterA.putCode(accountHashD, bytesC); - - updaterA.commit(); - updaterB.commit(); - - assertThat(storage.getCode(Hash.hash(bytesB), accountHashB)).contains(bytesB); - assertThat(storage.getCode(Hash.hash(bytesC), accountHashD)).contains(bytesC); - } - - @ParameterizedTest - @MethodSource("data") - void isWorldStateAvailable_defaultIsFalse(final FlatDbMode flatDbMode) { - setUp(flatDbMode); - assertThat(emptyStorage().isWorldStateAvailable(UInt256.valueOf(1), Hash.EMPTY)).isFalse(); - } - - @ParameterizedTest - @MethodSource("data") - void isWorldStateAvailable_StateAvailableByRootHash(final FlatDbMode flatDbMode) { - setUp(flatDbMode); - - final BonsaiWorldStateKeyValueStorage.BonsaiUpdater updater = storage.updater(); - final Bytes rootHashKey = Bytes32.fromHexString("0x01"); - updater - .getWorldStateTransaction() - .put(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY, rootHashKey.toArrayUnsafe()); - updater.commit(); - assertThat(storage.isWorldStateAvailable(Hash.wrap(Bytes32.wrap(rootHashKey)), Hash.EMPTY)) - .isTrue(); - } - - @ParameterizedTest - @MethodSource("data") - void isWorldStateAvailable_afterCallingSaveWorldstate(final FlatDbMode flatDbMode) { - setUp(flatDbMode); - - final BonsaiWorldStateKeyValueStorage.BonsaiUpdater updater = storage.updater(); - - final Bytes blockHash = Bytes32.fromHexString("0x01"); - final Bytes32 nodeHashKey = Bytes32.fromHexString("0x02"); - final Bytes nodeValue = Bytes32.fromHexString("0x03"); - - assertThat(storage.isWorldStateAvailable(Bytes32.wrap(nodeHashKey), Hash.EMPTY)).isFalse(); - - updater.saveWorldState(blockHash, nodeHashKey, nodeValue); - updater.commit(); - - assertThat(storage.isWorldStateAvailable(Bytes32.wrap(nodeHashKey), Hash.EMPTY)).isTrue(); - } - - private BonsaiWorldStateKeyValueStorage emptyStorage() { - return new BonsaiWorldStateKeyValueStorage( - new InMemoryKeyValueStorageProvider(), new NoOpMetricsSystem()); - } - - @Test - void successfulPruneReturnsTrue() { - final KeyValueStorage mockTrieLogStorage = mock(KeyValueStorage.class); - when(mockTrieLogStorage.tryDelete(any())).thenReturn(true); - final BonsaiWorldStateKeyValueStorage storage = setupMockStorage(mockTrieLogStorage); - assertThat(storage.pruneTrieLog(Hash.ZERO)).isTrue(); - } - - @Test - void failedPruneReturnsFalse() { - final KeyValueStorage mockTrieLogStorage = mock(KeyValueStorage.class); - when(mockTrieLogStorage.tryDelete(any())).thenReturn(false); - final BonsaiWorldStateKeyValueStorage storage = setupMockStorage(mockTrieLogStorage); - assertThat(storage.pruneTrieLog(Hash.ZERO)).isFalse(); - } - - @Test - void exceptionalPruneReturnsFalse() { - final KeyValueStorage mockTrieLogStorage = mock(KeyValueStorage.class); - when(mockTrieLogStorage.tryDelete(any())).thenThrow(new RuntimeException("test exception")); - final BonsaiWorldStateKeyValueStorage storage = setupMockStorage(mockTrieLogStorage); - assertThat(storage.pruneTrieLog(Hash.ZERO)).isFalse(); - } - - private BonsaiWorldStateKeyValueStorage setupMockStorage( - final KeyValueStorage mockTrieLogStorage) { - final StorageProvider mockStorageProvider = mock(StorageProvider.class); - when(mockStorageProvider.getStorageBySegmentIdentifier( - KeyValueSegmentIdentifier.TRIE_LOG_STORAGE)) - .thenReturn(mockTrieLogStorage); - when(mockStorageProvider.getStorageBySegmentIdentifiers(any())) - .thenReturn(mock(SegmentedKeyValueStorage.class)); - return new BonsaiWorldStateKeyValueStorage(mockStorageProvider, new NoOpMetricsSystem()); - } -} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPrunerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPrunerTest.java deleted file mode 100644 index 8538edda378..00000000000 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogPrunerTest.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright contributors to Hyperledger Besu. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.besu.ethereum.bonsai.trielog; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.chain.Blockchain; -import org.hyperledger.besu.ethereum.core.BlockDataGenerator; -import org.hyperledger.besu.ethereum.core.BlockHeader; - -import java.util.Optional; -import java.util.stream.Stream; - -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.core.config.Configurator; -import org.apache.tuweni.bytes.Bytes; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.InOrder; -import org.mockito.Mockito; - -public class TrieLogPrunerTest { - - private BonsaiWorldStateKeyValueStorage worldState; - private Blockchain blockchain; - - @SuppressWarnings("BannedMethod") - @BeforeEach - public void setup() { - Configurator.setLevel(LogManager.getLogger(TrieLogPruner.class).getName(), Level.TRACE); - worldState = Mockito.mock(BonsaiWorldStateKeyValueStorage.class); - blockchain = Mockito.mock(Blockchain.class); - when(worldState.pruneTrieLog(any(Hash.class))).thenReturn(true); - } - - @Test - public void initialize_preloads_queue_and_prunes_orphaned_blocks() { - // Given - int loadingLimit = 2; - final BlockDataGenerator generator = new BlockDataGenerator(); - final BlockHeader header1 = generator.header(1); - final BlockHeader header2 = generator.header(2); - when(worldState.streamTrieLogKeys(loadingLimit)) - .thenReturn(Stream.of(header1.getBlockHash().toArray(), header2.getBlockHash().toArray())); - when(blockchain.getBlockHeader(header1.getBlockHash())).thenReturn(Optional.of(header1)); - when(blockchain.getBlockHeader(header2.getBlockHash())).thenReturn(Optional.empty()); - - // When - TrieLogPruner trieLogPruner = new TrieLogPruner(worldState, blockchain, 3, loadingLimit, false); - trieLogPruner.initialize(); - - // Then - verify(worldState, times(1)).streamTrieLogKeys(2); - verify(worldState, times(1)).pruneTrieLog(header2.getBlockHash()); - } - - @Test - public void trieLogs_pruned_in_reverse_order_within_pruning_window() { - // Given - - // pruning window is below numBlocksToRetain and inside the pruningWindowSize offset. - final long blocksToRetain = 3; - final int pruningWindowSize = 2; - when(blockchain.getChainHeadBlockNumber()).thenReturn(5L); - when(worldState.pruneTrieLog(any(Hash.class))).thenReturn(true); - // requireFinalizedBlock = false means this is not a PoS chain - TrieLogPruner trieLogPruner = - new TrieLogPruner(worldState, blockchain, blocksToRetain, pruningWindowSize, false); - - trieLogPruner.addToPruneQueue(0, key(0)); // older block outside prune window - trieLogPruner.addToPruneQueue(1, key(1)); // block inside the prune window - trieLogPruner.addToPruneQueue(1, key(2)); // same block number (fork) - trieLogPruner.addToPruneQueue(2, key(3)); // different block inside prune window - trieLogPruner.addToPruneQueue(3, key(4)); // retained block - trieLogPruner.addToPruneQueue(4, key(5)); // different retained block - trieLogPruner.addToPruneQueue(5, key(6)); // another retained block - - // When - int wasPruned = trieLogPruner.pruneFromQueue(); - - // Then - assertThat(wasPruned).isEqualTo(3); - InOrder inOrder = Mockito.inOrder(worldState); - inOrder.verify(worldState, times(1)).pruneTrieLog(key(3)); - inOrder.verify(worldState, times(1)).pruneTrieLog(key(1)); // forks in order - inOrder.verify(worldState, times(1)).pruneTrieLog(key(2)); - - // Subsequent run should add one more block, then prune two oldest remaining keys - trieLogPruner.addToPruneQueue(6, key(6)); - when(blockchain.getChainHeadBlockNumber()).thenReturn(6L); - - wasPruned = trieLogPruner.pruneFromQueue(); - - assertThat(wasPruned).isEqualTo(2); - inOrder.verify(worldState, times(1)).pruneTrieLog(key(4)); - inOrder.verify(worldState, times(1)).pruneTrieLog(key(0)); - } - - @Test - public void retain_non_finalized_blocks() { - // Given - // finalizedBlockHeight < configuredRetainHeight - final long finalizedBlockHeight = 1; - final long configuredRetainHeight = 3; - TrieLogPruner trieLogPruner = - setupPrunerAndFinalizedBlock(configuredRetainHeight, finalizedBlockHeight); - - // When - final int wasPruned = trieLogPruner.pruneFromQueue(); - - // Then - assertThat(wasPruned).isEqualTo(1); - verify(worldState, times(1)).pruneTrieLog(key(1)); // should prune (finalized) - verify(worldState, never()).pruneTrieLog(key(2)); // would prune but (NOT finalized) - verify(worldState, never()).pruneTrieLog(key(3)); // would prune but (NOT finalized) - verify(worldState, never()).pruneTrieLog(key(4)); // retained block (NOT finalized) - verify(worldState, never()).pruneTrieLog(key(5)); // chain height (NOT finalized) - } - - @Test - public void boundary_test_when_configured_retain_equals_finalized_block() { - // Given - // finalizedBlockHeight == configuredRetainHeight - final long finalizedBlockHeight = 2; - final long configuredRetainHeight = 2; - TrieLogPruner trieLogPruner = - setupPrunerAndFinalizedBlock(configuredRetainHeight, finalizedBlockHeight); - - // When - final int wasPruned = trieLogPruner.pruneFromQueue(); - - // Then - assertThat(wasPruned).isEqualTo(1); - verify(worldState, times(1)).pruneTrieLog(key(1)); // should prune (finalized) - verify(worldState, never()).pruneTrieLog(key(2)); // retained block (finalized) - verify(worldState, never()).pruneTrieLog(key(3)); // retained block (NOT finalized) - verify(worldState, never()).pruneTrieLog(key(4)); // retained block (NOT finalized) - verify(worldState, never()).pruneTrieLog(key(5)); // chain height (NOT finalized) - } - - @Test - public void use_configured_retain_when_finalized_block_is_higher() { - // Given - // finalizedBlockHeight > configuredRetainHeight - final long finalizedBlockHeight = 4; - final long configuredRetainHeight = 3; - final TrieLogPruner trieLogPruner = - setupPrunerAndFinalizedBlock(configuredRetainHeight, finalizedBlockHeight); - - // When - final int wasPruned = trieLogPruner.pruneFromQueue(); - - // Then - assertThat(wasPruned).isEqualTo(2); - final InOrder inOrder = Mockito.inOrder(worldState); - inOrder.verify(worldState, times(1)).pruneTrieLog(key(2)); // should prune (finalized) - inOrder.verify(worldState, times(1)).pruneTrieLog(key(1)); // should prune (finalized) - verify(worldState, never()).pruneTrieLog(key(3)); // retained block (finalized) - verify(worldState, never()).pruneTrieLog(key(4)); // retained block (finalized) - verify(worldState, never()).pruneTrieLog(key(5)); // chain height (NOT finalized) - } - - @Test - public void skip_pruning_when_finalized_block_required_but_not_present() { - // This can occur at the start of PoS chains - - // Given - when(blockchain.getFinalized()).thenReturn(Optional.empty()); - final long configuredRetainHeight = 2; - final long chainHeight = 2; - final long configuredRetainAboveHeight = configuredRetainHeight - 1; - final long blocksToRetain = chainHeight - configuredRetainAboveHeight; - final int pruningWindowSize = (int) chainHeight; - when(blockchain.getChainHeadBlockNumber()).thenReturn(chainHeight); - TrieLogPruner trieLogPruner = - new TrieLogPruner(worldState, blockchain, blocksToRetain, pruningWindowSize, true); - - trieLogPruner.addToPruneQueue(1, key(1)); - trieLogPruner.addToPruneQueue(2, key(2)); - - // When - final int wasPruned = trieLogPruner.pruneFromQueue(); - - // Then - assertThat(wasPruned).isEqualTo(0); - verify(worldState, never()).pruneTrieLog(key(1)); // not finalized - verify(worldState, never()).pruneTrieLog(key(2)); // not finalized - } - - @Test - public void do_not_count_trieLog_when_prune_fails_first_attempt() { - // Given - when(worldState.pruneTrieLog(key(2))).thenReturn(false); - final long finalizedBlockHeight = 4; - final long configuredRetainHeight = 4; - final TrieLogPruner trieLogPruner = - setupPrunerAndFinalizedBlock(configuredRetainHeight, finalizedBlockHeight); - - // When - final int wasPruned = trieLogPruner.pruneFromQueue(); - - // Then - assertThat(wasPruned).isEqualTo(2); - - // Subsequent run should prune previously skipped trieLog - when(worldState.pruneTrieLog(key(2))).thenReturn(true); - assertThat(trieLogPruner.pruneFromQueue()).isEqualTo(1); - } - - private TrieLogPruner setupPrunerAndFinalizedBlock( - final long configuredRetainHeight, final long finalizedBlockHeight) { - final long chainHeight = 5; - final long configuredRetainAboveHeight = configuredRetainHeight - 1; - final long blocksToRetain = chainHeight - configuredRetainAboveHeight; - final int pruningWindowSize = (int) chainHeight; - - final BlockHeader finalizedHeader = new BlockDataGenerator().header(finalizedBlockHeight); - when(blockchain.getFinalized()).thenReturn(Optional.of(finalizedHeader.getBlockHash())); - when(blockchain.getBlockHeader(finalizedHeader.getBlockHash())) - .thenReturn(Optional.of(finalizedHeader)); - when(blockchain.getChainHeadBlockNumber()).thenReturn(chainHeight); - TrieLogPruner trieLogPruner = - new TrieLogPruner(worldState, blockchain, blocksToRetain, pruningWindowSize, true); - - trieLogPruner.addToPruneQueue(1, key(1)); - trieLogPruner.addToPruneQueue(2, key(2)); - trieLogPruner.addToPruneQueue(3, key(3)); - trieLogPruner.addToPruneQueue(4, key(4)); - trieLogPruner.addToPruneQueue(5, key(5)); - - return trieLogPruner; - } - - private Hash key(final int k) { - return Hash.hash(Bytes.of(k)); - } -} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/BadBlockManagerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/BadBlockManagerTest.java new file mode 100644 index 00000000000..10206af504d --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/BadBlockManagerTest.java @@ -0,0 +1,132 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.chain; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.jupiter.api.Test; + +public class BadBlockManagerTest { + + final BlockchainSetupUtil chainUtil = BlockchainSetupUtil.forMainnet(); + final Block block = chainUtil.getBlock(1); + final Block block2 = chainUtil.getBlock(2); + final BadBlockManager badBlockManager = new BadBlockManager(); + + @Test + public void addBadBlock_addsBlock() { + BadBlockManager badBlockManager = new BadBlockManager(); + final BadBlockCause cause = BadBlockCause.fromValidationFailure("failed"); + badBlockManager.addBadBlock(block, cause); + + assertThat(badBlockManager.getBadBlocks()).containsExactly(block); + } + + @Test + public void reset_clearsBadBlocks() { + BadBlockManager badBlockManager = new BadBlockManager(); + final BadBlockCause cause = BadBlockCause.fromValidationFailure(""); + badBlockManager.addBadBlock(block, cause); + assertThat(badBlockManager.getBadBlocks()).containsExactly(block); + + badBlockManager.reset(); + + assertThat(badBlockManager.getBadBlocks()).isEmpty(); + } + + @Test + public void isBadBlock_falseWhenEmpty() { + assertThat(badBlockManager.isBadBlock(block.getHash())).isFalse(); + } + + @Test + public void isBadBlock_trueForBadHeader() { + badBlockManager.addBadHeader(block.getHeader(), BadBlockCause.fromValidationFailure("failed")); + assertThat(badBlockManager.isBadBlock(block.getHash())).isTrue(); + } + + @Test + public void isBadBlock_trueForBadBlock() { + badBlockManager.addBadBlock(block, BadBlockCause.fromValidationFailure("failed")); + + assertThat(badBlockManager.isBadBlock(block.getHash())).isTrue(); + } + + @Test + public void subscribeToBadBlocks_listenerReceivesBadBlockEvent() { + + final AtomicReference badBlockResult = + new AtomicReference<>(); + final AtomicReference badBlockCauseResult = + new AtomicReference<>(); + + badBlockManager.subscribeToBadBlocks( + (badBlock, cause) -> { + badBlockResult.set(badBlock); + badBlockCauseResult.set(cause); + }); + + final BadBlockCause cause = BadBlockCause.fromValidationFailure("fail"); + badBlockManager.addBadBlock(block, cause); + + // Check event was emitted + assertThat(badBlockResult.get()).isEqualTo(block.getHeader()); + assertThat(badBlockCauseResult.get()).isEqualTo(cause); + } + + @Test + public void subscribeToBadBlocks_listenerReceivesBadHeaderEvent() { + + final AtomicReference badBlockResult = + new AtomicReference<>(); + final AtomicReference badBlockCauseResult = + new AtomicReference<>(); + + badBlockManager.subscribeToBadBlocks( + (badBlock, cause) -> { + badBlockResult.set(badBlock); + badBlockCauseResult.set(cause); + }); + + final BadBlockCause cause = BadBlockCause.fromValidationFailure("fail"); + badBlockManager.addBadHeader(block.getHeader(), cause); + + // Check event was emitted + assertThat(badBlockResult.get()).isEqualTo(block.getHeader()); + assertThat(badBlockCauseResult.get()).isEqualTo(cause); + } + + @Test + public void unsubscribeFromBadBlocks_listenerReceivesNoEvents() { + + final AtomicInteger eventCount = new AtomicInteger(0); + final long subscribeId = + badBlockManager.subscribeToBadBlocks((block, cause) -> eventCount.incrementAndGet()); + badBlockManager.unsubscribeFromBadBlocks(subscribeId); + + final BadBlockCause cause = BadBlockCause.fromValidationFailure("fail"); + badBlockManager.addBadBlock(block, cause); + badBlockManager.addBadHeader(block2.getHeader(), cause); + + // Check no events fired + assertThat(eventCount.get()).isEqualTo(0); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java index 00d0797d39b..556e4edefc7 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/ChainDataPrunerTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.chain; @@ -28,8 +27,8 @@ import java.util.List; import java.util.concurrent.AbstractExecutorService; import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; public class ChainDataPrunerTest { @@ -41,13 +40,15 @@ public void singleChainPruning() { new KeyValueStoragePrefixedKeyBlockchainStorage( new InMemoryKeyValueStorage(), new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), - new MainnetBlockHeaderFunctions()); + new MainnetBlockHeaderFunctions(), + false); final ChainDataPruner chainDataPruner = new ChainDataPruner( blockchainStorage, new ChainDataPrunerStorage(new InMemoryKeyValueStorage()), 512, 0, + // completed new BlockingExecutor()); Block genesisBlock = gen.genesisBlock(); final MutableBlockchain blockchain = @@ -79,13 +80,15 @@ public void forkPruning() { new KeyValueStoragePrefixedKeyBlockchainStorage( new InMemoryKeyValueStorage(), new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), - new MainnetBlockHeaderFunctions()); + new MainnetBlockHeaderFunctions(), + false); final ChainDataPruner chainDataPruner = new ChainDataPruner( blockchainStorage, new ChainDataPrunerStorage(new InMemoryKeyValueStorage()), 512, 0, + // completed new BlockingExecutor()); Block genesisBlock = gen.genesisBlock(); final MutableBlockchain blockchain = @@ -121,7 +124,7 @@ protected static class BlockingExecutor extends AbstractExecutorService { @Override public void shutdown() {} - @NotNull + @Nonnull @Override public List shutdownNow() { return List.of(); @@ -138,12 +141,12 @@ public boolean isTerminated() { } @Override - public boolean awaitTermination(final long timeout, final @NotNull TimeUnit unit) { + public boolean awaitTermination(final long timeout, final @Nonnull TimeUnit unit) { return true; } @Override - public void execute(final @NotNull Runnable command) { + public void execute(final @Nonnull Runnable command) { command.run(); } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/DefaultBlockchainTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/DefaultBlockchainTest.java index 66c9ac5593b..7ad1d82bfd2 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/DefaultBlockchainTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/DefaultBlockchainTest.java @@ -35,8 +35,10 @@ import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.Set; @@ -161,33 +163,42 @@ public void initializeReadOnly_withGiantDifficultyAndLiveMetrics() { () -> BlockOptions.create().setDifficulty(Difficulty.of(Long.MAX_VALUE))); final KeyValueStorage kvStore = new InMemoryKeyValueStorage(); final KeyValueStorage kvStoreVariables = new InMemoryKeyValueStorage(); - final List blocks = gen.blockSequence(10); + final ArrayDeque blocks = new ArrayDeque<>(gen.blockSequence(10)); final List> blockReceipts = new ArrayList<>(blocks.size()); blockReceipts.add(Collections.emptyList()); // Write small chain to storage final MutableBlockchain mutableBlockchain = - createMutableBlockchain(kvStore, kvStoreVariables, blocks.get(0)); - for (int i = 1; i < blocks.size(); i++) { - final Block block = blocks.get(i); - final List receipts = gen.receipts(block); - blockReceipts.add(receipts); - mutableBlockchain.appendBlock(block, receipts); - } + createMutableBlockchain(kvStore, kvStoreVariables, blocks.getFirst()); + blocks.stream() + .filter(block -> !block.equals(blocks.getFirst())) + .forEach( + block -> { + final List receipts = gen.receipts(block); + blockReceipts.add(receipts); + mutableBlockchain.appendBlock(block, receipts); + }); + + // To make sure there are no surprising NPEs during DefaultBlockchain. + BlockchainStorage blockchainStorage = createStorage(kvStore, kvStoreVariables); + BlockchainStorage.Updater updater = blockchainStorage.updater(); + updater.setFinalized(blocks.getLast().getHash()); + updater.setSafeBlock(blocks.getLast().getHash()); + updater.setChainHead(blocks.getLast().getHash()); + updater.commit(); // Create read only chain final Blockchain blockchain = DefaultBlockchain.create( - createStorage(kvStore, kvStoreVariables), + blockchainStorage, MetricsSystemFactory.create(MetricsConfiguration.builder().enabled(true).build()), 0); - for (int i = 0; i < blocks.size(); i++) { - assertBlockDataIsStored(blockchain, blocks.get(i), blockReceipts.get(i)); - } - final Block lastBlock = blocks.get(blocks.size() - 1); - assertBlockIsHead(blockchain, lastBlock); - assertTotalDifficultiesAreConsistent(blockchain, lastBlock); + assertThat(blockReceipts.size()).isEqualTo(blocks.size()); + Iterator> it = blockReceipts.iterator(); + blocks.forEach(block -> assertBlockDataIsStored(blockchain, block, it.next())); + assertBlockIsHead(blockchain, blocks.getLast()); + assertTotalDifficultiesAreConsistent(blockchain, blocks.getLast()); } @Test @@ -1055,7 +1066,8 @@ private BlockchainStorage createStorage( return new KeyValueStoragePrefixedKeyBlockchainStorage( kvStoreChain, new VariablesKeyValueStorage(kvStorageVariables), - new MainnetBlockHeaderFunctions()); + new MainnetBlockHeaderFunctions(), + false); } private DefaultBlockchain createMutableBlockchain( diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/GenesisStateTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/GenesisStateTest.java index 1a069542672..70c1e64bd1a 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/GenesisStateTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/chain/GenesisStateTest.java @@ -24,16 +24,22 @@ import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.ProtocolScheduleFixture; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.evm.account.Account; -import com.google.common.base.Charsets; -import com.google.common.io.Resources; +import java.util.stream.Stream; + import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; import org.bouncycastle.util.encoders.Hex; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; -public final class GenesisStateTest { +final class GenesisStateTest { /** Known RLP encoded bytes of the Olympic Genesis Block. */ private static final String OLYMPIC_RLP = @@ -46,11 +52,22 @@ public final class GenesisStateTest { private static final String EXPECTED_CODE = "0x608060405260043610610116577c01000000000000000000000000000000000000000000000000000000006000350463025e7c278114610158578063173825d91461019e57806320ea8d86146101d15780632f54bf6e146101fb5780633411c81c14610242578063547415251461027b5780637065cb48146102c1578063784547a7146102f45780638b51d13f1461031e5780639ace38c214610348578063a0e67e2b14610415578063a8abe69a1461047a578063b5dc40c3146104ba578063b77bf600146104e4578063ba51a6df146104f9578063c01a8c8414610523578063c64274741461054d578063d74f8edd14610615578063dc8452cd1461062a578063e20056e61461063f578063ee22610b1461067a575b60003411156101565760408051348152905133917fe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c919081900360200190a25b005b34801561016457600080fd5b506101826004803603602081101561017b57600080fd5b50356106a4565b60408051600160a060020a039092168252519081900360200190f35b3480156101aa57600080fd5b50610156600480360360208110156101c157600080fd5b5035600160a060020a03166106cc565b3480156101dd57600080fd5b50610156600480360360208110156101f457600080fd5b503561083c565b34801561020757600080fd5b5061022e6004803603602081101561021e57600080fd5b5035600160a060020a03166108f6565b604080519115158252519081900360200190f35b34801561024e57600080fd5b5061022e6004803603604081101561026557600080fd5b5080359060200135600160a060020a031661090b565b34801561028757600080fd5b506102af6004803603604081101561029e57600080fd5b50803515159060200135151561092b565b60408051918252519081900360200190f35b3480156102cd57600080fd5b50610156600480360360208110156102e457600080fd5b5035600160a060020a0316610997565b34801561030057600080fd5b5061022e6004803603602081101561031757600080fd5b5035610abc565b34801561032a57600080fd5b506102af6004803603602081101561034157600080fd5b5035610b43565b34801561035457600080fd5b506103726004803603602081101561036b57600080fd5b5035610bb2565b6040518085600160a060020a0316600160a060020a031681526020018481526020018060200183151515158152602001828103825284818151815260200191508051906020019080838360005b838110156103d75781810151838201526020016103bf565b50505050905090810190601f1680156104045780820380516001836020036101000a031916815260200191505b509550505050505060405180910390f35b34801561042157600080fd5b5061042a610c70565b60408051602080825283518183015283519192839290830191858101910280838360005b8381101561046657818101518382015260200161044e565b505050509050019250505060405180910390f35b34801561048657600080fd5b5061042a6004803603608081101561049d57600080fd5b508035906020810135906040810135151590606001351515610cd3565b3480156104c657600080fd5b5061042a600480360360208110156104dd57600080fd5b5035610e04565b3480156104f057600080fd5b506102af610f75565b34801561050557600080fd5b506101566004803603602081101561051c57600080fd5b5035610f7b565b34801561052f57600080fd5b506101566004803603602081101561054657600080fd5b5035610ffa565b34801561055957600080fd5b506102af6004803603606081101561057057600080fd5b600160a060020a03823516916020810135918101906060810160408201356401000000008111156105a057600080fd5b8201836020820111156105b257600080fd5b803590602001918460018302840111640100000000831117156105d457600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506110c5945050505050565b34801561062157600080fd5b506102af6110e4565b34801561063657600080fd5b506102af6110e9565b34801561064b57600080fd5b506101566004803603604081101561066257600080fd5b50600160a060020a03813581169160200135166110ef565b34801561068657600080fd5b506101566004803603602081101561069d57600080fd5b5035611289565b60038054829081106106b257fe5b600091825260209091200154600160a060020a0316905081565b3330146106d857600080fd5b600160a060020a038116600090815260026020526040902054819060ff16151561070157600080fd5b600160a060020a0382166000908152600260205260408120805460ff191690555b600354600019018110156107d75782600160a060020a031660038281548110151561074957fe5b600091825260209091200154600160a060020a031614156107cf5760038054600019810190811061077657fe5b60009182526020909120015460038054600160a060020a03909216918390811061079c57fe5b9060005260206000200160006101000a815481600160a060020a030219169083600160a060020a031602179055506107d7565b600101610722565b506003805460001901906107eb9082611557565b5060035460045411156108045760035461080490610f7b565b604051600160a060020a038316907f8001553a916ef2f495d26a907cc54d96ed840d7bda71e73194bf5a9df7a76b9090600090a25050565b3360008181526002602052604090205460ff16151561085a57600080fd5b60008281526001602090815260408083203380855292529091205483919060ff16151561088657600080fd5b600084815260208190526040902060030154849060ff16156108a757600080fd5b6000858152600160209081526040808320338085529252808320805460ff191690555187927ff6a317157440607f36269043eb55f1287a5a19ba2216afeab88cd46cbcfb88e991a35050505050565b60026020526000908152604090205460ff1681565b600160209081526000928352604080842090915290825290205460ff1681565b6000805b60055481101561099057838015610958575060008181526020819052604090206003015460ff16155b8061097c575082801561097c575060008181526020819052604090206003015460ff165b15610988576001820191505b60010161092f565b5092915050565b3330146109a357600080fd5b600160a060020a038116600090815260026020526040902054819060ff16156109cb57600080fd5b81600160a060020a03811615156109e157600080fd5b600380549050600101600454603282111580156109fe5750818111155b8015610a0957508015155b8015610a1457508115155b1515610a1f57600080fd5b600160a060020a038516600081815260026020526040808220805460ff1916600190811790915560038054918201815583527fc2575a0e9e593c00f959f8c92f12db2869c3395a3b0502d05e2516446f71f85b01805473ffffffffffffffffffffffffffffffffffffffff191684179055517ff39e6e1eb0edcf53c221607b54b00cd28f3196fed0a24994dc308b8f611b682d9190a25050505050565b600080805b600354811015610b3b5760008481526001602052604081206003805491929184908110610aea57fe5b6000918252602080832090910154600160a060020a0316835282019290925260400190205460ff1615610b1e576001820191505b600454821415610b3357600192505050610b3e565b600101610ac1565b50505b919050565b6000805b600354811015610bac5760008381526001602052604081206003805491929184908110610b7057fe5b6000918252602080832090910154600160a060020a0316835282019290925260400190205460ff1615610ba4576001820191505b600101610b47565b50919050565b6000602081815291815260409081902080546001808301546002808501805487516101009582161595909502600019011691909104601f8101889004880284018801909652858352600160a060020a0390931695909491929190830182828015610c5d5780601f10610c3257610100808354040283529160200191610c5d565b820191906000526020600020905b815481529060010190602001808311610c4057829003601f168201915b5050506003909301549192505060ff1684565b60606003805480602002602001604051908101604052809291908181526020018280548015610cc857602002820191906000526020600020905b8154600160a060020a03168152600190910190602001808311610caa575b505050505090505b90565b606080600554604051908082528060200260200182016040528015610d02578160200160208202803883390190505b5090506000805b600554811015610d8457858015610d32575060008181526020819052604090206003015460ff16155b80610d565750848015610d56575060008181526020819052604090206003015460ff165b15610d7c57808383815181101515610d6a57fe5b60209081029091010152600191909101905b600101610d09565b878703604051908082528060200260200182016040528015610db0578160200160208202803883390190505b5093508790505b86811015610df9578281815181101515610dcd57fe5b9060200190602002015184898303815181101515610de757fe5b60209081029091010152600101610db7565b505050949350505050565b606080600380549050604051908082528060200260200182016040528015610e36578160200160208202803883390190505b5090506000805b600354811015610eee5760008581526001602052604081206003805491929184908110610e6657fe5b6000918252602080832090910154600160a060020a0316835282019290925260400190205460ff1615610ee6576003805482908110610ea157fe5b6000918252602090912001548351600160a060020a0390911690849084908110610ec757fe5b600160a060020a03909216602092830290910190910152600191909101905b600101610e3d565b81604051908082528060200260200182016040528015610f18578160200160208202803883390190505b509350600090505b81811015610f6d578281815181101515610f3657fe5b906020019060200201518482815181101515610f4e57fe5b600160a060020a03909216602092830290910190910152600101610f20565b505050919050565b60055481565b333014610f8757600080fd5b6003548160328211801590610f9c5750818111155b8015610fa757508015155b8015610fb257508115155b1515610fbd57600080fd5b60048390556040805184815290517fa3f1ee9126a074d9326c682f561767f710e927faa811f7a99829d49dc421797a9181900360200190a1505050565b3360008181526002602052604090205460ff16151561101857600080fd5b6000828152602081905260409020548290600160a060020a0316151561103d57600080fd5b60008381526001602090815260408083203380855292529091205484919060ff161561106857600080fd5b6000858152600160208181526040808420338086529252808420805460ff1916909317909255905187927f4a504a94899432a9846e1aa406dceb1bcfd538bb839071d49d1e5e23f5be30ef91a36110be85611289565b5050505050565b60006110d2848484611444565b90506110dd81610ffa565b9392505050565b603281565b60045481565b3330146110fb57600080fd5b600160a060020a038216600090815260026020526040902054829060ff16151561112457600080fd5b600160a060020a038216600090815260026020526040902054829060ff161561114c57600080fd5b82600160a060020a038116151561116257600080fd5b60005b6003548110156111ee5785600160a060020a031660038281548110151561118857fe5b600091825260209091200154600160a060020a031614156111e657846003828154811015156111b357fe5b9060005260206000200160006101000a815481600160a060020a030219169083600160a060020a031602179055506111ee565b600101611165565b50600160a060020a03808616600081815260026020526040808220805460ff1990811690915593881682528082208054909416600117909355915190917f8001553a916ef2f495d26a907cc54d96ed840d7bda71e73194bf5a9df7a76b9091a2604051600160a060020a038516907ff39e6e1eb0edcf53c221607b54b00cd28f3196fed0a24994dc308b8f611b682d90600090a25050505050565b3360008181526002602052604090205460ff1615156112a757600080fd5b60008281526001602090815260408083203380855292529091205483919060ff1615156112d357600080fd5b600084815260208190526040902060030154849060ff16156112f457600080fd5b6112fd85610abc565b156110be576000858152602081815260409182902060038101805460ff19166001908117909155815481830154600280850180548851601f6000199783161561010002979097019091169290920494850187900487028201870190975283815293956113cf95600160a060020a039093169491939283908301828280156113c55780601f1061139a576101008083540402835291602001916113c5565b820191906000526020600020905b8154815290600101906020018083116113a857829003601f168201915b5050505050611534565b156114045760405186907f33e13ecb54c3076d8e8bb8c2881800a4d972b792045ffae98fdf46df365fed7590600090a261143c565b60405186907f526441bb6c1aba3c9a4a6ca1d6545da9c2333c8c48343ef398eb858d72b7923690600090a260038101805460ff191690555b505050505050565b600083600160a060020a038116151561145c57600080fd5b60055460408051608081018252600160a060020a0388811682526020808301898152838501898152600060608601819052878152808452959095208451815473ffffffffffffffffffffffffffffffffffffffff1916941693909317835551600183015592518051949650919390926114dc926002850192910190611580565b50606091909101516003909101805460ff191691151591909117905560058054600101905560405182907fc0ba8fe4b176c1714197d43b9cc6bcf797a4a7461c5fe8d0ef6e184ae7601e5190600090a2509392505050565b6000806040516020840160008287838a8c6187965a03f198975050505050505050565b81548183558181111561157b5760008381526020902061157b9181019083016115fe565b505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106115c157805160ff19168380011785556115ee565b828001600101855582156115ee579182015b828111156115ee5782518255916020019190600101906115d3565b506115fa9291506115fe565b5090565b610cd091905b808211156115fa576000815560010161160456fea165627a7a7230582070d3c680a2cf749f81772e7fffa2883f27a13c65fcfff32190d7585b0c6f0ce40029"; - @Test - public void createFromJsonWithAllocs() throws Exception { + static class GenesisStateTestArguments implements ArgumentsProvider { + @Override + public Stream provideArguments(final ExtensionContext context) { + return Stream.of( + Arguments.of(DataStorageConfiguration.DEFAULT_FOREST_CONFIG), + Arguments.of(DataStorageConfiguration.DEFAULT_BONSAI_CONFIG)); + } + } + + @ParameterizedTest + @ArgumentsSource(GenesisStateTestArguments.class) + public void createFromJsonWithAllocs(final DataStorageConfiguration dataStorageConfiguration) { final GenesisState genesisState = - GenesisState.fromJson( - Resources.toString(GenesisStateTest.class.getResource("genesis1.json"), Charsets.UTF_8), + GenesisState.fromJsonSource( + dataStorageConfiguration, + GenesisStateTest.class.getResource("genesis1.json"), ProtocolScheduleFixture.MAINNET); final BlockHeader header = genesisState.getBlock().getHeader(); assertThat(header.getStateRoot()) @@ -74,11 +91,13 @@ public void createFromJsonWithAllocs() throws Exception { assertThat(second.getBalance().toLong()).isEqualTo(222222222); } - @Test - public void createFromJsonNoAllocs() throws Exception { + @ParameterizedTest + @ArgumentsSource(GenesisStateTestArguments.class) + void createFromJsonNoAllocs(final DataStorageConfiguration dataStorageConfiguration) { final GenesisState genesisState = - GenesisState.fromJson( - Resources.toString(GenesisStateTest.class.getResource("genesis2.json"), Charsets.UTF_8), + GenesisState.fromJsonSource( + dataStorageConfiguration, + GenesisStateTest.class.getResource("genesis2.json"), ProtocolScheduleFixture.MAINNET); final BlockHeader header = genesisState.getBlock().getHeader(); assertThat(header.getStateRoot()).isEqualTo(Hash.EMPTY_TRIE_HASH); @@ -89,11 +108,14 @@ public void createFromJsonNoAllocs() throws Exception { assertThat(header.getParentHash()).isEqualTo(Hash.ZERO); } - private void assertContractInvariants(final String sourceFile, final String blockHash) - throws Exception { + private void assertContractInvariants( + final DataStorageConfiguration dataStorageConfiguration, + final String sourceFile, + final String blockHash) { final GenesisState genesisState = - GenesisState.fromJson( - Resources.toString(GenesisStateTest.class.getResource(sourceFile), Charsets.UTF_8), + GenesisState.fromJsonSource( + dataStorageConfiguration, + GenesisStateTest.class.getResource(sourceFile), ProtocolScheduleFixture.MAINNET); final BlockHeader header = genesisState.getBlock().getHeader(); assertThat(header.getHash()).isEqualTo(Hash.fromHexString(blockHash)); @@ -113,18 +135,22 @@ private void assertContractInvariants(final String sourceFile, final String bloc "000000000000000000000000385ef55e292fa39cf5ffbad99f534294565519ba"); } - @Test - public void createFromJsonWithContract() throws Exception { + @ParameterizedTest + @ArgumentsSource(GenesisStateTestArguments.class) + void createFromJsonWithContract(final DataStorageConfiguration dataStorageConfiguration) { assertContractInvariants( - "genesis3.json", "0xe7fd8db206dcaf066b7c97b8a42a0abc18653613560748557ab44868652a78b6"); + dataStorageConfiguration, + "genesis3.json", + "0xe7fd8db206dcaf066b7c97b8a42a0abc18653613560748557ab44868652a78b6"); } - @Test - public void createFromJsonWithNonce() throws Exception { + @ParameterizedTest + @ArgumentsSource(GenesisStateTestArguments.class) + void createFromJsonWithNonce(final DataStorageConfiguration dataStorageConfiguration) { final GenesisState genesisState = - GenesisState.fromJson( - Resources.toString( - GenesisStateTest.class.getResource("genesisNonce.json"), Charsets.UTF_8), + GenesisState.fromJsonSource( + dataStorageConfiguration, + GenesisStateTest.class.getResource("genesisNonce.json"), ProtocolScheduleFixture.MAINNET); final BlockHeader header = genesisState.getBlock().getHeader(); assertThat(header.getHash()) @@ -133,12 +159,13 @@ public void createFromJsonWithNonce() throws Exception { "0x36750291f1a8429aeb553a790dc2d149d04dbba0ca4cfc7fd5eb12d478117c9f")); } - @Test - public void encodeOlympicBlock() throws Exception { + @ParameterizedTest + @ArgumentsSource(GenesisStateTestArguments.class) + void encodeOlympicBlock(final DataStorageConfiguration dataStorageConfiguration) { final GenesisState genesisState = - GenesisState.fromJson( - Resources.toString( - GenesisStateTest.class.getResource("genesis-olympic.json"), Charsets.UTF_8), + GenesisState.fromJsonSource( + dataStorageConfiguration, + GenesisStateTest.class.getResource("genesis-olympic.json"), ProtocolScheduleFixture.MAINNET); final BytesValueRLPOutput tmp = new BytesValueRLPOutput(); genesisState.getBlock().writeTo(tmp); @@ -152,12 +179,13 @@ private void assertStorageValue(final Account contract, final String key, final .isEqualTo(UInt256.fromHexString(value)); } - @Test - public void genesisFromShanghai() throws Exception { + @ParameterizedTest + @ArgumentsSource(GenesisStateTestArguments.class) + void genesisFromShanghai(final DataStorageConfiguration dataStorageConfiguration) { final GenesisState genesisState = - GenesisState.fromJson( - Resources.toString( - GenesisStateTest.class.getResource("genesis_shanghai.json"), Charsets.UTF_8), + GenesisState.fromJsonSource( + dataStorageConfiguration, + GenesisStateTest.class.getResource("genesis_shanghai.json"), ProtocolScheduleFixture.MAINNET); final BlockHeader header = genesisState.getBlock().getHeader(); assertThat(header.getHash()) @@ -165,8 +193,8 @@ public void genesisFromShanghai() throws Exception { Hash.fromHexString( "0xfdc41f92053811b877be43e61cab6b0d9ee55501ae2443df0970c753747f12d8")); assertThat(header.getGasLimit()).isEqualTo(0x2fefd8); - assertThat(header.getGasUsed()).isEqualTo(0); - assertThat(header.getNumber()).isEqualTo(0); + assertThat(header.getGasUsed()).isZero(); + assertThat(header.getNumber()).isZero(); assertThat(header.getReceiptsRoot()) .isEqualTo( Hash.fromHexString( @@ -192,7 +220,7 @@ public void genesisFromShanghai() throws Exception { final Account last = worldState.get(Address.fromHexString("fb289e2b2b65fb63299a682d000744671c50417b")); assertThat(first).isNotNull(); - assertThat(first.getBalance().toLong()).isEqualTo(0); + assertThat(first.getBalance().toLong()).isZero(); assertThat(first.getCode()) .isEqualTo(Bytes.fromHexString("0x5f804955600180495560028049556003804955")); assertThat(last).isNotNull(); @@ -200,12 +228,13 @@ public void genesisFromShanghai() throws Exception { assertThat(lastBalance).isEqualTo(Wei.fromHexString("0x123450000000000000000")); } - @Test - public void genesisFromCancun() throws Exception { + @ParameterizedTest + @ArgumentsSource(GenesisStateTestArguments.class) + void genesisFromCancun(final DataStorageConfiguration dataStorageConfiguration) { final GenesisState genesisState = - GenesisState.fromJson( - Resources.toString( - GenesisStateTest.class.getResource("genesis_cancun.json"), Charsets.UTF_8), + GenesisState.fromJsonSource( + dataStorageConfiguration, + GenesisStateTest.class.getResource("genesis_cancun.json"), ProtocolScheduleFixture.MAINNET); final BlockHeader header = genesisState.getBlock().getHeader(); assertThat(header.getHash()) @@ -213,8 +242,58 @@ public void genesisFromCancun() throws Exception { Hash.fromHexString( "0x87846b86c1026fa7d7be2da045716274231de1871065a320659c9b111287c688")); assertThat(header.getGasLimit()).isEqualTo(0x2fefd8); - assertThat(header.getGasUsed()).isEqualTo(0); - assertThat(header.getNumber()).isEqualTo(0); + assertThat(header.getGasUsed()).isZero(); + assertThat(header.getNumber()).isZero(); + assertThat(header.getReceiptsRoot()) + .isEqualTo( + Hash.fromHexString( + "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")); + assertThat(header.getTransactionsRoot()).isEqualTo(Hash.EMPTY_TRIE_HASH); + assertThat(header.getOmmersHash()) + .isEqualTo( + Hash.fromHexString( + "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347")); + assertThat(header.getExtraData()).isEqualTo(Bytes.EMPTY); + assertThat(header.getParentHash()).isEqualTo(Hash.ZERO); + + final MutableWorldState worldState = InMemoryKeyValueStorageProvider.createInMemoryWorldState(); + genesisState.writeStateTo(worldState); + Hash computedStateRoot = worldState.rootHash(); + assertThat(computedStateRoot).isEqualTo(header.getStateRoot()); + assertThat(header.getStateRoot()) + .isEqualTo( + Hash.fromHexString( + "0x7f5cfe1375a61009a22d24512d18035bc8f855129452fa9c6a6be2ef4e9da7db")); + final Account first = + worldState.get(Address.fromHexString("0000000000000000000000000000000000000100")); + final Account last = + worldState.get(Address.fromHexString("fb289e2b2b65fb63299a682d000744671c50417b")); + assertThat(first).isNotNull(); + assertThat(first.getBalance().toLong()).isZero(); + assertThat(first.getCode()) + .isEqualTo(Bytes.fromHexString("0x5f804955600180495560028049556003804955")); + assertThat(last).isNotNull(); + Wei lastBalance = last.getBalance(); + assertThat(lastBalance).isEqualTo(Wei.fromHexString("0x123450000000000000000")); + assertThat(header.getRequestsRoot().isPresent()).isFalse(); + } + + @ParameterizedTest + @ArgumentsSource(GenesisStateTestArguments.class) + void genesisFromPrague(final DataStorageConfiguration dataStorageConfiguration) { + final GenesisState genesisState = + GenesisState.fromJsonSource( + dataStorageConfiguration, + GenesisStateTest.class.getResource("genesis_prague.json"), + ProtocolScheduleFixture.MAINNET); + final BlockHeader header = genesisState.getBlock().getHeader(); + assertThat(header.getHash()) + .isEqualTo( + Hash.fromHexString( + "0xaad700fd347070b47165c299dd5b843d0a47d4eaee12d3414a5cb58c5c8a8fe4")); + assertThat(header.getGasLimit()).isEqualTo(0x2fefd8); + assertThat(header.getGasUsed()).isZero(); + assertThat(header.getNumber()).isZero(); assertThat(header.getReceiptsRoot()) .isEqualTo( Hash.fromHexString( @@ -240,11 +319,24 @@ public void genesisFromCancun() throws Exception { final Account last = worldState.get(Address.fromHexString("fb289e2b2b65fb63299a682d000744671c50417b")); assertThat(first).isNotNull(); - assertThat(first.getBalance().toLong()).isEqualTo(0); + assertThat(first.getBalance().toLong()).isZero(); assertThat(first.getCode()) .isEqualTo(Bytes.fromHexString("0x5f804955600180495560028049556003804955")); assertThat(last).isNotNull(); Wei lastBalance = last.getBalance(); assertThat(lastBalance).isEqualTo(Wei.fromHexString("0x123450000000000000000")); + + assertThat(header.getRequestsRoot().isPresent()).isTrue(); + assertThat(header.getRequestsRoot().get()) + .isEqualTo( + Hash.fromHexString( + "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")); + } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/BlockValueCalculatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/BlockValueCalculatorTest.java index e819064d9f2..32dc063f313 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/BlockValueCalculatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/BlockValueCalculatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright Contributors to Hyperledger Besu. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -41,8 +41,8 @@ public void shouldCalculateZeroBlockValueForEmptyTransactions() { final Block block = new Block(blockHeader, new BlockBody(Collections.emptyList(), Collections.emptyList())); Wei blockValue = - new BlockValueCalculator() - .calculateBlockValue(new BlockWithReceipts(block, Collections.emptyList())); + BlockValueCalculator.calculateBlockValue( + new BlockWithReceipts(block, Collections.emptyList())); assertThat(blockValue).isEqualTo(Wei.ZERO); } @@ -85,9 +85,8 @@ public void shouldCalculateCorrectBlockValue() { final Block block = new Block(blockHeader, new BlockBody(List.of(tx1, tx2, tx3), Collections.emptyList())); Wei blockValue = - new BlockValueCalculator() - .calculateBlockValue( - new BlockWithReceipts(block, List.of(receipt1, receipt2, receipt3))); + BlockValueCalculator.calculateBlockValue( + new BlockWithReceipts(block, List.of(receipt1, receipt2, receipt3))); // Block value = 71 * 1 + (143-71) * 2 + (214-143) * 5 = 1427 assertThat(blockValue).isEqualTo(Wei.of(570L)); } @@ -114,8 +113,7 @@ public void shouldCalculateCorrectBlockValueExceedingLong() { final Block block = new Block(blockHeader, new BlockBody(List.of(tx1), Collections.emptyList())); Wei blockValue = - new BlockValueCalculator() - .calculateBlockValue(new BlockWithReceipts(block, List.of(receipt1))); + BlockValueCalculator.calculateBlockValue(new BlockWithReceipts(block, List.of(receipt1))); // Block value =~ max_long * 2 assertThat(blockValue).isGreaterThan(Wei.of(Long.MAX_VALUE)); } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/LogTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/LogTest.java index 18fb8f3328d..d47efa4b074 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/LogTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/LogTest.java @@ -18,16 +18,43 @@ import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.evm.log.Log; +import org.hyperledger.besu.evm.log.LogTopic; +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.Test; public class LogTest { + final BlockDataGenerator gen = new BlockDataGenerator(); @Test public void toFromRlp() { - final BlockDataGenerator gen = new BlockDataGenerator(); - final Log log = gen.log(); + final Log log = gen.log(2); final Log copy = Log.readFrom(RLP.input(RLP.encode(log::writeTo))); assertThat(copy).isEqualTo(log); } + + @Test + public void toFromRlpCompacted() { + final Log log = gen.log(2); + final Log copy = Log.readFrom(RLP.input(RLP.encode(rlpOut -> log.writeTo(rlpOut, true))), true); + assertThat(copy).isEqualTo(log); + } + + @Test + public void toFromRlpCompactedWithLeadingZeros() { + final Bytes logData = bytesWithLeadingZeros(10, 100); + final List logTopics = + List.of( + LogTopic.of(bytesWithLeadingZeros(20, 32)), LogTopic.of(bytesWithLeadingZeros(30, 32))); + final Log log = new Log(gen.address(), logData, logTopics); + final Log copy = Log.readFrom(RLP.input(RLP.encode(rlpOut -> log.writeTo(rlpOut, true))), true); + assertThat(copy).isEqualTo(log); + } + + private Bytes bytesWithLeadingZeros(final int noLeadingZeros, final int totalSize) { + return Bytes.concatenate( + Bytes.repeat((byte) 0, noLeadingZeros), gen.bytesValue(totalSize - noLeadingZeros)); + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/TransactionBuilderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/TransactionBuilderTest.java index 91d19c28dce..e56b9bc0e8a 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/TransactionBuilderTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/TransactionBuilderTest.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.core; import static java.util.stream.Collectors.toUnmodifiableSet; diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/TransactionReceiptTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/TransactionReceiptTest.java index 00a04354c69..06b9367ba93 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/TransactionReceiptTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/TransactionReceiptTest.java @@ -28,7 +28,7 @@ public void toFromRlp() { final BlockDataGenerator gen = new BlockDataGenerator(); final TransactionReceipt receipt = gen.receipt(); final TransactionReceipt copy = - TransactionReceipt.readFrom(RLP.input(RLP.encode(receipt::writeToWithRevertReason)), false); + TransactionReceipt.readFrom(RLP.input(RLP.encode(receipt::writeToForNetwork)), false); assertThat(copy).isEqualTo(receipt); } @@ -37,7 +37,40 @@ public void toFromRlpWithReason() { final BlockDataGenerator gen = new BlockDataGenerator(); final TransactionReceipt receipt = gen.receipt(Bytes.fromHexString("0x1122334455667788")); final TransactionReceipt copy = - TransactionReceipt.readFrom(RLP.input(RLP.encode(receipt::writeToWithRevertReason))); + TransactionReceipt.readFrom( + RLP.input(RLP.encode(rlpOut -> receipt.writeToForReceiptTrie(rlpOut, true, false)))); assertThat(copy).isEqualTo(receipt); } + + @Test + public void toFromRlpCompacted() { + final BlockDataGenerator gen = new BlockDataGenerator(); + final TransactionReceipt receipt = gen.receipt(Bytes.fromHexString("0x1122334455667788")); + final TransactionReceipt copy = + TransactionReceipt.readFrom( + RLP.input(RLP.encode(rlpOut -> receipt.writeToForReceiptTrie(rlpOut, false, true)))); + assertThat(copy).isEqualTo(receipt); + } + + @Test + public void toFromRlpCompactedWithReason() { + final BlockDataGenerator gen = new BlockDataGenerator(); + final TransactionReceipt receipt = gen.receipt(Bytes.fromHexString("0x1122334455667788")); + final TransactionReceipt copy = + TransactionReceipt.readFrom( + RLP.input(RLP.encode(rlpOut -> receipt.writeToForReceiptTrie(rlpOut, true, true)))); + assertThat(copy).isEqualTo(receipt); + } + + @Test + public void uncompactedAndCompactedDecodeToSameReceipt() { + final BlockDataGenerator gen = new BlockDataGenerator(); + final TransactionReceipt receipt = gen.receipt(Bytes.fromHexString("0x1122334455667788")); + final Bytes compactedReceipt = + RLP.encode(rlpOut -> receipt.writeToForReceiptTrie(rlpOut, false, true)); + final Bytes unCompactedReceipt = + RLP.encode(rlpOut -> receipt.writeToForReceiptTrie(rlpOut, false, false)); + assertThat(TransactionReceipt.readFrom(RLP.input(compactedReceipt))).isEqualTo(receipt); + assertThat(TransactionReceipt.readFrom(RLP.input(unCompactedReceipt))).isEqualTo(receipt); + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/UtilTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/UtilTest.java index f5df655236f..39b804abe07 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/UtilTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/UtilTest.java @@ -14,7 +14,7 @@ */ package org.hyperledger.besu.ethereum.core; -import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import org.junit.jupiter.api.Test; diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/VersionMetadataTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/VersionMetadataTest.java new file mode 100644 index 00000000000..32a978c3705 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/VersionMetadataTest.java @@ -0,0 +1,191 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +class VersionMetadataTest { + @TempDir public Path temporaryFolder; + + @Test + void getVersion() { + final VersionMetadata versionMetadata = new VersionMetadata("23.10.2"); + assertThat(versionMetadata).isNotNull(); + assertThat(versionMetadata.getBesuVersion()).isEqualTo("23.10.2"); + } + + @Test + void metaFileShouldContain() throws Exception { + final Path tempDataDir = + createAndWrite("data", "VERSION_METADATA.json", "{\"besuVersion\":\"23.10.3\"}"); + + final VersionMetadata versionMetadata = VersionMetadata.lookUpFrom(tempDataDir); + assertThat(versionMetadata).isNotNull(); + assertThat(versionMetadata.getBesuVersion()).isEqualTo("23.10.3"); + } + + @Test + void dataDirShouldBeCreatedIfNotPresent() throws Exception { + Files.deleteIfExists(temporaryFolder); + assertThat(Files.exists(temporaryFolder)).isFalse(); + + final VersionMetadata versionMetadata = VersionMetadata.lookUpFrom(temporaryFolder); + assertThat(versionMetadata).isNotNull(); + + assertThat(Files.exists(temporaryFolder)).isTrue(); + } + + static Stream versionTestProvider() { + return Stream.of( + Arguments.of("24.4.0", "24.3.3", 1), + Arguments.of("24.3.3", "24.3.3", 0), + Arguments.of("24.2.0", "24.3.3", -1), + Arguments.of("24.3.3", "24.3-develop-59da092", 1), + Arguments.of("24.3.0", "24.3-develop-59da092", 0), + Arguments.of("24.3.3", "24.4-develop-59da092", -1), + Arguments.of("24.2-develop-59da092", "24.3-develop-9999999", -1), + Arguments.of("24.3-develop-59da092", "24.3-develop-9999999", 0), + Arguments.of("24.4-develop-59da092", "24.3-develop-9999999", 1), + Arguments.of("24.4-develop-59da092", "24.3.0", 1), + Arguments.of("24.4-develop-59da092", "24.4.0", 0), + Arguments.of("24.4-develop-59da092", "24.4.1", -1)); + } + + @ParameterizedTest + @MethodSource("versionTestProvider") + public void assertComparableChecks( + final String runtimeVersion, + final String metadataVersion, + final int expectedComparisonResult) { + VersionMetadata runtime = new VersionMetadata(runtimeVersion); + VersionMetadata develop = new VersionMetadata(metadataVersion); + assertThat(runtime.compareTo(develop)).isEqualTo(expectedComparisonResult); + } + + @Test + void compatibilityCheckShouldThrowExceptionIfEnabled() throws Exception { + // The version file says the last version to start was 23.10.3 + final Path tempDataDir = + createAndWrite("data", "VERSION_METADATA.json", "{\"besuVersion\":\"23.10.3\"}"); + + // The runtime says the current version is 23.10.2 (i.e. a downgrade) + try (MockedStatic mocked = + Mockito.mockStatic(VersionMetadata.class, Mockito.CALLS_REAL_METHODS)) { + mocked.when(VersionMetadata::getRuntimeVersionString).thenReturn("23.10.2"); + + final VersionMetadata versionMetadata = VersionMetadata.lookUpFrom(tempDataDir); + assertThat(versionMetadata).isNotNull(); + assertThat(versionMetadata.getBesuVersion()).isEqualTo("23.10.3"); + assertThatThrownBy(() -> VersionMetadata.versionCompatibilityChecks(true, tempDataDir)) + .isInstanceOf(IllegalStateException.class); + } + + // Check that the file hasn't been updated + final String updatedFileContents = + Files.readString(tempDataDir.resolve("VERSION_METADATA.json")); + VersionMetadata newVersionMetadata = + new ObjectMapper().readValue(updatedFileContents, VersionMetadata.class); + assertThat(newVersionMetadata.getBesuVersion()).isEqualTo("23.10.3"); + } + + @Test + void compatibilityCheckShouldNotThrowExceptionIfDisabled() throws Exception { + // The version file says the last version to start was 23.10.3 + final Path tempDataDir = + createAndWrite("data", "VERSION_METADATA.json", "{\"besuVersion\":\"23.10.3\"}"); + + // The runtime says the current version is 23.10.2 (i.e. a downgrade) but we're setting + // version-compatibility-protection = false so no exception should be thrown + try (MockedStatic mocked = + Mockito.mockStatic(VersionMetadata.class, Mockito.CALLS_REAL_METHODS)) { + mocked.when(VersionMetadata::getRuntimeVersionString).thenReturn("23.10.2"); + + final VersionMetadata versionMetadata = VersionMetadata.lookUpFrom(tempDataDir); + assertThat(versionMetadata).isNotNull(); + assertThat(versionMetadata.getBesuVersion()).isEqualTo("23.10.3"); + + assertThatNoException() + .isThrownBy(() -> VersionMetadata.versionCompatibilityChecks(false, tempDataDir)); + } + + // Check that the file has been updated + final String updatedFileContents = + Files.readString(tempDataDir.resolve("VERSION_METADATA.json")); + VersionMetadata newVersionMetadata = + new ObjectMapper().readValue(updatedFileContents, VersionMetadata.class); + assertThat(newVersionMetadata.getBesuVersion()).isEqualTo("23.10.2"); + } + + @Test + void compatibilityCheckShouldNotThrowExceptionIfResultIsUpgrade() throws Exception { + final Path tempDataDir = + createAndWrite("data", "VERSION_METADATA.json", "{\"besuVersion\":\"23.10.3\"}"); + + // The runtime says the current version is 23.10.2 (i.e. a downgrade) + try (MockedStatic mocked = + Mockito.mockStatic(VersionMetadata.class, Mockito.CALLS_REAL_METHODS)) { + mocked.when(VersionMetadata::getRuntimeVersionString).thenReturn("23.10.4"); + + final VersionMetadata versionMetadata = VersionMetadata.lookUpFrom(tempDataDir); + assertThat(versionMetadata).isNotNull(); + assertThat(versionMetadata.getBesuVersion()).isEqualTo("23.10.3"); + + assertThatNoException() + .isThrownBy(() -> VersionMetadata.versionCompatibilityChecks(true, tempDataDir)); + } + + // Check that the file has been updated + final String updatedFileContents = + Files.readString(tempDataDir.resolve("VERSION_METADATA.json")); + VersionMetadata newVersionMetadata = + new ObjectMapper().readValue(updatedFileContents, VersionMetadata.class); + assertThat(newVersionMetadata.getBesuVersion()).isEqualTo("23.10.4"); + } + + private Path createAndWrite(final String dir, final String file, final String content) + throws IOException { + return createAndWrite(temporaryFolder, dir, file, content); + } + + private Path createAndWrite( + final Path temporaryFolder, final String dir, final String file, final String content) + throws IOException { + final Path tmpDir = temporaryFolder.resolve(dir); + Files.createDirectories(tmpDir); + createAndWrite(tmpDir.resolve(file), content); + return tmpDir; + } + + private void createAndWrite(final Path path, final String content) throws IOException { + path.toFile().createNewFile(); + Files.writeString(path, content); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/BlobTransactionEncodingTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/BlobTransactionEncodingTest.java index 6b33f69dd5a..e53c1b50e00 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/BlobTransactionEncodingTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/BlobTransactionEncodingTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -27,6 +27,7 @@ import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -117,4 +118,11 @@ public String toString() { : bytes.toString(); } } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationDecoderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationDecoderTest.java new file mode 100644 index 00000000000..d6ff585b4fc --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationDecoderTest.java @@ -0,0 +1,102 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core.encoding; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import org.hyperledger.besu.crypto.SECPSignature; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.CodeDelegation; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; + +import java.math.BigInteger; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; + +class CodeDelegationDecoderTest { + + @Test + void shouldDecodeInnerPayloadWithNonce() { + // "0xd80194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c105" + + final BytesValueRLPInput input = + new BytesValueRLPInput( + Bytes.fromHexString( + "0xf85a0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa562a80a0840798fa67118e034c1eb7e42fe89e28d7cd5006dc813d5729e5f75b0d1a7ec5a03b1dbace38ceb862a65bf2eac0637693b5c3493bcb2a022dd614c0a74cce0b99"), + true); + final CodeDelegation authorization = CodeDelegationTransactionDecoder.decodeInnerPayload(input); + + assertThat(authorization.chainId()).isEqualTo(BigInteger.ONE); + assertThat(authorization.address()) + .isEqualTo(Address.fromHexStringStrict("0x633688abc3cCf8B0C03088D2d1C6ae4958c2fA56")); + assertThat(authorization.nonce()).isEqualTo(42); + + final SECPSignature signature = authorization.signature(); + assertThat(signature.getRecId()).isEqualTo((byte) 0); + assertThat(signature.getR().toString(16)) + .isEqualTo("840798fa67118e034c1eb7e42fe89e28d7cd5006dc813d5729e5f75b0d1a7ec5"); + assertThat(signature.getS().toString(16)) + .isEqualTo("3b1dbace38ceb862a65bf2eac0637693b5c3493bcb2a022dd614c0a74cce0b99"); + } + + @Test + void shouldDecodeInnerPayloadWithNonceZero() { + // "0xd70194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c5" + + final BytesValueRLPInput input = + new BytesValueRLPInput( + Bytes.fromHexString( + "0xf85a0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa568001a0dd6b24048be1b7d7fe5bbbb73ffc37eb2ce1997ecb4ae5b6096532ef19363148a025b58a1ff8ad00bddbbfa1d5c2411961cbb6d08dcdc8ae88303db3c6cf983031"), + true); + final CodeDelegation authorization = CodeDelegationTransactionDecoder.decodeInnerPayload(input); + + assertThat(authorization.chainId()).isEqualTo(BigInteger.ONE); + assertThat(authorization.address()) + .isEqualTo(Address.fromHexStringStrict("0x633688abc3cCf8B0C03088D2d1C6ae4958c2fA56")); + assertThat(authorization.nonce()).isEqualTo(0); + + final SECPSignature signature = authorization.signature(); + assertThat(signature.getRecId()).isEqualTo((byte) 1); + assertThat(signature.getR().toString(16)) + .isEqualTo("dd6b24048be1b7d7fe5bbbb73ffc37eb2ce1997ecb4ae5b6096532ef19363148"); + assertThat(signature.getS().toString(16)) + .isEqualTo("25b58a1ff8ad00bddbbfa1d5c2411961cbb6d08dcdc8ae88303db3c6cf983031"); + } + + @Test + void shouldDecodeInnerPayloadWithChainIdZero() { + // "d70094633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c5" + + final BytesValueRLPInput input = + new BytesValueRLPInput( + Bytes.fromHexString( + "0xf85a8094633688abc3ccf8b0c03088d2d1c6ae4958c2fa560501a0025c1240d7ffec0daeedb752d3357aff2e3cd58468f0c2d43ee0ee999e02ace2a03c8a25b2becd6e666f69803d1ae3322f2e137b7745c2c7f19da80f993ffde4df"), + true); + final CodeDelegation authorization = CodeDelegationTransactionDecoder.decodeInnerPayload(input); + + assertThat(authorization.chainId()).isEqualTo(BigInteger.ZERO); + assertThat(authorization.address()) + .isEqualTo(Address.fromHexStringStrict("0x633688abc3cCf8B0C03088D2d1C6ae4958c2fA56")); + assertThat(authorization.nonce()).isEqualTo(5); + + final SECPSignature signature = authorization.signature(); + assertThat(signature.getRecId()).isEqualTo((byte) 1); + assertThat(signature.getR().toString(16)) + .isEqualTo("25c1240d7ffec0daeedb752d3357aff2e3cd58468f0c2d43ee0ee999e02ace2"); + assertThat(signature.getS().toString(16)) + .isEqualTo("3c8a25b2becd6e666f69803d1ae3322f2e137b7745c2c7f19da80f993ffde4df"); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationEncoderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationEncoderTest.java new file mode 100644 index 00000000000..34b7c6d4491 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/CodeDelegationEncoderTest.java @@ -0,0 +1,121 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core.encoding; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import org.hyperledger.besu.crypto.SignatureAlgorithm; +import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.core.CodeDelegation; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; + +import java.math.BigInteger; +import java.util.function.Supplier; + +import com.google.common.base.Suppliers; +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class CodeDelegationEncoderTest { + private static final Supplier SIGNATURE_ALGORITHM = + Suppliers.memoize(SignatureAlgorithmFactory::getInstance); + + BytesValueRLPOutput output; + + @BeforeEach + void setUp() { + output = new BytesValueRLPOutput(); + } + + @Test + void shouldEncodeSingleCodeDelegationWithNonceAndChainId() { + // "0xd80194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c105" + + final CodeDelegation authorization = + new CodeDelegation( + BigInteger.ONE, + Address.fromHexString("0x633688abc3cCf8B0C03088D2d1C6ae4958c2fA56"), + 42, + SIGNATURE_ALGORITHM + .get() + .createSignature( + new BigInteger( + "840798fa67118e034c1eb7e42fe89e28d7cd5006dc813d5729e5f75b0d1a7ec5", 16), + new BigInteger( + "3b1dbace38ceb862a65bf2eac0637693b5c3493bcb2a022dd614c0a74cce0b99", 16), + (byte) 0)); + + CodeDelegationEncoder.encodeSingleCodeDelegation(authorization, output); + + assertThat(output.encoded()) + .isEqualTo( + Bytes.fromHexString( + "0xf85a0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa562a80a0840798fa67118e034c1eb7e42fe89e28d7cd5006dc813d5729e5f75b0d1a7ec5a03b1dbace38ceb862a65bf2eac0637693b5c3493bcb2a022dd614c0a74cce0b99")); + } + + @Test + void shouldEncodeSingleCodeDelegationWithNonceZero() { + // "0xd70194633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c5" + + final CodeDelegation authorization = + new CodeDelegation( + BigInteger.ONE, + Address.fromHexString("0x633688abc3cCf8B0C03088D2d1C6ae4958c2fA56"), + 0, + SIGNATURE_ALGORITHM + .get() + .createSignature( + new BigInteger( + "dd6b24048be1b7d7fe5bbbb73ffc37eb2ce1997ecb4ae5b6096532ef19363148", 16), + new BigInteger( + "25b58a1ff8ad00bddbbfa1d5c2411961cbb6d08dcdc8ae88303db3c6cf983031", 16), + (byte) 1)); + + CodeDelegationEncoder.encodeSingleCodeDelegation(authorization, output); + + assertThat(output.encoded()) + .isEqualTo( + Bytes.fromHexString( + "0xf85a0194633688abc3ccf8b0c03088d2d1c6ae4958c2fa568001a0dd6b24048be1b7d7fe5bbbb73ffc37eb2ce1997ecb4ae5b6096532ef19363148a025b58a1ff8ad00bddbbfa1d5c2411961cbb6d08dcdc8ae88303db3c6cf983031")); + } + + @Test + void shouldEncodeSingleCodeDelegationWithChainIdZero() { + // "d70094633688abc3ccf8b0c03088d2d1c6ae4958c2fa56c5" + + final CodeDelegation authorization = + new CodeDelegation( + BigInteger.ZERO, + Address.fromHexString("0x633688abc3cCf8B0C03088D2d1C6ae4958c2fA56"), + 5, + SIGNATURE_ALGORITHM + .get() + .createSignature( + new BigInteger( + "25c1240d7ffec0daeedb752d3357aff2e3cd58468f0c2d43ee0ee999e02ace2", 16), + new BigInteger( + "3c8a25b2becd6e666f69803d1ae3322f2e137b7745c2c7f19da80f993ffde4df", 16), + (byte) 1)); + + CodeDelegationEncoder.encodeSingleCodeDelegation(authorization, output); + + assertThat(output.encoded()) + .isEqualTo( + Bytes.fromHexString( + "0xf85a8094633688abc3ccf8b0c03088d2d1c6ae4958c2fa560501a0025c1240d7ffec0daeedb752d3357aff2e3cd58468f0c2d43ee0ee999e02ace2a03c8a25b2becd6e666f69803d1ae3322f2e137b7745c2c7f19da80f993ffde4df")); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/ConsolidationRequestDecoderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/ConsolidationRequestDecoderTest.java new file mode 100644 index 00000000000..bb264dfa23f --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/ConsolidationRequestDecoderTest.java @@ -0,0 +1,46 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core.encoding; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.ethereum.core.ConsolidationRequest; +import org.hyperledger.besu.ethereum.core.Request; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.ethereum.rlp.RLP; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +class ConsolidationRequestDecoderTest { + + @Test + public void shouldDecodeWithdrawalRequest() { + final ConsolidationRequest expectedConsolidationRequest = + new ConsolidationRequest( + Address.fromHexString("0x814FaE9f487206471B6B0D713cD51a2D35980000"), + BLSPublicKey.fromHexString( + "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e"), + BLSPublicKey.fromHexString( + "0xa09a4a15bf67b328c9b101d09e5c6ee6672978f7ad9ef0d9e2c457aee99223555d8601f0cb3bcc4ce1af9864779a416e")); + + final BytesValueRLPOutput out = new BytesValueRLPOutput(); + expectedConsolidationRequest.writeTo(out); + + final Request decodedWithdrawalRequest = RequestDecoder.decode(RLP.input(out.encoded())); + + Assertions.assertThat(decodedWithdrawalRequest).isEqualTo(expectedConsolidationRequest); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/ConsolidationRequestEncoderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/ConsolidationRequestEncoderTest.java new file mode 100644 index 00000000000..10a53379b83 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/ConsolidationRequestEncoderTest.java @@ -0,0 +1,55 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core.encoding; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.ethereum.core.ConsolidationRequest; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; + +class ConsolidationRequestEncoderTest { + + private final String expectedEncodedBytes = + "f87794763c396673f9c391dce3361a9a71c8e161388000b0b10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416eb0a09a4a15bf67b328c9b101d09e5c6ee6672978f7ad9ef0d9e2c457aee99223555d8601f0cb3bcc4ce1af9864779a416e"; + + final ConsolidationRequest consolidationRequest = + new ConsolidationRequest( + Address.fromHexString("0x763c396673F9c391DCe3361A9A71C8E161388000"), + BLSPublicKey.fromHexString( + "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e"), + BLSPublicKey.fromHexString( + "0xa09a4a15bf67b328c9b101d09e5c6ee6672978f7ad9ef0d9e2c457aee99223555d8601f0cb3bcc4ce1af9864779a416e")); + + @Test + void shouldEncodeConsolidationRequest() { + final Bytes encoded = ConsolidationRequestEncoder.encodeOpaqueBytes(consolidationRequest); + assertThat(encoded).isEqualTo(Bytes.fromHexString(expectedEncodedBytes)); + } + + @Test + void shouldEncodeRequest() { + final Bytes encoded = RequestEncoder.encodeOpaqueBytes(consolidationRequest); + assertThat(encoded) + .isEqualTo( + Bytes.fromHexString( + String.format( + "0x%02X%s", + consolidationRequest.getType().getSerializedType(), expectedEncodedBytes))); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/DepositDecoderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/DepositDecoderTest.java deleted file mode 100644 index 5939611c5e9..00000000000 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/DepositDecoderTest.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright contributors to Hyperledger Besu - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.core.encoding; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.BLSPublicKey; -import org.hyperledger.besu.datatypes.BLSSignature; -import org.hyperledger.besu.datatypes.GWei; -import org.hyperledger.besu.ethereum.core.Deposit; -import org.hyperledger.besu.evm.log.Log; -import org.hyperledger.besu.evm.log.LogTopic; - -import java.util.List; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt64; -import org.junit.jupiter.api.Test; - -class DepositDecoderTest { - @Test - void shouldDecodeDeposit() { - final Deposit expectedDeposit = - new Deposit( - BLSPublicKey.fromHexString( - "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e"), - Bytes32.fromHexString( - "0x0017a7fcf06faf493d30bbe2632ea7c2383cd86825e12797165de7aa35589483"), - GWei.of(32000000000L), - BLSSignature.fromHexString( - "0xa889db8300194050a2636c92a95bc7160515867614b7971a9500cdb62f9c0890217d2901c3241f86fac029428fc106930606154bd9e406d7588934a5f15b837180b17194d6e44bd6de23e43b163dfe12e369dcc75a3852cd997963f158217eb5"), - UInt64.ONE); - - final Deposit deposit = - DepositDecoder.decodeOpaqueBytes( - Bytes.fromHexString( - "0xf8bbb0b10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416ea00017a7fcf06faf493d30bbe2632ea7c2383cd86825e12797165de7aa35589483850773594000b860a889db8300194050a2636c92a95bc7160515867614b7971a9500cdb62f9c0890217d2901c3241f86fac029428fc106930606154bd9e406d7588934a5f15b837180b17194d6e44bd6de23e43b163dfe12e369dcc75a3852cd997963f158217eb501")); - - assertThat(deposit).isEqualTo(expectedDeposit); - } - - @Test - void shouldDecodeDepositFromLog() { - final Address address = Address.fromHexString("0x00000000219ab540356cbb839cbe05303d7705fa"); - final List topics = - List.of( - LogTopic.fromHexString( - "0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5")); - final Bytes data = - Bytes.fromHexString( - "0x00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030b10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200017a7fcf06faf493d30bbe2632ea7c2383cd86825e12797165de7aa35589483000000000000000000000000000000000000000000000000000000000000000800405973070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060a889db8300194050a2636c92a95bc7160515867614b7971a9500cdb62f9c0890217d2901c3241f86fac029428fc106930606154bd9e406d7588934a5f15b837180b17194d6e44bd6de23e43b163dfe12e369dcc75a3852cd997963f158217eb500000000000000000000000000000000000000000000000000000000000000083f3d080000000000000000000000000000000000000000000000000000000000"); - - final Log log = new Log(address, data, topics); - final Deposit deposit = DepositDecoder.decodeFromLog(log); - - final Deposit expectedDeposit = - new Deposit( - BLSPublicKey.fromHexString( - "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e"), - Bytes32.fromHexString( - "0x0017a7fcf06faf493d30bbe2632ea7c2383cd86825e12797165de7aa35589483"), - GWei.of(32000000000L), - BLSSignature.fromHexString( - "0xa889db8300194050a2636c92a95bc7160515867614b7971a9500cdb62f9c0890217d2901c3241f86fac029428fc106930606154bd9e406d7588934a5f15b837180b17194d6e44bd6de23e43b163dfe12e369dcc75a3852cd997963f158217eb5"), - UInt64.valueOf(539967)); - - assertThat(deposit).isEqualTo(expectedDeposit); - } -} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/DepositEncoderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/DepositEncoderTest.java deleted file mode 100644 index b0d15607cba..00000000000 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/DepositEncoderTest.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright contributors to Hyperledger Besu - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.core.encoding; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.datatypes.BLSPublicKey; -import org.hyperledger.besu.datatypes.BLSSignature; -import org.hyperledger.besu.datatypes.GWei; -import org.hyperledger.besu.ethereum.core.Deposit; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt64; -import org.junit.jupiter.api.Test; - -class DepositEncoderTest { - @Test - void shouldEncodeDeposit() { - final Deposit deposit = - new Deposit( - BLSPublicKey.fromHexString( - "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e"), - Bytes32.fromHexString( - "0x0017a7fcf06faf493d30bbe2632ea7c2383cd86825e12797165de7aa35589483"), - GWei.of(32000000000L), - BLSSignature.fromHexString( - "0xa889db8300194050a2636c92a95bc7160515867614b7971a9500cdb62f9c0890217d2901c3241f86fac029428fc106930606154bd9e406d7588934a5f15b837180b17194d6e44bd6de23e43b163dfe12e369dcc75a3852cd997963f158217eb5"), - UInt64.ONE); - - final Bytes encoded = DepositEncoder.encodeOpaqueBytes(deposit); - - assertThat(encoded) - .isEqualTo( - Bytes.fromHexString( - "0xf8bbb0b10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416ea00017a7fcf06faf493d30bbe2632ea7c2383cd86825e12797165de7aa35589483850773594000b860a889db8300194050a2636c92a95bc7160515867614b7971a9500cdb62f9c0890217d2901c3241f86fac029428fc106930606154bd9e406d7588934a5f15b837180b17194d6e44bd6de23e43b163dfe12e369dcc75a3852cd997963f158217eb501")); - } -} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/DepositRequestDecoderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/DepositRequestDecoderTest.java new file mode 100644 index 00000000000..622bd897545 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/DepositRequestDecoderTest.java @@ -0,0 +1,83 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core.encoding; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.datatypes.BLSSignature; +import org.hyperledger.besu.datatypes.GWei; +import org.hyperledger.besu.ethereum.core.DepositRequest; +import org.hyperledger.besu.evm.log.Log; +import org.hyperledger.besu.evm.log.LogTopic; + +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt64; +import org.junit.jupiter.api.Test; + +class DepositRequestDecoderTest { + @Test + void shouldDecodeDeposit() { + final DepositRequest expectedDepositRequest = + new DepositRequest( + BLSPublicKey.fromHexString( + "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e"), + Bytes32.fromHexString( + "0x0017a7fcf06faf493d30bbe2632ea7c2383cd86825e12797165de7aa35589483"), + GWei.of(32000000000L), + BLSSignature.fromHexString( + "0xa889db8300194050a2636c92a95bc7160515867614b7971a9500cdb62f9c0890217d2901c3241f86fac029428fc106930606154bd9e406d7588934a5f15b837180b17194d6e44bd6de23e43b163dfe12e369dcc75a3852cd997963f158217eb5"), + UInt64.ONE); + + final DepositRequest depositRequest = + DepositRequestDecoder.decodeOpaqueBytes( + Bytes.fromHexString( + "0xf8bbb0b10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416ea00017a7fcf06faf493d30bbe2632ea7c2383cd86825e12797165de7aa35589483850773594000b860a889db8300194050a2636c92a95bc7160515867614b7971a9500cdb62f9c0890217d2901c3241f86fac029428fc106930606154bd9e406d7588934a5f15b837180b17194d6e44bd6de23e43b163dfe12e369dcc75a3852cd997963f158217eb501")); + + assertThat(depositRequest).isEqualTo(expectedDepositRequest); + } + + @Test + void shouldDecodeDepositFromLog() { + final Address address = Address.fromHexString("0x00000000219ab540356cbb839cbe05303d7705fa"); + final List topics = + List.of( + LogTopic.fromHexString( + "0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5")); + final Bytes data = + Bytes.fromHexString( + "0x00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030b10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200017a7fcf06faf493d30bbe2632ea7c2383cd86825e12797165de7aa35589483000000000000000000000000000000000000000000000000000000000000000800405973070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060a889db8300194050a2636c92a95bc7160515867614b7971a9500cdb62f9c0890217d2901c3241f86fac029428fc106930606154bd9e406d7588934a5f15b837180b17194d6e44bd6de23e43b163dfe12e369dcc75a3852cd997963f158217eb500000000000000000000000000000000000000000000000000000000000000083f3d080000000000000000000000000000000000000000000000000000000000"); + + final Log log = new Log(address, data, topics); + final DepositRequest depositRequest = DepositRequestDecoder.decodeFromLog(log); + + final DepositRequest expectedDepositRequest = + new DepositRequest( + BLSPublicKey.fromHexString( + "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e"), + Bytes32.fromHexString( + "0x0017a7fcf06faf493d30bbe2632ea7c2383cd86825e12797165de7aa35589483"), + GWei.of(32000000000L), + BLSSignature.fromHexString( + "0xa889db8300194050a2636c92a95bc7160515867614b7971a9500cdb62f9c0890217d2901c3241f86fac029428fc106930606154bd9e406d7588934a5f15b837180b17194d6e44bd6de23e43b163dfe12e369dcc75a3852cd997963f158217eb5"), + UInt64.valueOf(539967)); + + assertThat(depositRequest).isEqualTo(expectedDepositRequest); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/DepositRequestEncoderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/DepositRequestEncoderTest.java new file mode 100644 index 00000000000..1e6dde6aa87 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/DepositRequestEncoderTest.java @@ -0,0 +1,61 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core.encoding; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.datatypes.BLSSignature; +import org.hyperledger.besu.datatypes.GWei; +import org.hyperledger.besu.ethereum.core.DepositRequest; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt64; +import org.junit.jupiter.api.Test; + +class DepositRequestEncoderTest { + private final String expectedDepositEncodedBytes = + "f8bbb0b10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416ea00017a7fcf06faf493d30bbe2632ea7c2383cd86825e12797165de7aa35589483850773594000b860a889db8300194050a2636c92a95bc7160515867614b7971a9500cdb62f9c0890217d2901c3241f86fac029428fc106930606154bd9e406d7588934a5f15b837180b17194d6e44bd6de23e43b163dfe12e369dcc75a3852cd997963f158217eb501"; + + final DepositRequest depositRequest = + new DepositRequest( + BLSPublicKey.fromHexString( + "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e"), + Bytes32.fromHexString( + "0x0017a7fcf06faf493d30bbe2632ea7c2383cd86825e12797165de7aa35589483"), + GWei.of(32000000000L), + BLSSignature.fromHexString( + "0xa889db8300194050a2636c92a95bc7160515867614b7971a9500cdb62f9c0890217d2901c3241f86fac029428fc106930606154bd9e406d7588934a5f15b837180b17194d6e44bd6de23e43b163dfe12e369dcc75a3852cd997963f158217eb5"), + UInt64.ONE); + + @Test + void shouldEncodeDeposit() { + final Bytes encoded = DepositRequestEncoder.encodeOpaqueBytes(depositRequest); + assertThat(encoded).isEqualTo(Bytes.fromHexString(expectedDepositEncodedBytes)); + } + + @Test + void shouldEncodeDepositRequest() { + final Bytes encoded = RequestEncoder.encodeOpaqueBytes(depositRequest); + // Request encoding is Request = RequestType ++ RequestData + assertThat(encoded) + .isEqualTo( + Bytes.fromHexString( + String.format( + "0x%02X%s", + depositRequest.getType().getSerializedType(), expectedDepositEncodedBytes))); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalDecoderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalDecoderTest.java index a23bf24cff8..9ffd219db0a 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalDecoderTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalDecoderTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalEncoderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalEncoderTest.java index 7dd79e229a0..3e4d81f02be 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalEncoderTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalEncoderTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalRequestDecoderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalRequestDecoderTest.java new file mode 100644 index 00000000000..ad6da667c1e --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalRequestDecoderTest.java @@ -0,0 +1,46 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core.encoding; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.datatypes.GWei; +import org.hyperledger.besu.ethereum.core.Request; +import org.hyperledger.besu.ethereum.core.WithdrawalRequest; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.ethereum.rlp.RLP; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +class WithdrawalRequestDecoderTest { + + @Test + public void shouldDecodeWithdrawalRequest() { + final WithdrawalRequest expectedWithdrawalRequest = + new WithdrawalRequest( + Address.fromHexString("0x814FaE9f487206471B6B0D713cD51a2D35980000"), + BLSPublicKey.fromHexString( + "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e"), + GWei.of(5)); + + final BytesValueRLPOutput out = new BytesValueRLPOutput(); + expectedWithdrawalRequest.writeTo(out); + + final Request decodedWithdrawalRequest = RequestDecoder.decode(RLP.input(out.encoded())); + + Assertions.assertThat(decodedWithdrawalRequest).isEqualTo(expectedWithdrawalRequest); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalRequestEncoderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalRequestEncoderTest.java new file mode 100644 index 00000000000..14c9cfacef2 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/encoding/WithdrawalRequestEncoderTest.java @@ -0,0 +1,55 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.core.encoding; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.datatypes.GWei; +import org.hyperledger.besu.ethereum.core.WithdrawalRequest; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; + +class WithdrawalRequestEncoderTest { + + private final String expectedEncodedBytes = + "f84794763c396673f9c391dce3361a9a71c8e161388000b0b10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e05"; + + final WithdrawalRequest withdrawalRequest = + new WithdrawalRequest( + Address.fromHexString("0x763c396673F9c391DCe3361A9A71C8E161388000"), + BLSPublicKey.fromHexString( + "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e"), + GWei.of(5)); + + @Test + void shouldEncodeWithdrawalRequest() { + final Bytes encoded = WithdrawalRequestEncoder.encodeOpaqueBytes(withdrawalRequest); + assertThat(encoded).isEqualTo(Bytes.fromHexString(expectedEncodedBytes)); + } + + @Test + void shouldEncodeRequest() { + final Bytes encoded = RequestEncoder.encodeOpaqueBytes(withdrawalRequest); + assertThat(encoded) + .isEqualTo( + Bytes.fromHexString( + String.format( + "0x%02X%s", + withdrawalRequest.getType().getSerializedType(), expectedEncodedBytes))); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/feemarket/BaseFeeMarketBaseFeeTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/feemarket/BaseFeeMarketBaseFeeTest.java index d6d94c8453d..e657c5c2e20 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/feemarket/BaseFeeMarketBaseFeeTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/feemarket/BaseFeeMarketBaseFeeTest.java @@ -30,6 +30,7 @@ import com.google.common.base.Charsets; import com.google.common.io.Resources; import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -99,4 +100,11 @@ public void setExpectedBaseFee(final Wei expectedBaseFee) { this.expectedBaseFee = expectedBaseFee; } } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/feemarket/CoinbaseFeePriceCalculatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/feemarket/CoinbaseFeePriceCalculatorTest.java index 2706444f83f..1da9a689cf2 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/feemarket/CoinbaseFeePriceCalculatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/feemarket/CoinbaseFeePriceCalculatorTest.java @@ -21,6 +21,7 @@ import java.util.Optional; import java.util.stream.Stream; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -54,4 +55,11 @@ public void assertThatCalculatorWorks( assertThat(coinbaseFeePriceCalculator.price(coinbaseFee, transactionGasPrice, baseFee)) .isEqualByComparingTo(expectedPrice); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/feemarket/TransactionPriceCalculatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/feemarket/TransactionPriceCalculatorTest.java index c23061ac8f1..df07fd1f752 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/feemarket/TransactionPriceCalculatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/core/feemarket/TransactionPriceCalculatorTest.java @@ -29,6 +29,7 @@ import java.util.Optional; import java.util.stream.Stream; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -146,4 +147,11 @@ public void assertThatCalculatorWorks( baseFee)) .isEqualByComparingTo(expectedPrice); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/difficulty/fixed/FixedProtocolScheduleTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/difficulty/fixed/FixedProtocolScheduleTest.java index 9f192cf833b..7969ab6824a 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/difficulty/fixed/FixedProtocolScheduleTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/difficulty/fixed/FixedProtocolScheduleTest.java @@ -17,10 +17,13 @@ import static org.assertj.core.api.Assertions.assertThat; import org.hyperledger.besu.config.GenesisConfigFile; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.junit.jupiter.api.Test; @@ -31,7 +34,12 @@ public void reportedDifficultyForAllBlocksIsAFixedValue() { final ProtocolSchedule schedule = FixedDifficultyProtocolSchedule.create( - GenesisConfigFile.development().getConfigOptions(), EvmConfiguration.DEFAULT); + GenesisConfigFile.fromResource("/dev.json").getConfigOptions(), + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); final BlockHeaderTestFixture headerBuilder = new BlockHeaderTestFixture(); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/forkid/ForkIdBackwardCompatibilityTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/forkid/ForkIdBackwardCompatibilityTest.java index cd28e6e9aa6..df97386e9f0 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/forkid/ForkIdBackwardCompatibilityTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/forkid/ForkIdBackwardCompatibilityTest.java @@ -18,12 +18,15 @@ import static org.hyperledger.besu.ethereum.forkid.ForkIdTestUtil.GenesisHash; import static org.hyperledger.besu.ethereum.forkid.ForkIdTestUtil.mockBlockchain; +import org.hyperledger.besu.ethereum.chain.Blockchain; + import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -76,6 +79,13 @@ public static Stream data() { 8L, Arrays.asList(0L, 0L, 4L, 5L, 6L), true, + null), + Arguments.of( + "no forks and legacyEth64=true", + GenesisHash.PRIVATE, + 8L, + Collections.emptyList(), + true, null)); } @@ -89,14 +99,19 @@ public void assertBackwardCompatibilityWorks( final boolean legacyEth64, final ForkId wantForkId) { LOG.info("Running test case {}", name); + final Blockchain blockchain = mockBlockchain(genesisHash, head, 0); final ForkIdManager forkIdManager = - new ForkIdManager( - mockBlockchain(genesisHash, head, 0), forks, Collections.emptyList(), legacyEth64); + new ForkIdManager(blockchain, forks, Collections.emptyList(), legacyEth64); final ForkId legacyForkId = - legacyEth64 - ? new LegacyForkIdManager(mockBlockchain(genesisHash, head, 0), forks).getLatestForkId() - : null; + legacyEth64 ? new LegacyForkIdManager(blockchain, forks).getLatestForkId() : null; assertThat(forkIdManager.getForkIdForChainHead()) .isEqualTo(legacyEth64 ? legacyForkId : wantForkId); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/forkid/ForkIdTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/forkid/ForkIdTest.java index c9c727286cd..e3a4c272bd4 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/forkid/ForkIdTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/forkid/ForkIdTest.java @@ -297,39 +297,6 @@ public static Stream data() { ForkIdTestUtil.wantForkId("0xf7f9bc08", 0L), Optional.of(ForkIds.SEPOLIA), empty()), - // goerli - Arguments.of( - "Goerli // Unsynced, last Frontier, Homestead, Tangerine, Spurious, Byzantium, Constantinople and first Petersburg block", - Network.GOERLI, - 0L, - 0L, - ForkIdTestUtil.wantForkId("0xa3f5ab08", 1561651L), - Optional.of(ForkIds.GOERLI), - empty()), - Arguments.of( - "Goerli // Last Petersburg block", - Network.GOERLI, - 1561650L, - 0L, - ForkIdTestUtil.wantForkId("0xa3f5ab08", 1561651L), - Optional.of(ForkIds.GOERLI), - empty()), - Arguments.of( - "Goerli // First Istanbul block", - Network.GOERLI, - 1561651L, - 0L, - ForkIdTestUtil.wantForkId("0xc25efa5c", 0L), - Optional.of(ForkIds.GOERLI), - empty()), - Arguments.of( - "Goerli // Future Istanbul block", - Network.GOERLI, - 2000000L, - 0L, - ForkIdTestUtil.wantForkId("0xc25efa5c", 0L), - Optional.of(ForkIds.GOERLI), - empty()), // private Arguments.of( "Private // Unsynced", diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/forkid/ForkIdTestUtil.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/forkid/ForkIdTestUtil.java index 7fc23c2d475..d165074c3d2 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/forkid/ForkIdTestUtil.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/forkid/ForkIdTestUtil.java @@ -64,8 +64,6 @@ public static class GenesisHash { "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"; public static final String SEPOLIA = "0x25a5cc106eea7138acab33231d7160d69cb777ee0c2c553fcddf5138993e6dd9"; - public static final String GOERLI = - "0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a"; public static final String PRIVATE = "0x0000000000000000000000000000000000000000000000000000000000000000"; } @@ -80,7 +78,6 @@ public static class Forks { public static final List SEPOLIA_TIMESTAMPS = Arrays.asList(1677557088L); - public static final List GOERLI = Arrays.asList(0L, 0L, 0L, 0L, 0L, 0L, 0L, 1561651L); public static final List PRIVATE = Arrays.asList(0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L); public static final List MAINNET_WITH_SHANGHAI_BLOCKS = @@ -111,10 +108,6 @@ public static class ForkIds { new ForkId(Bytes.fromHexString("0xfe3366e7"), 1735371L), new ForkId(Bytes.fromHexString("0xb96cbd13"), 1677557088L), new ForkId(Bytes.fromHexString("0xf7f9bc08"), 0L)); // First Shanghai block (timestamp) - public static final List GOERLI = - Arrays.asList( - new ForkId(Bytes.fromHexString("0xa3f5ab08"), 1561651L), - new ForkId(Bytes.fromHexString("0xc25efa5c"), 0L)); public static final List WITHDRAWALS = Arrays.asList( @@ -141,7 +134,6 @@ public static class Network { public static final Network MAINNET = network(GenesisHash.MAINNET, Forks.MAINNET, emptyList()); public static final Network SEPOLIA = network(GenesisHash.SEPOLIA, Forks.SEPOLIA_BLOCKNUMBERS, Forks.SEPOLIA_TIMESTAMPS); - public static final Network GOERLI = network(GenesisHash.GOERLI, Forks.GOERLI, emptyList()); public static final Network PRIVATE = network(GenesisHash.PRIVATE, Forks.PRIVATE, emptyList()); public static final Network MAINNET_WITH_SHANGHAI = diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/forkid/LegacyForkIdManager.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/forkid/LegacyForkIdManager.java index 325fd41c812..bc9196e6d50 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/forkid/LegacyForkIdManager.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/forkid/LegacyForkIdManager.java @@ -20,7 +20,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; import java.util.zip.CRC32; import org.apache.tuweni.bytes.Bytes; @@ -30,12 +29,13 @@ public class LegacyForkIdManager { private final Hash genesisHash; private final List forks; private List forkAndHashList; + private CRC32 crc; + private Bytes genesisHashCrc; public LegacyForkIdManager(final Blockchain blockchain, final List forks) { this.genesisHash = blockchain.getGenesisBlock().getHash(); // de-dupe and sanitize forks - this.forks = - forks.stream().filter(fork -> fork > 0).distinct().collect(Collectors.toUnmodifiableList()); + this.forks = forks.stream().filter(fork -> fork > 0).distinct().toList(); createForkIds(); } @@ -44,10 +44,10 @@ public List getForkAndHashList() { } public ForkId getLatestForkId() { - if (forkAndHashList.size() > 0) { - return forkAndHashList.get(forkAndHashList.size() - 1); + if (!forkAndHashList.isEmpty()) { + return forkAndHashList.getLast(); } - return null; + return new ForkId(genesisHashCrc, 0); } public static ForkId readFrom(final RLPInput in) { @@ -59,8 +59,9 @@ public static ForkId readFrom(final RLPInput in) { } private void createForkIds() { - final CRC32 crc = new CRC32(); + crc = new CRC32(); crc.update(genesisHash.toArray()); + genesisHashCrc = getCurrentCrcHash(crc); final List forkHashes = new ArrayList<>(List.of(getCurrentCrcHash(crc))); for (final Long fork : forks) { updateCrc(crc, fork); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessorIntegrationTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessorIntegrationTest.java new file mode 100644 index 00000000000..c9e43327d96 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessorIntegrationTest.java @@ -0,0 +1,794 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.hyperledger.besu.config.GenesisConfigFile; +import org.hyperledger.besu.crypto.KeyPair; +import org.hyperledger.besu.crypto.SECPPrivateKey; +import org.hyperledger.besu.crypto.SignatureAlgorithm; +import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.TransactionType; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.BlockProcessingResult; +import org.hyperledger.besu.ethereum.chain.DefaultBlockchain; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockBody; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.core.ExecutionContextTestFixture; +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.mainnet.parallelization.MainnetParallelBlockProcessor; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.BonsaiAccount; +import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.web3j.abi.FunctionEncoder; +import org.web3j.abi.datatypes.Function; +import org.web3j.abi.datatypes.Type; +import org.web3j.abi.datatypes.generated.Uint256; + +@SuppressWarnings("rawtypes") +class AbstractBlockProcessorIntegrationTest { + + private static final String ACCOUNT_GENESIS_1 = "0x627306090abab3a6e1400e9345bc60c78a8bef57"; + private static final String ACCOUNT_GENESIS_2 = "0x7f2d653f56ea8de6ffa554c7a0cd4e03af79f3eb"; + + private static final String ACCOUNT_2 = "0x0000000000000000000000000000000000000002"; + private static final String ACCOUNT_3 = "0x0000000000000000000000000000000000000003"; + private static final String ACCOUNT_4 = "0x0000000000000000000000000000000000000004"; + private static final String ACCOUNT_5 = "0x0000000000000000000000000000000000000005"; + private static final String ACCOUNT_6 = "0x0000000000000000000000000000000000000006"; + private static final String CONTRACT_ADDRESS = "0x00000000000000000000000000000000000fffff"; + + private static final KeyPair ACCOUNT_GENESIS_1_KEYPAIR = + generateKeyPair("c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3"); + + private static final KeyPair ACCOUNT_GENESIS_2_KEYPAIR = + generateKeyPair("fc5141e75bf622179f8eedada7fab3e2e6b3e3da8eb9df4f46d84df22df7430e"); + + private WorldStateArchive worldStateArchive; + private DefaultBlockchain blockchain; + private Address coinbase; + + @BeforeEach + public void setUp() { + final ExecutionContextTestFixture contextTestFixture = + ExecutionContextTestFixture.builder( + GenesisConfigFile.fromResource( + "/org/hyperledger/besu/ethereum/mainnet/genesis-bp-it.json")) + .dataStorageFormat(DataStorageFormat.BONSAI) + .build(); + final BlockHeader blockHeader = new BlockHeaderTestFixture().number(0L).buildHeader(); + coinbase = blockHeader.getCoinbase(); + worldStateArchive = contextTestFixture.getStateArchive(); + blockchain = (DefaultBlockchain) contextTestFixture.getBlockchain(); + } + + private static Stream blockProcessorProvider() { + final ExecutionContextTestFixture contextTestFixture = + ExecutionContextTestFixture.builder( + GenesisConfigFile.fromResource( + "/org/hyperledger/besu/ethereum/mainnet/genesis-bp-it.json")) + .dataStorageFormat(DataStorageFormat.BONSAI) + .build(); + final ProtocolSchedule protocolSchedule = contextTestFixture.getProtocolSchedule(); + final BlockHeader blockHeader = new BlockHeaderTestFixture().number(0L).buildHeader(); + final MainnetTransactionProcessor transactionProcessor = + protocolSchedule.getByBlockHeader(blockHeader).getTransactionProcessor(); + + final BlockProcessor sequentialBlockProcessor = + new MainnetBlockProcessor( + transactionProcessor, + protocolSchedule + .getByBlockHeader(new BlockHeaderTestFixture().number(0L).buildHeader()) + .getTransactionReceiptFactory(), + Wei.of(2_000_000_000_000_000L), + BlockHeader::getCoinbase, + false, + protocolSchedule); + + final BlockProcessor parallelBlockProcessor = + new MainnetParallelBlockProcessor( + transactionProcessor, + protocolSchedule + .getByBlockHeader(new BlockHeaderTestFixture().number(0L).buildHeader()) + .getTransactionReceiptFactory(), + Wei.of(2_000_000_000_000_000L), + BlockHeader::getCoinbase, + false, + protocolSchedule, + new NoOpMetricsSystem()); + + return Stream.of( + Arguments.of("sequential", sequentialBlockProcessor), + Arguments.of("parallel", parallelBlockProcessor)); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("blockProcessorProvider") + void testBlockProcessingWithTransfers( + final String ignoredName, final BlockProcessor blockProcessor) { + processSimpleTransfers(blockProcessor); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("blockProcessorProvider") + void testProcessConflictedSimpleTransfersSameSender( + final String ignoredName, final BlockProcessor blockProcessor) { + processConflictedSimpleTransfersSameSender(blockProcessor); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("blockProcessorProvider") + void testProcessConflictedSimpleTransfersSameAddressReceiverAndSender( + final String ignoredName, final BlockProcessor blockProcessor) { + processConflictedSimpleTransfersSameAddressReceiverAndSender(blockProcessor); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("blockProcessorProvider") + void testProcessConflictedSimpleTransfersWithCoinbase( + final String ignoredName, final BlockProcessor blockProcessor) { + processConflictedSimpleTransfersWithCoinbase(blockProcessor); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("blockProcessorProvider") + void testProcessContractSlotUpdateThenReadTx( + final String ignoredName, final BlockProcessor blockProcessor) { + processContractSlotUpdateThenReadTx(blockProcessor); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("blockProcessorProvider") + void testProcessSlotReadThenUpdateTx( + final String ignoredName, final BlockProcessor blockProcessor) { + processSlotReadThenUpdateTx(blockProcessor); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("blockProcessorProvider") + void testProcessAccountReadThenUpdateTx( + final String ignoredName, final BlockProcessor blockProcessor) { + processAccountReadThenUpdateTx(blockProcessor); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("blockProcessorProvider") + void testProcessAccountUpdateThenReadTx( + final String ignoredName, final BlockProcessor blockProcessor) { + processAccountUpdateThenReadTx(blockProcessor); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("blockProcessorProvider") + void testProcessAccountReadThenUpdateTxWithTwoAccounts( + final String ignoredName, final BlockProcessor blockProcessor) { + processAccountReadThenUpdateTxWithTwoAccounts(blockProcessor); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("blockProcessorProvider") + void testProcessAccountUpdateThenReadTeTxWithTwoAccounts( + final String ignoredName, final BlockProcessor blockProcessor) { + processAccountUpdateThenReadTxWithTwoAccounts(blockProcessor); + } + + private void processSimpleTransfers(final BlockProcessor blockProcessor) { + // Create two non conflicted transactions + Transaction transactionTransfer1 = // ACCOUNT_GENESIS_1 -> ACCOUNT_2 + createTransferTransaction( + 0, 1_000_000_000_000_000_000L, 300000L, 5L, 7L, ACCOUNT_2, ACCOUNT_GENESIS_1_KEYPAIR); + Transaction transactionTransfer2 = // ACCOUNT_GENESIS_2 -> ACCOUNT_3 + createTransferTransaction( + 0, 2_000_000_000_000_000_000L, 300000L, 5L, 7L, ACCOUNT_3, ACCOUNT_GENESIS_2_KEYPAIR); + + MutableWorldState worldState = worldStateArchive.getMutable(); + BonsaiAccount senderAccount1 = (BonsaiAccount) worldState.get(transactionTransfer1.getSender()); + BonsaiAccount senderAccount2 = (BonsaiAccount) worldState.get(transactionTransfer2.getSender()); + + Block blockWithTransactions = + createBlockWithTransactions( + "0x4ca6e755674a1df696e5365361a0c352422934ba3ad0a74c9e6b0b56e4f80b4c", + transactionTransfer1, + transactionTransfer2); + + BlockProcessingResult blockProcessingResult = + blockProcessor.processBlock(blockchain, worldState, blockWithTransactions); + + BonsaiAccount updatedSenderAccount1 = + (BonsaiAccount) worldState.get(transactionTransfer1.getSender()); + BonsaiAccount updatedSenderAccount2 = + (BonsaiAccount) worldState.get(transactionTransfer2.getSender()); + + BonsaiAccount updatedAccount0x2 = + (BonsaiAccount) worldState.get(Address.fromHexStringStrict(ACCOUNT_2)); + BonsaiAccount updatedAccount0x3 = + (BonsaiAccount) worldState.get(Address.fromHexStringStrict(ACCOUNT_3)); + + assertTrue(blockProcessingResult.isSuccessful()); + assertThat(updatedAccount0x2.getBalance()).isEqualTo(Wei.of(1_000_000_000_000_000_000L)); + assertThat(updatedAccount0x3.getBalance()).isEqualTo(Wei.of(2_000_000_000_000_000_000L)); + assertThat(updatedSenderAccount1.getBalance()).isLessThan(senderAccount1.getBalance()); + assertThat(updatedSenderAccount2.getBalance()).isLessThan(senderAccount2.getBalance()); + } + + private void processConflictedSimpleTransfersSameSender(final BlockProcessor blockProcessor) { + // Create three transactions with the same sender + Transaction transferTransaction1 = // ACCOUNT_GENESIS_1 -> ACCOUNT_4 + createTransferTransaction( + 0, 1_000_000_000_000_000_000L, 300000L, 5L, 7L, ACCOUNT_4, ACCOUNT_GENESIS_1_KEYPAIR); + Transaction transferTransaction2 = // ACCOUNT_GENESIS_1 -> ACCOUNT_5 + createTransferTransaction( + 1, 2_000_000_000_000_000_000L, 300000L, 5L, 7L, ACCOUNT_5, ACCOUNT_GENESIS_1_KEYPAIR); + Transaction transferTransaction3 = // ACCOUNT_GENESIS_1 -> ACCOUNT_6 + createTransferTransaction( + 2, 3_000_000_000_000_000_000L, 300000L, 5L, 7L, ACCOUNT_6, ACCOUNT_GENESIS_1_KEYPAIR); + + MutableWorldState worldState = worldStateArchive.getMutable(); + BonsaiAccount senderAccount = (BonsaiAccount) worldState.get(transferTransaction1.getSender()); + + Block blockWithTransactions = + createBlockWithTransactions( + "0x7420935ee980cb06060f119ee3ee3dcd5a96989985938a3b3ca096558ad61484", + transferTransaction1, + transferTransaction2, + transferTransaction3); + + BlockProcessingResult blockProcessingResult = + blockProcessor.processBlock(blockchain, worldState, blockWithTransactions); + + BonsaiAccount updatedSenderAccount = + (BonsaiAccount) worldState.get(transferTransaction1.getSender()); + + BonsaiAccount updatedAccount0x4 = + (BonsaiAccount) worldState.get(Address.fromHexStringStrict(ACCOUNT_4)); + BonsaiAccount updatedAccount0x5 = + (BonsaiAccount) worldState.get(Address.fromHexStringStrict(ACCOUNT_5)); + BonsaiAccount updatedAccount0x6 = + (BonsaiAccount) worldState.get(Address.fromHexStringStrict(ACCOUNT_6)); + + assertTrue(blockProcessingResult.isSuccessful()); + assertThat(updatedAccount0x4.getBalance()).isEqualTo(Wei.of(1_000_000_000_000_000_000L)); + assertThat(updatedAccount0x5.getBalance()).isEqualTo(Wei.of(2_000_000_000_000_000_000L)); + assertThat(updatedAccount0x6.getBalance()).isEqualTo(Wei.of(3_000_000_000_000_000_000L)); + + assertThat(updatedSenderAccount.getBalance()).isLessThan(senderAccount.getBalance()); + } + + private void processConflictedSimpleTransfersSameAddressReceiverAndSender( + final BlockProcessor blockProcessor) { + // Create conflicted transfer transactions + Transaction transferTransaction1 = + createTransferTransaction( + 0, + 1_000_000_000_000_000_000L, + 300000L, + 5L, + 7L, + ACCOUNT_GENESIS_2, + ACCOUNT_GENESIS_1_KEYPAIR); // ACCOUNT_GENESIS_1 -> ACCOUNT_GENESIS_2 + Transaction transferTransaction2 = + createTransferTransaction( + 0, + 2_000_000_000_000_000_000L, + 300000L, + 5L, + 7L, + ACCOUNT_2, + ACCOUNT_GENESIS_2_KEYPAIR); // ACCOUNT_GENESIS_2 -> ACCOUNT_2 + + MutableWorldState worldState = worldStateArchive.getMutable(); + BonsaiAccount transferTransaction1Sender = + (BonsaiAccount) worldState.get(transferTransaction1.getSender()); + + Block blockWithTransactions = + createBlockWithTransactions( + "0x5c0158e79b66c86cf5e5256390b95add0c2e6891c24e72d71b9dbea5845fea72", + transferTransaction1, + transferTransaction2); + + BlockProcessingResult blockProcessingResult = + blockProcessor.processBlock(blockchain, worldState, blockWithTransactions); + + BonsaiAccount updatedSenderAccount1 = + (BonsaiAccount) worldState.get(transferTransaction1.getSender()); + BonsaiAccount updatedGenesisAccount1 = + (BonsaiAccount) worldState.get(Address.fromHexStringStrict(ACCOUNT_GENESIS_1)); + BonsaiAccount updatedGenesisAccount2 = + (BonsaiAccount) worldState.get(Address.fromHexStringStrict(ACCOUNT_GENESIS_2)); + BonsaiAccount updatedAccount0x2 = + (BonsaiAccount) worldState.get(Address.fromHexStringStrict(ACCOUNT_2)); + + assertTrue(blockProcessingResult.isSuccessful()); + assertThat(updatedGenesisAccount1.getBalance()) + .isEqualTo( + Wei.of( + UInt256.fromHexString( + ("0x00000000000000000000000000000000000000000000003627e8f7123739c1c8")))); + assertThat(updatedGenesisAccount2.getBalance()) + .isEqualTo( + Wei.of( + UInt256.fromHexString( + ("0x00000000000000000000000000000000000000000000003627e8f7123739c024")))); + assertThat(updatedAccount0x2.getBalance()).isEqualTo(Wei.of(2_000_000_000_000_000_000L)); + assertThat(updatedSenderAccount1.getBalance()) + .isLessThan(transferTransaction1Sender.getBalance()); + } + + private void processConflictedSimpleTransfersWithCoinbase(final BlockProcessor blockProcessor) { + // Create conflicted transactions using coinbase + Transaction transferTransaction1 = + createTransferTransaction( + 0, + 1_000_000_000_000_000_000L, + 300000L, + 5L, + 7L, + ACCOUNT_2, + ACCOUNT_GENESIS_1_KEYPAIR); // ACCOUNT_GENESIS_1 -> ACCOUNT_2 + Transaction transferTransaction2 = + createTransferTransaction( + 0, + 2_000_000_000_000_000_000L, + 300000L, + 5L, + 7L, + coinbase.toHexString(), + ACCOUNT_GENESIS_2_KEYPAIR); // ACCOUNT_GENESIS_2 -> COINBASE + + MutableWorldState worldState = worldStateArchive.getMutable(); + BonsaiAccount transferTransaction1Sender = + (BonsaiAccount) worldState.get(transferTransaction1.getSender()); + Block blockWithTransactions = + createBlockWithTransactions( + "0xd9544f389692face27352d23494dd1446d9af025067bc11b29e0eb83e258676a", + transferTransaction1, + transferTransaction2); + + BlockProcessingResult blockProcessingResult = + blockProcessor.processBlock(blockchain, worldState, blockWithTransactions); + + BonsaiAccount updatedSenderAccount1 = + (BonsaiAccount) worldState.get(transferTransaction1.getSender()); + + BonsaiAccount updatedAccount0x2 = + (BonsaiAccount) worldState.get(Address.fromHexStringStrict(ACCOUNT_2)); + + BonsaiAccount updatedCoinbase = (BonsaiAccount) worldState.get(coinbase); + + assertTrue(blockProcessingResult.isSuccessful()); + assertThat(updatedAccount0x2.getBalance()).isEqualTo(Wei.of(1_000_000_000_000_000_000L)); + assertThat(updatedCoinbase.getBalance()) + .isEqualTo( + Wei.of( + UInt256.fromHexString( + ("0x0000000000000000000000000000000000000000000000001bc8886498566008")))); + + assertThat(updatedSenderAccount1.getBalance()) + .isLessThan(transferTransaction1Sender.getBalance()); + } + + void processContractSlotUpdateThenReadTx(final BlockProcessor blockProcessor) { + Address contractAddress = Address.fromHexStringStrict(CONTRACT_ADDRESS); + + // create conflicted transactions on the same slot (update then read) + Transaction setSlot1Transaction = + createContractUpdateSlotTransaction( + 0, contractAddress, "setSlot1", ACCOUNT_GENESIS_1_KEYPAIR, Optional.of(100)); + Transaction getSlot1Transaction = + createContractUpdateSlotTransaction( + 0, contractAddress, "getSlot1", ACCOUNT_GENESIS_2_KEYPAIR, Optional.empty()); + Transaction setSlot3Transaction = + createContractUpdateSlotTransaction( + 1, contractAddress, "setSlot2", ACCOUNT_GENESIS_1_KEYPAIR, Optional.of(200)); + Transaction setSlot4Transaction = + createContractUpdateSlotTransaction( + 2, contractAddress, "setSlot3", ACCOUNT_GENESIS_1_KEYPAIR, Optional.of(300)); + + Block blockWithTransactions = + createBlockWithTransactions( + "0x51d59f64426ea986b1323aa22b9881c83f67947b4f90c2c302b21d3f8c459aff", + setSlot1Transaction, + getSlot1Transaction, + setSlot3Transaction, + setSlot4Transaction); + + MutableWorldState worldState = worldStateArchive.getMutable(); + BlockProcessingResult blockProcessingResult = + blockProcessor.processBlock(blockchain, worldState, blockWithTransactions); + + assertTrue(blockProcessingResult.isSuccessful()); + + // Verify the state + assertContractStorage(worldState, contractAddress, 0, 100); + assertContractStorage(worldState, contractAddress, 1, 200); + assertContractStorage(worldState, contractAddress, 2, 300); + } + + void processSlotReadThenUpdateTx(final BlockProcessor blockProcessor) { + Address contractAddress = Address.fromHexStringStrict(CONTRACT_ADDRESS); + + Transaction getSlot1Transaction = + createContractUpdateSlotTransaction( + 0, contractAddress, "getSlot1", ACCOUNT_GENESIS_1_KEYPAIR, Optional.empty()); + Transaction setSlot1Transaction = + createContractUpdateSlotTransaction( + 0, contractAddress, "setSlot1", ACCOUNT_GENESIS_2_KEYPAIR, Optional.of(1000)); + Transaction setSlo2Transaction = + createContractUpdateSlotTransaction( + 1, contractAddress, "setSlot2", ACCOUNT_GENESIS_1_KEYPAIR, Optional.of(2000)); + Transaction setSlot3Transaction = + createContractUpdateSlotTransaction( + 2, contractAddress, "setSlot3", ACCOUNT_GENESIS_1_KEYPAIR, Optional.of(3000)); + + Block blockWithTransactions = + createBlockWithTransactions( + "0xdf21d4fef211d7a905022dc87f2a68f4bf9cb273fcf9745cfa7f7c2f258c03f3", + getSlot1Transaction, + setSlot1Transaction, + setSlo2Transaction, + setSlot3Transaction); + MutableWorldState worldState = worldStateArchive.getMutable(); + + BlockProcessingResult blockProcessingResult = + blockProcessor.processBlock(blockchain, worldState, blockWithTransactions); + + assertTrue(blockProcessingResult.isSuccessful()); + + // Verify the state + assertContractStorage(worldState, contractAddress, 0, 1000); + assertContractStorage(worldState, contractAddress, 1, 2000); + assertContractStorage(worldState, contractAddress, 2, 3000); + } + + void processAccountReadThenUpdateTx(final BlockProcessor blockProcessor) { + Address contractAddress = Address.fromHexStringStrict(CONTRACT_ADDRESS); + Transaction transactionTransfer = // ACCOUNT_GENESIS_1 -> CONTRACT_ADDRESS + createTransferTransaction( + 0, + 1_000_000_000_000_000_000L, + 300000L, + 5L, + 7L, + CONTRACT_ADDRESS, + ACCOUNT_GENESIS_1_KEYPAIR); + Transaction getcontractBalanceTransaction = + createContractReadAccountTransaction( + 1, contractAddress, "getBalance", ACCOUNT_GENESIS_1_KEYPAIR, ACCOUNT_2); + + Transaction sendEthFromContractTransaction = + createContractUpdateAccountTransaction( + 0, + contractAddress, + "transferTo", + ACCOUNT_GENESIS_2_KEYPAIR, + ACCOUNT_2, + 500_000_000_000_000_000L); + + Block blockWithTransactions = + createBlockWithTransactions( + "0x91966cdde619acb05a1d9fef2f8801432a30edde7131f1f194002b0a766026c7", + transactionTransfer, + getcontractBalanceTransaction, + sendEthFromContractTransaction); + MutableWorldState worldState = worldStateArchive.getMutable(); + + BlockProcessingResult blockProcessingResult = + blockProcessor.processBlock(blockchain, worldState, blockWithTransactions); + + assertTrue(blockProcessingResult.isSuccessful()); + + // Verify the state + BonsaiAccount contractAccount = (BonsaiAccount) worldState.get(contractAddress); + BonsaiAccount updatedAccount0x2 = + (BonsaiAccount) worldState.get(Address.fromHexStringStrict(ACCOUNT_2)); + assertThat(contractAccount.getBalance()).isEqualTo(Wei.of(500_000_000_000_000_000L)); + assertThat(updatedAccount0x2.getBalance()).isEqualTo(Wei.of(500_000_000_000_000_000L)); + } + + void processAccountUpdateThenReadTx(final BlockProcessor blockProcessor) { + Address contractAddress = Address.fromHexStringStrict(CONTRACT_ADDRESS); + Transaction transactionTransfer = + createTransferTransaction( + 0, + 1_000_000_000_000_000_000L, + 300000L, + 5L, + 7L, + CONTRACT_ADDRESS, + ACCOUNT_GENESIS_1_KEYPAIR); + + Transaction sendEthFromContractTransaction = + createContractUpdateAccountTransaction( + 1, + contractAddress, + "transferTo", + ACCOUNT_GENESIS_1_KEYPAIR, + ACCOUNT_2, + 500_000_000_000_000_000L); + + Transaction getcontractBalanceTransaction = + createContractReadAccountTransaction( + 0, contractAddress, "getBalance", ACCOUNT_GENESIS_2_KEYPAIR, ACCOUNT_2); + + Block blockWithTransactions = + createBlockWithTransactions( + "0x375af730c0f9e04666659fc419fda74cc0cb29936607c08adf21d3b236c6b7f6", + transactionTransfer, + sendEthFromContractTransaction, + getcontractBalanceTransaction); + MutableWorldState worldState = worldStateArchive.getMutable(); + + BlockProcessingResult blockProcessingResult = + blockProcessor.processBlock(blockchain, worldState, blockWithTransactions); + assertTrue(blockProcessingResult.isSuccessful()); + + // Verify the state + BonsaiAccount contractAccount = (BonsaiAccount) worldState.get(contractAddress); + BonsaiAccount updatedAccount0x2 = + (BonsaiAccount) worldState.get(Address.fromHexStringStrict(ACCOUNT_2)); + + assertThat(contractAccount.getBalance()).isEqualTo(Wei.of(500_000_000_000_000_000L)); + assertThat(updatedAccount0x2.getBalance()).isEqualTo(Wei.of(500_000_000_000_000_000L)); + } + + void processAccountReadThenUpdateTxWithTwoAccounts(final BlockProcessor blockProcessor) { + Address contractAddress = Address.fromHexStringStrict(CONTRACT_ADDRESS); + Transaction transactionTransfer = // ACCOUNT_GENESIS_1 -> CONTRACT_ADDRESS + createTransferTransaction( + 0, + 1_000_000_000_000_000_000L, + 300000L, + 5L, + 7L, + CONTRACT_ADDRESS, + ACCOUNT_GENESIS_1_KEYPAIR); + Transaction getcontractBalanceTransaction = + createContractReadAccountTransaction( + 1, contractAddress, "getBalance", ACCOUNT_GENESIS_1_KEYPAIR, ACCOUNT_2); + + Transaction sendEthFromContractTransaction = + createContractUpdateAccountTransaction( + 0, + contractAddress, + "transferTo", + ACCOUNT_GENESIS_2_KEYPAIR, + ACCOUNT_3, + 500_000_000_000_000_000L); + + Block blockWithTransactions = + createBlockWithTransactions( + "0x3c2366a28dadbcef39ba04cde7bc30a5dccfce1e478a5c2602f5a28ab9498e6c", + transactionTransfer, + getcontractBalanceTransaction, + sendEthFromContractTransaction); + MutableWorldState worldState = worldStateArchive.getMutable(); + + BlockProcessingResult blockProcessingResult = + blockProcessor.processBlock(blockchain, worldState, blockWithTransactions); + + assertTrue(blockProcessingResult.isSuccessful()); + + // Verify the state + BonsaiAccount contractAccount = (BonsaiAccount) worldState.get(contractAddress); + BonsaiAccount updatedAccount0x3 = + (BonsaiAccount) worldState.get(Address.fromHexStringStrict(ACCOUNT_3)); + assertThat(contractAccount.getBalance()).isEqualTo(Wei.of(500_000_000_000_000_000L)); + assertThat(updatedAccount0x3.getBalance()).isEqualTo(Wei.of(500_000_000_000_000_000L)); + } + + void processAccountUpdateThenReadTxWithTwoAccounts(final BlockProcessor blockProcessor) { + Address contractAddress = Address.fromHexStringStrict(CONTRACT_ADDRESS); + Transaction transactionTransfer = // ACCOUNT_GENESIS_1 -> CONTRACT_ADDRESS + createTransferTransaction( + 0, + 1_000_000_000_000_000_000L, + 300000L, + 5L, + 7L, + CONTRACT_ADDRESS, + ACCOUNT_GENESIS_1_KEYPAIR); + + Transaction sendEthFromContractTransaction = + createContractUpdateAccountTransaction( + 0, + contractAddress, + "transferTo", + ACCOUNT_GENESIS_2_KEYPAIR, + ACCOUNT_3, + 500_000_000_000_000_000L); + + Transaction getcontractBalanceTransaction = + createContractReadAccountTransaction( + 1, contractAddress, "getBalance", ACCOUNT_GENESIS_1_KEYPAIR, ACCOUNT_2); + + Block blockWithTransactions = + createBlockWithTransactions( + "0x3c2366a28dadbcef39ba04cde7bc30a5dccfce1e478a5c2602f5a28ab9498e6c", + transactionTransfer, + sendEthFromContractTransaction, + getcontractBalanceTransaction); + MutableWorldState worldState = worldStateArchive.getMutable(); + + BlockProcessingResult blockProcessingResult = + blockProcessor.processBlock(blockchain, worldState, blockWithTransactions); + + assertTrue(blockProcessingResult.isSuccessful()); + + // Verify the state + BonsaiAccount contractAccount = (BonsaiAccount) worldState.get(contractAddress); + BonsaiAccount updatedAccount0x3 = + (BonsaiAccount) worldState.get(Address.fromHexStringStrict(ACCOUNT_3)); + assertThat(contractAccount.getBalance()).isEqualTo(Wei.of(500_000_000_000_000_000L)); + assertThat(updatedAccount0x3.getBalance()).isEqualTo(Wei.of(500_000_000_000_000_000L)); + } + + private static KeyPair generateKeyPair(final String privateKeyHex) { + final KeyPair keyPair = + SignatureAlgorithmFactory.getInstance() + .createKeyPair( + SECPPrivateKey.create( + Bytes32.fromHexString(privateKeyHex), SignatureAlgorithm.ALGORITHM)); + return keyPair; + } + + private Transaction createContractUpdateSlotTransaction( + final int nonce, + final Address contractAddress, + final String methodSignature, + final KeyPair keyPair, + final Optional value) { + Bytes payload = encodeFunctionCall(methodSignature, value); + return Transaction.builder() + .type(TransactionType.EIP1559) + .nonce(nonce) + .maxPriorityFeePerGas(Wei.of(5)) + .maxFeePerGas(Wei.of(7)) + .gasLimit(3000000L) + .to(contractAddress) + .value(Wei.ZERO) + .payload(payload) + .chainId(BigInteger.valueOf(42)) + .signAndBuild(keyPair); + } + + private Transaction createContractReadAccountTransaction( + final int nonce, + final Address contractAddress, + final String methodSignature, + final KeyPair keyPair, + final String address) { + Bytes payload = encodeFunctionCall(methodSignature, address); + return Transaction.builder() + .type(TransactionType.EIP1559) + .nonce(nonce) + .maxPriorityFeePerGas(Wei.of(5)) + .maxFeePerGas(Wei.of(7)) + .gasLimit(3000000L) + .to(contractAddress) + .value(Wei.ZERO) + .payload(payload) + .chainId(BigInteger.valueOf(42)) + .signAndBuild(keyPair); + } + + private Transaction createContractUpdateAccountTransaction( + final int nonce, + final Address contractAddress, + final String methodSignature, + final KeyPair keyPair, + final String address, + final long value) { + Bytes payload = encodeFunctionCall(methodSignature, address, value); + return Transaction.builder() + .type(TransactionType.EIP1559) + .nonce(nonce) + .maxPriorityFeePerGas(Wei.of(5)) + .maxFeePerGas(Wei.of(7)) + .gasLimit(3000000L) + .to(contractAddress) + .value(Wei.ZERO) + .payload(payload) + .chainId(BigInteger.valueOf(42)) + .signAndBuild(keyPair); + } + + private Bytes encodeFunctionCall(final String methodSignature, final Optional value) { + List inputParameters = + value.isPresent() ? Arrays.asList(new Uint256(value.get())) : List.of(); + Function function = new Function(methodSignature, inputParameters, List.of()); + return Bytes.fromHexString(FunctionEncoder.encode(function)); + } + + private Bytes encodeFunctionCall(final String methodSignature, final String address) { + List inputParameters = Arrays.asList(new org.web3j.abi.datatypes.Address(address)); + Function function = new Function(methodSignature, inputParameters, List.of()); + return Bytes.fromHexString(FunctionEncoder.encode(function)); + } + + private Bytes encodeFunctionCall( + final String methodSignature, final String address, final long value) { + List inputParameters = + Arrays.asList(new org.web3j.abi.datatypes.Address(address), new Uint256(value)); + Function function = new Function(methodSignature, inputParameters, List.of()); + return Bytes.fromHexString(FunctionEncoder.encode(function)); + } + + private Block createBlockWithTransactions( + final String stateRoot, final Transaction... transactions) { + BlockHeader blockHeader = + new BlockHeaderTestFixture() + .number(1L) + .stateRoot(Hash.fromHexString(stateRoot)) + .gasLimit(30_000_000L) + .baseFeePerGas(Wei.of(5)) + .buildHeader(); + BlockBody blockBody = new BlockBody(Arrays.asList(transactions), Collections.emptyList()); + return new Block(blockHeader, blockBody); + } + + private void assertContractStorage( + final MutableWorldState worldState, + final Address contractAddress, + final int slot, + final int expectedValue) { + BonsaiAccount contractAccount = (BonsaiAccount) worldState.get(contractAddress); + UInt256 actualValue = contractAccount.getStorageValue(UInt256.valueOf(slot)); + assertThat(actualValue).isEqualTo(UInt256.valueOf(expectedValue)); + } + + private Transaction createTransferTransaction( + final long nonce, + final long value, + final long gasLimit, + final long maxPriorityFeePerGas, + final long maxFeePerGas, + final String hexAddress, + final KeyPair keyPair) { + return Transaction.builder() + .type(TransactionType.EIP1559) + .nonce(nonce) + .maxPriorityFeePerGas(Wei.of(maxPriorityFeePerGas)) + .maxFeePerGas(Wei.of(maxFeePerGas)) + .gasLimit(gasLimit) + .to(Address.fromHexStringStrict(hexAddress)) + .value(Wei.of(value)) + .payload(Bytes.EMPTY) + .chainId(BigInteger.valueOf(42)) + .signAndBuild(keyPair); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessorTest.java index 867bfeb7e5f..2a66c6e1151 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/AbstractBlockProcessorTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -32,6 +32,8 @@ import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.Withdrawal; +import org.hyperledger.besu.ethereum.mainnet.blockhash.FrontierBlockHashProcessor; +import org.hyperledger.besu.ethereum.mainnet.requests.RequestsValidatorCoordinator; import org.hyperledger.besu.ethereum.referencetests.ReferenceTestBlockchain; import org.hyperledger.besu.ethereum.referencetests.ReferenceTestWorldState; @@ -66,6 +68,12 @@ abstract class AbstractBlockProcessorTest { @BeforeEach void baseSetup() { lenient().when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); + lenient() + .when(protocolSpec.getRequestsValidatorCoordinator()) + .thenReturn(RequestsValidatorCoordinator.empty()); + lenient() + .when(protocolSpec.getBlockHashProcessor()) + .thenReturn(new FrontierBlockHashProcessor()); blockProcessor = new TestBlockProcessor( transactionProcessor, @@ -80,14 +88,7 @@ void baseSetup() { void withProcessorAndEmptyWithdrawals_WithdrawalsAreNotProcessed() { when(protocolSpec.getWithdrawalsProcessor()).thenReturn(Optional.empty()); blockProcessor.processBlock( - blockchain, - worldState, - emptyBlockHeader, - emptyList(), - emptyList(), - Optional.empty(), - Optional.empty(), - null); + blockchain, worldState, emptyBlockHeader, emptyList(), emptyList(), Optional.empty(), null); verify(withdrawalsProcessor, never()).processWithdrawals(any(), any()); } @@ -95,14 +96,7 @@ void withProcessorAndEmptyWithdrawals_WithdrawalsAreNotProcessed() { void withNoProcessorAndEmptyWithdrawals_WithdrawalsAreNotProcessed() { when(protocolSpec.getWithdrawalsProcessor()).thenReturn(Optional.empty()); blockProcessor.processBlock( - blockchain, - worldState, - emptyBlockHeader, - emptyList(), - emptyList(), - Optional.empty(), - Optional.empty(), - null); + blockchain, worldState, emptyBlockHeader, emptyList(), emptyList(), Optional.empty(), null); verify(withdrawalsProcessor, never()).processWithdrawals(any(), any()); } @@ -118,7 +112,6 @@ void withProcessorAndWithdrawals_WithdrawalsAreProcessed() { emptyList(), emptyList(), Optional.of(withdrawals), - Optional.empty(), null); verify(withdrawalsProcessor).processWithdrawals(eq(withdrawals), any()); } @@ -136,7 +129,6 @@ void withNoProcessorAndWithdrawals_WithdrawalsAreNotProcessed() { emptyList(), emptyList(), Optional.of(withdrawals), - Optional.empty(), null); verify(withdrawalsProcessor, never()).processWithdrawals(any(), any()); } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/BodyValidationTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/BodyValidationTest.java index cd5c5d89631..fd5c4387ccd 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/BodyValidationTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/BodyValidationTest.java @@ -23,7 +23,6 @@ import org.apache.tuweni.bytes.Bytes32; import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; /** Tests for {@link BodyValidation}. */ @@ -58,15 +57,4 @@ public void calculateWithdrawalsRoot() throws IOException { Assertions.assertThat(header.getWithdrawalsRoot()).hasValue(Hash.wrap(withdrawalsRoot)); } } - - @Disabled // TODO: RLP encoding has changed, so testdata needs to be updated - @Test - public void calculateDepositsRoot() throws IOException { - for (final int block : Arrays.asList(123, 124)) { - final BlockHeader header = ValidationTestUtils.readHeader(block); - final BlockBody body = ValidationTestUtils.readBody(block); - final Bytes32 depositsRoot = BodyValidation.depositsRoot(body.getDeposits().get()); - Assertions.assertThat(header.getDepositsRoot()).hasValue(Hash.wrap(depositsRoot)); - } - } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ConsolidationRequestValidatorTestFixtures.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ConsolidationRequestValidatorTestFixtures.java new file mode 100644 index 00000000000..fbd304d1cd9 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ConsolidationRequestValidatorTestFixtures.java @@ -0,0 +1,136 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet; + +import static org.hyperledger.besu.ethereum.mainnet.requests.ConsolidationRequestValidator.MAX_CONSOLIDATION_REQUESTS_PER_BLOCK; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockDataGenerator; +import org.hyperledger.besu.ethereum.core.ConsolidationRequest; +import org.hyperledger.besu.ethereum.core.Request; + +import java.util.List; +import java.util.Optional; +import java.util.stream.IntStream; + +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.bytes.Bytes48; + +public class ConsolidationRequestValidatorTestFixtures { + + private static final BlockDataGenerator blockDataGenerator = new BlockDataGenerator(); + + static ConsolidationRequestTestParameter + blockWithConsolidationRequestsAndWithdrawalRequestsRoot() { + final ConsolidationRequest consolidationRequest = createConsolidationRequest(); + final Optional> maybeConsolidationRequests = + Optional.of(List.of(consolidationRequest)); + + final BlockDataGenerator.BlockOptions blockOptions = + BlockDataGenerator.BlockOptions.create() + .setRequestsRoot(BodyValidation.requestsRoot(maybeConsolidationRequests.get())) + .setRequests(maybeConsolidationRequests); + final Block block = blockDataGenerator.block(blockOptions); + + return new ConsolidationRequestTestParameter( + "Block with consolidation requests and withdrawal_requests_root", + block, + Optional.of(List.of(consolidationRequest))); + } + + static ConsolidationRequestTestParameter blockWithConsolidationRequestsMismatch() { + final ConsolidationRequest consolidationRequest = createConsolidationRequest(); + + final Optional> requests = + Optional.of(List.of(consolidationRequest, consolidationRequest)); + + final BlockDataGenerator.BlockOptions blockOptions = + BlockDataGenerator.BlockOptions.create() + .setRequestsRoot(BodyValidation.requestsRoot(requests.get())) + .setRequests(requests); + final Block block = blockDataGenerator.block(blockOptions); + + return new ConsolidationRequestTestParameter( + "Block with consolidation requests mismatch", + block, + Optional.of(List.of(consolidationRequest, consolidationRequest)), + List.of(createConsolidationRequest())); + } + + static ConsolidationRequestTestParameter blockWithMoreThanMaximumConsolidationRequests() { + final List consolidationRequests = + IntStream.range(0, MAX_CONSOLIDATION_REQUESTS_PER_BLOCK + 1) + .mapToObj(__ -> createConsolidationRequest()) + .toList(); + + final Optional> maybeConsolidationRequest = + Optional.of(consolidationRequests); + final Optional> maybeRequests = + Optional.of(consolidationRequests.stream().map(r -> (Request) r).toList()); + + final BlockDataGenerator.BlockOptions blockOptions = + BlockDataGenerator.BlockOptions.create() + .setRequestsRoot(BodyValidation.requestsRoot(maybeRequests.get())) + .setRequests(maybeRequests); + final Block block = blockDataGenerator.block(blockOptions); + + return new ConsolidationRequestTestParameter( + "Block with more than maximum consolidation requests", block, maybeConsolidationRequest); + } + + static ConsolidationRequest createConsolidationRequest() { + return new ConsolidationRequest( + Address.extract(Bytes32.random()), + BLSPublicKey.wrap(Bytes48.random()), + BLSPublicKey.wrap(Bytes48.random())); + } + + static class ConsolidationRequestTestParameter { + + String description; + Block block; + Optional> maybeConsolidationRequest; + List expectedConsolidationRequest; + + public ConsolidationRequestTestParameter( + final String description, + final Block block, + final Optional> maybeConsolidationRequest) { + this( + description, + block, + maybeConsolidationRequest, + maybeConsolidationRequest.orElseGet(List::of)); + } + + public ConsolidationRequestTestParameter( + final String description, + final Block block, + final Optional> maybeConsolidationRequest, + final List expectedConsolidationRequest) { + this.description = description; + this.block = block; + this.maybeConsolidationRequest = maybeConsolidationRequest; + this.expectedConsolidationRequest = expectedConsolidationRequest; + } + + @Override + public String toString() { + return description; + } + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/DefaultProtocolScheduleTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/DefaultProtocolScheduleTest.java index 784ba569b3d..2d5dd2cee34 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/DefaultProtocolScheduleTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/DefaultProtocolScheduleTest.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.mainnet; import static org.assertj.core.api.Assertions.assertThat; @@ -20,10 +19,13 @@ import static org.mockito.Mockito.spy; import org.hyperledger.besu.config.StubGenesisConfigOptions; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import java.math.BigInteger; import java.util.Optional; @@ -58,7 +60,11 @@ public void setup() { ProtocolSpecAdapters.create(FIRST_TIMESTAMP_FORK, modifier), privacyParameters, isRevertReasonEnabled, - evmConfiguration); + evmConfiguration, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); } @Test diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/DepositRequestValidatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/DepositRequestValidatorTest.java new file mode 100644 index 00000000000..0360032388b --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/DepositRequestValidatorTest.java @@ -0,0 +1,234 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.datatypes.BLSSignature; +import org.hyperledger.besu.datatypes.GWei; +import org.hyperledger.besu.datatypes.RequestType; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockDataGenerator; +import org.hyperledger.besu.ethereum.core.DepositRequest; +import org.hyperledger.besu.ethereum.core.Request; +import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.mainnet.requests.DepositRequestValidator; +import org.hyperledger.besu.ethereum.mainnet.requests.RequestsValidatorCoordinator; +import org.hyperledger.besu.ethereum.mainnet.requests.WithdrawalRequestValidator; +import org.hyperledger.besu.evm.log.Log; +import org.hyperledger.besu.evm.log.LogTopic; + +import java.util.List; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt64; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class DepositRequestValidatorTest { + private final BlockDataGenerator blockDataGenerator = new BlockDataGenerator(); + private static DepositRequest depositRequest1; + private static DepositRequest depositRequest2; + private static Log LOG_1; + private static Log LOG_2; + private static Address DEPOSIT_CONTRACT_ADDRESS; + private static RequestsValidatorCoordinator requestsValidatorCoordinator; + private static DepositRequestValidator depositRequestValidator; + + @BeforeAll + public static void setup() { + depositRequest1 = + new DepositRequest( + BLSPublicKey.fromHexString( + "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e"), + Bytes32.fromHexString( + "0x0017a7fcf06faf493d30bbe2632ea7c2383cd86825e12797165de7aa35589483"), + GWei.of(32000000000L), + BLSSignature.fromHexString( + "0xa889db8300194050a2636c92a95bc7160515867614b7971a9500cdb62f9c0890217d2901c3241f86fac029428fc106930606154bd9e406d7588934a5f15b837180b17194d6e44bd6de23e43b163dfe12e369dcc75a3852cd997963f158217eb5"), + UInt64.valueOf(539967)); + + depositRequest2 = + new DepositRequest( + BLSPublicKey.fromHexString( + "0x8706d19a62f28a6a6549f96c5adaebac9124a61d44868ec94f6d2d707c6a2f82c9162071231dfeb40e24bfde4ffdf243"), + Bytes32.fromHexString( + "0x006a8dc800c6d8dd6977ef53264e2d030350f0145a91bcd167b4f1c3ea21b271"), + GWei.of(32000000000L), + BLSSignature.fromHexString( + "0x801b08ca107b623eca32ee9f9111b4e50eb9cfe19e38204b72de7dc04c5a5e00f61bab96f10842576f66020ce851083f1583dd9a6b73301bea6c245cf51f27cf96aeb018852c5f70bf485d16b957cfe49ca008913346b431e7653ae3ddb23b07"), + UInt64.valueOf(559887)); + + LOG_1 = + new Log( + Address.fromHexString("0x00000000219ab540356cbb839cbe05303d7705fa"), + Bytes.fromHexString( + "0x00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030b10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200017a7fcf06faf493d30bbe2632ea7c2383cd86825e12797165de7aa35589483000000000000000000000000000000000000000000000000000000000000000800405973070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060a889db8300194050a2636c92a95bc7160515867614b7971a9500cdb62f9c0890217d2901c3241f86fac029428fc106930606154bd9e406d7588934a5f15b837180b17194d6e44bd6de23e43b163dfe12e369dcc75a3852cd997963f158217eb500000000000000000000000000000000000000000000000000000000000000083f3d080000000000000000000000000000000000000000000000000000000000"), + List.of( + LogTopic.fromHexString( + "0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"))); + + LOG_2 = + new Log( + Address.fromHexString("0x00000000219ab540356cbb839cbe05303d7705fa"), + Bytes.fromHexString( + "0x00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000308706d19a62f28a6a6549f96c5adaebac9124a61d44868ec94f6d2d707c6a2f82c9162071231dfeb40e24bfde4ffdf243000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020006a8dc800c6d8dd6977ef53264e2d030350f0145a91bcd167b4f1c3ea21b271000000000000000000000000000000000000000000000000000000000000000800405973070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060801b08ca107b623eca32ee9f9111b4e50eb9cfe19e38204b72de7dc04c5a5e00f61bab96f10842576f66020ce851083f1583dd9a6b73301bea6c245cf51f27cf96aeb018852c5f70bf485d16b957cfe49ca008913346b431e7653ae3ddb23b0700000000000000000000000000000000000000000000000000000000000000080f8b080000000000000000000000000000000000000000000000000000000000"), + List.of( + LogTopic.fromHexString( + "0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"))); + DEPOSIT_CONTRACT_ADDRESS = Address.fromHexString("0x00000000219ab540356cbb839cbe05303d7705fa"); + requestsValidatorCoordinator = createAllowDepositValidator(); + } + + @Test + public void validateAllowedDepositRequests() { + final List request = List.of(depositRequest1, depositRequest2); + final BlockDataGenerator.BlockOptions blockOptions = + BlockDataGenerator.BlockOptions.create() + .setRequests(Optional.of(request)) + .setRequestsRoot(BodyValidation.requestsRoot(request)); + final Block block = blockDataGenerator.block(blockOptions); + + final TransactionReceipt receipt = + new TransactionReceipt(null, 0L, List.of(LOG_1, LOG_2), Optional.empty()); + + assertThat(requestsValidatorCoordinator.validate(block, Optional.of(request), List.of(receipt))) + .isTrue(); + } + + @Test + public void validateAllowedDepositRequestsSeparateReceipts() { + + final List requests = List.of(depositRequest1, depositRequest2); + + final BlockDataGenerator.BlockOptions blockOptions = + BlockDataGenerator.BlockOptions.create() + .setRequests(Optional.of(requests)) + .setRequestsRoot(BodyValidation.requestsRoot(requests)); + final Block block = blockDataGenerator.block(blockOptions); + + final TransactionReceipt receipt1 = + new TransactionReceipt(null, 0L, List.of(LOG_1), Optional.empty()); + final TransactionReceipt receipt2 = + new TransactionReceipt(null, 0L, List.of(LOG_2), Optional.empty()); + + assertThat( + requestsValidatorCoordinator.validate( + block, Optional.of(requests), List.of(receipt1, receipt2))) + .isTrue(); + } + + @Test + public void invalidateAllowedDepositRequests() { + final BlockDataGenerator.BlockOptions blockOptions = + BlockDataGenerator.BlockOptions.create().setRequests(Optional.of(List.of(depositRequest1))); + final Block block = blockDataGenerator.block(blockOptions); + + final TransactionReceipt receipt1 = + new TransactionReceipt(null, 0L, List.of(LOG_2), Optional.empty()); + + assertThat( + requestsValidatorCoordinator.validate(block, Optional.of(List.of()), List.of(receipt1))) + .isFalse(); + } + + @Test + public void invalidateAllowedDepositRequestsMissingLogInReceipt() { + final BlockDataGenerator.BlockOptions blockOptions = + BlockDataGenerator.BlockOptions.create() + .setRequests(Optional.of(List.of(depositRequest1, depositRequest2))); + final Block block = blockDataGenerator.block(blockOptions); + + final TransactionReceipt receipt1 = + new TransactionReceipt(null, 0L, List.of(LOG_2), Optional.empty()); + + assertThat( + requestsValidatorCoordinator.validate(block, Optional.of(List.of()), List.of(receipt1))) + .isFalse(); + } + + @Test + public void invalidateAllowedDepositRequestsExtraLogInReceipt() { + final BlockDataGenerator.BlockOptions blockOptions = + BlockDataGenerator.BlockOptions.create().setRequests(Optional.of(List.of(depositRequest1))); + final Block block = blockDataGenerator.block(blockOptions); + + final TransactionReceipt receipt1 = + new TransactionReceipt(null, 0L, List.of(LOG_1, LOG_2), Optional.empty()); + + assertThat( + requestsValidatorCoordinator.validate(block, Optional.of(List.of()), List.of(receipt1))) + .isFalse(); + } + + @Test + public void invalidateAllowedDepositRequestsWrongOrder() { + final BlockDataGenerator.BlockOptions blockOptions = + BlockDataGenerator.BlockOptions.create() + .setRequests(Optional.of(List.of(depositRequest1, depositRequest2))); + final Block block = blockDataGenerator.block(blockOptions); + + final TransactionReceipt receipt1 = + new TransactionReceipt(null, 0L, List.of(LOG_2, LOG_1), Optional.empty()); + + assertThat( + requestsValidatorCoordinator.validate(block, Optional.of(List.of()), List.of(receipt1))) + .isFalse(); + } + + @Test + public void invalidateAllowedDepositRequestsMismatchContractAddress() { + + final BlockDataGenerator.BlockOptions blockOptions = + BlockDataGenerator.BlockOptions.create() + .setRequests(Optional.of(List.of(depositRequest1, depositRequest2))); + final Block block = blockDataGenerator.block(blockOptions); + + final TransactionReceipt receipt1 = + new TransactionReceipt(null, 0L, List.of(LOG_1, LOG_2), Optional.empty()); + + assertThat( + requestsValidatorCoordinator.validate(block, Optional.of(List.of()), List.of(receipt1))) + .isFalse(); + } + + @Test + public void validateAllowedDepositParams() { + final Optional> depositRequests = + Optional.of(List.of(depositRequest1, depositRequest2)); + assertThat(depositRequestValidator.validateParameter(depositRequests)).isTrue(); + + final Optional> emptyDepositRequests = Optional.of(List.of()); + assertThat(depositRequestValidator.validateParameter(emptyDepositRequests)).isTrue(); + } + + @Test + public void invalidateAllowedDepositParams() { + final Optional> depositRequests = Optional.empty(); + assertThat(depositRequestValidator.validateParameter(depositRequests)).isFalse(); + } + + static RequestsValidatorCoordinator createAllowDepositValidator() { + depositRequestValidator = new DepositRequestValidator(DEPOSIT_CONTRACT_ADDRESS); + return new RequestsValidatorCoordinator.Builder() + .addValidator(RequestType.WITHDRAWAL, new WithdrawalRequestValidator()) + .addValidator(RequestType.DEPOSIT, depositRequestValidator) + .build(); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/DepositsValidatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/DepositsValidatorTest.java deleted file mode 100644 index 09bd5a110b7..00000000000 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/DepositsValidatorTest.java +++ /dev/null @@ -1,300 +0,0 @@ -/* - * Copyright contributors to Hyperledger Besu - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.besu.ethereum.mainnet; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.BLSPublicKey; -import org.hyperledger.besu.datatypes.BLSSignature; -import org.hyperledger.besu.datatypes.GWei; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.core.Block; -import org.hyperledger.besu.ethereum.core.BlockDataGenerator; -import org.hyperledger.besu.ethereum.core.Deposit; -import org.hyperledger.besu.ethereum.core.TransactionReceipt; -import org.hyperledger.besu.evm.log.Log; -import org.hyperledger.besu.evm.log.LogTopic; - -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt64; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -public class DepositsValidatorTest { - private final BlockDataGenerator blockDataGenerator = new BlockDataGenerator(); - private static Deposit DEPOSIT_1; - private static Deposit DEPOSIT_2; - private static Log LOG_1; - private static Log LOG_2; - private static Address DEPOSIT_CONTRACT_ADDRESS; - - @BeforeAll - public static void setup() { - DEPOSIT_1 = - new Deposit( - BLSPublicKey.fromHexString( - "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e"), - Bytes32.fromHexString( - "0x0017a7fcf06faf493d30bbe2632ea7c2383cd86825e12797165de7aa35589483"), - GWei.of(32000000000L), - BLSSignature.fromHexString( - "0xa889db8300194050a2636c92a95bc7160515867614b7971a9500cdb62f9c0890217d2901c3241f86fac029428fc106930606154bd9e406d7588934a5f15b837180b17194d6e44bd6de23e43b163dfe12e369dcc75a3852cd997963f158217eb5"), - UInt64.valueOf(539967)); - - DEPOSIT_2 = - new Deposit( - BLSPublicKey.fromHexString( - "0x8706d19a62f28a6a6549f96c5adaebac9124a61d44868ec94f6d2d707c6a2f82c9162071231dfeb40e24bfde4ffdf243"), - Bytes32.fromHexString( - "0x006a8dc800c6d8dd6977ef53264e2d030350f0145a91bcd167b4f1c3ea21b271"), - GWei.of(32000000000L), - BLSSignature.fromHexString( - "0x801b08ca107b623eca32ee9f9111b4e50eb9cfe19e38204b72de7dc04c5a5e00f61bab96f10842576f66020ce851083f1583dd9a6b73301bea6c245cf51f27cf96aeb018852c5f70bf485d16b957cfe49ca008913346b431e7653ae3ddb23b07"), - UInt64.valueOf(559887)); - - LOG_1 = - new Log( - Address.fromHexString("0x00000000219ab540356cbb839cbe05303d7705fa"), - Bytes.fromHexString( - "0x00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030b10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200017a7fcf06faf493d30bbe2632ea7c2383cd86825e12797165de7aa35589483000000000000000000000000000000000000000000000000000000000000000800405973070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060a889db8300194050a2636c92a95bc7160515867614b7971a9500cdb62f9c0890217d2901c3241f86fac029428fc106930606154bd9e406d7588934a5f15b837180b17194d6e44bd6de23e43b163dfe12e369dcc75a3852cd997963f158217eb500000000000000000000000000000000000000000000000000000000000000083f3d080000000000000000000000000000000000000000000000000000000000"), - List.of( - LogTopic.fromHexString( - "0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"))); - - LOG_2 = - new Log( - Address.fromHexString("0x00000000219ab540356cbb839cbe05303d7705fa"), - Bytes.fromHexString( - "0x00000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000180000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000308706d19a62f28a6a6549f96c5adaebac9124a61d44868ec94f6d2d707c6a2f82c9162071231dfeb40e24bfde4ffdf243000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020006a8dc800c6d8dd6977ef53264e2d030350f0145a91bcd167b4f1c3ea21b271000000000000000000000000000000000000000000000000000000000000000800405973070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060801b08ca107b623eca32ee9f9111b4e50eb9cfe19e38204b72de7dc04c5a5e00f61bab96f10842576f66020ce851083f1583dd9a6b73301bea6c245cf51f27cf96aeb018852c5f70bf485d16b957cfe49ca008913346b431e7653ae3ddb23b0700000000000000000000000000000000000000000000000000000000000000080f8b080000000000000000000000000000000000000000000000000000000000"), - List.of( - LogTopic.fromHexString( - "0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"))); - DEPOSIT_CONTRACT_ADDRESS = Address.fromHexString("0x00000000219ab540356cbb839cbe05303d7705fa"); - } - - @Test - public void validateProhibitedDeposits() { - final BlockDataGenerator.BlockOptions blockOptions = - BlockDataGenerator.BlockOptions.create().setDeposits(Optional.empty()); - final Block block = blockDataGenerator.block(blockOptions); - assertThat(new DepositsValidator.ProhibitedDeposits().validateDeposits(block, null)).isTrue(); - } - - @Test - public void validateProhibitedDepositsRoot() { - final Block block = blockDataGenerator.block(); - assertThat(new DepositsValidator.ProhibitedDeposits().validateDepositsRoot(block)).isTrue(); - } - - @Test - public void invalidateProhibitedDeposits() { - final BlockDataGenerator.BlockOptions blockOptions = - BlockDataGenerator.BlockOptions.create().setDeposits(Optional.of(List.of(DEPOSIT_1))); - final Block block = blockDataGenerator.block(blockOptions); - assertThat(new DepositsValidator.ProhibitedDeposits().validateDeposits(block, null)).isFalse(); - } - - @Test - public void invalidateProhibitedDepositsRoot() { - final BlockDataGenerator.BlockOptions blockOptions = - BlockDataGenerator.BlockOptions.create().setDepositsRoot(Hash.EMPTY_LIST_HASH); - final Block block = blockDataGenerator.block(blockOptions); - assertThat(new DepositsValidator.ProhibitedDeposits().validateDepositsRoot(block)).isFalse(); - } - - @Test - public void validateAllowedDeposits() { - final BlockDataGenerator.BlockOptions blockOptions = - BlockDataGenerator.BlockOptions.create() - .setDeposits(Optional.of(List.of(DEPOSIT_1, DEPOSIT_2))); - final Block block = blockDataGenerator.block(blockOptions); - - final TransactionReceipt receipt = - new TransactionReceipt(null, 0L, List.of(LOG_1, LOG_2), Optional.empty()); - - assertThat( - new DepositsValidator.AllowedDeposits(DEPOSIT_CONTRACT_ADDRESS) - .validateDeposits(block, List.of(receipt))) - .isTrue(); - } - - @Test - public void validateAllowedDepositsSeparateReceipts() { - final BlockDataGenerator.BlockOptions blockOptions = - BlockDataGenerator.BlockOptions.create() - .setDeposits(Optional.of(List.of(DEPOSIT_1, DEPOSIT_2))); - final Block block = blockDataGenerator.block(blockOptions); - - final TransactionReceipt receipt1 = - new TransactionReceipt(null, 0L, List.of(LOG_1), Optional.empty()); - final TransactionReceipt receipt2 = - new TransactionReceipt(null, 0L, List.of(LOG_2), Optional.empty()); - - assertThat( - new DepositsValidator.AllowedDeposits(DEPOSIT_CONTRACT_ADDRESS) - .validateDeposits(block, List.of(receipt1, receipt2))) - .isTrue(); - } - - @Test - public void validateAllowedDepositsRoot() { - final BlockDataGenerator.BlockOptions blockOptions = - BlockDataGenerator.BlockOptions.create() - .setDeposits(Optional.of(Collections.emptyList())) - .setDepositsRoot(Hash.EMPTY_TRIE_HASH); - final Block block = blockDataGenerator.block(blockOptions); - assertThat( - new DepositsValidator.AllowedDeposits(DEPOSIT_CONTRACT_ADDRESS) - .validateDepositsRoot(block)) - .isTrue(); - } - - @Test - public void invalidateAllowedDeposits() { - final BlockDataGenerator.BlockOptions blockOptions = - BlockDataGenerator.BlockOptions.create().setDeposits(Optional.of(List.of(DEPOSIT_1))); - final Block block = blockDataGenerator.block(blockOptions); - - final TransactionReceipt receipt1 = - new TransactionReceipt(null, 0L, List.of(LOG_2), Optional.empty()); - - assertThat( - new DepositsValidator.AllowedDeposits(DEPOSIT_CONTRACT_ADDRESS) - .validateDeposits(block, List.of(receipt1))) - .isFalse(); - } - - @Test - public void invalidateAllowedDepositsMissingLogInReceipt() { - final BlockDataGenerator.BlockOptions blockOptions = - BlockDataGenerator.BlockOptions.create() - .setDeposits(Optional.of(List.of(DEPOSIT_1, DEPOSIT_2))); - final Block block = blockDataGenerator.block(blockOptions); - - final TransactionReceipt receipt1 = - new TransactionReceipt(null, 0L, List.of(LOG_2), Optional.empty()); - - assertThat( - new DepositsValidator.AllowedDeposits(DEPOSIT_CONTRACT_ADDRESS) - .validateDeposits(block, List.of(receipt1))) - .isFalse(); - } - - @Test - public void invalidateAllowedDepositsExtraLogInReceipt() { - final BlockDataGenerator.BlockOptions blockOptions = - BlockDataGenerator.BlockOptions.create().setDeposits(Optional.of(List.of(DEPOSIT_1))); - final Block block = blockDataGenerator.block(blockOptions); - - final TransactionReceipt receipt1 = - new TransactionReceipt(null, 0L, List.of(LOG_1, LOG_2), Optional.empty()); - - assertThat( - new DepositsValidator.AllowedDeposits(DEPOSIT_CONTRACT_ADDRESS) - .validateDeposits(block, List.of(receipt1))) - .isFalse(); - } - - @Test - public void invalidateAllowedDepositsWrongOrder() { - final BlockDataGenerator.BlockOptions blockOptions = - BlockDataGenerator.BlockOptions.create() - .setDeposits(Optional.of(List.of(DEPOSIT_1, DEPOSIT_2))); - final Block block = blockDataGenerator.block(blockOptions); - - final TransactionReceipt receipt1 = - new TransactionReceipt(null, 0L, List.of(LOG_2, LOG_1), Optional.empty()); - - assertThat( - new DepositsValidator.AllowedDeposits(DEPOSIT_CONTRACT_ADDRESS) - .validateDeposits(block, List.of(receipt1))) - .isFalse(); - } - - @Test - public void invalidateAllowedDepositsMismatchContractAddress() { - - final BlockDataGenerator.BlockOptions blockOptions = - BlockDataGenerator.BlockOptions.create() - .setDeposits(Optional.of(List.of(DEPOSIT_1, DEPOSIT_2))); - final Block block = blockDataGenerator.block(blockOptions); - - final TransactionReceipt receipt1 = - new TransactionReceipt(null, 0L, List.of(LOG_1, LOG_2), Optional.empty()); - - assertThat( - new DepositsValidator.AllowedDeposits(Address.ZERO) - .validateDeposits(block, List.of(receipt1))) - .isFalse(); - } - - @Test - public void invalidateAllowedDepositsRoot() { - final BlockDataGenerator.BlockOptions blockOptions = - BlockDataGenerator.BlockOptions.create() - .setDeposits(Optional.of(Collections.emptyList())) - .setDepositsRoot(Hash.ZERO); // this is invalid it should be empty trie hash - final Block block = blockDataGenerator.block(blockOptions); - assertThat( - new DepositsValidator.AllowedDeposits(DEPOSIT_CONTRACT_ADDRESS) - .validateDepositsRoot(block)) - .isFalse(); - } - - @Test - public void validateProhibitedDepositParams() { - final Optional> deposits = Optional.empty(); - assertThat(new DepositsValidator.ProhibitedDeposits().validateDepositParameter(deposits)) - .isTrue(); - } - - @Test - public void invalidateProhibitedDepositParams() { - final Optional> deposits = Optional.of(List.of(DEPOSIT_1, DEPOSIT_2)); - assertThat(new DepositsValidator.ProhibitedDeposits().validateDepositParameter(deposits)) - .isFalse(); - } - - @Test - public void validateAllowedDepositParams() { - final Optional> deposits = Optional.of(List.of(DEPOSIT_1, DEPOSIT_2)); - assertThat( - new DepositsValidator.AllowedDeposits(DEPOSIT_CONTRACT_ADDRESS) - .validateDepositParameter(deposits)) - .isTrue(); - - final Optional> emptyDeposits = Optional.of(List.of()); - assertThat( - new DepositsValidator.AllowedDeposits(DEPOSIT_CONTRACT_ADDRESS) - .validateDepositParameter(emptyDeposits)) - .isTrue(); - } - - @Test - public void invalidateAllowedDepositParams() { - final Optional> deposits = Optional.empty(); - assertThat( - new DepositsValidator.AllowedDeposits(DEPOSIT_CONTRACT_ADDRESS) - .validateDepositParameter(deposits)) - .isFalse(); - } -} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/EthHashTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/EthHashTest.java index 458bc741320..10a3b15b458 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/EthHashTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/EthHashTest.java @@ -82,39 +82,21 @@ public void prepareCache() { } assertThat(Hex.toHexString(buffer.array())) .isEqualTo( - new StringBuilder() - .append( - "7ce2991c951f7bf4c4c1bb119887ee07871eb5339d7b97b8588e85c742de90e5bafd5bbe6ce93a134fb6be9ad3e30db99d9528a2ea7846833f52e9ca119b6b54") - .append( - "8979480c46e19972bd0738779c932c1b43e665a2fd3122fc3ddb2691f353ceb0ed3e38b8f51fd55b6940290743563c9f8fa8822e611924657501a12aafab8a8d") - .append( - "88fb5fbae3a99d14792406672e783a06940a42799b1c38bc28715db6d37cb11f9f6b24e386dc52dd8c286bd8c36fa813dffe4448a9f56ebcbeea866b42f68d22") - .append( - "6c32aae4d695a23cab28fd74af53b0c2efcc180ceaaccc0b2e280103d097a03c1d1b0f0f26ce5f32a90238f9bc49f645db001ef9cd3d13d44743f841fad11a37") - .append( - "fa290c62c16042f703578921f30b9951465aae2af4a5dad43a7341d7b4a62750954965a47a1c3af638dc3495c4d62a9bab843168c9fc0114e79cffd1b2827b01") - .append( - "75d30ba054658f214e946cf24c43b40d3383fbb0493408e5c5392434ca21bbcf43200dfb876c713d201813934fa485f48767c5915745cf0986b1dc0f33e57748") - .append( - "bf483ee2aff4248dfe461ec0504a13628401020fc22638584a8f2f5206a13b2f233898c78359b21c8226024d0a7a93df5eb6c282bdbf005a4aab497e096f2847") - .append( - "76c71cee57932a8fb89f6d6b8743b60a4ea374899a94a2e0f218d5c55818cefb1790c8529a76dba31ebb0f4592d709b49587d2317970d39c086f18dd244291d9") - .append( - "eedb16705e53e3350591bd4ff4566a3595ac0f0ce24b5e112a3d033bc51b6fea0a92296dea7f5e20bf6ee6bc347d868fda193c395b9bb147e55e5a9f67cfe741") - .append( - "7eea7d699b155bd13804204df7ea91fa9249e4474dddf35188f77019c67d201e4c10d7079c5ad492a71afff9a23ca7e900ba7d1bdeaf3270514d8eb35eab8a0a") - .append( - "718bb7273aeb37768fa589ed8ab01fbf4027f4ebdbbae128d21e485f061c20183a9bc2e31edbda0727442e9d58eb0fe198440fe199e02e77c0f7b99973f1f74c") - .append( - "c9089a51ab96c94a84d66e6aa48b2d0a4543adb5a789039a2aa7b335ca85c91026c7d3c894da53ae364188c3fd92f78e01d080399884a47385aa792e38150cda") - .append( - "a8620b2ebeca41fbc773bb837b5e724d6eb2de570d99858df0d7d97067fb8103b21757873b735097b35d3bea8fd1c359a9e8a63c1540c76c9784cf8d975e995c") - .append( - "778401b94a2e66e6993ad67ad3ecdc2acb17779f1ea8606827ec92b11c728f8c3b6d3f04a3e6ed05ff81dd76d5dc5695a50377bc135aaf1671cf68b750315493") - .append( - "6c64510164d53312bf3c41740c7a237b05faf4a191bd8a95dafa068dbcf370255c725900ce5c934f36feadcfe55b687c440574c1f06f39d207a8553d39156a24") - .append( - "845f64fd8324bb85312979dead74f764c9677aab89801ad4f927f1c00f12e28f22422bb44200d1969d9ab377dd6b099dc6dbc3222e9321b2c1e84f8e2f07731c") - .toString()); + "7ce2991c951f7bf4c4c1bb119887ee07871eb5339d7b97b8588e85c742de90e5bafd5bbe6ce93a134fb6be9ad3e30db99d9528a2ea7846833f52e9ca119b6b54" + + "8979480c46e19972bd0738779c932c1b43e665a2fd3122fc3ddb2691f353ceb0ed3e38b8f51fd55b6940290743563c9f8fa8822e611924657501a12aafab8a8d" + + "88fb5fbae3a99d14792406672e783a06940a42799b1c38bc28715db6d37cb11f9f6b24e386dc52dd8c286bd8c36fa813dffe4448a9f56ebcbeea866b42f68d22" + + "6c32aae4d695a23cab28fd74af53b0c2efcc180ceaaccc0b2e280103d097a03c1d1b0f0f26ce5f32a90238f9bc49f645db001ef9cd3d13d44743f841fad11a37" + + "fa290c62c16042f703578921f30b9951465aae2af4a5dad43a7341d7b4a62750954965a47a1c3af638dc3495c4d62a9bab843168c9fc0114e79cffd1b2827b01" + + "75d30ba054658f214e946cf24c43b40d3383fbb0493408e5c5392434ca21bbcf43200dfb876c713d201813934fa485f48767c5915745cf0986b1dc0f33e57748" + + "bf483ee2aff4248dfe461ec0504a13628401020fc22638584a8f2f5206a13b2f233898c78359b21c8226024d0a7a93df5eb6c282bdbf005a4aab497e096f2847" + + "76c71cee57932a8fb89f6d6b8743b60a4ea374899a94a2e0f218d5c55818cefb1790c8529a76dba31ebb0f4592d709b49587d2317970d39c086f18dd244291d9" + + "eedb16705e53e3350591bd4ff4566a3595ac0f0ce24b5e112a3d033bc51b6fea0a92296dea7f5e20bf6ee6bc347d868fda193c395b9bb147e55e5a9f67cfe741" + + "7eea7d699b155bd13804204df7ea91fa9249e4474dddf35188f77019c67d201e4c10d7079c5ad492a71afff9a23ca7e900ba7d1bdeaf3270514d8eb35eab8a0a" + + "718bb7273aeb37768fa589ed8ab01fbf4027f4ebdbbae128d21e485f061c20183a9bc2e31edbda0727442e9d58eb0fe198440fe199e02e77c0f7b99973f1f74c" + + "c9089a51ab96c94a84d66e6aa48b2d0a4543adb5a789039a2aa7b335ca85c91026c7d3c894da53ae364188c3fd92f78e01d080399884a47385aa792e38150cda" + + "a8620b2ebeca41fbc773bb837b5e724d6eb2de570d99858df0d7d97067fb8103b21757873b735097b35d3bea8fd1c359a9e8a63c1540c76c9784cf8d975e995c" + + "778401b94a2e66e6993ad67ad3ecdc2acb17779f1ea8606827ec92b11c728f8c3b6d3f04a3e6ed05ff81dd76d5dc5695a50377bc135aaf1671cf68b750315493" + + "6c64510164d53312bf3c41740c7a237b05faf4a191bd8a95dafa068dbcf370255c725900ce5c934f36feadcfe55b687c440574c1f06f39d207a8553d39156a24" + + "845f64fd8324bb85312979dead74f764c9677aab89801ad4f927f1c00f12e28f22422bb44200d1969d9ab377dd6b099dc6dbc3222e9321b2c1e84f8e2f07731c"); } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/IntrinsicGasTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/IntrinsicGasTest.java index a6ae23724e3..bb9ce2b495a 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/IntrinsicGasTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/IntrinsicGasTest.java @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.ethereum.mainnet; +import static org.assertj.core.api.Assertions.assertThat; + import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.evm.gascalculator.FrontierGasCalculator; @@ -24,6 +26,7 @@ import org.apache.tuweni.bytes.Bytes; import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -90,4 +93,11 @@ public void validateGasCost( gasCalculator.transactionIntrinsicGasCost(t.getPayload(), t.isContractCreation())) .isEqualTo(expectedGas); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockBodyValidatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockBodyValidatorTest.java index 8543321b470..c116d545d80 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockBodyValidatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockBodyValidatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode.NONE; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; import org.hyperledger.besu.datatypes.Address; @@ -27,6 +28,8 @@ import org.hyperledger.besu.ethereum.core.BlockDataGenerator.BlockOptions; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; import org.hyperledger.besu.ethereum.core.Withdrawal; +import org.hyperledger.besu.ethereum.mainnet.requests.DepositRequestValidator; +import org.hyperledger.besu.ethereum.mainnet.requests.RequestsValidatorCoordinator; import org.hyperledger.besu.evm.log.LogsBloomFilter; import java.util.Collections; @@ -34,6 +37,7 @@ import java.util.Optional; import org.apache.tuweni.units.bigints.UInt64; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -49,7 +53,24 @@ class MainnetBlockBodyValidatorTest { @Mock private ProtocolSchedule protocolSchedule; @Mock private ProtocolSpec protocolSpec; @Mock private WithdrawalsValidator withdrawalsValidator; - @Mock private DepositsValidator depositsValidator; + @Mock private DepositRequestValidator depositRequestValidator; + @Mock private RequestsValidatorCoordinator requestValidator; + + @BeforeEach + public void setUp() { + lenient().when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); + + lenient().when(protocolSpec.getWithdrawalsValidator()).thenReturn(withdrawalsValidator); + lenient().when(withdrawalsValidator.validateWithdrawals(any())).thenReturn(true); + lenient().when(withdrawalsValidator.validateWithdrawalsRoot(any())).thenReturn(true); + + lenient() + .when(depositRequestValidator.validateDepositRequests(any(), any(), any())) + .thenReturn(true); + + lenient().when(protocolSpec.getRequestsValidatorCoordinator()).thenReturn(requestValidator); + lenient().when(requestValidator.validate(any(), any(), any())).thenReturn(true); + } @Test void validatesWithdrawals() { @@ -67,18 +88,12 @@ void validatesWithdrawals() { .setWithdrawalsRoot(BodyValidation.withdrawalsRoot(withdrawals))); blockchainSetupUtil.getBlockchain().appendBlock(block, Collections.emptyList()); - when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); - when(protocolSpec.getWithdrawalsValidator()).thenReturn(withdrawalsValidator); - when(protocolSpec.getDepositsValidator()).thenReturn(depositsValidator); when(withdrawalsValidator.validateWithdrawals(Optional.of(withdrawals))).thenReturn(true); - when(withdrawalsValidator.validateWithdrawalsRoot(block)).thenReturn(true); - when(depositsValidator.validateDeposits(any(), any())).thenReturn(true); - when(depositsValidator.validateDepositsRoot(block)).thenReturn(true); assertThat( new MainnetBlockBodyValidator(protocolSchedule) .validateBodyLight( - blockchainSetupUtil.getProtocolContext(), block, emptyList(), NONE)) + blockchainSetupUtil.getProtocolContext(), block, emptyList(), any(), NONE)) .isTrue(); } @@ -97,14 +112,12 @@ void validationFailsIfWithdrawalsValidationFails() { .setWithdrawalsRoot(BodyValidation.withdrawalsRoot(withdrawals))); blockchainSetupUtil.getBlockchain().appendBlock(block, Collections.emptyList()); - when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); - when(protocolSpec.getWithdrawalsValidator()).thenReturn(withdrawalsValidator); when(withdrawalsValidator.validateWithdrawals(Optional.empty())).thenReturn(false); assertThat( new MainnetBlockBodyValidator(protocolSchedule) .validateBodyLight( - blockchainSetupUtil.getProtocolContext(), block, emptyList(), NONE)) + blockchainSetupUtil.getProtocolContext(), block, emptyList(), any(), NONE)) .isFalse(); } @@ -123,15 +136,36 @@ void validationFailsIfWithdrawalsRootValidationFails() { .setWithdrawals(Optional.of(withdrawals))); blockchainSetupUtil.getBlockchain().appendBlock(block, Collections.emptyList()); - when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); - when(protocolSpec.getWithdrawalsValidator()).thenReturn(withdrawalsValidator); - when(withdrawalsValidator.validateWithdrawals(Optional.of(withdrawals))).thenReturn(true); when(withdrawalsValidator.validateWithdrawalsRoot(block)).thenReturn(false); assertThat( new MainnetBlockBodyValidator(protocolSchedule) .validateBodyLight( - blockchainSetupUtil.getProtocolContext(), block, emptyList(), NONE)) + blockchainSetupUtil.getProtocolContext(), block, emptyList(), any(), NONE)) + .isFalse(); + } + + @Test + public void validationFailsIfWithdrawalRequestsValidationFails() { + final Block block = + blockDataGenerator.block( + new BlockOptions() + .setBlockNumber(1) + .setGasUsed(0) + .hasTransactions(false) + .hasOmmers(false) + .setReceiptsRoot(BodyValidation.receiptsRoot(emptyList())) + .setLogsBloom(LogsBloomFilter.empty()) + .setParentHash(blockchainSetupUtil.getBlockchain().getChainHeadHash()) + .setRequests(Optional.of(List.of()))); + blockchainSetupUtil.getBlockchain().appendBlock(block, Collections.emptyList()); + + when(requestValidator.validate(any(), any(), any())).thenReturn(false); + + assertThat( + new MainnetBlockBodyValidator(protocolSchedule) + .validateBodyLight( + blockchainSetupUtil.getProtocolContext(), block, emptyList(), any(), NONE)) .isFalse(); } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockProcessorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockProcessorTest.java index 1b106686bfc..a06aece37bd 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockProcessorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetBlockProcessorTest.java @@ -27,6 +27,8 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.mainnet.blockhash.FrontierBlockHashProcessor; +import org.hyperledger.besu.ethereum.mainnet.requests.RequestsValidatorCoordinator; import org.hyperledger.besu.ethereum.referencetests.ReferenceTestBlockchain; import org.hyperledger.besu.ethereum.referencetests.ReferenceTestWorldState; @@ -48,6 +50,9 @@ public class MainnetBlockProcessorTest extends AbstractBlockProcessorTest { @BeforeEach public void setup() { when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); + when(protocolSpec.getRequestsValidatorCoordinator()) + .thenReturn(RequestsValidatorCoordinator.empty()); + when(protocolSpec.getBlockHashProcessor()).thenReturn(new FrontierBlockHashProcessor()); } @Test diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetPrecompiledContractRegistriesTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetPrecompiledContractRegistriesTest.java index 0bee331d17b..8bedaad0c77 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetPrecompiledContractRegistriesTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetPrecompiledContractRegistriesTest.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.mainnet; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolScheduleTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolScheduleTest.java index 93f4ab023ec..c63a9b5ccbd 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolScheduleTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetProtocolScheduleTest.java @@ -15,14 +15,14 @@ package org.hyperledger.besu.ethereum.mainnet; import org.hyperledger.besu.config.GenesisConfigFile; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.ProtocolScheduleFixture; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; -import java.nio.charset.StandardCharsets; - -import com.google.common.io.Resources; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; @@ -71,7 +71,12 @@ public void shouldReturnDefaultProtocolSpecsWhenCustomNumbersAreNotUsed() { public void shouldOnlyUseFrontierWhenEmptyJsonConfigIsUsed() { final ProtocolSchedule sched = MainnetProtocolSchedule.fromConfig( - GenesisConfigFile.fromConfig("{}").getConfigOptions(), EvmConfiguration.DEFAULT); + GenesisConfigFile.fromConfig("{}").getConfigOptions(), + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); Assertions.assertThat(sched.getByBlockHeader(blockHeader(1L)).getName()).isEqualTo("Frontier"); Assertions.assertThat(sched.getByBlockHeader(blockHeader(Long.MAX_VALUE)).getName()) .isEqualTo("Frontier"); @@ -83,7 +88,12 @@ public void createFromConfigWithSettings() { "{\"config\": {\"homesteadBlock\": 2, \"daoForkBlock\": 3, \"eip150Block\": 14, \"eip158Block\": 15, \"byzantiumBlock\": 16, \"constantinopleBlock\": 18, \"petersburgBlock\": 19, \"chainId\":1234}}"; final ProtocolSchedule sched = MainnetProtocolSchedule.fromConfig( - GenesisConfigFile.fromConfig(json).getConfigOptions(), EvmConfiguration.DEFAULT); + GenesisConfigFile.fromConfig(json).getConfigOptions(), + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); Assertions.assertThat(sched.getByBlockHeader(blockHeader(1)).getName()).isEqualTo("Frontier"); Assertions.assertThat(sched.getByBlockHeader(blockHeader(2)).getName()).isEqualTo("Homestead"); Assertions.assertThat(sched.getByBlockHeader(blockHeader(3)).getName()) @@ -113,28 +123,11 @@ public void outOfOrderConstantinoplesFail() { () -> MainnetProtocolSchedule.fromConfig( GenesisConfigFile.fromConfig(json).getConfigOptions(), - EvmConfiguration.DEFAULT)); - } - - @Test - public void shouldCreateGoerliConfig() throws Exception { - final ProtocolSchedule sched = - MainnetProtocolSchedule.fromConfig( - GenesisConfigFile.fromConfig( - Resources.toString( - this.getClass().getResource("/goerli.json"), StandardCharsets.UTF_8)) - .getConfigOptions(), - EvmConfiguration.DEFAULT); - Assertions.assertThat(sched.getByBlockHeader(blockHeader(0L)).getName()) - .isEqualTo("Petersburg"); - Assertions.assertThat(sched.getByBlockHeader(blockHeader(1_561_651L)).getName()) - .isEqualTo("Istanbul"); - Assertions.assertThat(sched.getByBlockHeader(blockHeader(4_460_644L)).getName()) - .isEqualTo("Berlin"); - Assertions.assertThat(sched.getByBlockHeader(blockHeader(5_062_605L)).getName()) - .isEqualTo("London"); - Assertions.assertThat(sched.getByBlockHeader(blockHeader(Long.MAX_VALUE)).getName()) - .isEqualTo("London"); + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem())); } private BlockHeader blockHeader(final long number) { diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessorTest.java index 4bc2c6944e2..d8d9f3777ea 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionProcessorTest.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.mainnet; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.when; @@ -22,26 +23,35 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.feemarket.CoinbaseFeePriceCalculator; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; -import org.hyperledger.besu.ethereum.vm.BlockHashLookup; +import org.hyperledger.besu.ethereum.trie.MerkleTrieException; import org.hyperledger.besu.evm.account.MutableAccount; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.gascalculator.LondonGasCalculator; +import org.hyperledger.besu.evm.log.Log; import org.hyperledger.besu.evm.processor.AbstractMessageProcessor; +import org.hyperledger.besu.evm.tracing.OperationTracer; import org.hyperledger.besu.evm.worldstate.WorldUpdater; +import org.hyperledger.besu.evm.worldstate.WorldView; +import java.math.BigInteger; +import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Answers; import org.mockito.ArgumentCaptor; import org.mockito.Mock; @@ -60,13 +70,13 @@ class MainnetTransactionProcessorTest { @Mock private AbstractMessageProcessor contractCreationProcessor; @Mock private AbstractMessageProcessor messageCallProcessor; - @Mock private Blockchain blockchain; @Mock private WorldUpdater worldState; @Mock private ProcessableBlockHeader blockHeader; @Mock private Transaction transaction; @Mock private BlockHashLookup blockHashLookup; @Mock private MutableAccount senderAccount; + @Mock private MutableAccount receiverAccount; MainnetTransactionProcessor createTransactionProcessor(final boolean warmCoinbase) { return new MainnetTransactionProcessor( @@ -78,14 +88,15 @@ MainnetTransactionProcessor createTransactionProcessor(final boolean warmCoinbas warmCoinbase, MAX_STACK_SIZE, FeeMarket.legacy(), - CoinbaseFeePriceCalculator.frontier()); + CoinbaseFeePriceCalculator.frontier(), + new CodeDelegationProcessor(Optional.of(BigInteger.ONE))); } @Test void shouldWarmCoinbaseIfRequested() { - Optional
toAddresss = + Optional
toAddress = Optional.of(Address.fromHexString("0x2222222222222222222222222222222222222222")); - when(transaction.getTo()).thenReturn(toAddresss); + when(transaction.getTo()).thenReturn(toAddress); Address senderAddress = Address.fromHexString("0x5555555555555555555555555555555555555555"); Address coinbaseAddress = Address.fromHexString("0x4242424242424242424242424242424242424242"); @@ -93,7 +104,7 @@ void shouldWarmCoinbaseIfRequested() { when(transaction.getPayload()).thenReturn(Bytes.EMPTY); when(transaction.getSender()).thenReturn(senderAddress); when(transaction.getValue()).thenReturn(Wei.ZERO); - when(transactionValidatorFactory.get().validate(any(), any(), any())) + when(transactionValidatorFactory.get().validate(any(), any(), any(), any())) .thenReturn(ValidationResult.valid()); when(transactionValidatorFactory.get().validateForSender(any(), any(), any())) .thenReturn(ValidationResult.valid()); @@ -114,7 +125,6 @@ void shouldWarmCoinbaseIfRequested() { var transactionProcessor = createTransactionProcessor(true); transactionProcessor.processTransaction( - blockchain, worldState, blockHeader, transaction, @@ -128,7 +138,6 @@ void shouldWarmCoinbaseIfRequested() { transactionProcessor = createTransactionProcessor(false); transactionProcessor.processTransaction( - blockchain, worldState, blockHeader, transaction, @@ -141,6 +150,81 @@ void shouldWarmCoinbaseIfRequested() { assertThat(coinbaseWarmed).isFalse(); } + @ParameterizedTest + @MethodSource("provideExceptionsForTransactionProcessing") + /* + This test is to ensure that the OperationTracer.traceEndTxCalled is called even if the transaction processing fails. + @param exception will either be of class RuntimeException or MerkleTrieException + */ + void shouldTraceEndTxOnFailingTransaction(final Exception exception) { + Optional
toAddress = + Optional.of(Address.fromHexString("0x2222222222222222222222222222222222222222")); + Address senderAddress = Address.fromHexString("0x5555555555555555555555555555555555555555"); + Address coinbaseAddress = Address.fromHexString("0x4242424242424242424242424242424242424242"); + + when(transaction.getTo()).thenReturn(toAddress); + when(transaction.getHash()).thenReturn(Hash.EMPTY); + when(transaction.getPayload()).thenReturn(Bytes.EMPTY); + when(transaction.getSender()).thenReturn(senderAddress); + when(transaction.getValue()).thenReturn(Wei.ZERO); + when(transactionValidatorFactory.get().validate(any(), any(), any(), any())) + .thenReturn(ValidationResult.valid()); + when(transactionValidatorFactory.get().validateForSender(any(), any(), any())) + .thenReturn(ValidationResult.valid()); + when(worldState.getOrCreateSenderAccount(senderAddress)).thenReturn(senderAccount); + when(worldState.get(toAddress.get())).thenReturn(receiverAccount); + when(worldState.updater()).thenReturn(worldState); + // throw exception when processing the transaction + doAnswer( + invocation -> { + throw exception; + }) + .when(messageCallProcessor) + .process(any(), any()); + + final TraceEndTxTracer tracer = new TraceEndTxTracer(); + var transactionProcessor = createTransactionProcessor(true); + try { + transactionProcessor.processTransaction( + worldState, + blockHeader, + transaction, + coinbaseAddress, + blockHashLookup, + false, + ImmutableTransactionValidationParams.builder().build(), + tracer, + Wei.ZERO); + } catch (final MerkleTrieException e) { + // the MerkleTrieException is thrown again in MainnetTransactionProcessor, we ignore it here + } + + assertThat(tracer.traceEndTxCalled).isTrue(); + } + + // those two exceptions can be thrown while processing a transaction + private static Stream provideExceptionsForTransactionProcessing() { + return Stream.of( + Arguments.of(new MerkleTrieException("")), Arguments.of(new RuntimeException())); + } + + static class TraceEndTxTracer implements OperationTracer { + boolean traceEndTxCalled = false; + + @Override + public void traceEndTransaction( + final WorldView worldView, + final org.hyperledger.besu.datatypes.Transaction tx, + final boolean status, + final Bytes output, + final List logs, + final long gasUsed, + final Set
selfDestructs, + final long timeNs) { + this.traceEndTxCalled = true; + } + } + @Test void shouldCallTransactionValidatorWithExpectedTransactionValidationParams() { final ArgumentCaptor txValidationParamCaptor = @@ -152,7 +236,6 @@ void shouldCallTransactionValidatorWithExpectedTransactionValidationParams() { var transactionProcessor = createTransactionProcessor(false); transactionProcessor.processTransaction( - blockchain, worldState, blockHeader, transaction, @@ -170,7 +253,7 @@ void shouldCallTransactionValidatorWithExpectedTransactionValidationParams() { private ArgumentCaptor transactionValidationParamCaptor() { final ArgumentCaptor txValidationParamCaptor = ArgumentCaptor.forClass(TransactionValidationParams.class); - when(transactionValidatorFactory.get().validate(any(), any(), any())) + when(transactionValidatorFactory.get().validate(any(), any(), any(), any())) .thenReturn(ValidationResult.valid()); // returning invalid transaction to halt method execution when(transactionValidatorFactory diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidatorTest.java index 5ab351c2efa..182f1b609e8 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,6 +17,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams.processingBlockParams; import static org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams.transactionPoolParams; +import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.BLOB_GAS_PRICE_BELOW_CURRENT_BLOB_BASE_FEE; import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.GAS_PRICE_BELOW_CURRENT_BASE_FEE; import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.INVALID_TRANSACTION_FORMAT; import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.MAX_PRIORITY_FEE_PER_GAS_EXCEEDS_MAX_FEE_PER_GAS; @@ -71,15 +72,16 @@ public class MainnetTransactionValidatorTest { private static final Supplier SIGNATURE_ALGORITHM = Suppliers.memoize(SignatureAlgorithmFactory::getInstance); - private static final KeyPair senderKeys = SIGNATURE_ALGORITHM.get().generateKeyPair(); + protected static final KeyPair senderKeys = SIGNATURE_ALGORITHM.get().generateKeyPair(); private static final TransactionValidationParams transactionValidationParams = processingBlockParams; - @Mock private GasCalculator gasCalculator; + @Mock protected GasCalculator gasCalculator; private final Transaction basicTransaction = new TransactionTestFixture() + .nonce(30) .chainId(Optional.of(BigInteger.ONE)) .createTransaction(senderKeys); @@ -128,7 +130,9 @@ public void shouldRejectTransactionIfIntrinsicGasExceedsGasLimit() { .createTransaction(senderKeys); when(gasCalculator.transactionIntrinsicGasCost(any(), anyBoolean())).thenReturn(50L); - assertThat(validator.validate(transaction, Optional.empty(), transactionValidationParams)) + assertThat( + validator.validate( + transaction, Optional.empty(), Optional.empty(), transactionValidationParams)) .isEqualTo( ValidationResult.invalid(TransactionInvalidReason.INTRINSIC_GAS_EXCEEDS_GAS_LIMIT)); } @@ -138,7 +142,9 @@ public void shouldRejectTransactionWhenTransactionHasChainIdAndValidatorDoesNot( final TransactionValidator validator = createTransactionValidator( gasCalculator, GasLimitCalculator.constant(), false, Optional.empty()); - assertThat(validator.validate(basicTransaction, Optional.empty(), transactionValidationParams)) + assertThat( + validator.validate( + basicTransaction, Optional.empty(), Optional.empty(), transactionValidationParams)) .isEqualTo( ValidationResult.invalid( TransactionInvalidReason.REPLAY_PROTECTED_SIGNATURES_NOT_SUPPORTED)); @@ -152,7 +158,9 @@ public void shouldRejectTransactionWhenTransactionHasIncorrectChainId() { GasLimitCalculator.constant(), false, Optional.of(BigInteger.valueOf(2))); - assertThat(validator.validate(basicTransaction, Optional.empty(), transactionValidationParams)) + assertThat( + validator.validate( + basicTransaction, Optional.empty(), Optional.empty(), transactionValidationParams)) .isEqualTo(ValidationResult.invalid(TransactionInvalidReason.WRONG_CHAIN_ID)); } @@ -300,13 +308,60 @@ public void shouldRejectTransactionWithMaxPriorityFeeGreaterThanMaxFee() { .signAndBuild(new SECP256K1().generateKeyPair()); final ValidationResult validationResult = - validator.validate(transaction, Optional.of(Wei.ONE), transactionValidationParams); + validator.validate( + transaction, Optional.of(Wei.ONE), Optional.empty(), transactionValidationParams); assertThat(validationResult) .isEqualTo(ValidationResult.invalid(MAX_PRIORITY_FEE_PER_GAS_EXCEEDS_MAX_FEE_PER_GAS)); assertThat(validationResult.getErrorMessage()) .isEqualTo("max priority fee per gas cannot be greater than max fee per gas"); } + @Test + public void shouldRejectTransactionWithMaxBlobPriorityFeeSmallerThanBlobBaseFee() { + final TransactionValidator validator = + createTransactionValidator( + gasCalculator, + GasLimitCalculator.constant(), + FeeMarket.cancun(0L, Optional.empty()), + false, + Optional.of(BigInteger.ONE), + Set.of( + new TransactionType[] { + TransactionType.FRONTIER, + TransactionType.ACCESS_LIST, + TransactionType.EIP1559, + TransactionType.BLOB + }), + Integer.MAX_VALUE); + + BlobTestFixture blobTestFixture = new BlobTestFixture(); + BlobsWithCommitments bwc = blobTestFixture.createBlobsWithCommitments(1); + + final Transaction transaction = + new TransactionTestFixture() + .to(Optional.of(Address.fromHexString("0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF"))) + .type(TransactionType.BLOB) + .chainId(Optional.of(BigInteger.ONE)) + .maxFeePerGas(Optional.of(Wei.of(15))) + .maxFeePerBlobGas(Optional.of(Wei.of(7))) + .maxPriorityFeePerGas(Optional.of(Wei.of(1))) + .blobsWithCommitments(Optional.of(bwc)) + .versionedHashes(Optional.of(bwc.getVersionedHashes())) + .createTransaction(senderKeys); + + final ValidationResult validationResult = + validator.validate( + transaction, + Optional.of(Wei.ONE), + Optional.of(Wei.of(10)), + transactionValidationParams); + assertThat(validationResult) + .isEqualTo(ValidationResult.invalid(BLOB_GAS_PRICE_BELOW_CURRENT_BLOB_BASE_FEE)); + assertThat(validationResult.getErrorMessage()) + .matches( + "tx max fee per blob gas less than block blob gas fee: address 0x[0-9a-f]+ blobGasFeeCap: 7 wei, blobBaseFee: 10 wei"); + } + @Test public void shouldAcceptOnlyTransactionsInAcceptedTransactionTypes() { final TransactionValidator frontierValidator = @@ -339,14 +394,15 @@ public void shouldAcceptOnlyTransactionsInAcceptedTransactionTypes() { .createTransaction(senderKeys); assertThat( - frontierValidator.validate(transaction, Optional.empty(), transactionValidationParams)) + frontierValidator.validate( + transaction, Optional.empty(), Optional.empty(), transactionValidationParams)) .isEqualTo(ValidationResult.invalid(INVALID_TRANSACTION_FORMAT)); when(gasCalculator.transactionIntrinsicGasCost(any(), anyBoolean())).thenReturn(0L); assertThat( eip1559Validator.validate( - transaction, Optional.of(Wei.ONE), transactionValidationParams)) + transaction, Optional.of(Wei.ONE), Optional.empty(), transactionValidationParams)) .isEqualTo(ValidationResult.valid()); } @@ -369,7 +425,8 @@ public void shouldRejectTransactionIfEIP1559TransactionGasPriceLessBaseFee() { .chainId(Optional.of(BigInteger.ONE)) .createTransaction(senderKeys); final Optional basefee = Optional.of(Wei.of(150000L)); - assertThat(validator.validate(transaction, basefee, transactionValidationParams)) + assertThat( + validator.validate(transaction, basefee, Optional.empty(), transactionValidationParams)) .isEqualTo(ValidationResult.invalid(GAS_PRICE_BELOW_CURRENT_BASE_FEE)); } @@ -393,7 +450,9 @@ public void shouldAcceptZeroGasPriceTransactionIfBaseFeeIsZero() { .chainId(Optional.of(BigInteger.ONE)) .createTransaction(senderKeys); - assertThat(validator.validate(transaction, zeroBaseFee, transactionValidationParams)) + assertThat( + validator.validate( + transaction, zeroBaseFee, Optional.empty(), transactionValidationParams)) .isEqualTo(ValidationResult.valid()); } @@ -418,7 +477,8 @@ public void shouldAcceptValidEIP1559() { final Optional basefee = Optional.of(Wei.of(150000L)); when(gasCalculator.transactionIntrinsicGasCost(any(), anyBoolean())).thenReturn(50L); - assertThat(validator.validate(transaction, basefee, transactionValidationParams)) + assertThat( + validator.validate(transaction, basefee, Optional.empty(), transactionValidationParams)) .isEqualTo(ValidationResult.valid()); } @@ -444,7 +504,10 @@ public void shouldValidate1559TransactionWithPriceLowerThanBaseFeeForTransaction assertThat( validator.validate( - transaction, Optional.of(Wei.ONE), TransactionValidationParams.transactionPool())) + transaction, + Optional.of(Wei.ONE), + Optional.empty(), + TransactionValidationParams.transactionPool())) .isEqualTo(ValidationResult.valid()); } @@ -466,7 +529,8 @@ public void shouldRejectTooLargeInitcode() { .chainId(Optional.of(BigInteger.ONE)) .createTransaction(senderKeys); var validationResult = - validator.validate(bigPayload, Optional.empty(), transactionValidationParams); + validator.validate( + bigPayload, Optional.empty(), Optional.empty(), transactionValidationParams); assertThat(validationResult.isValid()).isFalse(); assertThat(validationResult.getInvalidReason()) @@ -511,7 +575,8 @@ public void shouldRejectContractCreateWithBlob() { .versionedHashes(Optional.of(List.of(VersionedHash.DEFAULT_VERSIONED_HASH))) .createTransaction(senderKeys); var validationResult = - validator.validate(blobTx, Optional.empty(), transactionValidationParams); + validator.validate( + blobTx, Optional.empty(), Optional.of(Wei.of(15)), transactionValidationParams); if (!validationResult.isValid()) { System.out.println( validationResult.getInvalidReason() + " " + validationResult.getErrorMessage()); @@ -549,7 +614,8 @@ public void shouldAcceptTransactionWithAtLeastOneBlob() { .versionedHashes(Optional.of(bwc.getVersionedHashes())) .createTransaction(senderKeys); var validationResult = - validator.validate(blobTx, Optional.empty(), transactionValidationParams); + validator.validate( + blobTx, Optional.empty(), Optional.of(Wei.of(15)), transactionValidationParams); if (!validationResult.isValid()) { System.out.println( validationResult.getInvalidReason() + " " + validationResult.getErrorMessage()); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PermissionTransactionValidatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PermissionTransactionValidatorTest.java index 0b4c948f55e..519401c97ba 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PermissionTransactionValidatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PermissionTransactionValidatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -32,7 +32,6 @@ import org.hyperledger.besu.ethereum.core.TransactionTestFixture; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; import org.hyperledger.besu.evm.account.Account; -import org.hyperledger.besu.evm.gascalculator.GasCalculator; import java.math.BigInteger; import java.util.Optional; @@ -42,7 +41,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; -import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) @@ -51,7 +49,6 @@ public class PermissionTransactionValidatorTest extends MainnetTransactionValida private static final Supplier SIGNATURE_ALGORITHM = Suppliers.memoize(SignatureAlgorithmFactory::getInstance); private static final KeyPair senderKeys = SIGNATURE_ALGORITHM.get().generateKeyPair(); - @Mock private GasCalculator gasCalculator; private final Transaction basicTransaction = new TransactionTestFixture() diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PragueConsolidationRequestValidatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PragueConsolidationRequestValidatorTest.java new file mode 100644 index 00000000000..9d0c9588cc8 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PragueConsolidationRequestValidatorTest.java @@ -0,0 +1,76 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.mainnet.ConsolidationRequestValidatorTestFixtures.blockWithConsolidationRequestsAndWithdrawalRequestsRoot; +import static org.hyperledger.besu.ethereum.mainnet.ConsolidationRequestValidatorTestFixtures.blockWithConsolidationRequestsMismatch; +import static org.hyperledger.besu.ethereum.mainnet.ConsolidationRequestValidatorTestFixtures.blockWithMoreThanMaximumConsolidationRequests; + +import org.hyperledger.besu.ethereum.core.Request; +import org.hyperledger.besu.ethereum.mainnet.ConsolidationRequestValidatorTestFixtures.ConsolidationRequestTestParameter; +import org.hyperledger.besu.ethereum.mainnet.requests.ConsolidationRequestValidator; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class PragueConsolidationRequestValidatorTest { + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("paramsForValidateConsolidationRequestParameter") + public void validateConsolidationRequestParameter( + final String description, + final Optional> maybeRequests, + final boolean expectedValidity) { + assertThat(new ConsolidationRequestValidator().validateParameter(maybeRequests)) + .isEqualTo(expectedValidity); + } + + private static Stream paramsForValidateConsolidationRequestParameter() { + return Stream.of( + Arguments.of( + "Allowed ConsolidationRequests - validating empty ConsolidationRequests", + Optional.empty(), + true), + Arguments.of( + "Allowed ConsolidationRequests - validating present ConsolidationRequests", + Optional.of(List.of()), + true)); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("validateConsolidationRequestsInBlockParamsForPrague") + public void validateConsolidationRequestsInBlock_WhenPrague( + final ConsolidationRequestTestParameter param, final boolean expectedValidity) { + assertThat( + new ConsolidationRequestValidator() + .validate( + param.block, new ArrayList<>(param.expectedConsolidationRequest), List.of())) + .isEqualTo(expectedValidity); + } + + private static Stream validateConsolidationRequestsInBlockParamsForPrague() { + return Stream.of( + Arguments.of(blockWithConsolidationRequestsAndWithdrawalRequestsRoot(), true), + Arguments.of(blockWithConsolidationRequestsMismatch(), false), + Arguments.of(blockWithMoreThanMaximumConsolidationRequests(), false)); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PragueRequestsValidatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PragueRequestsValidatorTest.java new file mode 100644 index 00000000000..833e692d7f4 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PragueRequestsValidatorTest.java @@ -0,0 +1,112 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet; + +import static java.util.Collections.emptyList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode.NONE; +import static org.hyperledger.besu.ethereum.mainnet.requests.DepositRequestProcessor.DEFAULT_DEPOSIT_CONTRACT_ADDRESS; +import static org.hyperledger.besu.ethereum.mainnet.requests.MainnetRequestsValidator.pragueRequestsValidator; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.lenient; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.datatypes.GWei; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockDataGenerator; +import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; +import org.hyperledger.besu.ethereum.core.Request; +import org.hyperledger.besu.ethereum.core.WithdrawalRequest; +import org.hyperledger.besu.ethereum.mainnet.requests.RequestsValidatorCoordinator; +import org.hyperledger.besu.evm.log.LogsBloomFilter; + +import java.util.List; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.bytes.Bytes48; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class PragueRequestsValidatorTest { + + private final BlockchainSetupUtil blockchainSetupUtil = BlockchainSetupUtil.forMainnet(); + @Mock private ProtocolSchedule protocolSchedule; + @Mock private ProtocolSpec protocolSpec; + @Mock private WithdrawalsValidator withdrawalsValidator; + + RequestsValidatorCoordinator requestValidator = + pragueRequestsValidator(DEFAULT_DEPOSIT_CONTRACT_ADDRESS); + + @BeforeEach + public void setUp() { + lenient().when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); + lenient().when(protocolSpec.getWithdrawalsValidator()).thenReturn(withdrawalsValidator); + lenient().when(withdrawalsValidator.validateWithdrawals(any())).thenReturn(true); + lenient().when(withdrawalsValidator.validateWithdrawalsRoot(any())).thenReturn(true); + lenient().when(protocolSpec.getRequestsValidatorCoordinator()).thenReturn(requestValidator); + } + + private static final BlockDataGenerator blockDataGenerator = new BlockDataGenerator(); + + @Test + void shouldValidateRequestsTest() { + WithdrawalRequest request = + new WithdrawalRequest( + Address.extract(Bytes32.fromHexStringLenient("1")), + BLSPublicKey.wrap(Bytes48.fromHexStringLenient("1")), + GWei.ONE); + + WithdrawalRequest requestTwo = + new WithdrawalRequest( + Address.extract(Bytes32.fromHexStringLenient("1")), + BLSPublicKey.wrap(Bytes48.fromHexStringLenient("1")), + GWei.ZERO); + + Optional> blockRequests = Optional.of(List.of(request)); + Optional> expectedRequests = Optional.of(List.of(requestTwo)); + Hash requestsRoot = BodyValidation.requestsRoot(blockRequests.get()); + + final BlockDataGenerator.BlockOptions blockOptions = + BlockDataGenerator.BlockOptions.create() + .setRequestsRoot(requestsRoot) + .setRequests(blockRequests) + .setGasUsed(0) + .setReceiptsRoot(BodyValidation.receiptsRoot(emptyList())) + .hasTransactions(false) + .hasOmmers(false) + .setReceiptsRoot(BodyValidation.receiptsRoot(emptyList())) + .setLogsBloom(LogsBloomFilter.empty()) + .setParentHash(blockchainSetupUtil.getBlockchain().getChainHeadHash()); + + final Block block = blockDataGenerator.block(blockOptions); + assertThat(block).isNotNull(); + assertThat( + new MainnetBlockBodyValidator(protocolSchedule) + .validateBodyLight( + blockchainSetupUtil.getProtocolContext(), + block, + emptyList(), + expectedRequests, + NONE)) + .isFalse(); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PragueWithdrawalRequestValidatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PragueWithdrawalRequestValidatorTest.java new file mode 100644 index 00000000000..8756f855928 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PragueWithdrawalRequestValidatorTest.java @@ -0,0 +1,83 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestValidatorTestFixtures.blockWithMoreThanMaximumWithdrawalRequests; +import static org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestValidatorTestFixtures.blockWithWithdrawalRequestsAndWithdrawalRequestsRoot; +import static org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestValidatorTestFixtures.blockWithWithdrawalRequestsMismatch; + +import org.hyperledger.besu.ethereum.core.Request; +import org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestValidatorTestFixtures.WithdrawalRequestTestParameter; +import org.hyperledger.besu.ethereum.mainnet.requests.WithdrawalRequestValidator; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class PragueWithdrawalRequestValidatorTest { + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("paramsForValidateWithdrawalRequestParameter") + public void validateWithdrawalRequestParameter( + final String description, + final Optional> maybeRequests, + final boolean expectedValidity) { + assertThat(new WithdrawalRequestValidator().validateParameter(maybeRequests)) + .isEqualTo(expectedValidity); + } + + private static Stream paramsForValidateWithdrawalRequestParameter() { + return Stream.of( + Arguments.of( + "Allowed WithdrawalRequests - validating empty WithdrawalRequests", + Optional.empty(), + false), + Arguments.of( + "Allowed WithdrawalRequests - validating present WithdrawalRequests", + Optional.of(List.of()), + true)); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("validateWithdrawalRequestsInBlockParamsForPrague") + public void validateWithdrawalRequestsInBlock_WhenPrague( + final WithdrawalRequestTestParameter param, final boolean expectedValidity) { + assertThat( + new WithdrawalRequestValidator() + .validate(param.block, new ArrayList<>(param.expectedWithdrawalRequest), List.of())) + .isEqualTo(expectedValidity); + } + + private static Stream validateWithdrawalRequestsInBlockParamsForPrague() { + return Stream.of( + Arguments.of(blockWithWithdrawalRequestsAndWithdrawalRequestsRoot(), true), + Arguments.of(blockWithWithdrawalRequestsMismatch(), false), + Arguments.of(blockWithMoreThanMaximumWithdrawalRequests(), false)); + } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessorTest.java index 4711e456cf0..2749b37fcdc 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/PrivacyBlockProcessorTest.java @@ -116,7 +116,6 @@ void mustCopyPreviousPrivacyGroupBlockHeadMap() { eq(firstBlock.getBody().getTransactions()), eq(firstBlock.getBody().getOmmers()), eq(Optional.empty()), - eq(Optional.empty()), any()); verify(blockProcessor) .processBlock( @@ -126,7 +125,6 @@ void mustCopyPreviousPrivacyGroupBlockHeadMap() { eq(secondBlock.getBody().getTransactions()), eq(secondBlock.getBody().getOmmers()), eq(Optional.empty()), - eq(Optional.empty()), any()); } @@ -183,7 +181,6 @@ void mustPerformRehydration() { eq(secondBlock.getBody().getTransactions()), eq(secondBlock.getBody().getOmmers()), eq(Optional.empty()), - eq(Optional.empty()), any()); } @@ -202,7 +199,7 @@ private ProtocolSpec mockProtocolSpec() { final MainnetTransactionProcessor mockPublicTransactionProcessor = mock(MainnetTransactionProcessor.class); when(mockPublicTransactionProcessor.processTransaction( - any(), any(), any(), any(), any(), any(), anyBoolean(), any(), any())) + any(), any(), any(), any(), any(), anyBoolean(), any(), any())) .thenReturn( TransactionProcessingResult.successful( Collections.emptyList(), 0, 0, Bytes.EMPTY, ValidationResult.valid())); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilderTest.java index 3d66d8df403..ad261abf472 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilderTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ProtocolScheduleBuilderTest.java @@ -16,19 +16,28 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId.BERLIN; +import static org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId.CANCUN; +import static org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId.LONDON; +import static org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId.PRAGUE; +import static org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId.SHANGHAI; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import org.hyperledger.besu.config.GenesisConfigOptions; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.MilestoneStreamingProtocolSchedule; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import java.math.BigInteger; +import java.util.Optional; import java.util.OptionalLong; import java.util.function.Function; import java.util.stream.Collectors; @@ -41,7 +50,7 @@ import org.mockito.stubbing.Answer; @ExtendWith(MockitoExtension.class) -public class ProtocolScheduleBuilderTest { +class ProtocolScheduleBuilderTest { private final long PRE_SHANGHAI_TIMESTAMP = 1680488620L; // Mon, 03 Apr 2023 02:23:40 UTC @Mock GenesisConfigOptions configOptions; @Mock private Function modifier; @@ -57,17 +66,22 @@ public void setup() { ProtocolSpecAdapters.create(0, Function.identity()), new PrivacyParameters(), false, - EvmConfiguration.DEFAULT); + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); } @Test - public void createProtocolScheduleInOrder() { + void createProtocolScheduleInOrder() { when(configOptions.getHomesteadBlockNumber()).thenReturn(OptionalLong.of(1L)); when(configOptions.getDaoForkBlock()).thenReturn(OptionalLong.of(2L)); when(configOptions.getByzantiumBlockNumber()).thenReturn(OptionalLong.of(13L)); when(configOptions.getMergeNetSplitBlockNumber()).thenReturn(OptionalLong.of(15L)); when(configOptions.getShanghaiTime()).thenReturn(OptionalLong.of(PRE_SHANGHAI_TIMESTAMP + 1)); when(configOptions.getCancunTime()).thenReturn(OptionalLong.of(PRE_SHANGHAI_TIMESTAMP + 3)); + when(configOptions.getPragueTime()).thenReturn(OptionalLong.of(PRE_SHANGHAI_TIMESTAMP + 5)); final ProtocolSchedule protocolSchedule = builder.createProtocolSchedule(); assertThat(protocolSchedule.getChainId()).contains(CHAIN_ID); @@ -102,10 +116,58 @@ public void createProtocolScheduleInOrder() { .getByBlockHeader(blockHeader(54, PRE_SHANGHAI_TIMESTAMP + 4)) .getName()) .isEqualTo("Cancun"); + assertThat( + protocolSchedule + .getByBlockHeader(blockHeader(55, PRE_SHANGHAI_TIMESTAMP + 5)) + .getName()) + .isEqualTo("Prague"); + assertThat( + protocolSchedule + .getByBlockHeader(blockHeader(56, PRE_SHANGHAI_TIMESTAMP + 6)) + .getName()) + .isEqualTo("Prague"); } @Test - public void createProtocolScheduleOverlappingUsesLatestFork() { + void milestoneForShouldQueryAllAvailableHardforks() { + final long PRAGUE_TIME = 1722333828L; + + when(configOptions.getHomesteadBlockNumber()).thenReturn(OptionalLong.of(0)); + when(configOptions.getByzantiumBlockNumber()).thenReturn(OptionalLong.of(0)); + when(configOptions.getConstantinopleBlockNumber()).thenReturn(OptionalLong.of(0)); + when(configOptions.getPetersburgBlockNumber()).thenReturn(OptionalLong.of(0)); + when(configOptions.getIstanbulBlockNumber()).thenReturn(OptionalLong.of(0)); + when(configOptions.getBerlinBlockNumber()).thenReturn(OptionalLong.of(0)); + when(configOptions.getLondonBlockNumber()).thenReturn(OptionalLong.of(0)); + when(configOptions.getShanghaiTime()).thenReturn(OptionalLong.of(0)); + when(configOptions.getCancunTime()).thenReturn(OptionalLong.of(0)); + when(configOptions.getPragueTime()).thenReturn(OptionalLong.of(PRAGUE_TIME)); + + final ProtocolSchedule protocolSchedule = builder.createProtocolSchedule(); + + final Optional maybeBerlinMileStone = protocolSchedule.milestoneFor(BERLIN); + assertThat(maybeBerlinMileStone).isPresent(); + assertThat(maybeBerlinMileStone.get()).isEqualTo(0); + + final Optional maybeLondonMileStone = protocolSchedule.milestoneFor(LONDON); + assertThat(maybeLondonMileStone).isPresent(); + assertThat(maybeLondonMileStone.get()).isEqualTo(0); + + final Optional maybeShanghaiMileStone = protocolSchedule.milestoneFor(SHANGHAI); + assertThat(maybeShanghaiMileStone).isPresent(); + assertThat(maybeShanghaiMileStone.get()).isEqualTo(0); + + final Optional maybeCancunMileStone = protocolSchedule.milestoneFor(CANCUN); + assertThat(maybeCancunMileStone).isPresent(); + assertThat(maybeCancunMileStone.get()).isEqualTo(0); + + final Optional maybePragueMileStone = protocolSchedule.milestoneFor(PRAGUE); + assertThat(maybePragueMileStone).isPresent(); + assertThat(maybePragueMileStone.get()).isEqualTo(PRAGUE_TIME); + } + + @Test + void createProtocolScheduleOverlappingUsesLatestFork() { when(configOptions.getHomesteadBlockNumber()).thenReturn(OptionalLong.of(0L)); when(configOptions.getByzantiumBlockNumber()).thenReturn(OptionalLong.of(0L)); final ProtocolSchedule protocolSchedule = builder.createProtocolSchedule(); @@ -116,7 +178,7 @@ public void createProtocolScheduleOverlappingUsesLatestFork() { } @Test - public void createProtocolScheduleOutOfOrderThrows() { + void createProtocolScheduleOutOfOrderThrows() { when(configOptions.getDaoForkBlock()).thenReturn(OptionalLong.of(0L)); when(configOptions.getArrowGlacierBlockNumber()).thenReturn(OptionalLong.of(12L)); when(configOptions.getGrayGlacierBlockNumber()).thenReturn(OptionalLong.of(11L)); @@ -127,7 +189,7 @@ public void createProtocolScheduleOutOfOrderThrows() { } @Test - public void createProtocolScheduleWithTimestampsOutOfOrderThrows() { + void createProtocolScheduleWithTimestampsOutOfOrderThrows() { when(configOptions.getDaoForkBlock()).thenReturn(OptionalLong.of(0L)); when(configOptions.getShanghaiTime()).thenReturn(OptionalLong.of(3L)); when(configOptions.getCancunTime()).thenReturn(OptionalLong.of(2L)); @@ -138,7 +200,7 @@ public void createProtocolScheduleWithTimestampsOutOfOrderThrows() { } @Test - public void modifierInsertedBetweenBlocksIsAppliedToLaterAndCreatesInterimMilestone() { + void modifierInsertedBetweenBlocksIsAppliedToLaterAndCreatesInterimMilestone() { when(configOptions.getHomesteadBlockNumber()).thenReturn(OptionalLong.of(5L)); when(modifier.apply(any())) @@ -158,7 +220,7 @@ public void modifierInsertedBetweenBlocksIsAppliedToLaterAndCreatesInterimMilest } @Test - public void modifierPastEndOfDefinedMilestonesGetsItsOwnMilestoneCreated() { + void modifierPastEndOfDefinedMilestonesGetsItsOwnMilestoneCreated() { when(modifier.apply(any())) .thenAnswer((Answer) invocation -> invocation.getArgument(0)); @@ -175,7 +237,7 @@ public void modifierPastEndOfDefinedMilestonesGetsItsOwnMilestoneCreated() { } @Test - public void modifierOnDefinedMilestoneIsAppliedButDoesNotGetAnExtraMilestoneCreated() { + void modifierOnDefinedMilestoneIsAppliedButDoesNotGetAnExtraMilestoneCreated() { when(configOptions.getHomesteadBlockNumber()).thenReturn(OptionalLong.of(5L)); when(modifier.apply(any())) .thenAnswer((Answer) invocation -> invocation.getArgument(0)); @@ -199,7 +261,11 @@ private MilestoneStreamingProtocolSchedule createScheduleModifiedAt(final int bl ProtocolSpecAdapters.create(blockNumber, modifier), new PrivacyParameters(), false, - EvmConfiguration.DEFAULT); + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); return new MilestoneStreamingProtocolSchedule( (DefaultProtocolSchedule) builder.createProtocolSchedule()); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/RefundSstoreGasTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/RefundSstoreGasTest.java index e98bbc528c1..77edb8a76fe 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/RefundSstoreGasTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/RefundSstoreGasTest.java @@ -16,6 +16,7 @@ import static org.apache.tuweni.units.bigints.UInt256.ONE; import static org.apache.tuweni.units.bigints.UInt256.ZERO; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -29,6 +30,7 @@ import org.apache.tuweni.units.bigints.UInt256; import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -134,4 +136,11 @@ public void shouldRefundCorrectGas( newValue, mockSupplierCurrentValue, mockSupplierForOriginalValue)) .isEqualTo(expectedGasRefund); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessorTest.java new file mode 100644 index 00000000000..e1d3906e734 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/SystemCallProcessorTest.java @@ -0,0 +1,113 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.BlockHashOperation; +import org.hyperledger.besu.evm.processor.AbstractMessageProcessor; +import org.hyperledger.besu.evm.processor.MessageCallProcessor; +import org.hyperledger.besu.evm.tracing.OperationTracer; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class SystemCallProcessorTest { + private static final Address CALL_ADDRESS = Address.fromHexString("0x1"); + private static final Bytes EXPECTED_OUTPUT = Bytes.fromHexString("0x01"); + private ProcessableBlockHeader mockBlockHeader; + private MainnetTransactionProcessor mockTransactionProcessor; + private BlockHashOperation.BlockHashLookup mockBlockHashLookup; + private AbstractMessageProcessor mockMessageCallProcessor; + + @BeforeEach + public void setUp() { + mockBlockHeader = mock(ProcessableBlockHeader.class); + mockTransactionProcessor = mock(MainnetTransactionProcessor.class); + mockMessageCallProcessor = mock(MessageCallProcessor.class); + mockBlockHashLookup = mock(BlockHashOperation.BlockHashLookup.class); + when(mockTransactionProcessor.getMessageProcessor(any())).thenReturn(mockMessageCallProcessor); + } + + @Test + void shouldProcessSuccessfully() { + doAnswer( + invocation -> { + MessageFrame messageFrame = invocation.getArgument(0); + messageFrame.setOutputData(EXPECTED_OUTPUT); + messageFrame.getMessageFrameStack().pop(); + messageFrame.setState(MessageFrame.State.COMPLETED_SUCCESS); + return null; + }) + .when(mockMessageCallProcessor) + .process(any(), any()); + final MutableWorldState worldState = createWorldState(CALL_ADDRESS); + Bytes actualOutput = processSystemCall(worldState); + assertThat(actualOutput).isEqualTo(EXPECTED_OUTPUT); + } + + @Test + void shouldThrowExceptionOnFailedExecution() { + doAnswer( + invocation -> { + MessageFrame messageFrame = invocation.getArgument(0); + messageFrame.getMessageFrameStack().pop(); + messageFrame.setState(MessageFrame.State.COMPLETED_FAILED); + return null; + }) + .when(mockMessageCallProcessor) + .process(any(), any()); + final MutableWorldState worldState = createWorldState(CALL_ADDRESS); + var exception = assertThrows(RuntimeException.class, () -> processSystemCall(worldState)); + assertThat(exception.getMessage()).isEqualTo("System call did not execute to completion"); + } + + @Test + void shouldReturnNullWhenContractDoesNotExist() { + final MutableWorldState worldState = InMemoryKeyValueStorageProvider.createInMemoryWorldState(); + Bytes actualOutput = processSystemCall(worldState); + assertThat(actualOutput).isNull(); + } + + Bytes processSystemCall(final MutableWorldState worldState) { + SystemCallProcessor systemCallProcessor = new SystemCallProcessor(mockTransactionProcessor); + return systemCallProcessor.process( + CALL_ADDRESS, + worldState.updater(), + mockBlockHeader, + OperationTracer.NO_TRACING, + mockBlockHashLookup); + } + + private MutableWorldState createWorldState(final Address address) { + final MutableWorldState worldState = InMemoryKeyValueStorageProvider.createInMemoryWorldState(); + final WorldUpdater updater = worldState.updater(); + updater.getOrCreate(address); + updater.commit(); + return worldState; + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/TargetingGasLimitCalculatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/TargetingGasLimitCalculatorTest.java index b19d2310c5c..4932eef30d8 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/TargetingGasLimitCalculatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/TargetingGasLimitCalculatorTest.java @@ -27,22 +27,6 @@ public class TargetingGasLimitCalculatorTest { private static final long ADJUSTMENT_FACTOR = 1024L; - @Test - public void verifyGasLimitIsIncreasedWithinLimits() { - FrontierTargetingGasLimitCalculator targetingGasLimitCalculator = - new FrontierTargetingGasLimitCalculator(); - assertThat(targetingGasLimitCalculator.nextGasLimit(8_000_000L, 10_000_000L, 1L)) - .isEqualTo(8_000_000L + ADJUSTMENT_FACTOR); - } - - @Test - public void verifyGasLimitIsDecreasedWithinLimits() { - FrontierTargetingGasLimitCalculator targetingGasLimitCalculator = - new FrontierTargetingGasLimitCalculator(); - assertThat(targetingGasLimitCalculator.nextGasLimit(12_000_000L, 10_000_000L, 1L)) - .isEqualTo(12_000_000L - ADJUSTMENT_FACTOR); - } - @Test public void verifyGasLimitReachesTarget() { final long target = 10_000_000L; @@ -55,12 +39,45 @@ public void verifyGasLimitReachesTarget() { .isEqualTo(target); } + @Test + public void verifyAdjustmentDeltas() { + assertDeltas(20000000L, 20019530L, 19980470L); + assertDeltas(40000000L, 40039061L, 39960939L); + } + + private void assertDeltas( + final long gasLimit, final long expectedIncrease, final long expectedDecrease) { + FrontierTargetingGasLimitCalculator targetingGasLimitCalculator = + new FrontierTargetingGasLimitCalculator(); + // increase + assertThat(targetingGasLimitCalculator.nextGasLimit(gasLimit, gasLimit * 2, 1L)) + .isEqualTo(expectedIncrease); + // decrease + assertThat(targetingGasLimitCalculator.nextGasLimit(gasLimit, 0, 1L)) + .isEqualTo(expectedDecrease); + // small decrease + assertThat(targetingGasLimitCalculator.nextGasLimit(gasLimit, gasLimit - 1, 1L)) + .isEqualTo(gasLimit - 1); + // small increase + assertThat(targetingGasLimitCalculator.nextGasLimit(gasLimit, gasLimit + 1, 1L)) + .isEqualTo(gasLimit + 1); + // no change + assertThat(targetingGasLimitCalculator.nextGasLimit(gasLimit, gasLimit, 1L)) + .isEqualTo(gasLimit); + } + @Test public void verifyMinGasLimit() { assertThat(AbstractGasLimitSpecification.isValidTargetGasLimit(DEFAULT_MIN_GAS_LIMIT - 1)) .isFalse(); } + @Test + public void verifyMaxGasLimit() { + assertThat(AbstractGasLimitSpecification.isValidTargetGasLimit(Long.MAX_VALUE - 1)).isTrue(); + assertThat(AbstractGasLimitSpecification.isValidTargetGasLimit(Long.MAX_VALUE)).isTrue(); + } + @Test public void verifyWithinGasLimitDelta() { final long targetGasLimit = 10_000_000L; diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidationFactoryTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidationFactoryTest.java index 334439b0844..9e29acbe637 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidationFactoryTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/TransactionValidationFactoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ValidationTestUtils.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ValidationTestUtils.java index 98fbaa1734f..fa912a6de87 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ValidationTestUtils.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/ValidationTestUtils.java @@ -17,7 +17,7 @@ import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.Deposit; +import org.hyperledger.besu.ethereum.core.Request; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; @@ -59,11 +59,11 @@ public static BlockBody readBody(final long num) throws IOException { input.isEndOfCurrentList() ? Optional.empty() : Optional.of(input.readList(Withdrawal::readFrom)); - final Optional> deposits = + final Optional> requests = input.isEndOfCurrentList() ? Optional.empty() - : Optional.of(input.readList(Deposit::readFrom)); - return new BlockBody(transactions, ommers, withdrawals, deposits); + : Optional.of(input.readList(Request::readFrom)); + return new BlockBody(transactions, ommers, withdrawals, requests); } public static Block readBlock(final long num) throws IOException { diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalRequestValidatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalRequestValidatorTest.java new file mode 100644 index 00000000000..f93067d94f2 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalRequestValidatorTest.java @@ -0,0 +1,87 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestValidatorTestFixtures.blockWithWithdrawalRequestsAndWithdrawalRequestsRoot; +import static org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestValidatorTestFixtures.blockWithWithdrawalRequestsWithoutWithdrawalRequestsRoot; +import static org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestValidatorTestFixtures.blockWithoutWithdrawalRequestsAndWithdrawalRequestsRoot; +import static org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestValidatorTestFixtures.blockWithoutWithdrawalRequestsWithWithdrawalRequestsRoot; + +import org.hyperledger.besu.ethereum.core.Request; +import org.hyperledger.besu.ethereum.mainnet.WithdrawalRequestValidatorTestFixtures.WithdrawalRequestTestParameter; +import org.hyperledger.besu.ethereum.mainnet.requests.ProhibitedRequestsValidator; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class WithdrawalRequestValidatorTest { + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("paramsForValidateWithdrawalRequestParameter") + public void validateWithdrawalRequestParameter( + final String description, + final Optional> maybeWithdrawalRequests, + final boolean expectedValidity) { + assertThat(new ProhibitedRequestsValidator().validateParameter(maybeWithdrawalRequests)) + .isEqualTo(expectedValidity); + } + + private static Stream paramsForValidateWithdrawalRequestParameter() { + return Stream.of( + Arguments.of( + "Prohibited WithdrawalRequests - validating empty WithdrawalRequests", + Optional.empty(), + true), + Arguments.of( + "Prohibited WithdrawalRequests - validating present WithdrawalRequests", + Optional.of(List.of()), + false)); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("validateWithdrawalRequestsInBlockParamsForProhibited") + public void validateWithdrawalRequestsInBlock_WhenProhibited( + final WithdrawalRequestTestParameter param, final boolean expectedValidity) { + + var list = param.expectedWithdrawalRequest; + var requests = new ArrayList(list).stream().toList(); + + assertThat(new ProhibitedRequestsValidator().validate(param.block, requests, List.of())) + .isEqualTo(expectedValidity); + } + + private static Stream validateWithdrawalRequestsInBlockParamsForProhibited() { + return Stream.of( + Arguments.of(blockWithWithdrawalRequestsAndWithdrawalRequestsRoot(), false), + Arguments.of(blockWithWithdrawalRequestsWithoutWithdrawalRequestsRoot(), false), + Arguments.of(blockWithoutWithdrawalRequestsWithWithdrawalRequestsRoot(), false), + Arguments.of(blockWithoutWithdrawalRequestsAndWithdrawalRequestsRoot(), true)); + } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalRequestValidatorTestFixtures.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalRequestValidatorTestFixtures.java new file mode 100644 index 00000000000..e719810c282 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalRequestValidatorTestFixtures.java @@ -0,0 +1,186 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet; + +import static org.hyperledger.besu.ethereum.mainnet.requests.WithdrawalRequestValidator.MAX_WITHDRAWAL_REQUESTS_PER_BLOCK; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BLSPublicKey; +import org.hyperledger.besu.datatypes.GWei; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockDataGenerator; +import org.hyperledger.besu.ethereum.core.Request; +import org.hyperledger.besu.ethereum.core.WithdrawalRequest; + +import java.util.List; +import java.util.Optional; +import java.util.stream.IntStream; + +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.bytes.Bytes48; + +public class WithdrawalRequestValidatorTestFixtures { + + private static final BlockDataGenerator blockDataGenerator = new BlockDataGenerator(); + + static WithdrawalRequestTestParameter blockWithWithdrawalRequestsAndWithdrawalRequestsRoot() { + final WithdrawalRequest withdrawalRequest = createWithdrawalRequest(); + final Optional> maybeWithdrawalRequests = + Optional.of(java.util.List.of(withdrawalRequest)); + + final BlockDataGenerator.BlockOptions blockOptions = + BlockDataGenerator.BlockOptions.create() + .setRequestsRoot(BodyValidation.requestsRoot(maybeWithdrawalRequests.get())) + .setRequests(maybeWithdrawalRequests); + final Block block = blockDataGenerator.block(blockOptions); + + return new WithdrawalRequestTestParameter( + "Block with withdrawal requests and withdrawal_requests_root", + block, + Optional.of(java.util.List.of(withdrawalRequest))); + } + + static WithdrawalRequestTestParameter blockWithoutWithdrawalRequestsWithWithdrawalRequestsRoot() { + final BlockDataGenerator.BlockOptions blockOptions = + BlockDataGenerator.BlockOptions.create() + .setRequestsRoot(Hash.EMPTY) + .setRequests(Optional.empty()); + final Block block = blockDataGenerator.block(blockOptions); + + return new WithdrawalRequestTestParameter( + "Block with withdrawal_requests_root but without withdrawal requests", + block, + Optional.empty()); + } + + static WithdrawalRequestTestParameter blockWithWithdrawalRequestsWithoutWithdrawalRequestsRoot() { + final WithdrawalRequest withdrawalRequest = createWithdrawalRequest(); + final Optional> requests = Optional.of(java.util.List.of(withdrawalRequest)); + + final BlockDataGenerator.BlockOptions blockOptions = + BlockDataGenerator.BlockOptions.create().setRequests(requests); + final Block block = blockDataGenerator.block(blockOptions); + + return new WithdrawalRequestTestParameter( + "Block with withdrawal requests but without withdrawal_requests_root", + block, + Optional.of(java.util.List.of(withdrawalRequest))); + } + + static WithdrawalRequestTestParameter blockWithoutWithdrawalRequestsAndWithdrawalRequestsRoot() { + + final BlockDataGenerator.BlockOptions blockOptions = + BlockDataGenerator.BlockOptions.create().setRequests(Optional.empty()); + final Block block = blockDataGenerator.block(blockOptions); + + return new WithdrawalRequestTestParameter( + "Block without withdrawal requests and withdrawal_requests_root", block, Optional.empty()); + } + + static WithdrawalRequestTestParameter blockWithWithdrawalRequestsRootMismatch() { + final WithdrawalRequest withdrawalRequest = createWithdrawalRequest(); + + final Optional> requests = Optional.of(java.util.List.of(withdrawalRequest)); + + final BlockDataGenerator.BlockOptions blockOptions = + BlockDataGenerator.BlockOptions.create().setRequestsRoot(Hash.EMPTY).setRequests(requests); + final Block block = blockDataGenerator.block(blockOptions); + + return new WithdrawalRequestTestParameter( + "Block with withdrawal_requests_root mismatch", + block, + Optional.of(java.util.List.of(withdrawalRequest))); + } + + static WithdrawalRequestTestParameter blockWithWithdrawalRequestsMismatch() { + final WithdrawalRequest withdrawalRequest = createWithdrawalRequest(); + + final Optional> requests = + Optional.of(java.util.List.of(withdrawalRequest, withdrawalRequest)); + + final BlockDataGenerator.BlockOptions blockOptions = + BlockDataGenerator.BlockOptions.create() + .setRequestsRoot(BodyValidation.requestsRoot(requests.get())) + .setRequests(requests); + final Block block = blockDataGenerator.block(blockOptions); + + return new WithdrawalRequestTestParameter( + "Block with withdrawal requests mismatch", + block, + Optional.of(java.util.List.of(withdrawalRequest, withdrawalRequest)), + List.of(createWithdrawalRequest())); + } + + static WithdrawalRequestTestParameter blockWithMoreThanMaximumWithdrawalRequests() { + final List withdrawalRequest = + IntStream.range(0, MAX_WITHDRAWAL_REQUESTS_PER_BLOCK + 1) + .mapToObj(__ -> createWithdrawalRequest()) + .toList(); + + final Optional> maybeWithdrawalRequest = Optional.of(withdrawalRequest); + final Optional> maybeRequests = + Optional.of(withdrawalRequest.stream().map(r -> (Request) r).toList()); + + final BlockDataGenerator.BlockOptions blockOptions = + BlockDataGenerator.BlockOptions.create() + .setRequestsRoot(BodyValidation.requestsRoot(maybeRequests.get())) + .setRequests(maybeRequests); + final Block block = blockDataGenerator.block(blockOptions); + + return new WithdrawalRequestTestParameter( + "Block with more than maximum withdrawal requests", block, maybeWithdrawalRequest); + } + + static WithdrawalRequest createWithdrawalRequest() { + return new WithdrawalRequest( + Address.extract(Bytes32.random()), BLSPublicKey.wrap(Bytes48.random()), GWei.ONE); + } + + static class WithdrawalRequestTestParameter { + + String description; + Block block; + Optional> maybeWithdrawalRequest; + List expectedWithdrawalRequest; + + public WithdrawalRequestTestParameter( + final String description, + final Block block, + final Optional> maybeWithdrawalRequest) { + this( + description, + block, + maybeWithdrawalRequest, + maybeWithdrawalRequest.orElseGet(java.util.List::of)); + } + + public WithdrawalRequestTestParameter( + final String description, + final Block block, + final Optional> maybeWithdrawalRequest, + final List expectedWithdrawalRequest) { + this.description = description; + this.block = block; + this.maybeWithdrawalRequest = maybeWithdrawalRequest; + this.expectedWithdrawalRequest = expectedWithdrawalRequest; + } + + @Override + public String toString() { + return description; + } + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalsProcessorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalsProcessorTest.java index db82d33e33c..4f6434bbd6f 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalsProcessorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalsProcessorTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalsValidatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalsValidatorTest.java index 4b96fd6c1ed..d05a9650928 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalsValidatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/WithdrawalsValidatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.mainnet; import static java.util.Collections.emptyList; diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/blockhash/BlockHashProcessorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/blockhash/BlockHashProcessorTest.java new file mode 100644 index 00000000000..8ef178559a9 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/blockhash/BlockHashProcessorTest.java @@ -0,0 +1,132 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.blockhash; + +import static org.hyperledger.besu.datatypes.Hash.fromHexStringLenient; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.Optional; + +import org.apache.tuweni.units.bigints.UInt256; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class BlockHashProcessorTest { + private Blockchain blockchain; + private WorldUpdater worldUpdater; + private MutableWorldState mutableWorldState; + private MutableAccount account; + private BlockHashProcessor processor; + + private final long historicalWindow = 255; + + @BeforeEach + void setUp() { + blockchain = mock(Blockchain.class); + mutableWorldState = mock(MutableWorldState.class); + worldUpdater = mock(WorldUpdater.class); + account = mock(MutableAccount.class); + when(mutableWorldState.updater()).thenReturn(worldUpdater); + when(worldUpdater.getOrCreate(PragueBlockHashProcessor.HISTORY_STORAGE_ADDRESS)) + .thenReturn(account); + } + + @Test + void shouldStoreParentBlockHash() { + long currentBlock = 3; + processor = new PragueBlockHashProcessor(); + BlockHeader currentBlockHeader = mockBlockHeader(currentBlock); + mockAncestorHeaders(currentBlockHeader, 3); + processor.processBlockHashes(blockchain, mutableWorldState, currentBlockHeader); + // only parent slot number must be set + verify(account, times(1)).setStorageValue(any(), any()); + verifyAccount(currentBlock - 1, historicalWindow); + } + + @Test + void shouldNotStoreBlockHashForGenesisBlock() { + // For the fork to be activated at genesis, no history is written to the genesis state, and at + // the start of block 1, genesis hash will be written as a normal operation to slot 0. + long currentBlock = 0; + processor = new PragueBlockHashProcessor(); + BlockHeader currentBlockHeader = mockBlockHeader(currentBlock); + mockAncestorHeaders(currentBlockHeader, 0); + + processor.processBlockHashes(blockchain, mutableWorldState, currentBlockHeader); + verifyNoInteractions(account); + } + + @Test + void shouldStoreAncestorBlockHashesAtForkCorrectlyParentIsGenesis() { + // for activation at block 1, only genesis hash will be written at slot 0 as there is no + // additional history that needs to be persisted. + long currentBlock = 1; + processor = new PragueBlockHashProcessor(); + BlockHeader currentBlockHeader = mockBlockHeader(currentBlock); + mockAncestorHeaders(currentBlockHeader, 10); + + processor.processBlockHashes(blockchain, mutableWorldState, currentBlockHeader); + verify(account, times(1)).setStorageValue(any(), any()); + verifyAccount(0, historicalWindow); + } + + @Test + void shouldWriteGenesisHashAtSlot0() { + processor = new PragueBlockHashProcessor(); + BlockHeader header = mockBlockHeader(1); + mockAncestorHeaders(header, 1); + processor.processBlockHashes(blockchain, mutableWorldState, header); + verify(account) + .setStorageValue(UInt256.valueOf(0), UInt256.fromHexString(Hash.ZERO.toHexString())); + } + + private void verifyAccount(final long number, final long historicalWindow) { + verify(account) + .setStorageValue(UInt256.valueOf(number % historicalWindow), UInt256.valueOf(number)); + } + + private void mockAncestorHeaders(final BlockHeader blockHeader, final int count) { + long firstAncestor = Math.max(blockHeader.getNumber() - count, 0); + var block = blockHeader; + for (long i = blockHeader.getNumber(); i > firstAncestor; i--) { + long parentNumber = block.getNumber() - 1; + block = mockBlockHeader(parentNumber); + } + } + + private BlockHeader mockBlockHeader(final long currentNumber) { + BlockHeader blockHeader = mock(BlockHeader.class); + when(blockHeader.getNumber()).thenReturn(currentNumber); + Hash hash = fromHexStringLenient("0x" + Long.toHexString(currentNumber)); + Hash parentHash = fromHexStringLenient("0x" + Long.toHexString(currentNumber - 1)); + when(blockHeader.getHash()).thenReturn(hash); + when(blockHeader.getTimestamp()).thenReturn(currentNumber); + when(blockHeader.getParentHash()).thenReturn(parentHash); + when(blockchain.getBlockHeader(hash)).thenReturn(Optional.of(blockHeader)); + return blockHeader; + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/feemarket/CancunFeeMarketTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/feemarket/CancunFeeMarketTest.java index 2394d66da51..06fe62de581 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/feemarket/CancunFeeMarketTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/feemarket/CancunFeeMarketTest.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.mainnet.feemarket; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/feemarket/FixedBaseFeeMarketTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/feemarket/FixedBaseFeeMarketTest.java new file mode 100644 index 00000000000..2bc3cf783ad --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/feemarket/FixedBaseFeeMarketTest.java @@ -0,0 +1,152 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.feemarket; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.crypto.KeyPair; +import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; +import org.hyperledger.besu.datatypes.BlobGas; +import org.hyperledger.besu.datatypes.TransactionType; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.TransactionTestFixture; + +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class FixedBaseFeeMarketTest { + + private static final KeyPair KEY_PAIR1 = + SignatureAlgorithmFactory.getInstance().generateKeyPair(); + private static final long FORK_BLOCK = 0; + private FixedBaseFeeMarket fixedBaseFeeMarket; + + @BeforeEach + public void setUp() throws Exception { + fixedBaseFeeMarket = new FixedBaseFeeMarket(FORK_BLOCK, Wei.ONE); + } + + @Test + public void getBasefeeMaxChangeDenominatorShouldUseLondonDefault() { + assertThat(fixedBaseFeeMarket.getBasefeeMaxChangeDenominator()) + .isEqualTo(LondonFeeMarket.DEFAULT_BASEFEE_MAX_CHANGE_DENOMINATOR); + } + + @Test + public void getInitialBasefeeShouldBeZero() { + assertThat(fixedBaseFeeMarket.getInitialBasefee()).isEqualTo(Wei.ONE); + } + + @Test + public void getSlackCoefficientShouldUseLondonDefault() { + assertThat(fixedBaseFeeMarket.getSlackCoefficient()) + .isEqualTo(LondonFeeMarket.DEFAULT_SLACK_COEFFICIENT); + } + + @Test + public void getTransactionPriceCalculatorShouldBeEIP1559() { + // only eip1559 will read the fee per gas values + final Transaction transaction = + new TransactionTestFixture() + .type(TransactionType.EIP1559) + .maxFeePerGas(Optional.of(Wei.of(8))) + .maxPriorityFeePerGas(Optional.of(Wei.of(8))) + .gasPrice(null) + .createTransaction(KEY_PAIR1); + + assertThat( + fixedBaseFeeMarket + .getTransactionPriceCalculator() + .price(transaction, Optional.of(Wei.ZERO))) + .isEqualTo(Wei.of(8)); + } + + @Test + public void satisfiesFloorTxCostWhenGasFeeIsNonZero() { + final Transaction transaction = + new TransactionTestFixture() + .type(TransactionType.FRONTIER) + .gasPrice(Wei.of(7)) + .createTransaction(KEY_PAIR1); + assertThat(fixedBaseFeeMarket.satisfiesFloorTxFee(transaction)).isTrue(); + } + + @Test + public void satisfiesFloorTxCostWhenGasFeeIsZero() { + final Transaction transaction = + new TransactionTestFixture() + .type(TransactionType.EIP1559) + .maxFeePerGas(Optional.of(Wei.ZERO)) + .maxPriorityFeePerGas(Optional.of(Wei.ZERO)) + .gasPrice(null) + .createTransaction(KEY_PAIR1); + assertThat(fixedBaseFeeMarket.satisfiesFloorTxFee(transaction)).isFalse(); + } + + @Test + public void computeBaseFeeReturnsFixedValue() { + assertThat(fixedBaseFeeMarket.computeBaseFee(1L, Wei.of(1), 1L, 2L)).isEqualTo(Wei.ONE); + } + + @Test + public void baseFeeValidationModeShouldBeNoneWhenIsForkBlock() { + assertThat(fixedBaseFeeMarket.baseFeeValidationMode(FORK_BLOCK)) + .isEqualTo(BaseFeeMarket.ValidationMode.NONE); + } + + @Test + public void baseFeeValidationModeShouldBeNoneWhenIsNotForkBlock() { + assertThat(fixedBaseFeeMarket.baseFeeValidationMode(FORK_BLOCK + 1)) + .isEqualTo(BaseFeeMarket.ValidationMode.NONE); + } + + @Test + public void gasLimitValidationModeShouldBeInitialWhenIsForkBlock() { + assertThat(fixedBaseFeeMarket.gasLimitValidationMode(FORK_BLOCK)) + .isEqualTo(BaseFeeMarket.ValidationMode.INITIAL); + } + + @Test + public void gasLimitValidationModeShouldBeOngoingWhenIsNotForkBlock() { + assertThat(fixedBaseFeeMarket.gasLimitValidationMode(FORK_BLOCK + 1)) + .isEqualTo(BaseFeeMarket.ValidationMode.ONGOING); + } + + @Test + public void isBeforeForkBlockShouldBeTrue() { + final FixedBaseFeeMarket fixedBaseFeeMarket = new FixedBaseFeeMarket(10, Wei.ONE); + assertThat(fixedBaseFeeMarket.isBeforeForkBlock(9)).isTrue(); + } + + @Test + public void isBeforeForkBlockShouldBeFalse() { + final FixedBaseFeeMarket fixedBaseFeeMarket = new FixedBaseFeeMarket(10, Wei.ONE); + assertThat(fixedBaseFeeMarket.isBeforeForkBlock(10)).isFalse(); + assertThat(fixedBaseFeeMarket.isBeforeForkBlock(11)).isFalse(); + } + + @Test + public void implementsDataFeedShouldReturnFalse() { + assertThat(fixedBaseFeeMarket.implementsDataFee()).isFalse(); + } + + @Test + public void dataPriceShouldReturnsZero() { + assertThat(fixedBaseFeeMarket.blobGasPricePerGas(BlobGas.ONE)).isEqualTo(Wei.ZERO); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LondonFeeMarketTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LondonFeeMarketTest.java index 1d0bdd53c97..032ede3a8ee 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LondonFeeMarketTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/feemarket/LondonFeeMarketTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/feemarket/ZeroBaseFeeMarketTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/feemarket/ZeroBaseFeeMarketTest.java index 4047c647aec..47f251cc409 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/feemarket/ZeroBaseFeeMarketTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/feemarket/ZeroBaseFeeMarketTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/BaseFeeMarketBlockHeaderGasPriceValidationRuleTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/BaseFeeMarketBlockHeaderGasPriceValidationRuleTest.java index ca2cfe4d565..2cd1936b3ab 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/BaseFeeMarketBlockHeaderGasPriceValidationRuleTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/BaseFeeMarketBlockHeaderGasPriceValidationRuleTest.java @@ -128,4 +128,29 @@ public void shouldReturnTrueIfUsingZeroBaseFeeMarketOnNonZeroLondonForkBlock() { blockHeader(FORK_BLOCK - 1, 0, Optional.of(londonFeeMarketBaseFee)))) .isTrue(); } + + @Test + public void shouldReturnTrueIfUsingFixedBaseFeeMarket() { + final BaseFeeMarket fixedBaseFeeMarket = FeeMarket.fixedBaseFee(FORK_BLOCK, Wei.ONE); + final var validationRule = + new BaseFeeMarketBlockHeaderGasPriceValidationRule(fixedBaseFeeMarket); + assertThat( + validationRule.validate( + blockHeader(FORK_BLOCK + 2, 0, Optional.of(fixedBaseFeeMarket.getInitialBasefee())), + blockHeader(FORK_BLOCK + 1, 0, Optional.of(feeMarket.getInitialBasefee()), 2))) + .isTrue(); + } + + @Test + public void shouldReturnTrueIfUsingFixedBaseFeeMarketOnNonZeroLondonForkBlock() { + final BaseFeeMarket zeroBaseFeeMarket = FeeMarket.fixedBaseFee(FORK_BLOCK, Wei.ONE); + final var validationRule = + new BaseFeeMarketBlockHeaderGasPriceValidationRule(zeroBaseFeeMarket); + final Wei londonFeeMarketBaseFee = feeMarket.getInitialBasefee(); + assertThat( + validationRule.validate( + blockHeader(FORK_BLOCK, 0, Optional.of(londonFeeMarketBaseFee)), + blockHeader(FORK_BLOCK - 1, 0, Optional.of(londonFeeMarketBaseFee)))) + .isTrue(); + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/BlobGasValidationRuleTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/BlobGasValidationRuleTest.java index 34f7079be10..858a8a9a26c 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/BlobGasValidationRuleTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/BlobGasValidationRuleTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.mainnet.headervalidationrules; import static org.assertj.core.api.Assertions.assertThat; diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/GasLimitElasticityValidationRuleFixedBaseFeeMarketTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/GasLimitElasticityValidationRuleFixedBaseFeeMarketTest.java new file mode 100644 index 00000000000..94b58fa6904 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/GasLimitElasticityValidationRuleFixedBaseFeeMarketTest.java @@ -0,0 +1,83 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.headervalidationrules; + +import static java.lang.Long.MAX_VALUE; +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.mainnet.feemarket.BaseFeeMarket; +import org.hyperledger.besu.ethereum.mainnet.feemarket.FixedBaseFeeMarket; + +import java.util.Optional; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +public class GasLimitElasticityValidationRuleFixedBaseFeeMarketTest { + + private static final Optional fixedBaseFeeMarket = + Optional.of(new FixedBaseFeeMarket(10, Wei.ONE)); + + public GasLimitRangeAndDeltaValidationRule uut = + new GasLimitRangeAndDeltaValidationRule(5000, MAX_VALUE, fixedBaseFeeMarket); + + @ParameterizedTest + @CsvSource({ + "20000000, 10000000, 10, true", + "20019530, 10000000, 10, true", + "20019531, 10000000, 10, false", + "19980470, 10000000, 10, true", + "19980469, 10000000, 10, false", + "20000000, 20000000, 11, true", + "20019530, 20000000, 11, true", + "20019531, 20000000, 11, false", + "19980470, 20000000, 11, true", + "19980469, 20000000, 11, false", + "40039061, 40000000, 11, true", + "40039062, 40000000, 11, false", + "39960939, 40000000, 11, true", + "39960938, 40000000, 11, false", + "4999, 40000000, 11, false" + }) + public void test( + final long headerGasLimit, + final long parentGasLimit, + final long headerNumber, + final boolean expectedResult) { + + final BlockHeaderTestFixture blockHeaderBuilder = new BlockHeaderTestFixture(); + + blockHeaderBuilder.number(headerNumber); + blockHeaderBuilder.gasLimit(headerGasLimit); + final BlockHeader header = blockHeaderBuilder.buildHeader(); + + blockHeaderBuilder.number(headerNumber - 1); + blockHeaderBuilder.gasLimit(parentGasLimit); + final BlockHeader parent = blockHeaderBuilder.buildHeader(); + + assertThat(uut.validate(header, parent)).isEqualTo(expectedResult); + } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/GasLimitElasticityValidationRuleTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/GasLimitElasticityValidationRuleTest.java index bd69303a05c..1f74233445c 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/GasLimitElasticityValidationRuleTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/GasLimitElasticityValidationRuleTest.java @@ -24,6 +24,7 @@ import java.util.Optional; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -69,4 +70,11 @@ public void testGasLimitElasticityValidationRule( assertThat(uut.validate(header, parent)).isEqualTo(expectedResult); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/GasLimitElasticityValidationRuleZeroBaseFeeMarketTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/GasLimitElasticityValidationRuleZeroBaseFeeMarketTest.java index a25345994bc..39462a22cdb 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/GasLimitElasticityValidationRuleZeroBaseFeeMarketTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/GasLimitElasticityValidationRuleZeroBaseFeeMarketTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -24,6 +24,7 @@ import java.util.Optional; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -71,4 +72,11 @@ public void test( assertThat(uut.validate(header, parent)).isEqualTo(expectedResult); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/GasLimitRangeAndDeltaValidationRuleTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/GasLimitRangeAndDeltaValidationRuleTest.java index 635f8e1c190..665da47354c 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/GasLimitRangeAndDeltaValidationRuleTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/GasLimitRangeAndDeltaValidationRuleTest.java @@ -23,6 +23,7 @@ import java.util.Optional; import java.util.stream.Stream; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -102,4 +103,11 @@ public void test( assertThat(uut.validate(header, parent)).isEqualTo(expectedResult); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/GasUsageValidationRuleTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/GasUsageValidationRuleTest.java index 06dff080461..a06fa0c7001 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/GasUsageValidationRuleTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/GasUsageValidationRuleTest.java @@ -21,6 +21,7 @@ import java.util.stream.Stream; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -48,4 +49,11 @@ public void test(final long gasUsed, final long gasLimit, final boolean expected assertThat(uut.validate(header, null)).isEqualTo(expectedResult); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/ProofOfWorkValidationRuleTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/ProofOfWorkValidationRuleTest.java index 8d831ab7c4a..8f11434d1e0 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/ProofOfWorkValidationRuleTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/ProofOfWorkValidationRuleTest.java @@ -37,6 +37,7 @@ import java.util.stream.Stream; import org.apache.tuweni.units.bigints.UInt256; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -216,4 +217,11 @@ private BlockHeaderFunctions mainnetBlockHashFunction() { final ProtocolSchedule protocolSchedule = ProtocolScheduleFixture.MAINNET; return ScheduleBasedBlockHeaderFunctions.create(protocolSchedule); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/TimestampValidationRuleTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/TimestampValidationRuleTest.java index 24dc4987afe..42ffea95061 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/TimestampValidationRuleTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/headervalidationrules/TimestampValidationRuleTest.java @@ -102,7 +102,8 @@ public void headerNewerThanCurrentSystemFailsValidation() { final BlockHeader parent = headerBuilder.buildHeader(); // Create header for validation with a timestamp in the future (1 second too far away) - headerBuilder.timestamp(parent.getTimestamp() + acceptableClockDrift + 1); + // (+1 to avoid spurious failures) + headerBuilder.timestamp(parent.getTimestamp() + acceptableClockDrift + 2); final BlockHeader header = headerBuilder.buildHeader(); assertThat(uut00.validate(header, parent)).isFalse(); @@ -124,7 +125,7 @@ public void futureHeadersAreValidIfTimestampWithinTolerance() { TimeUnit.SECONDS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS)); final BlockHeader parent = headerBuilder.buildHeader(); - // Create header for validation with a timestamp in the future (1 second too far away) + // Create header for validation with a timestamp an acceptable amount in the future // (-1) to prevent spurious failures headerBuilder.timestamp(parent.getTimestamp() + acceptableClockDrift - 1); final BlockHeader header = headerBuilder.buildHeader(); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/parallelization/ParallelizedConcurrentTransactionProcessorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/parallelization/ParallelizedConcurrentTransactionProcessorTest.java new file mode 100644 index 00000000000..bba91843568 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/parallelization/ParallelizedConcurrentTransactionProcessorTest.java @@ -0,0 +1,213 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.parallelization; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor; +import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; +import org.hyperledger.besu.ethereum.mainnet.ValidationResult; +import org.hyperledger.besu.ethereum.privacy.storage.PrivateMetadataUpdater; +import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; +import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.NoOpBonsaiCachedWorldStorageManager; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.NoopBonsaiCachedMerkleTrieLoader; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldState; +import org.hyperledger.besu.ethereum.trie.diffbased.common.trielog.NoOpTrieLogManager; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldStateConfig; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.DiffBasedWorldStateUpdateAccumulator; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.operation.BlockHashOperation; +import org.hyperledger.besu.evm.tracing.OperationTracer; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; + +import java.util.Collections; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ParallelizedConcurrentTransactionProcessorTest { + + @Mock private MainnetTransactionProcessor transactionProcessor; + @Mock private BlockHeader blockHeader; + @Mock private Transaction transaction; + @Mock private PrivateMetadataUpdater privateMetadataUpdater; + @Mock private TransactionCollisionDetector transactionCollisionDetector; + + private BonsaiWorldState worldState; + + private ParallelizedConcurrentTransactionProcessor processor; + + @BeforeEach + void setUp() { + processor = + new ParallelizedConcurrentTransactionProcessor( + transactionProcessor, transactionCollisionDetector); + final BonsaiWorldStateKeyValueStorage bonsaiWorldStateKeyValueStorage = + new BonsaiWorldStateKeyValueStorage( + new InMemoryKeyValueStorageProvider(), + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG); + worldState = + new BonsaiWorldState( + bonsaiWorldStateKeyValueStorage, + new NoopBonsaiCachedMerkleTrieLoader(), + new NoOpBonsaiCachedWorldStorageManager(bonsaiWorldStateKeyValueStorage), + new NoOpTrieLogManager(), + EvmConfiguration.DEFAULT, + new DiffBasedWorldStateConfig()); + when(transactionCollisionDetector.hasCollision(any(), any(), any(), any())).thenReturn(false); + } + + @Test + void testRunTransaction() { + Address miningBeneficiary = Address.fromHexString("0x1"); + Wei blobGasPrice = Wei.ZERO; + + Mockito.when( + transactionProcessor.processTransaction( + any(), any(), any(), any(), any(), any(), anyBoolean(), any(), any(), any())) + .thenReturn( + TransactionProcessingResult.successful( + Collections.emptyList(), 0, 0, Bytes.EMPTY, ValidationResult.valid())); + + processor.runTransaction( + worldState, + blockHeader, + 0, + transaction, + miningBeneficiary, + (blockNumber) -> Hash.EMPTY, + blobGasPrice, + privateMetadataUpdater); + + verify(transactionProcessor, times(1)) + .processTransaction( + any(DiffBasedWorldStateUpdateAccumulator.class), + eq(blockHeader), + eq(transaction), + eq(miningBeneficiary), + any(OperationTracer.class), + any(BlockHashOperation.BlockHashLookup.class), + eq(true), + eq(TransactionValidationParams.processingBlock()), + eq(privateMetadataUpdater), + eq(blobGasPrice)); + + assertTrue( + processor + .applyParallelizedTransactionResult( + worldState, miningBeneficiary, transaction, 0, Optional.empty(), Optional.empty()) + .isPresent(), + "Expected the transaction context to be stored"); + } + + @Test + void testRunTransactionWithFailure() { + Address miningBeneficiary = Address.fromHexString("0x1"); + Wei blobGasPrice = Wei.ZERO; + + when(transactionProcessor.processTransaction( + any(), any(), any(), any(), any(), any(), anyBoolean(), any(), any(), any())) + .thenReturn( + TransactionProcessingResult.failed( + 0, + 0, + ValidationResult.invalid( + TransactionInvalidReason.BLOB_GAS_PRICE_BELOW_CURRENT_BLOB_BASE_FEE), + Optional.of(Bytes.EMPTY))); + + processor.runTransaction( + worldState, + blockHeader, + 0, + transaction, + miningBeneficiary, + (blockNumber) -> Hash.EMPTY, + blobGasPrice, + privateMetadataUpdater); + + Optional result = + processor.applyParallelizedTransactionResult( + worldState, miningBeneficiary, transaction, 0, Optional.empty(), Optional.empty()); + assertTrue(result.isEmpty(), "Expected the transaction result to indicate a failure"); + } + + @Test + void testRunTransactionWithConflict() { + + Address miningBeneficiary = Address.fromHexString("0x1"); + Wei blobGasPrice = Wei.ZERO; + + Mockito.when( + transactionProcessor.processTransaction( + any(), any(), any(), any(), any(), any(), anyBoolean(), any(), any(), any())) + .thenReturn( + TransactionProcessingResult.successful( + Collections.emptyList(), 0, 0, Bytes.EMPTY, ValidationResult.valid())); + + processor.runTransaction( + worldState, + blockHeader, + 0, + transaction, + miningBeneficiary, + (blockNumber) -> Hash.EMPTY, + blobGasPrice, + privateMetadataUpdater); + + verify(transactionProcessor, times(1)) + .processTransaction( + any(DiffBasedWorldStateUpdateAccumulator.class), + eq(blockHeader), + eq(transaction), + eq(miningBeneficiary), + any(OperationTracer.class), + any(BlockHashOperation.BlockHashLookup.class), + eq(true), + eq(TransactionValidationParams.processingBlock()), + eq(privateMetadataUpdater), + eq(blobGasPrice)); + + // simulate a conflict + when(transactionCollisionDetector.hasCollision(any(), any(), any(), any())).thenReturn(true); + + Optional result = + processor.applyParallelizedTransactionResult( + worldState, miningBeneficiary, transaction, 0, Optional.empty(), Optional.empty()); + assertTrue(result.isEmpty(), "Expected no transaction result to be applied due to conflict"); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/parallelization/TransactionCollisionDetectorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/parallelization/TransactionCollisionDetectorTest.java new file mode 100644 index 00000000000..0cd1bad3c57 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/parallelization/TransactionCollisionDetectorTest.java @@ -0,0 +1,290 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.mainnet.parallelization; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.BonsaiAccount; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldState; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; +import org.hyperledger.besu.ethereum.trie.diffbased.common.DiffBasedValue; +import org.hyperledger.besu.evm.internal.EvmConfiguration; + +import java.math.BigInteger; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class TransactionCollisionDetectorTest { + + private TransactionCollisionDetector collisionDetector; + @Mock BonsaiWorldState worldState; + BonsaiWorldStateUpdateAccumulator bonsaiUpdater; + BonsaiWorldStateUpdateAccumulator trxUpdater; + + @BeforeEach + public void setUp() { + collisionDetector = new TransactionCollisionDetector(); + bonsaiUpdater = + new BonsaiWorldStateUpdateAccumulator( + worldState, (__, ___) -> {}, (__, ___) -> {}, EvmConfiguration.DEFAULT); + trxUpdater = + new BonsaiWorldStateUpdateAccumulator( + worldState, (__, ___) -> {}, (__, ___) -> {}, EvmConfiguration.DEFAULT); + } + + private Transaction createTransaction(final Address sender, final Address to) { + return new Transaction.Builder() + .nonce(1) + .gasPrice(Wei.of(1)) + .gasLimit(21000) + .to(to) + .value(Wei.ZERO) + .payload(Bytes.EMPTY) + .chainId(BigInteger.ONE) + .sender(sender) + .build(); + } + + private BonsaiAccount createAccount(final Address address) { + return new BonsaiAccount( + worldState, + address, + Hash.hash(Address.ZERO), + 0, + Wei.ONE, + Hash.EMPTY_TRIE_HASH, + Hash.EMPTY, + false); + } + + @Test + void testCollisionWithModifiedBalance() { + final Address address = Address.fromHexString("0x1"); + final BonsaiAccount priorAccountValue = createAccount(address); + final BonsaiAccount nextAccountValue = new BonsaiAccount(priorAccountValue, worldState, true); + nextAccountValue.setBalance(Wei.MAX_WEI); + + // Simulate that the address was already modified in the block + bonsaiUpdater + .getAccountsToUpdate() + .put(address, new DiffBasedValue<>(priorAccountValue, nextAccountValue)); + + final Transaction transaction = createTransaction(address, address); + + // Simulate that the address is read in the next transaction + trxUpdater + .getAccountsToUpdate() + .put(address, new DiffBasedValue<>(priorAccountValue, priorAccountValue)); + + boolean hasCollision = + collisionDetector.hasCollision( + transaction, + Address.ZERO, + new ParallelizedTransactionContext(trxUpdater, null, false, Wei.ZERO), + bonsaiUpdater); + + assertTrue(hasCollision, "Expected a collision with the modified address"); + } + + @Test + void testCollisionWithModifiedNonce() { + final Address address = Address.fromHexString("0x1"); + final BonsaiAccount priorAccountValue = createAccount(address); + final BonsaiAccount nextAccountValue = new BonsaiAccount(priorAccountValue, worldState, true); + nextAccountValue.setNonce(1); + + // Simulate that the address was already modified in the block + bonsaiUpdater + .getAccountsToUpdate() + .put(address, new DiffBasedValue<>(priorAccountValue, nextAccountValue)); + + final Transaction transaction = createTransaction(address, address); + + // Simulate that the address is read in the next transaction + trxUpdater + .getAccountsToUpdate() + .put(address, new DiffBasedValue<>(priorAccountValue, priorAccountValue)); + + boolean hasCollision = + collisionDetector.hasCollision( + transaction, + Address.ZERO, + new ParallelizedTransactionContext(trxUpdater, null, false, Wei.ZERO), + bonsaiUpdater); + + assertTrue(hasCollision, "Expected a collision with the modified address"); + } + + @Test + void testCollisionWithModifiedCode() { + final Address address = Address.fromHexString("0x1"); + final BonsaiAccount priorAccountValue = createAccount(address); + final BonsaiAccount nextAccountValue = new BonsaiAccount(priorAccountValue, worldState, true); + nextAccountValue.setCode(Bytes.repeat((byte) 0x01, 10)); + + // Simulate that the address was already modified in the block + bonsaiUpdater + .getAccountsToUpdate() + .put(address, new DiffBasedValue<>(priorAccountValue, nextAccountValue)); + + final Transaction transaction = createTransaction(address, address); + + // Simulate that the address is read in the next transaction + trxUpdater + .getAccountsToUpdate() + .put(address, new DiffBasedValue<>(priorAccountValue, priorAccountValue)); + + boolean hasCollision = + collisionDetector.hasCollision( + transaction, + Address.ZERO, + new ParallelizedTransactionContext(trxUpdater, null, false, Wei.ZERO), + bonsaiUpdater); + + assertTrue(hasCollision, "Expected a collision with the modified address"); + } + + @Test + void testCollisionWithModifiedStorageRoot() { + final Address address = Address.fromHexString("0x1"); + final BonsaiAccount priorAccountValue = createAccount(address); + final BonsaiAccount nextAccountValue = new BonsaiAccount(priorAccountValue, worldState, true); + nextAccountValue.setStorageRoot(Hash.EMPTY); + + // Simulate that the address was already modified in the block + bonsaiUpdater + .getAccountsToUpdate() + .put(address, new DiffBasedValue<>(priorAccountValue, nextAccountValue)); + + final Transaction transaction = createTransaction(address, address); + + // Simulate that the address is read in the next transaction + trxUpdater + .getAccountsToUpdate() + .put(address, new DiffBasedValue<>(priorAccountValue, priorAccountValue)); + + boolean hasCollision = + collisionDetector.hasCollision( + transaction, + Address.ZERO, + new ParallelizedTransactionContext(trxUpdater, null, false, Wei.ZERO), + bonsaiUpdater); + + assertTrue(hasCollision, "Expected a collision with the modified address"); + } + + @Test + void testCollisionWithMiningBeneficiaryAddress() { + final Address miningBeneficiary = Address.ZERO; + final Address address = Address.fromHexString("0x1"); + + final Transaction transaction = createTransaction(miningBeneficiary, address); + + boolean hasCollision = + collisionDetector.hasCollision( + transaction, + miningBeneficiary, + new ParallelizedTransactionContext(trxUpdater, null, false, Wei.ZERO), + bonsaiUpdater); + + assertTrue(hasCollision, "Expected collision with the mining beneficiary address as sender"); + } + + @Test + void testCollisionWithAnotherMiningBeneficiaryAddress() { + final Address miningBeneficiary = Address.ZERO; + final Address address = Address.fromHexString("0x1"); + final BonsaiAccount miningBeneficiaryValue = createAccount(address); + + final Transaction transaction = createTransaction(address, address); + + // Simulate that the mining beneficiary is read in the next transaction + trxUpdater + .getAccountsToUpdate() + .put( + miningBeneficiary, + new DiffBasedValue<>(miningBeneficiaryValue, miningBeneficiaryValue)); + + boolean hasCollision = + collisionDetector.hasCollision( + transaction, + miningBeneficiary, + new ParallelizedTransactionContext(trxUpdater, null, true, Wei.ZERO), + bonsaiUpdater); + + assertTrue(hasCollision, "Expected collision with the read mining beneficiary address"); + } + + @Test + void testCollisionWithDeletedAddress() { + final Address address = Address.fromHexString("0x1"); + final BonsaiAccount accountValue = createAccount(address); + + // Simulate that the address was deleted in the block + bonsaiUpdater.getDeletedAccountAddresses().add(address); + + final Transaction transaction = createTransaction(address, address); + + // Simulate that the deleted address is read in the next transaction + trxUpdater.getAccountsToUpdate().put(address, new DiffBasedValue<>(accountValue, accountValue)); + + boolean hasCollision = + collisionDetector.hasCollision( + transaction, + Address.ZERO, + new ParallelizedTransactionContext(trxUpdater, null, false, Wei.ZERO), + bonsaiUpdater); + + assertTrue(hasCollision, "Expected a collision with the deleted address"); + } + + @Test + void testCollisionWithNoModifiedAddress() { + final Address address = Address.fromHexString("0x1"); + final BonsaiAccount priorAccountValue = createAccount(address); + + // Simulate that the address was already read in the block + bonsaiUpdater + .getAccountsToUpdate() + .put(address, new DiffBasedValue<>(priorAccountValue, priorAccountValue)); + + final Transaction transaction = createTransaction(address, address); + + // Simulate that the address is read in the next transaction + trxUpdater + .getAccountsToUpdate() + .put(address, new DiffBasedValue<>(priorAccountValue, priorAccountValue)); + + boolean hasCollision = + collisionDetector.hasCollision( + transaction, + Address.ZERO, + new ParallelizedTransactionContext(trxUpdater, null, false, Wei.ZERO), + bonsaiUpdater); + + assertFalse(hasCollision, "Expected no collision with the read address"); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/AbstractPrecompiledContractTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/AbstractPrecompiledContractTest.java index eb4cc5cc670..f51a550eadb 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/AbstractPrecompiledContractTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/AbstractPrecompiledContractTest.java @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.mainnet.precompiles; import org.hyperledger.besu.datatypes.Address; diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/FlexiblePrivacyPrecompiledContractTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/FlexiblePrivacyPrecompiledContractTest.java index 83e1858f86c..fc08f519d3d 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/FlexiblePrivacyPrecompiledContractTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/FlexiblePrivacyPrecompiledContractTest.java @@ -20,6 +20,7 @@ import static org.hyperledger.besu.ethereum.core.PrivateTransactionDataFixture.versionedPrivateTransactionBesu; import static org.hyperledger.besu.ethereum.mainnet.PrivateStateUtils.KEY_IS_PERSISTING_PRIVATE_STATE; import static org.hyperledger.besu.ethereum.mainnet.PrivateStateUtils.KEY_PRIVATE_METADATA_UPDATER; +import static org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; @@ -49,7 +50,6 @@ import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; -import org.hyperledger.besu.ethereum.vm.BlockHashLookup; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.SpuriousDragonGasCalculator; @@ -383,7 +383,8 @@ public void testInvalidPrivateTransaction() { enclave, worldStateArchive, privateStateRootResolver, - privateStateGenesisAllocator); + privateStateGenesisAllocator, + false); contract.setPrivateTransactionProcessor( mockPrivateTxProcessor( @@ -427,6 +428,7 @@ private FlexiblePrivacyPrecompiledContract buildPrivacyPrecompiledContract( enclave, worldStateArchive, privateStateRootResolver, - privateStateGenesisAllocator); + privateStateGenesisAllocator, + false); } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPluginPrecompiledContractTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPluginPrecompiledContractTest.java index d4816d6b015..34a5c3af355 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPluginPrecompiledContractTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPluginPrecompiledContractTest.java @@ -21,6 +21,7 @@ import static org.hyperledger.besu.ethereum.mainnet.PrivateStateUtils.KEY_TRANSACTION; import static org.hyperledger.besu.ethereum.privacy.PrivateTransaction.readFrom; import static org.hyperledger.besu.ethereum.privacy.PrivateTransaction.serialize; +import static org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -42,7 +43,6 @@ import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; -import org.hyperledger.besu.ethereum.vm.BlockHashLookup; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.SpuriousDragonGasCalculator; import org.hyperledger.besu.evm.log.Log; diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java index 81d9d7e20cb..7d253057ac7 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/mainnet/precompiles/privacy/PrivacyPrecompiledContractTest.java @@ -21,6 +21,7 @@ import static org.hyperledger.besu.ethereum.core.PrivateTransactionDataFixture.privateTransactionBesu; import static org.hyperledger.besu.ethereum.mainnet.PrivateStateUtils.KEY_IS_PERSISTING_PRIVATE_STATE; import static org.hyperledger.besu.ethereum.mainnet.PrivateStateUtils.KEY_PRIVATE_METADATA_UPDATER; +import static org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; @@ -52,7 +53,6 @@ import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; -import org.hyperledger.besu.ethereum.vm.BlockHashLookup; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.SpuriousDragonGasCalculator; @@ -302,6 +302,7 @@ public void testInvalidPrivateTransaction() { worldStateArchive, privateStateRootResolver, privateStateGenesisAllocator, + false, "RestrictedPrivacyTest"); contract.setPrivateTransactionProcessor( @@ -328,7 +329,7 @@ public void testInvalidPrivateTransaction() { @Test public void testSimulatedPublicTransactionIsSkipped() { final PrivacyPrecompiledContract emptyContract = - new PrivacyPrecompiledContract(null, null, null, null, null, null); + new PrivacyPrecompiledContract(null, null, null, null, null, false, null); // A simulated public transaction doesn't contain a PrivateMetadataUpdater final MessageFrame frame = mock(MessageFrame.class); @@ -355,6 +356,7 @@ private PrivacyPrecompiledContract buildPrivacyPrecompiledContract(final Enclave worldStateArchive, privateStateRootResolver, privateStateGenesisAllocator, + false, "PrivacyTests"); } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/FlexibleUtilTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/FlexibleUtilTest.java index f4247c7c6d2..6d556f324c9 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/FlexibleUtilTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/FlexibleUtilTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/PrivateStateGenesisAllocatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/PrivateStateGenesisAllocatorTest.java index e53244ef6d8..5a037b3b1cf 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/PrivateStateGenesisAllocatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/PrivateStateGenesisAllocatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.privacy; import static org.assertj.core.api.Assertions.assertThat; diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionLocatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionLocatorTest.java index 31ca945549e..10985a2be84 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionLocatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/PrivateTransactionLocatorTest.java @@ -206,7 +206,7 @@ public void locateBesuPrivateTransactionNotFoundInAddBlob() { } /* - Overrride enclave so it returns 404 when searching for a single tx (first call) and when + Override enclave so it returns 404 when searching for a single tx (first call) and when searching for the add blob (second call) */ private void mockEnlaveNoSingleTxOrAddBlob() { diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/PrivateWorldStateReaderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/PrivateWorldStateReaderTest.java index acc5313cd84..3f7bc81d3c1 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/PrivateWorldStateReaderTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/PrivateWorldStateReaderTest.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.privacy; import static org.assertj.core.api.Assertions.assertThat; diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/storage/migration/PrivateStorageMigrationTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/storage/migration/PrivateStorageMigrationTest.java index 5279a06de1d..a4f7b3e400c 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/storage/migration/PrivateStorageMigrationTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/privacy/storage/migration/PrivateStorageMigrationTest.java @@ -44,6 +44,7 @@ import org.hyperledger.besu.ethereum.mainnet.MiningBeneficiaryCalculator; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.blockhash.BlockHashProcessor; import org.hyperledger.besu.ethereum.privacy.PrivateStateRootResolver; import org.hyperledger.besu.ethereum.privacy.storage.LegacyPrivateStateStorage; import org.hyperledger.besu.ethereum.privacy.storage.PrivacyGroupHeadBlockMap; @@ -93,6 +94,7 @@ public class PrivateStorageMigrationTest { @Mock private TransactionReceiptFactory transactionReceiptFactory; @Mock private MiningBeneficiaryCalculator miningBeneficiaryCalculator; @Mock private PrivateMigrationBlockProcessor privateMigrationBlockProcessor; + @Mock private BlockHashProcessor blockHashProcessor; private PrivateStateKeyValueStorage privateStateStorage; private PrivateStateRootResolver privateStateRootResolver; @@ -115,6 +117,7 @@ public void setUp() { .when(protocolSpec.getMiningBeneficiaryCalculator()) .thenReturn(miningBeneficiaryCalculator); lenient().when(protocolSpec.isSkipZeroBlockRewards()).thenReturn(false); + lenient().when(protocolSpec.getBlockHashProcessor()).thenReturn(blockHashProcessor); migration = new PrivateStorageMigration( @@ -199,6 +202,7 @@ public void migrationReprocessBlocksWithPMT() { verify(privateMigrationBlockProcessor) .processBlock( + any(), any(), any(), eq(blockWithPMT.getHeader()), @@ -223,7 +227,7 @@ public void migrationOnlyProcessRequiredTransactions() { final ArgumentCaptor txsCaptor = ArgumentCaptor.forClass(List.class); verify(privateMigrationBlockProcessor) - .processBlock(any(), any(), any(), txsCaptor.capture(), any()); + .processBlock(any(), any(), any(), any(), txsCaptor.capture(), any()); // won't process transaction after PMT, that's why we only process 2 txs final List processedTxs = txsCaptor.getValue(); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/proof/WorldStateProofProviderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/proof/WorldStateProofProviderTest.java index b6357954f53..95b39197ea0 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/proof/WorldStateProofProviderTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/proof/WorldStateProofProviderTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -20,11 +20,11 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.rlp.RLP; -import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.trie.MerkleTrie; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import java.util.ArrayList; @@ -46,15 +46,15 @@ public class WorldStateProofProviderTest { private static final Address address = Address.fromHexString("0x1234567890123456789012345678901234567890"); - - private final WorldStateStorage worldStateStorage = - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + private final ForestWorldStateKeyValueStorage worldStateKeyValueStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); private WorldStateProofProvider worldStateProofProvider; @BeforeEach public void setup() { - worldStateProofProvider = new WorldStateProofProvider(worldStateStorage); + worldStateProofProvider = + new WorldStateProofProvider(new WorldStateStorageCoordinator(worldStateKeyValueStorage)); } @Test @@ -68,19 +68,17 @@ public void getProofWhenWorldStateNotAvailable() { @Test public void getProofWhenWorldStateAvailable() { final Hash addressHash = address.addressHash(); - final MerkleTrie worldStateTrie = emptyWorldStateTrie(addressHash); + final MerkleTrie worldStateTrie = emptyWorldStateTrie(); final MerkleTrie storageTrie = emptyStorageTrie(); - final WorldStateStorage.Updater updater = worldStateStorage.updater(); + final ForestWorldStateKeyValueStorage.Updater updater = worldStateKeyValueStorage.updater(); // Add some storage values writeStorageValue(storageTrie, UInt256.ONE, UInt256.valueOf(2L)); writeStorageValue(storageTrie, UInt256.valueOf(2L), UInt256.valueOf(4L)); writeStorageValue(storageTrie, UInt256.valueOf(3L), UInt256.valueOf(6L)); // Save to Storage - storageTrie.commit( - (location, hash, value) -> - updater.putAccountStorageTrieNode(addressHash, location, hash, value)); + storageTrie.commit((location, hash, value) -> updater.putAccountStorageTrieNode(hash, value)); // Define account value final Hash codeHash = Hash.hash(Bytes.fromHexString("0x1122")); @@ -88,7 +86,7 @@ public void getProofWhenWorldStateAvailable() { new StateTrieAccountValue(1L, Wei.of(2L), Hash.wrap(storageTrie.getRootHash()), codeHash); // Save to storage worldStateTrie.put(addressHash, RLP.encode(accountValue::writeTo)); - worldStateTrie.commit(updater::putAccountStateTrieNode); + worldStateTrie.commit((location, hash, value) -> updater.putAccountStateTrieNode(hash, value)); // Persist updates updater.commit(); @@ -121,7 +119,7 @@ public void getProofWhenWorldStateAvailable() { @Test public void getProofWhenStateTrieAccountUnavailable() { - final MerkleTrie worldStateTrie = emptyWorldStateTrie(null); + final MerkleTrie worldStateTrie = emptyWorldStateTrie(); final Optional accountProof = worldStateProofProvider.getAccountProof( @@ -145,13 +143,14 @@ private Bytes encodeStorageValue(final UInt256 storageValue) { private MerkleTrie emptyStorageTrie() { return new StoredMerklePatriciaTrie<>( - worldStateStorage::getAccountStateTrieNode, b -> b, b -> b); + (location, hash) -> worldStateKeyValueStorage.getAccountStateTrieNode(hash), + b -> b, + b -> b); } - private MerkleTrie emptyWorldStateTrie(final Hash accountHash) { + private MerkleTrie emptyWorldStateTrie() { return new StoredMerklePatriciaTrie<>( - (location, hash) -> - worldStateStorage.getAccountStorageTrieNode(accountHash, location, hash), + (location, hash) -> worldStateKeyValueStorage.getAccountStorageTrieNode(hash), b -> b, b -> b); } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/proof/WorldStateRangeProofProviderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/proof/WorldStateRangeProofProviderTest.java index bd4d95321a7..535a91ba939 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/proof/WorldStateRangeProofProviderTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/proof/WorldStateRangeProofProviderTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -18,11 +18,11 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.core.TrieGenerator; -import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.trie.MerkleTrie; import org.hyperledger.besu.ethereum.trie.RangeStorageEntriesCollector; import org.hyperledger.besu.ethereum.trie.TrieIterator; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import java.util.ArrayList; @@ -40,21 +40,22 @@ public class WorldStateRangeProofProviderTest { private static final Hash MAX_RANGE = Hash.fromHexString("0x0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + private static final ForestWorldStateKeyValueStorage worldStateKeyValueStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); - private static final WorldStateStorage worldStateStorage = - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); - + private WorldStateStorageCoordinator worldStateStorageCoordinator; private static WorldStateProofProvider worldStateProofProvider; @BeforeEach public void setup() { - worldStateProofProvider = new WorldStateProofProvider(worldStateStorage); + worldStateStorageCoordinator = new WorldStateStorageCoordinator(worldStateKeyValueStorage); + worldStateProofProvider = new WorldStateProofProvider(worldStateStorageCoordinator); } @Test public void rangeProofValidationNominalCase() { final MerkleTrie accountStateTrie = - TrieGenerator.generateTrie(worldStateStorage, 15); + TrieGenerator.generateTrie(worldStateStorageCoordinator, 15); // collect accounts in range final RangeStorageEntriesCollector collector = RangeStorageEntriesCollector.createCollector(Hash.ZERO, MAX_RANGE, 10, Integer.MAX_VALUE); @@ -82,7 +83,8 @@ public void rangeProofValidationNominalCase() { @Test public void rangeProofValidationMissingAccount() { - MerkleTrie accountStateTrie = TrieGenerator.generateTrie(worldStateStorage, 15); + MerkleTrie accountStateTrie = + TrieGenerator.generateTrie(worldStateStorageCoordinator, 15); // collect accounts in range final RangeStorageEntriesCollector collector = RangeStorageEntriesCollector.createCollector(Hash.ZERO, MAX_RANGE, 10, Integer.MAX_VALUE); @@ -119,7 +121,8 @@ public void rangeProofValidationMissingAccount() { @Test public void rangeProofValidationNoMonotonicIncreasing() { - MerkleTrie accountStateTrie = TrieGenerator.generateTrie(worldStateStorage, 15); + MerkleTrie accountStateTrie = + TrieGenerator.generateTrie(worldStateStorageCoordinator, 15); // generate the invalid proof final RangeStorageEntriesCollector collector = @@ -155,7 +158,8 @@ public void rangeProofValidationNoMonotonicIncreasing() { @Test public void rangeProofValidationEmptyProof() { - MerkleTrie accountStateTrie = TrieGenerator.generateTrie(worldStateStorage, 15); + MerkleTrie accountStateTrie = + TrieGenerator.generateTrie(worldStateStorageCoordinator, 15); // generate the invalid proof final RangeStorageEntriesCollector collector = @@ -182,7 +186,8 @@ public void rangeProofValidationEmptyProof() { @Test public void rangeProofValidationInvalidEmptyProof() { - MerkleTrie accountStateTrie = TrieGenerator.generateTrie(worldStateStorage, 15); + MerkleTrie accountStateTrie = + TrieGenerator.generateTrie(worldStateStorageCoordinator, 15); // generate the invalid proof final RangeStorageEntriesCollector collector = diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStoragePrefixedKeyBlockchainStorageTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStoragePrefixedKeyBlockchainStorageTest.java index 1e89581a095..0f0911b4fa0 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStoragePrefixedKeyBlockchainStorageTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStoragePrefixedKeyBlockchainStorageTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -62,7 +62,7 @@ public void migrationToVariablesStorage() { final var blockchainStorage = new KeyValueStoragePrefixedKeyBlockchainStorage( - kvBlockchain, variablesStorage, blockHeaderFunctions); + kvBlockchain, variablesStorage, blockHeaderFunctions, false); assertNoVariablesInStorage(kvBlockchain); assertVariablesPresentInVariablesStorage(kvVariables, variableValues); @@ -80,7 +80,7 @@ public void migrationToVariablesStorageWhenSomeVariablesDoNotExist() { final var blockchainStorage = new KeyValueStoragePrefixedKeyBlockchainStorage( - kvBlockchain, variablesStorage, blockHeaderFunctions); + kvBlockchain, variablesStorage, blockHeaderFunctions, false); assertNoVariablesInStorage(kvBlockchain); assertVariablesPresentInVariablesStorage(kvVariables, variableValues); @@ -96,7 +96,7 @@ public void doesNothingIfVariablesAlreadyMigrated() { final var blockchainStorage = new KeyValueStoragePrefixedKeyBlockchainStorage( - kvBlockchain, variablesStorage, blockHeaderFunctions); + kvBlockchain, variablesStorage, blockHeaderFunctions, false); assertNoVariablesInStorage(kvBlockchain); assertVariablesPresentInVariablesStorage(kvVariables, variableValues); @@ -114,6 +114,6 @@ public void failIfInconsistencyDetectedDuringVariablesMigration() { IllegalStateException.class, () -> new KeyValueStoragePrefixedKeyBlockchainStorage( - kvBlockchain, variablesStorage, blockHeaderFunctions)); + kvBlockchain, variablesStorage, blockHeaderFunctions, false)); } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStorageWorldStateStorageTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStorageWorldStateStorageTest.java deleted file mode 100644 index 758866fd6ff..00000000000 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/storage/keyvalue/KeyValueStorageWorldStateStorageTest.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.storage.keyvalue; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStateKeyValueStorage.Updater; -import org.hyperledger.besu.ethereum.trie.MerkleTrie; -import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.units.bigints.UInt256; -import org.junit.jupiter.api.Test; - -public class KeyValueStorageWorldStateStorageTest { - - @Test - public void getCode_returnsEmpty() { - final WorldStateKeyValueStorage storage = emptyStorage(); - assertThat(storage.getCode(Hash.EMPTY, null)).contains(Bytes.EMPTY); - } - - @Test - public void getAccountStateTrieNode_returnsEmptyNode() { - final WorldStateKeyValueStorage storage = emptyStorage(); - assertThat(storage.getAccountStateTrieNode(Bytes.EMPTY, MerkleTrie.EMPTY_TRIE_NODE_HASH)) - .contains(MerkleTrie.EMPTY_TRIE_NODE); - } - - @Test - public void getAccountStorageTrieNode_returnsEmptyNode() { - final WorldStateKeyValueStorage storage = emptyStorage(); - assertThat( - storage.getAccountStorageTrieNode(null, Bytes.EMPTY, MerkleTrie.EMPTY_TRIE_NODE_HASH)) - .contains(MerkleTrie.EMPTY_TRIE_NODE); - } - - @Test - public void getNodeData_returnsEmptyValue() { - final WorldStateKeyValueStorage storage = emptyStorage(); - assertThat(storage.getNodeData(null, Hash.EMPTY)).contains(Bytes.EMPTY); - } - - @Test - public void getNodeData_returnsEmptyNode() { - final WorldStateKeyValueStorage storage = emptyStorage(); - assertThat(storage.getNodeData(Bytes.EMPTY, MerkleTrie.EMPTY_TRIE_NODE_HASH)) - .contains(MerkleTrie.EMPTY_TRIE_NODE); - } - - @Test - public void getCode_saveAndGetSpecialValues() { - final WorldStateKeyValueStorage storage = emptyStorage(); - storage.updater().putCode(null, MerkleTrie.EMPTY_TRIE_NODE).putCode(null, Bytes.EMPTY).commit(); - - assertThat(storage.getCode(MerkleTrie.EMPTY_TRIE_NODE_HASH, null)) - .contains(MerkleTrie.EMPTY_TRIE_NODE); - assertThat(storage.getCode(Hash.EMPTY, null)).contains(Bytes.EMPTY); - } - - @Test - public void getCode_saveAndGetRegularValue() { - final Bytes bytes = Bytes.fromHexString("0x123456"); - final WorldStateKeyValueStorage storage = emptyStorage(); - storage.updater().putCode(null, bytes).commit(); - - assertThat(storage.getCode(Hash.hash(bytes), null)).contains(bytes); - } - - @Test - public void getAccountStateTrieNode_saveAndGetSpecialValues() { - final WorldStateKeyValueStorage storage = emptyStorage(); - storage - .updater() - .putAccountStateTrieNode( - null, Hash.hash(MerkleTrie.EMPTY_TRIE_NODE), MerkleTrie.EMPTY_TRIE_NODE) - .putAccountStateTrieNode(null, Hash.hash(Bytes.EMPTY), Bytes.EMPTY) - .commit(); - - assertThat(storage.getAccountStateTrieNode(Bytes.EMPTY, MerkleTrie.EMPTY_TRIE_NODE_HASH)) - .contains(MerkleTrie.EMPTY_TRIE_NODE); - assertThat(storage.getAccountStateTrieNode(Bytes.EMPTY, Hash.EMPTY)).contains(Bytes.EMPTY); - } - - @Test - public void getAccountStateTrieNode_saveAndGetRegularValue() { - final Bytes bytes = Bytes.fromHexString("0x123456"); - final WorldStateKeyValueStorage storage = emptyStorage(); - storage.updater().putAccountStateTrieNode(null, Hash.hash(bytes), bytes).commit(); - - assertThat(storage.getAccountStateTrieNode(Bytes.EMPTY, Hash.hash(bytes))).contains(bytes); - } - - @Test - public void getAccountStorageTrieNode_saveAndGetSpecialValues() { - final WorldStateKeyValueStorage storage = emptyStorage(); - storage - .updater() - .putAccountStorageTrieNode( - null, null, Hash.hash(MerkleTrie.EMPTY_TRIE_NODE), MerkleTrie.EMPTY_TRIE_NODE) - .putAccountStorageTrieNode(null, null, Hash.hash(Bytes.EMPTY), Bytes.EMPTY) - .commit(); - - assertThat( - storage.getAccountStorageTrieNode(null, Bytes.EMPTY, MerkleTrie.EMPTY_TRIE_NODE_HASH)) - .contains(MerkleTrie.EMPTY_TRIE_NODE); - assertThat(storage.getAccountStorageTrieNode(null, Bytes.EMPTY, Hash.EMPTY)) - .contains(Bytes.EMPTY); - } - - @Test - public void getAccountStorageTrieNode_saveAndGetRegularValue() { - final Bytes bytes = Bytes.fromHexString("0x123456"); - final WorldStateKeyValueStorage storage = emptyStorage(); - storage.updater().putAccountStorageTrieNode(null, null, Hash.hash(bytes), bytes).commit(); - - assertThat(storage.getAccountStateTrieNode(Bytes.EMPTY, Hash.hash(bytes))).contains(bytes); - } - - @Test - public void getNodeData_saveAndGetSpecialValues() { - final WorldStateKeyValueStorage storage = emptyStorage(); - storage - .updater() - .putAccountStorageTrieNode( - null, null, Hash.hash(MerkleTrie.EMPTY_TRIE_NODE), MerkleTrie.EMPTY_TRIE_NODE) - .putAccountStorageTrieNode(null, null, Hash.hash(Bytes.EMPTY), Bytes.EMPTY) - .commit(); - - assertThat(storage.getNodeData(Bytes.EMPTY, MerkleTrie.EMPTY_TRIE_NODE_HASH)) - .contains(MerkleTrie.EMPTY_TRIE_NODE); - assertThat(storage.getNodeData(Bytes.EMPTY, Hash.EMPTY)).contains(Bytes.EMPTY); - } - - @Test - public void getNodeData_saveAndGetRegularValue() { - final Bytes bytes = Bytes.fromHexString("0x123456"); - final WorldStateKeyValueStorage storage = emptyStorage(); - storage.updater().putAccountStorageTrieNode(null, null, Hash.hash(bytes), bytes).commit(); - - assertThat(storage.getNodeData(null, Hash.hash(bytes))).contains(bytes); - } - - @Test - public void reconcilesNonConflictingUpdaters() { - final Bytes bytesA = Bytes.fromHexString("0x12"); - final Bytes bytesB = Bytes.fromHexString("0x1234"); - final Bytes bytesC = Bytes.fromHexString("0x123456"); - - final WorldStateKeyValueStorage storage = emptyStorage(); - final Updater updaterA = storage.updater(); - final Updater updaterB = storage.updater(); - - updaterA.putCode(null, bytesA); - updaterB.putCode(null, bytesA); - updaterB.putCode(null, bytesB); - updaterA.putCode(null, bytesC); - - updaterA.commit(); - updaterB.commit(); - - assertThat(storage.getCode(Hash.hash(bytesA), null)).contains(bytesA); - assertThat(storage.getCode(Hash.hash(bytesB), null)).contains(bytesB); - assertThat(storage.getCode(Hash.hash(bytesC), null)).contains(bytesC); - } - - @Test - public void isWorldStateAvailable_defaultIsFalse() { - assertThat(emptyStorage().isWorldStateAvailable(UInt256.valueOf(1), null)).isFalse(); - } - - @Test - public void isWorldStateAvailable_emptyTrieStateAlwaysAvailable() { - assertThat(emptyStorage().isWorldStateAvailable(Hash.EMPTY_TRIE_HASH, null)).isTrue(); - } - - private WorldStateKeyValueStorage emptyStorage() { - return new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); - } -} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorTest.java index 1fde6b27e2f..0aac2f026f7 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/transaction/TransactionSimulatorTest.java @@ -26,10 +26,12 @@ import org.hyperledger.besu.crypto.SignatureAlgorithm; import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BlobsWithCommitments; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.BlobTestFixture; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; @@ -41,6 +43,7 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; +import org.hyperledger.besu.ethereum.mainnet.blockhash.BlockHashProcessor; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult.Status; @@ -563,8 +566,9 @@ public void shouldCapGasLimitWhenOriginalTransactionExceedsGasCap() { } @Test - public void shouldKeepOriginalGasLimitWhenCapIsHigherThanOriginalValue() { - // generate a transaction with a gas limit that is lower than the gas cap + public void shouldUseRpcGasCapWhenCapIsHigherThanGasLimit() { + // generate a transaction with a gas limit that is lower than the gas cap, + // expect the gas cap to override parameter gas limit final CallParameter callParameter = eip1559TransactionCallParameter(Wei.ZERO, Wei.ZERO, GASCAP - 1); @@ -587,6 +591,7 @@ public void shouldKeepOriginalGasLimitWhenCapIsHigherThanOriginalValue() { .value(callParameter.getValue()) .payload(callParameter.getPayload()) .signature(FAKE_SIGNATURE) + .gasLimit(GASCAP) .build(); // call process with original transaction @@ -600,6 +605,86 @@ public void shouldKeepOriginalGasLimitWhenCapIsHigherThanOriginalValue() { verifyTransactionWasProcessed(expectedTransaction); } + @Test + public void shouldReturnSuccessfulResultWhenBlobTransactionProcessingIsSuccessful() { + final CallParameter callParameter = + blobTransactionCallParameter(Wei.ONE, Wei.ONE, Wei.ONE, 300, 3); + + final BlockHeader blockHeader = mockBlockHeader(Hash.ZERO, 1L, Wei.ONE); + + mockBlockchainForBlockHeader(blockHeader); + mockWorldStateForAccount(blockHeader, callParameter.getFrom(), 1L); + + final Transaction expectedTransaction = + Transaction.builder() + .type(TransactionType.BLOB) + .chainId(BigInteger.ONE) + .nonce(1L) + .gasLimit(callParameter.getGasLimit()) + .maxFeePerGas(callParameter.getMaxFeePerGas().orElseThrow()) + .maxPriorityFeePerGas(callParameter.getMaxPriorityFeePerGas().orElseThrow()) + .to(callParameter.getTo()) + .sender(callParameter.getFrom()) + .value(callParameter.getValue()) + .payload(callParameter.getPayload()) + .maxFeePerBlobGas(callParameter.getMaxFeePerBlobGas().get()) + .versionedHashes(callParameter.getBlobVersionedHashes().get()) + .signature(FAKE_SIGNATURE) + .build(); + + final CallParameter reverseEngineeredCallParam = + CallParameter.fromTransaction(expectedTransaction); + assertThat(reverseEngineeredCallParam).isEqualTo(callParameter); + + mockProcessorStatusForTransaction(expectedTransaction, Status.SUCCESSFUL); + + final Optional result = + transactionSimulator.process(callParameter, 1L); + + assertThat(result.get().isSuccessful()).isTrue(); + verifyTransactionWasProcessed(expectedTransaction); + } + + @Test + public void shouldReturnFailureResultWhenBlobTransactionProcessingFails() { + final CallParameter callParameter = + blobTransactionCallParameter(Wei.ONE, Wei.ONE, Wei.ONE, 300, 3); + + final BlockHeader blockHeader = mockBlockHeader(Hash.ZERO, 1L, Wei.ONE); + + mockBlockchainForBlockHeader(blockHeader); + mockWorldStateForAccount(blockHeader, callParameter.getFrom(), 1L); + + final Transaction expectedTransaction = + Transaction.builder() + .type(TransactionType.BLOB) + .chainId(BigInteger.ONE) + .nonce(1L) + .gasLimit(callParameter.getGasLimit()) + .maxFeePerGas(callParameter.getMaxFeePerGas().orElseThrow()) + .maxPriorityFeePerGas(callParameter.getMaxPriorityFeePerGas().orElseThrow()) + .to(callParameter.getTo()) + .sender(callParameter.getFrom()) + .value(callParameter.getValue()) + .payload(callParameter.getPayload()) + .maxFeePerBlobGas(callParameter.getMaxFeePerBlobGas().get()) + .versionedHashes(callParameter.getBlobVersionedHashes().get()) + .signature(FAKE_SIGNATURE) + .build(); + + final CallParameter reverseEngineeredCallParam = + CallParameter.fromTransaction(expectedTransaction); + assertThat(reverseEngineeredCallParam).isEqualTo(callParameter); + + mockProcessorStatusForTransaction(expectedTransaction, Status.FAILED); + + final Optional result = + transactionSimulator.process(callParameter, 1L); + + assertThat(result.get().isSuccessful()).isFalse(); + verifyTransactionWasProcessed(expectedTransaction); + } + private void mockWorldStateForAccount( final BlockHeader blockHeader, final Address address, final long nonce) { final Account account = mock(Account.class); @@ -637,12 +722,14 @@ private void mockBlockchainForBlockHeader(final BlockHeader blockHeader) { private void mockProtocolSpecForProcessWithWorldUpdater() { final BlockHeaderFunctions blockHeaderFunctions = mock(BlockHeaderFunctions.class); + final BlockHashProcessor blockHashProcessor = mock(BlockHashProcessor.class); when(protocolSchedule.getChainId()).thenReturn(Optional.of(BigInteger.ONE)); when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); when(protocolSpec.getTransactionProcessor()).thenReturn(transactionProcessor); when(protocolSpec.getMiningBeneficiaryCalculator()).thenReturn(BlockHeader::getCoinbase); when(protocolSpec.getBlockHeaderFunctions()).thenReturn(blockHeaderFunctions); when(protocolSpec.getFeeMarket()).thenReturn(FeeMarket.london(0)); + when(protocolSpec.getBlockHashProcessor()).thenReturn(blockHashProcessor); } private void mockProcessorStatusForTransaction( @@ -660,7 +747,6 @@ private void mockProcessorStatusForTransaction( } when(transactionProcessor.processTransaction( - any(), any(), any(), eq(transaction), @@ -676,7 +762,6 @@ private void mockProcessorStatusForTransaction( private void verifyTransactionWasProcessed(final Transaction expectedTransaction) { verify(transactionProcessor) .processTransaction( - any(), any(), any(), eq(expectedTransaction), @@ -724,4 +809,25 @@ private CallParameter eip1559TransactionCallParameter( Bytes.EMPTY, Optional.empty()); } + + private CallParameter blobTransactionCallParameter( + final Wei maxFeePerBlobGas, + final Wei maxFeePerGas, + final Wei maxPriorityFeePerGas, + final long gasLimit, + final int numberOfBlobs) { + BlobsWithCommitments bwc = new BlobTestFixture().createBlobsWithCommitments(numberOfBlobs); + return new CallParameter( + Address.fromHexString("0x0"), + Address.fromHexString("0x0"), + gasLimit, + Wei.of(0), + Optional.of(maxFeePerGas), + Optional.of(maxPriorityFeePerGas), + Wei.of(0), + Bytes.EMPTY, + Optional.empty(), + Optional.of(maxFeePerBlobGas), + Optional.of(bwc.getVersionedHashes())); + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/AbstractIsolationTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/AbstractIsolationTests.java similarity index 75% rename from ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/AbstractIsolationTests.java rename to ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/AbstractIsolationTests.java index 4f40afdb437..dd9257441e8 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/AbstractIsolationTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/AbstractIsolationTests.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,9 +11,8 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ -package org.hyperledger.besu.ethereum.bonsai; +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai; import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryBlockchain; import static org.mockito.ArgumentMatchers.any; @@ -21,7 +20,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import org.hyperledger.besu.config.GenesisAllocation; +import org.hyperledger.besu.config.GenesisAccount; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.crypto.KeyPair; import org.hyperledger.besu.crypto.SECPPrivateKey; @@ -32,9 +31,7 @@ import org.hyperledger.besu.ethereum.BlockProcessingResult; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.blockcreation.AbstractBlockCreator; -import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogPruner; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.GenesisState; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; @@ -68,10 +65,14 @@ import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStorageProviderBuilder; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.BonsaiCachedMerkleTrieLoader; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.BesuConfiguration; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBKeyValueStorageFactory; import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBMetricsFactory; import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBFactoryConfiguration; @@ -84,7 +85,6 @@ import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Function; -import java.util.stream.Collectors; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; @@ -93,25 +93,31 @@ public abstract class AbstractIsolationTests { protected BonsaiWorldStateProvider archive; - protected BonsaiWorldStateKeyValueStorage bonsaiWorldStateStorage; + protected WorldStateKeyValueStorage worldStateKeyValueStorage; protected ProtocolContext protocolContext; protected EthContext ethContext; protected EthScheduler ethScheduler = new DeterministicEthScheduler(); - final Function asKeyPair = + final Function asKeyPair = key -> SignatureAlgorithmFactory.getInstance() - .createKeyPair(SECPPrivateKey.create(Bytes32.fromHexString(key), "ECDSA")); + .createKeyPair(SECPPrivateKey.create(key, "ECDSA")); protected final ProtocolSchedule protocolSchedule = - MainnetProtocolSchedule.fromConfig(GenesisConfigFile.development().getConfigOptions()); + MainnetProtocolSchedule.fromConfig( + GenesisConfigFile.fromResource("/dev.json").getConfigOptions(), + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); protected final GenesisState genesisState = - GenesisState.fromConfig(GenesisConfigFile.development(), protocolSchedule); + GenesisState.fromConfig(GenesisConfigFile.fromResource("/dev.json"), protocolSchedule); protected final MutableBlockchain blockchain = createInMemoryBlockchain(genesisState.getBlock()); protected final TransactionPoolConfiguration poolConfiguration = ImmutableTransactionPoolConfiguration.builder().txPoolMaxSize(100).build(); protected final TransactionPoolReplacementHandler transactionReplacementHandler = - new TransactionPoolReplacementHandler(poolConfiguration.getPriceBump()); + new TransactionPoolReplacementHandler( + poolConfiguration.getPriceBump(), poolConfiguration.getBlobPriceBump()); protected final BiFunction transactionReplacementTester = @@ -127,40 +133,44 @@ public abstract class AbstractIsolationTests { poolConfiguration, new GasPricePrioritizedTransactions( poolConfiguration, + ethScheduler, new EndLayer(txPoolMetrics), txPoolMetrics, transactionReplacementTester, - new BlobCache())); + new BlobCache(), + MiningParameters.newDefault()), + ethScheduler); - protected final List accounts = - GenesisConfigFile.development() + protected final List accounts = + GenesisConfigFile.fromResource("/dev.json") .streamAllocations() - .filter(ga -> ga.getPrivateKey().isPresent()) - .collect(Collectors.toList()); + .filter(ga -> ga.privateKey() != null) + .toList(); - KeyPair sender1 = asKeyPair.apply(accounts.get(0).getPrivateKey().get()); + KeyPair sender1 = Optional.ofNullable(accounts.get(0).privateKey()).map(asKeyPair).orElseThrow(); TransactionPool transactionPool; @TempDir private Path tempData; @BeforeEach public void createStorage() { - bonsaiWorldStateStorage = - (BonsaiWorldStateKeyValueStorage) - createKeyValueStorageProvider().createWorldStateStorage(DataStorageFormat.BONSAI); + worldStateKeyValueStorage = + // FYI: BonsaiSnapshoIsolationTests work with frozen/cached worldstates, using PARTIAL + // flat db strategy allows the tests to make account assertions based on trie + // (whereas a full db strategy will not, since the worldstates are frozen/cached) + createKeyValueStorageProvider() + .createWorldStateStorage(DataStorageConfiguration.DEFAULT_BONSAI_PARTIAL_DB_CONFIG); archive = new BonsaiWorldStateProvider( - bonsaiWorldStateStorage, + (BonsaiWorldStateKeyValueStorage) worldStateKeyValueStorage, blockchain, Optional.of(16L), - new CachedMerkleTrieLoader(new NoOpMetricsSystem()), - new NoOpMetricsSystem(), + new BonsaiCachedMerkleTrieLoader(new NoOpMetricsSystem()), null, - EvmConfiguration.DEFAULT, - TrieLogPruner.noOpTrieLogPruner()); + EvmConfiguration.DEFAULT); var ws = archive.getMutable(); genesisState.writeStateTo(ws); - protocolContext = new ProtocolContext(blockchain, archive, null, Optional.empty()); + protocolContext = new ProtocolContext(blockchain, archive, null, new BadBlockManager()); ethContext = mock(EthContext.class, RETURNS_DEEP_STUBS); when(ethContext.getEthPeers().subscribeConnect(any())).thenReturn(1L); transactionPool = @@ -172,7 +182,7 @@ public void createStorage() { ethContext, txPoolMetrics, poolConfiguration, - null); + new BlobCache()); transactionPool.setEnabled(); } @@ -188,11 +198,20 @@ protected StorageProvider createKeyValueStorageProvider() { 8388608 /*CACHE_CAPACITY*/, false), Arrays.asList(KeyValueSegmentIdentifier.values()), - 2, RocksDBMetricsFactory.PUBLIC_ROCKS_DB_METRICS)) .withCommonConfiguration( new BesuConfiguration() { + @Override + public Optional getRpcHttpHost() { + return Optional.empty(); + } + + @Override + public Optional getRpcHttpPort() { + return Optional.empty(); + } + @Override public Path getStoragePath() { return tempData.resolve("database"); @@ -204,8 +223,29 @@ public Path getDataPath() { } @Override - public int getDatabaseVersion() { - return 2; + public DataStorageFormat getDatabaseFormat() { + return DataStorageFormat.BONSAI; + } + + @Override + public Wei getMinGasPrice() { + return MiningParameters.newDefault().getMinTransactionGasPrice(); + } + + @Override + public org.hyperledger.besu.plugin.services.storage.DataStorageConfiguration + getDataStorageConfiguration() { + return new org.hyperledger.besu.plugin.services.storage.DataStorageConfiguration() { + @Override + public DataStorageFormat getDatabaseFormat() { + return DataStorageFormat.BONSAI; + } + + @Override + public boolean getReceiptCompactionEnabled() { + return false; + } + }; } }) .withMetricsSystem(new NoOpMetricsSystem()) @@ -220,7 +260,6 @@ private TestBlockCreator( final TransactionPool transactionPool, final ProtocolContext protocolContext, final ProtocolSchedule protocolSchedule, - final BlockHeader parentHeader, final EthScheduler ethScheduler) { super( miningParameters, @@ -229,13 +268,10 @@ private TestBlockCreator( transactionPool, protocolContext, protocolSchedule, - parentHeader, - Optional.empty(), ethScheduler); } static TestBlockCreator forHeader( - final BlockHeader parentHeader, final ProtocolContext protocolContext, final ProtocolSchedule protocolSchedule, final TransactionPool transactionPool, @@ -260,7 +296,6 @@ static TestBlockCreator forHeader( transactionPool, protocolContext, protocolSchedule, - parentHeader, ethScheduler); } @@ -293,8 +328,8 @@ protected Block forTransactions(final List transactions) { protected Block forTransactions( final List transactions, final BlockHeader forHeader) { return TestBlockCreator.forHeader( - forHeader, protocolContext, protocolSchedule, transactionPool, ethScheduler) - .createBlock(transactions, Collections.emptyList(), System.currentTimeMillis()) + protocolContext, protocolSchedule, transactionPool, ethScheduler) + .createBlock(transactions, Collections.emptyList(), System.currentTimeMillis(), forHeader) .getBlock(); } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/BonsaiAccountTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/BonsaiAccountTest.java new file mode 100644 index 00000000000..53cf9e1ca12 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/BonsaiAccountTest.java @@ -0,0 +1,74 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldState; +import org.hyperledger.besu.evm.worldstate.UpdateTrackingAccount; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.units.bigints.UInt256; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +public class BonsaiAccountTest { + + @Mock BonsaiWorldState bonsaiWorldState; + + @Test + void shouldCopyTrackedBonsaiAccountCorrectly() { + final BonsaiAccount trackedAccount = + new BonsaiAccount( + bonsaiWorldState, + Address.ZERO, + Hash.hash(Address.ZERO), + 0, + Wei.ONE, + Hash.EMPTY_TRIE_HASH, + Hash.EMPTY, + true); + trackedAccount.setCode(Bytes.of(1)); + final UpdateTrackingAccount bonsaiAccountUpdateTrackingAccount = + new UpdateTrackingAccount<>(trackedAccount); + bonsaiAccountUpdateTrackingAccount.setStorageValue(UInt256.ONE, UInt256.ONE); + + final BonsaiAccount expectedAccount = new BonsaiAccount(trackedAccount, bonsaiWorldState, true); + expectedAccount.setStorageValue(UInt256.ONE, UInt256.ONE); + assertThat(new BonsaiAccount(bonsaiWorldState, bonsaiAccountUpdateTrackingAccount)) + .isEqualToComparingFieldByField(expectedAccount); + } + + @Test + void shouldCopyBonsaiAccountCorrectly() { + final BonsaiAccount account = + new BonsaiAccount( + bonsaiWorldState, + Address.ZERO, + Hash.hash(Address.ZERO), + 0, + Wei.ONE, + Hash.EMPTY_TRIE_HASH, + Hash.EMPTY, + true); + account.setCode(Bytes.of(1)); + account.setStorageValue(UInt256.ONE, UInt256.ONE); + assertThat(new BonsaiAccount(account, bonsaiWorldState, true)) + .isEqualToComparingFieldByField(account); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/BonsaiCachedMerkleTrieLoaderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/BonsaiCachedMerkleTrieLoaderTest.java new file mode 100644 index 00000000000..06b377ad83a --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/BonsaiCachedMerkleTrieLoaderTest.java @@ -0,0 +1,189 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.StorageSlotKey; +import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; +import org.hyperledger.besu.ethereum.core.TrieGenerator; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.storage.StorageProvider; +import org.hyperledger.besu.ethereum.trie.MerkleTrie; +import org.hyperledger.besu.ethereum.trie.TrieIterator; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.BonsaiCachedMerkleTrieLoader; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class BonsaiCachedMerkleTrieLoaderTest { + + private BonsaiCachedMerkleTrieLoader merkleTrieLoader; + private final StorageProvider storageProvider = new InMemoryKeyValueStorageProvider(); + private final BonsaiWorldStateKeyValueStorage inMemoryWorldState = + Mockito.spy( + new BonsaiWorldStateKeyValueStorage( + storageProvider, + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG)); + private final WorldStateStorageCoordinator worldStateStorageCoordinator = + new WorldStateStorageCoordinator(inMemoryWorldState); + + final List
accounts = + List.of(Address.fromHexString("0xdeadbeef"), Address.fromHexString("0xdeadbeee")); + + private MerkleTrie trie; + + @BeforeEach + public void setup() { + trie = + TrieGenerator.generateTrie( + worldStateStorageCoordinator, + accounts.stream().map(Address::addressHash).collect(Collectors.toList())); + merkleTrieLoader = new BonsaiCachedMerkleTrieLoader(new NoOpMetricsSystem()); + } + + @Test + void shouldAddAccountNodesInCacheDuringPreload() { + merkleTrieLoader.cacheAccountNodes( + inMemoryWorldState, Hash.wrap(trie.getRootHash()), accounts.get(0)); + + final BonsaiWorldStateKeyValueStorage emptyStorage = + new BonsaiWorldStateKeyValueStorage( + new InMemoryKeyValueStorageProvider(), + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG); + StoredMerklePatriciaTrie cachedTrie = + new StoredMerklePatriciaTrie<>( + (location, hash) -> + merkleTrieLoader.getAccountStateTrieNode(emptyStorage, location, hash), + trie.getRootHash(), + Function.identity(), + Function.identity()); + + final Hash hashAccountZero = accounts.get(0).addressHash(); + assertThat(cachedTrie.get(hashAccountZero)).isEqualTo(trie.get(hashAccountZero)); + } + + @Test + void shouldAddStorageNodesInCacheDuringPreload() { + final Hash hashAccountZero = accounts.get(0).addressHash(); + final StateTrieAccountValue stateTrieAccountValue = + StateTrieAccountValue.readFrom(RLP.input(trie.get(hashAccountZero).orElseThrow())); + final StoredMerklePatriciaTrie storageTrie = + new StoredMerklePatriciaTrie<>( + (location, hash) -> + inMemoryWorldState.getAccountStorageTrieNode(hashAccountZero, location, hash), + stateTrieAccountValue.getStorageRoot(), + Function.identity(), + Function.identity()); + final List originalSlots = new ArrayList<>(); + storageTrie.visitLeafs( + (keyHash, node) -> { + merkleTrieLoader.cacheStorageNodes( + inMemoryWorldState, + accounts.get(0), + new StorageSlotKey(Hash.wrap(keyHash), Optional.empty())); + originalSlots.add(node.getEncodedBytes()); + return TrieIterator.State.CONTINUE; + }); + + final List cachedSlots = new ArrayList<>(); + final BonsaiWorldStateKeyValueStorage emptyStorage = + new BonsaiWorldStateKeyValueStorage( + new InMemoryKeyValueStorageProvider(), + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_CONFIG); + final StoredMerklePatriciaTrie cachedTrie = + new StoredMerklePatriciaTrie<>( + (location, hash) -> + merkleTrieLoader.getAccountStorageTrieNode( + emptyStorage, hashAccountZero, location, hash), + stateTrieAccountValue.getStorageRoot(), + Function.identity(), + Function.identity()); + cachedTrie.visitLeafs( + (keyHash, node) -> { + cachedSlots.add(node.getEncodedBytes()); + return TrieIterator.State.CONTINUE; + }); + assertThat(originalSlots).isNotEmpty().isEqualTo(cachedSlots); + } + + @Test + void shouldFallbackWhenAccountNodesIsNotInCache() { + final StoredMerklePatriciaTrie cachedTrie = + new StoredMerklePatriciaTrie<>( + (location, hash) -> + merkleTrieLoader.getAccountStateTrieNode(inMemoryWorldState, location, hash), + trie.getRootHash(), + Function.identity(), + Function.identity()); + final Hash hashAccountZero = accounts.get(0).addressHash(); + assertThat(cachedTrie.get(hashAccountZero)).isEqualTo(trie.get(hashAccountZero)); + } + + @Test + void shouldFallbackWhenStorageNodesIsNotInCache() { + final Hash hashAccountZero = accounts.get(0).addressHash(); + final StateTrieAccountValue stateTrieAccountValue = + StateTrieAccountValue.readFrom(RLP.input(trie.get(hashAccountZero).orElseThrow())); + final StoredMerklePatriciaTrie storageTrie = + new StoredMerklePatriciaTrie<>( + (location, hash) -> + inMemoryWorldState.getAccountStorageTrieNode(hashAccountZero, location, hash), + stateTrieAccountValue.getStorageRoot(), + Function.identity(), + Function.identity()); + final List originalSlots = new ArrayList<>(); + storageTrie.visitLeafs( + (keyHash, node) -> { + originalSlots.add(node.getEncodedBytes()); + return TrieIterator.State.CONTINUE; + }); + + final List cachedSlots = new ArrayList<>(); + final StoredMerklePatriciaTrie cachedTrie = + new StoredMerklePatriciaTrie<>( + (location, hash) -> + merkleTrieLoader.getAccountStorageTrieNode( + inMemoryWorldState, hashAccountZero, location, hash), + stateTrieAccountValue.getStorageRoot(), + Function.identity(), + Function.identity()); + cachedTrie.visitLeafs( + (keyHash, node) -> { + cachedSlots.add(node.getEncodedBytes()); + return TrieIterator.State.CONTINUE; + }); + assertThat(originalSlots).isNotEmpty().isEqualTo(cachedSlots); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiSnapshotIsolationTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/BonsaiSnapshotIsolationTests.java similarity index 92% rename from ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiSnapshotIsolationTests.java rename to ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/BonsaiSnapshotIsolationTests.java index 03407d2868f..322aacb7eb7 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/BonsaiSnapshotIsolationTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/BonsaiSnapshotIsolationTests.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,10 +11,8 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - -package org.hyperledger.besu.ethereum.bonsai; +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -37,11 +35,11 @@ public void ensureTruncateDoesNotCauseSegfault() { var preTruncatedWorldState = archive.getMutable(genesisState.getBlock().getHeader(), false); assertThat(preTruncatedWorldState) .isPresent(); // really just assert that we have not segfaulted after truncating - bonsaiWorldStateStorage.clear(); + worldStateKeyValueStorage.clear(); var postTruncatedWorldState = archive.getMutable(genesisState.getBlock().getHeader(), false); assertThat(postTruncatedWorldState).isEmpty(); // assert that trying to access pre-worldstate does not segfault after truncating - preTruncatedWorldState.get().get(Address.fromHexString(accounts.get(0).getAddress())); + preTruncatedWorldState.get().get(accounts.get(0).address()); assertThat(true).isTrue(); } @@ -61,12 +59,8 @@ public void testIsolatedFromHead_behindHead() { assertThat(res.isSuccessful()).isTrue(); assertThat(res2.isSuccessful()).isTrue(); - assertThat( - archive.getCachedWorldStorageManager().containWorldStateStorage(firstBlock.getHash())) - .isTrue(); - assertThat( - archive.getCachedWorldStorageManager().containWorldStateStorage(secondBlock.getHash())) - .isTrue(); + assertThat(archive.getCachedWorldStorageManager().contains(firstBlock.getHash())).isTrue(); + assertThat(archive.getCachedWorldStorageManager().contains(secondBlock.getHash())).isTrue(); assertThat(archive.getMutable().get(testAddress)).isNotNull(); assertThat(archive.getMutable().get(testAddress).getBalance()) diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/BonsaiWorldStateProviderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/BonsaiWorldStateProviderTest.java new file mode 100644 index 00000000000..4ac99377ef0 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/BonsaiWorldStateProviderTest.java @@ -0,0 +1,325 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.BLOCKCHAIN; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE; +import static org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedWorldStateKeyValueStorage.WORLD_BLOCK_HASH_KEY; +import static org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedWorldStateKeyValueStorage.WORLD_ROOT_HASH_KEY; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.ethereum.storage.StorageProvider; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.BonsaiCachedMerkleTrieLoader; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.BonsaiCachedWorldStorageManager; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.trielog.TrieLogFactoryImpl; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldState; +import org.hyperledger.besu.ethereum.trie.diffbased.common.trielog.TrieLogLayer; +import org.hyperledger.besu.ethereum.trie.diffbased.common.trielog.TrieLogManager; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; +import org.hyperledger.besu.plugin.services.storage.KeyValueStorageTransaction; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; + +import java.util.Optional; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +class BonsaiWorldStateProviderTest { + final BlockHeaderTestFixture blockBuilder = new BlockHeaderTestFixture(); + + @Mock Blockchain blockchain; + + @Mock StorageProvider storageProvider; + + @Mock SegmentedKeyValueStorage segmentedKeyValueStorage; + @Mock KeyValueStorage trieLogStorage; + @Mock SegmentedKeyValueStorageTransaction segmentedKeyValueStorageTransaction; + BonsaiWorldStateProvider bonsaiWorldStateArchive; + + @Mock BonsaiCachedWorldStorageManager cachedWorldStorageManager; + @Mock TrieLogManager trieLogManager; + + @BeforeEach + public void setUp() { + when(storageProvider.getStorageBySegmentIdentifiers(anyList())) + .thenReturn(segmentedKeyValueStorage); + when(segmentedKeyValueStorage.startTransaction()) + .thenReturn(segmentedKeyValueStorageTransaction); + when(storageProvider.getStorageBySegmentIdentifier(any())).thenReturn(trieLogStorage); + when(trieLogStorage.startTransaction()).thenReturn(mock(KeyValueStorageTransaction.class)); + } + + @Test + void testGetMutableReturnPersistedStateWhenNeeded() { + final BlockHeader chainHead = blockBuilder.number(0).buildHeader(); + + when(segmentedKeyValueStorage.get(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY)) + .thenReturn(Optional.of(chainHead.getStateRoot().toArrayUnsafe())); + when(segmentedKeyValueStorage.get(TRIE_BRANCH_STORAGE, WORLD_BLOCK_HASH_KEY)) + .thenReturn(Optional.of(chainHead.getHash().toArrayUnsafe())); + when(segmentedKeyValueStorage.get(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY)) + .thenReturn(Optional.of(chainHead.getStateRoot().toArrayUnsafe())); + when(segmentedKeyValueStorage.get(TRIE_BRANCH_STORAGE, WORLD_BLOCK_HASH_KEY)) + .thenReturn(Optional.of(chainHead.getHash().toArrayUnsafe())); + bonsaiWorldStateArchive = + new BonsaiWorldStateProvider( + cachedWorldStorageManager, + trieLogManager, + new BonsaiWorldStateKeyValueStorage( + storageProvider, + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG), + blockchain, + new BonsaiCachedMerkleTrieLoader(new NoOpMetricsSystem()), + EvmConfiguration.DEFAULT); + + assertThat(bonsaiWorldStateArchive.getMutable(chainHead, true)) + .containsInstanceOf(BonsaiWorldState.class); + } + + @Test + void testGetMutableReturnEmptyWhenLoadMoreThanLimitLayersBack() { + bonsaiWorldStateArchive = + new BonsaiWorldStateProvider( + new BonsaiWorldStateKeyValueStorage( + storageProvider, + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG), + blockchain, + Optional.of(512L), + new BonsaiCachedMerkleTrieLoader(new NoOpMetricsSystem()), + null, + EvmConfiguration.DEFAULT); + final BlockHeader blockHeader = blockBuilder.number(0).buildHeader(); + final BlockHeader chainHead = blockBuilder.number(512).buildHeader(); + when(blockchain.getChainHeadHeader()).thenReturn(chainHead); + assertThat(bonsaiWorldStateArchive.getMutable(blockHeader, false)).isEmpty(); + verify(cachedWorldStorageManager, Mockito.never()).getWorldState(any(Hash.class)); + } + + @Test + void testGetMutableWhenLoadLessThanLimitLayersBack() { + + bonsaiWorldStateArchive = + new BonsaiWorldStateProvider( + cachedWorldStorageManager, + trieLogManager, + new BonsaiWorldStateKeyValueStorage( + storageProvider, + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG), + blockchain, + new BonsaiCachedMerkleTrieLoader(new NoOpMetricsSystem()), + EvmConfiguration.DEFAULT); + final BlockHeader blockHeader = blockBuilder.number(0).buildHeader(); + final BlockHeader chainHead = blockBuilder.number(511).buildHeader(); + final BonsaiWorldState mockWorldState = mock(BonsaiWorldState.class); + when(mockWorldState.blockHash()).thenReturn(blockHeader.getHash()); + when(mockWorldState.freeze()).thenReturn(mockWorldState); + + when(trieLogManager.getMaxLayersToLoad()).thenReturn(Long.valueOf(512)); + when(cachedWorldStorageManager.getWorldState(blockHeader.getHash())) + .thenReturn(Optional.of(mockWorldState)); + when(blockchain.getChainHeadHeader()).thenReturn(chainHead); + assertThat(bonsaiWorldStateArchive.getMutable(blockHeader, false)) + .containsInstanceOf(BonsaiWorldState.class); + } + + @Test + void testGetMutableWithStorageInconsistencyRollbackTheState() { + + doAnswer(__ -> Optional.of(mock(TrieLogLayer.class))) + .when(trieLogManager) + .getTrieLogLayer(any(Hash.class)); + + var worldStateKeyValueStorage = + new BonsaiWorldStateKeyValueStorage( + storageProvider, + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG); + bonsaiWorldStateArchive = + spy( + new BonsaiWorldStateProvider( + cachedWorldStorageManager, + trieLogManager, + worldStateKeyValueStorage, + blockchain, + new BonsaiCachedMerkleTrieLoader(new NoOpMetricsSystem()), + EvmConfiguration.DEFAULT)); + final BlockHeader blockHeader = blockBuilder.number(0).buildHeader(); + + when(blockchain.getBlockHeader(blockHeader.getHash())).thenReturn(Optional.of(blockHeader)); + + assertThat(bonsaiWorldStateArchive.getMutable(null, blockHeader.getHash())) + .containsInstanceOf(BonsaiWorldState.class); + + // verify is trying to get the trie log layer to rollback + verify(trieLogManager).getTrieLogLayer(Hash.ZERO); + } + + // @SuppressWarnings({"unchecked", "rawtypes"}) + @Test + void testGetMutableWithStorageConsistencyNotRollbackTheState() { + + var worldStateKeyValueStorage = + new BonsaiWorldStateKeyValueStorage( + storageProvider, + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG); + bonsaiWorldStateArchive = + spy( + new BonsaiWorldStateProvider( + cachedWorldStorageManager, + trieLogManager, + worldStateKeyValueStorage, + blockchain, + new BonsaiCachedMerkleTrieLoader(new NoOpMetricsSystem()), + EvmConfiguration.DEFAULT)); + + final BlockHeader blockHeader = blockBuilder.number(0).buildHeader(); + + when(blockchain.getBlockHeader(blockHeader.getHash())).thenReturn(Optional.of(blockHeader)); + when(blockchain.getBlockHeader(Hash.ZERO)).thenReturn(Optional.of(blockHeader)); + + assertThat(bonsaiWorldStateArchive.getMutable(null, blockHeader.getHash())) + .containsInstanceOf(BonsaiWorldState.class); + + // verify is not trying to get the trie log layer to rollback when block is present + verify(trieLogManager, Mockito.never()).getTrieLogLayer(any()); + } + + @Test + void testGetMutableWithStorageConsistencyToRollbackAndRollForwardTheState() { + final BlockHeader genesis = blockBuilder.number(0).buildHeader(); + final BlockHeader blockHeaderChainA = + blockBuilder.number(1).timestamp(1).parentHash(genesis.getHash()).buildHeader(); + final BlockHeader blockHeaderChainB = + blockBuilder.number(1).timestamp(2).parentHash(genesis.getHash()).buildHeader(); + + doAnswer(__ -> Optional.of(mock(TrieLogLayer.class))) + .when(trieLogManager) + .getTrieLogLayer(any(Hash.class)); + + var worldStateKeyValueStorage = + new BonsaiWorldStateKeyValueStorage( + storageProvider, + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG); + + bonsaiWorldStateArchive = + spy( + new BonsaiWorldStateProvider( + cachedWorldStorageManager, + trieLogManager, + worldStateKeyValueStorage, + blockchain, + new BonsaiCachedMerkleTrieLoader(new NoOpMetricsSystem()), + EvmConfiguration.DEFAULT)); + + // initial persisted state hash key + when(blockchain.getBlockHeader(Hash.ZERO)).thenReturn(Optional.of(blockHeaderChainA)); + when(blockchain.getBlockHeader(blockHeaderChainB.getHash())) + .thenReturn(Optional.of(blockHeaderChainB)); + when(blockchain.getBlockHeader(genesis.getHash())).thenReturn(Optional.of(genesis)); + + assertThat(bonsaiWorldStateArchive.getMutable(null, blockHeaderChainB.getHash())) + .containsInstanceOf(BonsaiWorldState.class); + + // verify is trying to get the trie log layers to rollback and roll forward + verify(trieLogManager).getTrieLogLayer(blockHeaderChainA.getHash()); + verify(trieLogManager).getTrieLogLayer(blockHeaderChainB.getHash()); + } + + @Test + // TODO: refactor to test original intent + @Disabled("needs refactor, getMutable(hash, hash) cannot trigger saveTrieLog") + void testGetMutableWithRollbackNotOverrideTrieLogLayer() { + when(segmentedKeyValueStorage.startTransaction()) + .thenReturn(segmentedKeyValueStorageTransaction); + final BlockHeader genesis = blockBuilder.number(0).buildHeader(); + final BlockHeader blockHeaderChainA = + blockBuilder.number(1).timestamp(1).parentHash(genesis.getHash()).buildHeader(); + final BlockHeader blockHeaderChainB = + blockBuilder.number(1).timestamp(2).parentHash(genesis.getHash()).buildHeader(); + + doAnswer(__ -> Optional.of(mock(TrieLogLayer.class))) + .when(trieLogManager) + .getTrieLogLayer(any(Hash.class)); + + bonsaiWorldStateArchive = + spy( + new BonsaiWorldStateProvider( + cachedWorldStorageManager, + trieLogManager, + new BonsaiWorldStateKeyValueStorage( + storageProvider, + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG), + blockchain, + new BonsaiCachedMerkleTrieLoader(new NoOpMetricsSystem()), + EvmConfiguration.DEFAULT)); + + // initial persisted state hash key + when(blockchain.getBlockHeader(Hash.ZERO)).thenReturn(Optional.of(blockHeaderChainA)); + // fake trie log layer + final BytesValueRLPOutput rlpLogBlockB = new BytesValueRLPOutput(); + final TrieLogLayer trieLogLayerBlockB = new TrieLogLayer(); + trieLogLayerBlockB.setBlockHash(blockHeaderChainB.getHash()); + TrieLogFactoryImpl.writeTo(trieLogLayerBlockB, rlpLogBlockB); + when(segmentedKeyValueStorage.get(BLOCKCHAIN, blockHeaderChainB.getHash().toArrayUnsafe())) + .thenReturn(Optional.of(rlpLogBlockB.encoded().toArrayUnsafe())); + + when(blockchain.getBlockHeader(blockHeaderChainB.getHash())) + .thenReturn(Optional.of(blockHeaderChainB)); + when(blockchain.getBlockHeader(genesis.getHash())).thenReturn(Optional.of(genesis)); + + assertThat(bonsaiWorldStateArchive.getMutable(null, blockHeaderChainB.getHash())) + .containsInstanceOf(BonsaiWorldState.class); + + // verify is not persisting if already present + verify(segmentedKeyValueStorageTransaction, never()) + .put(BLOCKCHAIN, eq(blockHeaderChainA.getHash().toArrayUnsafe()), any()); + verify(segmentedKeyValueStorageTransaction, never()) + .put(BLOCKCHAIN, eq(blockHeaderChainB.getHash().toArrayUnsafe()), any()); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/LogRollingTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/LogRollingTests.java similarity index 85% rename from ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/LogRollingTests.java rename to ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/LogRollingTests.java index 225930fdb74..e43d464474c 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/LogRollingTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/LogRollingTests.java @@ -11,10 +11,8 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - -package org.hyperledger.besu.ethereum.bonsai; +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -22,11 +20,6 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogFactoryImpl; -import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogLayer; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Difficulty; @@ -34,6 +27,13 @@ import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.trielog.TrieLogFactoryImpl; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldState; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; +import org.hyperledger.besu.ethereum.trie.diffbased.common.trielog.TrieLogLayer; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldStateConfig; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.evm.account.MutableAccount; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.log.LogsBloomFilter; @@ -161,8 +161,10 @@ void simpleRollForwardTest() { final BonsaiWorldState worldState = new BonsaiWorldState( archive, - new BonsaiWorldStateKeyValueStorage(provider, new NoOpMetricsSystem()), - EvmConfiguration.DEFAULT); + new BonsaiWorldStateKeyValueStorage( + provider, new NoOpMetricsSystem(), DataStorageConfiguration.DEFAULT_BONSAI_CONFIG), + EvmConfiguration.DEFAULT, + new DiffBasedWorldStateConfig()); final WorldUpdater updater = worldState.updater(); final MutableAccount mutableAccount = updater.createAccount(addressOne, 1, Wei.of(1L)); @@ -174,8 +176,12 @@ void simpleRollForwardTest() { final BonsaiWorldState secondWorldState = new BonsaiWorldState( secondArchive, - new BonsaiWorldStateKeyValueStorage(secondProvider, new NoOpMetricsSystem()), - EvmConfiguration.DEFAULT); + new BonsaiWorldStateKeyValueStorage( + secondProvider, + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG), + EvmConfiguration.DEFAULT, + new DiffBasedWorldStateConfig()); final BonsaiWorldStateUpdateAccumulator secondUpdater = (BonsaiWorldStateUpdateAccumulator) secondWorldState.updater(); @@ -205,8 +211,10 @@ void rollForwardTwice() { final BonsaiWorldState worldState = new BonsaiWorldState( archive, - new BonsaiWorldStateKeyValueStorage(provider, new NoOpMetricsSystem()), - EvmConfiguration.DEFAULT); + new BonsaiWorldStateKeyValueStorage( + provider, new NoOpMetricsSystem(), DataStorageConfiguration.DEFAULT_BONSAI_CONFIG), + EvmConfiguration.DEFAULT, + new DiffBasedWorldStateConfig()); final WorldUpdater updater = worldState.updater(); final MutableAccount mutableAccount = updater.createAccount(addressOne, 1, Wei.of(1L)); @@ -226,8 +234,12 @@ void rollForwardTwice() { final BonsaiWorldState secondWorldState = new BonsaiWorldState( secondArchive, - new BonsaiWorldStateKeyValueStorage(secondProvider, new NoOpMetricsSystem()), - EvmConfiguration.DEFAULT); + new BonsaiWorldStateKeyValueStorage( + secondProvider, + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG), + EvmConfiguration.DEFAULT, + new DiffBasedWorldStateConfig()); final BonsaiWorldStateUpdateAccumulator secondUpdater = (BonsaiWorldStateUpdateAccumulator) secondWorldState.updater(); @@ -258,8 +270,10 @@ void rollBackOnce() { final BonsaiWorldState worldState = new BonsaiWorldState( archive, - new BonsaiWorldStateKeyValueStorage(provider, new NoOpMetricsSystem()), - EvmConfiguration.DEFAULT); + new BonsaiWorldStateKeyValueStorage( + provider, new NoOpMetricsSystem(), DataStorageConfiguration.DEFAULT_BONSAI_CONFIG), + EvmConfiguration.DEFAULT, + new DiffBasedWorldStateConfig()); final WorldUpdater updater = worldState.updater(); final MutableAccount mutableAccount = updater.createAccount(addressOne, 1, Wei.of(1L)); @@ -286,8 +300,12 @@ void rollBackOnce() { final BonsaiWorldState secondWorldState = new BonsaiWorldState( secondArchive, - new BonsaiWorldStateKeyValueStorage(secondProvider, new NoOpMetricsSystem()), - EvmConfiguration.DEFAULT); + new BonsaiWorldStateKeyValueStorage( + secondProvider, + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG), + EvmConfiguration.DEFAULT, + new DiffBasedWorldStateConfig()); final WorldUpdater secondUpdater = secondWorldState.updater(); final MutableAccount secondMutableAccount = diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/RollingImport.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/RollingImport.java similarity index 81% rename from ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/RollingImport.java rename to ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/RollingImport.java index 5aa0f68daa0..09e21138fd3 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/RollingImport.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/RollingImport.java @@ -11,10 +11,8 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - -package org.hyperledger.besu.ethereum.bonsai; +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai; import static com.google.common.base.Preconditions.checkArgument; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.ACCOUNT_INFO_STATE; @@ -22,14 +20,16 @@ import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.CODE_STORAGE; import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogFactoryImpl; -import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogLayer; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.trielog.TrieLogFactoryImpl; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldState; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; +import org.hyperledger.besu.ethereum.trie.diffbased.common.trielog.TrieLogLayer; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldStateConfig; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; @@ -56,9 +56,11 @@ public static void main(final String[] arg) throws IOException { final BonsaiWorldState bonsaiState = new BonsaiWorldState( archive, - new BonsaiWorldStateKeyValueStorage(provider, new NoOpMetricsSystem()), - EvmConfiguration.DEFAULT); - final SegmentedInMemoryKeyValueStorage worldStateStorage = + new BonsaiWorldStateKeyValueStorage( + provider, new NoOpMetricsSystem(), DataStorageConfiguration.DEFAULT_BONSAI_CONFIG), + EvmConfiguration.DEFAULT, + new DiffBasedWorldStateConfig()); + final SegmentedInMemoryKeyValueStorage worldStateKeyValueStorage = (SegmentedInMemoryKeyValueStorage) provider.getStorageBySegmentIdentifiers( List.of( @@ -126,7 +128,7 @@ public static void main(final String[] arg) throws IOException { } } System.out.printf("Back to zero!%n"); - worldStateStorage.dump(System.out); + worldStateKeyValueStorage.dump(System.out); trieLogStorage.dump(System.out); } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java new file mode 100644 index 00000000000..02818407b46 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/storage/BonsaiWorldStateKeyValueStorageTest.java @@ -0,0 +1,519 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE; +import static org.hyperledger.besu.ethereum.trie.diffbased.common.storage.DiffBasedWorldStateKeyValueStorage.WORLD_ROOT_HASH_KEY; +import static org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration.DEFAULT_BONSAI_MAX_LAYERS_TO_LOAD; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.StorageSlotKey; +import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; +import org.hyperledger.besu.ethereum.core.TrieGenerator; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.storage.StorageProvider; +import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; +import org.hyperledger.besu.ethereum.trie.MerkleTrie; +import org.hyperledger.besu.ethereum.trie.StorageEntriesCollector; +import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.ethereum.worldstate.FlatDbMode; +import org.hyperledger.besu.ethereum.worldstate.ImmutableDataStorageConfiguration; +import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; +import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Optional; +import java.util.TreeMap; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mockito; + +public class BonsaiWorldStateKeyValueStorageTest { + + public static Collection flatDbMode() { + return Arrays.asList(new Object[][] {{FlatDbMode.FULL}, {FlatDbMode.PARTIAL}}); + } + + public static Collection flatDbModeAndCodeStorageMode() { + return Arrays.asList( + new Object[][] { + {FlatDbMode.FULL, false}, + {FlatDbMode.PARTIAL, false}, + {FlatDbMode.FULL, true}, + {FlatDbMode.PARTIAL, true} + }); + } + + BonsaiWorldStateKeyValueStorage storage; + + public BonsaiWorldStateKeyValueStorage setUp(final FlatDbMode flatDbMode) { + return setUp(flatDbMode, false); + } + + public BonsaiWorldStateKeyValueStorage setUp( + final FlatDbMode flatDbMode, final boolean useCodeHashStorage) { + storage = emptyStorage(useCodeHashStorage); + if (flatDbMode.equals(FlatDbMode.FULL)) { + storage.upgradeToFullFlatDbMode(); + } else if (flatDbMode.equals(FlatDbMode.PARTIAL)) { + storage.downgradeToPartialFlatDbMode(); + } + return storage; + } + + @ParameterizedTest + @MethodSource("flatDbMode") + void getCode_returnsEmpty(final FlatDbMode flatDbMode) { + setUp(flatDbMode); + assertThat(storage.getCode(Hash.EMPTY, Hash.EMPTY)).contains(Bytes.EMPTY); + } + + @ParameterizedTest + @MethodSource("flatDbMode") + void getAccountStateTrieNode_returnsEmptyNode(final FlatDbMode flatDbMode) { + setUp(flatDbMode); + assertThat(storage.getAccountStateTrieNode(Bytes.EMPTY, MerkleTrie.EMPTY_TRIE_NODE_HASH)) + .contains(MerkleTrie.EMPTY_TRIE_NODE); + } + + @ParameterizedTest + @MethodSource("flatDbMode") + void getAccountStorageTrieNode_returnsEmptyNode(final FlatDbMode flatDbMode) { + setUp(flatDbMode); + assertThat( + storage.getAccountStorageTrieNode( + Hash.EMPTY, Bytes.EMPTY, MerkleTrie.EMPTY_TRIE_NODE_HASH)) + .contains(MerkleTrie.EMPTY_TRIE_NODE); + } + + @ParameterizedTest + @MethodSource("flatDbModeAndCodeStorageMode") + void getCode_saveAndGetSpecialValues( + final FlatDbMode flatDbMode, final boolean accountHashCodeStorage) { + setUp(flatDbMode, accountHashCodeStorage); + storage + .updater() + .putCode(Hash.EMPTY, MerkleTrie.EMPTY_TRIE_NODE) + .putCode(Hash.EMPTY, Bytes.EMPTY) + .commit(); + + assertThat(storage.getCode(Hash.hash(MerkleTrie.EMPTY_TRIE_NODE), Hash.EMPTY)) + .contains(MerkleTrie.EMPTY_TRIE_NODE); + } + + @ParameterizedTest + @MethodSource("flatDbModeAndCodeStorageMode") + void getCode_saveAndGetRegularValue( + final FlatDbMode flatDbMode, final boolean accountHashCodeStorage) { + setUp(flatDbMode, accountHashCodeStorage); + final Bytes bytes = Bytes.fromHexString("0x123456"); + storage.updater().putCode(Hash.EMPTY, bytes).commit(); + + assertThat(storage.getCode(Hash.hash(bytes), Hash.EMPTY)).contains(bytes); + } + + @ParameterizedTest + @MethodSource("flatDbMode") + void getAccountStateTrieNode_saveAndGetSpecialValues(final FlatDbMode flatDbMode) { + setUp(flatDbMode); + storage + .updater() + .putAccountStateTrieNode( + Bytes.EMPTY, Hash.hash(MerkleTrie.EMPTY_TRIE_NODE), MerkleTrie.EMPTY_TRIE_NODE) + .putAccountStateTrieNode(Bytes.EMPTY, Hash.hash(Bytes.EMPTY), Bytes.EMPTY) + .commit(); + + assertThat(storage.getAccountStateTrieNode(Bytes.EMPTY, MerkleTrie.EMPTY_TRIE_NODE_HASH)) + .contains(MerkleTrie.EMPTY_TRIE_NODE); + assertThat(storage.getAccountStateTrieNode(Bytes.EMPTY, Hash.EMPTY)).contains(Bytes.EMPTY); + } + + @ParameterizedTest + @MethodSource("flatDbMode") + void getAccountStateTrieNode_saveAndGetRegularValue(final FlatDbMode flatDbMode) { + setUp(flatDbMode); + final Bytes location = Bytes.fromHexString("0x01"); + final Bytes bytes = Bytes.fromHexString("0x123456"); + + storage.updater().putAccountStateTrieNode(location, Hash.hash(bytes), bytes).commit(); + + assertThat(storage.getAccountStateTrieNode(location, Hash.hash(bytes))).contains(bytes); + } + + @ParameterizedTest + @MethodSource("flatDbMode") + void getAccountStorageTrieNode_saveAndGetSpecialValues(final FlatDbMode flatDbMode) { + setUp(flatDbMode); + + storage + .updater() + .putAccountStorageTrieNode( + Hash.EMPTY, + Bytes.EMPTY, + Hash.hash(MerkleTrie.EMPTY_TRIE_NODE), + MerkleTrie.EMPTY_TRIE_NODE) + .putAccountStorageTrieNode(Hash.EMPTY, Bytes.EMPTY, Hash.hash(Bytes.EMPTY), Bytes.EMPTY) + .commit(); + + assertThat( + storage.getAccountStorageTrieNode( + Hash.EMPTY, Bytes.EMPTY, Hash.hash(MerkleTrie.EMPTY_TRIE_NODE))) + .contains(MerkleTrie.EMPTY_TRIE_NODE); + assertThat(storage.getAccountStorageTrieNode(Hash.EMPTY, Bytes.EMPTY, Hash.EMPTY)) + .contains(Bytes.EMPTY); + } + + @ParameterizedTest + @MethodSource("flatDbMode") + void getAccountStorageTrieNode_saveAndGetRegularValue(final FlatDbMode flatDbMode) { + setUp(flatDbMode); + final Hash accountHash = Address.fromHexString("0x1").addressHash(); + final Bytes location = Bytes.fromHexString("0x01"); + final Bytes bytes = Bytes.fromHexString("0x123456"); + + storage + .updater() + .putAccountStorageTrieNode(accountHash, location, Hash.hash(bytes), bytes) + .commit(); + + assertThat(storage.getAccountStorageTrieNode(accountHash, location, Hash.hash(bytes))) + .contains(bytes); + } + + @ParameterizedTest + @MethodSource("flatDbMode") + void getAccount_notLoadFromTrieWhenEmptyAndFlatDbFullMode(final FlatDbMode flatDbMode) { + Assumptions.assumeTrue(flatDbMode == FlatDbMode.FULL); + final BonsaiWorldStateKeyValueStorage storage = spy(setUp(flatDbMode)); + final WorldStateStorageCoordinator coordinator = new WorldStateStorageCoordinator(storage); + final MerkleTrie trie = TrieGenerator.generateTrie(coordinator, 1); + + final TreeMap accounts = + (TreeMap) + trie.entriesFrom(root -> StorageEntriesCollector.collectEntries(root, Hash.ZERO, 1)); + + // save world state root hash + final BonsaiWorldStateKeyValueStorage.Updater updater = storage.updater(); + updater + .getWorldStateTransaction() + .put(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY, trie.getRootHash().toArrayUnsafe()); + updater.commit(); + + // remove flat database + storage.downgradeToPartialFlatDbMode(); + storage.clearFlatDatabase(); + storage.upgradeToFullFlatDbMode(); + + Mockito.reset(storage); + + assertThat(storage.getAccount(Hash.wrap(accounts.firstKey()))).isEmpty(); + + verify(storage, times(0)).getAccountStateTrieNode(any(), eq(trie.getRootHash())); + } + + @ParameterizedTest + @MethodSource("flatDbMode") + void getAccount_loadFromTrieWhenEmptyAndFlatDbPartialMode(final FlatDbMode flatDbMode) { + Assumptions.assumeTrue(flatDbMode == FlatDbMode.PARTIAL); + final BonsaiWorldStateKeyValueStorage storage = spy(setUp(flatDbMode)); + final WorldStateStorageCoordinator coordinator = new WorldStateStorageCoordinator(storage); + final MerkleTrie trie = TrieGenerator.generateTrie(coordinator, 1); + final TreeMap accounts = + (TreeMap) + trie.entriesFrom(root -> StorageEntriesCollector.collectEntries(root, Hash.ZERO, 1)); + + // save world state root hash + final BonsaiWorldStateKeyValueStorage.Updater updater = storage.updater(); + updater + .getWorldStateTransaction() + .put(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY, trie.getRootHash().toArrayUnsafe()); + updater.commit(); + + // remove flat database + storage.clearFlatDatabase(); + + Mockito.reset(storage); + + assertThat(storage.getAccount(Hash.wrap(accounts.firstKey()))) + .contains(accounts.firstEntry().getValue()); + + verify(storage, times(1)).getAccountStateTrieNode(any(), eq(trie.getRootHash())); + } + + @ParameterizedTest + @MethodSource("flatDbMode") + void shouldUsePartialDBStrategyAfterDowngradingMode(final FlatDbMode flatDbMode) { + Assumptions.assumeTrue(flatDbMode == FlatDbMode.PARTIAL); + final BonsaiWorldStateKeyValueStorage storage = spy(setUp(flatDbMode)); + final WorldStateStorageCoordinator coordinator = new WorldStateStorageCoordinator(storage); + final MerkleTrie trie = TrieGenerator.generateTrie(coordinator, 1); + final TreeMap accounts = + (TreeMap) + trie.entriesFrom(root -> StorageEntriesCollector.collectEntries(root, Hash.ZERO, 1)); + + // save world state root hash + final BonsaiWorldStateKeyValueStorage.Updater updater = storage.updater(); + updater + .getWorldStateTransaction() + .put(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY, trie.getRootHash().toArrayUnsafe()); + updater.commit(); + + Mockito.reset(storage); + + // remove flat database + storage.clearFlatDatabase(); + + storage.upgradeToFullFlatDbMode(); + assertThat(storage.getAccount(Hash.wrap(accounts.firstKey()))).isEmpty(); + + storage.downgradeToPartialFlatDbMode(); + assertThat(storage.getAccount(Hash.wrap(accounts.firstKey()))) + .contains(accounts.firstEntry().getValue()); + } + + @ParameterizedTest + @MethodSource("flatDbMode") + void getStorage_loadFromTrieWhenEmptyWithPartialMode(final FlatDbMode flatDbMode) { + final BonsaiWorldStateKeyValueStorage storage = spy(setUp(flatDbMode)); + Assumptions.assumeTrue(flatDbMode == FlatDbMode.PARTIAL); + + final WorldStateStorageCoordinator coordinator = new WorldStateStorageCoordinator(storage); + final MerkleTrie trie = TrieGenerator.generateTrie(coordinator, 1); + final TreeMap accounts = + (TreeMap) + trie.entriesFrom(root -> StorageEntriesCollector.collectEntries(root, Hash.ZERO, 1)); + + final StateTrieAccountValue stateTrieAccountValue = + StateTrieAccountValue.readFrom(RLP.input(accounts.firstEntry().getValue())); + + final StoredMerklePatriciaTrie storageTrie = + new StoredMerklePatriciaTrie<>( + (location, hash) -> + storage.getAccountStorageTrieNode(Hash.wrap(accounts.firstKey()), location, hash), + stateTrieAccountValue.getStorageRoot(), + b -> b, + b -> b); + + final TreeMap slots = + (TreeMap) + storageTrie.entriesFrom( + root -> StorageEntriesCollector.collectEntries(root, Hash.ZERO, 1)); + + // save world state root hash + final BonsaiWorldStateKeyValueStorage.Updater updater = storage.updater(); + updater + .getWorldStateTransaction() + .put(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY, trie.getRootHash().toArrayUnsafe()); + updater.commit(); + + // remove flat database + storage.clearFlatDatabase(); + + assertThat( + storage.getStorageValueByStorageSlotKey( + Hash.wrap(accounts.firstKey()), + new StorageSlotKey(Hash.wrap(slots.firstKey()), Optional.empty()))) + .map(Bytes::toShortHexString) + .contains(slots.firstEntry().getValue().toShortHexString()); + + verify(storage, times(2)) + .getAccountStorageTrieNode( + eq(Hash.wrap(accounts.firstKey())), any(), eq(storageTrie.getRootHash())); + } + + @ParameterizedTest + @MethodSource("flatDbMode") + void getStorage_loadFromTrieWhenEmptyWithFullMode(final FlatDbMode flatDbMode) { + Assumptions.assumeTrue(flatDbMode == FlatDbMode.FULL); + final BonsaiWorldStateKeyValueStorage storage = spy(setUp(flatDbMode)); + storage.upgradeToFullFlatDbMode(); + final WorldStateStorageCoordinator coordinator = new WorldStateStorageCoordinator(storage); + final MerkleTrie trie = TrieGenerator.generateTrie(coordinator, 1); + + // save world state root hash + final BonsaiWorldStateKeyValueStorage.Updater updater = storage.updater(); + updater + .getWorldStateTransaction() + .put(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY, trie.getRootHash().toArrayUnsafe()); + updater.commit(); + + // remove flat database + storage.clearFlatDatabase(); + } + + @ParameterizedTest + @MethodSource("flatDbMode") + void clear_reloadFlatDbStrategy(final FlatDbMode flatDbMode) { + final BonsaiWorldStateKeyValueStorage storage = spy(setUp(flatDbMode)); + + // save world state root hash + final BonsaiWorldStateKeyValueStorage.Updater updater = storage.updater(); + updater.putAccountInfoState(Hash.ZERO, Bytes32.random()).commit(); + + assertThat(storage.getAccount(Hash.ZERO)).isNotEmpty(); + + // clear + storage.clear(); + + assertThat(storage.getFlatDbStrategy()).isNotNull(); + + assertThat(storage.getAccount(Hash.ZERO)).isEmpty(); + } + + @ParameterizedTest + @MethodSource("flatDbMode") + void reconcilesNonConflictingUpdaters(final FlatDbMode flatDbMode) { + setUp(flatDbMode); + final Hash accountHashA = Address.fromHexString("0x1").addressHash(); + final Hash accountHashB = Address.fromHexString("0x2").addressHash(); + final Hash accountHashD = Address.fromHexString("0x4").addressHash(); + final Bytes bytesA = Bytes.fromHexString("0x12"); + final Bytes bytesB = Bytes.fromHexString("0x1234"); + final Bytes bytesC = Bytes.fromHexString("0x123456"); + + final BonsaiWorldStateKeyValueStorage.Updater updaterA = storage.updater(); + final BonsaiWorldStateKeyValueStorage.Updater updaterB = storage.updater(); + + updaterA.putCode(accountHashA, bytesA); + updaterB.putCode(accountHashB, bytesA); + updaterB.putCode(accountHashB, bytesB); + updaterA.putCode(accountHashD, bytesC); + + updaterA.commit(); + updaterB.commit(); + + assertThat(storage.getCode(Hash.hash(bytesB), accountHashB)).contains(bytesB); + assertThat(storage.getCode(Hash.hash(bytesC), accountHashD)).contains(bytesC); + } + + @ParameterizedTest + @MethodSource("flatDbMode") + void isWorldStateAvailable_defaultIsFalse(final FlatDbMode flatDbMode) { + setUp(flatDbMode); + assertThat(emptyStorage().isWorldStateAvailable(UInt256.valueOf(1), Hash.EMPTY)).isFalse(); + } + + @ParameterizedTest + @MethodSource("flatDbMode") + void isWorldStateAvailable_StateAvailableByRootHash(final FlatDbMode flatDbMode) { + setUp(flatDbMode); + + final BonsaiWorldStateKeyValueStorage.Updater updater = storage.updater(); + final Bytes rootHashKey = Bytes32.fromHexString("0x01"); + updater + .getWorldStateTransaction() + .put(TRIE_BRANCH_STORAGE, WORLD_ROOT_HASH_KEY, rootHashKey.toArrayUnsafe()); + updater.commit(); + assertThat(storage.isWorldStateAvailable(Hash.wrap(Bytes32.wrap(rootHashKey)), Hash.EMPTY)) + .isTrue(); + } + + @ParameterizedTest + @MethodSource("flatDbMode") + void isWorldStateAvailable_afterCallingSaveWorldstate(final FlatDbMode flatDbMode) { + setUp(flatDbMode); + + final BonsaiWorldStateKeyValueStorage.Updater updater = storage.updater(); + + final Bytes blockHash = Bytes32.fromHexString("0x01"); + final Bytes32 nodeHashKey = Bytes32.fromHexString("0x02"); + final Bytes nodeValue = Bytes32.fromHexString("0x03"); + + assertThat(storage.isWorldStateAvailable(Bytes32.wrap(nodeHashKey), Hash.EMPTY)).isFalse(); + + updater.saveWorldState(blockHash, nodeHashKey, nodeValue); + updater.commit(); + + assertThat(storage.isWorldStateAvailable(Bytes32.wrap(nodeHashKey), Hash.EMPTY)).isTrue(); + } + + private BonsaiWorldStateKeyValueStorage emptyStorage() { + return new BonsaiWorldStateKeyValueStorage( + new InMemoryKeyValueStorageProvider(), + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG); + } + + private BonsaiWorldStateKeyValueStorage emptyStorage(final boolean useCodeHashStorage) { + return new BonsaiWorldStateKeyValueStorage( + new InMemoryKeyValueStorageProvider(), + new NoOpMetricsSystem(), + ImmutableDataStorageConfiguration.builder() + .dataStorageFormat(DataStorageFormat.BONSAI) + .bonsaiMaxLayersToLoad(DEFAULT_BONSAI_MAX_LAYERS_TO_LOAD) + .unstable( + ImmutableDataStorageConfiguration.Unstable.builder() + .bonsaiCodeStoredByCodeHashEnabled(useCodeHashStorage) + .build()) + .build()); + } + + @Test + void successfulPruneReturnsTrue() { + final KeyValueStorage mockTrieLogStorage = mock(KeyValueStorage.class); + when(mockTrieLogStorage.tryDelete(any())).thenReturn(true); + final BonsaiWorldStateKeyValueStorage storage = setupSpyStorage(mockTrieLogStorage); + assertThat(storage.pruneTrieLog(Hash.ZERO)).isTrue(); + } + + @Test + void failedPruneReturnsFalse() { + final KeyValueStorage mockTrieLogStorage = mock(KeyValueStorage.class); + when(mockTrieLogStorage.tryDelete(any())).thenReturn(false); + final BonsaiWorldStateKeyValueStorage storage = setupSpyStorage(mockTrieLogStorage); + assertThat(storage.pruneTrieLog(Hash.ZERO)).isFalse(); + } + + @Test + void exceptionalPruneReturnsFalse() { + final KeyValueStorage mockTrieLogStorage = mock(KeyValueStorage.class); + when(mockTrieLogStorage.tryDelete(any())).thenThrow(new RuntimeException("test exception")); + final BonsaiWorldStateKeyValueStorage storage = setupSpyStorage(mockTrieLogStorage); + assertThat(storage.pruneTrieLog(Hash.ZERO)).isFalse(); + } + + private BonsaiWorldStateKeyValueStorage setupSpyStorage( + final KeyValueStorage mockTrieLogStorage) { + final StorageProvider mockStorageProvider = spy(new InMemoryKeyValueStorageProvider()); + when(mockStorageProvider.getStorageBySegmentIdentifier( + KeyValueSegmentIdentifier.TRIE_LOG_STORAGE)) + .thenReturn(mockTrieLogStorage); + + return new BonsaiWorldStateKeyValueStorage( + mockStorageProvider, + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogFactoryTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/trielog/TrieLogFactoryTests.java similarity index 91% rename from ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogFactoryTests.java rename to ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/trielog/TrieLogFactoryTests.java index 78161826acb..5173df717fc 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogFactoryTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/trielog/TrieLogFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,9 +11,8 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ -package org.hyperledger.besu.ethereum.bonsai.trielog; +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai.trielog; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -24,8 +23,9 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; +import org.hyperledger.besu.ethereum.trie.diffbased.common.trielog.TrieLogLayer; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.plugin.services.trielogs.TrieLog; import org.hyperledger.besu.plugin.services.trielogs.TrieLogFactory; diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/trielog/TrieLogManagerTests.java similarity index 81% rename from ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java rename to ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/trielog/TrieLogManagerTests.java index f2fb2224be6..129a10f1bd0 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogManagerTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/trielog/TrieLogManagerTests.java @@ -11,21 +11,20 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ -package org.hyperledger.besu.ethereum.bonsai.trielog; +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai.trielog; import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogPruner.noOpTrieLogPruner; import static org.mockito.Mockito.spy; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldState; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; +import org.hyperledger.besu.ethereum.trie.diffbased.common.trielog.TrieLogManager; import org.hyperledger.besu.evm.internal.EvmConfiguration; import java.util.concurrent.atomic.AtomicBoolean; @@ -57,9 +56,7 @@ class TrieLogManagerTests { @BeforeEach public void setup() { - trieLogManager = - new TrieLogManager( - blockchain, bonsaiWorldStateKeyValueStorage, 512, null, noOpTrieLogPruner()); + trieLogManager = new TrieLogManager(blockchain, bonsaiWorldStateKeyValueStorage, 512, null); } @Test diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/worldview/BonsaiWorldStateTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/worldview/BonsaiWorldStateTest.java new file mode 100644 index 00000000000..b342172b1ba --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/bonsai/worldview/BonsaiWorldStateTest.java @@ -0,0 +1,144 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.common.DiffBasedValue; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldStateConfig; +import org.hyperledger.besu.evm.internal.EvmConfiguration; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class BonsaiWorldStateTest { + @Mock BonsaiWorldStateUpdateAccumulator bonsaiWorldStateUpdateAccumulator; + @Mock BonsaiWorldStateKeyValueStorage.Updater bonsaiUpdater; + @Mock Blockchain blockchain; + @Mock BonsaiWorldStateKeyValueStorage bonsaiWorldStateKeyValueStorage; + + private static final Bytes CODE = Bytes.of(10); + private static final Hash CODE_HASH = Hash.hash(CODE); + private static final Hash ACCOUNT_HASH = Hash.hash(Address.ZERO); + private static final Address ACCOUNT = Address.ZERO; + + private BonsaiWorldState worldState; + + @BeforeEach + void setup() { + worldState = + new BonsaiWorldState( + InMemoryKeyValueStorageProvider.createBonsaiInMemoryWorldStateArchive(blockchain), + bonsaiWorldStateKeyValueStorage, + EvmConfiguration.DEFAULT, + new DiffBasedWorldStateConfig()); + } + + @ParameterizedTest + @MethodSource("priorAndUpdatedEmptyAndNullBytes") + void codeUpdateDoesNothingWhenMarkedAsDeletedButAlreadyDeleted( + final Bytes prior, final Bytes updated) { + final Map> codeToUpdate = + Map.of(Address.ZERO, new DiffBasedValue<>(prior, updated)); + when(bonsaiWorldStateUpdateAccumulator.getCodeToUpdate()).thenReturn(codeToUpdate); + worldState.updateCode(Optional.of(bonsaiUpdater), bonsaiWorldStateUpdateAccumulator); + + verifyNoInteractions(bonsaiUpdater); + } + + @Test + void codeUpdateDoesNothingWhenAddingSameAsExistingValue() { + final Map> codeToUpdate = + Map.of(Address.ZERO, new DiffBasedValue<>(CODE, CODE)); + when(bonsaiWorldStateUpdateAccumulator.getCodeToUpdate()).thenReturn(codeToUpdate); + worldState.updateCode(Optional.of(bonsaiUpdater), bonsaiWorldStateUpdateAccumulator); + + verifyNoInteractions(bonsaiUpdater); + } + + @ParameterizedTest + @MethodSource("emptyAndNullBytes") + void removesCodeWhenMarkedAsDeleted(final Bytes updated) { + final Map> codeToUpdate = + Map.of(Address.ZERO, new DiffBasedValue<>(CODE, updated)); + when(bonsaiWorldStateUpdateAccumulator.getCodeToUpdate()).thenReturn(codeToUpdate); + worldState.updateCode(Optional.of(bonsaiUpdater), bonsaiWorldStateUpdateAccumulator); + + verify(bonsaiUpdater).removeCode(ACCOUNT_HASH, CODE_HASH); + } + + @ParameterizedTest + @MethodSource("codeValueAndEmptyAndNullBytes") + void addsCodeForNewCodeValue(final Bytes prior) { + final Map> codeToUpdate = + Map.of(ACCOUNT, new DiffBasedValue<>(prior, CODE)); + + when(bonsaiWorldStateUpdateAccumulator.getCodeToUpdate()).thenReturn(codeToUpdate); + worldState.updateCode(Optional.of(bonsaiUpdater), bonsaiWorldStateUpdateAccumulator); + + verify(bonsaiUpdater).putCode(ACCOUNT_HASH, CODE_HASH, CODE); + } + + @Test + void updateCodeForMultipleValues() { + final Map> codeToUpdate = new HashMap<>(); + codeToUpdate.put(Address.fromHexString("0x1"), new DiffBasedValue<>(null, CODE)); + codeToUpdate.put(Address.fromHexString("0x2"), new DiffBasedValue<>(CODE, null)); + codeToUpdate.put(Address.fromHexString("0x3"), new DiffBasedValue<>(Bytes.of(9), CODE)); + + when(bonsaiWorldStateUpdateAccumulator.getCodeToUpdate()).thenReturn(codeToUpdate); + worldState.updateCode(Optional.of(bonsaiUpdater), bonsaiWorldStateUpdateAccumulator); + + verify(bonsaiUpdater).putCode(Address.fromHexString("0x1").addressHash(), CODE_HASH, CODE); + verify(bonsaiUpdater).removeCode(Address.fromHexString("0x2").addressHash(), CODE_HASH); + verify(bonsaiUpdater).putCode(Address.fromHexString("0x3").addressHash(), CODE_HASH, CODE); + } + + private static Stream emptyAndNullBytes() { + return Stream.of(Bytes.EMPTY, null); + } + + private static Stream codeValueAndEmptyAndNullBytes() { + return Stream.of(Bytes.EMPTY, null); + } + + private static Stream priorAndUpdatedEmptyAndNullBytes() { + return Stream.of( + Arguments.of(null, Bytes.EMPTY), + Arguments.of(Bytes.EMPTY, null), + Arguments.of(null, null), + Arguments.of(Bytes.EMPTY, Bytes.EMPTY)); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/flat/FlatDbStrategyProviderTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/flat/FlatDbStrategyProviderTest.java new file mode 100644 index 00000000000..b3c709b89f2 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/common/storage/flat/FlatDbStrategyProviderTest.java @@ -0,0 +1,186 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.common.storage.flat; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration.DEFAULT_BONSAI_MAX_LAYERS_TO_LOAD; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.flat.FullFlatDbStrategy; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.flat.PartialFlatDbStrategy; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.ethereum.worldstate.FlatDbMode; +import org.hyperledger.besu.ethereum.worldstate.ImmutableDataStorageConfiguration; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; +import org.hyperledger.besu.services.kvstore.SegmentedInMemoryKeyValueStorage; + +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class FlatDbStrategyProviderTest { + private final FlatDbStrategyProvider flatDbStrategyProvider = + new FlatDbStrategyProvider(new NoOpMetricsSystem(), DataStorageConfiguration.DEFAULT_CONFIG); + private final SegmentedKeyValueStorage composedWorldStateStorage = + new SegmentedInMemoryKeyValueStorage( + List.of( + KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE, + KeyValueSegmentIdentifier.CODE_STORAGE)); + + @ParameterizedTest + @EnumSource(FlatDbMode.class) + void loadsFlatDbStrategyForStoredFlatDbMode(final FlatDbMode flatDbMode) { + updateFlatDbMode(flatDbMode); + + flatDbStrategyProvider.loadFlatDbStrategy(composedWorldStateStorage); + assertThat(flatDbStrategyProvider.getFlatDbMode()).isEqualTo(flatDbMode); + } + + @Test + void loadsPartialFlatDbStrategyWhenNoFlatDbModeStored() { + flatDbStrategyProvider.loadFlatDbStrategy(composedWorldStateStorage); + assertThat(flatDbStrategyProvider.getFlatDbMode()).isEqualTo(FlatDbMode.FULL); + } + + @Test + void upgradesFlatDbStrategyToFullFlatDbMode() { + updateFlatDbMode(FlatDbMode.PARTIAL); + + flatDbStrategyProvider.upgradeToFullFlatDbMode(composedWorldStateStorage); + assertThat(flatDbStrategyProvider.flatDbMode).isEqualTo(FlatDbMode.FULL); + assertThat(flatDbStrategyProvider.flatDbStrategy).isNotNull(); + assertThat(flatDbStrategyProvider.getFlatDbStrategy(composedWorldStateStorage)) + .isInstanceOf(FullFlatDbStrategy.class); + assertThat(flatDbStrategyProvider.flatDbStrategy.codeStorageStrategy) + .isInstanceOf(CodeHashCodeStorageStrategy.class); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + void emptyDbCreatesFlatDbStrategyUsingCodeByHashConfig(final boolean codeByHashEnabled) { + final DataStorageConfiguration dataStorageConfiguration = + ImmutableDataStorageConfiguration.builder() + .dataStorageFormat(DataStorageFormat.BONSAI) + .bonsaiMaxLayersToLoad(DEFAULT_BONSAI_MAX_LAYERS_TO_LOAD) + .unstable( + ImmutableDataStorageConfiguration.Unstable.builder() + .bonsaiCodeStoredByCodeHashEnabled(codeByHashEnabled) + .build()) + .build(); + final FlatDbStrategyProvider flatDbStrategyProvider = + new FlatDbStrategyProvider(new NoOpMetricsSystem(), dataStorageConfiguration); + + flatDbStrategyProvider.loadFlatDbStrategy(composedWorldStateStorage); + final Class expectedCodeStorageClass = + codeByHashEnabled + ? CodeHashCodeStorageStrategy.class + : AccountHashCodeStorageStrategy.class; + assertThat(flatDbStrategyProvider.flatDbMode).isEqualTo(FlatDbMode.FULL); + assertThat(flatDbStrategyProvider.flatDbStrategy.codeStorageStrategy) + .isInstanceOf(expectedCodeStorageClass); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + void existingAccountHashDbUsesAccountHash(final boolean codeByHashEnabled) { + final DataStorageConfiguration dataStorageConfiguration = + ImmutableDataStorageConfiguration.builder() + .dataStorageFormat(DataStorageFormat.BONSAI) + .bonsaiMaxLayersToLoad(DEFAULT_BONSAI_MAX_LAYERS_TO_LOAD) + .unstable( + ImmutableDataStorageConfiguration.Unstable.builder() + .bonsaiCodeStoredByCodeHashEnabled(codeByHashEnabled) + .build()) + .build(); + final FlatDbStrategyProvider flatDbStrategyProvider = + new FlatDbStrategyProvider(new NoOpMetricsSystem(), dataStorageConfiguration); + + final SegmentedKeyValueStorageTransaction transaction = + composedWorldStateStorage.startTransaction(); + final AccountHashCodeStorageStrategy accountHashCodeStorageStrategy = + new AccountHashCodeStorageStrategy(); + // key representing account hash just needs to not be the code hash + final Hash accountHash = Hash.wrap(Bytes32.fromHexString("0001")); + accountHashCodeStorageStrategy.putFlatCode(transaction, accountHash, null, Bytes.of(2)); + transaction.commit(); + + flatDbStrategyProvider.loadFlatDbStrategy(composedWorldStateStorage); + assertThat(flatDbStrategyProvider.flatDbMode).isEqualTo(FlatDbMode.FULL); + assertThat(flatDbStrategyProvider.flatDbStrategy.codeStorageStrategy) + .isInstanceOf(AccountHashCodeStorageStrategy.class); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + void existingCodeHashDbUsesCodeHash(final boolean codeByHashEnabled) { + final DataStorageConfiguration dataStorageConfiguration = + ImmutableDataStorageConfiguration.builder() + .dataStorageFormat(DataStorageFormat.BONSAI) + .bonsaiMaxLayersToLoad(DEFAULT_BONSAI_MAX_LAYERS_TO_LOAD) + .unstable( + ImmutableDataStorageConfiguration.Unstable.builder() + .bonsaiCodeStoredByCodeHashEnabled(codeByHashEnabled) + .build()) + .build(); + final FlatDbStrategyProvider flatDbStrategyProvider = + new FlatDbStrategyProvider(new NoOpMetricsSystem(), dataStorageConfiguration); + + final SegmentedKeyValueStorageTransaction transaction = + composedWorldStateStorage.startTransaction(); + + final CodeHashCodeStorageStrategy codeHashCodeStorageStrategy = + new CodeHashCodeStorageStrategy(); + codeHashCodeStorageStrategy.putFlatCode(transaction, null, Hash.hash(Bytes.of(1)), Bytes.of(1)); + transaction.commit(); + + flatDbStrategyProvider.loadFlatDbStrategy(composedWorldStateStorage); + assertThat(flatDbStrategyProvider.flatDbMode).isEqualTo(FlatDbMode.FULL); + assertThat(flatDbStrategyProvider.flatDbStrategy.codeStorageStrategy) + .isInstanceOf(CodeHashCodeStorageStrategy.class); + } + + @Test + void downgradesFlatDbStrategyToPartiallyFlatDbMode() { + updateFlatDbMode(FlatDbMode.FULL); + + flatDbStrategyProvider.downgradeToPartialFlatDbMode(composedWorldStateStorage); + assertThat(flatDbStrategyProvider.flatDbMode).isEqualTo(FlatDbMode.PARTIAL); + assertThat(flatDbStrategyProvider.flatDbStrategy).isNotNull(); + assertThat(flatDbStrategyProvider.getFlatDbStrategy(composedWorldStateStorage)) + .isInstanceOf(PartialFlatDbStrategy.class); + } + + private void updateFlatDbMode(final FlatDbMode flatDbMode) { + final SegmentedKeyValueStorageTransaction transaction = + composedWorldStateStorage.startTransaction(); + transaction.put( + KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE, + FlatDbStrategyProvider.FLAT_DB_MODE, + flatDbMode.getVersion().toArrayUnsafe()); + transaction.commit(); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogLayerTests.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/common/trielog/TrieLogLayerTests.java similarity index 98% rename from ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogLayerTests.java rename to ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/common/trielog/TrieLogLayerTests.java index cd45bce5d92..85534de1b06 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/bonsai/trielog/TrieLogLayerTests.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/common/trielog/TrieLogLayerTests.java @@ -11,9 +11,8 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ -package org.hyperledger.besu.ethereum.bonsai.trielog; +package org.hyperledger.besu.ethereum.trie.diffbased.common.trielog; import org.hyperledger.besu.datatypes.AccountValue; import org.hyperledger.besu.datatypes.Address; diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/common/trielog/TrieLogPrunerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/common/trielog/TrieLogPrunerTest.java new file mode 100644 index 00000000000..5f985b1f89c --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/diffbased/common/trielog/TrieLogPrunerTest.java @@ -0,0 +1,387 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.diffbased.common.trielog; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.core.BlockDataGenerator; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; + +import java.util.Optional; +import java.util.function.Consumer; +import java.util.stream.Stream; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InOrder; +import org.mockito.Mockito; +import org.mockito.internal.stubbing.answers.AnswersWithDelay; + +public class TrieLogPrunerTest { + + private BonsaiWorldStateKeyValueStorage worldState; + private Blockchain blockchain; + private final Consumer executeAsync = Runnable::run; + + @SuppressWarnings("BannedMethod") + @BeforeEach + public void setup() { + Configurator.setLevel(LogManager.getLogger(TrieLogPruner.class).getName(), Level.TRACE); + worldState = Mockito.mock(BonsaiWorldStateKeyValueStorage.class); + blockchain = Mockito.mock(Blockchain.class); + when(worldState.pruneTrieLog(any(Hash.class))).thenReturn(true); + } + + @Test + public void initialize_preloads_queue_and_prunes_orphaned_blocks() { + // Given + int loadingLimit = 2; + final BlockDataGenerator generator = new BlockDataGenerator(); + final BlockHeader header1 = generator.header(1); + final BlockHeader header2 = generator.header(2); + when(worldState.streamTrieLogKeys(loadingLimit)) + .thenReturn(Stream.of(header1.getBlockHash().toArray(), header2.getBlockHash().toArray())); + when(blockchain.getBlockHeader(header1.getBlockHash())).thenReturn(Optional.of(header1)); + when(blockchain.getBlockHeader(header2.getBlockHash())).thenReturn(Optional.empty()); + + // When + TrieLogPruner trieLogPruner = + new TrieLogPruner( + worldState, blockchain, executeAsync, 3, loadingLimit, false, new NoOpMetricsSystem()); + trieLogPruner.initialize(); + + // Then + verify(worldState, times(1)).streamTrieLogKeys(2); + verify(worldState, times(1)).pruneTrieLog(header2.getBlockHash()); + } + + @Test + public void preloadQueueWithTimeout_handles_timeout_during_streamTrieLogKeys() { + // Given + final int timeoutInSeconds = 1; + final long timeoutInMillis = timeoutInSeconds * 1000; + final int loadingLimit = 2; + TrieLogPruner trieLogPruner = + new TrieLogPruner( + worldState, blockchain, executeAsync, 3, loadingLimit, false, new NoOpMetricsSystem()); + + // Simulate a long-running operation + when(worldState.streamTrieLogKeys(loadingLimit)) + .thenAnswer(new AnswersWithDelay(timeoutInMillis * 2, invocation -> Stream.empty())); + + // When + long startTime = System.currentTimeMillis(); + trieLogPruner.preloadQueueWithTimeout(timeoutInSeconds); + long elapsedTime = System.currentTimeMillis() - startTime; + + // Then + assertThat(elapsedTime).isLessThan(timeoutInMillis * 2); + } + + @Test + public void preloadQueueWithTimeout_handles_timeout_during_getBlockHeader() { + // Given + final int timeoutInSeconds = 1; + final long timeoutInMillis = timeoutInSeconds * 1000; + TrieLogPruner trieLogPruner = setupPrunerAndFinalizedBlock(3, 1); + + // Simulate a long-running operation + when(blockchain.getBlockHeader(any(Hash.class))) + // delay on first invocation, then return empty + .thenAnswer(new AnswersWithDelay(timeoutInMillis * 2, invocation -> Optional.empty())) + .thenReturn(Optional.empty()); + + // When + long startTime = System.currentTimeMillis(); + trieLogPruner.preloadQueueWithTimeout(timeoutInSeconds); + long elapsedTime = System.currentTimeMillis() - startTime; + + // Then + assertThat(elapsedTime).isLessThan(timeoutInMillis * 2); + verify(worldState, times(1)).pruneTrieLog(key(1)); + verify(worldState, times(1)).pruneTrieLog(key(2)); + } + + @Test + public void trieLogs_pruned_in_reverse_order_within_pruning_window() { + // Given + + // pruning window is below numBlocksToRetain and inside the pruningWindowSize offset. + final long blocksToRetain = 3; + final int pruningWindowSize = 2; + when(blockchain.getChainHeadBlockNumber()).thenReturn(5L); + when(worldState.pruneTrieLog(any(Hash.class))).thenReturn(true); + // requireFinalizedBlock = false means this is not a PoS chain + TrieLogPruner trieLogPruner = + new TrieLogPruner( + worldState, + blockchain, + executeAsync, + blocksToRetain, + pruningWindowSize, + false, + new NoOpMetricsSystem()); + + trieLogPruner.addToPruneQueue(0, key(0)); // older block outside prune window + trieLogPruner.addToPruneQueue(1, key(1)); // block inside the prune window + trieLogPruner.addToPruneQueue(1, key(2)); // same block number (fork) + trieLogPruner.addToPruneQueue(2, key(3)); // different block inside prune window + trieLogPruner.addToPruneQueue(3, key(4)); // retained block + trieLogPruner.addToPruneQueue(4, key(5)); // different retained block + trieLogPruner.addToPruneQueue(5, key(6)); // another retained block + + // When + int wasPruned = trieLogPruner.pruneFromQueue(); + + // Then + assertThat(wasPruned).isEqualTo(3); + InOrder inOrder = Mockito.inOrder(worldState); + inOrder.verify(worldState, times(1)).pruneTrieLog(key(3)); + inOrder.verify(worldState, times(1)).pruneTrieLog(key(1)); // forks in order + inOrder.verify(worldState, times(1)).pruneTrieLog(key(2)); + + // Subsequent run should add one more block, then prune two oldest remaining keys + trieLogPruner.addToPruneQueue(6, key(6)); + when(blockchain.getChainHeadBlockNumber()).thenReturn(6L); + + wasPruned = trieLogPruner.pruneFromQueue(); + + assertThat(wasPruned).isEqualTo(2); + inOrder.verify(worldState, times(1)).pruneTrieLog(key(4)); + inOrder.verify(worldState, times(1)).pruneTrieLog(key(0)); + } + + @Test + public void retain_non_finalized_blocks() { + // Given + // finalizedBlockHeight < configuredRetainHeight + final long finalizedBlockHeight = 1; + final long configuredRetainHeight = 3; + TrieLogPruner trieLogPruner = + setupPrunerAndFinalizedBlock(configuredRetainHeight, finalizedBlockHeight); + + // When + final int wasPruned = trieLogPruner.pruneFromQueue(); + + // Then + assertThat(wasPruned).isEqualTo(1); + verify(worldState, times(1)).pruneTrieLog(key(1)); // should prune (finalized) + verify(worldState, never()).pruneTrieLog(key(2)); // would prune but (NOT finalized) + verify(worldState, never()).pruneTrieLog(key(3)); // would prune but (NOT finalized) + verify(worldState, never()).pruneTrieLog(key(4)); // retained block (NOT finalized) + verify(worldState, never()).pruneTrieLog(key(5)); // chain height (NOT finalized) + } + + @Test + public void boundary_test_when_configured_retain_equals_finalized_block() { + // Given + // finalizedBlockHeight == configuredRetainHeight + final long finalizedBlockHeight = 2; + final long configuredRetainHeight = 2; + TrieLogPruner trieLogPruner = + setupPrunerAndFinalizedBlock(configuredRetainHeight, finalizedBlockHeight); + + // When + final int wasPruned = trieLogPruner.pruneFromQueue(); + + // Then + assertThat(wasPruned).isEqualTo(1); + verify(worldState, times(1)).pruneTrieLog(key(1)); // should prune (finalized) + verify(worldState, never()).pruneTrieLog(key(2)); // retained block (finalized) + verify(worldState, never()).pruneTrieLog(key(3)); // retained block (NOT finalized) + verify(worldState, never()).pruneTrieLog(key(4)); // retained block (NOT finalized) + verify(worldState, never()).pruneTrieLog(key(5)); // chain height (NOT finalized) + } + + @Test + public void use_configured_retain_when_finalized_block_is_higher() { + // Given + // finalizedBlockHeight > configuredRetainHeight + final long finalizedBlockHeight = 4; + final long configuredRetainHeight = 3; + final TrieLogPruner trieLogPruner = + setupPrunerAndFinalizedBlock(configuredRetainHeight, finalizedBlockHeight); + + // When + final int wasPruned = trieLogPruner.pruneFromQueue(); + + // Then + assertThat(wasPruned).isEqualTo(2); + final InOrder inOrder = Mockito.inOrder(worldState); + inOrder.verify(worldState, times(1)).pruneTrieLog(key(2)); // should prune (finalized) + inOrder.verify(worldState, times(1)).pruneTrieLog(key(1)); // should prune (finalized) + verify(worldState, never()).pruneTrieLog(key(3)); // retained block (finalized) + verify(worldState, never()).pruneTrieLog(key(4)); // retained block (finalized) + verify(worldState, never()).pruneTrieLog(key(5)); // chain height (NOT finalized) + } + + @Test + public void skip_pruning_when_finalized_block_required_but_not_present() { + // This can occur at the start of PoS chains + + // Given + when(blockchain.getFinalized()).thenReturn(Optional.empty()); + final long configuredRetainHeight = 2; + final long chainHeight = 2; + final long configuredRetainAboveHeight = configuredRetainHeight - 1; + final long blocksToRetain = chainHeight - configuredRetainAboveHeight; + final int pruningWindowSize = (int) chainHeight; + when(blockchain.getChainHeadBlockNumber()).thenReturn(chainHeight); + TrieLogPruner trieLogPruner = + new TrieLogPruner( + worldState, + blockchain, + executeAsync, + blocksToRetain, + pruningWindowSize, + true, + new NoOpMetricsSystem()); + + trieLogPruner.addToPruneQueue(1, key(1)); + trieLogPruner.addToPruneQueue(2, key(2)); + + // When + final int wasPruned = trieLogPruner.pruneFromQueue(); + + // Then + assertThat(wasPruned).isEqualTo(0); + verify(worldState, never()).pruneTrieLog(key(1)); // not finalized + verify(worldState, never()).pruneTrieLog(key(2)); // not finalized + } + + @Test + public void do_not_count_trieLog_when_prune_fails_first_attempt() { + // Given + when(worldState.pruneTrieLog(key(2))).thenReturn(false); + final long finalizedBlockHeight = 4; + final long configuredRetainHeight = 4; + final TrieLogPruner trieLogPruner = + setupPrunerAndFinalizedBlock(configuredRetainHeight, finalizedBlockHeight); + + // When + final int wasPruned = trieLogPruner.pruneFromQueue(); + + // Then + assertThat(wasPruned).isEqualTo(2); + + // Subsequent run should prune previously skipped trieLog + when(worldState.pruneTrieLog(key(2))).thenReturn(true); + assertThat(trieLogPruner.pruneFromQueue()).isEqualTo(1); + } + + @Test + public void onTrieLogAdded_should_prune() { + // Given + final TriggerableConsumer triggerableConsumer = new TriggerableConsumer(); + TrieLogPruner trieLogPruner = + new TrieLogPruner( + worldState, blockchain, triggerableConsumer, 0, 1, false, new NoOpMetricsSystem()); + assertThat(trieLogPruner.pruneFromQueue()).isEqualTo(0); + + final TrieLogLayer layer = new TrieLogLayer(); + layer.setBlockNumber(1L); + layer.setBlockHash(key(1)); + when(blockchain.getChainHeadBlockNumber()).thenReturn(1L); + + // When + trieLogPruner.onTrieLogAdded(new TrieLogAddedEvent(layer)); + verify(worldState, never()).pruneTrieLog(key(1)); + triggerableConsumer.run(); + + // Then + verify(worldState, times(1)).pruneTrieLog(key(1)); + } + + @Test + public void onTrieLogAdded_should_not_prune_when_no_blockNumber() { + // Given + TrieLogPruner trieLogPruner = + new TrieLogPruner( + worldState, blockchain, executeAsync, 0, 1, false, new NoOpMetricsSystem()); + assertThat(trieLogPruner.pruneFromQueue()).isEqualTo(0); + + final TrieLogLayer layer = new TrieLogLayer(); + layer.setBlockHash(key(1)); + when(blockchain.getChainHeadBlockNumber()).thenReturn(1L); + + // When + trieLogPruner.onTrieLogAdded(new TrieLogAddedEvent(layer)); + + // Then + verify(worldState, never()).pruneTrieLog(key(1)); + } + + private TrieLogPruner setupPrunerAndFinalizedBlock( + final long configuredRetainHeight, final long finalizedBlockHeight) { + final long chainHeight = 5; + final long configuredRetainAboveHeight = configuredRetainHeight - 1; + final long blocksToRetain = chainHeight - configuredRetainAboveHeight; + final int pruningWindowSize = (int) chainHeight; + + final BlockHeader finalizedHeader = new BlockDataGenerator().header(finalizedBlockHeight); + when(blockchain.getFinalized()).thenReturn(Optional.of(finalizedHeader.getBlockHash())); + when(blockchain.getBlockHeader(finalizedHeader.getBlockHash())) + .thenReturn(Optional.of(finalizedHeader)); + when(blockchain.getChainHeadBlockNumber()).thenReturn(chainHeight); + TrieLogPruner trieLogPruner = + new TrieLogPruner( + worldState, + blockchain, + executeAsync, + blocksToRetain, + pruningWindowSize, + true, + new NoOpMetricsSystem()); + + trieLogPruner.addToPruneQueue(1, key(1)); + trieLogPruner.addToPruneQueue(2, key(2)); + trieLogPruner.addToPruneQueue(3, key(3)); + trieLogPruner.addToPruneQueue(4, key(4)); + trieLogPruner.addToPruneQueue(5, key(5)); + + return trieLogPruner; + } + + private Hash key(final int k) { + return Hash.hash(Bytes.of(k)); + } + + private static class TriggerableConsumer implements Consumer { + + private Runnable runnable; + + @Override + public void accept(final Runnable runnable) { + this.runnable = runnable; + } + + public void run() { + runnable.run(); + } + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/forest/storage/ForestKeyValueStorageWorldStateStorageTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/forest/storage/ForestKeyValueStorageWorldStateStorageTest.java new file mode 100644 index 00000000000..2316089ba99 --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/forest/storage/ForestKeyValueStorageWorldStateStorageTest.java @@ -0,0 +1,189 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.forest.storage; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.trie.MerkleTrie; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage.Updater; +import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.units.bigints.UInt256; +import org.junit.jupiter.api.Test; + +public class ForestKeyValueStorageWorldStateStorageTest { + + @Test + public void getCode_returnsEmpty() { + final ForestWorldStateKeyValueStorage storage = emptyStorage(); + assertThat(storage.getCode(Hash.EMPTY)).contains(Bytes.EMPTY); + } + + @Test + public void getAccountStateTrieNode_returnsEmptyNode() { + final ForestWorldStateKeyValueStorage storage = emptyStorage(); + assertThat(storage.getAccountStateTrieNode(MerkleTrie.EMPTY_TRIE_NODE_HASH)) + .contains(MerkleTrie.EMPTY_TRIE_NODE); + } + + @Test + public void getAccountStorageTrieNode_returnsEmptyNode() { + final ForestWorldStateKeyValueStorage storage = emptyStorage(); + assertThat(storage.getAccountStorageTrieNode(MerkleTrie.EMPTY_TRIE_NODE_HASH)) + .contains(MerkleTrie.EMPTY_TRIE_NODE); + } + + @Test + public void getNodeData_returnsEmptyValue() { + final ForestWorldStateKeyValueStorage storage = emptyStorage(); + assertThat(storage.getNodeData(Hash.EMPTY)).contains(Bytes.EMPTY); + } + + @Test + public void getNodeData_returnsEmptyNode() { + final ForestWorldStateKeyValueStorage storage = emptyStorage(); + assertThat(storage.getNodeData(MerkleTrie.EMPTY_TRIE_NODE_HASH)) + .contains(MerkleTrie.EMPTY_TRIE_NODE); + } + + @Test + public void getCode_saveAndGetSpecialValues() { + final ForestWorldStateKeyValueStorage storage = emptyStorage(); + storage.updater().putCode(MerkleTrie.EMPTY_TRIE_NODE).putCode(Bytes.EMPTY).commit(); + + assertThat(storage.getCode(Hash.EMPTY_TRIE_HASH)).contains(MerkleTrie.EMPTY_TRIE_NODE); + + assertThat(storage.getCode(Hash.EMPTY)).contains(Bytes.EMPTY); + } + + @Test + public void getCode_saveAndGetRegularValue() { + final Bytes bytes = Bytes.fromHexString("0x123456"); + final ForestWorldStateKeyValueStorage storage = emptyStorage(); + storage.updater().putCode(bytes).commit(); + + assertThat(storage.getCode(Hash.hash(bytes))).contains(bytes); + } + + @Test + public void getAccountStateTrieNode_saveAndGetSpecialValues() { + final ForestWorldStateKeyValueStorage storage = emptyStorage(); + storage + .updater() + .putAccountStateTrieNode(Hash.hash(MerkleTrie.EMPTY_TRIE_NODE), MerkleTrie.EMPTY_TRIE_NODE) + .putAccountStateTrieNode(Hash.hash(Bytes.EMPTY), Bytes.EMPTY) + .commit(); + + assertThat(storage.getAccountStateTrieNode(MerkleTrie.EMPTY_TRIE_NODE_HASH)) + .contains(MerkleTrie.EMPTY_TRIE_NODE); + assertThat(storage.getAccountStateTrieNode(Hash.EMPTY)).contains(Bytes.EMPTY); + } + + @Test + public void getAccountStateTrieNode_saveAndGetRegularValue() { + final Bytes bytes = Bytes.fromHexString("0x123456"); + final ForestWorldStateKeyValueStorage storage = emptyStorage(); + storage.updater().putAccountStateTrieNode(Hash.hash(bytes), bytes).commit(); + + assertThat(storage.getAccountStateTrieNode(Hash.hash(bytes))).contains(bytes); + } + + @Test + public void getAccountStorageTrieNode_saveAndGetSpecialValues() { + final ForestWorldStateKeyValueStorage storage = emptyStorage(); + storage + .updater() + .putAccountStorageTrieNode( + Hash.hash(MerkleTrie.EMPTY_TRIE_NODE), MerkleTrie.EMPTY_TRIE_NODE) + .putAccountStorageTrieNode(Hash.hash(Bytes.EMPTY), Bytes.EMPTY) + .commit(); + + assertThat(storage.getAccountStorageTrieNode(MerkleTrie.EMPTY_TRIE_NODE_HASH)) + .contains(MerkleTrie.EMPTY_TRIE_NODE); + assertThat(storage.getAccountStorageTrieNode(Hash.EMPTY)).contains(Bytes.EMPTY); + } + + @Test + public void getAccountStorageTrieNode_saveAndGetRegularValue() { + final Bytes bytes = Bytes.fromHexString("0x123456"); + final ForestWorldStateKeyValueStorage storage = emptyStorage(); + storage.updater().putAccountStorageTrieNode(Hash.hash(bytes), bytes).commit(); + + assertThat(storage.getAccountStateTrieNode(Hash.hash(bytes))).contains(bytes); + } + + @Test + public void getNodeData_saveAndGetSpecialValues() { + final ForestWorldStateKeyValueStorage storage = emptyStorage(); + storage + .updater() + .putAccountStorageTrieNode( + Hash.hash(MerkleTrie.EMPTY_TRIE_NODE), MerkleTrie.EMPTY_TRIE_NODE) + .putAccountStorageTrieNode(Hash.hash(Bytes.EMPTY), Bytes.EMPTY) + .commit(); + + assertThat(storage.getNodeData(MerkleTrie.EMPTY_TRIE_NODE_HASH)) + .contains(MerkleTrie.EMPTY_TRIE_NODE); + assertThat(storage.getNodeData(Hash.EMPTY)).contains(Bytes.EMPTY); + } + + @Test + public void getNodeData_saveAndGetRegularValue() { + final Bytes bytes = Bytes.fromHexString("0x123456"); + final ForestWorldStateKeyValueStorage storage = emptyStorage(); + storage.updater().putAccountStorageTrieNode(Hash.hash(bytes), bytes).commit(); + + assertThat(storage.getNodeData(Hash.hash(bytes))).contains(bytes); + } + + @Test + public void reconcilesNonConflictingUpdaters() { + final Bytes bytesA = Bytes.fromHexString("0x12"); + final Bytes bytesB = Bytes.fromHexString("0x1234"); + final Bytes bytesC = Bytes.fromHexString("0x123456"); + + final ForestWorldStateKeyValueStorage storage = emptyStorage(); + final Updater updaterA = storage.updater(); + final Updater updaterB = storage.updater(); + + updaterA.putCode(bytesA); + updaterB.putCode(bytesA); + updaterB.putCode(bytesB); + updaterA.putCode(bytesC); + + updaterA.commit(); + updaterB.commit(); + + assertThat(storage.getCode(Hash.hash(bytesA))).contains(bytesA); + assertThat(storage.getCode(Hash.hash(bytesB))).contains(bytesB); + assertThat(storage.getCode(Hash.hash(bytesC))).contains(bytesC); + } + + @Test + public void isWorldStateAvailable_defaultIsFalse() { + assertThat(emptyStorage().isWorldStateAvailable(UInt256.valueOf(1))).isFalse(); + } + + @Test + public void isWorldStateAvailable_emptyTrieStateAlwaysAvailable() { + assertThat(emptyStorage().isWorldStateAvailable(Hash.EMPTY_TRIE_HASH)).isTrue(); + } + + private ForestWorldStateKeyValueStorage emptyStorage() { + return new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/forest/worldview/ForestMutableWorldStateTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/forest/worldview/ForestMutableWorldStateTest.java new file mode 100644 index 00000000000..4cac816227f --- /dev/null +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/trie/forest/worldview/ForestMutableWorldStateTest.java @@ -0,0 +1,678 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie.forest.worldview; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryWorldState; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStatePreimageKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.MerkleTrie; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; +import org.hyperledger.besu.evm.account.AccountStorageEntry; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.worldstate.WorldState; +import org.hyperledger.besu.evm.worldstate.WorldState.StreamableAccount; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; +import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; +import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; +import org.junit.jupiter.api.Test; + +// TODO: make that an abstract mutable world state test, and create sub-class for all world state +// implementations. +class ForestMutableWorldStateTest { + // The following test cases are loosely derived from the testTransactionToItself + // GeneralStateReferenceTest. + + private static final Address ADDRESS = + Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"); + + private static MutableWorldState createEmpty(final ForestWorldStateKeyValueStorage storage) { + final WorldStatePreimageKeyValueStorage preimageStorage = + new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage()); + return new ForestMutableWorldState(storage, preimageStorage, EvmConfiguration.DEFAULT); + } + + private static MutableWorldState createEmpty() { + return createInMemoryWorldState(); + } + + @Test + void rootHash_Empty() { + final MutableWorldState worldState = createEmpty(); + assertThat(worldState.rootHash()).isEqualTo(MerkleTrie.EMPTY_TRIE_NODE_HASH); + + worldState.persist(null); + assertThat(worldState.rootHash()).isEqualTo(MerkleTrie.EMPTY_TRIE_NODE_HASH); + } + + @Test + void containsAccount_AccountDoesNotExist() { + final WorldState worldState = createEmpty(); + assertThat(worldState.get(ADDRESS)).isNull(); + } + + @Test + void containsAccount_AccountExists() { + final MutableWorldState worldState = createEmpty(); + final WorldUpdater updater = worldState.updater(); + updater.createAccount(ADDRESS).setBalance(Wei.of(100000)); + updater.commit(); + assertThat(worldState.get(ADDRESS)).isNotNull(); + assertThat(worldState.rootHash()) + .isEqualTo( + Hash.fromHexString( + "0xa3e1c133a5a51b03399ed9ad0380f3182e9e18322f232b816dd4b9094f871e1b")); + } + + @Test + void removeAccount_AccountDoesNotExist() { + final MutableWorldState worldState = createEmpty(); + final WorldUpdater updater = worldState.updater(); + updater.deleteAccount(ADDRESS); + updater.commit(); + assertThat(worldState.rootHash()).isEqualTo(MerkleTrie.EMPTY_TRIE_NODE_HASH); + + worldState.persist(null); + assertThat(worldState.rootHash()).isEqualTo(MerkleTrie.EMPTY_TRIE_NODE_HASH); + } + + @Test + void removeAccount_UpdatedAccount() { + final MutableWorldState worldState = createEmpty(); + final WorldUpdater updater = worldState.updater(); + updater.createAccount(ADDRESS).setBalance(Wei.of(100000)); + updater.deleteAccount(ADDRESS); + updater.commit(); + assertThat(worldState.rootHash()).isEqualTo(MerkleTrie.EMPTY_TRIE_NODE_HASH); + + worldState.persist(null); + assertThat(worldState.rootHash()).isEqualTo(MerkleTrie.EMPTY_TRIE_NODE_HASH); + } + + @Test + void removeAccount_AccountExists() { + // Create a world state with one account + final MutableWorldState worldState = createEmpty(); + WorldUpdater updater = worldState.updater(); + updater.createAccount(ADDRESS).setBalance(Wei.of(100000)); + updater.commit(); + assertThat(worldState.get(ADDRESS)).isNotNull(); + assertThat(worldState.rootHash()).isNotEqualTo(MerkleTrie.EMPTY_TRIE_NODE_HASH); + + // Delete account + updater = worldState.updater(); + updater.deleteAccount(ADDRESS); + assertThat(updater.get(ADDRESS)).isNull(); + assertThat(updater.getAccount(ADDRESS)).isNull(); + updater.commit(); + assertThat(updater.get(ADDRESS)).isNull(); + + assertThat(worldState.rootHash()).isEqualTo(MerkleTrie.EMPTY_TRIE_NODE_HASH); + } + + @Test + void removeAccount_AccountExistsAndIsPersisted() { + // Create a world state with one account + final MutableWorldState worldState = createEmpty(); + WorldUpdater updater = worldState.updater(); + updater.createAccount(ADDRESS).setBalance(Wei.of(100000)); + updater.commit(); + worldState.persist(null); + assertThat(worldState.get(ADDRESS)).isNotNull(); + assertThat(worldState.rootHash()).isNotEqualTo(MerkleTrie.EMPTY_TRIE_NODE_HASH); + + // Delete account + updater = worldState.updater(); + updater.deleteAccount(ADDRESS); + assertThat(updater.get(ADDRESS)).isNull(); + assertThat(updater.getAccount(ADDRESS)).isNull(); + // Check account is gone after committing + updater.commit(); + assertThat(updater.get(ADDRESS)).isNull(); + // And after persisting + worldState.persist(null); + assertThat(updater.get(ADDRESS)).isNull(); + + assertThat(worldState.rootHash()).isEqualTo(MerkleTrie.EMPTY_TRIE_NODE_HASH); + } + + @Test + void streamAccounts_empty() { + final MutableWorldState worldState = createEmpty(); + final Stream accounts = worldState.streamAccounts(Bytes32.ZERO, 10); + assertThat(accounts.count()).isZero(); + } + + @Test + void streamAccounts_singleAccount() { + final MutableWorldState worldState = createEmpty(); + final WorldUpdater updater = worldState.updater(); + updater.createAccount(ADDRESS).setBalance(Wei.of(100000)); + updater.commit(); + + List accounts = + worldState.streamAccounts(Bytes32.ZERO, 10).collect(Collectors.toList()); + assertThat(accounts).hasSize(1); + assertThat(accounts.get(0).getAddress()).hasValue(ADDRESS); + assertThat(accounts.get(0).getBalance()).isEqualTo(Wei.of(100000)); + + // Check again after persisting + worldState.persist(null); + accounts = worldState.streamAccounts(Bytes32.ZERO, 10).collect(Collectors.toList()); + assertThat(accounts).hasSize(1); + assertThat(accounts.get(0).getAddress()).hasValue(ADDRESS); + assertThat(accounts.get(0).getBalance()).isEqualTo(Wei.of(100000)); + } + + @Test + void streamAccounts_multipleAccounts() { + final Address addr1 = Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"); + final Address addr2 = Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0c"); + + final MutableWorldState worldState = createEmpty(); + final WorldUpdater updater = worldState.updater(); + + // Create an account + final MutableAccount accountA = updater.createAccount(addr1); + accountA.setBalance(Wei.of(100000)); + // Create another + final MutableAccount accountB = updater.createAccount(addr2); + accountB.setNonce(1); + // Commit changes + updater.commit(); + + final boolean accountAIsFirst = + accountA + .getAddressHash() + .toUnsignedBigInteger() + .compareTo(accountB.getAddressHash().toUnsignedBigInteger()) + < 0; + final Hash startHash = accountAIsFirst ? accountA.getAddressHash() : accountB.getAddressHash(); + + // Get first account + final List firstAccount = worldState.streamAccounts(startHash, 1).toList(); + assertThat(firstAccount).hasSize(1); + assertThat(firstAccount.get(0).getAddress()) + .hasValue(accountAIsFirst ? accountA.getAddress() : accountB.getAddress()); + + // Get both accounts + final List allAccounts = worldState.streamAccounts(Bytes32.ZERO, 2).toList(); + assertThat(allAccounts).hasSize(2); + assertThat(allAccounts.get(0).getAddress()) + .hasValue(accountAIsFirst ? accountA.getAddress() : accountB.getAddress()); + assertThat(allAccounts.get(1).getAddress()) + .hasValue(accountAIsFirst ? accountB.getAddress() : accountA.getAddress()); + + // Get second account + final Bytes32 startHashForSecondAccount = UInt256.fromBytes(startHash).add(1L); + final List secondAccount = + worldState.streamAccounts(startHashForSecondAccount, 100).toList(); + assertThat(secondAccount).hasSize(1); + assertThat(secondAccount.get(0).getAddress()) + .hasValue(accountAIsFirst ? accountB.getAddress() : accountA.getAddress()); + } + + @Test + void commitAndPersist() { + final KeyValueStorage storage = new InMemoryKeyValueStorage(); + final ForestWorldStateKeyValueStorage kvWorldStateStorage = + new ForestWorldStateKeyValueStorage(storage); + final MutableWorldState worldState = createEmpty(kvWorldStateStorage); + final WorldUpdater updater = worldState.updater(); + final Wei newBalance = Wei.of(100000); + final Hash expectedRootHash = + Hash.fromHexString("0xa3e1c133a5a51b03399ed9ad0380f3182e9e18322f232b816dd4b9094f871e1b"); + + // Update account and assert we get the expected response from updater + updater.createAccount(ADDRESS).setBalance(newBalance); + assertThat(updater.get(ADDRESS)).isNotNull(); + assertThat(updater.get(ADDRESS).getBalance()).isEqualTo(newBalance); + + // Commit and check assertions + updater.commit(); + assertThat(worldState.rootHash()).isEqualTo(expectedRootHash); + assertThat(worldState.get(ADDRESS)).isNotNull(); + assertThat(worldState.get(ADDRESS).getBalance()).isEqualTo(newBalance); + + // Check that storage is empty before persisting + assertThat(kvWorldStateStorage.isWorldStateAvailable(worldState.rootHash())).isFalse(); + + // Persist and re-run assertions + worldState.persist(null); + + assertThat(kvWorldStateStorage.isWorldStateAvailable(worldState.rootHash())).isTrue(); + assertThat(worldState.rootHash()).isEqualTo(expectedRootHash); + assertThat(worldState.get(ADDRESS)).isNotNull(); + assertThat(worldState.get(ADDRESS).getBalance()).isEqualTo(newBalance); + + // Create new world state and check that it can access modified address + final MutableWorldState newWorldState = + new ForestMutableWorldState( + expectedRootHash, + new ForestWorldStateKeyValueStorage(storage), + new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage()), + EvmConfiguration.DEFAULT); + assertThat(newWorldState.rootHash()).isEqualTo(expectedRootHash); + assertThat(newWorldState.get(ADDRESS)).isNotNull(); + assertThat(newWorldState.get(ADDRESS).getBalance()).isEqualTo(newBalance); + } + + @Test + void getAccountNonce_AccountExists() { + final MutableWorldState worldState = createEmpty(); + final WorldUpdater updater = worldState.updater(); + updater.createAccount(ADDRESS).setNonce(1L); + updater.commit(); + assertThat(worldState.get(ADDRESS).getNonce()).isEqualTo(1L); + assertThat(worldState.rootHash()) + .isEqualTo( + Hash.fromHexString( + "0x9648b05cc2eef5513ae2edfe16bfcedb3d1c60ffb5dff3fc501bd3e4ae39f536")); + } + + @Test + void replaceAccountNonce() { + final MutableWorldState worldState = createEmpty(); + final WorldUpdater updater = worldState.updater(); + final MutableAccount account = updater.createAccount(ADDRESS); + account.setNonce(1L); + account.setNonce(2L); + updater.commit(); + assertThat(worldState.get(ADDRESS).getNonce()).isEqualTo(2L); + assertThat(worldState.rootHash()) + .isEqualTo( + Hash.fromHexString( + "0x7f64d13e61301a5154a5f06483a38572629e977b316cbe5a28b5f0522010a4bf")); + } + + @Test + void getAccountBalance_AccountExists() { + final MutableWorldState worldState = createEmpty(); + final WorldUpdater updater = worldState.updater(); + updater.createAccount(ADDRESS).setBalance(Wei.of(100000)); + updater.commit(); + assertThat(worldState.get(ADDRESS).getBalance()).isEqualTo(Wei.of(100000)); + } + + @Test + void replaceAccountBalance() { + final MutableWorldState worldState = createEmpty(); + final WorldUpdater updater = worldState.updater(); + final MutableAccount account = updater.createAccount(ADDRESS); + account.setBalance(Wei.of(100000)); + account.setBalance(Wei.of(200000)); + updater.commit(); + assertThat(worldState.get(ADDRESS).getBalance()).isEqualTo(Wei.of(200000)); + assertThat(worldState.rootHash()) + .isEqualTo( + Hash.fromHexString( + "0xbfa4e0598cc2b810a8ccc4a2d9a4c575574d05c9c4a7f915e6b8545953a5051e")); + } + + @Test + void setStorageValue_ZeroValue() { + final MutableWorldState worldState = createEmpty(); + final WorldUpdater updater = worldState.updater(); + final MutableAccount account = updater.createAccount(ADDRESS); + account.setBalance(Wei.of(100000)); + account.setStorageValue(UInt256.ZERO, UInt256.ZERO); + updater.commit(); + assertThat(worldState.get(ADDRESS).getStorageValue(UInt256.ZERO)).isEqualTo(UInt256.ZERO); + assertThat(worldState.rootHash()) + .isEqualTo( + Hash.fromHexString( + "0xa3e1c133a5a51b03399ed9ad0380f3182e9e18322f232b816dd4b9094f871e1b")); + } + + @Test + void setStorageValue_NonzeroValue() { + final MutableWorldState worldState = createEmpty(); + final WorldUpdater updater = worldState.updater(); + final MutableAccount account = updater.createAccount(ADDRESS); + account.setBalance(Wei.of(100000)); + account.setStorageValue(UInt256.ONE, UInt256.valueOf(2)); + updater.commit(); + assertThat(worldState.get(ADDRESS).getStorageValue(UInt256.ONE)).isEqualTo(UInt256.valueOf(2)); + assertThat(worldState.rootHash()) + .isEqualTo( + Hash.fromHexString( + "0xd31ce0bf3bf8790083a8ebde418244fda3b1cca952d7119ed244f86d03044656")); + } + + @Test + void replaceStorageValue_NonzeroValue() { + final MutableWorldState worldState = createEmpty(); + final WorldUpdater updater = worldState.updater(); + final MutableAccount account = updater.createAccount(ADDRESS); + account.setBalance(Wei.of(100000)); + account.setStorageValue(UInt256.ONE, UInt256.valueOf(2)); + account.setStorageValue(UInt256.ONE, UInt256.valueOf(3)); + updater.commit(); + assertThat(worldState.get(ADDRESS).getStorageValue(UInt256.ONE)).isEqualTo(UInt256.valueOf(3)); + assertThat(worldState.rootHash()) + .isEqualTo( + Hash.fromHexString( + "0x1d0ddb5079fe5b8689124b68c9e5bb3f4d8e13c2f7489d24f088c78fd45e058d")); + } + + @Test + void replaceStorageValue_ZeroValue() { + final MutableWorldState worldState = createEmpty(); + final WorldUpdater updater = worldState.updater(); + final MutableAccount account = updater.createAccount(ADDRESS); + account.setBalance(Wei.of(100000)); + account.setStorageValue(UInt256.ONE, UInt256.valueOf(2)); + account.setStorageValue(UInt256.ONE, UInt256.ZERO); + updater.commit(); + assertThat(worldState.rootHash()) + .isEqualTo( + Hash.fromHexString( + "0xa3e1c133a5a51b03399ed9ad0380f3182e9e18322f232b816dd4b9094f871e1b")); + } + + @Test + void getOriginalStorageValue() { + final MutableWorldState worldState = createEmpty(); + final WorldUpdater setupUpdater = worldState.updater(); + final MutableAccount setupAccount = setupUpdater.createAccount(ADDRESS); + setupAccount.setStorageValue(UInt256.ONE, UInt256.valueOf(2)); + setupUpdater.commit(); + + final WorldUpdater updater = worldState.updater(); + final MutableAccount account = updater.getOrCreate(ADDRESS); + assertThat(account.getOriginalStorageValue(UInt256.ONE)).isEqualTo(UInt256.valueOf(2)); + + account.setStorageValue(UInt256.ONE, UInt256.valueOf(3)); + assertThat(account.getOriginalStorageValue(UInt256.ONE)).isEqualTo(UInt256.valueOf(2)); + } + + @Test + void originalStorageValueIsAlwaysZeroIfStorageWasCleared() { + final MutableWorldState worldState = createEmpty(); + final WorldUpdater setupUpdater = worldState.updater(); + final MutableAccount setupAccount = setupUpdater.createAccount(ADDRESS); + setupAccount.setStorageValue(UInt256.ONE, UInt256.valueOf(2)); + setupUpdater.commit(); + + final WorldUpdater updater = worldState.updater(); + final MutableAccount account = updater.getOrCreate(ADDRESS); + + account.clearStorage(); + assertThat(account.getOriginalStorageValue(UInt256.ONE)).isEqualTo(UInt256.ZERO); + } + + @Test + void clearStorage() { + final UInt256 storageKey = UInt256.ONE; + final UInt256 storageValue = UInt256.valueOf(2L); + + // Create a world state with one account + final MutableWorldState worldState = createEmpty(); + final WorldUpdater updater = worldState.updater(); + MutableAccount account = updater.createAccount(ADDRESS); + account.setBalance(Wei.of(100000)); + account.setStorageValue(storageKey, storageValue); + assertThat(account.getStorageValue(storageKey)).isEqualTo(storageValue); + + // Clear storage + account = updater.getAccount(ADDRESS); + assertThat(account).isNotNull(); + assertThat(account.getStorageValue(storageKey)).isEqualTo(storageValue); + account.clearStorage(); + assertThat(account.getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); + assertThat(updater.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); + + // Check storage is cleared after committing + updater.commit(); + assertThat(updater.getAccount(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); + assertThat(updater.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); + assertThat(worldState.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); + + // And after persisting + assertThat(updater.getAccount(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); + assertThat(updater.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); + assertThat(worldState.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); + } + + @Test + void clearStorage_AfterPersisting() { + final UInt256 storageKey = UInt256.ONE; + final UInt256 storageValue = UInt256.valueOf(2L); + + // Create a world state with one account + final MutableWorldState worldState = createEmpty(); + final WorldUpdater updater = worldState.updater(); + MutableAccount account = updater.createAccount(ADDRESS); + account.setBalance(Wei.of(100000)); + account.setStorageValue(storageKey, storageValue); + updater.commit(); + worldState.persist(null); + assertThat(worldState.get(ADDRESS)).isNotNull(); + assertThat(worldState.rootHash()).isNotEqualTo(MerkleTrie.EMPTY_TRIE_NODE_HASH); + + // Clear storage + account = updater.getAccount(ADDRESS); + assertThat(account).isNotNull(); + assertThat(account.getStorageValue(storageKey)).isEqualTo(storageValue); + account.clearStorage(); + assertThat(account.getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); + assertThat(updater.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); + assertThat(worldState.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(storageValue); + + // Check storage is cleared after committing + updater.commit(); + assertThat(updater.getAccount(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); + assertThat(updater.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); + assertThat(worldState.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); + + // And after persisting + assertThat(updater.getAccount(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); + assertThat(updater.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); + assertThat(worldState.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); + } + + @Test + void clearStorageThenEdit() { + final UInt256 storageKey = UInt256.ONE; + final UInt256 originalStorageValue = UInt256.valueOf(2L); + final UInt256 newStorageValue = UInt256.valueOf(3L); + + // Create a world state with one account + final MutableWorldState worldState = createEmpty(); + final WorldUpdater updater = worldState.updater(); + MutableAccount account = updater.createAccount(ADDRESS); + account.setBalance(Wei.of(100000)); + account.setStorageValue(storageKey, originalStorageValue); + assertThat(account.getStorageValue(storageKey)).isEqualTo(originalStorageValue); + + // Clear storage then edit + account = updater.getAccount(ADDRESS); + assertThat(account).isNotNull(); + assertThat(account.getStorageValue(storageKey)).isEqualTo(originalStorageValue); + assertThat(updater.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(originalStorageValue); + account.clearStorage(); + account.setStorageValue(storageKey, newStorageValue); + assertThat(account.getStorageValue(storageKey)).isEqualTo(newStorageValue); + + // Check storage is cleared after committing + updater.commit(); + assertThat(updater.getAccount(ADDRESS).getStorageValue(storageKey)).isEqualTo(newStorageValue); + assertThat(updater.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(newStorageValue); + assertThat(worldState.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(newStorageValue); + + // And after persisting + assertThat(updater.getAccount(ADDRESS).getStorageValue(storageKey)).isEqualTo(newStorageValue); + assertThat(updater.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(newStorageValue); + assertThat(worldState.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(newStorageValue); + } + + @Test + void clearStorageThenEditAfterPersisting() { + final UInt256 storageKey = UInt256.ONE; + final UInt256 originalStorageValue = UInt256.valueOf(2L); + final UInt256 newStorageValue = UInt256.valueOf(3L); + + // Create a world state with one account + final MutableWorldState worldState = createEmpty(); + final WorldUpdater updater = worldState.updater(); + MutableAccount account = updater.createAccount(ADDRESS); + account.setBalance(Wei.of(100000)); + account.setStorageValue(storageKey, originalStorageValue); + assertThat(account.getStorageValue(storageKey)).isEqualTo(originalStorageValue); + updater.commit(); + worldState.persist(null); + + // Clear storage then edit + account = updater.getAccount(ADDRESS); + assertThat(account).isNotNull(); + assertThat(account.getStorageValue(storageKey)).isEqualTo(originalStorageValue); + assertThat(updater.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(originalStorageValue); + account.clearStorage(); + account.setStorageValue(storageKey, newStorageValue); + assertThat(account.getStorageValue(storageKey)).isEqualTo(newStorageValue); + assertThat(worldState.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(originalStorageValue); + + // Check storage is cleared after committing + updater.commit(); + assertThat(updater.getAccount(ADDRESS).getStorageValue(storageKey)).isEqualTo(newStorageValue); + assertThat(updater.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(newStorageValue); + assertThat(worldState.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(newStorageValue); + + // And after persisting + assertThat(updater.getAccount(ADDRESS).getStorageValue(storageKey)).isEqualTo(newStorageValue); + assertThat(updater.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(newStorageValue); + assertThat(worldState.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(newStorageValue); + } + + @Test + void replaceAccountCode() { + final MutableWorldState worldState = createEmpty(); + final WorldUpdater updater = worldState.updater(); + final MutableAccount account = updater.createAccount(ADDRESS); + account.setBalance(Wei.of(100000)); + account.setCode(Bytes.of(1, 2, 3)); + account.setCode(Bytes.of(3, 2, 1)); + updater.commit(); + assertThat(worldState.get(ADDRESS).getCode()).isEqualTo(Bytes.of(3, 2, 1)); + assertThat(worldState.rootHash()) + .isEqualTo( + Hash.fromHexString( + "0xc14f5e30581de9155ea092affa665fad83bcd9f98e45c4a42885b9b36d939702")); + } + + @Test + void revert() { + final MutableWorldState worldState = createEmpty(); + final WorldUpdater updater1 = worldState.updater(); + final MutableAccount account1 = updater1.createAccount(ADDRESS); + account1.setBalance(Wei.of(200000)); + updater1.commit(); + + final WorldUpdater updater2 = worldState.updater(); + final MutableAccount account2 = updater2.getAccount(ADDRESS); + account2.setBalance(Wei.of(300000)); + assertThat(updater2.get(ADDRESS).getBalance()).isEqualTo(Wei.of(300000)); + + updater2.revert(); + assertThat(updater2.get(ADDRESS).getBalance()).isEqualTo(Wei.of(200000)); + + updater2.commit(); + assertThat(worldState.get(ADDRESS).getBalance()).isEqualTo(Wei.of(200000)); + + assertThat(worldState.rootHash()) + .isEqualTo( + Hash.fromHexString( + "0xbfa4e0598cc2b810a8ccc4a2d9a4c575574d05c9c4a7f915e6b8545953a5051e")); + } + + @Test + void shouldReturnNullForGetMutableWhenAccountDeletedInAncestor() { + final MutableWorldState worldState = createEmpty(); + final WorldUpdater updater1 = worldState.updater(); + final MutableAccount account1 = updater1.createAccount(ADDRESS); + updater1.commit(); + assertThat(updater1.get(ADDRESS)) + .isEqualToComparingOnlyGivenFields(account1, "address", "nonce", "balance", "codeHash"); + updater1.deleteAccount(ADDRESS); + + final WorldUpdater updater2 = updater1.updater(); + assertThat(updater2.get(ADDRESS)).isNull(); + + final WorldUpdater updater3 = updater2.updater(); + assertThat(updater3.getAccount(ADDRESS)).isNull(); + } + + @Test + void shouldCombineUnchangedAndChangedValuesWhenRetrievingStorageEntries() { + final MutableWorldState worldState = createEmpty(); + WorldUpdater updater = worldState.updater(); + MutableAccount account = updater.createAccount(ADDRESS); + account.setBalance(Wei.of(100000)); + account.setStorageValue(UInt256.ONE, UInt256.valueOf(2)); + account.setStorageValue(UInt256.valueOf(2), UInt256.valueOf(5)); + updater.commit(); + + final List initialSetOfEntries = new ArrayList<>(); + initialSetOfEntries.add(AccountStorageEntry.forKeyAndValue(UInt256.ONE, UInt256.valueOf(2))); + initialSetOfEntries.add( + AccountStorageEntry.forKeyAndValue(UInt256.valueOf(2), UInt256.valueOf(5))); + final Map initialEntries = new TreeMap<>(); + initialSetOfEntries.forEach(entry -> initialEntries.put(entry.getKeyHash(), entry)); + + updater = worldState.updater(); + account = updater.getAccount(ADDRESS); + account.setStorageValue(UInt256.ONE, UInt256.valueOf(3)); + account.setStorageValue(UInt256.valueOf(3), UInt256.valueOf(6)); + + final List finalSetOfEntries = new ArrayList<>(); + finalSetOfEntries.add(AccountStorageEntry.forKeyAndValue(UInt256.ONE, UInt256.valueOf(3))); + finalSetOfEntries.add( + AccountStorageEntry.forKeyAndValue(UInt256.valueOf(2), UInt256.valueOf(5))); + finalSetOfEntries.add( + AccountStorageEntry.forKeyAndValue(UInt256.valueOf(3), UInt256.valueOf(6))); + final Map finalEntries = new TreeMap<>(); + finalSetOfEntries.forEach(entry -> finalEntries.put(entry.getKeyHash(), entry)); + + assertThat(account.storageEntriesFrom(Hash.ZERO, 10)).isEqualTo(finalEntries); + assertThat(updater.get(ADDRESS).storageEntriesFrom(Hash.ZERO, 10)).isEqualTo(finalEntries); + assertThat(worldState.get(ADDRESS).storageEntriesFrom(Hash.ZERO, 10)).isEqualTo(initialEntries); + + worldState.persist(null); + assertThat(updater.get(ADDRESS).storageEntriesFrom(Hash.ZERO, 10)).isEqualTo(finalEntries); + assertThat(worldState.get(ADDRESS).storageEntriesFrom(Hash.ZERO, 10)).isEqualTo(initialEntries); + + updater.commit(); + assertThat(worldState.get(ADDRESS).storageEntriesFrom(Hash.ZERO, 10)).isEqualTo(finalEntries); + + worldState.persist(null); + assertThat(worldState.get(ADDRESS).storageEntriesFrom(Hash.ZERO, 10)).isEqualTo(finalEntries); + } +} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/util/BlockchainUtilParameterizedTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/util/BlockchainUtilParameterizedTest.java index e4f57803092..af673d10d53 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/util/BlockchainUtilParameterizedTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/util/BlockchainUtilParameterizedTest.java @@ -33,6 +33,7 @@ import java.util.stream.Stream; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -131,4 +132,11 @@ public void searchesDescending(final int commonAncestorHeight) { assertThat(maybeAncestorNumber.getAsInt()) .isEqualTo(Math.toIntExact(chainHeight - commonHeader.getNumber())); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/util/RawBlockIteratorTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/util/RawBlockIteratorTest.java index 7581fe7f75e..408377b7178 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/util/RawBlockIteratorTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/util/RawBlockIteratorTest.java @@ -38,7 +38,7 @@ public class RawBlockIteratorTest { - @TempDir private static Path tmp; + @TempDir private Path tmp; private BlockDataGenerator gen; @BeforeEach diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/CachingBlockHashLookupTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/CachingBlockHashLookupTest.java index 194009e7057..9ea4219a1ac 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/CachingBlockHashLookupTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/CachingBlockHashLookupTest.java @@ -26,6 +26,7 @@ import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; import java.util.Optional; @@ -37,7 +38,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -public class CachingBlockHashLookupTest { +class CachingBlockHashLookupTest { private static final int CURRENT_BLOCK_NUMBER = 256; private final Blockchain blockchain = mock(Blockchain.class); @@ -45,7 +46,7 @@ public class CachingBlockHashLookupTest { private BlockHashLookup lookup; @BeforeEach - public void setUp() { + void setUp() { BlockHeader parentHeader = null; for (int i = 0; i < headers.length; i++) { final BlockHeader header = createHeader(i, parentHeader); @@ -59,35 +60,35 @@ public void setUp() { } @AfterEach - public void verifyBlocksNeverLookedUpByNumber() { + void verifyBlocksNeverLookedUpByNumber() { // Looking up the block by number is incorrect because it always uses the canonical chain even // if the block being imported is on a fork. verify(blockchain, never()).getBlockHeader(anyLong()); } @Test - public void shouldGetHashOfImmediateParent() { + void shouldGetHashOfImmediateParent() { assertHashForBlockNumber(CURRENT_BLOCK_NUMBER - 1); } @Test - public void shouldGetHashOfGenesisBlock() { + void shouldGetHashOfGenesisBlock() { assertHashForBlockNumber(0); } @Test - public void shouldGetHashForRecentBlockAfterOlderBlock() { + void shouldGetHashForRecentBlockAfterOlderBlock() { assertHashForBlockNumber(10); assertHashForBlockNumber(CURRENT_BLOCK_NUMBER - 1); } @Test - public void shouldReturnEmptyHashWhenRequestedBlockNotOnchain() { + void shouldReturnEmptyHashWhenRequestedBlockNotOnchain() { Assertions.assertThat(lookup.apply(CURRENT_BLOCK_NUMBER + 20L)).isEqualTo(Hash.ZERO); } @Test - public void shouldReturnEmptyHashWhenParentBlockNotOnchain() { + void shouldReturnEmptyHashWhenParentBlockNotOnchain() { final BlockHashLookup lookupWithUnavailableParent = new CachingBlockHashLookup( new BlockHeaderTestFixture().number(CURRENT_BLOCK_NUMBER + 20).buildHeader(), @@ -97,13 +98,13 @@ public void shouldReturnEmptyHashWhenParentBlockNotOnchain() { } @Test - public void shouldGetParentHashFromCurrentBlock() { + void shouldGetParentHashFromCurrentBlock() { assertHashForBlockNumber(CURRENT_BLOCK_NUMBER - 1); verifyNoInteractions(blockchain); } @Test - public void shouldCacheBlockHashesWhileIteratingBackToPreviousHeader() { + void shouldCacheBlockHashesWhileIteratingBackToPreviousHeader() { assertHashForBlockNumber(CURRENT_BLOCK_NUMBER - 4); assertHashForBlockNumber(CURRENT_BLOCK_NUMBER - 1); verify(blockchain).getBlockHeader(headers[CURRENT_BLOCK_NUMBER - 1].getHash()); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java index 146213722b5..2a1bb23a097 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java @@ -29,12 +29,15 @@ import org.hyperledger.besu.evm.account.MutableAccount; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator; import org.hyperledger.besu.evm.operation.AbstractOperation; +import org.hyperledger.besu.evm.operation.CallOperation; import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.operation.Operation.OperationResult; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.util.Map; +import java.util.OptionalLong; import java.util.TreeMap; import org.apache.tuweni.bytes.Bytes32; @@ -63,6 +66,8 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { } }; + private final CallOperation callOperation = new CallOperation(new CancunGasCalculator()); + @Test void shouldRecordProgramCounter() { final MessageFrame frame = validMessageFrame(); @@ -108,7 +113,7 @@ void shouldRecordStackWhenEnabled() { frame.pushStackItem(stackItem1); frame.pushStackItem(stackItem2); frame.pushStackItem(stackItem3); - final TraceFrame traceFrame = traceFrame(frame, new TraceOptions(false, false, true)); + final TraceFrame traceFrame = traceFrame(frame, new TraceOptions(false, false, true), false); assertThat(traceFrame.getStack()).isPresent(); assertThat(traceFrame.getStack().get()).containsExactly(stackItem1, stackItem2, stackItem3); } @@ -116,7 +121,7 @@ void shouldRecordStackWhenEnabled() { @Test void shouldNotRecordStackWhenDisabled() { final TraceFrame traceFrame = - traceFrame(validMessageFrame(), new TraceOptions(false, false, false)); + traceFrame(validMessageFrame(), new TraceOptions(false, false, false), false); assertThat(traceFrame.getStack()).isEmpty(); } @@ -129,7 +134,7 @@ void shouldRecordMemoryWhenEnabled() { frame.writeMemory(0, 32, word1); frame.writeMemory(32, 32, word2); frame.writeMemory(64, 32, word3); - final TraceFrame traceFrame = traceFrame(frame, new TraceOptions(false, true, false)); + final TraceFrame traceFrame = traceFrame(frame, new TraceOptions(false, true, false), false); assertThat(traceFrame.getMemory()).isPresent(); assertThat(traceFrame.getMemory().get()).containsExactly(word1, word2, word3); } @@ -137,7 +142,7 @@ void shouldRecordMemoryWhenEnabled() { @Test void shouldNotRecordMemoryWhenDisabled() { final TraceFrame traceFrame = - traceFrame(validMessageFrame(), new TraceOptions(false, false, false)); + traceFrame(validMessageFrame(), new TraceOptions(false, false, false), false); assertThat(traceFrame.getMemory()).isEmpty(); } @@ -145,7 +150,7 @@ void shouldNotRecordMemoryWhenDisabled() { void shouldRecordStorageWhenEnabled() { final MessageFrame frame = validMessageFrame(); final Map updatedStorage = setupStorageForCapture(frame); - final TraceFrame traceFrame = traceFrame(frame, new TraceOptions(true, false, false)); + final TraceFrame traceFrame = traceFrame(frame, new TraceOptions(true, false, false), false); assertThat(traceFrame.getStorage()).isPresent(); assertThat(traceFrame.getStorage()).contains(updatedStorage); } @@ -153,17 +158,41 @@ void shouldRecordStorageWhenEnabled() { @Test void shouldNotRecordStorageWhenDisabled() { final TraceFrame traceFrame = - traceFrame(validMessageFrame(), new TraceOptions(false, false, false)); + traceFrame(validMessageFrame(), new TraceOptions(false, false, false), false); assertThat(traceFrame.getStorage()).isEmpty(); } + @Test + void shouldNotAddGasWhenDisabled() { + final TraceFrame traceFrame = + traceFrame(validCallFrame(), new TraceOptions(false, false, false), false); + assertThat(traceFrame.getGasCost()).isEqualTo(OptionalLong.of(20)); + } + + @Test + void shouldAddGasWhenEnabled() { + final TraceFrame traceFrame = + traceFrame(validCallFrame(), new TraceOptions(false, false, false), true); + assertThat(traceFrame.getGasCost()).isEqualTo(OptionalLong.of(1020L)); + } + + @Test + void childGasFlagDoesNotMatterForNonCallOperations() { + final TraceFrame flagDisabledTracer = + traceFrame(validMessageFrame(), new TraceOptions(false, false, false), false); + final TraceFrame flagEnabledTracer = + traceFrame(validMessageFrame(), new TraceOptions(false, false, false), true); + + assertThat(flagEnabledTracer.getGasCost()).isEqualTo(flagDisabledTracer.getGasCost()); + } + @Test void shouldCaptureFrameWhenExceptionalHaltOccurs() { final MessageFrame frame = validMessageFrame(); final Map updatedStorage = setupStorageForCapture(frame); final DebugOperationTracer tracer = - new DebugOperationTracer(new TraceOptions(true, true, true)); + new DebugOperationTracer(new TraceOptions(true, true, true), false); tracer.tracePostExecution( frame, new OperationResult(50L, ExceptionalHaltReason.INSUFFICIENT_GAS)); @@ -174,11 +203,12 @@ void shouldCaptureFrameWhenExceptionalHaltOccurs() { } private TraceFrame traceFrame(final MessageFrame frame) { - return traceFrame(frame, new TraceOptions(false, false, false)); + return traceFrame(frame, new TraceOptions(false, false, false), false); } - private TraceFrame traceFrame(final MessageFrame frame, final TraceOptions traceOptions) { - final DebugOperationTracer tracer = new DebugOperationTracer(traceOptions); + private TraceFrame traceFrame( + final MessageFrame frame, final TraceOptions traceOptions, final boolean additionalCallGas) { + final DebugOperationTracer tracer = new DebugOperationTracer(traceOptions, additionalCallGas); tracer.tracePreExecution(frame); OperationResult operationResult = anOperation.execute(frame, null); tracer.tracePostExecution(frame, operationResult); @@ -192,6 +222,13 @@ private MessageFrame validMessageFrame() { return frame; } + private MessageFrame validCallFrame() { + final MessageFrame frame = validMessageFrameBuilder().build(); + frame.setCurrentOperation(callOperation); + frame.setPC(10); + return frame; + } + private TraceFrame getOnlyTraceFrame(final DebugOperationTracer tracer) { Assertions.assertThat(tracer.getTraceFrames()).hasSize(1); return tracer.getTraceFrames().get(0); diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutableWorldStateTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutableWorldStateTest.java deleted file mode 100644 index 4e423563416..00000000000 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/worldstate/DefaultMutableWorldStateTest.java +++ /dev/null @@ -1,677 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.worldstate; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryWorldState; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.ethereum.core.MutableWorldState; -import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStatePreimageKeyValueStorage; -import org.hyperledger.besu.ethereum.trie.MerkleTrie; -import org.hyperledger.besu.evm.account.AccountStorageEntry; -import org.hyperledger.besu.evm.account.MutableAccount; -import org.hyperledger.besu.evm.internal.EvmConfiguration; -import org.hyperledger.besu.evm.worldstate.WorldState; -import org.hyperledger.besu.evm.worldstate.WorldState.StreamableAccount; -import org.hyperledger.besu.evm.worldstate.WorldUpdater; -import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; -import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt256; -import org.junit.jupiter.api.Test; - -// TODO: make that an abstract mutable world state test, and create sub-class for all world state -// implementations. -class DefaultMutableWorldStateTest { - // The following test cases are loosely derived from the testTransactionToItself - // GeneralStateReferenceTest. - - private static final Address ADDRESS = - Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"); - - private static MutableWorldState createEmpty(final WorldStateKeyValueStorage storage) { - final WorldStatePreimageKeyValueStorage preimageStorage = - new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage()); - return new DefaultMutableWorldState(storage, preimageStorage, EvmConfiguration.DEFAULT); - } - - private static MutableWorldState createEmpty() { - return createInMemoryWorldState(); - } - - @Test - void rootHash_Empty() { - final MutableWorldState worldState = createEmpty(); - assertThat(worldState.rootHash()).isEqualTo(MerkleTrie.EMPTY_TRIE_NODE_HASH); - - worldState.persist(null); - assertThat(worldState.rootHash()).isEqualTo(MerkleTrie.EMPTY_TRIE_NODE_HASH); - } - - @Test - void containsAccount_AccountDoesNotExist() { - final WorldState worldState = createEmpty(); - assertThat(worldState.get(ADDRESS)).isNull(); - } - - @Test - void containsAccount_AccountExists() { - final MutableWorldState worldState = createEmpty(); - final WorldUpdater updater = worldState.updater(); - updater.createAccount(ADDRESS).setBalance(Wei.of(100000)); - updater.commit(); - assertThat(worldState.get(ADDRESS)).isNotNull(); - assertThat(worldState.rootHash()) - .isEqualTo( - Hash.fromHexString( - "0xa3e1c133a5a51b03399ed9ad0380f3182e9e18322f232b816dd4b9094f871e1b")); - } - - @Test - void removeAccount_AccountDoesNotExist() { - final MutableWorldState worldState = createEmpty(); - final WorldUpdater updater = worldState.updater(); - updater.deleteAccount(ADDRESS); - updater.commit(); - assertThat(worldState.rootHash()).isEqualTo(MerkleTrie.EMPTY_TRIE_NODE_HASH); - - worldState.persist(null); - assertThat(worldState.rootHash()).isEqualTo(MerkleTrie.EMPTY_TRIE_NODE_HASH); - } - - @Test - void removeAccount_UpdatedAccount() { - final MutableWorldState worldState = createEmpty(); - final WorldUpdater updater = worldState.updater(); - updater.createAccount(ADDRESS).setBalance(Wei.of(100000)); - updater.deleteAccount(ADDRESS); - updater.commit(); - assertThat(worldState.rootHash()).isEqualTo(MerkleTrie.EMPTY_TRIE_NODE_HASH); - - worldState.persist(null); - assertThat(worldState.rootHash()).isEqualTo(MerkleTrie.EMPTY_TRIE_NODE_HASH); - } - - @Test - void removeAccount_AccountExists() { - // Create a world state with one account - final MutableWorldState worldState = createEmpty(); - WorldUpdater updater = worldState.updater(); - updater.createAccount(ADDRESS).setBalance(Wei.of(100000)); - updater.commit(); - assertThat(worldState.get(ADDRESS)).isNotNull(); - assertThat(worldState.rootHash()).isNotEqualTo(MerkleTrie.EMPTY_TRIE_NODE_HASH); - - // Delete account - updater = worldState.updater(); - updater.deleteAccount(ADDRESS); - assertThat(updater.get(ADDRESS)).isNull(); - assertThat(updater.getAccount(ADDRESS)).isNull(); - updater.commit(); - assertThat(updater.get(ADDRESS)).isNull(); - - assertThat(worldState.rootHash()).isEqualTo(MerkleTrie.EMPTY_TRIE_NODE_HASH); - } - - @Test - void removeAccount_AccountExistsAndIsPersisted() { - // Create a world state with one account - final MutableWorldState worldState = createEmpty(); - WorldUpdater updater = worldState.updater(); - updater.createAccount(ADDRESS).setBalance(Wei.of(100000)); - updater.commit(); - worldState.persist(null); - assertThat(worldState.get(ADDRESS)).isNotNull(); - assertThat(worldState.rootHash()).isNotEqualTo(MerkleTrie.EMPTY_TRIE_NODE_HASH); - - // Delete account - updater = worldState.updater(); - updater.deleteAccount(ADDRESS); - assertThat(updater.get(ADDRESS)).isNull(); - assertThat(updater.getAccount(ADDRESS)).isNull(); - // Check account is gone after committing - updater.commit(); - assertThat(updater.get(ADDRESS)).isNull(); - // And after persisting - worldState.persist(null); - assertThat(updater.get(ADDRESS)).isNull(); - - assertThat(worldState.rootHash()).isEqualTo(MerkleTrie.EMPTY_TRIE_NODE_HASH); - } - - @Test - void streamAccounts_empty() { - final MutableWorldState worldState = createEmpty(); - final Stream accounts = worldState.streamAccounts(Bytes32.ZERO, 10); - assertThat(accounts.count()).isZero(); - } - - @Test - void streamAccounts_singleAccount() { - final MutableWorldState worldState = createEmpty(); - final WorldUpdater updater = worldState.updater(); - updater.createAccount(ADDRESS).setBalance(Wei.of(100000)); - updater.commit(); - - List accounts = - worldState.streamAccounts(Bytes32.ZERO, 10).collect(Collectors.toList()); - assertThat(accounts).hasSize(1); - assertThat(accounts.get(0).getAddress()).hasValue(ADDRESS); - assertThat(accounts.get(0).getBalance()).isEqualTo(Wei.of(100000)); - - // Check again after persisting - worldState.persist(null); - accounts = worldState.streamAccounts(Bytes32.ZERO, 10).collect(Collectors.toList()); - assertThat(accounts).hasSize(1); - assertThat(accounts.get(0).getAddress()).hasValue(ADDRESS); - assertThat(accounts.get(0).getBalance()).isEqualTo(Wei.of(100000)); - } - - @Test - void streamAccounts_multipleAccounts() { - final Address addr1 = Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"); - final Address addr2 = Address.fromHexString("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0c"); - - final MutableWorldState worldState = createEmpty(); - final WorldUpdater updater = worldState.updater(); - - // Create an account - final MutableAccount accountA = updater.createAccount(addr1); - accountA.setBalance(Wei.of(100000)); - // Create another - final MutableAccount accountB = updater.createAccount(addr2); - accountB.setNonce(1); - // Commit changes - updater.commit(); - - final boolean accountAIsFirst = - accountA - .getAddressHash() - .toUnsignedBigInteger() - .compareTo(accountB.getAddressHash().toUnsignedBigInteger()) - < 0; - final Hash startHash = accountAIsFirst ? accountA.getAddressHash() : accountB.getAddressHash(); - - // Get first account - final List firstAccount = worldState.streamAccounts(startHash, 1).toList(); - assertThat(firstAccount).hasSize(1); - assertThat(firstAccount.get(0).getAddress()) - .hasValue(accountAIsFirst ? accountA.getAddress() : accountB.getAddress()); - - // Get both accounts - final List allAccounts = worldState.streamAccounts(Bytes32.ZERO, 2).toList(); - assertThat(allAccounts).hasSize(2); - assertThat(allAccounts.get(0).getAddress()) - .hasValue(accountAIsFirst ? accountA.getAddress() : accountB.getAddress()); - assertThat(allAccounts.get(1).getAddress()) - .hasValue(accountAIsFirst ? accountB.getAddress() : accountA.getAddress()); - - // Get second account - final Bytes32 startHashForSecondAccount = UInt256.fromBytes(startHash).add(1L); - final List secondAccount = - worldState.streamAccounts(startHashForSecondAccount, 100).toList(); - assertThat(secondAccount).hasSize(1); - assertThat(secondAccount.get(0).getAddress()) - .hasValue(accountAIsFirst ? accountB.getAddress() : accountA.getAddress()); - } - - @Test - void commitAndPersist() { - final KeyValueStorage storage = new InMemoryKeyValueStorage(); - final WorldStateKeyValueStorage kvWorldStateStorage = new WorldStateKeyValueStorage(storage); - final MutableWorldState worldState = createEmpty(kvWorldStateStorage); - final WorldUpdater updater = worldState.updater(); - final Wei newBalance = Wei.of(100000); - final Hash expectedRootHash = - Hash.fromHexString("0xa3e1c133a5a51b03399ed9ad0380f3182e9e18322f232b816dd4b9094f871e1b"); - - // Update account and assert we get the expected response from updater - updater.createAccount(ADDRESS).setBalance(newBalance); - assertThat(updater.get(ADDRESS)).isNotNull(); - assertThat(updater.get(ADDRESS).getBalance()).isEqualTo(newBalance); - - // Commit and check assertions - updater.commit(); - assertThat(worldState.rootHash()).isEqualTo(expectedRootHash); - assertThat(worldState.get(ADDRESS)).isNotNull(); - assertThat(worldState.get(ADDRESS).getBalance()).isEqualTo(newBalance); - - // Check that storage is empty before persisting - assertThat(kvWorldStateStorage.isWorldStateAvailable(worldState.rootHash(), null)).isFalse(); - - // Persist and re-run assertions - worldState.persist(null); - - assertThat(kvWorldStateStorage.isWorldStateAvailable(worldState.rootHash(), null)).isTrue(); - assertThat(worldState.rootHash()).isEqualTo(expectedRootHash); - assertThat(worldState.get(ADDRESS)).isNotNull(); - assertThat(worldState.get(ADDRESS).getBalance()).isEqualTo(newBalance); - - // Create new world state and check that it can access modified address - final MutableWorldState newWorldState = - new DefaultMutableWorldState( - expectedRootHash, - new WorldStateKeyValueStorage(storage), - new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage()), - EvmConfiguration.DEFAULT); - assertThat(newWorldState.rootHash()).isEqualTo(expectedRootHash); - assertThat(newWorldState.get(ADDRESS)).isNotNull(); - assertThat(newWorldState.get(ADDRESS).getBalance()).isEqualTo(newBalance); - } - - @Test - void getAccountNonce_AccountExists() { - final MutableWorldState worldState = createEmpty(); - final WorldUpdater updater = worldState.updater(); - updater.createAccount(ADDRESS).setNonce(1L); - updater.commit(); - assertThat(worldState.get(ADDRESS).getNonce()).isEqualTo(1L); - assertThat(worldState.rootHash()) - .isEqualTo( - Hash.fromHexString( - "0x9648b05cc2eef5513ae2edfe16bfcedb3d1c60ffb5dff3fc501bd3e4ae39f536")); - } - - @Test - void replaceAccountNonce() { - final MutableWorldState worldState = createEmpty(); - final WorldUpdater updater = worldState.updater(); - final MutableAccount account = updater.createAccount(ADDRESS); - account.setNonce(1L); - account.setNonce(2L); - updater.commit(); - assertThat(worldState.get(ADDRESS).getNonce()).isEqualTo(2L); - assertThat(worldState.rootHash()) - .isEqualTo( - Hash.fromHexString( - "0x7f64d13e61301a5154a5f06483a38572629e977b316cbe5a28b5f0522010a4bf")); - } - - @Test - void getAccountBalance_AccountExists() { - final MutableWorldState worldState = createEmpty(); - final WorldUpdater updater = worldState.updater(); - updater.createAccount(ADDRESS).setBalance(Wei.of(100000)); - updater.commit(); - assertThat(worldState.get(ADDRESS).getBalance()).isEqualTo(Wei.of(100000)); - } - - @Test - void replaceAccountBalance() { - final MutableWorldState worldState = createEmpty(); - final WorldUpdater updater = worldState.updater(); - final MutableAccount account = updater.createAccount(ADDRESS); - account.setBalance(Wei.of(100000)); - account.setBalance(Wei.of(200000)); - updater.commit(); - assertThat(worldState.get(ADDRESS).getBalance()).isEqualTo(Wei.of(200000)); - assertThat(worldState.rootHash()) - .isEqualTo( - Hash.fromHexString( - "0xbfa4e0598cc2b810a8ccc4a2d9a4c575574d05c9c4a7f915e6b8545953a5051e")); - } - - @Test - void setStorageValue_ZeroValue() { - final MutableWorldState worldState = createEmpty(); - final WorldUpdater updater = worldState.updater(); - final MutableAccount account = updater.createAccount(ADDRESS); - account.setBalance(Wei.of(100000)); - account.setStorageValue(UInt256.ZERO, UInt256.ZERO); - updater.commit(); - assertThat(worldState.get(ADDRESS).getStorageValue(UInt256.ZERO)).isEqualTo(UInt256.ZERO); - assertThat(worldState.rootHash()) - .isEqualTo( - Hash.fromHexString( - "0xa3e1c133a5a51b03399ed9ad0380f3182e9e18322f232b816dd4b9094f871e1b")); - } - - @Test - void setStorageValue_NonzeroValue() { - final MutableWorldState worldState = createEmpty(); - final WorldUpdater updater = worldState.updater(); - final MutableAccount account = updater.createAccount(ADDRESS); - account.setBalance(Wei.of(100000)); - account.setStorageValue(UInt256.ONE, UInt256.valueOf(2)); - updater.commit(); - assertThat(worldState.get(ADDRESS).getStorageValue(UInt256.ONE)).isEqualTo(UInt256.valueOf(2)); - assertThat(worldState.rootHash()) - .isEqualTo( - Hash.fromHexString( - "0xd31ce0bf3bf8790083a8ebde418244fda3b1cca952d7119ed244f86d03044656")); - } - - @Test - void replaceStorageValue_NonzeroValue() { - final MutableWorldState worldState = createEmpty(); - final WorldUpdater updater = worldState.updater(); - final MutableAccount account = updater.createAccount(ADDRESS); - account.setBalance(Wei.of(100000)); - account.setStorageValue(UInt256.ONE, UInt256.valueOf(2)); - account.setStorageValue(UInt256.ONE, UInt256.valueOf(3)); - updater.commit(); - assertThat(worldState.get(ADDRESS).getStorageValue(UInt256.ONE)).isEqualTo(UInt256.valueOf(3)); - assertThat(worldState.rootHash()) - .isEqualTo( - Hash.fromHexString( - "0x1d0ddb5079fe5b8689124b68c9e5bb3f4d8e13c2f7489d24f088c78fd45e058d")); - } - - @Test - void replaceStorageValue_ZeroValue() { - final MutableWorldState worldState = createEmpty(); - final WorldUpdater updater = worldState.updater(); - final MutableAccount account = updater.createAccount(ADDRESS); - account.setBalance(Wei.of(100000)); - account.setStorageValue(UInt256.ONE, UInt256.valueOf(2)); - account.setStorageValue(UInt256.ONE, UInt256.ZERO); - updater.commit(); - assertThat(worldState.rootHash()) - .isEqualTo( - Hash.fromHexString( - "0xa3e1c133a5a51b03399ed9ad0380f3182e9e18322f232b816dd4b9094f871e1b")); - } - - @Test - void getOriginalStorageValue() { - final MutableWorldState worldState = createEmpty(); - final WorldUpdater setupUpdater = worldState.updater(); - final MutableAccount setupAccount = setupUpdater.createAccount(ADDRESS); - setupAccount.setStorageValue(UInt256.ONE, UInt256.valueOf(2)); - setupUpdater.commit(); - - final WorldUpdater updater = worldState.updater(); - final MutableAccount account = updater.getOrCreate(ADDRESS); - assertThat(account.getOriginalStorageValue(UInt256.ONE)).isEqualTo(UInt256.valueOf(2)); - - account.setStorageValue(UInt256.ONE, UInt256.valueOf(3)); - assertThat(account.getOriginalStorageValue(UInt256.ONE)).isEqualTo(UInt256.valueOf(2)); - } - - @Test - void originalStorageValueIsAlwaysZeroIfStorageWasCleared() { - final MutableWorldState worldState = createEmpty(); - final WorldUpdater setupUpdater = worldState.updater(); - final MutableAccount setupAccount = setupUpdater.createAccount(ADDRESS); - setupAccount.setStorageValue(UInt256.ONE, UInt256.valueOf(2)); - setupUpdater.commit(); - - final WorldUpdater updater = worldState.updater(); - final MutableAccount account = updater.getOrCreate(ADDRESS); - - account.clearStorage(); - assertThat(account.getOriginalStorageValue(UInt256.ONE)).isEqualTo(UInt256.ZERO); - } - - @Test - void clearStorage() { - final UInt256 storageKey = UInt256.ONE; - final UInt256 storageValue = UInt256.valueOf(2L); - - // Create a world state with one account - final MutableWorldState worldState = createEmpty(); - final WorldUpdater updater = worldState.updater(); - MutableAccount account = updater.createAccount(ADDRESS); - account.setBalance(Wei.of(100000)); - account.setStorageValue(storageKey, storageValue); - assertThat(account.getStorageValue(storageKey)).isEqualTo(storageValue); - - // Clear storage - account = updater.getAccount(ADDRESS); - assertThat(account).isNotNull(); - assertThat(account.getStorageValue(storageKey)).isEqualTo(storageValue); - account.clearStorage(); - assertThat(account.getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); - assertThat(updater.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); - - // Check storage is cleared after committing - updater.commit(); - assertThat(updater.getAccount(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); - assertThat(updater.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); - assertThat(worldState.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); - - // And after persisting - assertThat(updater.getAccount(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); - assertThat(updater.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); - assertThat(worldState.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); - } - - @Test - void clearStorage_AfterPersisting() { - final UInt256 storageKey = UInt256.ONE; - final UInt256 storageValue = UInt256.valueOf(2L); - - // Create a world state with one account - final MutableWorldState worldState = createEmpty(); - final WorldUpdater updater = worldState.updater(); - MutableAccount account = updater.createAccount(ADDRESS); - account.setBalance(Wei.of(100000)); - account.setStorageValue(storageKey, storageValue); - updater.commit(); - worldState.persist(null); - assertThat(worldState.get(ADDRESS)).isNotNull(); - assertThat(worldState.rootHash()).isNotEqualTo(MerkleTrie.EMPTY_TRIE_NODE_HASH); - - // Clear storage - account = updater.getAccount(ADDRESS); - assertThat(account).isNotNull(); - assertThat(account.getStorageValue(storageKey)).isEqualTo(storageValue); - account.clearStorage(); - assertThat(account.getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); - assertThat(updater.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); - assertThat(worldState.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(storageValue); - - // Check storage is cleared after committing - updater.commit(); - assertThat(updater.getAccount(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); - assertThat(updater.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); - assertThat(worldState.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); - - // And after persisting - assertThat(updater.getAccount(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); - assertThat(updater.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); - assertThat(worldState.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(UInt256.ZERO); - } - - @Test - void clearStorageThenEdit() { - final UInt256 storageKey = UInt256.ONE; - final UInt256 originalStorageValue = UInt256.valueOf(2L); - final UInt256 newStorageValue = UInt256.valueOf(3L); - - // Create a world state with one account - final MutableWorldState worldState = createEmpty(); - final WorldUpdater updater = worldState.updater(); - MutableAccount account = updater.createAccount(ADDRESS); - account.setBalance(Wei.of(100000)); - account.setStorageValue(storageKey, originalStorageValue); - assertThat(account.getStorageValue(storageKey)).isEqualTo(originalStorageValue); - - // Clear storage then edit - account = updater.getAccount(ADDRESS); - assertThat(account).isNotNull(); - assertThat(account.getStorageValue(storageKey)).isEqualTo(originalStorageValue); - assertThat(updater.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(originalStorageValue); - account.clearStorage(); - account.setStorageValue(storageKey, newStorageValue); - assertThat(account.getStorageValue(storageKey)).isEqualTo(newStorageValue); - - // Check storage is cleared after committing - updater.commit(); - assertThat(updater.getAccount(ADDRESS).getStorageValue(storageKey)).isEqualTo(newStorageValue); - assertThat(updater.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(newStorageValue); - assertThat(worldState.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(newStorageValue); - - // And after persisting - assertThat(updater.getAccount(ADDRESS).getStorageValue(storageKey)).isEqualTo(newStorageValue); - assertThat(updater.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(newStorageValue); - assertThat(worldState.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(newStorageValue); - } - - @Test - void clearStorageThenEditAfterPersisting() { - final UInt256 storageKey = UInt256.ONE; - final UInt256 originalStorageValue = UInt256.valueOf(2L); - final UInt256 newStorageValue = UInt256.valueOf(3L); - - // Create a world state with one account - final MutableWorldState worldState = createEmpty(); - final WorldUpdater updater = worldState.updater(); - MutableAccount account = updater.createAccount(ADDRESS); - account.setBalance(Wei.of(100000)); - account.setStorageValue(storageKey, originalStorageValue); - assertThat(account.getStorageValue(storageKey)).isEqualTo(originalStorageValue); - updater.commit(); - worldState.persist(null); - - // Clear storage then edit - account = updater.getAccount(ADDRESS); - assertThat(account).isNotNull(); - assertThat(account.getStorageValue(storageKey)).isEqualTo(originalStorageValue); - assertThat(updater.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(originalStorageValue); - account.clearStorage(); - account.setStorageValue(storageKey, newStorageValue); - assertThat(account.getStorageValue(storageKey)).isEqualTo(newStorageValue); - assertThat(worldState.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(originalStorageValue); - - // Check storage is cleared after committing - updater.commit(); - assertThat(updater.getAccount(ADDRESS).getStorageValue(storageKey)).isEqualTo(newStorageValue); - assertThat(updater.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(newStorageValue); - assertThat(worldState.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(newStorageValue); - - // And after persisting - assertThat(updater.getAccount(ADDRESS).getStorageValue(storageKey)).isEqualTo(newStorageValue); - assertThat(updater.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(newStorageValue); - assertThat(worldState.get(ADDRESS).getStorageValue(storageKey)).isEqualTo(newStorageValue); - } - - @Test - void replaceAccountCode() { - final MutableWorldState worldState = createEmpty(); - final WorldUpdater updater = worldState.updater(); - final MutableAccount account = updater.createAccount(ADDRESS); - account.setBalance(Wei.of(100000)); - account.setCode(Bytes.of(1, 2, 3)); - account.setCode(Bytes.of(3, 2, 1)); - updater.commit(); - assertThat(worldState.get(ADDRESS).getCode()).isEqualTo(Bytes.of(3, 2, 1)); - assertThat(worldState.rootHash()) - .isEqualTo( - Hash.fromHexString( - "0xc14f5e30581de9155ea092affa665fad83bcd9f98e45c4a42885b9b36d939702")); - } - - @Test - void revert() { - final MutableWorldState worldState = createEmpty(); - final WorldUpdater updater1 = worldState.updater(); - final MutableAccount account1 = updater1.createAccount(ADDRESS); - account1.setBalance(Wei.of(200000)); - updater1.commit(); - - final WorldUpdater updater2 = worldState.updater(); - final MutableAccount account2 = updater2.getAccount(ADDRESS); - account2.setBalance(Wei.of(300000)); - assertThat(updater2.get(ADDRESS).getBalance()).isEqualTo(Wei.of(300000)); - - updater2.revert(); - assertThat(updater2.get(ADDRESS).getBalance()).isEqualTo(Wei.of(200000)); - - updater2.commit(); - assertThat(worldState.get(ADDRESS).getBalance()).isEqualTo(Wei.of(200000)); - - assertThat(worldState.rootHash()) - .isEqualTo( - Hash.fromHexString( - "0xbfa4e0598cc2b810a8ccc4a2d9a4c575574d05c9c4a7f915e6b8545953a5051e")); - } - - @Test - void shouldReturnNullForGetMutableWhenAccountDeletedInAncestor() { - final MutableWorldState worldState = createEmpty(); - final WorldUpdater updater1 = worldState.updater(); - final MutableAccount account1 = updater1.createAccount(ADDRESS); - updater1.commit(); - assertThat(updater1.get(ADDRESS)) - .isEqualToComparingOnlyGivenFields(account1, "address", "nonce", "balance", "codeHash"); - updater1.deleteAccount(ADDRESS); - - final WorldUpdater updater2 = updater1.updater(); - assertThat(updater2.get(ADDRESS)).isNull(); - - final WorldUpdater updater3 = updater2.updater(); - assertThat(updater3.getAccount(ADDRESS)).isNull(); - } - - @Test - void shouldCombineUnchangedAndChangedValuesWhenRetrievingStorageEntries() { - final MutableWorldState worldState = createEmpty(); - WorldUpdater updater = worldState.updater(); - MutableAccount account = updater.createAccount(ADDRESS); - account.setBalance(Wei.of(100000)); - account.setStorageValue(UInt256.ONE, UInt256.valueOf(2)); - account.setStorageValue(UInt256.valueOf(2), UInt256.valueOf(5)); - updater.commit(); - - final List initialSetOfEntries = new ArrayList<>(); - initialSetOfEntries.add(AccountStorageEntry.forKeyAndValue(UInt256.ONE, UInt256.valueOf(2))); - initialSetOfEntries.add( - AccountStorageEntry.forKeyAndValue(UInt256.valueOf(2), UInt256.valueOf(5))); - final Map initialEntries = new TreeMap<>(); - initialSetOfEntries.forEach(entry -> initialEntries.put(entry.getKeyHash(), entry)); - - updater = worldState.updater(); - account = updater.getAccount(ADDRESS); - account.setStorageValue(UInt256.ONE, UInt256.valueOf(3)); - account.setStorageValue(UInt256.valueOf(3), UInt256.valueOf(6)); - - final List finalSetOfEntries = new ArrayList<>(); - finalSetOfEntries.add(AccountStorageEntry.forKeyAndValue(UInt256.ONE, UInt256.valueOf(3))); - finalSetOfEntries.add( - AccountStorageEntry.forKeyAndValue(UInt256.valueOf(2), UInt256.valueOf(5))); - finalSetOfEntries.add( - AccountStorageEntry.forKeyAndValue(UInt256.valueOf(3), UInt256.valueOf(6))); - final Map finalEntries = new TreeMap<>(); - finalSetOfEntries.forEach(entry -> finalEntries.put(entry.getKeyHash(), entry)); - - assertThat(account.storageEntriesFrom(Hash.ZERO, 10)).isEqualTo(finalEntries); - assertThat(updater.get(ADDRESS).storageEntriesFrom(Hash.ZERO, 10)).isEqualTo(finalEntries); - assertThat(worldState.get(ADDRESS).storageEntriesFrom(Hash.ZERO, 10)).isEqualTo(initialEntries); - - worldState.persist(null); - assertThat(updater.get(ADDRESS).storageEntriesFrom(Hash.ZERO, 10)).isEqualTo(finalEntries); - assertThat(worldState.get(ADDRESS).storageEntriesFrom(Hash.ZERO, 10)).isEqualTo(initialEntries); - - updater.commit(); - assertThat(worldState.get(ADDRESS).storageEntriesFrom(Hash.ZERO, 10)).isEqualTo(finalEntries); - - worldState.persist(null); - assertThat(worldState.get(ADDRESS).storageEntriesFrom(Hash.ZERO, 10)).isEqualTo(finalEntries); - } -} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/worldstate/MarkSweepPrunerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/worldstate/MarkSweepPrunerTest.java deleted file mode 100644 index 28997591f73..00000000000 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/worldstate/MarkSweepPrunerTest.java +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.worldstate; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryBlockchain; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.spy; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.chain.MutableBlockchain; -import org.hyperledger.besu.ethereum.core.Block; -import org.hyperledger.besu.ethereum.core.BlockDataGenerator; -import org.hyperledger.besu.ethereum.core.BlockDataGenerator.BlockOptions; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.MutableWorldState; -import org.hyperledger.besu.ethereum.core.TransactionReceipt; -import org.hyperledger.besu.ethereum.rlp.RLP; -import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStatePreimageKeyValueStorage; -import org.hyperledger.besu.ethereum.trie.MerkleTrie; -import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; -import org.hyperledger.besu.evm.internal.EvmConfiguration; -import org.hyperledger.besu.evm.worldstate.WorldState; -import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; -import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InOrder; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -class MarkSweepPrunerTest { - - private final BlockDataGenerator gen = new BlockDataGenerator(); - private final NoOpMetricsSystem metricsSystem = new NoOpMetricsSystem(); - private final Map> hashValueStore = spy(new HashMap<>()); - private final InMemoryKeyValueStorage stateStorage = new TestInMemoryStorage(hashValueStore); - private final WorldStateStorage worldStateStorage = - spy(new WorldStateKeyValueStorage(stateStorage)); - private final WorldStateArchive worldStateArchive = - new DefaultWorldStateArchive( - worldStateStorage, - new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage()), - EvmConfiguration.DEFAULT); - private final InMemoryKeyValueStorage markStorage = new InMemoryKeyValueStorage(); - private final Block genesisBlock = gen.genesisBlock(); - private final MutableBlockchain blockchain = createInMemoryBlockchain(genesisBlock); - - @Test - void mark_marksAllExpectedNodes() { - final MarkSweepPruner pruner = - new MarkSweepPruner(worldStateStorage, blockchain, markStorage, metricsSystem); - - // Generate accounts and save corresponding state root - final int numBlocks = 15; - final int numAccounts = 10; - generateBlockchainData(numBlocks, numAccounts); - - final int markBlockNumber = 10; - final BlockHeader markBlock = blockchain.getBlockHeader(markBlockNumber).get(); - // Collect the nodes we expect to keep - final Set expectedNodes = collectWorldStateNodes(markBlock.getStateRoot()); - assertThat(hashValueStore).hasSizeGreaterThan(expectedNodes.size()); // Sanity check - - // Mark and sweep - pruner.mark(markBlock.getStateRoot()); - pruner.sweepBefore(markBlock.getNumber()); - - // Assert that the block we marked is still present and all accounts are accessible - assertThat(worldStateArchive.get(markBlock.getStateRoot(), markBlock.getHash())).isPresent(); - final WorldState markedState = - worldStateArchive.get(markBlock.getStateRoot(), markBlock.getHash()).get(); - // Traverse accounts and make sure all are accessible - final int expectedAccounts = numAccounts * markBlockNumber; - final long accounts = markedState.streamAccounts(Bytes32.ZERO, expectedAccounts * 2).count(); - assertThat(accounts).isEqualTo(expectedAccounts); - // Traverse storage to ensure that all storage is accessible - markedState - .streamAccounts(Bytes32.ZERO, expectedAccounts * 2) - .forEach(a -> a.storageEntriesFrom(Bytes32.ZERO, 1000)); - - // All other state roots should have been removed - for (int i = 0; i < numBlocks; i++) { - final BlockHeader curHeader = blockchain.getBlockHeader(i + 1L).get(); - if (curHeader.getNumber() == markBlock.getNumber()) { - continue; - } - assertThat(worldStateArchive.get(curHeader.getStateRoot(), curHeader.getHash())).isEmpty(); - } - - // Check that storage contains only the values we expect - assertThat(hashValueStore).hasSameSizeAs(expectedNodes); - assertThat(hashValueStore.values().stream().map(Optional::get)) - .containsExactlyInAnyOrderElementsOf( - expectedNodes.stream().map(Bytes::toArrayUnsafe).collect(Collectors.toSet())); - } - - @Test - void sweepBefore_shouldSweepStateRootFirst() { - final MarkSweepPruner pruner = - new MarkSweepPruner(worldStateStorage, blockchain, markStorage, metricsSystem); - - // Generate accounts and save corresponding state root - final int numBlocks = 15; - final int numAccounts = 10; - generateBlockchainData(numBlocks, numAccounts); - - final int markBlockNumber = 10; - final BlockHeader markBlock = blockchain.getBlockHeader(markBlockNumber).get(); - - // Collect state roots we expect to be swept first - final List stateRoots = new ArrayList<>(); - for (int i = markBlockNumber - 1; i >= 0; i--) { - stateRoots.add(blockchain.getBlockHeader(i).get().getStateRoot()); - } - - // Mark and sweep - pruner.mark(markBlock.getStateRoot()); - pruner.sweepBefore(markBlock.getNumber()); - - // we use individual inOrders because we only want to make sure we remove a state root before - // the full prune but without enforcing an ordering between the state root removals - stateRoots.forEach( - stateRoot -> { - final InOrder thisRootsOrdering = - inOrder(worldStateStorage, hashValueStore, worldStateStorage); - thisRootsOrdering.verify(worldStateStorage).isWorldStateAvailable(stateRoot, null); - thisRootsOrdering.verify(hashValueStore).keySet(); - thisRootsOrdering.verify(worldStateStorage).prune(any()); - }); - } - - @Test - void sweepBefore_shouldNotRemoveMarkedStateRoots() { - final MarkSweepPruner pruner = - new MarkSweepPruner(worldStateStorage, blockchain, markStorage, metricsSystem); - - // Generate accounts and save corresponding state root - final int numBlocks = 15; - final int numAccounts = 10; - generateBlockchainData(numBlocks, numAccounts); - - final int markBlockNumber = 10; - final BlockHeader markBlock = blockchain.getBlockHeader(markBlockNumber).get(); - - // Collect state roots we expect to be swept first - final List stateRoots = new ArrayList<>(); - for (int i = markBlockNumber - 1; i >= 0; i--) { - stateRoots.add(blockchain.getBlockHeader(i).get().getStateRoot()); - } - - // Mark - pruner.mark(markBlock.getStateRoot()); - // Mark an extra state root - Hash markedRoot = Hash.wrap(stateRoots.remove(stateRoots.size() / 2)); - pruner.markNode(markedRoot); - // Sweep - pruner.sweepBefore(markBlock.getNumber()); - - // we use individual inOrders because we only want to make sure we remove a state root before - // the full prune but without enforcing an ordering between the state root removals - stateRoots.forEach( - stateRoot -> { - final InOrder thisRootsOrdering = - inOrder(worldStateStorage, hashValueStore, worldStateStorage); - thisRootsOrdering.verify(worldStateStorage).isWorldStateAvailable(stateRoot, null); - thisRootsOrdering.verify(hashValueStore).keySet(); - thisRootsOrdering.verify(worldStateStorage).prune(any()); - }); - - assertThat(stateStorage.containsKey(markedRoot.toArray())).isTrue(); - } - - private void generateBlockchainData(final int numBlocks, final int numAccounts) { - Block parentBlock = blockchain.getChainHeadBlock(); - for (int i = 0; i < numBlocks; i++) { - final BlockHeader parentHeader = parentBlock.getHeader(); - final MutableWorldState worldState = - worldStateArchive.getMutable(parentHeader.getStateRoot(), parentHeader.getHash()).get(); - gen.createRandomContractAccountsWithNonEmptyStorage(worldState, numAccounts); - final Hash stateRoot = worldState.rootHash(); - - final Block block = - gen.block( - BlockOptions.create() - .setStateRoot(stateRoot) - .setBlockNumber(parentHeader.getNumber() + 1L) - .setParentHash(parentBlock.getHash())); - final List receipts = gen.receipts(block); - blockchain.appendBlock(block, receipts); - parentBlock = block; - } - } - - private Set collectWorldStateNodes(final Hash stateRootHash) { - final Set nodeData = new HashSet<>(); - collectWorldStateNodes(stateRootHash, nodeData); - return nodeData; - } - - private Set collectWorldStateNodes(final Hash stateRootHash, final Set collector) { - final List storageRoots = new ArrayList<>(); - final MerkleTrie stateTrie = createStateTrie(stateRootHash); - - // Collect storage roots and code - stateTrie - .entriesFrom(Bytes32.ZERO, 1000) - .forEach( - (key, val) -> { - final StateTrieAccountValue accountValue = - StateTrieAccountValue.readFrom(RLP.input(val)); - stateStorage - .get(accountValue.getCodeHash().toArray()) - .ifPresent(v -> collector.add(Bytes.wrap(v))); - storageRoots.add(accountValue.getStorageRoot()); - }); - - // Collect state nodes - collectTrieNodes(stateTrie, collector); - // Collect storage nodes - for (Hash storageRoot : storageRoots) { - final MerkleTrie storageTrie = createStorageTrie(storageRoot); - collectTrieNodes(storageTrie, collector); - } - - return collector; - } - - private void collectTrieNodes(final MerkleTrie trie, final Set collector) { - final Bytes32 rootHash = trie.getRootHash(); - trie.visitAll( - (node) -> { - if (node.isReferencedByHash() || node.getHash().equals(rootHash)) { - collector.add(node.getEncodedBytes()); - } - }); - } - - private MerkleTrie createStateTrie(final Bytes32 rootHash) { - return new StoredMerklePatriciaTrie<>( - worldStateStorage::getAccountStateTrieNode, - rootHash, - Function.identity(), - Function.identity()); - } - - private MerkleTrie createStorageTrie(final Bytes32 rootHash) { - return new StoredMerklePatriciaTrie<>( - (location, hash) -> worldStateStorage.getAccountStorageTrieNode(null, location, hash), - rootHash, - Function.identity(), - Function.identity()); - } - - // Proxy class so that we have access to the constructor that takes our own map - private static class TestInMemoryStorage extends InMemoryKeyValueStorage { - - public TestInMemoryStorage(final Map> hashValueStore) { - super(hashValueStore); - } - } -} diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/worldstate/PrunerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/worldstate/PrunerTest.java deleted file mode 100644 index 8f1b34bccac..00000000000 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/worldstate/PrunerTest.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.worldstate; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.chain.Blockchain; -import org.hyperledger.besu.ethereum.chain.BlockchainStorage; -import org.hyperledger.besu.ethereum.chain.DefaultBlockchain; -import org.hyperledger.besu.ethereum.chain.MutableBlockchain; -import org.hyperledger.besu.ethereum.core.Block; -import org.hyperledger.besu.ethereum.core.BlockDataGenerator; -import org.hyperledger.besu.ethereum.core.BlockDataGenerator.BlockOptions; -import org.hyperledger.besu.ethereum.core.TransactionReceipt; -import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; -import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; -import org.hyperledger.besu.ethereum.storage.keyvalue.VariablesKeyValueStorage; -import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; -import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; -import org.hyperledger.besu.testutil.MockExecutorService; - -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -public class PrunerTest { - - private final NoOpMetricsSystem metricsSystem = new NoOpMetricsSystem(); - - private final BlockDataGenerator gen = new BlockDataGenerator(); - - @Mock private MarkSweepPruner markSweepPruner; - private final ExecutorService mockExecutorService = new MockExecutorService(); - - private final Block genesisBlock = gen.genesisBlock(); - - @Test - public void shouldMarkCorrectBlockAndSweep() throws ExecutionException, InterruptedException { - final BlockchainStorage blockchainStorage = - new KeyValueStoragePrefixedKeyBlockchainStorage( - new InMemoryKeyValueStorage(), - new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), - new MainnetBlockHeaderFunctions()); - final MutableBlockchain blockchain = - DefaultBlockchain.createMutable(genesisBlock, blockchainStorage, metricsSystem, 0); - - final Pruner pruner = - new Pruner(markSweepPruner, blockchain, new PrunerConfiguration(0, 1), mockExecutorService); - pruner.start(); - - final Block block1 = appendBlockWithParent(blockchain, genesisBlock); - appendBlockWithParent(blockchain, block1); - appendBlockWithParent(blockchain, blockchain.getChainHeadBlock()); - - verify(markSweepPruner).mark(block1.getHeader().getStateRoot()); - verify(markSweepPruner).sweepBefore(1); - pruner.stop(); - } - - @Test - public void shouldOnlySweepAfterBlockConfirmationPeriodAndRetentionPeriodEnds() { - final BlockchainStorage blockchainStorage = - new KeyValueStoragePrefixedKeyBlockchainStorage( - new InMemoryKeyValueStorage(), - new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), - new MainnetBlockHeaderFunctions()); - final MutableBlockchain blockchain = - DefaultBlockchain.createMutable(genesisBlock, blockchainStorage, metricsSystem, 0); - - final Pruner pruner = - new Pruner(markSweepPruner, blockchain, new PrunerConfiguration(1, 2), mockExecutorService); - pruner.start(); - - final Hash markBlockStateRootHash = - appendBlockWithParent(blockchain, genesisBlock).getHeader().getStateRoot(); - verify(markSweepPruner, never()).mark(markBlockStateRootHash); - verify(markSweepPruner, never()).sweepBefore(anyLong()); - - appendBlockWithParent(blockchain, blockchain.getChainHeadBlock()); - verify(markSweepPruner).mark(markBlockStateRootHash); - verify(markSweepPruner, never()).sweepBefore(anyLong()); - - appendBlockWithParent(blockchain, blockchain.getChainHeadBlock()); - verify(markSweepPruner).sweepBefore(1); - pruner.stop(); - } - - @Test - public void abortsPruningWhenFullyMarkedBlockNoLongerOnCanonicalChain() { - final BlockchainStorage blockchainStorage = - new KeyValueStoragePrefixedKeyBlockchainStorage( - new InMemoryKeyValueStorage(), - new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), - new MainnetBlockHeaderFunctions()); - final MutableBlockchain blockchain = - DefaultBlockchain.createMutable(genesisBlock, blockchainStorage, metricsSystem, 0); - - // start pruner so it can start handling block added events - final Pruner pruner = - new Pruner(markSweepPruner, blockchain, new PrunerConfiguration(0, 1), mockExecutorService); - pruner.start(); - - /* - Set up pre-marking state: - O <---- marking of this block's parent will begin when this block is added - | - | O <- this is a fork as of now (non-canonical) - O | <- this is the initially canonical block that will be marked - \/ - O <--- the common ancestor when the reorg happens - */ - final Block initiallyCanonicalBlock = appendBlockWithParent(blockchain, genesisBlock); - appendBlockWithParent(blockchain, initiallyCanonicalBlock); - final Block forkBlock = appendBlockWithParent(blockchain, genesisBlock); - /* - Cause reorg: - Set up pre-marking state: - O - | O <---- this block causes a reorg; this branch becomes canonical - | O <---- which means that state here is referring to nodes from the common ancestor block, - O | <- because this was block at which marking began - \/ - O - */ - appendBlockWithParent(blockchain, forkBlock); - verify(markSweepPruner).mark(initiallyCanonicalBlock.getHeader().getStateRoot()); - verify(markSweepPruner, never()).sweepBefore(anyLong()); - pruner.stop(); - } - - @Test - public void shouldRejectInvalidArguments() { - final Blockchain mockchain = mock(Blockchain.class); - assertThatThrownBy( - () -> - new Pruner( - markSweepPruner, - mockchain, - new PrunerConfiguration(-1, -2), - mockExecutorService)) - .isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy( - () -> - new Pruner( - markSweepPruner, - mockchain, - new PrunerConfiguration(10, 8), - mockExecutorService)) - .isInstanceOf(IllegalArgumentException.class); - assertThatThrownBy( - () -> - new Pruner( - markSweepPruner, - mockchain, - new PrunerConfiguration(10, 10), - mockExecutorService)) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void shouldCleanUpPruningStrategyOnShutdown() throws InterruptedException { - final BlockchainStorage blockchainStorage = - new KeyValueStoragePrefixedKeyBlockchainStorage( - new InMemoryKeyValueStorage(), - new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), - new MainnetBlockHeaderFunctions()); - final MutableBlockchain blockchain = - DefaultBlockchain.createMutable(genesisBlock, blockchainStorage, metricsSystem, 0); - - final Pruner pruner = - new Pruner(markSweepPruner, blockchain, new PrunerConfiguration(0, 1), mockExecutorService); - pruner.start(); - pruner.stop(); - verify(markSweepPruner).cleanup(); - } - - private Block appendBlockWithParent(final MutableBlockchain blockchain, final Block parent) { - BlockOptions options = - new BlockOptions() - .setBlockNumber(parent.getHeader().getNumber() + 1) - .setParentHash(parent.getHash()); - final Block newBlock = gen.block(options); - final List receipts = gen.receipts(newBlock); - blockchain.appendBlock(newBlock, receipts); - return newBlock; - } -} diff --git a/ethereum/core/src/test/resources/org/hyperledger/besu/ethereum/chain/genesis_prague.json b/ethereum/core/src/test/resources/org/hyperledger/besu/ethereum/chain/genesis_prague.json new file mode 100644 index 00000000000..d99071b328d --- /dev/null +++ b/ethereum/core/src/test/resources/org/hyperledger/besu/ethereum/chain/genesis_prague.json @@ -0,0 +1,4077 @@ +{ + "config": { + "ethash": {}, + "chainID": 7, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "constantinopleFixBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "parisBlock": 0, + "terminalTotalDifficulty": 0, + "shanghaiTime": 0, + "cancunTime": 0, + "pragueTime": 0 + }, + "nonce": "0x0", + "timestamp": "0x1234", + "extraData": "0x", + "gasLimit": "0x2fefd8", + "difficulty": "0x0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "alloc": { + "0000000000000000000000000000000000000100": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000101": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000102": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000103": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000104": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000105": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000106": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000107": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000108": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000109": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000010a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000010b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000010c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000010d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000010e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000010f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000110": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000111": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000112": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000113": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000114": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000115": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000116": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000117": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000118": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000119": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000011a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000011b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000011c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000011d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000011e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000011f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000120": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000121": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000122": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000123": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000124": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000125": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000126": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000127": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000128": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000129": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000012a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000012b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000012c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000012d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000012e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000012f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000130": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000131": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000132": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000133": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000134": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000135": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000136": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000137": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000138": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000139": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000013a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000013b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000013c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000013d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000013e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000013f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000140": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000141": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000142": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000143": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000144": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000145": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000146": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000147": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000148": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000149": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000014a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000014b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000014c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000014d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000014e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000014f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000150": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000151": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000152": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000153": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000154": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000155": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000156": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000157": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000158": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000159": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000015a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000015b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000015c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000015d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000015e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000015f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000160": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000161": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000162": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000163": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000164": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000165": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000166": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000167": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000168": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000169": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000016a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000016b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000016c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000016d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000016e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000016f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000170": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000171": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000172": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000173": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000174": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000175": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000176": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000177": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000178": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000179": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000017a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000017b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000017c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000017d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000017e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000017f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000180": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000181": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000182": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000183": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000184": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000185": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000186": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000187": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000188": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000189": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000018a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000018b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000018c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000018d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000018e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000018f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000190": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000191": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000192": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000193": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000194": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000195": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000196": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000197": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000198": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000199": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000019a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000019b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000019c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000019d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000019e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000019f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001a0": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001a1": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001a2": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001a3": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001a4": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001a5": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001a6": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001a7": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001a8": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001a9": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001aa": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001ab": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001ac": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001ad": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001ae": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001af": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001b0": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001b1": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001b2": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001b3": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001b4": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001b5": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001b6": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001b7": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001b8": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001b9": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001ba": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001bb": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001bc": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001bd": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001be": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001bf": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001c0": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001c1": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001c2": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001c3": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001c4": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001c5": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001c6": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001c7": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001c8": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001c9": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001ca": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001cb": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001cc": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001cd": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001ce": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001cf": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001d0": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001d1": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001d2": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001d3": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001d4": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001d5": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001d6": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001d7": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001d8": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001d9": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001da": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001db": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001dc": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001dd": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001de": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001df": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001e0": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001e1": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001e2": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001e3": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001e4": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001e5": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001e6": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001e7": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001e8": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001e9": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001ea": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001eb": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001ec": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001ed": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001ee": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001ef": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001f0": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001f1": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001f2": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001f3": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001f4": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001f5": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001f6": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001f7": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001f8": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001f9": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001fa": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001fb": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001fc": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001fd": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001fe": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000001ff": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000200": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000201": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000202": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000203": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000204": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000205": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000206": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000207": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000208": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000209": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000020a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000020b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000020c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000020d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000020e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000020f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000210": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000211": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000212": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000213": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000214": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000215": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000216": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000217": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000218": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000219": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000021a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000021b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000021c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000021d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000021e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000021f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000220": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000221": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000222": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000223": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000224": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000225": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000226": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000227": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000228": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000229": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000022a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000022b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000022c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000022d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000022e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000022f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000230": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000231": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000232": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000233": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000234": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000235": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000236": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000237": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000238": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000239": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000023a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000023b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000023c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000023d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000023e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000023f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000240": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000241": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000242": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000243": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000244": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000245": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000246": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000247": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000248": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000249": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000024a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000024b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000024c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000024d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000024e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000024f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000250": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000251": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000252": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000253": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000254": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000255": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000256": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000257": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000258": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000259": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000025a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000025b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000025c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000025d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000025e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000025f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000260": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000261": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000262": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000263": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000264": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000265": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000266": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000267": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000268": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000269": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000026a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000026b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000026c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000026d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000026e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000026f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000270": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000271": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000272": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000273": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000274": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000275": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000276": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000277": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000278": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000279": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000027a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000027b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000027c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000027d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000027e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000027f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000280": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000281": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000282": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000283": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000284": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000285": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000286": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000287": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000288": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000289": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000028a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000028b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000028c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000028d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000028e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000028f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000290": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000291": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000292": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000293": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000294": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000295": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000296": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000297": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000298": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000299": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000029a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000029b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000029c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000029d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000029e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000029f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002a0": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002a1": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002a2": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002a3": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002a4": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002a5": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002a6": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002a7": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002a8": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002a9": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002aa": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002ab": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002ac": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002ad": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002ae": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002af": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002b0": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002b1": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002b2": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002b3": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002b4": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002b5": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002b6": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002b7": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002b8": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002b9": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002ba": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002bb": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002bc": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002bd": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002be": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002bf": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002c0": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002c1": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002c2": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002c3": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002c4": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002c5": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002c6": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002c7": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002c8": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002c9": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002ca": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002cb": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002cc": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002cd": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002ce": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002cf": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002d0": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002d1": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002d2": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002d3": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002d4": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002d5": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002d6": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002d7": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002d8": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002d9": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002da": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002db": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002dc": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002dd": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002de": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002df": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002e0": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002e1": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002e2": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002e3": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002e4": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002e5": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002e6": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002e7": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002e8": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002e9": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002ea": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002eb": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002ec": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002ed": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002ee": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002ef": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002f0": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002f1": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002f2": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002f3": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002f4": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002f5": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002f6": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002f7": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002f8": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002f9": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002fa": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002fb": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002fc": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002fd": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002fe": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000002ff": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000300": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000301": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000302": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000303": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000304": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000305": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000306": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000307": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000308": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000309": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000030a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000030b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000030c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000030d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000030e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000030f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000310": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000311": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000312": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000313": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000314": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000315": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000316": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000317": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000318": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000319": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000031a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000031b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000031c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000031d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000031e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000031f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000320": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000321": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000322": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000323": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000324": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000325": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000326": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000327": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000328": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000329": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000032a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000032b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000032c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000032d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000032e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000032f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000330": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000331": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000332": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000333": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000334": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000335": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000336": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000337": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000338": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000339": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000033a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000033b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000033c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000033d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000033e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000033f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000340": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000341": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000342": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000343": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000344": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000345": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000346": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000347": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000348": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000349": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000034a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000034b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000034c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000034d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000034e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000034f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000350": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000351": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000352": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000353": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000354": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000355": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000356": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000357": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000358": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000359": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000035a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000035b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000035c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000035d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000035e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000035f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000360": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000361": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000362": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000363": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000364": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000365": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000366": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000367": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000368": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000369": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000036a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000036b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000036c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000036d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000036e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000036f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000370": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000371": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000372": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000373": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000374": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000375": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000376": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000377": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000378": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000379": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000037a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000037b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000037c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000037d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000037e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000037f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000380": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000381": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000382": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000383": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000384": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000385": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000386": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000387": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000388": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000389": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000038a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000038b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000038c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000038d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000038e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000038f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000390": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000391": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000392": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000393": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000394": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000395": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000396": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000397": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000398": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000399": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000039a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000039b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000039c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000039d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000039e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000039f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003a0": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003a1": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003a2": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003a3": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003a4": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003a5": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003a6": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003a7": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003a8": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003a9": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003aa": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003ab": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003ac": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003ad": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003ae": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003af": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003b0": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003b1": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003b2": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003b3": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003b4": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003b5": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003b6": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003b7": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003b8": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003b9": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003ba": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003bb": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003bc": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003bd": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003be": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003bf": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003c0": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003c1": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003c2": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003c3": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003c4": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003c5": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003c6": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003c7": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003c8": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003c9": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003ca": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003cb": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003cc": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003cd": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003ce": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003cf": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003d0": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003d1": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003d2": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003d3": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003d4": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003d5": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003d6": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003d7": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003d8": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003d9": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003da": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003db": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003dc": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003dd": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003de": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003df": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003e0": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003e1": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003e2": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003e3": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003e4": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003e5": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003e6": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003e7": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003e8": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003e9": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003ea": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003eb": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003ec": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003ed": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003ee": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003ef": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003f0": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003f1": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003f2": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003f3": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003f4": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003f5": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003f6": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003f7": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003f8": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003f9": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003fa": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003fb": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003fc": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003fd": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003fe": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000003ff": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000400": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000401": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000402": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000403": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000404": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000405": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000406": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000407": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000408": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000409": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000040a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000040b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000040c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000040d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000040e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000040f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000410": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000411": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000412": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000413": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000414": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000415": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000416": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000417": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000418": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000419": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000041a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000041b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000041c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000041d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000041e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000041f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000420": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000421": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000422": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000423": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000424": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000425": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000426": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000427": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000428": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000429": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000042a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000042b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000042c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000042d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000042e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000042f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000430": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000431": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000432": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000433": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000434": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000435": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000436": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000437": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000438": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000439": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000043a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000043b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000043c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000043d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000043e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000043f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000440": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000441": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000442": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000443": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000444": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000445": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000446": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000447": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000448": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000449": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000044a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000044b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000044c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000044d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000044e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000044f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000450": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000451": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000452": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000453": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000454": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000455": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000456": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000457": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000458": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000459": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000045a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000045b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000045c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000045d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000045e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000045f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000460": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000461": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000462": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000463": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000464": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000465": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000466": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000467": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000468": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000469": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000046a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000046b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000046c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000046d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000046e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000046f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000470": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000471": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000472": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000473": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000474": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000475": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000476": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000477": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000478": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000479": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000047a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000047b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000047c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000047d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000047e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000047f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000480": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000481": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000482": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000483": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000484": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000485": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000486": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000487": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000488": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000489": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000048a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000048b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000048c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000048d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000048e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000048f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000490": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000491": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000492": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000493": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000494": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000495": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000496": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000497": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000498": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0000000000000000000000000000000000000499": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000049a": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000049b": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000049c": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000049d": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000049e": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "000000000000000000000000000000000000049f": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004a0": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004a1": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004a2": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004a3": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004a4": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004a5": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004a6": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004a7": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004a8": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004a9": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004aa": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004ab": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004ac": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004ad": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004ae": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004af": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004b0": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004b1": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004b2": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004b3": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004b4": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004b5": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004b6": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004b7": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004b8": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004b9": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004ba": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004bb": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004bc": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004bd": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004be": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004bf": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004c0": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004c1": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004c2": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004c3": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004c4": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004c5": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004c6": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004c7": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004c8": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004c9": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004ca": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004cb": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004cc": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004cd": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004ce": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004cf": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004d0": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004d1": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004d2": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004d3": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004d4": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004d5": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004d6": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004d7": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004d8": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004d9": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004da": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004db": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004dc": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004dd": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004de": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004df": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004e0": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004e1": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004e2": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004e3": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004e4": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004e5": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004e6": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "00000000000000000000000000000000000004e7": { + "code": "0x5f804955600180495560028049556003804955", + "balance": "0x0" + }, + "0161e041aad467a890839d5b08b138c1e6373072": { + "balance": "0x123450000000000000000" + }, + "0f830fc93e76c8d26ed1f65045e70f5f986da383": { + "balance": "0x123450000000000000000" + }, + "2180290b54d5d9e38f85fdaf086bfe35b610f404": { + "balance": "0x123450000000000000000" + }, + "763fc7d39ab4cfa496c39077542589819aea5c54": { + "balance": "0x123450000000000000000" + }, + "87da6a8c6e9eff15d703fc2773e32f6af8dbe301": { + "balance": "0x123450000000000000000" + }, + "9c09de85a373dc3486115cab94c72549029f1103": { + "balance": "0x123450000000000000000" + }, + "9d2f18c852eb5b37292dfc9f68ac5ecbf0d22dd2": { + "balance": "0x123450000000000000000" + }, + "a995b3ebccfb2b8e8b1db15b7ca8cf2f11ed0310": { + "balance": "0x123450000000000000000" + }, + "aa82d35d5eaec5cf87c311a3af844ed682d5a247": { + "balance": "0x123450000000000000000" + }, + "b76ae96839fe445670262bce61144e6cda9099b8": { + "balance": "0x123450000000000000000" + }, + "b97de4b8c857e4f6bc354f226dc3249aaee49209": { + "balance": "0x123450000000000000000" + }, + "c5065c9eeebe6df2c2284d046bfc906501846c51": { + "balance": "0x123450000000000000000" + }, + "cf49fda3be353c69b41ed96333cd24302da4556f": { + "balance": "0x123450000000000000000" + }, + "fb289e2b2b65fb63299a682d000744671c50417b": { + "balance": "0x123450000000000000000" + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "baseFeePerGas": "0x3b9aca00" +} diff --git a/ethereum/core/src/test/resources/org/hyperledger/besu/ethereum/chain/genesis_prague2.json b/ethereum/core/src/test/resources/org/hyperledger/besu/ethereum/chain/genesis_prague2.json new file mode 100644 index 00000000000..cdb755ace18 --- /dev/null +++ b/ethereum/core/src/test/resources/org/hyperledger/besu/ethereum/chain/genesis_prague2.json @@ -0,0 +1,91 @@ +{ + "config": { + "ethash": {}, + "chainID": 1, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "constantinopleFixBlock": 0, + "istanbulBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "terminalTotalDifficulty": 0, + "shanghaiTime": 0, + "cancunTime": 0, + "pragueTime": 0 + }, + "nonce": "0x0", + "timestamp": "0x0", + "extraData": "0x00", + "gasLimit": "0x16345785d8a0000", + "difficulty": "0x0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "alloc": { + "00000000219ab540356cbb839cbe05303d7705fa": { + "code": "0x60806040526004361061003f5760003560e01c806301ffc9a71461004457806322895118146100a4578063621fd130146101ba578063c5f2892f14610244575b600080fd5b34801561005057600080fd5b506100906004803603602081101561006757600080fd5b50357fffffffff000000000000000000000000000000000000000000000000000000001661026b565b604080519115158252519081900360200190f35b6101b8600480360360808110156100ba57600080fd5b8101906020810181356401000000008111156100d557600080fd5b8201836020820111156100e757600080fd5b8035906020019184600183028401116401000000008311171561010957600080fd5b91939092909160208101903564010000000081111561012757600080fd5b82018360208201111561013957600080fd5b8035906020019184600183028401116401000000008311171561015b57600080fd5b91939092909160208101903564010000000081111561017957600080fd5b82018360208201111561018b57600080fd5b803590602001918460018302840111640100000000831117156101ad57600080fd5b919350915035610304565b005b3480156101c657600080fd5b506101cf6110b5565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102095781810151838201526020016101f1565b50505050905090810190601f1680156102365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561025057600080fd5b506102596110c7565b60408051918252519081900360200190f35b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a70000000000000000000000000000000000000000000000000000000014806102fe57507fffffffff0000000000000000000000000000000000000000000000000000000082167f8564090700000000000000000000000000000000000000000000000000000000145b92915050565b6030861461035d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118056026913960400191505060405180910390fd5b602084146103b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603681526020018061179c6036913960400191505060405180910390fd5b6060821461040f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806118786029913960400191505060405180910390fd5b670de0b6b3a7640000341015610470576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118526026913960400191505060405180910390fd5b633b9aca003406156104cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260338152602001806117d26033913960400191505060405180910390fd5b633b9aca00340467ffffffffffffffff811115610535576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602781526020018061182b6027913960400191505060405180910390fd5b6060610540826114ba565b90507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c589898989858a8a6105756020546114ba565b6040805160a0808252810189905290819060208201908201606083016080840160c085018e8e80828437600083820152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690910187810386528c815260200190508c8c808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690920188810386528c5181528c51602091820193918e019250908190849084905b83811015610648578181015183820152602001610630565b50505050905090810190601f1680156106755780820380516001836020036101000a031916815260200191505b5086810383528881526020018989808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169092018881038452895181528951602091820193918b019250908190849084905b838110156106ef5781810151838201526020016106d7565b50505050905090810190601f16801561071c5780820380516001836020036101000a031916815260200191505b509d505050505050505050505050505060405180910390a1600060028a8a600060801b604051602001808484808284377fffffffffffffffffffffffffffffffff0000000000000000000000000000000090941691909301908152604080517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0818403018152601090920190819052815191955093508392506020850191508083835b602083106107fc57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016107bf565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610859573d6000803e3d6000fd5b5050506040513d602081101561086e57600080fd5b5051905060006002806108846040848a8c6116fe565b6040516020018083838082843780830192505050925050506040516020818303038152906040526040518082805190602001908083835b602083106108f857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016108bb565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610955573d6000803e3d6000fd5b5050506040513d602081101561096a57600080fd5b5051600261097b896040818d6116fe565b60405160009060200180848480828437919091019283525050604080518083038152602092830191829052805190945090925082918401908083835b602083106109f457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016109b7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610a51573d6000803e3d6000fd5b5050506040513d6020811015610a6657600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610ada57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610a9d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610b37573d6000803e3d6000fd5b5050506040513d6020811015610b4c57600080fd5b50516040805160208101858152929350600092600292839287928f928f92018383808284378083019250505093505050506040516020818303038152906040526040518082805190602001908083835b60208310610bd957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610b9c565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610c36573d6000803e3d6000fd5b5050506040513d6020811015610c4b57600080fd5b50516040518651600291889160009188916020918201918291908601908083835b60208310610ca957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610c6c565b6001836020036101000a0380198251168184511680821785525050505050509050018367ffffffffffffffff191667ffffffffffffffff1916815260180182815260200193505050506040516020818303038152906040526040518082805190602001908083835b60208310610d4e57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610d11565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610dab573d6000803e3d6000fd5b5050506040513d6020811015610dc057600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610e3457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610df7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610e91573d6000803e3d6000fd5b5050506040513d6020811015610ea657600080fd5b50519050858114610f02576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260548152602001806117486054913960600191505060405180910390fd5b60205463ffffffff11610f60576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806117276021913960400191505060405180910390fd5b602080546001019081905560005b60208110156110a9578160011660011415610fa0578260008260208110610f9157fe5b0155506110ac95505050505050565b600260008260208110610faf57fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061102557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610fe8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015611082573d6000803e3d6000fd5b5050506040513d602081101561109757600080fd5b50519250600282049150600101610f6e565b50fe5b50505050505050565b60606110c26020546114ba565b905090565b6020546000908190815b60208110156112f05781600116600114156111e6576002600082602081106110f557fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061116b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161112e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156111c8573d6000803e3d6000fd5b5050506040513d60208110156111dd57600080fd5b505192506112e2565b600283602183602081106111f657fe5b015460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061126b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161122e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156112c8573d6000803e3d6000fd5b5050506040513d60208110156112dd57600080fd5b505192505b6002820491506001016110d1565b506002826112ff6020546114ba565b600060401b6040516020018084815260200183805190602001908083835b6020831061135a57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161131d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790527fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000095909516920191825250604080518083037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8018152601890920190819052815191955093508392850191508083835b6020831061143f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101611402565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa15801561149c573d6000803e3d6000fd5b5050506040513d60208110156114b157600080fd5b50519250505090565b60408051600880825281830190925260609160208201818036833701905050905060c082901b8060071a60f81b826000815181106114f457fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060061a60f81b8260018151811061153757fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060051a60f81b8260028151811061157a57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060041a60f81b826003815181106115bd57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060031a60f81b8260048151811061160057fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060021a60f81b8260058151811061164357fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060011a60f81b8260068151811061168657fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060001a60f81b826007815181106116c957fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535050919050565b6000808585111561170d578182fd5b83861115611719578182fd5b505082019391909203915056fe4465706f736974436f6e74726163743a206d65726b6c6520747265652066756c6c4465706f736974436f6e74726163743a207265636f6e7374727563746564204465706f7369744461746120646f6573206e6f74206d6174636820737570706c696564206465706f7369745f646174615f726f6f744465706f736974436f6e74726163743a20696e76616c6964207769746864726177616c5f63726564656e7469616c73206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c7565206e6f74206d756c7469706c65206f6620677765694465706f736974436f6e74726163743a20696e76616c6964207075626b6579206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f20686967684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f206c6f774465706f736974436f6e74726163743a20696e76616c6964207369676e6174757265206c656e677468a2646970667358221220dceca8706b29e917dacf25fceef95acac8d90d765ac926663ce4096195952b6164736f6c634300060b0033", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000022": "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", + "0x0000000000000000000000000000000000000000000000000000000000000023": "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", + "0x0000000000000000000000000000000000000000000000000000000000000024": "0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c", + "0x0000000000000000000000000000000000000000000000000000000000000025": "0x536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c", + "0x0000000000000000000000000000000000000000000000000000000000000026": "0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30", + "0x0000000000000000000000000000000000000000000000000000000000000027": "0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1", + "0x0000000000000000000000000000000000000000000000000000000000000028": "0x87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c", + "0x0000000000000000000000000000000000000000000000000000000000000029": "0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193", + "0x000000000000000000000000000000000000000000000000000000000000002a": "0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1", + "0x000000000000000000000000000000000000000000000000000000000000002b": "0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b", + "0x000000000000000000000000000000000000000000000000000000000000002c": "0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220", + "0x000000000000000000000000000000000000000000000000000000000000002d": "0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f", + "0x000000000000000000000000000000000000000000000000000000000000002e": "0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e", + "0x000000000000000000000000000000000000000000000000000000000000002f": "0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784", + "0x0000000000000000000000000000000000000000000000000000000000000030": "0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb", + "0x0000000000000000000000000000000000000000000000000000000000000031": "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb", + "0x0000000000000000000000000000000000000000000000000000000000000032": "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab", + "0x0000000000000000000000000000000000000000000000000000000000000033": "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", + "0x0000000000000000000000000000000000000000000000000000000000000034": "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f", + "0x0000000000000000000000000000000000000000000000000000000000000035": "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", + "0x0000000000000000000000000000000000000000000000000000000000000036": "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", + "0x0000000000000000000000000000000000000000000000000000000000000037": "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", + "0x0000000000000000000000000000000000000000000000000000000000000038": "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", + "0x0000000000000000000000000000000000000000000000000000000000000039": "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", + "0x000000000000000000000000000000000000000000000000000000000000003a": "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", + "0x000000000000000000000000000000000000000000000000000000000000003b": "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", + "0x000000000000000000000000000000000000000000000000000000000000003c": "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", + "0x000000000000000000000000000000000000000000000000000000000000003d": "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", + "0x000000000000000000000000000000000000000000000000000000000000003e": "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", + "0x000000000000000000000000000000000000000000000000000000000000003f": "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", + "0x0000000000000000000000000000000000000000000000000000000000000040": "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7" + }, + "balance": "0x0", + "nonce": "0x1" + }, + "000f3df6d732807ef1319fb7b8bb8522d0beac02": { + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500", + "balance": "0x0", + "nonce": "0x1" + }, + "00a3ca265ebcb825b45f985a16cefb49958ce017": { + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe146090573615156028575f545f5260205ff35b366038141561012e5760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061012e57600154600101600155600354806003026004013381556001015f3581556001016020359055600101600355005b6003546002548082038060101160a4575060105b5f5b81811460dd5780604c02838201600302600401805490600101805490600101549160601b83528260140152906034015260010160a6565b910180921460ed579060025560f8565b90505f6002555f6003555b5f548061049d141561010757505f5b60015460028282011161011c5750505f610122565b01600290035b5f555f600155604c025ff35b5f5ffd", + "balance": "0x0", + "nonce": "0x1" + }, + "0x0aae40965e6800cd9b1f4b05ff21581047e3f91e": { + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe1460575767ffffffffffffffff5f3511605357600143035f3511604b575f35612000014311604b57611fff5f3516545f5260205ff35b5f5f5260205ff35b5f5ffd5b5f35611fff60014303165500", + "balance": "0x0", + "nonce": "0x1" + }, + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0xad78ebc5ac62000000" + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "baseFeePerGas": "0x7", + "excessBlobGas": "0x0", + "blobGasUsed": "0x0" +} \ No newline at end of file diff --git a/ethereum/core/src/test/resources/org/hyperledger/besu/ethereum/mainnet/contractUsedInBlockProcessingIT.sol b/ethereum/core/src/test/resources/org/hyperledger/besu/ethereum/mainnet/contractUsedInBlockProcessingIT.sol new file mode 100644 index 00000000000..496ae672025 --- /dev/null +++ b/ethereum/core/src/test/resources/org/hyperledger/besu/ethereum/mainnet/contractUsedInBlockProcessingIT.sol @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +pragma solidity ^0.8.19; + +contract SimpleStorage { + uint256 private slot1; //0x0 + uint256 private slot2; //0x1 + uint256 private slot3; //0x2 + + function setSlot1(uint256 value) public { + slot1 = value; + } + + function setSlot2(uint256 value) public { + slot2 = value; + } + + function setSlot3(uint256 value) public { + slot3 = value; + } + + function getSlot1() public view returns (uint256) { + return slot1; + } + + function getSlot2() public view returns (uint256) { + return slot2; + } + + function getSlot3() public view returns (uint256) { + return slot3; + } + + function getBalance(address account) public view returns (uint256) { + return account.balance; + } + + function transferTo(address payable recipient, uint256 amount) public payable { + require(amount <= address(this).balance, "Insufficient balance in contract"); + recipient.transfer(amount); + } + + receive() external payable {} +} diff --git a/ethereum/core/src/test/resources/org/hyperledger/besu/ethereum/mainnet/genesis-bp-it.json b/ethereum/core/src/test/resources/org/hyperledger/besu/ethereum/mainnet/genesis-bp-it.json new file mode 100644 index 00000000000..72fbc561ec7 --- /dev/null +++ b/ethereum/core/src/test/resources/org/hyperledger/besu/ethereum/mainnet/genesis-bp-it.json @@ -0,0 +1,48 @@ +{ + "config": { + "chainId":42, + "homesteadBlock":0, + "eip150Block":0, + "eip155Block":0, + "eip158Block":0, + "byzantiumBlock":0, + "constantinopleBlock":0, + "petersburgBlock":0, + "istanbulBlock":0, + "muirGlacierBlock":0, + "berlinBlock":0, + "londonBlock":0, + "shanghaiTime": 0, + "terminalTotalDifficulty":0, + "cancunTime":0 + }, + "coinbase": "0x0000000000000000000000000000000000000000", + "difficulty": "0x0000001", + "extraData": "", + "gasLimit": "0x1C9C380", + "nonce": "0x0000000000000107", + "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp": "0x00", + "alloc": { + "627306090abab3a6e1400e9345bc60c78a8bef57": { + "balance": "0x3635C9ADC5DEA00000", + "privateKey": "c87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3", + "comment": "private key and this comment are ignored. In a real chain, the private key should NOT be stored" + }, + "7f2d653f56ea8de6ffa554c7a0cd4e03af79f3eb": { + "balance": "0x3635C9ADC5DEA00000", + "privateKey": "fc5141e75bf622179f8eedada7fab3e2e6b3e3da8eb9df4f46d84df22df7430e", + "comment": "private key and this comment are ignored. In a real chain, the private key should NOT be stored" + },"0x00000000000000000000000000000000000fffff": { + "balance": "0", + "code": "60806040526004361061007e575f3560e01c8063713ba21b1161004d578063713ba21b14610121578063bad4ba0b14610149578063cda7be6114610171578063f8b2cb4f1461019b57610085565b8063250976e7146100895780632ccb1b30146100b35780635e8ad6c7146100cf5780636c190cd1146100f757610085565b3661008557005b5f80fd5b348015610094575f80fd5b5061009d6101d7565b6040516100aa91906102d1565b60405180910390f35b6100cd60048036038101906100c89190610372565b6101e0565b005b3480156100da575f80fd5b506100f560048036038101906100f091906103b0565b61026b565b005b348015610102575f80fd5b5061010b610274565b60405161011891906102d1565b60405180910390f35b34801561012c575f80fd5b50610147600480360381019061014291906103b0565b61027d565b005b348015610154575f80fd5b5061016f600480360381019061016a91906103b0565b610287565b005b34801561017c575f80fd5b50610185610291565b60405161019291906102d1565b60405180910390f35b3480156101a6575f80fd5b506101c160048036038101906101bc9190610416565b610299565b6040516101ce91906102d1565b60405180910390f35b5f600254905090565b47811115610223576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161021a9061049b565b60405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f19350505050158015610266573d5f803e3d5ffd5b505050565b805f8190555050565b5f600154905090565b8060018190555050565b8060028190555050565b5f8054905090565b5f8173ffffffffffffffffffffffffffffffffffffffff16319050919050565b5f819050919050565b6102cb816102b9565b82525050565b5f6020820190506102e45f8301846102c2565b92915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610317826102ee565b9050919050565b6103278161030d565b8114610331575f80fd5b50565b5f813590506103428161031e565b92915050565b610351816102b9565b811461035b575f80fd5b50565b5f8135905061036c81610348565b92915050565b5f8060408385031215610388576103876102ea565b5b5f61039585828601610334565b92505060206103a68582860161035e565b9150509250929050565b5f602082840312156103c5576103c46102ea565b5b5f6103d28482850161035e565b91505092915050565b5f6103e5826102ee565b9050919050565b6103f5816103db565b81146103ff575f80fd5b50565b5f81359050610410816103ec565b92915050565b5f6020828403121561042b5761042a6102ea565b5b5f61043884828501610402565b91505092915050565b5f82825260208201905092915050565b7f496e73756666696369656e742062616c616e636520696e20636f6e74726163745f82015250565b5f610485602083610441565b915061049082610451565b602082019050919050565b5f6020820190508181035f8301526104b281610479565b905091905056fea264697066735822122023baf1931f9dfdc95c00818838ff0de5f17ac26981187c1407ae4f1d111ff57464736f6c634300081a0033", + "comment": "This bytecode corresponds to the smart contract contractUsedInBlockProcessingIT.sol, visible in the resource files", + "storage": { + "0x0": "0x2a", + "0x1": "0x54", + "0x2": "0x7e" + } + } + } +} \ No newline at end of file diff --git a/ethereum/eth/build.gradle b/ethereum/eth/build.gradle index b51088f4cf8..677783dfdff 100644 --- a/ethereum/eth/build.gradle +++ b/ethereum/eth/build.gradle @@ -22,7 +22,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } diff --git a/ethereum/eth/src/jmh/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldStateDownloaderBenchmark.java b/ethereum/eth/src/jmh/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldStateDownloaderBenchmark.java index 5f7cbcdc8df..079b6ba47fa 100644 --- a/ethereum/eth/src/jmh/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldStateDownloaderBenchmark.java +++ b/ethereum/eth/src/jmh/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldStateDownloaderBenchmark.java @@ -38,10 +38,12 @@ import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStorageProviderBuilder; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.metrics.ObservableMetricsSystem; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBKeyValueStorageFactory; import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBMetricsFactory; @@ -76,7 +78,7 @@ public class WorldStateDownloaderBenchmark { private BlockHeader blockHeader; private final ObservableMetricsSystem metricsSystem = new NoOpMetricsSystem(); private WorldStateDownloader worldStateDownloader; - private WorldStateStorage worldStateStorage; + private WorldStateStorageCoordinator worldStateStorageCoordinator; private RespondingEthPeer peer; private Responder responder; private InMemoryTasksPriorityQueues pendingRequests; @@ -105,20 +107,22 @@ public void setUpUnchangedState() { final StorageProvider storageProvider = createKeyValueStorageProvider(tempDir, tempDir.resolve("database")); - worldStateStorage = storageProvider.createWorldStateStorage(DataStorageFormat.FOREST); + worldStateStorageCoordinator = + storageProvider.createWorldStateStorageCoordinator(DataStorageConfiguration.DEFAULT_CONFIG); pendingRequests = new InMemoryTasksPriorityQueues<>(); worldStateDownloader = new FastWorldStateDownloader( ethContext, - worldStateStorage, + worldStateStorageCoordinator, pendingRequests, syncConfig.getWorldStateHashCountPerRequest(), syncConfig.getWorldStateRequestParallelism(), syncConfig.getWorldStateMaxRequestsWithoutProgress(), syncConfig.getWorldStateMinMillisBeforeStalling(), Clock.fixed(Instant.ofEpochSecond(1000), ZoneOffset.UTC), - metricsSystem); + metricsSystem, + SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS); } private Hash createExistingWorldState() { @@ -151,7 +155,9 @@ public Optional downloadWorldState() { peer.respondWhileOtherThreadsWork(responder, () -> !result.isDone()); result.getNow(null); final Optional rootData = - worldStateStorage.getNodeData(Bytes.EMPTY, blockHeader.getStateRoot()); + worldStateStorageCoordinator + .getStrategy(ForestWorldStateKeyValueStorage.class) + .getNodeData(blockHeader.getStateRoot()); if (rootData.isEmpty()) { throw new IllegalStateException("World state download did not complete."); } @@ -159,6 +165,8 @@ public Optional downloadWorldState() { } private StorageProvider createKeyValueStorageProvider(final Path dataDir, final Path dbDir) { + final var besuConfiguration = new BesuConfigurationImpl(); + besuConfiguration.init(dataDir, dbDir, DataStorageConfiguration.DEFAULT_CONFIG); return new KeyValueStorageProviderBuilder() .withStorageFactory( new RocksDBKeyValueStorageFactory( @@ -170,7 +178,7 @@ private StorageProvider createKeyValueStorageProvider(final Path dataDir, final DEFAULT_IS_HIGH_SPEC), Arrays.asList(KeyValueSegmentIdentifier.values()), RocksDBMetricsFactory.PUBLIC_ROCKS_DB_METRICS)) - .withCommonConfiguration(new BesuConfigurationImpl(dataDir, dbDir)) + .withCommonConfiguration(besuConfiguration) .withMetricsSystem(new NoOpMetricsSystem()) .build(); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/consensus/merge/ForkchoiceEvent.java b/ethereum/eth/src/main/java/org/hyperledger/besu/consensus/merge/ForkchoiceEvent.java index bc38cd38808..ddb0ec5efd7 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/consensus/merge/ForkchoiceEvent.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/consensus/merge/ForkchoiceEvent.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -91,8 +91,6 @@ public String toString() { + safeBlockHash + ", finalizedBlockHash=" + finalizedBlockHash - + ", safeBlockHash=" - + safeBlockHash + '}'; } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/consensus/merge/MergeStateHandler.java b/ethereum/eth/src/main/java/org/hyperledger/besu/consensus/merge/MergeStateHandler.java index d668e13698e..99d7f2af4e1 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/consensus/merge/MergeStateHandler.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/consensus/merge/MergeStateHandler.java @@ -1,8 +1,8 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.consensus.merge; import org.hyperledger.besu.ethereum.core.Difficulty; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/consensus/merge/UnverifiedForkchoiceListener.java b/ethereum/eth/src/main/java/org/hyperledger/besu/consensus/merge/UnverifiedForkchoiceListener.java index ccbe8949baa..14081d6b205 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/consensus/merge/UnverifiedForkchoiceListener.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/consensus/merge/UnverifiedForkchoiceListener.java @@ -1,8 +1,8 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.consensus.merge; /** The interface Unverified forkchoice listener. */ diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/EthProtocol.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/EthProtocol.java index e633d3b99ce..9af1ee1aa61 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/EthProtocol.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/EthProtocol.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/EthProtocolConfiguration.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/EthProtocolConfiguration.java index 73834831eff..93c69f92da9 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/EthProtocolConfiguration.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/EthProtocolConfiguration.java @@ -24,7 +24,7 @@ public class EthProtocolConfiguration { public static final int DEFAULT_MAX_MESSAGE_SIZE = 10 * ByteUnits.MEGABYTE; - public static final int DEFAULT_MAX_GET_BLOCK_HEADERS = 192; + public static final int DEFAULT_MAX_GET_BLOCK_HEADERS = 512; public static final int DEFAULT_MAX_GET_BLOCK_BODIES = 128; public static final int DEFAULT_MAX_GET_RECEIPTS = 256; public static final int DEFAULT_MAX_GET_NODE_DATA = 384; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/EthProtocolVersion.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/EthProtocolVersion.java index 9e2d1184cf8..239333da431 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/EthProtocolVersion.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/EthProtocolVersion.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -67,6 +67,7 @@ public class EthProtocolVersion { EthPV63.NODE_DATA, EthPV63.GET_RECEIPTS, EthPV63.RECEIPTS); + /** * eth/65 (EIP-2464, January 2020) * diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/encoding/TransactionAnnouncementDecoder.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/encoding/TransactionAnnouncementDecoder.java index 8fb89158b72..cdc1fc67fca 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/encoding/TransactionAnnouncementDecoder.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/encoding/TransactionAnnouncementDecoder.java @@ -50,6 +50,7 @@ public static Decoder getDecoder(final Capability capability) { return TransactionAnnouncementDecoder::decodeForEth66; } } + /** * Decode the list of transactions in the NewPooledTransactionHashesMessage * diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/ChainHeadEstimate.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/ChainHeadEstimate.java index 7e92c1c0a53..3b109d97ca3 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/ChainHeadEstimate.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/ChainHeadEstimate.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.eth.manager; import org.hyperledger.besu.ethereum.chain.ChainHead; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/ChainStateSnapshot.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/ChainStateSnapshot.java index fa7baa7c0a3..b486de027d8 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/ChainStateSnapshot.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/ChainStateSnapshot.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.eth.manager; import org.hyperledger.besu.ethereum.core.Difficulty; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthPeer.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthPeer.java index 4e734447035..899e7027243 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthPeer.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthPeer.java @@ -1,5 +1,5 @@ /* - * Copyright ConsenSys AG. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -103,6 +103,7 @@ protected boolean removeEldestEntry(final Map.Entry eldest) { private final PeerReputation reputation = new PeerReputation(); private final Map validationStatus = new ConcurrentHashMap<>(); private final Bytes id; + private boolean isServingSnap = false; private static final Map roundMessages; @@ -211,20 +212,20 @@ public void removeChainEstimatedHeightListener(final long listenerId) { public void recordRequestTimeout(final int requestCode) { LOG.atDebug() - .setMessage("Timed out while waiting for response from peer {}...") - .addArgument(this::getShortNodeId) + .setMessage("Timed out while waiting for response from peer {}") + .addArgument(this::getLoggableId) .log(); LOG.trace("Timed out while waiting for response from peer {}", this); - reputation.recordRequestTimeout(requestCode).ifPresent(this::disconnect); + reputation.recordRequestTimeout(requestCode, this).ifPresent(this::disconnect); } public void recordUselessResponse(final String requestType) { LOG.atTrace() - .setMessage("Received useless response for request type {} from peer {}...") + .setMessage("Received useless response for request type {} from peer {}") .addArgument(requestType) - .addArgument(this::getShortNodeId) + .addArgument(this::getLoggableId) .log(); - reputation.recordUselessResponse(System.currentTimeMillis()).ifPresent(this::disconnect); + reputation.recordUselessResponse(System.currentTimeMillis(), this).ifPresent(this::disconnect); } public void recordUsefulResponse() { @@ -262,9 +263,9 @@ public RequestManager.ResponseStream send( if (connectionToUse.getAgreedCapabilities().stream() .noneMatch(capability -> capability.getName().equalsIgnoreCase(protocolName))) { LOG.atDebug() - .setMessage("Protocol {} unavailable for this peer {}...") + .setMessage("Protocol {} unavailable for this peer {}") .addArgument(protocolName) - .addArgument(this.getShortNodeId()) + .addArgument(this.getLoggableId()) .log(); return null; } @@ -272,9 +273,9 @@ public RequestManager.ResponseStream send( .anyMatch( p -> !p.isMessagePermitted(connectionToUse.getRemoteEnode(), messageData.getCode()))) { LOG.info( - "Permissioning blocked sending of message code {} to {}...", + "Permissioning blocked sending of message code {} to {}", messageData.getCode(), - this.getShortNodeId()); + this.getLoggableId()); if (LOG.isDebugEnabled()) { LOG.debug( "Permissioning blocked by providers {}", @@ -290,12 +291,13 @@ public RequestManager.ResponseStream send( if (messageData.getSize() > maxMessageSize) { // This is a bug or else a misconfiguration of the max message size. LOG.error( - "Sending {} message to peer ({}) which exceeds local message size limit of {} bytes. Message code: {}, Message Size: {}", + "Dropping {} message to peer ({}) which exceeds local message size limit of {} bytes. Message code: {}, Message Size: {}", protocolName, this, maxMessageSize, messageData.getCode(), messageData.getSize()); + return null; } if (requestManagers.containsKey(protocolName)) { @@ -393,6 +395,14 @@ public RequestManager.ResponseStream getSnapTrieNode( requestManagers.get(SnapProtocol.NAME).get(SnapV1.GET_TRIE_NODES), getTrieNodes); } + public void setIsServingSnap(final boolean isServingSnap) { + this.isServingSnap = isServingSnap; + } + + public boolean isServingSnap() { + return isServingSnap; + } + private RequestManager.ResponseStream sendRequest( final RequestManager requestManager, final MessageData messageData) throws PeerNotConnected { lastRequestTimestamp = clock.millis(); @@ -401,6 +411,18 @@ private RequestManager.ResponseStream sendRequest( messageData); } + /** + * Determines the validity of a message received from a peer. A message is considered valid if + * either of the following conditions are met: 1) The message is a request type message (e.g. + * GET_BLOCK_HEADERS), or 2) The message is a response type message (e.g. BLOCK_HEADERS), the node + * has made at least 1 request for that type of message (i.e. it has sent at least 1 + * GET_BLOCK_HEADERS request), and it has at least 1 outstanding request of that type which it + * expects to receive a response for. + * + * @param message The message being validated + * @param protocolName The protocol type of the message + * @return true if the message is valid as per the above logic, otherwise false. + */ public boolean validateReceivedMessage(final EthMessage message, final String protocolName) { checkArgument(message.getPeer().equals(this), "Mismatched message sent to peer for dispatch"); return getRequestManager(protocolName, message.getData().getCode()) @@ -425,7 +447,7 @@ Optional dispatch(final EthMessage ethMessage, final String prot localRequestManager -> localRequestManager.dispatchResponse(ethMessage), () -> { LOG.trace( - "Message {} not expected has just been received for protocol {}, peer {} ", + "Request message {} has just been received for protocol {}, peer {} ", messageCode, protocolName, this); @@ -442,6 +464,16 @@ void dispatch(final EthMessage ethMessage) { dispatch(ethMessage, protocolName); } + /** + * Attempt to get a request manager for a received response-type message e.g. BLOCK_HEADERS. If + * the message is a request-type message e.g. GET_BLOCK_HEADERS no request manager will exist so + * Optional.empty() will be returned. + * + * @param protocolName the type of protocol the message is for + * @param code the message code + * @return a request manager for the received response message, or Optional.empty() if this is a + * request message + */ private Optional getRequestManager(final String protocolName, final int code) { if (requestManagers.containsKey(protocolName)) { final Map managers = requestManagers.get(protocolName); @@ -560,9 +592,9 @@ public String getProtocolName() { } /** - * Return A read-only snapshot of this peer's current {@code chainState} } + * Return A read-only snapshot of this peer's current {@code chainState} * - * @return A read-only snapshot of this peer's current {@code chainState} } + * @return A read-only snapshot of this peer's current {@code chainState} */ public ChainHeadEstimate chainStateSnapshot() { return chainHeadState.getSnapshot(); @@ -607,19 +639,23 @@ public boolean hasSupportForMessage(final int messageCode) { @Override public String toString() { return String.format( - "PeerId: %s... %s, validated? %s, disconnected? %s, client: %s, %s, %s", - getShortNodeId(), + "PeerId: %s %s, validated? %s, disconnected? %s, client: %s, %s, %s, isServingSnap %s, has height %s, connected for %s ms", + getLoggableId(), reputation, isFullyValidated(), isDisconnected(), connection.getPeerInfo().getClientId(), connection, - connection.getPeer().getEnodeURLString()); + connection.getPeer().getEnodeURLString(), + isServingSnap, + chainHeadState.getEstimatedHeight(), + System.currentTimeMillis() - connection.getInitiatedAt()); } @Nonnull - public String getShortNodeId() { - return nodeId().toString().substring(0, 20); + public String getLoggableId() { + // 8 bytes plus the 0x prefix is 18 characters + return nodeId().toString().substring(0, 18) + "..."; } @Override @@ -675,7 +711,11 @@ private int compareDuplicateConnections(final PeerConnection a, final PeerConnec } } // Otherwise, keep older connection - LOG.trace("comparing timestamps " + a.getInitiatedAt() + " with " + b.getInitiatedAt()); + LOG.atTrace() + .setMessage("comparing timestamps {} with {}") + .addArgument(a.getInitiatedAt()) + .addArgument(b.getInitiatedAt()) + .log(); return a.getInitiatedAt() < b.getInitiatedAt() ? -1 : 1; } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthPeers.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthPeers.java index 36f57b15686..d1a54d4d3d3 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthPeers.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthPeers.java @@ -14,8 +14,16 @@ */ package org.hyperledger.besu.ethereum.eth.manager; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.eth.SnapProtocol; import org.hyperledger.besu.ethereum.eth.manager.EthPeer.DisconnectCallback; import org.hyperledger.besu.ethereum.eth.peervalidation.PeerValidator; +import org.hyperledger.besu.ethereum.eth.sync.ChainHeadTracker; +import org.hyperledger.besu.ethereum.eth.sync.SnapServerChecker; +import org.hyperledger.besu.ethereum.eth.sync.SyncMode; +import org.hyperledger.besu.ethereum.eth.sync.TrailingPeerRequirements; +import org.hyperledger.besu.ethereum.forkid.ForkId; +import org.hyperledger.besu.ethereum.forkid.ForkIdManager; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.p2p.peers.Peer; import org.hyperledger.besu.ethereum.p2p.rlpx.RlpxAgent; @@ -35,30 +43,35 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.annotation.Nonnull; import com.google.common.annotations.VisibleForTesting; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.cache.RemovalNotification; import org.apache.tuweni.bytes.Bytes; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class EthPeers { private static final Logger LOG = LoggerFactory.getLogger(EthPeers.class); public static final Comparator TOTAL_DIFFICULTY = - Comparator.comparing(((final EthPeer p) -> p.chainState().getEstimatedTotalDifficulty())); + Comparator.comparing((final EthPeer p) -> p.chainState().getEstimatedTotalDifficulty()); public static final Comparator CHAIN_HEIGHT = - Comparator.comparing(((final EthPeer p) -> p.chainState().getEstimatedHeight())); + Comparator.comparing((final EthPeer p) -> p.chainState().getEstimatedHeight()); + + public static final Comparator MOST_USEFUL_PEER = + Comparator.comparing((final EthPeer p) -> p.getReputation().getScore()) + .thenComparing(CHAIN_HEIGHT); public static final Comparator HEAVIEST_CHAIN = TOTAL_DIFFICULTY.thenComparing(CHAIN_HEIGHT); @@ -67,8 +80,9 @@ public class EthPeers { Comparator.comparing(EthPeer::outstandingRequests) .thenComparing(EthPeer::getLastRequestTimestamp); public static final int NODE_ID_LENGTH = 64; + public static final int USEFULL_PEER_SCORE_THRESHOLD = 102; - private final Map completeConnections = new ConcurrentHashMap<>(); + private final Map activeConnections = new ConcurrentHashMap<>(); private final Cache incompleteConnections = CacheBuilder.newBuilder() @@ -83,18 +97,27 @@ public class EthPeers { private final Subscribers connectCallbacks = Subscribers.create(); private final Subscribers disconnectCallbacks = Subscribers.create(); private final Collection pendingRequests = new CopyOnWriteArrayList<>(); - private final int peerLowerBound; private final int peerUpperBound; private final int maxRemotelyInitiatedConnections; private final Boolean randomPeerPriority; private final Bytes nodeIdMask = Bytes.random(NODE_ID_LENGTH); private final Supplier currentProtocolSpecSupplier; + private final SyncMode syncMode; + private final ForkIdManager forkIdManager; + private final int snapServerTargetNumber; + private final boolean shouldLimitRemoteConnections; private Comparator bestPeerComparator; private final Bytes localNodeId; private RlpxAgent rlpxAgent; private final Counter connectedPeersCounter; + // private List protocolManagers; + private ChainHeadTracker tracker; + private SnapServerChecker snapServerChecker; + private boolean snapServerPeersNeeded = false; + private Supplier trailingPeerRequirementsSupplier = + () -> TrailingPeerRequirements.UNRESTRICTED; public EthPeers( final String protocolName, @@ -104,10 +127,11 @@ public EthPeers( final int maxMessageSize, final List permissioningProviders, final Bytes localNodeId, - final int peerLowerBound, final int peerUpperBound, final int maxRemotelyInitiatedConnections, - final Boolean randomPeerPriority) { + final Boolean randomPeerPriority, + final SyncMode syncMode, + final ForkIdManager forkIdManager) { this.protocolName = protocolName; this.currentProtocolSpecSupplier = currentProtocolSpecSupplier; this.clock = clock; @@ -115,20 +139,26 @@ public EthPeers( this.maxMessageSize = maxMessageSize; this.bestPeerComparator = HEAVIEST_CHAIN; this.localNodeId = localNodeId; - this.peerLowerBound = peerLowerBound; this.peerUpperBound = peerUpperBound; this.maxRemotelyInitiatedConnections = maxRemotelyInitiatedConnections; this.randomPeerPriority = randomPeerPriority; - LOG.trace( - "MaxPeers: {}, Lower Bound: {}, Max Remote: {}", - peerUpperBound, - peerLowerBound, - maxRemotelyInitiatedConnections); + LOG.trace("MaxPeers: {}, Max Remote: {}", peerUpperBound, maxRemotelyInitiatedConnections); + this.syncMode = syncMode; + this.forkIdManager = forkIdManager; + this.snapServerTargetNumber = + peerUpperBound / 2; // 50% of peers should be snap servers while snap syncing + this.shouldLimitRemoteConnections = maxRemotelyInitiatedConnections < peerUpperBound; + metricsSystem.createIntegerGauge( BesuMetricCategory.ETHEREUM, "peer_count", "The current number of peers connected", - () -> (int) streamAvailablePeers().filter(p -> p.readyForRequests()).count()); + activeConnections::size); + metricsSystem.createIntegerGauge( + BesuMetricCategory.ETHEREUM, + "peer_count_snap_server", + "The current number of peers connected that serve snap data", + () -> (int) streamAvailablePeers().filter(EthPeer::isServingSnap).count()); metricsSystem.createIntegerGauge( BesuMetricCategory.PEERS, "pending_peer_requests_current", @@ -139,6 +169,7 @@ public EthPeers( "peer_limit", "The maximum number of peers this node allows to connect", () -> peerUpperBound); + connectedPeersCounter = metricsSystem.createCounter( BesuMetricCategory.PEERS, "connected_total", "Total number of peers connected"); @@ -148,7 +179,7 @@ public void registerNewConnection( final PeerConnection newConnection, final List peerValidators) { final Bytes id = newConnection.getPeer().getId(); synchronized (this) { - EthPeer ethPeer = completeConnections.get(id); + EthPeer ethPeer = activeConnections.get(id); if (ethPeer == null) { final Optional peerInList = incompleteConnections.asMap().values().stream() @@ -170,11 +201,7 @@ public void registerNewConnection( } } - public int getPeerLowerBound() { - return peerLowerBound; - } - - @NotNull + @Nonnull private List getIncompleteConnections(final Bytes id) { return incompleteConnections.asMap().keySet().stream() .filter(nrc -> nrc.getPeer().getId().equals(id)) @@ -182,23 +209,35 @@ private List getIncompleteConnections(final Bytes id) { } public boolean registerDisconnect(final PeerConnection connection) { - final Bytes id = connection.getPeer().getId(); - final EthPeer peer = completeConnections.get(id); - return registerDisconnect(id, peer, connection); + final EthPeer peer = peer(connection); + return registerDisconnect(peer, connection); } - private boolean registerDisconnect( - final Bytes id, final EthPeer peer, final PeerConnection connection) { + private boolean registerDisconnect(final EthPeer peer, final PeerConnection connection) { incompleteConnections.invalidate(connection); boolean removed = false; - if (peer != null && peer.getConnection().equals(connection)) { + if (peer == null) { + LOG.atTrace() + .setMessage("attempt to remove null peer with connection {}") + .addArgument(connection) + .log(); + return false; + } + if (peer.getConnection().equals(connection)) { + final Bytes id = peer.getId(); if (!peerHasIncompleteConnection(id)) { - removed = completeConnections.remove(id, peer); + removed = activeConnections.remove(id, peer); disconnectCallbacks.forEach(callback -> callback.onDisconnect(peer)); peer.handleDisconnect(); abortPendingRequestsAssignedToDisconnectedPeers(); - LOG.debug("Disconnected EthPeer {}...", peer.getShortNodeId()); - LOG.trace("Disconnected EthPeer {}", peer); + if (peer.getReputation().getScore() > USEFULL_PEER_SCORE_THRESHOLD) { + LOG.atDebug().setMessage("Disconnected USEFUL peer {}").addArgument(peer).log(); + } else { + LOG.atDebug() + .setMessage("Disconnected EthPeer {}") + .addArgument(peer.getLoggableId()) + .log(); + } } } reattemptPendingPeerRequests(); @@ -220,16 +259,8 @@ private void abortPendingRequestsAssignedToDisconnectedPeers() { } public EthPeer peer(final PeerConnection connection) { - try { - return incompleteConnections.get( - connection, () -> completeConnections.get(connection.getPeer().getId())); - } catch (final ExecutionException e) { - throw new RuntimeException(e); - } - } - - public EthPeer peer(final Bytes peerId) { - return completeConnections.get(peerId); + final EthPeer ethPeer = incompleteConnections.getIfPresent(connection); + return ethPeer != null ? ethPeer : activeConnections.get(connection.getPeer().getId()); } public PendingPeerRequest executePeerRequest( @@ -267,7 +298,7 @@ public void dispatchMessage(final EthPeer peer, final EthMessage ethMessage) { @VisibleForTesting void reattemptPendingPeerRequests() { synchronized (this) { - final List peers = streamAvailablePeers().collect(Collectors.toList()); + final List peers = streamAvailablePeers().toList(); final Iterator iterator = pendingRequests.iterator(); while (iterator.hasNext() && peers.stream().anyMatch(EthPeer::hasAvailableRequestCapacity)) { final PendingPeerRequest request = iterator.next(); @@ -292,7 +323,7 @@ public void subscribeDisconnect(final DisconnectCallback callback) { public int peerCount() { removeDisconnectedPeers(); - return completeConnections.size(); + return activeConnections.size(); } public int getMaxPeers() { @@ -300,32 +331,32 @@ public int getMaxPeers() { } public Stream streamAllPeers() { - return completeConnections.values().stream(); + return activeConnections.values().stream(); } private void removeDisconnectedPeers() { - completeConnections + activeConnections .values() .forEach( ep -> { if (ep.isDisconnected()) { - registerDisconnect(ep.getId(), ep, ep.getConnection()); + registerDisconnect(ep, ep.getConnection()); } }); } public Stream streamAvailablePeers() { - return streamAllPeers() - .filter(EthPeer::readyForRequests) - .filter(peer -> !peer.isDisconnected()); + return streamAllPeers().filter(peer -> !peer.isDisconnected()); } public Stream streamBestPeers() { - return streamAvailablePeers().sorted(getBestChainComparator().reversed()); + return streamAvailablePeers() + .filter(EthPeer::isFullyValidated) + .sorted(getBestPeerComparator().reversed()); } public Optional bestPeer() { - return streamAvailablePeers().max(getBestChainComparator()); + return streamAvailablePeers().max(getBestPeerComparator()); } public Optional bestPeerWithHeightEstimate() { @@ -334,15 +365,15 @@ public Optional bestPeerWithHeightEstimate() { } public Optional bestPeerMatchingCriteria(final Predicate matchesCriteria) { - return streamAvailablePeers().filter(matchesCriteria).max(getBestChainComparator()); + return streamAvailablePeers().filter(matchesCriteria).max(getBestPeerComparator()); } - public void setBestChainComparator(final Comparator comparator) { + public void setBestPeerComparator(final Comparator comparator) { LOG.info("Updating the default best peer comparator"); bestPeerComparator = comparator; } - public Comparator getBestChainComparator() { + public Comparator getBestPeerComparator() { return bestPeerComparator; } @@ -350,56 +381,90 @@ public void setRlpxAgent(final RlpxAgent rlpxAgent) { this.rlpxAgent = rlpxAgent; } - public Stream getAllActiveConnections() { - return completeConnections.values().stream() + public Stream streamAllActiveConnections() { + return activeConnections.values().stream() .map(EthPeer::getConnection) .filter(c -> !c.isDisconnected()); } - public Stream getAllConnections() { + public Stream streamAllConnections() { return Stream.concat( - completeConnections.values().stream().map(EthPeer::getConnection), + activeConnections.values().stream().map(EthPeer::getConnection), incompleteConnections.asMap().keySet().stream()) .distinct() .filter(c -> !c.isDisconnected()); } - public boolean shouldConnect(final Peer peer, final boolean inbound) { - if (peerCount() >= peerUpperBound) { - return false; + public boolean shouldTryToConnect(final Peer peer, final boolean inbound) { + + if (peer.getForkId().isPresent()) { + final ForkId forkId = peer.getForkId().get(); + if (!forkIdManager.peerCheck(forkId)) { + LOG.atDebug() + .setMessage("Wrong fork id, not trying to connect to peer {}") + .addArgument(peer::getId) + .log(); + + return false; + } } + final Bytes id = peer.getId(); - final EthPeer ethPeer = completeConnections.get(id); - if (ethPeer != null && !ethPeer.isDisconnected()) { + if (alreadyConnectedOrConnecting(inbound, id)) { + LOG.atTrace() + .setMessage("not connecting to peer {} - already connected") + .addArgument(peer.getLoggableId()) + .log(); return false; } - final List incompleteConnections = getIncompleteConnections(id); - if (!incompleteConnections.isEmpty()) { - if (incompleteConnections.stream() - .anyMatch(c -> !c.isDisconnected() && (!inbound || (inbound && c.inboundInitiated())))) { - return false; - } + + return peerCount() < getMaxPeers() || needMoreSnapServers() || canExceedPeerLimits(id); + } + + private boolean alreadyConnectedOrConnecting(final boolean inbound, final Bytes id) { + final EthPeer ethPeer = activeConnections.get(id); + if (ethPeer != null && !ethPeer.isDisconnected()) { + return true; } - return true; + final List incompleteConnections = getIncompleteConnections(id); + return incompleteConnections.stream() + .anyMatch(c -> !c.isDisconnected() && (!inbound || (inbound && c.inboundInitiated()))); } public void disconnectWorstUselessPeer() { streamAvailablePeers() - .sorted(getBestChainComparator()) - .findFirst() + .filter(p -> !canExceedPeerLimits(p.getId())) + .min(getBestPeerComparator()) .ifPresent( peer -> { LOG.atDebug() .setMessage( - "disconnecting peer {}... Waiting for better peers. Current {} of max {}") - .addArgument(peer::getShortNodeId) + "disconnecting peer {}. Waiting for better peers. Current {} of max {}") + .addArgument(peer::getLoggableId) .addArgument(this::peerCount) .addArgument(this::getMaxPeers) .log(); - peer.disconnect(DisconnectMessage.DisconnectReason.USELESS_PEER); + peer.disconnect(DisconnectMessage.DisconnectReason.USELESS_PEER_BY_CHAIN_COMPARATOR); }); } + public void setChainHeadTracker(final ChainHeadTracker tracker) { + this.tracker = tracker; + } + + public void setSnapServerChecker(final SnapServerChecker checker) { + this.snapServerChecker = checker; + } + + public void snapServerPeersNeeded(final boolean b) { + this.snapServerPeersNeeded = b; + } + + public void setTrailingPeerRequirementsSupplier( + final Supplier tprSupplier) { + this.trailingPeerRequirementsSupplier = tprSupplier; + } + @FunctionalInterface public interface ConnectCallback { void onPeerConnected(EthPeer newPeer); @@ -407,29 +472,116 @@ public interface ConnectCallback { @Override public String toString() { - if (completeConnections.isEmpty()) { + if (activeConnections.isEmpty()) { return "0 EthPeers {}"; } final String connectionsList = - completeConnections.values().stream() + activeConnections.values().stream() .sorted() .map(EthPeer::toString) .collect(Collectors.joining(", \n")); - return completeConnections.size() + " EthPeers {\n" + connectionsList + '}'; + return activeConnections.size() + " EthPeers {\n" + connectionsList + '}'; } private void ethPeerStatusExchanged(final EthPeer peer) { - if (addPeerToEthPeers(peer)) { - connectedPeersCounter.inc(); - connectCallbacks.forEach(cb -> cb.onPeerConnected(peer)); + // We have a connection to a peer that is on the right chain and is willing to connect to us. + // Find out what the EthPeer block height is and whether it can serve snap data (if we are doing + // snap sync) + LOG.debug("Peer {} status exchanged", peer); + assert tracker != null : "ChainHeadTracker must be set before EthPeers can be used"; + CompletableFuture future = tracker.getBestHeaderFromPeer(peer); + + future.whenComplete( + (peerHeadBlockHeader, error) -> { + if (peerHeadBlockHeader == null) { + LOG.debug( + "Failed to retrieve chain head info. Disconnecting {}... {}", + peer.getLoggableId(), + error); + peer.disconnect( + DisconnectMessage.DisconnectReason.USELESS_PEER_FAILED_TO_RETRIEVE_CHAIN_HEAD); + } else { + + // we can check trailing peers now + final TrailingPeerRequirements trailingPeerRequirements = + trailingPeerRequirementsSupplier.get(); + if (trailingPeerRequirements != null) { + if (peer.chainState().getEstimatedHeight() + < trailingPeerRequirements.getMinimumHeightToBeUpToDate()) { + if (!(getNumTrailingPeers(trailingPeerRequirements.getMinimumHeightToBeUpToDate()) + < trailingPeerRequirements.getMaxTrailingPeers())) { + LOG.atTrace() + .setMessage( + "Adding trailing peer {} would exceed max trailing peers {}. Disconnecting...") + .addArgument(peer.getLoggableId()) + .addArgument(trailingPeerRequirements.getMaxTrailingPeers()) + .log(); + peer.disconnect( + DisconnectMessage.DisconnectReason.USELESS_PEER_EXCEEDS_TRAILING_PEERS); + return; + } + } + } + + peer.chainState().updateHeightEstimate(peerHeadBlockHeader.getNumber()); + CompletableFuture isServingSnapFuture; + if (syncMode == SyncMode.SNAP || syncMode == SyncMode.CHECKPOINT) { + // even if we have finished the snap sync, we still want to know if the peer is a snap + // server + isServingSnapFuture = + CompletableFuture.runAsync( + () -> { + try { + checkIsSnapServer(peer, peerHeadBlockHeader); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } else { + isServingSnapFuture = CompletableFuture.completedFuture(null); + } + isServingSnapFuture.thenRun( + () -> { + if (!peer.getConnection().isDisconnected() && addPeerToEthPeers(peer)) { + connectedPeersCounter.inc(); + connectCallbacks.forEach(cb -> cb.onPeerConnected(peer)); + } + }); + } + }); + } + + private void checkIsSnapServer(final EthPeer peer, final BlockHeader peersHeadBlockHeader) { + if (peer.getAgreedCapabilities().contains(SnapProtocol.SNAP1)) { + if (snapServerChecker != null) { + // set that peer is a snap server for doing the test + peer.setIsServingSnap(true); + Boolean isServer; + try { + isServer = snapServerChecker.check(peer, peersHeadBlockHeader).get(6L, TimeUnit.SECONDS); + } catch (Exception e) { + LOG.atTrace() + .setMessage("Error checking if peer {} is a snap server. Setting to false.") + .addArgument(peer.getLoggableId()) + .log(); + peer.setIsServingSnap(false); + return; + } + peer.setIsServingSnap(isServer); + LOG.atTrace() + .setMessage("{}: peer {}") + .addArgument(isServer ? "Is a snap server" : "Is NOT a snap server") + .addArgument(peer.getLoggableId()) + .log(); + } } } private int comparePeerPriorities(final EthPeer p1, final EthPeer p2) { final PeerConnection a = p1.getConnection(); final PeerConnection b = p2.getConnection(); - final boolean aCanExceedPeerLimits = canExceedPeerLimits(a); - final boolean bCanExceedPeerLimits = canExceedPeerLimits(b); + final boolean aCanExceedPeerLimits = canExceedPeerLimits(a.getPeer().getId()); + final boolean bCanExceedPeerLimits = canExceedPeerLimits(b.getPeer().getId()); if (aCanExceedPeerLimits && !bCanExceedPeerLimits) { return -1; } else if (bCanExceedPeerLimits && !aCanExceedPeerLimits) { @@ -441,11 +593,11 @@ private int comparePeerPriorities(final EthPeer p1, final EthPeer p2) { } } - private boolean canExceedPeerLimits(final PeerConnection a) { + private boolean canExceedPeerLimits(final Bytes peerId) { if (rlpxAgent == null) { - return true; + return false; } - return rlpxAgent.canExceedConnectionLimits(a.getPeer()); + return rlpxAgent.canExceedConnectionLimits(peerId); } private int compareConnectionInitiationTimes(final PeerConnection a, final PeerConnection b) { @@ -457,14 +609,14 @@ private int compareByMaskedNodeId(final PeerConnection a, final PeerConnection b } private void enforceRemoteConnectionLimits() { - if (!shouldLimitRemoteConnections() || peerCount() < maxRemotelyInitiatedConnections) { + if (!shouldLimitRemoteConnections || peerCount() < maxRemotelyInitiatedConnections) { // Nothing to do return; } getActivePrioritizedPeers() .filter(p -> p.getConnection().inboundInitiated()) - .filter(p -> !canExceedPeerLimits(p.getConnection())) + .filter(p -> !canExceedPeerLimits(p.getId())) .skip(maxRemotelyInitiatedConnections) .forEach( conn -> { @@ -477,7 +629,7 @@ private void enforceRemoteConnectionLimits() { } private Stream getActivePrioritizedPeers() { - return completeConnections.values().stream() + return activeConnections.values().stream() .filter(p -> !p.isDisconnected()) .sorted(this::comparePeerPriorities); } @@ -488,10 +640,9 @@ private void enforceConnectionLimits() { return; } getActivePrioritizedPeers() - .filter(p -> !p.isDisconnected()) .skip(peerUpperBound) .map(EthPeer::getConnection) - .filter(c -> !canExceedPeerLimits(c)) + .filter(c -> !canExceedPeerLimits(c.getPeer().getId())) .forEach( conn -> { LOG.trace( @@ -502,21 +653,17 @@ private void enforceConnectionLimits() { }); } - private boolean remoteConnectionLimitReached() { - return shouldLimitRemoteConnections() - && countUntrustedRemotelyInitiatedConnections() >= maxRemotelyInitiatedConnections; - } - - private boolean shouldLimitRemoteConnections() { - return maxRemotelyInitiatedConnections < peerUpperBound; + private boolean inboundInitiatedConnectionLimitExceeded() { + return shouldLimitRemoteConnections + && countUntrustedRemotelyInitiatedConnections() > maxRemotelyInitiatedConnections; } private long countUntrustedRemotelyInitiatedConnections() { - return completeConnections.values().stream() - .map(ep -> ep.getConnection()) - .filter(c -> c.inboundInitiated()) + return activeConnections.values().stream() + .map(EthPeer::getConnection) + .filter(PeerConnection::inboundInitiated) .filter(c -> !c.isDisconnected()) - .filter(conn -> !canExceedPeerLimits(conn)) + .filter(conn -> !canExceedPeerLimits(conn.getPeer().getId())) .count(); } @@ -524,56 +671,123 @@ private void onCacheRemoval( final RemovalNotification removalNotification) { if (removalNotification.wasEvicted()) { final PeerConnection peerConnectionRemoved = removalNotification.getKey(); - final PeerConnection peerConnectionOfEthPeer = removalNotification.getValue().getConnection(); - if (!peerConnectionRemoved.equals(peerConnectionOfEthPeer)) { - // If this connection is not the connection of the EthPeer by now we can disconnect - peerConnectionRemoved.disconnect(DisconnectMessage.DisconnectReason.ALREADY_CONNECTED); + final EthPeer peer = removalNotification.getValue(); + if (peer == null) { + return; + } + final PeerConnection peerConnectionOfEthPeer = peer.getConnection(); + if (peerConnectionRemoved != null) { + if (!peerConnectionRemoved.equals(peerConnectionOfEthPeer)) { + // If this connection is not the connection of the EthPeer by now we can disconnect + peerConnectionRemoved.disconnect(DisconnectMessage.DisconnectReason.ALREADY_CONNECTED); + } } } } - private boolean addPeerToEthPeers(final EthPeer peer) { + boolean addPeerToEthPeers(final EthPeer peer) { // We have a connection to a peer that is on the right chain and is willing to connect to us. - // Figure out whether we want to keep this peer and add it to the EthPeers connections. - if (completeConnections.containsValue(peer)) { + // Figure out whether we want to add it to the active connections. + final PeerConnection connection = peer.getConnection(); + if (activeConnections.containsValue(peer)) { return false; } - final PeerConnection connection = peer.getConnection(); + final Bytes id = peer.getId(); if (!randomPeerPriority) { - // Disconnect if too many peers - if (!canExceedPeerLimits(connection) && peerCount() >= peerUpperBound) { - LOG.trace( - "Too many peers. Disconnect connection: {}, max connections {}", - connection, - peerUpperBound); - connection.disconnect(DisconnectMessage.DisconnectReason.TOO_MANY_PEERS); - return false; - } - // Disconnect if too many remotely-initiated connections - if (connection.inboundInitiated() - && !canExceedPeerLimits(connection) - && remoteConnectionLimitReached()) { - LOG.trace( - "Too many remotely-initiated connections. Disconnect incoming connection: {}, maxRemote={}", - connection, - maxRemotelyInitiatedConnections); - connection.disconnect(DisconnectMessage.DisconnectReason.TOO_MANY_PEERS); - return false; + + if (peerCount() >= peerUpperBound) { + final long numSnapServers = numberOfSnapServers(); + final boolean inboundLimitExceeded = inboundInitiatedConnectionLimitExceeded(); + // three reasons why we would disconnect an existing peer to accommodate the new peer + if (canExceedPeerLimits(id) + || (snapServerPeersNeeded + && numSnapServers < snapServerTargetNumber + && peer.isServingSnap()) + || (inboundLimitExceeded && !peer.getConnection().inboundInitiated())) { + + final boolean filterOutSnapServers = + snapServerPeersNeeded && (numSnapServers <= snapServerTargetNumber); + + // find and disconnect the least useful peer we can disconnect + activeConnections.values().stream() + .filter(p -> !canExceedPeerLimits(p.getId())) + .filter(filterOutSnapServers ? p -> !p.isServingSnap() : p -> true) + .filter(inboundLimitExceeded ? p -> p.getConnection().inboundInitiated() : p -> true) + .min(MOST_USEFUL_PEER) + .ifPresentOrElse( + pe -> { + pe.disconnect(DisconnectMessage.DisconnectReason.TOO_MANY_PEERS); + LOG.atTrace() + .setMessage("Disconnecting peer {} to be replaced by prioritised peer {}") + .addArgument(pe.getLoggableId()) + .addArgument(peer.getLoggableId()) + .log(); + }, + () -> // disconnect the least useful peer + activeConnections.values().stream() + .filter(p -> !canExceedPeerLimits(p.getId())) + .min(MOST_USEFUL_PEER) + .ifPresent( + p -> { + p.disconnect(DisconnectMessage.DisconnectReason.TOO_MANY_PEERS); + LOG.atTrace() + .setMessage( + "Disconnecting peer {} to be replaced by prioritised peer {}") + .addArgument(p.getLoggableId()) + .addArgument(peer.getLoggableId()) + .log(); + })); + } else { + LOG.atTrace() + .setMessage( + "Too many peers. Disconnect peer {} with connection: {}, max connections {}") + .addArgument(peer.getLoggableId()) + .addArgument(connection) + .addArgument(peerUpperBound) + .log(); + connection.disconnect(DisconnectMessage.DisconnectReason.TOO_MANY_PEERS); + return false; + } } - final boolean added = (completeConnections.putIfAbsent(id, peer) == null); + + final boolean added = (activeConnections.putIfAbsent(id, peer) == null); if (added) { - LOG.trace("Added peer {} with connection {} to completeConnections", id, connection); + LOG.atTrace() + .setMessage("Added peer {} with connection {} to activeConnections") + .addArgument(id) + .addArgument(connection) + .log(); } else { - LOG.trace("Did not add peer {} with connection {} to completeConnections", id, connection); + LOG.atTrace() + .setMessage("Did not add peer {} with connection {} to activeConnections") + .addArgument(id) + .addArgument(connection) + .log(); } return added; + } else { // randomPeerPriority! Add the peer and if there are too many connections fix it - completeConnections.putIfAbsent(id, peer); + // TODO: random peer priority does not care yet about snap server peers -> check later + activeConnections.putIfAbsent(id, peer); enforceRemoteConnectionLimits(); enforceConnectionLimits(); - return completeConnections.containsKey(id); + return activeConnections.containsKey(id); } } + + private long getNumTrailingPeers(final long minimumHeightToBeUpToDate) { + return streamAvailablePeers() + .filter(p -> p.chainState().getEstimatedHeight() < minimumHeightToBeUpToDate) + .count(); + } + + private boolean needMoreSnapServers() { + return snapServerPeersNeeded && numberOfSnapServers() < snapServerTargetNumber; + } + + private long numberOfSnapServers() { + return activeConnections.values().stream().filter(EthPeer::isServingSnap).count(); + } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolLogger.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolLogger.java index 0b57488b193..c4a613c2e0a 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolLogger.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolLogger.java @@ -1,5 +1,5 @@ /* - * Copyright Contributors to Hyperledger Besu. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManager.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManager.java index 8b14197b007..58318f9611e 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManager.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManager.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -34,7 +34,6 @@ import org.hyperledger.besu.ethereum.forkid.ForkId; import org.hyperledger.besu.ethereum.forkid.ForkIdManager; import org.hyperledger.besu.ethereum.p2p.network.ProtocolManager; -import org.hyperledger.besu.ethereum.p2p.peers.Peer; import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection; import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection.PeerNotConnected; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Capability; @@ -108,9 +107,10 @@ public EthProtocolManager( this.ethMessages = ethMessages; this.ethContext = ethContext; - this.blockBroadcaster = new BlockBroadcaster(ethContext); + this.blockBroadcaster = + new BlockBroadcaster(ethContext, ethereumWireProtocolConfiguration.getMaxMessageSize()); - supportedCapabilities = + this.supportedCapabilities = calculateCapabilities(synchronizerConfiguration, ethereumWireProtocolConfiguration); // Run validators @@ -161,41 +161,6 @@ public EthProtocolManager( ethereumWireProtocolConfiguration.isLegacyEth64ForkIdEnabled())); } - public EthProtocolManager( - final Blockchain blockchain, - final BigInteger networkId, - final WorldStateArchive worldStateArchive, - final TransactionPool transactionPool, - final EthProtocolConfiguration ethereumWireProtocolConfiguration, - final EthPeers ethPeers, - final EthMessages ethMessages, - final EthContext ethContext, - final List peerValidators, - final Optional mergePeerFilter, - final SynchronizerConfiguration synchronizerConfiguration, - final EthScheduler scheduler, - final List blockNumberForks, - final List timestampForks) { - this( - blockchain, - networkId, - worldStateArchive, - transactionPool, - ethereumWireProtocolConfiguration, - ethPeers, - ethMessages, - ethContext, - peerValidators, - mergePeerFilter, - synchronizerConfiguration, - scheduler, - new ForkIdManager( - blockchain, - blockNumberForks, - timestampForks, - ethereumWireProtocolConfiguration.isLegacyEth64ForkIdEnabled())); - } - public EthContext ethContext() { return ethContext; } @@ -252,11 +217,14 @@ public List getSupportedCapabilities() { @Override public void stop() { if (stopped.compareAndSet(false, true)) { - LOG.info("Stopping {} Subprotocol.", getSupportedProtocol()); + LOG.atInfo().setMessage("Stopping {} Subprotocol.").addArgument(getSupportedProtocol()).log(); scheduler.stop(); shutdown.countDown(); } else { - LOG.error("Attempted to stop already stopped {} Subprotocol.", getSupportedProtocol()); + LOG.atInfo() + .setMessage("Attempted to stop already stopped {} Subprotocol.") + .addArgument(this::getSupportedProtocol) + .log(); } } @@ -264,7 +232,10 @@ public void stop() { public void awaitStop() throws InterruptedException { shutdown.await(); scheduler.awaitStop(); - LOG.info("{} Subprotocol stopped.", getSupportedProtocol()); + LOG.atInfo() + .setMessage("{} Subprotocol stopped.") + .addArgument(this::getSupportedProtocol) + .log(); } @Override @@ -277,8 +248,10 @@ public void processMessage(final Capability cap, final Message message) { EthProtocolLogger.logProcessMessage(cap, code); final EthPeer ethPeer = ethPeers.peer(message.getConnection()); if (ethPeer == null) { - LOG.debug( - "Ignoring message received from unknown peer connection: {}", message.getConnection()); + LOG.atDebug() + .setMessage("Ignoring message received from unknown peer connection: {}") + .addArgument(message::getConnection) + .log(); return; } @@ -288,20 +261,26 @@ public void processMessage(final Capability cap, final Message message) { return; } else if (!ethPeer.statusHasBeenReceived()) { // Peers are required to send status messages before any other message type - LOG.debug( - "{} requires a Status ({}) message to be sent first. Instead, received message {} (BREACH_OF_PROTOCOL). Disconnecting from {}.", - this.getClass().getSimpleName(), - EthPV62.STATUS, - code, - ethPeer); - ethPeer.disconnect(DisconnectReason.BREACH_OF_PROTOCOL); + LOG.atDebug() + .setMessage( + "{} requires a Status ({}) message to be sent first. Instead, received message {} (BREACH_OF_PROTOCOL). Disconnecting from {}.") + .addArgument(() -> this.getClass().getSimpleName()) + .addArgument(EthPV62.STATUS) + .addArgument(code) + .addArgument(ethPeer::toString) + .log(); + ethPeer.disconnect(DisconnectReason.BREACH_OF_PROTOCOL_RECEIVED_OTHER_MESSAGE_BEFORE_STATUS); return; } if (this.mergePeerFilter.isPresent()) { if (this.mergePeerFilter.get().disconnectIfGossipingBlocks(message, ethPeer)) { - LOG.debug("Post-merge disconnect: peer still gossiping blocks {}", ethPeer); - handleDisconnect(ethPeer.getConnection(), DisconnectReason.SUBPROTOCOL_TRIGGERED, false); + LOG.atDebug() + .setMessage("Post-merge disconnect: peer still gossiping blocks {}") + .addArgument(ethPeer::toString) + .log(); + handleDisconnect( + ethPeer.getConnection(), DisconnectReason.SUBPROTOCOL_TRIGGERED_POW_BLOCKS, false); return; } } @@ -310,9 +289,10 @@ public void processMessage(final Capability cap, final Message message) { if (!ethPeer.validateReceivedMessage(ethMessage, getSupportedProtocol())) { LOG.debug( - "Unsolicited message received (BREACH_OF_PROTOCOL), disconnecting from EthPeer: {}", + "Unsolicited message received {} (BREACH_OF_PROTOCOL), disconnecting from EthPeer: {}", + ethMessage.getData().getCode(), ethPeer); - ethPeer.disconnect(DisconnectReason.BREACH_OF_PROTOCOL); + ethPeer.disconnect(DisconnectReason.BREACH_OF_PROTOCOL_UNSOLICITED_MESSAGE_RECEIVED); return; } @@ -333,13 +313,15 @@ public void processMessage(final Capability cap, final Message message) { maybeResponseData = ethMessages.dispatch(ethMessage); } } catch (final RLPException e) { - LOG.debug( - "Received malformed message {} (BREACH_OF_PROTOCOL), disconnecting: {}", - messageData.getData(), - ethPeer, - e); + LOG.atDebug() + .setMessage("Received malformed message {} (BREACH_OF_PROTOCOL), disconnecting: {}, {}") + .addArgument(messageData::getData) + .addArgument(ethPeer::toString) + .addArgument(e::toString) + .log(); - ethPeer.disconnect(DisconnectMessage.DisconnectReason.BREACH_OF_PROTOCOL); + ethPeer.disconnect( + DisconnectMessage.DisconnectReason.BREACH_OF_PROTOCOL_MALFORMED_MESSAGE_RECEIVED); } maybeResponseData.ifPresent( responseData -> { @@ -368,24 +350,17 @@ public void handleNewConnection(final PeerConnection connection) { genesisHash, latestForkId); try { - LOG.trace("Sending status message to {} for connection {}.", peer.getId(), connection); + LOG.atTrace() + .setMessage("Sending status message to {} for connection {}.") + .addArgument(peer::getId) + .addArgument(connection::toString) + .log(); peer.send(status, getSupportedProtocol(), connection); peer.registerStatusSent(connection); } catch (final PeerNotConnected peerNotConnected) { // Nothing to do. } - LOG.trace("{}", ethPeers); - } - - @Override - public boolean shouldConnect(final Peer peer, final boolean incoming) { - if (peer.getForkId().map(forkId -> forkIdManager.peerCheck(forkId)).orElse(true)) { - LOG.trace("ForkId OK or not available"); - if (ethPeers.shouldConnect(peer, incoming)) { - return true; - } - } - return false; + LOG.atTrace().setMessage("{}").addArgument(ethPeers::toString).log(); } @Override @@ -393,16 +368,17 @@ public void handleDisconnect( final PeerConnection connection, final DisconnectReason reason, final boolean initiatedByPeer) { - if (ethPeers.registerDisconnect(connection)) { - LOG.atDebug() - .setMessage("Disconnect - {} - {} - {}... - {} peers left") - .addArgument(initiatedByPeer ? "Inbound" : "Outbound") - .addArgument(reason) - .addArgument(connection.getPeer().getId().slice(0, 8)) - .addArgument(ethPeers.peerCount()) - .log(); - LOG.trace("{}", ethPeers); - } + final boolean wasActiveConnection = ethPeers.registerDisconnect(connection); + LOG.atDebug() + .setMessage("Disconnect - active Connection? {} - {} - {} - {} {} - {} peers left") + .addArgument(wasActiveConnection) + .addArgument(initiatedByPeer ? "Inbound" : "Outbound") + .addArgument(reason::toString) + .addArgument(() -> connection.getPeer().getLoggableId()) + .addArgument(() -> connection.getPeerInfo().getClientId()) + .addArgument(ethPeers::peerCount) + .log(); + LOG.atTrace().setMessage("{}").addArgument(ethPeers::toString).log(); } private void handleStatusMessage(final EthPeer peer, final Message message) { @@ -412,43 +388,42 @@ private void handleStatusMessage(final EthPeer peer, final Message message) { try { if (!status.networkId().equals(networkId)) { LOG.atDebug() - .setMessage("Mismatched network id: {}, EthPeer {}...") - .addArgument(status.networkId()) - .addArgument(peer.getShortNodeId()) + .setMessage("Mismatched network id: {}, peer {}") + .addArgument(status::networkId) + .addArgument(() -> getPeerOrPeerId(peer)) .log(); - LOG.atTrace() - .setMessage("Mismatched network id: {}, EthPeer {}") - .addArgument(status.networkId()) - .addArgument(peer) - .log(); - peer.disconnect(DisconnectReason.SUBPROTOCOL_TRIGGERED); + peer.disconnect(DisconnectReason.SUBPROTOCOL_TRIGGERED_MISMATCHED_NETWORK); } else if (!forkIdManager.peerCheck(forkId) && status.protocolVersion() > 63) { - LOG.debug( - "{} has matching network id ({}), but non-matching fork id: {}", - peer, - networkId, - forkId); - peer.disconnect(DisconnectReason.SUBPROTOCOL_TRIGGERED); + LOG.atDebug() + .setMessage("{} has matching network id ({}), but non-matching fork id: {}") + .addArgument(() -> getPeerOrPeerId(peer)) + .addArgument(networkId::toString) + .addArgument(forkId) + .log(); + peer.disconnect(DisconnectReason.SUBPROTOCOL_TRIGGERED_MISMATCHED_FORKID); } else if (forkIdManager.peerCheck(status.genesisHash())) { - LOG.debug( - "{} has matching network id ({}), but non-matching genesis hash: {}", - peer, - networkId, - status.genesisHash()); - peer.disconnect(DisconnectReason.SUBPROTOCOL_TRIGGERED); + LOG.atDebug() + .setMessage("{} has matching network id ({}), but non-matching genesis hash: {}") + .addArgument(() -> getPeerOrPeerId(peer)) + .addArgument(networkId::toString) + .addArgument(status::genesisHash) + .log(); + peer.disconnect(DisconnectReason.SUBPROTOCOL_TRIGGERED_MISMATCHED_GENESIS_HASH); } else if (mergePeerFilter.isPresent() && mergePeerFilter.get().disconnectIfPoW(status, peer)) { LOG.atDebug() .setMessage("Post-merge disconnect: peer still PoW {}") - .addArgument(peer.getShortNodeId()) + .addArgument(() -> getPeerOrPeerId(peer)) .log(); - handleDisconnect(peer.getConnection(), DisconnectReason.SUBPROTOCOL_TRIGGERED, false); + handleDisconnect( + peer.getConnection(), DisconnectReason.SUBPROTOCOL_TRIGGERED_POW_DIFFICULTY, false); } else { - LOG.debug( - "Received status message from {}: {} with connection {}", - peer, - status, - message.getConnection()); + LOG.atDebug() + .setMessage("Received status message from {}: {} with connection {}") + .addArgument(peer::toString) + .addArgument(status::toString) + .addArgument(message::getConnection) + .log(); peer.registerStatusReceived( status.bestHash(), status.totalDifficulty(), @@ -456,13 +431,21 @@ private void handleStatusMessage(final EthPeer peer, final Message message) { message.getConnection()); } } catch (final RLPException e) { - LOG.debug("Unable to parse status message from peer {}.", peer, e); + LOG.atDebug() + .setMessage("Unable to parse status message from peer {} {}") + .addArgument(peer::getLoggableId) + .addArgument(e) + .log(); // Parsing errors can happen when clients broadcast network ids outside the int range, // So just disconnect with "subprotocol" error rather than "breach of protocol". - peer.disconnect(DisconnectReason.SUBPROTOCOL_TRIGGERED); + peer.disconnect(DisconnectReason.SUBPROTOCOL_TRIGGERED_UNPARSABLE_STATUS); } } + private Object getPeerOrPeerId(final EthPeer peer) { + return LOG.isTraceEnabled() ? peer : peer.getLoggableId(); + } + @Override public void blockMined(final Block block) { // This assumes the block has already been included in the chain diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthScheduler.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthScheduler.java index 43f8d6a4c35..1e2f3eb6abb 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthScheduler.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthScheduler.java @@ -23,9 +23,11 @@ import java.time.Duration; import java.util.Collection; +import java.util.Queue; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; @@ -34,6 +36,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; @@ -43,7 +47,6 @@ public class EthScheduler { private static final Logger LOG = LoggerFactory.getLogger(EthScheduler.class); - private final Duration defaultTimeout = Duration.ofSeconds(5); private final AtomicBoolean stopped = new AtomicBoolean(false); private final CountDownLatch shutdown = new CountDownLatch(1); private static final int TX_WORKER_CAPACITY = 1_000; @@ -138,8 +141,12 @@ public void scheduleTxWorkerTask(final Runnable command) { txWorkerExecutor.execute(command); } - public CompletableFuture scheduleServiceTask(final Supplier task) { - return CompletableFuture.supplyAsync(task, servicesExecutor); + public void executeServiceTask(final Runnable command) { + servicesExecutor.execute(command); + } + + public CompletableFuture scheduleServiceTask(final Runnable task) { + return CompletableFuture.runAsync(task, servicesExecutor); } public CompletableFuture scheduleServiceTask(final EthTask task) { @@ -211,10 +218,6 @@ public CompletableFuture scheduleBlockCreationTask(final Runnable task) { return CompletableFuture.runAsync(task, blockCreationExecutor); } - public CompletableFuture timeout(final EthTask task) { - return timeout(task, defaultTimeout); - } - public CompletableFuture timeout(final EthTask task, final Duration timeout) { final CompletableFuture future = task.run(); final CompletableFuture result = timeout(future, timeout); @@ -240,7 +243,7 @@ private CompletableFuture timeout( public void stop() { if (stopped.compareAndSet(false, true)) { - LOG.trace("Stopping " + getClass().getSimpleName()); + LOG.atTrace().setMessage("Stopping {}").addArgument(getClass().getSimpleName()).log(); syncWorkerExecutor.shutdownNow(); txWorkerExecutor.shutdownNow(); scheduler.shutdownNow(); @@ -248,7 +251,10 @@ public void stop() { computationExecutor.shutdownNow(); shutdown.countDown(); } else { - LOG.trace("Attempted to stop already stopped " + getClass().getSimpleName()); + LOG.atTrace() + .setMessage("Attempted to stop already stopped {}") + .addArgument(getClass().getSimpleName()) + .log(); } } @@ -295,4 +301,49 @@ public void failAfterTimeout(final CompletableFuture promise, final Durat delay, unit); } + + public OrderedProcessor createOrderedProcessor(final Consumer processor) { + return new OrderedProcessor<>(processor); + } + + /** + * This class is a way to execute a set of tasks, one by one, in a strict order, without blocking + * the caller in case there are still previous tasks queued + * + * @param the class of item to be processed + */ + public class OrderedProcessor { + private final Queue blockAddedQueue = new ConcurrentLinkedQueue<>(); + private final ReentrantLock blockAddedLock = new ReentrantLock(); + private final Consumer processor; + + private OrderedProcessor(final Consumer processor) { + this.processor = processor; + } + + public void submit(final ITEM item) { + // add the item to the processing queue + blockAddedQueue.add(item); + + if (blockAddedLock.hasQueuedThreads()) { + // another thread is already waiting to process the queue with our item, there is no need to + // schedule another thread + LOG.trace( + "Block added event queue is already being processed and an already queued thread is present, nothing to do"); + } else { + servicesExecutor.submit( + () -> { + blockAddedLock.lock(); + try { + // now that we have the lock, process as many items as possible + for (ITEM i = blockAddedQueue.poll(); i != null; i = blockAddedQueue.poll()) { + processor.accept(i); + } + } finally { + blockAddedLock.unlock(); + } + }); + } + } + } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthServer.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthServer.java index 76346f4152e..d70623d7bfd 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthServer.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/EthServer.java @@ -234,7 +234,7 @@ static MessageData constructGetReceiptsResponse( } final BytesValueRLPOutput encodedReceipts = new BytesValueRLPOutput(); encodedReceipts.startList(); - maybeReceipts.get().forEach(r -> r.writeTo(encodedReceipts)); + maybeReceipts.get().forEach(r -> r.writeToForNetwork(encodedReceipts)); encodedReceipts.endList(); final int encodedSize = encodedReceipts.encodedSize(); if (responseSizeEstimate + encodedSize > maxMessageSize) { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/MergePeerFilter.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/MergePeerFilter.java index 5cb58466623..a5e0b8c5e51 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/MergePeerFilter.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/MergePeerFilter.java @@ -1,8 +1,8 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at + * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.eth.manager; import org.hyperledger.besu.consensus.merge.ForkchoiceEvent; @@ -47,7 +45,7 @@ public boolean disconnectIfPoW(final StatusMessage status, final EthPeer peer) { LOG.debug( "Disconnecting peer with difficulty {}, likely still on PoW chain", status.totalDifficulty()); - peer.disconnect(DisconnectReason.SUBPROTOCOL_TRIGGERED); + peer.disconnect(DisconnectReason.SUBPROTOCOL_TRIGGERED_POW_DIFFICULTY); return true; } else { return false; @@ -61,7 +59,7 @@ public boolean disconnectIfGossipingBlocks(final Message message, final EthPeer final int code = message.getData().getCode(); if (isFinalized() && (code == EthPV62.NEW_BLOCK || code == EthPV62.NEW_BLOCK_HASHES)) { LOG.debug("disconnecting peer for sending new blocks after transition to PoS"); - peer.disconnect(DisconnectReason.SUBPROTOCOL_TRIGGERED); + peer.disconnect(DisconnectReason.SUBPROTOCOL_TRIGGERED_POW_BLOCKS); return true; } else { return false; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/PeerReputation.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/PeerReputation.java index ff7617b0f46..8f601ed9005 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/PeerReputation.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/PeerReputation.java @@ -34,21 +34,19 @@ public class PeerReputation implements Comparable { static final long USELESS_RESPONSE_WINDOW_IN_MILLIS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES); - static final int DEFAULT_MAX_SCORE = 200; - // how much above the initial score you need to be to not get disconnected for timeouts/useless - // responses - private final int hasBeenUsefulThreshold; + static final int DEFAULT_MAX_SCORE = 150; static final int DEFAULT_INITIAL_SCORE = 100; private static final Logger LOG = LoggerFactory.getLogger(PeerReputation.class); - private static final int TIMEOUT_THRESHOLD = 5; - private static final int USELESS_RESPONSE_THRESHOLD = 5; + public static final int TIMEOUT_THRESHOLD = 5; + public static final int USELESS_RESPONSE_THRESHOLD = 5; private final ConcurrentMap timeoutCountByRequestType = new ConcurrentHashMap<>(); private final Queue uselessResponseTimes = new ConcurrentLinkedQueue<>(); private static final int SMALL_ADJUSTMENT = 1; - private static final int LARGE_ADJUSTMENT = 5; + private static final int LARGE_ADJUSTMENT = 10; + private int score; private final int maxScore; @@ -61,37 +59,24 @@ public PeerReputation(final int initialScore, final int maxScore) { checkArgument( initialScore <= maxScore, "Initial score must be less than or equal to max score"); this.maxScore = maxScore; - this.hasBeenUsefulThreshold = Math.min(maxScore, initialScore + 10); this.score = initialScore; } - public Optional recordRequestTimeout(final int requestCode) { + public Optional recordRequestTimeout( + final int requestCode, final EthPeer peer) { final int newTimeoutCount = getOrCreateTimeoutCount(requestCode).incrementAndGet(); if (newTimeoutCount >= TIMEOUT_THRESHOLD) { - score -= LARGE_ADJUSTMENT; - // don't trigger disconnect if this peer has a sufficiently high reputation score - if (peerHasNotBeenUseful()) { - LOG.debug( - "Disconnection triggered by {} repeated timeouts for requestCode {}, peer score {}", - newTimeoutCount, - requestCode, - score); - return Optional.of(DisconnectReason.TIMEOUT); - } - - LOG.trace( - "Not triggering disconnect for {} repeated timeouts for requestCode {} because peer has high score {}", + LOG.debug( + "Disconnection triggered by {} repeated timeouts for requestCode {} for peer {}", newTimeoutCount, requestCode, - score); + peer.getLoggableId()); + score -= LARGE_ADJUSTMENT; + return Optional.of(DisconnectReason.TIMEOUT); } else { score -= SMALL_ADJUSTMENT; + return Optional.empty(); } - return Optional.empty(); - } - - private boolean peerHasNotBeenUseful() { - return score < hasBeenUsefulThreshold; } public void resetTimeoutCount(final int requestCode) { @@ -106,26 +91,22 @@ public Map timeoutCounts() { return timeoutCountByRequestType; } - public Optional recordUselessResponse(final long timestamp) { + public Optional recordUselessResponse( + final long timestamp, final EthPeer peer) { uselessResponseTimes.add(timestamp); while (shouldRemove(uselessResponseTimes.peek(), timestamp)) { uselessResponseTimes.poll(); } if (uselessResponseTimes.size() >= USELESS_RESPONSE_THRESHOLD) { score -= LARGE_ADJUSTMENT; - // don't trigger disconnect if this peer has a sufficiently high reputation score - if (peerHasNotBeenUseful()) { - LOG.debug( - "Disconnection triggered by exceeding useless response threshold, score {}", score); - return Optional.of(DisconnectReason.USELESS_PEER); - } - LOG.trace( - "Not triggering disconnect for exceeding useless response threshold because peer has high score {}", - score); + LOG.debug( + "Disconnection triggered by exceeding useless response threshold for peer {}", + peer.getLoggableId()); + return Optional.of(DisconnectReason.USELESS_PEER_USELESS_RESPONSES); } else { score -= SMALL_ADJUSTMENT; + return Optional.empty(); } - return Optional.empty(); } public void recordUsefulResponse() { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/PeerRequest.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/PeerRequest.java index 09cbdb969d2..5daad56b9be 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/PeerRequest.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/PeerRequest.java @@ -19,4 +19,8 @@ public interface PeerRequest { ResponseStream sendRequest(EthPeer peer) throws PeerNotConnected; + + default boolean isEthPeerSuitable(final EthPeer ethPeer) { + return true; + } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/PendingPeerRequest.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/PendingPeerRequest.java index 120b32bdfe3..71da1e4d2d9 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/PendingPeerRequest.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/PendingPeerRequest.java @@ -86,6 +86,7 @@ private Optional getPeerToUse() { : ethPeers .streamAvailablePeers() .filter(peer -> peer.chainState().getEstimatedHeight() >= minimumBlockNumber) + .filter(request::isEthPeerSuitable) .min(EthPeers.LEAST_TO_MOST_BUSY); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/RequestManager.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/RequestManager.java index 8d163aeed80..19699bec49b 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/RequestManager.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/RequestManager.java @@ -95,7 +95,8 @@ public void dispatchResponse(final EthMessage ethMessage) { peer, e); - peer.disconnect(DisconnectMessage.DisconnectReason.BREACH_OF_PROTOCOL); + peer.disconnect( + DisconnectMessage.DisconnectReason.BREACH_OF_PROTOCOL_MALFORMED_MESSAGE_RECEIVED); } if (count == 0) { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/GetAccountRangeFromPeerTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/GetAccountRangeFromPeerTask.java index c52758eeb94..8e67ff4aeb2 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/GetAccountRangeFromPeerTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/GetAccountRangeFromPeerTask.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,10 +17,13 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; +import org.hyperledger.besu.ethereum.eth.manager.PeerRequest; import org.hyperledger.besu.ethereum.eth.manager.PendingPeerRequest; +import org.hyperledger.besu.ethereum.eth.manager.RequestManager; import org.hyperledger.besu.ethereum.eth.manager.task.AbstractPeerRequestTask; import org.hyperledger.besu.ethereum.eth.messages.snap.AccountRangeMessage; import org.hyperledger.besu.ethereum.eth.messages.snap.SnapV1; +import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; import org.hyperledger.besu.plugin.services.MetricsSystem; @@ -64,14 +67,34 @@ public static GetAccountRangeFromPeerTask forAccountRange( @Override protected PendingPeerRequest sendRequest() { return sendRequestToPeer( - peer -> { - LOG.trace( - "Requesting account range [{} ,{}] for state root {} from peer {} .", - startKeyHash, - endKeyHash, - blockHeader.getStateRoot(), - peer); - return peer.getSnapAccountRange(blockHeader.getStateRoot(), startKeyHash, endKeyHash); + new PeerRequest() { + @Override + public RequestManager.ResponseStream sendRequest(final EthPeer peer) + throws PeerConnection.PeerNotConnected { + LOG.atTrace() + .setMessage("Requesting account range [{} ,{}] for state root {} from peer {} .") + .addArgument(startKeyHash) + .addArgument(endKeyHash) + .addArgument(blockHeader) + .addArgument(peer) + .log(); + if (!peer.isServingSnap()) { + LOG.atDebug() + .setMessage("EthPeer that is not serving snap called in {}, peer: {}") + .addArgument(GetAccountRangeFromPeerTask.class) + .addArgument(peer) + .log(); + throw new RuntimeException( + "EthPeer that is not serving snap called in " + + GetAccountRangeFromPeerTask.class); + } + return peer.getSnapAccountRange(blockHeader.getStateRoot(), startKeyHash, endKeyHash); + } + + @Override + public boolean isEthPeerSuitable(final EthPeer ethPeer) { + return ethPeer.isServingSnap(); + } }, blockHeader.getNumber()); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/GetBytecodeFromPeerTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/GetBytecodeFromPeerTask.java index 357157484a0..1c8c1d12d74 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/GetBytecodeFromPeerTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/GetBytecodeFromPeerTask.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -21,10 +21,13 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; +import org.hyperledger.besu.ethereum.eth.manager.PeerRequest; import org.hyperledger.besu.ethereum.eth.manager.PendingPeerRequest; +import org.hyperledger.besu.ethereum.eth.manager.RequestManager; import org.hyperledger.besu.ethereum.eth.manager.task.AbstractPeerRequestTask; import org.hyperledger.besu.ethereum.eth.messages.snap.ByteCodesMessage; import org.hyperledger.besu.ethereum.eth.messages.snap.SnapV1; +import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; import org.hyperledger.besu.plugin.services.MetricsSystem; @@ -66,9 +69,32 @@ public static GetBytecodeFromPeerTask forBytecode( @Override protected PendingPeerRequest sendRequest() { return sendRequestToPeer( - peer -> { - LOG.trace("Requesting {} Bytecodes from {} .", codeHashes.size(), peer); - return peer.getSnapBytecode(blockHeader.getStateRoot(), codeHashes); + new PeerRequest() { + @Override + public RequestManager.ResponseStream sendRequest(final EthPeer peer) + throws PeerConnection.PeerNotConnected { + LOG.atTrace() + .setMessage("Requesting {} Bytecodes from {} .") + .addArgument(codeHashes.size()) + .addArgument(peer) + .log(); + if (!peer.isServingSnap()) { + LOG.atDebug() + .setMessage("EthPeer that is not serving snap called in {}, peer: {}") + .addArgument(GetAccountRangeFromPeerTask.class) + .addArgument(peer) + .log(); + throw new RuntimeException( + "EthPeer that is not serving snap called in " + + GetAccountRangeFromPeerTask.class); + } + return peer.getSnapBytecode(blockHeader.getStateRoot(), codeHashes); + } + + @Override + public boolean isEthPeerSuitable(final EthPeer ethPeer) { + return ethPeer.isServingSnap(); + } }, blockHeader.getNumber()); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/GetStorageRangeFromPeerTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/GetStorageRangeFromPeerTask.java index 48b3426ae42..cb3e4c83ea9 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/GetStorageRangeFromPeerTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/GetStorageRangeFromPeerTask.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,10 +17,13 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; +import org.hyperledger.besu.ethereum.eth.manager.PeerRequest; import org.hyperledger.besu.ethereum.eth.manager.PendingPeerRequest; +import org.hyperledger.besu.ethereum.eth.manager.RequestManager; import org.hyperledger.besu.ethereum.eth.manager.task.AbstractPeerRequestTask; import org.hyperledger.besu.ethereum.eth.messages.snap.SnapV1; import org.hyperledger.besu.ethereum.eth.messages.snap.StorageRangeMessage; +import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; import org.hyperledger.besu.plugin.services.MetricsSystem; @@ -69,15 +72,35 @@ public static GetStorageRangeFromPeerTask forStorageRange( @Override protected PendingPeerRequest sendRequest() { return sendRequestToPeer( - peer -> { - LOG.trace( - "Requesting storage range [{} ,{}] for {} accounts from peer {} .", - startKeyHash, - endKeyHash, - accountHashes.size(), - peer); - return peer.getSnapStorageRange( - blockHeader.getStateRoot(), accountHashes, startKeyHash, endKeyHash); + new PeerRequest() { + @Override + public RequestManager.ResponseStream sendRequest(final EthPeer peer) + throws PeerConnection.PeerNotConnected { + LOG.atTrace() + .setMessage("Requesting storage range [{} ,{}] for {} accounts from peer {} .") + .addArgument(startKeyHash) + .addArgument(endKeyHash) + .addArgument(accountHashes.size()) + .addArgument(peer) + .log(); + if (!peer.isServingSnap()) { + LOG.atDebug() + .setMessage("EthPeer that is not serving snap called in {}, peer: {}") + .addArgument(GetAccountRangeFromPeerTask.class) + .addArgument(peer) + .log(); + throw new RuntimeException( + "EthPeer that is not serving snap called in " + + GetAccountRangeFromPeerTask.class); + } + return peer.getSnapStorageRange( + blockHeader.getStateRoot(), accountHashes, startKeyHash, endKeyHash); + } + + @Override + public boolean isEthPeerSuitable(final EthPeer ethPeer) { + return ethPeer.isServingSnap(); + } }, blockHeader.getNumber()); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/GetTrieNodeFromPeerTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/GetTrieNodeFromPeerTask.java index 56c7d9a2357..a060ca79d0b 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/GetTrieNodeFromPeerTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/GetTrieNodeFromPeerTask.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -20,10 +20,13 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; +import org.hyperledger.besu.ethereum.eth.manager.PeerRequest; import org.hyperledger.besu.ethereum.eth.manager.PendingPeerRequest; +import org.hyperledger.besu.ethereum.eth.manager.RequestManager; import org.hyperledger.besu.ethereum.eth.manager.task.AbstractPeerRequestTask; import org.hyperledger.besu.ethereum.eth.messages.snap.SnapV1; import org.hyperledger.besu.ethereum.eth.messages.snap.TrieNodesMessage; +import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; import org.hyperledger.besu.plugin.services.MetricsSystem; @@ -72,9 +75,31 @@ public static GetTrieNodeFromPeerTask forTrieNodes( @Override protected PendingPeerRequest sendRequest() { return sendRequestToPeer( - peer -> { - LOG.trace("Requesting {} trie nodes from peer {}", paths.size(), peer); - return peer.getSnapTrieNode(blockHeader.getStateRoot(), paths); + new PeerRequest() { + @Override + public RequestManager.ResponseStream sendRequest(final EthPeer peer) + throws PeerConnection.PeerNotConnected { + LOG.atTrace() + .setMessage("Requesting {} trie nodes from peer {}") + .addArgument(paths.size()) + .addArgument(peer) + .log(); + if (!peer.isServingSnap()) { + LOG.debug( + "EthPeer that is not serving snap called in {}, {}", + GetAccountRangeFromPeerTask.class, + peer); + throw new RuntimeException( + "EthPeer that is not serving snap called in " + + GetAccountRangeFromPeerTask.class); + } + return peer.getSnapTrieNode(blockHeader.getStateRoot(), paths); + } + + @Override + public boolean isEthPeerSuitable(final EthPeer ethPeer) { + return ethPeer.isServingSnap(); + } }, blockHeader.getNumber()); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/RetryingGetAccountRangeFromPeerTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/RetryingGetAccountRangeFromPeerTask.java index 7512bdd03a9..ef48dcf952d 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/RetryingGetAccountRangeFromPeerTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/RetryingGetAccountRangeFromPeerTask.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,18 +17,19 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; -import org.hyperledger.besu.ethereum.eth.manager.task.AbstractRetryingPeerTask; +import org.hyperledger.besu.ethereum.eth.manager.task.AbstractRetryingSwitchingPeerTask; import org.hyperledger.besu.ethereum.eth.manager.task.EthTask; import org.hyperledger.besu.ethereum.eth.messages.snap.AccountRangeMessage; import org.hyperledger.besu.plugin.services.MetricsSystem; -import java.util.Optional; import java.util.concurrent.CompletableFuture; import org.apache.tuweni.bytes.Bytes32; public class RetryingGetAccountRangeFromPeerTask - extends AbstractRetryingPeerTask { + extends AbstractRetryingSwitchingPeerTask { + + public static final int MAX_RETRIES = 4; private final EthContext ethContext; private final Bytes32 startKeyHash; @@ -43,7 +44,10 @@ private RetryingGetAccountRangeFromPeerTask( final BlockHeader blockHeader, final MetricsSystem metricsSystem) { super( - ethContext, 4, data -> data.accounts().isEmpty() && data.proofs().isEmpty(), metricsSystem); + ethContext, + metricsSystem, + data -> data.accounts().isEmpty() && data.proofs().isEmpty(), + MAX_RETRIES); this.ethContext = ethContext; this.startKeyHash = startKeyHash; this.endKeyHash = endKeyHash; @@ -62,12 +66,12 @@ public static EthTask forAccountRange( } @Override - protected CompletableFuture executePeerTask( - final Optional assignedPeer) { + protected CompletableFuture executeTaskOnCurrentPeer( + final EthPeer peer) { final GetAccountRangeFromPeerTask task = GetAccountRangeFromPeerTask.forAccountRange( ethContext, startKeyHash, endKeyHash, blockHeader, metricsSystem); - assignedPeer.ifPresent(task::assignPeer); + task.assignPeer(peer); return executeSubTask(task::run) .thenApply( peerResult -> { @@ -75,4 +79,9 @@ protected CompletableFuture executePeerTas return peerResult.getResult(); }); } + + @Override + protected boolean isSuitablePeer(final EthPeer peer) { + return peer.isServingSnap(); + } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/RetryingGetBytecodeFromPeerTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/RetryingGetBytecodeFromPeerTask.java index 7d23ec944d3..e30d062597b 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/RetryingGetBytecodeFromPeerTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/RetryingGetBytecodeFromPeerTask.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,19 +17,21 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; -import org.hyperledger.besu.ethereum.eth.manager.task.AbstractRetryingPeerTask; +import org.hyperledger.besu.ethereum.eth.manager.task.AbstractRetryingSwitchingPeerTask; import org.hyperledger.besu.ethereum.eth.manager.task.EthTask; import org.hyperledger.besu.plugin.services.MetricsSystem; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.concurrent.CompletableFuture; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; -public class RetryingGetBytecodeFromPeerTask extends AbstractRetryingPeerTask> { +public class RetryingGetBytecodeFromPeerTask + extends AbstractRetryingSwitchingPeerTask> { + + public static final int MAX_RETRIES = 4; private final EthContext ethContext; private final List codeHashes; @@ -41,7 +43,7 @@ private RetryingGetBytecodeFromPeerTask( final List codeHashes, final BlockHeader blockHeader, final MetricsSystem metricsSystem) { - super(ethContext, 4, Map::isEmpty, metricsSystem); + super(ethContext, metricsSystem, Map::isEmpty, MAX_RETRIES); this.ethContext = ethContext; this.codeHashes = codeHashes; this.blockHeader = blockHeader; @@ -57,11 +59,10 @@ public static EthTask> forByteCode( } @Override - protected CompletableFuture> executePeerTask( - final Optional assignedPeer) { + protected CompletableFuture> executeTaskOnCurrentPeer(final EthPeer peer) { final GetBytecodeFromPeerTask task = GetBytecodeFromPeerTask.forBytecode(ethContext, codeHashes, blockHeader, metricsSystem); - assignedPeer.ifPresent(task::assignPeer); + task.assignPeer(peer); return executeSubTask(task::run) .thenApply( peerResult -> { @@ -69,4 +70,9 @@ protected CompletableFuture> executePeerTask( return peerResult.getResult(); }); } + + @Override + protected boolean isSuitablePeer(final EthPeer peer) { + return peer.isServingSnap(); + } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/RetryingGetStorageRangeFromPeerTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/RetryingGetStorageRangeFromPeerTask.java index 7a095de9292..6e977bb4e79 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/RetryingGetStorageRangeFromPeerTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/RetryingGetStorageRangeFromPeerTask.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,19 +17,20 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; -import org.hyperledger.besu.ethereum.eth.manager.task.AbstractRetryingPeerTask; +import org.hyperledger.besu.ethereum.eth.manager.task.AbstractRetryingSwitchingPeerTask; import org.hyperledger.besu.ethereum.eth.manager.task.EthTask; import org.hyperledger.besu.ethereum.eth.messages.snap.StorageRangeMessage; import org.hyperledger.besu.plugin.services.MetricsSystem; import java.util.List; -import java.util.Optional; import java.util.concurrent.CompletableFuture; import org.apache.tuweni.bytes.Bytes32; public class RetryingGetStorageRangeFromPeerTask - extends AbstractRetryingPeerTask { + extends AbstractRetryingSwitchingPeerTask { + + public static final int MAX_RETRIES = 4; private final EthContext ethContext; private final List accountHashes; @@ -45,7 +46,11 @@ private RetryingGetStorageRangeFromPeerTask( final Bytes32 endKeyHash, final BlockHeader blockHeader, final MetricsSystem metricsSystem) { - super(ethContext, 4, data -> data.proofs().isEmpty() && data.slots().isEmpty(), metricsSystem); + super( + ethContext, + metricsSystem, + data -> data.proofs().isEmpty() && data.slots().isEmpty(), + MAX_RETRIES); this.ethContext = ethContext; this.accountHashes = accountHashes; this.startKeyHash = startKeyHash; @@ -66,12 +71,12 @@ public static EthTask forStorageRange( } @Override - protected CompletableFuture executePeerTask( - final Optional assignedPeer) { + protected CompletableFuture executeTaskOnCurrentPeer( + final EthPeer peer) { final GetStorageRangeFromPeerTask task = GetStorageRangeFromPeerTask.forStorageRange( ethContext, accountHashes, startKeyHash, endKeyHash, blockHeader, metricsSystem); - assignedPeer.ifPresent(task::assignPeer); + task.assignPeer(peer); return executeSubTask(task::run) .thenApply( peerResult -> { @@ -79,4 +84,9 @@ protected CompletableFuture executePeerTask( return peerResult.getResult(); }); } + + @Override + protected boolean isSuitablePeer(final EthPeer peer) { + return peer.isServingSnap(); + } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/RetryingGetTrieNodeFromPeerTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/RetryingGetTrieNodeFromPeerTask.java index ebfd8856898..152fd91712b 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/RetryingGetTrieNodeFromPeerTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/RetryingGetTrieNodeFromPeerTask.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,18 +17,20 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; -import org.hyperledger.besu.ethereum.eth.manager.task.AbstractRetryingPeerTask; +import org.hyperledger.besu.ethereum.eth.manager.task.AbstractRetryingSwitchingPeerTask; import org.hyperledger.besu.ethereum.eth.manager.task.EthTask; import org.hyperledger.besu.plugin.services.MetricsSystem; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.concurrent.CompletableFuture; import org.apache.tuweni.bytes.Bytes; -public class RetryingGetTrieNodeFromPeerTask extends AbstractRetryingPeerTask> { +public class RetryingGetTrieNodeFromPeerTask + extends AbstractRetryingSwitchingPeerTask> { + + public static final int MAX_RETRIES = 4; private final EthContext ethContext; private final Map> paths; @@ -40,7 +42,7 @@ private RetryingGetTrieNodeFromPeerTask( final Map> paths, final BlockHeader blockHeader, final MetricsSystem metricsSystem) { - super(ethContext, 4, Map::isEmpty, metricsSystem); + super(ethContext, metricsSystem, Map::isEmpty, MAX_RETRIES); this.ethContext = ethContext; this.paths = paths; this.blockHeader = blockHeader; @@ -56,11 +58,10 @@ public static EthTask> forTrieNodes( } @Override - protected CompletableFuture> executePeerTask( - final Optional assignedPeer) { + protected CompletableFuture> executeTaskOnCurrentPeer(final EthPeer peer) { final GetTrieNodeFromPeerTask task = GetTrieNodeFromPeerTask.forTrieNodes(ethContext, paths, blockHeader, metricsSystem); - assignedPeer.ifPresent(task::assignPeer); + task.assignPeer(peer); return executeSubTask(task::run) .thenApply( peerResult -> { @@ -68,4 +69,9 @@ protected CompletableFuture> executePeerTask( return peerResult.getResult(); }); } + + @Override + protected boolean isSuitablePeer(final EthPeer peer) { + return peer.isServingSnap(); + } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapProtocolManager.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapProtocolManager.java index de2cb280c8c..ce639a7a9a0 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapProtocolManager.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapProtocolManager.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,14 +14,14 @@ */ package org.hyperledger.besu.ethereum.eth.manager.snap; +import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.eth.SnapProtocol; import org.hyperledger.besu.ethereum.eth.manager.EthMessage; import org.hyperledger.besu.ethereum.eth.manager.EthMessages; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; import org.hyperledger.besu.ethereum.eth.manager.EthPeers; -import org.hyperledger.besu.ethereum.eth.peervalidation.PeerValidator; +import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncConfiguration; import org.hyperledger.besu.ethereum.p2p.network.ProtocolManager; -import org.hyperledger.besu.ethereum.p2p.peers.Peer; import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.AbstractSnapMessageData; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Capability; @@ -29,7 +29,7 @@ import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason; import org.hyperledger.besu.ethereum.rlp.RLPException; -import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import java.math.BigInteger; import java.util.Comparator; @@ -49,14 +49,15 @@ public class SnapProtocolManager implements ProtocolManager { private final EthMessages snapMessages; public SnapProtocolManager( - final List peerValidators, + final WorldStateStorageCoordinator worldStateStorageCoordinator, + final SnapSyncConfiguration snapConfig, final EthPeers ethPeers, final EthMessages snapMessages, - final WorldStateArchive worldStateArchive) { + final ProtocolContext protocolContext) { this.ethPeers = ethPeers; this.snapMessages = snapMessages; this.supportedCapabilities = calculateCapabilities(); - new SnapServer(snapMessages, worldStateArchive); + new SnapServer(snapConfig, snapMessages, worldStateStorageCoordinator, protocolContext); } private List calculateCapabilities() { @@ -83,7 +84,7 @@ public void stop() {} public void awaitStop() throws InterruptedException {} /** - * This function is called by the P2P framework when an "SNAP message has been received. + * This function is called by the P2P framework when a SNAP message has been received. * * @param cap The capability under which the message was transmitted. * @param message The message to be decoded. @@ -96,13 +97,16 @@ public void processMessage(final Capability cap, final Message message) { final EthPeer ethPeer = ethPeers.peer(message.getConnection()); if (ethPeer == null) { LOG.debug( - "Ignoring message received from unknown peer connection: " + message.getConnection()); + "Ignoring message received from unknown peer connection: {}", message.getConnection()); return; } final EthMessage ethMessage = new EthMessage(ethPeer, messageData); if (!ethPeer.validateReceivedMessage(ethMessage, getSupportedProtocol())) { - LOG.debug("Unsolicited message received from, disconnecting: {}", ethPeer); - ethPeer.disconnect(DisconnectReason.BREACH_OF_PROTOCOL); + LOG.debug( + "Unsolicited message {} received from, disconnecting: {}", + ethMessage.getData().getCode(), + ethPeer); + ethPeer.disconnect(DisconnectReason.BREACH_OF_PROTOCOL_UNSOLICITED_MESSAGE_RECEIVED); return; } @@ -121,16 +125,17 @@ public void processMessage(final Capability cap, final Message message) { } catch (final RLPException e) { LOG.debug( "Received malformed message {} , disconnecting: {}", messageData.getData(), ethPeer, e); - ethPeer.disconnect(DisconnectReason.BREACH_OF_PROTOCOL); + ethPeer.disconnect(DisconnectReason.BREACH_OF_PROTOCOL_MALFORMED_MESSAGE_RECEIVED); } maybeResponseData.ifPresent( responseData -> { try { ethPeer.send(responseData, getSupportedProtocol()); } catch (final PeerConnection.PeerNotConnected error) { - // Peer disconnected before we could respond - nothing to do - LOG.trace( - "Peer disconnected before we could respond - nothing to do " + error.getMessage()); + LOG.atTrace() + .setMessage("Peer disconnected before we could respond - nothing to do {}") + .addArgument(error.getMessage()) + .log(); } }); } @@ -138,11 +143,6 @@ public void processMessage(final Capability cap, final Message message) { @Override public void handleNewConnection(final PeerConnection connection) {} - @Override - public boolean shouldConnect(final Peer peer, final boolean incoming) { - return false; // EthManager is taking care of this for now - } - @Override public void handleDisconnect( final PeerConnection connection, diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServer.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServer.java index 17fb0a246cb..c1b855f2d6f 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServer.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServer.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,66 +14,688 @@ */ package org.hyperledger.besu.ethereum.eth.manager.snap; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.eth.manager.EthMessages; import org.hyperledger.besu.ethereum.eth.messages.snap.AccountRangeMessage; import org.hyperledger.besu.ethereum.eth.messages.snap.ByteCodesMessage; +import org.hyperledger.besu.ethereum.eth.messages.snap.GetAccountRangeMessage; +import org.hyperledger.besu.ethereum.eth.messages.snap.GetByteCodesMessage; +import org.hyperledger.besu.ethereum.eth.messages.snap.GetStorageRangeMessage; +import org.hyperledger.besu.ethereum.eth.messages.snap.GetTrieNodesMessage; import org.hyperledger.besu.ethereum.eth.messages.snap.SnapV1; import org.hyperledger.besu.ethereum.eth.messages.snap.StorageRangeMessage; import org.hyperledger.besu.ethereum.eth.messages.snap.TrieNodesMessage; +import org.hyperledger.besu.ethereum.eth.sync.DefaultSynchronizer; +import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncConfiguration; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; -import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.ethereum.proof.WorldStateProofProvider; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.ethereum.trie.CompactEncoding; +import org.hyperledger.besu.ethereum.trie.MerkleTrie; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.BonsaiWorldStateProvider; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.FlatDbMode; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.plugin.services.BesuEvents; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.List; +import java.util.NavigableMap; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import com.google.common.annotations.VisibleForTesting; +import kotlin.Pair; import kotlin.collections.ArrayDeque; +import org.apache.commons.lang3.time.StopWatch; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -@SuppressWarnings("unused") -class SnapServer { +/** See https://github.com/ethereum/devp2p/blob/master/caps/snap.md */ +class SnapServer implements BesuEvents.InitialSyncCompletionListener { + private static final Logger LOGGER = LoggerFactory.getLogger(SnapServer.class); + private static final int PRIME_STATE_ROOT_CACHE_LIMIT = 128; + private static final int MAX_ENTRIES_PER_REQUEST = 100000; + private static final int MAX_RESPONSE_SIZE = 2 * 1024 * 1024; + private static final int MAX_CODE_LOOKUPS_PER_REQUEST = 1024; + private static final int MAX_TRIE_LOOKUPS_PER_REQUEST = 1024; + private static final AccountRangeMessage EMPTY_ACCOUNT_RANGE = + AccountRangeMessage.create(new HashMap<>(), new ArrayDeque<>()); + private static final StorageRangeMessage EMPTY_STORAGE_RANGE = + StorageRangeMessage.create(new ArrayDeque<>(), Collections.emptyList()); + private static final TrieNodesMessage EMPTY_TRIE_NODES_MESSAGE = + TrieNodesMessage.create(new ArrayList<>()); + private static final ByteCodesMessage EMPTY_BYTE_CODES_MESSAGE = + ByteCodesMessage.create(new ArrayDeque<>()); + static final Hash HASH_LAST = Hash.wrap(Bytes32.leftPad(Bytes.fromHexString("FF"), (byte) 0xFF)); + + private final AtomicBoolean isStarted = new AtomicBoolean(false); + private final AtomicLong listenerId = new AtomicLong(); private final EthMessages snapMessages; - private final WorldStateArchive worldStateArchive; - SnapServer(final EthMessages snapMessages, final WorldStateArchive worldStateArchive) { + private final WorldStateStorageCoordinator worldStateStorageCoordinator; + private final Optional protocolContext; + + // whether snap server is enabled + private final boolean snapServerEnabled; + + // provide worldstate storage by root hash + private Function> worldStateStorageProvider = + __ -> Optional.empty(); + + SnapServer( + final SnapSyncConfiguration snapConfig, + final EthMessages snapMessages, + final WorldStateStorageCoordinator worldStateStorageCoordinator, + final ProtocolContext protocolContext) { + this.snapServerEnabled = + Optional.ofNullable(snapConfig) + .map(SnapSyncConfiguration::isSnapServerEnabled) + .orElse(false); + this.snapMessages = snapMessages; + this.worldStateStorageCoordinator = worldStateStorageCoordinator; + this.protocolContext = Optional.of(protocolContext); + registerResponseConstructors(); + + // subscribe to initial sync completed events to start/stop snap server: + this.protocolContext + .flatMap(ProtocolContext::getSynchronizer) + .filter(z -> z instanceof DefaultSynchronizer) + .map(DefaultSynchronizer.class::cast) + .ifPresentOrElse( + z -> this.listenerId.set(z.subscribeInitialSync(this)), + () -> LOGGER.warn("SnapServer created without reference to sync status")); + } + + /** + * Create a snap server without registering a listener for worldstate initial sync events or + * priming worldstates by root hash. Used by unit tests. + */ + @VisibleForTesting + SnapServer( + final EthMessages snapMessages, + final WorldStateStorageCoordinator worldStateStorageCoordinator, + final Function> worldStateStorageProvider) { + this.snapServerEnabled = true; this.snapMessages = snapMessages; - this.worldStateArchive = worldStateArchive; - this.registerResponseConstructors(); + this.worldStateStorageCoordinator = worldStateStorageCoordinator; + this.worldStateStorageProvider = worldStateStorageProvider; + this.protocolContext = Optional.empty(); + } + + @Override + public void onInitialSyncCompleted() { + start(); + } + + @Override + public void onInitialSyncRestart() { + stop(); + } + + public synchronized SnapServer start() { + if (!isStarted.get() && snapServerEnabled) { + // if we are bonsai and full flat, we can provide a worldstate storage: + var worldStateKeyValueStorage = worldStateStorageCoordinator.worldStateKeyValueStorage(); + if (worldStateKeyValueStorage.getDataStorageFormat().equals(DataStorageFormat.BONSAI) + && worldStateStorageCoordinator.isMatchingFlatMode(FlatDbMode.FULL)) { + LOGGER.debug("Starting SnapServer with Bonsai full flat db"); + var bonsaiArchive = + protocolContext + .map(ProtocolContext::getWorldStateArchive) + .map(BonsaiWorldStateProvider.class::cast); + var cachedStorageManagerOpt = + bonsaiArchive.map(archive -> archive.getCachedWorldStorageManager()); + + if (cachedStorageManagerOpt.isPresent()) { + var cachedStorageManager = cachedStorageManagerOpt.get(); + this.worldStateStorageProvider = + rootHash -> + cachedStorageManager + .getStorageByRootHash(rootHash) + .map(BonsaiWorldStateKeyValueStorage.class::cast); + + // when we start we need to build the cache of latest 128 worldstates + // trielogs-to-root-hash: + var blockchain = protocolContext.map(ProtocolContext::getBlockchain).orElse(null); + + // at startup, prime the latest worldstates by roothash: + cachedStorageManager.primeRootToBlockHashCache(blockchain, PRIME_STATE_ROOT_CACHE_LIMIT); + + var flatDbStrategy = + ((BonsaiWorldStateKeyValueStorage) + worldStateStorageCoordinator.worldStateKeyValueStorage()) + .getFlatDbStrategy(); + if (!flatDbStrategy.isCodeByCodeHash()) { + LOGGER.warn("SnapServer requires code stored by codehash, but it is not enabled"); + } + } else { + LOGGER.warn( + "SnapServer started without cached storage manager, this should only happen in tests"); + } + isStarted.set(true); + } + } + return this; + } + + public synchronized SnapServer stop() { + isStarted.set(false); + return this; } private void registerResponseConstructors() { snapMessages.registerResponseConstructor( - SnapV1.GET_ACCOUNT_RANGE, - messageData -> constructGetAccountRangeResponse(worldStateArchive, messageData)); + SnapV1.GET_ACCOUNT_RANGE, messageData -> constructGetAccountRangeResponse(messageData)); snapMessages.registerResponseConstructor( - SnapV1.GET_STORAGE_RANGE, - messageData -> constructGetStorageRangeResponse(worldStateArchive, messageData)); + SnapV1.GET_STORAGE_RANGE, messageData -> constructGetStorageRangeResponse(messageData)); snapMessages.registerResponseConstructor( - SnapV1.GET_BYTECODES, - messageData -> constructGetBytecodesResponse(worldStateArchive, messageData)); + SnapV1.GET_BYTECODES, messageData -> constructGetBytecodesResponse(messageData)); snapMessages.registerResponseConstructor( - SnapV1.GET_TRIE_NODES, - messageData -> constructGetTrieNodesResponse(worldStateArchive, messageData)); + SnapV1.GET_TRIE_NODES, messageData -> constructGetTrieNodesResponse(messageData)); + } + + MessageData constructGetAccountRangeResponse(final MessageData message) { + if (!isStarted.get()) { + return EMPTY_ACCOUNT_RANGE; + } + StopWatch stopWatch = StopWatch.createStarted(); + + final GetAccountRangeMessage getAccountRangeMessage = GetAccountRangeMessage.readFrom(message); + final GetAccountRangeMessage.Range range = getAccountRangeMessage.range(true); + final int maxResponseBytes = Math.min(range.responseBytes().intValue(), MAX_RESPONSE_SIZE); + + LOGGER + .atTrace() + .setMessage("Received getAccountRangeMessage for {} from {} to {}") + .addArgument(() -> asLogHash(range.worldStateRootHash())) + .addArgument(() -> asLogHash(range.startKeyHash())) + .addArgument(() -> asLogHash(range.endKeyHash())) + .log(); + try { + if (range.worldStateRootHash().equals(Hash.EMPTY_TRIE_HASH)) { + return AccountRangeMessage.create(new HashMap<>(), List.of(MerkleTrie.EMPTY_TRIE_NODE)); + } + return worldStateStorageProvider + .apply(range.worldStateRootHash()) + .map( + storage -> { + LOGGER.trace("obtained worldstate in {}", stopWatch); + ResponseSizePredicate responseSizePredicate = + new ResponseSizePredicate( + "account", + stopWatch, + maxResponseBytes, + (pair) -> { + var rlpOutput = new BytesValueRLPOutput(); + rlpOutput.startList(); + rlpOutput.writeBytes(pair.getFirst()); + rlpOutput.writeRLPBytes(pair.getSecond()); + rlpOutput.endList(); + return rlpOutput.encodedSize(); + }); + + final Bytes32 endKeyBytes = range.endKeyHash(); + var shouldContinuePredicate = + new ExceedingPredicate( + new EndKeyExceedsPredicate(endKeyBytes).and(responseSizePredicate)); + + NavigableMap accounts = + storage.streamFlatAccounts(range.startKeyHash(), shouldContinuePredicate); + + if (accounts.isEmpty() && shouldContinuePredicate.shouldContinue.get()) { + // fetch next account after range, if it exists + LOGGER.debug( + "found no accounts in range, taking first value starting from {}", + asLogHash(range.endKeyHash())); + accounts = storage.streamFlatAccounts(range.endKeyHash(), UInt256.MAX_VALUE, 1L); + } + + final var worldStateProof = + new WorldStateProofProvider(new WorldStateStorageCoordinator(storage)); + final List proof = + worldStateProof.getAccountProofRelatedNodes( + range.worldStateRootHash(), Hash.wrap(range.startKeyHash())); + + if (!accounts.isEmpty()) { + proof.addAll( + worldStateProof.getAccountProofRelatedNodes( + range.worldStateRootHash(), Hash.wrap(accounts.lastKey()))); + } + var resp = AccountRangeMessage.create(accounts, proof); + if (accounts.isEmpty()) { + LOGGER.debug( + "returned empty account range message for {} to {}, proof count {}", + asLogHash(range.startKeyHash()), + asLogHash(range.endKeyHash()), + proof.size()); + } + LOGGER.debug( + "returned in {} account range {} to {} with {} accounts and {} proofs, resp size {} of max {}", + stopWatch, + asLogHash(range.startKeyHash()), + asLogHash(range.endKeyHash()), + accounts.size(), + proof.size(), + resp.getSize(), + maxResponseBytes); + return resp; + }) + .orElseGet( + () -> { + LOGGER.debug("returned empty account range due to worldstate not present"); + return EMPTY_ACCOUNT_RANGE; + }); + } catch (Exception ex) { + LOGGER.error("Unexpected exception serving account range request", ex); + } + return EMPTY_ACCOUNT_RANGE; + } + + MessageData constructGetStorageRangeResponse(final MessageData message) { + if (!isStarted.get()) { + return EMPTY_STORAGE_RANGE; + } + StopWatch stopWatch = StopWatch.createStarted(); + + final GetStorageRangeMessage getStorageRangeMessage = GetStorageRangeMessage.readFrom(message); + final GetStorageRangeMessage.StorageRange range = getStorageRangeMessage.range(true); + final int maxResponseBytes = Math.min(range.responseBytes().intValue(), MAX_RESPONSE_SIZE); + + LOGGER + .atTrace() + .setMessage("Receive get storage range message size {} from {} to {} for {}") + .addArgument(message::getSize) + .addArgument(() -> asLogHash(range.startKeyHash())) + .addArgument( + () -> Optional.ofNullable(range.endKeyHash()).map(SnapServer::asLogHash).orElse("''")) + .addArgument( + () -> + range.hashes().stream() + .map(SnapServer::asLogHash) + .collect(Collectors.joining(",", "[", "]"))) + .log(); + try { + return worldStateStorageProvider + .apply(range.worldStateRootHash()) + .map( + storage -> { + LOGGER.trace("obtained worldstate in {}", stopWatch); + // reusable predicate to limit by rec count and bytes: + var responsePredicate = + new ResponseSizePredicate( + "storage", + stopWatch, + maxResponseBytes, + (pair) -> { + var slotRlpOutput = new BytesValueRLPOutput(); + slotRlpOutput.startList(); + slotRlpOutput.writeBytes(pair.getFirst()); + slotRlpOutput.writeBytes(pair.getSecond()); + slotRlpOutput.endList(); + return slotRlpOutput.encodedSize(); + }); + + // only honor start and end hash if request is for a single account's storage: + Bytes32 startKeyBytes, endKeyBytes; + boolean isPartialRange = false; + if (range.hashes().size() > 1) { + startKeyBytes = Bytes32.ZERO; + endKeyBytes = HASH_LAST; + } else { + startKeyBytes = range.startKeyHash(); + endKeyBytes = range.endKeyHash(); + isPartialRange = + !(startKeyBytes.equals(Hash.ZERO) && endKeyBytes.equals(HASH_LAST)); + } + + ArrayDeque> collectedStorages = new ArrayDeque<>(); + List proofNodes = new ArrayList<>(); + final var worldStateProof = + new WorldStateProofProvider(new WorldStateStorageCoordinator(storage)); + + for (var forAccountHash : range.hashes()) { + var predicate = + new ExceedingPredicate( + new EndKeyExceedsPredicate(endKeyBytes).and(responsePredicate)); + var accountStorages = + storage.streamFlatStorages( + Hash.wrap(forAccountHash), startKeyBytes, predicate); + + //// address partial range queries that return empty + if (accountStorages.isEmpty() && isPartialRange) { + // fetch next slot after range, if it exists + LOGGER.debug( + "found no slots in range, taking first value starting from {}", + asLogHash(range.endKeyHash())); + accountStorages = + storage.streamFlatStorages( + Hash.wrap(forAccountHash), range.endKeyHash(), UInt256.MAX_VALUE, 1L); + } + + // don't send empty storage ranges + if (!accountStorages.isEmpty()) { + collectedStorages.add(accountStorages); + } + + // if a partial storage range was requested, or we interrupted storage due to + // request limits, send proofs: + if (isPartialRange || !predicate.shouldGetMore()) { + // send a proof for the left side range origin + proofNodes.addAll( + worldStateProof.getStorageProofRelatedNodes( + getAccountStorageRoot(forAccountHash, storage), + forAccountHash, + Hash.wrap(startKeyBytes))); + if (!accountStorages.isEmpty()) { + // send a proof for the last key on the right + proofNodes.addAll( + worldStateProof.getStorageProofRelatedNodes( + getAccountStorageRoot(forAccountHash, storage), + forAccountHash, + Hash.wrap(accountStorages.lastKey()))); + } + } + + if (!predicate.shouldGetMore()) { + break; + } + } + + var resp = StorageRangeMessage.create(collectedStorages, proofNodes); + LOGGER.debug( + "returned in {} storage {} to {} range {} to {} with {} storages and {} proofs, resp size {} of max {}", + stopWatch, + asLogHash(range.hashes().first()), + asLogHash(range.hashes().last()), + asLogHash(range.startKeyHash()), + asLogHash(range.endKeyHash()), + collectedStorages.size(), + proofNodes.size(), + resp.getSize(), + maxResponseBytes); + return resp; + }) + .orElseGet( + () -> { + LOGGER.debug("returned empty storage range due to missing worldstate"); + return EMPTY_STORAGE_RANGE; + }); + } catch (Exception ex) { + LOGGER.error("Unexpected exception serving storage range request", ex); + return EMPTY_STORAGE_RANGE; + } + } + + MessageData constructGetBytecodesResponse(final MessageData message) { + if (!isStarted.get()) { + return EMPTY_BYTE_CODES_MESSAGE; + } + StopWatch stopWatch = StopWatch.createStarted(); + + final GetByteCodesMessage getByteCodesMessage = GetByteCodesMessage.readFrom(message); + final GetByteCodesMessage.CodeHashes codeHashes = getByteCodesMessage.codeHashes(true); + final int maxResponseBytes = Math.min(codeHashes.responseBytes().intValue(), MAX_RESPONSE_SIZE); + LOGGER + .atTrace() + .setMessage("Received get bytecodes message for {} hashes") + .addArgument(codeHashes.hashes()::size) + .log(); + + try { + List codeBytes = new ArrayDeque<>(); + var codeHashList = + (codeHashes.hashes().size() < MAX_CODE_LOOKUPS_PER_REQUEST) + ? codeHashes.hashes() + : codeHashes.hashes().subList(0, MAX_CODE_LOOKUPS_PER_REQUEST); + for (Bytes32 codeHash : codeHashList) { + if (Hash.EMPTY.equals(codeHash)) { + codeBytes.add(Bytes.EMPTY); + } else { + Optional optCode = worldStateStorageCoordinator.getCode(Hash.wrap(codeHash), null); + if (optCode.isPresent()) { + if (!codeBytes.isEmpty() + && (sumListBytes(codeBytes) + optCode.get().size() > maxResponseBytes + || stopWatch.getTime() > ResponseSizePredicate.MAX_MILLIS_PER_REQUEST)) { + break; + } + codeBytes.add(optCode.get()); + } + } + } + var resp = ByteCodesMessage.create(codeBytes); + LOGGER.debug( + "returned in {} code bytes message with {} entries, resp size {} of max {}", + stopWatch, + codeBytes.size(), + resp.getSize(), + maxResponseBytes); + return resp; + } catch (Exception ex) { + LOGGER.error("Unexpected exception serving bytecodes request", ex); + return EMPTY_BYTE_CODES_MESSAGE; + } + } + + MessageData constructGetTrieNodesResponse(final MessageData message) { + if (!isStarted.get()) { + return EMPTY_TRIE_NODES_MESSAGE; + } + StopWatch stopWatch = StopWatch.createStarted(); + + final GetTrieNodesMessage getTrieNodesMessage = GetTrieNodesMessage.readFrom(message); + final GetTrieNodesMessage.TrieNodesPaths triePaths = getTrieNodesMessage.paths(true); + final int maxResponseBytes = Math.min(triePaths.responseBytes().intValue(), MAX_RESPONSE_SIZE); + LOGGER + .atTrace() + .setMessage("Received get trie nodes message of size {}") + .addArgument(() -> triePaths.paths().size()) + .log(); + + try { + return worldStateStorageProvider + .apply(triePaths.worldStateRootHash()) + .map( + storage -> { + LOGGER.trace("obtained worldstate in {}", stopWatch); + ArrayList trieNodes = new ArrayList<>(); + var triePathList = + triePaths.paths().size() < MAX_TRIE_LOOKUPS_PER_REQUEST + ? triePaths.paths() + : triePaths.paths().subList(0, MAX_TRIE_LOOKUPS_PER_REQUEST); + for (var triePath : triePathList) { + // first element in paths is account + if (triePath.size() == 1) { + // if there is only one path, presume it should be compact encoded account path + final Bytes location = CompactEncoding.decode(triePath.get(0)); + var optStorage = storage.getTrieNodeUnsafe(location); + if (optStorage.isEmpty() && location.isEmpty()) { + optStorage = Optional.of(MerkleTrie.EMPTY_TRIE_NODE); + } + var trieNode = optStorage.orElse(Bytes.EMPTY); + if (!trieNodes.isEmpty() + && (sumListBytes(trieNodes) + trieNode.size() > maxResponseBytes + || stopWatch.getTime() + > ResponseSizePredicate.MAX_MILLIS_PER_REQUEST)) { + break; + } + trieNodes.add(trieNode); + } else { + // There must be at least one element in the path otherwise it is invalid + if (triePath.isEmpty()) { + LOGGER.debug("returned empty trie nodes message due to invalid path"); + return EMPTY_TRIE_NODES_MESSAGE; + } + + // otherwise the first element should be account hash, and subsequent paths + // are compact encoded account storage paths + + final Bytes32 accountPrefix = Bytes32.leftPad(triePath.getFirst()); + var optAccount = storage.getAccount(Hash.wrap(accountPrefix)); + if (optAccount.isEmpty()) { + continue; + } + + List storagePaths = triePath.subList(1, triePath.size()); + for (var path : storagePaths) { + final Bytes location = CompactEncoding.decode(path); + var optStorage = + storage.getTrieNodeUnsafe(Bytes.concatenate(accountPrefix, location)); + if (optStorage.isEmpty() && location.isEmpty()) { + optStorage = Optional.of(MerkleTrie.EMPTY_TRIE_NODE); + } + var trieNode = optStorage.orElse(Bytes.EMPTY); + if (!trieNodes.isEmpty() + && sumListBytes(trieNodes) + trieNode.size() > maxResponseBytes) { + break; + } + trieNodes.add(trieNode); + } + } + } + var resp = TrieNodesMessage.create(trieNodes); + LOGGER.debug( + "returned in {} trie nodes message with {} entries, resp size {} of max {}", + stopWatch, + trieNodes.size(), + resp.getCode(), + maxResponseBytes); + return resp; + }) + .orElseGet( + () -> { + LOGGER.debug("returned empty trie nodes message due to missing worldstate"); + return EMPTY_TRIE_NODES_MESSAGE; + }); + } catch (Exception ex) { + LOGGER.error("Unexpected exception serving trienodes request", ex); + return EMPTY_TRIE_NODES_MESSAGE; + } + } + + /** + * Predicate that doesn't immediately stop when the delegate predicate returns false, but instead + * sets a flag to stop after the current element is processed. + */ + static class ExceedingPredicate implements Predicate> { + private final Predicate> delegate; + final AtomicBoolean shouldContinue = new AtomicBoolean(true); + + public ExceedingPredicate(final Predicate> delegate) { + this.delegate = delegate; + } + + @Override + public boolean test(final Pair pair) { + final boolean result = delegate.test(pair); + return shouldContinue.getAndSet(result); + } + + public boolean shouldGetMore() { + return shouldContinue.get(); + } + } + + /** Predicate that stops when the end key is exceeded. */ + record EndKeyExceedsPredicate(Bytes endKey) implements Predicate> { + + @Override + public boolean test(final Pair pair) { + return endKey.compareTo(Bytes.wrap(pair.getFirst())) > 0; + } } - private MessageData constructGetAccountRangeResponse( - final WorldStateArchive worldStateArchive, final MessageData message) { - // TODO implement - return AccountRangeMessage.create(new HashMap<>(), new ArrayDeque<>()); + static class ResponseSizePredicate implements Predicate> { + // default to a max of 4 seconds per request + static final long MAX_MILLIS_PER_REQUEST = 4000; + + final AtomicInteger byteLimit = new AtomicInteger(0); + final AtomicInteger recordLimit = new AtomicInteger(0); + final AtomicBoolean shouldContinue = new AtomicBoolean(true); + final Function, Integer> encodingSizeAccumulator; + final StopWatch stopWatch; + final int maxResponseBytes; + final String forWhat; + + ResponseSizePredicate( + final String forWhat, + final StopWatch stopWatch, + final int maxResponseBytes, + final Function, Integer> encodingSizeAccumulator) { + this.stopWatch = stopWatch; + this.maxResponseBytes = maxResponseBytes; + this.forWhat = forWhat; + this.encodingSizeAccumulator = encodingSizeAccumulator; + } + + @Override + public boolean test(final Pair pair) { + LOGGER + .atTrace() + .setMessage("{} pre-accumulate limits, bytes: {} , stream count: {}") + .addArgument(() -> forWhat) + .addArgument(byteLimit::get) + .addArgument(recordLimit::get) + .log(); + if (stopWatch.getTime() > MAX_MILLIS_PER_REQUEST) { + shouldContinue.set(false); + LOGGER.warn( + "{} took too long, stopped at {} ms with {} records and {} bytes", + forWhat, + stopWatch.formatTime(), + recordLimit.get(), + byteLimit.get()); + return false; + } + + var underRecordLimit = recordLimit.addAndGet(1) <= MAX_ENTRIES_PER_REQUEST; + var underByteLimit = + byteLimit.accumulateAndGet(0, (cur, __) -> cur + encodingSizeAccumulator.apply(pair)) + < maxResponseBytes; + if (underRecordLimit && underByteLimit) { + return true; + } else { + shouldContinue.set(false); + LOGGER + .atDebug() + .setMessage("{} post-accumulate limits, bytes: {} , stream count: {}") + .addArgument(() -> forWhat) + .addArgument(byteLimit::get) + .addArgument(recordLimit::get) + .log(); + return false; + } + } } - private MessageData constructGetStorageRangeResponse( - final WorldStateArchive worldStateArchive, final MessageData message) { - // TODO implement - return StorageRangeMessage.create(new ArrayDeque<>(), new ArrayDeque<>()); + Hash getAccountStorageRoot( + final Bytes32 accountHash, final BonsaiWorldStateKeyValueStorage storage) { + return storage + .getTrieNodeUnsafe(Bytes.concatenate(accountHash, Bytes.EMPTY)) + .map(Hash::hash) + .orElse(Hash.EMPTY_TRIE_HASH); } - private MessageData constructGetBytecodesResponse( - final WorldStateArchive worldStateArchive, final MessageData message) { - // TODO implement - return ByteCodesMessage.create(new ArrayDeque<>()); + private static int sumListBytes(final List listOfBytes) { + // TODO: remove hack, 10% is a fudge factor to account for the overhead of rlp encoding + return listOfBytes.stream().map(Bytes::size).reduce((a, b) -> a + b).orElse(0) * 11 / 10; } - private MessageData constructGetTrieNodesResponse( - final WorldStateArchive worldStateArchive, final MessageData message) { - return TrieNodesMessage.create(new ArrayDeque<>()); + private static String asLogHash(final Bytes32 hash) { + var str = hash.toHexString(); + return str.substring(0, 4) + ".." + str.substring(59, 63); } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractGetHeadersFromPeerTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractGetHeadersFromPeerTask.java index 83af1e0b32d..083bbc62dbe 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractGetHeadersFromPeerTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractGetHeadersFromPeerTask.java @@ -74,19 +74,19 @@ protected Optional> processResponse( final List headers = headersMessage.getHeaders(protocolSchedule); if (headers.isEmpty()) { // Message contains no data - nothing to do - LOG.debug("headers.isEmpty. Peer: {}", peer.getShortNodeId()); + LOG.debug("headers.isEmpty. Peer: {}", peer.getLoggableId()); return Optional.empty(); } if (headers.size() > count) { // Too many headers - this isn't our response - LOG.debug("headers.size()>count. Peer: {}", peer.getShortNodeId()); + LOG.debug("headers.size()>count. Peer: {}", peer.getLoggableId()); return Optional.empty(); } final BlockHeader firstHeader = headers.get(0); if (!matchesFirstHeader(firstHeader)) { // This isn't our message - nothing to do - LOG.debug("!matchesFirstHeader. Peer: {}", peer.getShortNodeId()); + LOG.debug("!matchesFirstHeader. Peer: {}", peer.getLoggableId()); return Optional.empty(); } @@ -100,7 +100,7 @@ protected Optional> processResponse( header = headers.get(i); if (header.getNumber() != prevBlockHeader.getNumber() + expectedDelta) { // Skip doesn't match, this isn't our data - LOG.debug("header not matching the expected number. Peer: {}", peer.getShortNodeId()); + LOG.debug("header not matching the expected number. Peer: {}", peer.getLoggableId()); return Optional.empty(); } // if headers are supposed to be sequential check if a chain is formed @@ -110,8 +110,9 @@ protected Optional> processResponse( if (!parent.getHash().equals(child.getParentHash())) { LOG.debug( "Sequential headers must form a chain through hashes (BREACH_OF_PROTOCOL), disconnecting peer: {}", - peer.getShortNodeId()); - peer.disconnect(DisconnectMessage.DisconnectReason.BREACH_OF_PROTOCOL); + peer.getLoggableId()); + peer.disconnect( + DisconnectMessage.DisconnectReason.BREACH_OF_PROTOCOL_NON_SEQUENTIAL_HEADERS); return Optional.empty(); } } @@ -126,10 +127,10 @@ protected Optional> processResponse( } LOG.atTrace() - .setMessage("Received {} of {} headers requested from peer {}...") + .setMessage("Received {} of {} headers requested from peer {}") .addArgument(headersList::size) .addArgument(count) - .addArgument(peer::getShortNodeId) + .addArgument(peer::getLoggableId) .log(); return Optional.of(headersList); } @@ -137,8 +138,8 @@ protected Optional> processResponse( private void updatePeerChainState(final EthPeer peer, final BlockHeader blockHeader) { if (blockHeader.getNumber() > peer.chainState().getEstimatedHeight()) { LOG.atTrace() - .setMessage("Updating chain state for peer {}... to block header {}") - .addArgument(peer::getShortNodeId) + .setMessage("Updating chain state for peer {} to block header {}") + .addArgument(peer::getLoggableId) .addArgument(blockHeader::toLogString) .log(); peer.chainState().update(blockHeader); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractPeerRequestTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractPeerRequestTask.java index eda8aa17387..28e63e7009f 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractPeerRequestTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractPeerRequestTask.java @@ -110,10 +110,10 @@ private void handleMessage( // Peer sent us malformed data - disconnect LOG.debug( "Disconnecting with BREACH_OF_PROTOCOL due to malformed message: {}", - peer.getShortNodeId(), + peer.getLoggableId(), e); LOG.trace("Peer {} Malformed message data: {}", peer, message.getData()); - peer.disconnect(DisconnectReason.BREACH_OF_PROTOCOL); + peer.disconnect(DisconnectReason.BREACH_OF_PROTOCOL_MALFORMED_MESSAGE_RECEIVED); promise.completeExceptionally(new PeerBreachedProtocolException()); } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractRetryingPeerTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractRetryingPeerTask.java index cf48d69847d..e7f1556b5a2 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractRetryingPeerTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractRetryingPeerTask.java @@ -67,8 +67,20 @@ protected AbstractRetryingPeerTask( this.metricsSystem = metricsSystem; } - public void assignPeer(final EthPeer peer) { - assignedPeer = Optional.of(peer); + /** + * Assign the peer to be used for the task. + * + * @param peer The peer to assign to the task. + * @return True if the peer was assigned, false otherwise. + */ + public boolean assignPeer(final EthPeer peer) { + if (isSuitablePeer(peer)) { + assignedPeer = Optional.of(peer); + return true; + } else { + assignedPeer = Optional.empty(); + return false; + } } public Optional getAssignedPeer() { @@ -122,15 +134,18 @@ protected void handleTaskError(final Throwable error) { () -> ethContext .getScheduler() + // wait for a new peer for up to 5 seconds .timeout(waitTask, Duration.ofSeconds(5)) + // execute the task again .whenComplete((r, t) -> executeTaskTimed())); return; } - LOG.debug( - "Retrying after recoverable failure from peer task {}: {}", - this.getClass().getSimpleName(), - cause.getMessage()); + LOG.atDebug() + .setMessage("Retrying after recoverable failure from peer task {}: {}") + .addArgument(this.getClass().getSimpleName()) + .addArgument(cause.getMessage()) + .log(); // Wait before retrying on failure executeSubTask( () -> @@ -164,4 +179,8 @@ public int getRetryCount() { public int getMaxRetries() { return maxRetries; } + + protected boolean isSuitablePeer(final EthPeer peer) { + return true; + } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractRetryingSwitchingPeerTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractRetryingSwitchingPeerTask.java index f9400a6dac6..a1021289779 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractRetryingSwitchingPeerTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractRetryingSwitchingPeerTask.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -49,9 +49,12 @@ protected AbstractRetryingSwitchingPeerTask( } @Override - public void assignPeer(final EthPeer peer) { - super.assignPeer(peer); - triedPeers.add(peer); + public boolean assignPeer(final EthPeer peer) { + if (super.assignPeer(peer)) { + triedPeers.add(peer); + return true; + } + return false; } protected abstract CompletableFuture executeTaskOnCurrentPeer(final EthPeer peer); @@ -62,8 +65,7 @@ protected CompletableFuture executePeerTask(final Optional assignedP final Optional maybePeer = assignedPeer .filter(u -> getRetryCount() == 1) // first try with the assigned peer if present - .map(Optional::of) - .orElseGet(this::selectNextPeer); // otherwise, select a new one from the pool + .or(this::selectNextPeer); // otherwise select a new one from the pool if (maybePeer.isEmpty()) { LOG.atTrace() @@ -101,7 +103,7 @@ protected CompletableFuture executePeerTask(final Optional assignedP @Override protected void handleTaskError(final Throwable error) { if (isPeerFailure(error)) { - getAssignedPeer().ifPresent(peer -> failedPeers.add(peer)); + getAssignedPeer().ifPresent(failedPeers::add); } super.handleTaskError(error); } @@ -124,10 +126,11 @@ private Optional selectNextPeer() { return maybeNextPeer; } - private Stream remainingPeersToTry() { + protected Stream remainingPeersToTry() { return getEthContext() .getEthPeers() .streamBestPeers() + .filter(this::isSuitablePeer) .filter(peer -> !triedPeers.contains(peer)); } @@ -137,20 +140,19 @@ private void refreshPeers() { // or the least useful if (peers.peerCount() >= peers.getMaxPeers()) { - failedPeers.stream() - .filter(peer -> !peer.isDisconnected()) - .findAny() - .or(() -> peers.streamAvailablePeers().sorted(peers.getBestChainComparator()).findFirst()) + failedPeers.stream().filter(peer -> !peer.isDisconnected()).findAny().stream() + .min(EthPeers.MOST_USEFUL_PEER) + .or(() -> peers.streamAvailablePeers().min(EthPeers.MOST_USEFUL_PEER)) .ifPresent( peer -> { LOG.atDebug() .setMessage( - "Refresh peers disconnecting peer {}... Waiting for better peers. Current {} of max {}") - .addArgument(peer::getShortNodeId) + "Refresh peers disconnecting peer {} Waiting for better peers. Current {} of max {}") + .addArgument(peer::getLoggableId) .addArgument(peers::peerCount) .addArgument(peers::getMaxPeers) .log(); - peer.disconnect(DisconnectReason.USELESS_PEER); + peer.disconnect(DisconnectReason.USELESS_PEER_BY_REPUTATION); }); } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/BufferedGetPooledTransactionsFromPeerFetcher.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/BufferedGetPooledTransactionsFromPeerFetcher.java index 0e414ba5db8..5bf2a364108 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/BufferedGetPooledTransactionsFromPeerFetcher.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/BufferedGetPooledTransactionsFromPeerFetcher.java @@ -89,10 +89,10 @@ public void requestTransactions() { transactionTracker.markTransactionsAsSeen(peer, retrievedTransactions); LOG.atTrace() - .setMessage("Got {} transactions of {} hashes requested from peer {}...") + .setMessage("Got {} transactions of {} hashes requested from peer {}") .addArgument(retrievedTransactions::size) .addArgument(task.getTransactionHashes()::size) - .addArgument(peer::getShortNodeId) + .addArgument(peer::getLoggableId) .log(); transactionPool.addRemoteTransactions(retrievedTransactions); @@ -120,8 +120,8 @@ private List getTxHashesAnnounced() { metrics.incrementAlreadySeenTransactions(metricLabel, alreadySeenCount); LOG.atTrace() .setMessage( - "Transaction hashes to request from peer {}... fresh count {}, already seen count {}") - .addArgument(peer::getShortNodeId) + "Transaction hashes to request from peer {} fresh count {}, already seen count {}") + .addArgument(peer::getLoggableId) .addArgument(toRetrieve::size) .addArgument(alreadySeenCount) .log(); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetBodiesFromPeerTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetBodiesFromPeerTask.java index 4cd66830251..56a85b040b0 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetBodiesFromPeerTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetBodiesFromPeerTask.java @@ -20,7 +20,7 @@ import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.Deposit; +import org.hyperledger.besu.ethereum.core.Request; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.eth.manager.EthContext; @@ -151,33 +151,33 @@ static class BodyIdentifier { private final Bytes32 transactionsRoot; private final Bytes32 ommersHash; private final Bytes32 withdrawalsRoot; - private final Bytes32 depositsRoot; + private final Bytes32 requestsRoot; public BodyIdentifier( final Bytes32 transactionsRoot, final Bytes32 ommersHash, final Bytes32 withdrawalsRoot, - final Bytes32 depositsRoot) { + final Bytes32 requestsRoot) { this.transactionsRoot = transactionsRoot; this.ommersHash = ommersHash; this.withdrawalsRoot = withdrawalsRoot; - this.depositsRoot = depositsRoot; + this.requestsRoot = requestsRoot; } public BodyIdentifier(final BlockBody body) { - this(body.getTransactions(), body.getOmmers(), body.getWithdrawals(), body.getDeposits()); + this(body.getTransactions(), body.getOmmers(), body.getWithdrawals(), body.getRequests()); } public BodyIdentifier( final List transactions, final List ommers, final Optional> withdrawals, - final Optional> deposits) { + final Optional> requests) { this( BodyValidation.transactionsRoot(transactions), BodyValidation.ommersHash(ommers), withdrawals.map(BodyValidation::withdrawalsRoot).orElse(null), - deposits.map(BodyValidation::depositsRoot).orElse(null)); + requests.map(BodyValidation::requestsRoot).orElse(null)); } public BodyIdentifier(final BlockHeader header) { @@ -185,7 +185,7 @@ public BodyIdentifier(final BlockHeader header) { header.getTransactionsRoot(), header.getOmmersHash(), header.getWithdrawalsRoot().orElse(null), - header.getDepositsRoot().orElse(null)); + header.getRequestsRoot().orElse(null)); } @Override @@ -196,12 +196,12 @@ public boolean equals(final Object o) { return Objects.equals(transactionsRoot, that.transactionsRoot) && Objects.equals(ommersHash, that.ommersHash) && Objects.equals(withdrawalsRoot, that.withdrawalsRoot) - && Objects.equals(depositsRoot, that.depositsRoot); + && Objects.equals(requestsRoot, that.requestsRoot); } @Override public int hashCode() { - return Objects.hash(transactionsRoot, ommersHash, withdrawalsRoot, depositsRoot); + return Objects.hash(transactionsRoot, ommersHash, withdrawalsRoot, requestsRoot); } } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetHeadersFromPeerByHashTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetHeadersFromPeerByHashTask.java index 4fb2ef226f4..a5de52a481c 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetHeadersFromPeerByHashTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetHeadersFromPeerByHashTask.java @@ -120,10 +120,10 @@ protected PendingPeerRequest sendRequest() { return sendRequestToPeer( peer -> { LOG.atTrace() - .setMessage("Requesting {} headers (hash {}...) from peer {}...") + .setMessage("Requesting {} headers (hash {}...) from peer {}") .addArgument(count) .addArgument(referenceHash.slice(0, 6)) - .addArgument(peer::getShortNodeId) + .addArgument(peer::getLoggableId) .log(); return peer.getHeadersByHash(referenceHash, count, skip, reverse); }, diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetHeadersFromPeerByNumberTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetHeadersFromPeerByNumberTask.java index 3f6e153df37..b4f5aa4f487 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetHeadersFromPeerByNumberTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetHeadersFromPeerByNumberTask.java @@ -81,7 +81,7 @@ protected PendingPeerRequest sendRequest() { .setMessage("Requesting {} headers (blockNumber {}) from peer {}.") .addArgument(count) .addArgument(blockNumber) - .addArgument(peer::getShortNodeId) + .addArgument(peer::getLoggableId) .log(); return peer.getHeadersByNumber(blockNumber, count, skip, reverse); }, diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetNodeDataFromPeerTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetNodeDataFromPeerTask.java index d6b0de35069..e37ca7291ec 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetNodeDataFromPeerTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetNodeDataFromPeerTask.java @@ -67,9 +67,9 @@ protected PendingPeerRequest sendRequest() { return sendRequestToPeer( peer -> { LOG.atTrace() - .setMessage("Requesting {} node data entries from peer {}...") + .setMessage("Requesting {} node data entries from peer {}") .addArgument(hashes::size) - .addArgument(peer::getShortNodeId) + .addArgument(peer::getLoggableId) .log(); return peer.getNodeData(hashes); }, diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetPooledTransactionsFromPeerTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetPooledTransactionsFromPeerTask.java index e3a45418d18..5eb17ab1464 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetPooledTransactionsFromPeerTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetPooledTransactionsFromPeerTask.java @@ -62,9 +62,9 @@ protected PendingPeerRequest sendRequest() { return sendRequestToPeer( peer -> { LOG.atTrace() - .setMessage("Requesting {} transaction pool entries from peer {}...") + .setMessage("Requesting {} transaction pool entries from peer {}") .addArgument(hashes::size) - .addArgument(peer::getShortNodeId) + .addArgument(peer::getLoggableId) .log(); return peer.getPooledTransactions(new ArrayList<>(hashes)); }, diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetReceiptsFromPeerTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetReceiptsFromPeerTask.java index 42e0928829f..5b52078b23e 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetReceiptsFromPeerTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/GetReceiptsFromPeerTask.java @@ -84,9 +84,9 @@ protected PendingPeerRequest sendRequest() { return sendRequestToPeer( peer -> { LOG.atTrace() - .setMessage("Requesting {} receipts from peer {}...") + .setMessage("Requesting {} receipts from peer {}") .addArgument(blockHeaders::size) - .addArgument(peer::getShortNodeId) + .addArgument(peer::getLoggableId) .log(); return peer.getReceipts(blockHashes); }, diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/RetryingGetBlockFromPeersTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/RetryingGetBlockFromPeersTask.java index f7266f9c88e..e4e472f4f4c 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/RetryingGetBlockFromPeersTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/RetryingGetBlockFromPeersTask.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/RetryingGetBlocksFromPeersTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/RetryingGetBlocksFromPeersTask.java index 0e663ffe24d..ed2c1756257 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/RetryingGetBlocksFromPeersTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/RetryingGetBlocksFromPeersTask.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -80,7 +80,7 @@ protected CompletableFuture>> executeTaskOnCurrentPee if (peerResult.getResult().isEmpty()) { currentPeer.recordUselessResponse("GetBodiesFromPeerTask"); throw new IncompleteResultsException( - "No blocks returned by peer " + currentPeer.getShortNodeId()); + "No blocks returned by peer " + currentPeer.getLoggableId()); } result.complete(peerResult); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/RetryingGetHeadersEndingAtFromPeerByHashTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/RetryingGetHeadersEndingAtFromPeerByHashTask.java index 5b5b0cef359..5ded06e023b 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/RetryingGetHeadersEndingAtFromPeerByHashTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/manager/task/RetryingGetHeadersEndingAtFromPeerByHashTask.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.eth.manager.task; import static com.google.common.base.Preconditions.checkNotNull; @@ -104,7 +103,7 @@ protected CompletableFuture> executeTaskOnCurrentPeer( "No block headers for hash " + referenceHash + " returned by peer " - + currentPeer.getShortNodeId()); + + currentPeer.getLoggableId()); } result.complete(peerResult.getResult()); return peerResult.getResult(); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/NewBlockMessage.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/NewBlockMessage.java index 9227e574136..d198cacb36c 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/NewBlockMessage.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/NewBlockMessage.java @@ -44,11 +44,20 @@ public int getCode() { return MESSAGE_CODE; } - public static NewBlockMessage create(final Block block, final Difficulty totalDifficulty) { + public static NewBlockMessage create( + final Block block, final Difficulty totalDifficulty, final int maxMessageSize) + throws IllegalArgumentException { final NewBlockMessageData msgData = new NewBlockMessageData(block, totalDifficulty); final BytesValueRLPOutput out = new BytesValueRLPOutput(); msgData.writeTo(out); - return new NewBlockMessage(out.encoded()); + final Bytes data = out.encoded(); + if (data.size() > maxMessageSize) { + throw new IllegalArgumentException( + String.format( + "Block message size %d bytes is larger than allowed message size %d bytes", + data.size(), maxMessageSize)); + } + return new NewBlockMessage(data); } public static NewBlockMessage readFrom(final MessageData message) { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/ReceiptsMessage.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/ReceiptsMessage.java index 9d9ed2cc7f1..7ca823cd8d7 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/ReceiptsMessage.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/ReceiptsMessage.java @@ -46,7 +46,7 @@ public static ReceiptsMessage create(final List> receip receipts.forEach( (receiptSet) -> { tmp.startList(); - receiptSet.forEach(r -> r.writeTo(tmp)); + receiptSet.forEach(r -> r.writeToForNetwork(tmp)); tmp.endList(); }); tmp.endList(); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/AccountRangeMessage.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/AccountRangeMessage.java index 417bbd27fcd..8e1b1a31a54 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/AccountRangeMessage.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/AccountRangeMessage.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -24,6 +24,7 @@ import java.math.BigInteger; import java.util.List; import java.util.Map; +import java.util.NavigableMap; import java.util.Optional; import java.util.TreeMap; @@ -133,7 +134,7 @@ private Bytes toFullAccount(final RLPInput rlpInput) { @Value.Immutable public interface AccountRangeData { - TreeMap accounts(); + NavigableMap accounts(); ArrayDeque proofs(); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/ByteCodesMessage.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/ByteCodesMessage.java index b86fcdb41ae..9012b6c6f98 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/ByteCodesMessage.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/ByteCodesMessage.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetAccountRangeMessage.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetAccountRangeMessage.java index 66aae554d48..8f5fcaf9a73 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetAccountRangeMessage.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetAccountRangeMessage.java @@ -22,6 +22,7 @@ import org.hyperledger.besu.ethereum.rlp.RLPInput; import java.math.BigInteger; +import java.util.Optional; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; @@ -95,6 +96,8 @@ public Range range(final boolean withRequestId) { @Value.Immutable public interface Range { + Optional requestId(); + Hash worldStateRootHash(); Hash startKeyHash(); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetByteCodesMessage.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetByteCodesMessage.java index 25302084d1b..658a8775842 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetByteCodesMessage.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetByteCodesMessage.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetStorageRangeMessage.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetStorageRangeMessage.java index fe27f9eb5b1..1c912b3ae0d 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetStorageRangeMessage.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetStorageRangeMessage.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -116,7 +116,7 @@ public StorageRange range(final boolean withRequestId) { } if (input.nextIsNull()) { input.skipNext(); - range.endKeyHash(Hash.ZERO); + range.endKeyHash(Hash.LAST); } else { range.endKeyHash(Hash.wrap(Bytes32.wrap(input.readBytes32()))); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetTrieNodesMessage.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetTrieNodesMessage.java index 1d184231edf..6ee9cf37eb0 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetTrieNodesMessage.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetTrieNodesMessage.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/SnapV1.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/SnapV1.java index 219d81031ce..784a7e1964d 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/SnapV1.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/SnapV1.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/StorageRangeMessage.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/StorageRangeMessage.java index ab4a353fea2..6167583c02d 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/StorageRangeMessage.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/StorageRangeMessage.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -22,6 +22,7 @@ import java.math.BigInteger; import java.util.List; +import java.util.NavigableMap; import java.util.Optional; import java.util.TreeMap; @@ -49,13 +50,13 @@ public static StorageRangeMessage readFrom(final MessageData message) { } public static StorageRangeMessage create( - final ArrayDeque> slots, final List proof) { + final ArrayDeque> slots, final List proof) { return create(Optional.empty(), slots, proof); } public static StorageRangeMessage create( final Optional requestId, - final ArrayDeque> slots, + final ArrayDeque> slots, final List proof) { final BytesValueRLPOutput tmp = new BytesValueRLPOutput(); tmp.startList(); @@ -88,7 +89,7 @@ public int getCode() { } public SlotRangeData slotsData(final boolean withRequestId) { - final ArrayDeque> slots = new ArrayDeque<>(); + final ArrayDeque> slots = new ArrayDeque<>(); final ArrayDeque proofs = new ArrayDeque<>(); final RLPInput input = new BytesValueRLPInput(data, false); input.enterList(); @@ -120,7 +121,7 @@ public SlotRangeData slotsData(final boolean withRequestId) { @Value.Immutable public interface SlotRangeData { - ArrayDeque> slots(); + ArrayDeque> slots(); ArrayDeque proofs(); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/TrieNodesMessage.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/TrieNodesMessage.java index 15208e7b420..5890d4a414a 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/TrieNodesMessage.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/messages/snap/TrieNodesMessage.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/peervalidation/AbstractPeerBlockValidator.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/peervalidation/AbstractPeerBlockValidator.java index 55c3dd89e56..9de0a77144b 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/peervalidation/AbstractPeerBlockValidator.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/peervalidation/AbstractPeerBlockValidator.java @@ -1,18 +1,16 @@ /* + * Copyright 2019 ConsenSys AG. * - * * Copyright 2019 ConsenSys AG. - * * - * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * * the License. You may obtain a copy of the License at - * * - * * http://www.apache.org/licenses/LICENSE-2.0 - * * - * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * * specific language governing permissions and limitations under the License. - * * - * * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 */ package org.hyperledger.besu.ethereum.eth.peervalidation; @@ -86,12 +84,20 @@ public CompletableFuture validatePeer( } final List headers = res.getResult(); if (headers.size() == 0) { - // If no headers are returned, fail - LOG.debug( - "Peer {} is invalid because required block ({}) is unavailable.", - ethPeer, - blockNumber); - return false; + if (blockIsRequired()) { + // If no headers are returned, fail + LOG.debug( + "Peer {} is invalid because required block ({}) is unavailable.", + ethPeer, + blockNumber); + return false; + } else { + LOG.debug( + "Peer {} deemed valid because unavailable block ({}) is not required.", + ethPeer, + blockNumber); + return true; + } } final BlockHeader header = headers.get(0); return validateBlockHeader(ethPeer, header); @@ -105,6 +111,10 @@ public boolean canBeValidated(final EthPeer ethPeer) { return ethPeer.chainState().getEstimatedHeight() >= (blockNumber + chainHeightEstimationBuffer); } + protected boolean blockIsRequired() { + return true; + } + @Override public Duration nextValidationCheckTimeout(final EthPeer ethPeer) { if (!ethPeer.chainState().hasEstimatedHeight()) { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/peervalidation/CheckpointBlocksPeerValidator.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/peervalidation/CheckpointBlocksPeerValidator.java index 2e36566d209..a66cc16785d 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/peervalidation/CheckpointBlocksPeerValidator.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/peervalidation/CheckpointBlocksPeerValidator.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/peervalidation/DaoForkPeerValidator.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/peervalidation/DaoForkPeerValidator.java index d038ad0e9a9..01ce6144ee0 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/peervalidation/DaoForkPeerValidator.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/peervalidation/DaoForkPeerValidator.java @@ -49,4 +49,13 @@ boolean validateBlockHeader(final EthPeer ethPeer, final BlockHeader header) { } return validDaoBlock; } + + /** + * In order to support chain history pruning, clients do not need to have the dao fork block to be + * deemed valid. + */ + @Override + protected boolean blockIsRequired() { + return false; + } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/peervalidation/RequiredBlocksPeerValidator.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/peervalidation/RequiredBlocksPeerValidator.java index de5a6f359fa..bf8716c328b 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/peervalidation/RequiredBlocksPeerValidator.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/peervalidation/RequiredBlocksPeerValidator.java @@ -1,18 +1,16 @@ /* + * Copyright ConsenSys AG. * - * * Copyright ConsenSys AG. - * * - * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * * the License. You may obtain a copy of the License at - * * - * * http://www.apache.org/licenses/LICENSE-2.0 - * * - * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * * specific language governing permissions and limitations under the License. - * * - * * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 */ package org.hyperledger.besu.ethereum.eth.peervalidation; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/AbstractSyncTargetManager.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/AbstractSyncTargetManager.java new file mode 100644 index 00000000000..f5b5f978cb3 --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/AbstractSyncTargetManager.java @@ -0,0 +1,133 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.sync; + +import static java.util.concurrent.CompletableFuture.completedFuture; + +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.manager.EthPeer; +import org.hyperledger.besu.ethereum.eth.manager.task.WaitForPeerTask; +import org.hyperledger.besu.ethereum.eth.sync.state.SyncTarget; +import org.hyperledger.besu.ethereum.eth.sync.tasks.DetermineCommonAncestorTask; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.plugin.services.MetricsSystem; + +import java.time.Duration; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class AbstractSyncTargetManager { + + private static final Logger LOG = LoggerFactory.getLogger(AbstractSyncTargetManager.class); + + private final AtomicBoolean cancelled = new AtomicBoolean(false); + + private final SynchronizerConfiguration config; + private final ProtocolSchedule protocolSchedule; + private final ProtocolContext protocolContext; + private final EthContext ethContext; + private final MetricsSystem metricsSystem; + + protected AbstractSyncTargetManager( + final SynchronizerConfiguration config, + final ProtocolSchedule protocolSchedule, + final ProtocolContext protocolContext, + final EthContext ethContext, + final MetricsSystem metricsSystem) { + this.config = config; + this.protocolSchedule = protocolSchedule; + this.protocolContext = protocolContext; + this.ethContext = ethContext; + this.metricsSystem = metricsSystem; + } + + public CompletableFuture findSyncTarget() { + if (isCancelled()) { + return completedFuture(null); + } + return selectBestAvailableSyncTarget() + .thenCompose( + maybeBestPeer -> { + if (maybeBestPeer.isPresent()) { + final EthPeer bestPeer = maybeBestPeer.get(); + return DetermineCommonAncestorTask.create( + protocolSchedule, + protocolContext, + ethContext, + bestPeer, + config.getDownloaderHeaderRequestSize(), + metricsSystem) + .run() + .handle( + (result, error) -> { + if (error != null) { + LOG.debug("Failed to find common ancestor", error); + } + return result; + }) + .thenCompose( + (target) -> { + if (target == null) { + return waitForPeerAndThenSetSyncTarget(); + } + final SyncTarget syncTarget = new SyncTarget(bestPeer, target); + LOG.debug( + "Found common ancestor with peer {} at block {}", + bestPeer, + target.getNumber()); + return completedFuture(syncTarget); + }) + .thenCompose( + syncTarget -> + finalizeSelectedSyncTarget(syncTarget) + .map(CompletableFuture::completedFuture) + .orElseGet(this::waitForPeerAndThenSetSyncTarget)); + } else { + return waitForPeerAndThenSetSyncTarget(); + } + }); + } + + public synchronized void cancel() { + cancelled.set(true); + } + + protected Optional finalizeSelectedSyncTarget(final SyncTarget syncTarget) { + return Optional.of(syncTarget); + } + + protected abstract CompletableFuture> selectBestAvailableSyncTarget(); + + private CompletableFuture waitForPeerAndThenSetSyncTarget() { + return waitForNewPeer().handle((r, t) -> r).thenCompose((r) -> findSyncTarget()); + } + + private CompletableFuture waitForNewPeer() { + return ethContext + .getScheduler() + .timeout(WaitForPeerTask.create(ethContext, metricsSystem), Duration.ofSeconds(5)); + } + + private boolean isCancelled() { + return cancelled.get(); + } + + public abstract boolean shouldContinueDownloading(); +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/BlockBroadcaster.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/BlockBroadcaster.java index 1bf55b01f81..b81c0448324 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/BlockBroadcaster.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/BlockBroadcaster.java @@ -28,11 +28,13 @@ public class BlockBroadcaster { private static final Logger LOG = LoggerFactory.getLogger(BlockBroadcaster.class); private final EthContext ethContext; + private final int maxMessageSize; private final Subscribers blockPropagatedSubscribers = Subscribers.create(); - public BlockBroadcaster(final EthContext ethContext) { + public BlockBroadcaster(final EthContext ethContext, final int maxMessageSize) { this.ethContext = ethContext; + this.maxMessageSize = maxMessageSize; } public long subscribePropagateNewBlocks(final BlockPropagatedSubscriber callback) { @@ -45,7 +47,13 @@ public void unsubscribePropagateNewBlocks(final long id) { public void propagate(final Block block, final Difficulty totalDifficulty) { blockPropagatedSubscribers.forEach(listener -> listener.accept(block, totalDifficulty)); - final NewBlockMessage newBlockMessage = NewBlockMessage.create(block, totalDifficulty); + final NewBlockMessage newBlockMessage; + try { + newBlockMessage = NewBlockMessage.create(block, totalDifficulty, this.maxMessageSize); + } catch (final IllegalArgumentException e) { + LOG.error("Failed to create block", e); + return; + } ethContext .getEthPeers() .streamAvailablePeers() diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/BlockPropagationManager.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/BlockPropagationManager.java index 2e9e13b45db..aee8ec48ea7 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/BlockPropagationManager.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/BlockPropagationManager.java @@ -20,6 +20,7 @@ import org.hyperledger.besu.consensus.merge.UnverifiedForkchoiceListener; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockCause; import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.BlockAddedEvent; import org.hyperledger.besu.ethereum.chain.BlockAddedEvent.EventType; @@ -338,7 +339,7 @@ private void handleNewBlockFromNetwork(final EthMessage message) { "Malformed NEW_BLOCK message received from peer (BREACH_OF_PROTOCOL), disconnecting: {}", message.getPeer(), e); - message.getPeer().disconnect(DisconnectReason.BREACH_OF_PROTOCOL); + message.getPeer().disconnect(DisconnectReason.BREACH_OF_PROTOCOL_MALFORMED_MESSAGE_RECEIVED); } } @@ -401,7 +402,7 @@ private void handleNewBlockHashesFromNetwork(final EthMessage message) { "Malformed NEW_BLOCK_HASHES message received from peer (BREACH_OF_PROTOCOL), disconnecting: {}", message.getPeer(), e); - message.getPeer().disconnect(DisconnectReason.BREACH_OF_PROTOCOL); + message.getPeer().disconnect(DisconnectReason.BREACH_OF_PROTOCOL_MALFORMED_MESSAGE_RECEIVED); } } @@ -609,7 +610,7 @@ CompletableFuture importOrSavePendingBlock(final Block block, final Bytes + block.toLogString())); final ProtocolSpec protocolSpec = protocolSchedule.getByBlockHeader(block.getHeader()); final BlockHeaderValidator blockHeaderValidator = protocolSpec.getBlockHeaderValidator(); - final BadBlockManager badBlockManager = protocolSpec.getBadBlocksManager(); + final BadBlockManager badBlockManager = protocolContext.getBadBlockManager(); return ethContext .getScheduler() .scheduleSyncWorkerTask( @@ -665,13 +666,15 @@ private CompletableFuture validateAndProcessPendingBlock( final Block block, final BlockHeader parent, final BadBlockManager badBlockManager) { + final HeaderValidationMode validationMode = HeaderValidationMode.FULL; if (blockHeaderValidator.validateHeader( - block.getHeader(), parent, protocolContext, HeaderValidationMode.FULL)) { + block.getHeader(), parent, protocolContext, validationMode)) { ethContext.getScheduler().scheduleSyncWorkerTask(() -> broadcastBlock(block, parent)); return runImportTask(block); } else { processingBlocksManager.registerBlockImportDone(block.getHash()); - badBlockManager.addBadBlock(block, Optional.empty()); + final String description = String.format("Failed header validation (%s)", validationMode); + badBlockManager.addBadBlock(block, BadBlockCause.fromValidationFailure(description)); LOG.warn( "Added to bad block manager for invalid header, failed to import announced block {}", block.toLogString()); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/ChainHeadTracker.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/ChainHeadTracker.java index 5e4862da1ec..67681acf6a7 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/ChainHeadTracker.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/ChainHeadTracker.java @@ -19,24 +19,25 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; -import org.hyperledger.besu.ethereum.eth.manager.EthPeers.ConnectCallback; +import org.hyperledger.besu.ethereum.eth.manager.task.AbstractPeerTask; import org.hyperledger.besu.ethereum.eth.manager.task.GetHeadersFromPeerByHashTask; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason; +import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage; import org.hyperledger.besu.plugin.services.MetricsSystem; +import java.util.List; +import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ChainHeadTracker implements ConnectCallback { +public class ChainHeadTracker { private static final Logger LOG = LoggerFactory.getLogger(ChainHeadTracker.class); private final EthContext ethContext; private final ProtocolSchedule protocolSchedule; - private final TrailingPeerLimiter trailingPeerLimiter; private final MetricsSystem metricsSystem; public ChainHeadTracker( @@ -46,7 +47,6 @@ public ChainHeadTracker( final MetricsSystem metricsSystem) { this.ethContext = ethContext; this.protocolSchedule = protocolSchedule; - this.trailingPeerLimiter = trailingPeerLimiter; this.metricsSystem = metricsSystem; } @@ -60,44 +60,53 @@ public static void trackChainHeadForPeers( new TrailingPeerLimiter(ethContext.getEthPeers(), trailingPeerRequirementsCalculator); final ChainHeadTracker tracker = new ChainHeadTracker(ethContext, protocolSchedule, trailingPeerLimiter, metricsSystem); - ethContext.getEthPeers().subscribeConnect(tracker); + ethContext.getEthPeers().setChainHeadTracker(tracker); blockchain.observeBlockAdded(trailingPeerLimiter); } - @Override - public void onPeerConnected(final EthPeer peer) { + public CompletableFuture getBestHeaderFromPeer(final EthPeer peer) { LOG.atDebug() .setMessage("Requesting chain head info from {}...") - .addArgument(peer::getShortNodeId) + .addArgument(peer::getLoggableId) .log(); - GetHeadersFromPeerByHashTask.forSingleHash( + final CompletableFuture>> + bestHeaderFromPeerCompletableFuture = getBestHeaderFromPeerCompletableFuture(peer); + final CompletableFuture future = new CompletableFuture<>(); + bestHeaderFromPeerCompletableFuture.whenComplete( + (peerResult, error) -> { + if (peerResult != null && !peerResult.getResult().isEmpty()) { + final BlockHeader chainHeadHeader = peerResult.getResult().get(0); + peer.chainState().update(chainHeadHeader); + future.complete(chainHeadHeader); + LOG.atDebug() + .setMessage("Retrieved chain head info {} from {}...") + .addArgument( + () -> chainHeadHeader.getNumber() + " (" + chainHeadHeader.getBlockHash() + ")") + .addArgument(peer::getLoggableId) + .log(); + } else { + LOG.atDebug() + .setMessage("Failed to retrieve chain head info. Disconnecting {}... {}") + .addArgument(peer::getLoggableId) + .addArgument(error != null ? error : "Empty Response") + .log(); + peer.disconnect( + DisconnectMessage.DisconnectReason.USELESS_PEER_FAILED_TO_RETRIEVE_CHAIN_HEAD); + future.complete(null); + } + }); + return future; + } + + public CompletableFuture>> + getBestHeaderFromPeerCompletableFuture(final EthPeer peer) { + return GetHeadersFromPeerByHashTask.forSingleHash( protocolSchedule, ethContext, Hash.wrap(peer.chainState().getBestBlock().getHash()), 0, metricsSystem) .assignPeer(peer) - .run() - .whenComplete( - (peerResult, error) -> { - if (peerResult != null && !peerResult.getResult().isEmpty()) { - final BlockHeader chainHeadHeader = peerResult.getResult().get(0); - peer.chainState().update(chainHeadHeader); - trailingPeerLimiter.enforceTrailingPeerLimit(); - LOG.atDebug() - .setMessage("Retrieved chain head info {} from {}...") - .addArgument( - () -> - chainHeadHeader.getNumber() - + " (" - + chainHeadHeader.getBlockHash() - + ")") - .addArgument(peer::getShortNodeId) - .log(); - } else { - LOG.debug("Failed to retrieve chain head info. Disconnecting {}", peer, error); - peer.disconnect(DisconnectReason.USELESS_PEER); - } - }); + .run(); } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/DefaultSynchronizer.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/DefaultSynchronizer.java index 2f98c1a8314..b7dc2adb160 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/DefaultSynchronizer.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/DefaultSynchronizer.java @@ -20,12 +20,12 @@ import org.hyperledger.besu.consensus.merge.UnverifiedForkchoiceListener; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.ProtocolContext; -import org.hyperledger.besu.ethereum.bonsai.BonsaiWorldStateProvider; import org.hyperledger.besu.ethereum.core.Synchronizer; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.sync.checkpointsync.CheckpointDownloaderFactory; import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncDownloader; import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncState; +import org.hyperledger.besu.ethereum.eth.sync.fastsync.NoSyncRequiredState; import org.hyperledger.besu.ethereum.eth.sync.fastsync.worldstate.FastDownloaderFactory; import org.hyperledger.besu.ethereum.eth.sync.fullsync.FullSyncDownloader; import org.hyperledger.besu.ethereum.eth.sync.fullsync.SyncTerminationCondition; @@ -35,10 +35,12 @@ import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.storage.StorageProvider; -import org.hyperledger.besu.ethereum.worldstate.Pruner; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.BonsaiWorldStateProvider; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.metrics.BesuMetricCategory; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.plugin.data.SyncStatus; +import org.hyperledger.besu.plugin.services.BesuEvents; import org.hyperledger.besu.plugin.services.BesuEvents.SyncStatusListener; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.util.log.FramedLogMessage; @@ -62,11 +64,11 @@ public class DefaultSynchronizer implements Synchronizer, UnverifiedForkchoiceLi private static final Logger LOG = LoggerFactory.getLogger(DefaultSynchronizer.class); - private final Optional maybePruner; private final SyncState syncState; private final AtomicBoolean running = new AtomicBoolean(false); private final Optional blockPropagationManager; private final Supplier>> fastSyncFactory; + private final SyncDurationMetrics syncDurationMetrics; private Optional> fastSyncDownloader; private final Optional fullSyncDownloader; private final ProtocolContext protocolContext; @@ -77,9 +79,8 @@ public DefaultSynchronizer( final SynchronizerConfiguration syncConfig, final ProtocolSchedule protocolSchedule, final ProtocolContext protocolContext, - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final BlockBroadcaster blockBroadcaster, - final Optional maybePruner, final EthContext ethContext, final SyncState syncState, final Path dataDirectory, @@ -88,7 +89,6 @@ public DefaultSynchronizer( final MetricsSystem metricsSystem, final SyncTerminationCondition terminationCondition, final PivotBlockSelector pivotBlockSelector) { - this.maybePruner = maybePruner; this.syncState = syncState; this.pivotBlockSelector = pivotBlockSelector; this.protocolContext = protocolContext; @@ -101,6 +101,11 @@ public DefaultSynchronizer( this::calculateTrailingPeerRequirements, metricsSystem); + if (syncConfig.getSyncMode() == SyncMode.SNAP + || syncConfig.getSyncMode() == SyncMode.CHECKPOINT) { + SnapServerChecker.createAndSetSnapServerChecker(ethContext, metricsSystem); + } + this.blockPropagationManager = terminationCondition.shouldStopDownload() ? Optional.empty() @@ -115,6 +120,8 @@ public DefaultSynchronizer( metricsSystem, blockBroadcaster)); + syncDurationMetrics = new SyncDurationMetrics(metricsSystem); + this.fullSyncDownloader = terminationCondition.shouldStopDownload() ? Optional.empty() @@ -126,7 +133,8 @@ public DefaultSynchronizer( ethContext, syncState, metricsSystem, - terminationCondition)); + terminationCondition, + syncDurationMetrics)); if (SyncMode.FAST.equals(syncConfig.getSyncMode())) { this.fastSyncFactory = @@ -139,10 +147,11 @@ public DefaultSynchronizer( protocolContext, metricsSystem, ethContext, - worldStateStorage, + worldStateStorageCoordinator, syncState, - clock); - } else if (SyncMode.X_CHECKPOINT.equals(syncConfig.getSyncMode())) { + clock, + syncDurationMetrics); + } else if (syncConfig.getSyncMode() == SyncMode.CHECKPOINT) { this.fastSyncFactory = () -> CheckpointDownloaderFactory.createCheckpointDownloader( @@ -154,9 +163,10 @@ public DefaultSynchronizer( protocolContext, metricsSystem, ethContext, - worldStateStorage, + worldStateStorageCoordinator, syncState, - clock); + clock, + syncDurationMetrics); } else { this.fastSyncFactory = () -> @@ -169,9 +179,10 @@ public DefaultSynchronizer( protocolContext, metricsSystem, ethContext, - worldStateStorage, + worldStateStorageCoordinator, syncState, - clock); + clock, + syncDurationMetrics); } // create a non-resync fast sync downloader: @@ -189,7 +200,7 @@ public DefaultSynchronizer( () -> getSyncStatus().isPresent() ? 0 : 1); } - private TrailingPeerRequirements calculateTrailingPeerRequirements() { + public TrailingPeerRequirements calculateTrailingPeerRequirements() { return fastSyncDownloader .flatMap(FastSyncDownloader::calculateTrailingPeerRequirements) .orElse( @@ -202,6 +213,9 @@ private TrailingPeerRequirements calculateTrailingPeerRequirements() { public CompletableFuture start() { if (running.compareAndSet(false, true)) { LOG.info("Starting synchronizer."); + + syncDurationMetrics.startTimer(SyncDurationMetrics.Labels.TOTAL_SYNC_DURATION); + blockPropagationManager.ifPresent( manager -> { if (!manager.isRunning()) { @@ -228,7 +242,6 @@ public void stop() { LOG.info("Stopping synchronizer"); fastSyncDownloader.ifPresent(FastSyncDownloader::stop); fullSyncDownloader.ifPresent(FullSyncDownloader::stop); - maybePruner.ifPresent(Pruner::stop); blockPropagationManager.ifPresent( manager -> { if (manager.isRunning()) { @@ -239,27 +252,31 @@ public void stop() { } @Override - public void awaitStop() throws InterruptedException { - if (maybePruner.isPresent()) { - maybePruner.get().awaitStop(); - } - } + public void awaitStop() {} private CompletableFuture handleSyncResult(final FastSyncState result) { if (!running.get()) { // We've been shutdown which will have triggered the fast sync future to complete return CompletableFuture.completedFuture(null); } - fastSyncDownloader.ifPresent(FastSyncDownloader::deleteFastSyncState); - result - .getPivotBlockHeader() - .ifPresent( - blockHeader -> protocolContext.getWorldStateArchive().resetArchiveStateTo(blockHeader)); - LOG.info( - "Sync completed successfully with pivot block {}", - result.getPivotBlockNumber().getAsLong()); - pivotBlockSelector.close(); - syncState.markInitialSyncPhaseAsDone(); + + if (result instanceof NoSyncRequiredState) { + LOG.info("Sync ended (no sync required)"); + syncState.markInitialSyncPhaseAsDone(); + } else { + fastSyncDownloader.ifPresent(FastSyncDownloader::deleteFastSyncState); + result + .getPivotBlockHeader() + .ifPresent( + blockHeader -> + protocolContext.getWorldStateArchive().resetArchiveStateTo(blockHeader)); + if (result.hasPivotBlockHash()) + LOG.info( + "Sync completed successfully with pivot block {}", + result.getPivotBlockNumber().getAsLong()); + pivotBlockSelector.close(); + syncState.markInitialSyncPhaseAsDone(); + } if (terminationCondition.shouldContinueDownload()) { return startFullSync(); @@ -270,7 +287,6 @@ private CompletableFuture handleSyncResult(final FastSyncState result) { } private CompletableFuture startFullSync() { - maybePruner.ifPresent(Pruner::start); return fullSyncDownloader .map(FullSyncDownloader::start) .orElse(CompletableFuture.completedFuture(null)) @@ -372,12 +388,23 @@ public boolean unsubscribeInSync(final long listenerId) { return syncState.unsubscribeSyncStatus(listenerId); } + public long subscribeInitialSync(final BesuEvents.InitialSyncCompletionListener listener) { + return syncState.subscribeCompletionReached(listener); + } + + public boolean unsubscribeInitialSync(final long listenerId) { + return syncState.unsubscribeInitialConditionReached(listenerId); + } + private Void finalizeSync(final Void unused) { LOG.info("Stopping block propagation."); blockPropagationManager.ifPresent(BlockPropagationManager::stop); LOG.info("Stopping the pruner."); - maybePruner.ifPresent(Pruner::stop); running.set(false); + + syncDurationMetrics.stopTimer(SyncDurationMetrics.Labels.FLAT_DB_HEAL); + syncDurationMetrics.stopTimer(SyncDurationMetrics.Labels.TOTAL_SYNC_DURATION); + return null; } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/PipelineChainDownloader.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/PipelineChainDownloader.java index d1c3d008830..8f2ee1d48a0 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/PipelineChainDownloader.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/PipelineChainDownloader.java @@ -24,6 +24,7 @@ import org.hyperledger.besu.ethereum.eth.sync.tasks.exceptions.InvalidBlockException; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason; import org.hyperledger.besu.metrics.BesuMetricCategory; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.plugin.services.metrics.Counter; import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; @@ -43,7 +44,7 @@ public class PipelineChainDownloader implements ChainDownloader { private static final Logger LOG = LoggerFactory.getLogger(PipelineChainDownloader.class); static final Duration PAUSE_AFTER_ERROR_DURATION = Duration.ofSeconds(2); private final SyncState syncState; - private final SyncTargetManager syncTargetManager; + private final AbstractSyncTargetManager syncTargetManager; private final DownloadPipelineFactory downloadPipelineFactory; private final EthScheduler scheduler; @@ -51,18 +52,21 @@ public class PipelineChainDownloader implements ChainDownloader { private final AtomicBoolean cancelled = new AtomicBoolean(false); private final Counter pipelineCompleteCounter; private final Counter pipelineErrorCounter; + private final SyncDurationMetrics syncDurationMetrics; private Pipeline currentDownloadPipeline; public PipelineChainDownloader( final SyncState syncState, - final SyncTargetManager syncTargetManager, + final AbstractSyncTargetManager syncTargetManager, final DownloadPipelineFactory downloadPipelineFactory, final EthScheduler scheduler, - final MetricsSystem metricsSystem) { + final MetricsSystem metricsSystem, + final SyncDurationMetrics syncDurationMetrics) { this.syncState = syncState; this.syncTargetManager = syncTargetManager; this.downloadPipelineFactory = downloadPipelineFactory; this.scheduler = scheduler; + this.syncDurationMetrics = syncDurationMetrics; final LabelledMetric labelledCounter = metricsSystem.createLabelledCounter( @@ -79,12 +83,16 @@ public CompletableFuture start() { if (!started.compareAndSet(false, true)) { throw new IllegalStateException("Cannot start a chain download twice"); } + + syncDurationMetrics.startTimer(SyncDurationMetrics.Labels.CHAIN_DOWNLOAD_DURATION); + return performDownload(); } @Override public synchronized void cancel() { cancelled.set(true); + syncTargetManager.cancel(); if (currentDownloadPipeline != null) { currentDownloadPipeline.abort(); } @@ -120,7 +128,7 @@ private CompletionStage handleFailedDownload(final Throwable error) { LOG.warn( "Invalid block detected (BREACH_OF_PROTOCOL). Disconnecting from sync target. {}", ExceptionUtils.rootCause(error).getMessage()); - syncState.disconnectSyncTarget(DisconnectReason.BREACH_OF_PROTOCOL); + syncState.disconnectSyncTarget(DisconnectReason.BREACH_OF_PROTOCOL_INVALID_BLOCK); } if (!cancelled.get() && syncTargetManager.shouldContinueDownloading()) { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/PivotBlockSelector.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/PivotBlockSelector.java index b19727cb4d0..fdb6df140ea 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/PivotBlockSelector.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/PivotBlockSelector.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/SnapServerChecker.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/SnapServerChecker.java new file mode 100644 index 00000000000..c7fa141837b --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/SnapServerChecker.java @@ -0,0 +1,86 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.sync; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.manager.EthPeer; +import org.hyperledger.besu.ethereum.eth.manager.snap.GetAccountRangeFromPeerTask; +import org.hyperledger.besu.ethereum.eth.manager.task.AbstractPeerTask; +import org.hyperledger.besu.ethereum.eth.messages.snap.AccountRangeMessage; +import org.hyperledger.besu.plugin.services.MetricsSystem; + +import java.util.concurrent.CompletableFuture; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SnapServerChecker { + + private static final Logger LOG = LoggerFactory.getLogger(SnapServerChecker.class); + + private final EthContext ethContext; + private final MetricsSystem metricsSystem; + + public SnapServerChecker(final EthContext ethContext, final MetricsSystem metricsSystem) { + this.ethContext = ethContext; + this.metricsSystem = metricsSystem; + } + + public static void createAndSetSnapServerChecker( + final EthContext ethContext, final MetricsSystem metricsSystem) { + final SnapServerChecker checker = new SnapServerChecker(ethContext, metricsSystem); + ethContext.getEthPeers().setSnapServerChecker(checker); + } + + public CompletableFuture check(final EthPeer peer, final BlockHeader peersHeadHeader) { + LOG.atTrace() + .setMessage("Checking whether peer {} is a snap server ...") + .addArgument(peer::getLoggableId) + .log(); + final CompletableFuture> + snapServerCheckCompletableFuture = getAccountRangeFromPeer(peer, peersHeadHeader); + final CompletableFuture future = new CompletableFuture<>(); + snapServerCheckCompletableFuture.whenComplete( + (peerResult, error) -> { + if (peerResult != null) { + if (!peerResult.getResult().accounts().isEmpty() + || !peerResult.getResult().proofs().isEmpty()) { + LOG.atTrace() + .setMessage("Peer {} is a snap server.") + .addArgument(peer::getLoggableId) + .log(); + future.complete(true); + } else { + LOG.atTrace() + .setMessage("Peer {} is not a snap server.") + .addArgument(peer::getLoggableId) + .log(); + future.complete(false); + } + } + }); + return future; + } + + public CompletableFuture> + getAccountRangeFromPeer(final EthPeer peer, final BlockHeader header) { + return GetAccountRangeFromPeerTask.forAccountRange( + ethContext, Hash.ZERO, Hash.ZERO, header, metricsSystem) + .assignPeer(peer) + .run(); + } +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/StorageExceptionManager.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/StorageExceptionManager.java index 97d1506cf3e..7a1bb00cc45 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/StorageExceptionManager.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/StorageExceptionManager.java @@ -30,6 +30,7 @@ public final class StorageExceptionManager { private static final long ERROR_THRESHOLD = 1000; private static long retryableErrorCounter; + /** * Determines if an operation can be retried based on the error received. This method checks if * the cause of the StorageException is a RocksDBException. If it is, it retrieves the status code diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/SyncMode.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/SyncMode.java index 7aaa1ba07e3..c69a128c54d 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/SyncMode.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/SyncMode.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.eth.sync; import java.util.EnumSet; +import java.util.Locale; import org.apache.commons.lang3.StringUtils; @@ -24,24 +25,15 @@ public enum SyncMode { // Perform light validation on older blocks, and switch to full validation for more recent blocks FAST, // Perform snapsync - X_SNAP, + SNAP, // Perform snapsync but starting from a checkpoint instead of starting from genesis - X_CHECKPOINT; + CHECKPOINT; public String normalize() { - if (this.toString().startsWith("X_")) { - // removes X_ at the beginning - return StringUtils.capitalize(this.toString().substring(2).toLowerCase()); - } - - return StringUtils.capitalize(this.toString().toLowerCase()); + return StringUtils.capitalize(this.toString().toLowerCase(Locale.ROOT)); } public static boolean isFullSync(final SyncMode syncMode) { - return !EnumSet.of(SyncMode.FAST, SyncMode.X_SNAP, SyncMode.X_CHECKPOINT).contains(syncMode); - } - - public static boolean isCheckpointSync(final SyncMode syncMode) { - return syncMode.equals(X_CHECKPOINT); + return !EnumSet.of(SyncMode.FAST, SyncMode.SNAP, SyncMode.CHECKPOINT).contains(syncMode); } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/SyncTargetManager.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/SyncTargetManager.java deleted file mode 100644 index c231d13af35..00000000000 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/SyncTargetManager.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.eth.sync; - -import static java.util.concurrent.CompletableFuture.completedFuture; - -import org.hyperledger.besu.ethereum.ProtocolContext; -import org.hyperledger.besu.ethereum.eth.manager.EthContext; -import org.hyperledger.besu.ethereum.eth.manager.EthPeer; -import org.hyperledger.besu.ethereum.eth.manager.task.WaitForPeerTask; -import org.hyperledger.besu.ethereum.eth.sync.state.SyncTarget; -import org.hyperledger.besu.ethereum.eth.sync.tasks.DetermineCommonAncestorTask; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.plugin.services.MetricsSystem; - -import java.time.Duration; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public abstract class SyncTargetManager { - - private static final Logger LOG = LoggerFactory.getLogger(SyncTargetManager.class); - - private final SynchronizerConfiguration config; - private final ProtocolSchedule protocolSchedule; - private final ProtocolContext protocolContext; - private final EthContext ethContext; - private final MetricsSystem metricsSystem; - - protected SyncTargetManager( - final SynchronizerConfiguration config, - final ProtocolSchedule protocolSchedule, - final ProtocolContext protocolContext, - final EthContext ethContext, - final MetricsSystem metricsSystem) { - this.config = config; - this.protocolSchedule = protocolSchedule; - this.protocolContext = protocolContext; - this.ethContext = ethContext; - this.metricsSystem = metricsSystem; - } - - public CompletableFuture findSyncTarget() { - return selectBestAvailableSyncTarget() - .thenCompose( - maybeBestPeer -> { - if (maybeBestPeer.isPresent()) { - final EthPeer bestPeer = maybeBestPeer.get(); - return DetermineCommonAncestorTask.create( - protocolSchedule, - protocolContext, - ethContext, - bestPeer, - config.getDownloaderHeaderRequestSize(), - metricsSystem) - .run() - .handle( - (result, error) -> { - if (error != null) { - LOG.debug("Failed to find common ancestor", error); - } - return result; - }) - .thenCompose( - (target) -> { - if (target == null) { - return waitForPeerAndThenSetSyncTarget(); - } - final SyncTarget syncTarget = new SyncTarget(bestPeer, target); - LOG.debug( - "Found common ancestor with peer {} at block {}", - bestPeer, - target.getNumber()); - return completedFuture(syncTarget); - }) - .thenCompose( - syncTarget -> - finalizeSelectedSyncTarget(syncTarget) - .map(CompletableFuture::completedFuture) - .orElseGet(this::waitForPeerAndThenSetSyncTarget)); - } else { - return waitForPeerAndThenSetSyncTarget(); - } - }); - } - - protected Optional finalizeSelectedSyncTarget(final SyncTarget syncTarget) { - return Optional.of(syncTarget); - } - - protected abstract CompletableFuture> selectBestAvailableSyncTarget(); - - private CompletableFuture waitForPeerAndThenSetSyncTarget() { - return waitForNewPeer().handle((r, t) -> r).thenCompose((r) -> findSyncTarget()); - } - - private CompletableFuture waitForNewPeer() { - return ethContext - .getScheduler() - .timeout(WaitForPeerTask.create(ethContext, metricsSystem), Duration.ofSeconds(5)); - } - - public abstract boolean shouldContinueDownloading(); -} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/SynchronizerConfiguration.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/SynchronizerConfiguration.java index ee542b1b0f7..d46da85dc48 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/SynchronizerConfiguration.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/SynchronizerConfiguration.java @@ -29,7 +29,7 @@ public class SynchronizerConfiguration { public static final int DEFAULT_PIVOT_DISTANCE_FROM_HEAD = 50; public static final float DEFAULT_FULL_VALIDATION_RATE = .1f; - public static final int DEFAULT_FAST_SYNC_MINIMUM_PEERS = 5; + public static final int DEFAULT_SYNC_MINIMUM_PEERS = 5; public static final int DEFAULT_WORLD_STATE_HASH_COUNT_PER_REQUEST = 384; public static final int DEFAULT_WORLD_STATE_REQUEST_PARALLELISM = 10; public static final int DEFAULT_WORLD_STATE_MAX_REQUESTS_WITHOUT_PROGRESS = 1000; @@ -43,7 +43,7 @@ public class SynchronizerConfiguration { public static final int DEFAULT_DOWNLOADER_CHECKPOINT_TIMEOUTS_PERMITTED = 5; public static final int DEFAULT_DOWNLOADER_CHAIN_SEGMENT_SIZE = 200; public static final int DEFAULT_DOWNLOADER_PARALLELISM = 4; - public static final int DEFAULT_TRANSACTIONS_PARALLELISM = 2; + public static final int DEFAULT_TRANSACTIONS_PARALLELISM = 4; public static final int DEFAULT_COMPUTATION_PARALLELISM = 2; public static final int DEFAULT_WORLD_STATE_TASK_CACHE_SIZE = CachingTaskCollection.DEFAULT_CACHE_SIZE; @@ -53,9 +53,9 @@ public class SynchronizerConfiguration { public static final boolean DEFAULT_CHECKPOINT_POST_MERGE_ENABLED = false; // Fast sync config - private final int fastSyncPivotDistance; + private final int syncPivotDistance; private final float fastSyncFullValidationRate; - private final int fastSyncMinimumPeerCount; + private final int syncMinimumPeerCount; private final int worldStateHashCountPerRequest; private final int worldStateRequestParallelism; private final int worldStateMaxRequestsWithoutProgress; @@ -77,7 +77,7 @@ public class SynchronizerConfiguration { private final long downloaderChangeTargetThresholdByHeight; private final UInt256 downloaderChangeTargetThresholdByTd; private final int downloaderHeaderRequestSize; - private final int downloaderCheckpointTimeoutsPermitted; + private final int downloaderCheckpointRetries; private final int downloaderChainSegmentSize; private final int downloaderParallelism; private final int transactionsParallelism; @@ -87,9 +87,9 @@ public class SynchronizerConfiguration { private final long propagationManagerGetBlockTimeoutMillis; private SynchronizerConfiguration( - final int fastSyncPivotDistance, + final int syncPivotDistance, final float fastSyncFullValidationRate, - final int fastSyncMinimumPeerCount, + final int syncMinimumPeerCount, final int worldStateHashCountPerRequest, final int worldStateRequestParallelism, final int worldStateMaxRequestsWithoutProgress, @@ -101,7 +101,7 @@ private SynchronizerConfiguration( final long downloaderChangeTargetThresholdByHeight, final UInt256 downloaderChangeTargetThresholdByTd, final int downloaderHeaderRequestSize, - final int downloaderCheckpointTimeoutsPermitted, + final int downloaderCheckpointRetries, final int downloaderChainSegmentSize, final int downloaderParallelism, final int transactionsParallelism, @@ -109,9 +109,9 @@ private SynchronizerConfiguration( final int maxTrailingPeers, final long propagationManagerGetBlockTimeoutMillis, final boolean checkpointPostMergeEnabled) { - this.fastSyncPivotDistance = fastSyncPivotDistance; + this.syncPivotDistance = syncPivotDistance; this.fastSyncFullValidationRate = fastSyncFullValidationRate; - this.fastSyncMinimumPeerCount = fastSyncMinimumPeerCount; + this.syncMinimumPeerCount = syncMinimumPeerCount; this.worldStateHashCountPerRequest = worldStateHashCountPerRequest; this.worldStateRequestParallelism = worldStateRequestParallelism; this.worldStateMaxRequestsWithoutProgress = worldStateMaxRequestsWithoutProgress; @@ -123,7 +123,7 @@ private SynchronizerConfiguration( this.downloaderChangeTargetThresholdByHeight = downloaderChangeTargetThresholdByHeight; this.downloaderChangeTargetThresholdByTd = downloaderChangeTargetThresholdByTd; this.downloaderHeaderRequestSize = downloaderHeaderRequestSize; - this.downloaderCheckpointTimeoutsPermitted = downloaderCheckpointTimeoutsPermitted; + this.downloaderCheckpointRetries = downloaderCheckpointRetries; this.downloaderChainSegmentSize = downloaderChainSegmentSize; this.downloaderParallelism = downloaderParallelism; this.transactionsParallelism = transactionsParallelism; @@ -171,12 +171,14 @@ public Range getBlockPropagationRange() { } /** - * The distance from the chain head at which we should switch from fast sync to full sync. + * The distance from the chain head at which we should switch from fast, snap, or checkpoint sync + * to full sync. * - * @return distance from the chain head at which we should switch from fast sync to full sync. + * @return distance from the chain head at which we should switch from fast, snap or checkpoint + * sync to full sync. */ - public int getFastSyncPivotDistance() { - return fastSyncPivotDistance; + public int getSyncPivotDistance() { + return syncPivotDistance; } public long getDownloaderChangeTargetThresholdByHeight() { @@ -191,8 +193,8 @@ public int getDownloaderHeaderRequestSize() { return downloaderHeaderRequestSize; } - public int getDownloaderCheckpointTimeoutsPermitted() { - return downloaderCheckpointTimeoutsPermitted; + public int getDownloaderCheckpointRetries() { + return downloaderCheckpointRetries; } public int getDownloaderChainSegmentSize() { @@ -222,8 +224,8 @@ public float getFastSyncFullValidationRate() { return fastSyncFullValidationRate; } - public int getFastSyncMinimumPeerCount() { - return fastSyncMinimumPeerCount; + public int getSyncMinimumPeerCount() { + return syncMinimumPeerCount; } public int getWorldStateHashCountPerRequest() { @@ -256,7 +258,7 @@ public long getPropagationManagerGetBlockTimeoutMillis() { public static class Builder { private SyncMode syncMode = SyncMode.FULL; - private int fastSyncMinimumPeerCount = DEFAULT_FAST_SYNC_MINIMUM_PEERS; + private int syncMinimumPeerCount = DEFAULT_SYNC_MINIMUM_PEERS; private int maxTrailingPeers = Integer.MAX_VALUE; private Range blockPropagationRange = DEFAULT_BLOCK_PROPAGATION_RANGE; private long downloaderChangeTargetThresholdByHeight = @@ -264,14 +266,13 @@ public static class Builder { private UInt256 downloaderChangeTargetThresholdByTd = DEFAULT_DOWNLOADER_CHANGE_TARGET_THRESHOLD_BY_TD; private int downloaderHeaderRequestSize = DEFAULT_DOWNLOADER_HEADER_REQUEST_SIZE; - private int downloaderCheckpointTimeoutsPermitted = - DEFAULT_DOWNLOADER_CHECKPOINT_TIMEOUTS_PERMITTED; + private int downloaderCheckpointRetries = DEFAULT_DOWNLOADER_CHECKPOINT_TIMEOUTS_PERMITTED; private SnapSyncConfiguration snapSyncConfiguration = SnapSyncConfiguration.getDefault(); private int downloaderChainSegmentSize = DEFAULT_DOWNLOADER_CHAIN_SEGMENT_SIZE; private int downloaderParallelism = DEFAULT_DOWNLOADER_PARALLELISM; private int transactionsParallelism = DEFAULT_TRANSACTIONS_PARALLELISM; private int computationParallelism = DEFAULT_COMPUTATION_PARALLELISM; - private int fastSyncPivotDistance = DEFAULT_PIVOT_DISTANCE_FROM_HEAD; + private int syncPivotDistance = DEFAULT_PIVOT_DISTANCE_FROM_HEAD; private float fastSyncFullValidationRate = DEFAULT_FULL_VALIDATION_RATE; private int worldStateHashCountPerRequest = DEFAULT_WORLD_STATE_HASH_COUNT_PER_REQUEST; private int worldStateRequestParallelism = DEFAULT_WORLD_STATE_REQUEST_PARALLELISM; @@ -284,8 +285,8 @@ public static class Builder { DEFAULT_PROPAGATION_MANAGER_GET_BLOCK_TIMEOUT_MILLIS; private boolean checkpointPostMergeEnabled = DEFAULT_CHECKPOINT_POST_MERGE_ENABLED; - public Builder fastSyncPivotDistance(final int distance) { - fastSyncPivotDistance = distance; + public Builder syncPivotDistance(final int distance) { + syncPivotDistance = distance; return this; } @@ -327,9 +328,8 @@ public Builder downloaderHeadersRequestSize(final int downloaderHeaderRequestSiz return this; } - public Builder downloaderCheckpointTimeoutsPermitted( - final int downloaderCheckpointTimeoutsPermitted) { - this.downloaderCheckpointTimeoutsPermitted = downloaderCheckpointTimeoutsPermitted; + public Builder downloaderCheckpointRetries(final int downloaderCheckpointRetries) { + this.downloaderCheckpointRetries = downloaderCheckpointRetries; return this; } @@ -359,8 +359,8 @@ public Builder computationParallelism(final int computationParallelism) { return this; } - public Builder fastSyncMinimumPeerCount(final int fastSyncMinimumPeerCount) { - this.fastSyncMinimumPeerCount = fastSyncMinimumPeerCount; + public Builder syncMinimumPeerCount(final int syncMinimumPeerCount) { + this.syncMinimumPeerCount = syncMinimumPeerCount; return this; } @@ -408,9 +408,9 @@ public Builder checkpointPostMergeEnabled(final boolean checkpointPostMergeEnabl public SynchronizerConfiguration build() { return new SynchronizerConfiguration( - fastSyncPivotDistance, + syncPivotDistance, fastSyncFullValidationRate, - fastSyncMinimumPeerCount, + syncMinimumPeerCount, worldStateHashCountPerRequest, worldStateRequestParallelism, worldStateMaxRequestsWithoutProgress, @@ -422,7 +422,7 @@ public SynchronizerConfiguration build() { downloaderChangeTargetThresholdByHeight, downloaderChangeTargetThresholdByTd, downloaderHeaderRequestSize, - downloaderCheckpointTimeoutsPermitted, + downloaderCheckpointRetries, downloaderChainSegmentSize, downloaderParallelism, transactionsParallelism, diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/TrailingPeerLimiter.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/TrailingPeerLimiter.java index 3804f1507ba..f5705e06731 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/TrailingPeerLimiter.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/TrailingPeerLimiter.java @@ -64,8 +64,18 @@ public void enforceTrailingPeerLimit() { while (!trailingPeers.isEmpty() && trailingPeers.size() > maxTrailingPeers) { final EthPeer peerToDisconnect = trailingPeers.remove(0); - LOG.debug("Enforcing trailing peers limit by disconnecting {}", peerToDisconnect); - peerToDisconnect.disconnect(DisconnectReason.USELESS_PEER); + LOG.atDebug() + .setMessage( + "Enforcing trailing peers limit (min height {}, max trailing peers {}) by disconnecting {}... with height {}") + .addArgument(minimumHeightToBeUpToDate) + .addArgument(maxTrailingPeers) + .addArgument(peerToDisconnect::getLoggableId) + .addArgument( + peerToDisconnect.chainState() == null + ? "(no chain state)" + : peerToDisconnect.chainState().getEstimatedHeight()) + .log(); + peerToDisconnect.disconnect(DisconnectReason.USELESS_PEER_TRAILING_PEER); } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardChain.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardChain.java index ee4718b0613..713cb7179e7 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardChain.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardChain.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.eth.sync.backwardsync; import static org.slf4j.LoggerFactory.getLogger; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncAlgorithm.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncAlgorithm.java index 731291dc1ea..506b360730e 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncAlgorithm.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncAlgorithm.java @@ -1,6 +1,5 @@ /* - * - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.eth.sync.backwardsync; @@ -22,11 +20,13 @@ import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.eth.manager.exceptions.MaxRetriesReachedException; import org.hyperledger.besu.ethereum.eth.manager.task.WaitForPeersTask; import org.hyperledger.besu.plugin.services.BesuEvents; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -61,16 +61,7 @@ public CompletableFuture executeBackwardsSync(final Void unused) { public CompletableFuture pickNextStep() { final Optional firstHash = context.getBackwardChain().getFirstHashToAppend(); if (firstHash.isPresent()) { - return executeSyncStep(firstHash.get()) - .thenAccept( - result -> { - LOG.atDebug() - .setMessage("Backward sync target block is {}") - .addArgument(result::toLogString) - .log(); - context.getBackwardChain().removeFromHashToAppend(firstHash.get()); - context.getStatus().updateTargetHeight(result.getHeader().getNumber()); - }); + return handleSyncStep(firstHash.get()); } if (!context.isReady()) { return waitForReady(); @@ -111,6 +102,59 @@ public CompletableFuture pickNextStep() { return executeBackwardAsync(firstAncestorHeader); } + private CompletableFuture handleSyncStep(final Hash firstHash) { + final CompletableFuture syncStep = new CompletableFuture<>(); + executeSyncStep(firstHash) + .whenComplete( + (result, error) -> { + if (error != null) { + handleSyncStepError(error, firstHash, syncStep); + } else { + handleSyncStepSuccess(result, firstHash, syncStep); + } + }); + return syncStep; + } + + private void handleSyncStepSuccess( + final Block result, final Hash firstHash, final CompletableFuture syncStep) { + if (result == null) { + LOG.atWarn().setMessage("Unexpected null result in for hash {}").addArgument(firstHash).log(); + syncStep.completeExceptionally(new BackwardSyncException("Unexpected null result", true)); + } else { + LOG.atDebug() + .setMessage("Backward sync target block is {}") + .addArgument(result::toLogString) + .log(); + context.getBackwardChain().removeFromHashToAppend(firstHash); + context.getStatus().updateTargetHeight(result.getHeader().getNumber()); + syncStep.complete(null); + } + } + + private void handleSyncStepError( + final Throwable error, final Hash firstHash, final CompletableFuture syncStep) { + if (error instanceof CompletionException + && error.getCause() instanceof MaxRetriesReachedException) { + handleEthPeerMaxRetriesException(firstHash); + syncStep.complete(null); + } else { + syncStep.completeExceptionally(error); + } + } + + private void handleEthPeerMaxRetriesException(final Hash firstHash) { + context.getBackwardChain().removeFromHashToAppend(firstHash); + LOG.atWarn() + .setMessage( + "Unable to retrieve block {} from any peer, with {} peers available. Could be a reorged block. Waiting for the next block from the consensus client to try again.") + .addArgument(firstHash) + .addArgument(context.getEthContext().getEthPeers().peerCount()) + .addArgument(context.getBackwardChain().getFirstHashToAppend()) + .log(); + LOG.atDebug().setMessage("Removing hash {} from hashesToAppend").addArgument(firstHash).log(); + } + @VisibleForTesting public CompletableFuture executeProcessKnownAncestors() { return new ProcessKnownAncestorsStep(context, context.getBackwardChain()).executeAsync(); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncContext.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncContext.java index 6a681bec288..bef62ec927e 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncContext.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncContext.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -44,7 +44,7 @@ public class BackwardSyncContext { private static final Logger LOG = LoggerFactory.getLogger(BackwardSyncContext.class); public static final int BATCH_SIZE = 200; - private static final int DEFAULT_MAX_RETRIES = 20; + private static final int DEFAULT_MAX_RETRIES = 2; private static final long MILLIS_DELAY_BETWEEN_PROGRESS_LOG = 10_000L; private static final long DEFAULT_MILLIS_BETWEEN_RETRIES = 5000; private static final int DEFAULT_MAX_CHAIN_EVENT_ENTRIES = BadBlockManager.MAX_BAD_BLOCKS_SIZE; @@ -125,16 +125,24 @@ public synchronized void maybeUpdateTargetHeight(final Hash headHash) { } public synchronized CompletableFuture syncBackwardsUntil(final Hash newBlockHash) { - if (!isTrusted(newBlockHash)) { - backwardChain.addNewHash(newBlockHash); - } + if (isReady()) { + if (!isTrusted(newBlockHash)) { + LOG.atDebug() + .setMessage("Appending new head block hash {} to backward sync") + .addArgument(newBlockHash::toHexString) + .log(); + backwardChain.addNewHash(newBlockHash); + } - final Status status = getOrStartSyncSession(); - backwardChain - .getBlock(newBlockHash) - .ifPresent( - newTargetBlock -> status.updateTargetHeight(newTargetBlock.getHeader().getNumber())); - return status.currentFuture; + final Status status = getOrStartSyncSession(); + backwardChain + .getBlock(newBlockHash) + .ifPresent( + newTargetBlock -> status.updateTargetHeight(newTargetBlock.getHeader().getNumber())); + return status.currentFuture; + } else { + return CompletableFuture.failedFuture(new Throwable("Backward sync is not ready")); + } } public synchronized CompletableFuture syncBackwardsUntil(final Block newPivot) { @@ -142,9 +150,13 @@ public synchronized CompletableFuture syncBackwardsUntil(final Block newPi backwardChain.appendTrustedBlock(newPivot); } - final Status status = getOrStartSyncSession(); - status.updateTargetHeight(newPivot.getHeader().getNumber()); - return status.currentFuture; + if (isReady()) { + final Status status = getOrStartSyncSession(); + status.updateTargetHeight(newPivot.getHeader().getNumber()); + return status.currentFuture; + } else { + return CompletableFuture.failedFuture(new Throwable("Backward sync is not ready")); + } } private Status getOrStartSyncSession() { @@ -316,10 +328,9 @@ protected Void saveBlock(final Block block) { HeaderValidationMode.NONE); if (optResult.isSuccessful()) { LOG.atTrace() - .setMessage("Block {} was validated, going to import it") + .setMessage("Block {} was validated, going to move the head") .addArgument(block::toLogString) .log(); - optResult.getYield().get().getWorldState().persist(block.getHeader()); this.getProtocolContext() .getBlockchain() .appendBlock(block, optResult.getYield().get().getReceipts()); @@ -401,7 +412,7 @@ private void logBlockImportProgress(final long currImportedHeight) { final float completedPercentage = 100.0f * imported / estimatedTotal; if (completedPercentage < 100.0f) { - if (currentStatus.progressLogDue()) { + if (currentStatus.progressLogDue() && targetHeight > 0) { LOG.info( String.format( "Backward sync phase 2 of 2, %.2f%% completed, imported %d blocks of at least %d (current head %d, target head %d). Peers: %d", diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncException.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncException.java index 9464a911ede..413c01b25ce 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncException.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncException.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncStep.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncStep.java index 6f9a5822de1..748c71fc6f6 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncStep.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncStep.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -61,7 +61,8 @@ protected CompletableFuture> requestHeaders(final Hash hash) { context.getProtocolContext().getBlockchain().getBlockHeader(hash); if (blockHeader.isPresent()) { LOG.debug( - "Hash {} already present in local blockchain no need to request headers to peers", hash); + "Hash {} already present in local blockchain no need to request headers from peers", + hash); return CompletableFuture.completedFuture(List.of(blockHeader.get())); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BadChainListener.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BadChainListener.java index 1e88f6d659c..e766e043f17 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BadChainListener.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BadChainListener.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BlocksConvertor.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BlocksConvertor.java index dc17914345d..3a186ee91c1 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BlocksConvertor.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BlocksConvertor.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.eth.sync.backwardsync; import org.hyperledger.besu.ethereum.core.Block; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BlocksHeadersConvertor.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BlocksHeadersConvertor.java index 0655ec0a938..23ed93b591c 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BlocksHeadersConvertor.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BlocksHeadersConvertor.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.eth.sync.backwardsync; import org.hyperledger.besu.ethereum.core.BlockHeader; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/FinalBlockConfirmation.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/FinalBlockConfirmation.java index 078220c535e..2ad136ac831 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/FinalBlockConfirmation.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/FinalBlockConfirmation.java @@ -1,6 +1,5 @@ /* - * - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.eth.sync.backwardsync; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ForwardSyncStep.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ForwardSyncStep.java index c71a97a5f49..c1ce88398e4 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ForwardSyncStep.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ForwardSyncStep.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/GenericKeyValueStorageFacade.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/GenericKeyValueStorageFacade.java index c6c929d0d26..af8fb183052 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/GenericKeyValueStorageFacade.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/GenericKeyValueStorageFacade.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.eth.sync.backwardsync; import org.hyperledger.besu.plugin.services.exception.StorageException; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/HashConvertor.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/HashConvertor.java index 98fe808894b..bedfbc0ad4f 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/HashConvertor.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/HashConvertor.java @@ -1,6 +1,5 @@ /* - * - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.eth.sync.backwardsync; import org.hyperledger.besu.datatypes.Hash; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/KeyConvertor.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/KeyConvertor.java index bf7ed0c482f..e880b0c0183 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/KeyConvertor.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/KeyConvertor.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.eth.sync.backwardsync; public interface KeyConvertor { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ProcessKnownAncestorsStep.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ProcessKnownAncestorsStep.java index 77fb01524ba..9878957a326 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ProcessKnownAncestorsStep.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ProcessKnownAncestorsStep.java @@ -1,6 +1,5 @@ /* - * - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.eth.sync.backwardsync; import static org.slf4j.LoggerFactory.getLogger; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/SyncStepStep.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/SyncStepStep.java index 78586030516..1f7bc8979b9 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/SyncStepStep.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/SyncStepStep.java @@ -1,6 +1,5 @@ /* - * - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.eth.sync.backwardsync; import static org.slf4j.LoggerFactory.getLogger; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ValueConvertor.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ValueConvertor.java index a6054e14530..11a5d3b5323 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ValueConvertor.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ValueConvertor.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.eth.sync.backwardsync; public interface ValueConvertor { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointBlockImportStep.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointBlockImportStep.java index de5076900cd..2ebbe678973 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointBlockImportStep.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointBlockImportStep.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointDownloadBlockStep.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointDownloadBlockStep.java index 8fbe8127b26..b4bdf585410 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointDownloadBlockStep.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointDownloadBlockStep.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointDownloaderFactory.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointDownloaderFactory.java index ff8187d8d21..03df47e4407 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointDownloaderFactory.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointDownloaderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -35,7 +35,8 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions; import org.hyperledger.besu.ethereum.trie.CompactEncoding; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.services.tasks.InMemoryTasksPriorityQueues; @@ -60,9 +61,10 @@ public static Optional> createCheckpointDownloader( final ProtocolContext protocolContext, final MetricsSystem metricsSystem, final EthContext ethContext, - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final SyncState syncState, - final Clock clock) { + final Clock clock, + final SyncDurationMetrics syncDurationMetrics) { final Path fastSyncDataDirectory = dataDirectory.resolve(FAST_SYNC_FOLDER); final FastSyncStateStorage fastSyncStateStorage = @@ -104,7 +106,7 @@ public static Optional> createCheckpointDownloader( fastSyncActions = new FastSyncActions( syncConfig, - worldStateStorage, + worldStateStorageCoordinator, protocolSchedule, protocolContext, ethContext, @@ -121,7 +123,7 @@ public static Optional> createCheckpointDownloader( fastSyncActions = new CheckpointSyncActions( syncConfig, - worldStateStorage, + worldStateStorageCoordinator, protocolSchedule, protocolContext, ethContext, @@ -142,23 +144,25 @@ public static Optional> createCheckpointDownloader( ethContext, snapContext, protocolContext, - worldStateStorage, + worldStateStorageCoordinator, snapTaskCollection, syncConfig.getSnapSyncConfiguration(), syncConfig.getWorldStateRequestParallelism(), syncConfig.getWorldStateMaxRequestsWithoutProgress(), syncConfig.getWorldStateMinMillisBeforeStalling(), clock, - metricsSystem); + metricsSystem, + syncDurationMetrics); final FastSyncDownloader fastSyncDownloader = new SnapSyncDownloader( fastSyncActions, - worldStateStorage, + worldStateStorageCoordinator, snapWorldStateDownloader, fastSyncStateStorage, snapTaskCollection, fastSyncDataDirectory, - snapSyncState); + snapSyncState, + syncDurationMetrics); syncState.setWorldStateDownloadStatus(snapWorldStateDownloader); return Optional.of(fastSyncDownloader); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointSource.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointSource.java index 2f3fa2f4306..4df5e39958b 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointSource.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointSource.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointSyncActions.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointSyncActions.java index 8159ccdaa02..5096b74e24f 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointSyncActions.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointSyncActions.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -23,13 +23,14 @@ import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncState; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.plugin.services.MetricsSystem; public class CheckpointSyncActions extends FastSyncActions { public CheckpointSyncActions( final SynchronizerConfiguration syncConfig, - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final ProtocolSchedule protocolSchedule, final ProtocolContext protocolContext, final EthContext ethContext, @@ -38,7 +39,7 @@ public CheckpointSyncActions( final MetricsSystem metricsSystem) { super( syncConfig, - worldStateStorage, + worldStateStorageCoordinator, protocolSchedule, protocolContext, ethContext, @@ -48,15 +49,17 @@ public CheckpointSyncActions( } @Override - public ChainDownloader createChainDownloader(final FastSyncState currentState) { + public ChainDownloader createChainDownloader( + final FastSyncState currentState, final SyncDurationMetrics syncDurationMetrics) { return CheckpointSyncChainDownloader.create( syncConfig, - worldStateStorage, + worldStateStorageCoordinator, protocolSchedule, protocolContext, ethContext, syncState, metricsSystem, - currentState); + currentState, + syncDurationMetrics); } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointSyncChainDownloader.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointSyncChainDownloader.java index 854972c39fd..5450b9e5a49 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointSyncChainDownloader.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointSyncChainDownloader.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,8 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - */ package org.hyperledger.besu.ethereum.eth.sync.checkpointsync; + */ +package org.hyperledger.besu.ethereum.eth.sync.checkpointsync; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.eth.manager.EthContext; @@ -20,28 +21,30 @@ import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncChainDownloader; import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncState; -import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncTargetManager; +import org.hyperledger.besu.ethereum.eth.sync.fastsync.SyncTargetManager; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.plugin.services.MetricsSystem; public class CheckpointSyncChainDownloader extends FastSyncChainDownloader { public static ChainDownloader create( final SynchronizerConfiguration config, - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final ProtocolSchedule protocolSchedule, final ProtocolContext protocolContext, final EthContext ethContext, final SyncState syncState, final MetricsSystem metricsSystem, - final FastSyncState fastSyncState) { + final FastSyncState fastSyncState, + final SyncDurationMetrics syncDurationMetrics) { - final FastSyncTargetManager syncTargetManager = - new FastSyncTargetManager( + final SyncTargetManager syncTargetManager = + new SyncTargetManager( config, - worldStateStorage, + worldStateStorageCoordinator, protocolSchedule, protocolContext, ethContext, @@ -54,6 +57,7 @@ public static ChainDownloader create( new CheckpointSyncDownloadPipelineFactory( config, protocolSchedule, protocolContext, ethContext, fastSyncState, metricsSystem), ethContext.getScheduler(), - metricsSystem); + metricsSystem, + syncDurationMetrics); } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointSyncDownloadPipelineFactory.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointSyncDownloadPipelineFactory.java index 8c853717f5c..45f3f243d8c 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointSyncDownloadPipelineFactory.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckpointSyncDownloadPipelineFactory.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastImportBlocksStep.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastImportBlocksStep.java deleted file mode 100644 index c2297faa199..00000000000 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastImportBlocksStep.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.eth.sync.fastsync; - -import org.hyperledger.besu.ethereum.ProtocolContext; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.BlockImporter; -import org.hyperledger.besu.ethereum.core.BlockWithReceipts; -import org.hyperledger.besu.ethereum.eth.manager.EthContext; -import org.hyperledger.besu.ethereum.eth.sync.ValidationPolicy; -import org.hyperledger.besu.ethereum.eth.sync.tasks.exceptions.InvalidBlockException; -import org.hyperledger.besu.ethereum.mainnet.BlockImportResult; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; - -import java.util.List; -import java.util.OptionalLong; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; - -import com.google.common.annotations.VisibleForTesting; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class FastImportBlocksStep implements Consumer> { - private static final Logger LOG = LoggerFactory.getLogger(FastImportBlocksStep.class); - private static final long PRINT_DELAY = TimeUnit.SECONDS.toMillis(30L); - - private final ProtocolSchedule protocolSchedule; - protected final ProtocolContext protocolContext; - private final ValidationPolicy headerValidationPolicy; - private final ValidationPolicy ommerValidationPolicy; - private final EthContext ethContext; - private long accumulatedTime = 0L; - private OptionalLong logStartBlock = OptionalLong.empty(); - private final BlockHeader pivotHeader; - - public FastImportBlocksStep( - final ProtocolSchedule protocolSchedule, - final ProtocolContext protocolContext, - final ValidationPolicy headerValidationPolicy, - final ValidationPolicy ommerValidationPolicy, - final EthContext ethContext, - final BlockHeader pivotHeader) { - this.protocolSchedule = protocolSchedule; - this.protocolContext = protocolContext; - this.headerValidationPolicy = headerValidationPolicy; - this.ommerValidationPolicy = ommerValidationPolicy; - this.ethContext = ethContext; - this.pivotHeader = pivotHeader; - } - - @Override - public void accept(final List blocksWithReceipts) { - final long startTime = System.nanoTime(); - for (final BlockWithReceipts blockWithReceipts : blocksWithReceipts) { - if (!importBlock(blockWithReceipts)) { - throw new InvalidBlockException( - "Failed to import block", - blockWithReceipts.getHeader().getNumber(), - blockWithReceipts.getHash()); - } - LOG.atTrace() - .setMessage("Imported block {}") - .addArgument(blockWithReceipts.getBlock()::toLogString) - .log(); - } - if (logStartBlock.isEmpty()) { - logStartBlock = OptionalLong.of(blocksWithReceipts.get(0).getNumber()); - } - final long lastBlock = blocksWithReceipts.get(blocksWithReceipts.size() - 1).getNumber(); - int peerCount = -1; // ethContext is not available in tests - if (ethContext != null && ethContext.getEthPeers().peerCount() >= 0) { - peerCount = ethContext.getEthPeers().peerCount(); - } - final long endTime = System.nanoTime(); - - accumulatedTime += TimeUnit.MILLISECONDS.convert(endTime - startTime, TimeUnit.NANOSECONDS); - if (accumulatedTime > PRINT_DELAY) { - final long blocksPercent = getBlocksPercent(lastBlock, pivotHeader.getNumber()); - LOG.info( - "Block import progress: {} of {} ({}%)", - lastBlock, pivotHeader.getNumber(), blocksPercent); - LOG.debug( - "Completed importing chain segment {} to {} ({} blocks in {}ms), Peer count: {}", - logStartBlock.getAsLong(), - lastBlock, - lastBlock - logStartBlock.getAsLong() + 1, - accumulatedTime, - peerCount); - accumulatedTime = 0L; - logStartBlock = OptionalLong.empty(); - } - } - - @VisibleForTesting - protected static long getBlocksPercent(final long lastBlock, final long totalBlocks) { - if (totalBlocks == 0) { - return 0; - } - final long blocksPercent = (100 * lastBlock / totalBlocks); - return blocksPercent; - } - - protected boolean importBlock(final BlockWithReceipts blockWithReceipts) { - final BlockImporter importer = - protocolSchedule.getByBlockHeader(blockWithReceipts.getHeader()).getBlockImporter(); - final BlockImportResult blockImportResult = - importer.fastImportBlock( - protocolContext, - blockWithReceipts.getBlock(), - blockWithReceipts.getReceipts(), - headerValidationPolicy.getValidationModeForNextBlock(), - ommerValidationPolicy.getValidationModeForNextBlock()); - return blockImportResult.isImported(); - } -} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncActions.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncActions.java index d7d3523538b..7f6bbae3f31 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncActions.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncActions.java @@ -26,8 +26,9 @@ import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.eth.sync.tasks.RetryingGetHeaderFromPeerByHashTask; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.metrics.BesuMetricCategory; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.plugin.services.metrics.Counter; @@ -43,7 +44,7 @@ public class FastSyncActions { private static final Logger LOG = LoggerFactory.getLogger(FastSyncActions.class); protected final SynchronizerConfiguration syncConfig; - protected final WorldStateStorage worldStateStorage; + protected final WorldStateStorageCoordinator worldStateStorageCoordinator; protected final ProtocolSchedule protocolSchedule; protected final ProtocolContext protocolContext; protected final EthContext ethContext; @@ -55,7 +56,7 @@ public class FastSyncActions { public FastSyncActions( final SynchronizerConfiguration syncConfig, - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final ProtocolSchedule protocolSchedule, final ProtocolContext protocolContext, final EthContext ethContext, @@ -63,7 +64,7 @@ public FastSyncActions( final PivotBlockSelector pivotBlockSelector, final MetricsSystem metricsSystem) { this.syncConfig = syncConfig; - this.worldStateStorage = worldStateStorage; + this.worldStateStorageCoordinator = worldStateStorageCoordinator; this.protocolSchedule = protocolSchedule; this.protocolContext = protocolContext; this.ethContext = ethContext; @@ -142,8 +143,8 @@ private CompletableFuture internalDownloadPivotBlockHeader( ethContext, metricsSystem, currentState.getPivotBlockNumber().getAsLong(), - syncConfig.getFastSyncMinimumPeerCount(), - syncConfig.getFastSyncPivotDistance()) + syncConfig.getSyncMinimumPeerCount(), + syncConfig.getSyncPivotDistance()) .downloadPivotBlockHeader())); } @@ -155,16 +156,18 @@ private FastSyncState updateStats(final FastSyncState fastSyncState) { return fastSyncState; } - public ChainDownloader createChainDownloader(final FastSyncState currentState) { + public ChainDownloader createChainDownloader( + final FastSyncState currentState, final SyncDurationMetrics syncDurationMetrics) { return FastSyncChainDownloader.create( syncConfig, - worldStateStorage, + worldStateStorageCoordinator, protocolSchedule, protocolContext, ethContext, syncState, metricsSystem, - currentState); + currentState, + syncDurationMetrics); } private CompletableFuture downloadPivotBlockHeader(final Hash hash) { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncChainDownloader.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncChainDownloader.java index c4034cb87a0..c36ff7cb482 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncChainDownloader.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncChainDownloader.java @@ -21,7 +21,8 @@ import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.plugin.services.MetricsSystem; public class FastSyncChainDownloader { @@ -30,18 +31,19 @@ protected FastSyncChainDownloader() {} public static ChainDownloader create( final SynchronizerConfiguration config, - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final ProtocolSchedule protocolSchedule, final ProtocolContext protocolContext, final EthContext ethContext, final SyncState syncState, final MetricsSystem metricsSystem, - final FastSyncState fastSyncState) { + final FastSyncState fastSyncState, + final SyncDurationMetrics syncDurationMetrics) { - final FastSyncTargetManager syncTargetManager = - new FastSyncTargetManager( + final SyncTargetManager syncTargetManager = + new SyncTargetManager( config, - worldStateStorage, + worldStateStorageCoordinator, protocolSchedule, protocolContext, ethContext, @@ -53,6 +55,7 @@ public static ChainDownloader create( new FastSyncDownloadPipelineFactory( config, protocolSchedule, protocolContext, ethContext, fastSyncState, metricsSystem), ethContext.getScheduler(), - metricsSystem); + metricsSystem, + syncDurationMetrics); } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncDownloadPipelineFactory.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncDownloadPipelineFactory.java index 206b76c09a5..07e964426cc 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncDownloadPipelineFactory.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncDownloadPipelineFactory.java @@ -47,7 +47,12 @@ import java.util.concurrent.CompletionStage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class FastSyncDownloadPipelineFactory implements DownloadPipelineFactory { + private static final Logger LOG = LoggerFactory.getLogger(FastSyncDownloadPipelineFactory.class); + protected final SynchronizerConfiguration syncConfig; protected final ProtocolSchedule protocolSchedule; protected final ProtocolContext protocolContext; @@ -120,7 +125,7 @@ public Pipeline createDownloadPipelineForSyncTarget(final SyncT ethContext.getScheduler(), target.peer(), getCommonAncestor(target), - syncConfig.getDownloaderCheckpointTimeoutsPermitted(), + syncConfig.getDownloaderCheckpointRetries(), SyncTerminationCondition.never()); final DownloadHeadersStep downloadHeadersStep = new DownloadHeadersStep( @@ -136,8 +141,8 @@ public Pipeline createDownloadPipelineForSyncTarget(final SyncT new DownloadBodiesStep(protocolSchedule, ethContext, metricsSystem); final DownloadReceiptsStep downloadReceiptsStep = new DownloadReceiptsStep(ethContext, metricsSystem); - final FastImportBlocksStep importBlockStep = - new FastImportBlocksStep( + final ImportBlocksStep importBlockStep = + new ImportBlocksStep( protocolSchedule, protocolContext, attachedValidationPolicy, @@ -172,6 +177,18 @@ protected BlockHeader getCommonAncestor(final SyncTarget syncTarget) { protected boolean shouldContinueDownloadingFromPeer( final EthPeer peer, final BlockHeader lastRoundHeader) { final BlockHeader pivotBlockHeader = fastSyncState.getPivotBlockHeader().get(); - return !peer.isDisconnected() && lastRoundHeader.getNumber() < pivotBlockHeader.getNumber(); + final boolean shouldContinue = + !peer.isDisconnected() && lastRoundHeader.getNumber() < pivotBlockHeader.getNumber(); + + if (!shouldContinue && peer.isDisconnected()) { + LOG.debug("Stopping chain download due to disconnected peer {}", peer); + } else if (!shouldContinue && lastRoundHeader.getNumber() >= pivotBlockHeader.getNumber()) { + LOG.debug( + "Stopping chain download as lastRoundHeader={} is not less than pivotBlockHeader={} for peer {}", + lastRoundHeader.getNumber(), + pivotBlockHeader.getNumber(), + peer); + } + return shouldContinue; } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncDownloader.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncDownloader.java index b73daa28301..0aaeefc6d6e 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncDownloader.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncDownloader.java @@ -1,5 +1,5 @@ /* - * Copyright ConsenSys AG. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -21,8 +21,10 @@ import org.hyperledger.besu.ethereum.eth.sync.TrailingPeerRequirements; import org.hyperledger.besu.ethereum.eth.sync.worldstate.StalledDownloadException; import org.hyperledger.besu.ethereum.eth.sync.worldstate.WorldStateDownloader; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.metrics.SyncDurationMetrics; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.services.tasks.TaskCollection; import org.hyperledger.besu.util.ExceptionUtils; @@ -44,11 +46,14 @@ public class FastSyncDownloader { private static final Duration FAST_SYNC_RETRY_DELAY = Duration.ofSeconds(5); - private static final Logger LOG = LoggerFactory.getLogger(FastSyncDownloader.class); - private final WorldStateStorage worldStateStorage; + @SuppressWarnings("PrivateStaticFinalLoggers") + protected final Logger LOG = LoggerFactory.getLogger(getClass()); + + private final WorldStateStorageCoordinator worldStateStorageCoordinator; private final WorldStateDownloader worldStateDownloader; private final TaskCollection taskCollection; private final Path fastSyncDataDirectory; + private final SyncDurationMetrics syncDurationMetrics; private volatile Optional trailingPeerRequirements = Optional.empty(); private final AtomicBoolean running = new AtomicBoolean(false); @@ -58,36 +63,43 @@ public class FastSyncDownloader { public FastSyncDownloader( final FastSyncActions fastSyncActions, - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final WorldStateDownloader worldStateDownloader, final FastSyncStateStorage fastSyncStateStorage, final TaskCollection taskCollection, final Path fastSyncDataDirectory, - final FastSyncState initialFastSyncState) { + final FastSyncState initialFastSyncState, + final SyncDurationMetrics syncDurationMetrics) { this.fastSyncActions = fastSyncActions; - this.worldStateStorage = worldStateStorage; + this.worldStateStorageCoordinator = worldStateStorageCoordinator; this.worldStateDownloader = worldStateDownloader; this.fastSyncStateStorage = fastSyncStateStorage; this.taskCollection = taskCollection; this.fastSyncDataDirectory = fastSyncDataDirectory; this.initialFastSyncState = initialFastSyncState; + this.syncDurationMetrics = syncDurationMetrics; } public CompletableFuture start() { if (!running.compareAndSet(false, true)) { - throw new IllegalStateException("FastSyncDownloader already running"); + throw new IllegalStateException("SyncDownloader already running"); } - LOG.info("Starting sync"); + LOG.info("Starting pivot-based sync"); + return start(initialFastSyncState); } protected CompletableFuture start(final FastSyncState fastSyncState) { - if (worldStateStorage.getDataStorageFormat().equals(DataStorageFormat.BONSAI)) { - LOG.info("Clearing bonsai flat account db"); - worldStateStorage.clearFlatDatabase(); - worldStateStorage.clearTrieLog(); - } - LOG.debug("Start sync with initial sync state {}", fastSyncState); + worldStateStorageCoordinator.applyOnMatchingStrategy( + DataStorageFormat.BONSAI, + worldStateKeyValueStorage -> { + BonsaiWorldStateKeyValueStorage onBonsai = + (BonsaiWorldStateKeyValueStorage) worldStateKeyValueStorage; + LOG.info("Clearing bonsai flat account db"); + onBonsai.clearFlatDatabase(); + onBonsai.clearTrieLog(); + }); + LOG.debug("Start fast sync with initial sync state {}", fastSyncState); return findPivotBlock(fastSyncState, fss -> downloadChainAndWorldState(fastSyncActions, fss)); } @@ -107,7 +119,9 @@ public CompletableFuture findPivotBlock( protected CompletableFuture handleFailure(final Throwable error) { trailingPeerRequirements = Optional.empty(); Throwable rootCause = ExceptionUtils.rootCause(error); - if (rootCause instanceof FastSyncException) { + if (rootCause instanceof NoSyncRequiredException) { + return CompletableFuture.completedFuture(new NoSyncRequiredState()); + } else if (rootCause instanceof SyncException) { return CompletableFuture.failedFuture(error); } else if (rootCause instanceof StalledDownloadException) { LOG.debug("Stalled sync re-pivoting to newer block."); @@ -120,7 +134,7 @@ protected CompletableFuture handleFailure(final Throwable error) return start(FastSyncState.EMPTY_SYNC_STATE); } else { LOG.error( - "Encountered an unexpected error during fast sync. Restarting sync in " + "Encountered an unexpected error during sync. Restarting sync in " + FAST_SYNC_RETRY_DELAY.getSeconds() + " seconds.", error); @@ -132,7 +146,7 @@ protected CompletableFuture handleFailure(final Throwable error) public void stop() { synchronized (this) { if (running.compareAndSet(true, false)) { - LOG.info("Stopping fast sync"); + LOG.info("Stopping sync"); // Cancelling the world state download will also cause the chain download to be cancelled. worldStateDownloader.cancel(); } @@ -149,7 +163,7 @@ public void deleteFastSyncState() { MoreFiles.deleteRecursively(fastSyncDataDirectory, RecursiveDeleteOption.ALLOW_INSECURE); } } catch (final IOException e) { - LOG.error("Unable to clean up fast sync state", e); + LOG.error("Unable to clean up sync state", e); } } @@ -180,7 +194,8 @@ protected CompletableFuture downloadChainAndWorldState( } final CompletableFuture worldStateFuture = worldStateDownloader.run(fastSyncActions, currentState); - final ChainDownloader chainDownloader = fastSyncActions.createChainDownloader(currentState); + final ChainDownloader chainDownloader = + fastSyncActions.createChainDownloader(currentState, syncDurationMetrics); final CompletableFuture chainFuture = chainDownloader.start(); // If either download fails, cancel the other one. diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncError.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncError.java deleted file mode 100644 index d8361733574..00000000000 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncError.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.eth.sync.fastsync; - -public enum FastSyncError { - NO_PEERS_AVAILABLE, - PIVOT_BLOCK_HEADER_MISMATCH, - UNEXPECTED_ERROR -} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncException.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncException.java deleted file mode 100644 index 79baf02c43c..00000000000 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncException.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.eth.sync.fastsync; - -public class FastSyncException extends RuntimeException { - - private final FastSyncError error; - - public FastSyncException(final FastSyncError error) { - super("Fast sync failed: " + error); - this.error = error; - } - - public FastSyncError getError() { - return error; - } - - public FastSyncException(final Throwable error) { - super(error); - this.error = FastSyncError.UNEXPECTED_ERROR; - } -} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncTargetManager.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncTargetManager.java deleted file mode 100644 index 11dc8c1af61..00000000000 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncTargetManager.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.eth.sync.fastsync; - -import static java.util.concurrent.CompletableFuture.completedFuture; -import static org.hyperledger.besu.ethereum.eth.sync.fastsync.PivotBlockRetriever.MAX_QUERY_RETRIES_PER_PEER; -import static org.hyperledger.besu.ethereum.util.LogUtil.throttledLog; - -import org.hyperledger.besu.ethereum.ProtocolContext; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.eth.manager.EthContext; -import org.hyperledger.besu.ethereum.eth.manager.EthPeer; -import org.hyperledger.besu.ethereum.eth.manager.EthPeers; -import org.hyperledger.besu.ethereum.eth.sync.SyncTargetManager; -import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; -import org.hyperledger.besu.ethereum.eth.sync.tasks.RetryingGetHeaderFromPeerByNumberTask; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; -import org.hyperledger.besu.plugin.services.MetricsSystem; - -import java.util.List; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class FastSyncTargetManager extends SyncTargetManager { - private static final Logger LOG = LoggerFactory.getLogger(FastSyncTargetManager.class); - - private final WorldStateStorage worldStateStorage; - private final ProtocolSchedule protocolSchedule; - private final ProtocolContext protocolContext; - private final EthContext ethContext; - private final MetricsSystem metricsSystem; - private final FastSyncState fastSyncState; - private final AtomicBoolean logDebug = new AtomicBoolean(true); - private final AtomicBoolean logInfo = new AtomicBoolean(true); - private final int logDebugRepeatDelay = 15; - private final int logInfoRepeatDelay = 120; - - public FastSyncTargetManager( - final SynchronizerConfiguration config, - final WorldStateStorage worldStateStorage, - final ProtocolSchedule protocolSchedule, - final ProtocolContext protocolContext, - final EthContext ethContext, - final MetricsSystem metricsSystem, - final FastSyncState fastSyncState) { - super(config, protocolSchedule, protocolContext, ethContext, metricsSystem); - this.worldStateStorage = worldStateStorage; - this.protocolSchedule = protocolSchedule; - this.protocolContext = protocolContext; - this.ethContext = ethContext; - this.metricsSystem = metricsSystem; - this.fastSyncState = fastSyncState; - } - - @Override - protected CompletableFuture> selectBestAvailableSyncTarget() { - final BlockHeader pivotBlockHeader = fastSyncState.getPivotBlockHeader().get(); - final EthPeers ethPeers = ethContext.getEthPeers(); - final Optional maybeBestPeer = ethPeers.bestPeerWithHeightEstimate(); - if (maybeBestPeer.isEmpty()) { - throttledLog( - LOG::debug, - String.format( - "Unable to find sync target. Currently checking %d peers for usefulness. Pivot block: %d", - ethContext.getEthPeers().peerCount(), pivotBlockHeader.getNumber()), - logDebug, - logDebugRepeatDelay); - throttledLog( - LOG::info, - String.format( - "Unable to find sync target. Currently checking %d peers for usefulness.", - ethContext.getEthPeers().peerCount()), - logInfo, - logInfoRepeatDelay); - return completedFuture(Optional.empty()); - } else { - final EthPeer bestPeer = maybeBestPeer.get(); - if (bestPeer.chainState().getEstimatedHeight() < pivotBlockHeader.getNumber()) { - LOG.info( - "Best peer {} has chain height {} below pivotBlock height {}. Waiting for better peers. Current {} of max {}", - maybeBestPeer.map(EthPeer::getShortNodeId).orElse("none"), - maybeBestPeer.map(p -> p.chainState().getEstimatedHeight()).orElse(-1L), - pivotBlockHeader.getNumber(), - ethPeers.peerCount(), - ethPeers.getMaxPeers()); - ethPeers.disconnectWorstUselessPeer(); - return completedFuture(Optional.empty()); - } else { - return confirmPivotBlockHeader(bestPeer); - } - } - } - - private CompletableFuture> confirmPivotBlockHeader(final EthPeer bestPeer) { - final BlockHeader pivotBlockHeader = fastSyncState.getPivotBlockHeader().get(); - final RetryingGetHeaderFromPeerByNumberTask task = - RetryingGetHeaderFromPeerByNumberTask.forSingleNumber( - protocolSchedule, - ethContext, - metricsSystem, - pivotBlockHeader.getNumber(), - MAX_QUERY_RETRIES_PER_PEER); - task.assignPeer(bestPeer); - return ethContext - .getScheduler() - .timeout(task) - .thenCompose( - result -> { - if (peerHasDifferentPivotBlock(result)) { - if (!hasPivotChanged(pivotBlockHeader)) { - // if the pivot block has not changed, then warn and disconnect this peer - LOG.warn( - "Best peer has wrong pivot block (#{}) expecting {} but received {}. Disconnect: {}", - pivotBlockHeader.getNumber(), - pivotBlockHeader.getHash(), - result.size() == 1 ? result.get(0).getHash() : "invalid response", - bestPeer); - bestPeer.disconnect(DisconnectReason.USELESS_PEER); - return CompletableFuture.completedFuture(Optional.empty()); - } - LOG.debug( - "Retrying best peer {} with new pivot block {}", - bestPeer.getShortNodeId(), - pivotBlockHeader.toLogString()); - return confirmPivotBlockHeader(bestPeer); - } else { - return CompletableFuture.completedFuture(Optional.of(bestPeer)); - } - }) - .exceptionally( - error -> { - LOG.debug("Could not confirm best peer had pivot block", error); - return Optional.empty(); - }); - } - - private boolean hasPivotChanged(final BlockHeader requestedPivot) { - return fastSyncState - .getPivotBlockHash() - .filter(currentPivotHash -> requestedPivot.getBlockHash().equals(currentPivotHash)) - .isEmpty(); - } - - private boolean peerHasDifferentPivotBlock(final List result) { - final BlockHeader pivotBlockHeader = fastSyncState.getPivotBlockHeader().get(); - return result.size() != 1 || !result.get(0).equals(pivotBlockHeader); - } - - @Override - public boolean shouldContinueDownloading() { - final BlockHeader pivotBlockHeader = fastSyncState.getPivotBlockHeader().get(); - boolean isValidChainHead = - protocolContext.getBlockchain().getChainHeadHash().equals(pivotBlockHeader.getHash()); - if (!isValidChainHead) { - if (protocolContext.getBlockchain().contains(pivotBlockHeader.getHash())) { - protocolContext.getBlockchain().rewindToBlock(pivotBlockHeader.getHash()); - } else { - return true; - } - } - return !worldStateStorage.isWorldStateAvailable( - pivotBlockHeader.getStateRoot(), pivotBlockHeader.getBlockHash()); - } -} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/ImportBlocksStep.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/ImportBlocksStep.java new file mode 100644 index 00000000000..c6945964cf0 --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/ImportBlocksStep.java @@ -0,0 +1,125 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.sync.fastsync; + +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockImporter; +import org.hyperledger.besu.ethereum.core.BlockWithReceipts; +import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.sync.ValidationPolicy; +import org.hyperledger.besu.ethereum.eth.sync.tasks.exceptions.InvalidBlockException; +import org.hyperledger.besu.ethereum.mainnet.BlockImportResult; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; + +import java.util.List; +import java.util.OptionalLong; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import com.google.common.annotations.VisibleForTesting; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ImportBlocksStep implements Consumer> { + private static final Logger LOG = LoggerFactory.getLogger(ImportBlocksStep.class); + private static final long PRINT_DELAY = TimeUnit.SECONDS.toMillis(30L); + + private final ProtocolSchedule protocolSchedule; + protected final ProtocolContext protocolContext; + private final ValidationPolicy headerValidationPolicy; + private final ValidationPolicy ommerValidationPolicy; + private final EthContext ethContext; + private long accumulatedTime = 0L; + private OptionalLong logStartBlock = OptionalLong.empty(); + private final BlockHeader pivotHeader; + + public ImportBlocksStep( + final ProtocolSchedule protocolSchedule, + final ProtocolContext protocolContext, + final ValidationPolicy headerValidationPolicy, + final ValidationPolicy ommerValidationPolicy, + final EthContext ethContext, + final BlockHeader pivotHeader) { + this.protocolSchedule = protocolSchedule; + this.protocolContext = protocolContext; + this.headerValidationPolicy = headerValidationPolicy; + this.ommerValidationPolicy = ommerValidationPolicy; + this.ethContext = ethContext; + this.pivotHeader = pivotHeader; + } + + @Override + public void accept(final List blocksWithReceipts) { + final long startTime = System.nanoTime(); + for (final BlockWithReceipts blockWithReceipts : blocksWithReceipts) { + if (!importBlock(blockWithReceipts)) { + throw InvalidBlockException.fromInvalidBlock(blockWithReceipts.getHeader()); + } + LOG.atTrace() + .setMessage("Imported block {}") + .addArgument(blockWithReceipts.getBlock()::toLogString) + .log(); + } + if (logStartBlock.isEmpty()) { + logStartBlock = OptionalLong.of(blocksWithReceipts.get(0).getNumber()); + } + final long lastBlock = blocksWithReceipts.get(blocksWithReceipts.size() - 1).getNumber(); + int peerCount = -1; // ethContext is not available in tests + if (ethContext != null && ethContext.getEthPeers().peerCount() >= 0) { + peerCount = ethContext.getEthPeers().peerCount(); + } + final long endTime = System.nanoTime(); + + accumulatedTime += TimeUnit.MILLISECONDS.convert(endTime - startTime, TimeUnit.NANOSECONDS); + if (accumulatedTime > PRINT_DELAY) { + final long blocksPercent = getBlocksPercent(lastBlock, pivotHeader.getNumber()); + LOG.info( + "Block import progress: {} of {} ({}%), Peer count: {}", + lastBlock, pivotHeader.getNumber(), blocksPercent, peerCount); + LOG.debug( + "Completed importing chain segment {} to {} ({} blocks in {}ms), Peer count: {}", + logStartBlock.getAsLong(), + lastBlock, + lastBlock - logStartBlock.getAsLong() + 1, + accumulatedTime, + peerCount); + accumulatedTime = 0L; + logStartBlock = OptionalLong.empty(); + } + } + + @VisibleForTesting + protected static long getBlocksPercent(final long lastBlock, final long totalBlocks) { + if (totalBlocks == 0) { + return 0; + } + final long blocksPercent = (100 * lastBlock / totalBlocks); + return blocksPercent; + } + + protected boolean importBlock(final BlockWithReceipts blockWithReceipts) { + final BlockImporter importer = + protocolSchedule.getByBlockHeader(blockWithReceipts.getHeader()).getBlockImporter(); + final BlockImportResult blockImportResult = + importer.fastImportBlock( + protocolContext, + blockWithReceipts.getBlock(), + blockWithReceipts.getReceipts(), + headerValidationPolicy.getValidationModeForNextBlock(), + ommerValidationPolicy.getValidationModeForNextBlock()); + return blockImportResult.isImported(); + } +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/NoSyncRequiredException.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/NoSyncRequiredException.java new file mode 100644 index 00000000000..caa45abdc19 --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/NoSyncRequiredException.java @@ -0,0 +1,17 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.sync.fastsync; + +public class NoSyncRequiredException extends RuntimeException {} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/NoSyncRequiredState.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/NoSyncRequiredState.java new file mode 100644 index 00000000000..9cefe78ed15 --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/NoSyncRequiredState.java @@ -0,0 +1,17 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.sync.fastsync; + +public class NoSyncRequiredState extends FastSyncState {} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockConfirmer.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockConfirmer.java index 1253dbb17c1..c5c586ea77c 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockConfirmer.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockConfirmer.java @@ -210,8 +210,7 @@ Optional createGetHeaderTask( } } - LOG.debug( - "Query peer {}... for block {}.", peer.nodeId().toString().substring(0, 8), blockNumber); + LOG.debug("Query peer {} for block {}.", peer.getLoggableId(), blockNumber); return Optional.of(task); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockRetriever.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockRetriever.java index e20bb16799c..d8ff3627101 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockRetriever.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockRetriever.java @@ -39,7 +39,7 @@ public class PivotBlockRetriever { private static final Logger LOG = LoggerFactory.getLogger(PivotBlockRetriever.class); - public static final int MAX_QUERY_RETRIES_PER_PEER = 4; + public static final int MAX_QUERY_RETRIES_PER_PEER = 5; private static final int DEFAULT_MAX_PIVOT_BLOCK_RESETS = 250; private static final int SUSPICIOUS_NUMBER_OF_RETRIES = 5; @@ -156,8 +156,7 @@ private void handleContestedPivotBlock(final long contestedBlockNumber) { || pivotBlockNumber.get() <= BlockHeader.GENESIS_BLOCK_NUMBER) { LOG.info("Max retries reached, cancel pivot block download."); // Pivot block selection has failed - result.completeExceptionally( - new FastSyncException(FastSyncError.PIVOT_BLOCK_HEADER_MISMATCH)); + result.completeExceptionally(new SyncException(SyncError.PIVOT_BLOCK_HEADER_MISMATCH)); return; } else { LOG.info("Move pivot block back to {} and retry.", pivotBlockNumber); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotSelectorFromPeers.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotSelectorFromPeers.java index 9999f991ec3..16f7e09d6d1 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotSelectorFromPeers.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotSelectorFromPeers.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -35,8 +35,8 @@ public class PivotSelectorFromPeers implements PivotBlockSelector { private static final Logger LOG = LoggerFactory.getLogger(PivotSelectorFromPeers.class); - private final EthContext ethContext; - private final SynchronizerConfiguration syncConfig; + protected final EthContext ethContext; + protected final SynchronizerConfiguration syncConfig; private final SyncState syncState; private final MetricsSystem metricsSystem; @@ -66,7 +66,7 @@ public CompletableFuture prepareRetry() { conservativelyEstimatedPivotBlock(), syncConfig.getMaxTrailingPeers())); trailingPeerLimiter.enforceTrailingPeerLimit(); - return waitForPeers(syncConfig.getFastSyncMinimumPeerCount()); + return waitForPeers(syncConfig.getSyncMinimumPeerCount()); } @Override @@ -74,9 +74,9 @@ public long getBestChainHeight() { return syncState.bestChainHeight(); } - private Optional fromBestPeer(final EthPeer peer) { + protected Optional fromBestPeer(final EthPeer peer) { final long pivotBlockNumber = - peer.chainState().getEstimatedHeight() - syncConfig.getFastSyncPivotDistance(); + peer.chainState().getEstimatedHeight() - syncConfig.getSyncPivotDistance(); if (pivotBlockNumber <= BlockHeader.GENESIS_BLOCK_NUMBER) { // Peer's chain isn't long enough, return an empty value, so we can try again. LOG.info("Waiting for peers with sufficient chain height"); @@ -86,7 +86,7 @@ private Optional fromBestPeer(final EthPeer peer) { return Optional.of(new FastSyncState(pivotBlockNumber)); } - private Optional selectBestPeer() { + protected Optional selectBestPeer() { return ethContext .getEthPeers() .bestPeerMatchingCriteria(this::canPeerDeterminePivotBlock) @@ -96,7 +96,7 @@ private Optional selectBestPeer() { private boolean enoughFastSyncPeersArePresent() { final long peerCount = countPeersThatCanDeterminePivotBlock(); - final int minPeerCount = syncConfig.getFastSyncMinimumPeerCount(); + final int minPeerCount = syncConfig.getSyncMinimumPeerCount(); if (peerCount < minPeerCount) { LOG.info( "Waiting for valid peers with chain height information. {} / {} required peers currently available.", @@ -118,7 +118,7 @@ private long countPeersThatCanDeterminePivotBlock() { private boolean canPeerDeterminePivotBlock(final EthPeer peer) { LOG.debug( "peer {} hasEstimatedHeight {} isFullyValidated? {}", - peer.getShortNodeId(), + peer.getLoggableId(), peer.chainState().hasEstimatedHeight(), peer.isFullyValidated()); return peer.chainState().hasEstimatedHeight() && peer.isFullyValidated(); @@ -126,7 +126,7 @@ private boolean canPeerDeterminePivotBlock(final EthPeer peer) { private long conservativelyEstimatedPivotBlock() { final long estimatedNextPivot = - syncState.getLocalChainHeight() + syncConfig.getFastSyncPivotDistance(); + syncState.getLocalChainHeight() + syncConfig.getSyncPivotDistance(); return Math.min(syncState.bestChainHeight(), estimatedNextPivot); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotSelectorFromSafeBlock.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotSelectorFromSafeBlock.java index 052bee29fa3..c3bc9a5f903 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotSelectorFromSafeBlock.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotSelectorFromSafeBlock.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -28,6 +28,7 @@ import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import org.slf4j.Logger; @@ -127,7 +128,7 @@ public long getBestChainHeight() { maybeCachedHeadBlockHeader = Optional.of(blockHeader); return blockHeader.getNumber(); }) - .get(); + .get(20, TimeUnit.SECONDS); } catch (Throwable t) { LOG.debug( "Error trying to download chain head block header by hash {}", diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/SyncError.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/SyncError.java new file mode 100644 index 00000000000..d1277ddfdc1 --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/SyncError.java @@ -0,0 +1,21 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.sync.fastsync; + +public enum SyncError { + NO_PEERS_AVAILABLE, + PIVOT_BLOCK_HEADER_MISMATCH, + UNEXPECTED_ERROR +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/SyncException.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/SyncException.java new file mode 100644 index 00000000000..72e928dc18b --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/SyncException.java @@ -0,0 +1,34 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.sync.fastsync; + +public class SyncException extends RuntimeException { + + private final SyncError error; + + public SyncException(final SyncError error) { + super("Sync failed: " + error); + this.error = error; + } + + public SyncError getError() { + return error; + } + + public SyncException(final Throwable error) { + super(error); + this.error = SyncError.UNEXPECTED_ERROR; + } +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/SyncTargetManager.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/SyncTargetManager.java new file mode 100644 index 00000000000..2acfa54d04d --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/SyncTargetManager.java @@ -0,0 +1,194 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.sync.fastsync; + +import static java.util.concurrent.CompletableFuture.completedFuture; +import static org.hyperledger.besu.ethereum.eth.sync.fastsync.PivotBlockRetriever.MAX_QUERY_RETRIES_PER_PEER; +import static org.hyperledger.besu.util.log.LogUtil.throttledLog; + +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.manager.EthPeer; +import org.hyperledger.besu.ethereum.eth.manager.EthPeers; +import org.hyperledger.besu.ethereum.eth.sync.AbstractSyncTargetManager; +import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; +import org.hyperledger.besu.ethereum.eth.sync.tasks.RetryingGetHeaderFromPeerByNumberTask; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.plugin.services.MetricsSystem; + +import java.time.Duration; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SyncTargetManager extends AbstractSyncTargetManager { + private static final Logger LOG = LoggerFactory.getLogger(SyncTargetManager.class); + + private static final int LOG_DEBUG_REPEAT_DELAY = 15; + private static final int LOG_INFO_REPEAT_DELAY = 120; + private static final int SECONDS_PER_REQUEST = 6; // 5s per request + 1s wait between retries + + private final WorldStateStorageCoordinator worldStateStorageCoordinator; + private final ProtocolSchedule protocolSchedule; + private final ProtocolContext protocolContext; + private final EthContext ethContext; + private final MetricsSystem metricsSystem; + private final FastSyncState fastSyncState; + private final AtomicBoolean logDebug = new AtomicBoolean(true); + private final AtomicBoolean logInfo = new AtomicBoolean(true); + + public SyncTargetManager( + final SynchronizerConfiguration config, + final WorldStateStorageCoordinator worldStateStorageCoordinator, + final ProtocolSchedule protocolSchedule, + final ProtocolContext protocolContext, + final EthContext ethContext, + final MetricsSystem metricsSystem, + final FastSyncState fastSyncState) { + super(config, protocolSchedule, protocolContext, ethContext, metricsSystem); + this.worldStateStorageCoordinator = worldStateStorageCoordinator; + this.protocolSchedule = protocolSchedule; + this.protocolContext = protocolContext; + this.ethContext = ethContext; + this.metricsSystem = metricsSystem; + this.fastSyncState = fastSyncState; + } + + @Override + protected CompletableFuture> selectBestAvailableSyncTarget() { + final BlockHeader pivotBlockHeader = fastSyncState.getPivotBlockHeader().get(); + final EthPeers ethPeers = ethContext.getEthPeers(); + final Optional maybeBestPeer = ethPeers.bestPeerWithHeightEstimate(); + if (maybeBestPeer.isEmpty()) { + throttledLog( + LOG::debug, + String.format( + "Unable to find sync target. Currently checking %d peers for usefulness. Pivot block: %d", + ethContext.getEthPeers().peerCount(), pivotBlockHeader.getNumber()), + logDebug, + LOG_DEBUG_REPEAT_DELAY); + throttledLog( + LOG::info, + String.format( + "Unable to find sync target. Currently checking %d peers for usefulness.", + ethContext.getEthPeers().peerCount()), + logInfo, + LOG_INFO_REPEAT_DELAY); + return completedFuture(Optional.empty()); + } else { + final EthPeer bestPeer = maybeBestPeer.get(); + // Do not check the best peers estimated height if we are doing PoS + if (!protocolSchedule.getByBlockHeader(pivotBlockHeader).isPoS() + && bestPeer.chainState().getEstimatedHeight() < pivotBlockHeader.getNumber()) { + LOG.info( + "Best peer {} has chain height {} below pivotBlock height {}. Waiting for better peers. Current {} of max {}", + maybeBestPeer.map(EthPeer::getLoggableId).orElse("none"), + maybeBestPeer.map(p -> p.chainState().getEstimatedHeight()).orElse(-1L), + pivotBlockHeader.getNumber(), + ethPeers.peerCount(), + ethPeers.getMaxPeers()); + ethPeers.disconnectWorstUselessPeer(); + return completedFuture(Optional.empty()); + } else { + return confirmPivotBlockHeader(bestPeer); + } + } + } + + private CompletableFuture> confirmPivotBlockHeader(final EthPeer bestPeer) { + final BlockHeader pivotBlockHeader = fastSyncState.getPivotBlockHeader().get(); + final RetryingGetHeaderFromPeerByNumberTask task = + RetryingGetHeaderFromPeerByNumberTask.forSingleNumber( + protocolSchedule, + ethContext, + metricsSystem, + pivotBlockHeader.getNumber(), + MAX_QUERY_RETRIES_PER_PEER); + task.assignPeer(bestPeer); + return ethContext + .getScheduler() + // Task is a retrying task. Make sure that the timeout is long enough to allow for retries. + .timeout(task, Duration.ofSeconds(MAX_QUERY_RETRIES_PER_PEER * SECONDS_PER_REQUEST + 2)) + .thenCompose( + result -> { + if (peerHasDifferentPivotBlock(result)) { + if (!hasPivotChanged(pivotBlockHeader)) { + // if the pivot block has not changed, then warn and disconnect this peer + LOG.warn( + "Best peer has wrong pivot block (#{}) expecting {} but received {}. Disconnect: {}", + pivotBlockHeader.getNumber(), + pivotBlockHeader.getHash(), + result.size() == 1 ? result.get(0).getHash() : "invalid response", + bestPeer); + bestPeer.disconnect(DisconnectReason.USELESS_PEER_MISMATCHED_PIVOT_BLOCK); + return CompletableFuture.completedFuture(Optional.empty()); + } + LOG.debug( + "Retrying best peer {} with new pivot block {}", + bestPeer.getLoggableId(), + pivotBlockHeader.toLogString()); + return confirmPivotBlockHeader(bestPeer); + } else { + return CompletableFuture.completedFuture(Optional.of(bestPeer)); + } + }) + .exceptionally( + error -> { + LOG.atDebug() + .setMessage("Could not confirm best peer {} had pivot block {}, {}") + .addArgument(bestPeer.getLoggableId()) + .addArgument(pivotBlockHeader.getNumber()) + .addArgument(error) + .log(); + bestPeer.disconnect(DisconnectReason.USELESS_PEER_CANNOT_CONFIRM_PIVOT_BLOCK); + return Optional.empty(); + }); + } + + private boolean hasPivotChanged(final BlockHeader requestedPivot) { + return fastSyncState + .getPivotBlockHash() + .filter(currentPivotHash -> requestedPivot.getBlockHash().equals(currentPivotHash)) + .isEmpty(); + } + + private boolean peerHasDifferentPivotBlock(final List result) { + final BlockHeader pivotBlockHeader = fastSyncState.getPivotBlockHeader().get(); + return result.size() != 1 || !result.get(0).equals(pivotBlockHeader); + } + + @Override + public boolean shouldContinueDownloading() { + final BlockHeader pivotBlockHeader = fastSyncState.getPivotBlockHeader().get(); + boolean isValidChainHead = + protocolContext.getBlockchain().getChainHeadHash().equals(pivotBlockHeader.getHash()); + if (!isValidChainHead) { + if (protocolContext.getBlockchain().contains(pivotBlockHeader.getHash())) { + protocolContext.getBlockchain().rewindToBlock(pivotBlockHeader.getHash()); + } else { + return true; + } + } + return !worldStateStorageCoordinator.isWorldStateAvailable( + pivotBlockHeader.getStateRoot(), pivotBlockHeader.getBlockHash()); + } +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/checkpoint/Checkpoint.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/checkpoint/Checkpoint.java index 46f8e3d39df..883417c942b 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/checkpoint/Checkpoint.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/checkpoint/Checkpoint.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/AccountTrieNodeDataRequest.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/AccountTrieNodeDataRequest.java index 90aa59c045a..366cfd50f51 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/AccountTrieNodeDataRequest.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/AccountTrieNodeDataRequest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,16 +14,16 @@ */ package org.hyperledger.besu.ethereum.eth.sync.fastsync.worldstate; +import static org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator.applyForStrategy; + import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.ethereum.rlp.RLPOutput; import org.hyperledger.besu.ethereum.trie.CompactEncoding; import org.hyperledger.besu.ethereum.trie.MerkleTrie; -import org.hyperledger.besu.ethereum.worldstate.FlatDbMode; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage.Updater; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import java.util.Optional; import java.util.stream.Stream; @@ -38,16 +38,24 @@ class AccountTrieNodeDataRequest extends TrieNodeDataRequest { } @Override - protected void doPersist(final Updater updater) { - updater.putAccountStateTrieNode(getLocation().orElse(Bytes.EMPTY), getHash(), getData()); + protected void doPersist(final WorldStateKeyValueStorage.Updater updater) { + applyForStrategy( + updater, + onBonsai -> { + onBonsai.putAccountStateTrieNode(getLocation().orElse(Bytes.EMPTY), getHash(), getData()); + }, + onForest -> { + onForest.putAccountStateTrieNode(getHash(), getData()); + }); } @Override - public Optional getExistingData(final WorldStateStorage worldStateStorage) { + public Optional getExistingData( + final WorldStateStorageCoordinator worldStateKeyValueStorage) { return getLocation() .flatMap( location -> - worldStateStorage + worldStateKeyValueStorage .getAccountStateTrieNode(location, getHash()) .filter(data -> Hash.hash(data).equals(getHash()))); } @@ -60,7 +68,7 @@ protected NodeDataRequest createChildNodeDataRequest( @Override protected Stream getRequestsFromTrieNodeValue( - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateKeyValueStorage, final Optional location, final Bytes path, final Bytes value) { @@ -73,11 +81,11 @@ protected Stream getRequestsFromTrieNodeValue( Bytes32.wrap( CompactEncoding.pathToBytes( Bytes.concatenate(getLocation().orElse(Bytes.EMPTY), path))))); - if (!worldStateStorage.getFlatDbMode().equals(FlatDbMode.NO_FLATTENED)) { - ((BonsaiWorldStateKeyValueStorage.Updater) worldStateStorage.updater()) - .putAccountInfoState(accountHash.get(), value) - .commit(); - } + + worldStateKeyValueStorage.applyWhenFlatModeEnabled( + onBonsai -> { + onBonsai.updater().putAccountInfoState(accountHash.get(), value).commit(); + }); // Add code, if appropriate if (!accountValue.getCodeHash().equals(Hash.EMPTY)) { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/CodeNodeDataRequest.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/CodeNodeDataRequest.java index 0a8672ef795..714f6066f64 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/CodeNodeDataRequest.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/CodeNodeDataRequest.java @@ -14,10 +14,12 @@ */ package org.hyperledger.besu.ethereum.eth.sync.fastsync.worldstate; +import static org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator.applyForStrategy; + import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.rlp.RLPOutput; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage.Updater; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import java.util.Optional; import java.util.stream.Stream; @@ -34,21 +36,36 @@ class CodeNodeDataRequest extends NodeDataRequest { } @Override - protected void doPersist(final Updater updater) { - updater.putCode(accountHash.orElse(Hash.EMPTY), getHash(), getData()); + protected void doPersist(final WorldStateKeyValueStorage.Updater updater) { + applyForStrategy( + updater, + onBonsai -> { + onBonsai.putCode(accountHash.orElse(Hash.EMPTY), getHash(), getData()); + }, + onForest -> { + onForest.putCode(getHash(), getData()); + }); } @Override - public Stream getChildRequests(final WorldStateStorage worldStateStorage) { + public Stream getChildRequests( + final WorldStateStorageCoordinator worldStateStorageCoordinator) { // Code nodes have nothing further to download return Stream.empty(); } @Override - public Optional getExistingData(final WorldStateStorage worldStateStorage) { - return worldStateStorage - .getCode(getHash(), accountHash.orElse(Hash.EMPTY)) - .filter(codeBytes -> Hash.hash(codeBytes).equals(getHash())); + public Optional getExistingData( + final WorldStateStorageCoordinator worldStateStorageCoordinator) { + return worldStateStorageCoordinator.applyForStrategy( + onBonsai -> { + return onBonsai + .getCode(getHash(), accountHash.orElse(Hash.EMPTY)) + .filter(codeBytes -> Hash.hash(codeBytes).equals(getHash())); + }, + onForest -> { + return onForest.getCode(getHash()); + }); } public Optional getAccountHash() { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastDownloaderFactory.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastDownloaderFactory.java index 467dc0a53d0..8b71a57885d 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastDownloaderFactory.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastDownloaderFactory.java @@ -15,7 +15,6 @@ package org.hyperledger.besu.ethereum.eth.sync.fastsync.worldstate; import org.hyperledger.besu.ethereum.ProtocolContext; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.sync.PivotBlockSelector; @@ -29,8 +28,9 @@ import org.hyperledger.besu.ethereum.eth.sync.worldstate.WorldStateDownloader; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.metrics.BesuMetricCategory; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.services.tasks.InMemoryTasksPriorityQueues; @@ -59,9 +59,10 @@ public static Optional> create( final ProtocolContext protocolContext, final MetricsSystem metricsSystem, final EthContext ethContext, - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final SyncState syncState, - final Clock clock) { + final Clock clock, + final SyncDurationMetrics syncDurationMetrics) { final Path fastSyncDataDirectory = dataDirectory.resolve(FAST_SYNC_FOLDER); final FastSyncStateStorage fastSyncStateStorage = @@ -90,54 +91,58 @@ public static Optional> create( return Optional.empty(); } - if (worldStateStorage instanceof BonsaiWorldStateKeyValueStorage) { - worldStateStorage.clearFlatDatabase(); - } else { - final Path queueDataDir = fastSyncDataDirectory.resolve("statequeue"); - if (queueDataDir.toFile().exists()) { - LOG.warn( - "Fast sync is picking up after old fast sync version. Pruning the world state and starting from scratch."); - clearOldFastSyncWorldStateData(worldStateStorage, queueDataDir); - } - } + worldStateStorageCoordinator.consumeForStrategy( + onBonsai -> { + onBonsai.clearFlatDatabase(); + }, + onForest -> { + final Path queueDataDir = fastSyncDataDirectory.resolve("statequeue"); + if (queueDataDir.toFile().exists()) { + LOG.warn( + "Fast sync is picking up after old fast sync version. Pruning the world state and starting from scratch."); + clearOldFastSyncWorldStateData(worldStateStorageCoordinator, queueDataDir); + } + }); final InMemoryTasksPriorityQueues taskCollection = createWorldStateDownloaderTaskCollection( metricsSystem, syncConfig.getWorldStateTaskCacheSize()); final WorldStateDownloader worldStateDownloader = new FastWorldStateDownloader( ethContext, - worldStateStorage, + worldStateStorageCoordinator, taskCollection, syncConfig.getWorldStateHashCountPerRequest(), syncConfig.getWorldStateRequestParallelism(), syncConfig.getWorldStateMaxRequestsWithoutProgress(), syncConfig.getWorldStateMinMillisBeforeStalling(), clock, - metricsSystem); + metricsSystem, + syncDurationMetrics); final FastSyncDownloader fastSyncDownloader = new FastSyncDownloader<>( new FastSyncActions( syncConfig, - worldStateStorage, + worldStateStorageCoordinator, protocolSchedule, protocolContext, ethContext, syncState, pivotBlockSelector, metricsSystem), - worldStateStorage, + worldStateStorageCoordinator, worldStateDownloader, fastSyncStateStorage, taskCollection, fastSyncDataDirectory, - fastSyncState); + fastSyncState, + syncDurationMetrics); syncState.setWorldStateDownloadStatus(worldStateDownloader); return Optional.of(fastSyncDownloader); } private static void clearOldFastSyncWorldStateData( - final WorldStateStorage worldStateStorage, final Path queueDataDir) { - worldStateStorage.clear(); + final WorldStateStorageCoordinator worldStateKeyValueStorage, final Path queueDataDir) { + worldStateKeyValueStorage.clear(); try (final Stream stream = Files.list(queueDataDir); ) { stream.forEach(FastDownloaderFactory::deleteFile); deleteFile(queueDataDir); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastWorldDownloadState.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastWorldDownloadState.java index 0703c93ef89..307f98e7f5c 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastWorldDownloadState.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastWorldDownloadState.java @@ -14,10 +14,13 @@ */ package org.hyperledger.besu.ethereum.eth.sync.fastsync.worldstate; +import static org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator.applyForStrategy; + import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.eth.sync.worldstate.WorldDownloadState; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage.Updater; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.services.tasks.InMemoryTasksPriorityQueues; import java.time.Clock; @@ -31,17 +34,19 @@ public class FastWorldDownloadState extends WorldDownloadState private static final Logger LOG = LoggerFactory.getLogger(FastWorldDownloadState.class); public FastWorldDownloadState( - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final InMemoryTasksPriorityQueues pendingRequests, final int maxRequestsWithoutProgress, final long minMillisBeforeStalling, - final Clock clock) { + final Clock clock, + final SyncDurationMetrics syncDurationMetrics) { super( - worldStateStorage, + worldStateStorageCoordinator, pendingRequests, maxRequestsWithoutProgress, minMillisBeforeStalling, - clock); + clock, + syncDurationMetrics); } @Override @@ -53,15 +58,24 @@ public synchronized boolean checkCompletion(final BlockHeader header) { header.getStateRoot(), Optional.of(Bytes.EMPTY))); return false; } - final Updater updater = worldStateStorage.updater(); - updater.saveWorldState(header.getHash(), header.getStateRoot(), rootNodeData); + final WorldStateKeyValueStorage.Updater updater = worldStateStorageCoordinator.updater(); + applyForStrategy( + updater, + onBonsai -> { + onBonsai.saveWorldState(header.getHash(), header.getStateRoot(), rootNodeData); + }, + onForest -> { + onForest.saveWorldState(header.getStateRoot(), rootNodeData); + }); updater.commit(); internalFuture.complete(null); // THere are no more inputs to process so make sure we wake up any threads waiting to dequeue // so they can give up waiting. notifyAll(); + LOG.info("Finished downloading world state from peers"); + return true; } else { return false; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastWorldStateDownloader.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastWorldStateDownloader.java index 4bdbae71cee..341f1f66acf 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastWorldStateDownloader.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastWorldStateDownloader.java @@ -20,8 +20,9 @@ import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncActions; import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncState; import org.hyperledger.besu.ethereum.eth.sync.worldstate.WorldStateDownloader; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.metrics.BesuMetricCategory; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.services.tasks.InMemoryTasksPriorityQueues; @@ -48,24 +49,26 @@ public class FastWorldStateDownloader implements WorldStateDownloader { private final int hashCountPerRequest; private final int maxOutstandingRequests; private final int maxNodeRequestsWithoutProgress; - private final WorldStateStorage worldStateStorage; + private final WorldStateStorageCoordinator worldStateStorageCoordinator; private final AtomicReference downloadState = new AtomicReference<>(); + private final SyncDurationMetrics syncDurationMetrics; private Optional maybeCompleteTask = Optional.empty(); public FastWorldStateDownloader( final EthContext ethContext, - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final InMemoryTasksPriorityQueues taskCollection, final int hashCountPerRequest, final int maxOutstandingRequests, final int maxNodeRequestsWithoutProgress, final long minMillisBeforeStalling, final Clock clock, - final MetricsSystem metricsSystem) { + final MetricsSystem metricsSystem, + final SyncDurationMetrics syncDurationMetrics) { this.ethContext = ethContext; - this.worldStateStorage = worldStateStorage; + this.worldStateStorageCoordinator = worldStateStorageCoordinator; this.taskCollection = taskCollection; this.hashCountPerRequest = hashCountPerRequest; this.maxOutstandingRequests = maxOutstandingRequests; @@ -73,6 +76,7 @@ public FastWorldStateDownloader( this.minMillisBeforeStalling = minMillisBeforeStalling; this.clock = clock; this.metricsSystem = metricsSystem; + this.syncDurationMetrics = syncDurationMetrics; metricsSystem.createIntegerGauge( BesuMetricCategory.SYNCHRONIZER, @@ -117,7 +121,7 @@ public CompletableFuture run( final BlockHeader header = fastSyncState.getPivotBlockHeader().get(); final Hash stateRoot = header.getStateRoot(); - if (worldStateStorage.isWorldStateAvailable(stateRoot, header.getHash())) { + if (worldStateStorageCoordinator.isWorldStateAvailable(stateRoot, header.getHash())) { LOG.info( "World state already available for block {} ({}). State root {}", header.getNumber(), @@ -133,11 +137,12 @@ public CompletableFuture run( final FastWorldDownloadState newDownloadState = new FastWorldDownloadState( - worldStateStorage, + worldStateStorageCoordinator, taskCollection, maxNodeRequestsWithoutProgress, minMillisBeforeStalling, - clock); + clock, + syncDurationMetrics); this.downloadState.set(newDownloadState); if (!newDownloadState.downloadWasResumed()) { @@ -151,9 +156,9 @@ public CompletableFuture run( FastWorldStateDownloadProcess.builder() .hashCountPerRequest(hashCountPerRequest) .maxOutstandingRequests(maxOutstandingRequests) - .loadLocalDataStep(new LoadLocalDataStep(worldStateStorage, metricsSystem)) + .loadLocalDataStep(new LoadLocalDataStep(worldStateStorageCoordinator, metricsSystem)) .requestDataStep(new RequestDataStep(ethContext, metricsSystem)) - .persistDataStep(new PersistDataStep(worldStateStorage)) + .persistDataStep(new PersistDataStep(worldStateStorageCoordinator)) .completeTaskStep(maybeCompleteTask.get()) .downloadState(newDownloadState) .pivotBlockHeader(header) diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/LoadLocalDataStep.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/LoadLocalDataStep.java index f56780f5259..d84b92af2df 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/LoadLocalDataStep.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/LoadLocalDataStep.java @@ -14,7 +14,8 @@ */ package org.hyperledger.besu.ethereum.eth.sync.fastsync.worldstate; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.metrics.BesuMetricCategory; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.plugin.services.metrics.Counter; @@ -28,12 +29,13 @@ public class LoadLocalDataStep { - private final WorldStateStorage worldStateStorage; + private final WorldStateStorageCoordinator worldStateStorageCoordinator; private final Counter existingNodeCounter; public LoadLocalDataStep( - final WorldStateStorage worldStateStorage, final MetricsSystem metricsSystem) { - this.worldStateStorage = worldStateStorage; + final WorldStateStorageCoordinator worldStateStorageCoordinator, + final MetricsSystem metricsSystem) { + this.worldStateStorageCoordinator = worldStateStorageCoordinator; existingNodeCounter = metricsSystem.createCounter( BesuMetricCategory.SYNCHRONIZER, @@ -44,12 +46,12 @@ public LoadLocalDataStep( public Stream> loadLocalData( final Task task, final Pipe> completedTasks) { final NodeDataRequest request = task.getData(); - final Optional existingData = request.getExistingData(worldStateStorage); + final Optional existingData = request.getExistingData(worldStateStorageCoordinator); if (existingData.isPresent()) { existingNodeCounter.inc(); request.setData(existingData.get()); request.setRequiresPersisting(false); - final WorldStateStorage.Updater updater = worldStateStorage.updater(); + final WorldStateKeyValueStorage.Updater updater = worldStateStorageCoordinator.updater(); request.persist(updater); updater.commit(); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/NodeDataRequest.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/NodeDataRequest.java index 2ff8429316e..a5dda8524f0 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/NodeDataRequest.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/NodeDataRequest.java @@ -20,7 +20,8 @@ import org.hyperledger.besu.ethereum.eth.sync.worldstate.WorldStateDownloaderException; import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.ethereum.rlp.RLPOutput; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.services.tasks.TasksPriorityProvider; import java.util.Optional; @@ -103,7 +104,7 @@ public NodeDataRequest setRequiresPersisting(final boolean requiresPersisting) { return this; } - public final void persist(final WorldStateStorage.Updater updater) { + public final void persist(final WorldStateKeyValueStorage.Updater updater) { if (pendingChildren.get() > 0) { return; // we do nothing. Our last child will eventually persist us. } @@ -115,7 +116,7 @@ public final void persist(final WorldStateStorage.Updater updater) { parent -> parent.saveParent(updater), () -> LOG.warn("Missing a parent for {}", this.hash)); } - private void saveParent(final WorldStateStorage.Updater updater) { + private void saveParent(final WorldStateKeyValueStorage.Updater updater) { if (pendingChildren.decrementAndGet() == 0) { persist(updater); } @@ -127,11 +128,13 @@ private int incrementChildren() { protected abstract void writeTo(final RLPOutput out); - protected abstract void doPersist(final WorldStateStorage.Updater updater); + protected abstract void doPersist(final WorldStateKeyValueStorage.Updater updater); - public abstract Stream getChildRequests(WorldStateStorage worldStateStorage); + public abstract Stream getChildRequests( + WorldStateStorageCoordinator worldStateStorageCoordinator); - public abstract Optional getExistingData(final WorldStateStorage worldStateStorage); + public abstract Optional getExistingData( + final WorldStateStorageCoordinator worldStateStorageCoordinator); protected void registerParent(final NodeDataRequest parent) { if (this.possibleParent.isPresent()) { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/PersistDataStep.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/PersistDataStep.java index 1ab202ee6ce..fe83a12ec42 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/PersistDataStep.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/PersistDataStep.java @@ -20,8 +20,8 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.eth.sync.worldstate.WorldDownloadState; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage.Updater; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.plugin.services.exception.StorageException; import org.hyperledger.besu.services.tasks.Task; @@ -34,10 +34,10 @@ public class PersistDataStep { private static final Logger LOG = LoggerFactory.getLogger(PersistDataStep.class); - private final WorldStateStorage worldStateStorage; + private final WorldStateStorageCoordinator worldStateStorageCoordinator; - public PersistDataStep(final WorldStateStorage worldStateStorage) { - this.worldStateStorage = worldStateStorage; + public PersistDataStep(final WorldStateStorageCoordinator worldStateStorageCoordinator) { + this.worldStateStorageCoordinator = worldStateStorageCoordinator; } public List> persist( @@ -45,7 +45,7 @@ public List> persist( final BlockHeader blockHeader, final WorldDownloadState downloadState) { try { - final Updater updater = worldStateStorage.updater(); + final WorldStateKeyValueStorage.Updater updater = worldStateStorageCoordinator.updater(); tasks.stream() .map( task -> { @@ -88,6 +88,6 @@ private boolean isRootState(final BlockHeader blockHeader, final NodeDataRequest private void enqueueChildren( final Task task, final WorldDownloadState downloadState) { final NodeDataRequest request = task.getData(); - downloadState.enqueueRequests(request.getChildRequests(worldStateStorage)); + downloadState.enqueueRequests(request.getChildRequests(worldStateStorageCoordinator)); } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/StorageTrieNodeDataRequest.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/StorageTrieNodeDataRequest.java index a46faa06a67..9b1dc0839c1 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/StorageTrieNodeDataRequest.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/StorageTrieNodeDataRequest.java @@ -14,13 +14,13 @@ */ package org.hyperledger.besu.ethereum.eth.sync.fastsync.worldstate; +import static org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator.applyForStrategy; + import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.rlp.RLPOutput; import org.hyperledger.besu.ethereum.trie.CompactEncoding; -import org.hyperledger.besu.ethereum.worldstate.FlatDbMode; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage.Updater; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import java.util.Optional; import java.util.stream.Stream; @@ -40,20 +40,31 @@ class StorageTrieNodeDataRequest extends TrieNodeDataRequest { } @Override - protected void doPersist(final Updater updater) { - updater.putAccountStorageTrieNode( - accountHash.orElse(Hash.EMPTY), getLocation().orElse(Bytes.EMPTY), getHash(), getData()); + protected void doPersist(final WorldStateKeyValueStorage.Updater updater) { + applyForStrategy( + updater, + onBonsai -> { + onBonsai.putAccountStorageTrieNode( + accountHash.orElse(Hash.EMPTY), + getLocation().orElse(Bytes.EMPTY), + getHash(), + getData()); + }, + onForest -> { + onForest.putAccountStorageTrieNode(getHash(), getData()); + }); } @Override - public Optional getExistingData(final WorldStateStorage worldStateStorage) { + public Optional getExistingData( + final WorldStateStorageCoordinator worldStateStorageCoordinator) { return getAccountHash() .flatMap( accountHash -> getLocation() .flatMap( location -> - worldStateStorage + worldStateStorageCoordinator .getAccountStorageTrieNode(accountHash, location, getHash()) .filter(data -> Hash.hash(data).equals(getHash())))); } @@ -66,18 +77,21 @@ protected NodeDataRequest createChildNodeDataRequest( @Override protected Stream getRequestsFromTrieNodeValue( - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final Optional location, final Bytes path, final Bytes value) { - if (!worldStateStorage.getFlatDbMode().equals(FlatDbMode.NO_FLATTENED)) { - ((BonsaiWorldStateKeyValueStorage.Updater) worldStateStorage.updater()) - .putStorageValueBySlotHash( - accountHash.get(), - getSlotHash(location, path), - Bytes32.leftPad(RLP.decodeValue(value))) - .commit(); - } + + worldStateStorageCoordinator.applyWhenFlatModeEnabled( + worldStateKeyValueStorage -> { + worldStateKeyValueStorage + .updater() + .putStorageValueBySlotHash( + accountHash.get(), + getSlotHash(location, path), + Bytes32.leftPad(RLP.decodeValue(value))) + .commit(); + }); return Stream.empty(); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/TrieNodeDataRequest.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/TrieNodeDataRequest.java index 2761db14329..4d989d81ba9 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/TrieNodeDataRequest.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/TrieNodeDataRequest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,7 +17,7 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.trie.Node; import org.hyperledger.besu.ethereum.trie.patricia.TrieNodeDecoder; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import java.util.List; import java.util.Objects; @@ -34,7 +34,8 @@ protected TrieNodeDataRequest( } @Override - public Stream getChildRequests(final WorldStateStorage worldStateStorage) { + public Stream getChildRequests( + final WorldStateStorageCoordinator worldStateStorageCoordinator) { if (getData() == null) { // If this node hasn't been downloaded yet, we can't return any child data return Stream.empty(); @@ -53,7 +54,10 @@ public Stream getChildRequests(final WorldStateStorage worldSta .map( value -> getRequestsFromTrieNodeValue( - worldStateStorage, node.getLocation(), node.getPath(), value)) + worldStateStorageCoordinator, + node.getLocation(), + node.getPath(), + value)) .orElseGet(Stream::empty); } }) @@ -68,7 +72,7 @@ protected abstract NodeDataRequest createChildNodeDataRequest( final Hash childHash, final Optional location); protected abstract Stream getRequestsFromTrieNodeValue( - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final Optional location, final Bytes path, final Bytes value); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/BetterSyncTargetEvaluator.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/BetterSyncTargetEvaluator.java index 68aa5ec95f8..1df59c654c1 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/BetterSyncTargetEvaluator.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/BetterSyncTargetEvaluator.java @@ -40,7 +40,7 @@ public boolean shouldSwitchSyncTarget(final EthPeer currentSyncTarget) { return maybeBestPeer .map( bestPeer -> { - if (ethPeers.getBestChainComparator().compare(bestPeer, currentSyncTarget) <= 0) { + if (ethPeers.getBestPeerComparator().compare(bestPeer, currentSyncTarget) <= 0) { // Our current target is better or equal to the best peer return false; } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullImportBlockStep.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullImportBlockStep.java index a43646d4a54..f9d215ae2fc 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullImportBlockStep.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullImportBlockStep.java @@ -62,7 +62,7 @@ public void accept(final Block block) { final BlockImportResult blockImportResult = importer.importBlock(protocolContext, block, HeaderValidationMode.SKIP_DETACHED); if (!blockImportResult.isImported()) { - throw new InvalidBlockException("Failed to import block", blockNumber, block.getHash()); + throw InvalidBlockException.fromInvalidBlock(block.getHeader()); } gasAccumulator += block.getHeader().getGasUsed(); int peerCount = -1; // ethContext is not available in tests diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncChainDownloader.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncChainDownloader.java index f4a70116269..3a0f6edb086 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncChainDownloader.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncChainDownloader.java @@ -21,6 +21,7 @@ import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.plugin.services.MetricsSystem; public class FullSyncChainDownloader { @@ -33,7 +34,8 @@ public static ChainDownloader create( final EthContext ethContext, final SyncState syncState, final MetricsSystem metricsSystem, - final SyncTerminationCondition terminationCondition) { + final SyncTerminationCondition terminationCondition, + final SyncDurationMetrics syncDurationMetrics) { final FullSyncTargetManager syncTargetManager = new FullSyncTargetManager( @@ -55,6 +57,7 @@ public static ChainDownloader create( metricsSystem, terminationCondition), ethContext.getScheduler(), - metricsSystem); + metricsSystem, + syncDurationMetrics); } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncDownloadPipelineFactory.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncDownloadPipelineFactory.java index 27f50eead83..b822236b8f0 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncDownloadPipelineFactory.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncDownloadPipelineFactory.java @@ -91,7 +91,7 @@ public Pipeline createDownloadPipelineForSyncTarget(final SyncTarget target) ethContext.getScheduler(), target.peer(), target.commonAncestor(), - syncConfig.getDownloaderCheckpointTimeoutsPermitted(), + syncConfig.getDownloaderCheckpointRetries(), fullSyncTerminationCondition); final DownloadHeadersStep downloadHeadersStep = new DownloadHeadersStep( diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncDownloader.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncDownloader.java index 4484c194460..8f1aca792c3 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncDownloader.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncDownloader.java @@ -21,6 +21,7 @@ import org.hyperledger.besu.ethereum.eth.sync.TrailingPeerRequirements; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.plugin.services.MetricsSystem; import java.util.concurrent.CompletableFuture; @@ -43,7 +44,8 @@ public FullSyncDownloader( final EthContext ethContext, final SyncState syncState, final MetricsSystem metricsSystem, - final SyncTerminationCondition terminationCondition) { + final SyncTerminationCondition terminationCondition, + final SyncDurationMetrics syncDurationMetrics) { this.syncConfig = syncConfig; this.protocolContext = protocolContext; this.syncState = syncState; @@ -56,7 +58,8 @@ public FullSyncDownloader( ethContext, syncState, metricsSystem, - terminationCondition); + terminationCondition, + syncDurationMetrics); } public CompletableFuture start() { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncTargetManager.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncTargetManager.java index 43c2cfa19c5..6bb9e3b7adc 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncTargetManager.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncTargetManager.java @@ -21,7 +21,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; -import org.hyperledger.besu.ethereum.eth.sync.SyncTargetManager; +import org.hyperledger.besu.ethereum.eth.sync.AbstractSyncTargetManager; import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; import org.hyperledger.besu.ethereum.eth.sync.state.SyncTarget; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; @@ -34,7 +34,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -class FullSyncTargetManager extends SyncTargetManager { +class FullSyncTargetManager extends AbstractSyncTargetManager { private static final Logger LOG = LoggerFactory.getLogger(FullSyncTargetManager.class); private final ProtocolContext protocolContext; @@ -67,7 +67,7 @@ protected Optional finalizeSelectedSyncTarget(final SyncTarget syncT syncTarget.peer(), commonAncestor.getNumber(), commonAncestor.getHash()); - syncTarget.peer().disconnect(DisconnectReason.USELESS_PEER); + syncTarget.peer().disconnect(DisconnectReason.USELESS_PEER_WORLD_STATE_NOT_AVAILABLE); return Optional.empty(); } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/range/RangeHeadersFetcher.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/range/RangeHeadersFetcher.java index 8ee1dc28a43..e73c5aa9f5f 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/range/RangeHeadersFetcher.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/range/RangeHeadersFetcher.java @@ -68,6 +68,10 @@ public RangeHeadersFetcher( public CompletableFuture> getNextRangeHeaders( final EthPeer peer, final BlockHeader previousRangeHeader) { + LOG.atTrace() + .setMessage("Requesting next range headers from peer {}") + .addArgument(peer.getLoggableId()) + .log(); final int skip = syncConfig.getDownloaderChainSegmentSize() - 1; final int maximumHeaderRequestSize = syncConfig.getDownloaderHeaderRequestSize(); final long previousRangeNumber = previousRangeHeader.getNumber(); @@ -78,11 +82,20 @@ public CompletableFuture> getNextRangeHeaders( final BlockHeader targetHeader = finalRangeHeader.get(); final long blocksUntilTarget = targetHeader.getNumber() - previousRangeNumber; if (blocksUntilTarget <= 0) { + LOG.atTrace() + .setMessage("Requesting next range headers: no blocks until target: {}") + .addArgument(blocksUntilTarget) + .log(); return completedFuture(emptyList()); } final long maxHeadersToRequest = blocksUntilTarget / (skip + 1); additionalHeaderCount = (int) Math.min(maxHeadersToRequest, maximumHeaderRequestSize); if (additionalHeaderCount == 0) { + LOG.atTrace() + .setMessage( + "Requesting next range headers: additional header count is 0, blocks until target: {}") + .addArgument(blocksUntilTarget) + .log(); return completedFuture(singletonList(targetHeader)); } } else { @@ -97,11 +110,12 @@ private CompletableFuture> requestHeaders( final BlockHeader referenceHeader, final int headerCount, final int skip) { - LOG.trace( - "Requesting {} range headers, starting from {}, {} blocks apart", - headerCount, - referenceHeader.getNumber(), - skip); + LOG.atTrace() + .setMessage("Requesting {} range headers, starting from {}, {} blocks apart") + .addArgument(headerCount) + .addArgument(referenceHeader.getNumber()) + .addArgument(skip) + .log(); return GetHeadersFromPeerByHashTask.startingAtHash( protocolSchedule, ethContext, @@ -114,7 +128,19 @@ private CompletableFuture> requestHeaders( .assignPeer(peer) .run() .thenApply(PeerTaskResult::getResult) - .thenApply(headers -> stripExistingRangeHeaders(referenceHeader, headers)); + .thenApply( + headers -> { + if (headers.size() < headerCount) { + LOG.atTrace() + .setMessage( + "Peer {} returned fewer headers than requested. Expected: {}, Actual: {}") + .addArgument(peer) + .addArgument(headerCount) + .addArgument(headers.size()) + .log(); + } + return stripExistingRangeHeaders(referenceHeader, headers); + }); } private List stripExistingRangeHeaders( diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/range/RangeHeadersValidationStep.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/range/RangeHeadersValidationStep.java index 80530286da4..fe60df02772 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/range/RangeHeadersValidationStep.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/range/RangeHeadersValidationStep.java @@ -67,8 +67,7 @@ public Stream apply(final RangeHeaders rangeHeaders) { rangeEndDescription, firstHeader.getNumber(), firstHeader.getHash()); - throw new InvalidBlockException( - errorMessage, firstHeader.getNumber(), firstHeader.getHash()); + throw InvalidBlockException.fromInvalidBlock(errorMessage, firstHeader); } }) .orElse(Stream.empty()); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/range/SyncTargetRangeSource.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/range/SyncTargetRangeSource.java index fbeac02f406..05d7899226b 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/range/SyncTargetRangeSource.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/range/SyncTargetRangeSource.java @@ -22,6 +22,7 @@ import org.hyperledger.besu.ethereum.eth.manager.EthPeer; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.sync.fullsync.SyncTerminationCondition; +import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage; import java.time.Duration; import java.util.ArrayDeque; @@ -39,12 +40,13 @@ public class SyncTargetRangeSource implements Iterator { private static final Logger LOG = LoggerFactory.getLogger(SyncTargetRangeSource.class); private static final Duration RETRY_DELAY_DURATION = Duration.ofSeconds(2); + public static final int DEFAULT_TIME_TO_WAIT_IN_SECONDS = 6; private final RangeHeadersFetcher fetcher; private final SyncTargetChecker syncTargetChecker; private final EthPeer peer; private final EthScheduler ethScheduler; - private final int rangeTimeoutsPermitted; + private final int retriesPermitted; private final Duration newHeaderWaitDuration; private final SyncTerminationCondition terminationCondition; @@ -52,7 +54,7 @@ public class SyncTargetRangeSource implements Iterator { private BlockHeader lastRangeEnd; private boolean reachedEndOfRanges = false; private Optional>> pendingRequests = Optional.empty(); - private int requestFailureCount = 0; + private int retryCount = 0; public SyncTargetRangeSource( final RangeHeadersFetcher fetcher, @@ -60,7 +62,7 @@ public SyncTargetRangeSource( final EthScheduler ethScheduler, final EthPeer peer, final BlockHeader commonAncestor, - final int rangeTimeoutsPermitted, + final int retriesPermitted, final SyncTerminationCondition terminationCondition) { this( fetcher, @@ -68,8 +70,8 @@ public SyncTargetRangeSource( ethScheduler, peer, commonAncestor, - rangeTimeoutsPermitted, - Duration.ofSeconds(5), + retriesPermitted, + Duration.ofSeconds(DEFAULT_TIME_TO_WAIT_IN_SECONDS), terminationCondition); } @@ -79,7 +81,7 @@ public SyncTargetRangeSource( final EthScheduler ethScheduler, final EthPeer peer, final BlockHeader commonAncestor, - final int rangeTimeoutsPermitted, + final int retriesPermitted, final Duration newHeaderWaitDuration, final SyncTerminationCondition terminationCondition) { this.fetcher = fetcher; @@ -87,7 +89,7 @@ public SyncTargetRangeSource( this.ethScheduler = ethScheduler; this.peer = peer; this.lastRangeEnd = commonAncestor; - this.rangeTimeoutsPermitted = rangeTimeoutsPermitted; + this.retriesPermitted = retriesPermitted; this.newHeaderWaitDuration = newHeaderWaitDuration; this.terminationCondition = terminationCondition; } @@ -96,7 +98,7 @@ public SyncTargetRangeSource( public boolean hasNext() { return terminationCondition.shouldContinueDownload() && (!retrievedRanges.isEmpty() - || (requestFailureCount < rangeTimeoutsPermitted + || (retryCount < retriesPermitted && syncTargetChecker.shouldContinueDownloadingFromSyncTarget(peer, lastRangeEnd) && !reachedEndOfRanges)); } @@ -148,24 +150,40 @@ private SyncTargetRange getRangeFromPendingRequest() { pendingRequest.get(newHeaderWaitDuration.toMillis(), MILLISECONDS); this.pendingRequests = Optional.empty(); if (newHeaders.isEmpty()) { - requestFailureCount++; + retryCount++; + if (retryCount >= retriesPermitted) { + LOG.atDebug() + .setMessage( + "Disconnecting target peer {} for providing useless or empty range headers.") + .addArgument(peer) + .log(); + peer.disconnect(DisconnectMessage.DisconnectReason.USELESS_PEER_USELESS_RESPONSES); + } } else { - requestFailureCount = 0; - } - for (final BlockHeader header : newHeaders) { - retrievedRanges.add(new SyncTargetRange(peer, lastRangeEnd, header)); - lastRangeEnd = header; + retryCount = 0; + for (final BlockHeader header : newHeaders) { + retrievedRanges.add(new SyncTargetRange(peer, lastRangeEnd, header)); + lastRangeEnd = header; + } } return retrievedRanges.poll(); } catch (final InterruptedException e) { LOG.trace("Interrupted while waiting for new range headers", e); return null; - } catch (final ExecutionException e) { - LOG.debug("Failed to retrieve new range headers", e); - this.pendingRequests = Optional.empty(); - requestFailureCount++; - return null; - } catch (final TimeoutException e) { + } catch (final ExecutionException | TimeoutException e) { + if (e instanceof ExecutionException) { + this.pendingRequests = Optional.empty(); + } + retryCount++; + if (retryCount >= retriesPermitted) { + LOG.atDebug() + .setMessage( + "Disconnecting target peer {} for not providing useful range headers: Exception: {}.") + .addArgument(peer) + .addArgument(e) + .log(); + peer.disconnect(DisconnectMessage.DisconnectReason.USELESS_PEER_USELESS_RESPONSES); + } return null; } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/CompleteTaskStep.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/CompleteTaskStep.java index 202853ce28f..5af6d2f30d9 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/CompleteTaskStep.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/CompleteTaskStep.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -43,9 +43,11 @@ public CompleteTaskStep( public synchronized void markAsCompleteOrFailed( final SnapWorldDownloadState downloadState, final Task task) { - if (task.getData().isResponseReceived() - || (task.getData() instanceof TrieNodeHealingRequest - && task.getData().isExpired(snapSyncState))) { + final boolean isResponseReceived = task.getData().isResponseReceived(); + final boolean isExpiredRequest = + task.getData() instanceof TrieNodeHealingRequest && task.getData().isExpired(snapSyncState); + // if pivot block has changed, the request is expired and we mark this one completed + if (isResponseReceived || isExpiredRequest) { completedRequestsCounter.inc(); task.markCompleted(); downloadState.checkCompletion(snapSyncState.getPivotBlockHeader().orElseThrow()); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/DynamicPivotBlockSelector.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/DynamicPivotBlockSelector.java index 1e3d6bb2a5e..4b5efcd61db 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/DynamicPivotBlockSelector.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/DynamicPivotBlockSelector.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -161,7 +161,7 @@ private CompletableFuture downloadNewPivotBlock(final FastSyncState fss) { .addArgument(this::logLastPivotBlockFound) .log(); }) - .orTimeout(5, TimeUnit.MINUTES); + .orTimeout(20, TimeUnit.SECONDS); } private boolean isSamePivotBlock(final FastSyncState fss) { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/LoadLocalDataStep.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/LoadLocalDataStep.java index c24dbf6037d..943fb614703 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/LoadLocalDataStep.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/LoadLocalDataStep.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -20,7 +20,8 @@ import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.SnapDataRequest; import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.heal.TrieNodeHealingRequest; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.metrics.BesuMetricCategory; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.plugin.services.exception.StorageException; @@ -38,7 +39,7 @@ public class LoadLocalDataStep { private static final Logger LOG = LoggerFactory.getLogger(LoadLocalDataStep.class); - private final WorldStateStorage worldStateStorage; + private final WorldStateStorageCoordinator worldStateStorageCoordinator; private final SnapWorldDownloadState downloadState; private final SnapSyncProcessState snapSyncState; @@ -46,12 +47,12 @@ public class LoadLocalDataStep { private final Counter existingNodeCounter; public LoadLocalDataStep( - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final SnapWorldDownloadState downloadState, final SnapSyncConfiguration snapSyncConfiguration, final MetricsSystem metricsSystem, final SnapSyncProcessState snapSyncState) { - this.worldStateStorage = worldStateStorage; + this.worldStateStorageCoordinator = worldStateStorageCoordinator; this.downloadState = downloadState; this.snapSyncConfiguration = snapSyncConfiguration; existingNodeCounter = @@ -68,16 +69,21 @@ public Stream> loadLocalDataTrieNode( // check if node is already stored in the worldstate try { if (snapSyncState.hasPivotBlockHeader()) { - Optional existingData = request.getExistingData(downloadState, worldStateStorage); + Optional existingData = request.getExistingData(worldStateStorageCoordinator); if (existingData.isPresent()) { existingNodeCounter.inc(); request.setData(existingData.get()); request.setRequiresPersisting(false); - final WorldStateStorage.Updater updater = worldStateStorage.updater(); + final WorldStateKeyValueStorage.Updater updater = worldStateStorageCoordinator.updater(); request.persist( - worldStateStorage, updater, downloadState, snapSyncState, snapSyncConfiguration); + worldStateStorageCoordinator, + updater, + downloadState, + snapSyncState, + snapSyncConfiguration); updater.commit(); - downloadState.enqueueRequests(request.getRootStorageRequests(worldStateStorage)); + downloadState.enqueueRequests( + request.getRootStorageRequests(worldStateStorageCoordinator)); completedTasks.put(task); return Stream.empty(); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/PersistDataStep.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/PersistDataStep.java index df3696ccdf7..890db52a072 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/PersistDataStep.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/PersistDataStep.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -18,10 +18,11 @@ import static org.hyperledger.besu.ethereum.eth.sync.StorageExceptionManager.errorCountAtThreshold; import static org.hyperledger.besu.ethereum.eth.sync.StorageExceptionManager.getRetryableErrorCounter; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.SnapDataRequest; import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.heal.TrieNodeHealingRequest; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.plugin.services.exception.StorageException; import org.hyperledger.besu.services.tasks.Task; @@ -35,30 +36,31 @@ public class PersistDataStep { private static final Logger LOG = LoggerFactory.getLogger(PersistDataStep.class); private final SnapSyncProcessState snapSyncState; - private final WorldStateStorage worldStateStorage; + private final WorldStateStorageCoordinator worldStateStorageCoordinator; private final SnapWorldDownloadState downloadState; private final SnapSyncConfiguration snapSyncConfiguration; public PersistDataStep( final SnapSyncProcessState snapSyncState, - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final SnapWorldDownloadState downloadState, final SnapSyncConfiguration snapSyncConfiguration) { this.snapSyncState = snapSyncState; - this.worldStateStorage = worldStateStorage; + this.worldStateStorageCoordinator = worldStateStorageCoordinator; this.downloadState = downloadState; this.snapSyncConfiguration = snapSyncConfiguration; } public List> persist(final List> tasks) { try { - final WorldStateStorage.Updater updater = worldStateStorage.updater(); + final WorldStateKeyValueStorage.Updater updater = worldStateStorageCoordinator.updater(); for (Task task : tasks) { if (task.getData().isResponseReceived()) { // enqueue child requests final Stream childRequests = - task.getData().getChildRequests(downloadState, worldStateStorage, snapSyncState); + task.getData() + .getChildRequests(downloadState, worldStateStorageCoordinator, snapSyncState); if (!(task.getData() instanceof TrieNodeHealingRequest)) { enqueueChildren(childRequests); } else { @@ -73,7 +75,7 @@ public List> persist(final List> tas final int persistedNodes = task.getData() .persist( - worldStateStorage, + worldStateStorageCoordinator, updater, downloadState, snapSyncState, @@ -115,15 +117,21 @@ public List> persist(final List> tas */ public List> healFlatDatabase(final List> tasks) { final BonsaiWorldStateKeyValueStorage.Updater updater = - (BonsaiWorldStateKeyValueStorage.Updater) worldStateStorage.updater(); + (BonsaiWorldStateKeyValueStorage.Updater) worldStateStorageCoordinator.updater(); for (Task task : tasks) { // heal and/or persist task.getData() - .persist(worldStateStorage, updater, downloadState, snapSyncState, snapSyncConfiguration); + .persist( + worldStateStorageCoordinator, + updater, + downloadState, + snapSyncState, + snapSyncConfiguration); // enqueue child requests, these will be the right part of the ranges to complete if we have // not healed all the range enqueueChildren( - task.getData().getChildRequests(downloadState, worldStateStorage, snapSyncState)); + task.getData() + .getChildRequests(downloadState, worldStateStorageCoordinator, snapSyncState)); } updater.commit(); return tasks; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/RangeManager.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/RangeManager.java deleted file mode 100644 index 83f58c10fbf..00000000000 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/RangeManager.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.eth.sync.snapsync; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.trie.InnerNodeDiscoveryManager; -import org.hyperledger.besu.ethereum.trie.MerkleTrieException; -import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; - -import java.math.BigInteger; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.TreeMap; -import java.util.function.Function; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; - -/** - * This class helps to generate ranges according to several parameters (the start and the end of the - * range, its size) - */ -public class RangeManager { - - public static final Hash MIN_RANGE = Hash.wrap(Bytes32.ZERO); - public static final Hash MAX_RANGE = - Hash.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); - - private RangeManager() {} - - public static int getRangeCount( - final Bytes32 min, final Bytes32 max, final TreeMap items) { - if (min.equals(MIN_RANGE) && max.equals(MAX_RANGE)) { - return MAX_RANGE - .toUnsignedBigInteger() - .divide(items.lastKey().toUnsignedBigInteger().subtract(min.toUnsignedBigInteger())) - .intValue(); - } - return 1; - } - - public static Map generateAllRanges(final int sizeRange) { - if (sizeRange == 1) { - return Map.ofEntries(Map.entry(MIN_RANGE, MAX_RANGE)); - } - return generateRanges( - MIN_RANGE.toUnsignedBigInteger(), MAX_RANGE.toUnsignedBigInteger(), sizeRange); - } - - /** - * Generate a range - * - * @param min start of the range in bytes - * @param max the max end of the range in bytes - * @param nbRange number of ranges - * @return the start and end of the generated range - */ - public static Map generateRanges( - final Bytes32 min, final Bytes32 max, final int nbRange) { - return generateRanges(min.toUnsignedBigInteger(), max.toUnsignedBigInteger(), nbRange); - } - - /** - * Generate a range - * - * @param min start of the range - * @param max the max end of the range - * @param nbRange number of ranges - * @return the start and end of the generated range - */ - public static Map generateRanges( - final BigInteger min, final BigInteger max, final int nbRange) { - final BigInteger rangeSize = max.subtract(min).divide(BigInteger.valueOf(nbRange)); - final TreeMap ranges = new TreeMap<>(); - if (min.compareTo(max) > 0) { - return ranges; - } - if (min.equals(max) || nbRange == 1) { - ranges.put(format(min), format(max)); - return ranges; - } - BigInteger currentStart = min; - BigInteger currentEnd = min; - while (max.subtract(currentEnd).compareTo(rangeSize) > 0) { - currentEnd = currentStart.add(rangeSize); - ranges.put(format(currentStart), format(currentEnd)); - currentStart = currentStart.add(rangeSize).add(BigInteger.ONE); - } - if (max.subtract(currentEnd).compareTo(BigInteger.ZERO) > 0) { - currentEnd = currentStart.add(max.subtract(currentEnd)).subtract(BigInteger.ONE); - ranges.put(format(currentStart), format(currentEnd)); - } - return ranges; - } - - /** - * Helps to create a new range according to the last data obtained. This happens when a peer - * doesn't return all of the data in a range. - * - * @param worldstateRootHash the root hash - * @param proofs proof received - * @param endKeyHash the end of the range initially wanted - * @param receivedKeys the last key received - * @return begin of the new range - */ - public static Optional findNewBeginElementInRange( - final Bytes32 worldstateRootHash, - final List proofs, - final TreeMap receivedKeys, - final Bytes32 endKeyHash) { - if (receivedKeys.isEmpty() || receivedKeys.lastKey().compareTo(endKeyHash) >= 0) { - return Optional.empty(); - } else { - final Map proofsEntries = new HashMap<>(); - for (Bytes proof : proofs) { - proofsEntries.put(Hash.hash(proof), proof); - } - final StoredMerklePatriciaTrie storageTrie = - new StoredMerklePatriciaTrie<>( - new InnerNodeDiscoveryManager<>( - (location, key) -> Optional.ofNullable(proofsEntries.get(key)), - Function.identity(), - Function.identity(), - receivedKeys.lastKey(), - endKeyHash, - false), - worldstateRootHash); - - try { - storageTrie.visitAll(bytesNode -> {}); - } catch (MerkleTrieException e) { - return Optional.of(InnerNodeDiscoveryManager.decodePath(e.getLocation())); - } - return Optional.empty(); - } - } - - private static Bytes32 format(final BigInteger data) { - return Bytes32.leftPad(Bytes.of(data.toByteArray()).trimLeadingZeros()); - } -} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/RequestDataStep.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/RequestDataStep.java index f78ffe3af9a..5fec76994fd 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/RequestDataStep.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/RequestDataStep.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -31,7 +31,9 @@ import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.heal.StorageFlatDatabaseHealingRangeRequest; import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.heal.TrieNodeHealingRequest; import org.hyperledger.besu.ethereum.proof.WorldStateProofProvider; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.trie.RangeManager; +import org.hyperledger.besu.ethereum.worldstate.FlatDbMode; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.services.tasks.Task; @@ -39,18 +41,22 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.NavigableMap; import java.util.TreeMap; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import com.google.common.collect.Lists; import kotlin.collections.ArrayDeque; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class RequestDataStep { - - private final WorldStateStorage worldStateStorage; + private static final Logger LOG = LoggerFactory.getLogger(RequestDataStep.class); + private final WorldStateStorageCoordinator worldStateStorageCoordinator; private final SnapSyncProcessState fastSyncState; private final SnapWorldDownloadState downloadState; private final SnapSyncConfiguration snapSyncConfiguration; @@ -60,18 +66,18 @@ public class RequestDataStep { public RequestDataStep( final EthContext ethContext, - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final SnapSyncProcessState fastSyncState, final SnapWorldDownloadState downloadState, final SnapSyncConfiguration snapSyncConfiguration, final MetricsSystem metricsSystem) { - this.worldStateStorage = worldStateStorage; + this.worldStateStorageCoordinator = worldStateStorageCoordinator; this.fastSyncState = fastSyncState; this.downloadState = downloadState; this.snapSyncConfiguration = snapSyncConfiguration; this.metricsSystem = metricsSystem; this.ethContext = ethContext; - this.worldStateProofProvider = new WorldStateProofProvider(worldStateStorage); + this.worldStateProofProvider = new WorldStateProofProvider(worldStateStorageCoordinator); } public CompletableFuture> requestAccount( @@ -90,14 +96,23 @@ public CompletableFuture> requestAccount( downloadState.addOutstandingTask(getAccountTask); return getAccountTask .run() + .orTimeout(10, TimeUnit.SECONDS) .handle( (response, error) -> { + downloadState.removeOutstandingTask(getAccountTask); if (response != null) { - downloadState.removeOutstandingTask(getAccountTask); accountDataRequest.setRootHash(blockHeader.getStateRoot()); accountDataRequest.addResponse( worldStateProofProvider, response.accounts(), response.proofs()); } + if (error != null) { + LOG.atDebug() + .setMessage("Error handling account download accounts ({} - {}) task: {}") + .addArgument(accountDataRequest.getStartKeyHash()) + .addArgument(accountDataRequest.getEndKeyHash()) + .addArgument(error) + .log(); + } return requestTask; }); } @@ -125,21 +140,47 @@ public CompletableFuture>> requestStorage( downloadState.addOutstandingTask(getStorageRangeTask); return getStorageRangeTask .run() + .orTimeout(10, TimeUnit.SECONDS) .handle( (response, error) -> { + downloadState.removeOutstandingTask(getStorageRangeTask); if (response != null) { - downloadState.removeOutstandingTask(getStorageRangeTask); - for (int i = 0; i < response.slots().size(); i++) { - final StorageRangeDataRequest request = - (StorageRangeDataRequest) requestTasks.get(i).getData(); - request.setRootHash(blockHeader.getStateRoot()); - request.addResponse( - downloadState, - worldStateProofProvider, - response.slots().get(i), - i < response.slots().size() - 1 ? new ArrayDeque<>() : response.proofs()); + final ArrayDeque> slots = new ArrayDeque<>(); + /* + * Checks if the response represents an "empty range". + * + * An "empty range" is defined as a response where at least one proof exists + * and either no slots are present, or the first slot is empty + */ + try { + final boolean isEmptyRange = + (response.slots().isEmpty() || response.slots().get(0).isEmpty()) + && !response.proofs().isEmpty(); + if (isEmptyRange) { // empty range detected + slots.add(new TreeMap<>()); + } else { + slots.addAll(response.slots()); + } + for (int i = 0; i < slots.size(); i++) { + final StorageRangeDataRequest request = + (StorageRangeDataRequest) requestTasks.get(i).getData(); + request.setRootHash(blockHeader.getStateRoot()); + request.addResponse( + downloadState, + worldStateProofProvider, + slots.get(i), + i < slots.size() - 1 ? new ArrayDeque<>() : response.proofs()); + } + } catch (final Exception e) { + LOG.error("Error while processing storage range response", e); } } + if (error != null) { + LOG.atDebug() + .setMessage("Error handling storage range request task: {}") + .addArgument(error) + .log(); + } return requestTasks; }); } @@ -160,10 +201,11 @@ public CompletableFuture>> requestCode( downloadState.addOutstandingTask(getByteCodeTask); return getByteCodeTask .run() + .orTimeout(10, TimeUnit.SECONDS) .handle( (response, error) -> { + downloadState.removeOutstandingTask(getByteCodeTask); if (response != null) { - downloadState.removeOutstandingTask(getByteCodeTask); for (Task requestTask : requestTasks) { final BytecodeRequest request = (BytecodeRequest) requestTask.getData(); request.setRootHash(blockHeader.getStateRoot()); @@ -172,6 +214,12 @@ public CompletableFuture>> requestCode( } } } + if (error != null) { + LOG.atDebug() + .setMessage("Error handling code request task: {}") + .addArgument(error) + .log(); + } return requestTasks; }); } @@ -199,10 +247,11 @@ public CompletableFuture>> requestTrieNodeByPath( downloadState.addOutstandingTask(getTrieNodeFromPeerTask); return getTrieNodeFromPeerTask .run() + .orTimeout(10, TimeUnit.SECONDS) .handle( (response, error) -> { + downloadState.removeOutstandingTask(getTrieNodeFromPeerTask); if (response != null) { - downloadState.removeOutstandingTask(getTrieNodeFromPeerTask); for (final Task task : requestTasks) { final TrieNodeHealingRequest request = (TrieNodeHealingRequest) task.getData(); final Bytes matchingData = response.get(request.getPathId()); @@ -211,6 +260,12 @@ public CompletableFuture>> requestTrieNodeByPath( } } } + if (error != null) { + LOG.atDebug() + .setMessage("Error handling trie node request task: {}") + .addArgument(error) + .log(); + } return requestTasks; }); } @@ -230,12 +285,18 @@ public CompletableFuture> requestLocalFlatAccounts( final BlockHeader blockHeader = fastSyncState.getPivotBlockHeader().get(); // retrieve accounts from flat database - final TreeMap accounts = - (TreeMap) - worldStateStorage.streamFlatAccounts( - accountDataRequest.getStartKeyHash(), - accountDataRequest.getEndKeyHash(), - snapSyncConfiguration.getLocalFlatAccountCountToHealPerRequest()); + final TreeMap accounts = new TreeMap<>(); + + worldStateStorageCoordinator.applyOnMatchingFlatMode( + FlatDbMode.FULL, + onBonsai -> { + accounts.putAll( + onBonsai.streamFlatAccounts( + accountDataRequest.getStartKeyHash(), + accountDataRequest.getEndKeyHash(), + snapSyncConfiguration.getLocalFlatAccountCountToHealPerRequest())); + }); + final List proofs = new ArrayList<>(); if (!accounts.isEmpty()) { // generate range proof if accounts are present @@ -270,13 +331,18 @@ public CompletableFuture> requestLocalFlatStorages( storageDataRequest.setRootHash(blockHeader.getStateRoot()); // retrieve slots from flat database - final TreeMap slots = - (TreeMap) - worldStateStorage.streamFlatStorages( - storageDataRequest.getAccountHash(), - storageDataRequest.getStartKeyHash(), - storageDataRequest.getEndKeyHash(), - snapSyncConfiguration.getLocalFlatStorageCountToHealPerRequest()); + final TreeMap slots = new TreeMap<>(); + worldStateStorageCoordinator.applyOnMatchingFlatMode( + FlatDbMode.FULL, + onBonsai -> { + slots.putAll( + onBonsai.streamFlatStorages( + storageDataRequest.getAccountHash(), + storageDataRequest.getStartKeyHash(), + storageDataRequest.getEndKeyHash(), + snapSyncConfiguration.getLocalFlatStorageCountToHealPerRequest())); + }); + final List proofs = new ArrayList<>(); if (!slots.isEmpty()) { // generate range proof if slots are present diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/RequestType.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/RequestType.java index 6b7e932e000..2c7359288ac 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/RequestType.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/RequestType.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapDownloaderFactory.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapDownloaderFactory.java index 65fb117788e..5de8ceb9843 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapDownloaderFactory.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapDownloaderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -32,7 +32,8 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions; import org.hyperledger.besu.ethereum.trie.CompactEncoding; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.services.tasks.InMemoryTasksPriorityQueues; @@ -56,9 +57,10 @@ public static Optional> createSnapDownloader( final ProtocolContext protocolContext, final MetricsSystem metricsSystem, final EthContext ethContext, - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final SyncState syncState, - final Clock clock) { + final Clock clock, + final SyncDurationMetrics syncDurationMetrics) { final Path fastSyncDataDirectory = dataDirectory.resolve(FAST_SYNC_FOLDER); final FastSyncStateStorage fastSyncStateStorage = @@ -93,10 +95,7 @@ public static Optional> createSnapDownloader( return Optional.empty(); } - final SnapSyncProcessState snapSyncState = - new SnapSyncProcessState( - fastSyncStateStorage.loadState( - ScheduleBasedBlockHeaderFunctions.create(protocolSchedule))); + final SnapSyncProcessState snapSyncState = new SnapSyncProcessState(fastSyncState); final InMemoryTasksPriorityQueues snapTaskCollection = createSnapWorldStateDownloaderTaskCollection(); @@ -105,31 +104,33 @@ public static Optional> createSnapDownloader( ethContext, snapContext, protocolContext, - worldStateStorage, + worldStateStorageCoordinator, snapTaskCollection, syncConfig.getSnapSyncConfiguration(), syncConfig.getWorldStateRequestParallelism(), syncConfig.getWorldStateMaxRequestsWithoutProgress(), syncConfig.getWorldStateMinMillisBeforeStalling(), clock, - metricsSystem); + metricsSystem, + syncDurationMetrics); final FastSyncDownloader fastSyncDownloader = new SnapSyncDownloader( new FastSyncActions( syncConfig, - worldStateStorage, + worldStateStorageCoordinator, protocolSchedule, protocolContext, ethContext, syncState, pivotBlockSelector, metricsSystem), - worldStateStorage, + worldStateStorageCoordinator, snapWorldStateDownloader, fastSyncStateStorage, snapTaskCollection, fastSyncDataDirectory, - snapSyncState); + snapSyncState, + syncDurationMetrics); syncState.setWorldStateDownloadStatus(snapWorldStateDownloader); return Optional.of(fastSyncDownloader); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapSyncConfiguration.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapSyncConfiguration.java index 6358590395b..dbe043b92a0 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapSyncConfiguration.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapSyncConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -36,7 +36,9 @@ public class SnapSyncConfiguration { public static final int DEFAULT_LOCAL_FLAT_STORAGE_COUNT_TO_HEAL_PER_REQUEST = 1024; // The default number of flat slots entries to verify and heal per request. - public static final Boolean DEFAULT_IS_FLAT_DB_HEALING_ENABLED = Boolean.FALSE; + public static final Boolean DEFAULT_SNAP_SERVER_ENABLED = Boolean.FALSE; + + public static final Boolean DEFAULT_SNAP_SYNC_BFT_ENABLED = Boolean.FALSE; public static SnapSyncConfiguration getDefault() { return ImmutableSnapSyncConfiguration.builder().build(); @@ -78,7 +80,12 @@ public int getLocalFlatStorageCountToHealPerRequest() { } @Value.Default - public Boolean isFlatDbHealingEnabled() { - return DEFAULT_IS_FLAT_DB_HEALING_ENABLED; + public Boolean isSnapServerEnabled() { + return DEFAULT_SNAP_SERVER_ENABLED; + } + + @Value.Default + public Boolean isSnapSyncBftEnabled() { + return DEFAULT_SNAP_SYNC_BFT_ENABLED; } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapSyncDownloader.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapSyncDownloader.java index ac409513156..34fd4de5bea 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapSyncDownloader.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapSyncDownloader.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -20,35 +20,33 @@ import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncStateStorage; import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.SnapDataRequest; import org.hyperledger.besu.ethereum.eth.sync.worldstate.WorldStateDownloader; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.services.tasks.TaskCollection; import java.nio.file.Path; import java.util.concurrent.CompletableFuture; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - public class SnapSyncDownloader extends FastSyncDownloader { - private static final Logger LOG = LoggerFactory.getLogger(SnapSyncDownloader.class); - public SnapSyncDownloader( final FastSyncActions fastSyncActions, - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final WorldStateDownloader worldStateDownloader, final FastSyncStateStorage fastSyncStateStorage, final TaskCollection taskCollection, final Path fastSyncDataDirectory, - final FastSyncState initialFastSyncState) { + final FastSyncState initialFastSyncState, + final SyncDurationMetrics syncDurationMetrics) { super( fastSyncActions, - worldStateStorage, + worldStateStorageCoordinator, worldStateDownloader, fastSyncStateStorage, taskCollection, fastSyncDataDirectory, - initialFastSyncState); + initialFastSyncState, + syncDurationMetrics); } @Override diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapSyncMetricsManager.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapSyncMetricsManager.java new file mode 100644 index 00000000000..1091c766bd7 --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapSyncMetricsManager.java @@ -0,0 +1,246 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.sync.snapsync; + +import static io.netty.util.internal.ObjectUtil.checkNonEmpty; +import static org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncMetricsManager.Step.HEAL_TRIE; + +import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.trie.RangeManager; +import org.hyperledger.besu.metrics.BesuMetricCategory; +import org.hyperledger.besu.plugin.services.MetricsSystem; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.MathContext; +import java.math.RoundingMode; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.tuweni.bytes.Bytes32; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Manages the metrics related to the SnapSync process. */ +public class SnapSyncMetricsManager { + + private static final Logger LOG = LoggerFactory.getLogger(SnapSyncMetricsManager.class); + private static final long PRINT_DELAY = TimeUnit.MINUTES.toMillis(1); + + private final MetricsSystem metricsSystem; + private final EthContext ethContext; + + /** Represents the progress status of the snapsync process. */ + private final AtomicReference percentageProgress; + + /** + * Represents the number of accounts downloaded during the initial step of the snapsync process. + */ + private final AtomicLong nbAccountsDownloaded; + + /** Represents the number of slots downloaded during the initial step of the snapsync process. */ + private final AtomicLong nbSlotsDownloaded; + + /** Represents the number of code entries downloaded. */ + private final AtomicLong nbCodes; + + /** + * Represents the number of trie nodes generated during the initial step of the snapsync process. + */ + private final AtomicLong nbTrieNodesGenerated; + + /** Represents the number of flat accounts healed during the healing process. */ + private final AtomicLong nbFlatAccountsHealed; + + /** Represents the number of flat slots healed during the healing process. */ + private final AtomicLong nbFlatSlotsHealed; + + /** Represents the number of trie nodes healed during the healing process. */ + private final AtomicLong nbTrieNodesHealed; + + private long startSyncTime; + + private final Map lastRangeIndex = new HashMap<>(); + + private long lastNotifyTimestamp; + + public SnapSyncMetricsManager(final MetricsSystem metricsSystem, final EthContext ethContext) { + this.metricsSystem = metricsSystem; + this.ethContext = ethContext; + percentageProgress = new AtomicReference<>(new BigDecimal(0)); + nbAccountsDownloaded = new AtomicLong(0); + nbSlotsDownloaded = new AtomicLong(0); + nbCodes = new AtomicLong(0); + nbTrieNodesGenerated = new AtomicLong(0); + nbFlatAccountsHealed = new AtomicLong(0); + nbFlatSlotsHealed = new AtomicLong(0); + nbTrieNodesHealed = new AtomicLong(0); + metricsSystem.createLongGauge( + BesuMetricCategory.SYNCHRONIZER, + "snap_world_state_generated_nodes_total", + "Total number of data nodes generated as part of snap sync world state download", + nbTrieNodesGenerated::get); + metricsSystem.createLongGauge( + BesuMetricCategory.SYNCHRONIZER, + "snap_world_state_healed_nodes_total", + "Total number of data nodes healed as part of snap sync world state heal process", + nbTrieNodesHealed::get); + metricsSystem.createLongGauge( + BesuMetricCategory.SYNCHRONIZER, + "snap_world_state_accounts_total", + "Total number of accounts downloaded as part of snap sync world state", + nbAccountsDownloaded::get); + metricsSystem.createLongGauge( + BesuMetricCategory.SYNCHRONIZER, + "snap_world_state_slots_total", + "Total number of slots downloaded as part of snap sync world state", + nbSlotsDownloaded::get); + metricsSystem.createLongGauge( + BesuMetricCategory.SYNCHRONIZER, + "snap_world_state_flat_accounts_healed_total", + "Total number of accounts healed in the flat database as part of snap sync world state", + nbFlatAccountsHealed::get); + metricsSystem.createLongGauge( + BesuMetricCategory.SYNCHRONIZER, + "snap_world_state_flat_slots_healed_total", + "Total number of slots healed in the flat database as part of snap sync world state", + nbFlatSlotsHealed::get); + metricsSystem.createLongGauge( + BesuMetricCategory.SYNCHRONIZER, + "snap_world_state_codes_total", + "Total number of codes downloaded as part of snap sync world state", + nbCodes::get); + } + + public void initRange(final Map ranges) { + for (Map.Entry entry : ranges.entrySet()) { + this.lastRangeIndex.put(entry.getValue(), entry.getKey().toUnsignedBigInteger()); + } + this.startSyncTime = System.currentTimeMillis(); + this.lastNotifyTimestamp = startSyncTime; + } + + public void notifyRangeProgress( + final Step step, final Bytes32 startKeyHash, final Bytes32 endKeyHash) { + checkNonEmpty(lastRangeIndex, "snapsync range collection"); + if (lastRangeIndex.containsKey(endKeyHash)) { + final BigInteger lastPos = lastRangeIndex.get(endKeyHash); + final BigInteger newPos = startKeyHash.toUnsignedBigInteger(); + percentageProgress.getAndAccumulate( + BigDecimal.valueOf(100) + .multiply(new BigDecimal(newPos.subtract(lastPos))) + .divide( + new BigDecimal(RangeManager.MAX_RANGE.toUnsignedBigInteger()), + MathContext.DECIMAL32), + BigDecimal::add); + lastRangeIndex.put(endKeyHash, newPos); + print(step); + } + } + + public void notifyAccountsDownloaded(final long nbAccounts) { + this.nbAccountsDownloaded.getAndAdd(nbAccounts); + } + + public void notifySlotsDownloaded(final long nbSlots) { + this.nbSlotsDownloaded.getAndAdd(nbSlots); + } + + public void notifyCodeDownloaded() { + this.nbCodes.getAndIncrement(); + } + + public void notifyNodesGenerated(final long nbNodes) { + this.nbTrieNodesGenerated.getAndAdd(nbNodes); + } + + public void notifyTrieNodesHealed(final long nbNodes) { + this.nbTrieNodesHealed.getAndAdd(nbNodes); + print(HEAL_TRIE); + } + + private void print(final Step step) { + final long now = System.currentTimeMillis(); + if (now - lastNotifyTimestamp >= PRINT_DELAY) { + lastNotifyTimestamp = now; + int peerCount = -1; // ethContext is not available in tests + if (ethContext != null && ethContext.getEthPeers().peerCount() >= 0) { + peerCount = ethContext.getEthPeers().peerCount(); + } + switch (step) { + case DOWNLOAD -> { + LOG.debug( + "Worldstate {} in progress accounts={}, slots={}, codes={}, nodes={}", + step.message, + nbAccountsDownloaded, + nbSlotsDownloaded, + nbCodes, + nbTrieNodesGenerated); + LOG.info( + "Worldstate {} progress: {}%, Peer count: {}", + step.message, percentageProgress.get().setScale(2, RoundingMode.HALF_UP), peerCount); + } + case HEAL_FLAT -> { + LOG.debug( + "Worldstate {} in progress accounts={}, slots={}", + step.message, + nbFlatAccountsHealed, + nbFlatSlotsHealed); + LOG.info( + "Worldstate {} progress: {}%, Peer count: {}", + step.message, percentageProgress.get().setScale(2, RoundingMode.HALF_UP), peerCount); + } + case HEAL_TRIE -> { + LOG.info( + "Healed {} world state trie nodes, Peer count: {}", + nbTrieNodesHealed.get(), + peerCount); + } + } + } + } + + public void notifySnapSyncCompleted() { + final Duration duration = Duration.ofMillis(System.currentTimeMillis() - startSyncTime); + LOG.info( + "Finished worldstate snapsync with nodes {} (healed={}) duration {}{}:{},{}.", + nbTrieNodesGenerated.addAndGet(nbTrieNodesHealed.get()), + nbTrieNodesHealed, + duration.toHoursPart() > 0 ? (duration.toHoursPart() + ":") : "", + duration.toMinutesPart(), + duration.toSecondsPart(), + duration.toMillisPart()); + } + + public MetricsSystem getMetricsSystem() { + return metricsSystem; + } + + public enum Step { + DOWNLOAD("download"), + HEAL_TRIE("trie node healing"), + HEAL_FLAT("flat database healing"); + + final String message; + + Step(final String message) { + this.message = message; + } + } +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapSyncProcessState.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapSyncProcessState.java index 344631763aa..a80ba2aa6e7 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapSyncProcessState.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapSyncProcessState.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldDownloadState.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldDownloadState.java index dd7481cdd00..41beaafa6a4 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldDownloadState.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldDownloadState.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -16,10 +16,12 @@ import static org.hyperledger.besu.ethereum.eth.sync.snapsync.request.SnapDataRequest.createAccountFlatHealingRangeRequest; import static org.hyperledger.besu.ethereum.eth.sync.snapsync.request.SnapDataRequest.createAccountTrieNodeDataRequest; +import static org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator.applyForStrategy; import org.hyperledger.besu.ethereum.chain.BlockAddedObserver; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.sync.snapsync.context.SnapSyncStatePersistenceManager; import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.AccountRangeDataRequest; import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.BytecodeRequest; @@ -28,9 +30,15 @@ import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.heal.AccountFlatDatabaseHealingRangeRequest; import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.heal.StorageFlatDatabaseHealingRangeRequest; import org.hyperledger.besu.ethereum.eth.sync.worldstate.WorldDownloadState; +import org.hyperledger.besu.ethereum.trie.RangeManager; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.worldstate.FlatDbMode; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.metrics.BesuMetricCategory; +import org.hyperledger.besu.metrics.SyncDurationMetrics; +import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.services.tasks.InMemoryTaskQueue; import org.hyperledger.besu.services.tasks.InMemoryTasksPriorityQueues; import org.hyperledger.besu.services.tasks.Task; @@ -41,7 +49,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.OptionalLong; +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.function.Predicate; @@ -72,7 +80,7 @@ public class SnapWorldDownloadState extends WorldDownloadState protected final InMemoryTasksPriorityQueues pendingStorageFlatDatabaseHealingRequests = new InMemoryTasksPriorityQueues<>(); - private HashSet accountsHealingList = new HashSet<>(); + private Set accountsHealingList = new HashSet<>(); private DynamicPivotBlockSelector pivotBlockSelector; private final SnapSyncStatePersistenceManager snapContext; @@ -80,72 +88,68 @@ public class SnapWorldDownloadState extends WorldDownloadState // blockchain private final Blockchain blockchain; - private OptionalLong blockObserverId; + private final Long blockObserverId; + private final EthContext ethContext; // metrics around the snapsync - private final SnapsyncMetricsManager metricsManager; + private final SnapSyncMetricsManager metricsManager; + + private final AtomicBoolean trieHealStartedBefore = new AtomicBoolean(false); public SnapWorldDownloadState( - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final SnapSyncStatePersistenceManager snapContext, final Blockchain blockchain, final SnapSyncProcessState snapSyncState, final InMemoryTasksPriorityQueues pendingRequests, final int maxRequestsWithoutProgress, final long minMillisBeforeStalling, - final SnapsyncMetricsManager metricsManager, - final Clock clock) { + final SnapSyncMetricsManager metricsManager, + final Clock clock, + final EthContext ethContext, + final SyncDurationMetrics syncDurationMetrics) { super( - worldStateStorage, + worldStateStorageCoordinator, pendingRequests, maxRequestsWithoutProgress, minMillisBeforeStalling, - clock); + clock, + syncDurationMetrics); this.snapContext = snapContext; this.blockchain = blockchain; this.snapSyncState = snapSyncState; this.metricsManager = metricsManager; - this.blockObserverId = OptionalLong.empty(); - metricsManager - .getMetricsSystem() - .createLongGauge( - BesuMetricCategory.SYNCHRONIZER, - "snap_world_state_pending_account_requests_current", - "Number of account pending requests for snap sync world state download", - pendingAccountRequests::size); - metricsManager - .getMetricsSystem() - .createLongGauge( - BesuMetricCategory.SYNCHRONIZER, - "snap_world_state_pending_storage_requests_current", - "Number of storage pending requests for snap sync world state download", - pendingStorageRequests::size); - metricsManager - .getMetricsSystem() - .createLongGauge( - BesuMetricCategory.SYNCHRONIZER, - "snap_world_state_pending_big_storage_requests_current", - "Number of storage pending requests for snap sync world state download", - pendingLargeStorageRequests::size); - metricsManager - .getMetricsSystem() - .createLongGauge( - BesuMetricCategory.SYNCHRONIZER, - "snap_world_state_pending_code_requests_current", - "Number of code pending requests for snap sync world state download", - pendingCodeRequests::size); - metricsManager - .getMetricsSystem() - .createLongGauge( - BesuMetricCategory.SYNCHRONIZER, - "snap_world_state_pending_trie_node_requests_current", - "Number of trie node pending requests for snap sync world state download", - pendingTrieNodeRequests::size); - } - - @Override - public synchronized void notifyTaskAvailable() { - notifyAll(); + this.blockObserverId = blockchain.observeBlockAdded(createBlockchainObserver()); + this.ethContext = ethContext; + + final MetricsSystem metricsSystem = metricsManager.getMetricsSystem(); + metricsSystem.createLongGauge( + BesuMetricCategory.SYNCHRONIZER, + "snap_world_state_pending_account_requests_current", + "Number of account pending requests for snap sync world state download", + pendingAccountRequests::size); + metricsSystem.createLongGauge( + BesuMetricCategory.SYNCHRONIZER, + "snap_world_state_pending_storage_requests_current", + "Number of storage pending requests for snap sync world state download", + pendingStorageRequests::size); + metricsSystem.createLongGauge( + BesuMetricCategory.SYNCHRONIZER, + "snap_world_state_pending_big_storage_requests_current", + "Number of storage pending requests for snap sync world state download", + pendingLargeStorageRequests::size); + metricsSystem.createLongGauge( + BesuMetricCategory.SYNCHRONIZER, + "snap_world_state_pending_code_requests_current", + "Number of code pending requests for snap sync world state download", + pendingCodeRequests::size); + metricsSystem.createLongGauge( + BesuMetricCategory.SYNCHRONIZER, + "snap_world_state_pending_trie_node_requests_current", + "Number of trie node pending requests for snap sync world state download", + pendingTrieNodeRequests::size); + syncDurationMetrics.startTimer( + SyncDurationMetrics.Labels.SNAP_INITIAL_WORLD_STATE_DOWNLOAD_DURATION); } @Override @@ -168,11 +172,6 @@ public synchronized boolean checkCompletion(final BlockHeader header) { // if all snapsync tasks are completed and the healing process was not running if (!snapSyncState.isHealTrieInProgress()) { - // Register blockchain observer if not already registered - blockObserverId = - blockObserverId.isEmpty() - ? OptionalLong.of(blockchain.observeBlockAdded(createBlockchainObserver())) - : blockObserverId; // Start the healing process startTrieHeal(); } @@ -186,25 +185,36 @@ else if (pivotBlockSelector.isBlockchainBehind()) { // if all snapsync tasks are completed and the healing was running and the blockchain is not // behind the pivot block else { - // Remove the blockchain observer - blockObserverId.ifPresent(blockchain::removeObserver); + syncDurationMetrics.stopTimer(SyncDurationMetrics.Labels.SNAP_WORLD_STATE_HEALING_DURATION); + syncDurationMetrics.stopTimer(SyncDurationMetrics.Labels.CHAIN_DOWNLOAD_DURATION); + // If the flat database healing process is not in progress and the flat database mode is // FULL if (!snapSyncState.isHealFlatDatabaseInProgress() - && worldStateStorage.getFlatDbMode().equals(FlatDbMode.FULL)) { - // Start the flat database healing process + && worldStateStorageCoordinator.isMatchingFlatMode(FlatDbMode.FULL)) { startFlatDatabaseHeal(header); } // If the flat database healing process is in progress or the flat database mode is not FULL else { - final WorldStateStorage.Updater updater = worldStateStorage.updater(); - updater.saveWorldState(header.getHash(), header.getStateRoot(), rootNodeData); + final WorldStateKeyValueStorage.Updater updater = worldStateStorageCoordinator.updater(); + applyForStrategy( + updater, + onBonsai -> { + onBonsai.saveWorldState(header.getHash(), header.getStateRoot(), rootNodeData); + }, + onForest -> { + onForest.saveWorldState(header.getStateRoot(), rootNodeData); + }); updater.commit(); + + // Remove the blockchain observer + blockchain.removeObserver(blockObserverId); // Notify that the snap sync has completed metricsManager.notifySnapSyncCompleted(); // Clear the snap context snapContext.clear(); internalFuture.complete(null); + return true; } } @@ -224,6 +234,12 @@ protected synchronized void cleanupQueues() { /** Method to start the healing process of the trie */ public synchronized void startTrieHeal() { + if (trieHealStartedBefore.compareAndSet(false, true)) { + syncDurationMetrics.stopTimer( + SyncDurationMetrics.Labels.SNAP_INITIAL_WORLD_STATE_DOWNLOAD_DURATION); + + syncDurationMetrics.startTimer(SyncDurationMetrics.Labels.SNAP_WORLD_STATE_HEALING_DURATION); + } snapContext.clearAccountRangeTasks(); snapSyncState.setHealTrieStatus(true); // Try to find a new pivot block before starting the healing process @@ -242,8 +258,14 @@ public synchronized void startTrieHeal() { /** Method to reload the healing process of the trie */ public synchronized void reloadTrieHeal() { // Clear the flat database and trie log from the world state storage if needed - worldStateStorage.clearFlatDatabase(); - worldStateStorage.clearTrieLog(); + worldStateStorageCoordinator.applyOnMatchingStrategy( + DataStorageFormat.BONSAI, + worldStateKeyValueStorage -> { + final BonsaiWorldStateKeyValueStorage strategy = + worldStateStorageCoordinator.getStrategy(BonsaiWorldStateKeyValueStorage.class); + strategy.clearFlatDatabase(); + strategy.clearTrieLog(); + }); // Clear pending trie node and code requests pendingTrieNodeRequests.clear(); pendingCodeRequests.clear(); @@ -254,6 +276,7 @@ public synchronized void reloadTrieHeal() { public synchronized void startFlatDatabaseHeal(final BlockHeader header) { LOG.info("Initiating the healing process for the flat database"); + syncDurationMetrics.startTimer(SyncDurationMetrics.Labels.FLAT_DB_HEAL); snapSyncState.setHealFlatDatabaseInProgress(true); final Map ranges = RangeManager.generateAllRanges(16); ranges.forEach( @@ -286,7 +309,7 @@ public synchronized void enqueueRequest(final SnapDataRequest request) { } } - public synchronized void setAccountsHealingList(final HashSet addAccountToHealingList) { + public synchronized void setAccountsHealingList(final Set addAccountToHealingList) { this.accountsHealingList = addAccountToHealingList; } @@ -304,7 +327,7 @@ public synchronized void addAccountToHealingList(final Bytes account) { } } - public HashSet getAccountsHealingList() { + public Set getAccountsHealingList() { return accountsHealingList; } @@ -400,7 +423,7 @@ public synchronized Task dequeueStorageFlatDatabaseHealingReque __ -> {}); } - public SnapsyncMetricsManager getMetricsManager() { + public SnapSyncMetricsManager getMetricsManager() { return metricsManager; } @@ -409,25 +432,29 @@ public void setPivotBlockSelector(final DynamicPivotBlockSelector pivotBlockSele } public BlockAddedObserver createBlockchainObserver() { - return addedBlockContext -> { - final AtomicBoolean foundNewPivotBlock = new AtomicBoolean(false); - pivotBlockSelector.check( - (____, isNewPivotBlock) -> { - if (isNewPivotBlock) { - foundNewPivotBlock.set(true); - } - }); - - final boolean isNewPivotBlockFound = foundNewPivotBlock.get(); - final boolean isBlockchainCaughtUp = - snapSyncState.isWaitingBlockchain() && !pivotBlockSelector.isBlockchainBehind(); - - if (isNewPivotBlockFound - || isBlockchainCaughtUp) { // restart heal if we found a new pivot block or if close to - // head again - snapSyncState.setWaitingBlockchain(false); - reloadTrieHeal(); - } - }; + return addedBlockContext -> + ethContext + .getScheduler() + .executeServiceTask( + () -> { + final AtomicBoolean foundNewPivotBlock = new AtomicBoolean(false); + pivotBlockSelector.check( + (____, isNewPivotBlock) -> { + if (isNewPivotBlock) { + foundNewPivotBlock.set(true); + } + }); + + final boolean isNewPivotBlockFound = foundNewPivotBlock.get(); + final boolean isBlockchainCaughtUp = + snapSyncState.isWaitingBlockchain() + && !pivotBlockSelector.isBlockchainBehind(); + + if (snapSyncState.isHealTrieInProgress() + && (isNewPivotBlockFound || isBlockchainCaughtUp)) { + snapSyncState.setWaitingBlockchain(false); + reloadTrieHeal(); + } + }); } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldStateDownloadProcess.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldStateDownloadProcess.java index c19ae6facc7..7af298bde4d 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldStateDownloadProcess.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldStateDownloadProcess.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldStateDownloader.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldStateDownloader.java index 91bdd83a0f3..fc9d0d8ef10 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldStateDownloader.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldStateDownloader.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,12 +14,11 @@ */ package org.hyperledger.besu.ethereum.eth.sync.snapsync; -import static org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapsyncMetricsManager.Step.DOWNLOAD; +import static org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncMetricsManager.Step.DOWNLOAD; import static org.hyperledger.besu.ethereum.eth.sync.snapsync.request.SnapDataRequest.createAccountRangeDataRequest; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ProtocolContext; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.sync.fastsync.FastSyncActions; @@ -28,17 +27,20 @@ import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.AccountRangeDataRequest; import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.SnapDataRequest; import org.hyperledger.besu.ethereum.eth.sync.worldstate.WorldStateDownloader; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.trie.RangeManager; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.metrics.BesuMetricCategory; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.services.tasks.InMemoryTasksPriorityQueues; import java.time.Clock; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; @@ -63,25 +65,27 @@ public class SnapWorldStateDownloader implements WorldStateDownloader { private final int maxOutstandingRequests; private final int maxNodeRequestsWithoutProgress; private final ProtocolContext protocolContext; - private final WorldStateStorage worldStateStorage; + private final WorldStateStorageCoordinator worldStateStorageCoordinator; private final AtomicReference downloadState = new AtomicReference<>(); + private final SyncDurationMetrics syncDurationMetrics; public SnapWorldStateDownloader( final EthContext ethContext, final SnapSyncStatePersistenceManager snapContext, final ProtocolContext protocolContext, - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final InMemoryTasksPriorityQueues snapTaskCollection, final SnapSyncConfiguration snapSyncConfiguration, final int maxOutstandingRequests, final int maxNodeRequestsWithoutProgress, final long minMillisBeforeStalling, final Clock clock, - final MetricsSystem metricsSystem) { + final MetricsSystem metricsSystem, + final SyncDurationMetrics syncDurationMetrics) { this.ethContext = ethContext; this.protocolContext = protocolContext; - this.worldStateStorage = worldStateStorage; + this.worldStateStorageCoordinator = worldStateStorageCoordinator; this.snapContext = snapContext; this.snapTaskCollection = snapTaskCollection; this.snapSyncConfiguration = snapSyncConfiguration; @@ -90,6 +94,7 @@ public SnapWorldStateDownloader( this.minMillisBeforeStalling = minMillisBeforeStalling; this.clock = clock; this.metricsSystem = metricsSystem; + this.syncDurationMetrics = syncDurationMetrics; metricsSystem.createIntegerGauge( BesuMetricCategory.SYNCHRONIZER, @@ -133,12 +138,12 @@ public CompletableFuture run( stateRoot, snapTaskCollection.size()); - final SnapsyncMetricsManager snapsyncMetricsManager = - new SnapsyncMetricsManager(metricsSystem, ethContext); + final SnapSyncMetricsManager snapsyncMetricsManager = + new SnapSyncMetricsManager(metricsSystem, ethContext); final SnapWorldDownloadState newDownloadState = new SnapWorldDownloadState( - worldStateStorage, + worldStateStorageCoordinator, snapContext, protocolContext.getBlockchain(), snapSyncState, @@ -146,14 +151,16 @@ public CompletableFuture run( maxNodeRequestsWithoutProgress, minMillisBeforeStalling, snapsyncMetricsManager, - clock); + clock, + ethContext, + syncDurationMetrics); final Map ranges = RangeManager.generateAllRanges(16); snapsyncMetricsManager.initRange(ranges); final List currentAccountRange = snapContext.getCurrentAccountRange(); - final HashSet inconsistentAccounts = snapContext.getAccountsHealingList(); + final Set inconsistentAccounts = snapContext.getAccountsHealingList(); if (!currentAccountRange.isEmpty()) { // continue to download worldstate ranges newDownloadState.setAccountsHealingList(inconsistentAccounts); @@ -167,20 +174,21 @@ public CompletableFuture run( }); } else if (!snapContext.getAccountsHealingList().isEmpty()) { // restart only the heal step snapSyncState.setHealTrieStatus(true); - worldStateStorage.clearFlatDatabase(); - worldStateStorage.clearTrieLog(); + worldStateStorageCoordinator.applyOnMatchingStrategy( + DataStorageFormat.BONSAI, + strategy -> { + BonsaiWorldStateKeyValueStorage onBonsai = (BonsaiWorldStateKeyValueStorage) strategy; + onBonsai.clearFlatDatabase(); + onBonsai.clearTrieLog(); + }); + newDownloadState.setAccountsHealingList(inconsistentAccounts); newDownloadState.enqueueRequest( SnapDataRequest.createAccountTrieNodeDataRequest( stateRoot, Bytes.EMPTY, snapContext.getAccountsHealingList())); } else { // start from scratch - worldStateStorage.clear(); - // we have to upgrade to full flat db mode if we are in bonsai mode - if (worldStateStorage.getDataStorageFormat().equals(DataStorageFormat.BONSAI) - && snapSyncConfiguration.isFlatDbHealingEnabled()) { - ((BonsaiWorldStateKeyValueStorage) worldStateStorage).upgradeToFullFlatDbMode(); - } + worldStateStorageCoordinator.clear(); ranges.forEach( (key, value) -> newDownloadState.enqueueRequest( @@ -205,7 +213,7 @@ public CompletableFuture run( .dynamicPivotBlockSelector(dynamicPivotBlockManager) .loadLocalDataStep( new LoadLocalDataStep( - worldStateStorage, + worldStateStorageCoordinator, newDownloadState, snapSyncConfiguration, metricsSystem, @@ -213,14 +221,17 @@ public CompletableFuture run( .requestDataStep( new RequestDataStep( ethContext, - worldStateStorage, + worldStateStorageCoordinator, snapSyncState, newDownloadState, snapSyncConfiguration, metricsSystem)) .persistDataStep( new PersistDataStep( - snapSyncState, worldStateStorage, newDownloadState, snapSyncConfiguration)) + snapSyncState, + worldStateStorageCoordinator, + newDownloadState, + snapSyncConfiguration)) .completeTaskStep(maybeCompleteTask.get()) .downloadState(newDownloadState) .fastSyncState(snapSyncState) diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapsyncMetricsManager.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapsyncMetricsManager.java deleted file mode 100644 index 25af2e4262e..00000000000 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapsyncMetricsManager.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright contributors to Hyperledger Besu - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.eth.sync.snapsync; - -import static io.netty.util.internal.ObjectUtil.checkNonEmpty; -import static org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapsyncMetricsManager.Step.HEAL_TRIE; - -import org.hyperledger.besu.ethereum.eth.manager.EthContext; -import org.hyperledger.besu.metrics.BesuMetricCategory; -import org.hyperledger.besu.plugin.services.MetricsSystem; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.math.MathContext; -import java.math.RoundingMode; -import java.time.Duration; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; - -import org.apache.tuweni.bytes.Bytes32; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** Manages the metrics related to the SnapSync process. */ -public class SnapsyncMetricsManager { - - private static final Logger LOG = LoggerFactory.getLogger(SnapsyncMetricsManager.class); - private static final long PRINT_DELAY = TimeUnit.MINUTES.toMillis(1); - - private final MetricsSystem metricsSystem; - private final EthContext ethContext; - - /** Represents the progress status of the snapsync process. */ - private final AtomicReference percentageProgress; - - /** - * Represents the number of accounts downloaded during the initial step of the snapsync process. - */ - private final AtomicLong nbAccountsDownloaded; - - /** Represents the number of slots downloaded during the initial step of the snapsync process. */ - private final AtomicLong nbSlotsDownloaded; - - /** Represents the number of code entries downloaded. */ - private final AtomicLong nbCodes; - - /** - * Represents the number of trie nodes generated during the initial step of the snapsync process. - */ - private final AtomicLong nbTrieNodesGenerated; - - /** Represents the number of flat accounts healed during the healing process. */ - private final AtomicLong nbFlatAccountsHealed; - - /** Represents the number of flat slots healed during the healing process. */ - private final AtomicLong nbFlatSlotsHealed; - - /** Represents the number of trie nodes healed during the healing process. */ - private final AtomicLong nbTrieNodesHealed; - - private long startSyncTime; - - private final Map lastRangeIndex = new HashMap<>(); - - private long lastNotifyTimestamp; - - public SnapsyncMetricsManager(final MetricsSystem metricsSystem, final EthContext ethContext) { - this.metricsSystem = metricsSystem; - this.ethContext = ethContext; - percentageProgress = new AtomicReference<>(new BigDecimal(0)); - nbAccountsDownloaded = new AtomicLong(0); - nbSlotsDownloaded = new AtomicLong(0); - nbCodes = new AtomicLong(0); - nbTrieNodesGenerated = new AtomicLong(0); - nbFlatAccountsHealed = new AtomicLong(0); - nbFlatSlotsHealed = new AtomicLong(0); - nbTrieNodesHealed = new AtomicLong(0); - metricsSystem.createLongGauge( - BesuMetricCategory.SYNCHRONIZER, - "snap_world_state_generated_nodes_total", - "Total number of data nodes generated as part of snap sync world state download", - nbTrieNodesGenerated::get); - metricsSystem.createLongGauge( - BesuMetricCategory.SYNCHRONIZER, - "snap_world_state_healed_nodes_total", - "Total number of data nodes healed as part of snap sync world state heal process", - nbTrieNodesHealed::get); - metricsSystem.createLongGauge( - BesuMetricCategory.SYNCHRONIZER, - "snap_world_state_accounts_total", - "Total number of accounts downloaded as part of snap sync world state", - nbAccountsDownloaded::get); - metricsSystem.createLongGauge( - BesuMetricCategory.SYNCHRONIZER, - "snap_world_state_slots_total", - "Total number of slots downloaded as part of snap sync world state", - nbSlotsDownloaded::get); - metricsSystem.createLongGauge( - BesuMetricCategory.SYNCHRONIZER, - "snap_world_state_flat_accounts_healed_total", - "Total number of accounts healed in the flat database as part of snap sync world state", - nbFlatAccountsHealed::get); - metricsSystem.createLongGauge( - BesuMetricCategory.SYNCHRONIZER, - "snap_world_state_flat_slots_healed_total", - "Total number of slots healed in the flat database as part of snap sync world state", - nbFlatSlotsHealed::get); - metricsSystem.createLongGauge( - BesuMetricCategory.SYNCHRONIZER, - "snap_world_state_codes_total", - "Total number of codes downloaded as part of snap sync world state", - nbCodes::get); - } - - public void initRange(final Map ranges) { - for (Map.Entry entry : ranges.entrySet()) { - this.lastRangeIndex.put(entry.getValue(), entry.getKey().toUnsignedBigInteger()); - } - this.startSyncTime = System.currentTimeMillis(); - this.lastNotifyTimestamp = startSyncTime; - } - - public void notifyRangeProgress( - final Step step, final Bytes32 startKeyHash, final Bytes32 endKeyHash) { - checkNonEmpty(lastRangeIndex, "snapsync range collection"); - if (lastRangeIndex.containsKey(endKeyHash)) { - final BigInteger lastPos = lastRangeIndex.get(endKeyHash); - final BigInteger newPos = startKeyHash.toUnsignedBigInteger(); - percentageProgress.getAndAccumulate( - BigDecimal.valueOf(100) - .multiply(new BigDecimal(newPos.subtract(lastPos))) - .divide( - new BigDecimal(RangeManager.MAX_RANGE.toUnsignedBigInteger()), - MathContext.DECIMAL32), - BigDecimal::add); - lastRangeIndex.put(endKeyHash, newPos); - print(step); - } - } - - public void notifyAccountsDownloaded(final long nbAccounts) { - this.nbAccountsDownloaded.getAndAdd(nbAccounts); - } - - public void notifySlotsDownloaded(final long nbSlots) { - this.nbSlotsDownloaded.getAndAdd(nbSlots); - } - - public void notifyCodeDownloaded() { - this.nbCodes.getAndIncrement(); - } - - public void notifyNodesGenerated(final long nbNodes) { - this.nbTrieNodesGenerated.getAndAdd(nbNodes); - } - - public void notifyTrieNodesHealed(final long nbNodes) { - this.nbTrieNodesHealed.getAndAdd(nbNodes); - print(HEAL_TRIE); - } - - private void print(final Step step) { - final long now = System.currentTimeMillis(); - if (now - lastNotifyTimestamp >= PRINT_DELAY) { - lastNotifyTimestamp = now; - int peerCount = -1; // ethContext is not available in tests - if (ethContext != null && ethContext.getEthPeers().peerCount() >= 0) { - peerCount = ethContext.getEthPeers().peerCount(); - } - switch (step) { - case DOWNLOAD -> { - LOG.debug( - "Worldstate {} in progress accounts={}, slots={}, codes={}, nodes={}", - step.message, - nbAccountsDownloaded, - nbSlotsDownloaded, - nbCodes, - nbTrieNodesGenerated); - LOG.info( - "Worldstate {} progress: {}%, Peer count: {}", - step.message, percentageProgress.get().setScale(2, RoundingMode.HALF_UP), peerCount); - } - case HEAL_FLAT -> { - LOG.debug( - "Worldstate {} in progress accounts={}, slots={}", - step.message, - nbFlatAccountsHealed, - nbFlatSlotsHealed); - LOG.info( - "Worldstate {} progress: {}%, Peer count: {}", - step.message, percentageProgress.get().setScale(2, RoundingMode.HALF_UP), peerCount); - } - case HEAL_TRIE -> { - LOG.info( - "Healed {} world state trie nodes, Peer count: {}", - nbTrieNodesHealed.get(), - peerCount); - } - } - } - } - - public void notifySnapSyncCompleted() { - final Duration duration = Duration.ofMillis(System.currentTimeMillis() - startSyncTime); - LOG.info( - "Finished worldstate snapsync with nodes {} (healed={}) duration {}{}:{},{}.", - nbTrieNodesGenerated.addAndGet(nbTrieNodesHealed.get()), - nbTrieNodesHealed, - duration.toHoursPart() > 0 ? (duration.toHoursPart() + ":") : "", - duration.toMinutesPart(), - duration.toSecondsPart(), - duration.toMillisPart()); - } - - public MetricsSystem getMetricsSystem() { - return metricsSystem; - } - - public enum Step { - DOWNLOAD("download"), - HEAL_TRIE("trie node healing"), - HEAL_FLAT("flat database healing"); - - final String message; - - Step(final String message) { - this.message = message; - } - } -} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/StackTrie.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/StackTrie.java index 01f17eb79b4..d83ab20cc5d 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/StackTrie.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/StackTrie.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,12 +15,12 @@ package org.hyperledger.besu.ethereum.eth.sync.snapsync; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.trie.CommitVisitor; import org.hyperledger.besu.ethereum.trie.InnerNodeDiscoveryManager; import org.hyperledger.besu.ethereum.trie.MerkleTrie; import org.hyperledger.besu.ethereum.trie.Node; import org.hyperledger.besu.ethereum.trie.NodeUpdater; -import org.hyperledger.besu.ethereum.trie.SnapPutVisitor; +import org.hyperledger.besu.ethereum.trie.RangeManager; +import org.hyperledger.besu.ethereum.trie.SnapCommitVisitor; import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; import java.util.ArrayList; @@ -28,6 +28,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.NavigableMap; import java.util.Optional; import java.util.TreeMap; import java.util.concurrent.atomic.AtomicInteger; @@ -72,7 +73,9 @@ public StackTrie( } public void addElement( - final Bytes32 taskIdentifier, final List proofs, final TreeMap keys) { + final Bytes32 taskIdentifier, + final List proofs, + final NavigableMap keys) { this.elementsCount.addAndGet(keys.size()); this.elements.put( taskIdentifier, ImmutableTaskElement.builder().proofs(proofs).keys(keys).build()); @@ -111,40 +114,50 @@ public void commit(final FlatDatabaseUpdater flatDatabaseUpdater, final NodeUpda keys.putAll(taskElement.keys()); }); + if (keys.isEmpty()) { + return; // empty range we can ignore it + } + final Map proofsEntries = new HashMap<>(); for (Bytes proof : proofs) { proofsEntries.put(Hash.hash(proof), proof); } - final InnerNodeDiscoveryManager snapStoredNodeFactory = - new InnerNodeDiscoveryManager<>( - (location, hash) -> Optional.ofNullable(proofsEntries.get(hash)), - Function.identity(), - Function.identity(), - startKeyHash, - keys.lastKey(), - true); - - final MerkleTrie trie = - new StoredMerklePatriciaTrie<>( - snapStoredNodeFactory, proofs.isEmpty() ? MerkleTrie.EMPTY_TRIE_NODE_HASH : rootHash); - - for (Map.Entry entry : keys.entrySet()) { - trie.put(entry.getKey(), new SnapPutVisitor<>(snapStoredNodeFactory, entry.getValue())); - } - - keys.forEach(flatDatabaseUpdater::update); - - trie.commit( - nodeUpdater, - (new CommitVisitor<>(nodeUpdater) { - @Override - public void maybeStoreNode(final Bytes location, final Node node) { - if (!node.isHealNeeded()) { - super.maybeStoreNode(location, node); + if (!keys.isEmpty()) { + final InnerNodeDiscoveryManager snapStoredNodeFactory = + new InnerNodeDiscoveryManager<>( + (location, hash) -> Optional.ofNullable(proofsEntries.get(hash)), + Function.identity(), + Function.identity(), + startKeyHash, + proofs.isEmpty() ? RangeManager.MAX_RANGE : keys.lastKey(), + true); + + final MerkleTrie trie = + new StoredMerklePatriciaTrie<>( + snapStoredNodeFactory, + proofs.isEmpty() ? MerkleTrie.EMPTY_TRIE_NODE_HASH : rootHash); + + for (Map.Entry entry : keys.entrySet()) { + trie.put(entry.getKey(), entry.getValue()); + } + + keys.forEach(flatDatabaseUpdater::update); + + trie.commit( + nodeUpdater, + (new SnapCommitVisitor<>( + nodeUpdater, + startKeyHash, + proofs.isEmpty() ? RangeManager.MAX_RANGE : keys.lastKey()) { + @Override + public void maybeStoreNode(final Bytes location, final Node node) { + if (!node.isHealNeeded()) { + super.maybeStoreNode(location, node); + } } - } - })); + })); + } } } @@ -180,7 +193,7 @@ public List proofs() { } @Value.Default - public TreeMap keys() { + public NavigableMap keys() { return new TreeMap<>(); } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/context/SnapSyncStatePersistenceManager.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/context/SnapSyncStatePersistenceManager.java index 75ee91352ad..f31b77274e7 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/context/SnapSyncStatePersistenceManager.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/context/SnapSyncStatePersistenceManager.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -28,6 +28,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -127,7 +128,7 @@ public List getCurrentAccountRange() { .collect(Collectors.toList()); } - public HashSet getAccountsHealingList() { + public Set getAccountsHealingList() { return healContext .streamValuesFromKeysThat(notEqualsTo(SNAP_ACCOUNT_HEALING_LIST_INDEX)) .collect(Collectors.toCollection(HashSet::new)); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/AccountRangeDataRequest.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/AccountRangeDataRequest.java index 06181fd09f1..21a707e9a63 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/AccountRangeDataRequest.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/AccountRangeDataRequest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,15 +14,15 @@ */ package org.hyperledger.besu.ethereum.eth.sync.snapsync.request; -import static org.hyperledger.besu.ethereum.eth.sync.snapsync.RangeManager.MAX_RANGE; -import static org.hyperledger.besu.ethereum.eth.sync.snapsync.RangeManager.MIN_RANGE; -import static org.hyperledger.besu.ethereum.eth.sync.snapsync.RangeManager.findNewBeginElementInRange; import static org.hyperledger.besu.ethereum.eth.sync.snapsync.RequestType.ACCOUNT_RANGE; -import static org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapsyncMetricsManager.Step.DOWNLOAD; +import static org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncMetricsManager.Step.DOWNLOAD; import static org.hyperledger.besu.ethereum.eth.sync.snapsync.StackTrie.FlatDatabaseUpdater.noop; +import static org.hyperledger.besu.ethereum.trie.RangeManager.MAX_RANGE; +import static org.hyperledger.besu.ethereum.trie.RangeManager.MIN_RANGE; +import static org.hyperledger.besu.ethereum.trie.RangeManager.findNewBeginElementInRange; +import static org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator.applyForStrategy; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncConfiguration; import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncProcessState; import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapWorldDownloadState; @@ -31,17 +31,19 @@ import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.ethereum.rlp.RLPInput; import org.hyperledger.besu.ethereum.trie.NodeUpdater; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.worldstate.FlatDbMode; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage.Updater; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.NavigableMap; import java.util.Optional; -import java.util.TreeMap; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; import com.google.common.annotations.VisibleForTesting; @@ -56,12 +58,12 @@ public class AccountRangeDataRequest extends SnapDataRequest { private static final Logger LOG = LoggerFactory.getLogger(AccountRangeDataRequest.class); - private final Bytes32 startKeyHash; - private final Bytes32 endKeyHash; - private final Optional startStorageRange; - private final Optional endStorageRange; + protected final Bytes32 startKeyHash; + protected final Bytes32 endKeyHash; + protected final Optional startStorageRange; + protected final Optional endStorageRange; - private final StackTrie stackTrie; + protected final StackTrie stackTrie; private Optional isProofValid; protected AccountRangeDataRequest( @@ -104,8 +106,8 @@ protected AccountRangeDataRequest( @Override protected int doPersist( - final WorldStateStorage worldStateStorage, - final Updater updater, + final WorldStateStorageCoordinator worldStateStorageCoordinator, + final WorldStateKeyValueStorage.Updater updater, final SnapWorldDownloadState downloadState, final SnapSyncProcessState snapSyncState, final SnapSyncConfiguration snapSyncConfiguration) { @@ -120,19 +122,31 @@ protected int doPersist( final AtomicInteger nbNodesSaved = new AtomicInteger(); final NodeUpdater nodeUpdater = (location, hash, value) -> { - updater.putAccountStateTrieNode(location, hash, value); + applyForStrategy( + updater, + onBonsai -> { + onBonsai.putAccountStateTrieNode(location, hash, value); + }, + onForest -> { + onForest.putAccountStateTrieNode(hash, value); + }); nbNodesSaved.getAndIncrement(); }; - StackTrie.FlatDatabaseUpdater flatDatabaseUpdater = noop(); - if (worldStateStorage.getFlatDbMode().equals(FlatDbMode.FULL)) { - // we have a flat DB only with Bonsai - flatDatabaseUpdater = - (key, value) -> - ((BonsaiWorldStateKeyValueStorage.BonsaiUpdater) updater) - .putAccountInfoState(Hash.wrap(key), value); - } - stackTrie.commit(flatDatabaseUpdater, nodeUpdater); + final AtomicReference flatDatabaseUpdater = + new AtomicReference<>(noop()); + + // we have a flat DB only with Bonsai + worldStateStorageCoordinator.applyOnMatchingFlatMode( + FlatDbMode.FULL, + bonsaiWorldStateStorageStrategy -> { + flatDatabaseUpdater.set( + (key, value) -> + ((BonsaiWorldStateKeyValueStorage.Updater) updater) + .putAccountInfoState(Hash.wrap(key), value)); + }); + + stackTrie.commit(flatDatabaseUpdater.get(), nodeUpdater); downloadState.getMetricsManager().notifyAccountsDownloaded(stackTrie.getElementsCount().get()); @@ -141,15 +155,27 @@ protected int doPersist( public void addResponse( final WorldStateProofProvider worldStateProofProvider, - final TreeMap accounts, + final NavigableMap accounts, final ArrayDeque proofs) { if (!accounts.isEmpty() || !proofs.isEmpty()) { if (!worldStateProofProvider.isValidRangeProof( startKeyHash, endKeyHash, getRootHash(), proofs, accounts)) { + // this happens on repivot and on bad proofs + LOG.atTrace() + .setMessage("invalid range proof received for account range {} {}") + .addArgument(accounts.firstKey()) + .addArgument(accounts.lastKey()) + .log(); isProofValid = Optional.of(false); } else { stackTrie.addElement(startKeyHash, proofs, accounts); isProofValid = Optional.of(true); + LOG.atDebug() + .setMessage("{} accounts received during sync for account range {} {}") + .addArgument(accounts.size()) + .addArgument(startKeyHash) + .addArgument(endKeyHash) + .log(); } } } @@ -162,7 +188,7 @@ public boolean isResponseReceived() { @Override public Stream getChildRequests( final SnapWorldDownloadState downloadState, - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final SnapSyncProcessState snapSyncState) { final List childRequests = new ArrayList<>(); @@ -212,7 +238,7 @@ public Bytes32 getEndKeyHash() { } @VisibleForTesting - public TreeMap getAccounts() { + public NavigableMap getAccounts() { return stackTrie.getElement(startKeyHash).keys(); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/BytecodeRequest.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/BytecodeRequest.java index 5db5ec0211e..3b434d45296 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/BytecodeRequest.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/BytecodeRequest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,14 +15,15 @@ package org.hyperledger.besu.ethereum.eth.sync.snapsync.request; import static org.hyperledger.besu.ethereum.eth.sync.snapsync.RequestType.BYTECODES; +import static org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator.applyForStrategy; import static org.slf4j.LoggerFactory.getLogger; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncConfiguration; import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncProcessState; import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapWorldDownloadState; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage.Updater; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import java.util.stream.Stream; @@ -51,18 +52,26 @@ protected BytecodeRequest( @Override protected int doPersist( - final WorldStateStorage worldStateStorage, - final Updater updater, + final WorldStateStorageCoordinator worldStateStorageCoordinator, + final WorldStateKeyValueStorage.Updater updater, final SnapWorldDownloadState downloadState, final SnapSyncProcessState snapSyncState, final SnapSyncConfiguration snapSyncConfiguration) { - updater.putCode(Hash.wrap(accountHash), code); + + applyForStrategy( + updater, + onBonsai -> { + onBonsai.putCode(Hash.wrap(accountHash), Hash.wrap(codeHash), code); + }, + onForest -> { + onForest.putCode(codeHash, code); + }); downloadState.getMetricsManager().notifyCodeDownloaded(); return possibleParent .map( trieNodeDataRequest -> trieNodeDataRequest.saveParent( - worldStateStorage, + worldStateStorageCoordinator, updater, downloadState, snapSyncState, @@ -79,7 +88,7 @@ public boolean isResponseReceived() { @Override public Stream getChildRequests( final SnapWorldDownloadState downloadState, - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final SnapSyncProcessState snapSyncState) { return Stream.empty(); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/SnapDataRequest.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/SnapDataRequest.java index 76d2d9fe596..7bcf08fe6c3 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/SnapDataRequest.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/SnapDataRequest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -27,11 +27,12 @@ import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.heal.StorageTrieNodeHealingRequest; import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.heal.TrieNodeHealingRequest; import org.hyperledger.besu.ethereum.eth.sync.worldstate.WorldStateDownloaderException; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.services.tasks.TasksPriorityProvider; -import java.util.HashSet; import java.util.Optional; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; @@ -92,7 +93,7 @@ public static StorageRangeDataRequest createStorageRangeDataRequest( } public static AccountTrieNodeHealingRequest createAccountTrieNodeDataRequest( - final Hash hash, final Bytes location, final HashSet inconsistentAccounts) { + final Hash hash, final Bytes location, final Set inconsistentAccounts) { return new AccountTrieNodeHealingRequest(hash, hash, location, inconsistentAccounts); } @@ -100,7 +101,7 @@ public static AccountTrieNodeHealingRequest createAccountTrieNodeDataRequest( final Hash hash, final Hash rootHash, final Bytes location, - final HashSet inconsistentAccounts) { + final Set inconsistentAccounts) { return new AccountTrieNodeHealingRequest(hash, rootHash, location, inconsistentAccounts); } @@ -115,18 +116,18 @@ public static BytecodeRequest createBytecodeRequest( } public int persist( - final WorldStateStorage worldStateStorage, - final WorldStateStorage.Updater updater, + final WorldStateStorageCoordinator worldStateStorageCoordinator, + final WorldStateKeyValueStorage.Updater updater, final SnapWorldDownloadState downloadState, final SnapSyncProcessState snapSyncState, final SnapSyncConfiguration snapSyncConfiguration) { return doPersist( - worldStateStorage, updater, downloadState, snapSyncState, snapSyncConfiguration); + worldStateStorageCoordinator, updater, downloadState, snapSyncState, snapSyncConfiguration); } protected abstract int doPersist( - final WorldStateStorage worldStateStorage, - final WorldStateStorage.Updater updater, + final WorldStateStorageCoordinator worldStateStorageCoordinator, + final WorldStateKeyValueStorage.Updater updater, final SnapWorldDownloadState downloadState, final SnapSyncProcessState snapSyncState, final SnapSyncConfiguration snapSyncConfiguration); @@ -139,7 +140,7 @@ public boolean isExpired(final SnapSyncProcessState snapSyncState) { public abstract Stream getChildRequests( final SnapWorldDownloadState downloadState, - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final SnapSyncProcessState snapSyncState); public void registerParent(final TrieNodeHealingRequest parent) { @@ -156,14 +157,18 @@ protected int incrementChildren() { } protected int saveParent( - final WorldStateStorage worldStateStorage, - final WorldStateStorage.Updater updater, + final WorldStateStorageCoordinator worldStateStorageCoordinator, + final WorldStateKeyValueStorage.Updater updater, final SnapWorldDownloadState downloadState, final SnapSyncProcessState snapSyncState, final SnapSyncConfiguration snapSyncConfiguration) { if (pendingChildren.decrementAndGet() == 0) { return persist( - worldStateStorage, updater, downloadState, snapSyncState, snapSyncConfiguration); + worldStateStorageCoordinator, + updater, + downloadState, + snapSyncState, + snapSyncConfiguration); } return 0; } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/StorageRangeDataRequest.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/StorageRangeDataRequest.java index 14839f0ad6f..14f92e2c3f9 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/StorageRangeDataRequest.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/StorageRangeDataRequest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,16 +14,15 @@ */ package org.hyperledger.besu.ethereum.eth.sync.snapsync.request; -import static org.hyperledger.besu.ethereum.eth.sync.snapsync.RangeManager.MAX_RANGE; -import static org.hyperledger.besu.ethereum.eth.sync.snapsync.RangeManager.MIN_RANGE; -import static org.hyperledger.besu.ethereum.eth.sync.snapsync.RangeManager.findNewBeginElementInRange; -import static org.hyperledger.besu.ethereum.eth.sync.snapsync.RangeManager.getRangeCount; import static org.hyperledger.besu.ethereum.eth.sync.snapsync.RequestType.STORAGE_RANGE; import static org.hyperledger.besu.ethereum.eth.sync.snapsync.StackTrie.FlatDatabaseUpdater.noop; +import static org.hyperledger.besu.ethereum.trie.RangeManager.MAX_RANGE; +import static org.hyperledger.besu.ethereum.trie.RangeManager.MIN_RANGE; +import static org.hyperledger.besu.ethereum.trie.RangeManager.findNewBeginElementInRange; +import static org.hyperledger.besu.ethereum.trie.RangeManager.getRangeCount; +import static org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator.applyForStrategy; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.eth.sync.snapsync.RangeManager; import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncConfiguration; import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncProcessState; import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapWorldDownloadState; @@ -31,15 +30,18 @@ import org.hyperledger.besu.ethereum.proof.WorldStateProofProvider; import org.hyperledger.besu.ethereum.trie.CompactEncoding; import org.hyperledger.besu.ethereum.trie.NodeUpdater; +import org.hyperledger.besu.ethereum.trie.RangeManager; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.worldstate.FlatDbMode; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage.Updater; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import java.util.ArrayList; import java.util.List; +import java.util.NavigableMap; import java.util.Optional; -import java.util.TreeMap; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; import com.google.common.annotations.VisibleForTesting; @@ -60,7 +62,7 @@ public class StorageRangeDataRequest extends SnapDataRequest { private final Bytes32 startKeyHash; private final Bytes32 endKeyHash; - private StackTrie stackTrie; + private final StackTrie stackTrie; private Optional isProofValid; protected StorageRangeDataRequest( @@ -75,7 +77,7 @@ protected StorageRangeDataRequest( this.startKeyHash = startKeyHash; this.endKeyHash = endKeyHash; this.isProofValid = Optional.empty(); - addStackTrie(Optional.empty()); + this.stackTrie = new StackTrie(Hash.wrap(getStorageRoot()), startKeyHash); LOG.trace( "create get storage range data request for account {} with root hash={} from {} to {}", accountHash, @@ -86,8 +88,8 @@ protected StorageRangeDataRequest( @Override protected int doPersist( - final WorldStateStorage worldStateStorage, - final Updater updater, + final WorldStateStorageCoordinator worldStateStorageCoordinator, + final WorldStateKeyValueStorage.Updater updater, final SnapWorldDownloadState downloadState, final SnapSyncProcessState snapSyncState, final SnapSyncConfiguration snapSyncConfiguration) { @@ -96,20 +98,28 @@ protected int doPersist( final AtomicInteger nbNodesSaved = new AtomicInteger(); final NodeUpdater nodeUpdater = (location, hash, value) -> { - updater.putAccountStorageTrieNode(accountHash, location, hash, value); + applyForStrategy( + updater, + onBonsai -> onBonsai.putAccountStorageTrieNode(accountHash, location, hash, value), + onForest -> onForest.putAccountStorageTrieNode(hash, value)); + nbNodesSaved.incrementAndGet(); }; - StackTrie.FlatDatabaseUpdater flatDatabaseUpdater = noop(); - if (worldStateStorage.getFlatDbMode().equals(FlatDbMode.FULL)) { - // we have a flat DB only with Bonsai - flatDatabaseUpdater = - (key, value) -> - ((BonsaiWorldStateKeyValueStorage.Updater) updater) - .putStorageValueBySlotHash( - accountHash, Hash.wrap(key), Bytes32.leftPad(RLP.decodeValue(value))); - } + final AtomicReference flatDatabaseUpdater = + new AtomicReference<>(noop()); + + // we have a flat DB only with Bonsai + worldStateStorageCoordinator.applyOnMatchingFlatMode( + FlatDbMode.FULL, + bonsaiWorldStateStorageStrategy -> { + flatDatabaseUpdater.set( + (key, value) -> + ((BonsaiWorldStateKeyValueStorage.Updater) updater) + .putStorageValueBySlotHash( + accountHash, Hash.wrap(key), Bytes32.leftPad(RLP.decodeValue(value)))); + }); - stackTrie.commit(flatDatabaseUpdater, nodeUpdater); + stackTrie.commit(flatDatabaseUpdater.get(), nodeUpdater); downloadState.getMetricsManager().notifySlotsDownloaded(stackTrie.getElementsCount().get()); @@ -119,13 +129,20 @@ protected int doPersist( public void addResponse( final SnapWorldDownloadState downloadState, final WorldStateProofProvider worldStateProofProvider, - final TreeMap slots, + final NavigableMap slots, final ArrayDeque proofs) { if (!slots.isEmpty() || !proofs.isEmpty()) { if (!worldStateProofProvider.isValidRangeProof( startKeyHash, endKeyHash, storageRoot, proofs, slots)) { // If the proof is invalid, it means that the storage will be a mix of several blocks. // Therefore, it will be necessary to heal the account's storage subsequently + LOG.atDebug() + .setMessage("invalid storage range proof received for account hash {} range {} {}") + .addArgument(() -> accountHash) + .addArgument(() -> slots.isEmpty() ? "none" : slots.firstKey()) + .addArgument(() -> slots.isEmpty() ? "none" : slots.lastKey()) + .log(); + downloadState.addAccountToHealingList(CompactEncoding.bytesToPath(accountHash)); // We will request the new storage root of the account because it is apparently no longer // valid with the new pivot block. @@ -153,7 +170,7 @@ public boolean isExpired(final SnapSyncProcessState snapSyncState) { @Override public Stream getChildRequests( final SnapWorldDownloadState downloadState, - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final SnapSyncProcessState snapSyncState) { final List childRequests = new ArrayList<>(); @@ -173,7 +190,6 @@ public Stream getChildRequests( final StorageRangeDataRequest storageRangeDataRequest = createStorageRangeDataRequest( getRootHash(), accountHash, storageRoot, key, value); - storageRangeDataRequest.addStackTrie(Optional.of(stackTrie)); childRequests.add(storageRangeDataRequest); }); if (startKeyHash.equals(MIN_RANGE) && endKeyHash.equals(MAX_RANGE)) { @@ -193,7 +209,7 @@ public Bytes32 getStorageRoot() { return storageRoot; } - public TreeMap getSlots() { + public NavigableMap getSlots() { return stackTrie.getElement(startKeyHash).keys(); } @@ -215,11 +231,4 @@ public void clear() { public void setProofValid(final boolean isProofValid) { this.isProofValid = Optional.of(isProofValid); } - - public void addStackTrie(final Optional maybeStackTrie) { - stackTrie = - maybeStackTrie - .filter(StackTrie::addSegment) - .orElse(new StackTrie(Hash.wrap(getStorageRoot()), 1, 3, startKeyHash)); - } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/AccountFlatDatabaseHealingRangeRequest.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/AccountFlatDatabaseHealingRangeRequest.java index a40a4fb11a3..7e66fa7d88a 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/AccountFlatDatabaseHealingRangeRequest.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/AccountFlatDatabaseHealingRangeRequest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,13 +14,11 @@ */ package org.hyperledger.besu.ethereum.eth.sync.snapsync.request.heal; -import static org.hyperledger.besu.ethereum.eth.sync.snapsync.RangeManager.MAX_RANGE; -import static org.hyperledger.besu.ethereum.eth.sync.snapsync.RangeManager.MIN_RANGE; -import static org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapsyncMetricsManager.Step.HEAL_FLAT; +import static org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncMetricsManager.Step.HEAL_FLAT; +import static org.hyperledger.besu.ethereum.trie.RangeManager.MAX_RANGE; +import static org.hyperledger.besu.ethereum.trie.RangeManager.MIN_RANGE; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.eth.sync.snapsync.RangeManager; import org.hyperledger.besu.ethereum.eth.sync.snapsync.RequestType; import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncConfiguration; import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncProcessState; @@ -30,16 +28,20 @@ import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.ethereum.trie.CompactEncoding; import org.hyperledger.besu.ethereum.trie.MerkleTrie; +import org.hyperledger.besu.ethereum.trie.RangeManager; import org.hyperledger.besu.ethereum.trie.RangeStorageEntriesCollector; import org.hyperledger.besu.ethereum.trie.TrieIterator; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import java.math.BigInteger; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.NavigableMap; import java.util.TreeMap; import java.util.function.Function; import java.util.stream.Stream; @@ -57,9 +59,9 @@ public class AccountFlatDatabaseHealingRangeRequest extends SnapDataRequest { private final Bytes32 startKeyHash; private final Bytes32 endKeyHash; - private TreeMap existingAccounts; + private NavigableMap existingAccounts; - private TreeMap flatDbAccounts; + private NavigableMap flatDbAccounts; private boolean isProofValid; public AccountFlatDatabaseHealingRangeRequest( @@ -75,7 +77,7 @@ public AccountFlatDatabaseHealingRangeRequest( @Override public Stream getChildRequests( final SnapWorldDownloadState downloadState, - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final SnapSyncProcessState snapSyncState) { final List childRequests = new ArrayList<>(); if (!existingAccounts.isEmpty()) { @@ -131,7 +133,7 @@ public boolean isResponseReceived() { public void addLocalData( final WorldStateProofProvider worldStateProofProvider, - final TreeMap accounts, + final NavigableMap accounts, final ArrayDeque proofs) { if (!accounts.isEmpty() && !proofs.isEmpty()) { // very proof in order to check if the local flat database is valid or not @@ -144,8 +146,8 @@ public void addLocalData( @Override protected int doPersist( - final WorldStateStorage worldStateStorage, - final WorldStateStorage.Updater updater, + final WorldStateStorageCoordinator worldStateStorageCoordinator, + final WorldStateKeyValueStorage.Updater updater, final SnapWorldDownloadState downloadState, final SnapSyncProcessState snapSyncState, final SnapSyncConfiguration syncConfig) { @@ -157,7 +159,7 @@ protected int doPersist( final MerkleTrie accountTrie = new StoredMerklePatriciaTrie<>( - worldStateStorage::getAccountStateTrieNode, + worldStateStorageCoordinator::getAccountStateTrieNode, getRootHash(), Function.identity(), Function.identity()); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/AccountTrieNodeHealingRequest.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/AccountTrieNodeHealingRequest.java index d9297217146..be6060d52ef 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/AccountTrieNodeHealingRequest.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/AccountTrieNodeHealingRequest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,9 +15,9 @@ package org.hyperledger.besu.ethereum.eth.sync.snapsync.request.heal; import static org.hyperledger.besu.ethereum.eth.sync.snapsync.request.SnapDataRequest.createAccountTrieNodeDataRequest; +import static org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator.applyForStrategy; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncConfiguration; import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncProcessState; import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapWorldDownloadState; @@ -26,14 +26,15 @@ import org.hyperledger.besu.ethereum.trie.CompactEncoding; import org.hyperledger.besu.ethereum.trie.MerkleTrie; import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; -import org.hyperledger.besu.ethereum.worldstate.FlatDbMode; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.function.Function; import java.util.stream.Stream; @@ -43,35 +44,42 @@ /** Represents a healing request for an account trie node. */ public class AccountTrieNodeHealingRequest extends TrieNodeHealingRequest { - private final HashSet inconsistentAccounts; + private final Set inconsistentAccounts; public AccountTrieNodeHealingRequest( final Hash hash, final Hash originalRootHash, final Bytes location, - final HashSet inconsistentAccounts) { + final Set inconsistentAccounts) { super(hash, originalRootHash, location); this.inconsistentAccounts = inconsistentAccounts; } @Override protected int doPersist( - final WorldStateStorage worldStateStorage, - final WorldStateStorage.Updater updater, + final WorldStateStorageCoordinator worldStateStorageCoordinator, + final WorldStateKeyValueStorage.Updater updater, final SnapWorldDownloadState downloadState, final SnapSyncProcessState snapSyncState, final SnapSyncConfiguration snapSyncConfiguration) { if (isRoot()) { downloadState.setRootNodeData(data); } - updater.putAccountStateTrieNode(getLocation(), getNodeHash(), data); + applyForStrategy( + updater, + onBonsai -> { + onBonsai.putAccountStateTrieNode(getLocation(), getNodeHash(), data); + }, + onForest -> { + onForest.putAccountStateTrieNode(getNodeHash(), data); + }); return 1; } @Override public Optional getExistingData( - final SnapWorldDownloadState downloadState, final WorldStateStorage worldStateStorage) { - return worldStateStorage + final WorldStateStorageCoordinator worldStateStorageCoordinator) { + return worldStateStorageCoordinator .getAccountStateTrieNode(getLocation(), getNodeHash()) .filter(data -> !getLocation().isEmpty()); } @@ -82,7 +90,7 @@ protected SnapDataRequest createChildNodeDataRequest(final Hash childHash, final childHash, getRootHash(), location, getSubLocation(location)); } - private HashSet getSubLocation(final Bytes location) { + private Set getSubLocation(final Bytes location) { final HashSet foundAccountsToHeal = new HashSet<>(); for (Bytes account : inconsistentAccounts) { if (account.commonPrefixLength(location) == location.size()) { @@ -93,11 +101,12 @@ private HashSet getSubLocation(final Bytes location) { } @Override - public Stream getRootStorageRequests(final WorldStateStorage worldStateStorage) { + public Stream getRootStorageRequests( + final WorldStateStorageCoordinator worldStateStorageCoordinator) { final List requests = new ArrayList<>(); final StoredMerklePatriciaTrie accountTrie = new StoredMerklePatriciaTrie<>( - worldStateStorage::getAccountStateTrieNode, + worldStateStorageCoordinator::getAccountStateTrieNode, Hash.hash(data), getLocation(), Function.identity(), @@ -137,7 +146,7 @@ public Stream getRootStorageRequests(final WorldStateStorage wo @Override protected Stream getRequestsFromTrieNodeValue( - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final SnapWorldDownloadState downloadState, final Bytes location, final Bytes path, @@ -151,11 +160,10 @@ protected Stream getRequestsFromTrieNodeValue( Bytes32.wrap(CompactEncoding.pathToBytes(Bytes.concatenate(getLocation(), path)))); // update the flat db only for bonsai - if (!worldStateStorage.getFlatDbMode().equals(FlatDbMode.NO_FLATTENED)) { - ((BonsaiWorldStateKeyValueStorage.Updater) worldStateStorage.updater()) - .putAccountInfoState(accountHash, value) - .commit(); - } + worldStateStorageCoordinator.applyWhenFlatModeEnabled( + onBonsai -> { + onBonsai.updater().putAccountInfoState(accountHash, value).commit(); + }); // Add code, if appropriate if (!accountValue.getCodeHash().equals(Hash.EMPTY)) { @@ -164,7 +172,7 @@ protected Stream getRequestsFromTrieNodeValue( // Retrieve the storage root from the database, if available final Hash storageRootFoundInDb = - worldStateStorage + worldStateStorageCoordinator .getTrieNodeUnsafe(Bytes.concatenate(accountHash, Bytes.EMPTY)) .map(Hash::hash) .orElse(Hash.wrap(MerkleTrie.EMPTY_TRIE_NODE_HASH)); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/StorageFlatDatabaseHealingRangeRequest.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/StorageFlatDatabaseHealingRangeRequest.java index a94424f023f..03df0e5e731 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/StorageFlatDatabaseHealingRangeRequest.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/StorageFlatDatabaseHealingRangeRequest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,27 +14,29 @@ */ package org.hyperledger.besu.ethereum.eth.sync.snapsync.request.heal; -import static org.hyperledger.besu.ethereum.eth.sync.snapsync.RangeManager.getRangeCount; import static org.hyperledger.besu.ethereum.eth.sync.snapsync.RequestType.STORAGE_RANGE; +import static org.hyperledger.besu.ethereum.trie.RangeManager.getRangeCount; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.eth.sync.snapsync.RangeManager; import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncConfiguration; import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncProcessState; import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapWorldDownloadState; import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.SnapDataRequest; import org.hyperledger.besu.ethereum.proof.WorldStateProofProvider; import org.hyperledger.besu.ethereum.trie.MerkleTrie; +import org.hyperledger.besu.ethereum.trie.RangeManager; import org.hyperledger.besu.ethereum.trie.RangeStorageEntriesCollector; import org.hyperledger.besu.ethereum.trie.TrieIterator; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.NavigableMap; import java.util.TreeMap; import java.util.function.Function; import java.util.stream.Stream; @@ -55,7 +57,7 @@ public class StorageFlatDatabaseHealingRangeRequest extends SnapDataRequest { private final Bytes32 storageRoot; private final Bytes32 startKeyHash; private final Bytes32 endKeyHash; - private TreeMap slots; + private NavigableMap slots; private boolean isProofValid; public StorageFlatDatabaseHealingRangeRequest( @@ -75,7 +77,7 @@ public StorageFlatDatabaseHealingRangeRequest( @Override public Stream getChildRequests( final SnapWorldDownloadState downloadState, - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final SnapSyncProcessState snapSyncState) { final List childRequests = new ArrayList<>(); if (!slots.isEmpty()) { @@ -119,10 +121,10 @@ public boolean isResponseReceived() { public void addLocalData( final WorldStateProofProvider worldStateProofProvider, - final TreeMap slots, + final NavigableMap slots, final ArrayDeque proofs) { if (!slots.isEmpty() && !proofs.isEmpty()) { - // very proof in order to check if the local flat database is valid or not + // verify proof in order to check if the local flat database is valid or not isProofValid = worldStateProofProvider.isValidRangeProof( startKeyHash, endKeyHash, storageRoot, proofs, slots); @@ -132,8 +134,8 @@ public void addLocalData( @Override protected int doPersist( - final WorldStateStorage worldStateStorage, - final WorldStateStorage.Updater updater, + final WorldStateStorageCoordinator worldStateStorageCoordinator, + final WorldStateKeyValueStorage.Updater updater, final SnapWorldDownloadState downloadState, final SnapSyncProcessState snapSyncState, final SnapSyncConfiguration snapSyncConfiguration) { @@ -147,7 +149,8 @@ protected int doPersist( final MerkleTrie storageTrie = new StoredMerklePatriciaTrie<>( (location, hash) -> - worldStateStorage.getAccountStorageTrieNode(accountHash, location, hash), + worldStateStorageCoordinator.getAccountStorageTrieNode( + accountHash, location, hash), storageRoot, Function.identity(), Function.identity()); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/StorageTrieNodeHealingRequest.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/StorageTrieNodeHealingRequest.java index ffcdb3e77b7..e38c9c3849c 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/StorageTrieNodeHealingRequest.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/StorageTrieNodeHealingRequest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,16 +14,16 @@ */ package org.hyperledger.besu.ethereum.eth.sync.snapsync.request.heal; +import static org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator.applyForStrategy; + import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncConfiguration; import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncProcessState; import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapWorldDownloadState; import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.SnapDataRequest; import org.hyperledger.besu.ethereum.trie.CompactEncoding; -import org.hyperledger.besu.ethereum.worldstate.FlatDbMode; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage.Updater; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import java.util.List; import java.util.Optional; @@ -46,19 +46,26 @@ public StorageTrieNodeHealingRequest( @Override protected int doPersist( - final WorldStateStorage worldStateStorage, - final Updater updater, + final WorldStateStorageCoordinator worldStateStorageCoordinator, + final WorldStateKeyValueStorage.Updater updater, final SnapWorldDownloadState downloadState, final SnapSyncProcessState snapSyncState, final SnapSyncConfiguration snapSyncConfiguration) { - updater.putAccountStorageTrieNode(getAccountHash(), getLocation(), getNodeHash(), data); + applyForStrategy( + updater, + onBonsai -> { + onBonsai.putAccountStorageTrieNode(getAccountHash(), getLocation(), getNodeHash(), data); + }, + onForest -> { + onForest.putAccountStorageTrieNode(getNodeHash(), data); + }); return 1; } @Override public Optional getExistingData( - final SnapWorldDownloadState downloadState, final WorldStateStorage worldStateStorage) { - return worldStateStorage.getAccountStorageTrieNode( + final WorldStateStorageCoordinator worldStateStorageCoordinator) { + return worldStateStorageCoordinator.getAccountStorageTrieNode( getAccountHash(), getLocation(), getNodeHash()); } @@ -70,17 +77,19 @@ protected SnapDataRequest createChildNodeDataRequest(final Hash childHash, final @Override protected Stream getRequestsFromTrieNodeValue( - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final SnapWorldDownloadState downloadState, final Bytes location, final Bytes path, final Bytes value) { - if (!worldStateStorage.getFlatDbMode().equals(FlatDbMode.NO_FLATTENED)) { - ((BonsaiWorldStateKeyValueStorage.Updater) worldStateStorage.updater()) - .putStorageValueBySlotHash( - accountHash, getSlotHash(location, path), Bytes32.leftPad(RLP.decodeValue(value))) - .commit(); - } + worldStateStorageCoordinator.applyWhenFlatModeEnabled( + onBonsai -> { + onBonsai + .updater() + .putStorageValueBySlotHash( + accountHash, getSlotHash(location, path), Bytes32.leftPad(RLP.decodeValue(value))) + .commit(); + }); return Stream.empty(); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/TrieNodeHealingRequest.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/TrieNodeHealingRequest.java index ef7191a0167..7e206232c57 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/TrieNodeHealingRequest.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/TrieNodeHealingRequest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -24,7 +24,8 @@ import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.SnapDataRequest; import org.hyperledger.besu.ethereum.trie.Node; import org.hyperledger.besu.ethereum.trie.patricia.TrieNodeDecoder; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.services.tasks.TasksPriorityProvider; import java.util.ArrayList; @@ -54,8 +55,8 @@ protected TrieNodeHealingRequest(final Hash nodeHash, final Hash rootHash, final @Override public int persist( - final WorldStateStorage worldStateStorage, - final WorldStateStorage.Updater updater, + final WorldStateStorageCoordinator worldStateStorageCoordinator, + final WorldStateKeyValueStorage.Updater updater, final SnapWorldDownloadState downloadState, final SnapSyncProcessState snapSyncState, final SnapSyncConfiguration snapSyncConfiguration) { @@ -68,13 +69,21 @@ public int persist( checkNotNull(data, "Must set data before node can be persisted."); saved = doPersist( - worldStateStorage, updater, downloadState, snapSyncState, snapSyncConfiguration); + worldStateStorageCoordinator, + updater, + downloadState, + snapSyncState, + snapSyncConfiguration); } if (possibleParent.isPresent()) { return possibleParent .get() .saveParent( - worldStateStorage, updater, downloadState, snapSyncState, snapSyncConfiguration) + worldStateStorageCoordinator, + updater, + downloadState, + snapSyncState, + snapSyncConfiguration) + saved; } return saved; @@ -83,7 +92,7 @@ public int persist( @Override public Stream getChildRequests( final SnapWorldDownloadState downloadState, - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final SnapSyncProcessState snapSyncState) { if (!isResponseReceived()) { // If this node hasn't been downloaded yet, we can't return any child data @@ -103,7 +112,7 @@ public Stream getChildRequests( .map( value -> getRequestsFromTrieNodeValue( - worldStateStorage, + worldStateStorageCoordinator, downloadState, node.getLocation().orElse(Bytes.EMPTY), node.getPath(), @@ -172,19 +181,20 @@ private boolean nodeIsHashReferencedDescendant(final Node node) { } public abstract Optional getExistingData( - final SnapWorldDownloadState downloadState, final WorldStateStorage worldStateStorage); + final WorldStateStorageCoordinator worldStateStorageCoordinator); public abstract List getTrieNodePath(); protected abstract SnapDataRequest createChildNodeDataRequest( final Hash childHash, final Bytes location); - public Stream getRootStorageRequests(final WorldStateStorage worldStateStorage) { + public Stream getRootStorageRequests( + final WorldStateStorageCoordinator worldStateStorageCoordinator) { return Stream.empty(); } protected abstract Stream getRequestsFromTrieNodeValue( - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final SnapWorldDownloadState downloadState, final Bytes location, final Bytes path, diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/state/InSyncTracker.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/state/InSyncTracker.java index 5b7189cf787..2be996877dd 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/state/InSyncTracker.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/state/InSyncTracker.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.eth.sync.state; import org.hyperledger.besu.ethereum.chain.ChainHead; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/CompleteBlocksTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/CompleteBlocksTask.java index 6e04833b644..524851af5f6 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/CompleteBlocksTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/CompleteBlocksTask.java @@ -38,8 +38,8 @@ import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; +import javax.annotation.Nonnull; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,7 +51,7 @@ public class CompleteBlocksTask extends AbstractRetryingPeerTask> { private static final Logger LOG = LoggerFactory.getLogger(CompleteBlocksTask.class); private static final int MIN_SIZE_INCOMPLETE_LIST = 1; - private static final int DEFAULT_RETRIES = 4; + private static final int DEFAULT_RETRIES = 5; private final EthContext ethContext; private final ProtocolSchedule protocolSchedule; @@ -85,7 +85,7 @@ private CompleteBlocksTask( createEmptyBodyBasedOnProtocolSchedule(protocolSchedule, header)))); } - @NotNull + @Nonnull private BlockBody createEmptyBodyBasedOnProtocolSchedule( final ProtocolSchedule protocolSchedule, final BlockHeader header) { return new BlockBody( diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DownloadHeaderSequenceTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DownloadHeaderSequenceTask.java index 23e26ff8922..8ca376902e4 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DownloadHeaderSequenceTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DownloadHeaderSequenceTask.java @@ -19,20 +19,21 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockCause; import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; import org.hyperledger.besu.ethereum.eth.manager.task.AbstractGetHeadersFromPeerTask; -import org.hyperledger.besu.ethereum.eth.manager.task.AbstractPeerTask; import org.hyperledger.besu.ethereum.eth.manager.task.AbstractPeerTask.PeerTaskResult; import org.hyperledger.besu.ethereum.eth.manager.task.AbstractRetryingPeerTask; -import org.hyperledger.besu.ethereum.eth.manager.task.GetBlockFromPeerTask; +import org.hyperledger.besu.ethereum.eth.manager.task.GetBodiesFromPeerTask; import org.hyperledger.besu.ethereum.eth.manager.task.GetHeadersFromPeerByHashTask; import org.hyperledger.besu.ethereum.eth.sync.ValidationPolicy; import org.hyperledger.besu.ethereum.eth.sync.tasks.exceptions.InvalidBlockException; import org.hyperledger.besu.ethereum.mainnet.BlockHeaderValidator; +import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason; @@ -44,6 +45,7 @@ import java.util.Optional; import java.util.concurrent.CompletableFuture; +import com.google.common.annotations.VisibleForTesting; import com.google.common.primitives.Ints; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -54,7 +56,7 @@ */ public class DownloadHeaderSequenceTask extends AbstractRetryingPeerTask> { private static final Logger LOG = LoggerFactory.getLogger(DownloadHeaderSequenceTask.class); - private static final int DEFAULT_RETRIES = 4; + private static final int DEFAULT_RETRIES = 5; private final EthContext ethContext; private final ProtocolContext protocolContext; @@ -177,7 +179,8 @@ private CompletableFuture>> downloadHeaders( }); } - private CompletableFuture> processHeaders( + @VisibleForTesting + CompletableFuture> processHeaders( final PeerTaskResult> headersResult) { return executeWorkerSubTask( ethContext.getScheduler(), @@ -199,42 +202,40 @@ private CompletableFuture> processHeaders( child = (headerIndex == segmentLength - 1) ? referenceHeader : headers[headerIndex + 1]; } - final ProtocolSpec protocolSpec = protocolSchedule.getByBlockHeader(child); - final BadBlockManager badBlockManager = protocolSpec.getBadBlocksManager(); - if (!validateHeader(child, header)) { - // Invalid headers - disconnect from peer - - final BlockHeader invalidBlock = child; - // even though the header is known bad we are downloading the block body for the - // debug_badBlocks RPC - final AbstractPeerTask getBlockTask = - GetBlockFromPeerTask.create( - protocolSchedule, - ethContext, - Optional.of(child.getHash()), - child.getNumber(), - metricsSystem) - .assignPeer(headersResult.getPeer()); - - getBlockTask - .run() - .whenComplete( - (blockPeerTaskResult, error) -> { - if (error == null && blockPeerTaskResult.getResult() != null) { - badBlockManager.addBadBlock( - blockPeerTaskResult.getResult(), Optional.ofNullable(error)); - } - LOG.debug( - "Received invalid headers from peer (BREACH_OF_PROTOCOL), disconnecting from: {}", - headersResult.getPeer()); - headersResult.getPeer().disconnect(DisconnectReason.BREACH_OF_PROTOCOL); - future.completeExceptionally( - new InvalidBlockException( - "Header failed validation.", - invalidBlock.getNumber(), - invalidBlock.getHash())); - }); + final boolean foundChild = child != null; + final boolean headerInRange = checkHeaderInRange(header); + final boolean headerInvalid = foundChild && !validateHeader(child, header); + if (!headerInRange || !foundChild || headerInvalid) { + final BlockHeader invalidHeader = child; + final CompletableFuture badBlockHandled = + headerInvalid + ? markBadBlock(invalidHeader, headersResult.getPeer()) + : CompletableFuture.completedFuture(null); + badBlockHandled.whenComplete( + (res, err) -> { + LOG.debug( + "Received invalid headers from peer (BREACH_OF_PROTOCOL), disconnecting from: {}", + headersResult.getPeer()); + headersResult + .getPeer() + .disconnect(DisconnectReason.BREACH_OF_PROTOCOL_INVALID_HEADERS); + final InvalidBlockException exception; + if (invalidHeader == null) { + final String msg = + String.format( + "Received misordered blocks. Missing child of %s", + header.toLogString()); + exception = InvalidBlockException.create(msg); + } else { + final String errorMsg = + headerInvalid + ? "Header failed validation" + : "Out-of-range header received from peer"; + exception = InvalidBlockException.fromInvalidBlock(errorMsg, invalidHeader); + } + future.completeExceptionally(exception); + }); return future; } @@ -247,20 +248,41 @@ private CompletableFuture> processHeaders( }); } - private boolean validateHeader(final BlockHeader child, final BlockHeader header) { + private CompletableFuture markBadBlock(final BlockHeader badHeader, final EthPeer badPeer) { + // even though the header is known bad we are downloading the block body for the debug_badBlocks + // RPC + final BadBlockManager badBlockManager = protocolContext.getBadBlockManager(); + return GetBodiesFromPeerTask.forHeaders( + protocolSchedule, ethContext, List.of(badHeader), metricsSystem) + .assignPeer(badPeer) + .run() + .whenComplete( + (blockPeerTaskResult, error) -> { + final HeaderValidationMode validationMode = + validationPolicy.getValidationModeForNextBlock(); + final String description = + String.format("Failed header validation (%s)", validationMode); + final BadBlockCause cause = BadBlockCause.fromValidationFailure(description); + if (blockPeerTaskResult != null) { + final Optional block = blockPeerTaskResult.getResult().stream().findFirst(); + block.ifPresentOrElse( + (b) -> badBlockManager.addBadBlock(b, cause), + () -> badBlockManager.addBadHeader(badHeader, cause)); + } else { + badBlockManager.addBadHeader(badHeader, cause); + } + }); + } + + private boolean checkHeaderInRange(final BlockHeader header) { final long finalBlockNumber = startingBlockNumber + segmentLength; - final boolean blockInRange = - header.getNumber() >= startingBlockNumber && header.getNumber() < finalBlockNumber; - if (!blockInRange) { - return false; - } - if (child == null) { - return false; - } + return header.getNumber() >= startingBlockNumber && header.getNumber() < finalBlockNumber; + } - final ProtocolSpec protocolSpec = protocolSchedule.getByBlockHeader(child); + private boolean validateHeader(final BlockHeader header, final BlockHeader parent) { + final ProtocolSpec protocolSpec = protocolSchedule.getByBlockHeader(header); final BlockHeaderValidator blockHeaderValidator = protocolSpec.getBlockHeaderValidator(); return blockHeaderValidator.validateHeader( - child, header, protocolContext, validationPolicy.getValidationModeForNextBlock()); + header, parent, protocolContext, validationPolicy.getValidationModeForNextBlock()); } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/GetReceiptsForHeadersTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/GetReceiptsForHeadersTask.java index 699bf9c4b17..58c4d3a7afa 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/GetReceiptsForHeadersTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/GetReceiptsForHeadersTask.java @@ -42,7 +42,7 @@ public class GetReceiptsForHeadersTask extends AbstractRetryingPeerTask>> { private static final Logger LOG = LoggerFactory.getLogger(GetReceiptsForHeadersTask.class); - private static final int DEFAULT_RETRIES = 4; + private static final int DEFAULT_RETRIES = 5; private final EthContext ethContext; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/PersistBlockTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/PersistBlockTask.java index 8c8e7aad3fb..85a478c9aaf 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/PersistBlockTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/PersistBlockTask.java @@ -202,9 +202,7 @@ protected void executeTask() { .log(); blockImportResult = blockImporter.importBlock(protocolContext, block, validateHeaders); if (!blockImportResult.isImported()) { - result.completeExceptionally( - new InvalidBlockException( - "Failed to import block", block.getHeader().getNumber(), block.getHash())); + result.completeExceptionally(InvalidBlockException.fromInvalidBlock(block.getHeader())); return; } result.complete(block); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/RetryingGetHeaderFromPeerByHashTask.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/RetryingGetHeaderFromPeerByHashTask.java index c36599e6354..e2e76c030b5 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/RetryingGetHeaderFromPeerByHashTask.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/RetryingGetHeaderFromPeerByHashTask.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -95,7 +95,7 @@ protected CompletableFuture> executeTaskOnCurrentPeer(final Et "No block header for hash " + referenceHash + " returned by peer " - + peer.getShortNodeId()); + + peer.getLoggableId()); } result.complete(peerResult.getResult()); return peerResult.getResult(); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/exceptions/InvalidBlockException.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/exceptions/InvalidBlockException.java index e53ce3e86e2..c9ba0dbfa4a 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/exceptions/InvalidBlockException.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/tasks/exceptions/InvalidBlockException.java @@ -14,11 +14,25 @@ */ package org.hyperledger.besu.ethereum.eth.sync.tasks.exceptions; -import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.core.BlockHeader; public class InvalidBlockException extends RuntimeException { - public InvalidBlockException(final String message, final long blockNumber, final Hash blockHash) { - super(message + ": Invalid block at #" + blockNumber + " (" + blockHash + ")"); + private InvalidBlockException(final String message) { + super(message); + } + + public static InvalidBlockException create(final String message) { + return new InvalidBlockException(message); + } + + public static InvalidBlockException fromInvalidBlock(final BlockHeader header) { + return fromInvalidBlock("Failed to import block", header); + } + + public static InvalidBlockException fromInvalidBlock( + final String description, final BlockHeader header) { + final String message = description + ": Invalid block " + header.toLogString(); + return new InvalidBlockException(message); } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldDownloadState.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldDownloadState.java index 8f53921f4dd..4945713ae3c 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldDownloadState.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldDownloadState.java @@ -17,7 +17,8 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.manager.task.EthTask; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.services.tasks.InMemoryTasksPriorityQueues; import org.hyperledger.besu.services.tasks.Task; import org.hyperledger.besu.services.tasks.TasksPriorityProvider; @@ -37,6 +38,7 @@ public abstract class WorldDownloadState { private static final Logger LOG = LoggerFactory.getLogger(WorldDownloadState.class); + protected final SyncDurationMetrics syncDurationMetrics; private boolean downloadWasResumed; protected final InMemoryTasksPriorityQueues pendingRequests; @@ -53,22 +55,24 @@ public abstract class WorldDownloadState private volatile long timestampOfLastProgress; protected Bytes rootNodeData; - protected final WorldStateStorage worldStateStorage; + protected final WorldStateStorageCoordinator worldStateStorageCoordinator; protected WorldStateDownloadProcess worldStateDownloadProcess; public WorldDownloadState( - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final InMemoryTasksPriorityQueues pendingRequests, final int maxRequestsWithoutProgress, final long minMillisBeforeStalling, - final Clock clock) { - this.worldStateStorage = worldStateStorage; + final Clock clock, + final SyncDurationMetrics syncDurationMetrics) { + this.worldStateStorageCoordinator = worldStateStorageCoordinator; this.minMillisBeforeStalling = minMillisBeforeStalling; this.timestampOfLastProgress = clock.millis(); this.downloadWasResumed = !pendingRequests.isEmpty(); this.pendingRequests = pendingRequests; this.maxRequestsWithoutProgress = maxRequestsWithoutProgress; this.clock = clock; + this.syncDurationMetrics = syncDurationMetrics; this.internalFuture = new CompletableFuture<>(); this.downloadFuture = new CompletableFuture<>(); this.internalFuture.whenComplete(this::cleanup); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldStateDownloadStatus.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldStateDownloadStatus.java index 9c38b0e18b8..d54d5253d88 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldStateDownloadStatus.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/sync/worldstate/WorldStateDownloadStatus.java @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.eth.sync.worldstate; import java.util.Optional; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/BlobCache.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/BlobCache.java index 980f01ed7a2..3d3a435f1f8 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/BlobCache.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/BlobCache.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.eth.transactions; import org.hyperledger.besu.datatypes.BlobsWithCommitments; @@ -88,4 +87,8 @@ public Optional restoreBlob(final Transaction transaction) { return Optional.empty(); } } + + public BlobsWithCommitments.BlobQuad get(final VersionedHash vh) { + return cache.getIfPresent(vh); + } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/BlobCacheModule.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/BlobCacheModule.java index 29ca246232e..fd5bc456013 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/BlobCacheModule.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/BlobCacheModule.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.eth.transactions; import javax.inject.Singleton; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/DisabledPendingTransactions.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/DisabledPendingTransactions.java index c52876ced75..ce79cf2855f 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/DisabledPendingTransactions.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/DisabledPendingTransactions.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageProcessor.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageProcessor.java index f447c1df315..b4ae7ec87c0 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageProcessor.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageProcessor.java @@ -29,7 +29,6 @@ import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledFuture; -import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -86,8 +85,8 @@ private void processNewPooledTransactionHashesMessage( LOG.atTrace() .setMessage( - "Received pooled transaction hashes message from {}... incoming hashes {}, incoming list {}") - .addArgument(() -> peer == null ? null : peer.getShortNodeId()) + "Received pooled transaction hashes message from {} incoming hashes {}, incoming list {}") + .addArgument(() -> peer == null ? null : peer.getLoggableId()) .addArgument(incomingTransactionHashes::size) .addArgument(incomingTransactionHashes) .log(); @@ -121,7 +120,7 @@ private void processNewPooledTransactionHashesMessage( bufferedTask.addHashes( incomingTransactionHashes.stream() .filter(hash -> transactionPool.getTransactionByHash(hash).isEmpty()) - .collect(Collectors.toList())); + .toList()); } catch (final RLPException ex) { if (peer != null) { LOG.debug( @@ -129,7 +128,7 @@ private void processNewPooledTransactionHashesMessage( peer, ex); LOG.trace("Message data: {}", transactionsMessage.getData()); - peer.disconnect(DisconnectReason.BREACH_OF_PROTOCOL); + peer.disconnect(DisconnectReason.BREACH_OF_PROTOCOL_MALFORMED_MESSAGE_RECEIVED); } } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageSender.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageSender.java index 2071226b605..bbc1dd3cd4f 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageSender.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageSender.java @@ -45,7 +45,7 @@ public void sendTransactionHashesToPeer(final EthPeer peer) { final Capability capability = peer.getConnection().capability(EthProtocol.NAME); for (final List txBatch : Iterables.partition( - transactionTracker.claimTransactionsToSendToPeer(peer), MAX_TRANSACTIONS_HASHES)) { + transactionTracker.claimTransactionHashesToSendToPeer(peer), MAX_TRANSACTIONS_HASHES)) { try { final List txHashes = toHashList(txBatch); LOG.atTrace() diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PeerTransactionTracker.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PeerTransactionTracker.java index fc6b0f1d819..da959004291 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PeerTransactionTracker.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PeerTransactionTracker.java @@ -20,22 +20,38 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; +import org.hyperledger.besu.ethereum.eth.manager.EthPeers; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class PeerTransactionTracker implements EthPeer.DisconnectCallback { + private static final Logger LOG = LoggerFactory.getLogger(PeerTransactionTracker.class); + private static final int MAX_TRACKED_SEEN_TRANSACTIONS = 100_000; + + private final EthPeers ethPeers; private final Map> seenTransactions = new ConcurrentHashMap<>(); private final Map> transactionsToSend = new ConcurrentHashMap<>(); + private final Map> transactionHashesToSend = new ConcurrentHashMap<>(); + + public PeerTransactionTracker(final EthPeers ethPeers) { + this.ethPeers = ethPeers; + } public void reset() { seenTransactions.clear(); transactionsToSend.clear(); + transactionHashesToSend.clear(); } public synchronized void markTransactionsAsSeen( @@ -55,6 +71,15 @@ public synchronized void addToPeerSendQueue(final EthPeer peer, final Transactio } } + public synchronized void addToPeerHashSendQueue( + final EthPeer peer, final Transaction transaction) { + if (!hasPeerSeenTransaction(peer, transaction)) { + transactionHashesToSend + .computeIfAbsent(peer, key -> createTransactionsSet()) + .add(transaction); + } + } + public Iterable getEthPeersWithUnsentTransactions() { return transactionsToSend.keySet(); } @@ -69,6 +94,16 @@ public synchronized Set claimTransactionsToSendToPeer(final EthPeer } } + public synchronized Set claimTransactionHashesToSendToPeer(final EthPeer peer) { + final Set transactionHashesToSend = this.transactionHashesToSend.remove(peer); + if (transactionHashesToSend != null) { + markTransactionHashesAsSeen(peer, toHashList(transactionHashesToSend)); + return transactionHashesToSend; + } else { + return emptySet(); + } + } + public boolean hasSeenTransaction(final Hash txHash) { return seenTransactions.values().stream().anyMatch(seen -> seen.contains(txHash)); } @@ -98,7 +133,46 @@ protected boolean removeEldestEntry(final Map.Entry eldest) { @Override public void onDisconnect(final EthPeer peer) { - seenTransactions.remove(peer); - transactionsToSend.remove(peer); + LOG.atTrace().setMessage("onDisconnect for peer {}").addArgument(peer::getLoggableId).log(); + + // here we reconcile all the trackers with the active peers, since due to the asynchronous + // processing of incoming messages it could seldom happen that a tracker is recreated just + // after a peer was disconnected, resulting in a memory leak. + final Set trackedPeers = new HashSet<>(seenTransactions.keySet()); + trackedPeers.addAll(transactionsToSend.keySet()); + trackedPeers.addAll(transactionHashesToSend.keySet()); + + LOG.atTrace() + .setMessage("{} tracked peers ({})") + .addArgument(trackedPeers.size()) + .addArgument(() -> logPeerSet(trackedPeers)) + .log(); + + final Set connectedPeers = + ethPeers.streamAllPeers().collect(Collectors.toUnmodifiableSet()); + + final var disconnectedPeers = trackedPeers; + disconnectedPeers.removeAll(connectedPeers); + LOG.atTrace() + .setMessage("Removing {} transaction trackers for disconnected peers ({})") + .addArgument(disconnectedPeers.size()) + .addArgument(() -> logPeerSet(disconnectedPeers)) + .log(); + + disconnectedPeers.stream() + .forEach( + disconnectedPeer -> { + seenTransactions.remove(disconnectedPeer); + transactionsToSend.remove(disconnectedPeer); + transactionHashesToSend.remove(disconnectedPeer); + LOG.atTrace() + .setMessage("Removed transaction trackers for disconnected peer {}") + .addArgument(disconnectedPeer::getLoggableId) + .log(); + }); + } + + private String logPeerSet(final Set peers) { + return peers.stream().map(EthPeer::getLoggableId).collect(Collectors.joining(",")); } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransaction.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransaction.java index a622507b190..91b4efd7b21 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransaction.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransaction.java @@ -1,5 +1,5 @@ /* - * Copyright Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -31,8 +31,8 @@ public abstract class PendingTransaction implements org.hyperledger.besu.datatypes.PendingTransaction { static final int NOT_INITIALIZED = -1; - static final int FRONTIER_AND_ACCESS_LIST_BASE_MEMORY_SIZE = 872; - static final int EIP1559_AND_EIP4844_BASE_MEMORY_SIZE = 984; + static final int FRONTIER_AND_ACCESS_LIST_SHALLOW_MEMORY_SIZE = 904; + static final int EIP1559_AND_EIP4844_SHALLOW_MEMORY_SIZE = 1016; static final int OPTIONAL_TO_MEMORY_SIZE = 112; static final int OPTIONAL_CHAIN_ID_MEMORY_SIZE = 80; static final int PAYLOAD_BASE_MEMORY_SIZE = 32; @@ -50,18 +50,20 @@ public abstract class PendingTransaction private final Transaction transaction; private final long addedAt; private final long sequence; // Allows prioritization based on order transactions are added + private volatile byte score; private int memorySize = NOT_INITIALIZED; private PendingTransaction( - final Transaction transaction, final long addedAt, final long sequence) { + final Transaction transaction, final long addedAt, final long sequence, final byte score) { this.transaction = transaction; this.addedAt = addedAt; this.sequence = sequence; + this.score = score; } private PendingTransaction(final Transaction transaction, final long addedAt) { - this(transaction, addedAt, TRANSACTIONS_ADDED.getAndIncrement()); + this(transaction, addedAt, TRANSACTIONS_ADDED.getAndIncrement(), Byte.MAX_VALUE); } public static PendingTransaction newPendingTransaction( @@ -123,6 +125,20 @@ public int memorySize() { return memorySize; } + public byte getScore() { + return score; + } + + public void decrementScore() { + // use temp var to avoid non-atomic update of volatile var + final byte newScore = (byte) (score - 1); + + // check to avoid underflow + if (newScore < score) { + score = newScore; + } + } + public abstract PendingTransaction detachedCopy(); private int computeMemorySize() { @@ -131,19 +147,20 @@ private int computeMemorySize() { case ACCESS_LIST -> computeAccessListMemorySize(); case EIP1559 -> computeEIP1559MemorySize(); case BLOB -> computeBlobMemorySize(); + case DELEGATE_CODE -> computeSetCodeMemorySize(); } + PENDING_TRANSACTION_MEMORY_SIZE; } private int computeFrontierMemorySize() { - return FRONTIER_AND_ACCESS_LIST_BASE_MEMORY_SIZE + return FRONTIER_AND_ACCESS_LIST_SHALLOW_MEMORY_SIZE + computePayloadMemorySize() + computeToMemorySize() + computeChainIdMemorySize(); } private int computeAccessListMemorySize() { - return FRONTIER_AND_ACCESS_LIST_BASE_MEMORY_SIZE + return FRONTIER_AND_ACCESS_LIST_SHALLOW_MEMORY_SIZE + computePayloadMemorySize() + computeToMemorySize() + computeChainIdMemorySize() @@ -151,7 +168,7 @@ private int computeAccessListMemorySize() { } private int computeEIP1559MemorySize() { - return EIP1559_AND_EIP4844_BASE_MEMORY_SIZE + return EIP1559_AND_EIP4844_SHALLOW_MEMORY_SIZE + computePayloadMemorySize() + computeToMemorySize() + computeChainIdMemorySize() @@ -164,6 +181,10 @@ private int computeBlobMemorySize() { + computeBlobWithCommitmentsMemorySize(); } + private int computeSetCodeMemorySize() { + return 0; + } + private int computeBlobWithCommitmentsMemorySize() { final int blobCount = transaction.getBlobCount(); @@ -250,6 +271,8 @@ public String toString() { + isReceivedFromLocalSource() + ", hasPriority=" + hasPriority() + + ", score=" + + score + '}'; } @@ -262,6 +285,8 @@ public String toTraceLog() { + isReceivedFromLocalSource() + ", hasPriority=" + hasPriority() + + ", score=" + + score + ", " + transaction.toTraceLog() + "}"; @@ -277,13 +302,13 @@ public Local(final Transaction transaction) { this(transaction, System.currentTimeMillis()); } - private Local(final long sequence, final Transaction transaction) { - super(transaction, System.currentTimeMillis(), sequence); + private Local(final long sequence, final byte score, final Transaction transaction) { + super(transaction, System.currentTimeMillis(), sequence, score); } @Override public PendingTransaction detachedCopy() { - return new Local(getSequence(), getTransaction().detachedCopy()); + return new Local(getSequence(), getScore(), getTransaction().detachedCopy()); } @Override @@ -305,13 +330,13 @@ public Priority(final Transaction transaction, final long addedAt) { super(transaction, addedAt); } - public Priority(final long sequence, final Transaction transaction) { - super(sequence, transaction); + public Priority(final long sequence, final byte score, final Transaction transaction) { + super(sequence, score, transaction); } @Override public PendingTransaction detachedCopy() { - return new Priority(getSequence(), getTransaction().detachedCopy()); + return new Priority(getSequence(), getScore(), getTransaction().detachedCopy()); } @Override @@ -331,13 +356,13 @@ public Remote(final Transaction transaction) { this(transaction, System.currentTimeMillis()); } - private Remote(final long sequence, final Transaction transaction) { - super(transaction, System.currentTimeMillis(), sequence); + private Remote(final long sequence, final byte score, final Transaction transaction) { + super(transaction, System.currentTimeMillis(), sequence, score); } @Override public PendingTransaction detachedCopy() { - return new Remote(getSequence(), getTransaction().detachedCopy()); + return new Remote(getSequence(), getScore(), getTransaction().detachedCopy()); } @Override @@ -359,13 +384,13 @@ public Priority(final Transaction transaction, final long addedAt) { super(transaction, addedAt); } - public Priority(final long sequence, final Transaction transaction) { - super(sequence, transaction); + public Priority(final long sequence, final byte score, final Transaction transaction) { + super(sequence, score, transaction); } @Override public PendingTransaction detachedCopy() { - return new Priority(getSequence(), getTransaction().detachedCopy()); + return new Priority(getSequence(), getScore(), getTransaction().detachedCopy()); } @Override diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactions.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactions.java index dbb95edd828..c66f0e1df12 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactions.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactions.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionAddedResult.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionAddedResult.java index a4b8279f19f..aa169c0e334 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionAddedResult.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionAddedResult.java @@ -1,5 +1,5 @@ /* - * Copyright Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionBroadcaster.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionBroadcaster.java index f64f4e7973c..c60c064c4c2 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionBroadcaster.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionBroadcaster.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -30,8 +30,10 @@ import java.util.EnumSet; import java.util.List; import java.util.Map; +import java.util.Random; import java.util.stream.Collectors; +import com.google.common.annotations.VisibleForTesting; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,16 +49,33 @@ public class TransactionBroadcaster implements TransactionBatchAddedListener { private final TransactionsMessageSender transactionsMessageSender; private final NewPooledTransactionHashesMessageSender newPooledTransactionHashesMessageSender; private final EthContext ethContext; + private final Random random; public TransactionBroadcaster( final EthContext ethContext, final PeerTransactionTracker transactionTracker, final TransactionsMessageSender transactionsMessageSender, final NewPooledTransactionHashesMessageSender newPooledTransactionHashesMessageSender) { + this( + ethContext, + transactionTracker, + transactionsMessageSender, + newPooledTransactionHashesMessageSender, + null); + } + + @VisibleForTesting + protected TransactionBroadcaster( + final EthContext ethContext, + final PeerTransactionTracker transactionTracker, + final TransactionsMessageSender transactionsMessageSender, + final NewPooledTransactionHashesMessageSender newPooledTransactionHashesMessageSender, + final Long seed) { this.transactionTracker = transactionTracker; this.transactionsMessageSender = transactionsMessageSender; this.newPooledTransactionHashesMessageSender = newPooledTransactionHashesMessageSender; this.ethContext = ethContext; + this.random = seed != null ? new Random(seed) : new Random(); } public void relayTransactionPoolTo( @@ -65,7 +84,13 @@ public void relayTransactionPoolTo( if (peer.hasSupportForMessage(EthPV65.NEW_POOLED_TRANSACTION_HASHES)) { sendTransactionHashes(toTransactionList(pendingTransactions), List.of(peer)); } else { - sendFullTransactions(toTransactionList(pendingTransactions), List.of(peer)); + // we need to exclude txs that support hash only broadcasting + final var fullBroadcastTxs = + pendingTransactions.stream() + .map(PendingTransaction::getTransaction) + .filter(tx -> !ANNOUNCE_HASH_ONLY_TX_TYPES.contains(tx.getType())) + .toList(); + sendFullTransactions(fullBroadcastTxs, List.of(peer)); } } } @@ -77,7 +102,7 @@ public void onTransactionsAdded(final Collection transactions) { return; } - final int numPeersToSendFullTransactions = (int) Math.ceil(Math.sqrt(currPeerCount)); + final int numPeersToSendFullTransactions = (int) Math.round(Math.sqrt(currPeerCount)); final Map> transactionByBroadcastMode = transactions.stream() @@ -107,7 +132,7 @@ public void onTransactionsAdded(final Collection transactions) { numPeersToSendFullTransactions - sendOnlyFullTransactionPeers.size(), sendOnlyHashPeers.size()); - Collections.shuffle(sendOnlyHashPeers); + Collections.shuffle(sendOnlyHashPeers, random); // move peers from the mixed list to reach the required size for full transaction peers movePeersBetweenLists(sendOnlyHashPeers, sendMixedPeers, delta); @@ -121,7 +146,7 @@ public void onTransactionsAdded(final Collection transactions) { .addArgument(sendOnlyHashPeers::size) .addArgument(sendMixedPeers::size) .addArgument(sendOnlyFullTransactionPeers) - .addArgument(() -> sendOnlyHashPeers.toString() + sendMixedPeers.toString()) + .addArgument(() -> sendOnlyHashPeers.toString() + sendMixedPeers) .log(); sendToFullTransactionsPeers( @@ -141,7 +166,7 @@ private void sendToOnlyHashPeers( final Map> txsByHashOnlyBroadcast, final List hashOnlyPeers) { final List allTransactions = - txsByHashOnlyBroadcast.values().stream().flatMap(List::stream).collect(Collectors.toList()); + txsByHashOnlyBroadcast.values().stream().flatMap(List::stream).toList(); sendTransactionHashes(allTransactions, hashOnlyPeers); } @@ -175,7 +200,7 @@ private void sendTransactionHashes( .forEach( peer -> { transactions.forEach( - transaction -> transactionTracker.addToPeerSendQueue(peer, transaction)); + transaction -> transactionTracker.addToPeerHashSendQueue(peer, transaction)); ethContext .getScheduler() .scheduleSyncWorkerTask( diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPool.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPool.java index edefbc17a36..315f82921bb 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPool.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPool.java @@ -20,8 +20,10 @@ import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.TRANSACTION_ALREADY_KNOWN; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.BlobsWithCommitments; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.TransactionType; +import org.hyperledger.besu.datatypes.VersionedHash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.chain.BlockAddedEvent; @@ -31,6 +33,7 @@ import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; import org.hyperledger.besu.ethereum.mainnet.TransactionValidator; @@ -41,8 +44,6 @@ import org.hyperledger.besu.ethereum.trie.MerkleTrieException; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.fluent.SimpleAccount; -import org.hyperledger.besu.plugin.services.txvalidator.PluginTransactionValidator; -import org.hyperledger.besu.plugin.services.txvalidator.PluginTransactionValidatorFactory; import org.hyperledger.besu.util.Subscribers; import java.io.BufferedReader; @@ -56,23 +57,21 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; +import java.util.HashMap; import java.util.IntSummaryStatistics; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.OptionalLong; -import java.util.Queue; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutionException; +import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -93,8 +92,8 @@ public class TransactionPool implements BlockAddedObserver { private static final Logger LOG = LoggerFactory.getLogger(TransactionPool.class); private static final Logger LOG_FOR_REPLAY = LoggerFactory.getLogger("LOG_FOR_REPLAY"); private final Supplier pendingTransactionsSupplier; - private final PluginTransactionValidator pluginTransactionValidator; - private volatile PendingTransactions pendingTransactions; + private final BlobCache cacheForBlobsOfTransactionsAddedToABlock; + private volatile PendingTransactions pendingTransactions = new DisabledPendingTransactions(); private final ProtocolSchedule protocolSchedule; private final ProtocolContext protocolContext; private final EthContext ethContext; @@ -107,8 +106,9 @@ public class TransactionPool implements BlockAddedObserver { private volatile OptionalLong subscribeConnectId = OptionalLong.empty(); private final SaveRestoreManager saveRestoreManager = new SaveRestoreManager(); private final Set
localSenders = ConcurrentHashMap.newKeySet(); - private final Lock blockAddedLock = new ReentrantLock(); - private final Queue blockAddedQueue = new ConcurrentLinkedQueue<>(); + private final EthScheduler.OrderedProcessor blockAddedEventOrderedProcessor; + private final Map mapOfBlobsInTransactionPool = + new HashMap<>(); public TransactionPool( final Supplier pendingTransactionsSupplier, @@ -118,7 +118,7 @@ public TransactionPool( final EthContext ethContext, final TransactionPoolMetrics metrics, final TransactionPoolConfiguration configuration, - final PluginTransactionValidatorFactory pluginTransactionValidatorFactory) { + final BlobCache blobCache) { this.pendingTransactionsSupplier = pendingTransactionsSupplier; this.protocolSchedule = protocolSchedule; this.protocolContext = protocolContext; @@ -126,11 +126,12 @@ public TransactionPool( this.transactionBroadcaster = transactionBroadcaster; this.metrics = metrics; this.configuration = configuration; - this.pluginTransactionValidator = - pluginTransactionValidatorFactory == null - ? null - : pluginTransactionValidatorFactory.create(); + this.blockAddedEventOrderedProcessor = + ethContext.getScheduler().createOrderedProcessor(this::processBlockAddedEvent); + this.cacheForBlobsOfTransactionsAddedToABlock = blobCache; initLogForReplay(); + subscribePendingTransactions(this::mapBlobsOnTransactionAdded); + subscribeDroppedTransactions(this::unmapBlobsOnTransactionDropped); } private void initLogForReplay() { @@ -158,6 +159,16 @@ private void initLogForReplay() { .map(Address::toHexString) .collect(Collectors.joining(","))) .log(); + // log the max prioritized txs by type + LOG_FOR_REPLAY + .atTrace() + .setMessage("{}") + .addArgument( + () -> + configuration.getMaxPrioritizedTransactionsByType().entrySet().stream() + .map(e -> e.getKey().name() + "=" + e.getValue()) + .collect(Collectors.joining(","))) + .log(); } @VisibleForTesting @@ -322,51 +333,29 @@ public void unsubscribeDroppedTransactions(final long id) { @Override public void onBlockAdded(final BlockAddedEvent event) { if (isPoolEnabled.get()) { - final long started = System.currentTimeMillis(); if (event.getEventType().equals(BlockAddedEvent.EventType.HEAD_ADVANCED) || event.getEventType().equals(BlockAddedEvent.EventType.CHAIN_REORG)) { - // add the event to the processing queue - blockAddedQueue.add(event); - - // we want to process the added block asynchronously, - // but at the same time we must ensure that blocks are processed in order one at time - ethContext - .getScheduler() - .scheduleServiceTask( - () -> { - while (!blockAddedQueue.isEmpty()) { - if (blockAddedLock.tryLock()) { - // no other thread is processing the queue, so start processing it - try { - BlockAddedEvent e = blockAddedQueue.poll(); - // check again since another thread could have stolen our task - if (e != null) { - pendingTransactions.manageBlockAdded( - e.getBlock().getHeader(), - e.getAddedTransactions(), - e.getRemovedTransactions(), - protocolSchedule - .getByBlockHeader(e.getBlock().getHeader()) - .getFeeMarket()); - reAddTransactions(e.getRemovedTransactions()); - LOG.atTrace() - .setMessage("Block added event {} processed in {}ms") - .addArgument(e) - .addArgument(() -> System.currentTimeMillis() - started) - .log(); - } - } finally { - blockAddedLock.unlock(); - } - } - } - return null; - }); + blockAddedEventOrderedProcessor.submit(event); } } } + private void processBlockAddedEvent(final BlockAddedEvent e) { + final long started = System.currentTimeMillis(); + pendingTransactions.manageBlockAdded( + e.getBlock().getHeader(), + e.getAddedTransactions(), + e.getRemovedTransactions(), + protocolSchedule.getByBlockHeader(e.getBlock().getHeader()).getFeeMarket()); + reAddTransactions(e.getRemovedTransactions()); + LOG.atTrace() + .setMessage("Block added event {} processed in {}ms") + .addArgument(e) + .addArgument(() -> System.currentTimeMillis() - started) + .log(); + } + private void reAddTransactions(final List reAddTransactions) { if (!reAddTransactions.isEmpty()) { // if adding a blob tx, and it is missing its blob, is a re-org and we should restore the blob @@ -421,7 +410,6 @@ private ValidationResultAndAccount validateTransaction( final FeeMarket feeMarket = protocolSchedule.getByBlockHeader(chainHeadBlockHeader).getFeeMarket(); - final TransactionInvalidReason priceInvalidReason = validatePrice(transaction, isLocal, hasPriority, feeMarket); if (priceInvalidReason != null) { @@ -433,6 +421,9 @@ private ValidationResultAndAccount validateTransaction( .validate( transaction, chainHeadBlockHeader.getBaseFee(), + Optional.of( + Wei.ZERO), // TransactionValidationParams.transactionPool() allows underpriced + // txs TransactionValidationParams.transactionPool()); if (!basicValidationResult.isValid()) { return new ValidationResultAndAccount(basicValidationResult); @@ -462,14 +453,15 @@ && strictReplayProtectionShouldBeEnforcedLocally(chainHeadBlockHeader) TransactionInvalidReason.INVALID_BLOBS, "Blob transaction must have at least one blob"); } - // Call the transaction validator plugin if one is available - if (pluginTransactionValidator != null) { - final Optional maybeError = - pluginTransactionValidator.validateTransaction(transaction); - if (maybeError.isPresent()) { - return ValidationResultAndAccount.invalid( - TransactionInvalidReason.PLUGIN_TX_VALIDATOR, maybeError.get()); - } + // Call the transaction validator plugin + final Optional maybePluginInvalid = + configuration + .getTransactionPoolValidatorService() + .createTransactionValidator() + .validateTransaction(transaction, isLocal, hasPriority); + if (maybePluginInvalid.isPresent()) { + return ValidationResultAndAccount.invalid( + TransactionInvalidReason.PLUGIN_TX_POOL_VALIDATOR, maybePluginInvalid.get()); } try (final var worldState = @@ -658,6 +650,38 @@ public CompletableFuture setDisabled() { return CompletableFuture.completedFuture(null); } + private void mapBlobsOnTransactionAdded( + final org.hyperledger.besu.datatypes.Transaction transaction) { + final Optional maybeBlobsWithCommitments = + transaction.getBlobsWithCommitments(); + if (maybeBlobsWithCommitments.isEmpty()) { + return; + } + final List blobQuads = + maybeBlobsWithCommitments.get().getBlobQuads(); + blobQuads.forEach(bq -> mapOfBlobsInTransactionPool.put(bq.versionedHash(), bq)); + } + + private void unmapBlobsOnTransactionDropped( + final org.hyperledger.besu.datatypes.Transaction transaction) { + final Optional maybeBlobsWithCommitments = + transaction.getBlobsWithCommitments(); + if (maybeBlobsWithCommitments.isEmpty()) { + return; + } + final List blobQuads = + maybeBlobsWithCommitments.get().getBlobQuads(); + blobQuads.forEach(bq -> mapOfBlobsInTransactionPool.remove(bq.versionedHash())); + } + + public BlobsWithCommitments.BlobQuad getBlobQuad(final VersionedHash vh) { + BlobsWithCommitments.BlobQuad blobQuad = mapOfBlobsInTransactionPool.get(vh); + if (blobQuad == null) { + blobQuad = cacheForBlobsOfTransactionsAddedToABlock.get(vh); + } + return blobQuad; + } + public boolean isEnabled() { return isPoolEnabled.get(); } @@ -691,7 +715,7 @@ private void onAdded(final Transaction transaction) { } class SaveRestoreManager { - private final Lock diskAccessLock = new ReentrantLock(); + private final Semaphore diskAccessLock = new Semaphore(1, true); private final AtomicReference> writeInProgress = new AtomicReference<>(CompletableFuture.completedFuture(null)); private final AtomicReference> readInProgress = @@ -712,25 +736,21 @@ private CompletableFuture serializeAndDedupOperation( final AtomicReference> operationInProgress) { if (configuration.getEnableSaveRestore()) { try { - if (diskAccessLock.tryLock(1, TimeUnit.MINUTES)) { - try { - if (!operationInProgress.get().isDone()) { - isCancelled.set(true); - try { - operationInProgress.get().get(); - } catch (ExecutionException ee) { - // nothing to do - } + if (diskAccessLock.tryAcquire(1, TimeUnit.MINUTES)) { + if (!operationInProgress.get().isDone()) { + isCancelled.set(true); + try { + operationInProgress.get().get(); + } catch (ExecutionException ee) { + // nothing to do } - - isCancelled.set(false); - operationInProgress.set(CompletableFuture.runAsync(operation)); - return operationInProgress.get(); - } catch (InterruptedException ie) { - isCancelled.set(false); - } finally { - diskAccessLock.unlock(); } + + isCancelled.set(false); + operationInProgress.set( + CompletableFuture.runAsync(operation) + .whenComplete((res, err) -> diskAccessLock.release())); + return operationInProgress.get(); } else { CompletableFuture.failedFuture( new TimeoutException("Timeout waiting for disk access lock")); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolConfiguration.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolConfiguration.java index e30d24c158f..2f823830fcc 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolConfiguration.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolConfiguration.java @@ -15,12 +15,18 @@ package org.hyperledger.besu.ethereum.eth.transactions; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.plugin.services.TransactionPoolValidatorService; +import org.hyperledger.besu.plugin.services.txvalidator.PluginTransactionPoolValidator; +import org.hyperledger.besu.plugin.services.txvalidator.PluginTransactionPoolValidatorFactory; import org.hyperledger.besu.util.number.Fraction; import org.hyperledger.besu.util.number.Percentage; import java.io.File; import java.time.Duration; +import java.util.EnumMap; +import java.util.Map; import java.util.Set; import org.immutables.value.Value; @@ -50,8 +56,9 @@ default int getTxMessageKeepAliveSeconds() { } enum Implementation { - LEGACY, - LAYERED; + LEGACY, // Remove in future version + LAYERED, + SEQUENCED; // Synonym for LEGACY } String DEFAULT_SAVE_FILE_NAME = "txpool.dump"; @@ -60,16 +67,20 @@ enum Implementation { int DEFAULT_TX_RETENTION_HOURS = 13; boolean DEFAULT_STRICT_TX_REPLAY_PROTECTION_ENABLED = false; Percentage DEFAULT_PRICE_BUMP = Percentage.fromInt(10); + Percentage DEFAULT_BLOB_PRICE_BUMP = Percentage.fromInt(100); Wei DEFAULT_RPC_TX_FEE_CAP = Wei.fromEth(1); boolean DEFAULT_NO_LOCAL_PRIORITY = false; boolean DEFAULT_ENABLE_SAVE_RESTORE = false; File DEFAULT_SAVE_FILE = new File(DEFAULT_SAVE_FILE_NAME); long DEFAULT_PENDING_TRANSACTIONS_LAYER_MAX_CAPACITY_BYTES = 12_500_000L; int DEFAULT_MAX_PRIORITIZED_TRANSACTIONS = 2000; + EnumMap DEFAULT_MAX_PRIORITIZED_TRANSACTIONS_BY_TYPE = + new EnumMap<>(Map.of(TransactionType.BLOB, 6)); int DEFAULT_MAX_FUTURE_BY_SENDER = 200; Implementation DEFAULT_TX_POOL_IMPLEMENTATION = Implementation.LAYERED; Set
DEFAULT_PRIORITY_SENDERS = Set.of(); Wei DEFAULT_TX_POOL_MIN_GAS_PRICE = Wei.of(1000); + byte DEFAULT_TX_POOL_MIN_SCORE = -128; TransactionPoolConfiguration DEFAULT = ImmutableTransactionPoolConfiguration.builder().build(); @@ -98,6 +109,11 @@ default Percentage getPriceBump() { return DEFAULT_PRICE_BUMP; } + @Value.Default + default Percentage getBlobPriceBump() { + return DEFAULT_BLOB_PRICE_BUMP; + } + @Value.Default default Wei getTxFeeCap() { return DEFAULT_RPC_TX_FEE_CAP; @@ -138,6 +154,11 @@ default int getMaxPrioritizedTransactions() { return DEFAULT_MAX_PRIORITIZED_TRANSACTIONS; } + @Value.Default + default Map getMaxPrioritizedTransactionsByType() { + return DEFAULT_MAX_PRIORITIZED_TRANSACTIONS_BY_TYPE; + } + @Value.Default default int getMaxFutureBySender() { return DEFAULT_MAX_FUTURE_BY_SENDER; @@ -153,6 +174,25 @@ default Wei getMinGasPrice() { return DEFAULT_TX_POOL_MIN_GAS_PRICE; } + @Value.Default + default byte getMinScore() { + return DEFAULT_TX_POOL_MIN_SCORE; + } + + @Value.Default + default TransactionPoolValidatorService getTransactionPoolValidatorService() { + return new TransactionPoolValidatorService() { + @Override + public PluginTransactionPoolValidator createTransactionValidator() { + return PluginTransactionPoolValidator.VALIDATE_ALL; + } + + @Override + public void registerPluginTransactionValidatorFactory( + final PluginTransactionPoolValidatorFactory pluginTransactionPoolValidatorFactory) {} + }; + } + @Value.Default default Unstable getUnstable() { return Unstable.DEFAULT; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolEvictionService.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolEvictionService.java index c572f5847a1..fb72f303955 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolEvictionService.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolEvictionService.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactory.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactory.java index 97c2a1538fa..79b1298d27d 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactory.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactory.java @@ -17,7 +17,9 @@ import static org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration.Implementation.LAYERED; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.messages.EthPV62; import org.hyperledger.besu.ethereum.eth.messages.EthPV65; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; @@ -35,7 +37,6 @@ import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.plugin.services.BesuEvents; import org.hyperledger.besu.plugin.services.MetricsSystem; -import org.hyperledger.besu.plugin.services.txvalidator.PluginTransactionValidatorFactory; import java.time.Clock; import java.util.function.BiFunction; @@ -54,12 +55,13 @@ public static TransactionPool createTransactionPool( final MetricsSystem metricsSystem, final SyncState syncState, final TransactionPoolConfiguration transactionPoolConfiguration, - final PluginTransactionValidatorFactory pluginTransactionValidatorFactory, - final BlobCache blobCache) { + final BlobCache blobCache, + final MiningParameters miningParameters) { final TransactionPoolMetrics metrics = new TransactionPoolMetrics(metricsSystem); - final PeerTransactionTracker transactionTracker = new PeerTransactionTracker(); + final PeerTransactionTracker transactionTracker = + new PeerTransactionTracker(ethContext.getEthPeers()); final TransactionsMessageSender transactionsMessageSender = new TransactionsMessageSender(transactionTracker); @@ -77,8 +79,8 @@ public static TransactionPool createTransactionPool( transactionTracker, transactionsMessageSender, newPooledTransactionHashesMessageSender, - pluginTransactionValidatorFactory, - blobCache); + blobCache, + miningParameters); } static TransactionPool createTransactionPool( @@ -92,8 +94,8 @@ static TransactionPool createTransactionPool( final PeerTransactionTracker transactionTracker, final TransactionsMessageSender transactionsMessageSender, final NewPooledTransactionHashesMessageSender newPooledTransactionHashesMessageSender, - final PluginTransactionValidatorFactory pluginTransactionValidatorFactory, - final BlobCache blobCache) { + final BlobCache blobCache, + final MiningParameters miningParameters) { final TransactionPool transactionPool = new TransactionPool( @@ -101,10 +103,12 @@ static TransactionPool createTransactionPool( createPendingTransactions( protocolSchedule, protocolContext, + ethContext.getScheduler(), clock, metrics, transactionPoolConfiguration, - blobCache), + blobCache, + miningParameters), protocolSchedule, protocolContext, new TransactionBroadcaster( @@ -115,7 +119,7 @@ static TransactionPool createTransactionPool( ethContext, metrics, transactionPoolConfiguration, - pluginTransactionValidatorFactory); + blobCache); final TransactionsMessageHandler transactionsMessageHandler = new TransactionsMessageHandler( @@ -156,24 +160,64 @@ static TransactionPool createTransactionPool( @Override public void onInitialSyncCompleted() { LOG.info("Enabling transaction handling following initial sync"); - transactionTracker.reset(); - transactionPool.setEnabled(); - transactionsMessageHandler.setEnabled(); - pooledTransactionsMessageHandler.setEnabled(); + enableTransactionHandling( + transactionTracker, + transactionPool, + transactionsMessageHandler, + pooledTransactionsMessageHandler); } @Override public void onInitialSyncRestart() { LOG.info("Disabling transaction handling during re-sync"); - pooledTransactionsMessageHandler.setDisabled(); - transactionsMessageHandler.setDisabled(); - transactionPool.setDisabled(); + disableTransactionHandling( + transactionPool, transactionsMessageHandler, pooledTransactionsMessageHandler); + } + }); + + syncState.subscribeInSync( + isInSync -> { + if (isInSync != transactionPool.isEnabled()) { + if (isInSync && syncState.isInitialSyncPhaseDone()) { + LOG.info("Node is in sync, enabling transaction handling"); + enableTransactionHandling( + transactionTracker, + transactionPool, + transactionsMessageHandler, + pooledTransactionsMessageHandler); + } else { + if (transactionPool.isEnabled()) { + LOG.info("Node out of sync, disabling transaction handling"); + disableTransactionHandling( + transactionPool, transactionsMessageHandler, pooledTransactionsMessageHandler); + } + } } }); return transactionPool; } + private static void enableTransactionHandling( + final PeerTransactionTracker transactionTracker, + final TransactionPool transactionPool, + final TransactionsMessageHandler transactionsMessageHandler, + final NewPooledTransactionHashesMessageHandler pooledTransactionsMessageHandler) { + transactionTracker.reset(); + transactionPool.setEnabled(); + transactionsMessageHandler.setEnabled(); + pooledTransactionsMessageHandler.setEnabled(); + } + + private static void disableTransactionHandling( + final TransactionPool transactionPool, + final TransactionsMessageHandler transactionsMessageHandler, + final NewPooledTransactionHashesMessageHandler pooledTransactionsMessageHandler) { + transactionPool.setDisabled(); + transactionsMessageHandler.setDisabled(); + pooledTransactionsMessageHandler.setDisabled(); + } + private static void subscribeTransactionHandlers( final ProtocolContext protocolContext, final EthContext ethContext, @@ -192,10 +236,12 @@ private static void subscribeTransactionHandlers( private static PendingTransactions createPendingTransactions( final ProtocolSchedule protocolSchedule, final ProtocolContext protocolContext, + final EthScheduler ethScheduler, final Clock clock, final TransactionPoolMetrics metrics, final TransactionPoolConfiguration transactionPoolConfiguration, - final BlobCache blobCache) { + final BlobCache blobCache, + final MiningParameters miningParameters) { boolean isFeeMarketImplementBaseFee = protocolSchedule.anyMatch( @@ -205,10 +251,12 @@ private static PendingTransactions createPendingTransactions( return createLayeredPendingTransactions( protocolSchedule, protocolContext, + ethScheduler, metrics, transactionPoolConfiguration, isFeeMarketImplementBaseFee, - blobCache); + blobCache, + miningParameters); } else { return createPendingTransactionSorter( protocolContext, @@ -243,13 +291,17 @@ private static AbstractPendingTransactionsSorter createPendingTransactionSorter( private static PendingTransactions createLayeredPendingTransactions( final ProtocolSchedule protocolSchedule, final ProtocolContext protocolContext, + final EthScheduler ethScheduler, final TransactionPoolMetrics metrics, final TransactionPoolConfiguration transactionPoolConfiguration, final boolean isFeeMarketImplementBaseFee, - final BlobCache blobCache) { + final BlobCache blobCache, + final MiningParameters miningParameters) { final TransactionPoolReplacementHandler transactionReplacementHandler = - new TransactionPoolReplacementHandler(transactionPoolConfiguration.getPriceBump()); + new TransactionPoolReplacementHandler( + transactionPoolConfiguration.getPriceBump(), + transactionPoolConfiguration.getBlobPriceBump()); final BiFunction transactionReplacementTester = (t1, t2) -> @@ -261,6 +313,7 @@ private static PendingTransactions createLayeredPendingTransactions( final SparseTransactions sparseTransactions = new SparseTransactions( transactionPoolConfiguration, + ethScheduler, endLayer, metrics, transactionReplacementTester, @@ -269,6 +322,7 @@ private static PendingTransactions createLayeredPendingTransactions( final ReadyTransactions readyTransactions = new ReadyTransactions( transactionPoolConfiguration, + ethScheduler, sparseTransactions, metrics, transactionReplacementTester, @@ -285,21 +339,26 @@ private static PendingTransactions createLayeredPendingTransactions( new BaseFeePrioritizedTransactions( transactionPoolConfiguration, protocolContext.getBlockchain()::getChainHeadHeader, + ethScheduler, readyTransactions, metrics, transactionReplacementTester, feeMarket, - blobCache); + blobCache, + miningParameters); } else { pendingTransactionsSorter = new GasPricePrioritizedTransactions( transactionPoolConfiguration, + ethScheduler, readyTransactions, metrics, transactionReplacementTester, - blobCache); + blobCache, + miningParameters); } - return new LayeredPendingTransactions(transactionPoolConfiguration, pendingTransactionsSorter); + return new LayeredPendingTransactions( + transactionPoolConfiguration, pendingTransactionsSorter, ethScheduler); } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolMetrics.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolMetrics.java index 9ace1819cc1..e08805551f9 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolMetrics.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolMetrics.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,8 +14,11 @@ */ package org.hyperledger.besu.ethereum.eth.transactions; +import org.hyperledger.besu.datatypes.TransactionType; +import org.hyperledger.besu.ethereum.eth.transactions.layered.AddReason; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; import org.hyperledger.besu.metrics.BesuMetricCategory; +import org.hyperledger.besu.metrics.ReplaceableDoubleSupplier; import org.hyperledger.besu.metrics.RunnableCounter; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.plugin.services.metrics.Counter; @@ -26,6 +29,7 @@ import java.util.Map; import java.util.function.DoubleSupplier; +import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,18 +38,26 @@ public class TransactionPoolMetrics { public static final String ADDED_COUNTER_NAME = "added_total"; public static final String REMOVED_COUNTER_NAME = "removed_total"; public static final String REJECTED_COUNTER_NAME = "rejected_total"; + public static final String PENALIZED_COUNTER_NAME = "penalized_total"; public static final String EXPIRED_MESSAGES_COUNTER_NAME = "messages_expired_total"; private static final int SKIPPED_MESSAGES_LOGGING_THRESHOLD = 1000; private final MetricsSystem metricsSystem; private final LabelledMetric addedCounter; private final LabelledMetric removedCounter; private final LabelledMetric rejectedCounter; + private final LabelledMetric penalizedCounter; private final LabelledGauge spaceUsed; private final LabelledGauge transactionCount; + private final LabelledGauge transactionCountByType; private final LabelledGauge uniqueSenderCount; private final LabelledMetric expiredMessagesCounter; private final Map expiredMessagesRunnableCounters = new HashMap<>(); private final LabelledMetric alreadySeenTransactionsCounter; + private final Map spaceUsedSuppliers = new HashMap<>(); + private final Map transactionCountSuppliers = new HashMap<>(); + private final Map, ReplaceableDoubleSupplier> + transactionCountByTypeSuppliers = new HashMap<>(); + private final Map uniqueSendersSuppliers = new HashMap<>(); public TransactionPoolMetrics(final MetricsSystem metricsSystem) { this.metricsSystem = metricsSystem; @@ -57,6 +69,7 @@ public TransactionPoolMetrics(final MetricsSystem metricsSystem) { "Count of transactions added to the transaction pool", "source", "priority", + "reason", "layer"); removedCounter = @@ -79,6 +92,15 @@ public TransactionPoolMetrics(final MetricsSystem metricsSystem) { "reason", "layer"); + penalizedCounter = + metricsSystem.createLabelledCounter( + BesuMetricCategory.TRANSACTION_POOL, + PENALIZED_COUNTER_NAME, + "Count of penalized transactions in the transaction pool", + "source", + "priority", + "layer"); + spaceUsed = metricsSystem.createLabelledGauge( BesuMetricCategory.TRANSACTION_POOL, @@ -93,6 +115,14 @@ public TransactionPoolMetrics(final MetricsSystem metricsSystem) { "The number of transactions currently present in the layer", "layer"); + transactionCountByType = + metricsSystem.createLabelledGauge( + BesuMetricCategory.TRANSACTION_POOL, + "number_of_transactions_by_type", + "The number of transactions, of a specified type, currently present in the layer", + "layer", + "type"); + uniqueSenderCount = metricsSystem.createLabelledGauge( BesuMetricCategory.TRANSACTION_POOL, @@ -120,17 +150,58 @@ public MetricsSystem getMetricsSystem() { } public void initSpaceUsed(final DoubleSupplier spaceUsedSupplier, final String layer) { - spaceUsed.labels(spaceUsedSupplier, layer); + spaceUsedSuppliers.compute( + layer, + (unused, existingSupplier) -> { + if (existingSupplier == null) { + final var newSupplier = new ReplaceableDoubleSupplier(spaceUsedSupplier); + spaceUsed.labels(newSupplier, layer); + return newSupplier; + } + return existingSupplier.replaceDoubleSupplier(spaceUsedSupplier); + }); + } + + public void initTransactionCountByType( + final DoubleSupplier spaceUsedSupplier, final String layer, final TransactionType type) { + transactionCountByTypeSuppliers.compute( + Pair.of(layer, type), + (unused, existingSupplier) -> { + if (existingSupplier == null) { + final var newSupplier = new ReplaceableDoubleSupplier(spaceUsedSupplier); + transactionCountByType.labels(newSupplier, layer, type.name()); + return newSupplier; + } + return existingSupplier.replaceDoubleSupplier(spaceUsedSupplier); + }); } public void initTransactionCount( final DoubleSupplier transactionCountSupplier, final String layer) { - transactionCount.labels(transactionCountSupplier, layer); + transactionCountSuppliers.compute( + layer, + (unused, existingSupplier) -> { + if (existingSupplier == null) { + final var newSupplier = new ReplaceableDoubleSupplier(transactionCountSupplier); + transactionCount.labels(newSupplier, layer); + return newSupplier; + } + return existingSupplier.replaceDoubleSupplier(transactionCountSupplier); + }); } public void initUniqueSenderCount( final DoubleSupplier uniqueSenderCountSupplier, final String layer) { - uniqueSenderCount.labels(uniqueSenderCountSupplier, layer); + uniqueSendersSuppliers.compute( + layer, + (unused, existingSupplier) -> { + if (existingSupplier == null) { + final var newSupplier = new ReplaceableDoubleSupplier(uniqueSenderCountSupplier); + uniqueSenderCount.labels(newSupplier, layer); + return newSupplier; + } + return existingSupplier.replaceDoubleSupplier(uniqueSenderCountSupplier); + }); } public void initExpiredMessagesCounter(final String message) { @@ -146,11 +217,13 @@ public void initExpiredMessagesCounter(final String message) { SKIPPED_MESSAGES_LOGGING_THRESHOLD)); } - public void incrementAdded(final PendingTransaction pendingTransaction, final String layer) { + public void incrementAdded( + final PendingTransaction pendingTransaction, final AddReason addReason, final String layer) { addedCounter .labels( location(pendingTransaction.isReceivedFromLocalSource()), priority(pendingTransaction.hasPriority()), + addReason.label(), layer) .inc(); } @@ -188,6 +261,15 @@ public void incrementRejected( .inc(); } + public void incrementPenalized(final PendingTransaction pendingTransaction, final String layer) { + penalizedCounter + .labels( + location(pendingTransaction.isReceivedFromLocalSource()), + priority(pendingTransaction.hasPriority()), + layer) + .inc(); + } + public void incrementExpiredMessages(final String message) { expiredMessagesCounter.labels(message).inc(); } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolReplacementHandler.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolReplacementHandler.java index 6e2138798ea..10a4422e35e 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolReplacementHandler.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolReplacementHandler.java @@ -26,11 +26,12 @@ public class TransactionPoolReplacementHandler { private final List rules; - public TransactionPoolReplacementHandler(final Percentage priceBump) { + public TransactionPoolReplacementHandler( + final Percentage priceBump, final Percentage blobPriceBump) { this( asList( new TransactionReplacementByGasPriceRule(priceBump), - new TransactionReplacementByFeeMarketRule(priceBump))); + new TransactionReplacementByFeeMarketRule(priceBump, blobPriceBump))); } @VisibleForTesting diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementByFeeMarketRule.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementByFeeMarketRule.java index 559d6f51420..2ba3d5f5e5e 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementByFeeMarketRule.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementByFeeMarketRule.java @@ -28,9 +28,12 @@ public class TransactionReplacementByFeeMarketRule implements TransactionPoolRep private static final TransactionPriceCalculator EIP1559_CALCULATOR = TransactionPriceCalculator.eip1559(); private final Percentage priceBump; + private final Percentage blobPriceBump; - public TransactionReplacementByFeeMarketRule(final Percentage priceBump) { + public TransactionReplacementByFeeMarketRule( + final Percentage priceBump, final Percentage blobPriceBump) { this.priceBump = priceBump; + this.blobPriceBump = blobPriceBump; } @Override @@ -39,6 +42,16 @@ public boolean shouldReplace( final PendingTransaction newPendingTransaction, final Optional maybeBaseFee) { + return validExecutionPriceReplacement( + existingPendingTransaction, newPendingTransaction, maybeBaseFee) + && validBlobPriceReplacement(existingPendingTransaction, newPendingTransaction); + } + + private boolean validExecutionPriceReplacement( + final PendingTransaction existingPendingTransaction, + final PendingTransaction newPendingTransaction, + final Optional maybeBaseFee) { + // bail early if basefee is absent or neither transaction supports 1559 fee market if (maybeBaseFee.isEmpty() || !(isNotGasPriced(existingPendingTransaction) || isNotGasPriced(newPendingTransaction))) { @@ -65,6 +78,37 @@ public boolean shouldReplace( return false; } + private boolean validBlobPriceReplacement( + final PendingTransaction existingPendingTransaction, + final PendingTransaction newPendingTransaction) { + + final var existingType = existingPendingTransaction.getTransaction().getType(); + final var newType = newPendingTransaction.getTransaction().getType(); + + if (existingType.supportsBlob() || newType.supportsBlob()) { + if (existingType.supportsBlob() && newType.supportsBlob()) { + final Wei replacementThreshold = + existingPendingTransaction + .getTransaction() + .getMaxFeePerBlobGas() + .orElseThrow() + .multiply(100 + blobPriceBump.getValue()) + .divide(100); + return newPendingTransaction + .getTransaction() + .getMaxFeePerBlobGas() + .orElseThrow() + .compareTo(replacementThreshold) + >= 0; + } + // blob tx can only replace and be replaced by blob tx + return false; + } + + // in case no blob tx, then we are fine + return true; + } + private Wei priceOf(final Transaction transaction, final Optional maybeBaseFee) { final TransactionPriceCalculator transactionPriceCalculator = transaction.getType().supports1559FeeMarket() ? EIP1559_CALCULATOR : FRONTIER_CALCULATOR; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionsMessageProcessor.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionsMessageProcessor.java index aee229990a3..fd9d0170ab8 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionsMessageProcessor.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionsMessageProcessor.java @@ -92,7 +92,7 @@ private void processTransactionsMessage( "Malformed transaction message received (BREACH_OF_PROTOCOL), disconnecting: {}", peer, ex); - peer.disconnect(DisconnectReason.BREACH_OF_PROTOCOL); + peer.disconnect(DisconnectReason.BREACH_OF_PROTOCOL_MALFORMED_MESSAGE_RECEIVED); } } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionsMessageSender.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionsMessageSender.java index eb0eb538e4b..1173fce69a8 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionsMessageSender.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionsMessageSender.java @@ -52,11 +52,13 @@ void sendTransactionsToPeer(final EthPeer peer) { LOG.atTrace() .setMessage( "Sending transactions to peer {} all transactions count {}, " - + "single message transactions {}, single message list {}") + + "single message transactions {}, single message list {}, transactions {}, AgreedCapabilities {}") .addArgument(peer) .addArgument(allTxToSend::size) .addArgument(includedTransactions::size) .addArgument(() -> toHashList(includedTransactions)) + .addArgument(() -> includedTransactions) + .addArgument(peer::getAgreedCapabilities) .log(); allTxToSend.removeAll(limitedTransactionsMessages.getIncludedTransactions()); try { diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractPrioritizedTransactions.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractPrioritizedTransactions.java index 7d59f40afb7..470e7f5b7b7 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractPrioritizedTransactions.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractPrioritizedTransactions.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,12 +15,18 @@ package org.hyperledger.besu.ethereum.eth.transactions.layered; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.TransactionType; +import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolMetrics; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.NavigableMap; @@ -28,7 +34,7 @@ import java.util.TreeSet; import java.util.function.BiFunction; import java.util.function.Predicate; -import java.util.stream.Stream; +import java.util.stream.Collectors; /** * Holds the current set of executable pending transactions, that are candidate for inclusion on @@ -36,16 +42,26 @@ */ public abstract class AbstractPrioritizedTransactions extends AbstractSequentialTransactionsLayer { protected final TreeSet orderByFee; + protected final MiningParameters miningParameters; public AbstractPrioritizedTransactions( final TransactionPoolConfiguration poolConfig, + final EthScheduler ethScheduler, final TransactionsLayer prioritizedTransactions, final TransactionPoolMetrics metrics, final BiFunction transactionReplacementTester, - final BlobCache blobCache) { - super(poolConfig, prioritizedTransactions, transactionReplacementTester, metrics, blobCache); + final BlobCache blobCache, + final MiningParameters miningParameters) { + super( + poolConfig, + ethScheduler, + prioritizedTransactions, + transactionReplacementTester, + metrics, + blobCache); this.orderByFee = new TreeSet<>(this::compareByFee); + this.miningParameters = miningParameters; } @Override @@ -84,6 +100,15 @@ protected void internalReplaced(final PendingTransaction replacedTx) { } private boolean hasPriority(final PendingTransaction pendingTransaction) { + // check if there is space for that tx type + final var txType = pendingTransaction.getTransaction().getType(); + if (txCountByType[txType.ordinal()] + >= poolConfig + .getMaxPrioritizedTransactionsByType() + .getOrDefault(txType, Integer.MAX_VALUE)) { + return false; + } + // if it does not pass the promotion filter, then has not priority if (!promotionFilter(pendingTransaction)) { return false; @@ -116,17 +141,116 @@ protected void internalRemove( orderByFee.remove(removedTx); } + @Override + protected void internalPenalize(final PendingTransaction penalizedTx) { + orderByFee.remove(penalizedTx); + penalizedTx.decrementScore(); + orderByFee.add(penalizedTx); + } + @Override public List promote( final Predicate promotionFilter, final long freeSpace, - final int freeSlots) { + final int freeSlots, + final int[] remainingPromotionsPerType) { return List.of(); } + /** + * Here the max number of txs of a specific type that can be promoted, is defined by the + * configuration, so we return the difference between the configured max and the current count of + * txs for each type + * + * @return an array containing the max amount of txs that can be promoted for each type + */ @Override - public Stream stream() { - return orderByFee.descendingSet().stream(); + protected int[] getRemainingPromotionsPerType() { + final var allTypes = TransactionType.values(); + final var remainingPromotionsPerType = new int[allTypes.length]; + for (int i = 0; i < allTypes.length; i++) { + remainingPromotionsPerType[i] = + poolConfig + .getMaxPrioritizedTransactionsByType() + .getOrDefault(allTypes[i], Integer.MAX_VALUE) + - txCountByType[i]; + } + return remainingPromotionsPerType; + } + + /** + * Return the full content of this layer, organized as a list of sender pending txs. For each + * sender the collection pending txs is ordered by nonce asc. + * + *

Returned sender list order detail: first the sender of the most profitable tx. + * + * @return a list of sender pending txs + */ + @Override + public List getBySender() { + final var sendersToAdd = new HashSet<>(txsBySender.keySet()); + return orderByFee.descendingSet().stream() + .map(PendingTransaction::getSender) + .filter(sendersToAdd::remove) + .map( + sender -> + new SenderPendingTransactions( + sender, List.copyOf(txsBySender.get(sender).values()))) + .toList(); + } + + /** + * Returns pending txs by sender and ordered by score desc. In case a sender has pending txs with + * different scores, then in nonce sequence, every time there is a score decrease, his pending txs + * will be put in a new entry with that score. For example if a sender has 3 pending txs (where + * the first number is the nonce and the score is between parenthesis): 0(127), 1(126), 2(127), + * then for he there will be 2 entries: + * + *

    + *
  • 0(127) + *
  • 1(126), 2(127) + *
+ * + * @return pending txs by sender and ordered by score desc + */ + public NavigableMap> getByScore() { + final var sendersToAdd = new HashSet<>(txsBySender.keySet()); + return orderByFee.descendingSet().stream() + .map(PendingTransaction::getSender) + .filter(sendersToAdd::remove) + .flatMap(sender -> splitByScore(sender, txsBySender.get(sender)).entrySet().stream()) + .collect( + Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue, + (a, b) -> { + a.addAll(b); + return a; + }, + TreeMap::new)) + .descendingMap(); + } + + private Map> splitByScore( + final Address sender, final NavigableMap txsBySender) { + final var splitByScore = new HashMap>(); + byte currScore = txsBySender.firstEntry().getValue().getScore(); + var currSplit = new ArrayList(); + for (final var entry : txsBySender.entrySet()) { + if (entry.getValue().getScore() < currScore) { + // score decreased, we need to save current split and start a new one + splitByScore + .computeIfAbsent(currScore, k -> new ArrayList<>()) + .add(new SenderPendingTransactions(sender, currSplit)); + currSplit = new ArrayList<>(); + currScore = entry.getValue().getScore(); + } + currSplit.add(entry.getValue()); + } + splitByScore + .computeIfAbsent(currScore, k -> new ArrayList<>()) + .add(new SenderPendingTransactions(sender, currSplit)); + return splitByScore; } @Override @@ -136,7 +260,7 @@ protected long cacheFreeSpace() { @Override protected void internalConsistencyCheck( - final Map> prevLayerTxsBySender) { + final Map> prevLayerTxsBySender) { super.internalConsistencyCheck(prevLayerTxsBySender); final var controlOrderByFee = new TreeSet<>(this::compareByFee); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractSequentialTransactionsLayer.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractSequentialTransactionsLayer.java index 9596f6fefb7..f7d71ca3724 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractSequentialTransactionsLayer.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractSequentialTransactionsLayer.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,41 +14,44 @@ */ package org.hyperledger.besu.ethereum.eth.transactions.layered; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.EVICTED; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.FOLLOW_INVALIDATED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.AddReason.MOVE; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.LayerMoveReason.EVICTED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.LayerMoveReason.FOLLOW_INVALIDATED; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolMetrics; +import org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason; import java.util.Map; import java.util.NavigableMap; import java.util.OptionalLong; import java.util.Set; -import java.util.TreeMap; import java.util.function.BiFunction; public abstract class AbstractSequentialTransactionsLayer extends AbstractTransactionsLayer { public AbstractSequentialTransactionsLayer( final TransactionPoolConfiguration poolConfig, + final EthScheduler ethScheduler, final TransactionsLayer nextLayer, final BiFunction transactionReplacementTester, final TransactionPoolMetrics metrics, final BlobCache blobCache) { - super(poolConfig, nextLayer, transactionReplacementTester, metrics, blobCache); + super(poolConfig, ethScheduler, nextLayer, transactionReplacementTester, metrics, blobCache); } @Override - public void remove(final PendingTransaction invalidatedTx, final RemovalReason reason) { + public void remove(final PendingTransaction invalidatedTx, final PoolRemovalReason reason) { nextLayer.remove(invalidatedTx, reason); final var senderTxs = txsBySender.get(invalidatedTx.getSender()); final long invalidNonce = invalidatedTx.getNonce(); - if (senderTxs != null && invalidNonce <= senderTxs.lastKey()) { + if (senderTxs != null && Long.compareUnsigned(invalidNonce, senderTxs.lastKey()) <= 0) { // on sequential layers we need to push to next layer all the txs following the invalid one, // even if it belongs to a previous layer @@ -76,7 +79,7 @@ private void pushDown( senderTxs.remove(txToRemove.getNonce()); processRemove(senderTxs, txToRemove.getTransaction(), FOLLOW_INVALIDATED); }) - .forEach(followingTx -> nextLayer.add(followingTx, gap)); + .forEach(followingTx -> nextLayer.add(followingTx, gap, MOVE)); } @Override @@ -142,7 +145,7 @@ protected boolean hasExpectedNonce( @Override protected void internalConsistencyCheck( - final Map> prevLayerTxsBySender) { + final Map> prevLayerTxsBySender) { txsBySender.values().stream() .filter(senderTxs -> senderTxs.size() > 1) .map(NavigableMap::entrySet) diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractTransactionsLayer.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractTransactionsLayer.java index a323b6833e9..ca980b219dc 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractTransactionsLayer.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractTransactionsLayer.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,20 +14,25 @@ */ package org.hyperledger.besu.ethereum.eth.transactions.layered; +import static java.util.Collections.unmodifiableList; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.ADDED; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.ALREADY_KNOWN; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.REJECTED_UNDERPRICED_REPLACEMENT; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.TRY_NEXT_LAYER; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.CONFIRMED; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.CROSS_LAYER_REPLACED; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.EVICTED; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.PROMOTED; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.REPLACED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.AddReason.MOVE; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.LayerMoveReason.EVICTED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason.BELOW_MIN_SCORE; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason.CONFIRMED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason.CROSS_LAYER_REPLACED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason.REPLACED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.RemovedFrom.POOL; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactionAddedListener; @@ -39,6 +44,7 @@ import org.hyperledger.besu.util.Subscribers; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -51,7 +57,6 @@ import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.stream.Collectors; -import java.util.stream.Stream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,6 +64,13 @@ public abstract class AbstractTransactionsLayer implements TransactionsLayer { private static final Logger LOG = LoggerFactory.getLogger(AbstractTransactionsLayer.class); private static final NavigableMap EMPTY_SENDER_TXS = new TreeMap<>(); + private static final int[] UNLIMITED_PROMOTIONS_PER_TYPE = + new int[TransactionType.values().length]; + + static { + Arrays.fill(UNLIMITED_PROMOTIONS_PER_TYPE, Integer.MAX_VALUE); + } + protected final TransactionPoolConfiguration poolConfig; protected final TransactionsLayer nextLayer; protected final BiFunction @@ -74,23 +86,31 @@ public abstract class AbstractTransactionsLayer implements TransactionsLayer { private OptionalLong nextLayerOnAddedListenerId = OptionalLong.empty(); private OptionalLong nextLayerOnDroppedListenerId = OptionalLong.empty(); protected long spaceUsed = 0; - + protected final int[] txCountByType = new int[TransactionType.values().length]; private final BlobCache blobCache; + private final EthScheduler ethScheduler; protected AbstractTransactionsLayer( final TransactionPoolConfiguration poolConfig, + final EthScheduler ethScheduler, final TransactionsLayer nextLayer, final BiFunction transactionReplacementTester, final TransactionPoolMetrics metrics, final BlobCache blobCache) { this.poolConfig = poolConfig; + this.ethScheduler = ethScheduler; this.nextLayer = nextLayer; this.transactionReplacementTester = transactionReplacementTester; this.metrics = metrics; metrics.initSpaceUsed(this::getLayerSpaceUsed, name()); metrics.initTransactionCount(pendingTransactions::size, name()); metrics.initUniqueSenderCount(txsBySender::size, name()); + Arrays.stream(TransactionType.values()) + .forEach( + type -> + metrics.initTransactionCountByType( + () -> txCountByType[type.ordinal()], name(), type)); this.blobCache = blobCache; } @@ -101,6 +121,7 @@ public void reset() { pendingTransactions.clear(); txsBySender.clear(); spaceUsed = 0; + Arrays.fill(txCountByType, 0); nextLayer.reset(); } @@ -119,6 +140,14 @@ public boolean contains(final Transaction transaction) { || nextLayer.contains(transaction); } + /** + * Return the full content of this layer, organized as a list of sender pending txs. For each + * sender the collection pending txs is ordered by nonce asc. + * + * @return a list of sender pending txs + */ + public abstract List getBySender(); + @Override public List getAll() { final List allNextLayers = nextLayer.getAll(); @@ -142,7 +171,8 @@ protected abstract TransactionAddedResult canAdd( final PendingTransaction pendingTransaction, final int gap); @Override - public TransactionAddedResult add(final PendingTransaction pendingTransaction, final int gap) { + public TransactionAddedResult add( + final PendingTransaction pendingTransaction, final int gap, final AddReason addReason) { // is replacing an existing one? TransactionAddedResult addStatus = maybeReplaceTransaction(pendingTransaction); @@ -151,21 +181,26 @@ public TransactionAddedResult add(final PendingTransaction pendingTransaction, f } if (addStatus.equals(TRY_NEXT_LAYER)) { - return addToNextLayer(pendingTransaction, gap); + return addToNextLayer(pendingTransaction, gap, addReason); } if (addStatus.isSuccess()) { - processAdded(pendingTransaction.detachedCopy()); + final var addedPendingTransaction = + addReason.makeCopy() ? pendingTransaction.detachedCopy() : pendingTransaction; + processAdded(addedPendingTransaction, addReason); addStatus.maybeReplacedTransaction().ifPresent(this::replaced); - nextLayer.notifyAdded(pendingTransaction); + nextLayer.notifyAdded(addedPendingTransaction); if (!maybeFull()) { // if there is space try to see if the added tx filled some gaps - tryFillGap(addStatus, pendingTransaction); + tryFillGap(addStatus, addedPendingTransaction, getRemainingPromotionsPerType()); + } + + if (addReason.sendNotification()) { + ethScheduler.scheduleTxWorkerTask(() -> notifyTransactionAdded(addedPendingTransaction)); } - notifyTransactionAdded(pendingTransaction); } else { final var rejectReason = addStatus.maybeInvalidReason().orElseThrow(); metrics.incrementRejected(pendingTransaction, rejectReason, name()); @@ -199,16 +234,21 @@ private boolean maybeFull() { } private void tryFillGap( - final TransactionAddedResult addStatus, final PendingTransaction pendingTransaction) { + final TransactionAddedResult addStatus, + final PendingTransaction pendingTransaction, + final int[] remainingPromotionsPerType) { // it makes sense to fill gaps only if the add is not a replacement and this layer does not // allow gaps if (!addStatus.isReplacement() && !gapsAllowed()) { final PendingTransaction promotedTx = - nextLayer.promoteFor(pendingTransaction.getSender(), pendingTransaction.getNonce()); + nextLayer.promoteFor( + pendingTransaction.getSender(), + pendingTransaction.getNonce(), + remainingPromotionsPerType); if (promotedTx != null) { - processAdded(promotedTx); + processAdded(promotedTx, AddReason.PROMOTED); if (!maybeFull()) { - tryFillGap(ADDED, promotedTx); + tryFillGap(ADDED, promotedTx, remainingPromotionsPerType); } } } @@ -243,51 +283,62 @@ protected abstract void internalNotifyAdded( final PendingTransaction pendingTransaction); @Override - public PendingTransaction promoteFor(final Address sender, final long nonce) { + public PendingTransaction promoteFor( + final Address sender, final long nonce, final int[] remainingPromotionsPerType) { final var senderTxs = txsBySender.get(sender); if (senderTxs != null) { long expectedNonce = nonce + 1; if (senderTxs.firstKey() == expectedNonce) { - final PendingTransaction promotedTx = senderTxs.pollFirstEntry().getValue(); - processRemove(senderTxs, promotedTx.getTransaction(), PROMOTED); - metrics.incrementRemoved(promotedTx, "promoted", name()); - - if (senderTxs.isEmpty()) { - txsBySender.remove(sender); + final var candidateTx = senderTxs.firstEntry().getValue(); + final var txType = candidateTx.getTransaction().getType(); + + if (remainingPromotionsPerType[txType.ordinal()] > 0) { + senderTxs.pollFirstEntry(); + processRemove( + senderTxs, candidateTx.getTransaction(), RemovalReason.LayerMoveReason.PROMOTED); + metrics.incrementRemoved(candidateTx, "promoted", name()); + + if (senderTxs.isEmpty()) { + txsBySender.remove(sender); + } + --remainingPromotionsPerType[txType.ordinal()]; + return candidateTx; } - return promotedTx; + return null; } } - return nextLayer.promoteFor(sender, nonce); + return nextLayer.promoteFor(sender, nonce, remainingPromotionsPerType); } private TransactionAddedResult addToNextLayer( - final PendingTransaction pendingTransaction, final int distance) { + final PendingTransaction pendingTransaction, final int distance, final AddReason addReason) { return addToNextLayer( txsBySender.getOrDefault(pendingTransaction.getSender(), EMPTY_SENDER_TXS), pendingTransaction, - distance); + distance, + addReason); } protected TransactionAddedResult addToNextLayer( final NavigableMap senderTxs, final PendingTransaction pendingTransaction, - final int distance) { + final int distance, + final AddReason addReason) { final int nextLayerDistance; if (senderTxs.isEmpty()) { nextLayerDistance = distance; } else { nextLayerDistance = (int) (pendingTransaction.getNonce() - (senderTxs.lastKey() + 1)); } - return nextLayer.add(pendingTransaction, nextLayerDistance); + return nextLayer.add(pendingTransaction, nextLayerDistance, addReason); } - private void processAdded(final PendingTransaction addedTx) { + private void processAdded(final PendingTransaction addedTx, final AddReason addReason) { pendingTransactions.put(addedTx.getHash(), addedTx); final var senderTxs = txsBySender.computeIfAbsent(addedTx.getSender(), s -> new TreeMap<>()); senderTxs.put(addedTx.getNonce(), addedTx); - increaseSpaceUsed(addedTx); - metrics.incrementAdded(addedTx, name()); + increaseCounters(addedTx); + metrics.incrementAdded(addedTx, addReason, name()); internalAdd(senderTxs, addedTx); } @@ -313,7 +364,7 @@ private void evict(final long spaceToFree, final int txsToEvict) { ++evictedCount; evictedSize += lastTx.memorySize(); // evicted can always be added to the next layer - addToNextLayer(lessReadySenderTxs, lastTx, 0); + addToNextLayer(lessReadySenderTxs, lastTx, 0, MOVE); } if (lessReadySenderTxs.isEmpty()) { @@ -332,7 +383,7 @@ private void evict(final long spaceToFree, final int txsToEvict) { protected void replaced(final PendingTransaction replacedTx) { pendingTransactions.remove(replacedTx.getHash()); - decreaseSpaceUsed(replacedTx); + decreaseCounters(replacedTx); metrics.incrementRemoved(replacedTx, REPLACED.label(), name()); internalReplaced(replacedTx); notifyTransactionDropped(replacedTx); @@ -368,9 +419,12 @@ protected PendingTransaction processRemove( final PendingTransaction removedTx = pendingTransactions.remove(transaction.getHash()); if (removedTx != null) { - decreaseSpaceUsed(removedTx); + decreaseCounters(removedTx); metrics.incrementRemoved(removedTx, removalReason.label(), name()); internalRemove(senderTxs, removedTx, removalReason); + if (removalReason.removedFrom().equals(POOL)) { + notifyTransactionDropped(removedTx); + } } return removedTx; } @@ -381,7 +435,7 @@ protected PendingTransaction processEvict( final RemovalReason reason) { final PendingTransaction removedTx = pendingTransactions.remove(evictedTx.getHash()); if (removedTx != null) { - decreaseSpaceUsed(evictedTx); + decreaseCounters(evictedTx); metrics.incrementRemoved(evictedTx, reason.label(), name()); internalEvict(senderTxs, removedTx); } @@ -417,11 +471,39 @@ final void promoteTransactions() { if (freeSlots > 0 && freeSpace > 0) { nextLayer - .promote(this::promotionFilter, cacheFreeSpace(), freeSlots) - .forEach(this::processAdded); + .promote( + this::promotionFilter, cacheFreeSpace(), freeSlots, getRemainingPromotionsPerType()) + .forEach(addedTx -> processAdded(addedTx, AddReason.PROMOTED)); } } + @Override + public void penalize(final PendingTransaction penalizedTransaction) { + if (pendingTransactions.containsKey(penalizedTransaction.getHash())) { + internalPenalize(penalizedTransaction); + metrics.incrementPenalized(penalizedTransaction, name()); + if (penalizedTransaction.getScore() < poolConfig.getMinScore()) { + remove(penalizedTransaction, BELOW_MIN_SCORE); + } + } else { + nextLayer.penalize(penalizedTransaction); + } + } + + protected abstract void internalPenalize(final PendingTransaction pendingTransaction); + + /** + * How many txs of a specified type can be promoted? This make sense when a max number of txs of a + * type can be included in a single block (ex. blob txs), to avoid filling the layer with more txs + * than the useful ones. By default, there are no limits, but each layer can define its own + * policy. + * + * @return an array containing the max amount of txs that can be promoted for each type + */ + protected int[] getRemainingPromotionsPerType() { + return Arrays.copyOf(UNLIMITED_PROMOTIONS_PER_TYPE, UNLIMITED_PROMOTIONS_PER_TYPE.length); + } + private void confirmed(final Address sender, final long maxConfirmedNonce) { final var senderTxs = txsBySender.get(sender); @@ -467,12 +549,14 @@ protected abstract void internalRemove( protected abstract PendingTransaction getEvictable(); - protected void increaseSpaceUsed(final PendingTransaction pendingTransaction) { + protected void increaseCounters(final PendingTransaction pendingTransaction) { spaceUsed += pendingTransaction.memorySize(); + ++txCountByType[pendingTransaction.getTransaction().getType().ordinal()]; } - protected void decreaseSpaceUsed(final PendingTransaction pendingTransaction) { + protected void decreaseCounters(final PendingTransaction pendingTransaction) { spaceUsed -= pendingTransaction.memorySize(); + --txCountByType[pendingTransaction.getTransaction().getType().ordinal()]; } protected abstract long cacheFreeSpace(); @@ -501,17 +585,17 @@ public List getAllPriority() { return priorityTxs; } - Stream stream(final Address sender) { - return txsBySender.getOrDefault(sender, EMPTY_SENDER_TXS).values().stream(); - } - @Override - public List getAllFor(final Address sender) { - return Stream.concat(stream(sender), nextLayer.getAllFor(sender).stream()).toList(); + public synchronized List getAllFor(final Address sender) { + final var fromNextLayers = nextLayer.getAllFor(sender); + final var fromThisLayer = txsBySender.getOrDefault(sender, EMPTY_SENDER_TXS).values(); + final var concatLayers = + new ArrayList(fromThisLayer.size() + fromNextLayers.size()); + concatLayers.addAll(fromThisLayer); + concatLayers.addAll(fromNextLayers); + return unmodifiableList(concatLayers); } - abstract Stream stream(); - @Override public int count() { return pendingTransactions.size() + nextLayer.count(); @@ -569,7 +653,7 @@ public String logSender(final Address sender) { protected abstract String internalLogStats(); boolean consistencyCheck( - final Map> prevLayerTxsBySender) { + final Map> prevLayerTxsBySender) { final BinaryOperator noMergeExpected = (a, b) -> { throw new IllegalArgumentException(); @@ -606,7 +690,7 @@ boolean consistencyCheck( } protected abstract void internalConsistencyCheck( - final Map> prevLayerTxsBySender); + final Map> prevLayerTxsBySender); public BlobCache getBlobCache() { return blobCache; diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AddReason.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AddReason.java new file mode 100644 index 00000000000..895dd61e3e2 --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AddReason.java @@ -0,0 +1,65 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.transactions.layered; + +import java.util.Locale; + +/** Describe why we are trying to add a tx to a layer. */ +public enum AddReason { + /** When adding a tx, that is not present in the pool. */ + NEW(true, true), + /** When adding a tx as result of an internal move between layers. */ + MOVE(false, false), + /** When adding a tx as result of a promotion from a lower layer. */ + PROMOTED(false, false); + + private final boolean sendNotification; + private final boolean makeCopy; + private final String label; + + AddReason(final boolean sendNotification, final boolean makeCopy) { + this.sendNotification = sendNotification; + this.makeCopy = makeCopy; + this.label = name().toLowerCase(Locale.ROOT); + } + + /** + * Should we send add notification for this reason? + * + * @return true if notification should be sent + */ + public boolean sendNotification() { + return sendNotification; + } + + /** + * Should the layer make a copy of the pending tx before adding it, to avoid keeping reference to + * potentially large underlying byte buffers? + * + * @return true is a copy is necessary + */ + public boolean makeCopy() { + return makeCopy; + } + + /** + * Return a label that identify this reason to be used in the metric system. + * + * @return a label + */ + public String label() { + return label; + } +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseFeePrioritizedTransactions.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseFeePrioritizedTransactions.java index c1e9bec63d2..c06fb53e33a 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseFeePrioritizedTransactions.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseFeePrioritizedTransactions.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,11 +14,13 @@ */ package org.hyperledger.besu.ethereum.eth.transactions.layered; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.BELOW_BASE_FEE; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.AddReason.MOVE; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.LayerMoveReason.DEMOTED; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; @@ -42,20 +44,30 @@ public class BaseFeePrioritizedTransactions extends AbstractPrioritizedTransacti public BaseFeePrioritizedTransactions( final TransactionPoolConfiguration poolConfig, final Supplier chainHeadHeaderSupplier, + final EthScheduler ethScheduler, final TransactionsLayer nextLayer, final TransactionPoolMetrics metrics, final BiFunction transactionReplacementTester, final FeeMarket feeMarket, - final BlobCache blobCache) { - super(poolConfig, nextLayer, metrics, transactionReplacementTester, blobCache); + final BlobCache blobCache, + final MiningParameters miningParameters) { + super( + poolConfig, + ethScheduler, + nextLayer, + metrics, + transactionReplacementTester, + blobCache, + miningParameters); this.nextBlockBaseFee = Optional.of(calculateNextBlockBaseFee(feeMarket, chainHeadHeaderSupplier.get())); } @Override protected int compareByFee(final PendingTransaction pt1, final PendingTransaction pt2) { - return Comparator.comparing(PendingTransaction::hasPriority) + return Comparator.comparing(PendingTransaction::getScore) + .thenComparing(PendingTransaction::hasPriority) .thenComparing( (PendingTransaction pendingTransaction) -> pendingTransaction.getTransaction().getEffectivePriorityFeePerGas(nextBlockBaseFee)) @@ -93,7 +105,7 @@ protected void internalBlockAdded(final BlockHeader blockHeader, final FeeMarket while (itTxsBySender.hasNext()) { final var senderTxs = itTxsBySender.next().getValue(); - Optional maybeFirstUnderpricedNonce = Optional.empty(); + Optional maybeFirstDemotedNonce = Optional.empty(); for (final var e : senderTxs.entrySet()) { final PendingTransaction tx = e.getValue(); @@ -103,26 +115,28 @@ protected void internalBlockAdded(final BlockHeader blockHeader, final FeeMarket } else { // otherwise sender txs starting from this nonce need to be demoted to next layer, // and we can go to next sender - maybeFirstUnderpricedNonce = Optional.of(e.getKey()); + maybeFirstDemotedNonce = Optional.of(e.getKey()); break; } } - maybeFirstUnderpricedNonce.ifPresent( + maybeFirstDemotedNonce.ifPresent( nonce -> { - // demote all txs after the first underpriced to the next layer, because none of them is + // demote all txs after the first demoted to the next layer, because none of them is // executable now, and we can avoid sorting them until they are candidate for execution // again final var demoteTxs = senderTxs.tailMap(nonce, true); while (!demoteTxs.isEmpty()) { final PendingTransaction demoteTx = demoteTxs.pollLastEntry().getValue(); LOG.atTrace() - .setMessage("Demoting tx {} with max gas price below next block base fee {}") + .setMessage( + "Demoting tx {} since it does not respect anymore the requisites to stay in this layer." + + " Next block base fee {}") .addArgument(demoteTx::toTraceLog) .addArgument(newNextBlockBaseFee::toHumanReadableString) .log(); - processEvict(senderTxs, demoteTx, BELOW_BASE_FEE); - addToNextLayer(senderTxs, demoteTx, 0); + processEvict(senderTxs, demoteTx, DEMOTED); + addToNextLayer(senderTxs, demoteTx, 0, MOVE); } }); @@ -146,11 +160,35 @@ private Wei calculateNextBlockBaseFee(final FeeMarket feeMarket, final BlockHead @Override protected boolean promotionFilter(final PendingTransaction pendingTransaction) { - return nextBlockBaseFee - .map( - baseFee -> - pendingTransaction.getTransaction().getMaxGasPrice().greaterOrEqualThan(baseFee)) - .orElse(false); + + // check if the tx is willing to pay at least the base fee + if (nextBlockBaseFee + .map(pendingTransaction.getTransaction().getMaxGasPrice()::lessThan) + .orElse(true)) { + return false; + } + + // priority txs are promoted even if they pay less + if (!pendingTransaction.hasPriority()) { + // check if effective gas price is higher than the min gas price + if (pendingTransaction + .getTransaction() + .getEffectiveGasPrice(nextBlockBaseFee) + .lessThan(miningParameters.getMinTransactionGasPrice())) { + return false; + } + + // check if enough priority fee is paid + if (!miningParameters.getMinPriorityFeePerGas().equals(Wei.ZERO)) { + final Wei priorityFeePerGas = + pendingTransaction.getTransaction().getEffectivePriorityFeePerGas(nextBlockBaseFee); + if (priorityFeePerGas.lessThan(miningParameters.getMinPriorityFeePerGas())) { + return false; + } + } + } + + return true; } @Override @@ -160,8 +198,8 @@ protected String internalLogStats() { return "Basefee Prioritized: Empty"; } - final Transaction highest = orderByFee.last().getTransaction(); - final Transaction lowest = orderByFee.first().getTransaction(); + final PendingTransaction highest = orderByFee.last(); + final PendingTransaction lowest = orderByFee.first(); return "Basefee Prioritized: " + "count: " @@ -170,16 +208,26 @@ protected String internalLogStats() { + spaceUsed + ", unique senders: " + txsBySender.size() - + ", highest priority tx: [max fee: " - + highest.getMaxGasPrice().toHumanReadableString() + + ", highest priority tx: [score: " + + highest.getScore() + + ", max fee: " + + highest.getTransaction().getMaxGasPrice().toHumanReadableString() + ", curr prio fee: " - + highest.getEffectivePriorityFeePerGas(nextBlockBaseFee).toHumanReadableString() + + highest + .getTransaction() + .getEffectivePriorityFeePerGas(nextBlockBaseFee) + .toHumanReadableString() + ", hash: " + highest.getHash() - + "], lowest priority tx: [max fee: " - + lowest.getMaxGasPrice().toHumanReadableString() + + "], lowest priority tx: [score: " + + lowest.getScore() + + ", max fee: " + + lowest.getTransaction().getMaxGasPrice().toHumanReadableString() + ", curr prio fee: " - + lowest.getEffectivePriorityFeePerGas(nextBlockBaseFee).toHumanReadableString() + + lowest + .getTransaction() + .getEffectivePriorityFeePerGas(nextBlockBaseFee) + .toHumanReadableString() + ", hash: " + lowest.getHash() + "], next block base fee: " diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/EndLayer.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/EndLayer.java index e79ce1d8a71..c76d2ab97a6 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/EndLayer.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/EndLayer.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,7 +14,7 @@ */ package org.hyperledger.besu.ethereum.eth.transactions.layered; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.DROPPED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason.DROPPED; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; @@ -25,6 +25,7 @@ import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactionDroppedListener; import org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolMetrics; +import org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.util.Subscribers; @@ -75,7 +76,8 @@ public List getAll() { } @Override - public TransactionAddedResult add(final PendingTransaction pendingTransaction, final int gap) { + public TransactionAddedResult add( + final PendingTransaction pendingTransaction, final int gap, final AddReason reason) { notifyTransactionDropped(pendingTransaction); metrics.incrementRemoved(pendingTransaction, DROPPED.label(), name()); ++droppedCount; @@ -83,7 +85,10 @@ public TransactionAddedResult add(final PendingTransaction pendingTransaction, f } @Override - public void remove(final PendingTransaction pendingTransaction, final RemovalReason reason) {} + public void remove(final PendingTransaction pendingTransaction, final PoolRemovalReason reason) {} + + @Override + public void penalize(final PendingTransaction penalizedTx) {} @Override public void blockAdded( @@ -122,7 +127,8 @@ public OptionalLong getCurrentNonceFor(final Address sender) { public List promote( final Predicate promotionFilter, final long freeSpace, - final int freeSlots) { + final int freeSlots, + final int[] remainingPromotionsPerType) { return List.of(); } @@ -152,7 +158,8 @@ protected void notifyTransactionDropped(final PendingTransaction pendingTransact } @Override - public PendingTransaction promoteFor(final Address sender, final long nonce) { + public PendingTransaction promoteFor( + final Address sender, final long nonce, final int[] remainingPromotionsPerType) { return null; } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/GasPricePrioritizedTransactions.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/GasPricePrioritizedTransactions.java index c6c402a1108..504a453fa88 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/GasPricePrioritizedTransactions.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/GasPricePrioritizedTransactions.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,6 +17,8 @@ import static java.util.Comparator.comparing; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; @@ -35,17 +37,27 @@ public class GasPricePrioritizedTransactions extends AbstractPrioritizedTransact public GasPricePrioritizedTransactions( final TransactionPoolConfiguration poolConfig, + final EthScheduler ethScheduler, final TransactionsLayer nextLayer, final TransactionPoolMetrics metrics, final BiFunction transactionReplacementTester, - final BlobCache blobCache) { - super(poolConfig, nextLayer, metrics, transactionReplacementTester, blobCache); + final BlobCache blobCache, + final MiningParameters miningParameters) { + super( + poolConfig, + ethScheduler, + nextLayer, + metrics, + transactionReplacementTester, + blobCache, + miningParameters); } @Override protected int compareByFee(final PendingTransaction pt1, final PendingTransaction pt2) { - return comparing(PendingTransaction::hasPriority) + return comparing(PendingTransaction::getScore) + .thenComparing(PendingTransaction::hasPriority) .thenComparing(PendingTransaction::getGasPrice) .thenComparing(PendingTransaction::getSequence) .compare(pt1, pt2); @@ -58,25 +70,42 @@ protected void internalBlockAdded(final BlockHeader blockHeader, final FeeMarket @Override protected boolean promotionFilter(final PendingTransaction pendingTransaction) { - return true; + return pendingTransaction.hasPriority() + || pendingTransaction + .getTransaction() + .getGasPrice() + .map(miningParameters.getMinTransactionGasPrice()::lessThan) + .orElse(false); } @Override - public String internalLogStats() { + protected String internalLogStats() { if (orderByFee.isEmpty()) { return "GasPrice Prioritized: Empty"; } + final PendingTransaction highest = orderByFee.last(); + final PendingTransaction lowest = orderByFee.first(); + return "GasPrice Prioritized: " + "count: " + pendingTransactions.size() - + " space used: " + + ", space used: " + spaceUsed - + " unique senders: " + + ", unique senders: " + txsBySender.size() - + ", highest fee tx: " - + orderByFee.last().getTransaction().getGasPrice().get().toHumanReadableString() - + ", lowest fee tx: " - + orderByFee.first().getTransaction().getGasPrice().get().toHumanReadableString(); + + ", highest priority tx: [score: " + + highest.getScore() + + ", gas price: " + + highest.getTransaction().getGasPrice().get().toHumanReadableString() + + ", hash: " + + highest.getHash() + + "], lowest priority tx: [score: " + + lowest.getScore() + + ", gas price: " + + lowest.getTransaction().getGasPrice().get().toHumanReadableString() + + ", hash: " + + lowest.getHash() + + "]"; } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactions.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactions.java index d9b6b60814d..21c75b364a0 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactions.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactions.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -20,13 +20,15 @@ import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.ALREADY_KNOWN; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.INTERNAL_ERROR; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.NONCE_TOO_FAR_IN_FUTURE_FOR_SENDER; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.INVALIDATED; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.RECONCILED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.AddReason.NEW; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason.INVALIDATED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason.RECONCILED; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactionAddedListener; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactionDroppedListener; @@ -37,34 +39,39 @@ import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.account.AccountState; +import org.hyperledger.besu.plugin.data.TransactionSelectionResult; import java.util.ArrayDeque; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.OptionalLong; import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collector; import java.util.stream.Collectors; import kotlin.ranges.LongRange; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.Marker; +import org.slf4j.MarkerFactory; public class LayeredPendingTransactions implements PendingTransactions { private static final Logger LOG = LoggerFactory.getLogger(LayeredPendingTransactions.class); private static final Logger LOG_FOR_REPLAY = LoggerFactory.getLogger("LOG_FOR_REPLAY"); + private static final Marker INVALID_TX_REMOVED = MarkerFactory.getMarker("INVALID_TX_REMOVED"); private final TransactionPoolConfiguration poolConfig; private final AbstractPrioritizedTransactions prioritizedTransactions; + private final EthScheduler ethScheduler; public LayeredPendingTransactions( final TransactionPoolConfiguration poolConfig, - final AbstractPrioritizedTransactions prioritizedTransactions) { + final AbstractPrioritizedTransactions prioritizedTransactions, + final EthScheduler ethScheduler) { this.poolConfig = poolConfig; this.prioritizedTransactions = prioritizedTransactions; + this.ethScheduler = ethScheduler; } @Override @@ -93,7 +100,7 @@ public synchronized TransactionAddedResult addTransaction( } try { - return prioritizedTransactions.add(pendingTransaction, (int) nonceDistance); + return prioritizedTransactions.add(pendingTransaction, (int) nonceDistance, NEW); } catch (final Throwable throwable) { return reconcileAndRetryAdd( pendingTransaction, stateSenderNonce, (int) nonceDistance, throwable); @@ -107,23 +114,25 @@ private TransactionAddedResult reconcileAndRetryAdd( final Throwable throwable) { // in case something unexpected happened, log this sender txs, force a reconcile and retry // another time - // ToDo: demote to debug when Layered TxPool is out of preview - LOG.warn( - "Unexpected error {} when adding transaction {}, current sender status {}", - throwable, - pendingTransaction.toTraceLog(), - prioritizedTransactions.logSender(pendingTransaction.getSender())); - LOG.warn("Stack trace", throwable); + LOG.atDebug() + .setMessage( + "Unexpected error when adding transaction {}, current sender status {}, force a reconcile and retry") + .setCause(throwable) + .addArgument(pendingTransaction::toTraceLog) + .addArgument(() -> prioritizedTransactions.logSender(pendingTransaction.getSender())) + .log(); reconcileSender(pendingTransaction.getSender(), stateSenderNonce); try { - return prioritizedTransactions.add(pendingTransaction, nonceDistance); + return prioritizedTransactions.add(pendingTransaction, nonceDistance, NEW); } catch (final Throwable throwable2) { - LOG.warn( - "Unexpected error {} when adding transaction {}, current sender status {}", - throwable, - pendingTransaction.toTraceLog(), - prioritizedTransactions.logSender(pendingTransaction.getSender())); - LOG.warn("Stack trace", throwable); + // the error should have been solved by the reconcile, logging at higher level now + LOG.atWarn() + .setCause(throwable2) + .setMessage( + "Unexpected error when adding transaction {} after reconciliation, current sender status {}") + .addArgument(pendingTransaction.toTraceLog()) + .addArgument(prioritizedTransactions.logSender(pendingTransaction.getSender())) + .log(); return INTERNAL_ERROR; } } @@ -201,7 +210,7 @@ private void reconcileSender(final Address sender, final long stateSenderNonce) final long lowestNonce = reAddTxs.getFirst().getNonce(); final int newNonceDistance = (int) Math.max(0, lowestNonce - stateSenderNonce); - reAddTxs.forEach(ptx -> prioritizedTransactions.add(ptx, newNonceDistance)); + reAddTxs.forEach(ptx -> prioritizedTransactions.add(ptx, newNonceDistance, NEW)); } LOG.atDebug() @@ -234,7 +243,8 @@ private void logTransactionForReplayAdd( .log(); } - private void logTransactionForReplayDelete(final PendingTransaction pendingTransaction) { + private void logDiscardedTransaction( + final PendingTransaction pendingTransaction, final TransactionSelectionResult result) { // csv fields: sequence, addedAt, sender, nonce, type, hash, rlp LOG_FOR_REPLAY .atTrace() @@ -252,6 +262,19 @@ private void logTransactionForReplayDelete(final PendingTransaction pendingTrans return rlp.encoded().toHexString(); }) .log(); + LOG.atInfo() + .addMarker(INVALID_TX_REMOVED) + .addKeyValue("txhash", pendingTransaction::getHash) + .addKeyValue("txlog", pendingTransaction::toTraceLog) + .addKeyValue("reason", result) + .addKeyValue( + "txrlp", + () -> { + final BytesValueRLPOutput rlp = new BytesValueRLPOutput(); + pendingTransaction.getTransaction().writeTo(rlp); + return rlp.encoded().toHexString(); + }) + .log(); } private TransactionAddedResult nonceChecks( @@ -291,77 +314,74 @@ public synchronized List getPriorityTransactions() { } @Override - // There's a small edge case here we could encounter. - // When we pass an upgrade block that has a new transaction type, we start allowing transactions - // of that new type into our pool. - // If we then reorg to a block lower than the upgrade block height _and_ we create a block, that - // block could end up with transactions of the new type. - // This seems like it would be very rare but worth it to document that we don't handle that case - // right now. - public synchronized void selectTransactions( - final PendingTransactions.TransactionSelector selector) { - final List invalidTransactions = new ArrayList<>(); - final Set alreadyChecked = new HashSet<>(); + public void selectTransactions(final PendingTransactions.TransactionSelector selector) { final Set
skipSenders = new HashSet<>(); - final AtomicBoolean completed = new AtomicBoolean(false); - - prioritizedTransactions.stream() - .takeWhile(unused -> !completed.get()) - .filter(highPrioPendingTx -> !skipSenders.contains(highPrioPendingTx.getSender())) - .peek(this::logSenderTxs) - .forEach( - highPrioPendingTx -> - prioritizedTransactions.stream(highPrioPendingTx.getSender()) - .takeWhile( - candidatePendingTx -> - !skipSenders.contains(candidatePendingTx.getSender()) - && !completed.get()) - .filter( - candidatePendingTx -> - !alreadyChecked.contains(candidatePendingTx.getHash()) - && candidatePendingTx.getNonce() <= highPrioPendingTx.getNonce()) - .forEach( - candidatePendingTx -> { - alreadyChecked.add(candidatePendingTx.getHash()); - final var res = selector.evaluateTransaction(candidatePendingTx); - - LOG.atTrace() - .setMessage("Selection result {} for transaction {}") - .addArgument(res) - .addArgument(candidatePendingTx::toTraceLog) - .log(); - - if (res.discard()) { - invalidTransactions.add(candidatePendingTx); - logTransactionForReplayDelete(candidatePendingTx); - } - - if (res.stop()) { - completed.set(true); - } - - if (!res.selected()) { - // avoid processing other txs from this sender if this one is skipped - // since the following will not be selected due to the nonce gap - skipSenders.add(candidatePendingTx.getSender()); - LOG.trace("Skipping tx from sender {}", candidatePendingTx.getSender()); - } - })); - - invalidTransactions.forEach( - invalidTx -> prioritizedTransactions.remove(invalidTx, INVALIDATED)); - } - private void logSenderTxs(final PendingTransaction highPrioPendingTx) { - LOG.atTrace() - .setMessage("highPrioPendingTx {}, senderTxs {}") - .addArgument(highPrioPendingTx::toTraceLog) - .addArgument( - () -> - prioritizedTransactions.stream(highPrioPendingTx.getSender()) - .map(PendingTransaction::toTraceLog) - .collect(Collectors.joining(", "))) - .log(); + final Map> candidateTxsByScore; + synchronized (this) { + // since selecting transactions for block creation is a potential long operation + // we want to avoid to keep the lock for all the process, but we just lock to get + // the candidate transactions + candidateTxsByScore = prioritizedTransactions.getByScore(); + } + + selection: + for (final var entry : candidateTxsByScore.entrySet()) { + LOG.trace("Evaluating txs with score {}", entry.getKey()); + + for (final var senderTxs : entry.getValue()) { + LOG.trace("Evaluating sender txs {}", senderTxs); + + if (!skipSenders.contains(senderTxs.sender())) { + + for (final var candidatePendingTx : senderTxs.pendingTransactions()) { + final var selectionResult = selector.evaluateTransaction(candidatePendingTx); + + LOG.atTrace() + .setMessage("Selection result {} for transaction {}") + .addArgument(selectionResult) + .addArgument(candidatePendingTx::toTraceLog) + .log(); + + if (selectionResult.discard()) { + ethScheduler.scheduleTxWorkerTask( + () -> { + synchronized (this) { + prioritizedTransactions.remove(candidatePendingTx, INVALIDATED); + } + }); + logDiscardedTransaction(candidatePendingTx, selectionResult); + } + + if (selectionResult.penalize()) { + ethScheduler.scheduleTxWorkerTask( + () -> { + synchronized (this) { + prioritizedTransactions.penalize(candidatePendingTx); + } + }); + LOG.atTrace() + .setMessage("Transaction {} penalized") + .addArgument(candidatePendingTx::toTraceLog) + .log(); + } + + if (selectionResult.stop()) { + LOG.trace("Stopping selection"); + break selection; + } + + if (!selectionResult.selected()) { + // avoid processing other txs from this sender if this one is skipped + // since the following will not be selected due to the nonce gap + LOG.trace("Skipping remaining txs for sender {}", candidatePendingTx.getSender()); + skipSenders.add(candidatePendingTx.getSender()); + break; + } + } + } + } + } } @Override diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReadyTransactions.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReadyTransactions.java index 2820fe64b67..7c7ddcdaeb2 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReadyTransactions.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReadyTransactions.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,11 +14,11 @@ */ package org.hyperledger.besu.ethereum.eth.transactions.layered; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.PROMOTED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.LayerMoveReason.PROMOTED; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult; @@ -33,29 +33,29 @@ import java.util.NavigableMap; import java.util.NavigableSet; import java.util.Optional; -import java.util.TreeMap; import java.util.TreeSet; import java.util.function.BiFunction; import java.util.function.Predicate; import java.util.stream.Collectors; -import java.util.stream.Stream; public class ReadyTransactions extends AbstractSequentialTransactionsLayer { private final NavigableSet orderByMaxFee = new TreeSet<>( - Comparator.comparing(PendingTransaction::hasPriority) + Comparator.comparing(PendingTransaction::getScore) + .thenComparing(PendingTransaction::hasPriority) .thenComparing((PendingTransaction pt) -> pt.getTransaction().getMaxGasPrice()) .thenComparing(PendingTransaction::getSequence)); public ReadyTransactions( final TransactionPoolConfiguration poolConfig, + final EthScheduler ethScheduler, final TransactionsLayer nextLayer, final TransactionPoolMetrics metrics, final BiFunction transactionReplacementTester, final BlobCache blobCache) { - super(poolConfig, nextLayer, transactionReplacementTester, metrics, blobCache); + super(poolConfig, ethScheduler, nextLayer, transactionReplacementTester, metrics, blobCache); } @Override @@ -116,6 +116,20 @@ protected void internalRemove( } } + @Override + protected void internalPenalize(final PendingTransaction penalizedTx) { + final var senderTxs = txsBySender.get(penalizedTx.getSender()); + if (senderTxs.firstKey() == penalizedTx.getNonce()) { + // since we only sort the first tx of sender, we only need to re-sort in this case + orderByMaxFee.remove(penalizedTx); + penalizedTx.decrementScore(); + orderByMaxFee.add(penalizedTx); + } else { + // otherwise we just decrement the score + penalizedTx.decrementScore(); + } + } + @Override protected void internalReplaced(final PendingTransaction replacedTx) { orderByMaxFee.remove(replacedTx); @@ -136,18 +150,32 @@ protected boolean promotionFilter(final PendingTransaction pendingTransaction) { return true; } + /** + * Return the full content of this layer, organized as a list of sender pending txs. For each + * sender the collection pending txs is ordered by nonce asc. + * + *

Returned sender list order detail: first the sender of the tx with the highest max gas + * price. + * + * @return a list of sender pending txs + */ @Override - public Stream stream() { + public List getBySender() { return orderByMaxFee.descendingSet().stream() .map(PendingTransaction::getSender) - .flatMap(sender -> txsBySender.get(sender).values().stream()); + .map( + sender -> + new SenderPendingTransactions( + sender, List.copyOf(txsBySender.get(sender).values()))) + .toList(); } @Override public List promote( final Predicate promotionFilter, final long freeSpace, - final int freeSlots) { + final int freeSlots, + final int[] remainingPromotionsPerType) { long accumulatedSpace = 0; final List promotedTxs = new ArrayList<>(); @@ -156,10 +184,12 @@ public List promote( for (final var senderFirstTx : orderByMaxFee.descendingSet()) { final var senderTxs = txsBySender.get(senderFirstTx.getSender()); for (final var candidateTx : senderTxs.values()) { - if (promotionFilter.test(candidateTx)) { + final var txType = candidateTx.getTransaction().getType(); + if (promotionFilter.test(candidateTx) && remainingPromotionsPerType[txType.ordinal()] > 0) { accumulatedSpace += candidateTx.memorySize(); if (promotedTxs.size() < freeSlots && accumulatedSpace <= freeSpace) { promotedTxs.add(candidateTx); + --remainingPromotionsPerType[txType.ordinal()]; } else { // no room for more txs the search is over exit the loops break search; @@ -197,8 +227,8 @@ public String internalLogStats() { return "Ready: Empty"; } - final Transaction top = orderByMaxFee.last().getTransaction(); - final Transaction last = orderByMaxFee.first().getTransaction(); + final PendingTransaction top = orderByMaxFee.last(); + final PendingTransaction last = orderByMaxFee.first(); return "Ready: " + "count=" @@ -207,12 +237,16 @@ public String internalLogStats() { + spaceUsed + ", unique senders: " + txsBySender.size() - + ", top by max fee[max fee:" - + top.getMaxGasPrice().toHumanReadableString() + + ", top by score and max gas price[score: " + + top.getScore() + + ", max gas price:" + + top.getTransaction().getMaxGasPrice().toHumanReadableString() + ", hash: " + top.getHash() - + "], last by max fee [max fee: " - + last.getMaxGasPrice().toHumanReadableString() + + "], last by score and max gas price [score: " + + last.getScore() + + ", max fee: " + + last.getTransaction().getMaxGasPrice().toHumanReadableString() + ", hash: " + last.getHash() + "]"; @@ -220,7 +254,7 @@ public String internalLogStats() { @Override protected void internalConsistencyCheck( - final Map> prevLayerTxsBySender) { + final Map> prevLayerTxsBySender) { super.internalConsistencyCheck(prevLayerTxsBySender); final var minNonceBySender = @@ -241,7 +275,6 @@ protected void internalConsistencyCheck( : "orderByMaxFee does not match pendingTransactions"; } - assert itCurrent.hasNext() == false - : "orderByMaxFee has more elements than pendingTransactions"; + assert !itCurrent.hasNext() : "orderByMaxFee has more elements than pendingTransactions"; } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/RemovalReason.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/RemovalReason.java new file mode 100644 index 00000000000..8acd026b14e --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/RemovalReason.java @@ -0,0 +1,136 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.transactions.layered; + +import java.util.Locale; + +/** The reason why a pending tx has been removed */ +public interface RemovalReason { + /** + * From where the tx has been removed + * + * @return removed from item + */ + RemovedFrom removedFrom(); + + /** + * Return a label that identify this reason to be used in the metric system. + * + * @return a label + */ + String label(); + + /** There are 2 kinds of removals, from a layer and from the pool. */ + enum RemovedFrom { + /** + * Removing from a layer, can be also seen as a move between layers, since it is removed + * from the current layer and added to another layer, for example in the case the layer is full + * and some txs need to be moved to the next layer, or in the opposite case when some txs are + * promoted to the upper layer. + */ + LAYER, + /** + * Removing from the pool, instead means that the tx is directly removed from the pool, and it + * will not be present in any layer, for example, when it is added to an imported block, or it + * is replaced by another tx. + */ + POOL + } + + /** The reason why the tx has been removed from the pool */ + enum PoolRemovalReason implements RemovalReason { + /** Tx removed since it is confirmed on chain, as part of an imported block. */ + CONFIRMED, + /** Tx removed since it has been replaced by another one added in the same layer. */ + REPLACED, + /** Tx removed since it has been replaced by another one added in another layer. */ + CROSS_LAYER_REPLACED, + /** Tx removed when the pool is full, to make space for new incoming txs. */ + DROPPED, + /** + * Tx removed since found invalid after it was added to the pool, for example during txs + * selection for a new block proposal. + */ + INVALIDATED, + /** + * Special case, when for a sender, discrepancies are found between the world state view and the + * pool view, then all the txs for this sender are removed and added again. Discrepancies, are + * rare, and can happen during a short windows when a new block is being imported and the world + * state being updated. + */ + RECONCILED, + /** + * When a pending tx is penalized its score is decreased, if at some point its score is lower + * than the configured minimum then the pending tx is removed from the pool. + */ + BELOW_MIN_SCORE; + + private final String label; + + PoolRemovalReason() { + this.label = name().toLowerCase(Locale.ROOT); + } + + @Override + public RemovedFrom removedFrom() { + return RemovedFrom.POOL; + } + + @Override + public String label() { + return label; + } + } + + /** The reason why the tx has been moved across layers */ + enum LayerMoveReason implements RemovalReason { + /** + * When the current layer is full, and this tx needs to be moved to the lower layer, in order to + * free space. + */ + EVICTED, + /** + * Specific to sequential layers, when a tx is removed because found invalid, then if the sender + * has other txs with higher nonce, then a gap is created, and since sequential layers do not + * permit gaps, txs following the invalid one need to be moved to lower layers. + */ + FOLLOW_INVALIDATED, + /** + * When a tx is moved to the upper layer, since it satisfies all the requirement to be promoted. + */ + PROMOTED, + /** + * When a tx is moved to the lower layer, since it, or a preceding one from the same sender, + * does not respect anymore the requisites to stay in this layer. + */ + DEMOTED; + + private final String label; + + LayerMoveReason() { + this.label = name().toLowerCase(Locale.ROOT); + } + + @Override + public RemovedFrom removedFrom() { + return RemovedFrom.LAYER; + } + + @Override + public String label() { + return label; + } + } +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/SenderPendingTransactions.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/SenderPendingTransactions.java new file mode 100644 index 00000000000..7fb7c5cfeb7 --- /dev/null +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/SenderPendingTransactions.java @@ -0,0 +1,43 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.transactions.layered; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * A list of pending transactions of a specific sender, ordered by nonce asc + * + * @param sender the sender + * @param pendingTransactions the list of pending transactions order by nonce asc + */ +public record SenderPendingTransactions( + Address sender, List pendingTransactions) { + + @Override + public String toString() { + return "Sender " + + sender + + " has " + + pendingTransactions.size() + + " pending transactions " + + pendingTransactions.stream() + .map(PendingTransaction::toTraceLog) + .collect(Collectors.joining(",", "[", "]")); + } +} diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/SparseTransactions.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/SparseTransactions.java index 41e2f6d9c33..7a20f945409 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/SparseTransactions.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/SparseTransactions.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,17 +14,19 @@ */ package org.hyperledger.besu.ethereum.eth.transactions.layered; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.INVALIDATED; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.PROMOTED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.LayerMoveReason.PROMOTED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason.INVALIDATED; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolMetrics; +import org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import java.util.ArrayList; @@ -39,31 +41,35 @@ import java.util.Objects; import java.util.OptionalLong; import java.util.Set; -import java.util.TreeMap; import java.util.TreeSet; import java.util.function.BiFunction; import java.util.function.Predicate; import java.util.stream.IntStream; -import java.util.stream.Stream; import com.google.common.collect.Iterables; public class SparseTransactions extends AbstractTransactionsLayer { + /** + * Order sparse tx by priority flag and sequence asc, so that we pick for eviction txs that have + * no priority and with the lowest sequence number (oldest) first. + */ private final NavigableSet sparseEvictionOrder = new TreeSet<>( Comparator.comparing(PendingTransaction::hasPriority) .thenComparing(PendingTransaction::getSequence)); + private final Map gapBySender = new HashMap<>(); private final List orderByGap; public SparseTransactions( final TransactionPoolConfiguration poolConfig, + final EthScheduler ethScheduler, final TransactionsLayer nextLayer, final TransactionPoolMetrics metrics, final BiFunction transactionReplacementTester, final BlobCache blobCache) { - super(poolConfig, nextLayer, transactionReplacementTester, metrics, blobCache); + super(poolConfig, ethScheduler, nextLayer, transactionReplacementTester, metrics, blobCache); orderByGap = new ArrayList<>(poolConfig.getMaxFutureBySender()); IntStream.range(0, poolConfig.getMaxFutureBySender()) .forEach(i -> orderByGap.add(new SendersByPriority())); @@ -102,7 +108,9 @@ protected TransactionAddedResult canAdd( orderByGap.get(gap).add(pendingTransaction); return gap; } - if (pendingTransaction.getNonce() < txsBySender.get(sender).firstKey()) { + if (Long.compareUnsigned( + pendingTransaction.getNonce(), txsBySender.get(sender).firstKey()) + < 0) { orderByGap.get(currGap).remove(sender); orderByGap.get(gap).add(pendingTransaction); return gap; @@ -146,7 +154,8 @@ protected void internalBlockAdded(final BlockHeader blockHeader, final FeeMarket public List promote( final Predicate promotionFilter, final long freeSpace, - final int freeSlots) { + final int freeSlots, + final int[] remainingPromotionsPerType) { long accumulatedSpace = 0; final List promotedTxs = new ArrayList<>(); @@ -157,11 +166,12 @@ public List promote( final var senderSeqTxs = getSequentialSubset(txsBySender.get(sender)); for (final var candidateTx : senderSeqTxs.values()) { - - if (promotionFilter.test(candidateTx)) { + final var txType = candidateTx.getTransaction().getType(); + if (promotionFilter.test(candidateTx) && remainingPromotionsPerType[txType.ordinal()] > 0) { accumulatedSpace += candidateTx.memorySize(); if (promotedTxs.size() < freeSlots && accumulatedSpace <= freeSpace) { promotedTxs.add(candidateTx); + --remainingPromotionsPerType[txType.ordinal()]; } else { // no room for more txs the search is over exit the loops break search; @@ -215,7 +225,8 @@ private NavigableMap getSequentialSubset( } @Override - public void remove(final PendingTransaction invalidatedTx, final RemovalReason reason) { + public synchronized void remove( + final PendingTransaction invalidatedTx, final PoolRemovalReason reason) { final var senderTxs = txsBySender.get(invalidatedTx.getSender()); if (senderTxs != null && senderTxs.containsKey(invalidatedTx.getNonce())) { @@ -293,6 +304,11 @@ protected void internalRemove( } } + @Override + protected void internalPenalize(final PendingTransaction penalizedTx) { + // intentionally no-op + } + private void deleteGap(final Address sender) { orderByGap.get(gapBySender.remove(sender)).remove(sender); } @@ -307,9 +323,27 @@ protected boolean promotionFilter(final PendingTransaction pendingTransaction) { return false; } + /** + * Return the full content of this layer, organized as a list of sender pending txs. For each + * sender the collection pending txs is ordered by nonce asc. + * + *

Returned sender list order detail: first the sender of the tx that will be evicted as last. + * So for example if the same sender has the first and the last txs in the eviction order, it will + * be the first in the returned list, since we give precedence to tx that will be evicted later. + * + * @return a list of sender pending txs + */ @Override - public Stream stream() { - return sparseEvictionOrder.descendingSet().stream(); + public List getBySender() { + final var sendersToAdd = new HashSet<>(txsBySender.keySet()); + return sparseEvictionOrder.descendingSet().stream() + .map(PendingTransaction::getSender) + .filter(sendersToAdd::remove) + .map( + sender -> + new SenderPendingTransactions( + sender, List.copyOf(txsBySender.get(sender).values()))) + .toList(); } @Override @@ -404,7 +438,7 @@ private void updateGap(final Address sender, final int currGap, final int newGap @Override protected void internalConsistencyCheck( - final Map> prevLayerTxsBySender) { + final Map> prevLayerTxsBySender) { txsBySender.values().stream() .filter(senderTxs -> senderTxs.size() > 1) .map(NavigableMap::entrySet) @@ -432,7 +466,7 @@ protected void internalConsistencyCheck( while (itNonce.hasNext()) { final long currNonce = itNonce.next().getKey(); - assert prevNonce < currNonce : "non incremental nonce"; + assert Long.compareUnsigned(prevNonce, currNonce) < 0 : "non incremental nonce"; prevNonce = currNonce; } }); diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/TransactionsLayer.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/TransactionsLayer.java index 85227766b40..16ce957fb89 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/TransactionsLayer.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/TransactionsLayer.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -22,6 +22,7 @@ import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactionAddedListener; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactionDroppedListener; import org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult; +import org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import java.util.List; @@ -40,17 +41,50 @@ public interface TransactionsLayer { boolean contains(Transaction transaction); - List getAll(); + /** + * Try to add a pending transaction to this layer. The {@code addReason} is used to discriminate + * between a new tx that is added to the pool, or a tx that is already in the pool, but is moving + * internally between layers, for example, due to a promotion or demotion. The distinction is + * needed since we only need to send a notification for a new tx, and not when it is only an + * internal move. + * + * @param pendingTransaction the tx to try to add to this layer + * @param gap the nonce gap between the current sender nonce and the tx + * @param addReason define if it is a new tx or an internal move + * @return the result of the add operation + */ + TransactionAddedResult add(PendingTransaction pendingTransaction, int gap, AddReason addReason); - TransactionAddedResult add(PendingTransaction pendingTransaction, int gap); + /** + * Remove the pending tx from the pool + * + * @param pendingTransaction the pending tx + * @param reason the reason it is removed from the pool + */ + void remove(PendingTransaction pendingTransaction, PoolRemovalReason reason); - void remove(PendingTransaction pendingTransaction, RemovalReason reason); + /** + * Penalize a pending transaction. Penalization could be applied to notify the txpool that this + * pending tx has some temporary issues that prevent it from being included in a block, and so it + * should be de-prioritized in some ways, so it will be re-evaluated only after non penalized + * pending txs. For example: if during the evaluation for block inclusion, the pending tx is + * excluded because the sender has not enough balance to send it, this could be a transient issue + * since later the sender could receive some funds, but in any case we penalize the pending tx, so + * it is pushed down in the order of prioritized pending txs. + * + * @param penalizedTransaction the tx to penalize + */ + void penalize(PendingTransaction penalizedTransaction); void blockAdded( FeeMarket feeMarket, BlockHeader blockHeader, final Map maxConfirmedNonceBySender); + List getAll(); + + List getAllFor(Address sender); + List getAllLocal(); List getAllPriority(); @@ -69,7 +103,10 @@ void blockAdded( OptionalLong getCurrentNonceFor(Address sender); List promote( - Predicate promotionFilter, final long freeSpace, final int freeSlots); + Predicate promotionFilter, + final long freeSpace, + final int freeSlots, + final int[] remainingPromotionsPerType); long subscribeToAdded(PendingTransactionAddedListener listener); @@ -79,7 +116,7 @@ List promote( void unsubscribeFromDropped(long id); - PendingTransaction promoteFor(Address sender, long nonce); + PendingTransaction promoteFor(Address sender, long nonce, final int[] remainingPromotionsPerType); void notifyAdded(PendingTransaction pendingTransaction); @@ -88,29 +125,4 @@ List promote( String logStats(); String logSender(Address sender); - - List getAllFor(Address sender); - - enum RemovalReason { - CONFIRMED, - CROSS_LAYER_REPLACED, - EVICTED, - DROPPED, - FOLLOW_INVALIDATED, - INVALIDATED, - PROMOTED, - REPLACED, - RECONCILED, - BELOW_BASE_FEE; - - private final String label; - - RemovalReason() { - this.label = name().toLowerCase(); - } - - public String label() { - return label; - } - } } diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/package-info.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/package-info.java index fbc9da9fe57..ff274ce4ea4 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/package-info.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/layered/package-info.java @@ -22,7 +22,8 @@ * transactions that could be selected for a future block proposal, and at the same time, without * penalizing legitimate unordered transactions, that are only temporary non-executable. * - *

It is disabled by default, to enable use the option {@code Xlayered-tx-pool=true} + *

It is enabled by default on public networks, to switch to another implementation use the + * option {@code tx-pool} * *

The main idea is to organize the txpool in an arbitrary number of layers, where each layer has * specific rules and constraints that determine if a transaction belong or not to that layer and @@ -38,6 +39,14 @@ * transactions are removed since confirmed in a block, transactions from the next layer are * promoted until there is space. * + *

Some layers could make use of the score of a pending transactions, to push back in the rank + * those pending transactions that have been penalized. + * + *

Layers are not thread safe, since they are not meant to be accessed directly, and all the + * synchronization is managed at the level of {@link + * org.hyperledger.besu.ethereum.eth.transactions.layered.LayeredPendingTransactions + * LayeredPendingTransactions} class. + * *

The current implementation is based on 3 layers, plus the last one that just drop every * transaction when the previous layers are full. The 3 layers are, in order: * @@ -48,20 +57,20 @@ * * *

Prioritized: This is where candidate transactions are selected for creating a new block. - * Transactions ordered by the effective priority fee, and it is limited by size, 2000 by default, - * to reduce the overhead of the sorting and because that number is enough to fill any block, at the - * current gas limit. Does not allow nonce gaps, and the first transaction for each sender must be - * the next one for that sender. Eviction is done removing the transaction with the higher nonce for - * the sender of the less valuable transaction, to avoid creating nonce gaps, evicted transactions - * go into the next layer Ready. + * Transactions ordered by score and then effective priority fee, and it is limited by size, 2000 by + * default, to reduce the overhead of the sorting and because that number is enough to fill any + * block, at the current gas limit. Does not allow nonce gaps, and the first transaction for each + * sender must be the next one for that sender. Eviction is done removing the transaction with the + * higher nonce for the sender of the less score and less effective priority fee transaction, to + * avoid creating nonce gaps, evicted transactions go into the next layer Ready. * *

Ready: Similar to the Prioritized, it does not allow nonce gaps, and the first transaction for * each sender must be the next one for that sender, but it is limited by space instead of count, * thus allowing many more transactions, think about this layer like a buffer for the Prioritized. * Since it is meant to keep ten to hundreds of thousand of transactions, it does not have a full * ordering, like the previous, but only the first transaction for each sender is ordered using a - * stable value that is the max fee per gas. Eviction is the same as the Prioritized, and evicted - * transaction go into the next layer Sparse. + * stable value that is score and then max fee per gas. Eviction is the same as the Prioritized, and + * evicted transaction go into the next layer Sparse. * *

Sparse: This is the first layer where nonce gaps are allowed and where the first transaction * for a sender could not be the next expected one for that sender. The main purpose of this layer diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractPendingTransactionsSorter.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractPendingTransactionsSorter.java index bb53a8e4fbd..0e1737d65be 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractPendingTransactionsSorter.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractPendingTransactionsSorter.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -32,6 +32,7 @@ import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolReplacementHandler; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.account.AccountState; import org.hyperledger.besu.metrics.BesuMetricCategory; @@ -61,6 +62,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.Marker; +import org.slf4j.MarkerFactory; /** * Holds the current set of pending transactions with the ability to iterate them based on priority @@ -71,6 +74,7 @@ public abstract class AbstractPendingTransactionsSorter implements PendingTransactions { private static final Logger LOG = LoggerFactory.getLogger(AbstractPendingTransactionsSorter.class); + private static final Marker INVALID_TX_REMOVED = MarkerFactory.getMarker("INVALID_TX_REMOVED"); protected final Clock clock; protected final TransactionPoolConfiguration poolConfig; @@ -106,7 +110,8 @@ public AbstractPendingTransactionsSorter( this.clock = clock; this.chainHeadHeaderSupplier = chainHeadHeaderSupplier; this.transactionReplacementHandler = - new TransactionPoolReplacementHandler(poolConfig.getPriceBump()); + new TransactionPoolReplacementHandler( + poolConfig.getPriceBump(), poolConfig.getBlobPriceBump()); final LabelledMetric transactionAddedCounter = metricsSystem.createLabelledCounter( BesuMetricCategory.TRANSACTION_POOL, @@ -247,6 +252,7 @@ public void selectTransactions(final TransactionSelector selector) { if (result.discard()) { transactionsToRemove.add(transactionToProcess.getTransaction()); + logDiscardedTransaction(transactionToProcess, result); } if (result.stop()) { @@ -259,6 +265,23 @@ public void selectTransactions(final TransactionSelector selector) { } } + private void logDiscardedTransaction( + final PendingTransaction pendingTransaction, final TransactionSelectionResult result) { + LOG.atInfo() + .addMarker(INVALID_TX_REMOVED) + .addKeyValue("txhash", pendingTransaction::getHash) + .addKeyValue("txlog", pendingTransaction::toTraceLog) + .addKeyValue("reason", result) + .addKeyValue( + "txrlp", + () -> { + final BytesValueRLPOutput rlp = new BytesValueRLPOutput(); + pendingTransaction.getTransaction().writeTo(rlp); + return rlp.encoded().toHexString(); + }) + .log(); + } + private AccountTransactionOrder createSenderTransactionOrder(final Address address) { return new AccountTransactionOrder( transactionsBySender.get(address).streamPendingTransactions()); @@ -494,6 +517,7 @@ public String toTraceLog() { return sb.toString(); } } + /** * @param transaction to restore blobs onto * @return an optional copy of the supplied transaction, but with the BlobsWithCommitments diff --git a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/PendingTransactionsForSender.java b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/PendingTransactionsForSender.java index f4cd95cba21..2ac0ca609c0 100644 --- a/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/PendingTransactionsForSender.java +++ b/ethereum/eth/src/main/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/PendingTransactionsForSender.java @@ -1,5 +1,5 @@ /* - * Copyright Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -42,7 +42,7 @@ public void trackPendingTransaction(final PendingTransaction pendingTransaction) synchronized (pendingTransactions) { if (!pendingTransactions.isEmpty()) { final long expectedNext = pendingTransactions.lastKey() + 1; - if (nonce > (expectedNext) && nextGap.isEmpty()) { + if (Long.compareUnsigned(nonce, expectedNext) > 0 && nextGap.isEmpty()) { nextGap = OptionalLong.of(expectedNext); } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/EthPeerTestUtil.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/EthPeerTestUtil.java index aae559040fa..129942644e4 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/EthPeerTestUtil.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/EthPeerTestUtil.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthPeersTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthPeersTest.java index 06499fb8b8f..f6c635aa51f 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthPeersTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthPeersTest.java @@ -21,15 +21,18 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.eth.manager.exceptions.NoAvailablePeersException; import org.hyperledger.besu.ethereum.eth.manager.exceptions.PeerDisconnectedException; import org.hyperledger.besu.ethereum.eth.messages.NodeDataMessage; +import org.hyperledger.besu.ethereum.eth.sync.ChainHeadTracker; import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection.PeerNotConnected; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason; @@ -37,6 +40,7 @@ import java.util.Optional; import java.util.OptionalLong; import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; import org.junit.jupiter.api.BeforeEach; @@ -56,6 +60,11 @@ public void setup() throws Exception { when(peerRequest.sendRequest(any())).thenReturn(responseStream); ethProtocolManager = EthProtocolManagerTestUtil.create(); ethPeers = ethProtocolManager.ethContext().getEthPeers(); + final ChainHeadTracker mock = mock(ChainHeadTracker.class); + final BlockHeader blockHeader = mock(BlockHeader.class); + when(mock.getBestHeaderFromPeer(any())) + .thenReturn(CompletableFuture.completedFuture(blockHeader)); + ethPeers.setChainHeadTracker(mock); } @Test @@ -112,6 +121,9 @@ public void comparesPeersWithTdAndNoHeight() { @Test public void shouldExecutePeerRequestImmediatelyWhenPeerIsAvailable() throws Exception { final RespondingEthPeer peer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + + when(peerRequest.isEthPeerSuitable(peer.getEthPeer())).thenReturn(true); + final PendingPeerRequest pendingRequest = ethPeers.executePeerRequest(peerRequest, 10, Optional.empty()); @@ -127,6 +139,8 @@ public void shouldUseLeastBusyPeerForRequest() throws Exception { EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); useRequestSlot(workingPeer.getEthPeer()); + when(peerRequest.isEthPeerSuitable(any())).thenReturn(true); + final PendingPeerRequest pendingRequest = ethPeers.executePeerRequest(peerRequest, 10, Optional.empty()); @@ -147,6 +161,8 @@ public void shouldUseLeastRecentlyUsedPeerWhenBothHaveSameNumberOfOutstandingReq assertThat(leastRecentlyUsedPeer.getEthPeer().outstandingRequests()) .isEqualTo(mostRecentlyUsedPeer.getEthPeer().outstandingRequests()); + when(peerRequest.isEthPeerSuitable(any())).thenReturn(true); + final PendingPeerRequest pendingRequest = ethPeers.executePeerRequest(peerRequest, 10, Optional.empty()); @@ -180,10 +196,13 @@ public void shouldFailWhenAllPeersWithSufficientHeightHaveDisconnected() throws EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); useAllAvailableCapacity(suitablePeer.getEthPeer()); + when(peerRequest.isEthPeerSuitable(suitablePeer.getEthPeer())).thenReturn(true); + final PendingPeerRequest pendingRequest = ethPeers.executePeerRequest(peerRequest, 200, Optional.empty()); - verifyNoInteractions(peerRequest); + verify(peerRequest, times(0)).sendRequest(suitablePeer.getEthPeer()); + assertNotDone(pendingRequest); suitablePeer.disconnect(DisconnectReason.TOO_MANY_PEERS); @@ -194,6 +213,8 @@ public void shouldFailWhenAllPeersWithSufficientHeightHaveDisconnected() throws public void shouldFailWithPeerNotConnectedIfPeerRequestThrows() throws Exception { final RespondingEthPeer peer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); when(peerRequest.sendRequest(peer.getEthPeer())).thenThrow(new PeerNotConnected("Oh dear")); + when(peerRequest.isEthPeerSuitable(any())).thenReturn(true); + final PendingPeerRequest pendingRequest = ethPeers.executePeerRequest(peerRequest, 100, Optional.empty()); @@ -205,9 +226,11 @@ public void shouldDelayExecutionUntilPeerHasCapacity() throws Exception { final RespondingEthPeer peer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); useAllAvailableCapacity(peer.getEthPeer()); + when(peerRequest.isEthPeerSuitable(any())).thenReturn(true); + final PendingPeerRequest pendingRequest = ethPeers.executePeerRequest(peerRequest, 100, Optional.empty()); - verifyNoInteractions(peerRequest); + verify(peerRequest, times(0)).sendRequest(peer.getEthPeer()); freeUpCapacity(peer.getEthPeer()); @@ -221,11 +244,12 @@ public void shouldDelayExecutionUntilPeerWithSufficientHeightHasCapacity() throw EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 10); final RespondingEthPeer peer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + when(peerRequest.isEthPeerSuitable(peer.getEthPeer())).thenReturn(true); useAllAvailableCapacity(peer.getEthPeer()); final PendingPeerRequest pendingRequest = ethPeers.executePeerRequest(peerRequest, 100, Optional.empty()); - verifyNoInteractions(peerRequest); + verify(peerRequest, times(0)).sendRequest(peer.getEthPeer()); freeUpCapacity(peer.getEthPeer()); @@ -238,15 +262,17 @@ public void shouldNotExecuteAbortedRequest() throws Exception { final RespondingEthPeer peer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); useAllAvailableCapacity(peer.getEthPeer()); + when(peerRequest.isEthPeerSuitable(peer.getEthPeer())).thenReturn(true); + final PendingPeerRequest pendingRequest = ethPeers.executePeerRequest(peerRequest, 100, Optional.empty()); - verifyNoInteractions(peerRequest); + verify(peerRequest, times(0)).sendRequest(peer.getEthPeer()); pendingRequest.abort(); freeUpCapacity(peer.getEthPeer()); - verifyNoInteractions(peerRequest); + verify(peerRequest, times(0)).sendRequest(peer.getEthPeer()); assertRequestFailure(pendingRequest, CancellationException.class); } @@ -346,7 +372,60 @@ public void toString_hasExpectedInfo() { .getEthPeer(); ethPeers.registerNewConnection(peerA.getConnection(), Collections.emptyList()); assertThat(ethPeers.toString()).contains("1 EthPeers {"); - assertThat(ethPeers.toString()).contains(peerA.getShortNodeId()); + assertThat(ethPeers.toString()).contains(peerA.getLoggableId()); + } + + @Test + public void snapServersPreferredWhileSyncing() { + + ethPeers.snapServerPeersNeeded(true); + + while (ethPeers.peerCount() < ethPeers.getMaxPeers()) { + final EthPeer ethPeer = + EthProtocolManagerTestUtil.createPeer( + ethProtocolManager, Difficulty.of(50), 20, false, false) + .getEthPeer(); + assertThat(ethPeers.addPeerToEthPeers(ethPeer)).isTrue(); + } + + final EthPeer nonSnapServingPeer = + EthProtocolManagerTestUtil.createPeer( + ethProtocolManager, Difficulty.of(50), 20, false, false) + .getEthPeer(); + + assertThat(ethPeers.addPeerToEthPeers(nonSnapServingPeer)).isFalse(); + assertThat(nonSnapServingPeer.getConnection().isDisconnected()).isTrue(); + + final EthPeer snapServingPeer = + EthProtocolManagerTestUtil.createPeer( + ethProtocolManager, Difficulty.of(50), 20, true, false) + .getEthPeer(); + + assertThat(ethPeers.addPeerToEthPeers(snapServingPeer)).isTrue(); + assertThat(ethPeers.peerCount()).isEqualTo(ethPeers.getMaxPeers()); + } + + @Test + public void snapServersNotPreferredWhenInSync() { + + ethPeers.snapServerPeersNeeded(false); + + while (ethPeers.peerCount() < ethPeers.getMaxPeers()) { + final EthPeer ethPeer = + EthProtocolManagerTestUtil.createPeer( + ethProtocolManager, Difficulty.of(50), 20, false, false) + .getEthPeer(); + assertThat(ethPeers.addPeerToEthPeers(ethPeer)).isTrue(); + } + + final EthPeer snapServingPeer = + EthProtocolManagerTestUtil.createPeer( + ethProtocolManager, Difficulty.of(50), 20, true, false) + .getEthPeer(); + + assertThat(ethPeers.addPeerToEthPeers(snapServingPeer)).isFalse(); + assertThat(snapServingPeer.getConnection().isDisconnected()).isTrue(); + assertThat(ethPeers.peerCount()).isEqualTo(ethPeers.getMaxPeers()); } private void freeUpCapacity(final EthPeer ethPeer) { diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTest.java index 3b7178c365b..3a3331b568f 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -36,6 +36,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.ProtocolScheduleFixture; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; @@ -63,6 +64,7 @@ import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolFactory; +import org.hyperledger.besu.ethereum.forkid.ForkId; import org.hyperledger.besu.ethereum.forkid.ForkIdManager; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection; @@ -71,10 +73,10 @@ import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.RawMessage; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.testutil.TestClock; import java.math.BigInteger; @@ -92,6 +94,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import java.util.zip.CRC32; import com.google.common.collect.Lists; import org.apache.tuweni.bytes.Bytes; @@ -256,8 +259,7 @@ public void disconnectNewPoWPeers() { assertThat(workPeer.isDisconnected()).isTrue(); assertThat(workPeer.getDisconnectReason()).isPresent(); assertThat(workPeer.getDisconnectReason()) - .get() - .isEqualTo(DisconnectReason.SUBPROTOCOL_TRIGGERED); + .hasValue(DisconnectReason.SUBPROTOCOL_TRIGGERED_POW_DIFFICULTY); assertThat(stakePeer.isDisconnected()).isFalse(); } } @@ -528,7 +530,7 @@ public void respondToGetHeadersReversedWithSkip() private MockPeerConnection setupPeer( final EthProtocolManager ethManager, final PeerSendHandler onSend) { - final MockPeerConnection peer = setupPeerWithoutStatusExchange(ethManager, onSend); + final MockPeerConnection peerConnection = setupPeerWithoutStatusExchange(ethManager, onSend); final StatusMessage statusMessage = StatusMessage.create( EthProtocolVersion.V63, @@ -536,8 +538,11 @@ private MockPeerConnection setupPeer( blockchain.getChainHead().getTotalDifficulty(), blockchain.getChainHeadHash(), blockchain.getBlockHeader(BlockHeader.GENESIS_BLOCK_NUMBER).get().getHash()); - ethManager.processMessage(EthProtocol.ETH63, new DefaultMessage(peer, statusMessage)); - return peer; + ethManager.processMessage(EthProtocol.ETH63, new DefaultMessage(peerConnection, statusMessage)); + final EthPeers ethPeers = ethManager.ethContext().getEthPeers(); + final EthPeer ethPeer = ethPeers.peer(peerConnection); + ethPeers.addPeerToEthPeers(ethPeer); + return peerConnection; } private MockPeerConnection setupPeerWithoutStatusExchange( @@ -1117,8 +1122,8 @@ public void transactionMessagesGoToTheCorrectExecutor() { metricsSystem, new SyncState(blockchain, ethManager.ethContext().getEthPeers()), TransactionPoolConfiguration.DEFAULT, - null, - new BlobCache()) + new BlobCache(), + MiningParameters.newDefault()) .setEnabled(); // Send just a transaction message. @@ -1133,7 +1138,7 @@ public void transactionMessagesGoToTheCorrectExecutor() { } @Test - public void forkIdForChainHeadMayBeNull() { + public void forkIdForChainHeadLegacyNoForksNotEmpty() { final EthScheduler ethScheduler = mock(EthScheduler.class); try (final EthProtocolManager ethManager = EthProtocolManagerTestUtil.create( @@ -1146,15 +1151,21 @@ public void forkIdForChainHeadMayBeNull() { new ForkIdManager( blockchain, Collections.emptyList(), Collections.emptyList(), true))) { - assertThat(ethManager.getForkIdAsBytesList()).isEmpty(); + assertThat(ethManager.getForkIdAsBytesList()).isNotEmpty(); + final CRC32 genesisHashCRC = new CRC32(); + genesisHashCRC.update(blockchain.getGenesisBlock().getHash().toArray()); + assertThat(ethManager.getForkIdAsBytesList()) + .isEqualTo( + new ForkId(Bytes.ofUnsignedInt(genesisHashCRC.getValue()), 0L) + .getForkIdAsBytesList()); } } @Test public void shouldUseRightCapabilityDependingOnSyncMode() { - assertHighestCapability(SyncMode.X_SNAP, EthProtocol.ETH68); + assertHighestCapability(SyncMode.SNAP, EthProtocol.ETH68); assertHighestCapability(SyncMode.FULL, EthProtocol.ETH68); - assertHighestCapability(SyncMode.X_CHECKPOINT, EthProtocol.ETH68); + assertHighestCapability(SyncMode.CHECKPOINT, EthProtocol.ETH68); /* Eth67 does not support fast sync, see EIP-4938 */ assertHighestCapability(SyncMode.FAST, EthProtocol.ETH66); } @@ -1166,9 +1177,9 @@ public void shouldRespectFlagForMaxCapability() { final EthProtocolConfiguration configuration = EthProtocolConfiguration.builder().maxEthCapability(EthProtocolVersion.V65).build(); - assertHighestCapability(SyncMode.X_SNAP, EthProtocol.ETH65, configuration); + assertHighestCapability(SyncMode.SNAP, EthProtocol.ETH65, configuration); assertHighestCapability(SyncMode.FULL, EthProtocol.ETH65, configuration); - assertHighestCapability(SyncMode.X_CHECKPOINT, EthProtocol.ETH65, configuration); + assertHighestCapability(SyncMode.CHECKPOINT, EthProtocol.ETH65, configuration); /* Eth67 does not support fast sync, see EIP-4938 */ assertHighestCapability(SyncMode.FAST, EthProtocol.ETH65, configuration); } @@ -1180,7 +1191,7 @@ public void shouldRespectFlagForMinCapability() { final EthProtocolConfiguration configuration = EthProtocolConfiguration.builder().minEthCapability(EthProtocolVersion.V64).build(); - final EthProtocolManager ethManager = createEthManager(SyncMode.X_SNAP, configuration); + final EthProtocolManager ethManager = createEthManager(SyncMode.SNAP, configuration); assertThat(ethManager.getSupportedCapabilities()).contains(EthProtocol.ETH64); assertThat(ethManager.getSupportedCapabilities()).doesNotContain(EthProtocol.ETH63); @@ -1193,9 +1204,9 @@ public void shouldRespectProtocolForMaxCapabilityIfFlagGreaterThanProtocol() { final EthProtocolConfiguration configuration = EthProtocolConfiguration.builder().maxEthCapability(EthProtocolVersion.V67).build(); - assertHighestCapability(SyncMode.X_SNAP, EthProtocol.ETH67, configuration); + assertHighestCapability(SyncMode.SNAP, EthProtocol.ETH67, configuration); assertHighestCapability(SyncMode.FULL, EthProtocol.ETH67, configuration); - assertHighestCapability(SyncMode.X_CHECKPOINT, EthProtocol.ETH67, configuration); + assertHighestCapability(SyncMode.CHECKPOINT, EthProtocol.ETH67, configuration); /* Eth67 does not support fast sync, see EIP-4938 */ assertHighestCapability(SyncMode.FAST, EthProtocol.ETH66, configuration); } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTestUtil.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTestUtil.java index 5f970e44dc5..0b0bd1e3eb7 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTestUtil.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthProtocolManagerTestUtil.java @@ -16,12 +16,15 @@ import static com.google.common.base.Preconditions.checkArgument; import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryBlockchain; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import org.hyperledger.besu.config.GenesisConfigFile; +import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.ChainHead; import org.hyperledger.besu.ethereum.chain.GenesisState; +import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.ProtocolScheduleFixture; @@ -29,15 +32,17 @@ import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.manager.snap.SnapProtocolManager; import org.hyperledger.besu.ethereum.eth.peervalidation.PeerValidator; +import org.hyperledger.besu.ethereum.eth.sync.ChainHeadTracker; +import org.hyperledger.besu.ethereum.eth.sync.SyncMode; import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.forkid.ForkIdManager; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.DefaultMessage; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.testutil.DeterministicEthScheduler; import org.hyperledger.besu.testutil.DeterministicEthScheduler.TimeoutPolicy; import org.hyperledger.besu.testutil.TestClock; @@ -46,8 +51,10 @@ import java.util.Collections; import java.util.Optional; import java.util.OptionalLong; +import java.util.concurrent.CompletableFuture; import org.apache.tuweni.bytes.Bytes; +import org.mockito.Mockito; public class EthProtocolManagerTestUtil { @@ -86,8 +93,13 @@ public static EthProtocolManager create( Bytes.random(64), 25, 25, - 25, - false); + false, + SyncMode.FAST, + new ForkIdManager(blockchain, Collections.emptyList(), Collections.emptyList(), false)); + + final ChainHeadTracker chainHeadTrackerMock = getChainHeadTrackerMock(); + peers.setChainHeadTracker(chainHeadTrackerMock); + final EthMessages messages = new EthMessages(); final EthScheduler ethScheduler = new DeterministicEthScheduler(TimeoutPolicy.NEVER_TIMEOUT); final EthContext ethContext = new EthContext(peers, messages, ethScheduler); @@ -140,6 +152,8 @@ public static EthProtocolManager create( final EthContext ethContext, final ForkIdManager forkIdManager) { + ethPeers.setChainHeadTracker(getChainHeadTrackerMock()); + final BigInteger networkId = BigInteger.ONE; return new EthProtocolManager( blockchain, @@ -206,10 +220,15 @@ public static EthProtocolManager create( Bytes.random(64), 25, 25, - 25, - false); + false, + SyncMode.FAST, + new ForkIdManager(blockchain, Collections.emptyList(), Collections.emptyList(), false)); final EthMessages messages = new EthMessages(); + final ChainHeadTracker chtMock = getChainHeadTrackerMock(); + + peers.setChainHeadTracker(chtMock); + return create( blockchain, ethScheduler, @@ -221,6 +240,17 @@ public static EthProtocolManager create( new EthContext(peers, messages, ethScheduler)); } + public static ChainHeadTracker getChainHeadTrackerMock() { + final ChainHeadTracker chtMock = mock(ChainHeadTracker.class); + final BlockHeader blockHeaderMock = mock(BlockHeader.class); + Mockito.lenient() + .when(chtMock.getBestHeaderFromPeer(any())) + .thenReturn(CompletableFuture.completedFuture(blockHeaderMock)); + Mockito.lenient().when(blockHeaderMock.getNumber()).thenReturn(0L); + Mockito.lenient().when(blockHeaderMock.getStateRoot()).thenReturn(Hash.ZERO); + return chtMock; + } + public static EthProtocolManager create( final ProtocolSchedule protocolSchedule, final Blockchain blockchain, @@ -241,8 +271,9 @@ public static EthProtocolManager create( Bytes.random(64), 25, 25, - 25, - false); + false, + SyncMode.FAST, + new ForkIdManager(blockchain, Collections.emptyList(), Collections.emptyList(), false)); final EthMessages messages = new EthMessages(); return create( @@ -261,7 +292,7 @@ public static EthProtocolManager create( final ProtocolSchedule protocolSchedule, final Blockchain blockchain, final EthScheduler ethScheduler) { - final EthPeers peers = + final EthPeers ethPeers = new EthPeers( EthProtocol.NAME, () -> protocolSchedule.getByBlockHeader(blockchain.getChainHeadHeader()), @@ -272,8 +303,13 @@ public static EthProtocolManager create( Bytes.random(64), 25, 25, - 25, - false); + false, + SyncMode.FAST, + new ForkIdManager(blockchain, Collections.emptyList(), Collections.emptyList(), false)); + + final ChainHeadTracker chainHeadTrackerMock = getChainHeadTrackerMock(); + ethPeers.setChainHeadTracker(chainHeadTrackerMock); + final EthMessages messages = new EthMessages(); return create( @@ -282,9 +318,9 @@ public static EthProtocolManager create( BlockchainSetupUtil.forTesting(DataStorageFormat.FOREST).getWorldArchive(), mock(TransactionPool.class), EthProtocolConfiguration.defaultConfig(), - peers, + ethPeers, messages, - new EthContext(peers, messages, ethScheduler)); + new EthContext(ethPeers, messages, ethScheduler)); } public static EthProtocolManager create() { @@ -450,4 +486,19 @@ public static RespondingEthPeer createPeer( .estimatedHeight(blockchain.getChainHeadBlockNumber()) .build(); } + + public static RespondingEthPeer createPeer( + final EthProtocolManager ethProtocolManager, + final Difficulty td, + final int estimatedHeight, + final boolean isServingSnap, + final boolean addToEthPeers) { + return RespondingEthPeer.builder() + .ethProtocolManager(ethProtocolManager) + .totalDifficulty(td) + .estimatedHeight(estimatedHeight) + .isServingSnap(isServingSnap) + .addToEthPeers(addToEthPeers) + .build(); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthSchedulerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthSchedulerTest.java index d09f02c6b76..e7797576d64 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthSchedulerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthSchedulerTest.java @@ -21,16 +21,24 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.testutil.DeterministicEthScheduler; import org.hyperledger.besu.testutil.MockExecutorService; import org.hyperledger.besu.testutil.MockScheduledExecutor; import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Future; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; +import java.util.stream.IntStream; +import org.awaitility.Awaitility; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -209,4 +217,43 @@ public void timeout_cancelsTaskWhenResultIsCancelled() { assertThat(task.isFailed()).isTrue(); assertThat(task.isCancelled()).isTrue(); } + + @Test + public void itemsSubmittedToOrderedProcessorAreProcessedInOrder() throws InterruptedException { + final int numOfItems = 100; + final Random random = new Random(); + final EthScheduler realEthScheduler = new EthScheduler(1, 1, 1, new NoOpMetricsSystem()); + + final List processedStrings = new CopyOnWriteArrayList<>(); + + final Consumer stringProcessor = + s -> { + processedStrings.add(s); + try { + Thread.sleep(random.nextInt(20)); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }; + + final var orderProcessor = realEthScheduler.createOrderedProcessor(stringProcessor); + IntStream.range(0, numOfItems) + .mapToObj(String::valueOf) + .forEach( + s -> { + orderProcessor.submit(s); + try { + Thread.sleep(random.nextInt(20)); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + + final List expectedStrings = new ArrayList<>(numOfItems); + IntStream.range(0, numOfItems).mapToObj(String::valueOf).forEach(expectedStrings::add); + + Awaitility.await().until(() -> processedStrings.size() == numOfItems); + + assertThat(processedStrings).containsExactlyElementsOf(expectedStrings); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthServerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthServerTest.java index 7c75c6ddad3..7cd7bdcdaa8 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthServerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/EthServerTest.java @@ -399,7 +399,7 @@ private int calculateRlpEncodedSize(final Bytes data) { private int calculateRlpEncodedSize(final List receipts) { final BytesValueRLPOutput rlp = new BytesValueRLPOutput(); rlp.startList(); - receipts.forEach(r -> r.writeTo(rlp)); + receipts.forEach(r -> r.writeToForNetwork(rlp)); rlp.endList(); return rlp.encodedSize(); } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/PeerReputationTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/PeerReputationTest.java index f49abee8bb9..1387461f7f6 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/PeerReputationTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/PeerReputationTest.java @@ -16,7 +16,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason.TIMEOUT; -import static org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason.USELESS_PEER; +import static org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason.USELESS_PEER_USELESS_RESPONSES; +import static org.mockito.Mockito.mock; import org.hyperledger.besu.ethereum.eth.messages.EthPV62; @@ -28,6 +29,7 @@ public class PeerReputationTest { private static final int INITIAL_SCORE = 25; private static final int MAX_SCORE = 50; private final PeerReputation reputation = new PeerReputation(INITIAL_SCORE, MAX_SCORE); + private final EthPeer mockEthPeer = mock(EthPeer.class); @Test public void shouldThrowOnInvalidInitialScore() { @@ -36,65 +38,49 @@ public void shouldThrowOnInvalidInitialScore() { @Test public void shouldOnlyDisconnectWhenTimeoutLimitReached() { - assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_HEADERS)).isEmpty(); - assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_HEADERS)).isEmpty(); - assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_HEADERS)).isEmpty(); - assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_HEADERS)).isEmpty(); - assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_HEADERS)).contains(TIMEOUT); + sendRequestTimeouts(EthPV62.GET_BLOCK_HEADERS, PeerReputation.TIMEOUT_THRESHOLD - 1); + assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_HEADERS, mockEthPeer)) + .contains(TIMEOUT); } @Test public void shouldTrackTimeoutsSeparatelyForDifferentRequestTypes() { - assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_HEADERS)).isEmpty(); - assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_HEADERS)).isEmpty(); - assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_HEADERS)).isEmpty(); - assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_HEADERS)).isEmpty(); - - assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_BODIES)).isEmpty(); - assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_BODIES)).isEmpty(); - assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_BODIES)).isEmpty(); - assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_BODIES)).isEmpty(); - - assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_HEADERS)).contains(TIMEOUT); - assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_BODIES)).contains(TIMEOUT); + sendRequestTimeouts(EthPV62.GET_BLOCK_HEADERS, PeerReputation.TIMEOUT_THRESHOLD - 1); + sendRequestTimeouts(EthPV62.GET_BLOCK_BODIES, PeerReputation.TIMEOUT_THRESHOLD - 1); + + assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_HEADERS, mockEthPeer)) + .contains(TIMEOUT); + assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_BODIES, mockEthPeer)) + .contains(TIMEOUT); } @Test public void shouldResetTimeoutCountForRequestType() { - assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_HEADERS)).isEmpty(); - assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_HEADERS)).isEmpty(); - - assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_BODIES)).isEmpty(); - assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_BODIES)).isEmpty(); - assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_BODIES)).isEmpty(); - assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_BODIES)).isEmpty(); + sendRequestTimeouts(EthPV62.GET_BLOCK_HEADERS, PeerReputation.TIMEOUT_THRESHOLD - 1); + sendRequestTimeouts(EthPV62.GET_BLOCK_BODIES, PeerReputation.TIMEOUT_THRESHOLD - 1); reputation.resetTimeoutCount(EthPV62.GET_BLOCK_HEADERS); - assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_HEADERS)).isEmpty(); - assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_BODIES)).contains(TIMEOUT); + assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_HEADERS, mockEthPeer)).isEmpty(); + assertThat(reputation.recordRequestTimeout(EthPV62.GET_BLOCK_BODIES, mockEthPeer)) + .contains(TIMEOUT); } @Test public void shouldOnlyDisconnectWhenEmptyResponseThresholdReached() { - assertThat(reputation.recordUselessResponse(1001)).isEmpty(); - assertThat(reputation.recordUselessResponse(1002)).isEmpty(); - assertThat(reputation.recordUselessResponse(1003)).isEmpty(); - assertThat(reputation.recordUselessResponse(1004)).isEmpty(); - assertThat(reputation.recordUselessResponse(1005)).contains(USELESS_PEER); + sendUselessResponses(1001, PeerReputation.USELESS_RESPONSE_THRESHOLD - 1); + assertThat(reputation.recordUselessResponse(1005, mockEthPeer)) + .contains(USELESS_PEER_USELESS_RESPONSES); } @Test public void shouldDiscardEmptyResponseRecordsAfterTimeWindowElapses() { // Bring it to the brink of disconnection. - assertThat(reputation.recordUselessResponse(1001)).isEmpty(); - assertThat(reputation.recordUselessResponse(1002)).isEmpty(); - assertThat(reputation.recordUselessResponse(1003)).isEmpty(); - assertThat(reputation.recordUselessResponse(1004)).isEmpty(); + sendUselessResponses(1001, PeerReputation.USELESS_RESPONSE_THRESHOLD - 1); // But then the next empty response doesn't come in until after the window expires on the first assertThat( reputation.recordUselessResponse( - 1001 + PeerReputation.USELESS_RESPONSE_WINDOW_IN_MILLIS + 1)) + 1001 + PeerReputation.USELESS_RESPONSE_WINDOW_IN_MILLIS + 1, mockEthPeer)) .isEmpty(); } @@ -111,4 +97,16 @@ public void shouldNotIncreaseScoreOverMax() { } assertThat(reputation.getScore()).isEqualTo(MAX_SCORE); } + + private void sendRequestTimeouts(final int requestType, final int repeatCount) { + for (int i = 0; i < repeatCount; i++) { + assertThat(reputation.recordRequestTimeout(requestType, mockEthPeer)).isEmpty(); + } + } + + private void sendUselessResponses(final long timestamp, final int repeatCount) { + for (int i = 0; i < repeatCount; i++) { + assertThat(reputation.recordUselessResponse(timestamp + i, mockEthPeer)).isEmpty(); + } + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/RespondingEthPeer.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/RespondingEthPeer.java index 7427545984b..58f4893432c 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/RespondingEthPeer.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/RespondingEthPeer.java @@ -121,7 +121,9 @@ private static RespondingEthPeer create( final Hash chainHeadHash, final Difficulty totalDifficulty, final OptionalLong estimatedHeight, - final List peerValidators) { + final List peerValidators, + final boolean isServingSnap, + final boolean addToEthPeers) { final EthPeers ethPeers = ethProtocolManager.ethContext().getEthPeers(); final Set caps = new HashSet<>(Collections.singletonList(EthProtocol.ETH63)); @@ -130,10 +132,24 @@ private static RespondingEthPeer create( new MockPeerConnection( caps, (cap, msg, conn) -> outgoingMessages.add(new OutgoingMessage(cap, msg))); ethPeers.registerNewConnection(peerConnection, peerValidators); + final int before = ethPeers.peerCount(); final EthPeer peer = ethPeers.peer(peerConnection); peer.registerStatusReceived(chainHeadHash, totalDifficulty, 63, peerConnection); estimatedHeight.ifPresent(height -> peer.chainState().update(chainHeadHash, height)); - peer.registerStatusSent(peerConnection); + if (addToEthPeers) { + peer.registerStatusSent(peerConnection); + ethPeers.addPeerToEthPeers(peer); + while (ethPeers.peerCount() + <= before) { // this is needed to make sure that the peer is added to the active + // connections + try { + Thread.sleep(100L); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + peer.setIsServingSnap(isServingSnap); return new RespondingEthPeer( ethProtocolManager, snapProtocolManager, peerConnection, peer, outgoingMessages); @@ -396,6 +412,8 @@ public static class Builder { private Difficulty totalDifficulty = Difficulty.of(1000L); private OptionalLong estimatedHeight = OptionalLong.of(1000L); private final List peerValidators = new ArrayList<>(); + private boolean isServingSnap = false; + private boolean addToEthPeers = true; public RespondingEthPeer build() { checkNotNull(ethProtocolManager, "Must configure EthProtocolManager"); @@ -406,7 +424,9 @@ public RespondingEthPeer build() { chainHeadHash, totalDifficulty, estimatedHeight, - peerValidators); + peerValidators, + isServingSnap, + addToEthPeers); } public Builder ethProtocolManager(final EthProtocolManager ethProtocolManager) { @@ -444,6 +464,11 @@ public Builder estimatedHeight(final long estimatedHeight) { return this; } + public Builder isServingSnap(final boolean isServingSnap) { + this.isServingSnap = isServingSnap; + return this; + } + public Builder peerValidators(final List peerValidators) { checkNotNull(peerValidators); this.peerValidators.addAll(peerValidators); @@ -454,6 +479,11 @@ public Builder peerValidators(final PeerValidator... peerValidators) { peerValidators(Arrays.asList(peerValidators)); return this; } + + public Builder addToEthPeers(final boolean addToEthPeers) { + this.addToEthPeers = addToEthPeers; + return this; + } } static class OutgoingMessage { diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/AbstractMessageTaskTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/AbstractMessageTaskTest.java index c5ca2f3d8a7..6dbea259cc2 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/AbstractMessageTaskTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/AbstractMessageTaskTest.java @@ -23,8 +23,10 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.eth.EthProtocol; import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.manager.EthContext; @@ -36,15 +38,17 @@ import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer; import org.hyperledger.besu.ethereum.eth.manager.task.EthTask; +import org.hyperledger.besu.ethereum.eth.sync.SyncMode; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolFactory; +import org.hyperledger.besu.ethereum.forkid.ForkIdManager; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.testutil.DeterministicEthScheduler; import org.hyperledger.besu.testutil.TestClock; @@ -104,6 +108,7 @@ public static void setup() { @BeforeEach public void setupTest() { + protocolContext.getBadBlockManager().reset(); peersDoTimeout = new AtomicBoolean(false); peerCountToTimeout = new AtomicInteger(0); ethPeers = @@ -118,8 +123,10 @@ public void setupTest() { Bytes.random(64), MAX_PEERS, MAX_PEERS, - MAX_PEERS, - false)); + false, + SyncMode.FAST, + new ForkIdManager( + blockchain, Collections.emptyList(), Collections.emptyList(), false))); final EthMessages ethMessages = new EthMessages(); final EthScheduler ethScheduler = @@ -136,8 +143,8 @@ public void setupTest() { metricsSystem, syncState, TransactionPoolConfiguration.DEFAULT, - null, - new BlobCache()); + new BlobCache(), + MiningParameters.newDefault()); transactionPool.setEnabled(); ethProtocolManager = @@ -162,11 +169,9 @@ protected abstract void assertResultMatchesExpectation( @Test public void completesWhenPeersAreResponsive() { // Setup a responsive peer - final RespondingEthPeer.Responder responder = - RespondingEthPeer.blockchainResponder( - blockchain, protocolContext.getWorldStateArchive(), transactionPool); + final RespondingEthPeer.Responder responder = getFullResponder(); final RespondingEthPeer respondingPeer = - EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 32); // Setup data to be requested and expected response final T requestedData = generateDataToBeRequested(); @@ -185,12 +190,13 @@ public void completesWhenPeersAreResponsive() { assertThat(done).isTrue(); assertResultMatchesExpectation(requestedData, actualResult.get(), respondingPeer.getEthPeer()); + assertNoBadBlocks(); } @Test public void doesNotCompleteWhenPeersDoNotRespond() { // Setup a unresponsive peer - EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 32); // Setup data to be requested final T requestedData = generateDataToBeRequested(); @@ -209,7 +215,7 @@ public void doesNotCompleteWhenPeersDoNotRespond() { @Test public void cancel() { // Setup a unresponsive peer - EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 32); // Setup data to be requested final T requestedData = generateDataToBeRequested(); @@ -224,4 +230,15 @@ public void cancel() { assertThat(future.isCancelled()).isTrue(); assertThat(task.run().isCancelled()).isTrue(); } + + protected RespondingEthPeer.Responder getFullResponder() { + return RespondingEthPeer.blockchainResponder( + blockchain, protocolContext.getWorldStateArchive(), transactionPool); + } + + protected void assertNoBadBlocks() { + BadBlockManager badBlockManager = protocolContext.getBadBlockManager(); + assertThat(badBlockManager.getBadBlocks().size()).isEqualTo(0); + assertThat(badBlockManager.getBadHeaders().size()).isEqualTo(0); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/PeerMessageTaskTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/PeerMessageTaskTest.java index 1405fbc24ac..0ed6569387b 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/PeerMessageTaskTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/PeerMessageTaskTest.java @@ -58,7 +58,7 @@ public void completesWhenPeerReturnsPartialResult() { protocolSchedule, 0.5f); final RespondingEthPeer respondingEthPeer = - EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 32); // Execute task and wait for response final AtomicReference actualResult = new AtomicReference<>(); @@ -109,7 +109,7 @@ public void completesWhenPeersSendEmptyResponses() { // Setup a unresponsive peer final RespondingEthPeer.Responder responder = RespondingEthPeer.emptyResponder(); final RespondingEthPeer respondingEthPeer = - EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 32); // Setup data to be requested final T requestedData = generateDataToBeRequested(); @@ -129,7 +129,7 @@ public void recordsTimeoutAgainstPeerWhenTaskTimesOut() { peersDoTimeout.set(true); // Setup a unresponsive peer final RespondingEthPeer respondingEthPeer = - EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1000); + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 32); // Setup data to be requested final T requestedData = generateDataToBeRequested(); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/RetryingSwitchingPeerMessageTaskTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/RetryingSwitchingPeerMessageTaskTest.java index 084c730a6e3..374c217a605 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/RetryingSwitchingPeerMessageTaskTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/ethtaskutils/RetryingSwitchingPeerMessageTaskTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -136,6 +136,10 @@ public void failsWhenAllPeersFail() { assertThat(future.isDone()).isTrue(); assertThat(future.isCompletedExceptionally()).isTrue(); assertThatThrownBy(future::get).hasCauseInstanceOf(MaxRetriesReachedException.class); + + // since we are below the max number of peers, no peer should be disconnected + assertThat(firstPeer.getEthPeer().isDisconnected()).isFalse(); + assertThat(secondPeer.getEthPeer().isDisconnected()).isFalse(); } @Test diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServerTest.java new file mode 100644 index 00000000000..a41da1ef6b8 --- /dev/null +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/snap/SnapServerTest.java @@ -0,0 +1,858 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.manager.snap; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.hyperledger.besu.ethereum.eth.manager.snap.SnapServer.HASH_LAST; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.eth.manager.EthMessages; +import org.hyperledger.besu.ethereum.eth.messages.snap.AccountRangeMessage; +import org.hyperledger.besu.ethereum.eth.messages.snap.ByteCodesMessage; +import org.hyperledger.besu.ethereum.eth.messages.snap.GetAccountRangeMessage; +import org.hyperledger.besu.ethereum.eth.messages.snap.GetByteCodesMessage; +import org.hyperledger.besu.ethereum.eth.messages.snap.GetStorageRangeMessage; +import org.hyperledger.besu.ethereum.eth.messages.snap.GetTrieNodesMessage; +import org.hyperledger.besu.ethereum.eth.messages.snap.StorageRangeMessage; +import org.hyperledger.besu.ethereum.eth.messages.snap.TrieNodesMessage; +import org.hyperledger.besu.ethereum.proof.WorldStateProofProvider; +import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.ethereum.rlp.RLP; +import org.hyperledger.besu.ethereum.trie.CompactEncoding; +import org.hyperledger.besu.ethereum.trie.MerkleTrie; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.common.storage.flat.FlatDbStrategyProvider; +import org.hyperledger.besu.ethereum.trie.patricia.SimpleMerklePatriciaTrie; +import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.ethereum.worldstate.FlatDbMode; +import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.metrics.ObservableMetricsSystem; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; +import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; +import org.hyperledger.besu.services.kvstore.SegmentedInMemoryKeyValueStorage; + +import java.math.BigInteger; +import java.util.Collections; +import java.util.List; +import java.util.NavigableMap; +import java.util.Optional; +import java.util.Random; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class SnapServerTest { + static Random rand = new Random(); + + record SnapTestAccount( + Hash addressHash, + StateTrieAccountValue accountValue, + MerkleTrie storage, + Bytes code) { + Bytes accountRLP() { + return RLP.encode(accountValue::writeTo); + } + } + + static final ObservableMetricsSystem noopMetrics = new NoOpMetricsSystem(); + final SegmentedInMemoryKeyValueStorage storage = new SegmentedInMemoryKeyValueStorage(); + + // force a full flat db with code stored by code hash: + final BonsaiWorldStateKeyValueStorage inMemoryStorage = + new BonsaiWorldStateKeyValueStorage( + new FlatDbStrategyProvider(noopMetrics, DataStorageConfiguration.DEFAULT_BONSAI_CONFIG) { + @Override + public FlatDbMode getFlatDbMode() { + return FlatDbMode.FULL; + } + + @Override + protected boolean deriveUseCodeStorageByHash( + final SegmentedKeyValueStorage composedWorldStateStorage) { + return true; + } + }, + storage, + new InMemoryKeyValueStorage()); + + final WorldStateStorageCoordinator storageCoordinator = + new WorldStateStorageCoordinator(inMemoryStorage); + final StoredMerklePatriciaTrie storageTrie = + new StoredMerklePatriciaTrie<>( + inMemoryStorage::getAccountStateTrieNode, Function.identity(), Function.identity()); + final WorldStateProofProvider proofProvider = new WorldStateProofProvider(storageCoordinator); + + final Function> spyProvider = + spy( + new Function>() { + // explicit non-final class is necessary for Mockito to spy: + @Override + public Optional apply(final Hash hash) { + return Optional.of(inMemoryStorage); + } + }); + + final SnapServer snapServer = + new SnapServer(new EthMessages(), storageCoordinator, spyProvider).start(); + + final SnapTestAccount acct1 = createTestAccount("10"); + final SnapTestAccount acct2 = createTestAccount("20"); + final SnapTestAccount acct3 = createTestContractAccount("30", inMemoryStorage); + final SnapTestAccount acct4 = createTestContractAccount("40", inMemoryStorage); + + @BeforeEach + public void setup() { + snapServer.start(); + } + + @Test + public void assertNoStartNoOp() { + // account found at startHash + insertTestAccounts(acct4, acct3, acct1, acct2); + + // stop snap server so that we should not be processing snap requests + snapServer.stop(); + + var rangeData = requestAccountRange(acct1.addressHash, acct4.addressHash).accountData(false); + + // assert empty account response and no attempt to fetch worldstate + assertThat(rangeData.accounts().isEmpty()).isTrue(); + assertThat(rangeData.proofs().isEmpty()).isTrue(); + verify(spyProvider, never()).apply(any()); + + // assert empty storage response and no attempt to fetch worldstate + var storageRange = + requestStorageRange(List.of(acct3.addressHash), Hash.ZERO, HASH_LAST).slotsData(false); + assertThat(storageRange.slots().isEmpty()).isTrue(); + assertThat(storageRange.proofs().isEmpty()).isTrue(); + verify(spyProvider, never()).apply(any()); + + // assert empty trie nodes response and no attempt to fetch worldstate + var trieNodes = + requestTrieNodes(storageTrie.getRootHash(), List.of(List.of(Bytes.fromHexString("0x01")))) + .nodes(false); + assertThat(trieNodes.isEmpty()).isTrue(); + verify(spyProvider, never()).apply(any()); + + // assert empty code response and no attempt to fetch worldstate + var codes = + requestByteCodes(List.of(acct3.accountValue.getCodeHash())).bytecodes(false).codes(); + assertThat(codes.isEmpty()).isTrue(); + verify(spyProvider, never()).apply(any()); + } + + @Test + public void assertEmptyRangeLeftProofOfExclusionAndNextAccount() { + // for a range request that returns empty, we should return just a proof of exclusion on the + // left and the next account after the limit hash + insertTestAccounts(acct1, acct4); + + var rangeData = + getAndVerifyAccountRangeData(requestAccountRange(acct2.addressHash, acct3.addressHash), 1); + + // expect to find only one value acct4, outside the requested range + var outOfRangeVal = rangeData.accounts().entrySet().stream().findFirst(); + assertThat(outOfRangeVal).isPresent(); + assertThat(outOfRangeVal.get().getKey()).isEqualTo(acct4.addressHash()); + + // assert proofs are valid for the requested range + assertThat(assertIsValidAccountRangeProof(acct2.addressHash, rangeData)).isTrue(); + } + + @Test + public void assertAccountLimitRangeResponse() { + // assert we limit the range response according to size + final int acctCount = 2000; + final long acctRLPSize = 105; + + List randomLoad = IntStream.range(1, 4096).boxed().collect(Collectors.toList()); + Collections.shuffle(randomLoad); + randomLoad.stream() + .forEach( + i -> + insertTestAccounts( + createTestAccount( + Bytes.concatenate( + Bytes.fromHexString("0x40"), + Bytes.fromHexStringLenient(Integer.toHexString(i * 256))) + .toHexString()))); + + final BytesValueRLPOutput tmp = new BytesValueRLPOutput(); + tmp.startList(); + tmp.writeBytes(storageTrie.getRootHash()); + tmp.writeBytes(Hash.ZERO); + tmp.writeBytes(HASH_LAST); + tmp.writeBigIntegerScalar(BigInteger.valueOf(acctRLPSize * acctCount)); + tmp.endList(); + var tinyRangeLimit = new GetAccountRangeMessage(tmp.encoded()).wrapMessageData(BigInteger.ONE); + + var rangeData = + getAndVerifyAccountRangeData( + (AccountRangeMessage) snapServer.constructGetAccountRangeResponse(tinyRangeLimit), + acctCount); + + // assert proofs are valid for the requested range + assertThat(assertIsValidAccountRangeProof(Hash.ZERO, rangeData)).isTrue(); + } + + @Test + public void assertAccountLimitRangeResponse_atLeastOneAccount() { + List randomLoad = IntStream.range(1, 4096).boxed().collect(Collectors.toList()); + Collections.shuffle(randomLoad); + randomLoad.stream() + .forEach( + i -> + insertTestAccounts( + createTestAccount( + Bytes.concatenate( + Bytes.fromHexString("0x40"), + Bytes.fromHexStringLenient(Integer.toHexString(i * 256))) + .toHexString()))); + + final BytesValueRLPOutput tmp = new BytesValueRLPOutput(); + tmp.startList(); + tmp.writeBytes(storageTrie.getRootHash()); + tmp.writeBytes(Hash.ZERO); + tmp.writeBytes(HASH_LAST); + tmp.writeBigIntegerScalar(BigInteger.ZERO); + tmp.endList(); + var tinyRangeLimit = new GetAccountRangeMessage(tmp.encoded()).wrapMessageData(BigInteger.ZERO); + + var rangeData = + getAndVerifyAccountRangeData( + (AccountRangeMessage) snapServer.constructGetAccountRangeResponse(tinyRangeLimit), 1); + + // assert proofs are valid for the requested range + assertThat(assertIsValidAccountRangeProof(Hash.ZERO, rangeData)).isTrue(); + } + + @Test + public void assertLastEmptyRange() { + // When our final range request is empty, no next account is possible, + // and we should return just a proof of exclusion of the right + insertTestAccounts(acct1, acct2); + var rangeData = + getAndVerifyAccountRangeData(requestAccountRange(acct3.addressHash, acct4.addressHash), 0); + + // assert proofs are valid for the requested range + assertThat(assertIsValidAccountRangeProof(acct3.addressHash, rangeData)).isTrue(); + } + + @Test + public void assertAccountFoundAtStartHashProof() { + // account found at startHash + insertTestAccounts(acct4, acct3, acct1, acct2); + var rangeData = + getAndVerifyAccountRangeData(requestAccountRange(acct1.addressHash, acct4.addressHash), 4); + + // assert proofs are valid for requested range + assertThat(assertIsValidAccountRangeProof(acct1.addressHash, rangeData)).isTrue(); + } + + @Test + public void assertCompleteStorageForSingleAccount() { + insertTestAccounts(acct1, acct2, acct3, acct4); + var rangeData = requestStorageRange(List.of(acct3.addressHash), Hash.ZERO, HASH_LAST); + assertThat(rangeData).isNotNull(); + var slotsData = rangeData.slotsData(false); + assertThat(slotsData).isNotNull(); + assertThat(slotsData.slots()).isNotNull(); + assertThat(slotsData.slots().size()).isEqualTo(1); + var firstAccountStorages = slotsData.slots().first(); + assertThat(firstAccountStorages.size()).isEqualTo(10); + // no proofs for complete storage range: + assertThat(slotsData.proofs().size()).isEqualTo(0); + + assertThat( + assertIsValidStorageProof(acct3, Hash.ZERO, firstAccountStorages, slotsData.proofs())) + .isTrue(); + } + + @Test + public void assertPartialStorageForSingleAccountEmptyRange() { + insertTestAccounts(acct3); + var rangeData = + requestStorageRange( + List.of(acct3.addressHash), Hash.ZERO, Hash.fromHexStringLenient("0x00ff")); + assertThat(rangeData).isNotNull(); + var slotsData = rangeData.slotsData(false); + assertThat(slotsData).isNotNull(); + assertThat(slotsData.slots()).isNotNull(); + // expect 1 slot PAST the requested empty range + assertThat(slotsData.slots().size()).isEqualTo(1); + // expect left and right proofs for empty storage range: + assertThat(slotsData.proofs().size()).isGreaterThan(0); + // assert proofs are valid for the requested range + assertThat( + assertIsValidStorageProof( + acct3, Hash.ZERO, slotsData.slots().first(), slotsData.proofs())) + .isTrue(); + } + + @Test + public void assertPartialStorageLimitHashBetweenSlots() { + Bytes accountShortHash = Bytes.fromHexStringLenient("0x40"); + Hash accountFullHash = Hash.wrap(Bytes32.leftPad(accountShortHash)); + SnapTestAccount testAccount = createTestContractAccount(accountFullHash, 2, inMemoryStorage); + + Hash startHash = Hash.wrap(Bytes32.rightPad(Bytes.fromHexString("12"))); // slot 2 + Hash endHash = Hash.wrap(Bytes32.rightPad(Bytes.fromHexString("13"))); // between slots 2 and 3 + var rangeData = requestStorageRange(List.of(testAccount.addressHash), startHash, endHash); + + assertThat(rangeData).isNotNull(); + var slotsData = rangeData.slotsData(false); + assertThat(slotsData).isNotNull(); + assertThat(slotsData.slots()).isNotNull(); + assertThat(slotsData.slots().size()).isEqualTo(1); + var firstAccountStorages = slotsData.slots().first(); + // expecting to see 2 slots + assertThat(firstAccountStorages.size()).isEqualTo(2); + // assert proofs are valid for the requested range + assertThat( + assertIsValidStorageProof( + testAccount, startHash, firstAccountStorages, slotsData.proofs())) + .isTrue(); + } + + @Test + public void assertLastEmptyPartialStorageForSingleAccount() { + // When our final range request is empty, no next account is possible, + // and we should return just a proof of exclusion of the right + + insertTestAccounts(acct3); + var rangeData = requestStorageRange(List.of(acct3.addressHash), HASH_LAST, HASH_LAST); + assertThat(rangeData).isNotNull(); + var slotsData = rangeData.slotsData(false); + assertThat(slotsData).isNotNull(); + assertThat(slotsData.slots()).isNotNull(); + // expect no slots PAST the requested empty range + assertThat(slotsData.slots().size()).isEqualTo(0); + // expect left and right proofs for empty storage range: + assertThat(slotsData.proofs().size()).isGreaterThan(0); + // assert proofs are valid for the requested range + assertThat( + assertIsValidStorageProof( + acct3, + Hash.fromHexStringLenient("0xFF"), + Collections.emptyNavigableMap(), + slotsData.proofs())) + .isTrue(); + } + + @Test + public void assertStorageLimitRangeResponse() { + // assert we limit the range response according to bytessize + final int storageSlotSize = 69; + final int storageSlotCount = 16; + insertTestAccounts(acct1, acct2, acct3, acct4); + + final BytesValueRLPOutput tmp = new BytesValueRLPOutput(); + tmp.startList(); + tmp.writeBigIntegerScalar(BigInteger.ONE); + tmp.writeBytes(storageTrie.getRootHash()); + tmp.writeList( + List.of(acct3.addressHash, acct4.addressHash), + (hash, rlpOutput) -> rlpOutput.writeBytes(hash)); + tmp.writeBytes(Hash.ZERO); + tmp.writeBytes(HASH_LAST); + tmp.writeBigIntegerScalar(BigInteger.valueOf(storageSlotCount * storageSlotSize)); + tmp.endList(); + var tinyRangeLimit = new GetStorageRangeMessage(tmp.encoded()); + + var rangeData = + (StorageRangeMessage) snapServer.constructGetStorageRangeResponse(tinyRangeLimit); + + // assert proofs are valid for the requested range + assertThat(rangeData).isNotNull(); + var slotsData = rangeData.slotsData(false); + assertThat(slotsData).isNotNull(); + assertThat(slotsData.slots()).isNotNull(); + assertThat(slotsData.slots().size()).isEqualTo(2); + var firstAccountStorages = slotsData.slots().first(); + // expecting to see complete 10 slot storage for acct3 + assertThat(firstAccountStorages.size()).isEqualTo(10); + var secondAccountStorages = slotsData.slots().last(); + // expecting to see only 6 since request was limited to 16 slots + assertThat(secondAccountStorages.size()).isEqualTo(6); + // proofs required for interrupted storage range: + assertThat(slotsData.proofs().size()).isNotEqualTo(0); + + assertThat( + assertIsValidStorageProof(acct4, Hash.ZERO, secondAccountStorages, slotsData.proofs())) + .isTrue(); + } + + @Test + public void assertStorageLimitRangeResponse_atLeastOneSlot() { + insertTestAccounts(acct1, acct2, acct3, acct4); + + final BytesValueRLPOutput tmp = new BytesValueRLPOutput(); + tmp.startList(); + tmp.writeBigIntegerScalar(BigInteger.ONE); + tmp.writeBytes(storageTrie.getRootHash()); + tmp.writeList( + List.of(acct3.addressHash, acct4.addressHash), + (hash, rlpOutput) -> rlpOutput.writeBytes(hash)); + tmp.writeBytes(Hash.ZERO); + tmp.writeBytes(HASH_LAST); + tmp.writeBigIntegerScalar(BigInteger.ZERO); + tmp.endList(); + var tinyRangeLimit = new GetStorageRangeMessage(tmp.encoded()); + + var rangeData = + (StorageRangeMessage) snapServer.constructGetStorageRangeResponse(tinyRangeLimit); + + // assert proofs are valid for the requested range + assertThat(rangeData).isNotNull(); + var slotsData = rangeData.slotsData(false); + assertThat(slotsData).isNotNull(); + assertThat(slotsData.slots()).isNotNull(); + assertThat(slotsData.slots().size()).isEqualTo(1); + var firstAccountStorages = slotsData.slots().first(); + // expecting to see complete 10 slot storage for acct3 + assertThat(firstAccountStorages.size()).isEqualTo(1); + assertThat(slotsData.proofs().size()).isNotEqualTo(0); + + assertThat( + assertIsValidStorageProof(acct4, Hash.ZERO, firstAccountStorages, slotsData.proofs())) + .isTrue(); + } + + @Test + public void assertAccountTriePathRequest() { + insertTestAccounts(acct1, acct2, acct3, acct4); + var partialPathToAcct2 = CompactEncoding.bytesToPath(acct2.addressHash).slice(0, 1); + var partialPathToAcct1 = Bytes.fromHexString("0x01"); // first nibble is 1 + var trieNodeRequest = + requestTrieNodes( + storageTrie.getRootHash(), + List.of(List.of(partialPathToAcct2), List.of(partialPathToAcct1))); + assertThat(trieNodeRequest).isNotNull(); + List trieNodes = trieNodeRequest.nodes(false); + assertThat(trieNodes).isNotNull(); + assertThat(trieNodes.size()).isEqualTo(2); + } + + @Test + public void assertAccountTrieRequest_invalidEmptyPath() { + insertTestAccounts(acct1); + var partialPathToAcct1 = Bytes.fromHexString("0x01"); // first nibble is 1 + var trieNodeRequest = + requestTrieNodes( + storageTrie.getRootHash(), List.of(List.of(), List.of(partialPathToAcct1))); + assertThat(trieNodeRequest).isNotNull(); + List trieNodes = trieNodeRequest.nodes(false); + assertThat(trieNodes.isEmpty()).isTrue(); + } + + @Test + public void assertAccountTrieLimitRequest() { + insertTestAccounts(acct1, acct2, acct3, acct4); + final int accountNodeSize = 147; + final int accountNodeLimit = 3; + + var partialPathToAcct1 = Bytes.fromHexString("0x01"); // first nibble is 1 + var partialPathToAcct2 = CompactEncoding.bytesToPath(acct2.addressHash).slice(0, 1); + var partialPathToAcct3 = Bytes.fromHexString("0x03"); // first nibble is 1 + var partialPathToAcct4 = Bytes.fromHexString("0x04"); // first nibble is 1 + final BytesValueRLPOutput tmp = new BytesValueRLPOutput(); + tmp.startList(); + tmp.writeBigIntegerScalar(BigInteger.ONE); + tmp.writeBytes(storageTrie.getRootHash()); + tmp.writeList( + List.of( + List.of(partialPathToAcct4), + List.of(partialPathToAcct3), + List.of(partialPathToAcct2), + List.of(partialPathToAcct1)), + (path, rlpOutput) -> + rlpOutput.writeList(path, (b, subRlpOutput) -> subRlpOutput.writeBytes(b))); + tmp.writeBigIntegerScalar(BigInteger.valueOf(accountNodeLimit * accountNodeSize)); + tmp.endList(); + + var trieNodeRequest = + (TrieNodesMessage) + snapServer.constructGetTrieNodesResponse(new GetTrieNodesMessage(tmp.encoded())); + + assertThat(trieNodeRequest).isNotNull(); + List trieNodes = trieNodeRequest.nodes(false); + assertThat(trieNodes).isNotNull(); + // TODO: adjust this assertion after sorting out the request fudge factor + assertThat(trieNodes.size()).isEqualTo(accountNodeLimit * 90 / 100); + } + + @Test + public void assertAccountTrieLimitRequest_atLeastOneTrieNode() { + insertTestAccounts(acct1, acct2, acct3, acct4); + + var partialPathToAcct1 = Bytes.fromHexString("0x01"); // first nibble is 1 + var partialPathToAcct2 = CompactEncoding.bytesToPath(acct2.addressHash).slice(0, 1); + var partialPathToAcct3 = Bytes.fromHexString("0x03"); // first nibble is 1 + var partialPathToAcct4 = Bytes.fromHexString("0x04"); // first nibble is 1 + final BytesValueRLPOutput tmp = new BytesValueRLPOutput(); + tmp.startList(); + tmp.writeBigIntegerScalar(BigInteger.ONE); + tmp.writeBytes(storageTrie.getRootHash()); + tmp.writeList( + List.of( + List.of(partialPathToAcct4), + List.of(partialPathToAcct3), + List.of(partialPathToAcct2), + List.of(partialPathToAcct1)), + (path, rlpOutput) -> + rlpOutput.writeList(path, (b, subRlpOutput) -> subRlpOutput.writeBytes(b))); + tmp.writeBigIntegerScalar(BigInteger.ZERO); + tmp.endList(); + + var trieNodeRequest = + (TrieNodesMessage) + snapServer.constructGetTrieNodesResponse(new GetTrieNodesMessage(tmp.encoded())); + + assertThat(trieNodeRequest).isNotNull(); + List trieNodes = trieNodeRequest.nodes(false); + assertThat(trieNodes).isNotNull(); + assertThat(trieNodes.size()).isEqualTo(1); + } + + @Test + public void assertStorageTriePathRequest() { + insertTestAccounts(acct1, acct2, acct3, acct4); + var pathToSlot11 = CompactEncoding.encode(Bytes.fromHexStringLenient("0x0101")); + var pathToSlot12 = CompactEncoding.encode(Bytes.fromHexStringLenient("0x0102")); + var pathToSlot1a = CompactEncoding.encode(Bytes.fromHexStringLenient("0x010A")); // not present + var trieNodeRequest = + requestTrieNodes( + storageTrie.getRootHash(), + List.of( + List.of(acct3.addressHash, pathToSlot11, pathToSlot12, pathToSlot1a), + List.of(acct4.addressHash, pathToSlot11, pathToSlot12, pathToSlot1a))); + assertThat(trieNodeRequest).isNotNull(); + List trieNodes = trieNodeRequest.nodes(false); + assertThat(trieNodes).isNotNull(); + assertThat(trieNodes.size()).isEqualTo(6); + assertThat(trieNodes.get(2)).isEqualTo(Bytes.EMPTY); + assertThat(trieNodes.get(5)).isEqualTo(Bytes.EMPTY); + } + + @Test + public void assertStorageTriePathRequest_accountNotPresent() { + insertTestAccounts(acct4); + var pathToSlot11 = CompactEncoding.encode(Bytes.fromHexStringLenient("0x0101")); + var trieNodeRequest = + requestTrieNodes( + storageTrie.getRootHash(), + List.of( + List.of(acct3.addressHash, pathToSlot11) // account not present + )); + assertThat(trieNodeRequest).isNotNull(); + List trieNodes = trieNodeRequest.nodes(false); + assertThat(trieNodes).isNotNull(); + assertThat(trieNodes.size()).isEqualTo(0); + } + + @Test + public void assertStorageTrieShortAccountHashPathRequest() { + Bytes accountShortHash = Bytes.fromHexStringLenient("0x40"); + Hash accountFullHash = Hash.wrap(Bytes32.leftPad(accountShortHash)); + SnapTestAccount testAccount = createTestContractAccount(accountFullHash, 1, inMemoryStorage); + insertTestAccounts(testAccount); + var pathToSlot11 = CompactEncoding.encode(Bytes.fromHexStringLenient("0x0101")); + var pathToSlot12 = CompactEncoding.encode(Bytes.fromHexStringLenient("0x0102")); + var trieNodeRequest = + requestTrieNodes( + storageTrie.getRootHash(), + List.of(List.of(accountShortHash, pathToSlot11, pathToSlot12))); + assertThat(trieNodeRequest).isNotNull(); + List trieNodes = trieNodeRequest.nodes(false); + assertThat(trieNodes).isNotNull(); + assertThat(trieNodes.size()).isEqualTo(2); + } + + @Test + public void assertStorageTrieLimitRequest() { + insertTestAccounts(acct1, acct2, acct3, acct4); + final int trieNodeSize = 69; + final int trieNodeLimit = 3; + + var pathToSlot11 = CompactEncoding.encode(Bytes.fromHexStringLenient("0x0101")); + var pathToSlot12 = CompactEncoding.encode(Bytes.fromHexStringLenient("0x0102")); + var pathToSlot1a = CompactEncoding.encode(Bytes.fromHexStringLenient("0x010A")); // not present + + final BytesValueRLPOutput tmp = new BytesValueRLPOutput(); + tmp.startList(); + tmp.writeBigIntegerScalar(BigInteger.ONE); + tmp.writeBytes(storageTrie.getRootHash()); + tmp.writeList( + List.of( + List.of(acct3.addressHash, pathToSlot11, pathToSlot12, pathToSlot1a), + List.of(acct4.addressHash, pathToSlot11, pathToSlot12, pathToSlot1a)), + (path, rlpOutput) -> + rlpOutput.writeList(path, (b, subRlpOutput) -> subRlpOutput.writeBytes(b))); + tmp.writeBigIntegerScalar(BigInteger.valueOf(trieNodeLimit * trieNodeSize)); + tmp.endList(); + + var trieNodeRequest = + (TrieNodesMessage) + snapServer.constructGetTrieNodesResponse(new GetTrieNodesMessage(tmp.encoded())); + + assertThat(trieNodeRequest).isNotNull(); + List trieNodes = trieNodeRequest.nodes(false); + assertThat(trieNodes).isNotNull(); + assertThat(trieNodes.size()).isEqualTo(3); + } + + @Test + public void assertStorageTrieLimitRequest_atLeastOneTrieNode() { + insertTestAccounts(acct1, acct2, acct3, acct4); + + var pathToSlot11 = CompactEncoding.encode(Bytes.fromHexStringLenient("0x0101")); + var pathToSlot12 = CompactEncoding.encode(Bytes.fromHexStringLenient("0x0102")); + var pathToSlot1a = CompactEncoding.encode(Bytes.fromHexStringLenient("0x010A")); // not present + + final BytesValueRLPOutput tmp = new BytesValueRLPOutput(); + tmp.startList(); + tmp.writeBigIntegerScalar(BigInteger.ONE); + tmp.writeBytes(storageTrie.getRootHash()); + tmp.writeList( + List.of( + List.of(acct3.addressHash, pathToSlot11, pathToSlot12, pathToSlot1a), + List.of(acct4.addressHash, pathToSlot11, pathToSlot12, pathToSlot1a)), + (path, rlpOutput) -> + rlpOutput.writeList(path, (b, subRlpOutput) -> subRlpOutput.writeBytes(b))); + tmp.writeBigIntegerScalar(BigInteger.ZERO); + tmp.endList(); + + var trieNodeRequest = + (TrieNodesMessage) + snapServer.constructGetTrieNodesResponse(new GetTrieNodesMessage(tmp.encoded())); + + assertThat(trieNodeRequest).isNotNull(); + List trieNodes = trieNodeRequest.nodes(false); + assertThat(trieNodes).isNotNull(); + assertThat(trieNodes.size()).isEqualTo(1); + } + + @Test + public void assertCodePresent() { + insertTestAccounts(acct1, acct2, acct3, acct4); + var codeRequest = + requestByteCodes( + List.of(acct3.accountValue.getCodeHash(), acct4.accountValue.getCodeHash())); + assertThat(codeRequest).isNotNull(); + ByteCodesMessage.ByteCodes codes = codeRequest.bytecodes(false); + assertThat(codes).isNotNull(); + assertThat(codes.codes().size()).isEqualTo(2); + } + + @Test + public void assertCodeLimitRequest() { + insertTestAccounts(acct1, acct2, acct3, acct4); + final int codeSize = 32; + final int codeLimit = 2; + + final BytesValueRLPOutput tmp = new BytesValueRLPOutput(); + tmp.startList(); + tmp.writeBigIntegerScalar(BigInteger.ONE); + tmp.writeList( + List.of(acct3.accountValue.getCodeHash(), acct4.accountValue.getCodeHash()), + (hash, rlpOutput) -> rlpOutput.writeBytes(hash)); + tmp.writeBigIntegerScalar(BigInteger.valueOf(codeSize * codeLimit)); + tmp.endList(); + + var codeRequest = + (ByteCodesMessage) + snapServer.constructGetBytecodesResponse(new GetByteCodesMessage(tmp.encoded())); + + assertThat(codeRequest).isNotNull(); + ByteCodesMessage.ByteCodes codes = codeRequest.bytecodes(false); + assertThat(codes).isNotNull(); + // TODO adjust this assertion after sorting out the request fudge factor + assertThat(codes.codes().size()).isEqualTo(codeLimit * 90 / 100); + } + + @Test + public void assertCodeLimitRequest_atLeastOneByteCode() { + insertTestAccounts(acct1, acct2, acct3, acct4); + + final BytesValueRLPOutput tmp = new BytesValueRLPOutput(); + tmp.startList(); + tmp.writeBigIntegerScalar(BigInteger.ONE); + tmp.writeList( + List.of(acct3.accountValue.getCodeHash(), acct4.accountValue.getCodeHash()), + (hash, rlpOutput) -> rlpOutput.writeBytes(hash)); + tmp.writeBigIntegerScalar(BigInteger.ZERO); + tmp.endList(); + + var codeRequest = + (ByteCodesMessage) + snapServer.constructGetBytecodesResponse(new GetByteCodesMessage(tmp.encoded())); + + assertThat(codeRequest).isNotNull(); + ByteCodesMessage.ByteCodes codes = codeRequest.bytecodes(false); + assertThat(codes).isNotNull(); + assertThat(codes.codes().size()).isEqualTo(1); + } + + static SnapTestAccount createTestAccount(final String hexAddr) { + return new SnapTestAccount( + Hash.wrap(Bytes32.rightPad(Bytes.fromHexString(hexAddr))), + new StateTrieAccountValue( + rand.nextInt(0, 1), Wei.of(rand.nextLong(0L, 1L)), Hash.EMPTY_TRIE_HASH, Hash.EMPTY), + new SimpleMerklePatriciaTrie<>(a -> a), + Bytes.EMPTY); + } + + static SnapTestAccount createTestContractAccount( + final String hexAddr, final BonsaiWorldStateKeyValueStorage storage) { + final Hash acctHash = Hash.wrap(Bytes32.rightPad(Bytes.fromHexString(hexAddr))); + return createTestContractAccount(acctHash, 1, storage); + } + + static SnapTestAccount createTestContractAccount( + final Hash acctHash, final int slotKeyGap, final BonsaiWorldStateKeyValueStorage storage) { + MerkleTrie trie = + new StoredMerklePatriciaTrie<>( + (loc, hash) -> storage.getAccountStorageTrieNode(acctHash, loc, hash), + Hash.EMPTY_TRIE_HASH, + a -> a, + a -> a); + Bytes32 mockCode = Bytes32.random(); + + // mock some storage data + var flatdb = storage.getFlatDbStrategy(); + var updater = storage.updater(); + updater.putCode(Hash.hash(mockCode), mockCode); + IntStream.iterate(10, i -> i < 20, i -> i + slotKeyGap) + .boxed() + .forEach( + i -> { + Bytes32 mockBytes32 = Bytes32.rightPad(Bytes.fromHexString(i.toString())); + var rlpOut = new BytesValueRLPOutput(); + rlpOut.writeBytes(mockBytes32); + trie.put(mockBytes32, rlpOut.encoded()); + flatdb.putFlatAccountStorageValueByStorageSlotHash( + updater.getWorldStateTransaction(), + acctHash, + Hash.wrap(mockBytes32), + mockBytes32); + }); + trie.commit( + (location, key, value) -> + updater.putAccountStorageTrieNode(acctHash, location, key, value)); + updater.commit(); + return new SnapTestAccount( + acctHash, + new StateTrieAccountValue( + rand.nextInt(0, 1), Wei.of(rand.nextLong(0L, 1L)), + Hash.wrap(trie.getRootHash()), Hash.hash(mockCode)), + trie, + mockCode); + } + + void insertTestAccounts(final SnapTestAccount... accounts) { + final var updater = inMemoryStorage.updater(); + for (SnapTestAccount account : accounts) { + updater.putAccountInfoState(account.addressHash(), account.accountRLP()); + storageTrie.put(account.addressHash(), account.accountRLP()); + } + storageTrie.commit(updater::putAccountStateTrieNode); + updater.commit(); + } + + boolean assertIsValidAccountRangeProof( + final Hash startHash, final AccountRangeMessage.AccountRangeData accountRange) { + Bytes32 lastKey = + Optional.of(accountRange.accounts()) + .filter(z -> z.size() > 0) + .map(NavigableMap::lastKey) + .orElse(startHash); + + return proofProvider.isValidRangeProof( + startHash, + lastKey, + storageTrie.getRootHash(), + accountRange.proofs(), + accountRange.accounts()); + } + + boolean assertIsValidStorageProof( + final SnapTestAccount account, + final Hash startHash, + final NavigableMap slotRangeData, + final List proofs) { + + Bytes32 lastKey = + Optional.of(slotRangeData) + .filter(z -> z.size() > 0) + .map(NavigableMap::lastKey) + .orElse(startHash); + + // this is only working for single account ranges for now + return proofProvider.isValidRangeProof( + startHash, lastKey, account.accountValue.getStorageRoot(), proofs, slotRangeData); + } + + AccountRangeMessage requestAccountRange(final Hash startHash, final Hash limitHash) { + return (AccountRangeMessage) + snapServer.constructGetAccountRangeResponse( + GetAccountRangeMessage.create( + Hash.wrap(storageTrie.getRootHash()), startHash, limitHash) + .wrapMessageData(BigInteger.ONE)); + } + + StorageRangeMessage requestStorageRange( + final List accountHashes, final Hash startHash, final Hash limitHash) { + return (StorageRangeMessage) + snapServer.constructGetStorageRangeResponse( + GetStorageRangeMessage.create( + Hash.wrap(storageTrie.getRootHash()), accountHashes, startHash, limitHash) + .wrapMessageData(BigInteger.ONE)); + } + + TrieNodesMessage requestTrieNodes(final Bytes32 rootHash, final List> trieNodesList) { + return (TrieNodesMessage) + snapServer.constructGetTrieNodesResponse( + GetTrieNodesMessage.create(Hash.wrap(rootHash), trieNodesList) + .wrapMessageData(BigInteger.ONE)); + } + + ByteCodesMessage requestByteCodes(final List codeHashes) { + return (ByteCodesMessage) + snapServer.constructGetBytecodesResponse( + GetByteCodesMessage.create(codeHashes).wrapMessageData(BigInteger.ONE)); + } + + AccountRangeMessage.AccountRangeData getAndVerifyAccountRangeData( + final AccountRangeMessage range, final int expectedSize) { + assertThat(range).isNotNull(); + var accountData = range.accountData(false); + assertThat(accountData).isNotNull(); + assertThat(accountData.accounts().size()).isEqualTo(expectedSize); + return accountData; + } +} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractRetryingPeerTaskTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractRetryingPeerTaskTest.java index e180420a4f5..989ae2b4cdf 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractRetryingPeerTaskTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/AbstractRetryingPeerTaskTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/BufferedGetPooledTransactionsFromPeerFetcherTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/BufferedGetPooledTransactionsFromPeerFetcherTest.java index 17b834cedf1..232ccc0aae6 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/BufferedGetPooledTransactionsFromPeerFetcherTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/BufferedGetPooledTransactionsFromPeerFetcherTest.java @@ -29,6 +29,7 @@ import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; +import org.hyperledger.besu.ethereum.eth.manager.EthPeers; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.PeerTransactionTracker; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; @@ -57,6 +58,7 @@ public class BufferedGetPooledTransactionsFromPeerFetcherTest { @Mock TransactionPool transactionPool; @Mock EthContext ethContext; @Mock EthScheduler ethScheduler; + @Mock EthPeers ethPeers; private final BlockDataGenerator generator = new BlockDataGenerator(); @@ -67,7 +69,8 @@ public class BufferedGetPooledTransactionsFromPeerFetcherTest { @BeforeEach public void setup() { metricsSystem = new StubMetricsSystem(); - transactionTracker = new PeerTransactionTracker(); + when(ethContext.getEthPeers()).thenReturn(ethPeers); + transactionTracker = new PeerTransactionTracker(ethPeers); when(ethContext.getScheduler()).thenReturn(ethScheduler); ScheduledFuture mock = mock(ScheduledFuture.class); fetcher = diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/GetBodiesFromPeerTaskTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/GetBodiesFromPeerTaskTest.java index dbe1ba9bc86..b8546ad532d 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/GetBodiesFromPeerTaskTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/GetBodiesFromPeerTaskTest.java @@ -24,8 +24,10 @@ import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.Deposit; +import org.hyperledger.besu.ethereum.core.DepositRequest; +import org.hyperledger.besu.ethereum.core.Request; import org.hyperledger.besu.ethereum.core.Withdrawal; +import org.hyperledger.besu.ethereum.core.WithdrawalRequest; import org.hyperledger.besu.ethereum.eth.manager.ethtaskutils.PeerMessageTaskTest; import java.util.ArrayList; @@ -87,9 +89,9 @@ public void assertBodyIdentifierUsesWithdrawalsToGenerateBodyIdentifiers() { } @Test - public void assertBodyIdentifierUsesDepositsToGenerateBodyIdentifiers() { - final Deposit deposit = - new Deposit( + public void assertBodyIdentifierUsesDepositRequestsToGenerateBodyIdentifiers() { + final Request deposit = + new DepositRequest( BLSPublicKey.fromHexString( "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e"), Bytes32.fromHexString( @@ -110,4 +112,26 @@ public void assertBodyIdentifierUsesDepositsToGenerateBodyIdentifiers() { .equals(new GetBodiesFromPeerTask.BodyIdentifier(bodyBlockWithDeposit))) .isFalse(); } + + @Test + public void assertBodyIdentifierUsesWithdrawalRequestsToGenerateBodyIdentifiers() { + final WithdrawalRequest withdrawalRequest = + new WithdrawalRequest( + Address.fromHexString("0x763c396673F9c391DCe3361A9A71C8E161388000"), + BLSPublicKey.fromHexString( + "0xb10a4a15bf67b328c9b101d09e5c6ee6672978fdad9ef0d9e2ceffaee99223555d8601f0cb3bcc4ce1af9864779a416e"), + GWei.ONE); + + // Empty body block + final BlockBody emptyBodyBlock = BlockBody.empty(); + // Block with no tx, no ommers, 1 validator exit + final BlockBody bodyBlockWithValidatorExit = + new BlockBody( + emptyList(), emptyList(), Optional.empty(), Optional.of(List.of(withdrawalRequest))); + + assertThat( + new GetBodiesFromPeerTask.BodyIdentifier(emptyBodyBlock) + .equals(new GetBodiesFromPeerTask.BodyIdentifier(bodyBlockWithValidatorExit))) + .isFalse(); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/GetHeadersFromPeerByHashTaskTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/GetHeadersFromPeerByHashTaskTest.java index aecb7191afe..61b1b48cc30 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/GetHeadersFromPeerByHashTaskTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/GetHeadersFromPeerByHashTaskTest.java @@ -198,6 +198,6 @@ public void checkThatSequentialHeadersNotFormingAChainFails() { assertThat(optionalBlockHeaders).isEmpty(); assertThat(peer.isDisconnected()).isTrue(); assertThat(((MockPeerConnection) peer.getConnection()).getDisconnectReason().get()) - .isEqualTo(DisconnectMessage.DisconnectReason.BREACH_OF_PROTOCOL); + .isEqualTo(DisconnectMessage.DisconnectReason.BREACH_OF_PROTOCOL_NON_SEQUENTIAL_HEADERS); } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/GetHeadersFromPeerByNumberTaskTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/GetHeadersFromPeerByNumberTaskTest.java index 5f2479ef458..5e0c5dea25b 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/GetHeadersFromPeerByNumberTaskTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/GetHeadersFromPeerByNumberTaskTest.java @@ -169,6 +169,6 @@ public void checkThatSequentialHeadersNotFormingAChainFails() { assertThat(optionalBlockHeaders).isEmpty(); assertThat(peer.isDisconnected()).isTrue(); assertThat(((MockPeerConnection) peer.getConnection()).getDisconnectReason().get()) - .isEqualTo(DisconnectMessage.DisconnectReason.BREACH_OF_PROTOCOL); + .isEqualTo(DisconnectMessage.DisconnectReason.BREACH_OF_PROTOCOL_NON_SEQUENTIAL_HEADERS); } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/RetryingGetBlockFromPeersTaskTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/RetryingGetBlockFromPeersTaskTest.java index 9996842bd2d..d1594d46f07 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/RetryingGetBlockFromPeersTaskTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/RetryingGetBlockFromPeersTaskTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/SnapProtocolManagerTestUtil.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/SnapProtocolManagerTestUtil.java deleted file mode 100644 index 68bd4e4e5a1..00000000000 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/manager/task/SnapProtocolManagerTestUtil.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright contributors to Hyperledger Besu - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.eth.manager.task; - -import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; -import org.hyperledger.besu.ethereum.eth.manager.EthMessages; -import org.hyperledger.besu.ethereum.eth.manager.EthPeers; -import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; -import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer; -import org.hyperledger.besu.ethereum.eth.manager.snap.SnapProtocolManager; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; -import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; - -import java.util.Collections; - -public class SnapProtocolManagerTestUtil { - - public static SnapProtocolManager create(final EthPeers ethPeers) { - return create( - BlockchainSetupUtil.forTesting(DataStorageFormat.FOREST).getWorldArchive(), ethPeers); - } - - public static SnapProtocolManager create( - final WorldStateArchive worldStateArchive, final EthPeers ethPeers) { - - EthMessages messages = new EthMessages(); - - return new SnapProtocolManager(Collections.emptyList(), ethPeers, messages, worldStateArchive); - } - - public static SnapProtocolManager create( - final WorldStateArchive worldStateArchive, - final EthPeers ethPeers, - final EthMessages snapMessages) { - return new SnapProtocolManager( - Collections.emptyList(), ethPeers, snapMessages, worldStateArchive); - } - - public static RespondingEthPeer createPeer( - final EthProtocolManager ethProtocolManager, - final SnapProtocolManager snapProtocolManager, - final long estimatedHeight) { - return RespondingEthPeer.builder() - .ethProtocolManager(ethProtocolManager) - .snapProtocolManager(snapProtocolManager) - .estimatedHeight(estimatedHeight) - .build(); - } -} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/BlockBodiesMessageTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/BlockBodiesMessageTest.java index 665384f1f66..b11afeb4c56 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/BlockBodiesMessageTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/BlockBodiesMessageTest.java @@ -17,9 +17,11 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import org.hyperledger.besu.config.GenesisConfigFile; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.difficulty.fixed.FixedDifficultyProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; @@ -32,6 +34,7 @@ import org.hyperledger.besu.ethereum.rlp.RLPException; import org.hyperledger.besu.ethereum.rlp.RLPInput; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import java.io.IOException; import java.nio.ByteBuffer; @@ -54,7 +57,13 @@ public final class BlockBodiesMessageTest { public void setup() { protocolSchedule = FixedDifficultyProtocolSchedule.create( - GenesisConfigFile.development().getConfigOptions(), false, EvmConfiguration.DEFAULT); + GenesisConfigFile.fromResource("/dev.json").getConfigOptions(), + false, + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); } @Test diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/BlockHeadersMessageTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/BlockHeadersMessageTest.java index 6dc6b08fd21..72f089a53eb 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/BlockHeadersMessageTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/BlockHeadersMessageTest.java @@ -15,7 +15,9 @@ package org.hyperledger.besu.ethereum.eth.messages; import org.hyperledger.besu.config.GenesisConfigFile; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.difficulty.fixed.FixedDifficultyProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; @@ -24,6 +26,7 @@ import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.ethereum.rlp.RLPInput; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import java.io.IOException; import java.nio.ByteBuffer; @@ -61,9 +64,13 @@ public void blockHeadersRoundTrip() throws IOException { final List readHeaders = message.getHeaders( FixedDifficultyProtocolSchedule.create( - GenesisConfigFile.development().getConfigOptions(), + GenesisConfigFile.fromResource("/dev.json").getConfigOptions(), false, - EvmConfiguration.DEFAULT)); + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem())); for (int i = 0; i < 50; ++i) { Assertions.assertThat(readHeaders.get(i)).isEqualTo(headers.get(i)); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/NewBlockMessageTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/NewBlockMessageTest.java index 2d7427bc2a5..4d30e9e93ae 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/NewBlockMessageTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/NewBlockMessageTest.java @@ -24,12 +24,14 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.RawMessage; import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput; +import org.hyperledger.besu.util.number.ByteUnits; import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.Test; public class NewBlockMessageTest { private static final ProtocolSchedule protocolSchedule = ProtocolScheduleFixture.MAINNET; + private static final int maxMessageSize = 10 * ByteUnits.MEGABYTE; @Test public void roundTripNewBlockMessage() { @@ -37,7 +39,8 @@ public void roundTripNewBlockMessage() { final BlockDataGenerator blockGenerator = new BlockDataGenerator(); final Block blockForInsertion = blockGenerator.block(); - final NewBlockMessage msg = NewBlockMessage.create(blockForInsertion, totalDifficulty); + final NewBlockMessage msg = + NewBlockMessage.create(blockForInsertion, totalDifficulty, maxMessageSize); assertThat(msg.getCode()).isEqualTo(EthPV62.NEW_BLOCK); assertThat(msg.totalDifficulty(protocolSchedule)).isEqualTo(totalDifficulty); final Block extractedBlock = msg.block(protocolSchedule); @@ -73,4 +76,14 @@ public void readFromMessageWithWrongCodeThrows() { assertThatExceptionOfType(IllegalArgumentException.class) .isThrownBy(() -> NewBlockMessage.readFrom(rawMsg)); } + + @Test + public void createBlockMessageLargerThanLimitThrows() { + final Difficulty totalDifficulty = Difficulty.of(98765); + final BlockDataGenerator blockGenerator = new BlockDataGenerator(); + final Block newBlock = blockGenerator.block(); + + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> NewBlockMessage.create(newBlock, totalDifficulty, 1)); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/AccountRangeMessageTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/AccountRangeMessageTest.java index 11d16a61e0b..bd715b64597 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/AccountRangeMessageTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/AccountRangeMessageTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/BytecodeMessageTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/BytecodeMessageTest.java index 05c63f71f2d..9f9bf5c48a8 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/BytecodeMessageTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/BytecodeMessageTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetAccountRangeMessageTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetAccountRangeMessageTest.java index c8ef62a6d51..3399bcbe16e 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetAccountRangeMessageTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetAccountRangeMessageTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,10 +15,10 @@ package org.hyperledger.besu.ethereum.eth.messages.snap; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.eth.sync.snapsync.RangeManager; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.AbstractSnapMessageData; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.RawMessage; +import org.hyperledger.besu.ethereum.trie.RangeManager; import org.apache.tuweni.bytes.Bytes32; import org.assertj.core.api.Assertions; diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetBytecodeMessageTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetBytecodeMessageTest.java index 8091015d68d..bd4dc9cb5bd 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetBytecodeMessageTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetBytecodeMessageTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetStorageRangeMessageTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetStorageRangeMessageTest.java index 4ff1b09b9a0..c831fc32676 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetStorageRangeMessageTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetStorageRangeMessageTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,10 +15,10 @@ package org.hyperledger.besu.ethereum.eth.messages.snap; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.eth.sync.snapsync.RangeManager; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.AbstractSnapMessageData; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.RawMessage; +import org.hyperledger.besu.ethereum.trie.RangeManager; import java.util.List; diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetTrieNodeMessageTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetTrieNodeMessageTest.java index 625c96931bb..074795edef9 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetTrieNodeMessageTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/GetTrieNodeMessageTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/StorageRangeMessageTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/StorageRangeMessageTest.java index c9179329b02..81f54a4a8bf 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/StorageRangeMessageTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/StorageRangeMessageTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -20,6 +20,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.NavigableMap; import java.util.TreeMap; import kotlin.collections.ArrayDeque; @@ -33,7 +34,7 @@ public final class StorageRangeMessageTest { @Test public void roundTripTest() { - final ArrayDeque> keys = new ArrayDeque<>(); + final ArrayDeque> keys = new ArrayDeque<>(); final TreeMap storage = new TreeMap<>(); storage.put(Hash.wrap(Bytes32.leftPad(Bytes.of(1))), Bytes32.random()); keys.add(storage); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/TrieNodeMessageTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/TrieNodeMessageTest.java index 6223102bf9b..6f00a9dd401 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/TrieNodeMessageTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/messages/snap/TrieNodeMessageTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/peervalidation/AbstractPeerBlockValidatorTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/peervalidation/AbstractPeerBlockValidatorTest.java index 1781428531a..b7258de5b7e 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/peervalidation/AbstractPeerBlockValidatorTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/peervalidation/AbstractPeerBlockValidatorTest.java @@ -1,20 +1,17 @@ /* + * Copyright 2019 ConsenSys AG. * - * * Copyright 2019 ConsenSys AG. - * * - * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * * the License. You may obtain a copy of the License at - * * - * * http://www.apache.org/licenses/LICENSE-2.0 - * * - * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * * specific language governing permissions and limitations under the License. - * * - * * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.eth.peervalidation; import static org.assertj.core.api.Assertions.assertThat; @@ -71,7 +68,7 @@ public void validatePeer_requestBlockFromPeerBeingTested() { final PeerValidator validator = createValidator(blockNumber, 0); - final int peerCount = 1000; + final int peerCount = 24; final List otherPeers = Stream.generate( () -> EthProtocolManagerTestUtil.createPeer(ethProtocolManager, blockNumber)) diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/peervalidation/DaoForkPeerValidatorTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/peervalidation/DaoForkPeerValidatorTest.java index 8586874c434..042ca8f9605 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/peervalidation/DaoForkPeerValidatorTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/peervalidation/DaoForkPeerValidatorTest.java @@ -98,4 +98,28 @@ public void validatePeer_responsivePeerOnWrongSideOfFork() { assertThat(result).isDone(); assertThat(result).isCompletedWithValue(false); } + + @Test + public void validatePeer_responsivePeerDoesNotHaveBlockWhenPastForkHeight() { + final EthProtocolManager ethProtocolManager = EthProtocolManagerTestUtil.create(); + final long daoBlockNumber = 500; + + final PeerValidator validator = + new DaoForkPeerValidator( + ProtocolScheduleFixture.MAINNET, new NoOpMetricsSystem(), daoBlockNumber, 0); + + final RespondingEthPeer peer = + EthProtocolManagerTestUtil.createPeer(ethProtocolManager, daoBlockNumber); + + final CompletableFuture result = + validator.validatePeer(ethProtocolManager.ethContext(), peer.getEthPeer()); + + assertThat(result).isNotDone(); + + // Respond to block header request with empty + peer.respond(RespondingEthPeer.emptyResponder()); + + assertThat(result).isDone(); + assertThat(result).isCompletedWithValue(true); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/peervalidation/RequiredBlocksPeerValidatorTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/peervalidation/RequiredBlocksPeerValidatorTest.java index 67c80345880..4fdb679880b 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/peervalidation/RequiredBlocksPeerValidatorTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/peervalidation/RequiredBlocksPeerValidatorTest.java @@ -1,18 +1,16 @@ /* + * Copyright ConsenSys AG. * - * * Copyright ConsenSys AG. - * * - * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * * the License. You may obtain a copy of the License at - * * - * * http://www.apache.org/licenses/LICENSE-2.0 - * * - * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * * specific language governing permissions and limitations under the License. - * * - * * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 */ package org.hyperledger.besu.ethereum.eth.peervalidation; @@ -104,4 +102,26 @@ public void validatePeer_responsivePeerWithBadRequiredBlock() { assertThat(result).isDone(); assertThat(result).isCompletedWithValue(false); } + + @Test + public void validatePeer_responsivePeerDoesNotHaveBlockWhenPastForkHeight() { + final EthProtocolManager ethProtocolManager = EthProtocolManagerTestUtil.create(); + + final PeerValidator validator = + new RequiredBlocksPeerValidator( + ProtocolScheduleFixture.MAINNET, new NoOpMetricsSystem(), 1, Hash.ZERO); + + final RespondingEthPeer peer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 1); + + final CompletableFuture result = + validator.validatePeer(ethProtocolManager.ethContext(), peer.getEthPeer()); + + assertThat(result).isNotDone(); + + // Respond to block header request with empty + peer.respond(RespondingEthPeer.emptyResponder()); + + assertThat(result).isDone(); + assertThat(result).isCompletedWithValue(false); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/AbstractBlockPropagationManagerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/AbstractBlockPropagationManagerTest.java index 25ff8ab4b46..c576b7f8e26 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/AbstractBlockPropagationManagerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/AbstractBlockPropagationManagerTest.java @@ -55,17 +55,18 @@ import org.hyperledger.besu.ethereum.eth.sync.BlockPropagationManager.ProcessingBlocksManager; import org.hyperledger.besu.ethereum.eth.sync.state.PendingBlocksManager; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; +import org.hyperledger.besu.ethereum.forkid.ForkIdManager; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.testutil.TestClock; +import org.hyperledger.besu.util.number.ByteUnits; import java.util.Collections; import java.util.List; -import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; @@ -95,6 +96,7 @@ public abstract class AbstractBlockPropagationManagerTest { protected SyncState syncState; protected final MetricsSystem metricsSystem = new NoOpMetricsSystem(); private final Hash finalizedHash = Hash.fromHexStringLenient("0x1337"); + private final int maxMessageSize = 10 * ByteUnits.MEGABYTE; protected void setup(final DataStorageFormat dataStorageFormat) { blockchainUtil = BlockchainSetupUtil.forTesting(dataStorageFormat); @@ -106,7 +108,7 @@ protected void setup(final DataStorageFormat dataStorageFormat) { blockchain, tempProtocolContext.getWorldStateArchive(), tempProtocolContext.getConsensusContext(ConsensusContext.class), - Optional.empty()); + new BadBlockManager()); ethProtocolManager = EthProtocolManagerTestUtil.create( protocolSchedule, @@ -222,11 +224,14 @@ public void importsAnnouncedNewBlocks_aheadOfChainInOrder() { final RespondingEthPeer peer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 0); final NewBlockMessage nextAnnouncement = NewBlockMessage.create( - nextBlock, getFullBlockchain().getTotalDifficultyByHash(nextBlock.getHash()).get()); + nextBlock, + getFullBlockchain().getTotalDifficultyByHash(nextBlock.getHash()).get(), + maxMessageSize); final NewBlockMessage nextNextAnnouncement = NewBlockMessage.create( nextNextBlock, - getFullBlockchain().getTotalDifficultyByHash(nextNextBlock.getHash()).get()); + getFullBlockchain().getTotalDifficultyByHash(nextNextBlock.getHash()).get(), + maxMessageSize); final Responder responder = RespondingEthPeer.blockchainResponder(getFullBlockchain()); // Broadcast first message @@ -256,11 +261,14 @@ public void importsAnnouncedNewBlocks_aheadOfChainOutOfOrder() { final RespondingEthPeer peer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 0); final NewBlockMessage nextAnnouncement = NewBlockMessage.create( - nextBlock, getFullBlockchain().getTotalDifficultyByHash(nextBlock.getHash()).get()); + nextBlock, + getFullBlockchain().getTotalDifficultyByHash(nextBlock.getHash()).get(), + maxMessageSize); final NewBlockMessage nextNextAnnouncement = NewBlockMessage.create( nextNextBlock, - getFullBlockchain().getTotalDifficultyByHash(nextNextBlock.getHash()).get()); + getFullBlockchain().getTotalDifficultyByHash(nextNextBlock.getHash()).get(), + maxMessageSize); final Responder responder = RespondingEthPeer.blockchainResponder(getFullBlockchain()); // Broadcast second message first @@ -299,7 +307,9 @@ public void importsMixedOutOfOrderMessages() { block1.getHash(), block1.getHeader().getNumber()))); final NewBlockMessage block2Msg = NewBlockMessage.create( - block2, getFullBlockchain().getTotalDifficultyByHash(block2.getHash()).get()); + block2, + getFullBlockchain().getTotalDifficultyByHash(block2.getHash()).get(), + maxMessageSize); final NewBlockHashesMessage block3Msg = NewBlockHashesMessage.create( Collections.singletonList( @@ -307,7 +317,9 @@ public void importsMixedOutOfOrderMessages() { block3.getHash(), block3.getHeader().getNumber()))); final NewBlockMessage block4Msg = NewBlockMessage.create( - block4, getFullBlockchain().getTotalDifficultyByHash(block4.getHash()).get()); + block4, + getFullBlockchain().getTotalDifficultyByHash(block4.getHash()).get(), + maxMessageSize); final Responder responder = RespondingEthPeer.blockchainResponder(getFullBlockchain()); // Broadcast older blocks @@ -362,7 +374,9 @@ public void handlesDuplicateAnnouncements() { nextBlock.getHash(), nextBlock.getHeader().getNumber()))); final NewBlockMessage newBlock = NewBlockMessage.create( - nextBlock, getFullBlockchain().getTotalDifficultyByHash(nextBlock.getHash()).get()); + nextBlock, + getFullBlockchain().getTotalDifficultyByHash(nextBlock.getHash()).get(), + maxMessageSize); final Responder responder = RespondingEthPeer.blockchainResponder(getFullBlockchain()); // Broadcast first message @@ -413,7 +427,9 @@ public void handlesPendingDuplicateAnnouncements() { nextBlock.getHash(), nextBlock.getHeader().getNumber()))); final NewBlockMessage newBlock = NewBlockMessage.create( - nextBlock, getFullBlockchain().getTotalDifficultyByHash(nextBlock.getHash()).get()); + nextBlock, + getFullBlockchain().getTotalDifficultyByHash(nextBlock.getHash()).get(), + maxMessageSize); // Broadcast messages EthProtocolManagerTestUtil.broadcastMessage(ethProtocolManager, peer, newBlock); @@ -467,7 +483,9 @@ public void ignoresFutureNewBlockAnnouncement() { final RespondingEthPeer peer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 0); final NewBlockMessage futureAnnouncement = NewBlockMessage.create( - futureBlock, getFullBlockchain().getTotalDifficultyByHash(futureBlock.getHash()).get()); + futureBlock, + getFullBlockchain().getTotalDifficultyByHash(futureBlock.getHash()).get(), + maxMessageSize); // Broadcast EthProtocolManagerTestUtil.broadcastMessage(ethProtocolManager, peer, futureAnnouncement); @@ -522,7 +540,8 @@ public void ignoresOldNewBlockAnnouncement() { // Setup peer and messages final RespondingEthPeer peer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 0); - final NewBlockMessage oldAnnouncement = NewBlockMessage.create(oldBlock, Difficulty.ZERO); + final NewBlockMessage oldAnnouncement = + NewBlockMessage.create(oldBlock, Difficulty.ZERO, maxMessageSize); // Broadcast EthProtocolManagerTestUtil.broadcastMessage(ethProtocolManager, peer, oldAnnouncement); @@ -559,7 +578,7 @@ public void purgesOldBlocks() { blockPropagationManager.start(); final RespondingEthPeer peer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 0); final NewBlockMessage blockAnnouncementMsg = - NewBlockMessage.create(blockToPurge, Difficulty.ZERO); + NewBlockMessage.create(blockToPurge, Difficulty.ZERO, maxMessageSize); // Broadcast EthProtocolManagerTestUtil.broadcastMessage(ethProtocolManager, peer, blockAnnouncementMsg); @@ -597,7 +616,8 @@ public void updatesChainHeadWhenNewBlockMessageReceived() { getFullBlockchain().getTotalDifficultyByHash(nextBlock.getHeader().getParentHash()).get(); final Difficulty totalDifficulty = getFullBlockchain().getTotalDifficultyByHash(nextBlock.getHash()).get(); - final NewBlockMessage nextAnnouncement = NewBlockMessage.create(nextBlock, totalDifficulty); + final NewBlockMessage nextAnnouncement = + NewBlockMessage.create(nextBlock, totalDifficulty, maxMessageSize); // Broadcast message EthProtocolManagerTestUtil.broadcastMessage(ethProtocolManager, peer, nextAnnouncement); @@ -630,8 +650,10 @@ public void shouldNotImportBlocksThatAreAlreadyBeingImported() { Bytes.random(64), 25, 25, - 25, - false), + false, + SyncMode.SNAP, + new ForkIdManager( + blockchain, Collections.emptyList(), Collections.emptyList(), false)), new EthMessages(), ethScheduler); final BlockPropagationManager blockPropagationManager = @@ -733,7 +755,8 @@ public void verifyBroadcastBlockInvocation() { final Difficulty totalDifficulty = getFullBlockchain().getTotalDifficultyByHash(block.getHash()).get(); - final NewBlockMessage newBlockMessage = NewBlockMessage.create(block, totalDifficulty); + final NewBlockMessage newBlockMessage = + NewBlockMessage.create(block, totalDifficulty, maxMessageSize); // Broadcast message EthProtocolManagerTestUtil.broadcastMessage(ethProtocolManager, peer, newBlockMessage); @@ -769,8 +792,10 @@ public Object answer(final InvocationOnMock invocation) throws Throwable { Bytes.random(64), 25, 25, - 25, - false), + false, + SyncMode.SNAP, + new ForkIdManager( + blockchain, Collections.emptyList(), Collections.emptyList(), false)), new EthMessages(), ethScheduler); final BlockPropagationManager blockPropagationManager = @@ -786,8 +811,7 @@ public Object answer(final InvocationOnMock invocation) throws Throwable { blockchainUtil.importFirstBlocks(2); final Block firstBlock = blockchainUtil.getBlock(1); - final BadBlockManager badBlocksManager = - protocolSchedule.getByBlockHeader(blockHeader(1)).getBadBlocksManager(); + final BadBlockManager badBlocksManager = protocolContext.getBadBlockManager(); final Block badBlock = new BlockDataGenerator() .block( @@ -930,7 +954,9 @@ public void shouldNotListenToNewBlockAnnouncementsWhenTTDReachedAndFinal() { final RespondingEthPeer peer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager, 0); final NewBlockMessage nextAnnouncement = NewBlockMessage.create( - nextBlock, getFullBlockchain().getTotalDifficultyByHash(nextBlock.getHash()).get()); + nextBlock, + getFullBlockchain().getTotalDifficultyByHash(nextBlock.getHash()).get(), + maxMessageSize); final Responder responder = RespondingEthPeer.blockchainResponder(getFullBlockchain()); syncState.setReachedTerminalDifficulty(true); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/BlockBroadcasterTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/BlockBroadcasterTest.java index da8f269b89d..ef1220fd55b 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/BlockBroadcasterTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/BlockBroadcasterTest.java @@ -30,6 +30,7 @@ import org.hyperledger.besu.ethereum.eth.manager.EthPeers; import org.hyperledger.besu.ethereum.eth.messages.NewBlockMessage; import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection; +import org.hyperledger.besu.util.number.ByteUnits; import java.util.Collections; import java.util.stream.Stream; @@ -38,6 +39,8 @@ public class BlockBroadcasterTest { + final int maxMessageSize = 10 * ByteUnits.MEGABYTE; + @Test public void blockPropagationUnitTest() throws PeerConnection.PeerNotConnected { final EthPeer ethPeer = mock(EthPeer.class); @@ -47,10 +50,10 @@ public void blockPropagationUnitTest() throws PeerConnection.PeerNotConnected { final EthContext ethContext = mock(EthContext.class); when(ethContext.getEthPeers()).thenReturn(ethPeers); - final BlockBroadcaster blockBroadcaster = new BlockBroadcaster(ethContext); + final BlockBroadcaster blockBroadcaster = new BlockBroadcaster(ethContext, maxMessageSize); final Block block = generateBlock(); final NewBlockMessage newBlockMessage = - NewBlockMessage.create(block, block.getHeader().getDifficulty()); + NewBlockMessage.create(block, block.getHeader().getDifficulty(), maxMessageSize); blockBroadcaster.propagate(block, Difficulty.ZERO); @@ -70,10 +73,10 @@ public void blockPropagationUnitTestSeenUnseen() throws PeerConnection.PeerNotCo final EthContext ethContext = mock(EthContext.class); when(ethContext.getEthPeers()).thenReturn(ethPeers); - final BlockBroadcaster blockBroadcaster = new BlockBroadcaster(ethContext); + final BlockBroadcaster blockBroadcaster = new BlockBroadcaster(ethContext, maxMessageSize); final Block block = generateBlock(); final NewBlockMessage newBlockMessage = - NewBlockMessage.create(block, block.getHeader().getDifficulty()); + NewBlockMessage.create(block, block.getHeader().getDifficulty(), maxMessageSize); blockBroadcaster.propagate(block, Difficulty.ZERO); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/BonsaiBlockPropagationManagerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/BonsaiBlockPropagationManagerTest.java index 7bd4d2bbbc4..eda0473147a 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/BonsaiBlockPropagationManagerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/BonsaiBlockPropagationManagerTest.java @@ -16,7 +16,7 @@ import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/ChainHeadTrackerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/ChainHeadTrackerTest.java index 4cde3fd7edc..5f7baaff418 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/ChainHeadTrackerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/ChainHeadTrackerTest.java @@ -14,13 +14,16 @@ */ package org.hyperledger.besu.ethereum.eth.sync; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.difficulty.fixed.FixedDifficultyProtocolSchedule; import org.hyperledger.besu.ethereum.eth.manager.ChainState; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; @@ -28,13 +31,14 @@ import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer; import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer.Responder; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import java.util.stream.Stream; import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -51,7 +55,13 @@ public class ChainHeadTrackerTest { private final ProtocolSchedule protocolSchedule = FixedDifficultyProtocolSchedule.create( - GenesisConfigFile.development().getConfigOptions(), false, EvmConfiguration.DEFAULT); + GenesisConfigFile.fromResource("/dev.json").getConfigOptions(), + false, + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); private final TrailingPeerLimiter trailingPeerLimiter = mock(TrailingPeerLimiter.class); @@ -92,7 +102,7 @@ public void shouldRequestHeaderChainHeadWhenNewPeerConnects( blockchainSetupUtil.getBlockchain(), blockchainSetupUtil.getWorldArchive(), blockchainSetupUtil.getTransactionPool()); - chainHeadTracker.onPeerConnected(respondingPeer.getEthPeer()); + chainHeadTracker.getBestHeaderFromPeer(respondingPeer.getEthPeer()); Assertions.assertThat(chainHeadState().getEstimatedHeight()).isZero(); @@ -112,7 +122,7 @@ public void shouldIgnoreHeadersIfChainHeadHasAlreadyBeenUpdatedWhileWaiting( blockchainSetupUtil.getBlockchain(), blockchainSetupUtil.getWorldArchive(), blockchainSetupUtil.getTransactionPool()); - chainHeadTracker.onPeerConnected(respondingPeer.getEthPeer()); + chainHeadTracker.getBestHeaderFromPeer(respondingPeer.getEthPeer()); // Change the hash of the current known head respondingPeer.getEthPeer().chainState().statusReceived(Hash.EMPTY_TRIE_HASH, Difficulty.ONE); @@ -131,7 +141,7 @@ public void shouldCheckTrialingPeerLimits(final DataStorageFormat storageFormat) blockchainSetupUtil.getBlockchain(), blockchainSetupUtil.getWorldArchive(), blockchainSetupUtil.getTransactionPool()); - chainHeadTracker.onPeerConnected(respondingPeer.getEthPeer()); + chainHeadTracker.getBestHeaderFromPeer(respondingPeer.getEthPeer()); Assertions.assertThat(chainHeadState().getEstimatedHeight()).isZero(); @@ -144,4 +154,11 @@ public void shouldCheckTrialingPeerLimits(final DataStorageFormat storageFormat) private ChainState chainHeadState() { return respondingPeer.getEthPeer().chainState(); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/DownloadHeadersStepTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/DownloadHeadersStepTest.java index b9d8eec0216..3dba7d1cf10 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/DownloadHeadersStepTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/DownloadHeadersStepTest.java @@ -30,8 +30,8 @@ import org.hyperledger.besu.ethereum.eth.sync.range.SyncTargetRange; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import java.util.ArrayList; import java.util.Collections; diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/ForestBlockPropagationManagerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/ForestBlockPropagationManagerTest.java index 000958b1fac..9b2bd896203 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/ForestBlockPropagationManagerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/ForestBlockPropagationManagerTest.java @@ -16,7 +16,7 @@ import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.core.BlockchainSetupUtil; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/PipelineChainDownloaderTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/PipelineChainDownloaderTest.java index babacfc5e0f..f4402349058 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/PipelineChainDownloaderTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/PipelineChainDownloaderTest.java @@ -34,6 +34,7 @@ import org.hyperledger.besu.ethereum.eth.sync.state.SyncTarget; import org.hyperledger.besu.ethereum.eth.sync.tasks.exceptions.InvalidBlockException; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.services.pipeline.Pipeline; @@ -52,7 +53,7 @@ @ExtendWith(MockitoExtension.class) public class PipelineChainDownloaderTest { - @Mock private SyncTargetManager syncTargetManager; + @Mock private AbstractSyncTargetManager syncTargetManager; @Mock private DownloadPipelineFactory downloadPipelineFactory; @Mock private EthScheduler scheduler; @Mock private Pipeline downloadPipeline; @@ -69,13 +70,15 @@ public class PipelineChainDownloaderTest { public void setUp() { syncTarget = new SyncTarget(peer1, commonAncestor); syncTarget2 = new SyncTarget(peer2, commonAncestor); + final NoOpMetricsSystem noOpMetricsSystem = new NoOpMetricsSystem(); chainDownloader = new PipelineChainDownloader( syncState, syncTargetManager, downloadPipelineFactory, scheduler, - new NoOpMetricsSystem()); + noOpMetricsSystem, + SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS); } @Test @@ -318,9 +321,10 @@ public void testInvalidBlockHandling( if (isCancelled) { chainDownloader.cancel(); } - selectTargetFuture.completeExceptionally(new InvalidBlockException("", 1, null)); + selectTargetFuture.completeExceptionally(InvalidBlockException.create("Failed")); - verify(syncState, times(1)).disconnectSyncTarget(DisconnectReason.BREACH_OF_PROTOCOL); + verify(syncState, times(1)) + .disconnectSyncTarget(DisconnectReason.BREACH_OF_PROTOCOL_INVALID_BLOCK); } private CompletableFuture expectPipelineStarted(final SyncTarget syncTarget) { diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/RangeHeadersFetcherTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/RangeHeadersFetcherTest.java index 0b18a0f64c6..39cad8407e7 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/RangeHeadersFetcherTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/RangeHeadersFetcherTest.java @@ -33,9 +33,9 @@ import org.hyperledger.besu.ethereum.eth.sync.range.RangeHeadersFetcher; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import java.util.List; import java.util.concurrent.CompletableFuture; diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/TrailingPeerLimiterTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/TrailingPeerLimiterTest.java index 9bcac6f0a9a..6f905ec4fe6 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/TrailingPeerLimiterTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/TrailingPeerLimiterTest.java @@ -145,7 +145,7 @@ private void assertDisconnections(final EthPeer... disconnectedPeers) { final List disconnected = asList(disconnectedPeers); for (final EthPeer peer : peers) { if (disconnected.contains(peer)) { - verify(peer).disconnect(DisconnectReason.USELESS_PEER); + verify(peer).disconnect(DisconnectReason.USELESS_PEER_TRAILING_PEER); } else { verify(peer, never()).disconnect(any(DisconnectReason.class)); } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncAlgSpec.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncAlgSpec.java index 8a1a0a226e8..bd5a9e7ec9c 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncAlgSpec.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncAlgSpec.java @@ -1,20 +1,17 @@ /* + * Copyright contributors to Hyperledger Besu. * - * * Copyright Hyperledger Besu Contributors. - * * - * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * * the License. You may obtain a copy of the License at - * * - * * http://www.apache.org/licenses/LICENSE-2.0 - * * - * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * * specific language governing permissions and limitations under the License. - * * - * * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.eth.sync.backwardsync; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -165,7 +162,7 @@ public void shouldAwokeWhenTTDReachedAndReady() throws Exception { ttdCaptor.getValue().onTTDReached(true); - voidCompletableFuture.get(100, TimeUnit.MILLISECONDS); + voidCompletableFuture.get(200, TimeUnit.MILLISECONDS); assertThat(voidCompletableFuture).isCompleted(); verify(context.getSyncState()).unsubscribeTTDReached(88L); @@ -192,7 +189,7 @@ public void shouldAwokeWhenConditionReachedAndReady() throws Exception { completionCaptor.getValue().onInitialSyncCompleted(); - voidCompletableFuture.get(100, TimeUnit.MILLISECONDS); + voidCompletableFuture.get(800, TimeUnit.MILLISECONDS); assertThat(voidCompletableFuture).isCompleted(); verify(context.getSyncState()).unsubscribeTTDReached(88L); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncContextTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncContextTest.java index 3187f55ef9f..cfc9f9dc123 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncContextTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncContextTest.java @@ -1,20 +1,17 @@ /* + * Copyright contributors to Hyperledger Besu. * - * * Copyright Hyperledger Besu Contributors. - * * - * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * * the License. You may obtain a copy of the License at - * * - * * http://www.apache.org/licenses/LICENSE-2.0 - * * - * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * * specific language governing permissions and limitations under the License. - * * - * * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.eth.sync.backwardsync; import static org.assertj.core.api.Assertions.assertThat; @@ -34,10 +31,12 @@ import org.hyperledger.besu.ethereum.BlockProcessingResult; import org.hyperledger.besu.ethereum.BlockValidator; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockDataGenerator; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; @@ -48,7 +47,8 @@ import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; -import org.hyperledger.besu.ethereum.referencetests.DefaultReferenceTestWorldState; +import org.hyperledger.besu.ethereum.referencetests.ForestReferenceTestWorldState; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; @@ -81,7 +81,7 @@ public class BackwardSyncContextTest { public static final int REMOTE_HEIGHT = 50; public static final int UNCLE_HEIGHT = 25 - 3; - public static final int NUM_OF_RETRIES = 100; + public static final int NUM_OF_RETRIES = 1; public static final int TEST_MAX_BAD_CHAIN_EVENT_ENTRIES = 25; private BackwardSyncContext context; @@ -93,7 +93,12 @@ public class BackwardSyncContextTest { @Spy private ProtocolSchedule protocolSchedule = - MainnetProtocolSchedule.fromConfig(new StubGenesisConfigOptions()); + MainnetProtocolSchedule.fromConfig( + new StubGenesisConfigOptions(), + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); @Spy private ProtocolSpec protocolSpec = @@ -154,8 +159,9 @@ public void setup() { new BlockProcessingOutputs( // use forest-based worldstate since it does not require // blockheader stateroot to match actual worldstate root - DefaultReferenceTestWorldState.create(Collections.emptyMap()), - blockDataGenerator.receipts(block)))); + ForestReferenceTestWorldState.create(Collections.emptyMap()), + blockDataGenerator.receipts(block), + Optional.empty()))); }); backwardChain = inMemoryBackwardChain(); @@ -183,13 +189,16 @@ public void setup() { } private Block createUncle(final int i, final Hash parentHash) { + return createBlock(i, parentHash); + } + + private Block createBlock(final int i, final Hash parentHash) { final BlockDataGenerator.BlockOptions options = new BlockDataGenerator.BlockOptions() .setBlockNumber(i) .setParentHash(parentHash) .transactionTypes(TransactionType.ACCESS_LIST); - final Block block = blockDataGenerator.block(options); - return block; + return blockDataGenerator.block(options); } public static BackwardChain inMemoryBackwardChain() { @@ -225,6 +234,20 @@ public void shouldSyncUntilHash() throws Exception { assertThat(localBlockchain.getChainHeadBlock()).isEqualTo(remoteBlockchain.getChainHeadBlock()); } + @Test + public void shouldNotSyncUntilHashWhenNotInSync() { + doReturn(false).when(context).isReady(); + final Hash hash = getBlockByNumber(REMOTE_HEIGHT).getHash(); + final CompletableFuture future = context.syncBackwardsUntil(hash); + + respondUntilFutureIsDone(future); + + assertThatThrownBy(future::get) + .isInstanceOf(ExecutionException.class) + .hasMessageContaining("Backward sync is not ready"); + assertThat(backwardChain.getFirstHashToAppend()).isEmpty(); + } + @Test public void shouldSyncUntilRemoteBranch() throws Exception { @@ -435,4 +458,51 @@ public void shouldFailAfterMaxNumberOfRetries() { } } } + + @Test + public void whenBlockNotFoundInPeers_shouldRemoveBlockFromQueueAndProgressInNextSession() { + // This scenario can happen due to a reorg + // Expectation we progress beyond the reorg block upon receiving the next FCU + + // choose an intermediate remote block to create a reorg block from + int reorgBlockHeight = REMOTE_HEIGHT - 1; // 49 + final Hash reorgBlockParentHash = getBlockByNumber(reorgBlockHeight - 1).getHash(); + final Block reorgBlock = createBlock(reorgBlockHeight, reorgBlockParentHash); + + // represents first FCU with a block that will become reorged away + final CompletableFuture fcuBeforeReorg = context.syncBackwardsUntil(reorgBlock.getHash()); + respondUntilFutureIsDone(fcuBeforeReorg); + assertThat(localBlockchain.getChainHeadBlockNumber()).isLessThan(reorgBlockHeight); + + // represents subsequent FCU with successfully reorged version of the same block + final CompletableFuture fcuAfterReorg = + context.syncBackwardsUntil(getBlockByNumber(reorgBlockHeight).getHash()); + respondUntilFutureIsDone(fcuAfterReorg); + assertThat(localBlockchain.getChainHeadBlock()) + .isEqualTo(remoteBlockchain.getBlockByNumber(reorgBlockHeight).orElseThrow()); + } + + @Test + public void + whenBlockNotFoundInPeers_shouldRemoveBlockFromQueueAndProgressWithQueueInSameSession() { + // This scenario can happen due to a reorg + // Expectation we progress beyond the reorg block due to FCU we received during the same session + + // choose an intermediate remote block to create a reorg block from + int reorgBlockHeight = REMOTE_HEIGHT - 1; // 49 + final Hash reorgBlockParentHash = getBlockByNumber(reorgBlockHeight - 1).getHash(); + final Block reorgBlock = createBlock(reorgBlockHeight, reorgBlockParentHash); + + // represents first FCU with a block that will become reorged away + final CompletableFuture fcuBeforeReorg = context.syncBackwardsUntil(reorgBlock.getHash()); + // represents subsequent FCU with successfully reorged version of the same block + // received during the first FCU's BWS session + final CompletableFuture fcuAfterReorg = + context.syncBackwardsUntil(getBlockByNumber(reorgBlockHeight).getHash()); + + respondUntilFutureIsDone(fcuBeforeReorg); + respondUntilFutureIsDone(fcuAfterReorg); + assertThat(localBlockchain.getChainHeadBlock()) + .isEqualTo(remoteBlockchain.getBlockByNumber(reorgBlockHeight).orElseThrow()); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncStepTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncStepTest.java index 19c4a1b4e2e..6dc69ea274e 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncStepTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/BackwardSyncStepTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -24,10 +24,12 @@ import org.hyperledger.besu.config.StubGenesisConfigOptions; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockDataGenerator; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; @@ -37,6 +39,7 @@ import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import org.hyperledger.besu.testutil.DeterministicEthScheduler; @@ -67,7 +70,12 @@ public class BackwardSyncStepTest { private BackwardSyncContext context; private final ProtocolSchedule protocolSchedule = - MainnetProtocolSchedule.fromConfig(new StubGenesisConfigOptions()); + MainnetProtocolSchedule.fromConfig( + new StubGenesisConfigOptions(), + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); private final DeterministicEthScheduler ethScheduler = new DeterministicEthScheduler(); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ChainForTestCreator.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ChainForTestCreator.java index a1ee46a4eea..82d5de4f38c 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ChainForTestCreator.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ChainForTestCreator.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -88,7 +88,7 @@ public static BlockHeader prepareWrongParentHash(final BlockHeader blockHeader) blockHeader.getBlobGasUsed().orElse(null), blockHeader.getExcessBlobGas().orElse(null), blockHeader.getParentBeaconBlockRoot().orElse(null), - blockHeader.getDepositsRoot().orElse(null), + blockHeader.getRequestsRoot().orElse(null), new MainnetBlockHeaderFunctions()); } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ForwardSyncStepTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ForwardSyncStepTest.java index e38a9647b79..a5d2cf61045 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ForwardSyncStepTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/ForwardSyncStepTest.java @@ -1,20 +1,17 @@ /* + * Copyright contributors to Hyperledger Besu. * - * * Copyright Hyperledger Besu Contributors. - * * - * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * * the License. You may obtain a copy of the License at - * * - * * http://www.apache.org/licenses/LICENSE-2.0 - * * - * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * * specific language governing permissions and limitations under the License. - * * - * * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.eth.sync.backwardsync; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -26,10 +23,12 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.BlockProcessingOutputs; import org.hyperledger.besu.ethereum.BlockProcessingResult; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockDataGenerator; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; @@ -38,7 +37,8 @@ import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.referencetests.DefaultReferenceTestWorldState; +import org.hyperledger.besu.ethereum.referencetests.ForestReferenceTestWorldState; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import java.nio.charset.StandardCharsets; @@ -73,7 +73,12 @@ public class ForwardSyncStepTest { private RespondingEthPeer peer; private final ProtocolSchedule protocolSchedule = - MainnetProtocolSchedule.fromConfig(new StubGenesisConfigOptions()); + MainnetProtocolSchedule.fromConfig( + new StubGenesisConfigOptions(), + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); private MutableBlockchain localBlockchain; GenericKeyValueStorageFacade headersStorage; GenericKeyValueStorageFacade blocksStorage; @@ -138,7 +143,7 @@ public void setup() { return new BlockProcessingResult( Optional.of( new BlockProcessingOutputs( - DefaultReferenceTestWorldState.create(Collections.emptyMap()), + ForestReferenceTestWorldState.create(Collections.emptyMap()), blockDataGenerator.receipts(block)))); }); } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/InMemoryBackwardChainTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/InMemoryBackwardChainTest.java index c8bffb95793..318773c372e 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/InMemoryBackwardChainTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/backwardsync/InMemoryBackwardChainTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckPointBlockImportStepTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckPointBlockImportStepTest.java index 13b4718794a..7e79fb60bbc 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckPointBlockImportStepTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckPointBlockImportStepTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -52,7 +52,8 @@ public void setup() { new KeyValueStoragePrefixedKeyBlockchainStorage( new InMemoryKeyValueStorage(), new VariablesKeyValueStorage(new InMemoryKeyValueStorage()), - new MainnetBlockHeaderFunctions()); + new MainnetBlockHeaderFunctions(), + false); blockchain = DefaultBlockchain.createMutable( generateBlock(0), blockchainStorage, mock(MetricsSystem.class), 0); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckPointSourceTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckPointSourceTest.java index 1fde5dfee16..eeb5ec51a71 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckPointSourceTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckPointSourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckPointSyncChainDownloaderTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckPointSyncChainDownloaderTest.java index 62ef446503b..43f03100a75 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckPointSyncChainDownloaderTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/checkpointsync/CheckPointSyncChainDownloaderTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -36,15 +36,20 @@ import org.hyperledger.besu.ethereum.eth.sync.fastsync.checkpoint.ImmutableCheckpoint; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.stream.Stream; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -53,8 +58,6 @@ public class CheckPointSyncChainDownloaderTest { - private final WorldStateStorage worldStateStorage = mock(WorldStateStorage.class); - protected ProtocolSchedule protocolSchedule; protected EthProtocolManager ethProtocolManager; protected EthContext ethContext; @@ -66,6 +69,8 @@ public class CheckPointSyncChainDownloaderTest { protected Blockchain otherBlockchain; private Checkpoint checkpoint; + private WorldStateStorageCoordinator worldStateStorageCoordinator; + static class CheckPointSyncChainDownloaderTestArguments implements ArgumentsProvider { @Override public Stream provideArguments(final ExtensionContext context) { @@ -74,11 +79,26 @@ public Stream provideArguments(final ExtensionContext conte } } - public void setup(final DataStorageFormat storageFormat) { - when(worldStateStorage.isWorldStateAvailable(any(), any())).thenReturn(true); - final BlockchainSetupUtil localBlockchainSetup = BlockchainSetupUtil.forTesting(storageFormat); + public void setup(final DataStorageFormat dataStorageFormat) { + final WorldStateKeyValueStorage worldStateKeyValueStorage; + if (dataStorageFormat.equals(DataStorageFormat.BONSAI)) { + worldStateKeyValueStorage = mock(BonsaiWorldStateKeyValueStorage.class); + when(((BonsaiWorldStateKeyValueStorage) worldStateKeyValueStorage) + .isWorldStateAvailable(any(), any())) + .thenReturn(true); + } else { + worldStateKeyValueStorage = mock(ForestWorldStateKeyValueStorage.class); + when(((ForestWorldStateKeyValueStorage) worldStateKeyValueStorage) + .isWorldStateAvailable(any())) + .thenReturn(true); + } + when(worldStateKeyValueStorage.getDataStorageFormat()).thenReturn(dataStorageFormat); + worldStateStorageCoordinator = new WorldStateStorageCoordinator(worldStateKeyValueStorage); + + final BlockchainSetupUtil localBlockchainSetup = + BlockchainSetupUtil.forTesting(dataStorageFormat); localBlockchain = localBlockchainSetup.getBlockchain(); - otherBlockchainSetup = BlockchainSetupUtil.forTesting(storageFormat); + otherBlockchainSetup = BlockchainSetupUtil.forTesting(dataStorageFormat); otherBlockchain = otherBlockchainSetup.getBlockchain(); protocolSchedule = localBlockchainSetup.getProtocolSchedule(); protocolContext = localBlockchainSetup.getProtocolContext(); @@ -106,21 +126,24 @@ public void setup(final DataStorageFormat storageFormat) { } @AfterEach - public void tearDown() { - ethProtocolManager.stop(); + void tearDown() { + if (ethContext != null) { + ethProtocolManager.stop(); + } } private ChainDownloader downloader( final SynchronizerConfiguration syncConfig, final long pivotBlockNumber) { return CheckpointSyncChainDownloader.create( syncConfig, - worldStateStorage, + worldStateStorageCoordinator, protocolSchedule, protocolContext, ethContext, syncState, new NoOpMetricsSystem(), - new FastSyncState(otherBlockchain.getBlockHeader(pivotBlockNumber).get())); + new FastSyncState(otherBlockchain.getBlockHeader(pivotBlockNumber).get()), + SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS); } @ParameterizedTest @@ -190,4 +213,11 @@ public void shouldSyncToPivotBlockInSingleSegment(final DataStorageFormat storag assertThat(localBlockchain.getChainHeadHeader()) .isEqualTo(otherBlockchain.getBlockHeader(pivotBlockNumber).get()); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/DownloadReceiptsStepTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/DownloadReceiptsStepTest.java index 0a775b130fa..c9cfeda1191 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/DownloadReceiptsStepTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/DownloadReceiptsStepTest.java @@ -31,8 +31,8 @@ import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManagerTestUtil; import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import java.util.List; import java.util.concurrent.CompletableFuture; diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastDownloaderFactoryTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastDownloaderFactoryTest.java index 1cb6521e00e..37ca5be2e99 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastDownloaderFactoryTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastDownloaderFactoryTest.java @@ -31,8 +31,13 @@ import org.hyperledger.besu.ethereum.eth.sync.fastsync.worldstate.FastDownloaderFactory; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import java.io.File; import java.io.IOException; @@ -40,10 +45,17 @@ import java.nio.file.Path; import java.time.Clock; import java.util.Optional; +import java.util.stream.Stream; import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; @@ -59,15 +71,36 @@ public class FastDownloaderFactoryTest { @Mock private ProtocolContext protocolContext; @Mock private MetricsSystem metricsSystem; @Mock private EthContext ethContext; - @Mock private WorldStateStorage worldStateStorage; @Mock private SyncState syncState; @Mock private Clock clock; @Mock private Path dataDirectory; @Mock private PivotBlockSelector pivotBlockSelector; + private WorldStateKeyValueStorage worldStateKeyValueStorage; + private WorldStateStorageCoordinator worldStateStorageCoordinator; - @SuppressWarnings("unchecked") - @Test - public void shouldThrowIfSyncModeChangedWhileFastSyncIncomplete() { + static class FastDownloaderFactoryTestArguments implements ArgumentsProvider { + @Override + public Stream provideArguments(final ExtensionContext context) { + return Stream.of( + Arguments.of(DataStorageFormat.BONSAI), Arguments.of(DataStorageFormat.FOREST)); + } + } + + public void setup(final DataStorageFormat dataStorageFormat) { + if (dataStorageFormat.equals(DataStorageFormat.BONSAI)) { + worldStateKeyValueStorage = mock(BonsaiWorldStateKeyValueStorage.class); + } else { + worldStateKeyValueStorage = mock(ForestWorldStateKeyValueStorage.class); + } + when(worldStateKeyValueStorage.getDataStorageFormat()).thenReturn(dataStorageFormat); + worldStateStorageCoordinator = new WorldStateStorageCoordinator(worldStateKeyValueStorage); + } + + @ParameterizedTest + @ArgumentsSource(FastDownloaderFactoryTestArguments.class) + public void shouldThrowIfSyncModeChangedWhileFastSyncIncomplete( + final DataStorageFormat dataStorageFormat) { + setup(dataStorageFormat); initDataDirectory(true); when(syncConfig.getSyncMode()).thenReturn(SyncMode.FULL); @@ -81,15 +114,19 @@ public void shouldThrowIfSyncModeChangedWhileFastSyncIncomplete() { protocolContext, metricsSystem, ethContext, - worldStateStorage, + worldStateStorageCoordinator, syncState, - clock)) + clock, + SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS)) .isInstanceOf(IllegalStateException.class); } @SuppressWarnings({"unchecked", "rawtypes"}) - @Test - public void shouldNotThrowIfSyncModeChangedWhileFastSyncComplete() { + @ParameterizedTest + @ArgumentsSource(FastDownloaderFactoryTestArguments.class) + public void shouldNotThrowIfSyncModeChangedWhileFastSyncComplete( + final DataStorageFormat dataStorageFormat) { + setup(dataStorageFormat); initDataDirectory(false); when(syncConfig.getSyncMode()).thenReturn(SyncMode.FULL); @@ -102,15 +139,19 @@ public void shouldNotThrowIfSyncModeChangedWhileFastSyncComplete() { protocolContext, metricsSystem, ethContext, - worldStateStorage, + worldStateStorageCoordinator, syncState, - clock); + clock, + SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS); assertThat(result).isEmpty(); } @SuppressWarnings("unchecked") - @Test - public void shouldNotThrowWhenFastSyncModeRequested() throws NoSuchFieldException { + @ParameterizedTest + @ArgumentsSource(FastDownloaderFactoryTestArguments.class) + public void shouldNotThrowWhenFastSyncModeRequested(final DataStorageFormat dataStorageFormat) + throws NoSuchFieldException { + setup(dataStorageFormat); initDataDirectory(false); final MutableBlockchain mutableBlockchain = mock(MutableBlockchain.class); @@ -126,15 +167,20 @@ public void shouldNotThrowWhenFastSyncModeRequested() throws NoSuchFieldExceptio protocolContext, metricsSystem, ethContext, - worldStateStorage, + worldStateStorageCoordinator, syncState, - clock); + clock, + SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS); verify(mutableBlockchain).getChainHeadBlockNumber(); } - @Test - public void shouldClearWorldStateDuringFastSyncWhenStateQueDirectoryExists() throws IOException { + @ParameterizedTest + @ArgumentsSource(FastDownloaderFactoryTestArguments.class) + public void shouldClearWorldStateDuringFastSyncWhenStateQueDirectoryExists( + final DataStorageFormat dataStorageFormat) throws IOException { + Assumptions.assumeTrue(dataStorageFormat == DataStorageFormat.FOREST); + setup(dataStorageFormat); when(syncConfig.getSyncMode()).thenReturn(SyncMode.FAST); final MutableBlockchain mutableBlockchain = mock(MutableBlockchain.class); when(mutableBlockchain.getChainHeadBlockNumber()).thenReturn(0L); @@ -156,16 +202,21 @@ public void shouldClearWorldStateDuringFastSyncWhenStateQueDirectoryExists() thr protocolContext, metricsSystem, ethContext, - worldStateStorage, + worldStateStorageCoordinator, syncState, - clock); + clock, + SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS); - verify(worldStateStorage).clear(); + verify(worldStateKeyValueStorage).clear(); assertThat(Files.exists(stateQueueDir)).isFalse(); } - @Test - public void shouldCrashWhenStateQueueIsNotDirectory() throws IOException { + @ParameterizedTest + @ArgumentsSource(FastDownloaderFactoryTestArguments.class) + public void shouldCrashWhenStateQueueIsNotDirectory(final DataStorageFormat dataStorageFormat) + throws IOException { + Assumptions.assumeTrue(dataStorageFormat == DataStorageFormat.FOREST); + setup(dataStorageFormat); when(syncConfig.getSyncMode()).thenReturn(SyncMode.FAST); final MutableBlockchain mutableBlockchain = mock(MutableBlockchain.class); when(mutableBlockchain.getChainHeadBlockNumber()).thenReturn(0L); @@ -188,9 +239,10 @@ public void shouldCrashWhenStateQueueIsNotDirectory() throws IOException { protocolContext, metricsSystem, ethContext, - worldStateStorage, + worldStateStorageCoordinator, syncState, - clock)) + clock, + SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS)) .isInstanceOf(IllegalStateException.class); } @@ -209,4 +261,11 @@ private void initDataDirectory(final boolean isPivotBlockHeaderFileExist) { when(fastSyncDir.toFile()).thenReturn(fastSyncDirFile); when(dataDirectory.resolve(anyString())).thenReturn(fastSyncDir); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastImportBlocksPercentageCalculationTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastImportBlocksPercentageCalculationTest.java index a68194abbe7..7b89387e26d 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastImportBlocksPercentageCalculationTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastImportBlocksPercentageCalculationTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -22,11 +22,11 @@ public class FastImportBlocksPercentageCalculationTest { @Test public void blocksPercent_calculations() { - assertThat(FastImportBlocksStep.getBlocksPercent(1, 1)).isEqualByComparingTo(100l); - assertThat(FastImportBlocksStep.getBlocksPercent(1, 100)).isEqualByComparingTo(1l); - assertThat(FastImportBlocksStep.getBlocksPercent(0, 100)).isEqualByComparingTo(0l); - assertThat(FastImportBlocksStep.getBlocksPercent(99, 0)).isEqualByComparingTo(0l); - assertThat(FastImportBlocksStep.getBlocksPercent(1, 1000)).isEqualByComparingTo(0l); - assertThat(FastImportBlocksStep.getBlocksPercent(1, 10000)).isEqualByComparingTo(0l); + assertThat(ImportBlocksStep.getBlocksPercent(1, 1)).isEqualByComparingTo(100l); + assertThat(ImportBlocksStep.getBlocksPercent(1, 100)).isEqualByComparingTo(1l); + assertThat(ImportBlocksStep.getBlocksPercent(0, 100)).isEqualByComparingTo(0l); + assertThat(ImportBlocksStep.getBlocksPercent(99, 0)).isEqualByComparingTo(0l); + assertThat(ImportBlocksStep.getBlocksPercent(1, 1000)).isEqualByComparingTo(0l); + assertThat(ImportBlocksStep.getBlocksPercent(1, 10000)).isEqualByComparingTo(0l); } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastImportBlocksStepTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastImportBlocksStepTest.java deleted file mode 100644 index 448608df715..00000000000 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastImportBlocksStepTest.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.eth.sync.fastsync; - -import static java.util.Collections.singletonList; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode.FULL; -import static org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode.LIGHT; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.ethereum.ProtocolContext; -import org.hyperledger.besu.ethereum.core.Block; -import org.hyperledger.besu.ethereum.core.BlockDataGenerator; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.BlockImporter; -import org.hyperledger.besu.ethereum.core.BlockWithReceipts; -import org.hyperledger.besu.ethereum.eth.sync.ValidationPolicy; -import org.hyperledger.besu.ethereum.eth.sync.tasks.exceptions.InvalidBlockException; -import org.hyperledger.besu.ethereum.mainnet.BlockImportResult; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; - -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -public class FastImportBlocksStepTest { - - @Mock private ProtocolSchedule protocolSchedule; - @Mock private ProtocolSpec protocolSpec; - @Mock private ProtocolContext protocolContext; - @Mock private BlockImporter blockImporter; - @Mock private ValidationPolicy validationPolicy; - @Mock private ValidationPolicy ommerValidationPolicy; - @Mock private BlockHeader pivotHeader; - private final BlockDataGenerator gen = new BlockDataGenerator(); - - private FastImportBlocksStep importBlocksStep; - - @BeforeEach - public void setUp() { - when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); - when(protocolSpec.getBlockImporter()).thenReturn(blockImporter); - when(validationPolicy.getValidationModeForNextBlock()).thenReturn(FULL); - when(ommerValidationPolicy.getValidationModeForNextBlock()).thenReturn(LIGHT); - - importBlocksStep = - new FastImportBlocksStep( - protocolSchedule, - protocolContext, - validationPolicy, - ommerValidationPolicy, - null, - pivotHeader); - } - - @Test - public void shouldImportBlocks() { - final List blocks = gen.blockSequence(5); - final List blocksWithReceipts = - blocks.stream() - .map(block -> new BlockWithReceipts(block, gen.receipts(block))) - .collect(toList()); - - for (final BlockWithReceipts blockWithReceipts : blocksWithReceipts) { - when(blockImporter.fastImportBlock( - protocolContext, - blockWithReceipts.getBlock(), - blockWithReceipts.getReceipts(), - FULL, - LIGHT)) - .thenReturn(new BlockImportResult(true)); - } - importBlocksStep.accept(blocksWithReceipts); - - for (final BlockWithReceipts blockWithReceipts : blocksWithReceipts) { - verify(protocolSchedule).getByBlockHeader(blockWithReceipts.getHeader()); - } - verify(validationPolicy, times(blocks.size())).getValidationModeForNextBlock(); - } - - @Test - public void shouldThrowExceptionWhenValidationFails() { - final Block block = gen.block(); - final BlockWithReceipts blockWithReceipts = new BlockWithReceipts(block, gen.receipts(block)); - - when(blockImporter.fastImportBlock( - protocolContext, block, blockWithReceipts.getReceipts(), FULL, LIGHT)) - .thenReturn(new BlockImportResult(false)); - assertThatThrownBy(() -> importBlocksStep.accept(singletonList(blockWithReceipts))) - .isInstanceOf(InvalidBlockException.class); - } -} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncActionsTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncActionsTest.java index a707228e92d..68caf2182c0 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncActionsTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncActionsTest.java @@ -40,10 +40,10 @@ import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import java.util.ArrayList; import java.util.List; @@ -53,6 +53,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -62,9 +63,10 @@ public class FastSyncActionsTest { private final SynchronizerConfiguration.Builder syncConfigBuilder = - new SynchronizerConfiguration.Builder().syncMode(SyncMode.FAST).fastSyncPivotDistance(1000); + new SynchronizerConfiguration.Builder().syncMode(SyncMode.FAST).syncPivotDistance(1000); - private final WorldStateStorage worldStateStorage = mock(WorldStateStorage.class); + private final WorldStateStorageCoordinator worldStateStorageCoordinator = + mock(WorldStateStorageCoordinator.class); private final AtomicInteger timeoutCount = new AtomicInteger(0); private SynchronizerConfiguration syncConfig = syncConfigBuilder.build(); private FastSyncActions fastSyncActions; @@ -85,6 +87,7 @@ public Stream provideArguments(final ExtensionContext conte } public void setUp(final DataStorageFormat storageFormat) { + when(worldStateStorageCoordinator.getDataStorageFormat()).thenReturn(storageFormat); blockchainSetupUtil = BlockchainSetupUtil.forTesting(storageFormat); blockchainSetupUtil.importAllBlocks(); blockchain = blockchainSetupUtil.getBlockchain(); @@ -111,9 +114,9 @@ public void setUp(final DataStorageFormat storageFormat) { public void waitForPeersShouldSucceedIfEnoughPeersAreFound( final DataStorageFormat storageFormat) { setUp(storageFormat); - for (int i = 0; i < syncConfig.getFastSyncMinimumPeerCount(); i++) { + for (int i = 0; i < syncConfig.getSyncMinimumPeerCount(); i++) { EthProtocolManagerTestUtil.createPeer( - ethProtocolManager, syncConfig.getFastSyncPivotDistance() + i + 1); + ethProtocolManager, syncConfig.getSyncPivotDistance() + i + 1); } final CompletableFuture result = fastSyncActions.selectPivotBlock(FastSyncState.EMPTY_SYNC_STATE); @@ -151,7 +154,7 @@ public void selectPivotBlockShouldSelectBlockPivotDistanceFromBestPeer( final DataStorageFormat storageFormat) { setUp(storageFormat); final int minPeers = 1; - syncConfigBuilder.fastSyncMinimumPeerCount(minPeers); + syncConfigBuilder.syncMinimumPeerCount(minPeers); syncConfig = syncConfigBuilder.build(); fastSyncActions = createFastSyncActions( @@ -172,7 +175,7 @@ public void selectPivotBlockShouldConsiderTotalDifficultyWhenSelectingBestPeer( final DataStorageFormat storageFormat) { setUp(storageFormat); final int minPeers = 1; - syncConfigBuilder.fastSyncMinimumPeerCount(minPeers); + syncConfigBuilder.syncMinimumPeerCount(minPeers); syncConfig = syncConfigBuilder.build(); fastSyncActions = createFastSyncActions( @@ -195,7 +198,7 @@ public void selectPivotBlockShouldWaitAndRetryUntilMinHeightEstimatesAreAvailabl setUp(storageFormat); EthProtocolManagerTestUtil.disableEthSchedulerAutoRun(ethProtocolManager); final int minPeers = 2; - syncConfigBuilder.fastSyncMinimumPeerCount(minPeers); + syncConfigBuilder.syncMinimumPeerCount(minPeers); syncConfig = syncConfigBuilder.build(); fastSyncActions = createFastSyncActions( @@ -226,13 +229,13 @@ public void selectPivotBlockShouldWaitAndRetryIfSufficientChainHeightEstimatesAr final DataStorageFormat storageFormat) { setUp(storageFormat); final int minPeers = 3; - syncConfigBuilder.fastSyncMinimumPeerCount(minPeers); + syncConfigBuilder.syncMinimumPeerCount(minPeers); syncConfig = syncConfigBuilder.build(); fastSyncActions = createFastSyncActions( syncConfig, new PivotSelectorFromPeers(ethContext, syncConfig, syncState, metricsSystem)); - final long minPivotHeight = syncConfig.getFastSyncPivotDistance() + 1L; + final long minPivotHeight = syncConfig.getSyncPivotDistance() + 1L; EthProtocolManagerTestUtil.disableEthSchedulerAutoRun(ethProtocolManager); // Create peers without chain height estimates @@ -265,7 +268,7 @@ public void selectPivotBlockShouldWaitAndRetryIfSufficientChainHeightEstimatesAr final long bestPeerHeight = minPivotHeight + 1; peers.get(minPeers - 1).getEthPeer().chainState().updateHeightEstimate(bestPeerHeight); final FastSyncState expected = - new FastSyncState(bestPeerHeight - syncConfig.getFastSyncPivotDistance()); + new FastSyncState(bestPeerHeight - syncConfig.getSyncPivotDistance()); EthProtocolManagerTestUtil.runPendingFutures(ethProtocolManager); assertThat(result).isCompletedWithValue(expected); } @@ -277,13 +280,13 @@ public void selectPivotBlockShouldWaitAndRetryIfSufficientValidatedPeersUnavaila setUp(storageFormat); final int minPeers = 3; final PeerValidator validator = mock(PeerValidator.class); - syncConfigBuilder.fastSyncMinimumPeerCount(minPeers); + syncConfigBuilder.syncMinimumPeerCount(minPeers); syncConfig = syncConfigBuilder.build(); fastSyncActions = createFastSyncActions( syncConfig, new PivotSelectorFromPeers(ethContext, syncConfig, syncState, metricsSystem)); - final long minPivotHeight = syncConfig.getFastSyncPivotDistance() + 1L; + final long minPivotHeight = syncConfig.getSyncPivotDistance() + 1L; EthProtocolManagerTestUtil.disableEthSchedulerAutoRun(ethProtocolManager); // Create peers that are not validated @@ -317,7 +320,7 @@ public void selectPivotBlockShouldWaitAndRetryIfSufficientValidatedPeersUnavaila bestPeer.chainState().updateHeightEstimate(bestPeerHeight); bestPeer.markValidated(validator); final FastSyncState expected = - new FastSyncState(bestPeerHeight - syncConfig.getFastSyncPivotDistance()); + new FastSyncState(bestPeerHeight - syncConfig.getSyncPivotDistance()); EthProtocolManagerTestUtil.runPendingFutures(ethProtocolManager); assertThat(result).isCompletedWithValue(expected); } @@ -349,13 +352,13 @@ private void selectPivotBlockUsesBestPeerMatchingRequiredCriteria( final boolean bestMissingHeight, final boolean bestNotValidated) { final int minPeers = 3; final int peerCount = minPeers + 1; - syncConfigBuilder.fastSyncMinimumPeerCount(minPeers); + syncConfigBuilder.syncMinimumPeerCount(minPeers); syncConfig = syncConfigBuilder.build(); fastSyncActions = createFastSyncActions( syncConfig, new PivotSelectorFromPeers(ethContext, syncConfig, syncState, metricsSystem)); - final long minPivotHeight = syncConfig.getFastSyncPivotDistance() + 1L; + final long minPivotHeight = syncConfig.getSyncPivotDistance() + 1L; EthProtocolManagerTestUtil.disableEthSchedulerAutoRun(ethProtocolManager); // Create peers without chain height estimates @@ -390,7 +393,7 @@ private void selectPivotBlockUsesBestPeerMatchingRequiredCriteria( final long expectedBestChainHeight = peers.get(1).getEthPeer().chainState().getEstimatedHeight(); final FastSyncState expected = - new FastSyncState(expectedBestChainHeight - syncConfig.getFastSyncPivotDistance()); + new FastSyncState(expectedBestChainHeight - syncConfig.getSyncPivotDistance()); EthProtocolManagerTestUtil.runPendingFutures(ethProtocolManager); assertThat(result).isCompletedWithValue(expected); } @@ -401,13 +404,13 @@ public void selectPivotBlockShouldWaitAndRetryIfBestPeerChainIsShorterThanPivotD final DataStorageFormat storageFormat) { setUp(storageFormat); final int minPeers = 1; - syncConfigBuilder.fastSyncMinimumPeerCount(minPeers); + syncConfigBuilder.syncMinimumPeerCount(minPeers); syncConfig = syncConfigBuilder.build(); fastSyncActions = createFastSyncActions( syncConfig, new PivotSelectorFromPeers(ethContext, syncConfig, syncState, metricsSystem)); - final long pivotDistance = syncConfig.getFastSyncPivotDistance(); + final long pivotDistance = syncConfig.getSyncPivotDistance(); EthProtocolManagerTestUtil.disableEthSchedulerAutoRun(ethProtocolManager); EthProtocolManagerTestUtil.createPeer(ethProtocolManager, pivotDistance - 1); @@ -430,10 +433,10 @@ public void selectPivotBlockShouldWaitAndRetryIfBestPeerChainIsShorterThanPivotD public void selectPivotBlockShouldRetryIfBestPeerChainIsEqualToPivotDistance( final DataStorageFormat storageFormat) { setUp(storageFormat); - final long pivotDistance = syncConfig.getFastSyncPivotDistance(); + final long pivotDistance = syncConfig.getSyncPivotDistance(); EthProtocolManagerTestUtil.disableEthSchedulerAutoRun(ethProtocolManager); // Create peers with chains that are too short - for (int i = 0; i < syncConfig.getFastSyncMinimumPeerCount(); i++) { + for (int i = 0; i < syncConfig.getSyncMinimumPeerCount(); i++) { EthProtocolManagerTestUtil.createPeer(ethProtocolManager, pivotDistance); } @@ -465,7 +468,7 @@ public void downloadPivotBlockHeaderShouldUseExistingPivotBlockHeaderIfPresent( public void downloadPivotBlockHeaderShouldRetrievePivotBlockHeader( final DataStorageFormat storageFormat) { setUp(storageFormat); - syncConfig = SynchronizerConfiguration.builder().fastSyncMinimumPeerCount(1).build(); + syncConfig = SynchronizerConfiguration.builder().syncMinimumPeerCount(1).build(); fastSyncActions = createFastSyncActions( syncConfig, @@ -487,7 +490,7 @@ public void downloadPivotBlockHeaderShouldRetrievePivotBlockHeader( public void downloadPivotBlockHeaderShouldRetrievePivotBlockHash( final DataStorageFormat storageFormat) { setUp(storageFormat); - syncConfig = SynchronizerConfiguration.builder().fastSyncMinimumPeerCount(1).build(); + syncConfig = SynchronizerConfiguration.builder().syncMinimumPeerCount(1).build(); GenesisConfigOptions genesisConfig = mock(GenesisConfigOptions.class); when(genesisConfig.getTerminalBlockNumber()).thenReturn(OptionalLong.of(10L)); @@ -529,7 +532,7 @@ private FastSyncActions createFastSyncActions( final EthContext ethContext = ethProtocolManager.ethContext(); return new FastSyncActions( syncConfig, - worldStateStorage, + worldStateStorageCoordinator, protocolSchedule, protocolContext, ethContext, @@ -537,4 +540,11 @@ private FastSyncActions createFastSyncActions( pivotBlockSelector, new NoOpMetricsSystem()); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncChainDownloaderTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncChainDownloaderTest.java index d2c63a18a15..34014246d28 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncChainDownloaderTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncChainDownloaderTest.java @@ -35,15 +35,17 @@ import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import java.util.concurrent.CompletableFuture; import java.util.concurrent.locks.LockSupport; import java.util.stream.Stream; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -52,7 +54,8 @@ public class FastSyncChainDownloaderTest { - private final WorldStateStorage worldStateStorage = mock(WorldStateStorage.class); + private final WorldStateStorageCoordinator worldStateStorageCoordinator = + mock(WorldStateStorageCoordinator.class); protected ProtocolSchedule protocolSchedule; protected EthProtocolManager ethProtocolManager; @@ -73,7 +76,8 @@ public Stream provideArguments(final ExtensionContext conte } public void setup(final DataStorageFormat storageFormat) { - when(worldStateStorage.isWorldStateAvailable(any(), any())).thenReturn(true); + when(worldStateStorageCoordinator.getDataStorageFormat()).thenReturn(storageFormat); + when(worldStateStorageCoordinator.isWorldStateAvailable(any(), any())).thenReturn(true); final BlockchainSetupUtil localBlockchainSetup = BlockchainSetupUtil.forTesting(storageFormat); localBlockchain = localBlockchainSetup.getBlockchain(); otherBlockchainSetup = BlockchainSetupUtil.forTesting(storageFormat); @@ -93,20 +97,23 @@ public void setup(final DataStorageFormat storageFormat) { @AfterEach public void tearDown() { - ethProtocolManager.stop(); + if (ethProtocolManager != null) { + ethProtocolManager.stop(); + } } private ChainDownloader downloader( final SynchronizerConfiguration syncConfig, final long pivotBlockNumber) { return FastSyncChainDownloader.create( syncConfig, - worldStateStorage, + worldStateStorageCoordinator, protocolSchedule, protocolContext, ethContext, syncState, new NoOpMetricsSystem(), - new FastSyncState(otherBlockchain.getBlockHeader(pivotBlockNumber).get())); + new FastSyncState(otherBlockchain.getBlockHeader(pivotBlockNumber).get()), + SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS); } @ParameterizedTest @@ -220,4 +227,11 @@ public void recoversFromSyncTargetDisconnect(final DataStorageFormat storageForm assertThat(localBlockchain.getChainHeadHeader()) .isEqualTo(otherBlockchain.getBlockHeader(pivotBlockNumber).get()); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java index 632fed6ede1..13444a91278 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/FastSyncDownloaderTest.java @@ -34,8 +34,12 @@ import org.hyperledger.besu.ethereum.eth.sync.fastsync.worldstate.NodeDataRequest; import org.hyperledger.besu.ethereum.eth.sync.worldstate.StalledDownloadException; import org.hyperledger.besu.ethereum.eth.sync.worldstate.WorldStateDownloader; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.metrics.SyncDurationMetrics; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.services.tasks.TaskCollection; import java.nio.file.Path; @@ -44,18 +48,21 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; +import java.util.stream.Stream; import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; public class FastSyncDownloaderTest { @SuppressWarnings("unchecked") private final FastSyncActions fastSyncActions = mock(FastSyncActions.class); - private final WorldStateStorage worldStateStorage = mock(WorldStateStorage.class); - private final WorldStateDownloader worldStateDownloader = mock(FastWorldStateDownloader.class); private final FastSyncStateStorage storage = mock(FastSyncStateStorage.class); @@ -65,25 +72,48 @@ public class FastSyncDownloaderTest { private final ChainDownloader chainDownloader = mock(ChainDownloader.class); private final Path fastSyncDataDirectory = null; + private WorldStateStorageCoordinator worldStateStorageCoordinator; + private FastSyncDownloader downloader; + + static class FastSyncDownloaderTestArguments implements ArgumentsProvider { + @Override + public Stream provideArguments(final ExtensionContext context) { + return Stream.of( + Arguments.of(DataStorageFormat.BONSAI), Arguments.of(DataStorageFormat.FOREST)); + } + } - private final FastSyncDownloader downloader = - new FastSyncDownloader<>( - fastSyncActions, - worldStateStorage, - worldStateDownloader, - storage, - taskCollection, - fastSyncDataDirectory, - FastSyncState.EMPTY_SYNC_STATE); - - @BeforeEach - public void setup() { - when(worldStateStorage.getDataStorageFormat()).thenReturn(DataStorageFormat.FOREST); - when(worldStateStorage.isWorldStateAvailable(any(), any())).thenReturn(true); + public void setup(final DataStorageFormat dataStorageFormat) { + final WorldStateKeyValueStorage worldStateKeyValueStorage; + if (dataStorageFormat.equals(DataStorageFormat.BONSAI)) { + worldStateKeyValueStorage = mock(BonsaiWorldStateKeyValueStorage.class); + when(((BonsaiWorldStateKeyValueStorage) worldStateKeyValueStorage) + .isWorldStateAvailable(any(), any())) + .thenReturn(true); + } else { + worldStateKeyValueStorage = mock(ForestWorldStateKeyValueStorage.class); + when(((ForestWorldStateKeyValueStorage) worldStateKeyValueStorage) + .isWorldStateAvailable(any())) + .thenReturn(true); + } + when(worldStateKeyValueStorage.getDataStorageFormat()).thenReturn(dataStorageFormat); + worldStateStorageCoordinator = new WorldStateStorageCoordinator(worldStateKeyValueStorage); + downloader = + new FastSyncDownloader<>( + fastSyncActions, + worldStateStorageCoordinator, + worldStateDownloader, + storage, + taskCollection, + fastSyncDataDirectory, + FastSyncState.EMPTY_SYNC_STATE, + SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS); } - @Test - public void shouldCompleteFastSyncSuccessfully() { + @ParameterizedTest + @ArgumentsSource(FastSyncDownloaderTestArguments.class) + public void shouldCompleteFastSyncSuccessfully(final DataStorageFormat dataStorageFormat) { + setup(dataStorageFormat); final FastSyncState selectPivotBlockState = new FastSyncState(50); final BlockHeader pivotBlockHeader = new BlockHeaderTestFixture().number(50).buildHeader(); final FastSyncState downloadPivotBlockHeaderState = new FastSyncState(pivotBlockHeader); @@ -91,7 +121,8 @@ public void shouldCompleteFastSyncSuccessfully() { .thenReturn(completedFuture(selectPivotBlockState)); when(fastSyncActions.downloadPivotBlockHeader(selectPivotBlockState)) .thenReturn(completedFuture(downloadPivotBlockHeaderState)); - when(fastSyncActions.createChainDownloader(downloadPivotBlockHeaderState)) + when(fastSyncActions.createChainDownloader( + downloadPivotBlockHeaderState, SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS)) .thenReturn(chainDownloader); when(chainDownloader.start()).thenReturn(completedFuture(null)); when(worldStateDownloader.run( @@ -103,7 +134,9 @@ public void shouldCompleteFastSyncSuccessfully() { verify(fastSyncActions).selectPivotBlock(FastSyncState.EMPTY_SYNC_STATE); verify(fastSyncActions).downloadPivotBlockHeader(selectPivotBlockState); verify(storage).storeState(downloadPivotBlockHeaderState); - verify(fastSyncActions).createChainDownloader(downloadPivotBlockHeaderState); + verify(fastSyncActions) + .createChainDownloader( + downloadPivotBlockHeaderState, SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS); verify(chainDownloader).start(); verify(worldStateDownloader) .run(any(FastSyncActions.class), eq(new FastSyncState(pivotBlockHeader))); @@ -111,14 +144,18 @@ public void shouldCompleteFastSyncSuccessfully() { assertThat(result).isCompletedWithValue(downloadPivotBlockHeaderState); } - @Test - public void shouldResumeFastSync() { + @ParameterizedTest + @ArgumentsSource(FastSyncDownloaderTestArguments.class) + public void shouldResumeFastSync(final DataStorageFormat dataStorageFormat) { + setup(dataStorageFormat); final BlockHeader pivotBlockHeader = new BlockHeaderTestFixture().number(50).buildHeader(); final FastSyncState fastSyncState = new FastSyncState(pivotBlockHeader); final CompletableFuture complete = completedFuture(fastSyncState); when(fastSyncActions.selectPivotBlock(fastSyncState)).thenReturn(complete); when(fastSyncActions.downloadPivotBlockHeader(fastSyncState)).thenReturn(complete); - when(fastSyncActions.createChainDownloader(fastSyncState)).thenReturn(chainDownloader); + when(fastSyncActions.createChainDownloader( + fastSyncState, SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS)) + .thenReturn(chainDownloader); when(chainDownloader.start()).thenReturn(completedFuture(null)); when(worldStateDownloader.run( any(FastSyncActions.class), eq(new FastSyncState(pivotBlockHeader)))) @@ -127,19 +164,21 @@ public void shouldResumeFastSync() { final FastSyncDownloader resumedDownloader = new FastSyncDownloader<>( fastSyncActions, - worldStateStorage, + worldStateStorageCoordinator, worldStateDownloader, storage, taskCollection, fastSyncDataDirectory, - fastSyncState); + fastSyncState, + SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS); final CompletableFuture result = resumedDownloader.start(); verify(fastSyncActions).selectPivotBlock(fastSyncState); verify(fastSyncActions).downloadPivotBlockHeader(fastSyncState); verify(storage).storeState(fastSyncState); - verify(fastSyncActions).createChainDownloader(fastSyncState); + verify(fastSyncActions) + .createChainDownloader(fastSyncState, SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS); verify(chainDownloader).start(); verify(worldStateDownloader) .run(any(FastSyncActions.class), eq(new FastSyncState(pivotBlockHeader))); @@ -147,21 +186,25 @@ public void shouldResumeFastSync() { assertThat(result).isCompletedWithValue(fastSyncState); } - @Test - public void shouldAbortIfSelectPivotBlockFails() { + @ParameterizedTest + @ArgumentsSource(FastSyncDownloaderTestArguments.class) + public void shouldAbortIfSelectPivotBlockFails(final DataStorageFormat dataStorageFormat) { + setup(dataStorageFormat); when(fastSyncActions.selectPivotBlock(FastSyncState.EMPTY_SYNC_STATE)) - .thenThrow(new FastSyncException(FastSyncError.UNEXPECTED_ERROR)); + .thenThrow(new SyncException(SyncError.UNEXPECTED_ERROR)); final CompletableFuture result = downloader.start(); - assertCompletedExceptionally(result, FastSyncError.UNEXPECTED_ERROR); + assertCompletedExceptionally(result, SyncError.UNEXPECTED_ERROR); verify(fastSyncActions).selectPivotBlock(FastSyncState.EMPTY_SYNC_STATE); verifyNoMoreInteractions(fastSyncActions); } - @Test - public void shouldAbortIfWorldStateDownloadFails() { + @ParameterizedTest + @ArgumentsSource(FastSyncDownloaderTestArguments.class) + public void shouldAbortIfWorldStateDownloadFails(final DataStorageFormat dataStorageFormat) { + setup(dataStorageFormat); final CompletableFuture worldStateFuture = new CompletableFuture<>(); final CompletableFuture chainFuture = new CompletableFuture<>(); final FastSyncState selectPivotBlockState = new FastSyncState(50); @@ -172,7 +215,8 @@ public void shouldAbortIfWorldStateDownloadFails() { .thenReturn(completedFuture(selectPivotBlockState)); when(fastSyncActions.downloadPivotBlockHeader(selectPivotBlockState)) .thenReturn(completedFuture(downloadPivotBlockHeaderState)); - when(fastSyncActions.createChainDownloader(downloadPivotBlockHeaderState)) + when(fastSyncActions.createChainDownloader( + downloadPivotBlockHeaderState, SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS)) .thenReturn(chainDownloader); when(chainDownloader.start()).thenReturn(chainFuture); when(worldStateDownloader.run( @@ -184,22 +228,26 @@ public void shouldAbortIfWorldStateDownloadFails() { verify(fastSyncActions).selectPivotBlock(FastSyncState.EMPTY_SYNC_STATE); verify(fastSyncActions).downloadPivotBlockHeader(selectPivotBlockState); verify(storage).storeState(downloadPivotBlockHeaderState); - verify(fastSyncActions).createChainDownloader(downloadPivotBlockHeaderState); + verify(fastSyncActions) + .createChainDownloader( + downloadPivotBlockHeaderState, SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS); verify(worldStateDownloader) .run(any(FastSyncActions.class), eq(new FastSyncState(pivotBlockHeader))); verifyNoMoreInteractions(fastSyncActions, worldStateDownloader, storage); assertThat(result).isNotDone(); - worldStateFuture.completeExceptionally(new FastSyncException(FastSyncError.NO_PEERS_AVAILABLE)); + worldStateFuture.completeExceptionally(new SyncException(SyncError.NO_PEERS_AVAILABLE)); verify(chainDownloader).cancel(); chainFuture.completeExceptionally(new CancellationException()); - assertCompletedExceptionally(result, FastSyncError.NO_PEERS_AVAILABLE); + assertCompletedExceptionally(result, SyncError.NO_PEERS_AVAILABLE); assertThat(chainFuture).isCancelled(); } - @Test - public void shouldAbortIfChainDownloadFails() { + @ParameterizedTest + @ArgumentsSource(FastSyncDownloaderTestArguments.class) + public void shouldAbortIfChainDownloadFails(final DataStorageFormat dataStorageFormat) { + setup(dataStorageFormat); final CompletableFuture chainFuture = new CompletableFuture<>(); final CompletableFuture worldStateFuture = new CompletableFuture<>(); final FastSyncState selectPivotBlockState = new FastSyncState(50); @@ -210,7 +258,8 @@ public void shouldAbortIfChainDownloadFails() { .thenReturn(completedFuture(selectPivotBlockState)); when(fastSyncActions.downloadPivotBlockHeader(selectPivotBlockState)) .thenReturn(completedFuture(downloadPivotBlockHeaderState)); - when(fastSyncActions.createChainDownloader(downloadPivotBlockHeaderState)) + when(fastSyncActions.createChainDownloader( + downloadPivotBlockHeaderState, SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS)) .thenReturn(chainDownloader); when(chainDownloader.start()).thenReturn(chainFuture); when(worldStateDownloader.run( @@ -221,7 +270,9 @@ public void shouldAbortIfChainDownloadFails() { verify(fastSyncActions).selectPivotBlock(FastSyncState.EMPTY_SYNC_STATE); verify(fastSyncActions).downloadPivotBlockHeader(selectPivotBlockState); - verify(fastSyncActions).createChainDownloader(downloadPivotBlockHeaderState); + verify(fastSyncActions) + .createChainDownloader( + downloadPivotBlockHeaderState, SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS); verify(worldStateDownloader) .run(any(FastSyncActions.class), eq(new FastSyncState(pivotBlockHeader))); verifyNoMoreInteractions(fastSyncActions); @@ -229,13 +280,15 @@ public void shouldAbortIfChainDownloadFails() { assertThat(result).isNotDone(); - chainFuture.completeExceptionally(new FastSyncException(FastSyncError.NO_PEERS_AVAILABLE)); - assertCompletedExceptionally(result, FastSyncError.NO_PEERS_AVAILABLE); + chainFuture.completeExceptionally(new SyncException(SyncError.NO_PEERS_AVAILABLE)); + assertCompletedExceptionally(result, SyncError.NO_PEERS_AVAILABLE); assertThat(worldStateFuture).isCancelled(); } - @Test - public void shouldAbortIfStopped() { + @ParameterizedTest + @ArgumentsSource(FastSyncDownloaderTestArguments.class) + public void shouldAbortIfStopped(final DataStorageFormat dataStorageFormat) { + setup(dataStorageFormat); final FastSyncState selectPivotBlockState = new FastSyncState(50); final BlockHeader pivotBlockHeader = new BlockHeaderTestFixture().number(50).buildHeader(); final FastSyncState downloadPivotBlockHeaderState = new FastSyncState(pivotBlockHeader); @@ -268,8 +321,11 @@ public void shouldAbortIfStopped() { verifyNoMoreInteractions(fastSyncActions, worldStateDownloader, storage); } - @Test - public void shouldNotConsiderFastSyncCompleteIfOnlyWorldStateDownloadIsComplete() { + @ParameterizedTest + @ArgumentsSource(FastSyncDownloaderTestArguments.class) + public void shouldNotConsiderFastSyncCompleteIfOnlyWorldStateDownloadIsComplete( + final DataStorageFormat dataStorageFormat) { + setup(dataStorageFormat); final CompletableFuture chainFuture = new CompletableFuture<>(); final CompletableFuture worldStateFuture = new CompletableFuture<>(); final FastSyncState selectPivotBlockState = new FastSyncState(50); @@ -280,7 +336,8 @@ public void shouldNotConsiderFastSyncCompleteIfOnlyWorldStateDownloadIsComplete( .thenReturn(completedFuture(selectPivotBlockState)); when(fastSyncActions.downloadPivotBlockHeader(selectPivotBlockState)) .thenReturn(completedFuture(downloadPivotBlockHeaderState)); - when(fastSyncActions.createChainDownloader(downloadPivotBlockHeaderState)) + when(fastSyncActions.createChainDownloader( + downloadPivotBlockHeaderState, SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS)) .thenReturn(chainDownloader); when(chainDownloader.start()).thenReturn(chainFuture); when(worldStateDownloader.run( @@ -291,7 +348,9 @@ public void shouldNotConsiderFastSyncCompleteIfOnlyWorldStateDownloadIsComplete( verify(fastSyncActions).selectPivotBlock(FastSyncState.EMPTY_SYNC_STATE); verify(fastSyncActions).downloadPivotBlockHeader(selectPivotBlockState); - verify(fastSyncActions).createChainDownloader(downloadPivotBlockHeaderState); + verify(fastSyncActions) + .createChainDownloader( + downloadPivotBlockHeaderState, SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS); verify(worldStateDownloader) .run(any(FastSyncActions.class), eq(new FastSyncState(pivotBlockHeader))); verifyNoMoreInteractions(fastSyncActions); @@ -303,8 +362,11 @@ public void shouldNotConsiderFastSyncCompleteIfOnlyWorldStateDownloadIsComplete( assertThat(result).isNotDone(); } - @Test - public void shouldNotConsiderFastSyncCompleteIfOnlyChainDownloadIsComplete() { + @ParameterizedTest + @ArgumentsSource(FastSyncDownloaderTestArguments.class) + public void shouldNotConsiderFastSyncCompleteIfOnlyChainDownloadIsComplete( + final DataStorageFormat dataStorageFormat) { + setup(dataStorageFormat); final CompletableFuture chainFuture = new CompletableFuture<>(); final CompletableFuture worldStateFuture = new CompletableFuture<>(); final FastSyncState selectPivotBlockState = new FastSyncState(50); @@ -315,7 +377,8 @@ public void shouldNotConsiderFastSyncCompleteIfOnlyChainDownloadIsComplete() { .thenReturn(completedFuture(selectPivotBlockState)); when(fastSyncActions.downloadPivotBlockHeader(selectPivotBlockState)) .thenReturn(completedFuture(downloadPivotBlockHeaderState)); - when(fastSyncActions.createChainDownloader(downloadPivotBlockHeaderState)) + when(fastSyncActions.createChainDownloader( + downloadPivotBlockHeaderState, SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS)) .thenReturn(chainDownloader); when(chainDownloader.start()).thenReturn(chainFuture); when(worldStateDownloader.run( @@ -326,7 +389,9 @@ public void shouldNotConsiderFastSyncCompleteIfOnlyChainDownloadIsComplete() { verify(fastSyncActions).selectPivotBlock(FastSyncState.EMPTY_SYNC_STATE); verify(fastSyncActions).downloadPivotBlockHeader(selectPivotBlockState); - verify(fastSyncActions).createChainDownloader(downloadPivotBlockHeaderState); + verify(fastSyncActions) + .createChainDownloader( + downloadPivotBlockHeaderState, SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS); verify(worldStateDownloader) .run(any(FastSyncActions.class), eq(new FastSyncState(pivotBlockHeader))); verifyNoMoreInteractions(fastSyncActions); @@ -339,8 +404,11 @@ public void shouldNotConsiderFastSyncCompleteIfOnlyChainDownloadIsComplete() { } @SuppressWarnings("unchecked") - @Test - public void shouldResetFastSyncStateAndRestartProcessIfWorldStateIsUnavailable() { + @ParameterizedTest + @ArgumentsSource(FastSyncDownloaderTestArguments.class) + public void shouldResetFastSyncStateAndRestartProcessIfWorldStateIsUnavailable( + final DataStorageFormat dataStorageFormat) { + setup(dataStorageFormat); final CompletableFuture firstWorldStateFuture = new CompletableFuture<>(); final CompletableFuture secondWorldStateFuture = new CompletableFuture<>(); final CompletableFuture chainFuture = new CompletableFuture<>(); @@ -360,7 +428,8 @@ public void shouldResetFastSyncStateAndRestartProcessIfWorldStateIsUnavailable() completedFuture(selectPivotBlockState), completedFuture(secondSelectPivotBlockState)); when(fastSyncActions.downloadPivotBlockHeader(selectPivotBlockState)) .thenReturn(completedFuture(downloadPivotBlockHeaderState)); - when(fastSyncActions.createChainDownloader(downloadPivotBlockHeaderState)) + when(fastSyncActions.createChainDownloader( + downloadPivotBlockHeaderState, SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS)) .thenReturn(chainDownloader); when(chainDownloader.start()).thenReturn(chainFuture); when(worldStateDownloader.run( @@ -371,7 +440,8 @@ public void shouldResetFastSyncStateAndRestartProcessIfWorldStateIsUnavailable() when(fastSyncActions.downloadPivotBlockHeader(secondSelectPivotBlockState)) .thenReturn(completedFuture(secondDownloadPivotBlockHeaderState)); - when(fastSyncActions.createChainDownloader(secondDownloadPivotBlockHeaderState)) + when(fastSyncActions.createChainDownloader( + secondDownloadPivotBlockHeaderState, SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS)) .thenReturn(secondChainDownloader); when(secondChainDownloader.start()).thenReturn(completedFuture(null)); when(worldStateDownloader.run( @@ -383,7 +453,9 @@ public void shouldResetFastSyncStateAndRestartProcessIfWorldStateIsUnavailable() verify(fastSyncActions).selectPivotBlock(FastSyncState.EMPTY_SYNC_STATE); verify(fastSyncActions).downloadPivotBlockHeader(selectPivotBlockState); verify(storage).storeState(downloadPivotBlockHeaderState); - verify(fastSyncActions).createChainDownloader(downloadPivotBlockHeaderState); + verify(fastSyncActions) + .createChainDownloader( + downloadPivotBlockHeaderState, SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS); verify(worldStateDownloader) .run(any(FastSyncActions.class), eq(new FastSyncState(pivotBlockHeader))); verifyNoMoreInteractions(fastSyncActions, worldStateDownloader, storage); @@ -399,7 +471,9 @@ public void shouldResetFastSyncStateAndRestartProcessIfWorldStateIsUnavailable() verify(fastSyncActions, times(2)).selectPivotBlock(FastSyncState.EMPTY_SYNC_STATE); verify(fastSyncActions).downloadPivotBlockHeader(secondSelectPivotBlockState); verify(storage).storeState(secondDownloadPivotBlockHeaderState); - verify(fastSyncActions).createChainDownloader(secondDownloadPivotBlockHeaderState); + verify(fastSyncActions) + .createChainDownloader( + secondDownloadPivotBlockHeaderState, SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS); verify(worldStateDownloader) .run(any(FastSyncActions.class), eq(new FastSyncState(secondPivotBlockHeader))); verifyNoMoreInteractions(fastSyncActions, worldStateDownloader, storage); @@ -410,8 +484,11 @@ public void shouldResetFastSyncStateAndRestartProcessIfWorldStateIsUnavailable() } @SuppressWarnings("unchecked") - @Test - public void shouldResetFastSyncStateAndRestartProcessIfANonFastSyncExceptionOccurs() { + @ParameterizedTest + @ArgumentsSource(FastSyncDownloaderTestArguments.class) + public void shouldResetFastSyncStateAndRestartProcessIfANonFastSyncExceptionOccurs( + final DataStorageFormat dataStorageFormat) { + setup(dataStorageFormat); final CompletableFuture firstWorldStateFuture = new CompletableFuture<>(); final CompletableFuture secondWorldStateFuture = new CompletableFuture<>(); final CompletableFuture chainFuture = new CompletableFuture<>(); @@ -431,7 +508,8 @@ public void shouldResetFastSyncStateAndRestartProcessIfANonFastSyncExceptionOccu completedFuture(selectPivotBlockState), completedFuture(secondSelectPivotBlockState)); when(fastSyncActions.downloadPivotBlockHeader(selectPivotBlockState)) .thenReturn(completedFuture(downloadPivotBlockHeaderState)); - when(fastSyncActions.createChainDownloader(downloadPivotBlockHeaderState)) + when(fastSyncActions.createChainDownloader( + downloadPivotBlockHeaderState, SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS)) .thenReturn(chainDownloader); when(chainDownloader.start()).thenReturn(chainFuture); when(worldStateDownloader.run( @@ -444,7 +522,8 @@ public void shouldResetFastSyncStateAndRestartProcessIfANonFastSyncExceptionOccu when(fastSyncActions.downloadPivotBlockHeader(secondSelectPivotBlockState)) .thenReturn(completedFuture(secondDownloadPivotBlockHeaderState)); - when(fastSyncActions.createChainDownloader(secondDownloadPivotBlockHeaderState)) + when(fastSyncActions.createChainDownloader( + secondDownloadPivotBlockHeaderState, SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS)) .thenReturn(secondChainDownloader); when(secondChainDownloader.start()).thenReturn(completedFuture(null)); when(worldStateDownloader.run( @@ -456,7 +535,9 @@ public void shouldResetFastSyncStateAndRestartProcessIfANonFastSyncExceptionOccu verify(fastSyncActions).selectPivotBlock(FastSyncState.EMPTY_SYNC_STATE); verify(fastSyncActions).downloadPivotBlockHeader(selectPivotBlockState); verify(storage).storeState(downloadPivotBlockHeaderState); - verify(fastSyncActions).createChainDownloader(downloadPivotBlockHeaderState); + verify(fastSyncActions) + .createChainDownloader( + downloadPivotBlockHeaderState, SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS); verify(worldStateDownloader) .run(any(FastSyncActions.class), eq(new FastSyncState(pivotBlockHeader))); verifyNoMoreInteractions(fastSyncActions, worldStateDownloader, storage); @@ -474,7 +555,9 @@ public void shouldResetFastSyncStateAndRestartProcessIfANonFastSyncExceptionOccu verify(fastSyncActions, times(2)).selectPivotBlock(FastSyncState.EMPTY_SYNC_STATE); verify(fastSyncActions).downloadPivotBlockHeader(secondSelectPivotBlockState); verify(storage).storeState(secondDownloadPivotBlockHeaderState); - verify(fastSyncActions).createChainDownloader(secondDownloadPivotBlockHeaderState); + verify(fastSyncActions) + .createChainDownloader( + secondDownloadPivotBlockHeaderState, SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS); verify(worldStateDownloader) .run(any(FastSyncActions.class), eq(new FastSyncState(secondPivotBlockHeader))); verifyNoMoreInteractions(fastSyncActions, worldStateDownloader, storage); @@ -484,14 +567,20 @@ public void shouldResetFastSyncStateAndRestartProcessIfANonFastSyncExceptionOccu assertThat(result).isCompletedWithValue(secondDownloadPivotBlockHeaderState); } - @Test - public void shouldNotHaveTrailingPeerRequirementsBeforePivotBlockSelected() { + @ParameterizedTest + @ArgumentsSource(FastSyncDownloaderTestArguments.class) + public void shouldNotHaveTrailingPeerRequirementsBeforePivotBlockSelected( + final DataStorageFormat dataStorageFormat) { + setup(dataStorageFormat); downloader.start(); Assertions.assertThat(downloader.calculateTrailingPeerRequirements()).isEmpty(); } - @Test - public void shouldNotAllowPeersBeforePivotBlockOnceSelected() { + @ParameterizedTest + @ArgumentsSource(FastSyncDownloaderTestArguments.class) + public void shouldNotAllowPeersBeforePivotBlockOnceSelected( + final DataStorageFormat dataStorageFormat) { + setup(dataStorageFormat); final FastSyncState selectPivotBlockState = new FastSyncState(50); final BlockHeader pivotBlockHeader = new BlockHeaderTestFixture().number(50).buildHeader(); final FastSyncState downloadPivotBlockHeaderState = new FastSyncState(pivotBlockHeader); @@ -500,7 +589,8 @@ public void shouldNotAllowPeersBeforePivotBlockOnceSelected() { .thenReturn(completedFuture(selectPivotBlockState)); when(fastSyncActions.downloadPivotBlockHeader(selectPivotBlockState)) .thenReturn(completedFuture(downloadPivotBlockHeaderState)); - when(fastSyncActions.createChainDownloader(downloadPivotBlockHeaderState)) + when(fastSyncActions.createChainDownloader( + downloadPivotBlockHeaderState, SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS)) .thenReturn(chainDownloader); when(chainDownloader.start()).thenReturn(new CompletableFuture<>()); when(worldStateDownloader.run( @@ -512,8 +602,11 @@ public void shouldNotAllowPeersBeforePivotBlockOnceSelected() { .contains(new TrailingPeerRequirements(50, 0)); } - @Test - public void shouldNotHaveTrailingPeerRequirementsAfterDownloadCompletes() { + @ParameterizedTest + @ArgumentsSource(FastSyncDownloaderTestArguments.class) + public void shouldNotHaveTrailingPeerRequirementsAfterDownloadCompletes( + final DataStorageFormat dataStorageFormat) { + setup(dataStorageFormat); final FastSyncState selectPivotBlockState = new FastSyncState(50); final BlockHeader pivotBlockHeader = new BlockHeaderTestFixture().number(50).buildHeader(); final FastSyncState downloadPivotBlockHeaderState = new FastSyncState(pivotBlockHeader); @@ -522,7 +615,8 @@ public void shouldNotHaveTrailingPeerRequirementsAfterDownloadCompletes() { .thenReturn(completedFuture(selectPivotBlockState)); when(fastSyncActions.downloadPivotBlockHeader(selectPivotBlockState)) .thenReturn(completedFuture(downloadPivotBlockHeaderState)); - when(fastSyncActions.createChainDownloader(downloadPivotBlockHeaderState)) + when(fastSyncActions.createChainDownloader( + downloadPivotBlockHeaderState, SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS)) .thenReturn(chainDownloader); when(chainDownloader.start()).thenReturn(completedFuture(null)); when(worldStateDownloader.run( @@ -536,15 +630,22 @@ public void shouldNotHaveTrailingPeerRequirementsAfterDownloadCompletes() { } private void assertCompletedExceptionally( - final CompletableFuture future, final FastSyncError expectedError) { + final CompletableFuture future, final SyncError expectedError) { assertThat(future).isCompletedExceptionally(); future.exceptionally( actualError -> { assertThat(actualError) - .isInstanceOf(FastSyncException.class) - .extracting(ex -> ((FastSyncException) ex).getError()) + .isInstanceOf(SyncException.class) + .extracting(ex -> ((SyncException) ex).getError()) .isEqualTo(expectedError); return null; }); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/ImportBlocksStepTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/ImportBlocksStepTest.java new file mode 100644 index 00000000000..70c9e10eba6 --- /dev/null +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/ImportBlocksStepTest.java @@ -0,0 +1,114 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.sync.fastsync; + +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode.FULL; +import static org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode.LIGHT; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockDataGenerator; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockImporter; +import org.hyperledger.besu.ethereum.core.BlockWithReceipts; +import org.hyperledger.besu.ethereum.eth.sync.ValidationPolicy; +import org.hyperledger.besu.ethereum.eth.sync.tasks.exceptions.InvalidBlockException; +import org.hyperledger.besu.ethereum.mainnet.BlockImportResult; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; + +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class ImportBlocksStepTest { + + @Mock private ProtocolSchedule protocolSchedule; + @Mock private ProtocolSpec protocolSpec; + @Mock private ProtocolContext protocolContext; + @Mock private BlockImporter blockImporter; + @Mock private ValidationPolicy validationPolicy; + @Mock private ValidationPolicy ommerValidationPolicy; + @Mock private BlockHeader pivotHeader; + private final BlockDataGenerator gen = new BlockDataGenerator(); + + private ImportBlocksStep importBlocksStep; + + @BeforeEach + public void setUp() { + when(protocolSchedule.getByBlockHeader(any())).thenReturn(protocolSpec); + when(protocolSpec.getBlockImporter()).thenReturn(blockImporter); + when(validationPolicy.getValidationModeForNextBlock()).thenReturn(FULL); + when(ommerValidationPolicy.getValidationModeForNextBlock()).thenReturn(LIGHT); + + importBlocksStep = + new ImportBlocksStep( + protocolSchedule, + protocolContext, + validationPolicy, + ommerValidationPolicy, + null, + pivotHeader); + } + + @Test + public void shouldImportBlocks() { + final List blocks = gen.blockSequence(5); + final List blocksWithReceipts = + blocks.stream() + .map(block -> new BlockWithReceipts(block, gen.receipts(block))) + .collect(toList()); + + for (final BlockWithReceipts blockWithReceipts : blocksWithReceipts) { + when(blockImporter.fastImportBlock( + protocolContext, + blockWithReceipts.getBlock(), + blockWithReceipts.getReceipts(), + FULL, + LIGHT)) + .thenReturn(new BlockImportResult(true)); + } + importBlocksStep.accept(blocksWithReceipts); + + for (final BlockWithReceipts blockWithReceipts : blocksWithReceipts) { + verify(protocolSchedule).getByBlockHeader(blockWithReceipts.getHeader()); + } + verify(validationPolicy, times(blocks.size())).getValidationModeForNextBlock(); + } + + @Test + public void shouldThrowExceptionWhenValidationFails() { + final Block block = gen.block(); + final BlockWithReceipts blockWithReceipts = new BlockWithReceipts(block, gen.receipts(block)); + + when(blockImporter.fastImportBlock( + protocolContext, block, blockWithReceipts.getReceipts(), FULL, LIGHT)) + .thenReturn(new BlockImportResult(false)); + assertThatThrownBy(() -> importBlocksStep.accept(singletonList(blockWithReceipts))) + .isInstanceOf(InvalidBlockException.class); + } +} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockConfirmerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockConfirmerTest.java index 08ce7e9d37f..2a8a3110840 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockConfirmerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockConfirmerTest.java @@ -32,9 +32,9 @@ import org.hyperledger.besu.ethereum.eth.sync.fastsync.PivotBlockConfirmer.ContestedPivotBlockException; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -42,6 +42,7 @@ import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -341,4 +342,11 @@ private Responder responderForFakeBlocks(final long... blockNumbers) { return RespondingEthPeer.blockchainResponder(mockBlockchain); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockRetrieverTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockRetrieverTest.java index 7e539c8a596..a377ee78278 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockRetrieverTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/PivotBlockRetrieverTest.java @@ -34,9 +34,9 @@ import org.hyperledger.besu.ethereum.eth.peervalidation.PeerValidator; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.util.ExceptionUtils; import java.util.Optional; @@ -45,6 +45,7 @@ import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -288,15 +289,15 @@ public void shouldRecoverFromUnresponsivePeer(final DataStorageFormat storageFor final CompletableFuture future = pivotBlockRetriever.downloadPivotBlockHeader(); peerA.respond(responder); - peerB.respondTimes(emptyResponder, 2); + peerB.respondTimes(emptyResponder, 4); // PeerA should have responded, while peerB is being retried, peerC shouldn't have been queried // yet assertThat(future).isNotCompleted(); assertThat(peerC.hasOutstandingRequests()).isFalse(); - // After exhausting retries for peerB, we should try peerC - peerB.respondTimes(emptyResponder, 2); + // After exhausting retries (max retries is 5) for peerB, we should try peerC + peerB.respondTimes(emptyResponder, 1); peerC.respond(responder); assertThat(future) @@ -376,9 +377,9 @@ public void shouldRetryWhenPeersDisagreeOnPivot_exceedMaxRetries( assertThat(future).isCompletedExceptionally(); assertThatThrownBy(future::get) - .hasRootCauseInstanceOf(FastSyncException.class) - .extracting(e -> ((FastSyncException) ExceptionUtils.rootCause(e)).getError()) - .isEqualTo(FastSyncError.PIVOT_BLOCK_HEADER_MISMATCH); + .hasRootCauseInstanceOf(SyncException.class) + .extracting(e -> ((SyncException) ExceptionUtils.rootCause(e)).getError()) + .isEqualTo(SyncError.PIVOT_BLOCK_HEADER_MISMATCH); } @ParameterizedTest @@ -406,9 +407,9 @@ public void shouldRetryWhenPeersDisagreeOnPivot_pivotInvalidOnRetry( assertThat(future).isCompletedExceptionally(); assertThatThrownBy(future::get) - .hasRootCauseInstanceOf(FastSyncException.class) - .extracting(e -> ((FastSyncException) ExceptionUtils.rootCause(e)).getError()) - .isEqualTo(FastSyncError.PIVOT_BLOCK_HEADER_MISMATCH); + .hasRootCauseInstanceOf(SyncException.class) + .extracting(e -> ((SyncException) ExceptionUtils.rootCause(e)).getError()) + .isEqualTo(SyncError.PIVOT_BLOCK_HEADER_MISMATCH); } private Responder responderForFakeBlocks(final long... blockNumbers) { @@ -425,4 +426,11 @@ private Responder responderForFakeBlocks(final long... blockNumbers) { return RespondingEthPeer.blockchainResponder(mockBlockchain); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastWorldDownloadStateTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastWorldDownloadStateTest.java index b8531288276..e3d2a37cd0c 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastWorldDownloadStateTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastWorldDownloadStateTest.java @@ -20,17 +20,20 @@ import static org.mockito.Mockito.verify; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; import org.hyperledger.besu.ethereum.eth.manager.task.EthTask; import org.hyperledger.besu.ethereum.eth.sync.worldstate.StalledDownloadException; import org.hyperledger.besu.ethereum.eth.sync.worldstate.WorldStateDownloadProcess; -import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import org.hyperledger.besu.services.tasks.InMemoryTasksPriorityQueues; import org.hyperledger.besu.testutil.TestClock; @@ -41,6 +44,7 @@ import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -54,7 +58,9 @@ public class FastWorldDownloadStateTest { private static final int MAX_REQUESTS_WITHOUT_PROGRESS = 10; private static final long MIN_MILLIS_BEFORE_STALLING = 50_000; - private WorldStateStorage worldStateStorage; + private WorldStateKeyValueStorage worldStateKeyValueStorage; + + private WorldStateStorageCoordinator worldStateStorageCoordinator; private final BlockHeader header = new BlockHeaderTestFixture().stateRoot(ROOT_NODE_HASH).buildHeader(); @@ -78,19 +84,25 @@ public Stream provideArguments(final ExtensionContext conte public void setUp(final DataStorageFormat storageFormat) { if (storageFormat == DataStorageFormat.BONSAI) { - worldStateStorage = + worldStateKeyValueStorage = new BonsaiWorldStateKeyValueStorage( - new InMemoryKeyValueStorageProvider(), new NoOpMetricsSystem()); + new InMemoryKeyValueStorageProvider(), + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG); } else { - worldStateStorage = new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + worldStateKeyValueStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); } + worldStateStorageCoordinator = new WorldStateStorageCoordinator(worldStateKeyValueStorage); + downloadState = new FastWorldDownloadState( - worldStateStorage, + worldStateStorageCoordinator, pendingRequests, MAX_REQUESTS_WITHOUT_PROGRESS, MIN_MILLIS_BEFORE_STALLING, - clock); + clock, + SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS); assertThat(downloadState.isDownloading()).isTrue(); downloadState.setRootNodeData(ROOT_NODE_DATA); future = downloadState.getDownloadFuture(); @@ -115,7 +127,9 @@ public void shouldStoreRootNodeBeforeReturnedFutureCompletes( final CompletableFuture postFutureChecks = future.thenAccept( result -> - assertThat(worldStateStorage.getAccountStateTrieNode(Bytes.EMPTY, ROOT_NODE_HASH)) + assertThat( + worldStateStorageCoordinator.getAccountStateTrieNode( + Bytes.EMPTY, ROOT_NODE_HASH)) .contains(ROOT_NODE_DATA)); downloadState.checkCompletion(header); @@ -134,7 +148,8 @@ public void shouldNotCompleteWhenThereArePendingTasks(final DataStorageFormat st downloadState.checkCompletion(header); assertThat(future).isNotDone(); - assertThat(worldStateStorage.getAccountStateTrieNode(Bytes.EMPTY, ROOT_NODE_HASH)).isEmpty(); + assertThat(worldStateStorageCoordinator.getAccountStateTrieNode(Bytes.EMPTY, ROOT_NODE_HASH)) + .isEmpty(); assertThat(downloadState.isDownloading()).isTrue(); } @@ -253,4 +268,11 @@ private void assertWorldStateStalled(final FastWorldDownloadState state) { .isInstanceOf(ExecutionException.class) .hasRootCauseInstanceOf(StalledDownloadException.class); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastWorldStateDownloaderTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastWorldStateDownloaderTest.java index 6d352abb7a8..75fe892aebd 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastWorldStateDownloaderTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/FastWorldStateDownloaderTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -48,22 +48,23 @@ import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; import org.hyperledger.besu.ethereum.rlp.RLP; -import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStatePreimageKeyValueStorage; import org.hyperledger.besu.ethereum.trie.MerkleTrie; import org.hyperledger.besu.ethereum.trie.Node; +import org.hyperledger.besu.ethereum.trie.forest.ForestWorldStateArchive; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; import org.hyperledger.besu.ethereum.trie.patricia.TrieNodeDecoder; -import org.hyperledger.besu.ethereum.worldstate.DefaultWorldStateArchive; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.worldstate.WorldStatePreimageStorage; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage.Updater; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.account.AccountStorageEntry; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.worldstate.WorldState; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import org.hyperledger.besu.services.tasks.InMemoryTasksPriorityQueues; @@ -178,8 +179,8 @@ void downloadEmptyWorldState() { final InMemoryTasksPriorityQueues taskCollection = new InMemoryTasksPriorityQueues<>(); - final WorldStateStorage localStorage = - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final ForestWorldStateKeyValueStorage localStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); final WorldStateDownloader downloader = createDownloader(ethProtocolManager.ethContext(), localStorage, taskCollection); @@ -196,7 +197,7 @@ void downloadEmptyWorldState() { @Timeout(value = 60) void downloadAlreadyAvailableWorldState() { // Setup existing state - final DefaultWorldStateArchive worldStateArchive = createInMemoryWorldStateArchive(); + final ForestWorldStateArchive worldStateArchive = createInMemoryWorldStateArchive(); final MutableWorldState worldState = worldStateArchive.getMutable(); // Generate accounts and save corresponding state root @@ -263,8 +264,8 @@ void canRecoverFromTimeouts() { final InMemoryTasksPriorityQueues taskCollection = new InMemoryTasksPriorityQueues<>(); - final WorldStateStorage localStorage = - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final ForestWorldStateKeyValueStorage localStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); final WorldStateDownloader downloader = createDownloader(ethProtocolManager.ethContext(), localStorage, taskCollection); @@ -282,8 +283,10 @@ void canRecoverFromTimeouts() { // Check that all expected account data was downloaded final WorldStateArchive localWorldStateArchive = - new DefaultWorldStateArchive( - localStorage, createPreimageStorage(), EvmConfiguration.DEFAULT); + new ForestWorldStateArchive( + new WorldStateStorageCoordinator(localStorage), + createPreimageStorage(), + EvmConfiguration.DEFAULT); final WorldState localWorldState = localWorldStateArchive.get(stateRoot, null).get(); assertThat(result).isDone(); assertAccountsMatch(localWorldState, accounts); @@ -318,14 +321,14 @@ void doesNotRequestKnownCodeFromNetwork() { final InMemoryTasksPriorityQueues taskCollection = new InMemoryTasksPriorityQueues<>(); - final WorldStateStorage localStorage = - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final ForestWorldStateKeyValueStorage localStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); // Seed local storage with some contract values final Map knownCode = new HashMap<>(); accounts.subList(0, 5).forEach(a -> knownCode.put(a.getCodeHash(), a.getCode())); - final Updater localStorageUpdater = localStorage.updater(); - knownCode.forEach((bytes32, code) -> localStorageUpdater.putCode(null, code)); + final ForestWorldStateKeyValueStorage.Updater localStorageUpdater = localStorage.updater(); + knownCode.forEach((bytes32, code) -> localStorageUpdater.putCode(code)); localStorageUpdater.commit(); final WorldStateDownloader downloader = @@ -356,8 +359,10 @@ void doesNotRequestKnownCodeFromNetwork() { // Check that all expected account data was downloaded final WorldStateArchive localWorldStateArchive = - new DefaultWorldStateArchive( - localStorage, createPreimageStorage(), EvmConfiguration.DEFAULT); + new ForestWorldStateArchive( + new WorldStateStorageCoordinator(localStorage), + createPreimageStorage(), + EvmConfiguration.DEFAULT); final WorldState localWorldState = localWorldStateArchive.get(stateRoot, null).get(); assertThat(result).isDone(); assertAccountsMatch(localWorldState, accounts); @@ -403,8 +408,8 @@ private void testCancellation(final boolean shouldCancelFuture) { final InMemoryTasksPriorityQueues taskCollection = new InMemoryTasksPriorityQueues<>(); - final WorldStateStorage localStorage = - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final ForestWorldStateKeyValueStorage localStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); final WorldStateDownloader downloader = createDownloader(ethProtocolManager.ethContext(), localStorage, taskCollection); @@ -449,19 +454,20 @@ private void testCancellation(final boolean shouldCancelFuture) { verify(taskCollection, never()).remove(); verify(taskCollection, never()).add(any(NodeDataRequest.class)); // Target world state should not be available - assertThat(localStorage.isWorldStateAvailable(header.getStateRoot(), header.getHash())) - .isFalse(); + assertThat(localStorage.isWorldStateAvailable(header.getStateRoot())).isFalse(); } @Test @Timeout(value = 60) void doesNotRequestKnownAccountTrieNodesFromNetwork() { // Setup "remote" state - final WorldStateStorage remoteStorage = - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final ForestWorldStateKeyValueStorage remoteStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); final WorldStateArchive remoteWorldStateArchive = - new DefaultWorldStateArchive( - remoteStorage, createPreimageStorage(), EvmConfiguration.DEFAULT); + new ForestWorldStateArchive( + new WorldStateStorageCoordinator(remoteStorage), + createPreimageStorage(), + EvmConfiguration.DEFAULT); final MutableWorldState remoteWorldState = remoteWorldStateArchive.getMutable(); // Generate accounts and save corresponding state root @@ -480,8 +486,8 @@ void doesNotRequestKnownAccountTrieNodesFromNetwork() { final InMemoryTasksPriorityQueues taskCollection = new InMemoryTasksPriorityQueues<>(); - final WorldStateStorage localStorage = - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final ForestWorldStateKeyValueStorage localStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); // Seed local storage with some trie node values final Map allNodes = @@ -489,12 +495,12 @@ void doesNotRequestKnownAccountTrieNodesFromNetwork() { final Set knownNodes = new HashSet<>(); final Set unknownNodes = new HashSet<>(); assertThat(allNodes).isNotEmpty(); // Sanity check - final Updater localStorageUpdater = localStorage.updater(); + final ForestWorldStateKeyValueStorage.Updater localStorageUpdater = localStorage.updater(); final AtomicBoolean storeNode = new AtomicBoolean(true); allNodes.forEach( (nodeHash, node) -> { if (storeNode.get()) { - localStorageUpdater.putAccountStateTrieNode(null, nodeHash, node); + localStorageUpdater.putAccountStateTrieNode(nodeHash, node); knownNodes.add(nodeHash); } else { unknownNodes.add(nodeHash); @@ -533,8 +539,10 @@ void doesNotRequestKnownAccountTrieNodesFromNetwork() { // Check that all expected account data was downloaded final WorldStateArchive localWorldStateArchive = - new DefaultWorldStateArchive( - localStorage, createPreimageStorage(), EvmConfiguration.DEFAULT); + new ForestWorldStateArchive( + new WorldStateStorageCoordinator(localStorage), + createPreimageStorage(), + EvmConfiguration.DEFAULT); final WorldState localWorldState = localWorldStateArchive.get(stateRoot, null).get(); assertThat(result).isDone(); assertAccountsMatch(localWorldState, accounts); @@ -544,11 +552,13 @@ void doesNotRequestKnownAccountTrieNodesFromNetwork() { @Timeout(value = 60) void doesNotRequestKnownStorageTrieNodesFromNetwork() { // Setup "remote" state - final WorldStateStorage remoteStorage = - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final ForestWorldStateKeyValueStorage remoteStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); final WorldStateArchive remoteWorldStateArchive = - new DefaultWorldStateArchive( - remoteStorage, createPreimageStorage(), EvmConfiguration.DEFAULT); + new ForestWorldStateArchive( + new WorldStateStorageCoordinator(remoteStorage), + createPreimageStorage(), + EvmConfiguration.DEFAULT); final MutableWorldState remoteWorldState = remoteWorldStateArchive.getMutable(); // Generate accounts and save corresponding state root @@ -567,13 +577,13 @@ void doesNotRequestKnownStorageTrieNodesFromNetwork() { final InMemoryTasksPriorityQueues taskCollection = new InMemoryTasksPriorityQueues<>(); - final WorldStateStorage localStorage = - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final ForestWorldStateKeyValueStorage localStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); // Seed local storage with some trie node values final List storageRootHashes = new StoredMerklePatriciaTrie<>( - remoteStorage::getNodeData, + (location, hash) -> remoteStorage.getNodeData(hash), remoteWorldState.rootHash(), Function.identity(), Function.identity()) @@ -590,13 +600,13 @@ void doesNotRequestKnownStorageTrieNodesFromNetwork() { collectTrieNodesToBeRequestedAfterRoot(remoteStorage, storageRootHash, 5)); } assertThat(allTrieNodes).isNotEmpty(); // Sanity check - final Updater localStorageUpdater = localStorage.updater(); + final ForestWorldStateKeyValueStorage.Updater localStorageUpdater = localStorage.updater(); boolean storeNode = true; for (final Map.Entry entry : allTrieNodes.entrySet()) { final Bytes32 hash = entry.getKey(); final Bytes data = entry.getValue(); if (storeNode) { - localStorageUpdater.putAccountStorageTrieNode(null, null, hash, data); + localStorageUpdater.putAccountStorageTrieNode(hash, data); knownNodes.add(hash); } else { unknownNodes.add(hash); @@ -622,7 +632,7 @@ void doesNotRequestKnownStorageTrieNodesFromNetwork() { respondUntilDone(peers, responder, result); // World state should be available by the time the result is complete - assertThat(localStorage.isWorldStateAvailable(stateRoot, header.getHash())).isTrue(); + assertThat(localStorage.isWorldStateAvailable(stateRoot)).isTrue(); // Check that unknown trie nodes were requested final List requestedHashes = @@ -638,8 +648,10 @@ void doesNotRequestKnownStorageTrieNodesFromNetwork() { // Check that all expected account data was downloaded final WorldStateArchive localWorldStateArchive = - new DefaultWorldStateArchive( - localStorage, createPreimageStorage(), EvmConfiguration.DEFAULT); + new ForestWorldStateArchive( + new WorldStateStorageCoordinator(localStorage), + createPreimageStorage(), + EvmConfiguration.DEFAULT); final WorldState localWorldState = localWorldStateArchive.get(stateRoot, null).get(); assertThat(result).isDone(); assertAccountsMatch(localWorldState, accounts); @@ -652,11 +664,13 @@ void stalledDownloader() { EthProtocolManagerTestUtil.create(new EthScheduler(1, 1, 1, 1, new NoOpMetricsSystem())); // Setup "remote" state - final WorldStateStorage remoteStorage = - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final ForestWorldStateKeyValueStorage remoteStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); final WorldStateArchive remoteWorldStateArchive = - new DefaultWorldStateArchive( - remoteStorage, createPreimageStorage(), EvmConfiguration.DEFAULT); + new ForestWorldStateArchive( + new WorldStateStorageCoordinator(remoteStorage), + createPreimageStorage(), + EvmConfiguration.DEFAULT); final MutableWorldState remoteWorldState = remoteWorldStateArchive.getMutable(); // Generate accounts and save corresponding state root @@ -668,8 +682,8 @@ void stalledDownloader() { final InMemoryTasksPriorityQueues taskCollection = new InMemoryTasksPriorityQueues<>(); - final WorldStateStorage localStorage = - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final ForestWorldStateKeyValueStorage localStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); final SynchronizerConfiguration syncConfig = SynchronizerConfiguration.builder().worldStateMaxRequestsWithoutProgress(10).build(); final WorldStateDownloader downloader = @@ -714,11 +728,13 @@ void stalledDownloader() { @Timeout(value = 60) void resumesFromNonEmptyQueue() { // Setup "remote" state - final WorldStateStorage remoteStorage = - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final ForestWorldStateKeyValueStorage remoteStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); final WorldStateArchive remoteWorldStateArchive = - new DefaultWorldStateArchive( - remoteStorage, createPreimageStorage(), EvmConfiguration.DEFAULT); + new ForestWorldStateArchive( + new WorldStateStorageCoordinator(remoteStorage), + createPreimageStorage(), + EvmConfiguration.DEFAULT); final MutableWorldState remoteWorldState = remoteWorldStateArchive.getMutable(); // Generate accounts and save corresponding state root @@ -742,8 +758,8 @@ void resumesFromNonEmptyQueue() { verify(taskCollection, times(1)).add(argThat((r) -> r.getHash().equals(hash))); } - final WorldStateStorage localStorage = - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final ForestWorldStateKeyValueStorage localStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); final SynchronizerConfiguration syncConfig = SynchronizerConfiguration.builder().worldStateMaxRequestsWithoutProgress(10).build(); final WorldStateDownloader downloader = @@ -762,7 +778,7 @@ void resumesFromNonEmptyQueue() { CompletableFuture result = downloader.run(null, new FastSyncState(header)); peer.respondWhileOtherThreadsWork(responder, () -> !result.isDone()); - assertThat(localStorage.isWorldStateAvailable(stateRoot, header.getHash())).isTrue(); + assertThat(localStorage.isWorldStateAvailable(stateRoot)).isTrue(); // Check that already enqueued trie nodes were requested final List requestedHashes = @@ -782,8 +798,10 @@ void resumesFromNonEmptyQueue() { // Check that all expected account data was downloaded assertThat(result).isDone(); final WorldStateArchive localWorldStateArchive = - new DefaultWorldStateArchive( - localStorage, createPreimageStorage(), EvmConfiguration.DEFAULT); + new ForestWorldStateArchive( + new WorldStateStorageCoordinator(localStorage), + createPreimageStorage(), + EvmConfiguration.DEFAULT); final WorldState localWorldState = localWorldStateArchive.get(stateRoot, null).get(); assertAccountsMatch(localWorldState, accounts); } @@ -800,10 +818,10 @@ void resumesFromNonEmptyQueue() { * @return A list of hash-node pairs */ private Map collectTrieNodesToBeRequestedAfterRoot( - final WorldStateStorage storage, final Bytes32 rootHash, final int maxNodes) { + final ForestWorldStateKeyValueStorage storage, final Bytes32 rootHash, final int maxNodes) { final Map trieNodes = new HashMap<>(); - TrieNodeDecoder.breadthFirstDecoder(storage::getNodeData, rootHash) + TrieNodeDecoder.breadthFirstDecoder((location, hash) -> storage.getNodeData(hash), rootHash) .filter(n -> !Objects.equals(n.getHash(), rootHash)) .filter(Node::isReferencedByHash) .limit(maxNodes) @@ -823,10 +841,10 @@ private Map collectTrieNodesToBeRequestedAfterRoot( * @return A list of node hashes */ private List getFirstSetOfChildNodeRequests( - final WorldStateStorage storage, final Bytes32 rootHash) { + final ForestWorldStateKeyValueStorage storage, final Bytes32 rootHash) { final List hashesToRequest = new ArrayList<>(); - final Bytes rootNodeRlp = storage.getNodeData(Bytes.EMPTY, rootHash).get(); + final Bytes rootNodeRlp = storage.getNodeData(rootHash).get(); TrieNodeDecoder.decodeNodes(Bytes.EMPTY, rootNodeRlp).stream() .filter(n -> !Objects.equals(n.getHash(), rootHash)) .filter(Node::isReferencedByHash) @@ -853,11 +871,13 @@ private void downloadAvailableWorldStateFromPeers( final int trailingPeerCount = 5; // Setup "remote" state - final WorldStateStorage remoteStorage = - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final ForestWorldStateKeyValueStorage remoteStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); final WorldStateArchive remoteWorldStateArchive = - new DefaultWorldStateArchive( - remoteStorage, createPreimageStorage(), EvmConfiguration.DEFAULT); + new ForestWorldStateArchive( + new WorldStateStorageCoordinator(remoteStorage), + createPreimageStorage(), + EvmConfiguration.DEFAULT); final MutableWorldState remoteWorldState = remoteWorldStateArchive.getMutable(); // Generate accounts and save corresponding state root @@ -878,11 +898,13 @@ private void downloadAvailableWorldStateFromPeers( final InMemoryTasksPriorityQueues taskCollection = new InMemoryTasksPriorityQueues<>(); - final WorldStateStorage localStorage = - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final ForestWorldStateKeyValueStorage localStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); final WorldStateArchive localWorldStateArchive = - new DefaultWorldStateArchive( - localStorage, createPreimageStorage(), EvmConfiguration.DEFAULT); + new ForestWorldStateArchive( + new WorldStateStorageCoordinator(localStorage), + createPreimageStorage(), + EvmConfiguration.DEFAULT); final SynchronizerConfiguration syncConfig = SynchronizerConfiguration.builder() .worldStateHashCountPerRequest(hashesPerRequest) @@ -1010,7 +1032,7 @@ private void assertAccountsMatch( private WorldStateDownloader createDownloader( final EthContext context, - final WorldStateStorage storage, + final WorldStateKeyValueStorage storage, final InMemoryTasksPriorityQueues taskCollection) { return createDownloader( SynchronizerConfiguration.builder().build(), context, storage, taskCollection); @@ -1019,18 +1041,19 @@ private WorldStateDownloader createDownloader( private WorldStateDownloader createDownloader( final SynchronizerConfiguration config, final EthContext context, - final WorldStateStorage storage, + final WorldStateKeyValueStorage storage, final InMemoryTasksPriorityQueues taskCollection) { return new FastWorldStateDownloader( context, - storage, + new WorldStateStorageCoordinator(storage), taskCollection, config.getWorldStateHashCountPerRequest(), config.getWorldStateRequestParallelism(), config.getWorldStateMaxRequestsWithoutProgress(), config.getWorldStateMinMillisBeforeStalling(), TestClock.fixed(), - new NoOpMetricsSystem()); + new NoOpMetricsSystem(), + SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS); } private WorldStatePreimageStorage createPreimageStorage() { diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/LoadLocalDataStepTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/LoadLocalDataStepTest.java index 625b3f72eaa..582bdff6335 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/LoadLocalDataStepTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/LoadLocalDataStepTest.java @@ -22,8 +22,10 @@ import static org.mockito.Mockito.when; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.services.pipeline.Pipe; import org.hyperledger.besu.services.tasks.Task; @@ -31,6 +33,7 @@ import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -38,17 +41,25 @@ public class LoadLocalDataStepTest { private static final Bytes DATA = Bytes.of(1, 2, 3); private static final Hash HASH = Hash.hash(DATA); - private final WorldStateStorage worldStateStorage = mock(WorldStateStorage.class); - private final WorldStateStorage.Updater updater = mock(WorldStateStorage.Updater.class); + private final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage = + mock(BonsaiWorldStateKeyValueStorage.class); + private final BonsaiWorldStateKeyValueStorage.Updater updater = + mock(BonsaiWorldStateKeyValueStorage.Updater.class); private final CodeNodeDataRequest request = NodeDataRequest.createCodeRequest(HASH, Optional.empty()); private final Task task = new StubTask(request); private final Pipe> completedTasks = - new Pipe<>(10, NO_OP_COUNTER, NO_OP_COUNTER, NO_OP_COUNTER); + new Pipe<>(10, NO_OP_COUNTER, NO_OP_COUNTER, NO_OP_COUNTER, "test_pipe"); private final LoadLocalDataStep loadLocalDataStep = - new LoadLocalDataStep(worldStateStorage, new NoOpMetricsSystem()); + new LoadLocalDataStep( + new WorldStateStorageCoordinator(worldStateKeyValueStorage), new NoOpMetricsSystem()); + + @BeforeEach + public void setup() { + when(worldStateKeyValueStorage.getDataStorageFormat()).thenReturn(DataStorageFormat.BONSAI); + } @Test public void shouldReturnStreamWithUnchangedTaskWhenDataNotPresent() { @@ -61,8 +72,8 @@ public void shouldReturnStreamWithUnchangedTaskWhenDataNotPresent() { @Test public void shouldReturnEmptyStreamAndSendTaskToCompletedPipeWhenDataIsPresent() { - when(worldStateStorage.getCode(HASH, Hash.EMPTY)).thenReturn(Optional.of(DATA)); - when(worldStateStorage.updater()).thenReturn(updater); + when(worldStateKeyValueStorage.getCode(HASH, Hash.EMPTY)).thenReturn(Optional.of(DATA)); + when(worldStateKeyValueStorage.updater()).thenReturn(updater); final Stream> output = loadLocalDataStep.loadLocalData(task, completedTasks); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/PersistDataStepTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/PersistDataStepTest.java index e857648f7fc..e103b9e79b4 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/PersistDataStepTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fastsync/worldstate/PersistDataStepTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -23,11 +23,11 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; -import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; import org.hyperledger.besu.ethereum.trie.MerkleTrie; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.trie.patricia.SimpleMerklePatriciaTrie; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import org.hyperledger.besu.services.tasks.Task; import java.nio.charset.StandardCharsets; @@ -39,15 +39,18 @@ public class PersistDataStepTest { - private final WorldStateStorage worldStateStorage = - new InMemoryKeyValueStorageProvider().createWorldStateStorage(DataStorageFormat.FOREST); + final ForestWorldStateKeyValueStorage worldStateKeyValueStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + private final WorldStateStorageCoordinator worldStateStorageCoordinator = + new WorldStateStorageCoordinator(worldStateKeyValueStorage); + private final FastWorldDownloadState downloadState = mock(FastWorldDownloadState.class); private final Bytes rootNodeData = Bytes.of(1, 1, 1, 1); private final BlockHeader blockHeader = new BlockHeaderTestFixture().stateRoot(Hash.hash(rootNodeData)).buildHeader(); - private final PersistDataStep persistDataStep = new PersistDataStep(worldStateStorage); + private final PersistDataStep persistDataStep = new PersistDataStep(worldStateStorageCoordinator); @Test public void shouldPersistDataWhenPresentWithoutChildren() { @@ -75,8 +78,8 @@ public void shouldSkipPersistingTasksWithNoData() { persistDataStep.persist(tasks, blockHeader, downloadState); assertThat(result).isSameAs(tasks); - assertThat(worldStateStorage.contains(withData.getData().getHash())).isTrue(); - assertThat(worldStateStorage.contains(withoutData.getData().getHash())).isFalse(); + assertThat(worldStateKeyValueStorage.contains(withData.getData().getHash())).isTrue(); + assertThat(worldStateKeyValueStorage.contains(withoutData.getData().getHash())).isFalse(); } @Test @@ -87,7 +90,7 @@ public void shouldStoreRootNodeDataInDownloadStateInsteadOfPersisting() { persistDataStep.persist(tasks, blockHeader, downloadState); assertThat(result).isSameAs(tasks); - assertThat(worldStateStorage.contains(rootNode.getData().getHash())).isFalse(); + assertThat(worldStateKeyValueStorage.contains(rootNode.getData().getHash())).isFalse(); verify(downloadState).setRootNodeData(rootNode.getData().getData()); } @@ -119,7 +122,7 @@ private StubTask createTaskWithoutData(final Bytes data) { private void assertDataPersisted(final List> tasks) { tasks.forEach( task -> - assertThat(worldStateStorage.getNodeData(null, task.getData().getHash())) + assertThat(worldStateKeyValueStorage.getNodeData(task.getData().getHash())) .isEqualTo(Optional.of(task.getData().getData()))); } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/BetterSyncTargetEvaluatorTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/BetterSyncTargetEvaluatorTest.java index 9acf45965d6..0bc98aaa9ad 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/BetterSyncTargetEvaluatorTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/BetterSyncTargetEvaluatorTest.java @@ -49,7 +49,7 @@ public class BetterSyncTargetEvaluatorTest { @BeforeEach public void setupMocks() { - when(ethPeers.getBestChainComparator()).thenReturn(EthPeers.HEAVIEST_CHAIN); + when(ethPeers.getBestPeerComparator()).thenReturn(EthPeers.HEAVIEST_CHAIN); } @Test diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncChainDownloaderForkTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncChainDownloaderForkTest.java index 00a0e6d628f..d7b5970098e 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncChainDownloaderForkTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncChainDownloaderForkTest.java @@ -32,6 +32,7 @@ import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; @@ -89,7 +90,8 @@ private ChainDownloader downloader(final SynchronizerConfiguration syncConfig) { ethContext, syncState, metricsSystem, - SyncTerminationCondition.never()); + SyncTerminationCondition.never(), + SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS); } private ChainDownloader downloader() { @@ -127,7 +129,7 @@ public void disconnectsFromPeerOnBadFork() { // We should have disconnected from our peer on the invalid chain assertThat(peer.getEthPeer().isDisconnected()).isTrue(); assertThat(peer.getPeerConnection().getDisconnectReason()) - .contains(DisconnectReason.BREACH_OF_PROTOCOL); + .contains(DisconnectReason.BREACH_OF_PROTOCOL_INVALID_BLOCK); assertThat(syncState.syncTarget()).isEmpty(); } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncChainDownloaderTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncChainDownloaderTest.java index b7588d1b4f9..ac7f0fb8257 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncChainDownloaderTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncChainDownloaderTest.java @@ -42,9 +42,10 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import java.util.ArrayList; import java.util.List; @@ -55,6 +56,7 @@ import org.awaitility.Awaitility; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -107,7 +109,9 @@ public void setupTest(final DataStorageFormat storageFormat) { @AfterEach public void tearDown() { - ethProtocolManager.stop(); + if (ethProtocolManager != null) { + ethProtocolManager.stop(); + } } private ChainDownloader downloader(final SynchronizerConfiguration syncConfig) { @@ -118,7 +122,8 @@ private ChainDownloader downloader(final SynchronizerConfiguration syncConfig) { ethContext, syncState, metricsSystem, - SyncTerminationCondition.never()); + SyncTerminationCondition.never(), + SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS); } private ChainDownloader downloader() { @@ -499,4 +504,11 @@ private MutableBlockchain createShortChain( } return shortChain; } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncChainDownloaderTotalTerminalDifficultyTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncChainDownloaderTotalTerminalDifficultyTest.java index c86be6d6407..311ccf5de30 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncChainDownloaderTotalTerminalDifficultyTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncChainDownloaderTotalTerminalDifficultyTest.java @@ -31,14 +31,16 @@ import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import java.util.concurrent.CompletableFuture; import java.util.stream.Stream; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -91,7 +93,9 @@ public void setupTest(final DataStorageFormat storageFormat) { @AfterEach public void tearDown() { - ethProtocolManager.stop(); + if (ethProtocolManager != null) { + ethProtocolManager.stop(); + } } private ChainDownloader downloader( @@ -104,7 +108,8 @@ private ChainDownloader downloader( ethContext, syncState, metricsSystem, - terminalCondition); + terminalCondition, + SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS); } private SynchronizerConfiguration.Builder syncConfigBuilder() { @@ -179,4 +184,11 @@ public void syncsFullyAndContinuesWhenTTDNotSpecified(final DataStorageFormat st assertThat(future.isDone()).isFalse(); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncDownloaderTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncDownloaderTest.java index aff0195db2d..63e41f6d25c 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncDownloaderTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncDownloaderTest.java @@ -29,13 +29,15 @@ import org.hyperledger.besu.ethereum.eth.sync.TrailingPeerRequirements; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import java.util.stream.Stream; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -82,7 +84,9 @@ public void setupTest(final DataStorageFormat storageFormat) { @AfterEach public void tearDown() { - ethProtocolManager.stop(); + if (ethProtocolManager != null) { + ethProtocolManager.stop(); + } } private FullSyncDownloader downloader(final SynchronizerConfiguration syncConfig) { @@ -93,7 +97,8 @@ private FullSyncDownloader downloader(final SynchronizerConfiguration syncConfig ethContext, syncState, metricsSystem, - SyncTerminationCondition.never()); + SyncTerminationCondition.never(), + SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS); } @ParameterizedTest @@ -129,4 +134,11 @@ public void shouldNotLimitTrailingPeersWhenInSync(final DataStorageFormat storag assertThat(synchronizer.calculateTrailingPeerRequirements()) .isEqualTo(TrailingPeerRequirements.UNRESTRICTED); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncTargetManagerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncTargetManagerTest.java index 790e17998a3..027bd270d29 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncTargetManagerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/fullsync/FullSyncTargetManagerTest.java @@ -19,6 +19,7 @@ import static org.mockito.Mockito.when; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.BlockHeader; @@ -34,15 +35,15 @@ import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; import org.hyperledger.besu.ethereum.eth.sync.state.SyncTarget; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; -import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.stream.Stream; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -76,7 +77,7 @@ public void setup(final DataStorageFormat storageFormat) { final ProtocolSchedule protocolSchedule = ProtocolScheduleFixture.MAINNET; final ProtocolContext protocolContext = - new ProtocolContext(localBlockchain, localWorldState, null, Optional.empty()); + new ProtocolContext(localBlockchain, localWorldState, null, new BadBlockManager()); ethProtocolManager = EthProtocolManagerTestUtil.create( protocolSchedule, @@ -100,7 +101,9 @@ public void setup(final DataStorageFormat storageFormat) { @AfterEach public void tearDown() { - ethProtocolManager.stop(); + if (ethProtocolManager != null) { + ethProtocolManager.stop(); + } } @ParameterizedTest @@ -180,4 +183,11 @@ public void shouldAllowSyncTargetWhenIfWorldStateIsAvailableForCommonAncestor( new SyncTarget(bestPeer.getEthPeer(), localBlockchain.getChainHeadHeader())); assertThat(bestPeer.getPeerConnection().isDisconnected()).isFalse(); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/AccountHealingTrackingTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/AccountHealingTrackingTest.java index ca39d0c86fe..70bdbf8b801 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/AccountHealingTrackingTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/AccountHealingTrackingTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,14 +14,13 @@ */ package org.hyperledger.besu.ethereum.eth.sync.snapsync; -import static org.hyperledger.besu.ethereum.eth.sync.snapsync.RangeManager.MAX_RANGE; +import static org.hyperledger.besu.ethereum.trie.RangeManager.MAX_RANGE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; import org.hyperledger.besu.ethereum.core.TrieGenerator; import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.SnapDataRequest; @@ -32,10 +31,12 @@ import org.hyperledger.besu.ethereum.trie.MerkleTrie; import org.hyperledger.besu.ethereum.trie.RangeStorageEntriesCollector; import org.hyperledger.besu.ethereum.trie.TrieIterator; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; import org.hyperledger.besu.ethereum.trie.patricia.StoredNodeFactory; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import java.util.List; @@ -56,9 +57,13 @@ public class AccountHealingTrackingTest { private final List

accounts = List.of(Address.fromHexString("0xdeadbeef")); - private final WorldStateStorage worldStateStorage = + private final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage = new BonsaiWorldStateKeyValueStorage( - new InMemoryKeyValueStorageProvider(), new NoOpMetricsSystem()); + new InMemoryKeyValueStorageProvider(), + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG); + + private WorldStateStorageCoordinator worldStateStorageCoordinator; private WorldStateProofProvider worldStateProofProvider; @@ -68,11 +73,12 @@ public class AccountHealingTrackingTest { @BeforeEach public void setup() { + worldStateStorageCoordinator = new WorldStateStorageCoordinator(worldStateKeyValueStorage); accountStateTrie = TrieGenerator.generateTrie( - worldStateStorage, + worldStateStorageCoordinator, accounts.stream().map(Address::addressHash).collect(Collectors.toList())); - worldStateProofProvider = new WorldStateProofProvider(worldStateStorage); + worldStateProofProvider = new WorldStateProofProvider(worldStateStorageCoordinator); } @Test @@ -87,7 +93,8 @@ void avoidMarkingAccountWhenStorageProofValid() { new StoredMerklePatriciaTrie<>( new StoredNodeFactory<>( (location, hash) -> - worldStateStorage.getAccountStorageTrieNode(accountHash, location, hash), + worldStateKeyValueStorage.getAccountStorageTrieNode( + accountHash, location, hash), Function.identity(), Function.identity()), stateTrieAccountValue.getStorageRoot()); @@ -118,7 +125,8 @@ void avoidMarkingAccountWhenStorageProofValid() { MAX_RANGE); storageRangeDataRequest.addResponse( snapWorldDownloadState, worldStateProofProvider, slots, new ArrayDeque<>(proofs)); - storageRangeDataRequest.getChildRequests(snapWorldDownloadState, worldStateStorage, null); + storageRangeDataRequest.getChildRequests( + snapWorldDownloadState, worldStateStorageCoordinator, null); verify(snapWorldDownloadState, never()).addAccountToHealingList(any(Bytes.class)); } @@ -130,7 +138,7 @@ void markAccountOnInvalidStorageProof() { final List proofs = List.of( - worldStateStorage + worldStateKeyValueStorage .getAccountStorageTrieNode( accountHash, Bytes.EMPTY, stateTrieAccountValue.getStorageRoot()) .get()); @@ -159,7 +167,8 @@ void markAccountOnPartialStorageRange() { new StoredMerklePatriciaTrie<>( new StoredNodeFactory<>( (location, hash) -> - worldStateStorage.getAccountStorageTrieNode(accountHash, location, hash), + worldStateKeyValueStorage.getAccountStorageTrieNode( + accountHash, location, hash), Function.identity(), Function.identity()), stateTrieAccountValue.getStorageRoot()); @@ -197,7 +206,8 @@ void markAccountOnPartialStorageRange() { verify(snapWorldDownloadState, never()).addAccountToHealingList(any(Bytes.class)); // should mark during the getchild request - storageRangeDataRequest.getChildRequests(snapWorldDownloadState, worldStateStorage, null); + storageRangeDataRequest.getChildRequests( + snapWorldDownloadState, worldStateStorageCoordinator, null); verify(snapWorldDownloadState).addAccountToHealingList(any(Bytes.class)); } @@ -212,7 +222,7 @@ void avoidMarkingAccountOnValidStorageTrieNodeDetection() { accountHash, Hash.wrap(accountStateTrie.getRootHash()), Bytes.EMPTY); - storageTrieNodeHealingRequest.getExistingData(snapWorldDownloadState, worldStateStorage); + storageTrieNodeHealingRequest.getExistingData(worldStateStorageCoordinator); verify(snapWorldDownloadState, never()).addAccountToHealingList(any(Bytes.class)); } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/CompleteTaskStepTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/CompleteTaskStepTest.java index 214070d9684..2bfc58a3b8a 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/CompleteTaskStepTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/CompleteTaskStepTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/DynamicPivotBlockManagerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/DynamicPivotBlockManagerTest.java index 2fa778ad5d4..e119b8de4c8 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/DynamicPivotBlockManagerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/DynamicPivotBlockManagerTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/LoadLocalDataStepTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/LoadLocalDataStepTest.java index 7ecd284e98f..06f92460c27 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/LoadLocalDataStepTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/LoadLocalDataStepTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -27,7 +27,8 @@ import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.SnapDataRequest; import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.heal.AccountTrieNodeHealingRequest; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.services.pipeline.Pipe; import org.hyperledger.besu.services.tasks.Task; @@ -54,18 +55,20 @@ public class LoadLocalDataStepTest { private final Task task = new StubTask(request); private final Pipe> completedTasks = - new Pipe<>(10, NO_OP_COUNTER, NO_OP_COUNTER, NO_OP_COUNTER); + new Pipe<>(10, NO_OP_COUNTER, NO_OP_COUNTER, NO_OP_COUNTER, "test_pipe"); private final SnapSyncProcessState snapSyncState = mock(SnapSyncProcessState.class); private final SnapWorldDownloadState downloadState = mock(SnapWorldDownloadState.class); - private final WorldStateStorage worldStateStorage = mock(WorldStateStorage.class); - private final WorldStateStorage.Updater updater = mock(WorldStateStorage.Updater.class); + private final WorldStateStorageCoordinator worldStateStorageCoordinator = + mock(WorldStateStorageCoordinator.class); + private final WorldStateKeyValueStorage.Updater updater = + mock(WorldStateKeyValueStorage.Updater.class); private final SnapSyncConfiguration snapSyncConfiguration = mock(SnapSyncConfiguration.class); private final LoadLocalDataStep loadLocalDataStep = new LoadLocalDataStep( - worldStateStorage, + worldStateStorageCoordinator, downloadState, snapSyncConfiguration, new NoOpMetricsSystem(), @@ -92,8 +95,10 @@ public void shouldReturnStreamWithSameRootHashTaskWhenDataArePresent() { task.getData().setRootHash(blockHeader.getStateRoot()); - when(worldStateStorage.getAccountStateTrieNode(any(), any())).thenReturn(Optional.of(DATA)); - when(worldStateStorage.updater()).thenReturn(mock(WorldStateStorage.Updater.class)); + when(worldStateStorageCoordinator.getAccountStateTrieNode(any(), any())) + .thenReturn(Optional.of(DATA)); + when(worldStateStorageCoordinator.updater()) + .thenReturn(mock(WorldStateKeyValueStorage.Updater.class)); final BlockHeader newBlockHeader = new BlockHeaderTestFixture().stateRoot(Hash.EMPTY).buildHeader(); @@ -107,8 +112,9 @@ public void shouldReturnStreamWithSameRootHashTaskWhenDataArePresent() { @Test public void shouldReturnEmptyStreamAndSendTaskToCompletedPipeWhenDataIsPresent() { - when(worldStateStorage.getAccountStateTrieNode(any(), any())).thenReturn(Optional.of(DATA)); - when(worldStateStorage.updater()).thenReturn(updater); + when(worldStateStorageCoordinator.getAccountStateTrieNode(any(), any())) + .thenReturn(Optional.of(DATA)); + when(worldStateStorageCoordinator.updater()).thenReturn(updater); final Stream> output = loadLocalDataStep.loadLocalDataTrieNode(task, completedTasks); @@ -122,7 +128,7 @@ public void shouldReturnEmptyStreamAndSendTaskToCompletedPipeWhenDataIsPresent() // Should not require persisting. request.persist( - worldStateStorage, updater, downloadState, snapSyncState, snapSyncConfiguration); + worldStateStorageCoordinator, updater, downloadState, snapSyncState, snapSyncConfiguration); verifyNoInteractions(updater); } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/PersistDataStepTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/PersistDataStepTest.java index 69047bc6e50..0364dc75814 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/PersistDataStepTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/PersistDataStepTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -25,9 +25,10 @@ import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.BytecodeRequest; import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.SnapDataRequest; import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.StorageRangeDataRequest; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.services.tasks.Task; import java.util.List; @@ -38,19 +39,22 @@ public class PersistDataStepTest { - private final WorldStateStorage worldStateStorage = - new InMemoryKeyValueStorageProvider().createWorldStateStorage(DataStorageFormat.FOREST); + private final WorldStateStorageCoordinator worldStateStorageCoordinator = + new InMemoryKeyValueStorageProvider() + .createWorldStateStorageCoordinator(DataStorageConfiguration.DEFAULT_CONFIG); + private final SnapSyncProcessState snapSyncState = mock(SnapSyncProcessState.class); private final SnapWorldDownloadState downloadState = mock(SnapWorldDownloadState.class); private final SnapSyncConfiguration snapSyncConfiguration = mock(SnapSyncConfiguration.class); private final PersistDataStep persistDataStep = - new PersistDataStep(snapSyncState, worldStateStorage, downloadState, snapSyncConfiguration); + new PersistDataStep( + snapSyncState, worldStateStorageCoordinator, downloadState, snapSyncConfiguration); @BeforeEach public void setUp() { - when(downloadState.getMetricsManager()).thenReturn(mock(SnapsyncMetricsManager.class)); + when(downloadState.getMetricsManager()).thenReturn(mock(SnapSyncMetricsManager.class)); } @Test @@ -69,7 +73,10 @@ public void shouldSkipPersistDataWhenNoData() { final List> result = persistDataStep.persist(tasks); assertThat(result).isSameAs(tasks); - assertThat(worldStateStorage.getNodeData(Bytes.EMPTY, tasks.get(0).getData().getRootHash())) + assertThat( + worldStateStorageCoordinator + .getStrategy(BonsaiWorldStateKeyValueStorage.class) + .getTrieNodeUnsafe(tasks.get(0).getData().getRootHash())) .isEmpty(); } @@ -80,14 +87,17 @@ private void assertDataPersisted(final List> tasks) { final AccountRangeDataRequest data = (AccountRangeDataRequest) task.getData(); StoredMerklePatriciaTrie trie = new StoredMerklePatriciaTrie<>( - worldStateStorage::getAccountStateTrieNode, data.getRootHash(), b -> b, b -> b); + worldStateStorageCoordinator::getAccountStateTrieNode, + data.getRootHash(), + b -> b, + b -> b); data.getAccounts().forEach((key, value) -> assertThat(trie.get(key)).isPresent()); } else if (task.getData() instanceof StorageRangeDataRequest) { final StorageRangeDataRequest data = (StorageRangeDataRequest) task.getData(); final StoredMerklePatriciaTrie trie = new StoredMerklePatriciaTrie<>( (location, hash) -> - worldStateStorage.getAccountStorageTrieNode( + worldStateStorageCoordinator.getAccountStorageTrieNode( Hash.wrap(data.getAccountHash()), location, hash), data.getStorageRoot(), b -> b, @@ -96,7 +106,9 @@ private void assertDataPersisted(final List> tasks) { } else if (task.getData() instanceof BytecodeRequest) { final BytecodeRequest data = (BytecodeRequest) task.getData(); assertThat( - worldStateStorage.getCode(data.getCodeHash(), Hash.wrap(data.getAccountHash()))) + worldStateStorageCoordinator + .getStrategy(BonsaiWorldStateKeyValueStorage.class) + .getCode(Hash.wrap(data.getCodeHash()), Hash.wrap(data.getAccountHash()))) .isPresent(); } else { fail("not expected message"); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/RangeManagerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/RangeManagerTest.java index 003db79a7e2..c5427b7e0d7 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/RangeManagerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/RangeManagerTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -19,11 +19,12 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.core.TrieGenerator; import org.hyperledger.besu.ethereum.proof.WorldStateProofProvider; -import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.trie.MerkleTrie; +import org.hyperledger.besu.ethereum.trie.RangeManager; import org.hyperledger.besu.ethereum.trie.RangeStorageEntriesCollector; import org.hyperledger.besu.ethereum.trie.TrieIterator; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import java.util.HashMap; @@ -117,11 +118,14 @@ public void testGenerateRangesWithSize3() { @Test public void testFindNewBeginElement() { - final WorldStateStorage worldStateStorage = - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final ForestWorldStateKeyValueStorage worldStateKeyValueStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + + final WorldStateStorageCoordinator worldStateStorageCoordinator = + new WorldStateStorageCoordinator(worldStateKeyValueStorage); final MerkleTrie accountStateTrie = - TrieGenerator.generateTrie(worldStateStorage, 15); + TrieGenerator.generateTrie(worldStateStorageCoordinator, 15); final RangeStorageEntriesCollector collector = RangeStorageEntriesCollector.createCollector( @@ -135,7 +139,7 @@ public void testFindNewBeginElement() { collector, visitor, root, Hash.ZERO)); final WorldStateProofProvider worldStateProofProvider = - new WorldStateProofProvider(worldStateStorage); + new WorldStateProofProvider(worldStateStorageCoordinator); // generate the proof final List proofs = @@ -156,11 +160,13 @@ public void testFindNewBeginElement() { @Test public void testFindNewBeginElementWhenNothingIsMissing() { - final WorldStateStorage worldStateStorage = - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final ForestWorldStateKeyValueStorage worldStateKeyValueStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final WorldStateStorageCoordinator worldStateStorageCoordinator = + new WorldStateStorageCoordinator(worldStateKeyValueStorage); final MerkleTrie accountStateTrie = - TrieGenerator.generateTrie(worldStateStorage, 15); + TrieGenerator.generateTrie(worldStateStorageCoordinator, 15); final RangeStorageEntriesCollector collector = RangeStorageEntriesCollector.createCollector( @@ -174,7 +180,7 @@ public void testFindNewBeginElementWhenNothingIsMissing() { collector, visitor, root, Hash.ZERO)); final WorldStateProofProvider worldStateProofProvider = - new WorldStateProofProvider(worldStateStorage); + new WorldStateProofProvider(worldStateStorageCoordinator); // generate the proof final List proofs = diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldDownloadStateTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldDownloadStateTest.java index 22c2b73765d..24e4869960e 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldDownloadStateTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/SnapWorldDownloadStateTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -25,7 +25,6 @@ import static org.mockito.Mockito.when; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.chain.BlockAddedEvent; import org.hyperledger.besu.ethereum.chain.BlockAddedObserver; import org.hyperledger.besu.ethereum.chain.Blockchain; @@ -34,15 +33,22 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; +import org.hyperledger.besu.ethereum.eth.manager.EthContext; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.manager.task.EthTask; import org.hyperledger.besu.ethereum.eth.sync.snapsync.context.SnapSyncStatePersistenceManager; import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.BytecodeRequest; import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.SnapDataRequest; import org.hyperledger.besu.ethereum.eth.sync.worldstate.WorldStateDownloadProcess; -import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.trie.RangeManager; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.metrics.SyncDurationMetrics; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import org.hyperledger.besu.services.tasks.InMemoryTasksPriorityQueues; import org.hyperledger.besu.testutil.TestClock; @@ -57,6 +63,7 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -71,7 +78,9 @@ public class SnapWorldDownloadStateTest { private static final int MAX_REQUESTS_WITHOUT_PROGRESS = 10; private static final long MIN_MILLIS_BEFORE_STALLING = 50_000; - private WorldStateStorage worldStateStorage; + private WorldStateKeyValueStorage worldStateKeyValueStorage; + + private WorldStateStorageCoordinator worldStateStorageCoordinator; private final BlockHeader header = new BlockHeaderTestFixture().stateRoot(ROOT_NODE_HASH).buildHeader(); private final InMemoryTasksPriorityQueues pendingRequests = @@ -81,10 +90,11 @@ public class SnapWorldDownloadStateTest { private final SnapSyncProcessState snapSyncState = mock(SnapSyncProcessState.class); private final SnapSyncStatePersistenceManager snapContext = mock(SnapSyncStatePersistenceManager.class); - private final SnapsyncMetricsManager metricsManager = mock(SnapsyncMetricsManager.class); + private final SnapSyncMetricsManager metricsManager = mock(SnapSyncMetricsManager.class); private final Blockchain blockchain = mock(Blockchain.class); private final DynamicPivotBlockSelector dynamicPivotBlockManager = mock(DynamicPivotBlockSelector.class); + private final EthContext ethContext = mock(EthContext.class); private final TestClock clock = new TestClock(); private SnapWorldDownloadState downloadState; @@ -106,15 +116,20 @@ public void setUp(final DataStorageFormat storageFormat) { when(metricsManager.getMetricsSystem()).thenReturn(new NoOpMetricsSystem()); if (storageFormat == DataStorageFormat.BONSAI) { - worldStateStorage = + worldStateKeyValueStorage = new BonsaiWorldStateKeyValueStorage( - new InMemoryKeyValueStorageProvider(), new NoOpMetricsSystem()); + new InMemoryKeyValueStorageProvider(), + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_BONSAI_PARTIAL_DB_CONFIG); } else { - worldStateStorage = new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + worldStateKeyValueStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); } + worldStateStorageCoordinator = new WorldStateStorageCoordinator(worldStateKeyValueStorage); + downloadState = new SnapWorldDownloadState( - worldStateStorage, + worldStateStorageCoordinator, snapContext, blockchain, snapSyncState, @@ -122,7 +137,9 @@ public void setUp(final DataStorageFormat storageFormat) { MAX_REQUESTS_WITHOUT_PROGRESS, MIN_MILLIS_BEFORE_STALLING, metricsManager, - clock); + clock, + ethContext, + SyncDurationMetrics.NO_OP_SYNC_DURATION_METRICS); final DynamicPivotBlockSelector dynamicPivotBlockManager = mock(DynamicPivotBlockSelector.class); doAnswer( @@ -137,6 +154,17 @@ public void setUp(final DataStorageFormat storageFormat) { downloadState.setRootNodeData(ROOT_NODE_DATA); future = downloadState.getDownloadFuture(); assertThat(downloadState.isDownloading()).isTrue(); + + final EthScheduler ethScheduler = mock(EthScheduler.class); + when(ethContext.getScheduler()).thenReturn(ethScheduler); + doAnswer( + invocation -> { + Runnable runnable = invocation.getArgument(0); + runnable.run(); + return null; + }) + .when(ethScheduler) + .executeServiceTask(any(Runnable.class)); } @ParameterizedTest @@ -176,7 +204,9 @@ public void shouldStoreRootNodeBeforeReturnedFutureCompletes( final CompletableFuture postFutureChecks = future.thenAccept( result -> - assertThat(worldStateStorage.getAccountStateTrieNode(Bytes.EMPTY, ROOT_NODE_HASH)) + assertThat( + worldStateStorageCoordinator.getAccountStateTrieNode( + Bytes.EMPTY, ROOT_NODE_HASH)) .contains(ROOT_NODE_DATA)); downloadState.checkCompletion(header); @@ -201,7 +231,8 @@ public void shouldNotCompleteWhenThereAreAccountPendingTasks( downloadState.checkCompletion(header); assertThat(future).isNotDone(); - assertThat(worldStateStorage.getAccountStateTrieNode(Bytes.EMPTY, ROOT_NODE_HASH)).isEmpty(); + assertThat(worldStateStorageCoordinator.getAccountStateTrieNode(Bytes.EMPTY, ROOT_NODE_HASH)) + .isEmpty(); assertThat(downloadState.isDownloading()).isTrue(); } @@ -218,7 +249,8 @@ public void shouldNotCompleteWhenThereAreStoragePendingTasks( downloadState.checkCompletion(header); assertThat(future).isNotDone(); - assertThat(worldStateStorage.getAccountStateTrieNode(Bytes.EMPTY, ROOT_NODE_HASH)).isEmpty(); + assertThat(worldStateStorageCoordinator.getAccountStateTrieNode(Bytes.EMPTY, ROOT_NODE_HASH)) + .isEmpty(); assertThat(downloadState.isDownloading()).isTrue(); downloadState.pendingLargeStorageRequests.add( @@ -228,7 +260,8 @@ public void shouldNotCompleteWhenThereAreStoragePendingTasks( downloadState.checkCompletion(header); assertThat(future).isNotDone(); - assertThat(worldStateStorage.getAccountStateTrieNode(Bytes.EMPTY, ROOT_NODE_HASH)).isEmpty(); + assertThat(worldStateStorageCoordinator.getAccountStateTrieNode(Bytes.EMPTY, ROOT_NODE_HASH)) + .isEmpty(); assertThat(downloadState.isDownloading()).isTrue(); } @@ -245,7 +278,8 @@ public void shouldNotCompleteWhenThereAreTriePendingTasks( downloadState.checkCompletion(header); assertThat(future).isNotDone(); - assertThat(worldStateStorage.getAccountStateTrieNode(Bytes.EMPTY, ROOT_NODE_HASH)).isEmpty(); + assertThat(worldStateStorageCoordinator.getAccountStateTrieNode(Bytes.EMPTY, ROOT_NODE_HASH)) + .isEmpty(); assertThat(downloadState.isDownloading()).isTrue(); } @@ -263,7 +297,8 @@ public void shouldNotCompleteWhenThereAreFlatDBHealingPendingTasks( downloadState.checkCompletion(header); assertThat(future).isNotDone(); - assertThat(worldStateStorage.getAccountStateTrieNode(Bytes.EMPTY, ROOT_NODE_HASH)).isEmpty(); + assertThat(worldStateStorageCoordinator.getAccountStateTrieNode(Bytes.EMPTY, ROOT_NODE_HASH)) + .isEmpty(); assertThat(downloadState.isDownloading()).isTrue(); } @@ -341,7 +376,8 @@ public void shouldListeningBlockchainDuringHeal( verify(snapSyncState, atLeastOnce()).setHealTrieStatus(true); assertThat(future).isNotDone(); - assertThat(worldStateStorage.getAccountStateTrieNode(Bytes.EMPTY, ROOT_NODE_HASH)).isEmpty(); + assertThat(worldStateStorageCoordinator.getAccountStateTrieNode(Bytes.EMPTY, ROOT_NODE_HASH)) + .isEmpty(); assertThat(downloadState.isDownloading()).isTrue(); } @@ -450,13 +486,21 @@ public void shouldNotCompleteReturnedFutureWhenNoPendingTasksRemainAndFlatDBHeal setUp(storageFormat); Assumptions.assumeTrue(storageFormat == DataStorageFormat.BONSAI); Assumptions.assumeTrue(isFlatDbHealingEnabled); - ((BonsaiWorldStateKeyValueStorage) worldStateStorage).upgradeToFullFlatDbMode(); + ((BonsaiWorldStateKeyValueStorage) worldStateKeyValueStorage).upgradeToFullFlatDbMode(); when(snapSyncState.isHealTrieInProgress()).thenReturn(true); downloadState.checkCompletion(header); assertThat(future).isNotDone(); verify(snapSyncState).setHealFlatDatabaseInProgress(true); - assertThat(worldStateStorage.getAccountStateTrieNode(Bytes.EMPTY, ROOT_NODE_HASH)).isEmpty(); + assertThat(worldStateStorageCoordinator.getAccountStateTrieNode(Bytes.EMPTY, ROOT_NODE_HASH)) + .isEmpty(); assertThat(downloadState.isDownloading()).isTrue(); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/StackTrieTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/StackTrieTest.java index 10a0cf3f84c..4e1a86e4db7 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/StackTrieTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/StackTrieTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,13 +17,16 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.core.TrieGenerator; import org.hyperledger.besu.ethereum.proof.WorldStateProofProvider; -import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.trie.MerkleTrie; +import org.hyperledger.besu.ethereum.trie.RangeManager; import org.hyperledger.besu.ethereum.trie.RangeStorageEntriesCollector; import org.hyperledger.besu.ethereum.trie.TrieIterator; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; +import java.util.ArrayList; import java.util.List; import java.util.TreeMap; @@ -41,14 +44,16 @@ public void shouldNotSaveTheRootWhenIncomplete() { final int nbAccounts = 15; - final WorldStateStorage worldStateStorage = - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final ForestWorldStateKeyValueStorage worldStateKeyValueStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final WorldStateStorageCoordinator worldStateStorageCoordinator = + new WorldStateStorageCoordinator(worldStateKeyValueStorage); - final WorldStateStorage recreatedWorldStateStorage = - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final ForestWorldStateKeyValueStorage recreatedWorldStateStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); final MerkleTrie accountStateTrie = - TrieGenerator.generateTrie(worldStateStorage, nbAccounts); + TrieGenerator.generateTrie(worldStateStorageCoordinator, nbAccounts); final StackTrie stackTrie = new StackTrie(Hash.wrap(accountStateTrie.getRootHash()), 0, 256, lastAccount); @@ -66,7 +71,7 @@ public void shouldNotSaveTheRootWhenIncomplete() { collector, visitor, root, lastAccount)); final WorldStateProofProvider worldStateProofProvider = - new WorldStateProofProvider(worldStateStorage); + new WorldStateProofProvider(worldStateStorageCoordinator); // generate the proof final List proofs = @@ -78,13 +83,12 @@ public void shouldNotSaveTheRootWhenIncomplete() { stackTrie.addElement(Bytes32.random(), proofs, accounts); - final WorldStateStorage.Updater updater = recreatedWorldStateStorage.updater(); - stackTrie.commit(updater::putAccountStateTrieNode); + final ForestWorldStateKeyValueStorage.Updater updater = recreatedWorldStateStorage.updater(); + stackTrie.commit(((location, hash, value) -> updater.putAccountStateTrieNode(hash, value))); updater.commit(); Assertions.assertThat( - recreatedWorldStateStorage.getAccountStateTrieNode( - Bytes.EMPTY, accountStateTrie.getRootHash())) + recreatedWorldStateStorage.getAccountStateTrieNode(accountStateTrie.getRootHash())) .isEmpty(); } @@ -93,14 +97,16 @@ public void shouldSaveTheRootWhenComplete() { final int nbAccounts = 15; - final WorldStateStorage worldStateStorage = - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final ForestWorldStateKeyValueStorage worldStateKeyValueStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final WorldStateStorageCoordinator worldStateStorageCoordinator = + new WorldStateStorageCoordinator(worldStateKeyValueStorage); - final WorldStateStorage recreatedWorldStateStorage = - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final ForestWorldStateKeyValueStorage recreatedWorldStateStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); final MerkleTrie accountStateTrie = - TrieGenerator.generateTrie(worldStateStorage, nbAccounts); + TrieGenerator.generateTrie(worldStateStorageCoordinator, nbAccounts); final StackTrie stackTrie = new StackTrie(Hash.wrap(accountStateTrie.getRootHash()), 0, 256, lastAccount); @@ -119,7 +125,7 @@ public void shouldSaveTheRootWhenComplete() { collector, visitor, root, lastAccount)); final WorldStateProofProvider worldStateProofProvider = - new WorldStateProofProvider(worldStateStorage); + new WorldStateProofProvider(worldStateStorageCoordinator); // generate the proof final List proofs = @@ -131,13 +137,136 @@ public void shouldSaveTheRootWhenComplete() { stackTrie.addElement(Bytes32.random(), proofs, accounts); - final WorldStateStorage.Updater updater = recreatedWorldStateStorage.updater(); - stackTrie.commit(updater::putAccountStateTrieNode); + final ForestWorldStateKeyValueStorage.Updater updater = recreatedWorldStateStorage.updater(); + stackTrie.commit((location, hash, value) -> updater.putAccountStateTrieNode(hash, value)); updater.commit(); } Assertions.assertThat( - worldStateStorage.getAccountStateTrieNode(Bytes.EMPTY, accountStateTrie.getRootHash())) + worldStateKeyValueStorage.getAccountStateTrieNode(accountStateTrie.getRootHash())) + .isPresent(); + } + + @Test + public void shouldNotSaveNodeWithChildNotInTheRange() { + final ForestWorldStateKeyValueStorage worldStateStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final WorldStateStorageCoordinator worldStateStorageCoordinator = + new WorldStateStorageCoordinator(worldStateStorage); + + final MerkleTrie trie = + new StoredMerklePatriciaTrie<>( + (location, hash) -> worldStateStorage.getAccountStateTrieNode(hash).map(Bytes::wrap), + b -> b, + b -> b); + + trie.put(Bytes32.rightPad(Bytes.of(0x10)), Bytes.of(0x01)); + trie.put(Bytes32.rightPad(Bytes.of(0x11)), Bytes.of(0x01)); + trie.put(Bytes32.rightPad(Bytes.of(0x20)), Bytes.of(0x01)); + trie.put(Bytes32.rightPad(Bytes.of(0x21)), Bytes.of(0x01)); + trie.put(Bytes32.rightPad(Bytes.of(0x01)), Bytes.of(0x02)); + trie.put(Bytes32.rightPad(Bytes.of(0x02)), Bytes.of(0x03)); + trie.put(Bytes32.rightPad(Bytes.of(0x03)), Bytes.of(0x04)); + + final ForestWorldStateKeyValueStorage.Updater updater = worldStateStorage.updater(); + trie.commit((location, hash, value) -> updater.putAccountStateTrieNode(hash, value)); + updater.commit(); + + final Bytes32 startRange = Bytes32.rightPad(Bytes.of(0x02)); + + final RangeStorageEntriesCollector collector = + RangeStorageEntriesCollector.createCollector( + startRange, RangeManager.MAX_RANGE, 15, Integer.MAX_VALUE); + final TrieIterator visitor = RangeStorageEntriesCollector.createVisitor(collector); + final TreeMap entries = + (TreeMap) + trie.entriesFrom( + root -> + RangeStorageEntriesCollector.collectEntries( + collector, visitor, root, startRange)); + + final WorldStateProofProvider worldStateProofProvider = + new WorldStateProofProvider(worldStateStorageCoordinator); + + // generate the proof + final List proofs = + worldStateProofProvider.getAccountProofRelatedNodes( + Hash.wrap(trie.getRootHash()), startRange); + proofs.addAll( + worldStateProofProvider.getAccountProofRelatedNodes( + Hash.wrap(trie.getRootHash()), entries.lastKey())); + + // try to commit with stack trie + final ForestWorldStateKeyValueStorage recreatedWorldStateStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final StackTrie stackTrie = new StackTrie(Hash.wrap(trie.getRootHash()), 0, 256, startRange); + stackTrie.addSegment(); + stackTrie.addElement(Bytes32.random(), proofs, entries); + final ForestWorldStateKeyValueStorage.Updater updaterStackTrie = + recreatedWorldStateStorage.updater(); + stackTrie.commit( + (location, hash, value) -> updaterStackTrie.putAccountStateTrieNode(hash, value)); + updaterStackTrie.commit(); + + // verify the state of the db + Assertions.assertThat(worldStateStorage.getAccountStateTrieNode(trie.getRootHash())) + .isPresent(); + Assertions.assertThat(recreatedWorldStateStorage.getAccountStateTrieNode(trie.getRootHash())) + .isNotPresent(); + } + + @Test + public void shouldSaveNodeWithAllChildsInTheRange() { + final ForestWorldStateKeyValueStorage worldStateStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + + final MerkleTrie trie = + new StoredMerklePatriciaTrie<>( + (location, hash) -> worldStateStorage.getAccountStateTrieNode(hash).map(Bytes::wrap), + b -> b, + b -> b); + + trie.put(Bytes32.rightPad(Bytes.of(0x10)), Bytes.of(0x01)); + trie.put(Bytes32.rightPad(Bytes.of(0x11)), Bytes.of(0x01)); + trie.put(Bytes32.rightPad(Bytes.of(0x20)), Bytes.of(0x01)); + trie.put(Bytes32.rightPad(Bytes.of(0x21)), Bytes.of(0x01)); + trie.put(Bytes32.rightPad(Bytes.of(0x01)), Bytes.of(0x02)); + trie.put(Bytes32.rightPad(Bytes.of(0x02)), Bytes.of(0x03)); + trie.put(Bytes32.rightPad(Bytes.of(0x03)), Bytes.of(0x04)); + + final ForestWorldStateKeyValueStorage.Updater updater = worldStateStorage.updater(); + trie.commit((location, hash, value) -> updater.putAccountStateTrieNode(hash, value)); + updater.commit(); + + final Bytes32 startRange = Bytes32.rightPad(Bytes.of(0x00)); + + final RangeStorageEntriesCollector collector = + RangeStorageEntriesCollector.createCollector( + startRange, RangeManager.MAX_RANGE, 15, Integer.MAX_VALUE); + final TrieIterator visitor = RangeStorageEntriesCollector.createVisitor(collector); + final TreeMap entries = + (TreeMap) + trie.entriesFrom( + root -> + RangeStorageEntriesCollector.collectEntries( + collector, visitor, root, startRange)); + + // try to commit with stack trie + final ForestWorldStateKeyValueStorage recreatedWorldStateStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final StackTrie stackTrie = new StackTrie(Hash.wrap(trie.getRootHash()), 0, 256, startRange); + stackTrie.addSegment(); + stackTrie.addElement(Bytes32.random(), new ArrayList<>(), entries); + final ForestWorldStateKeyValueStorage.Updater updaterStackTrie = + recreatedWorldStateStorage.updater(); + stackTrie.commit( + (location, hash, value) -> updaterStackTrie.putAccountStateTrieNode(hash, value)); + updaterStackTrie.commit(); + + // verify the state of the db + Assertions.assertThat(worldStateStorage.getAccountStateTrieNode(trie.getRootHash())) + .isPresent(); + Assertions.assertThat(recreatedWorldStateStorage.getAccountStateTrieNode(trie.getRootHash())) .isPresent(); } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/StubTask.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/StubTask.java index 6c5dd72f3ae..6d2e9cd6ef5 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/StubTask.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/StubTask.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/TaskGenerator.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/TaskGenerator.java index 64bda37ae88..0e3915bc777 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/TaskGenerator.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/TaskGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -24,12 +24,15 @@ import org.hyperledger.besu.ethereum.proof.WorldStateProofProvider; import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.ethereum.trie.MerkleTrie; +import org.hyperledger.besu.ethereum.trie.RangeManager; import org.hyperledger.besu.ethereum.trie.RangeStorageEntriesCollector; import org.hyperledger.besu.ethereum.trie.TrieIterator; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.services.tasks.Task; import java.util.List; @@ -43,13 +46,20 @@ public class TaskGenerator { public static List> createAccountRequest(final boolean withData) { - final WorldStateStorage worldStateStorage = - new InMemoryKeyValueStorageProvider().createWorldStateStorage(DataStorageFormat.FOREST); + final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage = + new BonsaiWorldStateKeyValueStorage( + new InMemoryKeyValueStorageProvider(), + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG); + + final WorldStateStorageCoordinator worldStateStorageCoordinator = + new WorldStateStorageCoordinator(worldStateKeyValueStorage); final WorldStateProofProvider worldStateProofProvider = - new WorldStateProofProvider(worldStateStorage); + new WorldStateProofProvider(worldStateStorageCoordinator); - final MerkleTrie trie = TrieGenerator.generateTrie(worldStateStorage, 1); + final MerkleTrie trie = + TrieGenerator.generateTrie(worldStateStorageCoordinator, 1); final RangeStorageEntriesCollector collector = RangeStorageEntriesCollector.createCollector( Bytes32.ZERO, RangeManager.MAX_RANGE, 1, Integer.MAX_VALUE); @@ -77,14 +87,14 @@ public static List> createAccountRequest(final boolean wit final StorageRangeDataRequest storageRangeDataRequest = createStorageRangeDataRequest( worldStateProofProvider, - worldStateStorage, + worldStateStorageCoordinator, rootHash, accountHash, stateTrieAccountValue.getStorageRoot(), withData); final BytecodeRequest bytecodeRequest = createBytecodeDataRequest( - worldStateStorage, + worldStateKeyValueStorage, rootHash, accountHash, stateTrieAccountValue.getCodeHash(), @@ -98,7 +108,7 @@ public static List> createAccountRequest(final boolean wit private static StorageRangeDataRequest createStorageRangeDataRequest( final WorldStateProofProvider worldStateProofProvider, - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateKeyValueStorage, final Hash rootHash, final Hash accountHash, final Bytes32 storageRoot, @@ -110,7 +120,7 @@ private static StorageRangeDataRequest createStorageRangeDataRequest( final StoredMerklePatriciaTrie storageTrie = new StoredMerklePatriciaTrie<>( (location, hash) -> - worldStateStorage.getAccountStorageTrieNode(accountHash, location, hash), + worldStateKeyValueStorage.getAccountStorageTrieNode(accountHash, location, hash), storageRoot, b -> b, b -> b); @@ -134,7 +144,7 @@ private static StorageRangeDataRequest createStorageRangeDataRequest( } private static BytecodeRequest createBytecodeDataRequest( - final WorldStateStorage worldStateStorage, + final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage, final Hash rootHash, final Hash accountHash, final Hash codeHash, @@ -142,7 +152,7 @@ private static BytecodeRequest createBytecodeDataRequest( final BytecodeRequest request = SnapDataRequest.createBytecodeRequest(accountHash, rootHash, codeHash); if (withData) { - request.setCode(worldStateStorage.getCode(codeHash, accountHash).get()); + request.setCode(worldStateKeyValueStorage.getCode(codeHash, accountHash).get()); } return request; } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/AccountFlatDatabaseHealingRangeRequestTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/AccountFlatDatabaseHealingRangeRequestTest.java index a95d4c34114..ffa50401aee 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/AccountFlatDatabaseHealingRangeRequestTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/AccountFlatDatabaseHealingRangeRequestTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,23 +15,25 @@ package org.hyperledger.besu.ethereum.eth.sync.snapsync.request.heal; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; import org.hyperledger.besu.ethereum.core.TrieGenerator; -import org.hyperledger.besu.ethereum.eth.sync.snapsync.RangeManager; import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncConfiguration; +import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncMetricsManager; import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncProcessState; import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapWorldDownloadState; -import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapsyncMetricsManager; import org.hyperledger.besu.ethereum.eth.sync.snapsync.request.SnapDataRequest; import org.hyperledger.besu.ethereum.proof.WorldStateProofProvider; import org.hyperledger.besu.ethereum.storage.StorageProvider; -import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.trie.CompactEncoding; import org.hyperledger.besu.ethereum.trie.MerkleTrie; +import org.hyperledger.besu.ethereum.trie.RangeManager; import org.hyperledger.besu.ethereum.trie.RangeStorageEntriesCollector; import org.hyperledger.besu.ethereum.trie.TrieIterator; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; @@ -66,17 +68,20 @@ public class AccountFlatDatabaseHealingRangeRequestTest { @BeforeEach public void setup() { Mockito.when(downloadState.getMetricsManager()) - .thenReturn(Mockito.mock(SnapsyncMetricsManager.class)); + .thenReturn(Mockito.mock(SnapSyncMetricsManager.class)); Mockito.when(downloadState.getAccountsHealingList()).thenReturn(new HashSet<>()); } @Test public void shouldReturnChildRequests() { - final WorldStateStorage worldStateStorage = - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); - final WorldStateProofProvider proofProvider = new WorldStateProofProvider(worldStateStorage); + final ForestWorldStateKeyValueStorage worldStateKeyValueStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final WorldStateStorageCoordinator worldStateStorageCoordinator = + new WorldStateStorageCoordinator(worldStateKeyValueStorage); + final WorldStateProofProvider proofProvider = + new WorldStateProofProvider(worldStateStorageCoordinator); final MerkleTrie accountStateTrie = - TrieGenerator.generateTrie(worldStateStorage, 15); + TrieGenerator.generateTrie(worldStateStorageCoordinator, 15); // Create a collector to gather account entries within a specific range final RangeStorageEntriesCollector collector = @@ -113,7 +118,9 @@ public void shouldReturnChildRequests() { // Verify that the start key hash of the snapDataRequest is greater than the last key in the // accounts TreeMap List childRequests = - request.getChildRequests(downloadState, worldStateStorage, snapSyncState).toList(); + request + .getChildRequests(downloadState, worldStateStorageCoordinator, snapSyncState) + .toList(); Assertions.assertThat(childRequests).hasSize(1); AccountFlatDatabaseHealingRangeRequest snapDataRequest = (AccountFlatDatabaseHealingRangeRequest) childRequests.get(0); @@ -127,7 +134,9 @@ public void shouldReturnChildRequests() { .map(CompactEncoding::bytesToPath) .collect(Collectors.toList()))); childRequests = - request.getChildRequests(downloadState, worldStateStorage, snapSyncState).toList(); + request + .getChildRequests(downloadState, worldStateStorageCoordinator, snapSyncState) + .toList(); Assertions.assertThat(childRequests).hasSizeGreaterThan(1); Assertions.assertThat(childRequests) .hasAtLeastOneElementOfType(AccountFlatDatabaseHealingRangeRequest.class); @@ -137,11 +146,14 @@ public void shouldReturnChildRequests() { @Test public void shouldNotReturnChildRequestsWhenNoMoreAccounts() { - final WorldStateStorage worldStateStorage = - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); - final WorldStateProofProvider proofProvider = new WorldStateProofProvider(worldStateStorage); + final ForestWorldStateKeyValueStorage worldStateKeyValueStorage = + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + final WorldStateStorageCoordinator worldStateStorageCoordinator = + new WorldStateStorageCoordinator(worldStateKeyValueStorage); + final WorldStateProofProvider proofProvider = + new WorldStateProofProvider(worldStateStorageCoordinator); final MerkleTrie accountStateTrie = - TrieGenerator.generateTrie(worldStateStorage, 15); + TrieGenerator.generateTrie(worldStateStorageCoordinator, 15); // Create a collector to gather account entries within a specific range final RangeStorageEntriesCollector collector = @@ -169,7 +181,7 @@ public void shouldNotReturnChildRequestsWhenNoMoreAccounts() { // Verify that no child requests are returned from the request final Stream childRequests = - request.getChildRequests(downloadState, worldStateStorage, snapSyncState); + request.getChildRequests(downloadState, worldStateStorageCoordinator, snapSyncState); Assertions.assertThat(childRequests).isEmpty(); } @@ -178,11 +190,18 @@ public void doNotPersistWhenProofIsValid() { final StorageProvider storageProvider = new InMemoryKeyValueStorageProvider(); - final WorldStateStorage worldStateStorage = - new BonsaiWorldStateKeyValueStorage(storageProvider, new NoOpMetricsSystem()); - final WorldStateProofProvider proofProvider = new WorldStateProofProvider(worldStateStorage); + final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage = + new BonsaiWorldStateKeyValueStorage( + storageProvider, + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG); + final WorldStateStorageCoordinator worldStateStorageCoordinator = + new WorldStateStorageCoordinator(worldStateKeyValueStorage); + final WorldStateProofProvider proofProvider = + new WorldStateProofProvider(worldStateStorageCoordinator); + final MerkleTrie accountStateTrie = - TrieGenerator.generateTrie(worldStateStorage, 15); + TrieGenerator.generateTrie(worldStateStorageCoordinator, 15); // Create a collector to gather account entries within a specific range final RangeStorageEntriesCollector collector = RangeStorageEntriesCollector.createCollector( @@ -217,9 +236,9 @@ public void doNotPersistWhenProofIsValid() { // an ArrayDeque request.addLocalData(proofProvider, accounts, new ArrayDeque<>(proofs)); - WorldStateStorage.Updater updater = Mockito.spy(worldStateStorage.updater()); + WorldStateKeyValueStorage.Updater updater = Mockito.spy(worldStateKeyValueStorage.updater()); request.doPersist( - worldStateStorage, + worldStateStorageCoordinator, updater, downloadState, snapSyncState, @@ -232,11 +251,18 @@ public void doHealAndPersistWhenProofIsInvalid() { final StorageProvider storageProvider = new InMemoryKeyValueStorageProvider(); - final WorldStateStorage worldStateStorage = - new BonsaiWorldStateKeyValueStorage(storageProvider, new NoOpMetricsSystem()); - final WorldStateProofProvider proofProvider = new WorldStateProofProvider(worldStateStorage); + final BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage = + new BonsaiWorldStateKeyValueStorage( + storageProvider, + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG); + final WorldStateStorageCoordinator worldStateStorageCoordinator = + new WorldStateStorageCoordinator(worldStateKeyValueStorage); + final WorldStateProofProvider proofProvider = + new WorldStateProofProvider(worldStateStorageCoordinator); + final MerkleTrie accountStateTrie = - TrieGenerator.generateTrie(worldStateStorage, 15); + TrieGenerator.generateTrie(worldStateStorageCoordinator, 15); // Create a collector to gather account entries within a specific range final RangeStorageEntriesCollector collector = RangeStorageEntriesCollector.createCollector( @@ -286,9 +312,9 @@ public void doHealAndPersistWhenProofIsInvalid() { request.addLocalData(proofProvider, accounts, new ArrayDeque<>(proofs)); BonsaiWorldStateKeyValueStorage.Updater updater = - (BonsaiWorldStateKeyValueStorage.Updater) Mockito.spy(worldStateStorage.updater()); + Mockito.spy(worldStateKeyValueStorage.updater()); request.doPersist( - worldStateStorage, + worldStateStorageCoordinator, updater, downloadState, snapSyncState, diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/StorageFlatDatabaseHealingRangeRequestTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/StorageFlatDatabaseHealingRangeRequestTest.java index a281385dd5b..fb8dd742857 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/StorageFlatDatabaseHealingRangeRequestTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/StorageFlatDatabaseHealingRangeRequestTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -18,10 +18,8 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; import org.hyperledger.besu.ethereum.core.TrieGenerator; -import org.hyperledger.besu.ethereum.eth.sync.snapsync.RangeManager; import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncConfiguration; import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapSyncProcessState; import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapWorldDownloadState; @@ -30,11 +28,15 @@ import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.ethereum.storage.StorageProvider; import org.hyperledger.besu.ethereum.trie.MerkleTrie; +import org.hyperledger.besu.ethereum.trie.RangeManager; import org.hyperledger.besu.ethereum.trie.RangeStorageEntriesCollector; import org.hyperledger.besu.ethereum.trie.TrieIterator; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import java.util.Iterator; @@ -69,7 +71,9 @@ class StorageFlatDatabaseHealingRangeRequestTest { Address.fromHexString("0xdeadbeeb")); private MerkleTrie trie; - private BonsaiWorldStateKeyValueStorage worldStateStorage; + + private WorldStateStorageCoordinator worldStateStorageCoordinator; + private BonsaiWorldStateKeyValueStorage worldStateKeyValueStorage; private WorldStateProofProvider proofProvider; private Hash account0Hash; private Hash account0StorageRoot; @@ -77,12 +81,17 @@ class StorageFlatDatabaseHealingRangeRequestTest { @BeforeEach public void setup() { final StorageProvider storageProvider = new InMemoryKeyValueStorageProvider(); - worldStateStorage = - new BonsaiWorldStateKeyValueStorage(storageProvider, new NoOpMetricsSystem()); - proofProvider = new WorldStateProofProvider(worldStateStorage); + + worldStateKeyValueStorage = + new BonsaiWorldStateKeyValueStorage( + storageProvider, + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG); + worldStateStorageCoordinator = new WorldStateStorageCoordinator(worldStateKeyValueStorage); + proofProvider = new WorldStateProofProvider(worldStateStorageCoordinator); trie = TrieGenerator.generateTrie( - worldStateStorage, + worldStateStorageCoordinator, accounts.stream().map(Address::addressHash).collect(Collectors.toList())); account0Hash = accounts.get(0).addressHash(); account0StorageRoot = @@ -99,7 +108,7 @@ void shouldReturnChildRequests() { final StoredMerklePatriciaTrie storageTrie = new StoredMerklePatriciaTrie<>( (location, hash) -> - worldStateStorage.getAccountStorageTrieNode(account0Hash, location, hash), + worldStateKeyValueStorage.getAccountStorageTrieNode(account0Hash, location, hash), account0StorageRoot, b -> b, b -> b); @@ -143,7 +152,9 @@ void shouldReturnChildRequests() { // Verify that the start key hash of the snapDataRequest is greater than the last key in the // slots TreeMap List childRequests = - request.getChildRequests(downloadState, worldStateStorage, snapSyncState).toList(); + request + .getChildRequests(downloadState, worldStateStorageCoordinator, snapSyncState) + .toList(); Assertions.assertThat(childRequests).hasSizeGreaterThan(1); StorageFlatDatabaseHealingRangeRequest snapDataRequest = (StorageFlatDatabaseHealingRangeRequest) childRequests.get(0); @@ -156,7 +167,7 @@ void shouldNotReturnChildRequestsWhenNoMoreSlots() { final StoredMerklePatriciaTrie storageTrie = new StoredMerklePatriciaTrie<>( (location, hash) -> - worldStateStorage.getAccountStorageTrieNode(account0Hash, location, hash), + worldStateKeyValueStorage.getAccountStorageTrieNode(account0Hash, location, hash), account0StorageRoot, b -> b, b -> b); @@ -187,7 +198,7 @@ void shouldNotReturnChildRequestsWhenNoMoreSlots() { // Verify that no child requests are returned from the request final Stream childRequests = - request.getChildRequests(downloadState, worldStateStorage, snapSyncState); + request.getChildRequests(downloadState, worldStateStorageCoordinator, snapSyncState); Assertions.assertThat(childRequests).isEmpty(); } @@ -197,7 +208,7 @@ void doNotPersistWhenProofIsValid() { final StoredMerklePatriciaTrie storageTrie = new StoredMerklePatriciaTrie<>( (location, hash) -> - worldStateStorage.getAccountStorageTrieNode(account0Hash, location, hash), + worldStateKeyValueStorage.getAccountStorageTrieNode(account0Hash, location, hash), account0StorageRoot, b -> b, b -> b); @@ -238,9 +249,9 @@ void doNotPersistWhenProofIsValid() { // Add local data to the request request.addLocalData(proofProvider, slots, new ArrayDeque<>(proofs)); - WorldStateStorage.Updater updater = Mockito.spy(worldStateStorage.updater()); + WorldStateKeyValueStorage.Updater updater = Mockito.spy(worldStateKeyValueStorage.updater()); request.doPersist( - worldStateStorage, + worldStateStorageCoordinator, updater, downloadState, snapSyncState, @@ -254,7 +265,7 @@ void doHealAndPersistWhenProofIsInvalid() { final StoredMerklePatriciaTrie storageTrie = new StoredMerklePatriciaTrie<>( (location, hash) -> - worldStateStorage.getAccountStorageTrieNode(account0Hash, location, hash), + worldStateKeyValueStorage.getAccountStorageTrieNode(account0Hash, location, hash), account0StorageRoot, b -> b, b -> b); @@ -309,9 +320,9 @@ void doHealAndPersistWhenProofIsInvalid() { request.addLocalData(proofProvider, slots, new ArrayDeque<>(proofs)); BonsaiWorldStateKeyValueStorage.Updater updater = - (BonsaiWorldStateKeyValueStorage.Updater) Mockito.spy(worldStateStorage.updater()); + Mockito.spy(worldStateKeyValueStorage.updater()); request.doPersist( - worldStateStorage, + worldStateStorageCoordinator, updater, downloadState, snapSyncState, diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/StorageTrieNodeHealingRequestTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/StorageTrieNodeHealingRequestTest.java index 447b37d14ce..4f8299f9acd 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/StorageTrieNodeHealingRequestTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/snapsync/request/heal/StorageTrieNodeHealingRequestTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,20 +14,22 @@ */ package org.hyperledger.besu.ethereum.eth.sync.snapsync.request.heal; +import static org.assertj.core.api.Assertions.assertThat; + import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; import org.hyperledger.besu.ethereum.core.TrieGenerator; -import org.hyperledger.besu.ethereum.eth.sync.snapsync.SnapWorldDownloadState; import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.ethereum.storage.StorageProvider; -import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.trie.MerkleTrie; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.ethereum.worldstate.StateTrieAccountValue; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; import java.util.List; @@ -36,19 +38,18 @@ import org.apache.tuweni.bytes.Bytes; import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsSource; -import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) class StorageTrieNodeHealingRequestTest { - @Mock private SnapWorldDownloadState downloadState; final List
accounts = List.of( Address.fromHexString("0xdeadbeef"), @@ -56,7 +57,7 @@ class StorageTrieNodeHealingRequestTest { Address.fromHexString("0xdeadbeea"), Address.fromHexString("0xdeadbeeb")); - private WorldStateStorage worldStateStorage; + private WorldStateStorageCoordinator worldStateStorageCoordinator; private Hash account0Hash; private Hash account0StorageRoot; @@ -70,15 +71,21 @@ public Stream provideArguments(final ExtensionContext conte public void setup(final DataStorageFormat storageFormat) { if (storageFormat.equals(DataStorageFormat.FOREST)) { - worldStateStorage = new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()); + worldStateStorageCoordinator = + new WorldStateStorageCoordinator( + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage())); } else { final StorageProvider storageProvider = new InMemoryKeyValueStorageProvider(); - worldStateStorage = - new BonsaiWorldStateKeyValueStorage(storageProvider, new NoOpMetricsSystem()); + worldStateStorageCoordinator = + new WorldStateStorageCoordinator( + new BonsaiWorldStateKeyValueStorage( + storageProvider, + new NoOpMetricsSystem(), + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG)); } final MerkleTrie trie = TrieGenerator.generateTrie( - worldStateStorage, + worldStateStorageCoordinator, accounts.stream().map(Address::addressHash).collect(Collectors.toList())); account0Hash = accounts.get(0).addressHash(); @@ -99,7 +106,7 @@ void shouldDetectExistingData(final DataStorageFormat storageFormat) { new StorageTrieNodeHealingRequest( account0StorageRoot, account0Hash, Hash.EMPTY, Bytes.EMPTY); - Assertions.assertThat(request.getExistingData(downloadState, worldStateStorage)).isPresent(); + Assertions.assertThat(request.getExistingData(worldStateStorageCoordinator)).isPresent(); } @ParameterizedTest @@ -109,6 +116,13 @@ void shouldDetectMissingData(final DataStorageFormat storageFormat) { final StorageTrieNodeHealingRequest request = new StorageTrieNodeHealingRequest(Hash.EMPTY, account0Hash, Hash.EMPTY, Bytes.EMPTY); - Assertions.assertThat(request.getExistingData(downloadState, worldStateStorage)).isEmpty(); + Assertions.assertThat(request.getExistingData(worldStateStorageCoordinator)).isEmpty(); + } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DetermineCommonAncestorTaskParameterizedTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DetermineCommonAncestorTaskParameterizedTest.java index 3eddd2bae24..73d5e5138b4 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DetermineCommonAncestorTaskParameterizedTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DetermineCommonAncestorTaskParameterizedTest.java @@ -20,6 +20,7 @@ import static org.mockito.Mockito.mock; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockBody; @@ -43,7 +44,6 @@ import java.io.IOException; import java.util.List; -import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -51,6 +51,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -150,7 +151,7 @@ public void searchesAgainstNetwork(final int headerRequestSize, final int common final EthContext ethContext = ethProtocolManager.ethContext(); final ProtocolContext protocolContext = - new ProtocolContext(localBlockchain, worldStateArchive, null, Optional.empty()); + new ProtocolContext(localBlockchain, worldStateArchive, null, new BadBlockManager()); final EthTask task = DetermineCommonAncestorTask.create( @@ -174,4 +175,11 @@ public void searchesAgainstNetwork(final int headerRequestSize, final int common assertThat(actualResult.get().getHash()) .isEqualTo(MainnetBlockHeaderFunctions.createHash(commonHeader)); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DetermineCommonAncestorTaskTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DetermineCommonAncestorTaskTest.java index ca183d57af0..1b19a076d2c 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DetermineCommonAncestorTaskTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DetermineCommonAncestorTaskTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.verify; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.Blockchain; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; @@ -54,7 +55,6 @@ import org.hyperledger.besu.util.ExceptionUtils; import java.util.List; -import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; @@ -88,7 +88,7 @@ public void setup() { EthProtocolConfiguration.defaultConfig()); ethContext = ethProtocolManager.ethContext(); protocolContext = - new ProtocolContext(localBlockchain, worldStateArchive, null, Optional.empty()); + new ProtocolContext(localBlockchain, worldStateArchive, null, new BadBlockManager()); } @Test diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DownloadHeaderSequenceTaskTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DownloadHeaderSequenceTaskTest.java index d7a36a5d8f4..c168594d0c1 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DownloadHeaderSequenceTaskTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/DownloadHeaderSequenceTaskTest.java @@ -16,21 +16,35 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; +import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManagerTestUtil; import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer; import org.hyperledger.besu.ethereum.eth.manager.ethtaskutils.RetryingMessageTaskTest; import org.hyperledger.besu.ethereum.eth.manager.exceptions.MaxRetriesReachedException; +import org.hyperledger.besu.ethereum.eth.manager.task.AbstractPeerTask.PeerTaskResult; import org.hyperledger.besu.ethereum.eth.manager.task.EthTask; import org.hyperledger.besu.ethereum.eth.messages.BlockHeadersMessage; import org.hyperledger.besu.ethereum.eth.messages.EthPV62; import org.hyperledger.besu.ethereum.eth.sync.ValidationPolicy; +import org.hyperledger.besu.ethereum.eth.sync.tasks.exceptions.InvalidBlockException; +import org.hyperledger.besu.ethereum.mainnet.BlockHeaderValidator; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -96,6 +110,7 @@ public void failsWhenPeerReturnsOnlyReferenceHeader() { assertThat(future.isDone()).isTrue(); assertThat(future.isCompletedExceptionally()).isTrue(); assertThatThrownBy(future::get).hasCauseInstanceOf(MaxRetriesReachedException.class); + assertNoBadBlocks(); } @Test @@ -118,8 +133,7 @@ public void failsWhenPeerReturnsOnlySubsetOfHeaders() { final CompletableFuture> future = task.run(); // Filter response to include only reference header and previous header - final RespondingEthPeer.Responder fullResponder = - RespondingEthPeer.blockchainResponder(blockchain); + final RespondingEthPeer.Responder fullResponder = getFullResponder(); final RespondingEthPeer.Responder responder = (cap, message) -> { final Optional fullResponse = fullResponder.respond(cap, message); @@ -140,5 +154,262 @@ public void failsWhenPeerReturnsOnlySubsetOfHeaders() { assertThat(future.isDone()).isTrue(); assertThat(future.isCompletedExceptionally()).isTrue(); assertThatThrownBy(future::get).hasCauseInstanceOf(MaxRetriesReachedException.class); + assertNoBadBlocks(); + } + + @Test + public void marksBadBlockWhenHeaderValidationFails() { + final RespondingEthPeer respondingPeer = + EthProtocolManagerTestUtil.createPeer(ethProtocolManager); + // Set up a chain with an invalid block + final int blockCount = 5; + final long startBlock = blockchain.getChainHeadBlockNumber() - blockCount; + final List chain = getBlockSequence(startBlock, blockCount); + final Block badBlock = chain.get(2); + ProtocolSchedule protocolScheduleSpy = setupHeaderValidationToFail(badBlock.getHeader()); + + // Execute the task + final BlockHeader referenceHeader = chain.get(blockCount - 1).getHeader(); + final EthTask> task = + DownloadHeaderSequenceTask.endingAtHeader( + protocolScheduleSpy, + protocolContext, + ethContext, + referenceHeader, + blockCount - 1, // The reference header is not included in this count + maxRetries, + validationPolicy, + metricsSystem); + final CompletableFuture> future = task.run(); + final RespondingEthPeer.Responder fullResponder = getFullResponder(); + respondingPeer.respondWhile(fullResponder, () -> !future.isDone()); + + // Check that the future completed exceptionally + assertThat(future.isCompletedExceptionally()).isTrue(); + assertThatThrownBy(future::get) + .hasCauseInstanceOf(InvalidBlockException.class) + .hasMessageContaining("Header failed validation"); + + // Check bad blocks + assertBadBlock(badBlock); + } + + @Test + public void processHeaders_markBadBlockWhenHeaderValidationFails() { + final RespondingEthPeer respondingPeer = + EthProtocolManagerTestUtil.createPeer(ethProtocolManager); + // Set up a chain with an invalid block + final int blockCount = 5; + final long startBlock = blockchain.getChainHeadBlockNumber() - blockCount; + final List chain = getBlockSequence(startBlock, blockCount); + final Block badBlock = chain.get(2); + ProtocolSchedule protocolScheduleSpy = setupHeaderValidationToFail(badBlock.getHeader()); + + // Execute the task + final BlockHeader referenceHeader = chain.get(blockCount - 1).getHeader(); + final DownloadHeaderSequenceTask task = + DownloadHeaderSequenceTask.endingAtHeader( + protocolScheduleSpy, + protocolContext, + ethContext, + referenceHeader, + blockCount - 1, // The reference header is not included in this count + maxRetries, + validationPolicy, + metricsSystem); + + // Run + final List responseHeaders = + chain.stream() + .map(Block::getHeader) + .sorted(Comparator.comparing(BlockHeader::getNumber).reversed()) + .toList(); + PeerTaskResult> peerResponse = + new PeerTaskResult<>(respondingPeer.getEthPeer(), responseHeaders); + final CompletableFuture> future = task.processHeaders(peerResponse); + respondingPeer.respondWhile(this.getFullResponder(), () -> !future.isDone()); + + // Check that the future completed exceptionally + assertThat(future.isCompletedExceptionally()).isTrue(); + assertThatThrownBy(future::get) + .hasCauseInstanceOf(InvalidBlockException.class) + .hasMessageContaining("Header failed validation"); + + // Check bad blocks + assertBadBlock(badBlock); + } + + @Test + public void processHeaders_markBadBlockHashWhenHeaderValidationFailsAndBodyUnavailable() { + final RespondingEthPeer respondingPeer = + EthProtocolManagerTestUtil.createPeer(ethProtocolManager); + // Set up a chain with an invalid block + final int blockCount = 5; + final long startBlock = blockchain.getChainHeadBlockNumber() - blockCount; + final List chain = getBlockSequence(startBlock, blockCount); + final Block badBlock = chain.get(2); + ProtocolSchedule protocolScheduleSpy = setupHeaderValidationToFail(badBlock.getHeader()); + + // Set up the task + final BlockHeader referenceHeader = chain.get(blockCount - 1).getHeader(); + final DownloadHeaderSequenceTask task = + DownloadHeaderSequenceTask.endingAtHeader( + protocolScheduleSpy, + protocolContext, + ethContext, + referenceHeader, + blockCount - 1, // The reference header is not included in this count + maxRetries, + validationPolicy, + metricsSystem); + + // Run + final List responseHeaders = + chain.stream() + .map(Block::getHeader) + .sorted(Comparator.comparing(BlockHeader::getNumber).reversed()) + .toList(); + PeerTaskResult> peerResponse = + new PeerTaskResult<>(respondingPeer.getEthPeer(), responseHeaders); + final CompletableFuture> future = task.processHeaders(peerResponse); + // Use empty responder so block body cannot be retrieved + respondingPeer.respondWhile(RespondingEthPeer.emptyResponder(), () -> !future.isDone()); + + // Check that the future completed exceptionally + assertThat(future.isCompletedExceptionally()).isTrue(); + assertThatThrownBy(future::get) + .hasCauseInstanceOf(InvalidBlockException.class) + .hasMessageContaining("Header failed validation"); + + // Check bad blocks + assertBadHeader(badBlock.getHeader()); + } + + @Test + public void processHeaders_doesNotMarkBadBlockForOutOfRangeResponse() { + final RespondingEthPeer respondingPeer = + EthProtocolManagerTestUtil.createPeer(ethProtocolManager); + + // Set up the task + final int blockCount = 3; + final long startBlock = blockchain.getChainHeadBlockNumber() - blockCount; + final List chain = getBlockSequence(startBlock, blockCount); + final int segmentLength = blockCount - 1; // The reference header is not included in this count + final BlockHeader referenceHeader = chain.get(blockCount - 1).getHeader(); + final DownloadHeaderSequenceTask task = + DownloadHeaderSequenceTask.endingAtHeader( + protocolSchedule, + protocolContext, + ethContext, + referenceHeader, + segmentLength, + maxRetries, + validationPolicy, + metricsSystem); + + // Run + final Block outOfRangeBlock = blockchain.getBlockByNumber(startBlock - 1).get(); + final List headerResponse = + chain.stream() + .map(Block::getHeader) + .sorted(Comparator.comparing(BlockHeader::getNumber).reversed()) + .collect(Collectors.toCollection(ArrayList::new)); + headerResponse.add(outOfRangeBlock.getHeader()); + + PeerTaskResult> peerResponse = + new PeerTaskResult<>(respondingPeer.getEthPeer(), headerResponse); + final CompletableFuture> future = task.processHeaders(peerResponse); + + // Check that the future completed exceptionally + assertThat(future.isCompletedExceptionally()).isTrue(); + assertThatThrownBy(future::get) + .hasCauseInstanceOf(InvalidBlockException.class) + .hasMessageNotContaining("Header failed validation") + .hasMessageContaining("Out-of-range"); + + // Check bad blocks + assertNoBadBlocks(); + } + + @Test + public void processHeaders_doesNotMarkBadBlockForMisorderedBlocks() { + final RespondingEthPeer respondingPeer = + EthProtocolManagerTestUtil.createPeer(ethProtocolManager); + + // Set up the task + final int blockCount = 5; + final long startBlock = blockchain.getChainHeadBlockNumber() - blockCount; + final List chain = getBlockSequence(startBlock, blockCount); + final int segmentLength = blockCount - 1; // The reference header is not included in this count + final BlockHeader referenceHeader = blockchain.getChainHeadHeader(); + final DownloadHeaderSequenceTask task = + DownloadHeaderSequenceTask.endingAtHeader( + protocolSchedule, + protocolContext, + ethContext, + referenceHeader, + segmentLength, + maxRetries, + validationPolicy, + metricsSystem); + + // Return blocks in ascending order + final List responseHeaders = chain.stream().map(Block::getHeader).toList(); + PeerTaskResult> peerResponse = + new PeerTaskResult<>(respondingPeer.getEthPeer(), responseHeaders); + final CompletableFuture> future = task.processHeaders(peerResponse); + + // Check that the future completed exceptionally + assertThat(future.isCompletedExceptionally()).isTrue(); + assertThatThrownBy(future::get) + .hasCauseInstanceOf(InvalidBlockException.class) + .hasMessageNotContaining("Header failed validation") + .hasMessageContaining("misordered blocks"); + + // Check bad blocks + assertNoBadBlocks(); + } + + private List getBlockSequence(final long firstBlockNumber, final int blockCount) { + final List blocks = new ArrayList<>(blockCount); + for (int i = 0; i < blockCount; i++) { + final Block block = blockchain.getBlockByNumber(firstBlockNumber + i).get(); + blocks.add(block); + } + + return blocks; + } + + private ProtocolSchedule setupHeaderValidationToFail(final BlockHeader badHeader) { + ProtocolSchedule protocolScheduleSpy = spy(protocolSchedule); + ProtocolSpec failingProtocolSpec = spy(protocolSchedule.getByBlockHeader(badHeader)); + BlockHeaderValidator failingValidator = mock(BlockHeaderValidator.class); + when(failingValidator.validateHeader(eq(badHeader), any(), any(), any())).thenReturn(false); + when(failingProtocolSpec.getBlockHeaderValidator()).thenReturn(failingValidator); + doAnswer( + invocation -> { + BlockHeader header = invocation.getArgument(0); + if (header.getNumber() == badHeader.getNumber()) { + return failingProtocolSpec; + } else { + return invocation.callRealMethod(); + } + }) + .when(protocolScheduleSpy) + .getByBlockHeader(any(BlockHeader.class)); + + return protocolScheduleSpy; + } + + private void assertBadBlock(final Block badBlock) { + BadBlockManager badBlockManager = protocolContext.getBadBlockManager(); + assertThat(badBlockManager.getBadBlocks()).containsExactly(badBlock); + assertThat(badBlockManager.getBadHeaders()).isEmpty(); + } + + private void assertBadHeader(final BlockHeader badHeader) { + BadBlockManager badBlockManager = protocolContext.getBadBlockManager(); + assertThat(badBlockManager.getBadHeaders()).containsExactly(badHeader); + assertThat(badBlockManager.getBadBlocks()).isEmpty(); } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/PersistBlockTaskTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/PersistBlockTaskTest.java index 5d02f398470..dd5d79b6670 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/PersistBlockTaskTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/sync/tasks/PersistBlockTaskTest.java @@ -29,9 +29,9 @@ import org.hyperledger.besu.ethereum.eth.sync.tasks.exceptions.InvalidBlockException; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import java.util.Arrays; import java.util.Collections; @@ -40,6 +40,7 @@ import java.util.stream.Stream; import org.awaitility.Awaitility; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -420,4 +421,11 @@ public void cancelAfterRunning(final DataStorageFormat storageFormat) { assertThat(result.isCancelled()).isTrue(); assertThat(blockchain.contains(nextBlock.getHash())).isFalse(); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/AbstractTransactionPoolTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/AbstractTransactionPoolTest.java index a87ca36bfb8..e5ec06fd356 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/AbstractTransactionPoolTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/AbstractTransactionPoolTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -32,7 +32,6 @@ import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -44,13 +43,14 @@ import static org.mockito.Mockito.when; import static org.mockito.quality.Strictness.LENIENT; -import org.hyperledger.besu.config.StubGenesisConfigOptions; +import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.crypto.KeyPair; import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.BlobTestFixture; import org.hyperledger.besu.ethereum.core.Block; @@ -60,6 +60,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.ExecutionContextTestFixture; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionReceipt; @@ -87,8 +88,10 @@ import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; -import org.hyperledger.besu.plugin.services.txvalidator.PluginTransactionValidator; -import org.hyperledger.besu.plugin.services.txvalidator.PluginTransactionValidatorFactory; +import org.hyperledger.besu.plugin.services.TransactionPoolValidatorService; +import org.hyperledger.besu.plugin.services.txvalidator.PluginTransactionPoolValidator; +import org.hyperledger.besu.plugin.services.txvalidator.PluginTransactionPoolValidatorFactory; +import org.hyperledger.besu.testutil.DeterministicEthScheduler; import org.hyperledger.besu.util.number.Percentage; import java.math.BigInteger; @@ -100,7 +103,6 @@ import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; -import java.util.function.Supplier; import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; @@ -129,6 +131,9 @@ public abstract class AbstractTransactionPoolTest { private static final KeyPair KEY_PAIR2 = SignatureAlgorithmFactory.getInstance().generateKeyPair(); protected static final Wei BASE_FEE_FLOOR = Wei.of(7L); + protected static final Wei DEFAULT_MIN_GAS_PRICE = Wei.of(50L); + + protected final EthScheduler ethScheduler = new DeterministicEthScheduler(); @Mock(answer = Answers.RETURNS_DEEP_STUBS) protected TransactionValidatorFactory transactionValidatorFactory; @@ -186,17 +191,24 @@ protected TransactionTestFixture createBaseTransactionBaseFeeMarket(final int no protected abstract ExecutionContextTestFixture createExecutionContextTestFixture(); protected static ExecutionContextTestFixture createExecutionContextTestFixtureBaseFeeMarket() { + final var genesisConfigFile = GenesisConfigFile.fromResource("/txpool-test-genesis.json"); final ProtocolSchedule protocolSchedule = new ProtocolScheduleBuilder( - new StubGenesisConfigOptions().londonBlock(0L).baseFeePerGas(10L), + genesisConfigFile.getConfigOptions(), BigInteger.valueOf(1), ProtocolSpecAdapters.create(0, Function.identity()), new PrivacyParameters(), false, - EvmConfiguration.DEFAULT) + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()) .createProtocolSchedule(); final ExecutionContextTestFixture executionContextTestFixture = - ExecutionContextTestFixture.builder().protocolSchedule(protocolSchedule).build(); + ExecutionContextTestFixture.builder(genesisConfigFile) + .protocolSchedule(protocolSchedule) + .build(); final Block block = new Block( @@ -233,15 +245,12 @@ public void setUp() { ethProtocolManager = EthProtocolManagerTestUtil.create(); ethContext = spy(ethProtocolManager.ethContext()); - final EthScheduler ethScheduler = mock(EthScheduler.class); + final EthScheduler ethScheduler = spy(ethContext.getScheduler()); syncTaskCapture = ArgumentCaptor.forClass(Runnable.class); doNothing().when(ethScheduler).scheduleSyncWorkerTask(syncTaskCapture.capture()); - doAnswer(invocation -> ((Supplier) invocation.getArguments()[0]).get()) - .when(ethScheduler) - .scheduleServiceTask(any(Supplier.class)); doReturn(ethScheduler).when(ethContext).getScheduler(); - peerTransactionTracker = new PeerTransactionTracker(); + peerTransactionTracker = new PeerTransactionTracker(ethContext.getEthPeers()); transactionBroadcaster = spy( new TransactionBroadcaster( @@ -258,21 +267,16 @@ protected TransactionPool createTransactionPool() { return createTransactionPool(b -> b.minGasPrice(Wei.of(2))); } - protected TransactionPool createTransactionPool( - final Consumer configConsumer) { - return createTransactionPool(configConsumer, null); - } - private TransactionPool createTransactionPool( - final Consumer configConsumer, - final PluginTransactionValidatorFactory pluginTransactionValidatorFactory) { + final Consumer configConsumer) { final ImmutableTransactionPoolConfiguration.Builder configBuilder = ImmutableTransactionPoolConfiguration.builder(); configConsumer.accept(configBuilder); final TransactionPoolConfiguration poolConfig = configBuilder.build(); final TransactionPoolReplacementHandler transactionReplacementHandler = - new TransactionPoolReplacementHandler(poolConfig.getPriceBump()); + new TransactionPoolReplacementHandler( + poolConfig.getPriceBump(), poolConfig.getBlobPriceBump()); final BiFunction transactionReplacementTester = (t1, t2) -> @@ -290,7 +294,7 @@ private TransactionPool createTransactionPool( ethContext, new TransactionPoolMetrics(metricsSystem), poolConfig, - pluginTransactionValidatorFactory); + new BlobCache()); txPool.setEnabled(); return txPool; } @@ -458,6 +462,7 @@ public void shouldNotReAddTransactionsThatAreInBothForksWhenReorgHappens() { } @Test + @EnabledIf("isBaseFeeMarket") public void shouldReAddBlobTxsWhenReorgHappens() { givenTransactionIsValid(transaction0); givenTransactionIsValid(transaction1); @@ -556,11 +561,11 @@ public void shouldNotAddRemoteTransactionsThatAreInvalidAccordingToStateDependen assertTransactionNotPending(transaction1); verify(transactionBroadcaster).onTransactionsAdded(singletonList(transaction0)); verify(transactionValidatorFactory.get()) - .validate(eq(transaction0), any(Optional.class), any()); + .validate(eq(transaction0), any(Optional.class), any(Optional.class), any()); verify(transactionValidatorFactory.get()) .validateForSender(eq(transaction0), eq(null), any(TransactionValidationParams.class)); verify(transactionValidatorFactory.get()) - .validate(eq(transaction1), any(Optional.class), any()); + .validate(eq(transaction1), any(Optional.class), any(Optional.class), any()); verify(transactionValidatorFactory.get()).validateForSender(eq(transaction1), any(), any()); verifyNoMoreInteractions(transactionValidatorFactory.get()); } @@ -731,7 +736,9 @@ public void shouldCallValidatorWithExpectedValidationParameters() { final ArgumentCaptor txValidationParamCaptor = ArgumentCaptor.forClass(TransactionValidationParams.class); - when(transactionValidatorFactory.get().validate(eq(transaction0), any(Optional.class), any())) + when(transactionValidatorFactory + .get() + .validate(eq(transaction0), any(Optional.class), any(Optional.class), any())) .thenReturn(valid()); when(transactionValidatorFactory .get() @@ -793,11 +800,13 @@ public void shouldAcceptRemoteTransactionEvenIfFeeCapExceeded(final boolean hasP @ParameterizedTest @ValueSource(booleans = {true, false}) public void transactionNotRejectedByPluginShouldBeAdded(final boolean noLocalPriority) { - final PluginTransactionValidatorFactory pluginTransactionValidatorFactory = - getPluginTransactionValidatorFactoryReturning(null); // null -> not rejecting !! + final TransactionPoolValidatorService transactionPoolValidatorService = + getTransactionPoolValidatorServiceReturning(null); // null -> not rejecting !! this.transactionPool = createTransactionPool( - b -> b.noLocalPriority(noLocalPriority), pluginTransactionValidatorFactory); + b -> + b.noLocalPriority(noLocalPriority) + .transactionPoolValidatorService(transactionPoolValidatorService)); givenTransactionIsValid(transaction0); @@ -807,23 +816,27 @@ public void transactionNotRejectedByPluginShouldBeAdded(final boolean noLocalPri @ParameterizedTest @ValueSource(booleans = {true, false}) public void transactionRejectedByPluginShouldNotBeAdded(final boolean noLocalPriority) { - final PluginTransactionValidatorFactory pluginTransactionValidatorFactory = - getPluginTransactionValidatorFactoryReturning("false"); + final TransactionPoolValidatorService transactionPoolValidatorService = + getTransactionPoolValidatorServiceReturning("false"); this.transactionPool = createTransactionPool( - b -> b.noLocalPriority(noLocalPriority), pluginTransactionValidatorFactory); + b -> + b.noLocalPriority(noLocalPriority) + .transactionPoolValidatorService(transactionPoolValidatorService)); givenTransactionIsValid(transaction0); addAndAssertTransactionViaApiInvalid( - transaction0, TransactionInvalidReason.PLUGIN_TX_VALIDATOR); + transaction0, TransactionInvalidReason.PLUGIN_TX_POOL_VALIDATOR); } @Test public void remoteTransactionRejectedByPluginShouldNotBeAdded() { - final PluginTransactionValidatorFactory pluginTransactionValidatorFactory = - getPluginTransactionValidatorFactoryReturning("false"); - this.transactionPool = createTransactionPool(b -> {}, pluginTransactionValidatorFactory); + final TransactionPoolValidatorService transactionPoolValidatorService = + getTransactionPoolValidatorServiceReturning("false"); + this.transactionPool = + createTransactionPool( + b -> b.transactionPoolValidatorService(transactionPoolValidatorService)); givenTransactionIsValid(transaction0); @@ -1269,11 +1282,18 @@ public void addRemoteTransactionsShouldAllowDuplicates() { .containsExactlyInAnyOrder(transaction1, transaction2a, transaction3); } - private static PluginTransactionValidatorFactory getPluginTransactionValidatorFactoryReturning( + private static TransactionPoolValidatorService getTransactionPoolValidatorServiceReturning( final String errorMessage) { - final PluginTransactionValidator pluginTransactionValidator = - transaction -> Optional.ofNullable(errorMessage); - return () -> pluginTransactionValidator; + return new TransactionPoolValidatorService() { + @Override + public PluginTransactionPoolValidator createTransactionValidator() { + return (transaction, isLocal, hasPriority) -> Optional.ofNullable(errorMessage); + } + + @Override + public void registerPluginTransactionValidatorFactory( + final PluginTransactionPoolValidatorFactory pluginTransactionPoolValidatorFactory) {} + }; } @SuppressWarnings("unused") @@ -1350,7 +1370,9 @@ protected void addAndAssertTransactionViaApiInvalid( @SuppressWarnings("unchecked") protected void givenTransactionIsValid(final Transaction transaction) { - when(transactionValidatorFactory.get().validate(eq(transaction), any(Optional.class), any())) + when(transactionValidatorFactory + .get() + .validate(eq(transaction), any(Optional.class), any(Optional.class), any())) .thenReturn(valid()); when(transactionValidatorFactory .get() diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/AbstractTransactionReplacementTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/AbstractTransactionReplacementTest.java index f979fec3924..e6aa0b53ce1 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/AbstractTransactionReplacementTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/AbstractTransactionReplacementTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -18,10 +18,12 @@ import static org.mockito.Mockito.when; import org.hyperledger.besu.datatypes.TransactionType; +import org.hyperledger.besu.datatypes.VersionedHash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.Transaction; import java.math.BigInteger; +import java.util.List; public class AbstractTransactionReplacementTest { protected static PendingTransaction frontierTx(final long price) { @@ -50,4 +52,20 @@ protected static PendingTransaction eip1559Tx( when(pendingTransaction.getTransaction()).thenReturn(transaction); return pendingTransaction; } + + protected static PendingTransaction blobTx( + final long maxPriorityFeePerGas, final long maxFeePerGas, final long maxFeePerBlobGas) { + final PendingTransaction pendingTransaction = mock(PendingTransaction.class); + final Transaction transaction = + Transaction.builder() + .chainId(BigInteger.ZERO) + .type(TransactionType.BLOB) + .maxPriorityFeePerGas(Wei.of(maxPriorityFeePerGas)) + .maxFeePerGas(Wei.of(maxFeePerGas)) + .maxFeePerBlobGas(Wei.of(maxFeePerBlobGas)) + .versionedHashes(List.of(VersionedHash.DEFAULT_VERSIONED_HASH)) + .build(); + when(pendingTransaction.getTransaction()).thenReturn(transaction); + return pendingTransaction; + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageSenderTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageSenderTest.java index ec6f772c796..4e544d39d07 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageSenderTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/NewPooledTransactionHashesMessageSenderTest.java @@ -30,6 +30,7 @@ import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.eth.EthProtocol; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; +import org.hyperledger.besu.ethereum.eth.manager.EthPeers; import org.hyperledger.besu.ethereum.eth.manager.MockPeerConnection; import org.hyperledger.besu.ethereum.eth.messages.EthPV65; import org.hyperledger.besu.ethereum.eth.messages.NewPooledTransactionHashesMessage; @@ -47,6 +48,7 @@ import org.mockito.ArgumentCaptor; public class NewPooledTransactionHashesMessageSenderTest { + private final EthPeers ethPeers = mock(EthPeers.class); private final EthPeer peer1 = mock(EthPeer.class); private final EthPeer peer2 = mock(EthPeer.class); @@ -63,7 +65,7 @@ public class NewPooledTransactionHashesMessageSenderTest { @BeforeEach public void setUp() { - transactionTracker = new PeerTransactionTracker(); + transactionTracker = new PeerTransactionTracker(ethPeers); messageSender = new NewPooledTransactionHashesMessageSender(transactionTracker); final Transaction tx = mock(Transaction.class); pendingTransactions = mock(PendingTransactions.class); @@ -78,9 +80,9 @@ public void setUp() { @Test public void shouldSendPendingTransactionsToEachPeer() throws Exception { - transactionTracker.addToPeerSendQueue(peer1, transaction1); - transactionTracker.addToPeerSendQueue(peer1, transaction2); - transactionTracker.addToPeerSendQueue(peer2, transaction3); + transactionTracker.addToPeerHashSendQueue(peer1, transaction1); + transactionTracker.addToPeerHashSendQueue(peer1, transaction2); + transactionTracker.addToPeerHashSendQueue(peer2, transaction3); List.of(peer1, peer2).forEach(messageSender::sendTransactionHashesToPeer); @@ -96,7 +98,8 @@ public void shouldSendTransactionsInBatchesWithLimit() throws Exception { final Set transactions = generator.transactions(6000).stream().collect(Collectors.toSet()); - transactions.forEach(transaction -> transactionTracker.addToPeerSendQueue(peer1, transaction)); + transactions.forEach( + transaction -> transactionTracker.addToPeerHashSendQueue(peer1, transaction)); messageSender.sendTransactionHashesToPeer(peer1); final ArgumentCaptor messageDataArgumentCaptor = diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PeerTransactionTrackerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PeerTransactionTrackerTest.java index e1f1c83f588..520f6641483 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PeerTransactionTrackerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PeerTransactionTrackerTest.java @@ -16,20 +16,26 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import org.hyperledger.besu.ethereum.core.BlockDataGenerator; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; +import org.hyperledger.besu.ethereum.eth.manager.EthPeers; + +import java.util.List; +import java.util.stream.Stream; import com.google.common.collect.ImmutableSet; import org.junit.jupiter.api.Test; public class PeerTransactionTrackerTest { + private final EthPeers ethPeers = mock(EthPeers.class); private final EthPeer ethPeer1 = mock(EthPeer.class); private final EthPeer ethPeer2 = mock(EthPeer.class); private final BlockDataGenerator generator = new BlockDataGenerator(); - private final PeerTransactionTracker tracker = new PeerTransactionTracker(); + private final PeerTransactionTracker tracker = new PeerTransactionTracker(ethPeers); private final Transaction transaction1 = generator.transaction(); private final Transaction transaction2 = generator.transaction(); private final Transaction transaction3 = generator.transaction(); @@ -79,6 +85,7 @@ public void shouldClearDataWhenPeerDisconnects() { tracker.addToPeerSendQueue(ethPeer1, transaction2); tracker.addToPeerSendQueue(ethPeer2, transaction3); + when(ethPeers.streamAllPeers()).thenReturn(Stream.of(ethPeer2)); tracker.onDisconnect(ethPeer1); assertThat(tracker.getEthPeersWithUnsentTransactions()).containsOnly(ethPeer2); @@ -90,4 +97,32 @@ public void shouldClearDataWhenPeerDisconnects() { assertThat(tracker.claimTransactionsToSendToPeer(ethPeer1)).containsOnly(transaction1); assertThat(tracker.claimTransactionsToSendToPeer(ethPeer2)).containsOnly(transaction3); } + + @Test + public void shouldClearDataForAllDisconnectedPeers() { + tracker.markTransactionsAsSeen(ethPeer1, List.of(transaction1)); + tracker.markTransactionsAsSeen(ethPeer2, List.of(transaction2)); + + when(ethPeers.streamAllPeers()).thenReturn(Stream.of(ethPeer2)); + tracker.onDisconnect(ethPeer1); + + // false because tracker removed for ethPeer1 + assertThat(tracker.hasPeerSeenTransaction(ethPeer1, transaction1)).isFalse(); + assertThat(tracker.hasPeerSeenTransaction(ethPeer2, transaction2)).isTrue(); + + // simulate a concurrent interaction, that just after the disconnection of the peer, + // recreates the transaction tackers for it + tracker.markTransactionsAsSeen(ethPeer1, List.of(transaction1)); + // ethPeer1 is here again, due to the above interaction with the tracker + assertThat(tracker.hasPeerSeenTransaction(ethPeer1, transaction1)).isTrue(); + + // disconnection of ethPeers2 will reconcile the tracker, removing also all the other + // disconnected peers + when(ethPeers.streamAllPeers()).thenReturn(Stream.of()); + tracker.onDisconnect(ethPeer2); + + // since no peers are connected, all the transaction trackers have been removed + assertThat(tracker.hasPeerSeenTransaction(ethPeer1, transaction1)).isFalse(); + assertThat(tracker.hasPeerSeenTransaction(ethPeer2, transaction2)).isFalse(); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionEstimatedMemorySizeTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionEstimatedMemorySizeTest.java index c9c81543a63..af54f668fe8 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionEstimatedMemorySizeTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/PendingTransactionEstimatedMemorySizeTest.java @@ -1,5 +1,5 @@ /* - * Copyright Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -58,7 +58,7 @@ public class PendingTransactionEstimatedMemorySizeTest extends BaseTransactionPo private static final Set> SHARED_CLASSES = Set.of(SignatureAlgorithm.class, TransactionType.class); private static final Set COMMON_CONSTANT_FIELD_PATHS = - Set.of(".value.ctor", ".hashNoSignature"); + Set.of(".value.ctor", ".hashNoSignature", ".signature.encoded.delegate"); private static final Set EIP1559_EIP4844_CONSTANT_FIELD_PATHS = Sets.union(COMMON_CONSTANT_FIELD_PATHS, Set.of(".gasPrice")); private static final Set FRONTIER_ACCESS_LIST_CONSTANT_FIELD_PATHS = @@ -69,7 +69,7 @@ public class PendingTransactionEstimatedMemorySizeTest extends BaseTransactionPo @Test public void toSize() { TransactionTestFixture preparedTx = - prepareTransaction(TransactionType.ACCESS_LIST, 10, Wei.of(500), 10, 0); + prepareTransaction(TransactionType.ACCESS_LIST, 10, Wei.of(500), Wei.ZERO, 10, 0); Transaction txTo = preparedTx.to(Optional.of(Address.extract(Bytes32.random()))).createTransaction(KEYS1); BytesValueRLPOutput rlpOut = new BytesValueRLPOutput(); @@ -115,7 +115,7 @@ public void toSize() { public void payloadSize() { TransactionTestFixture preparedTx = - prepareTransaction(TransactionType.ACCESS_LIST, 10, Wei.of(500), 10, 0); + prepareTransaction(TransactionType.ACCESS_LIST, 10, Wei.of(500), Wei.ZERO, 10, 0); Transaction txPayload = preparedTx.createTransaction(KEYS1); BytesValueRLPOutput rlpOut = new BytesValueRLPOutput(); txPayload.writeTo(rlpOut); @@ -207,7 +207,7 @@ private void blobsWithCommitmentsFieldSize( final long containerSize, final long itemSize) { TransactionTestFixture preparedTx = - prepareTransaction(TransactionType.BLOB, 10, Wei.of(500), 10, 1); + prepareTransaction(TransactionType.BLOB, 10, Wei.of(500), Wei.of(50), 10, 1); Transaction txBlob = preparedTx.createTransaction(KEYS1); BytesValueRLPOutput rlpOut = new BytesValueRLPOutput(); TransactionEncoder.encodeRLP(txBlob, rlpOut, EncodingContext.POOLED_TRANSACTION); @@ -239,7 +239,7 @@ private void blobsWithCommitmentsFieldSize( @Test public void blobsWithCommitmentsSize() { TransactionTestFixture preparedTx = - prepareTransaction(TransactionType.BLOB, 10, Wei.of(500), 10, 1); + prepareTransaction(TransactionType.BLOB, 10, Wei.of(500), Wei.of(50), 10, 1); Transaction txBlob = preparedTx.createTransaction(KEYS1); BytesValueRLPOutput rlpOut = new BytesValueRLPOutput(); TransactionEncoder.encodeRLP(txBlob, rlpOut, EncodingContext.POOLED_TRANSACTION); @@ -268,7 +268,7 @@ public void blobsWithCommitmentsSize() { public void pendingTransactionSize() { TransactionTestFixture preparedTx = - prepareTransaction(TransactionType.ACCESS_LIST, 10, Wei.of(500), 10, 0); + prepareTransaction(TransactionType.ACCESS_LIST, 10, Wei.of(500), Wei.ZERO, 10, 0); Transaction txPayload = preparedTx.createTransaction(KEYS1); BytesValueRLPOutput rlpOut = new BytesValueRLPOutput(); txPayload.writeTo(rlpOut); @@ -300,7 +300,7 @@ public void accessListSize() { final List ales = List.of(ale1); TransactionTestFixture preparedTx = - prepareTransaction(TransactionType.ACCESS_LIST, 0, Wei.of(500), 0, 0); + prepareTransaction(TransactionType.ACCESS_LIST, 0, Wei.of(500), Wei.ZERO, 0, 0); Transaction txAccessList = preparedTx.accessList(ales).createTransaction(KEYS1); BytesValueRLPOutput rlpOut = new BytesValueRLPOutput(); txAccessList.writeTo(rlpOut); @@ -381,7 +381,7 @@ public void baseEIP1559AndEIP4844TransactionMemorySize() { System.out.println("Base EIP1559 size: " + eip1559size); assertThat(eip1559size.sum()) - .isEqualTo(PendingTransaction.EIP1559_AND_EIP4844_BASE_MEMORY_SIZE); + .isEqualTo(PendingTransaction.EIP1559_AND_EIP4844_SHALLOW_MEMORY_SIZE); } @Test @@ -426,7 +426,7 @@ public void baseFrontierAndAccessListTransactionMemorySize() { System.out.println("Base Frontier size: " + frontierSize); assertThat(frontierSize.sum()) - .isEqualTo(PendingTransaction.FRONTIER_AND_ACCESS_LIST_BASE_MEMORY_SIZE); + .isEqualTo(PendingTransaction.FRONTIER_AND_ACCESS_LIST_SHALLOW_MEMORY_SIZE); } private GraphWalker getGraphWalker( diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TestNode.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TestNode.java index d593c38b5ea..c679183b0ff 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TestNode.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TestNode.java @@ -18,6 +18,7 @@ import static java.util.Objects.requireNonNull; import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryBlockchain; import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryWorldStateArchive; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -27,10 +28,13 @@ import org.hyperledger.besu.cryptoservices.NodeKey; import org.hyperledger.besu.cryptoservices.NodeKeyUtils; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.GenesisState; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; +import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.difficulty.fixed.FixedDifficultyProtocolSchedule; import org.hyperledger.besu.ethereum.eth.EthProtocol; @@ -40,8 +44,11 @@ import org.hyperledger.besu.ethereum.eth.manager.EthPeers; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; +import org.hyperledger.besu.ethereum.eth.sync.ChainHeadTracker; +import org.hyperledger.besu.ethereum.eth.sync.SyncMode; import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; +import org.hyperledger.besu.ethereum.forkid.ForkIdManager; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions; import org.hyperledger.besu.ethereum.p2p.config.DiscoveryConfiguration; @@ -75,6 +82,7 @@ import io.vertx.core.Vertx; import org.apache.tuweni.bytes.Bytes; +import org.mockito.Mockito; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -108,10 +116,16 @@ public TestNode( .setBindPort(listenPort) .setSupportedProtocols(EthProtocol.get())); - final GenesisConfigFile genesisConfigFile = GenesisConfigFile.development(); + final GenesisConfigFile genesisConfigFile = GenesisConfigFile.fromResource("/dev.json"); final ProtocolSchedule protocolSchedule = FixedDifficultyProtocolSchedule.create( - GenesisConfigFile.development().getConfigOptions(), false, EvmConfiguration.DEFAULT); + GenesisConfigFile.fromResource("/dev.json").getConfigOptions(), + false, + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()); final GenesisState genesisState = GenesisState.fromConfig(genesisConfigFile, protocolSchedule); final BlockHeaderFunctions blockHeaderFunctions = @@ -121,7 +135,7 @@ public TestNode( final WorldStateArchive worldStateArchive = createInMemoryWorldStateArchive(); genesisState.writeStateTo(worldStateArchive.getMutable()); final ProtocolContext protocolContext = - new ProtocolContext(blockchain, worldStateArchive, null, Optional.empty()); + new ProtocolContext(blockchain, worldStateArchive, null, new BadBlockManager()); final SyncState syncState = mock(SyncState.class); final SynchronizerConfiguration syncConfig = mock(SynchronizerConfiguration.class); @@ -147,8 +161,12 @@ public boolean isMessagePermitted(final EnodeURL destinationEnode, final int cod Bytes.random(64), 25, 25, - 25, - false); + false, + SyncMode.SNAP, + new ForkIdManager(blockchain, Collections.emptyList(), Collections.emptyList(), false)); + + final ChainHeadTracker mockCHT = getChainHeadTracker(); + ethPeers.setChainHeadTracker(mockCHT); final EthScheduler scheduler = new EthScheduler(1, 1, 1, metricsSystem); final EthContext ethContext = new EthContext(ethPeers, ethMessages, scheduler); @@ -162,8 +180,8 @@ public boolean isMessagePermitted(final EnodeURL destinationEnode, final int cod metricsSystem, syncState, TransactionPoolConfiguration.DEFAULT, - null, - new BlobCache()); + new BlobCache(), + MiningParameters.newDefault()); final EthProtocolManager ethProtocolManager = new EthProtocolManager( @@ -184,6 +202,7 @@ public boolean isMessagePermitted(final EnodeURL destinationEnode, final int cod NetworkRunner.builder() .subProtocols(EthProtocol.get()) .protocolManagers(singletonList(ethProtocolManager)) + .ethPeersShouldConnect((p, d) -> true) .network( capabilities -> DefaultP2PNetwork.builder() @@ -196,8 +215,8 @@ public boolean isMessagePermitted(final EnodeURL destinationEnode, final int cod .blockchain(blockchain) .blockNumberForks(Collections.emptyList()) .timestampForks(Collections.emptyList()) - .allConnectionsSupplier(ethPeers::getAllConnections) - .allActiveConnectionsSupplier(ethPeers::getAllActiveConnections) + .allConnectionsSupplier(ethPeers::streamAllConnections) + .allActiveConnectionsSupplier(ethPeers::streamAllActiveConnections) .build()) .metricsSystem(new NoOpMetricsSystem()) .build(); @@ -212,6 +231,16 @@ public boolean isMessagePermitted(final EnodeURL destinationEnode, final int cod selfPeer = DefaultPeer.fromEnodeURL(network.getLocalEnode().get()); } + private static ChainHeadTracker getChainHeadTracker() { + final ChainHeadTracker mockCHT = mock(ChainHeadTracker.class); + final BlockHeader mockBlockHeader = mock(BlockHeader.class); + Mockito.lenient().when(mockBlockHeader.getNumber()).thenReturn(0L); + Mockito.lenient() + .when(mockCHT.getBestHeaderFromPeer(any())) + .thenReturn(CompletableFuture.completedFuture(mockBlockHeader)); + return mockCHT; + } + public Bytes id() { return nodeKey.getPublicKey().getEncodedBytes(); } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionBroadcasterTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionBroadcasterTest.java index b7d6d054f93..b6faf666976 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionBroadcasterTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionBroadcasterTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -55,7 +55,7 @@ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) public class TransactionBroadcasterTest { - + private static final Long FIXED_RANDOM_SEED = 0L; @Mock private EthContext ethContext; @Mock private EthPeers ethPeers; @Mock private EthScheduler ethScheduler; @@ -92,12 +92,14 @@ public void setUp() { when(ethContext.getEthPeers()).thenReturn(ethPeers); when(ethContext.getScheduler()).thenReturn(ethScheduler); + // we use the fixed random seed to have a predictable shuffle of peers txBroadcaster = new TransactionBroadcaster( ethContext, transactionTracker, transactionsMessageSender, - newPooledTransactionHashesMessageSender); + newPooledTransactionHashesMessageSender, + FIXED_RANDOM_SEED); } @Test @@ -132,7 +134,7 @@ public void relayTransactionHashesFromPoolWhenPeerSupportEth65() { txBroadcaster.relayTransactionPoolTo(ethPeerWithEth65, pendingTxs); - verifyTransactionAddedToPeerSendingQueue(ethPeerWithEth65, txs); + verifyTransactionAddedToPeerHashSendingQueue(ethPeerWithEth65, txs); sendTaskCapture.getValue().run(); @@ -177,14 +179,16 @@ public void onTransactionsAddedWithOnlyFewEth65PeersSendFullTransactions() { List txs = toTransactionList(setupTransactionPool(1, 1)); txBroadcaster.onTransactionsAdded(txs); - - verifyTransactionAddedToPeerSendingQueue(ethPeerWithEth65, txs); + // the shuffled hash only peer list is always: + // [ethPeerWithEth65_3, ethPeerWithEth65_2, ethPeerWithEth65] + // so ethPeerWithEth65 and ethPeerWithEth65_2 are moved to the mixed broadcast list + verifyTransactionAddedToPeerHashSendingQueue(ethPeerWithEth65, txs); verifyTransactionAddedToPeerSendingQueue(ethPeerWithEth65_2, txs); sendTaskCapture.getAllValues().forEach(Runnable::run); - verify(transactionsMessageSender, times(2)).sendTransactionsToPeer(any(EthPeer.class)); - verifyNoInteractions(newPooledTransactionHashesMessageSender); + verify(transactionsMessageSender).sendTransactionsToPeer(ethPeerWithEth65_2); + verify(newPooledTransactionHashesMessageSender).sendTransactionHashesToPeer(ethPeerWithEth65); } @Test @@ -196,10 +200,12 @@ public void onTransactionsAddedWithOnlyEth65PeersSendFullTransactionsAndTransact List txs = toTransactionList(setupTransactionPool(1, 1)); txBroadcaster.onTransactionsAdded(txs); - + // the shuffled hash only peer list is always: + // [ethPeerWithEth65_3, ethPeerWithEth65_2, ethPeerWithEth65] + // so ethPeerWithEth65 and ethPeerWithEth65_2 are moved to the mixed broadcast list verifyTransactionAddedToPeerSendingQueue(ethPeerWithEth65, txs); verifyTransactionAddedToPeerSendingQueue(ethPeerWithEth65_2, txs); - verifyTransactionAddedToPeerSendingQueue(ethPeerWithEth65_3, txs); + verifyTransactionAddedToPeerHashSendingQueue(ethPeerWithEth65_3, txs); sendTaskCapture.getAllValues().forEach(Runnable::run); @@ -218,8 +224,10 @@ public void onTransactionsAddedWithMixedPeersSendFullTransactionsAndTransactionH List txs = toTransactionList(setupTransactionPool(1, 1)); txBroadcaster.onTransactionsAdded(txs); - - verifyTransactionAddedToPeerSendingQueue(ethPeerWithEth65, txs); + // the shuffled hash only peer list is always: + // [ethPeerWithEth65, ethPeerWithEth65_2] + // so ethPeerWithEth65_2 is moved to the mixed broadcast list + verifyTransactionAddedToPeerHashSendingQueue(ethPeerWithEth65, txs); verifyTransactionAddedToPeerSendingQueue(ethPeerWithEth65_2, txs); verifyTransactionAddedToPeerSendingQueue(ethPeerNoEth65, txs); @@ -250,9 +258,11 @@ public void onTransactionsAddedWithMixedPeersSendFullTransactionsAndTransactionH List txs = toTransactionList(setupTransactionPool(BLOB, 0, 1)); txBroadcaster.onTransactionsAdded(txs); - - verifyTransactionAddedToPeerSendingQueue(ethPeerWithEth65, txs); - verifyTransactionAddedToPeerSendingQueue(ethPeerWithEth65_2, txs); + // the shuffled hash only peer list is always: + // [ethPeerWithEth65, ethPeerWithEth65_2] + // so ethPeerWithEth65_2 is moved to the mixed broadcast list + verifyTransactionAddedToPeerHashSendingQueue(ethPeerWithEth65, txs); + verifyTransactionAddedToPeerHashSendingQueue(ethPeerWithEth65_2, txs); verifyNoTransactionAddedToPeerSendingQueue(ethPeerNoEth65); sendTaskCapture.getAllValues().forEach(Runnable::run); @@ -268,7 +278,6 @@ public void onTransactionsAddedWithMixedPeersSendFullTransactionsAndTransactionH @Test public void onTransactionsAddedWithMixedPeersAndMixedBroadcastKind() { - List eth65Peers = List.of(ethPeerWithEth65, ethPeerWithEth65_2); when(ethPeers.peerCount()).thenReturn(3); @@ -285,9 +294,12 @@ public void onTransactionsAddedWithMixedPeersAndMixedBroadcastKind() { mixedTxs.addAll(hashBroadcastTxs); txBroadcaster.onTransactionsAdded(mixedTxs); - - verifyTransactionAddedToPeerSendingQueue(ethPeerWithEth65, mixedTxs); - verifyTransactionAddedToPeerSendingQueue(ethPeerWithEth65_2, mixedTxs); + // the shuffled hash only peer list is always: + // [ethPeerWithEth65, ethPeerWithEth65_2] + // so ethPeerWithEth65_2 is moved to the mixed broadcast list + verifyTransactionAddedToPeerHashSendingQueue(ethPeerWithEth65, mixedTxs); + verifyTransactionAddedToPeerHashSendingQueue(ethPeerWithEth65_2, hashBroadcastTxs); + verifyTransactionAddedToPeerSendingQueue(ethPeerWithEth65_2, fullBroadcastTxs); verifyTransactionAddedToPeerSendingQueue(ethPeerNoEth65, fullBroadcastTxs); sendTaskCapture.getAllValues().forEach(Runnable::run); @@ -348,6 +360,16 @@ private void verifyTransactionAddedToPeerSendingQueue( .containsExactlyInAnyOrderElementsOf(transactions); } + private void verifyTransactionAddedToPeerHashSendingQueue( + final EthPeer peer, final Collection transactions) { + + ArgumentCaptor trackedTransactions = ArgumentCaptor.forClass(Transaction.class); + verify(transactionTracker, times(transactions.size())) + .addToPeerHashSendQueue(eq(peer), trackedTransactions.capture()); + assertThat(trackedTransactions.getAllValues()) + .containsExactlyInAnyOrderElementsOf(transactions); + } + private void verifyNoTransactionAddedToPeerSendingQueue(final EthPeer peer) { verify(transactionTracker, times(0)).addToPeerSendQueue(eq(peer), any()); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactoryTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactoryTest.java index 70d24e8c9e9..5742637c3e8 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactoryTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolFactoryTest.java @@ -16,28 +16,36 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration.Implementation.LAYERED; +import static org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration.Implementation.LEGACY; import static org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSchedule.DEFAULT_CHAIN_ID; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import org.hyperledger.besu.config.StubGenesisConfigOptions; +import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.BlockAddedObserver; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; +import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; +import org.hyperledger.besu.ethereum.core.Synchronizer; import org.hyperledger.besu.ethereum.eth.EthProtocolConfiguration; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthMessages; import org.hyperledger.besu.ethereum.eth.manager.EthPeers; import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; +import org.hyperledger.besu.ethereum.eth.sync.SyncMode; import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.eth.transactions.layered.LayeredPendingTransactions; @@ -96,6 +104,9 @@ public class TransactionPoolFactoryTest { @BeforeEach public void setup() { when(blockchain.getBlockHashByNumber(anyLong())).thenReturn(Optional.of(mock(Hash.class))); + final Block mockBlock = mock(Block.class); + when(mockBlock.getHash()).thenReturn(Hash.ZERO); + when(blockchain.getGenesisBlock()).thenReturn(mockBlock); when(context.getBlockchain()).thenReturn(blockchain); final NodeMessagePermissioningProvider nmpp = (destinationEnode, code) -> true; @@ -110,8 +121,9 @@ public void setup() { Bytes.random(64), 25, 25, - 25, - false); + false, + SyncMode.SNAP, + new ForkIdManager(blockchain, Collections.emptyList(), Collections.emptyList(), false)); when(ethContext.getEthMessages()).thenReturn(ethMessages); when(ethContext.getEthPeers()).thenReturn(ethPeers); @@ -128,6 +140,37 @@ public void notRegisteredToBlockAddedEventBeforeInitialSyncIsDone() { assertThat(pool.isEnabled()).isFalse(); } + @Test + public void assertPoolDisabledIfChainInSyncWithoutInitialSync() { + SyncState syncSpy = spy(new SyncState(blockchain, ethPeers, true, Optional.empty())); + ArgumentCaptor chainSyncCaptor = + ArgumentCaptor.forClass(Synchronizer.InSyncListener.class); + + setupInitialSyncPhase(syncSpy); + // verify that we are registered to the sync state + verify(syncSpy).subscribeInSync(chainSyncCaptor.capture()); + // Retrieve the captured InSyncListener + Synchronizer.InSyncListener chainSyncListener = chainSyncCaptor.getValue(); + + // mock chain being in sync: + chainSyncListener.onInSyncStatusChange(true); + + // assert pool is disabled if chain in sync and initial sync not done + assertThat(pool.isEnabled()).isFalse(); + + // mock initial sync done (avoid triggering initial sync listener) + when(syncSpy.isInitialSyncPhaseDone()).thenReturn(true); + + // assert pool is enabled when chain in sync and initial sync done + chainSyncListener.onInSyncStatusChange(true); + assertThat(pool.isEnabled()).isTrue(); + + // assert pool is re-disabled when initial sync is incomplete but chain reaches head: + when(syncSpy.isInitialSyncPhaseDone()).thenCallRealMethod(); + chainSyncListener.onInSyncStatusChange(false); + assertThat(pool.isEnabled()).isFalse(); + } + @Test public void registeredToBlockAddedEventAfterInitialSyncIsDone() { setupInitialSyncPhase(true); @@ -228,30 +271,38 @@ public void incomingTransactionMessageHandlersRegisteredIfNoInitialSync() { "transaction messages handler should be enabled")); } + @Test + public void txPoolStartsDisabledWhenInitialSyncPhaseIsRequired() { + // when using any of the initial syncs (SNAP, FAST, ...), txpool starts disabled + // and is enabled only after it is in sync + setupInitialSyncPhase(true); + assertThat(pool.isEnabled()).isFalse(); + } + + @Test + public void callingGetNextNonceForSenderOnDisabledTxPoolWorks() { + setupInitialSyncPhase(true); + + assertThat(pool.getNextNonceForSender(Address.fromHexString("0x123abc"))).isEmpty(); + } + + @Test + public void txPoolStartsEnabledWhenFullSyncConfigured() { + // when using FULL sync, txpool starts enabled, because it could be + // that it is the first node on a new network and so, it is in sync with genesis + // and must accept txs. Otherwise, asap it find a sync target that is ahead, the txpool + // will be disabled, until in sync. + setupInitialSyncPhase(false); + assertThat(pool.isEnabled()).isTrue(); + } + private void setupInitialSyncPhase(final boolean hasInitialSyncPhase) { syncState = new SyncState(blockchain, ethPeers, hasInitialSyncPhase, Optional.empty()); + setupInitialSyncPhase(syncState); + } - pool = - TransactionPoolFactory.createTransactionPool( - schedule, - context, - ethContext, - TestClock.fixed(), - new TransactionPoolMetrics(new NoOpMetricsSystem()), - syncState, - ImmutableTransactionPoolConfiguration.builder() - .txPoolMaxSize(1) - .pendingTxRetentionPeriod(1) - .unstable( - ImmutableTransactionPoolConfiguration.Unstable.builder() - .txMessageKeepAliveSeconds(1) - .build()) - .build(), - peerTransactionTracker, - transactionsMessageSender, - newPooledTransactionHashesMessageSender, - null, - new BlobCache()); + private void setupInitialSyncPhase(final SyncState syncState) { + pool = createTransactionPool(LAYERED, syncState); ethProtocolManager = new EthProtocolManager( @@ -275,8 +326,7 @@ private void setupInitialSyncPhase(final boolean hasInitialSyncPhase) { createLegacyTransactionPool_shouldUseBaseFeePendingTransactionsSorter_whenLondonEnabled() { setupScheduleWith(new StubGenesisConfigOptions().londonBlock(0)); - final TransactionPool pool = - createTransactionPool(TransactionPoolConfiguration.Implementation.LEGACY); + final TransactionPool pool = createAndEnableTransactionPool(LEGACY, syncState); assertThat(pool.pendingTransactionsImplementation()) .isEqualTo(BaseFeePendingTransactionsSorter.class); @@ -287,8 +337,7 @@ private void setupInitialSyncPhase(final boolean hasInitialSyncPhase) { createLegacyTransactionPool_shouldUseGasPricePendingTransactionsSorter_whenLondonNotEnabled() { setupScheduleWith(new StubGenesisConfigOptions().berlinBlock(0)); - final TransactionPool pool = - createTransactionPool(TransactionPoolConfiguration.Implementation.LEGACY); + final TransactionPool pool = createAndEnableTransactionPool(LEGACY, syncState); assertThat(pool.pendingTransactionsImplementation()) .isEqualTo(GasPricePendingTransactionsSorter.class); @@ -299,7 +348,7 @@ private void setupInitialSyncPhase(final boolean hasInitialSyncPhase) { createLayeredTransactionPool_shouldUseBaseFeePendingTransactionsSorter_whenLondonEnabled() { setupScheduleWith(new StubGenesisConfigOptions().londonBlock(0)); - final TransactionPool pool = createTransactionPool(LAYERED); + final TransactionPool pool = createAndEnableTransactionPool(LAYERED, syncState); assertThat(pool.pendingTransactionsImplementation()) .isEqualTo(LayeredPendingTransactions.class); @@ -312,7 +361,7 @@ private void setupInitialSyncPhase(final boolean hasInitialSyncPhase) { createLayeredTransactionPool_shouldUseGasPricePendingTransactionsSorter_whenLondonNotEnabled() { setupScheduleWith(new StubGenesisConfigOptions().berlinBlock(0)); - final TransactionPool pool = createTransactionPool(LAYERED); + final TransactionPool pool = createAndEnableTransactionPool(LAYERED, syncState); assertThat(pool.pendingTransactionsImplementation()) .isEqualTo(LayeredPendingTransactions.class); @@ -328,7 +377,11 @@ private void setupScheduleWith(final StubGenesisConfigOptions config) { ProtocolSpecAdapters.create(0, Function.identity()), PrivacyParameters.DEFAULT, false, - EvmConfiguration.DEFAULT) + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()) .createProtocolSchedule(); protocolContext = mock(ProtocolContext.class); @@ -339,27 +392,33 @@ private void setupScheduleWith(final StubGenesisConfigOptions config) { } private TransactionPool createTransactionPool( - final TransactionPoolConfiguration.Implementation implementation) { - final TransactionPool txPool = - TransactionPoolFactory.createTransactionPool( - schedule, - protocolContext, - ethContext, - TestClock.fixed(), - new NoOpMetricsSystem(), - syncState, - ImmutableTransactionPoolConfiguration.builder() - .txPoolImplementation(implementation) - .txPoolMaxSize(1) - .pendingTxRetentionPeriod(1) - .unstable( - ImmutableTransactionPoolConfiguration.Unstable.builder() - .txMessageKeepAliveSeconds(1) - .build()) - .build(), - null, - new BlobCache()); + final TransactionPoolConfiguration.Implementation implementation, final SyncState syncState) { + return TransactionPoolFactory.createTransactionPool( + schedule, + context, + ethContext, + TestClock.fixed(), + new TransactionPoolMetrics(new NoOpMetricsSystem()), + syncState, + ImmutableTransactionPoolConfiguration.builder() + .txPoolImplementation(implementation) + .txPoolMaxSize(1) + .pendingTxRetentionPeriod(1) + .unstable( + ImmutableTransactionPoolConfiguration.Unstable.builder() + .txMessageKeepAliveSeconds(1) + .build()) + .build(), + peerTransactionTracker, + transactionsMessageSender, + newPooledTransactionHashesMessageSender, + new BlobCache(), + MiningParameters.newDefault()); + } + private TransactionPool createAndEnableTransactionPool( + final TransactionPoolConfiguration.Implementation implementation, final SyncState syncState) { + final TransactionPool txPool = createTransactionPool(implementation, syncState); txPool.setEnabled(); return txPool; } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolReplacementHandlerTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolReplacementHandlerTest.java index f57d6fac3e7..aca1f202c26 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolReplacementHandlerTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolReplacementHandlerTest.java @@ -30,6 +30,7 @@ import java.util.Optional; import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -88,4 +89,11 @@ private static PendingTransaction mockTransactionInfo() { when(pendingTransaction.getTransaction()).thenReturn(transaction); return pendingTransaction; } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementByFeeMarketRuleTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementByFeeMarketRuleTest.java index 455f268e46f..eb22184a2b2 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementByFeeMarketRuleTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementByFeeMarketRuleTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -24,6 +24,7 @@ import java.util.Collection; import java.util.Optional; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -34,52 +35,60 @@ public static Collection data() { new Object[][] { // basefee absent - {frontierTx(5L), frontierTx(6L), empty(), 0, false}, - {frontierTx(5L), frontierTx(5L), empty(), 0, false}, - {frontierTx(5L), frontierTx(4L), empty(), 0, false}, - {frontierTx(100L), frontierTx(105L), empty(), 10, false}, - {frontierTx(100L), frontierTx(110L), empty(), 10, false}, - {frontierTx(100L), frontierTx(111L), empty(), 10, false}, + {frontierTx(5L), frontierTx(6L), empty(), 0, 100, false}, + {frontierTx(5L), frontierTx(5L), empty(), 0, 100, false}, + {frontierTx(5L), frontierTx(4L), empty(), 0, 100, false}, + {frontierTx(100L), frontierTx(105L), empty(), 10, 100, false}, + {frontierTx(100L), frontierTx(110L), empty(), 10, 100, false}, + {frontierTx(100L), frontierTx(111L), empty(), 10, 100, false}, // basefee present - {frontierTx(5L), frontierTx(6L), Optional.of(Wei.of(3L)), 0, false}, - {frontierTx(5L), frontierTx(5L), Optional.of(Wei.of(3L)), 0, false}, - {frontierTx(5L), frontierTx(4L), Optional.of(Wei.of(3L)), 0, false}, - {frontierTx(100L), frontierTx(105L), Optional.of(Wei.of(3L)), 10, false}, - {frontierTx(100L), frontierTx(110L), Optional.of(Wei.of(3L)), 10, false}, - {frontierTx(100L), frontierTx(111L), Optional.of(Wei.of(3L)), 10, false}, + {frontierTx(5L), frontierTx(6L), Optional.of(Wei.of(3L)), 0, 100, false}, + {frontierTx(5L), frontierTx(5L), Optional.of(Wei.of(3L)), 0, 100, false}, + {frontierTx(5L), frontierTx(4L), Optional.of(Wei.of(3L)), 0, 100, false}, + {frontierTx(100L), frontierTx(105L), Optional.of(Wei.of(3L)), 10, 100, false}, + {frontierTx(100L), frontierTx(110L), Optional.of(Wei.of(3L)), 10, 100, false}, + {frontierTx(100L), frontierTx(111L), Optional.of(Wei.of(3L)), 10, 100, false}, // eip1559 replacing frontier - {frontierTx(5L), eip1559Tx(3L, 6L), Optional.of(Wei.of(1L)), 0, false}, - {frontierTx(5L), eip1559Tx(3L, 5L), Optional.of(Wei.of(3L)), 0, true}, - {frontierTx(5L), eip1559Tx(3L, 6L), Optional.of(Wei.of(3L)), 0, true}, + {frontierTx(5L), eip1559Tx(3L, 6L), Optional.of(Wei.of(1L)), 0, 100, false}, + {frontierTx(5L), eip1559Tx(3L, 5L), Optional.of(Wei.of(3L)), 0, 100, true}, + {frontierTx(5L), eip1559Tx(3L, 6L), Optional.of(Wei.of(3L)), 0, 100, true}, // frontier replacing 1559 - {eip1559Tx(3L, 8L), frontierTx(7L), Optional.of(Wei.of(4L)), 0, true}, - {eip1559Tx(3L, 8L), frontierTx(7L), Optional.of(Wei.of(5L)), 0, false}, - {eip1559Tx(3L, 8L), frontierTx(8L), Optional.of(Wei.of(4L)), 0, true}, + {eip1559Tx(3L, 8L), frontierTx(7L), Optional.of(Wei.of(4L)), 0, 100, true}, + {eip1559Tx(3L, 8L), frontierTx(7L), Optional.of(Wei.of(5L)), 0, 100, false}, + {eip1559Tx(3L, 8L), frontierTx(8L), Optional.of(Wei.of(4L)), 0, 100, true}, // eip1559 replacing eip1559 - {eip1559Tx(3L, 6L), eip1559Tx(3L, 6L), Optional.of(Wei.of(3L)), 0, true}, - {eip1559Tx(3L, 6L), eip1559Tx(3L, 7L), Optional.of(Wei.of(3L)), 0, true}, - {eip1559Tx(3L, 6L), eip1559Tx(3L, 7L), Optional.of(Wei.of(4L)), 0, true}, - {eip1559Tx(3L, 6L), eip1559Tx(3L, 7L), Optional.of(Wei.of(5L)), 0, true}, - {eip1559Tx(3L, 6L), eip1559Tx(3L, 7L), Optional.of(Wei.of(6L)), 0, true}, - {eip1559Tx(3L, 6L), eip1559Tx(3L, 7L), Optional.of(Wei.of(7L)), 0, true}, - {eip1559Tx(3L, 6L), eip1559Tx(3L, 7L), Optional.of(Wei.of(8L)), 0, true}, - {eip1559Tx(10L, 200L), eip1559Tx(10L, 200L), Optional.of(Wei.of(90L)), 10, false}, - {eip1559Tx(10L, 200L), eip1559Tx(15L, 200L), Optional.of(Wei.of(90L)), 10, false}, - {eip1559Tx(10L, 200L), eip1559Tx(20L, 200L), Optional.of(Wei.of(90L)), 10, true}, - {eip1559Tx(10L, 200L), eip1559Tx(20L, 200L), Optional.of(Wei.of(200L)), 10, true}, - {eip1559Tx(10L, 200L), eip1559Tx(10L, 220L), Optional.of(Wei.of(220L)), 10, true}, + {eip1559Tx(3L, 6L), eip1559Tx(3L, 6L), Optional.of(Wei.of(3L)), 0, 100, true}, + {eip1559Tx(3L, 6L), eip1559Tx(3L, 7L), Optional.of(Wei.of(3L)), 0, 100, true}, + {eip1559Tx(3L, 6L), eip1559Tx(3L, 7L), Optional.of(Wei.of(4L)), 0, 100, true}, + {eip1559Tx(3L, 6L), eip1559Tx(3L, 7L), Optional.of(Wei.of(5L)), 0, 100, true}, + {eip1559Tx(3L, 6L), eip1559Tx(3L, 7L), Optional.of(Wei.of(6L)), 0, 100, true}, + {eip1559Tx(3L, 6L), eip1559Tx(3L, 7L), Optional.of(Wei.of(7L)), 0, 100, true}, + {eip1559Tx(3L, 6L), eip1559Tx(3L, 7L), Optional.of(Wei.of(8L)), 0, 100, true}, + {eip1559Tx(10L, 200L), eip1559Tx(10L, 200L), Optional.of(Wei.of(90L)), 10, 100, false}, + {eip1559Tx(10L, 200L), eip1559Tx(15L, 200L), Optional.of(Wei.of(90L)), 10, 100, false}, + {eip1559Tx(10L, 200L), eip1559Tx(20L, 200L), Optional.of(Wei.of(90L)), 10, 100, true}, + {eip1559Tx(10L, 200L), eip1559Tx(20L, 200L), Optional.of(Wei.of(200L)), 10, 100, true}, + {eip1559Tx(10L, 200L), eip1559Tx(10L, 220L), Optional.of(Wei.of(220L)), 10, 100, true}, // pathological, priority fee > max fee - {eip1559Tx(8L, 6L), eip1559Tx(3L, 6L), Optional.of(Wei.of(2L)), 0, false}, - {eip1559Tx(8L, 6L), eip1559Tx(3L, 7L), Optional.of(Wei.of(3L)), 0, true}, - {eip1559Tx(8L, 6L), eip1559Tx(3L, 7L), Optional.of(Wei.of(4L)), 0, true}, + {eip1559Tx(8L, 6L), eip1559Tx(3L, 6L), Optional.of(Wei.of(2L)), 0, 100, false}, + {eip1559Tx(8L, 6L), eip1559Tx(3L, 7L), Optional.of(Wei.of(3L)), 0, 100, true}, + {eip1559Tx(8L, 6L), eip1559Tx(3L, 7L), Optional.of(Wei.of(4L)), 0, 100, true}, // pathological, eip1559 without basefee - {eip1559Tx(8L, 6L), eip1559Tx(3L, 7L), Optional.empty(), 0, false}, - {eip1559Tx(8L, 6L), eip1559Tx(3L, 7L), Optional.empty(), 0, false}, + {eip1559Tx(8L, 6L), eip1559Tx(3L, 7L), Optional.empty(), 0, 100, false}, + {eip1559Tx(8L, 6L), eip1559Tx(3L, 7L), Optional.empty(), 0, 100, false}, // zero base fee market - {frontierTx(0L), frontierTx(0L), Optional.of(Wei.ZERO), 0, false}, - {eip1559Tx(0L, 0L), frontierTx(0L), Optional.of(Wei.ZERO), 0, true}, - {frontierTx(0L), eip1559Tx(0L, 0L), Optional.of(Wei.ZERO), 0, true}, - {eip1559Tx(0L, 0L), eip1559Tx(0L, 0L), Optional.of(Wei.ZERO), 0, true}, + {frontierTx(0L), frontierTx(0L), Optional.of(Wei.ZERO), 0, 100, false}, + {eip1559Tx(0L, 0L), frontierTx(0L), Optional.of(Wei.ZERO), 0, 100, true}, + {frontierTx(0L), eip1559Tx(0L, 0L), Optional.of(Wei.ZERO), 0, 100, true}, + {eip1559Tx(0L, 0L), eip1559Tx(0L, 0L), Optional.of(Wei.ZERO), 0, 100, true}, + // blob tx + {blobTx(10L, 200L, 1L), blobTx(20L, 220L, 1L), Optional.of(Wei.of(90L)), 10, 0, true}, + {blobTx(10L, 200L, 1L), blobTx(20L, 220L, 2L), Optional.of(Wei.of(90L)), 10, 100, true}, + {blobTx(10L, 200L, 1L), blobTx(20L, 220L, 1L), Optional.of(Wei.of(90L)), 10, 100, false}, + {blobTx(10L, 200L, 2L), blobTx(20L, 220L, 3L), Optional.of(Wei.of(90L)), 10, 100, false}, + // // blob tx must be replaced by blob tx only + {blobTx(3L, 6L, 1L), eip1559Tx(3L, 6L), Optional.of(Wei.of(3L)), 0, 0, false}, + {eip1559Tx(3L, 6L), blobTx(3L, 6L, 1L), Optional.of(Wei.of(3L)), 0, 0, false}, }); } @@ -90,11 +99,20 @@ public void shouldReplace( final PendingTransaction newTx, final Optional baseFee, final int priceBump, + final int blobPriceBump, final boolean expected) { assertThat( - new TransactionReplacementByFeeMarketRule(Percentage.fromInt(priceBump)) + new TransactionReplacementByFeeMarketRule( + Percentage.fromInt(priceBump), Percentage.fromInt(blobPriceBump)) .shouldReplace(oldTx, newTx, baseFee)) .isEqualTo(expected); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementByGasPriceRuleTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementByGasPriceRuleTest.java index 5b0b6ade211..651666fdd55 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementByGasPriceRuleTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementByGasPriceRuleTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -24,6 +24,7 @@ import java.util.Collection; import java.util.Optional; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -84,4 +85,11 @@ public void shouldReplace( .shouldReplace(oldTx, newTx, baseFee)) .isEqualTo(expected); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementRulesTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementRulesTest.java index 00492e32266..da7b465583d 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementRulesTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionReplacementRulesTest.java @@ -27,6 +27,7 @@ import java.util.Collection; import java.util.Optional; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -37,46 +38,54 @@ public static Collection data() { new Object[][] { // TransactionReplacementByGasPriceRule // basefee absent - {frontierTx(5L), frontierTx(6L), empty(), 0, true}, - {frontierTx(5L), frontierTx(5L), empty(), 0, true}, - {frontierTx(5L), frontierTx(4L), empty(), 0, false}, - {frontierTx(100L), frontierTx(105L), empty(), 10, false}, - {frontierTx(100L), frontierTx(110L), empty(), 10, true}, - {frontierTx(100L), frontierTx(111L), empty(), 10, true}, + {frontierTx(5L), frontierTx(6L), empty(), 0, 100, true}, + {frontierTx(5L), frontierTx(5L), empty(), 0, 100, true}, + {frontierTx(5L), frontierTx(4L), empty(), 0, 100, false}, + {frontierTx(100L), frontierTx(105L), empty(), 10, 100, false}, + {frontierTx(100L), frontierTx(110L), empty(), 10, 100, true}, + {frontierTx(100L), frontierTx(111L), empty(), 10, 100, true}, // basefee present - {frontierTx(5L), frontierTx(6L), Optional.of(Wei.of(3L)), 0, true}, - {frontierTx(5L), frontierTx(5L), Optional.of(Wei.of(3L)), 0, true}, - {frontierTx(5L), frontierTx(4L), Optional.of(Wei.of(3L)), 0, false}, - {frontierTx(100L), frontierTx(105L), Optional.of(Wei.of(3L)), 10, false}, - {frontierTx(100L), frontierTx(110L), Optional.of(Wei.of(3L)), 10, true}, - {frontierTx(100L), frontierTx(111L), Optional.of(Wei.of(3L)), 10, true}, + {frontierTx(5L), frontierTx(6L), Optional.of(Wei.of(3L)), 0, 100, true}, + {frontierTx(5L), frontierTx(5L), Optional.of(Wei.of(3L)), 0, 100, true}, + {frontierTx(5L), frontierTx(4L), Optional.of(Wei.of(3L)), 0, 100, false}, + {frontierTx(100L), frontierTx(105L), Optional.of(Wei.of(3L)), 10, 100, false}, + {frontierTx(100L), frontierTx(110L), Optional.of(Wei.of(3L)), 10, 100, true}, + {frontierTx(100L), frontierTx(111L), Optional.of(Wei.of(3L)), 10, 100, true}, // TransactionReplacementByFeeMarketRule // eip1559 replacing frontier - {frontierTx(5L), eip1559Tx(3L, 6L), Optional.of(Wei.of(1L)), 0, false}, - {frontierTx(5L), eip1559Tx(3L, 5L), Optional.of(Wei.of(3L)), 0, true}, - {frontierTx(5L), eip1559Tx(3L, 6L), Optional.of(Wei.of(3L)), 0, true}, + {frontierTx(5L), eip1559Tx(3L, 6L), Optional.of(Wei.of(1L)), 0, 100, false}, + {frontierTx(5L), eip1559Tx(3L, 5L), Optional.of(Wei.of(3L)), 0, 100, true}, + {frontierTx(5L), eip1559Tx(3L, 6L), Optional.of(Wei.of(3L)), 0, 100, true}, // frontier replacing 1559 - {eip1559Tx(3L, 8L), frontierTx(6L), Optional.of(Wei.of(4L)), 0, false}, - {eip1559Tx(3L, 8L), frontierTx(7L), Optional.of(Wei.of(4L)), 0, true}, - {eip1559Tx(3L, 8L), frontierTx(8L), Optional.of(Wei.of(4L)), 0, true}, + {eip1559Tx(3L, 8L), frontierTx(6L), Optional.of(Wei.of(4L)), 0, 100, false}, + {eip1559Tx(3L, 8L), frontierTx(7L), Optional.of(Wei.of(4L)), 0, 100, true}, + {eip1559Tx(3L, 8L), frontierTx(8L), Optional.of(Wei.of(4L)), 0, 100, true}, // eip1559 replacing eip1559 - {eip1559Tx(3L, 6L), eip1559Tx(3L, 6L), Optional.of(Wei.of(3L)), 0, true}, - {eip1559Tx(3L, 6L), eip1559Tx(3L, 7L), Optional.of(Wei.of(3L)), 0, true}, - {eip1559Tx(3L, 6L), eip1559Tx(3L, 7L), Optional.of(Wei.of(4L)), 0, true}, - {eip1559Tx(10L, 200L), eip1559Tx(10L, 200L), Optional.of(Wei.of(90L)), 10, false}, - {eip1559Tx(10L, 200L), eip1559Tx(15L, 200L), Optional.of(Wei.of(90L)), 10, false}, - {eip1559Tx(10L, 200L), eip1559Tx(21L, 200L), Optional.of(Wei.of(90L)), 10, true}, + {eip1559Tx(3L, 6L), eip1559Tx(3L, 6L), Optional.of(Wei.of(3L)), 0, 100, true}, + {eip1559Tx(3L, 6L), eip1559Tx(3L, 7L), Optional.of(Wei.of(3L)), 0, 100, true}, + {eip1559Tx(3L, 6L), eip1559Tx(3L, 7L), Optional.of(Wei.of(4L)), 0, 100, true}, + {eip1559Tx(10L, 200L), eip1559Tx(10L, 200L), Optional.of(Wei.of(90L)), 10, 100, false}, + {eip1559Tx(10L, 200L), eip1559Tx(15L, 200L), Optional.of(Wei.of(90L)), 10, 100, false}, + {eip1559Tx(10L, 200L), eip1559Tx(21L, 200L), Optional.of(Wei.of(90L)), 10, 100, true}, // pathological, priority fee > max fee - {eip1559Tx(8L, 6L), eip1559Tx(3L, 7L), Optional.of(Wei.of(3L)), 0, true}, - {eip1559Tx(8L, 6L), eip1559Tx(3L, 7L), Optional.of(Wei.of(4L)), 0, true}, + {eip1559Tx(8L, 6L), eip1559Tx(3L, 7L), Optional.of(Wei.of(3L)), 0, 100, true}, + {eip1559Tx(8L, 6L), eip1559Tx(3L, 7L), Optional.of(Wei.of(4L)), 0, 100, true}, // pathological, eip1559 without basefee - {eip1559Tx(8L, 6L), eip1559Tx(3L, 7L), Optional.empty(), 0, false}, - {eip1559Tx(8L, 6L), eip1559Tx(3L, 7L), Optional.empty(), 0, false}, + {eip1559Tx(8L, 6L), eip1559Tx(3L, 7L), Optional.empty(), 0, 100, false}, + {eip1559Tx(8L, 6L), eip1559Tx(3L, 7L), Optional.empty(), 0, 100, false}, // zero base fee market - {frontierTx(0L), frontierTx(0L), Optional.of(Wei.ZERO), 0, true}, - {eip1559Tx(0L, 0L), frontierTx(0L), Optional.of(Wei.ZERO), 0, true}, - {frontierTx(0L), eip1559Tx(0L, 0L), Optional.of(Wei.ZERO), 0, true}, - {eip1559Tx(0L, 0L), eip1559Tx(0L, 0L), Optional.of(Wei.ZERO), 0, true}, + {frontierTx(0L), frontierTx(0L), Optional.of(Wei.ZERO), 0, 100, true}, + {eip1559Tx(0L, 0L), frontierTx(0L), Optional.of(Wei.ZERO), 0, 100, true}, + {frontierTx(0L), eip1559Tx(0L, 0L), Optional.of(Wei.ZERO), 0, 100, true}, + {eip1559Tx(0L, 0L), eip1559Tx(0L, 0L), Optional.of(Wei.ZERO), 0, 100, true}, + // blob tx + {blobTx(3L, 6L, 1L), blobTx(3L, 6L, 1L), Optional.of(Wei.of(3L)), 0, 0, true}, + {blobTx(3L, 6L, 1L), blobTx(3L, 6L, 2L), Optional.of(Wei.of(3L)), 0, 100, true}, + {blobTx(10L, 200L, 1L), blobTx(10L, 200L, 1L), Optional.of(Wei.of(90L)), 10, 100, false}, + {blobTx(10L, 200L, 2L), blobTx(15L, 200L, 3L), Optional.of(Wei.of(90L)), 10, 100, false}, + // blob tx must be replaced by blob tx only + {blobTx(3L, 6L, 1L), eip1559Tx(3L, 6L), Optional.of(Wei.of(3L)), 0, 0, false}, + {eip1559Tx(3L, 6L), blobTx(3L, 6L, 1L), Optional.of(Wei.of(3L)), 0, 0, false}, }); } @@ -87,13 +96,22 @@ public void shouldReplace( final PendingTransaction newTx, final Optional baseFee, final int priceBump, + final int blobPriceBump, final boolean expected) { BlockHeader mockHeader = mock(BlockHeader.class); when(mockHeader.getBaseFee()).thenReturn(baseFee); assertThat( - new TransactionPoolReplacementHandler(Percentage.fromInt(priceBump)) + new TransactionPoolReplacementHandler( + Percentage.fromInt(priceBump), Percentage.fromInt(blobPriceBump)) .shouldReplace(oldTx, newTx, mockHeader)) .isEqualTo(expected); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionsMessageSenderTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionsMessageSenderTest.java index 02a2d248bcb..85c38fd8fe6 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionsMessageSenderTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionsMessageSenderTest.java @@ -25,6 +25,7 @@ import org.hyperledger.besu.ethereum.core.BlockDataGenerator; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; +import org.hyperledger.besu.ethereum.eth.manager.EthPeers; import org.hyperledger.besu.ethereum.eth.messages.EthPV62; import org.hyperledger.besu.ethereum.eth.messages.TransactionsMessage; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData; @@ -37,6 +38,7 @@ import org.mockito.ArgumentCaptor; public class TransactionsMessageSenderTest { + private final EthPeers ethPeers = mock(EthPeers.class); private final EthPeer peer1 = mock(EthPeer.class); private final EthPeer peer2 = mock(EthPeer.class); @@ -46,7 +48,7 @@ public class TransactionsMessageSenderTest { private final Transaction transaction2 = generator.transaction(); private final Transaction transaction3 = generator.transaction(); - private final PeerTransactionTracker transactionTracker = new PeerTransactionTracker(); + private final PeerTransactionTracker transactionTracker = new PeerTransactionTracker(ethPeers); private final TransactionsMessageSender messageSender = new TransactionsMessageSender(transactionTracker); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractLayeredTransactionPoolTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractLayeredTransactionPoolTest.java index ee45b4998e1..1e2c4101929 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractLayeredTransactionPoolTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractLayeredTransactionPoolTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -42,17 +42,24 @@ protected PendingTransactions createPendingTransactions( final TransactionsLayer sparseLayer = new SparseTransactions( poolConfig, + ethScheduler, new EndLayer(txPoolMetrics), txPoolMetrics, transactionReplacementTester, new BlobCache()); final TransactionsLayer readyLayer = new ReadyTransactions( - poolConfig, sparseLayer, txPoolMetrics, transactionReplacementTester, new BlobCache()); + poolConfig, + ethScheduler, + sparseLayer, + txPoolMetrics, + transactionReplacementTester, + new BlobCache()); return new LayeredPendingTransactions( poolConfig, createPrioritizedTransactions( - poolConfig, readyLayer, txPoolMetrics, transactionReplacementTester)); + poolConfig, readyLayer, txPoolMetrics, transactionReplacementTester), + ethScheduler); } protected abstract AbstractPrioritizedTransactions createPrioritizedTransactions( diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractPrioritizedTransactionsTestBase.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractPrioritizedTransactionsTestBase.java index 86741de9146..6a0005eec63 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractPrioritizedTransactionsTestBase.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractPrioritizedTransactionsTestBase.java @@ -1,5 +1,5 @@ /* - * Copyright Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -16,9 +16,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.ADDED; +import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.DROPPED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.AddReason.NEW; +import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; @@ -27,10 +31,11 @@ import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolMetrics; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolReplacementHandler; -import java.math.BigInteger; import java.util.ArrayList; +import java.util.EnumMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.function.BiFunction; import java.util.stream.IntStream; @@ -38,21 +43,31 @@ public abstract class AbstractPrioritizedTransactionsTestBase extends BaseTransactionPoolTest { protected static final int MAX_TRANSACTIONS = 5; + protected static final EnumMap MAX_TRANSACTIONS_BY_TYPE = + new EnumMap<>(Map.of(TransactionType.BLOB, 2)); protected final TransactionPoolMetrics txPoolMetrics = new TransactionPoolMetrics(metricsSystem); protected final EvictCollectorLayer evictCollector = new EvictCollectorLayer(txPoolMetrics); + protected final MiningParameters miningParameters = + MiningParameters.newDefault() + .setMinTransactionGasPrice(DEFAULT_MIN_GAS_PRICE) + .setMinPriorityFeePerGas(DEFAULT_MIN_PRIORITY_FEE); protected AbstractPrioritizedTransactions transactions = getSorter( ImmutableTransactionPoolConfiguration.builder() .maxPrioritizedTransactions(MAX_TRANSACTIONS) + .maxPrioritizedTransactionsByType(MAX_TRANSACTIONS_BY_TYPE) .maxFutureBySender(MAX_TRANSACTIONS) - .build()); + .build(), + miningParameters); - private AbstractPrioritizedTransactions getSorter(final TransactionPoolConfiguration poolConfig) { + private AbstractPrioritizedTransactions getSorter( + final TransactionPoolConfiguration poolConfig, final MiningParameters miningParameters) { return getSorter( poolConfig, evictCollector, txPoolMetrics, - (pt1, pt2) -> transactionReplacementTester(poolConfig, pt1, pt2)); + (pt1, pt2) -> transactionReplacementTester(poolConfig, pt1, pt2), + miningParameters); } abstract AbstractPrioritizedTransactions getSorter( @@ -60,7 +75,8 @@ abstract AbstractPrioritizedTransactions getSorter( final TransactionsLayer nextLayer, final TransactionPoolMetrics txPoolMetrics, final BiFunction - transactionReplacementTester); + transactionReplacementTester, + final MiningParameters miningParameters); abstract BlockHeader mockBlockHeader(); @@ -69,7 +85,8 @@ private boolean transactionReplacementTester( final PendingTransaction pt1, final PendingTransaction pt2) { final TransactionPoolReplacementHandler transactionReplacementHandler = - new TransactionPoolReplacementHandler(poolConfig.getPriceBump()); + new TransactionPoolReplacementHandler( + poolConfig.getPriceBump(), poolConfig.getBlobPriceBump()); return transactionReplacementHandler.shouldReplace(pt1, pt2, mockBlockHeader()); } @@ -80,13 +97,13 @@ public void prioritizeLocalTransactionThenValue() { assertThat(prioritizeTransaction(localTransaction)).isEqualTo(ADDED); final List remoteTxs = new ArrayList<>(); - TransactionAddedResult prioritizeResult = null; + TransactionAddedResult prioritizeResult; for (int i = 0; i < MAX_TRANSACTIONS; i++) { final PendingTransaction highValueRemoteTx = createRemotePendingTransaction( createTransaction( 0, - Wei.of(BigInteger.valueOf(100).pow(i)), + Wei.of(DEFAULT_MIN_GAS_PRICE.multiply(2).toBigInteger().pow(i + 1)), SIGNATURE_ALGORITHM.get().generateKeyPair())); remoteTxs.add(highValueRemoteTx); prioritizeResult = prioritizeTransaction(highValueRemoteTx); @@ -123,6 +140,25 @@ public void shouldStartDroppingLocalTransactionsWhenPoolIsFullOfLocalTransaction assertTransactionNotPrioritized(lastLocalTransaction); } + @Test + public void txBelowCurrentMineableMinGasPriceIsNotPrioritized() { + final PendingTransaction lowGasPriceTx = + createRemotePendingTransaction( + createTransaction(0, DEFAULT_MIN_GAS_PRICE.subtract(1), KEYS1)); + assertThat(prioritizeTransaction(lowGasPriceTx)).isEqualTo(DROPPED); + assertEvicted(lowGasPriceTx); + assertTransactionNotPrioritized(lowGasPriceTx); + } + + @Test + public void txWithPriorityBelowCurrentMineableMinGasPriceIsPrioritized() { + final PendingTransaction lowGasPriceTx = + createRemotePendingTransaction( + createTransaction(0, DEFAULT_MIN_GAS_PRICE.subtract(1), KEYS1), true); + assertThat(prioritizeTransaction(lowGasPriceTx)).isEqualTo(ADDED); + assertTransactionPrioritized(lowGasPriceTx); + } + protected void shouldPrioritizeValueThenTimeAddedToPool( final Iterator lowValueTxSupplier, final PendingTransaction highValueTx, @@ -134,7 +170,7 @@ protected void shouldPrioritizeValueThenTimeAddedToPool( .mapToObj( i -> { final var lowPriceTx = lowValueTxSupplier.next(); - final var prioritizeResult = transactions.add(lowPriceTx, 0); + final var prioritizeResult = transactions.add(lowPriceTx, 0, NEW); assertThat(prioritizeResult).isEqualTo(ADDED); assertThat(evictCollector.getEvictedTransactions()).isEmpty(); @@ -145,7 +181,7 @@ protected void shouldPrioritizeValueThenTimeAddedToPool( assertThat(transactions.count()).isEqualTo(MAX_TRANSACTIONS); // This should kick the oldest tx with the low gas price out, namely the first one we added - final var highValuePrioRes = transactions.add(highValueTx, 0); + final var highValuePrioRes = transactions.add(highValueTx, 0, NEW); assertThat(highValuePrioRes).isEqualTo(ADDED); assertEvicted(expectedDroppedTx); @@ -160,7 +196,7 @@ protected TransactionAddedResult prioritizeTransaction(final Transaction tx) { } protected TransactionAddedResult prioritizeTransaction(final PendingTransaction tx) { - return transactions.add(tx, 0); + return transactions.add(tx, 0, NEW); } protected void assertTransactionPrioritized(final PendingTransaction tx) { diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseFeePrioritizedTransactionsTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseFeePrioritizedTransactionsTest.java index 4529f7f36e1..b9a675287f9 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseFeePrioritizedTransactionsTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseFeePrioritizedTransactionsTest.java @@ -1,5 +1,5 @@ /* - * Copyright Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,8 +14,11 @@ */ package org.hyperledger.besu.ethereum.eth.transactions.layered; +import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.datatypes.TransactionType.EIP1559; import static org.hyperledger.besu.datatypes.TransactionType.FRONTIER; +import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.ADDED; +import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.DROPPED; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -23,6 +26,7 @@ import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionTestFixture; import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; @@ -31,6 +35,7 @@ import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolMetrics; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; +import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Optional; @@ -40,9 +45,12 @@ import java.util.stream.IntStream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; public class BaseFeePrioritizedTransactionsTest extends AbstractPrioritizedTransactionsTestBase { - + private static final FeeMarket EIP1559_FEE_MARKET = FeeMarket.london(0L); + private static final Wei DEFAULT_BASE_FEE = DEFAULT_MIN_GAS_PRICE.subtract(2); private static final Random randomizeTxType = new Random(); @Override @@ -51,22 +59,29 @@ AbstractPrioritizedTransactions getSorter( final TransactionsLayer nextLayer, final TransactionPoolMetrics txPoolMetrics, final BiFunction - transactionReplacementTester) { + transactionReplacementTester, + final MiningParameters miningParameters) { return new BaseFeePrioritizedTransactions( poolConfig, this::mockBlockHeader, + ethScheduler, nextLayer, txPoolMetrics, transactionReplacementTester, - FeeMarket.london(0L), - new BlobCache()); + EIP1559_FEE_MARKET, + new BlobCache(), + miningParameters); } @Override protected BlockHeader mockBlockHeader() { + return mockBlockHeader(DEFAULT_BASE_FEE); + } + + private BlockHeader mockBlockHeader(final Wei baseFee) { final BlockHeader blockHeader = mock(BlockHeader.class); - when(blockHeader.getBaseFee()).thenReturn(Optional.of(Wei.ONE)); + when(blockHeader.getBaseFee()).thenReturn(Optional.of(baseFee)); return blockHeader; } @@ -98,25 +113,19 @@ protected Transaction createTransactionReplacement( originalTransaction.getType(), originalTransaction.getNonce(), originalTransaction.getMaxGasPrice().multiply(2), + originalTransaction.getMaxGasPrice().multiply(2).divide(10), + originalTransaction.getPayload().size(), + originalTransaction.getBlobCount(), keys); } - @Test - public void shouldPrioritizePriorityFeeThenTimeAddedToPoolOnlyEIP1559Txs() { - shouldPrioritizePriorityFeeThenTimeAddedToPoolSameTypeTxs(EIP1559); - } - - @Test - public void shouldPrioritizeGasPriceThenTimeAddedToPoolOnlyFrontierTxs() { - shouldPrioritizePriorityFeeThenTimeAddedToPoolSameTypeTxs(FRONTIER); - } - @Test public void shouldPrioritizeEffectivePriorityFeeThenTimeAddedToPoolOnMixedTypes() { - final var nextBlockBaseFee = Optional.of(Wei.ONE); + final var nextBlockBaseFee = Optional.of(DEFAULT_MIN_GAS_PRICE.subtract(1)); final PendingTransaction highGasPriceTransaction = - createRemotePendingTransaction(createTransaction(0, Wei.of(100), KEYS1)); + createRemotePendingTransaction( + createTransaction(0, DEFAULT_MIN_GAS_PRICE.multiply(2), KEYS1)); final List lowValueTxs = IntStream.range(0, MAX_TRANSACTIONS) @@ -124,7 +133,9 @@ public void shouldPrioritizeEffectivePriorityFeeThenTimeAddedToPoolOnMixedTypes( i -> new PendingTransaction.Remote( createTransaction( - 0, Wei.of(10), SIGNATURE_ALGORITHM.get().generateKeyPair()))) + 0, + DEFAULT_MIN_GAS_PRICE.add(1), + SIGNATURE_ALGORITHM.get().generateKeyPair()))) .collect(Collectors.toUnmodifiableList()); final var lowestPriorityFee = @@ -151,10 +162,50 @@ public void shouldPrioritizeEffectivePriorityFeeThenTimeAddedToPoolOnMixedTypes( lowValueTxs.iterator(), highGasPriceTransaction, firstLowValueTx); } - private void shouldPrioritizePriorityFeeThenTimeAddedToPoolSameTypeTxs( + @Test + public void txBelowCurrentMineableMinPriorityFeeIsNotPrioritized() { + miningParameters.setMinPriorityFeePerGas(Wei.of(5)); + final PendingTransaction lowPriorityFeeTx = + createRemotePendingTransaction( + createTransaction(0, DEFAULT_MIN_GAS_PRICE.subtract(1), KEYS1)); + assertThat(prioritizeTransaction(lowPriorityFeeTx)).isEqualTo(DROPPED); + assertEvicted(lowPriorityFeeTx); + assertTransactionNotPrioritized(lowPriorityFeeTx); + } + + @Test + public void txWithPriorityBelowCurrentMineableMinPriorityFeeIsPrioritized() { + miningParameters.setMinPriorityFeePerGas(Wei.of(5)); + final PendingTransaction lowGasPriceTx = + createRemotePendingTransaction( + createTransaction(0, DEFAULT_MIN_GAS_PRICE.subtract(1), KEYS1), true); + assertThat(prioritizeTransaction(lowGasPriceTx)).isEqualTo(ADDED); + assertTransactionPrioritized(lowGasPriceTx); + } + + @ParameterizedTest + @EnumSource( + value = TransactionType.class, + names = {"EIP1559", "BLOB"}) + public void txWithEffectiveGasPriceBelowCurrentMineableMinGasPriceIsNotPrioritized( + final TransactionType type) { + final PendingTransaction lowGasPriceTx = + createRemotePendingTransaction( + createTransaction(type, 0, DEFAULT_MIN_GAS_PRICE, Wei.ONE, 0, 1, KEYS1)); + assertThat(prioritizeTransaction(lowGasPriceTx)).isEqualTo(DROPPED); + assertEvicted(lowGasPriceTx); + assertTransactionNotPrioritized(lowGasPriceTx); + } + + @ParameterizedTest + @EnumSource( + value = TransactionType.class, + names = {"EIP1559", "FRONTIER"}) + public void shouldPrioritizePriorityFeeThenTimeAddedToPoolSameTypeTxs( final TransactionType transactionType) { final PendingTransaction highGasPriceTransaction = - createRemotePendingTransaction(createTransaction(0, Wei.of(100), KEYS1)); + createRemotePendingTransaction( + createTransaction(0, DEFAULT_MIN_GAS_PRICE.multiply(200), KEYS1)); final var lowValueTxs = IntStream.range(0, MAX_TRANSACTIONS) @@ -164,7 +215,7 @@ private void shouldPrioritizePriorityFeeThenTimeAddedToPoolSameTypeTxs( createTransaction( transactionType, 0, - Wei.of(10), + DEFAULT_MIN_GAS_PRICE.add(1).multiply(20), 0, SIGNATURE_ALGORITHM.get().generateKeyPair()))) .collect(Collectors.toUnmodifiableList()); @@ -172,4 +223,72 @@ private void shouldPrioritizePriorityFeeThenTimeAddedToPoolSameTypeTxs( shouldPrioritizeValueThenTimeAddedToPool( lowValueTxs.iterator(), highGasPriceTransaction, lowValueTxs.get(0)); } + + @Test + public void maxNumberOfTxsForTypeIsEnforced() { + final var limitedType = MAX_TRANSACTIONS_BY_TYPE.entrySet().iterator().next(); + final var maxNumber = limitedType.getValue(); + final var addedTxs = new ArrayList(maxNumber); + for (int i = 0; i < maxNumber; i++) { + final var tx = + createTransaction( + limitedType.getKey(), + 0, + DEFAULT_MIN_GAS_PRICE, + DEFAULT_MIN_GAS_PRICE.divide(10), + 0, + 1, + SIGNATURE_ALGORITHM.get().generateKeyPair()); + addedTxs.add(tx); + assertThat(prioritizeTransaction(tx)).isEqualTo(ADDED); + } + + final var overflowTx = + createTransaction( + limitedType.getKey(), + 0, + DEFAULT_MIN_GAS_PRICE, + DEFAULT_MIN_GAS_PRICE.divide(10), + 0, + 1, + SIGNATURE_ALGORITHM.get().generateKeyPair()); + assertThat(prioritizeTransaction(overflowTx)).isEqualTo(DROPPED); + + addedTxs.forEach(this::assertTransactionPrioritized); + assertTransactionNotPrioritized(overflowTx); + } + + @Test + public void maxNumberOfTxsForTypeWithReplacement() { + final var limitedType = MAX_TRANSACTIONS_BY_TYPE.entrySet().iterator().next(); + final var maxNumber = limitedType.getValue(); + final var addedTxs = new ArrayList(maxNumber); + for (int i = 0; i < maxNumber; i++) { + final var tx = + createTransaction( + limitedType.getKey(), + i, + DEFAULT_MIN_GAS_PRICE, + DEFAULT_MIN_GAS_PRICE.divide(10), + 0, + 1, + KEYS1); + addedTxs.add(tx); + assertThat(prioritizeTransaction(tx)).isEqualTo(ADDED); + } + + final var replacedTx = addedTxs.get(0); + final var replacementTx = createTransactionReplacement(replacedTx, KEYS1); + final var txAddResult = prioritizeTransaction(replacementTx); + + assertThat(txAddResult.isReplacement()).isTrue(); + assertThat(txAddResult.maybeReplacedTransaction()) + .map(PendingTransaction::getTransaction) + .contains(replacedTx); + + addedTxs.remove(replacedTx); + addedTxs.forEach(this::assertTransactionPrioritized); + assertTransactionNotPrioritized(replacedTx); + assertTransactionPrioritized(replacementTx); + } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseTransactionPoolTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseTransactionPoolTest.java index 0d69dac4faa..690ab02a95f 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseTransactionPoolTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/BaseTransactionPoolTest.java @@ -1,5 +1,5 @@ /* - * Copyright Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -31,11 +31,13 @@ import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionTestFixture; import org.hyperledger.besu.ethereum.core.Util; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolMetrics; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.metrics.StubMetricsSystem; +import org.hyperledger.besu.testutil.DeterministicEthScheduler; import java.util.Optional; import java.util.Random; @@ -54,13 +56,16 @@ public class BaseTransactionPoolTest { protected static final KeyPair KEYS2 = SIGNATURE_ALGORITHM.get().generateKeyPair(); protected static final Address SENDER1 = Util.publicKeyToAddress(KEYS1.getPublicKey()); protected static final Address SENDER2 = Util.publicKeyToAddress(KEYS2.getPublicKey()); - + protected static final Wei DEFAULT_MIN_GAS_PRICE = Wei.of(50); + protected static final Wei DEFAULT_MIN_PRIORITY_FEE = Wei.ZERO; private static final Random randomizeTxType = new Random(); protected final Transaction transaction0 = createTransaction(0); protected final Transaction transaction1 = createTransaction(1); protected final Transaction transaction2 = createTransaction(2); + protected final Transaction blobTransaction0 = createEIP4844Transaction(0, KEYS1, 1, 1); + protected final EthScheduler ethScheduler = new DeterministicEthScheduler(); protected final StubMetricsSystem metricsSystem = new StubMetricsSystem(); protected Transaction createTransaction(final long nonce) { @@ -93,16 +98,42 @@ protected Transaction createEIP1559Transaction( protected Transaction createEIP4844Transaction( final long nonce, final KeyPair keys, final int gasFeeMultiplier, final int blobCount) { return createTransaction( - TransactionType.BLOB, nonce, Wei.of(5000L).multiply(gasFeeMultiplier), 0, blobCount, keys); + TransactionType.BLOB, + nonce, + Wei.of(5000L).multiply(gasFeeMultiplier), + Wei.of(5000L).multiply(gasFeeMultiplier).divide(10), + 0, + blobCount, + keys); + } + + protected Transaction createTransactionOfSize( + final long nonce, final Wei maxGasPrice, final int txSize, final KeyPair keys) { + + final TransactionType txType = + TransactionType.values()[ + randomizeTxType.nextInt(txSize < blobTransaction0.getSize() ? 3 : 4)]; + + final Transaction baseTx = + createTransaction(txType, nonce, maxGasPrice, maxGasPrice.divide(10), 0, 1, keys); + final int payloadSize = txSize - baseTx.getSize(); + + return createTransaction( + txType, nonce, maxGasPrice, maxGasPrice.divide(10), payloadSize, 1, keys); } protected Transaction createTransaction( final long nonce, final Wei maxGasPrice, final int payloadSize, final KeyPair keys) { - // ToDo 4844: include BLOB tx here - final TransactionType txType = TransactionType.values()[randomizeTxType.nextInt(3)]; + final TransactionType txType = TransactionType.values()[randomizeTxType.nextInt(4)]; - return createTransaction(txType, nonce, maxGasPrice, payloadSize, keys); + return switch (txType) { + case FRONTIER, ACCESS_LIST, EIP1559, DELEGATE_CODE -> + createTransaction(txType, nonce, maxGasPrice, payloadSize, keys); + case BLOB -> + createTransaction( + txType, nonce, maxGasPrice, maxGasPrice.divide(10), payloadSize, 1, keys); + }; } protected Transaction createTransaction( @@ -111,17 +142,20 @@ protected Transaction createTransaction( final Wei maxGasPrice, final int payloadSize, final KeyPair keys) { - return createTransaction(type, nonce, maxGasPrice, payloadSize, 0, keys); + return createTransaction( + type, nonce, maxGasPrice, maxGasPrice.divide(10), payloadSize, 0, keys); } protected Transaction createTransaction( final TransactionType type, final long nonce, final Wei maxGasPrice, + final Wei maxPriorityFeePerGas, final int payloadSize, final int blobCount, final KeyPair keys) { - return prepareTransaction(type, nonce, maxGasPrice, payloadSize, blobCount) + return prepareTransaction( + type, nonce, maxGasPrice, maxPriorityFeePerGas, payloadSize, blobCount) .createTransaction(keys); } @@ -129,6 +163,7 @@ protected TransactionTestFixture prepareTransaction( final TransactionType type, final long nonce, final Wei maxGasPrice, + final Wei maxPriorityFeePerGas, final int payloadSize, final int blobCount) { @@ -144,8 +179,9 @@ protected TransactionTestFixture prepareTransaction( } if (type.supports1559FeeMarket()) { tx.maxFeePerGas(Optional.of(maxGasPrice)) - .maxPriorityFeePerGas(Optional.of(maxGasPrice.divide(10))); + .maxPriorityFeePerGas(Optional.of(maxPriorityFeePerGas)); if (type.supportsBlob() && blobCount > 0) { + tx.maxFeePerBlobGas(Optional.of(maxGasPrice)); final var versionHashes = IntStream.range(0, blobCount) .mapToObj(i -> new VersionedHash((byte) 1, Hash.ZERO)) @@ -175,7 +211,9 @@ protected Transaction createTransactionReplacement( originalTransaction.getType(), originalTransaction.getNonce(), originalTransaction.getMaxGasPrice().multiply(2), + originalTransaction.getMaxGasPrice().multiply(2).divide(10), 0, + 1, keys); } @@ -220,9 +258,10 @@ protected void addLocalTransactions( } } - protected long getAddedCount(final String source, final String priority, final String layer) { + protected long getAddedCount( + final String source, final String priority, final AddReason addReason, final String layer) { return metricsSystem.getCounterValue( - TransactionPoolMetrics.ADDED_COUNTER_NAME, source, priority, layer); + TransactionPoolMetrics.ADDED_COUNTER_NAME, source, priority, addReason.label(), layer); } protected long getRemovedCount( diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/EvictCollectorLayer.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/EvictCollectorLayer.java index 27f478d172c..31cedb1d948 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/EvictCollectorLayer.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/EvictCollectorLayer.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -35,8 +35,9 @@ public String name() { } @Override - public TransactionAddedResult add(final PendingTransaction pendingTransaction, final int gap) { - final var res = super.add(pendingTransaction, gap); + public TransactionAddedResult add( + final PendingTransaction pendingTransaction, final int gap, final AddReason addReason) { + final var res = super.add(pendingTransaction, gap, addReason); evictedTxs.add(pendingTransaction); return res; } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/GasPricePrioritizedTransactionsTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/GasPricePrioritizedTransactionsTest.java index 89e986678ff..66dcd3c7219 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/GasPricePrioritizedTransactionsTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/GasPricePrioritizedTransactionsTest.java @@ -1,5 +1,5 @@ /* - * Copyright Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -20,6 +20,7 @@ import org.hyperledger.besu.crypto.KeyPair; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionTestFixture; import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; @@ -42,10 +43,17 @@ AbstractPrioritizedTransactions getSorter( final TransactionsLayer nextLayer, final TransactionPoolMetrics txPoolMetrics, final BiFunction - transactionReplacementTester) { + transactionReplacementTester, + final MiningParameters miningParameters) { return new GasPricePrioritizedTransactions( - poolConfig, nextLayer, txPoolMetrics, transactionReplacementTester, new BlobCache()); + poolConfig, + ethScheduler, + nextLayer, + txPoolMetrics, + transactionReplacementTester, + new BlobCache(), + miningParameters); } @Override @@ -80,11 +88,14 @@ public void shouldPrioritizeGasPriceThenTimeAddedToPool() { i -> createRemotePendingTransaction( createTransaction( - 0, Wei.of(10), SIGNATURE_ALGORITHM.get().generateKeyPair()))) + 0, + DEFAULT_MIN_GAS_PRICE.add(1), + SIGNATURE_ALGORITHM.get().generateKeyPair()))) .toList(); final PendingTransaction highGasPriceTransaction = - createRemotePendingTransaction(createTransaction(0, Wei.of(100), KEYS1)); + createRemotePendingTransaction( + createTransaction(0, DEFAULT_MIN_GAS_PRICE.multiply(2), KEYS1)); shouldPrioritizeValueThenTimeAddedToPool( lowValueTxs.iterator(), highGasPriceTransaction, lowValueTxs.get(0)); diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactionsTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactionsTest.java index 9f86cfc9207..4fa3fa727aa 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactionsTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactionsTest.java @@ -1,5 +1,5 @@ /* - * Copyright Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,12 +15,15 @@ package org.hyperledger.besu.ethereum.eth.transactions.layered; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.datatypes.TransactionType.BLOB; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.ADDED; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.ALREADY_KNOWN; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.NONCE_TOO_FAR_IN_FUTURE_FOR_SENDER; import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.REJECTED_UNDERPRICED_REPLACEMENT; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.DROPPED; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.REPLACED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.AddReason.MOVE; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.AddReason.NEW; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason.DROPPED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason.REPLACED; import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.GAS_PRICE_BELOW_CURRENT_BASE_FEE; import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE; import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.BLOB_PRICE_BELOW_CURRENT_MIN; @@ -39,6 +42,7 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; @@ -54,6 +58,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.OptionalLong; import java.util.function.BiFunction; @@ -67,7 +72,8 @@ public class LayeredPendingTransactionsTest extends BaseTransactionPoolTest { protected static final int MAX_TRANSACTIONS = 5; - protected static final int MAX_CAPACITY_BYTES = 10_000; + protected static final int MAX_PRIORITIZED_BLOB_TRANSACTIONS = MAX_TRANSACTIONS + 1; + protected static final int MAX_CAPACITY_BYTES = 150_000; protected static final Wei DEFAULT_BASE_FEE = Wei.of(100); protected static final int LIMITED_TRANSACTIONS_BY_SENDER = 4; protected static final String REMOTE = "remote"; @@ -81,20 +87,31 @@ public class LayeredPendingTransactionsTest extends BaseTransactionPoolTest { private final TransactionPoolConfiguration poolConf = ImmutableTransactionPoolConfiguration.builder() .maxPrioritizedTransactions(MAX_TRANSACTIONS) + .maxPrioritizedTransactionsByType(Map.of(BLOB, MAX_PRIORITIZED_BLOB_TRANSACTIONS)) .maxFutureBySender(MAX_TRANSACTIONS) - .pendingTransactionsLayerMaxCapacityBytes(MAX_CAPACITY_BYTES) .build(); private final TransactionPoolConfiguration senderLimitedConfig = ImmutableTransactionPoolConfiguration.builder() .maxPrioritizedTransactions(MAX_TRANSACTIONS) + .maxPrioritizedTransactionsByType(Map.of(BLOB, MAX_PRIORITIZED_BLOB_TRANSACTIONS)) + .maxFutureBySender(LIMITED_TRANSACTIONS_BY_SENDER) + .build(); + + private final TransactionPoolConfiguration smallPoolConfig = + ImmutableTransactionPoolConfiguration.builder() + .maxPrioritizedTransactions(MAX_TRANSACTIONS) + .maxPrioritizedTransactionsByType(Map.of(BLOB, MAX_PRIORITIZED_BLOB_TRANSACTIONS)) .maxFutureBySender(LIMITED_TRANSACTIONS_BY_SENDER) .pendingTransactionsLayerMaxCapacityBytes(MAX_CAPACITY_BYTES) .build(); + private LayeredPendingTransactions senderLimitedTransactions; private LayeredPendingTransactions pendingTransactions; + private LayeredPendingTransactions smallPendingTransactions; private CreatedLayers senderLimitedLayers; private CreatedLayers layers; + private CreatedLayers smallLayers; private TransactionPoolMetrics txPoolMetrics; private static BlockHeader mockBlockHeader() { @@ -107,7 +124,8 @@ private CreatedLayers createLayers(final TransactionPoolConfiguration poolConfig final BiFunction transactionReplacementTester = (t1, t2) -> - new TransactionPoolReplacementHandler(poolConf.getPriceBump()) + new TransactionPoolReplacementHandler( + poolConf.getPriceBump(), poolConfig.getBlobPriceBump()) .shouldReplace(t1, t2, mockBlockHeader()); final EvictCollectorLayer evictCollector = new EvictCollectorLayer(txPoolMetrics); @@ -115,6 +133,7 @@ private CreatedLayers createLayers(final TransactionPoolConfiguration poolConfig final SparseTransactions sparseTransactions = new SparseTransactions( poolConfig, + ethScheduler, evictCollector, txPoolMetrics, transactionReplacementTester, @@ -123,6 +142,7 @@ private CreatedLayers createLayers(final TransactionPoolConfiguration poolConfig final ReadyTransactions readyTransactions = new ReadyTransactions( poolConfig, + ethScheduler, sparseTransactions, txPoolMetrics, transactionReplacementTester, @@ -132,11 +152,13 @@ private CreatedLayers createLayers(final TransactionPoolConfiguration poolConfig new BaseFeePrioritizedTransactions( poolConfig, LayeredPendingTransactionsTest::mockBlockHeader, + ethScheduler, readyTransactions, txPoolMetrics, transactionReplacementTester, FeeMarket.london(0L), - new BlobCache()); + new BlobCache(), + MiningParameters.newDefault().setMinTransactionGasPrice(DEFAULT_MIN_GAS_PRICE)); return new CreatedLayers( prioritizedTransactions, readyTransactions, sparseTransactions, evictCollector); } @@ -148,12 +170,18 @@ public void setup() { layers = createLayers(poolConf); senderLimitedLayers = createLayers(senderLimitedConfig); + smallLayers = createLayers(smallPoolConfig); - pendingTransactions = new LayeredPendingTransactions(poolConf, layers.prioritizedTransactions); + pendingTransactions = + new LayeredPendingTransactions(poolConf, layers.prioritizedTransactions, ethScheduler); senderLimitedTransactions = new LayeredPendingTransactions( - senderLimitedConfig, senderLimitedLayers.prioritizedTransactions); + senderLimitedConfig, senderLimitedLayers.prioritizedTransactions, ethScheduler); + + smallPendingTransactions = + new LayeredPendingTransactions( + smallPoolConfig, smallLayers.prioritizedTransactions, ethScheduler); } @Test @@ -181,14 +209,14 @@ public void addRemoteTransactions() { createRemotePendingTransaction(transaction0), Optional.empty()); assertThat(pendingTransactions.size()).isEqualTo(1); - assertThat(getAddedCount(REMOTE, NO_PRIORITY, layers.prioritizedTransactions.name())) + assertThat(getAddedCount(REMOTE, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) .isEqualTo(1); pendingTransactions.addTransaction( createRemotePendingTransaction(transaction1), Optional.empty()); assertThat(pendingTransactions.size()).isEqualTo(2); - assertThat(getAddedCount(REMOTE, NO_PRIORITY, layers.prioritizedTransactions.name())) + assertThat(getAddedCount(REMOTE, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) .isEqualTo(2); } @@ -208,46 +236,84 @@ public void getTransactionByHash() { public void evictTransactionsWhenSizeLimitExceeded() { final List firstTxs = new ArrayList<>(MAX_TRANSACTIONS); - pendingTransactions.subscribeDroppedTransactions(droppedListener); + smallPendingTransactions.subscribeDroppedTransactions(droppedListener); for (int i = 0; i < MAX_TRANSACTIONS; i++) { final Account sender = mock(Account.class); when(sender.getNonce()).thenReturn((long) i); final var tx = - createTransaction( + createTransactionOfSize( i, - Wei.of((i + 1) * 100L), - (int) poolConf.getPendingTransactionsLayerMaxCapacityBytes() + 1, + DEFAULT_BASE_FEE.add(i), + (int) smallPoolConfig.getPendingTransactionsLayerMaxCapacityBytes() + 1, SIGNATURE_ALGORITHM.get().generateKeyPair()); - pendingTransactions.addTransaction(createRemotePendingTransaction(tx), Optional.of(sender)); + smallPendingTransactions.addTransaction( + createRemotePendingTransaction(tx), Optional.of(sender)); firstTxs.add(tx); - assertTransactionPending(pendingTransactions, tx); + assertTransactionPending(smallPendingTransactions, tx); } - assertThat(pendingTransactions.size()).isEqualTo(MAX_TRANSACTIONS); + assertThat(smallPendingTransactions.size()).isEqualTo(MAX_TRANSACTIONS); final Transaction lastBigTx = - createTransaction( + createTransactionOfSize( 0, - Wei.of(100_000L), - (int) poolConf.getPendingTransactionsLayerMaxCapacityBytes(), + DEFAULT_MIN_GAS_PRICE.multiply(1000), + (int) smallPoolConfig.getPendingTransactionsLayerMaxCapacityBytes(), SIGNATURE_ALGORITHM.get().generateKeyPair()); final Account lastSender = mock(Account.class); when(lastSender.getNonce()).thenReturn(0L); - pendingTransactions.addTransaction( + smallPendingTransactions.addTransaction( createRemotePendingTransaction(lastBigTx), Optional.of(lastSender)); - assertTransactionPending(pendingTransactions, lastBigTx); + assertTransactionPending(smallPendingTransactions, lastBigTx); - assertTransactionNotPending(pendingTransactions, firstTxs.get(0)); + assertTransactionNotPending(smallPendingTransactions, firstTxs.get(0)); assertThat( - getRemovedCount(REMOTE, NO_PRIORITY, DROPPED.label(), layers.evictedCollector.name())) + getRemovedCount( + REMOTE, NO_PRIORITY, DROPPED.label(), smallLayers.evictedCollector.name())) + .isEqualTo(1); + // before get evicted definitively, the tx moves to the lower layers, where it does not fix, + // until is discarded + assertThat(getAddedCount(REMOTE, NO_PRIORITY, MOVE, smallLayers.readyTransactions.name())) + .isEqualTo(1); + assertThat(getAddedCount(REMOTE, NO_PRIORITY, MOVE, smallLayers.sparseTransactions.name())) .isEqualTo(1); - assertThat(layers.evictedCollector.getEvictedTransactions()) + assertThat(smallLayers.evictedCollector.getEvictedTransactions()) .map(PendingTransaction::getTransaction) .contains(firstTxs.get(0)); verify(droppedListener).onTransactionDropped(firstTxs.get(0)); } + @Test + public void txsMovingToNextLayerWhenFirstIsFull() { + final List txs = new ArrayList<>(MAX_TRANSACTIONS + 1); + + pendingTransactions.subscribeDroppedTransactions(droppedListener); + + for (int i = 0; i < MAX_TRANSACTIONS + 1; i++) { + final Account sender = mock(Account.class); + when(sender.getNonce()).thenReturn((long) i); + final var tx = + createTransaction( + i, DEFAULT_BASE_FEE.add(i), SIGNATURE_ALGORITHM.get().generateKeyPair()); + pendingTransactions.addTransaction(createRemotePendingTransaction(tx), Optional.of(sender)); + txs.add(tx); + assertTransactionPending(pendingTransactions, tx); + } + + assertThat(pendingTransactions.size()).isEqualTo(MAX_TRANSACTIONS + 1); + assertThat(getAddedCount(REMOTE, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) + .isEqualTo(MAX_TRANSACTIONS + 1); + + // one tx moved to the ready layer since the prioritized was full + assertThat(getAddedCount(REMOTE, NO_PRIORITY, MOVE, layers.readyTransactions.name())) + .isEqualTo(1); + + // first tx is the lowest value one so it is the first to be moved to ready + assertThat(layers.readyTransactions.contains(txs.get(0))).isTrue(); + verifyNoInteractions(droppedListener); + } + @Test public void addTransactionForMultipleSenders() { final var transactionSenderA = createTransaction(0, KEYS1); @@ -458,8 +524,10 @@ public void notForceNonceOrderWhenSendersDiffer() { final Account sender2 = mock(Account.class); when(sender2.getNonce()).thenReturn(1L); - final Transaction transactionSender1 = createTransaction(0, Wei.of(100), KEYS1); - final Transaction transactionSender2 = createTransaction(1, Wei.of(200), KEYS2); + final Transaction transactionSender1 = + createTransaction(0, DEFAULT_MIN_GAS_PRICE.multiply(2), KEYS1); + final Transaction transactionSender2 = + createTransaction(1, DEFAULT_MIN_GAS_PRICE.multiply(4), KEYS2); pendingTransactions.addTransaction( createLocalPendingTransaction(transactionSender1), Optional.empty()); @@ -523,9 +591,9 @@ public void returnEmptyOptionalAsMaximumNonceWhenNoTransactionsPresent() { @Test public void replaceTransactionWithSameSenderAndNonce() { - final Transaction transaction1 = createTransaction(0, Wei.of(200), KEYS1); + final Transaction transaction1 = createTransaction(0, DEFAULT_MIN_GAS_PRICE.multiply(4), KEYS1); final Transaction transaction1b = createTransactionReplacement(transaction1, KEYS1); - final Transaction transaction2 = createTransaction(1, Wei.of(100), KEYS1); + final Transaction transaction2 = createTransaction(1, DEFAULT_MIN_GAS_PRICE.multiply(2), KEYS1); assertThat( pendingTransactions.addTransaction( createRemotePendingTransaction(transaction1), Optional.empty())) @@ -544,7 +612,7 @@ public void replaceTransactionWithSameSenderAndNonce() { assertTransactionPending(pendingTransactions, transaction1b); assertTransactionPending(pendingTransactions, transaction2); assertThat(pendingTransactions.size()).isEqualTo(2); - assertThat(getAddedCount(REMOTE, NO_PRIORITY, layers.prioritizedTransactions.name())) + assertThat(getAddedCount(REMOTE, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) .isEqualTo(3); assertThat( getRemovedCount( @@ -582,7 +650,7 @@ public void replaceTransactionWithSameSenderAndNonce_multipleReplacements() { assertTransactionPending(pendingTransactions, independentTx); assertThat(pendingTransactions.size()).isEqualTo(2); - assertThat(getAddedCount(REMOTE, NO_PRIORITY, layers.prioritizedTransactions.name())) + assertThat(getAddedCount(REMOTE, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) .isEqualTo(replacedTxCount + 2); assertThat( getRemovedCount( @@ -630,9 +698,9 @@ public void replaceTransactionWithSameSenderAndNonce_multipleReplacements() { final int localDuplicateCount = replacedTxCount - remoteDuplicateCount; assertThat(pendingTransactions.size()).isEqualTo(2); - assertThat(getAddedCount(REMOTE, NO_PRIORITY, layers.prioritizedTransactions.name())) + assertThat(getAddedCount(REMOTE, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) .isEqualTo(remoteDuplicateCount + 1); - assertThat(getAddedCount(LOCAL, NO_PRIORITY, layers.prioritizedTransactions.name())) + assertThat(getAddedCount(LOCAL, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) .isEqualTo(localDuplicateCount + 1); assertThat( getRemovedCount( @@ -646,8 +714,8 @@ public void replaceTransactionWithSameSenderAndNonce_multipleReplacements() { @Test public void notReplaceTransactionWithSameSenderAndNonceWhenGasPriceIsLower() { - final Transaction transaction1 = createTransaction(0, Wei.of(2)); - final Transaction transaction1b = createTransaction(0, Wei.ONE); + final Transaction transaction1 = createTransaction(0, DEFAULT_MIN_GAS_PRICE.add(1)); + final Transaction transaction1b = createTransaction(0, DEFAULT_MIN_GAS_PRICE); assertThat( pendingTransactions.addTransaction( createRemotePendingTransaction(transaction1), Optional.empty())) @@ -784,18 +852,20 @@ public void shouldNotIncrementAddedCounterWhenRemoteTransactionAlreadyPresent() pendingTransactions.addTransaction( createLocalPendingTransaction(transaction0), Optional.empty()); assertThat(pendingTransactions.size()).isEqualTo(1); - assertThat(getAddedCount(LOCAL, NO_PRIORITY, layers.prioritizedTransactions.name())) + assertThat(getAddedCount(LOCAL, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) .isEqualTo(1); - assertThat(getAddedCount(REMOTE, NO_PRIORITY, layers.prioritizedTransactions.name())).isZero(); + assertThat(getAddedCount(REMOTE, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) + .isZero(); assertThat( pendingTransactions.addTransaction( createRemotePendingTransaction(transaction0), Optional.empty())) .isEqualTo(ALREADY_KNOWN); assertThat(pendingTransactions.size()).isEqualTo(1); - assertThat(getAddedCount(LOCAL, NO_PRIORITY, layers.prioritizedTransactions.name())) + assertThat(getAddedCount(LOCAL, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) .isEqualTo(1); - assertThat(getAddedCount(REMOTE, NO_PRIORITY, layers.prioritizedTransactions.name())).isZero(); + assertThat(getAddedCount(REMOTE, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) + .isZero(); } @Test @@ -803,8 +873,9 @@ public void shouldNotIncrementAddedCounterWhenLocalTransactionAlreadyPresent() { pendingTransactions.addTransaction( createRemotePendingTransaction(transaction0), Optional.empty()); assertThat(pendingTransactions.size()).isEqualTo(1); - assertThat(getAddedCount(LOCAL, NO_PRIORITY, layers.prioritizedTransactions.name())).isZero(); - assertThat(getAddedCount(REMOTE, NO_PRIORITY, layers.prioritizedTransactions.name())) + assertThat(getAddedCount(LOCAL, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) + .isZero(); + assertThat(getAddedCount(REMOTE, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) .isEqualTo(1); assertThat( @@ -812,8 +883,9 @@ public void shouldNotIncrementAddedCounterWhenLocalTransactionAlreadyPresent() { createLocalPendingTransaction(transaction0), Optional.empty())) .isEqualTo(ALREADY_KNOWN); assertThat(pendingTransactions.size()).isEqualTo(1); - assertThat(getAddedCount(LOCAL, NO_PRIORITY, layers.prioritizedTransactions.name())).isZero(); - assertThat(getAddedCount(REMOTE, NO_PRIORITY, layers.prioritizedTransactions.name())) + assertThat(getAddedCount(LOCAL, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) + .isZero(); + assertThat(getAddedCount(REMOTE, NO_PRIORITY, NEW, layers.prioritizedTransactions.name())) .isEqualTo(1); } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredTransactionPoolBaseFeeTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredTransactionPoolBaseFeeTest.java index b70f3c24ac5..b3a840e7063 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredTransactionPoolBaseFeeTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredTransactionPoolBaseFeeTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -19,6 +19,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.ExecutionContextTestFixture; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionTestFixture; import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; @@ -42,11 +43,13 @@ protected AbstractPrioritizedTransactions createPrioritizedTransactions( return new BaseFeePrioritizedTransactions( poolConfig, protocolContext.getBlockchain()::getChainHeadHeader, + ethScheduler, nextLayer, txPoolMetrics, transactionReplacementTester, FeeMarket.london(0L), - new BlobCache()); + new BlobCache(), + MiningParameters.newDefault()); } @Override diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredTransactionPoolGasPriceTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredTransactionPoolGasPriceTest.java index e0066e03703..0c6f5f0bd2d 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredTransactionPoolGasPriceTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredTransactionPoolGasPriceTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -19,6 +19,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.ExecutionContextTestFixture; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionTestFixture; import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; @@ -39,7 +40,13 @@ protected AbstractPrioritizedTransactions createPrioritizedTransactions( final BiFunction transactionReplacementTester) { return new GasPricePrioritizedTransactions( - poolConfig, nextLayer, txPoolMetrics, transactionReplacementTester, new BlobCache()); + poolConfig, + ethScheduler, + nextLayer, + txPoolMetrics, + transactionReplacementTester, + new BlobCache(), + MiningParameters.newDefault()); } @Override diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayersTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayersTest.java index 7a22683ec42..b5503ba81cf 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayersTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayersTest.java @@ -15,21 +15,31 @@ package org.hyperledger.besu.ethereum.eth.transactions.layered; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.awaitility.Awaitility.await; +import static org.hyperledger.besu.datatypes.TransactionType.ACCESS_LIST; +import static org.hyperledger.besu.datatypes.TransactionType.BLOB; +import static org.hyperledger.besu.datatypes.TransactionType.EIP1559; +import static org.hyperledger.besu.datatypes.TransactionType.FRONTIER; import static org.hyperledger.besu.ethereum.eth.transactions.layered.LayersTest.Sender.S1; import static org.hyperledger.besu.ethereum.eth.transactions.layered.LayersTest.Sender.S2; import static org.hyperledger.besu.ethereum.eth.transactions.layered.LayersTest.Sender.S3; import static org.hyperledger.besu.ethereum.eth.transactions.layered.LayersTest.Sender.S4; import static org.hyperledger.besu.ethereum.eth.transactions.layered.LayersTest.Sender.SP1; import static org.hyperledger.besu.ethereum.eth.transactions.layered.LayersTest.Sender.SP2; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.INVALIDATED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason.INVALIDATED; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import org.hyperledger.besu.crypto.KeyPair; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.Util; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; @@ -38,19 +48,23 @@ import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolReplacementHandler; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.testutil.DeterministicEthScheduler; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.EnumMap; +import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.NavigableMap; import java.util.Optional; import java.util.OptionalLong; +import java.util.TreeMap; import java.util.stream.Stream; -import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -58,51 +72,35 @@ public class LayersTest extends BaseTransactionPoolTest { private static final int MAX_PRIO_TRANSACTIONS = 3; private static final int MAX_FUTURE_FOR_SENDER = 10; + private static final Wei BASE_FEE = Wei.ONE; + private static final Wei MIN_GAS_PRICE = BASE_FEE; + private static final byte MIN_SCORE = 125; - private final TransactionPoolConfiguration poolConfig = + private static final TransactionPoolConfiguration DEFAULT_TX_POOL_CONFIG = ImmutableTransactionPoolConfiguration.builder() .maxPrioritizedTransactions(MAX_PRIO_TRANSACTIONS) + .maxPrioritizedTransactionsByType(Map.of(BLOB, 1)) .maxFutureBySender(MAX_FUTURE_FOR_SENDER) + .minScore(MIN_SCORE) .pendingTransactionsLayerMaxCapacityBytes( - new PendingTransaction.Remote(createEIP1559Transaction(0, KEYS1, 1)).memorySize() * 3) + new PendingTransaction.Remote( + new BaseTransactionPoolTest().createEIP1559Transaction(0, KEYS1, 1)) + .memorySize() + * 3L) .build(); - private final TransactionPoolMetrics txPoolMetrics = new TransactionPoolMetrics(metricsSystem); - - private final EvictCollectorLayer evictCollector = new EvictCollectorLayer(txPoolMetrics); - private final SparseTransactions sparseTransactions = - new SparseTransactions( - poolConfig, - evictCollector, - txPoolMetrics, - this::transactionReplacementTester, - new BlobCache()); - - private final ReadyTransactions readyTransactions = - new ReadyTransactions( - poolConfig, - sparseTransactions, - txPoolMetrics, - this::transactionReplacementTester, - new BlobCache()); - - private final BaseFeePrioritizedTransactions prioritizedTransactions = - new BaseFeePrioritizedTransactions( - poolConfig, - LayersTest::mockBlockHeader, - readyTransactions, - txPoolMetrics, - this::transactionReplacementTester, - FeeMarket.london(0L), - new BlobCache()); - - private final LayeredPendingTransactions pendingTransactions = - new LayeredPendingTransactions(poolConfig, prioritizedTransactions); - - @AfterEach - void reset() { - pendingTransactions.reset(); - } + private static final TransactionPoolConfiguration BLOB_TX_POOL_CONFIG = + ImmutableTransactionPoolConfiguration.builder() + .maxPrioritizedTransactions(MAX_PRIO_TRANSACTIONS) + .maxPrioritizedTransactionsByType(Map.of(BLOB, 1)) + .maxFutureBySender(MAX_FUTURE_FOR_SENDER) + .minScore(MIN_SCORE) + .pendingTransactionsLayerMaxCapacityBytes( + new PendingTransaction.Remote( + new BaseTransactionPoolTest().createEIP4844Transaction(0, KEYS1, 1, 1)) + .memorySize() + * 3L) + .build(); @ParameterizedTest @MethodSource("providerAddTransactions") @@ -164,13 +162,20 @@ void prioritySenders(final Scenario scenario) { assertScenario(scenario); } + @ParameterizedTest + @MethodSource("providerMaxPrioritizedByType") + void maxPrioritizedByType(final Scenario scenario) { + assertScenario(scenario); + } + + @ParameterizedTest + @MethodSource("providerPenalized") + void penalized(final Scenario scenario) { + assertScenario(scenario); + } + private void assertScenario(final Scenario scenario) { - scenario.execute( - pendingTransactions, - prioritizedTransactions, - readyTransactions, - sparseTransactions, - evictCollector); + scenario.run(); } static Stream providerAddTransactions() { @@ -270,7 +275,7 @@ static Stream providerAddTransactions() { Arguments.of( new Scenario("fill sparse 2") .addForSender(S1, 5, 3, 2) - .expectedSparseForSender(S1, 5, 3, 2)), + .expectedSparseForSender(S1, 2, 3, 5)), Arguments.of( new Scenario("overflow sparse 1") .addForSender(S1, 1, 2, 3, 4) @@ -279,13 +284,13 @@ static Stream providerAddTransactions() { Arguments.of( new Scenario("overflow sparse 2") .addForSender(S1, 4, 2, 3, 1) - .expectedSparseForSender(S1, 2, 3, 1) + .expectedSparseForSender(S1, 1, 2, 3) .expectedDroppedForSender(S1, 4)), Arguments.of( new Scenario("overflow sparse 3") .addForSender(S1, 0, 4, 2, 3, 5) .expectedPrioritizedForSender(S1, 0) - .expectedSparseForSender(S1, 4, 2, 3) + .expectedSparseForSender(S1, 2, 3, 4) .expectedDroppedForSender(S1, 5))); } @@ -298,7 +303,7 @@ static Stream providerAddTransactionsMultipleSenders() { Arguments.of( new Scenario("add first sparse") .addForSenders(S1, 1, S2, 2) - .expectedSparseForSenders(S1, 1, S2, 2)), + .expectedSparseForSenders(S2, 2, S1, 1)), Arguments.of( new Scenario("fill prioritized 1") .addForSender(S1, 0, 1, 2) @@ -321,11 +326,11 @@ static Stream providerAddTransactionsMultipleSenders() { .addForSenders(S1, 2, S2, 1) .expectedPrioritizedForSenders() .expectedReadyForSenders() - .expectedSparseForSenders(S1, 2, S2, 1) + .expectedSparseForSenders(S2, 1, S1, 2) .addForSenders(S2, 2, S1, 0) .expectedPrioritizedForSender(S1, 0) .expectedReadyForSenders() - .expectedSparseForSenders(S1, 2, S2, 1, S2, 2) + .expectedSparseForSenders(S2, 1, S2, 2, S1, 2) .addForSenders(S1, 1) .expectedPrioritizedForSenders(S1, 0, S1, 1, S1, 2) .expectedReadyForSenders() @@ -395,38 +400,38 @@ static Stream providerAddTransactionsMultipleSenders() { .addForSenders(S2, 0, S3, 2, S1, 1) .expectedPrioritizedForSender(S2, 0) .expectedReadyForSenders() - .expectedSparseForSenders(S3, 2, S1, 1) + .expectedSparseForSenders(S1, 1, S3, 2) .addForSenders(S2, 1) .expectedPrioritizedForSenders(S2, 0, S2, 1) .expectedReadyForSenders() - .expectedSparseForSenders(S3, 2, S1, 1) + .expectedSparseForSenders(S1, 1, S3, 2) .addForSenders(S3, 0) .expectedPrioritizedForSenders(S3, 0, S2, 0, S2, 1) .expectedReadyForSenders() - .expectedSparseForSenders(S3, 2, S1, 1) + .expectedSparseForSenders(S1, 1, S3, 2) .addForSenders(S1, 0) .expectedPrioritizedForSenders(S3, 0, S2, 0, S2, 1) .expectedReadyForSenders(S1, 0, S1, 1) .expectedSparseForSender(S3, 2) .addForSenders(S3, 1) - // ToDo: only S3[1] is prioritized because there is no space to try to fill gaps + // only S3[1] is prioritized because there is no space to try to fill gaps .expectedPrioritizedForSenders(S3, 0, S3, 1, S2, 0) .expectedReadyForSenders(S2, 1, S1, 0, S1, 1) .expectedSparseForSender(S3, 2) .addForSenders(S4, 0, S4, 1, S3, 3) .expectedPrioritizedForSenders(S4, 0, S4, 1, S3, 0) .expectedReadyForSenders(S3, 1, S2, 0, S2, 1) - .expectedSparseForSenders(S3, 2, S1, 1, S1, 0) + .expectedSparseForSenders(S1, 0, S1, 1, S3, 2) // ToDo: non optimal discard, worth to improve? .expectedDroppedForSender(S3, 3)), Arguments.of( new Scenario("replacement cross layer") .addForSenders(S2, 0, S3, 2, S1, 1, S2, 1, S3, 0, S1, 0, S3, 1) - // ToDo: only S3[1] is prioritized because there is no space to try to fill gaps + // only S3[1] is prioritized because there is no space to try to fill gaps .expectedPrioritizedForSenders(S3, 0, S3, 1, S2, 0) .expectedReadyForSenders(S2, 1, S1, 0, S1, 1) .expectedSparseForSender(S3, 2) - .addForSenders(S3, 2) // added in prioritized, but replacement in sparse + .replaceForSenders(S3, 2) // added in prioritized, but replacement in sparse .expectedPrioritizedForSenders(S3, 0, S3, 1, S3, 2) .expectedReadyForSenders(S2, 0, S2, 1, S1, 0) .expectedSparseForSender(S1, 1))); @@ -434,6 +439,8 @@ static Stream providerAddTransactionsMultipleSenders() { static Stream providerRemoveTransactions() { return Stream.of( + // when expected*ForSender(s) is not present, by default there is a check that the layers + // are empty Arguments.of(new Scenario("remove not existing").removeForSender(S1, 0)), Arguments.of(new Scenario("add/remove first").addForSender(S1, 0).removeForSender(S1, 0)), Arguments.of( @@ -777,7 +784,7 @@ static Stream providerNextNonceForSender() { Arguments.of( new Scenario("out of order sequence with gap 1") .addForSender(S1, 2, 1) - .expectedSparseForSender(S1, 2, 1) + .expectedSparseForSender(S1, 1, 2) .expectedNextNonceForSenders(S1, null)), Arguments.of( new Scenario("out of order sequence with gap 2") @@ -933,7 +940,7 @@ static Stream providerSelectTransactions() { Arguments.of( new Scenario("out of order sequence with gap 1") .addForSender(S1, 2, 1) - .expectedSparseForSender(S1, 2, 1) + .expectedSparseForSender(S1, 1, 2) .expectedSelectedTransactions()), Arguments.of( new Scenario("out of order sequence with gap 2") @@ -1017,10 +1024,10 @@ static Stream providerReorg() { .expectedNextNonceForSenders(S1, 3) .addForSender(S1, 3) .expectedPrioritizedForSender(S1, 2, 3) - .setAccountNonce(S1, 0) // rewind nonce due to reorg - .addForSender(S1, 0) - .expectedPrioritizedForSender(S1, 0) - .expectedSparseForSender(S1, 2, 3))); + .reorgForSenders(S1, 0) // rewind nonce due to reorg + .addForSender(S1, 0, 1) // re-add reorged txs + .expectedPrioritizedForSender(S1, 0, 1, 2) + .expectedReadyForSender(S1, 3))); } static Stream providerAsyncWorldStateUpdates() { @@ -1037,8 +1044,7 @@ static Stream providerAsyncWorldStateUpdates() { .setAccountNonce(S1, 5) .addForSender(S1, 7) .expectedPrioritizedForSenders() - // remember that sparse are checked by oldest first - .expectedSparseForSender(S1, 8, 9, 7))); + .expectedSparseForSender(S1, 7, 8, 9))); } static Stream providerPrioritySenders() { @@ -1159,7 +1165,7 @@ static Stream providerPrioritySenders() { .addForSender(S3, 0) .expectedSparseForSender(S3, 0) .addForSender(SP1, 0) - .expectedSparseForSenders(S3, 0, SP1, 0) + .expectedSparseForSenders(SP1, 0, S3, 0) .confirmedForSenders(SP2, 0) .expectedPrioritizedForSender(SP2, 1, 2, 3) .expectedReadyForSenders(SP2, 4, SP2, 5, SP1, 0) @@ -1176,38 +1182,161 @@ static Stream providerPrioritySenders() { .expectedDroppedForSender(S3, 0))); } + static Stream providerMaxPrioritizedByType() { + return Stream.of( + Arguments.of( + new Scenario("first blob tx is prioritized", BLOB_TX_POOL_CONFIG) + .addForSender(S1, BLOB, 0) + .expectedPrioritizedForSender(S1, 0)), + Arguments.of( + new Scenario("multiple senders only first blob tx is prioritized", BLOB_TX_POOL_CONFIG) + .addForSender(S1, BLOB, 0) + .addForSender(S2, BLOB, 0) + .expectedPrioritizedForSender(S1, 0) + .expectedReadyForSender(S2, 0)), + Arguments.of( + new Scenario("same sender following blob txs are moved to ready", BLOB_TX_POOL_CONFIG) + .addForSender(S1, BLOB, 0, 1, 2) + .expectedPrioritizedForSender(S1, 0) + .expectedReadyForSender(S1, 1, 2)), + Arguments.of( + new Scenario("promoting txs respect prioritized count limit", BLOB_TX_POOL_CONFIG) + .addForSender(S1, BLOB, 0, 1, 2) + .expectedPrioritizedForSender(S1, 0) + .expectedReadyForSender(S1, 1, 2) + .confirmedForSenders(S1, 0) + .expectedPrioritizedForSender(S1, 1) + .expectedReadyForSender(S1, 2)), + Arguments.of( + new Scenario("filling gaps respect prioritized count limit", BLOB_TX_POOL_CONFIG) + .addForSender(S1, BLOB, 1) + .expectedSparseForSender(S1, 1) + .addForSender(S1, BLOB, 0) + .expectedPrioritizedForSender(S1, 0) + .expectedSparseForSender(S1, 1)), + Arguments.of( + new Scenario("promoting to ready is unbounded", BLOB_TX_POOL_CONFIG) + .addForSender(S1, BLOB, 0, 1, 2, 3, 4, 5, 6) + .expectedPrioritizedForSender(S1, 0) + .expectedReadyForSender(S1, 1, 2, 3) + .expectedSparseForSender(S1, 4, 5, 6) + .confirmedForSenders(S1, 3) + .expectedPrioritizedForSender(S1, 4) + .expectedReadyForSender(S1, 5, 6) + .expectedSparseForSenders())); + } + + static Stream providerPenalized() { + return Stream.of( + Arguments.of( + new Scenario("single sender, single tx") + .addForSender(S1, 0) + .expectedPrioritizedForSender(S1, 0) + .penalizeForSender(S1, 0) + .expectedPrioritizedForSender(S1, 0)), + Arguments.of( + new Scenario("single sender penalize last") + .addForSender(S1, 0, 1) + .expectedPrioritizedForSender(S1, 0, 1) + .penalizeForSender(S1, 1) + .expectedPrioritizedForSender(S1, 0, 1)), + Arguments.of( + new Scenario("single sender penalize first") + .addForSender(S1, 0, 1) + .expectedPrioritizedForSender(S1, 0, 1) + .penalizeForSender(S1, 0, 1) + // even if 0 has less score it is always the first for the sender + // since otherwise there is a nonce gap + .expectedPrioritizedForSender(S1, 0, 1)), + Arguments.of( + new Scenario("multiple senders, penalize top") + .addForSenders(S1, 0, S2, 0) + // remember S2 pays more fees + .expectedPrioritizedForSenders(S2, 0, S1, 0) + .penalizeForSender(S2, 0) + .expectedPrioritizedForSenders(S1, 0, S2, 0)), + Arguments.of( + new Scenario("multiple senders, penalize bottom") + .addForSenders(S1, 0, S2, 0) + .expectedPrioritizedForSenders(S2, 0, S1, 0) + .penalizeForSender(S1, 0) + .expectedPrioritizedForSenders(S2, 0, S1, 0)), + Arguments.of( + new Scenario("multiple senders, penalize middle") + .addForSenders(S1, 0, S2, 0, S3, 0) + .expectedPrioritizedForSenders(S3, 0, S2, 0, S1, 0) + .penalizeForSender(S2, 0) + .expectedPrioritizedForSenders(S3, 0, S1, 0, S2, 0)), + Arguments.of( + new Scenario("single sender, promote from ready") + .addForSender(S1, 0, 1, 2, 3, 4, 5) + .expectedPrioritizedForSender(S1, 0, 1, 2) + .expectedReadyForSender(S1, 3, 4, 5) + .penalizeForSender(S1, 3) + .confirmedForSenders(S1, 0) + // even if penalized 3 is promoted to avoid nonce gap + .expectedPrioritizedForSender(S1, 1, 2, 3) + .expectedReadyForSender(S1, 4, 5)), + Arguments.of( + new Scenario("multiple senders, overflow to ready") + .addForSenders(S1, 0, S2, 0, S3, 0) + .expectedPrioritizedForSenders(S3, 0, S2, 0, S1, 0) + .expectedReadyForSenders() + .penalizeForSender(S3, 0) + .addForSender(S1, 1) + .expectedPrioritizedForSenders(S2, 0, S1, 0, S1, 1) + // S3(0) is demoted to ready even if it is paying more fees, + // since has a lower score + .expectedReadyForSender(S3, 0)), + Arguments.of( + new Scenario("multiple senders, overflow to sparse") + .addForSenders(S1, 0, S2, 0, S3, 0, S1, 1, S2, 1, S3, 1) + .expectedPrioritizedForSenders(S3, 0, S3, 1, S2, 0) + .expectedReadyForSenders(S2, 1, S1, 0, S1, 1) + .penalizeForSender(S2, 1) + .addForSender(S2, 2) + .expectedReadyForSenders(S1, 0, S1, 1, S2, 1) + .expectedSparseForSender(S2, 2)), + Arguments.of( + new Scenario("remove below min score") + .addForSender(S1, 0) // score 127 + .expectedPrioritizedForSender(S1, 0) + .penalizeForSender(S1, 0) // score 126 + .expectedPrioritizedForSender(S1, 0) + .penalizeForSender(S1, 0) // score 125 + .expectedPrioritizedForSender(S1, 0) + .penalizeForSender(S1, 0) // score 124, removed since decreased score < MIN_SCORE + .expectedPrioritizedForSenders())); + } + private static BlockHeader mockBlockHeader() { final BlockHeader blockHeader = mock(BlockHeader.class); - when(blockHeader.getBaseFee()).thenReturn(Optional.of(Wei.ONE)); + when(blockHeader.getBaseFee()).thenReturn(Optional.of(BASE_FEE)); return blockHeader; } - private boolean transactionReplacementTester( - final PendingTransaction pt1, final PendingTransaction pt2) { - return transactionReplacementTester(poolConfig, pt1, pt2); - } - private static boolean transactionReplacementTester( final TransactionPoolConfiguration poolConfig, final PendingTransaction pt1, final PendingTransaction pt2) { final TransactionPoolReplacementHandler transactionReplacementHandler = - new TransactionPoolReplacementHandler(poolConfig.getPriceBump()); + new TransactionPoolReplacementHandler( + poolConfig.getPriceBump(), poolConfig.getBlobPriceBump()); return transactionReplacementHandler.shouldReplace(pt1, pt2, mockBlockHeader()); } - static class Scenario extends BaseTransactionPoolTest { - interface TransactionLayersConsumer { - void accept( - LayeredPendingTransactions pending, - AbstractPrioritizedTransactions prioritized, - ReadyTransactions ready, - SparseTransactions sparse, - EvictCollectorLayer dropped); - } + static class Scenario extends BaseTransactionPoolTest implements Runnable { final String description; - final List actions = new ArrayList<>(); + final TransactionPoolConfiguration poolConfig; + final EvictCollectorLayer dropped; + final SparseTransactions sparse; + final ReadyTransactions ready; + final AbstractPrioritizedTransactions prio; + final LayeredPendingTransactions pending; + + final NotificationsChecker notificationsChecker = new NotificationsChecker(); + final List actions = new ArrayList<>(); List lastExpectedPrioritized = new ArrayList<>(); List lastExpectedReady = new ArrayList<>(); List lastExpectedSparse = new ArrayList<>(); @@ -1219,207 +1348,443 @@ void accept( Arrays.stream(Sender.values()).forEach(e -> nonceBySender.put(e, 0L)); } - final EnumMap> txsBySender = new EnumMap<>(Sender.class); + final EnumSet sendersWithReorg = EnumSet.noneOf(Sender.class); + + final EnumMap> liveTxsBySender = + new EnumMap<>(Sender.class); { - Arrays.stream(Sender.values()).forEach(e -> txsBySender.put(e, new HashMap<>())); + Arrays.stream(Sender.values()).forEach(e -> liveTxsBySender.put(e, new TreeMap<>())); + } + + final EnumMap> droppedTxsBySender = + new EnumMap<>(Sender.class); + + { + Arrays.stream(Sender.values()).forEach(e -> droppedTxsBySender.put(e, new TreeMap<>())); } Scenario(final String description) { + this(description, DEFAULT_TX_POOL_CONFIG); + } + + Scenario(final String description, final TransactionPoolConfiguration poolConfig) { this.description = description; + this.poolConfig = poolConfig; + + final TransactionPoolMetrics txPoolMetrics = new TransactionPoolMetrics(metricsSystem); + + this.dropped = new EvictCollectorLayer(txPoolMetrics); + final EthScheduler ethScheduler = new DeterministicEthScheduler(); + this.sparse = + new SparseTransactions( + poolConfig, + ethScheduler, + this.dropped, + txPoolMetrics, + (pt1, pt2) -> transactionReplacementTester(poolConfig, pt1, pt2), + new BlobCache()); + + this.ready = + new ReadyTransactions( + poolConfig, + ethScheduler, + this.sparse, + txPoolMetrics, + (pt1, pt2) -> transactionReplacementTester(poolConfig, pt1, pt2), + new BlobCache()); + + this.prio = + new BaseFeePrioritizedTransactions( + poolConfig, + LayersTest::mockBlockHeader, + ethScheduler, + this.ready, + txPoolMetrics, + (pt1, pt2) -> transactionReplacementTester(poolConfig, pt1, pt2), + FeeMarket.london(0L), + new BlobCache(), + MiningParameters.newDefault().setMinTransactionGasPrice(MIN_GAS_PRICE)); + + this.pending = new LayeredPendingTransactions(poolConfig, this.prio, ethScheduler); + + this.pending.subscribePendingTransactions(notificationsChecker::collectAddNotification); + this.pending.subscribeDroppedTransactions(notificationsChecker::collectDropNotification); } - Scenario addForSender(final Sender sender, final long... nonce) { - Arrays.stream(nonce) - .forEach( - n -> { - final var pendingTx = getOrCreate(sender, n); - actions.add( - (pending, prio, ready, sparse, dropped) -> { + @Override + public void run() { + actions.forEach(Runnable::run); + assertExpectedPrioritized(prio, lastExpectedPrioritized); + assertExpectedReady(ready, lastExpectedReady); + assertExpectedSparse(sparse, lastExpectedSparse); + assertExpectedDropped(dropped, lastExpectedDropped); + } + + public Scenario addForSender(final Sender sender, final long... nonce) { + return addForSender(sender, EIP1559, nonce); + } + + public Scenario addForSender( + final Sender sender, final TransactionType type, final long... nonce) { + internalAddForSender(sender, type, nonce); + actions.add(notificationsChecker::assertExpectedNotifications); + return this; + } + + private void internalAddForSender( + final Sender sender, final TransactionType type, final long... nonce) { + actions.add( + () -> { + Arrays.stream(nonce) + .forEach( + n -> { + final var pendingTx = create(sender, type, n); final Account mockSender = mock(Account.class); when(mockSender.getNonce()).thenReturn(nonceBySender.get(sender)); pending.addTransaction(pendingTx, Optional.of(mockSender)); + notificationsChecker.addExpectedAddNotification(pendingTx); }); + + // reorg case + if (sendersWithReorg.contains(sender)) { + // reorg is removing and re-adding all sender txs, so assert notifications accordingly + final var currentPendingTxs = + liveTxsBySender.get(sender).tailMap(nonce[nonce.length - 1], false).values(); + currentPendingTxs.forEach( + pt -> { + notificationsChecker.addExpectedAddNotification(pt); + notificationsChecker.addExpectedDropNotification(pt); + }); + sendersWithReorg.remove(sender); + } + + // reconciliation case + final var txsRemovedByReconciliation = + liveTxsBySender.get(sender).headMap(nonceBySender.get(sender), false).values(); + if (!txsRemovedByReconciliation.isEmpty()) { + // reconciliation is removing all sender txs, and re-adding only the ones with a + // larger nonce, so assert notifications accordingly + final var reconciledPendingTxs = + liveTxsBySender.get(sender).tailMap(nonce[nonce.length - 1], false).values(); + txsRemovedByReconciliation.forEach(notificationsChecker::addExpectedDropNotification); + reconciledPendingTxs.forEach( + pt -> { + notificationsChecker.addExpectedDropNotification(pt); + notificationsChecker.addExpectedAddNotification(pt); + }); + txsRemovedByReconciliation.clear(); + } + + handleDropped(); + }); + } + + private void handleDropped() { + // handle dropped tx due to layer or pool full + final var droppedTxs = dropped.getEvictedTransactions(); + droppedTxs.forEach(notificationsChecker::addExpectedDropNotification); + droppedTxs.stream() + .forEach( + pt -> { + liveTxsBySender.get(Sender.getByAddress(pt.getSender())).remove(pt.getNonce()); + droppedTxsBySender.get(Sender.getByAddress(pt.getSender())).put(pt.getNonce(), pt); }); - return this; } - Scenario addForSenders(final Object... args) { + public Scenario addForSenders(final Object... args) { for (int i = 0; i < args.length; i = i + 2) { final Sender sender = (Sender) args[i]; final long nonce = (int) args[i + 1]; - addForSender(sender, nonce); + internalAddForSender(sender, EIP1559, nonce); } + actions.add(notificationsChecker::assertExpectedNotifications); return this; } - public Scenario confirmedForSenders(final Object... args) { - final Map maxConfirmedNonceBySender = new HashMap<>(); + public Scenario replaceForSender(final Sender sender, final long... nonce) { + internalReplaceForSender(sender, nonce); + actions.add(notificationsChecker::assertExpectedNotifications); + return this; + } + + private Scenario internalReplaceForSender(final Sender sender, final long... nonce) { + actions.add( + () -> { + Arrays.stream(nonce) + .forEach( + n -> { + final var maybeExistingTx = getMaybe(sender, n); + maybeExistingTx.ifPresentOrElse( + existingTx -> { + final var pendingTx = replace(sender, existingTx); + final Account mockSender = mock(Account.class); + when(mockSender.getNonce()).thenReturn(nonceBySender.get(sender)); + pending.addTransaction(pendingTx, Optional.of(mockSender)); + notificationsChecker.addExpectedAddNotification(pendingTx); + notificationsChecker.addExpectedDropNotification(existingTx); + }, + () -> + fail( + "Could not replace non-existing transaction with nonce " + + n + + " for sender " + + sender.name())); + }); + }); + return this; + } + + public Scenario replaceForSenders(final Object... args) { for (int i = 0; i < args.length; i = i + 2) { final Sender sender = (Sender) args[i]; final long nonce = (int) args[i + 1]; - maxConfirmedNonceBySender.put(sender.address, nonce); - setAccountNonce(sender, nonce + 1); + internalReplaceForSender(sender, nonce); } + actions.add(notificationsChecker::assertExpectedNotifications); + return this; + } + + public Scenario confirmedForSenders(final Object... args) { actions.add( - (pending, prio, ready, sparse, dropped) -> - prio.blockAdded(FeeMarket.london(0L), mockBlockHeader(), maxConfirmedNonceBySender)); + () -> { + final Map maxConfirmedNonceBySender = new HashMap<>(); + for (int i = 0; i < args.length; i = i + 2) { + final Sender sender = (Sender) args[i]; + final long nonce = (int) args[i + 1]; + maxConfirmedNonceBySender.put(sender.address, nonce); + nonceBySender.put(sender, nonce + 1); + for (final var pendingTx : getAll(sender)) { + if (pendingTx.getNonce() <= nonce) { + notificationsChecker.addExpectedDropNotification( + liveTxsBySender.get(sender).remove(pendingTx.getNonce())); + } + } + } + + prio.blockAdded(FeeMarket.london(0L), mockBlockHeader(), maxConfirmedNonceBySender); + notificationsChecker.assertExpectedNotifications(); + }); return this; } - Scenario setAccountNonce(final Sender sender, final long nonce) { - actions.add((pending, prio, ready, sparse, dropped) -> nonceBySender.put(sender, nonce)); + public Scenario setAccountNonce(final Sender sender, final long nonce) { + actions.add(() -> nonceBySender.put(sender, nonce)); return this; } - void execute( - final LayeredPendingTransactions pending, - final AbstractPrioritizedTransactions prioritized, - final ReadyTransactions ready, - final SparseTransactions sparse, - final EvictCollectorLayer dropped) { - actions.forEach(action -> action.accept(pending, prioritized, ready, sparse, dropped)); - assertExpectedPrioritized(prioritized, lastExpectedPrioritized); - assertExpectedReady(ready, lastExpectedReady); - assertExpectedSparse(sparse, lastExpectedSparse); - assertExpectedDropped(dropped, lastExpectedDropped); + public Scenario reorgForSenders(final Object... args) { + actions.add( + () -> { + for (int i = 0; i < args.length; i = i + 2) { + final Sender sender = (Sender) args[i]; + final long nonce = (int) args[i + 1]; + nonceBySender.put(sender, nonce); + sendersWithReorg.add(sender); + } + }); + return this; + } + + private PendingTransaction create( + final Sender sender, final TransactionType type, final long nonce) { + if (liveTxsBySender.get(sender).containsKey(nonce)) { + fail( + "Transaction for sender " + sender.name() + " with nonce " + nonce + " already exists"); + } + final var newPendingTx = + switch (type) { + case FRONTIER -> createFrontierPendingTransaction(sender, nonce); + case ACCESS_LIST -> createAccessListPendingTransaction(sender, nonce); + case EIP1559 -> createEIP1559PendingTransaction(sender, nonce); + case BLOB -> createBlobPendingTransaction(sender, nonce); + case DELEGATE_CODE -> throw new UnsupportedOperationException(); + }; + liveTxsBySender.get(sender).put(nonce, newPendingTx); + return newPendingTx; } - private PendingTransaction getOrCreate(final Sender sender, final long nonce) { - return txsBySender - .get(sender) - .computeIfAbsent(nonce, n -> createEIP1559PendingTransactions(sender, n)); + private PendingTransaction replace(final Sender sender, final PendingTransaction pendingTx) { + final var replaceTx = + createRemotePendingTransaction( + createTransactionReplacement(pendingTx.getTransaction(), sender.key), + sender.hasPriority); + liveTxsBySender.get(sender).replace(pendingTx.getNonce(), replaceTx); + return replaceTx; + } + + private Optional getMaybe(final Sender sender, final long nonce) { + return Optional.ofNullable(liveTxsBySender.get(sender).get(nonce)); } private PendingTransaction get(final Sender sender, final long nonce) { - return txsBySender.get(sender).get(nonce); + return getMaybe(sender, nonce).get(); + } + + private List getAll(final Sender sender) { + return List.copyOf(liveTxsBySender.get(sender).values()); } - private PendingTransaction createEIP1559PendingTransactions( + private PendingTransaction createFrontierPendingTransaction( + final Sender sender, final long nonce) { + return createRemotePendingTransaction( + createTransaction(FRONTIER, nonce, Wei.ONE, 0, sender.key), sender.hasPriority); + } + + private PendingTransaction createAccessListPendingTransaction( + final Sender sender, final long nonce) { + return createRemotePendingTransaction( + createTransaction(ACCESS_LIST, nonce, Wei.ONE, 0, sender.key), sender.hasPriority); + } + + private PendingTransaction createEIP1559PendingTransaction( final Sender sender, final long nonce) { return createRemotePendingTransaction( createEIP1559Transaction(nonce, sender.key, sender.gasFeeMultiplier), sender.hasPriority); } + private PendingTransaction createBlobPendingTransaction(final Sender sender, final long nonce) { + return createRemotePendingTransaction( + createEIP4844Transaction(nonce, sender.key, sender.gasFeeMultiplier, 1), + sender.hasPriority); + } + public Scenario expectedPrioritizedForSender(final Sender sender, final long... nonce) { - lastExpectedPrioritized = expectedForSender(sender, nonce); - final var expectedCopy = List.copyOf(lastExpectedPrioritized); actions.add( - (pending, prio, ready, sparse, dropped) -> assertExpectedPrioritized(prio, expectedCopy)); + () -> { + lastExpectedPrioritized = expectedForSender(sender, nonce); + assertExpectedPrioritized(prio, lastExpectedPrioritized); + }); return this; } public Scenario expectedReadyForSender(final Sender sender, final long... nonce) { - lastExpectedReady = expectedForSender(sender, nonce); - final var expectedCopy = List.copyOf(lastExpectedReady); actions.add( - (pending, prio, ready, sparse, dropped) -> assertExpectedReady(ready, expectedCopy)); + () -> { + lastExpectedReady = expectedForSender(sender, nonce); + assertExpectedReady(ready, lastExpectedReady); + }); return this; } public Scenario expectedSparseForSender(final Sender sender, final long... nonce) { - lastExpectedSparse = expectedForSender(sender, nonce); - final var expectedCopy = List.copyOf(lastExpectedSparse); actions.add( - (pending, prio, ready, sparse, dropped) -> assertExpectedSparse(sparse, expectedCopy)); + () -> { + lastExpectedSparse = expectedForSender(sender, nonce); + assertExpectedSparse(sparse, lastExpectedSparse); + }); return this; } public Scenario expectedDroppedForSender(final Sender sender, final long... nonce) { - lastExpectedDropped = expectedForSender(sender, nonce); - final var expectedCopy = List.copyOf(lastExpectedDropped); actions.add( - (pending, prio, ready, sparse, dropped) -> assertExpectedDropped(dropped, expectedCopy)); + () -> { + lastExpectedDropped = droppedForSender(sender, nonce); + assertExpectedDropped(dropped, lastExpectedDropped); + }); return this; } public Scenario expectedPrioritizedForSenders( - final Sender sender1, final long nonce1, final Sender sender2, Object... args) { - lastExpectedPrioritized = expectedForSenders(sender1, nonce1, sender2, args); - final var expectedCopy = List.copyOf(lastExpectedPrioritized); + final Sender sender1, final long nonce1, final Sender sender2, final Object... args) { actions.add( - (pending, prio, ready, sparse, dropped) -> assertExpectedPrioritized(prio, expectedCopy)); + () -> { + lastExpectedPrioritized = expectedForSenders(sender1, nonce1, sender2, args); + assertExpectedPrioritized(prio, lastExpectedPrioritized); + }); return this; } public Scenario expectedPrioritizedForSenders() { - lastExpectedPrioritized = List.of(); - final var expectedCopy = List.copyOf(lastExpectedPrioritized); actions.add( - (pending, prio, ready, sparse, dropped) -> assertExpectedPrioritized(prio, expectedCopy)); + () -> { + lastExpectedPrioritized = List.of(); + assertExpectedPrioritized(prio, lastExpectedPrioritized); + }); return this; } public Scenario expectedReadyForSenders( final Sender sender1, final long nonce1, final Sender sender2, final Object... args) { - lastExpectedReady = expectedForSenders(sender1, nonce1, sender2, args); - final var expectedCopy = List.copyOf(lastExpectedReady); actions.add( - (pending, prio, ready, sparse, dropped) -> assertExpectedReady(ready, expectedCopy)); + () -> { + lastExpectedReady = expectedForSenders(sender1, nonce1, sender2, args); + assertExpectedReady(ready, lastExpectedReady); + }); return this; } public Scenario expectedReadyForSenders() { - lastExpectedReady = List.of(); - final var expectedCopy = List.copyOf(lastExpectedReady); actions.add( - (pending, prio, ready, sparse, dropped) -> assertExpectedReady(ready, expectedCopy)); + () -> { + lastExpectedReady = List.of(); + assertExpectedReady(ready, lastExpectedReady); + }); return this; } public Scenario expectedSparseForSenders( final Sender sender1, final long nonce1, final Sender sender2, final Object... args) { - lastExpectedSparse = expectedForSenders(sender1, nonce1, sender2, args); - final var expectedCopy = List.copyOf(lastExpectedSparse); actions.add( - (pending, prio, ready, sparse, dropped) -> assertExpectedSparse(sparse, expectedCopy)); + () -> { + lastExpectedSparse = expectedForSenders(sender1, nonce1, sender2, args); + assertExpectedSparse(sparse, lastExpectedSparse); + }); return this; } public Scenario expectedSparseForSenders() { - lastExpectedSparse = List.of(); - final var expectedCopy = List.copyOf(lastExpectedSparse); actions.add( - (pending, prio, ready, sparse, dropped) -> assertExpectedSparse(sparse, expectedCopy)); + () -> { + lastExpectedSparse = List.of(); + assertExpectedSparse(sparse, lastExpectedSparse); + }); return this; } public Scenario expectedDroppedForSenders( final Sender sender1, final long nonce1, final Sender sender2, final Object... args) { - lastExpectedDropped = expectedForSenders(sender1, nonce1, sender2, args); - final var expectedCopy = List.copyOf(lastExpectedDropped); actions.add( - (pending, prio, ready, sparse, dropped) -> assertExpectedDropped(dropped, expectedCopy)); + () -> { + lastExpectedDropped = expectedForSenders(sender1, nonce1, sender2, args); + assertExpectedDropped(dropped, lastExpectedDropped); + }); return this; } public Scenario expectedDroppedForSenders() { - lastExpectedDropped = List.of(); - final var expectedCopy = List.copyOf(lastExpectedDropped); actions.add( - (pending, prio, ready, sparse, dropped) -> assertExpectedDropped(dropped, expectedCopy)); + () -> { + lastExpectedDropped = List.of(); + assertExpectedDropped(dropped, lastExpectedDropped); + }); return this; } private void assertExpectedPrioritized( final AbstractPrioritizedTransactions prioLayer, final List expected) { - assertThat(prioLayer.stream()).describedAs("Prioritized").containsExactlyElementsOf(expected); + final var flatOrder = + prioLayer.getByScore().values().stream() + .flatMap(List::stream) + .flatMap(spt -> spt.pendingTransactions().stream()) + .toList(); + assertThat(flatOrder).describedAs("Prioritized").containsExactlyElementsOf(expected); } private void assertExpectedReady( final ReadyTransactions readyLayer, final List expected) { - assertThat(readyLayer.stream()).describedAs("Ready").containsExactlyElementsOf(expected); + assertThat(readyLayer.getBySender()) + .describedAs("Ready") + .flatExtracting(SenderPendingTransactions::pendingTransactions) + .containsExactlyElementsOf(expected); } private void assertExpectedSparse( final SparseTransactions sparseLayer, final List expected) { - // sparse txs are returned from the most recent to the oldest, so reverse it to make writing - // scenarios easier - final var sortedExpected = new ArrayList<>(expected); - Collections.reverse(sortedExpected); - assertThat(sparseLayer.stream()) + assertThat(sparseLayer.getBySender()) .describedAs("Sparse") - .containsExactlyElementsOf(sortedExpected); + .flatExtracting(SenderPendingTransactions::pendingTransactions) + .containsExactlyElementsOf(expected); } private void assertExpectedDropped( @@ -1447,40 +1812,74 @@ private List expectedForSender(final Sender sender, final lo return Arrays.stream(nonce).mapToObj(n -> get(sender, n)).toList(); } + private List droppedForSender(final Sender sender, final long... nonce) { + return Arrays.stream(nonce).mapToObj(n -> droppedTxsBySender.get(sender).get(n)).toList(); + } + public Scenario expectedNextNonceForSenders(final Object... args) { for (int i = 0; i < args.length; i = i + 2) { final Sender sender = (Sender) args[i]; final Integer nullableInt = (Integer) args[i + 1]; final OptionalLong nonce = nullableInt == null ? OptionalLong.empty() : OptionalLong.of(nullableInt); - actions.add( - (pending, prio, ready, sparse, dropped) -> - assertThat(prio.getNextNonceFor(sender.address)).isEqualTo(nonce)); + actions.add(() -> assertThat(prio.getNextNonceFor(sender.address)).isEqualTo(nonce)); } return this; } public Scenario removeForSender(final Sender sender, final long... nonce) { - Arrays.stream(nonce) - .forEach( - n -> { - final var pendingTx = getOrCreate(sender, n); - actions.add( - (pending, prio, ready, sparse, dropped) -> prio.remove(pendingTx, INVALIDATED)); - }); + actions.add( + () -> { + Arrays.stream(nonce) + .forEach( + n -> { + final var maybeLiveTx = getMaybe(sender, n); + final var pendingTx = maybeLiveTx.orElseGet(() -> create(sender, EIP1559, n)); + prio.remove(pendingTx, INVALIDATED); + maybeLiveTx.ifPresent( + liveTx -> { + notificationsChecker.addExpectedDropNotification(liveTx); + liveTxsBySender.get(sender).remove(liveTx.getNonce()); + droppedTxsBySender.get(sender).put(liveTx.getNonce(), liveTx); + }); + }); + handleDropped(); + notificationsChecker.assertExpectedNotifications(); + }); + return this; + } + + public Scenario penalizeForSender(final Sender sender, final long... nonce) { + actions.add( + () -> + Arrays.stream(nonce) + .forEach( + n -> { + final var senderTxs = prio.getAllFor(sender.address); + Arrays.stream(nonce) + .mapToObj( + n2 -> + senderTxs.stream().filter(pt -> pt.getNonce() == n2).findAny()) + .map(Optional::get) + .forEach(prio::penalize); + })); return this; } public Scenario expectedSelectedTransactions(final Object... args) { - List expectedSelected = new ArrayList<>(); - for (int i = 0; i < args.length; i = i + 2) { - final Sender sender = (Sender) args[i]; - final long nonce = (int) args[i + 1]; - expectedSelected.add(get(sender, nonce)); - } actions.add( - (pending, prio, ready, sparse, dropped) -> - assertThat(prio.stream()).containsExactlyElementsOf(expectedSelected)); + () -> { + List expectedSelected = new ArrayList<>(); + for (int i = 0; i < args.length; i = i + 2) { + final Sender sender = (Sender) args[i]; + final long nonce = (int) args[i + 1]; + expectedSelected.add(get(sender, nonce)); + } + + assertThat(prio.getBySender()) + .flatExtracting(SenderPendingTransactions::pendingTransactions) + .containsExactlyElementsOf(expectedSelected); + }); return this; } @@ -1510,5 +1909,68 @@ enum Sender { this.gasFeeMultiplier = gasFeeMultiplier; this.hasPriority = hasPriority; } + + static Sender getByAddress(final Address address) { + return Arrays.stream(values()).filter(s -> s.address.equals(address)).findAny().get(); + } + } + + static class NotificationsChecker { + private final List collectedAddNotifications = + Collections.synchronizedList(new ArrayList<>()); + private final List collectedDropNotifications = + Collections.synchronizedList(new ArrayList<>()); + private final List expectedAddNotifications = new ArrayList<>(); + private final List expectedDropNotifications = new ArrayList<>(); + + void collectAddNotification(final Transaction tx) { + collectedAddNotifications.add(tx); + } + + void collectDropNotification(final Transaction tx) { + collectedDropNotifications.add(tx); + } + + void addExpectedAddNotification(final PendingTransaction tx) { + expectedAddNotifications.add(tx.getTransaction()); + } + + void addExpectedDropNotification(final PendingTransaction tx) { + expectedDropNotifications.add(tx.getTransaction()); + } + + void assertExpectedNotifications() { + assertAddNotifications(expectedAddNotifications); + assertDropNotifications(expectedDropNotifications); + } + + private void assertAddNotifications(final List expectedAddedTxs) { + await() + .untilAsserted( + () -> + assertThat(collectedAddNotifications) + .describedAs("Added notifications") + .containsExactlyInAnyOrderElementsOf(expectedAddedTxs)); + collectedAddNotifications.clear(); + expectedAddNotifications.clear(); + } + + private void assertDropNotifications(final List expectedDroppedTxs) { + await() + .untilAsserted( + () -> + assertThat(collectedDropNotifications) + .describedAs("Dropped notifications") + .containsExactlyInAnyOrderElementsOf(expectedDroppedTxs)); + collectedDropNotifications.clear(); + expectedDropNotifications.clear(); + } + } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReplayTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReplayTest.java index 10595b6fd7a..18ce6f49ed0 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReplayTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/ReplayTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -16,15 +16,18 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; -import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.INVALIDATED; +import static org.hyperledger.besu.ethereum.eth.transactions.layered.RemovalReason.PoolRemovalReason.INVALIDATED; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; @@ -39,6 +42,7 @@ import org.hyperledger.besu.ethereum.rlp.RLPInput; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.metrics.StubMetricsSystem; +import org.hyperledger.besu.testutil.DeterministicEthScheduler; import java.io.BufferedReader; import java.io.IOException; @@ -47,11 +51,13 @@ import java.nio.charset.StandardCharsets; import java.time.Instant; import java.util.Arrays; +import java.util.EnumMap; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.BiFunction; +import java.util.stream.Collectors; import java.util.zip.GZIPInputStream; import com.google.common.base.Splitter; @@ -66,6 +72,7 @@ public class ReplayTest { private static final Logger LOG = LoggerFactory.getLogger(ReplayTest.class); private final StubMetricsSystem metricsSystem = new StubMetricsSystem(); private final TransactionPoolMetrics txPoolMetrics = new TransactionPoolMetrics(metricsSystem); + protected final EthScheduler ethScheduler = new DeterministicEthScheduler(); private final Address senderToLog = Address.fromHexString("0xf7445f4b8a07921bf882175470dc8f7221c53996"); @@ -116,12 +123,13 @@ public void replay() throws IOException { final TransactionPoolConfiguration poolConfig = ImmutableTransactionPoolConfiguration.builder() .prioritySenders(readPrioritySenders(br.readLine())) + .maxPrioritizedTransactionsByType(readMaxPrioritizedByType(br.readLine())) .build(); final AbstractPrioritizedTransactions prioritizedTransactions = createLayers(poolConfig, txPoolMetrics, baseFeeMarket); final LayeredPendingTransactions pendingTransactions = - new LayeredPendingTransactions(poolConfig, prioritizedTransactions); + new LayeredPendingTransactions(poolConfig, prioritizedTransactions, ethScheduler); br.lines() .forEach( line -> { @@ -160,6 +168,17 @@ public void replay() throws IOException { } } + private Map readMaxPrioritizedByType(final String line) { + return Arrays.stream(line.split(",")) + .map(e -> e.split("=")) + .collect( + Collectors.toMap( + a -> TransactionType.valueOf(a[0]), + a -> Integer.parseInt(a[1]), + (a, b) -> a, + () -> new EnumMap<>(TransactionType.class))); + } + private List
readPrioritySenders(final String line) { return Arrays.stream(line.split(",")).map(Address::fromHexString).toList(); } @@ -189,20 +208,31 @@ private BaseFeePrioritizedTransactions createLayers( (tx1, tx2) -> transactionReplacementTester(poolConfig, tx1, tx2); final SparseTransactions sparseTransactions = new SparseTransactions( - poolConfig, evictCollector, txPoolMetrics, txReplacementTester, new BlobCache()); + poolConfig, + ethScheduler, + evictCollector, + txPoolMetrics, + txReplacementTester, + new BlobCache()); final ReadyTransactions readyTransactions = new ReadyTransactions( - poolConfig, sparseTransactions, txPoolMetrics, txReplacementTester, new BlobCache()); - + poolConfig, + ethScheduler, + sparseTransactions, + txPoolMetrics, + txReplacementTester, + new BlobCache()); return new BaseFeePrioritizedTransactions( poolConfig, () -> currBlockHeader, + ethScheduler, readyTransactions, txPoolMetrics, txReplacementTester, baseFeeMarket, - new BlobCache()); + new BlobCache(), + MiningParameters.newDefault()); } // ToDo: commented since not always working, needs fix @@ -303,7 +333,8 @@ private boolean transactionReplacementTester( final PendingTransaction pt1, final PendingTransaction pt2) { final TransactionPoolReplacementHandler transactionReplacementHandler = - new TransactionPoolReplacementHandler(poolConfig.getPriceBump()); + new TransactionPoolReplacementHandler( + poolConfig.getPriceBump(), poolConfig.getBlobPriceBump()); return transactionReplacementHandler.shouldReplace(pt1, pt2, currBlockHeader); } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractPendingTransactionsTestBase.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractPendingTransactionsTestBase.java index 9f85e583186..89e278318ea 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractPendingTransactionsTestBase.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractPendingTransactionsTestBase.java @@ -1,5 +1,5 @@ /* - * Copyright Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/BaseFeePendingTransactionsTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/BaseFeePendingTransactionsTest.java index 41bf8763811..413a778d5b5 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/BaseFeePendingTransactionsTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/BaseFeePendingTransactionsTest.java @@ -1,5 +1,5 @@ /* - * Copyright Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/GasPricePendingTransactionsTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/GasPricePendingTransactionsTest.java index 569e8102a91..0e7f413cdc1 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/GasPricePendingTransactionsTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/GasPricePendingTransactionsTest.java @@ -1,5 +1,5 @@ /* - * Copyright Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/LegacyTransactionPoolBaseFeeTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/LegacyTransactionPoolBaseFeeTest.java index 688cc23d32e..15754abbdeb 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/LegacyTransactionPoolBaseFeeTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/LegacyTransactionPoolBaseFeeTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/LegacyTransactionPoolGasPriceTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/LegacyTransactionPoolGasPriceTest.java index d6f644bb7e1..27cac0f352c 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/LegacyTransactionPoolGasPriceTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/LegacyTransactionPoolGasPriceTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/eth/src/test/resources/txpool-test-genesis.json b/ethereum/eth/src/test/resources/txpool-test-genesis.json new file mode 100644 index 00000000000..d6a19fe7052 --- /dev/null +++ b/ethereum/eth/src/test/resources/txpool-test-genesis.json @@ -0,0 +1,15 @@ +{ + "config": { + "chainId": 1, + "cancunTime": 0, + "terminalTotalDifficulty": 0 + }, + "nonce": "0x42", + "timestamp": "0x0", + "extraData": "", + "gasLimit": "0x1fffffffffffff", + "difficulty": "0x10000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "baseFeePerGas": "0xA" +} diff --git a/ethereum/ethstats/build.gradle b/ethereum/ethstats/build.gradle index 2097f0f1bb6..8f10b73b36d 100644 --- a/ethereum/ethstats/build.gradle +++ b/ethereum/ethstats/build.gradle @@ -22,7 +22,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } diff --git a/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/EthStatsService.java b/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/EthStatsService.java index 0fa93e1b68c..b96a48dc5e6 100644 --- a/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/EthStatsService.java +++ b/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/EthStatsService.java @@ -80,8 +80,8 @@ import org.slf4j.LoggerFactory; /** - * This class describes the behaviour of the EthStats service. This class is used to report pending - * transactions, blocks, and several node-related information to a netstats server. + * This class describes the behaviour of the EthStatsService. This class is used to report pending + * transactions, blocks, and several node-related information to an ethstats server. */ public class EthStatsService { @@ -112,18 +112,18 @@ public class EthStatsService { private long pingTimestamp; /** - * Instantiates a new EthStats service. + * Instantiates a new EthStatsService. * - * @param ethStatsConnectOptions the netstats url + * @param ethStatsConnectOptions the ethstats options * @param blockchainQueries the blockchain queries * @param protocolManager the protocol manager * @param transactionPool the transaction pool * @param miningCoordinator the mining coordinator - * @param syncState the sync state - * @param vertx the vertx + * @param syncState the SyncState + * @param vertx the vertx instance * @param clientVersion the client version * @param genesisConfigOptions the genesis config options - * @param p2PNetwork the p 2 p network + * @param p2PNetwork the p2p network */ public EthStatsService( final EthStatsConnectOptions ethStatsConnectOptions, diff --git a/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/authentication/AuthenticationData.java b/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/authentication/AuthenticationData.java index 2e222f08b0c..cb68ebc7d13 100644 --- a/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/authentication/AuthenticationData.java +++ b/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/authentication/AuthenticationData.java @@ -19,18 +19,37 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.immutables.value.Value; +/** + * This interface represents the authentication data. It provides methods to get the id, info, and + * secret of the authentication data. + */ @Value.Immutable @JsonSerialize(as = ImmutableAuthenticationData.class) @JsonDeserialize(as = ImmutableAuthenticationData.class) @Value.Style(allParameters = true) public interface AuthenticationData { + /** + * Gets the id of the authentication data. + * + * @return the id of the authentication data. + */ @JsonProperty("id") String getId(); + /** + * Gets the info of the authentication data. + * + * @return the info of the authentication data. + */ @JsonProperty("info") NodeInfo getInfo(); + /** + * Gets the secret of the authentication data. + * + * @return the secret of the authentication data. + */ @JsonProperty("secret") String getSecret(); } diff --git a/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/authentication/NodeInfo.java b/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/authentication/NodeInfo.java index 19c34db4b33..b54fd9fa395 100644 --- a/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/authentication/NodeInfo.java +++ b/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/authentication/NodeInfo.java @@ -19,42 +19,102 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.immutables.value.Value; +/** + * This interface represents the information of a node. It provides methods to get the name, node, + * port, network, protocol, api, os, os version, client, update history capability, and contact of + * the node. + */ @Value.Immutable @JsonSerialize(as = ImmutableNodeInfo.class) @JsonDeserialize(as = ImmutableNodeInfo.class) @Value.Style(allParameters = true) public interface NodeInfo { + /** + * Gets the name of the node. + * + * @return the name of the node. + */ @JsonProperty("name") String getName(); + /** + * Gets the node. + * + * @return the node. + */ @JsonProperty("node") String getNode(); + /** + * Gets the port of the node. + * + * @return the port of the node. + */ @JsonProperty("port") String getPort(); + /** + * Gets the network of the node. + * + * @return the network of the node. + */ @JsonProperty("net") String getNetwork(); + /** + * Gets the protocol of the node. + * + * @return the protocol of the node. + */ @JsonProperty("protocol") String getProtocol(); + /** + * Gets the api of the node. + * + * @return the api of the node. + */ @JsonProperty("api") String getApi(); + /** + * Gets the operating system of the node. + * + * @return the operating system of the node. + */ @JsonProperty("os") String getOs(); + /** + * Gets the operating system version of the node. + * + * @return the operating system version of the node. + */ @JsonProperty("os_v") String getOsVer(); + /** + * Gets the client of the node. + * + * @return the client of the node. + */ @JsonProperty("client") String getClient(); + /** + * Gets the update history capability of the node. + * + * @return the update history capability of the node. + */ @JsonProperty("canUpdateHistory") Boolean getCanUpdateHistory(); + /** + * Gets the contact of the node. + * + * @return the contact of the node. + */ @JsonProperty("contact") String getContact(); } diff --git a/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/report/BlockReport.java b/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/report/BlockReport.java index ccedbb03950..c4816d6486a 100644 --- a/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/report/BlockReport.java +++ b/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/report/BlockReport.java @@ -21,15 +21,29 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.immutables.value.Value; +/** + * This interface represents a block report. It provides methods to get the id and block result of + * the block report. + */ @Value.Immutable @Value.Style(allParameters = true) @JsonSerialize(as = ImmutableBlockReport.class) @JsonDeserialize(as = ImmutableBlockReport.class) public interface BlockReport { + /** + * Gets the id of the block report. + * + * @return the id of the block report. + */ @JsonProperty("id") String getId(); + /** + * Gets the block result of the block report. + * + * @return the block result of the block report. + */ @JsonProperty("block") BlockResult getBlock(); } diff --git a/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/report/HistoryReport.java b/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/report/HistoryReport.java index 3aafa6200b8..5df2c848cab 100644 --- a/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/report/HistoryReport.java +++ b/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/report/HistoryReport.java @@ -23,15 +23,29 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.immutables.value.Value; +/** + * This interface represents a history report. It provides methods to get the id and history of the + * history report. + */ @Value.Immutable @Value.Style(allParameters = true) @JsonSerialize(as = ImmutableHistoryReport.class) @JsonDeserialize(as = ImmutableHistoryReport.class) public interface HistoryReport { + /** + * Gets the id of the history report. + * + * @return the id of the history report. + */ @JsonProperty(value = "id") String getId(); + /** + * Gets the block results of the history report. + * + * @return the list of block results of the history report. + */ @JsonProperty("history") List getHistory(); } diff --git a/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/report/LatencyReport.java b/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/report/LatencyReport.java index 58201332b9d..5c588427243 100644 --- a/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/report/LatencyReport.java +++ b/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/report/LatencyReport.java @@ -19,15 +19,29 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.immutables.value.Value; +/** + * This interface represents a latency report. It provides methods to get the id and latency of the + * latency report. + */ @Value.Immutable @Value.Style(allParameters = true) @JsonSerialize(as = ImmutableLatencyReport.class) @JsonDeserialize(as = ImmutableLatencyReport.class) public interface LatencyReport { + /** + * Gets the id of the latency report. + * + * @return the id of the latency report. + */ @JsonProperty("id") String getId(); + /** + * Gets the latency of the latency report. + * + * @return the latency of the latency report. + */ @JsonProperty("latency") String getLatency(); } diff --git a/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/report/NodeStatsReport.java b/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/report/NodeStatsReport.java index 67bc977a198..af95de720aa 100644 --- a/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/report/NodeStatsReport.java +++ b/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/report/NodeStatsReport.java @@ -19,42 +19,92 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.immutables.value.Value; +/** + * This interface represents a node stats report. It provides methods to get the id and stats of the + * node stats report. + */ @Value.Immutable @Value.Style(deepImmutablesDetection = true, depluralize = true) @JsonSerialize(as = ImmutableNodeStatsReport.class) @JsonDeserialize(as = ImmutableNodeStatsReport.class) public interface NodeStatsReport { + /** + * Gets the id of the node stats report. + * + * @return the id of the node stats report. + */ @JsonProperty("id") String getId(); + /** + * Gets the stats of the node stats report. + * + * @return the stats of the node stats report. + */ @JsonProperty("stats") NStats getStats(); + /** This interface represents the stats of a node. */ @Value.Immutable @Value.Style(allParameters = true) @JsonSerialize(as = ImmutableNStats.class) @JsonDeserialize(as = ImmutableNStats.class) interface NStats { + /** + * Checks if the node is active. + * + * @return true if the node is active, false otherwise. + */ @JsonProperty("active") boolean isActive(); + /** + * Checks if the node is mining. + * + * @return true if the node is mining, false otherwise. + */ @JsonProperty("mining") boolean isMining(); + /** + * Gets the hashrate of the node. + * + * @return the hashrate of the node. + */ @JsonProperty("hashrate") long getHashrate(); + /** + * Gets the number of peers of the node. + * + * @return the number of peers of the node. + */ @JsonProperty("peers") int getPeers(); + /** + * Gets the gas price of the node. + * + * @return the gas price of the node. + */ @JsonProperty("gasPrice") long getGasPrice(); + /** + * Checks if the node is syncing. + * + * @return true if the node is syncing, false otherwise. + */ @JsonProperty("syncing") boolean isSyncing(); + /** + * Gets the uptime of the node. + * + * @return the uptime of the node. + */ @JsonProperty("uptime") int getUpTime(); } diff --git a/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/report/PendingTransactionsReport.java b/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/report/PendingTransactionsReport.java index 5c168e29071..dbb19ac34b3 100644 --- a/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/report/PendingTransactionsReport.java +++ b/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/report/PendingTransactionsReport.java @@ -19,24 +19,44 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.immutables.value.Value; +/** + * This interface represents a pending transactions report. It provides methods to get the id and + * stats of the pending transactions report. + */ @Value.Immutable @Value.Style(deepImmutablesDetection = true, depluralize = true) @JsonSerialize(as = ImmutablePendingTransactionsReport.class) @JsonDeserialize(as = ImmutablePendingTransactionsReport.class) public interface PendingTransactionsReport { + /** + * Gets the id of the pending transactions report. + * + * @return the id of the pending transactions report. + */ @JsonProperty("id") String getId(); + /** + * Gets the stats of the pending transactions report. + * + * @return the stats of the pending transactions report. + */ @JsonProperty("stats") PStats getStats(); + /** This interface represents the stats of a pending transactions report. */ @Value.Immutable @Value.Style(allParameters = true) @JsonSerialize(as = ImmutablePStats.class) @JsonDeserialize(as = ImmutablePStats.class) interface PStats { + /** + * Gets the number of pending transactions. + * + * @return the number of pending transactions. + */ @JsonProperty("pending") int getPending(); } diff --git a/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/report/PingReport.java b/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/report/PingReport.java index 945f35530b2..d7470a65cf9 100644 --- a/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/report/PingReport.java +++ b/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/report/PingReport.java @@ -19,15 +19,29 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.immutables.value.Value; +/** + * This interface represents a ping report. It provides methods to get the id and current time of + * the ping report. + */ @Value.Immutable @Value.Style(allParameters = true) @JsonSerialize(as = ImmutablePingReport.class) @JsonDeserialize(as = ImmutablePingReport.class) public interface PingReport { + /** + * Gets the id of the ping report. + * + * @return the id of the ping report. + */ @JsonProperty("id") String getId(); + /** + * Gets the current time of the ping report. + * + * @return the current time of the ping report. + */ @JsonProperty("clientTime") String getCurrentTime(); } diff --git a/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/request/EthStatsRequest.java b/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/request/EthStatsRequest.java index dbd8b9ccf66..8d2acf3a056 100644 --- a/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/request/EthStatsRequest.java +++ b/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/request/EthStatsRequest.java @@ -23,10 +23,16 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +/** + * This class represents an Ethereum statistics request. It provides methods to get the type of the + * request and the parameters associated with it. + */ public class EthStatsRequest { + /** The constant MAPPER. */ public static final ObjectMapper MAPPER = new ObjectMapper(); + /** The constant EMIT_FIELD. */ public static final String EMIT_FIELD = "emit"; @JsonProperty(EMIT_FIELD) @@ -34,11 +40,22 @@ public class EthStatsRequest { private EthStatsRequest() {} + /** + * Constructs a new EthStatsRequest with the given type and parameters. + * + * @param type the type of the request + * @param parameters the parameters of the request + */ public EthStatsRequest(final Type type, final Object... parameters) { this.emit = Stream.concat(Stream.of(type.value), Stream.of(parameters)).collect(Collectors.toList()); } + /** + * Gets the type of the request. + * + * @return the type of the request + */ @JsonIgnore public Type getType() { return getEmit().stream() @@ -49,14 +66,31 @@ public Type getType() { .orElse(Type.UNKNOWN); } + /** + * Gets the parameters of the request. + * + * @return the parameters of the request + */ public List getEmit() { return emit; } + /** + * Generates a command string from the request. + * + * @return the command string + * @throws JsonProcessingException if there is an error processing the JSON + */ public String generateCommand() throws JsonProcessingException { return MAPPER.writeValueAsString(this); } + /** + * Creates an EthStatsRequest from a response string. + * + * @param value the response string + * @return the EthStatsRequest + */ public static EthStatsRequest fromResponse(final String value) { try { return MAPPER.readValue(value, EthStatsRequest.class); @@ -65,16 +99,36 @@ public static EthStatsRequest fromResponse(final String value) { } } + /** The enum Type represents the type of the request. */ public enum Type { + /** Represents the 'hello' type of the request. */ HELLO("hello"), + + /** Represents the 'ready' type of the request. */ READY("ready"), + + /** Represents the 'node-ping' type of the request. */ NODE_PING("node-ping"), + + /** Represents the 'node-pong' type of the request. */ NODE_PONG("node-pong"), + + /** Represents the 'latency' type of the request. */ LATENCY("latency"), + + /** Represents the 'block' type of the request. */ BLOCK("block"), + + /** Represents the 'history' type of the request. */ HISTORY("history"), + + /** Represents the 'pending' type of the request. */ PENDING("pending"), + + /** Represents the 'stats' type of the request. */ STATS("stats"), + + /** Represents an unknown type of the request. */ UNKNOWN(""); String value; @@ -83,10 +137,21 @@ public enum Type { this.value = value; } + /** + * Gets the value of the type. + * + * @return the value of the type + */ public String getValue() { return value; } + /** + * Gets the type from a value string. + * + * @param value the value string + * @return the type + */ public static Type fromValue(final String value) { for (Type type : values()) { if (type.value.equalsIgnoreCase(value)) { diff --git a/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/util/EthStatsConnectOptions.java b/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/util/EthStatsConnectOptions.java index 7f020adf452..1db74735239 100644 --- a/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/util/EthStatsConnectOptions.java +++ b/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/util/EthStatsConnectOptions.java @@ -23,24 +23,71 @@ import org.immutables.value.Value; import org.slf4j.LoggerFactory; +/** + * This interface represents the connection options for Ethereum statistics. It provides methods to + * get the scheme, node name, secret, host, port, contact, and CA certificate. + */ @Value.Immutable public interface EthStatsConnectOptions { + /** + * Gets the scheme of the connection. + * + * @return the scheme of the connection. + */ @Nullable String getScheme(); + /** + * Gets the node name of the connection. + * + * @return the node name of the connection. + */ String getNodeName(); + /** + * Gets the secret of the connection. + * + * @return the secret of the connection. + */ String getSecret(); + /** + * Gets the host of the connection. + * + * @return the host of the connection. + */ String getHost(); + /** + * Gets the port of the connection. + * + * @return the port of the connection. + */ Integer getPort(); + /** + * Gets the contact of the connection. + * + * @return the contact of the connection. + */ String getContact(); + /** + * Gets the CA certificate of the connection. + * + * @return the CA certificate of the connection. + */ @Nullable Path getCaCert(); + /** + * Creates an EthStatsConnectOptions from the given parameters. + * + * @param url the url of the connection + * @param contact the contact of the connection + * @param caCert the CA certificate of the connection + * @return the EthStatsConnectOptions + */ static EthStatsConnectOptions fromParams( final String url, final String contact, final Path caCert) { try { diff --git a/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/util/PrimusHeartBeatsHelper.java b/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/util/PrimusHeartBeatsHelper.java index e96019ef818..03b6634af1d 100644 --- a/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/util/PrimusHeartBeatsHelper.java +++ b/ethereum/ethstats/src/main/java/org/hyperledger/besu/ethstats/util/PrimusHeartBeatsHelper.java @@ -18,14 +18,29 @@ import io.vertx.core.http.WebSocket; +/** This class provides helper methods for handling Primus heartbeats. */ public final class PrimusHeartBeatsHelper { + /** The constant PRIMUS_PING_REGEX. */ public static final Pattern PRIMUS_PING_REGEX = Pattern.compile("primus::ping::([\\d]+)"); + private PrimusHeartBeatsHelper() {} + + /** + * Checks if the given request is a heartbeat request. + * + * @param request the request to check + * @return true if the request is a heartbeat request, false otherwise + */ public static boolean isHeartBeatsRequest(final String request) { return PRIMUS_PING_REGEX.matcher(request).find(); } + /** + * Sends a heartbeat response through the given WebSocket. + * + * @param webSocket the WebSocket to send the response through + */ public static void sendHeartBeatsResponse(final WebSocket webSocket) { if (webSocket != null) { webSocket.writeTextMessage(String.format("\"primus::pong::%d\"", System.currentTimeMillis())); diff --git a/ethereum/ethstats/src/test/java/org/hyperledger/besu/ethstats/util/EthStatsConnectOptionsTest.java b/ethereum/ethstats/src/test/java/org/hyperledger/besu/ethstats/util/EthStatsConnectOptionsTest.java index f3227a62e76..01c58bbdddf 100644 --- a/ethereum/ethstats/src/test/java/org/hyperledger/besu/ethstats/util/EthStatsConnectOptionsTest.java +++ b/ethereum/ethstats/src/test/java/org/hyperledger/besu/ethstats/util/EthStatsConnectOptionsTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/evmtool/README.md b/ethereum/evmtool/README.md index a1a703f9132..50db1066f38 100644 --- a/ethereum/evmtool/README.md +++ b/ethereum/evmtool/README.md @@ -8,13 +8,9 @@ and enclosing data structures. Using EVM Tool in execution-specification-tests ----------------------------------------------- -To use EVM Tool in Execution Spec tests it is recommended that you use -the GraalVM build, as the framework incurs significant startup penalties -for each invocation when run via the Java runtime. - ### Building Execution Tests on macOS -Current as of 24 Jun 2023. +Current as of 26 Jun 2024. MacOS users will typically encounter two problems,one relating to the version of Python used and one relating to zsh. @@ -35,7 +31,7 @@ install python packages needs to escape the brackets pip install -e .\[docs,lint,test\] ``` -An all-in-one script, including homebrew, would look like +An all-in-one script, using homebrew, would look like ```zsh brew install ethereum solidity @@ -43,85 +39,56 @@ git clone https://github.com/ethereum/execution-spec-tests cd execution-spec-tests python3 -m venv ./venv/ source ./venv/bin/activate -pip install -e .[docs,lint,test] +pip install -e .\[docs,lint,test\] ``` -### Building EvmTool with GraalVM on macOS +### Building EvmTool on macOS -First you need a GraalVM JDK installed, if not already installed. +First you need a Java 21+ JDK installed, if not already installed. It is recommended you install [SDKMAN](https://sdkman.io/install) to -manage the graalVM install, homebrew has issues with native attributes -and code signing. +manage the jvm install. ```zsh -sdk install java 22.3.r17-grl -sdk use java 22.3.r17-grl +sdk install java 21.0.3-tem +sdk use java 21.0.3-tem ``` -You can also manually install GraalVM from -the [GraalVM website](https://www.graalvm.org/downloads).. - -Once GraalVM is installed you use the `nativeCompile` target. +Once a JVM is installed you use the gradle target: ```zsh -./gradlew nativeCompile +./gradlew installDist -x test ``` The resulting binary -is `./ethereum/evmtool/build/native/nativeCompile/evmtool` +is `build/install/besu/bin/evmtool` If the testing repository and besu are installed in the same parent directory, the command to run the execution tests is ```zsh -fill -v tests --evm-bin ../besu/ethereum/evmtool/build/install/evmtool/bin/evm +fill -v tests --evm-bin ../besu/build/install/besu/bin/evmtool ``` Assuming homebrew and SDKMan are both installed, the complete script is ```zsh -sdk install java 22.3.r17-grl -sdk use java 22.3.r17-grl +sdk install java 21.0.3-tem +sdk use java 21.0.3-tem git clone https://github.com/hyperledger/besu cd besu -./gradlew nativeCompile +./gradlew installDist -x test cd .. brew install ethereum solidity +solc-select install latest +solc-select use latest git clone https://github.com/ethereum/execution-spec-tests cd execution-spec-tests python3 -m venv ./venv/ source ./venv/bin/activate -pip install -e .[docs,lint,test] +pip install -e .\[docs,lint,test\] -fill -v tests --evm-bin ../besu/ethereum/evmtool/build/install/evmtool/bin/evm +fill -v tests --evm-bin ../besu/build/install/besu/bin/evmtool ``` -If you don't want to use the GraalVM tool the binary that is compatible -is generated by the `ethereum:evmtool:installdist` target and is located -at `./ethereum/evmtool/build/install/evmtool/bin/evm` - -Why not GraalVM for everything? -------------------------------- - -Using GraalVM in execution-spec-tests results in over 20x performance -increase in execution. It will be faster to build GraalVM from scratch -and run the execution-spec-tests than to run just the Java version. - -It is demonstrably faster to run the Java version for a node. -All the test execution gains are the result of reduced startup -penalties. Larger benchmarks will show that performance intensive EVM -code will be slower in GraalVM than the Java version due to the adaptive -compiler. - -For contracts that execute 30 million gas in small operations it is -often faster to run the Java EVMTool than the GraalVM EVMTool, including -startup penalty. The execution tests focus on smaller VM tests that -demonstrate specification conformance. - -We would also need to reconsider some library choices. GraalVM does not -work with Log4J, and we would have to ban that library across Besu and -all dependents. Libraries such as Netty also have some problematic entry -points that interact poorly with how SLF4J needs to initialize. - diff --git a/ethereum/evmtool/build.gradle b/ethereum/evmtool/build.gradle index 9bc302f60c7..c4c83883b57 100644 --- a/ethereum/evmtool/build.gradle +++ b/ethereum/evmtool/build.gradle @@ -15,7 +15,7 @@ */ plugins { - id 'org.graalvm.buildtools.native' version '0.9.17' + id 'org.graalvm.buildtools.native' version '0.10.2' } apply plugin: 'java-library' @@ -29,7 +29,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } @@ -66,22 +67,12 @@ dependencies { testRuntimeOnly 'org.junit.vintage:junit-vintage-engine' - // No logging in grallvm EvmTool + // No logging in graalvm EvmTool nativeImageClasspath 'org.slf4j:slf4j-nop' } mainClassName = 'org.hyperledger.besu.evmtool.EvmTool' -startScripts { - applicationName = 'evm' - defaultJvmOpts = [ - "-Dsecp256k1.randomize=false" - ] - doLast { - unixScript.text = unixScript.text.replace('BESU_HOME', '\$APP_HOME') - windowsScript.text = windowsScript.text.replace('BESU_HOME', '%~dp0..') - } -} // rename the top level dir from besu- to besu and this makes it really // simple for use in docker @@ -120,8 +111,9 @@ tasks.register('distDocker', Exec) { } } + def gitDetails = getGitCommitDetails(10) executable "sh" - args "-c", "docker build --build-arg BUILD_DATE=${buildTime()} --build-arg VERSION=${dockerBuildVersion} --build-arg VCS_REF=${getCheckedOutGitCommitHash()} -t ${image} ." + args "-c", "docker build --build-arg BUILD_DATE=${buildTime()} --build-arg VERSION=${dockerBuildVersion} --build-arg VCS_REF=${gitDetails.hash} -t ${image} ." } tasks.register('dockerUpload', Exec) { @@ -138,7 +130,7 @@ tasks.register('dockerUpload', Exec) { additionalTags.add('develop') } - if (!(dockerBuildVersion ==~ /.*-SNAPSHOT/)) { + if (!(dockerBuildVersion ==~ /.*-SNAPSHOT/ || dockerBuildVersion ==~/.*develop.*/)) { additionalTags.add('latest') additionalTags.add(dockerBuildVersion.split(/\./)[0..1].join('.')) } @@ -147,34 +139,3 @@ tasks.register('dockerUpload', Exec) { executable "sh" args "-c", cmd } - -graalvmNative { - binaries { - main { - sharedLibrary = false - buildArgs.addAll( - "-H:ReflectionConfigurationFiles=${projectDir}/src/main/graal/reflection-config.json", - "-H:AdditionalSecurityProviders=org.bouncycastle.jce.provider.BouncyCastleProvider", - '-H:+TraceSecurityServices' - ) - - - // Netty drags in older versions of bouncy castle, exclude it so there are no conflicts - excludeConfig.put("io.netty:netty-buffer", [".*"]) - } - } -} - - -configurations.nativeImageClasspath { - // netty statically allocates some problematic classes - exclude group: 'io.netty', module: 'netty-buffer' - exclude group: 'io.netty', module: 'netty-common' - exclude group: 'io.netty', module: 'netty-transport' - - // keep log4j from sneaking in. GraalVM has an aleric reaction if it sees even one class - exclude group: 'org.slf4j', module: 'log4j-over-slf4j:1.7.36' - exclude group: "log4j", module: "log4j" - exclude group: "org.apache.logging.log4j" - exclude group: 'org.bouncycastle', module: 'bcprov-jdk18on' -} diff --git a/ethereum/evmtool/src/main/docker/Dockerfile b/ethereum/evmtool/src/main/docker/Dockerfile index f487d56766b..b16407be293 100644 --- a/ethereum/evmtool/src/main/docker/Dockerfile +++ b/ethereum/evmtool/src/main/docker/Dockerfile @@ -1,14 +1,22 @@ FROM ubuntu:22.04 ARG VERSION="dev" +ENV NO_PROXY_CACHE="-o Acquire::BrokenProxy=true -o Acquire::http::No-Cache=true -o Acquire::http::Pipeline-Depth=0" -RUN apt-get update && \ - apt-get install --no-install-recommends -q --assume-yes ca-certificates-java=20190909* && \ - apt-get install --no-install-recommends -q --assume-yes openjdk-17-jre-headless=17* && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* && \ - adduser --disabled-password --gecos "" --home /opt/besu besu && \ - chown besu:besu /opt/besu +# Update and install dependencies without using any cache +RUN apt-get update $NO_PROXY_CACHE && \ + # $NO_PROXY_CACHE must not be used here or otherwise will trigger a hadolint error + apt-get -o Acquire::BrokenProxy=true -o Acquire::http::No-Cache=true -o Acquire::http::Pipeline-Depth=0 \ + --no-install-recommends -q --assume-yes install ca-certificates-java=20190909* && \ + apt-get -o Acquire::BrokenProxy=true -o Acquire::http::No-Cache=true -o Acquire::http::Pipeline-Depth=0 \ + --no-install-recommends -q --assume-yes install openjdk-21-jre-headless=21* && \ + # Clean apt cache \ + apt-get clean && \ + rm -rf /var/cache/apt/archives/* /var/cache/apt/archives/partial/* && \ + rm -rf /var/lib/apt/lists/* && \ + # Creating a user for besu + adduser --disabled-password --gecos "" --home /opt/besu besu && \ + chown besu:besu /opt/besu USER besu WORKDIR /opt/besu-evmtool @@ -16,7 +24,7 @@ WORKDIR /opt/besu-evmtool COPY --chown=besu:besu besu-evmtool /opt/besu-evmtool/ ENV PATH="/opt/besu-evmtool/bin:${PATH}" -ENTRYPOINT ["evm"] +ENTRYPOINT ["evmtool"] # Build-time metadata as defined at http://label-schema.org ARG BUILD_DATE diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/B11rSubCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/B11rSubCommand.java index 6c65fc2804e..0bb7949754f 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/B11rSubCommand.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/B11rSubCommand.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.evmtool; import static org.hyperledger.besu.evmtool.B11rSubCommand.COMMAND_ALIAS; @@ -53,10 +51,15 @@ import picocli.CommandLine.Parameters; import picocli.CommandLine.ParentCommand; +/** + * This class implements the Runnable interface and represents the B11rSubCommand. It is responsible + * for handling the block builder subcommand in the EVM tool. It provides methods to read headers, + * move fields, and run the command. + */ @Command( name = COMMAND_NAME, aliases = {COMMAND_ALIAS}, - description = "Execute an Ethereum State Test.", + description = "Block Builder subcommand.", mixinStandardHelpOptions = true, versionProvider = VersionProvider.class) public class B11rSubCommand implements Runnable { @@ -140,12 +143,18 @@ public void consumeParameters( } } + /** Default constructor for the B11rSubCommand class. This is required by PicoCLI. */ @SuppressWarnings("unused") public B11rSubCommand() { // PicoCLI requires this parentCommand = null; } + /** + * Constructs a new B11rSubCommand with the given parent command. + * + * @param parentCommand the parent command of this subcommand + */ @SuppressWarnings("unused") public B11rSubCommand(final EvmToolCommand parentCommand) { // PicoCLI requires this too diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/BenchmarkSubCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/BenchmarkSubCommand.java index 8078bd6ba1b..829bf2d58e7 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/BenchmarkSubCommand.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/BenchmarkSubCommand.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -18,7 +18,6 @@ import static picocli.CommandLine.ScopeType.INHERIT; import org.hyperledger.besu.BesuInfo; -import org.hyperledger.besu.evm.EvmSpecVersion; import org.hyperledger.besu.evmtool.benchmarks.AltBN128Benchmark; import org.hyperledger.besu.evmtool.benchmarks.BenchmarkExecutor; import org.hyperledger.besu.evmtool.benchmarks.ECRecoverBenchmark; @@ -34,13 +33,22 @@ import picocli.CommandLine.Parameters; import picocli.CommandLine.ParentCommand; +/** + * This class represents the BenchmarkSubCommand. It is responsible for executing an Ethereum State + * Test. + */ @CommandLine.Command( name = COMMAND_NAME, description = "Execute an Ethereum State Test.", mixinStandardHelpOptions = true, versionProvider = VersionProvider.class) public class BenchmarkSubCommand implements Runnable { + /** + * The command name for the BenchmarkSubCommand. This constant is used as the name attribute in + * the {@code CommandLine.Command} annotation. + */ public static final String COMMAND_NAME = "benchmark"; + private final PrintStream output; enum Benchmark { @@ -64,22 +72,22 @@ enum Benchmark { negatable = true) Boolean nativeCode; - @Option( - names = {"--fork"}, - paramLabel = "", - description = "Fork to evaluate, when it impacts gas costing.") - String fork = EvmSpecVersion.defaultVersion().getName(); - @Parameters(description = "One or more of ${COMPLETION-CANDIDATES}.") EnumSet benchmarks = EnumSet.noneOf(Benchmark.class); @ParentCommand EvmToolCommand parentCommand; + /** Default constructor for the BenchmarkSubCommand class. This is required by PicoCLI. */ public BenchmarkSubCommand() { // PicoCLI requires this this(System.out); } + /** + * Constructs a new BenchmarkSubCommand with the given output stream. + * + * @param output the output stream to be used + */ public BenchmarkSubCommand(final PrintStream output) { this.output = output; } @@ -91,7 +99,7 @@ public void run() { var benchmarksToRun = benchmarks.isEmpty() ? EnumSet.allOf(Benchmark.class) : benchmarks; for (var benchmark : benchmarksToRun) { System.out.println("Benchmarks for " + benchmark); - benchmark.benchmarkExecutor.runBenchmark(output, nativeCode, fork); + benchmark.benchmarkExecutor.runBenchmark(output, nativeCode, parentCommand.getFork()); } } } diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/BlockchainModule.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/BlockchainModule.java index 0109d6c819a..7b463fe08e3 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/BlockchainModule.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/BlockchainModule.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evmtool; @@ -23,11 +22,11 @@ import org.hyperledger.besu.ethereum.chain.GenesisState; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.MutableWorldState; -import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStatePreimageKeyValueStorage; -import org.hyperledger.besu.ethereum.worldstate.DefaultMutableWorldState; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.forest.worldview.ForestMutableWorldState; import org.hyperledger.besu.ethereum.worldstate.WorldStatePreimageStorage; -import org.hyperledger.besu.ethereum.worldstate.WorldStateStorage; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.hyperledger.besu.plugin.services.MetricsSystem; @@ -40,10 +39,18 @@ import dagger.Provides; import org.apache.tuweni.bytes.Bytes32; +/** + * This class is a Dagger module that provides dependencies related to the blockchain. It includes + * the GenesisFileModule and DataStoreModule for providing the genesis block and data store + * respectively. The class is annotated with {@code @Module} to indicate that it is a Dagger module. + */ @SuppressWarnings("WeakerAccess") @Module(includes = {GenesisFileModule.class, DataStoreModule.class}) public class BlockchainModule { + /** Default constructor for the BlockchainModule class. */ + public BlockchainModule() {} + @Singleton @Provides Blockchain provideBlockchain( @@ -57,28 +64,33 @@ Blockchain provideBlockchain( @Singleton MutableWorldState getMutableWorldState( @Named("StateRoot") final Bytes32 stateRoot, - final WorldStateStorage worldStateStorage, + final WorldStateStorageCoordinator worldStateStorageCoordinator, final WorldStatePreimageStorage worldStatePreimageStorage, final GenesisState genesisState, @Named("KeyValueStorageName") final String keyValueStorageName, final EvmConfiguration evmConfiguration) { if ("memory".equals(keyValueStorageName)) { final MutableWorldState mutableWorldState = - new DefaultMutableWorldState( - worldStateStorage, worldStatePreimageStorage, evmConfiguration); + new ForestMutableWorldState( + worldStateStorageCoordinator.worldStateKeyValueStorage(), + worldStatePreimageStorage, + evmConfiguration); genesisState.writeStateTo(mutableWorldState); return mutableWorldState; } else { - return new DefaultMutableWorldState( - stateRoot, worldStateStorage, worldStatePreimageStorage, evmConfiguration); + return new ForestMutableWorldState( + stateRoot, + worldStateStorageCoordinator.worldStateKeyValueStorage(), + worldStatePreimageStorage, + evmConfiguration); } } @Provides @Singleton - WorldStateStorage provideWorldStateStorage( + WorldStateStorageCoordinator provideWorldStateStorage( @Named("worldState") final KeyValueStorage keyValueStorage) { - return new WorldStateKeyValueStorage(keyValueStorage); + return new WorldStateStorageCoordinator(new ForestWorldStateKeyValueStorage(keyValueStorage)); } @Provides diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/BlockchainTestSubCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/BlockchainTestSubCommand.java new file mode 100644 index 00000000000..bec68a3cc39 --- /dev/null +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/BlockchainTestSubCommand.java @@ -0,0 +1,249 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evmtool; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hyperledger.besu.evmtool.BlockchainTestSubCommand.COMMAND_NAME; + +import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; +import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.MutableBlockchain; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockImporter; +import org.hyperledger.besu.ethereum.core.MutableWorldState; +import org.hyperledger.besu.ethereum.mainnet.BlockImportResult; +import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.referencetests.BlockchainReferenceTestCaseSpec; +import org.hyperledger.besu.ethereum.referencetests.ReferenceTestProtocolSchedules; +import org.hyperledger.besu.ethereum.rlp.RLPException; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.EvmSpecVersion; +import org.hyperledger.besu.evm.account.AccountState; +import org.hyperledger.besu.evm.internal.EvmConfiguration.WorldUpdaterMode; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.tuweni.bytes.Bytes32; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; +import picocli.CommandLine.ParentCommand; + +/** + * This class, BlockchainTestSubCommand, is a command-line interface (CLI) command that executes an + * Ethereum State Test. It implements the Runnable interface, meaning it can be used in a thread of + * execution. + * + *

The class is annotated with @CommandLine.Command, which is a PicoCLI annotation that + * designates this class as a command-line command. The annotation parameters define the command's + * name, description, whether it includes standard help options, and the version provider. + * + *

The command's functionality is defined in the run() method, which is overridden from the + * Runnable interface. + */ +@Command( + name = COMMAND_NAME, + description = "Execute an Ethereum Blockchain Test.", + mixinStandardHelpOptions = true, + versionProvider = VersionProvider.class) +public class BlockchainTestSubCommand implements Runnable { + /** + * The name of the command for the BlockchainTestSubCommand. This constant is used as the name + * parameter in the @CommandLine.Command annotation. It defines the command name that users should + * enter on the command line to invoke this command. + */ + public static final String COMMAND_NAME = "block-test"; + + @Option( + names = {"--test-name"}, + description = "Limit execution to one named test.") + private String testName = null; + + @ParentCommand private final EvmToolCommand parentCommand; + + // picocli does it magically + @Parameters private final List blockchainTestFiles = new ArrayList<>(); + + /** + * Default constructor for the BlockchainTestSubCommand class. This constructor doesn't take any + * arguments and initializes the parentCommand to null. PicoCLI requires this constructor. + */ + @SuppressWarnings("unused") + public BlockchainTestSubCommand() { + // PicoCLI requires this + this(null); + } + + BlockchainTestSubCommand(final EvmToolCommand parentCommand) { + this.parentCommand = parentCommand; + } + + @Override + public void run() { + // presume ethereum mainnet for reference and state tests + SignatureAlgorithmFactory.setDefaultInstance(); + final ObjectMapper blockchainTestMapper = JsonUtils.createObjectMapper(); + + final JavaType javaType = + blockchainTestMapper + .getTypeFactory() + .constructParametricType( + Map.class, String.class, BlockchainReferenceTestCaseSpec.class); + try { + if (blockchainTestFiles.isEmpty()) { + // if no state tests were specified, use standard input to get filenames + final BufferedReader in = + new BufferedReader(new InputStreamReader(parentCommand.in, UTF_8)); + while (true) { + final String fileName = in.readLine(); + if (fileName == null) { + // Reached end-of-file. Stop the loop. + break; + } + final File file = new File(fileName); + if (file.isFile()) { + final Map blockchainTests = + blockchainTestMapper.readValue(file, javaType); + executeBlockchainTest(blockchainTests); + } else { + parentCommand.out.println("File not found: " + fileName); + } + } + } else { + for (final Path blockchainTestFile : blockchainTestFiles) { + final Map blockchainTests; + if ("stdin".equals(blockchainTestFile.toString())) { + blockchainTests = blockchainTestMapper.readValue(parentCommand.in, javaType); + } else { + blockchainTests = blockchainTestMapper.readValue(blockchainTestFile.toFile(), javaType); + } + executeBlockchainTest(blockchainTests); + } + } + } catch (final JsonProcessingException jpe) { + parentCommand.out.println("File content error: " + jpe); + } catch (final IOException e) { + System.err.println("Unable to read state file"); + e.printStackTrace(System.err); + } + } + + private void executeBlockchainTest( + final Map blockchainTests) { + blockchainTests.forEach(this::traceTestSpecs); + } + + private void traceTestSpecs(final String test, final BlockchainReferenceTestCaseSpec spec) { + if (testName != null && !testName.equals(test)) { + parentCommand.out.println("Skipping test: " + test); + return; + } + parentCommand.out.println("Considering " + test); + + final BlockHeader genesisBlockHeader = spec.getGenesisBlockHeader(); + final MutableWorldState worldState = + spec.getWorldStateArchive() + .getMutable(genesisBlockHeader.getStateRoot(), genesisBlockHeader.getHash()) + .orElseThrow(); + + final ProtocolSchedule schedule = + ReferenceTestProtocolSchedules.getInstance().getByName(spec.getNetwork()); + + final MutableBlockchain blockchain = spec.getBlockchain(); + final ProtocolContext context = spec.getProtocolContext(); + + for (final BlockchainReferenceTestCaseSpec.CandidateBlock candidateBlock : + spec.getCandidateBlocks()) { + if (!candidateBlock.isExecutable()) { + return; + } + + try { + final Block block = candidateBlock.getBlock(); + + final ProtocolSpec protocolSpec = schedule.getByBlockHeader(block.getHeader()); + final BlockImporter blockImporter = protocolSpec.getBlockImporter(); + + verifyJournaledEVMAccountCompatability(worldState, protocolSpec); + + final HeaderValidationMode validationMode = + "NoProof".equalsIgnoreCase(spec.getSealEngine()) + ? HeaderValidationMode.LIGHT + : HeaderValidationMode.FULL; + final BlockImportResult importResult = + blockImporter.importBlock(context, block, validationMode, validationMode); + + if (importResult.isImported() != candidateBlock.isValid()) { + parentCommand.out.printf( + "Block %d (%s) %s%n", + block.getHeader().getNumber(), + block.getHash(), + importResult.isImported() ? "Failed to be rejected" : "Failed to import"); + } else { + parentCommand.out.printf( + "Block %d (%s) %s%n", + block.getHeader().getNumber(), + block.getHash(), + importResult.isImported() ? "Imported" : "Rejected (correctly)"); + } + } catch (final RLPException e) { + if (candidateBlock.isValid()) { + parentCommand.out.printf( + "Block %d (%s) should have imported but had an RLP exception %s%n", + candidateBlock.getBlock().getHeader().getNumber(), + candidateBlock.getBlock().getHash(), + e.getMessage()); + } + } + } + if (!blockchain.getChainHeadHash().equals(spec.getLastBlockHash())) { + parentCommand.out.printf( + "Chain header mismatch, have %s want %s - %s%n", + blockchain.getChainHeadHash(), spec.getLastBlockHash(), test); + } else { + parentCommand.out.println("Chain import successful - " + test); + } + } + + void verifyJournaledEVMAccountCompatability( + final MutableWorldState worldState, final ProtocolSpec protocolSpec) { + EVM evm = protocolSpec.getEvm(); + if (evm.getEvmConfiguration().worldUpdaterMode() == WorldUpdaterMode.JOURNALED) { + if (worldState + .streamAccounts(Bytes32.ZERO, Integer.MAX_VALUE) + .anyMatch(AccountState::isEmpty)) { + parentCommand.out.println("Journaled account configured and empty account detected"); + } + + if (EvmSpecVersion.SPURIOUS_DRAGON.compareTo(evm.getEvmVersion()) > 0) { + parentCommand.out.println( + "Journaled account configured and fork prior to the merge specified"); + } + } + } +} diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/CodeValidateSubCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/CodeValidateSubCommand.java index 210efe014ca..d8be71cde13 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/CodeValidateSubCommand.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/CodeValidateSubCommand.java @@ -17,35 +17,53 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.hyperledger.besu.evmtool.CodeValidateSubCommand.COMMAND_NAME; -import org.hyperledger.besu.evm.code.CodeFactory; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.referencetests.ReferenceTestProtocolSchedules; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.EvmSpecVersion; import org.hyperledger.besu.evm.code.CodeInvalid; +import org.hyperledger.besu.evm.code.CodeV1; import org.hyperledger.besu.evm.code.EOFLayout; +import org.hyperledger.besu.evm.code.EOFLayout.EOFContainerMode; import org.hyperledger.besu.util.LogConfigurator; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; -import java.io.InputStream; import java.io.InputStreamReader; -import java.io.PrintStream; import java.util.ArrayList; import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.IntStream; +import java.util.function.Supplier; +import com.google.common.base.Suppliers; import org.apache.tuweni.bytes.Bytes; +import org.web3j.utils.Strings; import picocli.CommandLine; +import picocli.CommandLine.ParentCommand; +/** + * This class represents the CodeValidateSubCommand. It is responsible for validating EVM code for + * fuzzing. It implements the Runnable interface and is annotated with the {@code + * CommandLine.Command} annotation. + */ +@SuppressWarnings({"ConstantValue"}) @CommandLine.Command( name = COMMAND_NAME, - description = "Execute an Ethereum State Test.", + description = "Validates EVM code for fuzzing", mixinStandardHelpOptions = true, versionProvider = VersionProvider.class) public class CodeValidateSubCommand implements Runnable { + /** + * The command name for the CodeValidateSubCommand. This constant is used as the name attribute in + * the CommandLine.Command annotation. + */ public static final String COMMAND_NAME = "code-validate"; - private final InputStream input; - private final PrintStream output; + + @ParentCommand EvmToolCommand parentCommand; + + private final Supplier evm; @CommandLine.Option( names = {"--file"}, @@ -56,79 +74,114 @@ public class CodeValidateSubCommand implements Runnable { @CommandLine.Parameters private final List cliCode = new ArrayList<>(); + /** Default constructor for the CodeValidateSubCommand class. This is required by PicoCLI. */ @SuppressWarnings("unused") public CodeValidateSubCommand() { // PicoCLI requires this - this(System.in, System.out); + this(null); } - CodeValidateSubCommand(final InputStream input, final PrintStream output) { - this.input = input; - this.output = output; + CodeValidateSubCommand(final EvmToolCommand parentCommand) { + this.parentCommand = parentCommand; + String fork = + parentCommand != null && parentCommand.hasFork() + ? parentCommand.getFork() + : EvmSpecVersion.PRAGUE.getName(); + + evm = + Suppliers.memoize( + () -> { + ProtocolSpec protocolSpec = + ReferenceTestProtocolSchedules.getInstance().geSpecByName(fork); + return protocolSpec.getEvm(); + }); } @Override public void run() { LogConfigurator.setLevel("", "OFF"); if (cliCode.isEmpty() && codeFile == null) { - try (BufferedReader in = new BufferedReader(new InputStreamReader(input, UTF_8))) { + try (BufferedReader in = new BufferedReader(new InputStreamReader(parentCommand.in, UTF_8))) { checkCodeFromBufferedReader(in); } catch (IOException e) { throw new RuntimeException(e); } - } else { - if (codeFile != null) { - try (BufferedReader in = new BufferedReader(new FileReader(codeFile, UTF_8))) { - checkCodeFromBufferedReader(in); - } catch (IOException e) { - throw new RuntimeException(e); - } + } else if (codeFile != null) { + try (BufferedReader in = new BufferedReader(new FileReader(codeFile, UTF_8))) { + checkCodeFromBufferedReader(in); + } catch (IOException e) { + throw new RuntimeException(e); } + } else { for (String code : cliCode) { - output.print(considerCode(code)); + String validation = considerCode(code); + if (!Strings.isBlank(validation)) { + parentCommand.out.println(validation); + } } } + parentCommand.out.flush(); } private void checkCodeFromBufferedReader(final BufferedReader in) { try { for (String code = in.readLine(); code != null; code = in.readLine()) { - output.print(considerCode(code)); + try { + String validation = considerCode(code); + if (!Strings.isBlank(validation)) { + parentCommand.out.println(validation); + } + } catch (RuntimeException e) { + parentCommand.out.println("fail: " + e.getMessage()); + } } } catch (IOException e) { throw new RuntimeException(e); } } + /** + * This method is responsible for validating the EVM code. It takes a hexadecimal string + * representation of the EVM code as input. The method first converts the hexadecimal string to + * Bytes. It then checks if the code follows the EOF layout. If the layout is valid, it retrieves + * the code from the EVM. If the code is invalid, it returns an error message with the reason for + * the invalidity. If the code is valid, it returns a string with "OK" followed by the hexadecimal + * string representation of each code section. + * + * @param hexCode the hexadecimal string representation of the EVM code + * @return a string indicating whether the code is valid or not, and in case of validity, the + * hexadecimal string representation of each code section + */ public String considerCode(final String hexCode) { Bytes codeBytes; try { - codeBytes = - Bytes.fromHexString( - hexCode.replaceAll("(^|\n)#[^\n]*($|\n)", "").replaceAll("[^0-9A-Za-z]", "")); + String strippedString = + hexCode.replaceAll("(^|\n)#[^\n]*($|\n)", "").replaceAll("[^0-9A-Za-z]", ""); + if (Strings.isEmpty(strippedString)) { + return ""; + } + codeBytes = Bytes.fromHexString(strippedString); } catch (RuntimeException re) { - return "err: hex string -" + re + "\n"; + return "err: hex string -" + re; } - if (codeBytes.size() == 0) { - return ""; + if (codeBytes.isEmpty()) { + return "err: empty container"; } - var layout = EOFLayout.parseEOF(codeBytes); + EOFLayout layout = evm.get().parseEOF(codeBytes); if (!layout.isValid()) { - return "err: layout - " + layout.getInvalidReason() + "\n"; + return "err: layout - " + layout.invalidReason(); } - var code = CodeFactory.createCode(codeBytes, 1, true); - if (!code.isValid()) { - return "err: " + ((CodeInvalid) code).getInvalidReason() + "\n"; + Code code = evm.get().getCodeUncached(codeBytes); + if (code instanceof CodeInvalid codeInvalid) { + return "err: " + codeInvalid.getInvalidReason(); + } else if (EOFContainerMode.INITCODE.equals( + ((CodeV1) code).getEofLayout().containerMode().get())) { + return "err: code is valid initcode. Runtime code expected"; + } else { + return "OK %d/%d/%d" + .formatted(code.getCodeSectionCount(), code.getSubcontainerCount(), code.getDataSize()); } - - return "OK " - + IntStream.range(0, code.getCodeSectionCount()) - .mapToObj(code::getCodeSection) - .map(cs -> layout.getContainer().slice(cs.getEntryPoint(), cs.getLength())) - .map(Bytes::toUnprefixedHexString) - .collect(Collectors.joining(",")) - + "\n"; } } diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/DataStoreModule.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/DataStoreModule.java index 65585b6441e..7629b318476 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/DataStoreModule.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/DataStoreModule.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evmtool; @@ -39,6 +38,14 @@ import dagger.Module; import dagger.Provides; +/** + * This class is a Dagger module that provides dependencies related to the data store. It includes + * the GenesisFileModule for providing the genesis block. The class is annotated with + * {@code @Module} to indicate that it is a Dagger module. It provides various key-value storages + * such as variables, blockchain, world state, world state preimage, and pruning. The type of + * key-value storage (e.g., rocksdb, memory) can be specified. The class also provides a + * BlockchainStorage which is a prefixed key blockchain storage. + */ @SuppressWarnings({"CloseableProvides"}) @Module(includes = GenesisFileModule.class) public class DataStoreModule { @@ -51,6 +58,9 @@ public class DataStoreModule { List.of(KeyValueSegmentIdentifier.values()), RocksDBMetricsFactory.PUBLIC_ROCKS_DB_METRICS)); + /** Default constructor for the DataStoreModule class. */ + public DataStoreModule() {} + @Provides @Singleton @Named("variables") @@ -142,6 +152,9 @@ static BlockchainStorage provideBlockchainStorage( @Named("variables") final KeyValueStorage variablesKeyValueStorage, final BlockHeaderFunctions blockHashFunction) { return new KeyValueStoragePrefixedKeyBlockchainStorage( - keyValueStorage, new VariablesKeyValueStorage(variablesKeyValueStorage), blockHashFunction); + keyValueStorage, + new VariablesKeyValueStorage(variablesKeyValueStorage), + blockHashFunction, + false); } } diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EOFTestSubCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EOFTestSubCommand.java new file mode 100644 index 00000000000..94f8b2cd492 --- /dev/null +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EOFTestSubCommand.java @@ -0,0 +1,242 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evmtool; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hyperledger.besu.ethereum.referencetests.EOFTestCaseSpec.TestResult.failed; +import static org.hyperledger.besu.ethereum.referencetests.EOFTestCaseSpec.TestResult.passed; +import static org.hyperledger.besu.evmtool.EOFTestSubCommand.COMMAND_NAME; + +import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.referencetests.EOFTestCaseSpec; +import org.hyperledger.besu.ethereum.referencetests.EOFTestCaseSpec.TestResult; +import org.hyperledger.besu.ethereum.referencetests.ReferenceTestProtocolSchedules; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.EvmSpecVersion; +import org.hyperledger.besu.evm.code.CodeInvalid; +import org.hyperledger.besu.evm.code.EOFLayout; +import org.hyperledger.besu.util.LogConfigurator; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.tuweni.bytes.Bytes; +import picocli.CommandLine; + +/** A PicoCli annotated command for running EOF validation reference tests. */ +@CommandLine.Command( + name = COMMAND_NAME, + description = "Runs EOF validation reference tests", + mixinStandardHelpOptions = true, + versionProvider = VersionProvider.class) +public class EOFTestSubCommand implements Runnable { + /** The name of the EOF validation reference test command. */ + public static final String COMMAND_NAME = "eof-test"; + + @CommandLine.ParentCommand private final EvmToolCommand parentCommand; + + // picocli does it magically + @CommandLine.Parameters private final List eofTestFiles = new ArrayList<>(); + + @CommandLine.Option( + names = {"--test-name"}, + description = "Limit execution to one test.") + private String testVectorName = null; + + EVM evm; + String fork = null; + + /** Default constructor for the EOFTestSubCommand class. Sets the parent command to null. */ + public EOFTestSubCommand() { + this(null); + } + + /** + * Constructor for the EOFTestSubCommand class with a parent command. + * + * @param parentCommand The parent command for this sub command. + */ + public EOFTestSubCommand(final EvmToolCommand parentCommand) { + this.parentCommand = parentCommand; + } + + @Override + public void run() { + LogConfigurator.setLevel("", "OFF"); + // presume ethereum mainnet for reference and EOF tests + SignatureAlgorithmFactory.setDefaultInstance(); + final ObjectMapper eofTestMapper = JsonUtils.createObjectMapper(); + + if (parentCommand.hasFork()) { + fork = parentCommand.getFork(); + } + ProtocolSpec protocolSpec = + ReferenceTestProtocolSchedules.getInstance() + .geSpecByName(fork == null ? EvmSpecVersion.PRAGUE.getName() : fork); + evm = protocolSpec.getEvm(); + + final JavaType javaType = + eofTestMapper + .getTypeFactory() + .constructParametricType(Map.class, String.class, EOFTestCaseSpec.class); + try { + if (eofTestFiles.isEmpty()) { + // if no EOF tests were specified use standard input to get filenames + final BufferedReader in = + new BufferedReader(new InputStreamReader(parentCommand.in, UTF_8)); + while (true) { + final String fileName = in.readLine(); + if (fileName == null) { + // reached end of file. Stop the loop. + break; + } + final File file = new File(fileName); + if (file.isFile()) { + final Map eofTests = eofTestMapper.readValue(file, javaType); + executeEOFTest(file.toString(), eofTests); + } else { + parentCommand.out.println("File not found: " + fileName); + } + } + } else { + for (final Path eofTestFile : eofTestFiles) { + final Map eofTests; + if ("stdin".equals(eofTestFile.toString())) { + eofTests = eofTestMapper.readValue(parentCommand.in, javaType); + } else { + eofTests = eofTestMapper.readValue(eofTestFile.toFile(), javaType); + } + executeEOFTest(eofTestFile.toString(), eofTests); + } + } + } catch (final JsonProcessingException jpe) { + parentCommand.out.println("File content error: " + jpe); + } catch (final IOException e) { + System.err.println("Unable to read EOF test file"); + e.printStackTrace(System.err); + } + } + + record TestExecutionResult( + String fileName, + String group, + String name, + String fork, + boolean pass, + String expectedError, + String actualError) {} + + private void executeEOFTest(final String fileName, final Map eofTests) { + List results = new ArrayList<>(); + + for (var testGroup : eofTests.entrySet()) { + String groupName = testGroup.getKey(); + for (var testVector : testGroup.getValue().getVector().entrySet()) { + String testName = testVector.getKey(); + if (testVectorName != null && !testVectorName.equals(testName)) { + continue; + } + String code = testVector.getValue().code(); + for (var testResult : testVector.getValue().results().entrySet()) { + String expectedForkName = testResult.getKey(); + if (fork != null && !fork.equals(expectedForkName)) { + System.out.println("Wrong fork - " + fork + " != " + expectedForkName); + continue; + } + TestResult expectedResult = testResult.getValue(); + EvmSpecVersion evmVersion = EvmSpecVersion.fromName(expectedForkName); + if (evmVersion == null) { + results.add( + new TestExecutionResult( + fileName, + groupName, + testName, + expectedForkName, + false, + "Valid fork name", + "Unknown fork: " + expectedForkName)); + + continue; + } + TestResult actualResult; + if (evmVersion.ordinal() < EvmSpecVersion.PRAGUE_EOF.ordinal()) { + actualResult = failed("EOF_InvalidCode"); + } else { + actualResult = considerCode(code); + } + results.add( + new TestExecutionResult( + fileName, + groupName, + testName, + expectedForkName, + actualResult.result() == expectedResult.result(), + expectedResult.exception(), + actualResult.exception())); + } + } + } + for (TestExecutionResult result : results) { + try { + parentCommand.out.println(JsonUtils.createObjectMapper().writeValueAsString(result)); + } catch (JsonProcessingException e) { + e.printStackTrace(parentCommand.out); + throw new RuntimeException(e); + } + } + } + + /** + * Considers the given hexadecimal code string for EOF validation. + * + * @param hexCode The hexadecimal string representation of the code to be considered. + * @return The result of the EOF validation test. + */ + public TestResult considerCode(final String hexCode) { + Bytes codeBytes; + try { + codeBytes = + Bytes.fromHexString( + hexCode.replaceAll("(^|\n)#[^\n]*($|\n)", "").replaceAll("[^0-9A-Za-z]", "")); + } catch (RuntimeException re) { + return failed(re.getMessage()); + } + if (codeBytes.isEmpty()) { + return passed(); + } + + var layout = EOFLayout.parseEOF(codeBytes); + if (!layout.isValid()) { + return failed("layout - " + layout.invalidReason()); + } + + var code = evm.getCodeUncached(codeBytes); + if (!code.isValid()) { + return failed("validate " + ((CodeInvalid) code).getInvalidReason()); + } + + return passed(); + } +} diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmTool.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmTool.java index 00582c9503d..12632070281 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmTool.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmTool.java @@ -11,16 +11,24 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evmtool; import org.hyperledger.besu.util.LogConfigurator; +/** The main entry point for the EVM (Ethereum Virtual Machine) tool. */ public final class EvmTool { + /** Default constructor for the EvmTool class. */ + public EvmTool() {} + + /** + * The main entry point for the EVM (Ethereum Virtual Machine) tool. + * + * @param args The command line arguments. + */ public static void main(final String... args) { - LogConfigurator.setLevel("", "DEBUG"); + LogConfigurator.setLevel("", "OFF"); final EvmToolCommand evmToolCommand = new EvmToolCommand(); evmToolCommand.execute(args); diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java index 743c215ecbb..f9d7c0db476 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommand.java @@ -11,20 +11,22 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evmtool; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hyperledger.besu.evm.code.EOFLayout.EOFContainerMode.INITCODE; import static picocli.CommandLine.ScopeType.INHERIT; import org.hyperledger.besu.cli.config.NetworkName; +import org.hyperledger.besu.collections.trie.BytesTrieSet; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; @@ -32,13 +34,12 @@ import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.EvmSpecVersion; -import org.hyperledger.besu.evm.account.AccountStorageEntry; import org.hyperledger.besu.evm.code.CodeInvalid; +import org.hyperledger.besu.evm.code.CodeV1; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.log.LogsBloomFilter; import org.hyperledger.besu.evm.tracing.OperationTracer; import org.hyperledger.besu.evm.tracing.StandardJsonTracer; -import org.hyperledger.besu.evm.worldstate.WorldState; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.hyperledger.besu.metrics.MetricsSystemModule; import org.hyperledger.besu.util.LogConfigurator; @@ -50,14 +51,13 @@ import java.io.InputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; -import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.Deque; import java.util.LinkedHashMap; import java.util.List; -import java.util.NavigableMap; +import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -71,6 +71,21 @@ import picocli.CommandLine.Command; import picocli.CommandLine.Option; +/** + * This class, EvmToolCommand, serves as the main command for the EVM (Ethereum Virtual Machine) + * tool. The EVM tool is used to execute Ethereum transactions and contracts in a local environment. + * + *

EvmToolCommand implements the Runnable interface, making it the entrypoint for PicoCLI to + * execute this command. + * + *

The class provides various options for setting up and executing EVM transactions. These + * options include, but are not limited to, setting the gas price, sender address, receiver address, + * and the data to be sent with the transaction. + * + *

Key methods in this class include 'run()' for executing the command, 'execute()' for setting + * up and running the EVM transaction, and 'dumpWorldState()' for outputting the current state of + * the Ethereum world state. + */ @Command( description = "This command evaluates EVM transactions.", abbreviateSynopsis = true, @@ -87,7 +102,10 @@ subcommands = { BenchmarkSubCommand.class, B11rSubCommand.class, + BlockchainTestSubCommand.class, CodeValidateSubCommand.class, + EOFTestSubCommand.class, + PrettyPrintSubCommand.class, StateTestSubCommand.class, T8nSubCommand.class, T8nServerSubCommand.class @@ -110,6 +128,13 @@ void setBytes(final String optionValue) { paramLabel = "") private final Long gas = 10_000_000_000L; + @Option( + names = {"--intrinsic-gas"}, + description = "Calculate and charge intrinsic and tx content gas. Default is not to charge.", + scope = INHERIT, + negatable = true) + final Boolean chargeIntrinsicGas = false; + @Option( names = {"--price"}, description = "Price of gas (in GWei) for this invocation", @@ -126,19 +151,24 @@ void setBytes(final String optionValue) { names = {"--sender"}, paramLabel = "

", description = "Calling address for this invocation.") - private final Address sender = Address.ZERO; + private final Address sender = Address.fromHexString("0x73656e646572"); @Option( names = {"--receiver"}, paramLabel = "
", description = "Receiving address for this invocation.") - private final Address receiver = Address.ZERO; + private final Address receiver = Address.fromHexString("0x7265636569766572"); + + @Option( + names = {"--create"}, + description = "Run call should be a create instead of a call operation.") + private final Boolean createTransaction = false; @Option( names = {"--contract"}, paramLabel = "
", description = "The address holding the contract code.") - private final Address contract = Address.ZERO; + private final Address contract = Address.fromHexString("0x7265636569766572"); @Option( names = {"--coinbase"}, @@ -234,12 +264,22 @@ void setBytes(final String optionValue) { PrintWriter out; InputStream in; + /** + * Default constructor for the EvmToolCommand class. It initializes the input stream with an empty + * byte array and the output stream with the standard output. + */ public EvmToolCommand() { this( new ByteArrayInputStream(new byte[0]), new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out, UTF_8)), true)); } + /** + * Constructor for the EvmToolCommand class with custom input and output streams. + * + * @param in The input stream to be used. + * @param out The output stream to be used. + */ public EvmToolCommand(final InputStream in, final PrintWriter out) { this.in = in; this.out = out; @@ -309,73 +349,91 @@ private static void addForkHelp(final CommandLine subCommandLine) { subCommandLine.setHelpSectionKeys(keys); } + /** + * Returns the fork name provided by the Dagger options. If no fork is provided, it returns the + * name of the default EVM specification version. + * + * @return The fork name. + */ + public String getFork() { + return daggerOptions.provideFork().orElse(EvmSpecVersion.defaultVersion().getName()); + } + + /** + * Checks if a fork is provided in the Dagger options. + * + * @return True if a fork is provided, false otherwise. + */ + public boolean hasFork() { + return daggerOptions.provideFork().isPresent(); + } + @Override public void run() { LogConfigurator.setLevel("", "OFF"); try { + GenesisFileModule genesisFileModule; + if (network != null) { + genesisFileModule = GenesisFileModule.createGenesisModule(network); + } else if (genesisFile != null) { + genesisFileModule = GenesisFileModule.createGenesisModule(genesisFile); + } else { + genesisFileModule = GenesisFileModule.createGenesisModule(); + } final EvmToolComponent component = DaggerEvmToolComponent.builder() .dataStoreModule(new DataStoreModule()) - .genesisFileModule( - network == null - ? genesisFile == null - ? GenesisFileModule.createGenesisModule(NetworkName.DEV) - : GenesisFileModule.createGenesisModule(genesisFile) - : GenesisFileModule.createGenesisModule(network)) + .genesisFileModule(genesisFileModule) .evmToolCommandOptionsModule(daggerOptions) .metricsSystemModule(new MetricsSystemModule()) .build(); - final BlockHeader blockHeader = - BlockHeaderBuilder.create() - .parentHash(Hash.EMPTY) - .coinbase(coinbase) - .difficulty(Difficulty.ONE) - .number(1) - .gasLimit(5000) - .timestamp(Instant.now().toEpochMilli()) - .ommersHash(Hash.EMPTY_LIST_HASH) - .stateRoot(Hash.EMPTY_TRIE_HASH) - .transactionsRoot(Hash.EMPTY) - .receiptsRoot(Hash.EMPTY) - .logsBloom(LogsBloomFilter.empty()) - .gasUsed(0) - .extraData(Bytes.EMPTY) - .mixHash(Hash.EMPTY) - .nonce(0) - .blockHeaderFunctions(new MainnetBlockHeaderFunctions()) - .buildBlockHeader(); - int remainingIters = this.repeat; - final ProtocolSpec protocolSpec = - component.getProtocolSpec().apply(BlockHeaderBuilder.createDefault().buildBlockHeader()); + final ProtocolSpec protocolSpec = component.getProtocolSpec(); final Transaction tx = new Transaction.Builder() .nonce(0) .gasPrice(Wei.ZERO) .gasLimit(Long.MAX_VALUE) - .to(receiver) + .to(createTransaction ? null : receiver) .value(Wei.ZERO) .payload(callData) .sender(sender) .build(); - final long intrinsicGasCost = - protocolSpec - .getGasCalculator() - .transactionIntrinsicGasCost(tx.getPayload(), tx.isContractCreation()); - final long accessListCost = - tx.getAccessList() - .map(list -> protocolSpec.getGasCalculator().accessListGasCost(list)) - .orElse(0L); - long txGas = gas - intrinsicGasCost - accessListCost; + long txGas = gas; + if (chargeIntrinsicGas) { + final long intrinsicGasCost = + protocolSpec + .getGasCalculator() + .transactionIntrinsicGasCost(tx.getPayload(), tx.isContractCreation()); + txGas -= intrinsicGasCost; + final long accessListCost = + tx.getAccessList() + .map(list -> protocolSpec.getGasCalculator().accessListGasCost(list)) + .orElse(0L); + txGas -= accessListCost; + } final EVM evm = protocolSpec.getEvm(); - Code code = evm.getCode(Hash.hash(codeBytes), codeBytes); + if (codeBytes.isEmpty() && !createTransaction) { + codeBytes = component.getWorldState().get(receiver).getCode(); + } + Code code = + createTransaction ? evm.getCodeForCreation(codeBytes) : evm.getCodeUncached(codeBytes); if (!code.isValid()) { out.println(((CodeInvalid) code).getInvalidReason()); return; + } else if (code.getEofVersion() == 1 + && createTransaction + != INITCODE.equals(((CodeV1) code).getEofLayout().containerMode().get())) { + out.println( + createTransaction + ? "--create requires EOF in INITCODE mode" + : "To evaluate INITCODE mode EOF code use the --create flag"); + return; } + final Stopwatch stopwatch = Stopwatch.createUnstarted(); long lastTime = 0; do { @@ -388,22 +446,65 @@ public void run() { WorldUpdater updater = component.getWorldUpdater(); updater.getOrCreate(sender); - updater.getOrCreate(receiver); + if (!createTransaction) { + updater.getOrCreate(receiver); + } var contractAccount = updater.getOrCreate(contract); contractAccount.setCode(codeBytes); + final Set
addressList = new BytesTrieSet<>(Address.SIZE); + addressList.add(sender); + addressList.add(contract); + if (EvmSpecVersion.SHANGHAI.compareTo(evm.getEvmVersion()) <= 0) { + addressList.add(coinbase); + } + final BlockHeader blockHeader = + BlockHeaderBuilder.create() + .parentHash(Hash.EMPTY) + .coinbase(coinbase) + .difficulty( + Difficulty.fromHexString( + genesisFileModule.providesGenesisConfigFile().getDifficulty())) + .number(0) + .gasLimit(genesisFileModule.providesGenesisConfigFile().getGasLimit()) + .timestamp(0) + .ommersHash(Hash.EMPTY_LIST_HASH) + .stateRoot(Hash.EMPTY_TRIE_HASH) + .transactionsRoot(Hash.EMPTY) + .receiptsRoot(Hash.EMPTY) + .logsBloom(LogsBloomFilter.empty()) + .gasUsed(0) + .extraData(Bytes.EMPTY) + .mixHash(Hash.ZERO) + .nonce(0) + .blockHeaderFunctions(new MainnetBlockHeaderFunctions()) + .baseFee( + component + .getBlockchain() + .getChainHeadHeader() + .getBaseFee() + .or(() -> genesisFileModule.providesGenesisConfigFile().getBaseFeePerGas()) + .orElse( + protocolSpec.getFeeMarket().implementsBaseFee() ? Wei.of(0xa) : null)) + .buildBlockHeader(); + + Address contractAddress = + createTransaction ? Address.contractAddress(receiver, 0) : receiver; MessageFrame initialMessageFrame = MessageFrame.builder() - .type(MessageFrame.Type.MESSAGE_CALL) + .type( + createTransaction + ? MessageFrame.Type.CONTRACT_CREATION + : MessageFrame.Type.MESSAGE_CALL) .worldUpdater(updater.updater()) .initialGas(txGas) - .contract(Address.ZERO) - .address(receiver) + .contract(contractAddress) + .address(contractAddress) .originator(sender) .sender(sender) .gasPrice(gasPriceGWei) .blobGasPrice(blobGasPrice) - .inputData(callData) + .inputData(createTransaction ? codeBytes.slice(code.getSize()) : callData) .value(ethValue) .apparentValue(ethValue) .code(code) @@ -411,10 +512,7 @@ public void run() { .completer(c -> {}) .miningBeneficiary(blockHeader.getCoinbase()) .blockHashLookup(new CachingBlockHashLookup(blockHeader, component.getBlockchain())) - .accessListWarmAddresses( - EvmSpecVersion.SHANGHAI.compareTo(evm.getEvmVersion()) <= 0 - ? Set.of(coinbase) - : Set.of()) + .accessListWarmAddresses(addressList) .build(); Deque messageFrameStack = initialMessageFrame.getMessageFrameStack(); @@ -428,36 +526,38 @@ public void run() { lastTime = stopwatch.elapsed().toNanos(); } if (lastLoop) { - if (messageFrame.getExceptionalHaltReason().isPresent()) { - out.println(messageFrame.getExceptionalHaltReason().get()); - } - if (messageFrame.getRevertReason().isPresent()) { - out.println( - new String(messageFrame.getRevertReason().get().toArrayUnsafe(), UTF_8)); - } - } - } - - if (lastLoop && messageFrameStack.isEmpty()) { - final long evmGas = txGas - messageFrame.getRemainingGas(); - final JsonObject resultLine = new JsonObject(); - resultLine.put("gasUser", "0x" + Long.toHexString(evmGas)); - if (!noTime) { - resultLine.put("timens", lastTime).put("time", lastTime / 1000); + messageFrame + .getExceptionalHaltReason() + .ifPresent(haltReason -> out.println(haltReason)); + messageFrame + .getRevertReason() + .ifPresent(bytes -> out.println(new String(bytes.toArrayUnsafe(), UTF_8))); } - resultLine - .put("gasTotal", "0x" + Long.toHexString(evmGas)) - .put("output", messageFrame.getOutputData().toHexString()); - out.println(); - out.println(resultLine); } } lastTime = stopwatch.elapsed().toNanos(); stopwatch.reset(); - if (showJsonAlloc && lastLoop) { + if (lastLoop) { + initialMessageFrame.getSelfDestructs().forEach(updater::deleteAccount); + updater.clearAccountsThatAreEmpty(); updater.commit(); - WorldState worldState = component.getWorldState(); - dumpWorldState(worldState, out); + MutableWorldState worldState = component.getWorldState(); + final long evmGas = txGas - initialMessageFrame.getRemainingGas(); + final JsonObject resultLine = new JsonObject(); + resultLine + .put("stateRoot", worldState.rootHash().toHexString()) + .put("output", initialMessageFrame.getOutputData().toHexString()) + .put("gasUsed", "0x" + Long.toHexString(evmGas)) + .put("pass", initialMessageFrame.getExceptionalHaltReason().isEmpty()) + .put("fork", protocolSpec.getName()); + if (!noTime) { + resultLine.put("timens", lastTime).put("time", lastTime / 1000); + } + out.println(resultLine); + + if (showJsonAlloc) { + dumpWorldState(worldState, out); + } } } while (remainingIters-- > 0); @@ -467,40 +567,52 @@ public void run() { } } - public static void dumpWorldState(final WorldState worldState, final PrintWriter out) { + /** + * Dumps the current state of the Ethereum world state to the provided PrintWriter. The state + * includes account balances, nonces, codes, and storage. The output is in JSON format. + * + * @param worldState The Ethereum world state to be dumped. + * @param out The PrintWriter to which the state is dumped. + */ + public static void dumpWorldState(final MutableWorldState worldState, final PrintWriter out) { out.println("{"); worldState .streamAccounts(Bytes32.ZERO, Integer.MAX_VALUE) - .sorted(Comparator.comparing(o -> o.getAddress().get().toHexString())) + .sorted(Comparator.comparing(o -> o.getAddress().orElse(Address.ZERO).toHexString())) .forEach( - account -> { - out.println( - " \"" + account.getAddress().map(Address::toHexString).orElse("-") + "\": {"); + a -> { + var account = worldState.get(a.getAddress().get()); + out.println(" \"" + account.getAddress().toHexString() + "\": {"); if (account.getCode() != null && !account.getCode().isEmpty()) { out.println(" \"code\": \"" + account.getCode().toHexString() + "\","); } - NavigableMap storageEntries = - account.storageEntriesFrom(Bytes32.ZERO, Integer.MAX_VALUE); + var storageEntries = + account.storageEntriesFrom(Bytes32.ZERO, Integer.MAX_VALUE).values().stream() + .map( + e -> + Map.entry( + e.getKey().orElse(UInt256.ZERO), + account.getStorageValue(UInt256.fromBytes(e.getKey().get())))) + .filter(e -> !e.getValue().isZero()) + .sorted(Map.Entry.comparingByKey()) + .toList(); if (!storageEntries.isEmpty()) { out.println(" \"storage\": {"); out.println( STORAGE_JOINER.join( - storageEntries.values().stream() + storageEntries.stream() .map( - accountStorageEntry -> + e -> " \"" - + accountStorageEntry - .getKey() - .map(UInt256::toQuantityHexString) - .orElse("-") + + e.getKey().toQuantityHexString() + "\": \"" - + accountStorageEntry.getValue().toQuantityHexString() + + e.getValue().toQuantityHexString() + "\"") .toList())); out.println(" },"); } out.print(" \"balance\": \"" + account.getBalance().toShortHexString() + "\""); - if (account.getNonce() > 0) { + if (account.getNonce() != 0) { out.println(","); out.println( " \"nonce\": \"" diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommandOptionsModule.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommandOptionsModule.java index b8df5fa4a95..01238e2b286 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommandOptionsModule.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolCommandOptionsModule.java @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.evmtool; import static org.hyperledger.besu.cli.DefaultCommandValues.getDefaultBesuDataPath; @@ -34,6 +32,16 @@ import picocli.CommandLine; import picocli.CommandLine.Option; +/** + * This class, EvmToolCommandOptionsModule, is a Dagger module that provides dependencies for the + * EvmToolCommand. It contains options for setting up the EVM tool, such as whether revert reasons + * should be persisted, the fork to evaluate, the key-value storage to be used, the data path, the + * block number to evaluate against, and the world state update mode. + * + *

The class uses PicoCLI annotations to define these options, which can be provided via the + * command line when running the EVM tool. Each option has a corresponding provider method that + * Dagger uses to inject the option's value where needed. + */ @SuppressWarnings("WeakerAccess") @Module public class EvmToolCommandOptionsModule { @@ -88,7 +96,9 @@ String provideKeyValueStorageName() { @Provides @Singleton BesuConfiguration provideBesuConfiguration() { - return new BesuConfigurationImpl(dataPath, dataPath.resolve(BesuController.DATABASE_PATH)); + final var besuConfiguration = new BesuConfigurationImpl(); + besuConfiguration.init(dataPath, dataPath.resolve(BesuController.DATABASE_PATH), null); + return besuConfiguration; } @Option( @@ -133,4 +143,9 @@ BlockParameter provideBlockParameter() { EvmConfiguration provideEvmConfiguration() { return new EvmConfiguration(jumpDestCacheWeightKilobytes, worldstateUpdateMode); } + + /** Default constructor for the EvmToolCommandOptionsModule class. */ + public EvmToolCommandOptionsModule() { + // This is only here because of JavaDoc linting + } } diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolComponent.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolComponent.java index 5f66c5ddf50..cc84b59480c 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolComponent.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/EvmToolComponent.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,23 +11,37 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evmtool; import org.hyperledger.besu.ethereum.chain.Blockchain; -import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.hyperledger.besu.metrics.MetricsConfigurationModule; import org.hyperledger.besu.metrics.MetricsSystemModule; -import java.util.function.Function; import javax.inject.Singleton; import dagger.Component; +/** + * This is a Dagger component interface for the EVM (Ethereum Virtual Machine) tool. It is annotated + * with @Singleton to ensure that only a single instance of this component exists within the Dagger + * component graph. + * + *

The component is composed of several modules that provide the necessary dependencies for the + * EVM tool: - ProtocolModule: Provides the protocol specification. - GenesisFileModule: Provides + * the genesis file for the blockchain. - DataStoreModule: Provides the data store for blockchain + * data. - BlockchainModule: Provides the blockchain instance. - EvmToolCommandOptionsModule: + * Provides the command options for the EVM tool. - MetricsConfigurationModule and + * MetricsSystemModule: Provide the metrics system and its configuration. + * + *

The interface defines methods to get instances of key classes like ProtocolSpec, EVM, + * WorldUpdater, MutableWorldState, and Blockchain. These methods are used by Dagger to inject the + * returned instances where needed. + */ @Singleton @Component( modules = { @@ -41,11 +55,44 @@ }) public interface EvmToolComponent { - Function getProtocolSpec(); + /** + * Retrieves the ProtocolSpec instance. ProtocolSpec defines the Ethereum protocol specifications, + * which includes the precompiled contracts, the gas calculator, the EVM, and the private nonce + * calculator. + * + * @return The ProtocolSpec instance. + */ + ProtocolSpec getProtocolSpec(); + + /** + * Retrieves the EVM instance. EVM (Ethereum Virtual Machine) is responsible for executing the + * bytecode of smart contracts in Ethereum. + * + * @return The EVM instance. + */ + EVM getEVM(); + /** + * Retrieves the WorldUpdater instance. WorldUpdater is used to modify the world state, which + * includes the accounts and their associated storage and code. + * + * @return The WorldUpdater instance. + */ WorldUpdater getWorldUpdater(); + /** + * Retrieves the MutableWorldState instance. MutableWorldState represents the world state of + * Ethereum, which includes all accounts, their balances, nonces, codes, and storage. + * + * @return The MutableWorldState instance. + */ MutableWorldState getWorldState(); + /** + * Retrieves the Blockchain instance. Blockchain represents the Ethereum blockchain, which + * includes blocks, transactions, and the world state. + * + * @return The Blockchain instance. + */ Blockchain getBlockchain(); } diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/GenesisFileModule.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/GenesisFileModule.java index 596c9eddc7f..8512b5537e0 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/GenesisFileModule.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/GenesisFileModule.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evmtool; @@ -19,6 +18,7 @@ import org.hyperledger.besu.cli.config.NetworkName; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.config.GenesisConfigOptions; +import org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId; import org.hyperledger.besu.ethereum.chain.GenesisState; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; @@ -29,6 +29,7 @@ import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; +import java.util.Locale; import java.util.Optional; import javax.inject.Named; import javax.inject.Singleton; @@ -37,11 +38,26 @@ import dagger.Provides; import io.vertx.core.json.JsonObject; +/** + * This class, GenesisFileModule, is a Dagger module that provides dependencies for the GenesisFile. + * It contains options for setting up the GenesisFile, such as the genesis configuration, genesis + * state, block header functions, and the genesis block. + * + *

The class uses Dagger annotations to define these options, which can be provided via the + * command line when running the EVM tool. Each option has a corresponding provider method that + * Dagger uses to inject the option's value where needed. + */ @Module public class GenesisFileModule { private final String genesisConfig; + /** + * Constructs a new GenesisFileModule with the specified genesis configuration. + * + * @param genesisConfig The configuration for the genesis file. This is typically a JSON string + * that specifies various parameters for the genesis block of the blockchain. + */ protected GenesisFileModule(final String genesisConfig) { this.genesisConfig = genesisConfig; } @@ -97,6 +113,20 @@ static GenesisFileModule createGenesisModule(final File genesisFile) throws IOEx return createGenesisModule(Files.readString(genesisFile.toPath(), Charset.defaultCharset())); } + static GenesisFileModule createGenesisModule() { + final JsonObject genesis = new JsonObject(); + final JsonObject config = new JsonObject(); + genesis.put("config", config); + config.put("chainId", 1337); + config.put(MainnetHardforkId.mostRecent().toString().toLowerCase(Locale.ROOT) + "Time", 0); + genesis.put("baseFeePerGas", "0x3b9aca00"); + genesis.put("gasLimit", "0x2540be400"); + genesis.put("difficulty", "0x0"); + genesis.put("mixHash", "0x0000000000000000000000000000000000000000000000000000000000000000"); + genesis.put("coinbase", "0x0000000000000000000000000000000000000000"); + return createGenesisModule(genesis.toString()); + } + private static GenesisFileModule createGenesisModule(final String genesisConfig) { final JsonObject genesis = new JsonObject(genesisConfig); final JsonObject config = genesis.getJsonObject("config"); diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/JsonUtils.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/JsonUtils.java index 277679c0c80..7af31054a65 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/JsonUtils.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/JsonUtils.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.evmtool; import org.hyperledger.besu.datatypes.Address; @@ -26,6 +24,12 @@ import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import org.apache.tuweni.bytes.Bytes; +/** + * Utility class for JSON related operations. This class provides a method to create an ObjectMapper + * with standard configurations needed for evmtool. The ObjectMapper is configured to match the + * standard JSON output of Go, and it does not auto close the source. It also registers serializers + * for Address and Bytes classes. This class is not meant to be instantiated. + */ public class JsonUtils { private JsonUtils() {} diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/MainnetGenesisFileModule.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/MainnetGenesisFileModule.java index e7c576c5852..0ce7bf98f58 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/MainnetGenesisFileModule.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/MainnetGenesisFileModule.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evmtool; @@ -19,7 +18,9 @@ import org.hyperledger.besu.config.StubGenesisConfigOptions; import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; import org.hyperledger.besu.crypto.SignatureAlgorithmType; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; import org.hyperledger.besu.ethereum.mainnet.MainnetProtocolSchedule; @@ -27,8 +28,10 @@ import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import java.math.BigInteger; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.function.Function; @@ -68,18 +71,25 @@ ProtocolSchedule provideProtocolSchedule( } if (fork.isPresent()) { - var schedules = createSchedules(); - var schedule = schedules.get(fork.map(String::toLowerCase).get()); + var schedules = createSchedules(configOptions.getChainId().orElse(BigInteger.valueOf(1337))); + var schedule = schedules.get(fork.get().toLowerCase(Locale.getDefault())); if (schedule != null) { return schedule.get(); } } - return MainnetProtocolSchedule.fromConfig(configOptions, evmConfiguration); + + return MainnetProtocolSchedule.fromConfig( + configOptions, + evmConfiguration, + MiningParameters.newDefault(), + new BadBlockManager(), + false, + new NoOpMetricsSystem()); } - public static Map> createSchedules() { + public static Map> createSchedules(final BigInteger chainId) { return Map.ofEntries( - Map.entry("frontier", createSchedule(new StubGenesisConfigOptions())), + Map.entry("frontier", createSchedule(new StubGenesisConfigOptions().chainId(chainId))), Map.entry("homestead", createSchedule(new StubGenesisConfigOptions().homesteadBlock(0))), Map.entry("eip150", createSchedule(new StubGenesisConfigOptions().eip150Block(0))), Map.entry("eip158", createSchedule(new StubGenesisConfigOptions().eip158Block(0))), @@ -90,34 +100,86 @@ public static Map> createSchedules() { Map.entry( "constantinoplefix", createSchedule(new StubGenesisConfigOptions().petersburgBlock(0))), Map.entry("petersburg", createSchedule(new StubGenesisConfigOptions().petersburgBlock(0))), - Map.entry("istanbul", createSchedule(new StubGenesisConfigOptions().istanbulBlock(0))), Map.entry( - "muirglacier", createSchedule(new StubGenesisConfigOptions().muirGlacierBlock(0))), - Map.entry("berlin", createSchedule(new StubGenesisConfigOptions().berlinBlock(0))), + "istanbul", + createSchedule(new StubGenesisConfigOptions().istanbulBlock(0).chainId(chainId))), + Map.entry( + "muirglacier", + createSchedule(new StubGenesisConfigOptions().muirGlacierBlock(0).chainId(chainId))), + Map.entry( + "berlin", + createSchedule(new StubGenesisConfigOptions().berlinBlock(0).chainId(chainId))), Map.entry( "london", - createSchedule(new StubGenesisConfigOptions().londonBlock(0).baseFeePerGas(0x0a))), + createSchedule( + new StubGenesisConfigOptions() + .londonBlock(0) + .baseFeePerGas(0x0a) + .chainId(chainId))), Map.entry( - "arrowglacier", createSchedule(new StubGenesisConfigOptions().arrowGlacierBlock(0))), + "arrowglacier", + createSchedule( + new StubGenesisConfigOptions() + .arrowGlacierBlock(0) + .baseFeePerGas(0x0a) + .chainId(chainId))), Map.entry( - "grayglacier", createSchedule(new StubGenesisConfigOptions().grayGlacierBlock(0))), + "grayglacier", + createSchedule( + new StubGenesisConfigOptions() + .grayGlacierBlock(0) + .baseFeePerGas(0x0a) + .chainId(chainId))), Map.entry( "merge", createSchedule( - new StubGenesisConfigOptions().mergeNetSplitBlock(0).baseFeePerGas(0x0a))), + new StubGenesisConfigOptions() + .mergeNetSplitBlock(0) + .baseFeePerGas(0x0a) + .chainId(chainId))), Map.entry( "shanghai", - createSchedule(new StubGenesisConfigOptions().shanghaiTime(0).baseFeePerGas(0x0a))), + createSchedule( + new StubGenesisConfigOptions() + .shanghaiTime(0) + .baseFeePerGas(0x0a) + .chainId(chainId))), Map.entry( "cancun", - createSchedule(new StubGenesisConfigOptions().cancunTime(0).baseFeePerGas(0x0a))), + createSchedule( + new StubGenesisConfigOptions().cancunTime(0).baseFeePerGas(0x0a).chainId(chainId))), + Map.entry( + "cancuneof", + createSchedule( + new StubGenesisConfigOptions() + .cancunEOFTime(0) + .baseFeePerGas(0x0a) + .chainId(chainId))), + Map.entry( + "prague", + createSchedule( + new StubGenesisConfigOptions().pragueTime(0).baseFeePerGas(0x0a).chainId(chainId))), + Map.entry( + "pragueeof", + createSchedule( + new StubGenesisConfigOptions() + .pragueEOFTime(0) + .baseFeePerGas(0x0a) + .chainId(chainId))), Map.entry( "futureeips", - createSchedule(new StubGenesisConfigOptions().futureEipsTime(0).baseFeePerGas(0x0a))), + createSchedule( + new StubGenesisConfigOptions() + .futureEipsTime(0) + .baseFeePerGas(0x0a) + .chainId(chainId))), Map.entry( "experimentaleips", createSchedule( - new StubGenesisConfigOptions().experimentalEipsTime(0).baseFeePerGas(0x0a)))); + new StubGenesisConfigOptions() + .experimentalEipsTime(0) + .baseFeePerGas(0x0a) + .chainId(chainId)))); } private static Supplier createSchedule(final GenesisConfigOptions options) { @@ -128,7 +190,11 @@ private static Supplier createSchedule(final GenesisConfigOpti ProtocolSpecAdapters.create(0, Function.identity()), PrivacyParameters.DEFAULT, false, - EvmConfiguration.DEFAULT) + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()) .createProtocolSchedule(); } } diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/PrettyPrintSubCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/PrettyPrintSubCommand.java new file mode 100644 index 00000000000..e24c24a21e9 --- /dev/null +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/PrettyPrintSubCommand.java @@ -0,0 +1,130 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evmtool; + +import static org.hyperledger.besu.evmtool.PrettyPrintSubCommand.COMMAND_NAME; + +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.referencetests.ReferenceTestProtocolSchedules; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.EvmSpecVersion; +import org.hyperledger.besu.evm.code.CodeInvalid; +import org.hyperledger.besu.evm.code.EOFLayout; +import org.hyperledger.besu.util.LogConfigurator; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; +import picocli.CommandLine; + +/** + * This class, PrettyPrintSubCommand, is a command-line interface (CLI) command that pretty prints + * EOF (Ethereum Object Format) code. It implements the Runnable interface, meaning it can be used + * in a thread of execution. + * + *

The class is annotated with {@code @CommandLine.Command}, which is a PicoCLI annotation that + * designates this class as a command-line command. The annotation parameters define the command's + * name, description, whether it includes standard help options, and the version provider. + * + *

The command's functionality is defined in the run() method, which is overridden from the + * Runnable interface. + */ +@CommandLine.Command( + name = COMMAND_NAME, + description = "Pretty Prints EOF Code", + mixinStandardHelpOptions = true, + versionProvider = VersionProvider.class) +public class PrettyPrintSubCommand implements Runnable { + /** + * The name of the command for the PrettyPrintSubCommand. This constant is used as the name + * parameter in the @CommandLine.Command annotation. It defines the command name that users should + * enter on the command line to invoke this command. + */ + public static final String COMMAND_NAME = "pretty-print"; + + @CommandLine.ParentCommand private final EvmToolCommand parentCommand; + + @CommandLine.Option( + names = {"-f", "--force"}, + description = "Always print well formated code, even if there is an error", + paramLabel = "") + private final Boolean force = false; + + // picocli does it magically + @CommandLine.Parameters private final List codeList = new ArrayList<>(); + + /** + * Default constructor for the PrettyPrintSubCommand class. This constructor initializes the + * parentCommand to null. + */ + public PrettyPrintSubCommand() { + this(null); + } + + /** + * Constructs a new PrettyPrintSubCommand with the specified parent command. + * + * @param parentCommand The parent command for this subcommand. This is typically an instance of + * EvmToolCommand. + */ + public PrettyPrintSubCommand(final EvmToolCommand parentCommand) { + this.parentCommand = parentCommand; + } + + @Override + public void run() { + LogConfigurator.setLevel("", "OFF"); + + for (var hexCode : codeList) { + Bytes container; + try { + container = Bytes.fromHexString(hexCode); + } catch (IllegalArgumentException e) { + parentCommand.out.println("Invalid hex string: " + e.getMessage()); + continue; + } + if (container.get(0) != ((byte) 0xef) && container.get(1) != 0) { + parentCommand.out.println( + "Pretty printing of legacy EVM is not supported. Patches welcome!"); + + } else { + String fork = EvmSpecVersion.PRAGUE.getName(); + if (parentCommand.hasFork()) { + fork = parentCommand.getFork(); + } + ProtocolSpec protocolSpec = ReferenceTestProtocolSchedules.getInstance().geSpecByName(fork); + EVM evm = protocolSpec.getEvm(); + EOFLayout layout = evm.parseEOF(container); + if (layout.isValid()) { + var validatedCode = evm.getCodeUncached(container); + if (validatedCode.isValid() || force) { + layout.prettyPrint(parentCommand.out); + } + if (validatedCode instanceof CodeInvalid codeInvalid) { + parentCommand.out.println("EOF code is invalid - " + codeInvalid.getInvalidReason()); + } + if (layout.container().size() != container.size()) { + parentCommand.out.println( + "EOF code is invalid - dangling data after container - " + + container.slice(layout.container().size()).toHexString()); + } + } else { + parentCommand.out.println("EOF layout is invalid - " + layout.invalidReason()); + } + } + } + } +} diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/ProtocolModule.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/ProtocolModule.java index 14702bc5e8d..a196a35f5ba 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/ProtocolModule.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/ProtocolModule.java @@ -11,27 +11,51 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evmtool; -import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.evm.EVM; -import java.util.function.Function; import javax.inject.Singleton; import dagger.Module; import dagger.Provides; +/** + * This class, ProtocolModule, is a Dagger module that provides dependencies for the ProtocolSpec + * and EVM. It includes the GenesisFileModule, which provides the genesis configuration for the + * blockchain. + * + *

The class uses Dagger annotations to define these dependencies, which can be provided via the + * command line when running the EVM tool. Each dependency has a corresponding provider method that + * Dagger uses to inject the dependency's value where needed. + */ @SuppressWarnings("WeakerAccess") @Module(includes = GenesisFileModule.class) public class ProtocolModule { + /** + * Default constructor for the ProtocolModule class. This constructor doesn't take any arguments + * and doesn't perform any initialization. + */ + public ProtocolModule() {} + + @Provides + @Singleton + ProtocolSpec getProtocolSpec(final ProtocolSchedule protocolSchedule) { + return protocolSchedule.getByBlockHeader( + BlockHeaderBuilder.createDefault() + .timestamp(Long.MAX_VALUE) + .number(Long.MAX_VALUE) + .buildBlockHeader()); + } + @Provides @Singleton - Function getProtocolSpec(final ProtocolSchedule protocolSchedule) { - return protocolSchedule::getByBlockHeader; + EVM getEVM(final ProtocolSpec protocolSpec) { + return protocolSpec.getEvm(); } } diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/StateTestSubCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/StateTestSubCommand.java index f45797f9643..f6e05b5a498 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/StateTestSubCommand.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/StateTestSubCommand.java @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.evmtool; import static java.nio.charset.StandardCharsets.UTF_8; @@ -34,7 +32,6 @@ import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult; import org.hyperledger.besu.ethereum.referencetests.GeneralStateTestCaseEipSpec; import org.hyperledger.besu.ethereum.referencetests.GeneralStateTestCaseSpec; -import org.hyperledger.besu.ethereum.referencetests.ReferenceTestBlockchain; import org.hyperledger.besu.ethereum.referencetests.ReferenceTestProtocolSchedules; import org.hyperledger.besu.ethereum.rlp.RLP; import org.hyperledger.besu.evm.account.Account; @@ -54,37 +51,54 @@ import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.base.Stopwatch; -import com.google.common.base.Suppliers; import org.apache.tuweni.bytes.Bytes; import picocli.CommandLine.Command; import picocli.CommandLine.Option; import picocli.CommandLine.Parameters; import picocli.CommandLine.ParentCommand; +/** + * This class, StateTestSubCommand, is a command-line interface (CLI) command that executes an + * Ethereum State Test. It implements the Runnable interface, meaning it can be used in a thread of + * execution. + * + *

The class is annotated with @CommandLine.Command, which is a PicoCLI annotation that + * designates this class as a command-line command. The annotation parameters define the command's + * name, description, whether it includes standard help options, and the version provider. + * + *

The command's functionality is defined in the run() method, which is overridden from the + * Runnable interface. + */ @Command( name = COMMAND_NAME, description = "Execute an Ethereum State Test.", mixinStandardHelpOptions = true, versionProvider = VersionProvider.class) public class StateTestSubCommand implements Runnable { + /** + * The name of the command for the StateTestSubCommand. This constant is used as the name + * parameter in the @CommandLine.Command annotation. It defines the command name that users should + * enter on the command line to invoke this command. + */ public static final String COMMAND_NAME = "state-test"; - static final Supplier referenceTestProtocolSchedules = - Suppliers.memoize(ReferenceTestProtocolSchedules::create); - @SuppressWarnings({"FieldCanBeFinal"}) @Option( names = {"--fork"}, description = "Force the state tests to run on a specific fork.") private String fork = null; + @Option( + names = {"--test-name"}, + description = "Limit execution to one named test.") + private String testName = null; + @Option( names = {"--data-index"}, description = "Limit execution to one data variable.") @@ -110,6 +124,10 @@ public class StateTestSubCommand implements Runnable { // picocli does it magically @Parameters private final List stateTestFiles = new ArrayList<>(); + /** + * Default constructor for the StateTestSubCommand class. This constructor doesn't take any + * arguments and initializes the parentCommand to null. PicoCLI requires this constructor. + */ @SuppressWarnings("unused") public StateTestSubCommand() { // PicoCLI requires this @@ -173,10 +191,12 @@ public void run() { private void executeStateTest(final Map generalStateTests) { for (final Map.Entry generalStateTestEntry : generalStateTests.entrySet()) { - generalStateTestEntry - .getValue() - .finalStateSpecs() - .forEach((__, specs) -> traceTestSpecs(generalStateTestEntry.getKey(), specs)); + if (testName == null || testName.equals(generalStateTestEntry.getKey())) { + generalStateTestEntry + .getValue() + .finalStateSpecs() + .forEach((__, specs) -> traceTestSpecs(generalStateTestEntry.getKey(), specs)); + } } } @@ -207,7 +227,7 @@ private void traceTestSpecs(final String test, final List EMPTY_ADDRESS_SET = Set.of(); + + /** + * A record that represents a transaction that has been rejected. It contains the index of the + * transaction and the error message explaining why it was rejected. + * + * @param index The index of the rejected transaction. + * @param error The error message explaining why the transaction was rejected. + */ public record RejectedTransaction(int index, String error) {} + /** + * Default constructor for the T8nExecutor class. This constructor does not perform any + * operations. + */ + public T8nExecutor() { + // Default constructor required for Javadoc linting + } + + /** + * Extracts transactions from a given JSON iterator and adds them to the provided transactions + * list. If a transaction cannot be parsed or is invalid, it is added to the rejections list with + * its index and error message. + * + * @param out PrintWriter used for outputting information or errors. + * @param it Iterator over JSON nodes, each representing a transaction. + * @param transactions List of transactions to which parsed transactions are added. + * @param rejections List of RejectedTransaction records to which rejected transactions are added. + * @return The updated list of transactions after parsing and validation. + */ protected static List extractTransactions( final PrintWriter out, final Iterator it, @@ -160,12 +207,58 @@ protected static List extractTransactions( false) .map(JsonNode::textValue) .toList(); - var accessListEntry = AccessListEntry.createAccessListEntry(address, storageKeys); + AccessListEntry accessListEntry = + AccessListEntry.createAccessListEntry(address, storageKeys); entries.add(accessListEntry); } builder.accessList(entries); } + if (txNode.has("authorizationList")) { + JsonNode authorizationList = txNode.get("authorizationList"); + + if (!authorizationList.isArray()) { + out.printf( + "TX json node unparseable: expected authorizationList to be an array - %s%n", + txNode); + continue; + } + + List authorizations = new ArrayList<>(authorizationList.size()); + for (JsonNode entryAsJson : authorizationList) { + final BigInteger authorizationChainId = + Bytes.fromHexStringLenient(entryAsJson.get("chainId").textValue()) + .toUnsignedBigInteger(); + final Address authorizationAddress = + Address.fromHexString(entryAsJson.get("address").textValue()); + + final long authorizationNonce = + Bytes.fromHexStringLenient(entryAsJson.get("nonce").textValue()).toLong(); + + final byte authorizationV = + Bytes.fromHexStringLenient(entryAsJson.get("v").textValue()) + .toUnsignedBigInteger() + .byteValueExact(); + final BigInteger authorizationR = + Bytes.fromHexStringLenient(entryAsJson.get("r").textValue()) + .toUnsignedBigInteger(); + final BigInteger authorizationS = + Bytes.fromHexStringLenient(entryAsJson.get("s").textValue()) + .toUnsignedBigInteger(); + + final SECPSignature authorizationSignature = + new SECPSignature(authorizationR, authorizationS, authorizationV); + + authorizations.add( + new org.hyperledger.besu.ethereum.core.CodeDelegation( + authorizationChainId, + authorizationAddress, + authorizationNonce, + authorizationSignature)); + } + builder.codeDelegations(authorizations); + } + if (txNode.has("blobVersionedHashes")) { JsonNode blobVersionedHashes = txNode.get("blobVersionedHashes"); if (!blobVersionedHashes.isArray()) { @@ -209,14 +302,17 @@ protected static List extractTransactions( Bytes.fromHexStringLenient(txNode.get("s").textValue()) .toUnsignedBigInteger(), v.byteValueExact())); - transactions.add(builder.build()); + + final Transaction tx = builder.build(); + + transactions.add(tx); } } } else { out.printf("TX json node unparseable: %s%n", txNode); } - } catch (IllegalArgumentException iae) { - rejections.add(new RejectedTransaction(i, iae.getMessage())); + } catch (IllegalArgumentException | ArithmeticException e) { + rejections.add(new RejectedTransaction(i, e.getMessage())); } i++; } @@ -246,40 +342,60 @@ static T8nResult runTest( throw new UnsupportedForkException(fork); } - ProtocolSpec protocolSpec = - protocolSchedule.getByBlockHeader(BlockHeaderBuilder.createDefault().buildBlockHeader()); - final BlockHeader blockHeader = referenceTestEnv.updateFromParentValues(protocolSpec); + ProtocolSpec protocolSpec = protocolSchedule.getByBlockHeader(referenceTestEnv); + Blockchain blockchain = new T8nBlockchain(referenceTestEnv, protocolSpec); + final BlockHeader blockHeader = referenceTestEnv.parentBlockHeader(protocolSpec); final MainnetTransactionProcessor processor = protocolSpec.getTransactionProcessor(); - final WorldUpdater worldStateUpdater = worldState.updater(); - final ReferenceTestBlockchain blockchain = new ReferenceTestBlockchain(blockHeader.getNumber()); final Wei blobGasPrice = protocolSpec .getFeeMarket() - .blobGasPricePerGas(blockHeader.getExcessBlobGas().orElse(BlobGas.ZERO)); + .blobGasPricePerGas(calculateExcessBlobGasForParent(protocolSpec, blockHeader)); + long blobGasLimit = protocolSpec.getGasLimitCalculator().currentBlobGasLimit(); + + if (!referenceTestEnv.isStateTest()) { + protocolSpec + .getBlockHashProcessor() + .processBlockHashes(blockchain, worldState, referenceTestEnv); + } + final WorldUpdater rootWorldStateUpdater = worldState.updater(); List receipts = new ArrayList<>(); List invalidTransactions = new ArrayList<>(rejections); List validTransactions = new ArrayList<>(); ArrayNode receiptsArray = objectMapper.createArrayNode(); long gasUsed = 0; - for (int i = 0; i < transactions.size(); i++) { - Transaction transaction = transactions.get(i); - + long blobGasUsed = 0; + final WorldUpdater worldStateUpdater = rootWorldStateUpdater.updater(); + for (int transactionIndex = 0; transactionIndex < transactions.size(); transactionIndex++) { + worldStateUpdater.markTransactionBoundary(); + Transaction transaction = transactions.get(transactionIndex); final Stopwatch timer = Stopwatch.createStarted(); + + GasCalculator gasCalculator = protocolSpec.getGasCalculator(); + int blobCount = transaction.getBlobCount(); + blobGasUsed += gasCalculator.blobGasCost(blobCount); + if (blobGasUsed > blobGasLimit) { + invalidTransactions.add( + new RejectedTransaction( + transactionIndex, + String.format( + "blob gas (%d) would exceed block maximum %d", blobGasUsed, blobGasLimit))); + continue; + } final OperationTracer tracer; // You should have picked Mercy. final TransactionProcessingResult result; try { - tracer = tracerManager.getManagedTracer(i, transaction.getHash()); + tracer = tracerManager.getManagedTracer(transactionIndex, transaction.getHash()); + tracer.tracePrepareTransaction(worldStateUpdater, transaction); tracer.traceStartTransaction(worldStateUpdater, transaction); result = processor.processTransaction( - blockchain, worldStateUpdater, blockHeader, transaction, blockHeader.getCoinbase(), - blockNumber -> referenceTestEnv.getBlockhashByNumber(blockNumber).orElse(Hash.ZERO), + number -> referenceTestEnv.getBlockhashByNumber(number).orElse(Hash.ZERO), false, TransactionValidationParams.processingBlock(), tracer, @@ -291,93 +407,166 @@ static T8nResult runTest( timer.stop(); if (shouldClearEmptyAccounts(fork)) { - final Account coinbase = worldStateUpdater.getOrCreate(blockHeader.getCoinbase()); - if (coinbase != null && coinbase.isEmpty()) { - worldStateUpdater.deleteAccount(coinbase.getAddress()); - } - final Account txSender = worldStateUpdater.getAccount(transaction.getSender()); - if (txSender != null && txSender.isEmpty()) { - worldStateUpdater.deleteAccount(txSender.getAddress()); + var entries = new ArrayList<>(worldState.getAccumulator().getAccountsToUpdate().entrySet()); + for (var entry : entries) { + DiffBasedAccount updated = entry.getValue().getUpdated(); + if (updated != null && updated.isEmpty()) { + worldState.getAccumulator().deleteAccount(entry.getKey()); + } } } if (result.isInvalid()) { invalidTransactions.add( - new RejectedTransaction(i, result.getValidationResult().getErrorMessage())); + new RejectedTransaction( + transactionIndex, result.getValidationResult().getErrorMessage())); + continue; + } + validTransactions.add(transaction); + + long transactionGasUsed = transaction.getGasLimit() - result.getGasRemaining(); + + gasUsed += transactionGasUsed; + long intrinsicGas = + gasCalculator.transactionIntrinsicGasCost( + transaction.getPayload(), transaction.getTo().isEmpty()); + TransactionReceipt receipt = + protocolSpec + .getTransactionReceiptFactory() + .create(transaction.getType(), result, worldState, gasUsed); + tracer.traceEndTransaction( + worldStateUpdater, + transaction, + result.isSuccessful(), + result.getOutput(), + result.getLogs(), + gasUsed - intrinsicGas, + EMPTY_ADDRESS_SET, + timer.elapsed(TimeUnit.NANOSECONDS)); + Bytes gasUsedInTransaction = Bytes.ofUnsignedLong(transactionGasUsed); + receipts.add(receipt); + ObjectNode receiptObject = receiptsArray.addObject(); + receiptObject.put( + "root", receipt.getStateRoot() == null ? "0x" : receipt.getStateRoot().toHexString()); + int status = receipt.getStatus(); + receiptObject.put("status", "0x" + Math.max(status, 0)); + receiptObject.put("cumulativeGasUsed", Bytes.ofUnsignedLong(gasUsed).toQuantityHexString()); + receiptObject.put("logsBloom", receipt.getBloomFilter().toHexString()); + if (result.getLogs().isEmpty()) { + receiptObject.putNull("logs"); } else { - validTransactions.add(transaction); - - long transactionGasUsed = transaction.getGasLimit() - result.getGasRemaining(); - - gasUsed += transactionGasUsed; - long intrinsicGas = - protocolSpec - .getGasCalculator() - .transactionIntrinsicGasCost( - transaction.getPayload(), transaction.getTo().isEmpty()); - TransactionReceipt receipt = - protocolSpec - .getTransactionReceiptFactory() - .create(transaction.getType(), result, worldState, gasUsed); - tracer.traceEndTransaction( - worldStateUpdater, - transaction, - result.isSuccessful(), - result.getOutput(), - result.getLogs(), - gasUsed - intrinsicGas, - timer.elapsed(TimeUnit.NANOSECONDS)); - Bytes gasUsedInTransaction = Bytes.ofUnsignedLong(transactionGasUsed); - receipts.add(receipt); - ObjectNode receiptObject = receiptsArray.addObject(); - receiptObject.put( - "root", receipt.getStateRoot() == null ? "0x" : receipt.getStateRoot().toHexString()); - receiptObject.put("status", "0x" + receipt.getStatus()); - receiptObject.put("cumulativeGasUsed", Bytes.ofUnsignedLong(gasUsed).toQuantityHexString()); - receiptObject.put("logsBloom", receipt.getBloomFilter().toHexString()); - if (result.getLogs().isEmpty()) { - receiptObject.putNull("logs"); - } else { - ArrayNode logsArray = receiptObject.putArray("logs"); - for (Log log : result.getLogs()) { - logsArray.addPOJO(log); - } + ArrayNode logsArray = receiptObject.putArray("logs"); + List logs = result.getLogs(); + for (int logIndex = 0; logIndex < logs.size(); logIndex++) { + Log log = logs.get(logIndex); + var obj = logsArray.addObject(); + obj.put("address", log.getLogger().toHexString()); + var topics = obj.putArray("topics"); + log.getTopics().forEach(topic -> topics.add(topic.toHexString())); + obj.put("data", log.getData().toHexString()); + obj.put("blockNumber", blockHeader.getNumber()); + obj.put("transactionHash", transaction.getHash().toHexString()); + obj.put("transactionIndex", String.format("0x%x", transactionIndex)); + obj.put("blockHash", blockHeader.getHash().toHexString()); + obj.put("logIndex", String.format("0x%x", logIndex)); + obj.put("removed", "false"); } - receiptObject.put("transactionHash", transaction.getHash().toHexString()); - receiptObject.put( - "contractAddress", transaction.contractAddress().orElse(Address.ZERO).toHexString()); - receiptObject.put("gasUsed", gasUsedInTransaction.toQuantityHexString()); - receiptObject.put("blockHash", Hash.ZERO.toHexString()); - receiptObject.put("transactionIndex", Bytes.ofUnsignedLong(i).toQuantityHexString()); } + receiptObject.put("transactionHash", transaction.getHash().toHexString()); + receiptObject.put( + "contractAddress", transaction.contractAddress().orElse(Address.ZERO).toHexString()); + receiptObject.put("gasUsed", gasUsedInTransaction.toQuantityHexString()); + receiptObject.put("blockHash", Hash.ZERO.toHexString()); + receiptObject.put( + "transactionIndex", Bytes.ofUnsignedLong(transactionIndex).toQuantityHexString()); + worldStateUpdater.commit(); } final ObjectNode resultObject = objectMapper.createObjectNode(); // block reward // The max production reward was 5 Eth, longs can hold over 18 Eth. - if (!validTransactions.isEmpty() && (rewardString == null || Long.decode(rewardString) > 0)) { + if (!referenceTestEnv.isStateTest() + && !validTransactions.isEmpty() + && (rewardString == null || Long.decode(rewardString) > 0)) { Wei reward = (rewardString == null) ? protocolSpec.getBlockReward() : Wei.of(Long.decode(rewardString)); - worldStateUpdater + rootWorldStateUpdater .getOrCreateSenderAccount(blockHeader.getCoinbase()) .incrementBalance(reward); } - // Invoke the withdrawal processor to handle CL withdrawals. - if (!referenceTestEnv.getWithdrawals().isEmpty()) { - try { - protocolSpec - .getWithdrawalsProcessor() - .ifPresent( - p -> p.processWithdrawals(referenceTestEnv.getWithdrawals(), worldStateUpdater)); - } catch (RuntimeException re) { - resultObject.put("exception", re.getMessage()); + rootWorldStateUpdater.commit(); + + if (referenceTestEnv.isStateTest()) { + if (!referenceTestEnv.getWithdrawals().isEmpty()) { + resultObject.put("exception", "withdrawals are not supported in state tests"); + } + } else { + // Invoke the withdrawal processor to handle CL withdrawals. + if (!referenceTestEnv.getWithdrawals().isEmpty()) { + try { + protocolSpec + .getWithdrawalsProcessor() + .ifPresent( + p -> + p.processWithdrawals( + referenceTestEnv.getWithdrawals(), worldState.updater())); + } catch (RuntimeException re) { + resultObject.put("exception", re.getMessage()); + } } } - worldStateUpdater.commit(); + var requestProcessorCoordinator = protocolSpec.getRequestProcessorCoordinator(); + if (requestProcessorCoordinator.isPresent()) { + var rpc = requestProcessorCoordinator.get(); + ProcessRequestContext context = + new ProcessRequestContext( + blockHeader, + worldState, + protocolSpec, + receipts, + new CachingBlockHashLookup(blockHeader, blockchain), + OperationTracer.NO_TRACING); + Optional> maybeRequests = rpc.process(context); + Hash requestRoot = BodyValidation.requestsRoot(maybeRequests.orElse(List.of())); + + resultObject.put("requestsRoot", requestRoot.toHexString()); + var deposits = resultObject.putArray("depositRequests"); + RequestUtil.filterRequestsOfType(maybeRequests.orElse(List.of()), DepositRequest.class) + .forEach( + deposit -> { + var obj = deposits.addObject(); + obj.put("pubkey", deposit.getPubkey().toHexString()); + obj.put("withdrawalCredentials", deposit.getWithdrawalCredentials().toHexString()); + obj.put("amount", deposit.getAmount().toHexString()); + obj.put("signature", deposit.getSignature().toHexString()); + obj.put("index", deposit.getIndex().toHexString()); + }); + + var withdrawalRequests = resultObject.putArray("withdrawalRequests"); + RequestUtil.filterRequestsOfType(maybeRequests.orElse(List.of()), WithdrawalRequest.class) + .forEach( + wr -> { + var obj = withdrawalRequests.addObject(); + obj.put("sourceAddress", wr.getSourceAddress().toHexString()); + obj.put("validatorPubkey", wr.getValidatorPubkey().toHexString()); + obj.put("amount", wr.getAmount().toHexString()); + }); + + var consolidationRequests = resultObject.putArray("consolidationRequests"); + RequestUtil.filterRequestsOfType(maybeRequests.orElse(List.of()), ConsolidationRequest.class) + .forEach( + cr -> { + var obj = consolidationRequests.addObject(); + obj.put("sourceAddress", cr.getSourceAddress().toHexString()); + obj.put("sourcePubkey", cr.getSourcePubkey().toHexString()); + obj.put("targetPubkey", cr.getTargetPubkey().toHexString()); + }); + } + worldState.persist(blockHeader); resultObject.put("stateRoot", worldState.rootHash().toHexString()); @@ -410,44 +599,47 @@ static T8nResult runTest( blockHeader .getWithdrawalsRoot() .ifPresent(wr -> resultObject.put("withdrawalsRoot", wr.toHexString())); - blockHeader - .getBlobGasUsed() - .ifPresentOrElse( - bgu -> resultObject.put("blobGasUsed", Bytes.ofUnsignedLong(bgu).toQuantityHexString()), - () -> - blockHeader - .getExcessBlobGas() - .ifPresent(ebg -> resultObject.put("blobGasUsed", "0x0"))); - blockHeader - .getExcessBlobGas() - .ifPresent(ebg -> resultObject.put("currentExcessBlobGas", ebg.toShortHexString())); + var maybeExcessBlobGas = blockHeader.getExcessBlobGas(); + if (maybeExcessBlobGas.isPresent()) { + resultObject.put( + "currentExcessBlobGas", + calculateExcessBlobGasForParent(protocolSpec, blockHeader) + .toBytes() + .toQuantityHexString()); + resultObject.put("blobGasUsed", Bytes.ofUnsignedLong(blobGasUsed).toQuantityHexString()); + } ObjectNode allocObject = objectMapper.createObjectNode(); worldState .streamAccounts(Bytes32.ZERO, Integer.MAX_VALUE) .sorted(Comparator.comparing(o -> o.getAddress().get().toHexString())) .forEach( - account -> { - ObjectNode accountObject = - allocObject.putObject( - account.getAddress().map(Address::toHexString).orElse("0x")); + a -> { + Account account = worldState.get(a.getAddress().get()); + ObjectNode accountObject = allocObject.putObject(account.getAddress().toHexString()); if (account.getCode() != null && !account.getCode().isEmpty()) { accountObject.put("code", account.getCode().toHexString()); } - NavigableMap storageEntries = - account.storageEntriesFrom(Bytes32.ZERO, Integer.MAX_VALUE); + List> storageEntries = + account.storageEntriesFrom(Bytes32.ZERO, Integer.MAX_VALUE).values().stream() + .map( + e -> + Map.entry( + e.getKey().get(), + account.getStorageValue(UInt256.fromBytes(e.getKey().get())))) + .filter(e -> !e.getValue().isZero()) + .sorted(Map.Entry.comparingByKey()) + .toList(); if (!storageEntries.isEmpty()) { ObjectNode storageObject = accountObject.putObject("storage"); - storageEntries.values().stream() - .sorted(Comparator.comparing(a -> a.getKey().get())) - .forEach( - accountStorageEntry -> - storageObject.put( - accountStorageEntry.getKey().map(UInt256::toHexString).orElse("0x"), - accountStorageEntry.getValue().toHexString())); + storageEntries.forEach( + accountStorageEntry -> + storageObject.put( + accountStorageEntry.getKey().toHexString(), + accountStorageEntry.getValue().toHexString())); } accountObject.put("balance", account.getBalance().toShortHexString()); - if (account.getNonce() > 0) { + if (account.getNonce() != 0) { accountObject.put( "nonce", Bytes.ofUnsignedLong(account.getNonce()).toShortHexString()); } @@ -459,12 +651,45 @@ static T8nResult runTest( return new T8nResult(allocObject, bodyBytes, resultObject); } - interface TracerManager { + /** + * The TracerManager interface provides methods for managing OperationTracer instances. It is used + * in the context of Ethereum Virtual Machine (EVM) execution to trace operations. + * + *

The interface defines two methods: - getManagedTracer: This method is used to get a managed + * OperationTracer instance for a specific transaction. - disposeTracer: This method is used to + * dispose of an OperationTracer instance when it is no longer needed. + */ + public interface TracerManager { + + /** + * Retrieves a managed OperationTracer instance for a specific transaction. + * + * @param txIndex The index of the transaction for which the tracer is to be retrieved. + * @param txHash The hash of the transaction for which the tracer is to be retrieved. + * @return The managed OperationTracer instance. + * @throws Exception If an error occurs while retrieving the tracer. + */ OperationTracer getManagedTracer(int txIndex, Hash txHash) throws Exception; + /** + * Disposes of an OperationTracer instance when it is no longer needed. + * + * @param tracer The OperationTracer instance to be disposed. + * @throws IOException If an error occurs while disposing the tracer. + */ void disposeTracer(OperationTracer tracer) throws IOException; } + /** + * A record that represents the result of a transaction test run in the Ethereum Virtual Machine + * (EVM). It contains the final state of the accounts (allocObject), the raw bytes of the + * transactions (bodyBytes), and the result of the test run (resultObject). + * + * @param allocObject The final state of the accounts after the test run. + * @param bodyBytes The raw bytes of the transactions that were run. + * @param resultObject The result of the test run, including stateRoot, txRoot, receiptsRoot, + * logsHash, and other details. + */ @SuppressWarnings("unused") record T8nResult(ObjectNode allocObject, TextNode bodyBytes, ObjectNode resultObject) {} } diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nServerSubCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nServerSubCommand.java index a0ef6384b08..ffa6e67b72f 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nServerSubCommand.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nServerSubCommand.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evmtool; @@ -25,14 +24,19 @@ import org.hyperledger.besu.evm.EvmSpecVersion; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.tracing.OperationTracer; +import org.hyperledger.besu.evm.tracing.StandardJsonTracer; import org.hyperledger.besu.evmtool.T8nExecutor.RejectedTransaction; import org.hyperledger.besu.util.LogConfigurator; import java.io.ByteArrayOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; import java.io.PrintStream; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -49,7 +53,22 @@ import io.vertx.core.http.HttpServerOptions; import io.vertx.core.http.HttpServerRequest; import picocli.CommandLine; +import picocli.CommandLine.ParentCommand; +/** + * The T8nServerSubCommand class is responsible for running an Ethereum State Test server. It reads + * the initial state, transactions, and environment from input files or stdin, executes the + * transactions in the Ethereum Virtual Machine (EVM), and writes the final state, transaction + * results, and traces to output files or stdout. + * + *

The class uses the Vert.x library for handling HTTP requests and the picocli library for + * command line argument parsing. It includes options for specifying the host and port to bind to, + * and the base directory for output. + * + *

The class also includes a TracerManager for managing OperationTracer instances, which are used + * to trace EVM operations when the --json flag is specified. + */ +@SuppressWarnings("java:S106") // using standard output is the point of this class @CommandLine.Command( name = "t8n-server", description = "Run Ethereum State Test server", @@ -66,6 +85,28 @@ public class T8nServerSubCommand implements Runnable { description = "Port to bind to") private int port = 3000; + @CommandLine.Option( + names = {"--output.basedir"}, + paramLabel = "full path", + description = "The output ") + private final Path outDir = Path.of("."); + + @ParentCommand private final EvmToolCommand parentCommand; + + /** + * Default constructor for the T8nServerSubCommand class. This constructor is required by PicoCLI + * and assigns null to parentCommand. + */ + @SuppressWarnings("unused") + public T8nServerSubCommand() { + // PicoCLI requires this + this(null); + } + + T8nServerSubCommand(final EvmToolCommand parentCommand) { + this.parentCommand = parentCommand; + } + @Override public void run() { LogConfigurator.setLevel("", "OFF"); @@ -144,6 +185,57 @@ void handleT8nRequest( } } + T8nExecutor.TracerManager tracerManager; + if (parentCommand.showJsonResults) { + tracerManager = + new T8nExecutor.TracerManager() { + private final Map outputStreams = + new HashMap<>(); + + @Override + public OperationTracer getManagedTracer(final int txIndex, final Hash txHash) + throws Exception { + outDir.toFile().mkdirs(); + var traceDest = + new FileOutputStream( + outDir + .resolve( + String.format("trace-%d-%s.jsonl", txIndex, txHash.toHexString())) + .toFile()); + + var jsonTracer = + new StandardJsonTracer( + new PrintStream(traceDest), + parentCommand.showMemory, + !parentCommand.hideStack, + parentCommand.showReturnData, + parentCommand.showStorage); + outputStreams.put(jsonTracer, traceDest); + return jsonTracer; + } + + @Override + public void disposeTracer(final OperationTracer tracer) throws IOException { + if (outputStreams.containsKey(tracer)) { + outputStreams.remove(tracer).close(); + } + } + }; + } else { + tracerManager = + new T8nExecutor.TracerManager() { + @Override + public OperationTracer getManagedTracer(final int txIndex, final Hash txHash) { + return OperationTracer.NO_TRACING; + } + + @Override + public void disposeTracer(final OperationTracer tracer) { + // single-test mode doesn't need to track tracers + } + }; + } + result = T8nExecutor.runTest( chainId, @@ -154,17 +246,7 @@ void handleT8nRequest( initialWorldState, transactions, rejections, - new T8nExecutor.TracerManager() { - @Override - public OperationTracer getManagedTracer(final int txIndex, final Hash txHash) { - return OperationTracer.NO_TRACING; - } - - @Override - public void disposeTracer(final OperationTracer tracer) { - // No output streams to dispose of - } - }); + tracerManager); } ObjectNode outputObject = objectMapper.createObjectNode(); @@ -172,15 +254,11 @@ public void disposeTracer(final OperationTracer tracer) { outputObject.set("body", result.bodyBytes()); outputObject.set("result", result.resultObject()); - try { - String response = - objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(outputObject); - req.response().setChunked(true); - req.response().putHeader("Content-Type", "application/json").send(response); - } catch (JsonProcessingException e) { - req.response().setStatusCode(500).end(e.getMessage()); - } - } catch (Throwable t) { + String response = + objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(outputObject); + req.response().setChunked(true); + req.response().putHeader("Content-Type", "application/json").send(response); + } catch (Exception t) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(baos, true, StandardCharsets.UTF_8); t.printStackTrace(ps); diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nSubCommand.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nSubCommand.java index 40f7bb07987..d6db1a1e8d2 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nSubCommand.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/T8nSubCommand.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.evmtool; import static org.hyperledger.besu.evmtool.T8nSubCommand.COMMAND_ALIAS; @@ -61,6 +59,19 @@ import picocli.CommandLine.Parameters; import picocli.CommandLine.ParentCommand; +/** + * The T8nSubCommand class is responsible for executing an Ethereum State Test. It reads the initial + * state, transactions, and environment from input files or stdin, executes the transactions in the + * Ethereum Virtual Machine (EVM), and writes the final state, transaction results, and traces to + * output files or stdout. + * + *

The class uses the picocli library for command line argument parsing and includes options for + * specifying the input and output files, the fork to run the transition against, the chain ID, and + * the block reward. + * + *

The class also includes a TracerManager for managing OperationTracer instances, which are used + * to trace EVM operations when the --json flag is specified. + */ @Command( name = COMMAND_NAME, aliases = COMMAND_ALIAS, @@ -156,12 +167,22 @@ public void consumeParameters( } } + /** + * Default constructor for the T8nSubCommand class. This constructor is required by PicoCLI and + * assigns parent command to 'null'. + */ @SuppressWarnings("unused") public T8nSubCommand() { // PicoCLI requires this parentCommand = null; } + /** + * Constructor for the T8nSubCommand class with a parent command. This constructor is required by + * PicoCLI. + * + * @param parentCommand The parent command for this sub command. + */ @SuppressWarnings("unused") public T8nSubCommand(final EvmToolCommand parentCommand) { // PicoCLI requires this too diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/VersionProvider.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/VersionProvider.java index f20e8d21087..6f3ff330717 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/VersionProvider.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/VersionProvider.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evmtool; @@ -19,8 +18,26 @@ import picocli.CommandLine; +/** + * The VersionProvider class is responsible for providing the version of the Hyperledger Besu EVM + * tool. It implements the IVersionProvider interface from the picocli library. + * + *

The getVersion method returns a string array containing the version of the Hyperledger Besu + * EVM tool. + */ public class VersionProvider implements CommandLine.IVersionProvider { + /** + * Default constructor for the VersionProvider class. This constructor does not perform any + * operations. + */ + public VersionProvider() {} + + /** + * This method returns the version of the Hyperledger Besu EVM tool. + * + * @return A string array containing the version of the Hyperledger Besu EVM tool. + */ @Override public String[] getVersion() { return new String[] {"Hyperledger Besu evm " + BesuInfo.shortVersion()}; diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/benchmarks/AltBN128Benchmark.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/benchmarks/AltBN128Benchmark.java index 1145cfbf1af..c8c82cb6faa 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/benchmarks/AltBN128Benchmark.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/benchmarks/AltBN128Benchmark.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/benchmarks/BenchmarkExecutor.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/benchmarks/BenchmarkExecutor.java index c552512cb7d..082fae7f69d 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/benchmarks/BenchmarkExecutor.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/benchmarks/BenchmarkExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -31,10 +31,13 @@ import org.hyperledger.besu.evm.gascalculator.IstanbulGasCalculator; import org.hyperledger.besu.evm.gascalculator.LondonGasCalculator; import org.hyperledger.besu.evm.gascalculator.PetersburgGasCalculator; +import org.hyperledger.besu.evm.gascalculator.PragueEOFGasCalculator; +import org.hyperledger.besu.evm.gascalculator.PragueGasCalculator; import org.hyperledger.besu.evm.gascalculator.ShanghaiGasCalculator; import org.hyperledger.besu.evm.precompile.PrecompiledContract; import java.io.PrintStream; +import java.util.Locale; import java.util.concurrent.TimeUnit; import com.google.common.base.Stopwatch; @@ -110,12 +113,12 @@ protected double runPrecompileBenchmark(final Bytes arg, final PrecompiledContra } timer.stop(); - if (executions < 1) { + if (executions > 0) { + final double elapsed = timer.elapsed(TimeUnit.NANOSECONDS) / 1.0e9D; + return elapsed / executions; + } else { return Double.NaN; } - - final double elapsed = timer.elapsed(TimeUnit.NANOSECONDS) / 1.0e9D; - return elapsed / executions; } /** @@ -126,9 +129,11 @@ protected double runPrecompileBenchmark(final Bytes arg, final PrecompiledContra * @return a gas calculator */ public static GasCalculator gasCalculatorForFork(final String fork) { - return switch (EvmSpecVersion.valueOf(fork.toUpperCase())) { + return switch (EvmSpecVersion.valueOf(fork.toUpperCase(Locale.ROOT))) { case HOMESTEAD -> new HomesteadGasCalculator(); case FRONTIER -> new FrontierGasCalculator(); + case TANGERINE_WHISTLE -> null; + case SPURIOUS_DRAGON -> null; case BYZANTIUM -> new ByzantiumGasCalculator(); case CONSTANTINOPLE -> new ConstantinopleGasCalculator(); case PETERSBURG -> new PetersburgGasCalculator(); @@ -136,14 +141,25 @@ public static GasCalculator gasCalculatorForFork(final String fork) { case BERLIN -> new BerlinGasCalculator(); case LONDON, PARIS -> new LondonGasCalculator(); case SHANGHAI -> new ShanghaiGasCalculator(); - default -> new CancunGasCalculator(); + case CANCUN -> new CancunGasCalculator(); + case PRAGUE -> new PragueGasCalculator(); + case CANCUN_EOF, + PRAGUE_EOF, + OSAKA, + AMSTERDAM, + BOGOTA, + POLIS, + BANGKOK, + FUTURE_EIPS, + EXPERIMENTAL_EIPS -> + new PragueEOFGasCalculator(); }; } /** * Run the benchmarks * - * @param output stream to print results to (typicall System.out) + * @param output stream to print results to (typically System.out) * @param attemptNative Should the benchmark attempt to us native libraries? (null use the * default, false disabled, true enabled) * @param fork the fork name to run the benchmark against. diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/benchmarks/ECRecoverBenchmark.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/benchmarks/ECRecoverBenchmark.java index a2cca4c5846..5f44c6ff660 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/benchmarks/ECRecoverBenchmark.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/benchmarks/ECRecoverBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/benchmarks/ModExpBenchmark.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/benchmarks/ModExpBenchmark.java index 452fd407626..38c6171fea9 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/benchmarks/ModExpBenchmark.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/benchmarks/ModExpBenchmark.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/benchmarks/Secp256k1Benchmark.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/benchmarks/Secp256k1Benchmark.java index fad5d5013c8..57bd39a931a 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/benchmarks/Secp256k1Benchmark.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/benchmarks/Secp256k1Benchmark.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/exception/UnsupportedForkException.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/exception/UnsupportedForkException.java index 284fe890909..96209689854 100644 --- a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/exception/UnsupportedForkException.java +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/exception/UnsupportedForkException.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evmtool.exception; diff --git a/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/t8n/T8nBlockchain.java b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/t8n/T8nBlockchain.java new file mode 100644 index 00000000000..c5905967fc2 --- /dev/null +++ b/ethereum/evmtool/src/main/java/org/hyperledger/besu/evmtool/t8n/T8nBlockchain.java @@ -0,0 +1,211 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evmtool.t8n; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.chain.BlockAddedObserver; +import org.hyperledger.besu.ethereum.chain.Blockchain; +import org.hyperledger.besu.ethereum.chain.ChainHead; +import org.hyperledger.besu.ethereum.chain.ChainReorgObserver; +import org.hyperledger.besu.ethereum.chain.TransactionLocation; +import org.hyperledger.besu.ethereum.core.BlockBody; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; +import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.TransactionReceipt; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.referencetests.ReferenceTestEnv; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * A blockchain mock for the Ethereum reference tests. + * + *

Operations which would lead to non-deterministic behaviour if executed while processing + * transactions throw {@link NonDeterministicOperationException}. For example all methods that + * lookup blocks by number since the block being processed may not be on the canonical chain but + * that must not affect the execution of its transactions. + * + *

The Ethereum reference tests for VM execution (VMTests) and transaction processing + * (GeneralStateTests) require a block's hash to be to be the hash of the string of it's block + * number. + */ +public class T8nBlockchain implements Blockchain { + + // Maximum number of blocks prior to the chain head that can be retrieved by hash. + private static final String NUMBER_LOOKUP_ERROR = + "Blocks must not be looked up by number in the EVM. The block being processed may not be on the canonical chain."; + private static final String CHAIN_HEAD_ERROR = + "Chain head is inherently non-deterministic. The block currently being processed should be treated as the chain head."; + private static final String FINALIZED_ERROR = + "Finalized block is inherently non-deterministic. The block currently being processed should be treated as the finalized block."; + private static final String SAFE_BLOCK_ERROR = + "Safe block is inherently non-deterministic. The block currently being processed should be treated as the safe block."; + private final Map hashToHeader = new HashMap<>(); + private final BlockHeader chainHeader; + + /** + * Create a new blockchain object for T8n testing. + * + * @param referenceTestEnv the referenfe test environment, containing most of the block header + * stuff + * @param protocolSpec the protocol spec, which impacts what block header fields are implemente. + */ + public T8nBlockchain(final ReferenceTestEnv referenceTestEnv, final ProtocolSpec protocolSpec) { + + Map blockHashes = referenceTestEnv.getBlockHashes(); + + chainHeader = referenceTestEnv.parentBlockHeader(protocolSpec); + + blockHashes.forEach( + (num, blockHash) -> + hashToHeader.put( + blockHash, + BlockHeaderBuilder.createDefault() + .number(num) + .parentHash(blockHashes.getOrDefault(num - 1, Hash.ZERO)) + .timestamp(0) + .coinbase(chainHeader.getCoinbase()) + .difficulty(chainHeader.getDifficulty()) + .gasLimit(chainHeader.getGasLimit()) + .buildBlockHeader())); + hashToHeader.put(referenceTestEnv.getParentHash(), chainHeader); + } + + @Override + public Optional getBlockHashByNumber(final long number) { + throw new NonDeterministicOperationException(NUMBER_LOOKUP_ERROR); + } + + @Override + public ChainHead getChainHead() { + return new ChainHead(chainHeader, chainHeader.getDifficulty(), chainHeader.getNumber()); + } + + @Override + public Optional getFinalized() { + throw new NonDeterministicOperationException(FINALIZED_ERROR); + } + + @Override + public Optional getSafeBlock() { + throw new NonDeterministicOperationException(SAFE_BLOCK_ERROR); + } + + @Override + public long getChainHeadBlockNumber() { + throw new NonDeterministicOperationException(CHAIN_HEAD_ERROR); + } + + @Override + public Hash getChainHeadHash() { + return chainHeader.getHash(); + } + + @Override + public Optional getTransactionLocation(final Hash transactionHash) { + throw new NonDeterministicOperationException("Transaction location may be different on forks"); + } + + @Override + public Optional getBlockHeader(final long blockNumber) { + throw new NonDeterministicOperationException(NUMBER_LOOKUP_ERROR); + } + + @Override + public Optional getBlockHeader(final Hash blockHeaderHash) { + return Optional.ofNullable(hashToHeader.get(blockHeaderHash)); + } + + @Override + public synchronized Optional getBlockHeaderSafe(final Hash blockHeaderHash) { + return Optional.ofNullable(hashToHeader.get(blockHeaderHash)); + } + + @Override + public Optional getBlockBody(final Hash blockHeaderHash) { + // Deterministic, but just not implemented. + throw new UnsupportedOperationException(); + } + + @Override + public synchronized Optional getBlockBodySafe(final Hash blockHeaderHash) { + return getBlockBody(blockHeaderHash); + } + + @Override + public Optional> getTxReceipts(final Hash blockHeaderHash) { + // Deterministic, but just not implemented. + throw new UnsupportedOperationException(); + } + + @Override + public Optional getTotalDifficultyByHash(final Hash blockHeaderHash) { + // Deterministic, but just not implemented. + throw new UnsupportedOperationException(); + } + + @Override + public Optional getTransactionByHash(final Hash transactionHash) { + throw new NonDeterministicOperationException( + "Which transactions are on the chain may vary on different forks"); + } + + @Override + public long observeBlockAdded(final BlockAddedObserver observer) { + throw new NonDeterministicOperationException("Listening for new blocks is not deterministic"); + } + + @Override + public boolean removeObserver(final long observerId) { + throw new NonDeterministicOperationException("Listening for new blocks is not deterministic"); + } + + @Override + public long observeChainReorg(final ChainReorgObserver observer) { + throw new NonDeterministicOperationException("Listening for chain reorg is not deterministic"); + } + + @Override + public boolean removeChainReorgObserver(final long observerId) { + throw new NonDeterministicOperationException("Listening for chain reorg is not deterministic"); + } + + /** An exception thrown for methods not supported by the T8nBlockchain. */ + public static class NonDeterministicOperationException extends RuntimeException { + NonDeterministicOperationException(final String message) { + super(message); + } + } + + @Override + @SuppressWarnings("unused") + public Comparator getBlockChoiceRule() { + return (a, b) -> { + throw new NonDeterministicOperationException( + "ReferenceTestBlockchain for VMTest Chains do not support fork choice rules"); + }; + } + + @Override + public void setBlockChoiceRule(final Comparator blockChoiceRule) { + throw new UnsupportedOperationException("Not Used for Reference Tests"); + } +} diff --git a/ethereum/evmtool/src/main/resources/log4j2.xml b/ethereum/evmtool/src/main/resources/log4j2.xml index 0d3a7de136c..8e70c35eea9 100644 --- a/ethereum/evmtool/src/main/resources/log4j2.xml +++ b/ethereum/evmtool/src/main/resources/log4j2.xml @@ -6,7 +6,7 @@ - + diff --git a/ethereum/evmtool/src/test/benchmarks/bench-caliper.sh b/ethereum/evmtool/src/test/benchmarks/bench-caliper.sh index b5793946555..48d628aae26 100755 --- a/ethereum/evmtool/src/test/benchmarks/bench-caliper.sh +++ b/ethereum/evmtool/src/test/benchmarks/bench-caliper.sh @@ -1,3 +1,19 @@ +#!/usr/bin/env sh +## +## Copyright contributors to Hyperledger Besu. +## +## Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +## an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +## specific language governing permissions and limitations under the License. +## +## SPDX-License-Identifier: Apache-2.0 +## + # open ../../../build/install/evmtool/bin/evm \ --code=0x608060405234801561001057600080fd5b50600436106100415760003560e01c80631de45b10146100465780637c261929146101a25780639064129314610271575b600080fd5b6101a06004803603606081101561005c57600080fd5b810190808035906020019064010000000081111561007957600080fd5b82018360208201111561008b57600080fd5b803590602001918460018302840111640100000000831117156100ad57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192908035906020019064010000000081111561011057600080fd5b82018360208201111561012257600080fd5b8035906020019184600183028401116401000000008311171561014457600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190929190505050610336565b005b61025b600480360360208110156101b857600080fd5b81019080803590602001906401000000008111156101d557600080fd5b8201836020820111156101e757600080fd5b8035906020019184600183028401116401000000008311171561020957600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610429565b6040518082815260200191505060405180910390f35b6103346004803603604081101561028757600080fd5b81019080803590602001906401000000008111156102a457600080fd5b8201836020820111156102b657600080fd5b803590602001918460018302840111640100000000831117156102d857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192908035906020019092919050505061049b565b005b806000846040518082805190602001908083835b6020831061036d578051825260208201915060208101905060208303925061034a565b6001836020036101000a038019825116818451168082178552505050505050905001915050908152602001604051809103902060008282540392505081905550806000836040518082805190602001908083835b602083106103e457805182526020820191506020810190506020830392506103c1565b6001836020036101000a038019825116818451168082178552505050505050905001915050908152602001604051809103902060008282540192505081905550505050565b600080826040518082805190602001908083835b60208310610460578051825260208201915060208101905060208303925061043d565b6001836020036101000a0380198251168184511680821785525050505050509050019150509081526020016040518091039020549050919050565b806000836040518082805190602001908083835b602083106104d257805182526020820191506020810190506020830392506104af565b6001836020036101000a038019825116818451168082178552505050505050905001915050908152602001604051809103902081905550505056fea265627a7a72315820f89fa48c6c4d5d7df165b5e3dba04cbe258ce57a3a4ef3bab1377546b84b699f64736f6c634300050b0032 \ diff --git a/ethereum/evmtool/src/test/benchmarks/bench-loop.sh b/ethereum/evmtool/src/test/benchmarks/bench-loop.sh index 8300b876455..e3f747530fc 100755 --- a/ethereum/evmtool/src/test/benchmarks/bench-loop.sh +++ b/ethereum/evmtool/src/test/benchmarks/bench-loop.sh @@ -1,3 +1,19 @@ +#!/usr/bin/env sh +## +## Copyright contributors to Hyperledger Besu. +## +## Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +## an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +## specific language governing permissions and limitations under the License. +## +## SPDX-License-Identifier: Apache-2.0 +## + # divadd ../../../build/install/evmtool/bin/evm \ --code=0x606060405263ffffffff60e060020a60003504166315d42327811461004257806359e3e1ea14610070578063c4f8b9fb1461009e578063e01330bb146100c9575bfe5b341561004a57fe5b61005e6004356024356044356064356100f4565b60408051918252519081900360200190f35b341561007857fe5b61005e60043560243560443560643561011e565b60408051918252519081900360200190f35b34156100a657fe5b61005e600435602435604435610152565b60408051918252519081900360200190f35b34156100d157fe5b61005e600435602435604435610179565b60408051918252519081900360200190f35b600084815b83811015610110578486830991505b6001016100f9565b8192505b5050949350505050565b600084815b8381101561011057858281151561013657fe5b04850191505b600101610123565b8192505b5050949350505050565b600083815b8381101561016c57908401905b600101610157565b8192505b50509392505050565b600083815b8381101561016c57908402905b60010161017e565b8192505b505093925050505600a165627a7a72305820065081bd1e9fdffccd251332523241eaabd0fb1881a06529599a9c67d0a568e50029 \ @@ -17,4 +33,3 @@ --genesis=evmtool-genesis.json \ --gas 1000000000 \ --repeat=10 - diff --git a/ethereum/evmtool/src/test/benchmarks/bench-mainnet.sh b/ethereum/evmtool/src/test/benchmarks/bench-mainnet.sh index 3300df7178b..084799e96a4 100755 --- a/ethereum/evmtool/src/test/benchmarks/bench-mainnet.sh +++ b/ethereum/evmtool/src/test/benchmarks/bench-mainnet.sh @@ -1,3 +1,19 @@ +#!/usr/bin/env sh +## +## Copyright contributors to Hyperledger Besu. +## +## Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +## an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +## specific language governing permissions and limitations under the License. +## +## SPDX-License-Identifier: Apache-2.0 +## + # mainnet SLOAD heavy ../../../build/install/evmtool/bin/evm \ --code=6060604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063260a2da6146100b15780634ea90317146100ea578063502a86301461012357806355a5194b146102115780636c4d727c146102555780636e11957014610298578063736d8b9c146102d15780637592a81d14610309578063786454ae14610351578063b1fa6a391461038a578063dd0c8643146103db575b005b34156100bc57600080fd5b6100e8600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506103f0565b005b34156100f557600080fd5b610121600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610492565b005b341561012e57600080fd5b610136610534565b604051808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200194505050505060405180910390f35b341561021c57600080fd5b610253600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080351515906020019091905050610632565b005b341561026057600080fd5b61027660048080359060200190919050506106ea565b604051808263ffffffff1663ffffffff16815260200191505060405180910390f35b34156102a357600080fd5b6102cf600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610717565b005b34156102dc57600080fd5b610307600480803563ffffffff1690602001909190803563ffffffff169060200190919050506107b9565b005b341561031457600080fd5b61034f600480803563ffffffff1690602001909190803573ffffffffffffffffffffffffffffffffffffffff1690602001909190505061098c565b005b341561035c57600080fd5b610388600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190505061167e565b005b341561039557600080fd5b6103c1600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050611720565b604051808215151515815260200191505060405180910390f35b34156103e657600080fd5b6103ee6117d3565b005b600115156000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16151514151561044e57600080fd5b80600460006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600115156000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff1615151415156104f057600080fd5b80600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600080600080600115156000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16151514151561059857600080fd5b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600460009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16935093509350935090919293565b600115156000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16151514151561069057600080fd5b806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548160ff0219169083151502179055505050565b60006007600083815260200190815260200160002060009054906101000a900463ffffffff169050919050565b600115156000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16151514151561077557600080fd5b80600360006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600080600080600115156000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16151514151561081d57600080fd5b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1693508590505b8463ffffffff168163ffffffff161015610984578373ffffffffffffffffffffffffffffffffffffffff166352898b1282600060405160e001526040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808263ffffffff1663ffffffff16815260200191505060e060405180830381600087803b15156108dd57600080fd5b6102c65a03f115156108ee57600080fd5b505050604051805190602001805190602001805190602001805190602001805190602001805190602001805190509091929394955090919293509091925090915050809350819450505060048260ff16101561097757806007600085815260200190815260200160002060006101000a81548163ffffffff021916908363ffffffff1602179055505b8080600101915050610846565b505050505050565b6000806000806000806000600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169250600460009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169150600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050671bc16d674ec80000600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16311115611063578273ffffffffffffffffffffffffffffffffffffffff166321555d82896000604051608001526040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050608060405180830381600087803b1515610af257600080fd5b6102c65a03f11515610b0357600080fd5b505050604051805190602001805190602001805190602001805190509091509050508096505060008663ffffffff16111515610b3e57600080fd5b88620186a087020163ffffffff1696506007600088815260200190815260200160002060009054906101000a900463ffffffff1694508273ffffffffffffffffffffffffffffffffffffffff166352898b1286600060405160e001526040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808263ffffffff1663ffffffff16815260200191505060e060405180830381600087803b1515610bf757600080fd5b6102c65a03f11515610c0857600080fd5b505050604051805190602001805190602001805190602001805190602001805190602001805190602001805190509091929394955090919293945090919250909150600860008b81526020019081526020016000206000600a60008d81526020019081526020016000206000600960008f8152602001908152602001600020600086919050558591906101000a81548160ff021916908360ff16021790555084919050555050508173ffffffffffffffffffffffffffffffffffffffff1663e52926228a8a600060405160e001526040518363ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808363ffffffff1663ffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019250505060e060405180830381600087803b1515610d6657600080fd5b6102c65a03f11515610d7757600080fd5b5050506040518051906020018051906020018051906020018051906020018051906020018051906020018051905090919293949550909192939450909192935090919250905050809450506003600a600089815260200190815260200160002060009054906101000a900460ff1660ff16148015610e1a57508360ff16600a600089815260200190815260200160002060009054906101000a900460ff1660ff16115b1561105e578173ffffffffffffffffffffffffffffffffffffffff16631fcec53e600089600a60008c815260200190815260200160002060009054906101000a900460ff16600860008d815260200190815260200160002054600960008e8152602001908152602001600020548b6040518763ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808760ff1681526020018681526020018560ff1660ff1681526020018481526020018381526020018263ffffffff1663ffffffff1681526020019650505050505050600060405180830381600087803b1515610f1157600080fd5b6102c65a03f11515610f2257600080fd5b5050508073ffffffffffffffffffffffffffffffffffffffff16637350ac76600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff168a670de0b6b3a764000060648e63ffffffff16811515610f8057fe5b0463ffffffff16026040518463ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018267ffffffffffffffff1681526020019350505050600060405180830381600087803b151561104957600080fd5b6102c65a03f1151561105a57600080fd5b5050505b611673565b8273ffffffffffffffffffffffffffffffffffffffff166321555d82896000604051608001526040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001915050608060405180830381600087803b151561110657600080fd5b6102c65a03f1151561111757600080fd5b505050604051805190602001805190602001805190602001805190509091509050508096505060008663ffffffff1611151561115257600080fd5b88620186a087020163ffffffff1696506007600088815260200190815260200160002060009054906101000a900463ffffffff1694508273ffffffffffffffffffffffffffffffffffffffff166352898b1286600060405160e001526040518263ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808263ffffffff1663ffffffff16815260200191505060e060405180830381600087803b151561120b57600080fd5b6102c65a03f1151561121c57600080fd5b505050604051805190602001805190602001805190602001805190602001805190602001805190602001805190509091929394955090919293945090919250909150600860008b81526020019081526020016000206000600a60008d81526020019081526020016000206000600960008f8152602001908152602001600020600086919050558591906101000a81548160ff021916908360ff16021790555084919050555050508173ffffffffffffffffffffffffffffffffffffffff1663e52926228a8a600060405160e001526040518363ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808363ffffffff1663ffffffff1681526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019250505060e060405180830381600087803b151561137a57600080fd5b6102c65a03f1151561138b57600080fd5b5050506040518051906020018051906020018051906020018051906020018051906020018051906020018051905090919293949550909192939450909192935090919250905050809450506004600a600089815260200190815260200160002060009054906101000a900460ff1660ff1610801561142e57508360ff16600a600089815260200190815260200160002060009054906101000a900460ff1660ff16115b15611672578173ffffffffffffffffffffffffffffffffffffffff16631fcec53e600089600a60008c815260200190815260200160002060009054906101000a900460ff16600860008d815260200190815260200160002054600960008e8152602001908152602001600020548b6040518763ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808760ff1681526020018681526020018560ff1660ff1681526020018481526020018381526020018263ffffffff1663ffffffff1681526020019650505050505050600060405180830381600087803b151561152557600080fd5b6102c65a03f1151561153657600080fd5b5050508073ffffffffffffffffffffffffffffffffffffffff16637350ac76600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff168a670de0b6b3a764000060648e63ffffffff1681151561159457fe5b0463ffffffff16026040518463ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018267ffffffffffffffff1681526020019350505050600060405180830381600087803b151561165d57600080fd5b6102c65a03f1151561166e57600080fd5b5050505b5b505050505050505050565b600115156000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff1615151415156116dc57600080fd5b80600560006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b6000600115156000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16151514151561178057600080fd5b6000808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff169050919050565b600115156000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900460ff16151514151561183157600080fd5b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff00a165627a7a723058202089229a3472e7827d9f5418524ce35d576b9c32144b129f723a7dec09646c0c0029 \ @@ -6,4 +22,3 @@ --receiver=0x888bC67Ec5c6F6DabbD49490bEe2383E674E426D \ --genesis=evmtool-mainnet.json \ --repeat=100 - diff --git a/ethereum/evmtool/src/test/benchmarks/bench-static.sh b/ethereum/evmtool/src/test/benchmarks/bench-static.sh index 7efaa711d37..6ae063d3b0c 100755 --- a/ethereum/evmtool/src/test/benchmarks/bench-static.sh +++ b/ethereum/evmtool/src/test/benchmarks/bench-static.sh @@ -1,3 +1,19 @@ +#!/usr/bin/env sh +## +## Copyright contributors to Hyperledger Besu. +## +## Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +## an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +## specific language governing permissions and limitations under the License. +## +## SPDX-License-Identifier: Apache-2.0 +## + # call ../../../build/install/evmtool/bin/evm \ --code=5B600080808060045AFA50600056 \ diff --git a/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/CodeValidationSubCommandTest.java b/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/CodeValidationSubCommandTest.java index f98256324a7..9fe34b4836c 100644 --- a/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/CodeValidationSubCommandTest.java +++ b/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/CodeValidationSubCommandTest.java @@ -19,143 +19,145 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.PrintStream; +import java.io.PrintWriter; import org.junit.jupiter.api.Test; import picocli.CommandLine; -public class CodeValidationSubCommandTest { +class CodeValidationSubCommandTest { - static final String CODE_STOP_ONLY = "0xef0001 010004 020001-0001 030000 00 00000000 00"; - static final String CODE_RETF_ONLY = "0xef0001 010004 020001-0001 030000 00 00000000 e4"; - static final String CODE_BAD_MAGIC = "0xefffff 010004 020001-0001 030000 00 00000000 e4"; + static final String CODE_STOP_ONLY = "0xef0001 010004 020001-0001 040000 00 00800000 00"; + static final String CODE_RETURN_ONLY = "0xef0001 010004 020001-0003 040000 00 00800002 5f5ff3"; + static final String CODE_BAD_MAGIC = "0xefffff 010004 020001-0001 040000 00 00800000 e4"; static final String CODE_INTERIOR_COMMENTS = """ - 0xef0001 010008 020002-000c-0002 030000 00 + 0xef0001 010008 020002-0009-0002 040000 00 # 7 inputs 1 output, - 00000007-07010007 - 59-59-59-59-59-59-59-e30001-50-e4 + 00800004-04010004 + 59-59-59-59-e30001-50-00 # No immediate data - f1-e4"""; + f8-e4"""; static final String CODE_MULTIPLE = - CODE_STOP_ONLY + "\n" + CODE_BAD_MAGIC + "\n" + CODE_RETF_ONLY + "\n"; + CODE_STOP_ONLY + "\n" + CODE_BAD_MAGIC + "\n" + CODE_RETURN_ONLY + "\n"; @Test - public void testSingleValidViaInput() { + void testSingleValidViaInput() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(CODE_STOP_ONLY.getBytes(UTF_8)); - final CodeValidateSubCommand codeValidateSubCommand = - new CodeValidateSubCommand(bais, new PrintStream(baos)); + EvmToolCommand parentCommand = new EvmToolCommand(bais, new PrintWriter(baos, true, UTF_8)); + final CodeValidateSubCommand codeValidateSubCommand = new CodeValidateSubCommand(parentCommand); codeValidateSubCommand.run(); - assertThat(baos.toString(UTF_8)).contains("OK 00\n"); + assertThat(baos.toString(UTF_8)).contains("OK 1/0/0\n"); } @Test - public void testSingleInvalidViaInput() { + void testSingleInvalidViaInput() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(CODE_BAD_MAGIC.getBytes(UTF_8)); - final CodeValidateSubCommand codeValidateSubCommand = - new CodeValidateSubCommand(bais, new PrintStream(baos)); + EvmToolCommand parentCommand = new EvmToolCommand(bais, new PrintWriter(baos, true, UTF_8)); + final CodeValidateSubCommand codeValidateSubCommand = new CodeValidateSubCommand(parentCommand); codeValidateSubCommand.run(); - assertThat(baos.toString(UTF_8)).contains("err: layout - EOF header byte 1 incorrect\n"); + assertThat(baos.toString(UTF_8)) + .contains("err: layout - invalid_magic EOF header byte 1 incorrect\n"); } @Test - public void testMultipleViaInput() { + void testMultipleViaInput() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(CODE_MULTIPLE.getBytes(UTF_8)); - final CodeValidateSubCommand codeValidateSubCommand = - new CodeValidateSubCommand(bais, new PrintStream(baos)); + EvmToolCommand parentCommand = new EvmToolCommand(bais, new PrintWriter(baos, true, UTF_8)); + final CodeValidateSubCommand codeValidateSubCommand = new CodeValidateSubCommand(parentCommand); codeValidateSubCommand.run(); assertThat(baos.toString(UTF_8)) .contains( """ - OK 00 - err: layout - EOF header byte 1 incorrect - OK e4 + OK 1/0/0 + err: layout - invalid_magic EOF header byte 1 incorrect + OK 1/0/0 """); } @Test - public void testSingleValidViaCli() { + void testSingleValidViaCli() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]); - final CodeValidateSubCommand codeValidateSubCommand = - new CodeValidateSubCommand(bais, new PrintStream(baos)); + EvmToolCommand parentCommand = new EvmToolCommand(bais, new PrintWriter(baos, true, UTF_8)); + final CodeValidateSubCommand codeValidateSubCommand = new CodeValidateSubCommand(parentCommand); final CommandLine cmd = new CommandLine(codeValidateSubCommand); cmd.parseArgs(CODE_STOP_ONLY); codeValidateSubCommand.run(); - assertThat(baos.toString(UTF_8)).contains("OK 00\n"); + assertThat(baos.toString(UTF_8)).contains("OK 1/0/0\n"); } @Test - public void testSingleInvalidViaCli() { + void testSingleInvalidViaCli() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]); - final CodeValidateSubCommand codeValidateSubCommand = - new CodeValidateSubCommand(bais, new PrintStream(baos)); + EvmToolCommand parentCommand = new EvmToolCommand(bais, new PrintWriter(baos, true, UTF_8)); + final CodeValidateSubCommand codeValidateSubCommand = new CodeValidateSubCommand(parentCommand); final CommandLine cmd = new CommandLine(codeValidateSubCommand); cmd.parseArgs(CODE_BAD_MAGIC); codeValidateSubCommand.run(); - assertThat(baos.toString(UTF_8)).contains("err: layout - EOF header byte 1 incorrect\n"); + assertThat(baos.toString(UTF_8)) + .contains("err: layout - invalid_magic EOF header byte 1 incorrect\n"); } @Test - public void testMultipleViaCli() { + void testMultipleViaCli() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]); - final CodeValidateSubCommand codeValidateSubCommand = - new CodeValidateSubCommand(bais, new PrintStream(baos)); + EvmToolCommand parentCommand = new EvmToolCommand(bais, new PrintWriter(baos, true, UTF_8)); + final CodeValidateSubCommand codeValidateSubCommand = new CodeValidateSubCommand(parentCommand); final CommandLine cmd = new CommandLine(codeValidateSubCommand); - cmd.parseArgs(CODE_STOP_ONLY, CODE_BAD_MAGIC, CODE_RETF_ONLY); + cmd.parseArgs(CODE_STOP_ONLY, CODE_BAD_MAGIC, CODE_RETURN_ONLY); codeValidateSubCommand.run(); assertThat(baos.toString(UTF_8)) .contains( """ - OK 00 - err: layout - EOF header byte 1 incorrect - OK e4 + OK 1/0/0 + err: layout - invalid_magic EOF header byte 1 incorrect + OK 1/0/0 """); } @Test - public void testCliEclipsesInput() { + void testCliEclipsesInput() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(CODE_STOP_ONLY.getBytes(UTF_8)); - final CodeValidateSubCommand codeValidateSubCommand = - new CodeValidateSubCommand(bais, new PrintStream(baos)); + EvmToolCommand parentCommand = new EvmToolCommand(bais, new PrintWriter(baos, true, UTF_8)); + final CodeValidateSubCommand codeValidateSubCommand = new CodeValidateSubCommand(parentCommand); final CommandLine cmd = new CommandLine(codeValidateSubCommand); - cmd.parseArgs(CODE_RETF_ONLY); + cmd.parseArgs(CODE_RETURN_ONLY); codeValidateSubCommand.run(); - assertThat(baos.toString(UTF_8)).contains("OK e4\n"); + assertThat(baos.toString(UTF_8)).contains("OK 1/0/0\n"); } @Test - public void testInteriorCommentsSkipped() { + void testInteriorCommentsSkipped() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(new byte[0]); - final CodeValidateSubCommand codeValidateSubCommand = - new CodeValidateSubCommand(bais, new PrintStream(baos)); + EvmToolCommand parentCommand = new EvmToolCommand(bais, new PrintWriter(baos, true, UTF_8)); + final CodeValidateSubCommand codeValidateSubCommand = new CodeValidateSubCommand(parentCommand); final CommandLine cmd = new CommandLine(codeValidateSubCommand); cmd.parseArgs(CODE_INTERIOR_COMMENTS); codeValidateSubCommand.run(); - assertThat(baos.toString(UTF_8)).contains("OK 59595959595959e3000150e4,f1e4\n"); + assertThat(baos.toString(UTF_8)).contains("OK 2/0/0\n"); } @Test - public void testBlankLinesAndCommentsSkipped() { + void testBlankLinesAndCommentsSkipped() { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final ByteArrayInputStream bais = new ByteArrayInputStream(("# comment\n\n#blank line\n\n" + CODE_MULTIPLE).getBytes(UTF_8)); - final CodeValidateSubCommand codeValidateSubCommand = - new CodeValidateSubCommand(bais, new PrintStream(baos)); + EvmToolCommand parentCommand = new EvmToolCommand(bais, new PrintWriter(baos, true, UTF_8)); + final CodeValidateSubCommand codeValidateSubCommand = new CodeValidateSubCommand(parentCommand); codeValidateSubCommand.run(); assertThat(baos.toString(UTF_8)) .isEqualTo( """ - OK 00 - err: layout - EOF header byte 1 incorrect - OK e4 + OK 1/0/0 + err: layout - invalid_magic EOF header byte 1 incorrect + OK 1/0/0 """); } } diff --git a/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/EvmToolSpecTests.java b/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/EvmToolSpecTests.java index c326d653e52..9ec5e431da6 100644 --- a/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/EvmToolSpecTests.java +++ b/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/EvmToolSpecTests.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -45,6 +45,7 @@ import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.JsonNodeType; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -53,10 +54,22 @@ public class EvmToolSpecTests { static final ObjectMapper objectMapper = new ObjectMapper(); static final ObjectReader specReader = objectMapper.reader(); + public static Object[][] blocktestTests() { + return findSpecFiles(new String[] {"block-test"}); + } + public static Object[][] b11rTests() { return findSpecFiles(new String[] {"b11r"}); } + public static Object[][] codeValidateTests() { + return findSpecFiles(new String[] {"code-validate"}); + } + + public static Object[][] prettyPrintTests() { + return findSpecFiles(new String[] {"pretty-print"}); + } + public static Object[][] stateTestTests() { return findSpecFiles(new String[] {"state-test"}); } @@ -110,7 +123,15 @@ private static Object[] pathToParams(final String subDir, final File file) { } @ParameterizedTest(name = "{0}") - @MethodSource({"b11rTests", "stateTestTests", "t8nTests", "traceTests"}) + @MethodSource({ + "blocktestTests", + "b11rTests", + "codeValidateTests", + "prettyPrintTests", + "stateTestTests", + "t8nTests", + "traceTests" + }) void testBySpec( final String file, final JsonNode cliNode, @@ -168,4 +189,11 @@ void testBySpec( assertThat(actualNode).isEqualTo(stdoutNode); } } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/T8nServerSubCommandTest.java b/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/T8nServerSubCommandTest.java index 2f658eeb323..f3312372f67 100644 --- a/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/T8nServerSubCommandTest.java +++ b/ethereum/evmtool/src/test/java/org/hyperledger/besu/evmtool/T8nServerSubCommandTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/block-test/prague-eof-rjump.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/block-test/prague-eof-rjump.json new file mode 100644 index 00000000000..b9d2e541795 --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/block-test/prague-eof-rjump.json @@ -0,0 +1,250 @@ +{ + "cli": [ + "block-test", + "stdin" + ], + "stdin": { + "tests/prague/eip7692_eof_v1/eip4200_relative_jumps/test_rjump.py::test_rjump_zero[fork_PragueEIP7692-blockchain_test]": { + "network": "Prague", + "genesisBlockHeader": { + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "uncleHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "coinbase": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x34ccf8774a5b8833da9451a3f7f8a0af0147956c058f0831dab07c348d7ac0d9", + "transactionsTrie": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "receiptTrie": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x00", + "number": "0x00", + "gasLimit": "0x016345785d8a0000", + "gasUsed": "0x00", + "timestamp": "0x00", + "extraData": "0x00", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "baseFeePerGas": "0x07", + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "blobGasUsed": "0x00", + "excessBlobGas": "0x00", + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "requestsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "hash": "0xc9397e8a1b99cbb2b885852fde56de8fa686091a4f4430163f5237d7aaf33a14" + }, + "pre": { + "0x00000000219ab540356cbb839cbe05303d7705fa": { + "nonce": "0x01", + "balance": "0x00", + "code": "0x60806040526004361061003f5760003560e01c806301ffc9a71461004457806322895118146100a4578063621fd130146101ba578063c5f2892f14610244575b600080fd5b34801561005057600080fd5b506100906004803603602081101561006757600080fd5b50357fffffffff000000000000000000000000000000000000000000000000000000001661026b565b604080519115158252519081900360200190f35b6101b8600480360360808110156100ba57600080fd5b8101906020810181356401000000008111156100d557600080fd5b8201836020820111156100e757600080fd5b8035906020019184600183028401116401000000008311171561010957600080fd5b91939092909160208101903564010000000081111561012757600080fd5b82018360208201111561013957600080fd5b8035906020019184600183028401116401000000008311171561015b57600080fd5b91939092909160208101903564010000000081111561017957600080fd5b82018360208201111561018b57600080fd5b803590602001918460018302840111640100000000831117156101ad57600080fd5b919350915035610304565b005b3480156101c657600080fd5b506101cf6110b5565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102095781810151838201526020016101f1565b50505050905090810190601f1680156102365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561025057600080fd5b506102596110c7565b60408051918252519081900360200190f35b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a70000000000000000000000000000000000000000000000000000000014806102fe57507fffffffff0000000000000000000000000000000000000000000000000000000082167f8564090700000000000000000000000000000000000000000000000000000000145b92915050565b6030861461035d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118056026913960400191505060405180910390fd5b602084146103b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603681526020018061179c6036913960400191505060405180910390fd5b6060821461040f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806118786029913960400191505060405180910390fd5b670de0b6b3a7640000341015610470576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118526026913960400191505060405180910390fd5b633b9aca003406156104cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260338152602001806117d26033913960400191505060405180910390fd5b633b9aca00340467ffffffffffffffff811115610535576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602781526020018061182b6027913960400191505060405180910390fd5b6060610540826114ba565b90507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c589898989858a8a6105756020546114ba565b6040805160a0808252810189905290819060208201908201606083016080840160c085018e8e80828437600083820152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690910187810386528c815260200190508c8c808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690920188810386528c5181528c51602091820193918e019250908190849084905b83811015610648578181015183820152602001610630565b50505050905090810190601f1680156106755780820380516001836020036101000a031916815260200191505b5086810383528881526020018989808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169092018881038452895181528951602091820193918b019250908190849084905b838110156106ef5781810151838201526020016106d7565b50505050905090810190601f16801561071c5780820380516001836020036101000a031916815260200191505b509d505050505050505050505050505060405180910390a1600060028a8a600060801b604051602001808484808284377fffffffffffffffffffffffffffffffff0000000000000000000000000000000090941691909301908152604080517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0818403018152601090920190819052815191955093508392506020850191508083835b602083106107fc57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016107bf565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610859573d6000803e3d6000fd5b5050506040513d602081101561086e57600080fd5b5051905060006002806108846040848a8c6116fe565b6040516020018083838082843780830192505050925050506040516020818303038152906040526040518082805190602001908083835b602083106108f857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016108bb565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610955573d6000803e3d6000fd5b5050506040513d602081101561096a57600080fd5b5051600261097b896040818d6116fe565b60405160009060200180848480828437919091019283525050604080518083038152602092830191829052805190945090925082918401908083835b602083106109f457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016109b7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610a51573d6000803e3d6000fd5b5050506040513d6020811015610a6657600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610ada57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610a9d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610b37573d6000803e3d6000fd5b5050506040513d6020811015610b4c57600080fd5b50516040805160208101858152929350600092600292839287928f928f92018383808284378083019250505093505050506040516020818303038152906040526040518082805190602001908083835b60208310610bd957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610b9c565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610c36573d6000803e3d6000fd5b5050506040513d6020811015610c4b57600080fd5b50516040518651600291889160009188916020918201918291908601908083835b60208310610ca957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610c6c565b6001836020036101000a0380198251168184511680821785525050505050509050018367ffffffffffffffff191667ffffffffffffffff1916815260180182815260200193505050506040516020818303038152906040526040518082805190602001908083835b60208310610d4e57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610d11565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610dab573d6000803e3d6000fd5b5050506040513d6020811015610dc057600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610e3457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610df7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610e91573d6000803e3d6000fd5b5050506040513d6020811015610ea657600080fd5b50519050858114610f02576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260548152602001806117486054913960600191505060405180910390fd5b60205463ffffffff11610f60576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806117276021913960400191505060405180910390fd5b602080546001019081905560005b60208110156110a9578160011660011415610fa0578260008260208110610f9157fe5b0155506110ac95505050505050565b600260008260208110610faf57fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061102557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610fe8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015611082573d6000803e3d6000fd5b5050506040513d602081101561109757600080fd5b50519250600282049150600101610f6e565b50fe5b50505050505050565b60606110c26020546114ba565b905090565b6020546000908190815b60208110156112f05781600116600114156111e6576002600082602081106110f557fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061116b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161112e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156111c8573d6000803e3d6000fd5b5050506040513d60208110156111dd57600080fd5b505192506112e2565b600283602183602081106111f657fe5b015460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061126b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161122e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156112c8573d6000803e3d6000fd5b5050506040513d60208110156112dd57600080fd5b505192505b6002820491506001016110d1565b506002826112ff6020546114ba565b600060401b6040516020018084815260200183805190602001908083835b6020831061135a57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161131d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790527fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000095909516920191825250604080518083037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8018152601890920190819052815191955093508392850191508083835b6020831061143f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101611402565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa15801561149c573d6000803e3d6000fd5b5050506040513d60208110156114b157600080fd5b50519250505090565b60408051600880825281830190925260609160208201818036833701905050905060c082901b8060071a60f81b826000815181106114f457fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060061a60f81b8260018151811061153757fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060051a60f81b8260028151811061157a57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060041a60f81b826003815181106115bd57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060031a60f81b8260048151811061160057fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060021a60f81b8260058151811061164357fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060011a60f81b8260068151811061168657fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060001a60f81b826007815181106116c957fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535050919050565b6000808585111561170d578182fd5b83861115611719578182fd5b505082019391909203915056fe4465706f736974436f6e74726163743a206d65726b6c6520747265652066756c6c4465706f736974436f6e74726163743a207265636f6e7374727563746564204465706f7369744461746120646f6573206e6f74206d6174636820737570706c696564206465706f7369745f646174615f726f6f744465706f736974436f6e74726163743a20696e76616c6964207769746864726177616c5f63726564656e7469616c73206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c7565206e6f74206d756c7469706c65206f6620677765694465706f736974436f6e74726163743a20696e76616c6964207075626b6579206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f20686967684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f206c6f774465706f736974436f6e74726163743a20696e76616c6964207369676e6174757265206c656e677468a2646970667358221220dceca8706b29e917dacf25fceef95acac8d90d765ac926663ce4096195952b6164736f6c634300060b0033", + "storage": { + "0x22": "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", + "0x23": "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", + "0x24": "0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c", + "0x25": "0x536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c", + "0x26": "0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30", + "0x27": "0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1", + "0x28": "0x87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c", + "0x29": "0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193", + "0x2a": "0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1", + "0x2b": "0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b", + "0x2c": "0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220", + "0x2d": "0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f", + "0x2e": "0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e", + "0x2f": "0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784", + "0x30": "0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb", + "0x31": "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb", + "0x32": "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab", + "0x33": "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", + "0x34": "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f", + "0x35": "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", + "0x36": "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", + "0x37": "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", + "0x38": "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", + "0x39": "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", + "0x3a": "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", + "0x3b": "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", + "0x3c": "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", + "0x3d": "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", + "0x3e": "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", + "0x3f": "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", + "0x40": "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7" + } + }, + "0x00a3ca265ebcb825b45f985a16cefb49958ce017": { + "nonce": "0x01", + "balance": "0x00", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe146090573615156028575f545f5260205ff35b366038141561012e5760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061012e57600154600101600155600354806003026004013381556001015f3581556001016020359055600101600355005b6003546002548082038060101160a4575060105b5f5b81811460dd5780604c02838201600302600401805490600101805490600101549160601b83528260140152906034015260010160a6565b910180921460ed579060025560f8565b90505f6002555f6003555b5f548061049d141561010757505f5b60015460028282011161011c5750505f610122565b01600290035b5f555f600155604c025ff35b5f5ffd", + "storage": {} + }, + "0x0aae40965e6800cd9b1f4b05ff21581047e3f91e": { + "nonce": "0x01", + "balance": "0x00", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe1460575767ffffffffffffffff5f3511605357600143035f3511604b575f35612000014311604b57611fff5f3516545f5260205ff35b5f5f5260205ff35b5f5ffd5b5f35611fff60014303165500", + "storage": {} + }, + "0x000f3df6d732807ef1319fb7b8bb8522d0beac02": { + "nonce": "0x01", + "balance": "0x00", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500", + "storage": {} + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "nonce": "0x00", + "balance": "0x3635c9adc5dea00000", + "code": "0x", + "storage": {} + }, + "0x0000000000000000000000000000000000001000": { + "nonce": "0x01", + "balance": "0x00", + "code": "0xef0001010004020001000a0400000000800002e0000061201560015500", + "storage": {} + } + }, + "postState": { + "0x0000000000000000000000000000000000001000": { + "nonce": "0x01", + "balance": "0x00", + "code": "0xef0001010004020001000a0400000000800002e0000061201560015500", + "storage": { + "0x01": "0x2015" + } + }, + "0x00000000219ab540356cbb839cbe05303d7705fa": { + "nonce": "0x01", + "balance": "0x00", + "code": "0x60806040526004361061003f5760003560e01c806301ffc9a71461004457806322895118146100a4578063621fd130146101ba578063c5f2892f14610244575b600080fd5b34801561005057600080fd5b506100906004803603602081101561006757600080fd5b50357fffffffff000000000000000000000000000000000000000000000000000000001661026b565b604080519115158252519081900360200190f35b6101b8600480360360808110156100ba57600080fd5b8101906020810181356401000000008111156100d557600080fd5b8201836020820111156100e757600080fd5b8035906020019184600183028401116401000000008311171561010957600080fd5b91939092909160208101903564010000000081111561012757600080fd5b82018360208201111561013957600080fd5b8035906020019184600183028401116401000000008311171561015b57600080fd5b91939092909160208101903564010000000081111561017957600080fd5b82018360208201111561018b57600080fd5b803590602001918460018302840111640100000000831117156101ad57600080fd5b919350915035610304565b005b3480156101c657600080fd5b506101cf6110b5565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102095781810151838201526020016101f1565b50505050905090810190601f1680156102365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561025057600080fd5b506102596110c7565b60408051918252519081900360200190f35b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a70000000000000000000000000000000000000000000000000000000014806102fe57507fffffffff0000000000000000000000000000000000000000000000000000000082167f8564090700000000000000000000000000000000000000000000000000000000145b92915050565b6030861461035d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118056026913960400191505060405180910390fd5b602084146103b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603681526020018061179c6036913960400191505060405180910390fd5b6060821461040f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806118786029913960400191505060405180910390fd5b670de0b6b3a7640000341015610470576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118526026913960400191505060405180910390fd5b633b9aca003406156104cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260338152602001806117d26033913960400191505060405180910390fd5b633b9aca00340467ffffffffffffffff811115610535576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602781526020018061182b6027913960400191505060405180910390fd5b6060610540826114ba565b90507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c589898989858a8a6105756020546114ba565b6040805160a0808252810189905290819060208201908201606083016080840160c085018e8e80828437600083820152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690910187810386528c815260200190508c8c808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690920188810386528c5181528c51602091820193918e019250908190849084905b83811015610648578181015183820152602001610630565b50505050905090810190601f1680156106755780820380516001836020036101000a031916815260200191505b5086810383528881526020018989808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169092018881038452895181528951602091820193918b019250908190849084905b838110156106ef5781810151838201526020016106d7565b50505050905090810190601f16801561071c5780820380516001836020036101000a031916815260200191505b509d505050505050505050505050505060405180910390a1600060028a8a600060801b604051602001808484808284377fffffffffffffffffffffffffffffffff0000000000000000000000000000000090941691909301908152604080517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0818403018152601090920190819052815191955093508392506020850191508083835b602083106107fc57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016107bf565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610859573d6000803e3d6000fd5b5050506040513d602081101561086e57600080fd5b5051905060006002806108846040848a8c6116fe565b6040516020018083838082843780830192505050925050506040516020818303038152906040526040518082805190602001908083835b602083106108f857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016108bb565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610955573d6000803e3d6000fd5b5050506040513d602081101561096a57600080fd5b5051600261097b896040818d6116fe565b60405160009060200180848480828437919091019283525050604080518083038152602092830191829052805190945090925082918401908083835b602083106109f457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016109b7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610a51573d6000803e3d6000fd5b5050506040513d6020811015610a6657600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610ada57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610a9d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610b37573d6000803e3d6000fd5b5050506040513d6020811015610b4c57600080fd5b50516040805160208101858152929350600092600292839287928f928f92018383808284378083019250505093505050506040516020818303038152906040526040518082805190602001908083835b60208310610bd957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610b9c565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610c36573d6000803e3d6000fd5b5050506040513d6020811015610c4b57600080fd5b50516040518651600291889160009188916020918201918291908601908083835b60208310610ca957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610c6c565b6001836020036101000a0380198251168184511680821785525050505050509050018367ffffffffffffffff191667ffffffffffffffff1916815260180182815260200193505050506040516020818303038152906040526040518082805190602001908083835b60208310610d4e57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610d11565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610dab573d6000803e3d6000fd5b5050506040513d6020811015610dc057600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610e3457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610df7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610e91573d6000803e3d6000fd5b5050506040513d6020811015610ea657600080fd5b50519050858114610f02576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260548152602001806117486054913960600191505060405180910390fd5b60205463ffffffff11610f60576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806117276021913960400191505060405180910390fd5b602080546001019081905560005b60208110156110a9578160011660011415610fa0578260008260208110610f9157fe5b0155506110ac95505050505050565b600260008260208110610faf57fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061102557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610fe8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015611082573d6000803e3d6000fd5b5050506040513d602081101561109757600080fd5b50519250600282049150600101610f6e565b50fe5b50505050505050565b60606110c26020546114ba565b905090565b6020546000908190815b60208110156112f05781600116600114156111e6576002600082602081106110f557fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061116b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161112e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156111c8573d6000803e3d6000fd5b5050506040513d60208110156111dd57600080fd5b505192506112e2565b600283602183602081106111f657fe5b015460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061126b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161122e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156112c8573d6000803e3d6000fd5b5050506040513d60208110156112dd57600080fd5b505192505b6002820491506001016110d1565b506002826112ff6020546114ba565b600060401b6040516020018084815260200183805190602001908083835b6020831061135a57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161131d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790527fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000095909516920191825250604080518083037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8018152601890920190819052815191955093508392850191508083835b6020831061143f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101611402565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa15801561149c573d6000803e3d6000fd5b5050506040513d60208110156114b157600080fd5b50519250505090565b60408051600880825281830190925260609160208201818036833701905050905060c082901b8060071a60f81b826000815181106114f457fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060061a60f81b8260018151811061153757fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060051a60f81b8260028151811061157a57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060041a60f81b826003815181106115bd57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060031a60f81b8260048151811061160057fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060021a60f81b8260058151811061164357fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060011a60f81b8260068151811061168657fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060001a60f81b826007815181106116c957fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535050919050565b6000808585111561170d578182fd5b83861115611719578182fd5b505082019391909203915056fe4465706f736974436f6e74726163743a206d65726b6c6520747265652066756c6c4465706f736974436f6e74726163743a207265636f6e7374727563746564204465706f7369744461746120646f6573206e6f74206d6174636820737570706c696564206465706f7369745f646174615f726f6f744465706f736974436f6e74726163743a20696e76616c6964207769746864726177616c5f63726564656e7469616c73206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c7565206e6f74206d756c7469706c65206f6620677765694465706f736974436f6e74726163743a20696e76616c6964207075626b6579206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f20686967684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f206c6f774465706f736974436f6e74726163743a20696e76616c6964207369676e6174757265206c656e677468a2646970667358221220dceca8706b29e917dacf25fceef95acac8d90d765ac926663ce4096195952b6164736f6c634300060b0033", + "storage": { + "0x22": "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", + "0x23": "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", + "0x24": "0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c", + "0x25": "0x536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c", + "0x26": "0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30", + "0x27": "0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1", + "0x28": "0x87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c", + "0x29": "0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193", + "0x2a": "0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1", + "0x2b": "0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b", + "0x2c": "0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220", + "0x2d": "0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f", + "0x2e": "0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e", + "0x2f": "0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784", + "0x30": "0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb", + "0x31": "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb", + "0x32": "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab", + "0x33": "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", + "0x34": "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f", + "0x35": "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", + "0x36": "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", + "0x37": "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", + "0x38": "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", + "0x39": "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", + "0x3a": "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", + "0x3b": "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", + "0x3c": "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", + "0x3d": "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", + "0x3e": "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", + "0x3f": "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", + "0x40": "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7" + } + }, + "0x000f3df6d732807ef1319fb7b8bb8522d0beac02": { + "nonce": "0x01", + "balance": "0x00", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500", + "storage": { + "0x03e8": "0x03e8" + } + }, + "0x00a3ca265ebcb825b45f985a16cefb49958ce017": { + "nonce": "0x01", + "balance": "0x00", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe146090573615156028575f545f5260205ff35b366038141561012e5760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061012e57600154600101600155600354806003026004013381556001015f3581556001016020359055600101600355005b6003546002548082038060101160a4575060105b5f5b81811460dd5780604c02838201600302600401805490600101805490600101549160601b83528260140152906034015260010160a6565b910180921460ed579060025560f8565b90505f6002555f6003555b5f548061049d141561010757505f5b60015460028282011161011c5750505f610122565b01600290035b5f555f600155604c025ff35b5f5ffd", + "storage": {} + }, + "0x0aae40965e6800cd9b1f4b05ff21581047e3f91e": { + "nonce": "0x01", + "balance": "0x00", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe1460575767ffffffffffffffff5f3511605357600143035f3511604b575f35612000014311604b57611fff5f3516545f5260205ff35b5f5f5260205ff35b5f5ffd5b5f35611fff60014303165500", + "storage": { + "0x00": "0xc9397e8a1b99cbb2b885852fde56de8fa686091a4f4430163f5237d7aaf33a14" + } + }, + "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba": { + "nonce": "0x00", + "balance": "0x01f92c", + "code": "0x", + "storage": {} + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "nonce": "0x01", + "balance": "0x3635c9adc5de996c18", + "code": "0x", + "storage": {} + } + }, + "genesisRLP": "0xf90262f9025ba00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a034ccf8774a5b8833da9451a3f7f8a0af0147956c058f0831dab07c348d7ac0d9a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000808088016345785d8a0000808000a0000000000000000000000000000000000000000000000000000000000000000088000000000000000007a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b4218080a00000000000000000000000000000000000000000000000000000000000000000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421c0c0c0c0", + "blocks": [ + { + "blockHeader": { + "parentHash": "0xc9397e8a1b99cbb2b885852fde56de8fa686091a4f4430163f5237d7aaf33a14", + "uncleHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "coinbase": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "stateRoot": "0x1a4885d1f64bac16e5ab17fb13e23fb5d1a0ec2ca4519c81714683f69c8d9a84", + "transactionsTrie": "0xec9d10cff79619f2df45db8c66526ef3fbd32d283fdd2dcc9b55c0efe643d8c3", + "receiptTrie": "0x9593f56abf23bcbb26d27b0c6e46a56415d9103ed6b4d8ac7b4182f9f250cafa", + "bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "difficulty": "0x00", + "number": "0x01", + "gasLimit": "0x016345785d8a0000", + "gasUsed": "0xa864", + "timestamp": "0x03e8", + "extraData": "0x00", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0000000000000000", + "baseFeePerGas": "0x07", + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "blobGasUsed": "0x00", + "excessBlobGas": "0x00", + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "requestsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "hash": "0x879cf54bf493b6585db592b1b24ec30a8a7fbe3b9146d3fb20ca36200f2aca87" + }, + "transactions": [ + { + "type": "0x00", + "chainId": "0x01", + "nonce": "0x00", + "gasPrice": "0x0a", + "gasLimit": "0x989680", + "to": "0x0000000000000000000000000000000000001000", + "value": "0x00", + "data": "0x", + "v": "0x26", + "r": "0xe5d462429669f661291a8dc4c49a092cfd4922b6f3f31c9189a2f4adf5ecd730", + "s": "0x01494afaf472fbb80bcb107ffeb918a2b9115f454027840615d6d20d63c69ac0", + "sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" + } + ], + "uncleHeaders": [], + "withdrawals": [], + "depositRequests": [], + "withdrawalRequests": [], + "rlp": "0xf902c9f9025fa0c9397e8a1b99cbb2b885852fde56de8fa686091a4f4430163f5237d7aaf33a14a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa01a4885d1f64bac16e5ab17fb13e23fb5d1a0ec2ca4519c81714683f69c8d9a84a0ec9d10cff79619f2df45db8c66526ef3fbd32d283fdd2dcc9b55c0efe643d8c3a09593f56abf23bcbb26d27b0c6e46a56415d9103ed6b4d8ac7b4182f9f250cafab9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800188016345785d8a000082a8648203e800a0000000000000000000000000000000000000000000000000000000000000000088000000000000000007a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b4218080a00000000000000000000000000000000000000000000000000000000000000000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421f862f860800a83989680940000000000000000000000000000000000001000808026a0e5d462429669f661291a8dc4c49a092cfd4922b6f3f31c9189a2f4adf5ecd730a001494afaf472fbb80bcb107ffeb918a2b9115f454027840615d6d20d63c69ac0c0c0c0", + "blocknumber": "1" + } + ], + "lastblockhash": "0x879cf54bf493b6585db592b1b24ec30a8a7fbe3b9146d3fb20ca36200f2aca87", + "sealEngine": "NoProof", + "_info": { + "hash": "0xbfd1223f9b5b8dbf202178f7c1f18dc089cb24e54c9cb7fc9831907547e937c4", + "comment": "`execution-spec-tests` generated test", + "filling-transition-tool": "Hyperledger Besu evm 24.7-develop-8ca7129", + "description": "Test function documentation:\nEOF1V4200_0002 (Valid) EOF code containing RJUMP (Zero)", + "url": "https://github.com/ethereum/execution-spec-tests/blob/891a6111370c89d4ce89bf91589c6d5ff6785158/tests/prague/eip7692_eof_v1/eip4200_relative_jumps/test_rjump.py#L63", + "reference-spec": "https://github.com/ethereum/EIPs/blob/master/EIPS/eip-4200.md", + "reference-spec-version": "17d4a8d12d2b5e0f2985c866376c16c8c6df7cba" + } + } + }, + "stdout": "Considering tests/prague/eip7692_eof_v1/eip4200_relative_jumps/test_rjump.py::test_rjump_zero[fork_PragueEIP7692-blockchain_test]\nBlock 1 (0x879cf54bf493b6585db592b1b24ec30a8a7fbe3b9146d3fb20ca36200f2aca87) Imported\nChain import successful - tests/prague/eip7692_eof_v1/eip4200_relative_jumps/test_rjump.py::test_rjump_zero[fork_PragueEIP7692-blockchain_test]\n" +} diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/code-validate/createdata.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/code-validate/createdata.json new file mode 100644 index 00000000000..9207cd65e02 --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/code-validate/createdata.json @@ -0,0 +1,7 @@ +{ + "cli": [ + "code-validate" + ], + "stdin": "0xef0001010004020001000b0300010014040004000080000436600060ff6000ec005000ef000101000402000100010400000000800000feda7ac0de", + "stdout": "OK 1/1/4\n" +} diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/code-validate/initcode.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/code-validate/initcode.json new file mode 100644 index 00000000000..5ecd5511a50 --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/code-validate/initcode.json @@ -0,0 +1,7 @@ +{ + "cli": [ + "code-validate" + ], + "stdin": "ef000101000402000100060300010014040000000080000260006000ee00ef00010100040200010001040000000080000000", + "stdout": "err: code is valid initcode. Runtime code expected\n" +} diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/code-validate/runtime.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/code-validate/runtime.json new file mode 100644 index 00000000000..fedeaa1bc05 --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/code-validate/runtime.json @@ -0,0 +1,7 @@ +{ + "cli": [ + "code-validate" + ], + "stdin": "ef00010100040200010001040000000080000000", + "stdout": "OK 1/0/0\n" +} diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/dangling-data.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/dangling-data.json new file mode 100644 index 00000000000..bdab829bbcd --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/dangling-data.json @@ -0,0 +1,8 @@ +{ + "cli": [ + "pretty-print", + "ef00010100040200010001040000000080000000ff" + ], + "stdin": "", + "stdout": "EOF layout is invalid - invalid_section_bodies_size data after end of all sections\n" +} \ No newline at end of file diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/rjumpv-max.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/rjumpv-max.json new file mode 100644 index 00000000000..988a8e31570 --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/rjumpv-max.json @@ -0,0 +1,8 @@ +{ + "cli": [ + "pretty-print", + "0xef0001010004020001090b04000000008000025f35e2ff0007000e0015001c0023002a00310038003f0046004d0054005b0062006900700077007e0085008c0093009a00a100a800af00b600bd00c400cb00d200d900e000e700ee00f500fc0103010a01110118011f0126012d0134013b0142014901500157015e0165016c0173017a01810188018f0196019d01a401ab01b201b901c001c701ce01d501dc01e301ea01f101f801ff0206020d0214021b0222022902300237023e0245024c0253025a02610268026f0276027d0284028b0292029902a002a702ae02b502bc02c302ca02d102d802df02e602ed02f402fb0302030903100317031e0325032c0333033a03410348034f0356035d0364036b0372037903800387038e0395039c03a303aa03b103b803bf03c603cd03d403db03e203e903f003f703fe0405040c0413041a04210428042f0436043d0444044b0452045904600467046e0475047c0483048a04910498049f04a604ad04b404bb04c204c904d004d704de04e504ec04f304fa05010508050f0516051d0524052b0532053905400547054e0555055c0563056a05710578057f0586058d0594059b05a205a905b005b705be05c505cc05d305da05e105e805ef05f605fd0604060b0612061906200627062e0635063c0643064a06510658065f0666066d0674067b0682068906900697069e06a506ac06b306ba06c106c806cf06d606dd06e406eb06f206f9070061ffff600255006110006002550061100160025500611002600255006110036002550061100460025500611005600255006110066002550061100760025500611008600255006110096002550061100a6002550061100b6002550061100c6002550061100d6002550061100e6002550061100f600255006110106002550061101160025500611012600255006110136002550061101460025500611015600255006110166002550061101760025500611018600255006110196002550061101a6002550061101b6002550061101c6002550061101d6002550061101e6002550061101f600255006110206002550061102160025500611022600255006110236002550061102460025500611025600255006110266002550061102760025500611028600255006110296002550061102a6002550061102b6002550061102c6002550061102d6002550061102e6002550061102f600255006110306002550061103160025500611032600255006110336002550061103460025500611035600255006110366002550061103760025500611038600255006110396002550061103a6002550061103b6002550061103c6002550061103d6002550061103e6002550061103f600255006110406002550061104160025500611042600255006110436002550061104460025500611045600255006110466002550061104760025500611048600255006110496002550061104a6002550061104b6002550061104c6002550061104d6002550061104e6002550061104f600255006110506002550061105160025500611052600255006110536002550061105460025500611055600255006110566002550061105760025500611058600255006110596002550061105a6002550061105b6002550061105c6002550061105d6002550061105e6002550061105f600255006110606002550061106160025500611062600255006110636002550061106460025500611065600255006110666002550061106760025500611068600255006110696002550061106a6002550061106b6002550061106c6002550061106d6002550061106e6002550061106f600255006110706002550061107160025500611072600255006110736002550061107460025500611075600255006110766002550061107760025500611078600255006110796002550061107a6002550061107b6002550061107c6002550061107d6002550061107e6002550061107f600255006110806002550061108160025500611082600255006110836002550061108460025500611085600255006110866002550061108760025500611088600255006110896002550061108a6002550061108b6002550061108c6002550061108d6002550061108e6002550061108f600255006110906002550061109160025500611092600255006110936002550061109460025500611095600255006110966002550061109760025500611098600255006110996002550061109a6002550061109b6002550061109c6002550061109d6002550061109e6002550061109f600255006110a0600255006110a1600255006110a2600255006110a3600255006110a4600255006110a5600255006110a6600255006110a7600255006110a8600255006110a9600255006110aa600255006110ab600255006110ac600255006110ad600255006110ae600255006110af600255006110b0600255006110b1600255006110b2600255006110b3600255006110b4600255006110b5600255006110b6600255006110b7600255006110b8600255006110b9600255006110ba600255006110bb600255006110bc600255006110bd600255006110be600255006110bf600255006110c0600255006110c1600255006110c2600255006110c3600255006110c4600255006110c5600255006110c6600255006110c7600255006110c8600255006110c9600255006110ca600255006110cb600255006110cc600255006110cd600255006110ce600255006110cf600255006110d0600255006110d1600255006110d2600255006110d3600255006110d4600255006110d5600255006110d6600255006110d7600255006110d8600255006110d9600255006110da600255006110db600255006110dc600255006110dd600255006110de600255006110df600255006110e0600255006110e1600255006110e2600255006110e3600255006110e4600255006110e5600255006110e6600255006110e7600255006110e8600255006110e9600255006110ea600255006110eb600255006110ec600255006110ed600255006110ee600255006110ef600255006110f0600255006110f1600255006110f2600255006110f3600255006110f4600255006110f5600255006110f6600255006110f7600255006110f8600255006110f9600255006110fa600255006110fb600255006110fc600255006110fd600255006110fe600255006110ff60025500" + ], + "stdin": "", + "stdout": "0x # EOF\nef0001 # Magic and Version ( 1 )\n010004 # Types length ( 4 )\n020001 # Total code sections ( 1 )\n 090b # Code section 0 , 2315 bytes\n040000 # Data section length( 0 )\n 00 # Terminator (end of header)\n # Code section 0 types\n 00 # 0 inputs \n 80 # 0 outputs (Non-returning function)\n 0002 # max stack: 2\n # Code section 0 - in=0 out=non-returning height=2\n 5f # [0] PUSH0\n 35 # [1] CALLDATALOAD\ne2ff0007000e0015001c0023002a00310038003f0046004d0054005b0062006900700077007e0085008c0093009a00a100a800af00b600bd00c400cb00d200d900e000e700ee00f500fc0103010a01110118011f0126012d0134013b0142014901500157015e0165016c0173017a01810188018f0196019d01a401ab01b201b901c001c701ce01d501dc01e301ea01f101f801ff0206020d0214021b0222022902300237023e0245024c0253025a02610268026f0276027d0284028b0292029902a002a702ae02b502bc02c302ca02d102d802df02e602ed02f402fb0302030903100317031e0325032c0333033a03410348034f0356035d0364036b0372037903800387038e0395039c03a303aa03b103b803bf03c603cd03d403db03e203e903f003f703fe0405040c0413041a04210428042f0436043d0444044b0452045904600467046e0475047c0483048a04910498049f04a604ad04b404bb04c204c904d004d704de04e504ec04f304fa05010508050f0516051d0524052b0532053905400547054e0555055c0563056a05710578057f0586058d0594059b05a205a905b005b705be05c505cc05d305da05e105e805ef05f605fd0604060b0612061906200627062e0635063c0643064a06510658065f0666066d0674067b0682068906900697069e06a506ac06b306ba06c106c806cf06d606dd06e406eb06f206f90700 # [2] RJUMPV(7,14,21,28,35,42,49,56,63,70,77,84,91,98,105,112,119,126,133,140,147,154,161,168,175,182,189,196,203,210,217,224,231,238,245,252,259,266,273,280,287,294,301,308,315,322,329,336,343,350,357,364,371,378,385,392,399,406,413,420,427,434,441,448,455,462,469,476,483,490,497,504,511,518,525,532,539,546,553,560,567,574,581,588,595,602,609,616,623,630,637,644,651,658,665,672,679,686,693,700,707,714,721,728,735,742,749,756,763,770,777,784,791,798,805,812,819,826,833,840,847,854,861,868,875,882,889,896,903,910,917,924,931,938,945,952,959,966,973,980,987,994,1001,1008,1015,1022,1029,1036,1043,1050,1057,1064,1071,1078,1085,1092,1099,1106,1113,1120,1127,1134,1141,1148,1155,1162,1169,1176,1183,1190,1197,1204,1211,1218,1225,1232,1239,1246,1253,1260,1267,1274,1281,1288,1295,1302,1309,1316,1323,1330,1337,1344,1351,1358,1365,1372,1379,1386,1393,1400,1407,1414,1421,1428,1435,1442,1449,1456,1463,1470,1477,1484,1491,1498,1505,1512,1519,1526,1533,1540,1547,1554,1561,1568,1575,1582,1589,1596,1603,1610,1617,1624,1631,1638,1645,1652,1659,1666,1673,1680,1687,1694,1701,1708,1715,1722,1729,1736,1743,1750,1757,1764,1771,1778,1785,1792)\n61ffff # [516] PUSH2(0xffff)\n 6002 # [519] PUSH1(2)\n 55 # [521] SSTORE\n 00 # [522] STOP\n611000 # [523] PUSH2(0x1000)\n 6002 # [526] PUSH1(2)\n 55 # [528] SSTORE\n 00 # [529] STOP\n611001 # [530] PUSH2(0x1001)\n 6002 # [533] PUSH1(2)\n 55 # [535] SSTORE\n 00 # [536] STOP\n611002 # [537] PUSH2(0x1002)\n 6002 # [540] PUSH1(2)\n 55 # [542] SSTORE\n 00 # [543] STOP\n611003 # [544] PUSH2(0x1003)\n 6002 # [547] PUSH1(2)\n 55 # [549] SSTORE\n 00 # [550] STOP\n611004 # [551] PUSH2(0x1004)\n 6002 # [554] PUSH1(2)\n 55 # [556] SSTORE\n 00 # [557] STOP\n611005 # [558] PUSH2(0x1005)\n 6002 # [561] PUSH1(2)\n 55 # [563] SSTORE\n 00 # [564] STOP\n611006 # [565] PUSH2(0x1006)\n 6002 # [568] PUSH1(2)\n 55 # [570] SSTORE\n 00 # [571] STOP\n611007 # [572] PUSH2(0x1007)\n 6002 # [575] PUSH1(2)\n 55 # [577] SSTORE\n 00 # [578] STOP\n611008 # [579] PUSH2(0x1008)\n 6002 # [582] PUSH1(2)\n 55 # [584] SSTORE\n 00 # [585] STOP\n611009 # [586] PUSH2(0x1009)\n 6002 # [589] PUSH1(2)\n 55 # [591] SSTORE\n 00 # [592] STOP\n61100a # [593] PUSH2(0x100a)\n 6002 # [596] PUSH1(2)\n 55 # [598] SSTORE\n 00 # [599] STOP\n61100b # [600] PUSH2(0x100b)\n 6002 # [603] PUSH1(2)\n 55 # [605] SSTORE\n 00 # [606] STOP\n61100c # [607] PUSH2(0x100c)\n 6002 # [610] PUSH1(2)\n 55 # [612] SSTORE\n 00 # [613] STOP\n61100d # [614] PUSH2(0x100d)\n 6002 # [617] PUSH1(2)\n 55 # [619] SSTORE\n 00 # [620] STOP\n61100e # [621] PUSH2(0x100e)\n 6002 # [624] PUSH1(2)\n 55 # [626] SSTORE\n 00 # [627] STOP\n61100f # [628] PUSH2(0x100f)\n 6002 # [631] PUSH1(2)\n 55 # [633] SSTORE\n 00 # [634] STOP\n611010 # [635] PUSH2(0x1010)\n 6002 # [638] PUSH1(2)\n 55 # [640] SSTORE\n 00 # [641] STOP\n611011 # [642] PUSH2(0x1011)\n 6002 # [645] PUSH1(2)\n 55 # [647] SSTORE\n 00 # [648] STOP\n611012 # [649] PUSH2(0x1012)\n 6002 # [652] PUSH1(2)\n 55 # [654] SSTORE\n 00 # [655] STOP\n611013 # [656] PUSH2(0x1013)\n 6002 # [659] PUSH1(2)\n 55 # [661] SSTORE\n 00 # [662] STOP\n611014 # [663] PUSH2(0x1014)\n 6002 # [666] PUSH1(2)\n 55 # [668] SSTORE\n 00 # [669] STOP\n611015 # [670] PUSH2(0x1015)\n 6002 # [673] PUSH1(2)\n 55 # [675] SSTORE\n 00 # [676] STOP\n611016 # [677] PUSH2(0x1016)\n 6002 # [680] PUSH1(2)\n 55 # [682] SSTORE\n 00 # [683] STOP\n611017 # [684] PUSH2(0x1017)\n 6002 # [687] PUSH1(2)\n 55 # [689] SSTORE\n 00 # [690] STOP\n611018 # [691] PUSH2(0x1018)\n 6002 # [694] PUSH1(2)\n 55 # [696] SSTORE\n 00 # [697] STOP\n611019 # [698] PUSH2(0x1019)\n 6002 # [701] PUSH1(2)\n 55 # [703] SSTORE\n 00 # [704] STOP\n61101a # [705] PUSH2(0x101a)\n 6002 # [708] PUSH1(2)\n 55 # [710] SSTORE\n 00 # [711] STOP\n61101b # [712] PUSH2(0x101b)\n 6002 # [715] PUSH1(2)\n 55 # [717] SSTORE\n 00 # [718] STOP\n61101c # [719] PUSH2(0x101c)\n 6002 # [722] PUSH1(2)\n 55 # [724] SSTORE\n 00 # [725] STOP\n61101d # [726] PUSH2(0x101d)\n 6002 # [729] PUSH1(2)\n 55 # [731] SSTORE\n 00 # [732] STOP\n61101e # [733] PUSH2(0x101e)\n 6002 # [736] PUSH1(2)\n 55 # [738] SSTORE\n 00 # [739] STOP\n61101f # [740] PUSH2(0x101f)\n 6002 # [743] PUSH1(2)\n 55 # [745] SSTORE\n 00 # [746] STOP\n611020 # [747] PUSH2(0x1020)\n 6002 # [750] PUSH1(2)\n 55 # [752] SSTORE\n 00 # [753] STOP\n611021 # [754] PUSH2(0x1021)\n 6002 # [757] PUSH1(2)\n 55 # [759] SSTORE\n 00 # [760] STOP\n611022 # [761] PUSH2(0x1022)\n 6002 # [764] PUSH1(2)\n 55 # [766] SSTORE\n 00 # [767] STOP\n611023 # [768] PUSH2(0x1023)\n 6002 # [771] PUSH1(2)\n 55 # [773] SSTORE\n 00 # [774] STOP\n611024 # [775] PUSH2(0x1024)\n 6002 # [778] PUSH1(2)\n 55 # [780] SSTORE\n 00 # [781] STOP\n611025 # [782] PUSH2(0x1025)\n 6002 # [785] PUSH1(2)\n 55 # [787] SSTORE\n 00 # [788] STOP\n611026 # [789] PUSH2(0x1026)\n 6002 # [792] PUSH1(2)\n 55 # [794] SSTORE\n 00 # [795] STOP\n611027 # [796] PUSH2(0x1027)\n 6002 # [799] PUSH1(2)\n 55 # [801] SSTORE\n 00 # [802] STOP\n611028 # [803] PUSH2(0x1028)\n 6002 # [806] PUSH1(2)\n 55 # [808] SSTORE\n 00 # [809] STOP\n611029 # [810] PUSH2(0x1029)\n 6002 # [813] PUSH1(2)\n 55 # [815] SSTORE\n 00 # [816] STOP\n61102a # [817] PUSH2(0x102a)\n 6002 # [820] PUSH1(2)\n 55 # [822] SSTORE\n 00 # [823] STOP\n61102b # [824] PUSH2(0x102b)\n 6002 # [827] PUSH1(2)\n 55 # [829] SSTORE\n 00 # [830] STOP\n61102c # [831] PUSH2(0x102c)\n 6002 # [834] PUSH1(2)\n 55 # [836] SSTORE\n 00 # [837] STOP\n61102d # [838] PUSH2(0x102d)\n 6002 # [841] PUSH1(2)\n 55 # [843] SSTORE\n 00 # [844] STOP\n61102e # [845] PUSH2(0x102e)\n 6002 # [848] PUSH1(2)\n 55 # [850] SSTORE\n 00 # [851] STOP\n61102f # [852] PUSH2(0x102f)\n 6002 # [855] PUSH1(2)\n 55 # [857] SSTORE\n 00 # [858] STOP\n611030 # [859] PUSH2(0x1030)\n 6002 # [862] PUSH1(2)\n 55 # [864] SSTORE\n 00 # [865] STOP\n611031 # [866] PUSH2(0x1031)\n 6002 # [869] PUSH1(2)\n 55 # [871] SSTORE\n 00 # [872] STOP\n611032 # [873] PUSH2(0x1032)\n 6002 # [876] PUSH1(2)\n 55 # [878] SSTORE\n 00 # [879] STOP\n611033 # [880] PUSH2(0x1033)\n 6002 # [883] PUSH1(2)\n 55 # [885] SSTORE\n 00 # [886] STOP\n611034 # [887] PUSH2(0x1034)\n 6002 # [890] PUSH1(2)\n 55 # [892] SSTORE\n 00 # [893] STOP\n611035 # [894] PUSH2(0x1035)\n 6002 # [897] PUSH1(2)\n 55 # [899] SSTORE\n 00 # [900] STOP\n611036 # [901] PUSH2(0x1036)\n 6002 # [904] PUSH1(2)\n 55 # [906] SSTORE\n 00 # [907] STOP\n611037 # [908] PUSH2(0x1037)\n 6002 # [911] PUSH1(2)\n 55 # [913] SSTORE\n 00 # [914] STOP\n611038 # [915] PUSH2(0x1038)\n 6002 # [918] PUSH1(2)\n 55 # [920] SSTORE\n 00 # [921] STOP\n611039 # [922] PUSH2(0x1039)\n 6002 # [925] PUSH1(2)\n 55 # [927] SSTORE\n 00 # [928] STOP\n61103a # [929] PUSH2(0x103a)\n 6002 # [932] PUSH1(2)\n 55 # [934] SSTORE\n 00 # [935] STOP\n61103b # [936] PUSH2(0x103b)\n 6002 # [939] PUSH1(2)\n 55 # [941] SSTORE\n 00 # [942] STOP\n61103c # [943] PUSH2(0x103c)\n 6002 # [946] PUSH1(2)\n 55 # [948] SSTORE\n 00 # [949] STOP\n61103d # [950] PUSH2(0x103d)\n 6002 # [953] PUSH1(2)\n 55 # [955] SSTORE\n 00 # [956] STOP\n61103e # [957] PUSH2(0x103e)\n 6002 # [960] PUSH1(2)\n 55 # [962] SSTORE\n 00 # [963] STOP\n61103f # [964] PUSH2(0x103f)\n 6002 # [967] PUSH1(2)\n 55 # [969] SSTORE\n 00 # [970] STOP\n611040 # [971] PUSH2(0x1040)\n 6002 # [974] PUSH1(2)\n 55 # [976] SSTORE\n 00 # [977] STOP\n611041 # [978] PUSH2(0x1041)\n 6002 # [981] PUSH1(2)\n 55 # [983] SSTORE\n 00 # [984] STOP\n611042 # [985] PUSH2(0x1042)\n 6002 # [988] PUSH1(2)\n 55 # [990] SSTORE\n 00 # [991] STOP\n611043 # [992] PUSH2(0x1043)\n 6002 # [995] PUSH1(2)\n 55 # [997] SSTORE\n 00 # [998] STOP\n611044 # [999] PUSH2(0x1044)\n 6002 # [1002] PUSH1(2)\n 55 # [1004] SSTORE\n 00 # [1005] STOP\n611045 # [1006] PUSH2(0x1045)\n 6002 # [1009] PUSH1(2)\n 55 # [1011] SSTORE\n 00 # [1012] STOP\n611046 # [1013] PUSH2(0x1046)\n 6002 # [1016] PUSH1(2)\n 55 # [1018] SSTORE\n 00 # [1019] STOP\n611047 # [1020] PUSH2(0x1047)\n 6002 # [1023] PUSH1(2)\n 55 # [1025] SSTORE\n 00 # [1026] STOP\n611048 # [1027] PUSH2(0x1048)\n 6002 # [1030] PUSH1(2)\n 55 # [1032] SSTORE\n 00 # [1033] STOP\n611049 # [1034] PUSH2(0x1049)\n 6002 # [1037] PUSH1(2)\n 55 # [1039] SSTORE\n 00 # [1040] STOP\n61104a # [1041] PUSH2(0x104a)\n 6002 # [1044] PUSH1(2)\n 55 # [1046] SSTORE\n 00 # [1047] STOP\n61104b # [1048] PUSH2(0x104b)\n 6002 # [1051] PUSH1(2)\n 55 # [1053] SSTORE\n 00 # [1054] STOP\n61104c # [1055] PUSH2(0x104c)\n 6002 # [1058] PUSH1(2)\n 55 # [1060] SSTORE\n 00 # [1061] STOP\n61104d # [1062] PUSH2(0x104d)\n 6002 # [1065] PUSH1(2)\n 55 # [1067] SSTORE\n 00 # [1068] STOP\n61104e # [1069] PUSH2(0x104e)\n 6002 # [1072] PUSH1(2)\n 55 # [1074] SSTORE\n 00 # [1075] STOP\n61104f # [1076] PUSH2(0x104f)\n 6002 # [1079] PUSH1(2)\n 55 # [1081] SSTORE\n 00 # [1082] STOP\n611050 # [1083] PUSH2(0x1050)\n 6002 # [1086] PUSH1(2)\n 55 # [1088] SSTORE\n 00 # [1089] STOP\n611051 # [1090] PUSH2(0x1051)\n 6002 # [1093] PUSH1(2)\n 55 # [1095] SSTORE\n 00 # [1096] STOP\n611052 # [1097] PUSH2(0x1052)\n 6002 # [1100] PUSH1(2)\n 55 # [1102] SSTORE\n 00 # [1103] STOP\n611053 # [1104] PUSH2(0x1053)\n 6002 # [1107] PUSH1(2)\n 55 # [1109] SSTORE\n 00 # [1110] STOP\n611054 # [1111] PUSH2(0x1054)\n 6002 # [1114] PUSH1(2)\n 55 # [1116] SSTORE\n 00 # [1117] STOP\n611055 # [1118] PUSH2(0x1055)\n 6002 # [1121] PUSH1(2)\n 55 # [1123] SSTORE\n 00 # [1124] STOP\n611056 # [1125] PUSH2(0x1056)\n 6002 # [1128] PUSH1(2)\n 55 # [1130] SSTORE\n 00 # [1131] STOP\n611057 # [1132] PUSH2(0x1057)\n 6002 # [1135] PUSH1(2)\n 55 # [1137] SSTORE\n 00 # [1138] STOP\n611058 # [1139] PUSH2(0x1058)\n 6002 # [1142] PUSH1(2)\n 55 # [1144] SSTORE\n 00 # [1145] STOP\n611059 # [1146] PUSH2(0x1059)\n 6002 # [1149] PUSH1(2)\n 55 # [1151] SSTORE\n 00 # [1152] STOP\n61105a # [1153] PUSH2(0x105a)\n 6002 # [1156] PUSH1(2)\n 55 # [1158] SSTORE\n 00 # [1159] STOP\n61105b # [1160] PUSH2(0x105b)\n 6002 # [1163] PUSH1(2)\n 55 # [1165] SSTORE\n 00 # [1166] STOP\n61105c # [1167] PUSH2(0x105c)\n 6002 # [1170] PUSH1(2)\n 55 # [1172] SSTORE\n 00 # [1173] STOP\n61105d # [1174] PUSH2(0x105d)\n 6002 # [1177] PUSH1(2)\n 55 # [1179] SSTORE\n 00 # [1180] STOP\n61105e # [1181] PUSH2(0x105e)\n 6002 # [1184] PUSH1(2)\n 55 # [1186] SSTORE\n 00 # [1187] STOP\n61105f # [1188] PUSH2(0x105f)\n 6002 # [1191] PUSH1(2)\n 55 # [1193] SSTORE\n 00 # [1194] STOP\n611060 # [1195] PUSH2(0x1060)\n 6002 # [1198] PUSH1(2)\n 55 # [1200] SSTORE\n 00 # [1201] STOP\n611061 # [1202] PUSH2(0x1061)\n 6002 # [1205] PUSH1(2)\n 55 # [1207] SSTORE\n 00 # [1208] STOP\n611062 # [1209] PUSH2(0x1062)\n 6002 # [1212] PUSH1(2)\n 55 # [1214] SSTORE\n 00 # [1215] STOP\n611063 # [1216] PUSH2(0x1063)\n 6002 # [1219] PUSH1(2)\n 55 # [1221] SSTORE\n 00 # [1222] STOP\n611064 # [1223] PUSH2(0x1064)\n 6002 # [1226] PUSH1(2)\n 55 # [1228] SSTORE\n 00 # [1229] STOP\n611065 # [1230] PUSH2(0x1065)\n 6002 # [1233] PUSH1(2)\n 55 # [1235] SSTORE\n 00 # [1236] STOP\n611066 # [1237] PUSH2(0x1066)\n 6002 # [1240] PUSH1(2)\n 55 # [1242] SSTORE\n 00 # [1243] STOP\n611067 # [1244] PUSH2(0x1067)\n 6002 # [1247] PUSH1(2)\n 55 # [1249] SSTORE\n 00 # [1250] STOP\n611068 # [1251] PUSH2(0x1068)\n 6002 # [1254] PUSH1(2)\n 55 # [1256] SSTORE\n 00 # [1257] STOP\n611069 # [1258] PUSH2(0x1069)\n 6002 # [1261] PUSH1(2)\n 55 # [1263] SSTORE\n 00 # [1264] STOP\n61106a # [1265] PUSH2(0x106a)\n 6002 # [1268] PUSH1(2)\n 55 # [1270] SSTORE\n 00 # [1271] STOP\n61106b # [1272] PUSH2(0x106b)\n 6002 # [1275] PUSH1(2)\n 55 # [1277] SSTORE\n 00 # [1278] STOP\n61106c # [1279] PUSH2(0x106c)\n 6002 # [1282] PUSH1(2)\n 55 # [1284] SSTORE\n 00 # [1285] STOP\n61106d # [1286] PUSH2(0x106d)\n 6002 # [1289] PUSH1(2)\n 55 # [1291] SSTORE\n 00 # [1292] STOP\n61106e # [1293] PUSH2(0x106e)\n 6002 # [1296] PUSH1(2)\n 55 # [1298] SSTORE\n 00 # [1299] STOP\n61106f # [1300] PUSH2(0x106f)\n 6002 # [1303] PUSH1(2)\n 55 # [1305] SSTORE\n 00 # [1306] STOP\n611070 # [1307] PUSH2(0x1070)\n 6002 # [1310] PUSH1(2)\n 55 # [1312] SSTORE\n 00 # [1313] STOP\n611071 # [1314] PUSH2(0x1071)\n 6002 # [1317] PUSH1(2)\n 55 # [1319] SSTORE\n 00 # [1320] STOP\n611072 # [1321] PUSH2(0x1072)\n 6002 # [1324] PUSH1(2)\n 55 # [1326] SSTORE\n 00 # [1327] STOP\n611073 # [1328] PUSH2(0x1073)\n 6002 # [1331] PUSH1(2)\n 55 # [1333] SSTORE\n 00 # [1334] STOP\n611074 # [1335] PUSH2(0x1074)\n 6002 # [1338] PUSH1(2)\n 55 # [1340] SSTORE\n 00 # [1341] STOP\n611075 # [1342] PUSH2(0x1075)\n 6002 # [1345] PUSH1(2)\n 55 # [1347] SSTORE\n 00 # [1348] STOP\n611076 # [1349] PUSH2(0x1076)\n 6002 # [1352] PUSH1(2)\n 55 # [1354] SSTORE\n 00 # [1355] STOP\n611077 # [1356] PUSH2(0x1077)\n 6002 # [1359] PUSH1(2)\n 55 # [1361] SSTORE\n 00 # [1362] STOP\n611078 # [1363] PUSH2(0x1078)\n 6002 # [1366] PUSH1(2)\n 55 # [1368] SSTORE\n 00 # [1369] STOP\n611079 # [1370] PUSH2(0x1079)\n 6002 # [1373] PUSH1(2)\n 55 # [1375] SSTORE\n 00 # [1376] STOP\n61107a # [1377] PUSH2(0x107a)\n 6002 # [1380] PUSH1(2)\n 55 # [1382] SSTORE\n 00 # [1383] STOP\n61107b # [1384] PUSH2(0x107b)\n 6002 # [1387] PUSH1(2)\n 55 # [1389] SSTORE\n 00 # [1390] STOP\n61107c # [1391] PUSH2(0x107c)\n 6002 # [1394] PUSH1(2)\n 55 # [1396] SSTORE\n 00 # [1397] STOP\n61107d # [1398] PUSH2(0x107d)\n 6002 # [1401] PUSH1(2)\n 55 # [1403] SSTORE\n 00 # [1404] STOP\n61107e # [1405] PUSH2(0x107e)\n 6002 # [1408] PUSH1(2)\n 55 # [1410] SSTORE\n 00 # [1411] STOP\n61107f # [1412] PUSH2(0x107f)\n 6002 # [1415] PUSH1(2)\n 55 # [1417] SSTORE\n 00 # [1418] STOP\n611080 # [1419] PUSH2(0x1080)\n 6002 # [1422] PUSH1(2)\n 55 # [1424] SSTORE\n 00 # [1425] STOP\n611081 # [1426] PUSH2(0x1081)\n 6002 # [1429] PUSH1(2)\n 55 # [1431] SSTORE\n 00 # [1432] STOP\n611082 # [1433] PUSH2(0x1082)\n 6002 # [1436] PUSH1(2)\n 55 # [1438] SSTORE\n 00 # [1439] STOP\n611083 # [1440] PUSH2(0x1083)\n 6002 # [1443] PUSH1(2)\n 55 # [1445] SSTORE\n 00 # [1446] STOP\n611084 # [1447] PUSH2(0x1084)\n 6002 # [1450] PUSH1(2)\n 55 # [1452] SSTORE\n 00 # [1453] STOP\n611085 # [1454] PUSH2(0x1085)\n 6002 # [1457] PUSH1(2)\n 55 # [1459] SSTORE\n 00 # [1460] STOP\n611086 # [1461] PUSH2(0x1086)\n 6002 # [1464] PUSH1(2)\n 55 # [1466] SSTORE\n 00 # [1467] STOP\n611087 # [1468] PUSH2(0x1087)\n 6002 # [1471] PUSH1(2)\n 55 # [1473] SSTORE\n 00 # [1474] STOP\n611088 # [1475] PUSH2(0x1088)\n 6002 # [1478] PUSH1(2)\n 55 # [1480] SSTORE\n 00 # [1481] STOP\n611089 # [1482] PUSH2(0x1089)\n 6002 # [1485] PUSH1(2)\n 55 # [1487] SSTORE\n 00 # [1488] STOP\n61108a # [1489] PUSH2(0x108a)\n 6002 # [1492] PUSH1(2)\n 55 # [1494] SSTORE\n 00 # [1495] STOP\n61108b # [1496] PUSH2(0x108b)\n 6002 # [1499] PUSH1(2)\n 55 # [1501] SSTORE\n 00 # [1502] STOP\n61108c # [1503] PUSH2(0x108c)\n 6002 # [1506] PUSH1(2)\n 55 # [1508] SSTORE\n 00 # [1509] STOP\n61108d # [1510] PUSH2(0x108d)\n 6002 # [1513] PUSH1(2)\n 55 # [1515] SSTORE\n 00 # [1516] STOP\n61108e # [1517] PUSH2(0x108e)\n 6002 # [1520] PUSH1(2)\n 55 # [1522] SSTORE\n 00 # [1523] STOP\n61108f # [1524] PUSH2(0x108f)\n 6002 # [1527] PUSH1(2)\n 55 # [1529] SSTORE\n 00 # [1530] STOP\n611090 # [1531] PUSH2(0x1090)\n 6002 # [1534] PUSH1(2)\n 55 # [1536] SSTORE\n 00 # [1537] STOP\n611091 # [1538] PUSH2(0x1091)\n 6002 # [1541] PUSH1(2)\n 55 # [1543] SSTORE\n 00 # [1544] STOP\n611092 # [1545] PUSH2(0x1092)\n 6002 # [1548] PUSH1(2)\n 55 # [1550] SSTORE\n 00 # [1551] STOP\n611093 # [1552] PUSH2(0x1093)\n 6002 # [1555] PUSH1(2)\n 55 # [1557] SSTORE\n 00 # [1558] STOP\n611094 # [1559] PUSH2(0x1094)\n 6002 # [1562] PUSH1(2)\n 55 # [1564] SSTORE\n 00 # [1565] STOP\n611095 # [1566] PUSH2(0x1095)\n 6002 # [1569] PUSH1(2)\n 55 # [1571] SSTORE\n 00 # [1572] STOP\n611096 # [1573] PUSH2(0x1096)\n 6002 # [1576] PUSH1(2)\n 55 # [1578] SSTORE\n 00 # [1579] STOP\n611097 # [1580] PUSH2(0x1097)\n 6002 # [1583] PUSH1(2)\n 55 # [1585] SSTORE\n 00 # [1586] STOP\n611098 # [1587] PUSH2(0x1098)\n 6002 # [1590] PUSH1(2)\n 55 # [1592] SSTORE\n 00 # [1593] STOP\n611099 # [1594] PUSH2(0x1099)\n 6002 # [1597] PUSH1(2)\n 55 # [1599] SSTORE\n 00 # [1600] STOP\n61109a # [1601] PUSH2(0x109a)\n 6002 # [1604] PUSH1(2)\n 55 # [1606] SSTORE\n 00 # [1607] STOP\n61109b # [1608] PUSH2(0x109b)\n 6002 # [1611] PUSH1(2)\n 55 # [1613] SSTORE\n 00 # [1614] STOP\n61109c # [1615] PUSH2(0x109c)\n 6002 # [1618] PUSH1(2)\n 55 # [1620] SSTORE\n 00 # [1621] STOP\n61109d # [1622] PUSH2(0x109d)\n 6002 # [1625] PUSH1(2)\n 55 # [1627] SSTORE\n 00 # [1628] STOP\n61109e # [1629] PUSH2(0x109e)\n 6002 # [1632] PUSH1(2)\n 55 # [1634] SSTORE\n 00 # [1635] STOP\n61109f # [1636] PUSH2(0x109f)\n 6002 # [1639] PUSH1(2)\n 55 # [1641] SSTORE\n 00 # [1642] STOP\n6110a0 # [1643] PUSH2(0x10a0)\n 6002 # [1646] PUSH1(2)\n 55 # [1648] SSTORE\n 00 # [1649] STOP\n6110a1 # [1650] PUSH2(0x10a1)\n 6002 # [1653] PUSH1(2)\n 55 # [1655] SSTORE\n 00 # [1656] STOP\n6110a2 # [1657] PUSH2(0x10a2)\n 6002 # [1660] PUSH1(2)\n 55 # [1662] SSTORE\n 00 # [1663] STOP\n6110a3 # [1664] PUSH2(0x10a3)\n 6002 # [1667] PUSH1(2)\n 55 # [1669] SSTORE\n 00 # [1670] STOP\n6110a4 # [1671] PUSH2(0x10a4)\n 6002 # [1674] PUSH1(2)\n 55 # [1676] SSTORE\n 00 # [1677] STOP\n6110a5 # [1678] PUSH2(0x10a5)\n 6002 # [1681] PUSH1(2)\n 55 # [1683] SSTORE\n 00 # [1684] STOP\n6110a6 # [1685] PUSH2(0x10a6)\n 6002 # [1688] PUSH1(2)\n 55 # [1690] SSTORE\n 00 # [1691] STOP\n6110a7 # [1692] PUSH2(0x10a7)\n 6002 # [1695] PUSH1(2)\n 55 # [1697] SSTORE\n 00 # [1698] STOP\n6110a8 # [1699] PUSH2(0x10a8)\n 6002 # [1702] PUSH1(2)\n 55 # [1704] SSTORE\n 00 # [1705] STOP\n6110a9 # [1706] PUSH2(0x10a9)\n 6002 # [1709] PUSH1(2)\n 55 # [1711] SSTORE\n 00 # [1712] STOP\n6110aa # [1713] PUSH2(0x10aa)\n 6002 # [1716] PUSH1(2)\n 55 # [1718] SSTORE\n 00 # [1719] STOP\n6110ab # [1720] PUSH2(0x10ab)\n 6002 # [1723] PUSH1(2)\n 55 # [1725] SSTORE\n 00 # [1726] STOP\n6110ac # [1727] PUSH2(0x10ac)\n 6002 # [1730] PUSH1(2)\n 55 # [1732] SSTORE\n 00 # [1733] STOP\n6110ad # [1734] PUSH2(0x10ad)\n 6002 # [1737] PUSH1(2)\n 55 # [1739] SSTORE\n 00 # [1740] STOP\n6110ae # [1741] PUSH2(0x10ae)\n 6002 # [1744] PUSH1(2)\n 55 # [1746] SSTORE\n 00 # [1747] STOP\n6110af # [1748] PUSH2(0x10af)\n 6002 # [1751] PUSH1(2)\n 55 # [1753] SSTORE\n 00 # [1754] STOP\n6110b0 # [1755] PUSH2(0x10b0)\n 6002 # [1758] PUSH1(2)\n 55 # [1760] SSTORE\n 00 # [1761] STOP\n6110b1 # [1762] PUSH2(0x10b1)\n 6002 # [1765] PUSH1(2)\n 55 # [1767] SSTORE\n 00 # [1768] STOP\n6110b2 # [1769] PUSH2(0x10b2)\n 6002 # [1772] PUSH1(2)\n 55 # [1774] SSTORE\n 00 # [1775] STOP\n6110b3 # [1776] PUSH2(0x10b3)\n 6002 # [1779] PUSH1(2)\n 55 # [1781] SSTORE\n 00 # [1782] STOP\n6110b4 # [1783] PUSH2(0x10b4)\n 6002 # [1786] PUSH1(2)\n 55 # [1788] SSTORE\n 00 # [1789] STOP\n6110b5 # [1790] PUSH2(0x10b5)\n 6002 # [1793] PUSH1(2)\n 55 # [1795] SSTORE\n 00 # [1796] STOP\n6110b6 # [1797] PUSH2(0x10b6)\n 6002 # [1800] PUSH1(2)\n 55 # [1802] SSTORE\n 00 # [1803] STOP\n6110b7 # [1804] PUSH2(0x10b7)\n 6002 # [1807] PUSH1(2)\n 55 # [1809] SSTORE\n 00 # [1810] STOP\n6110b8 # [1811] PUSH2(0x10b8)\n 6002 # [1814] PUSH1(2)\n 55 # [1816] SSTORE\n 00 # [1817] STOP\n6110b9 # [1818] PUSH2(0x10b9)\n 6002 # [1821] PUSH1(2)\n 55 # [1823] SSTORE\n 00 # [1824] STOP\n6110ba # [1825] PUSH2(0x10ba)\n 6002 # [1828] PUSH1(2)\n 55 # [1830] SSTORE\n 00 # [1831] STOP\n6110bb # [1832] PUSH2(0x10bb)\n 6002 # [1835] PUSH1(2)\n 55 # [1837] SSTORE\n 00 # [1838] STOP\n6110bc # [1839] PUSH2(0x10bc)\n 6002 # [1842] PUSH1(2)\n 55 # [1844] SSTORE\n 00 # [1845] STOP\n6110bd # [1846] PUSH2(0x10bd)\n 6002 # [1849] PUSH1(2)\n 55 # [1851] SSTORE\n 00 # [1852] STOP\n6110be # [1853] PUSH2(0x10be)\n 6002 # [1856] PUSH1(2)\n 55 # [1858] SSTORE\n 00 # [1859] STOP\n6110bf # [1860] PUSH2(0x10bf)\n 6002 # [1863] PUSH1(2)\n 55 # [1865] SSTORE\n 00 # [1866] STOP\n6110c0 # [1867] PUSH2(0x10c0)\n 6002 # [1870] PUSH1(2)\n 55 # [1872] SSTORE\n 00 # [1873] STOP\n6110c1 # [1874] PUSH2(0x10c1)\n 6002 # [1877] PUSH1(2)\n 55 # [1879] SSTORE\n 00 # [1880] STOP\n6110c2 # [1881] PUSH2(0x10c2)\n 6002 # [1884] PUSH1(2)\n 55 # [1886] SSTORE\n 00 # [1887] STOP\n6110c3 # [1888] PUSH2(0x10c3)\n 6002 # [1891] PUSH1(2)\n 55 # [1893] SSTORE\n 00 # [1894] STOP\n6110c4 # [1895] PUSH2(0x10c4)\n 6002 # [1898] PUSH1(2)\n 55 # [1900] SSTORE\n 00 # [1901] STOP\n6110c5 # [1902] PUSH2(0x10c5)\n 6002 # [1905] PUSH1(2)\n 55 # [1907] SSTORE\n 00 # [1908] STOP\n6110c6 # [1909] PUSH2(0x10c6)\n 6002 # [1912] PUSH1(2)\n 55 # [1914] SSTORE\n 00 # [1915] STOP\n6110c7 # [1916] PUSH2(0x10c7)\n 6002 # [1919] PUSH1(2)\n 55 # [1921] SSTORE\n 00 # [1922] STOP\n6110c8 # [1923] PUSH2(0x10c8)\n 6002 # [1926] PUSH1(2)\n 55 # [1928] SSTORE\n 00 # [1929] STOP\n6110c9 # [1930] PUSH2(0x10c9)\n 6002 # [1933] PUSH1(2)\n 55 # [1935] SSTORE\n 00 # [1936] STOP\n6110ca # [1937] PUSH2(0x10ca)\n 6002 # [1940] PUSH1(2)\n 55 # [1942] SSTORE\n 00 # [1943] STOP\n6110cb # [1944] PUSH2(0x10cb)\n 6002 # [1947] PUSH1(2)\n 55 # [1949] SSTORE\n 00 # [1950] STOP\n6110cc # [1951] PUSH2(0x10cc)\n 6002 # [1954] PUSH1(2)\n 55 # [1956] SSTORE\n 00 # [1957] STOP\n6110cd # [1958] PUSH2(0x10cd)\n 6002 # [1961] PUSH1(2)\n 55 # [1963] SSTORE\n 00 # [1964] STOP\n6110ce # [1965] PUSH2(0x10ce)\n 6002 # [1968] PUSH1(2)\n 55 # [1970] SSTORE\n 00 # [1971] STOP\n6110cf # [1972] PUSH2(0x10cf)\n 6002 # [1975] PUSH1(2)\n 55 # [1977] SSTORE\n 00 # [1978] STOP\n6110d0 # [1979] PUSH2(0x10d0)\n 6002 # [1982] PUSH1(2)\n 55 # [1984] SSTORE\n 00 # [1985] STOP\n6110d1 # [1986] PUSH2(0x10d1)\n 6002 # [1989] PUSH1(2)\n 55 # [1991] SSTORE\n 00 # [1992] STOP\n6110d2 # [1993] PUSH2(0x10d2)\n 6002 # [1996] PUSH1(2)\n 55 # [1998] SSTORE\n 00 # [1999] STOP\n6110d3 # [2000] PUSH2(0x10d3)\n 6002 # [2003] PUSH1(2)\n 55 # [2005] SSTORE\n 00 # [2006] STOP\n6110d4 # [2007] PUSH2(0x10d4)\n 6002 # [2010] PUSH1(2)\n 55 # [2012] SSTORE\n 00 # [2013] STOP\n6110d5 # [2014] PUSH2(0x10d5)\n 6002 # [2017] PUSH1(2)\n 55 # [2019] SSTORE\n 00 # [2020] STOP\n6110d6 # [2021] PUSH2(0x10d6)\n 6002 # [2024] PUSH1(2)\n 55 # [2026] SSTORE\n 00 # [2027] STOP\n6110d7 # [2028] PUSH2(0x10d7)\n 6002 # [2031] PUSH1(2)\n 55 # [2033] SSTORE\n 00 # [2034] STOP\n6110d8 # [2035] PUSH2(0x10d8)\n 6002 # [2038] PUSH1(2)\n 55 # [2040] SSTORE\n 00 # [2041] STOP\n6110d9 # [2042] PUSH2(0x10d9)\n 6002 # [2045] PUSH1(2)\n 55 # [2047] SSTORE\n 00 # [2048] STOP\n6110da # [2049] PUSH2(0x10da)\n 6002 # [2052] PUSH1(2)\n 55 # [2054] SSTORE\n 00 # [2055] STOP\n6110db # [2056] PUSH2(0x10db)\n 6002 # [2059] PUSH1(2)\n 55 # [2061] SSTORE\n 00 # [2062] STOP\n6110dc # [2063] PUSH2(0x10dc)\n 6002 # [2066] PUSH1(2)\n 55 # [2068] SSTORE\n 00 # [2069] STOP\n6110dd # [2070] PUSH2(0x10dd)\n 6002 # [2073] PUSH1(2)\n 55 # [2075] SSTORE\n 00 # [2076] STOP\n6110de # [2077] PUSH2(0x10de)\n 6002 # [2080] PUSH1(2)\n 55 # [2082] SSTORE\n 00 # [2083] STOP\n6110df # [2084] PUSH2(0x10df)\n 6002 # [2087] PUSH1(2)\n 55 # [2089] SSTORE\n 00 # [2090] STOP\n6110e0 # [2091] PUSH2(0x10e0)\n 6002 # [2094] PUSH1(2)\n 55 # [2096] SSTORE\n 00 # [2097] STOP\n6110e1 # [2098] PUSH2(0x10e1)\n 6002 # [2101] PUSH1(2)\n 55 # [2103] SSTORE\n 00 # [2104] STOP\n6110e2 # [2105] PUSH2(0x10e2)\n 6002 # [2108] PUSH1(2)\n 55 # [2110] SSTORE\n 00 # [2111] STOP\n6110e3 # [2112] PUSH2(0x10e3)\n 6002 # [2115] PUSH1(2)\n 55 # [2117] SSTORE\n 00 # [2118] STOP\n6110e4 # [2119] PUSH2(0x10e4)\n 6002 # [2122] PUSH1(2)\n 55 # [2124] SSTORE\n 00 # [2125] STOP\n6110e5 # [2126] PUSH2(0x10e5)\n 6002 # [2129] PUSH1(2)\n 55 # [2131] SSTORE\n 00 # [2132] STOP\n6110e6 # [2133] PUSH2(0x10e6)\n 6002 # [2136] PUSH1(2)\n 55 # [2138] SSTORE\n 00 # [2139] STOP\n6110e7 # [2140] PUSH2(0x10e7)\n 6002 # [2143] PUSH1(2)\n 55 # [2145] SSTORE\n 00 # [2146] STOP\n6110e8 # [2147] PUSH2(0x10e8)\n 6002 # [2150] PUSH1(2)\n 55 # [2152] SSTORE\n 00 # [2153] STOP\n6110e9 # [2154] PUSH2(0x10e9)\n 6002 # [2157] PUSH1(2)\n 55 # [2159] SSTORE\n 00 # [2160] STOP\n6110ea # [2161] PUSH2(0x10ea)\n 6002 # [2164] PUSH1(2)\n 55 # [2166] SSTORE\n 00 # [2167] STOP\n6110eb # [2168] PUSH2(0x10eb)\n 6002 # [2171] PUSH1(2)\n 55 # [2173] SSTORE\n 00 # [2174] STOP\n6110ec # [2175] PUSH2(0x10ec)\n 6002 # [2178] PUSH1(2)\n 55 # [2180] SSTORE\n 00 # [2181] STOP\n6110ed # [2182] PUSH2(0x10ed)\n 6002 # [2185] PUSH1(2)\n 55 # [2187] SSTORE\n 00 # [2188] STOP\n6110ee # [2189] PUSH2(0x10ee)\n 6002 # [2192] PUSH1(2)\n 55 # [2194] SSTORE\n 00 # [2195] STOP\n6110ef # [2196] PUSH2(0x10ef)\n 6002 # [2199] PUSH1(2)\n 55 # [2201] SSTORE\n 00 # [2202] STOP\n6110f0 # [2203] PUSH2(0x10f0)\n 6002 # [2206] PUSH1(2)\n 55 # [2208] SSTORE\n 00 # [2209] STOP\n6110f1 # [2210] PUSH2(0x10f1)\n 6002 # [2213] PUSH1(2)\n 55 # [2215] SSTORE\n 00 # [2216] STOP\n6110f2 # [2217] PUSH2(0x10f2)\n 6002 # [2220] PUSH1(2)\n 55 # [2222] SSTORE\n 00 # [2223] STOP\n6110f3 # [2224] PUSH2(0x10f3)\n 6002 # [2227] PUSH1(2)\n 55 # [2229] SSTORE\n 00 # [2230] STOP\n6110f4 # [2231] PUSH2(0x10f4)\n 6002 # [2234] PUSH1(2)\n 55 # [2236] SSTORE\n 00 # [2237] STOP\n6110f5 # [2238] PUSH2(0x10f5)\n 6002 # [2241] PUSH1(2)\n 55 # [2243] SSTORE\n 00 # [2244] STOP\n6110f6 # [2245] PUSH2(0x10f6)\n 6002 # [2248] PUSH1(2)\n 55 # [2250] SSTORE\n 00 # [2251] STOP\n6110f7 # [2252] PUSH2(0x10f7)\n 6002 # [2255] PUSH1(2)\n 55 # [2257] SSTORE\n 00 # [2258] STOP\n6110f8 # [2259] PUSH2(0x10f8)\n 6002 # [2262] PUSH1(2)\n 55 # [2264] SSTORE\n 00 # [2265] STOP\n6110f9 # [2266] PUSH2(0x10f9)\n 6002 # [2269] PUSH1(2)\n 55 # [2271] SSTORE\n 00 # [2272] STOP\n6110fa # [2273] PUSH2(0x10fa)\n 6002 # [2276] PUSH1(2)\n 55 # [2278] SSTORE\n 00 # [2279] STOP\n6110fb # [2280] PUSH2(0x10fb)\n 6002 # [2283] PUSH1(2)\n 55 # [2285] SSTORE\n 00 # [2286] STOP\n6110fc # [2287] PUSH2(0x10fc)\n 6002 # [2290] PUSH1(2)\n 55 # [2292] SSTORE\n 00 # [2293] STOP\n6110fd # [2294] PUSH2(0x10fd)\n 6002 # [2297] PUSH1(2)\n 55 # [2299] SSTORE\n 00 # [2300] STOP\n6110fe # [2301] PUSH2(0x10fe)\n 6002 # [2304] PUSH1(2)\n 55 # [2306] SSTORE\n 00 # [2307] STOP\n6110ff # [2308] PUSH2(0x10ff)\n 6002 # [2311] PUSH1(2)\n 55 # [2313] SSTORE\n 00 # [2314] STOP\n # Data section (empty)\n" +} \ No newline at end of file diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/rjumpv.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/rjumpv.json new file mode 100644 index 00000000000..08a7df4965b --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/rjumpv.json @@ -0,0 +1,8 @@ +{ + "cli": [ + "pretty-print", + "0xEF0001010004020001001304000000008000026000e20200030000fff65b5b00600160015500" + ], + "stdin": "", + "stdout": "0x # EOF\nef0001 # Magic and Version ( 1 )\n010004 # Types length ( 4 )\n020001 # Total code sections ( 1 )\n 0013 # Code section 0 , 19 bytes\n040000 # Data section length( 0 )\n 00 # Terminator (end of header)\n # Code section 0 types\n 00 # 0 inputs \n 80 # 0 outputs (Non-returning function)\n 0002 # max stack: 2\n # Code section 0 - in=0 out=non-returning height=2\n 6000 # [0] PUSH1(0)\ne20200030000fff6 # [2] RJUMPV(3,0,-10)\n 5b # [10] NOOP\n 5b # [11] NOOP\n 00 # [12] STOP\n 6001 # [13] PUSH1(1)\n 6001 # [15] PUSH1(1)\n 55 # [17] SSTORE\n 00 # [18] STOP\n # Data section (empty)\n" +} \ No newline at end of file diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/subcontainers.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/subcontainers.json new file mode 100644 index 00000000000..e277ea73e2b --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/pretty-print/subcontainers.json @@ -0,0 +1,8 @@ +{ + "cli": [ + "pretty-print", + "0xef000101000402000100130300010043040000000080000436600060003736600060006000ec0060005500ef0001010004020001000b03000100200400000000800003366000600037366000ee00ef0001010004020001000d0400400000800002d10000600055d1002060015500" + ], + "stdin": "", + "stdout": "0x # EOF\nef0001 # Magic and Version ( 1 )\n010004 # Types length ( 4 )\n020001 # Total code sections ( 1 )\n 0013 # Code section 0 , 19 bytes\n030001 # Total subcontainers ( 1 )\n 0043 # Sub container 0, 67 byte\n040000 # Data section length( 0 )\n 00 # Terminator (end of header)\n # Code section 0 types\n 00 # 0 inputs \n 80 # 0 outputs (Non-returning function)\n 0004 # max stack: 4\n # Code section 0 - in=0 out=non-returning height=4\n 36 # [0] CALLDATASIZE\n 6000 # [1] PUSH1(0)\n 6000 # [3] PUSH1(0)\n 37 # [5] CALLDATACOPY\n 36 # [6] CALLDATASIZE\n 6000 # [7] PUSH1(0)\n 6000 # [9] PUSH1(0)\n 6000 # [11] PUSH1(0)\n ec00 # [13] EOFCREATE(0)\n 6000 # [15] PUSH1(0)\n 55 # [17] SSTORE\n 00 # [18] STOP\n # Subcontainer 0 starts here\n ef0001 # Magic and Version ( 1 )\n 010004 # Types length ( 4 )\n 020001 # Total code sections ( 1 )\n 000b # Code section 0 , 11 bytes\n 030001 # Total subcontainers ( 1 )\n 0020 # Sub container 0, 32 byte\n 040000 # Data section length( 0 ) \n 00 # Terminator (end of header)\n # Code section 0 types\n 00 # 0 inputs \n 80 # 0 outputs (Non-returning function)\n 0003 # max stack: 3\n # Code section 0 - in=0 out=non-returning height=3\n 36 # [0] CALLDATASIZE\n 6000 # [1] PUSH1(0)\n 6000 # [3] PUSH1(0)\n 37 # [5] CALLDATACOPY\n 36 # [6] CALLDATASIZE\n 6000 # [7] PUSH1(0)\n ee00 # [9] RETURNCONTRACT(0)\n # Subcontainer 0.0 starts here\n ef0001 # Magic and Version ( 1 )\n 010004 # Types length ( 4 )\n 020001 # Total code sections ( 1 )\n 000d # Code section 0 , 13 bytes\n 040040 # Data section length( 64 ) (actual size 0) \n 00 # Terminator (end of header)\n # Code section 0 types\n 00 # 0 inputs \n 80 # 0 outputs (Non-returning function)\n 0002 # max stack: 2\n # Code section 0 - in=0 out=non-returning height=2\n d10000 # [0] DATALOADN(0x0000)\n 6000 # [3] PUSH1(0)\n 55 # [5] SSTORE\n d10020 # [6] DATALOADN(0x0020)\n 6001 # [9] PUSH1(1)\n 55 # [11] SSTORE\n 00 # [12] STOP\n # Data section (empty)\n # Subcontainer 0.0 ends\n # Data section (empty)\n # Subcontainer 0 ends\n # Data section (empty)\n" +} \ No newline at end of file diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/all-trace-flags-disabled.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/all-trace-flags-disabled.json index e4b8bc37ac5..adaa08952fd 100644 --- a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/all-trace-flags-disabled.json +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/all-trace-flags-disabled.json @@ -84,7 +84,7 @@ {"pc":8,"op":96,"gas":"0x9ffffadec","gasCost":"0x3","memSize":0,"depth":1,"refund":0,"opName":"PUSH1"}, {"pc":10,"op":115,"gas":"0x9ffffade9","gasCost":"0x3","memSize":0,"depth":1,"refund":0,"opName":"PUSH20"}, {"pc":31,"op":100,"gas":"0x9ffffade6","gasCost":"0x3","memSize":0,"depth":1,"refund":0,"opName":"PUSH5"}, - {"pc":37,"op":241,"gas":"0x9ffffade3","gasCost":"0xa28","memSize":0,"depth":1,"refund":0,"opName":"CALL"}, + {"pc":37,"op":241,"gas":"0x9ffffade3","gasCost":"0x900000a28","memSize":0,"depth":1,"refund":0,"opName":"CALL"}, {"pc":0,"op":125,"gas":"0x900000000","gasCost":"0x3","memSize":0,"depth":2,"refund":0,"opName":"PUSH30"}, {"pc":31,"op":96,"gas":"0x8fffffffd","gasCost":"0x3","memSize":0,"depth":2,"refund":0,"opName":"PUSH1"}, {"pc":33,"op":82,"gas":"0x8fffffffa","gasCost":"0x6","memSize":0,"depth":2,"refund":0,"opName":"MSTORE"}, diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/all-trace-flags.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/all-trace-flags.json index 7ab16b709c7..315b85b3a9d 100644 --- a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/all-trace-flags.json +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/all-trace-flags.json @@ -84,7 +84,7 @@ {"pc":8,"op":96,"gas":"0x9ffffadec","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH1"}, {"pc":10,"op":115,"gas":"0x9ffffade9","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH20"}, {"pc":31,"op":100,"gas":"0x9ffffade6","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0xaabbccdd5c57f15886f9b263e2f6d2d6c7b5ec6"],"depth":1,"refund":0,"opName":"PUSH5"}, - {"pc":37,"op":241,"gas":"0x9ffffade3","gasCost":"0xa28","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0xaabbccdd5c57f15886f9b263e2f6d2d6c7b5ec6","0x900000000"],"depth":1,"refund":0,"opName":"CALL"}, + {"pc":37,"op":241,"gas":"0x9ffffade3","gasCost":"0x900000a28","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0xaabbccdd5c57f15886f9b263e2f6d2d6c7b5ec6","0x900000000"],"depth":1,"refund":0,"opName":"CALL"}, {"pc":0,"op":125,"gas":"0x900000000","gasCost":"0x3","memSize":0,"stack":[],"depth":2,"refund":0,"opName":"PUSH30"}, {"pc":31,"op":96,"gas":"0x8fffffffd","gasCost":"0x3","memSize":0,"stack":["0x111122223333444455556666777788889999aaaabbbbccccddddeeeeffff"],"depth":2,"refund":0,"opName":"PUSH1"}, {"pc":33,"op":82,"gas":"0x8fffffffa","gasCost":"0x6","memSize":0,"stack":["0x111122223333444455556666777788889999aaaabbbbccccddddeeeeffff","0x0"],"depth":2,"refund":0,"opName":"MSTORE"}, diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/blockhash.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/blockhash.json index de44541641d..ad87a8238f4 100644 --- a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/blockhash.json +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/blockhash.json @@ -99,6 +99,6 @@ {"pc":81,"op":72,"gas":"0x79bc22","gasCost":"0x2","memSize":0,"stack":["0x0","0x1","0x1","0x2","0x2","0xffff","0x1f4","0x78859e5b97166c486532b1595a673e9f9073643f1b519c6f18511b9913","0x2","0x389","0x0","0x0","0x1","0x0","0x3e3d6d5ff042148d326c1898713a76759ca273","0x44852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d","0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b"],"depth":1,"refund":0,"opName":"BASEFEE"}, {"pc":82,"op":8,"gas":"0x79bc20","gasCost":"0x8","memSize":0,"stack":["0x0","0x1","0x1","0x2","0x2","0xffff","0x1f4","0x78859e5b97166c486532b1595a673e9f9073643f1b519c6f18511b9913","0x2","0x389","0x0","0x0","0x1","0x0","0x3e3d6d5ff042148d326c1898713a76759ca273","0x44852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d","0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b","0x10"],"depth":1,"refund":0,"opName":"ADDMOD"}, {"pc":83,"op":62,"gas":"0x79bc18","gasCost":"0x0","memSize":0,"stack":["0x0","0x1","0x1","0x2","0x2","0xffff","0x1f4","0x78859e5b97166c486532b1595a673e9f9073643f1b519c6f18511b9913","0x2","0x389","0x0","0x0","0x1","0x0","0x3e3d6d5ff042148d326c1898713a76759ca273","0xb94f5374fce5edbc8e2a8697c15331677e6ebf1b"],"depth":1,"refund":0,"opName":"RETURNDATACOPY","error":"Out of bounds"}, - {"output":"","gasUsed":"0x7a1200","test":"00000936-mixed-1","fork":"Shanghai","d":0,"g":0,"v":0,"postHash":"0xd14c10ed22a1cfb642e374be985ac581c39f3969bd59249e0405aca3beb47a47","postLogsHash":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","pass":false} + {"output":"","gasUsed":"0x7a1200","test":"00000936-mixed-1","fork":"Shanghai","d":0,"g":0,"v":0,"postHash":"0xd14c10ed22a1cfb642e374be985ac581c39f3969bd59249e0405aca3beb47a47","postLogsHash":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","pass":false,"error":"INVALID_RETURN_DATA_BUFFER_ACCESS"} ] } diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/create-eof.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/create-eof.json new file mode 100644 index 00000000000..e924043042e --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/create-eof.json @@ -0,0 +1,86 @@ +{ + "cli": [ + "state-test", + "stdin", + "--trace", + "--trace.memory", + "--trace.stack", + "--trace.returndata", + "--notime" + ], + "stdin": { + "create-eof": { + "env": { + "currentCoinbase": "b94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "currentDifficulty": "0x20000", + "currentRandom": "0x0000000000000000000000000000000000000000000000000000000000020000", + "currentGasLimit": "0x26e1f476fe1e22", + "currentNumber": "0x2", + "currentTimestamp": "0x3e8", + "previousHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "currentBaseFee": "0x10" + }, + "pre": { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "code": "0x", + "storage": {}, + "balance": "0xffffffffff", + "nonce": "0x0" + } + }, + "transaction": { + "gasPrice": "0x10", + "nonce": "0x0", + "to": null, + "data": [ + "ef00010100040200010009030001001404000000008000035f355f5fa15f5fee00ef00010100040200010001040000000080000000c0de471fe5" + ], + "gasLimit": [ + "0x7a1200" + ], + "value": [ + "0xdbbe" + ], + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + }, + "out": "0x", + "post": { + "CancunEOF": [ + { + "hash": "0x1a8642a04dae90535f00f53d3a30284c4db051d508a653db89eb100ba9aecbf3", + "logs": "0xf48b954a6a6f4ce6b28e4950b7027413f4bdc8f459df6003b6e8d7a1567c8940", + "indexes": { + "data": 0, + "gas": 0, + "value": 0 + } + } + ], + "Cancun": [ + { + "hash": "0xaa80d89bc89f58da8de41d3894bd1a241896ff91f7a5964edaefb39e8e3a4a98", + "logs": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "indexes": { + "data": 0, + "gas": 0, + "value": 0 + } + } + ] + } + } + }, + "stdout": [ + {"pc":0,"section":0,"op":95,"gas":"0x794068","gasCost":"0x2","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":1,"section":0,"op":53,"gas":"0x794066","gasCost":"0x3","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"CALLDATALOAD"}, + {"pc":2,"section":0,"op":95,"gas":"0x794063","gasCost":"0x2","memSize":0,"stack":["0xc0de471fe5000000000000000000000000000000000000000000000000000000"],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":3,"section":0,"op":95,"gas":"0x794061","gasCost":"0x2","memSize":0,"stack":["0xc0de471fe5000000000000000000000000000000000000000000000000000000","0x0"],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":4,"section":0,"op":161,"gas":"0x79405f","gasCost":"0x2ee","memSize":0,"stack":["0xc0de471fe5000000000000000000000000000000000000000000000000000000","0x0","0x0"],"depth":1,"refund":0,"opName":"LOG1"}, + {"pc":5,"section":0,"op":95,"gas":"0x793d71","gasCost":"0x2","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":6,"section":0,"op":95,"gas":"0x793d6f","gasCost":"0x2","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":7,"section":0,"op":238,"immediate":"0x00","gas":"0x793d6d","gasCost":"0x0","memSize":0,"stack":["0x0","0x0"],"depth":1,"refund":0,"opName":"RETURNCONTRACT"}, + {"output":"","gasUsed":"0xe433","test":"create-eof","fork":"CancunEOF","d":0,"g":0,"v":0,"postHash":"0x1a8642a04dae90535f00f53d3a30284c4db051d508a653db89eb100ba9aecbf3","postLogsHash":"0xf48b954a6a6f4ce6b28e4950b7027413f4bdc8f459df6003b6e8d7a1567c8940","pass":true}, + {"pc":0,"op":239,"gas":"0x794068","gasCost":"0x0","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"INVALID","error":"Bad instruction"}, + {"output":"","gasUsed":"0x7a1200","test":"create-eof","fork":"Cancun","d":0,"g":0,"v":0,"postHash":"0xaa80d89bc89f58da8de41d3894bd1a241896ff91f7a5964edaefb39e8e3a4a98","postLogsHash":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","pass":true,"error":"INVALID_OPERATION"} + ] +} diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/create-invalid-eof.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/create-invalid-eof.json new file mode 100644 index 00000000000..e2eb67602e9 --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/create-invalid-eof.json @@ -0,0 +1,78 @@ +{ + "cli": [ + "state-test", + "stdin", + "--trace", + "--trace.memory", + "--trace.stack", + "--trace.returndata", + "--notime" + ], + "stdin": { + "create-eof": { + "env": { + "currentCoinbase": "b94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "currentDifficulty": "0x20000", + "currentRandom": "0x0000000000000000000000000000000000000000000000000000000000020000", + "currentGasLimit": "0x26e1f476fe1e22", + "currentNumber": "0x2", + "currentTimestamp": "0x3e8", + "previousHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "currentBaseFee": "0x10" + }, + "pre": { + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "code": "0x", + "storage": {}, + "balance": "0xffffffffff", + "nonce": "0x0" + } + }, + "transaction": { + "gasPrice": "0x10", + "nonce": "0x0", + "to": null, + "data": [ + "ef00011100040200010009030001001404000000008000035f355f5fa15f5fee00ef00010100040200010001040000000080000000c0de471fe5" + ], + "gasLimit": [ + "0x7a1200" + ], + "value": [ + "0xdbbe" + ], + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + }, + "out": "0x", + "post": { + "Prague": [ + { + "hash": "0x1a8642a04dae90535f00f53d3a30284c4db051d508a653db89eb100ba9aecbf3", + "logs": "0xf48b954a6a6f4ce6b28e4950b7027413f4bdc8f459df6003b6e8d7a1567c8940", + "indexes": { + "data": 0, + "gas": 0, + "value": 0 + } + } + ], + "Cancun": [ + { + "hash": "0xaa80d89bc89f58da8de41d3894bd1a241896ff91f7a5964edaefb39e8e3a4a98", + "logs": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "indexes": { + "data": 0, + "gas": 0, + "value": 0 + } + } + ] + } + } + }, + "stdout": [ + {"output":"","gasUsed":"0xd198","test":"create-eof","fork":"Prague","d":0,"g":0,"v":0,"postHash":"0x2a9c58298ba5d4ec86ca682b9fcc9ff67c3fc44dbd39f85a2f9b74bfe4e5178e","postLogsHash":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","pass":false,"error":"Invalid EOF Layout: unexpected_header_kind expected 1 actual 17"}, + {"pc":0,"op":239,"gas":"0x794068","gasCost":"0x0","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"INVALID","error":"Bad instruction"}, + {"output":"","gasUsed":"0x7a1200","test":"create-eof","fork":"Cancun","d":0,"g":0,"v":0,"postHash":"0xaa80d89bc89f58da8de41d3894bd1a241896ff91f7a5964edaefb39e8e3a4a98","postLogsHash":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","pass":true,"error":"INVALID_OPERATION"} + ] +} diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/no-memory.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/no-memory.json index 14a995d26c2..f735eb23d17 100644 --- a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/no-memory.json +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/no-memory.json @@ -82,7 +82,7 @@ {"pc":8,"op":96,"gas":"0x9ffffadec","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH1"}, {"pc":10,"op":115,"gas":"0x9ffffade9","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0"],"depth":1,"refund":0,"opName":"PUSH20"}, {"pc":31,"op":100,"gas":"0x9ffffade6","gasCost":"0x3","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0xaabbccdd5c57f15886f9b263e2f6d2d6c7b5ec6"],"depth":1,"refund":0,"opName":"PUSH5"}, - {"pc":37,"op":241,"gas":"0x9ffffade3","gasCost":"0xa28","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0xaabbccdd5c57f15886f9b263e2f6d2d6c7b5ec6","0x900000000"],"depth":1,"refund":0,"opName":"CALL"}, + {"pc":37,"op":241,"gas":"0x9ffffade3","gasCost":"0x900000a28","memSize":0,"stack":["0x0","0x0","0x0","0x0","0x0","0xaabbccdd5c57f15886f9b263e2f6d2d6c7b5ec6","0x900000000"],"depth":1,"refund":0,"opName":"CALL"}, {"pc":0,"op":125,"gas":"0x900000000","gasCost":"0x3","memSize":0,"stack":[],"depth":2,"refund":0,"opName":"PUSH30"}, {"pc":31,"op":96,"gas":"0x8fffffffd","gasCost":"0x3","memSize":0,"stack":["0x111122223333444455556666777788889999aaaabbbbccccddddeeeeffff"],"depth":2,"refund":0,"opName":"PUSH1"}, {"pc":33,"op":82,"gas":"0x8fffffffa","gasCost":"0x6","memSize":0,"stack":["0x111122223333444455556666777788889999aaaabbbbccccddddeeeeffff","0x0"],"depth":2,"refund":0,"opName":"MSTORE"}, diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/shanghai-blockhash.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/shanghai-blockhash.json new file mode 100644 index 00000000000..b048e11e2a7 --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/state-test/shanghai-blockhash.json @@ -0,0 +1,82 @@ +{ + "cli": [ + "state-test", + "stdin", + "--trace", + "--trace.memory", + "--trace.stack", + "--trace.returndata", + "--notime" + ], + "stdin": { + "shanghai-blockhash": { + "env": { + "currentCoinbase": "b94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "currentDifficulty": "0x20000", + "currentRandom": "0x0000000000000000000000000000000000000000000000000000000000020000", + "currentGasLimit": "0x26e1f476fe1e22", + "currentNumber": "0x2", + "currentTimestamp": "0x3e8", + "previousHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "currentBaseFee": "0x10" + }, + "pre": { + "0x1000000000000000000000000000000000000000": { + "nonce": "0x00", + "balance": "0x0ba1a9ce0ba1a9ce", + "code": "0x600040600055600140600155434060025500", + "storage": {} + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0x1319ad251a00", + "storage": {} + } + }, + "transaction": { + "gasPrice": "0x10", + "nonce": "0x0", + "to": "0x1000000000000000000000000000000000000000", + "data": [ + "0x" + ], + "gasLimit": [ + "0x7a1200" + ], + "value": [ + "0x0" + ], + "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" + }, + "out": "0x", + "post": { + "Shanghai": [ + { + "hash": "0xb3967c897314312cf275055d754c6742d351a2a4bcf0121ed2f896971bf56563", + "logs": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "indexes": { + "data": 0, + "gas": 0, + "value": 0 + } + } + ] + } + } + }, + "stdout": [ + {"pc":0,"op":96,"gas":"0x79bff8","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":2,"op":64,"gas":"0x79bff5","gasCost":"0x14","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"BLOCKHASH"}, + {"pc":3,"op":96,"gas":"0x79bfe1","gasCost":"0x3","memSize":0,"stack":["0x44852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d"],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":5,"op":85,"gas":"0x79bfde","gasCost":"0x5654","memSize":0,"stack":["0x44852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d","0x0"],"depth":1,"refund":0,"opName":"SSTORE"}, + {"pc":6,"op":96,"gas":"0x79698a","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":8,"op":64,"gas":"0x796987","gasCost":"0x14","memSize":0,"stack":["0x1"],"depth":1,"refund":0,"opName":"BLOCKHASH"}, + {"pc":9,"op":96,"gas":"0x796973","gasCost":"0x3","memSize":0,"stack":["0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6"],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":11,"op":85,"gas":"0x796970","gasCost":"0x5654","memSize":0,"stack":["0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6","0x1"],"depth":1,"refund":0,"opName":"SSTORE"}, + {"pc":12,"op":67,"gas":"0x79131c","gasCost":"0x2","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"NUMBER"}, + {"pc":13,"op":64,"gas":"0x79131a","gasCost":"0x14","memSize":0,"stack":["0x2"],"depth":1,"refund":0,"opName":"BLOCKHASH"}, + {"pc":14,"op":96,"gas":"0x791306","gasCost":"0x3","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":16,"op":85,"gas":"0x791303","gasCost":"0x898","memSize":0,"stack":["0x0","0x2"],"depth":1,"refund":0,"opName":"SSTORE"}, + {"pc":17,"op":0,"gas":"0x790a6b","gasCost":"0x0","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"STOP"}, + {"output":"","gasUsed":"0x10795","test":"shanghai-blockhash","fork":"Shanghai","d":0,"g":0,"v":0,"postHash":"0xb3967c897314312cf275055d754c6742d351a2a4bcf0121ed2f896971bf56563","postLogsHash":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","pass":true} + ] +} diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/cancun-blobs-per-tx.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/cancun-blobs-per-tx.json index 0912b52b81a..d5822aa7d71 100644 --- a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/cancun-blobs-per-tx.json +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/cancun-blobs-per-tx.json @@ -111,8 +111,8 @@ "gasUsed": "0x5208", "currentBaseFee": "0x7", "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "blobGasUsed": "0x0", - "currentExcessBlobGas": "0x0" + "currentExcessBlobGas": "0x0", + "blobGasUsed": "0x20000" } } } diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/prague-deposit.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/prague-deposit.json new file mode 100644 index 00000000000..1a32e50e8b9 --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/prague-deposit.json @@ -0,0 +1,289 @@ +{ + "cli": [ + "t8n", + "--input.alloc=stdin", + "--input.txs=stdin", + "--input.env=stdin", + "--output.result=stdout", + "--output.alloc=stdout", + "--output.body=stdout", + "--state.fork=Prague", + "--state.chainid=1", + "--state.reward=0" + ], + "stdin": { + "alloc": { + "0x00000000219ab540356cbb839cbe05303d7705fa": { + "nonce": "0x01", + "balance": "0x00", + "code": "0x60806040526004361061003f5760003560e01c806301ffc9a71461004457806322895118146100a4578063621fd130146101ba578063c5f2892f14610244575b600080fd5b34801561005057600080fd5b506100906004803603602081101561006757600080fd5b50357fffffffff000000000000000000000000000000000000000000000000000000001661026b565b604080519115158252519081900360200190f35b6101b8600480360360808110156100ba57600080fd5b8101906020810181356401000000008111156100d557600080fd5b8201836020820111156100e757600080fd5b8035906020019184600183028401116401000000008311171561010957600080fd5b91939092909160208101903564010000000081111561012757600080fd5b82018360208201111561013957600080fd5b8035906020019184600183028401116401000000008311171561015b57600080fd5b91939092909160208101903564010000000081111561017957600080fd5b82018360208201111561018b57600080fd5b803590602001918460018302840111640100000000831117156101ad57600080fd5b919350915035610304565b005b3480156101c657600080fd5b506101cf6110b5565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102095781810151838201526020016101f1565b50505050905090810190601f1680156102365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561025057600080fd5b506102596110c7565b60408051918252519081900360200190f35b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a70000000000000000000000000000000000000000000000000000000014806102fe57507fffffffff0000000000000000000000000000000000000000000000000000000082167f8564090700000000000000000000000000000000000000000000000000000000145b92915050565b6030861461035d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118056026913960400191505060405180910390fd5b602084146103b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603681526020018061179c6036913960400191505060405180910390fd5b6060821461040f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806118786029913960400191505060405180910390fd5b670de0b6b3a7640000341015610470576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118526026913960400191505060405180910390fd5b633b9aca003406156104cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260338152602001806117d26033913960400191505060405180910390fd5b633b9aca00340467ffffffffffffffff811115610535576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602781526020018061182b6027913960400191505060405180910390fd5b6060610540826114ba565b90507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c589898989858a8a6105756020546114ba565b6040805160a0808252810189905290819060208201908201606083016080840160c085018e8e80828437600083820152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690910187810386528c815260200190508c8c808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690920188810386528c5181528c51602091820193918e019250908190849084905b83811015610648578181015183820152602001610630565b50505050905090810190601f1680156106755780820380516001836020036101000a031916815260200191505b5086810383528881526020018989808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169092018881038452895181528951602091820193918b019250908190849084905b838110156106ef5781810151838201526020016106d7565b50505050905090810190601f16801561071c5780820380516001836020036101000a031916815260200191505b509d505050505050505050505050505060405180910390a1600060028a8a600060801b604051602001808484808284377fffffffffffffffffffffffffffffffff0000000000000000000000000000000090941691909301908152604080517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0818403018152601090920190819052815191955093508392506020850191508083835b602083106107fc57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016107bf565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610859573d6000803e3d6000fd5b5050506040513d602081101561086e57600080fd5b5051905060006002806108846040848a8c6116fe565b6040516020018083838082843780830192505050925050506040516020818303038152906040526040518082805190602001908083835b602083106108f857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016108bb565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610955573d6000803e3d6000fd5b5050506040513d602081101561096a57600080fd5b5051600261097b896040818d6116fe565b60405160009060200180848480828437919091019283525050604080518083038152602092830191829052805190945090925082918401908083835b602083106109f457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016109b7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610a51573d6000803e3d6000fd5b5050506040513d6020811015610a6657600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610ada57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610a9d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610b37573d6000803e3d6000fd5b5050506040513d6020811015610b4c57600080fd5b50516040805160208101858152929350600092600292839287928f928f92018383808284378083019250505093505050506040516020818303038152906040526040518082805190602001908083835b60208310610bd957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610b9c565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610c36573d6000803e3d6000fd5b5050506040513d6020811015610c4b57600080fd5b50516040518651600291889160009188916020918201918291908601908083835b60208310610ca957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610c6c565b6001836020036101000a0380198251168184511680821785525050505050509050018367ffffffffffffffff191667ffffffffffffffff1916815260180182815260200193505050506040516020818303038152906040526040518082805190602001908083835b60208310610d4e57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610d11565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610dab573d6000803e3d6000fd5b5050506040513d6020811015610dc057600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610e3457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610df7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610e91573d6000803e3d6000fd5b5050506040513d6020811015610ea657600080fd5b50519050858114610f02576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260548152602001806117486054913960600191505060405180910390fd5b60205463ffffffff11610f60576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806117276021913960400191505060405180910390fd5b602080546001019081905560005b60208110156110a9578160011660011415610fa0578260008260208110610f9157fe5b0155506110ac95505050505050565b600260008260208110610faf57fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061102557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610fe8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015611082573d6000803e3d6000fd5b5050506040513d602081101561109757600080fd5b50519250600282049150600101610f6e565b50fe5b50505050505050565b60606110c26020546114ba565b905090565b6020546000908190815b60208110156112f05781600116600114156111e6576002600082602081106110f557fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061116b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161112e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156111c8573d6000803e3d6000fd5b5050506040513d60208110156111dd57600080fd5b505192506112e2565b600283602183602081106111f657fe5b015460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061126b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161122e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156112c8573d6000803e3d6000fd5b5050506040513d60208110156112dd57600080fd5b505192505b6002820491506001016110d1565b506002826112ff6020546114ba565b600060401b6040516020018084815260200183805190602001908083835b6020831061135a57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161131d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790527fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000095909516920191825250604080518083037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8018152601890920190819052815191955093508392850191508083835b6020831061143f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101611402565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa15801561149c573d6000803e3d6000fd5b5050506040513d60208110156114b157600080fd5b50519250505090565b60408051600880825281830190925260609160208201818036833701905050905060c082901b8060071a60f81b826000815181106114f457fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060061a60f81b8260018151811061153757fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060051a60f81b8260028151811061157a57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060041a60f81b826003815181106115bd57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060031a60f81b8260048151811061160057fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060021a60f81b8260058151811061164357fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060011a60f81b8260068151811061168657fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060001a60f81b826007815181106116c957fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535050919050565b6000808585111561170d578182fd5b83861115611719578182fd5b505082019391909203915056fe4465706f736974436f6e74726163743a206d65726b6c6520747265652066756c6c4465706f736974436f6e74726163743a207265636f6e7374727563746564204465706f7369744461746120646f6573206e6f74206d6174636820737570706c696564206465706f7369745f646174615f726f6f744465706f736974436f6e74726163743a20696e76616c6964207769746864726177616c5f63726564656e7469616c73206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c7565206e6f74206d756c7469706c65206f6620677765694465706f736974436f6e74726163743a20696e76616c6964207075626b6579206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f20686967684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f206c6f774465706f736974436f6e74726163743a20696e76616c6964207369676e6174757265206c656e677468a2646970667358221220dceca8706b29e917dacf25fceef95acac8d90d765ac926663ce4096195952b6164736f6c634300060b0033", + "storage": { + "0x22": "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", + "0x23": "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", + "0x24": "0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c", + "0x25": "0x536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c", + "0x26": "0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30", + "0x27": "0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1", + "0x28": "0x87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c", + "0x29": "0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193", + "0x2a": "0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1", + "0x2b": "0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b", + "0x2c": "0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220", + "0x2d": "0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f", + "0x2e": "0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e", + "0x2f": "0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784", + "0x30": "0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb", + "0x31": "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb", + "0x32": "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab", + "0x33": "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", + "0x34": "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f", + "0x35": "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", + "0x36": "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", + "0x37": "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", + "0x38": "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", + "0x39": "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", + "0x3a": "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", + "0x3b": "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", + "0x3c": "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", + "0x3d": "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", + "0x3e": "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", + "0x3f": "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", + "0x40": "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7" + } + }, + "0x00a3ca265ebcb825b45f985a16cefb49958ce017": { + "nonce": "0x01", + "balance": "0x00", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe146090573615156028575f545f5260205ff35b366038141561012e5760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061012e57600154600101600155600354806003026004013381556001015f3581556001016020359055600101600355005b6003546002548082038060101160a4575060105b5f5b81811460dd5780604c02838201600302600401805490600101805490600101549160601b83528260140152906034015260010160a6565b910180921460ed579060025560f8565b90505f6002555f6003555b5f548061049d141561010757505f5b60015460028282011161011c5750505f610122565b01600290035b5f555f600155604c025ff35b5f5ffd", + "storage": {} + }, + "0x000f3df6d732807ef1319fb7b8bb8522d0beac02": { + "nonce": "0x01", + "balance": "0x00", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500", + "storage": {} + }, + "0x0aae40965e6800cd9b1f4b05ff21581047e3f91e": { + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe1460575767ffffffffffffffff5f3511605357600143035f3511604b575f35612000014311604b57611fff5f3516545f5260205ff35b5f5f5260205ff35b5f5ffd5b5f35611fff60014303165500", + "balance": "0x0", + "nonce": "0x1" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "nonce": "0x00", + "balance": "0xad78ebc5ac62000000", + "code": "0x", + "storage": {} + } + }, + "txs": [ + { + "type": "0x0", + "chainId": "0x1", + "nonce": "0x0", + "gasPrice": "0x7", + "gas": "0xf4240", + "to": "0x00000000219ab540356cbb839cbe05303d7705fa", + "value": "0x1bc16d674ec800000", + "input": "0x22895118000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000d0000000000000000000000000000000000000000000000000000000000000011085acb6376c2707b118225da41825974c12b5924a05c6a53b988c9cbc33c55b050000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003", + "v": "0x25", + "r": "0xffeb1d6e7ef8e9ee9b64dcdc3b056f9a1d2b94c1572f1949954e712364604575", + "s": "0x3d0f42bad795205de84db8d4ab10b9abd0d081ffe560cbf45f6c281768112a69", + "sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" + }, + { + "type": "0x0", + "chainId": "0x1", + "nonce": "0x1", + "gasPrice": "0x7", + "gas": "0xf4240", + "to": "0x00000000219ab540356cbb839cbe05303d7705fa", + "value": "0x1bc16d674ec800000", + "input": "0x22895118000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000d0000000000000000000000000000000000000000000000000000000000000011085acb6376c2707b118225da41825974c12b5924a05c6a53b988c9cbc33c55b050000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003", + "v": "0x26", + "r": "0x5bb08e348c9c4b0a2e15d04f4a89d1a210d013205de8d3d93e38e5c1b0c8d8ab", + "s": "0x4300c0f575d9908d2cbc3413ab82895678bb8f3ef356224dd1e7cb972f2c4855", + "sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" + } + ], + "env": { + "currentCoinbase": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentGasLimit": "100000000000000000", + "currentNumber": "1", + "currentTimestamp": "12", + "currentRandom": "0", + "currentDifficulty": "0", + "parentDifficulty": "0", + "parentTimestamp": "0", + "parentBaseFee": "7", + "parentGasUsed": "0", + "parentGasLimit": "100000000000000000", + "parentUncleHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "parentBlobGasUsed": "0", + "parentExcessBlobGas": "0", + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockHashes": { + "0": "0xe4fb5d47f70d54b4f36777ea4c882cf767f93d8f8170285d97a1b8275dfe4dbb" + }, + "ommers": [], + "withdrawals": [], + "parentHash": "0xe4fb5d47f70d54b4f36777ea4c882cf767f93d8f8170285d97a1b8275dfe4dbb" + } + }, + "stdout": { + "alloc": { + "0x00000000219ab540356cbb839cbe05303d7705fa": { + "code": "0x60806040526004361061003f5760003560e01c806301ffc9a71461004457806322895118146100a4578063621fd130146101ba578063c5f2892f14610244575b600080fd5b34801561005057600080fd5b506100906004803603602081101561006757600080fd5b50357fffffffff000000000000000000000000000000000000000000000000000000001661026b565b604080519115158252519081900360200190f35b6101b8600480360360808110156100ba57600080fd5b8101906020810181356401000000008111156100d557600080fd5b8201836020820111156100e757600080fd5b8035906020019184600183028401116401000000008311171561010957600080fd5b91939092909160208101903564010000000081111561012757600080fd5b82018360208201111561013957600080fd5b8035906020019184600183028401116401000000008311171561015b57600080fd5b91939092909160208101903564010000000081111561017957600080fd5b82018360208201111561018b57600080fd5b803590602001918460018302840111640100000000831117156101ad57600080fd5b919350915035610304565b005b3480156101c657600080fd5b506101cf6110b5565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102095781810151838201526020016101f1565b50505050905090810190601f1680156102365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561025057600080fd5b506102596110c7565b60408051918252519081900360200190f35b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a70000000000000000000000000000000000000000000000000000000014806102fe57507fffffffff0000000000000000000000000000000000000000000000000000000082167f8564090700000000000000000000000000000000000000000000000000000000145b92915050565b6030861461035d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118056026913960400191505060405180910390fd5b602084146103b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603681526020018061179c6036913960400191505060405180910390fd5b6060821461040f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806118786029913960400191505060405180910390fd5b670de0b6b3a7640000341015610470576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118526026913960400191505060405180910390fd5b633b9aca003406156104cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260338152602001806117d26033913960400191505060405180910390fd5b633b9aca00340467ffffffffffffffff811115610535576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602781526020018061182b6027913960400191505060405180910390fd5b6060610540826114ba565b90507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c589898989858a8a6105756020546114ba565b6040805160a0808252810189905290819060208201908201606083016080840160c085018e8e80828437600083820152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690910187810386528c815260200190508c8c808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690920188810386528c5181528c51602091820193918e019250908190849084905b83811015610648578181015183820152602001610630565b50505050905090810190601f1680156106755780820380516001836020036101000a031916815260200191505b5086810383528881526020018989808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169092018881038452895181528951602091820193918b019250908190849084905b838110156106ef5781810151838201526020016106d7565b50505050905090810190601f16801561071c5780820380516001836020036101000a031916815260200191505b509d505050505050505050505050505060405180910390a1600060028a8a600060801b604051602001808484808284377fffffffffffffffffffffffffffffffff0000000000000000000000000000000090941691909301908152604080517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0818403018152601090920190819052815191955093508392506020850191508083835b602083106107fc57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016107bf565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610859573d6000803e3d6000fd5b5050506040513d602081101561086e57600080fd5b5051905060006002806108846040848a8c6116fe565b6040516020018083838082843780830192505050925050506040516020818303038152906040526040518082805190602001908083835b602083106108f857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016108bb565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610955573d6000803e3d6000fd5b5050506040513d602081101561096a57600080fd5b5051600261097b896040818d6116fe565b60405160009060200180848480828437919091019283525050604080518083038152602092830191829052805190945090925082918401908083835b602083106109f457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016109b7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610a51573d6000803e3d6000fd5b5050506040513d6020811015610a6657600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610ada57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610a9d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610b37573d6000803e3d6000fd5b5050506040513d6020811015610b4c57600080fd5b50516040805160208101858152929350600092600292839287928f928f92018383808284378083019250505093505050506040516020818303038152906040526040518082805190602001908083835b60208310610bd957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610b9c565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610c36573d6000803e3d6000fd5b5050506040513d6020811015610c4b57600080fd5b50516040518651600291889160009188916020918201918291908601908083835b60208310610ca957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610c6c565b6001836020036101000a0380198251168184511680821785525050505050509050018367ffffffffffffffff191667ffffffffffffffff1916815260180182815260200193505050506040516020818303038152906040526040518082805190602001908083835b60208310610d4e57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610d11565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610dab573d6000803e3d6000fd5b5050506040513d6020811015610dc057600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610e3457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610df7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610e91573d6000803e3d6000fd5b5050506040513d6020811015610ea657600080fd5b50519050858114610f02576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260548152602001806117486054913960600191505060405180910390fd5b60205463ffffffff11610f60576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806117276021913960400191505060405180910390fd5b602080546001019081905560005b60208110156110a9578160011660011415610fa0578260008260208110610f9157fe5b0155506110ac95505050505050565b600260008260208110610faf57fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061102557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610fe8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015611082573d6000803e3d6000fd5b5050506040513d602081101561109757600080fd5b50519250600282049150600101610f6e565b50fe5b50505050505050565b60606110c26020546114ba565b905090565b6020546000908190815b60208110156112f05781600116600114156111e6576002600082602081106110f557fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061116b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161112e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156111c8573d6000803e3d6000fd5b5050506040513d60208110156111dd57600080fd5b505192506112e2565b600283602183602081106111f657fe5b015460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061126b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161122e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156112c8573d6000803e3d6000fd5b5050506040513d60208110156112dd57600080fd5b505192505b6002820491506001016110d1565b506002826112ff6020546114ba565b600060401b6040516020018084815260200183805190602001908083835b6020831061135a57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161131d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790527fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000095909516920191825250604080518083037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8018152601890920190819052815191955093508392850191508083835b6020831061143f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101611402565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa15801561149c573d6000803e3d6000fd5b5050506040513d60208110156114b157600080fd5b50519250505090565b60408051600880825281830190925260609160208201818036833701905050905060c082901b8060071a60f81b826000815181106114f457fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060061a60f81b8260018151811061153757fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060051a60f81b8260028151811061157a57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060041a60f81b826003815181106115bd57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060031a60f81b8260048151811061160057fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060021a60f81b8260058151811061164357fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060011a60f81b8260068151811061168657fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060001a60f81b826007815181106116c957fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535050919050565b6000808585111561170d578182fd5b83861115611719578182fd5b505082019391909203915056fe4465706f736974436f6e74726163743a206d65726b6c6520747265652066756c6c4465706f736974436f6e74726163743a207265636f6e7374727563746564204465706f7369744461746120646f6573206e6f74206d6174636820737570706c696564206465706f7369745f646174615f726f6f744465706f736974436f6e74726163743a20696e76616c6964207769746864726177616c5f63726564656e7469616c73206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c7565206e6f74206d756c7469706c65206f6620677765694465706f736974436f6e74726163743a20696e76616c6964207075626b6579206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f20686967684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f206c6f774465706f736974436f6e74726163743a20696e76616c6964207369676e6174757265206c656e677468a2646970667358221220dceca8706b29e917dacf25fceef95acac8d90d765ac926663ce4096195952b6164736f6c634300060b0033", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x85acb6376c2707b118225da41825974c12b5924a05c6a53b988c9cbc33c55b05", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x2f93f18b1befc457f659e486ce25bbe413fe3943ee7634d2afbe83dc512c3d7a", + "0x0000000000000000000000000000000000000000000000000000000000000020": "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x0000000000000000000000000000000000000000000000000000000000000022": "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", + "0x0000000000000000000000000000000000000000000000000000000000000023": "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", + "0x0000000000000000000000000000000000000000000000000000000000000024": "0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c", + "0x0000000000000000000000000000000000000000000000000000000000000025": "0x536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c", + "0x0000000000000000000000000000000000000000000000000000000000000026": "0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30", + "0x0000000000000000000000000000000000000000000000000000000000000027": "0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1", + "0x0000000000000000000000000000000000000000000000000000000000000028": "0x87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c", + "0x0000000000000000000000000000000000000000000000000000000000000029": "0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193", + "0x000000000000000000000000000000000000000000000000000000000000002a": "0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1", + "0x000000000000000000000000000000000000000000000000000000000000002b": "0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b", + "0x000000000000000000000000000000000000000000000000000000000000002c": "0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220", + "0x000000000000000000000000000000000000000000000000000000000000002d": "0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f", + "0x000000000000000000000000000000000000000000000000000000000000002e": "0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e", + "0x000000000000000000000000000000000000000000000000000000000000002f": "0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784", + "0x0000000000000000000000000000000000000000000000000000000000000030": "0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb", + "0x0000000000000000000000000000000000000000000000000000000000000031": "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb", + "0x0000000000000000000000000000000000000000000000000000000000000032": "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab", + "0x0000000000000000000000000000000000000000000000000000000000000033": "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", + "0x0000000000000000000000000000000000000000000000000000000000000034": "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f", + "0x0000000000000000000000000000000000000000000000000000000000000035": "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", + "0x0000000000000000000000000000000000000000000000000000000000000036": "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", + "0x0000000000000000000000000000000000000000000000000000000000000037": "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", + "0x0000000000000000000000000000000000000000000000000000000000000038": "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", + "0x0000000000000000000000000000000000000000000000000000000000000039": "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", + "0x000000000000000000000000000000000000000000000000000000000000003a": "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", + "0x000000000000000000000000000000000000000000000000000000000000003b": "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", + "0x000000000000000000000000000000000000000000000000000000000000003c": "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", + "0x000000000000000000000000000000000000000000000000000000000000003d": "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", + "0x000000000000000000000000000000000000000000000000000000000000003e": "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", + "0x000000000000000000000000000000000000000000000000000000000000003f": "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", + "0x0000000000000000000000000000000000000000000000000000000000000040": "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7" + }, + "balance": "0x3782dace9d9000000", + "nonce": "0x1" + }, + "0x00a3ca265ebcb825b45f985a16cefb49958ce017": { + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe146090573615156028575f545f5260205ff35b366038141561012e5760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061012e57600154600101600155600354806003026004013381556001015f3581556001016020359055600101600355005b6003546002548082038060101160a4575060105b5f5b81811460dd5780604c02838201600302600401805490600101805490600101549160601b83528260140152906034015260010160a6565b910180921460ed579060025560f8565b90505f6002555f6003555b5f548061049d141561010757505f5b60015460028282011161011c5750505f610122565b01600290035b5f555f600155604c025ff35b5f5ffd", + "balance": "0x0", + "nonce": "0x1" + }, + "0x000f3df6d732807ef1319fb7b8bb8522d0beac02": { + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000000c": "0x000000000000000000000000000000000000000000000000000000000000000c" + }, + "balance": "0x0", + "nonce": "0x1" + }, + "0x0aae40965e6800cd9b1f4b05ff21581047e3f91e": { + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe1460575767ffffffffffffffff5f3511605357600143035f3511604b575f35612000014311604b57611fff5f3516545f5260205ff35b5f5f5260205ff35b5f5ffd5b5f35611fff60014303165500", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0xe4fb5d47f70d54b4f36777ea4c882cf767f93d8f8170285d97a1b8275dfe4dbb" + }, + "balance": "0x0", + "nonce": "0x1" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0xaa00be18c288efd690", + "nonce": "0x2" + } + }, + "body": "0xf90404f901ff8007830f42409400000000219ab540356cbb839cbe05303d7705fa8901bc16d674ec800000b9019422895118000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000d0000000000000000000000000000000000000000000000000000000000000011085acb6376c2707b118225da41825974c12b5924a05c6a53b988c9cbc33c55b05000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000325a0ffeb1d6e7ef8e9ee9b64dcdc3b056f9a1d2b94c1572f1949954e712364604575a03d0f42bad795205de84db8d4ab10b9abd0d081ffe560cbf45f6c281768112a69f901ff0107830f42409400000000219ab540356cbb839cbe05303d7705fa8901bc16d674ec800000b9019422895118000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000d0000000000000000000000000000000000000000000000000000000000000011085acb6376c2707b118225da41825974c12b5924a05c6a53b988c9cbc33c55b05000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000326a05bb08e348c9c4b0a2e15d04f4a89d1a210d013205de8d3d93e38e5c1b0c8d8aba04300c0f575d9908d2cbc3413ab82895678bb8f3ef356224dd1e7cb972f2c4855", + "result": { + "stateRoot": "0x3aa7839837ee1564276a0a05554e35215353a97c4e255c3aacbcd7c7819daefa", + "txRoot": "0x2b790bf82ef7259a0e4513d1b89a77d81e99672ba68758ef2ba3fde32851d023", + "receiptsRoot": "0x9c8d7a917ecb3ff2566f264abbf39131e51b08b07eb2b69cb46989d79d985593", + "logsHash": "0x43e31613bfefc1f55d8b3ca2b61f933f3838d523dc11cb5d7ffdd2ecf0ab5d49", + "logsBloom": "0x00000000000000000000400000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000020000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000", + "receipts": [ + { + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0x1431e", + "logsBloom": "0x00000000000000000000400000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000020000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000", + "logs": [ + { + "address": "0x00000000219ab540356cbb839cbe05303d7705fa", + "topics": [ + "0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000080040597307000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000", + "blockNumber": 1, + "transactionHash": "0x1fe5fcd93d503a72e93ef0a4249e6d9c983cecf33474684f62ece959fc3e8212", + "transactionIndex": "0x0", + "blockHash": "0xb7b43f46d6ee34eaaad5ab38807b451e0e85bacbe59893afc625550aa7c65262", + "logIndex": "0x0", + "removed": "false" + } + ], + "transactionHash": "0x1fe5fcd93d503a72e93ef0a4249e6d9c983cecf33474684f62ece959fc3e8212", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x1431e", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x0" + }, + { + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0x24f10", + "logsBloom": "0x00000000000000000000400000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000020000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000", + "logs": [ + { + "address": "0x00000000219ab540356cbb839cbe05303d7705fa", + "topics": [ + "0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5" + ], + "data": "0x00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000080040597307000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000080100000000000000000000000000000000000000000000000000000000000000", + "blockNumber": 1, + "transactionHash": "0x3059d7dbc2df8a05442e50ea529419277c2f45582534f452d2d5676d93273c7d", + "transactionIndex": "0x1", + "blockHash": "0xb7b43f46d6ee34eaaad5ab38807b451e0e85bacbe59893afc625550aa7c65262", + "logIndex": "0x0", + "removed": "false" + } + ], + "transactionHash": "0x3059d7dbc2df8a05442e50ea529419277c2f45582534f452d2d5676d93273c7d", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0x10bf2", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x1" + } + ], + "currentDifficulty": null, + "gasUsed": "0x24f10", + "currentBaseFee": "0x7", + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "currentExcessBlobGas": "0x0", + "blobGasUsed": "0x0", + "requestsRoot": "0xfba41d6600776bec2258a06e4bc00a34f7a2a1bc0aa802976d038ef4e3379d29", + "depositRequests": [ + { + "pubkey": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", + "withdrawalCredentials": "0x0000000000000000000000000000000000000000000000000000000000000002", + "amount": "0x0000000773594000", + "signature": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003", + "index": "0x0000000000000000" + }, + { + "pubkey": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", + "withdrawalCredentials": "0x0000000000000000000000000000000000000000000000000000000000000002", + "amount": "0x0000000773594000", + "signature": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003", + "index": "0x0000000000000001" + } + ], + "withdrawalRequests": [], + "consolidationRequests":[] + } + } +} \ No newline at end of file diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/prague-withdrawal.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/prague-withdrawal.json new file mode 100644 index 00000000000..148c7a23e30 --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/prague-withdrawal.json @@ -0,0 +1,345 @@ +{ + "cli": [ + "t8n", + "--input.alloc=stdin", + "--input.txs=stdin", + "--input.env=stdin", + "--output.result=stdout", + "--output.alloc=stdout", + "--output.body=stdout", + "--state.fork=Prague", + "--state.chainid=1", + "--state.reward=0" + ], + "stdin": { + "alloc": { + "0x00000000219ab540356cbb839cbe05303d7705fa": { + "nonce": "0x01", + "balance": "0x00", + "code": "0x60806040526004361061003f5760003560e01c806301ffc9a71461004457806322895118146100a4578063621fd130146101ba578063c5f2892f14610244575b600080fd5b34801561005057600080fd5b506100906004803603602081101561006757600080fd5b50357fffffffff000000000000000000000000000000000000000000000000000000001661026b565b604080519115158252519081900360200190f35b6101b8600480360360808110156100ba57600080fd5b8101906020810181356401000000008111156100d557600080fd5b8201836020820111156100e757600080fd5b8035906020019184600183028401116401000000008311171561010957600080fd5b91939092909160208101903564010000000081111561012757600080fd5b82018360208201111561013957600080fd5b8035906020019184600183028401116401000000008311171561015b57600080fd5b91939092909160208101903564010000000081111561017957600080fd5b82018360208201111561018b57600080fd5b803590602001918460018302840111640100000000831117156101ad57600080fd5b919350915035610304565b005b3480156101c657600080fd5b506101cf6110b5565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102095781810151838201526020016101f1565b50505050905090810190601f1680156102365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561025057600080fd5b506102596110c7565b60408051918252519081900360200190f35b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a70000000000000000000000000000000000000000000000000000000014806102fe57507fffffffff0000000000000000000000000000000000000000000000000000000082167f8564090700000000000000000000000000000000000000000000000000000000145b92915050565b6030861461035d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118056026913960400191505060405180910390fd5b602084146103b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603681526020018061179c6036913960400191505060405180910390fd5b6060821461040f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806118786029913960400191505060405180910390fd5b670de0b6b3a7640000341015610470576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118526026913960400191505060405180910390fd5b633b9aca003406156104cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260338152602001806117d26033913960400191505060405180910390fd5b633b9aca00340467ffffffffffffffff811115610535576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602781526020018061182b6027913960400191505060405180910390fd5b6060610540826114ba565b90507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c589898989858a8a6105756020546114ba565b6040805160a0808252810189905290819060208201908201606083016080840160c085018e8e80828437600083820152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690910187810386528c815260200190508c8c808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690920188810386528c5181528c51602091820193918e019250908190849084905b83811015610648578181015183820152602001610630565b50505050905090810190601f1680156106755780820380516001836020036101000a031916815260200191505b5086810383528881526020018989808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169092018881038452895181528951602091820193918b019250908190849084905b838110156106ef5781810151838201526020016106d7565b50505050905090810190601f16801561071c5780820380516001836020036101000a031916815260200191505b509d505050505050505050505050505060405180910390a1600060028a8a600060801b604051602001808484808284377fffffffffffffffffffffffffffffffff0000000000000000000000000000000090941691909301908152604080517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0818403018152601090920190819052815191955093508392506020850191508083835b602083106107fc57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016107bf565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610859573d6000803e3d6000fd5b5050506040513d602081101561086e57600080fd5b5051905060006002806108846040848a8c6116fe565b6040516020018083838082843780830192505050925050506040516020818303038152906040526040518082805190602001908083835b602083106108f857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016108bb565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610955573d6000803e3d6000fd5b5050506040513d602081101561096a57600080fd5b5051600261097b896040818d6116fe565b60405160009060200180848480828437919091019283525050604080518083038152602092830191829052805190945090925082918401908083835b602083106109f457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016109b7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610a51573d6000803e3d6000fd5b5050506040513d6020811015610a6657600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610ada57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610a9d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610b37573d6000803e3d6000fd5b5050506040513d6020811015610b4c57600080fd5b50516040805160208101858152929350600092600292839287928f928f92018383808284378083019250505093505050506040516020818303038152906040526040518082805190602001908083835b60208310610bd957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610b9c565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610c36573d6000803e3d6000fd5b5050506040513d6020811015610c4b57600080fd5b50516040518651600291889160009188916020918201918291908601908083835b60208310610ca957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610c6c565b6001836020036101000a0380198251168184511680821785525050505050509050018367ffffffffffffffff191667ffffffffffffffff1916815260180182815260200193505050506040516020818303038152906040526040518082805190602001908083835b60208310610d4e57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610d11565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610dab573d6000803e3d6000fd5b5050506040513d6020811015610dc057600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610e3457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610df7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610e91573d6000803e3d6000fd5b5050506040513d6020811015610ea657600080fd5b50519050858114610f02576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260548152602001806117486054913960600191505060405180910390fd5b60205463ffffffff11610f60576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806117276021913960400191505060405180910390fd5b602080546001019081905560005b60208110156110a9578160011660011415610fa0578260008260208110610f9157fe5b0155506110ac95505050505050565b600260008260208110610faf57fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061102557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610fe8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015611082573d6000803e3d6000fd5b5050506040513d602081101561109757600080fd5b50519250600282049150600101610f6e565b50fe5b50505050505050565b60606110c26020546114ba565b905090565b6020546000908190815b60208110156112f05781600116600114156111e6576002600082602081106110f557fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061116b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161112e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156111c8573d6000803e3d6000fd5b5050506040513d60208110156111dd57600080fd5b505192506112e2565b600283602183602081106111f657fe5b015460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061126b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161122e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156112c8573d6000803e3d6000fd5b5050506040513d60208110156112dd57600080fd5b505192505b6002820491506001016110d1565b506002826112ff6020546114ba565b600060401b6040516020018084815260200183805190602001908083835b6020831061135a57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161131d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790527fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000095909516920191825250604080518083037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8018152601890920190819052815191955093508392850191508083835b6020831061143f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101611402565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa15801561149c573d6000803e3d6000fd5b5050506040513d60208110156114b157600080fd5b50519250505090565b60408051600880825281830190925260609160208201818036833701905050905060c082901b8060071a60f81b826000815181106114f457fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060061a60f81b8260018151811061153757fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060051a60f81b8260028151811061157a57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060041a60f81b826003815181106115bd57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060031a60f81b8260048151811061160057fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060021a60f81b8260058151811061164357fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060011a60f81b8260068151811061168657fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060001a60f81b826007815181106116c957fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535050919050565b6000808585111561170d578182fd5b83861115611719578182fd5b505082019391909203915056fe4465706f736974436f6e74726163743a206d65726b6c6520747265652066756c6c4465706f736974436f6e74726163743a207265636f6e7374727563746564204465706f7369744461746120646f6573206e6f74206d6174636820737570706c696564206465706f7369745f646174615f726f6f744465706f736974436f6e74726163743a20696e76616c6964207769746864726177616c5f63726564656e7469616c73206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c7565206e6f74206d756c7469706c65206f6620677765694465706f736974436f6e74726163743a20696e76616c6964207075626b6579206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f20686967684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f206c6f774465706f736974436f6e74726163743a20696e76616c6964207369676e6174757265206c656e677468a2646970667358221220dceca8706b29e917dacf25fceef95acac8d90d765ac926663ce4096195952b6164736f6c634300060b0033", + "storage": { + "0x22": "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", + "0x23": "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", + "0x24": "0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c", + "0x25": "0x536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c", + "0x26": "0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30", + "0x27": "0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1", + "0x28": "0x87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c", + "0x29": "0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193", + "0x2a": "0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1", + "0x2b": "0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b", + "0x2c": "0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220", + "0x2d": "0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f", + "0x2e": "0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e", + "0x2f": "0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784", + "0x30": "0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb", + "0x31": "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb", + "0x32": "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab", + "0x33": "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", + "0x34": "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f", + "0x35": "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", + "0x36": "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", + "0x37": "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", + "0x38": "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", + "0x39": "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", + "0x3a": "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", + "0x3b": "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", + "0x3c": "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", + "0x3d": "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", + "0x3e": "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", + "0x3f": "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", + "0x40": "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7" + } + }, + "0x00a3ca265ebcb825b45f985a16cefb49958ce017": { + "nonce": "0x01", + "balance": "0x00", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe146090573615156028575f545f5260205ff35b366038141561012e5760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061012e57600154600101600155600354806003026004013381556001015f3581556001016020359055600101600355005b6003546002548082038060101160a4575060105b5f5b81811460dd5780604c02838201600302600401805490600101805490600101549160601b83528260140152906034015260010160a6565b910180921460ed579060025560f8565b90505f6002555f6003555b5f548061049d141561010757505f5b60015460028282011161011c5750505f610122565b01600290035b5f555f600155604c025ff35b5f5ffd", + "storage": { + } + }, + "0x000f3df6d732807ef1319fb7b8bb8522d0beac02": { + "nonce": "0x01", + "balance": "0x00", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500", + "storage": {} + }, + "0x0aae40965e6800cd9b1f4b05ff21581047e3f91e": { + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe1460575767ffffffffffffffff5f3511605357600143035f3511604b575f35612000014311604b57611fff5f3516545f5260205ff35b5f5f5260205ff35b5f5ffd5b5f35611fff60014303165500", + "balance": "0x0", + "nonce": "0x1" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "nonce": "0x00", + "balance": "0xad78ebc5ac62000000", + "code": "0x", + "storage": {} + }, + "0x0000000000000000000000000000000000000200": { + "nonce": "0x01", + "balance": "0xad78ebc5ac62000000", + "code": "0x60386000600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f15060386038600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f15060386070600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f150603860a8600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f150603860e0600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f1506038610118600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f1506038610150600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f1506038610188600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f15060386101c0600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f15060386101f8600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f1506038610230600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f1506038610268600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f15060386102a0600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f15060386102d8600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f1506038610310600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f1506038610348600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f150", + "storage": {} + } + }, + "txs": [ + { + "type": "0x0", + "chainId": "0x1", + "nonce": "0x0", + "gasPrice": "0x7", + "gas": "0xf4240", + "to": "0x0000000000000000000000000000000000000200", + "value": "0x0", + "input": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001fffffffffffffffe0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fffffffffffffffe0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005fffffffffffffffe0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007fffffffffffffffe0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009fffffffffffffffe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bfffffffffffffffe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dfffffffffffffffe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffe0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000", + "v": "0x26", + "r": "0x963cb6620fe5828cbc93bb7139d3f4501067d76275dff648bf48c3c100ca8dd4", + "s": "0x4ac396104a5be4643406718f59a6e74d62a32777f5f6135e55e805e87612013c", + "sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" + } + ], + "env": { + "currentCoinbase": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentGasLimit": "100000000000000000", + "currentNumber": "1", + "currentTimestamp": "12", + "currentRandom": "0", + "currentDifficulty": "0", + "parentDifficulty": "0", + "parentTimestamp": "0", + "parentBaseFee": "7", + "parentGasUsed": "0", + "parentGasLimit": "100000000000000000", + "parentUncleHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "parentBlobGasUsed": "0", + "parentExcessBlobGas": "0", + "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", + "blockHashes": { + "0": "0x10715cfbefdb8a0cb2f7d7ca5ee6d1ea65515ecb41cff0a22d1e11716a9d27fb" + }, + "ommers": [], + "withdrawals": [], + "parentHash": "0x10715cfbefdb8a0cb2f7d7ca5ee6d1ea65515ecb41cff0a22d1e11716a9d27fb" + } + }, + "stdout": { + "alloc": { + "0x0000000000000000000000000000000000000200": { + "code": "0x60386000600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f15060386038600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f15060386070600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f150603860a8600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f150603860e0600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f1506038610118600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f1506038610150600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f1506038610188600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f15060386101c0600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f15060386101f8600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f1506038610230600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f1506038610268600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f15060386102a0600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f15060386102d8600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f1506038610310600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f1506038610348600037600060006038600060017300a3ca265ebcb825b45f985a16cefb49958ce017620f4240f150", + "balance": "0xad78ebc5ac61fffff0", + "nonce": "0x1" + }, + "0x00000000219ab540356cbb839cbe05303d7705fa": { + "code": "0x60806040526004361061003f5760003560e01c806301ffc9a71461004457806322895118146100a4578063621fd130146101ba578063c5f2892f14610244575b600080fd5b34801561005057600080fd5b506100906004803603602081101561006757600080fd5b50357fffffffff000000000000000000000000000000000000000000000000000000001661026b565b604080519115158252519081900360200190f35b6101b8600480360360808110156100ba57600080fd5b8101906020810181356401000000008111156100d557600080fd5b8201836020820111156100e757600080fd5b8035906020019184600183028401116401000000008311171561010957600080fd5b91939092909160208101903564010000000081111561012757600080fd5b82018360208201111561013957600080fd5b8035906020019184600183028401116401000000008311171561015b57600080fd5b91939092909160208101903564010000000081111561017957600080fd5b82018360208201111561018b57600080fd5b803590602001918460018302840111640100000000831117156101ad57600080fd5b919350915035610304565b005b3480156101c657600080fd5b506101cf6110b5565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102095781810151838201526020016101f1565b50505050905090810190601f1680156102365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561025057600080fd5b506102596110c7565b60408051918252519081900360200190f35b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a70000000000000000000000000000000000000000000000000000000014806102fe57507fffffffff0000000000000000000000000000000000000000000000000000000082167f8564090700000000000000000000000000000000000000000000000000000000145b92915050565b6030861461035d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118056026913960400191505060405180910390fd5b602084146103b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603681526020018061179c6036913960400191505060405180910390fd5b6060821461040f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806118786029913960400191505060405180910390fd5b670de0b6b3a7640000341015610470576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118526026913960400191505060405180910390fd5b633b9aca003406156104cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260338152602001806117d26033913960400191505060405180910390fd5b633b9aca00340467ffffffffffffffff811115610535576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602781526020018061182b6027913960400191505060405180910390fd5b6060610540826114ba565b90507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c589898989858a8a6105756020546114ba565b6040805160a0808252810189905290819060208201908201606083016080840160c085018e8e80828437600083820152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690910187810386528c815260200190508c8c808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690920188810386528c5181528c51602091820193918e019250908190849084905b83811015610648578181015183820152602001610630565b50505050905090810190601f1680156106755780820380516001836020036101000a031916815260200191505b5086810383528881526020018989808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169092018881038452895181528951602091820193918b019250908190849084905b838110156106ef5781810151838201526020016106d7565b50505050905090810190601f16801561071c5780820380516001836020036101000a031916815260200191505b509d505050505050505050505050505060405180910390a1600060028a8a600060801b604051602001808484808284377fffffffffffffffffffffffffffffffff0000000000000000000000000000000090941691909301908152604080517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0818403018152601090920190819052815191955093508392506020850191508083835b602083106107fc57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016107bf565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610859573d6000803e3d6000fd5b5050506040513d602081101561086e57600080fd5b5051905060006002806108846040848a8c6116fe565b6040516020018083838082843780830192505050925050506040516020818303038152906040526040518082805190602001908083835b602083106108f857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016108bb565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610955573d6000803e3d6000fd5b5050506040513d602081101561096a57600080fd5b5051600261097b896040818d6116fe565b60405160009060200180848480828437919091019283525050604080518083038152602092830191829052805190945090925082918401908083835b602083106109f457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016109b7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610a51573d6000803e3d6000fd5b5050506040513d6020811015610a6657600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610ada57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610a9d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610b37573d6000803e3d6000fd5b5050506040513d6020811015610b4c57600080fd5b50516040805160208101858152929350600092600292839287928f928f92018383808284378083019250505093505050506040516020818303038152906040526040518082805190602001908083835b60208310610bd957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610b9c565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610c36573d6000803e3d6000fd5b5050506040513d6020811015610c4b57600080fd5b50516040518651600291889160009188916020918201918291908601908083835b60208310610ca957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610c6c565b6001836020036101000a0380198251168184511680821785525050505050509050018367ffffffffffffffff191667ffffffffffffffff1916815260180182815260200193505050506040516020818303038152906040526040518082805190602001908083835b60208310610d4e57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610d11565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610dab573d6000803e3d6000fd5b5050506040513d6020811015610dc057600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610e3457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610df7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610e91573d6000803e3d6000fd5b5050506040513d6020811015610ea657600080fd5b50519050858114610f02576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260548152602001806117486054913960600191505060405180910390fd5b60205463ffffffff11610f60576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806117276021913960400191505060405180910390fd5b602080546001019081905560005b60208110156110a9578160011660011415610fa0578260008260208110610f9157fe5b0155506110ac95505050505050565b600260008260208110610faf57fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061102557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610fe8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015611082573d6000803e3d6000fd5b5050506040513d602081101561109757600080fd5b50519250600282049150600101610f6e565b50fe5b50505050505050565b60606110c26020546114ba565b905090565b6020546000908190815b60208110156112f05781600116600114156111e6576002600082602081106110f557fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061116b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161112e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156111c8573d6000803e3d6000fd5b5050506040513d60208110156111dd57600080fd5b505192506112e2565b600283602183602081106111f657fe5b015460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061126b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161122e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156112c8573d6000803e3d6000fd5b5050506040513d60208110156112dd57600080fd5b505192505b6002820491506001016110d1565b506002826112ff6020546114ba565b600060401b6040516020018084815260200183805190602001908083835b6020831061135a57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161131d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790527fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000095909516920191825250604080518083037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8018152601890920190819052815191955093508392850191508083835b6020831061143f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101611402565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa15801561149c573d6000803e3d6000fd5b5050506040513d60208110156114b157600080fd5b50519250505090565b60408051600880825281830190925260609160208201818036833701905050905060c082901b8060071a60f81b826000815181106114f457fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060061a60f81b8260018151811061153757fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060051a60f81b8260028151811061157a57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060041a60f81b826003815181106115bd57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060031a60f81b8260048151811061160057fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060021a60f81b8260058151811061164357fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060011a60f81b8260068151811061168657fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060001a60f81b826007815181106116c957fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535050919050565b6000808585111561170d578182fd5b83861115611719578182fd5b505082019391909203915056fe4465706f736974436f6e74726163743a206d65726b6c6520747265652066756c6c4465706f736974436f6e74726163743a207265636f6e7374727563746564204465706f7369744461746120646f6573206e6f74206d6174636820737570706c696564206465706f7369745f646174615f726f6f744465706f736974436f6e74726163743a20696e76616c6964207769746864726177616c5f63726564656e7469616c73206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c7565206e6f74206d756c7469706c65206f6620677765694465706f736974436f6e74726163743a20696e76616c6964207075626b6579206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f20686967684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f206c6f774465706f736974436f6e74726163743a20696e76616c6964207369676e6174757265206c656e677468a2646970667358221220dceca8706b29e917dacf25fceef95acac8d90d765ac926663ce4096195952b6164736f6c634300060b0033", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000022": "0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b", + "0x0000000000000000000000000000000000000000000000000000000000000023": "0xdb56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71", + "0x0000000000000000000000000000000000000000000000000000000000000024": "0xc78009fdf07fc56a11f122370658a353aaa542ed63e44c4bc15ff4cd105ab33c", + "0x0000000000000000000000000000000000000000000000000000000000000025": "0x536d98837f2dd165a55d5eeae91485954472d56f246df256bf3cae19352a123c", + "0x0000000000000000000000000000000000000000000000000000000000000026": "0x9efde052aa15429fae05bad4d0b1d7c64da64d03d7a1854a588c2cb8430c0d30", + "0x0000000000000000000000000000000000000000000000000000000000000027": "0xd88ddfeed400a8755596b21942c1497e114c302e6118290f91e6772976041fa1", + "0x0000000000000000000000000000000000000000000000000000000000000028": "0x87eb0ddba57e35f6d286673802a4af5975e22506c7cf4c64bb6be5ee11527f2c", + "0x0000000000000000000000000000000000000000000000000000000000000029": "0x26846476fd5fc54a5d43385167c95144f2643f533cc85bb9d16b782f8d7db193", + "0x000000000000000000000000000000000000000000000000000000000000002a": "0x506d86582d252405b840018792cad2bf1259f1ef5aa5f887e13cb2f0094f51e1", + "0x000000000000000000000000000000000000000000000000000000000000002b": "0xffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b", + "0x000000000000000000000000000000000000000000000000000000000000002c": "0x6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220", + "0x000000000000000000000000000000000000000000000000000000000000002d": "0xb7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f", + "0x000000000000000000000000000000000000000000000000000000000000002e": "0xdf6af5f5bbdb6be9ef8aa618e4bf8073960867171e29676f8b284dea6a08a85e", + "0x000000000000000000000000000000000000000000000000000000000000002f": "0xb58d900f5e182e3c50ef74969ea16c7726c549757cc23523c369587da7293784", + "0x0000000000000000000000000000000000000000000000000000000000000030": "0xd49a7502ffcfb0340b1d7885688500ca308161a7f96b62df9d083b71fcc8f2bb", + "0x0000000000000000000000000000000000000000000000000000000000000031": "0x8fe6b1689256c0d385f42f5bbe2027a22c1996e110ba97c171d3e5948de92beb", + "0x0000000000000000000000000000000000000000000000000000000000000032": "0x8d0d63c39ebade8509e0ae3c9c3876fb5fa112be18f905ecacfecb92057603ab", + "0x0000000000000000000000000000000000000000000000000000000000000033": "0x95eec8b2e541cad4e91de38385f2e046619f54496c2382cb6cacd5b98c26f5a4", + "0x0000000000000000000000000000000000000000000000000000000000000034": "0xf893e908917775b62bff23294dbbe3a1cd8e6cc1c35b4801887b646a6f81f17f", + "0x0000000000000000000000000000000000000000000000000000000000000035": "0xcddba7b592e3133393c16194fac7431abf2f5485ed711db282183c819e08ebaa", + "0x0000000000000000000000000000000000000000000000000000000000000036": "0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c", + "0x0000000000000000000000000000000000000000000000000000000000000037": "0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167", + "0x0000000000000000000000000000000000000000000000000000000000000038": "0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7", + "0x0000000000000000000000000000000000000000000000000000000000000039": "0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0", + "0x000000000000000000000000000000000000000000000000000000000000003a": "0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544", + "0x000000000000000000000000000000000000000000000000000000000000003b": "0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765", + "0x000000000000000000000000000000000000000000000000000000000000003c": "0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4", + "0x000000000000000000000000000000000000000000000000000000000000003d": "0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1", + "0x000000000000000000000000000000000000000000000000000000000000003e": "0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636", + "0x000000000000000000000000000000000000000000000000000000000000003f": "0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c", + "0x0000000000000000000000000000000000000000000000000000000000000040": "0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7" + }, + "balance": "0x0", + "nonce": "0x1" + }, + "0x000f3df6d732807ef1319fb7b8bb8522d0beac02": { + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500", + "storage": { + "0x000000000000000000000000000000000000000000000000000000000000000c": "0x000000000000000000000000000000000000000000000000000000000000000c" + }, + "balance": "0x0", + "nonce": "0x1" + }, + "0x00a3ca265ebcb825b45f985a16cefb49958ce017": { + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe146090573615156028575f545f5260205ff35b366038141561012e5760115f54600182026001905f5b5f82111560595781019083028483029004916001019190603e565b90939004341061012e57600154600101600155600354806003026004013381556001015f3581556001016020359055600101600355005b6003546002548082038060101160a4575060105b5f5b81811460dd5780604c02838201600302600401805490600101805490600101549160601b83528260140152906034015260010160a6565b910180921460ed579060025560f8565b90505f6002555f6003555b5f548061049d141561010757505f5b60015460028282011161011c5750505f610122565b01600290035b5f555f600155604c025ff35b5f5ffd", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000000000000000000000000000000000000000000e", + "0x0000000000000000000000000000000000000000000000000000000000000004": "0x0000000000000000000000000000000000000000000000000000000000000200", + "0x0000000000000000000000000000000000000000000000000000000000000006": "0x00000000000000000000000000000001fffffffffffffffe0000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000007": "0x0000000000000000000000000000000000000000000000000000000000000200", + "0x0000000000000000000000000000000000000000000000000000000000000009": "0x0000000000000000000000000000000200000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000000000a": "0x0000000000000000000000000000000000000000000000000000000000000200", + "0x000000000000000000000000000000000000000000000000000000000000000c": "0x00000000000000000000000000000003fffffffffffffffe0000000000000000", + "0x000000000000000000000000000000000000000000000000000000000000000d": "0x0000000000000000000000000000000000000000000000000000000000000200", + "0x000000000000000000000000000000000000000000000000000000000000000f": "0x0000000000000000000000000000000400000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000010": "0x0000000000000000000000000000000000000000000000000000000000000200", + "0x0000000000000000000000000000000000000000000000000000000000000012": "0x00000000000000000000000000000005fffffffffffffffe0000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000013": "0x0000000000000000000000000000000000000000000000000000000000000200", + "0x0000000000000000000000000000000000000000000000000000000000000015": "0x0000000000000000000000000000000600000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000016": "0x0000000000000000000000000000000000000000000000000000000000000200", + "0x0000000000000000000000000000000000000000000000000000000000000018": "0x00000000000000000000000000000007fffffffffffffffe0000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000019": "0x0000000000000000000000000000000000000000000000000000000000000200", + "0x000000000000000000000000000000000000000000000000000000000000001b": "0x0000000000000000000000000000000800000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000000001c": "0x0000000000000000000000000000000000000000000000000000000000000200", + "0x000000000000000000000000000000000000000000000000000000000000001e": "0x00000000000000000000000000000009fffffffffffffffe0000000000000000", + "0x000000000000000000000000000000000000000000000000000000000000001f": "0x0000000000000000000000000000000000000000000000000000000000000200", + "0x0000000000000000000000000000000000000000000000000000000000000021": "0x0000000000000000000000000000000a00000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000022": "0x0000000000000000000000000000000000000000000000000000000000000200", + "0x0000000000000000000000000000000000000000000000000000000000000024": "0x0000000000000000000000000000000bfffffffffffffffe0000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000025": "0x0000000000000000000000000000000000000000000000000000000000000200", + "0x0000000000000000000000000000000000000000000000000000000000000027": "0x0000000000000000000000000000000c00000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000028": "0x0000000000000000000000000000000000000000000000000000000000000200", + "0x000000000000000000000000000000000000000000000000000000000000002a": "0x0000000000000000000000000000000dfffffffffffffffe0000000000000000", + "0x000000000000000000000000000000000000000000000000000000000000002b": "0x0000000000000000000000000000000000000000000000000000000000000200", + "0x000000000000000000000000000000000000000000000000000000000000002d": "0x0000000000000000000000000000000e00000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000000002e": "0x0000000000000000000000000000000000000000000000000000000000000200", + "0x0000000000000000000000000000000000000000000000000000000000000030": "0x0000000000000000000000000000000ffffffffffffffffe0000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000031": "0x0000000000000000000000000000000000000000000000000000000000000200", + "0x0000000000000000000000000000000000000000000000000000000000000033": "0x0000000000000000000000000000001000000000000000000000000000000000" + }, + "balance": "0x10", + "nonce": "0x1" + }, + "0x0aae40965e6800cd9b1f4b05ff21581047e3f91e": { + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe1460575767ffffffffffffffff5f3511605357600143035f3511604b575f35612000014311604b57611fff5f3516545f5260205ff35b5f5f5260205ff35b5f5ffd5b5f35611fff60014303165500", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x10715cfbefdb8a0cb2f7d7ca5ee6d1ea65515ecb41cff0a22d1e11716a9d27fb" + }, + "balance": "0x0", + "nonce": "0x1" + }, + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0xad78ebc5ac619bbcea", + "nonce": "0x1" + } + }, + "body": "0xf903e5f903e28007830f424094000000000000000000000000000000000000020080b90380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001fffffffffffffffe0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003fffffffffffffffe0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005fffffffffffffffe0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007fffffffffffffffe0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009fffffffffffffffe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000bfffffffffffffffe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dfffffffffffffffe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffe000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000026a0963cb6620fe5828cbc93bb7139d3f4501067d76275dff648bf48c3c100ca8dd4a04ac396104a5be4643406718f59a6e74d62a32777f5f6135e55e805e87612013c", + "result": { + "requestsRoot": "0x415d33e444f917658fd3bd6d3d8d88a1f501f9b83ace92ddacac823252c9fa47", + "depositRequests": [], + "withdrawalRequests": [ + { + "sourceAddress": "0x0000000000000000000000000000000000000200", + "validatorPubkey": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", + "amount": "0xfffffffffffffffe" + }, + { + "sourceAddress": "0x0000000000000000000000000000000000000200", + "validatorPubkey": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002", + "amount": "0x0000000000000000" + }, + { + "sourceAddress": "0x0000000000000000000000000000000000000200", + "validatorPubkey": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003", + "amount": "0xfffffffffffffffe" + }, + { + "sourceAddress": "0x0000000000000000000000000000000000000200", + "validatorPubkey": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004", + "amount": "0x0000000000000000" + }, + { + "sourceAddress": "0x0000000000000000000000000000000000000200", + "validatorPubkey": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005", + "amount": "0xfffffffffffffffe" + }, + { + "sourceAddress": "0x0000000000000000000000000000000000000200", + "validatorPubkey": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006", + "amount": "0x0000000000000000" + }, + { + "sourceAddress": "0x0000000000000000000000000000000000000200", + "validatorPubkey": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007", + "amount": "0xfffffffffffffffe" + }, + { + "sourceAddress": "0x0000000000000000000000000000000000000200", + "validatorPubkey": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008", + "amount": "0x0000000000000000" + }, + { + "sourceAddress": "0x0000000000000000000000000000000000000200", + "validatorPubkey": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009", + "amount": "0xfffffffffffffffe" + }, + { + "sourceAddress": "0x0000000000000000000000000000000000000200", + "validatorPubkey": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a", + "amount": "0x0000000000000000" + }, + { + "sourceAddress": "0x0000000000000000000000000000000000000200", + "validatorPubkey": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b", + "amount": "0xfffffffffffffffe" + }, + { + "sourceAddress": "0x0000000000000000000000000000000000000200", + "validatorPubkey": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c", + "amount": "0x0000000000000000" + }, + { + "sourceAddress": "0x0000000000000000000000000000000000000200", + "validatorPubkey": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d", + "amount": "0xfffffffffffffffe" + }, + { + "sourceAddress": "0x0000000000000000000000000000000000000200", + "validatorPubkey": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e", + "amount": "0x0000000000000000" + }, + { + "sourceAddress": "0x0000000000000000000000000000000000000200", + "validatorPubkey": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f", + "amount": "0xfffffffffffffffe" + }, + { + "sourceAddress": "0x0000000000000000000000000000000000000200", + "validatorPubkey": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010", + "amount": "0x0000000000000000" + } + ], + "consolidationRequests":[], + "stateRoot": "0xf63d7552dc407993393315e99272781d04eedfcf369a1acd3e386d1e6710229d", + "txRoot": "0x8521df63211790726b6f1a437bb0fd4b27c00e13e7678d324c4cfddb8d834ad2", + "receiptsRoot": "0x4bd8bd5580caf4ed45f873794ad7ff9d6fd2363ae529269b17b891b68d349d75", + "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "receipts": [ + { + "root": "0x", + "status": "0x1", + "cumulativeGasUsed": "0xe52ba", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "logs": null, + "transactionHash": "0x04a2d3f252dcc98edb684f7f15b572aaf980d0c2eea4c620a9f1ff1d275b2207", + "contractAddress": "0x0000000000000000000000000000000000000000", + "gasUsed": "0xe52ba", + "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "transactionIndex": "0x0" + } + ], + "currentDifficulty": null, + "gasUsed": "0xe52ba", + "currentBaseFee": "0x7", + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "currentExcessBlobGas": "0x0", + "blobGasUsed": "0x0" + } + } +} \ No newline at end of file diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/shanghai-blockhash.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/shanghai-blockhash.json index 9b2908976fc..f1b8c8ecf3d 100644 --- a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/shanghai-blockhash.json +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/t8n/shanghai-blockhash.json @@ -16,7 +16,7 @@ "0x1000000000000000000000000000000000000000": { "nonce": "0x00", "balance": "0x0ba1a9ce0ba1a9ce", - "code": "0x60004060005560014060015500", + "code": "0x600040600055600140600155434060025500", "storage": {} }, "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { @@ -55,7 +55,8 @@ "parentTimestamp": "12", "blockHashes": { "0": "0x651666d6d8727a6478a750dc8406c1bda0be85abc99094dca694807fa772f83c", - "1": "0x0275fc82fe832100726ec7c26078ffb1c1dbff14a3b52d93dc359768fc8c7baa" + "1": "0x0275fc82fe832100726ec7c26078ffb1c1dbff14a3b52d93dc359768fc8c7baa", + "257": "0x0275fc82fe832100726ec7c26078ffb1c1dbff14a3b52d93dc359768fc8c7baa" }, "ommers": [], "withdrawals": [], @@ -66,43 +67,43 @@ "stdout": { "alloc": { "0x1000000000000000000000000000000000000000": { - "code": "0x60004060005560014060015500", + "code": "0x600040600055600140600155434060025500", "storage": { "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0275fc82fe832100726ec7c26078ffb1c1dbff14a3b52d93dc359768fc8c7baa" }, "balance": "0xba1a9ce0ba1a9ce" }, "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba": { - "balance": "0x21378" + "balance": "0x22d8b" }, "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "0x1319ad1e2e70", + "balance": "0x1319ad1dd786", "nonce": "0x1" } }, "body": "0xf862f860800a8307a12094100000000000000000000000000000000000000080801ca091df80a63242846dcf1cc16c35e6ba39212cde92aad946f9c286fa0029782b54a0446170c2fb714c717885485ea7d93bf9277f3fb3d89ffd73e9b7f5f8eda3997b", "result": { - "stateRoot": "0x89412c0f1bb31b983d0317e6e2801a4e604e1ef9987a132ab63c52f2d5a3994b", + "stateRoot": "0x5538cf875e8e464ff08398cd8ab7cb03281e8264da450d471bcf67a4db2a9122", "txRoot": "0x8e987be72f36f97a98838e03d9e5d1b3d22795606240f16684532b1c994b4ee7", - "receiptsRoot": "0x8046ec5911721c453253b45a350eb90247e1a047bdc7cf1e2c7885b4c2403276", + "receiptsRoot": "0xb5c544c869a9a9de6177db6d326610105f678fc1add50ad05f3e2384655268e9", "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "receipts": [ { "root": "0x", "status": "0x1", - "cumulativeGasUsed": "0xb128", + "cumulativeGasUsed": "0xb9d9", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "logs": null, "transactionHash": "0x6c4a22ee7d2d9ae9307b09aac69a34600978e34313088917a7e69fd12e8f959a", "contractAddress": "0x0000000000000000000000000000000000000000", - "gasUsed": "0xb128", + "gasUsed": "0xb9d9", "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "transactionIndex": "0x0" } ], "currentDifficulty": null, - "gasUsed": "0xb128", + "gasUsed": "0xb9d9", "currentBaseFee": "0x7", "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" } diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/badcode.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/badcode.json index fec350ff333..78dc43c02e1 100644 --- a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/badcode.json +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/badcode.json @@ -7,8 +7,8 @@ ], "stdin": "", "stdout": [ - {"pc":0,"op":96,"gas":"0x2540b91f8","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"}, - {"pc":2,"op":239,"gas":"0x2540b91f5","gasCost":"0x0","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"INVALID","error":"Bad instruction"}, - {"gasUser":"0x2540b91f8","gasTotal":"0x2540b91f8","output":"0x"} + {"pc":0,"op":96,"gas":"0x2540be400","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":2,"op":239,"gas":"0x2540be3fd","gasCost":"0x0","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"INVALID","error":"Bad instruction"}, + {"stateRoot":"0xfc9dc1be50c1b0a497afa545d770cc7064f0d71efbc4338f002dc2e086965d98","output":"0x","gasUsed":"0x2540be400","pass":false,"fork":"Cancun"} ] } \ No newline at end of file diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/charge-intrinsic.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/charge-intrinsic.json new file mode 100644 index 00000000000..9355c472837 --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/charge-intrinsic.json @@ -0,0 +1,75 @@ +{ + "cli": [ + "--notime", + "--json", + "--code", + "60408053604060405560406000604060006000305af16040f3", + "--receiver", + "0xc0de", + "--contract", + "0xc0de", + "--gas", + "46180", + "--input", + "0xbadbadc0de", + "--intrinsic-gas" + ], + "stdin": "", + "stdout": [ + {"pc":0,"op":96,"gas":"0x620c","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":2,"op":128,"gas":"0x6209","gasCost":"0x3","memSize":0,"stack":["0x40"],"depth":1,"refund":0,"opName":"DUP1"}, + {"pc":3,"op":83,"gas":"0x6206","gasCost":"0xc","memSize":0,"stack":["0x40","0x40"],"depth":1,"refund":0,"opName":"MSTORE8"}, + {"pc":4,"op":96,"gas":"0x61fa","gasCost":"0x3","memSize":96,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":6,"op":96,"gas":"0x61f7","gasCost":"0x3","memSize":96,"stack":["0x40"],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":8,"op":85,"gas":"0x61f4","gasCost":"0x5654","memSize":96,"stack":["0x40","0x40"],"depth":1,"refund":0,"opName":"SSTORE"}, + {"pc":9,"op":96,"gas":"0xba0","gasCost":"0x3","memSize":96,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":11,"op":96,"gas":"0xb9d","gasCost":"0x3","memSize":96,"stack":["0x40"],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":13,"op":96,"gas":"0xb9a","gasCost":"0x3","memSize":96,"stack":["0x40","0x0"],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":15,"op":96,"gas":"0xb97","gasCost":"0x3","memSize":96,"stack":["0x40","0x0","0x40"],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":17,"op":96,"gas":"0xb94","gasCost":"0x3","memSize":96,"stack":["0x40","0x0","0x40","0x0"],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":19,"op":48,"gas":"0xb91","gasCost":"0x2","memSize":96,"stack":["0x40","0x0","0x40","0x0","0x0"],"depth":1,"refund":0,"opName":"ADDRESS"}, + {"pc":20,"op":90,"gas":"0xb8f","gasCost":"0x2","memSize":96,"stack":["0x40","0x0","0x40","0x0","0x0","0xc0de"],"depth":1,"refund":0,"opName":"GAS"}, + {"pc":21,"op":241,"gas":"0xb8d","gasCost":"0xb61","memSize":96,"stack":["0x40","0x0","0x40","0x0","0x0","0xc0de","0xb8d"],"depth":1,"refund":0,"opName":"CALL"}, + {"pc":0,"op":96,"gas":"0xafd","gasCost":"0x3","memSize":0,"stack":[],"depth":2,"refund":0,"opName":"PUSH1"}, + {"pc":2,"op":128,"gas":"0xafa","gasCost":"0x3","memSize":0,"stack":["0x40"],"depth":2,"refund":0,"opName":"DUP1"}, + {"pc":3,"op":83,"gas":"0xaf7","gasCost":"0xc","memSize":0,"stack":["0x40","0x40"],"depth":2,"refund":0,"opName":"MSTORE8"}, + {"pc":4,"op":96,"gas":"0xaeb","gasCost":"0x3","memSize":96,"stack":[],"depth":2,"refund":0,"opName":"PUSH1"}, + {"pc":6,"op":96,"gas":"0xae8","gasCost":"0x3","memSize":96,"stack":["0x40"],"depth":2,"refund":0,"opName":"PUSH1"}, + {"pc":8,"op":85,"gas":"0xae5","gasCost":"0x64","memSize":96,"stack":["0x40","0x40"],"depth":2,"refund":0,"opName":"SSTORE"}, + {"pc":9,"op":96,"gas":"0xa81","gasCost":"0x3","memSize":96,"stack":[],"depth":2,"refund":0,"opName":"PUSH1"}, + {"pc":11,"op":96,"gas":"0xa7e","gasCost":"0x3","memSize":96,"stack":["0x40"],"depth":2,"refund":0,"opName":"PUSH1"}, + {"pc":13,"op":96,"gas":"0xa7b","gasCost":"0x3","memSize":96,"stack":["0x40","0x0"],"depth":2,"refund":0,"opName":"PUSH1"}, + {"pc":15,"op":96,"gas":"0xa78","gasCost":"0x3","memSize":96,"stack":["0x40","0x0","0x40"],"depth":2,"refund":0,"opName":"PUSH1"}, + {"pc":17,"op":96,"gas":"0xa75","gasCost":"0x3","memSize":96,"stack":["0x40","0x0","0x40","0x0"],"depth":2,"refund":0,"opName":"PUSH1"}, + {"pc":19,"op":48,"gas":"0xa72","gasCost":"0x2","memSize":96,"stack":["0x40","0x0","0x40","0x0","0x0"],"depth":2,"refund":0,"opName":"ADDRESS"}, + {"pc":20,"op":90,"gas":"0xa70","gasCost":"0x2","memSize":96,"stack":["0x40","0x0","0x40","0x0","0x0","0xc0de"],"depth":2,"refund":0,"opName":"GAS"}, + {"pc":21,"op":241,"gas":"0xa6e","gasCost":"0xa46","memSize":96,"stack":["0x40","0x0","0x40","0x0","0x0","0xc0de","0xa6e"],"depth":2,"refund":0,"opName":"CALL"}, + {"pc":0,"op":96,"gas":"0x9e2","gasCost":"0x3","memSize":0,"stack":[],"depth":3,"refund":0,"opName":"PUSH1"}, + {"pc":2,"op":128,"gas":"0x9df","gasCost":"0x3","memSize":0,"stack":["0x40"],"depth":3,"refund":0,"opName":"DUP1"}, + {"pc":3,"op":83,"gas":"0x9dc","gasCost":"0xc","memSize":0,"stack":["0x40","0x40"],"depth":3,"refund":0,"opName":"MSTORE8"}, + {"pc":4,"op":96,"gas":"0x9d0","gasCost":"0x3","memSize":96,"stack":[],"depth":3,"refund":0,"opName":"PUSH1"}, + {"pc":6,"op":96,"gas":"0x9cd","gasCost":"0x3","memSize":96,"stack":["0x40"],"depth":3,"refund":0,"opName":"PUSH1"}, + {"pc":8,"op":85,"gas":"0x9ca","gasCost":"0x64","memSize":96,"stack":["0x40","0x40"],"depth":3,"refund":0,"opName":"SSTORE"}, + {"pc":9,"op":96,"gas":"0x966","gasCost":"0x3","memSize":96,"stack":[],"depth":3,"refund":0,"opName":"PUSH1"}, + {"pc":11,"op":96,"gas":"0x963","gasCost":"0x3","memSize":96,"stack":["0x40"],"depth":3,"refund":0,"opName":"PUSH1"}, + {"pc":13,"op":96,"gas":"0x960","gasCost":"0x3","memSize":96,"stack":["0x40","0x0"],"depth":3,"refund":0,"opName":"PUSH1"}, + {"pc":15,"op":96,"gas":"0x95d","gasCost":"0x3","memSize":96,"stack":["0x40","0x0","0x40"],"depth":3,"refund":0,"opName":"PUSH1"}, + {"pc":17,"op":96,"gas":"0x95a","gasCost":"0x3","memSize":96,"stack":["0x40","0x0","0x40","0x0"],"depth":3,"refund":0,"opName":"PUSH1"}, + {"pc":19,"op":48,"gas":"0x957","gasCost":"0x2","memSize":96,"stack":["0x40","0x0","0x40","0x0","0x0"],"depth":3,"refund":0,"opName":"ADDRESS"}, + {"pc":20,"op":90,"gas":"0x955","gasCost":"0x2","memSize":96,"stack":["0x40","0x0","0x40","0x0","0x0","0xc0de"],"depth":3,"refund":0,"opName":"GAS"}, + {"pc":21,"op":241,"gas":"0x953","gasCost":"0x930","memSize":96,"stack":["0x40","0x0","0x40","0x0","0x0","0xc0de","0x953"],"depth":3,"refund":0,"opName":"CALL"}, + {"pc":0,"op":96,"gas":"0x8cc","gasCost":"0x3","memSize":0,"stack":[],"depth":4,"refund":0,"opName":"PUSH1"}, + {"pc":2,"op":128,"gas":"0x8c9","gasCost":"0x3","memSize":0,"stack":["0x40"],"depth":4,"refund":0,"opName":"DUP1"}, + {"pc":3,"op":83,"gas":"0x8c6","gasCost":"0xc","memSize":0,"stack":["0x40","0x40"],"depth":4,"refund":0,"opName":"MSTORE8"}, + {"pc":4,"op":96,"gas":"0x8ba","gasCost":"0x3","memSize":96,"stack":[],"depth":4,"refund":0,"opName":"PUSH1"}, + {"pc":6,"op":96,"gas":"0x8b7","gasCost":"0x3","memSize":96,"stack":["0x40"],"depth":4,"refund":0,"opName":"PUSH1"}, + {"pc":8,"op":85,"gas":"0x8b4","gasCost":"0x8fc","memSize":96,"stack":["0x40","0x40"],"depth":4,"refund":0,"opName":"SSTORE","error":"Out of gas"}, + {"pc":22,"op":96,"gas":"0x23","gasCost":"0x3","memSize":96,"stack":["0x0"],"depth":3,"refund":0,"opName":"PUSH1"}, + {"pc":24,"op":243,"gas":"0x20","gasCost":"0x0","memSize":96,"stack":["0x0","0x40"],"depth":3,"refund":0,"opName":"RETURN"}, + {"pc":22,"op":96,"gas":"0x48","gasCost":"0x3","memSize":96,"stack":["0x1"],"depth":2,"refund":0,"opName":"PUSH1"}, + {"pc":24,"op":243,"gas":"0x45","gasCost":"0x0","memSize":96,"stack":["0x1","0x40"],"depth":2,"refund":0,"opName":"RETURN"}, + {"pc":22,"op":96,"gas":"0x71","gasCost":"0x3","memSize":96,"stack":["0x1"],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":24,"op":243,"gas":"0x6e","gasCost":"0x0","memSize":96,"stack":["0x1","0x40"],"depth":1,"refund":0,"opName":"RETURN"}, + {"stateRoot":"0xcb5e8e232189003640b6f131ea2c09b1791ffd2e8357f64610f638e9a11ab2d2","output":"0x40","gasUsed":"0x619e","pass":true,"fork":"Cancun"} + ] +} \ No newline at end of file diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/coinbase-cold.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/coinbase-cold.json index 406c2684e26..127d60fc60f 100644 --- a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/coinbase-cold.json +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/coinbase-cold.json @@ -7,51 +7,13 @@ "--coinbase", "4444588443C3A91288C5002483449ABA1054192B", "--fork", - "paris" + "london" ], "stdin": "", "stdout": [ - { - "pc": 0, - "op": 65, - "gas": "0x2540b91f8", - "gasCost": "0x2", - "memSize": 0, - "stack": [], - "depth": 1, - "refund": 0, - "opName": "COINBASE" - }, - { - "pc": 1, - "op": 49, - "gas": "0x2540b91f6", - "gasCost": "0xa28", - "memSize": 0, - "stack": [ - "0x4444588443c3a91288c5002483449aba1054192b" - ], - "depth": 1, - "refund": 0, - "opName": "BALANCE" - }, - { - "pc": 2, - "op": 255, - "gas": "0x2540b87ce", - "gasCost": "0x1388", - "memSize": 0, - "stack": [ - "0x0" - ], - "depth": 1, - "refund": 0, - "opName": "SELFDESTRUCT" - }, - { - "gasUser": "0x1db2", - "gasTotal": "0x1db2", - "output": "0x" - } + {"pc":0,"op":65,"gas":"0x2540be400","gasCost":"0x2","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"COINBASE"}, + {"pc":1,"op":49,"gas":"0x2540be3fe","gasCost":"0xa28","memSize":0,"stack":["0x4444588443c3a91288c5002483449aba1054192b"],"depth":1,"refund":0,"opName":"BALANCE"}, + {"pc":2,"op":255,"gas":"0x2540bd9d6","gasCost":"0x1db0","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"SELFDESTRUCT"}, + {"stateRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","output":"0x","gasUsed":"0x27da","pass":true,"fork":"London"} ] } \ No newline at end of file diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/coinbase-warm.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/coinbase-warm.json index 9140a0268c8..02f9395981d 100644 --- a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/coinbase-warm.json +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/coinbase-warm.json @@ -11,47 +11,9 @@ ], "stdin": "", "stdout": [ - { - "pc": 0, - "op": 65, - "gas": "0x2540b91f8", - "gasCost": "0x2", - "memSize": 0, - "stack": [], - "depth": 1, - "refund": 0, - "opName": "COINBASE" - }, - { - "pc": 1, - "op": 49, - "gas": "0x2540b91f6", - "gasCost": "0x64", - "memSize": 0, - "stack": [ - "0x4444588443c3a91288c5002483449aba1054192b" - ], - "depth": 1, - "refund": 0, - "opName": "BALANCE" - }, - { - "pc": 2, - "op": 255, - "gas": "0x2540b9192", - "gasCost": "0x1388", - "memSize": 0, - "stack": [ - "0x0" - ], - "depth": 1, - "refund": 0, - "opName": "SELFDESTRUCT" - }, - { - "gasUser": "0x13ee", - "gasTotal": "0x13ee", - "output": "0x" - } + {"pc":0,"op":65,"gas":"0x2540be400","gasCost":"0x2","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"COINBASE"}, + {"pc":1,"op":49,"gas":"0x2540be3fe","gasCost":"0x64","memSize":0,"stack":["0x4444588443c3a91288c5002483449aba1054192b"],"depth":1,"refund":0,"opName":"BALANCE"}, + {"pc":2,"op":255,"gas":"0x2540be39a","gasCost":"0x1db0","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"SELFDESTRUCT"}, + {"stateRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","output":"0x","gasUsed":"0x1e16","pass":true,"fork":"Shanghai"} ] } \ No newline at end of file diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/create-eof-error.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/create-eof-error.json new file mode 100644 index 00000000000..cd4c3fa14a1 --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/create-eof-error.json @@ -0,0 +1,15 @@ +{ + "cli": [ + "--notime", + "--json", + "--create", + "--code", + "ef00010100040200010001040000000080000000c0de471fe5", + "--coinbase", + "4444588443C3A91288C5002483449ABA1054192B", + "--fork", + "CancunEOF" + ], + "stdin": "", + "stdout": "EOF Code Invalid : incompatible_container_kind opcode STOP is only valid for runtime.\n" +} \ No newline at end of file diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/create-eof.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/create-eof.json new file mode 100644 index 00000000000..1fa459916b6 --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/create-eof.json @@ -0,0 +1,25 @@ +{ + "cli": [ + "--notime", + "--json", + "--create", + "--code", + "ef00010100040200010009030001001404000000008000035f355f5fa15f5fee00ef00010100040200010001040000000080000000c0de471fe5", + "--coinbase", + "4444588443C3A91288C5002483449ABA1054192B", + "--fork", + "CancunEOF" + ], + "stdin": "", + "stdout": [ + {"pc":0,"section":0,"op":95,"gas":"0x2540be400","gasCost":"0x2","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":1,"section":0,"op":53,"gas":"0x2540be3fe","gasCost":"0x3","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"CALLDATALOAD"}, + {"pc":2,"section":0,"op":95,"gas":"0x2540be3fb","gasCost":"0x2","memSize":0,"stack":["0xc0de471fe5000000000000000000000000000000000000000000000000000000"],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":3,"section":0,"op":95,"gas":"0x2540be3f9","gasCost":"0x2","memSize":0,"stack":["0xc0de471fe5000000000000000000000000000000000000000000000000000000","0x0"],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":4,"section":0,"op":161,"gas":"0x2540be3f7","gasCost":"0x2ee","memSize":0,"stack":["0xc0de471fe5000000000000000000000000000000000000000000000000000000","0x0","0x0"],"depth":1,"refund":0,"opName":"LOG1"}, + {"pc":5,"section":0,"op":95,"gas":"0x2540be109","gasCost":"0x2","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":6,"section":0,"op":95,"gas":"0x2540be107","gasCost":"0x2","memSize":0,"stack":["0x0"],"depth":1,"refund":0,"opName":"PUSH0"}, + {"pc":7,"section":0,"op":238,"immediate":"0x00","gas":"0x2540be105","gasCost":"0x0","memSize":0,"stack":["0x0","0x0"],"depth":1,"refund":0,"opName":"RETURNCONTRACT"}, + {"stateRoot":"0x9790b070a5749acec6a7252a867f795df3c2cb5b800fb509ea259a1c0b5d96c1","output":"0x","gasUsed":"0x129b","pass":true,"fork":"CancunEOF"} + ] +} \ No newline at end of file diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/eof-section.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/eof-section.json new file mode 100644 index 00000000000..439c69195a2 --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/eof-section.json @@ -0,0 +1,23 @@ +{ + "cli": [ + "--notime", + "--json", + "--code", + "0xef000101000c020003000a0001000304000000008000020000000000000000e3000261201560015500e4e50001", + "--coinbase", + "4444588443C3A91288C5002483449ABA1054192B", + "--fork", + "PragueEOF" + ], + "stdin": "", + "stdout": [ + {"pc":0,"section":0,"op":227,"immediate":"0x0002","gas":"0x2540be400","gasCost":"0x5","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"CALLF"}, + {"pc":0,"section":2,"op":229,"immediate":"0x0002","gas":"0x2540be3fb","gasCost":"0x5","memSize":0,"stack":[],"depth":1,"functionDepth":1,"refund":0,"opName":"JUMPF"}, + {"pc":0,"section":1,"op":228,"gas":"0x2540be3f6","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"functionDepth":1,"refund":0,"opName":"RETF"}, + {"pc":3,"section":0,"op":97,"immediate":"0x2015","gas":"0x2540be3f3","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH2"}, + {"pc":6,"section":0,"op":96,"immediate":"0x01","gas":"0x2540be3f0","gasCost":"0x3","memSize":0,"stack":["0x2015"],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":8,"section":0,"op":85,"gas":"0x2540be3ed","gasCost":"0x5654","memSize":0,"stack":["0x2015","0x1"],"depth":1,"refund":0,"opName":"SSTORE"}, + {"pc":9,"section":0,"op":0,"gas":"0x2540b8d99","gasCost":"0x0","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"STOP"}, + {"stateRoot":"0x761f723ceabb467d438fe74abf025c10bf65592b84ec389850038eb572f2b0fa","output":"0x","gasUsed":"0x5667","pass":true,"fork":"PragueEOF"} + ] +} \ No newline at end of file diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/eof.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/eof.json new file mode 100644 index 00000000000..c9553c92091 --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/eof.json @@ -0,0 +1,17 @@ +{ + "cli": [ + "--notime", + "--json", + "--code", + "ef00010100040200010001040000000080000000", + "--coinbase", + "4444588443C3A91288C5002483449ABA1054192B", + "--fork", + "PragueEOF" + ], + "stdin": "", + "stdout": [ + {"pc":0,"section":0,"op":0,"gas":"0x2540be400","gasCost":"0x0","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"STOP"}, + {"stateRoot":"0xdae5f2c233bf9fbb7413d06ce744a3345dbf971b5bb5638736c0388f43a61a4b","output":"0x","gasUsed":"0x0","pass":true,"fork":"PragueEOF"} + ] +} \ No newline at end of file diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/initcode-error.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/initcode-error.json new file mode 100644 index 00000000000..b88134778ec --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/initcode-error.json @@ -0,0 +1,14 @@ +{ + "cli": [ + "--notime", + "--json", + "--code", + "ef00010100040200010009030001001404000000008000035f355f5fa15f5fee00ef00010100040200010001040000000080000000", + "--coinbase", + "4444588443C3A91288C5002483449ABA1054192B", + "--fork", + "CancunEOF" + ], + "stdin": "", + "stdout": "To evaluate INITCODE mode EOF code use the --create flag\n" +} \ No newline at end of file diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/revert.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/revert.json index c919d748ddc..4798d5da706 100644 --- a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/revert.json +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/revert.json @@ -7,12 +7,12 @@ ], "stdin": "", "stdout": [ - {"pc":0,"op":99,"gas":"0x2540b91f8","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH4"}, - {"pc":5,"op":96,"gas":"0x2540b91f5","gasCost":"0x3","memSize":0,"stack":["0x4e6f7065"],"depth":1,"refund":0,"opName":"PUSH1"}, - {"pc":7,"op":82,"gas":"0x2540b91f2","gasCost":"0x6","memSize":0,"stack":["0x4e6f7065","0x0"],"depth":1,"refund":0,"opName":"MSTORE"}, - {"pc":8,"op":96,"gas":"0x2540b91ec","gasCost":"0x3","memSize":32,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"}, - {"pc":10,"op":96,"gas":"0x2540b91e9","gasCost":"0x3","memSize":32,"stack":["0x4"],"depth":1,"refund":0,"opName":"PUSH1"}, - {"pc":12,"op":253,"gas":"0x2540b91e6","gasCost":"0x0","memSize":32,"stack":["0x4","0x1c"],"depth":1,"refund":0,"opName":"REVERT","error":"Nope"}, - {"gasUser":"0x12","gasTotal":"0x12","output":"0x4e6f7065"} + {"pc":0,"op":99,"gas":"0x2540be400","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH4"}, + {"pc":5,"op":96,"gas":"0x2540be3fd","gasCost":"0x3","memSize":0,"stack":["0x4e6f7065"],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":7,"op":82,"gas":"0x2540be3fa","gasCost":"0x6","memSize":0,"stack":["0x4e6f7065","0x0"],"depth":1,"refund":0,"opName":"MSTORE"}, + {"pc":8,"op":96,"gas":"0x2540be3f4","gasCost":"0x3","memSize":32,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":10,"op":96,"gas":"0x2540be3f1","gasCost":"0x3","memSize":32,"stack":["0x4"],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":12,"op":253,"gas":"0x2540be3ee","gasCost":"0x0","memSize":32,"stack":["0x4","0x1c"],"depth":1,"refund":0,"opName":"REVERT","error":"Nope"}, + {"stateRoot":"0x405bbd98da2aca6dff77f79e0b270270c48d6a3e07b76db675b20e454b50bbcb","output":"0x4e6f7065","gasUsed":"0x12","pass":true,"fork":"Cancun"} ] } \ No newline at end of file diff --git a/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/warm-contract.json b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/warm-contract.json new file mode 100644 index 00000000000..1c2a9c8064a --- /dev/null +++ b/ethereum/evmtool/src/test/resources/org/hyperledger/besu/evmtool/trace/warm-contract.json @@ -0,0 +1,72 @@ +{ + "cli": [ + "--notime", + "--json", + "--code", + "60408053604060405560406000604060006000305af16040f3", + "--receiver", + "0xc0de", + "--contract", + "0xc0de", + "--gas", + "25100" + ], + "stdin": "", + "stdout": [ + {"pc":0,"op":96,"gas":"0x620c","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":2,"op":128,"gas":"0x6209","gasCost":"0x3","memSize":0,"stack":["0x40"],"depth":1,"refund":0,"opName":"DUP1"}, + {"pc":3,"op":83,"gas":"0x6206","gasCost":"0xc","memSize":0,"stack":["0x40","0x40"],"depth":1,"refund":0,"opName":"MSTORE8"}, + {"pc":4,"op":96,"gas":"0x61fa","gasCost":"0x3","memSize":96,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":6,"op":96,"gas":"0x61f7","gasCost":"0x3","memSize":96,"stack":["0x40"],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":8,"op":85,"gas":"0x61f4","gasCost":"0x5654","memSize":96,"stack":["0x40","0x40"],"depth":1,"refund":0,"opName":"SSTORE"}, + {"pc":9,"op":96,"gas":"0xba0","gasCost":"0x3","memSize":96,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":11,"op":96,"gas":"0xb9d","gasCost":"0x3","memSize":96,"stack":["0x40"],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":13,"op":96,"gas":"0xb9a","gasCost":"0x3","memSize":96,"stack":["0x40","0x0"],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":15,"op":96,"gas":"0xb97","gasCost":"0x3","memSize":96,"stack":["0x40","0x0","0x40"],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":17,"op":96,"gas":"0xb94","gasCost":"0x3","memSize":96,"stack":["0x40","0x0","0x40","0x0"],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":19,"op":48,"gas":"0xb91","gasCost":"0x2","memSize":96,"stack":["0x40","0x0","0x40","0x0","0x0"],"depth":1,"refund":0,"opName":"ADDRESS"}, + {"pc":20,"op":90,"gas":"0xb8f","gasCost":"0x2","memSize":96,"stack":["0x40","0x0","0x40","0x0","0x0","0xc0de"],"depth":1,"refund":0,"opName":"GAS"}, + {"pc":21,"op":241,"gas":"0xb8d","gasCost":"0xb61","memSize":96,"stack":["0x40","0x0","0x40","0x0","0x0","0xc0de","0xb8d"],"depth":1,"refund":0,"opName":"CALL"}, + {"pc":0,"op":96,"gas":"0xafd","gasCost":"0x3","memSize":0,"stack":[],"depth":2,"refund":0,"opName":"PUSH1"}, + {"pc":2,"op":128,"gas":"0xafa","gasCost":"0x3","memSize":0,"stack":["0x40"],"depth":2,"refund":0,"opName":"DUP1"}, + {"pc":3,"op":83,"gas":"0xaf7","gasCost":"0xc","memSize":0,"stack":["0x40","0x40"],"depth":2,"refund":0,"opName":"MSTORE8"}, + {"pc":4,"op":96,"gas":"0xaeb","gasCost":"0x3","memSize":96,"stack":[],"depth":2,"refund":0,"opName":"PUSH1"}, + {"pc":6,"op":96,"gas":"0xae8","gasCost":"0x3","memSize":96,"stack":["0x40"],"depth":2,"refund":0,"opName":"PUSH1"}, + {"pc":8,"op":85,"gas":"0xae5","gasCost":"0x64","memSize":96,"stack":["0x40","0x40"],"depth":2,"refund":0,"opName":"SSTORE"}, + {"pc":9,"op":96,"gas":"0xa81","gasCost":"0x3","memSize":96,"stack":[],"depth":2,"refund":0,"opName":"PUSH1"}, + {"pc":11,"op":96,"gas":"0xa7e","gasCost":"0x3","memSize":96,"stack":["0x40"],"depth":2,"refund":0,"opName":"PUSH1"}, + {"pc":13,"op":96,"gas":"0xa7b","gasCost":"0x3","memSize":96,"stack":["0x40","0x0"],"depth":2,"refund":0,"opName":"PUSH1"}, + {"pc":15,"op":96,"gas":"0xa78","gasCost":"0x3","memSize":96,"stack":["0x40","0x0","0x40"],"depth":2,"refund":0,"opName":"PUSH1"}, + {"pc":17,"op":96,"gas":"0xa75","gasCost":"0x3","memSize":96,"stack":["0x40","0x0","0x40","0x0"],"depth":2,"refund":0,"opName":"PUSH1"}, + {"pc":19,"op":48,"gas":"0xa72","gasCost":"0x2","memSize":96,"stack":["0x40","0x0","0x40","0x0","0x0"],"depth":2,"refund":0,"opName":"ADDRESS"}, + {"pc":20,"op":90,"gas":"0xa70","gasCost":"0x2","memSize":96,"stack":["0x40","0x0","0x40","0x0","0x0","0xc0de"],"depth":2,"refund":0,"opName":"GAS"}, + {"pc":21,"op":241,"gas":"0xa6e","gasCost":"0xa46","memSize":96,"stack":["0x40","0x0","0x40","0x0","0x0","0xc0de","0xa6e"],"depth":2,"refund":0,"opName":"CALL"}, + {"pc":0,"op":96,"gas":"0x9e2","gasCost":"0x3","memSize":0,"stack":[],"depth":3,"refund":0,"opName":"PUSH1"}, + {"pc":2,"op":128,"gas":"0x9df","gasCost":"0x3","memSize":0,"stack":["0x40"],"depth":3,"refund":0,"opName":"DUP1"}, + {"pc":3,"op":83,"gas":"0x9dc","gasCost":"0xc","memSize":0,"stack":["0x40","0x40"],"depth":3,"refund":0,"opName":"MSTORE8"}, + {"pc":4,"op":96,"gas":"0x9d0","gasCost":"0x3","memSize":96,"stack":[],"depth":3,"refund":0,"opName":"PUSH1"}, + {"pc":6,"op":96,"gas":"0x9cd","gasCost":"0x3","memSize":96,"stack":["0x40"],"depth":3,"refund":0,"opName":"PUSH1"}, + {"pc":8,"op":85,"gas":"0x9ca","gasCost":"0x64","memSize":96,"stack":["0x40","0x40"],"depth":3,"refund":0,"opName":"SSTORE"}, + {"pc":9,"op":96,"gas":"0x966","gasCost":"0x3","memSize":96,"stack":[],"depth":3,"refund":0,"opName":"PUSH1"}, + {"pc":11,"op":96,"gas":"0x963","gasCost":"0x3","memSize":96,"stack":["0x40"],"depth":3,"refund":0,"opName":"PUSH1"}, + {"pc":13,"op":96,"gas":"0x960","gasCost":"0x3","memSize":96,"stack":["0x40","0x0"],"depth":3,"refund":0,"opName":"PUSH1"}, + {"pc":15,"op":96,"gas":"0x95d","gasCost":"0x3","memSize":96,"stack":["0x40","0x0","0x40"],"depth":3,"refund":0,"opName":"PUSH1"}, + {"pc":17,"op":96,"gas":"0x95a","gasCost":"0x3","memSize":96,"stack":["0x40","0x0","0x40","0x0"],"depth":3,"refund":0,"opName":"PUSH1"}, + {"pc":19,"op":48,"gas":"0x957","gasCost":"0x2","memSize":96,"stack":["0x40","0x0","0x40","0x0","0x0"],"depth":3,"refund":0,"opName":"ADDRESS"}, + {"pc":20,"op":90,"gas":"0x955","gasCost":"0x2","memSize":96,"stack":["0x40","0x0","0x40","0x0","0x0","0xc0de"],"depth":3,"refund":0,"opName":"GAS"}, + {"pc":21,"op":241,"gas":"0x953","gasCost":"0x930","memSize":96,"stack":["0x40","0x0","0x40","0x0","0x0","0xc0de","0x953"],"depth":3,"refund":0,"opName":"CALL"}, + {"pc":0,"op":96,"gas":"0x8cc","gasCost":"0x3","memSize":0,"stack":[],"depth":4,"refund":0,"opName":"PUSH1"}, + {"pc":2,"op":128,"gas":"0x8c9","gasCost":"0x3","memSize":0,"stack":["0x40"],"depth":4,"refund":0,"opName":"DUP1"}, + {"pc":3,"op":83,"gas":"0x8c6","gasCost":"0xc","memSize":0,"stack":["0x40","0x40"],"depth":4,"refund":0,"opName":"MSTORE8"}, + {"pc":4,"op":96,"gas":"0x8ba","gasCost":"0x3","memSize":96,"stack":[],"depth":4,"refund":0,"opName":"PUSH1"}, + {"pc":6,"op":96,"gas":"0x8b7","gasCost":"0x3","memSize":96,"stack":["0x40"],"depth":4,"refund":0,"opName":"PUSH1"}, + {"pc":8,"op":85,"gas":"0x8b4","gasCost":"0x8fc","memSize":96,"stack":["0x40","0x40"],"depth":4,"refund":0,"opName":"SSTORE","error":"Out of gas"}, + {"pc":22,"op":96,"gas":"0x23","gasCost":"0x3","memSize":96,"stack":["0x0"],"depth":3,"refund":0,"opName":"PUSH1"}, + {"pc":24,"op":243,"gas":"0x20","gasCost":"0x0","memSize":96,"stack":["0x0","0x40"],"depth":3,"refund":0,"opName":"RETURN"}, + {"pc":22,"op":96,"gas":"0x48","gasCost":"0x3","memSize":96,"stack":["0x1"],"depth":2,"refund":0,"opName":"PUSH1"}, + {"pc":24,"op":243,"gas":"0x45","gasCost":"0x0","memSize":96,"stack":["0x1","0x40"],"depth":2,"refund":0,"opName":"RETURN"}, + {"pc":22,"op":96,"gas":"0x71","gasCost":"0x3","memSize":96,"stack":["0x1"],"depth":1,"refund":0,"opName":"PUSH1"}, + {"pc":24,"op":243,"gas":"0x6e","gasCost":"0x0","memSize":96,"stack":["0x1","0x40"],"depth":1,"refund":0,"opName":"RETURN"}, + {"stateRoot":"0xcb5e8e232189003640b6f131ea2c09b1791ffd2e8357f64610f638e9a11ab2d2","output":"0x40","gasUsed":"0x619e","pass":true,"fork":"Cancun"} + ] +} \ No newline at end of file diff --git a/ethereum/mock-p2p/build.gradle b/ethereum/mock-p2p/build.gradle index 373e25be3cf..a548d0cde77 100644 --- a/ethereum/mock-p2p/build.gradle +++ b/ethereum/mock-p2p/build.gradle @@ -22,7 +22,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } diff --git a/ethereum/mock-p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/testing/MockNetwork.java b/ethereum/mock-p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/testing/MockNetwork.java index b443afe9f94..d7a3c2b5065 100644 --- a/ethereum/mock-p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/testing/MockNetwork.java +++ b/ethereum/mock-p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/testing/MockNetwork.java @@ -55,6 +55,11 @@ public final class MockNetwork { private final Map nodes = new HashMap<>(); private final List capabilities; + /** + * Constructs a new MockNetwork with the specified capabilities. + * + * @param capabilities a list of capabilities that the mock network should have. + */ public MockNetwork(final List capabilities) { this.capabilities = capabilities; } diff --git a/ethereum/p2p/build.gradle b/ethereum/p2p/build.gradle index dccbe0fb877..971504a25af 100644 --- a/ethereum/p2p/build.gradle +++ b/ethereum/p2p/build.gradle @@ -22,7 +22,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } @@ -50,15 +51,14 @@ dependencies { implementation('io.tmio:tuweni-devp2p') { exclude group:'ch.qos.logback', module:'logback-classic' } - implementation('io.tmio:tuweni-dns-discovery'){ - exclude group:'ch.qos.logback', module:'logback-classic' - } implementation 'io.tmio:tuweni-io' implementation 'io.tmio:tuweni-rlp' implementation 'io.tmio:tuweni-units' + implementation 'org.apache.commons:commons-collections4' implementation 'org.jetbrains.kotlin:kotlin-stdlib' implementation 'org.owasp.encoder:encoder' implementation 'org.xerial.snappy:snappy-java' + implementation 'commons-net:commons-net' annotationProcessor "org.immutables:value" implementation "org.immutables:value-annotations" @@ -83,6 +83,7 @@ dependencies { } testImplementation 'io.vertx:vertx-codegen' testImplementation 'io.vertx:vertx-unit' + testImplementation 'io.vertx:vertx-junit5' testImplementation 'org.assertj:assertj-core' testImplementation 'org.awaitility:awaitility' testImplementation 'org.junit.jupiter:junit-jupiter' diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/config/DiscoveryConfiguration.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/config/DiscoveryConfiguration.java index 036e592e1dd..3f59abf2fdf 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/config/DiscoveryConfiguration.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/config/DiscoveryConfiguration.java @@ -32,7 +32,8 @@ public class DiscoveryConfiguration { private List bootnodes = new ArrayList<>(); private String dnsDiscoveryURL; private boolean discoveryV5Enabled = false; - private boolean filterOnEnrForkId = false; + private boolean filterOnEnrForkId = NetworkingConfiguration.DEFAULT_FILTER_ON_ENR_FORK_ID; + private boolean includeBootnodesOnPeerRefresh = true; public static DiscoveryConfiguration create() { return new DiscoveryConfiguration(); @@ -88,6 +89,16 @@ public DiscoveryConfiguration setBootnodes(final List bootnodes) { return this; } + public boolean getIncludeBootnodesOnPeerRefresh() { + return includeBootnodesOnPeerRefresh; + } + + public DiscoveryConfiguration setIncludeBootnodesOnPeerRefresh( + final boolean includeBootnodesOnPeerRefresh) { + this.includeBootnodesOnPeerRefresh = includeBootnodesOnPeerRefresh; + return this; + } + public String getAdvertisedHost() { return advertisedHost; } diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/config/NetworkingConfiguration.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/config/NetworkingConfiguration.java index 0de53cfd786..8efbcdd35b9 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/config/NetworkingConfiguration.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/config/NetworkingConfiguration.java @@ -23,13 +23,13 @@ public class NetworkingConfiguration { public static final int DEFAULT_INITIATE_CONNECTIONS_FREQUENCY_SEC = 30; public static final int DEFAULT_CHECK_MAINTAINED_CONNECTIONS_FREQUENCY_SEC = 60; public static final int DEFAULT_PEER_LOWER_BOUND = 25; + public static final boolean DEFAULT_FILTER_ON_ENR_FORK_ID = true; private DiscoveryConfiguration discovery = new DiscoveryConfiguration(); private RlpxConfiguration rlpx = new RlpxConfiguration(); private int initiateConnectionsFrequencySec = DEFAULT_INITIATE_CONNECTIONS_FREQUENCY_SEC; private int checkMaintainedConnectionsFrequencySec = DEFAULT_CHECK_MAINTAINED_CONNECTIONS_FREQUENCY_SEC; - private Integer peerLowerBound = DEFAULT_PEER_LOWER_BOUND; private Optional dnsDiscoveryServerOverride = Optional.empty(); public static NetworkingConfiguration create() { @@ -86,16 +86,6 @@ public NetworkingConfiguration setCheckMaintainedConnectionsFrequency( return this; } - public Integer getPeerLowerBound() { - return peerLowerBound; - } - - public NetworkingConfiguration setPeerLowerBound(final Integer peerLowerBoundConfig) { - checkArgument(peerLowerBoundConfig > 0); - this.peerLowerBound = peerLowerBoundConfig; - return this; - } - @Override public boolean equals(final Object o) { if (o == this) { diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/PeerDiscoveryAgent.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/PeerDiscoveryAgent.java index 8af596018a1..7efc50750fb 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/PeerDiscoveryAgent.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/PeerDiscoveryAgent.java @@ -26,6 +26,7 @@ import org.hyperledger.besu.ethereum.p2p.discovery.internal.Packet; import org.hyperledger.besu.ethereum.p2p.discovery.internal.PeerDiscoveryController; import org.hyperledger.besu.ethereum.p2p.discovery.internal.PeerRequirement; +import org.hyperledger.besu.ethereum.p2p.discovery.internal.PeerTable; import org.hyperledger.besu.ethereum.p2p.discovery.internal.PingPacketData; import org.hyperledger.besu.ethereum.p2p.discovery.internal.TimerUtil; import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl; @@ -73,7 +74,6 @@ public abstract class PeerDiscoveryAgent { // The devp2p specification says only accept packets up to 1280, but some // clients ignore that, so we add in a little extra padding. private static final int MAX_PACKET_SIZE_BYTES = 1600; - protected final List bootstrapPeers; private final List peerRequirements = new CopyOnWriteArrayList<>(); private final PeerPermissions peerPermissions; @@ -81,6 +81,8 @@ public abstract class PeerDiscoveryAgent { private final MetricsSystem metricsSystem; private final RlpxAgent rlpxAgent; private final ForkIdManager forkIdManager; + private final PeerTable peerTable; + private static final boolean isIpv6Available = NetworkUtility.isIPv6Available(); /* The peer controller, which takes care of the state machine of peers. */ protected Optional controller = Optional.empty(); @@ -109,7 +111,8 @@ protected PeerDiscoveryAgent( final MetricsSystem metricsSystem, final StorageProvider storageProvider, final ForkIdManager forkIdManager, - final RlpxAgent rlpxAgent) { + final RlpxAgent rlpxAgent, + final PeerTable peerTable) { this.metricsSystem = metricsSystem; checkArgument(nodeKey != null, "nodeKey cannot be null"); checkArgument(config != null, "provided configuration cannot be null"); @@ -130,6 +133,7 @@ protected PeerDiscoveryAgent( this.forkIdManager = forkIdManager; this.forkIdSupplier = () -> forkIdManager.getForkIdForChainHead().getForkIdAsBytesList(); this.rlpxAgent = rlpxAgent; + this.peerTable = peerTable; } protected abstract TimerUtil createTimer(); @@ -147,7 +151,11 @@ public CompletableFuture start(final int tcpPort) { if (config.isActive()) { final String host = config.getBindHost(); final int port = config.getBindPort(); - LOG.info("Starting peer discovery agent on host={}, port={}", host, port); + LOG.info( + "Starting peer discovery agent on host={}, port={}. IPv6 {}.", + host, + port, + NetworkUtility.isIPv6Available() ? "available" : "not available"); // override advertised host if we detect an external IP address via NAT manager this.advertisedAddress = natService.queryExternalIPAddress(config.getAdvertisedHost()); @@ -263,9 +271,10 @@ private PeerDiscoveryController createController(final DiscoveryPeer localNode) .peerRequirement(PeerRequirement.combine(peerRequirements)) .peerPermissions(peerPermissions) .metricsSystem(metricsSystem) - .forkIdManager(forkIdManager) .filterOnEnrForkId((config.isFilterOnEnrForkIdEnabled())) .rlpxAgent(rlpxAgent) + .peerTable(peerTable) + .includeBootnodesOnPeerRefresh(config.getIncludeBootnodesOnPeerRefresh()) .build(); } @@ -282,8 +291,9 @@ protected void handleIncomingPacket(final Endpoint sourceEndpoint, final Packet .flatMap(Endpoint::getTcpPort) .orElse(udpPort); + final String host = deriveHost(sourceEndpoint, packet); + // Notify the peer controller. - final String host = sourceEndpoint.getHost(); final DiscoveryPeer peer = DiscoveryPeer.fromEnode( EnodeURLImpl.builder() @@ -296,6 +306,53 @@ protected void handleIncomingPacket(final Endpoint sourceEndpoint, final Packet controller.ifPresent(c -> c.onMessage(packet, peer)); } + /** + * method to derive the host from the source endpoint and the P2P PING packet. If the host is + * present in the P2P PING packet itself, use that as the endpoint. If the P2P PING packet + * specifies 127.0.0.1 (the default if a custom value is not specified with --p2p-host or via a + * suitable --nat-method) we ignore it in favour of the UDP source address. Some implementations + * send 127.0.0.1 or 255.255.255.255 anyway, but this reduces the chance of an unexpected change + * in behaviour as a result of https://github.com/hyperledger/besu/issues/6224 being fixed. + * + * @param sourceEndpoint source endpoint of the packet + * @param packet P2P PING packet + * @return host address as string + */ + static String deriveHost(final Endpoint sourceEndpoint, final Packet packet) { + final Optional pingPacketHost = + packet + .getPacketData(PingPacketData.class) + .flatMap(PingPacketData::getFrom) + .map(Endpoint::getHost); + + return pingPacketHost + // fall back to source endpoint "from" if ping packet from address does not satisfy filters + .filter(InetAddresses::isInetAddress) + .filter(h -> !NetworkUtility.isUnspecifiedAddress(h)) + .filter(h -> !NetworkUtility.isLocalhostAddress(h)) + .filter(h -> isIpv6Available || !NetworkUtility.isIpV6Address(h)) + .stream() + .peek( + h -> + LOG.atTrace() + .setMessage( + "Using \"From\" endpoint {} specified in ping packet. Ignoring UDP source host {}") + .addArgument(h) + .addArgument(sourceEndpoint::getHost) + .log()) + .findFirst() + .orElseGet( + () -> { + LOG.atTrace() + .setMessage( + "Ignoring \"From\" endpoint {} in ping packet. Using UDP source host {}") + .addArgument(pingPacketHost.orElse("not specified")) + .addArgument(sourceEndpoint.getHost()) + .log(); + return sourceEndpoint.getHost(); + }); + } + /** * Send a packet to the given recipient. * diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/PeerDiscoveryStatus.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/PeerDiscoveryStatus.java index dc704915658..5311a2212a5 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/PeerDiscoveryStatus.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/PeerDiscoveryStatus.java @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.ethereum.p2p.discovery; +import java.util.Locale; + /** The status of a {@link DiscoveryPeer}, in relation to the peer discovery state machine. */ public enum PeerDiscoveryStatus { @@ -40,6 +42,6 @@ public enum PeerDiscoveryStatus { @Override public String toString() { - return name().toLowerCase(); + return name().toLowerCase(Locale.ROOT); } } diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/VertxPeerDiscoveryAgent.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/VertxPeerDiscoveryAgent.java index 27a2be8beb3..0c989021638 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/VertxPeerDiscoveryAgent.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/VertxPeerDiscoveryAgent.java @@ -23,6 +23,7 @@ import org.hyperledger.besu.ethereum.p2p.discovery.internal.Packet; import org.hyperledger.besu.ethereum.p2p.discovery.internal.PeerDiscoveryController; import org.hyperledger.besu.ethereum.p2p.discovery.internal.PeerDiscoveryController.AsyncExecutor; +import org.hyperledger.besu.ethereum.p2p.discovery.internal.PeerTable; import org.hyperledger.besu.ethereum.p2p.discovery.internal.TimerUtil; import org.hyperledger.besu.ethereum.p2p.discovery.internal.VertxTimerUtil; import org.hyperledger.besu.ethereum.p2p.permissions.PeerPermissions; @@ -73,7 +74,8 @@ public VertxPeerDiscoveryAgent( final MetricsSystem metricsSystem, final StorageProvider storageProvider, final ForkIdManager forkIdManager, - final RlpxAgent rlpxAgent) { + final RlpxAgent rlpxAgent, + final PeerTable peerTable) { super( nodeKey, config, @@ -82,7 +84,8 @@ public VertxPeerDiscoveryAgent( metricsSystem, storageProvider, forkIdManager, - rlpxAgent); + rlpxAgent, + peerTable); checkArgument(vertx != null, "vertx instance cannot be null"); this.vertx = vertx; @@ -217,12 +220,14 @@ protected void handleOutgoingPacketError( .addArgument(err) .log(); } else { - LOG.warn( - "Sending to peer {} failed, native error code {}, packet: {}, stacktrace: {}", - peer, - nativeErr.expectedErr(), - wrapBuffer(packet.encode()), - err); + LOG.atDebug() + .setMessage( + "Sending to peer {} failed, native error code {}, packet: {}, stacktrace: {}") + .addArgument(peer) + .addArgument(nativeErr.expectedErr()) + .addArgument(wrapBuffer(packet.encode())) + .addArgument(err) + .log(); } } else if (err instanceof SocketException && err.getMessage().contains("unreachable")) { LOG.atDebug() diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java new file mode 100644 index 00000000000..ef794ae2047 --- /dev/null +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemon.java @@ -0,0 +1,106 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.p2p.discovery.dns; + +import java.util.List; +import java.util.Optional; + +import io.vertx.core.AbstractVerticle; +import org.apache.tuweni.devp2p.EthereumNodeRecord; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// Adapted from https://github.com/tmio/tuweni and licensed under Apache 2.0 +/** + * Resolves DNS records over time, refreshing records. This is written as a Vertx Verticle which + * allows to run outside the Vertx event loop + */ +public class DNSDaemon extends AbstractVerticle { + private static final Logger LOG = LoggerFactory.getLogger(DNSDaemon.class); + private final String enrLink; + private final long seq; + private final long initialDelay; + private final long delay; + private final Optional listener; + private final Optional dnsServer; + private Optional periodicTaskId = Optional.empty(); + private DNSResolver dnsResolver; + + /** + * Creates a new DNSDaemon. + * + * @param enrLink the ENR link to start with, of the form enrtree://PUBKEY@domain + * @param listener Listener notified when records are read and whenever they are updated. + * @param seq the sequence number of the root record. If the root record seq is higher, proceed + * with visit. + * @param initialDelay the delay in milliseconds before the first poll of DNS records. + * @param delay the delay in milliseconds at which to poll DNS records. If negative or zero, it + * runs only once. + * @param dnsServer the DNS server to use for DNS query. If null, the default DNS server will be + * used. + */ + public DNSDaemon( + final String enrLink, + final DNSDaemonListener listener, + final long seq, + final long initialDelay, + final long delay, + final String dnsServer) { + this.enrLink = enrLink; + this.listener = Optional.ofNullable(listener); + this.seq = seq; + this.initialDelay = initialDelay; + this.delay = delay; + this.dnsServer = Optional.ofNullable(dnsServer); + } + + /** Starts the DNSDaemon. */ + @Override + public void start() { + if (vertx == null) { + throw new IllegalStateException("DNSDaemon must be deployed as a vertx verticle."); + } + + LOG.info("Starting DNSDaemon for {}, using {} DNS host.", enrLink, dnsServer.orElse("default")); + dnsResolver = new DNSResolver(vertx, enrLink, seq, dnsServer); + if (delay > 0) { + periodicTaskId = Optional.of(vertx.setPeriodic(initialDelay, delay, this::refreshENRRecords)); + } else { + // do one-shot resolution + refreshENRRecords(0L); + } + } + + /** Stops the DNSDaemon. */ + @Override + public void stop() { + LOG.info("Stopping DNSDaemon for {}", enrLink); + periodicTaskId.ifPresent(vertx::cancelTimer); + } + + /** + * Refresh enr records by calling dnsResolver and updating the listeners. + * + * @param taskId the task id of the periodic task + */ + void refreshENRRecords(final Long taskId) { + LOG.debug("Refreshing DNS records"); + final long startTime = System.nanoTime(); + final List ethereumNodeRecords = dnsResolver.collectAll(); + final long endTime = System.nanoTime(); + LOG.debug("Time taken to DNSResolver.collectAll: {} ms", (endTime - startTime) / 1_000_000); + listener.ifPresent(it -> it.newRecords(dnsResolver.sequence(), ethereumNodeRecords)); + } +} diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonListener.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonListener.java new file mode 100644 index 00000000000..cfa51d4eb73 --- /dev/null +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonListener.java @@ -0,0 +1,32 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.p2p.discovery.dns; + +import java.util.List; + +import org.apache.tuweni.devp2p.EthereumNodeRecord; + +// Adapted from https://github.com/tmio/tuweni and licensed under Apache 2.0 +/** Callback listening to updates of the DNS records. */ +@FunctionalInterface +public interface DNSDaemonListener { + /** + * Callback called when the seq is updated on the DNS server + * + * @param seq the update identifier of the records + * @param records the records stored on the server + */ + void newRecords(long seq, List records); +} diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntry.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntry.java new file mode 100644 index 00000000000..cd439eea063 --- /dev/null +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntry.java @@ -0,0 +1,279 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.p2p.discovery.dns; + +import static org.hyperledger.besu.ethereum.p2p.discovery.dns.KVReader.readKV; + +import java.net.URI; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.crypto.SECP256K1; +import org.apache.tuweni.devp2p.EthereumNodeRecord; +import org.apache.tuweni.io.Base32; +import org.apache.tuweni.io.Base64URLSafe; +import org.bouncycastle.math.ec.ECPoint; + +// Adapted from https://github.com/tmio/tuweni and licensed under Apache 2.0 +/** Intermediate format to write DNS entries */ +public interface DNSEntry { + + /** + * Read a DNS entry from a String. + * + * @param serialized the serialized form of a DNS entry + * @return DNS entry if found + * @throws IllegalArgumentException if the record cannot be read + */ + static DNSEntry readDNSEntry(final String serialized) { + final String record = trimQuotes(serialized); + final String prefix = getPrefix(record); + return switch (prefix) { + case "enrtree-root" -> new ENRTreeRoot(readKV(record)); + case "enrtree-branch" -> new ENRTree(record.substring(prefix.length() + 1)); + case "enr" -> new ENRNode(readKV(record)); + case "enrtree" -> new ENRTreeLink(record); + default -> + throw new IllegalArgumentException( + serialized + " should contain enrtree-branch, enr, enrtree-root or enrtree"); + }; + } + + private static String trimQuotes(final String str) { + if (str.startsWith("\"") && str.endsWith("\"")) { + return str.substring(1, str.length() - 1); + } + return str; + } + + private static String getPrefix(final String input) { + final String[] parts = input.split(":", 2); + return parts.length > 0 ? parts[0] : ""; + } + + /** Represents a node in the ENR record. */ + class ENRNode implements DNSEntry { + + private final EthereumNodeRecord nodeRecord; + + /** + * Constructs ENRNode with the given attributes. + * + * @param attrs the attributes of the node + */ + public ENRNode(final Map attrs) { + if (attrs == null) { + throw new IllegalArgumentException("ENRNode attributes cannot be null"); + } + nodeRecord = + Optional.ofNullable(attrs.get("enr")) + .map(Base64URLSafe::decode) + .map(EthereumNodeRecord::fromRLP) + .orElseThrow(() -> new IllegalArgumentException("Invalid ENR record")); + } + + /** + * Ethereum node record. + * + * @return the instance of EthereumNodeRecord + */ + public EthereumNodeRecord nodeRecord() { + return nodeRecord; + } + + @Override + public String toString() { + return nodeRecord.toString(); + } + } + + /** Root of the ENR tree */ + class ENRTreeRoot implements DNSEntry { + private final String version; + private final Long seq; + private final SECP256K1.Signature sig; + private final String enrRoot; + private final String linkRoot; + + /** + * Creates a new ENRTreeRoot + * + * @param attrs The attributes of the root + */ + public ENRTreeRoot(final Map attrs) { + if (attrs == null) { + throw new IllegalArgumentException("ENRNode attributes cannot be null"); + } + + version = + Optional.ofNullable(attrs.get("enrtree-root")) + .orElseThrow(() -> new IllegalArgumentException("Missing attribute enrtree-root")); + seq = + Optional.ofNullable(attrs.get("seq")) + .map(Long::parseLong) + .orElseThrow(() -> new IllegalArgumentException("Missing attribute seq")); + sig = + Optional.ofNullable(attrs.get("sig")) + .map(Base64URLSafe::decode) + .map( + sigBytes -> + SECP256K1.Signature.fromBytes( + Bytes.concatenate( + sigBytes, Bytes.wrap(new byte[Math.max(0, 65 - sigBytes.size())])))) + .orElseThrow(() -> new IllegalArgumentException("Missing attribute sig")); + enrRoot = + Optional.ofNullable(attrs.get("e")) + .orElseThrow(() -> new IllegalArgumentException("Missing attribute e")); + linkRoot = + Optional.ofNullable(attrs.get("l")) + .orElseThrow(() -> new IllegalArgumentException("Missing attribute l")); + } + + /** + * Gets sequence + * + * @return sequence + */ + public Long seq() { + return seq; + } + + /** + * Link root. + * + * @return the link root. + */ + public String linkRoot() { + return linkRoot; + } + + /** + * ENR root. + * + * @return the enr root. + */ + public String enrRoot() { + return enrRoot; + } + + /** + * Signature. + * + * @return SECP256K1 signature + */ + public SECP256K1.Signature sig() { + return sig; + } + + @Override + public String toString() { + return String.format( + "enrtree-root:%s e=%s l=%s seq=%d sig=%s", + version, enrRoot, linkRoot, seq, Base64URLSafe.encode(sig.bytes())); + } + + /** + * Returns the signed content of the root + * + * @return the signed content + */ + public String signedContent() { + return String.format("enrtree-root:%s e=%s l=%s seq=%d", version, enrRoot, linkRoot, seq); + } + } + + /** Represents a branch in the ENR record. */ + class ENRTree implements DNSEntry { + private final List entries; + + /** + * Constructs ENRTree with the given entries. + * + * @param entriesAsString the entries of the branch + */ + public ENRTree(final String entriesAsString) { + entries = + Arrays.stream(entriesAsString.split("[,\"]")) + .filter(it -> it.length() > 4) + .collect(Collectors.toList()); + } + + /** + * Entries of the branch. + * + * @return the entries of the branch + */ + public List entries() { + return entries; + } + + @Override + public String toString() { + return "enrtree-branch:" + String.join(",", entries); + } + } + + /** Class representing an ENR Tree link */ + class ENRTreeLink implements DNSEntry { + private final String domainName; + private final String encodedPubKey; + private final SECP256K1.PublicKey pubKey; + + /** + * Creates a new ENRTreeLink + * + * @param enrTreeLink The URI representing ENR Tree link + */ + public ENRTreeLink(final String enrTreeLink) { + final URI uri = URI.create(enrTreeLink); + this.domainName = uri.getHost(); + this.encodedPubKey = uri.getUserInfo() == null ? "" : uri.getUserInfo(); + this.pubKey = fromBase32(encodedPubKey); + } + + private static SECP256K1.PublicKey fromBase32(final String base32) { + final byte[] keyBytes = Base32.decodeBytes(base32); + final ECPoint ecPoint = SECP256K1.Parameters.CURVE.getCurve().decodePoint(keyBytes); + return SECP256K1.PublicKey.fromBytes(Bytes.wrap(ecPoint.getEncoded(false)).slice(1)); + } + + /** + * Decoded SECP256K1 public key. + * + * @return derived SECP256K1.PublicKey + */ + public SECP256K1.PublicKey publicKey() { + return pubKey; + } + + /** + * Domain name. + * + * @return the domain name + */ + public String domainName() { + return domainName; + } + + @Override + public String toString() { + return String.format("enrtree://%s@%s", encodedPubKey, domainName); + } + } +} diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java new file mode 100644 index 00000000000..0be4ca619d1 --- /dev/null +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSResolver.java @@ -0,0 +1,201 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.p2p.discovery.dns; + +import org.hyperledger.besu.crypto.Hash; +import org.hyperledger.besu.ethereum.p2p.discovery.dns.DNSEntry.ENRNode; +import org.hyperledger.besu.ethereum.p2p.discovery.dns.DNSEntry.ENRTreeLink; +import org.hyperledger.besu.ethereum.p2p.discovery.dns.DNSEntry.ENRTreeRoot; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import com.google.common.base.Splitter; +import io.vertx.core.Future; +import io.vertx.core.Vertx; +import io.vertx.core.dns.DnsClient; +import io.vertx.core.dns.DnsClientOptions; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.crypto.SECP256K1; +import org.apache.tuweni.devp2p.EthereumNodeRecord; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// Adapted from https://github.com/tmio/tuweni and licensed under Apache 2.0 +/** Resolves a set of ENR nodes from a host name. */ +public class DNSResolver { + private static final Logger LOG = LoggerFactory.getLogger(DNSResolver.class); + private final String enrLink; + private long seq; + private final DnsClient dnsClient; + + /** + * Creates a new DNSResolver. + * + * @param vertx Vertx instance which is used to create DNS Client + * @param enrLink the ENR link to start with, of the form enrtree://PUBKEY@domain + * @param seq the sequence number of the root record. If the root record seq is higher, proceed + * with visit. + * @param dnsServer the DNS server to use for DNS query. If empty, the default DNS server will be + * used. + */ + public DNSResolver( + final Vertx vertx, final String enrLink, final long seq, final Optional dnsServer) { + this.enrLink = enrLink; + this.seq = seq; + final DnsClientOptions dnsClientOptions = + dnsServer.map(DNSResolver::buildDnsClientOptions).orElseGet(DnsClientOptions::new); + dnsClient = vertx.createDnsClient(dnsClientOptions); + } + + private static DnsClientOptions buildDnsClientOptions(final String server) { + final List hostPort = Splitter.on(":").splitToList(server); + final DnsClientOptions dnsClientOptions = new DnsClientOptions(); + dnsClientOptions.setHost(hostPort.get(0)); + if (hostPort.size() > 1) { + try { + int port = Integer.parseInt(hostPort.get(1)); + dnsClientOptions.setPort(port); + } catch (NumberFormatException e) { + LOG.trace("Invalid port number {}, ignoring", hostPort.get(1)); + } + } + return dnsClientOptions; + } + + /** + * Convenience method to read all ENRs, from a top-level record. + * + * @return all ENRs collected + */ + public List collectAll() { + final List nodes = new ArrayList<>(); + final DNSVisitor visitor = nodes::add; + visitTree(new ENRTreeLink(enrLink), visitor); + if (!nodes.isEmpty()) { + LOG.debug("Resolved {} nodes from DNS for enr link {}", nodes.size(), enrLink); + } else { + LOG.debug("No nodes resolved from DNS"); + } + return Collections.unmodifiableList(nodes); + } + + /** + * Sequence number of the root record. + * + * @return the current sequence number of the root record + */ + public long sequence() { + return seq; + } + + /** + * Reads a complete tree of record, starting with the top-level record. + * + * @param link the ENR link to start with + * @param visitor the visitor that will look at each record + */ + private void visitTree(final ENRTreeLink link, final DNSVisitor visitor) { + Optional optionalEntry = resolveRecord(link.domainName()); + if (optionalEntry.isEmpty()) { + LOG.trace("No DNS record found for {}", link.domainName()); + return; + } + + final DNSEntry dnsEntry = optionalEntry.get(); + if (!(dnsEntry instanceof ENRTreeRoot treeRoot)) { + LOG.debug("Root entry {} is not an ENR tree root", dnsEntry); + return; + } + + if (!checkSignature(treeRoot, link.publicKey(), treeRoot.sig())) { + LOG.debug("ENR tree root {} failed signature check", link.domainName()); + return; + } + if (treeRoot.seq() <= seq) { + LOG.debug("ENR tree root seq {} is not higher than {}, aborting", treeRoot.seq(), seq); + return; + } + seq = treeRoot.seq(); + + internalVisit(treeRoot.enrRoot(), link.domainName(), visitor); + internalVisit(treeRoot.linkRoot(), link.domainName(), visitor); + } + + private boolean internalVisit( + final String entryName, final String domainName, final DNSVisitor visitor) { + final Optional optionalDNSEntry = resolveRecord(entryName + "." + domainName); + if (optionalDNSEntry.isEmpty()) { + return true; + } + + final DNSEntry entry = optionalDNSEntry.get(); + switch (entry) { + case ENRNode node -> { + return visitor.visit(node.nodeRecord()); + } + case DNSEntry.ENRTree tree -> { + for (String e : tree.entries()) { + boolean keepGoing = internalVisit(e, domainName, visitor); + if (!keepGoing) { + return false; + } + } + } + case ENRTreeLink link -> visitTree(link, visitor); + default -> LOG.debug("Unsupported type of node {}", entry); + } + return true; + } + + /** + * Maps TXT DNS record to DNSEntry. + * + * @param domainName the domain name to query + * @return the DNS entry read from the domain. Empty if no record is found. + */ + Optional resolveRecord(final String domainName) { + return resolveRawRecord(domainName).map(DNSEntry::readDNSEntry); + } + + /** + * Resolves the first TXT record for a domain name and returns it. + * + * @param domainName the name of the DNS domain to query + * @return the first TXT entry of the DNS record. Empty if no record is found. + */ + Optional resolveRawRecord(final String domainName) { + LOG.trace("Resolving TXT records on domain: {}", domainName); + try { + // Future.await parks current virtual thread and waits for the result. Any failure is + // thrown as a Throwable. + return Future.await(dnsClient.resolveTXT(domainName)).stream().findFirst(); + } catch (final Throwable e) { + LOG.trace("Error while resolving TXT records on domain: {}", domainName, e); + return Optional.empty(); + } + } + + private boolean checkSignature( + final ENRTreeRoot root, final SECP256K1.PublicKey pubKey, final SECP256K1.Signature sig) { + final Bytes32 hash = + Hash.keccak256(Bytes.wrap(root.signedContent().getBytes(StandardCharsets.UTF_8))); + return SECP256K1.verifyHashed(hash, sig, pubKey); + } +} diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSVisitor.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSVisitor.java new file mode 100644 index 00000000000..c6ea0a77ed7 --- /dev/null +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSVisitor.java @@ -0,0 +1,32 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.p2p.discovery.dns; + +import org.apache.tuweni.devp2p.EthereumNodeRecord; + +// Adapted from https://github.com/tmio/tuweni and licensed under Apache 2.0 +/** + * Reads ENR (Ethereum Node Records) entries passed in from DNS. The visitor may decide to stop the + * visit by returning false. + */ +public interface DNSVisitor { + /** + * Visit a new ENR record. + * + * @param enr the ENR record read from DNS + * @return true to continue visiting, false otherwise + */ + boolean visit(final EthereumNodeRecord enr); +} diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/KVReader.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/KVReader.java new file mode 100644 index 00000000000..63123b3ee41 --- /dev/null +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/KVReader.java @@ -0,0 +1,49 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.p2p.discovery.dns; + +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +// Adapted from https://github.com/tmio/tuweni and licensed under Apache 2.0 +/** Read Key value pairs from a DNS record */ +public class KVReader { + private KVReader() {} + + /** + * Read a key value pair from a DNS record + * + * @param record the record to read + * @return the key value pair + */ + public static Map readKV(final String record) { + return Arrays.stream(record.split("\\s+")) + .map( + it -> { + // if it contains an = or :, split into Map.entry from the first occurrence + if (it.contains("=")) { + return it.split("=", 2); + } else if (it.contains(":")) { + return it.split(":", 2); + } else { + // this should not happen, as the record should be well-formed + return new String[] {it}; + } + }) + .filter(kv -> kv.length == 2) + .collect(Collectors.toMap(kv -> kv[0], kv -> kv[1])); + } +} diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/DevP2PException.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/DevP2PException.java index b5f80faa8e1..8a8d7551dcc 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/DevP2PException.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/DevP2PException.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.p2p.discovery.internal; public class DevP2PException extends RuntimeException { diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/DiscoveryProtocolLogger.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/DiscoveryProtocolLogger.java index 2bb65abba21..a4aa58de5e9 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/DiscoveryProtocolLogger.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/DiscoveryProtocolLogger.java @@ -47,9 +47,9 @@ public DiscoveryProtocolLogger(final MetricsSystem metricsSystem) { void logSendingPacket(final Peer peer, final Packet packet) { outgoingMessageCounter.labels(packet.getType().name()).inc(); LOG.trace( - "<<< Sending {} packet to peer {}... ({}): {}", + "<<< Sending {} packet to peer {} ({}): {}", shortenPacketType(packet), - peer.getId().slice(0, 16), + peer.getLoggableId(), peer.getEnodeURL(), packet); } @@ -57,9 +57,9 @@ void logSendingPacket(final Peer peer, final Packet packet) { void logReceivedPacket(final Peer peer, final Packet packet) { incomingMessageCounter.labels(packet.getType().name()).inc(); LOG.trace( - ">>> Received {} packet from peer {}... ({}): {}", + ">>> Received {} packet from peer {} ({}): {}", shortenPacketType(packet), - peer.getId().slice(0, 16), + peer.getLoggableId(), peer.getEnodeURL(), packet); } diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerDiscoveryController.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerDiscoveryController.java index ec829d20709..11b5216a82b 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerDiscoveryController.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerDiscoveryController.java @@ -21,8 +21,6 @@ import static java.util.concurrent.TimeUnit.SECONDS; import org.hyperledger.besu.cryptoservices.NodeKey; -import org.hyperledger.besu.ethereum.forkid.ForkId; -import org.hyperledger.besu.ethereum.forkid.ForkIdManager; import org.hyperledger.besu.ethereum.p2p.discovery.DiscoveryPeer; import org.hyperledger.besu.ethereum.p2p.discovery.PeerDiscoveryStatus; import org.hyperledger.besu.ethereum.p2p.peers.Peer; @@ -129,7 +127,6 @@ public class PeerDiscoveryController { private final DiscoveryProtocolLogger discoveryProtocolLogger; private final LabelledMetric interactionCounter; private final LabelledMetric interactionRetryCounter; - private final ForkIdManager forkIdManager; private final boolean filterOnEnrForkId; private final RlpxAgent rlpxAgent; @@ -146,6 +143,7 @@ public class PeerDiscoveryController { private final AtomicBoolean peerTableIsDirty = new AtomicBoolean(false); private OptionalLong cleanTableTimerId = OptionalLong.empty(); private RecursivePeerRefreshState recursivePeerRefreshState; + private final boolean includeBootnodesOnPeerRefresh; private PeerDiscoveryController( final NodeKey nodeKey, @@ -161,9 +159,9 @@ private PeerDiscoveryController( final PeerPermissions peerPermissions, final MetricsSystem metricsSystem, final Optional> maybeCacheForEnrRequests, - final ForkIdManager forkIdManager, final boolean filterOnEnrForkId, - final RlpxAgent rlpxAgent) { + final RlpxAgent rlpxAgent, + final boolean includeBootnodesOnPeerRefresh) { this.timerUtil = timerUtil; this.nodeKey = nodeKey; this.localPeer = localPeer; @@ -177,6 +175,7 @@ private PeerDiscoveryController( this.discoveryProtocolLogger = new DiscoveryProtocolLogger(metricsSystem); this.peerPermissions = new PeerDiscoveryPermissions(localPeer, peerPermissions); this.rlpxAgent = rlpxAgent; + this.includeBootnodesOnPeerRefresh = includeBootnodesOnPeerRefresh; metricsSystem.createIntegerGauge( BesuMetricCategory.NETWORK, @@ -197,11 +196,11 @@ private PeerDiscoveryController( "discovery_interaction_retry_count", "Total number of interaction retries performed", "type"); + this.cachedEnrRequests = maybeCacheForEnrRequests.orElse( CacheBuilder.newBuilder().maximumSize(50).expireAfterWrite(10, SECONDS).build()); - this.forkIdManager = forkIdManager; this.filterOnEnrForkId = filterOnEnrForkId; } @@ -264,6 +263,7 @@ public CompletableFuture stop() { l.clear(); }); inflightInteractions.clear(); + recursivePeerRefreshState.cancel(); return CompletableFuture.completedFuture(null); } @@ -314,6 +314,10 @@ public void onMessage(final Packet packet, final DiscoveryPeer sender) { } final DiscoveryPeer peer = resolvePeer(sender); + if (peer == null) { + return; + } + final Bytes peerId = peer.getId(); switch (packet.getType()) { case PING: if (peerPermissions.allowInboundBonding(peer)) { @@ -333,10 +337,10 @@ public void onMessage(final Packet packet, final DiscoveryPeer sender) { if (filterOnEnrForkId) { requestENR(peer); } - bondingPeers.invalidate(peer.getId()); + bondingPeers.invalidate(peerId); addToPeerTable(peer); recursivePeerRefreshState.onBondingComplete(peer); - Optional.ofNullable(cachedEnrRequests.getIfPresent(peer.getId())) + Optional.ofNullable(cachedEnrRequests.getIfPresent(peerId)) .ifPresent(cachedEnrRequest -> processEnrRequest(peer, cachedEnrRequest)); }); break; @@ -360,12 +364,12 @@ public void onMessage(final Packet packet, final DiscoveryPeer sender) { if (PeerDiscoveryStatus.BONDED.equals(peer.getStatus())) { processEnrRequest(peer, packet); } else if (PeerDiscoveryStatus.BONDING.equals(peer.getStatus())) { - LOG.trace("ENR_REQUEST cached for bonding peer Id: {}", peer.getId()); + LOG.trace("ENR_REQUEST cached for bonding peer Id: {}", peerId); // Due to UDP, it may happen that we receive the ENR_REQUEST just before the PONG. // Because peers want to send the ENR_REQUEST directly after the pong. // If this happens we don't want to ignore the request but process when bonded. // this cache allows to keep the request and to respond after having processed the PONG - cachedEnrRequests.put(peer.getId(), packet); + cachedEnrRequests.put(peerId, packet); } break; case ENR_RESPONSE: @@ -376,26 +380,6 @@ public void onMessage(final Packet packet, final DiscoveryPeer sender) { packet.getPacketData(ENRResponsePacketData.class); final NodeRecord enr = packetData.get().getEnr(); peer.setNodeRecord(enr); - - final Optional maybeForkId = peer.getForkId(); - if (maybeForkId.isPresent()) { - if (forkIdManager.peerCheck(maybeForkId.get())) { - connectOnRlpxLayer(peer); - LOG.debug( - "Peer {} PASSED fork id check. ForkId received: {}", - sender.getId(), - maybeForkId.get()); - } else { - LOG.debug( - "Peer {} FAILED fork id check. ForkId received: {}", - sender.getId(), - maybeForkId.get()); - } - } else { - // if the peer hasn't sent the ForkId try to connect to it anyways - connectOnRlpxLayer(peer); - LOG.debug("No fork id sent by peer: {}", peer.getId()); - } }); break; } @@ -422,32 +406,33 @@ private List getPeersFromNeighborsPacket(final Packet packet) { } private boolean addToPeerTable(final DiscoveryPeer peer) { - // Reset the last seen timestamp. - final long now = System.currentTimeMillis(); - if (peer.getFirstDiscovered() == 0) { - peer.setFirstDiscovered(now); - } - peer.setLastSeen(now); + final PeerTable.AddResult result = peerTable.tryAdd(peer); + if (result.getOutcome() != PeerTable.AddResult.AddOutcome.INVALID) { + + // Reset the last seen timestamp. + final long now = System.currentTimeMillis(); + if (peer.getFirstDiscovered() == 0) { + peer.setFirstDiscovered(now); + } + peer.setLastSeen(now); - if (peer.getStatus() != PeerDiscoveryStatus.BONDED) { - peer.setStatus(PeerDiscoveryStatus.BONDED); - if (!filterOnEnrForkId) { + if (peer.getStatus() != PeerDiscoveryStatus.BONDED) { + peer.setStatus(PeerDiscoveryStatus.BONDED); connectOnRlpxLayer(peer); } - } - final PeerTable.AddResult result = peerTable.tryAdd(peer); + if (result.getOutcome() == PeerTable.AddResult.AddOutcome.ALREADY_EXISTED) { + // Bump peer. + peerTable.tryEvict(peer); + peerTable.tryAdd(peer); + } else if (result.getOutcome() == PeerTable.AddResult.AddOutcome.BUCKET_FULL) { + peerTable.tryEvict(result.getEvictionCandidate()); + peerTable.tryAdd(peer); + } - if (result.getOutcome() == PeerTable.AddResult.AddOutcome.ALREADY_EXISTED) { - // Bump peer. - peerTable.tryEvict(peer); - peerTable.tryAdd(peer); - } else if (result.getOutcome() == PeerTable.AddResult.AddOutcome.BUCKET_FULL) { - peerTable.tryEvict(result.getEvictionCandidate()); - peerTable.tryAdd(peer); + return true; } - - return true; + return false; } void connectOnRlpxLayer(final DiscoveryPeer peer) { @@ -501,7 +486,17 @@ RecursivePeerRefreshState getRecursivePeerRefreshState() { */ private void refreshTable() { final Bytes target = Peer.randomId(); + final List initialPeers = peerTable.nearestBondedPeers(Peer.randomId(), 16); + if (includeBootnodesOnPeerRefresh) { + bootstrapNodes.stream() + .filter(p -> p.getStatus() != PeerDiscoveryStatus.BONDED) + .forEach(p -> p.setStatus(PeerDiscoveryStatus.KNOWN)); + + // If configured to retry bootnodes during peer table refresh, include them + // in the initial peers list. + initialPeers.addAll(bootstrapNodes); + } recursivePeerRefreshState.start(initialPeers, target); lastRefreshTime = System.currentTimeMillis(); } @@ -560,8 +555,6 @@ void bond(final DiscoveryPeer peer) { */ @VisibleForTesting void requestENR(final DiscoveryPeer peer) { - peer.setStatus(PeerDiscoveryStatus.ENR_REQUESTED); - final Consumer action = interaction -> { final ENRRequestPacketData data = ENRRequestPacketData.create(); @@ -715,7 +708,9 @@ public void setRetryDelayFunction(final RetryDelayFunction retryDelayFunction) { public void handleBondingRequest(final DiscoveryPeer peer) { final DiscoveryPeer peerToBond = resolvePeer(peer); - + if (peerToBond == null) { + return; + } if (peerPermissions.allowOutboundBonding(peerToBond) && PeerDiscoveryStatus.KNOWN.equals(peerToBond.getStatus())) { bond(peerToBond); @@ -724,6 +719,9 @@ public void handleBondingRequest(final DiscoveryPeer peer) { // Load the peer first from the table, then from bonding cache or use the instance that comes in. private DiscoveryPeer resolvePeer(final DiscoveryPeer peer) { + if (peerTable.ipAddressIsInvalid(peer.getEndpoint())) { + return null; + } final Optional maybeKnownPeer = peerTable.get(peer).filter(known -> known.discoveryEndpointMatches(peer)); DiscoveryPeer resolvedPeer = maybeKnownPeer.orElse(peer); @@ -741,6 +739,7 @@ private DiscoveryPeer resolvePeer(final DiscoveryPeer peer) { private class PeerInteractionState implements Predicate { private static final int MAX_RETRIES = 5; + /** * The action that led to the peer being in this state (e.g. sending a PING or NEIGHBORS * message), in case it needs to be retried. @@ -748,12 +747,15 @@ private class PeerInteractionState implements Predicate { private final Consumer action; private final Bytes peerId; + /** The expected type of the message that will transition the peer out of this state. */ private final PacketType expectedType; private final Counter retryCounter; + /** A custom filter to accept transitions out of this state. */ private Predicate filter; + /** Timers associated with this entry. */ private OptionalLong timerId = OptionalLong.empty(); @@ -827,6 +829,7 @@ public static class Builder { private long cleanPeerTableIntervalMs = MILLISECONDS.convert(1, TimeUnit.MINUTES); private final List bootstrapNodes = new ArrayList<>(); private PeerTable peerTable; + private boolean includeBootnodesOnPeerRefresh = true; // Required dependencies private NodeKey nodeKey; @@ -838,7 +841,6 @@ public static class Builder { private Cache cachedEnrRequests = CacheBuilder.newBuilder().maximumSize(50).expireAfterWrite(10, SECONDS).build(); - private ForkIdManager forkIdManager; private RlpxAgent rlpxAgent; private Builder() {} @@ -846,10 +848,6 @@ private Builder() {} public PeerDiscoveryController build() { validate(); - if (peerTable == null) { - peerTable = new PeerTable(this.nodeKey.getPublicKey().getEncodedBytes(), 16); - } - return new PeerDiscoveryController( nodeKey, localPeer, @@ -864,9 +862,9 @@ public PeerDiscoveryController build() { peerPermissions, metricsSystem, Optional.of(cachedEnrRequests), - forkIdManager, filterOnEnrForkId, - rlpxAgent); + rlpxAgent, + includeBootnodesOnPeerRefresh); } private void validate() { @@ -875,8 +873,8 @@ private void validate() { validateRequiredDependency(timerUtil, "TimerUtil"); validateRequiredDependency(workerExecutor, "AsyncExecutor"); validateRequiredDependency(metricsSystem, "MetricsSystem"); - validateRequiredDependency(forkIdManager, "ForkIdManager"); validateRequiredDependency(rlpxAgent, "RlpxAgent"); + validateRequiredDependency(peerTable, "PeerTable"); } private void validateRequiredDependency(final Object object, final String name) { @@ -971,9 +969,8 @@ public Builder rlpxAgent(final RlpxAgent rlpxAgent) { return this; } - public Builder forkIdManager(final ForkIdManager forkIdManager) { - checkNotNull(forkIdManager); - this.forkIdManager = forkIdManager; + public Builder includeBootnodesOnPeerRefresh(final boolean includeBootnodesOnPeerRefresh) { + this.includeBootnodesOnPeerRefresh = includeBootnodesOnPeerRefresh; return this; } } diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerTable.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerTable.java index e153acbfc26..50250e1c2a0 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerTable.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerTable.java @@ -19,12 +19,14 @@ import org.hyperledger.besu.crypto.Hash; import org.hyperledger.besu.ethereum.p2p.discovery.DiscoveryPeer; +import org.hyperledger.besu.ethereum.p2p.discovery.Endpoint; import org.hyperledger.besu.ethereum.p2p.discovery.PeerDiscoveryStatus; import org.hyperledger.besu.ethereum.p2p.discovery.internal.PeerTable.AddResult.AddOutcome; import org.hyperledger.besu.ethereum.p2p.peers.Peer; import org.hyperledger.besu.ethereum.p2p.peers.PeerId; import java.util.Arrays; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -33,6 +35,7 @@ import java.util.stream.Stream; import com.google.common.hash.BloomFilter; +import org.apache.commons.collections4.queue.CircularFifoQueue; import org.apache.tuweni.bytes.Bytes; /** @@ -51,31 +54,30 @@ public class PeerTable { private final Map distanceCache; private BloomFilter idBloom; private int evictionCnt = 0; + private final LinkedHashMapWithMaximumSize ipAddressCheckMap = + new LinkedHashMapWithMaximumSize<>(DEFAULT_BUCKET_SIZE * N_BUCKETS); + private final CircularFifoQueue invalidIPs = + new CircularFifoQueue<>(DEFAULT_BUCKET_SIZE * N_BUCKETS); /** * Builds a new peer table, where distance is calculated using the provided nodeId as a baseline. * * @param nodeId The ID of the node where this peer table is stored. - * @param bucketSize The maximum length of each k-bucket. */ - public PeerTable(final Bytes nodeId, final int bucketSize) { + public PeerTable(final Bytes nodeId) { this.keccak256 = Hash.keccak256(nodeId); this.table = Stream.generate(() -> new Bucket(DEFAULT_BUCKET_SIZE)) .limit(N_BUCKETS + 1) .toArray(Bucket[]::new); this.distanceCache = new ConcurrentHashMap<>(); - this.maxEntriesCnt = N_BUCKETS * bucketSize; + this.maxEntriesCnt = N_BUCKETS * DEFAULT_BUCKET_SIZE; // A bloom filter with 4096 expected insertions of 64-byte keys with a 0.1% false positive // probability yields a memory footprint of ~7.5kb. buildBloomFilter(); } - public PeerTable(final Bytes nodeId) { - this(nodeId, DEFAULT_BUCKET_SIZE); - } - /** * Returns the table's representation of a peer, if it exists. * @@ -83,11 +85,12 @@ public PeerTable(final Bytes nodeId) { * @return The stored representation. */ public Optional get(final PeerId peer) { - if (!idBloom.mightContain(peer.getId())) { + final Bytes peerId = peer.getId(); + if (!idBloom.mightContain(peerId)) { return Optional.empty(); } final int distance = distanceFrom(peer); - return table[distance].getAndTouch(peer.getId()); + return table[distance].getAndTouch(peerId); } /** @@ -101,6 +104,7 @@ public Optional get(final PeerId peer) { *

  • the operation failed because the k-bucket was full, in which case a candidate is proposed * for eviction. *
  • the operation failed because the peer already existed. + *
  • the operation failed because the IP address is invalid. * * * @param peer The peer to add. @@ -108,6 +112,9 @@ public Optional get(final PeerId peer) { * @see AddOutcome */ public AddResult tryAdd(final DiscoveryPeer peer) { + if (ipAddressIsInvalid(peer.getEndpoint())) { + return AddResult.invalid(); + } final Bytes id = peer.getId(); final int distance = distanceFrom(peer); @@ -133,6 +140,7 @@ public AddResult tryAdd(final DiscoveryPeer peer) { if (!res.isPresent()) { idBloom.put(id); distanceCache.put(id, distance); + ipAddressCheckMap.put(getKey(peer.getEndpoint()), peer.getEndpoint().getUdpPort()); return AddResult.added(); } @@ -204,6 +212,34 @@ public Stream streamAllPeers() { return Arrays.stream(table).flatMap(e -> e.getPeers().stream()); } + boolean ipAddressIsInvalid(final Endpoint endpoint) { + final String key = getKey(endpoint); + if (invalidIPs.contains(key)) { + return true; + } + if (ipAddressCheckMap.containsKey(key) && ipAddressCheckMap.get(key) != endpoint.getUdpPort()) { + // This peer has multiple discovery services on the same IP address + TCP port. + invalidIPs.add(key); + for (final Bucket bucket : table) { + bucket.getPeers().stream() + .filter(p -> p.getEndpoint().getHost().equals(endpoint.getHost())) + .forEach(p -> evictAndStore(p, bucket, key)); + } + return true; + } else { + return false; + } + } + + private void evictAndStore(final DiscoveryPeer peer, final Bucket bucket, final String key) { + bucket.evict(peer); + invalidIPs.add(key); + } + + private static String getKey(final Endpoint endpoint) { + return endpoint.getHost() + endpoint.getFunctionalTcpPort(); + } + /** * Calculates the XOR distance between the keccak-256 hashes of our node ID and the provided * {@link DiscoveryPeer}. @@ -220,6 +256,7 @@ private int distanceFrom(final PeerId peer) { /** A class that encapsulates the result of a peer addition to the table. */ public static class AddResult { + /** The outcome of the operation. */ public enum AddOutcome { @@ -233,7 +270,10 @@ public enum AddOutcome { ALREADY_EXISTED, /** The caller requested to add ourselves. */ - SELF + SELF, + + /** The peer was not added because the IP address is invalid. */ + INVALID } private final AddOutcome outcome; @@ -260,6 +300,10 @@ static AddResult self() { return new AddResult(AddOutcome.SELF, null); } + public static AddResult invalid() { + return new AddResult((AddOutcome.INVALID), null); + } + public AddOutcome getOutcome() { return outcome; } @@ -269,6 +313,20 @@ public Peer getEvictionCandidate() { } } + private static class LinkedHashMapWithMaximumSize extends LinkedHashMap { + private final int maxSize; + + public LinkedHashMapWithMaximumSize(final int maxSize) { + super(maxSize, 0.75f, false); + this.maxSize = maxSize; + } + + @Override + protected boolean removeEldestEntry(final Map.Entry eldest) { + return size() > maxSize; + } + } + static class EvictResult { public enum EvictOutcome { EVICTED, diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/RecursivePeerRefreshState.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/RecursivePeerRefreshState.java index a392ee034de..3329dad4ce8 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/RecursivePeerRefreshState.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/RecursivePeerRefreshState.java @@ -105,6 +105,10 @@ private void addInitialPeers(final List initialPeers) { } private void bondingInitiateRound() { + if (!iterativeSearchInProgress) { + // cancelled so we can ignore + return; + } currentRoundTimeout.ifPresent(RoundTimeout::cancelTimeout); final List candidates = bondingRoundCandidates(); if (candidates.isEmpty()) { @@ -137,6 +141,10 @@ private void performIfNotCancelled(final Runnable action, final AtomicBoolean ca } private void bondingCancelOutstandingRequests() { + if (!iterativeSearchInProgress) { + // cancelled so we can ignore + return; + } LOG.debug("Bonding round timed out"); for (final Map.Entry entry : oneTrueMap.entrySet()) { final MetadataPeer metadataPeer = entry.getValue(); @@ -149,6 +157,10 @@ private void bondingCancelOutstandingRequests() { } private void neighboursInitiateRound() { + if (!iterativeSearchInProgress) { + // cancelled so we can ignore + return; + } currentRoundTimeout.ifPresent(RoundTimeout::cancelTimeout); final List candidates = neighboursRoundCandidates(); if (candidates.isEmpty() || reachedMaximumNumberOfRounds()) { @@ -172,6 +184,10 @@ private void neighboursInitiateRound() { } private void neighboursCancelOutstandingRequests() { + if (!iterativeSearchInProgress) { + // cancelled so we can ignore + return; + } LOG.debug("Neighbours round timed out"); for (final Map.Entry entry : oneTrueMap.entrySet()) { final MetadataPeer metadataPeer = entry.getValue(); @@ -185,7 +201,8 @@ private void neighboursCancelOutstandingRequests() { private boolean satisfiesMapAdditionCriteria(final DiscoveryPeer discoPeer) { return !oneTrueMap.containsKey(discoPeer.getId()) && (initialPeers.contains(discoPeer) || !peerTable.get(discoPeer).isPresent()) - && !discoPeer.getId().equals(localPeer.getId()); + && !discoPeer.getId().equals(localPeer.getId()) + && !peerTable.ipAddressIsInvalid(discoPeer.getEndpoint()); } void onNeighboursReceived(final DiscoveryPeer peer, final List peers) { @@ -217,6 +234,10 @@ void onNeighboursReceived(final DiscoveryPeer peer, final List pe } void onBondingComplete(final DiscoveryPeer peer) { + if (!iterativeSearchInProgress) { + // cancelled so we can ignore + return; + } final MetadataPeer iterationParticipant = oneTrueMap.get(peer.getId()); if (iterationParticipant == null) { return; diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetwork.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetwork.java index ec65934b297..55a536976fe 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetwork.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetwork.java @@ -27,6 +27,9 @@ import org.hyperledger.besu.ethereum.p2p.discovery.PeerDiscoveryAgent; import org.hyperledger.besu.ethereum.p2p.discovery.PeerDiscoveryStatus; import org.hyperledger.besu.ethereum.p2p.discovery.VertxPeerDiscoveryAgent; +import org.hyperledger.besu.ethereum.p2p.discovery.dns.DNSDaemon; +import org.hyperledger.besu.ethereum.p2p.discovery.dns.DNSDaemonListener; +import org.hyperledger.besu.ethereum.p2p.discovery.internal.PeerTable; import org.hyperledger.besu.ethereum.p2p.peers.DefaultPeerPrivileges; import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl; import org.hyperledger.besu.ethereum.p2p.peers.LocalNode; @@ -68,17 +71,19 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; import com.google.common.annotations.VisibleForTesting; +import io.vertx.core.DeploymentOptions; +import io.vertx.core.Future; +import io.vertx.core.ThreadingModel; import io.vertx.core.Vertx; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.devp2p.EthereumNodeRecord; -import org.apache.tuweni.discovery.DNSDaemon; -import org.apache.tuweni.discovery.DNSDaemonListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -146,7 +151,8 @@ public class DefaultP2PNetwork implements P2PNetwork { private final CountDownLatch shutdownLatch = new CountDownLatch(2); private final Duration shutdownTimeout = Duration.ofSeconds(15); private final Vertx vertx; - private DNSDaemon dnsDaemon; + private final AtomicReference> dnsDaemonRef = + new AtomicReference<>(Optional.empty()); /** * Creates a peer networking service for production purposes. @@ -188,10 +194,9 @@ public class DefaultP2PNetwork implements P2PNetwork { this.peerPermissions = peerPermissions; this.vertx = vertx; - // set the requirement here that the number of peers be greater than the lower bound - final int peerLowerBound = rlpxAgent.getPeerLowerBound(); - LOG.debug("setting peerLowerBound {}", peerLowerBound); - peerDiscoveryAgent.addPeerRequirement(() -> rlpxAgent.getConnectionCount() >= peerLowerBound); + final int maxPeers = rlpxAgent.getMaxPeers(); + LOG.debug("setting maxPeers {}", maxPeers); + peerDiscoveryAgent.addPeerRequirement(() -> rlpxAgent.getConnectionCount() >= maxPeers); subscribeDisconnect(reputationManager); } @@ -227,15 +232,25 @@ public void start() { LOG.info( "Starting DNS discovery with DNS Server override {}", dnsServer)); - dnsDaemon = + final DNSDaemon dnsDaemon = new DNSDaemon( disco, createDaemonListener(), 0L, + 1000L, // start after 1 second 600000L, - config.getDnsDiscoveryServerOverride().orElse(null), - vertx); - dnsDaemon.start(); + config.getDnsDiscoveryServerOverride().orElse(null)); + + // Use Java 21 virtual thread to deploy verticle + final DeploymentOptions options = + new DeploymentOptions() + .setThreadingModel(ThreadingModel.VIRTUAL_THREAD) + .setInstances(1) + .setWorkerPoolSize(1); + + final Future deployId = vertx.deployVerticle(dnsDaemon, options); + deployId.toCompletionStage().toCompletableFuture().join(); + dnsDaemonRef.set(Optional.of(dnsDaemon)); }); final int listeningPort = rlpxAgent.start().join(); @@ -282,7 +297,9 @@ public void stop() { return; } - getDnsDaemon().ifPresent(DNSDaemon::close); + // since dnsDaemon is a vertx verticle, vertx.close will undeploy it. + // However, we can safely call stop as well. + dnsDaemonRef.get().ifPresent(DNSDaemon::stop); peerConnectionScheduler.shutdownNow(); peerDiscoveryAgent.stop().whenComplete((res, err) -> shutdownLatch.countDown()); @@ -339,7 +356,7 @@ public boolean removeMaintainedConnectionPeer(final Peer peer) { @VisibleForTesting Optional getDnsDaemon() { - return Optional.ofNullable(dnsDaemon); + return dnsDaemonRef.get(); } @VisibleForTesting @@ -383,11 +400,12 @@ void checkMaintainedConnectionPeers() { @VisibleForTesting void attemptPeerConnections() { LOG.trace("Initiating connections to discovered peers."); - rlpxAgent.connect( + final Stream toTry = streamDiscoveredPeers() .filter(peer -> peer.getStatus() == PeerDiscoveryStatus.BONDED) .filter(peerDiscoveryAgent::checkForkId) - .sorted(Comparator.comparing(DiscoveryPeer::getLastAttemptedConnection))); + .sorted(Comparator.comparing(DiscoveryPeer::getLastAttemptedConnection)); + toTry.forEach(rlpxAgent::connect); } @Override @@ -424,7 +442,6 @@ public void subscribeConnect(final ConnectCallback callback) { public void subscribeConnectRequest(final ShouldConnectCallback callback) { rlpxAgent.subscribeConnectRequest(callback); } - ; @Override public void subscribeDisconnect(final DisconnectCallback callback) { @@ -510,7 +527,8 @@ public static class Builder { private boolean legacyForkIdEnabled = false; private Supplier> allConnectionsSupplier; private Supplier> allActiveConnectionsSupplier; - private int peersLowerBound; + private int maxPeers; + private PeerTable peerTable; public P2PNetwork build() { validate(); @@ -528,6 +546,7 @@ private P2PNetwork doBuild() { final MutableLocalNode localNode = MutableLocalNode.create(config.getRlpx().getClientId(), 5, supportedCapabilities); final PeerPrivileges peerPrivileges = new DefaultPeerPrivileges(maintainedPeers); + peerTable = new PeerTable(nodeKey.getPublicKey().getEncodedBytes()); rlpxAgent = rlpxAgent == null ? createRlpxAgent(localNode, peerPrivileges) : rlpxAgent; peerDiscoveryAgent = peerDiscoveryAgent == null ? createDiscoveryAgent() : peerDiscoveryAgent; @@ -572,7 +591,8 @@ private PeerDiscoveryAgent createDiscoveryAgent() { metricsSystem, storageProvider, forkIdManager, - rlpxAgent); + rlpxAgent, + peerTable); } private RlpxAgent createRlpxAgent( @@ -588,7 +608,8 @@ private RlpxAgent createRlpxAgent( .p2pTLSConfiguration(p2pTLSConfiguration) .allConnectionsSupplier(allConnectionsSupplier) .allActiveConnectionsSupplier(allActiveConnectionsSupplier) - .peersLowerBound(peersLowerBound) + .maxPeers(maxPeers) + .peerTable(peerTable) .build(); } @@ -704,8 +725,8 @@ public Builder allActiveConnectionsSupplier( return this; } - public Builder peersLowerBound(final int peersLowerBound) { - this.peersLowerBound = peersLowerBound; + public Builder maxPeers(final int maxPeers) { + this.maxPeers = maxPeers; return this; } } diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/NetworkRunner.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/NetworkRunner.java index 5811bd65f99..77e9903bb89 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/NetworkRunner.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/NetworkRunner.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.ethereum.p2p.network; +import org.hyperledger.besu.ethereum.p2p.peers.Peer; import org.hyperledger.besu.ethereum.p2p.rlpx.RlpxAgent; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Capability; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.SubProtocol; @@ -31,6 +32,7 @@ import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiFunction; import java.util.stream.Collectors; import org.slf4j.Logger; @@ -47,15 +49,18 @@ public class NetworkRunner implements AutoCloseable { private final Map subProtocols; private final List protocolManagers; private final LabelledMetric inboundMessageCounter; + private final BiFunction ethPeersShouldConnect; private NetworkRunner( final P2PNetwork network, final Map subProtocols, final List protocolManagers, - final MetricsSystem metricsSystem) { + final MetricsSystem metricsSystem, + final BiFunction ethPeersShouldConnect) { this.network = network; this.protocolManagers = protocolManagers; this.subProtocols = subProtocols; + this.ethPeersShouldConnect = ethPeersShouldConnect; inboundMessageCounter = metricsSystem.createLabelledCounter( BesuMetricCategory.NETWORK, @@ -130,7 +135,10 @@ private void setupHandlers() { cap.getVersion(), code, message.getConnection().getPeerInfo().getNodeId()); - message.getConnection().disconnect(DisconnectReason.BREACH_OF_PROTOCOL); + message + .getConnection() + .disconnect( + DisconnectReason.BREACH_OF_PROTOCOL_INVALID_MESSAGE_CODE_FOR_PROTOCOL); return; } inboundMessageCounter @@ -155,8 +163,7 @@ private void setupHandlers() { protocolManager.handleNewConnection(connection); }); - network.subscribeConnectRequest( - (peer, incoming) -> protocolManager.shouldConnect(peer, incoming)); + network.subscribeConnectRequest(ethPeersShouldConnect::apply); network.subscribeDisconnect( (connection, disconnectReason, initiatedByPeer) -> { @@ -183,6 +190,7 @@ public static class Builder { List protocolManagers = new ArrayList<>(); List subProtocols = new ArrayList<>(); MetricsSystem metricsSystem; + private BiFunction ethPeersShouldConnect; public NetworkRunner build() { final Map subProtocolMap = new HashMap<>(); @@ -200,7 +208,8 @@ public NetworkRunner build() { } } final P2PNetwork network = networkProvider.build(caps); - return new NetworkRunner(network, subProtocolMap, protocolManagers, metricsSystem); + return new NetworkRunner( + network, subProtocolMap, protocolManagers, metricsSystem, ethPeersShouldConnect); } public Builder protocolManagers(final List protocolManagers) { @@ -227,6 +236,11 @@ public Builder metricsSystem(final MetricsSystem metricsSystem) { this.metricsSystem = metricsSystem; return this; } + + public Builder ethPeersShouldConnect(final BiFunction shouldConnect) { + this.ethPeersShouldConnect = shouldConnect; + return this; + } } @FunctionalInterface diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/P2PNetwork.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/P2PNetwork.java index 268801c9dc0..36ccd494354 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/P2PNetwork.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/P2PNetwork.java @@ -88,6 +88,7 @@ default int getPeerCount() { void subscribeConnect(final ConnectCallback callback); void subscribeConnectRequest(final ShouldConnectCallback callback); + /** * Subscribe a {@link Consumer} to all incoming new Peer disconnect events. * diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/PeerDenylistManager.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/PeerDenylistManager.java index bf98a0bb3f5..505e38aa865 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/PeerDenylistManager.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/PeerDenylistManager.java @@ -50,7 +50,9 @@ public void onDisconnect( final PeerConnection connection, final DisconnectReason reason, final boolean initiatedByPeer) { - if (shouldBlock(reason, initiatedByPeer)) { + // we have a number of reasons that use the same code, but with different message strings + // so here we use the code of the reason param to ensure we get the no-message version + if (shouldBlock(DisconnectReason.forCode(reason.getValue()), initiatedByPeer)) { if (maintainedPeers.contains(connection.getPeer())) { LOG.debug( "Skip adding maintained peer {} to peer denylist for reason {}", diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/ProtocolManager.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/ProtocolManager.java index f87847a488d..c61e6d907cd 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/ProtocolManager.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/ProtocolManager.java @@ -14,7 +14,6 @@ */ package org.hyperledger.besu.ethereum.p2p.network; -import org.hyperledger.besu.ethereum.p2p.peers.Peer; import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Capability; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Message; @@ -59,14 +58,6 @@ public interface ProtocolManager extends AutoCloseable { */ void handleNewConnection(PeerConnection peerConnection); - /** - * Call this to find out whether we should try to connect to a certain peer - * - * @param peer the peer that we are trying to connect to - * @param incoming true if the connection is incoming - * @return true, if the ProtocolManager wants to connect to the peer, false otherwise - */ - boolean shouldConnect(Peer peer, final boolean incoming); /** * Handles peer disconnects. * diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/exceptions/PeerChannelClosedException.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/exceptions/PeerChannelClosedException.java index 34590ae1d61..05038f8f83b 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/exceptions/PeerChannelClosedException.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/network/exceptions/PeerChannelClosedException.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.p2p.network.exceptions; import org.hyperledger.besu.ethereum.p2p.rlpx.wire.PeerInfo; diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/DefaultPeerId.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/DefaultPeerId.java index 14a27b2fab3..6bd07fc7a73 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/DefaultPeerId.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/DefaultPeerId.java @@ -34,6 +34,11 @@ public Bytes getId() { return id; } + @Override + public String getLoggableId() { + return getId().slice(0, 8) + "..."; + } + @Override public Bytes32 keccak256() { if (keccak256 == null) { diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/DefaultPeerPrivileges.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/DefaultPeerPrivileges.java index 535a49f2ac6..c52b24f61cb 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/DefaultPeerPrivileges.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/DefaultPeerPrivileges.java @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.ethereum.p2p.peers; +import org.apache.tuweni.bytes.Bytes; + public class DefaultPeerPrivileges implements PeerPrivileges { private final MaintainedPeers maintainedPeers; @@ -22,7 +24,7 @@ public DefaultPeerPrivileges(final MaintainedPeers maintainedPeers) { } @Override - public boolean canExceedConnectionLimits(final Peer peer) { - return maintainedPeers.contains(peer); + public boolean canExceedConnectionLimits(final Bytes peerId) { + return maintainedPeers.streamPeers().anyMatch(p -> p.getId().equals(peerId)); } } diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/EnodeURLImpl.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/EnodeURLImpl.java index 747a06efdf6..e087edd4312 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/EnodeURLImpl.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/EnodeURLImpl.java @@ -23,6 +23,7 @@ import java.net.InetAddress; import java.net.URI; import java.net.UnknownHostException; +import java.util.Locale; import java.util.Objects; import java.util.Optional; import java.util.OptionalInt; @@ -83,7 +84,9 @@ public static EnodeURL fromString( String message = ""; if (enodeDnsConfiguration.dnsEnabled() && !enodeDnsConfiguration.updateEnabled()) { message = - "Invalid IP address (or DNS query resolved an invalid IP). --Xdns-enabled is true but --Xdns-update-enabled flag is false."; + String.format( + "Invalid IP address '%s' (or DNS query resolved an invalid IP). --Xdns-enabled is true but --Xdns-update-enabled flag is false.", + value); } else { message = String.format( @@ -155,7 +158,7 @@ public static boolean sameListeningEndpoint(final EnodeURL enodeA, final EnodeUR public static Bytes parseNodeId(final String nodeId) { int expectedSize = EnodeURLImpl.NODE_ID_SIZE * 2; - if (nodeId.toLowerCase().startsWith("0x")) { + if (nodeId.toLowerCase(Locale.ROOT).startsWith("0x")) { expectedSize += 2; } checkArgument( @@ -378,10 +381,11 @@ public Builder ipAddress(final String ip) { return ipAddress(ip, EnodeDnsConfiguration.dnsDisabled()); } - public Builder ipAddress(final String ip, final EnodeDnsConfiguration enodeDnsConfiguration) { + public Builder ipAddress( + final String hostField, final EnodeDnsConfiguration enodeDnsConfiguration) { if (enodeDnsConfiguration.dnsEnabled()) { try { - this.ip = InetAddress.getByName(ip); + this.ip = InetAddress.getByName(hostField); if (enodeDnsConfiguration.updateEnabled()) { if (this.ip.isLoopbackAddress()) { this.ip = InetAddress.getLocalHost(); @@ -395,10 +399,10 @@ public Builder ipAddress(final String ip, final EnodeDnsConfiguration enodeDnsCo this.ip = InetAddresses.forString("127.0.0.1"); } } - } else if (InetAddresses.isUriInetAddress(ip)) { - this.ip = InetAddresses.forUriString(ip); - } else if (InetAddresses.isInetAddress(ip)) { - this.ip = InetAddresses.forString(ip); + } else if (InetAddresses.isUriInetAddress(hostField)) { + this.ip = InetAddresses.forUriString(hostField); + } else if (InetAddresses.isInetAddress(hostField)) { + this.ip = InetAddresses.forString(hostField); } else { throw new IllegalArgumentException("Invalid ip address."); } diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/PeerId.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/PeerId.java index babb6bb1af0..d62c1bf5a39 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/PeerId.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/PeerId.java @@ -33,4 +33,6 @@ public interface PeerId { * @return The Keccak-256 hash of the peer's ID. */ Bytes32 keccak256(); + + String getLoggableId(); } diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/PeerPrivileges.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/PeerPrivileges.java index e99b1a39729..6447522e287 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/PeerPrivileges.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/peers/PeerPrivileges.java @@ -14,14 +14,16 @@ */ package org.hyperledger.besu.ethereum.p2p.peers; +import org.apache.tuweni.bytes.Bytes; + public interface PeerPrivileges { /** * If true, the given peer can connect or remain connected even if the max connection limit or the * maximum remote connection limit has been reached or exceeded. * - * @param peer The peer to be checked. + * @param peerId The peer id to be checked. * @return {@code true} if the peer should be allowed to connect regardless of connection limits. */ - boolean canExceedConnectionLimits(final Peer peer); + boolean canExceedConnectionLimits(final Bytes peerId); } diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/permissions/PeerPermissionSubnet.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/permissions/PeerPermissionSubnet.java new file mode 100644 index 00000000000..720cd4dd609 --- /dev/null +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/permissions/PeerPermissionSubnet.java @@ -0,0 +1,78 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.p2p.permissions; + +import org.hyperledger.besu.ethereum.p2p.peers.Peer; + +import java.util.List; + +import org.apache.commons.net.util.SubnetUtils.SubnetInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Manages peer permissions based on IP subnet restrictions. + * + *

    This class extends {@link PeerPermissions} to implement access control based on IP subnets. It + * allows for the configuration of permitted subnets and uses these configurations to determine + * whether a peer should be allowed or denied access based on its IP address. + * + *

    Note: If no subnets are specified, all peers are considered permitted by default. + * + * @see PeerPermissions + */ +public class PeerPermissionSubnet extends PeerPermissions { + private static final Logger LOG = LoggerFactory.getLogger(PeerPermissionSubnet.class); + + private final List allowedSubnets; + + /** + * Constructs a new {@code PeerPermissionSubnet} instance with specified allowed subnets. + * + * @param allowedSubnets A list of {@link SubnetInfo} objects representing the subnets that are + * allowed to interact with the local node. Cannot be {@code null}. + */ + public PeerPermissionSubnet(final List allowedSubnets) { + this.allowedSubnets = allowedSubnets; + } + + /** + * Determines if a peer is permitted based on the configured subnets. + * + *

    This method checks if the remote peer's IP address falls within any of the configured + * allowed subnets. If the peer's IP is within any of the allowed subnets, it is permitted. + * Otherwise, it is denied. + * + * @param localNode This parameter is not used in the current implementation. + * @param remotePeer The remote peer to check. Its IP address is used to determine permission. + * @param action Ignored. If the peer is not allowed in the subnet, all actions are now allowed. + * @return {@code true} if the peer is permitted based on its IP address; {@code false} otherwise. + */ + @Override + public boolean isPermitted(final Peer localNode, final Peer remotePeer, final Action action) { + // If no subnets are specified, all peers are permitted + if (allowedSubnets == null || allowedSubnets.isEmpty()) { + return true; + } + String remotePeerHostAddress = remotePeer.getEnodeURL().getIpAsString(); + for (SubnetInfo subnet : allowedSubnets) { + if (subnet.isInRange(remotePeerHostAddress)) { + return true; + } + } + LOG.trace("Peer {} is not allowed in any of the configured subnets.", remotePeerHostAddress); + return false; + } +} diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/MessageHandler.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/MessageHandler.java index 2b4efba9df6..294bb43cac7 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/MessageHandler.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/MessageHandler.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/MessageType.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/MessageType.java index 7335b9810f5..b0bb61dc207 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/MessageType.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/MessageType.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainFramer.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainFramer.java index dfab2043024..ad051653021 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainFramer.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainFramer.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainHandshaker.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainHandshaker.java index cc109c87d6b..5800994bccb 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainHandshaker.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainHandshaker.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainMessage.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainMessage.java index f212141037d..ffcb45e880e 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainMessage.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainMessage.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/RlpxAgent.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/RlpxAgent.java index 6cf250d715b..7363b765d4d 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/RlpxAgent.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/RlpxAgent.java @@ -20,6 +20,7 @@ import org.hyperledger.besu.cryptoservices.NodeKey; import org.hyperledger.besu.ethereum.p2p.config.RlpxConfiguration; import org.hyperledger.besu.ethereum.p2p.discovery.DiscoveryPeer; +import org.hyperledger.besu.ethereum.p2p.discovery.internal.PeerTable; import org.hyperledger.besu.ethereum.p2p.peers.LocalNode; import org.hyperledger.besu.ethereum.p2p.peers.Peer; import org.hyperledger.besu.ethereum.p2p.peers.PeerPrivileges; @@ -49,12 +50,12 @@ import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.annotation.Nonnull; import com.google.common.annotations.VisibleForTesting; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import org.apache.tuweni.bytes.Bytes; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -70,7 +71,7 @@ public class RlpxAgent { private final PeerPrivileges peerPrivileges; private final AtomicBoolean started = new AtomicBoolean(false); private final AtomicBoolean stopped = new AtomicBoolean(false); - private final int lowerBound; + private final int maxPeers; private final Supplier> allConnectionsSupplier; private final Supplier> allActiveConnectionsSupplier; private final Cache> peersConnectingCache = @@ -86,7 +87,7 @@ private RlpxAgent( final ConnectionInitializer connectionInitializer, final PeerRlpxPermissions peerPermissions, final PeerPrivileges peerPrivileges, - final int peersLowerBound, + final int maxPeers, final Supplier> allConnectionsSupplier, final Supplier> allActiveConnectionsSupplier) { this.localNode = localNode; @@ -94,7 +95,7 @@ private RlpxAgent( this.connectionInitializer = connectionInitializer; this.peerPermissions = peerPermissions; this.peerPrivileges = peerPrivileges; - this.lowerBound = peersLowerBound; + this.maxPeers = maxPeers; this.allConnectionsSupplier = allConnectionsSupplier; this.allActiveConnectionsSupplier = allActiveConnectionsSupplier; } @@ -162,13 +163,6 @@ public int getConnectionCount() { } } - public void connect(final Stream peerStream) { - if (!localNode.isReady()) { - return; - } - peerStream.forEach(this::connect); - } - public void disconnect(final Bytes peerId, final DisconnectReason reason) { try { allActiveConnectionsSupplier @@ -206,6 +200,7 @@ public CompletableFuture connect(final Peer peer) { + this.getClass().getSimpleName() + " has finished starting")); } + // Check peer is valid final EnodeURL enode = peer.getEnodeURL(); if (!enode.isListening()) { @@ -240,7 +235,7 @@ public CompletableFuture connect(final Peer peer) { return peerConnectionCompletableFuture; } - @NotNull + @Nonnull private CompletableFuture createPeerConnectionCompletableFuture(final Peer peer) { final CompletableFuture peerConnectionCompletableFuture = initiateOutboundConnection(peer); @@ -310,8 +305,8 @@ private CompletableFuture initiateOutboundConnection(final Peer }); } - public boolean canExceedConnectionLimits(final Peer peer) { - return peerPrivileges.canExceedConnectionLimits(peer); + public boolean canExceedConnectionLimits(final Bytes peerId) { + return peerPrivileges.canExceedConnectionLimits(peerId); } private void handleIncomingConnection(final PeerConnection peerConnection) { @@ -363,8 +358,8 @@ public ConcurrentMap> getMapOfCompletab return peersConnectingCache.asMap(); } - public int getPeerLowerBound() { - return lowerBound; + public int getMaxPeers() { + return maxPeers; } public static class Builder { @@ -379,7 +374,8 @@ public static class Builder { private Optional p2pTLSConfiguration; private Supplier> allConnectionsSupplier; private Supplier> allActiveConnectionsSupplier; - private int peersLowerBound; + private int maxPeers; + private PeerTable peerTable; private Builder() {} @@ -399,12 +395,13 @@ public RlpxAgent build() { localNode, connectionEvents, metricsSystem, - p2pTLSConfiguration.get()); + p2pTLSConfiguration.get(), + peerTable); } else { LOG.debug("Using default NettyConnectionInitializer"); connectionInitializer = new NettyConnectionInitializer( - nodeKey, config, localNode, connectionEvents, metricsSystem); + nodeKey, config, localNode, connectionEvents, metricsSystem, peerTable); } } @@ -416,7 +413,7 @@ public RlpxAgent build() { connectionInitializer, rlpxPermissions, peerPrivileges, - peersLowerBound, + maxPeers, allConnectionsSupplier, allActiveConnectionsSupplier); } @@ -495,8 +492,13 @@ public Builder allActiveConnectionsSupplier( return this; } - public Builder peersLowerBound(final int peersLowerBound) { - this.peersLowerBound = peersLowerBound; + public Builder maxPeers(final int maxPeers) { + this.maxPeers = maxPeers; + return this; + } + + public Builder peerTable(final PeerTable peerTable) { + this.peerTable = peerTable; return this; } } diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/RlpxFrameConstants.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/RlpxFrameConstants.java index 82b706c535e..1c119e53f5d 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/RlpxFrameConstants.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/RlpxFrameConstants.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/AbstractPeerConnection.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/AbstractPeerConnection.java index 066f9122cbc..59cf3490566 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/AbstractPeerConnection.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/AbstractPeerConnection.java @@ -51,6 +51,7 @@ public abstract class AbstractPeerConnection implements PeerConnection { private final Set agreedCapabilities; private final Map protocolToCapability = new HashMap<>(); private final AtomicBoolean disconnected = new AtomicBoolean(false); + private final AtomicBoolean terminatedImmediately = new AtomicBoolean(false); protected final PeerConnectionEventDispatcher connectionEventDispatcher; private final LabelledMetric outboundMessagesCounter; private final long initiatedAt; @@ -85,9 +86,9 @@ protected AbstractPeerConnection( this.initiatedAt = System.currentTimeMillis(); LOG.atDebug() - .setMessage("New PeerConnection ({}) established with peer {}...") + .setMessage("New PeerConnection ({}) established with peer {}") .addArgument(this) - .addArgument(peer.getId().slice(0, 16)) + .addArgument(peer.getLoggableId()) .log(); } @@ -162,17 +163,19 @@ public Set getAgreedCapabilities() { @Override public void terminateConnection(final DisconnectReason reason, final boolean peerInitiated) { - if (disconnected.compareAndSet(false, true)) { - connectionEventDispatcher.dispatchDisconnect(this, reason, peerInitiated); + if (terminatedImmediately.compareAndSet(false, true)) { + if (disconnected.compareAndSet(false, true)) { + connectionEventDispatcher.dispatchDisconnect(this, reason, peerInitiated); + } + // Always ensure the context gets closed immediately even if we previously sent a disconnect + // message and are waiting to close. + closeConnectionImmediately(); + LOG.atTrace() + .setMessage("Terminating connection {}, reason {}") + .addArgument(this) + .addArgument(reason) + .log(); } - // Always ensure the context gets closed immediately even if we previously sent a disconnect - // message and are waiting to close. - closeConnectionImmediately(); - LOG.atTrace() - .setMessage("Terminating connection {}, reason {}") - .addArgument(this) - .addArgument(reason) - .log(); } protected abstract void closeConnectionImmediately(); @@ -182,15 +185,20 @@ public void terminateConnection(final DisconnectReason reason, final boolean pee @Override public void disconnect(final DisconnectReason reason) { if (disconnected.compareAndSet(false, true)) { - connectionEventDispatcher.dispatchDisconnect(this, reason, false); - doSend(null, DisconnectMessage.create(reason)); - LOG.atDebug() - .setMessage("Disconnecting connection {}, peer {}... reason {}") - .addArgument(this.hashCode()) - .addArgument(peer.getId().slice(0, 16)) - .addArgument(reason) - .log(); - closeConnection(); + try { + // send the disconnect message first, in case the dispatchDisconnect throws an exception + doSend(null, DisconnectMessage.create(reason)); + LOG.atDebug() + .setMessage("Disconnecting connection {}, peer {} reason {}") + .addArgument(this.hashCode()) + .addArgument(peer.getLoggableId()) + .addArgument(reason) + .log(); + connectionEventDispatcher.dispatchDisconnect(this, reason, false); + } finally { + // always close the connection + closeConnection(); + } } } diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/AbstractHandshakeHandler.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/AbstractHandshakeHandler.java index 003a6ab1d9d..80be0a673db 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/AbstractHandshakeHandler.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/AbstractHandshakeHandler.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.ethereum.p2p.rlpx.connections.netty; +import org.hyperledger.besu.ethereum.p2p.discovery.internal.PeerTable; import org.hyperledger.besu.ethereum.p2p.peers.LocalNode; import org.hyperledger.besu.ethereum.p2p.peers.Peer; import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection; @@ -60,6 +61,7 @@ abstract class AbstractHandshakeHandler extends SimpleChannelInboundHandler subProtocols, @@ -70,7 +72,8 @@ abstract class AbstractHandshakeHandler extends SimpleChannelInboundHandler { + if (ff.isSuccess()) { + LOG.trace("Successfully wrote hello message"); + } + }); + msg.retain(); + ctx.fireChannelRead(msg); } - - LOG.trace("Sending framed hello"); - - // Exchange keys done - final Framer framer = this.framerProvider.buildFramer(handshaker.secrets()); - - final ByteToMessageDecoder deFramer = - new DeFramer( - framer, - subProtocols, - localNode, - expectedPeer, - connectionEventDispatcher, - connectionFuture, - metricsSystem, - inboundInitiated); - - ctx.channel() - .pipeline() - .replace(this, "DeFramer", deFramer) - .addBefore("DeFramer", "validate", new ValidateFirstOutboundMessage(framer)); - - ctx.writeAndFlush(new OutboundMessage(null, HelloMessage.create(localNode.getPeerInfo()))) - .addListener( - ff -> { - if (ff.isSuccess()) { - LOG.trace("Successfully wrote hello message"); - } - }); - msg.retain(); - ctx.fireChannelRead(msg); } private void disconnect( diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/DeFramer.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/DeFramer.java index b39a6b82193..9fffed3f9da 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/DeFramer.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/DeFramer.java @@ -14,6 +14,8 @@ */ package org.hyperledger.besu.ethereum.p2p.rlpx.connections.netty; +import org.hyperledger.besu.ethereum.p2p.discovery.DiscoveryPeer; +import org.hyperledger.besu.ethereum.p2p.discovery.internal.PeerTable; import org.hyperledger.besu.ethereum.p2p.network.exceptions.BreachOfProtocolException; import org.hyperledger.besu.ethereum.p2p.network.exceptions.IncompatiblePeerException; import org.hyperledger.besu.ethereum.p2p.network.exceptions.PeerChannelClosedException; @@ -70,6 +72,7 @@ final class DeFramer extends ByteToMessageDecoder { private final Optional expectedPeer; private final List subProtocols; private final boolean inboundInitiated; + private final PeerTable peerTable; private boolean hellosExchanged; private final LabelledMetric outboundMessagesCounter; @@ -81,7 +84,8 @@ final class DeFramer extends ByteToMessageDecoder { final PeerConnectionEventDispatcher connectionEventDispatcher, final CompletableFuture connectFuture, final MetricsSystem metricsSystem, - final boolean inboundInitiated) { + final boolean inboundInitiated, + final PeerTable peerTable) { this.framer = framer; this.subProtocols = subProtocols; this.localNode = localNode; @@ -89,6 +93,7 @@ final class DeFramer extends ByteToMessageDecoder { this.connectFuture = connectFuture; this.connectionEventDispatcher = connectionEventDispatcher; this.inboundInitiated = inboundInitiated; + this.peerTable = peerTable; this.outboundMessagesCounter = metricsSystem.createLabelledCounter( BesuMetricCategory.NETWORK, @@ -105,8 +110,11 @@ protected void decode(final ChannelHandlerContext ctx, final ByteBuf in, final L while ((message = framer.deframe(in)) != null) { if (hellosExchanged) { + out.add(message); + } else if (message.getCode() == WireMessageCodes.HELLO) { + hellosExchanged = true; // Decode first hello and use the payload to modify pipeline final PeerInfo peerInfo; @@ -129,13 +137,27 @@ protected void decode(final ChannelHandlerContext ctx, final ByteBuf in, final L subProtocols, localNode.getPeerInfo().getCapabilities(), peerInfo.getCapabilities()); - final Optional peer = expectedPeer.or(() -> createPeer(peerInfo, ctx)); - if (peer.isEmpty()) { - LOG.debug("Failed to create connection for peer {}", peerInfo); - connectFuture.completeExceptionally(new PeerChannelClosedException(peerInfo)); - ctx.close(); - return; + + Optional peer; + if (expectedPeer.isPresent()) { + peer = expectedPeer; + } else { + // This is an inbound "Hello" message. Create peer from information from the Hello message + peer = createPeer(peerInfo, ctx); + if (peer.isEmpty()) { + LOG.debug("Failed to create connection for peer {}", peerInfo); + connectFuture.completeExceptionally(new PeerChannelClosedException(peerInfo)); + ctx.close(); + return; + } + // If we can find the DiscoveryPeer for the peer in the PeerTable we use it, because + // it could contains additional information, like the fork id. + final Optional discoveryPeer = peerTable.get(peer.get()); + if (discoveryPeer.isPresent()) { + peer = Optional.of(discoveryPeer.get()); + } } + final PeerConnection connection = new NettyPeerConnection( ctx, @@ -162,7 +184,8 @@ protected void decode(final ChannelHandlerContext ctx, final ByteBuf in, final L LOG.debug("Disconnecting because no capabilities are shared: {}", peerInfo); connectFuture.completeExceptionally( new IncompatiblePeerException("No shared capabilities")); - connection.disconnect(DisconnectMessage.DisconnectReason.USELESS_PEER); + connection.disconnect( + DisconnectMessage.DisconnectReason.USELESS_PEER_NO_SHARED_CAPABILITIES); } // Setup next stage @@ -176,7 +199,9 @@ protected void decode(final ChannelHandlerContext ctx, final ByteBuf in, final L capabilityMultiplexer, connection, connectionEventDispatcher, waitingForPong), new MessageFramer(capabilityMultiplexer, framer)); connectFuture.complete(connection); + } else if (message.getCode() == WireMessageCodes.DISCONNECT) { + final DisconnectMessage disconnectMessage = DisconnectMessage.readFrom(message); LOG.debug( "Peer {} disconnected before sending HELLO. Reason: {}", @@ -185,8 +210,10 @@ protected void decode(final ChannelHandlerContext ctx, final ByteBuf in, final L ctx.close(); connectFuture.completeExceptionally( new PeerDisconnectedException(disconnectMessage.getReason())); + } else { // Unexpected message - disconnect + LOG.debug( "Message received before HELLO's exchanged (BREACH_OF_PROTOCOL), disconnecting. Peer: {}, Code: {}, Data: {}", expectedPeer.map(Peer::getEnodeURLString).orElse("unknown"), @@ -196,7 +223,8 @@ protected void decode(final ChannelHandlerContext ctx, final ByteBuf in, final L new OutboundMessage( null, DisconnectMessage.create( - DisconnectMessage.DisconnectReason.BREACH_OF_PROTOCOL))) + DisconnectMessage.DisconnectReason + .BREACH_OF_PROTOCOL_MESSAGE_RECEIVED_BEFORE_HELLO_EXCHANGE))) .addListener((f) -> ctx.close()); connectFuture.completeExceptionally( new BreachOfProtocolException("Message received before HELLO's exchanged")); @@ -233,7 +261,11 @@ public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable thr || cause instanceof IllegalArgumentException) { LOG.debug("Invalid incoming message (BREACH_OF_PROTOCOL)", throwable); if (connectFuture.isDone() && !connectFuture.isCompletedExceptionally()) { - connectFuture.get().disconnect(DisconnectMessage.DisconnectReason.BREACH_OF_PROTOCOL); + connectFuture + .get() + .disconnect( + DisconnectMessage.DisconnectReason + .BREACH_OF_PROTOCOL_INVALID_MESSAGE_RECEIVED_CAUGHT_EXCEPTION); return; } } else if (cause instanceof IOException) { diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/HandshakeHandlerInbound.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/HandshakeHandlerInbound.java index 184cf5cf8c1..962de68f980 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/HandshakeHandlerInbound.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/HandshakeHandlerInbound.java @@ -15,6 +15,7 @@ package org.hyperledger.besu.ethereum.p2p.rlpx.connections.netty; import org.hyperledger.besu.cryptoservices.NodeKey; +import org.hyperledger.besu.ethereum.p2p.discovery.internal.PeerTable; import org.hyperledger.besu.ethereum.p2p.peers.LocalNode; import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection; import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnectionEventDispatcher; @@ -40,7 +41,8 @@ public HandshakeHandlerInbound( final PeerConnectionEventDispatcher connectionEventDispatcher, final MetricsSystem metricsSystem, final HandshakerProvider handshakerProvider, - final FramerProvider framerProvider) { + final FramerProvider framerProvider, + final PeerTable peerTable) { super( subProtocols, localNode, @@ -50,7 +52,8 @@ public HandshakeHandlerInbound( metricsSystem, handshakerProvider, framerProvider, - true); + true, + peerTable); handshaker.prepareResponder(nodeKey); } diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/HandshakeHandlerOutbound.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/HandshakeHandlerOutbound.java index 205b6f655cc..46e600d74b6 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/HandshakeHandlerOutbound.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/HandshakeHandlerOutbound.java @@ -16,6 +16,7 @@ import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; import org.hyperledger.besu.cryptoservices.NodeKey; +import org.hyperledger.besu.ethereum.p2p.discovery.internal.PeerTable; import org.hyperledger.besu.ethereum.p2p.peers.LocalNode; import org.hyperledger.besu.ethereum.p2p.peers.Peer; import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection; @@ -50,7 +51,8 @@ public HandshakeHandlerOutbound( final PeerConnectionEventDispatcher connectionEventDispatcher, final MetricsSystem metricsSystem, final HandshakerProvider handshakerProvider, - final FramerProvider framerProvider) { + final FramerProvider framerProvider, + final PeerTable peerTable) { super( subProtocols, localNode, @@ -60,7 +62,8 @@ public HandshakeHandlerOutbound( metricsSystem, handshakerProvider, framerProvider, - false); + false, + peerTable); handshaker.prepareInitiator( nodeKey, SignatureAlgorithmFactory.getInstance().createPublicKey(peer.getId())); this.first = handshaker.firstMessage(); diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/NettyConnectionInitializer.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/NettyConnectionInitializer.java index c20e511df9d..f386c59a384 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/NettyConnectionInitializer.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/NettyConnectionInitializer.java @@ -17,6 +17,7 @@ import org.hyperledger.besu.cryptoservices.NodeKey; import org.hyperledger.besu.ethereum.p2p.config.RlpxConfiguration; import org.hyperledger.besu.ethereum.p2p.discovery.DiscoveryPeer; +import org.hyperledger.besu.ethereum.p2p.discovery.internal.PeerTable; import org.hyperledger.besu.ethereum.p2p.peers.LocalNode; import org.hyperledger.besu.ethereum.p2p.peers.Peer; import org.hyperledger.besu.ethereum.p2p.rlpx.ConnectCallback; @@ -68,6 +69,7 @@ public class NettyConnectionInitializer private final PeerConnectionEventDispatcher eventDispatcher; private final MetricsSystem metricsSystem; private final Subscribers connectSubscribers = Subscribers.create(); + private final PeerTable peerTable; private ChannelFuture server; private final EventLoopGroup boss = new NioEventLoopGroup(1); @@ -80,12 +82,14 @@ public NettyConnectionInitializer( final RlpxConfiguration config, final LocalNode localNode, final PeerConnectionEventDispatcher eventDispatcher, - final MetricsSystem metricsSystem) { + final MetricsSystem metricsSystem, + final PeerTable peerTable) { this.nodeKey = nodeKey; this.config = config; this.localNode = localNode; this.eventDispatcher = eventDispatcher; this.metricsSystem = metricsSystem; + this.peerTable = peerTable; metricsSystem.createIntegerGauge( BesuMetricCategory.NETWORK, @@ -244,7 +248,8 @@ private HandshakeHandlerInbound inboundHandler( eventDispatcher, metricsSystem, this, - this); + this, + peerTable); } @Nonnull @@ -259,7 +264,8 @@ private HandshakeHandlerOutbound outboundHandler( eventDispatcher, metricsSystem, this, - this); + this, + peerTable); } @Nonnull diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/NettyTLSConnectionInitializer.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/NettyTLSConnectionInitializer.java index 4e6010771fc..db41c1574c7 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/NettyTLSConnectionInitializer.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/NettyTLSConnectionInitializer.java @@ -19,6 +19,7 @@ import org.hyperledger.besu.cryptoservices.NodeKey; import org.hyperledger.besu.ethereum.p2p.config.RlpxConfiguration; +import org.hyperledger.besu.ethereum.p2p.discovery.internal.PeerTable; import org.hyperledger.besu.ethereum.p2p.peers.LocalNode; import org.hyperledger.besu.ethereum.p2p.peers.Peer; import org.hyperledger.besu.ethereum.p2p.plain.PlainFramer; @@ -55,7 +56,8 @@ public NettyTLSConnectionInitializer( final LocalNode localNode, final PeerConnectionEventDispatcher eventDispatcher, final MetricsSystem metricsSystem, - final TLSConfiguration p2pTLSConfiguration) { + final TLSConfiguration p2pTLSConfiguration, + final PeerTable peerTable) { this( nodeKey, config, @@ -63,7 +65,8 @@ public NettyTLSConnectionInitializer( eventDispatcher, metricsSystem, defaultTlsContextFactorySupplier(p2pTLSConfiguration), - p2pTLSConfiguration.getClientHelloSniHeaderEnabled()); + p2pTLSConfiguration.getClientHelloSniHeaderEnabled(), + peerTable); } @VisibleForTesting @@ -74,8 +77,9 @@ public NettyTLSConnectionInitializer( final PeerConnectionEventDispatcher eventDispatcher, final MetricsSystem metricsSystem, final Supplier tlsContextFactorySupplier, - final Boolean clientHelloSniHeaderEnabled) { - super(nodeKey, config, localNode, eventDispatcher, metricsSystem); + final Boolean clientHelloSniHeaderEnabled, + final PeerTable peerTable) { + super(nodeKey, config, localNode, eventDispatcher, metricsSystem, peerTable); if (tlsContextFactorySupplier != null) { this.tlsContextFactorySupplier = Optional.of(Suppliers.memoize(tlsContextFactorySupplier::get)); diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/TLSConfiguration.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/TLSConfiguration.java index 52771bc4212..2bcb6ddbc65 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/TLSConfiguration.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/TLSConfiguration.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.p2p.rlpx.connections.netty; import static java.util.Objects.requireNonNull; diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/framing/FramerProvider.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/framing/FramerProvider.java index 423bdccaf69..270ad9fd36b 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/framing/FramerProvider.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/framing/FramerProvider.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/handshake/HandshakerProvider.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/handshake/HandshakerProvider.java index e6eca777582..48d883bb6db 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/handshake/HandshakerProvider.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/handshake/HandshakerProvider.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/Capability.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/Capability.java index 5a3a57df46d..f530337ee46 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/Capability.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/Capability.java @@ -84,7 +84,7 @@ public int hashCode() { } @Override - /** Returned string is sanitized since it contains user input */ + // Returned string is sanitized since it contains user input public String toString() { return Encode.forJava(name) + "/" + version; } diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/PeerInfo.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/PeerInfo.java index bd98270e020..492280e85d0 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/PeerInfo.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/PeerInfo.java @@ -118,7 +118,7 @@ public void writeTo(final RLPOutput out) { } @Override - /** Returned string is sanitized since it contains user input */ + // Returned string is sanitized since it contains user input public String toString() { final StringBuilder sb = new StringBuilder("PeerInfo{"); sb.append("version=").append(version); diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/ShouldConnectCallback.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/ShouldConnectCallback.java index dbc208efb78..f6473528e6a 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/ShouldConnectCallback.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/ShouldConnectCallback.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/messages/DisconnectMessage.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/messages/DisconnectMessage.java index d81520f1ddc..d951ecc5d9e 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/messages/DisconnectMessage.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/messages/DisconnectMessage.java @@ -108,8 +108,32 @@ public enum DisconnectReason { UNKNOWN(null), REQUESTED((byte) 0x00), TCP_SUBSYSTEM_ERROR((byte) 0x01), + BREACH_OF_PROTOCOL((byte) 0x02), + BREACH_OF_PROTOCOL_RECEIVED_OTHER_MESSAGE_BEFORE_STATUS( + (byte) 0x02, "Message other than status received first"), + BREACH_OF_PROTOCOL_UNSOLICITED_MESSAGE_RECEIVED((byte) 0x02, "Unsolicited message received"), + BREACH_OF_PROTOCOL_MALFORMED_MESSAGE_RECEIVED((byte) 0x02, "Malformed message received"), + BREACH_OF_PROTOCOL_NON_SEQUENTIAL_HEADERS((byte) 0x02, "Non-sequential headers received"), + BREACH_OF_PROTOCOL_INVALID_BLOCK((byte) 0x02, "Invalid block detected"), + BREACH_OF_PROTOCOL_INVALID_HEADERS((byte) 0x02, "Invalid headers detected"), + BREACH_OF_PROTOCOL_INVALID_MESSAGE_CODE_FOR_PROTOCOL( + (byte) 0x02, "Invalid message code for specified protocol"), + BREACH_OF_PROTOCOL_MESSAGE_RECEIVED_BEFORE_HELLO_EXCHANGE( + (byte) 0x02, "A message was received before hello's exchanged"), + BREACH_OF_PROTOCOL_INVALID_MESSAGE_RECEIVED_CAUGHT_EXCEPTION( + (byte) 0x02, "An exception was caught decoding message"), USELESS_PEER((byte) 0x03), + USELESS_PEER_USELESS_RESPONSES((byte) 0x03, "Useless responses: exceeded threshold"), + USELESS_PEER_TRAILING_PEER((byte) 0x03, "Trailing peer requirement"), + USELESS_PEER_NO_SHARED_CAPABILITIES((byte) 0x03, "No shared capabilities"), + USELESS_PEER_WORLD_STATE_NOT_AVAILABLE((byte) 0x03, "World state not available"), + USELESS_PEER_MISMATCHED_PIVOT_BLOCK((byte) 0x03, "Mismatched pivot block"), + USELESS_PEER_FAILED_TO_RETRIEVE_CHAIN_HEAD((byte) 0x03, "Failed to retrieve chain head header"), + USELESS_PEER_CANNOT_CONFIRM_PIVOT_BLOCK((byte) 0x03, "Peer failed to confirm pivot block"), + USELESS_PEER_BY_REPUTATION((byte) 0x03, "Lowest reputation score"), + USELESS_PEER_BY_CHAIN_COMPARATOR((byte) 0x03, "Lowest by chain height comparator"), + USELESS_PEER_EXCEEDS_TRAILING_PEERS((byte) 0x03, "Adding peer would exceed max trailing peers"), TOO_MANY_PEERS((byte) 0x04), ALREADY_CONNECTED((byte) 0x05), INCOMPATIBLE_P2P_PROTOCOL_VERSION((byte) 0x06), @@ -118,10 +142,17 @@ public enum DisconnectReason { UNEXPECTED_ID((byte) 0x09), LOCAL_IDENTITY((byte) 0x0a), TIMEOUT((byte) 0x0b), - SUBPROTOCOL_TRIGGERED((byte) 0x10); + SUBPROTOCOL_TRIGGERED((byte) 0x10), + SUBPROTOCOL_TRIGGERED_MISMATCHED_NETWORK((byte) 0x10, "Mismatched network id"), + SUBPROTOCOL_TRIGGERED_MISMATCHED_FORKID((byte) 0x10, "Mismatched fork id"), + SUBPROTOCOL_TRIGGERED_MISMATCHED_GENESIS_HASH((byte) 0x10, "Mismatched genesis hash"), + SUBPROTOCOL_TRIGGERED_UNPARSABLE_STATUS((byte) 0x10, "Unparsable status message"), + SUBPROTOCOL_TRIGGERED_POW_DIFFICULTY((byte) 0x10, "Peer has difficulty greater than POS TTD"), + SUBPROTOCOL_TRIGGERED_POW_BLOCKS((byte) 0x10, "Peer sent blocks after POS transition"); private static final DisconnectReason[] BY_ID; private final Optional code; + private final Optional message; static { final int maxValue = @@ -132,7 +163,7 @@ public enum DisconnectReason { .getAsInt(); BY_ID = new DisconnectReason[maxValue + 1]; Stream.of(DisconnectReason.values()) - .filter(r -> r.code.isPresent()) + .filter(r -> r.code.isPresent() && r.message.isEmpty()) .forEach(r -> BY_ID[r.code.get()] = r); } @@ -144,17 +175,35 @@ public static DisconnectReason forCode(final Byte code) { return BY_ID[code]; } + public static DisconnectReason forCode(final Bytes codeBytes) { + if (codeBytes == null || codeBytes.isEmpty()) { + return UNKNOWN; + } else { + return forCode(codeBytes.get(0)); + } + } + DisconnectReason(final Byte code) { this.code = Optional.ofNullable(code); + this.message = Optional.empty(); + } + + DisconnectReason(final Byte code, final String message) { + this.code = Optional.ofNullable(code); + this.message = Optional.of(message); } public Bytes getValue() { return code.map(Bytes::of).orElse(Bytes.EMPTY); } + public String getMessage() { + return message.orElse(""); + } + @Override public String toString() { - return getValue().toString() + " " + name(); + return getValue().toString() + " " + name() + " " + getMessage(); } } } diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/PeerDiscoveryAgentTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/PeerDiscoveryAgentTest.java index 97d167a26fb..269eb92b084 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/PeerDiscoveryAgentTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/PeerDiscoveryAgentTest.java @@ -36,6 +36,7 @@ import org.hyperledger.besu.ethereum.p2p.discovery.internal.NeighborsPacketData; import org.hyperledger.besu.ethereum.p2p.discovery.internal.Packet; import org.hyperledger.besu.ethereum.p2p.discovery.internal.PacketType; +import org.hyperledger.besu.ethereum.p2p.discovery.internal.PingPacketData; import org.hyperledger.besu.ethereum.p2p.peers.DefaultPeer; import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl; import org.hyperledger.besu.ethereum.p2p.peers.Peer; @@ -53,6 +54,7 @@ import com.google.common.base.Suppliers; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt64; import org.ethereum.beacon.discovery.schema.NodeRecord; import org.junit.jupiter.api.Test; @@ -244,6 +246,30 @@ public void neighborsPacketLimited() { } } + @Test + public void endpointHonoursCustomAdvertisedAddressInPingPacket() { + + // Start a peer with the default advertised host + final MockPeerDiscoveryAgent agent1 = helper.startDiscoveryAgent(); + + // Start another peer with its advertised host set to a custom value + final MockPeerDiscoveryAgent agent2 = helper.startDiscoveryAgent("192.168.0.1"); + + // Send a PING so we can exchange messages + Packet packet = helper.createPingPacket(agent2, agent1); + helper.sendMessageBetweenAgents(agent2, agent1, packet); + + // Agent 1's peers should have endpoints that match the custom advertised value... + agent1 + .streamDiscoveredPeers() + .forEach(peer -> assertThat(peer.getEndpoint().getHost()).isEqualTo("192.168.0.1")); + + // ...but agent 2's peers should have endpoints that match the default + agent2 + .streamDiscoveredPeers() + .forEach(peer -> assertThat(peer.getEndpoint().getHost()).isEqualTo("127.0.0.1")); + } + @Test public void shouldEvictPeerWhenPermissionsRevoked() { final PeerPermissionsDenylist denylist = PeerPermissionsDenylist.create(); @@ -810,6 +836,67 @@ public void shouldNotBeActiveWhenConfigIsFalse() { assertThat(agent.isActive()).isFalse(); } + @Test + public void assertHostCorrectlyRevertsOnIgnoredPacketFrom() { + final String sourceHost = "UDP_SOURCE_ORIGIN_HOST"; + final String emptyIPv4Host = "0.0.0.0"; + final String emptyIPv6Host = "::"; + final String localHost = "127.0.0.1"; + final String broadcastDefaultHost = "255.255.255.255"; + final String routableHost = "50.50.50.50"; + + Endpoint source = new Endpoint(sourceHost, 30303, Optional.empty()); + Endpoint emptyIPv4 = new Endpoint(emptyIPv4Host, 30303, Optional.empty()); + Endpoint emptyIPv6 = new Endpoint(emptyIPv6Host, 30303, Optional.empty()); + Endpoint endpointLocal = new Endpoint(localHost, 30303, Optional.empty()); + Endpoint endpointBroadcast = new Endpoint(broadcastDefaultHost, 30303, Optional.empty()); + Endpoint endpointRoutable = new Endpoint(routableHost, 30303, Optional.empty()); + + Packet mockEmptyIPv4 = + when(mock(Packet.class).getPacketData(any())) + .thenReturn( + Optional.of( + PingPacketData.create(Optional.of(emptyIPv4), endpointLocal, UInt64.ONE))) + .getMock(); + Packet mockEmptyIPv6 = + when(mock(Packet.class).getPacketData(any())) + .thenReturn( + Optional.of( + PingPacketData.create(Optional.of(emptyIPv6), endpointLocal, UInt64.ONE))) + .getMock(); + Packet mockLocal = + when(mock(Packet.class).getPacketData(any())) + .thenReturn( + Optional.of( + PingPacketData.create(Optional.of(endpointLocal), endpointLocal, UInt64.ONE))) + .getMock(); + Packet mockBroadcast = + when(mock(Packet.class).getPacketData(any())) + .thenReturn( + Optional.of( + PingPacketData.create( + Optional.of(endpointBroadcast), endpointLocal, UInt64.ONE))) + .getMock(); + Packet mockWellFormed = + when(mock(Packet.class).getPacketData(any())) + .thenReturn( + Optional.of( + PingPacketData.create( + Optional.of(endpointRoutable), endpointLocal, UInt64.ONE))) + .getMock(); + + // assert a pingpacketdata with empty ipv4 address reverts to the udp source host + assertThat(PeerDiscoveryAgent.deriveHost(source, mockEmptyIPv4)).isEqualTo(sourceHost); + // assert a pingpacketdata with empty ipv6 address reverts to the udp source host + assertThat(PeerDiscoveryAgent.deriveHost(source, mockEmptyIPv6)).isEqualTo(sourceHost); + // assert a pingpacketdata from address of 127.0.0.1 reverts to the udp source host + assertThat(PeerDiscoveryAgent.deriveHost(source, mockLocal)).isEqualTo(sourceHost); + // assert that 255.255.255.255 reverts to the udp source host + assertThat(PeerDiscoveryAgent.deriveHost(source, mockBroadcast)).isEqualTo(sourceHost); + // assert that a well-formed routable address in the ping packet data is used + assertThat(PeerDiscoveryAgent.deriveHost(source, mockWellFormed)).isEqualTo(routableHost); + } + protected void bondViaIncomingPing( final MockPeerDiscoveryAgent agent, final MockPeerDiscoveryAgent otherNode) { final Packet pingPacket = helper.createPingPacket(otherNode, agent); diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/PeerDiscoveryTestHelper.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/PeerDiscoveryTestHelper.java index e51b6320a6e..c29855374ea 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/PeerDiscoveryTestHelper.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/PeerDiscoveryTestHelper.java @@ -128,7 +128,7 @@ public void sendMessageBetweenAgents( } /** - * Starts multiple discovery agents with the provided boostrap peers. + * Starts multiple discovery agents with the provided bootstrap peers. * * @param count the number of agents to start * @param bootstrapPeers the list of bootstrap peers @@ -165,6 +165,14 @@ public MockPeerDiscoveryAgent startDiscoveryAgent(final DiscoveryPeer... bootstr return startDiscoveryAgent(agentBuilder); } + public MockPeerDiscoveryAgent startDiscoveryAgent( + final String advertisedHost, final DiscoveryPeer... bootstrapPeers) { + final AgentBuilder agentBuilder = + agentBuilder().bootstrapPeers(bootstrapPeers).advertisedHost(advertisedHost); + + return startDiscoveryAgent(agentBuilder); + } + /** * Start a single discovery agent with the provided bootstrap peers. * @@ -287,6 +295,7 @@ public MockPeerDiscoveryAgent build() { config.setAdvertisedHost(advertisedHost); config.setBindPort(port); config.setActive(active); + config.setFilterOnEnrForkId(false); final ForkIdManager mockForkIdManager = mock(ForkIdManager.class); final ForkId forkId = new ForkId(Bytes.EMPTY, Bytes.EMPTY); diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/PeerDiscoveryTimestampsTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/PeerDiscoveryTimestampsTest.java index 90ee8b422cd..d75847050d4 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/PeerDiscoveryTimestampsTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/PeerDiscoveryTimestampsTest.java @@ -21,7 +21,6 @@ import org.hyperledger.besu.ethereum.p2p.discovery.internal.Packet; import java.util.Collections; -import java.util.concurrent.atomic.AtomicLong; import org.junit.jupiter.api.Test; @@ -41,8 +40,8 @@ public void lastSeenAndFirstDiscoveredTimestampsUpdatedOnMessage() { final Packet pong = helper.createPongPacket(agent, Hash.hash(agentPing.getHash())); helper.sendMessageBetweenAgents(testAgent, agent, pong); - final AtomicLong lastSeen = new AtomicLong(); - final AtomicLong firstDiscovered = new AtomicLong(); + long lastSeen; + long firstDiscovered; assertThat(agent.streamDiscoveredPeers()).hasSize(1); @@ -50,16 +49,16 @@ public void lastSeenAndFirstDiscoveredTimestampsUpdatedOnMessage() { assertThat(p.getLastSeen()).isGreaterThan(0); assertThat(p.getFirstDiscovered()).isGreaterThan(0); - lastSeen.set(p.getLastSeen()); - firstDiscovered.set(p.getFirstDiscovered()); + lastSeen = p.getLastSeen(); + firstDiscovered = p.getFirstDiscovered(); helper.sendMessageBetweenAgents(testAgent, agent, testAgentPing); assertThat(agent.streamDiscoveredPeers()).hasSize(1); p = agent.streamDiscoveredPeers().iterator().next(); - assertThat(p.getLastSeen()).isGreaterThan(lastSeen.get()); - assertThat(p.getFirstDiscovered()).isEqualTo(firstDiscovered.get()); + assertThat(p.getLastSeen()).isGreaterThan(lastSeen); + assertThat(p.getFirstDiscovered()).isEqualTo(firstDiscovered); } @Test @@ -74,9 +73,9 @@ public void lastContactedTimestampUpdatedOnOutboundMessage() { assertThat(agent.streamDiscoveredPeers()).hasSize(1); - final AtomicLong lastContacted = new AtomicLong(); - final AtomicLong lastSeen = new AtomicLong(); - final AtomicLong firstDiscovered = new AtomicLong(); + final long lastContacted; + final long lastSeen; + final long firstDiscovered; DiscoveryPeer peer = agent.streamDiscoveredPeers().iterator().next(); final long lc = peer.getLastContacted(); @@ -87,9 +86,9 @@ public void lastContactedTimestampUpdatedOnOutboundMessage() { assertThat(ls).isGreaterThan(0); assertThat(fd).isGreaterThan(0); - lastContacted.set(lc); - lastSeen.set(ls); - firstDiscovered.set(fd); + lastContacted = lc; + lastSeen = ls; + firstDiscovered = fd; // Send another packet and ensure that timestamps are updated accordingly. // Sleep beforehand to make sure timestamps will be different. @@ -102,8 +101,8 @@ public void lastContactedTimestampUpdatedOnOutboundMessage() { peer = agent.streamDiscoveredPeers().iterator().next(); - assertThat(peer.getLastContacted()).isGreaterThan(lastContacted.get()); - assertThat(peer.getLastSeen()).isGreaterThan(lastSeen.get()); - assertThat(peer.getFirstDiscovered()).isEqualTo(firstDiscovered.get()); + assertThat(peer.getLastContacted()).isGreaterThan(lastContacted); + assertThat(peer.getLastSeen()).isGreaterThan(lastSeen); + assertThat(peer.getFirstDiscovered()).isEqualTo(firstDiscovered); } } diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java new file mode 100644 index 00000000000..fd8ba1382d8 --- /dev/null +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSDaemonTest.java @@ -0,0 +1,145 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.p2p.discovery.dns; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.Security; +import java.util.concurrent.atomic.AtomicInteger; + +import io.vertx.core.DeploymentOptions; +import io.vertx.core.ThreadingModel; +import io.vertx.core.Vertx; +import io.vertx.junit5.Checkpoint; +import io.vertx.junit5.VertxExtension; +import io.vertx.junit5.VertxTestContext; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@ExtendWith(VertxExtension.class) +class DNSDaemonTest { + private static final int EXPECTED_SEQ = 932; + private static final String holeskyEnr = + "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUDPE@all.holesky.ethdisco.net"; + private final MockDnsServerVerticle mockDnsServerVerticle = new MockDnsServerVerticle(); + private DNSDaemon dnsDaemon; + + @BeforeAll + static void setup() { + Security.addProvider(new BouncyCastleProvider()); + } + + @BeforeEach + @DisplayName("Deploy Mock Dns Server Verticle") + void prepare(final Vertx vertx, final VertxTestContext vertxTestContext) { + vertx.deployVerticle(mockDnsServerVerticle, vertxTestContext.succeedingThenComplete()); + } + + @Test + @DisplayName("Test DNS Daemon with a mock DNS server") + void testDNSDaemon(final Vertx vertx, final VertxTestContext testContext) { + final Checkpoint checkpoint = testContext.checkpoint(); + dnsDaemon = + new DNSDaemon( + holeskyEnr, + (seq, records) -> { + if (seq != EXPECTED_SEQ) { + testContext.failNow( + String.format( + "Expecting sequence to be %d in first pass but got: %d", + EXPECTED_SEQ, seq)); + } + if (records.size() != 115) { + testContext.failNow( + "Expecting 115 records in first pass but got: " + records.size()); + } + checkpoint.flag(); + }, + 0, + 0, + 0, + "localhost:" + mockDnsServerVerticle.port()); + + final DeploymentOptions options = + new DeploymentOptions() + .setThreadingModel(ThreadingModel.VIRTUAL_THREAD) + .setWorkerPoolSize(1); + vertx.deployVerticle(dnsDaemon, options); + } + + @Test + @DisplayName("Test DNS Daemon with periodic lookup to a mock DNS server") + void testDNSDaemonPeriodic(final Vertx vertx, final VertxTestContext testContext) + throws InterruptedException { + // checkpoint should be flagged twice + final Checkpoint checkpoint = testContext.checkpoint(2); + final AtomicInteger pass = new AtomicInteger(0); + dnsDaemon = + new DNSDaemon( + holeskyEnr, + (seq, records) -> { + switch (pass.incrementAndGet()) { + case 1: + if (seq != EXPECTED_SEQ) { + testContext.failNow( + String.format( + "Expecting sequence to be %d in first pass but got: %d", + EXPECTED_SEQ, seq)); + } + if (records.size() != 115) { + testContext.failNow( + "Expecting 115 records in first pass but got: " + records.size()); + } + break; + case 2: + if (seq != EXPECTED_SEQ) { + testContext.failNow( + String.format( + "Expecting sequence to be %d in second pass but got: %d", + EXPECTED_SEQ, seq)); + } + if (!records.isEmpty()) { + testContext.failNow( + "Expecting 0 records in second pass but got: " + records.size()); + } + break; + default: + testContext.failNow("Third pass is not expected"); + } + checkpoint.flag(); + }, + 0, + 1, // initial delay + 3000, // second lookup after 3 seconds (the thread scheduling can be slower in CI) + "localhost:" + mockDnsServerVerticle.port()); + + final DeploymentOptions options = + new DeploymentOptions() + .setThreadingModel(ThreadingModel.VIRTUAL_THREAD) + .setWorkerPoolSize(1); + vertx.deployVerticle(dnsDaemon, options); + } + + @AfterEach + @DisplayName("Check that the vertx worker verticle is still there") + void lastChecks(final Vertx vertx) { + assertThat(vertx.deploymentIDs()).isNotEmpty().hasSize(2); + } +} diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntryTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntryTest.java new file mode 100644 index 00000000000..2197bf279ca --- /dev/null +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/DNSEntryTest.java @@ -0,0 +1,71 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.p2p.discovery.dns; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.security.Security; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class DNSEntryTest { + @BeforeAll + static void setup() { + Security.addProvider(new BouncyCastleProvider()); + } + + @Test + void enrTreeRootIsParsed() { + final String txtRecord = + "\"enrtree-root:v1 e=KVKZLGARGADDZSMCF65QQMEWLE l=FDXN3SN67NA5DKA4J2GOK7BVQI seq=919 sig=braPmdwMk-g65lQxums6hEy553s3bWMoecW0QQ0IdykIoM9i3We0bxFT0IDONPaFcRePcN-yaOpt8GBfeQ4qDAE\""; + final DNSEntry entry = DNSEntry.readDNSEntry(txtRecord); + assertThat(entry).isInstanceOf(DNSEntry.ENRTreeRoot.class); + final DNSEntry.ENRTreeRoot enrTreeRoot = (DNSEntry.ENRTreeRoot) entry; + assertThat(enrTreeRoot.enrRoot()).isEqualTo("KVKZLGARGADDZSMCF65QQMEWLE"); + assertThat(enrTreeRoot.linkRoot()).isEqualTo("FDXN3SN67NA5DKA4J2GOK7BVQI"); + assertThat(enrTreeRoot.seq()).isEqualTo(919); + } + + @Test + void enrTreeBranchIsParsed() { + final String txtRecord = + "\"enrtree-branch:HVKDJGU7SZMOAMNLBJYQBSKZTM,PVSVWO3NLKHTBAIWOY2NB67RFI," + + "6TCKCNWXNGBMNFTGSRKNRO4ERA,37NSKCRJVI5XRRHWLTHW4A6OX4,NV3IJMKDVQHHALY6MAVMPYN6ZU," + + "SZCFDMTYOERMIVOUXEWXSGDVEY,FZ26UT4LSG7D2NRX7SV6P3S6BI,7TWNYLCOQ7FEM4IG65WOTL4MVE," + + "6OJXGI7NJUESOLL2OZPS4B\" \"EC6Q,437FN4NSGMGFQLAXYWPX5JNACI,FCA7LN6NCO5IAWPG5FH7LX6XJA," + + "EYBOZ2NZSHDWDSNHV66XASXOHM,FUVRJMMMKJMCL4L4EBEOWCSOFA\""; + final DNSEntry entry = DNSEntry.readDNSEntry(txtRecord); + + assertThat(entry).isInstanceOf(DNSEntry.ENRTree.class); + assertThat(((DNSEntry.ENRTree) entry).entries()) + .containsExactly( + "HVKDJGU7SZMOAMNLBJYQBSKZTM", + "PVSVWO3NLKHTBAIWOY2NB67RFI", + "6TCKCNWXNGBMNFTGSRKNRO4ERA", + "37NSKCRJVI5XRRHWLTHW4A6OX4", + "NV3IJMKDVQHHALY6MAVMPYN6ZU", + "SZCFDMTYOERMIVOUXEWXSGDVEY", + "FZ26UT4LSG7D2NRX7SV6P3S6BI", + "7TWNYLCOQ7FEM4IG65WOTL4MVE", + "6OJXGI7NJUESOLL2OZPS4B", + "437FN4NSGMGFQLAXYWPX5JNACI", + "FCA7LN6NCO5IAWPG5FH7LX6XJA", + "EYBOZ2NZSHDWDSNHV66XASXOHM", + "FUVRJMMMKJMCL4L4EBEOWCSOFA") + .doesNotContain("EC6Q"); + } +} diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/KVReaderTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/KVReaderTest.java new file mode 100644 index 00000000000..2f1ecaf293c --- /dev/null +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/KVReaderTest.java @@ -0,0 +1,40 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.p2p.discovery.dns; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +public class KVReaderTest { + // copied from `dig all.holesky.ethdisco.net txt` + private static final String txtRecord = + "enrtree-root:v1 e=KVKZLGARGADDZSMCF65QQMEWLE l=FDXN3SN67NA5DKA4J2GOK7BVQI seq=919 sig=braPmdwMk-g65lQxums6hEy553s3bWMoecW0QQ0IdykIoM9i3We0bxFT0IDONPaFcRePcN-yaOpt8GBfeQ4qDAE"; + + @Test + void parseTXTRecord() { + final Map kv = KVReader.readKV(txtRecord); + assertThat(kv) + .containsEntry("enrtree-root", "v1") + .containsEntry("e", "KVKZLGARGADDZSMCF65QQMEWLE") + .containsEntry("l", "FDXN3SN67NA5DKA4J2GOK7BVQI") + .containsEntry("seq", "919") + .containsEntry( + "sig", + "braPmdwMk-g65lQxums6hEy553s3bWMoecW0QQ0IdykIoM9i3We0bxFT0IDONPaFcRePcN-yaOpt8GBfeQ4qDAE"); + } +} diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/MockDnsServerVerticle.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/MockDnsServerVerticle.java new file mode 100644 index 00000000000..f9bd7ea5cff --- /dev/null +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/dns/MockDnsServerVerticle.java @@ -0,0 +1,205 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.p2p.discovery.dns; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import com.google.common.base.Splitter; +import com.google.common.io.Resources; +import io.vertx.core.AbstractVerticle; +import io.vertx.core.Promise; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.datagram.DatagramPacket; +import io.vertx.core.datagram.DatagramSocket; +import io.vertx.core.datagram.DatagramSocketOptions; +import io.vertx.core.json.JsonObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Mock DNS server verticle. */ +public class MockDnsServerVerticle extends AbstractVerticle { + private static final Logger LOG = LoggerFactory.getLogger(MockDnsServerVerticle.class); + private final Map txtRecords = new HashMap<>(); + private int dnsPort; + + @Override + public void start(final Promise startPromise) throws Exception { + final DatagramSocket datagramSocket = vertx.createDatagramSocket(new DatagramSocketOptions()); + datagramSocket.handler(packet -> handleDatagramPacket(datagramSocket, packet)); + + final String dnsEntriesJsonPath = + Path.of(Resources.getResource("discovery/dns/dns-records.json").toURI()).toString(); + LOG.debug("Reading DNS entries from: {}", dnsEntriesJsonPath); + vertx + .fileSystem() + .readFile(dnsEntriesJsonPath) + .compose( + buffer -> { + final JsonObject dnsEntries = new JsonObject(buffer.toString()); + final Map jsonMap = dnsEntries.getMap(); + jsonMap.forEach((key, value) -> txtRecords.put(key, value.toString())); + + // start the server + return datagramSocket.listen(0, "127.0.0.1"); + }) + .onComplete( + res -> { + if (res.succeeded()) { + LOG.info("Mock Dns Server is now listening {}", res.result().localAddress()); + dnsPort = res.result().localAddress().port(); + startPromise.complete(); + } else { + startPromise.fail(res.cause()); + } + }); + } + + @Override + public void stop() { + LOG.info("Stopping Mock DNS Server"); + } + + private void handleDatagramPacket(final DatagramSocket socket, final DatagramPacket packet) { + LOG.debug("Packet Received"); + Buffer data = packet.data(); + final short queryId = getQueryId(data); + final String queryName = extractQueryName(data.getBytes()); + + final Buffer response; + if (txtRecords.containsKey(queryName)) { + LOG.debug("Query name found {}", queryName); + response = createTXTResponse(queryId, queryName, txtRecords.get(queryName)); + } else { + LOG.debug("Query name not found: {}", queryName); + response = createErrorResponse(queryId, queryName); + } + + socket.send(response, packet.sender().port(), packet.sender().host()); + } + + private String extractQueryName(final byte[] buffer) { + StringBuilder queryName = new StringBuilder(); + int index = 12; // Skip the DNS header + + while (index < buffer.length) { + int labelLength = buffer[index] & 0xFF; + + if (labelLength == 0) { + break; + } + + index++; + + for (int i = 0; i < labelLength; i++) { + char c = (char) (buffer[index + i] & 0xFF); + queryName.append(c); + } + + index += labelLength; + + if (index < buffer.length && buffer[index] != 0) { + queryName.append("."); + } + } + + return queryName.toString(); + } + + private Buffer createTXTResponse( + final short queryId, final String queryName, final String txtRecord) { + final Buffer buffer = Buffer.buffer(); + + // Write DNS header + buffer.appendShort(queryId); // Query Identifier + buffer.appendShort((short) 0x8180); // Flags (Standard query response, No error) + buffer.appendShort((short) 1); // Questions count + buffer.appendShort((short) 1); // Answers count + buffer.appendShort((short) 0); // Authority RRs count + buffer.appendShort((short) 0); // Additional RRs count + + // Write query name + final Iterable queryLabels = Splitter.on(".").split(queryName); + for (String label : queryLabels) { + buffer.appendByte((byte) label.length()); + buffer.appendString(label); + } + buffer.appendByte((byte) 0); // End of query name + + // Write query type and class + buffer.appendShort((short) 16); // Type (TXT) + buffer.appendShort((short) 1); // Class (IN) + + // Write answer + for (String label : queryLabels) { + buffer.appendByte((byte) label.length()); + buffer.appendString(label.toLowerCase(Locale.ROOT)); + } + buffer.appendByte((byte) 0); // End of answer name + + buffer.appendShort((short) 16); // TXT record type + buffer.appendShort((short) 1); // Class (IN) + buffer.appendInt(60); // TTL (60 seconds) + + int txtRecordsLength = txtRecord.getBytes(UTF_8).length; + buffer.appendShort((short) (txtRecordsLength + 1)); // Data length + buffer.appendByte((byte) txtRecordsLength); // TXT record length + buffer.appendString(txtRecord); + + return buffer; + } + + private Buffer createErrorResponse(final short queryId, final String queryName) { + Buffer buffer = Buffer.buffer(); + + // Write DNS header + buffer.appendShort(queryId); // Query Identifier + buffer.appendShort((short) 0x8183); // Flags (Standard query response, NXDOMAIN error) + buffer.appendShort((short) 1); // Questions count + buffer.appendShort((short) 0); // Answers count + buffer.appendShort((short) 0); // Authority RRs count + buffer.appendShort((short) 0); // Additional RRs count + + // Write query name + for (String label : Splitter.on(".").split(queryName)) { + buffer.appendByte((byte) label.length()); + buffer.appendString(label); + } + buffer.appendByte((byte) 0); // End of query name + + // Write query type and class + buffer.appendShort((short) 16); // Type (TXT) + buffer.appendShort((short) 1); // Class (IN) + + return buffer; + } + + private short getQueryId(final Buffer queryData) { + return (short) ((queryData.getByte(0) & 0xff) << 8 | (queryData.getByte(1) & 0xff)); + } + + /** + * Mock server local port + * + * @return server port + */ + public int port() { + return dnsPort; + } +} diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/ENRResponsePacketDataTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/ENRResponsePacketDataTest.java index 72bb407d162..7ba47bd97cc 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/ENRResponsePacketDataTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/ENRResponsePacketDataTest.java @@ -20,6 +20,8 @@ import org.hyperledger.besu.ethereum.rlp.RLP; import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.crypto.SECP256K1; import org.apache.tuweni.units.bigints.UInt64; import org.ethereum.beacon.discovery.schema.EnrField; import org.ethereum.beacon.discovery.schema.IdentitySchema; @@ -34,8 +36,10 @@ public void serializeDeserialize() { final Bytes requestHash = Bytes.fromHexStringLenient("0x1234"); final Bytes nodeId = Bytes.fromHexString("a448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7"); - final Bytes privateKey = - Bytes.fromHexString("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"); + final SECP256K1.SecretKey privateKey = + SECP256K1.SecretKey.fromBytes( + Bytes32.fromHexString( + "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")); NodeRecord nodeRecord = NodeRecordFactory.DEFAULT.createFromValues( @@ -48,7 +52,8 @@ public void serializeDeserialize() { new EnrField(EnrField.TCP, 8080), new EnrField(EnrField.TCP_V6, 8080), new EnrField( - EnrField.PKEY_SECP256K1, Functions.derivePublicKeyFromPrivate(privateKey))); + EnrField.PKEY_SECP256K1, + Functions.deriveCompressedPublicKeyFromPrivate(privateKey))); nodeRecord.sign(privateKey); assertThat(nodeRecord.getNodeId()).isEqualTo(nodeId); @@ -72,8 +77,10 @@ public void readFrom() { final Bytes requestHash = Bytes.fromHexStringLenient("0x1234"); final Bytes nodeId = Bytes.fromHexString("a448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7"); - final Bytes privateKey = - Bytes.fromHexString("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"); + final SECP256K1.SecretKey privateKey = + SECP256K1.SecretKey.fromBytes( + Bytes32.fromHexString( + "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")); NodeRecord nodeRecord = NodeRecordFactory.DEFAULT.createFromValues( @@ -82,7 +89,8 @@ public void readFrom() { new EnrField(EnrField.IP_V4, Bytes.fromHexString("0x7F000001")), new EnrField(EnrField.UDP, 30303), new EnrField( - EnrField.PKEY_SECP256K1, Functions.derivePublicKeyFromPrivate(privateKey))); + EnrField.PKEY_SECP256K1, + Functions.deriveCompressedPublicKeyFromPrivate(privateKey))); nodeRecord.sign(privateKey); assertThat(nodeRecord.getNodeId()).isEqualTo(nodeId); @@ -109,8 +117,10 @@ public void writeTo() { final Bytes requestHash = Bytes.fromHexStringLenient("0x1234"); final Bytes nodeId = Bytes.fromHexString("a448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7"); - final Bytes privateKey = - Bytes.fromHexString("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"); + final SECP256K1.SecretKey privateKey = + SECP256K1.SecretKey.fromBytes( + Bytes32.fromHexString( + "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")); NodeRecord nodeRecord = NodeRecordFactory.DEFAULT.createFromValues( @@ -119,7 +129,8 @@ public void writeTo() { new EnrField(EnrField.IP_V4, Bytes.fromHexString("0x7F000001")), new EnrField(EnrField.UDP, 30303), new EnrField( - EnrField.PKEY_SECP256K1, Functions.derivePublicKeyFromPrivate(privateKey))); + EnrField.PKEY_SECP256K1, + Functions.deriveCompressedPublicKeyFromPrivate(privateKey))); nodeRecord.sign(privateKey); assertThat(nodeRecord.getNodeId()).isEqualTo(nodeId); @@ -144,8 +155,10 @@ public void readFrom_withExtraFields() { final Bytes requestHash = Bytes.fromHexStringLenient("0x1234"); final Bytes nodeId = Bytes.fromHexString("a448f24c6d18e575453db13171562b71999873db5b286df957af199ec94617f7"); - final Bytes privateKey = - Bytes.fromHexString("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"); + final SECP256K1.SecretKey privateKey = + SECP256K1.SecretKey.fromBytes( + Bytes32.fromHexString( + "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")); NodeRecord nodeRecord = NodeRecordFactory.DEFAULT.createFromValues( @@ -153,7 +166,9 @@ public void readFrom_withExtraFields() { new EnrField(EnrField.ID, IdentitySchema.V4), new EnrField(EnrField.IP_V4, Bytes.fromHexString("0x7F000001")), new EnrField(EnrField.UDP, 30303), - new EnrField(EnrField.PKEY_SECP256K1, Functions.derivePublicKeyFromPrivate(privateKey)), + new EnrField( + EnrField.PKEY_SECP256K1, + Functions.deriveCompressedPublicKeyFromPrivate(privateKey)), new EnrField("foo", Bytes.fromHexString("0x1234"))); nodeRecord.sign(privateKey); @@ -181,8 +196,10 @@ public void readFrom_withExtraFields() { @Test public void readFrom_invalidSignature() { final Bytes requestHash = Bytes.fromHexStringLenient("0x1234"); - final Bytes privateKey = - Bytes.fromHexString("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f292"); + final SECP256K1.SecretKey privateKey = + SECP256K1.SecretKey.fromBytes( + Bytes32.fromHexString( + "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f292")); NodeRecord nodeRecord = NodeRecordFactory.DEFAULT.createFromValues( @@ -191,7 +208,8 @@ public void readFrom_invalidSignature() { new EnrField(EnrField.IP_V4, Bytes.fromHexString("0x7F000001")), new EnrField(EnrField.UDP, 30303), new EnrField( - EnrField.PKEY_SECP256K1, Functions.derivePublicKeyFromPrivate(privateKey))); + EnrField.PKEY_SECP256K1, + Functions.deriveCompressedPublicKeyFromPrivate(privateKey))); nodeRecord.sign(privateKey); nodeRecord.set(EnrField.UDP, 1234); diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/MockPeerDiscoveryAgent.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/MockPeerDiscoveryAgent.java index ea6e1593b83..88196f18b5d 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/MockPeerDiscoveryAgent.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/MockPeerDiscoveryAgent.java @@ -63,7 +63,8 @@ public MockPeerDiscoveryAgent( new NoOpMetricsSystem(), new InMemoryKeyValueStorageProvider(), forkIdManager, - rlpxAgent); + rlpxAgent, + new PeerTable(nodeKey.getPublicKey().getEncodedBytes())); this.agentNetwork = agentNetwork; } diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerDiscoveryControllerTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerDiscoveryControllerTest.java index 182366ac03a..1064da78e58 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerDiscoveryControllerTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerDiscoveryControllerTest.java @@ -35,8 +35,6 @@ import org.hyperledger.besu.crypto.SignatureAlgorithm; import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; import org.hyperledger.besu.cryptoservices.NodeKey; -import org.hyperledger.besu.ethereum.forkid.ForkId; -import org.hyperledger.besu.ethereum.forkid.ForkIdManager; import org.hyperledger.besu.ethereum.p2p.discovery.DiscoveryPeer; import org.hyperledger.besu.ethereum.p2p.discovery.Endpoint; import org.hyperledger.besu.ethereum.p2p.discovery.PeerDiscoveryStatus; @@ -61,6 +59,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import java.util.stream.Collectors; +import javax.annotation.Nonnull; import com.google.common.base.Ticker; import com.google.common.cache.Cache; @@ -77,7 +76,6 @@ import org.ethereum.beacon.discovery.schema.IdentitySchemaInterpreter; import org.ethereum.beacon.discovery.schema.NodeRecord; import org.ethereum.beacon.discovery.schema.NodeRecordFactory; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -1480,14 +1478,12 @@ public long read() { } @Test - public void shouldFiltersOnForkIdSuccess() { + public void forkIdShouldBeAvailableIfEnrPacketContainsForkId() { final List nodeKeys = PeerDiscoveryTestHelper.generateNodeKeys(1); final List peers = helper.createDiscoveryPeers(nodeKeys); - final ForkIdManager forkIdManager = mock(ForkIdManager.class); final DiscoveryPeer sender = peers.get(0); - final Packet enrPacket = prepareForForkIdCheck(forkIdManager, nodeKeys, sender, true); + final Packet enrPacket = prepareForForkIdCheck(nodeKeys, sender, true); - when(forkIdManager.peerCheck(any(ForkId.class))).thenReturn(true); controller.onMessage(enrPacket, sender); final Optional maybePeer = @@ -1501,35 +1497,12 @@ public void shouldFiltersOnForkIdSuccess() { verify(controller, times(1)).connectOnRlpxLayer(eq(maybePeer.get())); } - @Test - public void shouldFiltersOnForkIdFailure() { - final List nodeKeys = PeerDiscoveryTestHelper.generateNodeKeys(1); - final List peers = helper.createDiscoveryPeers(nodeKeys); - final ForkIdManager forkIdManager = mock(ForkIdManager.class); - final DiscoveryPeer sender = peers.get(0); - final Packet enrPacket = prepareForForkIdCheck(forkIdManager, nodeKeys, sender, true); - - when(forkIdManager.peerCheck(any(ForkId.class))).thenReturn(false); - controller.onMessage(enrPacket, sender); - - final Optional maybePeer = - controller - .streamDiscoveredPeers() - .filter(p -> p.getId().equals(sender.getId())) - .findFirst(); - - assertThat(maybePeer.isPresent()).isTrue(); - assertThat(maybePeer.get().getForkId().isPresent()).isTrue(); - verify(controller, never()).connectOnRlpxLayer(eq(maybePeer.get())); - } - @Test public void shouldStillCallConnectIfNoForkIdSent() { final List nodeKeys = PeerDiscoveryTestHelper.generateNodeKeys(1); final List peers = helper.createDiscoveryPeers(nodeKeys); final DiscoveryPeer sender = peers.get(0); - final Packet enrPacket = - prepareForForkIdCheck(mock(ForkIdManager.class), nodeKeys, sender, false); + final Packet enrPacket = prepareForForkIdCheck(nodeKeys, sender, false); controller.onMessage(enrPacket, sender); @@ -1544,12 +1517,9 @@ public void shouldStillCallConnectIfNoForkIdSent() { verify(controller, times(1)).connectOnRlpxLayer(eq(maybePeer.get())); } - @NotNull + @Nonnull private Packet prepareForForkIdCheck( - final ForkIdManager forkIdManager, - final List nodeKeys, - final DiscoveryPeer sender, - final boolean sendForkId) { + final List nodeKeys, final DiscoveryPeer sender, final boolean sendForkId) { final HashMap packetTypeBytesHashMap = new HashMap<>(); final OutboundMessageHandler outboundMessageHandler = (dp, pa) -> packetTypeBytesHashMap.put(pa.getType(), pa.getHash()); @@ -1573,7 +1543,6 @@ public long read() { .outboundMessageHandler(outboundMessageHandler) .enrCache(enrs) .filterOnForkId(true) - .forkIdManager(forkIdManager) .build(); // Mock the creation of the PING packet, so that we can control the hash, which gets validated @@ -1720,7 +1689,6 @@ static class ControllerBuilder { private Cache enrs = CacheBuilder.newBuilder().maximumSize(50).expireAfterWrite(10, TimeUnit.SECONDS).build(); private boolean filterOnForkId = false; - private ForkIdManager forkIdManager; public static ControllerBuilder create() { return new ControllerBuilder(); @@ -1776,11 +1744,6 @@ public ControllerBuilder filterOnForkId(final boolean filterOnForkId) { return this; } - public ControllerBuilder forkIdManager(final ForkIdManager forkIdManager) { - this.forkIdManager = forkIdManager; - return this; - } - PeerDiscoveryController build() { checkNotNull(nodeKey); if (localPeer == null) { @@ -1803,7 +1766,6 @@ PeerDiscoveryController build() { .peerPermissions(peerPermissions) .metricsSystem(new NoOpMetricsSystem()) .cacheForEnrRequests(enrs) - .forkIdManager(forkIdManager == null ? mock(ForkIdManager.class) : forkIdManager) .filterOnEnrForkId(filterOnForkId) .rlpxAgent(mock(RlpxAgent.class)) .build()); diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerDiscoveryTableRefreshTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerDiscoveryTableRefreshTest.java index 949b318906b..6320c909622 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerDiscoveryTableRefreshTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerDiscoveryTableRefreshTest.java @@ -24,7 +24,6 @@ import static org.mockito.Mockito.verify; import org.hyperledger.besu.cryptoservices.NodeKey; -import org.hyperledger.besu.ethereum.forkid.ForkIdManager; import org.hyperledger.besu.ethereum.p2p.discovery.DiscoveryPeer; import org.hyperledger.besu.ethereum.p2p.discovery.PeerDiscoveryStatus; import org.hyperledger.besu.ethereum.p2p.discovery.PeerDiscoveryTestHelper; @@ -72,7 +71,6 @@ public void tableRefreshSingleNode() { .tableRefreshIntervalMs(0) .metricsSystem(new NoOpMetricsSystem()) .rlpxAgent(mock(RlpxAgent.class)) - .forkIdManager(mock(ForkIdManager.class)) .build()); controller.start(); diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerRequirementCombineTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerRequirementCombineTest.java index 5c7a140dc64..7ad7f7c415e 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerRequirementCombineTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerRequirementCombineTest.java @@ -23,6 +23,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Stream; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -76,4 +77,11 @@ public void combine_withOn() { PeerRequirement combined = PeerRequirement.combine(Collections.emptyList()); assertThat(combined.hasSufficientPeers()).isTrue(); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerTableTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerTableTest.java index c0909a9b8b4..bcef09da817 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerTableTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/PeerTableTest.java @@ -43,7 +43,7 @@ public class PeerTableTest { @Test public void addPeer() { - final PeerTable table = new PeerTable(Peer.randomId(), 16); + final PeerTable table = new PeerTable(Peer.randomId()); final List peers = helper.createDiscoveryPeers(5); for (final DiscoveryPeer peer : peers) { @@ -63,7 +63,7 @@ public void addSelf() { .ipAddress("127.0.0.1") .discoveryAndListeningPorts(12345) .build()); - final PeerTable table = new PeerTable(localPeer.getId(), 16); + final PeerTable table = new PeerTable(localPeer.getId()); final PeerTable.AddResult result = table.tryAdd(localPeer); assertThat(result.getOutcome()).isEqualTo(AddOutcome.SELF); @@ -72,7 +72,7 @@ public void addSelf() { @Test public void peerExists() { - final PeerTable table = new PeerTable(Peer.randomId(), 16); + final PeerTable table = new PeerTable(Peer.randomId()); final DiscoveryPeer peer = helper.createDiscoveryPeer(); assertThat(table.tryAdd(peer).getOutcome()).isEqualTo(AddOutcome.ADDED); @@ -87,7 +87,7 @@ public void peerExists() { @Test public void peerExists_withDifferentIp() { - final PeerTable table = new PeerTable(Peer.randomId(), 16); + final PeerTable table = new PeerTable(Peer.randomId()); final Bytes peerId = SIGNATURE_ALGORITHM.get().generateKeyPair().getPublicKey().getEncodedBytes(); final DiscoveryPeer peer = @@ -107,7 +107,7 @@ public void peerExists_withDifferentIp() { @Test public void peerExists_withDifferentUdpPort() { - final PeerTable table = new PeerTable(Peer.randomId(), 16); + final PeerTable table = new PeerTable(Peer.randomId()); final Bytes peerId = SIGNATURE_ALGORITHM.get().generateKeyPair().getPublicKey().getEncodedBytes(); final DiscoveryPeer peer = @@ -127,7 +127,7 @@ public void peerExists_withDifferentUdpPort() { @Test public void peerExists_withDifferentIdAndUdpPort() { - final PeerTable table = new PeerTable(Peer.randomId(), 16); + final PeerTable table = new PeerTable(Peer.randomId()); final Bytes peerId = SIGNATURE_ALGORITHM.get().generateKeyPair().getPublicKey().getEncodedBytes(); final DiscoveryPeer peer = @@ -147,7 +147,7 @@ public void peerExists_withDifferentIdAndUdpPort() { @Test public void evictExistingPeerShouldEvict() { - final PeerTable table = new PeerTable(Peer.randomId(), 16); + final PeerTable table = new PeerTable(Peer.randomId()); final DiscoveryPeer peer = helper.createDiscoveryPeer(); table.tryAdd(peer); @@ -158,7 +158,7 @@ public void evictExistingPeerShouldEvict() { @Test public void evictPeerFromEmptyTableShouldNotEvict() { - final PeerTable table = new PeerTable(Peer.randomId(), 16); + final PeerTable table = new PeerTable(Peer.randomId()); final DiscoveryPeer peer = helper.createDiscoveryPeer(); final EvictResult evictResult = table.tryEvict(peer); @@ -167,7 +167,7 @@ public void evictPeerFromEmptyTableShouldNotEvict() { @Test public void evictAbsentPeerShouldNotEvict() { - final PeerTable table = new PeerTable(Peer.randomId(), 16); + final PeerTable table = new PeerTable(Peer.randomId()); final DiscoveryPeer peer = helper.createDiscoveryPeer(); final List otherPeers = helper.createDiscoveryPeers(5); otherPeers.forEach(table::tryAdd); @@ -179,9 +179,67 @@ public void evictAbsentPeerShouldNotEvict() { @Test public void evictSelfPeerShouldReturnSelfOutcome() { final DiscoveryPeer peer = helper.createDiscoveryPeer(); - final PeerTable table = new PeerTable(peer.getId(), 16); + final PeerTable table = new PeerTable(peer.getId()); final EvictResult evictResult = table.tryEvict(peer); assertThat(evictResult.getOutcome()).isEqualTo(EvictOutcome.SELF); } + + @Test + public void ipAddressIsInvalidReturnsTrue() { + final Endpoint endpoint1 = new Endpoint("1.1.1.1", 2, Optional.of(Integer.valueOf(1))); + final Endpoint endpoint2 = new Endpoint("1.1.1.1", 3, Optional.of(Integer.valueOf(1))); + final DiscoveryPeer peer1 = DiscoveryPeer.fromIdAndEndpoint(Peer.randomId(), endpoint1); + final DiscoveryPeer peer2 = DiscoveryPeer.fromIdAndEndpoint(Peer.randomId(), endpoint2); + final PeerTable table = new PeerTable(Bytes.random(64)); + + final PeerTable.AddResult addResult1 = table.tryAdd(peer1); + assertThat(addResult1.getOutcome()).isEqualTo(PeerTable.AddResult.added().getOutcome()); + + assertThat(table.ipAddressIsInvalid(peer2.getEndpoint())).isEqualTo(true); + } + + @Test + public void ipAddressIsInvalidReturnsFalse() { + final Endpoint endpoint1 = new Endpoint("1.1.1.1", 2, Optional.of(Integer.valueOf(1))); + final Endpoint endpoint2 = new Endpoint("1.1.1.1", 3, Optional.of(Integer.valueOf(2))); + final DiscoveryPeer peer1 = DiscoveryPeer.fromIdAndEndpoint(Peer.randomId(), endpoint1); + final DiscoveryPeer peer2 = DiscoveryPeer.fromIdAndEndpoint(Peer.randomId(), endpoint2); + final PeerTable table = new PeerTable(Bytes.random(64)); + + final PeerTable.AddResult addResult1 = table.tryAdd(peer1); + assertThat(addResult1.getOutcome()).isEqualTo(PeerTable.AddResult.added().getOutcome()); + + assertThat(table.ipAddressIsInvalid(peer2.getEndpoint())).isEqualTo(false); + } + + @Test + public void invalidIPAddressNotAdded() { + final Endpoint endpoint1 = new Endpoint("1.1.1.1", 2, Optional.of(Integer.valueOf(1))); + final Endpoint endpoint2 = new Endpoint("1.1.1.1", 3, Optional.of(Integer.valueOf(1))); + final DiscoveryPeer peer1 = DiscoveryPeer.fromIdAndEndpoint(Peer.randomId(), endpoint1); + final DiscoveryPeer peer2 = DiscoveryPeer.fromIdAndEndpoint(Peer.randomId(), endpoint2); + final PeerTable table = new PeerTable(Bytes.random(64)); + + final PeerTable.AddResult addResult1 = table.tryAdd(peer1); + assertThat(addResult1.getOutcome()).isEqualTo(PeerTable.AddResult.added().getOutcome()); + + final PeerTable.AddResult addResult2 = table.tryAdd(peer2); + assertThat(addResult2.getOutcome()).isEqualTo(PeerTable.AddResult.invalid().getOutcome()); + } + + @Test + public void validIPAddressAdded() { + final Endpoint endpoint1 = new Endpoint("1.1.1.1", 2, Optional.of(Integer.valueOf(1))); + final Endpoint endpoint2 = new Endpoint("1.1.1.1", 3, Optional.of(Integer.valueOf(2))); + final DiscoveryPeer peer1 = DiscoveryPeer.fromIdAndEndpoint(Peer.randomId(), endpoint1); + final DiscoveryPeer peer2 = DiscoveryPeer.fromIdAndEndpoint(Peer.randomId(), endpoint2); + final PeerTable table = new PeerTable(Bytes.random(64)); + + final PeerTable.AddResult addResult1 = table.tryAdd(peer1); + assertThat(addResult1.getOutcome()).isEqualTo(PeerTable.AddResult.added().getOutcome()); + + final PeerTable.AddResult addResult2 = table.tryAdd(peer2); + assertThat(addResult2.getOutcome()).isEqualTo(PeerTable.AddResult.added().getOutcome()); + } } diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/RecursivePeerRefreshStateTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/RecursivePeerRefreshStateTest.java index e79abb883dd..5d26f8cc6e9 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/RecursivePeerRefreshStateTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/discovery/internal/RecursivePeerRefreshStateTest.java @@ -57,7 +57,7 @@ public class RecursivePeerRefreshStateTest { neighborFinder, timerUtil, localPeer, - new PeerTable(createId(999), 16), + new PeerTable(createId(999)), peerPermissions, 5, 100); @@ -180,7 +180,7 @@ public void shouldStopWhenMaximumNumberOfRoundsReached() { neighborFinder, timerUtil, localPeer, - new PeerTable(createId(999), 16), + new PeerTable(createId(999)), peerPermissions, 5, 1); @@ -466,7 +466,7 @@ public void shouldNotBondWithNonPermittedNode() { neighborFinder, timerUtil, localPeer, - new PeerTable(createId(999), 16), + new PeerTable(createId(999)), peerPermissions, 5, 100); diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetworkTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetworkTest.java index a54c9038b8e..88b9197262a 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetworkTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/DefaultP2PNetworkTest.java @@ -55,12 +55,9 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; import java.util.stream.Stream; -import io.vertx.core.Context; import io.vertx.core.Vertx; -import io.vertx.core.dns.DnsClient; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.crypto.SECP256K1; import org.assertj.core.api.Assertions; @@ -82,7 +79,7 @@ public final class DefaultP2PNetworkTest { @Mock PeerDiscoveryAgent discoveryAgent; @Mock RlpxAgent rlpxAgent; - @Captor private ArgumentCaptor> peerStreamCaptor; + @Captor private ArgumentCaptor peerCaptor; private final NetworkingConfiguration config = NetworkingConfiguration.create() @@ -276,12 +273,9 @@ public void attemptPeerConnections_bondedPeers() { final DefaultP2PNetwork network = network(); network.attemptPeerConnections(); - verify(rlpxAgent, times(1)).connect(peerStreamCaptor.capture()); + verify(rlpxAgent, times(1)).connect(peerCaptor.capture()); - final List capturedPeers = - peerStreamCaptor.getValue().collect(Collectors.toList()); - assertThat(capturedPeers.contains(discoPeer)).isTrue(); - assertThat(capturedPeers.size()).isEqualTo(1); + assertThat(peerCaptor.getValue()).isEqualTo(discoPeer); } @Test @@ -293,12 +287,7 @@ public void attemptPeerConnections_unbondedPeers() { final DefaultP2PNetwork network = network(); network.attemptPeerConnections(); - verify(rlpxAgent, times(1)).connect(peerStreamCaptor.capture()); - - final List capturedPeers = - peerStreamCaptor.getValue().collect(Collectors.toList()); - assertThat(capturedPeers.contains(discoPeer)).isFalse(); - assertThat(capturedPeers.size()).isEqualTo(0); + verify(rlpxAgent, times(0)).connect(any()); } @Test @@ -314,14 +303,7 @@ public void attemptPeerConnections_sortsPeersByLastContacted() { final DefaultP2PNetwork network = network(); network.attemptPeerConnections(); - verify(rlpxAgent, times(1)).connect(peerStreamCaptor.capture()); - - final List capturedPeers = - peerStreamCaptor.getValue().collect(Collectors.toList()); - assertThat(capturedPeers.size()).isEqualTo(3); - assertThat(capturedPeers.get(0)).isEqualTo(discoPeers.get(1)); - assertThat(capturedPeers.get(1)).isEqualTo(discoPeers.get(0)); - assertThat(capturedPeers.get(2)).isEqualTo(discoPeers.get(2)); + verify(rlpxAgent, times(3)).connect(any()); } @Test @@ -351,16 +333,21 @@ public void shouldStartDnsDiscoveryWhenDnsURLIsConfigured() { final NetworkingConfiguration dnsConfig = when(spy(config).getDiscovery()).thenReturn(disco).getMock(); - Vertx vertx = mock(Vertx.class); - when(vertx.createDnsClient(any())).thenReturn(mock(DnsClient.class)); - when(vertx.getOrCreateContext()).thenReturn(mock(Context.class)); + final Vertx vertx = Vertx.vertx(); // use real instance // spy on DefaultP2PNetwork final DefaultP2PNetwork testClass = (DefaultP2PNetwork) builder().vertx(vertx).config(dnsConfig).build(); testClass.start(); - assertThat(testClass.getDnsDaemon()).isPresent(); + try { + // the actual lookup won't work because of mock discovery url, however, a valid DNSDaemon + // should be created. + assertThat(testClass.getDnsDaemon()).isPresent(); + } finally { + testClass.stop(); + vertx.close(); + } } @Test @@ -374,17 +361,19 @@ public void shouldUseDnsServerOverrideIfPresent() { doReturn(disco).when(dnsConfig).getDiscovery(); doReturn(Optional.of("localhost")).when(dnsConfig).getDnsDiscoveryServerOverride(); - Vertx vertx = mock(Vertx.class); - when(vertx.createDnsClient(any())).thenReturn(mock(DnsClient.class)); - when(vertx.getOrCreateContext()).thenReturn(mock(Context.class)); - + Vertx vertx = Vertx.vertx(); // use real instance final DefaultP2PNetwork testClass = (DefaultP2PNetwork) builder().config(dnsConfig).vertx(vertx).build(); testClass.start(); // ensure we used the dns server override config when building DNSDaemon: - assertThat(testClass.getDnsDaemon()).isPresent(); - verify(dnsConfig, times(2)).getDnsDiscoveryServerOverride(); + try { + assertThat(testClass.getDnsDaemon()).isPresent(); + verify(dnsConfig, times(2)).getDnsDiscoveryServerOverride(); + } finally { + testClass.stop(); + vertx.close(); + } } private DefaultP2PNetwork network() { diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/FileBasedPasswordProvider.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/FileBasedPasswordProvider.java index 7c72a66d23a..5a0e3e861b2 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/FileBasedPasswordProvider.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/FileBasedPasswordProvider.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/NetworkingServiceLifecycleTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/NetworkingServiceLifecycleTest.java index e0069c0069d..ac01e503ad9 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/NetworkingServiceLifecycleTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/NetworkingServiceLifecycleTest.java @@ -39,10 +39,10 @@ import java.util.Arrays; import java.util.Collections; import java.util.stream.Stream; +import javax.annotation.Nonnull; import io.vertx.core.Vertx; import org.assertj.core.api.Assertions; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -74,7 +74,7 @@ public void createP2PNetwork() throws IOException { } } - @NotNull + @Nonnull private DefaultP2PNetwork.Builder getP2PNetworkBuilder() { final DefaultP2PNetwork.Builder builder = builder(); final MutableBlockchain blockchainMock = mock(MutableBlockchain.class); diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/P2PPlainNetworkTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/P2PPlainNetworkTest.java index 32cf99635ff..d02cb1ff83f 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/P2PPlainNetworkTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/P2PPlainNetworkTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/PeerDenylistManagerTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/PeerDenylistManagerTest.java index db526cfc3f2..bef39a2a09b 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/PeerDenylistManagerTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/PeerDenylistManagerTest.java @@ -62,6 +62,16 @@ public void denylistPeerForBadBehavior() { checkPermissions(denylist, peer.getPeer(), false); } + @Test + public void denylistPeerForBadBehaviorWithDifferentMessage() { + final PeerConnection peer = generatePeerConnection(); + + checkPermissions(denylist, peer.getPeer(), true); + peerDenylistManager.onDisconnect( + peer, DisconnectReason.BREACH_OF_PROTOCOL_INVALID_MESSAGE_CODE_FOR_PROTOCOL, false); + checkPermissions(denylist, peer.getPeer(), false); + } + @Test public void doesNotDenylistPeerForOurBadBehavior() { final PeerConnection peer = generatePeerConnection(); @@ -101,6 +111,15 @@ public void denylistIncompatiblePeerWhoIssuesDisconnect() { checkPermissions(denylist, peer.getPeer(), false); } + @Test + public void disconnectReasonWithEmptyValue_doesNotAddToDenylist() { + final PeerConnection peer = generatePeerConnection(); + + checkPermissions(denylist, peer.getPeer(), true); + peerDenylistManager.onDisconnect(peer, DisconnectReason.UNKNOWN, false); + checkPermissions(denylist, peer.getPeer(), true); + } + private void checkPermissions( final PeerPermissionsDenylist denylist, final Peer remotePeer, final boolean expectedResult) { for (PeerPermissions.Action action : PeerPermissions.Action.values()) { diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/peers/EnodeURLImplTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/peers/EnodeURLImplTest.java index b7cc2a06327..cf8fdd6cf00 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/peers/EnodeURLImplTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/peers/EnodeURLImplTest.java @@ -32,6 +32,8 @@ import org.apache.tuweni.bytes.Bytes; import org.assertj.core.api.ThrowableAssert; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; import org.mockito.Mockito; public class EnodeURLImplTest { @@ -494,6 +496,9 @@ public void toURI_WithHostnameShouldWorkWhenDnsEnabled() { } @Test + @DisabledOnOs( + value = OS.MAC, + disabledReason = "canonical lookup may not match dns lookup for local machine") public void toURI_WithHostnameShouldWorkWhenDnsEnabledAndUpdateEnabled() throws UnknownHostException { final String enodeURLString = diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/peers/StaticNodesParserTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/peers/StaticNodesParserTest.java index 91f5e2d602e..6764126222e 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/peers/StaticNodesParserTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/peers/StaticNodesParserTest.java @@ -68,7 +68,7 @@ public class StaticNodesParserTest { .discoveryAndListeningPorts(30306) .build()); - @TempDir private static Path testFolder; + @TempDir private Path testFolder; @Test public void validFileLoadsWithExpectedEnodes() throws IOException, URISyntaxException { diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/permissions/PeerPermissionsSubnetTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/permissions/PeerPermissionsSubnetTest.java new file mode 100644 index 00000000000..185858e658e --- /dev/null +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/permissions/PeerPermissionsSubnetTest.java @@ -0,0 +1,81 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.p2p.permissions; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.ethereum.p2p.peers.DefaultPeer; +import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl; +import org.hyperledger.besu.ethereum.p2p.peers.Peer; +import org.hyperledger.besu.ethereum.p2p.permissions.PeerPermissions.Action; + +import java.util.List; + +import org.apache.commons.net.util.SubnetUtils; +import org.apache.commons.net.util.SubnetUtils.SubnetInfo; +import org.junit.jupiter.api.Test; + +public class PeerPermissionsSubnetTest { + + private final Peer remoteNode = createPeer(); + + @Test + public void peerInSubnetRangeShouldBePermitted() { + List allowedSubnets = List.of(subnet("127.0.0.0/24")); + PeerPermissionSubnet peerPermissionSubnet = new PeerPermissionSubnet(allowedSubnets); + checkPermissions(peerPermissionSubnet, remoteNode, true); + } + + @Test + public void peerInAtLeastOneSubnetRangeShouldBePermitted() { + List allowedSubnets = List.of(subnet("127.0.0.0/24"), subnet("10.0.0.1/24")); + PeerPermissionSubnet peerPermissionSubnet = new PeerPermissionSubnet(allowedSubnets); + checkPermissions(peerPermissionSubnet, remoteNode, true); + } + + @Test + public void peerOutSubnetRangeShouldNotBePermitted() { + List allowedSubnets = List.of(subnet("10.0.0.0/24")); + PeerPermissionSubnet peerPermissionSubnet = new PeerPermissionSubnet(allowedSubnets); + checkPermissions(peerPermissionSubnet, remoteNode, false); + } + + @Test + public void peerShouldBePermittedIfNoSubnets() { + PeerPermissionSubnet peerPermissionSubnet = new PeerPermissionSubnet(List.of()); + checkPermissions(peerPermissionSubnet, remoteNode, true); + } + + private void checkPermissions( + final PeerPermissions peerPermissions, final Peer remotePeer, final boolean expectedResult) { + for (Action action : Action.values()) { + assertThat(peerPermissions.isPermitted(createPeer(), remotePeer, action)) + .isEqualTo(expectedResult); + } + } + + private SubnetInfo subnet(final String subnet) { + return new SubnetUtils(subnet).getInfo(); + } + + private Peer createPeer() { + return DefaultPeer.fromEnodeURL( + EnodeURLImpl.builder() + .nodeId(Peer.randomId()) + .ipAddress("127.0.0.1") + .discoveryAndListeningPorts(EnodeURLImpl.DEFAULT_LISTENING_PORT) + .build()); + } +} diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/plain/MessageHandlerTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/plain/MessageHandlerTest.java index fba1bbdf2f3..550b7170a8a 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/plain/MessageHandlerTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/plain/MessageHandlerTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/RlpxAgentTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/RlpxAgentTest.java index d2fdf8c76a6..c639579eee1 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/RlpxAgentTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/RlpxAgentTest.java @@ -296,7 +296,7 @@ public void connect_largeStreamOfPeers() { Stream.generate(PeerTestHelper::createPeer).limit(peerNo); agent = spy(agent); - agent.connect(peerStream); + peerStream.forEach(agent::connect); assertThat(agent.getMapOfCompletableFutures().size()).isEqualTo(peerNo); verify(agent, times(peerNo)).connect(any(Peer.class)); diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/MockConnectionInitializer.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/MockConnectionInitializer.java index 814e9a797f6..64b7335cd45 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/MockConnectionInitializer.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/MockConnectionInitializer.java @@ -19,6 +19,7 @@ import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage; import org.hyperledger.besu.util.Subscribers; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.HashMap; import java.util.Map; @@ -58,7 +59,7 @@ public void simulateIncomingConnection(final PeerConnection incomingConnection) @Override public CompletableFuture start() { final InetSocketAddress socketAddress = - new InetSocketAddress("127.0.0.1", NEXT_PORT.incrementAndGet()); + new InetSocketAddress(InetAddress.getLoopbackAddress(), NEXT_PORT.incrementAndGet()); return CompletableFuture.completedFuture(socketAddress); } diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/DeFramerTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/DeFramerTest.java index dfafb002f49..31a9a126e13 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/DeFramerTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/DeFramerTest.java @@ -24,6 +24,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import org.hyperledger.besu.ethereum.forkid.ForkId; +import org.hyperledger.besu.ethereum.p2p.discovery.DiscoveryPeer; +import org.hyperledger.besu.ethereum.p2p.discovery.internal.PeerTable; import org.hyperledger.besu.ethereum.p2p.network.exceptions.BreachOfProtocolException; import org.hyperledger.besu.ethereum.p2p.network.exceptions.IncompatiblePeerException; import org.hyperledger.besu.ethereum.p2p.network.exceptions.PeerChannelClosedException; @@ -52,6 +55,7 @@ import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.data.EnodeURL; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Arrays; @@ -65,6 +69,7 @@ import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelId; import io.netty.channel.ChannelPipeline; @@ -88,7 +93,8 @@ public class DeFramerTest { private final PeerConnection peerConnection = mock(PeerConnection.class); private final CompletableFuture connectFuture = new CompletableFuture<>(); private final int remotePort = 12345; - private final InetSocketAddress remoteAddress = new InetSocketAddress("127.0.0.1", remotePort); + private final InetSocketAddress remoteAddress = + new InetSocketAddress(InetAddress.getLoopbackAddress(), remotePort); private final int p2pVersion = 5; private final String clientId = "abc"; @@ -103,7 +109,7 @@ public class DeFramerTest { private final LocalNode localNode = LocalNode.create(clientId, p2pVersion, capabilities, localEnode); - private final DeFramer deFramer = createDeFramer(null); + private final DeFramer deFramer = createDeFramer(null, Optional.empty()); @BeforeEach @SuppressWarnings("unchecked") @@ -132,7 +138,8 @@ public void exceptionCaught_shouldDisconnectForBreachOfProtocolWhenFramingExcept deFramer.exceptionCaught(ctx, new DecoderException(new FramingException("Test"))); - verify(peerConnection).disconnect(DisconnectReason.BREACH_OF_PROTOCOL); + verify(peerConnection) + .disconnect(DisconnectReason.BREACH_OF_PROTOCOL_INVALID_MESSAGE_RECEIVED_CAUGHT_EXCEPTION); } @Test @@ -142,7 +149,8 @@ public void exceptionCaught_shouldDisconnectForBreachOfProtocolWhenRlpExceptionT deFramer.exceptionCaught(ctx, new DecoderException(new RLPException("Test"))); - verify(peerConnection).disconnect(DisconnectReason.BREACH_OF_PROTOCOL); + verify(peerConnection) + .disconnect(DisconnectReason.BREACH_OF_PROTOCOL_INVALID_MESSAGE_RECEIVED_CAUGHT_EXCEPTION); } @Test @@ -196,7 +204,7 @@ public void decode_handlesHello() throws ExecutionException, InterruptedExceptio assertThat(out).isEmpty(); // Next phase of pipeline should be setup - verify(pipeline, times(1)).addLast(any()); + verify(pipeline, times(1)).addLast(any(ChannelHandler[].class)); // Next message should be pushed out final PingMessage nextMessage = PingMessage.get(); @@ -204,7 +212,7 @@ public void decode_handlesHello() throws ExecutionException, InterruptedExceptio when(framer.deframe(eq(nextData))) .thenReturn(new RawMessage(nextMessage.getCode(), nextMessage.getData())) .thenReturn(null); - verify(pipeline, times(1)).addLast(any()); + verify(pipeline, times(1)).addLast(any(ChannelHandler[].class)); deFramer.decode(ctx, nextData, out); assertThat(out.size()).isEqualTo(1); } @@ -218,7 +226,7 @@ public void decode_handlesHelloFromPeerWithAdvertisedPortOf0() final Peer peer = createRemotePeer(); final PeerInfo remotePeerInfo = new PeerInfo(p2pVersion, clientId, capabilities, 0, peer.getId()); - final DeFramer deFramer = createDeFramer(null); + final DeFramer deFramer = createDeFramer(null, Optional.empty()); final HelloMessage helloMessage = HelloMessage.create(remotePeerInfo); final ByteBuf data = Unpooled.wrappedBuffer(helloMessage.getData().toArray()); @@ -246,7 +254,7 @@ public void decode_handlesHelloFromPeerWithAdvertisedPortOf0() assertThat(peerConnection.getPeer().getEnodeURL()).isEqualTo(expectedEnode); // Next phase of pipeline should be setup - verify(pipeline, times(1)).addLast(any()); + verify(pipeline, times(1)).addLast(any(ChannelHandler[].class)); // Next message should be pushed out final PingMessage nextMessage = PingMessage.get(); @@ -254,11 +262,44 @@ public void decode_handlesHelloFromPeerWithAdvertisedPortOf0() when(framer.deframe(eq(nextData))) .thenReturn(new RawMessage(nextMessage.getCode(), nextMessage.getData())) .thenReturn(null); - verify(pipeline, times(1)).addLast(any()); + verify(pipeline, times(1)).addLast(any(ChannelHandler[].class)); deFramer.decode(ctx, nextData, out); assertThat(out.size()).isEqualTo(1); } + @Test + public void decode_duringHandshakeFindsPeerInPeerTable() + throws ExecutionException, InterruptedException { + final ChannelFuture future = NettyMocks.channelFuture(false); + when(channel.closeFuture()).thenReturn(future); + + final Peer peer = createRemotePeer(); + final PeerInfo remotePeerInfo = + new PeerInfo(p2pVersion, clientId, capabilities, 0, peer.getId()); + + final HelloMessage helloMessage = HelloMessage.create(remotePeerInfo); + final Bytes nodeId = helloMessage.getPeerInfo().getNodeId(); + final String enodeURLString = + "enode://" + nodeId.toString().substring(2) + "@" + "12.13.14.15:30303?discport=30301"; + final Optional discoveryPeer = + DiscoveryPeer.from(DefaultPeer.fromURI(enodeURLString)); + final ForkId forkId = new ForkId(Bytes.fromHexString("0x190a55ad"), 4L); + discoveryPeer.orElseThrow().setForkId(forkId); + final DeFramer deFramer = createDeFramer(null, discoveryPeer); + final ByteBuf data = Unpooled.wrappedBuffer(helloMessage.getData().toArray()); + when(framer.deframe(eq(data))) + .thenReturn(new RawMessage(helloMessage.getCode(), helloMessage.getData())) + .thenReturn(null); + final List out = new ArrayList<>(); + deFramer.decode(ctx, data, out); + + assertThat(connectFuture).isDone(); + assertThat(connectFuture).isNotCompletedExceptionally(); + final PeerConnection peerConnection = connectFuture.get(); + assertThat(peerConnection.getPeerInfo()).isEqualTo(remotePeerInfo); + assertThat(peerConnection.getPeer().getForkId().orElseThrow()).isEqualTo(forkId); + } + @Test public void decode_handlesUnexpectedPeerId() { final ChannelFuture future = NettyMocks.channelFuture(false); @@ -273,7 +314,7 @@ public void decode_handlesUnexpectedPeerId() { capabilities, peer.getEnodeURL().getListeningPortOrZero(), mismatchedId); - final DeFramer deFramer = createDeFramer(peer); + final DeFramer deFramer = createDeFramer(peer, Optional.empty()); final HelloMessage helloMessage = HelloMessage.create(remotePeerInfo); final ByteBuf data = Unpooled.wrappedBuffer(helloMessage.getData().toArray()); @@ -292,7 +333,7 @@ public void decode_handlesUnexpectedPeerId() { assertThat(out).isEmpty(); // Next phase of pipeline should be setup - verify(pipeline, times(1)).addLast(any()); + verify(pipeline, times(1)).addLast(any(ChannelHandler[].class)); } @Test @@ -321,7 +362,7 @@ public void decode_handlesNoSharedCaps() { assertThat(out).isEmpty(); // Next phase of pipeline should be setup - verify(pipeline, times(1)).addLast(any()); + verify(pipeline, times(1)).addLast(any(ChannelHandler[].class)); } @Test @@ -413,7 +454,10 @@ private PeerInfo createPeerInfo(final Peer forPeer) { forPeer.getId()); } - private DeFramer createDeFramer(final Peer expectedPeer) { + private DeFramer createDeFramer( + final Peer expectedPeer, final Optional peerInPeerTable) { + final PeerTable peerTable = new PeerTable(localNode.getPeerInfo().getNodeId()); + peerInPeerTable.ifPresent(peerTable::tryAdd); return new DeFramer( framer, Arrays.asList(MockSubProtocol.create("eth")), @@ -422,6 +466,7 @@ private DeFramer createDeFramer(final Peer expectedPeer) { connectionEventDispatcher, connectFuture, new NoOpMetricsSystem(), - true); + true, + peerTable); } } diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/NettyTLSConnectionInitializerTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/NettyTLSConnectionInitializerTest.java index d4637952b63..ac39d81bb07 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/NettyTLSConnectionInitializerTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/NettyTLSConnectionInitializerTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.p2p.rlpx.connections.netty; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -25,6 +24,7 @@ import org.hyperledger.besu.cryptoservices.NodeKey; import org.hyperledger.besu.ethereum.p2p.config.RlpxConfiguration; +import org.hyperledger.besu.ethereum.p2p.discovery.internal.PeerTable; import org.hyperledger.besu.ethereum.p2p.peers.LocalNode; import org.hyperledger.besu.ethereum.p2p.peers.Peer; import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnectionEventDispatcher; @@ -44,6 +44,7 @@ import io.netty.handler.codec.compression.SnappyFrameEncoder; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslHandler; +import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -95,7 +96,8 @@ private NettyTLSConnectionInitializer createNettyTLSConnectionInitializer( eventDispatcher, new NoOpMetricsSystem(), () -> tlsContextFactory, - clientHelloSniHeaderEnabled); + clientHelloSniHeaderEnabled, + new PeerTable(Bytes.random(64))); } @Test diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/TLSContextFactoryTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/TLSContextFactoryTest.java index 45d228cb40f..d7763892c5d 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/TLSContextFactoryTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/TLSContextFactoryTest.java @@ -48,6 +48,7 @@ import io.netty.handler.ssl.SslHandler; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.params.ParameterizedTest; @@ -493,4 +494,11 @@ Optional getCause() { return messageHandler != null ? messageHandler.getCause() : Optional.empty(); } } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/PeerInfoTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/PeerInfoTest.java index 3217f23fcbb..9e0c3a147ee 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/PeerInfoTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/PeerInfoTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/messages/DisconnectMessageTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/messages/DisconnectMessageTest.java index 26a49e00105..2348bb1a289 100644 --- a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/messages/DisconnectMessageTest.java +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/rlpx/wire/messages/DisconnectMessageTest.java @@ -89,4 +89,14 @@ public void createWithUnknownReason() { assertThat(disconnectMessage.getReason()).isEqualTo(DisconnectReason.UNKNOWN); assertThat(disconnectMessage.getData().toString()).isEqualToIgnoringCase("0xC180"); } + + @Test + public void readFromWithSubprotocolTriggeredUsesGenericReason() { + MessageData messageData = + new RawMessage(WireMessageCodes.DISCONNECT, Bytes.fromHexString("0xC110")); + DisconnectMessage disconnectMessage = DisconnectMessage.readFrom(messageData); + + DisconnectReason reason = disconnectMessage.getReason(); + assertThat(reason).isEqualTo(DisconnectReason.SUBPROTOCOL_TRIGGERED); + } } diff --git a/ethereum/p2p/src/test/resources/discovery/dns/dns-records.json b/ethereum/p2p/src/test/resources/discovery/dns/dns-records.json new file mode 100644 index 00000000000..e51da66dbb7 --- /dev/null +++ b/ethereum/p2p/src/test/resources/discovery/dns/dns-records.json @@ -0,0 +1,137 @@ +{ + "all.holesky.ethdisco.net" : "enrtree-root:v1 e=QXOF2GWVHBKMKW57Y2KSKWYNFQ l=FDXN3SN67NA5DKA4J2GOK7BVQI seq=932 sig=DuA35BkYo9-FBwJ6MPxdNnYfcGMSGunAKUyfNN2gYQhYDBCPFZkr_cfe40Wspl2Vl76w6Ccs-B8ZrXpI_YymrAA", + "I56MJYJBMXTZZEPBQR6HWNAH7A.all.holesky.ethdisco.net" : "enr:-KO4QCTGqfxu7eEfe6M83e3bQLUgkKWGCdv6Ib5D0gCuQkkvbq1D0CN8UmHgpsddZNKHa2iBl3EIiGhaLBs_NKTxGQuGAYranLv7g2V0aMfGhJsZKtCAgmlkgnY0gmlwhCKH3xKJc2VjcDI1NmsxoQMHuEvotUinJNm_8Vz7412P1yKZ6r5iz9EcYj-9v3weRYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "IPSUCB6CICZIW6SERKZL7FFTIM.all.holesky.ethdisco.net" : "enr:-KO4QD47Vx9bnFcvE6JNAhrLz6GGgPk9GaJtP95ogjSOFHxlcZjnrT5TQzW9Jyx99XO9rzhK6WSvgdFoA9_54QPQcUaGAYxBy9opg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIe1TLKJc2VjcDI1NmsxoQKMO6bMfiH5DnJNFZgSqUEi6uHNnH2_bMh7hwVXRWTMl4RzbmFwwIN0Y3CCdqmDdWRwgnap", + "QJM2RVJT7USPDSI5VVFGGDW6JM.all.holesky.ethdisco.net" : "enr:-Ji4QHWntDnLOi4S9GKmF4HY7WrnJuJGaIBrnM3Y75G-8EUpZExnUkaZJtlmzOeJs6WAcYsissKSybUG5rSEiad0s86ByoNldGjHxoSbGSrQgIJpZIJ2NIJpcISLY0UEiXNlY3AyNTZrMaED5hLZweFA_nvWEc-Q7Vxqf2lIYxScF-F4ORxs-rVyHZKDdGNwgtn5g3VkcILZ-Q", + "C7SDD5OPASV7B2XUOXF5NLBBKU.all.holesky.ethdisco.net" : "enr:-KO4QLo95TIKo_axZA9xafYgl9jQ2ZTDFCQ29znjUwp-6zPUe6o8L9NO3X3n3uMZH8bFGnRmoIhyHLxNoOp2BU6O7xGGAYwoJEiug2V0aMfGhJsZKtCAgmlkgnY0gmlwhKEjElWJc2VjcDI1NmsxoQPIffRacW7k1QukSBfJ8leopO1M5wOv89G06m0SAQDB8oRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "AUQGHXRAP7J3AGI6TUOQVYTYLY.all.holesky.ethdisco.net" : "enr:-KO4QGuiAZHlxH7T5XPcPpRTovca50B0pzDLkgJ-zaxkjT_PO6zsAWQezB-b53yAnqx01Jdj5tQWsvWoaNVfiFw3iw6GAY1nSgXzg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEp2jJuJc2VjcDI1NmsxoQMTiH7XteYOVAHiXAZoacUjo5JYLhQINeFMbPbEY9sA84RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "WLME2JINTIQACAMD5WZUQ4PRJI.all.holesky.ethdisco.net" : "enr:-J24QIMYTLuDOO1F5w8AL_WGXU6CZmzi05W35Rz8PuVT8b6vUqmUatifQtzuL2Q7REuRY5kYNurUbLHscGx3j1zjTmmGAYrbbtBig2V0aMfGhJsZKtCAgmlkgnY0gmlwhETp6VKJc2VjcDI1NmsxoQKrwy6w-9JGp3dagVeXMZEd3iZGff-rEVTqd4gmkhBRBIN0Y3CCdl-DdWRwgnZf", + "5JAERI2BPJN45X7IK6SWZOZ2FI.all.holesky.ethdisco.net" : "enr:-KO4QEsCIXXCDLUsTbUOT3ILg3rpga0gZXaG9_YT9_kFk97iRwu_Pr4RlhDvdkg4Y4bl9SBVzP9bFtL7H9AU7QxE7OeGAY1lbo59g2V0aMfGhJsZKtCAgmlkgnY0gmlwhA3Xl1-Jc2VjcDI1NmsxoQPsZi79eOcjTI3Xa2GCBiXRCgtm7wFo19cVlmhQSZ-i64RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "J7ZIDKH7CKVIYY244QVU6RMPDM.all.holesky.ethdisco.net" : "enr:-KO4QN942TWYsHn5c2-vZhoeXwELvI2zVg2eHk6nzoyTN8Cwcb8n-4_cwQPIU5p7DsuOehtu-raiGxYm4vE_6WUhT6yGAY1ClkGgg2V0aMfGhJsZKtCAgmlkgnY0gmlwhJBbb1yJc2VjcDI1NmsxoQKemKJgEuvyzZofyTm82rbw716cyTA2hf6zSJ73kzl_ZIRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "FFM67SMD4N3SK4ZQTY2HOMU4F4.all.holesky.ethdisco.net" : "enr:-KO4QG4O1MBSzBHtShtqoXXeAycBqcDpuBK2BBwIuK_Qppz5OpJC6Ga3-qDEQJQOtbEYm0yXMhenb50Get_H6Bu9SbGGAY16lghqg2V0aMfGhJsZKtCAgmlkgnY0gmlwhA_rV76Jc2VjcDI1NmsxoQJ1vrJ4fwXiDAhSqIJwbELp2ktd9A0zRLws1li91-aBmoRzbmFwwIN0Y3CCdmCDdWRwgnZg", + "GZVG4EONSAY6M7SMLJXDCFUF5Q.all.holesky.ethdisco.net" : "enr:-KO4QErAUNKF003oZAYn8UUocPIuq2MTpxHV3gHfrNkThYPbZ366m-mlvXezUX5JDA5iJE-LTWWEPh8JqjEDah5kVimGAYxA2nMcg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFtWv2Jc2VjcDI1NmsxoQMCgMWZQXkPyMKdwnzdBxQe6rcs25guZtULvfzDjhE71oRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "3AVV65MYCJ7AGFUCKXT2UX2DSQ.all.holesky.ethdisco.net" : "enr:-KO4QEaesvQ1kySNS_mnQfiBr4DZQmkhHmqKEGRQvgyd5K3lNDm3vjs9cMMVTY9FgENpP1o0UGBsapNNfvXIN2gaudGGAY3ulUKIg2V0aMfGhJsZKtCAgmlkgnY0gmlwhDIjTkmJc2VjcDI1NmsxoQNVbUX0SvjLX7d7psFtTTNkKT7x_zHl3f4bSVQrkbK1YoRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "QDCV4SQTQAGHHA3DRA2TR4LNMY.all.holesky.ethdisco.net" : "enr:-KO4QFVXTftQWqwggnKZlVzFmnQz-U5jzIx37kQSltdvU5kuYQHT1mboaEBh92lS0BUvn-aBzLmHu7wX6hhs2xdGw-6GAYrk72-Hg2V0aMfGhJsZKtCAgmlkgnY0gmlwhCJSHduJc2VjcDI1NmsxoQNGPGIxN3h_R3TiUe0Ud2WazTigmsK028DXXuVLna9rtoRzbmFwwIN0Y3CCdTWDdWRwgnU1", + "366Y3UIIKK2G5CUS7CHNPZMPEM.all.holesky.ethdisco.net" : "enr:-KO4QK1ecw-CGrDDZ4YwFrhgqctD0tWMHKJhUVxsS4um3aUFe3yBHRtVL9uYKk16DurN1IdSKTOB1zNCvjBybjZ_KAqGAYtJ5U8wg2V0aMfGhJsZKtCAgmlkgnY0gmlwhA_MtDmJc2VjcDI1NmsxoQNXD7fj3sscyOKBiHYy14igj1vJYWdKYZH7n3T8qRpIcYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "LHFO3FXNRHB5B3YSTKUALGMEAI.all.holesky.ethdisco.net" : "enr:-KO4QEuoZibAJnKlhcAENpJiju6nVH5J2or_xnVnX_eputCuTHBVtNJmoRKgDBU0idG4kbxFPPKaWRQ3TdYeIbsAAV-GAYrax-PMg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKIOJc2VjcDI1NmsxoQNCYX5kXJqI2deaVOtsTmUQka7dIocapHSAfnNn_CTL2YRzbmFwwIN0Y3CCdmCDdWRwgnZg", + "3PMAED3BC2HUS7AMEPGJDKENQY.all.holesky.ethdisco.net" : "enr:-KO4QFMsKiZk64UQsyLVHrxTpTpmdZYzuI-Xgy0gut5NgLulRvbrgQDmdrAT2PQuxK8K9AiplRzJ_i8CtseBD_d2bwGGAYx9HQirg2V0aMfGhJsZKtCAgmlkgnY0gmlwhLBn3miJc2VjcDI1NmsxoQJSdj6d-7J6YsK6vZwRl_zzMSCKauLadYxL8zfjjzOQZYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "4PFOQY7RKTNLRPXQXKKXD3K6LY.all.holesky.ethdisco.net" : "enr:-KO4QJ8BuQCt7gBXU_26FNx8waHuCzCkG54mErQoBAJrYaw_bdUJT0yQA0vG8gr-dUoS77OX0D4tD-R6e30832ncLMaGAYrbEjQ3g2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEV4tiJc2VjcDI1NmsxoQJHEsWJ3CoSi-1JgkeSogpyRqYvBaJI77-soOcbmB_nyYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "GUBLA7OPRPBMYMQ76EXDMB7BHY.all.holesky.ethdisco.net" : "enr:-KO4QIFA0MiYPPyzvlUqi5j1dL1RGz6MdFhhLN30iXeNc1JfCS1QqTJciby7ZhlQYwFdVMuk_ptgu530WxiQR-UmZpWGAY2ia8cWg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEt3l4OJc2VjcDI1NmsxoQJRcQCiMhxUex-gtsxf2IWJ6nGita_F8BaPZxTwE310WIRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "VN7EWW4RMUZM6KUOQ43GJ33DWM.all.holesky.ethdisco.net" : "enr:-KO4QI3OPqietyANJtQ9_RUOU6ELbwDgTn39MufjLJ9BEzkwTxEgDcgYekeisrchGWzrzTC8T_2zowbuQhZlTkqNXB2GAY39ItF5g2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFsBbiJc2VjcDI1NmsxoQLa2k_Jy-sjrZ-0305yX_F1ZxDNNfCKIImoL5tyDN3cEYRzbmFwwIN0Y3CCdl6DdWRwgnZe", + "FDXN3SN67NA5DKA4J2GOK7BVQI.all.holesky.ethdisco.net" : "enrtree-branch:", + "QDMF4HIR2UEXRJVOX4UJPJTPKA.all.holesky.ethdisco.net" : "enr:-KO4QCeMBK9Ur5AHbkdov7TdALRsHzc48RXNtvR861fcmcxPYH59PthmWiT_pQHfVOH3x9-HK2bG8_h2jm46sZyJ6UeGAY2ZCasag2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFs7nOJc2VjcDI1NmsxoQLT22lM9abRweZb93jeMd4jvuRsXnjQFT2oY5a0rb0cvoRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "3JSDZJCCWKIEBGPH5EI5VZF4RQ.all.holesky.ethdisco.net" : "enrtree-branch:4PFOQY7RKTNLRPXQXKKXD3K6LY,3AVV65MYCJ7AGFUCKXT2UX2DSQ,5JAERI2BPJN45X7IK6SWZOZ2FI,YUZZDZD57PTNEIB5QL3FEXK2ZA,TMY2W2YBNCXUUNA3Y7QXWVQLRE,MJB42632KKOPGJ2KNY3GPKP66I,QXRV67JYJNOYJDPC5CTKUVG76M,QJM2RVJT7USPDSI5VVFGGDW6JM,I33RZWBACJHNDEXG2RRXMX", + "A63OP4WTCB3HGDZE4NGDEID6Z4.all.holesky.ethdisco.net" : "enrtree-branch:QXKEJG4XZEQSXNUY7JJYCATPEI,HIDVATDVB36L2MASAWA7SBJAII,CHFGCI2RQS3XFN2MKFP6G2ZM4U,GSONRYZILMGUJEN3PYQXYD6GYQ,OSHAABXYJSRWW35VOMYHZUKJXU,RUJF27NEYDBNQAFMI6K6SKDVRY,TXAFU343ACQLSVIMT5LYG3W2AE,R6EQA5KQEQM77JJXB4BHHTDF6Y,CF25LCQ452FBTR24CZU4C2", + "N7HAL5M6HNZBGTWM3LWFDRX4WU.all.holesky.ethdisco.net" : "enr:-Je4QI2f2MAXFFi0Q3--GhXVZwNP-jI-G6XwmiDXxT-iobTTKOBJCyWUPk5WSoTtRS2ABqJmTAK8jflXBWLHuY7AueRFg2V0aMfGhP1PAWuAgmlkgnY0gmlwhDmAXJmJc2VjcDI1NmsxoQIc4YD6HsNIoj2HVJJDzr3cSfNtcEBRLtk5kgwXNH0q8YN0Y3CCXoeDdWRwgl6H", + "QXOF2GWVHBKMKW57Y2KSKWYNFQ.all.holesky.ethdisco.net" : "enrtree-branch:A63OP4WTCB3HGDZE4NGDEID6Z4,PL27G47LAASBPPAMXUDIJ3OCRQ", + "23B5UFB3JWETLIKE5Q6B2B5MWA.all.holesky.ethdisco.net" : "enr:-KO4QGjsmd6RDlGYHJu2JnI-Lf4TZ8s1yJtLDvvtp_Kw6xIwNn7_Ti_FmEHfWbaSyy0Icl4jlErvH-LrEmi9PCzB27uGAY0MEI03g2V0aMfGhJsZKtCAgmlkgnY0gmlwhNXvxn6Jc2VjcDI1NmsxoQOzxdn7S7O58IiaWGk4n3MsiB816GGv_bAd_PM9FkWeZIRzbmFwwIN0Y3CCVTyDdWRwglU8", + "TXAFU343ACQLSVIMT5LYG3W2AE.all.holesky.ethdisco.net" : "enrtree-branch:3EMD3PTQYRFVNFBQ73BYFL4AEQ,PMLO73ISEK4Z6IRPOSWIUUKF64,5NHWEAJHKSAO5DIIYAZ7MQJVSE,LNO6EN3Y2LJA5YOJMEMODAJFWM,23B5UFB3JWETLIKE5Q6B2B5MWA,HQ5S2EHH762PSKVTHWNQ6346NI,O25DXMPE4UEZ6SFMQRBGL2E2I4,2WHIXLN5QX3JEK3SSH7MXKTJMU,WQDLDQ7U74Y2HM5LHVCOCY", + "PMLO73ISEK4Z6IRPOSWIUUKF64.all.holesky.ethdisco.net" : "enr:-KO4QBxZ1JFNQEopzO-wMumFIw4fGHkuPZkuCLvgcz0X8L8yKWUkQ-UiS7-KHBCjGOby7yjR6m7m9dqoQvlHejBncSWGAYv2B0KZg2V0aMfGhJsZKtCAgmlkgnY0gmlwhA_ML-SJc2VjcDI1NmsxoQMEi6SKsKUVTnxDWKc1Go6ZG8o5nEapNJBaWAd_YC26R4RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "CDXRYZFKH6OUWTA2IJ2MYIMYPM.all.holesky.ethdisco.net" : "enr:-KO4QExy2s5Uh2eTEJtsODBEOcPZZN-CYY0WEr_8nA_uK43QV3iqmFzfXJ-29zootm5F-E-DrjzTObp2tw_klTXPRu-GAY2ZtbV8g2V0aMfGhJsZKtCAgmlkgnY0gmlwhBJ2VQmJc2VjcDI1NmsxoQJuk1X-4JUVQT-mguV7dSGSUC-RoxdnTyOmD-VrfRFQyYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "CTDMY3ALJA7FRKVS4MCTAJRRH4.all.holesky.ethdisco.net" : "enrtree-branch:UIGSNQMHERWJIZCP2OLUXSZ3KM,I56MJYJBMXTZZEPBQR6HWNAH7A,IZNO6JEFTYHQWWF3LJ2M66P7II", + "GSONRYZILMGUJEN3PYQXYD6GYQ.all.holesky.ethdisco.net" : "enrtree-branch:V4A7I7I2SFVUDFFH7AGPFXACBA,67FFSS5IYEYRGUYFEWGIJDIMII,NXU6DU7HEM2L4DN6G2VHHFT72I,3T4CNEKYCJP2PJ3EQVJSFKHH44,KYTZHQX2PSOGY6RQTCVTUKTS7E,GUBLA7OPRPBMYMQ76EXDMB7BHY,HGROZTZI7YPDW5F37QVUPQEBG4,AB27AIPX5Q5J6UAXQ6C2QFTYZA,7UIVPNORDS3RPPTLSNGYAN", + "IPU725BUGL4HIOTSOIH3KZG5ZA.all.holesky.ethdisco.net" : "enr:-KO4QLf3R97a5p3pRgY1AeN1DisWZUTnHbIMt2aPh0zvq8QIZguS9EiujNa71YXqJgxw7ztz3YmwJMWuPuhfWqSkxqWGAYrayxVYg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKIOJc2VjcDI1NmsxoQIHu5vOuFNLO8814yybwdh03yUQzfPTTyeK_X44Gz5jx4RzbmFwwIN0Y3CCdn6DdWRwgnZ-", + "V5LTO36DCXU2JCCSSJ76E35OFU.all.holesky.ethdisco.net" : "enr:-KO4QMLA20nV5gEPR5ugJWkqHSkU89wLsjSVlpdbjuXGueHRY_VJ7gfCBuPbnyxN3Y0rYJThJU7b7IAA1yBfcRx6Uo-GAYrbDOlPg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKGuJc2VjcDI1NmsxoQNZKgK-Lr-g81vUAH_bV24PDyOwNQaNPgAOBEA3qV3JxYRzbmFwwIN0Y3CCdnSDdWRwgnZ0", + "2SA643JTHJ75ZVPFHAKLXI2WXQ.all.holesky.ethdisco.net" : "enr:-KO4QEPFY3aDBt5F3VQU5qcGZED8Xr-J9v1Fi4CcGpx7q_fydrJM8h0RqAXSdcJbr03b7ysPaOR4mNUTRou4kjPsu7yGAY4QKOX4g2V0aMfGhJsZKtCAgmlkgnY0gmlwhCO4xmuJc2VjcDI1NmsxoQKaOskToHERfimE_ei_VPRsh3fimBlYooVb80PVt0k-aIRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "V52J3WKNVGG56JFRKYDLSZREJY.all.holesky.ethdisco.net" : "enr:-KO4QMC1Y7Fl44QyBr8KDFTyd1IQ_h0w67Igms87RDJeZJuXbc74aIVNkOeK0t3_zEJAT2spttSVu8tKDeV-cdiPE42GAY3KEqdCg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFsGDWJc2VjcDI1NmsxoQN24zvy5pQRtLA5_iKLgnTwXjI3T1KldFTOyp7Dk6dCRoRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "3PYSNKXWWSRBWV377EFNYINHSA.all.holesky.ethdisco.net" : "enr:-KO4QODvXk9lfhLNmdyttodPJQOIYQM36Rl9OZsFBZ4vMjaJcoJUXxTQCMqGLNTWmFh-1oEy0XoYKgPy4tywLZZcB8CGAYsolPILg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIe1OV6Jc2VjcDI1NmsxoQPj1f0OOW-g_vRGcihewV9-kcsQZdwXBHy4r8vwinurzoRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "ZV5CYUMJPF4XSTFHOG65622KEE.all.holesky.ethdisco.net" : "enr:-KO4QDd2Ia2HHxIk_v5zyXOs6LIkR7a3wVUmipLZnQTJJMZtTNVY5PnRonxr_GAQ3nqG5R8f-eiyoYprxN1XjsfRlnyGAY2jAkZPg2V0aMfGhJsZKtCAgmlkgnY0gmlwhJ7cbM2Jc2VjcDI1NmsxoQLx82Y08wCtO72z_znVjHxPl3hBz1YBlPokUpsLFKRHJ4RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "DQYYUE7KVEXGEYBH4V2QHI6P7M.all.holesky.ethdisco.net" : "enr:-KO4QBhk9j1k2tfVrwV4yVRS9jn2zwS9KKADlhqvMsZsqdXFZiz0s-vCAnqtkDfszY9peGDBiWulLdNe6KFuvqgxgy6GAYtHL5rAg2V0aMfGhJsZKtCAgmlkgnY0gmlwhDmBAQqJc2VjcDI1NmsxoQLXrL6FtBz248tpC2_DxJs2HsvtHdSoNsZIBwth_MMu94RzbmFwwIN0Y3CCdmqDdWRwgnZq", + "MZGQKIQKIOFO3W2GJURJUXVYMQ.all.holesky.ethdisco.net" : "enr:-KO4QI7ESDG0rx7lEU_FFFFkALgNr9roSDrKDOzmEdxTHwsxKj5ozskjEjwFPrShowZbzTTYCp-NRhhw3uhcmKCYy0GGAY1lomwig2V0aMfGhJsZKtCAgmlkgnY0gmlwhCUbQPGJc2VjcDI1NmsxoQMV_yyKYF25xvgoJARC1swzb0S0llZJEmMLRHoINSR81YRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "MJB42632KKOPGJ2KNY3GPKP66I.all.holesky.ethdisco.net" : "enr:-KO4QEBK_p981nqXnme4a2sYvHW2FOz_OwLwstllVRvE8w-dVnDBrHxVZg7XSQwseErDQpfUgYMnBPMzpsx_bDUui5eGAY09OSoRg2V0aMfGhJsZKtCAgmlkgnY0gmlwhLzWgw-Jc2VjcDI1NmsxoQM2fE-8xAAaBUMu7dwviv_y8osp0rYMaKtaGBnbUba_J4RzbmFwwIN0Y3CCdl2DdWRwgnZd", + "QY2XP6FTX7QKFNP6TNZ2MOHZKQ.all.holesky.ethdisco.net" : "enr:-KO4QFsIg8OnqK5HJhhCaEuUTJ7RkLcTAXnUOOcaK63Ra2Kya2HsYSRgM1jRyr9utt3ib83q4fsU_IOg7kuR1jf2u2SGAYray1Tfg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKI2Jc2VjcDI1NmsxoQJiD81UDxWESi3paPaopKxnoF5vf0GAxlroaPr6bSIykIRzbmFwwIN0Y3CCdn6DdWRwgnZ-", + "3A6T36TYZJBFLOHMJOJGL5SPXA.all.holesky.ethdisco.net" : "enr:-KO4QEVn1fwLQuOHSsRVS6bBIv7Nhr39Ze03wqUhwdgEn0IUeEB9TZAx6d9E-C0tBkQdEnbwld1YxEsB9gRY0Od13nGGAYzxMuKBg2V0aMfGhJsZKtCAgmlkgnY0gmlwhAW9up-Jc2VjcDI1NmsxoQKNkgm76tKKtumISB2-1oGEHv_mZtMVh7JGrSmvlX5qqYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "WRRS22MCKTZ4YS3ZLPUJEUAHUI.all.holesky.ethdisco.net" : "enr:-KO4QG6_DN_yg3WbcE7jc_tGXK9dLJPplfz0cbzCRwT8v5o6BBk_e_ER73cttVRyYkkvBDyw-akwTHYxjCe2Jp7OWuiGAYrbEiVVg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEV49OJc2VjcDI1NmsxoQKYHwvu1PazCFKpleCUgYvxialsHK6_iLwC_R2i3J70fYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "BAY6SKB2RTQCM7RTSHAFQ6TTBQ.all.holesky.ethdisco.net" : "enr:-KO4QIo--Wic2Dyk2II8x2uTPNOFY3CRZ8wmABP5qOhmZv4LXvAfbdISPp7HDrJwrejs032LHH5rhUY5l8bj9-M-_u2GAY3GDGQWg2V0aMfGhJsZKtCAgmlkgnY0gmlwhI6E2M-Jc2VjcDI1NmsxoQM46NJn0fjSRwgGkB0G1n7QQ9VyBDwiPXPX_xgM25tDOIRzbmFwwIN0Y3CCfEyDdWRwgnxM", + "5AOSND63QKPVT6EWNMALKAFC4I.all.holesky.ethdisco.net" : "enr:-KO4QKn6DlOQ0ybfLAfyPlyPssuWtP0Zu5FEUEgr3015XppiCP4yr9SeMgnpN90AVHJA5C61F3677GiO0N-JIXGfN2uGAYra2k7ag2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKJOJc2VjcDI1NmsxoQKgKJovbRS4hL3ugMVrevOCUGDS-ixgByq_tbh4T9oihIRzbmFwwIN0Y3CCdmCDdWRwgnZg", + "PL27G47LAASBPPAMXUDIJ3OCRQ.all.holesky.ethdisco.net" : "enrtree-branch:3JSDZJCCWKIEBGPH5EI5VZF4RQ,XTJ3PFTPBB3ATDDWAA6W7RRKMY,FTTHOFPRPIHMJNZA3VZUAW5TIM,DAVKEW6RQ5YTYHKCJ2A2J5GVCI,TGKURK5IPVCPXS4QPS2KZJCP3Q,RWRV55FT3DKDVGZK7AEU2DR77Y,CTDMY3ALJA7FRKVS4MCTAJRRH4", + "HQ5S2EHH762PSKVTHWNQ6346NI.all.holesky.ethdisco.net" : "enr:-KO4QK883RN_CIppuOvhzi0O3Qg-XzHUZsdKIEqLdvZMLN5BCV5QTqqxRzJjtXa7NZjBi22UQJUUlsrQ_3g3Uavv92aGAY1BRdWag2V0aMfGhJsZKtCAgmlkgnY0gmlwhFJkOnSJc2VjcDI1NmsxoQNAyMtRruAQcNn10ZReZiiZtPnk35o_oJEhabuBKi1gLoRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "XPTWTOASNO4WEBXH74WCQ5EYTQ.all.holesky.ethdisco.net" : "enr:-KO4QEk3I50HU8omZLCYgSwXs1caXS-q06lC_GnJT30gbzlIEDWBldaoC83sg9aWTQ9DdkQStosOuYEb13gxTwp4tSKGAY5DOiDfg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEVX7SJc2VjcDI1NmsxoQPCijWe7yQtKeDfuj6WXiJZ6CZ88FGXKj6DGSWdq0E0qYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "HOXANHAMPVCFA2QR54VEMWCWPE.all.holesky.ethdisco.net" : "enr:-KO4QELWztCXUz9MCxD8zbh65aa6LwP3wubhdXJVdlyrl7QzFJkUXosDXndbQqtAT2qD5nXAFpBnz5MeUSZf_GfjkI-GAY16vTc2g2V0aMfGhJsZKtCAgmlkgnY0gmlwhCZhyOSJc2VjcDI1NmsxoQMGwfOevbfZOGp3bL_2AH1SBdx3zn2_l56uG_4CWDf66YRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "BBNHIHYBPXVQTDZQ4JXK4QYDMM.all.holesky.ethdisco.net" : "enr:-KO4QCMGWCueZIoaBJyNAqOo_1wJ-QmbQ-qlTQx_rfz0gzKjF8VqFc99Q3ZMcfpK2qdLtWZHoRoiCkstH9_rWAU49DaGAYrbEkgdg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEVzHeJc2VjcDI1NmsxoQIy6rguhP2KemBUH2HJyW-GPo8prt6Ay9gjX3GugxLuGIRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "LHFTDS37XFOEXQKTWJGKWRLFWM.all.holesky.ethdisco.net" : "enr:-KO4QB0RN-qZSymbzZA-AlKT8213I60xJjVVwN-AWwjW9W9aLk-2aq8jW_jbgKnJY3c_wU26oBzYFBpOVcYKIV_nd16GAYwlWhtPg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMPJ9bKJc2VjcDI1NmsxoQKlc1OTSAanfHEsPtrKjZvkgQUuHFZon2GtnCRHlDRV7oRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "67FFSS5IYEYRGUYFEWGIJDIMII.all.holesky.ethdisco.net" : "enr:-KO4QHY48cNs9Lauv6W3XCDcuBvhJbmL-0MAASBah_V-jG6iODMGVIZ9xv4K3MHSqQQht3Fc39CjU5zoYROe6D8uOq2GAY2UXNmdg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMb0yKiJc2VjcDI1NmsxoQNjc7k9S4kjTIwltOf7EH2ZmK3vZiEBpxxNqondpU3qV4RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "MOBGCDYGQA4CNBQZDPN52JC5E4.all.holesky.ethdisco.net" : "enr:-KO4QDuar8SHEX1o_kUIKB0IajdUqcRb3ZkZdr5_MIxT1BUkLVk8AelLTv-_ioVGRAwNIJTzT4m8nyzV1VbsjjpeZ0yGAYrXy6Pag2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKEeJc2VjcDI1NmsxoQLy0OM9Ze8JjVHZhLQg7XejbI5iedlSDZNRmtQdTy_X8oRzbmFwwIN0Y3CCdn6DdWRwgnZ-", + "ERETBCE2BAPJRSWIEUQV3QPP7M.all.holesky.ethdisco.net" : "enr:-KO4QJY9GUtSCCXanmC9p7LYAfqk7jOUF-zxl7zd2ce5lD_aSNk9KfzBbh-Hii3qMzv-2x4a0Xal7RQMzQv9BqCkUMeGAYw1cGzgg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEVR7SJc2VjcDI1NmsxoQPQjk4qnGis3WKuSQDD20H-9n9xKFTbnLsB1DhAteyYk4RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "5AHKPN5NN5IHEH365X6OYZDHDE.all.holesky.ethdisco.net" : "enr:-KO4QM-W42wfRYcTwaegJy3SfjlCkZsQU8LhGcluTdxtFjqXOwsEE5kUewkPMe7qIQpwb2MAo5xTeRpnijbdYTaG37-GAYtxMJdhg2V0aMfGhJsZKtCAgmlkgnY0gmlwhKh3IxmJc2VjcDI1NmsxoQNDrMpeyQI2aO_yoNP41RAf_BHtckptN1l2QIWxgDKOC4RzbmFwwIN0Y3CCdl-DdWRwgnZi", + "FEORTRPBZJSNRB3XCLK7KCMACQ.all.holesky.ethdisco.net" : "enr:-KO4QDO4oH5cjhncisJSk1SyGmwJ5VFjNetXw4OSSqDKc0NMDGXYYl6wUjbhcvzADYDpE1Br3lRG-1xV6iA1OAyXSAqGAY1W3CQQg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEVfkOJc2VjcDI1NmsxoQKkjEwDL0wuG0AH3RWw1wRrYHdPa8OOL1Ko4DYcZeQjQoRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "AB27AIPX5Q5J6UAXQ6C2QFTYZA.all.holesky.ethdisco.net" : "enr:-KO4QNZrQF9iyry46odExoUh3kkHjWWHS0iEEjc8n2R4Orq8Rov4Ar3ozsyWt9Z-dmBtpTfmAo3UYspLFcvtHNzwjPmGAYwWbywRg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFtbbyJc2VjcDI1NmsxoQLD-zctKWrecek1fu3o2IKHUY9rkd0BYeQHrzpqElk_1oRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "NHLNHXPRKOALUKH4MZ7CIVDONU.all.holesky.ethdisco.net" : "enr:-Je4QBgb-GKZRRjU2TAE_9KyBHC8ImLYDPG8KShRKx8gbKF3F-gpoHiyeprH42weMNSg6i8GeG-n0SxwWJRe6zyXj88og2V0aMfGhJsZKtCAgmlkgnY0gmlwhCV4sKqJc2VjcDI1NmsxoQNuVgNhx7pgtJotEBq56F9YFom-6szZ97gCkea35_lDkoN0Y3CCdl-DdWRwgnZf", + "XPX2T64BHDYLVGT5UC4GVIQIVY.all.holesky.ethdisco.net" : "enr:-KO4QMlMC7rvmM9tvHXvwo2Vj5x5FePlyvH_6lBSCgZHN8HcWqtppk1peQlK9Ge79ma4j3gx_zX0hgJvprIy1ehjXXCGAY3Mer4jg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIe1FCCJc2VjcDI1NmsxoQL3IIF3eENfSsr5tih2ebcm3dWL-otLdZYqUvfJ-5_9U4RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "NZCRGKUE7ZXSIS4MIQ4WUS76BA.all.holesky.ethdisco.net" : "enr:-Je4QDqwqlm7UBywmVlR5UkkrQrg5B3UpkFexP7ucg4RAsdlMiJ4J1S5jT8jp6je6igMZ3OOnggdpd6l7QtQeNBICycDg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFs6LuJc2VjcDI1NmsxoQMgMreR2XspGJphg3fToxGKcMwWPE3e0gyxcqiQNLQXNIN0Y3CCdTuDdWRwgnU7", + "OF3GRGFZUAJTH3T2R2EXSYAZOU.all.holesky.ethdisco.net" : "enr:-KO4QDPbolwwOMTwV02zqyVgJFxko-HI8UWOT_K9sDcWgHG9fRDWkbSbTw_l3bcIA2-q3054lrJ9cbkHABlMZbQskUKGAY1y5VBWg2V0aMfGhJsZKtCAgmlkgnY0gmlwhKI3BbaJc2VjcDI1NmsxoQP9bM-ylBjwT9ujG09A__0to5F3Qw4QSvY4_vgyQ0A7j4RzbmFwwIN0Y3CCVTyDdWRwglU8", + "7QEPEVJFOCL5A63GZLZ4IZNMOI.all.holesky.ethdisco.net" : "enr:-KO4QNGh--C3ckyCMlUP4u86lt292CLHOQ3YIwk5Jqzz5x3OID_7BfRn5qN-ZBoBY80XOhFyHuo4eKDCIowQgYMvVY6GAY5S1aoSg2V0aMfGhJsZKtCAgmlkgnY0gmlwhFhjBqqJc2VjcDI1NmsxoQKZpCZSQgSun9pLzGrdfLboslJbD-JxtTsR4JF860eZYYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "Z5DOK3IB5X3IBOVVIK5MQCDYNA.all.holesky.ethdisco.net" : "enr:-KO4QJjEv14HV0BgdPwIa0iO-xrrwoi9k9LJRw3utIUVib5dF6s_b3y8z0NP2VyUGsJDFOwuVQq6OC6hb5_hFUqnf1yGAY12fbSog2V0aMfGhJsZKtCAgmlkgnY0gmlwhCU8-mOJc2VjcDI1NmsxoQIT2maaVlQBw4oD4loM1Q7gygn42pTQn1Sydo74LtZAZYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "CHFGCI2RQS3XFN2MKFP6G2ZM4U.all.holesky.ethdisco.net" : "enrtree-branch:XIOWDM2BTMANILG2M4LCMO7W3M,4LPHENNNENCUT7P3MWTB5IFRTY,5NQ4MKTYBPBMTGUT5IKUAXEGBE,CVK4DBIVPD2JPJOPQJQX77ZAWU,WLME2JINTIQACAMD5WZUQ4PRJI,DQYYUE7KVEXGEYBH4V2QHI6P7M,45V6KFI4NL43JFTJHL77KP6MQA,QDMF4HIR2UEXRJVOX4UJPJTPKA,BMVEIV6PNSN6RK5XEU4R4K", + "3EMD3PTQYRFVNFBQ73BYFL4AEQ.all.holesky.ethdisco.net" : "enr:-Je4QFiHIu4B3YbbG75mlr58JRaqpGUQij1n1pN3zaHoWbGrShq8aYqipbkPLaINPTX02YBfchyACgLHgH9cMarIUapQg2V0aMfGhJsZKtCAgmlkgnY0gmlwhDNRao6Jc2VjcDI1NmsxoQJHJlekJODLxBDPwpVLBtTMizLq5o9JtXkD8MQpqAIUYoN0Y3CC2fmDdWRwgtn5", + "IZNO6JEFTYHQWWF3LJ2M66P7II.all.holesky.ethdisco.net" : "enr:-KO4QLmjLLs27xB9n9N2TNC32EhbxjdiR32rroemDty1dQJsBSZQOawi3-HGqWDRifXce49pv2WewtuzF5j54iiUfbaGAYrbEiX_g2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEV5riJc2VjcDI1NmsxoQO4pbsV79dSOLkBaOX5vcv6Amy9rH2xg1wW3OsKxVA7PoRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "OA4ZFFGZNFUC3LIYCUDJOPVR6U.all.holesky.ethdisco.net" : "enr:-KO4QFuTcCcZkhGlEZ9YjHVURcwzRDKDu_o8RBID9eF5lmG9fb9ZHBHnz0E-tXO4TcPfwxEtefRAaAVPj9Ycdi3OeXuGAYraylBgg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKFKJc2VjcDI1NmsxoQJRTkcLLW6c3J6kd1b1siWyumgXACvQK7ViQjsgMFx0c4RzbmFwwIN0Y3CCdn6DdWRwgnZ-", + "ZCAW4EMBOL5EH4MSMW7FY6JMEE.all.holesky.ethdisco.net" : "enr:-KO4QHc4CzkY6KkiTGR6hTovOeJ8VJuUtjkq2-UMXwUijiKvAZOFVS_Nn_GHMVZ8Ppsu31rSLYAEFRu0tMJ-LIDdSjOGAY2sYroCg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIrJyaeJc2VjcDI1NmsxoQN7HHah7WnGluJb0mqKelBJ8rpvKlBb9t7848BwJ4qau4RzbmFwwIN0Y3CCIB-DdWRwgiAf", + "CVK4DBIVPD2JPJOPQJQX77ZAWU.all.holesky.ethdisco.net" : "enr:-KO4QH3RGFoO4KTuq8gxJC_mjnPBqNaDvoX_xsuYbDOqvJA4CPJs6nifZuqE2sTB3O-kHvEI0dbFJ87FoWkKhgmXYuOGAYrbEhVBg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEEV7HyJc2VjcDI1NmsxoQM1AMXary_Hiw-yesOw24q7GwMH_DmNHHitFPU46foJ24RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "O4SSPZG3DFA7PHZ5L42DVOPWDM.all.holesky.ethdisco.net" : "enr:-KO4QMrNy68H-dYJbPM7snJ1UrV-TPXA012sdna6WktkfksMED9fE3g-kzp7gBgOFEdFi1KIiEv5MXXAoOt1KOMVc1eGAYw66STEg2V0aMfGhJsZKtCAgmlkgnY0gmlwhF_ZxOCJc2VjcDI1NmsxoQJUhK-aKtl-Gc3ZnfF2k7oLFXmpGCHEi1_fBxAEfc1P54RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "TGKURK5IPVCPXS4QPS2KZJCP3Q.all.holesky.ethdisco.net" : "enrtree-branch:E4GGBOZ5L2UFPAKJIPLNBA2MKA,OF3GRGFZUAJTH3T2R2EXSYAZOU,D4GT5QUSIVDVPURCUOLE4WITZI,O6EQKGUJOO2BQGMADYZHOSZXLA,WRRS22MCKTZ4YS3ZLPUJEUAHUI,3A6T36TYZJBFLOHMJOJGL5SPXA,XPTWTOASNO4WEBXH74WCQ5EYTQ,2XMYGJOCRIHHMHAFVRH3OES5QY,FYJOHHN6LBARRNNLI6SK42", + "UIGSNQMHERWJIZCP2OLUXSZ3KM.all.holesky.ethdisco.net" : "enr:-KO4QEvn7KsE7UDJ5F3HwtSmGNSy0JSYDLfp9uJRtq5WfSsuOdkfq3V7GVSDo7IfCTXYAtQfRwT9q88II0sajv-m_CKGAYulV6JLg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMwQ9ISJc2VjcDI1NmsxoQNVsiSzpuxjNDgNToi4u48-5kCgvsjuoUsvlG9VrnEjhoRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "3T4CNEKYCJP2PJ3EQVJSFKHH44.all.holesky.ethdisco.net" : "enr:-KO4QKNzTVC-wa7whLxFFss7NzEFDdVuAjgEebNGGK9xTWKjHTjqmMNfY4_h90iqlSYM_LoGyH2FFXhbHT05zKyALMSGAYuqLQ8Sg2V0aMfGhJsZKtCAgmlkgnY0gmlwhA360gaJc2VjcDI1NmsxoQPSUi99W8lkrquAhLsmslwXU4-dxxVfied-w_yqYEXpU4RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "5ZWIGTGKMFAFF4LAODABANSBOQ.all.holesky.ethdisco.net" : "enr:-KO4QFP5g9U1YtBLeJi56xoBll8E4eUJeAAYMoEg1l0xPXRybh0OYNZfteBjkHMwbucccNdGB0amzNR4XtxmAfjoFWKGAYrbEjd8g2V0aMfGhJsZKtCAgmlkgnY0gmlwhF_YY_eJc2VjcDI1NmsxoQJb-JLk8MFQQPMz3QI2ya4FtZdHqZ1Nm0xD4MtOYc9ifYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "7J7D5R4SH6LVDCFZ5ELOP46IP4.all.holesky.ethdisco.net" : "enr:-KO4QArnrdy9hfW8KHAwokQf-x0LbdoDQ4WoK0H2dXqhP4D-TflLF_Ywf2lh4ybXao8jjNSxC53MzmFPUI9A8k06XDqGAY2KrTRtg2V0aMfGhJsZKtCAgmlkgnY0gmlwhLWkyzGJc2VjcDI1NmsxoQKSAHhJOTtkBlasBECB1mrukuL6jqOEtxWkpH-PfDp8h4RzbmFwwIN0Y3CCequDdWRwgnqr", + "5NHWEAJHKSAO5DIIYAZ7MQJVSE.all.holesky.ethdisco.net" : "enr:-KO4QMiOfa0JolrO470F-mRKslpq85BBGRz_JjoAOOnLuUdxBObP7579igrB-YGSdaUtb3Ih9QXBHRAl0PWYmE5NW32GAYtIvzSsg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMF6nk2Jc2VjcDI1NmsxoQI57ZbbGPYfl0DtrsmjeKDosRG14eV2qLI8AE6FsP2oy4RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "OGKXEQEVGNJ574WCU7KFNTXNQI.all.holesky.ethdisco.net" : "enr:-KO4QH_WNLhz4qmL-EOMCQFfhE3QOOvyG5uyX0RR77gYrsdcJnUxS5OBQWyMqhRQLO2P9htXeBxJ4kR9ez13qQRiBp6GAY3m5RB8g2V0aMfGhJsZKtCAgmlkgnY0gmlwhIj0RveJc2VjcDI1NmsxoQJFRRKo2aP4qXB8sZnA6_rJqKgo8FZGT2i3ipEzwXCayYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "D4GT5QUSIVDVPURCUOLE4WITZI.all.holesky.ethdisco.net" : "enr:-J24QAgjCyFyNsDJHJwpo6uy0l7yqJD5r6WF58M1T7_PqsyKC0t6y1iTsQX4Crz6iv5Ijc8LqPZIC6vhA_F5GqkdkgSGAYtfbIIbg2V0aMfGhJsZKtCAgmlkgnY0gmlwhCvIu-OJc2VjcDI1NmsxoQKq0Xs_18RfgehL1rawMx2B3VMaH9d-YNCY5kmHuaCnqYN0Y3CCdl-DdWRwgnZf", + "OOQAXEUVKR5EJ722HM2UANT3R4.all.holesky.ethdisco.net" : "enr:-KO4QBU5Jlmco2sEPOXNEctSfBJfxdgANrWXVLS-BVDQYrU3N_oaeceDQIH82zZ6BSi_F-FQpSKhEVYtPDxI1BA4OW-GAY5bg91Qg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIrJH32Jc2VjcDI1NmsxoQMXPocPKx0wRocEEzASwUfzDLKd1f-Jifv2pyIu2M8vaIRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "ZJOONJUDYAQ53XOJU4KG5W4ATM.all.holesky.ethdisco.net" : "enr:-KO4QBGQGqvIoSv3C7d-bCiMst5GGEvIQaQ5pAHs5On0MeNfcVjcZgd72l2UUoqf13HoKHUnVG37cmHw-11H4Y59F6iGAYrbEhVng2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFtO36Jc2VjcDI1NmsxoQJ3QRuoMLUn6djLfYCFTKSV8kUWRQTOethOOhLDW6ezdYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "INA5QYTPU6WIOBT7IC7U3WGDIY.all.holesky.ethdisco.net" : "enr:-KO4QDpTzc6voIJiaQO1T8cbJvPej2OvifSuuDkQVrAcC3imJXT9J9zt5DelQaMAeu_uk11kFFgYJHBAE88J2cvxYGqGAY1olkLcg2V0aMfGhJsZKtCAgmlkgnY0gmlwhLnRsD6Jc2VjcDI1NmsxoQKfdroTKrg5QqtuIyfF7LivDR4GjeBnA8xQrAg5ma4pzYRzbmFwwIN0Y3CCy_KDdWRwgsvy", + "HTMBFBAIZBACDG3EZZ4JVANCAY.all.holesky.ethdisco.net" : "enr:-KO4QHHstlNCTI8xMnZ5mcIRvd8lfo9YoVeyv4pqvhtkxGI5PhYqDDqu1W5XqHWCMcTt8fpVgu9KojZHcyi7fhQhPdyGAY2_zUcvg2V0aMfGhJsZKtCAgmlkgnY0gmlwhL5czy-Jc2VjcDI1NmsxoQJpsTl5abUECWCfSno785tWBNPiVCJ7GG4eQTf2Cf1qBIRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "KEC4722NVHE3KX3IYNZC34C7NY.all.holesky.ethdisco.net" : "enr:-KO4QMjtE87kVQpcuRPXV31w7fsvyit4Fw995wyZ_h6uK8atWGOhXpsi3xXsBQLgJFBWmNHgTw-V4JvZNFZkFHLU_8mGAY5hRoXMg2V0aMfGhJsZKtCAgmlkgnY0gmlwhC1Mp4CJc2VjcDI1NmsxoQPETNdXN5Cshfr8-rIIMev6E_MGqD-jzqcXW2yfzT3C54RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "LNO6EN3Y2LJA5YOJMEMODAJFWM.all.holesky.ethdisco.net" : "enr:-KO4QMIX_--Ar7-XnFcUVlJpBBUfZdfNOftELgvHhCGvbLGTMT_Yqs2M15BCG0qoHsYkVBP5b4wd2wqNyNIfFaTtQ7qGAYrXxOTEg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKEeJc2VjcDI1NmsxoQKG05NXftvaats582r82_zpOi2C54WjSeYwxKfSNeSJaoRzbmFwwIN0Y3CCdmqDdWRwgnZq", + "R6EQA5KQEQM77JJXB4BHHTDF6Y.all.holesky.ethdisco.net" : "enrtree-branch:5AOSND63QKPVT6EWNMALKAFC4I,WAU46WR54TX2VBK4QM7NFTA73Y,BBNHIHYBPXVQTDZQ4JXK4QYDMM,OA4ZFFGZNFUC3LIYCUDJOPVR6U,KMY5HFBGCUDWN2ZXE6QNUQHGYY,OGKXEQEVGNJ574WCU7KFNTXNQI,CDXRYZFKH6OUWTA2IJ2MYIMYPM,IPU725BUGL4HIOTSOIH3KZG5ZA,G4MENE2MCGXFPYEBF7SQEU", + "45V6KFI4NL43JFTJHL77KP6MQA.all.holesky.ethdisco.net" : "enr:-Je4QEedukMKHefwNNtNam8wvx8_0GMFHLC9nA-TWWPMTZ9FD4C1CrjqFxLNruZPGL4T1ApKPtes_ApKbwwOEc4dNxIDg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIrJ_vOJc2VjcDI1NmsxoQIKnge3StO67CequDTr_QwD_V_qhS7TnGO-tgE472AFroN0Y3CCdl-DdWRwgnZf", + "5RYAVZN3SSZZQ2KISUO7XDBPE4.all.holesky.ethdisco.net" : "enr:-KO4QGMFa1U36mwISUTSlJN8q2EKJSw4WHa6PAMrHkhL9-naVdAWmGuwfpel1E5_NBSnPWRsG8FzmmUH61iq8fodXcKGAYrbEkT3g2V0aMfGhJsZKtCAgmlkgnY0gmlwhEFtb_iJc2VjcDI1NmsxoQJ62Txw9OKK7DTE_CZRC6tX_223dks-FcinnSL1uXUbTYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "KMY5HFBGCUDWN2ZXE6QNUQHGYY.all.holesky.ethdisco.net" : "enr:-KO4QLBXgetH_VHZgzalnkfF-iibF4km3OCLzY1TK27U-yG0ZA7Z4C8DL-da5x65khpgvOBa8c2CXffZBdTtD2BZQCqGAYrg47Ahg2V0aMfGhJsZKtCAgmlkgnY0gmlwhJT7RHyJc2VjcDI1NmsxoQKeRdcysv01DZ_Mzk9t1jcWW-YTjP1fPIrHVpw9hc_Q_oRzbmFwwIN0Y3CCdn-DdWRwgnZ_", + "YUZZDZD57PTNEIB5QL3FEXK2ZA.all.holesky.ethdisco.net" : "enr:-KO4QKuFVKbKbtK1djMy6q3TLuAWYDAjoC20cYPdhcOk7PhUdA67TXr9vTGfZZ9AO7ivmQZyRW4w9TFk-29_xP72bkKGAY2EAMvIg2V0aMfGhJsZKtCAgmlkgnY0gmlwhF_ZKGKJc2VjcDI1NmsxoQN4FBojS_W4gviNwaTDsIXiBEGXaQMtUzxQHwKWDmMR74RzbmFwwIN0Y3CCequDdWRwgnqr", + "2XMYGJOCRIHHMHAFVRH3OES5QY.all.holesky.ethdisco.net" : "enr:-KO4QAMrUvclx-EHZrO6x4K6fWTwaKh2zS5oWJzVU6oZuzZ2GG5xdQky8tIrpUUSKGB6XGvr8TvmiS7Dimvxx9Hb1OOGAYy06xQvg2V0aMfGhP1PAWuAgmlkgnY0gmlwhEFsSLGJc2VjcDI1NmsxoQJiz0q4ywyhHvmXBrWbFykzflq_J6cmLFIbepO9PLBwhoRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "5NQ4MKTYBPBMTGUT5IKUAXEGBE.all.holesky.ethdisco.net" : "enr:-Ke4QLrsveUYt2tacm5EZETFc1F3EyvNYfRRkRhljyeLMIzccRlPI1kKmBWuELQs5iAIRZgv92P3Fxx_zJ3xyUbN3muGAY0x7p4Ng2V0aMvKhMYaYJiEZRbqwIJpZIJ2NIJpcIRU94PXiXNlY3AyNTZrMaEDBxF5W6guB9qZoR-c_zuDUE1UHyaH0FMjJKHC9Jq4ji6Ec25hcMCDdGNwgnZfg3VkcIJ2Xw", + "6WAXAZL7FYKEJBZOPWIHCI4F6E.all.holesky.ethdisco.net" : "enr:-KO4QCagHy1Q7UED3AJfgVHxFKOn_DRS8UTUg0okE7fW8VTcDCW7wlzWufdWCtdUwHpnaf3EZpRn2YRuo9u4LS-Oh3qGAYrayS-wg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKI2Jc2VjcDI1NmsxoQJYC74L06jkGeWkmh1aSjeBYVvzDCWEvtbd111Vu8WiG4RzbmFwwIN0Y3CCdmqDdWRwgnZq", + "ZRVWX74UFHUWOPCC7LOPLYGIZA.all.holesky.ethdisco.net" : "enr:-KO4QBfm8BEr7OIRv6UT1t5mhE2szefodQuIIBKMFnyvbky_K-wff-l6pqLwfLyUblqFNxqM6Xtcnari4ItzO9236DuGAY08Swa9g2V0aMfGhJsZKtCAgmlkgnY0gmlwhC7r5ZiJc2VjcDI1NmsxoQIH5RAmLOvYi50mYcMnglYJlz-EC0E2YsetPVUzvbs5BYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "2WHIXLN5QX3JEK3SSH7MXKTJMU.all.holesky.ethdisco.net" : "enr:-KO4QF2wWLadNPZ_qwOIZkLxPEJjj3N23892JKdtSOeeC-X-V0bZsjQ3QqpidJviGT0WrBakFANcbSa9IqFSOslJLV6GAYrayMEMg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKIGJc2VjcDI1NmsxoQKQ_STV3aCaM5we4KxMPMpdG63xPZieKw8A36OR2tuouIRzbmFwwIN0Y3CCdmqDdWRwgnZq", + "I2Q5U7BDYSM7O3O2IKIRL3KTZY.all.holesky.ethdisco.net" : "enr:-KO4QIvQh_txoF6gwXTRsMzzvHPdwQrkCHQYt4egwTWqUNQiMQh1yJ71U8UXs8b9pP9R1mUDTaiQ7-e0eB2Zbvouyb-GAYvSz8f0g2V0aMfGhP1PAWuAgmlkgnY0gmlwhJVmk6SJc2VjcDI1NmsxoQIuT88b-jLJBI40hV4VY3YeJnmYT65v2QPjyzuNG5dyF4RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "NQHD4WNRRGE5JHHX6IDKGIF7KU.all.holesky.ethdisco.net" : "enr:-KO4QMTq7zJT6fLS-LY7n3hWmcwJqxRJraGJcM60U9Cej2lRRE-u_KhOd9r712IBEOI39uiVQEXGdBZvc-dJIESUcOOGAY2dUsseg2V0aMfGhJsZKtCAgmlkgnY0gmlwhFD5eBSJc2VjcDI1NmsxoQO-UyE5ad7DaM-f7fZi4QEwjfxIR6HS-l-lTSlv4ZLdB4RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "PX6K4ZETH5UX56IKHKL5TNOOMU.all.holesky.ethdisco.net" : "enr:-KO4QJqslvj0RYuX2CI61yCK7VuyY9Ik3c5EHtpkKehriXyqchhCDMx4sRuewcCAiWO_frsUevV9GXMPnsO2nzCrgFGGAY25poLHg2V0aMfGhJsZKtCAgmlkgnY0gmlwhKfrsZeJc2VjcDI1NmsxoQOe9l9K5UW0RAuQTH1q6CwG_UzbJyKLGSt4lUt5_tVmyIRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "CK3HQLGRQATDVF5CEGHX4DVQRE.all.holesky.ethdisco.net" : "enr:-KO4QJy9FHDJZfMqgGGIVGn-j0rHI6JDjTWEo7sN1mBkzO68ZId8jkRQfgZ5dJkv4GGm2rsvrWk6OJYF0td7n0Ve9GqGAYu0QPj0g2V0aMfGhJsZKtCAgmlkgnY0gmlwhHTK4wuJc2VjcDI1NmsxoQIYyd-9-wdwfPPu6uRcnTKkQaCekdhR7488-2pxl36AZ4RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "IDXHFSATDDENJYOYJC2TUSUYOY.all.holesky.ethdisco.net" : "enr:-KO4QNWpVQT5pw-oUuoa7JDOVs1KheGaPRAR_fIarAflnc1yHawwEpptaB5JWkRXgh-wJtk2KlHE5zGICXefHD23Z4qGAY2-EYHHg2V0aMfGhJsZKtCAgmlkgnY0gmlwhK35GlyJc2VjcDI1NmsxoQPV0hDwmz1HaZ4pLm1hzee38NMugxA_7j3v6hbxP_LRCIRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "UYESJQUB2BHP2MSJNUSDRGXQ34.all.holesky.ethdisco.net" : "enr:-KO4QB9gKzGbKvqoPJZymq3AiwZvWSHOup-36D8ec3CwNZDFRoEmp64UmWaP0St3OXDIV4Q2Rm0MnelRnAjvn3HRfyyGAY5EPB9Jg2V0aMfGhJsZKtCAgmlkgnY0gmlwhIrJ3_qJc2VjcDI1NmsxoQKHt6N7ehDnwT8_lFh-C4KgqUplYwDvYy6rOP6DFRO8lYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "76OAILAEE52CRVMYFBIBMSEUXE.all.holesky.ethdisco.net" : "enr:-KO4QDRyM88zIGJ2P0Ya1W8fucbp8kRTbVzV8k_2eawB0W3ZHMQ3o0HlbXVw3j1rMgN167LmsaMt1iO015HMVKGT2GqGAY0rjdqhg2V0aMfGhP1PAWuAgmlkgnY0gmlwhKFhcJuJc2VjcDI1NmsxoQJ2emahe6-2fq_hQqxm99rgYi4TSzQ1ky4utO3Tcpe-yYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "NLYAF5XVTTQUOMK4LYUF3FXNPE.all.holesky.ethdisco.net" : "enr:-KO4QMiia-4vyLiHQQhxlbmCKdOTRqOiZin-BdAdi18LxNqqHEiuIQQ-F1dZu8sKi63vEk9zwr5gfMnxXQZCThEaiDSGAY2UmHG4g2V0aMfGhJsZKtCAgmlkgnY0gmlwhMb0_ESJc2VjcDI1NmsxoQLb1ZwjPQw7AjCvlHQpt9bmePVD85rbHbnFZ0naOB1RNYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "QXRV67JYJNOYJDPC5CTKUVG76M.all.holesky.ethdisco.net" : "enr:-KO4QN1MRyJdifSbU3Mg_GUI0_ApfQQ_BcPgDV0FXjVvk4iEWX6EgMjXkI62H1S0DFnyWeHnvr-caabqiKtlvbIBICyGAY5Cyclbg2V0aMfGhJsZKtCAgmlkgnY0gmlwhK9jhAaJc2VjcDI1NmsxoQL8tsMGtv3EuJfUdI8AzGBxYynVj2HlNoz4wi3COC4TTYRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "4LPHENNNENCUT7P3MWTB5IFRTY.all.holesky.ethdisco.net" : "enr:-KO4QF8nbKqJkOeYlF-akOVBYzusVwSjI_ra_t7TmM52mEisQLCwNAjujSSc9-6ECErhss-gVTgBDzhY5QmVcF0PuZOGAY3Li2SUg2V0aMfGhJsZKtCAgmlkgnY0gmlwhCL__BOJc2VjcDI1NmsxoQLHimHBtxJxch5bc20e_O8OdeN3UQx6TQQWqaYfyi66OYRzbmFwwIN0Y3CCdW-DdWRwgnVv", + "5FNWKKKVUPIHRKWZY5P33A7E74.all.holesky.ethdisco.net" : "enr:-KO4QMD5jLnGVTSO75tQPB1JOCFKmtSPRi5pmE7wMwjDRfVKdwpZJvr8qfv1WmfRnJFzH5qV2sfC187nT9mp-xE_-gGGAYupHwgxg2V0aMfGhJsZKtCAgmlkgnY0gmlwhE6KPgeJc2VjcDI1NmsxoQP9YVLocwkml9Z9YNDRpmzVceRY-Ts45qiPOBC1OsxuUIRzbmFwwIN0Y3CCdmmDdWRwgnZp", + "HIDVATDVB36L2MASAWA7SBJAII.all.holesky.ethdisco.net" : "enrtree-branch:N7HAL5M6HNZBGTWM3LWFDRX4WU,PX6K4ZETH5UX56IKHKL5TNOOMU,I2Q5U7BDYSM7O3O2IKIRL3KTZY,O4SSPZG3DFA7PHZ5L42DVOPWDM,Z5DOK3IB5X3IBOVVIK5MQCDYNA,ZJOONJUDYAQ53XOJU4KG5W4ATM,NLYAF5XVTTQUOMK4LYUF3FXNPE,XPX2T64BHDYLVGT5UC4GVIQIVY,AHB67R6KGAKWRSBL7CRAO3", + "FTTHOFPRPIHMJNZA3VZUAW5TIM.all.holesky.ethdisco.net" : "enrtree-branch:V52J3WKNVGG56JFRKYDLSZREJY,ZCAW4EMBOL5EH4MSMW7FY6JMEE,2SA643JTHJ75ZVPFHAKLXI2WXQ,5RYAVZN3SSZZQ2KISUO7XDBPE4,366Y3UIIKK2G5CUS7CHNPZMPEM,7J7D5R4SH6LVDCFZ5ELOP46IP4,7QEPEVJFOCL5A63GZLZ4IZNMOI,OOWRWZXNDJVJUCDDOJFOY4YB2Y,H6XMM4W6QZEQDAPVXGHJSU", + "O6EQKGUJOO2BQGMADYZHOSZXLA.all.holesky.ethdisco.net" : "enr:-KO4QAg_or5YgVU8ScSgcgvNmLMISW0LA4L5GtRLxmyVUlGRR4GNuEZ9q_tKtZdAbLH5B-FN2ie8hp0U6P90d39xtyWGAY3d9Tplg2V0aMfGhJsZKtCAgmlkgnY0gmlwhAMRsEKJc2VjcDI1NmsxoQJ7STjsgZvt1OOj5krr2l2iAwlY5AGl-dgTziPZsCd9qIRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "XIOWDM2BTMANILG2M4LCMO7W3M.all.holesky.ethdisco.net" : "enr:-Je4QCvStdCVA-jcYsFoXmDXjXlp-Cd_9sSMdi9Y1LM3tGH2C3k49yrWy-Y2Jg93ikASYljvD9qVUES0T9q5htcbL60ag2V0aMfGhJsZKtCAgmlkgnY0gmlwhLkIa-2Jc2VjcDI1NmsxoQKcjBIYl8vFVYVBs5trEl-Zn3IE7u1ZiiV986p0QCrAL4N0Y3CCdl2DdWRwgnZd", + "OL6KYVYI7SGS7U34FW3RFBA76A.all.holesky.ethdisco.net" : "enr:-KO4QIlf7XLihA1hw9d44SB0ENJ40RT1RmF2KrT9a2kRZfZoWPdO1jRi3GNnNSCLDxLo-aFc6uaGi3zxhCD8lg1aZgSGAYuoht2-g2V0aMfGhJsZKtCAgmlkgnY0gmlwhDNR0MWJc2VjcDI1NmsxoQKFvOHK4c-F1tHwdgsUa2yru5RrWyiVP9kRLnrw1kXcd4RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "NXU6DU7HEM2L4DN6G2VHHFT72I.all.holesky.ethdisco.net" : "enr:-KO4QLbrxgLUdY0NhpBPVfilpwn4d3gPFIEqoq62P1piOkVmGwh5qpbKtor5FfE4FM_eezlzMdeCM7bzau8ciKZU1GiGAY0-AhsZg2V0aMfGhJsZKtCAgmlkgnY0gmlwhEp2iPuJc2VjcDI1NmsxoQOGZRfYj6RgBYWulaw90MVPDt_9F-oM3PzevNe-RS6KyoRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "RUQLNXP5XBWPAOEKRUEBAQXXXU.all.holesky.ethdisco.net" : "enr:-KO4QPpoyIf1DfiiOW8Tt34tBuA68Qd55cyWcqGOCFJtcHKtcqYr373NJPyQVd8ktsuqZU8L_ERSRAfKesEwdBARg3OGAY45u3jEg2V0aMfGhJsZKtCAgmlkgnY0gmlwhJRxo0WJc2VjcDI1NmsxoQKhMTQ31q7EAIHkisiA02KVyFZZLq2Q9w4_jgmiYCsF_oRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "RUJF27NEYDBNQAFMI6K6SKDVRY.all.holesky.ethdisco.net" : "enrtree-branch:OOQAXEUVKR5EJ722HM2UANT3R4,HOXANHAMPVCFA2QR54VEMWCWPE,INA5QYTPU6WIOBT7IC7U3WGDIY,3PMAED3BC2HUS7AMEPGJDKENQY,VJJPHI6PWJ7NFBTW2XJH3JMMNY,5ZWIGTGKMFAFF4LAODABANSBOQ,UYESJQUB2BHP2MSJNUSDRGXQ34,CK3HQLGRQATDVF5CEGHX4DVQRE,AYB76YY6WPJVIBLRB5U7L5", + "ZM7CMNKKBVIXY7WGMB7GHYUXMA.all.holesky.ethdisco.net" : "enr:-KO4QNhl-I-sZux_gBOVLLbMdw8kMK9fSEBBNYukE2r95hDLJ9rOuhZgJO2dvNU_vCcuiqJNJHj8N3olsc7srxryuuCGAY0cB7Qsg2V0aMfGhP1PAWuAgmlkgnY0gmlwhJ7caOWJc2VjcDI1NmsxoQIyp37cpS7gvrKk0f3VxW4D9Jx7mOvSAQ6vOaQppsYp2oRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "FKSM7XWAT4KW3TDLRHWSXZPMKQ.all.holesky.ethdisco.net" : "enr:-Ke4QNDn6wmnaZU1IYGb9lX8zb2QFUpa_yen8vFTnuXpW3_iah53AKPZX1D05HE23bw-UtR6zXOpCquB9XDdxvGhZrGGAY4oYEAkg2V0aMvKhMYaYJiEZRbqwIJpZIJ2NIJpcIQlPPlxiXNlY3AyNTZrMaEDiVRaEbnUMSPCtpxluUoBgOy3Loaa6SaVW9QK8BGTFJiEc25hcMCDdGNwgnZ9g3VkcIJ2fQ", + "QXKEJG4XZEQSXNUY7JJYCATPEI.all.holesky.ethdisco.net" : "enrtree-branch:FEORTRPBZJSNRB3XCLK7KCMACQ,2M2GRZYAFKAVHHU5HTLUHPYHCM,KEC4722NVHE3KX3IYNZC34C7NY,NZCRGKUE7ZXSIS4MIQ4WUS76BA,3PYSNKXWWSRBWV377EFNYINHSA,C7SDD5OPASV7B2XUOXF5NLBBKU,QY2XP6FTX7QKFNP6TNZ2MOHZKQ,76OAILAEE52CRVMYFBIBMSEUXE,2QHFEUHLG6Q35B5LUS5BF3", + "OSHAABXYJSRWW35VOMYHZUKJXU.all.holesky.ethdisco.net" : "enrtree-branch:LHFO3FXNRHB5B3YSTKUALGMEAI,5FNWKKKVUPIHRKWZY5P33A7E74,NHLNHXPRKOALUKH4MZ7CIVDONU,BAY6SKB2RTQCM7RTSHAFQ6TTBQ,ERETBCE2BAPJRSWIEUQV3QPP7M,MFPFKTJREYMESD7STJJOFYDVV4,MOBGCDYGQA4CNBQZDPN52JC5E4,RUQLNXP5XBWPAOEKRUEBAQXXXU,NJCWGLUAGBNZQYPU752O5L", + "WAU46WR54TX2VBK4QM7NFTA73Y.all.holesky.ethdisco.net" : "enr:-KO4QE70KmVYr_jd9JOMBFTIJomf3oliyhugCm4kFnzYD7dcJk3y7dVYyakJdlWHBuk1t4hDjW05BzeVf2f-V0LQ-r6GAY0-Aab1g2V0aMfGhJsZKtCAgmlkgnY0gmlwhCUbP0KJc2VjcDI1NmsxoQMQLfyyzC-YY6A53fEu5a8tP-JWCUM3vkX-c7nRyu28d4RzbmFwwIN0Y3CCequDdWRwgnqr", + "CZ7ESLY5ZH4ULXKQ54XIFDCOUI.all.holesky.ethdisco.net" : "enr:-KO4QOQjO7c9J74CpllRngkrEXHLw-W8VqtJXxkLWPRIXC1CFL4TCjEM4ZfRW6MFlqXERaY3h9mztZXLAo1lf1DAS1qGAY2D0c0_g2V0aMfGhJsZKtCAgmlkgnY0gmlwhC06cBWJc2VjcDI1NmsxoQN8oyz6RCujPq6Hj9N8dBn3EeJ4A69HYeYnyLRiXLZ1UIRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "5HSHHSS4QV3FKCNDETO7CEXMZM.all.holesky.ethdisco.net" : "enr:-KO4QAOJNWWAbN0bM51WFaFZimloFw1eBc7Pi3x7uMejmIToDZxBoAVtvdwTg3p6T0Mwmvuot6uT7PG2OjGxkk3P-iGGAY5cIO3Hg2V0aMfGhJsZKtCAgmlkgnY0gmlwhLAJA5uJc2VjcDI1NmsxoQPdRE5vPW9Wj-HRwzubnKAA-1n0p6CAOGLP-E-GzE9EnIRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "DAVKEW6RQ5YTYHKCJ2A2J5GVCI.all.holesky.ethdisco.net" : "enrtree-branch:6WAXAZL7FYKEJBZOPWIHCI4F6E,OL6KYVYI7SGS7U34FW3RFBA76A,FKSM7XWAT4KW3TDLRHWSXZPMKQ,5HSHHSS4QV3FKCNDETO7CEXMZM,CZ7ESLY5ZH4ULXKQ54XIFDCOUI,ZRVWX74UFHUWOPCC7LOPLYGIZA,FFM67SMD4N3SK4ZQTY2HOMU4F4,IPSUCB6CICZIW6SERKZL7FFTIM,7XE2DGFKCGOST6BJ46JPNK", + "ELYMU6HPEMGLIYO2UO4YNUI7CQ.all.holesky.ethdisco.net" : "enr:-KO4QOWLzzuDHlwrEZhdOmeHEuFi87mX6iwJ_V820RsBL0mCLngiBaLbhumS1Q8zIc6YPzCYN07nigSMr-OXf19z3WSGAYra1TQYg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKFGJc2VjcDI1NmsxoQO4QASxyNUTDVIRkvEDQejetqJ4roUtGDD6U8CoI9wm-YRzbmFwwIN0Y3CCdnSDdWRwgnZ0", + "VNVQZHCW3CS7ELWR3ANLO7RFBA.all.holesky.ethdisco.net" : "enr:-Ji4QBY7W0c9-rsM9r3yeBngIOe0LFJx8wew3ZatidckbRSzAbXbvc5ahmadpUcrPG2oDm9ziwxw7maQtzEs4J2AnWeBr4NldGjHxoSbGSrQgIJpZIJ2NIJpcISU--s8iXNlY3AyNTZrMaECwwCgmDrePUxVSsOZqgS0USzkXwzkqF0-QK7a59EsnrCDdGNwgnX7g3VkcIJ1-w", + "MFPFKTJREYMESD7STJJOFYDVV4.all.holesky.ethdisco.net" : "enr:-KO4QJfXFSQOpwMtEgz0BSB0gJ0zXQZ6yMzoBfXYCK3ATxulM_mkkQhMWUHsrNyNH9cQYfxRbN_hEGfP3id3IfLiv7KGAY5iEk-hg2V0aMfGhJsZKtCAgmlkgnY0gmlwhGQmekqJc2VjcDI1NmsxoQOoFPqpQmRhcIfVINZ0knq8hxe2tUYnTSLEC-6Xoly7jIRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "HGROZTZI7YPDW5F37QVUPQEBG4.all.holesky.ethdisco.net" : "enr:-KO4QI3-xQxY2u-misXaxFtpfezZr0Fk0jiL6U7ZAbwV4yCRcWxgNVOxLifoUOm9uMUqH4wlGxIYL7onmzlavqRIjDGGAYs5av4dg2V0aMfGhJsZKtCAgmlkgnY0gmlwhJRxqGeJc2VjcDI1NmsxoQPc4dhahamqQVxv9D89fNMO9DOP_YaPJRQ3EmT2XantU4RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "VJJPHI6PWJ7NFBTW2XJH3JMMNY.all.holesky.ethdisco.net" : "enr:-KO4QHCu9ZPe68fKUXeUzje0Jwv0D8UCrAQ_K5W9KHrIpTdUYWIGwbzyl8heZ1cxijlyUhN1WB0HZpEBThUmrdm-uLiGAYra3Fk3g2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKJOJc2VjcDI1NmsxoQP0AAgBs1kDG4XBe55HY0J4F7GjHiwQVUY-xFKYXOB594RzbmFwwIN0Y3CCdnSDdWRwgnZ0", + "2M2GRZYAFKAVHHU5HTLUHPYHCM.all.holesky.ethdisco.net" : "enr:-KO4QIPWnAyt-tyaS1cnMwEJaFdvDd-MpwEcVv26cfaZ3Ffkc4WCpHqzPmd_SifEsxQf7ontOz9a1EPjbxlsu_V1XZqGAY1UHtm_g2V0aMfGhJsZKtCAgmlkgnY0gmlwhCU85MWJc2VjcDI1NmsxoQNyzc5NiamRt8y5TQ9Qj-EZAYeCb7ZaclF5sOdPrErpbIRzbmFwwIN0Y3CCdl-DdWRwgnZf", + "OOWRWZXNDJVJUCDDOJFOY4YB2Y.all.holesky.ethdisco.net" : "enr:-KO4QBKCkXrhG-hN-IY-O03b5A4JHaweb5xuuSZP4OdJ4uSSZ7E1-yioWl_0WSF8ShOfNU_7wgSCIwBPg0d4RD4w4sOGAY4oBl8og2V0aMfGhJsZKtCAgmlkgnY0gmlwhFJB7YiJc2VjcDI1NmsxoQL-RfOTNeu0SmlCfWAVQaQBUXF0WDEUCyGXv1LWC2_xqoRzbmFwwIN0Y3CCequDdWRwgnqr", + "E4GGBOZ5L2UFPAKJIPLNBA2MKA.all.holesky.ethdisco.net" : "enr:-Ke4QD_Gz-YGWvzpAe-a1l2KHz2pxG_nPUYTcHQDN1Wuvkk7TrEDq-HKIXtA_mbPukz9qLLW5sfkJH5mYda9eDssvkSGAY1Mq2nIg2V0aMvKhMYaYJiEZRbqwIJpZIJ2NIJpcIQf3GvbiXNlY3AyNTZrMaECyosSeovqxsDXTua8c47z7EK--WogcD_FCwDX6tHvVQSEc25hcMCDdGNwgnZfg3VkcIJ2Xw", + "KYTZHQX2PSOGY6RQTCVTUKTS7E.all.holesky.ethdisco.net" : "enr:-KO4QJn2ahKcTeE2karHSHOiJgE9b2BJWo54NS3nzjjCDnsvZ1JjDl8yulvzn8qQA48zCtHkiVMEVNHtdf_LM_N0Cs6GAYxElMbzg2V0aMfGhJsZKtCAgmlkgnY0gmlwhJ3m3GOJc2VjcDI1NmsxoQJzs6KencrR0_ylBVNGuZaknO_zQUYOrq4rjj_FmTlHN4RzbmFwwIN0Y3CCdl-DdWRwgnZf", + "O25DXMPE4UEZ6SFMQRBGL2E2I4.all.holesky.ethdisco.net" : "enr:-KO4QLC4qJt9OIKLkxROeQMNcj4ZuH_QZsVcS1v-NhzBGAf8B-5xF3GYqtMp40n6nwDg8o1yUs7xEx0kgf6wBJF-AuqGAYrbCvwJg2V0aMfGhJsZKtCAgmlkgnY0gmlwhMIhKGuJc2VjcDI1NmsxoQPogWsyNFpt9VYFYrsdUS4dxK2BMeL4FY41ZzgBjeEGRoRzbmFwwIN0Y3CCdmCDdWRwgnZg", + "RWRV55FT3DKDVGZK7AEU2DR77Y.all.holesky.ethdisco.net" : "enrtree-branch:ZV5CYUMJPF4XSTFHOG65622KEE,QDCV4SQTQAGHHA3DRA2TR4LNMY,MZGQKIQKIOFO3W2GJURJUXVYMQ,V5LTO36DCXU2JCCSSJ76E35OFU,NQHD4WNRRGE5JHHX6IDKGIF7KU,ELYMU6HPEMGLIYO2UO4YNUI7CQ,VN7EWW4RMUZM6KUOQ43GJ33DWM,J7ZIDKH7CKVIYY244QVU6RMPDM,QSK67XVHDPEQH7Q54HACQS", + "TMY2W2YBNCXUUNA3Y7QXWVQLRE.all.holesky.ethdisco.net" : "enr:-Ke4QEsOrFCJqnbVXiuSh3DKjvS3RlUoOrrLRlKp2mAqmCuJQBVLFREqABNWRvZfLOxDPEtvcRzDtDW2juSa1vd2xj6GAY025-JNg2V0aMvKhP1PAWuEZcNqwIJpZIJ2NIJpcIRU961viXNlY3AyNTZrMaECoqR94LV1BQMpEDbmfJdd8adEPWHV7qP09nePPVm9nLqEc25hcMCDdGNwgnZfg3VkcIJ2Xw", + "XTJ3PFTPBB3ATDDWAA6W7RRKMY.all.holesky.ethdisco.net" : "enrtree-branch:5AHKPN5NN5IHEH365X6OYZDHDE,AUQGHXRAP7J3AGI6TUOQVYTYLY,IDXHFSATDDENJYOYJC2TUSUYOY,GZVG4EONSAY6M7SMLJXDCFUF5Q,LHFTDS37XFOEXQKTWJGKWRLFWM,HTMBFBAIZBACDG3EZZ4JVANCAY,ZM7CMNKKBVIXY7WGMB7GHYUXMA,VNVQZHCW3CS7ELWR3ANLO7RFBA,PA4IN34FHBJOUK2MS7YRA3", + "V4A7I7I2SFVUDFFH7AGPFXACBA.all.holesky.ethdisco.net" : "enr:-KO4QMIHR0XgkMyX4E9Xh8Fw3rFr-QWyH49lpFmrEhx1Cc_WMxm6atHPm0g-bXmzradBORfb0S9_6dyi-GnQbtlm7GOGAYr8tSZLg2V0aMfGhJsZKtCAgmlkgnY0gmlwhA_MQUKJc2VjcDI1NmsxoQMrgP3au96duBa9PQzAFPhlQ7ikkA96YWAkrItcm5zCxIRzbmFwwIN0Y3CCdl-DdWRwgnZf" +} \ No newline at end of file diff --git a/ethereum/permissioning/build.gradle b/ethereum/permissioning/build.gradle index eaf36c8fda6..f404d70ede3 100644 --- a/ethereum/permissioning/build.gradle +++ b/ethereum/permissioning/build.gradle @@ -22,7 +22,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } diff --git a/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/AccountLocalConfigPermissioningController.java b/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/AccountLocalConfigPermissioningController.java index c62ed9d6b0b..cb1aa2fd0a1 100644 --- a/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/AccountLocalConfigPermissioningController.java +++ b/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/AccountLocalConfigPermissioningController.java @@ -84,7 +84,13 @@ public AccountLocalConfigPermissioningController( private void readAccountsFromConfig(final LocalPermissioningConfiguration configuration) { if (configuration != null && configuration.isAccountAllowlistEnabled()) { if (!configuration.getAccountAllowlist().isEmpty()) { - addAccounts(configuration.getAccountAllowlist()); + AllowlistOperationResult result = addAccounts(configuration.getAccountAllowlist()); + if (result != AllowlistOperationResult.SUCCESS) { + throw new IllegalStateException( + String.format( + "Error reloading permissions file. Invalid accounts allowlist, validation failed due to \"%s\"", + result)); + } } } } diff --git a/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/NodeSmartContractPermissioningController.java b/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/NodeSmartContractPermissioningController.java index 5f619ee3f80..a034f049da0 100644 --- a/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/NodeSmartContractPermissioningController.java +++ b/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/NodeSmartContractPermissioningController.java @@ -71,7 +71,7 @@ boolean checkSmartContractRules(final EnodeURL sourceEnode, final EnodeURL desti transactionSimulator.processAtHead(callParams); if (result.isPresent()) { - switch (result.get().getResult().getStatus()) { + switch (result.get().result().getStatus()) { case INVALID: throw new IllegalStateException("Permissioning transaction found to be Invalid"); case FAILED: diff --git a/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/NodeSmartContractV2PermissioningController.java b/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/NodeSmartContractV2PermissioningController.java index ca114d17955..ae78a4ab85a 100644 --- a/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/NodeSmartContractV2PermissioningController.java +++ b/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/NodeSmartContractV2PermissioningController.java @@ -114,7 +114,7 @@ private Bytes createPayload(final EnodeURL enodeUrl) { } private boolean parseResult(final TransactionSimulatorResult result) { - switch (result.getResult().getStatus()) { + switch (result.result().getStatus()) { case INVALID: throw new IllegalStateException("Invalid node permissioning smart contract call"); case FAILED: diff --git a/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/PermissioningConfigurationBuilder.java b/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/PermissioningConfigurationBuilder.java index d9c2ba0e806..b8ba4397459 100644 --- a/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/PermissioningConfigurationBuilder.java +++ b/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/PermissioningConfigurationBuilder.java @@ -27,8 +27,6 @@ public class PermissioningConfigurationBuilder { - @Deprecated public static final String ACCOUNTS_WHITELIST_KEY = "accounts-whitelist"; - @Deprecated public static final String NODES_WHITELIST_KEY = "nodes-whitelist"; public static final String ACCOUNTS_ALLOWLIST_KEY = "accounts-allowlist"; public static final String NODES_ALLOWLIST_KEY = "nodes-allowlist"; @@ -69,8 +67,7 @@ private static LocalPermissioningConfiguration loadNodePermissioning( if (localConfigNodePermissioningEnabled) { final TomlParseResult nodePermissioningToml = readToml(nodePermissioningConfigFilepath); - final TomlArray nodeAllowlistTomlArray = - getAllowlistArray(nodePermissioningToml, NODES_ALLOWLIST_KEY, NODES_WHITELIST_KEY); + final TomlArray nodeAllowlistTomlArray = getArray(nodePermissioningToml, NODES_ALLOWLIST_KEY); permissioningConfiguration.setNodePermissioningConfigFilePath( nodePermissioningConfigFilepath); @@ -104,8 +101,7 @@ private static LocalPermissioningConfiguration loadAccountPermissioning( if (localConfigAccountPermissioningEnabled) { final TomlParseResult accountPermissioningToml = readToml(accountPermissioningConfigFilepath); final TomlArray accountAllowlistTomlArray = - getAllowlistArray( - accountPermissioningToml, ACCOUNTS_ALLOWLIST_KEY, ACCOUNTS_WHITELIST_KEY); + getArray(accountPermissioningToml, ACCOUNTS_ALLOWLIST_KEY); permissioningConfiguration.setAccountPermissioningConfigFilePath( accountPermissioningConfigFilepath); @@ -137,23 +133,14 @@ private static LocalPermissioningConfiguration loadAccountPermissioning( } /** - * This method allows support for both keys for now. Whitelist TOML keys will be removed in future - * (breaking change) + * This method retrieves an array from parsed toml, using the given key. * * @param tomlParseResult result of a prior toml parse - * @param primaryKey key to fetch - * @param alternateKey alternate key to fetch - * @return In order: the array of the primaryKey if it exists, or the array of the alternateKey if - * it exists, or null. + * @param key key to fetch + * @return The array matching the key if it exists, or null. */ - private static TomlArray getAllowlistArray( - final TomlParseResult tomlParseResult, final String primaryKey, final String alternateKey) { - final TomlArray array = tomlParseResult.getArray(primaryKey); - if (array == null) { - return tomlParseResult.getArray(alternateKey); - } else { - return array; - } + private static TomlArray getArray(final TomlParseResult tomlParseResult, final String key) { + return tomlParseResult.getArray(key); } private static TomlParseResult readToml(final String filepath) throws Exception { diff --git a/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/TransactionSmartContractPermissioningController.java b/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/TransactionSmartContractPermissioningController.java index f8ee921668f..dd42727af85 100644 --- a/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/TransactionSmartContractPermissioningController.java +++ b/ethereum/permissioning/src/main/java/org/hyperledger/besu/ethereum/permissioning/TransactionSmartContractPermissioningController.java @@ -134,7 +134,7 @@ public boolean isPermitted(final Transaction transaction) { transactionSimulator.processAtHead(callParams); if (result.isPresent()) { - switch (result.get().getResult().getStatus()) { + switch (result.get().result().getStatus()) { case INVALID: throw new IllegalStateException( "Transaction permissioning transaction found to be Invalid"); diff --git a/ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/AccountLocalConfigPermissioningControllerTest.java b/ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/AccountLocalConfigPermissioningControllerTest.java index 1c823b83a83..db98cd95acc 100644 --- a/ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/AccountLocalConfigPermissioningControllerTest.java +++ b/ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/AccountLocalConfigPermissioningControllerTest.java @@ -17,6 +17,7 @@ import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.catchThrowable; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; @@ -108,6 +109,40 @@ public void whenLoadingAccountsFromConfigShouldNormalizeAccountsToLowerCase() { .containsExactly("0xfe3b557e8fb62b89f4916b721be55ceb828dbd73"); } + @Test + public void whenLoadingDuplicateAccountsFromConfigShouldThrowError() { + when(permissioningConfig.isAccountAllowlistEnabled()).thenReturn(true); + when(permissioningConfig.getAccountAllowlist()) + .thenReturn( + List.of( + "0xcb88953e60948e3a76fa658d65b7c2d5043c6409", + "0xdd76406b124f9e3ae9fbeb47e4d8dc0ab143902d", + "0x432132e8561785c33afe931762cf8eeb9c80e3ad", + "0xcb88953e60948e3a76fa658d65b7c2d5043c6409")); + + assertThrows( + IllegalStateException.class, + () -> { + controller = + new AccountLocalConfigPermissioningController( + permissioningConfig, allowlistPersistor, metricsSystem); + }); + } + + @Test + public void whenLoadingInvalidAccountsFromConfigShouldThrowError() { + when(permissioningConfig.isAccountAllowlistEnabled()).thenReturn(true); + when(permissioningConfig.getAccountAllowlist()).thenReturn(List.of("0x0", "0xzxy")); + + assertThrows( + IllegalStateException.class, + () -> { + controller = + new AccountLocalConfigPermissioningController( + permissioningConfig, allowlistPersistor, metricsSystem); + }); + } + @Test public void whenPermConfigContainsEmptyListOfAccountsContainsShouldReturnFalse() { when(permissioningConfig.isAccountAllowlistEnabled()).thenReturn(true); diff --git a/ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/LocalPermissioningConfigurationBuilderTest.java b/ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/LocalPermissioningConfigurationBuilderTest.java index c4e965513a4..8f2059f935f 100644 --- a/ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/LocalPermissioningConfigurationBuilderTest.java +++ b/ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/LocalPermissioningConfigurationBuilderTest.java @@ -36,10 +36,6 @@ public class LocalPermissioningConfigurationBuilderTest { private static final String PERMISSIONING_CONFIG_VALID = "/permissioning_config.toml"; - @Deprecated - private static final String PERMISSIONING_CONFIG_VALID_WHITELISTS = - "/permissioning_config_whitelists.toml"; - private static final String PERMISSIONING_CONFIG_ACCOUNT_ALLOWLIST_ONLY = "/permissioning_config_account_allowlist_only.toml"; private static final String PERMISSIONING_CONFIG_NODE_ALLOWLIST_ONLY = @@ -61,24 +57,6 @@ public class LocalPermissioningConfigurationBuilderTest { private final String VALID_NODE_ID = "6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0"; - @Test - public void permissioningConfig_usingDeprecatedKeysIsStillValid() throws Exception { - final String uri = "enode://" + VALID_NODE_ID + "@192.168.0.9:4567"; - final String uri2 = "enode://" + VALID_NODE_ID + "@192.169.0.9:4568"; - - final URL configFile = this.getClass().getResource(PERMISSIONING_CONFIG_VALID_WHITELISTS); - final Path toml = createTempFile("toml", Resources.toByteArray(configFile)); - - LocalPermissioningConfiguration permissioningConfiguration = permissioningConfig(toml); - - assertThat(permissioningConfiguration.isAccountAllowlistEnabled()).isTrue(); - assertThat(permissioningConfiguration.getAccountAllowlist()) - .containsExactly("0x0000000000000000000000000000000000000009"); - assertThat(permissioningConfiguration.isNodeAllowlistEnabled()).isTrue(); - assertThat(permissioningConfiguration.getNodeAllowlist()) - .containsExactly(EnodeURLImpl.fromString(uri), EnodeURLImpl.fromString(uri2)); - } - @Test public void permissioningConfig() throws Exception { final String uri = "enode://" + VALID_NODE_ID + "@192.168.0.9:4567"; diff --git a/ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/TransactionSmartContractPermissioningControllerTest.java b/ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/TransactionSmartContractPermissioningControllerTest.java index 81902e52c40..1fd4f58a4bd 100644 --- a/ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/TransactionSmartContractPermissioningControllerTest.java +++ b/ethereum/permissioning/src/test/java/org/hyperledger/besu/ethereum/permissioning/TransactionSmartContractPermissioningControllerTest.java @@ -14,7 +14,6 @@ */ package org.hyperledger.besu.ethereum.permissioning; -import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider.createInMemoryBlockchain; @@ -42,7 +41,6 @@ import java.io.IOException; import java.math.BigInteger; -import com.google.common.io.Resources; import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -57,13 +55,13 @@ public class TransactionSmartContractPermissioningControllerTest { @Mock private Counter checkUnpermittedCounter; private TransactionSmartContractPermissioningController setupController( - final String resourceName, final String contractAddressString) throws IOException { + final String resourceName, final String contractAddressString) { final ProtocolSchedule protocolSchedule = ProtocolScheduleFixture.MAINNET; - final String emptyContractFile = - Resources.toString(this.getClass().getResource(resourceName), UTF_8); final GenesisState genesisState = - GenesisState.fromConfig(GenesisConfigFile.fromConfig(emptyContractFile), protocolSchedule); + GenesisState.fromConfig( + GenesisConfigFile.fromSource(this.getClass().getResource(resourceName)), + protocolSchedule); final MutableBlockchain blockchain = createInMemoryBlockchain(genesisState.getBlock()); final WorldStateArchive worldArchive = createInMemoryWorldStateArchive(); diff --git a/ethereum/permissioning/src/test/resources/permissioning_config_whitelists.toml b/ethereum/permissioning/src/test/resources/permissioning_config_whitelists.toml deleted file mode 100644 index 9c1cfd9e474..00000000000 --- a/ethereum/permissioning/src/test/resources/permissioning_config_whitelists.toml +++ /dev/null @@ -1,6 +0,0 @@ -# Permissioning TOML file -# NOTE whitelist is being deprecated in favor of allowlist -# support for whitelist will be removed in future - -accounts-whitelist=["0x0000000000000000000000000000000000000009"] -nodes-whitelist=["enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.168.0.9:4567","enode://6f8a80d14311c39f35f516fa664deaaaa13e85b2f7493f37f6144d86991ec012937307647bd3b9a82abe2974e1407241d54947bbb39763a4cac9f77166ad92a0@192.169.0.9:4568"] diff --git a/ethereum/referencetests/build.gradle b/ethereum/referencetests/build.gradle index a30317f5d3f..22f22c70a54 100644 --- a/ethereum/referencetests/build.gradle +++ b/ethereum/referencetests/build.gradle @@ -34,6 +34,7 @@ def blockchainReferenceTests = tasks.register("blockchainReferenceTests") { "BlockchainTests", "$generatedTestsPath/org/hyperledger/besu/ethereum/vm/blockchain", "BlockchainReferenceTest", + "org.hyperledger.besu.ethereum.vm.blockchain", ("BlockchainTests/InvalidBlocks/bcExpectSection") // exclude test for test filling tool ) } @@ -50,6 +51,7 @@ def eipBlockchainReferenceTests = tasks.register("eipBlockchainReferenceTests") "EIPTests${File.separatorChar}BlockchainTests", "$generatedTestsPath/org/hyperledger/besu/ethereum/vm/eip", "EIPBlockchainReferenceTest", + "org.hyperledger.besu.ethereum.vm.eip", ) } @@ -65,6 +67,7 @@ def eipStateReferenceTests = tasks.register("eipStateReferenceTests") { "EIPTests${File.separatorChar}StateTests", "$generatedTestsPath/org/hyperledger/besu/ethereum/vm/eip", "EIPStateReferenceTest", + "org.hyperledger.besu.ethereum.vm.eip", ) } @@ -80,13 +83,24 @@ def executionSpecTests = tasks.register("executionSpecTests") { inputs.files fileTree(referenceTestsPath), fileTree(generatedTestsPath) outputs.files generatedTestsPath + // generate blockchain_tests: generateTestFiles( - fileTree(referenceTestsPath + "/fixtures"), + fileTree(referenceTestsPath + "/fixtures/blockchain_tests"), file("src/reference-test/templates/BlockchainReferenceTest.java.template"), "fixtures", "$generatedTestsPath/org/hyperledger/besu/ethereum/vm/executionspec", - "ExecutionSpecTest", - ("fixtures/example/example") // exclude test for test filling tool + "ExecutionSpecBlockchainTest", + "org.hyperledger.besu.ethereum.vm.executionspec", + ) + + // generate state_tests: + generateTestFiles( + fileTree(referenceTestsPath + "/fixtures/state_tests"), + file("src/reference-test/templates/GeneralStateReferenceTest.java.template"), + "fixtures", + "$generatedTestsPath/org/hyperledger/besu/ethereum/vm/executionspec", + "ExecutionSpecStateTest", + "org.hyperledger.besu.ethereum.vm.executionspec", ) } @@ -101,7 +115,8 @@ def generalstateReferenceTests = tasks.register("generalstateReferenceTests") { file("src/reference-test/templates/GeneralStateReferenceTest.java.template"), "GeneralStateTests", "$generatedTestsPath/org/hyperledger/besu/ethereum/vm/generalstate", - "GeneralStateReferenceTest" + "GeneralStateReferenceTest", + "org.hyperledger.besu.ethereum.vm.generalstate", ) } @@ -116,7 +131,24 @@ def generalstateRegressionReferenceTests = tasks.register("generalstateRegressio file("src/reference-test/templates/GeneralStateReferenceTest.java.template"), "regressions", "$generatedTestsPath/org/hyperledger/besu/ethereum/vm/generalstate", - "GeneralStateRegressionReferenceTest" + "GeneralStateRegressionReferenceTest", + "org.hyperledger.besu.ethereum.vm.generalstate" + ) +} + +def eofReferenceTests = tasks.register("eofReferenceTests") { + final referenceTestsPath = "src/reference-test/external-resources/EOFTests" + final generatedTestsPath = "$buildDir/generated/sources/reference-test/$name/java" + inputs.files fileTree(referenceTestsPath), + fileTree(generatedTestsPath) + outputs.files generatedTestsPath + generateTestFiles( + fileTree(referenceTestsPath), + file("src/reference-test/templates/EOFReferenceTest.java.template"), + "EOFTests", + "$generatedTestsPath/org/hyperledger/besu/ethereum/vm/eof", + "EOFReferenceTest", + "org.hyperledger.besu.ethereum.vm.eof" ) } @@ -131,7 +163,8 @@ sourceSets { eipStateReferenceTests, executionSpecTests, generalstateReferenceTests, - generalstateRegressionReferenceTests + generalstateRegressionReferenceTests, + eofReferenceTests } resources { srcDirs 'src/reference-test/resources', @@ -161,6 +194,7 @@ dependencies { referenceTestImplementation project(path: ':config') referenceTestImplementation project(path: ':datatypes') referenceTestImplementation project(path: ':ethereum:core') + referenceTestImplementation project(path: ':metrics:core') referenceTestImplementation project(path: ':ethereum:core', configuration: 'testSupportArtifacts') referenceTestImplementation project(path: ':ethereum:rlp') referenceTestImplementation project(path: ':ethereum:rlp', configuration: 'testSupportArtifacts') @@ -170,7 +204,7 @@ dependencies { referenceTestImplementation project(path: ':testutil') referenceTestImplementation project(path: ':util') // the following will be resolved via custom ivy repository declared in root build.gradle - referenceTestImplementation 'ethereum:execution-spec-tests:0.2.5:fixtures@tar.gz' + referenceTestImplementation 'ethereum:execution-spec-tests:3.0.0:fixtures_stable@tar.gz' referenceTestImplementation 'com.fasterxml.jackson.core:jackson-databind' referenceTestImplementation 'com.google.guava:guava' referenceTestImplementation 'io.tmio:tuweni-bytes' @@ -198,7 +232,7 @@ tasks.register('validateReferenceTestSubmodule') { description = "Checks that the reference tests submodule is not accidentally changed" doLast { def result = new ByteArrayOutputStream() - def expectedHash = '428f218d7d6f4a52544e12684afbfe6e2882ffbf' + def expectedHash = '9201075490807f58811078e9bb5ec895b4ac01a5' def submodulePath = java.nio.file.Path.of("${rootProject.projectDir}", "ethereum/referencetests/src/reference-test/external-resources").toAbsolutePath() try { exec { @@ -234,32 +268,36 @@ following commands: } processResources.dependsOn('validateReferenceTestSubmodule') -def generateTestFiles(FileTree jsonPath, File templateFile, String pathstrip, String destination, String namePrefix, String ... excludedPath) { +def generateTestFiles( + FileTree jsonPath, + File templateFile, + String pathstrip, + String destination, + String namePrefix, + String packageString, + String ... excludedPath) { mkdir(destination) def referenceTestTemplate = templateFile.text - // This is how many json files to include in each test file - def fileSets = jsonPath.getFiles().collate(5) - - fileSets.eachWithIndex { fileSet, idx -> - def paths = [] - fileSet.each { testJsonFile -> - def parentFile = testJsonFile.getParentFile() - def parentPathFile = parentFile.getPath().substring(parentFile.getPath().indexOf(pathstrip)) - if (!testJsonFile.getName().toString().startsWith(".") && !excludedPath.contains(parentPathFile)) { - def pathFile = testJsonFile.getPath() - paths << pathFile.substring(pathFile.indexOf(pathstrip)) - } + def paths = [] + jsonPath.getFiles().forEach { testJsonFile -> + def parentFile = testJsonFile.getParentFile() + def parentPathFile = parentFile.getPath().substring(parentFile.getPath().indexOf(pathstrip)) + if (!testJsonFile.getName().toString().startsWith(".") && !excludedPath.contains(parentPathFile)) { + def pathFile = testJsonFile.getPath() + paths << pathFile.substring(pathFile.indexOf(pathstrip)) } + } + paths.collate(5).eachWithIndex { tests, idx -> def testFile = file(destination + "/" + namePrefix + "_" + idx + ".java") - - def allPaths = '"' + paths.join('", "') + '"' + def allPaths = '"' + tests.join('",\n "') + '"' def testFileContents = referenceTestTemplate .replaceAll("%%TESTS_FILE%%", allPaths) .replaceAll("%%TESTS_NAME%%", namePrefix + "_" + idx) + .replaceAll("%%PACKAGE_NAME%%", packageString) testFile.newWriter().withWriter { w -> w << testFileContents } } } diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BlockchainReferenceTestCaseSpec.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BlockchainReferenceTestCaseSpec.java index f0d93f2243a..4645abdfcf9 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BlockchainReferenceTestCaseSpec.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BlockchainReferenceTestCaseSpec.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.referencetests; @@ -22,16 +21,17 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.ProtocolContext; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderFunctions; -import org.hyperledger.besu.ethereum.core.Deposit; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.ParsedExtraData; +import org.hyperledger.besu.ethereum.core.Request; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.Withdrawal; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; @@ -108,7 +108,7 @@ public BlockchainReferenceTestCaseSpec( this.blockchain = buildBlockchain(genesisBlockHeader); this.sealEngine = sealEngine; this.protocolContext = - new ProtocolContext(this.blockchain, this.worldStateArchive, null, Optional.empty()); + new ProtocolContext(this.blockchain, this.worldStateArchive, null, new BadBlockManager()); } public String getNetwork() { @@ -164,11 +164,7 @@ public ReferenceTestBlockHeader( @JsonProperty("mixHash") final String mixHash, @JsonProperty("nonce") final String nonce, @JsonProperty("withdrawalsRoot") final String withdrawalsRoot, - @JsonProperty("depositsRoot") final String depositsRoot, - @JsonProperty("dataGasUsed") - final String dataGasUsed, // TODO: remove once reference tests have been updated - @JsonProperty("excessDataGas") - final String excessDataGas, // TODO: remove once reference tests have been updated + @JsonProperty("requestsRoot") final String requestsRoot, @JsonProperty("blobGasUsed") final String blobGasUsed, @JsonProperty("excessBlobGas") final String excessBlobGas, @JsonProperty("parentBeaconBlockRoot") final String parentBeaconBlockRoot, @@ -195,14 +191,10 @@ public ReferenceTestBlockHeader( Hash.fromHexString(mixHash), // mixHash Bytes.fromHexStringLenient(nonce).toLong(), withdrawalsRoot != null ? Hash.fromHexString(withdrawalsRoot) : null, - dataGasUsed != null - ? Long.decode(dataGasUsed) - : blobGasUsed != null ? Long.decode(blobGasUsed) : 0, - excessDataGas != null - ? BlobGas.fromHexString(excessDataGas) - : excessBlobGas != null ? BlobGas.fromHexString(excessBlobGas) : null, + blobGasUsed != null ? Long.decode(blobGasUsed) : 0, + excessBlobGas != null ? BlobGas.fromHexString(excessBlobGas) : null, parentBeaconBlockRoot != null ? Bytes32.fromHexString(parentBeaconBlockRoot) : null, - depositsRoot != null ? Hash.fromHexString(depositsRoot) : null, + requestsRoot != null ? Hash.fromHexString(requestsRoot) : null, new BlockHeaderFunctions() { @Override public Hash hash(final BlockHeader header) { @@ -247,7 +239,10 @@ public CandidateBlock( @JsonProperty("blockHeader") final Object blockHeader, @JsonProperty("transactions") final Object transactions, @JsonProperty("uncleHeaders") final Object uncleHeaders, - @JsonProperty("withdrawals") final Object withdrawals) { + @JsonProperty("withdrawals") final Object withdrawals, + @JsonProperty("depositRequests") final Object depositRequests, + @JsonProperty("withdrawalRequests") final Object withdrawalRequests, + @JsonProperty("consolidationRequests") final Object consolidationRequests) { boolean blockVaid = true; // The BLOCK__WrongCharAtRLP_0 test has an invalid character in its rlp string. Bytes rlpAttempt = null; @@ -290,7 +285,7 @@ public Block getBlock() { : Optional.of(input.readList(Withdrawal::readFrom)), input.isEndOfCurrentList() ? Optional.empty() - : Optional.of(input.readList(Deposit::readFrom))); + : Optional.of(input.readList(Request::readFrom))); return new Block(header, body); } } diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestUpdateAccumulator.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestUpdateAccumulator.java index e346b5abf6d..db8bb4b3e9e 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestUpdateAccumulator.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestUpdateAccumulator.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,23 +14,28 @@ */ package org.hyperledger.besu.ethereum.referencetests; +import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.StorageSlotKey; -import org.hyperledger.besu.ethereum.bonsai.BonsaiAccount; -import org.hyperledger.besu.ethereum.bonsai.BonsaiValue; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiPreImageProxy; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldView; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.BonsaiAccount; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiPreImageProxy; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; +import org.hyperledger.besu.ethereum.trie.diffbased.common.DiffBasedValue; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldView; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.preload.Consumer; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.preload.StorageConsumingMap; import org.hyperledger.besu.evm.internal.EvmConfiguration; -import org.apache.tuweni.bytes.Bytes; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.tuweni.units.bigints.UInt256; public class BonsaiReferenceTestUpdateAccumulator extends BonsaiWorldStateUpdateAccumulator { private final BonsaiPreImageProxy preImageProxy; public BonsaiReferenceTestUpdateAccumulator( - final BonsaiWorldView world, - final Consumer> accountPreloader, + final DiffBasedWorldView world, + final Consumer> accountPreloader, final Consumer storagePreloader, final BonsaiPreImageProxy preImageProxy, final EvmConfiguration evmConfiguration) { @@ -39,8 +44,37 @@ public BonsaiReferenceTestUpdateAccumulator( } @Override - protected Hash hashAndSavePreImage(final Bytes bytes) { - // by default do not save hash preImages - return preImageProxy.hashAndSavePreImage(bytes); + protected Hash hashAndSaveAccountPreImage(final Address address) { + return preImageProxy.hashAndSavePreImage(address); + } + + @Override + protected Hash hashAndSaveSlotPreImage(final UInt256 slotKey) { + return preImageProxy.hashAndSavePreImage(slotKey); + } + + public BonsaiReferenceTestUpdateAccumulator createDetachedAccumulator() { + final BonsaiReferenceTestUpdateAccumulator copy = + new BonsaiReferenceTestUpdateAccumulator( + wrappedWorldView(), + accountPreloader, + storagePreloader, + preImageProxy, + evmConfiguration); + getAccountsToUpdate().forEach((k, v) -> copy.getAccountsToUpdate().put(k, v.copy())); + getCodeToUpdate().forEach((k, v) -> copy.getCodeToUpdate().put(k, v.copy())); + copy.getStorageToClear().addAll(getStorageToClear()); + getStorageToUpdate() + .forEach( + (k, v) -> { + StorageConsumingMap> newMap = + new StorageConsumingMap<>(k, new ConcurrentHashMap<>(), v.getConsumer()); + v.forEach((key, value) -> newMap.put(key, value.copy())); + copy.getStorageToUpdate().put(k, newMap); + }); + copy.updatedAccounts.putAll(updatedAccounts); + copy.deletedAccounts.addAll(deletedAccounts); + copy.isAccumulatorStateChanged = true; + return copy; } } diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestWorldState.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestWorldState.java index e56d5c858f8..e374f5c020f 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestWorldState.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestWorldState.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -16,17 +16,22 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.cache.CachedMerkleTrieLoader; -import org.hyperledger.besu.ethereum.bonsai.cache.CachedWorldStorageManager; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiPreImageProxy; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogAddedEvent; -import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogManager; -import org.hyperledger.besu.ethereum.bonsai.trielog.TrieLogPruner; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldState; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.BonsaiCachedMerkleTrieLoader; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.cache.NoOpBonsaiCachedWorldStorageManager; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiPreImageProxy; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateLayerStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldState; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.worldview.BonsaiWorldStateUpdateAccumulator; +import org.hyperledger.besu.ethereum.trie.diffbased.common.cache.DiffBasedCachedWorldStorageManager; +import org.hyperledger.besu.ethereum.trie.diffbased.common.trielog.TrieLogAddedEvent; +import org.hyperledger.besu.ethereum.trie.diffbased.common.trielog.TrieLogManager; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldState; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldStateConfig; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.accumulator.DiffBasedWorldStateUpdateAccumulator; +import org.hyperledger.besu.ethereum.worldstate.DataStorageConfiguration; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.hyperledger.besu.metrics.ObservableMetricsSystem; @@ -35,10 +40,11 @@ import java.util.Map; import java.util.Optional; -import java.util.function.Function; import java.util.stream.Stream; import com.fasterxml.jackson.annotation.JsonCreator; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; @@ -50,39 +56,41 @@ public class BonsaiReferenceTestWorldState extends BonsaiWorldState private final EvmConfiguration evmConfiguration; protected BonsaiReferenceTestWorldState( - final BonsaiReferenceTestWorldStateStorage worldStateStorage, - final CachedMerkleTrieLoader cachedMerkleTrieLoader, - final CachedWorldStorageManager cachedWorldStorageManager, + final BonsaiReferenceTestWorldStateStorage worldStateKeyValueStorage, + final BonsaiCachedMerkleTrieLoader bonsaiCachedMerkleTrieLoader, + final DiffBasedCachedWorldStorageManager cachedWorldStorageManager, final TrieLogManager trieLogManager, final BonsaiPreImageProxy preImageProxy, final EvmConfiguration evmConfiguration) { super( - worldStateStorage, - cachedMerkleTrieLoader, + worldStateKeyValueStorage, + bonsaiCachedMerkleTrieLoader, cachedWorldStorageManager, trieLogManager, - evmConfiguration); - this.refTestStorage = worldStateStorage; + evmConfiguration, + new DiffBasedWorldStateConfig()); + this.refTestStorage = worldStateKeyValueStorage; this.preImageProxy = preImageProxy; this.evmConfiguration = evmConfiguration; setAccumulator( new BonsaiReferenceTestUpdateAccumulator( this, (addr, value) -> - cachedMerkleTrieLoader.preLoadAccount( + bonsaiCachedMerkleTrieLoader.preLoadAccount( getWorldStateStorage(), worldStateRootHash, addr), (addr, value) -> - cachedMerkleTrieLoader.preLoadStorageSlot(getWorldStateStorage(), addr, value), + bonsaiCachedMerkleTrieLoader.preLoadStorageSlot( + getWorldStateStorage(), addr, value), preImageProxy, evmConfiguration)); } @Override public ReferenceTestWorldState copy() { - var layerCopy = new BonsaiReferenceTestWorldStateStorage(worldStateStorage, preImageProxy); + var layerCopy = new BonsaiReferenceTestWorldStateStorage(getWorldStateStorage(), preImageProxy); return new BonsaiReferenceTestWorldState( layerCopy, - cachedMerkleTrieLoader, + bonsaiCachedMerkleTrieLoader, cachedWorldStorageManager, trieLogManager, preImageProxy, @@ -101,6 +109,101 @@ protected void verifyWorldStateRoot(final Hash calculatedStateRoot, final BlockH // The test harness validates the root hash, no need to validate in-line for reference test } + @Override + public void processExtraStateStorageFormatValidation(final BlockHeader blockHeader) { + if (blockHeader != null) { + final Hash parentStateRoot = getWorldStateRootHash(); + final BonsaiReferenceTestUpdateAccumulator originalUpdater = + ((BonsaiReferenceTestUpdateAccumulator) updater()).createDetachedAccumulator(); + + // validate trielog generation with persisted state + validateStateRolling(parentStateRoot, originalUpdater, blockHeader, false); + // validate trielog generation with frozen state + validateStateRolling(parentStateRoot, originalUpdater, blockHeader, true); + } + } + + /** + * TrieLog is an important part of Bonsai, so it's important to verify the generation of the + * TrieLog by performing rollbacks and rollforwards. + * + * @param blockHeader header of the block to import + */ + private void validateStateRolling( + final Hash parentStateRoot, + final BonsaiReferenceTestUpdateAccumulator originalUpdater, + final BlockHeader blockHeader, + final boolean isFrozenState) { + // With Bonsai, a TrieLog is generated when the state is persisted. Therefore, we generate the + // TrieLog by triggering a state persist in order to closely match the real case scenario. + generateTrieLogFromState(blockHeader, originalUpdater, isFrozenState); + final TrieLog trieLogFromFrozenState = + trieLogManager + .getTrieLogLayer(blockHeader.getBlockHash()) + .orElseThrow(() -> new RuntimeException("trielog not found during test")); + // trying rollback rollfoward with frozen state + validateTrieLog(parentStateRoot, blockHeader, trieLogFromFrozenState); + } + + private void validateTrieLog( + final Hash parentStateRoot, final BlockHeader blockHeader, final TrieLog trieLog) { + + try (var bonsaiWorldState = createBonsaiWorldState(false)) { + BonsaiWorldStateUpdateAccumulator updaterForState = + (BonsaiWorldStateUpdateAccumulator) bonsaiWorldState.updater(); + updaterForState.rollForward(trieLog); + updaterForState.commit(); + bonsaiWorldState.persist(blockHeader); + Hash generatedRootHash = bonsaiWorldState.rootHash(); + if (!bonsaiWorldState.rootHash().equals(blockHeader.getStateRoot())) { + throw new RuntimeException( + "state root becomes invalid following a rollForward %s != %s" + .formatted(blockHeader.getStateRoot(), generatedRootHash)); + } + + updaterForState = (BonsaiWorldStateUpdateAccumulator) bonsaiWorldState.updater(); + updaterForState.rollBack(trieLog); + updaterForState.commit(); + bonsaiWorldState.persist(null); + generatedRootHash = bonsaiWorldState.rootHash(); + if (!bonsaiWorldState.rootHash().equals(parentStateRoot)) { + throw new RuntimeException( + "state root becomes invalid following a rollBackward %s != %s" + .formatted(parentStateRoot, generatedRootHash)); + } + } + } + + private void generateTrieLogFromState( + final BlockHeader blockHeader, + final BonsaiReferenceTestUpdateAccumulator originalUpdater, + final boolean isFrozen) { + // generate trielog + BonsaiReferenceTestUpdateAccumulator updaterForState = + originalUpdater.createDetachedAccumulator(); + try (var bonsaiWorldState = createBonsaiWorldState(isFrozen)) { + bonsaiWorldState.setAccumulator(updaterForState); + updaterForState.commit(); + bonsaiWorldState.persist(blockHeader); + } + } + + private BonsaiWorldState createBonsaiWorldState(final boolean isFrozen) { + BonsaiWorldState bonsaiWorldState = + new BonsaiWorldState( + new BonsaiWorldStateLayerStorage( + (BonsaiWorldStateKeyValueStorage) worldStateKeyValueStorage), + bonsaiCachedMerkleTrieLoader, + cachedWorldStorageManager, + trieLogManager, + evmConfiguration, + new DiffBasedWorldStateConfig()); + if (isFrozen) { + bonsaiWorldState.freeze(); // freeze state + } + return bonsaiWorldState; + } + @JsonCreator public static BonsaiReferenceTestWorldState create( final Map accounts) { @@ -112,24 +215,30 @@ public static BonsaiReferenceTestWorldState create( final Map accounts, final EvmConfiguration evmConfiguration) { final ObservableMetricsSystem metricsSystem = new NoOpMetricsSystem(); - final CachedMerkleTrieLoader cachedMerkleTrieLoader = new CachedMerkleTrieLoader(metricsSystem); - final TrieLogManager trieLogManager = new NoOpTrieLogManager(); + + final BonsaiCachedMerkleTrieLoader bonsaiCachedMerkleTrieLoader = + new BonsaiCachedMerkleTrieLoader(metricsSystem); + final TrieLogManager trieLogManager = new ReferenceTestsInMemoryTrieLogManager(); + final BonsaiPreImageProxy preImageProxy = new BonsaiPreImageProxy.BonsaiReferenceTestPreImageProxy(); - final BonsaiReferenceTestWorldStateStorage worldStateStorage = - new BonsaiReferenceTestWorldStateStorage( - new BonsaiWorldStateKeyValueStorage( - new InMemoryKeyValueStorageProvider(), metricsSystem), - preImageProxy); + final BonsaiWorldStateKeyValueStorage bonsaiWorldStateKeyValueStorage = + new BonsaiWorldStateKeyValueStorage( + new InMemoryKeyValueStorageProvider(), + metricsSystem, + DataStorageConfiguration.DEFAULT_BONSAI_CONFIG); + + final BonsaiReferenceTestWorldStateStorage worldStateKeyValueStorage = + new BonsaiReferenceTestWorldStateStorage(bonsaiWorldStateKeyValueStorage, preImageProxy); - final NoOpCachedWorldStorageManager noOpCachedWorldStorageManager = - new NoOpCachedWorldStorageManager(); + final NoOpBonsaiCachedWorldStorageManager noOpCachedWorldStorageManager = + new NoOpBonsaiCachedWorldStorageManager(bonsaiWorldStateKeyValueStorage); final BonsaiReferenceTestWorldState worldState = new BonsaiReferenceTestWorldState( - worldStateStorage, - cachedMerkleTrieLoader, + worldStateKeyValueStorage, + bonsaiCachedMerkleTrieLoader, noOpCachedWorldStorageManager, trieLogManager, preImageProxy, @@ -149,67 +258,24 @@ public Stream streamAccounts(final Bytes32 startKeyHash, fina return this.refTestStorage.streamAccounts(this, startKeyHash, limit); } - static class NoOpCachedWorldStorageManager extends CachedWorldStorageManager { - - public NoOpCachedWorldStorageManager() { - super( - null, - new BonsaiWorldStateKeyValueStorage( - new InMemoryKeyValueStorageProvider(), new NoOpMetricsSystem()), - new NoOpMetricsSystem()); - } - - @SuppressWarnings({"UnsynchronizedOverridesSynchronized", "squid:S3551"}) - @Override - public void addCachedLayer( - final BlockHeader blockHeader, - final Hash worldStateRootHash, - final BonsaiWorldState forWorldState) { - // reference test world states are not cached - } - - @Override - public boolean containWorldStateStorage(final Hash blockHash) { - return false; - } - - @Override - public Optional getWorldState(final Hash blockHash) { - return Optional.empty(); - } - - @Override - public Optional getNearestWorldState(final BlockHeader blockHeader) { - return Optional.empty(); - } - - @Override - public Optional getHeadWorldState( - final Function> hashBlockHeaderFunction) { - return Optional.empty(); - } - - @Override - public void reset() { - // reference test world states are not re-used - } - } + static class ReferenceTestsInMemoryTrieLogManager extends TrieLogManager { - static class NoOpTrieLogManager extends TrieLogManager { + private final Cache trieLogCache = + CacheBuilder.newBuilder().maximumSize(5).build(); - public NoOpTrieLogManager() { - super(null, null, 0, null, TrieLogPruner.noOpTrieLogPruner()); + public ReferenceTestsInMemoryTrieLogManager() { + super(null, null, 0, null); } - @SuppressWarnings({"UnsynchronizedOverridesSynchronized", "squid:S3551"}) @Override - public void saveTrieLog( - final BonsaiWorldStateUpdateAccumulator localUpdater, + public synchronized void saveTrieLog( + final DiffBasedWorldStateUpdateAccumulator localUpdater, final Hash forWorldStateRootHash, final BlockHeader forBlockHeader, - final BonsaiWorldState forWorldState) { + final DiffBasedWorldState forWorldState) { // notify trie log added observers, synchronously TrieLog trieLog = trieLogFactory.create(localUpdater, forBlockHeader); + trieLogCache.put(forBlockHeader.getHash(), trieLogFactory.serialize(trieLog)); trieLogObservers.forEach(o -> o.onTrieLogAdded(new TrieLogAddedEvent(trieLog))); } @@ -220,7 +286,9 @@ public long getMaxLayersToLoad() { @Override public Optional getTrieLogLayer(final Hash blockHash) { - return Optional.empty(); + final byte[] trielog = trieLogCache.getIfPresent(blockHash); + trieLogCache.invalidate(blockHash); // remove trielog from the cache + return Optional.ofNullable(trieLogFactory.deserialize(trielog)); } } diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestWorldStateStorage.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestWorldStateStorage.java index b998bf7d25b..3d059097810 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestWorldStateStorage.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/BonsaiReferenceTestWorldStateStorage.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -16,15 +16,16 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.bonsai.BonsaiAccount; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiPreImageProxy; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.bonsai.storage.BonsaiWorldStateLayerStorage; -import org.hyperledger.besu.ethereum.bonsai.worldview.BonsaiWorldView; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.BonsaiAccount; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiPreImageProxy; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.bonsai.storage.BonsaiWorldStateLayerStorage; +import org.hyperledger.besu.ethereum.trie.diffbased.common.worldview.DiffBasedWorldView; import org.hyperledger.besu.evm.account.AccountStorageEntry; import org.hyperledger.besu.evm.worldstate.WorldState; import java.util.Comparator; +import java.util.Map; import java.util.NavigableMap; import java.util.Optional; import java.util.TreeMap; @@ -53,7 +54,7 @@ public NavigableMap storageEntriesFrom( .stream() .collect( Collectors.toMap( - e -> e.getKey(), + Map.Entry::getKey, e -> AccountStorageEntry.create( UInt256.fromBytes(RLP.decodeValue(e.getValue())), @@ -64,7 +65,7 @@ public NavigableMap storageEntriesFrom( } public Stream streamAccounts( - final BonsaiWorldView context, final Bytes32 startKeyHash, final int limit) { + final DiffBasedWorldView context, final Bytes32 startKeyHash, final int limit) { return streamFlatAccounts(startKeyHash, UInt256.MAX_VALUE, limit) .entrySet() // map back to addresses using preImage provider: @@ -80,6 +81,7 @@ public Stream streamAccounts( BonsaiAccount.fromRLP(context, address, entry.getValue(), false)))) .filter(Optional::isPresent) .map(Optional::get) + .filter(acct -> context.updater().getAccount(acct.getAddress().orElse(null)) != null) .sorted(Comparator.comparing(account -> account.getAddress().orElse(Address.ZERO))); } } diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/DefaultReferenceTestWorldState.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/DefaultReferenceTestWorldState.java deleted file mode 100644 index 45771578e14..00000000000 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/DefaultReferenceTestWorldState.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.referencetests; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStateKeyValueStorage; -import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStatePreimageKeyValueStorage; -import org.hyperledger.besu.ethereum.worldstate.DefaultMutableWorldState; -import org.hyperledger.besu.evm.internal.EvmConfiguration; -import org.hyperledger.besu.evm.worldstate.WorldState; -import org.hyperledger.besu.evm.worldstate.WorldUpdater; -import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; - -import java.util.Map; - -import com.fasterxml.jackson.annotation.JsonCreator; - -public class DefaultReferenceTestWorldState extends DefaultMutableWorldState - implements ReferenceTestWorldState { - - DefaultReferenceTestWorldState() { - super( - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()), - new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage()), - EvmConfiguration.DEFAULT); - } - - public DefaultReferenceTestWorldState(final WorldState worldState) { - super(worldState, EvmConfiguration.DEFAULT); - } - - @Override - public ReferenceTestWorldState copy() { - return new DefaultReferenceTestWorldState(this); - } - - @JsonCreator - public static ReferenceTestWorldState create(final Map accounts) { - final ReferenceTestWorldState worldState = new DefaultReferenceTestWorldState(); - final WorldUpdater updater = worldState.updater(); - - for (final Map.Entry entry : accounts.entrySet()) { - ReferenceTestWorldState.insertAccount( - updater, Address.fromHexString(entry.getKey()), entry.getValue()); - } - - updater.commit(); - return worldState; - } -} diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/EOFTestCaseSpec.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/EOFTestCaseSpec.java new file mode 100644 index 00000000000..48fae5277de --- /dev/null +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/EOFTestCaseSpec.java @@ -0,0 +1,54 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.referencetests; + +import java.util.NavigableMap; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class EOFTestCaseSpec { + + public record TestVector( + @JsonProperty("code") String code, + @JsonProperty("results") NavigableMap results, + @JsonProperty("containerKind") String containerKind) {} + + public record TestResult( + @JsonProperty("exception") String exception, @JsonProperty("result") boolean result) { + public static TestResult TEST_RESULT_PASSED = new TestResult(null, true); + + public static TestResult failed(final String exception) { + return new TestResult(exception, false); + } + + public static TestResult passed() { + return TEST_RESULT_PASSED; + } + } + + NavigableMap vector; + + @JsonCreator + public EOFTestCaseSpec(@JsonProperty("vectors") final NavigableMap vector) { + this.vector = vector; + } + + public NavigableMap getVector() { + return vector; + } +} diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/EnvironmentInformation.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/EnvironmentInformation.java index d0dc94b4ab6..7f2f327b31a 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/EnvironmentInformation.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/EnvironmentInformation.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.referencetests; diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ForestReferenceTestWorldState.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ForestReferenceTestWorldState.java new file mode 100644 index 00000000000..014e589f696 --- /dev/null +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ForestReferenceTestWorldState.java @@ -0,0 +1,76 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.referencetests; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStatePreimageKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; +import org.hyperledger.besu.ethereum.trie.forest.worldview.ForestMutableWorldState; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.worldstate.WorldState; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; +import org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage; + +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonCreator; + +public class ForestReferenceTestWorldState extends ForestMutableWorldState + implements ReferenceTestWorldState { + + ForestReferenceTestWorldState() { + super( + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage()), + new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage()), + EvmConfiguration.DEFAULT); + } + + public ForestReferenceTestWorldState(final WorldState worldState) { + super(worldState, EvmConfiguration.DEFAULT); + } + + @Override + public ReferenceTestWorldState copy() { + return new ForestReferenceTestWorldState(this); + } + + /** + * Executes additional validation checks that are specific to the storage format. + * + *

    Depending on the storage format (e.g., Bonsai, etc.), this method performs additional checks + * to validate the state. This could include validating the TrieLog and rolling for Bonsai, or + * potentially other checks for other modes. This method is intended to be used before the state + * root has been validated, to ensure the integrity of other aspects of the state. + */ + @Override + public void processExtraStateStorageFormatValidation(final BlockHeader blockHeader) { + // nothing more to verify with forest + } + + @JsonCreator + public static ReferenceTestWorldState create(final Map accounts) { + final ReferenceTestWorldState worldState = new ForestReferenceTestWorldState(); + final WorldUpdater updater = worldState.updater(); + + for (final Map.Entry entry : accounts.entrySet()) { + ReferenceTestWorldState.insertAccount( + updater, Address.fromHexString(entry.getKey()), entry.getValue()); + } + + updater.commit(); + return worldState; + } +} diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/GeneralStateTestCaseEipSpec.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/GeneralStateTestCaseEipSpec.java index def8c14f17e..9d9b2785516 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/GeneralStateTestCaseEipSpec.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/GeneralStateTestCaseEipSpec.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.referencetests; @@ -19,6 +18,7 @@ import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.Transaction; +import java.util.List; import java.util.function.Supplier; public class GeneralStateTestCaseEipSpec { @@ -34,7 +34,7 @@ public class GeneralStateTestCaseEipSpec { // anything // is run, which isn't friendly and 2) this makes it harder to parallelize this step. Anyway, this // is why this is a supplier: calling get() actually does the signing. - private final Supplier transactionSupplier; + private final List> transactionSuppliers; private final ReferenceTestWorldState initialWorldState; @@ -50,9 +50,9 @@ public class GeneralStateTestCaseEipSpec { private final int valueIndex; private final String expectException; - GeneralStateTestCaseEipSpec( + public GeneralStateTestCaseEipSpec( final String fork, - final Supplier transactionSupplier, + final List> transactionSuppliers, final ReferenceTestWorldState initialWorldState, final Hash expectedRootHash, final Hash expectedLogsHash, @@ -62,7 +62,7 @@ public class GeneralStateTestCaseEipSpec { final int valueIndex, final String expectException) { this.fork = fork; - this.transactionSupplier = transactionSupplier; + this.transactionSuppliers = transactionSuppliers; this.initialWorldState = initialWorldState; this.expectedRootHash = expectedRootHash; this.expectedLogsHash = expectedLogsHash; @@ -89,9 +89,13 @@ public Hash getExpectedLogsHash() { return expectedLogsHash; } - public Transaction getTransaction() { + public int getTransactionsCount() { + return transactionSuppliers.size(); + } + + public Transaction getTransaction(final int txIndex) { try { - return transactionSupplier.get(); + return transactionSuppliers.get(txIndex).get(); } catch (RuntimeException re) { // some tests specify invalid transactions. We throw exceptions in // GeneralStateTests but they are encoded in BlockchainTests, so we diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/GeneralStateTestCaseSpec.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/GeneralStateTestCaseSpec.java index 59f0a86fe09..394cb9a2695 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/GeneralStateTestCaseSpec.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/GeneralStateTestCaseSpec.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.referencetests; @@ -57,10 +56,12 @@ private Map> generate( final ReferenceTestWorldState initialWorldState, final Map> postSections, final StateTestVersionedTransaction versionedTransaction) { - + if (initialWorldState == null) { + return Map.of(); + } initialWorldState.persist(null); final Map> res = - new LinkedHashMap<>(postSections.size()); + LinkedHashMap.newLinkedHashMap(postSections.size()); for (final Map.Entry> entry : postSections.entrySet()) { final String eip = entry.getKey(); final List post = entry.getValue(); @@ -71,11 +72,12 @@ private Map> generate( .stateRoot(p.rootHash) .blockHeaderFunctions(MAINNET_FUNCTIONS) .buildBlockHeader(); - final Supplier txSupplier = () -> versionedTransaction.get(p.indexes); + final List> txSupplierList = + List.of(() -> versionedTransaction.get(p.indexes)); specs.add( new GeneralStateTestCaseEipSpec( eip, - txSupplier, + txSupplierList, initialWorldState, p.rootHash, p.logsHash, diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestBlockchain.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestBlockchain.java index 7e84ccbaf87..8f03a01caa7 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestBlockchain.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestBlockchain.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.referencetests; @@ -143,6 +142,11 @@ public Optional getBlockBody(final Hash blockHeaderHash) { throw new UnsupportedOperationException(); } + @Override + public synchronized Optional getBlockBodySafe(final Hash blockHeaderHash) { + return getBlockBody(blockHeaderHash); + } + @Override public Optional> getTxReceipts(final Hash blockHeaderHash) { // Deterministic, but just not implemented. @@ -188,10 +192,11 @@ public static class NonDeterministicOperationException extends RuntimeException } @Override + @SuppressWarnings("unused") public Comparator getBlockChoiceRule() { return (a, b) -> { throw new NonDeterministicOperationException( - "ReferenceTestBlockchian for VMTest Chains do not support fork choice rules"); + "ReferenceTestBlockchain for VMTest Chains do not support fork choice rules"); }; } diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestEnv.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestEnv.java index 2daf1893ba4..636e23f8786 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestEnv.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestEnv.java @@ -11,12 +11,11 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.referencetests; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hyperledger.besu.evm.internal.Words.decodeUnsignedLong; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.BlobGas; @@ -84,6 +83,8 @@ Withdrawal asWithdrawal() { private final Bytes32 beaconRoot; + private final boolean isStateTest; + /** * Public constructor. * @@ -106,10 +107,8 @@ public ReferenceTestEnv( @JsonProperty("currentBeaconRoot") final String currentBeaconRoot, @JsonProperty("currentBlobGasUsed") final String currentBlobGasUsed, @JsonProperty("currentCoinbase") final String coinbase, - @JsonProperty("currentDataGasUsed") final String currentDataGasUsed, @JsonProperty("currentDifficulty") final String difficulty, @JsonProperty("currentExcessBlobGas") final String currentExcessBlobGas, - @JsonProperty("currentExcessDataGas") final String currentExcessDataGas, @JsonProperty("currentGasLimit") final String gasLimit, @JsonProperty("currentNumber") final String number, @JsonProperty("currentRandom") final String random, @@ -118,14 +117,13 @@ public ReferenceTestEnv( @JsonProperty("currentWithdrawalsRoot") final String currentWithdrawalsRoot, @JsonProperty("parentBaseFee") final String parentBaseFee, @JsonProperty("parentBlobGasUsed") final String parentBlobGasUsed, - @JsonProperty("parentDataGasUsed") final String parentDataGasUsed, @JsonProperty("parentDifficulty") final String parentDifficulty, @JsonProperty("parentExcessBlobGas") final String parentExcessBlobGas, - @JsonProperty("parentExcessDataGas") final String parentExcessDataGas, @JsonProperty("parentGasLimit") final String parentGasLimit, @JsonProperty("parentGasUsed") final String parentGasUsed, @JsonProperty("parentTimestamp") final String parentTimestamp, - @JsonProperty("parentUncleHash") final String _parentUncleHash) { + @JsonProperty("parentUncleHash") final String _parentUncleHash, + @JsonProperty("isStateTest") final String isStateTest) { super( generateTestBlockHash(previousHash, number), Hash.EMPTY_LIST_HASH, // ommersHash @@ -138,29 +136,24 @@ public ReferenceTestEnv( number == null ? 0 : Long.decode(number), gasLimit == null ? 15_000_000L : Long.decode(gasLimit), 0L, - timestamp == null ? 0L : Long.decode(timestamp), + timestamp == null ? 0L : decodeUnsignedLong(timestamp), Bytes.EMPTY, Optional.ofNullable(baseFee).map(Wei::fromHexString).orElse(null), Optional.ofNullable(random).map(Difficulty::fromHexString).orElse(Difficulty.ZERO), 0L, currentWithdrawalsRoot == null ? null : Hash.fromHexString(currentWithdrawalsRoot), - currentBlobGasUsed == null - ? currentDataGasUsed == null ? null : Long.decode(currentDataGasUsed) - : Long.decode(currentBlobGasUsed), - currentExcessBlobGas == null - ? currentExcessDataGas == null ? null : BlobGas.fromHexString(currentExcessDataGas) - : BlobGas.fromHexString(currentExcessBlobGas), + currentBlobGasUsed == null ? null : Long.decode(currentBlobGasUsed), + currentExcessBlobGas == null ? null : BlobGas.of(Long.decode(currentExcessBlobGas)), beaconRoot == null ? null : Bytes32.fromHexString(beaconRoot), - null, // depositsRoot + null, // requestsRoot new MainnetBlockHeaderFunctions()); this.parentDifficulty = parentDifficulty; this.parentBaseFee = parentBaseFee; this.parentGasUsed = parentGasUsed; this.parentGasLimit = parentGasLimit; this.parentTimestamp = parentTimestamp; - this.parentExcessBlobGas = - parentExcessBlobGas == null ? parentExcessDataGas : parentExcessBlobGas; - this.parentBlobGasUsed = parentBlobGasUsed == null ? parentDataGasUsed : parentBlobGasUsed; + this.parentExcessBlobGas = parentExcessBlobGas; + this.parentBlobGasUsed = parentBlobGasUsed; this.withdrawals = withdrawals == null ? List.of() @@ -174,10 +167,16 @@ public ReferenceTestEnv( Map.entry( Long.decode(entry.getKey()), Hash.fromHexString(entry.getValue()))) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - this.beaconRoot = - beaconRoot == null - ? (currentBeaconRoot == null ? null : Hash.fromHexString(currentBeaconRoot)) - : Hash.fromHexString(beaconRoot); + if (beaconRoot == null) { + if (currentBeaconRoot == null) { + this.beaconRoot = null; + } else { + this.beaconRoot = Hash.fromHexString(currentBeaconRoot); + } + } else { + this.beaconRoot = Hash.fromHexString(beaconRoot); + } + this.isStateTest = Boolean.parseBoolean(isStateTest); } @Override @@ -198,7 +197,7 @@ private static Hash generateTestBlockHash(final String previousHash, final Strin } } - public BlockHeader updateFromParentValues(final ProtocolSpec protocolSpec) { + public BlockHeader parentBlockHeader(final ProtocolSpec protocolSpec) { var builder = BlockHeaderBuilder.fromHeader(this) .blockHeaderFunctions(protocolSpec.getBlockHeaderFunctions()); @@ -228,13 +227,11 @@ public BlockHeader updateFromParentValues(final ProtocolSpec protocolSpec) { null))); } if (parentExcessBlobGas != null && parentBlobGasUsed != null) { - builder.excessBlobGas( - BlobGas.of( - protocolSpec - .getGasCalculator() - .computeExcessBlobGas( - Long.decode(parentExcessBlobGas), Long.decode(parentBlobGasUsed)))); + builder.excessBlobGas(BlobGas.of(Long.decode(parentExcessBlobGas))); + builder.blobGasUsed(Long.decode(parentBlobGasUsed)); } + Hash grandParentHash = blockHashes.get(number - 2); + builder.parentHash(grandParentHash == null ? Hash.ZERO : grandParentHash); return builder.buildBlockHeader(); } @@ -247,6 +244,14 @@ public Optional getBlockhashByNumber(final long number) { return Optional.ofNullable(blockHashes.get(number)); } + public Map getBlockHashes() { + return blockHashes; + } + + public boolean isStateTest() { + return isStateTest; + } + @Override public boolean equals(final Object o) { if (this == o) return true; diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java index 3c5d84567ae..bcdf5e25d5b 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestProtocolSchedules.java @@ -11,25 +11,31 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.referencetests; import org.hyperledger.besu.config.GenesisConfigOptions; import org.hyperledger.besu.config.StubGenesisConfigOptions; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.precompile.KZGPointEvalPrecompiledContract; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import java.math.BigInteger; import java.util.Arrays; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.function.Function; - -import com.google.common.collect.ImmutableMap; +import java.util.stream.Collectors; public class ReferenceTestProtocolSchedules { @@ -38,51 +44,81 @@ public class ReferenceTestProtocolSchedules { private static final List SPECS_PRIOR_TO_DELETING_EMPTY_ACCOUNTS = Arrays.asList("Frontier", "Homestead", "EIP150"); + private static ReferenceTestProtocolSchedules instance; + + public static ReferenceTestProtocolSchedules getInstance() { + if (instance == null) { + instance = create(); + } + return instance; + } + public static ReferenceTestProtocolSchedules create() { return create(new StubGenesisConfigOptions()); } public static ReferenceTestProtocolSchedules create(final StubGenesisConfigOptions genesisStub) { - final ImmutableMap.Builder builder = ImmutableMap.builder(); - builder.put("Frontier", createSchedule(genesisStub.clone())); - builder.put("FrontierToHomesteadAt5", createSchedule(genesisStub.clone().homesteadBlock(5))); - builder.put("Homestead", createSchedule(genesisStub.clone().homesteadBlock(0))); - builder.put( - "HomesteadToEIP150At5", - createSchedule(genesisStub.clone().homesteadBlock(0).eip150Block(5))); - builder.put( - "HomesteadToDaoAt5", createSchedule(genesisStub.clone().homesteadBlock(0).daoForkBlock(5))); - builder.put("EIP150", createSchedule(genesisStub.clone().eip150Block(0))); - builder.put("EIP158", createSchedule(genesisStub.clone().eip158Block(0))); - builder.put( - "EIP158ToByzantiumAt5", - createSchedule(genesisStub.clone().eip158Block(0).byzantiumBlock(5))); - builder.put("Byzantium", createSchedule(genesisStub.clone().byzantiumBlock(0))); - builder.put("Constantinople", createSchedule(genesisStub.clone().constantinopleBlock(0))); - builder.put("ConstantinopleFix", createSchedule(genesisStub.clone().petersburgBlock(0))); - builder.put("Petersburg", createSchedule(genesisStub.clone().petersburgBlock(0))); - builder.put("Istanbul", createSchedule(genesisStub.clone().istanbulBlock(0))); - builder.put("MuirGlacier", createSchedule(genesisStub.clone().muirGlacierBlock(0))); - builder.put("Berlin", createSchedule(genesisStub.clone().berlinBlock(0))); - // the following schedules activate EIP-1559, but may have non-default if (genesisStub.getBaseFeePerGas().isEmpty()) { genesisStub.baseFeePerGas(0x0a); } - builder.put("London", createSchedule(genesisStub.clone().londonBlock(0))); - builder.put("ArrowGlacier", createSchedule(genesisStub.clone().arrowGlacierBlock(0))); - builder.put("GrayGlacier", createSchedule(genesisStub.clone().grayGlacierBlock(0))); - builder.put("Merge", createSchedule(genesisStub.clone().mergeNetSplitBlock(0))); - builder.put("Shanghai", createSchedule(genesisStub.clone().shanghaiTime(0))); - builder.put( - "ShanghaiToCancunAtTime15k", - createSchedule(genesisStub.clone().shanghaiTime(0).cancunTime(15000))); - builder.put("Cancun", createSchedule(genesisStub.clone().cancunTime(0))); - // TODO remove this after execution-test-specs finalize - builder.put("Shanghai+6780", createSchedule(genesisStub.clone().cancunTime(0))); - builder.put("Future_EIPs", createSchedule(genesisStub.clone().futureEipsTime(0))); - builder.put("Experimental_EIPs", createSchedule(genesisStub.clone().experimentalEipsTime(0))); - return new ReferenceTestProtocolSchedules(builder.build()); + // also load KZG file for mainnet + KZGPointEvalPrecompiledContract.init(); + return new ReferenceTestProtocolSchedules( + Map.ofEntries( + Map.entry("Frontier", createSchedule(genesisStub.clone())), + Map.entry( + "FrontierToHomesteadAt5", + createSchedule(genesisStub.clone().homesteadBlock(5))), + Map.entry("Homestead", createSchedule(genesisStub.clone().homesteadBlock(0))), + Map.entry( + "HomesteadToEIP150At5", + createSchedule(genesisStub.clone().homesteadBlock(0).eip150Block(5))), + Map.entry( + "HomesteadToDaoAt5", + createSchedule(genesisStub.clone().homesteadBlock(0).daoForkBlock(5))), + Map.entry("EIP150", createSchedule(genesisStub.clone().eip150Block(0))), + Map.entry("EIP158", createSchedule(genesisStub.clone().eip158Block(0))), + Map.entry( + "EIP158ToByzantiumAt5", + createSchedule(genesisStub.clone().eip158Block(0).byzantiumBlock(5))), + Map.entry("Byzantium", createSchedule(genesisStub.clone().byzantiumBlock(0))), + Map.entry( + "Constantinople", createSchedule(genesisStub.clone().constantinopleBlock(0))), + Map.entry( + "ConstantinopleFix", createSchedule(genesisStub.clone().petersburgBlock(0))), + Map.entry("Petersburg", createSchedule(genesisStub.clone().petersburgBlock(0))), + Map.entry("Istanbul", createSchedule(genesisStub.clone().istanbulBlock(0))), + Map.entry("MuirGlacier", createSchedule(genesisStub.clone().muirGlacierBlock(0))), + Map.entry("Berlin", createSchedule(genesisStub.clone().berlinBlock(0))), + Map.entry("London", createSchedule(genesisStub.clone().londonBlock(0))), + Map.entry("ArrowGlacier", createSchedule(genesisStub.clone().arrowGlacierBlock(0))), + Map.entry("GrayGlacier", createSchedule(genesisStub.clone().grayGlacierBlock(0))), + Map.entry("Merge", createSchedule(genesisStub.clone().mergeNetSplitBlock(0))), + Map.entry("Paris", createSchedule(genesisStub.clone().mergeNetSplitBlock(0))), + Map.entry("Shanghai", createSchedule(genesisStub.clone().shanghaiTime(0))), + Map.entry( + "ShanghaiToCancunAtTime15k", + createSchedule(genesisStub.clone().shanghaiTime(0).cancunTime(15000))), + Map.entry("Cancun", createSchedule(genesisStub.clone().cancunTime(0))), + Map.entry("CancunEOF", createSchedule(genesisStub.clone().cancunEOFTime(0))), + Map.entry( + "CancunToPragueAtTime15k", + createSchedule(genesisStub.clone().cancunTime(0).pragueTime(15000))), + Map.entry("Prague", createSchedule(genesisStub.clone().pragueEOFTime(0))), + Map.entry("Osaka", createSchedule(genesisStub.clone().futureEipsTime(0))), + Map.entry("Amsterdam", createSchedule(genesisStub.clone().futureEipsTime(0))), + Map.entry("Bogota", createSchedule(genesisStub.clone().futureEipsTime(0))), + Map.entry("Polis", createSchedule(genesisStub.clone().futureEipsTime(0))), + Map.entry("Bangkok", createSchedule(genesisStub.clone().futureEipsTime(0))), + Map.entry("Future_EIPs", createSchedule(genesisStub.clone().futureEipsTime(0))), + Map.entry( + "Experimental_EIPs", + createSchedule(genesisStub.clone().experimentalEipsTime(0)))) + .entrySet() + .stream() + .map(e -> Map.entry(e.getKey().toLowerCase(Locale.ROOT), e.getValue())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); } private final Map schedules; @@ -92,7 +128,17 @@ private ReferenceTestProtocolSchedules(final Map sched } public ProtocolSchedule getByName(final String name) { - return schedules.get(name); + return schedules.get(name.toLowerCase(Locale.ROOT)); + } + + public ProtocolSpec geSpecByName(final String name) { + ProtocolSchedule schedule = getByName(name); + if (schedule == null) { + return null; + } + BlockHeader header = + new BlockHeaderTestFixture().timestamp(Long.MAX_VALUE).number(Long.MAX_VALUE).buildHeader(); + return schedule.getByBlockHeader(header); } private static ProtocolSchedule createSchedule(final GenesisConfigOptions options) { @@ -102,7 +148,11 @@ private static ProtocolSchedule createSchedule(final GenesisConfigOptions option ProtocolSpecAdapters.create(0, Function.identity()), PrivacyParameters.DEFAULT, false, - EvmConfiguration.DEFAULT) + EvmConfiguration.DEFAULT, + MiningParameters.MINING_DISABLED, + new BadBlockManager(), + false, + new NoOpMetricsSystem()) .createProtocolSchedule(); } diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestWorldState.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestWorldState.java index 3d4db1edaab..8549a2dd150 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestWorldState.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/ReferenceTestWorldState.java @@ -11,12 +11,12 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.referencetests; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.evm.account.MutableAccount; import org.hyperledger.besu.evm.internal.EvmConfiguration; @@ -90,6 +90,8 @@ static void insertAccount( ReferenceTestWorldState copy(); + void processExtraStateStorageFormatValidation(final BlockHeader blockHeader); + @JsonCreator static ReferenceTestWorldState create(final Map accounts) { // delegate to a Bonsai reference test world state: diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/StateTestAccessListDeserializer.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/StateTestAccessListDeserializer.java index 470fe2e5f37..0c14829c4c1 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/StateTestAccessListDeserializer.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/StateTestAccessListDeserializer.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.referencetests; diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/StateTestVersionedTransaction.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/StateTestVersionedTransaction.java index fa18dea1a09..a36d3804323 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/StateTestVersionedTransaction.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/StateTestVersionedTransaction.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.referencetests; @@ -104,7 +103,6 @@ public StateTestVersionedTransaction( @JsonDeserialize(using = StateTestAccessListDeserializer.class) @JsonProperty("accessLists") final List> maybeAccessLists, @JsonProperty("maxFeePerBlobGas") final String maxFeePerBlobGas, - @JsonProperty("maxFeePerDataGas") final String maxFeePerDataGas, @JsonProperty("blobVersionedHashes") final List blobVersionedHashes) { this.nonce = Bytes.fromHexStringLenient(nonce).toLong(); @@ -112,7 +110,7 @@ public StateTestVersionedTransaction( this.maxFeePerGas = Optional.ofNullable(maxFeePerGas).map(Wei::fromHexString).orElse(null); this.maxPriorityFeePerGas = Optional.ofNullable(maxPriorityFeePerGas).map(Wei::fromHexString).orElse(null); - this.to = to.isEmpty() ? null : Address.fromHexString(to); + this.to = (to == null || to.isEmpty()) ? null : Address.fromHexString(to); SignatureAlgorithm signatureAlgorithm = SignatureAlgorithmFactory.getInstance(); this.keys = @@ -124,9 +122,7 @@ public StateTestVersionedTransaction( this.payloads = parseArray(data, Bytes::fromHexString); this.maybeAccessLists = Optional.ofNullable(maybeAccessLists); this.maxFeePerBlobGas = - Optional.ofNullable(maxFeePerBlobGas == null ? maxFeePerDataGas : maxFeePerBlobGas) - .map(Wei::fromHexString) - .orElse(null); + Optional.ofNullable(maxFeePerBlobGas).map(Wei::fromHexString).orElse(null); this.blobVersionedHashes = blobVersionedHashes; } diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/VMReferenceTestCaseSpec.java b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/VMReferenceTestCaseSpec.java index ba1a9e00602..b39e28010e4 100644 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/VMReferenceTestCaseSpec.java +++ b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/VMReferenceTestCaseSpec.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.ethereum.referencetests; diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/blob b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/blob deleted file mode 100644 index 6b096168cff..00000000000 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/blob +++ /dev/null @@ -1,62 +0,0 @@ -INFO: Correctly produced payload 1/1 -INFO: Executing step 4: SendBlobTransactions: 7 Transactions, 6 blobs each, 1 max data gas fee ->> (9ee7c86c) {"jsonrpc":"2.0","id":25,"method":"eth_getBlockByNumber","params":["latest",false]} -<< (9ee7c86c) {"jsonrpc":"2.0","id":25,"result":{"number":"0x2","hash":"0x90c8b9e1e8e51584453f5516036bfe0031ebb595e47c5fd664bd56471ce618d6","mixHash":"0xf8caa5bee858bdf1581f3920c0a700cd25923f194fdafa96e47fb2198779c608","parentHash":"0xf9e9afb4b42515283ae292d4f9982cb0c87d2338c717af437fe39175fd5e5fae","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0xd51ab9181e964dcec962295995b1fff437d9504a88ab944324bc1227c0c94bc2","stateRoot":"0x724b6cd36d03f71b4c088b69f1db0a61b3d9789546c24ef32d59f1dc2007041f","receiptsRoot":"0xd50521034c860197d235df5876ea04b9bce05f69b7e89b96e597d9f6d35b1492","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","baseFeePerGas":"0x2da282a8","size":"0x408","gasLimit":"0x2ff7d8","gasUsed":"0x17a25","timestamp":"0x1236","uncles":[],"transactions":["0xd886baa4d7824402a508487d94b8efed257832170ecb5f5a85b8d2e15317728c","0x3cc701f8f4e4c7d32e1a55dacbf4175dd4a61b4b8f26be373b8f7b0bb4c430e5","0x6208c8da6ad2d0b72a65110f972a38861ab4d12a45397dc6026d07e5623f748b"],"withdrawalsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","withdrawals":[],"blobGasUsed":"0x60000","excessBlobGas":"0x0","parentBeaconBlockRoot":"0x062367f0b23e2d49ad5e770d9ad17b83c0c1c625c3f9a290cd9572b3fc6cfc9e"}} ->> (9ee7c86c) {"jsonrpc":"2.0","id":26,"method":"eth_sendRawTransaction","params":["0x03fa0c03a6f901350703843b9aca008506fc23ac00830186a09400000000000000000000000000000000000001008080c001f8c6a001a8a4a49dcd1b91c376c87d7d6a6e73ee3792205864bf61781e8e3ad19d0092a001069693395fb9a698b257e6c25380f32393bc0cf17a290f9e7fcea3c4ae7b8ba001111dafbfcc0caa0803fc22f697fd3b9171252504e14ec0443f04a2b3288715a001d1dfff9c15b3201980d8f9b958a8ade73e3e1e0ca76c54785a33e09519be7aa001a7b4b55dd68d59685abcd629708c5de44c8e9edf3671602538eba23375893ca0013586020d67ab6808e681c2e6a2d1e854c84e5bc49df2ed34e218eace844f8b01a0e540f5f3ffd4e41807cacab7e6d35fbcf6a544373bc0fb3371991f4c4948956ea056f7eb1dfb537638bf2ceae70259993bc4e1f33b11119fffe4d5ab6449acc9cbfa0c0018ba02000072688a52d55a02ec4aea5ec1eadfffe1c9e0ee6a4ddbe2377f98326d42dfc97572e90b3f4cb4196eec018dea56f4836b5ef860a4404252f08b4f99e40b41300772f022d107cd069bf1040921d5d3d5f77f6724d2d30588b45d6200139319add3409a6ae8b88e662a08c065783d1401665b7068368f8d7a85599f602b16341623721b16644482d12893be90042819a776abe5cd9ad8bbc544dfc3ae0c9b9810f4499c40e6fe77661e6e4dec51214220c80f41d95130a157b9c32a0545ee089c8872312bbe4864545b87803abe1c322a06aa08021117750fa3e35617ee6ba021737246ccaf12794d744a94c87983a455786eb60f6101ffcf4a308de32450ff5a93644c943a2c1fe10807f679e77459f87b192e8f757305b0852bbbc7f6853c95ca537f129ba21c63fa71b096abda334f9a9b0ac442740da04e8b36092a93203f91167dc343a6203e3f446646a2e5ce9c7c5577cbc1f28d717effb9ae8f1c2927c7729fc2061315f155a3836188cdbe27c04dc9b40ee14780a933a8ae4ea75b99057250261384bf3d8fc4a9d24b1a84cfc35cf1d5edbc011ec435e80e85f2b6d4bb18b9ae5cd8b9c81c0d8068a204b1a3f2e5a6244ab4c4ca6f475319df4f2c21477220b0e55cb3ff2c947b59b058d44c4f64d7be94517d011737e44b12369c2a3a2053be36d9c6298fe0642ed2d70cfb37fc3a2720f8b105015ed0bf8f3d88490b3452b0c75e1a9c6ed61d9405debc8e947f5aa2153a89956474494715d9bdb71e729aead078649daec15e1eb9bdeaad1f896eba956740d77ebf2f1ad9ffe9d5fc71980570fb14c360905b0c163d8eb55033fcc01f783fc53c3501ef1f5dd1da5704dbf89b44d000ec69998441c9f57e6bdbe1c0839bc504f989a0c8b27e25db9c726958a4a5333e8e405c957f4c23eb0beb6106ffa1e70b113e2391825e8224663d883552428a88e9e3b8446e95498968fcf05d941d6cf445931e483f7a7a94fc721d8f0c7fea9e677455c7bad3aa829b8c900d7adca4650285573e4cc0b3216565a11dcb43c6b4cca64b677e3f8426cfe617d566363294e91dd5e09e7dac0f4472d250b66ca8ea6c2703c400aee96fd30a69bd4122b7ff7aa77aede3f37787ce726258634edab9364a61caa2beb53a5cbbf67e51b0e08894a29edf28ad11cc90727ca217a0499ee8e1e8dae41dbfee01e34ee82513723d7335581aa33effd0ea4e75528b65fcf944d4a80fb1abefef24e43844b71ae61cce9401b3d5c0fc2341722dbef3a369fa78cd21304ba86ac7839611dda36d5e48b4e7c87e62342350157269c3eaf755cbdb6089e8234e961ca15354836a9b9096ecd5454a449caffb47278f27e8ae1c252afde499c6da661515dddcf76327dfccb9acc4ef97f69fedd272055d8a8e8155e0a9c0de3b2443a1c3f4e71211908ceb0c604f43f99c5f7c7b0379fc63d42d26d1c8715d772ef727dfa51b65964401361a53cdd016d2c387e772696922229d03f2a3f4cb50cfc9b9ab9170d565127543e2b0e301f8bf38979b72b8434ed32a5423685c70b7ab7182fdf431344ce1937462579d1aaba8889e7872ba820a9dedba63fa1ad7a41e3af3d4f828fd327512428405a3f1bb3e14b505538c31eb9e09f39956fb3b8888a8841641f802fa2a61a8abbc9706b0c17d1746720d4cd344c94733862f33224ff2197eeb2a74eee76fad8c1274309dfe2d5b32621378c2b48548d8c8d696acc7e6a6ceb87b6904398d72ebe56471c1fd97cfaa7247d692204c99c7d4ebc714e1c66a0961f5c055318b50f0fb61f15d39d9dc3f7221d3f2074781009c153f37b5f3a1bb5df676eb385a6165e51675e1b3ed243b5a76ec148d9c00fa4101e403185ebe95d3c8a0785b068a862cad792595b9a7a81e4ec2c8fac21dcb9c685e25b2b5d60d720c3f853e6035afe4fde8cd9a032613721ba51cc1897c612cfaa1788b30de3af259d9584978f573ef133f513e07c4ae72a0332e3be6e28ceffb6f88e917fb7e9c2883d3a2e20d0fad2f9e9d36b87a5e72a267871337d26d82eede016e81fb2e55ef03d7e7858540b4e76531a541f5c1728a0832e32d79343482373b378c1aaf5afe81c46b4a23da5cd3132cebcbfd4172b36590ea590d78022067782833176008c8e68920e3f2c939d990ddaf26a7e8722d13400a8ec0271625c3b0f94ca87b82728053f4de3224a87c347821f59cb7531c6f74f10dd9af0684bd43a25069e73a7ee778b1680e1bfac9cf6733c018ed72126f071bbac49e4f1e7cdb374189824f1807b2f83d10b87cb97db55f19bec96163516098998a533dfcff7e167cbb06580fc3613dd4aba3c55952ea6071f46b3eec3daffa35988342af4a281b73df9883c9bd248631d8b88a29f68f2f2eacb572f13e632c31779f1d4509f44c7c122d19b79bcef56e805af9a239073e7c95c433b3c371514a4f3314cefa6cfe49a053492cb4f8a0d45d4362ff163e51e708247253ea41e1e7296edd208e4f87bd34c95aaee0c7d7b2f5c5d1d2ec95b9d5d8dc720bc646721809fcb37feb72158f8823c3cc077d1ac9884707cfd0cc156269647201059337a3b97f08ef8f917fdd0a309a33a877bdb1b060fddd34d7a5847485723fa1692d54a60ed636e5dfeb990533e06c1b17ba3fe7914142b427669a8a4003f26914c655647354e07e49a931ee704feec3c1cd2e6fbf4be34a3b1ac5010f72ee5b68ec4673f6270aa9484db85a682fbfc3cd4ca565c6844a608a4e6840b9729e964a7b9d9e92320ac6d7e36bc55d3d12f09641069feab4eccbc8d378b284725d5d6c4a252e08b3590696d663721a2d8ba87e085c5da876f66fc91e87a690725c74ff0531bc89748d9752cbb9f063857b01ee0070d7e6a3f2085f4ab17a6e58d43b9607311b8397dc6f515c5e3ba74d704fd41f8d4ede0f7479ff1481933272ea705a9794e842aa8fb95d603c649087631854049f7fb12db0589de7bbd2ed726228034f35fc0d8b75bad03e2925a94d17962d74db87f943635cf78f6a1f344bd61e513d809ab8060ff2b7cab383fe614558f625dfc415dda310e7bec402366626e5c0054ad217afd8fecb0cc8a0ae55bc18276aed612f717e8fd865c1a95c1be40c5dcc4b2253ba7c366d605dc214dc461112d1f812c3e04c445330419260728811cb85496383bda7437ee099aef5799c644f2ddfc8da21419965023b3ec2720cd678b2d98eb9295b8d3bd7ecdcce06c520d2550789ae6a65b8de64fd23e372fcae7165548931c40510629d60266d7ba11dd960d78813d10ae08a262ca3a822020e41452d61ec46fbdfa0cd8ec8c3e3dc15f969fb65f55109518c55845e51727f4a85e7bee27ce36df356f327fd55744ed2e5a1c7398030fa8c899f40a86a2ba30878fa2f6f029a3a9260e609543e56eb09a48d82b92da34d770f1848d618728dee7e6aa05ec93429019970d08f4db73cc0e5743b263f6872fb8573112f5370ac3abc46e690350cbf8ce7a8160b051a2a45f5b60e1b40b315c7ba8aab6ce4729fcbbeb4af907f3e08e39e0a5b94b32aec2e993eedbef41ee5025e7e428a4372051ca705b6ee2a646e1487cbd6a2befee1cdde2c54bd8fd739fbec364069087218c6a2ecbe1e0b50586de0b8c1500413aa05b0639e023355db20ea1aefb7dc71f988fcf6fcdd87b09c1d294993fae79c8a8867c4432fdb99d4782500d6fde272153221c8ab10793b13deed56d6d1e32f50c96b82b07a6290927e445dfcf4a27240fa21fa694c97d8afdb895f4ced657fc9fd4b9876d58bcfb3965c548829350ec98e343852916fb7b028a7d4a8719baeee178fe0cb08dfd511a4acdff3ac463d799b43c1248d9addacb478116c70a501fe4e13a4067bcc71d03a36bea9375a06072b368422d96653c02de2fc3e3ed007a93e81765f39fab4e396c8aebca40a024b48c043afd795d23967a841a18dd455eccdbdcb022f50f997e65b6a6de7663d7d84e032c92f34cc682c93240ad41b571f7e4efc9a17a1790023081ee4681d7235481cf21c86b34bc488cde9a81ad10c940b339268d31a4943d86550ff312e72de9c20e4d4dbe1b27f6967557bd6dae3383d226699a627ad79718d281cc7607245f0891e0124ce720306c643a5b0169a9ffeca366654cbbc8a112066d95b9a7295735e85dd7d28bf080bf2ae90e66f182a6a0ec9d162812d217cb310493fd81dee67e9f0525af43f091afe379a63bb0495d816af68ea39cb4b4143106b0a25109f06e8e594ec24ff90b1b3151e1bd81c9f7d5a6643e12bbb8228cdd5cdd620162ce454510dd28dadb77472799c2339e45ec875c02aaacabbdf90736ab91f3136883743b14113a757487359c3aea7c15cc7bd3fefcb7158a55b66bef8e6d55972ef9ef9e88c0a29851c315a355a4bc4f8e4cc6f0abf1244acefd81e5898435723cf8bd97d8732c26eec949253101179c47bd95ae45944bbf837ba94cef22e7072147ec76bc0a7ce3fa0ee5344dbd22632cb67b5e0063f37b3439ce81f57b6c449d0d20b5b434f1974014c4698d10a1af3ac2fbe690721733861aeec6735b23228bb88c1ec3033b5bde20092ed39bd19697ee065fc43697a35e3690bc7aa49655bc29a50c22955b8a2a507840be054c225c90690123362fd40dcb6d981f4d721089ebff7697a3ff6c72625bdb08b503f9803c1b2df74753c0cf4c3b762ef874f400508edb50e05eb1ddebcf1a15260c74d8eeabec1e890f3f4f34eb6ed2f7187601dc10a6b07c85046ca2cd70a9304c8a87bcc92d39c0be69aa575a41f854eb672c75d40b8c3496207a5fe4457fa01bbe122e6da8384463c99435a50600698fe7200b03f8e78f2d88d5ba89df6091f8002d99e1197e45def488f12b221364a4b721317d25d1376ad3592703092bc2f79a9b258eef04bb62d34c87aebef9e94b81a7ff650106f688be3b5c5bb9e7adfbde74547a0c67c7d788764888bf93bff34720c5ed0a97ce2ff9ab234aa13a5bd6f7ec3d206544cb185c411e573efbfc9a532baa91b4a31a56ed3293d44ff2ab65e993d46f82d04d5ead13911590a022a9072a0a1802b70580be2602bdd7d1204c6c08c10e78d4520d2e517964ea6cb923972f43575fec0c31f84f7279f7cffe377e8e2f51bbc7366a3e5417e6e60038270727cfae4c66a7b31ccb7e13bfe63545e60c524ac11c5709719e59bafe8dafec372e155056c26028824a272ff8c48a780a2db94585d8cabad7a29ef604c118eca72369382304c26fd047a1618408ac4fb5f8c10a9b215076eed3218d67590f93a33291bf6db231442343f9ca80e4146f82c0a0d941948bff731fddcb6312a572d7246b67e7b800d16ae2dc5c0406df8591e1a6c6740bab5673d1d17fb884474096a4385229b3b63f44db1190ea0e058dfed703e2961aed297b44ffb79d0c44bff2e6e14c3e2df4c9da9d6a6919821c55d8d24efb1adb82ac750a11960d587cbe82703c1ae6e6cc7fb6410abe27bd2838fd9e0409800d2c8e3f8644728479c98e849906dada0bd157bbd08df602bd1bda112072bcedc34a239035de2c08c131146724b48dfc2398b520f5d700a65c5befd25a15d26b4b39dc790beff36bb7a5bac72bfa54875bdd060370cee2e03a464bedf2ffaa1cacd5ea18db92ab89ca233c64d1dcee75aeda20309330eb9cd4b531d4f350a8aad785df58f7c9546078a5c597210bf00dfff1def680d88fc53260ca9a4cf3721f4c56d454eb19b7c72e1c85b7270133ff9fe57ad3742a6e986b8c05b2d34ffc104424284acbc6f4cf2c1f873726195c0f02ed250cfc351c8a54415c57df2871cd3ba3155b5a2bd05dbd92419723d07ea1134428ed99f12175c88c152a44752da430cc70f99c6d55971ec481b5032924b37aa9d0ceebaf080eca62edd293ff5b659b5a3d21a8aa6b77e5a9cf44abff103d7ed705ba2a5409df02b09308c42fc366a6014497aeaf555a2f5ac60728dee3c03923ca81a13fc393a9cb2f31544403993356b554cb4bbc231d54332341e3d79123f3af7840f4a8c03d8703095b48e423bcb1033b5180a7fb85c913d6ee7bb0f88f2efbad36e6530b2129cf436bfc571fa079d1f8cca5d832b28f8cb04899e219715d4cfd1fbbce67c6f884e27ceda21448ef3b787be5401799a1aa920b9120cfbc91984132c168470fb243183c3aca3530a76062852a2834037e7a272b8d6b4e821770b96e60fbdadabb86a53a902ac34a1e18f95cd79371ca761a3721735bc00ac52404686f5541fce062f3691cac74a2f0dd4bc17faf0c9632ad23e053e07470bd074e479e51a3bc9075eb45e9c0c2a500cbb8fba878fd878ed2324b52c7d911256a588a8feda46f8daf526ba449390ca71327d37f7a54f46ab7272c5741e1c0a300730eb52d679e180ecf75f08233c9f12460a793143a2f219b910715f12b230d969d1c1a50d45c6d040aaac088d955377d1769ba2e6a5365ec81e2e937282d1f145511284838d8c030e7c09efdeccd7350c5485cf81f2e5f41d19a0bf8afbc6d73d4c0b9ff28ab2cefd618bc9376fe9e5cb1632f6b6f46192977298d1fb8ddc28357bc5967b42713778814fa7ea24340ddd839506742e0d3f0e35fea6bb9e68017b7c1a5c24dc33bf4b162da0c91eb6cf274293eebfa7c6830872a6a02d36ca7c87e37b50b3968efaefe19e5429e171d2dd0b530e6409ba1b307206bded9aa0c36595e94c2fca5b67a8808937675e25843fe82564bc940b0c33726248fa1b27fab046985bf599bf94267cd69241ba57c59714b81d57672cd19372e7ba20b0393dc6b602ddbdc888ac2a079caab7974801c456999c2eb7eef1cd725bd0514c86244bf37b8f0f05828f9da2f504bf55dafd354a913e72a515174772533d7422b3126bc4571cc3c06b91d42880e700cd65eada0e6cff139aba1c1a2b34baa00e005ec1ac4c2c164254c2bb1712966f70ec72667e9593b94660534072513a0b98991261291deb50a6f905dfe9362ccad9185c62a8e90064012bf17c382c0440657adb4a1bc0bc0c334923f825691267bafd2d718acfc73112b9897172dd29a415f38c343ac6531bf12ce83e5ee7a8daf5ff587b635a2d0b33cd5bd472c442ed2ffa38c329a82d30133ecfa90c006b90393d3a1fa935ad668446cf622ac81c921b3e05f18ae911ff43c0db42df100dba5b46b733e003071941ed98e372599ba08f4b3c4cf28ab7675b46d66419a627fca52a381d708bedb865140a8672f7e51646a70a7b51bf17a22ff8ca637da5565acee0fdaa59f241ec9b41fecd72987d8f75b5ca47147fd8dbc9d8de20bdd518b8a5442b1f473c7933e192e3825258ccc74534ff7fda612f270cf4dc9f73b5a716bdbc9314a2043301665bda5e54383bb7bf63f5f8a6d206ecdf061ad31295a2bd047dc6e0fb08c46123aa2234723d7256e4985c2cd91606036f9c6c15f5f67cce463659f5bc859575549d35603ad473b02139a554046dc0d79fcfdf96a86a3b3350d6b5b2877c8cb12768a21e723c48c3baff715c37f1c299a06151146edb092f5b633caf24e3191f4528b58433b1e30cfa3f15ec694eff5892925ada62bcfb87390e60797d4b68ee66e7a6ae3dbd7359ee16985ebc3a726db18a98dbd6e066844465c09f86a4e22b2fa7d2a05651f15d74473086171e3c8f4af7b22ffa56c379e2110fd1ec1deb891c1278827259e719a8fdf9a7137399d90e1c9bff5eec7c5d4cdf5788db860bfe02975bf87295d89e605bc9774a3b527b5cac182d0d810003f33f22bf758fa427eeb37e4f02f97139511c2ef8ec52f30c324d09f295dd7de9b252881940586b0073e68ccc24f5b5d9c972fff966ec9d45cf9d1bb0512a7a3439e1f67f2ea0dcc81ec16b3f729f058490d58f2acd3aae051230beec665b60c3f0c579889f64e7df0912f3c91238880c9f26e7978379f4e3f02795cf374ae79898a271f7b70b6c77a9a158c672f593d91ac0027106ab94bcaa32ab28dc921fe48b806c68e09932b798e1af3e722e21ad5d2f539cae1c00daa509c981395c1539ccd6dbef9cc8f9f2dd926f9972e8a4529f84654b7953d8994c1492f5253635f53d10aab66ae07ca988c1388172c2d58f74ac617a85be822a5d37d6e47e983153623768d4ea5d31d2d9d4779f17063c4ce9b3936caa3b69716d1310ed7a482d766799191467e1c25cd438030372cf544b6d07c92b4910bc02f128d054a1b21f008911a1c26be768203d170a79462fa855830312f7714016e929a89c643ee4c704d7bd413821ce08f72951834b72a68e79873bde6fe4c231fdc60a01cb9a787ca05dbbba08ce895c0f423ef39e3502ee325e44475873e79d1b7e0b5f7d20e8b0079033d3e933c7b44887aaa91d3a5d3445fb6be78c04077c295441ff168a1aaa4616f3e2662419541de2a0be343ed0c815e533467af50358601b21a62cb532194cc7954691c4d0d823f515414b3da141d7de4e4d2c49026e89e0788f719958500d2fe1bc941124235b4e6709bb72a928dd63f3098ba2b59f49306b0b2cbc106af58e8a95cf211187e4826a11ec721c31814181489121526fdd4e66bc97cf01f8b7f58d2d71f1ad5514e5b4e83a0206b36edc35de1cde3ed5e5863b3476de62f5f35e818e17e3a241c06f70e31e471008cffd522326473d6e959da122628b5f8d51d7ac46f4167c9ef5089b37dd729240981ca881965d4f19fa232ff008ad9972d1b1d2a8c8fbc01972e3ac7efb72cf61de0c246a7348f6beb40fad16398c7d6b814282eeb9331016321f82fe6a4bc867caa5c6fd25f1e0b0d601f68cd8bf87e7b9a4c8e8fe21f7bfdb949aab13720288777b16d9afa565fbe83e5525322977a21206c36a6f9535fddf54adf5907242c2367ce2fc9d4932d1cdcc501f5613e5417bb3bdb013dfa536f54cacb45641a63d042c734b2ad6c56f5966ee39d2b63918908b152108d60eea06f6c8c9cf668acc9d498e6f9ac388062bdcb99971640e8a1d31ea387ec68deb1ad59e5f85729d3905edcaa24b1f962e511e38cbd3ce255f0d508704d8c7707f7cae2fd59c41af6e293931544edcaca01f47796596f522c65314813499b7355f741c27372872bca56ff7121ab70e09e85c57077d86972a49fe2821136ef2d263a2b14e408910fe4c3f8efc74d844777f59f088b34b9c2cb2ae8de13350ec0eef2812ee58bd2f0f82ae0790600fdd187925b87802478d6620320aa567a2b115f8391190bfb7091f230a100179896c656a6fc879b335149c9c5e07740063a52502814cad3af372e3025b48e0e33fde7b0e5260389dbb28bea6ecfbe8a9a470b0dc53f5c3799f727002bf5c66ee40ef2538104d6b0daaad00c29e7caffde4b1351d1584709f0a7258d57b8621574beb30a6205728ee16d7bad6804fba64e10be18cf7c2409d49723af312738cc611e2b0b6c3126811d2672994c82c00bf54db1bac5b7663b7ba56cbd043349879bff5badbc5532750cfdbae339403afba10a3a87e866e4304da50cadfd767da0f7fb7e55a1a2742096742365f4caa24fdd29b1305f076b6f6472005aec5fc40ba5487351bd16c665c254e4c501952044755b9c0f15ff0be1f647212e007d02ddedb296970978b5e62c76f15399285a3b989eeb0208b2a7b004c72ba6705b3723dc84bee4a5572686da58a6ce61047b82fa074258f63fd2380727264f6a16d1c84ff152a79d7ca9672871d6914a6240ba598617472224373969647733040b9c0f29ce50ab142bd4cd72488cb22df14a62a47636b9d6580424b9472a70365bd401a130a092ea63f8a6fcee14c3703cb7092cae5577664388da5b23dcc0441707b6c5cd56dba25950cc67384b02d9cb5f0ca047ca7b56699be0942723516a021e56696b01bd5253372e8ff110b0c2b456d9412294a9a929af20fca72e46232cc5a5627df7dc2a4bfa0802b26e5c613e7458c0dd32944276d8fd646722efb281c62143bf7f02a38cda932a8e726ed8c60f454da9245c297992ec4b4500374d270f0ec4d881ba512c721fffdc2882800b5c75f5f03e52b29670d491b721cb6ebe82e95fad335bb8baa10242d230dd7793b5f8dea0c961265ad829f8c5df24272a20fa0b0b858472610d872b72aeb3f8103d9b7cf66b5d7405f88654e317a124c64b1459cb8402c34dfdc09b840ae431b47ffab4c9013e40d332bb17b3b3fbc13da02735cad96a89f32849c5a5d6d86ad4795c05dfc9685898b9c650015d00d95d29e8355ea19d2a6fb77f6824b416fc1289ad1dd8efa369da15e71f328fbaa96845d24f0eca7de9d53528e375f7240381b841196cf288a12f4a239f32461991174a189b2e6bb52ee6e7a4e9eb19c57de784e34dbb0837c0d72d1e3c372b3b86a15154034e40d303257b4045ff8bde8f289fc81470f7dc6e38e79a6f44812c49133c7f7609fa8580405237d7a9f16bb04502d2b791ea0862867fa191572cec114c90f38fcc1a79978a12887822e4658d95889cd39ebc072a49ad7bc4162b7c049f5ede25143c860140082fb2c5caa44885936c36641e31ff3de89601e725219f5ee0140e05b4620e84a599760c88cba4fb8118614d5a4ec9f54b38d297252216a9307c1f6b6f0ebc99df0bdac3ce9641e31ef375b566220c8b593b9855ce64919a3a79a8414030fec491e9ae92757971bbe6b8b4c5ca24600c39255c7724eab4a0acfb2385586fdd329617f292deabd7b242860c1fef8f2aecbb66ac01c5b23e9ebf14936677a048906df279d169682d3763eb5a3fb704ef32057519172da43abb4d936cc82ea14805608168abd40b06c5c0adb4c7c26d37874662ca172eaa2ee1a570cbfa481386b7baaab9882df8db85b2868e4df1fa11807202aed727b50db15cf97e636f2c279eaef2f292ba8f6e66da00816baec0e2da860e4c15633b4ca4f42c0265dbdec838b0a57d2613b81254b1005f1d706aadeccc65cc572eb9f32b1982ccf47e51e56900c56927185fe4be03700a20c152ea47de9954072bb75c9f5d2d71a172bd24d493b855a61ca80e8e82c5fef96cdb0f42122fa017269e0dd2873d1c8185030d86b98739c54ef81d183f9c61ec2576565c9bb8b0a724c137a70bbad1f79c1ff07bff017512e817a132edcc3c00884c4dec1bb99ff72414c4cc7753ee1eb825a98e8a5d345351777009787d424dba63793bc66a0fe0bf4154eefca2241a4736dcf97f30791feca1bca6de8b25c95bfdfa03f174d5772532d4d9aad9e86c4cc3d65d1cf7befa5f38559eacbcdf8882d77f21f77be530c32cc94e88b10c2bd9d7eb381b8bf8b78fbf3274c82cfae96151da07bb886556895836843c93ea80b08be7aef9cf696cdf1af4210baedc39fbb878ec36298d4726ba782f92c32c913470a7f6af3676cc836eff4f3eed70be52ae8c36ab68e5a724b2cde49f5db64fc658c682de2805b033621f92c694b6cb12a147063e0b2a057aabdabb2b6d051e588673d08e77d39448fc19fb2df679a8faa47c357fde635721c7128fc3075b3e451357f9ba0cc5cec8173a7837d36364c750f5d0c9124ce2ee96983e6f99d8ea409b505e76bdf184ac6ffc459b7c478d13b5e50d5e5c4ef20f2b434a729bfbae36f830ed3ccd6aac165a5d0ec65dbdc90e0889cca6bcb2449e3b603d1c2d40b9e68fe2b47d1f57737a9f9c31074421e54843b41df80cc8128f7af9c05dbdd03dda66a25cc0f98e4b69d285b0813fa03ad211bb07df272ac217c38f8f1e3ebb344022fedfb6b62436fdaffd2fe11eae58f5749bd78318c3f72bcc35fe5ddd710240e5e2a37e3bd67cb1f4ac3f017569379e544424dd81e2a7262841b3d4844f27f0ac608f096448ad16d6982264d219de4d08da9506c0e3c72c3236062aa3e63a564599f0eb81728d4db98f5de21118bf781d724f49fd9535495f73bc9b8415d175b8c5e9769ef8636206f8888629db260dc5f454166cf8372a2399d3cdf8366de358d694b3327436433a8e39065122652b3477c957184c47280a0feb8de537b25212afbf7b04dbb335ca0ca441764a726d6ea51a863c4f572b4af1f6685ea0ad7860bec9cd4c67d88205d53923a38f3c4fcb89edb8f5b5772ef0684e9b644a4550efee29c54f6b5bbe458f9adc037618ce099b52c388f9a72fa308625b3278c33843612ffbefbd61874fcdfbbfbeb66d78576927972cb8f72d95103ebc5cf1273154ab8e5fd7dbc34593d7461ad250c52dc869af8c6da935a3b7090220c6d822c867e772cb11baad81ed2215382b0666174e06a0a1dc85a72970de7c1704fa23285ef7b6b79de9a69577a5a5146fa7c8a3f9cca5f424dbb0a3e05c1007f627efcabfea6b8cded63c5ed68cb24e42aa05b7c6ecbe40f7c8d724238d032b301ded4db7372b33523e2078725265fbc93e2086fc85af961586c72e860f655286372a0920da4f4c2aa8355d8ed959c5b33a6dde7bb1e477459d37292279829c2c755bd7467a08058bdce82574227cda7739f7e4c2b0cddc5c02c0fbeb80be497a1b910192ae539e0f7a8760527f546f2005bd4dc7fbb50797d2672d5c3c969cea10986714ea8160dd53436e6f990e11e73692907fcce133c82f272ba5742f5986cbf61b27eaa23e02a2b5da77fd852bfbc832b78390f3fce4fe118d616e474b467390b64db5468f69a91db6d9dce960c55d65fc258bb5108c45e729038105969f3a65d5220a07a9cf13ebe62d45a72cc5a46d6ceac9fdfe00ca3728a782dcbd3fd3961e15dfb1978c884f99f6b46f9c681de6e445fee766e9808688d1b61e56118e95666a50c4da4c7dd942d00ed12db43522987d1297ddcab293de6cd0ed2d611682e15eb2d8b15856fd34bd447fe6ee39cb4a13ee5f2f437555cd20d09583358a1263e8f69643f5610ca264aa8c93080039f279ae2ceaaedae72c4ec55fb226b944bb052ffade228ef7e4d7e94336b5d22afd01e1d1e2d85b958af89352d4b00bef42fca886c2718e19f88cdf2f3407916e900523c09f40aba3fb66f5cde92a22a20a11b5ffbc27927679feb80d5af9c081afc4cfc73083a8d69c43a75dd984814f31bd1b59fead4eed7e20b34a59eecdee6a65289fbc13fd61700a53319e6e9ad99eee4894a60c254a1bbba3511ce92206dadb248f4627522729b9cb98acb2942e0aa50b8e2292de008804ffd46f316a7cf8fc3f7736c03bd722b03fe56edb355caee9cf6de56c354b33f20bbeb4af8839a8b95beaf362dfc72a6f873c5e586fbee62630b98780a202d9ae8a6c91bc5b495298cf69d60c5946fd8b35c0a26696dfad6210eb79a862a147cdde79d5d88f3214990e756cc41f1612b5cd12418fb17617e803df9072dfd09a0fdc2ac792455ccd41781a495ea6b728580508f2387fcf8417b3129a555410bb2ef4c11b87e8aa36f9bb678b1292c723add738d487b205a521a25d073b8343b51187377285ab1a8d0b7e74c791a7d72d97c97680578257520f007181c410c009b24fdcdacca2aabe09254be17f42c7291d16aca08a2f9628ff8c03e992d80c7cc1b758315627254e5f40c5a448d21335ee6608f02e0b3341d52dcf1e561da6cd6c7c62e668fca2f0057623e3a3e0872b25e7b5136b8a53fe7e17a68b77261a233027d99bf6db72a0c15be5452b21c627e9ec132bb6264e9f2ceef3e7b5140b3486b63902fa0a949b2af38fb30897e72977927205ee10d6bafde20a66c2f338f24bed105bcf2cf70541071469c143a72dbb3628f5248b48d6f712b70f5ebbce46fd53c547746d3981ada6f3025bea92d5b3c35d890b8a484ebea79396f9ffcb7fe6d9209c84b527fabe618b56e4c1472d4fe1854560dc6cd175c55c6989c7390c8db274ae14ce867a74c5ced8eddea7263aa964e5c128a1ee98be885eae5fc54a289a5b77b634059921212940da6e7721c06430e5f6ca47f3e640891dbb0eb5bbb29bee050a17e2fd7cc92f0ea28cc5b6e431a957e3d71b5a081711348b6366bacb2215b24f439a752338e967230e20ddf1406a11a0bde316f066cd309d156a78209929af5603675fb2fabf4f2fd9045a3f4525c674dacf120a5244a0417d13f86d64944ebee46e5db348ab40d25eb724b241ce33fef8f61e6e4a52fa2a58ac7df064c0d431b84aedfee94be59981144626537bfac9b8f2a6ca28be676e4aa201b822a1ccc302c5e406da0ad8da82072153d3d7153b9f284ae8c7515767ac353b0299a0b943223590c15898cbea397727abf24c0f55922506a9cfbe31c188ed26751e48a50aaad92621f9553f9c6e9725f93bfa0d7441e14c6daa3dd3e56d087db79c27e859db248d2ad507e75271d72206ad9b9dd57b848d515e944dba5a3cfb34a89f46cc12f2c7c841ac7a8657572dd819746a2b606bc3a5b63c46a44b66e4bffa0f6f636a1a7881d3cebf9013f723d14449a412888ae63cd119ebaab4a56b91cbc2f45fa9be330403737b4ae5f7257c68bd8033bd4c9ad3a50da86f7a31fbb7be3288486d207d1b6eb790017054f7d88db9cfbb10717ad0dec4fb385f4407f089f5dc9f6faf20039ecbec9c4c70dc0475e8f61177131e163c02657a854d5031939f50d4ad56b5326eb4b807c6d72f318081df9ef31a11b4f234452160738939bf43d2b5664dcbc8b5695cf0a06726f41b35e3f9c1d6e85c50f0a836683b97e2cf2c02c3f7b912f9de7e53ef1de3f1edc9774158c44739b0025940404c795c67adb34eb34b3e93121c24d308c58629d96075b3e5ff5e335361168db90081113472071a8eeb5f92a8ce5bc00e09f72e5c6bde499cba7a6590bb8615ee4807d72ae60d7116545b56f9351c13a9514395ab96da4a9122aa8a156657139513acbf9898599954ead8440d7433c6bf8ac171d78f71b41463280950221a6416ce664a29a42344aa5ee1c928e59135c1990723ab0a9c47cdd13747ef033b37f4c89e8fb9eb09d6c23bbe8e47c6dfa34e58b7282cb57bda18782e4163254b73670e42e917298c4a5397554b17af92f8adf355640b2a0083313fe4a014240b97c6d9baa5cfbb8b244f12998ba27b7ae51b69a72b9f7eaaf0c5c1472e47915d889d107be873d4169dc5598b81fcaee8e12965672c9898a1982d6ec18ef78d2bda3d43b498f29ba7d0d967877c3252327886fc672d447c50f987da66d5d2a8fb29e8b6e5db499b7bb32eccede8eda2efae9fff01981838c8127e955631afa6008e4d194984408c8d72e632e1774c43e86e3b2ac36fe65dbdaa9c9819434cb52dd60b1de096da20ae7b4b5270bb9dba4d991049e7232ae89a625285ef2f8ea98ee2c0dc99a43ca09738e1a6ebf4834f08c8490bd7223a9066d42c659f9baf72a45c00beb5be20f4209b8524187d6bc0a7e4770ba729dad63295dbd25903a9aa36b6cd38ce878bfa49fd47484f5fe2324b610ce6f723d45ae6eb7d3a150698b570453270151269c16b792946beaa3016944732734725fbcb2367412c4c7f11db65412797b197786efd72661a63ce1755e678071323df8fd71c7d362748a15de8fa913a1a8b5859133b172e00ce705377a6757e8976f68abb5fde58cdd1c38307391295de7a5758aa3c5cb0d10b8e0f75f5d336316721e044c0062de40b9149e2fb1c08de4cca4a3195cbe060ae0978dd67516ac0972a16eb50b554341caf7fc83b94afe5983de2245b77300b46bd535c90b1fe63463766fe0b6548ba701d0e9f75a73ecd47a677da87fd3003022de4564ee7432fe5a82c19cb326ea8dbfa56fbcbcb5ff94582f63aefb5804a1ccbe9dc083b0cc072d25876e96645797f6f794e9731a779a1668d094fd4fbffbdac2c03e90833e0e5005c075bd8ec735a7e2cd3116f2797a83480591352c18ef8148cf9e25ebf2977226c7f6c8fccf43f1ed72706003637937df62d5e73413ed8d3156b3f9eec65e7247318781c99ae0d865f1d265dbbc558a5adbf4810d655d76230a5912c5f89a72686d42566984bf7e01970b239cd7d69b6c736dfc7ecfddec8e58a566e11bfd604b2aa981b3b419aa854b3576d306b8e5dcd3449ce970079b00597bf7f8023472e2eefc3cc6e89fa99cf8805e7e86d9f5a604c47caa6aa1a00c046e28c6b52872fb9917a14d5383f876c506900a5c70f2c79b4aecff426ec89ca66dd72c9b483d67fe3acac03d5766d8b5904914b4c57263585a7994789da9f34b7fa62becf872eeb7d33bdedcdf46fe42029dc75122edeafdbf68a6350888c3f10fc9907ca972e95c7e0a74013d399390212662e7b53b09b145747187c09f6919a35e3bbcd1724bc1c26d99598da08a948c55eb7f29a722fa11c13b7cb78c249029dc56b21872821bc2a034b8be815056ea0d5cf606c9ef23e55980cd711ffb429bd3a5c0c6362c6c6d8db039825b87315f003a19b2380d0ed339a085e003b9da412a5077fc72093d7bed86ed1790fb07c2756f7e07ae9bbd625cd14fd5c3d8bb2e7bde8d2f7204bda7f2853aa30465d621eb995b64a360558cec15ecaef0b2aff3d5faeaeb605c6d8b1d66d4fa4b8a1fb1b76943e05bb22448a8842898d312ba4038959e39270896b1a5271ee253cc3b609740126751d8d537624f6d5de87f0642343f2138722150c4d7b238cc17369349a8e66d684730565a9e2d42744ecaa908fdedfa1834e5d1b711df9ed079f57da3ed1a4bee036ab980308869d8aec033f83c1917642bf10ec86cdb22f1ef00b1e734662e044ee3559dd6318e67b5523d13d38e65c700e4fe8de2b8da62eb65a36c3f4369d72129afacf374821906d82934d673c26572e88eba2bfe314efb80da0f02d70a9871c9bfbc182c48891d376543416da47b249869c16cfb41c4b46964eee1ffd9eeb48ed7954d340e72f8642ab7b1baaf09723195c344743eaa9ae9daa6005a8bddcbda226cd8195fe458c967fea8c4a998722f3a81e3a31b1308ffaf331dbb977c5d8015e344d70d17f0836e032f498edd720dbf648fd30519744c41f41feb77a27839802ae0b5d6a0049b5179114aa77c7232e0c411bed9b0cc4aa5e8a33fd53c065a573dade4454fd5898679f4936cd02d3683d650fe0aa170ce0774b8b2cd1d8229719a1b4aebf7d7fb442e8883f67b7239b2834517f0bc00a8ef8f6afc6e8dad269cac64c21bee06da66987ec525e8533c876a838c7f6810cff6ea6d98b89e82da6944035fbb5ce91c5e1a518c5a4f729664e6bd98742d08023456b5151e1fedc398912709d03a181317d4a17d89c672a68bae643b22a2308f026bbe94932e485145bdf826d56a5e099e68cb0028db29e9660d3221fc744993d0b13505fa7f684fff16b589b8a23dae5d40318a66fe46396154be9cdb6efba8d9606fe17e7cb103db1c2f51edfbe6e8140fbddd53b5724d8d44c9a2146832b021520b19987f0aec64c343bf5b5730d5edd69ff21023726b228d54135c5aa8f1a3df1ba7ad8380f45c9e268910385312b1d147f22b3d72952c2ae67961e8180ec9a81053ea43957e8e0f082117a3c410a47d0627a01768dd6f89bf6df23007e070866d2054bf9d4d23c8cfef85153bf62f90883f841764747bf59ff75cdbe085b7a596782178bedd096be8b359b58f63dafa472d5fa22ef1abc983e5303ef6335ae05294a8a3dd930b97f2c3028dc0713c0ac4add302720c3230f181976df17df2a71fb8743c029dc295fcc92cc8f8f29e5e1edc849972983bc5a1135899dcefc0f65defb2be5168510f768c9553eaf1bda30daadf0172f2d7ef852908cf57abe1f63116387f6cfb5e951bbc7c790c85a7696f8dd493722d65cfb828c4615dae2965e344a20338cfd0e3a342520b9921c07bc6790d1072914aa2a4593c373a3f33722251271a890153a0c59035b6df1eaa9ea004589c53ced2d370aeaf37e12724c667e5207b427362ca120a650be3544f4b4da5104b6836260a81cd8b1e7db50bdfe7b0f13bb81d920a9e37762eba5fce01a371a6264447164a026bde84089996c89218db59e502d9f14d4326dadcf261798342f6b5555e138522c51c7d5a0dcaf958c4e762230f1a32910184abaa23e04e9218820f72fc19dde735b11810adc243f1e508f86739b59e7740dc2866d2ad6d8ec918df07b95f2466f3a44150e4c8f9f247babdd78bc08243cd141de5266f076a4d804f72ccee7b8999d9c3acc52db9a0d7593b51cdce3cda96157001528162bdaec66972adc48546c1283855960623fee8cc953eec64476cf0402ec30b0acd4ab4c57d72befa4174f93347e83a66f407c4f1858022e1fbb2b5c747fb716d27c99d24f672a18cd6bb86f5f5fd3ae8e400a363225844ae19947621f4438c8f6b2e55e00637638d24869804f93a4e24324b7a25d7e6320dfdff2ecf73c65543eafccf35b57212f81f1007814ce6252f268ad368c7aa1b2cff5a9cca57a387d7c683a879e17257d4a4e0d4d7f7bee006ad43d4bcbba80dc606e77351cd0c4d2de9f4dc189672e242bb96c8a1ab7368c2db5295bca3d528e5ab7b0744a220a6841d39959d46087edc02dc3cfde67c43eab15ca87c901a8654101fbfaf8481a17efd3b76789b50f0ac2ca6c56cdcb1cc3b72cf35253d5758e6eb37a636c4a46a856c6da2e60272bd3b9d496165d22b7f79a51012f798a77feeb2bd78cdce2f59689436ca8a5c720c1ccc643c4001247909c1baf7edb2bbe4aa8931771870e3d4417e6c6fcc71724cb1036fed620cb0a51b9fcf2cd19ee36759b7a8610bad266b7e6d206ac695720f8efa04da53dfc706780e0c23411d9e56a4b309638da3062cd59c7c73ca7c721521bc454a858dd15fdefb0348ecc1e26ba384c879fa29ad9db2e4955ce3e96cde71f32d24e06f26521466c7c43e478af6fdf4e90e41da1b1b3013073568907286ec7776a7b9e19c283a8c134bac7ca167d327cdbe66d1602a461c030e3f342d18a0cf7882daffafd0ae54ae62fcaef3ae5543756767b634393f080b78aae41906558a0632f20177ebd55852df107f73aa806db632c8e209e439106980a9d67299349bf3f7dcc414989b65e75c9b394dd6c29acd8896bfcc3501bfa100ff782fe2e21888b3fe42d2c43d56365545c13ef5f9a72fabddf4160c3cd2087eee6958c75fb77892f7ff154cf177f6fa13e13903d2248dc32fe187bf566332b4d8495cc619ce31a607b54c52e4ad92248095fbea85f41bc6c7c6eb3b50e19efc9802729760ea2ea297af3730dd40475e10fcaac9783cd1bb12a149aec754adc6df5e1fcdb7269353db119461bae560f92eabd9d70271edbc071dcd35cdd8c5088f0655180f1e1a5b31fa898a8eaafffdc7c082f11f05d87c91a402cbff3c52d548fd3e1a68d97828ef20d40ca975af5e7ebfdc346dc99de345f124d509be53466d932018829ffdf6e37c0406a68aa987a1b1a3fada966f11f00851d02c044609ed55662ec0e114459f6fc79ad88431809a729a1d83420c8fc1680e04e78ce7bdfb2e603962ca6fcc1128dc1bf185855ac575b182976c737ea20fa4e9807b933bb43572869f430e81dbbe8cf16c614c6b31db697b54f950b30b7913bc2eb4c863ec32722461d18336673048a04b44c8631e299c1ebf875a26b8c7a3231c747182c43272c1ed9154bff3bebdf2f4c7bf5140392c5f24e690c02f5cb1211805031c628c72d45856bb04d4548af80f3926f9ba38268fac522e2c3a27de5db2e792553e6c52de1d5871c21e1c464d3435cde1cac2e85d1539ed1b14ae71cfaf5bfce00e1172ef0590a41fca48330c1689cdc2241fad3f44e42348f18b553cffa4a5dc34e40093b4ae4c263dc8d5ebf56e40c79d7664178b79205914c425e32f16e3a5580472a9617abab1747a710d1f16e4a9819b2460f30e2dea83d0f4fe6ff47f98ce08604027ea614b6ddda9423d836cb6734d6ca9cece8e17b1edad2f4b3e3d8c59391a86aa1360449f1b841c67c0545eb81415b50b6648dcdb6818cf73f845971e944274f7c41c7021e00be8c9b754c422b9ebbba8c544d4f538b5d5dcc6610731d1729be990274dad7d665011e93ad431aeaca24c121b37b94e653586dc0665b39a728b183c0e4be2e3136224d2f47dc28fb53d20185b6f3b81cde9b8c8f530f1380d70f9ce4e7ab3bc2b4c437b7339e7374b7e8bc8da0bd43e8ca9f9f4cfb9e01a7243d466d4d03cdb4408aa2370426d871c613de9956c48b0bcce23bb6f1e19567206380b3ad8613265222356f8688d5d12318608e42105483185cc39896c962a72ec75e321624031cb3977ec56ed584081867c7c02f45fed35aa949c939e76c1183c276916b7b65c292b40038f49e79e1cb564d5d6f58c29519efdbdabc29b6c017aca55311199deda80017e70e88d211c06564b8facabe20649b3de0174ff3472f5a58093eaad6d4d52f1d6d6771ae4407188e8c562cd51714973eea9f0e7657212782c675909e498ddf811a3880cabc6ed9e12b33ced489d0a012a57b875f77221f42587d6890d9dd6f0792dd7b482ae4c16f7aaf09d421675a3ae8075fe9e721270229336a1b7c04234d3fc05fe68103b7d3d805789dc48d1b830469ded747294a2fbdebfa632a04fd4b570dfef02f0899279cfe0ba464dcf375950b1969242563c1cec2928c106d6548d9bbf4c52fd4ecbf7a3db99e6925d3fa261c34697724c4a61100eadb464d891a12446aa8bf64038360294b85ae5f552009eebf4ca05df9e41cb601dffafde4487a19b8534c0bee081124e6295b08bae3ae6d95ecc15539b19d3a6b0429f077b5dc6e1552b3c49b8f8a88d8664dcc9b9f62da37c2f24db2f521e82a88f0bfeafd3568534108245979c03203ca3477d1e39e5c4ea6a247ff499f5e9385446c3cdd1b9cbfb75d8c3a437ee71e94bb6659e786cfff9da13edc0c14884dd750d3d94e174324cc5ffa41561e3fed0d3cb64674ec08e8c907215e23d299e15b0f3ef607ff9a7d039a70a9dbfe0acdcda0f204f1eeac430f10a54298daf2003b7352a0763fafbb536ad2e31920314c94e7948155db91663907225be3d2e9b1c78ef9d8719b4cb0cb540a365374846c66b38890f58cbacc1ca331fe649bd9c043e311e21e7443cd467d1565e9c5a54b29bc3c76701e4eed0d872d568fbcd6fad01e8b1eb93268aae23aeda3b3a948ea291d1544d8751023a627223c1fe17fae1cd476e3207371f8f39dd7fbf3b0f1a147cb63738bc3b0ab76972c1ec075a7b26ae2ffc04c7db239f767d92acba241b3f1f899c2ecceb17cb8b715a0ada0607e5b5669cef974469a7bf4aa05443212f659e45e43fbc4d70066c2cb283c830e400f83d5628de8d81d3785313adc47f1c88eefc758f57abf3094f72199b9602f85b132ada4d29b7a191570d515037e3d703d2613ee5cc82d5c516728a6fbc4a72944914fa65044162b633c9e9c88aaf93ff9524cbebd5555a9406347ef3cbd4ad1902cf7faa1c54d1f29c4ffb3fb2859248c5454d91c02483d1bc2afb24a35c2043ec3c0427e85441f28f9228c7bdf55b286ec9d1d9659a6e8aae3cc0e383d932fe901150955fdc47e9303b98f4dc2befd4ee68bbf26e8eb217092c99bd8e02cf98f816b856f1a555d4dc408237c13910a6d2b1dcb4097670332172783de30eaeb4729d67511bbd96f087df94539b8b1a1c6f02c40161747ed4505b0ea3dc7b9a6de934568d6c21eebcca7bc02341dfd056c9988a37ec140d08fb72c3c7177a7be30074c1801125585b533a4f90ce91ab5e3f308a4b6df74cb9e8726c083eb8cd104ec2bb6fabf42d229a5608784e925033e93ef8318e63a9795a72722cc0ba736543dfd8421be16765e56c23a1cdc3d5dcc9f4086ab5f2ee558a726f87af5ff3a2830ed54e142b9cf982861292f4c687792dbe36f059d21fbe3b721fa0cd610772891c5fb57ec1df8713e037eb850354359ea1093e38970bfb5a72bc57fe38a1ff9d33eeb20311a242bc41d6afc52cc0234fc496983e992ce00372b046e9b25791a00501e1adb2f0b5053bb7cb8b36e122418571d9cfe19342a172edd1e7401f0d42861ac0757e52781ab5da874e274fa0788b4bb0255c9b1b00723e49e4a4dd5add7bd7ffde2f74737d1da80ae28fc8b353ed7b79f974662001053b4d6f23f1b56a2953cac36af60be6a1ba46b08c8b157f9b32a5e018c980261d6ccfbb4717a611cec5d18abed865131a362d351593ecdf0245220f222c73d41661d503f44b3e1f3ef21dcfdc6aa0a47bdbd7e998808b2b5693a1c2e4aa6b4d72f6872a9731af2aaad9463767f29ed5b8b5fd60b64abb0db8399c7570c51f48198a8e0a000c69936eb1d297d58f664cd16deda69061b4d2ecf55a59f9ab7d1e725f09646830e2c1356a31354beb990244d2526c579396656b4a9b175a9c7cfa724c29cd271d90875bd14bb7e3f89b9d0e4ed737a43283388820a7172030440172cd673f84177edd5f588cf5d6559d578fab9f3522816ed56ae0ed6aa7fc54fd686b0efd8b6d483aef93e4cc463a78cd5134e68fefa45ae2e38dc0bc511b7d3d4dee037f1d9401cff3a18453f6bd49a86989a733210303d3e0ae0266d0b98ea17263c8ad395ed8e13aed827e32d0ada998ca4139411ee2c5f8f48a7f628ad7a36ed8d971d7ca743dc2a7d6a30628f7f07f22f4917252613d4a13b576f824091653186ddcbe998d311aff5986597b6ae16e3a777aa402744ae73476563a976af2729b9ee46752297f9766bfb114edf7ad01a87675091c57b5993b29ea004acace63b847d998e8c39b093fc67b67449bd5a6e027b4335235df842d61f2c1a54cff7273d1476ee6c1f72f39cc7dbbd6cf1375a5ef7c7d47c7b7c15af4ab65ce0f6945d050f7cecc96f4a0a5edce25f12fa19dbfe680d8206fd6633dfd135ce0e57f1e3e85701baf43b4500967142400a91bb110465b4358cd85b1a56d2e28c5b77444b2681ad17b1488cf2821acece483635cc127f644eb774edbda4dbfce924f394762b94ecc184a7ca6bd78604577c11d146305219f851ada0eeb82a5be41b29572dbb8c3f0c5a79af6d6c25966d1be40d6e97bfa057ecf771de845183a74540b72de97dda9826848f1928fd9f8e6bd39d3ae17ab328f9afe9a3aa75c33c5e17b72e5211f15d48435ff98f5e7d91a6ba38845883e654ec18413ff5f836c50314b722daa2ebfefdb3489da931c12908bc95188ce58b052b1d6de67facec3f2513f725c188389ee612bc3820720dba0ec5cd16d1b801c7bdd2b2c166d0843583f2b0cba9b3ee76c8c6593928bef2f55b1f172f0d3295319b3d2b876258d522df631155fae194318b53e46a39fbc5593aae6c5a8fa6417d50a0c27387039d70d937e549e9d9c5ed62576df48fe77e4ee929cd1f378391f420e27d27f58f228d654ab6964606f1022c91c1136d9d6448b68057ce891adccbb48d6b2998d3eea11b93272046529f62042340abf950ebf0a97b3af324c5ecaaf0cc14f264a8b2e259e581d7952ddf424d5cf0180deaf825a73c647777a8fba727005bfd972924f62d9f872a92b669d341327514eaa5d1891d03c87c988f69cb4861b05896e2031fe34c8555073b1446a54ec1b4c2738657d42e37f507d4c207879ebb92f78b042138265309f87889bac9099ccaf220436b5e785a59912a867ec74e89bb8c38094f7942323f11c95a2b3cad366e90cb61c5a3c76751078aa4d5be4094bfc52fdedb8673d721ffa1034264ef2d13d2336fe51f2f4480b45f43a3efac665ed23c02d09ed8972c6a607bb2ba252ea8a030f3632598cf84df99345b283ab3371efcad20ceea97250950cc33121be566893941e50ee733e71c3094680ab77bf0db15106fb225862fcbd3401face8ba339f29bd5707f7b48cfee70812fb13ff0e76b4bb59de3f5729da44f3a2aa30394c91d0d9c1bd8e1fae2502d1be3e70e8e2ddd3ed9bf136572cf0cadc1b3023029a2ccff3a0b3ef96e0edf2227ea1af621cc72bfcec430dd729363c28ef69ed490c635be14ad32bb43f5abaeabc8054a55f97d46537d4a5936060cf9cddbb92fbddc7bfb984d5eb454d5effbe35cc21840077ecf7c6f22aa501fd0f0219f7db2a71d9856470951c11cc21e3a83d04f38e973bfeb5e0f463c18bc882db457bba1dbb2ad480a21f7c67cb40e4523a1a35cb2e9f6a21eadaa774d7a13c3c005bc6562935fa3c1e0b56464d52ede590e6ccfb09c3c17b5f56eec72709c341cf9d774411630a5a0f5094dc913c2695919d8b4ebb19baf872fd6a32d1551e387196e6fa12e6817fbae60b1c8d046ec4fb9d1a7415e12b9608c71b472f5cc0f3fc7b1b7ef5cdd4cb510e22944fbe36081c686851415c968064990fa72b4a35f2e8a221692dd4a3821727ce76c843df284a651825c765d8e51cbe6ff04e86e15b534d9d947171741d25c99fa4403d3a51f17b297e94270ddf09f10602f112f95b1b29449a0f1ab50d2bc754340530e86a37c53c13e36d5b0074ea22e08a025ff20310842daaf0c36dec706b8a31225ec9384983e204f28e2c6a32e5372749535edfff4bebd0541031a4ef64a367aab05590919b14ddd0dc5f000493e72c332dd8be92cd3996e6539c42217a88516d87eef99fe9bc9221b265cc6c3cd72188cf0c097581b114cbfce5ca27d6ca5cacea0f2958d2580f32ad5b49fd4a372f9bde7549dabe9546dfe48b5dbd3702955ae447ecb041a20974d9b89158e8c7269f8daa81a33e87c2efebbe40eaa7a954de17ed406c8c44a07ee89c2cd2bfd72ec6cc0865cd432f6d10aa0080a069708a1b86bbcac775f3e6c6f31d929a5d872a6b402488df37fe57d7d577cff207f36e8b0a2ca3287b30ef69b0bb5ee029f72a6efb0b19214e4ea5e3d46becd31f364ba1a0ba81755735cc1e89fccefa27f7261c006987fe62a3d1fd76e77646435aab8d3a096e24cde8041d7ca8c3b40f940e77078660d728c0a8e18f59b64184461fc908de78a050583bd837c813f80087215633b1102d7c6370a6e03fe3440088015007f466cbd958ef32db294ed8e54695d6f9859de0b08b5d5b3e9af67b118d6507504330e44f6ddbca88d02a99f987292a947382c7689beb9516db719dfa1a3dbdbd1ea15774d19fb6b247dab5304727802e0e62a4a1093511e495f892b332657cd68758f9e935967537979571a5a71fc8664fff3ee2dd0c7a20c145361d7a9139339338c5cfc0cb475480451c05f72c31cc39e6197bb1cb163ebe1f4b07f8602500e93111c2fe648976a6737050d7254bac2861e4b2483867615cb58d64fb7d76901eb9197948a73a906c794f0dc723426a223b18b1ccdea07cab77b3b8b77c5368be05824ad216d333714ba6550722d575574368564875c18f016057a304f8ab7cca3acb49088b578de4aa876aa2d24102cfcbe6c57c2b70c5f5459fc9bc9fa79c7f53539be381e56340486d5ad72d4daa824357d385e63bcc38ab0c5adfe1ccbaf0aa2bc6aed13b4238d723cfb72925713a4cfa4033e73cd039c859d7e72a095cbb164db1ae1eb6354c8c7440a1a396248cdce10c7cdcae9820d04a59120cc3dd9cc737b7cbd0e75e0cea86f4c72a1e6b43e0d261e344e07c692cd1bc12d6b4961d257ad72ef5f8e07a01dedb0729584e0683d820f8d6372ed33efdfe22fc02e7ca0292c932393bc82c519831e0ac85608871fd33cc98a8880a81609ae6453f4661efdf856c23067090534916572a691cdfb3ecc4dd313ee0825b1d7672678d3d8897138c19ee4d1b46f9922f2486745972fb93f193810d4b19a140016ea33bfcdd4eac6e9b57992fdb4a4842e62c47422c890401e545d6130bb982d76b0879db87b94ab090523bc590d258166722551773c70be1c78fe21351f149ccb7df783ca43def123263c6f64177992cb6a3dbe03c49d1b866c2780ab36ad33adf6c7138c07194ad3129d48d38f827d7e72d1e9cd1db8fcd1d946940b78633d67969b8bbc6d132cb33c89158df5716f3b238fd04acf6aa8a8d44748d73fd1848873afbe450ddc4258e6a76c391e7c28bf40489f65b596224e9a19c0e53e1e1c2125f0fe8bad8b593fcd132fbdd4029a7e3515b5cd04819128431c18ff4fb3d2dced7972ec203d21067af242e075cb176172ce32d3fadba7b6e4303ba2d6162320b540d2a026ac88bf89de0b478a1af48d72f23f0b55ca7617134fb6813f83d39689eb4c3f4a1660723b1ebdb7e4858165727710091af73ca4ce10824303f4a3048183fab2143109289fda0ef5964aa3c93a4933fd944365baeab832feff04ebe68aaa55ac7da1be7023fb7a4b3522b8cc262a9203beb90bcf296cc738ded751e80c72435bf45b3ed09212ebd60bb14f2a720a3723195d5304eacffa3f4f31d02040f2d7f182a54ed90f9cba8526734bd8726e398330bc766dc0c01d7a0032cf4cd5233b430f9f7408d5895a48daef204324c8a9e89596f940dca7ae65f56b1ba819497adc8ebd9098c93fcf6e2b4c3ab1494a7db7121b982fbbf5f474b978b2457b686ff7ccd1bfd26a060046d384ba0b72c1e02bf5599c259abd2388fc97f47de10865ec6b4f51b7e98a8b059814218e72aa22070411a7ac243872366832d5954bc9dd115c43eb2b428c0f682487faad7284fe91660313858f47e0ab24671bc9778f2b01a61addcb0f08818dfa021253728a19658e84664be2394e6fabf33ad8b57da395af79f5647b7afa2b6609613672c00e6a90d04ad8045800efcbde30fd3c8945fd288fea3b3b643e731f5abe49724c3b9a32c43898785faf2c0c69bce97971cb379ed4907b164e49d998c7e2f972f67d2bee7ca5c40c5fe0751994b6d92fcf01355ffd74462995ac0351fe17842a02d473e622a23c0d422dfb9fe16893e22ad2c08d1539239c77e75d75229424256f1d1b5c38c283eceeaed5b4024b049d269bbe23b9bb79bf029fed61026a0172281a0f493f56b4b02d0d66490dcd5af2fc6212ae7629b339e7efcbeec76b53725496910389f57ea904b7dfddaf0d91ea0cad425c45e0ac8cecb253397aec193ab8b36d4c535bb809e1354899e3dff8d692d914c0bfcf1fc2fbcd12948ed96d1f48858c284fc9413672ca950d36afc8d83e644377eb49e5629cf2da2dba1e293445645e0f44fc72aceedbe7023f740c93b9c7135c1fbbadf10695c79d9ec35372f6db53e9084590b5e6a77985c285f8c4f653128532d04bd56e34c0183864b872b087380890c3e66d136e62cf5275726ba807325b12783fd322f759adceca9c20af389275934aecfa657e4fb4f0150e643df03fe3f39b761cfc55731ee94755721dbd50aa8c1c795b3e9177bfa1fa5a25a3b21ee160a357dd87693f020e0bd772432442d4f12e7efcccadba4f979ac57571cf07c210feba049c2403f389959f72aaa00ef496927962d3a429c8dd6d96a58841a14c40f791a3d1e1c8c329f0c6200753e17f9df5fad347a9855acfffca6e5777fe6c648db7f50dff7b187de7f70c4802bd3b03c6c947c661e39396689a39a3dc17207b5b30bdbbca6c1a95d0876f3be5e38aa61c140b58a832ff405ca0e93d8872469ecf3ce0569dd9bc9de3c857cdab155aa9a874860f0c412b581762948ed81b367c7310b492fe9a15853ecb701b8cae81faad68846574e4abccbbf490f6fac5e51c87db3425e6d734fb2a5972c804e532e3439065848b41daccbeaf263af63cc9ee7ffbdc3402a55c7640e9725e89fb293aad4d202ff87ca1d13a20d563adfef17a6c969ca8e55f45bb99e215f65995e5eeeb8125c7ae3db47c22079e710e94f9dcf3b6cd59cdeda4b4b8977200a374b075ac22919b89b635b30dae935e487999313427364ef9f78c7e62e35450800ff7ee549e8f09536fc7eb863f7c3c12b83679c66cb6bdf2928892943166066e404e87042cb98f235a6581fb1ef1f703eee0c7499f6abe69067c17af651ec201927a66bad40906773e5e6e107945066074f3415215ec19be66d31d8f3e728fdc53fe870a8d54a353808a8eee10ca023e48a5c4c73115ed25dd8ec411d172f0ceb8735f944edc365a5c6831f0e6393a31727b429e0e5467d7d28802653872ebc7f80dbbf1d25755e9422b8b0cd2456400cc6cc7936021074252978a38e25af12a09be12171c7fc9f57a026823f21139836708058da256db7f053e1e02ba721250582969c9e287ce205fe162356e430bc795ee8d5f4378dd361e85bbf3ed7240f3319d86ca46b7e589c212d129fb80bc0a8919c500afde55d1f1f4be08b0728aefb1fd75b0b676713ca02426c3aeb2c97690fc97d0b81674ea4b30ddf714729216510e8d409d5c00cb068fc146e92936c94a2b38b5f5726ff2ef6256be693e0677dc2b4232147554d0933a3f7fd2d5297659b70be6eabc4af014c38a50260975570ce42f3d4175b29b172320cf1adf21e4eddbb2b9c5d575b8da20247bfd720f1ecf11bc3f05cfd1b603a95d3e5207bb3374a8896f2d32718e5d4e3f3a8356ccafb03700d180d7bee46b9a7ff3574d59821ef5948d1209961881c8f42f1472b16003b4ae3e0a1ac4bff24408efac26ee66da296ebd6cdbf36eb6001c781a720307ef5f715c0c3f20e00dc84983159b1b2f75c0ed337edc9945bf5aec16a072b20b09d04d432199ec65e9c748e96830e7c23e0064bdff5101fd3e68875e08726d0bfaee207fac483891c5efa9f02193531bc039d08902ba8d111ef7893c1344a9f0aa6c955493cd8b3bb2df32b94baee1d0472ae06fe0cdcf73cac9b8653d72271f8deffc75a9b42a4cab469a99736f3b28bd736c8188b23f06a26de562147256e671336542fdaf5a9fb30f3cdbeca9534895e0a3ca75fd0307703fb91cfa72014208230fc6cc6f1852caeaeced2848e7547c0efaa8343f950552a51ddf3b7217371f06196a0e80b9a8ec6bc3fbe273c8034dd3629bdb0853605e81a1b2d20d636527d16771a26545b9ba2be8c3b837ef35b5c0b4d6039a8cf949c4c9188e10657844f11c6a4fbf82364eacd406acadb2e589484c27588f01fd824b019c91361644861f7032e98e9b1c8ed11c496c482752cff98599c06c234e70083eeab1728794d82b36b16102bcccad7a4eac0ca17abaaca4e537bbfcf92b51800f5db27238e32f86d4a110797a2cf252a3df4edf5b5b0d887b23ae8b9dcef67c717fb172345b31c637232b0e578255d2f224b5fa155b9d96b30d48cf69b0878151363943558ae701486ef5c57188f396698c7f5ecf106235aaa94a2c85b4e594d819bc0ce2c7807ff6530623ba82b4eb98b703d03c6281b89589eb248b35ae81852f9859315d08d3f0b84a335a79d7bf7c2234f017ba4f0767b858e5d14829981de25e72f95f9e106667f3dfd16a92bed8ea91acd5870b124dad0582b73f61cad42d47356ca44ef1cfd5b4d912257783748aa77cea2c2df628c85889561a47d0ca210817fe96f61fee33eb2ee31bf945405ce7d6d41a8c92572e23b4b169534900f62f727310f628b1fc056187352c154449fd699eaac003d64219f74e04a8042243934640b263e5d49590fe4dc272a75d8efb2a0619c2ddf94ebb57fb34ebfe6258dd44a8f24139700d5c9a79ff94754aeefa638cbcb854b586f9654c4f038225136672282a19fb364009be61a08ee7960525412d390b164ceb9e01a6579c705f7e9e44632503c1d786cf5bb9d630143cc3fb041d1dbb210a319a2fabe61e0f932a821c6c89fea1881e9613de0f51d5b8091a3682220bb0ad04aa8ab6c22a8fbdf8d072600e56791d10b6907bff0e5c4518ce650c0e0899ae3ceb28a7daa1f09c589a72ee2bdcf10b270ea329a3d47a44fd3cbe33cf868847631761276a68f896363d2a3791f2f2f62afb3088c9f53f7b61e307fa0702147f60d8c3c9034999c48f906510a255ca86febafdfdd4f8337e2bf107869585102bd4673aa344e57084a69672207e169133c37f00ea0219cb0b9723265e7b040812905e992e16db04f5494272d4d30801db6d5cd78092090b9608af9a1233332239553c975b427a4b73823d72fb6fe5ea4f3a14bbfaafc91604527de4b9ab1742491b7d252a11db4e6c5f0131f2abed1224fc9cb2b7b1f5c01eb01d347b09e8dbdaa4224a08f4ab575507b95b19d7f463af913fe4c5e947dc7515b95a512bd8bfcbcd03d7eeeefe5b13b2a523c2180e24792b2e02ca43b1a0db19cba91430fe6490b8c51d10898406234023715441c26fc7d9cc4838421f12e297b7c7db4b18f85c568c279c6f50c98dd8237219e174774098bab036bc32656c409fb350d94af9bbe99d41ba40194a3240a710fb7da13f26bb9af0e01fe8e935285f3d70aee09efdfd77ead4375f2818b6950aa0a5c917bc82438af463973cd55ebe93ac772ed12551a71648d0eb6d6bab822e89a4e1981dc2b15626b66f60e10786d4bf29de84210007a2b6d38041569a6d720e5fb1c7e2d396a40af3b5eb0ad22d6cc1a356e03d2f34c9b7b20a3d972c38723e043e1c6c6495bba7752679076d50fe05df1c5b41d0cf05e343096689719072dff36410f98b4e1ffe4a432c8ed337343fde8f42157bc02de342e972310b6f72fa21d685652603c005bef4e5b5f8d009221efd27ed091f5212d227de4096523c25d4db5bc11801585ccddd46314d0ee96e257953dc20e72baf1225340ee01416a65d1c4fb55619a1100bc71471a711779066823bfff1d6264ad6c0533c7bbc72711785d2b80ad13b12546765618e7b19f7e1f113c2c8c28b667b3c5abdd28f30fd0233379cfcf27895e3407613da9d76c88f41c0c3f3649e51a57a9e734f957213d8efab22b25ed35a77ba955d553621f5e3a105eded958cf8efee74d8a7c4470bd682a41b2197fc2011f27b95996026f2c90e965dd4309bd435fb1cd19ad6670ee426fcf47ae624aaab5d89bf32514a65497491b37df8250e78a04490fb4272aa0544768729ec34a2084bb5fc709d9dd12f8e7b9bed3ca87c3ed31ce9d9f072b99f9a70f44d2c7bde0bb2f95e6d04afe9c19c11846db42ef46076882cdbff459e9b8369d7bbb93d50d6e1659d231af72b7a1650c6357a08a20b6c2403fc45472b74ae44422006a8536c89c08dfd2a4aab14a1c73f264b8f5af7531ac06da3722cb173637dd57a01bbbd69507d0327227c29f71a56fd5c099991df995b029072c8c5aeb9b1a302333f74dde31b3b45d0fe5c838e47029022cd7653c20fbf6972b6c2ddbd55214648cbe42ef75c38558488626df619715de470a9f44a53c05950e8dea15e431e62881a83e2ab1c1dd711fdd2c90fb7cf31bfb767eb3374cc7840c4ac015810f6260c5544b0de77db3ae79a0821091d50b19e431fe83f6cdd812e28741b9f9cc065a226ae86245c2512e03f2eefcbb161a44d12624732923d5b72574265c97a56a8b9a62028768bcdf8a96b1d299841a19042dff785a0713af639fb9e0f54234d75e416a2144d73c2a7aed4b77791f8f353412dd4ba9071e6b823f2cd670ef479c7db73f5197bef5bab062b149054042464276ab6a07267e967725c6459c89f8b093d6dc0d7dfd76ac6554f6f0092931f36284f0eec38d14de30d0a97262b653c12f5ac62b32576d871f858d39656490eabed0553580d17ecc2727352a1ced499ca6d56d6897fbcb2a6e755c5b7bc80c75a0dc74f573306daff724629acfe8f4ec8456cb57dec1bd88e39c2364f78dbf3baae4ae3c2c3a9490f72b38f7a7db8d6fe3e7bd1fee63e10ecbd5e130d66736691bb5dc9797a9ed5e572ecf10deb245f144a397ade806f522b7b1654b284635a8aef822e7da6a54c847213be7b8ef22ac3f5f471744fa3cbc232a674ca57e68295fb3d2deff39d69e053f2fbd51a7e1ff77bcf7281c78db367a509e3d24e7704a4d17fdd4facc8f3cb5e11ea485ac90cd221776b4ec2f583dad3dfd769a457ae8d7761321aa3088e5472616309b9545715058748f47d07e3718b6e4f0d22108243e249f3255078d0877283198f6e9cf7fb88f86b6341de9e89c947c4dda2daf8be5e58be981a0a030b7209aca5a6ccc967cddb62dee2fdacf69e50ffdda5e76edf1025a39c71e59789720cb1eef25806b03027baea4f1d9309890005c4f4351a5f48949abdd184e93072593d65db7a3e4f3fa4f19ff861db19848eabc79679f2c0c7a5d4747042204272677cffd4cf0e0d126c1c6a910b4c6f6f5ee272c492432f6e6d7ffcb38e6d291733c2d86952c4ef0c231a1875f4463b3e89d3e41bde305b439cb746d385bd76721726b971a84c06d26e3c795cd72c88c556efc99a77c65a1d427173d79668b75b7bf52bf4c78da50ab2c6d930bd87720deaa6b55883cfc100c8c4d9ed9c1c5572baa84ea860653587ca85425ad7e96cb0262ddcf3b5d0b85b4306992f462df15ecf8c6ef5f794203fe9b3149908df22f697ff522ba8e46f0c4eddabfac2502572193961fccad91483f8958020a2941e2c12cc111f924d73eb64560e3384e4a5720134f51ffa4b738eeab0832fa65e2e251a30013d50658b1e62d86ea34917677266c2efa4789b845a27ed3677008c7577994b5dfc28c396f0e454eec0150c6772de1cd762191cab78cef444c146c3a837330c864101d3b08a759586767f4a84303c71cf0b7ba4b35bbac358a1171445a45f307afa72196019f3bfa8db0e67e0722784ad7394b01949761d250d9557ec47a10be14399b4cf5ee17781fafdb4907225d6bbefd01936100077aa96b5307148ed9796088a45c2707c195ac000d77b63ea7e63866100d211566e0ab573b5b71ee2994bd5aa1fb1e83eee602bbca0ae7204b42a66e6924f5a74c0414d7868b9550a952cfcfd4ba1a4ad53d2a1d25b3d3bfe75bef1d8e323ad4d2706c0ec0dc393bd2d406fd6f2841ab04524594e2db35f08a943e4bf96e41ebe6e9547db5a91dcad5bbaade63dc7eb4c00da3a912b3e2294d8647eeeada69245a1776644d3fb1bc0ef2f617ab26ab88dead8fe32a50322265f758681076329514009d509d0312767fe28ed585f1769807474dbecae1d6e9af58f6d90b05d46207706782a10ead8f8e5893e26fe00ffee0ad35b53d439720c84f5d7f67a16838d6106b3dbd4adb1ea2f8b8f51921561b951f42a5926f072a86d1e1c2ebe9d2d518babf9f8a89280187689714d65ac47767806ee106bf2727ecac417bfd69afac21a29322e7c95f40092928fc775f7b16f275fd6b8e96b086ad9844955068fb5250ca6445e29182fbb49442a1072f4603d00a472d5415172d981dd246c1e57fffce43cdda1ad4c36add619900fd0d7464287b026db09784e4d15b16525c5e1cf9f6efadb9a7441e64cbb66addeced9327d35744b7e3d9f316a564609d19a1f890933da85401dc85c3131c9322e144b156ec16a37759e1972bd249f520179059768fa641360e58d702882f98091a6181ca0319022ef16e872f93238639b2bb4045514533e151c6773358a893ee063834cfd45a2610331847240bcb2c55e87afc8a70f7faf42097512d42b4ea42e17616c35a465acbe0cfc6e9cdef672df4c8dc0b7d1780f491100a3d8c3d8ed91c9f5173fa84d20cbdfe97291bf51af9d9fc7af523c97ade1dd113a74eecf931f34fa0e95da6aba7300f7724c360fb8f2e99e62eac436f951306a9de82df8874a498ab7702ba11e4a920c5cfed5282e85c6e04d4483bf4dd7daec3f5e4e252d0e2b1cc0c7d86406180c5f2cc9a59ace179718125d42a2bcd76eb351fdb6a54b9d9dbe2468dac293658f23724bc675b34835fbc3be5b67ae4792f8055ceeef3270d0f6e25ff1efce455a947233d4c8d4ddf8cd6d141b194ba036e1c55fa1bf5d6fdcb51ce4b88bfbdab81f72b386b51348d36e7dc389023d34325b3745606740edd43d613ac9c70f25c7b672921387799471b92edc193a1be3b5ed02be84d7d1a6a41a3e966590ff744e4c7248e371494d7a8d93c40c6ce708b5b6d37e24630b9978ff75ab0be5deb4546e6c0e6b7735c86529df9b3b4ea9bd7f9f54e9462b17a2060996e02a045dcd3e38723c5489333a9cf26f88d3ae40a6ab36e9f2fb818edcd7947ceb7cdf74c23d79722c28fb7e196ecf7e2d06852fa2bbc61b990dffd9c3d4aeecd2b66e1c4474e972575a26859f95b035bb8165c1b5bcde6214fd2d4bf5aa5f7441a5383b51cc7808d6bed4d399a71d772bbe195397139a1fbef28d73f31c203be37a8c7b3bf5a9725da914972e427f2c56209e417432d68eb9a7a2ab31f328689bf1d0f942a3ad72accafc6d43b5644f5b99c2eea28a15e63e362f3a30c0cc0d23a5fd66e82473226ae6b8e299ae64360220a16163666ea88523f0266f8399c557210b09f5a1363fde35e97ea640e2eeced50f0184c877beb136f0e889e0f334479f21d8894dbe5aadc6ef836c3b822b604bba3e3aa864752d3c1577256a6ac60ecda059f851ba17bdc68788768882af2dab733974e9cfd534e3aca949a46d9b52ee437c2f91d172506b0d22b8eef5f4feca1528dcb74f3f1c4c144d4d9e6bdd83668898c0f62e721922a5e4994c0c90cec61fd431c118e7b930df65b18741ef8157fb03ebe9c172c42c978939176031ea9da898cfb6b0ce9efb70991258d8d9b299a1ac7fa8c1720852dd8b5d4623cab066003534d7e93004ec652195283fdbd2dd92f4fe7bae72b19ad6fb330614f4b6694f1514981d78a5bc55891b3a62a6dd20c638548f8e72fe4e2f026a6e857ddcf0528989d0a035768f0fe321cce27d26b5e1200560d8720bc03e0a5c97064a4792b854f41102a435daf7fba144e7cbbdcfe8a306e961729ee73397e0c77f2f4ffa2ef8f0d01a42955b87ab75c9b76dec9e421182914f4047057e3f27c797483753653afcdfff6e3eb15a55dd4ac3881f8b94df45076672a203c294787e119a9b1ecc4ddfdb328a8e7ac9b729409411fa30f3e914d8a849e0cbe2083fed7d0373eed0589c5b04063af2e6567b8b7a226e709ab46de57b68312e7b4fede6f6566844e0d19fd4c4bc211d696a06070692f1aaa79d4a3c6c3c1bd20e6de37005323c4fd139c19d9c0f16c0e61b61bf3879d2a74016a8e72b72a010a55df4d902a7e3b8f8d7094915b9a87506e196d01ddb6e6750869857544856b5d226186e02b968cd0d3fe84bd4a99f6cf1102bee9b29ae9b31c247df89721a2f4cad49bc585b4848bc2ef286c28b1bdb5a0c1031b22a8b6be458b2848272b0882721fe4a48377238e715be59c3bc7136212e16fa584ea265a964460c93456ef6553d0da5a969b9687da5ef4e2fa3425880719e2878af7ce62e1cc082037225a73bab06936610b92b3e392b7a26c2e532c8862e0a802ab90077eb82ee442d013b4236277951b150621800e18d8abe184737558d50a0f7fadb8a12b8bc6372079354253af094577153df168adb9fefa46ee19231846861161a676704a9b5722fa294a9daecc2d42459d820d2449c24ecc7bff983754ce7db170b1637913f72d0886b2caa5b25f62674848d1d7b0ea84616a4845263641a3e3026b197491e72df253a13ee0b06a205c293848fb43fb1136123a435af918d6ffc00f37a3fb7720db3f9677304d86f593d84b25fab963b79ba16a4074a3f1c7d66920e1f3a227200774742bb9c4284665501546bd1c70935f6e30b54ef6c5b9f5b88c9539a5e72bf2af45d6e0030f052bb2ed0688ec66b93a9424c6880d81d78053637f8fdee7221c3184d0b1d503aced74a3060f3eaa05161960573fb76eca154f69bb81ab972d3b68e48bd40acfc67fd2365efd85fd1987f3e7d40a87f296f5bbed198c5c37222a529d82e48e45f1f84a846166495a950c158ff8a24be7ca842c3f05c51a972390a958a10e85b0f1f55d5c85e4f7bf5b926b92e02747a1cba1906e8a8649245c044b5c75587c8841658f99366f15b1c55daa1cbbc1d5bfaafe644a21c1e89266e7fba46a72d102b95dc25592a94d12949a9cbe8c21d2b3fa2dc72447019fe3170d0db8a8439168447a4cb9b8b142751473c87a43ca648742847d50225b0612464b09be795d1fd47dcb9bf36394adcf5fb068bc9ddae44490ed39947d2f7b072965946f3db7cecd8b3c18ee3af804fd6c599db2909d7fe8618dcddf30f463e724c07570eb53cd4a6466a6e3802fd4e106e9fd04f1f3e5c94f511f712c250f172b939f6504694a408845d1143088951483585dba713b37854b0b88d58f21fcf7264cf631273e47a965fd5610997f5d153a6ad483ea030f062d10a2352a84788728b56052a67835e10162835c88f962d616994fa732e839ee43aafbd577cda884e221a9c3d1ac290011429816521a4c57e8aff68a5d9ac2a3d573d7c5cdabd9a68ea5bc0af2c86a1a3a756a8a0e073eedff70bc50111b2c7d7f729c5c03fc0a772a613d835bf9b8e7242495d70fee12375aa3aa1c9291b42214777c285c85ef572b51a091f2faecd2eb0d25e941552fd5477e62a2743644979a9a510793047ca7250f06532e616546c999069fba9051e701e4e33cc28d586f92ff56fc457c3124fd76bfa7840c6a7e973daa9a776e8137888e49a0130abf0e11005f0f41ae9044a14110b7fb6f9fa74ccce5a8b058f06f49447c4efe3d87d056ba15b6c09284d722c10e546d1683a3261c847e7bbd189614ece31c31ebbc5905292c0188ee21d72e364778ae0dde930b355404edc3eee9fce0e7de9082e9eb56c846948cb948902b26685faa4bf164fddecf40e5654f1c2ad20be0ad706469afc059e6af14b617279eb76363711ed5722dd0b3d3f669e218bfa5291d663d54c3a314264ec7b6472f1e8a76dde98ea05b40175b4b2e559a0fc3bc33631d2155b38d3b32338371b72ef6a5b9a16df5b990379c71acd1468348587fd19a87075f76890f38798050172e94f93e24d000b7510f64dbb67572dfd6f327f9513435ad71ccbc5e3870b5c66627e83538c727a2bc3d43d5fbe2e0374a3d2f24cc4a5122ebe2c7aa47b3e9672fea48c473af9de0de77a3e80ad30a3fd258f7d68781e27d6ba17bf9a4917ca6c3ce2859dd3e1eec4552db0e52d10097fee089bba408af3f97a0a884891ca4831a50e5becaae60327dd98758b9e239b8481a0dd703d01bea1e8ac742f8f49d41ceb5f3ac150d300cb79b1bee29f74e75a0b4061beca3152a5c6b4ef1047e1943893e2036c09071b281bb7f79b9e63a858fa0a839a8d25e88939e959875881af259789bd74d0955edfe7b93b303cf90adc9a6d7abed27da134bece350695d6ef722958bd60f87b3e6f99a8e8e915fe6224d3e901e7ec2b2719f888dc12153fe86dc54d5fb3332800c4b18353934664213e3f05ed4b4edc127a54fdf0cec1dbd15e05131f2d852dd6127b551e60514a0a830f51a3f8fee6113fbe4066b21be73d72a7d25f5fbf1a9cc225ef467880cd5e45c89aa07f77beb4d36ad343417b6b1114f60d09afcc429cc9a28988e8054781111e69dff9e2c247d77671b6dc38624f01704941db5a224c6b58933aa60e7a4e6a309a016a10bc509e10ef63eedd4dd0728479f5156e5f643fb9f8a91cdc63d2d8ea74c17f72fff68e2ee97b996f14990712603663e0505ea5e2534462fb3368e4695350ac2be2fe2a36fe3373a20ab36cafca07a1044c3e05c6504f5d55f1ea3af3fdd8b48bae01c505b16a2a8c1f0758397001dd253982968314e756d294975c8089e5889a021a8f3b14a9218b54ac72c806bbbed4d7f7b76e9ad5f1263338a09eec0a12e88eb9c55f824f56a9b33d723db547d2d417784e2d81501c3aa5e330f08b2de67bb6185399fa835869053c72a057369b6d9e1f8b95f5a59e5f421b5680de75a607be681d87a216b1a11bda72b7dd624adb1740b3619903f1175dd35b92a521ccd752f408d09d5a47904d4a28733616772c33f8b0709a386f69441084dae6f362fd1083872fd1e73db5c4f06f359b74b2ca066ab53fb11821c8162920c54696202a84b300c4653bdb333e327265a8b9d679c91ac9afd63f36c1c45ffc05cc95656c313d17a2527e01f41ff1145eacfba48aad50ec40a87174fa3601ecb808c7ccff4be2c2254b89f8ba8f5972907b9d63483f585ad1135350190f6962205ea7f86a98fa184da18f1da7ce687297a07d3135dd47b67f7e448a20441ec06ab5d4f95367df99b01436e646637e258cf25ffa0ee5bafb4605a3b5b3048beb5632423a73d408402b06e774c89485726d67bc70024429da4d077e3c2129aaf6f1493057647153f79d01e7766715c872f7ead32805dc873e4afb80e8b8a4b2a36b26010e20a7f7f2dff0a09e872f2672071fad942120d11b9e3aa79b0a9d9c82bcff9d0625d6563502869457581bfa0c6ab2a57cf538205bc267bcd794d71838b8b89945897e99f0bc701325e59f0d725af3538f81ba89f4ace8a1870982f5ead21ef63de1826b2c71c2154bf943b772649c99be9dabfba15169d887c21e0cdcb65134049925ad846654632c8f7c42502a788e75c44570b742ab52305fa89ceee1aeefb96eb3b4e21cdb4d8ebe19aa728287cefb65eff5decc04ece17517baa47002604588f82362ad3d40bc74e95872cfbf45726b3971984ba5896045514727b2387c2ae5c5a10e95b1d89b4ab0fd726490254b9435e092f604326c421b5e8a74a6b70283167289003db5afe610740189b60bb97bae197860d9c705e970d82d5fa2eae8795b516b10bdd877aaaa34729124686480db070435c7f30a59e21600fbf5a3d2e11400445d7ac7e6683a91722048265aef68123c0bac58ba152b2c5a831b94bf83dc31990beb294b3b107572d72b7d914f9ae1ab79fdd37a8827327f29bed19e72961394ba6da71de02c0a72c4c3f91400eae30784232f02d7dfb3be3d7aed8c1488f248e68a8526b4b56c72c5bf39b92080e7d8763ffebbf2c8917624e0133711ede3f6ec95bb07ccfbd772db07ab55e99fb6460d2e0394ac48e85aeb673726315c405c85edcc6415c273701cf0df95f56f01f0fb94cc6d6f031ef80f25f689cbca9682c168068220bc2a72f66f1dfbdfe56dc5c209ac470b653d7df36ca345edfbee37171427c39e1e8f70e92ecfd495713a6734102f4ef32e631017f7cbc0cee6cfa94a79f8528a9655721ddfcba49ebe168853c9edefe6dbc261bdcba4401d5f96df0027d85dbac523729fb1123d87caab9a86323e607fa09b0649a72c5904acda6ab32e5ef9dd7f837258288f7d8e299e4d660f583441fe8b01cc382e4ee76d5e41676bbce28de1a972b6021a392f50112dc158f5937576373436bbb373252fc776f6634f30aef6663c09c6adc5d4ac5cec37ece521352514c5ebfb9c49ac0e62dc84c7d0eef8da4372fdc4d37d08de6c364f81eb7a3a67763262c9dedccfe91720eda6900b1dd42357ab2d8cf9ad227eb5f98bbbf45ac72bfe0cab54889baa2590525c4d6624e57972cd05c810b56ff4d3216b3578a3a1832756e9a24172116a7656785c2c1b8828728e63f97672a5966e5ccf03d4d3ce60409a5a696a1b7c3a2b6122b06ff2350f05a88f6c32b95b6a4c90e24e837fa443995cf432f60b3d1ea0e6cd4ad0a6482772db0a7e1bfbb5e7304b37be133f866e07bec92c1426126b77662b87f757700464097b8abe67d7054b47823090be137494f73684b8092a1c4305a1c38781f4a072421be301d8f61614e52d95d71fe26f12024970889dce488530941202702e73724d17585ae69d1c6644cf6f2838442633d8d4461968bee1fcd300bad29b36637246f652e367bce476bc07409915cf7757e9dce4ab66d8e73890276f39a7095320e6cc80556d616bb24578eb41f4a26f208c918799e834e2373df2594c3a935a72ade8a987a80744b687effd7332f5e890b60d917f5015e00235e72f33cfa16a726ca94886695cdb241403811a32ac914392c0e4be1e92e6c2c22905b96e39e00c2ac7b9232a68229446b4881aafb8c42a9001339fe8d91fd399e158a1b4cfca1d319bbc27bf55d8979e1ee7d0687091ba0db6e56068c728da2bc1a9e609a49e5b38e4d4100d11519f72840052c4b9270d8efe2505c790d168a3b00ad253d2cb7250c751958e512264e29fd1b0b23d4749c97fdb40b726065dad966e64b61aed72005ac527be1e68b420b2c0ca8efdfe1c46a98dcea43e21fae50c06676b522d724169020370ca34d5a109a46a43d23cbe8715d6a6aa0cdec8c9fbd59aa7c6bc7206f8f5fbf2a4ad4196f98c1629b0fb1eae559d11a8eb592b6c77be841e412572bd704975cc9d708e32b2f9f4e3e500026a639da9f9a286ae289edab1b7a39272779300f812fa2da616574ce84b1c51523848611f3d7dd296580361a19b35cd721adb51ecc1601c7cdb0419ec4fc2d2df646edb6463ad34712556320e76d979724eb50e8c97b537c28df046d5afd287f883588ec2fa5dbdb1098b8caa50b6df728e99ec6849df49225852c0310c591200140b4948b8443c86724f1f151c8adf72b17e0dd6baf2d34cb1d5860537a0debe0249d4796e7a7a4ecf67878aa3b45a4c13cfd73f508c9302f8d9873eab4ec6bcd4bc980e05114ffe583b31668c61684df2a1aa8f4b16c52d036c054a11ff1035bb7728c6179720de0da7ca1d05e07810cccb41297d298e02a55bfd0e02068aa112d65a7a21049ce6fcea7b967b2fb737c1b742f9be398a7a5936fb5e84577dd97976cec871f1cc235a68606356105172857a44987f4ffb5db45564feac42dd064cdeee7bc174ddd032ba481eb99b5146150db0e74fc3293b1e669a7139c021c179db177a392470ab9c1c21c0ffa9a527d699fb4039399808c604d3f40a194dddfc126dccc5c2bc1a6e6d9277207b3272451726f4ad6a91423ea01748c269607c6fb82b28c79d428966083e7dcda1ac53708fee1ebcbb4c08e5b9bebd553300d9079b2279ce534c8282e8414055b40d728f8c747e84f5c1ce48e7ec5e587736dc38adc530385cb16c5986e21b22c0626d97f20c88b3548787fe01ca28bc6bc1edd8ce60c13a2f3eeffc1164e05a407472d6cdbe83ab35fbe8afef78f9353266669135ed6fe3440e8c5a89077b3d7fe0726e4842305b657a90bae8a9fcbc10fabd278bcc4d1bf8d51315e3594cb6ab9a72e9873ec2a446008882b96275b4a2a5c1de44c222d697b161eff0ecdc747f1d723aab94831d1b5effaa252460081be338eaf90ed14875c59c451f33b40ebf0864786446e2bc76488181a775b6a4ce38002614c8bbea764c32b6c16d0dbac0183b0a2bca80a852f16317bde4c1853e3268da5b1bd0b8570dafeb69dd5bac8c3b7277046641404f04f2390b7516bbbd787517fe53e7399490caae068f67cd7ae06f7e36a10d658be5bc38d0f10c94320ced4fc6100daf315120b1dd4402ede96840b16837b2078117c49790e3599185ec505abd557be92bc53ea4afc3754c3cf912482d116d7c89cb78a4be51bb558f360f6de8855a4a4204a476f53b22ea7c9011ea33601b9fe287d6c686dd4883a0b183f0486fdf76db6aec7032272411140e720e6373eff64ad0a2a334639f51b86427670210625d3b23fa3c368ee05d4a8972201c8a332cd327192d3e2324347ce4569e216b1f5a269423f77abe3486d7b072701b98e2fa84be9567fe8069941a445d17b906ba054d34164ed3435bdf521c72f10c7d310837ed88610bfd576b8c0f3c6f8b9befdadfe0373addd2c6289def13cd8d5f7f7dd3cde1d81aef1e1e4f3cdc6629d42edd538aa35827b443ceeed97253b0d567c1bff393de41c474ad0ab1a19a34a9fee6968663c79c33cc510fb54f67f2348c165977c9733bfd97aca30063fd637f2cf0172b93d39104c8e50f3e72b877f8ae3d0551564aea5db2af33212b8d50040c81a94adad8ff717d8d2f0b72078da973d351f0c41afe826d289ce0d6d91359bc735f6fb71d1555686b8e0c7222309cf0e222b4da3777b1f9ab6de30320173878e7057cd07a2799a43522ab655bd099fb52dbb473ad9f661adeabaedae092eeac417aed4004890b3e938e670b76100e5f575e1675aca8a336e384327bc18d587f5e022d0b5976385104ae8b72c368e085ac8aedd45f2d9e04332d4b4ba29c42573c033cfb398b8b0036f045722a0e6c9bd4f3392a0f358cc813841ac54aaba58bda47485bc175ac5c8ac92a37db44c7e1cf14097ce6a77ef620906f9a11ef2696ca0693175a3b945a844fdd72dbb43edb69aa95d88e6295470fffe802f89c6c1a75164c34e781e50821057c50360bd3adec5b2e1baeec6dc9379e2708d6d60eb11a8a385d3ba144a4fd24b10efd1603673de68bd3bfc9a0a82c16099fd9435cb2746424c409fbbafe2a023572db5d00c8c02be8373470be6d687113041d71238f9d9ad02404a241aac1fe1417cedc78898db6ef7a3a831a6522ec62269a5b186741f98504f41eb9258ee38772cf1cbccaa88983312728ce149a61dedaecb814277201abb92b60dd97d3e483721a92da431bb687503dc11a53f7c1686cdd84dc78003fc780e50f0b6b749070192fdada54fb6320158d777457d1d84ec15e6b652cee573bac20730e4d000c271cec0b5803523678c1435a3d679b216177b2142a2b7b2166242f08fa0e6af46072410c38a0e557e1f3038d0f5bff96aeb303ce30673583217306698ef0b784ee705abf98d3fb55db6b474697b39ab7cdcad12d113e298b1016d89fdf583c5776726b47c20f5bc3983ff5d014e4b429d8563fdde26397a9cd16420318f615483c72cb87e373a31591b42e0f11d779fda86f238a522e3af6d05faf68983e8428c3725e883a198cf888ce10fa61109772f73e0494dc973c7a1f2d86aa65b2600e2d72d5236a9764dff24f2c2dd0da7623a263ed34a46cd4110f5c4c3df2dd000f4272eb6cfeff1bfcb5c3c2683d556cc416036bd502511365016448a4aef40a8c3c72db6e646e143b2ba516b4c6a4c209ee047fe22f70e44efada64f85a8efbc54e64cdf7f2a16a0e75a55a394ac8438360f4f66f9ece819398039f619ba03247ba720ce9891227fd4bff5448268cbe88517ef1a8e2faf83a72c2fe9808272c279a25fd632313e84fb0d30460ccfafbd7460ecd7daac6ab521cb5ba0a449d33297e2c1aca9ca0db9f9ff31d3145aee21ab433ed9b8232e903de218408f01bcdd48c72f6dccac12642aab219ddae27865f7dd5f737909c643b11ac47c992d4b0459a72dd49723926f20b24a99ed6cf48894ca6e6ee650244b453414aa161fa7b60cb72250b384e8b26b05451bc7c1e1aafeffebb3b0ed4ae4fb3d48cd6ab4e456cfe72b96366aee2280ddbc21932adf1dc77a2dec25c515efb18e500d7d25d241d7572127b7ed51623267c226b012ba50ca4856f2ed85b38087cf9c5111ae45435a77256a3eceedb8b1153410adb6bcd238e0fed671f0b3b0e44700cb68ad75c68092937311e7b5b2cdd57ab934b8d1cdc43cff23fbf40dcf831563b29d0992dab6f20208608d96cb8c2d88d5bff6fc6ae6220d569d7ee47793548b328ed0de17b5d72c72e846cfc09e577318c57e04759dbd07027979a1d3f659f47df16657aad8b387e17336ed67961bc383d56f2451eeb59cf4b28e0779798c44391d18df8f6c8068cc0268286586f1e2217c2f024597fcdfc28276bd36c9ac3ff9d71fdfad2c372dc6907aea5302283f15c9d448fad4314397cd8a758eabcf81cf03c851a194c72ffe0ee08e25d6ca943b2a921f894552330c49c932254e874a92523770dea5272f95a2fd285174b9ca5ebf8eadf42e56d348babec198077c3242e8cfa794f90031107730e3c33f5f6fd182bedbf8301c97ecc82e02789220c8fc0009bdb899072552de7b7c204474ec26eff1360f5cfe8f0104a6c4c50088feeb829a864ea07510ebb3825d1f239c191f462a4d011d9653a04ea628c6af3fc68542cf806ba4e106556e08ced01a2c3dd6f9cd5b8e55eeaca90b064e215c472b7bf29c53d18b37277ce89462d9e052572cfa2fa16c8e8455b171d330e11b323d50f8efe33bacc2e5b9092eea2c4ed14ab6e21060a9e15f99fa970b55dc4d205250793dc0d57a152be435ea03792e3d25dc0d314ad871c78720104382ff7834036d952931d0e8c35b6af9f2c4d39dfe4f8279b7f4bba2905496eb06a9a53f9090d0305fed53d3668858fc20be33a091405bbfa62e8d765b1b93dbadea25643ac002a724995081d72570afd941c6ed3b3b524cc685eda59e81b7dfc0a8ad98bc6a5ca2dcedb3f047243073e36e051f90c3eada6fcfe27133867cf75882b77408648d87127c2320b50b2e9c76220088543ceb9ad119d3f0502a4b5d20a22c2dfe9cbafb8bb44cd82728986c33745cae52d4369777abf8127077de3fa7e06982b4101838da09fd5ca72dd7ac0bef8321f95e7fc107e7a237931b844c0baefa1020de7cfb475209cc45c41fb00c6144fcb0dd232c22902031572a04f32f437fc2ef46ff58cf961e498727138547e55d5c998e5a5fdc9b0b79fb3b7a58038caa5bb8410cf526a9fd8d72bc8542aeb7b54c84283a1d2909bdfe986bb8f990a6da13a4fb70dff1c6f46eb72a0912be6b6e621a5bf4aa63ce82a5a724086a3ec5087c8820de2b2992f3ac34af5f75fa7c09a79050bd75288b757ecdf2b90d79d708e74da8c3d43a3d98be572ecfde558f73722e3c9dd569012e5806a73ba9f2c12ad47b54062e2338336f872974c4bdd9b1d6e18f50c16596ca75bdb0664de6ab595e0f7e45e8193e7c6c8066956202a53a15aadfb6f848281a3730367b10011c8727a5efb99b187155c7072e972aff2a8cada63e47775c400520a5e7e94b17a32824a0569cb4f0ebeb98b72df5bbcb7dcc305300c72e84076a7666ec88c1d826d86eff8ac7e05224209c219e8c6249336fb8b4827afde2554dc4007e435afb214487176f22e909519cc86726bcf39b09bd15903afffe15c9cbca7837ae9a2f1cbd01a42eea80a4dfebd343b2e98e7d4938fb9371f3a091e2f4987f88bb3c49034ea98b57f42b672ee63ae72c729e6e5ca2086523c723500d9da2049fdf01be3edd4c6f3245e4ea0be41586c57b8d45880c0ba9aeb332ab05a69de2b12feadb8c7ce6300cd1903bf4ed08147f6d8a964b8d8059623eb121dbbd67312d9186d0c3a29df47178d9c1791b2511f182a5dca9a2a762fd171e90bf11bf0d81984bed0e145ffd29e0e80badca7d31555721642df12975ce5ade6cfe57af66cc3dbdddd66369bee44883ca8a0368c72fec1061d39ce1f6897da1e5325ee4b8d7d3478610efc0b07410f23edb624896134820fe0cde6e836984b5207edb7e79fcf10b0f457898f075f40744cd07e623d745f77691357ceb6618d956b684327ff2e68f57e3f9d4ccd0b47ae5018df1a727f238014d7e1bddab03469d9d38a13e0c17e383fe5d66f22eddc4fba8fc50372848277dd153cd639b98461c1ad61057c8393a563a0eeb74bec348b4c6f9c376ac75b1301b3d85174caf61612ac824db626b471ea5fdc08a74a4d3e182df3db1d6dfc10424b3ba6456a359e14cb46fc067fcddfea6780eac53c044081be459e72ad6065aaa7f124b813cfbcb937d68b305223a29bc3dbb340f180fdc1c2de375412fd6e1a614b5c97b23bb4356fc64efdc2ba0d92e8054dd1e9975914f2a64072f034f7b8ca810ccd95c19ee0e92d481d74d571e898557e08aef5839113ffa2606d8983be62e4b38932c1fbf6a51f66bf77f1c33aa7254328a7d2709842012b0a1b2dc5a37ee5e0396b94e191786875e053c05cb0ffee508c99da22a67f1a037215377eabb61382b77cb35fc50158e73197c81f54dc1c9e92f246113a511b677203d05e6124cf4a75af916e8f05bf327cbec0cc7485b129a0cc197eeedf63d672fe1ab530b5aedda5adf6060166257d11b2417dcbb11294b97b410d75782a8e72b75f05aa2843e09f99d362e4609859709be930f80809840d79eda943371b2a409492c150f66b30d8464108276ccaa4550f07fbfeada462123b29f611c39eb0721d9a2c0ff7150303d069e973433a2303f2fe8222b2a8c5bb390433360834824e063c55977d5f4b299ccb91d9f26d2c76454fbf67df4f1f46c5861f1ff51e78726f856b6e51c95765c2927bb4e9361d8c085f78a60d6a820f1d067cc08c9d8072aac6c7f538ee553af209b9b30ebf1b9c8993dbcf7c8e8f7cf60e34ea6d583f72db914df429caf35942f539a90a1d600067011bd87fd2d8b8a254fabef814a372dd41c6bea06d6283a76358487a44a24ec6d25f84cd7eefad46c56c4e54ea5f6afb833014438d6087a2fe6b0b0e1cfeef8c000b821c0f97d1a34c0b4d09322959d1737efd8ffd612284f6a937168055ba21c087ae2086dae6c48db0c8bb2da0722a82ce83d0750845d80e0bc5b8e2b2b6804483492727c0e7bb81a91d532e630de1adf04f13be6efa0648e12cfc81d0f822dcd6eded73217360c7ecb5f3903951cf122050266c75a94c07dd84c1f129a18f9794664313c5994524c6d0f76313729622430701259ba0c3505d8fb3cf49878cb240a72fe6e2af9aebc17cfff7f614c0b73ae1fc4d4a3728369cc865f3e41bebe32a04bb70f1c4aa2711488518c0724c569c2c61660b3152e9bb0932bcb1a62d4cdfbd5dde501a7c35045c5a34547240786fd42ea1939d28564357f967b4bd058255989a47afeb23efc7a81e54757294b11954ba836c3f2330ea9c4861eb25f0540f09b97339efebc43e66206eb73c068714eee28c5712c3d6feb7298af6a6a8e3113ccd7d3d1c243d13e8ca5903720999aaf8f902fe3d0fdaebfcb1bc339fa723facc7ba98d3b6f06486b2e0e9f72ee988dc71388dc40628d1859d8e87a53c4632b67700bfd930cfae648165bd67286c052b0237c88d7cf0a6b5c068c0093f2e298c59381b9e65ac47cc77ca2fd72888bd1808159a0beed1c33c2de25415223a5785f122cebe8e3b570e7e8ed43050b4726ad0c972acc83b67259d5512fae095d0abec7519a7cfc4fc3d6444d59655c0e27cabeb01c51b5ce45ac8e6b582cfb18327c8fcc97438da20124e184ea32609e07838dcb6acd615eceb1744c70d27e476a07f0cbe3e70684f8fc4d03507210994c5674263a88503090c7c04c6039bfd2ae64653e6b61a5faee4c6b7e6672a5badc19ea528f3de1168a6fca9ff27bc2165cb69fb4ae0b457fab6ac8350e72a3b565d655bc36d7c977f2787b7775ee82dee00f1b6fbd21117dd3e1284d1d72958ff8f9fdd4289db33e68daccd6a3c19e05d7e72eacc8088d9624c258ed8c721a7a63f4c07c06a9978b6f5c6f1affb2c7ea2e9daf95e03ab11aa6e23c358c729e1b56d81c5f51071e6f4a2a985db4ddfdb9c47086080001a0b9c746614b03728d1f3bba85d80d4cc4d587c61789936af67fff915958129ef50e40496c216147bcf613f6d89e53bd67a813c6f0cb9921e2ff1975c81dd8d9c5f8c16890e87672ad23c1c62b94e9515cfca3b9792a6eefbbb23fa3ce82c7c2219781f9575c882f7857eadab79527f8a3e05be75e2096e01d959cec1886910b927726bc5018cf72d9916136c842744210918c40e3709d9ec63caa7a16a2a95024e47a01a5f6da727da0ae58b61117e5d4e6217c23e8c36f0ddbc497159a499a7453261955d5ce728f10501769e03566c829f84853e3a772334aa5224cb6162c17e55e7638e1530ee1a02bce54f02a17525c1bc743953d602b5753e3dfa4c4336f808d508a1bad727334af70c8c2e350ea3ae38dd08354fbc1c618fee0d30bcd7f83d131ee1b01724128bb5edf3d6fd275b3e1b0ac1f1c102eb70d1a4452de5539d7212611796801bdd661f0224f2e4dfd6d67c438b69291bc3ddd77f2799ece9ee7fda9ce10f5729d3c7e2c11fa74236b49a3b0235f08847fab727378dc6b28011c09c30eee5105c5f30d326b2545093388aca35ab2b64274ab7bce1a6478435283b35552d4f928ed97d83778256111a3266133cfc3400f123d395dd2db8911ce7083d0e74cd6729799780fb7906341914f6ac6c9be0e450154352b347f0cda9b7c7eacaa87b144d9baabecfe66dceaf7ef472d62b37156f1e5c9b80b58009fe8a4e5e3191dcb727f97bde114d1730ce4966356312721336b2e0329b71ba586cc664bccf73008729086fde3a6d4c7646929edd4f1dfd98393667e8f4d5a73e6266264ae6ce40f66bb538baeff450221c7597ab5a92527506300f4c95cbbc90306c31e383bbdce492dd96497fa3a35c1879e7d9ccbdd7793946a81e639d8b337f38f02386f4c0941a911009a896507624faad0b62cec31ea09d4a48167cdfb0c4c2dac40d6af270587d5c524415e0c246b824214f0a8b14c02d1b269a4a23ee26a9446b4f9f68445923ece9adb03c4112e63f77e27743d19ebcee27b74a96badeff256c700b0954558f1a694cf660aed82a8f2ac57873795e5abd528491e40274e9c3997bbee2b33549ba59a0a01ce587d67cb7b4797fb42cb2e65f396ed2db9a7b05cc3996129724c1b94e3e0e8c59f8a9757f4e9613f145a3de7b5575f83644bdb52ef71ae2772f9d28d19da9a071e46a1166fa81620b6ea91bb6dec11c77d22676b04ea4cab2787c9dd3aafe12c5f788c5d8d2f45eeebe819757b47ca44312b19c28510a07a63d8c83bd59eaa0176544e909ed7ffb593fa0a8b812075b85818e66b718eec803da7e7b085d3a2f1aa6b3fee86368f4530763fc8a0c2b4429f18621aaa5d655f39bf15c01a76155dd86da2f7427f2a2e6c2d17de4ba28ba7220e8dab4f97debc726213b446a261564f6d508c7d5567549f144758f745e7f8320df8f13e1a501872b18f98c7111e91b90bc5d1f3d4e85ef408796e3620765fda7c4f65b5a9774215f6505d6fd77823c19652c03862e4b08bea5e14027990c97e5c008dbcb73a7550d9bb37063ec901946b67a79d67591b63cdeee67497a9e6b4d0bed65b508599723eb483ef804594cca7239f8f56761309d7ae309fa7832edc419c183fa95f6e72f345694899f20b6b705c24bc7047291f73bb74a33c0b9f4c7158d2416faa4e7239a027980be7096c313d7b469d9dc152022f69bb1ba4a1b654b758c390552867a9b2ceaa1436176f9fd01a9dea4093d20c4bc3bedc617ed02116ad78d58ed6722fe65b8819ccc75e459e288edc25bfb6b94b24a3361cea3ee74e72bec2227972989964137243a9e97de890738fddaef6018f70b5a17808fb889cd33c26995572fc0a313084e0cf9ae5a5cc152bdb7cb547803dd3fae036c9ffa835afefe040720f2659f6cf65e558d53a48eb17c3381a26dba3a67ccb2382ffd3e2b4b6236f26bd2d9340ae509be7164629b8befc68e3026bda165eaea914c8b8644c54d33d7209ab264bcbb3d87939230c24356d4f4bba20ea23d0415f4506669ef0aad45b72652493459cd7ac25268d8604cbf7ef686ab427da44c1d1a02e8f839aab2c2448868a8a6d3a228c5cc590f2eb658f5cee9cce4ddf1519f6c182cd7780dad91d13d42fb9d9a1966be7f6812ff66ae29f1b3ec562a12d5390c2fbe2adb5d2ce2572944325b2a6b3ed481140cbf5ef21de91166890f0e522482685dbb2616562f96608583dbcf1793ecb92d32bb31f82c80afa999f680be6ff6d3903541091fd1372268100419de8b4d2635cc6332577d9479d9d7e9f952f49fa3101632ffb5aac729a44166a34b5f837b7bd54a34d0b04c323f161bb44f8515ae5ba20ce636ddd372d14f81813126994e0e1ebfc897a608f19e7e7536a392b2e63c367f0197b4006bb373d483635316dc3cc8fa025e7691e2fd655afeec25ebcd4ec12dfa57d7f729fa77c94f08a32fe36fb64552fe8ab0b63130d56780b4587338b09f395fd9f722008105449c07db59d096a7cc81eb995735e35749253893738a7d9351aa399486cb4732d7f5db23303d0b787b2ed2c94a24bba8ed4c42461e8ca3884033fbb723bb956a87b52d5c021c769fd8e0410b83b4cd5e2dca3748f4e4efdcf465e0c72918d32cc946d406d0d400c0b00b7d2f9b01eb728be14263d34c562ac1a2171724ae58450916d5a38901e195c8c7a7489ed9f2cb6fbb9424e98d12e729b98f3727e1893e80c0092ceef5bea80f1bfae155fba824a35d067d0c9391c736b06d71d518d77757a986edde511572f677ed84538536f352631d65442d2ce86ff64d66c955de35885017ee2266b34b624757ac3b2dbb51b5df4eddd67a437c573804c7232064cefed6654cdb87d28a5702ce3043d83c645a1b8c96624a29ae2a6a8007223449b8a8784910d2339eb6d0a53bdc2cf41a94548fd754a86508b9446ab8c729ab0d691c7cc5f8c369d30a5d8112fc961019143223c6e41fa4cc0d6952ef707315d2e59db77eb835cac5fef0aa9568259d8a0eeba4503d00a62e6d6275f9172a8de1f2f5292ecaf2eb9af83f687b0d25a07f938a7918cf79bd65706a16b4460488f657dd4afcaa8d43e421242c86ff19274bdfa37f8df842910a47996124e7214aff2bef201eedb297d9a3f0c77d934655c11124778c4c679a930c52892856399fee643c9124e9c0f45a931277fdfedb394a7c51f5e38ddb356b632924ba8721408a9ee25504c8a767a19aa520c82d1d5d032192f63d63145925c188084d172062af963e5afd912fca5c8b87f6aabbe483dfa1f55b3782a679cdd12533670381d4debe96b8ea85a17273d2423057392ef805529473a15e4b073d5adab47ae7232cd57bc212d8b786b97c8e1f9d23bb0359a316a144038bda0446b765f6518723fd468292342cc417583ecebd510836f90277c033d6400d2dde08d40b5b581725953fc072c6a01abcd06394098743e82f72f44ae51c99a12cfdbe0376b801a72003ae7e71f67c920ea7d14f790c165fa9f54c90e97e6716a0b6edcc8f3078730d4193766484ad1608e7509787ff8892aa6378a41065ebce4278388589c98c572064df5b0cd9221fc6f6ab5cba90bb6e052339e6bc36015b5a720430e2d072e28dd5eaefb4501f52905dd48a4a7e7f00551572b4c33d3f7116229b90c70e37e7243640c75b03a1763f44c68fe16bfd9748a520fe4c1ac2d0fd3642eef6d58df6522506abad470a1f266ea77c777734b738604068fc230fbf8ce97e703ecb2de428f60ead418686f81112fd43f7c9faabeb43fec17fc6f75d042a0f5f55bf12e1f45e67d57941438ef34a07be35f261a258d71e1973a68942515502e89e6a87d22dded84a5df6e2705b6a83a7f4400ef7fe1a68210d9c9d8abc411650232874e72190971d300ab489a56ad6d4c5a459d665e21289506a4d06efad72e2886a37272ea7b2c8dea021ffc807b218a6a0a90d3e905d5d750c6da22cb3ffb973ee03972e61a110ef86cef73601db190831f095e62031ff76cc294f07a924440a059c872521e92592fbb8830c98f253da9084e3c6b427ed41bc4fe528de1bd2e3210a472a67f9ea087bfa6265e7b66657795cbf16d71d2b7ba341cc801777dcf84d76c6781e3a249d69b1aad135158eb88610338d195180aee490af905ce57bd3984871513ccdd5be4a679a0465db01f8f5ec1e7e3fa86c0c6ddcd16a12a60ad53cbbd72fee233b9cb813b8396971873e068e99c6b844d0b74509daf59fb51ce85dc1e34331a2c9754927233305860d010a6621da01434efa19074a43e1197abfe7d9240415f42e16a26ab6be6d57da3ad62e092b9684841e2f9a60c101f1a48513cf61668171f6f1d0fda4835831e83c5ce3daff17c6012abd3ba180feb518092aee228288729d87c544f4812036b0b1b9d53a0302fe295b6ba633505ec4b25ae0eb4728bd657edc83234b0a38734847af0134581f2042aa41ba845575f2a83286e9c14c4b9d65bdada6651ce7919ec4261119983b25208e6bfa9d335d1c7f1c3dbc072ed23867bc79b27847af649ef8b99530355f76b682e76668bb25fcb2897572072252cc149946b2c1a921ffdef9cc645a095858daebb179071f66482943413a0720886c5dc58abc4e61f4284708c42af94854a16bf06026c55f38d7577513c4972d893ca656a94dd393bcdcd86d5bce2e58261d8e2c4b447c8d27ba2e00316435cbe2035299ef5b788559380182142da8004a46ca77c7c576c73d88b6b6ad42d7279d760bc38546adf404719f1d23f0b786e537475ee431364fb75b3d81f3266475145028831c1aceeb0df186396bcd563476f804de8369afdb1771eabeb4dd80f79e8edc6dbf30007d15bca7d576c4f25b570f8052d0ae1599bfb1d3b8c90ad72d16270907169fe0a5ab594b54c136a66dec3e9e2fa58e5924427f7ede671fa2deff392ea130562b00f2807c787a5b4ac5a392d4b5703166623e3f745ce6124650b9679bc3e4dde15f306d1256798bf0bc9a3ee3346cdb51dd3e309fd8ad4d4286dd28c07f25a160289da7b9eca8ba15c0f144472ad4da0b5284f5847bf8353722071322ccddc52f1b180e081eba0337419a0570a18df12669c5bf83a32490372c9542492a04dbcb2eda2ecf86a45e757944a2877733493a153014aae7703b872913df913fbfd2918856fadd0a60f03760d38acf1161ece88466dd6f036fe247230e616fa964c7ada44bff16dc4420598482ebd3c7b058dfaaa42760eee1044355b4b95480206da143dbd9b2d1409879388c8f6668bdf92e73ee6d674d249534869b90b6e492adc2cbdf61675a0b820496d76a034c5737666c3b6aca2b41a80721f4a1d526dfcce8b16097ad640ec7a67511f6fed64e70f417ca767c0a909d46aa83bb69baf9d09f47efd28da065fdf33840031a030578dbff05372bf0e6f283a6d147989b682457bbd1522ad26b2a834a7b3c09e6cd0262649a7379d0b352472ed851deb835b6ab7c73396e756d4d424a6b807243f9f71af46aa677fd3c39972a7a74e94401ac0407723e92583c1c899b7416ea4f99fe68d393edb1fa644917290f9459f73a0ce80588cc1222fab84d2bfc8c4c297ac38c6b728e895fe2afe440265230908eaf1b01601b2781ead7b31030d5318b87e53df9e42b9db459b0672ad876ee7c27adc976f5cd0c9ff17912dfdce78d21ae1fc9f02516d12c8110b72f568a5c01d7898433430312b9a7ab2adbb55d6d2e13b005a6d17ff0d7bd9e05b7dbea030e916bc1ee11d5e15d5140b0804e0631f254c938a9386ae5d9b414d1ac0fe124d6fa79093495606c769d9cde7cc975e7adb99a052c617356ef0f7b3336a63105ac3eda8b82f27a853b9e63652ac9dd8ca85a1c7aaad85962d7c6be572ffcb6a98d2e75ba613ed48ad1fa32f920ecd7a9fb25715196da097295842e9723545b85563cd55ebbc4e355a098162ea4305b5a4f0d7e29b5e8ac0bbd598a4308f6588e3b2ad3bc9ff5493a17ab8b7463fd9babad1f8b4ffd7a46335bbb2c772a22fb41dc28c00125b6f7d7608d3b9cefb26a179046e077f2a9016eb22e361726617c908b082edb893d635eaf8746b58da07c98d7326ff18433850bffdd6c072c102386ecf61409582b13d4db09de3990abe5837c56de7f4d1e6e46c83f90572885c87d434b93d553c5b9cf7479f4b81649ab26255c6e3446466ef1be0437072dc4a7fa83c163724c04a94155f24974b2684555f90246d58671549976e996272fe8d973d95d2869461e4485dc94bdab22b454b4d5aa838b3942030514c77fc722d12dc35e5e6a1aeb6e3af61e9530aa60a12d64ac1d849e064b1a1da397632728879f2290b60cb9a311230dc775c7636f830e4616462fe1980d4a285909e5a6a6307d2337d4abce4bdd122e7eaff404383285337d5c44b9d0904204bb83d7672a67b03e9cc5491de920db39c9d8fb8ad733f16094bbb3c25e685cbd4a629372f8323055fcd06cc35fa8c6b0315576b17f6b1c27942865d0c08ef8e599d2a135073a2a1105520e5a4b043682cac0c156e8ee53c0a038a9c6776fcefdbd7471772ee17fd807d12cba5725574681a10bcea1ab85b23a13eab57ba9024d8a6027f2162c0f1741115640b993bb5b98b5a46bb9e3d694018c25d3e7c26e2d2b4c9c8729f4b68d44c8001d4978fdf6243ed404d0e0fafd5aec0fa5fa0625830ac300c10df66d3a0a2df418753d011f51261062641b2cb366b570c4996600db8d092b372d942fc35cd41c428fd6fdb3a4e58780cb84d24cec251345745e37408d3f3a666913b991f6d556550353f6ae9e21e69fcce298dd8864b95bf0c3caa03ec69697242f4ea0db58a8ef33ea27dac48e9e68f9a5757ca8cfc3b3aea85df8757735a688b15ecbd901ea9721616f5fcd99e4c51d9e2c4c4e7e9b66633fcfb7333cdd7725cccf391f05424e661dd3e8c781d5cffca991857c6cd07d473dddeeb471f7204f44d54d4bbd6ac2236e99308c2a2d684aa03d3dc530edcce411dbc63d8331f728581a3d03ab32ab30c4391123a7282fd160588f750e0a6725a84f7f528def911f528b0cbbc74248c2ff95c060064f8e80d4a486b8562347039d7b99c113477644c4f12120bcd3c01ece26850ec481b62fb71a45b065df52f39d9ee6127f6243ab0dd7ed49ee55f0a8eb31cb150a2d4711f7aee18e401a73c9ad0cb8e35053e72fc09cb22575d42a4dca0ef414323947629cd9c3587378d1657531f3dc9862672065c11196c27685be5da4b801020fe750a8a69ea26e0b03298052aa619fe8d55bf82ace7f066f2942e4d019fd90901706ab5984f0de04620ac4f7bfcaf27305d6bd4f5f7471e16abddd91ad0836fc04e3fee74345ef1098dd0fe3359308d076bda51d5a7ef67d8a30705ddbf3a70b0e52b8deba58621cc78836e192450599772bcf199ac682fc8176ff6810bf8d9b20a3e2d1fc3d938aaa53a070eebf23767075bc23df4fc16e71b77c878c896a63ac4bda0d784641d8777c2957796d775ce300c207dac6e2722665a3922681bc943cbf66632341a8d8c9edab7fb67a8064e46fd6b0c319342cb05778fa6320013b650f927d8a719e8ef1f4aeb6514f1761a525c7f10dc556573325dcccd25bfa8ca6ff7f446ea3ddce6118fad011876e2303f07b40fb50a4e50a0ba7ca4b12301158a1fcafbd3d414f386fdcbf8659d2e2b4c477d9d5ce5f43e838074a8c59a7202fe6ee8054fabad9530fc48546067e8b562fe42163b68ace46fb5ace124f7ec7f6443afa2969e90e842e455fee164662b528940578a38f4b8544ab854b9f5a1bbcfac2ec730a723faa5bbc2d62feab1c223065d3c8189afa5d0131f531ff103cb02e125d155ee95a56718b361a5e365694b6245040cc07b80195a25e43ffc6433eb91dc2dac34aebef8d3b3218795ec10724aec0f04b933c6dadf26873de5d420ce9cc732fb57112475a950e62df6af797224e43961c86e4f1cf6d1e225f22f71c98185a08615b424047499f83081bf1344d26faed1c62c74672432ddc6e5aac7c9e4f0caf2882bd9f032c1ab80354d0644d2e82797f3f7927b29377b72fc85cfe2ef1aed60025a09834b3b22f3b5eeb672a8d074944cf2908235786e1402943b78e8e8100325e0fb6fe9c64648f36edf6ae750057370e10fc27012e7484d777880a3c5b06d84ae76d264e7206783832d30881ac00fa2b3d65a65c8b5e05ea14b560cba48a94ed90996bd110eae1f3fb172ebe5a5e52af193a9d1a26cf0e4fc1fb2c689d0da1d8f72f992708492663981720998fd6f34a67ce80ab99a47dd05cde4285fd14d6fea701a6141c948c4f3460be9f5cec8dd3e3227e8b2870662f35289e87c43fa33b6225f3184d63a44730c3b285019753ce6d084e369be62f0bfcb8afa1c20c14af49d565e6c0202b36c524e623f0b619a571471143e8d1619016e39db0fe8a0955ce828f6478559684731725240028ed3c443d59f79f6e0a0e78b399381d9c4632f5fcafc7496b49d42722be41d189af2754db5aab5dcb1423af44055b023869eb9fe958b827dbcb95c103caf6711cd3a6b47817b039289dfb76cbd8a71d583d391d4fd918f2bcebd2c15720111e4da7212d49277c27f34e231ad1b5d01146f4eaca8bc6a24f4cd4509201760d7d9adf80e1b2cc5bac0ce86ca2f1586007018e172904e0ee9bfb172cc8f728a206a0af66f9dd246f74915a1eb4c40ce828812e8a1f76f10986412cc13b272fc36bf6eac83723918d33fcdd090b55aa87a94b472b186aa0b0ce49cb95c7172f6b4ff3163f73075c758185e4d48e124160f2b55fdcd42747279a8d59f3dab38527bb12017d09c44cd645c7676a0107a5e745d89a174d9146bee529523c68672c9c36b5d29f7154a25a4475f9f6533e71d4394accac334d1b853f25cdba852724ac45f6e6b2e51859aa9588b1221c14d484c452189c608739b80d0e984d6d87239e96b4b8b0e5c25c14ac6a8c7ee66a89143470d509b744157e862ee7ed03c714e3a431e253ec0935d0c0673f23100bdff267d53ff7784f401beb3e6585ef7725b3e3795a14feb8defeacbd04cb27a218ff62da76d34dcd3b732c1ff8873907221c7f98338f80fb44dada2d0ce94fa3f0fce74329b7c8d47f57943674f17dc4e085a5118515e29fb89397b579d452d32ceed6e640f29a150c5cad5bafeb27972a24ef07a6ac3c587042a7fdc973fea49d49f9880935074174285deef7ebd3f72ce5c23c36951686956f20fb6bc05035030e0ef20a46c8dff3c05d07ea1fc215e7a1be7d850a3e21a9eb4305fd3edb7ca4550ef81c5589414453fa33854527f724c985b6ae49d728065cd3a3c8f9af83af219e1b220d2595dd4a25cbf1fd91b726c37d44277726f6bfcf964bfc2580bb0868ba0df869f9a6525f2b3dcd1a26e482930154d0bed8749aed6249b77628bf7898c71694a43f7e1625c2dd4eb0e775e3e5c64f6b7e9b8d51a1a53e8c7d43b65ad028b9ce51f41330e9c68cbdbf86f21f5e7962699ffdb231795e62c5872ddfdfdc524d36a13f4d43902efbb84a4194db186b3bc3725204f626cec6f19abe8a3b76c3864a06636acdb7503c4706ebc72ad0f87e5034b7ab1fd5893762337300fdc6f23a50e01493ebbd13f6e176d80453dd22d340ac14953e49c969ff9b0405d1787f695bcbc2852d788137cae41a8723f2c3fabacbc4abb004a10fea36a6253da0eb64fafc5ebcfc18c7281f53bb3722abd36eaab4a55428cdb60a649e6ea467b5534429b0a4458ab626cfbb4a93c7206dcc8d27eb40562bf3cd261e5a05c0cd74d1318c102d166ca37e733297bb472666dffb083f726f43562d775ae70292ff71b8e008e57b979fae73f19f10cc472d63cc22c29f2571eb9a6bbe9eb84efaee1244536d73df5e87e809692bda47172e418ef0b5a5ff4fa5f4aac7fc5124b9f1b826c71935d585ad8b87f0add802c720ac7615ba185561b089489160021a8c68664bed3f2e541dd96554a6bd46d8872c17635dd117d461ab0c36946567dee210a043475bc0d9696a2a268f94bd31172cb46dbfbff4a635f57811dc8d72a47d1cc1c8c8cabf7f7ce362737878a5889723de4d1dc5d2ff1e9da439f691803df04703283aac2cfb8dcb7e674f9272d0c0d100b05eba4bdf7898edf59e7e132b0a30f1ba5cc8984050a5ceef7faf5198b725403e240499dafccd7046fdc43379ebd69fd61c378a02c059dd2c799ce3f21640017ad8d1fac0e1afae5a1d6ee721094ee2c6744d50714db6cbc0390a0c56c72dddce70b26a4d128a361a503187f325f25a7bba50a0a0f1e16c3b94b82648272ad7590e26691af323477a4de55f017469d38ea79e9d8f0955c4d25263c98a5036d7c94c92466eb449aa6ab8d93222450632a2616394dda3a68aee117a5abe75478b88a82ab9cc037543b523cb63db0ceac451c90913dff5f392ec455d7ffef72b37e2ab4427a901c6d98d1e4d6a7072ba3b80592bc8814f1f42d132f87d92972439eda53605c85ef0d6ce523193fc3c2c17a107e48df69b2942f77f0ab3bf54108e45c9a404b3bb329dfc2e2506c29a70b68358c55b7dc03aed6e9132575cb72cf8fdfdf68d2fae1a9261c08fe59846a7af91bb4446d427eceabdd0c7963362603703ec29af0ec159c0c4aec198b4d26a994a28601be112fd9e9cb20dfa2cf173c72367164e67c09a16e016713a350e559f696475e661c16ed3bdbbcee2f3d72dff65ce0611674a3d894cdbcc11028c438a63591e3ed88eccc4a98b33f956d72354270d5a75f61f9de5d66caf4d280d585ca4e8e67a5f3bc720a3e3254fe2c53940bde41a87b5d09f76fffb3e120862cfe99e5636246a48867bd16a57a3ed472546fade829d9b08a5c62fbb87c7bd7e0ff31e0bc1231b137cc2e8e795c5301722f43dca09e82a6df4360c8557e4ebd36e80a711c41382e7a9362cee49fac2b727bc959c56f4104884473581286cea40502d0a6cff0980ea6843c707475aaeb7271e9b99526dde3aec7f628ec48fd33170da63922ab746e18648a22e37ea1a46f2b8cc110003e991a6d557cd4af6ec27a0d7bcc034a112dcae2e0f4dc51cf6e72576f4653edd7202f6fba103e32fdd9c9cdbce13b1439743132f0c4c1d28eca72f91cfbbdf3c43068102d280321034f876ff135690a62bfd38d231839c8449472de82410adf59cd293d2279f32e0686a90c1a26fd8612e5159f530e3dd35165245d0229ed3401b38dd818521da375dcc6295dc4875748a82f2cab3f8cada53403cf835c2ad52512d527463a6667743fc70c5cb43db98cdf123b72d6fe294c927236e124ac06f8157c4baf5ab009be13a93dc9ab8fcbe3346269dd21cbbc8e614220b468bf8c2dcab35f94b4f035e675b079eae83d2774f6fb08b7d0deb948c63927447ba50125431151d304a03de33717e55d5a2dce5f7298621dab05467d137290c70385b40af879b6479b8efccf16fbbc08d16138790ea7661a79413a00587296c0a468c1f5d5d26797f2b986dfb5e01cc42b3a0b40a3431e61d325b3cf9372d8e9483333e229da259d96215286f10f00220f092f80df59c7fe80877d57b1228fda9415d97ff92a28288be42aa6a9519b62b6004fa010583764c3291ab36134e4081f7b2723fb6082735bcf10dbaa9fa461174051ca9a3bf8085c339283147245ff382789e291e4c32c4b7042682b3dc26e9d8789f126ce9cb4e06ebff89672f6ce71df5dcb5cc1d8ba4bc4f2c884fc2eacfed7599d86e284bfeca9afb8e600a9f6dfbff6abeba91dab017b657a94ad3e852ee83c0274f9ac7f43cdda1ed772ad8229491209c18cad88192d87e0fc3d0b6dffc8a513b0e4f64873a93ec0dd19e98a7f8c31c76151a183e80ebb4cd547a1bddecf7cef10f68bb0f4f098e490727841630ffd483314fd7549dd3679b1c68b3ef7a4199b19f33a8e05d2a7e99a303ec9606a3200e9dff8430869ce6e8ba32fa75cb201355c9f296c3e812fabba70767a4c3ca4c6d2d535bc2ae2efeb5ff92dd0e67b21048228ecb101b8f6c8ca7260f81196a32220704060cbdadb91d9c5f89e1efd90d15516b5e815dee3c580724040c6897a6816399c7a39c8ae82b5dffb6536ce5fcd49f264d420162b21012aa3b3e869b87ba42a72ecaacf7f1c27d754fb2c988d0fcef1c14788e454339a7270cba9520588e8b7bcb98b11d41c4ce251ba16d728626b2c1c535d877304ec08a8476937d36c3c4bd533bbfb928fa6f7d587811fa996a3e7bac615820b47ec7264bf2e1fd5609752481093b9f5b3b55b39974c310430af5b2b3362db7998f9202e8f8653eaed9f9970b8c2571db974d3ae1cc955d455b2e7f655401def76ab72df6991d4bc418421984307800b11769a14d9593e22c465cb24ce372661f6617231b12f1086ac45813fd17d81c77a1b41c08922bee2b0e5bd89b49ba79b82a90782694b0a80f61f93d6ad20629165c72baac2d79a59b81f0ce4228ac16e53b63b5422ecbf1bafac7225587b15f7bfd7eabd062f95c6d19c572a7da4a43410c3729b5dda9a044f2b66391276c5c83cc2dba0549ade615a6705200ebf469769044b4f56f495882ba0445928cf5d0b9022cdbc4b8f63d549e6c9f37a860ca35dc972ab4674229236a15e5644cd82c5a1238d588ef9fdd0ba154d0381d18df4117372c71604f3af6fa41f002b3578c90ccf72570a1d58cd77eac7b5925ce5839a321e9ab3404005d9bbb6fdc42d7fc215e7f8902b552a6eff60906624f76caeaefa7231bde416e5003a4d361081e03b779fd605a4b1f41a0f1d83af1622bab8023772644a80baa1f71a6bc41127384a5b04e069f1b681e9872a21da3b9c60f7751172c09fb5b97e2cc89620a01a0101259f4499545eece5fe0d908323c3245728bc72ae683b0c23b6defdfbacf69e1b97c62b5bb6a1618efafb276e79bf783ecdcd7232c674237c6e3f6a8f0f049bd06674db7277fd787a522902d746882699b86072fc38ca759f4ab40f1df6946d22536a29ac0bea3b5e407965c09175b13e691818950b6842b17e14a3ba80f44ae57508de8064c14402d7223e761c2a7bbfc87272c959c7f316e042b3bb331402d52039cf8f2769428f7900b1612b37f70ef955254e72781b3db0bfd87ba044bf29db170e5f8b9dd010440934dca518da96c059727e50422d467707fc2ff223ef62eaf266cc79c2b8be6dfe3d066a3e702f599c151816fce97b094cfbe13e0fcb72f5e7a83e2a2c7328edfdea7f060e59ba101572ad5802e058549ae49ed02a309fe74c2cfe095c41299056675a7220cc7ef075722b7a40ce9e77bd361088dc6143e1208097dd0965905466e9cdcaa00ee68e0572b42843b9b8748cb0976029535bb9b91669db531ebf41929836b2f2c65fd81f7238b9b34ed3fe06d9262858448242759f2b366540d67698c67f6bb55e5878d7614776f78631ef81167e2a11507c77184af602999e0bff01e1cc750e5c6437e872d916ebfc4007520d89d11afaed4a218ae6c276158636f5d261189be1adec9a72bc6ee3d37896f48f3b80c2a3a514dd290b259442a036d182100f6532da0100724541464fb1e8cb6b5622a01c10378df01e1085d8b73a2fca63b7304c6cd7621ce0c9cecca77e3e0d545861d87c7c0f35bcf484bb0aa9cfd5cebd80271e6c4372bd8c37034c6af02cd1e83d34a112d998cb924fada0fb9c41aed4aa5d3b9c3072709fec97d24a140989aa4082a3abaa005f33ec43fb52b7992e3e629148f4932e0b1a2fb27da4d8a63913d7d6b705f4be4352f78becebcadb08ddb3ba840fe8724e44ff56aca9a5c2ded4b6a75307bfeab061eaeec32391ce3bf310121edd84724a14e49c60d195d3079d251c7476513f3eaf53af229e3a5e4cd8d03684a4e91ef45ebf967666c82310704a37f66c517116580dbf6e37fd650c2d10549d604d7255da4c70391988a2911d0f9a3ed0bbf06bba6cc807946c1d4027b0de022416722d505f9ab89a6ee1ea1eb4c2d6ec57d888bd1743adfcf059b63ba3896e716372447fad8a0cff8c53c155f5668d8434fa1c40a405486efa5e9c503526c66d65726e79662a69f4846e76c6a243467515afc9fc7a3d7274ed7922e0422ed3a220652687576ebaacecc89d29186db01edefd55055bcc4be49bb43175f784226b9d45b298b9e42080a0c7008358ea658028c3f46c1bf9e10a99391eaaae6c8a957e72e080b6a0949de450508785fe60aa5fd2a9404bb7254451e3024e6c80429ed32381c35f439c25764cc9190ef7e06765f304cc353970eff74d627f561b8539bd7208f5e7d7be8fec26d602364e474241a7444f14145cd3f8f95115ccdf3a814372d869b62de75ef6c8ee039423c7a690a0482290a6f8a5ddfd3b63a751e34ba91e83859c21c36c58746073eb65d732a31cad859654d6c2cc1551acf605dcab1c72100d639615a8205f6e7d03b3adddd15a72e32e22552a33638b23ac8e0b33ae72bb5d98ed5101c692dd9903a69a02d6993a8a08c488be8d55bfdee6c7492c0f5c695ba5f0ef9c4859373aba8355dcef1c929ba42951aa87886a7cfb79dc8f367289c92f4094f86910ab4d6602239341c924bbe1a28836f9656e5b01fbc2b18172cca1a293fdfe96008551a0ae5e205aa0bb555caaa4871a16434f033b82c5cb72bd2f3e8ee489dcd4ac20410fed8fddb3a23b7c893551e1e4e9a59408255f3572405f6274293ed8b60d0c2a500f873e85fd9cd5f76308e5a73a1189283c5dcf72a0c267ff9c9f2bf8499e4ed349b1c8872d665334f8b60fe75cde98d1f29d8f06d1e4fd4a24af574111de02609365d5f0a7e90737213493c191bea81ae2331f724aef82473fef1cb6ec1b8a022676744a8298c6f352f2111acde7e0ca7a553742cf1de95260eca2a4c81f3e0e40eb9268f2bc1bbca7f53e488bc985e1f722ef7243eda213d2ade5a9f8a46f0d5143f72eea532d95fd7846092ea53e6bd2ecbb72dda691a9c495725effdea6b07ed395baae002de5a6b01321d36926d2fac96a7277222fe9bcae0abc8a3629bba33a5e74ccfa31f507e4270b8cef7fade4b4a94947012a0b7bfe55a0a1ac83666d96ddf0f615c5a57aa7113c9ba42807f93da77222da92df2c03e559c2bbdba997a389bbaeb93185c919c477ad7fc5f4231b9872e04c9f9b3b072b389913bffb78a14204c978a715befc401029f991bbed0a672bad7a9647dd9dd31ac40282e7f8fca76a4e0e27423c30dfcd607138c039dd9e720fdaef30e0ba10585a77ae92ccc1f5ae54d1ce81da109f56d3dc1ab138cc962856dc76df83c1d4582d972f1412ac0fbc409df2ed238efdd2e8daa6d28eb0b9688b730c25bf391ada71f9e4d423cf65b63ce4174f3a6e4a67a7ea71e8ce5b28100c26e0c7246e6527314406b11208dcc125aa937f2ee7bae224b50ce967e81d3ede4afa9f6a46738337b969bb374f9ed1e48146bad6b854f67dc6f3c293b5826559fd2c8db27c135e1ce736697a4882c0b6a65edd364329d538e37add695e930c17735a0dd11357c423af0b398277ef59e9a6bf78f9af3481c0e6efb2ae17027204d50c6925a4510f2b4aec891dba0dfdc05b37964535bd3de64a3873bdc5cd15b12359329e340a2e4baeb1fbbd4022f54d42910c77623b4932dfcc887a32f17204ca7990bc8906c51ac25e42193efc7df3fbec7d9789354320c2753d84a435721488697b35e39eb604bacdcab3f4e36f2dce86435f39fdba90c53247c0017872d0243309d96f8197a0dd30f2747aea57125cac11c2dbdb717d4210b1a5d44f0b2f403222fb812aec289cddbc1c943ee4c96bf9ba7f1b6d80beb06897b061f2005907c2b59b58be2f00de6d9fd7c956dc8b81695ad9eb86222f88aa97bd411a72424a06eb327bc95a1a328d0e191437c70e413cc904d11f583ce6938a547d28389e8b1540b973fdc85749db87824ed32e7c9b76de8c38725767b93422c5cd28722498e4676dc7fb0ac76f13a34786c13369df02ca9eb7d5170043ba4391fee211cea2ddcb135dd420453306a8a13a70644c62d48cb1f3cdabfe6f98a63ea944720c3888ca322731a5db8427d691b987c3bc3ff6ae74c9794b9fd7f7bab52d70723f58913dd37ab80566fbdb5555b6906c34d50bc073e561a8e54da9dcbb5eb2727521bb0009f1c9a2d116cbc006cec73adf8accb5f3e957e48daa399497baa172f0aab43f1d44a39ba3a69ecfc6c65e35f2246257ccdc52dfccecb38db12ce5729ed85962138be556f9e85dbab947e8b98a94fe38ed486f3b5254f8e1e8177f72a7e0dab5bb760e58d7ede33558b33732cda0b8e11536207b999e34c91959a97252cbd38134ebb45a8ed1e9e56ddec15fcddc5672ae404cd0c8e11c8514a9e64d03741e61272282367f24b4b296eecccaad189ba078182f9b0c872b30255d4872c78e36a2d54bfbb5b6debd263a2b6fcda396e0e98748c6c1003d28ca1a51f572072afea55413519e7414c36a67e38305488d4dbbb3fd9aad4d6d87ea84264d724df286c1835c47967d83e83e0d30c7d93483da7073eec09a98dc5e855005c572fa38020f1b2421e1cdfa5dc15eab7617b7ce59490f74b711eec63427f8352872ab89faaf0880bc9cd80b5f8dc08b75727730db49248333c6efc83f50c3165455683fb143bef4357ab14323fe2194e9503bc0393c656c73fe0bd7b326dfbefc724d552808810c6bb9c75735953ac27ce41afc46687d21eb8f528e824d658361722bfffa1427808721e477fb1e2a7a06043a49b43e0339c6aba2c7b629b3e5df721d4cc9202863a4efbdda66bdbf8d7013425279099efd173ff87290cff076826c4d7bad85e29889ba3758b675ac5e2037718fb4c74ed7824b2ba0f7275f8f837204f4bddd247ad8111d44e8b0905400a117af505b297ffa497c46c32902a51f4cb1788c3848411b651ad3975744fe98cca123c121136e8d43751bb29091b7db67f938bd91dc8af2ccd497628d6c604f6a4bdd8bd94d3831e7fd246dfaf0084272b3ea306183bbfaa1b94e1d6f0e7db977a539de91282378a4cd8a57c9d780f772bff3821baef3f97fee9f10075dab5d0948586e2bde00b4886eaeaf7aef8be6728bfc5c845765c54ddce911a21472986d7746f726410172cc2404cdc4d2db8272eb52bd8bf2133e36fe5e4327817497e6e53a498f7c16438ecdbef3bd77ed5672d567e66fa9c2024a4b9754094fcb73d01885a754a6c236596c61961417d8bc726abfd577ff1eaa8cf5f803e9972ba9ccc0749a658f90572afd59be4ccc12f0728a6bf1ce7e1092db88d9e274a7096b51c76f4b46a240f392da988e3e7ec369727101e796b684e0599840d7c5ab1e70a158312b4fe31eb59467a3215a9995c8728f9135723f6dc80430ad5891c78d68a989f70e0200c14e6a2f5bc4f038913b72d9bb39095c38599baf39ed3d602d0927a4e31b72086f5ef3c7fcb06d4a1ada72f83b0f1f99e4d47bdf42b4184c9a12b8ba0afde7659d8a7c5070c2553a44355b5c32e340f31f8ba329599e48866a7dff483ec460b822f9bba70f25435485363598b699c3ebfc06f816b4dd400c8e58dc91657f5fab50d34b8773e05d24de1172e83cd95bf502db1aa728f3c4562b6d0e3c430c95fbc26ba97e8d07f9f782597261846879eb837126add0d257b647f8425b52058f96530ad27724087031a82672c0cd6cf6683b3bbc38754270fa5a3d1fb97696c35d73ca37f93c451971e3266583eecb170b82d48d791501038bb895092d9dfa0371c70dc48a0733d44e906d7230b8d733ce1556c33a8756a8f579c100a34468a6f438152b83d353c5b8b6ae72991c517b8824a9bac86791fa2f7eee41dd74871ce1b4bd79d43b95495690ff72ec9f990cb28d412645d02756bcebbe28405067db0a77e91a720a5aaf035ac61a547b5b7f356c45cc3f95c08a72cc05d4f6fd2520719e0f8f33eab69ed84fb5310cbfc5387ed7e9404d554dd79d7f8a8823a9b0b3a7205170aaa4e9edb9c842349ffa52ddad171b7c15a6df5ebeb93b6f37a45430634f60f92bdb3f9d20ceb27234f314c3b452f04d74e78c97f35ff964f0b1a7bf1af72d6f1f0d60bcf81fb572a620c0613cd3f6ed622ea8fb723ff4c2421a5b6bfbb03ac401d4c71245084272a8e0fb4bc3ec19d13b3050d97ad45e6a0e5b6273a85e40c64e60a3e038622b720f6a065468fb21f20220373e2ef014f16b8474ae55db65deb211e8b7d74229694aad28569b32fe6bda0e0f8da5baf141aa3e0616017da13f05d763bac5eddc723d8f8f1c3b58791a49d7d28f860b00c4815b3a810d0d0a807a3b4cefd4406157086ee521537d9e8e60182f0be08509ab6801d5b003682022bfd742cdebba4f0180b522cfc2682695bff15c254c6cc313ae9b2f085f37bb488182ffedb34fd7720d91384a647ff4c14656cdcb0d9b9a30affacfdb590748fc71bb6b114e956e7200bc257383d25ca8f839c93adc80aac37a972bdcbb1fe8005641d4bc6f7bed7221b7d40b30d00778c0902ec2546acea3718b872e37514cb5a896211487476a04a7e57f3a4349a2feb70bf88731d44356dbe26599bb1c0a90ea6a6a81f8552a72dcae016f3430a90f23cbce44632d44b5ceeb7d5ba55066421ac3b33c2dd7c35ed902f8662ac6dce59388f794979fca521ac33ec6d9e871df23260815e4549e7250f3227d6663d85a2cebad2a0421268b2f4e0355893e307bc584203a4efa60097c45717f902907da3c667007b2669864431c379d7a3c1801f223fee996536920d23a452bd73bf5f7005ac1987c7c82eb79c4204a870f4c1aac8f89ec1113b50b2236a5d801033f56725f41edb8757fa8329502bd940b96887f169a66f5b3c972dadd174cb5a5982b6a3890de3cad65ebfaaf40abc2de024a9dbf0ec9bca0bb7285f5e035de459002eccbec282e04c71da8d992ac29dc03c607af645a5dbaaa2bc91b35f5a98719fb78d4b7e65115465c01edfa53fb6e37123c77677d6ec8c52626838baaa2e97afa119f217b9ada919180f2ece42d460f7c3a355515626b953a938de7fc0d7c6f14145970abf4e9919e833306e7e16d6558ba480ffd66bbc17251fc70c84e41de6baeeccf99152bf83ac244f32d300d39da72a78cd2dfe23557c301e929e7048ad54b218d9644ae9862f27cc3b064bb80d8d3ea9d2ac0bccc72dc0da1ad75923b7e44765c3cf77eb3f81dba61be2755106c2706e16f40cfcb7214bb79208c318b2190f45984e8d0364899768729ac169ecdf48d4b979f7626721a5a2f724da35ab15861bfa86c6c617d52bfa215c8e7d5ba10ad9a5ee124ef72997b7ba1e5642bd38f5615bb554ade8196c4101ce409c480c8ea52cf7483e272d2e43380e6e7bcf290ae6a6ae917e62d2845b7299a511034ab8dbdad9aea1b722e8959977ce6ae94db18810077d10baa020701dc48a35e7a0d5ee58e0702076a9a895907c48ed1cb44ff22086a7c4c35746880186d07645f4f98ae9c0a5cca2f33f2c18a6de9ff00ffa84acde2346a5a1f382ec93569b7ffc43fb3cdc5e37172612fa2d223e9ffca220f0ca5ff8358e462ed0e4903ae1766c743e80f9f5dbf721be2e9101a1e728e5133202c35fcabfa41be1b29b9fe1690f81ccb7107a140720c9d295bba72d83ef86f9e259a4c6588a5917d67bd817c038c38c2e9c89cee700aa6a36a6eba133cd2a9c147f635550208b86238220e36e852dc9a481955567281afea125657c4e61ae00afcaa144cedb2c26eba555c00bdf655d2e06566c25f9b9b07eaa522d79cc2443b4e5ebb9fe9f0db342ecd49d9694133d1347946a7472835205eb4e5c0631e51478ccdcfe48e0d54730afee3a7cfd8eed1e10c9e0172acdb559f2933a83c1d767646d2979ed99684a8a935ad4dd9fae057957ee9e3725861e752268058298b436da335a241f4d165cc58f28320d1a53f46937cd1c7722785450254e9af9f9639c9145c0f6cee744ed53d320500d611a32a8755d32a2d67547e0640ca500c457d70524362d3a877080e14ed7b249fcc157a8b18168155b9334d72e678d6d0c914dd87eba99a8007be72fd85f0a6f13b02da1699080f726a3c08fe7a85d529d11dc8cf7fd82c83df10a801fa7c974d1c57ed7bce242c40918f01f7e043d130c34b9396ac6a55420fc09177c27c736a1cab8d70a88e02729a791fba1878af44d056107cf2d846303894031b6863ab84e5a00374f5d11a11d63310c44472b75ecf80972d8167f21739a2ebb23eb84319bf1ce616ca17af0d20a3ae36f19cf81927e8342a8049624bab421814ea88aa44616434396cdb9772ff202c3de0410d8a8a63ef8e420ecf0573009ba3c2cdd1512c3e2dbe366680292a1cb1679adf5342b448a5df2ac8ac543077c97156401eae278b909493729c72d6722478f25c3e2ff6380ef5c2138a4ece1c0dd8631ee987619b51582aa63c72e9c6f05e9048f176b7f5de2c198d134fc50be2e694a0d5d385e75b419b75ea72cfbdaa22b858a21526ef1adaed356bd062b2375df2f3c4b0f16c0e292ab8dd7295874d1f84864e68d129e021e39e4462180596403d09405d00827b12c88aa90809bbeb56b0a3485ead873a59973f8faa110b208fd3290eda190231a68e8574638d2a0ebc465f0fbf85b111a252326e24d82317253fccf9d8f8407eb0d9dd5372e572e84e5376a1aecf2abe073fab121d891129072af946e998f345e9e064fc72ea696882f2ad220799fd7720607edf41e1c0a7b2d947274b9eb4e0fb675c8b7270b7f4c64442171d3d21fa69688e62d6a90e1172ffd9cf3262de84f5f98e3533c7883f5e812d1b6f41f868a4405c39d4fc37326583ff72bea0018e32816b1172610a65089b9ba3a82e8766cee6b7619827b81aa6f07d8f332ac88d3b3eb25d725d587124c0137470e576abcb04d6d4c05d89f556f7c12c2f95af73be7e700d729d1f917707d7d047e613779ac0004e049886f989231863d5e1255a8ccfd63b095878329de0b7097e6295461eeef789130bafd2f8428eb3eb042a1999cc7b722cf3f24f9dff4126a2939cc5aebbf506ebac7e6d603a98003630051dcefd10d872171c54b46647d0ead83ca629b2ee78f58a5e808e0f7172b40dfc51654eee927251ddab7b4aa1a869653c4226524e31efff12052edfa2ebeba23f42fde9df0172cf17a90affbcc532fbebd7fde6ef529cc3af2d07e176bb706e7c0602910d1831cfdd311eefc887dd9df77c94d65fe73cb487ff31c180d5f60764cdbe6fa9ec72a78391ba5f5bed39a74ae0bbccdfca25e9b4f6dfc1de007378a9dfc6564cd372a12bb1656a79082fb3eb70284f1e8f76adf98f976ebcc4727e2396b53c95413e7bd61fac174149b99f86cbb7226a157d8cdd3b65d879dbb4f94b92881b42087239ab4d9cfbe04ca2d47f3534bc8545fd403785ceb383efe75e7467b40686c97236c445b8bec1c72569dc3ff64935eeb76e68d8ec9e44b0195527a2580e1b3b54d252ff2834ec1453cfd9df45f281308de4982b0c12dc174f5373dccdd53d6c7226b8086a256f4f03796079d4f283a31ade417f776949c0804d60fff681cf28462a8ad3f166736237600cffdacefaed0deecb5ba11225a11fa4b3fa3e7e9c3b7297c334562bdabae3f5c5bfb138b9c088d7c12eff37871b349dae5f72b3a7cc720ce9d782d5d53406380cc52e10ca0fcbb8acb29314ddac4a4ca883341a5feb6c4aebadf4d0db336de65258d2bc43db60c921801c4bf90ce1acde06ff9b3857722fb448c4d06d7d87b4f13471e7bf4ae58d9432c11e5887b221eeeb70392f44722fa785c45311380540b4ef63992b913bd65b2edcb7183966d722ae585504085db6b8bcd73da6821fcfb11369db08951a6f4561508aee6573fb1690eb75bdd772533513a51bae083b65c526c08321c0a1a67128d5e3e18ddbe7514d10cdf79c030bd7f30c98a14d6d9ba7e5fadf1f49841074409c3c366f57d58341c41e3611610308f179843600135ac58a8f8cab66bb660fef37d6a86ef37107a30ce1ced85b535934a8c7100a53c4621084cecf0247f3d248d051732a6102fc91caf5d79772c228c47888a12c2bd8badef3c3f77886127ecdb00d7fcc60b4e8fb57ca40ae21d5716c64fe2b4fa53434a7763afc3d613f2ae1bddeb666bb3a4e9d60fdf7a072419d9ad4229f78526ec7893044eb4bbdac4e37d565ba94d6df421680dd1a29721b8003befbc8536138092b561f5042cd04829ca76912271340fe3da8b5989c0d5cb49c06b4e273c21adc1f278ae728ec8dfb42da0151e720364804c4d0753d6117860c5e97a155ba2d71a69eaf3efec3fb8c642a8b5a9499f570a3acc0ac2d34f06183bb8da3dfc03f7ca2858e045c7c3d088c4be4976898fba53764a2a67604c0f3904542170b7b21068d3471342c398858657fa1b143094179d4b310e28a72f0b9e5bb3f2fd996644a105cdcee42b555ae45edbe3a5e8a02701d51e15614728b452d9e57c704abb05a79c14583e1ef8440bc7f12ec917b6deae229f48f0e38d1ca1270b5981f1bfe236848ba2761558eea4e0a2eedbbfdfe89a52bff600f724b70960107bbd376873a6cc7fb2c1d745336e1bf0dc3c85f8b345442f517e87272784061125a5caa47c0a41dfcf1c456d6be25523833530577bf401947c0474c5857f7fc91443cde99f8d246ce2f21d8ebc39aadb04309e88c8e317e501fab72cd29f7055e9839f0c9996079ae6185d212fcd9f53c32935eafb0b138b1b98425fa4603c32a1381d16efcccab295e4263fcd56d611324564459ad655b4bff7d68aa0d3e5a7762edccd0a66301fcb514446798b3243a6014d6ef3993b82629c90b8267de21db8ad6c91c44b7fd619d3836069b961698d4565f7eb0dd2cf479fc728a92cc72b3d94b1ddac20fc56be37154300562ef72b6a17e8cbc841a6084d44bd8450685c3fd567c60b815d99082a5404da8c0160ea02e365d883037e80a170d246ec784d3ce544435a51617620235a00defb30f98e5812dcab7976408a88517af9436e220b9b29e01488ed2d712d0041e613b98919ba0d825b004bc36adc572d5841971b4b331130fa20fab8c170865e934cb0bae486d0cda96256241e2bb27aa1a054b8fb5a1e439c1141c52d3fedf95685825d061e87980f9fc726282936975fd4351de69550f8b28b500d964f60998998b1e538fac310386fd1ef2191113e44e9e2694b3770dfd4bb0c0f5069c89fe77ec842f75785dd4498c9f321f91729f0a33354b5444758e2d851652e20afa8e1be35dd863d73d557383cf383943722cea8cfd168a9db6a8566bf7dd6d2e156389790cbb7abe59e844277102d5845a2fb26374aa1680a27f122e65b5e98ba6ca2d5b2fb4e71824527f64dfa8e01b5c05c949fa4ffafe202141ece680ca624f812b9a9c41622df9f1c93430dcaa6e3d28fee58d437de1d6597f42b5bd2f791c7d4f68c6022b900d9e32a74e175c50724895305d4fbe55a53db19a6eba25d741fd012f2146eb5241cea2a1b4ca7e4072e263f7cbb22efe60925ba89dd2b4cf020166d88fbd638ad9700f1d6a1803f04d57a0c140d6fcf25837c69d68126dcee99e3f27b618a25a50ec36f90ea82d584df48d0902707c998342172ad175540a4fb9c67c7076ac369848cc726057c8a34cc03d51edcfe1b71afc682a17927b82c3afb1dfdcbd387db718068c04610b4a7208934c65fcef0306fe22f6c61fcd5728d562b39222ea757510a42e117523175441f7246a044b2d7accb8af5690d43f4a7a6de98025e4acbe2cb84e0ec6275272f69bd936a3f586976fdb569aac92962fba4d4fa89c129f79cf1cca8106be8050f7ccf9467645914fd7503539e94ae1e00523d0a6580173974c1fc48bc8052440633ca7f2a9e85bd4be295263c1676338cfefba2ce053a9b6d9d6e5fb9302ec1e0fe24fae2ec4d41c7ed65a1208ea880e9751cc21269f465fb53bdd02cd365b6636e44a829b3bf932067f29b203658a23c398790b457d7ff701aa8b69ee8e9a72c70b61d029358170e902dcad82809574b75876a741f3f447b4ddcafa0b534a725d620ff5a5569ba8ab16ce0a7bd281a014f7dcd226e57d4a7447629ebfcda372d20a7430e980aa33667c3682486da02251e25d77a7b54e81c3194d7b08faaa72395dc407e98d5f93198c60127502c9ae3fe35e48d3094446430e33eb27f5695eecd475a2770c2ef3ff38d142101e1d9d0ed1b82bc3b846340fd316404657e54562d14f4de754f7739b26da12a740aa97594a30e230f7d911dff31dbeaa9b1572d8a930df1412265f9246d9d9c0ecc5d05967f3d2a41ab80bca045ab744615f42f881c9869f6b3717390606c3dace15a210a6dfcf24deb15f43f83d826eea8772cdab54ecaaf5ec8038043e0bb6f1aebd6d9866714402bc0a943401eddc1d46299ec4c6e79b20e9ba084f322ccda0262a2f7b92495439378b08b9d35588a49c72bc17d54e5e9cada412138f13478fe1430b9059a0736ae99044d8be7d2597740044619ffcf3f31dce82191d7764aa4f5483f7c933f524d9ef3011a2080db6942cb9d99e74fec71b05af88ae78b3e846e2f6ccc239f67708612f21649cf5a2356a737a46774961d448f36b523e8d796e0bd53f4b7ab45c24cee8f4135cc4d3ad6271fa535da3ea942a4e3a30b37cfc6f79db1757321b7d7f2586d0440306e54264f673e1d939e6a7f1c73e76921f05ef953ede7b75d634ccb9f32b21d9cbd1a572f4a8c0ccb317bca1a8a898befc883fdbce206f6c861d9c12ff5620467dc9d27259e610b58af7f88ac5b7dc4da7828a0f8df07d4832f5a4622acc748c2b649c08de494c274203f8c51eb3755819b1df016cbda35ec2ff773387090169a903a6667a4294f7163ce32dec1c9066c0fdc3ef56471cda6273f0cf01d3d76639abf57238f1393046a569132c2fa986ddc38af7647446def56b82ff20843afc81fc29721205684a2b7dbac7a918542cbaafe83bdcbc561e37bd57f6fbabb7302c80e359b57d09e23baef4be9251d1a7d2b8e4c4d1ca390752139bcc60142ec590d19272c0267fdacf0af6bf3510159316bc9eb114df357bbccda8a5cf252e4e612cbc29ced53f7eb7a6b6570c24f4a7c80dda074dc456166ae20c1ed0c52c40f856c60d3012a35b038e395a416d247c2a063a6056c53357bd63b7a0e1bf604df9299e72019c3b52b0d8e2ea340b675aae32c2d571f37a9f0b9f02012fd24ca13a1eb340469488086aa5811219f8a5f6e270d755bd27afc7442939cfce504e803710b242529011a9fcefa5f707a45e01ae51c1667d0ef17fd3a3e0e3a565f42700d3c927bfee90ac08467235a811cf0986a75764ae317496eb3bf282d9f4d71120f7f1722ff12be00f2d1cba54b0ea75f39ceb6eb8a0c80e6a260bca98e3862b5072b5722f6846199c4ed8ee79376dc6a7a7205ec9c7087de8d8b92f247260b6739a866c93ac67a82d6db6332838b48400de61091a7d6eb717ab95e5821dd2475c05e37265440ae4756b4a1588ffe854f096dc8b90eb21e1254922974b2350b1f179560acdba4f53d106059a622a9a9cdddde4e3af7bdbba5e611dd2f0516954fba58572c8468ef22e4411bd5f0d995420b85a294ffbe675ff052d39a148bd9fadc5897222f7bfd19c6de544a99d843adb53d6168f34debd189e48a8d90e059ed4f33a151dbdc673d9104f0ff42c79f038ed42b24360b4d1f71379582e0f767f7aa1db5a31f0b312e7c367638f49957a519463063904b45ceeeded89b0c16d0d6a4b3e7223b28a457bb02b70080ec5755465b388b51c782f884e3cde9a26a7e9f41924033f0b3e99185ea771804b583b0a0b32e2b2464473b1f28912ae48d36088ded47214c73cb1b2ed85ce914c20aad44a84d7e0c63e0944865eab5583df3dabd3ec72f6ab7f7e7cea919eccd190a767e6e1796a020e4e5eec28dad9cf2ee38ff79f728194f773c57326500d70fa20ee310df81157b7dafc8f154c79c1285240f41f7259cc419f63e2f35d597c95863a2d8e7f7f14ee6377507b3921d2e7444f4c435825f739adf711b2c7d0416b11ed48b4d58eedb27a50a9dbba8220a2005076b572b357c98f7ed9b327614759434190f44611e21adffb41a956535586f9a00e5213aae8ed5336ab3b00beff1d02de66f8608bff3447f245ac72e50ee7c13eb8701b4c0589802f829a67a0683e729a9cf8c610b9f5172d75a8413670de16c0901272c74069df9004f5984faee974b7d0c56a2dbfad9b40ff0a48702e4da70caa52727de22cf8c99318c36ca22923bdbc47d4c406f8084ef3c630b3f790793f890372da595db009284267fba6eaf443d7fa923693ce396a5d6187cdaace5549ea82446228a4bd9d647b172eaad5cdf652940d0866a697c57b2221f84c5bed93cc453616e91a97a13f841a11e837cc892525e3d0bbbba655c3e82f631d0111973e7e72f376cabde8fb050db4e7e5ff21af425f04a302454d2b9a317806483b90a7cf72590f2510453d57039eb7b8df08cb6015f9b66835d10c426b5426863b5372a2726a5ca7173a444ce71d323abd5b1208bb851f999467e461c2a3980ed700299d7214a9b2b5c1a4eae59337597f3ece210aedba1c4b3d57cd6311bfbef6552dce726495044a8294b4a567f3acc708bf60c8de1b6c53352eb3250b54d6d0663d4a7231104ec5ff8ed362355928c0c739fecf2071c00fab4ff12e9fa7f207b4b13b0f0f058614ab316abfeebb3ba5c02e4e848b7a84aeb5c12a85aa3a7d6919819859c530933e636f25b73cc3fd6f47387268200024a9ae50a353ed11d816de33cc298b19249650b8953bc342002d27a88d29d985f316d8f6474097f3861811a69f72ad0b1fb17d1ad27efd9812b9085a09a197c42e29d4b0ac8e8a901fb3012d0d728fa9f5cc9e66e1f71e2d3cd0a038ad9443bf6b944c45a63a4b27af5e1110002b1a841175aaab9ddc7162dd87b7e96cb8cbe8abefab598ab15bd51db841d9fc660b6d58f2a4e9c5506b865a1a6f9b453019496c2c113708b5bca6889ca12e1c721a13a3597c9e93eb433b02363f85e4236f496f5f6492bc4e2a109677387e9d2454296b5629345f8ca689f4368042b9e20b26904bd9a5ce34e678c53aae65bb40d11cf6d9b58d0cf5db0421897668f0105c5ef35c4ded5a89f25667e606481472a8e165ef63f077cf46f7ad1d2eab89815d6a4ffd806cf4ffbc3550ad28b39b4c419e084b56a63f98e927b146648f225f9a4f8d14c9fe6790a1cf4d5bcb96aa720ff046dfd7940f760ba36a6997c22bd1fdd2f7543e817c82d27c0222d9300c1d7c32a7e9f1341cd9e6de643a38329d310b5e1ef2dd67b8ba0713eb2567595330831fb5f15dfcdb4c7de006426973a47bb361a8635453915fb0be3fc113d8d67253bd446c68d39717057ad6d22dc7002b7be146b470ed198d9dd490370efe9272dbc48d7f16f6fc25c349d8ef870039691a8a50e6c135605e3238fd788b7fcf65f6aa4f6a0f2f762909a7870c323fe5484313d2093a0073e2cbfb222d8a0aae0dd55b16209e8f1f4aecc88c3f002f6144214287b20498d9add6bff5ec4fb0fc1ca30484ead83438232b04eafed2d5cdfea9461478f95c14a4b593c3257829d049f1c397805b90de0533e9aa78c679b82d73896fb48709daafc7fe4166f591c47229da563a4f32f540ac0a0a773a8e1ead54b26f329226f06684639ae890cc8b44a7bc4d1c05f933977f3b72c07d36fc5fbee4c78d110bcb8c3429005c5f82127228dd73393680d5b423aca459885babdab5b1092d3c55fee0d3caa550059772333f8fc43ec130dcfac95defbdbadda27ec493073ce33200ff2108ac84c923ef72f42b33f7a79be31785fff7fa6d6ecb6e0c8609f1a01979a7cdd5f964584471720cc2702a0934ec46d2521ec84d2e729a8ca9ba5f2d26cfb669e710686b3e511aed292ad882012b7866e9c646b8bf75680e8654b1463ae3f1927d514aaabf1372cb39ff9258b7ad729aa6710a2fceef23a8409821649459b0bec0068729523b57c3edf9dedf2f448531b517a7980927151a17154257e1bb625d89932c60d33d1c219dc944f39fdbf940d9e05b4c7513a7d7930130d3bdb7cc6040160d307bf75542b98f6a2c2e353c8112540c2b1236b41a47abe2d2aa5aa0640b66bb110049064bec97b810ce94b59dc91a2185e90da94857dca49d6167ce8b580e78b7086472f077e6544728b6f5b45c3d39a09f8239b7ca629363f42b509e99410a8aaf39729f7196e05921444adc565b557f6dc2fe7b249dc0e7322a06506327d5cb3abc72f8dc4a53b05d302fdafe5a86f30bce958acd30009830f4b5b23b8c9b352ede4a30781eb7545cac589ecf5121f9c198b9c605e489073067f5f4f515e6b53908723bd2cba26c9eb0f0392a1e766cac99b1602e773ffef758e46d5f52849b678d50af6ed23c80c5e08339d6a7cf974739f2dc06763adf401bffeaee5186a5a56c720158edb20dca7185d304a6ab0d4ac48d9c365d8ac634d084ead4a36cc5ed2b729e9fdaad9615c1bf45d4f83a3c451d0ab8c6b356fa358477086d4e0f748bd072bf5038bf3d7220364e8f4022a00e6efc8a92dd1f00302fc60c00416f1a142c72fc1417f321e0b0ab0b5b131f07fcfdcbb085c4d0c9dbb4b893bb229abe7f1f49e4599d3169cc346e1903d4e6ae6e7c316a2fcc8306112be9faacfeab13b2f772a8f508962ac71186d9e18140c3a5910b456fc282f5c3b16fd712c54bbceef81eb222a3fe00dc61d3b669fdf27abcee832f9efaca6c9f56f3422d878247fa7134d29e6ad7199c54ec2ea337f09cbe2fa62860d6f3d46cff46aacacd8ae5b93c0e4fc63a369535fa48131fe23e3738c9ae39ae97448bd69b05fbe33aa558aea472646e5b2ed06e9edfe00e76b502659531b3a5228cf693abd5fc47c925c8884711132e670cb0df5df5968ac3a51730751ce575d5f30d3145578d19fc09d7a7c27238d19cb45c16f47aac7bba7773183491272f40cf25dd2f29279181732eb591209525efc79b0d28ca6fa1f7d1965c65433108e274e8a69d3ae3806b36d12806729ace002fcc2eb5a226b9c693dbec90e768fcb39140d805820a0ec05008f28a726a4d96d5e4ca25169334f9d93c5fad4948e6a224ea316e4f31f174a16c2a397213d2af12fa32e4fe6a68b1554a2deddc9246e8bc0d5d88714251c59901f5bd72ab412bfbd9e1144d680bc31c2fbef6ad61ff810fab334044203601537caba16f45a1872b3bb50f3f19b2e765549e507f22c1a010a6dc773a23bb9303035fda720c44774039f93d034bb6c39f771c92766ea107a65770b3ab14044f284bb8814511b2cd1b9bf7ed451cd35fba35dcf8703db0a2bfdbb48b4746a97e6b250b97722f34af2dc556c4ec5f9429d74b55264d87fa5f3723c71bdbb2798563f786e108c23f04a4cc7735272f50494dde94d014d398a4731129f4cce8e14a96e626687268760714e6a7b400a65c121e96b950f6867b45344c5ccef0ae50b23f5d93db318eb80bcf0394c16feba797955f5d22cc11f94890fe6e27049c52ab16ab26dd727635ac40b26ed5c0fc0c9ed9ac1bb733d3b81880a1b04d38656be54a23ed2b3864195a87cd7eb835981050069c46a2164e47cdcf5aaf5b0d981bbe8699738a725c07a181d472ef09f562e74f88f44e22d8fbaa52941b978779f1b5fc986c432154dc10812c92b91a9ef36d33772060ccc90060345b199adf3c0d77c1aa6b1c1d7dd4b0e8ceffa4686ea07fbf865331aa555fc7c90869d35c38487a22101dde7233e3ed3f36c40ac8d723850728784eb963b41a413d325262387058888e67fb5f7f05b8f0a7aa34fee6983c371aee1512058cbf43f7250a8dd372ba312b444e72c004e243a638bd705941a6f25ea9c27a3dd125847e26dd236273a5b4c1392d72ac4c1744daf05c68c56a2b87df58010549fcb3363d1ba9e298d66d0297da0804912be24dee41426cf306754cddab65cb49594e763becc2f3c0e1194cd01c3a72aed20961ba21df79760a49ea994520c5b4f2eb3cc17eb5c9de4ce83f79d2c117df66344542f1f85b8a1459115c4781342ccb4a546488fe304655f736b351b67285e344ebdf06a26b3aaa1ae9bba22dead1a3440f414c6e7e6cb77a8344ee4d72601049ea04018108295928007f37884c16916f591447bf50527c7f56eb10b372fd3a7cd2010f5df82c07f799c9004a5b5d8f5a13f53add881105dde04036627204d524b66594b663c8272cf9343dd5dcae3d7aefd1fc839eca2698aeb9bf5b59323d7bd725cdffa68bd1027b66885784a42e569a086d5d7f2dc24e9938a9ec4e701a5b332b51f1f970693832c27bb0497bf85e9c35ad24975d6665ff89604e72d23d5a0c894ffd205ba449eac3aa0b3e6f9cd9fdaa5981ff4a78eca4922c6a72f2af2c9ba15e088abfd321cd40bd369b9055f30e9d996b927e63ff2e8a8e5f72f2157f6a916d1746328210c98cc8645acbe0c61ba4d87aff836d3132a97ff051ab08996868776412400920f5821251d2b16a6906eb027499d8e2d87fa4f91753798a383ab8a5b629d7421d3fb02d7f24b6df937e1d26a161d3535b2581b9be72e6f003519134a0c8633b51e21ea83d7840986deddd27db0dde69c85c5741bb6d874988fe731e1f14e997472219f0e8b83c3ad30f6552c5038bb9a6adfe4087621515758cb199752334d65ca00be7414c132289856d1fc371fa8838b98a80d1720d9e27cbfcdd8b18579a9800f5d5b86e7d7625ce874d8ec56eaec58a6cfeac1d89b19b20d02d6c6bf0d93d278d39691cd63d70ece57592350de6883afe670f72d118de70a3a87780a5e440dfadf0efe695c4055289b32d1fed2e891957b410720350ec14d62a952bffc4a294496cb82d36e8effb910ba2f5d94017260fdf6c7261252529f8b52f2a6ab27911fb597979f118cc637d302d7a78ca21fa4f84416095ae2ae634dd881be52935636d36428beb9c55c3e1a25c9747a77d6d5a392072535f3f2ef63d62692522287e12e94db7c64969f645e467ef92b9ffc6e20a5d72b6fa5216be32b219bc3122fc50f2b415a648a6a0296ef9aadb59f11c4b756e29a36b010ec7c5d22f9c351b4b916bffbde8d2acc2e9f3387a852e44bc3fbe2d72353838bf8c123befa27287373a7c423c0046f3a71d400c4acbeac36840596e1df2b1315299c1246768875baa45ed684ce9a71414b0647a354fd342cbd2f18472f0eb48ad48c9d368cc6f72a4e4155f451615b8c1d821c3bb77787370b82a2072e8be312089aae1eef25517c91e2d6f115c1bc9b36427a37cb416bcd2e7d00a286ab9eb851384c003048c699371de22dd2d6fa83ba3497e356d65b522410bea727bfe28fc671f44bba4d442379b8d04055536a717dddc8a86edb990f9c1a8df729a093a5e62bf2a55d4cb06db800ba71be4cee6e43f612b50a8e446ee78ca3a6b24c83b501a629ede5acfb565e6a6238cd002e2483f673d245bf03bf67d528b3bcf69da018bbf6668a1b7aa3becf92701e55a739eb2f1fda2718e7d9f80934372d892b33e354857841c4f4e6b4942b7925c8c739363a591e456fff2de6ea1547299ef3100ad1853ddc84e1b2f729b2c81f0e80c588dfdb7aa5fb0c71e34663372364de41f6af66a8aa335bb21a126eb681574d04d7d962923df214ac5baf67472ffae69db9d1f484228c1493c84b33ad108823f3763d23b540b30a8403a5241727b977b24a7c5d82da33373ed64385d9897a4296c4ce150212573bded3e3613725b81abb1b67c8cd14592ede61002d14a43da25854532baba36639c7a9ac9970e8e210937c770282bf8cd9de1063c4f867dcf787f75904f3f4833459ffebe597270cbd194c92b68f6e0ef03b84b603552c1c0c6be9ea4fb7defacc1b0aa58ee2fbfcc323f2bc41ec1c8e1afd1a5ff0df6f9bca845e88f7fe06ff0e9f4fe6dae07bd225b4ab23ba0fe424ef8f2f4c2802d82f2804b341e201058d92b1daefa550a2475bff335f358c52ff43c6c5a08ef75bd38ff6fa9e1e93a17b374cbc0bba27296fe51765867df3798274f377155efdc920ee85b898faf83447d57bf03fa3272bc5b5c80bde3840d6d1cf7fb79e7de65dc91b8a0d37cfb4ba90f2e620c3a1d72c6dde56b9b24ff989d2398c64108cc611a88713d09b3807ddc2273992c9536729386e65b481b4fa80ed5b1c83a3aef5cb0992f080f5f9c4f0f7787b267766b05d8d71fbffb611bc7f13a23547dd02ad04d7056ec248bc26b2125ce7a9a224672e6ab17c782852ff6f8095b57587a2bb63ac70f32b52705f25c0d8eb80d322c728b6a85526acae3efa2291e798da3b316bb5381c06898bf728be2311bf43604150995d78b5e1da9d0871896c904dc1691381ed2a8579c807c050c7e52f1d4ca269a0f6f164c18c7dc613b785b656862fa361560bf89d594a0518f8b0338ee9b725a26adaba125022c2cc0e9a46e1cb88d606a42dce83d1f9cf3476dca395edc727c3d653801e5604f273d0d8499ab7381bc8e5af480b346d48653aa6675fbfa11f6854c6198d773a1ba30fb5d6c01dad36b232a9ffd8e44a1700c8a3cb632a845e74d517deedf195edae4b379bcaa2adb18287ef5838506c019d5bbe7ee78de4992cbad2d92e4ce06fe2b55831c640fa6664fcb3190d4a1569b0923ce46256c720eea656bb356165a10154db564fe4f972ebc7226f35fc10b2a3d0a41736a8f0c818579342bdaf65d668ce2a0e94df4668fbef6cbfcc3760a8ba735fdac2a187204ed4d09e0d32ae84740d9181e2c0ca687aada11339834fc1f071bec8e3d5a2b08acc6330b452ac8fff5705e1645dcc5b112e17aff9bdc645d1677e5d9ca9031b26d2e0614b410d011aa042cfffeec5b67ae2ddabf2862cc76a444a080ffdf726ed003d2b27cedc3fc46e1640e1d23477ccf67e00090277962827a59f153c62cbb96f3ac46bdd0a1fb4e7056709e80f08b064e9a12e50cd7ee03322eb23b9d72d20ed2f502d74f71b84a62b4df8023a068e2d406eb5bec8acaad871c4301e9655fb415fbb430837ff0eff583862a0ad94c9b4ee00d4182435de51c262d40a56a49d076200081094fb83e7b64535cdf5386b70e9d688e8ccd360b402e8f427a72de9fb18d8891186c511bbbd67a072463235107cc56d41697373d2f94f24ac200f1ad2d7b84bb4a3ac5e8b96d1056a01378bceed1e9e7c214a95b6a53d8e7572093e9189cd116700889494ac54e702e62bb3453167f41ae1307240042d4ecfb72a60ecbe4d11c2d317be19f510c3644c02c5fc86268e077397ce009cd0089596fddf9fca483de0102842bd9c250766596107d549117903555af51fc4ead2ec92f1ea1c0ab45d6ec8124cfae6e33d318b8684cefc1d47d8c768f9b1913627a8b55842b1987f00f15b90b0e8ce0fbcd2bac0fc55b65e8c6583ec335c36d03349c2cb19186c0bb2bedbd7fe1f8218a6e829da744a6f7884fcdd58c9eb3b12847ed72c91310ed04dce5efa6c9d4d100964802093b4dbb76737035574598a9c690bb48a8ee5fd60b60d8a98574662d1ed42fd8631dc8feb1d613789517fb5e6e2d977243a08165b3e517661b288a32bc56ad029622c28b0a2ce705f5c0e0122f1c0f0e4f0eabe92e5a310aaf6c89e7e240fb4e8815daeb5f152dd2af5770eabc149c72388120123a95418da19fea572011302946f459bb0a80fbb258adba8338a21744c0c7bb4527f6cffb8f45ac3ef02ed9656f8499728a601a69b40f4067673a2565c3db40e2666ce3d921e7ae1d9e193101d6433b3f7c0811f45507f5f170c18672eb4555ec9b08962a6cb3d6c26c6cee06917eb11abb04011f99d13e3d3d186a72cb2028f13544e252ef697c588c79262d6ea520103543ccdbb7d2b3478f7c4605ca6575874a0328983d4929d7288f6546451315caef57ac30f4b2e80e5485ef72b5d9c7fbe14beb7116e93b2a702de43370c7e21604af7957317e5da17057e0356f28d1e2ff335c820f3c54bfe922fdcf3105ecbd5b133aa4b4f9da4ec32159725d2f8cb1777eb0899bbe227ac63d18d1a2167772802a2cbfd1281c8a97dda172e559da30defe5ef7eeab1ab224696ee380f542828ca7e933617672cf7244e772fc86349903fbe4fe26069d880033201eedb49e2297af2a975be1aacaa84fa00c88ab415834abf393d29a6b8c6a631ca54231d650415b465c5088302f9d3db513bc93c3794adad59e420ac58a95d1a2b799f4b52dbe4b501d79247030b933c77265dfa801c8dd337c0b1b57e9c4d8c3772362e92f916d8794b43f453d8d3fc219b84aced187aaa1f6f146b0115c4368a9e72edb2cea96eccd15f47c4074221572966c572c93c1cfc59a83abe26867b6e148fe8005e72819875277e0a6aba1e646539e66c8d0c4268f3a42ab6697d84dd4c98269012e45ab6bd179aac3da483d72322bdaf495b73deaa3bfb29f7c115f22f35e20f274e01e41f8d4f80636801a451270289311c68ff40816f6371f9f7492bcf0c9d96eba01662842fc72629ddc3e9383d30be9541f790af3bcec4d98db83bd6d9eea1588d055e618cf19f43a1b72acb0df5c0ff56049f7b01916e40fd03c7c36a4b5e3848c43ffa177d5e65f075a437f6591ba75615ded2655bd6f615bfb8285bbb676f7abcfb1e3fb32272fb072e34bd647fd9c85d21f14d84a9811571569f0b0042994b0384c415b83de18db729eb285529e9b80c881c687940d30b4e13a5dbf688057bf50dfb6858b99ae7e72fa41c596ae56be5a47e54f1d3536c17d329b16f53b3fa538fe0ac8eb8b574f72f36d9b90c4b86d199d120d1e4ea624711d30946a671cc266c081468609f1f70e8dda5fec383a4341c2c78a34a72197d321a5e271bbc68d8fe354217a7560e57275fc7c4421fd0076e8b7b5646e2b1f95d549f9004731bbc9589ab8a39ce8f172cd821f98c460580013334d668cc208ff820abe647297bcbb73b4ffaf11a7364345f6fdbd17f56ab4fc8936e3e35eb11b91e28053dceccf76e2aea954b8d5b7723167641599c71d78d4b4722a845c4ff6dedef531f55be8500eec2b46d34d0b3652262bd15930b3d341fd176a3f5425e4264256d8af0b3fc3b5ffc77271d3517278050a3b9b2c5d0b83c0389eeaf5828b1145b9912f295f35556684152bb15e72c2892be4c4af6002ea47ee3d9f4408dae5825a2ca5a963d01dbf3a2b232a75273f03d3bc979b71e6e7257246f9fcc726bc0be846f61bee86f88db17d5b834f1ab928ed34fea3df2b09bcf6a2d6b3e91919f25c866a753ef34edc16104ce208721fcd9263938618b05395cf79296bebbbe8372150fc75bd488db3ae5dd2c0a6729d6b369ee2eb1f3fc276e74c89f326c2ab8c8fd32351f0f7db68d6b67750427128f57fdc2dbc5abc78e2f45ac3fe7eebd6a6490914bc445399752450e9675e72f5bcfa4f43a72d3aa1670a8543443547f19dc53d8a92b440c6128c51e49f547297dfa87e37ec1cdc33e184e8983ff58640afb6e77a3969159b372c86119ff9529dc369a086e5ca45b0c578aa9a96114b4169692fae3df16258d1180e34e2d972338a16b8d39613c70f274d9490d62d34712169feb333251f9f6aeb0d91d77c72e058a8f0b4892bc832b52aeb8f5d5e71b52ef5bae3c76f9de4d3cd4043a31b7216c96c514811de1f359aba00750ff1f7d0623075111a8ab9b07b627eb2d702727cb5e778836c368119d342dbe4e9a3811cb909129be27b41b4d061275cb6fd72dda8285d3a620966cac3da8fc82273fea82a3cde636473838ba6201a1fe87d72ccb2010ac503724d1704cfdf392ff67ea0b04ea304d43ed12be7e509d11fba5baa90d9303c4f9737f995e368f49f59daa37402298000a2555bc1ccd7857efe0b569f475545c1c4ea905931de751ab3a891495b80857648018ad9900a8214777290eb68ef5e44db9eae138b6521c653300e404dcfcb7813e4d1f796c8268e017274f30a9ddbd9b319d05f5c0bc3dd09d134f09818076da4f16017ff28fac7835c2eab3a6f9c64bc6cece59a01c60d87807c90d31e3c658296cfff9421d27ead18fc89291cd3477d66c4000c23a9e5d36de0ab29df18a3d7b9c66fe8e13cd70825ce1227cfa77617d3b5a6d10caf48e93d128af6fa35e4301dbf6e18e544b0412010eeb580fae8c44f11805bba2acfde67b7d7d44f44692826545eed4353ab21725450201f222a06aa244e68dfe4d9adc332f9c8fb95d90e6a1d02aec792dfd8729b8af830d0fa511b9a34b231444e16935194b29b2116e5c3919ed4ae66f90b72d7956e0ec0d6d8521ea84c8aaef0712846039c391c3de429aea2b88fff34d572e39c09dc4541631a1235a552c237ee3544cab4631e99159b24286533f0fe3f71d55aa1e67871ab4ad30ce902bf371b3f3711099099eb2fdca730e828743d2e3a4ba2a4e5aff05f173d145412673a683e882197d36cbaeec7d42048121851aa190fb8e11de8399d7d5c5ba6c25c58b2b935376aa59ae790bbda2d769cebe6b372af1c254ec59367b5e21ae983a143cd89425885397375761371ae54ff936fa36a62d04f0bf8db7db10d04687e2300159be96cdca71c5574fed192e326d0eb2a7205ae9912bf995083edd8c486c04d5cb86314ddb4aa15db84ebe6b8ef33469752c58fada47df47332e598e21c22037cd7344c924981a0ef3b299223af6393c372b24513161c8b24357324320a15ab43ad64af79a0912d0eb247fb757fd28afc726f3a08ef8bc2d77cd5cf5459424b114e8445cd377df15054369ae215330ef872bc89bec9ab678827b4e3cfdd31a9c8a9fb0522752ccc7874e13b50eb3677ef59a24a3af399a0c24bfb176fc68bb2739c4fef64ed0beb76ecdaafd5f8dcbe8372a575b99eae8cbf6e5f212740f2b93059bf6f153b680c6a47ae1de2ce4291d6720ab1c2fad5cf9f06ca23bee30beee2aee40a5877bf8fd83d11db4bd38253fd72085715d6d9834fd1fcc8da5ccf329a86db1c22f77a074e44a937484394e74c724f70d02406134cc68047f92a471122660f83204d95bc0d01602752238849162e7fc0055040c2e847d1a16bc392e11478ff5e093dcd6fba44ea161e102602e172d552dfef1f972be19d5842ab45e09c30b50304a3e00c73825f29208754cb2c5ca3dca1e31e071a374795aa0a49ad2eda8c179d2597184f3ee4ed1a30c0b81f467ca458db2419a0947f2d75dfdd532eed4ba4d00a91ff74f215fbceeadd9edf19f4e1df61520fc5d56d96edb017095e228906fe3d9cf5f11098de9f00fcb7283e74dd833746f69b17e3e4d3e30044873dba7252579870aed271077aff49c6fa7253539324e4b906ed334b667da6849d66e3d86d8dff6d3782223508ff5a920a4cb6032452fae97f800b09e8140e7f61519854121061e813a99afc523f84613363e4c52b83ba80d262ecd8557c2465291d6de8177570f50f3ef748b9adb308077240f5bde0c20a835b6207c3c00e7a2db00cb5bd1a844da8b8ef358f781217497210af992601bb13241667b4c20f4731ef872d54d1e315782c7df69988e019e93608ebe1c36606a8eb8c55bb8260d723b0e6fc47dd543a832442f41e5fce908029f6b998a823f752bc65eed513aaec51b59f0890ce01510ceba80eb95716168767563e6f784b46ba2a33f4e93f7f726f1ebf447f7ac1d16411d8d5454fe2edf546a59a2d02a51c84466a06657b5e6834e19d7f32da05ab83dd3a3899bfbd143a62be4fc4b776e5266ec948f3036f67a274fffd372a143d9a4fc21e5b0c1cc2976ecb792bf1dcc77af0c44f38be5acf7425131c327cb7ec2e6b771471aa52c28f2dbfa0a6b1e7d26af2e9b3e602a937beb333a6b47587a287cc923524647af70e0e29dd50c4b26e3908d5879a9b3f4d6976d4128017c20529b39a4578f7caba5972a6701920d8ac0576fc13c0330bf7dc5984d2e594fb2f89bf688268b56f7b2523b421de560a462dc4c0d3794d543e1518b1a6b001bce8a57a6902f1ab49857b72d8cae23e6430a05a4f0b74b4227f6feb502dfe3d138c678c7f8da46fbc0f3a042d419bbd338430e3324f6951fd459609ce421ac800dc2576f7a88fffb1decf720fc4fb9703670f8d5a558b233662b33136fb26337a9e47661ba415f4caef73726b0a8f6d89a337623f3180cb3e2fca3508943411a5f6ef998ac639c992c0c66b55f4e5e389d69f1815b470795c10f11bc29dcdab5f0f35b647625f5e22f88f1afa961a043e73f7b909edbca4f4bedd1d42735c6a3943a55fa8f45e0644d71572ea4f1a5e7006edb417d4ba2f9390bb60ff96a965cf6b5a870e85a605044f2c72d7add74cd401e9e0a1ac702dbd46f103a3bd5633794a4999e9ba6d8550f6ab6e86deba4777e9012e6dde6835c65818a44721d201137e1653ab34743b081e460dab6b3f0bd33e7e97bfd14e277f69fef75f6f56283ada718a25cae3dcb7bb6d6975954d59d105d27e6c0c78d03c3c2e803cbf9623c7308bde79650bb2082206722ce725c3b081fa2fd474d52da0d4978fa18b126cbbe1056b0602d7304b6fbd65a24e8995ca5a4df435706752e74b63cd406c2d9a6d3272d2e29d74bad7b1b172483965fc2ba52c7e6bdc0e112228b21d32e8a28a4db2a876a3fd1e60baa53f7254117435d00c999dcbcb245efe727b225db404c4f09c595c59705b947e3df312e72c8522e7f0062c71b9c97f98cc4a36db3b06def770398d8ddf8630f7e51f206d5e758a665e929bf47d44d603c972b968eaa7a2f39f2d6e75530ce4af173572112dea5e078175b4bee8961e73d2c7b3e226c09c22b1218dbca8dcecc0be7b723bf0f03116963fcc9291bbb71704d52875dd68925eb24f73f8cc257f1fe4710f91f89eefd94560d652537024ca128c20196a59ae9b1c807fcea9273116bd8e72ecec8d82198c62e4da2a1fe77f9cc3808d3e212ee645b927a1aba3d630709f0742ee75fc9f1c2e8bb307effca8ed2be588ba0fac45ce9919b3a11f9bacef9e073c16cd2cfc0266c9f98055224ace4cff81b9299d290010f249e7bcf2087e3a724620ad7933e83849bbcb2b97db5cbebc682c7d91a14bfcc61588a0da19bce25831029bc2c6040fdb7d1670ff813ef765a05ecb41e4048b975aceed13911610724205f1017ca76b3e33e85be954b76ec4ea0a8d944ab867a6d069a708a8618d6b5e83af9d3b53db7730f52241f6f95563b98e1066c9aa3b4f3906f5be6e1fef3c87de10f3af4772f163832d7c8ca756351756e1bbc5715ae0837e2521529261725d0679e7949f8cd59a2bf680e519e49fba1af7e375cf7aee7ce42a3429460b08a3cb7851337267a7df410d712839e63a65f7d258c3d5aaad24d1e729e1aa6e72280cc4fc4d2521b557b93acacbe71e2f54e1b36aad3b8f4cec333b0795cd057216a2374f187e895a8b5a7457a7baed1d9c9ce3b20281ac7f822a15d917b0d37263ebc90ed996280c4f62b6e14bec9367081ef4d49ad116304fefc6cf51cdd07227b8be970a11e57712da8b07e0285f778d28b0f5cc4ff2e64c0419e53206de727bc1eb09a6491f9a97426f0531622b49b9aa512bdfa04eb35eb77ddab83f7611fff9485fda37b1e96c5af4ade1d9637e1dee9601841dbfadedbe6d083a8fa6725a023b02ba1295dcd7ed092250826175c65b924bd82ea83411b8fb8b4674a672d43d24572f0bf1530ee0a0f8bf66ee36fb7e9ebae8ee7bfdc395d2bd4fe88c00041dd75fc1f4eb1bc3ce9b291130bbf23e9edd939656df52b8a47e03ab9d08727db6402a44141ad7e443ef8f4eac896d42ffd9e448ca65da222e5f5ad83ead72024583f0a72dc0394355f568b61dea2f4454256362b8c39b9e1e04e822b02f72292108a2fa7500fa49b5d478586dc8a51524eb2375558703a16c921a74484014e0842b8e3398326ebe36e7519052484c929df9dc76cb66dc7dce3f98c1ddc61d6892c7bde49042a5e9b91b6ac013981b5c3719b79739d0bf029b2eb975d74b726072078cd719daa04eab410789c14e7ddfa3f8eca6b8b3e1f538aa1b04aa9504a3a77d29a19d13680fb80cd8c4ad101616baf2bcb93d2dd41e2dd449e302ab127df3bbd3f3a77263834a3ef209e7daad4350b4042c0ad188d7d690229c94ce296108dcc72c4a319c1aef440beb9411796e52e295734cb417f259cb08de45f67219e68a19789aec4cd3a5fa25522401eedc38becf3ab6615bacee36a5e3e5057207cbcc6f3d9a10ddfa043d79a4a26e4dc0e82505dd8b2cf0c8d00f869cc74c725ff409c4e8b44561377610e46e045807eec13777cf2eeeb45dd8a4f16fae4472fec302be2d90fa2ffbab33d95e1af18c0e27166d7dd9964438a6b86c003b777269cd71f54fbf010679602c6f38373e439ace1c93a0cbfd612feb2b9ab4d4997267db0296b2b6b279540a80edfb563d49cfb3560de2bdbcda53b2df4aac681a3b48e8ba000e0727b37ec05ff7399e077f3fbf694dcaf6f0979a4abe925cc2de72c2a796cb100bc3785e418f1199b491144aac8a7f77371a57dcf596581c9fac721754e25530776653689b0cee71d5517ab6824a798f4e5731ded6c58c4e3ee072b3d3f19200ff4744d523dd5412d3eab90c50674c3028e27c7ab0da2d7c86f44439d3da9a670e3829fa642c8c2f39986937fbf7ab4e3bb934e3a85e0a361bad722a3c8aa5ef959dac681984b5fbf4c6320ed7f0d3a4bb1f1130158613808090309c157e1e35cc0773e5e997afb4710c94ce987ab54a09d118fd55e2c5878752722a5c9e408b2feabfa6619d57aa21f6443d8fd057e47b006f6773c6145b95e249aa41126b8ca729f479fa3701628a099407c584a814deabdaee1cfc7a64cbb805b0cca944808a6aff9155a10ce70fce3f9ede4277fde7d1434f770136ea3bb0319944e2b2760ffdea3fc8639b92bdcc60c71517a106677d4fe11f6ff51a031e721a827c639a6a4afeb86aa009216706948c1a236085216f9a96bd88b50e44e372d027ff6ea18bde7b60d95797d2c204e9fb42b138b21e8717f836a1fed13bb67231bed6b8875effe4ac2e8590dafdb9919b0214bf9ac77bc6553bb1c8522c4c54a1acd21f8dd3510c2571925718f55f64ea7e1961956187bfe14560aaec8d27284971a1816cadbc8f3d4ca20dac364ce8d8f6f42426cec5b7e810dbe3bc1fa6720a2e4b2e22b052b21bc572dd1c702d54c24b0eeb702b6940f7fcab1b8c453a63454d3f487daa58110e511ea9d0468882fcb8587dc41acee477d2d4364431c572d1cddfe2283925dc8c7fc2d7d07ac72a0f2d020e5fafc71f2af5e68b8df9bb561073179763148d40eaa17c5fe072dc95d1b8af7e32a14d2e218fb1b855b777617bebc5a33e4e6b680a02f40a42df6b5e210a263573c62f1b1ce1415faa2d161cd818d2ab3f9c868ba52a49d8fd3a6fdfeac2d161d0017524b47569a6f7155c728d52fa01eb26530094d6d1735c8869cf2dede9369138cbb35595a3422d14fa72db41f9c01b17bf3ff843f9c33ad7b6ce1c7f674bb963527bdad05c2393670d410a60634ebd54c6b68ff2fa44e73d3db7d0be1aa59eb4e6c0608bd72d7f82281f7fa50f944d6ea8ed8fbc041d98caf6e1aab7a0ef642aa5f994a538dda6575b723054cd69b2343c824657771131503b6e7ea0e709f6a33d325e01a279111c6f72bd7b14ed10eafb42ec0f13cdb68c06bc5c2b655a6337f404e9ff179d6d3ef405bdd4b9cd29e9b7059081dd8b547209a326064c16d820ccfa2c8dff3a3405fb72994fdaab8de9e6e7e14660136ff11dcfdcd619cca2a5274ecc4413589b65c32ccb25ded94327de3e542968c31f79d12bd909f1cd76957f3dd39ebfadda76ec098a600b386b3ab1a2b629bdb0bba9fab630bdb0243c2f845dc67feb03e4cebd5cb031fd007d21b5eef21e8cc490bd01d2b7dc163ea2d7bb9100ea88b2e0126312b893a6fced8295592f58d956cfb47e00d464f962f13c244c70d2a80a0edf644a27854d2ff9e70f1b76b84de4014da79187377ab247e39a01aea01c26faf89107ffd8d1eb42ab01f9b270a5380237c9e321564b13a5fa1e871190a32bea927a242b93907b8314506db2cf517718e11dccf68747d027fef7f14eaf5534d8154a7251a0e4b1eaf57771d5d010c64bc038d94cccae2173179691c084d31e33d64a0074f1868e7fa9cf46708bd62a68bc78dc49563ef535edd706d9106e7884021572ddd70f5a831e4266c20bcea0c4a6280c022fe46064610799a44b7521a3994e5d2f5b8e65eb828f33be40f6c075608d2f5df98eb9bb0dc160396209d9ccf8f37235aa458a84bb44e0b4c29b474f62b909ec42fbeb9205eb7d88d39ca09140db45d3912cb2c6d1d162484580b8c81093c333c426ef4e56c4e98807208004dd22722881c6fa48a6a533a7bda316b436b49226cdb37b0470ce3d11d7d2c8460d0772a7092192e387e0af5f2684d8d260aa62ffcf4d1108dc0cda50771936f48c0a538b66e7daff3c17b63d579e5304fad87ee3fffe6dd359372cf83b47523ff0e972cf64369c7f17eca5872a091f5e0108c7a9d399048a210c9401888d4a1b03cb72b2955fb71d7e1999a6e7ce10129b9d2901d292e62ae1509f47a13a1f54df4a72671f3d35288f5826ff9ca0aa86638cfcdda9b18edf5b42c367dba13dc7fadf1dbc80002851d14661867d96f30bd5455542fb23d09b99bf923ff70d15925c70260116287e2f1aee9a3c3ce0a83e14a59cd014305317fbb6395a595adbe424f348ab8f5f03ada7588a58f9a53fe4bcde3629d0dfa3c52ed474dc4f661ad9e64972c4510ce8ec81ff1525730aaf9f69644888bd5b0c5a0f502684ee2a7f2e41bc720927365668675ca33225970afe8638f5851d7cdc9cb9a75f37760f61b68a2872ad850cd546ad0de963ec49e8ddbc10092cb0549b42968ade62043c8ffea2e472063879a396a24ecdbdee168b305315f7ffef36805290e71590ac292488888972eaf3d026828f8288c94a8350f699c1208267cc3b0188964269a3b567e1a18c721f625992cc0658b173118cd3459ad76d1021ee2a865979b0e15bb847e4b26a610e63ca5fb55b9c239e1a68fb2b6f3ce2bc704ed1f10907f4f31b3ad637ce97728701261704db55666ac251af47689705a24decb11cd8454cacd0d65c3e159172e271676cfc6daf447393e29d15aa1845769b910806d8e5280a8549131828695cb9c46e869f0ef13b43bfba0dc78b488d35a87d930726f0cc4d2b046cea16955f768fec0dadf1b05a7098eacaad71ecc93db08d580ca7630431a3794bfa478972fc14aa407d66dbf9dac95384f9b23c2887fa0c3a4b9e023cb1cf82b19e23ad4283a854422494a2449d3a86df2c9547de45289d30f2eb9feb652b84aaf7108972239a8c62ed0fd782b49cb9128a24b6caf3e45d7d993aeeaf7dc2f134ffefb172c90dc2242fab6f75b7b5d76ff3136215f180ed7814eacb66663efa46f60f72677be93694d13ae5404534c3aac72f2aade67c9c512bc330044697fcea2c5cc1682dba29c14a2e7250e45503e61e4f08cb1838702de78cca28f5785d0e19a51d71dbe82ab21df0cd40e61d630f74dda51a75daea916625cb3c403870a927fa49720c34c7d42a538d7e9ed090ab8dca72b71ceec065029ac7a4c841404fed566666deccdc426f4d81536742273f3e14961c1034dbf8d1a39f7b65ba8cafc1ed6472505203ab01aa60a570a60176b78574a95683f81d7839d74a6b7576762ada4a39cdf8697309af3b84ba91ab702b9779e2ab53aa19c8295c3bde79641c093f0272043647ea86179d09bbabccc9438cd506701efd27eeb6c8b16f25e8c59140416adac7028c2e7ac79b17c996ff8cbf39b1b78c99bb94ea4055559c464e55981472e81b12b42cfe929ee6e4053b257587f30dc2a7fc9c29c42cd61d039d5b4e1772f08ba8531046f1e98c506b48c1292d3e455cb2be2c565368df3a5a3e24207c29d79bcb641fc2cec568df2e73b575cbda9127548c1d536f6a2d93db1cf8f8f37243be9b5486095de4b8f0e9dee85b2541ca53011dd7d176cb9b72be6b7848b372609b806e3d9e0db4615b0a5d3deff6f6cd582e1f8d4841d61144038ef20d15726679f0acd426ec1292b8952ee200610641a607d7cf4dffbafe093a0971e6b23b25526036838daaa94fa11d0b321739a69b324fbd7edfea9692d93d490c42647269739726f12f558da3b34e1983971df11ce5b85c9877c1a84bec92ec945a3c33d67f71dbe9631292ff603de9329382f256455fa131d88c6d093e63a7b058d418f8e30926ea6d2194792a33387649a6f03b6768500d404375e1c528c0bb89f923226642bc0d0d33f5e5095a33fc40292e12b5cd1bab834e275794871d0efb3e72ec77f6202c4d9e5507f3b8955616a5b54f9a321e7173de304870399c888e81725ad08366c3268c3d222bd1bc0da2674740bad0f8e0d9e4d6184b3fff6c94472fb4fce6ba52ad40594b13ab0af31a946353bc49512374a8a66ffc52c6acaa2c727c6d368ac21cc5b82e76786ded65c96dee18e0b2efed089f5b8940ed33c2b1720bda3c7e084cd1101aa711715f66b10f37db0f0ecbd5ed50b0bd9a3bf1dccd52e8095fc2531d7a83c8074832233817dcd857e3081316e39383661ea0d0e2f1238063c163efc9e3dd8cb03cffb78a53b0994bc9a49728fef1e4b0517ae9717f727d4b96be1a1d361f8341957ac6aa71c8b7902dee18adfe1d37aa23f3a3bb233457ca522607508182776b5e0a0e3ef410ecc165ce7ddfb7d1428f288170f28d72a170aa23532b9fb15fa07c7a5a7698b63cc3d8e36a51e9f5797f9265d4c3f672d40d34a7698670d20cc9e052ef99b33c91d5860f0f29e448a032da12559b767278aad619aa95b7b6f01fe3ec0e57892402bc35f1560175f091da7200f9b34272d8fabb5c8beef5756819b731ca8a46ecb94f48b19f0e4139bbb01eea89b4687206af1126b8c8431ce22b9990127f7e004f8990a0a36e68a19bc6e1c1c5dd0f35e67d24697a148be411a9f00038985ba661e8e9b7414165637467efc6e88e8872226a6245cb2d773b7485268574d36a3ca45746f9ec833b9480b7fdc4475f3d72f83537f5accdce80fc5a8892a6c6f870fff6eb53a472f102d8c933d126a32e724a4d2343f11705adb36c3744b8fbb53be6093dcf885297640ad7ee1b6c8dbe72632a49d0066bf7b8fa484b605ac251fb44a90514c2e6d44407a8b39f227be4722dc40918ebe2844af2d022d0a56c9980f940b82597761f4f914ce10305363f4ed65d30d6714ecc18399c2e1675a536d60dddcfd09884d98507b584cbf5e64f0ffb54d105bb9cbe03f0f57d455ad4012dfa63be03f1dd7b79c78931be9b125e7211cccbb1e132fad86225b4f38e60d9251798b214c67cf900364c08cb3a00bf724db3350dbafc817e6243929059ce315390326a840ce7d412937623460c7fce0532f3ea5d4903ae801a97c6f35b60e037724c0fcc92bed2c3dd08f33e756685721decd02758e2a1dac8069b2ec5b5a200f988e687925ef5de8032a568fb6a7472a4a98166538643b0e71df575d7e08f2c4c47b87955cd276dd59eab8a27a9b072c81e7a38824014dca425d273d7695519a278d3efd4d56ac8debf900bc6bed172401ccfae4982d4f6a1f618717fd425b53a2ddc9115dd69b13280fc8543221772a310a881a480f6d0465c91f7c61e971681488c5754dc3fa02dd8c1f597e55d72965f4dee787fbd012116487c6e2dcce6e4bb41850f795b0dfc90b54eee93a772deb30607073075bb3e1a43dc23f06823183d5007483fc7e1054009c9d7138d4732231a81022db6638296da8f4057ce978ce758fe8629021024d279f05d2bb572fde6cac42c4c7ce22c9048f65decf1c5fc474f0f1cada1103b42751cbcf8847257f0fc11ce7c1533c273e22c5f290b5ab21a9daaf1e8be464e63338a5edd5672fa5d39d02cc30eda68a04d2be202bd358685092f1faf30addad8eb079beab848367e22e00bdd5cfa3fdebf8c6e05c210374abb014662407f1db3aae4c85a783bb433d06d16b233d7e1a73ad15dae7037fb1f7227c1a1028e2e3965729d769226cbd8786bccd6cabc71bee861899e8760fa64908653ab2ef735c88cf5e12c371a53ee555713218fb77e4986f8715fe97c8196d8cd3a7b8fa5cba092196ee18672cd0eb1dca5e78317d071d07d43e224af914b6b854fb17dc352933ebc5d73387223eb0ebbcfcce22047ad7d6108165b730eae8808d964bd269e92287c4b39c172f3e865ddbba253ee8e380de33dcad37e10344e372cc4c2a3282a3f258a5f9320bdea7cab4a3fe8aac72437a484a380eb1fb9ab1b195eca2c0cb4a145c8736b729a3f845614f775bd3e9052a90f67a2f63dd71cc3518a4dce52884f0f9a20d12d86eeae57f8cb8f2b9c0d23f839173334f744dffa9177eba09ac85465fd32c139b01d21cf6dc97f1f46c738591f9966ba104d62d3f9446c8ab6f5c9ebb19f6c7244465e59a7bb562e1b3a7b5f7ef29f50e042ab27eb5e9e92745306ec4c792f72c0525552e28cc9d7447448009ee202b090ea2d7f5afd78bb848f2d5c31ef9d1f61cb1a650378ede7712706e1938b39410de338888001d8fa3fd7c8cc433948690ccbf1e7e193b8c61bf63e9cb06d8a66ecef07e06472c40335cb48b3338ea1455d63a2fbfb3b026319e973abe545ada66758d6eec599575bf3d91c8ee5d61b3ac3d51daace1a0bf5d990a822b7a563dd7ae56f25964008e12e1efa3f165e4a72a5b0cc9bfd507a456a212a0f6bde2424ceaf04071af19a9c7c0948853d579a6d936b162d1300a0c4d84ee4bc7488ff1fb6992fa0dd38ae447f5ac3b0fd5aa4554b88573c4ded9a520a2c25c0eec59f2651e50ff70caafb2f09bb41d1c7fd8d127b32d6b9b4b36026969fe3d96610575edc3442eef55a951c2aacd0f7343b442fdf4e81e7a22f53fa3b010d365b7161a687e8b5aefbd088e94121b4c8241525285c6be2f0c500c5c5f8bf57f9b120fd6a9ac65061d118af5caa7719dd664f1d723957d638c8cdc415cff0f58526f5fb0fc456e3979af889669907280e36d5e33c1eba13bfaaf27401decc18aa64a5cb2bccd30074ee5d0325ecdefc249bda2a72c5287a071317fc19bee1eca8825b1e74f804b060b1b40a2f0265859a0b051d7273eae74b81a50884b06d5e4791dd25608eb262d5d3ff7b7abfddcd2e47734a72a4aadba63b71b66d5cd175d7f1a575724cf7760407c6932afb4c307ad7e8d5603632f41ecb813f08073856403924dde1939080c1ec8b64d4c28e4509138f282955488e955e96b6a40a2005924cb5d350f0678c0932a613b217148f1802a38c727b529044450c3c497423c83c64048b396048b447ef16f27d6d0de10338c7092417a3b561c876d232fa9b9b61471ef81a6f1de6157f0d91a4b7a2fde9943d437269d1b3fb46f7cbb027a4fad65038093801a9ef4067ce1a48011f20611a0ab90dc43054d6a896d0999695c623356aa3ecc6d463a33dbb19ba60285b289d44627298caf41d41634fd83fe4c74ec82104e18894ff696c60c4cc39406e2a3f91a7722c1b0a5a8168a9403216fcde77fd3bf4175c14cdaea928e0c95b79ab8780a47239cb72baa79a8480df549d64326a4860b4b12ad73cefd87ce768a31fa8f1d172ba01004b9ab0a4ac1b601a5faff6ac15c05c571c987dbcf0eb0426a86d261e36eab4448ce2f6bb8b93f41cc122c2f2d07c8d7f4472c30fdf7a0d97efdaf0ac2856186e43e5adea0dbc56440f8b3fc3d878375f4cfde92b42a702dfe1942650720a2f330c69ed3803736bcb8188a46af6ae7b61e60276f7832b0346e1b4b788728546b001f5ad6d2079baac88cdde66e3635e4ae7e265d17d8d19dca2c3b6af72ce80c2a2d57887c6a7c8ae3defa63cd1757c5d6a6f50481eda6c2617202fa767b65563894d03fec64c575e85168bcc616fa12704d7a7422de7a7e5f4a681f57289635a51f8ac38e8e62c76018769cfd307ef33e1d47568de53e29e10b967d7727d9c5f4c21bc5210624f2a51d8bb38d109c820509457cda72a3bbdc05306374c24421aec5dc078a95ae2edd08b174857faaf9c5c57d1c2b1c9ec45e026d11a724b46bd99e69e932a4f2c4d4e170b2844de90f8eed49599d29404672889fd6925a5b1a5aa3ba0129c5b0d1899744d81001aaed182dc00f3ed4be516312d3fcc6956620e279fd2b4ed9f9ca5e4e6c40779d01b174ff27afa6bff5b82738863fb7268d84c293978d96a49a46f20149e150307a2b372963acc399bbba65afabb7e722a1213da166b03738708d1a12310f263d9a79fa0ce154c9d3e3c03afb05d520f78159dbc92afa5f4b5c6601418e97909a5433650642ca38a49f7e576bd763f72d51523d6f3827e52c1b172e4ea6cebcbf6d00b3c574a474120b3abb60a1ebc60ba8b9a2eede68ef0f7bfebe5834de40648e36281d39e63c70642e00d5ca79b720b3f9190811d31a4a98c0f3775e63039501dd7cc7d676fe29a9b25b64c2d6772cd97f4c7dcc31686b1d1862c98be76ffef500e619d4ba205b862aac9609b356bfbb51094bc9002379b0e854d543c23aac66a0dcb9c7bcdca9ade2b0e3077982e203a8d5a634eba8dbc6122bf0c5b1f53241f219b10951f8f0a9089d69ef41172e906a6ea31173ac298c724795cf4e5e0568dc01f21a2f22c48dd49dffe48fa46d59683c71183de79c5269c55c2c570d9551eba894d859ae33c86a3e840c22c72fd819bea5cce923763e33bce9313be72345c1f7f31ad068ef13d00073898397251662498eedc3bf5ad9a66d9297b351bad1c70f11460b442510329e065ab667220a9d1018adcfc3b13aa165f47a531900d55c6bf0afe8b992e7cf376905b7172fe7eced566ad4d2f7482cbc4ae1593b05660f7f68571618313b51e626c50706adff861647a558cc0c3849bfd00c9af98ae0b9a1ea7057ec9955efef27d34e11ead29ef476df864ccf054e6edce849372a4318d831e6df56a95ea6690ab2bf91382d9f82e2c10e52f18b3c26b198111c6508bfdfc30d7d57ba2a72120bc38b572bfd80b14f6b8eadafda55935306a947b73e40d3f551fd4de9428d7b4fb2e2423a00ece27b4a012f78312e372e68c4a678570469ca78f2b1254b2f0219bd80649f2378b05033c707c7c00886807408a12cf2d82fe41083fc815b6bfb1e2e34172d3243b68514b0c4a1da30c00944ec8cfebef858d1817738d9a2ea2688b257f72d155b16c030ab9deff19a3409e98b2627a67523b390eae2a460aacb69049d172e2e248f12eb13c387e0a88a222e1d74cf4392d790eb6f8e284a2bac21e1d8a1619c13eadd7c4b7e2b11548f8545f32774f85402aa16fa12e13d3ffa6e8c1ce72205d0c4a6c2c3438e0a1f430238880df5f8a1c410dd35952b8063b93426ff172351c6aee85e5abfc6db6327f21c818a71c803ce95221f9e0fec7b0eb12cb837241d362ef4e6bf4dc6302e74daa6f397305b912d083c14e80250b094ab08f5559259e6c18e9366ae3ffe3b0e7831eec4fb48ea6ee8de4ed3db3f770289db690728da3cfc49ce3f1d180af12824d231a3601efb6b03b412594dae8133193a1df17d0598fe50a629ef11a88e537074bbf1788bb8a8cf38b4e56cfe630a4615377408a3044101b02e8ee6a4ebee9606516dc89e1f15b5e3f496d224c28c456823942f41d02ddcd3ce3b6577fa549270d26d762fec64fc44a515c2025a0beb7e8ef722219cd7e8351ea20725ad642246638b9920ebbd7e0525c89136b1320492a5072ad2d1e242e0f6e2ff222feb095d7af4fceac46af85ff94972d3c5abf8e3103729141bb2650134974d5deba9c9b143ad99f0e6eaec6717034bea1335e869959154cc3ee66d84f02a06e9f4d2c9194c5434449e8d516bd63adaa8970a998ee00421698b7b776d96e64414a389aa52d4ec38dbb425e2a47ca523654ae2a28115159488198c8d0592e7640a93453e9673b454a585926fcb0fa49cda8bbdfd433d072ed5cffda59dbeb94efee1f9a07dc29328b9fe6899ecf5d45c36571784f49c320c0f90c9296576fa44a7565b441a1df506fc76df5e343080bbb6bbfdc212815722b8156bcf7a1690e5727a5deef2519767ed29abe462988fcac466f49f31431721dd19c8c328a020a280b33d43ca49657302f3ce608fe8d60e811911c251358724803ed232e5959d9c52f27bc8105105400f1fae697f89f7756d4d94b34e862727765cf407fb0ed53afd6ee6c177d30ca1c4ca0a0d69412b05896d235e177972e3b10c5ce2ac95be22f24f5381f0ca38d25c6b0a610848feafc1d979f910c46288c6af4ecce73631f6321ff292d1e99a6b6e71c5f1970dd3f5d0140d54542835ec0d9bdd1d3117465dcabaee0100bbb9a482a60b81c8a7fba822220f5eaa1f45e4a2c5c91830859d2e90aefd41486206852934003eb8a689caeff73fbbfd07072dee42e0453f1b59451f622292dc34ca7353927316b5397643aeba15c0628a33d6ef3c60bb9e4d8ffa2cb1291e0764ed4779a2d0a100b12284acd182d82d8100472831e500d3630101f9453f4599962d137790903d1e5f108ad398daf2e02c4726e5f24721f7bf70860036af2efdcf55b6f2434f7b3fa2bc26aa2c6f36174a472b75c4b03dde41df77678df65de3dd3f68b343f05c9087cd618abf5ff2e1d1472816cf97a88695226ad843359296f6e330d67b24ceeccdbab39060a8a0f2b01722b295dd36f9d2144f8b7cacf80afa0825cbc7d8238bef1fc63e995f3f0447d09851fe733a4ce576376f73dddf9107c32faf14fbc72db840a2a86268ce5deb5726c32ff5cf115ed9fccb314f080d89301b5e538b9d04ba288c1050aebc3ba1172532f859d6bba45ff2f88df1d0216c72ca6c9fc4f504791618c24303f1668aa7288ba54d8d500dd58e104e252adcca1b503a683505e3c1af20d4c38de2265c257ff47f7d633404ed469f2b58c5d5e8d76fa809a58232fe4e42d0e4dfa55185f7256f81446e965984e74c38fae6a46753295c7e1c15230769dbdb34d3a652c83339ea12bc02ae4af8b406f395e2cad41d3a2b359fe6fd6f9ed05b1f300c950e003f6ff9785bde737a54809d630c50bc25e5aaf713264396854772d21c2c2128472e9abb26964d6bf902bfac439e17d9ffc2b3b4ff3e9decdfc0fae8095ba240c2b7cd45eb5a24f646c7397256519dc3b3fecd50c83f63ae8dc9ea89419dea63f723f322635ec21e0a9927941e077f48ef944d6a2921c3e1389ae4cfb8894a33b4e73923b24a2ad1da64548a66d8c88d8df4aebe768c5cbce7f26144f603c953272aa3dc3623dfb1a328b47e52fdd085f3199acc9f21a85b3f0117302f3209ead7291b761d0be8b38ff65b108c494bf65b153162b719a80e2feca28d65a57644423a2a0a463744348ff62907002c38527c99d204286f840eddbebbe632074def772c62397fd0917eb8cb9cda25555b93720c665135761336be46c9b7d6e2541f23d44221e742fb6137bbaedc8d68fc99746e95b76d43f77bb639db479a4239a227238678372c15944b9c476b546bcb28d3d2acdd059a68c1876d63f2e1368749a72b32f9ca6ec0fc23ee4c7eeb7a41b4e070de565cf6822a7f2651faacea41e9c54cdd3d5e4fba56c8b58825f9caeb9fcd3b060adc8adbe0937c0467b10fc4a8e6a9824c66670a37752350acb857fdc8b9d93d3a5b5fd89406fb469d5df2ba64272de64ccc7acc6bac64fb35ec07297121eed73a1157217f2b7e9dbe85edc60f17225014fbd04fde659cecc00c442a36bbba05a21b1a88345264763afe85e5a662efc1192a0b6dd7c17b97f1a6e6dac9d0a7b6cd32790ac5c078b51e23c88ccb8723e37b6f4501a2672fc176651990a191add260b64d0e27a0f04087b0eded17372ff6ca0a3ad6799d6a431740be55a0a4552b728c33b8fa1d04d0fa8ebd18322724bf1fe3bf801cda7500797ad9e1636a1292106439022dc20bc832f5b8fb254397125db0a9ee77267ef00267e4e052b35bf8691a54d921bf87dd2f49735770c728d636f71a784d918384698a17afdb7e649e4b2e57454029164f27ddd4fe8517248c48ed2ca160f15f9156fedfad2b2b78b8ba59b2f866411cbc358df5a63e772bc528a305978631f923e2c37dbe61db88a7fc5e02483cdf1a58e5cab0cf5ad720be3f367c0123a7b3b3ece939f5db79ebb2c9de0b1d7c8a3e94e223e46628f461c798b3597fcafa88e2c5cb564ae6f24d1c90801bdc79d3da6bac9ca74466d0407dee4124ca4bb9e82a3645220946c94d1d233265d41371b91e744af874811729a1a7def823a0dd389775f85b7b3f514f13b99fceace4258b6113bfb63a5461e4d4fdc4f58dcf0f026c97b7140bf4e5e1b6ce5fea14c064ed4d72964a893c872462083755bfc7e4b596958041d1641312305f82f03043fb6319be779f83f8772f7f586a372597c5b14f9255d29dd32ce96be20ecb15d74db7745ffa8f33f505090c9cbedfe2c7637c128d2a206d19769e130592dc2843fd8f15f5f1d2535e772bd09181dd90e8c516c3e336585662cf29e37afa5621148b4c8edf79227e9463b9427b8460f5c13e9a7a4e69cd8481127240e3a98419131a41592def629075b7274345ed7db0c9f69158d4ed0c02c71c0a55dc633ea0aa72389573bd1e803fe721438ac68d27e37030d3b7681a2fbd97dcd4cc6e85596d888a2271b0b4baad21ddba41aa3a714ed9f865e737c1b3384cad45954f3b1503674ea8c1f2d86958b6dc5f73af1a5b8b40bcfdb19d10835ffdb841f3cb50d4526733ad343811c9130722cb30ca55b7ea6208bb40cedac076070a32cb37056eeb296489c4d4b77ca6f72c94aed6a025a62895d0c5826bc07b4a1d4ee029a505935f2b6704048047dfb727d00f634d5dcc05f65198cd7ad2b3993443bda3a4942b53f0bf22fe00be3e972d97223f65f7ed09700944a886832c3a21af86ba771a1fb0e3cd610ef23564d72ffb656aad927cc88e491d09de8dc793eb0d6844540b4266065bb451016271e35e84cddddf6a563d79d07e9f15af2bbb5733608ff5f8775bf8fd523267cb2797273bb133ae566ce8e390cb661c9387908aad4346f3ee1b0649c6e2cb531c98a72bb537c8dc601ccd9d17d1f083a552e06a8e0fe8b30683a62607b0ff5e3c07472640f1f6ed2f07d8d7980c170dec9671d85cf9580756bffef4e897880c6564772504d27394257851d44dd2be5d4a5df277aecae7ae204d87a1eb84f6d89c96e7271f5ec7f96e0711b6ac10ed94547aa3fbf359ac21cd54c754dcc5e4e2b148541b425b6dbcbde76b1f29c37ef65736d3830e35889be076cf005a01e8d045f1f28de087ddec9f4ee33e81d295dd205ba5f6fe74208249f34b0d1d9a267d0cf8472240dadd0f88ca1021fa73bf2f694b59000bfc66717c91f2059082692ade2813f3a4181be1700cbc3ae8e90e09ef84d4e105b0b3076971fe8a2c5ec8afd1516381b7516e294b9e122373cc8dc4e49f5d509041081c739352ff644cd42d0315c10c5378b2c424b1367d3c57379a8fac453eb9612e5dcfdff3b1551955a4e4add3cab4c0edc6dbdfd0fa2662cd7c1f05873328f8b54ab7156072be7bff2e04d9a72509be5c7378ae533fd151cbfeb44a77cac69a213eb2a2b80a7397c52b9fcd17240542325717177d56f3e8d1f571f638a8829d71468583a7bac32c17c7837f518542c52f866d2d4778aaf86c2fb740a27f01a9b4d352f308fbdcd6b0062ab5464b4d58aa4a21ea36487bd64f73ca6115be7d5190ca9c96478a5a62f85635d0f72fc7701f61c4d86082e2444bc8096b7613e628c0c0c1dc382f80bad270fad9b2bc84c271774132ba2dd595cbe0efab8605e0e3a40ad660cd35cd414c47500c272117387e2b576a3dde4e69d5af5352e9328c90434401d5fbfc3f9a47d5e649a72edeb157596051d24d9d00998409c61d406250d7952259b13fb8596bbc918f22af185e64727a7957812c492156e7b3547961eb1d19934573dde263a4a29fbc348f9f3b97b7bde6e9a67c90447b140c299b1c1e1227d1048e6cdffe02dcff8d65c8cdec31bccf64548778cab400529357f71a53877e70056d0f2580a18add05d556cae62fb7e0ef350c2d4345e3848e6a32a8baaf86accea338e6f31c249f4625748b59c96edd3469a5ec0e29d2e899d423ac60bfdb932ff655e7ff2b33840993535d036ad6c089156d27bc4088c600afdb2f594c882136b2ae833be7786f92672cde339d8c7cc5436feb089d3d51fff1ea7f93687c44da0d57121087ff03a1c7225d6ded4a30de2fa3f11b3e2e1396cdff83fc9a6a47e65417f737d599da6c6682ced5018593a0effaa46b4d3e46aa71a3543d67623b011ef245e259d8fc5a072c604d8235b1f6bae1a6382da72be3d0157c310a2a5e77e9ace85ffc5f9a18b7219af3a6f0a37dd23c956598c27893c6763271b6f0d714e698cbdeb4a013ac13b5c891295b26dc1977d8d4ab7dff3a4e4df59ef918c945dc6544f616a8a3b5b36fc69466b350200ae578dc82bd961534994f980e09d4d8057ec11397744db75469d093f8c37d3984487fcc1c7f4fbea1ffce402bb41b7cf765b820d81c6ba9572d2366cd35599e79102ff765f55fddf021bbdfee1846fc25aced4bdc82308f97215f4bedc391cd2c5ffb0ccee407b138e007e5740eeb9c976a6dc660d19bac4725edd8e79b116449c8916d7cb222c9baa2c128c41cce3cfe195cd3e7034c686728070f3de8bb654c9d6e1e887bf9f935392f368efaf78395ad2895210054aeb07af9954b0b22d0db4b8ed671df4fc4d192cb4c49e9bfa0d1c014016daaf42de72b3c9d0c0f64f22a9ebb36ef30b2c2582946a0a21dc748b84ecec2f76a04aec720ee4e4ad3b9fc9425f4350eeda10cba82833ef592c73271ce57c1962d146e34e7031547f135a77b00026c2c3b514ab88ad347514fa3ab5dcceef363551e18372918ba7904453341e56198e9e8fdb0a76be609f7fe97ba39e76bec04895625520500e357d62facf6dd899f6dcc024be8f73d159928366ab379f5a5b0f93110657fd44c7c7195e583c7a74b86ae3f4f883ef49de2d981567e16eb9dc317138ef3253a8880b43d009ccddbdb30c1d73a1009f41d3572610cb0bb29c0437a204997282d7964d7f522b5f57e826a321724522aee2f22053457c375c0069b9cc13d1669896c596f1b8829456d4b750af312746e21e8923c0ee2263bc65bdb2dde095490d91d3bff2fd93ae5a67678954b677324be66c06a2973531d634a9d5a1959972c6eefe6e674642881b90a48a6a0a6cf37238fb5c9aa02407a5d64624753e752e50c65b12c3005601be3b037bd59f2bfd7cee5686a5daab974cce67283cbcde72598baad49dc728f778f16a4a0aeccf676dd779e7ca77afa5d897aa49f1a28f727dc8136cb8d37f3410a7642cb5c5a6998ed65e1467e2a172ed5dae5d8c853272670643826788084c8c0173e2d10074d665b58583299d5cf8d15facc1e29e320e5ff52ef44e8d09f406e5b897cf78f40f23d8325a1476802c6502ee5657b7bf5685e1255f997375f1835364c62c9c41611710ee1ece5a28726b4350d76f9a54725ce10f60c4a2afc1c8e63b659db6a0abeacdde317e28f743ffd5ee54e8932e2f0023a66d00241cac5d22348dfcd252de81d7e929db132ddd5d5e0664b82b6f034bb2e6c316942ee937cacc3bf605634695d81db83888ad719db2b4d11206424f835e3c24c2acba34ca0d311e300df07ade93517d235f0ebb3d753845c612bd1bb7c63057674faf10be2acfb12170ec761530697b39b92d64fbabc2911a65b246b1991a3ed3363ed74a3ca22387e42f1610b00afb4ad54b8ff14ccb03cfbd9a459f1ff1cb3747c0f7bd2284abd24d4f2377639a959a9c711139789edd2af7c6434e5821e7ded8da5e7124838a3e1bef580991880322f35b51b17706440f79792eef54585d4846f288c86d036bd0b294feb2bb734eec6ae4fc81bc0b29c524c072c2cb61a19c617f7ca24a05fcd6038c2bcb8b4a257a18511408b707755476537221bb91e4d57eb5561aa1a3ed1fd90c4e938bf75d63d39a30fb9c8317582cbd7204be45b3af923b74ca7fa198723ec7b8ce1eca527dd4d271c98da7c66b7f1b724e79e5d4d947a8ac1fcda50782d3aad3cd2c9438204f24733a4a9b7490c87f72208c86e6f74148b155095b3ec8eac43a4284790cb832b0f38344f75eeaa0bb203233cdf50679bd6ea546a4a47de1383819726271b2ccda52b52304134da2ea43f90801affe31651ba514c2b3270d8195a520ebdb072773ec3a4b1bb555e096726282ab4db4a24aca7cbe00de95152a9f86cda78ea56b731f7d30dc86ff4fa172bd0baff5c2253dffaf191444c6ebf6f60b7c8beec62f5bba57cab1ce861f8172e12061fad6e1dd83f7b1331e100945ae279906a983efaeda7124aeb99074a9477399253ce41f92227e63d59512d634f5a7bb47ac8c7ed8251d00e2c17b32d25e069c9043d56fbbb186f8c42469489df3c4f557c4ff0a6bc57013e72d94c6417275a0c04ad6182e6a17c14b0d9c07321360e858917c5ca0991f86d6106ef7a972a98503f595fd7dd7d68fa0d9df1b7ab3a583c74ed53fc1fc4b82303fbf8fcd72643916ccb940c7b57b4d5e61f319ba2c620792866bb3e68fe63a39880bc98b7298600476dec0f2aa056bb26dbe214d523a9b5ed63398cb9f45d13923ee52ca361e45a82592b05b1c9096427f89da795be5e0d0fec1aae906d9346cf94465e072c40dfda1213b57ca8a60e72d1d8034301634c4c2bd22270b777e48b56e152072f829a061bca5e74f9db3340a2c5e5d012a2a2c8783003325da1b51cc672e640263508a338e36921234e3f1e74462b3f5675e6800ca9814d4692de41de87c415f0ad18841b39c99f8c36ddf0101d200bf4626492ca715ae538875c32fc853c943e52e9c4ccc6599ef59d681fb8e102b5543cc998a4c07d032edb684d0d3676301cf769043ffd5e69e1ad7bc1c08cfdf17532201ffb173818afbab120140cd47720ed2af84926276d7916b280f8a8adc7aa95f68feb7cb2e4e7331085693528c17db8407efd1e2e4d7191d978ee7c61840593d06cb09c7a16f1f1dc68474725b45c1360853ad5362f0d3acf02a06c6188696fae1b6e356327836121efd13f947723d7ac914d6b23ea3847b7cd645010bbdde567379ee82837a6ca8a9979fb96a72c4809de68337564253f36861bae459685fba6815f572cd1c9e38d27fe2045b09531e1a0bf72ec7684989eaebbea6c353252f83a486325a14207001853f51ff4e73f8745d69e115c5c1d78feabc61f30d3351af6bf44f773f99c6ab48e51ea872126c2dd64934dd23c549a9b0576bc222fcb559babf3235331468c5fa01e9b80bce8a3523a3ba2a04eb09a19ed102012be56517432b3651738b0f8edd745cf9724f2cfe9f559eb60bd29d213f904d44e795466da8e3fd9fb016ce8b4dd085fc24db7ab9f5899d7979fc05d15fbe9435d4af272be2e91fc0ecc566823c9f343372d5cd18fc5b2f46665e4eb02638e48d28f3d075a3f21f03fae07a7411f0994f725e63cd12da6cfc493e635e061032b318b0c6029032497202d0a004bcdb024d726cc73a11261da5e37372ab304c217935faeb53a1d3aed21e6a7aa6248582f172c5c0089492b2158f8f21f8935a574a742a40183426f633cd1674b0f826a26b176470eef5730b5f78eac848f6311ce68039354d59b2e608fcb978a95e06bf511ca77dc8ba398a0570510b0b1dd46d646551e859cc4cd36d1aae0d707a606ebf23281114aacec33ff64538525ed1a755c2491cae0b00a65ebf3b97e0f528b7f27222e70899d59dd44203a8f39c5ec0d02716f08d578440bb762ef2b402914b1c72d8b8ad51cfe0058049e2d146ff386da582d2d9daa5cc087b68a064ab63a4f0729db94423e760de163de6c39eef7d83bc0c0f85b90ada5fc02a48487f646d4403d73def57ca6b690e37bf6c3c03b4751cfc9388dbd96df2a41b3f56a52bd00c72c07cc29950e2fcedfa36060f71d9a54cdc4c2e226e07cb4caac2264515507e72e04dc2ca489649a1a79f0d1b26ae5ade7861aac033f932933d0e0f3dfe5d617263ac80642b50b8c128f252c0d54f34405205c6a1eee6b3a2fcef95b2efcfb272e5b9ae45b6146f045683dc55327041d3698e09002571e21eef727545750fff72c4e27a91dfce133b6b40abf9e4df14e9ccde197535e4b6410a00d8892d3c817262982d4c2cddc40f49070586d7feb2c75a272c2483e8e7b429538f28ec96cf72ff2483c0cf7259b14046cbea84d307b5a4cc0c2faa81f1707a9975d115f4894a4fcd66d4ab841f2d7d9c25ac93716d116be8057bb8df40e18ea2f3299af3ed5a636b645ec37c1645d43b66769a00c0cf5ae6fbf9abda3388ef8221657a78215d05a09740ed2ce7042ca9c0451ef438da49b0204705e35a7a6efc2a9f99e25769d62b2968dd8253107bec527a175b303de09a755f77eabeb6487daea5d588df6c312a84a84a8b678738beb9dd74f96cc393a2523bdc0ba0d84ae2fe5641b5fb72a801e14af3b96d9fa349ff71b1ff763f9c078992cd5a11b17d70de9802bada3ffe67c082bfb6667574b3e4889e1933dc32fdd931c6f9fb5e96a6055c630b7928f226fd724cefdf83c872e0ec089087b9fb376e73409dc93816c7710609688372ea29cffe93da86b18b4c946f3a1321725e5b417617069f677bddfdfd80b4a1725eb4fe3e3de605b19f6a565a4615aa617f6d5cf4d2511aa7501ac706e321167213ecaa398bc6070253456d195654c5ed6f5be5627bd520cdf45738ba4e9c562f502bde82e0ccc6ef69b2543fdc8988131f53485b7d694acac8e93925c3a85d33fe7d6329fef9995ae0e7cb7d4a5a2c786960410f578b0c0710c393bccf84d91caced230b8e129e07eb721ac44bb836461adcc25407959909bdf45abce4a74772857340fd40b59d9f2f7e2a055fa555ae4442e66d10f6824b211cd4f03330df1bdbe348de01e6a154d65b00ae21f858b49ba1e439c97819af24bda8f370aa5272d506f1f5618517e44712b0c62bf7b6a91b3afad778f8ab1a2613b1b8f96c3f72da486c99543b3b1d276b01bd8bd6a3e518a096116833d2229395f098303adb72dbeb81fb4480efb71547ea25d732febca06614a1f0bfb502eb5f112f2723f308a4c4d03baf622a2c324d7dc9ea71818dd58cc8fd226a3ed8f599f40e6340ed22abbf70673ce8ae2537a178e9307286f6d740eb09c3c74642d3d3f060c7a89d72fa3a06826560a06b231883d307b8ba72a476d244fd75610f9d9ef1a033f0aa5af28c976aeaa14808794f6d8ba89c8e0fa1ba35cdc46e02fabab74dceb925a66675441d7ae72f1533fd38f8d656f89107ae3055a10fc1cdb88c138246ea727115cf3484d20acea75b5c64a7bcde9c5471c02d50619b7aa13610f3f86fdec3c66624092ea989611c2e12923feba802db54b175fac2cdcf0ca2fc9e0d7a51e80a72d66f2c1003896dbc1c6dfafafc796eb86f5fcb748b248e99a464f5161ee1547268a9ba68b62082fc83b3bd6d128f5b23c018a645218a122b2459dc48a1f33972ddec961805ddcaf3fd70ff010d22ec11fcdc3c2dbbe215e407fc7895b6215272130f155c4ec6c9c9aeacf0b45290d7ddd8b14542b6c0c15aa02cbe87264d0b5186a02ad690c362d2c5a1c2d3b6eeac6bdca221fb53f4e9691ba0a46cd99072679c92b255418aaf1d1c2a671ad0d0722936bf8e63fdb44d39d8eae9d5188e72720c49f2f7d88920ae1d1378d5dfbdfd05584dc026ef12c052c5fdc9b00f174472e9ee3b6e07f3bdf712afde62aecf3574d47d614837812ed968fca9642b3ae4721be5fad1f19662bfad83df449655a55764dde7e80c2732a2f5e9f4f253195a6180a59559f8c95a6a32168bc33caf4ec64f12a1050340aac178e8d44fcfbb2a0b1bd8edcea1cfc503bd4eb18d03e5a7e4a7313989cbc8b72490c05c8d60c30a3d1c3ea4a57c4e6aa7d27853b1a8dfa6f1370e7898f84cc5e0509a73ea01aad9726b431deeba61be0776775c09720bd9075fbf1ac012910fde484f716967844b722aac3f27af95c90d6821c1e43161b78be0eae5de9a62e246991b0e7d73e83b72599a072ca5da531a6a3178a3a856eb33bed09214d3543c7ed34fc69c481155720072aa0442d26b8686b35b1e0685c5d436d2f53e02bc6128398f1040a5eee572e4f59092a39c49f5b2d55a6032e17665683bcfc514299f7f5722212973fa562afe52a4903af4c5d1f596451b47fd21b9bb4a4fc347ddaaa30e025fde8d68d072562365cda5f3b13e34cb9e5c0e4936ba866803c8cea22012eb90c6044f145b658b857a07a94612d047f408a20b3ad007aaf6555b39ac92dd959c66efb6e34472e79916ab88b1cfba7498f78d859dd1d43e3123a27b3c7292dd4d3bf692f25b722479a5593f3da29abdf7961d3712acc65cd61e8c0272f15765123911594acd3b11b0409afee3e27dd30cbd6df507de5883687accbcb641f98422347c28bb09724bfdec4aac8aa5e760dba91706c04efaa33644fd2d89e849ddcfda02c6ba910c6e594a54ea25fad7a5e3b23ca8029c00377d6526bfa5007258cd3ba3bcbfdd41c3b24e2c0804ecbd3a87883d27c052941799b0e267530dfd2259a1ed01a3ac5cebd82c98f862ea350206c7d435d1cc714b83b9253a66f5e5a50674c835949d721bd36aef2974c7311748db917f9426822a60fc1a016b4682f71594773585f372cdd5853f2c69e2578c1ec897e21a6d365269d33c3ddf7ead6c1bf971ec35d72e17ea489bd8de4f74ff59788c0ac1bf4f3c50a14f9be0fc6eb55e885c224702729d53ca4f82cc2b34d2395636d5eaf4121a3f8e89b4d589711f0a72d1d152d272ba9d4d6e41d823b4ef32bbb588c768c61a5a7d2cd5ca67a1ce2249e0e636b3723443fd7038647d20d98b3104b9b907636d3e5148d2e03382aab963dca6a2d80fb15716efb3290538a83b4f059bb88b3678b6cded637c55f351e17bc985920a724f0b38ba542cd2f59b6a938e9d24171cd6ada072d2f7f8ede4429fc295a4fb1e5a29503058319ac3f7e7f867cea6622659f6e8c55af8e63a3b97d93dd5544a72e9880d4ad2df326886267b64e6d53fcfa9ee82d87aa9a1c66d16598b965d22723724521d5de50e6bdbc9790335999ea5df29d40aff1993eca124f0f385d20172bc57a558512e72b4807f2fb4c3e9882eb596c6a70ebf38b5128f4ca10327894e062cf7799633d7d32a9a2638d931b46b6fef25647a958576ab74b9ce77713572822f1c715c785e884bdd1a796fd4a96d468b19c45498dc5e8d070103605462729172d59a7cb3667e272d838868d4f9a93ad8c12459880b4be014629982536553bb0f8af28410dbfa011a0b8f770d54c9a3cb6d1b91627a28464b62473f2fb60e5b6f07a2ff107584fc7485dc5ba5707a9646bd29eff880b708bfcb01e38de0725de541d9bf39ae5d6f2e71201fa61a64eb584c246880a46555a0c2326f929a649667916c4ce503175daa2ac3098603d1a669b1be1ebb2954931538627c1a5f30d700a2f5c4fb1f5778755732159d3ed9dfead47ff1876ffa4b9dd66fb6f20d0b1138601a17b2c6d2667f17219031d11ff335904c2de2ed5ddac9713ab4e32772c5bcdd8e483991a8f391531ad4a8402b1d891d72e04bf6605641f557096e135d45c37c100f621773fdc28a37b6819f317f3b64bac3c80c54839d9344b07fcc5a19e5c7240183c96ed4b3cfe394c671d341e7a0db7ca89a9a1c9bf12c85da97416180903cb33d7d4529061395b321a0a7ed5fec08d1351d19110f06a53bca4e0d7e8c120d347ec0dc3939aea877e3946c6f425422e975ec6730bc62fd64b4ab1081429e184a98a0f0c39742fe41d0fc5859b949b3748353e209197178996e6e0a329e3d546efbed0d40366d39b2ccf2a32570cac38d6da1cdd2b65388622dba3027da4a611014b6a225e1b78880701643a65c4191eff9f835384d7085ab06416e1bd16d8933f46fb62eb2f237b3b866984abe9a96487ed403e3a9e08e15a2d0558d77b477f082542e61e0d6ce89f62090ac5d0b9469ad02d90feda6ffb0b843725d5c7a23ef4363e58d449f42613f15ac3aef8e25c164ec32e485646220cb1320526ad40d0172d86ac66d83e48368ce8b72ce60a64c738e680621a9ffd3a37a7296781f1830ebfd26262b804af07c672b330de75562f0299ab3a22f0675ae2902dd99d04abb5182c4cd7a9d86e218e7e0a527fcf605f29b7031e3555be63e416b0dee3263a31e2165359162305c92216342acd07c697f0fc83d5e980edf6dab72649209dadebcba9e41b178d508b823c2089e96de8d17b7a6d84d9d623e4a53724e0b0a76fb56747c216af1630e6d1870d453e3ca23e74d5ed78a6ff61b4813514cbb69f0be31f4471db5450963d856b8c2ee1eaff105d5d92f28004b62115e6b9c654a3ffb6dc6e8c3d68ddb0129106021ba09ab0b0ac21395845759c599ee7281026a70c3e9e10438dbe74b2966998ad35ef37f04690d12116e232ebf09be72657f4669eabf6473a6900df21ed5c7a4ebd6d8773a2c8d656e8b6b3752f4ca35dd0032fdfd8dd0e1d90aa4f822b1d9f247a9f4df7cca9cfb218ec8f0216fa472171c44f4c4dfae69bcfbe866996ee07ad7f61218b4e92f556afb22d2720fe87258c3f68f5cb454b8d1a12e4cd2a1bed2fdc2a06c21deb32606e1b73b3ac721724f048b26b7931c0e9f0ad276a802ba99d19ea5cff53900c0463be3b8ab4d4d720e0e81f631a8c1de05c488de6200e9326c091d382207a25efc27dc7e8446161e952a6b203519f3d32223352655e9e91d2d4a77e09668a93f062321131d23864649fd440fe43ded463ad24d07296c41f29a428e50ad0a98f4e2a0048a369ec924690daf1a663a22c08795447b3eb82816b35d68853396c7652d98ab24f953a672cb48bc83722d65a03148180832798ff5e36a286f676da6067fface29e1ab957217435dd16d079e91957273415b9f219ab000c94497d26d5d11afcbf6b2f1fd58108dfaf9e151d9ab5d16b2e844b647e07647d0a95e186edf98cd214073b0cb7233851c146a54b3629a6eb5f91261e232a16ec38a5a1ac83feb41a9057a92bf0ab869aa46aeaabafcad3f860be7401f918df528cafdbe643478ab8a662f9eff72c830c3ed72eac2e7eab5b1feabd3b47b082c669d400e40924b8cd6353c907772be7ebc4ef64f1d1eb548486daeae69ebb11380385df5e85119b12d6c3c7ae6637d7d42dd62c4f8403d98a4ba4a0eac3b13d40935940c0952903f843a394aa772e2f36d27fb4159a322faa21ad6cde6b07424308ed8a6e554576039a856bf6b60d91a528f37f338d96d153f4fb3992c959619a02ef63c12bbc71561d70afdda15a4a51650ea208f2e2750f54b7afd46ea81e5bf5966d7765f8c400f327c339e69cd5084ca5814d9fde880ebbdca2b4f02fe8cb120b939f90feb5410c520dec672915b6fd0ab6ca8d56a8805c688d59ca514c6a0db0a9befb44aea6a68498bf7723b581050de3ecacea40cb249c151b57b7faa6b990e8edd3d8efde653198b6f7219e3ede83c453bad8b5d3b8b47f414ba7a8ba562dd70c92e94f0ff211c142e721d5c44d96334ca94f971ab7ac9a1dbf8499919d8445695d9211260aefffbab72bf712597084bd4d90e22d629c383955ea229b068bf56812ce28dfadf25291e7215a322983f13d5dffa3c7cd8c6103aedbb9c54426e3c79425a8f4d52e1c1e16b39f5dbdd3cc2a2365f74400122cf9fa3a0e4597efefe9fa944df3c37b3e3b764d27e654dd6341a862a7fbe91111d35a62285d50172d69188234fe19251d96772c09698fb8dfe89b260a6abeb7b2d08565080597113384df182ba201127888572dba23512a39e58542fcaea8a88297dd3637658dc0623e1b3fc062c1d9f64e3720c32d6161c89b3795301fdec12b9255fb617fa6e8489efb16d67a0e7398c66068bc021303835688d38e17b1fe8c19bad957949b16fa1ee0ef459e2ef2187c672d87477ccc8c7540e74e8dc055556b898a13af7dd62eac7d4b36e787c15dc9722ed2af6b44acb47dcf3b95b825fdfd579105635685f12037c7b706686e1b8dc34e9cb472988ea20823a9516b926143ef8dcdaf9a74bfcb45bb25c033e2833844c0d4a981552b0768ab445c7ab253ab0a5e4b8f699d50e99d90b0b2f75149a3a5f8898dd33ebde66e6ed4549d02ea4dc41ab9837af691ca28053589928ec1a82726a07f628861ed8a0c3fdd68c9a957d28f2b6efd1559404cdb8dc630ab3df6c7275c47700026f3d063c228a2d9101299da982f3750c87566bf2c4345f9e6d6f2a00acf3065e168562f9ff5f17aaeeee9efbf7dd83a4fff11ef266340bce9f81727571d9401cb7ee866fc80a751788e6f14af125d99f850f97cc5e45c9601b130a98842dfed0bb7eb873dbc284c26fade416e7777b04e09635e928f78599f1af721b20d7bce03e3dacefc42561370dc34943c087e9a4951e599837303d4f7363562c636435f70f384e89d4db7b4530c6edd0b0d4526d08f0c7596cdb577cdf483668dcf6d99bbe964209472917727d1c2c283c2b03327636155e451512ebc35e726f5819f8ed0eea8917c43a87d7b817a86dc0bfe95918cda8e9ec168ea3547072c096b88d9145fd7370ef4d00b73a7f7011903839e75affbf7fe4c0bad1db1446726c7128f8ea595c1e40dd665443406de4174bce0889af9717d6ba152d11d766fa53637bace2c4878ab976be6ea61d5d54911c79055065d16aef4dc995fa7627061ea8104d0733bb29479b9c74e93b407100017fc7772f128351570e0dbddb72b31f236cc478af398a7cc92a33b963660011f5f7b41ccccf917e6a9f51372a0055c487cd526bd14fb8e3c5694d51b0c9377f9a12be3233b0c692e61d81d8eb3a69cc0f31457d809ab5383aabb333ab25ef6eae890fb09782bea6ebf8bbfce80d63bd43a00f3c9f4480c13ec8056716b5b5491cc7908c474a541fa2e6e25e40725e2888fb448f3853bf76bc73d7d859f2aa77f860a9d8dca58220caec9717e03897b3cb280876639dcdec9368ec3530b4092c81fb925ef375042c1170fec32f7226c03fe11463e0b0ef7a76d76c03a5ea6ad10861f75efa2371f54c0601b67c72f9ecac267c1f091c2fcf28cc9f6f27fb2d64a766b4d7273f055251192f6271537037fd9cae2415dcaa0c934ef3e115f4f6d6802676486307887902c1ad4b3a35be0b8339797b9164c65109f044e1590976ba6129c46fc7528e2ebc3b6036af72afccf441793788e9659744eb9cc05e1f6f7abe0cdbd4ed556c203253e9642d2ad7c87cbdf852cf6eb69f23fa163ab41730ad718dc08d3488fd3d67e1024bfe57f9fb1f670160ef4dc4939c4ed3eb49ff5c271a0f66a4ec4d6082710cedaac96dab121021fb9bfc7a93d34d43af0769d1d3a8b1c3de7b7a7353194278508fff72f9a954dc4e019a90e57e7be5e7e6b427b39a1a11c699a4f97a28772bd5382e671ca00ce5c1b2024a1b25d056c2cbcfd187d1076185e31b21574fec8d48f25072ea053768e653551a71b3d85793ac3fa9ae65b2d5d09ffe6db7953d439fd1ab2b37d831775417ed739c7a83414b457accfe9fe7c7c17fae6832656ef494bf02720ee11886ae73ed1e5a92e0e9a27e4e976ca80aab32dcd03ec294421f4eef70185682f18233ba1f25bc01383f51cfdfb03f5c30b166f7b09b119b099c54d3d772f7a103e53950d0c376578da2e320c94166c8f805b8f64842a328d5ae5774ac720b146e621aedc56434110ac0ca37e3bd6aceeea2262426e1894e39d25007a3720696c47760623a6f31c1e4f206e49a7c3a8ff4202c6c491fe1999743c4bcc57280480cd5fa0bd024034cac8bea0f3886670efe4f75dcafd9060c95a27eeefa4111a1a0c21e546306c401c3ef0193670582383bbd4eb51730f406303c7e39a868b70c114009c4798d77a46e5903cf0bd1dd82ff77536eed94773c7be057d3b92ec97eb948e26dc24e511ec87f329fdda60a9bb0820c7ae8efd5758e70a2d6774323f2f107369aae166a7e372deadb6559e822787be8d3397f7ed305597b8dc529be0138d520f016dc11c6a77668a80e75e0fd853fdc8ecfb60f606cf11ac5c1724e9565c54e89c31377f5d2d5b58e9a87236b0f639409446fccdadc5cfb48ea72efe0b6f2d873beb79478c8c7153f4bc9e3554df1b7a327a5f70c59d70bd4ab72fa7565183cefef9ab53669ca7ec8f0fd5e305df61255ab743b0abc7d38df630f1631d739a704514ece275662f03e645d7ab95b8967adf5b34e24d78b76e99926d7365f6de722ad15ec7e5dbec1e276e4a8725ca4bfdaddc410aa13c7be7b8f7215c89d0b7a8df77df270a7e3866d1b6dfb6131154f5e8d0340a895427a58e75bb364e7f52b854f372c2b95f5fb876e5ed137c03cf7f2a71085d9706aa3f0ba0c68758282ab84717310712f336b6d7825e337a75472bee0a291c1b7e6eaa8163440d9e5f4a46d431015cdc196c67cf3393487fe8ce347b4ba0f78fe4ac12da509dbae80d3245ec125f48b9738de298a57f941a74bcda397410312488121ca071462bf302eb1da49f254b906cf8a98a6a3ba51faba036fa9d981cca23e3c8e185a16efe77c5c2b3c7842527caf76f740354eb258f45d7b197790eb97dc76428672bdd553b99553a35be99884ebb32fb7ee821b1290b991e39005cf8662457d0772abf778be59fa466425add209f5ee7db3e6c96561ab1c26d2002ada067b8dc557ac294c5b2715d7018aafcf6b655d453f787e71c5d107cd5e9428d3de47c289728d9b03f50323de3c6eb0a410f911997bf1d994c6fe0a84d7d22d361ee1c2e967789bc9d6176be0e871b5429c188c7d9349358d7db5f5ccf814ef6fe728cc13017924a504bc5bf90b84c43ff981c60f868fe6fa3c2e14930fda0795d86b2c6772851ef6054a644d07163f851c211f5a56021418a2a6cb9766b0a242dfcaf71c38161ba3a4b679ebb4488efe92d1c5ded61d75e6228e4c258c23290586f0add30f3242ff7f4d3b6f53d9a19ce7c1d86c972e175829d37cd5e674b04b2a1b92486421becf98e108809177004fa65d412bd3de35b0f6ec8938739613dbf42f536257bee6fcdc606a79bb2f64ef786e57724076ddd66ab7cc97474388310fa5ef8a72fb70e9507b74ebddb2d9f8995c2a67a39f2383aaeec328674d6b595a0bbc861d763efb6c56bbe6b20ae583ccbe1b92af69f6e3ba298beb913350a1de667f2172776179ec9aef045efde6d848917467c5e6a667015c782092e2cada3bc34384720eea24a297dea46f0b6d2bd4ba1bea077f3df52489fcfe674db8ae7df5e0d772603c04387a29a4df1e19a0c59feff13ed19b3cf7824bcc29740c1a483453e97207e024f4514442189b616d693cc20cdffa479fe33bae41a1c3ebac791c3ec972494894265f6beaa84630cdc8440f4babec88bbc4c3e124434b9ab71a3dcfe736286abdbab8ceae328ac3285763a5f0c313ab1614ffdae38135013d4dbd7a915365d1f74b549a15ff4531701c68a0eac8b141c2547808fa702f72fa04753cf8720bb8151cdaca10a0718a3c948e79730889aad66b0eb2b9be63d1212fef639d251601fd9a3a5cf81f6dc9842065df2ea87d26860f387fd6d7f9b0f9fec3b365726c2d0f55983c121b826fad9838b13493e98c14d883ae2eb0069e780e4020ba1ecad91ff0332ad058a69405a6af25339022a3bb9e4ba5392160912dbc94c01972cced89d45e9c3e5839a89152b7c891d1c3bcbe43a76e062306017482c31287720eeb0b617e2bde1cdbd1897c2918a1c1e776872c2771cb0af7d5f7da4660ec72b51c4b1a0b832ccd5b87610aacd2f666cab3a78792be4999029e49e343428372bd79f0632586bb4275f538feaa47043857e1f7c0cb4f0b2f5b5d1b979babba728a01b06e1923ae3c76a2c035bd25184d77b0c954fdb5f2237faa1bb6eb86455570e48c6326974d5c271b48cfffb43ae9ad40a2dccbaa87b00a6c96194ef58e1bbe9e912994fa62fae6d60786f079b923d3f17d860e0ba5c8a02519f5daa9cf72db658b250262062b9bc751ba7145daa35ddac4f21e4c83b921abcccf9eac27347ebcd07a9f4f1a788a05b5dcfb2e42eef4e8c93d88467c83fb8a1c76f2219c5aa13e89a2dcb7384e794394cc2d5727c6ce74ee8df3bee512c88372abcbfb547251edd15e08d5f58dec6ff19eb721cf287c6a4a920f3d2ff99753c2c21a3f1272dfa66977a50a966323f250b5c6ca9b44b44ae3857b17c251bfa239ee323442468ae2f967fc333e45cdc36ccba041484067d0144f13d64a5074e486c6355c717288666abcda68d12d755e280f2a2c241651e239ff471616a5106d9446d7d85872ccacfba2c28789abbaabb504485292e86f7da819b63da88bec433f214d84b44f9d46efef75bb12e129c9ef5cdc538a62d90ffa09a6afc177f6e6c10a3ab79272a0f01ff18e31befda5d6a071f46d54440586b2834d9fd731bdbe82dbd52ad57241024fe6d5841a9d2391c2016f56ab7e82dbb183056375a7aee6b7525a7ca472cbb2492f97435aff81b482792eb8ea23743829549ffe46e090887d63edfd1a72c1cc1920289904402bbafe255457539f2ea116841cdd9c91be6778a0e6cac05fb8b34ee0ba5eb2ac1e8884485f061f06c4af8a672d1e59cc284d0d18cd5e972ed26633ac3b2b6f6447af16ffab01354e203a2251af429b25de1593b8825da3726c5026235d167fd2930a49adf6d03db1b69eecd0732c94e1acd8c33551f832724b4b63bf5a154bce2029e61b861605ebb54d8de95eca75333de5f86d52c3f661ee11fc5ef6b1306ae20590b1b6cee2db220f14682cc4b56f7f39e4e792e9457208990c48d53c54c1f012a66ea5482b374339517e616e5eefc1db9a8ce23e66722317f695ab51c915b19412c49f83af01a3467ea9d24cddd61182640ae08f7f222dfb1676fc27d8bcb7e796820b1816148eb88fd789551edff0c8c8f16a34e945e78a901721b7695231ae2783752ca8866c3216b65259f7f33fc0a35e88e765721a3ce2ad21f6f563afaf332553ce045ca8afb8ac61b2fa76926e2a3a8d1b3e72c5e51dc39e0468b9a491ee0f4641ada50306bd46ddd1d2aa33189342b443402c9715b4e2f8ac41b227141aaafc44942ab3698592a44c328f5e05a63accd6ae72164b8edc4a504fcd450f4fe8bc063b8cb28b49654d0389844d53bb3e024ab654e0d7ae73d90b9fb9a0035004fa6243d70539ae8cc2802b288a17a76144535408068124c74251fd5235b545908e4248c6147749d651efae04546deb5b0577ec728a883d53897e5419a108cc144df6d74f760e0011905f04bde09138db72073c7250f5c44bebbf358dab20ed2e7c0278ef252f923d33a9192d883d5b075f130340bc931fea4658dd399297a7bc2f81ca5d2b4ac210cf94fadfb01d276cb1155d529dd63fcf6332d0d69b772c1ecb4388d1e798ee532c91e50b3f5f27a20ee9a9722657f47676926b2a680b4979c46faccc7e8da1e2037b0a08e296798710957f171f0b2ae7fa1ad7ea5457ebc8d1dac1c2de1953ee847b09833d29af143cd0f972e065d079594c54c4b22fb611548dbc9052117e10f936e46abb3e1651fa7779720b5197da6a59f388d066fba1c1f495ed6f274da7aeb6d0206b3a46b65a09e6720de8c4c3d26e84d1fd0aed80023afc375ec932af514cbfc35dd83bbec0eb6172cdcadc8327b841ef6e967c024dbdc43ea03028a1175911d44fad8250ea22de72bb98717d70d8b700dfd2deb2fa27056e3cf0f1842a475f8115ba3981991e4c29cd723948efc7a57ee6366e2db620e3b4d6acbb8c5d0ffe01a07a476f3889046c5899330040c5ca7aa10aee1c84b18910f0d4751adcf4aa667dc78b2c40b5f14a11a36f6794519ed2b375049f89f6a3c8f6e75ee7dca96e632eeef886373fc9257d7a8a6938f973e43b06b1f7f8415a5fd2fe134f20852b3ba838897c36ff061083d3f8e4c39334e9c3e2e4843243c52471aec73f358c4c308e0d242e7ca93a37a8155c8e5a5bb9995bfef53887535fb00c88dcf911497c0b128edc540a57ff721fbe31835b36137b588679c5345d29fb610ce7f7ad8384d32882cd8e5e1b62720e0561f843631d6aa7494dafd433b376d95e09de7948fae9eaa96a4c5c984b6fa6ae0f5da672e1322f7335984e9adf6d6e977136e52d1f12d36660c494541072282cedf0c9092c1504cae5fe69ca39fe69f8fe40b079ca5180bc3ba50c6cff721352ee4d189e9dabfb101e95f09a7737ab256ae8fa0e83f1f3ccf6089b92db72cc66d8dbc20b66cfc6e788c29c1db72993343487635e8889ee6bbcab78c40572c9feb46708712a323807122c8430461409325d6b2b0972723f5d3a896e4668727ed984d2f3b795e4fb720b45a813fcb997c764c0c31f3e9462b09feacfc71572119f3e10477b356084da345a5d2b327ed27de5ecdb33bb6e5babc65a8eadcc72079e727e90b8206980d3330ac201d2cf509506806f01075d7e5ccd4da9e5347205b987170167b8e9de0565dd7f7a28abc7ae8f42b3be183097c217e28863f272c609c75ed4f5c5bd6db9fccb741dfd157eaa19b67a8249ffeac2cb48681fc1722c344477da0215eb2ebda20af5df1791af79ea5f6203a650e4da3a28d9587f72305e2c2ce40dae512e8817e0950830549c4880adcc2f9cc614c2f9983d921972424a230ce68e0e2150e6ba8803cf682e8e3640e372a441834f32f8af40f56570914f39dca2152384e6f120549ddb5141b291b18996c202e5ec744e3619713e2148b8e887ede71bb574a118d215471bdc9b8ec91f0cee5a7329246c75d65bd06502ca7a5af0d20603b14ac017678e820d4b96318515ea0466c558b881b2c3a872fe45314864b845464cfbef73d68e1e29c1dff173f32f792c1adb2e317b16977285e54cbc90fab3f1e48f9f6c69bbe26c9226460cb92753f41c81bc40b067a210bea28057595d0eac45bd3fa2de78434ff398d969dc9203fbf384f2eb2d9eb827091ed6d9245eeffa0006ec941b8fb4d9c89477a89399a4795fa2a1e3576f421a70ef88bfd0403285be1da81266a6889f5c79c9c4dc63422fc24e94930cc7da34d027138f7c74b2b092e2b071dfdaf182d86c26fe7f273bf0e023d294a3893c1a2e9c548ef1da7e7b57f5f94b894a7d9d0c12932503f7c75afa3f678410e5733ef5fa312ea0524c924e03f0f9a88e9e6a9790f96902d9a0cf8290d6813110655f110ba2521df9f15fd99b160d514a82806a17fbb49ca0d445ba7ccb17545ec572e33d731379bece7c6f24ee357075b7954de6c7a2ba099df70c2933a0a8302172a548c325af2f1df1f3da9033ee85a2624e3fd8a04095e21a5811b5a06142cb72a7a82463c780fd33898c19c94cce40fefd5ff220d018585c1f1ad296a9cb4472c36c5d37521f27a9d6ae7fa323fe0515e3ee47eece0051f037fed0eaa01d445dc3d0a07bbb8ad906fe6220115394dc1b0913ebf554628f528c4a7aae7f286772ca020b606e9d12df6c762469de460715487ac3446f2d3556dff2dd53b8437d4b3215959c44ad7c1cfdd40cf0c6dac08d0a9559b9a70dea84030c03edfefb733addab22f7395a822c91907789e27b4968749735aa4a4976b7a3e377b5cfd8f03c9af90b7667b271dc8a70c9a706d8dc4e7435258d7f4f05b6dde0a0f8c27d0f7287c3f7df602848a2b49770161cbdd180113e23f2c43857ed6cff835c0c1cbb722396385137f9eaa15cbafd10b9f3de4791e4240523ac5ec97d4569408471a472935a4e75ea23b18e068e715788b64671f93f294f7cbf08b0a6ed726adb832965b71f6ea5232deb30683ff7e4e60e3c04676fd10b9b473991f7669b1acbd5d240d86b80eba9db683f41f11bbc17732fef0fd3dcdcf278635887bc7e5c68c93d72813323fd04d30f2d9c73b94bebd85f1111efd6074600cefc3d922dca289b7652c3549de522445ff8634075a0723968b196f6792f403272d599c8c9864a65ac7290f586e6e565dd592b2c30c831dd089c053f414cbd48df5f938ba710e4b2943ca8243a334b89d87334c5e6792cefde85f700d610cea17fe7d7105d354ecdc172367be8b355c8ae7700188eda1335d6db170357acccc948702832e73979806f72fd538eb501f8a1a5bc7e6859cbf644db7c0e8ad3635eff5cf9df9b6e22e9b572cee84bfa059779226f6cf7bed17774c73f9c2ee5dad3aa2c2e77543e2a1f9a722cc6ca388002069f4f008f3776d7591df8fbae9e5911425da70f893343e915724f38fe5b30b804f8878c72d2338b509a523e1fa6ab3ebc62d2a24face7df8c51e9ae7210ee64850f114f645a9c37e8628af417c1188d3b0d9dc5517b94040651865edc2d64c5411946fe5aa9a7a1fed41cd5998c9d0e7c81b95fe5dd559307727042ce8755d698630619fccc9901b8ef8ba4ae6c1569ac3af43990f2106b4e3c6ac69ca0af9b3d25524e535c929cab8d57db07836b583476a420e6c0002b02729fefc8ca8bbbbea0e1e01b115d9efb6be7df096f16ce7290ec2f43da7011b972a126427f680b484c28db491f883e6a82dc88eb53e1c9bc6c26a6b743ed5279726e75a8f1a8c22253203f7344efa8ec2f47c668fb7d029345406117a1292245724839fa3238d2ea5829add045c8f5b0cc2d19900581bd023064403314a7f07272b545cd81c58b057aa19d8dfd4ba1d1ee3c644ca68b7271b0456295103710ba1ef039644450f4688d53700436530c07d128e1ebb0c31b29deabdc422c793a8d22faa27d6ad3f7646c9fb18c70a9274dff3b19cc3d4d14df60be2c31453d7b9c726a41de1a3bfdc1ce87b75b767f8026539a76e149034ff1b7579c1de083a2a52e722376dcb2f696850618bd2250a58480d529f7eb973a0ab717d8490e7fd57e727ee5d66ff767caac1c3bcf4a42d9fbe903f2df0eac5aaa2f501b913170017172c73e3e90721274504703d7dc4a6f2e1991c88e4e7368b8c4a98dddf17c164172e507de5395f4577243f3e103af044205387a30e83e3ce3e619ee468d5f491c6c1691a12c196b981532dbe5cea160300a970fcb5db88c80699b921ca93d395572f836ee83582343e0a10c7ca8fb0ab62a28939b02861a412778af26a4e094054f47ca9ad3a7c7c7959ad890e7bf80f6b87b22a5e150bbec182d176868a707d272894ae350f8791e29333af3b7406236b0ad989e6ac361f6bde379ecfd9e9a931f73baf12761b943f7e8dbe7053119fcc912d62931339f89b081306080c7467a43a4be9618de30733130d3ec4c83a337161b7284c689fc2ab4265cd5c1041dae72f40573e243cf83c2b25d3615272362bdb34ae5c59c17a93cea0831277401fe72e41939592a07a4d3ad32b2ad27ac417aea7100321e8880a436dd7cc425b217723f9c6fab786b1702aa48bd87984ba57e60b58099878488260cefb80d4f636b721fece0149bf2e2e89147095873de3aa195cc3daebbe3b20862d78e992c9b1d2e88d3d270fdabdcbed4f2400e620163628a704bc91da5cd365108066a6c858434d2068cb2760b312bd2ac9edfef767145cb465ed3981bbb523e7ca69fb6655c72d489b4d3e0a957715c53f7cb082cbab16009ba4802484b625f63b144736a78504e49f58ba36af4efc4b2e7ee6ec754e43e2a77d621706c66a698d746899e157253f6bc8a1b4c2b20bcea8a4da551641cf1befbf3730cc273b8adc20d01bc3872758a0ba4365eb123cc4f383940c8ca9d39027847d389f061ee999aad21e189206865d813fb894b177774b2092e59e1a896324f4e0d400a920db3818c3bdd8b0ac94fb6f7eb6aa1f6c2fdd83dbacb60a872b4822bd512a9b14c85eba7bd2ef972e677a975b0fc55b3894a492c596f490cf4dc740142e83ac71236472ec38a4e72dc9052204756ed44ae1433435d1f727837ffa2a6fd262ca473c385c464c1d5542ac1b9e48dee30f68fbbab106d81fbc35f5b13903fe85ce6f1afbdbc5452d672932f4e809b80172339fe3606426ff37441131dcea7174a5e3895fa65216f507216ceecfe1964e6bd906fc4a1ac58e6c785397fc91a955083e9a27c56bf6b6661e55eb1991ca9c1c6cbb56a80bee606ac4045ac050eb7f7a1c61cc557a6163f729ff81c3fee39472ac55190edf1952ad7e0ba541767f1d3772fb1ce546a352356aee81b8c429eda91e1efb2c063a6d37817b6176a9de4c5f7e784522bb6a1d23b79deb8c833e6c518645f8d8621ef58f49e44aefd72eb22b76d000010485f627228caeb0d8d98559cce11de1563c3236872864e76fbfe90bf3117ce9974212c725a4de9fac004bf484d7b3fc9a46644010c36912718bf9505ebca39e6e3338a72b568c6642d00bf217e8380741d7058dddeddb6145ce38fb4ad04e111eacb79308a4872d27e7287c0e083bbc8ef10107e8a78c40329fef817549d58f098b17e3fe67a3614d5806d0f94414e80e0301bd13c2e46cdc988992bf8f8ba6648898272b96948f34450e89caac8cd9a8fa723de6bed3cc9a763dbb91f8374a7314b807207805e5fcd0417af717913b83f48fe5f1a58fd7df63c638cf75b1d6a0cf6e7728eca42a5a517d9344f2e0bb023515c9b250d6ada01202c9e46c40baee663da7289878527a747b5aac8b2e99c78ad778e67c8d3337d1a85b53a28cc4feada5c3418d1fbbaaf6ac765c26e627e91bb4720996f707c6344f699a2383cc626153e72b806b4d271efdc88c94f63fe78d7547d4900291e56a46df43ae2b4fc173400721f8d742056a0f677c370601892a7a65d6fc0d9ee6442d5bd9eee78e1a1256372d727c0ddfabd2dfceb0f9dcbd6b2609441dfdc07fc31586c4387b6aaa4e434723803085b1ea7d582eccbdc1badeac558b1396a729dcd4686391c3735c9e7276d02d2fcbd12da7fe2d64d8a430176a03b0932bab72f161cd53bfbdd9e0522f472182295d81809c24d641ddbeaa1b3e6573dcb31c12a3ef709ec3bff5956633672f5677a7bc2179bb46cdf36ef5fe608940739f0d94b7eeb1ad9e261b0dd2027722e899f673b276fa35c9d6dd6d55b4b15c898cfd9826871a26a3d98141ad21272d1249f4d2f5581d7b57f502dc8a2c3f2e4eefd6e0c2f89900250050026f404722721ac4e074cdc75cfa1b7dcbffe045401d86add3ebce597bcd7310a415cb87257ed6bfcf1b6a44124809bb8c0c3d061dabf309155dc57cdbb15b13c564d314632e9d2bbbc1a5de29c261311ecbbd11f9ec6dacdaaf26dac548c430a6e0d56721261de595e4654eab9f4c996836fdd908d4786e7bbd93d5cebbd2bf745e71e0df0999a8f896191f8dece95f5c8076226df54aec937f37bba9cc2b6fdd62337722caf7e3ae54749c440f1807a8993f4952f23a0c7244bfa0825ea077b77abc96d1f67b44eba2b58908a58090bafacbb25e574f8dcc631da350e4b57c37d803231f089576ca2eedebea7bee9984adf3d9765f659e729e35d4d068d86c3047a8c44b560891392aa15abeab67152e0484f18da6e1009afd559a0307dd4e5fd05d32b4f1ddc82f03f0e800551dd3c765cd2064fc7f306287787e18da3c08cfd11bd726458cc79a046ff9a559ca793f22ca72e0b1b38b1394bf8acef132a48ecbd4a3da842edebc1da4d687e9303dbdefe1ea3427ad8d26612ee3e2895dd0233bc2138acbf1a9535c66c62ffc3447d1d3531ad87d4f2afc0a326adda13a9702d5dc630eeb6d8021fcf081502ebc767bf3a95a94ea08edb1e6537f9073c3601ac3dfa721337127a9900336193cf34315b9b58bf9326490d2855291b1bcf3ae50f420e3ffbdc9aa482c1a700eaa6bc3d6d94682f3c1942dd6a8fdf87101eddbae5a4a03636f653c2b89c795a232ec7f239935c974c614511faace509b7b4c0af4c7e6d383d5af8bea997025347ed68ebf7fa032627e8d7ba7b2547562c6d569748911872b7f2abfa243e141d63db1717c793ed8f2a4863dc24a6120914377967ddf9ff72d12e88cd18ee314a60231f16712b859ec322b9849837f9e68a6a07269b35e072fb12295ff7ec085b2b86c41683cc0cfe5c1ad86bf107064fb7ebc9b182a8ff6f1b585452140a3cd4c589740dc73e12eae019b5175711a33758634594d18b94729df816a2f9c1c84ac9464752f738a49eff0e6995990eaf429562a830459c32721aec360118e1e0bb995dc5088703edad2b0776ad170fd412def7454b7f582472ad99b8b4f12bb611c9d356b8c275404c8b8e45ed0600de7cedc0017b86265a463e585528cb11792e1c81998a199b34ba75d9796ec8a20d9b296b39647d2c603097a971e9e67b0a5a9d7205564d07d7bc808da250856c1b9e12165b630cbc5b7246ad8fdd7713c0526046d60573389cdc6d25bd55048fe7759d4d5bab34985272b86c7b02ffc1c05e26ee8f0b6039924dc01bd60014cf99bfc52ac58fd029384b706ef336316fec15d11090f41f71c4875056858be619382af39b8e316cf38706e7d06ae7bf760bbd46f41388bccb9dd573f4cc3db22bad0f629de8f5947f0740535d57211953942f336942e26bd82be0dfd473cb33fcbbd3fe7a3ed92f63fe3710f915803e01fb7028add4803e63bfe6e0180688aa1e7b4bda6c4f7a614edc0fa817d2f57ef3276a98a4c7280b90f7191e5076d8aa91137adb15b3651fd49f7263b45bc3c2242f5ba67763530bef34a0d15ab5916d8b702e6d0868796c07f772d93f74993a4a6f266dfefe0760708a136013c642a2ae23181e98d5c1f18989725ddd6595682e2cd1eda6bce5b462c61cbdfa278747e705aee04e1e3cb985ef72c49c9e5ece829785c2819fd10b448b11a56a35676d3acf389fd814e358a33f7235edcd177808f964b939fc8c19ad43dc8cc4d9ee4bafe22bab80de56cdb229727aba6711d51379cdd974a8aa76c5b253b3d9e482ae0c1af938f37828893a1528e92207256f80e87a9380216050559c45588bd05dba05941b4f85317873fc6b363abc1f279d17614800f5caeab6511370c1f6c89cc4d7f5c0c7e909fd731a474017b5975f7e69c9be8aff44dfeb2379f9567414572256c429b448e0919b7b191a665fd55956bc85a7e42340357c7d2ea3f3d1eb36fff89391619343fefbac27725872fdb3925d7852798dec2e39e0cb4f37b053847d8af5ccd72b91055287a672e5ac31cc810b6b7044262b82f38735f7ed33c960be197919884a4c78e335c008377cafe51bfd129f2fa87ace35965190a4f3a85715ea971634a02e5ee5801572307ec71e9f9a48941f0697c6faad866861d73724dcfb474b84cbac8de6db44724b99e2e26a88a53a16c7151d2a2adce376f8a05123ef6a7cb6deba5bd7cfce7258bf05d3cf4a5b363a8102fafd5793cf7428bdd1f5261d36a09fdacfdc87b1720986c68a0a4ed75bc4c8c2a94893f6ec5a1080eb590aa0f990b860a44e75ef72616642c2883d1317c7a190032bff11d486692dd43f27bd672a3ff0a6798daf4e4a1516977192230a0fc62b68ab4e176a703e90369c82780f9a10703655df3026dbebc7e7cf71f51e5e9aa9169a713d3fa257c637601efdc54816c57a4c542172ccf1a3023654502082bb26f072cbfef4b966090d2200cad5b8b66a758805d10556c5885da3b19de52836ad08670ab7513e353554d6683d55946762cb0f7d85724009488ad07c7bfada028b3479c406ecdbdb03841a47a40ec3bc9f592aa61b72c84a7cc62994359b524223f7debd19d4fa9ec75da0d6c13a7ee5f763a5119e72a9e6393a471bc4072eb2f018445b4527e1de98a8f6978ab424ad3b936e468933aa493ad13c2461f0d2b6ad4146a233950896f8916af53b04fb034ebccc944b72ea6a5b9df5ba7e51552c58bc4d863f3b460df66dab8e00f15429b6cb14795404ad0c144cdced3374ab44360228115740cc58e9228d420594993732b40b14c064145530f53a743395c69fbd56b646d60eb875af54bfdbd16323c8b19676ad1b20eb710182aa01ebb9d3e91d1b4915b8f697229863749f9b25ff14eaf58505ea722dc11ac688e87b42f9584d80773990768645de53c84a7fbd009f6f07e2e08b561a154b941f53a094e0cf7eb894b871481c3848bac7f6f93f11914cb7951dd669f04174451b63bf8e9f5a33aa1dce3b97e9a0b08c8836a5b1f299edd56ecb8c0a7bc1a0733a95d280dcb918120e028273b52ba1f7ca390a48008ad8f1e60a644e730dc0ffdc6066c69195382a13917266f6c6f2fe94ed3e456d32c0360f86801c519b720ce5165030bace35cc430578f657492fcd22a44a21b35c072c51856e725f2d50ac45b07231fbc4a829d97fa8d425d5f5abba4aecf255eadc55b9fea2728d3208ed58a5471ad542e5237ce4511d0816853096d3487c5485e1f767fc9072d152a7f7e75b620c086bd4628fdb2b8069c996d0b6bd352e98654f1b51f6b70d5508f131ed9fa7d2e207fc3a2c17f576f7a9e926fbc58453a84ce65e4d14527249233626a78ccff58de2cb8a520ec09c065497b180075dbbba5a2ec0c23f41725bc233798543dea11f6a07b9e8445ad9afec117b48bd66229bf605055bcbe8727156af4a8fa9a456d40b3c7c5753d91c0e9bbd6880db618b129044c3635b59721965f6de8b4df166936aa16a7da404a9aaa5ab28e3e065bc620500f9d8dee701e632a2aa7f9d56a7045519632d43ff060ffda02db238d602ac64f93a016a1447c8e8d1cbdb092dee089f6d308d4fb6525f6bf54faf10d6b4613234c583a77b72a789fd8a64e33ece5654800393da555868d1c69b6e48a494fc862b9432464e72d39b80222a01a5606b7c28d61996397e8a7f997bd416aec4c81ed1dec86cd972c46f998a5c61469f30cde442799c4fbf2117d90998b3dd138758adf9784daf15776db9bba913e777769d391cf4fda4cc554708a46e541bbaa55916f17f84383671e7140adb8c80ec2fc2dd1e85972dae7de2805a63213bfd91bf22966ac61f720a6e5e1bd63d3c92768fc55651deb8ebe0dd24b09ac82b3a33b9f82d94891772190a6940357878a86c9978ccdd50f71d75afd19e680a950555815940a3d1e472a867f3547771fbaf786fdea8cef4f9252dea99d36867d14c0246ce8fbc05857274bb9dee8a4e45f0e20a031abc9e5cf391f1444daad11a125da42d240a50b35fbab8a40611724768b890a5d25835b049ff5faefafcae139655b4d990e807af386c0518c973108a1413d09d7052fa0b78f66f7311381eca86c3c1d61efd85df15c8abcac6f39b154b6c7c3887fbb1752d0fa2f79ae6b80223a9f69ee216d1705d9bd2a79bdde12f91ecbb687e9693fe6cf9c0d38f1769ff30f8377684f030b272618e04122ab027ee0a193639494d7896d4fbc29e15dcc32809a52bb26206b2526d3360fc72b66c19244bcffb5ab91fad19a0b554543fce4dc2c216e90065247245576340108a764c297059d8f4b3ec62862d463d7ca3c109267ee4cc86f7ca6f8ba8421659a9ebbe9f0bb3d19e8f9c2aaafb50fd74f089d555b77a62a2f65d4234bfee53d1b96debc3eea6ff034607c657f0163d409475b14f00e200039b8b5b3eddfe2241273e85bf76407b89e6346ffd50c364ab4addcbd353f0d0f8362472943118ce2e88f2b7f9f87b594a5b7b60fb08e4cd7cfa855ee2694e7ce8f642723b1e7f07a30d81c80dad5dbf8a2f1be05a2b1c630a62e184bfa1baef35be96721bffd63003e4b5b1f4db38ae3d3437efcbeee0e9cde1114ef21ebbf6274c66121dfbb73de326cef2c341da3ebc59c082d65b748d89face2e56c5efcfa1def67225731ee7ddf8b48a62eb384cc54696ed75ec285e615d1c0f586241b3d6a0c6727bbc087302df50a3221bbb57c90b40191079c9b9ca0aecd9bbfbac0427ef1253d4c774ce7b49a18e9872111d52aa038949793557561180ef20ed819122cb6372d125bcfbca71cf30e1fbf0461f0da31f6a1ff0513e6d57a8e4df8d0e59750b72fa70c948f1ed77bcb21f324274c30e963aac324f4facb331cd0367c82d991e27c43174d7c88be8f6e01f8763bc1405fbfc139df0ce3c1417cd8c712092820c728d7d8e76d6f663206302271be9277d9a6d9c4df9b7574dd01dc736f2b7f65a06273215e72810137202861936012cced21eb9500fe40e53faa081bed2233dff423c7dcc86ff3cde70e7657098fd93b95a722e3e60806274901845dc890a8835702f0efea18252bbebea13687d8977f4dc770d27e441bfe3520289d36b7c15b26745d84a50c8fbd4b94b9564f545f76f694e130e57fa5ee3305a11e6b7201641720fa5b23f4ee4974157b72d26a8b8fb1ba5937fd790242f57078a3cbaec3371724671b0504dd98ca681cc333e59af9a442cfeb7c1cdb120351a8d585ff68e8d135a4ffa34c8a60b1c8b8d63017a2b70441cdceed57e9bcc35d10728f5245767721267b7d1947dfc81197dfa6c0e7c9bdea4d68be48459ccef8bfb019a89634e7215d9775617afe4ecaa0d81744e27a4423cf42a9d1fab0f41099708d27486d172212b3fad6e59f666868d54118732b9049270d8fd971dd50214b6cb1b13910e723f011350389c5b91a9352e602a141d216181467cfcebe6b4c05bed19bfde674fce1145fe393908cfeec3bdc32d7e9d6a4d4e236180750a768e5e5b3bbb33553ce2b9c020e648cc733a96f283a5c7e1acea128c131b872428b90009c50ecc0472f7a1f6a7ed022bc085de6d7251023f5c71ce65c880ee3768f9248db7c6087b201e5b467e1af6d5225396946d4004cf130708644131bf3e49ddeca86a2b6db972c8d98d57fd3911dd07acc569028178ae02dde982af6aa50e3b03b1f29f6a0672e48ae804d9070081211e88c7eda17a36bf4f2a6d9e37146bf88298082194a03e9fbe22fa5d00c5e3358e222dbc7fce2e694056058c266a3b5d95cecaa3c412726b6349a78a49d9963bda24a580aa82393e7e8453cec51ad55021ff9e5103b472911b70f6948a37e519fc7119faebbd8871afb35e49ed897b337a92429559a50d64197985b33c6617830c4dbf8fb70d668cfcf215cc76508184ce13fc7a9527729d6f4dd416f0ebea55aca070dd767cded504a63fd7f7330114406d99bd58607293207f0709bdcac807557d55e59fc613834de1dae6da70c6d75da1e0c573037223bcf50eb2f14968346642c6ce99c02ac64e6a1c1191aaa1cd7717487e665b20af2a678775bccbf5c055d20944d60631d493cc588ac8567ed91f99ff82cc695424d53cc66702277ad7f79ab7029955826d5f8117cebc8c9650f7e327f98e8272108d27bab6e94ff4203c3de8364f122ca792a63cfecf4695ba771a5edd16b8726b701f3ad18074ddcca83990e37721d83d83b67c87bfc72dbade975ff53c0f72459d16a26b88d2ba506a77e65f1ee1f59d567c59afbeefd5a963e9f1662bd327faa2b197572adb9af92895e42ab8007301ab18e14362a1297621f07fe9fd9d72cb09e426f045b4449724ee1bdbd0f156e2c33894a4009d96eafd6d72980c6869404214df856bb18fdac3dc77ef263b34a8cea2ac38eb6ab92d09f21e41dbc2729456b03a22730ebe5ce0cf330d8cfe62049c4ea723cd99df8178bc7fda861f4230d77d77b5a5867e98de42f7db66a781b64ec5aad5aff98f72042be90417fe00c7155b3c152df31f52a03cd0b4172eee839d17e3c2ab690c6104429655ad9a72146f00e5f132b98a9450cb73081c5c4798ef3e7dac3c4b7467874541b0ba9d7229164df4067d125071f14a201216f0883b29b5cb854a75f45b235853eb403d70eafad0103b44b312ff8b6372bb0396b6794e9ae4163dbbfb8385ef47ddb2d6197ae9299ee212c3ab22a117631ec959c59218662e31a07cf99257dbbdf4057c2c6383777ab0f8939c3140da5e567b65de354736213f516e775d265f2635245a722b7cae24f5ff87602def81c039ddacfd35c256653e0e7e3347554bd0c317e172fc226ea6a8f0ab0b3c53591abb81659dd7065c6c0630d25440dfe781448df300c011b144667d81899a60fa9edfb0c9feeb28ad081fa6d0a8e2d2a6ac1df3cf725522f8b00ab5c0283c627bf62d6d9d7b45eda95028e632d5ef501f7e51ab861fd0ba245f39be698a6f00adfa6c532988c9c742aa775ae69a38bf905ecb4ef2722b8a3c743010ec710f181b0e8f747925ca41f7b32d199a635021fe5927ec367225d2cfe6fe11e29e3219ae930f0ac7803d4f54eee2d70ea7634ac0e2d328582f5af5592a4f2b2a7129a70a4d8f3724c8d79c6f82aaed16dd0b85fa37a606de1f0608e850d9ae6913eb7634af1bf55f93d4a7a0c3fc94f2b9c6862542e0067c723e26044b6023ea14da9f286d97fe490cae7c905151a8b09a5d4f5b3e5b8f0872082b100b3ae388a0be0efb84ff59b6c63633e494df5df6bbb865b6e8a5a79072cda3709b8d18702ccfabd304c02134af820c6d8a754f283ccfb73a067241607228e6b8cbf0153294f8b161df3531f8f67a6fc1e07686698ae560afcc2b726f721c87a3a33cfc33db1be166e062963772ff3df7d72f2a529dacde2fb6da01f772dd2827d431f0fa17bdbe86c6768050fb89acb99317ef30625e2a45b363ab1f2fcb52a32da7ac1ddb523ffa1eb1dfce74670d15d0960ce5478f0d4c3453ff7265d0401aa5ce1fca5da596bff112d1ecc7a4f024412c9a3bf7ae367e5c8c6019379af1e0d0c592bf23e8d16f6200b5bbca17d5a2683fb0876a29fa415327210a72e1e23b7b1dd46a3429166a84e943e0107a67eb85cb49e65ec09066c9a8be2a106ba5e02986f631edcd86f2f848b06723efb06a7277bed5cf9867c66e02008972a0cce6047ec38d0baa07f00cf7a7c84e0826c76c13fcf98446d22edd603aae4f9b24bd3b0925f9339ccfb88ed2abd51c7c90897d27cf52c86c5da7e327a4c63b70451681a6392cdb9dffe552c117e5a7bcb2c8cd496fde500e52b934ac41cb724ac7e98c773c2a7e5317a21504bab58bf8198c0ef3b07fe60222e8d471ba2272af6a1518f74932047de1a65b9542be6ea4356983c2d6e1e9699debf5391b60025abf724e71c0347175164c25f4c59d4cf6376e202ea13ca969870a220bd640108ee9ea8ca4dc6c0106993a8d77ac7025303220bdbbb51cd134f50bcb55f41d723ad0bd2f2740841c3d7d5c5485ff46724083e08776405700e43ed9509c2fa555a0748a1189f0b475057a06faa46be5d9ce8e44a34719d6425fe8adaef8ff0472f20b1335b09c13630ca3958491f87abcec9e3051bad23033f2dc6a82bb688672526b0fd6d74049db282add0f74ccd073a529b6ed5d04e63e6dfd6b6c001fa2472895040b75c01b79d7cabe5dfe64100a6da57c05fdfb48d4f73fde690d747d50203f357a090882fb85b2bee1bbaa1fe0d3db559e5f2f86200caccf6bd62fa2193bf5343c4178e1f7ad952b5d9a422a77ca7ef16e2ce02e5429fb1fc3c9a13a59ac5af8021b4c70e898d4174a0ad3659cf39ae4870d8f6f18aad333e5ad0163638514f28e93a17870c556834515de9f8a1edb28c679da6bb607e0ecf2e13a3348caaba3973b40a93ebf9d7a40d055ff7fde33be5bf74f61a65a2609d6e6f28b724cdf94eb23cc2b717563645ba78c1ff10385485c26372bd7d910baff86c3711dc7867e5276807d5781f397de2d4d658d40d020b76a0e8854c68d6525f5739b5846002e951fc3a71d1541ca7b47f31dbfeaa63bdc8b3dafb883d2dc88d4a78d72a2f4f960edc7cbccb48c2a9b8ca5db8de1d4840768578c46fbf06b5d93d0b2716e795b9fe6815f47efdaab1573023de108dc9100af18dd2f94d8d81fbeffa67281abac2d632e90ecbb51c0aaa0851e5d83c103c31932194833dfec13138e2b337bffd4ae71d16e727daba666e2139cb4a9d2126c8dc1c43111c444fb0bc51772e916ec6db63eb9387cfa3d04579ef09b8ed7ee0dec950e3dbf39ba37eccc84729ea75b9de16ec0deac77d50039a38267c9549264a9c0cfe04b7dc76fc93bec333c77b2ad73ad4bc2d3e8b3ffc977bea74e8f8e8b45613ebac45113c383106a163c7697c0593ef73b369b143ed556cabb364b67ef2fbe9a195a339adbe13c4872a96e82fe321213dac4fa135b9d0ce0ff6e6fc7afd25c85e15077e70d34128f72ffa224bf388fb3c3687bcd384d3252cda81b6986945e9ffcdf3c5f1957d2dd7257605e7f0a3fbbcfdc09542b4ae9387637cea03ea9342d8dd803f94d43b4fc721fb2e8399c776a07f7248bf193511128d83dabc370ef8a875a581ebab8f62672f0f89f38d970d4297de556e284ff2de76aa32f636747066ff06b4fc81187a872a94ac784261d5240cfa08007a613445e4b034ec1a53a360d4a46cf021c68f372b1146f3e88c17286aafa2b7583aa35ba3daa61e6fa504ba70df22cfc0203563d0a6529bc9130cf2cfdac0c7f8c010d95d9b19f1a0081293404d8d5e5f846f03b11a4c187293abf8c2d56577dc96feb46e488e21dadaf05bb937dc4a44229db72d79754fe2da5e390886b7ee2263044e144daae98b43274e075daf63771755172f1a6967ad91c0ecf2be69f3a9551573f749bc150f9acbe4bd879472013055b7201ce6de581847c8b0f5a2f988bf900239f12ed37402439fb09d5d791a0b04a3026d5782f6c4f5bf7b1308927087428d4ebf0e12d1044afdcc2106d15f056c57244104f5e25303a757cad21ab0d186bb049fce2f91dde5c858a2f90cfbaa8d672918b7c81f226f09735027cd49055a1e8e3ce10c25e5c7fa9bf4a78a355826d567bbff60df007de86f25f76c9231c5d3986c4f3e4ed5d153b9f1b64175d686072f2e72102049a307cd89f5a9b3392593c57f03e5ac73b97cf294656fd01e8c15ae87c47859b607901638b78aeb782450320fa7d7cbd8651e62cd315369c33117245c5f79e05e80238ce1cd8e68f1dc85690c001ec15bb6481b3952d3ec7a0ad0eee0404b61cada02b0b5d925eec9a989d76ee98c3fcfbb7c0a11e4aad5d6c2c60f8eca428831d5e1ef47afa5cec5238c2687a16f94a12f66e3639294251fcae725efbfebb52de19d601cb8a759ca41ee58f8339dc5a826ad0883ca99e9c6b7561f66a0944118430307932885ae3b20397397fb25af63341b8d49ce3c0c0937450840571ff85fc18ef7eac94a3b1a19612f5cbd7f9a0b1f3904892eca4a30a61215e95f20c57220f2d6fd5d36fb140007ded0d74601b206782ab1581ce3a5a4e7286b58fa6e6ea6f9fe771391fffe6b1123e922f05913d2bf45f29bca1afc1d33c961448c57c5186df56958bba7a66659f40b43e678a4b5dfdbd35d8f435483772a8a5138d8ed03b9abe5f5a6e9e683af68d764e4b21256bade8bae5d5da9dae72e02cab43a6a6ad662fd1069db3ebb85432c9cb56b76e896ac8512b0f06f2e73c2a6b85c5dee953e2076e892971f7003007e5dc641c0930df714c0dbc4f32307220b5e070ab840df02b5eccc169f84bc98ff31891356206ee8359fdd361c62034c9c6f9a99e6f0f5b23c923258dc6cd91c5f460dcb987923eb431202afab1ad724c4a7a4f607d4708de1ceee72ccdb81d64be30b0d96e63752eb01c9d14bab272b9b73a69df8fc4fca247c40954eb3c429034a89b7926469e560bc64418ea2a06c08ad6268a05757cef55b9fc08dacde9df4773c9c00734ab957c7a236bb44d7218e6fea20e96922a2729ff85aa24c9619f2b06675948bcda89cac0a91ce5e4720eecdcf4651f059628a1693141d740440017e129d17617d8bd91634464d4ef2c944d4ab2e4cfb714b6d4049b592589c6bb2a75ff96422290dca484672b289337fc4dbbf0391c435633fb74103e106d190ed521d84ea398659281b84fab7d622f8219bc12a5c637eff21a413590ec7d81bc58f6b9a3b4969039b10055eef6f172dbb02c4f8682a3c3916264253cb8d56916dc86df4c32261041028dadaf87c37204ec11040f365725ce3207a82da934dbeed23703c5f896c3fd06a7c5a095967279c41c5a682fe2ee2f1e2c54a97dc4892b25af316574ec46bea524c0da9c1b7278d69206dbad2b6e0c5a30617c51855bd52bc6eb941346b7af5103644d1fc4519744bc518fd09725cb1817437fd9f525f0f4f82dbd8d07074841e4e689fcf1175a88ea8deb0149ecd421808d9a0d53bcf756703a676264e0af447a9c5e87227239cf07248052524f718ca772c886dc552b76c156dd1106128eeea74cdf8f272bb3e905831405392a5f1768b3371022024ddcffc1ccbeee34ddc52aa17d1d5d488f140a956475678b8fa056a9b6850102aa70f9d16eb23b2c34a27615d02e840559f90044feaed11d967f1e82e1c9febbe24e386035590c5e9bc22650278ee772299bac5d0a7fd077a64dfce8cc6adb4c96a4dbc62fd269b1f39601498409e572b4b5b47822a3683b65b485b2d8e288edc966308f7f0c76d41c643b74038b4b721c878fd003ef6cc592f8c87bb6efdbfd112a3d1fc16f212e8f0f3d0c61fd78728906e8212bf9974620b740d1d9ee4ede03af965048af7f3a12f12496a127bf72f0484ff679b3c5db2a79c79538208d03c2e5f160272d459c7db202102ceae049e1157644421d6031c7e59901716cd6ed37323b100cbc43b2b5b39de4bd505672d1956236e0c27b14294efe86a2bb085ebdd2036ee0b33e8d42bc60c760b0f32b833b8a4092fa0cf034054101bc09710c6cdf00d7d88445d7bb0ae023b22c93332e9072cd04dcf812e67206a48cce3d81d6525c91aad70e73527253b4b5a29a4a7bd93bb49cdb1232231a671855c77ac4b6ebe4f34c07f582b50387de00b7f1725bfa5f06dd6785b107c949a249312edafab8d99ba5b0fe1088e7d04b2a9ebe577163c3d3a9fc137ff7447524ab40d3c6c63331cb3acc3ee996eacf8010f0c45f259c1163a6304b1fbc46287d399b9aeb6843ecfaf284424e97e7fa8246675d240f74d1ff0927292e34a25f4c515383dc6446f6c50ce4b798c4815039c4864372091438faec0cdab56f37197c22403d0b39be0e9be49abe43871847ee164040728eafe7b32b5d58dfe2c93a9de3422c72864c7addf378ade544eda17df92ad272095828a94140ce3cd0bac3fd056eda93522150d4c47372d8d7773491b4527e673075067c1199aa533b646786858ec1453d6566da537d73e35ece3c00f750003cecb9dc1940200040a87067915e2a090ba48cc547ce61c3ea05bbd664f9a298723e086374e940042feae8eccff387a7eb7d810d5b88856349cebc63cdce744a72aa5575e9593c4855aa1c4c40626c61fa689aa2dfccf45a2ccf2b1a54b262d471cd2c20946ada0f7c526ecaaa8848bd7bd23f84fe0e65a1fdd8786c7a17f66b4d9f27406b306ca4aa9eaad63762668c9fae92db12e36e02ec5cb5ed945ef9ec679f00318a89e42537c6f149ad9375f6f4a79fb763cc984cb50127d242eb636206510a294b842f16501bfaea355361183359e160c2227ab076fc75fb6b3bc73f71e01f222d87cc814185450fa5f09f0ccbe0fa456dff9f55c6db41f7e121cd530ab3d56e8725a30c54724d5073345fada67721bc3caa661ad2a048a987143edd05e17a561a4f1ed7187251d0531d1c896a53931de1404fd19bc1143c42c8c7ce72322a56df2727ccf524d53be8bf740546c25367b306bfae036819fa3d1ff1dc72e4fb1df88f9f0886ad2d2f7ee4a6742e099302b3cdfea72f68f6a5ccf61aae513cd56a693b76d74ca3ce15fb3a52cf44bdb4823e4f10012d67baa9635ebd5e723ef557bfa345c888bf4c8c11f715c341d6c5abf5b36fdb6447a7af08e276b072c2c02d353fbaec738e8ff6de89fe198c6efb7ec08d4701893579242fb6f226725d0c17621b2042501ac6460b35fb8d64434a75d281f521caff8853eb960f85729fbe1ed94e4138f2edc7daa874a459ff326bff1aa206cdbbdf7124be1351406b4dfab410c8f99370a6d08180da86059152301eae48f32ced1a4bbc21b5d5d71df4eab1688f0d2c0db95b506bec227b632c2cf4a0553ee50b942e86b61471382803ca46ac9dc6c534f1e5ba0c784df0e6e2092b68ec8a8cad2303463add03e972a57f8eddbff533f8dc3f0e777409a4cc7a207ca2c283e168eb383b05a6e06c72f1b3bd7e923970b84fe0ab2d76d8b18f572613d24a66e16ba5668b0c5779ac728d59919a3b11050aec3b52546eb6e89075f07817813e998aa284b9c0737a7d72b9cfbdd0963b3cd6d31e355718e4de288b0462376cb81e9047fc2bf959951872f311a1fa883ea52616ee3f8514e59d63b6dc4cc5a0defaa5a4187e51ca48544eac3c2a3dc09f0f1c19d03f20f54aa065e99c517558880157195e25b81b1c8947735bd94151fbec07629841fcb5581839431014fdfe4d24b553bb74c5036249359948f226fc2606f50081621b319f77c6df5ec9f74a6c0b08158d3c7fe8de0e28a54296bb3b855052e392b40abfda0fcdfcd04f837a21bc370cf00e9c8e6aa271fd47f76cd185f32df8a6d55380725f1f30e19c6d6e1dc0212e7a16bd44c505727aaa536b2c7298cef61c895ff7902ed3d2afabbe825f54d6d61e4bcb288f9b2f19456c812dcfdb15e9efa47aa8e799401a5baaa81c77edd33245198d3c81833d486ca0bd09a22db051d1b540c2dbe78d47ecc1e9f661af6df15df543a1b1bc35cb64ab7e98943ce1255d3ac7cee97ec7e1e04bac1b1c6d87cc068be43306b6676f2ba9de3953e2b316b556e67c66ac52f324ae6bcf298da8723775612f657f72d7ecbd9d8e0f1acfa79b3dd109e4bf57a6cfc4f23d8406316cfb5e4444113829c72b17c06d4a06bc0b84a69a8d6df7e6af6feedab27fe1caee2f06e309ffdd720316fdceb0dbd34a8682e1f6a86b5bf813911c064d3b90f19c273b51053fef12e9d1525c7972869eeef3593b956e068a8bb7a92772c29ab6b165a7c88a600d7222ce10fb1ef688f1ab5f9bd1fada97d30319c41620ba4381f0f2179559782d6cb70e28e029363722d762cfa3102905e8d08d2fe7701990aed13957839501f43b450789f9103367fa8028856a45bde673eae307534620283baa528da9cdb7be00f18d7549921e9afe54f76f57a2594fd3f828ed3bce8f2ef5f2accd964c7a8b72fa6c9a139abd24d1fb69759ba84ada75828cae47936c1e99584da10ab4d0006db9a425945ab574886fe9f6bb2e9b6dd0d6089111785f8e303e2353d752240d431dac0f569196e155734007716080775965ad69896bbd2a253b9c34e24b5b7a7268869faf90f37a3f8b618b2a3d0e79b9c91b6a74e59f3a8f4898d84ffb4f987210877ea94c6dc1b58b0cdc392d0b1580c01a98c37b563e8a8d03506a95e8ba7226f2bc24fe675febdf4e8d730097ce0435ad0098a6d407d587df9dcef45995442dd642ce2d87140d50c5ec25ac183a449a6ea5eeeb6faf8823b2cfb7651ff003176591f373f6f1c79f3ca898355aa3eadd6d2fc0d504329f8e85b53d7b5cd368d1cce7ca5f37da2dfc3a89aa67fb48a44f138eb5849fed36e08ba00bd8508b722b2cc64b01c0838fac2c622f908ae9bc516178aee33da53c8d487114c83a6b53f7d2db042cf912a6668b04e15b49796631a2ac2eb240680e8b4d8934f3686672713e09ac1d2a98f58eb03cda6d8a54d1a1765f4e8a156e5ce9f691a9e32c1e72f2b09949669390bbf80ccc8d4302bc8003829993eb2d824eb7ccefbb1f1551321ec44fc3beee802e8b76be48c933e9f77c874f35250857afcb669d268add6b72400e58d1607a118a546b0b56d117bfe847bd543a6d06558e59081954d4ce54725789921bd17a7888a86e2fbbfdbe9a92426c1c389ac0d1ba5e0f61f15ef1aa0d1c4e633d9ed65eac048da12f27a45988742d655ea9f9a364c46de3d8fdb2d07212b48dadcde7aabad7e0c4a09ecc37437c7d09197487ba99a6a1ac623214a15b8249a80d034b513e4e7349eaf6f83de2643314c105c4c7a9affe6cc1d9a14f727957bef83d784aabad5e4f49a813720b894403812d48bc8df17c72af5e1e6609ffd5c219d85d1680f9b80af4477f834f0d1ce7b647fd8d3dc8e30e93f722c46b68e08f95aa283bdc8923001c1494092980d7e3bf3be6dd4cbab1798c79663d6c71ae711a9fdfb6223a99700c3c1fef3425396a8f8faa488d6d91e0114af14d4d550054d021b2857feb4f6f13e4b49774b65cdf35f8d735ce5685a503b524ec4cc0dba3177d652e7e72661347cc463b6506737f3f4a436c73cee28e910c437a39e29cf24ffe637a78e49babff91f8f41e5428964a5787bb12bd0e63f54fc90f72d31ad4a10abafb5439232951635123be879ecd8f6ea2f351fce594c0fb7d5172f9931d30bad42cd6faa404735a5e179adfebcd71a03521a543aff6ad35c24304811e253792eaa71926b34488a268551ca9a834a632acccea07a011f1da82ff72caf7a5a93b632f7bcf314c6378e3b70c2c2077e5bd383e8d02949669f2ef8a518a9903e36a2967777915ea2f00da08e0378abb8feb7960478a33abfc58d92d7259a54122825258e4cee37979fdbdb3bd60d00711eceddaa7e341c45f1d418b729acfde132ffb69759c9296f7d32eb72c212e2e1fd8c9f1edf43070e5c2fd5e72517a3137420748885c86428e97f478bce0b5022a9d4cfebff9056d85a81eeb1f1f4ce4769ea72774ed80e9f26bc29402e486bf57b1a1c4d518d747dc42ad4772cd7ebc1212adb6fa1139295971298e122f4747a8fca80fa5ae1b51c8cb0a713da289150c0152b12494e00ab4f80244eb6608748d16a1e6e20fcfd2367be16772f253cb3cbfd87b69cccb0bcdb6dd5b3acbd7bd38f9c21cca47f8d0012079bb72ee19f30ee58bedc962faab7e303ea09d525f778dc81d45ed1d177e4f35a5a2728cb4eb60bc6b85f17bff0696752c784e7292424c38026fe2742b29ba44e47e726d5f3d68b3792e186bce37181b58661171f13c881629c5298facf52a5388bf29b49fff10f787cebf587c3cb74b586a1c59bf34e5341444f28f86755c6236c772b69c9cf4856fa9269230668951bc29763187cacfa2d1a73dd008e22794681572dc819533e32bc9dc7badcc463cd7d4573517915cebb15c6bc7a17fcdb09ae272c39ca9f5f541bfd3a65cd53b5a587e1e0652ece9457ed02d822a5837265f7172ab9a9756dead760a6f593349f86045c8bb28cbf344d4f8a7c0817fd46d6fdc727840b8f99335ab43338861bff5225c6a7f30abd7c20197690f0c9cb0f592c616e438c1d94f2cce1ba4feca765ed34931ec1b4d52dbdd83fb438b75e9e50ca172fd8ed660ed2b0fb68a025aeba95c4375afd1230bdb035ec9a536bc83bb94aa3ed64fcaa53eb824e8ca11f6b253a60d73f605794ae84c147c48d9cffd3f7839722af313243346bbb2ddb6525f57af837522407deda0b4c08d600e7cad8880a272d7daab39514998f42431262cad6551dea7f0bab5b89c419cfc96a7a92d57b82516dbba7360718056e5b38bd41dd75f614b3d70adfb8d1d27188b549fbc40782fddbc4ac7ae4c20b937425d2973613e408aa456bff51628b632493af115edc531a8aff67008469da1a662120fd25145ad7d950bd2e53bb49b8679baabfd0673729f5192d22cab4ae5ba3c7a7b39fff687e96bde24906790c70369a1ddcec67d728931ef0a5b57781899bc39bfc29ebedf6698645682fe46b9ed4541f207e716003e08d2797d1cfcf1fdf307d2eca35fe52c1caa9f6e6c4562b31dd2348c782872bbbcdc252a08cc3502fdb90bbd0ab4e463009a699bbd3a9eb099ad550c89d07294ca580b37abd88a5ceda6f92a4752e3b31a736aff8f6a476100100189c5a605700bad678481ad93eb173b51e9f9729c3ac385b574452e1ec5aaecbfd4fee472aacdbd8f6a3d564c4ca3c1f9387d237500c9a86428791cb10b76201cbe8f9872587bb652bde9c6be0c5237c09e897db1857efddce9dbe58c783d01557ac62972aefeb9c5a74f0bfb9be1a1ca5604b413b4d9b8a5ada20361b561c30ec2423a6cbe9511cae6ddc4dfd9e0c6418a47e8e7ba0bd2a12e0b71b198cc939d758dfb2bbd1a9b81aed921b9091383aa41de8664879d2b30aa3cf63817655429856ed572fa538c8f6264f204887bf490ec070fcdc498051d2c790c234e2b2c44f7083b7222386d35d0dbcc0022e50c6f8f37039a6e6f617fed74fc0f06ba513e0ba5ac7255a6e22cd76ae6ee8c71b70c249da4cf2fee47c124cf3c20144bd7c76ed4fd5dff5ddc5d3a0e375eab5dd4757b6d59d03a6369988d4e5a245f880916926f3e72a8d26ae33de578c4910afc6c82fc71e484a3236c81c10d18da17fb5223343572958628987e9808350807db6f244c60c15422304e914a070effa5c459612c81725774c186960471f66b76baf2a47941d90d97a8116d0fe719ce503e3d35d3cc6d72fc53e4299b513578101696c70bf96818ec06a28fb10cd9ffb20bc8cd5aeb72f1928dedf8ba9c4c1e8f4f8475150177e2bc3ba0062f6ca72e1e434d4785386f0d4789474dd9f1204546dbe6f15ddbd5a8fb9f0869c18273ae3b1161b4c5b172244d43c276f86509f7cde7cce588ae3f4d57465d3269df28b628dede740f1769d7f57553da0cd7e4bf14b3c41ffa895b9e2bab828d03835f6e06a0384204fa72cdda50d43464dfba1821cce730c1644dd4cf4d878edb98cb700f03b3eeac6172913542f23e4a699f1b7be5aa505966f4595550d0de8ab114fa5e6da9726a1072c87f5748834a754fb8b51bd17c8f065a8c9797a663105040176abdc4380fd272ac11c3289f0afb4f737543b12f3e8484e48dfa18ba5292ee968c74159ad2ad3027fc6cc54174971c0f71b838ebe5332a831359e2f34a358d7ce05c809f219572c46283fd59c45b259cdd1ae8fcc5055d0c3e5ee5ab466a06b4de2534f4b96e7271d49b901e32f2eba2f5c3e225ec73cc6edbff96d592eb0893515e94d70ab24d321f9a02e6ea58c0e6b077744a2126b2be147a14059d1528f528681fb10f7805f92449846211b2854be20e74efeaa315dc8ea1ea3543a4e0e6ed7c2420c83972e0e206fd75436bdc9965f602d4b62592980471e9203ed2c2f62cdf25c9e7e909e5fd1304beec6b541bb1b8df7743be8808add614bc21713f0c44bff633ce396205ed3167034d6369aff05b5ebe0a28eb7140792a2024e5171ed78900b1d6c86ee9f73487e0a5562adc4d52e8106a6ded78917fde783c16f31b424c12315b885810b731e65d2f1309ffe9b730abd9512b058719fcff60e437b0be563913dd80720d31a2dbf3f1bad0f915c4ef44632eac0dae224218778287ee66abd3dd1756087f923351e840ae1e63da74caea53b0d34ca858daea0842f7aa56799a70048c4eb7232f9ee8a85f0ccf32f843a2ab26ccd0330a8988ce8a471e9f9f78b4a4b85e1605d2c4e199fbfbcf22a4f2d6293cdc44d432c61342f13d6194854a1325c57200b871acab9819baf63cfe2b021b03494e24198afd782ad402677fdfd87b6972623755c7b18d6a8b1b4b79f5178719e1197ca3af9f3569c7e93083b03e55407200e6047b4cc4cef21c8c22b6fcdf9b77ec75b792ca75276c4ea4c196952bbc11b9c3573ca7a84047edc3df0e3b91c586dc31e13fbad78051277848bffb578d231a2726edfc41725ddfd7dc76b5a4d6161657c6dc387271e5886fefd23c9fa426a8e26ca90409e4a76b82472c184e2d53d4716c46696df569f5c917a310aa4f5c16674b3e72c557c3e885fed0ad8f2499514cf08d567b8be4732bf5849b6b155dd7aade0137da08f05996323fbe377adf6e793c5f34758c544fa41d68e36af9724a0ef195a6661895506c1cf9dd86d2a7adf79d8cb39530d7d7918fbf95fd8272fb241ffe402936c4033eeda6b597564ba28cfd823557bdba45f44c5fb033b1729829dfb08f3d4315cd31731bd63f118c6870c201e970cad01b5ec1afbc33c903d9607ffd2851569a8235b2d2891563c6fe3b39af5929f5c1dd1c26dc123ea472b131d5e787c8e4bc6412ee88c212b2d5f174a140875b553b6d98256cf0b19472cf1911d850c045a7faa662839b3d0cfb4e49e191b754d8d4207b8e82e4ccef720839c4bbd9f1deba03a9cfffa6efd0d8a0ee0035c1b5595cb1b1b2f948541e62930ecd50a01c641ebe2dbc941296a59c5cc3645b7b4b9cda1c1bc070ee5ec27266e6e46637e0ae83874c6d150fdbc2cd11972a7e5b2e09e0bd2eb37fe89e8472e3e8933a2a4e04a934f9b69e9e3b12b382336ba39fde1d1ae6247e7f87f949725c22f4fd39100f40ec580930f171527f9ba09224279a8d448b68ea85a99366487ce72d539a89c0ad798b9f3fc08e26158503e4c8b3149165b5c17464ba25457245230fcf6a94c546b008a37bc02696861827b1995f10952720ef780f765a4572d984d2891092bfac9864decaa4b50c10e8c82057058eba1f04a933f96cd536727015d64c51e1397d370b45faa9723c9b74026ea54a1d0770ae55fd31861868722d446c885511490813ff568094f446840050a94e6c28ffd2c01ef82a73802772d0f2a949dfe64cade48fef7e3c87d4c9fcab6acded88cecce848e2ee86be1d7264388fd18cc50fa90450b65b31385add54cc72692c4d0682cd7ee19173351f62bce7d111ff639cf489ec15456ecdb495b2c6f1358e27445a947ced15541b011873d32a8d777c00cf340d1965f299841aaf5a49661d615fa411572eb3b58fd672f17628c735768fd03128ba2c73658885f3360b6b19f30bb65881b0b7e65e9c721d8612f1f8a61b66896ce26e3799ca55576f96344ef9e88b3237fd019753db728b6af281968afabb384ae0a8058a098536e4997cf5879d354d05bc89fff49a72853996e2c6bc94323e70de66f3f60a0a14b4e36c0a07ac201162dae0e667030aeb4b8b4a8c6016f3676517b998d63410c38107b3fd9ae126cce80fa6227c01194231a2d46472ba3bf92861ddb1f367d92b73b68f2143e2b2ed3f89e31ce5cf098df33d3ba8e058057f00d0f0a0abde11eff9c503db3272763b02e5930f83b4720a6e5b200b7fa2891c454156e811787e63d69aa0a852681ad085e38fd5104a72fed35693ae4c7b8cefd4b50624c6a1f2f3925dc6bf37fce88376b89fb7523d72b65383e3b87c0cfbdee46ff791e95460a5de68bb0cf61dfa3d76a262940a0272e930a918598107d3dfe0c0c0c215c8e5243018a6eb674f2aaf516474f220825109e820ca0399643a4ce40e98f768e565261dd4f92ed6fc8f27d99f6226e0283ffa7ac59eeaf27bfb9085578ae02ef4239835e0bd86d8a8270c4126cd45cd490eba0ddd3b565e50fdc5085e82683ddced2e2ce331780e3f8ae745efae6f15b12dbe77f42b6150caa288369afcd571ed8e2e1119da42596e9ba2253e902b11ae23fcbfe758bb8796c28e00b2b79117d07a60f76a09d2054eba69952b2f810d9b724274fb369f064474004ee32db891f52a5a7bab9b307e6ee4491d3cde4db68b3f490ba905e7926c0a52146906395738f434f2cdb41336dcefc01544164ac0d472d1722559201518b54ce88612a39a154c0cb3b4c77bcba3732e9e94cca2ebcf72cb76292265a45af799b747e26dbe63257bdab13f4cc5d4869e50697d062ee8343338daf08fec7e2a2d43fc06a8c2a588ac39b7e2a902d8047f4383903f6908724ec7ba6d0811697f1b7bdb5d517277ea2396acdf80b4ee3d53a1ea5aa3fd497202e5926166010a804f7c6ec76bdc3e7dbcbddf40bd643f20cf649de63c0887726011f2cabcb5180f1a40d914ba9fbed09bc20ada90e67bba1c0fb1b91d114d72fc8f199733b36c6d9e281120c417462734cb61fc3d5374d5445870167976534d9c80004ec113dbcd7ba1b2e93be42654ced9b26594c175df143dd50c401319521e95d7686b4590753fe0a76b65dc4021a4652a5df13509801fac993b153e660f76c3b1e6a6031cae5edecedba1b54134139843a7750a31224561b236a044207257a3277b6964132871b53dcfe902b46e9c624f27b22c4297413482349085b572eb7210070724ad25a6b34064c031c76c74ffead6e2a869486dc510cb831554714f73ab698b7bb3a3bc0bdcac0a23b4b46f795977bf70ba514556f98907d71b68efe654b94d03cecd02667514ab60dddee398fe95bf8df534904496d8087c32727652ba6893d4b30557c6a2fa1ec74b44c5673c36c18123dee589feaeebab67722914a5b23938fc10381ef077d4cfbc3012ed096cac4bb99c847c3048f543b74af915e01fb8cc7e1e88dc2e7a21a4fd3cf37626d6ad764d3c4dd173d2d220e172b3eed4087b09494a047db402e9a1b7116c7f6950cd8d7a40b6741a63ac6c9772a3c465beec9f1447b7ddbca1122590ab707357e7f0e61e3fbd39bdd10cb58263001aa00b29aeed7e7f056723fa26c192cdb108183730b8c95021b9cfb8d0fe72d3fedb96bd7080e836563b2340d081ba0ec74fbff78c1ee2ef3c55fe7740b572d1b269e297885ad4c86aba43c6d7ceda61f1c300a26c043db0950f73f343f75908c7310b43b1d1e430b0b45f5dcc7a7e1c4db7a38786a5ce67d1a29e2e4460728ced61637bc7285ee5fcc2db15597c4143c74ed160620fc056109f5acec6863d521eea8c26b3f87c7d0d4c86dcf49bada2875f13d83b0c7cd38ff7f8a70a5a723c1257ef2d137ef01de5e41aaf2dfb7332c3447e32e8cf399ea95d9236bf7772652373c85616fa1ae4fef9d21f970be28410e8814cb403491a24fb28dbd11c23152cba30db438bf805e2392dbf15b9e2cb3e56bb21b1726fc3132edaf8ce2372e0609fcee79798826ffe874a456573a603d6bc507568ba9c1dd7606a2da348527a115bd63631972da1223d2420ab4ac80680af7a33294e9cf7f12269b938fe56dae958366c40f610d9b48f1343576becabfe728816d41065f70d5b3347fad27297173ba6550ffa66d4075ee989b1966a35f19eb6288af4a130ea925327f41964215b593414c15a94c080f29b01bf0c05b508ae716c99085182c42fa4b8014517b2673eb3d9f2d748b5ae27fbd1a37ea5f5b37089b77b23221bbbdac778f1f9720fcd6b8c8d962467b5885cb6ecb234208083cb1854082211eefc74d38bee5a0d733047d38fc0afae82c58712ac4e0238cbc1bbddd5ed79b5522f10dc21641772f1359c593f11b111ed01d9f72a8e1eef2936ae0fcbbd676500300f627918a327c1313fbc8cf32fc4b613bab97a9701740d2b01ca692a5d7a39cfd548c2cdb47291caff4fee74ac274a5637ab90a12c013865dc6a012224e6a4082099c11d8d614b77d9aa07db18baeff36b64f0c0372a8ab0bb16bec90a58a30004e01f3cd10e36af183a563383d49ef6c57e166093d4908be1ce29db3f251dbc84a3f1161019bd3e188fe19f15a31be3ed6d2a3425cc730ccff925ed22f7217a92e875b79d3e44c2061b426de0889ad5db9a9f8589f8b6b94e0f6e04729d8aff0361f22f161450e17d45fc6eb0673f8a7b68ab387ef1a3893f1b04e0138f6dca99d49f9fdc6dc622ce631a5c1755200f0ced473ae872a2b4726332179e366726bbe066cf5c4a842f5729c46ab0ccca096aab883704847061ff19f59356dc058e2fa994980b221e3db5b26e6944590ae1a614c62caf8c67d577b09410db238277b24906b79f7264ae1290bae235e25c79981aeb8e84809c530916e62df3e8017f1aeaeb421439298953c675775dbc6a5e6ea3c072f690c7c7529690752fb22dbbf8ac1f1b01720743da1156cf776fee3feabf8a9d178cbf883275b73f68155eda08b13472892e339792c28d863d8e3e7a9c87b8f8f2cc50f3ffb26794f3484c145561d0460972e26849573ae706f016db3935a34a7f1b46723406c09871cf06b1309d1446f072b00eba21b4e8920c1d977b8ba3388518bb649ad9d7e71545c911ef5b9b58537283e69115f16967af1e2a276df1a3a3206a3b1d1f81045b91defff95c3327e072593b921c329753e3d387a6613f8e39ce80f620902b7aea1d0910d0d4f2b5677237ec91616480250f42126f97d93393a3eff2e469b105551f52cad686c6d61472422e9554afdf17c30b76a2bbbd916db370fac2cc1a1196a098203f8b0673000381fc58e46fdc2c0c89e49a09785550dcda28fc095842c887bd87600152e8c3503bc38f5c5b0a1a41571d139e801c18c39cdb78a0a199526dc847014e57b04a6775b7b3e264564e8a812e4219f0c257a70479d4e8dba367a7176082e8ec92df720a783f6e08332e03dbf05969ff79c307b9873eccddf96a826bc57bdea7fc9254a8f6c741f3da63427afc9a56cf33e837ecd73c5a20fbd3b2ec3568effb9c4a72eadfd7ee4a2e53ef4984ac9cd908b8d870edd144e593482bb0e09937c6086d72944b7ddb6a1029a963b5f98448462648eb57cc0c109f0649d7d9945874035672059c9c430bc5e6bac1ce472a897c95f53789761f754735026b45680428b02d69a2b3795bcfb6cfdb52b3e451d35d2202e3e63478d15d1d9047c590cf1970876d5006cb3bc6dc38631b8f9cd5f285944e5c04bad52ee4aec0b0b59292f4570c72ec167fdc420c757ce942e42b5975612d0873ca318cde7b7ab0a6c14d09dfb372abc66309fa19104233757a825653b889ceddf8ef23f1ce5cdc01a03d21ce3972cd293a3abf41107da7139c75452ef088f0ac43aeb52f91294dd32be544100e277773f13883e481e9afacc5969301e9dee08630fad37c51e1f5d83f539a264c43800a2e360cbca6dd57d81d833ef29a80ea64785ae00708023cc9bf8f15db3c727a5db1271ef55b01a5e4b0ad4d5d9d0767f0ff4169824fae53a52ccba53770726a80dd76f86d69eff53f323a9d4170e770ee171c480dfb88732473b7f1595b05d24c778c828930ce952a2a4a1393dcfd3aaba1d95a8200cd9f9c0e13a1f15650b7d6ab6797787caec635ed6f6adac3293e02a8dc3cd6bb9f57f3fa6755ea4403901beb1ae7505e882d780f6e3b8dde1b2f2a2b979ee4f6692af245565d388e7222f86da89a21fb2395a24aac6947ee5d1c3103de27898ef73c1964d5319db07249ca2d935afe1fb650a74df6c50457e839bb1e6f5b76bfb0f25a33bc6587f4725ef6a6a3693662e33145952fa9d91df71b836db7d001a7d3fa1cda20993bf77272d013f3f63d9406f02ecdcb320538f053ddb4e4066d118a944126bd2ff049049c76dbb9ebb70460556b73017897c6551bfad5c76004a37420c377464db32972729383317c928c87ac936419aab14aea4894be029249b4d719bb6cdfe2c4cc6220ed37c920d88b1083be19ad6525949a6ade6f00d5ee745201e138daca761d7259be0a47e3ccf48ab31cebc1a439e875f622884f467fa354188bbec9fe9e46722e5f8664e9ca3ab6e31a094b7239e0b1fd40eb91a6de13213673ac3cd5e0cc72d4014e647b29b7d9aec5a732636023002a9c6febe07279fc02938db4a1607e7266cdd7e18f1e89dd08cefbd6269bb8c250717a348bf4ecab64c5f0afb21643725c3daa99f44df33d804145603fbcf7123c39801bd5a39143ef8f4db8533694476449c84432b6d755aa2e6568985813b356069f54c1e04e82e2af40c4c7445872a910294d9fd1a036d48013bf285b571aba0a84aff63446f52c45b3826d0def5a798b9e9743c2dc96fc5b163bc46b39605bce16b6cb809f7006886b7636ac6672a0efc2e9152e2fd2779cbd8612296d388982958abce99200c38e1ffe77611c2517317a8fd6dece0bc334d6bba0edcf3c1849bdf0122732ce98932c109de8f2726395e029f866573efa5d52dfc5cb1bd7799b39191298146054836c6dd331b172070e90139cd5e1cb1f251e7d321bf1d62f1307697dbfdd1009ba515030e47a722c073cf2cf5f77415d421c24083ff8b8e1560817add3efdab0c2f942dc76e85c3ed8e2640c0988c5c43051e43ffe8f09c94d7f3c7778ec5ed5c876bdbce98372881b4a836458801b6354a79c75c313e120d7ce0348ccdd522fdc13119ba69672d57856a186a6009fe81fba3a7464c004b2039a89d812e8c88a275802d74299727a1801032c0cf1233f78458c1d1ce42326362eeb8a8388d281ff326f146dd050683300238831c20c006de0999d18bcd122c478966b3cb355a91167a1164379161e1766a0a746856dc5eac8bf833b0ef4cb77beeee89d825422eb031d6bab6a7211b61ed464fbe51e126292dec5494bdf911f518fd4fcfddd57f4db309bdd1b0eb8f275496f20b15ac1145539072bffca98b3e6292a6e71ea5fc7bb7ddbcab538ed8ba5374dbe4cab04bda3d4db941458065d3984e379d7f202e53094ee39071abd5f878a25c8dd22540152cbb647fc46ace906cf8d2483b2aa0af3fa8122e6722a5531508ec5ecd8800d0968ac60f7799e90bd6ebe74e30492f01ea58eeeb67294b2274b126cb7e2c7f373cc204aa08d51219d35e72885451fb3454bda9d033ec9b38f8e95feab5e775469e766ab6cc07d07c353e74a855d2db54e6410b9812662f6d937e307a238041c06392f4b9771b30d6e043f54df31bfa45fe4823a65728440245ffa098ce670ed7b8824a7b894b2db54d6fa659e877a2cfad5a58e1272fea13abc22237d4fbedc3130808ddbd2fed13cf87bd93b5029273fa35a72c96941911cd6e83d48eb1b0726ee0b9dcb482de0c4c42e823f85cd7d7ff2838a767286495cb18d41c5bcd1be99e859ae8161bdde68346bfe492ea6a00c1480418c31aa52f7039740f5e21ed2a85de17bef128ca0e2f64ff0775f3a905c3ebe3b0b43cb7f154fbc95bd0b9f9a178cf8e54721fd51ed026c2ed99cbc283fefc2604c420b48b13acb6649e83cbbf5bf7074cda3f22cbd3980922719370407f4baef5b4dfe32c18bbb603594795b281387a4c6ffabb1e8011f85fba983ab3b5a101d6372b2b10b188abda925cffc428b4966d07b0871345a0740dd0115895f586ef95d72c23d19ff605d492ff5010a4c4e80b914f89bf4d91d5a1d367b497499ebefd8387699f4087ea9239ed295ecdd4f9f95635505f88bff801413d016bcbc84304b3f2a197da9b44f08467d4aa3b928698880920e65dda4cba080c749799b67b1c372b6494fdc81f65af98e32621416b28fe8840023ee7691f5f82e30f34629026723dc1dd692fad0831875f61578e0351586bab4302a02a78059ba372300574bae72d0d243e8da4096edebae6ab459dfdc45309c6307f2d05a720e9c442f477f27729851512a7c5ffb14f330872340ff07e1e4bc1a0dbb54279437801a594e5d0c726dbf49970c8411621a213068ca87071f0a3e2a506f747eb4192078a70028f8725401d98ade7fb8b15dab22fa3fcdd5194fcf2cd9ccbfeb9e9bfc092a90a59072b4592656c150a1d452e3d2c98759de4d43f25f909e1afb0254ffe46461f0997261f12cb40f4f409d5b13a3ea75a9d146535acd63baa93c68d9bc6a6b6887bd2573e60d17ec304e72b737a64a17c75e1c3f1abb5f00087084eedfbcb047e40702e8e3bc96356478ec78925a87cc87fae994eeb753d0ba689a963cb1766a0015570a4cc0db41f7e67318a060db318b7d1d8a666fa17ecf738ba42ee0f78369416f4e5136f6b17a0160db7c8564cbdc7a0cb68f82f2e43d7dd63ef5e3d8e3fcdc7282b686674fa4442f14c48fe5734e66c040cc3bc655f333d56f9aab437990e47277cdc26947581695303d554a00d3d465ae1af83c60d3c9e3ad8e35ffa46d8d00d4eae29e0d3e281872769f3ea731a71d996975a962ab8ff7f848ff6d3c27f472951cbc4af876bee93da86273295d54ae0ee6f59c8b39f757cfeea13c2a072b1c3733fab8e5d73cf95c25e55c453c9f1df135ae585ee85441484443a0549d0672b9f58fe4cc492ac8f8208fa78792411e7e0d97012f98804e993d9ad37ff05572ec053ffc6d6a24fd274aad144dff11bbfb2d56e6d83cee9ececdd775ed615a72ab13959015bfe21a86848b13223680fd6fc63ce77a9f6238b8104454710af87283b226cd88aa46d648dcfd2ace97219d630f8c2bb2c4551aa7fce5b2ff6d4267551e51f76d5b2baa9dff3bd5a0903cc4737fb0201f90f108d1180a77e6776c1cd59733e00211d3596fc5463ab01802508272fce072b22cc554e903e887959a72efce0a1573dd46696aafde72d2859b8969797334da9b54d40ce79549ce8c4d40e4f7e305e4719b0c5abc8d2623d37fae69c8fde2711e44c1d37333bc2db10272f91587e99c5b253034e517f825f4426674b11b6e4747e9f878448581a2d50072c979c6f0c05298a482103493a13d90ec62e930b35aff6342a791d340fe64233c109e185ac7ee9626596f49f4b484316da5ac46e90643251c1df936a68d98250672f1bf38defbd2496c8ab0f02efebaead79d2404d5dcd5f6fd67873407cbd605f2d5448ca3dfe8de1c465f49c0180be71053757957672ada1f97dd355fcfc37258545331cf161360c3678d2d68259d5c72413e81a4132b2f1d3c3efdf09f5572373097357229842f36329c08dba220f88b216676eebc6df6e80881005af0b65729712e3ecc920cd3c4a2f79f3561d699670e0b594802b229deb14aea717fc7727b376e2b51716e27038eb1040a766c2f29ae3bb844ad2550889360220ab28572cb7d1cb25836de9b34ac21eb4a542bb0e8ed06485e08be3d4a0fab9dbc7a9372e200dbb0f01b74d12fc27d5bb6f8c1fa52615f8a89fff4bce12486df76b4d7610233327d7f3c68275078dff5ad44b6602ba61514f2c702d59d0873e578a574667e6ad9d514ff16f92188d65cbcb7a15aeab3c3005bd9f8349d3ed1dc0fb47e11b0116c934af4447890f1f1ac6f11a9739164b36c737d5a1edcde96232e0fe5724596d2cfcacbde0fae9b5fb473065009c87ecce5582e3e24a77491d1b28f19645c0fc425d2f5a0ce5df48d76b4e063b00e59260900e65af387a15e9c0bded03cc6e482611b2e69d86407d395344287ee9a5504b0a5c19e1b01549aa929682f72a63080c68a9a7cf6e5697b131244af47347f698f2626aa41dce2d8824defa672c851a916a071b5e93e24388c604879ecee9bb8eac918ac86b2d22fa3cb8acb5d9c2937fd5cdbd20fdda6441081de82f3968ee26912de6ec92128db754b56de1bcd405b31f1600069c8007b66fe8fd768e3ea6ea00a749485c7f21411e0edc872a4c323cc3b337aff9ae76aa524c00c28fe38452efe7b4fefdc7cf9d090931a5de57a7974a6d9113054245fdbd508d0889928736f5ecafacbebde245bcca45572d46380a4fdba7fd3dba775df264fe975e60a5e295bcd41aa083fb72937666472bf43ba500e2f81aa0108f9f22dd01a4198d34383d372cea641746bb3afc44a382acb49273df962a714afd596a2c9292c615d4f0beea1514d8cc44a9c7a541951c8b71c59293ca01b3c7ca31b18843b4a88d218ee074b631484740529fd8375720a2f453ca17824ee5db76f7cf6f89b38da87cb954379653e073364bb20c8105e0f7d13a3bb5625cd34f25dd679a57fd0961677a1ca645cca10f7bd1a4be4d545caa6cf771cd6853e64d3b62b0eb601a87c305a777d23e73afc19448ba9060a382da20ce93917d6ffe66a518e709b8269629cf169beafffaa2d93029a907aa872562e7dd7dedcba5e2a25464c69fe32de820f11c6a643797914ad19f84da7bb42bfab7ead4f35e2a100990cfd9c31d9de17b43e3506ac04e87249ff02aa56657208d3871446200f3c88320d679fb6c72f7783699c82eca7fd356541fe62615225ec22457b0f2a8272335ed41d0545a202e2e4766a8b86edc5eb6ac53d7d6db8724706239716a8f0b0ef8288cd24c6eb154cc4cf2cb36760bb9561bd7502566072d475b2b3064f5e3990e941b72f8c73a39ad640e338ab761eb6415b0efdc13f723dff063478d213e864fc4466065f9f3d2670d430e3dbf4474c89b43fcd84c6728e4be89a70a24b60e5e3a43455d8a61bca4676cbede66cc6871f6ff5f926ca3427251eb15b7eb1acfcdb26f3d081f160089fea4e0900668efdc8b54b65ddee722af3bec81a1fec7d90508f42f544c245d47fc9bfcaec6adaacc8e95bdffba872a26f7d81b06fb27ab5e9a74ecfb71ab50f496194bafacd1c8fa346f5a6bb090aaf622df25bf68c64e51288b3ac361502474c8286a06293288586b6a9546dc102b2fe659318602c8c20f55d6ea209da3dee623d0a71fb25c41880ef2f246bf83df96794ecd5eec85af258a78a09016bda4a7bb80396eef9cfa9e318b434786e535a2b223c8b6c7b22c675a35c6b1b8e43dd01bb9faf29dc749e7ae02977c04f72ad5bf44b46da877524c599e50dcd1321da39d41c3a2e6dbd8d4325310e3dc5722cb631895e0b98d6a08644f58ee6b3d8c2128549dd73c07e9eed2cf49694b072d8e730b8562e59f71eed01ef32f1e0db6294bed9de7c02434bf6c75f2d74217239b3d6dd18c52b0e298c8426d033356ca6696637cd619bd4e1955972edfaaf7239df1d587230bed7c457f40b1bf1e7fcae737d255f1fed40edebef1fc4e4d272c1f98d36987d86d28e043b93cced7f9f26b7c874024704c2ed0885ee52d3ff02b1440d8d782834ca6a9511ea3fb7ed47308e9a5a2dfbbe9afec8c778dddea472466d0561bdf3c01045ab0bbe286fe1b0d83d5ffe66131dd83224d174beb370175a8def40ad5ab7dba1100d0ce555ee95a30ebcb224be92d7bd9f358f96cb973e56116b206e6d7d75799d7322635b00b5eadde4b7bf87dc401ce5f5dc1a92a052a7170256d58595dccd2f6d914876d6123a6769fe8ea6ee6ffd3a90fe0d7114723f7477a71009aa224447319b55a93c8dcbf324b2d2309fe22fc803846662fe498ac4818e8020b292df3a7b3611314c85c74a23cc3bd22a6a5f8a6aad36c1057200e23b8282155dc37f54e43d4f1ae15b9256ee2ff39985ec03fe88cb44de7b01b70438995ec75983932edcd18583743abd2c2ab799a4102032829c8cd1d1e25b4c631b490bb0f7348cca02ab13ab2bdfa7c5898010edab733e9fe9200f3c3237454143f64057e7c34d2864d2c8a3168ddf5a98e308f8969d5faba7d713cbd0724d605419d7f115bdab936e4f88714953b97a8854e49f2b950e34c6cc7684a536b8460c90585bc9d0f120acb933e15fe6342b6741e69441944eb04e0bf9efa5165213fdf0e00c7886d2fe1a772a4a46245c3bbbdf106e858433360a7d40f9567218c315b41ebdbcbbd7aa7126acf0529c1cece42aee9c1f2597b83b371a0612729bfa74772294762102d9aa4773ff8ac25b9180546c3d308ef4b44e427bd0df72f80ea7a5a7ce9ac954bc3e96d159ae118f8ff81c7da612d28dd7ddb62f24b21e68d0369562e331533d3dd5d3942354697d2c6fc289a5de15094513f46b499c728b6c42e7ea9b088ec4abc2317347ff1108ad798377539601c784358ebd1acc47f81d4378a15378ff618fa5bc04bbf4504cdac860895ef1d339e1683fae2ccf726207889ae33445f356004b3718c4619bd46bcd3209176675f8b867c9df9c335ea8a52f04c5d2cc749c5b3f9ab9e5d125ff2ebcb17107842dfdc439599ecfa572882a34d4af9eb5e4785b9c312aa7502ad4850d4892727fc891e063250478817271f9c711f0e68ed630fc4c7887e7b6510c46c9fb9df3e23fae884f2b85996e724c323339fb4e2fbcd19e49704d548fc571537e7596cc8b6d450e30135d6ff472e9c6e42553d8122da0ed7727679574bf1898e5545178f2a59d0ba3d8b00e9472783cc89a5e3291b9ad4add0e9d1b3d0a3ddea56c7625da096c46e8f7f868dc72512f984987213ec78f0671417cc0318bd2571dd9c0b8e6c3dedeca0ffb9c7c722cc59db295f162aaef7a08251421f8bdd949506700fe9ebc1dec5f1b6cf33772cd5ab685071e7947b300eb2d851b3e5edf96492390efbe33d645c0126fe387722b5204c0070238b64ae0bec233e7700a0cea9abbd6b8e04a99157376227aff1b62f20689aeacb038093c68a02734dfe79b39ae20a49dfcc0605235cd71829c7283dcb587a4e73699ba47cf2582a081bfc9a47724bbcc9be5c6f5c2d5a6854f72033fc57ee844c0653ed97493806075478308e25159d331c1536ecee082ed2b141892d2c52670a97e210eda1a04051f5ace7478da455f31388e274ba9940afa721fbe4f86583eaffd734401620c0416ac5047c0b8379fd24635af1278a2842419e1bb76dda2c2041e462e3736fa5b8098e0db03c2b2bbbba8ee5fcaefa167861d6af1e22f522609c00a2b89ad8ae2ebd8e7ab33fe6d1e549ec7d0671067f1832564037ab29d604cabc35672b742e6415783155e23febde4c76520c8133920236973de852fa41bbd542bd7908f90a3001cd870c4a6a63fc4d928c7fc59ed28057234f10581e98a3c8adaf9cdc4775eba27e285ee52be607976cd92aa123e23ad579fb32bf0e81f83108b92c6c60d692544c7f419cc9164f06467d07918ac9d0c7232f9f2a3c19d4f23b86d2d82e2c02f29ca9e79f26332ec32cf3c83d42b95e46a4390f4cd6752609acf4e609888244c064e9b51c48871ba0b97a86d834d662e31c4ecbd7eb2d1cdf3d106c0023947c198fffece43b2609cd9d2570eaaa652da289ac74711463eae9897ffd73d8b8900af25619f1bcfd0c0de6ff294443e679d31f8136cd8e09d6d326467d3cef2a090b7c1f9f0f2f4e76cb0b549a45b875ab7122b210139ea732cf74fa8c157c554973a0e0c41fe69aaae72574ecac00a0ac9722c275f3cec7c31f497b4a24b6869181237c48b56445992139b1ca11ec91a3133b0e114474655cffc50e61a00df813d4025c8e8fc847c52080d09c121a5b8317260405e3b5e4e8d9236af94e7c0fb992928c8c25165f466add286dde9c337e5725f37ba339991bcc847969e100e3609ebf8785507ba4396af1fc33a0c675adb7236eab71bd2b937268a3b5de3224924d38ef6738a6a8aca1ffab08827dead1f728e88ee03a766964932f66a4f3befdaa70db8b02ab1a1f49a597b6622d5586c72add104d379b584cd86b98c88053f6f5780188b33d1538547df1cebab8a4ddb726939afdcfe93044eef237c29c5eb997fdd9aa9d55448e9f3fd2526bec167c172b78cf5fe792ddf52b0c63270e15ff22e77cad991576af10a3ea17e2de0fa0c08bb8f48a3facc9159228b3f611bae11043c5a8b98f6bd5a8dcbe4734c31e4b2726a88c2173f5417859a584df841c9bf952e7ae829d3d3cb49674db064b696c27257c5754d646ebce32247b232bd8986f69a9aac0291862b97ae3bf72a13775d254b0296615a0f70468a19b5478a387faecbaebe40c1dd7bab17213b6f45be3372949d142869c13057be0442873c8a4450182c52c70a9058fb6be8852241d7f97247b75ede4e42856006eba3ceb2534800b52716ac5665a8d349754aecdc90ae72c7c908d713e5ac3e169e79cd336986b0686728cbb5581bea07dc4dec65945c725cc99855d250c7e4c3cac67af76d981dad4ec0fb003fb1a9b22ba1d7107145725b72b3e657c356b28778f682b710cdaaa9f896eeed9833bd306850295e81277226c87a0466d059384bcc9f05724b0ffa184f4066c5aab52aed0870ebd7c2222c72025dc0fe10d85d0df3ce911225dcfee30bcdcc24a02e2874bfc7c8374c54562dd338bd16a1b36e78323e793e9ccfac08818e0f318befd5e3497870571974726922006d67c9c344dc5dae8392641c38cbeb97e3752e7def07c18ff79bfb315a565ccd5a172c1048e40a2ba569eb0fe3e0017ba68946889c25c78d3806201c72c3bca1b66154f9bf61f9ca0ff9a528f9da69de48dc5d7b444b6c178e41831772bf9dda0ef9b8dc25482ea8eacee7e6226e071ad5d2cc50b217f47d0310f571209da6248504d2246aef927848b7dd98dd6c0f9a784c5388e07f63f353d711696d4e357623827babca98abdb987f02c74cd5dae7e81ea87846d655207f06713f0878e70320c0dde1c93aba5425064c738490d6f1557e978e492bbd33804b9f9f09a4b63aa6a81fcf006f5a7b274f8cd3108d6da5cb9a8aad413fded0596db1b072cd3d756e62a1b16cd93ae7fbaceddb421d5cbb24ea5d61a8472b937bbb49465d10654b6be54cbde4f12a39879b4f09d40596e41b350357bf790eb4e1e066693eae3df4aada675dd79cdbebb3bd593a62e104aa9e99723419041eba3c5d915272062dbb930192a0eb5f480310df62479a6e6840db9317f61e403dddefe3d4bd72cef509256749592c8c610c47d221d76404cff94d49afeba24877973455b6f5727cf2cf5241d0df3675092a079d2d26b076a286e40c868e16033b0ef8d8c7f372f4e68c9ae3c7b28b16e919f3c4fe9b54a100f6041cd518eb4924f244bfff5872fe537f9d511064762d799e32c6ea45165d453f7af76a9ffda9d98e9828efa972d1512419fef4280a2f48f9a67ead96b51138ad45d0a14381003afa94a6908372279155c5573e477908c947fa401b408fda41384d184e0d5beb02055e97a66241d8b2c081db8d2b9178c16d8b5774194193f8a359a9894a99eca427d2f49f25727ec0df6eaa9788d97e5fd71a3b1a3d71c423c3f88840d07f805ad3c649e49655c8ab2a966060f0d8f76422a0a942881d50236dd8b7a4692b561420f8d2499e72549440927fc2a87096fa02f33d144b83b4a21dbb5f316c6a6714c17ac32fbe720d3e7ee9e83c9022a4f9b86dda7b81775afe30aa7950c08708f2cfe6a728c213aed3d021c058c8a8cfd6cb0dae7d1458f163b0563f4ea0519a88d96832cc4c3a62f37cbc87996061c35a5d265268b2bbefad0270c6606d9007efa44a80c99b72f54cb73c279310403b3191446b210b0439aa11286378159fd200c4dc2a86b35d2b56b848c26b70b2a873d29763a8f33c8210e6bee3164ef9b6189e65442615491ce302d9f708eed2b51ff42ef499dc243114ddfb361c316e988d2ae292cea00621c281ab8a5b74fe187da30dd7663c35c391ddbf50d0fdb9859729ed125a8c72fa78573be6fbd82f99bd15799e5238f9eda1b9dc70a12cb41445e148d2675b7270cf7bbf458dfe6a6eac4212cd467148f86e8a9e2077b1dd940659fc46bdab729b7dceae8cbb05087c9a278c4b37adb3b00155014d209fa074b94bda5be81c728d899dd50c917ddb587b253101face4216f4deea4968f3e721e8027877718972adc9047c95435e864efc9334b6d6bc61e7311a9947cb88de294256ab3823c77285eb8c7b1ea52c1068fe15c9a456cc35ea9d5d610ba4d9aba71096686a366372eb00cfe305ec01aaaeef2a1aacaa55e260499f37d15fbd254a6f30de39adf572365b73cfaa2814b94edefbbce1207b4a764e97768bfada5662440383d4464e62c5f19a3983eb4955fdadaab2496ae52e55e43661cbfecb64147fc04fa9c5872f0a95be11b5d93de276d05f21a56d931fc538df57e34ce3409ba090ae21305d4e3ab0d66c7ec93285d6d1039c3065eefe2c92520333a6954da4f1270cb5607372883708911b68960e84e8265b1e8708e8865ece2af0754e684585ddd116ca037204afa44ea298b9eddb44b9b1fd9cf6f7b35ed9c266e58eb7e5532a40e038b56c9cf11e55bc50474e1046ddb575b178878c1df52a9d06c05c634b23895b7cd272e99cee0aab5b5a6caf8c33eeb00cacefd0e51e93ea9719738a26dc47527cc70dbe76b997fe1309e99199a6182415b656ae0745a551391cc1c17f2043aaeee5726787ff4986651f2bf04662c433e82caa9cdbe8a1525b02cf7333d7ed5da3a072054279e546d0a3bdeef826e7fb76f05a5a68182d0e5169ca5267343fdfe9e4729107b4beb26171a5682d2f2d1f4b0b358f022670d8422ad21f045e64c3e8bc581eed23ee85fd93e8a610b5a1a711564b8ad90f93982a9a59679386b003bf957282d864cb1de51c46b3878c656da5358c88f3e53d4f91ffc8b7bc8219f528904102945b8a925deb465d32dd9e0e8fdb5419364443aa619bb1bd14ed16f315907202c6bc7068ff1ddca6f4d8a40936d809ded1453b2682584dd030d0563cef2a728f7b28528aa5dc408fb2192661129bce4eadba2c5a1b10f84b0d7f610181f972918544432be256808bb02d774ae1cfc07c9ce2afa32d13295c956f384646c072f8a319be9f2ca7f97ffc951fa3f186045102b43932e30e28d8c3b210b09f25720b9533bc68b451094d73bb2c120eefe5a7ae1bc061c93a61c639681407bd4c726a6fffd68c102671a223f965a617c97e4183122d57101dc49219957d6fa267294e4a52a9e28b7f7aeb7b8671ef12ff06bf3dc06a1d0bd6f90d9f60a514a4d872472132a62ad7054d52fe4e3be0a0d3fb10e1dca593381d05bcf790dca8ebb372b23defaa1c91733b7bda4f3df6f5ff2c113773668c09c8bf3ff46c1cdae2277206d8055e0b18886798f2bc297b4c8c5fae1c4dc71bde0d59fe31f30d45239834535f741fbd8755e2eaaf05c606bd7984ce46407b3ad14d9bae91c09cc4262d200acfd5aca7f3e9caaa5a1972a33fdcc8ce9d51cf25cfb962c75e8736f95f752f6bce6ce9fffafe4934a3a2b3dbd77eaf45503375a251a92740eda9b0916efe72ad560abe8f5db38a6580cadf5bd5805b159571ece6fdc843b880c3313a483a4863508dce33e4d07c4e4e7f1fdb5c5843629a537121664bbbd8a021198766404b3859de4a8e2ae4ff6cd5dda46bb39882835fa8a079fff749003a45089e0a477218a45122608cf1298941fca08977c20470e5b37ed266fcb22309e8712e27b6720a6c1e2ce791f38247ac998522d64be37d2aad3438fa746e1c0bade2e6ef0c48599840005a98d1f5842af193925389da894f1130b9514f99fe38f3e4376f4872f3d78af7b83c2f00a4d37c5449a4ae850f6a57ee2ee38b4e6753d0be196f1372076c27ee8ab7fb65aa279df5da0a98ded776fba01e93301d5d35213acb208a725af6bedac4ac7c6a611023065afe04dd7cf21aef53d34febc488062b95f9dd72c7a096408b43e5f04d7ebda2e0e310550d34e19f2107567c93d24bf4fdf0cf72fcea2d404dcc1a25e28714705d39825ecde53afbb8019e9a558dc53dbed6ff13a7cd1c4513c6a58e6496095bcc54c06927bc2110cecdf7b353c2563c794b18720f3b6112a5635ac8258d29e6a0d9db0190ba242cfca804ffcef5c3bd8f75d972a7a8901f04ba6664f1e975f6ce80c6a0ad0ed7e8e4acf1b5a3fe00e1d0d2fc32706b428e47e8d3f3d606a00c5d5dd0e727a99c7f2c04cdce1a6bf3b6a09f91727546a70d2111ceec9e7980d90b3f1b835ec91cf751c66c429b03972cf09b4e4980e971a2911dcbcfc93ce9d6bed40f01c9a401f9144522e1b4d78c57ca0101725de417175517b90d5a76f229e28ec4d71ed0e2f743fd602ef80a8701b2e97d4ea00a3eaf4d9fad107cebda9cbd5a1653de54e8ffa004307326cea009a6166172d11b1159801a681077d6ec5ea249144cec46d256c464998256ee03c9964061027082c5180b52cb484cf8780ebf37adb81de61e24b65aabd4864c8eb73912016252f4461ceb2440394dc4dd01951acba376482929bbbcfce32f17b8235029a640eaa596684393aa28c5e4363b58d9d743c66e528eb5744ee94ff9064cf34cf03c30866dc90708797377c9d7818f672ba190862539c5cbdfa5302c7534794c577296233ff10a021d17b30f1de07c361a0e229886162e753383e13dd1ce3380ba72c002b6866d786f6431e7033c5f76f743002b99a3e095aac38118e8039c89a9728a8e86c1c2d5eba96075431e752fcb7f42a5767d04552addef0e4a047c8b550c884e5cd9f51e1db2c30b0246244187a26932b693654f4038569bacf009c74e72e44f1fa1b331b8695c15e3cd72ca4b3767a4cb08e9acc7e1f4e0ffc74a7a2e0659768e7ea839c96861e9bdecfc35f13d184fad001c7e34ad9672848aca318966cf6fd3e0f2b43fe42c69533763bae61a0da2bd3d8b5b6b86b1561e72c44112726fcd2f9ccf2178c51f6bc47d3d70e245ae2029cf429cfe18b3f443d7a6ac49722058382c37b9daf907cc6b58207a5f369b604a1e5acbe1ea1604984a109d2c3189fdcc0964fec7ce9daae36818081d7a73b55727d8ffe8a6d5552f1c3979e2723dd2316d1fc9f3a8f8a10993a1447e39858fc36be18d6609ef849c8257626c72c5d4b5d47d7fc708b6b6771a7a912e16f45f52b8690865a594050bdbb1e30022307a383c616926ce5ff231b12d7d945a002edb0175107146043100b3a8f2f772d06679c32031ef59f20b6a0dae2bdc03582a274ecce89e95059ee7b42c6dfc7211554ccac705356e757fc0b2799effc1397ef9e7a927875909c12544a8e981422960209c3618afce89cb30b8f3b8adad8d550fded015ff2b33781f2d71136372e76d0bef1f7d3526434b4394e415cafdc77a381b498c028162b09b44441e4372f99a06d691b731dc8bfe4a7623e1b9d0d6b6047460e60beec42ec6bd0798e372df0928e7f42b6783dbe5179350e78cd2d0a2b54c6cfcaae767ddca810b97df72da95f5684d770c664821d6fafe622ac316e32746831de9ab4ac4bffca1c08d31fef3422b97700d4ad2e1b060171d9c3f9c52d82bd9270a5b6b7c78a36f4f7e7290ce29836caf7cb6966daab5c37cc8d2b76e510f59a4c4b5c47cefaee9c4ec7277c6016b22b7986f140acab59095ce71af3228100bee954f368457f15dcbeb3d7d13a58ca809ece37225aa8f4c09918a35c3378f297b6b90a2edf8ba7f0af2725ec5bed7a374e1f35b6b0732b4881e3d43e45b764cc70c538167520f8dc4cd33ea92555381408bfc136dd3e06e3b0582a42588773b94c38e23780196b4ccd411bd1b6d7f394c217f7a0f50c68e2eb5b2ab9706f64ec1afdcdb035af75b5fd2721bdfb8dd2fcc24d6bd6e43843f4e5e79ee69e087aceead9445dde1cb0c7b87722a3b8e79f292c9937cd2726b4b3ac992ff16cfe7e1807cbdd7bbbb0e36e1587223af748f1b5c9680b762fae1a6257dbfb8c789089b5d9a2698e6f191673401720278c9686d2a3c113512b5618ce7fe0c11a88b3fc3f0d954318bc348768a28203b5d41e9283c980152c69a4f2a17a13af6d94a13b7f020554ed7ac43b929fb72b7bebbd896fa9ad56e4117a9a582ff937d009cbc8301d6d7e571c3f7d314cd30362bf807ab7c3efbcce596ee331296a5e990631e533e7610dd4201f735459c72f99bd56f962a5ff61ac39847ad47bb682694c2e24fb01e55f2768e12cc11b2728fd82ca4a2326f5834d7c6af44c487b8acbe81fa43355ae30af2bc7d2112212b7bf066fc179c9082f41c2521e6f68b2e32995589759ce51f2e05ba30fa9e68727012ac2ab3c354d2f95d30eaf536c10dd17193380e57504055bc678d372bda726118833bdb74f2d32ffab74fa4e812b83d742085248ead78edf1d34e48c19c72f96d94ac104dc07fa54cc92627e75b86e9cce72a0cfa2542bf660510c3187e702a0a226583e640db058509a6663156d8703b5d4989de74a039c9ad3a83d939722877aa7d65b0c076bbe67a2477a15bd436d9f7144c9111b89faf4bd31d3a010735c2d209f08d3600f4653b1ee6944142e1d11a148732af7588079970792a3d3116fa005875caeff21e6d685c433c485b2f055484f474ea77ecfcf25fa3119b725157d10d0b3cf122975c21b5742044db4c9b006aa82d1241739f4c2dd95abf3b38e182de87482616a1fe86209e39811bc9c287080b733cb1c3b097cbbde9ad726ec085e0540b7c87843036296501f201bda26c5114667731b9140693f20d5d72dd6e8ba67cf9134c286047fbbe9fb26bb0609878ff45add7cd18d0a020036e08aa08b2d10cf0ef6b1755eee19962d0ef74d71175b9388534d16dcfa191b4a772405a57d9f16f667c84f43a0f7cfb6492bc0f2f9b7f53f80d0041517b263c3b729967c48895136009612d48361a063a3dba81fa40b3613e93b63913ab6828ff72479bae84d7022ae7a2d7a90aae4cf97b55d6bbd27f8ae68963ddda8f4768542bb4911b3a1a60e32af2a617c34c7cd4fdd7c9c9c6c2e450b81b5fc544fdc22872c968047f2bf3ec759441d4ac4a455c1dcc085915f7f99cc11a095e072ba8ba72770ebb8f81b0116fa908e623b217fe1cba8eca1e7d4013155fb4566fa91bbd58cffee614da916c1daa44f4e02393ab9bbbc1434618ef6f8277916bb9345ed4728d592545f30cb158ccb916317bd03251d961a1c578266251ff382881fd2da37262959ea27d41f2adfdf2023e836ed214301d0e4e84d21cb490bd101c637c2b7221a373890193807a62f82b82333335038f91167158321f50d8e18add7ed8a872d4f0c6a0c8765128751e7acd402b7b27321132c54e52d4af96086c76de86337282ca6de912db1394f9cc9ca68e50542afd42b567768ead01131f7facedfd1772c0d62b90df76c5ffcb1720bf0659c71ab1e3f7f7e29df4c27b478bcccf99936c54359431b6c196428bc7d7d0e6bb55d412186e846edb85c3adbb650aa1400372f037abc3ef9242d306130574b35e67a330b80e694effbc43d59aded3553add300a7caf6b067c8ac63ccb8c234483dcaa5c56015196a3be7c43dd4a408fa99c2ac1a8146d326a4bc2f27e9694713e5f8710d6f32e3fa63155b41b54d052ea1b72b0b6a0721226c803c0bcfc06127a1110cbe068f4c84d7357f400615edcdb4c6ef1970219738f7b3263ec9175ea8af702d5b4bcb729f52e2209562f740a7b826eb9cfec5e65dac64c1891585b04bfcb419c13aa548db5a78cbcda1dfc2e6fbd6f25040033e9030bc865ef5c2bf9526f72b0f242fe9894790fc1df317b4665c372fdac2754c5e47db1f8223c09a484535afb4b58a0668e2ccaac32a0d36bbb1d0e06e6d118ce2c149fd06e25a87ab0709818b1b1487a2c6dd74b0851e99bb47472353a2e6e8bef5e47aa185ad8d398e98f81ccd4d2135f489e92f809e6bce828727ca70f91d9c5ae4de115b97784e2ba7bbe9bf155d4047980af65daed7fe0de72ca63a5d0dc06bdff7d609f83e42cf90711bac46733c9db0dbcf32659effcbc7258413b3c99fd60bfccd4bd74be0d97e55c31fffc133f05272303918f25971672da2f6b5bce907b3b43851368a611f75004577b8b547eacba08bdeb9120e43d327bd506e96bd160656f1cd9ed4eb8b656b55ba955107ad46f0a302222e718d87271b44150e26a45e61d8d3b517db48f5c398cb3493d703c4f3823631df25d0b724b60464e2a1125ce0a9d04c5e4f413f5af7e862a58d782221d4431f380664372623f4f648b221122b67070e9db5d22acca991bf4889f48b564ca3bdf50fca0524ac1dd011b6e6bb337a8dc32bbc5ae80edd8bc45ee2d1aa73a17bc68f19922723ce2e353d85993f6824ba8e1f6b4d146793f39b40564c000688dd66c5b052b72824a76e2c2062689184db250783c463f11880d86308bbc1af4964cb9bee6e772ac2ece91253fc3d69229095bc3bee049e4583f1890b80ba6c4ff584dbc6696722b1170d959f66129a484c31653394c9d9361b73d6199a9ee776632066e1cd272fc472cfcba0476f1114fdf110bf593ebb5160d8e50289e41f593bb7ee05bac355abfc2914a051563a5b1cff9651ef1d035a04c96dd0d2a73d1e54f5c0299f272f3ce06da9f04f910ae734521648e2198fd74e6de75eab7494885fab92618d21185b2048ed9239950bc48763eede55aca2b2f02639787997105428b8322cc2f3fd453d8fa6b2753f82f89d5ea2547e489a3eb9fbd4d5a02f567c45f82fa3450722636dd54a13ca83cf19859c35d1d62835930c519b2bb68fa08eb431b66d93b5881231b6eb6a2b8adf1707d6005b3d6ed9262675cef9ea0fccc6a93b50041cb2e6c44b71be5693901f6f975aa0b9fa5cb6497dba456d707157534b1c4f125656007632b4434e91f1b4016b3805a8c0ce0435fe7e754235bb26f619c96e0f0667296415d016017ccff23a591d55f60a044ca55335275d223cac43daaea94cd9a5db8b5da8e4931fb75a3860b640f49d4415eaa523ea327e1d9416c6a3634d7097218fbe0cbd303e57c0c0402d08f875873d7235821d63c81d1c64fcb3e7fdca41729b76a7efadccc8266ee8333cb113516a0de02ce19a3397fdf5c975a4f2ff572a4abef21260a219c49ebe3352f57e4783d1a01d9ba58ddf46d687509d07b8216569bfdf7df0067f418c227b22d95d405d0d63330e4fda415b191a689706f731fb92f7c08949f68770b6adecfee6e45bf4cbbfce7244a80aa44bea2715f38594fa5ce5d11022a4745b3f6a525e7e8ab1c5d1936cb3e7334ce906dd61f7825a71c5279bf6f79e6030a9fac912f15799b8ae52f45b7bd5946dd0f1d6a9141043b41b09cab74c37a22d11fe5fa36c77b6f2bcc947fe89f4b3490437bd84a4ec59e1d15fa7f1fe655dfca72d2d390ff25914dd11035357bc2779428999f45089436725147521f8b8693770a2326db76b4089517f8fdc18f08723c20bc4f655955c27250fe28ae15d7129dd9cd41a58165db7dcf66ab1950e20f4af5338cfc0d396c72b0174d7004f1c9404a6784757c8bfcaf0b6ab1f82363f694b201451ac351fe7246a12bec7573c0c77433d73c885af40fc38c64e19512be9703b8abd969678b13e51f28bbe27867d9add0fa34a247fb3f38b897535f1e940902a14adea85d4f3766ebdfecbb8518de02141c9dd3ac281ab68d3c6d08a1eb784093eb7393d44072ca012e6fc9158a3368985780ed0f1ea17b226bd31f3f1f2d08c6fe6e9abf3572292195b852c038463769a09ccd602b98cf30e9c70460358c4c476477c3c4f4720a1aaf15382c1ca261124aa5f80a98758e5aaabe422a3ea40f79f95d43129d72e6a5978f06389c6dc53b56763653e9a7cc9b632f8d86687dd15b854685216a5ce9b89f46e2c6cc53cf312c4756283ee7740de2e39fc506cb4d968a2461a39a5f5171c9d5076f291ebc9e1165f10cf23472be77da4168cbec994918c6c045986c8896d2182c4a240bd69c24012d4a432c913627dacf4658af2f42ecdfbb8ae0405e8663e6e61dee2e0f3f636640d63b8f77d81f0ac3ce525c6b3dab9522e830729b8f2aafad73a6173b53846549f8edd17979f64896c82cc2966969071e0b4c576f112eac1c01013d752d598e7b3d190abdbd6ef32ba8e21b48b0fdf2a44cef24992de09e32b8b0625d0d9b9b0b36a4ee51d1c889880a6cda84b3370fc03a157231d8a3fbc5f9eba3c3001a5f795eae4b662f0b6703ec9f4201fd101134ce6c7298035863c4e1742526ec1c232de7320475b9f4244e61f00f7c5161da70901272e4bab4441fc42220056db921be728e35a375b9ffb4e0dc0fc67912b801d8b17217641267b03395af05fc30679ed913842254643224281e12087d256764442f4dd0d836c2c6261a96b2cf1ac80157328e9559b264b1b6e371006220dc6cacf86ca37001ec4a21636387c4db642b4a831acc718756efbbc8080f9b1e749e9c58066842b537f49d6a2907651addf3482f0d2df7c445f673889be06fd0c89f842072ad7d2875bd5080180cbd6da4931bf1d7648862ca7896a886c25cdfdfc8067f7208b4ce62fd26fbb283971dd76f379c206e251dde6829771027300a4e2ecc063f1fac1cc797a32a1da1c8cbd326468cbcf63e5d9e43a2f570f39e94b5fdbc6e5b4517b4778f0511d4f3a09d32cfed1b0b5d561b8675dfad78cc0b300afbb5a672b6e7185614c35b8238782bb0862c4d69b6bbbe5d5c9ef11eabafb921279cf2727b9672f6b8ccac5afae30e06a33510bc2171e1606e4cf7f37961b1638a4983721e5b4e5b6f4e929105c0b2c3aac534a813341dfac230d38e63f8c046c9ab2e2828c63face2c70136da97674916588452b877489a7be1b51bffd335bcbe130b536017269231013a57d35a9336c892400390d6e02a1314137a3ad03af699fce54910beddb196d941b57d25326c946caaa331311d15c81652962d4dbb81ff3a0419af733cd16894bf3b99596ff5fa858ce6c0a6238016c89ce74770d53906d68272978ec96f36966f13ede31cb59e56ebd165901f1f7441b9e3786fba0225768130dbd4a8027049f7305ce50994857c01c1278c4605145c074f6175053b22c01f7239f937eb39fec7275ed9134bac7e5fddae2fb78e51c112f5bd92f3b62133ba71b1b40318d56c7308d36044a96bbb032b37aede1b738ac07a754c34cafd7b9472a2a64cccde75ad5ec0e2526b25792495b8e27e059dd4978d07e8704e046c195f4475ab7ac3d746d6b095d68ee7823cf76f9bc5df2998665aec9bee70c8ab7406a8e91e6886e4846c56f66ed897cbb6de4ae9fa4680fe5c0176607e164d981c7215d68d9603be672463e1a038b2b25748f22cec9c1d0996749f3d95efd335c40c4733a6a867c2274c46fe18993b5deb79df56c07240d5c5771e2057743de74262d3c701e9d65f42fdf0aa577db75819383a229d4789054c3808b6805ffa74f0013ba0b08d0cf35a2b08cbaafe85bf67901e7d237db15980b00ce8b2fbfe217472b1a97d3ec66c550646c13902d4f8e1442b825a0778e5295b73e576bfc15403722f864ed2a2de9b440365ef11a55a2f0557e37df01a2613b59b361810bff66172564c2150988ab9d1b00e8c15879d83e32eac749b3b939880a80210e2c6153362a26ddd998f52c780f7d80f6264f9c6ea2e150badbe2c31f3ffc32d89ff72f472e0e77fba23ca507bbbee4e0a865a61e8694170df682af659769944cee9af8372cc9d2dc737e5fe9a9fd87e7c888b21e71737ef6286cfefbd66412353adfc76705ee37361f8e3639ea6bb423d800e3c5af1182798f41361b435a9d979e3301572ff93de84f4351f1eaceef42985c39192d62a8f767da00684cc70586a9a713858175d480baf9e21cde843cb2d4cbd6049700073f3e912f1cc78319ed368a3f360d5861fc81e11207328c968aa85a783615e9c87172371a1b239043ad9435b037276d7b5d67f705b697a78bf92c200f986199751a955b2c6ea30dfa088035d462918128d44e8ede3ed3e2eede460d3915ca2b78ee46d0f3fbbfb26fc150802576eaac648c4e99fe85f5904307483d60aac1d64101e2732055427838f49813fd0418024264636bdd1f905c84044dd47359fa71ed83df48d38fc1e446a92a67ac3729be850240b1f1d091a1f8200b4ab3e39ed24c1c25d9fe48cbdde1ab2302cea3fec8bfd6679037450da026e56d1ae6f4bc62e0b7a6deb5643a275ea4daf00fc7261e0b95319566d9adcf79aeb79b7ae0facaf62607e3f50b7de4e2680ddff8872d024d2b66804259b8c44ca6327f673776c5380bc04403b765c2082bce10f777236010e4635ff03b45b146c9b8e2f0531f67906a134b6d615614ac9dd1229f5725c22a7863b7f0bf8c0ffb9b5118397efe7301c57746d154016222cd1f1a7682480deb8e1d017def5bca032eff78089ab6b146f9057c267a360bfd1ed9fea514474b10d06cdf16b5c16c27cdb02131096177eb71fbcea2fc3cb7fe0e5299767724177bf2a77fbc4a19f634a2975075bfc095b476cb04de43bd26fd32ff233b1721f52957b5694c5b2494c340230e9ba8ed3f52b806790842c7ae1035fb7058772477fc4627206be75168cb06712e22ed0371d1454f848495efc9c0515906d7146793fac56d6314958e44a0f89ce5fcedf8a6d735cba92f3dc5dd8f458951df4725dd2e4d76b9e22f12c71bcb72683183d98c4790f10fd1e43f70754a6f74bf255a30b1a02380abbe206a69cd7920d151ddb9ea197c9fd7fe9ae9497f56c64b23884cb3b9eb64c83e03314ef156bfe9863095b6f81acc6487e30ecb91311a7d8727baca59e1b4139c849977bfd2ce2238b4352359bb4f5f099a59844e673579e72e481af4e4aff241ad0191ebb8e1d1f9507984c619f617cf35eb9138100d3b272bce33ce60d369506a94b79ab8d457767209b263013cb483b4e1efe2a017d02721f5ba527d188aad5754f94e2fb16c8982a35cc43c36119f17a9e84a0ac13024faaa88ef657af9f32ff64e049e6b263e682ff35ba4ce56d6fa8b53dc15674fd678406a5085b02541630eeac901980541a523f7109d1ba0498eeb63bc6b8439d3114609325f185f206b64eb5549a79389daaaa55b15254dd575b80aeda63a9f7722fa2826375ecb62de4d91ce930a2826465b15cf52f5726a7619eaf8553186b62144411fa63961b69ea442e9b9630f6bbe9a3a27c94cad9fc7caf5fd97e84bc72064c58b7d5e74ea95ac94988afe474de7d9d89c3ba66c8b9194104bfc7722808211a81987c9a0da3867648b9fe2e7b378f143977df2dba8299dc531fa7595372b686683108b520971638e4e5e7349433dd265935ca6a4b6de056a7cd718b6c44267ea4a9e1d9ab1c282d783f7c898868b7be13df0f00cbcebbcbe49522f07350016b22235ee88e37258aec0d677db7e6267a87c313c853e92ce44245b2b6fb720705bcdcc8ac9bff714341876de935046cea6069b7fbd5b98ea5b88329a0dd62754c417b1ec530797214e0fbf0f1330ab25588915092feab274ef262c2afd71a056e1d512cb7ed36e2fd77cc7c71b866b68970b7ae2c8181a1f9070508194905e8551514ec341eb367353cf116bf0ccea6c59b89de3b905caf30ff04719b8461cb826611465378453f18569ae2db5b07ff6424bbf0ee1477804e074cf854e211712f4fd793b22aab80504592fb88b67e9f9fd7575b2327a98a4d914eb169a572be1b5d2fdaed1d1525d0eabc5fb121c5a9bafe4d709b807641a678685c2a48653dad48f08f0f46cefda2fcba1f8eaa9d2cb7811a1dc61f75ab09e935e37f3472576838dc3e34b14b8e72743b1752d881a398a5a6318252e7ad844241d5db2705d83deeb248558d076f741e778a7ce0a11c02661579d01d4abac3535beb575872b97ef2193d39f37585c39e1a6c5d1c35f32d9a804c31c5df27023cf2bc0c45722ace020c6ed68172a54eb1145cf92e8e1b525028d6ea125295fd83afad2ef672d1f3293200cefe8e7a265c40d302fe254488dd23716b1ba40d0da68ff9efc23e06589fa81d6fb17ea0d9172e41c10755105e28aaeb0843ff3fcb168d72b5db2dae0979ecb8bf32afa85a661e9d30c8edc24b0b2e5476a0e6e41d3d4804db4572b22643e46988b4a28edc1c580895107c31665406783a8da1c4a8eb59b13e0756da7fa3679142f36f21bba2380e9a7698ab8982fd92c20a5474eedabb0c2ebf7230712523141d12bd613456e36abf958ee5d80d6a03e7877bd077d1b2fe51bd4fe6cfa2e51044ead35aa829edf42fb6ab1b6c6221754fbc37c60217e0d2076672b29d5f01ada5e9f6ca9993c2cbabb25ee5ed2bd8ce930d56681ad4f86f2a1143c6d75ea03c0ec07b389ae2c1e40eacce91123bd5b2c74222cd1a173c460f1e7262af8b5e1df116949dcfdd3fe6dba5836bde88e73be753350fd87fbca5c8bd6c2da680165f9d8c328e0a59075ba9a68dff863825ed252a46d15cfa01cf2f4272f9e6caa9005140c561b3e30fc17ce3f6b2523c160762194da86aebffa78c27729bea479604f40a13195689c80cc5cbe4f6a3c38570983c0274db90ee9b5f1665df6a606b4672c4d072ab12ca802bc4f5b05ce4cb9a42d13dcfb8fcc99b686a72ae399b800d543125a5ebb0a3468a39595eaa994bff16470581b867d6cab97772bbf80d55fedd0e7edcc3b0c4d9474eb20a1fb7c39e3b7a9396e90b3203164752623c3d592950c732472021aa9c06a8584a5d78db22cd44271b1f4f3eebf4783a61eea9063fb51e0de68690a69851e520a2982d102208b1f0d7b370b9aefb835c899a5034938318ed466a8ea524e329a65466e431735fe10d833c44d838a2e772862befa249cce2c33c903087b838fde34e975c83170e5f49290d40893305b272615b4ff07f5f6c8159f19bcea56903bcbfed9db8cea86bb3345ad1f9b650a2721a06cef72cb516539f2c028d064801b81102b52098455f435ba414cc2ab6055d9576c6d59e4139c83d200e4cf7e7aa544fadd0f578273658b33f4b6a9040560d06a9427f35e41b4be072817ff94381c1e66d69217fc0f4d16772398c8a870c72240238ff1f01ef59d50431844ef925a45a2a807c9f8729ae0e45f7350837a3729b6f30f43a12562c112b923db395049f1a947375719a2afcd456a137dd21fb72ce4a032c8114f48b08c8571dae186760fc0e4670ccb346c0bd1366f9a81eb3723e7746052396b341a978ae04525ca645e429e616afb5f8353ae8f8ce19a93072b5685ace43b2d3cae20db97a8ad857895af7422223665d7f51ca8c326f209e25588f21ab236cecfc9d58edfd608cd025b816a6e03558a244bc86b7583750854d90d70760389ba7c1f1e5904300ed334390312eaa6cc9f1c939d6c0d082dad3725b2326d3983fb69fd0ebefd362c972e959aafc37c768150f0263412642aa9a25051c962e0142e4ad792756bd05c5d738b8c99aa9dc65e6fdcbbd221479f80a28d94be2a9418ca5517517afca9534022c8c6e4c50ed1646674d38de2e4bebec72e5c5b52efc37687154b89bce2c23c1bd80312f7057275227ff8e565c613ab470060b25b546662b81c61de2be57475f1c7f6d2cfc14c74b6fe8492654b75b9753b7e1bb328f34f8f0702e1f40ebc65a19c583fc7703f8c56b4b1896c3b5484e282bf63c0b3500c13cc41af16466f2029e776f4d81f4995c936d5b3b923a2aa95d2988f9db950816e6be099f722198b806c51a22b6a8aa964b863be6968ee73972e380684448769e691db484c3f89b84fa1f67be23972ac648dae1e8a6ede23472dc0700951ea33f9d92306287f61dd9c28865004af7b7fe072d6bfceb0529fd725ba06b8813dbeffed51923465cab774e5b7958bb836786e0e8b716fa61f508722e634b375236016146373559a2238705573ecce85927fb978f28778de1f4b0041726d06a109febc454b6b066d7db2583525bc62d66492b3f68c0b8e28c0bee326b8ad5d383b166ee5038a499483c4604f44bca86cca11dfec063d5d688d4f73d616bc725cffad3aa6578342fdabad5bbf5b19862cbce0f9781817e648fb8a472849b4c7e3c9b75bad5056c87aba6521f65d816b92df029218de220b885e0df72365833ca80bcb53e2a9f18652cf128f1c80ac8274c645eb1223287122260d37257652e3a0d22d58cbee1914669dc4cb1fdce7604ca58e4e2e0cb5ed308b2c159b028549faf17bb44f26241e51bd4d879a582a4153fd3649b105871dc40b0b5729638f9d2ff46e436f088d62f32f9fd2a148d1927f3337c90d1b4475a25daf472e4d27621b5cec366324d5c51f11df1397f78ff6ed390b0632b2ea8df381f046b7ebf42ef9ef1065b29137c9040e59ff5cdf6fb45e75f4259e7017b5fc33e7072fccf7bc2e110b1c4a579dbaa1106a7dc96fc599e9c905cab6dfb88fdf5fc1672f3d5f47e2adf0d620c061ed2229586e63fed6455cf49ca7e14f114d2754652725be513bbcdd52f0f9011746c2d437fc36009baf3c0db6732bffe00581137c0723d34c1ff3a466d5ea86c33b316d08f13917205aadf2378b039f94ad17ceebc6910c0085c5cc9ad2ae665dbeb4fece2fff0b44a2fb38f0a4fe242748e27e689164284cdc1a61857b61ff1b98054d1ef8fc78434dfc09dbbd04131f436e5e8f02c0d1960b0d76059df10681dbf3373699868ef65ebeadf85aa96a71902301ba3103d49c9f300e6112c635c3f86e21605f3d7487957e593c2210b7da68f6d145e65dedb5b6c75e67b6bca15ed9dc3765cd27b7c94663f41372f1d40d7176a47d921cb969d88b66e11199def8858e2e44638117b7a9f7d1b38f904433af51df071663ea6d27f453f576e12b8fbec7fc58f7ddda1994ec6dd12df7ab7e0a6283d8872f7b9b8fa524325a9d4bbbdcbf2ea04b6caa1a34e697d75479d54246d6c319f725c3f6d16af300902346b5be8d75bf35ce7652a42e0938e559f6fb10e16cb0772fb54b46a3986281893ca72560f388375808f8acf0a33c6af0d2f0fd5a54b8e71c72f3e2fd11075e783c9dfb69ae06a1bbc1d4d7d89c7a413bad962a3fc3404729e725361ce5869939fcd4ba4bee6afeb056cc5cd87a5444901409fda04650a723a35b290b1e0d7e2592df2035d3c37633b79834b88840f8df64f55cd0091e0721e1694fef7a36065a9c3bad0b88632ae2c9e51102bbcfa7ff099dfb1775c6f096f37ab8232bb5511ca0f7665e9d3b6886e23af4ad7f1926aa63d5c192a6f7d72339f25677aea4108ce75e9c34ee628ccc82d256a795801228ab32aaa87ecdd5312c12eab8fb8dd5178b595fe2cbae9ae8e7b120a3eb122c3ffb5e5904083d872fef98fb460f07be24be1d27d80a0b9fa838b4e6ff2c630f4bd146c75156af242b91131fe006d2eb0250bc8bf5e386f9967ac916bfa500384c7e3e10fd4dfd44fa26125dab940fda6302b6aac07101691677ce680c03ede8db6c7573482a738340b1ef32dc652e8d0e5efd73d25feb70f1a7c4c6641382511c78ece6db52660380ee580d2de74c93e7646f790dfd3780a18fbba71b22967740f42d3f0a40c5972bcd83115f2b395e28d73cae6abec92461ec7fc350aa6ad78c4e413a3c5f099164ed7d3247323029ca6e85db068b9e731610ed19875edffa48d8b6ee1064a61339d7f99306c988dadb6664aa91a6b6cd8b57bcc968823b56a58d2e4ff37cb8067b990289e8ff2816c254e01cc33dd02de13e64c8f36d711570e4a5e0df3f1a57260130d17f45fd5370cf61f011237e44872b718993692fc2c1abee1571412ad6e581fdb944fadd34747d405d1857f0fbd348dfbe49f096a27960957e73f078e01b5998ee1917a3c4df4f9771512b2590b4bff38aeead93966e1ff73395585ce724a7e60637a2fa610ffeaddd3b4b250d8c13c7e907cbb89365c4ccfee1a17a7728854e791dd3dfad9adbb937dbf6fad866395381915bf883106ab57f5a2d4b372688a2216dea188097193e09cb576042bee62fd93ccfa1a35b64875babdcc14362fe9c2afa7a6821f04b0b16a670cb95a220068459a682e7e448f9bb8069b0243c31725e3984ea6de8ccd853213ed21d30451cc7de3f562b94e8d7abe23304e722abab861a9e00cecb56997496a22baff17292e2ce03d24c90e9fad4d8e6d3b6e5f7dc9c4008a9bc40440647a3de0f264daeeccecc79c02ea5f2bdbd01415e1187b4851d3f9b95009ac5fb0a69c6e7ec7d1f3c4f09b7e94d4da918c97482de24a8687c0c6ed35a0cb8116cca9a367b49aa03828252d14e3f8673d5e1ac080dd4bdd455b30c901b3d99b295fc53c742f630296736b0737cbf1cecf1accf6a49c72e53cc1f26c57ba938eb1b273caf2b383cb33281177f839a7530ec8c7a1547d54fdfcd0333c627b7b752b059ab5f1943ab86e7bf5c94b1ea0702248146d7752726a3614f89503dcbacf966da8731f110d564964dd9e47bfd1ed1eab381a5f7672b448f0d6bf5dcb538f1e94a29a3188311522a2949d11aa296f4fbad9be525d61548eb2739576338ebb3a936f1d632f822aad011e66f469d19c7e44462616347284ac42cbc409564e223e03163ba557e293557ebc5f0401c0d274eb1a633ee068e9188a8aee3bd1c8282e0a800a7df1ccd7aabc9e08f6e1fa1555fd7b339fdc720dc510fb0a4b3ec635fd23f86edd6f753c40342965c203e2941aa2191e3f63720db008c82a50c835db0be54506f7e29bcd757e694cc0e7bedc1a539cb330b24ff8d344270e598ba9292d9ebb2696da9e832819b9ce685e6c0c8a30af0716ee4a7b29068dcc7c65df1dfd84437ad8e4c23aa0a49327ee547e8f876841968d8972422767cb3b9095b0584ddb2cd25a4061e4adc319a85963b45826be5e6fab64721443931a9f5bc97bbd9d8f830614974b4f3bd3b1513530c6da8dd44dc423d6725a7a7e35f92d64018004a6d991077d098b06ab197efc0186e3d574b9e0ff7a3f10709fce192e84cdfe7b6f40dc732a76d1001c499eb010da7f4065e45e693e54e71dbba320b342949b6fc63d7480b8d3dcf8e51c57987aa18022ed044df6920cdc9b905271dba4021965f0cf6d2161cf91dcf59e20894e04dbcc2a6a689c8b72425372c82c26a18568ea61d0b52b6ca01691aef501d11aef60cf73e9f5c03e1725eab115f51fd6fc1df3f1721f20950b7045edc1b758b4d1a3d67ce229667e72268845fa5f778bc00e717308185661f2d25578f0206898507a6b544b5ae5bd35e7cabfb1655f0e52911a679531e035d94264d783776c70dd777e5e60143c6c7259847aaa7bac7d2aa179fb81a08e7be2f4dd362bc19eb8f1adbe70ec8dbf02724a07c09f50db2de0547e9eaa2f2bb40ad1d964a88d6515ded8da1dc96585f512accb6479350a01748db7b64da69ad08afcef0f46bdaedf618bc056e1484dd121fce61a6026fd465de6b06266b24b660ba2a0974d27453411f71a1695bd7d3972d9b239045e55ede3b349d06f883a9c6824c349c1a809f73bb30027934b575748d7ad17c9e6a1f2c110c8745aa830d6d8985369b342de09af31eb818fcb0d3872627d0522d92145c20bfd6c37d17a945f54ce00bb5c6f0cdfe48cc085d7835b01db0c3d95fe04454ca956b1069d85c8a69730bc20cdf8590a4015aa4bed75f5217b0a646c77d8f9d0030d817e4664d20fea2b259095428bf5e0c9f46f0b675c72e09792bf15980e220c16ef9d83fb179f5ba0a30f2fd7eb0253d868a1d2d899130df676089e214b466c10044bfafc92663508264c68a84baad8c2eb90b064f3727186accebeb24f57a12a08b3b643f5be16a5748bf1fc8eb1e8b5b656b0fad572ecc8fb65143f939c5561ba600d428887cb242cc84b3bf481ddb3e2626b44ce5d0a10917534ce6fc662795a30f34659b1d680a65cb7d734550983ffa0272fcf67f452fa08bd54da192bb593072a62aebe50c83061ec8e0df5377ba652917dd072e48016b7c6b389ba52774aab9239c5107c98bbe6886429293fad58b27cd9ef72c65df88ea04366609b0c3b09cfcf6abff4977d27d043b702c46463a51bd412526f81874eb7c8426e86c18a768cafd4ade8e7bda8862f2c60d3105926dd52893dec82ae56d6f8009b893dfa08e3d44197931e2a425ed57207bf8fa4b77b0b88729d4a2580e062ef8febc3c3d33568b0047b26a7697acb9a6fc6bd34aec1fae20409616439ba77a6a9596fb251a46207aeea7f95f354f7fc4560b9239406ec6f378e1c2a028cee5af47dfb2fb0365bc3c77c8fd0fc1d4f8547099a2473526c431c19935ea714d81067529bbcce87ba6968f3e85c88d01bb3757698719aa8e560633c7c631c78805e77b7d5f6d4823bbf2f765175e850014e1e978bf71beaf51772c2094adde226d78b3b083a7501d0a51719b3ad22e29a54d7d50624c5c4a73f72cd7b4038ef38bde51481ef41f129ac63e0c0cc8e067f00f71c057794442c584519d1d1ea1b420b023898e77aebdc458e138a1d3faa745255c9256fe1f0c4a6139555b7fef0638000d491dc300b3bfdf7b724021f95746b758b0dd3f7015e427281e8d4fb18aa943ac262985611644f03d0d6f4458c262bc09dfc45830194e5722f70de224016f75679e1a0292acf3f2582f7f5912cb9b716301f485daaed217231df97226dc33103aee1f65a3b4e0479b6fc3380703527b68cef199621138d724a140b4a9899e61f8927968020f4768da5c3ef6828a3068fc9fe3bd959d64651e950a682cde953f15342786d091a013093b66d8dc3e72a4301fdef4c64a7d77223b67169c5ac3b7e375547f0e5c064dc0c6636c99235834703be90965ac5af1a59b14afabd7f6ef40467cae528603385bba940d5099b8fdf0886c93910740d72b021c02013d73f72a4ff270785ded1b3f24e30cb6ee2e57d42897848771ae672a75ac1998f0feae55821f2f999ae2755dd23597ad5a74aa4d447f56b4415bd6e44cadfe246f02d5d1a9cd573048f2363fd0102827fde2ce293ff7868313ab27260d8984c7689f7cc7334884a72fd1c277688d59ff384b3bed5968996307b347237d011f8c98a98d90737de74a025e0e48f311dd9a44a850049db442a2381d7148398990dd6c7a9aea06394be5a1f801e07ff56ed9ccdac836cf40052cd70ba726c8bc2fce0e9d868f453014140e3bf3bbc4228fd3b2204f5ec85f8688888477282f27195085f6b8a9a89403375624ae35f36c21baf7d0c47cba8fb4ebb199572d1094d43aeb14edd9a7ba5ca83a178318a19c0e5d8f06c98b43ca8c36f73a02f1ae3180e94795cd53a82e4df5072c2a7820921f8804251e09e7b79b6a5a4364c418b46a5fe4b7a2f9cf51297b41e533d33bd0ad007617e3b64641d18981160288b9bf94a0a175a1d34d473f5f63c8abcbc46f5506bd795677cc9b8268ee56e7243ec79a8183d91cf68215f5c75b451725ce6e36ba72dbee6ddfd449ca372b42201d0653ebcd4acb2f7d571f3c867e744c9ea6f287fab45a31962fc99991a8543c37f0dac1d9cd0b053ea749356dc4d15c6ee7af41ceab8727bd9ca10e5ad91725a7073cb99ae4d7be282e90310c62f202d347fe40d115a1bc8e078b89322b972032d86053c13902d843a12bb47ece97e1e4f63850e515d0517dc56ee7dd3e0721d6541dff286642a7fd01e33c11daba2d201405056e7c747353a5ba8e501607250828ad090564c1c013e20958e343cd4979a5242aae46c19765f24d54b251d72b852a4ff9ff2099730f4ec3b13635e7f922c646144dd94f48521e16ad6511a72d416455ab601f4efb3e8318128af317d6d43ef38b115f9bf36e6e75885e2be723a1016e2e615f8d4d6d7d3c4d4e2ca794857d9779ea8ae30991618c6823af00f4c665b5de9ce02d4a0b69dbb2eec8461edc8dba2cb68ba161cee262ce9a10e7239a5b8d26c6360a4c9d4deeb5a4a3724202a4f4ef9be7e56e239d978510421722a848f9c7165e7848a30c34ec9d63529bd6fd4f41d4f6ce2ba42b9e50287377294a6791d5de9e10acb0d58217f7f7b1d6bc35c1e2554d3608df12246f2ae0d5b71e139ab454e8bb1bea310088ac156cfb6016f086b1175a973a364807f68085bd05f62cb27e76147a4e27c5b36da0f8e2b66b336cfcb472b7635475ba0625f222135ad819005aa69d289965c4d878e9c65e1f112cfda88f89694d6106037155bd9628719f07b239284c9dc5996dc55d09bf87c191a02deb0b13842cd550c172b83029995496116f718ffc96163c220a2876655e856aaa8c5d6f969ab8ce1957237f1bf58a15f5b868f42d458959d6c561f066c27e24680d62ef976f7bc651e7291237404e238f0b8815b35169a31f88cb740cc0aa568ab68c84aad2f5d1c7a72ae72faa60bc60e618c01d4da16444a334d36ad918547a812ed654f84a8a28372d8ef40492976be38899a02227e7c856762229311bcaef926610ba8ddf8bed07231c244444b91040c2d080bf9e1b811544c882f4f3dd73c86e68c363daf45a221d3dfb74592144e0de3acc44de85161a13fae6f210727b3d54ef36bb51c9ed00373c225bfaa9ab5c1aa4c4d43658b66304519a84860e2269aef7932bf99d8330060cb802cd4c5d65159f130e53c80579a0d2a1acb04d4e0edd547ab529bc87f0bb321ff1f4619024a553cbe0d47301e5073c003799779476de65acdd32f6e1d7276428101e534ae33315b68e5336b11a6a27000eecbdd91e81b701245c876977203bac67f19e5c99ceb72e0d90326a669f350bc374b54987c75ac4532aa266972da1fc66f43042582772ccb5d6f87647eaa922f07dd6b357d07c06e8012725b7277ce1591e10d713c8935f29e7e58eaa31256a5013dbc8356d4eb9f040c167872f41338ce08fdfee3d40aac77730eb576c28b8fa2bac0364bc09e2dee18dcfb7279977fdb0ac82bad3f9f2cf49d5e8d318f487b2c0ae0d57182e023e86a3a924c15226de35dcfdcfff0b9c9362dbbb6d5d6bf7f1b8dc3b0966d6763c10c351a36bedf111648d74e24460a3f3f94b08c99a5beac905779300becae155f3b33af1cf0ef1530c1ab41ebfd28ea203c72ce048189e7333691366635dacb06f397157225d8077363c31635534b8c29a00317c74baabb7ab60acb3c3daaadc40660fd6ec10c2062381b3143c7526433369ee548cb5a1e9bab5c5fa1af2ddc2e757dc172f15df461097f8534795920d39df40884d680cd97521f45f45d1d639870a68d19610e1aec2cdc3438c99f17cfa10d3363d7014bc378d415a1bfa2b23f9aae8572314d9f94cdfece29f980bc76dfe3fe8c359f0e69984a33780506d63a15af5b4b05617aa53c1682384159eb7e3bf661af05d150f8ba48bc880ec4f6f724f1e03c2df966e9b1637caa04a5355999b3921c20e87e047e1a3975ffbc4783bdc1b4725d2df5c6dc8aceea679c3c1f386262e66102779a8bb11b9105d50066d6ed0d2e3c4ca9afd621d3e651736b50f5f59a7be7f6d76819271dfd2375144e5a600e1bb3838fcda183bcf787f74aa0eb4cdc38ca51b63a8ccab79404b72f1053327533993890bafae063feb03a3ad8959d07448cc87b5993d95cea779cb2106dbde9548dd862c5b98f0fb7e174f21d5ab377a9c89b4e838b9b680fffb0102bbe6f960720c5652d652c3fc57552bbeb22d07add8a1da1d4d7bee29872b3309762f7a97280d2694aee0297aacad53b27d18714d4b03e9c6e53aaaa73ec6c6f89a9f35d72ff3654fdd5c40116640843b50798f455657e7077b83e0abc231b0587a9d26a667941349daf074c0621af7be809412e006bfdf75ef270b2154f9194d6662920707514e585b21f7698e0fd791b89cf9093bb61c8cc854f25ad935330d5faa80a7205b9149fca2edb56b2d9a6faa92a88c205ec6e1c06665c2e97670d68700b5a721593fc885009aff8ba1ab08ace98acf149542322feb675d9c2546ad00d58bb726ebe6a65e1eb5f1cda7ca5ff231386b3b9b149dc2d55d18d4909dbdce36bcb729485fef2c86edfdb5418d5001b4f6cf62572be6ec07516e3b9fa8e30f6c249726a1d912729f0257c007d540bb0293a30c48c48d0db0964e1835ecf902a938b72c1d13853fff7b5092a698b4ad2f96a847c75ae9f8ef696be834099bea6dc0639b3ecb577927abf1f4e1a889927d763a37d4a282410a555cfc6dfdfd8e40287722123ed2f4beb952141fff2e624ca5b2a24ec49e372f00c2ca07a2d7ed15d9d21451dc408bce9c8b27cfb15f3ee8586af5879731d5eb63575f8d3b70d72ddf572f1ab40e70060658998f5105fe31471c80b14198ea0ff41745f7840c1c4484c3c0824f69265cda480e645207dad51ce5b5f34f3113945fdd209d5dd99ef72e972100b45c3d20c8ed4bb05d8c4867d7a472e2fc450bca8256376d1ce1651cb9572473c6baf0ad4e24ecd2568356a77f28704121a6dfd312d2f719d6659d454dd72a4974dd677fd125bd72f35cce0b76cfa07c99c1e56b26ff4d37eac7622a05c149a2c074d3f9c33d6baae922ce97cdd5185d7aaec5de2c820d07c6da3a2486272811701004f4a35caa9c013f35a74b110e0f55a3ec36824cf7ce7f2bfe7e2d51b0f9b40c1c2635505f3234cab29d6b0b3ed074abda2504255f8f1e215c9c41472b0954e9caf41d5ced45238815622e322b42da2d9f0f2595ce9b8fdd2d595ec00860a9c3d29c22428908d5d91e99329183d2fb66c943e22b23c7b5f5c72080b00c92776e53cdd1dca6806ff3390fd898d645f7028dc60c281b2cc43c23e1bce0b19bce3cac314ec8dc3bd5e01a99bb8715b536275e8269175955490b8914a615d170e9bfc5a015acf5ddbfeea474d4a67c695c22c1fa9ef08261696545afc31720985279c6a5838115ba52b44ae8175a9e9b7aafbcf8b949fcc8afd90d77525725cbc18124b2726cbae139c4c726ef4ca09da2f2030905efde9313a8154eff67224efcbf18875e12c0b84c0a002863fa3818f46d7181d41e066aa52e1aefd0572e87cede01ccb2e98e4584b0dec8418208250b9b8031bbcd97e13310d2730f572365623753a47b31fac5ce26244104534ba304092205c80426f0c3bdd5a563d72922371a879ce662508c8369ca3dfa04bb45ef10b236a5c87ae3bfd46215eaa50f8fae6ff922b1e6e27358d79f1681cc5e29e0387ed447cb9e8653fcbcedbfc72ca3c9e0e63de65378c85b079a6797615f80a847a145aeeb2b14e10ce7288a172b25132e828203058d0440940d66167bbdd1352ed2716b6899d4ad7d2dd53ff4808dc01d2419d291cdeb95d497bc19e380e59ce83d2e8936d35b81ed83bbc1f4a4573e9146a74247d17df7da1a82fe2a02efa9aa62b602dc1f23af925295606726f0ccdc63b72cccf0e70bbfd78889e3c7627275427118129f45a469ce116cf2a942d40d928695425dba3912fcbf56fccf44a2054e92d9726f4f4ba3c5129e53d32f3056f8cd1c1164be028f15e4ce8ec810bc081ea33c54eb2da3d224bcaf672ce09adbe667f7ea02fc145d4f78936f9fcbd785d5f64573b56d71c1b7e5e44723708b0e260db63d037290dcfca4c39a2bd7b6cd7b4ee1d027e2daa2796cfce72c1024c45bad40188ccb2a7b2b15bff236ba4b1c15bb4297a608944cdc8909a50f67f26b19527f59c20696463a49daa5468ba20c0369e898ae050a317528a8d72f98d6ee0b913c321e9d04ad331643d1623e1f11215bb9431d9edf2282b0b0172b1d01a71faafe3b142fd223055eac7722937ff841ce0ec93f99fabed10e6c97259c9b4fdadefa60962b3790d4eddaba43b5f8332d93f64a4bffc0cde8fa0e172889804bdaaacc0e26a0442bcffbcab18a66053e271c1ed7400eafde6ea756c1d604ecc270797c643cf4d31e58e4a2c3bd0611817b4fc3d33cc7e7a027b7e6972823c474b43e92a468f4f2d622426c7c39b7b16cf55efbb381a148276346e4c72d5d7debb501cc4481d1eee2c4293f91477b02aca693627a5071e3b4643b3e77272f9645d39cae03a3c8205396f465b8be583c5b374139ebaf9e7e20fb684cd295b8abd2d6bc829b6a99260e4797118f3c68a77357e26eff5c3f7355a020001727ff9d4a7c825a2e94b5d901e40285af843b1487943563f0062105d7f25328a724704d198be50202e7d1145d14d68fc11bae07fa69cd5955d10f3f1ceda6d6e72ab18b09af8a7dc55d36b387ffaa664b8de6b11a4dce7a8783c967a988142dc72e0a218ef0893dd0ff7c785069f63c67377f079b32a830ec34c889e4d0fe83072ecde07a3c369f8c86e522d54d80729ab14d5d6926c42c9c20761ffc067c9f8284a6183e540ce63df728a4da53ddf4b6dcad26747b4514930053bc1c7fc2d1d723fb04da23549685d01348071b212091d75d26401a9953bfe002f4ca31e2b9472e969cd59018f61c0bbe091e2e1a08f0a3bcdaedcd1605e9efd00d64b079fdb7275281aa4ef6654bf2d6ab7d08d30dd4d9f53b00b36ed04e10234a745b16bfb369fe9b1198c2873c9f57b9c18b0a853736fbff631701f386de585b321ee7f3e723a319612cd80c0a68dd10cb0a00cbdc46e46b5d22b662f2853bfa5061530065b8c5ad798ae8dc6ec54d8bb1b39eebc4cd0d45464cd818142c8c26b848eff016b7d74b1d5d294500328d68152948eaab876b588aaf8c1d8713f49729743e91e72eb56637ccfe70260613a76b129fce8dc6fdf7f760602547788a2e7ee8d77361bc7e43210daf416698db8b05e584cae11d0e6c172010da9846ca055d1159b6231ec7c1fa8a12a6cb746b2046de80aaa96fc7c239a7158490c91029fc8f4d2a70792f504c53b80d40272ccd14040c83272d37c3af6d9833dfe50f7a39ca8617d3de62bcea719e7ecda2d6ab1f85b912e07688763cda768355b226e4a656667c03df790c8edb152773ede1c1d5323e98a889f5df8ccb4576752058f27b2f042f4114c3777106dbf02fbf2c99e8f6802693e25f12e1bb2d7116e2f4c697d30bcf20de9e056268e1e1d7766b70f094cc088daa69c2e668c3f0bc0608e5477770b731f3cf60b6cfb5e0ea596ec290057e8048804f37ee8494d1a593ae0b3af9aabbb72d4ca7fa8abe73cdb3686c45d875aa27e1ac52b8edab3825fcf388bf33981c9208e48fd477e1c90adfb43b3ed57e0a1e269ccbec551e738b9fc09bd6256aec6720bd96f4ea0c7491c10ec569433b5f87e400049dc5af058744c0daac13988b312e1a0852b91cfacea84133cde0083853c743f0ce1d29214e3f9aee32cfc206f0cff64fd81e0bd69bfba3c04e384d36cf9715afa0597eb77c80736db43b596df724cdbd4f5932a58337357db84cee42cfae16d1df93c5bcbe97994d4f90cbba34d3ecf40ee7b9bf30a989936742f73f8abbfc8de0e3eaa84c260c58f81ca19c97216b44bcca7009f8054eb852ac5a56f9f1546a29fdf0b09187e496bd9838efa18c5a736739bdc77b546141cef70d414d1aaff47c3b43228b56a076ae19570c36270940b5a539e557af6e53d7245eff41937f1117a9c5c5ea744a4c78a588b645d56b4d4d681e0a0dc032f3c02476ad3fca8c32b8c1a0a558005ad0b7067613b4537ad2540037876db93038fbdddfc7f9529e6c31815647af5ea8d24778b6d41726cc43ace7f0def78392fcc52cd9942b0227dbc956f9532fee02d65271ec97f72882ecb60f6d9d6f4369554c17c4ab034afce2471778c164ad1e4759439ae4a72f4e3d4e6396f96d1e535ee2966a3e66f2b952351bc1274a782ef18e04e5c6672defd72ad8cf5f5ef4afcab7f4396d9acef264584116afacff0faf329d8e54172ab50fdaa70c7a69fecd89d9e179ab47d5d7cb5ffc47fee8990d197800e745572401777c1aba28cc6202b0285551074b6d4006c8861d616c91c5af2613a1733442953a4410d3d65cfc15537e830319580249e0bca48911b8bf8c0c4e2f37ca2725c3a71f082284f837db361a29bf69a73992591b9138381cdaef2bec553ff93058ad0e50723c681639eabe65ef929bb024a34eae8c762ffcf6c761925082243728f6fd8163d9cdb59230daf2c15389ecd661297a95401b3e17768452ba3345d521e2056fb9523776c6ce2de06cc43d6b8c5b2f2f3970d4422caaa815351894a06844c61273e50a8a35ff15986d2dcf78e46363da588c6f8d462a8ed3c19b6ac7294e008399a8f7d160a0c3eea091dca61d5c1e78ab05e49572c7ffd560b65c234aa837b4ee028b8f156d8564febd6bcd2d8ab4bcd3d98864b0b273ac51b15f149662a9c14d6cafd37f0c4b2a5331340f97b7d14cefacab87371706eaf70f23351d2532fd94b495ca516abafba707e4e07e84502765779c8a5c2a72e3b2a217e2c5db4a026a573d4f4bb9dd244bb6a075a701e8ee01d4106088d9840224876c972100c0d66c9a43b34f0a0f6f2ee18c99ee8b4ce3a6d9997c488e0940f6c9630722857601f514359031095005b1d1c8b80380f92c0040bc5cd29c24d69a5b472728875c0bfd0f5503d557608a6fd3d262b768edc5f1a1a85f1afb78575950eb0693066b5dfb1a3ed5090ca9979605e6686965392b76b84465a57ad8679931c8105e7032649b7c55d7261db06941492c85730b73f9b3d4955a98eae8e7c083dd272d288c51b91b94ccf5fb7506cce11eee025de6cbe74faf0a0d51d0eaa0fd9e77216611ca8292d15294311758490f212e68e1baa3b906f0295731e3870a09b5272d1912325aa3294f0f27a866a1a3549d1efb5a71afeea5a5858eb81d0c3a06235bfcde0d01a2dbe835e2e0e3a90a407088c6903c6d05c865522604076b06e5d72f412c74c3047cffbc9964a476dbcc90778939a30934500a3c32a3680d0da687280cc70d0c01dbc47e73f24f95421da15b2737653b36f3a3caf0410ebf99dfc3727a00137dc41291b80e18bbc3ebc2a6c0251a1fbf90629c741159c619d6b2607b8923033e2f58d407160786c75b2406e0ccbf98bc4061c166337e081b332cc7259165d0a09293b9335d6d7d5d4a4759ee3bf582a76e03e346dcfe6b51bfca372caf11812288a4a5718bd3e58643e9133e589d106c26b548c619fbc555ea81e0d6cd65fad49292b3350247e45f0aba2c45ead2a39fab5cdd54f9af653fef6356d458a7c6cb6e04fbf51eacbcfd346c2d352e9ce6d2bc2283f702077c8b5d4dc2f38c6e6c8f05a5c3c2fa72c191b6dc731dafa9b9175b3b0c2e94199fae59f38483fa86c94a9b54a05b3428342f65abc36732284f0b082147ab26abec30e1b505635f96e46bb1dfaa9e51c5032e85ce20402cd8c19ddb38eec1b3443b2df88af417c8ea57bd5cad9244cd4042dd90d5978c4c641dd651f19584a3401a71de6d706f3b619ac9c27112db85292e2b8213fce3540cb35e5b1879072c54fb18456e2205c98da4a609e652d4c667518a4e62ddb5f60c42ac52d9ff359e99f616cf3b2726625ab96c741c355bda6ab673ba20fa85ab3d1324fea39ad9674b563b88b6e7238a988925223606fd793f88300b758ed83b084e6a805eee342de78eb5ab2364e3447825eb3aed0ed4bb02186ec86b1d0945cca6006414786f5f8ba13b4662072fc729d63ebbb3668673176b1e28bb4e5ef4346cca26e0e289d181d1b4a718b72931daecd22d7eb428c1ecf4ad4e303bf7be37159af64b38054b93d2deead987286d49f974ff11ebc033806689de8cb0e02698822d393031c7dd74757faac97725c01a77ec9ca18d027db045460377151cc159f9105d4d4d84270065a60b0744f1aa512b414f2d9bc9ad3e716e655bbbacfa000c384b71045624cd4881eaa0972ffb75a0e5aafb4351f741865acde19e6263fee59525014cc85a40236781ee8728c0f9a98be6e3241fbd02019d809698fe9cc5ceb55755006f4b1185aa8aeba723ca84563c0fd4aff13f8119c3604dff5915c25d8037eecbf520177c62136ba3c76eaceaf13fccc295f6ca9c483b4c3aafa3897362d0e8cf9610ce0980ca50771aea3d53ec7680b8e8ee928f03d1beb4187462e52533b19f42b7161142bb75d72ce8b681a118155ce8275b75f8571dd77c9b2ebf0121a2c01a7749d7898cbc4725b809bb1375044af6a3498cae27d600189c3abd176cb06a7f0eae49fcbf7db3d1b5f0f3af850d3e008dd487b45edf9e1b9ccd601662ce407ee7b2bd82081b8724184c98d8d034433c92f9abb71608601ccfce562b839f08d857e201deffe8d59f20309916851c38759613a65a03fa3008daf6aae71d03c4c5b8c37cc91356772aa24529d2ed3978da6079a07a451883c9c37f4d11a4a32ce4447a3a6778fef72cb05ef2cad61d0387271d1362ca9904ba69adc6c3299deb35bb1cf0c9ffcf3422f14b66aafb56db013f9b8c7993a87b3fd391cc54f599e1c5284db2221ef1f5d9dbe1a118fd8480523fa83c21921654bb6b18c58baac270232326d5a37d755724ebb0f4274cb2ff459bd30673e679d242d7da3c554c1a063ecaf087e8bd4db72126f7fb7e10b1de80b4bef0bd8c6257bccd9ec2a34e2615bc3973416b0b50a72593b051c6e6ab4dc75b3dad4444fa591c1253a57dfc1e1f3c82f4ce6d1612472afc8d8c63655e861f04b0ec61f0d42a627a247b446b407be6e59668259fabe7250359f7a72c511836fdf189334d15672d7bf38d1596c31c903cef6eb3e81d369f202c9e13e430570427fc7e2f57c140243ef0ecda8be7d9e5155f21d30cbc5723cd206cb380195d03689d3fd32e70a6cbad5a979a325068c76ec6c959482dc7246ed216fc59a5390c68cdca122044070b379fb43d293efc205d97fbc17882a7208b0d08637a05361c4859707787d1ae5e3ba6f1b3af85eabd13e994e61b7e5ba0200007205f02d43fa06e7d0585fb64c961d57e318b27a145c857bcd3a6bdb413ff7fc7254cc6696747820bde1feb894597e52da063209f92ceaa86cfaead2a56f694254ab77fc148fe69d82973c5a00b126b9f1362c4d3c08ad817e29a4017b9302a92e4b6e55181e949fe6d72a866b7f96371c2a311f6dadd983d5521277c82e7f8239052ae7a055447c1c3e292ecaa7994612d230438adc015564e210cfae98b2dc3e6ea1cda03139572cf09cf203c79685dcf444d847fa614bddd39455a937fbbd16c5b03b0848aa7de4a9a1b55aa991d4d02b0776248ce687b413068f54671f97729b5d987f39ef476f2898e94e0dd5d73e09d96a73850b6c5fd46bd5fde3ae5a725ba3c43800754c383d05eac9cdf19a5108fa76e1df5f5324d5cf360f1a501172957081e63589fb8f1ac519069d8e9ad9cd25092342cf28df97a922999e148a517ec881fb8cca753e79bc90f655fb8f60d29f44405bd8671a254dec231401f1725a89e9a7913c95f5e316dfc1971c04adee868cfa3b58b13d5af9d7f862c6ad6baee0d8e9eb69700d0207550547dcab2b70d43ae364eb5a269deef06446b09272c376f5bd48284b1936e0a5c1cafb49c9e867c71b95bea06b3a886b470086fe7218e3fa898d4642293ee1d0b2eb86e22bc8342244091e6d2e3d10c89a8c42292b9c1892d456c1ce1d436426925017da6a95963cc4233092b2e392d0f9f5b60548cc91a293db9d0f29477fe30f5e5a277c9e69821b05c0a1dbf697ae9209fcca7260b1ddf8669b4d5ba1b87b3348385b9a480dd001377d99e513c0c791cb39d14218f9ef428adc34c2f2c8f786b059e0521c13fd17b3d0b38ce825e19b233b82726f7d839952a349106e71a057a11104e9e2d3e90235471f5a8e3bc9e7a4396472061bf4b3ccd02aa723d2505d97575dd4a9bb1bb8d04286941d55d6ea16237e05f855a1beb03eefea6106252281bdac6ab96a923648aea5a5b5c249c89700d37293101e951c62d661346c89707624d3c6ffab389c13288586d749d6f2aa758c07583d00f1a79bf4aaf97d5e013cbd7a0afe01e5193209c31cb5159c15788c2672790d57c6532abaf7efb4636998ab8cb6b576c1ce886a396daff0ae3003d4f07207c0e8c99ae6e815dfda9d146aae112b411b310e9a3edd53a53a3d251aab33720c7e93c2dfb23b1a843da49f8afddfd3a9fe267b5e477618bf696e46ca99983899f471fd60036ac9a7bd3a70a3a29d0fdf6920b07b3b5fbe49a8b7c8b3a92272e13c581151433dc79ec99d0cc0acba9e05d7167e655d985662d4088e71c3b0722d8a05f545c13499b289f942a3562387508b128ef5902605b7df030c4fe2787219487e77cc178b1174b181e8e58250ab21ff0f8842878a649f495a9c4093b17284b7a4e5f8caf214bfe295329428d838007483568dae665ce88601a41138bf72e3697fe912be340eb8b3b0c6c5c45129967df6b3c8a998a89c9d717f1e3428248575c2e8b24ffc8e1d39388996ed2537e42f85e137e9c5186cfbe86048884172308a88bb46b7d9e8a679f6019cb24615328c3756bf4d71aee15c750ddd267272fb6d0eb30d74fd6e1a40fb5aef7b8e46fa4bda70e976e05d3a0f4e970434e027b1f53f090e382ac98a47257f32e631131ecc1d12639b023291120b78f3528c7278f106551a2cb159f972576fa70644bef05ace8734b8733dfc708f6a073aa672cb75c24d58bc597f7ea89ef8017e000633f4e1ec9041f8301016bebe08be2b72b7a755cf876485f126e7172902920220aede7bd349df158e214a0aa6c2dc032ef97996c71c13563766405b2379a12a42b274b122497a6942cddf3973a883fa5266cf7d054f1795c22510d38f0d7c2a29f788c4e3a2b0bdba25a2fa4fbf3e9f7266caab22f09d663b6d90911dec2413cf7dd25dc58b3a421b7962811a5da37d5b3f5ca2bf6e5d8cfdd1a4f5531cb3b0dac56a5a791771797882e9506257a7fd72fe21b9e2aa53ab88e2a7c12095abb34497350fc0f67bd7398e584049fe47b902f62b91f8cced87d16c09b86649760e58e0d4a7d407855c973a835b716a01ba608ab2affd10ac455fb36de16bebac8688d8b6264d0dbb8d56f1bd7569deca7f72054b88a2493acc6bae484b8470149a154ff724c7cb4c8c314b237fa1e610261944577cce28e75938a82a912125d188923c8d5ac3cda01bf22580c55bfc27dd727a0e2175e8385570d192de704b3e0692ef3a30b7cff5a931e9da6cd1865ede285e5921102d26cd4b4eef0e1140d12055bbb7f56f2f9390fd26aee2858c222872dfa37c1d636476c019472cfac2d2052479a610de69fafcbdc9386f53f002b372959f1cbc602dc75f051736e7dd42039da3973bce884612e61616dcd60b849a721e334ef7cdc1b18741344252737e2c08a7350c2a41f869c928ceb51373025321d9844ff68ab48f58e388aee7887680f2f8a176ec3effa998e862e1333dbfc536076b4450ba8c4b2d59ea2cc993d8ea62f2be10a86c429151ffb2592662874b721d74c41f9f6780890867509d94e718b16beafe4d0b5e789f95079eadc13c51729813222532b66c4e3c42633bcc35890ec453e8fde7ebf54e59fdb41ff2b14b02ea62a3366a22c1151377a241b0d4af048fff8a6b5996ea9c786cf9eef18dd272bd60bf4a87a6ac6b655f57ce46d2272ddc4c27c7fbb3b10880ca48dc4cdbf3527c521f54ed2f799867639c169cf04f7b3eb57c928511778f2b97a16be404177214602b0730890543a8acbebe7cde171abe53d3ea7e7e115e222dd95eed215970aa812990266e59c8c702e71b599385a766fd797915e872528e86a60658b410729253bf6fd4eb699aa5b648af97b94452bb4a10222ac3ab5691aa5ce057996b727e1e509660a7aaa41bb0831a67094bde875f1177d4fd3bc34579dca5e39c4672e95824e53112917fcbee0c33d704bb9f093f56463c2bda3b5e06f51679f266725e8d68e208fd6c0d7aa234b24e1321b15c9ef0c7863993b007b663541fbc37452b54147370de97a7ebe1460032b5128c0cc68bfed9e2db5356ae1c30aa397d5ff684a60de27ee004c70ea0ce40cd7088fa56dd9015560d94a23215f11797ee72977fe3a8465e70283f44e37f3e49ea5c4b9f42d7b56f0130730a6f00290d457299f0ad64601fdee115d99e5ddc5c8e159a0a8f40398e6e0265c67216cc28a240e0c36f0ee5e67f37f9127da95df59da71d37c17db98cec0b75a509db6ad60b338f31b103b2e2e3c7d7acd9cfff4b99ebd50777125ce0b2559fd8aab8c4944c3e8139ab7020536fe5b9c92559be9a86b111152404b146358182e6fdab9171c61c797cde22ccf90ef9be06013ef0d1ad10850cf680b9db646a42858efe0194d70a8bc08efa03d348b229a6207549cc826784c803701265eb13088669aa28219272c8e735f9fcb37b879add904f88a3514447e42b489fa3da6e19c4d97fa5b8fb72986bb75e20ffa8deb17fd79e5cda775fc92e2ed92a6c84bb9786b102839e8a19905876a78e3d2c6a6cca0fe7f16827b5fb7e719f1269182b5499c505ceeff972f72385374ec864455d153580d4fd52c56782ae367b1d999b24864f28b8d790724adeff5a1ccbcd10e69cdaa65e78b981acc17c55a320755254997f8eb130e172487e8195b449cefd85a380d1ab27ec40b0be5d970cda084c5bfdc57d6c219b72c150b628628ed86f8a066a181137532d5696f5d224a96a49b0b7dab4698db072a88b1121738c1f0f41e8dae9448acc1cbeabdbd3421e64645181a8aca6abc6115f65469589ac41aa11f7dc2d2086f39b9a0aa608d69bdfd8b8a2ba56937f450a01862b53ed65259e1657c75f1553c543b990c8049bdb6e03f7511ff8e8b14761a3fc1672c959491e67663e49bc43062edfde7b0716544d07e6964b770f584d7253b21b25185bbec561804d6e62cd889c8497e0f7927b3f63a04f5673187c6972f4ce95d58f6ca5eea2fee90d1de7e2f7ada9cd0e855e862ec3a1c1c02cf3e072eb0e0c4d4b7190cf26b0601bd1b143bb851237d80893ffeda07fff7da9f61425408890f414a25ef964c3f53e265be69b0c58a5c891a563e8e37ba23965c0b472f1a377e3cc6d389692d289f324447332900ed77739c85373a89da67ad28f4472f2068898963452e98ff9a8c6e7302687cef44892297fe4e1dd709ad2793cde27c1dc40ac939336d404788239982ad4cb41b9346f137cb678512ae8209abf9f703d205c6314692d41560692dbb6f1138419ebda3732c87fcb65e7d92acb8564721e98c738ee686aed085b7e5bcc3bd1d0cb9d1e405831e4ab164cecb5f7661d72b9c72ea9d668d218ea452d4a3270e41bf514bfcdd5296fce4d8fbe1835816b72111384f2db034790f8ca3619df60f0b47d38d6305538e31da73e8439a2253072a55d9dbc0841b8a526f6177f762b979484eeac3e6889bfa90e88771ed41a4d72bd37d4aa2e93817c030df6144718ce0055c67cfec8fbf63967a348b22d3cf318f7d37527ae76967aaa6b14621e3287452deb9f43e6b9bff3416b3d747e757a4c8ac37d1ea7c5e9b9a2fea16b0d5d9ae195c44047f7c221147deda8fdb46f3504fef1e11944ee8ee7a7aa8ddd12441dd3fcb3215375babde5d5c0b5f8122cb0058ff23bf469d33c79ef27ca0a8dcfd66e53add0b52a47eb11d589d9a23d41e6728de4cd130da54dcaa0311ca6b032015090fb4652f0a5720ba485245115754d4ea5ee48a4d0634e80a08f5f1f783c695428401c50cc29eaedf73ea865bb488e7269305bff379472cf50c8a77d645708dedeeee2db7b8cc74bc873c33ce293f172b7e26c32e842b593a6e253db066d45213fe830e32611bedfde1ea7f4eb7d8861f04abdab62ac0dc0ddc2fe2fcc089d1d2ae973a4a6fd7647e607fafc38baaa72ed4d5e74fdbf86e9e84c7642fa316003c881e3fce2670ed0d2fb41640eec270a69f733f8d6258a807b55cce607ad194da63b14b51da18499ca7f3e7d75a1de342e81e7640dc2913a1bb6ab25b543973a6ea783079180b2e7e1e18c751d8f5a0a64c0105bac1e7fc1cf5b516ab2a242d373844f69fe618519855e1d5518e2b672e43853ee000806e9fae54f14b771900255f5e2b4258adc2288f04b56c6612972367890012ee2f85c19e2fae252fcd0db998f3fe55cb3950bb3c95bf4925fa872362637e74a47c7b25ab1136408acf51ad341c751dd8e1b8f91e45fd357928972ed869caa13a336d2712ed5f21baaab131b4e27bd62fe4c8fed85e6501b2bea72c8a4b35d3bebdba6ed7efd443f68f32e1b34a9146e797862dedaded8c979a572917185a134b0e6f74566f53b3e14fb53742b3a0c5e1c3ac6e18a081e075fe5589810eb1aaf1a5bdd7d3b3de8b8031fb77cdcd523e2f1611cb818be3fdbc7c67269a24525d2db04af6558b9ee6b61027ac5e95de0b0cc51de7a3a6675357a0a50af92c5522411dc180e6b93f9304e764aa4ba378080a5fc09ae54a28d8346692d796147d6b0900065e318723de32d3306650e3555085b7ff717310b7f9543aa05309c43f98d512caba28753219d601d60775f9eee226e0c3942c6399fd0256f722c8c32d8d0792a64b9bbe863e3cddfff1ce7989eb1dcedaaf15a63be8392ae6277f97c26cfdb49fc3fb716272e248917ad9b23c949d0da71a8e20523402f89728d02853265cf25796c5c9d6b28efa56c9bbbdcb104599e657a0f2e8973adca726a2c72f5260cc06ef26b55bee61e66efe9a3978c13aa645aba567d9895562b550f61f1714360406bce775ae73994c38e9e25dffac5f686e5d415883c2ae9f1191ccc62daf88b131cd842c275dbc5a2792827d5ae3a4fdc7664bffa88986d094021c95d4ae5591d3c87c8324a73e2a7168b4261e4cda361ac53567fcf57c8067240398f7eebc5ff27c23543a5f5993d1e6db76f1b371a0054a8e2f16ac12e14125743b11a122731c23b5ab2f610a95ae1c36458a97e250ca0f3607f1fc8a1017271f814aaf87f426cfa6390cdb7ec1da8e220811531fd99b3782f0a1847647149561970b7ff7d2dfa71459aeec9b839c2b6eb379d071e718328259a6ce6615b4c0c368f44087603713a1877a9e1c9beb8a68fa42c63d847bfba1d2d7f6db873726caec89b184ecd21d6d0cd8e6859a48ba670309a85a33bc07f8fc9c8c91e5072228fd71fd65b4124ad50368d01cba619a7434ece9dec30997e126ed05a51be3708ad05b91e5af88ef995276d6ab2c358a32a552b403356a6e92b5ae6cac8ad19b26b1adfa1ab7bb9c397c93bae187f292b82b2b69f93c0bfa371268b4264570da56db1efef33159252436d673791064dc0894bb3f320af917b386e1da7646b6ffcc334fef79191b3aff870b39bbd3502e83882f58b4ccba0d06610277cc8f672fcc177033e5c5617ace80b436004f007a67868bd6c9fe46fd5538681228b47726cc19f3d256938619ece4fc3fb6388a692e85ae847060a88ad47f5705aabdd72eadd7f32462b7ea7eabbffd0a544571beb86aa92c07bbe23821c1fe487cb49728ee5c8026a79fa80a494d3643958d210d3fc65700eb8526c05393151599fa17241a36fead02747facd6e1382843f4046108a6dcc02626535fed2ab039d67b372e749b7d5d7f6b71d0c70a16e1fb5ce1e4a81a3786b227feded4de1703807304acad96a209200ca003ac0acd07f7df159856244ab1ba1a46fd0fbac4c578e6b72111ab83a371a8a10b14ccbe35fd59f4cb5d4630d9f0de7399d250eedec756e7221b6ecedc24b125ae14d7d6fdc4e522bcc4694f17cef519cc2ea55a0e0d14372fb5ad7110d679eb33fa10831566b26de036be9c422ceb1ab08154305bf7fac72b66d40d2aef68d5885908ba1329fa0a58342b06578c4806600f9bdb196301f72d9098a8e94225ff6886df1d380cf17f9d81120d30a2dd3ed74791e4c95b76a72632c8f41cf9be459a00015651c4b1ed2fac7fa720993d021d2c2914af7ceac15e3f0e276e6f8eb4502568d6e234a0867ec29e624780959aa14f325bf345dff61f4775492f427a6c26f2e0c0559dde8523d11e475e8885d9fe773832599b69872c22be1a275d45067c2874a63b500945d0f78de82f145ff148fcb283e32410e2132f5d64bdba193bdd2966f02141366bfad69a8e9a4cb13a35f3c093a191062529f20a04b6e5385795287f70299b82043adda111b8f234af84dd164479ff55a72493c1d0d6c37d207a5d181e3b8d3736f9ef03ac17e10ebb7e1e70a552e641d1e3d9f291fe33b6edb9b7c8b103e53e357620605200405d6f0da9d0101e9df2e722ab82cb7762b563ed1841dc6f8fba2fe10d4f9b36ac2f903bb2f5e64b02c8c72a4aad053cf6d258ba998eff7eb440e2f4d2a29a34d8fbeffebade35c8d16a372b74ea5e60acfd1d3acf35f5e857d4a15a13ca0cc263bc68769a3bd3c9465f7084e2347968da8549b4c62cb7ce8660fd727b5dba48f6c074d6d40f97db9881572375c888127140d637cbf23fc5572efb05b22f157894bc52d96f04b1ac3d56e72accd522eacd566cfa65b4bb4cef63295d4f3efa1441be25daea09b0b6acf0772d824bd762936158f6a53fa51029681bd81e60a61f37a7f9d525d8345aa25f1726f1b1cf49f7df59539a261c324c7eed25c67350b6d603bb2912c1ff04595b3410b0e5d49553563466bb818e27ee492054d3471330863f22624edf083f90a48726c97c46a783136453cd7e730d48dde5cbcd47a5e83ca4f590a6004cdac22142c79bb6738454ec94d82e66583e089483909044d1fc37f9b280169ec9db8810c72e2edc58330e5c8d8721426155d97bcc9ca70424f8f8b80f3f3ef4a898c93a129012119bd919e213c40151b4931707a8a5f6d0744f133a678da44123a3d84e04dba5c111234f6717095f919a0d46f90c486ce7492667aa7637b09443f8351347263a36aa246a7d4d005f74b8b7414e9ebaaa647fa66a1993e64f671fb1e782c3a7b7f875d1b5a427afadc3aa342c222d311fa2c7cc87312acd7ee371268b739729e3773c127e984d119c293aae9cf0981b92e434db90dfdb9e25f35873be10a1040ac68c5803a08fdf061636037773ff6b96f4a25018334fa0f1353f332707a3a807fa61a1a9f1620494c911fefa41b1fcd1832ac80a0e4d3bd250f648b2f7a16eb1eff691ba4d22423b2680ffba01f83bf98eed6644d6d12e4c8635b5c09a57200afe9da5ba32cc4b1d124ffaf322b59e3d9bee10bc77f375210a6d10eb0492c4e158bf41aacd8a260a0a3121a46be93005146dc6601613490a38a06eb4c6a72c2f6a81d32ad23f342cf51fb65f74380aed914b4cca47f8945db11a8de251b724e4e0e25668a91e223ab72e3a26970b743bf3677ab68b1941dd255b4b1e22e72544192381e5a1ebcd2b589b6c20d7d3fe3f6528e3a394990262de64724fff17291353222eb83312babd7517f729da9bdf312c7e35d781d7b40f6e05bc5344f3cb1f60d77f2d3cf4edf02113e6f0aa5f2f78fdc2c737e6e32350215037da60326e3b39099aa9ff84b3330d55324096dfb5ec0ab527f10b22acbf657f63fdf7972cd31c7ea6ecac80b24d264df6ef1a16d8782f0d0bd3a63d028f0f8a82f809a72f58bc8f3cef326bbd15ff695e7128bce9cb71c38868891110b5afd70ff5b6d4235b79281aa2bd3199c4c25e1ca0c1e8f1eac65b4ee0fcaa7b047e805a2abd726abd51469b92773db5de88f066aeb9895314f6c7e6a9ce5788f72bb079cd0be72e14ebd512a780b891ad9a8aec2323166ec49d38e1b311c7183417af5d855d67283db88d75ef2fe5f675d7a7393bc48c102ae892d86c53fd8143cefb9dc520402db057319f7b40498ba4149f685aca15c2f9301e14d752db3146257e1a78b5472328d6aedb6d53e16d71b63fd032cc96396c5ecdb1f8b278d6688901f5fba4c471173b0a01a016afd8a14f87218f3cad0b488f03ee75535c2fd2b8a2eff8bd2306978610be1246cf2c28b7830e5b6fac832a3b8c201cb3f1cd60759e05bb7d03b9b5bb3648e352237af5cf94c23c5fa2afd24922a6e75b8ee2d53d3cf043952720ce3d46bbf7d15761bf1bce7a3579f793b1cc627d5d1f336f2ad61983adb3572e083dfb4b052954e81325d9def53aba5ec4b351171bb66b6d8ccc85712938c4d0914eced7009e502608e07e35ea548864d7c7c4b435b865dd0fe5c0a85872f72af483c0da618fe35c20af9d33c8c61849fb4b6318d5430388a0a20da440ce335ee975005c080437f25a164b94a917c57584072ba49c3d712c18263fb4d095972ab036fe4762979513e0b52009f6ffb0dc18690f4653891121651112fcb853b729024a35b83c118f566b00096517858a56f88eb38fe204a8f0a2b8e330ca2a8724f7ba848453e1e121f4d76189f26ff06be863bc6eae1a18db31070dde25b4c72a997ed0fa8e1edc3f5f0e3f3f9f3b834d98cba23115eaadef352a0d1fe5a6c6a5a62d47917e11500f0ca67ca9c0943ffae88324381cc465e69a124e9ddd8e36f07208683e0e0bfc26af885a8e2bf7d2fd570f5551f99abfae845186647c332729ed656b3c39dae83a50fe816e232b5057988bf470f6df783f6f64032091b9172e11d10e094b0b4b795b5869d55cda741994bfccf055b4fa35a244d629fbfd572297d36f916c6f0e677dc9d67c6fffeb17da1c7d09d749bff8cd968cf12ad6c724fa09d6b7b1f42cb7c08b4220eb2b24286b58361a77f91b3af27c9b25f465572c8fb4a55be61b4349105daff3bab673268c1642423ecfae9e0b063e24091c409aab559aefb29067f1e9e78ee5044bfb1e2a54a39487b7358d0e46da13d3adb72d972f2c18eb526fa3b9d883734eabd4d4969922782860e76c2f9a6550a26841da2e041e0dc1bc20a32de1bcaacdc11ed2ca46abbce10fe2a2e849f5f4dc16f72645583b401a6f424e0e590dc6ee067cb08ccc7350daf9beb4ab8dd25c3c43a4ff438d289ab2824ed0788acbf4b488209993caf0c073b57b79ee7bb05fde88a6bc2fd6f90cfd03f1b0187c1ee4df33bbf73c333897a9b0fe309b1fc4be2c1ed1ddd5807f08064ae0f2e80d8aa70cc3708204d7240771ae0e0aaec18db87997f72bd93fa9e1bf8553ba9a6b47d84c2e34c8d703848a179f46aab052b1161c0285af26878aa8999478c38c574776ef295df834005663f8bd50064349f5e61d33a72b1eac014f6595e44e5ad2f26e5db6bec0f074c9b45cd10875af5fff2b629df5101b2ef58028ec5bd1b67e5a2311689c14b3ba0ff55b2d1326a2d390b5312d472e41edf4498524c68fb920f7339a421f0e3006c99769e810401db5d7f3887bd72646cc5b5fe8ca2b3b8f93708b6957fc7995dd549d12d4df603b233494f1ab472ee901f4625ce6f4c67eda7eb4b77c59aafe75dc4272b15fa6e43476b8840137233575083eacdbc41fe9fc4fb6b63e6b78473511ac6d8876a26b0574c42d0a172a712b1ee46fd50a3d1e9f3e775b710e4903ad3b30e60f2ce0e4b95d4910eb1724d5a3f7c33c8f1201ed10af419c905f5a9c10a7c997f52ac1e1581c184054e405a1c9f8237eb2ba04201c44e17936f4258ef194ca7575a3ea880227efb46456fdff9264a56391f80cedfc110274e36b6504221c2146b056d3b4ead3ecee921725c0140b15a1fd9ca8e623b844a71468a69a7c89cf180e379df64ac04a19ec207f2a1d23a472baa0236251f27aa729faf2470a2fdf4813fe05c6909889c8a722e0b78414f057b401a3bedcc067f3e5a34244426afb089abaed15d791fa1a07e6712095b7b95681ab31a53955b257e69572a279e019544d1ea848a912383dbb758184a4dfb99d3c576e42354ba69df177c3364267b8a77d1f882921c85a41f86689c1c954ada4c60e2633f6052a9b4bd22e9b4335b3799fb95485a9546a9f12b721bbedbb4a13ed3d2b99a564183f79b822903466aa8a84f0b21dd221e7a59344849c9b1430a90a0990ae8186c82455bbde40ade8c97808da5c3d31c06eb2336721dc9a5b67998973ce85f45195c66cfc84b73791ab21ed698f434196c9c36437203f025b74ec9a356863c1e1c23ba0cddad83c0024b0d9e4a43241d4cfe185872a5de614865d23d994170c649a3453961b82206bef7191b1ff874ebf152f5572ccff50a8b6bd4f662cf6b372b5a0f48e70060504ded33d50cbbb60d7edeb747728eddb99c90f7974c8e7da326a7a7788b43c13108a29afcf1b503dfa120d32e72fae51b62b4db9202dfcf8e281b485f66e4492941562c27a089b5e184cb65a772f857048fe41ab1fe3f026a63b3811ff0cc4550b2030671dd84adb97a7777717237048b19f826c21fd54bff52fdfcc964b296abb544458912a8d12debc6e5bb728d5dd66b433356669ba8dbdd4168c788c3d7df2fee5b26deaf69d986c5173d662b2de5827c50b55669ab3004781be9cf486e68dd8235278bc26586b4c3f2037251cc7ff7d03022b90a3cebd2b42db0cb9d2792a0e698b8ecb294ff9190927972a034b071a232f9978d67e095b664ae7bad4ca0d1a0029f18920f040c7e50c87219cc97beef8dcbdcef08bf308a684bc0146fcc4271937a80759ecd7f46c50b7135dc9d788754c37d0564331938c812b4615052157aab83672bd72c3a7932567279ddfbc3274b8ed6493652d0e348e4f54ef5d7b2c1dd7337ef9cd937bef4a07201128b213f1731362c54ff637e4755d1a3a80bb9fcfd3aa5168e07f28c0d43726564b5746a38255203d78fdf3956622d3e6b205e75d4ceab44d308fe7559547225c293e7aa56aeb6dd08e746bcc50f7d2b0870d318f1da2489384e7aea6fb63d0024d0eb0ebf7069fcfc6356d38bee7e2df6129490eae7c77f1ac1f26137db72ebc8e0732cf9bc6497f123bbb63c904389a60bb1c4faf6e60647aaa64a6a434d03d9dcfedfe7c981bc98f0849c262a5a4caa69e1055ea7417511e7667769502242ef98578e4c933a96c94a960fe38b53d83e8e65238a653d4a9e112002303a729cc9d9456dcbe01824af6064467ed00e1d215a2f65374d254905a25361dbf661a91c6a34a6a6ee42fa9413e5d059636159702996b8c54222236c060d08e69e320549c6d7b5faae29e16e7fc5ed0ef31ea6a2eeff00a89d25fd0f3370d09dd07278d3439ffa6549d0c6c417bb560680968748154b2891f9e8d5352bb24370767280e504950f45cb4b28a5c8c0d9fcb0a0aaac2f0e712c263e448b2354870487724e32a2fbf77c8cbe63b4bec6af6fcdd362c413dca5cfdfb0db19dad5480eba727a96059f96b7ad52079dbd7828c09366f5707a63e2f20e9a4e0136da9d8fcd3715c08b3d1ed4f8ee7f7e4b5c0d57ea1868a4dfb43e028ef5ce6b1b0a96680b45dd0056450385a0a436400392c1b8fe6001a56e8447c6913967de9855e0a6b672d4d70ba2fbbb2251cfcb8a4489ab9cd48ccb357aa432185c8b1eb5cc294b9b72d53da509774a77bf60bb86846cb36899a90743063299828ac9cac7b766219d121cd24db547247054eee91497c023449e50964a66d00992a46cd6a8c1c57f923e86b735f71223a0245a384ecd8759490f0fb180df623bf7e9584e320161af2f725962b08a1d628f8ba5f45f30d44970a9f1a73c3ffede24820bafa15836275d6b28967b2e2fc5e63536d8e4e37fee6886f58e36309c51475ffd0a8924eba4cf728e094bea1b060514d0160ea27d6e8fc948e8046aed8218a42c77887f463a4772cc19f61e81f78f8b4fa4974dfe0688c0709be4fd9562b01a1c36203b65aada72f8e2184b23c2dcd258464dca45a7517ab87032fec5bbb15f8d56fa7e87b88e27ae425248089e1b4fcff31c1f58bda2fdce2b78836686a48b1a20c922e3a9e2724e5344df83f3bfdf8636b55230d86a14fe40ebf3d8892d5dd9da5866454316728642d9ca6b45a9767044660afc443cb0b2462f1d8366528f494a1a6a5d8b077279e1d98fe5cd6698de1ce75cd512a2898b57e765020fd2fef456e1e3b2634c38d1b61d97e842d78ad9b99eda4828b9affcb6845ec4f8604567b854a998a47e72d457268a31a6837ca70a41d1b9daba517edc1a744dfbe1ce2897d90a6b0021721e8e55492598cc2fcefa85dadb323db1ce7473ed7e284a2c3f681d7ae6df332ae83e0404b85d1a381c7fc713c60a116b9ace0aed17da60cdbd782b1fc6909551c73ef2911929eccce86746bed211cd4fff67fd5aaf5392ca572925d24611bd726be238db3d47a508612ab53b09056feeead96867b2f9fe85cefe050223f333728dbd386034a2df0ec73cae881c4302546da2a8a70d7f5ad5994697b372c351723b6fce5f6ed04490f26f98f6331bc717dacbf3a2848f5703c5ef3fa2fe04ca0eab17bb6cd72d6c359d1b3061bc55dc978958bc821d202e08826d0545c2e88c7251d81708f3bc036fcdfdd6bbf2f25442e3416568cbc7808532230886b79123212537a88c638e237cccc0ad7cb3af1b89731e38c00405f490eac610494d970d72fdad6355ad9ed136b74f5dd98242dab49bc0572ee2fa4e874b6a686779930b72903580803cfe6c91d9690bcd863a1bf07e62cb46c6a5a18984ee684330432b72d918f4f6a3ca7654ffb8608ef74a7fb3ddcfcdabeb72cb992e25d0ef43641c726fa22d06c836ff5a472500b4dd92d8777da7cdf0343b7ec74e63d0a9852ff7724cbea0efa24241b03d1d4c328f78beb75f90cf0f7a4fcfe9a83c7e54fa2e8617490224626adb28862406ef9a59e026185fcead51551a30d1411b3ecc3672d9114ae30a9d786fce3e25e523c9776482427e6db30bb7ecd961e69a0c201208ae310be7051be433339c4770d89708518844db145a57075cf5135f89d92ffc272972234acff8e293a167378cc488e335602a75c284d62565e0c52e490552ee22080f4f4ed547cb99c2cdc0b9e83347381a0b19ef1f851cffe24ee9c636cd0e25130d0b3469c948ba21770581794863eac4f87d7f21305dd8baafa5d958f05b8b9f72e41b017c171a2108f05c4320072021fa7aeeb3aa121fd3e9dbbf93329ece406b93df047bc78f515c463ac0622d7e135df7de069c8d597f348d912d4486fda572f0221b975992dc787bd22758d346560b07e8dc34e77b0deed73bf4729e6a1072a4359eb00e60ad05544a4da5af73d0a7e9ca95b2d75521d56a6cc1ac7ac0c172c830602c2fceacb61f2b57633a87d8f2882a9ec28368de49a123f339d3d4df72f2402bd732d41d8da9d647709affa22f79579ff10478268d2e5071bd754bfe72ac4b2275bc96b7f37d56919061834b7c36432b48717a6fabc1b3c8f737fc0d722e4098f391116819688bc14e457070d2e7f1cc59c2499c34902f34bda021a9137809c85351e58fe651120bf73423a97063f0349f1e5c5b74fdee03323fb49c72e1cc70a09f74ef81ea73001ac668bca95f0b5610199d0bf54e241079e5480672d51f6ae4d24a759f6ab781e98644424765a30eed745cdeba14f69487f6b4e172b620211557ecd2375fffde1d870f63d834c033b09e07524296188e996b2d8172620f11268064c206e7b2d46023af067526bc43886fb7eecd261f2d82f2da464ad56fd44bf3e9d2df12c7bf6c96fd045cfb7c7f1d388891f0a01627c0ebbb7f287feac4a8e250931cc5c06f79e3d79c4f0a81dc58c965179e2ac1f276a604915a769754c7ede4b0a2da481cd5260bd88348e4e8eec8215b6909bce7f084cf2f721cca33206a0b550a54e733c6d9ffdfd141de96514bfa71d86aeb224d3f9a40722ba2cd6d89518a9327014fe56a30e00b9d834fb23c2adb83601737845c348272e47921332a2cece0cf8d4db4702eb5ced57a534674b41b5e5a024b5f2590a372cd430f9908efb4ff04821b6ccec8ce334dc8b8fdc301a962d007a36e6a7ce2728b042b06095085679d0348d8a4f99c14bf63b893dc429b0e0497462ae3a6734d5f93d3bb0c011c75419990526f8a52f3c7ecab82ee33f5ebb262a27faa2a1d72f69955a3b49ded7d04be742bd2258ef22ead418f130621f83496aab40d04b822498e5ebbae2a13f1369acf2b47aea86d3efe08a07fb2264119ca617ab2ff4d72f700bae771ad6639b79b65c25eaa294ae6d83d41b3d29c035ce04700d151ab721f43c3bfcdfcedfe93f854624e8a1207ebc45f024571d982ab15455426454272516d2dc730e5becf24cb08afd22218c0f8a42d9bb23ae30f4b6a8b69c9fb377210c7f5bfe4d1dc0ba9cdde93dbb672383845ae1f4391abf018b5a7c93b2a6852c1c2e8b40be0af75fa800b8d7073296b42ea02e505ac263a79c6b6ea6dcb0b728c60d5c5918efad5f6ff45e17015285ab5aa350332d5627d1be46aab5065097276f68b8da3b2b4cd48d2006ad12be45adbf157e6a3ae5972f3be6e262d413072b45ae7b46696f115ba8677df1f3001e98e20e597099c5f571f8ca2bc6136be723fcc8931a2e16591fa832987b901696d4f4be3ebe62b3fffd7d776fa75b198353e8dbbfded268e4ed1fdf43d13ebd1da19492f66ec6540bbef55e3e67416b710a02985c28e5d7d2bc438035c2bc30276fd1a52fff29dab2e3257086c3cf85a72445a424ab456032f85a9133f6b0942f16f2cf9c8c5a3766ba6d853ca7206f6721b44aeb547d87cb0dca6785a5cb3558e8e155ddac7d31b41fb30694958757272280c285e378cc64ea90514171ad959214766f0c43d123fb87ef850cfd67ee172f8b3d034130af4b1dc2663a1c98f52ed65ed95cea5a4f4ddb84109aa4b179f72ca2777541c6a2b7bdb6b9bf245ab04d62cd4d5554a72a103cd766003d12fa7559d85e9fd6895918aa12d4d252f50199cc7d221dccb656c6b08a8ea86623e380ac12fe6fb4423e53375f8ad06d88a3abe8aed595ac1692c01a1a9d2440e5cec7232a948b64d4a2c5e7e46ede7d36a08427d9029e97584c4f7bb049020e4bc0b72842ba0b2472374526c704614c8828e4d04de2c165c7fedf6c56d439838c60172715a9a26f7d13bc8543f73917a099a4bfb02223ff7b9ac3501d4c47fcce17572cb1031a23454995489eaec2ca64ecb7c7453a524d9e5c47c2f67732b8d26f172eff45b9aa858353be0a08a9d9055c2c674d335c4a60a8e4f42d4889dc2f93072c4abcb0561dd901bdd7a3e16e8d36317dcc29c89bd6e9eb16dd18924d9560372cfee2fb25194764d83239581e4810e648dfba25f06974a9cffab66b5f4eda672ef06372c2967f3ec3663370abdd844ce7b21f31d7d9e9d0babeedbc75f5863724b4a24ee7a335a6a622742113498067ae797fd2e363140a6465e6e87bb8f2472fb0d4c21997f6a19a602a2f46407c566b99b8626dfb35b3a2d7b7338aef5c322dc445a369286a10defff546a78958535cdae81aafc178342fefe4284d02e7772caadec562caffcb1f4462ddaa21477fc3574a2b4713df2baa5d266a4cca2b572db394501623f4abcd9ae9a0e5236099957167cb0e97b9f1230fc0fe22452671d11058255e8ca96928d60b6c94ee3c288d1526f43e988834f5d4fee47902c012da79f5963c06e55515d772015d2b13f5806c0daf341f31ea96c1c6e918255971ff7927fd71723e0ee356e6ad94d14ed0747a3cdaaa797c757c5c3b80546eb08729b5af59c9df3018ac212e17efaf4a7c3e4d0846281a4243c208544ef83310f7252e9b031c5a85e9bc646dff0cd4b8642c4f5fba317d70ba1fbc7b2622dea2917a31f3aa8df5f64ba63cd87f5c2cb3e8d2c1b8ea41f2e0ef22775f314463fce581efe61c4cdaabdc79902449807fc56484e80fac6e5a8ade54f910b291aefcf729ee962be0bde0ddf403442fd07bc00e96bc2af42c53e5b7779d3e0d47d555072c46daa249fc13566be912a276eed0913f3ce4457e8c9611241f8a06ddc05605704d3a26fb3a6fa8e67b94aeb0186d70bdf4cc1bf7f05f8305bfde325b78aac520154082342f884aec966be5715d51836bd06cf68bfceb8ce37dda39092118d508c2cc38f335168ad0f5d96061833f3aab8f8f68f677174d7b0ee7c63e09110464bbcae2bb7eb3fb286612c22b1ed47196ebd9beefb8c8df575f108d3a7c6913eddf96018f9a66000c525ba5654c255a2304750672ffa151a802e20c45e342a724f5bb6745ed4b5f89d955eb6cbf3111a15ad1db45b8289a672bccfb2e0c1c042a156c5e332b15616ad7bf558ebd28e6bee76fe7f6ac9517c5c895ada94176572feac42e68dd626d10139bc6d22aceb0e09ec6dfafe8c3b1f22db0931bd717972576bfdaaf0eb061e26bfac70d7ebc8b744764f1a2a967d4216b935484f4fac506f95e9ef23f3539846b451cdea08b902c61a9c1026b1842ca4c8c9cc9463ae720f31d8552d0f49a67f190686efb6bb694ee529b0c33169ecd9c47a1a1465ea3be3d4469aaa946b819666d62f0fb115d25cafde9ad522b308730873bad5b0fa7262b4a448cb59bb137bf365ecd9dba6e6d1dcdf27b4e7e0f6335cd90c13d998725a47fc1f505d6c77c525c205ec7e3772adf94671a5ffd0e0dd98b3dd707bc47259bfa08fe7e7e868203fe55f4e7e01ce39800a5d73a6a53c104ce14ef2ffb772cce104e44889439052a076c4a2bd34d2e6662e90a7a1feaee4d449c8010f36187a548d5ad03c1a3ca699888c5926f0131870239913f728aeab23de49b0951a720c2e5d79dd36ec63f0a0b0e3e8c225c1706390719b01257fb9a03eed2d040d72280db1b582f469450c384d39a9a925641e20d898ec2c3a8ea56fd73e43eaae32678450041b305ebb013518aff8eff0d0772697a020e5aebe7d0920b38a49995c60f65bb6ffdd897165b433790f765cc0cf0ebefd64f3ebc7b0a9141918588d72ea93773b43b2692f0ec700314bed97fcfde88d526ae7dd297864e82aba3e547252ff35d670045a50f6ce8a9f43e95d86f2a43f5dc9e61591213ba3f94d9bf272d6dfb5b1c5e633732dbb4131820822cf959eea9171fc229caf5f49f34ff9ea6701e1585a100b747e335276f49b41d7d5ecd9a1354b644696ef5a26ded452536308f529b56578dcf869e8dbeadacd97cf0beef0ca3026869f9b8addbb0f032c0f15f398cbe0ed596a4c641214eadde244087908fc9a19c4253580b9f1f1ddbd6a56b5baf4bb50040a5460a2718a6c135697aca05ccd3e080cfe1fc8f13223c27253f2b72a0643b7c67cb78aa3263c8aa1e5823d6025014be6483d5b8bbaa05c628b149fa70240821e05e5de5546fcfcc0ef453ab6a7f265a02c8467b59a0da41a0e0bf93ea74572677645a6368024c5542a32ad62de6c1ac427cd9b71c8b6da72bf8eaa89619323cac4eb91e6915c01f5d009eb88db633c2f8e24bc7fc164dd72dca358efd616ad36715c222c5964cf5ddbf98f5fff03c007897c835584f92972653c9652390645f2807806a81c455359b99cc2d66cf690da9648644946c1bb72f31ee7ef907e93cf7f01255da82d745230ed9fe00869824952d5afef7be36772c8fafe93112ce356104e1e778aa184c94d6b6139bde41958f946178868f1783b82449e5dfafd55e7f9f5583d86d2f2fbf2b3f4c5fad8e9c6bd50ed638c67a6721edc40ce71334a3d737cd772a2e734337f9ba7ae90c5e23c0d127e59d7ea6f724cdb5e10a7fac2a64e3c42253688f6d0168ecd8796a31068c04c7eb5baef4e72f17c195d0faaa51b89b674f810a04c7b7a8eb9ba50799a998d91c87ec8d5ff725a9a195caf5de689c675995a489701ca56e9a31e195799199310af6599303f722700aa5dcd28ccdc05cc6131cd2bfa47fd0e22608795beae150278e51681bd72c93b4da648aae02263a3be9a4c63b2642d17b9edb0919e98a60c8336c38b38727721242e6f85e47294cac1959709a051d00737cb5ce4bbe9abf39fd4b07416068261fcd2160772ee05318dd9015c318b098233694f885202710008f9baaa6672a17c5d2d7e490f7604be1d2ed79093488b06560b286703eeb8be17312815e755a14fe5ba4bea50b01759cc0e256df5c50f22d4059a2c9dda484bcd4f901e651e32196723cb4c9156919b640dcf63f28a38c8e1c2adf480a3dc36e85f2bd974382de8c5d96bf9d5a45b1fabd655826c57b1d35e619384fb5deb5bfa576ec988102f4904e8e203233e5e6be26433ea99f735e068c742092047cd2740196a15e372c043176740f7c0fa2585db2ab19a4502a7da300969fecec1979749f6d67063722e31b83f9adb5425c8804ca4ebbc98cb4af13bac00a840d53c0963cc5203ee72a5d11f6b0450dc26c2a40079985612b9557bf2a4def5bc005fc5b21045067a72276e0c469aef4b5ed88914519d1a798a04e221e2eae670dfc1b38d8260d07c2965d4e5e65763d2e38a79d168732108e276be063cfe774c12682902da106e7572eea9b392596282cee5be369c94c189b7107fce8b4aeef4ede7f3afaf4d2c2972e6f42a0ac608987068329f0986704cd56a4b0f2a685fc64c8e3bf71818a57b089beeb3a3e9903ddfb7a60d9c2165c00abd5e51084ac18644670d5875ea49317232cddb845d2b251b1fc24868029fd21423040de30a719682daeafaa50668887278928181599a0202abf49f86752e45bfc1663629814969f03da4f5a68149954597190db8d5281e87b929db307bd74219f5994e0b25c9e2832c44d6fc732b4a2bf3a6e51ccbd6519200171640ad617f74d021f58e30ff958e9ec98c2e32460e72cc759c3fe4027d9aa24044bd5d2b193b908a21f9232887c72ca41bdb3eea173db63fab11a6630c637d432cfe01e3bffc8e19e8b989213e3754051b360f27c0724446ecac5a784faae4a761f9215ba81029867d8fe0c661dfab8744e27ddd3d721ce755553e61b547594aa5505243f9d8ff6f56a47df1947224897176d6d29c30a829365decc7bf9f9ac92b153310176d8c5624f3cf39afe9597cac07d578440fa10c1324c4ffa21adf8ce6fa3f8d7a9c46e0b0df02eea7450c26a91009d09c412f8b8c34b215fdeac338dc1663e21667c34fd5b979bbdd31c4345403ce2b1c0c8d76aaa07e89042bb8ce8922bccc8a0ba4d9519f25a8dcb80c30d4c614f4ce2dc379b1abc68323631a8962e5348bd862c7f23c8ee38fb078569d2e8655e0b8720101fcda080dde0045d8241fd67df3b62122264246502f14209d7e6559cdfd50328026ddcaca777e60aa8311f9135c5e6b70e13290519de396371e343bcd76725fd98d6c54fde418c5d7506eaffd73382cb38c899394beeb2194381755562072aeb3db1ab11c41fc73248fd8054575a5ddcff65ce6fae88a7d6cc676e79ee7726f909ed20583316ec433022f3655d7eae584ee47156f22563a3fb227fe9b3f7208e147a2201b5389b1ee6cc0257166015e84072746299b468b04591035632d7234bc14f3e632cac2500da0c1bcf65a0bee15ce5e6466ae8f90f8bef773ac1d62ec46c356d336c6d144ddcb33e2a7c114a1f54f360f4e2175d19c884378bf635c5f0e9b4ba3711ff7dbbc10d78bd34bc8e3bee5bd39b115b515e80e4a92d81f728c4e120e1ec2b9c513c5573efafe8ff67449b61a03730f4891aef3279659090fa6771e0b0b256fee5389a24d21336a9fc4b85f86c51e7598a4d23c5b7afac609255d3fdd8b446a88900f452b85779dc725b95b64596961ba63fb3b2c38d0a22d822a7438772e39869120c250eb675e4e05a47665151abe3491ab80411434957240b0df0a2a9118a62f73b982226e0f175a2a5d2245d599cb84c8f9add530f672f02e3c3011404a03c97367b4fbd6b1168c7de53d776d8298fa3b9534d228527276322e37a1229ffc2054484f09b4085743a511db77418ca82fe84698b7a7f7231e55d01e5cf0d3e64927c126cc97674f8157427b64b2c67e249eac5b4ce35372ce50f12eb39f16bf06445569169f3c014350621054fa31d5a60fdab8f7da5b7241275b132df707966026d41b08abfe6c524d4dceca1c21bc32fc30dda28bd226d8cf48f4dbf5602d2351cda6130abce7386350d98a65b7f431bbfefd09820d72ae3f6064cc473143e860fd47c528ef2de83ad5626a6749a242c610cddc253572b9489ea803433283a6076d550272cf73edc1f713c0d554fc90be169646c6f0614fcfcbd2adc613fc23292f58159a81acdaf8b1e1abb03fb01af77599b91e2b4163cce15c05f068e2c16ee4ad00ecfd76fe4c7bfdebf40e9c906eb5c962e049726d343bbd539df0a4f26ac11980fedeee256065c08361ac5de9be46ff48645972a82a7505c1b0371cf681625ded0c75aa3304c12ba1c63b99ab2033452886186bd3fab826e22d8d87fc073f4f07d56ffb81162870803dad0b35200abc459f12725dfbf61b59fa0b55259f02b3c31f1c229867cb67f644fc19987faf9b97563540051d3aad21c570afc11e3a3101d1ae71a606c134ce6fa57066545d7cd04f1672268cffcce812b7e958d81f3bfb08129b5eef1914e05a943bb6db860bf88be70628f658375ec476343aad0f0653e64b58c284ac058316017c05b023fefc30f033fd539bde789a2a207b912b5c9c39bd589bee21c280167da409e37bfc0a282e3bd872ccae7e8913507a3923531286c30593a13c1250fa2f90d13001032715640a125787db9ab2b48af7d74c62664c0d0a78b8bfa1fe62085482a5f8e6c156367221b58e1913b01c50bf0beabc552b1dd465355fc0543ad51f00d416f95151f272310452cec98c7fbae590b4654cde987cd2d46c746793c8b1c9c95275e68fef594511e5fc1874ef24d2f1c504b9782ee76ba2b4fa4d6bb2a8c13e1b29367d61720e14f6453dc143dd78ab7f26f1b8cd0733d86925365daaea50706959734ff872a02027c285ceda72ad4ca44f3327b9c24bc8de263a3283375704f97877bf0a72012a50ea740b95708a2ee59591d8b269b26a8a546c9484d10d727a3197ed6441fe3a4775887e4aa0b1faeb11d21b907b2700b9b753bae2e0871c67ac041d7672b701e70513231ce1310b64cf5e9ad6418d826b276916823b9725265834ff052910c76e14f7d5edddbb66e2359bb4d56117a6fcb875bc31874362c70631dc3c35f229a9c6cd43f15cab91ac1bb8be786362ead02f3eae12a3862e447ddbf0e772a284bed8a431c9b668c6c34b2395ce2c59196d89e2fa172f7cf4dc74e43940722d84c2ef39cd32e8741a4f8107903faea6b8727271aee640a044c14276aebe72678b6bbfc2916d99888dec8a19af5dc59fc246cdd0b8f1a9eedc9440e9b2b2721d46706f9d8c40614c4334d02e4ed027bbdd4c295a95000224dfca273fa03563ba02c0b5ad1b86257f19532e1b37d1bf4ccba90de7be84998067e8af5012350a204fe96453ed003ca6ac69a0a044effc1dc1ac5991cc180e6948edf24552d672b608e1ec1f24645ad5f625cf85e3f6633edc9fb8eca7613b07f670ecc11bd97283c42a9dbaa98e05148991968f6e392a8d9b78a5fc0f5f21cb4ae777e1c16e17b29290c8954bb2e7855531d61e06bcd90b91e2b5f12bce48b887beae4d012a7242fe0ea6c8cf4b02fe442acdf14cec281a3e66591187b9ade44d797ab9ad8f02d17081007b520dbbf89e0fac8ee8e31e3aa728a2da744893fab4ca911076e3722a541d822f0f40fedd07762ca35259b84c95323e69b8b198901d32fdd4bde77217025fe32f89a1d615d0a029925de9b59e92f5511aa3f1ace42dd4e607299d164a9c0a26b1d1f5e53ef84c3cf5f488efc2fa97f23dd4a39858cfafc13d07807214274d3e880279b792df57f19622d98016facb95cb446930c739821d23fd61725246627ba80b2f2016bb9e54498213f5b94b69e03417b54c2b0d9dda6f409d492f3eae8b2a67b92279d935d16a9f488bad7c115ad03619e22a77f985a2e5b5727106f2ecc8e60f20439611f89d4a94da24b4aa16d9cc365f542b468f19601372b9ef28201c8a87d93a0b9bc756190c4b18c51b85885a483056aa012ea5b96360f8da34522f31c066fe0dbe05bf5f3d406b2084edd1abb33a96480ffc86d2a772e716851d871698578398b2d24df9f1c0fd21585c49bf7c85add96a3bcf3a892318534ec9fa87b5733589eb9903df93479f9df5731f925c3a79241f3943d61c7230a685ca47719e3c7ff0cf9c01bae632621a81d2a9f738c509b7d153d6368629cca088ab81bdb43a4f273a6d7f7136647ddc5b3cd82ed8a9b842746c2cea9e72d71025bb08e84526667e96cbf734ce1d0a1de7d54e70430ce12fa5106d043c729c55e55c57d20224bc46132ab8558f7b6bc64475d15f449652a510cf6cdc587268957ea79c3eb0a012fea321d02d935a2f0f18775c807dbd667109327a7e7a72986e51bd2dbee91bd82b5c7307383def2bfe89114f6a468fdeb7fa96061c1472aacd3edaa2aee6aa3eabc3cf0452139586419bfca9b836fc9ab85c16d3dc8672ed7b93d02b9f74e898105ad865ac142c6b1cd252d7911c4bbdd46ce67eb3887258f0e15c06a3cba096c3f3ae111390a49cd30f9df929211aec3920e08413d012c0d0161c06da8f7d2dec805e9b491d5b2659c631532014c89ddf4355f4cddb4ab2706eb7155909eb299f41cf49619f69f36135e969ac910df2aed7ae28c7035ed5e8208d59fd0379a0c1c90351ac316511c004dfa80934f51d52c4727e1d0c2565173cd8b839592977e73a5c66de8a15d0c090d878c4ec2dbd1f7879a91e697214f6e83f38a7d399fb0714d8b843deea1e95a9064f51444437a83a099362f9725c444d3c768d141ff153ba09f14e7d71e93d879ea69ffcae44fdce8775f2e072715d5fcd2feb0f0326fdfb085e21f95fe48859a7b034a42019eaa9d11ee9bd72e408255e6076a4f70b1c31f0c4cfc6900463304c734240566d02b22fc2f59572384909ee0f23f32007429a9075ef15130ba99594e2a1eb3ad39ac832de197e06419260db762b153dc23ea651fae56f9c044247eda89460732298fb54e0072a31a510741f32cfe9b30abf68cf9b033fc2562cdd53a2d4c0edfd9911206946a717cc4a9ff33d5366629a5f458ff888979691483266ff7788e73d9a5c009db9a16c04d6fda4d49cf53ff10165ddb43fba3b9acb9db918a34479d037b5988b738d729f60c0345c23317aebabe941d7cfe94127261a4b49ac566df402dbc072b4040fd6a9253472063b4785f1516cb0b89f7f2510b638437206668e35c4c6487ca93c04a2cc83c53628976d3689ab2df126103ac8aca7f2ff7060d257414943656872e44bb795d11aa902d5813a50329e5f30bc9c5e4f8d8cfbce2aa3c8eceef1117220c92c394d3d148321769f588e7c127d925d2bec6fb7ed81044516f949055d37b2b64f343c48d144991e1ddadbed94820d7cbe0a7547fe8c3ef81a6ce1c90e72083e4198d0fc4f73831e53c1c205c39b03cd40ccad5064cac702d81ffe74f1723c5375e9bf98a0f712c086e3a47ce946d2fbb723fb4313f1b8fa2d87cc4b453b93fe1f4f35c603a96852f5985bd9d4c64e5e25fbf4a528ff841f21dec905e0725c5aac1cb410481e8d70ecc8209bf5376f48196e587b76853216f7b2acf5d872b26a0f84ac6d1d893a779e83228d16ebd727de8eea999811140b5723bc173b702f71e58e8e46a2963e10ec343903966a52ba4d79095bcac7f548e84045ed0472cf8954d92f3a98a213cf8e81d01aa702fcb976e0f54672b402c7177633e695727c40c99c3108939eeb45e3acd78d7e984fb070548d0bf847737466b6e8b96e3681cd5c2007f9c0b7e05a659509efe8670f3bf5e0e45c740631672921bf1bdc54d4b3084bb2c459d57f19a272938ca7b397ca2f9e9303683b5bfc4ba2d315d70b0d14d3e43b27ac898acec91b28d59d9e4188abe954881493fb8b032ea07bac2ba5e51da59f14a71d2649fadc9b8f55631c90b0fd6b96558840ce3b3f4ea2c972d8e7b7b11a444bf8406a2241ef329c3f1985a2b278663da777501e5f0c47527232aced32bc3242020a308d8562fe0d866dc480f8c1c53469503e98bad0063d7226aaf8985f4a1749fdd633cb0a1f3c039e643f60a49673c080eb72d6df2c91723bb8293671a7217c90dbddb287023c9cc41c87a6c44a48a9f89cc4cf18647a727973b615cdda09c7beaaa63374fca52a14bf4cf40a85b889c5b74a25bf71211bb7ccc337434bfcdbe04b16bc273a22f5e92e0f3466fb52d31d653fb4575f6d267684774ebb2d617da1750b4dcda9a2e53dd2c8e3f6a0954e8ea7ebd449c3ce7243789942a588d9724c53604a7150ca144cc71402c3f7ef7a18c9c56f6114644316d2b5b33810466db7a41d52939be967f02854f26eb90b663f0146f83175175ed96c321ed338653ad821aa37b46bcde6eafec6ac92f1ad0af05f0d8a0514bc72219285f5250b8fd280493a448cddf1dcded9d6858aada082ccaef17fe5ae2f72c922d84ac04e140710423cdc74c578755632d58677259afd22be00bd14791272bf3f9d32f6f6d8a8a670def2ca10deb8906e2a3b74cfea5b1676fb69d792ac72cdb38c3b6fe50e0adc4263b71861837db54c13ca7b3e74981123a2c4663b576f43b70fe0d99641a2c32c81386103d895d62b5b05b73fe5944e8cca34691fcf72bf2103d4bfb03647c7c12af8fe2761e3a46a34c33d7dc69d3c39e1f38b2738723f9dfce40132be18dfb22b719ef10b8d7dce79567f9fed5244ee82b5a2f71272145347bb07480514b105de273ece29b00c93d5c9072a83d7ea93bb35beef1772ac94e202a4a9787a9231af7a2acca5c1d5d125d151b0c39b4a4763e3de3d7b7282abcecdf7f4ff8f423982cbd2691c8767835cb02c562bf2a89b9a99aef5fe3755201922bc2dcb65f753d031f6cb9b0dcc0725aa8055e19dd60196bc654cab7244dc3bf3c934925334dc406d07170b488c6d4bece53a4b6f0d42387592943d51a5b0ea346e9c55f18a43b6038a08bc49626373784c1bb691f9e21f62d76b3f366468a57581b671555d9e295fbdf632fb39b794e0b14d9d9e96d362990e9e38722da6911320e572156363feae0ae22f921a22db3b8f4b73239e5233d009b9c6726df7591901825413c0bb2c41472db70b82c7bff324ec61a8ec9013dd2ba5e27224a07059a21f663b6de580a0215d6b7712170c9ffe9c24226def73f136c2227188530403589a9c0373cf8e575d1a3831317e085131d28793ce31ff2ffe318372fb038d1247ba7a8409db0c35d4667aa8620f8bac50acdc02c6397ccfad6b7c11b204afdbf48e435ecde55a1f58731e6cac246925a90881d89d3c99359f1d9972a34452cdf1f320a4e2af07d34ec651a4635a0bf2475fe2771283e2a29b6e14165bf0ccfb46b7ac0659f67302f330ef03f9993a2b02ea56cf18baff52e9f4f1695a6e33a73a4fa24d7182c2bf63d67a0d4c8ca0ebdf4d2e2ee6f1025127ab4a3f04c2fa377b4ecff688a050a5b2993c89a5eeb045ff3364d0922d5b87b91d9149369e512fde14dcc1b90689f6bf83d4a0c4c38d6c7ca61a7f6adff0ed25b2294b02af3f02cd921bc6f03d45e1583422391bf94a0e90a1356634bb77aed662d413b87a577ee7047dbd397dfbd8671c795cb6bc069d824690b5220779f3ae74f87288735165b8eda13f62f32754695889c26ae063ee52478816815fe55c9122cb720d2321cbd22d7a4b82e113001398c8d637ec8b266d4b64ad20785b6702d02172d4e636397b27484cd12bf9c761951abb541015bb3ee6ed46f99c2cfabef3fa10d108168b2f2049f65e879a7c826d44cb846e1b682599ceb935de6c2df1769372fd302a9c90b2bbfcfd71a50a5a60cc2f2e2cc5fd92eab5170cf0b78145e780299736bc40660595bd92b00256441640b2ade918767a9b69ee553358a30ddce0729034b6ce927f1a7c74bd6f37a8f477cbb0ecc08a852d2bcbd1586c01e486437209618881b6a05b6fcddd5221f77d2455cdc1f43d16c96b78f4b21a87d1ddc0551a0ee3fbc6d1a4653881898da0704e9daacf9d4920f2560ec218236e86440b3a5f4a54f33c20995ddcc149d8b41316e3290f959f7e143619055041055842bd72f3e11471f2cb116aabfd4935cf96dd4d34de1e752ebede9d4515f64c8a21fc101fe91679c51beaaac493ad90062796ce04c3450835e9bc02744764d08b215209d4d9709f8b15de699ca8a3a16987acd6c2e9e495994967bb1410e12791baa97275f39b116fd4da586e027eba8e5c46bdab57f67247eec39900dd5d3b8a53d47297e5080f99d703c859bc0f0f2f845a20511b90cc69247c7ae0590a2a7734fb2fccb5357703348e1d32888febba0f10310485b679a0e6c83e0d8305ab6e1cb127680b67fd5925405eb852680a8404fed6967431d5ed5d712eae5e8e58a1c2f9724f9052bb1788253d491b043a90586f94060d64ad681b8af8f730ad0c9c99d4727bbc59219c5f4d24a02e8595ee9e69e1829079543ab24b3172b3fd8ce23efe6b26a2bba0f943d5b648f076de596418c5002e7119db49a9436e5206a98b50c6722ec63b49a79cae736db58be9625e13f9a53f0bf73b38a0b13bffb1e47faea372af3c142097753de4fdecad167b243f6bb6c2105aee8b57c39f894b0c9bcee1725d3bc5653de5df33209e68ba614351d7e9d7b9dc906ea26efbcf691ea71cdf0e5ddf2bd91a781cf207a2a041670cfdd1f8a1d3e4f5f12d637f1c1c065aea2d72ac3bda482719f19a0b459ea835ab0a432214c5768f15a1a5fe0d36d8ef1aea72e143968cca75bde626730a307e9674d4bdc0760f1a575bf908484ae9d784af1ab19570fa4a5130ce23a4ed96e1df597112cb8d90a3ba538d729e12a8c62c86721c03add4020b172904ffa10378db73401de782b4d11d11d4a2109221ab8a053a624f5097d7d7e3074fc2356d4d53c6d4e226e2851f1602f26e6a24c66e81a20014d57cec7fe0a437ddc911321012e52068110f4e9f59a441b78740d3aa3ba1720780c6496eb3e46081dde85418c508c7d5da4599d0ae5e96268148a816fb6443bbef66f1aa288630ea19ec7622f9549a393dc7044f3d2dc6befe218ceba5d07266854566d3434134c5f6806160269e7f518fbac89b216f34bfca06e2eac9b1722b96a0572c36ecfe8437690a223d8204b01acc0a1a787edd511bdab885d05172f75ee4e60cb468114f492d831529718389fbfd5fc09290ca83c5ba13b699bf2e099aa8e35661433e474d0e646ef48fbc464efddb2293c6f7c5367052e852d5726a2418a0eaad95816ccb9e03752d6d6cb448ba33e9c08f70e3bcbeb3aeb4d86898026a1ef354da7def4904a7e06d8ff2fe81e02a2cd97e320cb3055fb7faf81c0e4edb8a68207e36196002077a0079388e1d2ee314ea6a439eef79ea033c7f72c4a0df8470b844e0a9086e28005b3292bdc5fc697ff8135b5a3ea97c07ec3568cf69f09b35d963fc7872bf95c9c75d1a0ae8b83b9bbf04c3a0d0d6fc66a9e372bb2c8125748aa9453f1db0a649dad68cb88c132cbf582b07c8a250585a9d34727a86b76338d65b4a6245dd5d3168d9a207945dc32d9c140e967557af18eb560769a9b74474acf43d9967080252f60c5c91af64aa0df4d362d52ed86d5799332498d5270c93a81b233b83f32e6a3c5e9f9f58ba5a3dcb3f45b21a13991c2c990418983597567f552e25ce0c0d4ed8b805b0cba65f20c2cb0ebc7320544ed08f2ef2c2c60f80e02d7d027cb69af3a889284b2a1f76dfbcecefb253bbaa3a8e8172b51c94d24d0f3fe1b0d33151ba0ed9e90c36838a382b88cff52610dc992a80728964d2c455e9da607460bf87302228f44a08eb69cb88d110980b74f40be67f72d751b97e8ba0a99e7e5f5230168b5467d75e29f32f4900a17f9ab6bab8ba1e7201c1b63b4b52700f1f4cc3d66acbd1e2216f610fb0d4a4f3fa1d337154f5d5725bdb466f9c0d77b6ad5023bd11986a6715793572f03d20a99f8f486f01df1b724fc0d0ab2ec774f57974f9726d2ea6f20c386a53fd9dd663b8742f877f04a9726148620305cb32b8bbd6871e2052fdc857cef31620f0371b0dba7aa4ab71f5093c091ddc736959d94642f1fc83cf3bed866ed58b399ffc7fbddb7b16ead28069cd4b73c21014e96a331de7e4f1013b9e7f8b46a7cf7a261fa620b6e6f9ece372cfa83ecf12eb9ba2e5d52711b053a6f7c7e47624c115b1a1d1a08b9bebbdec722bdf04ddb82b73cc38582320a994808c660d59bb81e13aba09d993de63eaa27257d02e9289cf3865ea4d4edc2ccd29b12a7524f1660014d24f8224158692103135cb63e92fb78c5ebfbda00cb9f0594fa5bb6f7999f70ebe74584d39449b3d72b9cc476ffd2629db7817fff1a1ffc2d446825381c1398ee20af15bda43296c723a6965fdd1a6a7dedcf4c26aa94f1037b3892b15c27737116488e3952084ca728ede8642b46b612a1457040465de02866981b8d69d8b3d2290325cb8a755e73ab6ad1851116aa610d1a3970e1470fcc957a4290a59c61907c82fa23af7645072ebe8ab9fefbe476bb8e2643a8ea831150ee180f90ebb9cfc9787f099c57a9c72626c00dc858ce0e053417f496b06a63e7404e030fe3051d666feb3cf5d43fd595f15441874a8799492fe83b5986a3e97b5cf108d8a115c2c19cffb281ef9aa722a71773b386c192bedf4f5bdfe74fd12f7c0d7bb66b0c73d3e7443503dd6bb7243231e2d57b8ae0efab388d5a67823050ead8e9d095fbc8bd071110084b6e27233f7467a4a7bbcffcc566c75c3ea7dfb4d943ae8da77b63706ee91b319f2441390a6ba7fcdfd90310f40b9b2274e3a8cb51ac1b93aaf3359ff62d5427489a672ece0d6db8252c0639ef6e905ed3e96c838d9d5971cb89ddcf65abc1d202ea072343101e3c183bb7c4aea6f31c0029ac2d633db9cae78e3c406f50e0f902938382664320416143d8b3dcf8465eae4e92eb753b3b64875cefd41e71f78a65064720b13e8c2a37a20b1a3f4516b8b926da875cac1764204c91848343c7bc46b7e72e2bfd784c33b995b6f4099d7ffaa2ad364e3d9c67a3d639d9df7e6bd658d7e72485fbb12fe316087a054a9a4720336b0dd53d35f34638425f350c1cced786b4cc018207fab1b5884f1092dcc3d938a3422ec6f0f986f6a96775e5c90790fa472c23b097083ebb439fee484ca0ee68937096df1d0686ebd9fff58b1d95bdad872aafb8fd515d3b9882b5573dc1533fe231ce6aa738c8e1b6a758899ccc775f66abbb4da519fa73c4149f60f579ac5febe3af465442158519906352c8d4ea72c23c2bf1ef572fd0b182ca9a87c6a0d315052fe950a20e73d550c3165fd8ebed772685f65d292a46fc10266f444bd47db3bc26ee0cc599c792fe51412b9bd6ea52a46d229453c220b1d51edc5ea7b757e8b5c4c7506fda1395ac182cb420453b00cefbc336e70c1f88748f652df673a612c4e3641fb7015c61357a9aad983a673726c4188a02aa554432ff84e161d91e0ac51abfcab16af242bd210efe68856e459119658a9bd9e2f0128f5aa8b5552a5abcaa9d9e9e9e5e8110f9a022c779b2b72c8dbd0ff908684b5193a49c08041ce0e1e4fd72075b60e75b876e4844c709213652ee2ec2c4b2a20821afcc6a814ae90c0077feb52abb9411896b1ff9d6f9572a9a61ab5aecc17d9246b3d4320302524b12af3ba6dfa54d3431c32669f115372440e002345c1e74a038e0e3b38f21ab28a84ad3e29e8bb800c249b581a669472b39c69308839cbcf9036d9942edbbd095c87b1633a9a76a26bcb3f1e31312d277747640f93b1bcb6f5fed9f9b6f80f752589906e05115f96d37e6e1bc48aeb728524202ac1edf5333c347a495cb7b853cfcf5ace51723845916181ad616b8245a2ca7461699a40239ec0c09d5be7282627c8a84455fc6f60a4407299cff0da72b6a9a499494e83f5705b55f6b15e947fd5f9a2b9e5032fa453b22e3fcd3a9c725f233bb8c569257d6eeee25a90041ecdcd01b396b341a82248bfea4e77a7583a635a154995eaff8c492dd26ea913f2137b4e22fc29d95de630be749d4bbcaf2aaca6bdcd3ad1abf6f772310e159e7a2125e66ddec20c85ea001dda87c7622d57405d4b6caf9d4f099f7c47beef0ba0857ad5f640dbce2ebfc43a6d9e72ff59726e88c3de1df6af6b5ca35c4c32788b1dfbb431d8270d928be8e7db28641c97729f38b9aa6ed606d0017fab38918e84da51e9283a641749f17c655eea22ebf66fbe0b5b2fc2fe8ea5ba69806286b0d9ddef46fe1472fddb984352d70689e76472ec7a620ad83155f9500323b1955ca69b76092406f1fccf28a8c6049eef38261854e790216f5f22f6c6a836337425003077b7c945b46ee8cf5aed8b678d223715c1bafd992faacbf1e308f773060852823037642ccb0758bb65585cb1a9ea98612b8f9b1f97faf8a841b11ab11ebe9b61c3d7633751da3b83f989cb918288fb3947706f8ec6f016bfa350f1db00dc31b58f032b00ad19a80f5d6554d061693c721b8eea2732a65fe3ffabcee6985630fbb529ed030dbe3a4ce030b838da51ab5b9af9d54e225e50e8928543eebcc38a4fe5669d4260f04ec5891044515c1e1172e3c24bdc80dd55cd0481b7a4c19a81056eacb41c19235a1231d8a9d5dc2dad631a8ee158d5d0528530d71440ffba6c3d55bd45f5a16d3729f81037776ca81a722d5e01b2bd17e82a17c9de0cb4908691715cfa6450db7f83ff5d5bcde9ad0072e08f503ba51c3038d3118b5aaa94b6414cfb5791e9b44b88a1cfb8f374f58072f844205250264af1e1770935028a2a787110a358cd77e325104c264875b0e172bae6eef3044313f6abcb6d134a45eddc040dd7829e1727e91910e4f2cb92db72702169bac8ba91259d65af162e59d10a874ecaea7504642d3ccfad3c37d56d7295c42a68a993dd3ebd5011ef8c7311e0ad30d45b95841074f56a2500ae3c620205818e90587541a16077fb77f40f458d85bc61d618c95fa82a283422ebf4467255a1bcc0f47b0ee80b455956171d00bf9d4c917d80f1e8902e899155802449283fbda9c91fd90688333f1323a325f90c44927f7076b74851274f0508546077081e48c48921e45efc2d6df620a37d5c59cbfaa1ff514f225562df0b12d1a9d472b9e71a20d9d522c7d245c5d1e6cb95ba50190e0f332803c53f90103a6db855675a9a81dcea0d9785d66c99737951ad8cb18f769e61e2e8ca4e29dfb8200f556923d410c74d60601e18274cb60c03db5642fafc6a87fa375aba89ba2bb5f30b72bd3186826c9bf128b3aa690fd41f0ddf7bf04916e77a0821654fe210c9518f33a28f5c4b6dd979bbda00b114f2b0bc322da0abbf8d68a8a8bf7387c9cc1385722f4b3075d4b11b230aa358ca2fcd90e7046d63bfb152b4760394de755057bb41a017d0090b5784c1ea4b7e8506b8fe819997a4de6ea541c47dca26fd6fd750729dc0bd265c363d83d26973abfdf31691744b0c13a9ef7cc73b668eeb1718ec644624bf9fbf5590987e7f8ca5fcb83e616aab1450ba672a67eedd9a5e6f84a872b4ea9d435eeeb61403cda533ada9fafdfe9c436474fef12613d0f78bb118a1340d2b2c35eafc48eba9ceacd25ca45ab36042a754edfe0adde9b4e86f24098a72d41eea6f92ae8365204713dc5e9585b5d1c6c400cc4170856f137c1c07d0937209bb4fad905811969f49b0f6028572047f7ee9ddd752d68acfcb2df00dcb1a728493e30fd3f7dd978eaab2c3a87b5fc2f330bf7f85b51c25c7ef9471567ff32a56efdb58b4a51710db1aa2d4516c1a2b144f5b7b9e302402a668457edada9172b824046d6351b2620f24a88532bef9ee6cc4c323fbe85177d7b285a43f788e460a3ce877f024da46e7b7e765f451b7c67098164327aaa1647470c83de39ac172721154ff3a17188d971131cf0c93508ae5ae116bc1a731b49d51390775001d083cb9746aab82c33e6be57bbfbee8b3cd7432e579d03dc7c1346ad35c403f0172d6d72acfbdfb272cdfbc51ce7be545deb9d6126038e4ac0d9164f8d8fa247a7221f17b2f6b69482da8c5871fe39ffafde879efbb8bcc61641f6eb2ab0c978d64b505fc0e791e5faff0a83b07613474580443296b108529b0b6bf8651c293c272aedddc37bec069b37672367a881f5f2a4def587bbb96de2ef0ced337d0535f72acae034abcdc0e04ebc227cf4b4dbf065ca12bdb3f09d51b15f9f2a3b7b777391b0160556bdf77303d5f636ee73458c907b2061d3c66fb86b3d5fac771626172702ec12703895a76f88e6dbac0b0c291063b89a3102ed2582c685333b48ce83d06159e0c9d6bc4fcf38692622f19297c7d676d80419109b105ef0b21618d5072df10bba37e73e8dc70b541360dc973414f0e36df46ec227c2c89b2d05882a5095514a18c8c8921dc762914ed290c8bf465c201cb85ee5d22ad55929f04e39d72cb8cc4393b841dda93944d45b1cbfef540ebe9c91b6f4d728cae30cbd8c1d7727b4e17083e47bcdb061abc70911f98292dac71a2f31c583db3d09d4df61e9a72522c1efad711ed850ef64eaec0d56ea82de67bfbed69e9cc455c428606f09b456a563cfb384b9df77a83e0df41029dcfe65abe6a02be117c60114368559c6a72dd00393dd972d6751884bdccd4b25dd9ad523739893fde9779bf4f4323fcd33ef3b9f0c8e6bcec36e60fb6fe391e41904be103fb041524f90b4a09b94a91b57262544480ba8f83fdf34131f48e47c6cb65d23f2f3e5f016335d2aebabf261c72b64c6b432f441fab3225e0a20dfa98e2189ac375a1623a9a519c8ef5dd06cf7296e6ccc83fd423d3681e9668fc9e0144908a30794f306f678ffbeed1bdae1a72c6f328eeba7d8dd1487c78a3d38e3edced7d3a4b4d8a5cc5c6a51f6d3d18ae71511a3e14965f1e82a17da1cf65ec19fec28de409755cb62a537c4af45800bc72dab4620bf125705056c62f54c0d95ce8fb652a077beb6de1911e6a3e4dbeca72305eacb63b81a5a82aae3f165155bf102adb27ffafe44b1826443e29e5c8cc72bb31e9f2c4801f021a63792e8520c55ee8e01fbd3986d12dfa0fbf135ffcc57268dbecba066282abc3a5c90c7d6a3390a5b355ca772582bba744bbbee1ef4b72519908f66c77aa48d6149296ef8ea5d755e38bf974fbd8e4db72abcd18874d720ffa2cd3dfca21e1c8041a0db1e05fdfe7ef553a8791307e51403fce360bbf7235b446b7257b37efdb7d72d1d9f871defb5667b857f2c19fa8acf158f64f8872bab69a0e186dbf1f881d3a03fb9cc3d5123aa5a9cdb59c46f0c17dd4275f6a381444c947c612d98dc08239d2759d0412a352c8859aebbbc217eed6b2af58b0228b9e6b003b1acb4a9a12c52c22d7ae3839494353265deff175d3bc633bbcca4c459fcadcb43276327e224619ea02f01cb37ccc41a39a7e67bc943474720a927201c333c9c3a8c30b8cd88c27f9db5352d30b0c426cd89c0404de21f0a6351572fc18b2a7b730eff0e04630b1decd2328ddde51dd60a3ea45bbaf944b89aa9f39a18f0e8ffa26b878ecdf246c72d4e4c0356a78c1fb1098a3096017b16bd8a467c416e94360becb7cd6a06e1ed2ca0a4723fb7bed5267335c3d5a098b1b0fd77288690856fbc259ecf94e9c800c903eb7f6c5e5910e63dfee5e44db5aa61a4d726fb874c8b17cbf457f240b3cfe2a54d10b20e3c91dbdc7083611d3f3d5b655726d0c44055a891bcb44ef1e25b485fa4ec1ebc60ec57f08561f82f799774a9972b745c51d1d7520ce5a2787a3a4eb68664848e0ed32ddeda4b7b8b1bb722e4d72d2a9515229eaee27d5731889e0ee35e09891d5a3bbb5aacbf10b984dd1dc375254b86bf4a2b244753e6ffabbc5fd37135801af3662b16dca800c8b2bddcf682198ef964b65dad64c3e783a9cb192fd8bc73811309dd0280482b1512873a089723705345d8062a840b9a633cbf170860e6750ce8a976afb843f41e85f20386d72f2b411abe9b3232aad943386763166c3e343938912127b311cb74b02b22d911e2157dd9c9fa5dee4443b6227ba55cf6c254cc04e51527eed375431cccbe30a02d7fd368e00e985016c2f5e8620d407dc00d1a245ea6f2f0a8ecc9e010f2c4872e526abdfa93e90abbcdbb863842d2b4888bec063471ed180c2f527201923d64da62e5758d2f057df07b39b1ce6028be103aeac2dfa25e92f203f4698abd261723c230df966d1ddebd703cbeba40d0f67aab4582d879a5ff07ea299779d801b3713cda840de5bdc2b3649c9b780aabcb091070840539235c46bd8adea92e53d1669ada9f7acbd7a6a284834b0252f6d0264ac73d5db43f972df2e2d89237b6d72dacf0b6e71d73128c0837b4834eaecf4a0afd32d66d0b5a52f661e69666b7872a97484e277880e15d39eeed1429ecabb29000df51e90e16f701ec728f5738d05e4a1b89104f8e9f85e8f5d27867c351034da3f70e42c4e7e263fc8f609d233532e2f22b7bd18033f8626fc0689685913156249fcbedffd570589fc9cf1e6797267de7d2697760b30fe9787ca4f2075811152ea95477927ec9046fdb5527d9e703494d0927a6086a465444ea1f538c723d7676e076bb99b435274f284050d824964b8e726b6def1ca03d66035b17e3e397c735376de9d1f9ff81687c598475a3314d034cd5d0e0641c01d38a86e8e0c05c5dd997b16aaf9eb7c6c357d4434c613de63ce8475b5daf2943fc4ab4044ecc67811b5c6bd249bc6834999d143c6126d713a1f7caef28d907edb45efdd866d668c9ff6277f68f4e94dc67c7606afec723c2e6ab26ddd21191a8d68ceb9d05d2de02c2c2a575ce27e1dfe308a7dfe970c5a396b339450fa1dd4a2707d4b665c03f72dded47a7fcba510c36799fff0d772ad0a96e470e46da06644dcc5626308d1a9e484cb14579e1e4ebfb48421e3143877722eaa5a4a1de0b45ba70c17ebca869e6f958162230222570193fed1d5966f97943ad10c4b6b7ba9554ec94ffa4cc0512aed88251e33e4c066ec4c8d8b5e4d3f631ffb93012b08dd51f6d1a9c466783fdf602f328d6cff99a25ded3382a972353e1e3c548d08abed89f77d7a2777aded611a81ec7b48e34f7adca01b5ada724c4e9bfd78a468c353bb19b3b767b5cbaaeff29a5b639a1b4e2095afac28df720dd0fc201fd46ed50922720f9375021cc386e017f81a44c304bad28e16707a6b686e5fd9e83008dff75801f8cbf5a16d0a87875ea03670dc136b48da0970553c6ed214956f3b72d3f83320735d02c94845f0ea9077e3e722508f411fb5db394534f8dcdfc095e6b95884acc8dd3c653c43f719f7f9e5aaa530f8b6d7681fae72acda8d75a2d788324c1e1a54c05c8ffdc6eef0d61970978ce4449a66abaac6723457112a8c665f2e98b3e658b2ff22405f47059bc52ea52504a453a85d6f2d7294debd185ecb4978256300abb639b54275e7368ea2acddb3ff161b197e05b660b932a6be983e9d54442f12f43ad5ce21839b35beb96d18b3c48f5b6166a70d72727b8f9c86dadb43023a5e7160f5c53652cf75d07af2295e35474513180b057240089481c0f2f02ecbb34fc785da3b7dc158e26a3dd24e091fb5951930487572bc1b361823feace7895b85e15e68eacef540bbc11932810d0ee8f450d4750272b3a705866a77a3c1b95593da2215ab53488b4b41b4598f1caa78054793899272537af5c9c78b0f321fd66cbed4aa6aa12e795920d3ff336f9287f0664652d672f306e73a20d29d129f242699ed7468e0e0c1238bfed0ebf73625ebe4c5b0c2727378601b8a6f3993a7451e3386920aa6225b14c92fd4b56d114071ce8e470f061f29cdf4dbe13a4422892e2917cbfffab8693c65a304445b0669a1b063b89c4dabbb8a1209cb877eb33ac970ee98ac6f03a099b154d58ebc018dcb9ca0bc2f0edf1952e5884ec4a06801e7d5fc42e8487078e25282f2e9dffc465011f62bad2baaca9e8424844336ee270f357f4262abe3773c5172818e4711e73bcd7fec4e72e2c8d1c7228ed2778c32511edb89a4d3e55b650a2f57aba62876b68383a18672f9ab43429fe60f1566ad4f375344101b1a2476b5c5debced45dba0541155ce72ec1b60987d26fc7505856dbf43789a4eb9341ee0ab885ec31b2c1f448d93ad2f87a8483a13cd0cca0ffeaf263f795defa6937f2ca1539c905795ff6d3aa68a729b5786f73e0f364e5f0ac9fc45108fb1cdd0f4bb3947ca4eab105687714351384162f5c7f16d4f88baf39d71f171a3c7487d91d02ffdad35f9fb0a3d40a6db7275b8058717333a195fa286ac0c4cfe0dff74046225f1631f898eea2b6afdc2724938d4b2389d43d879b8767da9159b58e40a203f7f9aeb6cac0cd10ce45002720380a89f2bf38d3f7c2ec79f0973f8556e8d6b176355a47e1b2a768679d757278ba6e11c8eb2ce8f6de0ff7896a56682b7a1e13c1105919a5dddf996b8ca8d72909fa9c4751b9fe64f80aab631e5b5fe50c0e9cff57c37bcb948762dcdd1a929e8eb8d3b8d47367e7f5a640ae8307d9539e22cc42d8c68ff27b39e2882854172d1ebf13883a342414213a6b3fede3ad8b72deab8d7f4f762d31220bc03eb8c1001ae13ad4a933238d29480dc95ec4bfd273ce4eab3291b7f106a162970ee886b0ccd0645afc0e5c35cf93aadeab3efe3062df5df9b9522165373aa2d55dae672fa33393bb96ace7736fd1b9db585d9e96aca1cb3f48495cb14b0bdc3cbd91472d211a01f3fa4b1553f1cbb630fe84b771019c9d5fb0b2c29a238c545c4385672c0e351ca82950abae5860fb8d5b56f39a1fb1977c18e42b5042b984f2f263672dc49448f5e980d3569e012fd55c51bff060c463b15fb4e043c129bd3bc163361757fbc912515e2894bcc0cb13abce9f64ab4b014a9cc78443376b9fd07ecb572f05456e159e80d1f03c0623d5f581e5cd89d1e38c864c118032c9509b3f62d298000570203c007dc48707e9a7d2769b077618ad7fa20ceeebbcb4dfab015db0f3f87fa807be49c4b9b3d4a03087417e080fe66fc4336847f70d03fe7c661205edb47bb57df0977ce6921da77022b56e0f79041e7197cabf81b6496a59d2c197295c278844e98f9e09429c4a4a031aced8b7306db9923ec33f64ae2ce95e16a725595eb54527ae9ef0c6b102f887851411aff0fa067b00598d617595255a39a06dcc3d83c702b533b618a248f3a45a4583e57347533f52c50dd9ba6245564fd6825f7e12cdb851373eccf3aeeb728e705519134a4e22972059fdedca3d974bf1c1f3f69f329afc4f471356751ac5aed0752f44f543bf56a54adf01a65d24734725a9a7d1044e6a1f75ad53982a0f31463c322601a110b02173d31d8ffebdac322ce4888e45aa4b96839322c6863e9e50cac9a3f63202972d232fa85eb1c92e072d9c6f0badc8566ebaf54115f85f860548f18d7ee68d44b12d6252fde16891f64580bb1f2d5c45089f32edf8e4df538afd94230d25775d052f4ed7aaf3746ea10d573969e74d84ad85ac8d50183cae76c8baa211092c4485d28d89efbed5a10724ef9b746f90f2a47e601023c401080ea1d9f17beb01c90c7e8b6c83c0b8b0872be2e0db6151651dd6a72dde577c75cce418f311a5bc8e8e48ae451592d551b198b7f7f751472b449ba5bcaf4b8e132d55a771c2f890c0faf3edd1473cd3f3572e7cbcf49303958567da71783e2673aa485c6385343da1d513a1ecdf6822d1972b73733cae2e0d839c4d46b63d82ba54ce781f21ccf83302eeb3d1ac48ccae132f9de7c93c40142a9ef0de1333352caef5689bd767b01c303ac6b6efb2283da677340bae6512b74cb2144617ffc447fbbf099d3a42f6e52d7595651c098d50b72df756f32fdffc65e1285d4859561f4f1b5ed79d225c1e1e1521092e71983d4721274835d6dbb23fd6abf6915c43d0dbe46c608f428683d9ee3bbb5c782fea73ca8d55726ac0ed0bafbafac76cb12e9cd3a8320a7940a24984afb06aabfdeb117ed31f08b3ed8d1241fd2b3bfd006104f927d3a6b691733fce489281706edf07242abb3552e2eb3904934358e2742a41a757f29033cccba133d2ff6fc2004ff7283fd570df2791529e77097366381a7bc87e12e3a68123f5f98321ab35fc5b172d55d5c435b6125e26a0e39e389bc157adc3c33aafb4986c1bebc28ada138647289745807b1776b2fb44367c1bb288097910b928e4f8d97311476aea206004a39f725a107233ea586051bbc549d620d6d6d8b2fb9985376b3a185d5ab048cae72353fde5f26ec8f0a045d20cd125f496a872ba40641bda33f2119c4f991372c726f6fb7be7086ee1c48efd86f83e65fef3dcd28931a240718dedffdc4fc04bd296a2135d097ab21a827fd3332988a4109861e847cbfc9b722227df716091d9872706bc5918cda3374cd34afeb3fbbd6be48baf705a2ca138cc39a740f760b742b49c98091edcfa37fc62e13418134edd2ad394d069b899b11133ce303511a507294f8c7edece587664ca0134324885a311d02d44b5d92521e87affce1596d10723966912c3e597ebc646e0d87070b3960a1f6e50be61622a068e2a9114616be72bc64830e92bf6969b9ffadc4cd967444810f25bda2f6c8c3176557375dcb3b728801bd4716e1b852df1793f40e1bece591fc776d88db65c98ab8e80a66b632728f4ec040aea0146314573d7ca9caff10a697340c5c160e43e60bd9f82cd745729da8fe4f18730f2a5d966c133e05f2b6f1a79cf004245f5928675b94c6be60725e7202d8c700be09ebffef94eb1ed275d3afde400f70badfaa198244bbec567257a8feca786a500a023780191f5fcf0a5cd197a9b49ff3db8f2bc6d21d261b06465e861a6e7de5faf0738fff37e1634dd80feb7fe8e21182985faac3ab21aa72ce829bbe3a9fb85434205bebde5d3050625b6d2422bb9885ea32b90633769d72bb0864b33a03c0b102a3d3e08d1795bf46eb0e6a9c6124f6317aff0607743e722273eb1ae688f59bddb36b06ce59ee37e621f72192ef95e2d2ae48159b73397238792e25d6557b250c07115ce3320864848909ae3091aa85b9622f4ba0ae0a2e75d069d2ed6fad7381e9445a478255119d05b8e1ad75f7afac3385c3220c843c82c8188dea4b4151ae287955160baa706c153173a708e9cee75c3359c4b5f90e0d253e01c3152634543edf6d047daf71cd2aa02e12e737b88fbfb4689c56370aeacbc4a8c61067211145f86ee1a4c5b2172d7714d75583e46bb4b53a99022272670ca44249d49a30a045db552ebfe6dfc266faa2e77311460994109e3dd3d23ab110ad901e92a45109bc0874eac9f0113a73214fc72c16e6ad417d84e6c35a37da667fef98f435528d6a2b153385bba4cc98e496b7fd19cf9a85cd28f6daf972207deb5b8c30bdfbbc468ab638bedf3c63c4ddf2249439138d9b06a8d2235d72bf68102b177dfa9be09b314dc4b18ef134ee7a7209bd8ff42476b16aff641a501c717b85a95239bb4a801a29d7832eeca02dd5886b6a6a456ee7113f74e8ab72b5c3b53025a4defa71d34b75217d30ae45714e08a1dddc19fad2e52e56a14d302c003a28f0524e719d1ea4cdbd475c049700feda2a5c9714923f66371649021716d539be9055e560ff5010e8757ce74293e891bc536f27235411971c9787aa72c7491cbee0a49c6d3bd263d3f6521bb244bec743a3ec16c655e3e97143a13a7251d6a5f04d5267dcbfbdb5d4ea719e922408f6d2c7b6ef02ef19a9d44dc19516ec554e8deb1bdf17e34d62f8e5bddb64cb7f34ab5d4240d4076c4e4ee386dc72cde3e6811f0ca71778b3fb494b216af5fa9f483bff0ff81751d5c5bc8982e6134189ea17e07da501d5a8c76b0461963d2e4352e2c87a5890eed06d6cd1b0cc6255a1bc9a2b1f5953a95c5880219aab8ede3ce25bd72a042ff39427bbfc45aa72d49eb29f2c0901dd069226a2e216754bdff448d996c0f1b643a24a959897715f5337652a1b14280b694736c93a5c3084818b206fd0c7a30c4e4cc4e4248e6443ca4d2ab00988dacf6d49a25c6bbd74b7138f11e6acdb2f155dc4650ab3627d1ec9b16043a66a22de0261718d3568bfffad0ee757b42f441015790754d421eb728ab66cc99714e7b5842a8cca4d1e2e8266a1e5a22c21ed0e68305ae83c7aa550d723684627f96119fe48989d9a278518fe2ae4f9b18427cf7044ec775c151f511bd400a752e96e5011734380fde509e2c386acf4043dd76887a6a1c1ad0d240f4d5511fa64dc501e7d1d905fe555fbd10e6cab99a52de2a8bb6d16d5c6241d72619717c2092bf53c38da25632847f62b9b9199776652e43110bd26fce38693726e67a6a2f3bce9316feadea571fe42904ff6f4104dc612f84905adb0c1c23c724cd28e862f6ae9a7a1b1704e25103d724d41444c8bca010f7af4039c3e9a21259b8c24c3f3b708eb39458cc1050ef02beb71f253172c4f276c405c001a312a6a5920c48f25b02dd9c8dbe606683ef46a740e6781e4555749dadd672db5f1be1cc6702aca3e957331185143be782bc305916cc36eb7cf953a4321ea0d49c1a14425eb8561dd951f7b84643e757bc20a1792779b6f7e417f0077deb56f68f09706338ccd0c7e0428c1dc5186485d491a29f5b19678a4c0ccd00bff94960c66af4b59e80cca41ce3f80f0b938bd86097f19c42b599c1d79baebcb74e4e38ed38c725f93a24fb04096717dc2e99455e304ed205dc3f38357d2ba174df513a0f39e72d9f004fa482a2db5db551e31ac706a719787c6bc1f3266556fe27663cdd3a672442cde30d7f77451b42574edf615b31d3fcceafe0f5615ec58215cd88ea6fd49ef4c1db3b94b3ae506a1a009da10bf1fc9bb9dd193ef9a045399858f21cd5017d683bd9ccb1fb4d3e9de2e382c8d7eb37cdce5af4dda57d37bdd6e143c4c5172145f1a7309a90fd828f44be67f0d391ec91f458c207560bcc88a3d867342aa298cac112ab9856de56296d8915ab3913c2c7dc2033605df12e3aef01a055ddf59898c2878c38766751f99bc85190cbaa13845aeb824d508922856dc51e99b737259d73afdc552d818962df585de3e1404be1676c19df8b23c5442127010a85b7275039f018501556ac619ccbf45fb8d801de8f098c4e42591a473d1c39f32e61e7807ea958e6fa0df7543d0ef952d255baa6e152c3fe3624f1635fb513dacfa7275d4f93e0da769f25efaa45e80c6ef41278db88f5778794558ea9fc6bae52372b5332c597bf361018c33c042923e8fc620f2f5181d1d03b48e5c6c34b5323c0a830f384b36e94422f55af5b45b7292c6773b9857e93461e574ee86992c6c9a720a703bb97a42e03dc01a19eb192faff34d14d04716b7c4da99c80c391e65fc72ae744fc82a147a80badb35162d8a7815f3b36740917db323daa40ff85c68b372e40bce6c58b528e007d75f376a507b9129a14c97f33b7b7c45389ac8e86ef569733f1b6921492c0f3e2cedc30c9fed1333af5c1585cda8fb34d010d839cda572584408b7d1ba2cfcbc1900a01e0f0c407652dfecd4293e9f277f096c8b92c572315d20ecbcc04e725c5943a359a9afdaa8ac3268054cb454c6034d3896e081485cd552be97284e082fc3e6fd04de836e52356df0f05e38c1dcb837b49dd5427202f59701f84db076f46b709335a9d960e44f80f8b4b2eefe6ce79f2339a3d2462adf5235c19d2f4cdc2abf09fc489eb81d862fd4737452e9937525eaece80f72ff37e80589fd9a06760c25a336c3ecfa7629e70716c3db9ff585b378e30c04720015f820c3bf361c4403e85a4a948ea538a7ad2f32cac4356c33f9b5985cbe3d6ef70b60fb96da20478097801a32d4485821af2e2038d9791704072bf2740c72c01ff792a2b7e71a0908958d412798b78b1ae372c5383549087d63639f45df72203710702c8fd090d7e45e5b1482bc5af1b90a6b70a0b165a10d39ca71127a72dff9ed561e1936fdbc5d30f5affcd2e7a63b1ca74ca6aa17d48ae3fdaf2d8652b5fd251f874651e1790dcf67d243e6605bb7eb5fbd70ba6765c158665eb0ec5a248741804290bafd484e60926e2746f50d4a6419c149881cd1a1c976c403e172c8b20cdb2367bf7f0a6b5a26769b0001cc929f60ad3d8c89a2a6b916361cc472b87fca7a7e4ea5d3428f54d773e752aef75e43654894a5ca0963d853b3d1bb7226652bac12cfdf292c11795df522de74f29458804022b8ff258e5667d4a8cb5c73539b032dfb3858ad80380f8046053e6ed91f61c9e5844fd4f59a5d37f03072980b309ea4448e6faf9f44c8ee173390752c7adb89c4a087856be4b7f56dec5fbf77c764f2a9f1d148f5a77a843c21df70ddcd2c26ad86e461056bd067930272e16bdd8e90bd1bcc4ce407b833ad56fbd065388e00a59ceb67ec3a41192a8b72e201616a461086400bbd0403e5e603d5a96ead5d009e98bdfe979cc38233c572ce2e5194611986301112449ac343ebe183d15884bd133ff02328cfbfe6e6646889337ff362eecf60a448fdcf2fafe3a3e49629ea318d51b72b851e2f166b4d6ba3d68d6950fefc6cb98a0611fdd8f8d68d3a4648c6c9fa308ee1c4a5e86a32722e1d89bf0db9661efcd8d943f44d665a65364cafe12571e69b2eb458d61c4e721b16e1b6c516bb0161534d218c92bcebb3fe16ef60582e2d94be4f4e86007e7201bc44887d95595266728d1d5f101d2e9030e667a895fcd43927468a2f2e3d3360e9449e4dbccae47f97e62729b5bae37d8ebb3d16cc45698f554849e276da3c14246f97b0aa921e4db0b94d52a9682c8e9de42332ecf0c89c73295ab3c7dd631ea5c6d8eec2e68799b178d7b0062c0553b29ab11b00f8ea972a86878e66b26cc21192591bb33399d2ecc3a60743e817d55e4e10d2fcd9d2907a5184ec836e726a59c261448c70554437d86960cddfeabfd0878c0cbc2af5c5be53419848b572373272aa1543dd0d722d3893377a497473399712d0fdfd6c77dc74d56700d3146052326d60b2efac47713b81f22022ceb2f4d727db7bb1fe6923f555e1ab875cb916071fe1d6d14bcb2d696fffb264b9b32696a25a235cfaa6ac2f963341d272b3c763f017edd4f397a261ff08d05c0eb72ca22de95fd0e4a0b30f5d689c3c0c8bab162690d474a0ae2440a042b7d941d46e9a5e0d129535b6ed1648ba7b99721367c782109cc889d0be64195fb5bbbb176bfcf48766e60ab27597d2903e2d39c2792be37b77c184647100157419d57b2d29d6c84f584f69bc4b7732b694cb72408fa50da4f36fb998199e7d24b2c9e585aaafc1ace59f62c81c04b6fae9cc72562a1ee24dc1457496cd635869386b9b241371caf6a299b6aba5f1a0f4107f296d7069511072582311c3c5c4941c0075a27a3fcf87722f11126d5ef1ad6213082ee9fba543bb510ad4b29f8eed822392ca2170c825ef0194ae545533d9ff4172dcdd11098df7f6bc6a28776f734b84fa45c168690376a6ea09690cba73a55f077a7147984f2be7844165d1965daf729b0e8e7ff377bc32d24dc421107678d17201f83256443d3270b40a1cb59a1e5a406f085293637221177f61fff8628fbc2a1798231431324b77140b6c911419357570451377666a83d2e77a469285faf1653eb15728d276a081b406cac20b67404e9c5b266ac0462af0867040d4f370e07296c8714f9985f1b0fc5843fab40afcb5524a1e9867aa6258b7936ef0711eaf0ce03bbdabb535d59f334a801ef409f238dae8b7ae5c1f39451af8dd4768f899729cf4fdc60d34a4f600244b484b40b8281d5a1216eca767eb806b9528252875726ccebe4dbf0301b49fb77d2480a5fe2f7062ed101fcd7c816404d1b7e5a5c704698a050202ba7585dd77a36c7894530b6c93729add0b839a076fc44c9654397014e0235aa5e157036e75c731199f45ed26866167f1fd035b9789e6f10ee3e27233c971f4cac2081eee5887f65130533252841d790af95d28400c83e23ca21f7299a30f1f84479b3a6d21e5ebec687764f15d609cd1c53c2e5bfaa6712819540a4ce67257fb52825b46a9c8ecb6f6a9cc95c8f7502fb412a29592fd10e3a18230181d56b6031f22b7a9a2741bda0bb157cddb7ca0852746b50357dbf24fa51658ee7805a6e6901a8dafb0f5c98c4d9ff713b2de0b64b411385d7b3af71edd1a366bd7e26dda432e2bd6e4d1cc41d9620b74b1a9dbb7ae299cc789d477c719e172a098b6f5af0bfe7cd515e48958094e57f67998159e0a2787cad6f615c8076617fe6dc8cedb3547aaa9d2a7649ae7bba87810087155cc6f08e4c38c9550085f31010914617a540f4f482a29601ac3b59c997793f854a84c1cc5048af262e1317296efc8fc19c36389a3abf2aebd238dfe75cba59096290fa91412babd093be872c04001e8c376ce799fb78fbfe608bf5b0c1a517bd7e4887c67c5183fb8de417229c10029efd9ac5138559bf6aa10faaf1f83359e00ae0d15c23fd9afb23b85005c883b964a146101f210fb1a2bc39f6b6763e89915603749b264916c837bc972ca84dcd7bc0864daf60b87652d6bbd4017a68896eb141bb4d93da098845f95729d97d47dae67cdcf740284ff59dc0105aec2c1b984fc520129e9e60b2bee665c53022d2171fdc9fc1d314ea045641a8c65c0834181ddf7f8bef5bfe0de5fb82f72fa2f2dde6328c714fcdb2b35c2a96dc3c8c47c531775a0877279ab9620177206fcebf010299c3a25686f40e0eaeaab09180a5b6f3a90a1d8d74e7247586672613e36d00e49d82ff407a9b401ad92af89777232785ce50a4579002c1189f04f99829c7f845ad0112dbfd0b0475dd42175e2446b60db42852d168d7326cb9171ee8f647dd582c92fff30a6eaa38683c35333082d4eef15563de98c6659354a72b6ee91b17866d763913f14fc9c2807826176bc6147795600bedd955627a1a2448140be845796f063e87dd1bdadf89108e79cf05f4fad612c2808e75986fb7f729ca3ab2cd36b7d1c2b59c78fd393ff56f755b4e7b42e479fe064016dfb13f872fd314d38fc2583dda2832ee8695c897bc2e26d48e304a6d7e76cac12d58fb3515b5e88da7da0b35f1963eb660e0755c39feadeaa5f3220bfc4e12e5fafd482036c7513a0ac43cc70e0eb891cb29f3aab3472621691d5d2ab993423c39be39964c9cfc0894fa1d46ef42babbba42a62a3d59efbcf064ffb62696c2b183e6f903d433f50403c6ee325d5d4a571f33cad7eadc444c98b035fdf463480f6beb50313900a3376da1c933b6429f3b0e16be40bddaa73eacf7fdb5ee6ee23f931754972bdf563e48a57a9e316696bcf42c6929bbe8b09dfbb78f6dc892e294282c43015a500baf5d4cbe6479454b6c84381b91e1e905c75e18cbcfceee6bacd09ff9f723e5ba1603cab766327eb0e6ed551ac61d89bd66cf7bdb2e301552ff1cb74b42e9584c0d1dc497dad6959bc131675509d5a7c1f1bfc424eab15038c8e2495653e9119363fd164d647f832a9d224b689c1817fccf69770b5d5c60c2c7aa82bde2f75b8653b0edba4e0cbd7ad42e684ad326803687e8a161e155bd559a662c02b724cb4a1b806b8412760ff2645f7cdd05907d4fd9f242b887e020340a38b187c722babbeb93e4d208b4768c8255af3bd25c64908b6c202fb3538b73d2f88273e1e8680f2c4f8d2caa2bb3e3eecb9391c2c7f665140fc0a54ca07cbc01f91de62720188e00f084d24b6bfd922aacddaa69de6b7185e3e6b5fb8b7cc36b041c56d7282ce3afea24dc92604704b0a087e5abe5cb33560d18e1ad5e964f37e062897724d3e4568ae251b2a8e8f197b99c235feb1f732304041423ffd7d5b5d53dce672980e23ae0df65ebebab032871a63a68b6c13f4ed3a0df46ef424ded0a7cff77295197898471abe5bda190c5ee4075a44c97467df06ee56775687d3fe01519e65c726d8e6491526e3c4b42ccd440b00f662546ab033d6482ac73ae47e028ce204ecead987a25f13d7569f6713ff509ed27c1091a1411c902555323a71e5344872138fe6c9764f058339de1baa9a7f548929dad60d7a897f395026f7322c233172301f3527744e1aea6e7ae8cc93f9b1203d11ac5cd56db05c46957763840b5a726c31cc8620f6e748b5c906e0993ac43c320ced7a8fd28e0aec06b11bcc1eac72626baf593eaf1e5873a6a1276e0a3a379d96b8d74b406b340076dc4a2feb2700b8b772cab44c5b59f64cfaa84c210a0d64f89ba2e1b0b54694b14e84f036cc6409583eba7bbf5e0094868c74a9df3966bc526969d854e66f3b80853367f73472ad263b2646bf73646ddf4011686b8f6b2b825f3518ead0daec5332aa781f47725bc30da74b38277989c24022513883046e52e6af6d2dff99d9bdf59c5eb61018dddb2b1a61fe764c8031dd046fc8e2c504c50cd97bb2eeba6dc28929d898c70ffb76e34b2d5f8dedbe9b268fac84bee5d35f591cc5b71b14ce4b6aa6872bdc22781d4b673ade47950f1fe2330e7f25902d900d2ba5da20a0e8469a73d7a4ec72a1fea55626891990c6361253710f3122f680209d0972739f47d9577afc5d7c332e7cd0904ef480ec45549f3a9a881d61be2f8feb47a5e84b8d621f4962a6357217143da4d52d7bb15d08585b6cb015e59a8c24510cc119ddbeb3ae200a1e7d72c358f98be84744708b689811e52423a1e8879ba8f29f31496159c3fbfbe534720a1054db5cba630643a9fd612b6159bcf40c55db118ff6b2dff9ee7d97c6164820ae4b5ad7127db652901491579d85b90560964f71320581ee4a99bd8457d872111503d65fd0bf2ec43890ac87627ea92ea2c9265dcd43f2d117a1e9daca8e72534ca4f662f669159633556849a2134b5513749b19002944f7b4016c081f8b41e89ea67f293f353c95c1ff71dd9ab94647aa79b0d94362a266b6ae9ff669f054a10714d7a8e8541262f48fc5071032e42e42f683b19ed70030dc101a8a16324bb18de3d249c23330c83d739352bc4964ed879cbc6aa9ca7d89736d1c9ac1d0720f5a82d768520605fd93285d259bc14c4f202add8d79ec85c55ac3c61b36dd727919b6154440be164a797b341636b00708388f95ab2ceb23f091d142d04d6d7238fd967f68124982e2f8221d848bb105030bb7d0c8e02a4f782462f1b8403f7231ef1786328f55513949831a014a9c8df2447ab444dcf6f9ccc99c691adf8872024cc12b6fdbb68b583b467acb1b40a7aab949186ab1abd3acff5e454a4654560c88544c51f2c38702cde418a4399da6d3f34e897f5ac06abd9cb440cb321e72f46211afaa9b281788a60d8513d65a1707653451e99aeb3e8ff45b4b65845e4a2c7a9a36239da5d7f8631eda738f439c6a6d1b9ed236ad9a7f5722d6e65cd672d36ff22e0941531643c8a4079a5e91e3c2cb62a1e43782be68d9390e83f1b523dba157e5cfb7ff01284f5a3e61f8046ed489d050a1a550e0a7198b4b75f36972ae87873739aee73206129bc78b8beec73dccf63f272f13a232e43103e238f572a388440b960199fadcfa1b683a07ed2088173a52dcf3de163941cceee1b1b372364e7cf4f7fa79ada6bc5b855e9e46896debbb6627e6bb648d98a55b8c4cb03998010154be953d603c12f03b6a4c7f1c2cde9aab1c1638426029b1e9b60ed810b847d5d8c79f338ffc280a44659f7a6a6a4b1c25ed870907356b6cf80628af72a6ecba8b287b1021f18ad8df2ea4b26d940c323a0276dd5c919aa743aea7e53471ccf52b3aeca0a85ffe4898f0ad7eb99cda0ef25496af11839eb825195634727bc686a5cefedd356106180f76732d2f3bcff27497b6669f7855b3ad6f8b24729e601d64142f2fd3d9de7ba0823605ed7866c36524a586a5d33b6d4a5c8ce272c00d8558d1b48172c4a31713b01ffb3aba8141254e34cd7b00f26d7b0d4021723e8867fe4945e9e4361aba642335bba717262c62475ee212015dc0e4e90dca09be02e2098975bd8c0f48bedc687027ea9aa36da4a35154ab4ef6c2793c46bd10d4c48b588e61d30d1649d14eebdd77b67bc14b78e39bb1b0c57bdfd451e4950be03f06ba8889934ef3685e8305f708a8fb577af1bd91ce3de0a31e40a9f55d3fc0ae15ee4c5c5bb1eef1f7dbb0478919a50523ba19dc0b98ccb4d77c40ad6b72dac6c086df7bad3994b3bab3ef35b31c64c3dd30b3e54cbd7e4aed43aa5705724e1eca8b66bd58704b3cb786ad00967fe27e277c9f2a2b4d97547b5d6149f47262709cf54e599ee3f8ebbec1286d3c665b716c5de678a339ebd7d99fb61f72720691c7c46453368aeb9c8f1118a505753195e5a7c7d4c6a9488dc958335e686350e77effb32585a3d9d2ffaed04951b73e9477b36d344a97a706645bfba788014eb95794aa38f76c058754af26ca6c9e3625dacd73ec89b63298d53d87fb5272dd4476382d9c80a8e2642e9704ffd034b39e94918f0af521e937cb20356f80721041e98a51db3b5a4d887357b5894d2a7f16b80817147f5a259ef2014206f872adad2311729d68141843830feec2f14fb94467a3f73a232cbc19427c61ba5272b27ee41acd732824f2d0f5f9d7627177f80787971222105660f35a3aa245d172cc84a8649ae3b023ee4ed7a22d851f4154c909dfe0b1ab55002465b76432421bbf36cfe25c0028bac0e6685779ed47d071ea458a00574f417924a6793c30844d0aaa0e93797b45caf7e3a209533b914ceb48da8598e9e5254d6deeeac08ee341e739a3bd78abb5f441fde4562701d6cbc8bef5453d4b51deae6c04b44a0f1103cfbbaaf903fdb9fbf04665334840747b3babdf0713f290c5faeff1b39cc2de6db72c6580e735f5052d00a2d025f05dcb0e4633c9d49eb64771fda53ce1cb4204a49af5c6cb2c18738d0a0b732abab03d149019b94d517d8ed8b9e073c97599725ac55c652baf7efe11a85854ba1ecc81e1cca33fbec38ce1ba7977fc946d1772119176ac2586a0fe06b6312b99114bd39a9e756d2e4b3954773792877cea9b721d02e45140ecf6aaee1c76da0df4916a99cbb87dce6a8bb3442bbbfe7c00ef5c3348be8bd68e470071442b7c43a4850db5351c5f755e3e7f119fad3961fadc72a04bdaefc6b618165532097a3171d7948d636895ea94822045b7e1ebbf5c375cccbc6d75efc1b6b720c30c230967ad2d1c4304a4f53bcc4b58b79e92e073ca72e806800dc5a7a44f638c7ad47114850908c01644a0d0c7204b38fdf76c70d772ccac58e0535264aca440590ac9a873043ddad3f3eb5bd7d1f5e8b639b2f9db70698b934aa6e31a334e6e06957eb5e3823c6b73d5973757416d211dd10a9f72725d051f6a5c58579e45756a8880ab0dbfe0cdd8b154e27c1391f7595e8ce6b97248998767107d33e368c1925c4b8e6bd225dee880e4e2c30f3674a9e501b87e722dfb9e942f4fc4a75826ed13ad43ec515a0aaa2ff74329444139bb32fd324f7236ecb2cf39551d0e2efe96d33a5c2491d41b8a821b918cc479bac039e4c28c72166a1e3fccf95b07bf4dcbd5ec9d1af321563157be4e97a336d3762dec45577229c305beeb2ef302f989162a53a0744c3bf8aee9ec0414ebe6cd2233a35c2e10f198b6c9baf66199289c90ddb97aca0b926942880c6931bc4a80975752be95356d9d39b322b449b658fc85c5f527b56459c1a1df8e68e39e973c1e27f3af2472ed77d50ce0a5f3bfee9ce5e188be0e3ab3113477bee9077258954ee4a4bd2e61e69f36663a1d17580e581a76632b145ac2a5f7bc41821085cb029566d81d38727e5dc5a56c2da4bb07bceee999c1e82440ca749aad5a57c572e2830c3e0bbc72ff8fa4003b6abe6fc431b0fe25c703b0bb7b36df1c4b34af54249c1b42423c7243af49ae6593f2969a04dd9775a2cbc9768a7972fefce2f7127bf801d36e53641a6c9685afc9acc45b12d403f7bbc295e9a0a58ff73ee279277124aecc05d90b3e22b823d7ef719efde63dbb00393d3ff50354f9f310a4646bbc8f3453075063ea1ad720b5456e6244f6622af6055ab5637ee5e9ff0d6c77df9f488e6533261ba6b8d553e1b68b3a531344c69fab3bec902ff6d6e83ecf570b5e1bcb02c59572367b0f2ff0a15e8c734b0f06b18929c422ef2451dd892fdff997ea72a88800722b3ea79faaefb7601b245719bd595348f30f4a3345e6f5ece773acafcde55472f28d0194284101de94a52c4deb5386beded0a6073011af9327f2f0f292ec7c729b2749850b6fc655085941605cfbf4e17db184e1618529d179db0c573f056b7230e96bee7f1b51d0d6632a439dd5fc59132bac328ffa12126af84debc0bb2b66dd2f317032a46c141c043e14b54b4a9f353a0412bcf4c90f32a02a965b8e1613abbcb4ab0900a86cc689f425c6987042946ea68c258fd72b50b95136f0d51d72e863395b1768911d55ff83d102d981f5764a3e98fceec80331a60c055f16ba723ab10baa49cb2bfd9b0fb36e8472dea8da4437f070badd84a2baa8bcfb61414cdf550c56cbbf25ac44ccd68493e4e796dd0e5d9f24f88583ce725a9978b500723214d4ddb38b43cb013a9bae98f93b364bb9051676365d416079e1fa91404572bae5a98c7044f6a63d8ddc9dd30e62b2030066100e822c1c56384fbfed678e301ba630f20f0e6aeebb221d7e7457ab1ad8e2643d1ee50b283735ea324690c642f99a05f7139e1487b2b5d75fbfbeb53d04e1d0f746c89c0b11652afee9a44e722af6f57df8fc4853e6866e452cfa7198f144e3eb8174e7b36e45097b359cea72c7823590a72839da6cd3f181b9c7642082129638f674dc40d0a7fb4c462c6272b3e2357da4b20a83789664e7e63a680199b5f974669dc8c6b26315ce05f58b726a6f0b78657a664363f052732d3a434e957e05b993f1ba60f36b3030b943285561495560591c8d0ca7fcb1d6f57c1611efcd21cbc855dbc7bc311752816c592111840df7262e53e318ae42c3bc1c80a6497bcf29516eb8ddd917c0f59e1ddb723548b7e016be77eb2a419c4dc496b270d729b710a69a044ee843bc17f602311670b97f0d641fbca848ce2cdd5d375ba1b54ea396ec09b8bedbb7ddd766b81072902989daf06479ef45d7c5933b882ca34449590a7755831695b217d69c2ed47249edc0bf1aac3937927b3321d82600bb939333d4f10e07381f76ca98b73f9012aa3f41697770a82f084751c8d051cfeed835c5b8a9bd7f20b2d8230711d2403089458fcacca83b9c281214fd6ec20cdd0322b8fdf2f98f392b61e846588bb1631b139f79060961d8d0584900ade836904e58c62a004d07bc40af0220d34ac072bb333371016d4a2e47cff6f44da569c02fc2d7bf2dfbcbef686f638b67b67b13c28e627a983b60ff1a9b768a03995cfabfb496892bf0d233031f9aeede22b87246a6f761f37c292fe3983f611414e27b814eb8bb84a67ad4f1a45b92be8cff1da9270ba157015ffc1f0425fe9c0cf6bdf0c0bd95af6bf30a42da16e68a7ee955c571a7afe0ce6ac556a7bcd7fe93d6ff7eb3371ba8531230f5366eaab7ef4c72b9dedd49a0f6c22487f708578c5ffcca9049104f4d4f92d3ff8fd4a4e82f6927f262879bb3dad9f7a04ce65c4a7b3c6a9e4f30d7671b88fde32bdc47b77b7972cd83c510f35d4542feb610a6aaa826f8f6f877801bfc9ee2eeb022cf4993b605d799d3443cff3946e6c03bfe1feb138fff09a770f74fdd5c5725435c4c220c7219414e3125a337b4f58111bbc6fdfcc35e69a6da096cb0fe2674835ea06f346ee5eb1514f9e76850790faba647731b424f458214bfdf90783a5ce955512d513055c575a2f23c513108acef44813914c5718d3a89236519b3b26b8506844df018e8c7b40570269540c5a2271c134252e830c6ebd7150d8b97fe3e1355cf0b62127e313c9bf35d997c8c10956873ff73c2b2fb2eeb29f1229102ce728bd57568128075f773b53fa8d985393041c524b705399f92aa7b7eff565ff2b70ee5c09672c5b94f2cfbde726969e4414e550a16970eb432fa93f052ccd89b53a60f8ba372cc5021eebb385a4c77ac0a44778dcbb5dabefa94483674ba8ed8de266b1176729c64657f9e272444b53901ff7d7acc08d1f24fe81ae4b5687cbb6fef1ea7935fff99fc34d5a88740f5e22ef1f8f9983210e900f1959ab41bf99d3acdf818bd017e5a4370f4827deb13a1802ad8260c07474b8aab77aa0b0819607d47caf04b0f0070740b3d7e28ac7138bb3ac69de206aedcb61e6be68859cdfb077f3bf67b18c0412db819392f31fb48b61da4e40354642ad5b5d39deb991e035d01cfc18472aedb7e5734d53684b96cb1de0ed85f33fe3c056417ef6746a220dc31bd4a3b7214adb954a8cff764d76c66ec3ca1f05f62b70c8846c3e71872bf38f97d4ae972da8338a516fcaf51b369737216aca793e5123d06b934586109504b76b6817a62c1a74bc01ea573d469c9e779f59fb0dff9ae68f5ec5f571bb2e55ec74e87a5154bccdc35eb5b617cc8209ae412b1660cd9bd204551e5d084730dcfa00d3339724ce24ae06ebd251a6a1bef3926c533276f6fe92d53bae90b68717035866fb7585d3b005895158e2f67e725670412ed0cf5e861e38b59fb11b6a04e91cf823408dec0e58890bda3a889d18e4f7df04799866d5952ee7e1f2cbd75eb5c01a3fc42bd2af121671e0bc006db8a7fafa972c73c17ae16724c85b0a5710afc90451f7256afb129a9edb2bb0694661a99243348c5c85b5f55d3e243166d11129ba87a0dc5f32ad98d2cc3e651b6748165d2df7941ecfeff4d2d69dd7d50d0fc1fbf66725a6270c15301a4eef6d452e43b800f8138ec5b741105ea7a0163e2e8547fac07a14dc16878a0bdbc0ae52c1c95ef97404ac44f65c826e81b7a0f05baa30d2b7236a6f102d2f6f7293f5ad1a1fbf21d8ae789ff6f9f41d503742162cd21a92c59dd5caa030e862d7b38a0868be627da655a3747721fa756d9cf6ee093c3aaa472cc57842d32b4f3be6de9149207b9a471f9717e7d9378fa8b8c60698f9e958d726650a6c50e8ad3d22cc6693410700bbc3e6e3a010a17fd76930f40101ac86455b5d5c94f26879ea868d3d98ff12c06a3c49f77e00a95f75e62ce87bcf412be2881f53e41e85277a068b03302bd61da2cc3b09e8f7215393fdf18c4660f4c36096da89be71245d16b8fba736160172b890991af8451e9e26958b7469a4d2ad37225aa21d8d204c098f043278478cc11ac3370a73fa20bd5086494d7df69377f669d22b914d3e899d14df2f281dd2a3b75184500d2d71b5dcb2f79ddf69e6fa35dda9e73f78bd3163775e454c1fdc3881a05ff22c00f83b86c1af0753f2d0dc9625296f205d5be2ecdf73dfe41e0bafaa7d1eb09000da9146e54ed70ec6bc32072cf3c04259a59379259abb1cf04bd41d517f2ac8bacebe1baa98cc3562c8008070967cabb08c17f8fbddec742366ad32342dab5689cc289ca5b5865766e1461399c67d91a045034424efe9780a3906ace380504c5421999d3722811e53dd86260988649e10a81d8235917f165d0f46abc9c0255f553ca81019462c932cb43ad620afec2d53774c0921d9736f8ba088fba85fa9442a5bd9cd262709327f88327725cc16fca8fdddc534694ae07d05044e3ae3810cd4c8428460a7515a896a1707299238c614d8f1400b64565aa3a49ccf3fb33ff02a9989b43275a711f0d4c2810bf1a4534a511da5bbdf5745f8e3057929dcd9d2ae2ed44033b4fb35f270ddb0c6ecc4d8a0e9a2c392f0fd82d66b0e56e77e0251091e356ad6e9c486e7f89bd72c4df563debb6af5009fb4060edb070f8ade6aab9b6f1e2711dbe52356a58bb721f495c272ef9e9be431b5945a9ee5c269512ada46072b628c223a4e859a9694289c3f6aacebe717cfa5f60c2a0ece4c4fda833014f626cfc88dacac0ad22fb7202ed733116c202090b31a9f8823ce768465790bb29fe1678bce96e85debd1372c2cb89f197196d825b01cdffd795c7af1deada09d0daf00412541594a673127243f95ddc1dfeeca8eb4afe8b60c202ce6ebfcd7bfda1832f7b8277e1a418286c53796879ca6181c7f19579cab583ac5206b8763ad1f3cffb3d0f30e48f198b727ae74aab782c1ff1485f1fab9860baf39a5557486d27041926a106f4707b327285601c95daf6a37646ac8c2b4e8c0a027c0d607ff0bc89e9378feb19746f3d724a54cfeb55575cf01b17e8a92e2cfa18088f7ac6fb470a17edfd6d7861267a5638f94590149771004fd86de8dbce1c9e9bdc2ce6ef7fd5f4429909bfd757a44a658ddadecc972e3a7c17473a81c15ba7c59795bd4b09bcdbcd5d6719ce684472e2646bcb5bd752305508ef7e1d682f3107bad6f4ebbfe18d2f09dcaadf747c7246d5a09e21cbab51e652c92af0badc0fab83cb7eed49ca48fddad8149d181772b11dcd121ad21d2c1469db9e220ec19b3d33bd77bc4badaf0cdc386b80289872de4cc92a306fb2d03fc6a35dfe703ac42da47b7257dfea21528c432d94e9587262c95a8a1a04da11bcea4c94db8a1d862b27fca72af98ddbd0ed51240182a7729f8ca542c40a6c87e81ec3783c6c6df5ab157f4b15181cf91cd2af48c1def17242efde968b58982347c1740b9dbad63dfd254a9639c2aab910d9c15df9c113726bc642a6571a8f31d06cd21044ae3d02c80651e7be5a04ea468908d437d006722392ad3044a589f04762d191425ba7ae132bf706f56a44d04daab2dfeb3c83726f12372c4863dba2c6fb9c0fdcff56bbacf0013fb9e024d4332e92b8853d6a727795b47414bafdeb6ab638b420a83c99a1c4faed0aed8024f2b84318e0e3e8360bde33c1a662951c141eb73bbe0e993b8859858aa9c49ae7b6ea64cd5151da25ec3b88794a921ee44016ce197fc6472a98fa29ca9ebfa69b65cb8db4b996616c999b4240fb263dee518c04df26e45191ed78c2922b4fa7898d28fb5ac015b532f3095c9710766bf6a9b4e06b47996e2cc230325ae7ddc8824647168a5e7bc45fb50b10712ef195a6a9ad0279dad27a85f474d77c1ee61b994ddfd730631187588d5f436e69f05bc29fc90d55b5f6b294035785a402f66afdaa04958ee7b0bf7256554bf849a4af22b13c50b788d69915a21213e1011fd7b9018eae788defd836c5a9ea9cda0c0cd06b67d5972254fe151b90eaf2ab38fe4c64486a304e0cb57288750683d43a362c92da4ddc2b5520565c804e66369ee16e230e7c5d26cdae726f7cf32ae92dd5f89fc388f24a214c2611d688c77d5be6bacb62108ac94fdd724c49e627e5c3d033dbbb014fc1881529777c9d6d211f3fe25343e971d82280431e5da5e1d654a7f0d61d2eebb1f236a126888001a439bd6bc2df75e3c5bfa7725d7ff8d606c2ed13f82311f7325344e17e10638fa5be89e58461032c746f4472b65bb2c832e3b065ecf3c5da5e0c7e7c94f83e31ab0fc1142d0e3e199787ff72d1632874893389756d1b3409245e581f087ae331352848f5463b25dcf9098d7291a13607e48d24492e63753195802a1b30bb62ffe612c1db357da0326e36106ed8f2ef7265352eae3a9cdf2959fead2f0c228cb830e35cc165c74bbfda9086334ce4bf1c0ad95105f723812bfc8a407b115c5fe94c09d310b71aeb9893141102110133dfcba00629b0bd6c99210b0d0099ac7b55245f6c4bd8332edc078c8509edd543dc319c4a09128fc8c3f6776ed0094f47a15426a37814738f216360d172d74544475390ee19e65b915cede880857b77d74268791125a71f5cf5ce3ed3593be773effc74580c5eb1e0eedf057d34b31153c77f65e5779a5911deab98142e391c426bcb703d947a717abb9f20465fc03ee52cb2b0846da325135b3a0de91c4149cb52c92c18335718064e8e3907328a8466c79669ba792bc2fcec83611472c7f4b95c63855144e87a2517956c187040a3aa4bb85dcce0d8eebd34c5872c3a6db1739db0b676cc3d8770c941213680f15894d1ee8b67b466f0970648443572b70753c3d0dc27e5b19403b9e581edec653c2afdb1c9de97f8d535358634d07265cdac08c0eade4c4df0c3cf87b295835b6115b1a0ea151bca9610e16cbffc7243760d5d70614b217771cbf30a4088eafeb2e5925022e119409bece81938477296ac2079d3b53378ae5d87f54423e01974573fe79c5a8d715163a2f4efef80135ff971b6eaf4ab0cf0b28fa8a13eb67f94fa61e86f887ef0e14ba12ebe06a81d15a04b4bd49a444cddfebf259eb8e00401a5bb2f10e3aa66127b2dd21055cf721a1881fcf8d19841805e444827a5d7653d05387607db8a02a9ffc834af8b9d29fd6a2a4cc58dead32671cc3f07b7ced7778ee323531636b735db547a5c7bfd572f194e9c0cc302602f7fa4e68fab797dfe6bf73585aae6ec60a8640744025172db33e36db18b10457a34a8456c366a5e266eb0518a458f13dbb10ca766f2f70bc0578c2bf0b23e79621864125b5e263df0bd8e9b8568a410c849dec9a3a18c3015e847721c8519f50fd330cdbe5f6c6974f8ae3169bbba29ae82e92160c0882e5a149bdc4dfc7dc06052daa61fb560d0e7e59acfbf46713550ab5187b8758915462f1886a0a713782c111d910106dad3699e3ea9efb81bda02bdc0b1126c95726c3e1bc84b8e8228ed3caa743b385236c89faf157b357900b34f3f31e60c903f32fd7a786e079f1b32088b9a77fad839c2987e322ef214345853aaf4fd2daa4fb356f1a6719f0fee29e39f2bea3eb2b40b2734a3031c3a1d25b8c06fec7a81729926963a79aede843da45a1b5afa8e2128b32ad4b36f53b960a4629359f06d72cfafc61a3ac23c2c942195180f068f3d19bf74de17050f31db0765c8c5cbad7210863ead8fd5f372308e5cf9f5d78543a2c1282a5e9440a09097cbf011becc321a8ca9fc760e278ee73fe1dea10ad3b68548c9979f7676862419bf080ca4bb726a1e458cf3660ab4aeeb7fa774af2f4a2dc06cb41b6ba228f5f59134c1cdcc6547d433d2b8419348356471667cfe5f835e960df124f5f524f8d3f2d38e681d33e5536dcda4cae9a2d3efe9ffd3759a1ff2d7b7329097d004afb71e211c4910720b87a975fa218ff11b0ceeefbd19339c26fa8df9cb5c2b36856ed6e044d42b728d08c506406e4e75cd96e9b421640c49a87420701f30965e65a34b529c0188729567553b6cb64f82c1261d868c05bfca1d1ec79428c1d41d28d3715dbc80ae34152d79a88107ce75bc3f3d090ee611e07d58d69eaf0ae60a10b5c4aba5e7ea7219354f16109881aa611ea85ab686be713ac5c5b9f4b7ef14f0abcc3b4b618b5fa9565b67212d59705a97fe29ceacd3f0155ed77208ee35517add138cc867bc72a0fedf7af70b583a139fe54afb6360beb0e03f9374c7945c554b7c710dec477210de8d1fb9939b053345c93a397330d69d9a686ea0f17294628e0292dd137865efe9bead2a81a1d79ccf6ea9ee25ca33292019bb5c744543274c10941241ef4c4e53c987894695b7644332f6dc1db9b02965a8ebfafca7fbff5a061ed30e246a55cc2c454f3d942b7e3687e416b756716f5549f178b8aecb9931e8eddbdbc072a70a15165fcd8eb84d91d5a4b1869553a35c3991e3646600b19e490ad3f07b722b7a2f0fd28abfce22606e03fcbd29a33acab5c7f91be2d2bbbb7211afe51b1e25a576cf3619479b7b0c72a021ba4223d6732ef5cb583818b917b2927d3914722257459d5ef21215bb0a0d4977ffd961895c1d06a869c81cfb469c1ef5f6435c64162daa3ab5fc709485bdf865c6526cb52c98a0a3d0a61f1f52ac36dda32f185367a4da2925bd61f0d96c4a232f5201d66745eb2dc0fa8ae5c2bfae0f54c04eba6ec9620dfe539a5238ff8e896ccdf1f1b564f21f4f94685903f8b1d8375b2cdf083c013f608446df89710ee513de0422af2fb4a5fe0c3ec1a9871afd438421ccf22a5c2aebdb0896e1b0e5507406caaf97fc98e74a301b845d7ef055c0e76e070cb82f320ae51acdf45f52218a8f5f98d33acf6801b1c5ca289411259a923f24019cec90da177a0e2e03f63c5c5f03b46fb0e6dfef374b93717b84f3e8e672d0e629ba3779717601f639c9fc0802724153adb3c0adb4a854529b8d1a74ba23a89ed778e771b3cbd30d91429b6dfe5135b1d13b505fd6c51e8b8290a083fe726432cd199efe2c20bc60a1ad0318d2222cbe29b748394cf458b750c616b74010ff5181e92c22bc2e0576cc96ab112e93b620f9ab4ef9cc4ac17b502c8a454872f5913237c19bcf7586729f33989e6c73a0f0e3247e780d28cbb49cd5d856bf7203828fd8e813e181d0346272bd0b0fd161b3dbd2d888fbbb901688ed8d57147204f0e83ab886a69f4a34386c617f31c0fe88bfa3c46ca4e0483be93ba5a2b839807ec0883e0d8ccfdb8677c49efab5a5a40cb0a6000272077ace4512c5cec8145327c0e338ed2713e710ab55464ca863ba100d92f67e3748a74637737959a91d660927f49b6ef81b3d577e51aed1b504d1b97a937d4743284405b01d9dbd8a7029154e5b7160d5df32e3f2f803906832fe379b31ef5a597296b042f25c7a4172b0909fc4e95a13c041b9ffebf201ead04a4c8a92fe67b14c266b1ff22edb5372a50523af84b0411eff8d12213218b77efd0a843c7d06ef1789bc823fcccb307251539ed5333d83f7d59d32b9629c799f63547e3454e836066bb57fd240b41008357a2319f2e96198994c4382df735f58f15cd4c2ab20a2a440e50533734251709bc8214525b62e2499387bd59db53d13304ca4712fd79473eeb68b981b3efa721eb6208273435475647ad4de2523891325aa2738b444897695cb33094f2a9b0f8dbc38e1ed4fc9149f4736103daf997b4130fa4e8033ca3507455776e66d2e723a977523fb6b7ba61f1d23a93f68cbfed928428001e3523cd10069c79c6a1a50a5947fb5a3e29ca80e2a0b907786f2e5fd25d1c0df9a16c9ed1d9f3df6a78872b84b25cf7a36d64ef0932df0f6afb371b8206cb7a78defcc7922da20547688723f113b8079659cfd462ce7283c28af72842d4dd3e228fc8f23b73a4c8fa71b7209dab16ecb122b6c8765a6f8a79ed571789b5895ac7f8fb82aa41737ba752f72d2bf876befc2d3314bdf52370fe86e563d65cb3c4e0a37f36b09c2c745d57b386a7c196350383d2546e3f206440660a7735dfb99a84327afb6434814df989f40f81c944e88064fab8047cbe3c8f10f2bdc1a7ce174269be5574ca99749c6a0723a902e1bf8cdffc741be7763e8c6570db803d47363453449c21edf59e2db863b2b76112cf7565648530816b91a16ac41a2e1e3c1da466bfd0ad1cd09a4af6672bb46d2ad2d5b79296c57adaf32dfd996cbcaa4a5338f8a6d62a4acf2be28c055db97db3089f8958b02289c3b7441a2657998f31e998b6f00b29964fa8835ae72477b1663d466f96ae0cec6aad259bc79e07e04c656f8759b492963a46f933669aa8280b7d4ed82fa74dd9cf0bcf17a5936e02f3b71b8de36aeb05498bcde7669cdd7e5b863c1debe9b27e409b94d71e378c6f1116541bb740443ff023caf73720599cbb5adb76167f3684e874b1c7c300908835236a2966fdc9809c45d5284721960c623ab924b347396546e1ab342235e23ee7078597b13ff37be2117aff1721439b8b18ccce6b95ee40fe84a2ac33e190be05d121795847626d3978bcd6872be30990611db0dd4af7e105be2e1f8ad4c7dc465af6bf1ff7748306aec1d2b72e9bd6c9fd0daa32d212e77369c94b987b3ac1350c6b6bc7eecad125cb5f17072cbc62c6f840283f3c5226ef672fb3a68be106c80894b0869f0c6e872b9098f44592d83e13c9d7a38b3ff3b50d09d7f113e1dfbb3801d3433f027e29f9b67b470409d5f834b40f7cef8c6bc97963dbd3201276bdd554d3791053c4209afa57247d840b4bdc1e1da9ae6904c60f4ab548e19b183d6e3f32cc65cb30006e05ac172e7b18bcd25929407bc9c6848764df2d060dcf5768a7eb85bdc4c7fc63ef458727dc751b25bdeafb5fd6a281f913626ed80475dbbe3c984b781bd60cd7cd288726ea13193d151e63532e62796a79a0abf8f6c3a422791ff4e680698b09ac80143ced89470f6497e5e7e24c48d8ac10035faf1bc0a98ae2c1c1604963f57f02600720a05a223504966e091689f1fb2ee57eadeaf64c050e92e0b3250279802f3724d6b0b455cddfd4c8459a590114cd71ba6842b1b550bec64afbf4955e2f96e17285f2f5a0042d4547bbc53f0b59b4147de28c75a5f6b8eaccd4bfa5fe5942a56ff1fa539d2e0a964d6996f58b87f7ccb4d6c50966ceefc380951d4bf4fe51922ff331076bff20e3342eb5c2c2e425cf8ae7f32dff3d944facab0a51f19020972a71892cdbc1548dd85f346656da4b9241fdddacea78b89616e27a5d0b4eadb10c2a6963585cebfc6dc66e50de75c93d461b17f57e48d1fdad7855ae5b01dde729f82c04880cd0b84200fc84073aab208f241bc27f3f1e59745fb33d9ad1fc772660fb02eb857d3ce9a5419c6c9e66bc4b996d65a97b9bb711fd379d3da3c8972bf2ed10a5b679271aa585069348f3b8ca6d3700e54fb05b40999cb9572515b106f9da65f6297a96e499e2270cbfe00a5ac3ead2682e5d528431614be22f7545e73cf8358189e3a83e6a7090070746813939db9c9ca7f9bbf53c8162a334e052bfe8c4a54b614c7ba2ad672b413c726fab4c18f658ed42f448d279f9b5f526c7288894c10992a1b7281095b3b676e2aecf9fb7ae55727d8497b4c9aaa8b8e0772c1337f8ba9964ba6fa34a733d948ffb7cef77bcd32b563d18d56fb2d19b4d01d84a19f1e940d5953303c9dd08458cc5397248eba5ad68abeb657f4a2a9356972ae94fbe20d052688c1a3b12c29a2380947433849fc6d2a3768ca60fe22d54772bd1fd847ce9f53684f5f43af5482b11814b31e518c18a6bb77aa768cb04e567220780073d4c3374d459e4109be645181db2e53c6e3e29b712d0f261717c45e44ddf83282b31638c3e525288994d348b647ef65547b51f68ced55fcd0651d2f7220404a048e51542de0c2402cdb9664970f109b40b8a7be64ed8a42073a48c020ccfe44d84aafa84d669db1197d8180b525e73e207da6566efd8772030322c272f259e21067b0d21c5c9d0a58ea9f8276c16f6f78cbba03b431fb89af295e524375d35abec00c15165186a798586caa545d2c6f058319daa876dd51dde7f51f6b8a8fcc71bb8536531bcde322680751b96fb1ffc0b829194f407372d48998cc1987822175fbe5fb3ebe2eeb9f767c875105979a348cb6359eab62d26dd4aa74726f32a55a3a01dab5e6ed8cb9a6ccf3d416af8f9659c1481e6155b2206b852072d0d73c18490847f790c783e93a33b6e1b523e8cf91ee4ce1200df516cea52b1cd0a0c03ea405ea856c327d40cdb6fe01295c0fbb5aa8d2ed07f13d800f055672c47c715279dd441cf5cff193fe8219c873f527bf71a3a47dc4be25ca7cfd3933ca6c3a8674a9b0c55aedf840979f385493f383f869f21fa609ab5fd9c11d121bf46ba24ca6269136932f350a965733d5025e3d0e11cb483452f59bf85a616654110b8e348143938deafafae40b7d2122db600a13f1eb3b893322daefb3a8871f1a3b22bf600289b9790d2923d64aa279e5b81c1cfb119432a16d325713d0ed72c3483510cffb20d33b9cbf933a30926a25d5195cedd4802d8af4d8d6c820d47272b53004758638ce9a2d28dec58ff4f685fb1d14f03da53c0abe23627867b472b5b0b2e5051cab21538f2ed1c98f2332276ae89f5700b40d9bcbac765d6d3a7246b983a3495cabe59a10e415c9ddf9de34ac6322a7787478555fca865089d0721c9b0a55b4a6d08d21d32a2335ffb26da5bfdb9a41fb4c366abd2b51bbde0c6119e3d68e1032dc0a1b7419b17d020fdac26de25536f823a4e3c3c4f1d7b84b21093280ab4a208a954ed0e4be6add24a4d13ff80c8622d8c80c63cbb3df9cfc69f2d49da6136450d414a7407e8bf83a32e5f313ec14b498c7b8caa25ebf20754c394c4ebf5965561ab3c08b479333aa48350a601004787375d8a0fded0958a5726b67d97a878e6a23a98e5c207a28839709e5d356ad5436dba02d7e16934dc10d5d8adfde9804183b4dc318138ded9bff2e5d55ebedb898f258fdfc3f89bd2e7255e89dcf9a3606412a8077a014979049c32a107578a1b3fb0e51dd3cafb2cb7291bf3e3bec1fab034a24241445caacabf7864f11a0c48a9350481bc19ba2de63c1812cdbd461f34298f8944b0b6f227baa8c12b6060d21da69886b4832f58d7219a814943fa8224b46bc5c681bcea986e0a567ecaf74597c920e32f8cd74b772f05ad15b98476794aec1b1df449ac7cae08e3f7fe6e2b94cfa1092368e7fa3545952f476fe07108ceb8ef7adc61e3adc1c078661959232caee105969a00d7d720ca8299ee8e59ab2875ec6c471cfac0128f66d68178eb50fc971623b5f315b728f95f336a6d08f50e52791853ad499288ebbf236afe628fb4b704df9e14f887233c9ed7752e0d3b75880f04458d0bae3bd4c1a87982ad1f1d7f6c9adc025225b93a267c82da54a26d1cf5d23cab9b95c91f4603f2a106b654d9bda1d7400a62f1ad9cde7a2dd3846d485d1f095cd88cf027788d8a966955e0e688482e488395e4bb76f6152c04e6c564bee4269a2c06cb0e58a3089e4157fa6377c592a36086dc18d3a9295bd67299eac258c4ddc3898622e358c41b595476d1b40cbd8afbd72de96a4369f610e7fc779f1f5417871861e571427df81c1ee7e492b9348a9f13146cf62c674a01ff17d8a33f7490c6e87cd9a2c8805122d49e610d56bba75344f729deeb388a7f456f2e2722426685d6f1c8aa02c3c35150e11ced7762047e1721b00275045f474a3f3d891746b81fdff26a6f70aaf89cb8c56bbb75776c88f05a763af92f264b498343a3ce9c0c30cf6e67e9a8b83c001640298e5d6c67bfd240815e7db7a69397ac3941f060bd6cc827b0d425a3783e507edd9c48e08221e72d5f2e0e73de879300aed1a5762ef46a2c41846ff6610ac5a1ef2b6cfc9f3ab1478f4134736d5c230d973a5a8444747b1aa8ecdac7b57c57bdd5a8df199f16b432b0a694baef9740ba24188ad57e98c1af16137105d3d35dfae6dd29fc74f7172faf606e3e42f2fc640ef83280807994586c181ebd38ba77ecb2609999d07e372e5b057305f508852fa09c883f6ab31e6857e80e052eb6c90e58f7a796ff05324134121bd62a9ab51c6b1a045629388809d726497c5874938ace0baabfe09f8724ac4be036ab27c1b466d0bd21bbbbb76c673b2bdc8da54ac7f37ef91b1bca472d581a7e9b0b097d316e71a7e9884f6c6237f1f4f22289cd37943b8cf9fac022a224994c8bdbdb08a6474bbf791da5becbd2c2e8189796b72cebf5f1754d3bc72cff3b3569911309ffdc952ab8f94606e2cabdd5eb8b963c6d6dd4739df01b6721eda1bd226744e8f6e6dd7c001093b4921632f68f211f717914d38bdbd94525d016859f40908bd9ecbb3caecda7cd16463b516a30e403f661468f1b3f7883a54d9d76a924b52694fb256090cff210287ee6d5010d4513b0015fc96546ef72b71e399586931427242fb9dcebd98bec86eff91f3bb10627f623a4b56195e916e72741326e6a537304535b3a84ec1aa146dab141d9e02ababc8d7f55d1549009b45a8e301a62292c43a7b0c942da60a92303b0b80ea6366cc1cd965d3b84b125172f2fe1202283097244fd1f2d9f963a4fb5a3e5798a99233f59d4d6b6e2fd31172dd4c4de843621b2c7a5a5f3079231475f5f2427342fcade4a307789ff7fcf972c4bc19fa0950ec324357362d111a45d0e1aaf4d61ff66fb23cb01faabb450e725f02384b1b4f35906c780ef04f472a09edf5d2cd4f33f433f2b27c63ccfbf972d8d267a42afd6d1102de204bbd4c534da13286a60d87d248ccbbe8cc89d43c7208d080082c7f300b1cc8e480cd381464a0082878bb0d756db246fa446469a561d6cc45b1f94d8fdb4449fde7981af353c63a091f5702ddae937e5571fce3d5091281e4ebbfbf290ad2f9e47f768cfdd1f1450737bf868e8fe7da366a4ff00d727b9387a042e0dbefe4d4f9fac0599e83b68bd91b69d74df643e08d3d50b0816db365a92cc2d01a2fcd741c58419b34958e3bc06c9f750a634c2b12bca16fd072f8b43dda37af2506538fde2ddd4623a2bd1eb8b3bc7461dd831c7121bcc6bb20e321ec970e10f5cafa6689e4fc154c957478d969474fd177a4ac4a79cc206e3337e97f71bea4baf3bfef9d328c2541f0e296c9269b042a9e097c090991cd3d6e89adf25e5563e2aa2f86c04d649b6cb5d5d59fe617487063ed5fddc02774974ad7625770a5ccb852c2fad5ff18bd0d8d7c4125b3cc203f23656dcf0b93e1415cbad16ffb07bca6fd6fffd37d7072a0372d60642227cec7403c55a95e780a2172788042b4e0583bfb23483ef2481ed7ddc68f72f12c6e45357916289355b1d1725be56baea821e2488df708b41e3056b4a490a9e37e5772c0298616ab843b0f72ca3c216a4e6031300b9c39ddd8ffdfddab789ff561d0b09e3366094827599472a55bc87dfc56fdf1d466b19178183a5da46bc427e3facd0f656f2512f7b7a7724baad5c94ccfc7958b891a757500a3d9dee8b2ab42825efcbd3fb5ee07e8965086300e32bd8f1ae5649a5f965855688374ef91ea48a8b217c9614c2ffb433b38e62cd836a29b3f67117cb91cbb76d74bd1701d66544fc35fc03e228d61fce164bab261dbb0975840f60b3a536d97ab61e7abd7e05b782d72b69b7029d00fd322bb904ac5896732f550c5944e41b62b00d2cb461d8504579aa03abd4cabce8172fdcc2ad9471ae0db1e605af534593e439cb06e575bb67c2347bc0d41d4574245458e5713d85f56ad5c4fea928f317b6fc8502281bfc47bd67d92abb5a2246e46cae77527b5f57c17c17f2124e3f4f68a329c681cfc04ad9adaacdf80aeac0a7282c3ea65cdb11836663dc479a0835cb25df4d2f485f74a15a0b8b50abcf8437239da87fa9f3d80effd54d1f91e13ec4e98a2e906129be0ffd875d22a2784ca72872df7ce8d2107e06a4a4f818b587b6cf915042e2c06f2e96bf9c07161d3297281aa1a3eb221b9694cecdb0006329f02b1a12bd7fdb440ed567cf342d0b1766e77ea8e998685e16e886c272ec0bf8a29975affdfd91189b33cc2e919dce9c4539b7b957c6fc945f7175eff1571a35a6ec0c1aa7cbffbc93ad79d35c84e499d2cdc11e2ad7788f788504f38380a97e1fbae7ca03ab74b90c4db8d332f15e8b739936e3ce5b79046ca99baf57c06a1b0daa2ba814c3cc20540174eb042aed828720063f3e26ac16cc6b20c3eccaf1c138442a000c13b9d28bd4b0a55381aaba6725b83aa26f2681bf9f87656205a35c157febccea917594057db8b19b8698dbb6334e0496eb9a199fc54afcd263222e8b2a6ca0d6fe99e4e7a2454f399ff7e9b724f9eff5763a986c68993bc2bdb325c42f7d9ae91b231b30be4c10f74c9484372d3a66d8041301dfc975bc78721e64f285aafcc34a2d9af0fa2ca8e3412df4c728764f0052eea2f18edae18fb3635236ce7ab43a43f05769d8ddf3cb938779b723489dc3671dd9fefa78f9cae6ea6b903878bf3d62fde35cce1b5973134bb9972934e5fbe8a6e3a6d5f9b58972f5c4919f8566fc5701a8558aa080948dc3ebe727374c8b067587585368d03d9c87c6816bce83072409fff647c35f5591c43515e6650c168b7337cb2060cdd98eaa5c4ef482c0e8060ea17fb7a00edb90b6c1b7298c1f8c622b4408a5ef877e1c9be63fb649c1eb0bb39b4e1cb6d915d5b2f0f72c2ecedb96025a50415385dd4deb16755c5ff2b4bc43619d59e5bcf7cc3a86572175149cde0577b87b05098e9a6e11a3e29605656ae68e3dcacada2fcc38c4c72be0c8ca41c6fd6b86abb0f2ec68827f3e94df70cea4d5d9abbcdce0d3cb8f97265e94bc3033299da9d06e6a8958e080d79f4146670998c11ee60a1c5fd408272c8bbf49566802472da6c38af6bbfe33b2a27e8a36b3e5929bdffffd3bb1f53722cc9c9e02d0d59d49a8f2daab7d1b508a5daa125d4df161bf8d2c9db6fcf3e727011689cd3b10c76051904befebba29bf446d0f91ff389f78fa75ff3b2b40f728c8a4d3fbb6a82481277abae184e790f60fb7549272f978a5a91e7d7efeab4728932849fe6274ec40f4bc70a3b58242c87539d03e0a2eb6cb193b8466d1216729e5abbee67acf60012e2319ccb08c28aa874bfdd36a9030e7991a898d5c35372d55628ffa5945229243983f0564864cf808b5ba71fe3429326b85557f034cd28ac2e89d14716cab03d3763fcc6e1617a2820d9060c1a3ddef04cc5c1469dc17203fbff782d148f96e4c127e1dc5190804e63e2fb8bfbc389249068a219a9ba107a5772ad13bc47b278adbd9a5fc054e260eb10a99178113cc8d38643ccbf336d3372287025a8f2d3b8417d03dff02df0d0ef85a17e7eb0b213857f98055b163a7eb3ca305d095616c65d38f6b0dcf07d04acf14c4ecbd3c4d85ebfc6e4a7e94aca1402ce94ab9d5a0a07d03f1637c3a06e6f6d640dff94a7119212d0f18587724bbfba33179eb8f7dbf0e97174c475f180cf0f70fc46f4c58def58b9eb6d8233de9dc5a0b12eb0ca8ce15e195e81c4ddb307623ffe1db7f8792ff96faf27b572d5db72ab0f95c3fe8a7c9a4c13b4e7e628edfdc86986fe1d4168052db55e4e28c9c17d9c3945e27d21ec7667a506a3edbf76c9e492feba77de047a23981f187277ecfa5e72ec0b6a3e5b7e35e417ff6c0bda030ccbd54d3211d6d7e606cd1c45a7b835524ef20a5a2c3e7b5c7315194ee8263530006fc8f930adffc721afb772350418867d36258d4cb5317c3a179478639e58eff7f72569cf2739a29950dc4e9a32d683888a8ccdd979e0e213dcc5b1ba11919bf9f932ca89b27dcf00ef1172dd6da1c7c06ccccb7a3e4e24427e3f42cbaa0db619e198b7e8cdc96ccc02ff5fabc5a3a28a8685f75018bb6526019bf6b42fc12b7d4022725f2da9c015c8a172d9297a45e0a09365646a31a39d8643756012299f92c2242c4b2865d86cdc1d72ef746aa5a2ea72a0dbe2473a18c491d005f45d6a26acceca805cc55c2486cb72f345410713c52a4e83ff75f58fc0b4b6e4d4a506a37b9ef0c3be4f545e0b8d72b85f95e0933ed0e4ea6951330335f2e0ce4d7a3eb556dae537e80f9e9c504d72db2731f157c4f2583f6dd832abbf8ce6d71f8f6707d690a02fbc5caf3e909e72fe4de1405c9eed20bc7e5cb531262211b1c567d9cf845a880203e68538f2537267372e6e6f9efd06cf5d735eeda2aad06bfac9af6a34f9fe4c2ed045609f36724687eec3b9a34039b1c8e548720496d24523bb3ab2c4f4631f87d4a239e186720ee41dbe505b47810f197c69fdbbf2a4769a5ceeb49879bdc5798c15e7db114f51d5ec42837b8c2694f54b6bc4935c8878c4b2bbb0f0bfa78a660692c3f6a16b59c344b636d6f863ba80c32d7610bc744c53b28a08cf92e002ca77b95ad6cd721b95193c0728191b1d2dc5777124254ab5654f4b9cdee85b091d4e2212074b2480fc50ffe5a755eb03f11c052eda2f46eee6fe47f9fc1d0299a6d69979563c42764cdf16bcaa286745fe51bb9c67a72d6d8d4ab880ee42dbd3749039e049ce2e5800a0b9c7452c8f36b8d21c109735fb4d303251deefab660eea1ec2afa43172859c04e8a53e2ca82275ea6875b9e48dbd96ee2e28ae02150ad04a6b15fc072f2b7a7885c0e318af75fb1b80c8235ee3f212a7103c4b2c8c8b479328ac3d4f726fe9df1ec5468b0897264d957a4dd7dcb8e7aaba89e1487835652e4c66cde872a6ff3c958574bb52d219ea0ef905c63d267f656f214174d1a25a0613d0a6b6726f693201a34fcba18fc33d04b99966290991f598250a0aa85e31b04427a7ed724c8bb13b125677d525b4346ac114f0ab17eb58c6b65adc1d9f414470256f3308eba25249efd8106e75f4ddd23c91b33beafaca29118f397df528008e55201e72f9948bc0b09210ea1f49cb5d573c725d24f799ffbef12531b2c37851c6eef772f8175e20e8bc10f0497b616f2e040a82a18ca8376358522144d074c0b1f4ad7201bf4aece54fc56dc6bc6e137a86d31b686254871160cb16034d666c3b40bf399b1c17abe0a185ff618297a4117dcd5354f4ceef1fd8a81b40240a58f6fb0872fb82cb60f5f41eaf6ff50e463b1746c0a0fb6a58bae8ce5720f70c0271bd6a2cb76162cd257ca5879a48d4c341babb056c20cc61f6ccfdcd52c6b94e3d8e091442a75234b3a5ca0d96517dd62e8182a3fd15e0c06b8dab1c228a7caf8f1e964d7972b4cdbd5678b3a0f32626fe075db08ff53a7778e8a0ce8d817592e6324a4b2abb2545a715a854c83c19a3e874f405c589e5a65409fd7888d66bac1d7b016d2cb6e70bec2db5a5b1b880121dffff2ce137eb9d04044b0ef97446a689cf29727ac6c5585ddf07e1400ee07dfc24e4ed948711904cc823d3ca723b2ff710d372b8257adaed098175b0ff636579019431bbaa1998860ce565a214245de2668c68e97c596cb6e40b26b3dfd9a9c90861718775a4df76938fb8307ddc2689809b726d6834285032049b7064b6e17b79f75185e383f957142de629288dc27653af7233b54790c250f177325477ef33344fd4d9819d945dd96dc0c8fa6f90745aaf72fec2bd7d7aecad2c97e90688ef03dafb5552f9b8344a220fa7afa79aa21fa1721808533c92bb0b033ba64b4c37a286ec708a09a5ef7773651b59a7db90e2b172a92e2efa26b17e38a51a629b28f6965b600a8c342ba85452d82830c6a7e1b472a88266fe304c919b83c59ff7c9f261aa0804083b76ab40063100d8541defe072e40ef7de19eea25ee8bb9d4b8fb23057575aed21f7af3699011afa608cffe5720ac69839b3bb1f291be2236da070ac8ca7dda9eff59985592afd0a3c86767f43c28d8301ebedbc5256cd164687266a11645ddb44a8391dd035cb96260289fa725715e557661bfeb940b3ea1ef87ae0d57cfc6769364866a3837d02757ed762699a086e0e6ae2551c248954162353c3ffa09612cb94e6451af290ded90d85906f348b953a4011dc23b314f1897ca23d8a4c5b30b86a6944590f615421f2014d3d1f5a4cc27b4e3b1650feb61e78a9c29dcce082667c21806c8a153d3e5d86b0722203e24abd6ce917b50cb1930fec7314e566007f465f9362d071f5c25ec8843eac497e3698725e615b2e5537393db1d457580fe655818e14ad98c6b66f818a725f6112c43866da5cba00c3ea0c69c730b70577e33a2e06a394c68a0c18aab85f30c92f8f1ca08eb8a757c6800178cad5b04662189a0bbc4da1a94820c58f2172401e79dcf0ba844aa1d93b1c028395b6c3033d47f7deb227758b98230b0e8c7017b2def0c179426ae66e67d7879376f41d6184e934efdc2d4bd3895af99c3b69761a0af26c19ea2af5681dc9a294c35154b697ab790a2b2b75109ca5c02aed7251a1e85c5b2ea120da44fc69664a24d2a8ae17d36ac5d28cd461bbd8c82cd872b74c21b22fe15fedd4f7b558449843bbc59468d51d0080c77226b6658cc28e664525188dcb66cc5745668614a6b192a76110c0f39e3b0e4f20d255124ec1f1720966be8dc098453ee4526df86e34bf06d37a9d521bfeac2b0b111ae632cb525b766594d788abe7b6c9a428726a8d05767fdfe48ca9af371d9c4a7a89c881f664c5b77ecd1eb301c6e3207fcbb8d4266c5e4dc478f0fe9af866b5da63a6c33b72fa01522bddf121715302925eace78d8f848866c73dc10cd3455a2b5f848bf2128168e77335c4084b574cf5d1ad961dcf929731546d178023723b7c66946d1d668c763c9ea869e448d8d559271b05e4bce0566114ff38818056c7df46d553061627e2f33f82cd6d051eaf7814dc47734710dbbf830f20a96c9d0e7c6d8cfbb8729972a4628166312c6812cb3ae1ae92972a5b1f4704cdae49caab61a61f62276db9d9a9b3dbe03ce58597bfcd8bbf60fbd1d375b314528a831619067204123572ff94912615ba4370ff8dcdefa71b668b8dbc46142874ec67c9f97b5235fa591b6a6be492d5e516722b1c510f6377dd615fa41ba36c8280a6a7339ca7ab1f9072e7109f6ea8792a59f18a4ff990c7a0b88e28daa5b52b5cb6bae7a84b07d24f2d2b6f45e9807acbd2c906aeac3e91ebc180cd1afb183234648b14803a307f280fde79cc8aefefdc891e2de5e5bff3ea66d81f1013d5468c1a6e27ab65967f16726e7d841e6346f3abc340aad1ccc813a1e03d27c34c49b019fca1e72f26e8fb6ca832a2a8167afc35eb7f7c89664294c86b525f166842c422da39f0fb0e78e6728cd97ce0a356368a73c8f105cc6a4a330c94761e8d3d031dd78b18e036f1fa721fafe5ffc1531012145c43243fb9f1a2be74cbbbfef740dfca116c52b4e09b72afb0da9f315c5bb5c63a325090c50712fd89126d8a29ed445362c5a31fa5f92d4473e396e1f6c5dc6e74526ebda9a6c1c460d71d35a8b24236f595b8cacf024479d0ba2bc684e2e67be202bf2e884044ddd7213fe8edb78c15d556d0969fde726a8545dbc06131945ce663c4884298607f0d971d8530052c4c24858fa742be13cc8bf457c84435ce3ff0c119b0ad8eb990a6dcff4844ac62c368a5f015cf9372deefb79a55f121a7290ad003ac1eb65a39267f5b91721b396522f20d26308f09b592f5c3fc9e92878835598de20e7f90d9b3cb7d7079e9c171b1014804a5c571161725ba64c7f9ca1c77aedb170f42a07e859c72f2d3d2618c2810c0eda2426ed2df627bb83a5b4ee6ff8fd4c7865713901db1536679af3f97a2b4bd28460972c46d8594398de416c55badc322bb16eb4adc4573b3e3fcb0f5b645d9fe8638727785900c6ae76fba3d4d9b8ff7acf59fbff19e4e0d319a0e3095272fb444167259544b78d83dc908bec8e405bcc81cdf7c48aaae81445f4fa13adc9454850672e0801015e9cb149e96b12c73b6604d890698d19af4dc79804deaf542d5d1fb72288f800f37ab8386ac14b91b138ebcdb304fe6ba46691885bbcd1b53b9dbef728d9dd277948e8d0397cbddb8931c8da4a0f3a9b498d767d2964b10df07ff9b115afbb75722ba498f142cc3d97c08bf4d44ce141555b4d74ce1c69b96bbee3c72d468328456c1142896d71e3179126516d7a3fadacf7bab820230c477c19221726f76d8b7f966bba483f0a05a4b6e962eada003fb195dae8dfefdecfebc797d72fe6c89067f16e38f82a2f06edf79754bed3855916cf2239c8280f9b4fb3d6b720a2d930d959ac474cb5e0eaf220913084fc3333d39eeabe9ef1dfaf55de4a63ccbb6044a5c2c4efab043646b6cb318173401e154e2a6cbb5c77012b48e876f239e479d0a8e13080890c94a55b408512073dc36b30b64d6f7607f30645ce2c60b486ab7f0da301212fbcdbaa5a9e2f48f2457dbf63121c750fe4607fb2ea00a727cf11d8269d28caf69875714495a9855b45a353476f25564ce68cda16ee5240cdf2fb1bacbb42514cbdedf6c1e488a377f5db9a36049505b848ed29015ef72725fbdf50f1b4a728f061286c31af7da2a5e5785a7eceb4cceee85ae9250a57a123ce21e5f48caa276854a109487206be0bccb2881bf85bbcdcc35c1ade664fc7233447e0ba19942f8f13bfea5c50d286126e14985e20d6c2e0080baf2c5461f723fb0c3ce2620a5df9371d38ef7a93011b89e54b34c7e9e4c9d1473d2e0408a67dcd833e24434c3a7cce2c2165c18c6f97a65630d54002dbead4b2ea3c03fcd72624f4893f56a5874ea35e33622173824169876a44c8982216ad741d9667d577213e106c93c470a2e0e646444f8e1e5065f3f09d4cfc8d0d3b4f29c1912a5f53baaf6374f9fddc2b64d14728a97ffe12969cf5a518f20beb1a51cf900ba72b17290c5614853965de2c5bddf66beb5f853073dd63f5c3d69d3ae6aa56e86583672dde498bcea88ad2a0fdc488f9cbb39159275d88cd5d67eeeece5003325d9cb72939f2ef3fd3daa155b1154e562f135f01c03a20632216ea00c19d5b9d8b8e3724c6690341314806b4375332077454166df62dbb3c83e5238e05e9f7e99d0ce729c21073e1479d297bd1fec719c05ea508b743e35171913f7842f6424ebd0e5415e73c89c0a6fe03d3e990defbca80e27af968e91a06b3f2118c8fcdcad02177237c378b3d59f9a5d4e84760a88a463f1646da6c9ebef8d0f319468b196f6b57201366ea338b19ff749040abbcb07dfb862bbef68b0071a2491d604817655d4724a4cd5cb4f5ed54ee2de4fa27168cb123d6d5c65f1ccf0d5ad8eaade31ffb6448c5ea5761a79cc27417c65ff2992910cc1d20dc26e591b4e2b6baba69d877a3d7eb3c1d1f6781cc94b8655a6199c7841df1f023f98c2d004b52bea4bec14e772f23493d1856e29d7fdb62e8b096119cf7039da3bc602b061f61ae5fe3d5d2b4ce472f216e3c156d5581c815d2d4e3289fca3be6f5bd33ed69ca12350b324bb72c7257cf8b251e59da592f02d1735d79ebb9b66181f59fed883b87716a93f032d8175a1714409a42c7c73a049cb1187e988a9cca9eca50f7f078de2ebe803e0544b0f0db8fbcecc09d5e172e84d7ec0558317932ae3dbbf0a75cac80b59707f72913227eebb7bb7d7a4cc62101b87dc26b974dc58001b1977d25c3bd455af23721d1c52643265eb079f7a54345258b820f3bd03708fe34c889d1ebd6ad9c5754ba0bc29756d323b7135cb96ed42c7baff94c280792c7d61724bd7725c256e4c7276f1352fea25642f93407f50ac2d214ebba0e9b5514f490911ae8f077ea2806d94d0c8a95cbc8d0f9ecdae50ca754479a9557530a60b586d9c357f611ab07c72366aa46388fe85127d972dc591101c8fd22054f801a47007609e6331f3f304726cf9d23a2f73770a062f1404f3482f80332e272f6f7da53276c8941e33558f20407e18998b75bd57fbeed97b8d20333339927c0cf03b30b4553c39ec16d5987235dd358b8b59aa8ed3b06af603275b2bfbf189aa5ed71dbe4ef0e6b33ee9627268f95fc15289ca4482180d298e5c2a3c58763af0bb97735a763ebe96350177367de466f916af92c8987859aa0d273f62d349ae6f2f90b1b5e1949123e9035e576503f92a85e1517a770504854528b45bfcf8183372d0293247301c7f204c8b720222c70902a886e9031919816b5b1acb6ac8e312b004c88523425993d2cdbf72e674dc2c60c10e05b6bac0f38f816803935dc4f4aa7fc8088b867bdb93b0a2417073d7e9fb7240c9e2a400a578274bdbc06a4e969d3efe90d6ef897fc2c1cf5e55776ed34a3a177b4674bec03f14b47efded2830d96b472b34ece4b931ac2b72a823b142584e3d796994dbf760e0fd0af855d44a97590c5cd415e16cc175e572c2c5a04b40a7d8265bebc2d1e2f8ea798b20ec3d4fdd1121e4571a20dcb3b35e9b9d39f7d68465a4a1ff0b843f3a17c984fddf0ab801a123d9185a6ec2b70a72b8f053509bedb7e2c27ec4d16c5787fe0e023e89ea8ca76d6e4bd31bb2c8b604c0f36da0df3f79fa7d14c18a8e24d9fd03b6188125a0bc185a73ee757461d1619613461354d5be44ce02084b63649d4f97ce1da9b7d564189efea781e9be1d728d2a78c3745b4806eb65ad22f5ab77bd5810646bfc4a9fa86dc07f82b2965772e40010ff9d7c75e560b8572c6355721a69bddb783f5544c8565b09c457663372da9081a46d0e92c80780cbcf1a0b00149559b8886b690917f5e007df1046422aa69d085afd31c8446b29a824d26746bc28950e9627bf2d14adf10ee147e146721eb9bb06e184202f55cf462e5c8c95b7a39aee140b90024d0302b6da51836172ff837fcadb1d2fecea6792cef735f8d8e60d7bde9aa5d3040fe14446a9157472c4a3ed6f89e4071369ed066ef62fb2cce1f7886aa1cf7d62d5c7999a717c8a3c50950b331d84020b16cd34a6230840b380a2c0b831160f241d0550c5755a61729a87bdfc502bc06b62d0dd474001b1016663437cc022797f391e4405407c1365ea0e71a2c250391c37d53e52493bf334f3ef618805614009f9591cc1ec2ce7725dd1918f455114a6a6a6c52bcdebcc71ffaa42817ad3224cecd8a586b9bd1901cff44a8a0240beae5f3ef763cd45ef00f3b108dd032d87cf67a6075a7ea01a72564facd2d3dc0c2f2d780d209e488c046ff6c377947266300ae128c198e8a3725e9baa095fae80e4775915f52d763c429878985d6b477b74212682657e06fa4f1003ec3d327f37a8a9a56a7388256a269996998ff91f43f4ef6846871cdc6c721c50d5e07a87584c4fe97464f002f917d7c2780c92b8779aed6b3f700567d566a74738f7ec67b912c0dfa4cb5b4607f0442655a94bea71865077e683850d9172568b7766ea551f71ac31f90f73a652fa92cccc0815406fe79b158477d4ffad26f077422ede9dce7c131e4e57581913829364b9aca149e85a07ccf311483a0e39c4e27fdbacf591e616d7877075021ab781fcd913591075b5b998de10397a0472ca591ec8ac7ab958a239230279bdc3e0020d49fcb411370123a8cdbd36ed02309ad9ba95ec8cdef4958647df4acf75facb5e0b278d5c207f3cf8dd41d5c4fc72704b4c3732ef684a27b561a7146ab15dfeb288af24f0891c3dd4c8f54f363f3760ac8d08bb993fc55f0a7d94509ea5939834d4aedb7b147b9d1532c4e7ce8f0e5e65c1b10e55dd4124eeaac8834eac5d49d67fb6ef01d9e1531e9da677f8bc7217c2de6612bc5a76da50a2eef251c66cea2c9c20eb4ab5ef3c0bdc3b969b3d11c5e952ae6f93bf9feb2459bfcaf1315d280f9983cace615580923697eac73b7279535026fe5dbe31f448bcecbf69e7a2690e7e2aa17fd93259e1bd6933f1db72b458f78b6826a91576ddf29a12e098cb4d9383b6711b0f366d5c6ca33248eb723c90e702729f867e36cb0157370eb579d97e165bfcdd915569e965e5dbe91a50cf8401a14682ce5401235f9da1eb0374374cbe43f6f06e0295b3ff81b727e536b5b45e127a5c1501fe3aaa9e78dde4dee36c7cf6f13a32c425641ed30eaab41320b3266595168f2274b89e0997177256bcd6d9becfe5f76f69281b302f833a72c766482d573cd653f85b0394b2d46d42051567d3a7b4c03bec38bac3f62c311311eddcb200812132c1755f1ef2d6039202731835a4c82361062934fe66eabe7270fdf0b0b6474c35da82323ad553cffe598a3800ed75582ad6dac76c067e817245438ae7850ce17467ca37c5bca381ea13579d3527160978a6c2360429c9d35871f4c1a90c5ddb7f80d696bf33e2794987e2d1a29fb87e0602fa3809df9e46721e106488105eb2024aac95015af21e68a0369fb4b8cb73f992796ad0a12fe817ceee103463a1186678dcd338f69b21df611b9ec4d9df5092ae3c4d5ba1bf6e72279398909fb594da42470464d355640543539a4e0299ddcdef49b99a97f37a72955634bf21a299131cfc4d0a715ec1045ee43634376d12a50c7ff6e84509877207a7e3e6ec1ed79c97ebed1cc9fb41e0dcb35ac3348758a0cc4b34d28b9e315bc8db515886b7cdc911b15d92643d4bbdba2e56bcff7b80e8fbcd48836b486f724950313611df4e33de2f1c6fe5ce4a87352cc3eabff298efb76b05b2d9168a722cd57448f26f7d5fd33490b88aaecbd172819da7473b20dbe6da4d5ed9883272c00da7fd02b189e0e3991e5db2045c3981d3abcb57656ed1df57bc4a8a0ce1705fe9219103edda7b122b5e531dbcbe3ed8b4c7d2982a22ce0138f05f4fe45358590c4050badaf497da7296741b70ddda0af50032959b036e1f6bb2ddee4c6b70e86edaf23ac0195d0bd0a2815954239fd76fe691ef001562993e835eb662270330aa71a18e3936f7213278606eafb14a1625d784ec4cfa56d5246913fab87672152bc343edd73725b57e21772bcffb7ed282a40bffd4afbe9d96cbab45b0da53c323f9de605b30295d6f0e703d544c653d17c888c4b4ca7f7418c7cb392aef720ce870ecf80640e5b185f48f325a776f31e58389eeaf72f79d1d303212419572c1e4fcf6541ccfb948173adb6a69c2b1b369e043be2e2562c6d9a08a350c0472b1742e2882f68a32694dbc1df77dcbb49b5d20881e4640cad233ea0346c6183ae6ae9f0ea406d4ad20c928f52c011afbb83167e84408eee35536617c5f8aa93b3fae10396a2089bc8e1d72bc9e5df9f1586ed60c071ed8663a61d5cb87591f72deb6c2e935e16142c56c88d5e69d2436977df29047556bce976ce75e22505362d395eac3fb36e51da1786a5d987dc30396d2436a3b09b6faec04129b00825b72a20e9fbf5f24e26c7eb928b56b16dfb82bd4d6c3aab7c5bf3b74b44082476572c73ec20558c4af50dd8e023278957de4e846ddbbf0ae841e0917972dbdcb65054928e42a508ad1a259e27a1eedde11ede957770b1b65a8ad6c6a11d205c7977284258131a8b806055418923d53471428fe878c71a04e30b25edf8e21038298720cd40a1bda2aeab2fda3363163c405f536bc4abd3d70dda69b3f051455859572e91722865e3dea6375a28397e0dee332265fded63ef0434e5b1e828863579b720f96691d3c919cb22279b33032820e053077f7334b26e3ab93cc90330d3c8a72b92f0c5961f4ccd65eb42e0b9410b1eaa300ec8780402db4429f21e4527414722fda132cfb445daaea927ed22ef82eb4796e0a9b33b86389dce8c723e77e7a1583517efef3c16089597dc03abb66359291aacb8dd9cab63f625de75e4ba2e072b2bd70813ea72214300e9c934704cf1d1d3ba6aa4d934db4dfe778d333392c72c00c08bd13109c9360de1fffc469b879aadb335a4b6794fe69e0f1c1434bd715de8b159cfefebf5b71a9e2c58361c570e7f512185840e18bc3655697cb57285ed65048de1016f1ef27cdc44e8d284a58479e66926092dad67296597eff4f1a03a8f0bd625419f09197c66cf453f37251d4733e80887a39c9f1230297ce8c5c53d08f8272d1035595ca484c1dfb982a00e3b84bfccb7ccee82eda4bcde8041e107aa889124da19c28e184f98037148cf20994df6b2beb0ef101b9cd3507820a725e7e423e13e082ad8dd5a69c7494c9fa3d87c37de2b711b00c9b6134477c9a1e5b09b02c613b8df2025c43024c246f14d3d46a68145ef5c4b0cf2282d9a81b3a19f6937e547c4692d96ee8ac22e6f62cab84fcbe13ae0489998518235341264b3b3902caac66cd85d0a424f3579c348317bc7660569550ec2173eb0607ec7072093607fcb16a8a0d7378f781c0efef88930720d4b9162daea7f30c5da8e44b2b068bf629d40a9020b81022c84468f33b8d6e1130344ae4b9aa3d8aae5df8f17224c74e4a1b1afdff83eb330d17c64700e4592c6c82b01bc8bbfd59a89f0b1542e6a15c3e2adb84ceb30578805b3f06a529ebf95e24bd3b41e7d0973896ec9d721ebb5b06cc45d77a77e901f83d928a9daa90f9d77c9e1fd8d232dfaab4a7fa725c9a255b45fbe06dfefac16af9e51807e44bb0b9f6e4ee325485d1cf1f60d75196fbd32f6a288ef4816549848f9ebef28dae8bb49441c77b234ef993b2d32d7211b869943f7f797094bc674b41aab0fa287b50fa4cce1641a15dd373d866ef72390ce43142a9ee1d4ba65c3f2eb8c9d23a1ff62a070dd73ee75cc796eb44d772d0a66a0131d3a22d758e2fbc6d677348c47ad5c39831b053462deaa5bda61c7260decf2297ab0ec5cf9fde8bcf9983e1c8e4bcd0e978add8c3a4b53f0cfba3720818eb3abe25a4257dda15b7b7a3957a0b86e674451715f79eebaa5740e50c3a26514a12f801509c214236d2abe570f00bcdcf8ba959baad512feea13f5daf72b14c98f459291fdbc2df65fbb4bd262354ed47912adde523cb2983c1fec83e72760efafdfca45e7f74f42bad7dea369f633f806e8b1991f72811246022187038559cd16b905c41d46a7101e64a045130caf5a59718020d00d3bb07a3e5230b7265155596b1678b7b740f00627ce953e1f393e4b8676859afbcf17cd5b6c9ca728df3e7974bedacbf357c4193f004badf572325dcac729fcfbfad81ed73e7c8727ab0cbbf1048e618c759766ed082db5d3d843c3101e0f7c788605afde4a32472c4b4753e0a33e1766c1547250dc6fec90cc0b3e5d0f577c6d2fad02a8085ef720637611e1b48e384bc016670636c39f6e93a5de9030fd32d6a185687601fcc125f51626eea3ca53d3c7516400cff96de6db22808f0a426e0c853bf4f378b5b1837013a89fe226d636be2da2e688ac01384c04752446e62c59882992e825476723d2086e785fb6e2f6cf126b6bdfd011f35fb65ab71ac0b4a1b1b4890da4e200c63e8b3d677f6200a9e17e6df73410d9eea6d2272138463a6ef81cffe6d2f6d43768314bca3ed3831c5fcfc60cc0f26f777cda7b4abba14275e258a40d3223c3ec76bf00f2c1a63bb2364724203468e1a1045ea8cc76654c361751d29b68da95636e79996fafea3bad49cd544c09cfa9ed996f74f44596fcd4ea7be5c722aa51d39bed7d2ea1e96044ab40f5f5120f083341c267dad3a7328d1a8a7276012fc5a16d295fd0b48c3804bab718d64ad9e7a60afc3dd54427fce46fc2438a7170472bb6d84477a5353a14d2c45c8d1d3305c61c89e4f804c552bcbddd47ed1814e71731cb6f6d0891875a891578604b6faaf79ff24121ab1f6bb8b62437d3f6e4814de30c88ac4c7edcb07e6938c012bbb12d07f3c38dca559bfe1df0932effc0563809e32d7d1fd51e7f1a245a2917c06c1982ce1794495fe533d87098e2fd8bb72d708128249007e50deeeeaec1cbf48eac957ba43e639f9925866dd819427710257249f8733f1ef0e565e79bfd1c8ad001594a1f307d99af76f8212ff3a8bbb72e8acea2247932d1fee62c9fd20ef570dc9260ff5b966295e6ab864b9e446d10e549153b5cd4cdde7388daf98c97cf4c86bd1c1533a1702872509351f33b69448922846b16b7079378375bddfbbe3d6a4d998fad59a515790dc564ae09192fd721634f955fed5f661a886a7804465cfd50bd1db76368eb8a910051524c24ce172aa82a78c2ca3a78566e107f0693b930d2c897435d4418290c60edd2b024c25041ff6eabf7a79b6ff4142f2dba94ee46b46491fae43de53af94350b474ee95d5c864d46752cc7b4352d5910c0b5f8540f3d7ab8f05279e0da848cef9acf822072594b800b1880555eac97517793be7ccbda1a838ad47998edc1597326a3f3327251bcd782f9fb56e7fce63cbf9fda1a035c8850d3ae7e9a5aceef00ee1b1f56727ea9acf4ebae3f664f00fa6205a1f98187aca37557a23f220734ee21bc92547254886e99d16615853d87f958d6f44dce3fb02958efe9e459c9271a0097390772b870a3dd41ef511d9a5e61cd2a65e70c8179260622b683e8b6da7f922d27f8726edcce7dc6c7da7fdbafcaf8d06548d71ae75cd9de591bcb488af165b7a0c972305ce9e0e52f80ff9c8721548332cce460e47b3c7145602f41949875ab8fda72a88e773afa33cc91c18af4e6117caf259f6ec6825c3e5ce0ab4ab01e5398c872806d70a7854726dd6af17562d8e4e8bd82cc39217dda263e5b75276f57c993725ee4b85b38d7fdbe5c67a2e14e90d41c51e950f7b909a24c31e66ebe21a477723c6eccf7b31329caabcdced7f6bdb34a7d14ba8ea122f2ed2ef9e255a8be935ef64cb8d8019d9a4b17375936220a1fa3ba594f64a6f1ad317d91bc2e5f19ca27afc65bdef961929f6e4d920ce97d41dade76d1e371f89eadc0078e2eada19e72f9347b6d93463d5bf7e3765f914aa68a302b38583df618f27ff9bb3b590f9d72d422a5673674b7111d958044e16bcb5f5def3147f4e07a3769c19640fc98ca72741a358a367685b5b8443280b1ed13227aee16e90b04a5bfc1878c7aab55810f1de3178ee0093006e696a0336d1c894f0c081146e0cacd38175bc7b3242d2e7214bee5653bc0295578ba11068d8df8fe84378e8578d4ef60bb5ef73d7bce7b72d1a115200d659b24fe95e394d6db45cee780bc6309d9c73403babca025674d72dc7dd2a0d86fe48667d35d3b56c6f5a576603ada8e9aa991e4ebc2cf824a0772b44d9f3432e12987a8379a18b57895b56635f3403222e43111ead1310dd51372a66278aa0fd9b1c09826ce52d401bdfc924bad4d94649a0187ee028a7898ae720dd6184e9b5ae2c7414a8a67c78d358f19bd6aa1d19d8d6075a9afd8f789506e7319cee019041ff23a4b0d16b8f4fac3c39fcae9d67564a613bc65a523c3da723f4bdf7e9355f517de1432231ace3126d86a3d84aa2aca6198c9cd9c33cf5e7285011da34b0ff528cbd0224477f78d0e8ee1f84a6838a2824659ced8ecd914724c8d39adc21d27992bf6ee9ea65b7f8b26ec785d328185f9943ff47ca053c572988e94e04236828dfd419b7f22da1a41aa1605565de3ab03a7340621348d39724a833ed126aba1395f160eca84ffe46fb0b140ba36924490cb7aea6e4ccb627271efaf48bd3f60bb2d2a9d58c8303644ff00787e33579ee7cbd45bc9153e4672ce98fd0e2dd26f6962ed0259cc8c24ef57d8a881b019c5e6a218dbf6db5765160cd2595c4ebe08c722dab34c9d9c9f68c85172a8da333665aaf9516fa94da61ed2b9a2905a469d50aecb4f982c12a086fb2bc0e3a5ab37c50b706065c752df527d264997487d52f319c66f6d3c6aade1d3784ded29c0354b1baf65a0943b5372e93dd03c4530027d67020393609a9591c08ca946f33323c7d582de57b548325af9c42fe8ee272d9fb3578983a698dce27c72c489fd815d91181ab00dcd32075174897a3cc509f849afbabedc3094b90bd4d9f95d8dfd87f723d6e2e4161c864fe8a39894681d23685c77d93717e6318c6ace84c3cd1d553d7b1cf71d794de872cb6db8d8a2b1520e71f4d05ade558fa39ffda3690ec4a44094cb615765d3692524e6a352f5fd32e2a0723af5d1c0f59a7554f34aea3f6801511c2590ffba6872928a7bb80428b33ff85c08e001796f0bf8666fb39ef003d3efb2eb22c1c6b772220713b4d43ae753633cecaa1909394917096b1cb9a844c173417b48873ad835226504157f30fb36b8070ff02ce53dd8e04952521d0840cbcc8b5e1233eef41c5ca7bcd2a4534cc22c27b18bf8e8bdbbd86c0bda913c9f673d197902bb27d1253dbbd671289651f45bfde3b74e34f4df496353074d3c672a9ba4a984b045bd5c680b0763fef40938732e305a8d4753b80dc8be97abe3c9da74ef1e36ffbdc3726c74ae731e94f0ff2fa8a7bc5377e64962a6cbe4dad274e9a35aad34bc0599724398fe74935815934079e4e7ef1de5fbfa2b7130f0629647fa2f9bf7a1968e1d3d118ac63b5dbc79e39f32216df0e7d8803dd5cd563becdda5d3d573ad04ec04c1385d579c0de423f354e819a961dc9760e3365e050440b0cda50722162a496eb1c3b641b44961dd5b2919781113d86c55acb6e53f40ae58cbd2dc243c19b323073004dec933ae29c56afe63934fbfebafbe7d3f58a44fbe4832280566fa51720f7bbfd7ea91ff0cca882caee1a289deac54cad30fd0fdb5912beb3572f07a725175908b533591cff58d7b612d79d02332889745abb436043a1e864d8475a0161c871602d1c2fb73fc6c101fa2f989d87100c6a7343a56653e88ccfdb2244b72541807bc0058ae946998229b4a7eeb8e1ae313806a27afc01ac3023dc7606d72031dbc353a1031c770c74c20c37f25197571b046ff6085ea9942a5712006de27968d8d2060065125a3c9351b9a24b4fb262774bdead0825bbe664de3fb1784720143d3cc3f2400a11a069e3901598c1f1c1846ae5689dd09c22b594396c2726279724dec1a3b625c57da5bd15a400622603281c5308a6118cef45dcca502b20a2d9376099c2eb4692cbd8fd07474a9b0492ac379223762300541a4269ba963726d61d3cb1fde1d2a0de80cc2417601514eed1095d73312441a8664e1c072842a9be6898f8c223c599059a0ef754f4f7c570d9166c1114e82cc5f77ed6e528172bc3ccf48f6e5b40d91ec4612c34cb19f8a09ab0042de7068636dfd19b9624e72ba55257a2d004351cfa5c7c85b03f0efc0cedce6cf3843c8f646f29f50ad58725ef99b0f0fd8abe637f6ff7f6d63faa14ff23966c502eb5b120f66efe9f17872ba312f4caeab84a0472574aa97706982021f5de2991caee0daa805b1e6e97e58ea3687a4523e43a0cf9ae6c2144df7118fe3330b1c9d0818ba6b1d33b097a2722a86c6b0935aa9962ae9b8dbc21aeb18e9c570f7533490448a7e7988338b06387cdfa6a03989e9f627265fd97cc30745029dbe7fe6fa3cc6a8099c65a3fea272d21b465fde8e61d089d724afeaa9598d327d3211662fd8db90b084dde0ec30272c3cc7edfb76451ca823568a1be7a0905b60f8854e4b72b13aa400a5cdedea1294ff900e8fe23b25c9f4f77d82f23fb8629b203e6e08d0c44345258c68020d72c3d2849ac5cc2306cd30a2d185bb2f86d49c2a0c660599d0765ad817931ebd31aa347745d74a4993fd7f5cca6237afcd927c76a4bb515788e679236c729c5a4d939ac6e2abd98b743fcf7efbe390c242873807be0d9fc40ae5e01c829c945a72b9d62966520eb6bc8915fa265636a7c17a890084bbecf1091159de5fe2d86772b13678d2f58172eab23ef62ec79c9028621708fc9aebe2bc1cc9f32b4eb36a72b0d9e519d5098e44a37569b957a7b98b77c6662cd539d7516768a8ca583a4272e75b5a7620065037271e7308fb15602c97cbeee713010f8b1ae1f18ceb3f530be0f89c7ca39729894fc638a061f7f4a42c6f3abd0ed96b361e647a958363545258875142b35da69b8eac7679ecefe08ebe4e0a315721afa02a0960961288ad72404bbe160fedd049b34b02576ebff7cd5008bf5553f46ef4675c7b656866221b1fd86c5168d7c4fe13bc87b8c00ed1439ae5d5cc9f52d86ebd73c52209bf85348f32f312e9b233ad43e5151dac378e3e3e6a68fea699dd231457bbc948592c72899d045cfea5b76d93a17243188a7906595981880d6dc2912ea43ce1bf2abd13122a89182f02e9fc4479ae3866b1c1cd8c9dc1b6a903577dc6ba55d65c42ee727cd7045ff5417dfe429d52aaa3d213d3280ba27f5409c8ba44c293106a2a5472e01ae81b3b46bcb57504399c5a67b56a8795be4fc8b814db80ec37b622de8c728bc54d2fa17fb85fd7187187810bd2398aef8d3aeb4fe8db2110ec1dd2b52a724a3fe46d447e5b66ecdba73b1246300b74ac7f38cf1313c2ce28f0425aeb8d725934475dc080b3e3db2e5c5d5404d5500ab6bab8131f1c21f5d14e8df7e20072eafa090abdcd221ee0888a93419dedc3687734eef5fa8d374d99bf6218e5ce7220ed5e3a6bb2ee8e4311c39e6bfc52436a20d7eadeb6f18578507f0337275505c85e7bf3a41e440b4c85e9004e88e6c60908ebc329b00be4a201bfbec7732411a8d420a93cf6b3a3db4ed9459b7a2f0c05249fa9e0a01c95c8599bfb9a1f757296cdad875af91aedf1b170ad7ca5c76e4133372cd311de26103ec7a73811145304c55e820481fb6bbcfbef422696f9cfeae343dbe27c4086d4a9f6ab9de64a33f19fc313c0f957735456da112c04e7f8887167893992f77004d91fba5c288f62a4295e40508bd1da8a3b264d175da0a6d731e985283371ad6c6604c84617ac3ce8f42adc501775eb842fb94c7e53a097cb1f8888a0a8a402849b6a66b303c434f69b5cd9c7e89b10d0706cb123886d8652fea51b6b2269aaf3710ea58022c9724b5fb6fe2784cdcfc49a6dfdeebfa7b8a7a7ac6b346618b9114c18169201064a4d73b2fdc302721be0657978aec989a449b9bea375dd3b5f197bf27998f161396ab4add2091c624a09cc2a7dd6cf99203bf09dd96f742c47bca36f478ec0f272716ab3800bec90ed9797f17390d1bddf6cbbefefc8433f23ee43fd19bde29d72b9477f8b88a91a013b2f2caec6b9c276929af7a065e866c1429c107ff4e11d723e20fc0278a6b3d358de119531e28833a631bb69f1928b450b9136abc1448f720de1ff9a8860f55e93069e801c701b0e316c31c03cce67fcd0833ee5f1ac50724b8778d38c86fc8327dcac7358a80b16ec95b87ed000fa7ce14d634fde2a620cbd4135ef44ee65e7558c12dd55b0b97936989d2cf630337ae620404e6a503772c1a1e4d4453f47955f926439b12c57b151954a4931f4a53a718840e99966bc1489a6b9e5aee76ab7eab21c957608a2d6a5c2d2fd3ed3dd54c4c1f8c880d2507227362f3189e05ee869ae565e01190c4a57c49b69f101320f76c396d05827a14e6b5dfef74ff3c491c03148fe85d3891f466a92490c37afd05e13fc81044cd73b4d5ee506b4845e21990ae670ca32d80cbaae4bc093f40e6ee84099d39032da727e2a0bbe3ee4b9b09dc0d64968b4750f8cf509f99a34699411da229684b33a7281784748dac9944dd513cbd9994dcc07c1d39ffe2930ea074aa0399c21b4dc5ee541b47e4e972db9d8211aa2084ddbd44273cc1f2f42f1d22a94604ba8a2b07291a1618fda911ad5fdc90772fd39928737c484794e8d4f47eca03b228bddff35d723e74782cd9775175f380ed529d27e2a032308d1c775d25d4bf550cc001e1d68ec6ac47f2bd5e5f2acb5740397a963f36e0dd682ee3e4bf2756ed48297b6721a0b2f0e9cc79050ddcd81d9f5e377f6bb2db0dfefc4f79765947174051626223bfde996dfb90fba0843e4ba0001a2874ef28e925e071194fe8c8eee547a35726be4f58f24dee76b5d763634dcf8aa1c86cc6b7652fd749f78922a98a1a2ae7271fd03a35ffeb1a71f5b414f0abd208f416efd1a6cf0edf17456d09c97909d7255161ad548a21b38a8cd2deb51737c8714d8c7ed03e907159613a57014e9b8720de548fc5179d4f105b62353acb0b413bcaae7e13a87ea81904889617b30cf0c3b91b23b2564805aea0617bfce1b30d22a72916a0dd77ffd6248618f46997e729705fc676d36f7dd080040813bd74eea27c27a19e0854fcc0c9e439c51132e51648bba39e391e9818f2e465774d7011322b13ed5d9eca1ed73bd10033608a23071a54fd2a1076d82d45d363c997327af982d8f8d1b84bf15435b4483e4ec26729464578577c218fcb93b9337568aa9c8a18d0f4a953705fc25164794d37159724adefc531b849b07ce35f1d285e9f83e2ac4fa675e65127126c723e736313b729f80ee0093eda71ce01de99f4288ea5221795db3ccbab80efc2d0bd5991f524949d488239d158a3b3920cb57328e2e123c575d006ab04462fe8e342c68b40b7244d0078122e508552da07d7b92bfc65e144c82b4fd05f95d1dadca2e7ab0e529bddd7071504002928959af5a82820c03ac6793487c34bea4658cf155d6e950720ec3eeac5103a0de12b715db60aef72ba6374de2980fa319ffcc01f9739ae67204800482124128e7f7f582b5d5e93e30378b499f546594ca6aef41d0a35eca72539877723cf6f50f245eee60e224975895f8304c694606515b20905311185e003c2ffa50b406bf462b6088ea69003ef7f04f0b65abc4a9871e69b551d29d1271916b6f2d74bf6eaa8470ad5e4c748b219a1b5a20fb32ca3073d1a197737b1704e176033ededc7aab6932beea5d6344ac271ad90ac66f1ced2173ae543301537257f57815913dedf413b574020173dc7b59ed92dc505d9287a9e45a74794bde7290696bbe32d11e782744fd79be39ded0b8721f38b5c676871e79a21e2949fe72145c9814b812c532070044cef0bd7741d9868bba0a964042a4c86d7ecc29c71c45777738eec82f1ec2415f70b24cf996f5f15d460b46e556502501c0cc12cf3a828374759acb659b8e39937007622c42576a1a1cbc9ef1880cf25ce42bc33d7216f8dd38fb31d45ea2d9564425caefca108e3d946c33c78144191d414a4857492297a29806e35dc99511fcaaf8cd526b5d5e605e76484cfa7de7d62e85bfe9720f86fecc8f7e8f2e65ef3b56ea857526ce80b81f2e555f406bafc9dbcb816b7274a6bda122b783c01b95759c2e7c436613aca734034a80d7e58beee8b2620872e19673527b67824a1fbb1323f8993cdd280745841674dc1e4b043f35b8322b1b9a35a8fbcfbca66c05bb8275128a57c7b9418f32a34f0150a30dce83ecb6431920bc3f84122a435e87fe18f5a46755ca9ff265ac7963c03f00ef8465463d0272172385cdac1ac7aa9bb6a8a5ba7937f7c58613ef2506a4be0afe31c4878d1a40db9ae19c7a853edb6d20f5a992e9b4aaefcb06d87b6ef1193a928522531cb872582559e1ff93c2da656475ff58addcc1c0746daa8a601ab0b5682abffb0d3623f01b8ec55f7be0862ce437541e0526309b05b3519be18e798742d29be0120f425ca1dfa48a696d62ad0dce73319e8a3763c1525548e0f2390ae8ed41995b9472af6fca81a45f32a959276162e767f3346894cc24fddc9484a399932ff6aade00a1af03250aa0fd0e8dfc3c9d1b894b54169d2e4499834253d78978b8f7668f72324d9faa0194e8b81d2992fea76551c10942e30c96e66fceb7c5ea0cfc079b72f2164f351c5f674351f427aa457027294ad0a79f2cf983a053f3678ca5e1d049df4759f31f6ff6bc5952957932dceaf3285fba1a7b26f962f6fa767a95bf8a72099d013f37297412bb56df2da22883ea94b9bd4fadb674103d1152c2945a447257cbbaef03024e91fcc50a2f35f47605aab4bed0f1002104430ad22836577d720876d42059587097c4c1108c73da39b9961e0694c5079909cf64ac219ea9ce72d646e2108e91ebd3e5547d690e4702806c1549f9bfc73eb2a87698dff793b1728af2ec180fe2dfd5ef54bb7c6ee34144544ebe612d8012ddba072bd7fe324372b9d3581ce4c680511e049068edf6f22290ebda9f7392af0fc499f8b4a70d55727eceb49fa76912710f81198de3ceebe101a6ea69a96eed12f1d437a177167916433ac3742e5e3093ea0a6d18c1aa87695376a2ffcf84ba00093dd5608cc13a0fcfe69d5b26fa897f6399005df430da5df2ca8cc55de3e9367af1e0a2c103c9723282192fc0a346b16ed8ea926880d61cd0138e02e4a1f99508d49290be9dd82ef920d47f70c1a5d1812af997e1d2582447c876bffe51cc0187cc18e95ad5c2116ebec58f7279fbe50eea30d2b46fa3ee6cc1a531bd1c76036373e584efb0b172dcb278d701393803e4c2d8e5208d68d5c064e247ebcf622cad9be7b819400836600d2152e0002347ae348878f0b01c38b3a6e231eeeb4d5cb430f72db14b4372de30a3202e70bc5c8a92597d19636bdf85a738bdd9836702a94a7d8eb2c6491e81c4dc2b3c16a75d2593813edb1ded1d2e569024a1dcefaae2badeb61e05e6655adc744040d6d7bcad165877638b60d07b8cdaa5a00814b555f67d4f4e3bf82fef2313655b45315c8adc9200d6d6e564d55d4e032508e535f4209daec090c3723a51b890f881ac5a8191eef1ca113ec74f91fa9479d0c94f0b9bce068943f772ed3bd3f4730c16f01b02124bd2594cb58fe8f04026dea85908992015b6e39f4bd80fb3e388754ba4c5ff4aa0c78842bf164e88e9583a74ec062fa1e04ad8b939df89fcc73b165017487be93d46244570577fef70298153d7948251dd496325723a3e33b4ffdaf7f98e7b6474ec2f5808e146b440923258b1d4cbccf00040ca727e3f9d52ec4e36b861959cd5e9d43beeae6e5607e8e660bf2933bf22e22e2847c4622f6e4398a6dda905d9ae2830954df7d7b7d9eb3151eaf884f1f61e963e6f3f3631eadce3ca27b3e75b4345b2916b4c250315453766fda83b63cfb4cc6d0e1eee3b510e19e742a0554c31fcebd20c1f934a0aaf7b8df7fb84e07a52b0fd7254be2c081c9f4f5a92a890cbbf0daecff36a5c904a34d2b0962a20eeb60649724513a78c040114bd334d423d6f61fcf6935ddc466814492d3068f43d252453723f199c75ce1c0a20153d788093ce668b58b94e2cb2bfb037ed5ba2d769331672c5a0a7b39f5d8ea4e04b343beb34923b174beff4de9524f15df75309a3abc02ab3de51a03516e0633ef1bb5973dc4f8c995041b2a7c30af980b879ed47818e51863044d8424f0c1fec9d61802c01b57d3366a5f8595255444f26391895af630438d8e2d35e69bde6230376e8e86a37b168b3b211720ae940347686aad9545e7276be399d0f24c53a057a722517ab6a6a19b53466430e4594e2c396d58b0b7872afdc03d3deaff6764c909180ec458ca1bd2458b513dd4a28016acb2034bab472b146d228a7c74fdf8b97d5c4f3702311e08628a75e39adcd1b62f6371a4f2372393db415f54b02c7d30e074774858af89a512c98b7f5a758cb385f152300ca7275b6e03c851a169dfabaf68f21c4b6d67f1ec0cd305cee132b4e90165d5aec7280fcd8cc39aea34053b7c339fc38573a918bda0fc735860b5cecfcf006ebb3725f9817df89d790d130e6400eef9f918d577b27b280c1d7cbb0d63efb409fe872f6899a3bbe9f49ec79ba4f6c65cd5131a98429a050be96198debc9d1da6f0d4d549604d138c171a3294663cbce07e595ab210eb11755f0b5e2e26b0121c1c11398faea6a9a5e684d07b10f949356dc2a31a4c12ee3d47671eb521e1128c0522bd9f72f8b8678c1ef5ba5571746f7898a1a04d785275e6b5dde1dc2ae26c11a72a501d3441134b2c32c9229d427ed7c7be90650f1d87a870d296ec28ab6a01e7271ddf0507200e5b7aa75539f4124a6762f8a3be0e25f23dffe4fb81bdd3869104a59a088011026bec1f1b9fcfa9a986a7b4d1662bef499a49104fa765b2957724d91deaf188a0dca2c8ad3478005b1b1d9b0694c0f02b90a6f9506813165ad72df14dabafbb7df4173700068f49d6adfb5403f682bdce7071b78e3f8e9956b72c43ac72d50fcc587c1d338f10859c60fc9f032ac35d88dcea65439d91b88697246f4e4d6713fecf09d7e992c1b761c749550038f1bc457ff06f98e7f8c90a002e564d3b8ad30c0ea26b35149b821f3e9c6922250713d88995ca4450d10f071646feed1a5c507e179bdec6dcb55fb993b10cf16cb050e02b762dec742b559944c02a7baae78a9db236533d3479bb21779f5128eff862c9b36230222519512c92ecbdb95276e02ca79b7e55ba80577b1659997019983a013515b77a995f514db0115b849f1ae5658558f1c7e7dc68753d9907ab7743543970862b77121b71e52703ddc0d693c09a7bda34bfabac0be10ddba96dd367fbb331a888d556c6f4d6459eb4f923134da4338bc8c54342777140af3376ecf0cc191573b97a517d6047372f1ff4bade157930b92bd5db685df2ca52e312e517a75862bfda22fc331ea6f724648ad306dcfc9bbeb6197eefe5df929938bc830123609f3b49ed28252785172bcaf1c74dde7b4bed5a8629495e2374c1ee1de6d8146fd5f77804aa1422ed1330839aed096f5154d0f871533d97e5ba040050a2e039b6edd228e5c9bdee49a716e9169fa557480491eb495c74b73ab7f9b98e17af0aba5910d3179e85c91b572766679659065214a93209851a851ddf45fefd35affe1436af9b2fb39eff4004a14442f538144ee3c07aa3036c2065007c73bdf87ab81aa7e96836bf31f6bc1728486edab84b1c4bdf5a62bbfebcfddc58559d3f07900a095afd66f46405e7b72da4d91b8eb06e5d2479cf5eaae386bd52f33c120e0200bd809610e33768a33723e23c271d71afe2d5f8e54cb546db71e7b8e9460b1a3f91349c7eb568a844b2a5a54b464232bc6321822bc14bdd25df798d90057c6137c480f03ffe5d1b0a772c85169665842b5f85428d67d3554041fdb6a492085e8ebbbb5c83d748e30f972f1c20e27eebe1b0f6f59558b232da8ff2eb1a494d2629a33732effd35534db72b3e0d6cd0e8a58fbae57404e289e7cd32ef24744560d4efe77b621db2c38c472fc407119dab79e9c3a95189233099e43daa119805738c918ae2cf3f8ec919172bcad1b3b0c6646c8786e67338d06dbc2aa8fba30c76d01ef71f55a147ce17072b988f477d380e5316a4afd32c8df30038a05eb68055742cd51c89aa03c089b7247f2b0763b55b09041795b08f2468213ae09dab473b7836f35a7054a8387e972c20fe036b34ca363d9dfedd07c3ed8a3b4512521f4855336282b4d84ba9ec80825b2b8f8e17f57be065cf6f9a49f96137250469c0ec3cdcff238c37803f20d2e1b2ec266d4bac70be09a25dc812c2054805b7b24dcf305894ae9ffae0884d4681889d1acfbef1fcb7b85587e72d3ffcb7ecc04b8e485818886b90d722d550172f6e78e2cf163009efed9f253f6f7db209757eaa914fdc590e43109aa15fb4b72cbbeaf79ebf4b0d2ab260213c19eb042df684d83eb01b124c06c3299db4f2e722892b83961accbc45ca203cfd0f0be059471eb6f7e9779d3862eab3ffaf57a72edc1da348c69a5a60f72e39fb6ae057b25329ac09dfff8a719296f3decc0615d8d2e593e60c920fca6aa4435891d0a7a12f5ef3cfc20f340e7c45eae3c16e772649ef925b37f75dfdfd7a38fe4bf013f7c0fa4b24c8e700a048226d9c29c5472a4601cbb9c6362fea691a7a85396042d54e52e1d90a34b57b38ada7ef78bc8727080aa3bcc067699cf9408695d43c81f0d17a91a28c12da025e21712ae988963dd6a44b61ad274f410da6fe163d726dbf0e434816bef7387f1b44f2aeefd7d729803717ceb5aad77850418f3aa6376019081cd50297ad68be22f90ffb35f5c72c3f1db8fca532c45e7b3be2ccd0f7aec140546e4d5037b1eb7375a4840fb4772f3dd68aaba8ef6001909b7a6cc6aad41d7b9c67ba0894e161c6151cdd6150572d1dbece58a0cfa19ce629a38a0941c79f3762f21839f260b434524c77b99ef720dc23445807a42966b0278609453e529b17d004381eb15ef1c561de1a3726a72e3686c41466ddcbe4a2be5b06367fad028a1635805077d375a8fcff2599c5d725ac7061fb952a80efa705c6b10893376492ebb927880bd0e2bfd1f1f311e0a72641c5c87a1788c1a65ce564ce5c62a96e7496d1881c3f58f61b4efb787902d7212bcc3094ac2cb7fa0043af03f83ea2351ecbf960826179420513165d32d485ed04560a1b9a618a395dec18318b9be10cef6d8b06878bf259feeeeec38721872b72ce11027071297312ad67553e685f09d96f0cdc2b774e3179e0e15b7b70372fde28467929cd7a26eba9f50ab55efa14cb0284983b39537643f5030f7a7ab4d3f0e65fc728b46dbe6548aa892d5bc9348b4b0fab03a894224bac61d12c64272968dfde9a2f2d95efc24dc27f1079bebed704581e52bfac066c182cdacda907274e0358538db9be059c2d8896a7ad68bafeca1cf1335d0c4910f69dd83c13c7203ee62d25d71fb73cabfa0a866c8fe7db8437b5ba4231e97f5a2e00c8b309012eadb864fd5f7fdc5f8747cdff2bb969665655705e7d86f28af1a6a2ca2de7272cbdc9894e10fe1991d80cf5dd44262b5b1b6a3a26f072f9c0149f831aa13046e6065684516ba05dda6e6cef9f005de4543624053416a6684ec96e043340c466c40d875c369f705d0b2ee973a215d595a3df250577d0372bfff5803bd5bba0d72ac81ef6994fa6942a3de19cb2a2cca3b420864148049f5a2d7f194e6e7896d6f83eb039089a783ce19b6a6104e27a301035cd350da64d32aaf3dacb00f064d72050c0cdeea9207891ed579a7efb938710a463b9399ad39a1cc15accafef00c726045bc71cdb0f5adf1ed8db7da560654c4c5938ce20bc3b0ff2c23d6f39d3512de1e5ca429a551174cb21c9d1bef4564dec7f4d14d6f8186edc955ea1767187266e9a0434522fc5c065eb6fe3e64c5fbca2e3afcac74c36a99c7fa25f14497721232e797e7009fc463d3d742728b702df230c6e48bae6bc65cc8aa0149d2bd729f7ddd40962bf904dc9682b4d4643e150a0603512a3e022cf5621020e6183c72a19a531a8d86c3be6910becd605627080b65e737f6f9609aa0f87f58d3b79701c06361b26629546b4c9d3d6e3a6ec1f27c3dd90571cc026d2fb082f39fb28407727e715e033bf542ee494da517bd7f376cd3bdd26a6b8e27336345771251ad72a54dfdfb93356573716fcf451f768a97261f9ac97a0b1a36b8daa33942fc5e3df9b5ee272efd8bb26b349c9c6d458444939abbfa436d90206fc0894c56c01872027255a2663b3a9b3c60b225edae2ff81569854d7d8a41b2a71150e8095031725c08d8e70e7d8f66f8a3b3e88219e6ed90374f20a96237d4a4c10b75d233a053d4ef5521307ec792599d0536e50a59f22fb0128fb68c17a337b5313615e4b06fadaeaad1625eaa047b16c916578e4ac52ea9076555e338bb69f25ca33379fe72c1dfc7739e481b81e6cb688447d604d3416bb012692178c01105c3d3fdb3723b203fa9f19b13f119808d56c204851a580f2b598a3e56c855f8594a8722ea9227ef4f50b1d28bd120685338876fa68e8efd5934e06038f96cf46aa72cec4262723eb5abc1415a7337b3e7d3c375386bff2e8abfa447449b2dc2f5aeb88813547204513dbe42a571ad6a84b28feff5e06274133e0856025b8f5372529e38f7d3084ea364d6d3e1a0f125f3e31656792709332e3322c6cea5e4e7b79c4d65b4e872650581ab0c74309708b9736e79fe5aab306ece8346b283480be357480c8dc751d7df6c96977d369e92182bbf31cb634d4e866a176031691a9199831f8176fe726bed2e3e432d48f056041012a79d507d8054e74d56d05067f22119ef1312577207ce22454a650dcbd6be1f4d25f6928e8ea29fe4eba9586c31a47f0ffa0a3b72f7ac3cac932038946c18de84404f867348be136ef4d3bc569b4d9eeeb2d88572180ce156f242c7ca7329148881fad1650d29134208168d9a76c206dd3f9f3872d7e142cf21e8886adeeb6412aa3013fd4231d6ffd0b04eee927b17f6c93a0d7269e052154c34adc98b52fb620482ed09454bff0bf8e2b42429d4b9b7f28b032199ebbfc91bbde3a1ecf740526b513ffaaa247e4e3aaaeedf9bae37159b48e072c4ed388ffe968987f665bdb6e6c59724a29c9beb7898250815843191513b212af3885520e7f5ed85ebe7066462ac729d220f1b013f7215bc1bec3406c8d46c726ed39ddef983a4672b3ac9ff1375fae1985314a0f2d1cdc2c0229801a772b8724bfb9c6ae62f2e09d96dcabcb8665d536242124d1be6c57e085aadc516b6f236ad8ddc4b60b22e3b21209d7633da696986bc92b9ff5af7f56782df494fe0d83ec25ff958cef729af7038b112f4be1329e72e5fe7518f7fcbc7e92ce207105c3361ba661348618c17736b50935d99e70ff7c615ec6e63f0a19532728b178cc5720a96e3a9b60409215211c930684fc80eaa09d894b5ad2f77ff7410fbdcd7c40534309d0faf9eceb7cccfcd1b049358cbed88609f052803fe5cf813b571a5320ef29b029520b13945698be4f0778a4d0a8e06c27a1ab45447c8407860a14a6e72c777270967df132b93fad949d3f75bc3e1215c84b790fea29e13eaf7f1342d722f22bf68666b5a5a9b9cc3232409badff5d85dce9ff6f2f6f60e04dd17b4b4727ec497cd17766f44d679ca2a86ce3afa932559f45fd22056a0ebcdfb82251b5150ab8fb9f82af428299e5e41bd40160181e3553f6de6d6a50336874d682dd4724c99d5e4a026b6474e393b41c89aa2d6a5b03f7cb76eb98aac0a9a73f925fa558f62a08270887be68d7a874723ef4611ada44803e261c7c63acdadd4d7f647728a9270231cf5dcf925424eed80f1a8f3b32437243d3065b8bcf74b3e13df21722438b1dbc731510d6498219603914aa983e3bb0cf371addf076450c72d162e2075b7c12998035e5e2cd515f4fdffbd83aa8fad872feefd71ffffeb32891a893b5ba4b7df26dfb7e11b341871fe074d1f4923fef644aa9c3687b9ccaed5fc4072b652ea94ca32196f5ad0b25986e131ab812da1cec5480b4b1607b3283624577202deb6d59c6a11c1e34a51540cc430fb59d6f24a7d263d1cf8130a41c0e04672e53e4aad97a939363359e177abda340a05f20f3d78f79cb3517763ead51b1a72e42905934c6477693ac9fcd5a2d1aa2a5eafd1337687b76c405459a684331904d513283963aaa8f551bc3a0bf6e8c60fe7d3ced7f7f9bc2b24494f70d47e0f72c21eb3bd370fb8705a18867a4d8b92517cc3f5ad5285867be8204e6bd9b1fd7256690869e7abda39af73fcb452c63e7d9601187b7e7e9c50f70a3346b6e1e972b1100545317602ad25e2212a70dd5d59d549ae0d21457930dd279ffada7f866ab2815fd02cfafbb342591814107f0f3420fdae6ac3c24eeece9e1e623d694a72f2cbcbde71e2ff80ceb0e2aaac4029edb3a399e07dff612a6ff59d420c16b621757d01589b885215594dfb8a130561edb0f172db27808de28c808e5db4046c1ce6ba1622e15523051c4d76419dcf824ab231a4fccf4387df50376191600e607229feb2d61967e7304c242edf5c4abc79bf8b29e9fdb5b55d9d08d56ee694cb320ea02373fbe34b110a941644a98f3c840b5901161ff310e15a2a0714d893f4729a7bd40a4b1c732dede8f477ae6c2e045355c03f7b200d69346554963061c97228b9f6fc547a52dff60fb76ab3991830c4bf471063c1c3332c422edc323ad072d9023261454966a79526afcc06b6aa4da4afc60cfc0e167b83c54cce649504243900cae76567ce8e530ca05ed8a79b10eca2864b474d34cff859cf8bd8d9e4724ac31cbc8c55df9cbb97c61456e0bb8aef4802d78a1b79422fe801f150ede072ad7ad69ed10247f931cc455fcb1d7ce6af7eadb06474901f41d475ddd4022d1e19bda473554cbc999e341f929a9795d64d999d8e8c75174d8c5b93c1197215723ab59d9111d41eb1b8d436204c75cb68e523a1a3a387e8dad00d09f1391f1f72b357a47799da9058003ac0f951363cc378d31a7a09def18453397c5006e57472276667517a5b84e00c4942d512603de0b5b29ce8f7254fc815e27aabd87a8672367066ca1be12659e69d8ed6306e4adce879f1370c52ffce47a734b5935fd008ae2fb931bcff8d9f37896293b07106482df1e7da7e6d342ff3e40498960c9b143f46337ad3de780b62ef10f42bd71d111dccd2e11e7b30c1db713c69635a5f222aa732bc3cd09266938b1e87d7f157587861710e94463f8921863b6e3f11c23752b1592cc263a583eeb2285cb515c0306e79044ec0946247734c8d4810fdd53d2824e55f36f3366aa48d6e54cdd72ac1b920588b3782d579742ba789c2b77919f25092b9225b0fe75635570a1764a5cb8d81a7b4d7a8bf2d739c8ff41a135e727abcc657467c47bd371927480da41c49bc3fe16cd5e0b4bf41f8793e963082721f50913b605574a1a7958772bf5c9ecadab5ee98a670488aa607d39e8ead0f72e5de4f0d85c2f389bf552b67570ca4bf74ab3ab161fec6bd41d65f29c5c040468f34caed8ed1dae54d7f132671178329784668d0e3cdbd523047d3d225391f725c10a5d2ebd4d5e8976166503acc43746502a31709f20ffec3d62eb0d0d095725bd00f8d2aea4c2161d6fdb15a2ed263b876b66ebb88c5f6de0de51fb755613fd9cb0abf7247c8bf9fa5c7ededf7f0041c69f262a1054ff575a852979155dc728fb2fafd2a7913918b8bd96dcac37af1f2c89e8602bf20491b8c8a99ebd3717249937813303900325361a415798b96f3bf7cb43f784851c0f498feaf44131572b8dcf321440f8c7b040df7295a0710b2723cea0162507c8bda1c610d91c29f72600ca25b9d3b8b6da472c3a9f5ebbe230207f71e629f4ac03f4460eeb782696e432fe434c952d7d01dfd8f2a6a70c6ded59e47346c77efa13b33c95c9e3a574a92eaaa7b947858c9f00b5f1163ceb0c48753aec9f19ad17313987c72e37031725ed60920b246227b564e1b2019d10212e6a0b15e73f93a5b71016e4c121136522e7a4d97777582fe1e2e2bef0b7752f017f5a54646b7177bc36808c1c42fa541ee5669f8706c482e0420be59019d0e73f007473d805316db6c1d4c2f35d332729b2e57e505027d6bd578f7c2283af5d8e70f8130a8f116c5d24813663e055964b84f0c13efc7430f9382f1fa4edd88098f5ecd79673da5bad4432b5a734d8c7211736c92868766faf092171df3f7d4fd722cc7964fbe8cb99e702bd7a81b6e44849d229be9cfeb179c0d8e18f0d8d58c7bc4be9f49e8158c2ae730c4b0cc126d3e80fd1f14a74eefb087aacd76411dcd7f383aacfc74ac2cf008334c53cb7249f14887f191b6f79863d3bc3acb1cd23acbfa2ec1a5e979d7188f9d001cff4268a16cdb7674a2cc29614f96cc98ec90c31d064949933a960e0d872b4e37487331f22ad4e5b1ddbbb00c0d7d3139551292b87dd8e15c4b3bf2b01c1caa2c0c97659f81e7c99fb3f59adfbdfc4b937d1f132ed8b09a1f89001dd4043dac751cce72f20821ae881a06e255c654f76916bb7d9b6cbefdc6b92ca124495fad5044e2727a9ec294dac5d93f48ce9d656a58942de37fe8a3a83a98e339282ad1bc5c880c9a0c82f33c20257c3a1154230505ead663c3cda60f6df5db9bfeb861fbc6e4723da5e78493e9f30736fdc76f3c78c7aa2c4cdfa3ce92c9070665d4837855155b2c72f579e4fbff9150b910bb8745cb24047ce738d47211e9c7c72db9ab8e5672bb5b1e18ba50b6edcdd7cdd5b134b8bd2d48457767a7deb696d2d25b033f3e5f7d479d266332ca2e5371640aad44b3079d29a5f9b79257572688bf01c11c14166c738dfeaf6bbef0afec6639b6b3255b763de5663035cf3f92ff379fcadbc0726b8a2899dd0f770adf2965830a42e63f9f08a52ff61125bce97b5c1c83a5907234af32e33b11b8ab75081c74a68119fa33d7a8c3867d0f504ab98aa1362a547281f05fdb6ff17f6ea1615832b30a07169eba6a27fdf6b07e544e616d55dd1d72a55261e27c5fc27f48078f61f8531c2719e78f7170b555be919a91dc11dac770ac640f3e209f3887f3cf40bef20ad525bc9d3cba8c4fbe58ed245e94dd619272bf899f89cee89f0b0e703a24fc1ea15acaae874df3a3cc52c23a0fde73f0de72b33687e3bfe352cacf6d88c778f0d585ac0aaa4b00a43629b7848d6391e83372e9325bff36d7c0c64619db21d2048a6dab7cc59b3b8207bafdea36da3f1cc472f6f0a8fb6c0218eea4656eb6e7ccd67c9c1456662996e7beab523e1e2dcaa172a787d98865cc5b21569a64c229958deb378030483e2acd948451b13c1d435c07aedd5e11f2957703b078936f0b909266555afc3ef7ce3b21449b41e629c13572224544a38801303a3d87811cc51bb9ca47fe514d5ef86314aac0b709f70b5d3b2c94006686f359e325ef8e441a114146dd987cb9bad25e9577583258b8f2d772e92f5a583da4bedd54eb60bbfcb2cd9f3ca13635e418252e37c2b7d302371d58247dbd7dc80a83fca1d47b5a1f8c37a4b4691e6ce9c4c0ff26bcfb16d8e6027209d419c41628a133f835d5fd9698314ebcbf03fc2e2416d4ac80efa647086972118542234a7dbc3cfc8cf8936e91f95e22878e1015133dc48c2b934f498ef10e22bbb4c1df811bf82660e1eba20f56d0de2aad5f8f777a1a56f1ca98e38d000cf3dd5c8a43a74c2803f6d93f1835bad1c9b2629a196e2ec463ca63138c1ed23258a7e8946055e6c7ede138a715777b421a9147023dafef4ca61a47584a9e3f72a0ebbca5a5d2d2562ac2744809420624e9bdc0cd364b9e1ff31796ff75b9d672831db0433ff1f130404bac953c5277f4ddc286bb38364149b4c4ed7abd65137284fe7a149db703d6354176eb2c7e1852c917a79eb5e38a576812164603838372234b52a8d31a86eb4004da91de2d18a1edd8c2c4d13e1fbba7789f1e24de2b4de45039c16ac3ba94f2b15b04d1cfdd475f9cd49c12fd3baf1a573069b922f572610fb1e3ba82b8ca4d0c2d69c803c100814b2b5bed4ed022941b64c79b209f2e12ff308896306155d32dacf2a5d1259f74a22edc35722e6c5e7fa1a78faa1872276c77bdb1f8621d59005784f7d9c498306094107b592d94bdcd3d6cca28d9723fa485e6a02a95e8e9047ce8ef9cd58ac3d8d71f87621a19bcbdf4ef96459007188b16f90456e962b77f631beec075f8e8f568c2c5daf69138a97c7639a5897277a01784960a1e12854e9a7a25f1594279fe6b11a9b5457541176a502dba6772820e2ccdaee28d2cc7c44ca87a287fb06b4e7e518c8140d4e2ef7034f2b9ac72d8f7aea06407a22c64070617658437c0716535ed301344bffb48c6c1a237385b3c98ea6717a71bb1d798c0427d68a9a39167b661a2bf29d981189ac9ee7e9c2741a2f4ff2310e3d4534aea0d898de690cc094afcd2df06f8388f4f55319f4072010de87b33152f79307bb418ec32f860cac47fdb12911c10079753be2b20f7721a8810c5a9627ffeb54a1922aceeb9be584e5832ab2a8b247ef0966607a5bc726a815d85f22b58dd14fb27997c05235f6e0b1d5dac607d7d1ffdd363678d5f72f093b3c19320a20d0ffb4b736964f029b8fafe26433f1ccb4eee09b7cd6bf82b8b9b66c88306779810f116d44f87a397d6a89d3ea1122209bd112a80e2521e40beaeb2b5f86a23ef8c0052b1c0cb1db454a28904be4031e61d5a00fbc1bb97724cec7e10f0436a7dfaa12d3b333b0c4c0aed1a9021ca4ac3b1d787ff4fabbe72460375cfd277a511192df4e5f10768b293a5658dc3bc6bcdef550dd5d703897211eb64d6157bc41ceb70d599c4428e8648097fa2b5b6a32bbe48ec123d825b722944187a6194816c0b17b18db95983f10f30b16424d0d438ac979aa2c4c0f972b58dba0163cc12d67c8cd068cecbf8d71a03e65e75f25c14674d6f1d91748b1642457458a3ac25ada2005fa9ded9d52cc67c123f27b12cea207443c0e339d672824b33a6c3cb838bf9bc599b75eacb632df7182d89ae5fd36149b4e25b827072cffc620b752b61e5bb764262bbaa1917d54a4009ed3c4f548056261b8786ae7297a7e567527e4ce4b6420684f3ca6598623a1720fdc1712502681560feb069538cbab4b7ea64cd257cd0b5c0d1f2e9689254ebf5e7c045087cf05a845e873072c91689ef1bfe1609b43f9020c29dbf447392e26d5457aae6041e6e9efb5e3952b1043575fbb2107cfa07ea5269e5df7d6189a7763c629a9e9eaf2908d07c7d7272d1e48d3045d4166be1bd628b2e26d8b928e0e2fb513dec729c61826ed8f2723db4213c802df6c8125054f3775e72a391eeadf805969465cf5dff699ece84726303abc79526d0cc8cc1cdc02bc1305492b227ba17948de59f626c75a903ab72e025bd25a3452363649a1d2011556a625bb67087379bd627338f3bd83b9a805cdaf7c3b1c7025edfa94b13e01971ec509f34e6fdfde117e218d1609abc363e724841b715c704987c5021928bc26830a81f4f5e70893834b2524da69ae022901c416133dca39e715f6c259cc2c1fa47eeccffa2cb4b236de080deb38a34867953b18a077042f44307c166ea0c5a77b5af781a6cb3a421a406aecf0af67f898472ba4db2b4eba08b54252729bdb82c9f4f7c9879128be7cbbad725a98348fbd27269c5008d8d7b9c1e6b377064c4e6101a585a31e8d22f31592ee8f13b5ce84372565beae57e9a9b988c5b11bc38e8165fe76fe5e96b6b6a80b10a5bd8339431728edd36688bd2add489d161eb23c2db4a02d1db5746fe73dc4c434074c2d94b7214d2807a3e9df68e254454c333ff54833ad6ed9885f7c7a9448973bd3a843f7246ffc6a44f042397662b8de67e6e1316596e6741a28d4513df242f7a4287364980c083195d5eddd53d2b4576591c3307db39ba896275a3cc0e664bafe099de3e2511b01af2a3f3bb1d99aa32529a86c29de174fc7a7b6ec4a5bde00044b3df3d351afba83a4e587dbf5615f41cdf20dd0484c6ffac04a41a48f69273ec9da372b25ac8a8b865c768b6dadd067ddddcd006337572e285f9f23f6396f77e3e1c1efdf074589146a0073493ba22b3874d99413313e25669df51b8c83f04f516f072037e368b7618ac60cd2edc3841b3266d0385571ee6f6d34b1f15dd9ad9c83f72cc5ef4995c0a7ac5e4a476c96f3f77c45a76a5ae35d4698e3d4ef28d3ec59b72d0df6052143198522000160be03eed64fa35ff55631636bdb1815a9f1c6069723ef1bcf065c5bad7349341bfabef6b80c021ad52d2523422ccfbfed2417a4272ac993fec802bf1d8c22a380565c9e3f729db5da7bf85567ba08135f37dd8f36439cdc62b539d5d47a668cb112a0a5038ac53793f781b15fce4ebf0a42d86ca2b34824dc31993b666e6ed6148ad0c2d8467a20cdd0119845e4ca8b6f455b3ac6b70be274abfbe7c0c72b38e682ccc9146d480cfab7be6471acb0d211f675e197249671dd1840234327b8b6f1cbdafb9b03683e3698ead3ccb52463fb34f0f4c72f69132f87433ddaa061d09cbc142ca74b83486e59c9dbf093face875133a0728c431506454b12ed1e1bc5be5934318a8af273bfaebb4d071a9884ff6bab38818c077d337e59ac19f3f0d1b8213510af8b57949987e329772cacdb3e718890872aa4cf294af12dfa6c222db6a7262d874362e9ef064043a9430800e0e6398177231c862faaba0e93f9de0898bced5af9fdebbd6bdc5485083e29060046ddd1472ea039dc5e03d144d9d9feac60d56be4f55ae3981719df617bdcaa7fb6bd5f072edf0e5a8b4b4ebdbfb751faf8480d6a49c2fdf01e1326cd761e3042d44c21c72ddb62d6405c55d5f65dac66ead36b2870b1a8bde5ee44db913a440d16304364ac27b196b49d0fce7a011f030a235b4997fd21d2f3d3cc376915513d6b3c88b51c9c86bc3ccf7de2524dc6d0ec82d08aa1122fe879bd5fa83f27b2f4806ae0672b7ada05595d88354a4d0a1e95fcf3977c65365b008cc5fcffd8d9a9d0ef89a72247b95cf0abd2db567758cebce9ffff34c10de929a0b8fe9eb946a538454652be63a7f28dc85dc6fb76ffc14b4722f7bfc943d4a70d83a729b5bd7cd0a79da11d933cf54dded8f1ecd96b84d58872a74d1a5027b7df961e2aed2f29c13de47720c1bb375abadcc86680a76bd5e439a4c3452025c5b6f3e62b8b3c5172523da72d84443afbbc163ab5c453f7350e2abdd844af1f88dacc4aeb9bc8479a130587227afd30ef5e3e263cf9e3ca31a76683da36dd8fe38a78df2af1113cd6f902572c5b4a634c2a2d1ec85b4787823d811450070d94ef6cee67468938a2af39e442b30afbfde8ac83d16a0d34bd87b1e452fc84cff50739d0f00c255a3f40c8a9f0dc707a2775229e5bb5ff0e2b72ee34a077cce352b4b55f01d88628bff6b2c7f1b9618732a06118e1d9c08f50718b44d3df124b973cb756759ef3dbd46f2d12772026842644421d8b92931444a4969491c99cf37b53e64ec22c72df00f25d24f72988f80bb5d695d9c5e8c426bb70ecbeee4463dd79892e40a76cdeb8c60b89272e0c66b9b5f810a486ee3983a24d957a4ade2709ceebf7678f8407a072eb18f726e1d175f31d69ecbbfa3afcbca353f111527b693799971a2e8b4b17e5a66ad22c04f281a8566f65725b1a5e5c9cda603c467260201cf3eac0e1c8c01c84df272dcd254838d11855631be85af1f9b660a845e41b0a1e72f0f6e6de7ee6ec42f6d02b072ab625921c697be5e78346341fa1db61f5d6d187a91579c0685c47cfd72bdd597c60dae14ca9fd59503df3a7e593c6468b9b8e9a0f9f4c4d9688e134a724490a907250287d65257ad6cf16df86fe5a895e3adeaf2ed14e7d4a68d89766765e182d5cdf0b2809b540cb522fbc66b7124a2131146c0bfeb7f624ceae90a726dc5895d2874664f8be92b7cde9e8b0da2f54876ae62106f506db9a0755c1a726dd0744576e93cbbc3fcf569ae3548bd3b4b8c02b2078f3ddf162032d227856d7bcb031f08b5db73295967333381533e38b34e064811796262db3f68d68a937283a8d7fc87b75db122614689e6dad9ef55ec0ef43ef147a0f1f8dcb39bdaab6e8486042de82d3c2d64041a888057a5b2079c4b0ba4d1e3f39a885db0f252927226615130bab6d35afd3d97d6ef21708e4c8a5124b6a576906161b5e002f14772edba94bbcf270b23825b782baa84547b91d63a6aa130310c33f052c0bbde567284b6dadf2bcc5fdaf3f98be39a69cb077b05a15f982d82c02186f18d9e88c92ce2fbbfe74992b0b79a7c918744b482249257d145eeefeefe1ecd2b1ef76c0533ca1fe7960aae11fe60c241b3100a5b25be81c71a5a8d0f1c26b9d902860a4f722fc406cf66d45102d174deab299580e1b890ad81a021e227ad82ae309f583172a795f0ed9ccd630f551451dc4547bd5296be9a4036a004fec2b65260fea9015eb4d9b1494c8c44ac8e21cf100d2c09a15528404a62e49b854fbdda9b4e6c171821fd15e9a3df5c2e307dece7e41afa3b5627f217c288d263c62f33ab13e34272f8e24c47bf5002f2db24d952c4341b9acfacdb78140c151b672d78e0c32c39724b43af1f00b5524779a062288da8aeae4dcdd851ff369c96cbcefadea35e2f70f7724d7769127576c65196b83889a500b424388276e6f3fe849fe500403ddf60b11c283b371b59d2857284f000e6197c0bb21bc3fad52dafcacb3fde376cec72ed41ecbfdc7173140c3f3da1c078366ab843c742b54b94069adbd38b23375c72e0f85f08ee15b9fe1772bdc90ebbe8f7e99769167584d5b21f8e321513528c7253b9206c53436b7967cd49fbf7d67ec39c7606740cfde5931eea9e4d77259672595df86262e9998eba4f460bc1af7f196b9495da5208f1daebd58c235f76a94d5d06a2be239551d85d9503678028ebfd690f01f2f0dd8dbca695f25bfe12d272488d9238b4907ea6085cc92cabfa4de6417d30beeef3088d4bfd746c2b8f2e40f2a1704f7309be298bc0633e6483480efc502b89770911cfe15f132a9118686b17f802c620e3251cf919c9e41f53ea392e65649cd8d9921d9f9729f997d31972eaf5c273614de31da959e85b0c3312184f8b66cd71dc6acff9302c50cad24e725ff56f0b3f27456fb7f79f65474d52ab667a9fee1dd3393dfff8a5a4d635c5722e7758bceeb52846297e84213a3e53bc859331297cebad110fb788fe4b6ef1297400c6afddba4eb594f92d690580c9b5d0c554f44525427c769f3bdf2753aa724a564cedbc96edeaad92c746264f3c50ffe8fff7f62e921cbdb5f4e591d85272a7fe5e5c28beddb24dbeb5c111c65644e8e8762ca219d28b1946dfc77c9669558964b81acbafcf1b3d44bc11fc848c973b77363e992cd5628307b9b3630355724e30d5160360b79346a35eed3943972fb88290b9a5e7cf2a44d428f13040b6661eb9bcd00cb3b6bc26d3cc80f32e48ec2821f3244f7f25fa5ffabdae7a1d667256ade9e937b46bbe329640b0731142fe9d1e2f68281637f0aefdb546f2ea79435a049c8e00a2b158f6268626dacac538d57a51d4449359a54bddd70fc378b8726c83ae2540f800740191c5b0c7899b3aec874f5421a6b6924dce9ed816d04172239a1080dccad94d3688ffc79ad8b20d9aa782e02dbfd1f4f805b8f07c20c47217878c4bb52136537ebc407218f7b024bfc5514c1bd0c5aa3aa522f1b53bc4063ca99fe51bde76a92b7435e11e8867b0f75f3cc50dd3c62c00987f94e31cde5d077fdc2be513319296130a601aee434d1a5c84adb87e88c7fe942ae976f8d47270858c9c36f6a45e138e22210691d39a2ab6b272fcd9e277f612202f1eb4e37258f8368cdb34bbf808b116be076c6c0435e232947e52d437d5c077b2b799522c8a0b81b980ae3fd13d58607906d53369c67a31c4d8dcda20cdeed9dfa2884472680c0c2cd71c513198b3cbbec4da340f2dbefc50b48794ca8271cf3272aae872c4f937be0c752dd6a013a1dc8eb81e1a0fd87a6cb1d8842eec9de6e268b6de61fe0273aa5c676471a756accc299a117edec497bd85094bb5566336a63f9d24727d353ae49477eab58ddc1353e4efc801faa0511f71388c06b8b5e36a758d79185a3dcee3e3429865c68737b847b95222b98dc6f3344ebee163f9a86c34fff572a3f1ebfdc77d503e1796e4a233af503ce485ffde1b4096c4458c3052e58fef72ca9fb45e5b893f3cb2e06ec07db2e5f8c8acc8fcbc56f6b4eab0414d04024572702c07203e91410a8f2bbd475ad2d8adb28b829755bc8c21bf1781a98b5ae253de4829ecd25a67c4682d220c20bed35074ed7d094838b6d0e6ba616b5792e8726ebf91ff9ea05c368897a0dbb58bd0dd9c86cdbdbdafda86b465429414fe6f5e4d0691c69168750e503f303f63240795fe3b2a85f375913be3ed46f3e413e972c063a4f3af1c450fe156ea3a07c346dfd6e08add7ec7c7682dbb39d2a67a794db511f3dae1b241a934b1d8b4955b9658093cafa6dd82e4ef0fb913b825f4c11b1d87f3e7bae22c260c8abad28bfdb1dbd81cf601fdc57fed78b054af59f5a5727d58a5701dde3686d3158326b61d70fe8b2177f2cdecfe9e77a8d74317726048d9b6155d426379fc2143632cea9e54c4b22091ec409a9893b9bea8d792ef8472286d8412256d54fd6ce5b70b50e15516c17a0ca862200b94f42681664178277224418e4e2c28b4889ededc3d8c156fe07afc22f9d6d69f881679274ec464dc720eb6330703e44fe04a6138d71be3ccee60ff0f8cf78fd0c61944230a7c4178725cf53fc03626ce4be8e3cda2653f8f979acb9862c4f544e97a14db5f39e64105052e40fafa62a4b0c4891ef0e0fa1f04a7eed02a63a4fc31270fb8bacb07d2112f251adbc974f72ec49e1c3029528d9197ff891cef2af81a51dbd152d669b972c49ef754d2263efc18997bf652ffbca2e48983ee7b2d6a608bcd1bbfa720d5231eeaf254adb7f9d629294351ae3d348a95fc3488517647f66289c5e20b9c145ae6fd1dbe8870b1b1d31d239a1a8e7637983302484b4a3c1d3ba328ae2ed2a13ac0a841860a79c3d23da4254e754014e6e44da2d37581fbc589f55fae95553672f978c18e6e0ec93b4a88b40265223bf385b0009d3dd8abeb130f507836876c54aa59a618ea0a2b7d8e818f2e94c4024c577a598f29e2d22f669e2793fee33172eb28e0481595f5dee345b814c9dc72c906d98698965f15b0e86d6b05689b8d72ea46bdf47e7f0b67a841ca285ad63ec2d6496ca2112167e129b4ca56c251274d2477a6d829a56292cc60b9879f0a3c3b8cc2819d4399f6d14c9a771da3bd5744762f9677160199ec83ae746e1f12d61d0356264ef561457e54b0eae703268949498c9236de5189f23aa254310452c410b465e3ccf9a2fca7748a1619406a87421058ad2cb094c5845857b2a4208d7deadd4666d3665de24979ce04ffaaaa8d7281f2e00eb096d45d67719daaf30695d68527c0243ea027b5d78443a61989043022b716de89e308de425d719c28681e72b29c54982ccf9c54dbf77375b248e272c92b834efedf43c000bbae0bd8c3d2d3f080eea9ef72244eb560c1683a8cae72ba9cb740f6f99925e0298b69bf53cef450ecbe7f34acde4462d3e5e8a54164720c8926aa9c239befeb8eb05af9eacbb11c8cd16b8fb2aeca783e19fe7254586c8b04581f7c6fa39dd179974d60e8a8efd6d6c85f32c0fee8369ddeb1063fb77280c6b8bcae9ebc9e549419faed549b62510670db43e0533d4e54a243499933729d32df846a5c841b1258d9ba0657371e37996928769d28be6bfbe851eab02472139292e0a2a1ad7b71cf3464156dd5ef809da8ff8ad2c31b15dd677e1d83d172a9441fba3b3596759976539f9733865a7648a05bdff8e965c10b80264a10327274eaa01cf98b830b32c6cfd2ae02298ac58c2f3f2e353c5e19809f3a3c547d7221f6cdfd56bdefb617b932c2c08eb537fbafd43c25a8df530a2a2e3d24c1f723d7ce7a37f18d88b5b562a132a1a51a9d0a8763eb286f68d8f064b67fd828637261e2f4344d77d1fb397e75f0fd02ff520eb65242c030e533a59087af4b2c4719b99a376defbfc3c9ccbb42b92745d0d40d353c6200fc0df66567c81d3a0851728e6967bc1c8e5bf1d001aa2742ef94ccd04c0092c55e1dc53455fdfaffc36f1b97c0b062fff82f1ba1e6ea2c7a2b1599c07bac90a2b06e94147b3200b046d4723ce3e30ac0566db84eba7c0d18e0cda6beeb5bb8f28509fb05fd6b9d46cab94b6dd1fea2aca3e1dee0e0ff0da2b2599e4ed215dcfa4cce521980b64a4c878f728e55b65f655518998023a1d02a6ff120137999d4a063c9f1bdf75bb0acc42d27d5022e4914abed5f638c0b07006166cd812812eb38544c8e38edad051546b0534c4613c265d05fa51d74036594cf5f10d838f62eceb613def060f54be7004072a25487adbf12615bd014934bfe368fd283de362dbda75c5ff5a5630bf9292372d8eb42cbb6bc25af1fb11eba0ed3dd133ea1b653d9be7fdd6f12151d34c2e54ffd711e8c7d3e93c81dae772cb9171c6dbca13965afab25a9e6dc548755029272821afe54d15e2cc5d76584812145fc16e268ad10f7266d9e65315928139e1772ec5b36c36d82f2d34dfcdcf4973340ecbc79bbef0b74b860caaa6f8ca000c3721e44f091914db2a9abebf0d2654c5e1f64a2463a301e4d088d56c562e441a65b895288d30747afc2dec1c8decd01b5323320841f7d9633b58495de9773dc59433e7629ecfb9bf50a69bd17d95657c2b169dc06f633fadd7e61a57592d7310e72e4efd418e230c49718e119d875ccf541f97b408ab07df3f3f896c5be98ab201561b01d1193844684b821e2708fa051167929b8a66dff6a2d75d6be55b13959269b1649df867c1f213c162077391595428f12d6a42360356e92f2a0040283a20845a09e272e6038d8e532c816be45ffb7f810673841690084ed7468990ddc5c72be9f223330de9c877900d8e974e02ba304dd2eec2eeb7c6f17efff0d280afe08ce053ad3ca12486def9d810539763ae852012da364cf7d97f7d01936c9414c729f38e4be7b69304f7aa5057206f7d57a545c5898ff90d4b8459347a3306af1723516b9b1359e812713ef90304b9386b3bd010e83f23ea27a3eb726c1f3193072d2cb11a11288a05c93b61d2d619834336638e021e9e9208098b5065f3dd3ce72008af8a2d1a4462f34dad380d830882dab8167f21969652b8e73a68d599b6272051efb6acbcae7498e17ba261152d8bd4a9190e6806f257c90135e04bd60307291c9d3e717f096b97e34c3b75cd59e66f8967cdedc6215cf058ece53dbacec7201d6ee2c86b2972d5a51f8f80a66c5232971ba7159d885c80c5985e23b594772788db0e0376c0021f06602c9c96a297c24d66ca109e0f553f886ef79e80ac053a24927ee0cb2d351485c669aa59415558f9a267792d39070c14502533c3ae34dfdf4558dd0a4fd0bd1f571ba7f2b39f5f9976f363efd27ee0145d2db4d3ec772ebd666e291e855e10b39d437c7baecb5e715ff9631f26acc87aea4b51d9e2e72a085cd33e10e595e9ab1dde21d8dbbf886b7bae77d594991d5c1042a69b854449c6e2d81458956f93aace51bf758423df3e2a8ada1e92621c8bff07cb5162c6a4788a90035d0313040a0d0e7c35e225aaf20f97f78ff089e88620cbd9aa088724aaaa385e783b95b3d4fd2a548eb599bc73b4dabc1e4f7c53294a00d7d6670729d811cde70d727b0fa3ece302c946630c275cd79e8cb8169e90ba5898b5c2c72144b6325145f3269317afc1a8e4b920f9488fb6cd08036c653f599fa621cf1727ea9494e4113f9ffd5ce5680cd1492f4209e83cdab0f29feac7cdac6c802de726231920ba0fc923322e5c733699bb535c485c09aa1c2a0dbbc5b91a6e530c567e961e8c1d369570e94a4678d4a031f6c6a09f1b918ea56e277edada3efe2f072e1e9c8ae24351f8c9361af6857c31eaf9b632365154d1b798bcc8745d1c46472332803e62f82ea83fae3b55c8730e7546747f66a389bd3b84530ace0ff876656d79037fd35382e9bfbb06241be9d80f1010e0c3f74e98c3d7b15984496bd9872c7b9a73b9c7165233946ab0de92f3a7f5607af6a9544247249382625f83e5a725a59e974fc22f59a6706cd3e4acdc6ab6d787e19683a74c6d8267ee433cda56871ee314db6d78a701daff4aef86bd4e81e56feed80fef56048d89af8f40d15727271a49520a13a30883a9cb774318170d1f5cb450078574617e6bcb027269c7227b9bcfa0df936087d9ff5b92ee38ab69a6a4be980f6e3578304c3445684325cb5a3d071eac635f54738083bdbf91b5fd55c27eebbfa6baa810b3a847e356c298df8cbf025070ff4508246b2329e7f5f2a006277fbc7da4a155bcc37d2c99272b279fa2a97ecf63332557be9c14d8b1754774bf381a0ca3e7134aae664119733042a97aebed0d3e5f63afb3153495d6ffe18e47697b5d4e2ded2356f9b3a0e72ccdcbbb8cd7adc60b235aca3da70bf8ab12906ca6fbad2f5bc9075f33b093f68c81fae0f6a94b1d8fa66e21e0931b2b0ed5945870cdfdb3357e9b06199427a22e19e4c8a327e9a33f1c5115d153b6c12de71ef41591c3fb41b146da560371c72bd0f9dd6a96a641b26eefd52e2753b6e0fd9751b99ae82b2efe97e3efe85f8721f6439781ce9e6df2ea4737adb0597ac65b4165db0e0ff5d776077d8d1f60672de45aa7d2bb14d17583c5c39a10399aa0a9655a12b22782ccd1b79df7e6f3a6e8f676755d9e77691758f1892524c74e776e0932b0f782e567df6467c979eba726acb1e5a41af47c3b43f2aa9127b7ada6fe6ac826d148243e4fc06d7118ab772a34556af456f04a787410ed5778bd2b3f5ceead30a319756e4a311c5d46362720da7d8c9b26939b0f0ddac0afa8fdac929c3275527a149cf135255bca7f69f7297202a5ca2ac1c2ab7e7cac5e21f444d2a3e11fd4acc9659e56076dc7978da4240cb7516567ded782cba46298126e06a3edca1003da86add008bc7a8ce4ae272785ee0e7b096e630e673d8f9f763ef731ba673a47f3683aa88c5fce806d1e76b205341ad9118369a2c6d9cb0fce13c5d83a5f9bb2e28f61c16eb6cfd824b2572186f6f4b448796f103437754fb30ea8b19f63e8ef2d481a51106bd31634b237200bcc3c66a25925336d164061111fe7cf452d894b0d5f77e8d7c136b6030db726abc79a6614d8dd521bde885193b7fe4b620f18570046aa6776b1e78f216fe724cc7989d89d86994b9f92c71c065d0339e7805b99b07caa15ffee0e084e3b972c8beaceac275abd9a28aec98ebe77287b1fc37cd6f4d008527dfed738c419572739ad2c97ce171457bb2dd6eefaa93b7205cce084da04be9214dd0bf9f40884cb9c7486f65807f223303797590139584d2837d4cfe42b527c1380a898ed1983b2e760378e69567f6458586ba6d0f2661982e273fd5824262d0d8d18172d2dd660335c8a5d8157bd9005eae7492e05b22bdf48d75b6c06cb487438442dd863208696b8a5218525d5f476f95085b07627e6d0488bfe5f84290cac22ab37afbb37237a7da54eefacaadd39572d474d36a2ffdaf699a1debd07acc7c04b4b8d35472f77bd9eff25638a43d9f7bde1518fd4a1a2e69b6f70cc701f2c8e1727178686df5376f00cf86c6aba86a562ce5599ba51885b62df8dd0a983e00e0b1c906906e233c03f70b1bb7bacc5b6a742d53434a40e57c2aca9a6859e424dbaba10d0072c04e2f39527d8ba66043d04ec901d790f1fa7651868a1e911527fc5b07f8696a3d27a008dae0e25f5e2e5907a3c7d0d7df43fcdbc3ac67ec06cc50c1b7525772172bd5eedc09fd3d5e43c1e002aaa8f174cb9b37908db282410a24fc89870617787cbbbd172972353875a628d6b710e7f7b931d55a660068eafffa7520cc55728f579a5b002a38623844cc775adfde491f2b80e7f93f85ee144456c8a12b41723b98de7642b4f6078b45eea9b0a5904ff85332d578aa4a9124a48cf1e076c57206ad3910629386ee2b0393cdaf5eac174c5ddae6d4b04fc6a5d350827e457f72361bc3ab8bd2a195a951dcb090c7e8c19781630d72a44d9261d8e3f6d957b1729b5ebc57162d224eb38ca8639a83e802b1386b309ef7b83080fe48031d195b72666de1db45ede0d2b04e99966f5942d7de4dd36fafd1df855071693aeb42312668740222111e30f95db327e88113ffba99189702f784b22149677dbe53b581721db9c26d53aa8f20279facb576509a56671d3567c6076c3e348c8c318d928e7209a7fd45d0160a12bd03fc995df6784fe8eacc424159437a5d4f008ed8580a4328b44f39d90a0e6471c84d01e3eb004b126caea69d0b570a0c21a1ec9d6bc72b09a040321393dcd94be972a8d0f92f33b73e5eefe2572d3cb0ac2c22be79903f2261d1f85bd19a30639494f84d880ba7a8144d57569749f05ac44113e6d797729ba274ba9fde85aec70df5cba272b6b7c74fb5ef694cab042df470b800c55572a17d41b619add036f1e9f8903ba3acf9e1f21f401fcdbe1a3fecfc51969fac7264b90c27bfdf2633f29943679d990c1e3dfba2b5143034273bfb57f16c90036f0d25aea6202b0ccdc6bf3cd4c530af27e9e9d3cc06c01bee9e67ffb9df887072f94d2c2c423b3c4fd708464a1ff3af4d70ccec6c8b65e053ec46b917600a0a3c99319b2a153ef8f76e43ba778e63d1cd2c065e6b1f0140040990efafb25fb072074ca710fdcc5f897fa492e916ad5587834448e72351973768ba0a4490420d723979e591618d62a90caf4e515f78238f53525b1d963316f7f9c5b44ec93a4a0729635490fc016bc3835590374425403f2018a9ae00aefa10cdf98b283d6bc134a1ec7d559ab9aa5e5dfa1046b026ef1ed5f8aa77dc0089d01f57c7193a2cb35a1514fc77345a6e49eb13a7da7228cfddfbc763c176163b2dfbbb44fc2c55b1725bdb1abb4af44ec50fd8967a23fcf6d2032b9b954707e3862438387d57850572a998535d5c7650d67d4254555a85910ae28202303e56dd6a3d5b67a4373cf55478325d22dedaedfe6d0f8906a883e329125f9e535a7071916a385688f79ecd72362df2c2a62d14ec64084a247c759580eeb69ccb7980703b72be205c7a0ade5534cec66cc2b35f0563caaf959a7d65f5b1196900fe0b45a67ae178ea8c25ef6f4eb8e476c1d9ccb6d5c32889dcacef0739c507e0d8759fba4517d722a6e10f72b99b3f6d342acf4e54353d09fe1930eab1eea48438eceaeba7107e33b774327274f4d89230a6e9fb6ca428a6cd52eb98a5135542c3c900e034d0ef3178399309721b82f2d68ba3c29ed61682bcc8afcea87ae01a17e39a27aa68d50de14ced725065c5757dd7c8e7ec8de043529fc5c4e8ed09748fb5fafa1e8cb1e85203fa7208266697e5ddaa361e566f4e5d32cf7d614ca20f7b086185b7142be631ecf8393d749d1505c03cde59e3151b1d9f233b6c4ff13e169ff9df817933ec99125772b5a7e53f2baa314cfd3118df5537bcb3ab0e7213991b6ef869d8b1a46f02ab3a1eb4e108e55ebe31a774fba42bee03a3e4fc6b4e1e3fd0db6b02145dc006732517b1324bfea775f0f2f57de09f84c4287fce82e3e437664ca07997dfcf572d02baeb3f494d4768335345c993a9418866026467038ed57c0f870abcd1c37edb0c8995c833fa693e0c3e6370c04f5e27a9c2517a3f05fe5b78f3485ae0337a9f25afd068b869413634be21c8a62f231c18549d80ef1df285a637a06704f1299f72ffff408e99aca9837a09d7d48015670eacbe2ad6687eb353becf10e4f4226572739238b68ed03575d3bc2eda7fc6fbef0401904c1b57996a29a967e68e151042ed99d9cad4d5d90aa91f82bf4c9f80e5c4026bcc82a5e53cfc775ed843ed3b533c742c99dba29ae8c1a6e1da38c54703f975dab2486e364bc2ec55245ec20e72f2af10e0a03cf383341918751b1e444aca23786303f427c319d8d57183137c72f92d328cf546cdd43ed6bccc2b8627b622fcb473f057023eb653d3ca65803e725170109e680581734542665b90d5a20108cfa965eb3851d66a127d43b9a2744e71bfec12b053e8f2d0c77797a7001ccf16097db4f33d9f1a4f6ed88bbad9072d2f65e7e9013f3c8d306865c594b4aa178d5875925698778dc38c0c48e9ec99723e4b499870ea0b9a8c10185acdd64a0c8db5b5916757ccdc7caf4d43bd91d527bbda49b79c4748a1bc3f561421835eebb97d6d2d5bf1d3904ffdd0f57e6d7e72866d9b40f27a2f9e72fecabd8d383cb85f36e2d6119b8a48ebb91aadaad8f87288bfca623d1bb06c0b486b4695654474dc760a2e238406d9fd18a4b5cdc2e2722dcf2a34b5013b4bdebfc0d22e12c982361bfdf72e72aabe9726e94a38d6715ab6fa5ef8243b692f809f8d44c4b21f67a6e1184bd05ae3eeb54b440febe03b318ddb92595391b876928bf4f11260f580a9d6b7918f2a13b8b3668041eea1e4720a1645849f7e0b8363aa6c4370679896e515213cfe94bfc50bd7cc8a60ee4e72f60025acdcff4cb517aa548641477547e20b976711ef352539c9f06e958a8b7253ace923d49b5852d3449623b632d648041b55b29d3252381368cba50b413c72a6d0de7acbe4b3aed2869d7a746b0efbf6bfaf97e54ee6b6d3351d8df0ad3d6f0bd423b3372ef25f3e0ec8c8f4ec5f5ef2dc5e0b7dbbd26aa2dff016c3d79b7260c332c6adf8282ad63c2acb98d9ef7fab3ff72abd1a4e04eef04750b81d2c72b919d434b0ee1eaa2dfe7dc937def09bf1e7bea985e06630a7fcf5747224b572b2e2a7c0cbdfa0a8609c419dccc5f1b106847cc53630315e1766b7c9f1ad3f72bf069ea7bea0ac1b3e02781b01de5e50f0a9032fa8744054eb00d80e969242698eac24b54714f5e4e8f619ed38ca957672772bcaeff14a733940fa95cb47c272cfea1a06b7aa3ba4ffcf503476b4916a35b4f1a74aa808b3db0374ef832f780a66fd79778b651515ac5eb3cc8d355e425fe0d11d8feb5f4784ee300aa2a2a872a9d7cb2288972b9026d48de835ee2423bff1c8764298e79520e71481859186725b432f1de9246664ea546a6857e1f94d6880a91b70208b6767ead24c8ced1272e1ca870dac352648782be3b3e180475f95b33ff323154471302ed55e5a5403725f8a1bf1d0d62be9b47344850d367386eb52988f3d6080699e23e7aa84aeff724e105dc63a639c99470eadc67900a5847b4755dbe0f30f11f78948a68165ea72cfa408127fd8341621c3207f2c261a768b33ad2ef0756c4bc795e76609bfde6546efb7774b027d285b6c93b217698b9e9c7a090594b79494c521eca2c8a4c1727d0fe9ac3e2acb674e6d8b353a4d619fa5d76972e1dc4b028082d7e606afe43db43eab6a3d4a8186f948146ebe69ecc11889505b8a464fab7d59f34ff673cc720847464948c94d0baf8fdfd05af7a88788657642b8037957c958ad6e65d68b72c41bbbc74629115de26e67cdab2146beedea96b3f807730504ac36308e8dc14d4ac7efd0b5cb91d53367b0e4e452186bc9666303ef25c2964c7a86ac34263039ed7b36cc0ae939de4a5c5facbd42bce1591189ad0d5506f239c81335dea0a657c9bcfaac505282706becef71b05523ac6da422f4797299839dd704ced588c2724f23fa8217ce8279b153ab1454e8fee79fd463261837af02a8bd665d6a4c7a721d8e52c984e74754e80c33a1b7c92be96da6b2624f527811f9a54bc4b3486c7224febace6f1502b35cf76fb1d799d0e7b781aa814559907410832da8460bf45eec77bec243d3262771ad26effed647a3f2be03263d90b244191611943ce71172b995ad379475121169d3e55429054fc8bc7f9d87e8855743fed753d174c25f7290cc2350d45615a759f18d0dab2a05ee5d62595223515aeb2200874f8b87b4723fa0277aab8b930333022d245d16568ebb4be539f1cf340dd7671b7200e69d653bafcb722631f8e86ec29de02bba4768cb2f7e5e4dd7953a771f883b175bb8723cca9784a89d955c9049f26018dc6cd51872e17a3d870140c56d6bd11bdd8a519eafbacd2a87b4c15a5dc00f602092f65a6c44c723e129a0e2966f82338b733e7920efeefcc0a495582aee8f0eb6ad15f8e48845f5a1f75cb9fe34a8fa89c8721e9f553f61f0ccf9acbc66c227f6858c5ac1bd60ae6c7ebe67ab52d9d0222d144df935de39032625da8661ac2ee6fead269a89412612c06863fe7b506be0ae725e29d9b0937d1557abdee9501f8f5841f6d85a7ed7e5cba62127aca18765ea1e9e798c1d0599ec52bd36f21b5826f88ce884aa80bec356777b8e2252c1a0b572432bab1eeed41816f4fc39de51182076ece2a4ceea5ff6fde928d6c293d7b4726127cb8b9a559d0e37f4d706b8e7a4782a6323a7735b8d57c13e640c3e939772722a3a18d3a8f03ee2a4976782cdc548b27408767e6db4c2c54caf84627fd6726ac49752b28c7937354e1998d5f3802b6af6118618d99c62e36e10594dcdd3728be86a1e761baf1acea9deff0b4a4a9ccc0c7e27cc3971ea7f81b1b9c9304c6e0140f27f1e32750077adf4c002f176b7e4ec7d2566e97fccfe4fe6f11369f4727ab4a2763d07bd6be8ff3c79b79dc105e6eba50b10d1912f73fba3b2b335d272e478ea217047b3d4e40f1e93b07a82defba83263d4273bf3a0036d07eeeaa34049a77f71bdf70867c2e5d47227108f2e66622c93d447f4d99ed0a04adc31f272c09fb11b175568c5d5d701b58fac20f69f624cc9774a3e8c519bdbb9169bd81d0fe51d11f35ed0cba32bad6085372bbd4deb58f1b59c69b88866bb610e9c58032a324324c854ffe0fdc49817645bddcdf3a73301d1c7a7e1a8d7642286f9bb5ed9d754ac34b775d6c25c5f50201179c506cc1eb19cd8b9dd32b66f2a38e3d27220f65682989878fcddf5b6339581baee0f567bf8b5bb24e0d0b6f4f8270ac672cf7d3ed21d39f1348bbc900700d8a9c4abe21531e1aa93f298c2ecdec5c29b72616555c4492aedf25ed34156c7a8b1d1b19a0caafb8156ac63820d480a8ce249646819fd690b745ffd8428bf22d6900dd4273f4620cbc9ffafa814584812bd126ade43b3422d64ad44c08ed1623e498992d1529fd36769f6b694909f30dc0c7250c77764cb4c493ea357c600461a2b37d8b5e775d94d95dd744aaf503448db0cee623640665f6b4d83f178578ad1c1b752b86dca491931e3308196d969b34c72f1f40db5094c11044eaec8807b40bb06127493f0867312709834e7a49cf128729e269b4dbba6bca37c6bf6cd8e261b6712038c35549d22084caffa704777ef720a0bf0d3262076fafee8b852dd9c09a1b0f6ccb3fc1d9df4781b2288a3ad293b56f2185837b13494332c461f1c6537f88a1646435537e919d14d054f252ebc220c8f0da4fb7373782ec8b9a42fe44bcec557d4c1ff9bc8db26e4cba8ab968c72d3cd9adf86ca7b80d4815636bad9516be28f8e7309b4bd0c929e2a4809e03254a48767c673f7dd4b37dc6c46bcdd0375fafbac4f28ac77d0f7a6ed0137a1902d31b84e2e53c4571b84b23f8e650e72b289bedba1b3f31d6d3caf8c78cc8ae8727d648a9fd4054655c04cd2b8045c6090251d2c30fb7692a6ec5b58028cbc24721fd7570d30894a3c302927e0a74c1573d03fbe30df84e69f159b0cb8b7d01402aa57e10666ac37991ae886ecef739cd920eee94e664111a98a4daa05650cac118209e1225612dc6aad206aa11ec817bea5cf251ec364a08ad253368deb57ae7212f28b7853da1d72439cb821fa074e63e45f3297702d26cb0cc4ace237d6df23e45e907366c9b874f31f833136b3b1d0879ff488daded944f0177260155cd7723727a80755e02b9c623c757a44210ec780c470896ca343c732f8a3685d69f51ea8faf5494631acd4ec28005a900f6c211b925c49f6a21e3087283144c972b37276079f697845078215e4ae4221e3ca07b33f1d1cf5fcfb8520864bf3fef08f14959936d3fd00944449dda9fee077d0abbef5cb469e64a9be01b4de2febde1b36931fc165fd3e79a69c1c904cedcb1e450dcd91cfc70acec1f337393de518852f2719105354c9426054ff8c779dab05e3f7275c6f93b26495ea34abca2f15aa72172a9d11ef8705f8e1d31cd5e10302b6453362a3e36426b401a4483e72cadd2ff39bd7f62874a3c89073a2ea64e4175cc6424737e2c246bd1e1139430f9e0e72ac6bf5c704236ca8ad2a68f6d10893b52d37d8c79d7405dabda2577ddc21277213e8a904b304f091588ce1697ca5f2c10d0bdebcd963013f0e602f32e5bc4672554af3c0f2eae3b2938ee8826de70d4862080fd0ac3fa77dae60a9a5ea9e6e7278cbff60f0696b574e0592b84acc562b32a981e9347d0ac55024a48ee3bab92ce78773f863a665a08bdff01c3bbbabcb67f939d7bd12c66e566b04ceda1f484f1597f606d689711f04fadfb3bf5a83cb173f4885d91cc76c575521fec5edb87251f65848cd4b21b2b973b788597bfc5d9e074dfe18b943ff2ac4e9b3b8ab7a1195e9552fde71d49e32797a7a5b8b3cd60d51045e9b895b19fea8702fc48efc72a8be85bec2170091cc4c6ac134491c52602af7138bd47523f65b82f9a1908a7237a1cc63996697f2157092316645533d47b9e32d83cc35c69fc8c0a78aee99726639d36c770208a85de3ecbbdbb00b177c4483064447cf42ea79d73520a1e172c4f16f198a563dbb9d2f23239a63d3d5e433624c01532ceddd0fe8e7f4a10216d014a18098221652ea3ceeea18d5092686cc0e00f8c9bdff2b04e6951dfb6e0b400b1900afce0d37c2c7fc18442397b7f5c06858c989df53d0cbc224e1562472c031ffe272738ce5efc824c98494c4209a05172d7c54fba2e82f1996afd2a06f3d534d23910e78fe757ca79da2f83c482ea8ede8746a2154a325763e8ca94c13732c48c4ef335ca44b10e41117222aa02c560c987190cb2ff340122ddc2ad267976b038b230a6864d494ccdf399055e1d09c0a870da0a0581d8c5e3b579c2b72a67693fbbd2f2faa3b154ce4324e80b6aaa662967858604b1cd2e54144c0cb4e17abd5aadece83f93b59c255b74892f46bf0a1a50259400e549421de08c43d165fe7e09475319810b5fab9431122f19b624a4e4fee59c0b8e481aba791050572758a642669fab0ca6847633fc1c6b767d7a857af3ebb3947dbdbf0300ee5ca0a60de1b911e1cc11584710ea1569ec2c8f6543f7b80a51535b364097262bb8a180d76e5218c1bef0bba30fd6ad2fb2689f5084d3f975804a842896bd75f17eb36d2b910818d1de1480c47c9dcd15312cda5482fafcdafe9114cf99ca9cf06fe72bc37afe6b3ee15ee28d8e657ccf58f97081313d291f35756aecafae4df8cec72ce6a2ae93cf26803b231456d35acb64d34c1940efe368b2cec118f72e93127729394be68caa358764e98578ac1ad20ea47c89db4c42097d95f10e9b40110c65fbf154663321d9a7b0635d4b78d2d47268d49ab94b3077fa4af594495aeb17472a7b0ea4fa351f14b0887cf5903a1ee566e75d6d2e35b4146e2ccfa1ff944971dd05929d788297d550dfcc3fd1b76d717906f560a75fc82fee7d6026d4c7845720d12af58d77928c68f7e9563d50705666b1fabb3a0868fb3b928a945af133b545cd640a089a163ae935af69d1657a1b1a93d26f3bc40230eec0314de6fb89d7255d2533a9c5d984df7d30faffa5500937382fac02ce06a399b300f94cf4fcf7297ef1745e3d63968d4abc829257549d468a38c652627c0455b681dfb92bfe17226a566e69343d1268602349f4d133a75d33e033238e55f3a31cc71bcb58fe2721b34a6f13364039badfcc70eac551cc528363cb4c76291249e963ad907c39f72179a4ca9ad0af2ad28c95439fa07d4285e93a27e88320e236555c385720b1d1083b9f8272eb0434c173f813e8e1a1747ead7f22c53304a7d3c62fa5428d05872fd787b08902ae2add42e34546348b12129c1fec42ebf9bf1fdbe3422d23c963e7892bdb342b60c345809ccfadf334ca5a73e5f1069423d16c257dd8a30725906347d2916c6b5ba5bf91bfdc9a2aab4cbdef84353bbfbf90142af4867250440724469513c73ad959351eee2d285b128fdfd66d674368d552b2ae2e8d79be1d52bb4c1e3e59d3bc58ca4adde5cb469c15db1f5777ebb161f268c1b4ae28383f270be37e8f74c0f0380f55439835d059a15e8f338ef8a1f0032ad15eab4938a1602eb74db9ee4e6ea69d0eb1e31e4e97732f3307d834668f2d385e5842bcefdfb72ac78d96b54abcc8bfe721b5cd69ec84c0c7f5df93cfe00ea2584bf5edb6f497224152bbcd20ccef97f60cb577a17c0aa79a2574572d3d1a070bc609ec65b496d624f2bd46065417b114b909ddc3cea76d3a8691bc5bb6af9c5d8dd1e39eb0f42678dbcb52b8fb1d790ed5cf9e2bdd6c413a880d9885bfedfa046cf755a247f4c3705a00e0476cef3dd662b29beac73bb34f342105a43b95346ce04c9368f414e182b563b3af896b5300091613d6b0970d1afbb28bba9484682b01cb9b025d26bb32f3b666b7414192a9204432003fee5a976b473a37e3bd7d100aab64529dd728e9acaf7ce9dac5b6891671ca444430fe6a2b02f7c4e51f4bf4dfcebc4ec604bd6c93e998f9f2c460eedb03ef7bc8b722cdeeae6a22315ac3a7f2742df082c19d3f2f9c350e0e15f3a4161fd77cc91aa74e8abc239f447e320e1f0a4b9b2c10233e64146ceab474f4b4a27a3e7c7e11b8faa30e80260ffcdeff80b652f53300829773b7a6975aca756b213df6f9fa695f779f7214459474706821e9e1c75ce2137309caa2e409c3904ceb37666aa8910658d58a604532c1566eb775b92defe72f7da9d7ddf73074e0ff96503b823ce0f33b4ceb982639e56d9a0be13468f3872ecd11bb8c03f421c68f7ab2ae3f37ad211272eee5c72401a8f7b7e84635f4e722e3a19abeb1773afc285e3522ee7353ae1582dde7c9566166b715ebed5b6cd022a74b32d0dec669c1245aebcb9b2eaab8a3da91c31760285168e27dbb739c772c544c80cb02747efcc6a4611f78bfa8f15a3b7ec6a7d9f652e009bb42d48ba720d9b380ced4e869b7344173d81bd4df2eff1f6279e1250e523e56025d729f73b39027ee68be3fe8dfff3cd676ce9bbf899050088a4804415d60d2b074dcd4972b5f8f6e3c0b500e962e32632766ed0b321a32e89fed77b10c0cbd71d3d12762d2afaf04ff4e6710d8ddb7dc58f09deed274e0606126917b56ce724c0e0ab2b72cdd00d89f48972399d5ca2fdb9eaa36e3cd4401dcecc19ba5ef5d4c6e8d85c72e1edf33011ac8eee0f53ef006f7cb5fb28427deeae30b09181e07ff62a67ff72c14cb7246d1aebfd988b404911851a9cafc27529b638b0d3944aed8bfa557e7232758b7bfa1a888bd7be0faa9b69628e2785a5bd8fbb318c61deaef866bae27120a6fe5f85495a2344b1470ac092025ad56ec92e07460f3f397f02e11d1d5b6b54b91115f8b45be827efb1e4625be46fbb03f76e30a14dceb518728c96a02272d0b58479e549115ad0af42c358852734ebaead21017f7021b8b8ddd0e7c73b72f820a3a1ee56577f50fe68cf7adbcc3579967488fe671474f54f774fe0cc5b7263f4336d0cf52700ac98add18da16767e99af2eb89c531b1fd2e9b328bca716dc77916d6aa4f6e159590628a54020e008c33d7e3bf54fa70293c264f61a367720b3c45a8cbe1a245d91ee831b2d5ef24c6529bc563b481b29279065e2b5cd36653e761389078c056f6906954809f82d4497092054189467233fddba4cb7823146325dc07b30e85a7501f7f5915cc486e60095919219aa121c85c1a89f8151d22a049265cf648b50237751a198faca5d50da265d13123680286e440cabd131a250d9d2aa9a8dadfb4f42e55a1f46ffd24f3c001d0ef7b83025a934bf02154ec1125810b6cbef504c36400e5409d1d13fea87bd70ae312149ef2979873758d1a720a4efb15be89bd87106e207994e98096594b87bd2e948eff4d08cf34bf7987726814702be16f73385df974e287f2fc025f7d72d2783400e391303fa757ce4272b5617bafb946c2270db3040690d530520673085d6ec21f743f4e3d1181a81f727476c1e571ace1bee3861ef1891e4ec37c6d1d9854a6931813173c86e973ed72c6b79f6178ccbb972b502105b91a359d3540af5e1ca3f17e60a6a59909c3517264210ab8ebc0923dfd8a39effafb86893027e14d608c2cdc575e6dff61b56472381dbc915d1e2b73964d905442b5de4546c8139957292f0ff603883cc60bc172edfceca207fb812889fbad5d57acd46371ea17995cbb5e1d90555bf3f9a6817294f143a7f3fa8b7368a1c4ff43ff568f33ab8992f833ae5fda90d5c23282b0728215ec96e1e22fb09cf5cdaad6766c8aa94342e890a82d755b75ad8c2a9d15712942be1d368a57d22a8730185b7eacf46f1c4e8821c4dcca76efba79738d7c5b7d32042238fc2388c91fd535450c9103b67191f80bbb4f74496c85a7f974027294081abdc4148c1592afc9789b3dc4537bad5bd98648e9621222f1a6335fdc729a2254978cd3167dd9b2e14e906fff43de86922d6cdeef31ae64127014474e0d9fb70d3ae2f13916e40a803327ec2e655d9c2c3266237b51d5beb4ba4b57aa7210634f6bd1ea8f72cdc6207f255c37d8ea2bf397899edd5ebbbe7542fe9e2472704b4e1695ef8c8ec13b5048a8fbb761310453b049c76d87cc81f431d8c2c372634f29c84695900debdade9860035c24c316a3d1702222e5ecef4ecdbe3a4572f1ef8b061f777fc07d768ace90c482a80fdcc6d90fb4ee98c4fb20d87dac6d2d009c5f11e8f55bbb00973a7bcd18a5825276601f8478cf00b1ddcab4ffd30c72db5618acb737e85a417a3ce26da326ea66f079a4efa54383b108fefd85ed256e143f1823cd32cd9d712e852d1e7f8083606b8c306576befef0ff8da77315ac72b276749830325ff39bde872284352fe3b0969e56572936011cb9c3a3c870b3519d69a91ea0a8432c0c9692a2f62e6880658811a514b06863ce181612f5acab21ab199aeff793d34f7d162d16c4337ab14db89969d2341e4118c83d291174cd720f476f33ee1208f595fe9cb1d677966c2621fb838e96c9e2a8541052d07cec72e594642723709363672bbd53963aa00ef29fed7d2ce91557cfa5b71add402847bc6bd7d2eef04dcb9c82912beb5148b50c97c59d559b3aba476ffb7744cb5a5e9686a32cc58a5efd428aab972d7a85fdd14218e11bd08623231871a2cd90243583a2ba05b13377dc6b1942f4206ecbceaafcf025725b0f18a7d84d12048145270a2275607361e5c141aed08745f39b686dce98dc0a88a4d5774b3eec96ee4a72893185a1359a936b4e49dc3f02ba93aef1c6f596583741836a58099f331802278da1ae279c47dc495c7b8be169cd16cd35026bbd0012c28092b2d1141ac5387217569a65e388799c8f3995f4df424f9b5d3b78d55c1fa2d88785eebf0215a4189197c538b6286bbe993dd09c8a69055f5d187e8dc990416d10174e32816a17727af9cb0042c9c236c998b4011657215f3ee4cfa17e381638cf1c630819757905da4a3832a8ff759e80f3139d1e393dd653b22bc0e6622f2e7cae1e2ddf7e89729a87ba38f45b828c73dd6573ad237783b8d12c169bb110a0fba31929b1e64b72133a64da3d636b44685bcecefb616ea28641550deb5fcd55935fdbd9e639f772232e34c78ddb28f2a823e2eaf785c243218c874b6320909cf710f934a6892d0bb2b46077fb35bb1be680686133adfa59b76239061235362984dfc25fde853724085d0837d7fba7a5fbe993cbe6dde463cc87c69e274b28746de490b7d5263c722ba786353975a108ae5fd5fc5988d2c47a4fd8a8d9f68afbf8539e6854ca7c72801d15441d21a44117e54ca58bea65d0d2b65d8039a9d60abbc9c57c0ed53672f8a48c54cb52dbc83f06ed3e322241c3cf80eb72f68ae674dde41c986d139502406e212c722b1ddaa9cded0170770b4af15d29c15bebb54fd3aeb80690d26172e3b1cc3cb937b978eaa92fb917dc09439917f6093aa99e0c0cd8a2b6aea060725000f8a5b6e94a1dfe32eb4863f1b55aaa9a4d6dbf45aac606f5311aa6ee16524fc543eba6620e6bbfa96845db5b45f0ee38b235c9be0523b14cc356b2760272ac6fc9f11f3de95ca028947fbc63bf7aac2eb048907fafccfc184184caae14277688d314e6f4fa3a6f2f96646e7387ee9f97aa6ad3cd059f50efa9e76e025a7265874820ad636f672908cac72d6963ce8fd19b7e8c0d624e6936d4efaa4b200a72a9e0558fef5f36076e8459bfe5607847108a52c80f2d8420dd25585df4ff72f57cae1c25495b89030fc143910d69aef691c97ddbfc8b43034a0ea0c581df728567cc103cce6fa847ae82abe01aaffe13d71a3112cb65709fe52f64b23e3e72998c4f1adc241ec9a8922fe003871ae6c48aa09ce69a04c4179d4c4ba25d2d722145d369986efa1c092313da50573008a4490495736b8389710841fcacf1be72b95256cafd128816e07e0206936fa7648e1628660677b4afc566c09fefaa7d72a04a32244d79b8a17f8cf8d3384ddb4f5760017904679e7f2598e47da2701072a06bb0bd7cba0c167f7c09827bf44d263a166e9b09824e8c018ca70b5e89e044f3560072f01be1333255313a7e025522213c8bc3fc4bd20ddef8c46a98cb6353fd37e5c29d51e4f5cdce96b941bd20d9121359b5bda85501025d556fb2ed00147901ddbb501aa6d1da7528fb29a320d4469191bc9cb601d9ee6c8878b5cc5b7235c197a92091de07307b11b21a103adc68af029cb1841eb595ef0e8c9291ce72076dd3eacf4a11ba03aba9198113afe4ddc43add21381d2b89112f0bfebc9a72c7ae74b242c0d302c89ca5e92f8bd4b878f2f960c67229d184ec8bfc4e03987239a90038ae62905d955adba5e3534db2f4e4949998ff7437c778a29e6e1b83515dd488a3e9c9df4705f9c2dc0f26092ffbdc7e4175eb0ec517f3ad51888e59004a20923ea1b3e787acc190c5517569b6de4adf1a7e1fe20ec73e927661cbea721c6f66d4462aacb243e129d32f4b8673df6d6e54ffd371e46f5abf15ef411b72bfa0df5af75118de35d82a686d478ff957a516e007eec25d1cc59793f403b57207423ed16f60b5226fd92cd62ac08e9afc6fc72237252027266a6c1af829bf2c0b851e65ed07929154dd3092d06dfe174d2e4fdd96ed7edfc6645e2fc6a58a7235955eb8c2c89344555f81db15a19eaa588cb04276eb3931e9534165be614072bb0d1ee19b3e125660028ac25bc110864d38bfbafc133d3e3450e90917b38a7218298a4b09ed8d49ca5c19dabb091ea4425d7bc3ce4d6392a2353d751984b92d4181b91d832c0f68b0b3a5fff06439642d674c734d2819826f75aa983c91b072e1192cdb0e6360652e306bdcfc2dfd03a84233b15f0cc86fa97b9a3b5db4bf72be1b2cd791c8db4030ee1961ad0ddc61dc305107c5e312e57f324193d49b977234912bf8cd7e8a0210f725a4ab638e1cc38d243e92f04457da25f3bed13a7c72913a50ef3cb73dd1ba62fd55330cbc52c741df9cf4aab6d6616ba943ee82c672b26908a7b846ed44b112681cc07129d64cdb23238c85f108b947179b4fef9054351029bbb10b9ff1c58ce9d4cc9f5e0a38f8d511664271f488e5d38af4fe53278a7f28effdfb810a215392c6c3a10c52f208ccfe95a63d589360375f8256ed7223910d57f3188cec5a236752660dbbfb1bf5b147478dad8acd371da12d5b1a06ea0c90e6b4fdf4038fb84c86ca60ea8dadf1f6fb146b0d7e1dbb6a9f22c8ec722c9ef17f29af02aeee4dd80c5c0e189e0060b9d923f23422b5be3fa20c2414403564e377cac35ee8206b61bdc6c4378d64864ba0d11a18ac2be0b7525b7cb572e3fcd054bdc66892c41153278542dcdff55fb3e725085e989ca025f5232fb672e33827a0ff72f13ea12864a6e4224f53ab8ae0ff66a13f2120f6abe381abef0828854ab47fa5a09227eecd9fcfe80ccf7576b6c9d92cc6bf0fbf9f1f5b8eba72547cc62a960a91a03ba399bbfcee4a1e8d13e6b4ddf8f93ee49e6e454139fa1debfc6c7343ad724fe32df2664d540a53d3c1bdde567bc5ccf7370c1e618a167260dc658c5b4a90f0ea8c4d63e46de4c5389e2b2a6323e62faf1aca38a5e3887276a4fa96f07080cf9f4b77df15ae8ab0dc8435392c500dacd3d978b68d689e72e0612ad0ade1eaf2c0212f4b90099c9b8af943d14d646b9d9c1d9a6e0898831409701a8eeb135553fd541d3b0f9e01079d6843d078cba6a980ae353a6025be44c5a4b4ab3f3516fe676e7609e35ad8f883353800e05c2f953b2d76366649ba72564b949835f1e5d8121c91ed15e893f154c97235b41693a125b5977ccdb0c772fe4825302e2c7b6c2f1ddb07ec0deb1a0144eccff8312ecb20c3f045c3bee7723c4f36f52c99ccc57c01c8b48f9786ea6ff1853a03d1e113029b31788e94de3f4b1ee89cf56b59056ac755e9584de89559aaf6cf0d70df05db70bf268e23bf630ac1d23d0fdf29b81a7ff0c91ca7426f8f6fe3fc02ad6a7dab7f921b63a0ce3cf1901cce8289e32d5a88de95c311907f3cac15b5f69bf411e9402ad9e5058e14c2fac0550f04dd481026f9356f352a2461719cbd02eb342d450b2279d8571c72cb2a8ccd0076bf9d19ab96269d11bef8c9db4a108b51ce2474ed2b9d3b12677264c1506dfdb9daea2fd5a1b0c66fef5447506516d98f3fc1aa6026cf7042016f3df1a37134621191054cb989f463b4ac1e19d73d7e640837e229616633a77d7276a2302a3dc0f61eb68b4417e35ea48730b219e6c42cb3227a352262ec4d2172347002bf41de7130201ab0cfb61c42ad9d978cd4379df8409eb688d5e96ba772ff60461c340ae1f120a3a429b2ccc489f4f106863aaa817cd5f9a0592f3f827297ca848d37f9807952b217175cdb034fae9d1f250d2b018275bd89f7c6b38a720fe78f0852d927a49ff42f6cedc3366cff90c8ef39219ef005a3300f7aa95921bf028e2ba9ce54142b9560e91f0258dba46460bb01c4f281dcadb4e81e125361963f98dd29a596c9d91a13017c22ed074c6cf9f8ad1d710db1d3e0458ba4c972a48d7c2d83e07653eb023b622e06cf5f51929384dee93ef750988947960e3c7258de56a42bd025f9690d8ccaded3fe346e47c579d424d4f01a47ee6b2ea0cd40ae435c6fab31a1163901cb160221e192ce679ea3f699ca9d6bdcde268f3dd172501419523370beae454b033dc7d42e041d0f86d95b91ec1c0ff1a548ac8121728351c949e34e330107ca763d9b3cd4ecf49fe820b5a5294e0f3642e361fdc8569070b9c2db91a281d20218c2f995fe5da9468e655236fddec8c43490aa34e272ce7ae38812595c65f11819dfed10cd1a320e02d14e192961ed00375b2d84087299a6fc1fdfbed6f9e7a63557375060da8c2e0a834214749b2349bb30aa4b5172ef467ef24bc76424e5469e0cb23d002f79bb9ff9cac4e85896b1058ee1ea6131e88b925dc75bbe66925a9d57cc618024cb628db2da9ffc92d572f8a661b8d872a2b825223ccc85e729e75db27e3bfc83c92aeb056626e97d38f61ce8fb88be460e0ddbdb8c6d57374de3d8a610f0f26aaf9cde0b4d0fec825a509f5f0935d272edb5123d77ebc1e11ce67caad0641e6a29c4eee9ccd887e09c1c104022f61772e12c7799355c08cdcc10470f0d1ad27e8a95edf9637ed8194e52aabca52de372672afd806fb462819f20c734345c782c05297e9877202d03b2ee75ca72b64672878104d42459651631d21a95a2108aaad3c668b4fef466dc6824da2f2bf608224912497363d21cda1178e89f0b7d7cfe567b43a501f9d6ed56be69f8e439e5723468a2835106abaff058385134bbf1e09bdab5a71ddb5f4d06afb66c4ab13172cb8e8ef35cea4b2f3728a3f99f8c0e14bdae4a45b64f8be49eb6cd5a814db21d17f86cb34ea44dadf0827425044040befa17783397d5c33841e84071f672c44bc6b8a095139a22ec1df996a1fe098045cffe5c8a1f86ca11f4a1868c7e64eb7216873ba224237db2ee503896c80f544bcd647080cd648a8dc3f3e9a5104f3d7282cf87d8e4647098f661e73389ce5b334a2fe418cc3fe8f4242eb48830868b359d991808d9153a631486870e4b07e9a056acc46dff6e8ac80001fdb027163e06bbf9071ba03fb88de2961eb0333d400b76dd61bb1debb8520c2f1e6ede910d7261c5268e09e6beacb3331dc247917509d0fdd7c699b54e1dbd65f05db0472e72189cea86f16d5cc3b992b126632bdbb14f5d111fca2ac5438484931eef4715213e92b2970b12b6b60493d3645d70c1022bd2fe87d281f3d48c7450f56c96b42c5c023611c5a6d808bd0b69e9edf567ca73f744f180673699fd9061e38f18ac7297769aa1a1311503a90dec2edff01e8a845ee1e48dd4786ffba3ea2430f7b5725409970b5253313d29870883e8e0ddbc8a65ef0ff104706e0c18dea7c86e0b7262d2bf93e54292ef42586195c964534551266e620663705c1a76f230d5327c45eea02e3abedd0bb9ab0fe4ab58ea61355690289cf3a2b38c4c50d39f664657642fd945be6a12a99df7b603629826a1e82871b789a0cd46f7d549450fb7063d033a27d909b228bbc70307b0286a1036876c745d6270dd24cafcb7fc327b39de725520db1c763b3ca6c0bc64efe9654ba6d33d9bf2e8fb7adf242ccad04bdfa159070d3d49554b5641be2c5aa36ba74a9c17aafd18baa7b17f34790872110a1521f59becc817fd111fac3bfc253eabf7a721c8af2a0318de42e372f1948bb93451d31f991c4b4649b9840aaf8757689856b4ade9ba787b221071e47066174065118974c36fb302c5f2c2eb1dea8500645138ae1580c9eec847a2d2cd42b1b4ef5304ba1d04e75ea1fc2534675f943cddd761314f8d9f4712866923e218b54d717257db93b12b190662a65e3d1b9329e14bd54ce98d9b5efa37829deaded061605f3323428129be6a1b556286242772b44272ff11845c6749deb29a6800be67774448a85e9764789f75415e82a50c2c7851628be24c5b8b4fe86a72c433cf9125342f81d46d682a0e8c5dfc48f0096dbfcd54db1982da066f5fc1a00b35ce040772a77afeae1034e69bd0f0d044c6bc395410093b1bb9f3056f6c48d4a87f2a197214087b204b2a5fd4af7339a589d7b46f5cafe754c23ff1c5da4581b9bc89cd72dc25047b129b3f15fb9a4fe9a0610160ec85a59458d549988d094da9e00db726a9a6d47e19382b511754c5a6e9f44b373e2a408b60c142b5085e7d28dd72e172cd23337d63f6df271ff6bc32a93ca81a2e93429d4fac0a62f46a657a1acbb315baa2019d1248d4139969ef3c44fb494642f5636a72aa3fbba7baa2bac0f9eb7259adc0d71d2ca11b43871196fcda6146663e981c6f56e841a26280b750588d72b7b5c2ef5486c8e8677f3b2889ec3f612bf4cb363ce3f704b642795133fb125281b89653b8ffecfd09eeffe65fafe480380b17e590196c7249cea44e83d63e63ac0d29f7dd551f786b61f40212171dcba4bb162dd6580fd18208428cb505a7722f4883fe8bbe55c0b00790549d1d5b7921cab88f53250db0cecef82f14ef3300591f1af75ed2fe1c7a9e1e48ecbd144e29f3ea7420cbbdfe13fb920a667e1917d623f53ca495eb90e095e609b4f5bf015f0e602cc7108a6bb435b19a1b6b592ffc8fe52d29ed2b31be801dd346cf164bb20f9cda05c0ebde3ebca7319a998072341caca3108c205e18498cbc250f19f925c07caad550642a7085cfc29dc8eb729f43020203922616d57aad7a8ad398e492322ac0b7f11c633d5c46d902a9b472eec9e0bb9977e4691299d3fbb5ec2b3fe1d61ad5929cd629a285b1eb134697722644f158f21bce0099a048807e755f27920bdf8fe996c1912602a921404b4443fa1b539f7d0dd7e8a92cc35e2b97408c3ed348d3715e6c8c212365816e5605213c31589607f7284b45faa4ba2fa31629fb8798bd5d160df28666b997b5d3fa72946fe4099da228a6674f3d874460005e0abe5d77f100b16fa2905d41abda415c54975bd237f21953aa5951bf947028c40bfbb46ef9fee67fb6c96d0f8fe29935711fec7df1f79b42c519252722ef503f0a868a91fb12dc48678e36f6c8cc183a52a015b1fb7a58d78cf7425c41d24c88c875ec36e0beb325d9f7dc8fdd830d72a20de6dc4663c3fc2444bc0650ad741d458be929115e1f1ff275594f1de5c5723af12671567f8ffdff4fd7b8e1915fb892899b6ff7924411561801907a3088723e601f1239e7cdfba8092eb305cdc92bb920116a503ed8b7b6f4ea05a737e101bf44f4612918c6e312745a8ab5708683960897790c34833f22454c3f0bc0a34ad11bb2f787d32a35091459086ffd289e66db229462823cd79ce43528ab132872844b303a5949befe6870f237e486744cbc3c3b4e6ad2661f308a5e70e41ee730e4d77040f078a094106f8cddd7f2761dacedcbc8ea9e62c6158581ed478e334aa7c58781e18eebf6c31562d93512fecd540e2608ab2048f824d0d0f32ffc08726905ba482501c3b561ffc5418c6e40a1c3b855458caceb8d04a9114709c9db0f71c0add761f2e842810363f372d1e4775c3b854c0a06e2659f0cf87b5ddf99724325fe7ea9b46d5d75a0c63f9b73fee810b8de7f0236c60f8ec3e48c7dd6de72d26aba3eb395ec07c9fd5589c5fea90a5f52dfdd72dbe4aae2c7fdeadd5bcd0ed5e977b40b013e9913d9a636786ca7a7136be056ce2f1bf043e7fe205733904c96e952c9ae18d0a98cdd5398ff9263cfa0bce1c7a7c5ad40bf4c6b326fa30972c1df6bb0f4bb46abfb057e96831f3ea7c0cca780302727eac81218546b644528a1b6ebaf4abfde7214e065733aefeb7bf9afbb83d5ec636a36667ef3fcd9cd7255f41f7667cedfa7b600e4e5e0a3cf456c53a33ef0af367ff96134cfd7bd427211c530a7c23c6f5edcc98cb3a8ae775442eba49efad4777e79aee3028cdba4666fd7ba04f892f43c3bb6eadcf8f882c56431c6ebbe2531c07575205e3565f565226dfaab440f9d365cd388e9c885e9f528396086da71ae1fc35da9b825270172b93899469b55836441eb1ff6afea9a9246b84e79a0f66f8af4581d812395340988cd09431d2726724a5b6edd953287e66193f98012dd37835625145bf880097260acec8384679d3dd1dfbebd7d2e542201c1c34e60344ae9a02de9954ab993728ebcb23addb35f85558899371371cc68d1a0af4de90c15b880adcfd56ac180661d914fc7c2c0d7bb5f914ff1d77a3ce927e49f42a19af136135ca9dba383210e84642836da1fbcae5582a073ef211343073147b61d19645ff8459afd4472317221934842fabf85b44e697b9425d422c594313a8f34c6c8b06f7c6cfb985a6b727d3a3ed44b902bf4dfcee99aeae48aef550f4cf4fbe2da86df304825397efd72c801d348be4fa09f892dd75b9d958f21b04c8b0d868321357cffbb8a8b6cb161ac4460badb9acfb439dab89f04d1e818892a3f51cac12e1ddbc1936f32430330d25e3c42f02b215682c0d3a1aac11a6fea077d61b6599d256257bdc5b5ca214aa32271ab06b11458f23492b041807da2c4ae69d9946321c74fd3c5118961705284a19a7145d3c19c8646cf36f53df953f0fcd7a2d8e924226749940aff865c72e04ca290a01754426285d663bf7607f85c8f40c648fed3005eec4d7863da602a29d2928ec0e0b019a614ab98078e88329bfd8fad05dc5eaac7460f53955eb259c6c4c2ec98a11742039da42703332368a5a00f292c8e013daef608d6f5d53e4cc1382e14ab980c6d2dc885fc91dba4c9d36ed98e137290affe32d430266136721e32d5e7e15213d1a50012ea2481afa673212295cdae0ec0a4275798c944a90cbc933810a15913434351328deb253995841f59aa2b9c2730b1c7252ae2761072a98ab56416dfc5a7be0a801f2349c79dfc7f6f15b39034668fde7dc485f1af728f4e285b2c678ff4ba45562ef9b07644e5559b63db2fb779bf5c1875cb07193c65f614a728b7157b06b4e406ac5faba536d40a28edd5e0ca5e33ef844f76b26f2df118e93dfa887229c6cb7348687f25ad29b4362e09588d0b4d705d97b59049665c38e4973b5f944d75c0e3d0706174f2e39189fb99942d59b087a4b2a82972cd47240f62d7b56f322028290d5072cf665dc3a8bcdbc8c8afa66b3d9978d972bea7c59aee72c2bfc92c9e5339c7d93a128f3e5d50c5496ee2083c35da80f9728b02eb300fc9e5f6481e05ba80651f50c98a082df52bd89b1a2701315301e35d80dd28d739ba371a467bc805be342e12d7ed3e33f0b5c91796113fce4db46a542ac14226a901fc45c977c51cef03f85bbf42785bf609c2280536ea6d00d900725c445388f1c10cc95cc1ff852e99b7d568eb5e3bc55a026dee8e04cb29a26b72c4a40c65aaa7eac2fb22e6174fbe73521beec03b062c03629e3736f6746b5d4cc5b8636040e64ceebd040ec9ff5e7340213564c9eccdafe14a32a6ff89d03672e45a8534500f26fd9311aa9560834a034f03fcbdb823e899fb9149ae0eed4072e55fe90a2bc337897315137f0d2b784f9d66081da83ac031f5146851be63df116aa7d68a988d41ca91389d61e84807db29c139f84c50ab1c7a9368ba7a250649eacb45820d8f47427f179cc4e1f74ec6cbffcb2e1e99c19762b9660e9ffe05720d1ab721334abf62b4e7e91576839eb57595df850c7a957002468e60e82fe772661015b7877d79936f93d760b2a91f7cd15608531639adf8a1e5392d93288d72ccb3f36e8c5529377792cf6cc8cfca8902944846e242682ea428ada28214a272b4e90c90281ab1201e8fd3d6bef16a615d37a2e24a4b288b645df1f12f8b7e72026edefcc89f39ea3792b448c51a2c7a8230d03ae68b54aafa1c9f8ec52a33721d539cef083874c9daf3f92100b7f4a9e0f901e08605228a204bd754d7f69272ea6408e6254b9bb7007902233394ba0ec6780166bdaa7cc36c8c941732d001726b008c46e23a51a572d6d4f00d59209a93f1894c2d0a0fb17e651f5a624c8a72f7c9324662c872eee8bd6e1a542c2d07091931cdc2ac0f609ab0cbec8658d572846dae4afcb9315bcfccdd702ae6a26f6523200ef1c30735944060e46b37740b19bef79cdfdc2f1c3330e7e39f11600b26ad2436f6e611538baf50266fd6b772f2999e5aa54c6e316bc74ec513fd4de2db7b5fe386d9f3668d97157b60453a333390d3206f7d2c399b281968773099fd1db837470d18204382ba03cfe1211f3b7ec11161670ee34f77137986a085c44425786386805e9a32338075bcb52a39729770a5dfc39256f90f0bc9ac327df086cbe548eb18b0e3b6c8acb9699cda501436418c8cf270f0db7506bc1242e03816fe9982679ec2338e8a952dfec59f21420933d581b9ad3b3af8198097bf38d0432b9aaee6eed5299594e91ce59d80700995043c2ab103e4bdd54850584aa3e4c12e1c333230a66c22453022a37f6924727dd3b64b5cc9e964c0c5ba46efc1a22b7b8e78253d57cff8d6d01dbc7533e6726fadb4cb01a612a1964a83a63d36012293c1ede2ef260c6747a5df257268d872a1dc6158855e52d1d9cc991987f18c81ea62b37d74588038bbba2ab790d3ab24f0b0c0ecfdb9d70082b46036b1131af31f908186af5ad17d137ce2a01146e2726709601facc49ce8f9882a184e73a56c13acaa440d497eccd00b943215907d01cc29bc8b0615346d63f89920c3293cc78290cef739f0b1af46a069478c67e172922e918436722e16c25adfad7857efa3e2c0dc4b91ea75f9ece9a0c0b5f97a72ed8371fcc35c967dc68342ce38a935f40756c37fe6a04fcbba2aa3dc4410117290687d36061106c161960d293dadc4fc90e0811e0552b9e4dde7b766a8bdd7728fa3d7502c3d1b8e03a749f6944d592743a734bf463a7d4331c393fe1266bf72043928c20ea6311c9b0854dc0aa429b6e9b4861f8a5e39b6c529ca5517ac5d6711ec051fe5689ab8d86b0226485809d62b5957ab670c1d288560fe6b9bfd815c0ff361c817d9acf652e70caab1a6175d4db0428945855ec7a107ecb76af10672adc5079ebf75f1e4adc3cde823f62f736608bf78ab41bd8c1cc8269b07d4b628e1d3b36b4f6a60966151a79faa6b264a2e31e7f03468f0819c0cd33423c4e137c7ac31de10648130f0bfce15dc3f0622e12ed7acf5e2ce308ea00ea5b357a6724e795051d22001be1f03a72683129d03138e993fa20a6968294629acad849e726d31cba268d45228465e3f5125fe1b587f1befa926a537e9b6445e46511fca720974abbbc81abd790542cae5150a5d25cb402d7b2e0538cddaa5fe696c058e63ed7442eb8cc7586711d7ed4eb8f30452570880cd4f5ae196df0b2ffef8744c7273205649ad4695ed27e170f5d58172f5b00512b5c80cd6a22067590b00157f232262257c2f87b6acc7de4ca3a741635342fc655904858a0e9cc87e4c1129480c2463850f6a631fe93dbe6abb276a2e6104cbf3b6b427c24bebbb4283ca2d3472c94eeb5e38d4b1ef83697dc929b1c42f1bf27b0199a2fd4115830b5fd475047284876af3bd62af510fd57f5ffd1d22fbf358b80d2f3bdf69cb36d69da4d76027293bf94c7dde8b66c66be569ecd2eed6618836456d04e3d9eaa9c2ad1175184d5596dc796630ff3a52e4416d4164a688ec427a93fb3e2b320a627ff56d77e37240382293672d3976e7b834926c39ed57504323ee5456509837181bf5d8604472282ccc01a22530e5bc1c27e6abe4da249332bc2e401dcc695fd4b1525c5df823470cf75bd2832fb428bd5510ccce4304aa3172834af8ddd63f61cdeab75a467111ac2be43fd034aab75597eee319308e7db2f484887f9684e2d561ddd950387211758ee56b06d0dfc1f1d72d3ff6fa08e6a06df9d0459dbfc79abe0b478ac67248e26d66007b19042cc816b42d0d28e10814c50ad098304f7256f0c2ed5c4d728eef7d11e07a16afce917ddba03457ebfddcd8c256f6925873d7c8b20a5a5872304495243b8b7c25cb85eefcda9aa25dfeac5e5bd2d36ec9ff1d2c3653be8372c578ac86bc9898e9dabaa245ca40e6076cd705a4b93baf19e49ce3addee586720914bc52b1d2806d1d79982947b218825223ba965836a20b58d7595abc22a62b01041300c313ead9498037728e3520ce393e0b4830dd1748a8134c54f9bef702de7b8630e335be32aada41fb0e5b8333e1ee3c9fe5a7855e8498b21996df63723dd92e0489ef0b44fc97e8327dbdc73d314aecffb42bf792bee5634491de47726d7420b34836a3eeb547842598ba0ab39df150ed68d186e6745c3b93bc9e2e4e6387c3f0295a124947f03cc98b4f19dc8f48f20a2bfd7c458856eedaf3ec1849dbfd2d18e44f28cac9421f15661c496602182e46afc2d336231e1d9d619db60734ee3506eb637c79a053a046cd88368a8d00b40ecf90c31deb6c3a8ea29eb8706ba91af0fe02f697dfcac65b1ddcaf11987908b3c9d7c8769b7d339b8e22eb7205b3320b4873e0b456103732afa4da3590bf123df91e03e84bd76c8ea487437213873f02a204cc9913ef64e27a05d34fc57cde5ab8a00db250114ac4f759315f3787ed5b3db66c964948d833a9660f63e8daccab463f9745a74b7a9ac2a38c721d75c975bcbb47ea532143df965080b1c79181eca3a50fd711039d7aa62b3772c7399d14073c4710ec5485493e653e9229959d936d97f372080749b43145887238bf8b5d52dce05a60e4385c5f58c250cf87ea85f560a545f212e6603c72057223b5efcc0ccac10adc1fd9a2a20411b660d6eeee4b1697d6a6f6902a56b1895d96861f2e979ace067f7a7cbf0cbcc1cbab52aade0f9df27425dcdd4d24b6ab72eddf6da5caabdebd2c6fb55c8bd6d15568629bde68b61e33486ae1c0ef539f72fa1e3d92f7e70c2659b929139c343f6f28d9fe291e8e41d6acf601c20ac1cd0a102f60e67687a02206fa9b99eb2f54ce4de626308c5483157aeccd5872ea2b123cf1dbde9f743c5922da39efc09cec4c275d0f551f5dbb4bfca30d3e2551c1723a8fbc9de685462362429cf0a46e3568a6fdb594eb16b02d94ffece91bd1e27241e21e7db9a30c3bdd66cb14825eb4ca55b8d577de67060bcbc7dff0995e3772f6ebe6e46828aea01e3fad9ae7e8b76a438e308faa7f0e25aaa638999eabe44089928f2a46a5ab672e2e7ff30f82fa5fb5d8bb58e21840de24300fc2f6a41c4fe64364029e72141b971aca46fa2ea016c7d5889811b58b09d9e598eea04a3929ed9def26eefb11443436a458a3d77c765015e3dc7bac84d42ccd2ab726c2277236ee846212127ed4e44f14383652b3a488efb3c2989866950cdbbf93cbae212a5d60cc7038adbff0fcb80d94eed161ad73e02ae7207461b6e06adde5bd4c137240b745a0424f52a91fdd94afc1a62c17ff0ce435027005dd8f9d5abc360e9872e780eb4418b28d20ecc150679361eb967311fccb5207248b0ca662daef809b72a223fdaac2b24ff374afb70824b0428fb5c51fb6439c3e27de4c7af9f6138e72fabcfedf61f2cef68824e78eebb971e098ed8afbf25e195e9292c0709a7b820d1e3611d05679a9d3a122e9325a523c65f1695ca41d5f0cd9d4a3ae8766b79672fd2d7e1f8f07f45fd0c4dba3649528ccacd1b9114ded2f3d6bc9b9d964a2fb004d79d74cb6487f894f1d41b44f58e0de173c4451b1445969540e6c69ab60ee42c82c0a782a01f1f30d17c6fefc64ad14264c7f4b4b5b47a1dd1e2d35623a4572de853d0446fda0823419705018a871dd1f4e587bce3a7a0dfa9409d3c72a4d0878e589ae833e2a319e3783b31ad561b9bc909e1b595bf9a9ebccf985f2a8207202ad078d2361814599f13e3b3fdc4c87207bfd89edf143acbc53fa54dd947f3fc5048e393d987db2201140d33e65107e2009e3c898f5c9fd373e616290603d72508050a34c53eddc5bb856a71c8c7c01fd9a73db511997efad6d017cebddab722735644649aedeae28943702e06aa5e1864c66cc5d7ddf7a0edb9f51a8520d727f976b2ce248b990ea991de42e99b20efd99dcb70dd06128d6247129c8b1d7242a7bcc2ceef73a2bed96ce91dbf5d3258c23971805f04cf77872c9d7572622728b680bf0c43f60000041c62c3bfa9ffe19ed5f4455407830232a1512c5c00772fcd938843ae85820c170980e05c9546b69c8888916e37da7ab2459735a6ef75899e8ad7bbe24d927da31fce8ca3ee951e22f028e2c3692572926185fd6e4f37225460f5b9df25106e3c4917a9081459f6c5fb35fecd3ccb08e0245a7ee048172201a4913b221b98d2c019ad42e23212377e8e4786451474b4b41e778478aeb72a8595bb384084a2a4bddbdf88a6681ab5ca9da92508d5646582a5fe51361cc72dd0f7c13a3b091a33833053b1d2a58b947f072c5aa9f5127eefd0b77f48eb272fda1b9afb40b28bae0b7ec602557483483a581f2a2c060e0ec53fc0bedd83372d193776e29fb82762b3334d7d6a202a7d1c3c9621e1ccd0f537416d59305875bf8231cb2cb782195fc7954a492c5145c70e50741c2166bbb6f57348f2f159d70efb73c5cdb3ebf02cb024335aed1dd40df3c3b9d21f8328250775ad43268c872135f0f583cc7fc75dfb799db8f4806c4e7f2dfdef7123fba0f527c6fcbd04572053bc48556bbdd8d77bb62ea09a49c7c2acf018295b814ff8d001afe11e02469fa10b90fa8366572e3e0fad251c282192a313075ee839127a5b9730d731c4933ecdcaefb72a3d26360dbf5cf635fa14cd5471380ee950ac51dd1b23e7ea28628e0dfe0be809b2fb7625438a50260c12a719a078bb15df673e48f8bfe3ca0ee0ce7dd572b07e3c78b468f5cc117e4f0926332dab29c58569d867481481b23ca5ffe76d9ecdb72eefb956b415a38379b71f5a8146714a381e90b01d0de3c1aaa721c1df7875bca4ee1de803416105d5e12f94a0cde3505f9a2164542af4ec4a50cd566d5cdf4c5fbee16b759b91c92d16ead6da65adebea9fdfe5196ef40b6c2723436a0b88e971342ea90c9e996f47472799896ae2e20826663f12b9a7b469d72f0f859d8607491af918f89fd86494b0a6b0cbcefbc579b7a00f059d22c162c31b4d042436663b926852fe17fe3dc6b2df0b6f325919b7418b151fc51a89f23720b10e8c3b56975ef626ad04420fcc0b6dc05d6f4acacb3678eded9e9299785729e5074bb0b39de831a593587fe1bedaeb38b873526f8aba332e944e540c57372cf598d9d997e7ddee8486c983ac02aeab8011c3217c11e8e194e8f788dcad9721cbf0eb43882a2dcb9909020699a271a8711e84b87f01feed100167894ad8131ed843cc350baf8a301dcb45632a50a77163d5799e79652affe8029da18c76e72eb483adef38200852d0feacd2d7cd45da4e85bec3bbef098a560298c42847572e3d33a3605b2d6f7d001134010188aa63a8f2f6595090d84ca783395345e7772b6efd3c8600ac0df0d2d46217fecb596530f58c47ad282aa82c580154e3b5c2015db77b20389f4a044c6f9851e4c530df66234396ecb58fa0701ca8d4ba0977256a0d8feb4843ba9d30307fc2f2941b2f2b9acecf714c53269acf45eee0a3d546acf16a222f8a6dd27f1be02bc5f009667abf95a7bef302ebfa5df069b4d30616703ffdccdb2e4a8036cee9eca0f96ef5425b97021db9673402ce0d4246b8a72cc2c53df5f1468aae3f0f7bd8ad48ff27d3d607255bcb360012dda1c32505672c77dd77c13ce15bdcd7ae0f7b20f488c41e8b5435e6d06754ef3dceaa2a6de7207b4034fe236073a1c684d4e9b28804824103a995f486cfcabbb6c472b98c84e9ba73f0deb4fffcc91028e6ab370e69f7b3bf98fe08d0397218077906dad186d83e4218f1e20b4cc04bf12297b699749996ce0a8514f663727ada33ec1313b721d8f2b8b8bce69d556afea9b190e772f306db04e6c8d2103745be3b572147872ad7de41118c35e7e60fca1478fee24bb91098871cdc26e9594e7109dc7dc26729f4a05e6ae7e51432aa6d5624045864f60ed88beba6fefbdfb653d4f3c03a06a1183089dd53951e0e412b83934b72994d1338906e3c97e78a8a2c786da2a996f27cbd5f39668229ee0f37ae50839530a38269644c3d2cba8adc824e9d68a0d0e97749a63d9f34a874f167e36d5208249be6b5bdb5e43616d34a637b925ee5a11bcf31434b1e0918a8a05aefab98bec84a712767f1e2897d7b793712ad36b4972a9614b75dfdcc5ca72b64cc1a9a3c544896cba7910c42ea9592482e3d1e51372cc356a1c5d374f409782f51e973d7c356853c60f388e96a5d9d09d8871a10530a0d8e46b5eda6ea37e0a4aff538fc7dbe503f8002e6e131bbafefc1c052c34664a698c325173e7c83c68c77b10414f5bfa056a5fe308f3a992d91f1108c8c1723b91bbdfd170955d19f0dd1afbe33833cb83ff661b3d01c4346c032d58c6fa7294cb6ac28ce14fb4df1936e73f79c5914f584b1dba2a5cffa81484fe2a553459f0c965b92964a6983269ce2a3c37286354c37ac4805024f59742fc362ca22538bc8a89ff4b4c38d12d1f059af6106a6c02356ab6f1939f221ef0b385b531080e51e747398713d8777933f78970bc1f888a920c8f24f52df98b0d51e058997c72a0e78d994079981881826712f94e9b5f321c2f9bf46c346f20e5d8ddd46c0b72395f4b9edd5dfd9cb6c34f6f5f08e899db59c3284bc2199636f527540af3dd3d418189d969eba9d5cddb59fcf8aa9cccc477e41260eb1b9c368487634b5b853480bfbf5c87b8b0755b17d78c0d20830e82b5d40db6b9173f4632e4beaa22f9081a9c937acc140e269b5b6e666c2fd235d34544265c21eb6378f6f12fa632ea725d9bb0b2e8f44942d36ab75bb421db6c098e569448e08e8e6c1bf4017057a97227fa9994d63965da793f8086e2ca1d2f824d57efcf6cb1b1df776385b372d17272c7d637e062cdd7061b36f4935883c94cf707d0e56d66013c53ceaf09dc2572df8a4c10949f5303899aba16dcc34bd9e6c9ffd1c257180eff2e4af6cc0a4c72f3e926982377ffeb5b3a76107680ef9c284b0d6fc5def949026edfe9cd71a41ba3fa65a0122095e56ad1e23c2cf8e9d2eeb3f446d21215d94f20e5136aa40d727c0341e306e46e5d7d78f1951b3b0c9be864dc0bc73f2bbf1f08fc1d78cc7a726baeff318ba664afddc47e67f04aabe198add82c3f22dd46e73fbc883a869572245bc2ab3e5b88531d1af0ef535dd9a37fcf4ed2e8c974028ab274a126dae572ec9f32c9b1945b8086427a31a1f33f93422ad0658c1884395f51582384a9d10b1ceaaeefb73330b36281e3c773bab887d32441fe319dc266a219abfd5e3aea7282e53b6187330c2fc8d94161be5462ad83bab5452b2d5908e2e518452ed0cd46ab93648f68636bd7034d286afd35e16bb620ba60c443e033787179f186f08d3eb0b5198610e62e736d2d28be8c00d8d3abfc9a13ebb6bc8df2bc163bd8cf8d15b44416cc89e50be0f80ce5716cc861215799638029b438d4c95ca370b87151726208298727f11c504c25f9c838f7fc4aa9aa75493cdc8eda25cc299f4753e4300e946cdfae718bf15942262e1dde7034b82d45c62cae957285531e67a064e5724deebf05f446ae883640be4b25828b7c41f34e29dfceee77e0d4d37c53ae817288d04ae2b72c9ba968412795ffca3d56c3bed4e923262be2033af7df0f7d413b4ac921e634db5f9304dd6bb84f6baf829de73735e58cf381a1a260a903ad0f053e7657a1735b851c71d8268922cb902180eedde0f976c7a513bc35232c935a728f822c50aa9f86597f98c3d6907539bdcd390c39e737ec6d1cfe8714a685df72d5666fda84cfe52d1655ad9e633fd80390c6732c41260699f3fa954fddb73b726f18e4121bebad5ca859943298ba5d3beaac5fb019c7e55ec59cdb0739943c72451bfba4c13d8a2682af296b8c716db28584553a2b1ae806868e22854745e572540be7c842f9fa6adb3940f43e24a6dc27e6fd42b6012bfe8b0e3b3f3b1079133d96c12653640ecc636b7ab5f1800b86f0c4670396e99e2a4609a5ec44d4057255e15310f9120aee27983a862061ad40f9e13116a3044cf2e5908bc8e2edb046e171c9aacb1394d1ef91f2cf7ba248bb07a14f8a316a06b9cb5b924db1a62672409a093108689bd46d6cebbbfd274554d817c3cede6ebc693939d3245cf4087233300de59506f2eee3955188f7790c6cee97a41874e6796cb22495ad0830e2723d4f193cd32212a8461b9c8c28a282ad70ece943aa002d6a584fd654f6dd04702c263df6b557e755c52c56f461c386f184bc94b393bf01c8d77f211897e4e7729d49144cdd4af70c9a1abfabf921316de6087b57f9e9010540b0112cf93621727d8e5c3598c15ee8ec3e8fd5bd8b8c3ae6b6c494e0934e9a8bcaf937c13a047217e463e77b757561388c5648044eab114ae27968e51431e981d1268cea480172c7caff4675692c9573bac30ba71a4b7e1dc5e1b777ab736321168f5b0fd80e4da6c43b97272ecee8a04aa8e0c3a37b2832aa51286283be975fc7459bd87b5b7295c9bd6bae91a1a2ad54f5b5008dc4ba98163b6936672e252e2137c358abd172701a34bbe027b828129c0ddd3c12204f90dbf56938ee062af81a1f0c608142245ec60fef94ec88ca3118759c8a98103ceb4d8dafb517fae2e0e766c9481be1633c3a05144c10292af1128bdd929bd49e57e73eaa67b1c8d2fe03e188430e7972b8cf5f586f092f3fa322e1a4ea4bfd12827c5e0e8b41d585266c69c5c62afb3a19c998a64dbac569aa5c7ce1f9cc1c9d047d74ada627a6870b7fe0c2e4fe0b727ec10eadef33f3af66d151d4fc6e97e658240daecb91cfcf1ac10e8b0a76f259033031ed75a2397a6991ba061cd4ad083f9d72fb4d0cfc7c83b61f5669e3946c97108c55a317e2c6b4c7d6958521a1cb9e611b2459416766f5bfb4542a936f1b83bd95860c21a387e41fa27a1f1d31553fbf206dc721fcc7582003059957897236151832ce056215b3469d4cda3d6c66a419e5c237dea5f817b6b34e2ca85f72f95f09ed75e700bab5842dacbe9e9f6fc22951ca908566596fddaae160a74b7204d560813134fb2f8ea29eb0c4e5147e72fd9143535e407903ba56266f2761725f31ffff904fd5ccfa77fa6e4c8690efa1678bf7dc32f3f29f381941aec49372c1b6b888267b3071f57e10f92cb5eb5803bdf273f4976b6e779d6163624506724afea045c5d999734ea876fc454fb574aebb7437bec4a69234c86a4b16f0516eed198c30b67bb1fc64ca98ba7ff9c1e451d016fc7b5ff834cc81db2263496b46b4933b9fed82d0c216591855de8e06d972dc04f4c8f3b044489cad369852a3723a9996aeb78a92ba39a2ccba7575c43ab73dadf8a79f13bad829da9bfe5296725b037caeebe7eaad374b9e0053edb23edbf0c81252bd6a10bc18e41c49889661f137ed55cae55b89866c34698f725f4c9e8ce3184a7914558f661ddbe7d431722ebe68ef6dce78747724f4856dd3908c4d5521961d226ca0c51fbd0f9b8573721104a4c09052801da1a2358b7ac54f69c643a5209273ecbf63ba75f16d46037257ce953ab62b2d1aae4ecab23c40f713f1f58f66f10d16ef71bdff8dbaf5963e80fde6bd66bfbe25e36847207906896f6a6ffd2cf301f4b9d68afd03bf8077723f6ac1110dbb76f18554e6991df0a4435eb8a4cff87ff24c7197d84b537c9472d29b5eaab167975633e8cc05d71150d002149d7eb362fa644ff3034a549f5772251942f9617f49ad9293e5a66070ccdae77ed3feaaa1ab656e4c2e554f105172996518853e110e32bff584fe85f77f709ca154e781f7a9d03bfaec8b0feaa36c26e5dddbd6f87f66b311d15e313a1352f97f6b8a085200ae01f537980af46a3dba10619b2eda3deeb97c0096ab6b3aa228b62a78a1023f5f1e94ba8900cc011984738cb0e52af468ee5e4e9ce7e1c708cbc9c3d4c4af57e0e362b544295b30336454ac8d2031d41509b12c53e4aedc85f592d480166538294a17947a1ccfab526eae60212a297d4394ae1b1ab78f76de9ee402c0bac9fcc900ca822f9acf4a729af9579649fe8edb54ad18b5f96539d6d8753ca5e0bc8b85ad6014945cae25728288f746d23b340f84e3a3449bbec8008fe1e7d4bd26bbf8698d3b25732f33720baec8363557d37a93f96faa3d9f0c1eab873d1fd0a824b7b8fa4b05af76c45eb03c8aad393c3c56caea888d33048c73d29e31faf1cd55a3405065ea8f8fc072d8c8ad7ace44b329e2cace83ad390f414dfaf6167fb2f78e34ef2bc9c3d78172ebef0d83ead853956dbe5b12c164f6cdc16d4ec585b9a3a00ff3b7c8bef3830dc3197df9fff98d4ad4f3e571541ae81179e5097d367f5afa9205aa1eb7784872cede13c649f85ee56d3356e9ec0c4873fed0b38329dd15d630136a6fa33429485e8a2bdc42053617202d0e2f40b28872178f582c756f5f6c173e77bd6a646372467ee58d3380959f22a422d707df4fbdea283664c6573021d680c03e6566430aabc74e20dda0029c672ee8a6359527b2d3ca89b1421bb8a17c18366962cb67724c55be0d6e9cda649e01a77c59ee60145baa7124721f59a70217b27340f54772e3265e2f66e78adc11bdc8e283f9ddb7f344714d1a96c49834d9943209cc5372c786f3e810c91d47d27cbff623f7b175f0ba6a62342a747da752e306df4f9d7290248efed7fac0e2af5e2c152e9200e93c41661ef52a455a636d565b5cabdb396b489ff377cf6c106fa31d71983727529944caa8356aa69a6e87e298436b1772af24892d96ccd6d674f1e9ca2e062bf98028d4622911e3e03f7fdd31e965500c5590899b2649725a7de1f074ec505fe8c3e0d0148031d9a70c5ff240bac87c72ed5fc4550111e3747a252d6441ad38f86f03131cbf61e6293044dfbc6359e210807dcf5747ab5f7150b77ae88354630478893b78f22c0499c496e2e2f53c4e09a9135c31b00109ac813d7588135253a8459fcd1e87705c8d758275479ec0d0722ad182d982493b43bc826a8fbd8ace5c593b0bcf888e8c63276cd69b52aca0074d2b08e4f2388554103c5e345fc21baefb28e37214fea9d16d73d6c9db6ca372da1affe854a25a088c1b75107ed8815e7f1fd5185b177c2a42cfec1ddb2e7e19ec5e9494e4e2ede5ff7caa7a21757a4387020426747c43fac3a851e1f0480a3c4f87f740c99436c51237c7cab2aaa7090f3b2d3798978e98ed8ca3f3777f49723e1d1beb25c1c2842e4520495389ce9e430c9fae00c47d1e160de170c3494072bfbb2a8b643733e0d1c238a57e2f940696f30323fc8976e64d6c77465dc1f203312a0a10c7239da0e0d315650d18b80adeb04f3d7d66204e7e7929a9fdc28c5979efff2fbd71cf9618549c6922823d12e9281c6eebc7c8b0414e68cd1a323d728879727c1d3310e1e879d9acc4c02ec3b6bf73d5aca227cb301d193eafdd725b97615b3772ee0abc6b348bf9b1019bc0ce2a54f0575d6678186ae2d41d35a07240d56d06ec023948e3b6b626c86d18ef10dbbd752e725154a7737dcc5e0713728509b3b6282b4e038adddfe283e0bcb8657907e2ae387cddf6828d85a6ebf2504a8457ab11d1dc59eddb2becd34164921c8d6512169a5d3f630c509afc9be5723da2a01873ebc12ab0a517795e4a862cae13ab894c88f421140458e42976f01c6d2c32b922b831320434b194b0b41603f600b1530951542bb328125e101a2703f5c6fb6d6b0673bc1156a64448e57553504767754235acf198b8c69f911bd90843e74cc5d04c93f738a27e957275d1f158cbf11f0ce651849d6abe03921aac72f2c10e622d104e6159147b1fefee84dc227172672449c584702dc60ff3170a6afd79f18abcb697adbe1021666d602bc1aab49ec542ebf5627fecfd28cd673e72311b5d8f1ac2a37dad1f3b76122886ea8e3340332d8f45c9c1785fe0d15c464330951295a0f8cf98853b4f94eea7f2e3087cfb60cb3dfd8877e52c21f14be072b32dbe6036aef25ff58a47d345d977af4936548be35d74fdc9239002a4931272021180ede02f77a14d28bc4b72df90f03a7e7482d0e74302c4ec0bbe488d0c54efdb1737756099f557af86cd345ca346f1aa2aba2ec38bf49433fbab1e59ef5f8630db603ed4d99ce55486f1bc48fccf504a525b2d9ddb32328a490e821a354ac05dae505574c38d0b78c9be92d930bdf79a05217d04c98a7f921eb9c989080bdb49e01591b76a37ce6eef3bc61a1552c5087de6c7c82f3b5337f096e4df3172ee920592429991ceafc3b7f23022f919932b3c959c519bd7158b374971686c1ccf73eb64c612a53b01cbf25837e715efd56b9c65a24552a88a07a9fab127b52249ff34aa2228f3d2d9201817a7261c7cd79963ec26a4bd7006c28e05a2d3b072cf7607a5ff1a0c2fb77134c3917efec834af4ce16ec93b460761b49a2b139d39b63733e58cd8d70fcf84ce3e648789794226de056cda8fe0ee740e40ff7be872e37ebe4c456641558ca37b74eab354d235d9fe622defe828e53d27615c2953725049172807ef86dc9b7eb305d4662d554d0121a5e236a6c97551af89a04e2d72a0fee629b8ad0b4360ae449eb9d6b78747863bf553c176f0457b43370a20c472d49a8b5094d2420105e886cdbaeb8a45e0519fcc157411eedaf44044461cf9725197c9163f4c2dfc9f33149de5654f4fb121b7ea1d60ae04ee6ba7058f1d7f7246e59aca26a11c3c5b8e0a5d1434da9e9654e8e3b5c8176ce7324c61ef7b3e72ae053d53d4d946b2f83aaaecb31b664410c58890d7b82e96623e08f3e17a8e7283e35be0f63ace233059854ed743c9c5d1c27a57483db8af77029ada7a95b872bb6f251e6155287a188d6f2e93979ed9a3acf7a3ed9ff1e5209452f7165dac7262fc16cc2959ddf296afa46ddd3058cc0ecf70aa7b4b9d9eea0ec6bcec653005d700092ed5221baaab17dbf55b14454e5a3e062ec03d9d26af680a1ddf17d372efaf2cb3722313d27ab365c44ca090973673906efd4aadfae4a6afdfc9a5aa72e2ec4f92147dc1046f31268e2b6e3c80d66d13bf2d8a88a86591b72125a46b3195db28002a5e7864c7d0036a5f324f9179e4e618b470bcc8b211a1636f9ebd72d85fb62019e7ad3775bfb07337f7fb54d77e2f01d5721b3d9a14fa50ee639f6624ef21758e73f8c3ef3ebb51a39ef1247dc80d20cb462f985080db519466e572d6047f032288346fad1c7a2c5510840c09e287dd8bbdc1a58134bdac2a465749d17dcf8a0a1a8197bf00580d136786d78a0bbd4019abe0171cfe84e9bf74d45728e8ca075dda8cf99123fc5f1612da9586230680ac50ce9014d6eb8d8247497275c66d614f77b63b8dcfae2f6ffa885968ac292b7856b22b80a081e0d0c0fb72e48c9a612ec2f5414d8af8e2b95f7e512753e05d1f6447bcadc42816f0018e4639225cbb26c581c9d487a1eb56834aa91fa2cfd370ff6d5176eff1938173ba415ef5be97e0b81291de82d7dc06dfa98645fa96d4d52f255a3b0b28c47438be4ef53d228501ed33be445ee9d63114b4f8776b8f675f9d4aa66f1167aa8743aa7058cc44e9077c43fd142a51544769ff6cf6f3150c9187078a9956da0ffd5c9f72a37dd24568c9c1d3cc60bbb09aa8991e2a8c162dac6e218909a620a9a775ce667e12c1bbbf98dd39534a91a6a16f8516f5e6baca54a5eedffd04776486c83772718d3348a120cd877c11bfe9fc1e752b89d0321bf4c4ac22934151c009db3572b2bc44381bc1cce9b75f8005a21a8ea58fa87719b568c5827aa9b8e893fba13984bb0ae15178f11e71b5cee6eddfdc2cec44a913e1fff3a015bb5b3cd31b7072e3446b392c822c89d162e631ccbe2f2a314583188803d1d2e2bb9c04265f8372871361c2e0b8ced33a4cdc34a70606d49644b3ee13b5723090fd1d7b870f33723cfcb8e55725ef88ea8c4dbd9edfb6f17602a319cb8bb36214adf3f81ac48c7245568743343f1f3bcb0368e0a3d3ab7877144d5fa6b99225cd6f9c812a097e72fffce97fa82ae706b7d1eca18ec23571bac072c3897c9a68ef578e2cc304c672163bf0640284e97a6fcce949dc01529ef2dbafc44cd518c22d00fc223bd9ac72992848eece2ecb3e3af7072ffcc3195909b3ead286bfda2505cfafdd6586c0723c0814c8e387b516a6687c246ec8b44a23686c9e30d75da2bb8483be934f903b2221839dbb9566c3805ccd52b38a657043ba1b5f34990a126b5912bd6a65b3729471e0b2d14968d270a7071dca748f19f33e0e0f3334d0925a9964c3955fc5720fe4d4f58d176e9937a3edd0074c7516b91b4e619dbb51f4c16f547b01417e72063e59cac125eafea8103cec270e40313bc1f2e3b488cc336153f54d9af0f55412a63d186916534e1d5db9203a96d660eb4422e66a855672fb820d2837982f3e9449d5d428c19703d91bddc83d99d8350e0cd86a3e5f7d2e99b078b1a56706723d035aea635513d11a1581654691ce04e7a4927abe76fdda753f2536ec8f3e1a9c998c3cd39ccf54944b5079f4d1db5027365acc6cf7626e13a07b98b229412662e76829b6d8f3c35504b0792ca4146ad7bc36f1307339757559a2e728098b4767d56c8675564fb747439f6bd81659686d695f0f3891a27448d87fec30791d72596595f556265323327d57a403bf9d7e17d310c5719dd69f11c81ee420e3ff5209e9fed3c7f1ffcb13d7a9df8ae6a08697de3013bb0300bc31122af1ccc68f72d41c65f17ac09f8ee13c784213896e77e35b518afce8ae289c5f1bde5041db7277d57ef4579181e536b7505aa915f34e6f600d92f6396abe654bdb71df5d69725b82a528fb93a0e9cdd01350f1456ab6d636a1b253967122d7bd15b44ae82c72289fb0fa8acf7398a23414aa9d946d845150100227f79281ed59ecc43ccc3a1e13ed5a56042121a6b01db82b5c08aef4923508ad4410da2599b7e913bf12ef72f7a565f47816076aecba009e688b8b863f2012147ca5bb1fa1c6a9a7c73fc972d4727ceecb45a6ef497dee123041e989588ccbab610d5b04d6ca7b2f6117bb725b3060cf5f1218c70ed736aa2daf59b27f36ed8bdf4b917160ffa5068ffc2c727974b9cb970b26ea469ce8bc46ef28caa0dfba95dd3689dc328cbec791f4cc72c861e68b79b81aafc336908efa44d56931a29c6b9a781c3becf05dc4e07a9b72fed222395ced62536ea72bf406d4e328f613b6506e0e20e39bdd9b3581e49a36538cb91a4265da613be467a67c2d4d829aaa323c7774037f0f2f829883aec406b21ad28721f53a8d8f90f7d693e21615357b6ea7f289cfa55a2d458f2927ee286dc4d2dc1a14df74c4f79fda1902979d6aa9616a054ab920048d7301d3cc077227029fde2624d5152ea4f0b6112af0cbca109674cb5ca2aca6f4bc0afc487e5343612a54b12374725282ed65ba3fc65817fcf38b26cae7a3a6e36f485d4935723208c0a1bc50fb6cee67420ad027ae8c8d88931379905eb1d7f47fca25c0c772b4f59d6a7f3713926d22417bf9230e3b033b86be50035f91956e9e30a6948e722a100023da978c2f8c582f72eed1d60e0096c676ccb170f19e0ded2e4af2a001482a9c123c74f7ddd7e07d04fd8633870620828c30883d92d469a0a066741f4ef9a241c1357b8f4f65d80bde4e29adfce3e1585e8e4c0e9ba160cd90cdf7977202e589bd052eae636dc49fdff2a2d8cfd205ca292bb3ab1cba569c386dec12058133c8846217ffeea42b0796a2b9d6b5d49e97d43b61d321bf036c634865b8033a2aa68c823945fe765b54e7b79d517a4544b4bcf529132f5cdd4f262077240d504930ee557ead30e2b6a0796f195f12e768266dbf687521baebe6f46ca80b5834ceeb98bca2e451e88b02180fd1ba6b02897a5bb44366e3c57b74f4110a8c0fc09ccc3b6e3b0270fde07a65e74f6530537ff60cb9b755c7b53ee9d9d7de47728c8f0f68dd5df106ad289838f854ce5f5f35d0c4a34a04547f6d89640d44ca72f4f3ec2bf480856350d5663b4db96f2045bba177a10eb3c77be92f764c5beb72c915fe5398b8d43b02a06b68e2850f9fa08c3629320f7413e4b8db58303440720217a527c518f2ab28c4503b399bc24c2047cda0504498668abaed0413e1e472133a8884323f50beea77fd17eb49aaf515f0762feb9b98d7ee1a78ef45c8422150316119ec69d9e0e24f079b5b90f7a58587462bb55005d6994832397a25395488d0f18c97a4e96f7d691125a203d6c08a8ea31aea5ad1eacc6bc2d606b4fa6cb928600238b5096d68854a4d1b296fa3433b8567165231dfdcd41f950125476a2e26120851c4c377f1259cd3a65abdea0df0becc214d559c547ee703f8552e7248b9832e86517c02816bc2d72034d6222ab672caa109e630b257300686770d0bb75e9828ea8d44a85ba08ae90a45ad58815adf312dac20c120ac2d0f57c06c356784c08d84c38a02e36895ed3fcdb4496fb7d8e1cca8d26f1af0c2c78a7856675fbb3d4039b0b9b52c126f417932168fd9f006cb832cd43149d8939571fd0059d1003867711377cafd8f2e957531768e1625b442a3e095132e4e05fbe24d6b1e06c36b00d32b6d8dbcece5ae12961a210103300209c8aeac58fc0f14235eb80efe11aee6c1875e2221bec8ea7374fad8ca5b952e3594151c8460df1a20f68b72a597e5787bcfb1d43b372dbe68e92ae7dc290d9b0566c2e33d455332bc31ec72255a7e2c1434ed213f47f163508609c412ca11c211b15783c6a85f2dc8471c6f004422e639d547414d9364397dea8ae2ea434c61ef8292d6d5cb6d5c8ba8557081ca3a64d93db44f511d6ba0b468e900860e4800b5601aa14d8a30e0c4c6b2334d14e9f465f31e001254855c629c3d0489dbf886c5c9415e3874d63082eac922a5239b94096155dbc435a739dadc9dd87bddc856ad98dcdfd72acc42d02d9772a9fa874b74705268fd251808dd1c1895cb11b8421170068209e1a1ed6927bd729ed7ab86453922536932355f55e6adcf3cd97496f8e4cded8976a62e37d8b61322dbbf8c14eff23ac48b5313784c15fd04e4d5714d32d5d8c7a5e6f3ab35c472b116c508ee3d088ed55589460b59bd2be0147ce9234101beb2f2e8e9b705c6347b9d9a082a4aa2927f54ee74b3ec8b703714c8054b232d6456c2508f7c863f0f8a6b4c1a786543909fc34e72b59490442efb9697409322bca2f0cbfb1b3f8d2c964a900cf1dabc1bcbbad71c416fff56db8839d8804433ce4baa5635f3ae2b722048c421f62fe5099f27b03dd8e5e48f9af62a07f2dd573db205c69614e53b72671cb0b469a87d49113f6bb3fe994f955cd8bc527a0396d990b6af1de89a3d589efab89458c3de34831beab19601984ca841d0eef465caa29357ed3c47037566170ed88db21aed1d36b62e76a42d3157a2aab8ad10966c65882e26c9de110872bc0bb2b81f4e21d06fcf69d47d753b5de7f5e784bc57a335c735bf136a28b372cf56b4954a7afbcc954d949c3384dc24a4eb3e7840675197c316653bfbc8027298aa0c77c9e06eae3fdbb6add38dfe2c215f394025953bff99a5fed314540172a2b6c7f29786e37f13e9702b2fc0acf2d44a8878c95078bbbab4d4983c5b6d4821bf1ca3fcd9484f76d90310e2331462ee9aa5fcafeaa8a000a47693df9c1b705d12a37e8b9ddb0a54fc0714db8d682b0f8cbad1d07179a016d23a55f6112472b2b51a69c9627bbc4b611237d230f575bcce437a50dea7b6f0714bb3e1022c724c115701359689306774755570ce4031eedb5830ba884475c27a5f6aaf569f2994421ed07aca263d00bbc8ac76fa4069730667e470b80b2e5661df814ca5090418ece8146c5e1bdaa59fc38e21b958d57e2083387600c7b39d2b92e6cce308723fe1f2bd12a8f239fad5b314167aee07a85b5a814b6a9a986e1f77392314287273d3c8a7f401ff0ff83a4d1244940d762ad27af1934477ebcac15891f210e472140b4696a1df99a9618b660048da6ab599c425723bffbb8203596c0ec1564029ded9f052947eda9e9fa980226a824114dfba1bc788a01564d476c71e70c08c7277bfba49d61d53033f1a1091a5c220529d3e8e286082371de3c814149eb77e3421c71cb6a5edf3f8b188f3f001301420a509e013f7dc1aee9dda356c4f1db133a3f02f8135eddae81909435dd9715e47a24d237929747465a619b93d4a8d304fb913bc005125396832eedc714f9decc4b107670035354aa9821c05e098ee4872235b4f78ef02502b329d22903de192d6ef6858929796f5df82ffc9d3fdbc921423979d5f3aa7b9069b26c3fd4f53c66d5bfd44372c939251838581726669bf726d05d7b4bd0508158e6050e355c4f82ee91861676d756c622e932e101fa59c4b87e3956ff4bc7fef07874914975edecbebc9b940de5e042ea8d0bc1527566b728a780419c3145d19b81c4dc5eb0b4e97fabbe2afe9a1cd6ff0ec3b3ec8a20e6ff22680ca48930e91297893c3d8d4f91810f86662bb34a298e14841f2fa6386723fae59c7a3cbc765c36fb2c323fe2fa111488bb35d3b85607209ee4a900b142433dcb64eae384980cd118e5d027d10775ed6e5c7b2a783b99a5fef1d4109e672d58f17db45ee8fd910a9a9e62ef410baf48fd30ee8dd4f6ca247e2d5b9af837225c81888043a31e9e7efdeea886f967cdc257f7e15a39631e4b7d9325b0d457245ca02e3900660aa3bee80842e7208e7cd23057890aa1b67d82031036abb49199bd5798d54e8ada150f5adbd702957f95c0a888b365b638fba858b3a938f3203a92506029441978aff97e5a1d9b9c297442cda8dc05695bd3250a95bb672334c8da3edaea3a0a16cfd0883e2b96466bd5a1f57cfc3faf7c59e6fad9ae32f2e720c0a0e298f34fe3ff5ed609f9852bf1aba74785689917d6f3a055db85670f14cafd5c91e30e026d79c1df531fb726016525ea3793f3032516207152a5c97ea723517dfe26b2ecd527d4e7a72482f7dbc3c44f80bb14513a87c84d125e113b4726994aae160040fbfe15042f8e8a4965f8baf2de65873f3f264dc8726b2f4447276812d20020730701bfa38ec6165ee18591985c946ad987da16e558de4e87c7284c2bcf76acc6460a1d14941dfdb1a239b881168355b9a91fde22b4f757ba9185dd7d81fc176ecbe0eac0dd9588f7b1571ed12ce57a0230beb3774a26d28cb69e1240a85d61c8486bd5990836f4f2d6f755335e9245d2ecbcd40b2d0d91ad82fb12fe953caf7206572a01b9ce0793d36cde217ad34365a6a57359acc0fad8035cc28ecf5c4d9f401ae5dcfaa2b0a3ab216b5878895cc915ac0564c6c70bd010b6f713f5822264b74b412ad25bf7635a283712c2262d7731ffa4340f5df1ab754fe4bbc8e28b3d6c0bdeaf720e8f63f6ee5c1fc330cba76eb96bcbb02b4d57872351afd781c32c6c9cfd694698af62ae7a63fa5ec00c6272c480bccc7f3bbcf5253ba5833ade5528ee4a0068d9d70e6943d4b2abeaf73be39d875384140a80a3feb9a476d644b9ef3274473f2edc2b3db35179a9ab01e26b90ce552feb162b072299a55baea24f74357eeaba275fe6d802f329dedd22a482d15a6e43b4be0334d92492af0eb100e15019732a184aaa8f3b9a4f9cd225a2bfefc88e730934b9e72c76f9460eda05e0a4257cc78e5c26ed3fb7ea2ea59a5f2e6d69141197c3779681b552dcdfaa7350d97690997609a10471687cebc0dcc1d005a089545532c0e72ae4c3bc7d21d264f8eedab11b8e215dba4d1d7edceda28ef12afe462898ad37246e31a1e392dab486315a22de921eb394096b3ea4056068ad6380f9c17b04126ff7d50ad9e950de372f9bd4e9d66b056bf924a98a14158966edb5cf1479e8918ee546e46becb4c4ebdd9e75c67eaead1292d3aa6a6529ccc724c7dd11ab74c721050c312c7f093dd284c42f6b6c59ff0448af7db9b37a3af25d7adf621406d72bf1f5fc2526c0aa4eedab7521634c3cf0984194d66e4fda2fe375fa8fde91a719e8c8a6c32f30fd05beee3f1ffa38a535bc356d38a25276c8e472aa97acac2446b1d12fff9ac63ecb44e2f9f50d7d59934a2a479bff66fef99e72ca32c18dd72b42cb62b3eaf456b2bc2d61622467491353e5248fba234652b17e49b18ee4c72300a69e4b4bf5fcd029ddee3bddf1ea9ccdf31659eba1379730f248178c51f72f70ced2fcb6a6447d44fe3f8192823b2a9e23c67ba6de7c80bd130d057c63e72489d33cd1f8b9ec5150576782d25cce34d3f78b7c13178a2104c4684c0c31f38b6c5e3ab72ec1086a36b7463a608185acbdb9b8b569610287c0e24278a5fcb72c8368ab470206e8c67b9eac72fe22e54243ff311518d2ea85bca5bdac941b27277bce4370d3ae3592cf2421d1783e07b53fac644dfc3fd393443c37794e74e7237edf0c03878a5fd2429d0c29e23833c23a833a6a33083efd0e44972babec1598b9f76b98bb87ae682b4f3d868c3af92f0488af3fa4b7c06a797572a748f1872d930f4b62fab497e2ae87b461c41487854cee9c73d2244ed565deb2c6c952b72ada0623eeb5550852f1a9e8bdd9194bd8d8d31327e18cb7da278b3a6f4ba29723f3aa1f31a72fd24be5e0304a0d8c1071112b1455f3079939eb10e08bd6695720a14dd1103c189d9f393485d0b97a9696d34935aceae8dfca707ad0ebaef1072186a6effe2d19050707259d9a9f06c047fd7b64a6302e4cf5c8df7f3538f4f63d4aaf1a39522906402b8d07190004a6d424d838311a74efcc508d1ac3a147d72329d99cd131f1c84e18ad807980d75288623ecbdb7a5de176ad534bf8ed60172e5a997de8b8e5299a00b7818374691b97c469d79230c410ff0b29db78e5578724c7266c529a6e1e67898d5aaaef2dd90a5875379208ad87922ebdaba35e0ab72e622e180b2b388796e40b0f54ca0150fb16202367b8ccccf7a1f5b28c6db4c1fb1773f8e30981f17a2af07b837ccb81808cbe9d67a5b133a28e818a739c69a72b2f201e4f6ffcb9d851557c8084925dd297d13ec5844a39b5c2af86f512e270727594fcff974556aa9c4cd7f60d524011885ceb848cc21044a94c257d681972956334be13f1c1530eac8280eeef1d1dfb6c9e724253c58487cdad6c9ed794c40cde1167f4bea2770c5fd1b8fc482097bca70c10f65c77ae4ffdc8233cca5130a373e204f6c7d4d3fdc4762eaebd0d27f18544c7efe4910bab72dadc207a09272c69dc02977bf3c67445f6c7eb3c3f9b7311f6e464bf1daae9afd3e8fff8cfa72db6477746dfbb01aaf2ce0d2dc194a123e50e67d10419359eb06c06576f6ac6cef85dbbee7473a475f77c4fb4f3146162ca1a9fcaa16f97147e4bb598e8ac30d130d5696dffcc85e60ea5bd9fca57e537e4ebbc37c78e14e6c6048b177fea74b9ccca17964783a4fbbb5742bbc50970eb4a12d9f8d9cebd2823a9124b6874872b668f36f1f485948b77df23d181aae21c4c1d64a38c31f53a3681b057403aa320a5c801c3124933f1c5d8af7db4315612155e21f0311f683a33d16a293371d0d431a98c21937f4496dd2a5d342c78468d67a147369017c6608b0a97e4374142b69e8d2de21adcb759ca3b6dbb347cf684e6d7e156aa63d0fd56df850f0cad972375d21dcb77bcb82a7035981bf4bc0c0e1f46850ba4bd3bbaeded0f95d6fa911214287fc753f1eeca0dd10670546527062b1e1f44a900f5498c0107a5c01967250f227f1e837ceb0abed436875af5d39bcbe230b452c1f3fb676e4d194b1270ac6360381144449c915604a9b9db25451cb508b0ba82d75a66c08398d5494fa2d1e48384460df55e2a9b4747da2159ac7708550e00e4e86109919bd46c85a6036581af15620e4dd450d39a351ba09fe12bddc6f036f90518d1f0d0ad62098dd722bfd4f242d282d662f4f404422d51456a05c5a5bc3c24a138531ae46a629e1720e117d7ec35507aea129d65d088e8222f19e78d1bfc67c7e9e15283a4ba26c729491ce980e46eecea88a9c85960a8f7748bb4c0b4e03ea06b7d71d792ced4272a9d43de01a0438d24725d6d6f2f66227182c37fe4662a6c7094360c4b5079b722d36d609473a26028c9dc653340f2bc75cee04528bfa11a684a5ae8689b4e16b91e053ccbb669dc86d403d6bc1812121a336e5b834012f50931d4d5dfdd6126e0df3d829b2f685b02cc56c6c7c7a83892d491b931525a9c9b9fd36e9bd9e9772b2c15ff358333dada76f9f9e806dba5378a5ffc3c2aafd9a3ad37c9e18697f72b21f90fa06c2221db6302f71a44af77df979dab15f83b6ea7d4d7a537de8133895efe14ec5b3c094fd7753d7c84d239fc28315e3a8eaad917e2049199ce4ac1ec6412270294a7a787216156fd348b28bb8569133fb094ce0adbeda8698b96172193306a37af469f9910b448bf41ecc969f3c1e095aa23bb44adee859d9fcf566282fa5be2a217be289ca54c7954ae357376be3356bdd54157eac8250b20b15720c64a8a7aa54b37135a2007041a54be1e3199da33d5a790705377515c4a6573067f598c930f7cc3bca13bc967f67b9de556fe5755998842016b9689d00924b72181fbe03df50e130865e6734cbba5a1043c5bac5f0533f33b4605a4ec26c6172c5b5f9c866ac17b6cd4cae32582edc0d51bd5e1b1ac671d19fc4327e00750357cfa799d2729380dcf9aad4c7d9f89c002b01b98469c4d774300510605224f1215d1b5c0d41d3a46a873def5548dca360187a01bf6bbf226a2774ecc9f79d4a729adc0da27edf4a9d745f96adc0dd13665af1e32940ecfdf5d400213b9bbe17342d1415105fa9162b000a5936f6f1aac6968ffff4d7428d0b56d546bb815047676c74ccd1c1af97aec5d4ad6237307ac5e83a97f433b611ceaf49a1b473cc4e728360b4f08820e44acc930dd4d4a08aceec03426cdcbb3b9f0bb809279fd34301281cd4d780cf5242a6ce84f0574a52aa71544859c148fc51c4843898b960dc0c5dab42b3b4e7583800492ef15e855b238b585cb566648fd98374367d16ecb872e6228f6e6e32ed058546328465377d09f2d9b7c11eae74ff8b44addcfba280037ed3756fe7e48780d33a3e7d88d08e7073b640e276d3cdca7a76e33159a632576205208038f7c0a6790f5c1f8878df0670be8af9fbd830f1139d11dabd3ada72caa26277b5dadc14ede8a76b936d7592371ffd491bed339c4bf78043db42b672efdeec6658323b71dce62a43e9e1a30213fe9c05fe7dfbb655ffaf4c2c3ee859d7a3fe2748e6b1d953385b83909b4076d96d470c282d18d534bdfd322015e472d0f6e451aed58f3902a49d8c96421a97f253eb0379b703bb4925a4ea74fbf3722b1ba0f8a2c2660a2e5ec0255bee817adbaf01d20f22ddd2130834f63be0ce72f8e438aebdf78702a7d02ceb2e87962ebed9565d6b54ce5c3aef69791088717261f9ad23ed9d6de1bbab95c54d7b246fc220bcdc730b358f3b6f652531f3d66af1fe794e13decc80cc6c14ce9f476c9ff550aacefd99651a5c622a3e4cf1fe2bd0e8c05133151cee4c0276f11375a74e304b23d66aabbc541c911506ead2341b6801a91fecc3cb5ab2893224fa178a83c48494ff32b9c8309a4c07c55c501d58bc44f672f967ab81841d114b4c1e9e6615abe6263ad756b74360ebae104558722ac4e5216a680fb708e559e4453e693cc6a2b1945f90bcdc63aa311c67f6c072ab4909d1cea9cba76b939d06c6592aeee2bde178c4e2302e1ba7254d9df8bf4fc38f9e0a9b360504eb3ffb394d0e5f2b0afdf7cffd3876d1e4548762adf1433bb50ed1a1f93f9435c66d1b2e6feabfda8a9a488ab27160db61bdda88badc5572296b47d1c4c8d8963eff42e68b012c42c260a0584dcd36f9cebcadea83f13514071aebc3c9a3a8eccf690aa0862c21a55d82b61290338606685fa334b56aaf5e8c0d5ff591a1237207c8ff425f9b492b94642bf63a7de703ab8eb852a4ea2641a2bb07093c4750759673371eb20883d60cca89776dd643c8cc58e5ea1517007240d57a692a80ef4f9692bba282b97dd52c88e41f5cb6593c7a2d65df68df4c7265a3600e93f4b9d3d05e2bbfb2db80e667b11c05960c4f596463603373e3e472db56dab707f4e3932de82af8b74b7f62a21dc51cb29bcd54cb3f286567e3987289911fa751d1222e69ce2e08a92ab774a4915dbdbd3766be02a463a1cd91ff51c5c6cde0a4cfa8793256a1c645f2bbda66cd7a599ab0ff4425fed96add0960538f3f5a63cd183c3b6a291dd78d6f3037c31174cbd4709aede65e1548b08f624dc805cd8c736e7632b4d0b37bea6871a41c1655bc6efb0f3d9c16538a5ba4df5a9dc7b5224c7718ec587ebb338d13fe4702612accbf276f27313c9369b3406b723b2c370833c08c7b19dd02f1272a46dc9440c31e6845a0af2fe83a7cc8a7cc41aa9105dea68e7a500344ff20b47e1caf51098a9f08ea3d8e5ec83bbb03e82572f0ebd08b06858a2c66ac8ec56d582127eb2fde417c9d81d924d11245d5fe43725992342d85ef535a352f04f7e0c2cc008bd1623a0563f2aca281bf14d3200c72b1dc42502892bb81c5e7d64723e0b73a0b0ca7f4f2fda67e48e3d8d753e915418cdc0cd3d5153605d61b00e6f0d9aca0fdefa8603354b701356ee085ee8f2f7258aa523b7bb31ac6f38a0f9ea853345793ca553137f70f00f8125d27e76e7272f081113433cb314e42bc617b574e0da29be0552eab8d5dc3d18310db33c36b7294ee0e6627589a9a4cf8d08b1a13467115d63e8c57647eec7d1ab7a9c2941c7206ed616659d1a129486f7d18dd37505827019c455cd1bfc6cefb9848f050da72289bdb73b61b2a8bc8b51384c1f1639404328b1d07d3552963910860cfa6b672871a5df7d83db397bcaa3413fe2cb2cd6d8a4235ade5211df6cb08ea47bd4e6f9fea60801e208d86586dfa20e807b93370998f5a156f3ba413febcc6ba1c71385562c2789b778d7470df8cd128ebde5620eb0b8c7bc31d9525e02ca2f94d171c574289c1f1eab072e6ad5179fe3e923cbaefcebe0462e92170cb65a816f2a2725dadad68bb813566d0b34d05060c522d2cbae7c2593c13788b920afd2bd03872b2bd3424ac05af026bcfd8fc0b25372fb94a1bb41ba83a7562e157822a94382fa26ce189096688ca3ba08962d79cdf852fce68815cf89c16ba0702a884da23268c29ed4061dbe0cf340cbda8023d6127c2e0d1cafde962d5dce9794901909272808aa33b243352afb71239c4fac2f63b8ccbd5e94c41e4be474ded149f8e0b726cc0c25fb5ed075fad7d0010aebdabb031615feaa8bc4ea4f7c78726e9a9cb7270743f721fecb78af9b30ab4b2f42057e91634533ce87eaa980e27c0b1b3240eca39c2daa661101c34b7f170273a405c95a48af3c83ff961c16e20d3eb72ba7283971f8adcd8cf8232e928d61aa315921e3a86884a89fe01dc24f71b9ece76720d8ffb204e0e0954710eb228e0ba3029dbad022978a43055e7410e539a5c95722d7e4bbcb955ffdc3c6e10c4ef5d4b4d45c71483a49527c4bee886844f69887264667efa56a2fa57baa5fa1e64092336f2529e30b02de8a328764222dba04d725c260552e20ffd37200e2e3d7c01836e8485d9517c570554a46f19efe846f05922cb90050c6aaf76b53a0946cd1dbb87cf2354a59831d03249a968e9fa35f94eb94c4f793fa533f4a03b11b1903a5dbf0173d7048705230d841b653388bb314cfa7447d715716afb94587fcca6b8be04ad4a9208c04537c29e5cc0bafac9df534eeaf48812b019d52d1b8a95168f6a7f414222710fc0bdfcdae7154539af95728d26fd0894c3f54693b2545009fbf86268bf1c56b89e37f471215f44b1b65c72bfc447b5f890877ba6d8435acca8c5fd3c472cbf4d60dd5634e2f94c6879eb72d20ab6348d45f354c3d9a8bef4f884b920021d0318465888435f51524f8a266c9c972504cb26a7ae7ebd44fc1fc1b7b4bb0c29be9fb9ee13e17e0a117fff2d7257c652afa5f6ad1a4c6b01c71cb2bb7493ad9fd5ecc6e4cb517adcac98e65d11fc8fa3849baba6bb8492cb83a9f9649bfc8629bec9ee0a3f2a8f1f7e02d2dc3310796735ca5a03c27d54afc070f455dd1f0580fb2426f30fa70f4a3609e8e4165771c8f4063c9bbd14fe397b127009e334bb0a4a76f61aa580ab766a30add472ac85d8dcedb60ad45493d72d38330074e7d753c390950c5b8edf27d60a6830729f96419b0e957673fc4354f8118beac8f67c9179e407f928e91faee80dea3b5edeca025a2a54b07031f2aa8be846f2bc7a89b8716a53d6f24f0a83890161b469904d8d1960af2f3000f08dc3015460998b4e06adb645f09728c3074c3d117f726ee3308eed7ffc6bfe2444b63685a567ddd031714afb26727db69d7fe2342c72420bb95480629084b659216e31e02a66864123feab6a153d3b0e8eea6f9ee850189e4e54ff928ade64128a295996c82d953d35bbb952c511e3bef591e4e2df6bea9fecfecc2f067efa62aeb8e7d51c22f3b879c9d15ac1747cfb37b8ccdf8d272a8482653ff3694d3f1efc37379a9ffd0063ef07f89c1fa0fec96518e93e787223bb1c94dd7b3ea85933dc42b950a4b18e83c1a10b970a621649aecc6d03ab689367c3b59f4b61be5cb229df8113a3db1ed0c79f92a5f24fa7e2609841834372e498ed0d6095df32cc3a37f0dd2004c6cc72f4be812a4a08b5a4b91f1c1938720785d9940eb4dc1c0819ec06d732eef62d6447dec9b7f6c56ae67c0be9f3ba10b8d0c7b70c906fd4ee07396c797e0bfbca77b25fa8bc1f3cbfb93a29a722ce72b968fab959c175152916fa429ff0de81f9b4078756a86815252331ee56b1f472d066fb18c4a0a55a3655112374ffee8a76a64239fbc0ec9c3dfee38216fe95726dbe1d30f823505d4460fbb8b32a546f39b4b5aa1dbaa997986d85435c88f24ba59a569f50d1a7aefef4db436256d8b4909849d72ff1fc02359cf60673c6a872ed68f0a5906e40b578b60050391fe3d8ab19f3491bd9da6850b9b4e6188cce57f6fca2af31656f56569fa99f2fc1f7cf0e0d58740955adf7519406c0166e13722c83f3135711545240a81d2f870cca03b64f4ce5a841b64269c11494d011b00283e29d0a9a619d39cbef28d1d75a91af0973771c6da1638b18d08881256fd63c3248e44a4e9b7bad0a51ff2a9a6653e24fa57941c99ba9d7af7318080df4217295b1c7bce2a286ad01039d28eb617ecdf049e5d6f5a3f6b8229d675a81d9ff29d2a127574307b24b3097ac95bd2f524622c4e0ff6bc4b41e3fa17056ddb17454aa5bd930b1182aea31ab8735d2c21126e69c6a6511d3f3a9812f00bded71c47214c57e14c4994bb1d06e2ae12529c13e97a6a20e70b119177e02950da1eef817d210a0bb0fb8ccbcbe92e35c2e25b3540257f31613875b4e0f74ef50098f49702700c6b82103d32bc9457e01787600c970e7549f31e92b0dff9e3c8cc7978072cd656be93cc84906831efc2f88975b8f770edac4dd9a32551c6a6c931459a7114d6612d18a917725e4aaac383a58a9f79de8603e2a9d0248848068906fec9c5427cd749745d11e3ec52fc5d304e7a62f0460cfb02f40fc1838de64d4543b836f8edc2a2073a02ab7099ae731fe9e688563eedb6bffa157c77c29518e191d28724b0b9753ffcbc7090d3941360b65ab323a4b699ea52ec08979a48eff3b5e25724692ffa32e17aa232735d8831a10e8cf42235c64e33cb57a71e628c04cee7d724df79f19b94abc478d142d23d60a4e689115878dee70b3e68d88efe6f7fbc072859cb9ebd4e92e965f462f0a939e6e121eed5388fe825099b79e2d1b92a54e1f3090d63968d6d5df27c8e71760d7db66fe6c384445ae3c52d7814b63a57aee723c1a82ebcc36ebe34a1c30749739c1bd9577cd598bd85450d9e717bf4b965523f0807499404da41351aa4b05b2f1d3b8f7b269e7a45d8c372d80936ef88d16724ecb44a45884ab5fc61c98b9e115722757c2fc2b68a34149839a66fab924304fadfc3670ced86757ac88da746b7ba0e185c65f048c10392e0ee434498ab07d6ea390479090d79e1d143205c8e005ad312330e6a6f4e761d6826079f9101aab705f07a549beec912874064aea4fac72b725eafc7a49246080e684931ad40a5d46903086ec38dbe7afdc5f2babac35de9c6e998c15aefe26bf1ebdd80c42897458a4020e25ba48a7443b1d5c83b6beb2e4af7a7e60daef2946ab33e55719c31e7246954b6b03b623ba6aae674751212f336716c7af79b114377c401028c2e34c5644108a60394e2c7a2cc48c9526d97e8ff91cfbd8d607a13eabb506557ff7697257d881c36758a00e802c228f7c2115a8a24eb650ebc2a2d0c72d40e76fea4b729780ffd169e2c9c1d56419f007c032cd42a5910205c3d28e55bcc4849cc02a723708c9a38a0ea5f93120982e36231704af231ba312c570ea81cb5967c676a4722a40f8195698ce4e19c452be008233140f8451b389d394b179cb8519b5c8ab723834915ea504275957debaee74ef6c8c1f68e102c7ccd460def1961f87fe8b059589816612212c89bb68d2fc7b27324c042fcac23674970412feed7f41b6ad04442fde3c8563aa432d88164f2116f6ccaf9f5128cfb4b8248ec0e9b72c695b72f98674e2abbc494c11b1bc8dc347e3ba773bbdbcb6a6b7e6ec2063a8dcdce372431fc33a97dab624657212a20aff973078259f7be5398a60a33657027b1380725aefa9e2735878892097077a4a85909cfa11bda295d36666ae4844995851c713bbce54274428d073d64c8091ce027c71c18c020820500d6d86908cd35003a772634019ab3667d9e020795b0916148775da40d7411c1f55020b9606072352fc72186feac790d75fae5904aae5596548f22eef7c0bc712b2a05c233b4eb636ed541ce28ced0d92ad18ae391ca0c9c8c6656ebea9bf5b8c1f21f0db3d7437d04f06da18731e45f5d989c0f3627d6ad101b2c067380ba386ffb255dc04a371ad3472d53a752449ecf261249d641b50f493b811216a850476e9d1750906b326519862e6691ccf8fe151adc2ef3fb1f5684067f29fecbe6e484a701371f50f081eca3e13234d556386521b2df3cafc801ea5029634c407ed67276fee6b4a36eee9ec72ef05fc4d1ee19de254040571392a6d92a949abe304402affb8e59d999e7e9039c6b324ee02dc77a4af2243244d599da51a0eeb6de54a99125a17ab028f7a74318707858920b8739572255b39d6f202d6173ab4f7887e6523b32754cb99cb3c72cb387c78b282aefb0071f6022b4198ae76639271ca97ec32c23cd8d3a6b106278df79e7a8a1e1c0a6beb216959f21fb9d7d09be2789e3213147cd8c402d053729d3c32e4e591ed28f530040181724fcb35029bc1583e75931a6bf77a0bad5163a517a31f738c772a36ef7de30545339d615a8bc034fefd7796ae398a3fa19732ef87842c8c0c5aefe98ea0dacd82095bc7b93b70af423f1d62247702f6821310242907474b9eb4eb2389fa97dd1efc09664eb3311b1c220561ab45a3670d14725211ba84344aaec7b00543b55600f4cf714cbc82b388c4b684294d3f2bcf2472ad25dfd3aa9d269d9c28cb3130ec2b8370471ac04596b4973e5e0f7f2408f062ca1ec1f1a369ce8efa8d2b5f94102c6835dd76aa2b7a35d3d5199bc0e683c372d45eb5b0243e5c32f10b90b3029a03a3d2b41306c2082bdd936e454038a482727ed652532c3e8fea775f67ee225c65a0ff2b6e18619634ca2d7d4c83a4ba5a6545f7ce0158030011134d5fe703d9a286bc6c04eac05126dbb11707bf8c043731977a9660a8eb32b384031078dc67245bf237ed43d37d5474f12be218fdeb4c7204426160a69554c90dfe0239b595e6d28718ac40028a5eae7585a6ae7e71c172e53d339be8f2f35db7537c77af3735a3c97281d2089cca63a5e4efa09e207272a49a7741484664fac369f56ef206d16803c0fd2f7f7fc0659054cad41cad7b724dfc7eb1b61154f8e4afa13e2ea8ed976daab555df1d43804eea554a07911e1efccce654a5569f476fba750cfe2995d2b100fd1086f7bc2ba7ee0e4689776c3eab6247b946ea56d7b3fd247c7e613dc98b2068fe2e1fca5f4d41b54c14234772e132e591eb30fd92cda64986ee534a8329f96d8ede9d8957756113b8c25a7d2885b92805d58b577991133c815b9dcfee3017c3d0527287419c7741f24559297224ba51cd3a6f7a66c40e001d1f188efe0e52edba1da26999277bca698d2bb029f03301df6b9078ba77d270d5647c923ac68d2321cc831f67babdf1741ab6ce6ed845b09e499ef2a67a3effc9903e6956163c9e9d146415b8b42ceef149b87b2d0a6366de4f0d5d65a8f061b4f92e8fdd241991bc543093f6687229a39f516372b28d66e4fe55e26019f0553bab296ba051d98e40453164d62a7913b97a42810d570d6e0bdaac1b6c567cf5a8d55679d402df9c1d10f6cda2ba83b8df41b65c427146b5c42b2b96c0aabe490d19a8a9a08f91a084005c4c7403b66fc23cd6d4727c5d702fc76507eb8cc7670bb1e26ae600da7fdca78ef4db0a0dc312a2d9a272ae18c3c5faa484115979f1796418006a9819c1f9586aa058b7b58044575baf7209fc3c896312b82ce689ef34c80d9240d1b42c122e797675d566bfb9bf02f072fab48e57221d284be9cbe6aceaa05eba390b1c4e353829f44c6f07097925c572ccf983448a1b69ca3f96f8411061628c909d71ee38cb4f07dee8f1900211c5392726262c4ed8a14d9edd72a15649fc9557ad082a5e698d6008b0a825c3980972d80929855c3a8d84dc3511da74a5bd06a65d78eb1792781680d1fe53cc1bb23e8f91690599c5641580d499771e4ef5e37fd7b8017c12d7dc1006c86d47daa9723cd115f6b38060c26a62b9366d1332573784b23866ca2eb7c63aebef6798b372abf8a0a84dd042ef6a4970ebc4ef13d81229749244aaa7f68e0895f2d0e91072db30ba10b42c7b951d584ac891a60a107c4b35cc35aca94e1a9f014213f4250bd10abe9e447ff112530e64d09003f9d06973e1a110832c02028fc7eeb9c163304214a266922615fb66c8a6f3c907510b510f4b5a117e9555829f137ab44fe67242548a34e7902497fe436f6804474621eb967e0f30c3147c2904b079c2ce9072382839e70dcc15fe2bc9d54ceea0f1923f90c672d2bc63faf3d0a404bc30a972185e5afa8054b6e838abaa0485fb3e51933b72c1e25f8220fd3bd387dfc769727bb27b52466631441d8501a6e3224573767482242a6e0aa7e5d43bbc37ed1d72857d6350dce9eaeab246235e7d6365e5971ca61168343c4b1861af9280c2177263e0353b1ac78cb6305c620b0bd2520f51d97208d1e493670afde482f6f3021cbe40ea83ad77d1c2053ebacc1242f22dc0a6d42ce542fff0ec1f1ceddb778a100552f0e408c92f0029ebfa7ea3b3064d883756b5a3e52a4a3219fbc5cf037919d1f61481418138bd59b3cdee6449b6f4d1297213c619c69a5dca9c7e4cd7797208f750bfcd10add7282805c02da5bbefa35b73fef2c374500814117ef4f3470aba6faacd869970d1551ee77c3e491135e629c1d6494fdcf62e6eaffcf23a333c7366fc4d172e5a5ae70c5ada7b20631ed09496aac90effe6281e00c2a7c97a4e7b32838c6c96dd5d6c83c597bdcd89063c601e2d20e4968878a0013770ab0b675ac91ebd65fc8fc6e31bba233c0c2cd5842b55c81adcd0e03e21ea306ce03e7273bb1e04ff67c99880d028d092a35ea46dabb70d35ba888229b7bd961dcc7e6ada20b14216004dbd0954b099a89c094643796d50022968bb4e709359b352fb34feeb972127d6696b8e7276e4191d18fb99e2f5862e68a9d942a7e69b32d30b72a5f92680baa2c82609a1901d2d24b6be1da66e38154b9be10236a515a554fd3361c74c0d00cdd1bd90b765bf096edfdb7b0f4801852c4121b4fa5d0ec76a5e72f3859a19136ec4a4952f8ee3959cecc6dd5aeb9f09df7f232fa21b64a8a2db2c655fa0d61b0c30bb3aecb936ebdf799fb2e18a5e8c04a911f2674150b42ec972e8447b7ab1387940bd3a03e9d9f45dbc795285810bde13e66756b12a244e2d30c4bcea772230a62a43d0ec3150125d6a189b70be11c915cd8ab048c835e9d058d4a5a8ed4f38eba409cd63511669826eee97841e11e4addcab86ea1e34f381722c3bc4e6da0b62944b4183be6bdb94710e3454371022ee9008d02d14c98fbc1fba8d7e41638c6950743b27c336728be6cf99eb15875e12ec935d9cf832b0e772c7e7873ab576467dafa777cee3fde6c9275c57fdcd70b660df6ab0d1cd52a44aac92795e7213ccad40c82bcc2604674324d4ed3c79bf66091b495515983d8b2dee53d746f155d330a826a7e82b6620d16963e2a299bcdcb7ae41921dbe275b7220d04640694453ea56bddc28f242ff424eaf816d8d526f29e1abb405f284a17292935ccda247086ae3907d246f31fed6e6542a024ae1301f6f49e7e38791b672b643e874e5d7570d7de580ee1782e95818f2b252af1e5cd5e81a8931e52bdd313fa46668e68134855b0551a6425c6781a40a277c076d0ca0816f334642154d724c35c74bca3362a2f2a8290ae80cd4546867245c7a0bf722a88b4637a781016892464012e6395a55b21c22b8a6a4978498a5c234b540b15d8c911b24f81bca209be712b65c9b1df465c8e44363bc54dd087398fadf9967a70df02c9fb9036b56c7f7918fbda5e79afdbc4613e2a417f79373e59254b0705bf7ac64c20e092c591a2d40707da9f16fb1b2d21975b2ef62d308d1ed9dd03a48cae7ef24099e21723b6cabf0ca7a3e592c541f52549bf0d4dcfaeaed314836b4187799454636fb727d68ce700505b6b5c5080c631bb2c3379ac071e2112146b7b516e026eba04372550117cf56cdab84dba5f436b9de824fe63283d74e29a709093a846153e65c72630ca10c838e4a1a83b63589498bc3fa256ed62362f3ee55eed3d979d6574472715c0213f06310614164c7bea4f566605e73768c37375e77b7400827a4a76f729ffe7d52c0666e8d87f1366d832bc8a3fb4be04e3f5fe9e67fc9c6b7302f5b55c3e231952e062a47c339e30ee9b0993e5ea0d966a259b9fe07317462fa4f4d7209b9f499e87e18e96ef18fc1842212fd5d312b6689f5e790cd84633edb90e372f7ff7b4fc47ceed53f64121f37303c60637e2595ded0a4628f1516e374084d72e266c006cad7cd7f89ff105e252048d8706877ce1ff21dbdc74a493b7e09c515223ab1a66a4373f5344340dc7292fae8795f7d9e5e5493cd22542686ad532641766ca067beeb18c6b42108b3fc46e6b684a0781e5092b2c2688eb3e09de4be7248add7b04eac84714b454218fb982e1dda0a8d1a71a1054addad2de9a2ea5672a657a1a35dc9a7d4518a01ff5f02dd558145b1a384627c60cf8e3379d1dea87201ddb03a3a472c3698af2bc9c55f8f02e503d9fe963ba6854636ebedd76ab333ba68895aa9cb94408a81efb06f0c38c9f10b3acf378f3cc5df9d55d2e05f6c72ef25511fb3bdff6b9822a016f8eb0ac796211442ffe6011ae77f82077953e37209cc06c27b383b2f179980eff34ef12fdccb70547f82bba225d8090190d07472bbc8c81b9344bbeb6e59dec4b45ef5600e5ef09f0b3732c4f3b7a1cb0b394872f9697c92d36019c0a1ba0b8c8828570376be76e99db9e6d8e0b89b3d72aac6720e858c1dadb2a0415bdd902db4eba2dcf12c49554afd739bd15e31575cdafb7296a71ec28c29cc811f1f21245cb076545fb127a7e22bef3547c63689702cbe72dbb005b7dffa5cc43b7de2e017844504a47546de3e94d6d6666666732220b918bedfd6da4199bf4277a68ee0e8c2c3c42de7f6fccbcd646789288962a8e8f172e6e67bd76c63c9b0cd6040e476a50dd1bcb7cacb55decda60b7d353862fa7f725f28e1cc196d1c9c8b4983d78364863726a9e5ea2d3b5ec0bb0f82c554e32e72ea1a9b08a234951c4ee5685dbc5171ebf51b0b52139969f520c94a216c668b251ab9330d8dea789e80118d9cbe33aceb29050326360bc6464775677f8e7d2a5f2d52e80e54410a10832a14a097a9428f7f948e25d4968157a89da8ce3c2bbe72d5e4262dbecb90c054d660222c584778651f9cea719c276f6211041f4284217225a802bd22fe926ac48806dd3c4699873c11cc3850b1d96b41007b070746d37272ff5e033287eb9c3fda7211ce47d13dbaed1c340f569c5534d47566a4ba7d72ea8ea9d530d1af254bdadf7b408e059ae574ea9921f3074badeeaaa71579b5729488493a48aa3a7617b0c6b436555fab7129062c8e8360b70e747bbe8624b3725f3b9910f09f220606c8f3c5da4de24989c2747f304c29cd18f2cf01d5b8031314e6cd5ed1b4190bde26d0560ee0ef28738ce25aeb51d2372feced8ea834f65379cbd47825943eefe9e90b80477c6538ee5ecc969758497face78a13fef1de2601a649f5d2b812a552b30ea1e1d2204e1aec4183ba24bb4516637fd629dce072322ded10381ee94072cbb82e668cd3c204c9cecf5a92b4a8de344335d1e12b726e36b11d6b627c79241f120c01e0f884c2a1fa1af15303cccc5f015a3b47383016445fbb23a74617d21b6c76786a3bae4cec381ac73e9df6df52064a411aa06d8fd4503af444488ddbae8db2ff0e306160579477eab1310ed20d83df18707b5037a9d7db6d093ec2a7f5d94aa35fb5e9fb41497463df30fa6171701f78bc182c64ff4aa8c5e5da0e59bb1415939be57299e5d7ac06ddd0127f1a874d2f3ca61a7613b1def1652fe55f6037914c49bac8cdfd9ecff1054e57bccc4ef0ea118c729d883ae319ad8025a22cee7e1b354d002513206552481ebd7a66ab7ec9437d2f551f5372e07fa2bc2ac33b19a10d7b0e3e7e434b50e086b5c1cc787ef0eff8726de3375204d66e36844d18c55b7445364c759b7640bb7426cbf1d92b0cb8a572ffbc9e9f95be7e7843d27ac2066ab06931f4c6aea6adb071eb7a478488d78d576655c6c95064e5a702582f6e8bb5a29d93dd364e2067abc3ab31a8d2184bbf72c0d237f03857233388fece4b8464342a1d189d2df3c780ba548c5a3849ff701f1be24ba724b6195190aed5e713128767979d26eef6522edbb14b742ef1d3ea720f3c0ce021243df153e75164db7052ce6d96b8639ec1097ef9dff0543f9b6a7292beee0420ecf8a6d7b04525d936a03e7f849787f0cf6b25e1b8b4ef3f06bc4f1312efa4d08f5f602fb75980bb296041d4c6e458062a382358fb9b2e68ea775a84666936e708bed5a2a5942465ea0f366cf9494fb13e6b311dd27644f074b6728f1389352d360b377ca5f1f2deaa8c2f8a2f1205a87b996b849b9609bae2707218b9f8bab63280ec6b31ebf799cdd352c7a0b0cf4f6c832f1c8049d813605d72c3c4e27d18b897da0be43d8bd31f936b4067ead50415b1d5022ea8bdcc720a311be5f708884c7bac65cf3d0a921924b01f47155671a49bb43471ba20c2abdc7283265718deecde40fe8e9e094386429df45b6a8273ce4bbfd57fe00ac9d0804fcf279deaf6a8532122c9c53107c4adec2fd1912781ac0b602d5a988cc3daed721ff95dd7b36f4249184832d0c1ad13aea028feeecfa7a5df26d68fcd0abd95726fc9376234d0fa8ce614f09acc1b8d1417ea78ebe658d4138711b968f5c32972cee06697916dab98aea36bd7743de5e98ba6c672cbcc290e7ef35a438021b53ec48a9653cd9eeca75f08fd49efa9ccf51cef392cac5a4472da1be982fa596a4f4ce247f905220c36ea65977edbd6eaa1b82c7dbbba7069fbe7d20e5efd824d33f3fb1cabd17dc95a05453f540fe494f09ae6336fe9fd25c1376894efbac44b43f5eadeafa141725dbc6cd7f427b120387a9c09eede940dc6493b3fe251b9e172866618d837491c16a4a4ad35974446ec5436cea351850fa97d40e493a44f2a19867ea4e4b828d0b21b5d5162ef87baa3aec520306898791fad58eceee153f80a697aa3a1c9c2e760bf9e4f4898c5d830e90b9a3f43910e71b598ce940efa3d725dd3383991d52ae9f99f33ebc63110be8476161b047e66b3a1126fe7584013165814837cea9af233d7d9c87dc6fb86e787bc9dafc527c4a97e06d7f25e754272ed9c55f1afb70c13cee2ded9c718066feeea121bb4a021747f331ce43c4d8472d95592c2ccaa78039a798a37174f73e8c55cb0de40ba3bc1cd832172abf9547256b11a4e66d3e45118aa7962ded943f3116e7c7c698959e652d5a48ef45028602238191b1fe0ede427196353ecbd9e8c6818c7d284a77f60d0f3c5c831d29331021d97951bf39b7a6e0670ae2e41b9d0e0111a7cf70b44956bca529596d72872e614875873bf40c9c84bd09ff3ba2ae74bcd15aef91494b53021b10f26edbb1d54e7c3bc2782e039c23d9500cc9c99f3ccb72e3ae4c3da744a8912a8882e397269a41d5bb868e23611c7e3f11be481ab3dbb832761d8ef9be9589ef480f3e42dd26252a64b423e5189de9d5980e7993470c8edb9062db7495ccb964710dccc72a00a32199d106a50183beb22d1dde10b9a37dcf53cdbcb417dbb088db848d372141d1001eee9cfaaa422db0b6b341052697be178db0e4d2dca310efc9c057e7201e396ef2758461fc4e95121aa867a914cf5dbb725258b2b7921d6f6ab02d5010d9518139dede6a32e403a7ebc4eeaab52f7a500fd289e7147e50dfe8aa42772ba773586c322877d94ff4febb26919bf7a0d5d156d310a51088daf2aebce5d33ba26ba4a70d37f6990fcc216bca291d1f8bba018656cbe7fd0deab6732fda272ac77f3a2ec9515bdd8a1890117390c74384ba01ef646be2c1e8f157c31749272ac9397b0546cc4f5aad7778ff800cf127aa5d0fbeb3c7251f203a8e0ec5d2a3381299ac998fc4b8d73594b458f0077ee04913fd12759171c4fa3656401748772b911ef4526dcbb664fc2b35a6d3df0645d3820ed452e1cd8161c35599479df083c8f8bdaeda1aff6d6d44feb4b63d5c5407ee8728a6a504b4cba926f27bea772deaba211734dc6302b0b953c4211b3bd6c8787e45208e2b0ab91a89eb263a9715085187d9c01f06510e98278053c53234d3be96419c20dfcb23ad8315300217283065017b70c356a68322f6d6f23c85047d5094ca7787af98eb46cf1aa88444b8810768f080a97aaea0f8973d27c5db4b3089989b97797634fbfbd4717f77a2f36925133228648693e625ba65bd25edc990e52ad9714c20a20e5d4ece484c83217f2b2ac71d50e0ffb3efd9d557519ecdd2a53a69743db59ffb171681e0e8510ba3629a7a329c73b277a2eca1215d7e6a2f46b99c64cdc291a8abba620897f727ec0f3d99b938f0f844d59512e7140729beee8a70c71eb0d6769d5e4fbe942728b732d1930314f20c5845ebe0ebfb9351c4c1ed2b7605dd18899e3947eb54b16fefeabcfd0a54dfb8b206148f5a17dbb6fff86462da8deb80a3b6393a2c9e37223bbf29c7b859c986354354193f6333a637c89f49cbfe6022c737b059966b26352c9429695b76f9c65c3a56fa79f2620e934cf6ecad464767523be29a271dd41eb4989814d289b724e8551f11dd213c381565a758fa896af96dc1382fe2bbc05b98f0d128c41198ca55d71cc98d8c313cd9f64587b93c4f3aeec116f8a4582722aa6d8c83db98d393a470945465f04b297ddf63d334cd6c0ebd5930f51d66356a554fb9479840a416d7e4f1fe54e234c7e372c22b36e3451ae2a80def1685272f579c5d00df806c7d3cf043da9e2720b1b8e3962fc53206306f33d28824f08729cf9024b4e5ee15b04fde30d39dad9490df42436f2474b7fd1569f942270bf72eaccc1c117a92f1402ccb485da2820314400487240f27c730a2b2758e6aeeb72c16c6708daf0b21eff671c4f537f6c8b57d6e1b55edc3bfaab158ee1ffe66005ed7199fdc04410d76ddc9eef90255810226d493e3a019536401219c32bbcdb722502055fa9d5b61a92f9921249f29b5072398448da5096fdd80372bcc762137205fda995616071fa23d2b3f4e3f5667575242f614f03e135518343f167092272f35ff836ac909ea42f6e10c39580d9b6e38abccf95663c0a8354ed2b38231f729832f73f52ebe0de64922df2e987c589879d33ad62f9dd2eb70b562f765ab372e979cfcd41d016b183a3870ddc5ac080f727a32304ba6ae9c1382f4f70d127417ff14a8e4a1d209c42ae6b5745b6eceb11f8de17c6715f4861472fbb507e24213e40b77c028959bba225216c09423493ca3ab6cbb57b7e5fd7122035c8ef2772160021311264d6b799765e9298249b06942c2ef6cf182519ca393fe4a721f93e8d7c13f0df60d05cbf682603dd1893163858238b591c05a275c7d5ba5e53fa72617f3e2688b42c2e5c1cf2f6d67fc7adc43d0f138af2aba5144b03db739bf16fef4b1eb0cf472ca5b616512f05afa588efe5bb7063eac66a424a9f3f9a7f1f72a9111297d7db9f0ba5965be49d2fbfad526e2f8508952d16ba6737ee10d1f672d5c125c5dcc574ec787bd707361f2ddfac73ada7145883907d6892047883113573307eceb6633c544e3d7d9f9a564f03921d9843e6fabf3819e7b6c0948d7a72718dc2501cac913a47e8d9efa5cf4ac8e77662c7bea1ee312c92bc17d999e872814a64e64f928f0d60871282d84064a651e36e3a91fb6011dacb3eb9c2781c6b6593a593ad028ebe579e5620bd4208b7ff14d5bbc3605210add1babaf7c8087251ab837125d59b8e5df1110637d57587cdc430e0d90b55499c2f47438af6da7289991fb3a282cd91a8ab0ce6a206bbc46fd6c9e8d0c7079fb3eb790eef5f53259843cae2f3890ab2f27777d6c07b583063ff57b2b75c4be863bab9cb99a250721865b998ce178733de49174c116327a06d27ed3e0ee12b7cdb01c2af1e4f8636b87aa951dcb024751bffcb6cf41cd84a1b89f469fcc9a6608e4e8ebd358a9672954fec8fce83855b5c9e8b63e434f6c80ffe6ab5c3d16528bc5c1a060a12f2724351b97f67124eb1870e9f4458fad8348345e0a6b183c63aa03c109d0beb4f7287cbdbb626b495c905ba5560734c2f5ee5f8caaba090853a62bd28ea58b4b35c486a43eba5d3e53f7d52fa3e9bc48270048ddd12fa4819faf88b0ab56b218e071ca7896582aee66e8b12e1f145eebcbb14cb85bba36be86f081a37f0b6f0ec72ddf1df9a5c97794e26751dff3eb359c64906c644fbb4bfe0ffc7c43606224f24be3d8c6a9fb3b6d683f3e714961c5b1262aa23f21eb9d9addedf2f3a82332e41d82fca16054cb1a271f42aec942710ea6639585c2200dd567817fcc08e06b767da326d9c5f2a9a9bd348be11cac7dca5b9a11d29c446d1e59d20f10c67e05e6cc7f5461369e509495315e41f8a4cc1aff5a7c29c0262d59e14ef972558b57c72bf4840eceddd258b77e99253eafa7749219a49d841b9c55bdaf9a18ba0ac5e140729616fb455e377ab13b23f2951ff39658b510df7a96886697ed13cc61c7302e70649c73acf7643f1cfd2a06f57d4a92151d32c1f2ee75055d80e0f31a72d0ecaae4320b52297086993a8b0cb5c2773092db807a5dcae5de38c4f7d3edd1b4bed6ee607364a1dc39eb7a15f284f46b3cc76ad60cba4ff63127fbae0b8b77f72d0c0a48b0a0406662ea0c0c6b80fd9acdce610025f4b909867f3aa063c82037290b3d2efe897c54ede23c5fd855b1188ef08e29994df6fa54ac9084898c2bd72e3e82ac75742b9218e48981981511eb7eaec626b5dcfed06bab1adc73f1df12782218a5d0981b91db7791a26ebdc6ce12a28aa43651e9ecd87064f107a938972746356b50265f5558509c6a43ce5d6f7d6f503266f6ca580b2d1953dd3ee337237e112ab6a8f278d876568bc4c59c3b610f15492efd03a217f04e0811bb1d872e621475354ee56785cd169542b039d00ab1026560e4a9d57b03cbcd90e4c08499995a5045d3867e8174b594036f1260d4b9220b2a61c9f23d90033e888e37272dce85be1710d9c8b1902a6918beb29dce0301647cf8b25a824096242ecc70d72745760d5597e251514692476d6b2ec5b6d1b8b650e585a8db0fcdbff4e849572b87896c979144663f9d8771a0333b841eae46bd1e428ebb2ee201629b624fc72f444f6471cc9d1552fa303bb3df1d83a8efae8fcaf590fcf16f84b2ca4f3b6722c8ba46f749f52eb4f659f9dc5c68d73e9922d6af8b31f8998addf5681894c0b44e0bb6e0450c1e9be5ef150a00229a536ab65e35d60cdad82bd33efb8d2c94db0a65104afd92e743d4e2f0a44a845f18a9e5994b0cba0c17f939304c8c7aa6bb251b570fd771e5b652ae7b1f606b91f75615de3bcc6225db340ac81f9dbdf2ddd9c55f776a25ad03e65397ace13ea5f7c134ff3561b3610e9a35032f0ef446606de749063b18b265eddfd062dc0ad923eaab82db45bb8b1c71ba567301bfa1b5277471ecdbcfc69042b3f0c8c5cd3a2d24966f83e0b8d5882bbc2a58e7bff237f3fc3e40286e0bd6aac07fb0a1591ebfb5d3ee582322c4fa36786984f2925723612ab8da3a15d5fb6f56d4a4092675fd02ce8c805d183d8aab4febfbe4915658a3443980c1ce6f014f00a243334c194556a6d505d32f743ff4f7d21d6d1e542df03fa57fa4163bf4af94a8ba7ad2a1d8dea1d3fc1e355918b736688d0f52a5411f06912e3a4e3bcc0231dea4bcbfc0be5be91fd31a98708232966b301d442724a274875f877f59e474469dd133d9b43d9e7ccbe323ab78386461a05d350562d1c8f41633b08245d5510734492d0f00c6ba1aa33eaf263434d3706258a3433131c9d5e6acf90c6ed18b65adfcd1a32b07cbf6bb05649dec6646b5d61e107a9726eac10870c438ae96b53702123a6e695e73ef6766c5cf3ba9f05f6e4d6659e72eca49263e95762504be1795e613ea3f19fc415dcec57a2680c838ba00eeef36613c978eef309f091c3ccb9512c500e99c1cde726341e2888f9d38b71b0456c362c506b1c1d4caad3bb00667ce8235edfda6edc57dccb8a836509c658d4cbe31e203d11dc589a218f6a4267870e59ae278ee45ff75dcbbb404b4e2ff7b51cbf13047955b50b6652695ed41329252a4edba4103e1dd1673d9198a59564ac341351a57da622d3f1025cc70deffc31c2e42572bbeccb2acad38abe1ae6130a736f7244c8747a6dc2b57305cde3332f7a8efa797fda9c7096336703d72e0acbf444729e3de6a7d45372407080e6b32adf032f25230d6dd655323fa864dab6f0ea7c033dc9faf7e08c6af1d5cf390dcb48ed342ceb1da79713ea69366b511f4183b17279d964acd5a00c79284c202d024377893696b82e1433228744ff946dedf3f772dc8352b9810349532c8e85e6c73c0a159a224ac2e4df05f8c1d9e96ff1b177727cdb1cb93c52a8c71130230b07d6452ef5a44a68637f026d8ad01e426c43b0667c378551e2d56dd18beabe133789375fcab836f4f30f6479d2acab145c22ff72894eb3e35d475e31ea4633bdde7e400e0bee3b1918b0c6993b2e4a412369f77240b0141794cd78951138c4863d59837e15d492135760a064c77da6925e9a3663c6e4fc97a0ec9f4f68525c680d063c216174b46554b18d7845fdaa913b1f2b721290a03c6e5545e506a7a56663ff814e24c211407924ff48e33f599f2286f13ba5ec1815376b6ea816fee4e7f9f61466e422e86d54de69dd9681ab476cb29172cde5e8bb1d9c45934c72041be554c8c216083940be2f900c0943852fe6d66502e3b6a153da75898dfac3e1ba35082e85a21099555555cb145d4d6cad13d07e725f1e013349955174df22993f8731494cb9e50625dda98078598af3f619f3d056c92a6da28bbe86dcf8195c94a9205485528eb4fbfc7c797e11337e951938c6725b36e1d8611ee199311552a0b74fed392f1a12dde1984437a11911c6e67fd634feaafab773d4b821957dd300b29059d9024ef62caf5c455d1850582b84534320580c7187ae0ad6ad15b525eef075eb7a0b50bb0b5847df542c5920fec94875502f4dc52e0331d2d8455d8a81ecaf74e4fa7c8de7c3abdae111f7c7c1e61ae172b9739fedb848cf0239c52ef2f6caf14f2d59a05a3b7539a2d9cc9973c3a9857297cfaac1a073474d0acea1f5a34ac19c1406a59884b5872082ab964cbfe82472e40cf430b8d36ee7e245f4c95ca29b4b472c681ae575425f3c8d7fd7ad032a72317f00212d8a3e14dbaa152011fef336654abd1bd8561fcedab54cd9a351e802402471ed9623189e9e7210386b85b30ac5437a7b983b0ca226c92fc3dd73dd72faec52d727131e4a16328d076ce512bdd0ea8f56ccaf27dc3d6fd1e108f36a721684c5cb758de5c53a759690318d56f99b720cfdfb4e9fcc7bd73de3b2a38943565807d15301de55527792618fbe7860a2e52d652afea3a0b6f442ddb45a9b72a3d8de73c8e54574eba200a5a2ebf96d636a00cef260fa5feec9d7e722ea4e3f3d403eaac2b87c5342fbaadd4afa848493986cd29bb4c258bca7a0b569ebb1725a7aff41613e3d9dd8ad97b579d13261a03918e537c455e3064187132cb68f72f85709e55f038802b722ab547c0f4915b0dc5557bb5cd03f5ccfa65c72f86672ba3f1b93f2fe95fb5ad2a22409e396da6e1f7ce1db071b6eba62045bdc2b1729d1ddc150077a231d6716cccd6d7e3bdd93eec71a965a7eeedc3bbbb03dfd61721c0d183499cd2365791000d864450924b089925d804f38cd5ff8d05977ac5172929fd32b3502fa837af2c3b841dd97e06c8e5b2bbd15aa287f3972912a5e5b72cc621dd2d3c1253614d162ce532dd3ea96c9d98f7599a20ff1798ed1afc01663e38371551b942c6be08683dcf54cba4694c1cb01850e37a016d7f5a497c54f4aeb5d73d7d599f0b60d43d4b1b8eff9338c75bec077b9899bc1b1a52064052072991b04b98f313f1a69a1b84a9d83aa0d4c304d0b1166689d3ebf5a4eac45f472aaad1aff238ed135511e37705f24c19fce3e00354f07d09dc9b502eedf66c072bfac68dc25d102ca06143c196c873306242c1fccd18bc71c2bfd4df059a6442ec2cc4941fcd1506359f5ad3bab876971646ec4e2d21611c13a0f21cad1b14e49fc22cea83b6cea1a83c494d7e403c8edf337b2c15e70fe483087cc5c2a8e7972aa82ff8eae52453dc427f4621deabb540149ae78dd2cc35ade975f35de34e01d4e2143f7548b6527562ced3e9563d97a62d19e5acca0c68570fe2316956c7d72ed7a79496ac95c42405cba39e136da634b8ce8afbff9282409d2801f32db7262f29f0ed842322d5fabad58a7a37edc7d838f4502b04a1a00f1750888291e6d19a9b5dd5a108897321b8daea5bec481858f7c50d6f136c95973461a3390d6d372f37c5ef5c789cfe812178df77a56a8b59c4241e058de45ae76db09f9a3715e0029264fef7d6b330d37481266b25bfc4faed380d910fcb9e7baa756e4424bfe72f6afee8d7909ccf911c9cba8a8eed4600cd4ac3a2a8bb44f5282b11ee3fd6a0e111998aab06471a8e66e045a29b7dc05db766d6a4960897bda192710001e6372cc7c9cf416a5ece0114a4abaf830919e2b4744807ba4fb95f6bda420e44ba272abf6478afc935cb2bf159be6fd4baea98f2055d74ba37c19828bd464738bd9388e131bbcf3466403fdbf6dbe6d6b1a4df26965cd1e66092a7eede40f4821d2720226cc9820a112938f3cdb6c073337a705e87dbb94e41e2d214728e296ca7b0146e9466589b3667d84b0cd75940157895a3b12ed642172cba6fc0fafe4f5f972e0adffc7def3e21319e791209cd4bd65afe388ff27cdf30cc2964bcd8ceb9b72c3c0c14ff5f640950b1630364ac251ffa3f4b09f468f4987c346c32da2790972085334c45fb67e4198f2114b5d506d97437678aaae833ec91b8154ebc6dccb72ff8806832f2a1389fd86fed608204c4312a2feb7e19b6ac22dd31bd666cc1f40c7ef74d2133c02bf6ede29ed7ddb01f777136905fb0ed8735bcc7edc95dfcc23d34b45b3798f11ae513d03306ae22ea59034956d532a6bfaa2087989e4646e5ffad835e96d8e30872fe30ceef785a0530e5d2326c1c61797d37d7dbe9fe71472b8d2d57fc7405391e0dac8bf4d556c7ed2f34ad87dd9048b46c8f88c58a974725e773697cf58ea498c69a7520748aa9e5022ac30ed71cd2938a92dd6f1797672d21a9567b8997b66bb9af8fbd14edd3db877de16d4f7a8144a0c2add09e32b729f6b88e2d3d910327864e0e82c41f8b9401b4b9826e0eaa4c17479960ac3b2720fe30c9f3269d8a40a108fe87546f68e97f6cb9782b48be829ec492aa99b72727399faa550692acc8cf5c727a26c7f48548fb81bba83fb7818be66fedc8eb1721e6f06b56222018bac038ce20e3699f3511ff9391895f633faf738c2adb77e7211bdca0588348bbc0b6ba4e0915c4d7f66d8ee0d698a071355e5f4710aec6449340425d82f44c97206aeb5db5fbda59bc1f0948e04522254871b9259f570937234c3682538dd4f4ee9f2374bf5baf958c575cdeff50712b2606de6c39ceef923d1343102d8adf21293aafa050c3c97ffef19496d4176821d6d945aaa4c083572d84cabd2094710f01b3deddb98871651f66a4eeb1d467f54d3c646659e5b5f722a190fd3276005a7e03473f20a94940632da15efa6063929729a3f2a5de4192486a030ea5c0c3157f6127ed34d7ec4c868814a5364c2fea9ec9e8c6e4ff57672bc7316881d6851c171fb262c76960f44030d5fe08b9fe7990ec577bc2581232b46fd80fece490d309f56ff1b8c6524b62d3df9855d08e8cce63a197d1239827289c8af699c08f14c753fd2d78f6d78cb4f67591b913b517d5ee974c420d87d6aa1b7d2c7163b58bf049db5f2e6c0e90972355cf779a022a7061c939f071be01cef0f764a46124db87b1b0f9bde54a01fec58b3a49d9180ec748cff168fed6941f4e8f68fffd29a0881dafc6747790e4588c29e333d1a6aa369f83f316fb2de728e6a27cf104f2ed59e22dc9e3f86f0b3099174e555fe06c10a09644da9866772e29330f5d4fb86b3cbaa254107157444a4c0c58ac969a48868749abc6d5f94725ad54bcb42489dba0d7213f6bd3d50008ba84505f4f1286be43daab76a0a3204e66a51d8c7a5bf6bfab91c995d05036727635f5b5012f1e982e3315bd936ea722dbadf346fb729acdca0de75f31169bdcd6b57695784db60bf9eb158768a321dee338aa6314866144faba962477112280b4963734e12aa3412a4811e4982501555ee8dc4b6fcd17248aa88b97c5e651eb9433d08cceeba84ae9cb2a3d8d4f923b8d5264be75caa2f2781a24242fd8c9a31f0dcf5728dc267740681dcbf02ed72e577b417163dfe2a47ebd47c6b7a43dc6ce97c7d221302ba879b39e64fc92c7290c778bd13733b5ad4b951df682ad2654c290603f1c79f2e8af055bde13e5d720cfeb1d42764a82273d7077e775645bbb2104668000cb2b62f47f2b3c7cc0a72cf2980db3436b3b5767226c3692f9d80ea25d063970fb1748bac9422fdda7a722a6fc8c4d8f7e6d486adef995d125863a5d64e20430bf1b9b202f178c5b8e609427181b2b200276d57fc35a1ba0e81f5c8658a4b8d23027f5ad52ce7a700b7519dccc17531179c7cbbd632cc02b395ef3b685865c7880b80b6ec8c5126a84e724d15996cb953d20b7be302286ee0936171c25a543b4e64fe1ccc529f15e13072b1c99fbc4b479f8e8b1001b750375cc2877377d78144d22850b0ca92448e675dd51a0588c11d345eadde70b4a74cabfb5825f64373c6600c0a024eafbc93c46b19ac0b8c016c2cfef74ef80f50a84b193a6b738c008eec3d7857196ef2afc9722b0e5e58eb0aa8ae1c60b668a8401a7ddcbce4e9c63dc71b0cf40ee5a2be494c672a74f6ed739db9c02a424022d565038fb68395bf696db46de9c7cba80a14365b89c915bee2121f3d8f95fee7c9470923ebcf1865b85b866527da5667c6ef722b8af2257bd2043d0938ab139919fc8fad3e1f94626be7d8622a0573901b5343ee551739d75846288beef0f3eecf928700aca1d53549e7c968351270258d4d728a238a93885e693af065f4fa97ca4bd13c03c8772c4048f452390636033ff872f2123d9d285a5f06891e2a109005a41de0d79505ca8ae31d5803c25bb8eb6a2ed7e250a11467cb59ca91d4f8f674f4e049080909c12dfbc27ebbbc64924a823850004d8b4961631532047b9318d99d216ce113acb6c8fb52e714274538387972288609741f73823235e2422d9c1b5f671f83ef73dd90b69c1bf85c94d199047269a7d7a15fdec1008aa76e747517ccd45c0d850886782cb03ff26f043b9a850a7221a2b436fbd2d17eb63532f22a94f9c15c5733dedac5e7475b1d64589c2f729b3917ba4f97f033bd017c641da8f8687c90d0106bec9e56f3ac083b0892350651f893bc774db909f00d7faff4f5fbc44a26d00790566c4e9dd0d91a393f6772266d8dd80c9d961f1532a0f53a4c9ad896d6b1bdd6ec61f1a498e83f0a32e32dde43c31dbcf6bec5f376c59de0b44a485d9d50e79ab706bcc4eb071dff5f9e252bf10a28dfb460dad1e02c2edd024175fa51e8d79cd7b0b3399b6008bd3ef4097c78922a22780898e0492d73a020cd6833d3f31b08ad015ec66bbca28ddae972ec572f0bfa6c082ef04f8f0c815df3ee707f9b1dcbc5d14a6faa009eebd8f4725c566ad6e4a76a38a2fef5a845a0a6fdbcb3865015ce2d4290bd6e2457d60f43bc522dd391bf912e3ad6c62bedcc75134f61fc0c0d09f64a1d988fae972b54286a232e6f076d3c9a352cb7c30f40216f0b8d066d88e65bfb5ef6b3c309f3fa4e209c3c2e57456b3ca48ed6a550f4d8690b1e73d8cb3450dffd0e818e0338b9207177eb9976e9588be3404abce5cf83da333028dbf80cc445b7d849815e2c1e720853d2f6438674c672f4ca745facb948b15d407904b062f3e75e5d29f802ea72185b4846fc83023cb0305c8de2b2739aacacb7aae0e743be00d548d4ffa91c729144a0620782ea126904a1470662f4532c6c53684a5357878930e91dd395b72942487c7858814d3fd1bf0ed4a2b0a1c90d2b20d5b038940126af9adc59730f4cd5dc7d4e7e88a3b57cde5210b688932014e7c8ed833a66805854a269fa21b372693c1021b53604605ea760377001d5f12574846571e2c5081ffabc0414d6024cf136387825bf96400656dd6a793a80d6f855d978f878274cc247de801f555c2141191db4c76db50189d6ca8832a5e9be0bef9a3d8c7a025cefab40dd0ffcde59e99585f5a2ee2e5a3c507680367991977184b4076eb5d9da36599318a8d9a21059ffbc8cc54bb15efaaaf511d3860b58d54cb291b764b217a4e425b444e46c724d37fbda139fcc7c85c0afd7d3312f259a7c979c6bafc83228427530a10c6e0ad572de2708c7a5220f6ae6cac6fe422b68c6e192bb9ea6aaec06de82adcd640430ea5d4619ab7007bc4556aa2f2fbcfe4b5da87662f7558b68fd2bdc324c2972e4195c20681d8098c199dcb6b19cb4311edcc6322703fb758c7fe08361c0b67231588100a97409ff73ed39ee653bdfa1b46140052c1bd8086727307c7b37cf0d94ddcd531f4c4eb4232d4ccc0b191544aec85a9fbf10970d8651903697e173724489d64dc0c425021d9dab287ca00ed6d253f5de83a2454b7a3eb18a9f3a8c729ac788109326a0873f8d88898afd5071617055d8f564b2ea474126e9610d427236da522457520cad35819a70e61360953aa4665ed1f224566c8886813d61c3725cab59c8a4def10d7ffd0a60fe34c5402a6f55a1dacf084ce0961cf247be6c72f67d0fc15860d79afa9e56edacafebaa0f6f30c53f6a395a8d982781a2c1a4549457031fa3d38704507ed213e0a544dffbaead9d7475cc7f60cf6991dc01a369b7eb2e2980c4cdfb77be63b8e14f845744ceaef9846668854667d805a7bfd872e8f096537c02fa27e2f834c39a0d965eafc67963da10d487d21693f8ccec86725d50ade9d8e75b24ac359fb0926af2399492c0ad36f4e91f5eae43879c0b5b6c88d10672bd5839e664f0cadf4d12c02bed0b06f9f1d1d3054f600ced25018f2ad49aac05bc6b217e6915132310ce7c67dbe15ff2be20c17ce7b0c5172ab32d72a3b17c03690e56050ae4c423cd24db23bd9002450b0eb1a77760697e2e3aab523a69ffb0ac34e8db6eaa5ddbb5e32e413c6bd5aa8b25b158125151d885779a72e1129f631d3dd00c4e06424d122f8e345e20f0060b582dbdcec83658997e466397214e3757ce9ef6a24311efbff7db53a3445c653b3fb2911e5bd136cc45b60461f2c78f2779554e4a6fb1d494783ec628d057390809ab28390cccd5794d8b0df5ff9114451cbb9047dd7e0cde1695a13c94a8a817aa9fe63e845a3fe9e5ed720358645b6a9e6039da93a0e6c4c75505676bc1d42f3a58282bb8975467291d72228736e575f0d2b6ab05ef5a6bae5f8b6eff55dee966675ab25d0c2793ac70727db8d97f230a603124d85cbfea0dc559cb7f337afb682c9516c41efea1256872023da511bc147a63a965d56fa38516c3b5f2c3b3b351e9286cb403737f531672ca2f48c639076255a19053d805c95948fd23a3b634be2fa22e3853c67a49c17256bea9ed12c22604c6c84c78154a234fee23fbc04c09333bfb1c14b18290787291f6def4298f3fe6c8584600d995d6a1994c6dbceed3d8c15d044ab1670a3e72e1fcd8d83e147340d0ebb68fc215b40c29ce1ccf0b0d39b98a54b0b09d72eb72575407c356a9e3a6d5faedb507e19ca01bc001e1d2f1524862799f59e0c15e4f4e81f38e6f817fd6fef642e73ac639d1b38ac1a3288fe31c9dc5951cea86a272f882ec457c114945250087e15a525bcc9b7710bd6944002c4f5920fe44092930bba58915b690e7fe4e1e995793ed2c6847e5ec1379fd673e2d3ab1514dc75f0186979f57264810c28e5a6f6a13ac860bcb4dc2c77547629e75e5f50f4d72ac72b251213267bdcdab3e1ad2903188ecbb3618ca508866415b114df2abfd7c6c10feb281c4da2d2a8ab481b7d95b0feab2e31502c9dab00e7bd8b486da274952722bb88a5036ec9303e5788247ccd2ddcb4688ee507beb75693c0229813554e0725cdac0ffad4da38a8a7a76258bc39062d4acc8fdb4ef6678ebf0c316a3d6d52e77db83f028f099c548f575485f2b7ac0e0fc0a38e745b7340070cd9d049a8a07078a140164e41e84fcdd7d0743467025891889626d056edee5852a0506e77b7214e2914ea28bfdeec85088411abd369bc198f1b55bb29728910f13df473d1372c4c0c507335a78c798623df73e55c3afa53bd2ab0dbb24f61d8091f8bbbf4772bb43ed532823ede5e9533f0d736660b45646c46399ba97bc8b4c2505fe7e5e70c549265f0ee870afa88d4496e7d199fc82309f4871920724ade8e231db3fa962cbb243dd1e2ad4056a20eb8a60f9af6cf5bf31f76a4014b20e4569f4dd48a5408af2320b5f202f4e80b994dbd1f591e6eb9f05c25507177f5808aea1c0cf7b7291f6840fff45d6a578ca50c0b65e45aaf0c61e0424a1248acf2006d66ebb61465d7a3e1c8153600a7614fdcdf3801c303395e4651b4b64217662a84f93a0b55b16d391e0ceecc60d0021596522063e90b144b5cd79ae57ad037d54e73f779172bab4f555ec0c31553ca342806fc93a068c23d07329da209809a4dcefef335372a851d142da9eb351fa7ddae86a1373f260f988a132716a1dc5f3af1a435fd972b8b7c3589efecde8bef51d281965a4242a58693a7da0c6cce5b058fbfccd0172d8983dbc799fc983f84904d15a72205af8f16518693640dfbeecf8dc8ddc30113840d03c1fb410a4b9cca50a43ceebd98a62dcaf5eb6c966c431339039975101f523b2d371aa17aa2a0c2148ec3c3e65974ed0b8ba1ebd786381b074d15e4e7299bb794c94d2f40107b9cedf83a0c87d22e07d6588e2fbf41ce71453bfd69472a5e0a7905e82c57aa1f3031049480607ef7921bb537c0ff38f0534bcb105e0728c4b9d744ff334dde62628965dc960f8d782338fcdca30146fdcbaef7207136e0cf1eca46d9d5e3d7eecd51063f0d72196fc21a75f4b45ea54b65b13b66c567221986f2d14ffac422bb4da38e95b6ae031c241a6dca669e5e64dea2dcb447772ff121fc03696feb9a5587654cc49c6ac0d9ccfb662e30f97e3545b492a778e6cb002979b7163ecce3cdd9a3f10d5a15f97cf3cc951e0cd13684c8b8db6fd1b72814f59c7557b2ce1c08ad1241a49b97be1ed9146e09b5e2e9f21848e92d72d4800d3e37abafeb37896a12b050a52c82fa73c300c13745c73c0a4dcd1bcee3a72158e82cf23ab93d7a4a11712627fa8962b48364c48eaade1a92549b2ec669235583c83e52cc4ce0238e79869f3afe66ac2798e3c63277f9bd8cb36c37cf38d728712834b5d131dea71158add01c9c35916f5a180221f6717f85abf1a0ce52643a59ef526aca3699a1555d47f11fe598910d04c4a7d68746925712a78a0671769a1c658ee85a89046930096fc0d2b63b6e29a6059de50020c5f4ef1f4d24cbc7241409460f4dfd0352c09a8728d56696853bd48220fde07abadfe4f45785c500abf9d298b7058f5efd95884e84eb7fe1a4b5f6ca1e0fd430cfe3463c9bd8a60720961ebdc5dd62cf006b0d9f80a422e1a921c130250a5c102983ac72d0c460872f7a9b3c732689ff94b9d7a1e096a00abbe2ced0d310d040ca7c480d36d249531a9878fac1633ea1bc05a508516f540e931d5ba01168ea0c901b6da21e56ad17252d593c1978b74a849c93935b04c36c2e026ffa1030323ba2858661aca62b27263f89ce6a2e20edfe7e84501583c1d77c3ef8bda35e53d5a72a65e957858250afb784cabc5d7c356d05768df8482bfe6aadace20e36098e9a502c35da8ddc072eff4273520c2a43e5f33e083de39062ea825ac56aa4a3e8cb1b57cf0aaa44a72fbc7ab80689987297f175fa172bfe3860ffe7705b1d665482e07b2c460ae0b72ec58bad4bf3df7c59289b8702f1f7d3829246df7420e45010c64f6ded541321a986d47775eb561e3fdb4995b88ef40566b4c1db4acc515c4ab195008f4bf0072810097117d5c318a8e605fea054f7a820d7fc33241283a8d5b123f36a6978072f33adf0f2038fd09ca104837d2f2208e599c39ac66e8fcb14e8591c8b6f08c722fe0d4dee3e69c2f0ab6c045ea795a8d858c0b19558f7b3fef27587a3a9af072bbd6e9f08bddf7c50c9e5fb6f694ccf71156783f38155df05bb39337c2b0fa715b8d5956109b610360bd5f2d3ed453ad0a7914891161464d1385e486a5e21e499b4e4044fcd6a96a31274614c47b0d7ca18511d190ea44c167ae6a62d41a6e72e7c1ee5a10ac4a4d4500da17972cc256a1edb75bcebe5cad55e07d70d46c2a727a442649c919161a6272ff234f83c7290eb3cf5a5f3c41f3c7b9545468d915195911674ec4803d928721c3c553c8a5a03019889628ab27cd822c8f3836efbf726ae3f005f074d894daa19fa0326f08c2940852b2a46adf22e60b8e507b967d724e38b28334e2b9a1b7618e640af6fc91f7a24247199c604d380c08ec8498ea70e4544641f2746d59f9214e8b72ea0858418a358b29e58bc02457a37ebd3b587297db039b89debab965606c11af47fd68cddb35cc169b0052c0b79eaeb28ab872a76f62fc08f3f382306de85d03e07cfab2aff1aa42c8a552627474312a48142037476f646e188811deaaa016b46c8645a65626bfbb6cc447979b943b7e031f30bc3d118f7d10c99498e9397cdcb1df9e6e137c9e7f853577374324858b209e72285e519c5f4a6baa569167bb7468c506e725714a280ef305cb6b637d7ae4f1142f1295a056064b01aab06d2f3a4cd6862d43bdc8409f43dc56f18b37c27f5572a09a2f5f7f3213a165104751cf8d05c6850a48abd90cbbfbdb0808266735cb7297a7244897fb267e08220ce861df02db84e61b8eda4d2f28c9e3a8976b0ba972fa9218c3893dc3e537a2ced56d5d0aaa5875140616234eb923ace87d82d5a740b731553e0cc0274a8561761c55a4fb0214ffae58b60d3c60fa695461cfbb2c6870c935247960b76d95d4211d50f65ea99c38ae77e68e945132e048abed93d56954f2d59acd7a8f5073735e57bab2ee379e784af7abc0c5a0b72a7a288f8b04415be6223c942579e9b3c00cb1f2b632157d24ebc737cc03c2ddccc266f9d8a2727e565735f52e422553a326b5208210c9e189bca1176bfd9a3a4ec8e3ccd822ba0200005dee4dd60ff8d0ba9900fe91e90e0dcf65f0570d42c431f727d0300dd70dc43172152a3acf9a9e602b0b603d8bc452f5759d56bd01dac5d6d96eed5d60ccd29738a7aab0a16ae06b51a10ab2de3ea5fbcfa37fe1231077a4cfea443debd76cdd51e40319fc4bf3f27641c11d506f04011ce492dc73dde8281d2cbbc0bc6a593a72ada13bb6bee2ef8b68d9116d918000ebdce25d82497c84ee7e2345fcb8f8a2721002af06c8267885308dc483dc740eff6d1f761f1e48c20f8c40193cfac8ff72db1fcf806ea0ef50949561a513c35ca7bf8dd3e53d857418adfedc4675be1072d0245147c3357837f155c0ebe3e8a0043ba7fc08b268bf80f7053396e26ceb72b9b759fb7f7c891bec6c1bdeb434710b2d6a6c326aca0da70dc710754e06eb4b70b21369c98f4f0f03c9aec89934894c884c01dd5c70a3ad6a98e5c69887780bf85b4201bfb1a3f69ff2ca83aa1faec448394c8201a1fffe31c979e2c4bf7d72fcddf9e45eb307b5611f66849314df9ba8603119b0fda16fe09830bcd626833ef6826a850d3e9af93a94efda48ded87d3d54041e18937b603d5910831780e872d854dc535e3372de5506807c602c792de5d1167fe37c3b58e231c534af2ab072995911924d6247109e88ca88fe7407d8021afaac37a3f57c553c77ca3e95641d358486d3f178ae195546f2b1e7ab140dbf53a06093936ba9e505496a609627117aa4e04e91c4cc7282421ab0a359aa23c02daf060df48573997bb2ad1019855df9eb2fc5b48deeddb241364a1c72671272af47bb096440241fddf1ee04a41440f8e49634a0ce4c64b8ec279c8ab98a6e18592e68caa5bc731040fac5f8717d72daf486c60ab4fe370dc2063a132070983b03c2e65dbd7892b22894dbf5e7f7529c95d17aedc69cb0e10a7a76722b8b60d7935d6cbaacf4e0397d7eea4b655f7288acfebcc365abdecd0e343e26eef600029e7733d7e19f7d67ddefa40b62d572565968908b3580f13d469aac9dcddf6af600f51fb91a10d782d8c743d72bb33aa1d12eeb03a521cc04a3bd8f2674f924452b4d0d027d096deaddfff78326186e3d76c4419f62a0283e1da722507e3c1a9e9b3c28d9031f185b4aed03b343d8725a03e4948c0b4924af9103991ea8949076b4f5b01ac112c7ac31bb0549df4a5b3ceabd581699d817503bf0bd75b18c25af64edd93c0031251c014b4b70cf3a72a9228ba26be855ffc8ad635d3941c7effc99d762fda3ce9cd8e31cc3816cf472b081defe71cf1777c4648cfa5a7389044e0c03783fa38a9f6d1d62f84da4905b88fcb011e2ad2c0fda21f3bce8b4f26c163682738387cf3d8814baecaab0367298a4d007461130a0e0a2408471ae5388ca43b88ca45e48459231113d0fe7f32f1addd51fbb28c390a2df59118ea5c615dd155e279de93c520df4c79d943a1772c243bc78d92a6ea5c760723d30dbccaf77d0305caff3e75c406c43585a6e367212fc89f105886a787852d355130603f9829842f7b3146d45e0af5c02d9afd372bd611102443d77e2d44f1e934f486554b12680ffe555b6878a9cd773105bec331549fdb19cb52d5ec71c20c5b151038c582a518fb6662d4232408ce6bdd0aa72c9e5273c9b7f230d955c65fedd6b557cff0144ccab68d20e412a98fc45c13f7296fdfb078ba02fa958b8dd9a377ed9dfc9296f9776b79c8bbc6e6338ba9f84720fc6f2a039a61cf81dfe496be4b5b4c7f6e7d2ba92c1bfa1fdd98ddd73b0c472f8d68ab9bb0802a8685e606298270977e102c23f2e614eed0187c3d213e78d720cd6ceb6f800503c703fe76c93df733e689f3143a8f471de422d29a71e636846c37099629e578f54b9d49f13d065920db3f86664342e281b52c45ffb8bb08508cccc4098c724c98198bb30345507c1ed31f781b65a9fb0c791912a88986599425d51771b6632615cfbe5f48f3888731b2bece86dd1e946fceb0e2b463bb6bc18e45476ed2e59399a5c2276de167f484e52ed036228bdfdbfab9b2bfc78bf1e2d9a2e7070660b7c64c7be16a84a56305c95a9e450722da6dbe83812e58a3fa867caced086c8c7fd4e27ea0f26ca741a526105d4d5754c6ae3a5814b61ad7f8c1ac30139a615002562d44fd3acb8e63864586634db12f2dfc07b88ce70337d8872a141ebf36da51237444d7cf9b7fbc97c80018c290249f1e53ef13582b8e2367240046f7b7340458e9fc90afae4473ca42ba5258cc849311a754848eab9c7b972adb6d7a69e0c2ade0f394a23c919c243cf466cf2b5cb93ec19b19e4d3319b57291db934028c6c80a3266def7686855ef86d5640629a7805d1993434ef43c650b373575c0e6908dc6f63de7d46270215a8417ed52b75772f7dedb085560172a72e3a9e9ee48f78651b70634df258d948d9e934d314f56580c3f1942b31e0d7772edcfe221f8f9cc58729ce5124651688407e4155f6f72e22a2101a653d267c25eeea21dd719375040e0788906739b5bbb0a586a3d34d601ee05e73de8d1049172533d2d118dea7d30d987d6e37689d3cc071632856c32beb3b16d77f7e86d5061026fd850172372f116238297569527c5dfb984b29ef3fdf77c078182a0b17772c99336edf95c8a597e76f7b592c54609b992dcf02b5029adc90fff80192b2d727bee06d9d31698515e1619579b87d218161c1095c2805b421e9ab43168fa1e09638005052493658551a1c1857206888dbc19b1188ef2eb5a6d2965742c5e5172d1b8266b9ab032ee243752ee9c6c92b8252466c93ff4f6b3cd696c12bdac925f98a7bc1329ac5213c5a0b94aa9e1bc9d78d68557312835a4e5e806388a4af97296be971c582228d80ef9767065b991dfdb27fabb4d73d44598a49f187914a640e7008b67dc9a4a2f764e183707a78ce1f3e6a2c26f4578df2cb83fc96fee7372b455f72764150ce683f6bd784c6453566fae57f6747bac32a28e12262976a14b4577cd43185f9652c53b2f45706f68961612cafea5a1c21a468ea7ccc88261728cefca2b7c2ab785c7da589ceeda39a1594255f99013b903f06448f855c4ee722c19d7e88d50adc52f19229fcc52ccc0967ed2cc9ea9e99ad7f6b8bb5a5c154cf3578565c9a0a0c0bce796f51e5ea1c73384a1e6f3b3680089ea6cdf1fd7f0722f0db23e07e705e1ec30260438acbbbc5ea6fbeacd22bd2c68793c086fae2a72b7872ba6198271d8657c2d595b4cd858cddb7ea5481eb9230193dd43d815815bdfbea03108df0340b0b74b53fc9a2fa772d419362de46b3f3d96501fbdb559721ec19c52dc14c22567331e1bbcb1e2af44e5dd7541246ca64863e8484b0020420bf9ff449afcba9bc8c3909e4199fd90d636e776b0399b22cd291553b225dd721cbc3ba2dc11c86f2aad64be508dacfdcb74eb4f66a838546c72d3bacb088f4d5de9ad58e4c2b2c13ca732ea1de570d3e3a6d5bdb5b1aab06ede124f3bc92940181607fbcded3f0d6f9177b5003b79f77b91eee789fbb59dac4747430c46a81da9d7783364c6f279b9efaf41d9f4d5eaf5b8ed7f1305676fb1d022d7cb3fbf728f384bab1c5822d12bb0fe79e52ee2c9900a267b4fef2688df5036d42421bd72414869aea59262e8b7a85463f0d33a742889979581db822dad6051202df7351b8232ca1c16da55a462096ddb7dd4c5b45297928fcb4da0781d8090745c10053bf33c0140e7cbaaf68fc0e942c965f4befff2cc03b3cff3b3eb06f99aa255b05a044bbd1e1e710e9836a657cad8100394548c143d5f0c3e82163459ac707b46724bfb4da2065a1c5e812a01d154a550b6178698a3aff4ac06512fff6efff64f416c4e071eaf587283af050643c800134a8d706475e7f98de525b4861cac5b6950b51f44bff751842c158a0f572e22fdaa4bd98f0086b0d053faa5487f046fa56733e333bd8d9a9a32fa5bed1e61fc3a8274b1b743529e1a8fa1423d1fe58674068797100ad400375228d22327277e7a3e54311e31b8a7f259ab47ad82dd589557325273f568d994ca87d536c5ddc4c930fe82d2f1cdb89a28bbfe64f1095a813816a029be8bc0568f1874ec88a6d33e0aec69ec9bca8ca5103996ff429900a5728233b160ed1ed668b19350871124e0ffbc5e1844e84a2f1c6f01f153ffd6c772c2b808f1f7b77b81c705622d0e0070bc62be944390570dd527aa278e7eaa6d72c910c2c31dbd0ff1d91925d1088e4b60d35e515bc69addedb3931177b240fd7278b95f85571b945923086c722e49308ddde551c449fc53fcaacb01b942b68f4d5a05233b43aa892f93e062d5ce06e9e2361d9197f354f255bc1ca4a6a3c41372fc39a8b36b7a8d2e33bb6842ac255712b33bec30391df151e8d7ea4aa846f272ce354c25c11ed0c443b7bcbee15e84631c847292db0e9383fd597923c1cd701f2d0b16b03fbfe73c8b09d036cf00e3bb3f19f192474390d412d2db3d379da7720b726e7d086ca6f5fd44deabdb53fdc4e431f63119ce99294416e82973a98a300889a10ab8a68b64ea33adfb278a0d64bd7767a584cf7ae4e4ce1d25aee16e72c9d177ebcff27bba4511e3b8f1509d2dd7d825f05b5844ef802dc1a3371037723d023052000dbf50b05fd6a6fd9b6dbaf10272f301f1f73c23b687a2a437147240202ea40e0ac8557cd37076238cc7c4e58153478b39084d088b5b7dab98b15ebf3fcd083e7a4356ce3a9261949f9e1f89912a165cea5fec907da33cd85122514ccd7da5269e7153b24d78bfb229ed032fd892da0a0c1ea145e50e39c51be072b8941f9174e57d720bf134fcf943f1496bfdc3e078665d8feeaaef57d57dc92dfa01976f4ebfa60f0197da4041849adf06a693fd2603d8136e9d095621f8f072824756d023bf9e02019a08578177654b827d3e60671fadbe14f4a2f9ccff837206e5e504600b507acf16b607c1fafc48096bd38869bbcda802e2fa22d9365453274e7067619e902df691ac48cbd9b0ce663f668b6e39c11c2fcc8c724b83fc721290e88781a9d6e81def065c7c95afdb6b521ec6421a2977e357d17a8cc752729933191f9ec3fc2dd9553688a1772fe17459d18a79d53e3a42e580a92cae9b31c1f0fa9a2dc2b01ecd3fd4abe3f51a408e2db4bd765a02013121df5f1be72520f82d4f83caa43f96c5432d99575be875bbaacf3999d67018386de25d215db072818511feb4935d0275f0567e025333d33cecc4be7b47e3ac03c6458819536c297ce1673a1dab940bfac3570989ce0c5f7054315b9d2cbe49aaa462271b7ca544ce8c681c1770398c9129484a7a6f9c4acd56b571fff31a6ce2d52e46e9ee4172b398cfbf9a6f5081c2591b91a057ba277810efaea7892ebcbe34737077c6dd72065365dec3dc6b84012820127d2d2a77bec6a932c90e3a701dc32fd6d26a4647dcf11c654efd5a8e60b305434b97e5b03c9457442f4d70b6328a561e3ffbca1921afc7ec8273d5bf48e6716655381cc245c8ec09126bf04a357ea8186c252a5e55b578a5d5e9bc459c94e8c63e146ec6d053739ebd6749daa2f0251841d34f726d042671bc5ffa87892f0a14a5797da21d7a6d93fa5f6723702e107a02215472682a8b56464a22b7e195bcb902994c3bbe466b65585a59ad51c87e3c4a620b31f413f3eaa0065782b64aa928f687818f42f9c289ec1d7b0d30120ff07ee9dd179ae7b5ee0925d3042a97daad99354b24913083284ca5963578595533b8454b5425388ec2034851234bc79ac969c2816eac65d5629c3afad1f1b9381bd1db3a227518609aadc4d3c5480a096896b5edcaa1232fc00d4fd77bcf9ad38675276572cc306a9e27403373c8f01a88790ab468410c8307fa615e4decbc3dbbee638b72f05b068b4eb7124b88c00badcbe517556e72db60c23c3919ab93cc35e8fb2c72f5d70db5ae8aaeec8079c633edb62ea5fc98f714c47edea7d9041311a649160a9afd3b06aa39cb3f579e44621e4b4eff6e7de1e3449f97b511df13975fd7221cbfaf13a8132f4168b665427407649d2680b66b657b342e730252b65ae6805f72a9d3ba8dfd3708e10df14c85b422f928f374ce0f3381579b6fdcfb919afe7339913d271c7b8d2db783e6628e1714931735565a98ecf15f4dd2f37ae8e025031a656039745656d5659db67ae807527253521b6ff67fde6858d15af797b80efd7206e6ae9c253a6c4d27d871a5da9a4ded4616379db9a1acb05dcfc41174ba3f72ece16e928d990f9ef4ea9f08a1bea27405c81751753cc128c81995fbe0146906f6258f4b2926fb18aa22d9afca8c2e3c427758cd06f48a0cc8c3837e67b19116e44366f4fc5622691a69532eccee7173fca72961f26b5157a3282a876aac8372eba91bc90a4eab8678a1ad978be63c26d0e1a9fa5392bfe021d2050099359c1ee49ec43c14cd66b0a38c9309cbcc3de0e465b8eabeec19278cbdf1736d3a4d72c483bddc573271f99ac26d472c5c4d4e5e4eea6cfad0a5fb2038f3c33ea08f72286ef99f8aa68ae36bab7cb8a4eb5e3cdb3987fd0b81f722dbfc2d039b9056236936295679a11d59a6d70fbe91278f283c774accd48038941d2a181e2280205ee36c20c959efaa1ca73768c28a7038f79f575d45c3787652fc12d476bfa0643a0ef11880db1511d1cf3dfb219e43df8fc84b5a3cbf0d9d87d5f439f14e9d9b47914a0beee03779066cd7886e1ed2df4e4282b9bf1da2591274569cdfc4078c729c2316cfe8410ee518bdff57bb6a91d8a13fcf4274f63082a147c400a1fe15720b8fb492709740ad0ddd050fa61e1fac93756f7e707cf99a5bd0c4b0015e3d727d74135513846bc0629a26eefb2230fae4bd172742f34938133e07fd15efb3722740259ddd4ff6b52972243685a996e6780e32cff51e00ccb6fda2ecf42864724e94dab3df7ef160a8364ee028ce3804896d98b6477eaad798df50397a731f18af501b43cdf07f7f126d031dbc0cf09c4b39ba56d8593477d694aa99d899ba7217fa20fe1bca5b6dd0643069b2b1315390a8e06437159ea44a183cd6af377a53473be0fece38e75678f100f667278867da08b0afd72ddc05bfde758fb8fa9972a7b7beec4b8d54089b6d2b06268561ad5738329ae676a8e1d277f7ed365e387239f313ad8102c55b5e21382e424196cce17329000a4ac21a39329cb1ace0ed72e9fede0b8a174192493bb5f34cfce1dcac610ed3b5efd6b8497c7f4e10f364720af0fb8c328445428d0ce5090f460473f14f98707af0d6e221eab783b15e2d721e071a9afc6cca2395f3a63f8618cb6431d111465d8a5f02f84dc19b18ea54723192978dc26eb7023b6a8e62fea3fb6c3fe3e8f99f6fdfe44a776e46f1c741722d0354d0dad3816e12f912b0a374223d1c0cf8411d8db8545f08adcc2989d07210dcb1ce01e5c83aa678ffa8dbdb41864e8446ea60d50a76f4f4b43ba1fa802480cff59a24a71c79f1ef4c6076a37a1bd9117e713ee15dafd421cc9eb5e181585951bd986cd0e75a5bb62032b985d8a31b5662ece5b311d9ef1398cf0bde366d9c339e89d487562d1b6772f79c751205fed8cc7b45ec3b0b61e947e7ff4acd72ecdce9a0be96e3d5926228d4d68b88a824030b892e8408280a84c5759962d772ac9cd3211a8d2d67fe237a57a21625e966cfe0595307bf304067bfceff5488727df28a5b0b9c601bdc4567382254b32ea732102bf8a873bf6adcdf55f5702c72da32da79f04ecdbd3e73152c1bf39e0052a97be27af3791e9df1842b9544aa69581563d3d60c513cf7726481aa8e1c44ec1d3e821b7330f44cda3afd6d2e2932acc6a29368bc04cb7ed527a3ee7f88006e1431975a3f074b73ff167f2662c41deaadb6afb03d8738611273bec0ec4054ced28855ef61bb8cb42213c642b6697256c95ea104a0671129ce59abda31f762a25a9d4373d26efadff633cd896b6e72b7a1ec6331fc4f73d3799cd7b9e0d84bbdb5ac3c9a5348b427178326e3db164e2d7fd331ef417cd927e5782f7756186b70d1a4125da7fb5be04afc01e17ced628a63bf394e27879e524e6b2323a8d9868a2be021db84647a6717c482f5983e074fab7a9f82a7b529956ff3a8c6afcb83ef8d9fb1d9707cc99cf3b5498659c612a4a69fdbc297f2dd2fef669428d73df51e410f2bb43ad95f80eb0162d3969972303a8621d2fc0125953dfe5580c88887e975d71979f3e3e52d91901110e76a3c76673cb00edbe04a820fa90f8c9203ba77c93f6c49eac5bff8e66b76cbfa545f75f18329ac2186247aaec68a56ec224d17bfd81fc41873d516759a32f352a47203472e0ad1dc0bd300003dc873e5a8254668bcf33af7ef9dd2515b1da9687e5e9dcd6329576485805a749273b8589ddbca8f206551573cd9e83d7c1c9a79da7289f7ee721433aeac0f00ccb802b932a7c759f43711b09842f9876c4e238d3b226c98e66f39cac92de5acafb906bebdb1148fd815f22b43a7a4fb8a9172d42428df1055066d652aed1359455385d2c88b90c03e2b3ffeb55de44964ebd2b78e722bdb17973bfc8acd18ddfea07adf8b14db40c7480453f5b2d9532c2ca7276c706c495ad9b55b1a920850224b64f956f4968934317e50dac436908c80aefed37292ba239466eb2ca217f9b439c6e6eab189365e982d52d577bea402325ff71472420d7f06d83758b55e24ab00c1d5511ab243d1f3d6ccdcf169357600322774726fd521489163183a93b124321fe70ee96cf6656f54aa1a017b2f5a6deb5abc3ed79352e0f3f5510059c6e271ab22ed59f132c44b129f66b2ced24d211cde642d5d161e984938b688f731cc3306f1577dbc94bb573bb2673574c38102c2d2640a9f75979645d3d45a0339e11a308ea220601acb3fdd07a161ae2f141441951f72addaf7d1240598d1c4769d865d027c27315693c1eaeb2b533a773a5990c1741ab49cfdfcfb76d4bb44e60c6fec53a898860996352138c8c2103249908d5e4e721395370009ad12b36f7c221fbd54ffac11d52a48502c149dfe3f2f03eb1a7c726fea85062e375c2d9862b65852de5c8f79c8030a3be40adbf2040b817939f97208c4cd421a86eb291f603a62d330c9324d37fe1f507570d8064ce02c493b2a12b0a52b09bc65c9926e1cecb36c9c30974562383c654bc26df2d60a36fd4d6272febe24289f7269afeb36f2c7b8baaf8bc37307167bcc96c8184dc5742d4d7972d9b42b5c9aa975b06bf18fbd1cccd5b9843fc590e34d537ae506ec5cf7518d7262da489b65e9de93e52b0d41b4922e449eb6e7a09f4e7f9eba97ca6ac8632872e612825c27316be9a9b85b25630dc1a7fedb9614c06750ebdab993b40f5072721dac17ebde319d742483a7d00235f189c2d394680d12993f9c76bc02a3f3577291d7f21ff740c11b504cf9872923e5ff40d7133d031ca79c6a321cf8cadeaa3474de3a189b09bc80147837211725a3b027c83f8efd9ab2f9c8e5186313857458aa9825fc8bd4be843d618bd2002dbecab5ae317adc96514fa01738450930957276fc089414ecd8861e7cf2a2800ec4447318cc0ede7df4b2d4c20c42441a763d9a7e9199188ead9f052a6a5365efe413d01d66a6bd442b042148bf470f3f5b62938617b14106317e0c06e11cde0e508b2ae77cc8300ebcbe3d8dabae05e1427275a3830d6b92b4e130723dde256dd229c63c6909079db2594d073cac416957702e10b1d8698580b2c32176d041e7a09ee662c4071ef9ccf4d7e6ca27d96de3724b30e1ef4d4bfae879f47a4d3393d620f552e3358c3b680fb07e86d8f2798346dfae3f1f9fffc75c863713e614e9faaa1ff5af7c3ceac9362f3557e20cb1ea7270180246a5180ed55d67c3510ee5186c2c8cbd7695871b180635ded967259e5d0d8b37462b04168f73e0861982081571e5eb64bf2a7324eb30cdcd63964e5a1a96dcdd0e2a7337e47d5c7f3ae4753b78fb5c771cfc099a25987157f15c780d3bc3feda941ef67bf649fe31213ed60c04e8eb9ada6fe0b82ff05775a9d834d546e1fad3ee77119a09b4a881b8b77fd4afc2ff739f8e9a9e69a32b11a6e75ec430623634a59e7576c0b7d3610b2b1d3b3290522f5fc6dab601fe19c96ca375ac523473df5b2d772b572ea9f39d1256093c3ca4896fa6a5fb0192dfc1dbc47b9872b8d20d7ea1b709b07e76cd7ec6f297ed7086b752a14ee51ba08356e651060f72958113fe9f173296384c405014d4e03e6792c7445b0b76ac4a29e10f1a24bc5b82d4a64dd6ab97917db0238cbe6be39e3f9c583559eed8d6447d8fd22e9be41a0c4c0eac4a5fd4ea38c4c4278f5a4c94aa47f308cf581b69deb8f8d4b6708445ebc0eeb3f714918fefed3a2033930ba2b7cf8c27581736a4572740372f2ef0233880871a7b9ce4d885883cad0790151b87175b15bc9d709e8409781138e13b0a2f19c120242ec06a83afbf2c7bf417edf77528df4a948221d401fcaad2546369d23e92279c4c2e894016a521bdc01ab2775690283e88a20532b53e9fe43b3f6991ae32f52b233bf047bdc58c136005dd965629e5471396238365ecaa358e8d72061717a88175d8d1b351dd94f64fdc9791f9deee0586ad82ec70550d06ec5c72094122de17969573d67b17a0f767bc72434af3b2e68b9097d54e0e6d7f7be34229f0d9dfe5699c3721d2abf3948b3622a50793f5082cc29f6fa0c7d12ab5bd3e86bd9acb925da10895d7337f3b1b11c99f6adfb0a24f1b9ce443a4e3b6349d7222b38fd4ab5dfc1c62cd61f2469f47b176d1e5d857c3c61ec0e9b21b03fc770c1ab8525f3c84dc4b2381d51cd84dcb43fd484dd97882416cfda641e109931272cf9f5ee6223503c6377f2e6bc51938bdcadf1b9e86e392629e21668ded2a8111aa6e9b9cfc113b00d19938f18e6572c9b92b79eb54a0978d0cf05bd94b5a7028922fd345b3ed1d4113e93fdd5887b7c5a8ab9dd0bf304f5cd8251e9cf10dd12b3f82ca4d878354599f9b8f61c9ff8c525a2addceb8cc5ff474ff3da650d03f7201cd1a23acc26cab6d707771812bd6d97154d56d4a3522dbbacfb611b9b2977217a20301eec63af2bbb6b9d6740ba4629b718b80abb2fa6762f1350050c4fa721340284ca2f0b819f85db6423e453fd4ee07a57bf398d6daaf54bdb5febfad01f422a2d76d4cc6e75cb88ccc959c902e54689350177f49b98764ff6240b1667292ec0cb5b940936e552b6c28f77285042255bda9c391cba414c8ec07fdad25723cfda82d54ee943b6815b99d6f94ae96b484d149034d5a7a6d621d4a90b24972f28bc2bf2c615311be3942a331db3bc28ac208c1c29df80f7ae4c206fb69b838a23e4e6abb6c9d6f396dd81595191df2df1dd6d4141876f7e5874aa2a2d8cf665b491729e38221a891aca703936f2b85ec562f12181d630611b1e3ee34084872358cfb4c8754c3bc6e379c44848c1e58cf667604ae06a14ced19403356191472643b079bb5e480e781e350a406397bdcd837953780db55574a57866f5f6aff7219887747650831a783b35e25016903471e345d0abc295ffe3818549b3cbef172ac4594bab16e7bdb97ed160ddb9c8540656319eaa4332e5914495db0b3f01972413f58e38c6f28b8da963582ab70532f51b0562b0c0ba3358095ef5a2c127772509770a138a412336af9894d4504eefcb572669ed7e781871acfe2d90767f13f918467c6a82d5c7cd646cfec48b76967cdfd077d4ade99a08fa5f56a97cd477281b66676fbf432fdecd40ddd7907b67908a3ee3f29c1185105bb852320e5d82792795e6cbd3bed30a318291ea41e38584ff517d64fb12b0103ce66320e8e682c02d44159473ff6c1970aa6b04246aaaf0260d3c492263a5167d15ea46d6983035623aeea98f987d707c4d6994f2f99444590a97c8fc40f89f8639e3b6c04ea228c84c2a9685edc220b44df98a819a7cb042475c7d853d23095fe4a179ccfbc4d084a4e6f3871a227f718f1f2deeb0ac4be8ad2916c44fe9ff2b5633a31517d72d11031e51803a72a9b98c62148ea46b3d114552e75fb257d3727c85ebb6c4d72d757f6b7f4ef4e8f007377ff7aacf5b96248cc8f4e70c70c4f6a61b364beca1a99bd103706d9e5a5e77cbface91cf17fdcc8b2d091406a4e521b8eac534e33726065d71e397b53bb4cf8bc1f876c4f644b956e855f20526482c6951634d4e02e581dd9a0d3122fdd9662cd05df4073ed42e647d5ee4a48e3949bcaf5241a03727ee601376f9873f93b19d4cfff50a04d944c1b43625f05bafb139bcee4047272e6673425b45f1b86c4e8c2b49ca88f5682c4928eab692e732cb2e1d682113b720f3cf1bbff4920e057d54de3fed6d2153edf916be11444a25b8c460d353b4c72568c761f91f75403ea51b89b867d06520f880b0ec30c9631668657375e91b26a452bc58201b224e89c9ea118b38fdd77be2dabbcc03f22f2c414de28b4040d1f9f201790f171022af92d5858a9881c2245ecde766e9d6a58671f60a0dcf89a72a534b1c74a178d7bad407c08700336a3cc1c0a29d7529897962b7f80a1bbf80179f4c0aff10d49f13dbdf6f32e37e58aec1f977278758d19bf4a0b100776bd2639877ac93e3426e1869409cbde67226bff882f0d27f49d34fe79d2caecb5f251dbc57bdc95d4fd50baf52122b3cafb25791f474c394178cc2d7df176fcc8b70efd2103675c5f81619f336a566b15eee98ca607e61e6438f3c438b03a3306f07263aa77de7abbf438dbd388a6b3939a0bb659eb6c60ec2c4a7520b699899f78724674a510e74c03794a1afcf4ad15ffb9a3a829a1ecbcc1c5351af8543c03eb72b52c10615bd47d290f3df2b660804990edf44eae9f2a6d30daae1215696fa814d79dbc3a5674c3a877b456b37086ea8fce2968ba3a7466b9c544d3404c09b7056b7dbb4832eecd5d5f91ca703aa86aa7868d4f251494163e6cd7731e0ffbc172e091e43a8816ef4c44df6daf8f09e02bafc182c2e0b9f2b46e99c73139d9294b25a7caaa80c5b54cd79806b66e5462c1fa447ff163fe727d4f617fe4955f4e72195c967f95d76ee78734154d066dbb763b92cc68d9b8a4e2efc46da2214bc82525588bc219a2321c9ac059fea78a6824c13f7c8bea3b4b8f75922424b9ec9672b73217a3a4f0c01f6a35c157ab2867d66972a3a3c588f16a5b27474d3f7ada3771a1d25fccc1be07da0a62e04167751b820c2ef765cf6ba60653f91b77cf43724813cd5b61024654db26e1231211e6a3ac727cf082fd41c79da1e7be7055bb72a74dcfb009f1eeca67b1f72c080fec0edbcc9817c1be511fa18f2a553b0be20204b9c2c81440bb4befb7fee44a377bcbf613cb0c1227c1f4e9622afef657366df5cce1d7cdb9132b06f8bf651e7e2721e8771bb5bb94cf3d1b8a7efb59ccc172325c9f228d209dc071ae08ebc0303d5161d74b88ef9c94f1f1640c2e39204f725a63ef1b267c76d47162900fdfc92050f71b6f195c0d42c3092c251648f40b451959c8270e80305facaa7b7f8df3a58d95af63cc79e92a04eb07b529447c0972e049096e2444469971e065aa3c968140d60343b63427767b21690ffc1de186637d4dee4f1170f20d69f1c138813b2bb369cd4b26ad33b05eddc3f839fbc7da55f52f8042315e1d8e274bc45fcadd4564f0d3d365bb98775d9f46d3eaa1241972f42fe4ba3c3cc6ddb4a5d40e2f5fa3195b843aeea3bfe2c514c7555a5de8ef725bac72db92d6199397203fe11a571aede98d05d21a94cab9ac0cf0d1576bba72544564871089d4e2eba831bc296ebbd0b7a11abf21dfce18cc545a24d3e2417276f997becbec2e228d80cd693455116a8ac4ecf4c1698ab7c264d086c8df1d24b5372ea3c4d44be6ab250fbe3f066d65e797def752b9ad1d1af08f8ef657fe724f11fac025fb27d59c65fb1313186781ae29e0b6eef1e6fcfe744624179afd7268afefa8b8e36d596a0a5c4acf1e8183deb53c0bc1f387a1fb62955c8cb0de72dbecfd38015ec7cf0df054affb60bbfe74198ebff73705da6dba7c44e9069f274e8540211e261240bed1f4d85d78f15b4459ac2c5ce81ccd5bf7245ed80f9f72611c1dd24a7af492a44ef713f70cd9dd106ee0803687780f8c61821ad6504d4b98dd9d2543f330869c840d74cc65f4a3255622dac33b6cfe7b3f59964f6631727b71312cd09a865710d6576a1434ab14bcd4afd27c8b5a3c3ad1517d0241ad72d96a08ede2e106aa28e297bab5b64989959689a0dc0a04f2a4f2a76543072c21bcce8acee2ec355864cf196eefc20288789952666998e3de00275b0e5583e972d9440445537e09886fa7188fe796b742359ee63f4553a9c225cbc72e03c4127229463dec4b50d7dd09fd2b7943a771cec3ee02dd96a1eb660fe5781ab5423772db5142fa5a8c6155335dbfc77cbb9bf4a1e67e863ae02e8202089d695c25ea72a5ccd21ffa90e1e459661946bda1113be95d0365c9251a47744e43f05050ec72c3c596532ee60ff0652764ac816d1a8c657cfb6c8702979d31a138ce918ed372f6c4b78f8847ed23c114c631fbbeaec7581acf2e5edaae842ac01cc3ef9f233311b37d76c405531bf2cda30b93d4fb611743eb016f8ecc826e8236e35c0b64729be36d7cdd3554ad679e3041936b0b3378c130db79b968e58bacf525dd5a8072f27ed0aff85fcf41616fac8e8311004bcbe5b9b651598955947749839ed4917292e019649b4b306671ee8cc356c5533a81778638ccfa3830445a8b5727bd8272d6614da95994891a42e6134e1cc2be41dde751a29b0d4fc61d3dd29c87bfe372df720b6a8958b0f61b6c4ce39f2bf5983e19ea4de759cb65415cfef8f317af1183f7d2460ba40fa773fe3aaf48aa7b7883556491d2d15314b13e0014d2c53f10979dc7715e5a6a78c47ebce714901e728879abef8ba50877ebb3c6f5ae6aad3bc94904ac8bb0e461a315bd4dd4619a9a5104408bf7905d4568b6981e85473c68b4d40a0ee1f5b1a11829dbbabcd86ac95b3303b9864a954ed5ccadcaa64ea8724212fd604bcc51af76a9655571ccd11b82c8df62971a8e2256c52634bb7d10252b9086837bc62a825dba8c2e248fd6ff5480217651ceab21bca984630e49b50e2692635e4dfb7bb198e144e5e21f12fdb624eab72517fdb15be28ed8504ece074589613dc759cf2c3749653f437691b56e59dffcbb27ed90f28297112642365c0c0c4df1403d48fa13ecb35c8b17ed28302e8d9c5f18fc1e356d1e12b3701f5a140c800046ecc37a10ba028bed7dbf6cf8c1204e75abb090cc2e69d33b1eff720541052730b7ded9d0a8dd34995517cad28f5334f17258aa1c6c8e64720345722a9cb1fdfcbd5d8288b0057bb23ea4db3071084f18569f35a9e8cc6825160766cdadb3e9f6a2fec15543324cd1c20440929742d0792100d2c4689fcb941d6a72378c4adfb237de34cac26ecc93977ce4682b0ad210c2dfce44f23fd48e29c2720d5edbf709f4fd91b7aa5f5b19e4b62b3ce812a34803da2244d9ecbfc9729b72a17c023d83dc382bafa767ce9d46258748937f6da30c88bacd250edb3e82734056315880cb59b730478ab449fd1b16feedb191cf2cea5c66cf1845be2efb2972d08a0b4ae625ea704f7debbacf4a1f79263b9145b01842e189f3709d848d602c6f08bca5ac35393e725d518c7c782a0f02b5c8a8381026d8c7f6754a25e5f31823e5f124151ebfe87b4ffcfd1370f3e99a3c16ef5e536350c47b03978ae52315d00d05f203b5edc384d9049a4edbc5a5e186a3071955c9a9757865cb641abf5e3a485e6b051b23cb137afd8dfa8156c04622324240e7004931786a611abce824f837008296ea3dd02c31815e92e3187e5c342ef9b45417e18ac0eb48b88cd67299c1c00011232ce7b7b383c14fe002fd2dbe6d73e839f935d078d678e1ceaf7241ddb3fd6f432f710582d607083a4d396a753bb6a9c4a72744ca8966bf44be2d2aad1875f502c435d946ff0476e49917ed4b2c0df3c46f5a2de006eeb3065f72fab1b17528c95a72feec1a7f224bc86123225355dd1246395673e2f310d6504b8e877a801d2fed6ac273f266ff7c7ba9e2feb0aeb0bd7e509551bfdf2aae0d057127f62a57c9a11070888b799353e4cf3d7e929401300cc073c3f088d7811c72dc44c692318e6523a231d5a3f0589cc63f9e565acb0e3eed33a3d90221653410437985ac8f2693facfd800eef38a04f1a784f92aaa7b50fe6809c46c88d98a39fc4a8f4c0ab837417e275937c435d9917ba798f92dbcf07f7366402ee1bec4155a1716abf571c7feb89d6ec1bf6aa730eee0cea02a3953873aa675ec8c195f72b2129b394cde7958d6519fc2a46e4002f3472e7912b4ab26603a99ace16a1d728673000ce74f73bf51d089e8de06efcf7872cb196513a88e1c41c646006f1772ac5d59f2fa075db487f80087b977147feb045fbaa2973baf08d53eb916f40b726478f2b6f3c288b37d73b2efd5a0f9c227ebb521171063ee546eaa9b0924e4675d9ccf875982498fc53a7fc43b2c9332671b4fa03b5604ca867cb36099f0ad7242bed4620619dc3028ecf0c28ff9574324901f1fc545085f031c96da97b3b3720e1a2c51af91c596b7d5e45fef32f9f34eeabed0d9d90cc43dd171d481b0b37221039b1899b5757b8424e758c9a4c078d75571ae8375a993814b689987d874352884821ce11ccdffef6f3ef83c0fb5750ee266a9b0af9dc9b2c2e4a01d383c727a423bcb03a19f4f16f9fad084558db92428e6471554e76fe010bd138de54072a2418945e35c86f077b667a7bfe03c04066b50df90149d1f61d9f5d538ed6d726ab74f1040ce390a8d22212daff12a2a2dadb33cbda112948211a9a312c90c720822e30bd3999e51a379f5e32aba4eef8f0bfdbeba616178788a07be1323a772d1d2d9d8a9da75630376c365e1b330841cf59b675a3ff9653ee43a1b186c7a1037f076d090afe80bb1c27fb1377d49675cbecd04f8857c2fc24915224cc27d5f8f1cbb2644c3dfdbcad242d743ca873fce4eddcc1a84bf0d10c3e3aec2e3845b9eeef0cd06ebf955c88edcc7ad6d51476b195cb301197d714737336540aad65b2f5a2159442e36d7b5c84568fb4e49beb0147c9366b72f36015367f46e1205060ae0419166ca8b89fa1a551504ad5dd3a8f5a3e983256085ae5c35898aeb47720c9c40952e17279e3838b3da4d279cd27c2d28b651997135c383867daa5c4f72341a14af5d0ae171449fc8f9e0f522271850247112e462eb822d982d34ecb372868887c46569516d45b3c602911de76c1cabc6557f0cdeb6b1dc79b28f3e442a350dff19db7ac19a9d511c28d86dc062428e360290af7f6c9733b22fe03b0472bcb6674ab11c8e65db16aeafc92c3528d955c9dd1a4bd0e6e18863d27a1d49726d8c9b2e80b1c9f6a9bc8408aeb4e8206657672d54c0e069d65eb7b9eaec5041b2aeadf7e63123c8244f060a6185adfddd2d7f09809ba7926e6416ee51d0c4657454797a46b98fad64391f0ced2d194151d1858257a9918a65a21f2d78f9b85ceec1b0eec464a795a2ad969b2ffe2f9d39d70c696ed3c4b91ad4665cd370ec72a8adb02930dcdfb6328ab2570ea5e1a794baf1b8a98af8e99638ff02d6fd6c72a389d3cbc33642679510cbbb9e8b694d986fdabbeacf3c442fc0167d82b683725d87057cefef6f4aeb0ecdd3ea3b19c64071b0eec3548d0b49204f4795d7a472fab4371f03420330935d2ed1f6e1999a4d9ce169f91c1d2c72ecb0d397e546721f7a14dea84840e3359356730b386bd9b9995fe7182059547283ee92c2717e72d7a912d01ee5ec2d9cb78b2d2f207a5efbf00a45ec7b584842dbf77f2320f624344b8f7bba4fa34f153bc6cce82a91d2bf7f8584658d71bbf546e9b30783a0519e9037b77bb8bbeb147e649a3ad0e8692f49944e826c59ead01d7c76a78676721260a170262469bfd197a4847ec78231f797d0ba2f3f5f752816be7cdf8a3c45fd739e6fc51561680e00ed775718888248644b547da180022433ffc3019df534e56fb5d5c3ff38ee587e95020f0646789eae2f2360f86df6effe11dd1917657253e100d2ed9842994be52d133fb97da6be6024418b687670881b905c1214eb5ef1a2c23918856cd9506da497431e3f1152b8ef8aaf6279622c281753739dbd1c273eaf439f403276faad8ff46565f7ad621796727415f98f204dd5b3af260b6c5d75ac25d1875538e3775f15fcca88610e6333496c1fa2cc0239594db2f9e1725769059f70e8e20cbe3de05af4eebdd9966161999d661195bf7e2619c390a07238293e8fbbf862ab83ccb93f821ecc5c9014c0fe1e6c4893c5f6390dd514d97204b152bf88738400e600dcf5ad6935230b05e43a8a519f6a7cd21aa0be592c29bfa3ee484da69f096a71b7cf81c798353c2c615c3cca3c1f27f1b1223fbf54721b776568a937beaf007983b6472be06985049a7b2598565c3d87a2a6dd3feb727893c9908863cc4188f492870b96fc5fbf1e66334df697e0e059c7e67f92a331d745b73586b4d26822386e5c62a9353374d5980ac7b2a9d4f995c307a0238072497b6b41ef59eef35d692da4f6f0530d580d9a4642be9c93b78f51e20e744072db37eb1ca37369bcf25c2c54aeecc23e10ffd7a4ae32f1718ae62a935845d072d4bdb162dbfeffb8b616c3ab65b258114fdb4c0759dd2de8746623ca17f46a72986e08b6f9224ec34ba29bceff3d361cb78b5dda8df4d9f7428f68e651ef4c722eadeb82e782fa02f097ea3473fa222ea2a619ab84ae55dc7b806c43b251e072820e40fac08cc5687fb6489e48e17af778d53f899caa05403598a97e19786a620533634ebf9f2f67fcbbf49ddca1427011cacaffc12cb1c9220898603113ae72c978db518ed4a5631fd0786acb0e2c3ec4a96391a8c2a9b65ca70acab5aa0d1f2abc33a1c8e250ab1587344f2c83832ba82e89212db1bb8df753bf4f27e09235b6f516f3d5800816a8f7a4ea27ccc4159aae42fb7dfe5fda0773514c97b3d3720bacf743f7a063a9258b06498897eed1d231e794d56168a14dac91c8ba718d7220be1ecc3b60aaf3346d4bbe90b11a8a6e2ee498df27990e772d9055ae26b7722643a98752919e6623b2a252178b3e75906f79437aaaf24762f5314b0a208272addf59a38758ec9a20438450f928ee58db8a0dfe4d75c0d334b3700fd2b3c6068009970c62f778e9d75ee122e49ec651dd3f2a8b81259a953f814564a6f389724a128120260583ab03e13f50c4ed8da23df7c6d57b4bdf57299f2838bb833e5a7fb7e43d928a7a8f86a7557f39fd385ced9a2cf40183fffb8e392909695b2e723287fdf602cad500008219fa4d1b8f44ae1cb26020434f6417f0afbc925f771061ca0a9a9417b5a84f98af36208df9953c67506e33bad42e897136e625bd43729c488216cf26114791f1bc3e0edf83053382ccc596b6de3f5f0c397194636b72d9e7cbada3afdafd3cfeeb3825733e748848e90a08ad8532f585360af1352f727a8043a049882690e3bcd324efe946f8981678e461903662d940ad871bbff072c9a949fcc1243b8e0ad797071a365df849455c996a117277ee413dae7f0a0872d105811c9838cd15c21ee4925b09673ab493011e34d55fecf9ec995b481c8f4af5d4786ac54f2346e1b5d2aa3f64bc53f55c603659629d58f4b7fabe663f443fccb4956f54abbf97b55448c756e1ae531dbdc896f7399872523a6bcaa01f1572b396bcf1727f230559d6b6d37cd827c3dddde0b3bc5e65ab1456256ac05cec728c1c26bceeba12b6b3a81ce43f781dc16c3bc8df994ccc7f5c01500b20a40d1c87b2a7e1947d3dadccabbfd5ffff49a44b65a51a2b6b5dbf8579a993a1d5944334699a991e0986460613d9cb2ecb7472da5aeb831ec3cdbdc24abb84c00e3272f59574ba98ccef01598a5deefbd39679c0fc088b97d22df5356440843e33127264bf54681c2bfc3c17bba515770e6f2b88ef38600e74538f5f8a8cdb1e5abb72807593700d909dd1ded116ebea66de46532466350b56b18699f8b2745359e63285dfa86fd2a2c1d990df33d2dede910c267cd36b189a0b14c8145250adee59727c7df32188499811c754e117534073d8cc6e8467e87732cd3fce99ebf9791f0da12b125eee5b7e92ad68b421e55457d50261175d8a3419242ea01caa0efa5f0dc883b2359284ec4a65e5c6a5d2cccaf6d3da774e700ee4dc73ceb39cb98b4a723f188901f013c551fa2bd465bd5aa652bdb58734dfbf20b470c4bfdcc149093ce45291a67b1563e5fc1bacc63d405c021a5f3ef37a53ff0057d55df55abfcb15bd5a03fff79a041a9e863ad4273bb26edee1ebb55d5f8ba14f1b49d88b8f6572f029f9121fa1c5b3895622de38f64936d3ac7b1c6d9392a78acbe13007cee272c3bf03c1253ad9432b5c6bd9eb729f6b43c883f51dae7a57923ec3f89da52b21055f140bc45860ee9b1bd78b00bc0ea12e62b42ccea2405d9944db9566098772120fb2f428f409dadedf722051f0bd4451a98a6e2205915d5238f1aa86b086721ebd3be718c3a42f5ae9b834d7b0f7d5641555bd2794d556ef65c49236be4272436e895bb0440335a87e319f1b8fb8243c2bc2a42f0eeb2d8ee576dd05e6126ba958c4aff97719b5101a8e26324b32180ce7e4bfd0bde70bc19cab86d9c7b972014c5938898f8a3d0e649ed4869d3262a5976fa8d062f131ae7508f40e735b27527c86fac6b975e85a701b4d49b1a1b98dbe6cc597a1c9d5f7e5db5099089272566168dd8eca75e034cf939f2fbb677b18aa12fd9bcc0aad88f3bfb7e09b8631e2560b51fbe6f0ee80f2e5566cc7687b62e944cd1e8c456148eced7e2e09ba2c070d44d8132b4224b40a512bf66554f3d141d604d9db710a79dba2f7f7d554720e07d6d82b891d8d1afab027142ed2422b49fc16fb37c584941923bc63250072fafc209cda06169684306dc69f38b2756be0e0ab245394d0467c40b9085a2d7285f01427473893e4c17a84e4319f08edc9e079bd51cfffead03f70f31a149b7278f53294c6d4e78a85a5a804315e2ba88f8a59b7801680ff59641ba1ecf2b7723eb8caf626815d1cafcaedda21e1728232ac5081396f5c9544f60fca2084263d775580a42628991abddafe5d404d0f25bd4cec541a20f608082a376965818c728c6e8484de2c20a0c5124700ca6df095b34c87c874980001e3cc7475c351b7138c5a1ca13e8f5b438103ee0d9d3bce2a25a15e3a072add4828ec8741bb1a7a216cf0030dd823201e8341b82feffca7bc7c9fa45f64c6f11ef193a00b8496db7262288f0d94a952d3caa3f5c962d5df90e389730da931ef33a4268601d03baf41ceebc8e53d7873f371c0e1ef3f460a0c56c277a43681fd35e3e6c74fdab2b204f09c61e60bdc382c72f03ed377655143f8d782d863a4ea92c3f2dddb8602e4348f508f691518a536a239741f1bae6d0c8d30a85251c647012998db43fb183972e52e1c5111f668f6f23dc671271e1e6daa621f27109f192a09016186c79de772ea6def81ab364506b2c1d47e486c4fd3b4c48af445bcfa15e8a863905a1a1270acf1800d49ceab3d3ac9e9c6e043d012240f396d86a13f70d3590df036c9b4539697a8a60530a4686598d9b69e94ee657d3d27846484127148f69492e347ce24742ad2edd516d7958129ee7fd1cc1b50af9226fe353be739c93627b897da7172a43074030e1356dc6b7b799fddfc92e66db6a88ef01f3bfc71d7cdd095ddac1995efe26aa61ab47393b893acd5416d09d7dac8e9d0468af5c77c0f7922dc566d76780688481a3f61f82007461407c8f137934d1c1225cea58ed601826ed55972b32010c4036bd98bcbb3908d6af83006693feaf599b4d61b5d00e3dd1b918260bb04430b24114de23f1ee9e183a559e863f4781bbdecdf6c58df0f63de7412725be35b7dcb18613b1f390878b50ac688d00befd68b0c1eaf7695e076cc4b2d728397caa6ab962a1b8f29098649ac40ad9afee7b0d9ee6e0ed511332bc1d03015df6da095b5c5283a9b739c196dbab4eac82947d1665430df2ba37fea80708a64ef00be4c0373b2aea455ae83c4c08313b9b2f86c9e69f6fd4af17755ba1a4672514eef7c06effe74441778b9f2a019135a4795196e3b1dd512916187544749721c4616c1290c8425fdf037d0da6711c44104b99c63aa4ca43f5a21f5fa38eb337d5fe87352587999ab795b59480b4a64ab8e0bff1df3fd31217c91e6557608720c4b48df401b6cb3329109475c11ea6cd8de4ea50bc51d4d6beffb8d9172c15219b092ff86e1f07e80249aa97a7ef0cbd3deeb24a0f93f882539442afb780f728cc6689b6bbefb7639fbc33ae87654b719dd9bc07cf04f945f3497b7e9acef72391efba1a5fa507179268904f4c9b0dc2b6b0d28077d87becd698319e43206720311a803628a8fd2925297d5f9c65fee266797612b46012ec72219ba2ad4eb72cb60a80714ecdedf627ea9b1b4cca71ef4e1ca3bc6b0b2aea3f808660dfa7372d3a54a189373a9aac6eb7ffbb3e0fb068b3022171afd78047d402b2a317c9b7271f893c8866b66625d3380e657f9a3a3f1b603ea2346566cd4cb562286ef704eb318c85669daeac47456178eb8c3b3f55c968ebe689f168486e2548a861fba2dc6c329cdf8c354d0bf0f5835fed9d7b5cda8ce5772a3d344c8cc5f60a0d69c20183305adebcd069e89cd1ac28a6478b356c1e204360167113a477c0b04405772460ea9cecc6fd8bd0495dd68e67d704030ce3b4b4a8db43bce96f8d8a2e8cd036c3836d6142238209a92039d9ccbb329730e07d21703d036961593cdaf8ad4729d0af2771861cf2e24741ea22284abe70ae495f5bb6308702c5ec7552e9557206a3b376a66c9f8e4a1371fedf5f16b01fcc75371b0555440f24553e56ddf5f72a5d090086196b89965492be8c8098ec953e903245812a688f26e576188322972ce0db45e4413bff797c971507a5dead30f078bcc0c07ba5cc4da64af758949725690e232e66c79f0b655f59080999d145ca1134301837f71dec7e2f1056d5972434959498f9a0feb2ed24c1d3da7f817c351b6e9eb70a6fc993544172434df7237272947945d7d84df51a12b6ede863e1c4f1d2a89e46e3aee7c83a4fe83ea3d435c8b765572beea223317e632b9f9a67a063367e8accf480de102fac13aee72d0b323a36437e6475707bb1c32ac3cd6c71230981295f12095326a66a8007422ac2c4e662382328defc99f4cc8633bf345108ddb70df551880c04f940ffa387253420f4dada6b1f6745f3fd7334284ad322475a42de2cede8d25810665d80737392fdbdd89d1d9d0dc23eeac8f2f29b2cf434d76788880ab85edf01eaea78d72f7269fec06cbec7185657e8221d2ec3604ac0b7537a6133ba0a800b6e0a83e3a1d873fe8fc49b2f87850a45fa67badde889247c6767327bf5e3b7dc9b3d2ca726be9d15f35894e4568ddd8b58be183a6716a9c0563a149be2087cb67d15775573ab05ab31cffc9827caccd28b280810b67419491db26566502452e712ae95a72968533a94e70587296414f87fdaef387696c4e23951e563970bd880f1177ae7250d2de3ed2fa1e819fdeab67406ae74ec85ca43904b0073c84964d423bc43823e7c03038d4daefe2624aa6e5e74db3ff1c42bccb6efdc9bdaa45a4e8425d1772bd4ee0eb5eae9822dabe4bdd7bf4e82152a9db149bbc5f0afbf38a5269a63072dca5afcd57487cef4a4dcfe1bb760b18995d5cd33bb28eec81e2bee205e042724a53106dc6068e7d05334c0dfe4f0d522fae586aa7efc641deacb7b08e9430727c056bd725ff6bd29b1dd770afdadd5412a8d859654502a7c32c25b57d104c54137f37bc8b60ba340d1fe028ade61cfcc1c301c31f7f6c365043792e2e984446afb040b41aaf136afe809d51b76c366a0dc1915c60135193795583bcfdd5d35ef8b234fa1666aa6c69ef7d9f6790f9344d8abc20fa76cd0a69b17b49ae1222290f07f537343c038f3ca9c6899a06e39ff00e172b0047c7e72d93ed0f8bd2852a00378cd10b4bb2488c2ab7efdeb053808c5d2ad65a6f919b4d11164e9cec1c72eafc59960ea7e46f5a80416360fbfa06afadc6f5916d287d46de11e6adbf48122c126040402490cb46dabc37f2097e8664a70c08898cb0e13ea427184109f2517f62564516927047538012da22862063c6d9ae7eb6fccac50974752aa9138457b20123e46bd91875d01b82d29e546050438c80cc9e606c5e2a01a4e996b3bc6d95fdc3a44aa55964d25b3a1f5bea8baa316ec017bced7928f7ffffa2078abf72a9bb619b5dffc7a2d5a683472ddc36c1b380b58a2106544c60dab790f7162f72e3cda2b38c3f8434f6264c74adab82b3e56b5f8ebb4428dcf7d5ac45165e7672e304ad416371d98810a45997f10a45682c0ff19cbb17002163b096230da14b72a90eeb17a455a79265931feb84262c5ba4effdcb4542e2bd1074a8771cbe937239b5a9a797acf8de8a47707d8065d8711f2f62a8a6f3b0f1fa2c6a3103c28572a9f72d19a6f74684056f97e8c1c9895aa2a83de9d8f94daef3b03cb988cbc972b1f8eed4df17700d1c02a7d77eaabd3932dbc4cdc01cfbf796bde2044a938966594cede19f84b8f467fd957b067bbfe26e362efe4ce276de0fc8d534cb97d70261cfffd612eac9d87b9e05d043f88c2c3d7b20337cd91beef8f5e0ea82c7bd721a4291851ec0a6eda4a32f97ea6b0d37eb74124d094a72c27ecdffa812cb1f729a35a1608ade495937308c3f22a44c8cb5e10618cd240ba579a73be3a0a38415fbcb8b6d00a8745950cbcf75a4eb9b59816be867096b20749038b901f49fa772c43855a80c2678e21676319ac2c932c52049174e38b52999ef55f9845ae23d41826305f8d5abaf6f18f8e297f2835c87b88f3467ebb7bb9a1aaf3adce1e4ce72ff7353c9c84868ac4ce5cd15ba1eb00a9ff5dac4730723f0d66631e5d050ba6553f30f1d8d5a8dc16e03ec7d7aac8518431bd6688aa4962580e89c5459902f7283838e69d690eeaf8916b4c2c1d567d6ef157dfe575ec6c1774005da8c044e0abdbf55ca18be1598562ad5f5ed7680ae50d2dc441188995cf6b20c178b0ffd3c04b105e262922c87044ae596067a14a9ea721ddf66a5da9bd6e08e6b01114e6aad708f5788284220d7ad76a8912c4cbd89945683aca431dec6bd348b4d33d3698602b491d30c62427fa741138618a73d853cb75cceb3bc8d7943210cea87fc0680d813671f4575cf23cc7a28f6a4039e35c7b1cd7998e2f2f8a5db60f20a82726af45eb758f37e7f36a6294c28f98b6c8432126bd189470cc2c3480d20d56c727cf5b92b5e8d1dfdc8c687963ab56db934c0e1a9733be6f9c41589418c50ee48e4477cdca12d505270d6fd966d39595df3c1a9194354582c934ad5384c4bae71dd9a0df20b7c40d9e54547ea420fd126559bbabcef3f8d66b9f3ec2eaa76951ff143ccdf61488f2bf097470ba534b6e5265c0148afac14edb7d4f5228cae05415e2070933aa3b4c21175040d4edd06e6f7cdb74df382d9828b9e6f4fba403f721e8efb81175ee401db0f091ae34536291e53f4087c1bced233bd03666b483f0e7e49c1ccbaf765b84c0371508a5decc39823c6933a3e804e3f88f2dd2e8a4a721a758f60585d2f4a19b5c32376ec830e35d5a254ec4c88ef8dad01e579a49672abe2b8e27e0d325fd84d8c2ebc16887351a2f92c1b32a5d96c35bc2127b0cc1f3f0813773f7763cc95ed4046473a363d34e0f89708efc657316435931e2dbd72a642858e88770dde39dcca6fa8a01955abc6bbaf5225a7197e1f18a58cb8ee571d0225e02d7182627b909f555fa7c72ccc67731b8786ab6182bde3938cd04b72a8eb6f0a2691daacddc7c52900153b29f957cb294d33063a6e64872785ff947221de2691ad062b83554e0666942268b1bf62a65390b4e89085446d113c15d23bf7502cb8dfe39dd059bed7d86e66b651c1f0641b0dbf656b939df34bd4a03a725fd7b80dd210c2ffa05ab213d6986f96afaea3559619975a1a7b8aea085e5572f536adfae629013afaa2aecb32ca0a34fd2ef35d8395ed2c35973e128eb008106b5adead61155a22a47d35953d4c8794844e47d90bc02e35f0c34a599b97b95261f80ea56e9802b6ba7dea7e20a06e14a207b5c6022ab8695deaf4245780341837dbd3f7eeece0d5d7008887168beb7f0f6e1719ba14c784d1e16a876a71cc5c121ac2bfbd03358513ba6e222ef78d473ffa4dc20e27ef4f460442e6911a36632c0a4a3a60c7ed53e0aeb1b3e155b35d204c6adc9e69ba7c11228fc93f0df660918fa388583e37fff5e3cd7c200febd3c2f9a5e1b4105bed8355613e36e39d723ddbdaccf6764e944eb3299604b37930dc54ab21d7766304bf20de9ab5133772bb939464d7820743594717549f0f382de16453604be8f65a4b29df47eca7866aaa30dfb8dd2a2f6db1125787b95de35fff0801962b6615e102ebb586bd1a41720c253fc2076146c1f4d49886143b1610de6a1124e96fc46dfd995f59959bb972ea307d3682cfdd05c51bbf7ea4e0591e069c71997db23dd2c19a7c22af47ed2faa358f4a583ee7ec66271e1be7c282b0bced529e334b2b006eb5811ea7106826db1490b68d62df98fc377c90dc5fbc2db203e1e73f0cce08adffcd3a782b60155d68eb6d6303bae5ba7715d2fe55d634838b16bbd40b28c5bff357944ba49472b6f02e079166b447af48918ab954f4fc111b0e5d16fd0833a6334ad7aa0552722d13f77574ae841b7b30555ef619a7eea71379a0d483bd392141ecd142322e72d0dc560b3f2db5801937c5636f224e0039df3c1ea3169a11b1403ad458ab2c72056ac1215d9dae0f0f1e059282004242602edadc117ebe6d6a5a8f5ac1ad4a724f3d0597a343fbca5aa9970f2e4b1f6ec8318f56fad98d88a797e2c3b479616c9f3650dea1fe5f779ac929868b874f400fd12472d00e458726d3d9615bc35b72a65459b3e3bf65869d6b2515d8fbef5caebf81c9d05864b0fc5436747e1c5c7216c5466fcfa764a8742695166a397c3da3cd5af85732ae0ce9cb5f16ac3c4072504dccabe9c7d77717fc598f8f366b122d13093f78c65a11865896f213592372a05daf1a8ae1cf03860183bd146a9312292ef128378c7f1c631768680a2fe0401a01472d27e65478907d0a70dbd2b578ee27d7962e1e8a143627265f989b1072a5cabfdf964a797f90566f040cb070a1ba197cf111d81c24c2f30a21697f27724e903d621e6fe21ed6ffd5137fabc2c4053197561f4a89c5304329c7a8280d3abef989b8e5cfd6442587369a2542188f2511f9090fad480ca6097a2e14c512721e1edd2a23264814daab039e74e3d0849429237b5732b8c134303da4f8aeaa725520da2d62aa954e2369dea47c6a0021d612ae96d4eb7868940ef469a58b593b7488b2ab143991e90de237dfcc567e31a038d4dd223fab4ee894964744ce6c046e43c795ea5dcc1af8d5dfd60b75071cc247901fcf407005cbbfcb94d6367c7216863c700bc17ce9eab6dcd432b43cfbeb58af45ab9e4c2e25a0aef2497dae72e13521712e48817388a7c836812986c6b4010f09b8c373df54af279d1c2c126a5f847e3b2fcb2869a41a2a2167852dfd690d540d0fba50b20ffa47062e5e95713ad03c308851c67e985e5406ae32accccaf0b678ac2ea3b3f972d599b8c7fd724938c27b008f04007d37856c90895b1413e6a20feae45129dfac54de15bc5f515e44e63f57c523665f7695b69a9284d4b65cda2fe94d85d341819370592d910fc4ac6dc0b2a78dec2b259862724876ed67b20975a516d8896562abb70132ad23863f1b1515afced812d9200f7b1a0c8b969b2027e2dfe5227848c8153d8b8a6c078f267f7d76cd7dc7f9de4b2748740d0806cc993255da2a18f1620547edf0068dc5375e572a331e4ea0dd74fac0998f10a0fa5b0ff44b69df2f1d497a6e4072ed537057b38521efac397f5fbcb6e3ca0bc5bc740950ac44516bacbe81adfc29e5acda90e44739881681449546a2d0276b0b54f4d27714d726aac912668aff7200083dea77c6f3a8cce69649b0096cba31478aa5671eba62300b3ccd964c1c5f759254f4513c32a769bdd8a1ea6d331302d62dd0be98156a98d71e730eabe31d0003347d1b25d297238ed05973432ed7fb673c45a7f031adc1a8d93d8b07af7253b8b527f57457eebef76a46a4e938bc277c2a64b30e877b4686b0ced63436728134fd86a72241859a1aea3799d5a48cc689b4bbf70bc44ee7e4d54623f79f72cc21c3a972f0e292fe140156da4cd35c1ae2d15583ebb7f5c3872dfbc92bdd5b43abf42bb2157f4f6269cf22561e1965ea73f702f93783a23bd939d022afb913dc9975c7b97cd95af743139d67db16b66182d8146a402eadf01cee8abb0732723c6cae259942b6423b3b1d5982fa7bcd7201abfcfdc54097b981f0d99f525059edd9c62fb61af966430cf31186cecb617abe2ec579ce132d470140f5a05a1d4d6e2dcbd377c57c9c418fc886d8a3df56bbc82372bbc9733186c2adf70d0a7057e8448fef029c5f06dc721e80336836df897bb9ddfb0fdeeafa45749edc441c6803ab6ff8c239ad3fb2d5d7a5b4bf0bd09a0678040413efde845f5196b2334416de570b809a6d2e74b49d4474f74516cec0fa9a6b0a2f3742994ab8ff2bbddb130e68dc240d83ef397a4d1f1e19f10e31f5dbe022bed83620296978e0751c5e179e5996df65f60ea7c59ee1d1b187fd813f45e0d8e54efdd91fca80a4bfa6db72124c46bce2d149752e58aacdb7a745e6e46ec83a31af31f040a5556da15c137202c7b3fb969603fb551fecde1ccbfc86685e829a8d5cf078c0c0bcdc949b6f30c79b78bc4024cb9b8d53fbcd72c47e16aa6ad7ff2986871e6a45d7ea8d087614477311003b74c0ae3b0a7fc15be5908d13beae230a1e800d5ed6d0e9a11dba724b47070ee77e3d0eb8e45b30c4197a1cefeaef91a695c5edfeac9fb35819fd06fa2b39e685f442e9ac31afcbdf725149ae588ef28ed88246a9f436d84405b9721a77049276442e435d5342fac8ba095aa0d06215fed9d18e554544b28836d972bdcc42dedb82eb3e205e764f1b81b9fe17b5e224a2e44cfe512d329898536b7214ceae58bf9b7ea387022f040445b0149e0762aa844fd084903fd74cf212cc6c6ee6c733e03bf72edf4dbd779f79e06f429cdf74ed9015d99079850f995c3822302e92a15b425cd94a894c7ffce4c2422912262879416cc842b5550623c55a724d8af11dded47c22798dd6e37cb6e7248e8b21311411e5b267e5ed620a2cd11c41168193c5a701162d1a0e7bcc18bdb853b8c3438dcd94ee37163f27f7dd150257da2fa30ae9691dbb7232cdd388b237b7d34b5c4577936e3ac01d265c7311449b69dec42c978c68cf784ec234840c9fb500bf93a51bd027880e83d308581f72428d0b978287546b89b9aff0ba3453327e723136acce8f9bccb0c3a3ab7a0572246343c2a2c3ea4d82969d8a6a43240b703bc3001dadd01cec85efeb573244342903d544e17d2e53b056a237b171b5b117ce4a3c9d7a12f86b7c694bfd24f872916bc803001a6036ed6a9479e0d0ce2222b5a57951904925e92d718a3b05c865d01c630bb53f25cc2fa340644cd403c66527f90eebd6b6725779dcb7bcb9cc446415301608cc64cd8bd232c4a0c69e63f89b22e411edd30bd4e57e52f1a82b6bb25974b7fc50ad1ab65eb8f3d4af7e80325d3a6feb8d4ec51cbcd4cd3006333816c20f16ffc0534f3ef8e8de68227f7325c6a264031986658b7233288c4fb872151998a6c5a04ca81cf0fa322b100f8fc8626db9f8be85703349b091dabd1d7210106a809ecd3c65eb0bdb3f72cf4cffc78666fc572cf8fc58795b3cfdfef7127e8b86f3a7680f95cf7d486978b5b3bf4ce63d9e71dae355c1bdffddab7e4672d75e9f9191a0a6177d464730a9606fad238facd7095106c5a467cb46be524072aaeee9a68a3dc009d1dda2f88f7a6913b863b027d75a1b9e9cd3b4f348bc9a72cba444129102f58095429ff14720a2e8d0c18789c45d1fd6996290ba11eaf0721766dee6cc9a4cf1e8415126ab627b5051119c8621d24bca7fd01508c09f0e72b35615c25204ba36a3bbf6a8de6c1051f92b298f5563691f9377887ed60846720d7d9be46df6c0a23b3bc2194c5ad6586cfeef9abd3376ec0c094a5c4ec8d872971a8c779d4d2b0d2c153fdf2b17d1b6d10ed87b2302b6d6d6ced370152c9672d789825de8e47420c4703ce14c453c4417d3828917a3c76bdeec93f1e4c03762d1292e1e089e7c3a91a32f93fae1e77f2aea94eecdb663f5e7e09d0fdffff9721de930d002a996a39beed18b43bbd2dc69750ba133386622e1e845e0fcad07721b78178c9a7ec23753c4d63bd8055587edd3574c5774dff3f36d6c6359e77e722d184c26b21f31ca018a989abcecfd3d08a14ec3408ac78ca0390db1e832f472ee54f5b5a397c839ab0c24422ef9a228667c0aaa53807f47728f0f99421f4e72f4f2a9c9afb75e0559dee82bc91a8dd298ccf5141f2dd9707d0cab67555f6472c68c7a0b888d4bbac22fbc20b671504ca357e44a90900f72997fcaf9e0a231189299fc8dd3e3a55159d8abab80cde8038071d724d9b0630009d64019082a2772dd83301d5f9847cb769b588c06fc586b5564c94353de6888a2a397f6347573727d023a421d7d47bc8552cade5a1ddbd7f37ff0a52eca77af1fc87ae3eb93b0727f1d2b4ce742029a1d74b0d5defaf54ea8c9d7dcacf2b4309a8603c53f2176724709e95fc25e360e52e5c7f48ca772729da66caf34e8325aae12a5fea4469503564179509ec5f2eeccc7eb79708963414d5b0ae88510ec62447da99327671023813dafd40018c3a79a0e5b2dfb2cf1611748c245a69a64f223712a9b4c9ce37250bb4c1c93561ee0f9fe5fdc7451c38b1c91773562b27d5353cdb5e4ad5bb025ac4c5f42329a46c59dd28220192322e2e7d042bd4dffda521c75b88cacb5697218bf38a07aa721325471c239f3c7b9b268734685c9b15ed637bc49f2acacdb729b78c618403c4a26575fc5654caafcd5616843d740fd77d1eb24b1cb78bab872c6eb3fb54bf8274f86d6eb82853ec5f54c804c7f9c0aa32c93186a7d2766fa555e6421e56ec3962e6633564fb4c85ae13a72c7b67b9a9ae0f51eead2a802c472b170d303d18c1756e53df563fa635bf99d0f9ad2c05c18f257ed92ea5f253f72ae012e4dbac1d5c33ab55bcf0b9adb0413b8e2076bdf41264d4c5f198e14597282a36bc9a71a88e3317520151111cf16062973a9a485229b77e3fa2e74ef6f728448beadac8cc291d1f3e80387a0a45a2f3e33a387783239c39e8d72beb3dc722874a6c40d190493d4e240e52c72e16ef8de0b4802b84612ad682490e54e8b724cd96ca41125884770e24c4bd24f4d161bf5684d9c8270afdfa71ff318cf864af6abf1b2fbc395021b58c180ad39d91df77004813cc1487937bf951bb000b738fd96d69750b2cb7c195670960c790aa2ba8d60fb1da4423e8562189b51aca1032022a4cdde3a3d38df41c96b56b8054bfbc25f13253f0340d1b283daf7cba172ea02a0701d5d64d97bf599323e20f76f3533842c21bc4bdc231fdfd4c56faf38f30249ad9e46df25a48a0c7599aee443ebd57e8b0db471bd27d446e04950647241b3ae45f287f65131e80ed7c81544fcbad21199c3c5c8f30a3f2f233766616402226eec3f671f217c3d3d93ab4220d59bcba94da27602cd52f870d94f063d2a7fb922dfb903e1b2b5bc0f74accb4c7bef6c4aa61baa92152f8efdbc144e20727fec609f8139a99ecf87c3d726dca943484b704ee2f3bdea4e105c6b20453c7201916dab1738c5aca7ab8ac433190620308c5499a81e0183528ea28a5a6dd8727b6a67625cda99bc2ab0654d07a669c25c740f80305b83fee78242d461cf7e72fa643098ddabc198ae722f8fb76981bec7fdb4f8984a19c5aea3089c52fe0f72d78ff2d964020f657d8822a2a1337e5df7bf9cbe9ac5982c4e14db5de6a9a27210d57f50ba03e2bbde0263cd6350419944374374bb57a53a155ebaccbeee761efe93509021397bf1ac43af8b563e2802b27d8097d738065d8eab91070fa15b475fc8f670085cecd48ad456f96e02a761b4e3ca28b3a91b06fa4798aba0102f4fce3e417c3d486dd86326e0471c9e64123817bb22b69bf452ff83df4523c85972d4b91b1591afd6d994808cad2c1624d70697417d938ac3d97b8680ffc6db167278c3ead7eec48dced7887f4170dea6542139583ac390b2e5df11c05dfbb723452595a6e53efd92e468d5461cb8a6f25793f694b81fc50d940ead65c9e328e90f2e61a2cab3bfc416ce1e6ea13e3f429452a0b38adf8181505588e1f9a25706722cbaa5c5c35e7d934d029645c10000e0b44a1a0e28ad680a06948bd8ef5b3c0466f37545833dacaeff19485b362eb2b326ca026ee28b84cd9b857d75ec889a72a2e261bfcad9d58a116737e2d5f9d4783b94cfe03759a0583daff721c4b03a59f695a8a65731a28496b7287a15bda8f28f0e1678dc2f785670de307f256ba272efd5f5018351789e037ef623f28147fbc351b5263e9c5818a9da06b9db8f2b7276bffb4cc0d3bddc1e3cfda2c236d80a61cce75ce121728d347b700e27800f72e827affd66627d032c3c4c4bcae9f651d0d7ea354862b39cc27e0f356a6fb017243929ea916a88f23026c1c19a79cf98dbb6d01234b192276eb76d4362a40272a76ad2979de833af1653082eb1cb0978017dea476aa9533cd1e6ba03c4ca934f6667c45ea2084387749a518b700884e337e3490f6578344265a3a5e5957f9f5a34b4138e3fb09d76eb8d30965693d34cfd740869f76a96d3802ea34dc6b70872121b0dbe71d471ba76d63fb6de342f504c8de53b3767f486bd053b3409db727258594b74a62ac3263adeedfafd4e37a76d62b2f5742810e19683534182f8b972fff48b8bfaee5c32193e896fc825c65b0d1f8fd8d5c8818393d819d95f7e71722d214e05cf35382084acef2de7483054e6ea503bfde3bf49c3b89b713003df72e73c49c2a45c657e62507bd8f339994de6e2c077806da37737222e0ffddf6e0a0aafa15f19076b08db5a06e58705db14cdab25bde19048b905746c2d2d16127214fa07cf49f66f030293ca99ed234419f510dfaed5ac13e59b4e44c51b46680a8bf511197ac422cba6b54e7a0d5ee5caa0b929b5f05237ad75a0a13bf5738072f98eeb35dc8f938dec27f3e9dc80fce1382a831ea3c552ba7cd95cb86272b23e8848ff4168c56ee6119a07499b3e4b95b712149768b4cb26707e6dfdcf47d57246df300e1e63844e04140ebf12b6e360ce26ebaf883b3e84cf8a8f0caa5ad1726f19df6a376ea70642a4c001ff740f5f0a456d2fff5451407a4cb8b82866e35c7e01d6721a7f27afb6d7515fad1eee2035ec52971905bd5db1581dce56d05e185ec33746b2a03b9dd8d0b0707c6d03825afa9f27c5a379b879540062bf959972a6348bb8ce535f398f1ccf234af1868c2197958b8b59d03e647048aafd51b672792e94f7f359faa962575c585b8151178c5cbf97c2101be9a92e74086629fc72bb5ff0a712f5fceba85b9c5523bf97e4136ee1d0a72f65304f2ecb62f2654872c15415c06bcae759d177f464264a50a7ab12cc825f20d3b7ca708f04ee70863000756ea19d661ff471bb13ef33e38dc972fed1deaf193ae2e10f07e72eb13272462477308281a748c531fab139523017cf241572ffa2c20c46c467854894120fb79f72e9137001f64226b1628d2ec066808131aa308a09919a63d5adea9e81725a72611a22ebf9acb8499e999093bc5626655c6c8bf43035fb2a4b821cbd88726674e58f2561d040126b7eed178e7c8ae70585dbc843714ea541b7f5c28a12286ff42995ca9a3fda4dadaaa28cf8179a8cef60de2af33e117e0a3329b7518419ef4ac5ef5ce1731a350a29f581fc506a909d877c437bd7b769f2f715fcd9b1726c370b9eea8af095d4a156c28818552beb2d330a7afbe572574c1914e0277c33283bd842fb6209bf05f3a603222c6476e6a0858c52bab68af4982f7a359fb272cd9369d935023375988f2520b81262207f64ea43afd6561a9645a4e216117f7226dc7c31a74dc70d1e76f2fece1b726afa7138a7c0499ea85cde35dc36a3b272e7472761443bf00eb73bf671c5959633c79eeafbb91cc4af1a3951d43627bd1f158f2ac59187dfaf359dcd00c36da1b521d6aa971d9e44a99e0e77689d17de08d97d5f24aa2c6bc6c199d6742ddf45cf7d5e6bd69376f13a4f4e3bd23ff64372be201a508a1de96c7e8a9783346efcd61948571de0e9c0ae4e64b879f2c0ae726044525bd4b1a9464467c029343d107cb9fe377d3851548b46fb116595b0397247a354cb26ef7022dcd25aaa4ac829166422aa39ae4adbcb30547328422f565dd588976f1db242270b9fc2fd4ce0cf3f6b0450f358436185c7dc42dc7b0364729e5bde67cecf732042152a8ab5330f84906e970694ca109e938172902ddbde724f90c4b062a21d853940e2eb9a853881272d17d91cb56e1a21578a9c98cfe847b1da10d0445de5ccdcd9123c7cf6e3af5dd15ed56359855c8315e149eb53fc72ffeea189eff44d42d1808f7498d758ed0b43fc99a160a12847acb4a0c992a8347d29748948dc34a172f6380869d7dc084725a98b5b8cbe679bd6a68dba45072869b8932e592dde3240157c4b1c066709eeaa199d99f88de9d4e03f7d8ed7d772a626e567cfadde49f4c01d8c7b6637d2a94b20b12d50c17707e9ef4d85934b4b84c5a944b959a7e0cc266e09a3f27bf71f0e1b63493c48f580270727d72a4a48cb4b2db111e59a636c89019b863e46995aa54f8e804508103f43e35c0813c672ac654cb6747ca4d03d6a5772004895790b5f35f2a9547fa369b36fb95615ed2e00c2199f6352a135d546e8ab2c550314ed7c100ad0b7c16c9570282efee4953ea558073b2fb08768a202ff88810c8b902c9af36fe3bea143ce7be4daa1b51309cd35357909f69d8136991cb57f636f546b22d291c4de3a9a46b93a3e6837c472b52b797696c4033127917785bc54ad38215db9ec1ab938aff597d148fd1ee3729074e8f0863ccd0e1b933b0de79c269d7284c3dcd02633952355d4336b81a172c1a07441893938ae099934e465d78401a455467b4adbf20d0861b1c8aa7a9858df3a150548d930b1fe942e4ed2f039e54863fe79a49e7f26b3f45ba03441df72dcf669daa587c0ef58b7197711831c1d6f376341974ff8f3b82a05315310ea5e89892d31fedb56348ec48be3742370efb9a503129c027909896b7d64814e2d28746af7c7273ae1f8042f219360a0b6b8785d0e16c6838dfaf20038ed12ea067272774e9847c8458bb18c915f776a260b1fbd891a9db702531563ceef53e0596aea63d966be1f82c43f6fc466753e2e5ff142f523f2281cbab0966bb26983f072a3770863b031c374447974dad3f5dc9209e9b9c9637e3e4013ecb93dc8518a728eeded84b1930ab82ca97a9a5647c75075820cdfa730ab135b08c40df4ab3672983d0d63b8d441bf340637fca3aefd0670063c5bf3bb8b36d564cf4622bab772791b7efb317dbc1f5d9e1934f39236f06080789af2f852ceca1307bd61a9887286fe6bf920c2758986994efece2b7d1d1a1fbc0b8ffa3ab29836896ea2f5b27256a2f32c91d3e984075cab48f02d7f02e31a0429e10caad6a68ac4b7dc501d2858f789e2671f27493ffda08b158034363112ccb3c236bca006530a587e0d7e10128968bdcfd115b0db27e64cae9b7c1915c8105ffba27f0335a51c8279d3677212acaf7dfb6fd4da8d9aa2c9d520dad1faf7ab9a843dd7c27198a7ded552f27207d786da022f3b01124a1209506363ecb9b7422b848512b8b435e020c271b07226447c1dcec68a748196109a655bfe83fa7875b0b7d6f3a47cf17800c8d90672bf2da5867779f759f2f09cd6b1280e63ff9a0a70f1509c0a129ed7073547805226b77dd7c079e7bfb79499074e76c9199f77c40d5dd442bbe154ad7e4727857253d50c596b988d42002b519c4b8877759cb343d6c31204f4a795b2f6adf0a422f9878c0b60412790c6b366e1abaa22f8b385b4af01be9a040ca8d8fdfe9b914fe61678f760f9454390703b9a9add58ca3473fd47e75941447afada87a174fc72a65890779ef26f0fc063ca70298e65eb3a914c38921bc95b84f8b70fc9ed9072aaf86642893408080c4168ef12bd5a92e6810fb6c1b068fe96f6199a6562b572ce466effaf92d400b2e44e960e65a3bafebfe1d865e7d3025b39b5d20ab8c672d38dfe9ab6951e1a06cd0f45aca39e0f22c082e72a10543494f22b5091799872002bf387cf17d1853a9ee406bf23075271bcd22d0bc0d538846a73598afac372a3a53a5bdddc9feee8db2ba2762c6e10e8c0e5e2999a90fe26a3ebc96e024c40d28242328a8d4c1fa26fa8b557eb3246d942e5b7a37e12758bd9ceaa7ab5bb416a81c8036b9100a46fd94f4d97eb6e2a06224edaaa9e7d0044bab7fe125c3f46f5821162728a712c6483d628c627983979ef77a57676de38cbe662d134f30f1bebef49f0c128b9064416fedfe1e5e26b82e9b28a86819fa06704ac4769e58472f0d8871086e62f46e86cc2b60531198b47e346c0616b60a46258cb1058dce372ac60e63c5a32d8ac044168dbc3d13af0b929427e269b29dc85cc5af9b832d572cdf2189c3974cc5a4dca929d5164406427842db0a6b4be7c6193be3f28b3fb04482d31aa64b454b7e0da2f1f50b3c0d3f29c79f42b71e37c5f5e4b6e3128a2508ac0d04b57936a8780c3d65806c7a9a4cb6fcda3668993368bf8d4cbddf75b72d5682ef421f7115db25940d990df4d89627d02c93d9dae03ec9eeea5d683bc31675d505e9e50d568c3ce2219cbf7841c9468728305bd4552339ca1affcfda172e7d29679d1d6a6d930e4b91dac93d2e95885cfbc426a8b47c7f002509cf4ba3fdda8c71bacc7fb45f2eb22ace6e68aa713d0cafcb6f2829026758d9ef83a06723cb708a69088dadaf243e050b6d87243b929764e4731bd9884d6a248549fc7728abe9473a8218a7e7eef99541444c9833fe67397a345976136bcf00ef86baf7219a22dead5e12e7f144afa9a96fee52d3f2bbdcc24d955ec7bca7ba4f00fbe721f870f93772781c5aef9af2d48c521f1519d786343faca183d400678fc916472d69cc50cb00f4500809700c4913578385327802575c5649f4cbcb8cc4dc16331ae0d5fd151d7770d62885d8911719eb07ecd50f13a77d06c3c752a0c04ac2672d3a8e09c68a9595feec472656ede0a9b2e1e3585654a3fe6689f3ac4d659d072775340f12f7195221a9ea48fd399a4acf2f209eedd4525fa69a666994d5e0a72c50af10f33bac5f43ef3b143b999b37a4c877f773a8ec5094a54d5502b24e4667da13b6e49caafe8d6ef87f788609160f05d818876c4b668802e935acee946720266ba98eae50d0ea3f0d87747e48f87e388b5301eed189d5bcbd5732a0de172af876f0f681936488489dbf4406f690bbaef75f3275efe5d6f8294779047c75be37e99e412a7f6dc893c384f0687d330833083d3f86e12cca5eb441abf553c7215b68296c895c049e2beb68c836e68147342610c2e1bcb908d6465fc2565566c0822140dd1eb7fdd95758d74f0c09293172749a0db437c2900ff7e4ce5cdc67225d93e97a94244bbe421f0a1b40827e64ac0bb7b42e46961088b464f47cb06723666eb737818f20b9b0edd87b9ee13f738a5e55108d63f0852bb335922b1c8103730894d2783fb0af94417f640ebc6ad4fba4e9790533e135d93e1c4d281441a48a43aa93142cc4259791c6e2e72de9140bbb414ec3c6a92024a99c33414d50e046e54386c93500fef561ad7d9087a56221a301ba19c2fb07b0e6b6c1044547278d0dc3f210960ee9a6795e78eb571cfec221cb07e6145f8dbe0b98041a0273c20c99d35c642323de5a30b8e0dace6bb0db0bf8fbdee7d03473553859d6c811ebc3028b4f3c3dfbf0b883bad22b20eaa38df5031afe86af7a7329d2114c6d8111a506051bb05d949a001915611ccec067cdc1eb1966b46a0ecb2c3de14160a7220667e724c7785235ada2b5c65e604e6af8eaa579b8fb6563aba5dc7b963337289fae78cd7c6e83afe47f4f1284b4724963693787a53fb4625a50558723327726c5d5da500c56314c24b3fa9076e3646ea4c5ff7d4b0c35352348473be091172637fb356a5682fe3a90de9c97a131662ede3bd0316d92b1002b31ddce8568c2e02434a87687aa64254b82f5ed045e2065a45344e7bcb2af4ed41abda62adc2269b652fbfebf5f62c4dbaacfbcefbd13b7d134ff2dad9588e0cb5d8ed9d8e2a0206730da16e7b6e637c2f7a2fa3ac0626739f5080fef05f4c2c618dbaaff84a7276a7c0e18b647c9a2aeda82b01c85bfe884d200fa5727f5dd6a248b08e3e44404b6ab38240780aee12bb5b21cdd9d407a0a0b2bd2841f4e26361396b03d9a972882a1c24cb346648142cc594066a0953dc2821334ed9058662adc88a49363672abf9a17cc8b86126433cf3c58ee1373db5645e3c4b598a3a59fea161f01b842cece4d7c303eee98c8eec71bce364bf38ae5985cc040a3e5644e7a7448d1b4f575a1c8b25aa59139b66457bf2204ebe997b41ea4e2c760be3f96ad8998e10371dd25dce60b637db5c43d8781258342388698de73405b2f81b5856af22af49f80c33308e829573a5e047acd444b77d3f11f30ebc0cb85d86ca2944c528fab41a724fb44ddd53dc3e0d0f45628484e04c6d4b28d5c12e4d6ded850edc16790564725e7414860057281c23981135febafd34e61a8a96210b98dd052de10950aaa172ef7bbf81ce3a19cf3d7e1b444d5bed100e8e53a23048944c807025939c13417252a680a1759ed2dcee6bd0c1ba53febb761674b8f3a28903eb6d5904d4a1e172d309ba5d8cc7b54231d48eb8318af420b0a9c740ab9899d793796b968e2ed072e44004ba9140f43108f14668ccc81d4a5e1dd20a09cb83230cc6184b2b4d85604cf9216b7ac57a23b6ed136699ad8ae8e42a37a1fa389b2846f6cde96ffbef71b0f835b2a6015b4503216ab8aa5682156301be433df3d43560f64675962f2b4fdadcb281c60b5b574b593538c1f63b987672e16bd4efb54edb1bab49fe50e77290bb8317146fb2b1b207de571f3b53481733b50a41f3cf413b2df11c7fe8157246814b31418f0b87aee39ed33ea459ccb68740dd6015ff0fa5109133b9e50a01340f418c16059d90a7a5d743f15ed4cac22a31e490cb39b13f0189617f542355815286626b152b109859d2de49d9196995e01624f69611b431b6f185fc8083729e24c06bd9453741b9e5362b605a785e55d52d8a25e814f6cc762fd257467147e0513cc70c3f489a1e2e00cbb89fa2be0d6fe90a7d57f52210b5cabc2adbbe09c7c62a0216af144b355d6b96086138756a759f7a3c3896fff55d49c844dea824804b7cb148f8fad3ce926681297104caf3ba64a523b9412aada175cda79c8d72282aa3d37ee3dfe5ac3ad9cfa4930aa8eb849656be2a1ff046124f32aaa74a72e22611883538aa26497ac026864216c6aa0845c3e08759b568e4beca5de4b472e5651edb2d862ff2431734d752a90ef75514e63ef2559a80d9043e89863e630441bdc3facfdd474e281b8c718b3f44c8ce022617e8198a64faf4f14ed46d0b729135719e768615e87e2e77189dac587c9db1e300b0401e85e0c9feee79e21f72c8b4acead7530a36bd22bc6ed76f42069d16510d0e612028a28d77e3ce069c725267c897feed32170298ddaf819171c907c75de4f478e70a23d6eb07d1050a5b23a2acbd642ed5b061d1037e98bac71826aae2dda29612e38e8de1daa84d386797316620d9a2fcdbd958694cd8f1c87ca48aab6221c53bd8d0622a380a5cd972d4f6fb143f1b0be3a9fa6c386571b35c5da3e87a1ce0ec72ed49f4e516d6217279196295a8ef113f5aab2023da8cf1a6df8f4e932e7da823b4178497a2518972f02c9ee596a8732c5cfc5d1ef1fedf72c250c4f50ecc9aa57a6b96f230f31172e52f38f3fd6cff614faa417806b5c2a40f20691570fd0bd8b9569aad009ad9723f4917748753d2da4ae9be184940718ed933c5c361adb9658f1a4c474334ce726966b59137b3f1e6c21b9d262948eecf7675b29d81c00ffe0a7a709f605bba7227dc1b1f59e2f16d560627a0e3d1bce4ed9c9fa1185c31948d2a3437d941ae724fa17c7965618a4bc3d9ff510bec0adc191230a4859ff90bb306917dc6149272292f98927fc4b8f585ef7bef569d432c31556e907269c21ac4bf8056f7d58972cd66cc87cc3dcb21d48b488f4584ea6ebcc157824ed2709169d077b72fb1ff561945fbd49d8c5b17bc0b6443de5da53161c4237c14b7e949da28f37e195ee972869b57b6f025ab857b6f5750cbaeaf1706288063238ede23bcdfeb3723f8207250c8eeb6563f21a171a18c6c79c48784cd412104356be5534d1b3aacb5fa902461d9fa212e6c0e68a4bef47ad78f44e2f735c7b056e19b726f0824f3b150825ef6f6e84fbb24eace390861bc1a3de57a8f78d8cbb3b3dd4b3ed0c707db6fa4725d69efc862dc5a2c8ecae12b911919af95bd8304e016c7778e204f94955661240f387a032d1bbafb52feba1380210a7370233732181b3031c5c8730527c7f172218c004788956c3491a6e545e136452295f89b3abd4f5faa6132365c5aa32b72e14477b87f35f712f6d84ffe8e9b4a12577ba06daa7560809f3b3db68658a50fa9cfd3efaa8cc0588de8ced5d9c12bd0b052937d0ede9b93819d3df55ae8ec1d90340ec6e8181b24cfc78568074b382834bfac28045278c7c4ade93f52261006ceb39a4481aced90b608e49a07bd7ccf42702178f18374e9a47e130963bcab3d4fec8462823d66a12683ff4e7da489afa28aa9eadf8f1d38a56f8acfc20b0d5178415e91ce855742d1faf807d278d66f09cff7d943df85493aa464f70b01106c7344446c5265dcdfda2b60e0b2d82c80579d6df14ec396e54b64dbf924f4f572889b54f3158503acafb2b499264f81204b23f0482973fae2537bf06a61c4e972d9772adb60fa74678be2f26020912c9f855b484200f14e677327a26a1adc6272574eade6beb121560cecb6386444fde695a0f7aabcde2c10f2d645edc5d2ea72075a5c3f12b46533bfca9a6d7b580742fb1baa284b2ed511f7ff6d65e4dc6a726e84b9865a1dc3b606b9413a84e708b5c140b9f04e65d31613136115d277c644d76dcad2076b3a8e2c9aa06501bd4b6dd7a74b006c9fc0dba9911498bed82c7239b3b6848c2dea32c7f9addd4d3f110e38f8faa7eb42dcd10e0cc801f9748957b5be1604c63f040fd5185d8f609ff135c3af54f6573b99c7c6c2f5b4f259d3720b63a7e84751aac02bff450f416e0b51ea29b6c155887de866d43bed1fe4b0723a249b07c3b0def9e8c1827fda5c43ff6df7b3398852dbd6131c1eae2ac9933ddff96bfcf9d96966a916cc659f6e61a9c8611ecf533fc1b1229883553c72da07b1427349f94f5dfc2dc86b8e25b7dfecc4ece1897ecde4767beba060ecf61c720d1b4ca17d979c443bd0676a475590f9f101bd7199124ba6ad82eb94dd1ac526bc0a0af4483f19cf03035cfb21df7db9f14072da0d966fccb780b2e33042e47252648c6312ffc9812cd2b33bc3bc19a3871758069ba03384e0b2fad99523cd6e3fdb4078a812414c18fa79b0f7affc9345d4db52b295dbda257d780b98d55d725c0621a8008eb4fce5d85740ff8a57b130db1688276f77c8a3ba7d55013e4372b39b2bdd43cef7105f313579bd82385dd0e3b764380d17014604ba8b3a647f5724c04f6a7475e0075677f6c12b41408a19f0a89ccd6a96b502607c836bd41a7218c02fe10497e199a0c456dd2b031598f9112294851aee5b68e62e5f54516c72f8d7a27fe36c957e665c8a93bbdb89f8a3a6abbfe7715b23e4024835334f5541d63462a9356a9797fd6a11ffa4461f45cb44be09ab4921a373adb02f7b1e7f0d8607c6ab0eadfb1dc6bc84724e503d96aafcedabebb88c6d55e7cf66c182de0bde7ff9c2c0d631e249b0f119676cf9ac966a1816f0be95e4a9094d9e777f7c51f8b9c681004e389b821a8ba034bca6c9dc95f53a78faa3488cad56395db6d35a289c273e4788e17b534b658ea45ded9c53393f727d84eceef1d4334ac4805172c7bf10bf40fc067fba5c5077350bbe2432df46c5fa6999c879a130cff9ef64583a8e1f1b28481f2614b3d8aefa7dfe3bbe4a5710645eb9766b6df33dafedde0e249e40476de5f0af6a0195fa11ed8d5803727381adba27379677662502170170bb4722f59a733384ce7422d4a7187bc1ccb80870023340f496de5482cf784d2f0fdf0fbc02b4b0b82bc397a33dba20201606ef39b3cf846f132e969d684ba144129ce2fbf5a37a11b02800c0593f56d14ab03f8679ffb9656b5563aca01039721754bb8586ad99baa77b6f9a557213bfb519596047cff9ae2e166264ad9bf946a96954ad7b2380db7fd7db430f192ca3c5cf5e77e653eb4bb7344579b0e271356017766add879b21da98d68c324ff9464a1bda52ef1a1b17830816363ebb493c9a7facef5966dd565bf56cbc636df90ef757609b42ab1d7fc3070a7e4ce37a72baeb7c58c2e9d24f2f45b4e1a2852f20879150839a4de2f38a20c5701c470522de76b8a926c2c7f33410f68cb0861a4e502ef0fd6cf2f2448ec79105d338cf72ffe47171e9bbeab71038559b1a3c7dd0b1f9cf137f8526485d42855069efc01d16f099ec484517ad87ae2ad007ba9e0ec888186b1e54cf1b0e33de6761135b3f34f8b6688abf310820c0eaa61f4bf02c0bfd2c0b544fafa85df91d0780ef7b729ffdda06ab4094ac51d5abfca793153fbd7bf14d9e5f195138576221ed6e897224e2d75b22d6cda0c238f2564b232c7cd410a6b06e083bcca4c671c75c6c1346913a38617d6ad2ab51708b27ab2b72298cacc26f150125e21debf767a2c82a72870e143407cecc4321c496641f98795a41c67a4be067c0764ae4f1756826b74a81560f45276fb8268c043f4b4f37af4eb99ac78d118447412cb7e5a71551174b873e45bd0d35e2a6770b671b6785cf998f261f9afc331c5f2e68f50a76cb887273d8809f662d96fc24a6116d46acc140930813466b58509e816fac423b511759eff5ff3004b23108a585a137447282c21d5b892a3560cfb8e3e98148f64c996d88842993626b531e5e5458c53061f55e2eda089705ada31324a88de72aca052018769b7e9c84f27965e82af3eb60c7e28a85bbc28e2183be6bf63641d11fe73048beb4441e67bcb6e23198e9a1a8cc90541871a1cbd889687f8f5ee7211ba44f4b49fc8e78307f6cd4286c68d05d01063a240302ed0286b71648060f2ae9e53d6b3cdd2123b9b6b1be77f20f4d1f362d2cf81665efff77b27a8e13fe3353de0722ceee576b84894d8dce6d7bb496c5e853b1ec762b6f25c1062d1639d3c840522b16af163a11e03a6dcbb778a2fd4a592fb15fa27c0ae92f9ccaaaa3ce3a875af348ec799d3eef3cdfbb801a0ead7f786fcfbeb03a068a3b50ca7a02c42d830eb256964adee7e9506d64fa81524d875bad4e3ca6d0a11ec13a8fb6d21f56b00e3b7945ee9ecf6c4bb6eb16645e552040aeb7e3c6db019c9722b29fb7c48f0e44991a71532e23b97c4c8dd59ea2688fe2b67af2386aaa93a8c3d1c39e1be9184a489faf309174d104374d7d837413f91a5455f590ee8574801cbfe7580b71e172897e7456f46f2952cfd65b040cecbae39a06fe335e84c8e3c60c6837248a0572d53d1b73a3a15812e11a771cfa5fb877b5ddf38553f42af5890f88b6491c764a99392fac78c3b84ee4880a7cee8d560dc7fe35305bfe5bf86b6b7eb49e7dfc58be734c1816b5203e2c048cddcceef42375a76ed2bdabe114c8480930d4caf672a343d1ac1824d63b773997dfa87de46fe2b9d51a9d5abd6da1e9ecf74774393fce19987333f309155208e65d153e4429c7e89c70c3f2b8df57716a1f34e1ef49781be889ca9020dea3e3f8738ed130e10be594fbe60494512b60b4d9f402da71ecb5fdbbaf61a47bf31c6e12243f0609c18300152413ca6013299c8c17f3b47222fb47bbd4e503434316c80ac70074ff1a34577bafab9b6d52d50e764853d072b84db7ace001effaf74f1cf626d15ced40fd6743f4dfb1e8cfbbb01fb59cdb7285e5c1c23a700b48da9a8d294f9526aebc8cb28e315c6e3a1afd687bf9bf8e4719a72e7390934256d2750d21ef3d76ca67ef5e4ad481192613dc17cb3c4d9772b47f73e13f009a28bef28040716835267215dc342ff75b0dadab02c548eb3a24337c034aab8ab7af31f51beb86aef3165a80615b13aea16057218901e135911581ef866afe6c75dab68fafd10e008332f5650df30416c6a287bc9e9dd5a1896a097ee7a2680c8dd3d08b711d9e6dc840fcf962c88102431f8fc6f4bf88b7060c00070e295a861d6b992cb4164d0dde66e27f5c37152cafde7ca212c043410c6d4e4a3fec02858697e11433ae19b8a4c836a835eb627908018156993ce0e6c107eddd5791c45872b470de2fff72f4c69d9e51cd2515128eda6818cd552001921daad32eba92686c09c2fecefd75e8bd0a72f9fff01cbd0ba8c228f29e402aa8725ce1d1b31b6beff20ee99be7719075627a5d33a80142a5da4965ec7f39659f3db19ce75cea6ec35c55f20b78f1cbae34959c3e8af5023cbe8ffcf3f8c6966b66ac00eaebe92437a6778f8d0a275a465527b576d390de60c29068d5d8e0279d72ee4e3619308e38ca27601101a518e8943855ae8b54efb966b0a6b9fc58bfa41721fb46ee80b4e9d73764f313192c5bcf63401e1d82e016638b041e575c958a57aa28a22618e872f6e9fefb434d807dab67991975e3d7d72d1547625f6e801d72de7f3e3d7dc3c6891058ef146ffd44eadbdce824e7eea55730aca49ddb45bb72ee6c1bb1526e31ec073f3d35e0c771fbfec9180b9b7270888355df819aa60a72705ef3b2ef07c4f76829ad09d927924cd05aefce0bb8ed5589d72b3a1f62fb727a122770a36baecab812f8524f2009c9ea8717d0ba3d288ea34fa2eb5d571c729a6dd2ff8cee4ead7a61d55b9679153becf25f38fae4835e071b18b4ae0674725df1d87118490c53b8725d96121290ec677b0857dddf6470e48ba2b8bb5445724368db2f721227ab1855b86b6717e5b3052e16a78a04897fcf1ded263d7b5e3d541389e998af4729c3d7027cf3fe04aa4367aee04a39fd545f507f49a5c8fd24dd031d76fbe4d679fa75c61c6191ec965973117527b54776d21e4f395ffe9a0b7fd012c357a5a3e3ba1491ca31a8097516510a0e7b5b4a792db2603da077e72c17b9b84409bbcfbd7146ca06e9ed7e1abae211074470228c8ca9385458ed9e72d9430f03988ac2567fee30e62398dc5bc7d37dcb5b03928c67b90dc015468372adde82e765d9e27262c487c89eb486d096315ad7f261811241c54c0f74d89272de00dbcc17afb6f4d56486657d1a498cef6fd3b2124a72a15429f4e08b3761724dc7bd8eb798621aaf39e64df9382a71444e90a8602b6cc38c1c586a3778d80fe0bbc1d18bd1bf12d66ae94ca6aeb52e2cf265773d0bd01e0a3b9797e4c26872fb937c8c51178c1178d70451885d9776b27cab8d34b8c96313d3f5fea98ee472e6db0a2ec30e3be18aac06f1c71fe6d967d993c393ef29ae8badd77fefcce47236e3cdb24d152163f065f8019cc78a6f6e3ea354b1afe6451dc8c4fdfd1a6c5a7bf3701d6de7c43e5acdfe2e8b1e8e7a2a870a7f606af674207d1c6a17c05172e4faba9694a4783498949b8ea2bc610e6dc12c130de8f6241bb8eeae313fa05dee62837ab3080bc4f37d6174c4edd6c8a892924e5e920a143571fc019bf698724223c888872d84f1b3579fb048906165af8b7cd02c1344bc416f73fd2fc771727ab03939757f92e7ecb97276437ab60d5d4ed816f808406f4c36cc096f290255ae60b8ae0018971f2c421345f47069821c0d377c37055f2823fd70c22324926c1b2ccb981b0fca9d5adc3ab3a6fadd74c11cbac70a012369998098d18ba66e7258a4b9e3c2d7dbdc93b7f6f7dde5b80b1626b776bcb82f23ec249f195fa7fa72d47de2a8bb0307bf4c205de6f8e456e8c9ff645521a4fcc401f69bdec6953c5e0a19f8668696731236e52c2ba3ed158ece5d30c33b1d21930241bdb588d2917233b0fb2b4121fb978423f5c93f07754978d68c3a1836cb897ef3d4ddbec89772606bf4976558235f0bbee3609e61a0977bd800b050349e2fba78e65ceea82d72357404aef88bdf390467240eac4f091b7254bec40d094f18212bddb95d247a72169ba843e744e547c18336fb286f99e4db790f2b5e2b5f1b08b0256b7ad8ab7217124331e2765f636b7d23c34bd13cc0e78b5e86b41a7ae022c0b0771053d728dbe85e20dd185083e7dc8463ac67e5081823b38a184ffc6be55bfddf0275c6728eac22833f43a33e6b99ec5a02c8d629c9243e519d1315695fd1857734af7828e420c1e0172b84d0fbad87f624e99bf16373c2331143f61a07a77e72bb085c727adb61da2d81d94d8ec40d2980bd4d0114ee2447f76aa6c70c1f584e62142e72aad8739d14e44a7d820f5a5e508c7414714be7191c32ad513cc086a56c935572b1201c6a734bfc88ef555bee6bd07d15ef91ee8b70f729b960df2c9282d194728d134003bde145f897c24b9c8124ec573f489b048112714e431e7bd4f717ac720c560f005541656584cada9d6d91d2fd906851a2760e627867c73524787dca0bea4df3dc1fb45dcfbeb4c8d1b8d9d61584588ca9f31f1633351c15fe75f2d229a158a3803bb460fcc7d97f3a0dd196bb84e6cca15e86addacef801cad05e3933183af52a4b392a612f8c579ce8e7d3937516fcc82a11176065e50a7227afb8597ec0d3b4f1e439393a2c51fba2bd1304cfeb68ad56e30e0c3441cc8170bb5306fac5685d63e8563b91b0191719ae64fb5f37ab57128bfb1a83e3c05a66650b72b83321caafa19a595f0f496d0c245ec7104d98ad9d010733ac695d7510b5a572939f7d81682a26ab82123093f1354f9f3f259a113fbf6d4d495e250433dfb423280c73d70f82638c97ff4ffdcdb1c8eeea952515ed3640110f8930946edcd708ce0c53ad028aa0c9d54f3ea5c9712ff7573edfe20f17650ac8d180d9b332d6637e89fcabce7ab42e3d787d08a770544c4d1e9f19ecc08472263ad13c0598d607e8b199724ddaa041383582a7c8dabb6589720a9f2816d4b2467bd2ba6fbfcc7238b0396bb7be9ec90d7317ddd8b0e52b910d725449b728974ffd131323aa6b727c2a5235301507c1e179bff130432d4cdc07c99d793396807f5672f6b0492a72679c8b17474caccf02fb47351da10c73beb4d4123d706e778ab20d804f0fa5728d433c782317dfc0322dbb42d5ac58cf63d1f86dd6e92b8a4b44bfd7afb9db5e02a0be60c590c1258f4aa28c332f3ebf65783994873c98781acb29b0384b14729f7f262db8a998a58e5850cd4019d754a1d1c358716341342f8f7e4f0589dc725d5d64bbaf420b2ee665e6d18c3e5e94464f3a8ee01d578bc4fbaadffb8380727219446bf548eb38a46c337e82a684a630c60351cbf3406bf31851b0ace3eb72cba167ed346c3c00cfa2c9d917633b3339ec4ea1778c88e2d9fe0569bf835772ea71fc8052197d867eb692a6fe7c13ef416c209bf70a032d987576f404f25f72c9a979d9407f0ee902770401802829a72ddfaa056d74e082055f8e1cb057b461936810f3d262e15a971a54f3fcff189f34f6bb6f2c674d1077a83818c7ab7f4be080310d1c9ddfe3918aed7378fe1dcadd6da7fb0430a0a1e410d4c3b3758d723adfad7e8a07ac892c82123c3cd96b926e612b833cf26db0607c6bdd78538019ab8662279288679cc001f3e9356e64de95cec6fc0e88d0dcfa1e6e2085ad3e72871b22b8f168642727dcbe313dbf03ff5216e0b5edbe3cd08b1b32f011c7425a8d81026fc020322b4d14b7863137b801457dd97c8f8aeba3b409f92937145126e61ebc2eaf5d5b5acfff399217e180929608b4e193174478d621fc76a551751337d7d31a19408cbee95e68b37d70eed322ef9cb55ac6fd66f37aea93d1e8190adf877b1c50df04413554dad861c0fe2755664ba74498d2ea1f229091bb4cc8121c9ec214fa21829b84eef52b553585860f9f7ea60b3da9c8d7817727cdf39220b7f9ea9b7f89cc86c5d6135e1022b99f46cbcfa126cbb879a63f67cc0b411172a773928b508aa5c015496da69c3304ac7c27b47b4094c82c5cac7b8424880f3c6006023ecb014916cc663faf1643b32b31a7b4f147a195c384078780a01ebf5a6e99d8fda1330d0b17100e1b873523a997eca1cf109c99fa3ef65ceb265fdc24d7f8c9178e187f03fddc0e5b3c35724bb2470628c438fc358011ed0f49691d3c14dc1a2c910a8178ce0470c6269c03024c32e67bfc95a7f8ca601b8ceac8b411d28f751883fb23bae1d9010386600aab1ab91138660f99fc60a7b0721445867247c0e3e937f585de6591ea31b7351b9b8f131a6b11b468fc044c40573e4cf019bbf0c60f9976eb47d14859390c17b3c4dad511b1b5029abb338e430aaa03e872afd8781c88bbe1c31f8b59755759177dbe0b1b6a68bdfa1d69c898e87b8d91727027258acc2cbb8768e6076762309f41417f1af1388b7410d36fe197a6edd8363fc8770253b002704ceb82bfc15d7a76ff31d21140abc9322afd983a175e193f3ffc4911fb64cab681a1e9bed03248014bc9c64895ff32b24b4cec1f46c3cf10843f6f5c809b8f1c7bd2264e1dfb257876f7d3f0f288576589f976a0500f5472f53b6011d3d8810f0e04a6f1f488c0bf3003e2efe777cd125651baf67df2c771051529cac67ebbf63dbf6629dbbde3b0f13f6af321f858d43966da26e21e0c72f434efaae219f64162a5490356c353db6341dc0971df204bd271a8678fda7a72977f9dcd096881e90b60cefdeae4593cd122a4c9279e18ae80f38d35968121008e23c6ca64ff111628c33f2a41bb375f7d681f5bdc6a9ecb948b2ff76e285f3043e44d0d0431c150b44158c36cbfca173202442fb02ea2dae863669716790a7228da7b810f58b061d68266616bd71f0a5fa25e7cce283fd71c54d106c914c76ab06964f234ae107a71209c156a79b573603b65453278a7584253d234abf99972d3786edb809e4a7c0fb4ab409b495bab64f163d555896c98ff2916090199e3720d4b13cb84914979907f978df4ccd48f04f92709a61f073049ae79cbafef8272665b2c82d9352dbf7203b69b0c22fb447e3e9ab5950844ba439bf3e30ede5f721065fbc6efc68f4d1c3879ddcd059578b7334fc0c4ed0fb13c97d2426ba33072bd41b128b5aa408627396560a22e183e53573959266949697449d8b29a8df17236cd339e7a2349f66b9b8545c19251c2d0518b93ae1711f3e9b2b85fc566a972451ae486c3f3fceb7396e558e5986d63771658097ce6fe803aa3274b15c13c4ba9abf66bf05436c51652cae20fe46eccabcaeda8e27b9ec336c54d071d0ab94758a062ff3822f28bce16eb2ce9b5687f0fba63113f7ca26074e613b151c1d3723c7fe8494b6c5c9632c821319d83611b83c440fe3ccc817b62e3b7fafb979a724178136550189c747565f2cb7544dd235b35ed916c04e5e718a4156f7e138d729a87f4385de0544f2d11b8331d872d977d0e7bd5d65656233f150e98ac12ea50f12f6abd0aefb23bebfdd709a91de3a4cc4eb3b59280c5cd6ffdb460206a93727223a456202f4edc8a00f96131b20c0cbc05f311784b557c0444146940099a6155d7cd611e45515eefb3033f2e9411e98790ec7c78d5614aa956488b5b1e7a1392fb99da713c1abd761a8fd2665411f05fdecebfaa36d2044f5a5b6bf162397251a2385b8aa579bd1874c2b540df71674e284d26c78a3b16e0f709b7cfd96872a9773a7c7e49d737a3582be20ff3162b308b92918d8b9b7b58b67cc5e635cc1da73deda17232fe2596a147eddfa767c835160f25f3513a5ccfcb445eee448e728c1b3b17921c886d318faf465b2903a45e36e4ecf408ee8532f62cea1a0ff372f8185dd92f86c06aa21785dfa0eb26f6274a29b46b7aafdde62de8795df9905eeb5907a277e6455ecd4ef4b3f748d4099397b19c1d4b43c68ff4162572b69672aabfd3cb6cf9af36e6c677e7b82835e1cb21336d76d2e36af9f0731dc0df343d227e7e2cd3448eb923441e458a346454635b3b10871aac2633a0c6f33b6e583b56fa5e17014b69cd76c9b9f0b3707414cf1ebfea4f90d260aa5cea0267057472287dac9b59dca69ca5def404f35ef4a2565fc2bde96b54ac57ee0fc25f187572089ce07b36b397fa77b7772c7a135f3ddddfa5bb336f09404467010ecb26ae720b2dbee5420ec705d81dcfb317fe8135e4a02595aea236b4a0a04eba60e4f56785f31ed82a235139f0480d59a08e708c9dd7965e33674d6e5464cd46a219e115bc1deb32cf6e8937f6c6a5ee4ed4e00bd0803796192fafdb27cb68f757bc4f721fcbece76c838f9a76ddb9ddeffd9fc0c8a6b5e2b7c3f9cc0a8f942666322972468ea0a46b3c47af678887722cdba158d00906e436c3c55fdbfd6914c4163905995904356e9927159c210bf9e4b17cce2c477b95f1ac6f05d3c5152e3ef70d0f0fa48128f6649b6f3d1f8644b284594f76a7769b79b4b6f4c359375e01f34172faba3d9387585bae59e511c23f8f1cb6c32359d9a69bee6fb1ce8a426b0a257289963cc2dc703756da27345efec35b446c457fe0d0ce9b532443764077dbfd3b30e2109f835c572ce3691572cd5cd6ca7c26e9fd622d9798449b80afe1015872b6e857c30676efbd4888f3012e2ba00c341d5b50fb74de79cfa8c9bd4636101e1807f29deb1a8e6d0bceb3a2326195f4ca6b7693814dab37fe125425bee4d47277bed520048cfcc28c93d8de2b4a8c1577f09dfe34dc9728ede6dea6ca7681724fce089e60e4514afb34b4cacc803f1298316190840af3c11c26f42f82db015354278ca1edbbf779894bb99cc237883329784dc9d67174ca075da015057d79720a0c91da2aa401f600ba44035a2871d9a49dc5d4232d354c6d0dcff88cff4a46bc952b730760894a7dc52dc847243ff7efb8b6efcfdfb8938a2cca77d37a9172b9332d2138c2e57d2864cbe8960ac129c7860c41f5ed75818d08995f96d5d658238c25d752da787985b410b6c1909c62c5d160cf0ceba91059a32b49c761e572df55253089e2fd712a29593e2b07c0148068f8b6e941d4ca4a7bb646cf79dd5cd6f185c05b568ac1c1f93ae492abb047aac60a47394b7f97891c6400cec715200527696d04a78d08c3904156bf183de5710bddb3ed3b5df3d9526f730e52eb05574a4e2ab638245fe8451b61c415aeb9c094a881bdf5e50b10e10db0b7fece2b45cd6e2981281a346281f538590cc93bf386d3ee6d677761b584cd19105a3772eae0c3fd2ad9c7d6cfe9d688ba5cba149811e779210969d2c1601aeef4729e0129ac8617dd6604ab96f21963cd6feb18e75805c5bfbf50637dc3a1191fafe07212b4d70f8bc47fdec86c50b508a90930a7fecc98eb0527c9f4060e985364447212f98bc9429839c7c64a647c1ccb839078cdb480a6de230fad801caa9a9a9b7277670e76550135f9fc01b74c62c680fef52fe8c8bc2ba6ffc37b4eb4c97746693d0a102776882b2efc6f8ac1c19f2d6d452bc0739371b0415e967edf480ff472c56c6a3915bcc2b733f21289d9313bece2f77ed16020be936d90d895bc6349727bd24bd20e61cff281ec335d4422f68b7c0b215b6681677e290060b66fc2cc729f7ccc411f870505887e72af37835c3ad6894ea308499a17dfad634a397b4372f183550f2cec7b970a5ed9d3dcfec8a6c21ec1878a0e2a6c28dc0aabda34bb09742d20b54aedb821382ba8412d30deead70353b8b30630696bc8fc07972cfa67ae4faf4d12eea1b36bd5bd9c3a0cf8164627e7255cee0bfed78f7a622b35377269f8a3058f2ebde0009f2b639a7d62778ecf7fa78e6c2e339f2fecde0920b572300b1d6f4c194ce4f3f49c8ffc440b519bea8226d197deafc2b64ce0eefbcf7297447fd19bade945aba25b1e1189a15e3c78b77767e0f405b4848fcaca085372f5d250109207c8335bd6be511e121f0b4c8803de512b65fe741c627a8ec4707250672e1ff5b676b875e6a0303b16ad464a4131b2f343f4f144486f63b13b41722ade6732c6f7d24e52fd6335e334c2f47c471511c66a4d1f3acc568030826946753e7a4757e56c010d8de6a3343a3e2dbee36962ec0682442dfe6f4956af257213d8924fcaa0fccae5bdad1df123f39a2fda99f54fc5c28f236cda0a80725972859d82768d615355e5242713bf371341aac27b0b564bbd92ade9d2d3b48b87729bb04e98a0d66ac3d692044a1d080b320fbbb4883dfa7ed5290ab59765f3cb72303f85f8be68add1f7ddb4fa89ec96e7da244fe4275e8e9598e05ed024f1cc728d01d642a7e6d030847381851fddb37341fec6b66218745c754b838b1fa7e6722cabc7483d89ef3983448051a9b4e4668ff386a789737654077c5e301b6cb572a8b4f945343db1458f759d8a44e2686a836aea88d81c6cef2498c5dfd5357360737c413df452440cf7a37f032a35a932a193055656c24e6dd239a7ea68ec2b72be5b7703fd3ddcb9c1cbf4c94e52e96387a8020e76c745a01906732c25183e728723ec4e3d6d173f5ae6cf017227b127029089211f0627ecfa40ea684317e46603f206552282c231ec2c16a591d199db3fae61da74622f525c9f3978ccd13b720e35e2e6fd809ae1a3679fd8eb2aedd2aafaf7979d257c1c7b0ea13ac6b6aa72b50ef6771c741e218255b1dcb0d227ed91f10da097be547620c000301a38a25f92e7016163eeaa2ad9fedb7c033bf458e9feaf749d04e5d62797565f9b4576725381648855238ad011c56cd2cc697f73ae32bdae5c40ff10154cbc8c4372c14d01c34b711c1bb87e37cda623547af2a66e70ba670d1c16214a5d2aced4ebd67261e0ce5d39053e3bb108daf1fdc1cb701376af8545fb69d7776ae267fa4a7c72c28f306dc6c7c6f29a26d6e4b1bc1dfd95ad3efc253b523b4b7184a6048d41729480dfd11b60ee8ba255d842c76e2dd0da77cb0016900b2c6ca5379b6d2ee1081ac4ce2651a3d354dcc181fc32eb4e4cffee2bf407845cb0e187d8b90ee440448a16f6658da8dc839da27e9bf90f95a1caa5ff17eb3ec8feece60cdfc78c8638d58959a4e52430010f09b8f0f259da63fce7ae76b6dae2c70fa323880e8fde5041190128bef5b9b69f97638cf95b00bdfa980d04730c35f4dc10d40988fe26729d3d65d2e7c79747ace9435c321e61a11461b10b10c2344087a96c421f76847202dd6d220366ca7d885fc41585fb8e69c9827efbfcf753de35713897f7bc9f72714b7280b51bc8300191bef9f779f814f864165e36da7fbb183eb677549325728c16d3ece2bae762009844ea398c31357745e50eb1ab9e1f02143a4a6a8e1a72c8c4c5f992201db3ac1f89905b2ce6f0c6f23919e1b1c85a0224269a1e0fb8724d717d6b92f7cdf3aa59388b92550815d0776c253f5b7a5cbe01743f47a650366dfcd6f37c5912254cfa26248e6ba02b99fc6fdeb144d3b3f59bfc095287b0361fb1b78664d9dbaf38bebd948d2dfe7ccddabb41c5e61a0d0691b122a783d6728d761f71a4f4d146434246c5a0b93d314684bf6b094605071b06d52aa98bb57268d7a37247a23f99d4882fbe40f7178bf41c50ad017111d9bd4b8e2558f3567290f5f8a3ebc69084e31bcefa57a209f607e7d11c6e532aa84b46f65aead2a07208075da0ff4e9e396f461ce082ebe40b78874252f42a710bd391a5e198771e317e1c2ea881852f152e7c3330b21d8efc55b47c0c448f5a7c6aea879288b80f724a2e931027f6817f3a197fb2fd36dcf5b1a968fec393614b68664e91c661b15a8690ce3bb49f5696b411c9c896360b34bf20a2194b7211b220ca1e9d06d4601e8f4045176b79d9cf627ed6074c94592836203611d03e68c13162ac328dc54b552bf10ff1c22cbe21c55d5bb996388817b6287cc6a6e19b08e1d445fb9681f735dca8100fd48030992f76dceece3a8206f509823c2dee6e10597bdd113c4cce727ee1e94981427ceec7513f8bf9f16e89227beff97e714e8534c2e60953ccbb7284b06d5e013dd8f8ea68af6391434eb0f94a013b4fd53a2bec4ad473b90f7a1f1986d6843c5684874c781aa296d1a10b495383bc68668e116ab2fb45726efd72dc036123aa30d582cf4b765912c9f1f26efadf8fda782d4855c36fd730c92940b30f30ce8a1ce5b5931b322423088f9f17ce0789febc189d00515a6ca47883724ce3350a3d1a13ee778d2033ae4552afe64d12aaf593d9b4c71549f29cabaf28439ddc37507fd2950f812b10d3a6aa55b63f38d70ddd2db0b555fa3985c72a3f5a14b98293501be32b787292dc878365c6b659822b21adb5aa710714bf420372b4c438a929a493be3d64a5f2fe05ea47a1e60ad251cec674c79c28d1310da67281081b0d9a6467c93669e27d4ac26014ba44d8940bec727a6f867ae86c626c7286c3659c40165e62de059ad58c3eef9e2553ae051b12bf8688b7ba59128a5572ee329e7e85321352450f84e5c28cc5554d977faba414178e40abbdbef2faef723486c0dafd70b6b00ce8c08698640d6eeda579370ef4ce2cd4ee8cd6ae5f932c46005d5cc1c421230a0a00f5eb125c1d46b749dbfe95c9d884b379de2a35cb72d3c0ce43b2b12b636231f0594d551fcf89334ee0582bba36311e2510826332728d9b141accb050a67bd2c130e249770cdeca8eb478cdad212a5e81ccd4f95072fce087cc41762da8e6fa6f2641ac569f498ba3b93505f90ee5e63b5c6930a0721ff2088103fd9140db52e7e2ab2f09e2756f1bb2e2b8830538e6f78b53700b720624d2aa02bd2ba8130c6c8543feb77bee67dd2c05d5acbc9a8e629ea3aab7199de11335c91df5c3505d9a371e704fdc589e94be3b8cfadf7fc9320dae8d592101dd22c509077d34ad96c15a3a5598e27cdf3493bd320e4b3a2a2468bd6af972a5ef15328f7c963d8e612853581e13404213347429d2cfe7782496dccfb84b18b04e13e7e63d954998446c609699afbb0c5fd4159ee613abbe969379687cca7298e2385a8edb4e7df377c53d0727cc5dfc3ba29906dbdb99ec583c31457f2a333c9eca491c76e1cee753fc02695cc8ed70947a30f0b619b225f3aabf55b5b5159c0cf01f9d1bf9911494083860e7f8e82593fd82895d8efc5e8964b3e9f1cc140c2e1f8fd9ab05ac8c847e444fc27aaff3247e50903432f1aa07570c3ae6cc2db2e6a3e96b930546c39474b96eb36b8b046ccba3669d26a9952613fc150c26723a0cdefd7f95ce367514d8cfc9479a4206d4305e5edc82a5a588748d30ba9a72bfe50db46ac4ef8ab9733e189e172618ff3f5be5fd6ea3444ea8cff62be89c7273434014514cf67560a4bb29669d3bdf7fd50354b8835a3374a04c2f74b1e07278354a90737c3e6acf34c9346f333068665ecae4fbb39fcfc6ecdcb7cf15e45fae66b294abbb1837ab3886848ad776a4261ab3fdd3dafad173335e988cea2230c82847efa97cf3eaa41c4be9b048c57296f3d064aac4e535f7a6bc30e8606572e5b3da5e97ab60350d6860dd2e3cacba77f0502774a2f24fbeea0346d5dcdc208f7f70564d3eb6d1ac5d9deda48c02436d9c4909f2ae103f6ad7cd2b3701ef64aa395dea1618ff798ca1df1ccd1ed74597ccd78b3996ced291d655a6be3a6063db39c0daf096032ca95b0c22e59baefe1878ac3b0eb720403357ed4475139e720bec9fa1a8d07aa9fc81e19d572ea0d83c99d34cf369ee7d291e51c0fdbbb23d4c87fec0914e163ec048b074690ad503187b2f1d999d95457e2d33a3e92acb0c4ca4b6aae9cd82eac84bdcc6863dbe243f25b8b57341c0f014b808e1a5fa6b7268e7a1a2c20d916955a13f4209baf6efc3770ccbe277ac5da922a3a7f5a59372698fdc88079ce09ab7d18b4ed1aa573985dfdd43cf0805f6cd8111b33a035272e3f23c323e41339a20cb1711930d7ec55abc517edef3dd1eda833d9dbf064e727da00f69db2bf2d5b8c6a03895f10ca55c6e0c5ef92d293bded93b5452a78018d4eb0e5f8352f1f981dc7ddb9419fe0fdaa98afd10b4a63237950663f29bc0720c802e69a4b6d7d9c07673e203f5031cf204d2c76fcaf579550b38701bd08361a7c76522bb669145d8bcb4d20d4eb5e3838fd51a7f0e042bd470ac4aa634f869a83ae809353f461f0887b12b47496f44c1699ddde8615f9452de4044aac23172c23cdac91b7cad190fee2f36c7d495960b59d106336315b085bd39e26289862908ba30f61026aacdb865b062e1368da7eea7b3d2cc2b123450ca8668a2b52a724ce11e76d875c5123696530979394d7e49facccc276796a6cb4999f8ffc7d67201f7cd530af5eb06341cbb3a64f3971c176e0277714e4e6d2357f6db9882726668e662a218d23b2a323f95ee77e7c8e1606c9e800b095b8b4fa6ab363ddc0c72a14d5a6390e121874d3990fd9e70e3926daa858968d75575ae4a78c5a23c2e72b2d331e630edb5df114f7d78f7a6dbd564f369c5cb743ffa23366fa513198e7234a511efb20bd7927a0828bceca830e63e326c6c061fccb86d18e960e1db2472a75d4e6193ceba34b07447568bf4fb1137d96bed66dd0555ca1a0309602e17155121c065d97f14e653a87e82a618875002dd1fc0793d9e6f74e549e72616c2254ef742c339902a88c0064f983b4fc80c8951a73a28aa66292ad984f30633780e57018ee235329e2ed7f1a97736c73c9aa9a85ffba97bd9af1b5b7a6047e42b087c4ab4491cb1b4129059a286ab29fbb9a7b1464a44128a11d14268dce382830ef5920ad2d853325dbe6bbab23088d66eddce2bfb8bd4de808d518213b6c42b7288e53b3278277c5bde02420d8eee4c32444327193b2a7df089d50870f06d0c7236dde90b50ff3c1ff3683278e9f50a548ff8f49918e90395d507a1b2748b573669e0e08598beae991eb0f1d79df161c0d76f4f63e61e7a6fa7dc329be1e188721f3e6268281011a530036be230edac67445805e13ec7d84f3d94b1b0d929f045574181cd901e724e50bf016edee63677518634e6387c5a35dfb0ffa3cce2b27287444f0eb9f0cca301c8136ade4c7ea362242d605bc7019207d7cecbf9893347f7dbaddf16b08bf0bf9d68d3024f237e099a175da2603bf177ac80b1f00c07726eb9c20279b77ee3fb14f5ac5b02b86a3b172b15f846c672dd008f380854be114a166e0e79cf540e88c0947e54014699d7f89fbfc79e9dda35123a124c6602727914931e8bcdc39af090c3b20ef0c19910e4f70d26dd8a2f85706b81e28c457296e131609f280294ceabf3956b441c01a582bcdcedecb080eacf14735c63d7721eb52610f0fdd969b0c1f9388fcc6451e83d19c689bd09aeeb66b1c8be57c20e96c454f3a27f555601bfbe2a47f11c4b6962468b842ed7050fc85ba96c22cf721f8acc529a97afd843f0b909b7d92898ae79a3ddf2fae85af162e40a21d90b727fe0e0fbaaef6fc7c31c9dafb5082925d8a9106b6d644e67322b0808ee05ce72a83d6827e224d27888f92f5f57220a3308ca162f0694d6e820481d3fe7db0568196ef33957db3f7324f38b33db61980e08ed8b2f4e77cb36ec2b5aa541eca2720f409a9f2c7bb557195237673c832c052b3635bd714213a2576c7ff93dde677272d14246ae575efd60b368c1daba968fe57527517b86f5224702716b161b7572b225cb3fa6896b5ae1764d5291344b644456e89e33645ec766a9d1a88d351a72d0824112078da89d07b3193cd7b6ebf1d1a4fc32408d504e1a4260298167bc725ec6fc049f2335332a00e1c6c6ac2b2f438eb83eb768da4b034c7e81752902728fb1983cc952dfcca948b422c7d42a2d86b7866a24b2c1c316c7f0376cba1a63c88249248665842a909cbe12dd5be83d7e09d2e8df2185f70213e9935eeea372cf554a359685fde80c8d38de02b775a52dc3f6fce6cc68907fe6fc54e28dbc72ad4e9cf680c74a3d88f8fd7cf3e6226c6ed73dd36ab35f6a7938c4afc38e9457f6a248b9054c0feaf13ca79856438cbcfebb1434a5f83a5ab8d34c2645746a25ee40c00279c3a673b207e081a4a321a563874e86d042bb4bc709a67437b38772dbe6b44f4956c42670df4136b4db954268999ed033cc345277bdcf8c1384c35ce56bc372a635d9688de4026bbfd98075adbb3c43832805737be608b370588d433ac2c778f237c5767146f9984e9b04c3fde7081fa7c7dd3f8f53921681b48d72dc2be47c6b2ddd97375e982968b32466bbc2e57288b97b4d9bcd56572c272372ad6c042ec81426d807be7a28dac8d72e34b3a85f9a2cd6daa90b843a3b7d8b40f4f3570ad0fa0fff9a34674fa7351ca71da31e6dd6176d398fa9943c377d4a138ab5476d416c7250b9f1a1043bb9b5bd43545853e49c6935e8c36d1488bf3d56751ed4323bf61131ceba7f73cd753ad55021664408d64d2629f9a47d4cf1760f7384994084311b70ab6e6c8658609b092e19c6dcf3e61b41578fc66eafa59d2072f763886fa5fd34a1d1bb37322835edb9a2772add4ca83bc7eb4668440eb1518310f377cec5f945ca255786a08a4052b84c39f94b1636f4cbbb44403ef81f72b0f5ceeda6ce1743f040c61481cae6c265c921b06eec2f1e661b2b6f49748f1b03a943da1581b24f0e6d90b5939544f8c959e95c23ddc22580d7dee17e5905019109be0c2a57308cc68df88dcc7fb9bc487b938ec84d5737abbca1958557c072dc5893431ad4fc258d6eb37752d509b5899673b6bcfa5d756650d039fb9d025943d028dc28afd2fc37d89617782e249b1110a018dce0a83e6ceb3c562512eb7247f22e10d6214ba96592ce15f6dda9c59d33e86601ae31687e5ba4838f7a5e1e8639e73c3cb6e24162603ff6796b8355d35078cf5f037bb81e84dc24fa95085c8014c04f44e6439181119ca423dcf1ab85d8f4164b4e2bcc4027fc7a4121474341aed5e69256f303726d017e3f20b30450e303ee27398c11e596ee485442de645d45f24ad56488b0cc808df0b367abb5fd700649af800a4fd47e3f15a066464978d81663871eae10d450e18dd5daa0c95eacb1f0aec93eb4e6bf4fd413c9c472653d871c21a26484e68f28103d0acca55cf6537d25209bd3c5be4338b37f2f51136a55006e46bb5454c0ae310d735220f160b54adc623ed7312479251561da0870190eb45a21d44b5954f271a64f899e13335a75cdfafb21b586613987a9314a75b3a1bc3d91716461344c43c5b44d592310c332d00a9a28113085a7882e97722b09bf32f7c41e02c5e9f032a583cc03f3adc92fb36650039386da8903186d722695655f83e75b1814207bc07c5550a5ec82ed513363626cbdb70eaeba0d38588d046e534c6aeacf7301d52c2015254ed953f7ee8db20657825e50bf85dff6612c2f9c9e72eb42bcbcabc012e96898ccfe2e01461d3863168b31947d45a66b721e7dd6218dcefbc33f29b49d83bea471a08b3a6581c342eb19b846eabeeef90ff657abbfeaa02f56bc29dbdb79a8e3170f8fe34dccf2d2180244e29c2f038b722c517508e8f64fbc4d5413cb0661486e49f4ec16665f20fff4e01a357591df1c847c342cf43e3c41cbe8b323c0134f322ceef2670d395499a0e60e98a8cb395e8d5fd4c364589a591599ce2b23a5c93b365f90bd283339e632fc62cd51894272e59c4669c304ab508a6f0a400ee46a58474a009c3569aa5abfbad0971ff7ac6471945c3761ec77d049eea647773f111dd0882be173908570c7e91d08c3112c72d46fe0df87962931f351b4746fcab412f5985edfd7817aca2ba64c1fbb1b0d390f3d8e37a8816d6fb482383a3fd0c0a4af26a21daf7e436b722207cae8976d5df2c8c8b42c893e1c5cb49746a669b15647a34bb40756053532ae8a115aaf7272db88b1e89c7cc6ad9a9bf77b94cd1c0d818f3cd9ba0234b583ac83f967b82072c6e2afc33f592c36d9c91a54fa82d6c757a3ec369e3eae30376b2eb1aa009c7276f712e0c7ebaf42111e20cb86f692f1012501b8a19fa5317a581ec9b11be072309b5e1dfa5a90b00f3e2fcbe5bad5ae5f665002b2901bc7897b39262cf0c448a8e4798fa65a556c172961793d005fc4d1b2653357c76c92a5d3ae965b007c72045cfdf8e163d10499c33b7ad4410f21fae51726972f33f56ddcf9e2facd5472236b736f54281add18a9881319c945f0415bcb73f37dd8621faa98ed0958a5395785bdf9d8a85dfd3117ae6f6833c4dda21df7bf9c04f36d620d36014c105a72cae8fcc3a0186463f783e86349ff2659200e02abcd63aa56dbf58477f1075c122a41a365e575a111334f7c2c493499389165d66c6b9689cd136d969ab9db39723b943cf347934264face4aefe0d6569ba1894973a11166a43a89edcf70e24b7208cb68443f0b6469e7f0b3a42861bc2a8972c004ef87af32d227de7fd9b8cc195830bc287e395bcc8b9cc8be9be4bbd706ac74df038150bff3226ddfeb1d947269609ded033323fbc2fffc83c653fd51526098ef1a4f86e1ae814bb965c3020b7a19866a90620a540c565d775686ca669d15dadfced2f9dbc874e063b4cbd34143bd4c7bb75711ce4da986df3976f3e3be1ff0ec1fffc3247873e36bf30eaf2fa2e0c6d2cd6139133f57819250dfa0d16b2344c3fc369b64c038675c9b10de72e51344f6497eea0d611e51b911a3b5497d9f03c924b4126416cc3ce51eac0972257bb4639d746c4b17c48ae3c9ef4583a93697c1a9b4ac41d0d7bba70bea51723900807c064e26a1af1658bf950131c4f6563e99583a2abe6697600c8004277231b2b9dc3cec995c44a3ea239d1270aa6c03d5c47b15d4f4e59eab776c97a7677cd85adedf304d8febcb88cbc35f3691ceab13ee8598119710e733e2edf83f727a4b7194ca9a6cd56c0ca3a1e99290c8f4d34f0c29131ba8de15f05ea9b18872c4e5ae90d00371bced49a591e27b57fe4f4c67d3d93af300a985d59225eb4f72504084600cab48a8eb2cdfaef4fb193a0e4aa128ca5661cee7cecfd02b056d727f5946126f9b62fe827126f386af1f15a3fa5d069f7a48880f6783788b0b3d50f33f12cf4ddd1abc7e22eebdf2a618b11101f2677130ee4a9c07613519f6b072a78ca1b414a1b34b141811457394e4878e27906d55521ad84f0e608bd4355772b72b5f66b20206c41872983387a3829dae7d294ec693e172d34d02069dfb3c72c42e0b3102dd09515269c216bce9e2865ce323954abebde942624b74cad036729f98f29db5be0584416d29402679c24b7345fadea03fe7e4fc1c0357c116ea449251d97aa5dcde9643572b9ff7859b60d1069d0b6c67470922f9b3e36408986de738d52f191dc4b22a5e3007cdfc71bbaf6c41a3592b9d26813ad0ab7671d81f45cc6bd671252b86dae257aa46886d0813fafd0cb6eb2513fabbee2fad7fbc69edbd35e39c28c315ae3c6721efc426bbfe96ac379fd6d3a72fe5b3a0a47c9b4b35fddbfcab8f0d04afc6846ac154991ad7c13d4b5565d341c4d35cf166401e72a65a5fecfdce8890257fb9c60f8d26d16bb75ccb85dded4cdba311e205e2a972413c678e2138d1f60e2b86111bc3c57048e98276f436bdf163c007734f6b9d0b8576e2b6800f1e78cf473aef07cea1189856880b6337fb82c26828de07abc4721815e60da0ca54f96dd136c31c35f00e758b2240b5f44a3fa6099d966cda636f801ccaff36d09c1bf1c1a2d6e376c0701b3a1b4b34dbb8be7d2b7d1bd13c4363d2998d5fcb0260b6189cfe0b9ce2d5ecc6fee601338e3c6580ebd383bba0532be0569651626adc374e66e30803406906eb3c25eedbb73ba3b9a496428ef3497221cbf4237a3a565c5a47bef57558df723b68adafd929e65142ae4b14ff5c9d0a8757889a29b03f9c18c9fb871f89f50d882230883674b7595913c66ca8c260014ceb0b00cb31cfe1e2cf53e967c43d41ae2e56719642199b86ed10305030527238c381665fb70239e23221b3fff737ef32a46bf49a0b4e0344c38594582095729a5b25407dc446157f5802dfbd36ad218a69a527cadcb7ad5877992d366aed7212057190bfa20f7356bf9fd7aa9980ff77e5d04a26adfd3a5a45706e0ac33a728b0726f35ddc5fac97c98c129bc3839ad541aad0a37a6100d912c9abe24e9872bd7ab069ddf20a218ae170e72435aee25081196090c1e397302cf88cb827ef5fe4df3b3006cd4b16a2f07e51ccd779e6fede0260a69edb5bb61aefbbe29ba3722df0a340bb94821d27ae102978a7299adcf5d7362b15a3e37dc9acbb479ea96b3f4c684db4af84d6e41c753172d2dba2defae37324c0d782e3b662d5e947687236aa97eed031a2d75d344252db1e37f599bf454aa139772f51bed77ded90451dae1e9f918e4b08eb9e62ee381728fea9d40417852bc662c68c36f1025df7c5727a693cf9ebefd7105cea5502c109c7000d53115e431c8a6612978236fba2da7293248949279407504bbef1b82ba3781dff62c4589dc1b88d07c7ff5c4b6c2472fccf68d89c1bf964481fce4c75ab4c3f740a18a825e0e5cbc1fcae65594e1b7202de9c080fa1020272ee5b1c1be78a76d8ed93e35335606d93c2ae4ef286be07da69a11e24a81093cd7a01d1aa580a5ee4e425866ecdc004164d27575186eb729d93c7e4c6a8fcbbbd902af0098bd375c987239f3c6393d9735bd142aa1d6d72f728b2e9249e6a0699587e74e79e56b10a805e998e2632f437e5d0514197d12f4cbea14472b4330387c5f0fa72451a045626f8cd2ea89c420ab8460351885e7248b3145a8e199cf7de86b028b3a2761003281496a8ea141d38441d8ec01a30724ae1fdf3553b02fdd83327815c46d94f544143805fb64fd291ac326db9092f7246bca1be8617918bd82a2291847cdb8d1834347e1c023dc94cf50604ac0680729cee0012e7a0b1bddead8f57d11147d1a832913e007b4844404112c05bf9db47fce29d61d223d6a8d4117ac9539123b633a5efbdc7549d45abd70a4f2b11b7083d743eb82614b924f56fb778464a133da56d01924d1690f53ab79520bd7988725e0872e3912a3fd872d11b5164fdc7a301d975359f822ed04e28bc996d5720726fe3095f20c8519196e324da700165b3f6ea78f107d0fa897d48778eb84bcc1c897407769c3ba8e5b6fe925673c82796ade3edf0f39d4b754cd67e027f9243727d5b3c2dd17385fc65df873d3c483e719b6dddd6126952532a9022901676f3276fafaaa97a36d2c4a870e34e6c5011907c3178c178dc8403fa3a98ddf51632428de364b948d68a2784fcd7a4b4a2ce429e16c96bf7600ace5655694e2539cb10a10b2af27f2c1d75e2aa9047b1d7ee520a679745ec1a4b9880d5427f645a1c2dfc6c59e9c125d5f11f6ed53a4df29b0758f2ac409240df20c1958d46cf37247249827d6f937f413a28e7a44d8e191c954cd5b0860ce0a5035596faae0658c872697cf1601e42d4838cfc99a1137249d57311b02227b6843fe0e8645b8b998c726eac635216d6a7fd0f734f66d4e237cdf15991fbcdd4bd6282b1e06185edd526b1b4b940e0bba035e85413c2624c71a13d8aaf474860f4d3a1b0e0b81bfd6a72d7541223f1487580a3e22f29d8c230239c753ef0dd32887a1656e0e94b5e0212c217cd0d8c999f0d6c1f24ff7bf4a68da690e9d600fb8739ef08cc844e252433992733eb3957ce88df234fb22a489a94038ad240a3ec843d64c383ffac94d072f1a46e41b2613b7b227cb7cf50a74f087675b4f3f696c405184bf133f3c1180b2ae1e2bc73e0fcc203a7fcf9fdb10f2c7e673467d1051469ce7ccc938118f0722ed8a33d7a37d282259dd1cd2e8ccf5a690b9b64a7b85fdf255ef1ceed09181ef4de28a757e860ef4aa0206fae1fa851521416c3fdd6cc8943898e1aa334dd72ce7f4db770c1491b00db44bed077e51595aa78e1205368df7a7832503b8d527261d041ccaf7d7038eae8723b50c7553f87d8cec2bb595cf2666badaae4a0ec72bb471a8b512d57f2d8a7694d515f04c7fc3ce2dd074291fa66f2795063550672885824e30c150bb2eb29bfa6976929a53f470e15f90823792b9efc5b81fbdc67f2d56f4b246b45e4ca4349d1ba6f005ba254a3f8f60b4c5834092e39678f0e51d6a2b86af39fef373e49f65154e0a47b761cb30723482d32afb6bcc95a58304641e8a9be51f1e5dba7f2bc52aa0b95a6e23b39e95363f9f67d98c67f23cf547250ba3df3c685de9d71f1427b23d1410966f078318f2b18cef451225ae50a8d729e8c69177718932e9dacbe87cd4fe41619cd892c02e8fb579e711577714ef2562b4bad15539eb9b779301b35e97acd1fbd10f472966a7087b3bcae3a55b1da72b6d5f73c21b0af0ff94262e8fd33e00611319e02b48a9b70a87b4b9dc83b1a09cc9d82fabd51c9c00dd83079cd106e8a2140f360225a5feccf042957e6a05c7263ef6f8c65010562b24dc69efc7e088981b5ee276d7c965cfce667bd482b6572e19a06359509774436f4e4d60e9549f5249d18bfc7f6a5d943216f7b100051727a96eb5957eec0a9d25342a0cbbb06a24c7f6688a2cea11a5a1ab97c66b0b572c873562886517a5d07cbbc1f5cf4986e2f156751e272c51553563291c920e7724927059735947dff1b1ad17f24002a34c5534fa8c04342fe5e91c32c8925b91853fe021a8c0c08ca7e836ca1212cd713dff612ce878ad33747f1a146b084f872595860ce81d9e836e0f9aed20043c3224564e7fea01c59b7d5ca58fcfe94b972945cbbd34bf628566be98cb61fd70dc1e8cf7ce2fcf0bc976948cd26b1ef512c9c395fa374a0b19c3650d91e4c52452993f17c927e5c18f656d80c922b24441fded5a267fe605e030fdd21d8548339333df886821484e70846572dd0c9ffd372298689235d2215eaa89d3fe7c517ffde2bc73bca5c5b786522e45e17cb6689725e41a319014db72088a939245fb5252fab61b28db3bac47fedec0a578376544fa04d3ae0774af77831a545f025495804100bf3bbeba4bd3dff8eba438c94f272214ca907d247e95203dd505ba35ac555f270c101a6178a3518df7de298c17172d3b56f8f4ebce5fd4354ae640451e6841bbe23f281204b9fc6a314c316809f7297aacc218c9d2e0304a9e39219fdd3fb1d5a482492beafa9bbc01ffd91b3bf18672e5e052923ac03a59e56adca6589345e5d0a27e711a0e132b3651c4ad7f372e02cc167867224dcc3ea3d5bed53a1b4217efc2e2137671e012b0cfd01d8e2725b71f29bd8f6da7da03fc8c8b905218edffcf38dc60d68dbbe8674f84477da0c369073df62b30c8557105ca2ee69d78f0c430b3e12aaf99a80cb5a53adc9f20a3438ee070ef4921fa6589ab1da22ed86ce1fe9e3bd0ab3125a91e63da6bea63edeaf94d23dbfe0fa02ea730c8ea9b2829c879badeec2645b0286a6adea94dd30cf2f6c655cc2b7040a28e31514e93206c9b34fa3be461503885b6586344314406e3e357c10d8faabdd5790653b72ef6f45e090c58841a8ca7e604b48d328ca72d87d4f593ed07dbd23e4676f582525d15b9b1857a86b74472300e72e8c2e5d56f081038e68e34f8fddbd1d0756eafe0258f9be710fe7054139b4aa971e18a3726c6c88754ea4df5c1b91683f671d4dd19a0e859f96791cc60e235747a3378153397c24d024a7ab40ce69250a73c6933f39bcec525bc387fa2b2df7c7f738f872244807fe38ec7973370ff1b2d1f8272eeb5c427d8fc445a19545bde4b1ed676f33fd17e8be78002e37836ad5744705d79a058d9a28e7bfb72962676f32cdcc6fe69952c1342aef65448c1063dbb4e963fa280ee69a3f9706c5bb2b382542e37274b1a0daf2dda77fd2d8c6b4198c2524bd0c7fbde000fa2da0fb9f85fcfffc72032e132b47f60a18a7272e96a710c419730f96c025ca55a2d77521fe22f2a216e80ada9dacaca3a054118064185394e30f4a0361f8ea0d71cfb231f41283f172357d0d5688f2117d328109c664674eab89618662915d3ca8002ba6e1dbeca07224f1db412c3c5c0c9ea580b73b0fa7fbb01d4852cd6651a06f101f8607f9f872fd61996ad1b905ab08ba00dd10c2ff6e7cabc14bd7719878e6ede18fbede3a72c82353425e471c1df9a0a689bce336cd0ad1f7b60c51a664149ca640247a6a72b147e3c6c14f2e2e93bf904f72041a9f607b89de130ebda752d5437f162a2532601af0de4f3bf9e010d56e299b71f06db188ba70f96ab55259378d5710638506da7bae6bc9169f8a1b9cc8b7dd541b24cfd612bfe2e17b7f653f7cca67660c72e88500e76fa5641f1152a886c35c18be99b665b3e39f9d82c77e5de76edaf749b8bb1920d4705c244337f520f200eda87f0ab0a6fc29d68d9ac901b35f236c7299930afa2d72de02e60e1933365106c8689ece1c8953e5e5be02fe2834eac472dde4e9ff809c78be2ee6c8e680caf0cc46be15a3be87f739148210d515bb1f724abd91a358ae1c426e629890cf5fb1ffdb1632b833d88c1ec0ae15650e8e0b7243aa236edd70ce9d649893eaffb933ac7b83498442c67cb1264bba51087e77636daf750b5ac825205f83eba3d4516358b56ded94aa9c9e4a2086e80d00aec81a1c2a95ef19e831fa1fa60c24da0706106a3d9982c4e072635f8eb534cb230d721fe89ebcbb4be18a529640ce45d904a3eea78d39b7a390542738d02a86948a72b9a1d8724f7965db4e4308e2101dfc18938a312d8a36d59b3fd2ad5adfc6cc725cd6ca53277c422020c5c6f5e7b330b7af22ea5f8ca63480598d53f644eb8b3011145a701a838bea658b4a95df643a427ead22da1412c50f7e7307f85355090820e9acb2dacdfeb820b2b8ddafc5a6a655286d128cd59a43bf2140d2eaaae2244637fe514aa93d822ad32091577709a8a67537cc126fa601183fb91bef7e0925d615dfc53fe5ffacd56d57376e4bcbad4af4773e58c495de67f30803a64f4172c2e1a1155456650810e1dc1891ac9b97717307e5cff30fd59435a2875c647c2554cb557838e7ee4551cac96cc6885a2c6f707410bf423ed27ddeb67935850d09729d9f4f970843c9697e4412e9686c52cfe50fc37aa8cdcbe6c4a5a05740a5729e69d436beff7c16f165741f4a40a7c03530bb46be8ea053952355bbef956458d2af76223236d477a64175c6db42a5be10499d993f3f8fafc05781c1fc686c7255595fa71b2e712f3fbdf49d96039482912c6a52852c05f306d49083d9a7f7723e897df451ee0f5216ba96f9c79062279893c4b46c20eb7ad67cccc6845e304da99b46debdda2546ceed6a2730d178ae9e339872d97853a5517f49f7b581ff7282b0479f5fdc1a1858ed94f90108e12789cfc3dd4633a315ce026e28435e28721522bb4bf056fb7c664c7a23c8c4f25ecdf5b32c2d4129fbd740f83ede340e72b7de0a013832211728e298f7fe61a8a94f4a54e23d65493a87576cb1e54f6072dbb0c7ba4340e8acfa64f1214fe41ed6f0d85dd9238f7d9643324397c3fb1972a6b969e5149da7c9a9446d6c8eb44654bca0a979c4897896ec5219c2a6f2df72df4bb464a892e727428571de8dfd5ee3592a338cadbd354fbdd312c0162c4b725e0d77c6f1b80ae33ee7bea90309d264f034cb264bd26952ecdef0d6845deb6d110c1439886da6ca6348e13b8564a885f4311d61f5e47a089eff77911791f9670d70e8454a38ab7f67942511cb9d151853ead9bfb64eb67cc32e8cda3ebf7a394c4fe5f9ea063f6e0217c4ce0f93cd86aedada0d0ba97bcbc3cd130558cd6e72834efb8bfe001fe5185fbb0c31be0259160a66df592ce97a7f3fd7655a14de72bb89baa144066fdf3b8a5edf0ff0b45ed092b6265ab480025675e0c88b02be72386147d82339bfe2eeae304e552fbcf5b5457ee5011bdda364c1f14817295a72c79f72aa559dbaa2f3a0378e3d2ae6762e76e0f8ddba49074997e323f3dea1697b10fa52fb47002407876f82bb1c99fe90df84a5a29bb069eb2bf6a25fed955fb259a7818c9b7418110eae98732e57a53757e497ebd1de69537c5fc819216f72f904e7290d6df02c40bd08c55e77dc7fec417c33c5a36ea0c20953f81353b32dd248cf5e1488107047008b140d85c543c176596ab5dba55d443c694d56b2197260e384ace31ff424ccf813a04ed6d033a30c77fc3c95026b850c8b3f0e8b2b7224e6462de8ab8c33b45ac129443c54007851b4c1daad26b78f9cf633b504ca7277a2247c9cfedc84f14488ccf4ccedda984b853008e36243a6968f09bb437f2a0971e9d1f828d3489ad6ed2c60923fabf7ce5a6f35489040a18731cce5c34a257768c2a294430024422399d1daeef00e5b8ecc5e704458acf48a49aa0ab44372635f8205e41b220bc3f10a3ffedf9aeda2050eba6c1593896a600278b8ccff0c317842e60eb1b36b9983bab739f3137c7dfbc83fe8998cb9ba0959276c40eb72e0351605e29a64a02bcfb202a31202b1e3618daeb6e929bf70634e5bcbe7087245bdd5feaf6b17cad93beb6ddfee742be1f8cb5876258d0da9814eb1615eb65e3e05ca3a86afa98853a8d15efe8de1951e55fe19d33be611fc709767a7fb3172130081b0ef3d1d5ad835cb62b41d4af0e42a0ef280867b21ee3faf23ba2f0f22abd6caeb1998b10fb2af43568f6fb569e7506f93942c8a00c70568a0ded94d72ecd09a16405f4abe26e052c7b5957c89c67eaa399ed2145f52570dfe6312a341312ee763921e09ff07f9d200d47f61ba6e241b3d9f8d9f616d98c291cb451e3b1160850b4026560d746dbe461d73a5dbb8ab1a47ed9cc54391e7a1e0e8654672d9d47f250523226a1fff6da0cc02659f7aca11be9ee5a60016a2b2b8cfa8b17276f8b3ce06f4ca6ff98eb17a022c1e02319d8608ddd41713c0e925ee8a7236081a65ef5ad2173ab0c5c0241c765f4b9183d093555b27b1cb834c76b8986903717ce9377c6bbeba5f9a22a3bb1b324fcf964819437c789e3242eee223774c65018fff8098c2a6507b08a6b46d820c10255daffd40209ba95357e9fe1ea0a706725ebb74c2d2f2c66c05a7cd127df5591fa4b209e7999767fb3578a28f0d414972b084f389ab8fb87cd5ef59db68601a5a56c9bac1773453dd8f1f31a8535f2a72687ffa0583f49114b19ce1c79074815bb421fc2b2a28ef30d222ee3649b38b72190d47b2a92a751749e2a273985f6b260ff29f72f45cb8a00dda266868ce3c7235b37eb39186d6e24d4c74dc7b167e89bc5e3cee695d7e32407ac2c020cdd92607f5ccc1a5465dbe3bf0e257b18d840878c92efe44dc54dbc803a06c23bd97346749a5b07e12da72bc5cc5e528cf0a808608a2c35f6b3c24afbff2df728e6b72c5c5b26629f1ad50d531e9aa5fd64c3bc854be463a8dfdd8be3db28135000f72941fb5491375ef44ded8bcbdc650de3f4cfc7fc5cb401f0d61bc8e4c4eaef149e1256139ec670059ce13178296a8a15ae460b75bcb3d96ba1dd3693d0883a4725d22dda82c77d856bf3105435010a1239e220e2f0152ec7750ecc7e17856a972eb7fcf71c8cd00c8bdc0cd3442a510808e6b8b417d1d0ebb5d699a03f8a4954e9cdce4e118057b014872a9daa01ef3e18e00a55ed7a8e26ccc73c4250008e319f0c02725cc33a7cf5c0b5b5b0873595d0f8ea158ce47f912548ecdec8dc14b72fb4acc1c8bbbdfecd07c1365600384c2aeacc73df1b104032f06495579a9ef72e1db721344392d383fc986e3e642d156bdccb92a62713279c8f3744f995ac672b49a57b790881763b08c074b5617f7c4be6b01f5599ce9dc0b05eb9253ddbc553c8135fcffc29c545593c3dc678f6624dd2da2e22726d5e2fe30189153c9994c173f9b07dfdc7132ef5a9265b2a50a586abd5c096bbf81fb25c13bfe28eaad4b013b5c5075d97b59e591f3c8f9965f310da503522529954fb04417919aef04727a0441001fae515de89df4c3e73f68826730dd8c0173cfd91a5f516370a3927247afc91124def6b0c7a8b6a5f7bf290b7ed04ce0bc18471fa9dad868dada28299b28ef58a1006a89acf13fc484b68e0d2d5ee0d4ef2eb696292d048ef804fc72b699256cd5e40bba90f6d44a4c845458eed8b8f0a66615cf7481694b72dfa76cc7ad1b5be3f28a90cf61a4f685065ccade1991dc67652011df9402fcc69a3329f1e451b85a98196460144c77aeff8874a45adafb8f346311b82b3307897b8172490328e5e1b3106a972dc36a3035e353c50fbb69cf8cd517ff3666d9689ac272f32d0bc22ca5b0a38dd3c4a4aa499656b6e04f52628b8637887005ce7962b867da242d086fdf51910310438bc7c8ae0fbc831b33faa0d372a235b02d6b460d7213d1824e1df8adfa95d996b4de8938ec4b782ee37783e0cd7ad17fd44217dc6154ab2187c0ccb2657001d2e7217a8efe888b295c2246a6fb0871af95519a760be13544502a1e5bf523e99065de243e8072a9e0faf944721368a3240cde57a572242f79a4a0ba02e842553f2017a3be98c84008cd6869343a0b242def6e3ca92fbd643cf97bf8e909896c3beeda90cce2d88c526d4895550d6efcfcb230933007f2edff0813270e59d2759e1865d38677500c301fded4a113ae5fe9e94204f572aefddca901b5fa78aba2b24c8cdddcbc49d890a6dadf620d6084819d3e3ccd57fdf08db5e8a77448041918b7afd618218cd7d902f773c8c8be69038dc63187725bbfc19add094a57ad61d0ab3661b0205076934275357b8be54d2a6345a28a722eeb14d75cf049fb9a9b42957730bf1878cadc9b2ee5e7c3f5790117ca14187272273eb57673eaf9b04e8a7934096107ecc948b3b0cb1101b28333f9ca03b86c37fb704ca0b4721a5739ea0ba16b411c0ca3da2f1450e44e16f4dd838c713233e317db1d5d1c667498b63aca39baf380084590d2072ead2d049308b05020cc7241cbd0a7a10cdbcca710f1bbd1d778d950294d1e5689f95e52acd10bab6b2c72cf287fbbda52122d77ca1d871e42d84387d9a6bf6fc89c20552542995303d052affc42b905c5c829e830d34351d511f564005d4546d07efafb43dd17803df6362a704a2ade93ee2cbae420307f2cc1f626e479f68b0ac1a0f41d7535557208725c27ee85a5a0c647082eb2c824d12fb80d0346e1dc6664133f558eb07cab8a7246fc7d3be4315321fee86dd9f053138a27e163dc5902ceaba1b393f5aedbed723801e1bae79539a94e9af6a49b8c7f89d6ef5ac06374196cb91066fe596dec7239d9287baf9853d12ea8f773058480231e0a4aae5232d294b261b34d7101ed72948cac91dadff46c66f767e5fbfe8c2b5f7c51e7a9d2b434c4748ba9af9e9c7241b4c5f535590e7e266f6c0800edc44b5814bb7541b7f64161166faa4829b1727b2cb1959d2d44696c84f80b9576f203417825823b82178caf611f663613970bbfb2a68f0f9954607b5cbfbe0ac73d2d7c2979313bc517ba6945b7d12bfd701bb4e80fe405bcae82c7264f3865f8d38f9b8b84334d085d86c9b27a5313eb51729e5553c243e6df44b3ab0cf6a41bf7927e62865946c379a222f2cbd6b3bf6b7240c1cf53fc07863bf78042a03d18d32935f85acffb10f47b10e8d786fb15e759b6bee6647c1cca2cbfa31730f64866cf59227ade79d4dc13420eab0537217f6578ccb00dd6b77e4797c4ab65129d891c881569a34c2837ad601b65ab6cb52a06a1b62bc54a8f64e0aa6af0dbc139eaf9da5eb992f938fd77a85355c35943957251d395762594636ac91146252d21e9d23fcfdef50d38d32d6670efd6c542f472f614b1496ea0a533dff40fe9ae4b28dacad57f487826c4d05f09d766f488287293aacfd5c7baa30b6dda1bf10b34b60ecfb2bc63d04684312c9f67713c789111480e2462b7677734c45cebc08c43e2a0733a47035b2aaba638c6081de4e0457264a7985ee7e30af953322f2a8fe30d10c2f86adecfe4a89f8c760a9abdd54d727905ebac307df50d2484d65e8865d49657b61bbc4426d0467220a2e8867010723c2eb9bcaa49e1b35a4576d6cce9355c4369715c05ed15f9ab02920834052a727fbac01e24da4fe45392bf304046fbff68317fbda86cd0f46ecf720f7ccc5972bb8bde0c38f9b914ec4c0d079df81ed5ab5245f3ca9a6f89ad4a7a00c3b77c702530ab6bc07f220f05a5e793cadee6bb2b303701bef8863561a2b06595b31a72da9574df89200c6bbccf1e31f4b0ac97810e5fa7ae5a9219c6ed0fd5fad20872bf27927fbf6e41797a06be75f79b429ce2bc151112a02bf56260166664d71f72358543976decb2ccad5611a68b3258c16f8e4c92e4dc6205c7d56c23d3cecc72922b97624254a7923cdf2971680bff6494b6e12a8c381ccc4a573e6921b581720229268a6bcd911fce37f7e3314226049eb831470335c9d6778632247d0d797249599ea8557d9e212a4f01c76bbb10746e3daab01b500e19063c835deeb376729839ce64ba0a5237de03bab90dbd7a7b8bcee429f32da285cd46400c164bc1720b75e8803b19141deec838937f4cce140e7ba4bd42512ea036ef910b5635f600d8d6f188fb76f131390ed3109ffaeec76c3f8c5b88ae3c7fd43975d7208328725f1b3c1176338482515401c78a51b68baeff222f2cdcb0f3fda9aa365720924e2f5d04f35d7908ef5e2e98cdb2c203d7940885c329126bd67dc1bddbd6a595722b5bb6057b4dc76a8e8d698fcf39e5a3170d751323f009e633588675856b3d720a5b2478453f3aa5cc50bdeebe4264670cd2e9cfeec33a3f85e3346ffb16dc721f2ae8dfeb0b12accd95086a9ee0383604da4b540c0a0b473b84850b2e4d6f72348d9dc44cf8463ef33586b0b3ce0635caa8fad8831dfb240c0ad6a9eeed3742bd91d1c5a56d4eb4c3d3ee2f816eb5b5c544022a4211399fb00d2f8bbcfc6d720f295a82adfa40bf50f0b7a7bb31a1b3352a4e1216917d21490390b4a7b6177290600190dd2e67c94f289326d9a3c271036247789866d0664d7b930b7f952029f61cea87ba4dc2c0eaae0f8d8a3acb418c901fa0574ce0392fd5112749645772f0fee667bb576d42b06e20812296c845eb16c3a0d8debdb1c78ce0a2d706cc72a444a034a0be8a07b263561ac4656b97b4bf6d20d3c53e99d15d6e8ef1d8ef2d66cf1268c06a2faea734824455488078a03f7bcc31817c833113b4194892f772b2cb10cfabf145b6d6092fe932e6c820d730d59f3b0b0542b1a0fdc0b8955372f69376ee40a6fffc355d1b156f640ed6d34265f557c21d8bb603568bef3415726bda8c25a15f15f3bfc465cb38099321e4449b03f6522353656ff67a0269d772dd85f3ed5a745dcd63fbd248de82116d04e4e44ba4d5bca322fc71c6c63d38722ff565d09f3f9f2f3d4c95bd434864f177020af3cd3537f4d5b3c9584b24b21b9dda4d5c07b4d696959156324817b261f4dabc571a2183520b9580749c696a474ab1d76581c09063884518fc08fcec31c1f6687287c153706ea929cae1b97c722862cd8bd9970cb254adb2df9fe3e26b8a72cf3f0536c73b0831f901e8e6837245df6f4644ba51b44e965e04f4f9e7192527b42083a0ce926d83bb9b712cb52d7f41fcbbfd0f3a9d43145ecc1c596d0609e8e36df0d1f05520ab8f9aaf942672f8950b222a0f1e4e97a93c979643aff08efabe4590c7e7bb39194e6fae9ed1726093980dea71d9c7e330b57107caf8d5004baac9f472aaede7a1c1db85e9ac72c1be139cf79fe7218f07dc0015e0b966bdb35558b7594e1b93110f86eb019c2a97587d515538ec603cec108ff8954ffded62e1ddcb4a6599ca80bf912150a348358152f435b3007cd6b9d13dcf5609457744ab23ed1dcbd3472a9ff4a13e950caf7d30b6ea2ccbb217f78ab3646340a9988dffe80cb7ffeebe4604007f651a72531cd1fac46026fb304a55fa660de68bd79bd116cefe9d9e04e9818a59100d5bc5cdc02505370453f08474d7d1065c60bd2de13edb87fe4a340193073a6c616c511c1b837cfc1563a6c434da83b13aa145ec0909eb1116b16489fbf5e9143d72ada8e5175173c1512aafeb0ed1bcbe1b7101f4c6727597b36e9e7854c1b76662c724df7e04d524ef126008509eec71b24b465365b2460107c513d96b85d7686f941db3d6b92a159bf4a7380a9c7cf12749ba839c65a21c7f52ef6ab082f6c272eab4fa3d059a716a7fbbf4037234d73231728dee9e0cbee7c599b7966fa997723df5882e0f2ca300e2080170ef43c5ddf84a1bbad079ed009050ff3ebd337724662a29c840fa7650b873db18865b03c83ad403aac4ef66a46c3757b308061672ed1f6886b81d6657723c9c0c3d3befe373ae1e08d228b4ca74f48eed2b271072729c04508a0e5148a67cb5d024c8fcef8d0545b319d80d61e1b3cecea79806447e333107b7e3dddcc67544bc51b0dfc25c88636b69b767e603827e8b1d758c7220462c915957a1ed207b806ef5e03df4e0f0300f9e52769aa2ac40ce9bb1a32e31495083f31870e38062cf4a3d8c4ad6c004317e198abfe01300718dfdf46972052170f053073f323853c13fba775d6114f4edffb560d42d11f4680e580a6372481203d6a8a5d3280d0bca8abda38f80da430fe3dfaddc2f3fd89d6811421b4cf3ef2f15de7ec593e4c2a53d37f23d97117b8571ba3e559a547770d24af26057d557dc02a44e24f40debb0a6c86568b99a0a8427af5e7fea7d5230f3a37ebd72d82801ea3c2a4b86c58030dfc98c6eddc98b4a028b363051be7b1906503ec04ad4dbbd30fc6c4436bf5dfdf30944d6bb54417f1695ee4716ec06f952bfe599726014cda427e94941f90316f5ce7a714b616c1b1e82d3a96cba5062aa34ff8272350b8a07329668352fea3e33f477c378205f11e51df368be832c403ff90bc2725239f02eed781086ef7c1d718333d0692a981d5ae67da4cae2bc465c4ca6d50f684af83b13550077872cd0c4e514380b503494e1ee90c299c417e870b4df1b72a2e664695eb1ac0215297e7d761069dc607ee97381e7907bbb1c74d7e54cda72f32d13b75026425bfd5297fe472df879d6ea595d1df9852036914710bff4ae1d9b0128fcd54c93f8a9e0378529d27b9bd39521fb9afbb08a3cddd2d56322a772f730f1efe42ebf42013daf7cd419117474559905cb75cf5262b07adf865c1d729b163dc1eb4d00c39a3c4ad8df77bc24437e67a31faba32eb45577020ea31b72ff0f327a943dde59883d5a796b7abc6445204bee1251cbc107017d6eb388865883fd3203ef1b07744b7e90cf7d7835b9378d8b6344957aa409a72a09ad7ced729f3c08ef9eb06cec3a67fe3ac12783ce79b364487beb6386ad5b8388501a00727e88662b326864a44cfad931b0ccd52b10257b87b3ba39ac6d9263602b5c2750596c068cb939f76547ca0213402ee6f9ebf71773693779ab3ec2749ada3f3f7257eed857b4988da2ac2127500424726b0a6cce77021ea5512a0677f6d7f5537281d6e63f3fcd631c13bed3921869d546691907f60c5073cd9bad4093e1274672bd312fa02a954abd7f67537e939ed282d53c1c4218c58ad2d9ce9c30cd6d336aa50a1130ffc60f7ad693213f2826e889c2c7ae049af5ea81ece33b491ddd72376e485721139feafc062f8d42032aae93fed14a2daa2c6a0d0e11d85e190a317226f76c7d7cb31247e6fac87bc93117abb4336be93455398d0108f0f408aa687200389b507417841352ccf43da5f4b41901445b2a8a7c8d0b6285145340354a7209f10b9d4ddf4f1f59dd05b4f4ad71e6751c94cb618929aff33c6bb010491572f279cec62bb009ebfd974a61f46659f4ede9cf1261cf7f9b2b3a1b26d53f5f72c89f1582d58c062b13eee16e20cb849579dc0535d8da8f30f426ce83d0a7fe72539652ef88e4c1e73612f79687df754daf7d59bd7e292f1179672c4885595472362a36b7d2ab6c08282765a4a099a6e043ed78ab5385c74c263649f4d5055772fcd4a68b50eed9ca630f4ffd821f7c36e65a619fd67926de5233b93f6c740d72060d1e9ed083f0d25dc6415ede5aba5a685f8a0273fe1e990e5e7f08f60d0b72785c81015c73747717685fb6ef49aeff6907054c2a2c4b0443317db31b3914729222cd4d1e4dcc5122f89fa929c18b9c483da256285fda913a6d5ffc4c5c6c729fe8d8c1123d93e96a55c9678d1834333408f9e1234addc257927e6b0f0b3f55aa061cc7a30895ecd7c0dc3177b53ae239301e989c03bef8cd7012a89514e500864651b355a9cdba72e54735d421481930d0b943857965506b0419c2a7b58c18963f761c92be074fa2b250fba401d86763378558ce93797dddb3454190ccfe72e9eb138c9b945515daf2546ce2ec0d5af62fed9a561f22bfe678877f683a717265d452e934932bf37a0ba60582382e878ae905c10a39e9156bc687c87415782715e16dd64ab02055e45ecf1cd5ac9ce886d2e6035ac73be1fc64df04a91ab139452948f23e5a5dea41cf2668e4dee78eda50f97355ea0a45d0949890ab1d857274433b252df463b7e2449f37d0d86cbc3ab1ea8bc1ee11d92e7d6b8903e9ca72320c3084e71377b6acf14379420c13c3e6166933ed610327fb3b646973efce24be00fae3019facbf7623133ea8c3f381d2d49dcea58abeae725b009d56592c72d80e30f73ba26c89b0b0408873467bba915f429035018b91874a2cb3c3f7dc722071cc8c5bd86ef61f1f4995803e11cc2cdcb9c59f3fa5f8997fcb89070a5172b27ff1a60a2865adc7bfaa00ad50eb5fb8bfad5d8038666494bff7230bb9a8729e31645daab92bb98f6dd9cdfd90f047f3b420344799e17b1d90f2176f643d72603b224f66c0cd334896943e059e7cade8c1fcb4c224fcd65da5a4587619465cbbeb2b1137f365f78b8532f542c677ce9525ea4ca9d72179d6ff3150cc892872d7771df501d925453349bf09a645cf17714d436f9fc10ec1214424a0cd011a72436d9abc08f478d6e731a0197715b077b0dbabc5b5c0ab5b1125b642f821d0727b0179544b2a167d0f8250321876b24a02f46e8e382bfdd8d0016bd9f574a118a89e83a1061ed2c645a03950d77fac3f6f83f0db271292e6a1c0f423d46818451b47fcfb7b08868ae5cee412a1c3428a0fa2909a1446343e9a6ded4bf4cc027245ec43cbd1d8267c6dcdbe23faf25504254a8f72138cd39fc7bcaabc91f71b7280227af8c016bccebd05344ecb88549e8fa8eef294c634d088d3661426ba9d72728e4e880d966bd729fd0200c2802c58e34f514e0e2d86f6a2c6ec559db350707277ff8551cf544406195805c1c89ec320179a642ef102e6e56dce7f36241f72835206d03b26007d567a1f282081e0cae5366c2dbecd97d77398e6bab89de74ee03077a7b69a83c6bd166e93ac08032c76b6a3171d22444c8b8180aaf4795972d35ae3120a9db67e635ee3aab2b8e0d0b53cd3e6d0d66fc996fca15c166a92727ea0baa5765882742cde12fa969fde42b738723214f2af9b0c9fe8d811d87c72930b845e22d3c3bb3272b89e62e0c099e6189806b296b10ee30817b8d8556770007137b42dfa4d9416231fd8b1e2e1d9af55a77ea09dd4b3af4539aabdd44d190193be80634fa8bb4971ae8a155c352fd0e92d069bf30ba2be6cca81941612723de396f960dcbd0da03537b5d304c2912861b93909c4a2a8a89949d2e96b8e7210f95f4bc3f4fe77be5682050075a42fda51136c4061962bc65e54d00ca27315c6db8699e291b035053e4ea9c62ad718da4cbf8c1f5766385a59bf848319e5721535a5123e2052804356d7245e50c202d9661c7800c1b919f08fe49f23ab87724417377726a17f9f3d78160adedee24e4c08587f705b5cc82fbea19bbd0fd13700fe00c12d6b1d8787a786dae7188957e7cca2c5995c4f3e2e81f38732aeb872f9a81047cb66235e45f842092aa6ac181a218f7f82f48f6c29c7252de3d3a672c7be327855d6fb939a58722ef862cc640fd7e92cd217cc51d9132cc65804ca72ddbd64bdd27c1ad3f6f37b51ee88f69e2d2fd39c3ced441df2bac2fa4c4aec718537c0b62b6df3716279aa42202e213dde3f58110dae905effb469be66080a72931258dbe247f82a51f3d825e2b9da6e5d62b1ed269800fc448ad75874be9a7219285034139bb35d2f5c41685971978b933342d622cd7a7f212f2f624c09bd226e1b3b6bdbc52245558da7d2ddbe4bb6fe073f476f61d681484d1d56be0706431eede993c8c92ef859c7535ed572a9e1705a24d713123a9d77f6048e46eab25c5dfd04d6d67cc80762c7e3ba12c6565c5139b3c3c726e08ae0eb2d7fc0afa572f92e227b20b35d88c4643504eedbf81878c4105690fe2e678b11cf19a014c17207f68703c1b17e6669e22d5ff442ebebed5926913400138822f461026e36e2725e60568b5a21cf847bb9d2b23c4e7c6e66d6e852e32c05f104932c6cd23ba454b8d8dd4f1acd44c3864ce2329783932e7dd9bfa51d99606764a22a51be8ad872772836a6335f79003b3e3fb015a783392fe94cdca68de6b6382878056658be70b434c5c173897117adb836a4ce7daa863dd0d8082c11a4ae284b94167e94de72ae7e01a7380c2e49dbfab6637d637a6f374b623a69af89f2abd3a137ed8e9572df81b83f7425efd1d2dfbabab49877d20d3eca15310cb48af29b692c92403072ce449927e5f9cca13f8a32d523d877195036ba4affa731c66f1ca0937e667172d3991a17e48089d3526cd6d99dd843c57b267e2a8c722e1535b764cebabe4d72519086347e2115488f16f4fc6947700cfa7f96b62dfbbc97742d4c422a152e4ab5cce58e5e165137c38f97d634bf2713d89acacf3db3fa7d7f2ee19fd02e2f72a7335c17b9e9db0f9c12d371868bd4afd9e02c1e2f6e72859fdd3d0173884172d90b02f4bc7b5497c9bb4273de0469b358ef7abf8656cf2c281ade761011d80942f438bc4cfda7034375832c5f5aa55ad9d4708c3e42c78156bd6de953252f72fd27902af7c7d83ae376514bbd0d6e8f727f195c77c4c7391edcc352d638c021e64cdc3b43839a3523ed4ade7e8ffbf303083f489987f7f59d59d57bc678306dc95aef6da3bc9620032a461bf8c3c1aba60b7a9418eccd1b03a90a4e66fb0a72b7a0408ad006623a262d04ebdaa516c542b933166473944cf5e0b894292d04729c73557870529eb3320ce7b9c366b0a033fa6077847bcb0d08d26a21b2da375a9f9beda6982cb74842f1e541923694b8e491e871e1e9eb6c38409414c8f6df72de76086ae07ceb0a47c72c2d1a6a68646c8229fc36228cfe151e2b63015c8272a0a7c9ab9a5313bee306f70dec4a5db6548900f1ab5414375fc5d9b79a3c1a729a103c23a3bbab44432f775974f28156eef5766d5b9b1029fa0b32c71de50a7209e4e0bec3cac18ac53ddfb24a3d4551f63e192783dd590aab6a61c5dc83dd72c3710c586eb66fb7fa9d6921e74447bac015f89194c1f77e32b7b4b819d9cd253d77f5a2ae826242a6ad0998e66c473f56bb78cb6185410255b870574df16372abab75ec29e1d5db97a33704161951977947cd7402e6a5fb6555c54cde281a7214a6f75e0b6269a32375f3ca9bff0a00a65e2a899684eada47594834582da6724b35e4bdbf123c1ab10708cd5f8f11d60b83b12ac6666b3568ee0be269886172104093543738f9a2fb4ad3def9190a5cd0558c646d96efe0c881917b1180f90fd84438d11907cdd4c22b86850c79e2a236c8c0216b9a4645f7ec28f96e06b172cdc515e2ec0af7b26c4ebf75f272c33ff4640f02abd2ab96d38b1ba1f2bd267242eaaaadfc4d9411c6580b4d643179bff2c7c98d0bfef8efd42d5b6988b47b72a3154ebc8481da81ae6216f952638ba228ee94d328cea8fd8d07df50f7dc7a727b265e64229a4ff59af856b9896710a06fab02b665689529a4769ce69c8c8072467aed9cdfb272a0c8dfbeeda42455f647064641d4b8ee103289a11991362772156dc300d15fa32eef46e830cb9582200f10dfd59ddf8ac7d8bb0eedfc1d7e47cb8b986cf55cd9d2197ffa1e9caec51669d165a578ece981fe1a5c0b1ed37c729d7b31c973fa9f7df4de9e16327aabf43730deefb4f00d0693f5c7fb011fa47293fcd52448fb61edf3d7f9cdebba7ea2e3a7e81be110e8274f0c3d5ba8e49d706962a8674dbf89ed69e76f9863ba653cdce9e79f0c34aeccf77337d03855d4722449989846c25e5dca75893daf28150e0cf53b1c67826f346dd6b379d7cdbb72a6615e1e5a5a59bbd2b16c1d9e8cde78e15a049ec77359ec726b9ad7546a24396447c68992784caf768073b399985761a9c952afce0f7afa9a7bbd2a0ad8ef4c3455a0963ebe70634447c6d2f2559ff0b513430e28c33ac005054e19054b860557fb427d1c979220fcd86a4f9afb1fa354c2547ab0048255cef10d634c556b0e0b6cc51f809e96f62909eef541af185008843572aff984b6f2869f62f5be5972292ca84f4dcc9e4093d512cbda633f710ce61da8c59dc78f11aea51255f54f72af642406ef4a37065a21170a2120811dfa45c08b54f391fd5d7659ff930028720fba8a4465507335cd2b5b0ded881eb44cc8e0e358b7eaf08b0da7960366e872c5c7ae9ea5a14d378b64c4f02aef668b0147e78533ec6f56710d1b2d9677037289c0c322eca7a6c9154e5389cf22b4558582c2fc99070bd782a3c3ab1bb0971c8acf60b7a75a7e6fd4f505d63b4483b870d536c80f054262e361da0e619631069beda10a1494ebfea3f53d598173839dae62232f2789a934a0152e92156fb17290daae866ff5acdeb7ba172ccae68da3ef0cd07df4a65384297b6458ea58cf120f47d64a31e013c96411a5ea84b0463e09c4cdfd6d35189405089e6eb4738f580cc5c93c5a108d7ac3249e128ce3552befce88aff8c789ef80bfd701194868724772135e0db325a5ebdffd6aeeffaa36a2905c250158c6bafa3debb24db98612b6f09d958aeaa1698c0003cd9e934f451b246e9466fe4debd95bc3ebf6bbfe2adb6eeef982a3b695652f4b7eb062c70a3aa088e805ee7ced1c832b93bf15523a879ad6c37fa56637d895df2173cbdd1890317fed7a3ed60200ae868fa3697f2d6c7fcfde48477c7f198a5ead0a5619ec061a30ceb2dcb7dcf3ebab55396d082a0bd9ead908c20bf0e3742864bab6fe3f035c70b0beefbf2d5a0bd02d99bcbd72406c1c1ac280c0460cb28bf503fb10adce826629b20c86ee968a567c557f7372f18007d327592672a5e60ab2163d83c73d391e9a76756ffb3473000a1a1ccb3c89ff52e040038f9bce13aa30a4c2fe5b43b174a86950ab98727074132e6c787201d5f17c4588d0f7b1dd61e36f15ec3b36485b3d62f032032ab9a2396d171672411e1cce996a8e27730dbfb76fe427dd13b2f3dda73584db3fc9cc917ec7db0ee180f3930f425bd70516bb04f2aa9152fec217076aa89fe5c051df138e33ff1bceb3bb7e75ef9e3033808dd373196372ca8a80fc825ebaed980314f3cf0979729e59912211740a726f0876048b6cfc377f35152ebf1b03afa6c461b42767fd724f960305ff8a63288c3eb995ee725c215b8fe72723557e689f702e47bbe3062eac55b876102dfe56ecab77df3316a4b5f03961c21cddb5735a3159c8b291122940be022aa1e49adf2f98215a21356a5e4001ddb8912791028d8300739c6af84dfb3f9888daaf38724f08a33f39ac35b129ad1afe981d06e46b7897030dc8a4727a61f3f94df57e95563bfc95901d43eaa545c346a083e35469deaf7c6d4f6172f143f1f767256e051452f8cab80613d12cc68d008d08edfa2bcd0e13b18eda72a76c0fc639d8602efe9d82d384ce8f6ac40f2e01faba718d767aa08cd00ba872b004cadf8379a576e0c3f78eb96f0be70c90989b8421fa18e236e6d1121eed72eef16f2abf2aea37cb85d82d40ef5f653e7f138d3ff56b288cc657cefb8fef53c83a911314f463f6318dbb16e2a2568b81328b46759a7b4d898f0404780fac467367e6cf4d9816e28685b194c93a5e043317655dbb527fded7d0c3b8a800f2075de1fb5bea4314abe36e8d846557ef2c16e6e3c3d72318c55ee99f637476af726487091589f7312939e2545cb9b31b801d25237dcbe8bda2db4c85e98ed0c56917aa017d5e5efcd4dd31cd529b98f9aea74dacd478534c525bd2a7130036f32b6fc84995f2836e91f0531656536b5f3bb9eb84ef779ad07016f13a730ae12a720923a6dbf9c9614a85b5ff13bce6ff2aa7e4653fbfbb0fbc84c1ce7383570b034525e3484c9de92a83da7064e86d7f4284edde1e625f88ff9be73b9fde718c2bc104d6242b514c0438ec0ed4cbc64c9c7b45ba0bba1db4a44f385a17d4618372348bef1c3f5bab3dce9599ebba662dda3b7c9e436c3ff385808c5f6394708429c1ef6431bcd461a078925fae842ad83f586e13cedff1df35b7f44b41b01c517251df026e2790a0d8bdb544746ff01a19de23675554fc6c43809067794b42342377408058686e7e76a091d611687203ed6f3222644d582b3f1b9fcf8331a3f05796a87a96ab9ba3d164b74d798dd57b43b69a63faaa97b46a073001bc8be94e728737ae7f55afb0d26c603c7514c72ca2f83702c07960a6f206b99a8874c84d7264bfc368453d459536356d9cdddd985b6a8fe2500267a656755d47a5d723075ba7309f3c7deb67a7d2ba679ee0f49f43dac054a40e0223a22587cc82dac2c8299e91f8fc3442b134330de232f58d2abf9394efb6ecc42189a074a8cb7c9c6e7269db1f87122b331d451521b797c1062914895ac3df991031318207335833a972c33b6a24b7a52e6387648c137c6cc52a8855e285ab735ca3b95e078a02c34c72b9fdf4d39782230fb50e69a0e08d1d0d435e75f5d1d21888d23da285c9cc3d72627651e3a5bc6ee8dec3be5d7e5dbe6c257b0772e0cfb77c90293ba9232fb949ebc4271cf1963beee2e02016535cecac133db7a032433b289d7b17e37eb11572c80573178d32143168c687c38d0f516c3830421c135171d36369463ed5df5a4b9c1ae1cb092e68fa3a112bfed81cde0ce5e970ef39562106345ef1a000ca8b729226648aadb26631d287c649d8bc63f74453139e66947d786e7ba3de2e532272702b6e30efcce7d4c86fa7295cad9ea41a01d95f56ca9fbfaf8be30e95faed72f7ebad64d98468ceb399c7f0baf6487393a666fbdd2e1a10fd2f104894bdaf439a2c33cc50af1f8f0c00e5b43e15f3aa9ef6aff808948ec64e42ab0503edbb3e8b0877478d783fda52d874b2c8ba86b04c98f29cb8076e981387a8ccf8ea65727c9b9f3319c829c3b45215b4e3b35192606e2664a5ad665882913182ba783820bfc40e9253fa1bd187e97d9740b54b6564cb2bb7c6cb2436e6b0aaa171781a7296c6649f4adace1f810e2935d17a43d4efdd9b4fd82dc3fba0b92fae744ec87283ded423b8d46c1f774f0e9d17d40cd6ba8d68f0cf76596bb190a5b27cb9e772bed019a4f8b7775bcad9285b17301dddbd80590e8e8718a05fce1860528508728253b63c60570e2e6b378094934ce66d21f62bdd5c9ce9b770b560cffff4f140196ac393bdd5ee2a5b04a6c9abc360518f39992fc15cfa453066e27ab1cc637293b6209c114dbae260b79254d77102a0147a390755c588d9d0ee04d857351736773a83ea836001d48cb8ada8d460442d83f02a457cf4b83d2875685956bb8b393d65262894f2733ceb1beb3b66db78ccda0788a0c09654dd1630b4f4c42b1d0d141d36346a0c623ecaeb7aa345f30b994dc837241782a10843de377a445901722994487ccb6f2d792cfc2e160091a5116cef8ec1de535049d7a34d8a028f3a630ce275e0107e3998546fe4f02313564818c0e80dccd4575a7b0836ac77c46c728f1c94c7bbf599832d027f260f743a347da9185b922ff285e1df1dd55f081972bee53e04102804ac35678f73419d7a1831b5cb330c9a790715407f690333664bc07aeb985fd4b64ad5be1f297b2958393c1f999d287837e02341573f20ba0572e770d7676dd1d7f901bc730be7a519c420d2111aa550ee4267cd08905bdc2172f36c664834eec8547fa78b423a89b3e0e155911ba500a397469297fa9fd9d0727800fabf3e934739a9efa7c6f7253b5d632380dac3db7abde871fbc682dcdd3a4fc0e30dd4edcc762b39b65f145259a78a121798df5d45efbc80eae3d8180322e5d523d99e25ad1cf36f88babdaf37e49122f1e619dce9eb1479f3baa986cc72b2868bd27ea394f13ba70f38626d6e9c7fb173737b6ca106645cf8383d3d5c72b654f454f59c3d82bcc7bbad1faa9869b46a9229da9c573f22cbb7ace9052228c5e034c04791ace687701e95de9fa808886e69dfe4361a83f51ce8d9d675246a0c5bdbad580be550f9cee6f8dcae1105d703275160340d617b613674710c7272cb3ed19d4850ef7e0348bd1448966887ccc703083d5cfe86c60102266b073911751596e16e8cc83f888aad015208dd75f3e7dc52d48f1f419d8f1cdf405937087d6d9a294ba9d54cec8f0ca692f3cd5b9a73abea90d8398bd6d4766f77e63247c70216995c0395141ab901e0e0f60e350b130e8292c0a19b752d3bf923de8e70b4941545c58b7e7a8fa30c3ca2c95a87f15dabb15232a1402eba16d9c70b3a723160b3b4be07c3d6eeda65b9746149f33733c374454f67ce9131dcdf460e6f71326a75644cb8a003f5a5171d540aec3ee0c6f9ac01bf77d77517e7c54e5b1c72f8556ce2473a126b12a9c32a1f790cf59ba4101129c042f4a71054cc97589e080034622dc0713447ac576623cd67e18114f7e52dbff1bbda5d514a46ccb14f7205209a825f23bbf632803f68a92762b4e398c3fb4bc14e1f30cf0e838f6421524ad63e89fff8695169453b85406e4130f78e7bf68fb808c32734e6dfc30d6b40dbfd4b9c778db88ab5d50a203b61a5bf56d861b781737a4f5dbeb43e604a333a16f859ae25c5dc2f0ceb53d61b17695da98793fc256aea98f0461413797a3164bf5416103601c8102a35a0bc61eb2412bb1eea33be607b31207dd0e5e213007228be6ba6ee08b2f0c20aaf98cc0a0d3d2f08816bff58eace8920c4e9e5ee9d723bcd84cbd758456e16b679741a91ba2290dede6bc9b1b92550ce8da5bdf7c9724fcf5d670ab69c0d6798065b9e6ccf69afacb16ea538f80ac6ca66f48e8fbc72cc844328dd9414d3ecf9f8f464cac28e1276b652b50678f90669da43c3914f72b10f0ca00b34b4c3aa7024c2600621a630abb74d22fab8ecd3b9b66fbf639672a5fdcb5a3ead79cf9e88208ec148ece69aaf050f3d7722eaef256bc453fe2a1b09601656d1105b71801de3784512ddc373f2edb38d4c98378372f92ad8db8a5afabd3e93e77c00c9c2a6a27938ccb9107e0e7bb4a924ad25beccd1d276c234726649a19a17f5757b35059d19e574c5fd22c20f1bd0a9d5210d3b92e4cc03394d324ce00c079ede77528457b33db293760f9e4feb88059dc02eb5c7a4e9bfd34cba752e859327a0f1bbe119228fd354a728d2667001719b6fd40d4414a1c1eb72fe7264d60d933b455db0ad101ca92c32baba9bd7cca2e93d06f95c1a6e9eee57ef6ff5735af130a970bc0932f759b6c4f62ae72721b06c0391922d9772450072d17a9a7a4f6458da0ac2d47b536807458d121c7f8c60f1dd8a27b7d56004fb72521035837f85c90bfe383d76461ec658e8092519ff9cd5e2b90d1051a1bc3672352537a6f0847a5b05ce38c93fcf306182b92ff8a5d3872bc7ffa50eef073072ae298a607da5577961300bfd406c2f963ede3445b3db745eaa2a8e8a438bde727d0cfde6e2030d225158d1e132b887594c4ce31f377d0ae5f132eb7b700a77724c0edec90f6c0b2eaa11996ab0ec027252049b49a59967e66374a83d151ba67272cb404ed6409409f6c800ee3344a37274c992779688df39c17025d747509535d278dea78385f0f2c1d83b807a21a4777dbe6c3721b91c7dd3be77122187570b9502ae2fb7c99c240e9730c65450517309ad20e3c6c8f5765d2f88bbd43428492cb739a7190d552eedcb1a445bf6562b9e836b294b34e38eea1e22e1b768e22b48f183501c596c20261cb194acee4ccbb32bb764f12598fe9332b7c699be2d426558c8ec6681f8ee096938fde288d184ffa3117db13995bcfa5f78054552757231ca3b96ec12f5b282f0c3026381e3e3d810c35fb70b42eabbd789ee5048ae72234ed147947cb6c9785c8d5fa3623330761c108d64fa1f9154deb0f5d053c41af7589718bda77ffb999b6ab75ca01ed0aec87182003e7a54ccaa1ee4a5e29872fb059b03ddeaf88140ee7499f36bcdc5472d038b8e882aba1dd6eabc1949ea516652e7fffd314215adf1cc2f85e64d7064526cb7853d0d74b0774ada8102ca39420a5c4ede70d05aa514fc4ef5b5135849bfd4871093f7dea1cbe3a45e82be7296da6a4659918e8a67b10b6836d3d590e732c23696b73ec652dea7a58122087237ffddb557d9c0c87392500e5bf27499e3c1d35d2e1861a794eee9e54c74316dacaaef28fce58390428aa4220be0c2e6bd2aa646ef50b8e0c8fbb4b3bdcc374f9961603659c6a1900ed37b2aa76d3da1ea61a3053003fd65af21b12d2f209372175c3b6cd25fb06b306c8f9a744dbf906b634cf0399d9cb1ee744636872b201ba33ea33705d828d40d7df046fb342dbd7bf524d754112fe06b79ec0f35523e3811f6a5cf3e1de9f95f906132bc475feb1a8ad8341e59bfd5a1011c9b80024e248586efdf81d428c012a7f97bf69a902f5eb50bf66e8e135d5c2100cdd6e6943acc529619dadb33e35c2dcf17e178aa3491a7df118e3d22ec3acf95f5fd38d272e373d8b246727049f16b42433f35756ab6d2c126890c1c6e81a69133a77d7f6d5c9e4a89a3a27aeeb8625092cdeae082f4e8040d1adb26c29c368958c60aff72c5d14b4a38f7206d5f362c75ff70ff54f76a21ec6cd6c81303e9914a9c58c57239be545690f56e4d03f62d6ac5272370fea9eb2f68d70bec30b29ce4cc243542aeab20fed76d1a3a01d51d5cf35f8d074a47297695d67fc3644fdc84591b03722381837aaa784f07f7d6801581e528899e3c3d4eb1d295e7e487b85338a36857c3780023bc42a3e362d763f2b200d1e18d49e68ae052a2d35bbcf2eb8f5545680f632044287c64d0f4fabe9215a31841388d935bb607db9be69834e9ee920872804e4375a0682d457f9733219326b7a9202fa4f816035e3eb9e031fa66638f723b64d36f0aa385d309027532e2bf65fcebc9286d0d819afd5b3647b1779cf4721f79b906e27098572b0b80841a9c357e33c2e5c92a5e5f1cb2cf6020d2086e5ad6b7eeb2ae351dd91fcc88dd9ea0b6c02e7e2b85c533589dd1b427c4a5eead3077255cfe9b21785701a21efb916a8bd6c38a1ea63defeab9a925f6509d38ae349490af92bbbb9e201af52724f13381b6e6958e5f5901ac1c1edc722d82d5a81a76e2d0324082ced2f3a8541cfd23ea5b257bdf39593076324450cab106e444588b7cdda7cd74bf82903e64cc61aa1f79a94ac0d6f8572a37bd6c1eabcf70ca728fdb8aa32ab8b73ef44498ba2e7c29e9e3c76c5dd94307bd24f6156a3665a758cff806240bbfcec2b0f66d75ca2bc248154bfc494cc6f4c3e8e6618aabaa4a720313a9f294001b6a97db3475c50d42721090c88553fa38974190026250e616723ed79603eb9816bc3b5241ef5ebf022fdd90fb937709656512a6b0012e49dc47d89ee4ac4b010d2bd2f74681bc0f996e0340bb835e200863578fcad4f7eae97251bb48fe26a6494eecae65143bb8a2d0244e131392e555ce3abb7658cfab3e723d46feada636ebf1005a13a55a7d4e60a3bc2b721a13b9723090ec648e20632d477da61108a2175b578266b4f85e6ba924e11f18900a7bdb65275e4ab7177172fce6fdc22e5efcda2f683a9be6427eef1a3a83a4a88e4c546d8e41b32bdc5b3a8c5eecaab75441166c2bd9799a8a1f7a9b1a921f5eb8d596f2255ee522078c13c0bf75351f3b25309f650b6b029e6d9f04505810c01afd069824370ae7933872cae1d9295ee99ff43c8b5be001a3f7a9d65f6c8d5ee7c81659cd5d1ee6561f72d1d00409d16a2d0fd1110d2e0ed4c0b45143b22cbf5381bf4048b96e32efab1080922004b537eb453e4923efb424212f0dcf257102fb309454b37ef3df75be72234fc92108eaf6d0317d6717b4d05bad10c9252212ecd31fc19323e93079c4726a2617656d6cf17b67ccee8f796f7fc3f2a888d081093d5d9a71814355221e7289945a66475d435bd3ebc2324d05bb0ab456b208469f4ec71c785c8b52612a7298d6b637f4a4024525927ed9740c5faf000f4b2de72c34f9ffe580b7345e21058769f51af8210dee6951bc68d4cf32b03d40da8ff319e963fc0385a4f209963187f7f485ae18f3641815ca250e3785b1c1add593a815baf417d50b21ba136e7268a8359b1c97d21e990d32eae4fa570d5ee020600dacc526b60307429030fd39a7a9a5a86fcfea913c2a191cfb6e371c759c0dd02a3979e43fbc4161bcaf627298b5a19bb0e82deb81d6a3a4a7aceec96068bf9276f65b484a1208bcee94e65e1dcedde033acbef45ec5220670e10b71ffb3f9a36b0d82fdcb82131353c2ad7271da7f1f36ba4d27eaa75f81bc9b2587ef2de7c639496b739a0f0294ed22547223a30a1ee90430f5ff3413e9f7e1d21c714d025256b37a2431543e7492064e5fa1fc69840536bc4748bc8b2431808c95c7c86357e7bbefe08b7133fc62c14672286fa7b8637088af58bad8b9b7236260c56deefca88c3298f7a36d14f6b4ee04adb719e4226b4d81dd25c92677952ebba41df92f5c4a81c5a679914561ab52720308709759e479eaad8383d9e7fbebcc9ee60f9b8fffa70342dcd8e3ab1c5822f0cf6d7cff7c181fd5756aa3b8bcb0d88a2cf351ba810d046ea8401e59940c72c44de1338654dfab680ac61d213f0a7da65803fe2074475d69bada799fdc926962187d34633ca6e1baf45ba056b4ddd9592bb0dca993c92cc8424269fdc15e547d864284c4b06ccce492871a2a7a6d716a3fc0f29d06e1f610bc08f6e65b90728813ab5901cf7be3e1cf9b8b3e0e3069b4d2810dbfdc97a27540cff9d655a372253f791f6e7ddf26907c48fdc2495c1f2c03363c0513707addde520866325b0038eee287fd2190062ef27fb1e1a79da05c6ff14db668376326a5988cf644227292a61e63e96c759419496458cb9d38ce936ff371c5883e56ed4a138a91a6ef720b7e6a4fef99ad6410fcefd0f48d28d99291325da21bea200623b87bfc933d728ab1d1dd399e9f6b0263637107efe67854b67e3435440c1eec307b380c50a97278c36e35e21b2994e3c508baceaf0fd5274ec605dbc695e849a90e3e685ae92f6ba2a50d3ef39d84b40d1aa5e33d5dfda9b0cbb7efc85178211a78c265ede30ef253f6dece47ac74e4ec95e74e53f8a368c0f4d5c247cb20ff61b6cadbff32724b860561fc7f54d1120aa9b7d75cbbede73791fe01dcd512a86195659bffec723a2b5cf84446b6be0cb23198c9dff6f186546966ae7fab71a29e7a9526a42b7279a81904fa7df37f357de035fbd3f7430b3a1b3f4815c4ceaa5b718b7b58491880288f2840b5ccae4d874e24fbea971c92994675af628ecccf7559711fc691478e950f6e85481da46a7d489bff43e4ec13582c9c673688c0816a3e05969619724f60667b4ae2119976b94036d23f036eac6ed382d620e30b1a94e99847359e727d0dc4478816568343ebd61a1ba5a70944b74c636f21a6ba9d06333586c2fd72002b0995df2c34ec60b4f3f9924b74ebf15735cc5b4ffe12bcbcd50bf0bcc2724e521f1e71ab1f505f1ab3a05ee4246e12b0221380a808af971350c188010e3f1b22ed21cd5374867acbcf57e13657510b158b2dba47464b30744eceaf4b7a72e874d02937f897d3f4e4a4dd80b24acb995a94e1464e2ca248c30b12fded1940a0be4b96bf5887fc485fc476c846de7fa77b8fa721b68176341b7b253805ca57b9f1ebea3bedf742fa06c6d7d5386e2e2bd2e064eef705240ca219ec8f774e45b140e3e22fd561a9163561962747b030ed7f4eb21c46095f1015109d8d7b2172ebc4131f9bfcc04bf8de5d4fae586c1bc8a884d722258da9e53c3249c20fe81a3ac0fab99fee47c3977b5106d1de70431f4f1474d50d441ecaa89a6da5b3fa7246f5b1369ed27a1a67e3a8b39c4243112e167fe8625366edd8ca2883f7b3521a6f8743dd2ff79b54c2a4e455012c1f062b259c0b360602a91579c03f67966672091b7aa60f7dd7f148826e1f556b5d10475635b2456d4dbe2f1aa55b259e5c0e4326a3abf8948be7a82a1e914a67972d508f3d524153b0395cfaebda6346916c671dfdaede1714a7051359f27fa9ed396f8a5c800b111628c6794b697b8f6d72fc19d5f413e529c91b81c04af5f9d32185ed5690eb65141a46f05a8516fc6d15a478e146dea5adca36b933761a0616a4c68c3d9bbff9ea2f8a515b0200fb23727bcbdfe265cb16b94787b98c0a79205afba4ffb05bed41f74286d60810ee7a4821d949c27b3c51aebe4f08103992ef53c05b466fedd486ab25bbd3d728f5ca714732acf84cc0b75139bd44044fbe748a8cffdbcc933b7b44ceeea7f708f78e72d0cc760f37390bd16325ea805d867905a1e86fe2b2f783691c3d6d6990d56d72403890092e950ca7972ba764174f3f7900da1cf6a31517275158155622cddd72388d0406fa9ec92e5c72543115294bdd4be904f3754c1ff2c3a123be1bf513083690639f5364a2a22be4bc68a80eb5b260b48061797274fa91f0e35bf301f25edb97c3053c3a25643051dd629772741672f0e66995e8177ce156509a7e54c2724ee4d2de574db9d769aa2a301ec40a183265678b21297ff56c7d95e21b28785f4785252bc24bad14588fc90d43887b29f2dbdd35baa50cc7fbe95a4416995d724cf920198714baf4e47cf4b9a3af9280dc8c56e17fd95820385d3ab68da6c4120c203314c0a5317d2a6e8dd1607dac3a79282cc7d3d7c8363406670f0e0f7e728a62b7bb0a7be1097b98ab4eb9a64d058c153abbc3b55645717995f056aa6472b77f17466c6e3c6a24e99443112aa0c42fe2de8a0e103dbe2221058e61316472a6d427d4fce51f94e200f5906fb3be3b46e74dfd9eeeb6ba2a412bd47a1ff2724d9fc67bde41d712694f54019a7e45a4537d5b94090f2319bf955b3412e83e15e9484ee5ca7fb09626ee6f1cd489e51f682cc2ca98287bc607bfb96db1575372f410b2bbf3b2459bcdefd9ccb4f69428671ee7e0629c55b12e341ff72f3cf8587df28b7a43c636c2d84d198b28434003d70313d578ae3861957d834d6d21bb726f96046df161137b37e142181f8c1a72df682a997946d6d57e1c3fd7d6758472f0561b9efe9659f74d8d81013b5329bd123e78b54c9716cf2abb74c3161cf71fef11636f0dfa2d8bc5dba4a2b1447c861215cb7d5bbf81eb4079553b72b24d7263cf4b58462e4434cd3941173bf8b07bff3bf66789e92be896c36c3118423172f4be343dbccea0d4609d39aabfbedb8b76e203fb6b3acaabab6655a934133672896fe1c4123072852ad04862295d9bd286448dccf12c0e3713fd5af1a42c2c72874a8a61e574558a55506abf749fdb8e273e356d6a6645abcb6acd164654784271ebf5c2abdead76767b31512c0e73b6d5699fcb054338388f7203f77aa79d725f2657593aaf58b0cb57ab54d959f17720837f6d94e21771daa593cd16a68e6d0f2b72b265d18005b40aca5a6c5a8c09b01d6396f81afab5aeb3520f6ab00341d0d6e43126073cd6964920c3470f6c9b418815abf0b628f0f9a0d93d4d9776223ff493150149ccd5da5f4e6b934d5271eb4cb0804791482c939795b93be909727a414ed520ef7bbb34d202c0e2a1bf8e7a57df13b06004afe18f08facd461b06671f6e181523110b9f4f85238d399aaa2b5dd26481e9584c03654a092f68f64debbaad4481ad1edb8da56bd8e6ca3802ba623d83dbe623f6cfba7054b69ca332bd18841fda4b075ecbba7a36cf1652903f39881a38413a1d23b82b3ab24cf372c5a0a084dc86cbb2f814fd7981b620cbb61757a0720b853d0ae71efba3196f7286437a5b30aa763db9b6e55c10bfe8f8307bb51ba976eed14bbd3c0a1bea81354d76178792b16f32bb7f86a605db8178f19a463c5b634029261ceb63b355af724a3fb11a04222458a79392796f205ced45991294ee65094f63de5a3ad8ba926d56590170c539a4f9e37c7083a83984c17be359c1316addbcc87ac1c849f01b72ab61859f5be1f4ede9a8e3c0ccca38e73b2b777b0984b0b1c5c2ea39d31ea427002c1b678202beac7ef7ada88d122246733d92b8dba31e600dcd540eb5fb174ca70a9af63d2542ed6fb029e7b2eec3244a3cd4261c01ab0e4d0e7d9f30d8ea68dfd96781dce1bfc900e8ff45d469a765fdef82a3fd8a8f14ccd7be6e0da38a6d7cfebe1b31d18198d76fbd39f8e02e37ea42fb596fdf8367df9e5b44e4449504c1e9d8c0caeff159411b830a1cbcc07a49be19cc91a2fec6c07d69cb528b5772523da19a4aad805d94b55562f3409394fa8cc6520e0da94acf57fcb2911b8a72978cc5f78d63983a9fd408e58410f9806dbcf000f6ac4b1dacdace73e409e71d06b1bf3da70ed10cad90134488ade2b6aea67ac86c087a6c6de178d20fda2b0454dea3bef844a34ff0b2630491040dd3df394dfbcd7ed9ceb23efe4159d755723730969c8de61b5427f9fe296ec0893b8f3e15316f1a65efe09e4bb21b943f72698f976f6f747059df05cb5969a216f4213ae763dbe8d63e097551712c70a43e91a0d930bbdd67e9a16bc40753b568f6daf1cd5e7a552c53655ec589873a827216b70c30672562978637c1f216701cf765384f58576c7f5b1241aa27a7b1f97237d25fba8253a0e4ef091aaf1b4af641848c313c39bc4a65e105b3ebda32c0729240b02616ded7c70f37e61eed7975a0082a92d41762e0733732af28a64c25723e57b3972d1514dd231202b324dd28b23511f1a64efd3e40efb6ea222cab8a1ecc1442aa22d99befb8d6aa27a4f9c7b0c8af3d56902ce5a1ed4718f050298c721cc740ab1fc2c73e4d48e31704e95f45c09524d52771d22fad133c6f7886647275cb2b7003324572ac7dd89d55089bcba4d226c1af074f1449f9fe7d4dcc1f72dabfa190a4a67c60f8b74ed85e45489ba0a94020e6ffc942f401d4a5450041727256db11ee2c2ac68faf480f32397f4a24b70ee7f12e7eb4710bb3a2307ec772f78bb73275dcaa6eb449e27c5e0a670f77a66816828b42cb7ae412f9bbe7dc62e9a64f7dbd464d02e454cc4a7b259b82c210722726816e4e8fca87957f75827207b58e4a5929c94709870cc37d9f70a7a0ed5d41ade71bb11618d0a692bf887208d95ab59313a485df0314c9b1180ad6e8ed3757978e048638b745fde768be7257d8cd0a39391fddc913f27608bf20bc30df4d2eab002f514b0f744bdba7be7205d20dd9cdddd449808c173c264fa26aa0c3740d3fe61ba9e661fd6756f8b766eed939e5f276bf0e7859c5adc3c56304df89d47e709aba25c0dc699a89f0bc7219fd0a3776461de3a119175b90c54dde24deb252c9df6a65f9e92e3b9e0b59728d838797473e2c21b0be2509f96949d3261581b8384b7f5866f815cf54b29872b96b9eb803ad777f6beb4266a47b0a40e8b8691bfd8a1fd0c223dd2bb4f775726c1397564866bff64b341c14493ed67feddddb54c700c1c0ddefbb46e2aaa872d182dd612120700c8ce9b5a17957c231b40669276ea19cfcb71503eb2740007273dae41abd6a4d6c6c7b2f748a682456475f72656453c2e7222cc2b1a9f4f472814cd6e849ee9310e3a30a8d77844a89584e0de7636c2ba74a353624641da35be3198e8f0a79cdb6b7ac7c9dba4f34629d2fd33f0bd0e57ecf02f79458088d347f90d66793f007306f2b7a80a555c516c3d3373a59646fba1e9d95dbf4bc770e345cb06dd7f19eff6fb65b8bdf59a4df6a936b3f18467f81fe096d885cd84d72eaa3ae261cab67334b4852e6201b041ef04827dcd9e965ab1790a3a8afc1564f15de9a538a069694612fd1f8d90aede6de58402f1b459d5fc9bee11c3ba5881580802b283c89e529a16ab859d22f34540e6278a499360b7ab6d5f27af941a372a8ec70f240111900727581e1882da5c111d62eca815aec656c02c993ec1a12147830f86aca505a6f5942d7bcc6a899c8e58f10858884bdba2a3bbfceb4736c72446fa96b20883ecc7b03356d3cb068fc864050e00469eab3fb7b50adc4fcb97291c836c59146091b0b1844dddbc3111b0f47311b968839d5f402c3ca1eab5750415ebb5300a8102a1389d54139ee0e3534e66659ae4bcc3230d7b365c4374d66132990b8f26708a17ec25495655cebac6c0e039d2e39ec41c0ae74d7e3f19d72e132f80c8ad3a965df9e15e1c7b138e9a36246b40b6efef88248054565b13a72fc27baef0c946d8249c82a276fdcc257ef26171d18dd006b5499dcabcb47045d2cfcf84a0611593bbbfbae8f49b3ae62ae92d43a595d4d4b5f14b71e3718ce72adc41a572ba64f4f9aa0185687c1d17db9e8cb3fdc6271823ebd55278204472c0c638d86657e7622f7453c48c3b197bca1fc068d1756e21c944cc27ab903ad7289ba0d310a0edf516d2081cc263401e57caf570be2ae3910ed6fbefd680a6a72b7270b105ea379b5b974d481cff71a2b9b28835518f745a39e7d2ef92af3d672c876b11ae94bcb3e474cc0d655e5691a11be8e065830f80673ef483c8d74aa1a5ac6c2a656d8f444f1cf9ee79939c230b1e15a366536b8a3b3cd69bb116b962c20ad2c69254cbedd073a0100f208ad0408359d8084168526e8584de253dd4f72e0e7896223b0921835c10c57e0afa82298429b6dc44d20f5e8a4f8c217e32a43fa928ef69ee96e9174015be6be7ff660059746b4bee1deaa19700477eec7930ffb79cef75ff1e51e0a45152120f3672210a28b33741f0210d2b57c3164d83872e821d59efd378c41a5cfe598b8374e662b4e09af9996f975d6758e28b0f5d4724f93a30dafc521106ae7403147705f90695008a93ef3802e882851c73bf44d72ab258c0443f111d4cfda89c36a0a2def50b0b860dfd2c0eb9fa1cfc5977e1264062dbfd4d5d1d66365dc382475dc3c2ef82ade6791502b46fff44af866c768438b1d230cc3bd23868418f649e9df7ee0ef2f9223d690f7e01f864ee0d5e290720fc3d6fe9f3f0df40c79aa889f265ff7d33a006a3c42b270d3af7259de5190343ab358f09856bf9c184683020d14c7b4ee750a2cd02479c83bc88d618f717d342f090863cf205077578871aa1f6b2489202113613d8c2ad6df1f04ee23e8c903dc236a3824b25c92822bd20c42a41242645ae661d18e8106efe702e8562e20541155f8e66dea6cd95a013c91e1058d361f21152183d480179372a5b8ebbc6a72411c69c242fc2d84ccc81925511a2070071cb0477e5ede77fd8c9475b1e9f77263602e0200c2f6004b4c10c1e61c943dad02e5c155cfc27c4df82ab48d64b42bd28ba897f291b0e9123d3fcebf052487d0a37af9936bec2795580e8dcb4e8a724337d1c9e1231635e70005b6bfca7b880fb69e8a4cb731e3c3778209ca31fa6ffdc80d0024454b37f8d5cecfd6b4d430f55ba466c213c033e4e3d6b46c197172b6feb612cb4cfef9cec2251a37a97d319033dd26da471f97141fcbba2b353172f4a7be5f36dac5ee08a85df87bdb673d42cd998251086d2a0d996d5251e35c4974353853b1920c661fdc3e2502b733e3366971571d5fb20c93f3466416b0665fbac84fc54d454040047a16e04e955ef1b6c651ae38e9d11976d1e22725d37e68cee9c6e461f9fda36c94653f2cdb0dfe3666008affbe79dba8f1c84173de5a72ecb2740e1099b25d5c4fc94fd08cc8c73dc596b38ea0f86f963f83436a4ef1726667866af65eae956f4e70b7c1a75d8227a8b7d48054fc582f07688f35b5f42adbfab670a2b16d62db99c1e9187d580f9854044d9159fb8681cf7c7a6c6219725c31b99c4f988f8b96257ed3aa4d1e845a1dd4db309dea1dd97781adf6319372cc71c5d13d1bf3e39291de8d1c1b3df5c596edeab26911197496ddd15be8b1723faf36ce770d234124eb3706dc80b40829298cad96677e09ad492f82646c5d72327ac394ce1650f127372e59b62b3d38c3bc1b345cd92facb6430081b7340572aac269b343d24709ae2ec4eb10dd3f70bc5c8995421c1a24d9489ecfc5a28c7269dbe1b86eb4cc4122748dba07b710ac8b42026ab4ab1093f1bd6709d673610978780513f0753a0d32169567ef44d2e2cb9bbe08864fcde89fb1d30e8b6e493dd7bc8995ee2f223f40b61f50935bf1c580a29cc6db36fd90fd6ffafca8dbec72198112a7f80ede0e94a471a724e562f0fca33c67943c2c1d35196da01c1cf272c25c8f5b5c6727438b63d2c186cd462c34525e1c3ae0566403607d117dfba063f864f2285a2aadaf8bc81662c3cbff4df8a8ef44b336db893b82450e50d7bd45fb0a2d844ca234fad4a75589bdee622f2cfce1d5eb49deedc0a840319421cd72137fc3dd46fa3866480117d93f1a1c00b256be24e0c53d71f21c11d98f463a72476f14731dc568a11f31804522b0f34df770755ed3641750550b13bc8f8ab65e5ca8560084c1ffc537421931b0b0978578630add6336d46e86bd8f48638b2972981be805cc6c67550cbdb4939bf476851565319e1edd502ff36293b00ea74472d22ab21517dfebc4f98faffd4fe16d4d9433331d50f53f44a09ef25da9144c72260596779a16163cb7b1e86ef2e6009532f8932ea311fca619fad2f1bf76b57277b87b63c919668022262970969bb19a6eb3ae4679b5e38f4d9137ff77136b0d7aed105689cd2db4d201aa43b5721769dc82478386aa9971ca9f3d89d0bad80adc3a5e8e53de67a9ccc20cd1b1f0c6b670918eff4c629a570bb82f793175814d268e3149a212e8c33d6b0c8f98565fc39b1d085f7c935bca2edfc76aa0386e72e9e3a140a33047115f3513554f84b352c6874d690e5193d4c2808c678e6aeb72543300458bde2dcd6078074bb5fd47be1c6f4691f404748ad8157067cb0307532e7fff39e4d5c3a772e6d6f9c6321236d9b9edf43362faae9846593df9797372d8c03c7472c4161c15708ce513ac52760ae5d0b9c6168e17266ffdf21b304d47e33f79391843c9dea36462d341b2a47a81fe3507bd678d850c58abdfb4c7a62da5ed33b3710121131322f9e4c78de768b1d85af1b1a9f19bd1b641bf06fa6772b93aeef64f7b738babd897682ab622bfc48c5073496ec16f416b996e300b89491af992439b32f0bc0cad250528b3fead9361e0b7588b2eba12b5f3096646a872f0fdf17034d92fcf1ec598e06fc58d882101ce2ada90bbc6343e602d3d63eb721473d1e693d651e8e7790091d7020e72584ff2ffea3a0267c7da0de55e955f5eb34e6828d2c01654aa9c967d864bb15b0c1e1b6897afe3fc77269079393694336f116058ffe764edbc18d535dea23df67bb244d1b762b70bd13155b13bbe98414f9c346ff95071c5b5c998c93cb6af66dbfd0bb61cf492b4d23a8d6d6666c072ccf28a2208e26d3237e9ee5ecbfc6b66dd100a7286aa11b902703c365c666f68b26711f8f22222cb0aa6bf82438bc838f9ff45c70372a95364bde2dc340bc101a0a9b81083373ec8568209045194299723dc5f1313681f889e0748f5d641f072c76adf1a66f479614000f2dc328f605640db2f76995cf7d1df75ccd55df0fd0f54239d6b2030966a48eaed824b54f65dc174f83dd8fd650c4e460f4bf1e4da726388b285c4cf04b1828cd8e222c576d0340958295a9df20c510aa25e4bb9a572ddd17a6b969ff4643c7ccd3fc453fa0afcff187cab42f52710386f2a3e432467a13db7ee760e576da37d95db11660f6e76f04d59ff59c7421377c9165611b808115ae0d6b33ae05e69062d7b228e4e2780394f94d41dbbb75e72a3b57dd3497225ed6d6afccf3b31c4781d227050a1d6ad25cfcd24cbc98b7df26e29d5b4050ff6539667612a5affd7155f23a98432a5639980189071288d6cc4e0d1ffd58a72aecbde298e2a60838d88487828ab7ccab655963a3b81b17e87bed6d6f4400d724777bada4751fe7676f171596cf4b0d82402bcbfa007e5cd39b036b7c15b745d1b83277580d751acb9c697934aca3e58b9dc20f5afe44fd52d2f6ea08f34ec3568ea801f10bafa74bb43980452e96882ee18d0666d3f86876f3176ec4545592d51b5ffb625b82ab08460aa3b5e0dfa54fbed9bae1f61914875c17760aa190468d4feb25f4eb33ecc51977afc6e294035f0ae1d5fd8ac6b5d0179e954ebcca351dadc0f983a2fe22b585d29bb44954fe003e8fec2c63769e0dd44218b7d365c7293f6deddd78fdfb5ed2b000643d4f5f1db7889216b9f20a601f45600ea748672db1e537281342ba8075c000cacf5d3a1ecf66955e31c7f32315657e20685fe04b580573e103be3057293bd9bb327cd548ebbde77e3a1ec05bc0ebc3750ee370588f1dcecc8f60009c85f701ebead9a6256dda82d1810034c69ea7c2e660511720df8dfc2ebb9c9b5437fbd2aded9a3f15ac1a0331c98377ed960930deee250724a4110d93aa38efef5e9804b46c733929644a851d834b56d40d3581d35aa01728f51e7b097ba0eccf1784347848858b8a84986bfe893df232d1e51b44e191572e558d4efa41d35dcca5c52aec971c75beae1faf8c94196cf45c2f5cdd4c7461e04342bd7eb625a422f4551101f8e2591319ca86e40031d4faf71d2b396006f44d938e401813fc7af9f39e8cda3b433b6a2542d63449aba0ecec1a18f8fcde872dc8cf594e2b559e25dcd4d481cf4753cc97a0d8a0761a84d441fd6ebe34529729445c80d278ac7ccc9b2770b22a0946c917b59737e86c7e283de4cab6204244bd3e3a9dc4ecb45adb2241dfdcf773865519f04c08492653160c99100d649cd6b00b1b637ffbb5aa8201331c6d5056ca70b83b9f4b5e40d6fa5c31a1ed0510572f0fc74549ca5ff1d7db7e235fa12c7497620f1cb2a50906b40b79ba73c9eb37232f0f9cc8941d1fe0c767698086d00c76bbc1d28f41d764cd50eeb695207e372bf719f71c86c8ced975ad869810e39ce8beed1d191b427891334792101b54772b2cb90e6845f8c4e5274b6928663c8f812048132e0a21321f03caee55eae1b7245b7a8d5c9317fe9752b42e888ae281d4fbb57d986e389eb754da94aa691de72d9dd05a0b4164d7ce7fdc58288cf697fa445ddd031ecda5af2d028998334c1073c6286cf58ced35e2518425a9c7203546987f8fb3f87a5c665d2caad9ba16672f790a0eb267816a2ea764a33d6787f2cb7e8461f86a0e8508d93392fce7a037295abcaceaa2ebab377567bca23ce819e247c85b91baf16936cb2dfbc3cd86572425ebf811f763bcfd4ada86f52be863715bf013396ffe81113150db7b4f431726503fa5d491f394ca593c1b13e08f9eba5074082c53c32845f4355acbbca2f325a4cb3aef6edb0e34f88006817708cc652960e252599fe69b8b7cf74d821e872e4da6416c5b36d2a88a29a011b9429833f531e5309170a41828ace42044fdd722bd0b3835ea4e11ec69526d2cbd5017f40bd50df1241a7aca12834681b2cd37228421fa3ccb138ab5a4b6fd5ace9911ed353b2abb97e35761b95a2af6a901619e1cc507cfa24f288abbe1add717cd6121e46920b7f286d927f13587848f9cc72dd8bdf7f61769a8f9eb2dbde613e82b4616537e1eb0f9f844b4c451fae4a23720b3dd9f8b0edf8e48276af4f921ef0db5571414ef92450ad8952fc575b953972914a896419a094d6d458af6b367bacfa28eb442ae3d712a84c43bb5db1746d498c09fcacc5ae15899c9a11d7c87d3ee65268e3206d1044d08b27ea5fedaa71217ddcc93070b1c782cb745a3ce67c91095a365147c25f882bdd79c3995b609726e15ceeec40027cb342f1e570922994901d9e3dc577712c9fdfdedc15daaef83a295d426ad2439c24c5deb99361e2862b7d17380fac04726e7adca77a9d0ce90de5f5eb852ce33b4797c22c5f665a020972de275468e8101e897e7ae108e9987234f389c8c271b8e9d8ec6d85f983f3af7b094435ac30af12f275a37421f5a97272241afee4b47db4a7c1d92a9d50ea08a17f0cc0a862d081ccce482fe4129972b26ceb35c8b9c2eb0c97e275f9da15b8eca6f644180275791dde95b0ab3855729ac33fb5288ecec07ebab5004006be9133fc7f80ba02fafb029d729ea54d4c72553f69b1fdedc06c8e49d54a6fe01f4e5dcd00478b7fb74e1ba58ad753f1a764ae350c250caa29347d2c8208b7290d2a56fa8d3745569c212ba03b513172ba7258c44b5a2aaf3674186060e6a1027064f98ca829d9e7b0d762cc20fc4d0f9f356c02c2353fd0c202de9e021f9cdb8b14289462e343d6da0888fdf304c8eed33102c59a3930e6836d179af2d2dff5b72db2221d8156b8b51fa53fbd16628a711a01f46f759207091f8c7b5b36fe91e587761f58531ced2b5b78344e4af726c2020c32fc2ab0b5026a6a00c65e2245554341bb3434b09070e2efa3f73185be9610a41453dd7c12b5b6573461097315f4e29f0a0f57f79fbc6806de8c777cd04a725e3aacef723a9a494167522e6a489fb388fdf92ce23105a3b0d5a1b4f7c98e72a949e12a9646c091ffdcf101b6596893e6205a88967115edd9b3e01d753a7c7205e1eb4b9f38b5164c965c7793dbc33d97dbb3870ed8c7c3b930ca6405e295724f30f0ec4055d70dd93bdbce867770f6b53aab769bc7b45c4db9303f74331e7200fc622de02707bc758d204c34e06f5f7416f1f41a02ded70ee77f24828a32511d33346008a352ac0b92285d83d8fbb95f69a2de9e85cce981bf11689ef7d04f950429abab637d39c51c7b20201f7b78bc9ee216775cab89668447ef0c176d188e0c5daf7247d93d64fa2dc89b3abcaf895938b8762d7cfaf48c5b7342c8247228d76846939697dcf87fbb8f5c916a4f16793110af651f0b2e203db6b01ada3cdecf124006bb506671a65dfba75a49ece4bcf2aaf03d6954675832f703f1f47257c7fc45e39a2130f15f2c2d1c36e132aa2f88838ea511a41e29d2dfa692b93c3c8efe8c619958e1fcc21a85a7819d7264d6d1c0c7812d4d9ebb703c09647c5a9940d424f51ef4b41f9256175f35954655f85cd32a91bb34d2c6b349dbfc9672c4eafd0396ba807b0a68a507db2062997973c8dd83a62efd158a4b7702ea745f58b791bb33cd257c4bf7170cdced44fe2d8c967b547a62d96105434316d3323ab2e16d952018ddb030bbd56ad6b3db1a13977d2b47f493026019016e76d403150ca4e8e4b219c2cdb68d14b62c3fbe38a8e9a36e893df2b140c219a01365eb1dd232bf8da036813825bf722c403b7203e95cce41c5bb01d1d7c77b110f39c837f9182821a463cad997fe5c184bf1cadfcde9c07beeee129ad9f8624f6c511572c304611d806e3bfb586e52f49c80bdc702b67e0afe5ab2ff3bdbbcb167d5144e6baa327af2ac95f3905507351e8917cebe3f5bc77ddab1d7582bcbbcea6c1e72c657e1623c733d6ec91d9bbb399c41ff19d15a20febf35b6b4eb14cdb05a3272adccddc8f25a6c337d2f7e329c48b1690d32fea5720dd68ace7a8f4e47ba942f8888bc386983f4bcfa21cb8305447897721ef2a9889952675e2ad61f29d03b72c660268f3f77a93fff958d1382995e503cc1e858d8b861302fc168e6706d6e5018a62834df322df3246f0d5ae081fc0283beab6f30ea3521e5033cd605c3cd721a21e3d5c337f7a5fa972dd6936bd6c5c94b7d807f77fd0e14ce30d9868d967277adf4416ebb6681eb32c5e3e2a6b61c88a2c4cf7c75a7b33fc71c118a6ca70705682865720bbd112368126d1d8a6b5b2e8bdaffe41a9d7b4dcebb7d55d0f9722d363e0d03e1836e6219715ded6ea5a363eccd571c69db542b396304aded6b70f552882d0de113754f17a1c3e490d667e5ec2fd9da8bef51152884ea7f91fa72587dc39732333d9bb506dcf362760cc7ac18b090fbbafb771c902b1e354bb4721d12b0f85543b4eaf7b60183fc55d9ebd0dd3c5dad18d69f08e969b6060caa723431b247cc7f15e92523163349db3f90cfb84886fd340c55e2004af952c96b727028b92431df2721765883f4ca6aab79e14fb7cfb482cbf9452164bcfaa50b7204e38ae4ef084937270c0f247608783a82a7bc4ed07f3fc2c64ffd125a7fc77253a8d83b16bf6f99538e69354f475b7d59da3ab108115e8bc90cdc042cf0ff526c7a148044db2d2b9f5332f93f71dff163a769b49544cfb56ce51affc00fd95932fa6b94d5e20eb8eccf34a3f316d4916fbbf0f71aeadff3b33bb2e439adc55f8f40ac87e694173d544c97f0c25bb1c05083342679fae7d78e936f3652b5de729de7f23b134c830208d09c1595b85ddb4794132bca13b18abe618c63831ab4722f4fb24deddd841d609f5a50b683c5f2fd9d5ca5ee8e78182af802f93e87dd7215e36ccc986539e201acbcf4e35ffbbb34517770b7bc29e447be085d2c523d5adc0cfb6a3d0ae0fd4ade6093265ec65943a627b2d14546076990c1aff3712072950271f2e3a9dcb7285264fbd34f6780a20db9c85eaac10492a34d70f33c3103c3dd840bbc28ea1a746d8182f842d84495ec66fbf3b10e91ceef7f913475d04fd2b436fde5978e95c7235ada7ff43ae07e9218faecc8c2972b85d1a8e2d2f819fe37cd89b26dd759d0273a4374b9b54c5f0d9dbd95a539c379971da0a34eb67236dd2776b5cca2071dbb81e0d019aef4b7af659ee26dad2b09895d96a965e96fe5f68653e3fd8967b2f983c270122d9262b85ca3ebbcbd9acf8db4fcb7541d5e00bb674c0242e0b743a8ab64f8bfe0468bd3916b162fa824c0d217bd7f6b277200675bbba0c379c05655ea94a709c8c562eb555f0b0caa093dfecb71b3ce55302310d8588faede81361f66d2373d87f66b79b676e274b4a480b740fb91db6e509da4463be5f68b19b68ac7a5c7efb80c74fd44eee227990f20f01f2fd3068267c774fe225b6cf2c49faa9a36390bb56a6fffc7fd12a410dab40304386a938a72e68119096eec1051f4433777905772087b9cde09e9859175d1adfbde04a6337204feb64dcb75fadcf3110ec6cf4bab8d418c9780bbe73c7cdb2b09f68b363872197d789fdb575ac2a7089f5f69d234fa5b9f7e4181268eb9ed517542da8c67722bbafd7fb6ca3c6cde13458bd990b1196051c4d102b233222c66eb52ef192c3669d4c2e71c4ada97a6ddd0fc56694b0419f3bc4271dfcddf89f6a48c2daf7b1f3112b828ab56ef78f7fbd24d49f9dd7239c779c6d0558eb1d3780418888077728a229e26d80404c0c94cca2d1d715b7f5a23571d530118a8ad3d3f0fd7250b334cddb4dd01c246d067818d17efbf5555c2688b8dd2c8dab4856628eea37e615e1ad79085d2a776a1aa70aa90a98bcc326eec884f19399a0782bac5f5a96e1c729919f1d67f1efa7be32a98b37c551d51357bdd4d6d597856e0759cd4d94c057224d824120037f305c8e3005363d0e0fe8149307f2553c50c7b5abfa0a0352e7279202c08df10136a80075bf47f3ca100df9da26980eef003cc3fd2b9ceed4d72edf734eab47a99ee0201d8d1b6c689c6834df372817834eca8266f08bafc22729c45439d82aa8ad2ceefa21c95cedb4d701a691f11a5e72284675560b173ba72a8e36c1b00457048c33b69a2e71696f219486d62480a287e342459e3f5b3f26348b3ebf7bd0632a5b3f861c989e6865bbcce6ab1440627a4454d864c93b2236fabb52ef07d0f5c67aa4baa5d444622c8c2af2fd6028dff8ec44342065522b072bd7471a0832e66ad695dc2b9516f296067c4aa9a10d91ab5953ca3c930640851cd64a12e851e05dc6b3d76d9719d6518335ac8eb56e34a60945f39030edc7472b1cfe00eb5e03aa6852c2552891d3bb44265155f951d8ea61ad5bddfb3496f6f2d84a781b3b240e7cb9a94dc6cff21ae76994391817df35cc3e215ba39efa8432e76a4e28c52381143d717d6f4bd9b2cd2db53ed053116fefea931a28b289b72acc5d4101b3b2543da65e341006fb67803da5caa1fd7e0e1f646b07ba19d262c9156e41cf0add7d1d10286f239bc84a3911e1e50d1f730ea27d71197cff78b72954b6cb295496230255887a721c32fae730d2e1757b66e4f321f172a95c4d4264581d1cdc84eb3a1ab7a3074059fe372156d01a5af73ca1e085a71a634013f72ace86d59a2c2cb5e05092eed1aa094d4ca6eaa13f73a57b4fe0395af128c0c0ff97414c2acc0d7f50e24f91c0fa5eaa80a1c564ef4478255cdcec3b88a8bdc721c712b97e336ac34069edebfeaa402b0dca134a313ced02db3b551853a844a417ef628f4fb7792fa18fd30ba75da4104b6e2cf13ff578e82859a8070fca9ac7297373825ad05da22f61e81a2fe475f85f094272647afa60d4c00128f9a828c7274630dabf66ee677648ac69ae1174332e04f7e86dada8d4dc1749f2b3ba6b7728d8d716a7c0aa17b5d64dc11312e8cf354f231ff4860aca80cd2d2065cf55172cdb5eb92bc46a90063c4c94086c5fc77cf4272df0ac45a59311b3557ffee676c8f6d119d4bc191d12acb71d2531f6f73cd080d01fa01e8e282242ee60f37cd72ed0f49516defca3f921add6b64a4540911fa2e72d1b0f1e9228bcbd848c0d04537efa970befd46c238894a376d63e7f6e7576e2f8c90e05aa732c9ce954d6638225dbded7e8cfe732ac1006d4e07b9c1a7594b263375c7734933bd487fbc596f0890517f4d5373386a68bf1379a2146784dd713c7100ecc959b9e304c420723c973c870e960011f4c54018ad77fafb51f2cd270b1215596d1f031dbad7bbed2296d7265d428105421ab30d3e891ed2fcd301418885d07c4cf129e6665d4e953c8ad4af4a46630a7f6458dbbeb261357b3ef0bf30c73f76910e7828ba0280f7724794513092a5dc18769be2d9022de760c0532460da0e5db985c7dcdb4427df06fb526f818704ec4a08870390b46d6d40d18f514ddc35b1ba3a724d953874af7214315a818742d17533b9763a2871960dc40d88acff48237cdf99c60a96a96a72cfa58af5a9300b835f83f7825dfd9c870adf91922bd7b2d45233577add185272daf10d207e1ff91776ae9c2708d473da5a257e6b402919a759ccfaa3292bb86356fd03cbda649885841a838ad5368d3c6f3be4c9530adfe3eacd2b137da9d672ffef42fbed0c58e0704bef8cfad6667eb03f07ea1b87ee0bd07e19e95bdec415c0ec1f6aae596206e1673a79f7add3384b85020015ee5b90a35687335c75745eda641401ea8745e1e1f478ed4d005dc066ac5a81295804e1222a2d4f0e2e9e72fe4c8a4c1f41e97312a027447d6b0b6460884f901b4250de9059429e25a99c28d233efeca6269ffbffaac43ae18de095602a9f27291f22cd426364351a415d72a9c3e955eeeb06fb78f4391903c4390468818312a3614285e07e19f17886b8725a0e812e6d7dac387053d984a9ffb3761c6b80cb33839292c7f9f319b0560b72ee77ec31a58062b8a9a9bfbecaf4cdb1385cb5ce66741ee261ca699c7396e61fb8ab7d4dc45026738cc1fef7b14331780a84d5ce773b14b4b092fde07905260f7cfda55c0362acef100af5c3c845d8375abf695ee86156fa2938106b1baa79726f0b70c0223c8f0cc69273b779dfa18516b9e377c8e2559fa4aebe77db155272352046d36b60ac99dce70ad8cab5fd025778e3304caea4c99123c8c819c7cf721fdaace7a5de663fcd8b13e31b7b6f2afc5bdcb42dae3e90b76c95ac6723ff252b94d8fd3ec8bef7203f216e65fd63689f475286e4747879516094f4dcca0f72aca0283f4700ec9d8943f6aaa185b886d7f17c67df8438d92894416118d5f972ca486593626a8a5be278f1252b631194731f6aed0dbc249e057ff0f577dcf6729bd01043097be48e8b3c7cd23ae20d09981cf30d84a20199b1ace9d6df540e725008726ba5869192e2605889cc461da7533167bd999c21ee3e70ce3faeea7572a9045271ce354d19a46e938f81f1d6fec8ba4b11b937790fd1f5599fc699ff4bfbaac3b20036339d1cbcd5bb736455eb9784f8539d179c5add72e396b361da5c1e5ed9880bbda8e09a40a5b9a4ce9df0a0ee86ecb891720e0c4c50e25ef3ae72cc0c9d0c47733b93c61a8b9404f190340b4835f19b853be8bf023be13f4ea4720c1b5a70c0b939ce9bdb49f67ff92b5b5e3b2541819e75b3b0eaab92e9a0492b481145bfa7c049c32b44eef7eb3f98f7175304c46555a614997d81796c33c272e715f128de0530280af92dfd8c1f3644bc4d507d43eb81a95e52725cdc62b86ebbc4f95be9796f72db548f1fb2f840d3cd22bd41577f0dd18b4c324b68e88b72d2bec1c601f4f4af5e366040bffbeebdc0aa8f1f4ac1ca86d8f6ef83caff245ab162e44c2355a0318c0e2c13de584bbb64525f2445a73e3a2f6411bc68db374a34f4c0d759f35a595dabc0f287a556643b0c0564ac2509653e03a988f91f0b6a5741f67646d0a0f61b495a7b0353d998707de3121a65c3e210b0d7c72de64772597b4ee65776d799714a2fba87a2a08fb1477340c9e542fdcfa69ca5c6d7c0360d65f78c6afe065ae4230def9d97e1dbfd9f253b1336caed4c4f6e54e757eb727120d8da951032d0ac97871dd79e401d8d2fdd568d104654ab3e626f724b5d72073f091ad2e1303bc64851cdfbfba8042f47c49c912823f354cc27c9ed2cb02b9fcf0e41ce1c10bc914292d4d9b668c5015e6db7163743cdbc7d9a876e74670180166b964bcaa9b582cdbf14cea2b391c2fa43a8f3d92b4f1fa0ac4099f26172d8257ebe96ff0a00c2fdf700b66787deb771ceaa72105bb40930716f6c4dee63700ef65199cabfb5655f627f79c5b2e090240f0620c88e66da0b6b10740f1f7270a1e19e87ae9be49cc353ededfdc5bc00c941287e7bce0589e09e804ccec6727c39ae2c3c42955c6a89173d393cd651b03f3c1b9e27f4849caa422b06d2c15b9a8e2dc2418d288721bbdd7443fb41d8831e9ae3ba67caf24a9332df31c08f7252d433f9812ef4f74cef8fd1dd21ed021efeaba1818222319cd0218c13873835a8e801760742e5de93af6b390045ac5dcb0e54c42b2e24eeb4ec2c9171653a72115cadd1c03e65a6eb6e254d7a6277c006f7a6bc6d5f13744ce161fa174cc672de8f675a83f74492b3eb613c803a9e07d20e18b8c26f8df9edcadd5382276f72a79734ed2d9e94c413917d30fadc1c8b9ec29c9d081e9dc8171db41c96aa8272cd4112a1c7df83cf995bb4b2f1d386dd69299ecead2e77e42183f2c4feccb7722c9a75c0871e9862c9176338f17eb729cec8945510257a1dddf89a6aaa3f21720c1f0e1412393eb287b0986335e3ebd63308694a1e9e4494d0e2cdbe222ee972e78bed48f5aa4a7919330c44e9777f893b3b46c282d057ddef25d0e5706c8772ea9a18a35b064c54a655513a2547659142655b181b5fcd544c6a413941c09c7222e1fff6cdfc6740243a49329fee9c4b9ef2e52127c160f8a07fe28cf95c52729a4508760741918f41b4d904ebd8fee55091630933b7561d3962d89ae1cc1a7201203eefdec21da7548a80d431430aceaf2e3ca4058adcfc4a1d64e66591dc0769162994f17707c49b6603a931a3b37bea9b6890db8b44a9eb48affaf10450590b2d05a937fd97f592408b9f62cf41cb24afba06e8b2ed5fac30a87345d56172ed18c2f1d7937ef2e1909a7d15b517239ccb1fcc134f0d3b474d60a30794047204235aa3349de5117c3af8734305b05f1e2a40f94c801e5e9589c7d7e3ada3723e61a6fb6c69f431c9731d20904cd6e31a138adb9fab96031b6920df79e039169a62de5f6e756d821e318ee1e329ddd12a6c4c156af3bab0f15c09c04b594b72672fa880b9f5c2a8868cd38decc5b29e7c28f836cdbec631c31aae894f9f5766e09fbf718de7e38334c342ab2f38944ffce57a1701b1b2155b5c10f281e5df722e77294e7efd36151404918992d0682e72305ee7a601c83034d94f14081bc972b9bbd8f1c69f69d354250a9b9b774ca0293109bafd19d5715634f3d8576fa972bb984c50ef92892e847655c0342e6eb8d29db67121f2f65dfad93d74c5074064fabdb7f06d95e5f496f91373cec1f2bb27b8ee14d62bd0aedf546fe8b7da6372f78b93903f26fed862d43ffd17166b638e1fdd448c85e2ad60e0de7d651bb972e72be5b40633801adcb7f5daa471a32f9f298bc3d0487629c670332862986200dce4176676593b9a7b581b4a805d1c78ba74bcb2f0c9caf55721d22eee36f73b877eab8ef3164da7342f666673c0dcbd8d3a1bb51c02d84499fe28d9223437544ccfeb64657c98e7f2263e8379c337aa7bd20aa7dfcfcc52cb3aa24804114c72fac0d81ee7b33d2887c0e3f00d4e122ee7b0a47ce4ebeb04188d0eff222e887227e641c9b7816a4a0917ddb293ec25bee14d005d1f331df3e3bca0fc1432ff7260788cbe514154796477f067682bfb79a61d5269063ab3259ca676dae9a62d729f45248efe742267b98e245426dead2833323ded8922fe2dec26797bd1034e72696c489c99e113d369c94ea428fc07d06da5b89fc77d3381a1e073fe94dba133816a066913ce88da38b6a7095dd520e8aacebc83712394ec1e955ae77dd9f45f900fbb50c5f54a7946150545cdc4c1ecfee6d1de31868c49c9748cd0d31dcf0b98f708a4962138f87fa2fd6f8f102c29c7afdd8b1b1f2cb31fcfdc9130fa8f729aa9645a1d284e0211c46636c7291e5f14331c5bc63798f684710bd5ff761872d14cad736e6f7a350605e08a700de41eaaa4eabb98dfef44731c15d6e2ec317282e12b71dea36cedf0dc1858f5c7e6ce55833d03fe149300d78f61625877cc72dde3078570d0a9011ba0d92762fbdc60e631cec26541cf67eed0bb033c15a2728c3b4f96eb130723932db47955fd7a791b9746f7bccb96fb98dd21b6776dd572fcea294ae81d325ad0b137629dad47b0cbea4cb27396f57437f995951954b97274b900a8d7ddbbb0f7c02357247e63064747801b2d8bcba88978d8d3a89fad725e604f1bd91eddd094601016b1fd3887239eb44986b3237a9a6fdcc25eb2171fe382978e75b7312d09f1e831ac41cb011601249cb55d3f61441039f6b2a8b524a7f0cd84f4cf5436f477812e61139e6a3f4717bff301450012adeed24c75492023320b430c4f95190b09783b93fb996bf3f0330b4c7afa997991d07831b343587dac8010be0971d52dd76e217e285af19420745cbbfaeb034a081a4c98fe382569c8475304ea3afca98efe413d3df77e185276f486c0468e76ee7ad573e41c723da0ad299f1e8b1530899c72a67b271a0d8ecdd0ee73283d9fc40cdd0a1aa0729526aa15b188f8349c1209a365a3daed3ed6a6e4e18f0c0f924ce7d48c13f362981fad82d4a9fceb2740d8840272ce8576ce7395d998ccce743682060144cd27ef8a6824539961f6bd2fc5e16f25211b301d6b3e62e77990a2b79c9c217a23053316427ae4852578c09f1f7ac060fe9f74931566cae98cb7196d5e28b6a48772233e66d2d707b098f72bb5c357973f8cef68dd5668152726a64ef967c626d105fc7185897294f1d1d79174efb5b37bbe77c3105e8a98d6b8a8ac26e889ef5801c314355264e29493e31694dd575a2b4a02c85baf0548bf4efdade64b2d048c720181b6fb27ad2c202aec6309afaf56977385a435ea16de2ca824c0cc02c39131d8251c103719760a9e9bf1c9a1d124b478b8b8643de9b39a03ec54654c783b72ff1c24987f76d0d72032093fa80cfaaa8c37854a93e3cf9ed6551471c4adc172bca20c24579d92da9d4d8afff3a4c7c0384ba48b5ad40925266b673d47c89d7268d5c08de2474db434d9cefd6062a56d22a0324ed32b0a81a82437171e92e472e38ed153d239e6ae26b992748b0c5ef342768b8e5c27167d80b926306144f27230991608c090eefed1ca50093b00d18a7889a4657c571f5ab730717e74ed657233e9605bcc70cd752e93bb9b2f8c76bec83981487f3a1723f9bd75be559864723e693588a24d192e4a4c6fe08a3ff430c0f5d06572682fd13c63b59cf1500b721a1e5295d8fb58aec68bdc39d4aca4dd391892e533e1af533e181b309db2c8726695839ebdf721c41eacb032ad7405d61dc1c390af830f766b51fa5b4829df72b06c8cca92f90e2c769cd7f8d77c345a1d68660caa87239b962e62aa962c5444526707a91c5858f277fa5cc1e02a216e0c474e5a866b6d1ef61fb2cf55d4f555735ab57235dbb9860f1134316b183d6c7a03fbe0a2d5a159d47d97572ba6b87282d78f620dbf7981ff57747bfacf72ed885ab85be32b301342b4f7d77a2d9f729096df54d703c122979cdb716377f423675ef83c72b2c5cf55dad540b01a3b58290b2c482432a22c5505a47ad432ab55a49124b3850d0f970a618cd4197b2672e1bbdc4716dad9907c97930b666f919e6f7dad6ecf34fc0811974be974c7d972f1a9c6a82ec42d45bad7c938207f05dbdac088d1fa04738fc6eff15df175ad34c2051201094733c7e9c65842997e51143e9e79cfd2618466af3ffc93daf57b4bfb5f86c19f231235143fce331bb2d81dcfda5cd3e5a973156ee0bfd3f8505a72b680442d3f34b233a2c9d38e7d2fc116021a3db7e4de7d9050a62978fb404c72e50de10962ce737eacf65d58d7c78089cbde2a3ed7787ba4a0dd71d39b246c72b3d80b2308af27beb19a66cfbc48d5e3b21773438a72c14a2695c9b9b4ced972a0ac640a1bea96673c7eaa46ae4b59deeb428501c122766404e22f274a1acb49b12e7092bbf428c3c8addbbe4c36d0bddf5a82c3b6f7361d08ae6294c916147211802ff57cf748a001f902a7553494a9ba57743bc82b8bdc618a46e7ce31327272d2955ddfb9ee3d385317821b42560e1d1cb0a55c5aa3c68ca980cf4a0579725d7d1b350c8eb57b5686aeddf8b234667d438344af05de602b043ac0d030c0421bedf52a664ff8cc9f2ac51baf8e874facbc4e07ec3a3624b0d5fad97dec74330fb5730fd772c59da3e14cb07ddff6a12b524c1ea84c509d482a23de542a7972938a9734dfc107b0320281c69b0a96f427e69e75f8b54a8bafab415301fb873c5d43bdf11ddc22b32442b9e3751899742c7d50032aacd1ce604db8f0e7d34c72f8ad6073c27919273db884459968ac220016f28a4fc176b00bd763358f419572577fae6eae9929211fcb296e55c41b82e7dae34f48aa888b70afffe9fd5dda72181e09000a7169aa0f6d4db654a897f024d96336c8c6272f8b117d20165b713aff9607f9480d4c9175c50ad24f9bbd0cbc6ba74003a104e05538026938e30b72aba6e597b9f55977d87a3b212b2dae810cdcc3e9c66fad3d99e2a80153e104727742e082d9f53d7fa48ccbf1a88dd39777f8cb09c30b58903f69855dd6693b72e22cbfccba9adf49320cd8f9a9790d9b59fbf8084d142e3f0188aca193618c6f3c851c89feab0cb7ef58de4a92ee8b25bac0e3ceca3fcd7d2b25a670654575266255798d33918506eada12d150b556b679e486ed4cbee39d9a20485135118c729e82099db29352f396af7d179944352ed10d8affbc90981782985ea5be7b00472bb823540be9275ff9926da3e5b932e2a4fd732b1e70eb6e332d189375ebca7271b5775d32cadeaa9dabb282022a4a1799144fbb31a007cca4e12e9b3b5138729a01c8169d758c7a8740104283709325d40364448998ca778342fc6193fb453a7d4b252325a10c6392a2e32603f3f2901688f69f85f765be46d47c9db11ea2068f9c5ad88f5d9620165fb257e9239846b1a2c7b2f11084663bb0d641a74ac7725dfed12127a6be2957dd00c392a0aad1435c261ed4edd97ddf41f13f6d0d3d32c82eb09bfff4807396e0045507a866d98bd864a1c6b6905c4c236a0df1929872bfa8ce1dc1bfc1933fa641fb3f63ea59166e66808f4ecc8548e9aff9f4307372810c434d2a2422a00426c237859645b94adc38d92b786cf19d64b85496206172a90981133b681bb34e45e79911cb5acde88e227c2e9742c866ce7f6d133d360da4444c6a7107dc8231d5762e710cffe69512156ce37f49d0243818feb8cb0172e615fc4c34bc6f95e03fe44019f4266beaf0aea6b7f148b58b01dd4955b39c6d2474058e4d5e3668c167d501ef08701e32ee5e5b00079c3fef72b97d54cced40d5c8eec46a2b973ec2f2f12908ed8440f04759d140797abd1e8ecbf311652e721fad2efd718273263300fa4e1203683af58669ae60d9b5c5112e7c5020962b37d5947ef8b4f6db4d7f923d8e59cc7ded71f86fac047b50ed4d81c025b85ded720bc0a95741134b73df5390b4c273a6b3bc3b7bd7cbe8f8f17dbf8dfcfcfdfe72e13b6089843cc712db68506d17d73aea3fa9fc1f72b05ee569210493acf50172170b3630b923135e42505579b3176a94676f38e012da818670736ab049a82872f461d4a475bd8ec1e39f7086511a303ceb81e185553f03c71003a3e2c98b9472bff6dc4613faf59d3f659a5dc4ed32b75339d99a2a2453a776ca0bf2bc50817278e93e0cf69fe56ba7865bb213e27dee026d98cade548d5c41eefd07c0d94d6f5a12988252a8291b25a347e282f875be511bbf524af9aac869e65fc0762d9a422dfc5baaa4e17382d307840b7ce4e558cad834322ee73e422a57b64a6b32cf12adb7117188ac858b7b979a264c3ece0c1d3ac38973ef1fa28b83cab796d9b672c69c9b61e2c60b8bfe02d1854ab480a56140cce05bc54bb115a1d901b52b6161327b61b700d1d6657fc64e5afd2ec682e99d9d2b35f4e045ec9e162b19e84e72f11c058c600d68ff4b9220948b19a1693def86efb56295f3028148b8ee976472811ef2611b7d9caddfa3ccf9c43adf267399e158b4be8b58d86fa4cdf117a05395a0bfc4f7c8430988ba02f05a8aabd11c9f163a0bd90df8bb92e9ef5ac0e12c569c339731cb62d509c4b665ccdfbcc23568778616586f536bf569ef90246c722632a8c9ca11d5d64eaf7f844a98c40c6315cff8799938f3835d34bb7791fe48cb8f658705e81149632307a00d9f42d92b7d53a98ce3e8fee370327380f422070dabc158d1adab29c1d738772b5be1825939e3701c7f5397f81b4ba82690090141a181a7eaa61bfd7ea947174061ba1b51fe98151ae46c92ba52e9518fc164071e729fbf1ec4ca2019d8537fe86b375c0eb699b3461bd0eb419b29a871de0372da9cc089dd13b429ddd65748c14f212476b6ecef6a86dab410f5b8889e2abc7210b3ee668906424a16eb38a1b60bca459c6ad3ed7e73542609c44aa7e2f35602f70b20fb8dc8289f298abc0ff053c659e53a2e97de4f309f7ca2a71341111b7266b45589d6f06d0dd68ab38b4a59e6e6d510afea599e3d7316c9306def180f724bdc938f38daa495ec4ba83524bce4b3fa70d502f92434ec51eb1ccdeb7f7d16291eecaa1c586849c1b9560e0b2e0faba3c2804be613a83ed2058b09eb9bb172a14223d703997f2a267339960fe711f1cdc9e84c38e63ade0098f7c68a802272d5e536c219f84d8ad44dce9c30a5f77dcd640d5b0240b5978898455d44ec0b72d14954a24a054b265990ad8fc6ec587acb1d0efa2669a1a79f464c955d2e694fa6c98455e09aae7817d60425ed02a823c9d722d8692b5b7e56271e5959a53272df158df39a803e49c0ce3b7fcf632cafb28041210f48c71f17330c9f0f750a03af193640f22e9f69f0657619b166b82a2b589470e7a3ea79806d2400163e213c80a1e651696127455a16d1204a1de067788e7552c8ce1bb7faf667aeb90cb861bad719111e296f4f7a1c844f1fd02e51c24327fd7f40ab5d006be0b459ea1872bdb75826ddf258046fa61f5af0c4264e9cc719cf3af71bd58567a4fdbbbe20721a8b93a4587fdb75f25ac2eb2e5292d05dcc722cb8237799de9a283f774e82721337c0d670e907b9f54b8a18392e2da07821708c6905a59f6d5c623ef525fd6e8c06e1b71248124c8370e8942ac114d3b43092d2f3e8e34b5b78d61690dabc729ebd4875fa4207b4fece8f7813c4dc74a75011fd72947314065d4493c87db872c7d3b2321ce6abed762eb2062b100267bd8873ff7c564adec36d3e94581c3c72306fae508962ca9b718771a8ad92ff946c6893a17500a91379bd39a5f239fc691bc3942a883e7f3e871e9aaf1cfcc77f165f92a6a71798980a48b5fd672bdc086eae46ff4138e5a7bef8d1b43397b500a8d346c1703678feba5cd1b3b041ef72fed12d43b78232d66ab9ce58a0aedfdb145528ccae6ef3951a28721dca4cc472a4375f3fed27630195d96290968562d2a1f437cf7f8acaf097cb336812185f72c11106ac2b2032b2f4a7dd6698e38114762517954aec3971fb9ce060622f82576b17868fbd7f05adf2d755ac4163c0292782715b8054f30b39ccec4e8516b85525ab492342cad8478714bee713861ead8bde6c8d631d83b3628b7ffc74fc54729865509b5674e1fba13de83bf4bb779ec2e21abda384854f19ea1cc17029504e5763bd2633b873f3a430510d320cd80ce68e879e1416b9b42e6c39c4c60a9f443ee333f6d2b5d13905ce9fe7f46db89c8de0ba793a1e1f02d923b4b70111e82479141b718bf881e5d447368e71b7478c81ffb2dace95a9aceb26afad64f5e76a2ec115c5c5629599622232642694bed6a65d9405ddbcbb7b758c3908a2fe5f71054600c1a7c12b9974ea4cc809e189b834a9ab2ccd675da5b223a6fa3d068e72b26742dfc72bb4aeb18f7e6e69a94b9f35f412ae0edbd3ad2cb3eb2cb9e6317208e302d8df0b06057bc75cae117d46221eda3ddadaead9002972d879be4a6509cefefae22837ccd42f462986bc9ac03fcec5749f61786b19a7b378e5e0d98c23d0b5ce90659ace57b0d12b3d86b76c07fb07c9a9fee4b527ce7de6b131f3cf72f710b97c5ee3e5a9faba2d0486614b6e08785da757c79e352c4d498db0a33972680bb7c2cd0483815b8c7fc8f1a50fe4875464b4b95d74a34d203597edd74c72f999e9a30428aa9417714cb75778891f83a60861b09dd905019ac0404cb64472a12f08f09caabd5ba82009169e172ea0f0aa6fa2a71afd84d14d0f46fa2caa3f0cfcbd36ae4807f719523e57d1b724b595b1d55268b5f14776937f4ffa67063b99de43ed2bb298e14ac0e25f5952cf51ee7cf9e50ea41d5e6317cfab3f67ab7205051ab35a4ff43fa690ad269ebee9e0873bb8579981804e19f839d363e706098285b3f5555be977b225d92b9cdc0cdd02a443162907b80c0afa86ab348d6672cdad22de3649be938cfb2b5347000505a0c6bbad3d8b59adcf59388a1a1ae80049a2be8509900eaeb6091ebe12f8247794a51cbc7f0b98a32bf1fd9358e7d57276636029adae10ee6debbf265263f4ac750eab0aef75b1af4b4201d127a2417236b7f3852b65730a87745276e65df428b89c37a8f370926ee3cd4fb8f7f9d26ffd7568a64fab695cb1541e42a3387d9e9df3c60fdc63247e2a05773c942492724a9fbaebf2c59290020a62062765b16c73451471911c8c1bb629e80094e2014a2cc4429b956895f1bae2d5e3ff2c5f8d948de4298f06abc23ab50731b63b4372ef5d7b9a41b38745a3e6db401e05de79b2ef3a45f8de0e91f26355855cf2c172a655b9c1078a61fb1a6bc8e71e5eeba92395492d41ce0afaf6b3e857c7577e22e5d7b740e2e746b77d9f733353179b28fd9c53ed54a376a3733efcced151e822cf8aa1e505204f4673304576889088a977e0780eb0d5ccec8dacc3671a025172636b58b23fd7f839155373e34c6618d694549c518697cfa38b8247ed4aa1a27223f58b930714fa266abdf4f23038bc47a1c33ef108b0492936352b9774182172cf9fe7bc292e3fa06f9d9813c1656a01c5793f550a6befa611fa04c07daa7e3e7c8a2a98f6163fb8e751da160327b2befdd98d9fb88bccacef7afbd75ebdd572214f65878e8f14455d8e7ce87ccdea3461060860c6635369f314df0c343c4f72f5aa361f77ec71251c38c97037fbd770307fe8d110a417c7f08059e31584a172d930b958c135ef8d6e64d5711697d588c2494f186d805de4c3cf810754947e4c1ca41355ad1141163ce1a784140065fa290403c73ee7798511bb459d756c3272102ae037b52aaafcec3a89d07fcde4a7f762af1647f2bb3ee31c66b0aa840f52760dbdf84d5d068b7a69823e5d20b30a5bd1392f1eb65d7d562c8ff5912ab46d5aa32e0359a97fc0a41d18d197d81dc47bba46cfea5534540c92346296d00772ec8eb62866cec0752127f3dfac7f478f55cc20f1e4293386514776c223b633722424aadaed6f63952b97d666ab2db2605c9bf2f8e394dab19a1031cf12d2e5271caa0769e89d575bca6ee0baf552a1a42c7431866d88d59edfdb13cb85a55b7298c839f8f8940e0a0f7875ee4a4deaa3f3483567cc78f93062ce00a68b49b37212610e27cef1ad0ccb355d7fe5b7d2dd0dd543389178d0e0d8464e8e85718941defeeb0e87b61cedbd5d3cbf6780fb52e15fccf8c824533bd457f49e410ebd72fa052ffcaaee0433e8659f7868d9aaf11fec0ad99a94435eeabe76add6988572110f8f940d13b51968519180a875672130b31408fd95963544dcd3741da2be72e89951a647fa343ad54ffe68a5e80e169f398196c5ec3a58a2a7dbc0fafbe2601e7e90b771b61d8434ef4b61435a3c1973cd3d3a6da3a1b4c6bc2f3b859bed72810947444b6ff603d59fcb3f397d3275643f519c522904089a63d4f12f32193361beb72d69fd070ae69475c728ddf48953d24118015bd343ca882af63dac6c728020941812183c8356926027e7940641fc1fc4d08ed7915f367e6bd4e785a661c27ef0399c806da8c7587bc78ee6e734818773b7b3549fac3b9900d6913b0c5214840b4c93bde3f35d4e2bcb678bb453f98055666f2f186a9ddee422e29db1652ff059dfb7b9f326ec2fb62cfe6fcd3ee75bab4b093624401bcd7610efe1a538c1c5f0addc2246a4bdadc7b50334e62473186cb994e123b6186a5c13357b3b46c0aaf532a6014e0dce370a9f48c11fcbd4fe5ae243ce2762e535c10e000b0572e2afc76d2f63b5c8cd8bb4eca70e231d9528dda427a58028b44d60767528bb60a9018eceeb4ff020e665e27bbebb3078a9cd9de2b4c1aa77baeb700ed942f472c06627300b20d75b0eb3309d754f6a2d35e5e6f4eb3e2f74fc56517cc254fc72e318de2f8ab8beb368c2877930b78569cf839d3646710e458a7bd7b4c5d157728ca298e3c2f1b7debf1bf125d5ff1ba7602baf8eaa2238c3c0b91a5abdfd77309d7c35c56261dcd7d245cb37981f01102aafa29fdc1bd5a66f85b8ec5e80d7726f3826259880a2ffe13dc85b9b5529dc1f2bc4b89baa2d6f7a4b711802c01c2b61ce615559a0b6cbadbb4574b613cf5d60bae0dabfe965ebdfaa29dce8f02e4dba4124b151781a96cf293706c88f456ae0ee7aadb54066bd3462594e29ff726428d386cc3ef2bffc9bd35af09f3eb3cb659e81f37ca90e7513fce2256bf69c72c75d50f6ec05aaee7ee44e771d2e138359c38bc330bd20832331d40c5bc3311d5dfa04e5de52846931b0c967c41c3d313a5f87a0a96841b2b8b18d00137fd40400bae66d2fdc995b91787d4573991d55599b57bb8a6dcff30f72fe56327521728f28c3d6c64645792a06e2fa5d7d6af4a71f29bd81599886da30b00de1fab072e5fae95a83caa315b38677ca8ab38aefc86317fd3c592dbc2a1a51fad09a603e4a88bfd00f8b8ed09684c84da2464e46155c719bf2376f54a9f91cabd88e7c4addbb8d098c2b47301c01021dd5586de7773d2aa5ef623f43ce7746347458a51b6bc5b35677ea63126ca7d63acdaf8d2b9b729a5f01e74686946aaf94c1c9ac656302aa43ff0892f7526e70ba90c183185275f5199bfce23879b8fbc4ee1bc9720699ad6aba9ddb1495182601a9815fa8764922a5a8f9d2c8c0c49d606b170872e07dffa28077aace7b97988232824abd07d6278a21cc8404616d7afeef323372ef8054c1c86c04672f1c4dc447befe1695ce1904ba14b8dfda19a85c26fca972a098bd407c11175fbd314db927bb9407437e945d0b156a9219d75990a6995607a9f7c061fbc9f55de4e733fb9cf29c7759960c633a1347898379629a0599ee3c1bf9d1767f418ea0f5c9e68c6c61a7e49d2e519300d84e2fb66261179dacf77297a659b5286cf9a26f1b1a68af53c5451b6a1d4398a2e22b84d0005c2364b772af13a1b56bd5d5e84867bc6adf4120112b3350c91554324682b9fb531a0e595040062754de977c20b11d4b84b3dbbdc36bb59cfae9ea4fce05977feb50ca0f29d1ea456f901fbff86b6a43378b330fb4cfc070ba1df8892d8a4317d46815611aa2b7ac7bb2be190422778f4fc80ea3085bbe754a1dd099cabb52e9cc1be3e072c29bae2340d3fb2c8c2b99cd65153a3f70ec36ffd1b39475eea84f2dd818bf72102390fa765a7cc1364072c47669e18e80dbaacb4bc642842bd1b7540de5eb725def74d765c928636791be216d9be206d4b1d77d17c59e8faf76ad626de05f324df308ce93d61e254fbd647a9d16ef0ffbe851ccff0974d0e0b94a86e7ba51725540641ada16c3454dc2e204afc4b123bd9a444a6ac6ac8976a3abaa02b4ea51249fe87249d95f6aa0acccb6b589e2fb8125b7c08312ad53b09309d69133340ea3893b9756cad769001a55e7ba4901a7a423880f737d72078aae45181d7fe4725b41bdde61a33f82c1644ca7cb27c7bce5c30557326d76e29929d5ea674bd272f5cea5dff0f6f67b93fce7274aaf04b5a9d985d9a9de2a3368024878772e0d728b22e20485c11980489f1a5b1694d9949ed046fc4da50c9c28029e181b13b454148a533ddd1c778a7aa8ce30d6ed9436e06c744438fc5820643025b6c10f6372a9b5cc6dcb88245d4024f45b2d4ccd013f924bcf46be2b372e26ea4d6b32234f5c4532df84e2820fc33d8ff0729e929c67bc6ea5d9136c6d2def28b37dd3b472764830168cf1db91d6abace94264e2347d49fe8756df511293b030a797ae555c5bf188720f9edd7e96d6381df9aa117e56cfecd81fa155a0aaa7c59177e1af72532abcf4602f2684fd1483fe9d582be7dc27c86d530edd7324296be385b86954d670e2e8be19591353a79097817f851a66ae0fc30da76c515837b1e884a0687250779e7a5ee7e9453b900b9df23c184786ad580a5c4b7fe06ef7e29d52df8e4e05e5ecc49804bb3f5d9eabee96040f039ce73afa8e6b3275e1210070578e0272e5fa9c29dde6e32760648ba9067dac57587f809030040ac82e6efb2f50afe1329e360aa715ba52331a223784751836646e3d347cb19d32a86c9a820d22130b7219d7bc2f24cac11bc66968644d9aacceca1e914e7ba24296c121ffc7c1e4fb72f3226e4f15117fffe08814d37d3ef0d0c7ea464571ad63a830ee80258b903d72ae831790d15024f562cd9f4b58de7bb253466a47dc1a5457f35d0cf91dc4a87257e7b9b07c421d82771a45c5005201506294018d627c893429d34712e53568721cdcb20ee09485459e8d0f2f6535c701f2d0cadda2e5e661ffe70ddc1eb41672b710e2821a3f634dcde320f3258e990950aa32dc7aa51d23dd831939d9d4b848a9c0b2b125af1db2c2c99fa2af546f2d5aca4f5f010a42ff30a3af38f5b257074b92623b54c5fd7a175c4e81ff24d365ea6d7e489adb2bca293a18676b3f15432fa3132b00c570f4cefc030ff83cc05fbdd5bc5b8f054c64b8a1fc29ce0618396daa3a55693904e8d11d8840928d8177de6c757605c10b512d7932df90d94c72b8326153dfd5ec369d1c0fe973c8de5593c37da398cdccb890946a6e61020a7202884f64b5017da0ce625610b68097f6a3c191a67b837c53e95910e3bba1567261fb3332c99205c87fc71d4cc2afd3414601164d1e52603dce7af3d46550c572a5f4147db96837724f8b5ebd063ecd6e1856913363f2616267709df22a47394e40a3d77c0e2c94831935bae657ea2394304a8c4cd614ba82d468d924d1632772c5a6ed1ca9a80958341b8e00dc1b755a38bcbcb199c3eff4f7af127e783a5a53685efca427faadc8b48b6ddcc90018de256b72fd01039cb50e97649277dfae641dd054f227cf19cd6063acfc6e293d4c400bafa333344db2489638496369d5727444df3899c224620074452d99f3706f405a3725a96885bc51a8d277d66bc143e0f81810980c5745c1b4237b84c1705d83a4c465d6dd84d0fc89180302ea7c7295b2cf87b7a3744bf34f0a969835d33610254b530f7cee00ff4f67e96ec63f72297fe39418604e7e14926f8da7c29b91f5ff28813a772b7c2c01a06b4da6ef3afe758258b70883f7135dc61081a0bf77f983f8b1dabb6f995c25698f9af3d3010cec96f875c9eecd22bb0b551c85697b42d8b1274943bfd403fc7e7f77437972726cef16ae176318e94b0c526f04f86f1c5983a4f5cefa16b6dafcf9195db172536c58a7bd6dc1cdac9de4e35c6345c002031030df2dcdf40e092e8b6163197201359d3e4dbae90a3728a4f8add58ccc00b703a611bd7776be4c123742eae21b6dcecc4ed662692f9bbedd24a9d7417bcdc6284d1dd805797d3ebe452430cb721dc1ce747bc4cce99c0d0c1dac7b3e5131bcf9c2b8f4ab40367a6c5997493c721765b00257c272ce4b04b0f9de5fa550d34e75e99f678d1d677d6ae4410f5a72d0780cdab52ca543592c5a9016f448629055711562b229de7f1e35aa656fd372cbf7088b8559c885afa93bd4ade17f2f7101ff006f386d530836053c66406b72791438d6c999c0e3d917ffff012e1d9a499e86a62a5a04b988599b445082324241c456e603adbc75670830a0912b6374a120ad38599e05206710df3e53147b72f1a8fa99989b564325b0e2d5bed6ce212707bb2ed9800b54a297b90d912dfe15fc724b424e1e78d7efd4820168c0c660fb876286877a592eeec767c95111ec1defe5301e0561afdbb4fe00b08eb567d6cc5c153c70210fe94e731b93f7b604722c4dce4510491492f8ccf6011fd6b3d8f59e6cb5fd8498b3b846b92f73d7563e2a1061020489044c1621e33fe319408b4452ccca1626b1971f940ea64282c372daea7a7e773456c09289ca25f61d1a60ed0351997897ea97b66e20e20174072ff01c3061ff7f781708ee8be75046095db23ead35b050a00e271438c275daee4f5c6376f58a26d7e6008c9c69ce31f555aab9a4ed8f0af4a3faee2e17ebb2a672017870784f3229ba0c1986fd434ae321605c5646fe6f6bb1b4d1f990b2650c7208eeaf9edb27c97930dfdc07e38cd1eac3a6db32b4b21b5c4ff582b5a73fcc723cc2cc02bdd26f203ee0172d355c1a00fa73459b890df3dba049769685f2a67211066c8d2da5e841532ebaad522e5e782dc0e00012b9ff52caa6594b4416484c2b1d5fede87d0055191db7b048fdc6bfc9ea123bb83d79b3671c041c734aaa729b91b57234bcb23e9aaa078bcdab03ab5937899c4ea0f7f0c7b369f3907c5d248f950bddac43a65c13371f4c4a2263e3c7f4fbf45f0aae9aea0df64b50922868a143eb8d433def28db98706e2ac9b5014bcb6a184bdecee193e5d53683670972b125e6dd4c87e32f26478071f101307871c3126d9f852ffef07a64119a38cb1dadfe64673da5eb37d816c2a5074607ee4ac6490305da9657797f12635a01b37283d78b9b5667807e1670c11f56c90e1a174aa2f98bc93a85dfca92a945340e7281d0c75f95c7e4e8d4630e65e95a135e058a9c48f0492de365b1e03259dd2772d400353c887222db08607dbff8a6986b029b6a93cb77ad3fabefeacb7dd80a5d3d927c2e7d5ad5b4dab9759da1ab026692c246bbf75b747c647eb65579a1da72bbcdd8bfcb60e211f0a0729ebc007f9f2039a62972d4e430e55ba24680c3d6729116b30777ab9dad71fe64ae49ba0a539f65d14a44c4f8e27b8b816d3be4a6727c3faf574cacff4364cc75b7f11c43e5a33bc3d20f60c444cf6ce023a9c29e463907e9d324e0d45f0f609cb4685d11aae6047602cbf46fd6af417eebae5a5772c1ddb41ce0de075eaebba238bcc2b41aa667faa752d925cabbc5b482ce39ea128d940c4a53f106c3988db5c21dbb216146f8afd2d39bd91d943867a6bfd9117280e85c75b27347565851187350a9c8c8e2145f4486dea9d6d3e3d3ffe20bad7215eebc99da3bcfa3ae7ee1d79e443c0764de7884fe4e63d48858b90c7e9c1b72bb362d0a823f950724817318d0d90fd1864bc0c28b2544c82e6aa87edebace5b7b3ddb368be06a4bb643b022b04a20eab7388e1efdda8c8c08e9f5b24df65d723f601f36334287458a67164417969a0d4f451b70e59dcfa76a30b78d3b8cf846583a34d83342546c720f60848df6aecd289f331d1e70aadd98505c38c7c45572c66891e1e3d674ccbed92d6b62f093116d8e5038e1e9b754671fbd268cae7c7202e3c0fb73e7dc23a5ea518b631c671937bc37a2b7a54f8a97cb9a867e8ede302ebf28b0f9d81282ebd5afd6e622ffbaec6bd8ca369d7396ef98c8cb5d899c01cd8ac152942419443ecde503198dfc23c4b2db0e1807e04bcb3e0ea1a60072182d4b13eef85aa05a99ed1a6cdafe01fbab998e74a6d933db00ca9c9363024972909238ebbee6cc63af3bbdec55069c79b206345b9f56d418e5137b63d3dc9b5a7a8e3248490afc94c1f30ecbd5c75595ce38adbf94ac8b98bf4188fbc8b9e37262414227c0e0a2136665b54ef0ba3e7ec59dbfcc159cf1704c78eb9237a74d72b3b2e9d966c2eb1f3ce87c9936ff4faab18cd070ab61e3901aebc06126a52a72de0c5beae873b6188ab347e0e13866f6be004bf96e514cd4c28df435477fdb2e6a31ecfacc509bfd4f49a1e84a591999a4679218c7d407f2e3da05bb8678c57273561e9e3e2924f2f45c0ab7ce792ab3ee61ac6fd534062bf9b19e5f4bcf332614407931e73c3a78d7adc9a86b6770968605279e3f3f1e4ee6642331889bc172b4eba56bc72e115de871b0e2fa9b090f08ab80e3f6ef241b8b9f400e3704db72e0392c7e3bb7af4bd2d99600bce20eab835f2a3e3404767cbdd44f5bcc2c3c72a3e739dd757727b9da946fcebed78affc1b84b31b331b1b397b2af1888e49e725fb09148dfe4deb038d2b16c30dfca6ca489398e34ba6d01857e1e496344680c0c1cdb198822b7bb47bc444b7bb50aeaf456dfc7ab7ff6f188ff79a22707444073229ef7ded4a707e1099ab5a49923c31d08b92dbe53a80c0ba9c4015ebf6c55d0971c13d1dc2506eab8973be288177a959babca0ed44f270835d4ced9087872e6b73c496fda9a20858fe09d394d6e68f6de76abe587829f554e1bad677ac4153341fb5c6c9276c938947f74465b9ee85819639831551da4105a22f94b6c7372d234da7c3b7c3c832700898cde306c4901baf866b3de0f57ea18afd4e849cf67cb49d249e00602045c364e15676e7bb34ef630608584c204d6f3936cb858077230397a5d7b15a327a99ba23ea7ce19dbe9fafeb1c72ec128239e876a487ffc64d37d6c6f9a16754825f0429a354714a86dfe70003ec59eb192873d843d071072f352818cd0026fe2e749f37bed4d07d65d5138c111112d9cfc2de02b9f41f772019fc72f542485084da8bcdae66c4e480d800aef04614ab7dffdb3f317c93368ab3bc26814012e1d8fa34671275110ff937d61e6e3edeb3f21198b49a3ed3c4f2bdb2da62d981cc108dcbc1b15c70eebbe02c02cf54cce41e19e99719d528772f53efc38eda9de6a24cc26d89bef8babd493c3a8542caeed3d4813c950d53e06ac4d0b5c7b10606fe582362ff1b0aabcf72dc98ee8d16e2b9072c189f4c78b399937edcfeb120b2f3412d6a5bbfbfdb97c2c9611e2769b208e294008736a7214181ef23e255bab962d2997fc79f308e697ae529152a8e0ced80a5717c8615d5e6361a0b174f738548693681fe1afb43af0f168f642491b061820813c9781bd3972aef3f3ebeed05fa12e5c63c34a07bbe5f974e0eabf59a7fe3412bdf19af5725de99e03cab2f3920bca96714ab924bf2962466dcf93eb1a14a3bc7e36948d72a79d5155bf1e47efee1591c79eb18e193812f1fac9fa5a4e7d5f98ceac37f072273f376432b9244ddb02e820fc4a4f3a032bdd96d35f665ce23edd3db87b1f0b06b8473e9a755924ccb51c25b55b2f1c4d596297d3cb79e1cbe8409586121072a71e6a53cb0dabbfcf9e06386018e45a5b33d9f34c729afd4bcaa5e3354acd5108ef82e89d8d625c7735c9d8dec76a41729a1bafc8ecbd3e9e513d31184fcc72579c7b1656f4d95fd01a0239cbc3c34dc084698df0e7200bacaf78aa577a275db33ddce394dfcf3fd5785aaf829ffc4643f7040e33d7db7d162760a68a59e5166bf2f416f72efefba84c796af87b881e99f361f9079437248ab78e7e2c6c2672c4e7850fdfdc2bfa5a6dc261ca271bf70a68b038e0b3a3b267f19f21b97e7f729e719065e18ed57b9234fde576737a012c8f3c95a68e5b9df7478d5ae44b715e8a9f78e2396751088a3e9415be7837a088c7fdc03cf91c526ded1bd8b258f772eb4871761ae6bd66cc392d0df662c7d691a0e5b1ae4daad00a4250014c02c92b2e978e60ef8cb846cb672ba3f6b3fad8dd270ea214845dde1ae338560fb93a08aa3a7d02be3d02a12fb918bf74ad42ed9096fb9425d7503f1de4fd8d4ccdb8722122ee3ad700ee5959116d8e857984c9794ac8ce69f13481128db2653892a8728b604c3ff1c9498ab364083186e4de8cf840a840186cfc1363889fee42f2757238ccfc52edc877cacabecfa65028932f4093ac2ad673b608e567e069620dde7291c6c4ad7ea0fd90c1444981dd1a29c9b9b333757897fc6897e3aa66ec9cf672d9c9e5f5a0bf632ebe955f403de0c6c6776de6858542919035e05496a3a09444a5fc525736035f132e5549d015176afb36a9aa520065e983d0607bc1f9254b16cf89e0fe337319decfd613cfbb95cfa2074cd890e6f092aac437058ca0822029dccf22d6563f3cccb20f1ddb947bb426a7d660acf80ce428bd0494f61a33bf72cccb37814f4b8d9556dade72534db9a918d10f4ecc52723e2e57ea0fc6bcf7725995238c6ee4e0af0da241faa8e54fbf429a94e8a776307b7fce40903271842d9107f3b2bf62d52254402eeafa417265ab962ea6eff46dd7afa7af7a3eb34831949363bd8389d08e9dabdf4bccbe68fe974663001c9d9af0b1e6308d0417340c1f53653a90f34fee393c4f769666d2a21af8d0aacc8898924e6e28eae76cb072b828382960f212bd61f9e9b74fcda8846bf3c8a59fc574e82bdd086fcb9ca20083d99f65e0a5bd9e87537ec3b585c68ff4bee0dda151a4013113967d6ea35d1310868fdbb5a12854c4e3b32191ee4bc7e6b47d62c53e569ba25fc48573f18c6c8e715c9a07c6849d40a05a70208c9848d90f75065d08619605379ac390879d7240331e04515d927fd2e33438a20d70df17e462c8560c542db2379e2ecfede072978bb40dc9d67043a0ac86b5cd8a458808ab924f22f38f2318644a9d78dd6113125f885bad4699d7d912b73fc6726a5d2b2818ea6cdc5bb1393471547671a64fd0330968b8ed76288ba51a653d396d1ad1ac0908f4e5542bc127ca722106d436fb3f6d18679b865285f9c07586a5db68b6cec64a857c3a0719fbda0d1a15f15764af3c2c186a8ea90515a9905ae5b89713e7af72232e51ee15c92bfac54a5872ff4407e7c71dec1627b66b6597e852149c2a9a51dfea7f69284f4911d1fcc7429a5d558578387fdf8ede9d61ea071aab45ba6ad64551cea4f59ff1e978963e675ecac30fa8ab91ea9e3a04765be66290d53087b8adc838be0f50674623839363b8cc3769bbb760ddf5b6bcee9ccc84b14dadfb08fc414066893b599f8dbb6f72a12c2f7ad343a2f2f8f64074544219eccf11580804cb45405f159aaa871c0d723b914ad7d6c7bb04905443655e6ac7e48d0bc18447c26ca14e8b817ef433c072a083e5967023df7629bd88b40a50d253d6f8540a903c047dc756f0353ab7c82c856ed1c9224e2515f70a83fa20aed278df1afb10f60197cea3db2a9b77c901400639bc98b56f284ef0c13be55584f9204bac849e32e700e72d359b7d59607c72e5c6963e3317fb5d2efbff8d69ff837d36165b2e1353edd09da67bf43c99bb5c2392e3e77a914e34ef83a70bdfd41179e9ab3c8b99699d617a0ced55356795724d3268472b25d7ae600152b82e53d8d7fc86a60d48cd9d294b572b936a1a1516eab125a0bb8896371040ad93155a9ba3e906da5a8fa0d833bcfbe96d7b915d2c2be64cb9df0f95f572308fb04709f55bff5955e3117fe26b795ef54168083536d9a1c30b3a9482ba301b2fbbc5ab7e80e15b3e36719f50c5a393d3113ec79c135b722ffaff81f08707556afe9eef8ac9424a6fed71a7ad62b9d22ef8d31c89680a24a096c909ae073e7094a969ea2ac7d4da196f596b7b2249b88bd77f58977237c672d7403766971595fe2fe8af95dfb1718b1a88f824de514038647598c026720506ec3debc6d262ed7bfcec933cff2c3e67fe0db94f0b1d6aac66173476724b50705e218496b09895470627c53bdadda07a8f627ecc1b783eb1a2babc1c72b44d5bdb3761c24d44ea68933dd164ad0c171cab83cd4e1ca0063905406541727bfe2970e92966da044b952c2cf9c4a4a5fedb384c9fad2f36957ac000353172af72872e869626a9733636ec33d7cd12c3b867b26f3867aa8e08c43370605d531a8db51fca1a8bfaff62fc95e7127356613c521810d34581686290c51632767278d1787c66d8082696b8eb31410cbf0831dcd237671ed2bfbff80af393a30172409667e30eb8f0a610df41e94a3f2b7aacf7592a2f9eb7659e3664bbc9a8cf72fbdf4d64be7cb0608ab509a70e8c51b4c99fdd4f1e33dd36bb8459bb6a841d726a1b683ea92e3a21cf50e0d14e94d7c00d7bb37a8dfb2b654b082d611afc8672bb558368dbbe04e56e41d0eb64c0a62cdd6320ab5abbef11eea9722fa8501659352c770e5539b9599a07871dd8bcf6ba3d6ac63a2e2a5c514386cc595d83d37201e09377ce75d59dc896a281ab045c03d56c737ef3bc6b711bdeb74a36e63f6184c446d78a07a8b67a2acc5ae12495c44ce6b4bcb403bc911cc44e016acba9725b922e3f7e7454b04ed2c975eeba89f86c3b8c274b4bc2333f4b54ab4f99d172096a5646683015bc666b34ae743a90d8ccb1adb7820f79e606d691633a4c69276505591632cea34d6346c6ce7e5b14571799af62b7af490ae5ae604e74400419e0c1426f694759f78eb04aa9356042bc4796ccab323310c0e3122bc9a15f7f72161f2845f8006423873805862db2bdb5daf95c64e30597264742f170b2c16e398cdb266030f7441afc65840cad89ccfa275e6d187b10c048d43dca13b14c2e72951d310a6afdd259e9abe010d71f8889a538beddea92b343e8c4459df47cc2524ce2da59e4e9605ff45f948de06b194ae7a55d9ed4b4b608bd66ad0d2eb0e67246a9dcad2a4b8ca93cfc2a2e64096aa2210f658017777f6c3f54ecd942cbaa72d89b681d05a4ee43e717c6862aace269c703e5ad5213191292249be05d54e4057e09e882d232790679014bbc70626a73ab6ca116c40862a21f9c152a3845d9727a4afc8e05a718ae1909157a2df89eb51e5da6b4c454142dde7d7cd94529e82567a2c088fd0ab3ea106e733eda5da91a1dd61a3fba5ccf3ec992e3a7d9c17815a87f57436b32f2b64f48fffc572404c91d2d3d6af4e169fab1aa8a00899dcc7236f473ac69c010bdc8c1c759589aeffd2b949ea227e5e056b533f3ef222f674e0d7201218deb227e35bcfaf0354b09e755f5d864750a761c2b5fde2c402ec04d937ef200720797afd26e795017901bcbecd91f6701213705f76d84b1e4ea3e7234efe55587784e0389821a6176beb7f5e0b81acf6caed739ba67c88c2ca14005f544873938574270bf23866b56eea31e307aa389885284fb59aee83f37527c7220ce9057dec94436f4a2796bb9166819f3b5a51ec80f16b423d003b827ace0269747b7e6ad8b22644a7f901abbcfb2e9883ec6435f98fe94ddf9673bb5d899507b07d4cf4586ddac39a0804af29f41dab0a2c1bb8607191111125130e84a275c297d5f68f6b47bc60f7292b9b9d3176cb108ce8f9b9925b7a5d8d68fe8b6bf724ce0f40fc612b3d3c5bd6d05e29d5529bfea093606fca353b7fa612ae9b54572fef6076beef92d805713ba68be92b1b18b2f7d5ec4198b9b85c61cec3216c46dbdd753926195e07680f7568ac25d7bb3670d3ebb77cb566fbd11705f3b4ca5720793cfb82923ced1959a3ac43b4f9ba8dda89cea0a7c284bcc034c795e7b7572845ff9f95df0586311dadf23b06dd9f144c9571c808a4add459d30e5f814e00a30a5f99bc60b35c4fb4829b1c007dec87428b74b2e25290fe7fe8bda13b69242d95b591b377d2eefec2bc65010941d564e7c6abf14a05df7db0c69bbce5eeb72d31b014ec61457eeaf81d92c6fa9d985081ae4a496df9676cb1d657c57987d725a4544f734719a24b1e7204ad4fadcb0d6147e5f99af56d313337c7f0f564572e859cadfb430b8f3db5ed42a42ca6526534db27f0e5c6ad3570ede0511b23a46b63420700fda9ba4abbb808cc4b0852c4260a9169ccf2ced4f7b16e7c7ff4872ae62111addd19ca33ae2d768e9de289b711f75c094db9cf8e16882f792f247085e19c84a6f1d9dca5ef34d2dc2b9c8b36c08576f8877977edb72f466555e9372ee7055a6cc9eda2f629236250f4dc7b62475b97aaac01c5715bdf0e36d8f51726890eb8c0dac786ff167066612d75234f74055e0868cdcb125802759855931336e9ec67bcbdccb5293600405dded3d7eb03a6e2f8f69e31c1b73eea888ecfe723504f3212896d018fda7b7e10b9fcced915e049b431b6e01320dbf4c21e89d72616c50ee993392fea81f3b6ec89cc3a5d427d61013b44f5b53d843e3ee8ea24ab2151b6fe54e3e8b03db7ad1ae488b4bf7b3d3841226c0d2e3d2941f4a9a434bffd97d84edd84cec02a8f027c4805107bfc878b5b84e51dc357fbccff9426a64ad0ae646badc9128e3a99f06f7a2586581daaeb4f8b423a6cfe9ac27a030e3723146d87adbbf66679fcf913045df05bb38940cfaed9bda51deb08a79845fc372086b0b2dd297308bf799e93f5fb65821bbc8bbeec473559bb46047c568d707355a4189dd11ba9ba4a208328abf54be8b4889fc408b8352f248c47c16f3e81b72dea9970edae226d04eb0500fb5b8fce83abcc1d90ae01b56f385d4d2ba47d3723b3b8165e9f8febac8c81a3a0ca0514a5d0f31c125d94f0025d87e716c609c7214152894a138f118f34786573132118bce3c797594ed8e758b328ab4d70e6a13b22b9481a72809c5bf8e1b3957a5039228677a04a74111ccd5dfc99af7dbe15462c86b6134b737e83e7eeffa758ddcbdfcc477d41cefb0db47e7c4756507ca720ac4d7795fb01f58d540ee3310eb15e37b416fc898243b9046f06cd299f7566be0359fed545bbba5532121996df66232801f94ccd67acf9dec86829e59f714723dd8b2088fb831b44318b6bf3674b96fbbfb266511e127744154529da0262c72444e39fecf833fdbf515aeb8a7511dbf5e6a304f56b8b977e0138f89c5edf55e71d0a0c9b302f5eb53a2590ff963db7c243b4aa5c7318c01d25cd976b493a412cbc30a1592bd1d264f3c6b26e89ba30de276faed2602d9a9398504c35bdc4b7218c03d07662292c21bf95d56dcb7474eae0ec8c0daedf397a6f0f20cb555cc47915bd3b46dc3bc6054093584295635642455efa50fc56bbda3358c605f8947727e8e15b9ec0749de6fcc0a421068c2267191b7f645556a2c53b40d9e40b672721cc6f7883cf89fdac17e471796d833c4ab10726821ef2fe308ff0b7fe2830272b16a102aa163e24ba9fa9e344bedea8458c35b580caa476eb214437f9afada48ff656785ea4236995b0b5abb72747a38eca5e3d0ec67e36e9b1cf851c2f262723683e8f5176cf6c4ab6daade9c3387f729b96f4166f10cef342e974bd045e00ae1777abf527703aa92d23a453d4d644915662c8615aa74fe47e1b60ce37d5d72b668a5ede92955baea6d929da273d42ee5b961f21ec57e11f72271192512257220e43dcde0567f53a9e72442d6f63ca68dffb3449c0f95c6f91606ada906c172008ce9b326118453d7f3e52748415e6a87f067be3e10bc1d0f2156209138623ac6cd08da1419a640bfe693064e7be45e582daf0b2561ca89f55177e98fa03e4f2f176fb707ad7990c160ac17cf7610dbbbcb0761c3e3886aec6d272af4730472ddb9e2291abf356eb365d5eb913ca74fc925a79561a5581109329d0b19670972503c000f632abd9e73c7684ba8386ac96bcbbe31978327dd6e22adaa8bf0951588ce567892c04cdd7f9e8c0b691854d35140e48b1632cdbec1f316eddddee75cfc9f141570508c69aa9f46a94a5de30537374c81efd76d571d2a40aca926b77257f1fd91f82e023cd4cc168ce64ab3ab18cb1ec27bd42d5c6ae8033c914c9172cc96dddf6257693fbb130d6bb8caf833e29a5f58b117b0246cc48619621c5b7218957b6c27002c0d2b1049d1518aac86e3b8492a918d9fe0ff820db13a05d97255ece0a0b72869c6a53b0d74039162769cf0188e675cf5caca53e38f03f8a067d037e8cd8202782ca6c2237e0a44fdc28bd94ae8ea769b8c276a68f99c00c672c9be97a090310e41bb293ba870e3f02d91a91cd02d20edf3fc4854364925410e7a519e2e95da67a75c7883ce5ce0a807462c2c18578ac0ec15bdce03148e5872b0c6fe57520482c87f4fec026f097fa50e9d66025a8f980d98d783af7e9ff5725dd1f552c61073e311585c14fd9e11b548519d9d8f2decae1a8472414a5608661991f505f3223c867bbf3be4fbc7a5686a27058725513f6959e14ad3135da372fb83a4739604f1852e5582da646f20ef686daac616b94cd42b37f7d7506082725650551073a914be9c65a7b9b5723e20d4b48a50603c349e24e5b22d92b1c45be0a9e56f3e9e8ad2b59281995d3da508d5a166ad20054b5de50666c4dc2c09239d34a12f38e1914f4f2c916b0746fab3f97b593dbfdfa32eaf25e012662de024c8629bced452e4e9524b4f299abffd5b6f14ffe2395344b63f858168670b9072d035a9bdaf3ee9edba274b94f1f82796486c7a81a339f1688fdc82396810377221956af1f4e33df7f6db1fc6260e35e1f139280a1e16bde989d3417d30ec4272507bf3561a6e7d97a4a00ba27d63549365e4e239dbfd3b0fb8410519541a306120ddbe413743740aaf45f1a47d3504a746f4794da18bd69d255a0df97301b752d06c0170cc40cf649db273e1ac64c4005233b1658232a09b37a85d04c6186e7236cf07fd238d214b27137b16bb6c412f85dddcb8c1bc95644316ff669069f33358578b112fbf5b53c212541c450cd5fb8dc6ff0bb274a89e9c7c9be7f2e6f441fef0ab139b902ba102ca6eb8b3555d6a12b2fc1a23763f5283d9c352bbb0f572db8fbbd4c4356b0322e9b7b9b044df9c3953f1690b050fe9498845ba82d730721f9272ca9dd6144b9429b4da5594612182aa596a04fb4b8fd65f8f72c2c2ef725fe5ee95b535ddf7923790b76e75bbbbffc20e7b659ca01509d6cf824870d0722bfedcc2397bc192e292aad8b608c9d5ca6cb139e92768bf8bd37ede9c2a0b72f9b808b29058d9693c199f356304a88b4a48e78203c6f9e575a4518784db127200bb2730d7b5f630893d0e7c7fc41ed7e95b2cb63ab681693cbd98f0f3233f23dd55b1206e83990ceb21ab60d3128c7e7dd0057539fd9be629c1aea2c6364022665145ee590d58b05e9e7f14f656c60fd48264a7e8c24482036c52309379e972f4848e7b68a1eb828b14a33e761c0cbd7db26b563c69d1c7eba59d6df732b5671620083b179d7a2bb43e42b197055b5dd9798362f33f5243dfdebe062f35c672449296d91547d752480d1c08e879d692abdbe171fa0ff0c1062a332254bd07728cec1463551d36016025d2e7500c5be0f2f62c0394a23618797bcd5d7b75dd0ec06efa6110d830d89e4bff553cc6dde723844e1948c3ca745bf56366d7c993724bac62661cfdd277d52d55094e560e11779bde586ff7be6b7f24bb4c29a16c0198cb80c8b183ca4fbdc9b19ea55bf36a9a6deb4edd86bafa97918abdff70422c5445221e402a898543d703969fba609744072aba857a1732f092714ba5319e72e6bd84df6bf4a1fc2799655fca04ea203655a6f9d24e08a73f06fce5d3dfba7224fdb932bed5ff1749eb0fca000015c0a0c02c66832aa796f7329b3fec280172a0042f32e50e2fe63fb9e40ddd3c88ad5485c9566dc9d20d41ec569c46fd021ad7d4ea0584b12648aa47bf5458df8bac5b59b30a6af3ce6e187e94e33115bc27c46a03e980193cd62acd51722b6509b98df9f0e944f522ad44b99b98d42cd37268967d805710d13b0d2539cbb03ef40162b5be3b1cc359ec680c0ffa0d79dd397baf6ed84bea004c7d5342ea71f005358aa720a37b44f8c866377d40d83c97726b75234fd113e1218ec7c0a34bfe1a86ad8598c293e842cc5ceb81a2d226884a33427ed423e6a3b71b9fb3828f2b557d006b90865fe7b60e50e1431610f51209a82137a2b8a948b6d26b5d38749d6a64345477af060050a748104ccfc68c5872aef9bc5339fcc46fec153f6c3b25f5a49e7bfebeffd84129dfdf236155ee3c5e8d566d551771389f1275ed1e6a8bf24d864d37f1d5041c1797383f3f47cead62a0d18f325ec5420c1bf4eaf4151fc8112f0fb53a477f759da6fcd1f01e686216b4eb839b063d03e1d9d77d3a95fcf06d0632f7be36a9e7d5e309e45cf6cbc072f1671192725056260c25f05c28608899c890b600ce1f188520735d299be6c672966b140e3ad99a7dcae407ace1216512f30730d1f7747127cd9adde8f753a61bdc1fae8d99a42aa34b6e998e5d9911f650a74095aa33323bdc9b1af07ee9f072dc050fdfb444b83dcc6086fbec38ac46d00087407e5d2bd6e4636034c932605d0c86e9ff1f63bd6ab11601fdee1df32a8fe61c044880ca831d96157bdebeb67263128ef8f2ac9f9053c649ef2495aeb0ff0235eccef82912ec8d2f4021384d72a4c49b587662df91e265525f0c1e7d68895a3deb784e90d509c2bb000eb04e727e68a81615872c6560c42c318efe4da9724a0445ac6697e0fe76cf1070f8035b8906db5ffdf932a607c554e1c3725fe31b0aaf0fa16e5d8db14cb869b5dfcc72c5cbbae6765e3037b13fb942b7cbc11ccf8481a9dc4930ee2526910908d860725d26b0f39f56e5d163785376cab69a0e71601522ff3205cb21599beae8bee9723267999202f3fd9e7c309b9f0ba630ee98d3ba6b30b5584b70b0658374d5a36a60e13361d9ae9243553b22a06ea2f715858b54ab9dede0e9b39e1124661062042e71bdfba83650a84e8ce00d991c3a64330d78bdcd8dc8d8add717e810c63339deaf2b4c8ad9242ddf4ea387730927eda039a87f04e51184afac8d317ea1b57272e69f3c17e96d459c30f053c73d738bb898903caab70017a7a0935983624772e0fd1a5a1e42a41df7f68a080f624265ee413d8e27ed98a22f254fd8974e351db4de9eec05f508bd601ddbdbf416d004c86f9f9bb87aeff2a8bfa703fb89ad724d2c3388ebd7ad41bcb24519a7e272d956d67a32c5b6e7f6ce0148bfb19643721b266658dd7148073b8107d788074b3d879b074df4801d4f01dea44be7354372ec327a2164bb1c8c7b961fd1806964d8e75902d1e1fcec4289bc025aebe76d721bd660a4e5dfaa83455c86a2b481ae4afcafe5d103d52df20d4a396de2f2cc72b04200970d1d6e54d76791087b56a50ec2a09e18da8b993ee3f605f931c1eb3f8d53f479c12a42557560bd485f51b0970d424b8fc8e2d3a6ff6ec40d223a3d72bcf08abf56909192d373cba462ecfbf776256a9c8f817709f67fa9e8584aa9723c6632c254f2892bb57ce77d46575ee423856814dcb50583db4f75ec62e80a72e8bef238e193c7d430161d904167449afc6b37ffa6b1e6b127a9fd777b77466d490b8ac142828ec563c10089707232110c915438826b181b52ad6389b7d1723875244af29b2b47329ebbf777b0aabf336202c2627652e4baf060bcfc08f1fc587522aa8fae7a5104dea745cf8fbb4488b8e222e035a99f6048b5bb2ed5ab65572f9414448d319bdb2091a6165691b41c6636e2d2b8a3b5f21dc1fcc6f3a1bc7223e1e90ace17804250d610571c1a7d85814de7277045b0effa8e71971843a668679f2d6594a23a6c341e5e3e78a043a8ab0b062c9abd9996c8c89319679c0172c8e6726f536e580df44ee9af62d7b091fd0b9f4ef66ad54614f726a9229fb2724920f0ad583a6c3d84b1b1342ebc20c4b0d715e7a9c37d4fa5826c20e17ec34fa2ed49ecc33ad66f7699da09446a2d16dcf4d2366b352c177ad2f426523bc172728888f4d107978d57957dbe79c2de2c4c9989c70fa7abd34fb25755f82baa081f59535d604434310f4431b61f919ecb483d7c54da9cc2e330bbb2892a264e721ec54961269c907f2e6aaffb9a6d82380c214eb813a32e565dbf3b8ef383e7477ab619a2b920e7769699c6cf9d238c4f88b5f994462616cf0a4e7e8b4fcf661d07c4b8c3a6c60017701f3d6c05c714eadafe43215f207cd636f740c3d0d5d1724437d8bc26e0dd0dad24ec2a8b7ee11e991a2038559b0b98b4de402217beb34e4642a5eb6640f8bbdd419ece0e99cee0cc5bee3146cc244952ab5c5f6f3e3251bd52911cc34a4e45143c1f1d143535db5661ee0cd49260e1431b6b28cfb0fe2ba4c1b4c7929341797ccc2315d66862b44abb8e48cb1952cf8c944adf61f0df72a2b428825f5389fd8d1894f3f3fe4f026ba73be8c7f145d22f1fde3b1c52585d1d8c77e6004a87ae7ca3a40a6a98474740ff2dcd54c9f6b934f95361896672722a087406f4d49613a21d03ff79c5ff982cd5d31e1e0c854861261b4822fafc72b3fecd84436d82589d4db635c010a024bd992835028153b94d8e074bffddcc72d9e58558ffa45d6b57f0cb6a84148654d6275687c5850d62d5932ae443b234016b430944ff0f49f025ab92dbf996051eca39a630ff8bfb545a2c6726798f3c488da32ef1891554260e6489c2f4c67b4e5ae27175281469d9554ebabd7bc01242d692cf22574e983585628e270d4a7bf9f8c04dc825b26ebabae5486a2fb509301330458a8fd7c7c4e0759d08f3e5fadd98ce3c6ad25aa87fccf85127ee2512722c23b105ab270b37f6f82bf82cc371d0a0bbc6199fbee91a8877302928bd1a3f75efddaafadb247ddfa310a0c3f65f823e1586891981870216fb1f784944d703d6b7d1ce9481da8ed319745bd1e7689328110607faba9f771bf6046958b40a06abdc0739b42e299661abdaa663ea7ec4711a92f0ef38c3bb8b3ca754aebe4d593a576c8ba0079e86b52e798532a1893efd26b6098c9134ce675744ad88cbb84f0b5d7fe413171cb76f211420ddfb7cc6130d4e7543b8bc2bbd96997d8259641993b41c56cc0645117c4b2067e76e63d8bc90d521f9a20bed2777b6565cb71e725359b3bf630839e56d22427e5f5167fe01a8b231ad9765df13f685441f797a110362659cb4fa1db421316c7f67f191c3c1318b41a14313e9464a2b1b4f025c72683d4fb7d124fd970ff125591424499b5047b09df96d9381123794d486c56f523a76b030d5596c0803d480260ce77e0478ee62bdf65eec53463531757478377296de64360feddcb914143f6cbdc9539b0fc2ff39af425a0366ed45314531cb728742fb87346db72a37e562f159c9a52789ba47ebbb684e5d28ba862b2632b41a61b56da84b844def5c49fbd48a1dbfe9427d41f813574e4333a4d2a3f74da0095ef6f5f0f0e6d74da2ff8b491ad56a8d1d76636e13eb65cbe013219389e57323ba3071b8b35a4e811a307fffbc8f562bb29181ecb66447c8182ab626bfafab7244188fd40b74d6f21ca9641b16deff017a3bec9d0d4633633b95387fda3f287231023450ee1e89e7395e9a1dfeea321578bc70e520b03e62d77cc0e344f5303b20f1cf1677ab4bd763b65d7e48a9be1db1894c6ae828256c9b7f6d2dc3a2866a1609dc3c3aa8d756f0ee76ddea38454043d5f21f31db67975388c17ec82d92729205e014742467cc82d38a4289ded5fb208d736f8dd1a4e4f8fcd79e31a94d72b070fab7939e05f2666062be25cb98e735090c116e6a2397a7ec4369c1680872ac2df84db0d616e505896cdf63e6af115306c6897883f53c02dfee0293153672b60b07a089a12ef244db9df34f1586f50d95c44154714257f8e0db797a788f7247ac59d9b0539ad42663a5fdae8c1e45a4acd7b00ae68de2e3cdc0277521ae729b9d92ab8926e7ea634eeff6edd52293cad7012a77edef6f990216c9e93495725b68a51b4ee93057a1b6e2e0b43d41ba2d9c5e860b12c08ca42ccf583ef9cd72cc100946e7138f10c33fbe1cb1974d7b08ed2a16591a2ed4a74d1dc01636fa722567a1510e68609ac5314282d55481c2d121532cbfa10cd93d6b1b19729a02720540cbf456c2a830dc04cfe56973c6f6a767cdb3a2622126dfdf616828edc221c92d18bf804deb43a223e114410c6c40875001e42ec64e9a929883b07ac3fa0f336b090ca59bb7acae55298f30ab9a78d8895d3271e84748500efa5149faa47283662e8740c735eb72232cfec812099ef552091a1003941d0760a0689679286977c2e689ca1a3bfcfc20811f7fad6baa8203b42db77b1e0e96dd42ef1fa04c72adb865e463bb653d4054eb10a14d9779045da714f94ea999ee74bf91217c2c726554b46dd78fca184dfd965d4e5f31ab173a6739a9a4472fce45a401f3d19b72130344b8f96134c35eec17720151542cef270725450d389059b5e3e360045750bce68d041c354f0b0988d978b1332ed7ae7dea8dd3baa1271dfcdc8150588c726a7d94e029bd3c56cfeb32feaa1b128e7a899a7e2b74bdc0ff59b99f63fb647252ec57f7b0ff47b41f407c7ca7c050e092a83f18aa36ccce11be117be09a2372a746cf586aeca4cbb5cea4457b55002fd3dc89ae792234b0f43565e5faa31e7259c0863f11b9627651ba691a914b580d2d8009b72a6fa3818c784ea67c5a532a3985491ddf6035064c28d77569619f57559853d22d66cb7b286e6160e8b42172e3474739cbad61e7e181accf5551227200f16aa239bdfafc4dd0de48d478bc5467c287c997ddc24a759389e10cbb2a623f8a0ae945ee0e3bb4b6eedf4e4dc872b28568bf8a1aa18e3b769d2a58a9e79e400d555c7aeaec24787a973f55977c72138620ef48de69b82d2b5afde423b5ee3bdbb4375f05f1d3f02851ef17773a724dfd5bd53c96d757884c0cf57f566e79797928cf6dcabafecc9643f7ef0ae65025bdbb6abf3f7859d4da13d94df2df82add7b476f12b3baab414104a0ae1d572f8c5535268b8585d3c890bead57d9363074029b50318f909306508dd25ef3a72bb4174cb22bde07c8b713662c92a9f20d8388bc0c173831596f7af03e1f8ba7241610098ec668efa8225ab13f3ef708e8635f149283d7c6934fa2c9c73234a72194c531bb891422d5a00b02534ecc8ceef3c5ec729ece9030dec0519e394537295aa027752363af309aee71341f66ade6f974cd69a6a5efffe1ac64add03087230cfe191eef8efc84f962f8307cdcff9fc9059e805f8708cc18940e1a02817725ea6f4040178969909455bdeb6d0c87ad5a36c86bbaa790cb72a707d15ff5a72ab541727081f2e3302146d4a564572dcb5de9d46c95ba2ab63425d5ac5861372021cedeed2812621a0980bb9784d12cfeb8b657006165c8e50b6dad061baf73b36bd20f3a2760b27dbb44b12c359f29df1ec83433b8cbbf25dadc9fd05f9cb6777f0bec6e3599c8e088ceb5048fc76fc9bb2e2dd74c55bbdea2b869aca3ee972702069a441f6a2e09e6e742225c11a750ea2fcbbf1030dbcdc72445d6d5ed972bb1cf50e1815924ebf9947fe8f010de5cf62767d6104cd1c47766c8ab3b4e072db9b5e6aa7136b476586b011e99ca1ac53117e678f161b8a8b03cdba2003ad10693b4970b6286690f90d81e8709baed3b11dfa775589fa14cb18cd30085afc7265f18728fa229c7cf756942e1dd39adfc997ff731f4430064995a8ac92a19c72197ef49a533726b9556ae7cbd2c2b8100d61f704e68c9bc36dc0d13b74b0a572a2087f9e85e4f0630f4a7145bb968badedc688414514be3f8b1da114616d824e58c2668b6ea0654125e9e45daef17bd1bd8f83051feab92c217302c6b4c32b721d13de093d701550640c51884f4d58df67797a674e2c379b8ea8712ea0d72f7225646ca4478ebd361e7adef7d4c3a18356b94f5a6cf97edd5900dd6e1b1cd672ddfd667a724e0ab2076a568c7e15768785f218a13f9619e7e38a5a5f229f9c6a3ce16b21f4304be10a35df663cf2e343e7e0c460701978640f15f0055781467223251843ff3fd4c783393928edb7dc509b4c507c4630ac944e1cee1344239819ec6f4969728188138a09f3998008487e4bc1a8c3f5f203f577896fd90714457283704b55847067e83b0b4dc233b407cf0ea0c6425284b2ac07d1a6d1c313b544bdc69fd1dd3841d97eba4ba637c15f28ef1e3e9bc72fd0b985f10653ceaf24720c6741eafb85f5c2ee8b7243c5918122ca6ebf3a3b343a746439410217361672819de6cddc5aa2c2afdec4a543fe2545ea517450284ab714543e3743228198689ed0f9a00ecf490112ce7e67e55873725c4fb07ea5dad7d2814c28406f8474721493c3e2a95a01060774bdf312060c8fc6c799a1b2ebcaea9dd16092fd13763267a09d5e304ac31772b3519b84209ffe6bb2091ab003668181e0b236abddd14e04fdc2b52b6f00d3fb9d3bf85191532563aff342bcd041fa33adeac63f3d2372d8b20198caf6cd460ef0b181347e6f013151125ed15af31444510b3bb2ba0c7213bb3bdfe190a4ca1de3101e1f7e18c3d7b3402b7bd5c41101a3636e4857ec72e3423be2f17a7b370a6cd7e9ecb525b98f94abdaf4c4ba84038e92cce2d40a2b74bdfff259d2455c5c4ddcd162c59238ebafca7563d69688f1f1577818d7354bbf57619926381fab894fd80241a991732adfaf1438b3961424607081d79c3c72e3e6d6acbc68b93fe2cbd63c6cd3e411de651c979b1e03d5619993ac0c858872808725cd7d36da52dbbf6209acc37df26e599fa609015a0392fa426b3b1e9172ac8e3e106163d4ede7d9796b22426e69d15a9f9c08ae96f2e937ca9bffe9fe1c3f4f14d91cd6921e241452f13e0e497d228493ea83eedd308d5afb6f5f895a723769c55bae6acad941df900ea7ce55833152d306e711fe08fdd1636fb332b800e7dcddb8af22bcb11f1010d8909145ccbcd642fcfcc68a67af92f68b9bdb77728448a3ceb741b034bcf8cdf3615221a8e826d48910c633b8323352a6a7606c72b88ee489c309b66f86e54524239cf16598bcff1ae55625bace9d2e32c6289027ce3f473ebc06e90a7b01a1d1bcae2dec7a226d2dd643bed320a2b4d13b3c2172547c7a09f75a044b7dc16bb98e15eb557b4daea353c88bc44d1e2735d4479d5c3d4c4615528df0a1d4fa56830eb92d2f92b1ff5240711e6a6f246fcc7a009e1bc2681bf1a0efa7bdc3d411961fbfa88cc1c5124de3f2ccbeda43d1e7b6dd3f3434df31e493141865dbb941dc0ed940e8edf94cf052df7d34a8731846e6170972bfbe44b5d4b85d4dbe7ddca8504c2cd1dabaaae62defdd1107e9cf4319980f728e79ceb02775912b45770aaeda50f03771704dc4ae6aad6f6040ed5e2f046f7253fc5ee725187276f92cef1f4cc7637c2d64c63813e4a5e0265afd8d10ba807281ea40d53e0e11ec0b99cb4ff47e80a1d206be46895699083f47afa3866b6c42fcac9380971034545cb5ca96b020edf0b033dd2f40d63ef62d9edcd95b705456eb2e47151788c4d5415d50a6cc3f4ed2aab3d74d4a5c98dee5fb069e4c728d5ebcadabb69b1772ab03766bf3bc48277d7fc1742ab79fafe71df4feec58959f6bd64a75301ef8e544f5055f21b831ac4eca584c4d7a18037e5b03b6a49e86f4729892e8a277440f76f43edc32cd0e450cdcdc94b87286851e888235eb9d82270f4947cca14dd4f0efd86f835bb11494c851a6eb7655fd82cd6b0d6a84693eda72384cb9b7808071d947b49f4336fa06df44a0db9f97abe0e3bd54cfb6c5b54c32e3ba54ab774fd9e543c1502dfe3aaff59569bc3e439351548d807845ab74447207c95b0655ed8d2671eaef1faf2dda6765981c63d521b96797e1cf6164cf7072687025edf1bbe8e5cce7a4caa5f72b482cb35e8703941a468b7ad87c66cb6c72cffa5022f3e3dd8fdbf4f1c3b43d9d5c1252b6c2c72ec0e6e219daf1c9ef0463e479c5f7bf1ae3f801d95efb63ce1b819aef8d580f5d079d7564fe8f76412f505d4b2a8c1d95935a150eb3bbc1fd34fc2acc04eb8970c69e9194cddc7863747272c32d88ec08cbee9e2801fc0f780fafd58fe6e5b7246b56ab7eddc9b49a7a195f300b97fe0a7ba903bd8309c94aca16a6f1a0b3dad23deeeac7e232a9ed9a30e37342ab1d33e2b528f50e95f45b1d26d372fed356ab06c15026c637c8287b72817452fd98ccebebdc0f449a6941e04ffa7963898653804525937d08c51e590808b7f7971ac025a3330a5549c32dd1d718763f6be27416465b6e2f52a300382ab29d48e92e63ffc5ccb160ca27868ac2dbb6f3789c829a54dce8b3649bced67286d95d20d7563dc7e7dddeffdbeafa1ee187e699fef24843617c652d8937d04cc41dcb7e5557dba41f86fe179f0c47374edebd54f560666fd23441bd5313145916b37137524f9a9ab4f697c873c768d0d8770521229bca2ee622dbf750ed51364f7b9943037ddc70daa2e2efd431e3b5e4c6fcd1739c905971020bef5bdec07260cc6b9337f4e2bb536d621fe5503de3f642970b33be6fefb48ca92253805072c8a352914a49df9bd62ec10c08d2812e2b03db0d391026330bd482b966e3710c8d4a6f923edd0727ec4b6a03e233a128b11170a179ea2b0696c47b3d8266af72a224a523d351be55b07eca7cc0a843d0739d9249e935dd439b6162d10743c9723c28249e53fb8901ebae03ab9bc7c6a18bbc689af71d58404f11bca0584c584e666326a2849e15230d5dcacb03ca22d64e4f97bbdc8aeb9f0dd3926e95253c725eb9892e737d3483a51ea3f8525e5a02510bd012d8e5f3cde50a4620be3a897255234679dd8bca6c20503aaf05f8b9c722cbf1f2171f82092cf1dbf418d0430f7ecaf1c324673c5f90e84ba305f306ed5717bb482613ce77616601f8ab33e96c099a82a0e65016b9bfa793baa462b45477b98c1d247c208e6833fbb8323504727b25ebb45d391929acea71984af252ccb134a01329a0ffaa0a5200cffb108d72533bc5a9d6832c01e866325f35fd89054f3e4d4bb4e2d93bde3b4d3475b9ed729b06ef1aab69c48ba847146059fd4711d129279f5d6200e80f988bb43fd2c072902a6d19da78798424638bb2c3fcdf2ee31515ed860837d34e2cc632d2ebb160c71fea2f37ab4c2da8f3f5a18f615b4c0be88f9bfb193468df02cbbd88f6db6bd0631b2b99c26588596c0de81acf99a284fa68098422369ad129788097eb5f7297a0ec6311f46f7ea1c650ec668f50aa5f0245aa1c4cec9aca19498b42df5172c399f8a6d96f1040c32ff67607f5834febd43a991e5dd47cd037c0972b7db9098923b4757b36f7112ff9f7da6217ec18a0b9163e219c65bcf04a13b674265d5a5c64e22c295c09ba0d33a330bc4ca16ee3b1f2c011783df5ca51d203b9903c72b94b6067308c6bdd721d0c7f7962875e7058a22e1055e71773b337854674af5e323d6ab8643bf85daf58c4848ae6a7646af1ce51a1ac9072d658eb854ea24727df79e1ae9e6e47a3e83554241baa3a2a9ffa9ebff46b114181b1662e9b7ba700d0134de24df1549b9001c44240a302a2a53042516bd845af98a9c0500cb46672023574e5ca2b8d647a2b692c5132d0c1813050d2c0538f0a5afa96b041b4a7724d6d5a937a5d5c2501a060b152a0b977157331d021dcb29a118c7541beaccb2a004fe7ef625133b216e6a9fa14741392cae211c7e57676bdaa8c1b79d266d500d986b88af3e31aa413e310061b36c19829606a7034c7be7f65c734bf0cdb0e72a93d706afaae687c4e74b1ad572143d60a157db904f95c8733723ffcec2a6c30a336ef784e4641c1cdf13312b0502118a09e7df2b2a1d26ec690141630c84272f21e9dd5e7b97761ad4142405d165c37fcf05ae7c6d2a1e3242c7835b34c9772e0e1af1e239233f280b8e5d9d1c8d38a1d0fd96cfbebd0b3f0c14e31d72d304c5b20a5f9dbbe489b808463449e6ade6531b3fd66b3529ee719173803fbe08f72c5f001f6412aa3a2f241d25ec03bff1a291c9fb023fa6cbb020308da10b3f16aa37c0523b0b544cd9563a95a0b2022bb644fd6c54be848a7f571018bfd4f9672f40c19d1895acb59f5c24146458221d042fdca04c7bbbc546c71d98690569472f92ad12747973d7570297ef079b5e7fd902e1eec4ea2041a5a629abcc98b3e72cee56708fed52b59869e51316c31e6ec174238d1e8aae0841db93def780f553bc7a737a132ca41ef3e7565ddcf5a2d93a11d10d52000831096820bd9d745ec72b6947b8e0b972d5a8b485f85a064be2e788b10d2fa1d6287484ff3b4fc4ba31b6dd483656bb9b17bb6baa830cd74adae1e36c0cb6921de1e17027ddcb3031372ddba292095e05c91db81a3ee7223d4ff75ebd3cb25273810bd8b5da478906572d712689e48c37d8e19438b90dcea4602ce987ef22950f9e8c91fe58ae73b0a55a6becdf63937d3905b4e6807d9a712cdd2f6b5b2b04163af89c83dd3df89d172bc67d44733d2d7efdb9a6ef9d5c2e4a4efb95c667f573fcc2e5196ba4e1a96724262749bbf5146328ec0bd12a16dc926575883b7bf4f455ecfb81f66697a8d7213e3fe7233754f0197bd9b1570c38782c2de545758c14bb7a0fb3f4c04bb703358c47e9549da5944b2d46055c78206c7bf23d9cd8eb107aa15bf7735bc45ea6559064aae114321b5152059fe8265d8c07f46514c9eba47050475281fd1857f72067a71e31b087862912865d5615b08464ef66833e2420ae7c533d7a284d27472037a44561f5b06e993e1db8213b138e731bf5a4b737245826522f05669b9ba1345a9d986b873c05529539b96fd8e4637141ac8c660c5e4790997d09c7b530301c484fc5104c2e783f0a0ec0ce6c6d88ef670a56a08501b97e268fcc19c12c672ed5b0f2fc12efbc1f2b45bda60a893ae4fb096eb00d20fa18721e2ba20e52572b2159fd0f999d8adee43fe003c955ce49e3573780e69944b929fd13cbf25477246e74d6d33ea74c04347ddcca0977c2f9632edb2d99dc44ca44bcaad912d7e72467e35494cd4f8ec0e82b92eed7abe70144bc4a73da52adf9c6657901962136ba5d71a5946de03fa69cab24f1eed87ccbc5c235343e10ef67f8206b23eb65b61f447d3f38675d7a497f787900a5ad5faf5fe651e5ceb9294a1a5511c55ccb5725aee82e8cea347808d33f7a187f7f14c0a0dccd631b289ac0f07e7108ba2f472e0dfd2580ade47dc9b5473e2e3b83ab086ba58f7678a13f0063963a8ffc713490fbae18ea4f4d0b6bc1439f4bea4deb94a91851f936236690a53a03bd3e6f345bc31342d18c2dd498630b7f91aea6a0c5f1217e2f73df58e4f8f78d2504a7a58e055e36291f8ce8bf95fffeb2dddc2d38aef0f6dbadd55621d03d10bc70af472b79906b5b55fba9f63eea81a412b75b401497a99737eb983423057953ff3954e6956d51731e321dc6db3723e7193d1991b31e8d623a9af479151175ce72abd2bf2419ed085200e92a74b53cec14993589bb73b62079ec1cb0f48a9d48489ba1b215f453313e1d01deece930737da9b9d1fa1af618aaa471e7ec5e2dd1232e372c59ed7df5e7d8ccfbc785ceb63f9e0402fc0039ce23988ade38107066f0c42724ac90d42203a9d07c568ab5073b6f35ea14370088576866607f23c19d096216ed1f8fcd59c28278c8ce93a397ff058efca2611061c01b2d433a89a6186c31a72660a468b5dd9b67267f816631894ee179e70d28ae93786e48f4ab8cc40a0442c3b9daa0c067b0c69c70ee8d327c9b7f6f46433152ced5951d5d147e2c3d6682b654314a2599884dd9927817aca95f98d61123f743a4df7ac87133d59826af57228cf063014f2a5e71a1896168cdad9e86145b928c96dd7a0902dcdf9b2af662d8112cc71e379a5a0d5fe4b11eb9270c31f8bbaf986e2b4ed7d1116038e446b729ab5dd3be31e0f6d2a726ef93b61b6aa6074fe938eeb7395d6d5a690e44e2772a52b1c41fc44f1251c6a4748d010f67d9c0ca689f56441c2e0acf71a09ef6d7228ec29ada6e0bbbc8b4437b8e5d97bf0b82a8e6cbe636c749b8de05ba7ba59723b9a2deac42f728987019bc94b96b5a59db480a69b974a3a1c99e4b67d2891375072872ef0f43c5affa685f96da24d29ed8fbeb44a76e86404a85fff40a65872c161155f86139a5bbe5c84aebb190f39f1168fe5cfbe66965a5f3ede685544726479de2386dc4d9916c31667a6225c0aacc2681da7c216ccf40e15b53e11527226c1f8d80e6bbc6af1456dd8a874ef65bd62f476cba74c6dfb41d10e5afc8c729a7bd2bf1b522ccc27d3c01b0db2c4e4e1e877582d30987e9e4deecafabd1e2177dfe1d930fdd1a6e6939bffb13aa4bbffff8f845c8f94eb723d5e6b454a38439559b36f9c8f61548e18d44972e323eecae8d23000b40ce511058cd983f50d627cfc12f5fffd1f51ea6c242c6f8477f3c63d68e5835d5acff81ee4d9e5d214220731af4785208386eab933d8f3ede9563f25f44f86dacc99497c5bcaf964b372ade5043df73325ba7ee4e33c1422d9c5b3ce2138264be0629536efc17af86232406bbacd532984e1ce66128b013050676f81e1e7129e2070d1dad93f4a1fda72773335863d1c74ea101e89936fefe1ab6ac07ebf362346accfe2578037dc140d64de9e194b54d983e22b8f9235ad7d4b07f08cbed5ae04dbdbe1b79754127972807fcdd4f81f0ddc554cee54bd0c216f390f3bf1e0a66f73a856e04e45fe682ba1ec95484de362b6238bc81f8c1b95fb8c33a7665a971637f1590ec0ae7a35637c37006366f48a5ae6867a00f2fd8963e64126cc79a51a72e7cca00f57e1cd72f1b35531a39a7d647e1c6aa6a2fca4e73c34508db367f23b298059c0f865ee2d6315ced37b18e2cc4f67ee1b8318bc0d54b387368f5586a8d646731f6fd2ba72815c29759308ae98dc1af4bd030fe2b6b3a76230d03b2deddf1fa96aba902472d9681fed827c6f1b20e7bf554b59ac035a1acf6c4cd1ef6aee75eb0320f62672899be6e62aca5fc09dcd942f62ab6f2b17084e9fa89dc96439b163cb7c002822da420164dfc16446f66c232d9d33ab013d1db837d2ec0b90c89c682bf044b47233fab355bcb9276b407d4844657be4d1bc032578ca7c64ac0f0216fafd64f53acf02c46d331b345f98ae6853e0d44609658355d73aa8f9d88c338c8df11245450c8a1bd870b8f4641b77667020307d5fea6a23c3531c3208fb50a892a51904724d26d4c41762e0cb11cab3a6374c40d2242507783352f1f81376f0c3aed4265f5d2399121d504d6b6f1db0599d89586ac72ff4a2c4a0ab720c2ede1e1b61607247bb9249852c6be893ae0675fd64faba629d26ba9696513b2895ef2619d1c11735c105326be2164a093b4819ea91dce5cbfce2d32141cb40d35434d666ad9250fb97302960b834a645ac0a49cac9983ec67d6c9eb46460bf392d921aa3918e72b3794fdb5790b9f39823c3a1924886966491acbb0c12567168711424b552481b2603ecd9d0f91c9002f940b7e82c0f5062055bdc738879a1d804b887b588c272d671813b7ed273e9c0ea1989189a125a09df3c6ff076d4006164145d1fe1d57223e8365617d76b92d306fe7554274f62a029d5210aad570f9584831771f34f4dac4c961ed3984e47830edf05d6666f89dfd0880b3650faf5eac4836b2211e3722c26add2e95d053ea168f14feaf36f96cbcb8b86cd5ebff67376f55f8019a2720220f5cabf10dfe81c756baa513c7169fe26eb4217e1abcee9951bbffebfad7243b781342d245417a996d6ba168cbe651e239d65b5f924814cb61a6c0de209725712184f13a3f8173adb0131a38c0017b0d0f2af5455f9f097449807f6e249727204bd2aa152ef88498aeb8d6cd61f74d774b6c385eca86a014ea258ca2e89720cf07a29f3330ae1e337099480d4a65a980a6c552328a16b3f63b96100bef8720d61f183d4fcf00d8db348d577003fd122a2ff41394b2c69884cb6ac6956d17243c087891873ed5f2fcced809b630f2f526836f5b6e9b1c9981720f21434e06df1ed9007b52fbcfdd4bd40f065a1d79fbe104043774e659c665704e58f454c7280dc5cb827947e23b1a68b47da0eab5824174dd643774bb6679d0a8d8d83f0727f1ff6090fab2ffdbc4315f5bd81ae540beb99262f14686333102533c1003d726dfa08fe64ae73139f32c62f4c003a3f328c585179bb644921fbbd794786ac14af0ffe31750f77ae80ba9029871f0815ca9214355ba73dea43d45572964a610fba6f9ffccf5cec5b3d95beafadfc8cce80db763e6c3e806f44e681d2a37a73723b7ce74a85fb05606ce56bf4dfefc4e401c9f9cf2bf04a04c92c36bef2d7e358ce05dadc5d4feae2c6451a889cfe04f929f0af9dc295343ebfb71fbaf34b79511c23d1f4c33a3e6bd39b2684cdf51162f8fc6d4bc0714ad4012e9a791dd03b726fe90e96e2b3956024751aaf2508344e1192a65309cf0af4145d677d55dc7672ef8b58f04041c8eac0d119905ea2300ad6a810836a93888e2c7b8f7877ecd07069211f01c7b7704ee2c02c84126866746a79d29b2e6f38ee3ca2a9fb1badf12c57126584aa35870d48a4e3eba35e6a5ceb0b8522572d12a3fe3bd1a42d9601721349606fd898eb353df204a0e7f63ee29b87cf0ae214c3ef2c69bf04cd2d5a72cd5afa927a5ccb27555dfc88a6de3bac6925102816146fa0a4ac1dab18438a7274c3beaa153b820cdf66d25034f01a1de675d0d0ef7c0fef941ae0661e46bd0b53478c67b7db44262b59258aa301d604ee8f5c7a6fef9239eced73ab1d624a72f811261bf0138d297e14411e063e3effd3ceac05b1954d208f402539617d3b0e569087552f39c6a971cee0cb3acb1b4ea00df49a09b87ea17b67efdc26add47231edf399b5f59ee1f314c03b8b073db813a56a5649a56865c5abd9c3204a830a6a0795a014d0045dda34b1ce8fdc24ff912f78fb13df61669bb11a77ff8b8f0994d1ddc4641623b51b02d20857a79ebf655d100e2efeee0034d00ae4ab89df72a32ea5b0fdde2dd5e1531d3efb1217aafbd40297a0c4dcfc8afbb1b7d71c997268000680c61fab5278aea412d8881abd39ab03ed3484178604f2bfeb8abcb872a957e37f9dfae5c134511288cc85672d6a4100c826026197b65f2e7006a716726d0442a53eab075193375d9113d89ade40f11ce3846207d56a02f4bb2720110941f44e6dd776b3e01be4964573daaa0a90412bfe40e74a2c970602e951463a6dc28e14d294511284f9a01942afd27ce8f04e756828884365d3835a535182aa56566c87c9393c6cd52fe95d5f3b59137549e3170f588b63bbb90cf7804a8ddc72f0163e32b6094b7159eb75c5a7ca501b8963793e22796c6ccd323a48c788c17275adac7cfff3c9ed4b250b179df220ae93f95f89eeb5253c14575aad6debc072b86f4abe9663da22a1c918f9e51443221edfca18d6aa3d46ce1854006d131953efee9bef2a884b7ce3110cc448ada31e4f1cc7959dcbd208e3400f37904915722c8d15eb748868737983bd35507145b1fa90b83709de0d560b94fa2ae954f3722c7f94b812ad46ae72a06d8ea5efc014c2e2181440dffa7c2dcf5cdf93ee0468f3e349825100cfd788bf51df9437d9e16cd8f57715ddee1d41cf1b49b262e22d0b4dbf0df2b7e6679399fe7b6853b0cd68e737f6d755739a01bed93f5927a9484f8c5b0c39b2f3d2fc79ec1f1ea9bac5f4090bc8c541ec411445d68d68190d1fec9591775db603c1c56445e5d242f0d21762cd5df0cf27cbb0f95adb49cb2e72ab1b14b98bc5726bab4ee07996f5cd8c3f2ed97c4bc22c716f5a39f1ffbc086c6860ce4f4e282d7f355690d314d2f521b1eda07f2e32c882b7f964b302e30a7209e596ccc92ee78ba20e057854c11190f0ed9bd5c8a4a1f2e3d06935c5b5f972b331a147a15fe1bb151330cbf60e7aee34e447343534e4695842ad7360bddf725a873e77cf851dafc00ae1258176cf57abad780758c3a8bdb933ddf5f36eb572340036529bf468c70440d582e7a5abefe1b7020829d3e7ebbb2d4f393def016058cf907c5afec5868cdaa27e3f8d8bf6da529d52e63c281d87a99726761c193132cfc68e54b5c110071baa0a13fc80d03537253723c79780fa3268e8896abf72f52f32dffbae516a318fa59332d5ef6a52fa7d6e1cdb6f2b445dc89d9f64dd722ba6fcd5be3d4051e2e4adbf4ea620afcec30275504999f0eff40ef1bb680a452ac1d5e2ad25c8c3a7bce2d20cf36f3c56bb590381a5afc4af8b77400cc6e330a51da350d97de46e9ed6b533e9d3c778224f302c2cf8ce658b41d592ec61f57246c0c4951b4def8b0841d93e381fde7c7d514a4625e625f0516544f5e8e33a72fafc3b99226374f7d22572065ce947bd42bca6488951bad45ef0a87ed58aee720b86ecd48c8a8c0d8cc26c589962214fbdf96f9dfdbe7c4eb0ccc1e487223b72cf7f229f286dd40bd63a1f2bfbd3ae5b689d056a99f834006a9a3c10e012b26c9190d987a2de23844e8d1b21919a0f272c9b55968073fb768b4f42c0e4d37c1a44b9f1f30b28f592a76bad7ed3f8b7b151e2b38f66dc4a978087959ee92db8723a3811bd82132facbef1e309af9c0380ddbb9b1706a440cdb339b05f9a9a0c72e0060b746a15e94ec98c640d9ab8d3044bcc92d75c56d77967c5bd633013c272ed19c284cc8bbc617f23097eb49bf9b31b88f5a73be7a53af6084ad9afbcad72bb9309b219a84423e20327b76a910e4674ee6b4e2a720edabc8850653ff82d72b97b708866b09e659bf64596d3e30e4f94470c881a3c6c4e2956ab2d377cb7401ddb909d1ca38cbf25b6de9d27a60122464f154d44497aaef3df872aacd6351de3a76772ef8b2002569ed9624a80e67d9bb3502579db5d16b016421f80dca172bfde669cdf34574f6a1e4fd14e98faf0a5772d0ee2363312a4cabe4c8f61eb7233f1892f7e9d2bbe1b6d0947b854e1dae7020ead26551bc334cbe5a47afe3a32224f4ba291911af981c49456309cab36db491b3704673241625947fe1e2334728845cc93f9ae5f5b36e983c18925a29a846a2e678880984b89449b0901a88a045fed85c9f67bffb6962d76c5a8918063ae07d0b3cd65bf569654d9baf142f73f775107473b1e02050144ae529adba55a412739223bdb46907be50806d233fc24a881c701d93b5c245bc859e2c7a97911e44060c6e676de82e467915e9a7ffd373cada7bdb3ccd32398a6a81017023b49264a8859d839a43a47aa9af33f4a95724a7024c0d458d64cc07dbc3e076e12b46be66b9167990ddc6204c654168e144115e2c69ec02e59d6fafae68db56e9b69658c584802a96fd50edb8f1b6e7dfd724b46160e577620b1e96968544ed536f04c9ad415cc052ffadb10484aeca5ec603651088122f54ea651d763de0eac266b639caadb9d1f3e2c209a33edd4b9b972cf075669027d70fac9e1a457e780d55f4b3a434f4bdc3ed480a36fd00d0977513d321c5a98c12cadd54f7f764bfd1a68703f1cb36dee202efe14fc5c55e60f72597969b7c1a705c93d4bff6b644d6eacede53760b3659ef4c69dde196ec52472e6bdb5b8df3f9cb67667bf7cbe7304c32dcef290d188b3e0f37c23132b638572a616d96a4ec2aa27ea26b5e733c96776cdcac3b6c8eb8b8c4dca4eb9471f7672904bd66b448fd04a24591a6a8c6b81a668f9d134004bfbfa551da01d07f24472814d1eef4e50d8f29372e7d6792c89db19db5ca4b5c553d06b65170659f26272b7ba45a1d61af41c3585d3f424f18e7a64be1ca8331bfff257066a9b220ce47203dab9807c2f1d188fd2a39b4c69bd53abd897343c22bcb1d5be105e974286727a7e38330f6af9dd91c8aae708b5c18b288eb5496004fd9620082b1cf74f8f1ac982e050c8504be037b6d7665f3d8dabd4b743c33e9518f714e690dcc7b0f509acde9cf5fa35b2a1b19ad694846c82f77b6e10becf986fdedd35e5b274ae2f2cac81a78049f74f3c5f99a58df33d4df497fdf23283391f3f1ee80dc603d8ca72f247e5c95aa4a7edd03417465bf3517c19da8cb6c7ae8a670453c7b93a42da256843e233dd2fcc375627e8a031fc574b3a5868a49503d8bd1a7586e0b02ec7720969d9d321b16ce9a9a420400a2e9d4bc546f46ed2ae4ebc785598f2f22c8800f3235d1f547cc445d63fa9e18828a644cb3d0a0bdb0766bc77b7912cf7f92e2b526c2b30755fd5ef1ad0edbe933cd2e03f5be87d0e99e64d8a2dff871a8aad72890bd2af68eecf48bc7119c850d6b28dc3bc37b3392e343ee8cf55c35dc1cf425ced17c021ffc9c699c4c027369a22c41f3e748e43dd35f506b4150e892d7b7210acb1753fdc5866e225bcd843ca39dc3290815964f2138d1b14c6e0ee978172d02c9d481ee7dad7ba07da1e59dba9564c97405843e5c47e27245a0ffec40602c95e42a350bd24d2ecb07e1778a9560e880bbce2dacfbc58fe6b8254789abe41d4958175cb362b64ee38985e8815e3504d6ead0196a01b92548b0f6ee933417277ade06fd7a2bcb2c861a4602eb11870a19ffd73b0b89006f46ab5d1de160772651fffb08545b950fca4261dbceafafc190c1a9186d414550dcd161dceedfa4b57cd68715c01f8cc81a56767a33f58d4ef3f8f5a6a7f13426aa52e543e247b2516dfdc91d0de3415616e7c7301b242dc804ded4299e0b45aa9024f0ebfe78172c0fbbe88bdede24403f7a2a35e1d8524e8d96cb30e5c90f04f17fb5e97d878724a95ce372778103b619c7938b5e679bbab5d0fdfce9b441fe7ee5e6d75ee8a4d814936bc088b8d5972ac2367f76f18dfd4f37774a71493ba75c7072827a56d72cffb52229928b30e7a2faa4fb9c8f4f01f01e377121c86598ad40610959ba32d52c2be89a6934fbacefd1308fc6c9b9145d3a2679afba6f832aae2eba6fdb048f0eb5e920b3e20177e60c25e323330f10b1a24b5115fbaae3afb659285c20b18a8895ee18731fa69db94d2f462eae0cfffd735dc48c9602484677b01e8db8f7204a762c6b9d7dd6919e5414599ee09f756e51c82618cfdf71fc2f3e30fcfe20155d1a9d202df01a640c9ab4d126f70ba13dcaf354d1de5f625d580dd7c1aed724f8ab35990c15388781a56525cb5976d251d676ae7d2d6928bbffd1b36c0b368c67b608dd17b482cb12efd533d92baabc7decb05121fb2985ec826d922cebd2aebfe3dabed16acd15a51abdc1561c2e9be52dd3a25618260ca7d6a10ebebff085954f0819d938170ddd794bb7bd744dd0299fe45a975276081dfbf98ef2ef7721d3dd5ae182a18982418c9cbb5bb775a3fd7171ad014f2e60eb335a3f54b2d72b985bdd60cf0494cbf39fca46a9d93fb854f5e98fcec7d1d63597b77e2805c72f68b40871f5b269c842b5ec9b7ec573a0935445f123dd9ae8699bac2967f1a5949f4775b8a0a1f8029bbe744818e60794409146d4f983f0c7420bfbb9b0a7e72b882d47dacefd5aa2c991667c18cf3056972bebb117ed85715e176f357223d1e8cca1afff9cb1a0283aa1a3d6969cec09d92046274ff3e43021b4fc23c83b172056ced19d61cdb7973826b32d32e8094775c9892b5aba2b9ad07710c74933f72a1e8577bf5bbaa123946811648cab1d0732d0c450414c1d51d19b7978bb00972e7b46382b47ad694b5c536788b72dbb5a22a80d8bc9bd72e522f7a66b6cb1272dbee74053a182b2999e85fbc9d0e06692082352e716e1837d34161aee8268172cb1c8ce0dad05a041c1457257b2b4165407609fcc59e213048f04d30a7554f723843a8a77bba8fe42cd9e8dbaf4d72815587f40a21c7a411f6cfb978247b0117fb876ea7640949e8bc509bacf93c04158f7b7ea01424f1343b9a6f1258a04f7292f6d4a4d06758fb9a1b72e8c754c7d49e7f2275a97c2a69c25044174c8f4d726e338f602b00c42ffb12a13be8814d5030be5c2f1d1d91c33c08b179d9c0a227c180b4882f293803cde0c168d3491ae1d76754b133f367f3da7757f983c667722703eddafd2312583101cd0210c3d63318b04964f71b8f5196d602c6c36b407237a2aa1cb093fa52d94ef1a3754164908e3d4ca44028ef07b59179c26ffe667229fee307a98a756338925886b311a03d55d91dc22ea6dba18986357b5608ce695853b0f20fce59c421b3cc9e4d6fcebb31c1d20dc019ee1eb40206324ea9ac72912423988ebef037671da16d042cbabc2ce0b11b912fee4686719dba2084697261bcf7a75915021b2bcdbf96f8a798e33ba42ee3c5f51558c2cce836c8580172f9136351745bdd03232e6f3c955283a351a63eef8a86d8a8a8595f0b91f56f0b011a22365c590148699ba1c53aa3d6b9fa95542982051329ce58a2d4eb39d1726100f8dcd75e084b94762c56d680115a174f1291f207e9dccb00268bc42fb57232259aca6df007b257595c17dd3fc821f36edd80bb6cb631be204f916e8af072958df06108d718eba529df7b53c2b9ed8c58a5026a34fdb5bf9468066d06ec722a0e5becda9fc0cb2cca169d1c7119f8ba567c4c58c064d6dcfe9d17ea9a6b729f66da0afad58a5fc3052e9d927e3731caf122633d84c6e46579f7ad8e01fa725836041f4998bb6140804089bd1dc034733f0186cd7d582048cca67e8c8645721e468435e324c4892b8625d70860213a16aa21110c9199b9e36d335affc43d1931797139124ba979a58c3707252ffdbb6190c3a1be35a6ed4be25b8175d8fe72ff4e7292021cda39fa5c5f663136236fb554c8d4781dc74c662079ee8837f07252b8352a8752690156857d3916ea95f25846f79678ac1b694067145d9b10e072a65d7754ca09ee3eec4961a2699bed1d47e3ddf5320b451e0c70ece569d20b72784cf80ffa8855cff747887df3bdab43fc31bedc917edf05a57b28299822823355da4d839ca66d2c66d611b8999bd03cde52082f43cb463721ffd529c263d772e046c712cfeadb230d4f5eb9b7afe1b240f94f1e74530af0901d396b312e156e45fadf5b5b2d51af4afee5cc61c6fe51af0d2d4600e0d864fb6d6d6a11081572c57937c3b4ec138a74f16bea673e00639fab87a9325d357ed8ceb20b4cbcb57201c7b0dcf69bad8e729c4103e37fc756ba7c648ff22b77184a7dde0e29d5bc72e3c145573a5ad9c9de169a2ed101160391e5d0b8b2158fee3c9924e4a0ff277234d435e734e1836859decae163a7869bc71eaed4447e5c86bcaa339d101e087277cd205aab27392788af30c2be6640a8e11fe46a20c4210e0c7b485a3c583039dad9544b22eb4259463410558771a0693b830650bbae6a7d08094866ff562c2889d21cd1a7f58865ce9f44e432c9da1a92bb8bb453a5554f41aa254a24f6ba72e52d8fd80d1557517b2d248e80369576552d8bbebe70e469f5f7af191515b25dc170101b8da0513f04d6308f5b1a1305db78a5f5e6aca858708867e02769b00a35d9927f5976ba711f0981f9f4bfe2bcacc720c84b2df29a3d994699e6f24d72cd6bde6d894575f19d98471de5e182d4871660bfa75e8bbd75b7b08dec0c0d1f77256cd4f8b446284303b47681bb7272b9e4c872fe0bfdeebbd5fd35bfa5cf72b5942b30a183a8f686036494e4fa4d8242c9ad6deab54356ac9af13e990ff50976b171fd371ea37c53536d8eb18aa956c582ee9d0be34c5ca613ecfabe580c5337c7828f8b7d99fa6d08faac17fda5c69cad4f93ab9d42924f8a7a19f33f723bde56a02d93fbcb601c37b84a92b90817255fd04a1570434ae71e95273484467285351434eca69c09d98d1a8683b9e0141ad2b293b6dfb37458a0c6fa39b0a772da0f65c654084dd5feb70856d31c9dec5a37d5dc6547c62cc76c9c6cafd2a80900d9cd8c409def6a1ef2c8be6d73f4ccec81fe08b0e34e145c513fd81a787f3828ffccbf4de36db90dd7d7f8ddfb908909c8ce14ed2a53502e2cdf8f5d5af772f4c82360542699320faf9b79b0217927e23adbc158a84bf31f72bf9dc1a0f9321afa3d111527f386b9d4bba31b11ab91461199967848e87e9ed1bc541bec0500fa9f7da6d63f8672e05eb98beae52dd9e07c7d877d551f7b0b968ba2e3963d26b4d9e4dbe18d43fba015959244b119af138c7c4e72dbed76c70524e1cfd2c772dc5358f26d58f38aa9d0414d71efd9e509f3dd29eab0644557ed40edf3c8dc72d356db809225ba59faa807425208f75c7719de2ef7818ac2c85c78f505f78e72d519f2fd810b1cbca4a18b1324b410a663e7890facbdaa8f4ca6eba50af5f772be00c74a2dc424f68dca10f05a10f52e253346980965ce2cec77e57ff9b3462ad6c8d59bb661e05fd1c6e2f0efb468e24ed0a4602887aa2c2a971530b405e22f7389224635057600df835dd9c58d94d56eeafe8c61233bf49dd926861e0574149d5db61bd1e16219178f39ee7e60dd6c9f679121f204579298373a28d0deee4b20111fbc61ffc88e0a7bfa4afbbd5596187bda635e7004ed31ab2a6041a44772bf3f4087edc43ca450e442ab0413e46cd6a3f33d6c3f29c9b8c3a7dbeec77b72f7b057546a7dd5107e3e1905efa9de850f8d3ce9822a7537157194f10be858722c2ff9d14238ac6b3090e58d8abfe2b4dc32496599b9d1fbc7da9b0a8f33a4729d784fc2a30c78bc87de6dcc959ddb9507893cd65144f606919cc46e708e7519a9064f0c9b5bd8f142cec9da5317d0df2ec0dd45806aab89295577d77da37318d78e9191dc48aa162517c837dbc195656204f2050a299acadf2ba6e5ab93fc72f4530a3cf59ad4f622abb211ba537657f7561bb0e197ac7deb298df39671be72d6efd91d5be85993d32828d0992adb1aad7e1a79b2f177c32820692f42b26f4cf1f3bbf8807e8c711e539047f1547e4ef6397a3bdd2d7c4aac72c60c32a6062c7a96d9ba26d019b239c18c3691a2347a5f0a6b61005a4b0b6bc148263fbdd172cdee78ba8eb80b2b0ddea122ad1eeeca24ff624f17c6fe2a143a551e2e478772ec0000907824f64cdff570a0cc6f887ffa6b20cdb19f398bbf109b3a6aaa017217e4273e9c310e44569848140ae8e1d3704dbdbc2808538a2511b995a06c113d94741ba8a0a2bd0c1c9335809c16222a7763a4df17a2851e78de36934153ce7229450dd9d3bd79913d68970715c395eaa11987ea42f6f805778e273ce3efc8347c2f67a79b8d52200cf22cb0910b5567892232d4d98dbf8f1cbf8f1d26b3ba2a03001b0a2facfff305ef744c346c0c43a2d3f6a6a8d31670e1540830d9e71336097234fb35b8c8f2c15350a0ab8a619869f19b22fcf9b3d7b9b64404895d64727f69cacaac27095d30a127a58bf9adeb4fca3512d2f252594256030a9e177c08e93d82b6f648e88a6999a34b410d34f984cf5c9fef5c049869fdeb9d40fea572d370d45300ea13647a24bc2d1634c676359e23158804cffd7cbe1fcfe519a330fbef88bbdfbcf34b6ae6a5298b3bd3bd1e457ceb7bd42349f34f8c0833cefd183d1b7d71b1b8c3da101db8cb974f104b197cc360038452453e7da5c36d7dda72da97e8bf52b25d720745f668b6937ff8eb8ec843d1ec50a519478372a179f534f7146b63c1009b984493845bb5c31393e0c78e8db723aa04071cd86923f0bf72c727dd2485ffe3affedc90b76c52b402ee0a9bc9ba04a23fa409da3d6b9efb6cf283f6ce3d84904d11e70e3499b75ee67ca0f81714eb45c59a50779a8551f25a282ea56f93a7bfd59e2e5692b0daf51ece20458b5cc0173c338fb9b994d03c152c78ba3ac15bc612ed0163ff6205bed270db813ea5689efbbbeb73342ba5d3727523e18bd69820de893f54f174994b461f210e5541020cf5064b934fc665ad7271a06fb8f43a96ae30ad7de1136fd5c9acb4cd732267c178b96b26313b20ec726223d4af6980ebcc90462ea99d8aaad858dacba37a5974f39542082308aaee1aa3ecad115508370fac98d3245fe20aae36e362099406494db40efc876861f272887d1b6b78837d3653a77c6e9932c3bc97ad955771c43b4c529ecdbf391d0b48d60093910168dd44751e6048fbf882e625d8cda2f32d9c29f7150f27bc4d11482b694953459e591f042408a756a9bf7e7827a00b9ba50414befc160497d2bb42698b6e75febee85f966cb1d5444daaba93511bf2d065f44721d763110f3b3f707daad6fba43b60e894d10d1d95813943b2c98e74eeebf5962029cc2606a57e09f10b2fec1033f8cb8955d33f8694699b14b00ee7954bae7c26b9cdc426313c49054bfdc58a9a0049a9ec316b520f34a09fd03092d1c08a762f7aa742def0cf72885dd3cf9ca65614b8a79d094a8d5f62de310ff6b0d3a3c920bc21ecdbf14e72e2c476a9a8f8601ade6d30ac42e81b10a46ac1b08a786c53db7c2b81daf7ec06a92f16a671b4b3c2d17a64eda0a3b0358efb832dda60242208b4f88ca25188306ca87c48235eabf821e53d1a0c2d4c68646639dc2fa23f183d8eb1c1c240234947d474a7ac0be1f3d2e245214e1a536415fac4123b4f07baee3b69fb2177557279128c6679a7fd863b6c37816ec0cbfd28438bab7b94c9cc3af9125a2edadb72f42a5551b1fb5e1cafe6830a0187184279ad7eea6a4a8f5ee18ef44f5be518722a317d9b2f6c14033e782a591adc2101c57a6dda78cd87ed67b89c0f369d0f132461114b664a4d002ddf877f4c6ff459c0ae03a4461cb2a7fa9c5c0a3d6a3b5c6bceb349950890995981f9616f338459dc61453d8595c7ecc45f16fbcc2d521af0db39428e6f480e2e99e04dd35fc175da19883bdfb10a7044b0c6e2e1256f611b06ca5f5f3ab57bfe4c231859c2fdf8d853a50f904ef01556030cdb6ddfca67e591777308f11c234e64c6fc4033523459311666c181273b4651283de2e3a3725def91f5662a17e53a5d416bfec7bd8d446ec65925a432cea7bc24d24fd22d3585c4145c6fc57b80c72b06f1c38051c497cf953100700616e29e2a527a58d9725405d13bc9572e75c243b133e3f5656955281bb0110dd93ccf0bf7ae389ca572cf2c8832fa9a024306947a3a02e6af49ebb0d39aec1cbaf8a4daffb554b059727031873be2451ea7244ec8a7c82d90d0a5727c9e3efaa243b7abb1d3daf67b69192230512548f7ee72e803f585909ed55676739045099efbff59958238815f729c162eb001f2a6b8bc751422ed12a4fc60582f2d7ad023be7ea74bdf18d5b24ee7154359c3758c50cdb1d1aca6177cebcd1687fad3a98ea50585c455efc1266a2773644a83b9cc73dbbaff3a9c7be244d93e6ab88c8593c9873e0aefd915df72408047c364dcf7eb7347ecff885a2b91b8540ab6c679fdfe759315c710bd9a56051ed5ed574865319726288d2f293fd410be5d9a243b56a444d257b0e5ab7972bdd8a1a871873f794badf47165d4d82f0f1e414e531a024583e619b245096772f7ed92654de82363f09158189ab7f646ebdb58271dd47d8b5ef87826aab5d87225240dc3516d727960b12c949dbe72d83983c1ca2d152892f8214e7d812fb77291b258175241bf786811106fc734f4f43d18478589d8bcb665da52cc1585d3560ebf0a6f9cabe53e07e26600b113f8c0ce3bf4e11a98ed391be78ca2459ae80c17fc1a1a6cc1bb1dee48f37f47a38c80cc35b9eabaed5b1cde2282386b55e43de3059cdcca614078101034551948d5cf113431c4f5001d355e46c224aff85572279ce8292753bb396849702645f45a8bcdb4871c2b4703786ca19fda6f116b7225d4ce264193c29e7463deee38351aa0ae7f9279a12c4cac578fb4a07fbd5c2038e273e3204600ea979b139b6810a7b703feb62a3ada5b2089457fda856cd41da8e57041d957b9fb6964450609204a14f3d2d34452a7652aa178b6aa1d35122fa87522d48c582266474b94f38ae0e84b59dbcfd8560d99eaa79b3f00a80e690c5bf79687beafd066d9d1f58fae0b75ab70e6ce015bf777874c57df9a4915f972b7abbe2ae4ee331d7edeec51ae60d214d7d6b153eaca5901c5646b50b0a1592aa3006d122ee0101f9e22d2db909800e41540ad650423f5f185122abd8dc36d53625377ddf84f781220be10a0eabeb56fa114cea645ae2c8e43c44914474bbe50b4a52292a1f408cdb8a22a7a58476f1914776ea8fb8ce0575419734db676756378a159c343a7b940e4d57bbab3463e742c17c54cae819797343903be5f984d6b35f2067734c8a1f5cdb147e5b534e23209cbb48d153fb7399f3b6bf25a36b272bb0585e4cf4b1a3eee58dbdc9cf6504b11adb0ce4209b2511f6fdd8f94a9d541d2307a42c94f0e264aa9ff3bf18443a062710ea882a56364c3e0ed076772047279b318d8ee8693fe14151e59f9bfc18805ee14571902ded17a17c9bea897552888010430c7c6ddd3671f7ab51ae0b91290bbf7af30d18405a7b8f7ac4c4a4872b57cdffb7a8a2514a09071cc5d4d3b2398004c0d71dd1d13f953174988dcd1722eed877f94aaee657af2952637aaf1c57885a3e567781af537042d7dc167ca728b48e88f7d76c2e15d7ac33c0720498174a1df5bab6773dc7d7d1808748bed110904605675367760fb3ad1cfcb6f2284ecaaf5aae1500402c51a600a3248291e5120234c9b210a4803ca6edcb6cb0e327fa97529c2b7022e1ecab0679e6d2e72c957f13cac32a9bb0ca8a29035db47f1216124dad6baa7d415f53a7fea22b472fd4838d652fb5cdd7370a05c3c3fbf014d7421ed59a5544038c4351a98a69b727c6dcb78a06602c6010f72bb9c40f35f29884e2069b5fe814ccd12c7cd1d85571c818d6d5ef74b67a1cea946ca8caf0621098cf00191f60e8293a549752e0a7289aaf48b0849204ba1b7a7b1c5d740e454f2648b55d4f8315254a8726e431a725eb242b6952c60a093bec6de7e1853618ef4e6df3f47543118a310e378030d72d00a64bd981b225c664361b8fecd79b14a1c38592b2a86b9bd827e3c0e973a7203ab97eab8f165475c4a29a2bfb44825b373d6c8340da5455374979dd7a10272cdfe1abd84e1163387c9e2b8938e1a0f6057b5184bbd6c8ef0056396a59c0d33b16a7129877ce29f7a918af58811620e248171b3888291f95cb29286354580721df8f15f758fdf7e925aff17f66eff05c39a76c2d4917abbb2b8aae93110d272b0f16102f1f444378ca95f6bef173fc2e7edd9fa59a87db9874e1ecd0c9bd4325a3aa650c8140fb2ed1c7e75f95167948dd6066bc2888d45db3fb018c271b7537900331c0bd7330a6c3a71c7e959ccd244e60f7ca1f9513d54df63d5e8fd9a72da75011ff573f87061423f9d2799c0609fb26bbdee3d592534ab4402367a043467b8d4dabb4904066892812caace6f712529c324c5a97b0f7aff20408955f47213cd8dbedbc4ef12e505c1168f2cf5dd2f66e848071780a7726ba1f293ef2a7217ed5795700ff408c6b50bf80c2686015d25c4859c6a7bfb15b95ed527db8f720f5021434808d3d5c8458f94e4e27f5213b3f419ebc84071bf68aefb42539d72f7a39bbf407ac211d3b285dc93625fc430e0d5a08bd61ca8c8c5e8c2c17e5672ea67f7fbb14e457478b0f11d5834b36c5bf8ad112e85bbd1b1c25ac4f088e96e1c52636afa1efece461aa7e3c3ff6e9227ce37061d436ce1cb3de3ed4a709c01ebe89745e34e4577f798163c11030d5dd92eb8cbb2a908b3679bd46eece96772123d0907931b27cc22f7fa68339a82da45a3547af37490579aa294ef634824468c94aefd308c9ede4c23d130f056c6d9462c3e30c53aff2f3cbeb976fdbed01485337fb67e66d90457042f2295f3820273007f2e6169dd7d8f33ba9c70059172ab2b5ea61fd186bf998aa59678d19a24d69f85b2fc173130c305ad46765a14722bd8f133f70cd1fa61063203e44c9267b7ad5cfbc3ff9a12d0dc5f2747fc2c3cd44a68d8029a16ce2070aff56e391eef5aae33e7373ea1a02645cc4a296de7726032355ee9458b40085c5d70589b9ded368e213b3d272f251af1eb79fbeb7d43667021b808b8cd25eb617cb7e58eba672124e660845f59eed1c5fbabc360fb05bb41cfb877a9f242c72b6d334c248250e15f590946e145c7bafc4d0252eaa42620949ed9b61ee3c1a623cda8391e9c09b92be2b3b0a9bf0c8f91504b5576ed728dc67c23bc2bcf96c870f5b845528323c07d5a6dde2500c89e173b932789cf23d7e4c7a8fd785c86eed534dc69c9471fa2bc1dba4a70b9cea3e7aafc83bbb17294e320cdd39541e95edc21dfd537e47088f4261e2f356e825848c786a391e217dee0a846a19f47e11ccd26837688412b9779ccab2bcadf80b8b018594964c172294e8c5a06404e72cbbcc429075d9abd33aa54aeefd390f69933344f974977328e9f858aa8d1a008178ea127ae772cf67a401a2890108d8b37a95594c7330b37f63ef47f68449e5558c3d38b6bc99bae78d617b2df16c9e92d39b488c5d8920b8cdc872d1a6f9bb31b9ddbe79b43e6ca0cfc6eff4e51264bc265459f96362d7260bd17ae68e3972d2ad448cb0df745a942be1e61794b04ef375e0fff28d4295430925c27d7bd377158f16a67ecb060b73a61d236355ec36d0464800169df0a724afb85b092c78f98005c0ed9ae67c67af4251971c58ae44fd83f31331e134455c42447c4d268118392bb0cbe210b07a719323e0e9e5c8893f7903d0a5956f67284714454dd91ba9900b5bed9db83e108263c912582e3f34170cdfa86d2c33d72dfd977a7488f56511c94d9f6f1c6a625c11e3c0a4b70b60dccadce4433a68b022d5e695b0e70de44b877816dee6447f808ad88ee95238fdeb49a56edb491ed72c507dd6a1d3f1ced7a5836bd673b1380f42632202ca1791db8ae6b3a3fe63872d8eae332c4f5b8199ba1c397299c4379a11dc2ed805efad9c8ed712e4e85d472b99aaa7374b47a2450ad64425bcd075a7b28aa12b9f1ecefef2d344b488d1c211f57d811f4cb79c0eb1d1fcc1eb9882b387715e9a871f8eb2473ad89d87a9b2c6dba7216a9593b38833e2c2b8db2008fce79c1f9e3e66c4c8d2b9fa7117e9472b640a396b8b7a9f64ad18ecd7db3c2cc82eb6d4d0a1b1dd9cae3504f62f483721789f09f8b429e8c6e253e77eb22ffc20f7a893406171a3bd880b40f5745e57283238fa4ced7bfc0f4e101fa1d83857af817c400226190f8a315b1e1f5f8924c0911fe8a7a054cb40cb64859e7b3ea3021ad7033ce40c01841c7dba4ea8c7d430f4d9842c336e13967b504dc3f09a880b49f715a6bd412c70a56be1367c8087246209d1849c165f59eab9c62aa6d959f5fb11c5cd5ecf2a92125651fd04d7d723326a4457f0187e3e6dd6d781a0a263823f1c8cde70bd578044ea2fb87d12572ca6250ca697eaf844ef4811d4dde36167c70e3d4b00fd1d7b2a2430e2122c27269ca8642a0cb693344bc2f96337f8e7c070bdb8ea9cfce078d2708a890fead728bbaea9c1d1b3331bdfeb480898e631ddce24548aecdb5a41380a5a3ee86573ceb0632cfde705cb06a8fb1d2495e1e46a71b5effdbaa241f5046d8ae486e6a651959ea122cc62341e78afe768f7ebef1da2fb5e348248287fb34d3da87738172ced93f37721d68a7d25f148f0c19b7dc387141dd9687ef4e8d8db2c8933e0972f17d15ec4d2eacd1e8897cdec0530cff05285fcdce60087cf61fd8829640a646e07b99b16d533d357ecafd736a5972cc0968745460b7d80043006b1ea77e15721a59114fd382187488d82566015f0d6d16521de37b97ac87d2fc2375e9f67c25374fad4b5b5483ee87f4f966cad52536821e70da17d3abfbf248e57967dbea38d9820468897a0a07f2eaa76cd59a9d1eb958da51a8f873878ef714278baaf3722203302cec38c986b30cae0d74fd62728e651bd6e3bb5e2b73dfccc6e80676728fa392a0c75f6bd7bf6d95d17e5349785cefae1ca1d5f5dc4df2495ccfdc567277b3a078ec946daa64b2d710e4804cb41fa394d179bd4ab63ca9e23ab267ed726188393b6654e519a95db6abc4d38601ec83dd76037413acd94e52a9f350447282aa142df165cc9cf688d5b223b0cd6b1d6ccc842617e6d2de65006e901a487205a04e2cffcf7d1fbd8f36c6e43efe3ca6772ad5997104afee77045337c54272929c833b159365f81f1cb309d84bd5d50954fb735c07cdd58cf8a0e7a7e097720526d8f5332abe99aa5e313af87b3935052615121df6dc9ea5785fbecdea2672119d4cca1a10b1830bbf3115f379dc79bbb5cc9d12f34a78d0d54484b960460af37532d21c4f6bba21cc32484aed2df2139e2ee197ebd2f619cffd498676d2728dcf53723d611fe10a86206d7d90f9aecaa3f2ec7da4c8fadf6f58183a9def72c7ec2155d4bb2431bcca09e78c5736a7a160893245aff8fc3e8eabf32fce0b389485e19e45564d58f826e3a87fb15fe8af73bdc38a517c871cbb2c0da3e96772a1375d01a71694f15b36f78d6e3c4557fff72d7239405766658adbb12c39dd72ef2aeee3d38a2a752dd4ef89c34889b9cc48061054d0940fca09d6f508fbf972573fbe346b63cba9533a2300d89a264c2d61aec0e819b877ca95bb6e018ff65934bcb652d5631651d4fa16000defdac13fab1b75d72ae22cd68e8f1c7fbe67216da5403d91c2287169adf257b66d4db29036a281c6b83b9ba80760c569e18428dcbb4769fb865a6c8aab8cc94522e7ed6fe2fdeda6165f9d860d37c0891071722ebc31287b413c352d9962128c4892b846000b15ec653c1d8924b377c4f94372120c5a45d1598ac01ccf247c472a598a8bb1b47ec0db8485c449c823e4132f72cd9ce63958064fc7bc1238282e5dbc6391ec479299c590e69a5c768349cc7872c0011ee766f772ac8d51b301cedd1af983254f946c9ea935e1b7d4d65bcf675c3d3810d7e39ed123e63fc19f89fa2f4173354643f05c0a82ae254fea1df1577251cfb7174b6990019c9fda56067aa998ca3472bb489b4a402f950ca6dd2837727eb9cf835f8fd8775858fffd6d169e6d98ff4ff27f1d755cda1fe3d2d4e382022854687bdca0c7bd28e8920ee50f42dacac3d68f902a3f49839b73d1bf784311b1c2c2e8db36ebd655fac310d357fa19c7507e5a585d9ce881cd8b6bcbef3d2a0753956a20ca6764030dba9826798e4657e245dfb4c1c62643e42b5b25c42d72b2837b7f439f31a2f3468ffbf43be46279bd394b2714244e12fc802c5744880a7206a37a8963e1db685b6a9fd60890a93cf9a851b0216b2ae488395ec26c233cda419cf85dad7db7eb085d91d7e51fbc5bbb585b85405ea24301ae11a348f61b9b1b0ade34affc188ba862a1354c8a650ae658131b03713e656f14c133c19c470e73ef392e2e261f492948511e5580cd604b92fa6d0db90972c3bd4f39c8d47273f70615754600dbf3c099cff48766a6cc78d004fb259d5e1a8848664209c8721dfeefd90209112b53c01b289c3d368732a4cdd417529806ade0aa91c5ddf367b47cb6f7c4394b173747afe2857ba67cb436821ac9d20bbbf6a7038cc40e2c72748a0705cd46f7668b6f65c541d1080d25edf0d26c7f3c4fb1643a73a82db5720b2900a0b2b3d180bda889882063c1f33a90df57b52ae75feec6ecfbb82192698b4e1854fead6d1c32ba78505b81e327d1d8fea324f3f054584904cb07e57f72c20d1d0e67299428b50c49e1b9e8c9be4f96b18533875b81b533eb1ab5a34971ee2ca27de4af942ab203691329e6851b7fe7df458aff93e1839db8b4be8ff35ce018f3cfa7fe0ba7f1784cdfac29d6e0041634306f9cd3dfb5c640005f7a9850e85f74b3b216c8574f171b3d5b0f16dbd7eb074aebbada2d5fa82df8bea01072dfb41fbea5975af4b4d1f4c6f8b442e80e640690710420cc7858ca08f562df72b52bf7a72f0ea7ae5d07e635fc2a4bb6f244e49da9b5ca04d95c68c8329da41fb4b7900666348fafe12c99aa5859c9400580d9b7c9aa9714aa5e33faea7c683c1da2db2566aad595baee24ea67453b00120f23e9fe246b96c878060cf4d3891a601589c990e71fa235b150878d197987039d7857c7fcf384436fb0e5af0ed772e12dbed7e1ecce417a710dacd3cdd3d3093ebfa9798e6bc14ad083ad52ef027218145766a130aa4d05f62dfb7115f919a4354d2cc4526abf94c300889b390f2906937e76b7d2c5f0d4395b9c2d78ea063cbf7263d64f731341b1a2ee41f20072a039cdb5abdf5c2360989aa009c863f515d5bb77d5700a617c97c789d7660572da42a45c9793bcbc94c4af3ce1c13077ddf66ee32aa576712030f1ef1577357254c11a5a471edb69ed2bab22f7dbd02ccd8f4f14bdd0ef9aea541cacf0b26b72ab87e1243e13959afe100bce14cf06dde911070f35e80cb9053e5accb70729591513a2003d1e92d9a8efe957efc3c0d5ce4a99dc1915f5c53787b4326d5e3e3f4faf5d219c5ef98e3692dd2ffac091a963d6dfcc35b0e510918c163bf3a43472177a6f4a46dd738afc1150ee936665200e65747ae38bb766e29fcd02171afc5182e48827fccc1983fed151a6ff0450c8b984182702d257f485a592f45aff0a7263f670fe52b62c7a828871a811f9521504858dfbb8433145975a391f2beb1472cf4958f7229899271dfedb17d5b5ba60f1a768cc64c5fe1b324ea115c27d5d0de06b726a32705bc50ef674c67ba54549dcddac81840f009b4acdaad618978472345c226a8cec7b171b82b6b1032c3cc694989345fe7bde81993fefd3b15e227298bc447268129b04565751c758f85176e40abb82e41fbfefe6272b3b24f6a72f27ab97412533f63d3088554187e3c0122dfaf04c5ad84b43871254592bd054535931b4eced7d303edfbe294dbaba5c98ac3898e255b74ecc161d95176eac195224dcee48fa78afad97a961b2f8239e885aa6015aefdde299de8469bb71c06c59ad41746b6cfc56627be663ca9ce7ae765264e611e84eb2ae909609a931276d722a7dcc4efa05f0074727cfba88acfbdbdeec91077b58ea5640195e3ec50af97126bd7709f1241e605798b04606e0c5bd326e5bac809aed1ee8d3366e02730a7276b20e4cfb0ef82db67d7c3da16b6fca99256fc4bf85d9554521e46055b21872a349b62cf662e7ce9fefa30e0cf0b3d753560037ce6dc95c170cdcf98e98cc4089d2c338ba74529d09e22501eaf6fa16a0903ddca49e806c6af2e0e518ad714bab3e6b12af8d741d709af425f63335b35556dbda3f7fc5f396cb716bcf7cda72c47251451d44262d37924ed7e263cb72845e32a579b3173a06431d064c04be72f4b019ffd43e4c4995b768039c3e84453917aef940118baa9d962bcba820dd72631ac4774c55e6e52e7f16b13acb65a876535b0ea63d8a89760e96244f3d3b5f0496572dc9591e433c85763b8248d4af8de6809cad75da1bbfa266ced06f8040328ba8b8f87db573a55e91a1b759cb8a503d84492e08f52017e1d4b19dfda8726902ec51f27c6dc77b92a37df71f8bfd7be16ee4fc69846b7c415100f165df722aed06b150b8b03541aa975fd57c6f596f6d51eedd810ae8a4ecb3a8e8906b729d015a8d9521a3ce1a973109efd513a142a0136d9d6382ef5eef4f90a310d472d4cedd2afe5bd935dce80b3fb87e52b16a459699324a081650e55c1e375a0a724b7dbaea3aca1f7068669061fc707bad598f781a53a9f69d7f1efa18d475d7721b5a13d51a9f07e412eb35194908ba3ad3890a0450f6870a4cf51ccdbca38750444aa6357912c4a663d868cb416e57622791fbe6458f8768c2d7680589cacf726f94b4422fddde585b21edbd9b864b3771f3101e1ead5b5d2b28ccdca9b4d6141db9731fcf71908d9895bf9fe3704812e7ba356170b990f9697bf5e9caead87223a06151daced44ae3b4b541ebc19cb88f2401cd66c4a03638e9246e9c2f05459ded36bb4bfbbca1037cb8b0cc478d1708a4f29ed79cf5e658a2e05980e4113b76f35db96d33262beba5f20393912b0eaf2489032b773de98d01fb6417a3ac720b387f946ec715ea0ee22519669d58be40e58e8c8cbc6a9ba491d3aa0943e172cc0eaabe8ec2440a6390a9066e82d7e7f49319844b819bc945591d9efe6d8b5867e2d54dc8f6e1609a556a69448cee177e0a358a4dd56db65f4d05b5fd03c530516d957239f8015c975636a3c9b74bc42310d81308826d8a1cc3bb6ab2276e72a5d283093799e174b31a1c48267b47350f3ef80e5eed24ba8da8db8ce47d1572171cda8eecbdb74a836de63cf951ad7583dc1c62f81d3e9f7d4ec047f12aa3728e5008a5089a26388f386900ca23aae34732dc5477e19359842c58065e88f16f0757f940dc0efafadb2cfb07449140e847d3421d224b7f65443fdaaac56879721d272116663d00b6267511104a0e2ede67f9464074d1a328e5385705debb65723f84206254317dc25c85362b1cedf96b0daa71d1189c127adae3a57dabb8440c9d75b21311990fb52265bba40bfc30a5d4c7d478a5d948611782c267dc487072073f98cbb985730ad5cd96d1e5101645502286c32d13648d991d340a4a4e685d2962ad3600d811d1c932688dd11f087b086bfddea8ae6455172192d1eb2ff6721c7e56a7a2b1620efcce28ac4a7edfb493daec2379febca071e6ac4177b2bf72811e0dabeb1e3e9b3caefe561c96a74274b27807e236c18d987a12f2e4be2c69ce5efc79d501776df13b8c105bb2ea0362fa29677ea9449d3d92d1adc14cd272277d344ca4db7461cfdcc493b051c43edf016efafdd0848cd2fd87b123b487402e2d4cafa11a70674e548fa7591440c6c2b4022cf457092d25debf88cabee857317a80d96d3496ccbd178274929f6ddcbb9398baaf269c99763a5a5b38095972831ff71dd22420ce8d780e71f14c959dab80b3e21f7185b0e630ba7ff9bd5272059e651179e2203735cc17b477907ffde59c6ccd2f9fc3bc21b663ac2f818472073670cb113ab1bf7a399bee880e82bf7dc15a685b3cf5cd23c591d6a3ef351aad467fe0784e591d0e71ced2f90698f04808b0988d203e17a9455b3d45b6d0722595db1ea332a65f4fb54536530a41baa9e7659ae420508e19b814341689627240834862eed4337a7846e48cbe3ca6a5f9ac4a779256ac9947fdca1d81c9e817fc7196d6776e3eba9169b5e090f32665e0413fe77c83271811c32fa39b9afc726c6f0d67e5ab5b62355f4316f79061ad458e4dcc561350a21a0c62682f57fd0a401dd2d5d90449c7ce90c6d24c97837e59cffd7528ef565d9ac2317584d6162c2f0b0a23cd4a0a38418309ea2f211d3db858ad4d04ba0e37ef9c6d60756c141f27f1e85f4d871aaf0228e6937f4e744914f376c53ef188ba9e5181b3a38acb07fce085e4b25cceee4a477495f2b3826985cfa48a03a57344fbb1ccfb54b61172e4ffce02d7b5382a79fc673805078c36ff3f7bb8b6e9361ea269af4eff094d7210a4effc100b62e92fc0ff41f989d27edb857023eaf18ab39bdf2ff2e94b4472749e00c9aa89f57696589cdc01a57cb5cb796a6384e4c7d93f5da04983717e53c56b3f5d03b2c7ad39795b737f8a865666a1c5a43f938a805d468b43cdfdbd4cf7967a440c055e512cbac18a6f82b25b010c4aef0984b28cc9780e632f0a8671a1661d0b7cb7fdaae8ca4a47c6b3c6753b4647438b005902e9fabd59746489722cd18f7f6d2c80fb5f83cb7b8a907d8fd380528e4a0a15e8f8a61a78ff4bc472f3bcf7321cf6f3b4d599e67e8af07a87062773ba2ef427e67ccb5f997442bb721223e0fc11b72dd3fa75ac46427aa8f1ec0e8c6a56bc7348c0e3d54ca9143360d93bc7c05fa8a12495890f80603044d885dbbf3e8e33a49690e03853dbdcd52f823d508f3a576a71cd8c65bf99f329882f21cc72e58fb166e31857904b2ab93d4f71f2b7d85158c62f2dfd5b6ef6e2b31005c11aac6ee5a07c63cbce9fa0b9720fd7a4e32e661db6a01a49692212c475086720320e9dbd0af5ae5696985bb4724dc02a076be6d2b9bce444c0a950de3bc1bfa5797ef232f890797c46530de5723cc9f681753c8bdd3c3af92d9a5f2f1c6d12cec0d52d9045ba77b572d8bd4572544f28e429bd1630d5a4b47c066eba774ce4f7cdd8f49d63c3fabc804ac35372a503107114f2ac6b1f23bbe9579f3b7b1a42037ff67124e13dd3fe82daa38b38fb8b14c9c3466773eacc58c3d2782713e9aacbff00d32aafad719132afdd4a728bff574dc029803989ab580db43503acc8ed9b648d19aa1bd7d45fb1dd09c9680fc359404b0a837098869b52428a09265ef79d733dca235f79e635819ffefd3a4f6ec95eeb685519d1fbc7154a7b9ff8241a7cca1bcf0d0da42f0e65fe163d72d3e0631c9628316ba9801379f17e81eb8d4767c11c598e669cfa8d4d259969722820132fe3c2bd883b95414746ee1862cbbbc204053f890e7b53589f125b8f36e49d0b4d38cc2a0c792b00ecb63aa082da124e56c40572dbdd69ec05e72b0072a95c38a24ed5523a7c08f3665e8e0d138606ea31fd5f5c015c600712a5d685725382c74b65021097c8da397b57d2146112f9e316434514c20088684d1e4db872c0542abfe60a4182d27645b3e3e3dfdef8d4f5c1242684e54081cd4f93ea556ba855d10a5ea5e92259153ce2b3caf03e19e0e932da2bb7e3073d0c4c56147e2ae477be5287cf65191d7a38a6fc64c0a4d1be9131ebb7a4cf7cd564afaf099f591a9063a1c21700e6090e6789878611b7af541933a02dc29615f08891dcd88143d0bd79b07cadb6f31ab70400ac0f40c25b0692fd176813126ef1dcac27f9757257993caa0eefd60798c8f243ab518166b4634c3f852d20f941be7ec045c8d43e968da7cde2cb085ce1a7e0b9d4f57c7e026631545934b31a3717f0b20ce6ac721fff424802ee8240352a344629ed78805a6fef8cb5d2f6d5a6a53651d4d8d0727e68999696daac17f94d2225df6bf1c1b66679d87f4024a02512d1ed06a75a3da42d0d6ac9265d1e3bc2f36ef65297b695f5201dd0748e16c796aacabbf29d218413513068a5cce8da60cc633d613d680037d9ce5d2d3675979ed068841af6165c95d6946b5110b533520e0bdf0737a512ebff3638e1b80a361bad4d4f3a0d2066f321e537902632f2dba529f2f8bc714707b58ede8e82fb3e4d2936c52da5720c988af7e3b2debe367473486032bbcd10e952a1ce3136b06a0a46decf5ef52915bee965f9be453b4a70ea3d56eb8f41ba10f54e661f50e078db9ee2181ce172bd18e56445537396431f87e9c0fa90dbcbc1dcbf58d408197dfe126dc49492726a816d55d6c0a76b159da8b2748b90306ef5117a3a5bf88126e9a499e1fc9472a7ab000e82b57f1141801c1c10f2fe781900adf128e7eb5adbf7a924c348787270c3e561bef33d7e8b7cc0175a8dc7866f4641a5f3ce6e1d8d5c7ed9846889720a48edefc0d11569ee5e2b5e6130487ccb3b54259018627dda10d9561b9ecd72c44e59af44da62b6bf77ceac1c32e7c243191a09a68d40ef54a81005212eb272aa0e4cf881d19b497d1977b7d91952512c4ab109b18dec28f7e092ec017a9d72365147874d72a1f198e1a7d9f5e9797a2608636439e5c8780b4668763395cf7213dcee2caa0a79ddc307315eafff2530db833a6f8921a7654c06e924bbbaf1646627d254dff8d3c6e135967c1532713e937c9c7f325cfa1edb5e2dd07b1fc564b22f485ecf9e534a40a2b2234148cbebf2fb229cfb6963bdcce8f0aab927c1516297c4bd8d1cf53e30c4d0cb1eb7986b3b7fc8ad08780c8ff439ddcf840474108208b8799e9491b703ff3f74d9eecb269b914114824588a14f4d3a667890f972fc8c41578ae945ae4b577fb8f04195c3d018331212c5f9a2e452c6a7806ec758d10abfcbbffa88509e8b510b8a4763b0ceea7d53476b25a8f716533d534c1721b33c83c08c04c1113d0933c31897fad3aafb2fe0d19dc259e1901a62d36b237220c35b22c35e2890a369ed2d5b950320aa93f8ff78b3f3765a7ed321f9dbaf429fd5239ee50a031280fbb10896f8549367a9e151fbab1587df924962d970df2fee5864cbdbef05cbe01736b69f0b10b747194c3ac0d85bd366d876aea6c7d472241c95518b00120612e739073e743206c12fcaa456c9d3a7a7768121a012c272d3c9a1aed5376e88c6cf246a3bb6f8919acd9abc231b1751c34491be7eb393382c7007131bbda38d87fc9ad60fcc18ccb2fbe5dc38d91db8aa1f38751b0c6d1fabe1bdb16ae96f5c0ecb54a72740408b4b1635cbae1b64994e0c7fde9c811d72b039098ce2ba30421f8b931bb16111fc6c1988d3e2168dc56b9284af0ef2a17292048c9a95a1b55f1d867744e703a63a31bb41538bfcafdbcbb99f4ea7a5bd5602baa06e026069f536855c9f4bf7a4ecdc4c01644f05ce0dd6d7bf9803165a5177210b55733cd6d82866761dc486fda11e8ef6ea9380c414431dd63ae8f3bf727a72fa812093424e708d17e20645d8fcd9700fbeeaabc258fdf1e61f6f8a5d724a59b87e55474059873ab6450080f10090b0030de9ba84bc76260dc9dd1eba723615f4613e05dfe17309adf48a9875ab07d0ac9c26b92012e4fd8faad3250572effeb3d87d72d8f1ef6b7887ba893d6d102c7176d3f82c6d8b39fc3f50365a2470a8c34387433562c0ceadd676442d05cdff88d34ce1d371840cd5b1dd87cd720e3ea76ed18e4faebb89e3f46737e73e4c94801541ef0ad5c712fc9c5cc8687207e881a4a8addfdaebe4bce6a99bf975a3f3ba81682f56443f6b889c5e124b652e7d6a46a0b5a88712282906dfafbbe707af3003ac9cd7295305e6fbc5e18c725a5d16af5b8a3013071b484a6b71d16f35e4cc3c42749ba8ac5bd316685c8772b06f23512a89a0ac051e76e7779926ebc89f932e97d95af7862ba1c3ee1ec872f9b1ff1cc2200d94b22bfd0602664926ba8ad30763a634868f5afe800769f910aa811ce5b8be49a28a9a7ece52550831e0d983394aa12be45288bdb8e6884e72a20f2fbf2a9d9b0e29e0cb4e0446690cbe10e1061bbfe936a351afc96cd3c772df7ccd9ecd229768a33f0756478f3af1b148a8b688dec1c07fe936a78642c872f153a267a8c81c0bfd54e59e067171df22f693c6388730f2267bedbe9afef359a57f7071f7968fb77a54b58f86ebe759f70515c95f4d4613052321ba95efbe72284f63ad537db80655fcedca03fdec9a3835f6676255100e85649bda9a292139360cd6068e5a4fe7c6b89b7444cd90ca69ce9042824bb48e6a6e6750f7cc4c721ac0e22d00f68197411a2a0757508079c83a19fd1429e847a173169cd2a75e725dc2d9249970c971269cfb390a216f331d1da1db03e3e298eb47ba746df10b72366dff4d50371be9605df2263e1948a73fca04dbe33e407719277b639541ae411d5e635cc0cb5183b3a7ae317e883fb676b0252801212e37dfd9f5f4690ee62fe3a2ce3e254b034775385a1055bd1e97f7fcd76b5e02bd4f9e8b4437991c374ae71c05176951bc455646cfd9dd07818286ba22857bfe82c86b47f92bf5a59f727a94d668b77b2cd30b0140f7c1581aa5a77df5204a40687380d696d36a0dd672c6b7488674851cd90684f0bdad0ba925bfd0beba37ae024cedcc7c1eba72334f46d282f8e7d47d98aaa58e1839a0da5c230909244822183ba58af9ab99acd172ca503a6588e8c1c4bb2e386388123f5d485895b1852551f760148e0bee63b6396a0cbdb1c41a455a1f76b83312994b7a8b2b8a508125461e7c77c58d7dc654723592e6c9487a37fe60aa44bfa8c7af358794a2ecb8a248c99a8c3faf09a69549793dce0286670a4b18669c5ab90d176d4495df834186094b5edc98cb4ab84e7247b81fc3540f17c9fd86fdf6eb69badae983b6aefe2f397b41694e00a779870e73ace6475f31ac4b11c67c476a55dbe3576225f6ca0f2aabe99094626981d75733b39ed383b912aa9e11539897c92aca1abb8e06de83f3706945650bd7c01868415c23e5fb0b5d2884e7a8c651b3ecc406366924d55336128cbefe21243785138b5ad3d34608a2bf6d47a04c8912382b390d34f0e322a1a6fed0d31239163272b0d1db6138570bed4c8a790d36e6f0f9ca1ff50a6408cea4a3a350590eac1d5a5d11b3a3a557c9650405cd007fb18b2ebe39813dba432a82a60d26e3f200987235d5c9a11fe40ebfc204025824701e07ea2fc3af424ea974a7b165ef598cd97273da49438c3900fef5343b271c6b48ef91d50e73195344c317d619aeff9ab748d3a715a39a87eaf2b1fa9144ecaae038dbd39f28283594ceddb17b1059d2e472acd89bcabf8015665d0ed3841a0194e07f116c9908e3f6498ebbb7878210375bc42a12f6242b61bc0a34defa9605f51119daa1fbbd943b75abe0a7649d3c6e11a22ae8563003ec4993f07f9e9f1d37ebdfd1a39212c2d334df377d374c2a7b6964c8138ae50b65c187e2bf07dcaa0f8871b8475e5779d94a67b1a5d86b34827242e8a0dd7673c2e1f4dad8a5cadd1a80d8133f5cd6e520d468660cff4d2f95721fb31125c2ee7fbb95321c71adb5db80706e8c05a946d91a7e7ecb171cc4e001a8098c8566575e4ce4631297276043e897ebd1cb4c892f0bd678d20062a1e70ea2da3bcd2a3b1f4f1e9ffdd5b50014668cf283769459389fdc737ad6c4f7f039d09c1af566e9e612d95bf54c1bdc0fe8c32f642fa3b7aefde86f70b8c5bea63324dc8fec7e7a51ee361253dffd6e864bc0c5e0717e5eb7aec88a0a4c98eeaf721ddfdddedd1c576282533c4a5660e512f08bdad3036157b68891c1d9f34ef160a4ff8b9a17cb7a491786379711853e0c147b23132ed43c3afda9c29299bb282fc199246a3d53e53e02e09cdc1ff0e96c64926aaa99b840d94a00ced2bbbd770743971d7e4f9d497069c1564a8a03ff9c5184b5f4e982431781a2b91355a2f3720a1e50525065f6cafedb643f1fa8c90f7b5cc0d782d87bd8f9f84edf31433f02ae1446b8f6bfc90af0e1661d57fb2116c692a058a9176377bd0d974dfe6149155c0b742348f12f0afb8775adf884ea6d6518e518510698194955947f85ae226af4555a048f6ec7ff05c8b3b2986610f7c937d53d2630e27f06055e9754cc0e7234b3db1eff702fa55767151a9315e3da464c36ddacaa98c54ab0fbe86a52b172521cec4dd82cf10e240bc80ee5c524e863f7ed9183ec32861c7ba44ed921ca72f992538d743336a869c21ebd21d598abf88d6d23263b45cdfb81be859917623661f62d3b718e751b25880fc28dc834b30022940e725279323828906c21371e7284a0947cecb3af745c8a35a265f5aa07c78b4ce3653eb608899595e0ca80f37207ef4c164e833ff428df8e9aef9809106aee99171ce5b31a52555e670c755172fc1b0ff90abf2dbd8d988f93d1379a76cb7025b3298299b8627937d5280dff7215a3c647fee2ce624b947efec2a47d3a3c9b31f0168b8525daa628b9e1f44c7289bd8d8df8d8c60585e3a559fc53364c800066fd3899503d2e9ac3c92a64f14a9d940270106cad3ca656c82abab56a91991fe7763821c6dcb1361b8f06af3f6b4c98af46ee7c1955ec6da8def85ce9f65ac9326cdd73e8e76635ee7c7676e772000f6b5d75d55054ec83882d9fd94ff7c0b2411d6037d145c4b91726dedbeb7224e20fcdfea3e40666da81accd5543d7c507ca7189cecadd5f037cca6b8f2f72d863748cb05ec905ee68435e79ff45d093100f5490be82f98bf1a6f7e929bc423887306e02af8f2f78bf0b8b07d252b21cac7fd8d12e3c79af51fcf149321d72ea142e60e40153e5a873c57dcf60029a4006b17c2ebbc500e1b2f03aee4bb3723b35af5adda21cef771e023a210530d1e27c939491b159fb154a028e58fb1a72bca82399eb25bfecee814c44c48983d233f917f5bf9f5b3a50b129603d858272d77fdca7ac4e38d9ab390c9726b60984dfcff6956306339dcbdaf0996b13b067547443a64e2e5749c5fb4f52692641e1a7afd01ef4d40bfd1ee7ae32efbf8b72a26322ff2cbb6547fab67b0ddef72fcab63bbab7fa45e25d7ce4f32a315d7d7270513dba0361e910f674a18553d498818128c3f028afdb10b0663833f2c9167208d56fe01bf0fb604bc6bc9b1b456714db5299a26134988bca0a8c0693a86672fcaa9420d6226522209c6e91183aaa0c9b047836b34479f369733d0e65962272c104dc424afc9c5cd3ed21e5550fbb5889b1c623fe0216df169e7b0fc481853d253599734154badb5b65ea8adac44ff506668c31ffeee65b3a83b5e39bc51e72fa771e224f0ba3b89830cf86b126cb8d9f7fb1cd8bba62802d4f6be6b3254c72943bfcfcff4e6fba72ad28419324ca89a612c170b5d61f3afb990d5d7ac456465ab1e77629587596df4a7f3bcb3898cbac6bdbe12d27c64fd091b7570a68a57216292b8b25a5e5d8541eb7ae4adfb0ca4cd8d8f27569ffd344296fc9ea979328c6a43a93b04b3c08ebda41267dd36b31da4e0fabd3f968ce7a15c8003609a03c3e945adc3cf7a386ef9b74ee90066e9529b9dcc434e6476e4b80ea75cf005e72427d51e8239e9c51343a66422c8303c234b730c3f57143633ad773d26184966247f4eda9996f7112212b84165f5e624899107d8bd5143672753a46250af01e72b07b72d5f26a64943e583c4e41f68b8fd641da829529b2b78cf5fbca513dea72c2af1e6d12d8c325c75ab7262de966d961c93f8b55499f2135ad4f52efc48c72bbef8772d6732f9271aa4af2f14e5b8cd3f913f5566848dedb6312b335e0bb72832b1b9a27fa25721a469aa5976783f032da7607e9f160ce525a921259c02a7250a71f0e17ca625787fdce9e1c9fa57ace30c8d468f728e01f08bb2cf3d28872e96dba08340517d41ea3064aba7a56098ed19da97795fd5041bd33e07866b572b8ff55a6bb8fef94017eb507cd9883e781332179b27e2fae1c75aa94b59b000b306d71cb1e6ed0e0ba17855f2b06ae6180ce26e7c5ae0fa1253aed6d66ccf1601f4daa684b14d108fec1d7fa488964e49452062a1afa90178f694cab16608f72b27f39b28708f87e39b172fabdd7756fc0e6a6ec09666946111c94a25a41061884422cf2f20a4896954d210279548c8f4ac6692e4803046c44d4c69fe333ab70c9e51921576cbe888440c812c4618897c53d70bfc34ae3fc11aa9d19f9184972a8a41cd52440627aaa47adb4f3c56024712825f51918ac134cc7f5c3b9fe2369dd404810010642b9a9452371b26e212a23af5ebb3e3ef5ec8b7a58ae5692a704ca2c29530cec776645ed2b5db17828f65c1ad78323fd215dd22d34fdbf67db72a7586394e655fdeb92f7b363571e1312f82ecdf15a5edabf06079ba848faf07279736153d8d475e0da917408f4e684e4195678ee0fcc7492e51cdeb1a58233053e383bb1b132821c4155c669e0fad49096aca0ae14232dc7b55767a52c6a42491f35ead483ef121af00c5764a5c6e661bea1cc2835da9d3bb9a8ab8c9b4a9e72233cf44608290c4ccdae3af1cb608b74befeb8055e86a36baf2d4ceecd2f27725fa2ef3618188160c9519dc971bbae3a17fbebd61d17a8dfe4fc9e62c6b61472212ca4d662fadeb575d8e268ffd6cc34e19b6303f1e3fc043ba67c72c4d2ea318eb613e6078c718195e83a46c683d66181c83b01cc5328b37d4a51a894ded64c7f15fd186a2e8b1516593cf091e9c9cd1e53c3d812ae19f256a60befac2d6172eadab332d9a604f4ca076e929a066d20593454a80edf99121600e745708f2c195cbb92094ba24eb396ad0f1deacca98c62539e9b9a64b83c48b8b1d6f535a872e51a705cd31b47bd0107d96fe66dba08de321f8ee11d0ece9f7f6a616890ba72aa9a435a78e0b6dc925f2fd423ea500d4469de88de94082554e4088845fa0c72388a0d455b508096c39659342647bc3caf0479ccbf6173c157fecf9951363f71a28dda8d0a0d8f3b6974898826eeee72eb831a4e473acabd5cfae9eda5c52a7203a2c47ccfbad2a311b6b630644016fbe551e90fe865adb87bab9f2ade6670724d66fce3c8d1016d4bc0befdd94ee642e626707b2758b6c60b839d33a79d324683d81200458f914be4e36082b5dd80ba85437077f98b3c0329dde29f44987e44c101ef32fa66e2e472450662a58bf104268f76f68872f70c4b129d7b294c4072e07d5bbd487b87213c85f96b51b0e50e053b2ed06ea4d6454c25d31f92c3437254ca31d2d3111cf261033bd607ea2aed1767d51e506c8cfdf80b21d2b9c0e572f6c171a3386bfa501026d08a652bf4b5b3d4e245cb5550b0b700222505b056724e5ed19d91513fa638a93f38bff90691345a9346c1ca8792187f2f3897ad7e203b2f77b899b3bfda765d4c804fe2263de061e9d9008cec864cbe56f9f4ea033898a584ccd2735026013b7b59fc666b67ba4936ba251060ef9950930124b754728ac0b990ead11237c5920c7cc8de36ce0e0c662060c3f541aa01ef25efea3f729cf389e740f8d9d860037e7045813d300c6854c78c2536c024a55a880b3961721c4a90c176dd06456bb1dd483684487ccc11caa77d8addec78628e5400d1bd62dfd00d72cccda71a189bc713ce399c739597b48862a14f3ad654df29ef902942b74725a9d1bd16e0ff78d57aec1eda21d2b57586f069d02c643e59bb2b4a4272192b3c5a44a56674725f3d1444106e2975200ec957438d6a69bedb4cc424bd7281bae70211403d780f9bded47ca822f8b685cfd3a25a8bef7ab53b91a0b57466abe53374601ddda80b2b706ad07937d41aa87d4de4a452e18add8a810cb6ab7223165cbd6d162a8238f0d8a5666b8504b229494cd1dfe2c26d9789047d26235a15ca67d9a63913ef389d29ac3c4deea3cdc0c3c670469343cbf1f339a10e7872c48bba8724b4ea80fc4c5c4e83d978784564d4f35d46f9d053a4b30dd015767213da3d217e3f20e811dfa21d27488242158c746c3c125d46502903936aaa1b2c331e11f8159fb6fc85e2f5613fda2724d564f46166a535ae59518144a41b2f15fb005f17f687f703c0408f89a601ad34c2e2f9de3273b3857ef1d2d00911156e740a330da630a72aa86afdf8eba6b32860b7a8b45f7b9d02fd2ac1626f1c3e4d3fe811af5061e163754da85afaf856b5f0337754400090c6a1fced87bab10d7209392994723d04d035838a4146c001a0e640208d115667cdd0c6a296ffc73372a3d4f7e0db4f15264f4f4695164feb5db63460ea00127edc4a6b3a096d2cb6698f548d8afe97db04e3354c3e619cac72824ca1fc3224e49bd9a74e136ee8d572ab558d3579084e69a52b71e957c1341e7bf78bd346b240d95cea58daf8425a26867e9956790fbe4c8826f7e84a8b616515e7d46d08e600f8b2d74ca64d496972677f131bc28159ea9795eca62be7cb032deda5cb7398577ab0eeef70090a377216775e4ee78fbbb0fd6fc1489d0f625f7eec4685eecb9b3900e0b5ad3f427f6b53e45bd274f6d659cfbe3600995855d14b3cae7b4b007d414a446f7ed33e4e51ba08556da22ee9efb176c8f3e7ae94dad2934b2bcf92ae4ccddc83e4c4975b72d451700934e7acff73e74064611ed6145c2665ec68da667af3ebe600f4aa7b726bed6eebccbf607b5f3aadeff931876879953ac26b5d405e959a3d796cc493722e94829feeb3df8c5e6fa6dd4b4e81309666b5014de1df7db700bc15317acd7234dfe700bc04b671a3d81e62a00bfb9b5cb7b4c0825e797e0e40de84d886e17249c2caa65745c182fe1cd3ce9795f00c1b94d478c1a0cdc727b11ab68f3c1472467e0932bb3338c1888ea6659852b68704fe02b223c891148724b343a1831a72c34d60f37879a794de5f31f7d086ad241edc86e4ecd868031740bd2b4ed41c72b16b470540e7a1e9c1c6f5c38d99b6da98a8fdd528f2b8dc330ad8173f813b053a540de532803a5fc27d4fdd2e9d3c590957c9080f6ab37d11ef53027aa01b7230cd40f2353851a48025aabb8d6ae8c7929bd17915d7cf43fe5d1e1373b4607215c53b72605ee03a61ce98975f290f4b5dd041ef25c846fb3dd8763115866172cea5dbbbcd88f00ddb6cda7dd2472baa35a467a9a7c4b39669fef368f700227206ae23250cc6a9d8d2fd448fcf6f888daa30703a8d5cb61e6fde963d0ea8522d0775c41d007e9d69a8544d956773d3d23f1b94bcd49aa49575f114b4bf53317210b605caed58d0222741b0270faa033bbbdc8b7465d727100e86e72f4b0d6f72aa05f4248cb6b1e9be7203e1b0e434ec24def7d19e6d467ec0860d9f66a5bc55a7ffbae4048902febeb8a1be10d222af1174920f0d830814f6f0e68b2bc44313cadbd8d3f54754aee5e3056010163b0cc2edb4f42e0f2fcb20bae29f59beca722485b532ab71c5ab710d18b250d835dc7ab2075a2c58aff4fbf2326714ed1630b2950ba7d70791cc34179e87d6b6953cc3e1117d4a1af4d44d971929263569721daa486cd5f7cf8a05173b80c465e042ef9de7550ebd9bcb1146d2ca3d134c72aad842fde4b71db7e6e42352817df3e5522d67a0572dd603fcb851de0aa55372ba50e9113cbae83eaae831a9a667c6919a471b71ad011df7aa91cbf38964c3059a7e48cecb666734591cbbf8b7093f689446dcdb149ff5ba5ef732a8591afe0d0490ed93279dc5848497ec7114aaa7a010bd18199d9a17930a719bab16349872f6166c82e2b780b3c467bb6aa376eaf99b90396af3368d7a6413e49ceebdd509bc66d669c5b03ba59268083aa64e9e4df84d9711ae7d8759a19fb36a575aed72112229e012e88c302b6ea62d9ea6ddf4ccb64e050279d230fff55b80fdcff8726c6d78f62db9d80471bd43d00ebb4c2110933bada0c2f96583bdb988c1405e42ddaa9ee853d9e46daac99df10e8ea8af1663df41a5c51e0407dc2688d9c449725d4c802fcfbac8764777258fa6d2a15f8d77aff0950d8bbd8f904d67558f9b728b0588bac445188e923c1d74eec2696d7cafbc4cba0de1df282247f272d74972d4e6e78c5b5b0fa56d3e7f78b941ae3eb2c2917811cb4a879abdb45ae0672b727df43c4f82e1d927a279afb196804e468252fd811ba8d8eb47271c8075befa3d94cd3cdeb4cd0d3cb6569db6022d3d4d532ce54ad7379944008733e1b61dba6a1e4e35d007001dd99ac10162739824755068c1bfc4bc510dbf6f914f5a004f72966bbe0cddb10644aa125778d24145f052a19812bd5dd340b1c5ad082b573f6df77b4e442fa708fe621e04b952425de7cd515e5a39d0ae4155e76a62abcb21605947bab13238ce4489292192de96d664f7bf4b7b8d3bd1cd0d27b35771158572a60b993d17c23716f44ba4e638c07805af193178d65403e222dd1b6dce8ab0567bfb8019b4bf826f823c2e197825638cdc17963b0abd1e20937f78df54ef2372d9c4b5be2bd4b25c02fd8926aab8bc92e81db34e34894b9e493688e7332c98168eea9221e27a5c1ce62411afda11c3d78049b6312560788ae0532897106e852f308c01d7d7ed7e9b6de7427569f41aa2688643064cc8e9655457fe082c2c9f72a23aa9a22a5ffa4c04e03e27a859b20fc7df56a53253868fa97fe883b6da2917a6116e7feecd283274814839d0f305526a8254fad8bbdffc1c0a33a1bcb42472f3661fbabcd6268a4143e294521e9760deca2ca600180e31d02658056c05e65f16f4dbefef79aeb1c4552490d70a50c600e014b26aa58edc6d3f8f3247c52c72325693546e21a7044fb5d50733bd4c3bf33799ac06ee5d8b3c3d1b4faec49301e3d3a756ca710bcc9a6d81281a08b1e84747a8e5247dd2e9f52ec31212918f34e7405db2e88efddc1a4301820c751e09b78142b6caa67b70307fe547c3b47c002d26a8a968a5169205266f7200adff6604067288fb71df28ada93c9d79aef17254b254da838c649406eda7dcaa2de67973ae51d64380c0d4dc6511de39737a721cf6c84b0655c401bf68eddc4b0a0fdaf6d12eeefd9ac91503f3a969fb682a04d1386bff3694cb0732cf6b1a2b8db1f3004c9879b178e089496ab146fc3d64728ebc5bf8e1f6cd365adc2f9647081ad0285e2761d462e7bb85569f5f90ac9c20aeabe6a610214471c53efd9de8f97576bc04b7587eeeb101505cfccf0a92b5155220296239d9197701a297ae200f5ead1e4125d97338debc0b49f474deba8d58b3e4607c5d599968ef0b49de258297db15b441ba312ffb6e9d3705f67346ce5a549d8fdb14ed1fadac9ace6c613bd2f92c6e1e1fd3bc8672ef2f4aa969aa7272861b66927f5a0dd41405c658547a877ee7f8f06f78b7cba265ebb30b9a41e96860d39bef101adde936bf0183211c4a55f7989692ebfc20bc939bcb82c74ff372d1ea8ac1761a67aa1a2b68393f05e27a8b52786d1b2f916e67e640c56320dc72218515e1fe6cee9377922499229841621a029f82d2af2c99e60c9ec49b21470abb1f90eeadd4222239ff47c4f1aa816dc932709ab9fb6066e5ecc1ca54e14772615f84bd971bdb46835cb2fa02ceac13c377f12966ac7c36485341e95625ee72c582e3d4f0f242f1e7626fb9f9af7549011d7b5da5ad8cd66a0a6f7082efde72943768a99c04ad3aac6527bda19dec0e63501459e0c5035870262732e0ea23726c89ecea910992c189a8a3cf749171082fe999f27cadefd4e21210b221b7f972b71d98dc47a4ba3d161575826f3ee596804b371d4a83b9508709197ee383eb19fba8f2f1f61f629ccf4af5c4b758bcdda0bd194839ce0a57b8161b22b3f28944005e8bac9bad5fd9cb8537ce9acd626f9e9d5091ce48e46b135e917d92ad704dfa59ab7a89163575577bb43ed72b8e54332563792c88b707aad6709c3b5829722e32d12fe8abfd14e52cd32aac1b34e4ad15ffc40e5d6fd8930f7a12d2d63d5257d495924209fb2d5ade974027d9156291db62d7dd3685e88ef80c2645e787725c618daecc259055dd2563859ff026e8a4b6b94af63dd3c0e0a39677bd06f87280316b0c0658d2f11991e4dbc399c6febd0487d7af41c134e047655c419a8872031d78ea560d29b4f115f0447915b513cb86968ed237b86d31c9b49381ee320fbd3be457b5a3833757e043d070ffbbfa907e7954fa57a9d20d30153bab6edd0d6217c185b84ff5635a81a79e7fcb1b4c34608c63708defba38a44db73d8c5d72582ab9b8a97681b270d955f7c48a34f36f8a9fff1103953101b9d503a062ac05c69ab4bbac01481985de78ce62897953f24792ab905b349a555814bde6594372db0080950b61107777305340210a60b7716cd0b860df192ac62fda637468c272c8cbf6a00a46326fb6cd96e4e44e54ff0d09b259cdb5d00c88fc16043dd72d353fd8de3d7d6323b68af821402f1e277a53e76f6708c7ed0686eca6dba768795ed165647cad6f969a23a71aba9ebebd20094c4a373d03b35c37c3ee12e786134f96bc0d4cd3fbbe8fe27d282340b2bf04a83b349ebd1be9d365d21325c528a26aca9f026894cc2c7a2630231bd1e35244430b8f9e05cc5b6759c71e90edb5cf7290f683eeaba257eed0759ec34c589b1e350a433bad8e165d7b8d39731fc8247234b17b8d3e49f78a80ee988476aaad086971095480f1d5b99c6cbf5aac989f684b592d6746ba18cb3ddb82b627e6e881c6748d87400c3914375c1bd97efc0b196075f00f18f669fd7526a313520fb266f82abb290fe3af8453c67430ea92ff3be90e47b28dfc33b6cccc7b2850ae2b458dbd7b25a1ec6b041ff7d4d0afba0e2020a12fe1401db34e4d1d1514a1493e28a298a04cd866de076ec103ad4c19da72f83ce8e576496eaab69b2efded7683af4dfad09b85b42ad59d409d58d8940e72726eecf8c3cd8fb9cc1602468ea8db3ba25c80f12ec486534c69b69f0ead212454c6a4f6acc12fb9fee6a66e5396aa2a0bb1ba0a091bc7db714b0c42af62c618b22c2296fbbf8215b6e3ada4daafd573dcf16947a9152ec43ab33f159f9e3172e47ba3c69b1cc0ac7c6712c9a71548aa94224e7aad16f413b7ef187ad387e972e87c1f3161c6aa1c94137c5969112cb3b2789d87caff5804224d31498e079e72d72a22064786ca27bdfb884ec2b3d70155d883fdcdc0082dae68e74491c3017201cda8cb9e311b87390b0c9acafe56c00f3c28385bd31f92824d9923a41f2b2d72666c3afee4eae86847b956b4994dacd8aedd05f3f3c522403bbd81b6796472d8cbad1481c6ee4ae65064bf99ddd583df2b599a7e809aebb479e7c9ff02be72f04cde9d140f663f4bd8b700a7979862a060cca5e9ede1b00b5c9402df4ccd726ce0d2240f0862b0946edb5ba52ecfd55d36b11257bd18d2e7af3f0b013d1c7288ea14b4b43e2ea0b9cde9c1c1258bc53602f1f8ef38c0fb4f9c017ea2d8d908cf8d3427d6386e1344fef73cf1360a514a85d5ef83532378608ee9fe379e6152f1b264041feb2486fe30d05c79ff1fad2f5caec864ef98faa3260c6aaa4630725b3d8c077e7198b2c226a1dd2bc7e1a7db2965d37f0987688f99654861d1f872af2adac30fefd4baae5941325f8a2bf2dafeda0784b3a48f0c50dbe744137a72dfe31162522df12246c54212770589d3721dd33d0e6644750776ca8fde699558fccdb3779bc43bcdb011f6f7d8ed4e6b37daa1015d06374225888788b81cf71a868b1d9c34a3531d26cfe0e4223105699be4782b86f3a05bb6b00a6df04ec61fe461f766181cbd607ceac8e01ec7c05000c55ba515a1f96f823f397d65271f20691179f47e8a53f7598dcf9ef1462305957ce0404b2b78b27f95ef486ba36272ea0a792054bb81c882404d154696f1b3ed6902b072567c2717a9f311f60c1109a58ef9682f53343071a46e47915b7e21b1539fd2273c7de18c98e169ff769a72f79caba90e86985cb7765a63447ece2e5c1c824171473d6e8692016ac0e6560e2cf60394a955817890f2728eb6c8e9aae1e4c5b5a76c80cb63c4bdd07e350d26f6e8490f7a36a7b2ac05a66266577dc8f289d8498891d9e726cb276470f130727ea4fbe0527067c64520902958100763199d69187e3064427fc4d7b216ed2572fa7178c2cd940fc1b8af421aa963d0b78b719c05982a376a1575fa58431ee5ba02000014ac577cdb2ef6d986078b4054cc9893a9a14a16dbb0d8f37b89167c1f1aacdf72a901e4e2788839202d0a95ae899fcd3cf4a5c7dca1b591cdf4d6cdf36183ac72a2036f92e522ee64b344265c29613ed1f58ec7dba94a7c9249f19cc7be25fe72575abfe59833dba3ca1668e66ba63dae613097b9de6d2e60d98e2381fc393972f823c6db0b1e2d78fdeb1ac02fcc5e1f3e73c292d6b8b18f58eb1196127e8d011d30ca6d7ee887b0113994b243cf03bf6ff672969bc7486df0945c182de9c5728cbbc2f7ab7ceef841e8e7638a1bdb589cf03fc78f1f548b44748084ad87857247e0c3d399047ae809989f81535bddfc187c13d3854ac0117345b94d976e880ebb9d0ee8a178be37f7fa87a205c820516014e7c61715aac105daeeb43ab4fd070c990f750863a70b355c063f6df5e1362fa06a0a64a73ac0390ef700922e5972eff754d3950bfa27730668a4edcf187e492796875e9c2eff783f35f52da58864f1853cb8b174de35cb87ca46259dc82fa197063b21ef8daa070beb4fa5a29b08217e91db7e1987069dd44901b5684bbc1f61424acee74e83d6d1dac2d1e83b72c3f993d569d49c1ee519860129bd72aa2422c2d9524815050b5eeb01053265215b368b3b2e9b99161aa003ed9a600b33cb0ef65a391a9514bd9b8819ee32db23fdac905656ddf082488ad23fa5dc55b6323dbb541b9641d22ddcbab8d79d137232cf0d6d873a8b1f488deaad843bd1928d907eaf6463fd8453bbdc21a3993c5ed4216097a86444ae262a8742a0ea10b55ff235de142bb10dffb09446db081166e5ac90e846f3f42b9cad7e88ef591b02da15b91c9aaa2c4671fdb1ecf21c4972cebe93407871549b3d0bc2bdaa1703b3a94f6f118b61c19e8605695d6890791fca182cfaf1b4ef7b9af53aaace49e3e289dbf60d150a1747092e49e57e507472bf259024544280345a697c762bb3111d6abd20d00681f9525fda61e4a438e872925e537d50521c6e321025592864ab74c067561540f39b424fd6df41e3346b045b0597b513ced2db583a7d2110b6ad081ac21748f2fd150ca4fde48ec52fe772f2337ca86ad04d1685a20d1f0f0fc4d6afed6c54d308aa4f38b6a012d134c47269146f276f3eadc417b8f479078cd11a8a910ba89f2801fc38c6ab24364d1d1b3ca6e15665adfa07ba3e548693000c8d420aa8b4129b25130c1f6e6458b86b2cb331435e8bce535b8a2c04bb5fca4f69bed56c066b0a21380a5d386d72bf1854f282cc0e2f95455c11e4f17bc25a3c6a71db5f7e7271cbc069162bd1e18d3a72359d64d0028d163f6f877d44c20d34ce4028a5d5e62925055dd8816c8a2725727bb6250833b5968289890fd40c1117bba31ae28d36bd49edc9eb2e66bd1f8b72eb7693c6753ff06c88586e6499f8ff80f8d4823a2d5d26f9b28c3e5ac0f8140fe830d02dd9e299cb0588e8d0e15d4b56b4c518518af6fab1cb6a5b7359c74c72054553d07d6efa333dc0905946459b0bdd8a11e4b51e3b7977cc1261144b97422519c35aa3e8084fa6206a18b008537fbb61a069089fdefab7f9e965566ee3528ed54581b66d7db8cadc3429a0a67dc049e2ae29a356fc49a26467ff87ac38526074c6793daaee7944fd835106f7ac601e50922a1589959a86194487decce972e786eecdca962ffb1e5f4ff09b5b1047b467d964abb29a272bcb6845ca358843c286d69ed1945d876f511877204cb88180ea96ff26f9c44744c972c86ca05672a7bba054aa66d90e276a8cffb337ea3c1d5e4800a3c222aff41477d577331346bff93d13c601414967de6ea0a8469a3042400177ea882ba971f6d6291fa72372b5629b085f8b37057c7d3a741ca81238fb1b22b0d051e20079d5fbb0e22ad41d67c83d1f8fe32814409067fbee2fba68988ca2ca0b2bdecc66ecec64af3f1e4fc9c024435965afd396166ac4f4c52b94caf18d5a33c1b3c64e03d2c43238810c8f66e6da7122c24e5a206dd9aa60b6ccbadd7e39a7ec07d27bf04bb794f31672dad283f1b0bf3c136a38932636143b9d584abffd969a342747bc5a016febdf235fe67654722f87c7c328a9c1f44e0b088e6f925598a063d48c05dacee6ad8972c8da988442171f7c95dd53393b37e8f62c3b6ac04c5eddd7fbf37d6201227972bf74216806166bcd2a5b6aa1f85291846cb414d0dcc3aad830c9eef3c2194c21bdcc10f42ba466f7821125733169c310bca61309431d9f81daa9c916dd98b668af35db1b46f45bb9aa5db412e990e6fd5af94f25e15a764f8dbf66832ea77821192fb59acde6840e0b74ba16974c36eda34fca64890823ffcbe189f95e27370e62eb13a10b120d66fa5f149964aab1c002a6cf0b0831ed99a77f572b01a5f8722c34e1450e368fef3ff7754e1c75f7c7c1f02c3ac97f5ba985850d797cf14b72de0694af80de0a1b7a0fc4798ecdad57b8871474b71e17ea3dfa520a43eab0728cb24da0ee4c453152258f264f1030767e3c91f3858f9b19de3d12a2122dcc6e1d74e635fff30d9adf10400de6e3fee3a5ff0911a7f2abc1e87b828eda2505389b1a80a412f21790e1d6615f17941e77678d3d24800ada2a446b3c48c5b0cd6d13ada022d7b3bfc64e0e2fa6a4e4e98d40877886debd207d3343650699b5ec721e1ecc565b7709a049e6fd4e74e714693a2b356fefbdb3a18de32b2bbfd7ad72eb7d40c6c00c348b5c95ca2fdabefe99049d7ca320f0aa4ba5328a503a2d2672dcfb7e49de7f8fa0108cb8cf9c017fd956cb5fd958f5c099ee87f4c2868307728eea709386888fc54eb79e3081482ad00d163e0c811a3ef1027685d25a541b1dae9262b0a0958428c7673dff3f96b581808744b4e3e2f0b188dd7a3cac926372fd5ed38022746a39d9aee6dd92ccc0d85230ea38d02bae73856fda3f98c45a1ecc7fcac7ccea2719df18d8e85e85211c1be9f9eb96e1d9779e880d75c61b974c7696580e77d9fdccc963107446786b1eec9a9a40e9d0bc8c0d76bdf82c8f535e0de5d7ffa609f2d9cbcdafa5a9a1a673a035fd98e4b7dd628cb129c985f322334e26735c0c4a1500f4248b173fe27950f8dad1430406ddc77bdad56bdb3d57720dc49cc83d7700d69ddec1a27e74b09e87c7ae0da5b6a5b49f4d94dee56a69548873191b7b78e1e5125a96ad49c97955746912340d471581e44d121db045912b4c6a529938965e2edbc84d5a18dfaee520d0573ebfa2ac52ebbfef8a9812f17221adc3a34943369da665216c8ca28ef8f22ddd6ca4e1d61a5949904473fc6a7210432d6317ef085eaecc674f744945ffb6143d7bb000d16a29c1e1e9f92582729437442f88dde7fbdb80587a53fd9bb63a23e4b8cc6ed714de9a61982e86a972321f9496dfc4969a33351225250eb733270d7ba27d7178bb1da5206dba809f723a1075378a98600e80779c66acfae312e81c1952046ea87d111ca6b1f27379543d62ff7cbdd77c5669689e617fe15a12803a017c1c5be7614d02c4c134a4c155740cd049d767acf918ded9c8ac17da31663f00b85ce20642407add6ea90875118a5f6ceb73bcc25c29e18fadf050b942f27e5ae874f72e00e4567bd293801372869468159d8d9e7c7349bca4f18c225cdf65cf024af00073f4e97b0f8d812752ae1bf8cbfca60d7f8f000c115639c1214c0d565ec5b26bf9f9fa1b10bcb13672b2539e3c65dcca8a3425196705548cd3632410ebcdea7e6f6173f6b74372ca1825618e31ab15b67fa52c933f544284f5bf91a00146f675b137e9571b3c3d7619af56a2badcd6d27d911c9fd7077bb48597ed2fb8422765071cae6af7227b6e61a5bb64dbcf30992866e9694420cb7c0c29b8a75d7f06644be43812ecb0e3b1654cca44c96c755537af3fdb22b5e78e954297e90e859f13f3d684b4cdf6177f72eb80c9fa98ea4f6fb95d05d13b8a8e65461409bdc76dcf3421bc40bba9966e72057c24a26e4382efce86964f9830343d736389132c4635c1db160db562598f3a5dbe25558b5d7658a3363b26cdf448203c276e9981200c442f615e930f380472de0e440630accce610b8f7f178da173e3f5e4a19d3afd3f7a9e215f4cab19c72ac28feb8bc953320845e18d29b6fac9114774e9a8d8b4a0b400d2215ac0ba9727d84fcf528bb5911f2b8f9c34fc17d3f8b59b0908fc27dbcaadcd6bf0fe9397258f788a7b7ff376195c1cfac8cea87ceb15e2b49de85fc368b2cff14d8a9936ac8c21242cb362fb320b7f2ca6b81be2e7bd4cf94a7ffbaa0dc45c984b301f6728666a532ef96326fe144dcf6bef4182a364646b951efc4f6b6e1adcd4f78d559400c736d4d718f938edb5149087c300844b6ace43c650ab66119f4a1b0890a7282a7bc70ef761834dd608a14b85323742bcbcb87952b3c7b012d5bdabf005a150a57f9d5af901d7f655bf144b4e597ed2ae3eb20e04a857c50754e428b6a884c765f45093f2efe01f4ef0a90dd02933509c0147d036e33b1bfb17dcb1eef7b4699621d94f68fe006876d2cca9cf8d83d37daa66c65294b62f8cf02689b79094dfbb4e5de9f23202f5e5628078f862121a74dd2ab39c634f0f460c3e0f5cdba4c4b63becd068b2b45b6cb5e396f8def83f39094798d791af538b0b9d6bd1c187260712823efec3c794d898d79ece1d33c7efa9a865e29696d8ac5ebc9d94e2d7211f43b2fdcbe59833ab903590ca72fb940762609799f2d0cc9c11fbd61fb06722971394470f0f5df0480f1babb8ec8074f3f55aafc94a10e0ee76f7c2d36000430781d966d94c75d10e50dcdf1e6c7d465ce1a5f13ef9603c65c0db73d5a3239e6a3e2abd23cde3c0a22fae7206bbb019ed72d492b5c2e6f3652ad80614d3072c5ca62b6be50f25992c883923b5c80dc27c297d96a83ecc092bcc5e41e530472f5b4a78abbc917e34101234000f99d67f208f4435c200d3bb44bd4f0bf9c7e72a999eae439151a6d5883860354edb9c7df8e6fb29f83e1907453ec5d8e4ac572c5f05b0a58cce9270e324f6990cafad24728409959a18c585bda2c16ee6c74504d879b555e545323bdc58fc0cdb056f2ef8c9646a5f7e8f31632921ace4a8572ed6913bf52c3525d7e306f65c56a2ce1ae77fd0addd98582771b928cf0e4ac1fb4e10b4ed88e2d57bebde7f8cd627fd6ff060704bbcf66375cb4db8946a75c724b7b48ed85a36c4e7c089098cb8118398203a0fef7611abc99196b4426fe53729418f66a0162f97ee896948d73a288732bece89d3135f794905bae5a2f0d0961a3b7c2d538ad85603942a514a5bf907a340a617fb0dc03aa829533405d3e552ff20be265509c15d3d50e4ebbd00b6db4d53ef4187aecbc90b853b7610a710b1ee93fb7482c6ce184bf3450a53b1fccd5f561f4dd400f582ca5e19666c7a3f15c843801d949e5b1c4c422e987647bca8175f83cdc4fa0c577fee2a51d67812a723bb1fe5a59b2f16634b662dc49df79c8a61ffeac36cbfee2e870bd2ed4f8e772955b6a5ecde6b6ff832a5430c1422428a3ad829587564736a2670f16620cce72b2f8820f81c47b6eee6d31eaadf2db4167f82a8f29efb89ce99d91c9f4bbad727608383b3590882ccf55b4f20916700a5ceb46275f18e061739954b44b26d315357f65cf7c3d1af2753a61c48d4bc7db26ca8dee0c34762fbe6881ebceada331d2268ce3e1d129ef7ae9a2e3868f26dae99c6b9aab3b65908a0d880664d4bd72013486a87d7f559b6a067cbd16c2ee6ee5346c7cb09c71990dae27f2cda6cb720604f0688378bac1ad125f9058807a83be4a1fe7466328edbd377435927df172f9b0218eb6b88a77cadb5699936d97225d3d7523a3fa8963d14e0e5845f8837200c4e74f0e39c18054fcb0f76c62bf6dd1fe8536f439f8206fdd809d790534729b77a1892b0a30e2209c4fea81c2e2f848e99683a3490a90133bbb42ba745472afe337cf46e82703391c0cd870fc2f33323a2d310fc98e4fdd929e601a28e00c33e81c18dbc6e48e45f2c5c67d1f10ba37a78d49bd28f8e5a0da0d557aebcb72a7bc39cda178a8bde6fdbccf607507f97aca572e690a815d312cdd5bd52a5e72506f86176d15ad2fbc79b15d478abef098437005822fd70ec8db9493aad76d618e7b146a04bdda9d89fbdc2d1a33fc8a54f9bdb826af84531b5f84695b166272b9287a57050fe0a01aa3ad5e9b197f03091c0c463d5588d6b086d24b6dab9c3ba97798d2e26f41f215e5a85d39bf35e0787f418df97855a3dbec204fb4b8a1727cf298d0d3b5136587db3c24c370e890570f34da504ff0ec8e3133fddef11d72caf9fb3773a24661bc1d649bc5c905d7574e3d5541e3291df78af010bd1a64155f4f26cac7d75fb5944d31bebe23e111e14c8e44eaa973827888e98995086c72323d390f6a8d5317f07601b224f63e06bf30cba24e21bd780bbe4c385a4f2a721f2426b2e9f7cf963552f6f238627bf79e1e18630ae90d7b7ba8040117b123727c90114764f1e802d2e59973e60cc13590cfcb6d25ec1d346c85e7cbff8691029f544cb6b3f6dbdbee5a7a896951bd71994a42d1d1640fefefc0d61db26af75eedb9be5e7eb739f53e451ee2a09cdfa1567a01cb214dc1d3126e46fb9f628372aca1a418d0d44681b7e43dedc7455b2a290af638a52f5920250dedab9ed9b81631851a1cbcf4591225960813009010282e80e717c4a22bf8c43b2062930e562fde29d5f7489e08def32f785883a4e710fd0b2abca482112016a16d09668d1d43ffb4e71175e5d13739a6b1ee2f7cb049b532fb44def8180e9f9699496ccbed724dea3bb8154c73b1d9c4f2a2b2ad5bbc6bc3e42e2685b3f85d42866cb86b1672ea7f8bbadbf044529f6372170ae03674bbdbfaae7532f70ea25379ae4b491c5296b74837baf45266558667019965ea3c1e56a15b63973890bfec0a7f29736609719040cf60c76ed136031f617d121f0aba3bafcaee847e42af3e1586bc91d72dc6d1ab994f6b44358f4b03445a8ab187e1bfe82e0324ad83739ebb599ec5337283e709448fdac2c828b9c0a219424dbd2b423b0bd230115786a639cb2d5fd021a619558e5ef77387182c014fa2ada96260047cd8d85812e1b00e62b195c770729983f96ebc4f8d372a6eccf56e0661f3bf5160406fa566b2711c7890cf568e33cbbd6706eac6b49621346ffe3984f21699468a0ebc577f07ebd010ff1c762772165d3d44afa76d1326b1944aef991ab9582de56c8bebcb21c78cae74e6ae763a2de11426e46ffe717330d4059f819e2f5efd28ac2905b62f7d88057dca0f06721cb259c1d9c737a4e00433188c16949ff3a3a0c5862fe45e19b9e7cfff8aa47270dc601c0ef09c7e9d11830bfbbe65dd57194e1b8ae7d8d8c6bdfe968fc709722d0181504416adcd01a63ce4381d35f20098eba9d0529aa11833da349de4a915cfb59783d350fdab5dc40c304f27a671b49609d2a58e5c5d2af24232d5c91b5eb94ba6d61efd4164abc0e42869d089766ed132f706508a7b6ebcfb75e4bdde5ece0c8e88916d5bdebf47a89d027ebbcf84350be0d3fef2c030cd7119ad1cfe6475bbdca26e720656e85e547739eb931c6b941b03d063f25484970b5cb5054f2632a6b4cb8b37768a720179c2f06ddd23ac73854a8a175cc86a058bf2b675b66e3fafac6f11031d4d7fbb9c49e1de4ab0b436b1a5e3293bab1e8c6a7b81a2391ed45f048b520cbf49d0f8a878b52c3ac47de4ea283f72691f7816294dd105e54fe67f280d10f6ca7272972fba9c00d4a77bb00e5e563ecfadb0163c51c70c5672c1894575913a9197c45a63fa508e6bd53904ad3676ab0657cf7f02224bf58d720e1d4d391412d3ac1851fb85ff43bbafcd63f5bd868bfcd87f27bf9409e2247257a850d61a53cee738ef2d69aee37baad49ecec5952e744bd3df17e0f266f33e8048c7fa3da02039d046da364569fc56cb787a2112a61b5399e4e525673e877275b3954078250667b7241ca511a2ed93b2b5a4a6289d510b506fa12dfb367f58bf667061ac39d902edea55ac2adfacd522a69144e738bfdee5ffefccc107553af6e1afc7b7416d6f181e6af363ac27229dfe8049e678de933ad54a1982ac037200f5f1a950a3c9e661b3040a862ef95693a725bdac6dc207fcfcfcb32b75aa72f01f17d0e1afe05482d616a754c831e273129bb39aea49ccf08f4e15019bfc0648d6cbd68a01abe3572129ff984e735e46c6c2436a8d9f57551e4ff623e0207221b2a5275e6b26423d4ec97f195ea7346f66cd01f8c53c4daddce5e6b8c7f160e1b6eddb41ad572edaf3d9b14a8feff278d79e1428257adefa14f49bbd72915f83dbb2670e51d13397b237289ce09d2851480859b8cb22936e571d913291b37231823104daa8b74f9ef8ffcccd672e887eae17b69af3d62034e3043d78df1972996108e151e8de673a2510f21f1e513dcd48934add634b7dca72144cdcd78f72269b4eacf7855ee97aa65f08a093a557b6832d9fcb976a6298571c6d717b53726ce7da72b59c0bab681d2d2d296969fe8824a58222ed015544161e33e0c1784818962487f483069bcc8a648443eeef1873b982c40821507d960fcc2f9fc12d72c84026036949a6d34134cf8de96f1f4516e2ecc2f62b1fe565f6e96d0b372f7270f0c2d0288c7d562e26df70fb8187af060f20173e74be1062932cf72fc4d14718f5658003c5b92362c2bf76c65f95c501d1592feb1c3cd3e0dffeaa98673772b4b9533e37a48248dc6667e3706034c084af9b220ea4cd8e0df3fe4552b73f72e26cdb24d9165222758a9b590b61ec55c68ce6b66d286ee29a9b13137ead6272c97681c73ca06ea0e37863cb95724bfc2d626b39a92bc5b2d627bf06e02210726fbbe6a5af33eb10e2f605f6b8474ee5db0faded410fd67f5cff8f9184ba9272b989ad691b5dc6c0409586b60525a9c8ff7fe2bb199c19c5ec8811fc223f6e024d9256a6583a11dee294608531ac7d07cc6bc4cc9ea5a75d1d7d3d0ee6974b72cb3a4c8e61071d1717445872d6d2dce004e1e52ca7f96fbb109c513dfb191e55242a6c0c62796131719be69f7adce22ff912848b8826de12cbf1f9caf8f57c29f4cf99f75b276c5137c105acf2a4a55404f13f9c54c1e632b5011b01cea2db72e234df9def8467c1748519c76f334277df52b31759a8ed15020e8412c29586726cd9d132240971cb55861e50a9fd05550f20edc71d4b1bf29527b1f1a080fe72e0a05e0fc0d6ff51d08985a78e863a006ccf2f10612b1de008c3ee4cbb4f273b299b36648a8f382953e99889382ce32c1cfaaf0de75b8df81ab4205decf54e6082a4e0e0573b5a5b2158336acf2d5b7db02cb502aa1a30f87791ece7d069c47298f6fdcaa5f9f399ba323ed9f529f0dc409095f17e86ef163712592b9eac6c14660af1cb159466646e31a4a384fbc11123ff07d06a6a0ee337b84b5ee15de772dc967cf06ef8689d9b3e857f0afbf38e2235bb03826e165b352df31612c6117200462539e402b5cda428154a666a659a965bee4fa63f57f6f2922ef09a05497280fa25ac08e48b14ef8db9e12892a259a32fb35591928600461db3339ff06f44f3c9f0e419e9b470b374f50ed2bb1cf560f190c251c6650f102cf138456bed721d0a1091880eefad4eda06b6ea11a2188f7816b66e089a8e90fb965963a2f25c32b1681e0f0a1248098e60664d9239aa868eaba4ac736bf3cfe8998f8326db729a19f39e11cdc3d3f1798a58b6c3bbf248252df0399c91f5b776b97334f5e07280b6a4bdb5f2060c088c04ad96757839f0fbc28d7321d801d629cc6c193fa272e45ad8cc2939a8fa2c1f8cafe1847357ad42e5dc6c6c510798bd7d387cf8742519cc02161d1e9f1c5c6c9c79f5c8527d36651fe4248c4ad853af0e084f7a3421704940cc759bb3b80238caf1dac1f84fea6d168532b988e978c056951380b372854144688d7aa458d5b8c04c17b8afb351e814cd20e8bb00f9c432ccae763b323ee8fe0015d39b9fdb89f6017a238a07d6c5470b4c15aaaa576034a8d757927226c4a777aa3ca14d279e7ade0bc4a2c6cff0a58b7f31daf6696e7e7e31ab2f5982011c78bb1f43c02c1eb9fc495ebe57b30a6b539fdfdb4fd09fe27d0f1ead5d0b257008665c0ac9beb115c9e7e8aaebcbafa3f6510eb68a14a191bbab790972e691865b8fb1dbfe36ffb1d453da345a32fb00ed688cfb214dcd5673d40f1761b72311a8d99e69db81b2c13be610304f7537041dbedda43e4c33ca6995c41472a2d3a442131e7249314df19a11d0bf470c7c5f13a87ed2f432c752f44134951cc83665e1d18e4ea6b473f7b0711de5b9b1dc8b92a8ccfebd601aee0dbf41381c39b12b5596b3d589811896fb2c899b2cfcfd7a6a3c194ba2d5139f7d697eba0771c0ebf01bc304a72905fcc814ddbd7bc88197d0e056af242ae6e18386598372cc9ba7b9e367b9a848577b844603a369404b7ccee923af72f6b3f9c70915c17276ed9006ab59cc35ff1b8c3a46887561c689e614d921d2b34024a075241ec472b8de3b5d938086dc9fd383691b06c317c1938c123b45a2a6960ea0be3cb9417231436c8da66aafcbf1b447c257943d37d93bb161d3f4d00365141ba3a0fd5b5d5b688ae54999c86696da245aed33aa2defcd06294a1deff35f96bce18ea1a5724e57f57ca6d60b238f47ca3c4af281c4a1d7cfaa259776528d4e5b95e4636d467291dab10601fece531da3e2d60d534c4a7caaf045e9d8b624ad71c4d1409272fcb86c0700deb8677f2e25d9056dd3f89346bdece56ddd956a44f778aaf945725455f1cc01217b41cb161e92d0eee0b2e2205bbcd88d13fbb3e443de4401e12d348f93182753760055ceae15f53d3c7c929b74c8c8ae7d8915c0e4ac61373b373686bd1dbe87b647a192a961a07f2543fa5fe2328c1ddc61ef0a121993d63667d51b13e0f98fd4b8ab9bd499909416cd29572733a423687bfe4e159d93b8327221a7c271dc8a9aedd285895059077dfd9eeb2f8506322542ba810c479a9aa916681c7202a5cc1183ec384e4093acd18db692d86ce4c102e1804b2517de7c6c72e64eaa8c9fef4db02eec528d4cd4e651ec567fe749ce6ef0aa33befe8b1d4f729667ccfd5bf7a67b65b00087b4f44c8ca26532e9ff34d1f65486d2aa5f6afb72b634d9115e7e1ff1ac7a521cde3732e42ba1586040e0d3f840c24d3efaf02372f7462876541075a544eba60faa60f9a0544df492111490c24f4bf289e6116a61be936f64c00a9075c843758c5a579d9b6518e9a39b0ad77e1cb856c61ac71272f75b15b811928dfb35faf4d3b034a8ff67d12baa6ca98696849e6b8b4e3cb1725b1ca9961c09097837cfb6ce2ee659ac9d9000456cd0bda862bbe635580c5a724f386f072419d9a91fbcb2a61529135253ae9e74c8c4ba9fe1db7e0e46fc3d72ce12dc06fd94495756410156a02e45809ab28292ddec10747ff4af1ae019c072c3b58b48d08751d5a26d1c27793bc5441f0c5e6d3d5ce354e6884befceec2f726e6cffe5dfd0934d2c8313206837b66f14d3f9650bcd6530c28c1c23073293723b1dd86151b2217be052689d8b59afa8c659ec49b40c4e71b166b867e05e2c7240aa57979c3e1bfb201149f2b9298478583e328c93ad189f949a663a692f0a7214ee33f8fc3d72e4525325524d58b28481a9e911df09d3e544c3673965a81572b5be5bc115e8378a1b60cb2ab357dd5e788382a82294c2f5f73cc9cbaeded57288576bdb2bf7a3b427a9350a2cfa6de9ad9edd29a0544e6e798e4658adb6c855a88670ea390df31f9555f432e1c54a36183e4f62291ef9b162a83c951a5f8126bd0ba674afd438c29bbbf869ae6f9dfb4e25aea38406c357c7418391a192173f13430bfa31c3dc1665defbe2621fa17e62ac298703c09639d2431917d92d6f4a98bf9a5e7d2c65c75014a4f17fc38b9e309ac273c29c2735ea72c2d8b95c3c728cac500310ec3bfcdb175e7e461236c931ac7033136968d83283216d187449726ee81be25be37330b148d2955a232447560888aeb076b0f7626e104fbdf05d725ace1b2bfd324fd21916cb9731cd298d11fcb76e11fccba3ad1ee46ce0fb6324487f8ac5ed17a419f490c62aefc124e104724e0b0edcd5325fd93e1609aa8a626d913228d425442313c7b6c369d52db1a7a939031fce088808f0e88b978f12727a8685e6b7340790ccbc328a131ce44b198f8a31d299e59b2ad12d3a75b49372c377e63bf7d388c97e7be65fca00c784d6c395042530e33fb946c0dd6e900a032a2c9598769fb6660209f806dee24d8b895f51999829f23606c9fcf83f195c72a3eaa62ecc5bd34a94eca20b43f0670c634398c03f9359e24e89583073ba0b1a0a0f15dea9009ed19d215473219c92a68c2e937c00cdda670b6747f10c4a1c7285df6b7b918e3507759d517bdd23b3770468b072f27cdbf18878e27ebaad4e3c3ace95913f27fdc0c59c329d5499b60c2308b9969a98d2eb516bae9141c4431615064f3f5d7cc71ea0fd01dc6f66b041789502c97527b1e24959ef5ca54253728e1f440c6f050a1f8579d520328836a5ef577340554ed66a023a23d75a76b8659bf983e86596490a39db4ff1a0383c754cc60b8f85b4f3454e44cb00cf6d5d0c5e2ffd2622837d4aef0ac6f5777208918680093fcd0f5c77e955a366f88d3b72dbaf66ff0104c18a4bb2732b17da0dadd0f2d1e3aa3ca797510b9324cb7b873de543d3d1757d82aa95d1db4f662f081dc516bc3811d203d697fa73992b99c072adafd2051dcae683e724e7d1c343c24fe2f0f7f23a5cd59cb7f0a28529a4b0229b183d25b3df1a70ff9fe0ba101637290cfcdacf4d039f7a0e08103c71718b11b61b217c60aef526632d6257301f42d22c746930f82416e11e79a22dbbd0ff374b42c4491f3b7343028a361c2cea46c33ff68150a2c97ca46355583a9b1359728bf4e48eabc81a1cfb07b43829f62d9602b711f102beeb362dd5b4f0c06225729ed10fbdb7272e75ba646ae48f9aca9444068e81c110ed884176a1464ae0d73ca204e6139b1cf7f77f264847f04cb4ee6d789fee03c9cd7aa72197b67564df7217dac3cb7fb9c2a6869326bba470cffa2a07fdb66ed538178e87ce21149d4b23818ca5bc5677eb09e050dd4fd18c463a82eab89d26cc34d5f6ac7aeb8a6f7a72eb91c57dda9db83a5a8a0a94b1d4bd96ba44342fb6f9592194151b9ebc2c9a0da03edaff6723e79c2dac6853e0477cd4d7d5330c34f977bf02578e46ed724d72ce1078107b5a561cc2312609fb1d44a7eb5dd848a0b7358368ed788bd1c43534a0ff525623159e87a8027f3bb1b1663fa71dec3d25374e0c8a6191c4849c642450ddc4d94ebc9291a8dd0e8a6b58a220c5b8a2306f0b613df48b757255e7737219fd9bfbec17237440dfac4166d152e9c09735178c04b4659ef3985a11f7c672d0a9443fc63659695e3e93ae6f370e5478251f2df12e45f5322ba4ca106e157279a34991d3ec25f3bc72533a19330538c1347f3a9dd1787ad3f79789a7a6a62437abbcce163980d14a2e293658eb0030f198d05cc80113e6f6c9d5381e078972a363c971094e421d8b98d93f62712c7155f12377d59b8970c20447072098036fc6e47e79d2a08734089eb98ad8d3538f0d36fd9f882c08ba7ca474e0853b7c7205d067a47f6047cc447abcb8592832ba512a775430ec787adb49c7b4d2c1d47238674b76e2b4b4b9397922a1519a9c2911ca69f552e82d88a3041a9ca305d67272f1a4beee69038966e4f6005244dd41934f5f3303ef4c03b4ed2600f5e0c8462ee9f965fc5c3f5bfa203faa6961e7b4ad87c160b585a5a9c26fd77e4011a6724138f832b23d94928c18effebf1b49744973562bcf05a579852acb5dfbbdd472070227f4d9c9511d72739a9a15cf2905d38a77e2e5e5c559bfbdabc7d822b10474d83bbdbecc4881dfc204759d5b9fb234919331d3fc30df1cae1f552ceeec7213cc9c95f42e7c6d4f80f8a5a80b54c6d848b060dcec6fb4081d45d09623de3957c048610635021f19b2c4074ef78ed19319f8b3ffa09f534e6ade5ed174d515e09bdf7648d87ab9707947999dbdad6e3efe997ef1ac3d151dc3c153a4f31e6e724bca15b0fbc2d74b91e92c8a21e575e26387e35f7e6cee92796350371ba67290d7557a79da10eeb6b909f973efa861f39dd9d0f62d90cc3e8ddacc67669972f8da43a5a432ea1719747b29d23c05d92bfe24c4cec48a191fbafdd3e6426916a9e64a554037b6ccf448c84499df9189d2929f17250c20f30f98b4136289d572d489ca8ab087ed67e4fdcf4117cc16bb8ea3b7af429831d858b920f491af21431802b1b1f8d6135f2ae705064d1bcec268c096465be775b9073f239e4a48f372250cca8bf6f8d44e7622d646647103e5095b5fbba40a8c2f2219e26c8cd8ba72d039827424a8933d47de72b67dd387c65312538b22be49e036e64bde1738ab72387785b7a5c6e2b476997a60b32bdea99f48832a2938951d37510d87c84ac672870514eb2a4c5d95c32ad7d0c730d55bf269ec370185830c9ee1174fbce6112bfbacc401be6cab2f90c7ce5915eccaa03561fe296abaef4ad9d248c676970f72bf39bebb213b251c374e77aa5f98e71e4591b1f48f5a20c3361ec51762f46a72c4ffa150ffe26326d806257f82287c9621afd52ed1a46b16909d1771c4606172d65a2bf8f2cdbb1fae69dd34f2683f65cde7ed256b11a868231348a56b61817279532848cb5dc968c00f4ed865c654efbc00db124f63b5f2c51be3828fd23372adeaa402af0329fe47abdc1b3aa6bf127367adb07bae0e46c58a2427afd89a72e20c88baa93aa8f496470d94e7d49e68d23af42d60cdf79b342a00644d2dbf724b30462dd466e6ae68e725ca159934a87d5af47e6b69e3fcf15d0f7c18ccd80ccd23f1ce2e6543186419421f1c3cca8d3f5925a015865f6fd1429419bac536727e2ad23ccc39dd4c2da5cee6b19e0861e11a22d3c3c4e635d001fa1e96e1827229ae8b24674045330734a78f9578a7173e2e7e784150247eb6097517f66f3c7219ec9c39868d6850592961c6c012c7af20c892af6c95eec555c38303ead14172621356c1422b400750da77da3d0e14b03035cd6eeb609c44aa6ff1b65ed17d2130b3fac45b811e71228924ac322fa5467fc7630b0a278fc0159e0d1c2d2bef72597a9bd729fbb45c2c33e0712eb3ea93c165a32a2c08549f59e4854957988c72b113e4e52d3800e11d7af6f0c56d2fe0a6b9b5d4c6507cac9d88a8df0ddd345faa1612dd1b193c260cf4e3ff9b113ede18e2f45d5fa59388d44dc156c66dd87200c739bceba2ebd0f62d4638aa572702baff235146edf594f9f4e30681e39a2982071f86d21721a89d9af0fd288e0005d0e1a44b13bc26f28ec51aeece883672e30fe871efa1409a7d7bf660ac408f97d735197a0c12188ad8bb66feab029a72ec4924f4d78e593cafbd7fe9b984b6a0b976c3ef5bc226962cfe23c49b64ab72fe974ecbfa56fa0a13b33c978b298f7aa653a972ac8fd0d9a6a9af1214a27c487fb59f36ba1783378ad3be20ab5c038d19cc75310a0d0ea1e440a31982ad980d27e63820c0078ee3927c87120c80ba9c58ff72f995b2987011cd095fff87d3727db5681f1929408c9635b5ea095ce03237f299ca628720b861fca4c9373b7c72e77cef9acb7bbbb638c92df336ab8f961f31e1d7751a8e65570eaf539c8c767281ad4e9f7098949355160039656eb9a29726009ee6543f628dc48b34eb41105457ec96ecee52c9f3c484c404fbc20b0e7b910112420beb8ec2df2bb635d2fe06a44c76b10c2c55b61211ba7494b36af9a453e4649ae2c7d425f78bed517d803c4482b9d93b1175cf0cd65b89cfdcc6c081fe4c869f43ee50157527e35794a572e7f1431409feb099a6705b97f13cfa08b3a3284ce931ffe7cf8ce43f49b5f6722645efed20b6557f64efe03cfd379018638c223fb38ee921fa341164aa0592727f26a36f7c39708004506c874d08a1d32647f52d5661d8bf300c575d9e76d172e9b8513272c60da3ffe21005da51607ec54171dae2f4e5b8def1951b315be43b654ae02ebee48ee121b88453888386fefd12d9cabf7e93a0d7083d79d5081f726a5c3d6ed9ab992afc68718af23d320309c5acb1d3f7bdf1dbebcd2fd4a1972327b7e1f84a2805e92a2d94d032efc27a4a75d6da18d16b2e0adedbcd5d49e93ee1b451ba68dac172ffeceabe28071cee275dc5788e14f9f4b82dcd363c6d4f7292cdf1672bbb9cad6bbdc3483802db1f4c2c1c4c097bfdf11a87a7edf5d1fc72e289e7c86a59ae311827f049ba6b34e94ae1d09c6f9c9b2e472a8b9b1aa4a1536c9709e81ba8545c10031cf3d47e3c125c9e8c982f5ff24fcc5437adcdf207518009bfeb5f272ef668b7a76538b5dce02de853dedebb55a8039eb3e99dbacd722e557a05a9e227838307961238ad20b0ac011b0806e64e7f57ff447121f2eb2c8ec94feb4ff75850bc7d2061c025e03a4a71dd16f8bf5827dc38be47634d93721d6d794047a5b5ad6244fe983f9db1cda9b8781b18062cb14671ad5d48581e3de217f2b73281f2cb53f12dd0b4dcadb622fafc3f1a153386c47c82f0ed8dee72785536175a4b77a346247ce790b00f0fe7f43c606a29a6209a9b965fdd0f8412e345a18c09f80101856b981ff2facbe09deda451e8e5e11d83a9d5e6460700721705436d7f03b3da482fdf244690248cdb8c354093e6abe26934c328616b4461136df2d2c066983d8bbb3dabfc41ea9568c1ffa2b3b9ed9b127ea645a5759522fde320caf219e2532f5b0ec4f8e272ba99e115fac098c2b78a3b6fb33e63cf23780fa4016cfeb28a6fc74eafac332e4ecf9efd494397e27b6fc9f7b49da79a18a7d8c999d02b2e4be39b528745c93197b1826912a4a44db53e86d33e426cab721d02b970c879020ba2d25f1685d1b1dc60e60e542b46d787f216fabf76578720a4590296f5783a6b2a87d7772a750f0c1c86ee9829e11f75730c33ff5b14a73e8b75ead820d5c6d5a3df509172284eaead79f7c27142cead28fbb3e0317c0a729decc7dcd65bb92a5eee11ffeb0f8b844c65aed0e367ad95f17efeb432ffac70d9d2d7aec56614a50e677a112834cbd852c040bed3b74c3456dd3b237ea481726db8bc8da5d20e61319f65e8c56dce679bb26d4141e80844baf046bb671e7772afe677f6f2f7b92f96d5891d46ad9b1296b2e79b6356f4773b93ce2b006f1434f7dcb6b594797d9db4c22930e9f50c42824a72c740b148c73bf5c46d8c8f3c72513a61dd5999c3f6cec029a556231e98eec7f22480ee46c71e51c89f62bd0072cd87a4d67837d27989bc339f8c3dcb8ecb2b1933b86519e791b631c66cf4d4723e83da37b2eaf1b0d2780d78b743c825e00ca8845c82b9ed56512378dc9615728eaaa2e780efe8d610dbb95c50269b2bb13c0024995f40a7d2697baaf958eb72b234b7920f4507060ef20f7258606257b204dc848f045dc483baa08c3b5f525ba5a4a3a52fcc15f0964673e00bc073caf495ec8d193b19dd03f877325a59b4702ff6df69d753a6b8a85aeb8516cff574e2b905b448f7945c86a88e16de844e72548b6340352ae947b1c40de70a9e07865f92e92dda75539c32738fa307174b72be335f5553756a4ba790c659e290ec8c20f72d45c73b5b3f28e7a7225194cd30407b626b2e5be5ae68dd2b75713e6724836de4bd5faf842522f5067b844e1e724059d3fa6e276b993eea0ed5796b54f25d75ed8635ecdfc1a6636c105f209f05070cabca54fa400b5b529f41eb30339c3ccc048f31741eebaa10404e8b6a62727c814fb2ae8807a3f33516594b8739cc3cc8a061f46b258da1f85fe57a1c6254d2da041214502c8d598641c29d459c39c00256cf818a3a6edd530bed9e6ead729de43d189824e95f7a23ddc60b151bbbbd846d3401c2ee1798b7d693945bd8727981b8d3611ef8e80c783b79304025f65b8581b068b09c465fe354934078da3e8d4d8c88faf442fafd112a86142c6f06ae94fe0a46591f2ae07d807c153a44296b288f972783dc286bac5f2648ec5dbaf141fdaf4d1dbe124d733fc9f2a0fe34aaad4c22d19c6b675b31afd8dba1c8960a7ec775f2964d824feb2ba8d0a5986b2579f875ed3332a0733ffabefc5611e33db489821d8044071e2b9c8330e01272ccd5d8d4a495c21a2c53002e8dd45532f1dbff29de1f6c9cb836ce666eaafc72391a2482ead090f67f221def6fd968bdb66e4f50cc39366d7fbee9f410195c72fede3b626f17b3c4fa8741be3ded87b8856fbc02b701491f632778d7813d2b72ae62a3770b3dc43b2907bb16ba581d8bb85e4ea350eaf66a550113dd98a40e6ca73a7dc99db024ecff420a2ca95c9d03c7882e72494aa3025f03953978859d5ce96fd4792a94113d2b3bba45b9d90b87833c76947e1e55a88c098fd70aec3607201997335fa86c5bf0dcabd8cb8bfbb755d45de3a6bcefdcdac91ee8e099ba72be5427c81a2c53a25e922c6d1998ce3833f19dc1eeadf4a09831e52feb20ec400fd69972b7dbc37527135886b44c0bb6ae07d746f89d252e4730373754b5ab722ac6ba83a906db5e2321b7ea8feac1d9af0008bb7334f06fb19de845eb7c4042a1c6be9597a33f09f973b9fb2d94adf7465402943393ae4760919e094771f372f36f99bd9de06784f247cb1b5e74944d8d01ad1d784aa2ed70f17e603cb0f172be84900566893340cde40cb80144bbe677ab15557152f753c394ad1e3e6727720f11efc2fac7347da16d3ea9aa159e14f04c7fb40f4773ddd44263cc67d33c720ba4ae2df025efd468cd8816208b5dab0c0719f9cacedd50307952571a369c50084af140ccd27f3abe7650920749b6414c024bf0939c978f0f0beb4c5bae8472c1c0cb14544113f80ebc2bf791315b26344ed842cfb5960b8f005158582e3e0b12029a79dcd7ecae8bb84e43fbc5101ae3dd753ef7d7a0a0ceb248bfda40947281e9781a54a3651b614db57fc454f84e8d513c3a487e2dfd7bab23a1174faf2e8c1750ef05074348abf3c9b40351e8501954e6d4eb76c0a47bdc686eab986e72aaf57d145423a1332b2c9c9429368372307593578a28727641682f3600bab5728870e7874b03e970234f734fc8cd5ff4c38381b458ec407b50f1ea860c18180dad79c1e4a52a4bb33ff0931c1c640dc62d4bcb6a6edcd277002a3f544c56827260b5b696291b970b2eafbf4a070c4bbc2b5ccbf69efc25db99c2d9fc7e39042cfd4c79ac54bf763181c2d295142a0b494298fce2d23d670c7be7dd264d58a172f198a39aced501f50a7a6df92a57e136b2519d396c1a3cb1cf8ea6025393a672f4d13d2f6a9ee5e08d23c2db53775cc06eb36dc94e751579c7fdecea295d026c483d1011c0822afb17116353312c3a3b052614e6ae3523ef25b7abbbc2483046bd3db9f22805d39ceb913e5f00932268f33fb31726d27748e68ca11af965e5728d92057d22c38705f8ae975d6e898d619e24b074674ef92fe71d1d83a90a3f72757d6c6429daff29d73d616d145eed82956bb5df6360fbcfded5d976a08f34720ca34235f501a27ca179f6d25b222b648fe1f63e88c684b26bedece698dc4b64b4f21c171093d16e4061058fbe14d48b645f5c9be4604758966f110f7a1a8872dd90bbeb3dfd70eb60eedff19bbf24d67f552f9a1a9607a7b62e246f6fc19545538142c3b09438ed9fe1cf0aa2564242d39b0e594101a1f7c8f0d03c62eac13d45e14bb8c3156a9d5fb6d636b9c64f3eb9b2988e9f24b924d5295f0216092a3d1b16d60d756604de1d6a080d0af3d330df88d943ff2e34eb6a9618a6622c7661f5db1d417ca6b3d918b7a464691b7a92d704e6c81ae4345d0460e5ce78f3ce7240082e8faf84bf9ec92d718b912d52fa6e5dbfbdf6b2cc5a80a31d38815ead1d6f1e4e13198b14dc7d3577efc1e023119e20fd0f1dfaab95d855aba76451ed724517f1cd7a14d0fbc946f30c889edc176a85dfec742a739b491a13fb93df18721cf650f5e0099bd4f935241257d5caeb9af04eb861670ec130884207c1c36372a3e103a85912505f61c26928eddeb1e5a5254479c3fc1d349d3d75bc5611e272e51f7ffacf3b99a9a29bed01c8334a349dd63a9050e18bbd82490f2c8fd85772c969ba5bdb53c33b44e75a2de93ebd44616d5052ddc4abd3893a2922f4e90f44a434da3472643e90f162e8f7148d7e650d256bbbcae6763021b50614493c147229d2a49d72b5ff2b09e6642be4114e127a89b7c659b5da9fef340359cf28ba2de8ac96b7e431500f0a76b69b032129ebb475864f68b723e7ec2dd71273b1f47292e904ae0fbd76dd0d383e21dc455b6977832909610ddf710b0ef184827a2672d16282756bc78a846c56d08c8152777e016421d1ad989a94618b9f36ad83c272c776cc1d56e8106d97fa438df83f9b6f68aa32f2008cb03c51209a0af8306072ed7e19861e9cf5ba5c5b0b3103b18133126b4df3dbf5425af8b2fed8a444f07221a6d26497103b9012d8dad23a938b48dfe0dc4cc6cbd0160b9dc6dd70237f540078e37e4e6f3a7f84804b491f7ad041ef8ec82ba40ef26a2695e4375c1f7c7287d8e9d0cfbcb392dfbe53a8a442e2366109bb245c9642a5ef6acc8d580cbe72fadf3caaaeaa22fbf5e44c8c0fad161ca89c26d3efcfa975e495c0bd86464072c874810baf2fba28ae4c2021cff0a1ae528d48c0ca301b543a10572974a24072e1fddc2af126caa03e332099fc561262bfc548088472d3ff0f17afdd3053fb72cc4f74c43ac604ee11154af2a55230940688537b99eaea130ebb64665a7570725dfc67defaade333aaf7ed8c72876bd1ca6a573a824ab29c52fd34b3d62d1972cff12f78888b7ce63edb3095cff49dda7a682e2f9b1a95870884039d06dd39725f657547e55ffe31fd7f1bcd12abe198b0f29febb4d8fc686fef74f2f00a0e725f23e78015ebc9a20686afe97850efb066856b777cfbd76510a668c529a4476cefa241c85a9e42bf99f02ef3c2574670ef9c11ffb65591aef6325853797ce87280371031f412bacf2755fc896da9e1d81071bc9b852f00fb26527dc9a7e94c0eeeecb74cffd85bd3f3282a509fdd03ba774f150372087316ec5c5d28f9a1bb526dcf6fd688bdccb893d8e19dd4ed5c5a10a4c100e4610aa53e729e767792c5721f94972cfc19966a7687faeeeaefd23d3481ae873542a4ed5e8040fb0a436c3f550bb8b84114677a0338f2466cdf782fc7dcba46fde6d8b31aa81fd64e4a1553a63ab542ae1b0e1a2143e3e7a740f34b2cc9719bb798106aca0ca96733a1a256b17cbc85878db8593d70cb6c2182e8cb2cd3900074bf26d927b1d86391846d520bfe01011efffb201353efc8786cf94b8444d8309e5ae079d3644172a0119b72051503b60e5cdb78a78e90493ad5db8d210ee93d0386756b9b8301c2e0356d3d5dadfbf3719a9ddfed483a14ced7ee76d80da19dc5e89ad680d345dcefdbc9236c241794840ea10c26395d42f201d88fec393fac1e2d09298250705ff7806d650e8090582ac2cb42821b766e6d053ac6cf34626ae7f056653c7f290fca24a708cd996a9dcd66c5317acc484f7a7f88408ca70d191c4e413421ff5db228c05872f421b8be3d65cccdf727aea776f58be4f978904e1ac6e67eae3008eea007d61e69049943fbb2bd2667648d3f3075c34553d899606d48254a364b7f2774527e72825fea0dcc0721e1f39b4ee05a72033ff6d059ef0cb7ffcc43f818cbb20a7f4ef6dbbac7db0934f973238807238aee32c87feedb91412d81c8c91b8d695d6d6d3f7324a34f25794dd00de3d211b9bc0492a3ac2e454a6d19a36627128f507c2f29575831e6a4e1e7fd197f16c4d5f9deb27d994f79332d0b5ee8241e2c688b72402f870a798a021238d1186c0466687326580ed7bf797cc81a0f5fe3ff38a07233b5577794dd8c3c805fc8ab34dcf216ab74fbeba849feddb08f6d11964d9a7229c4d95a822e906c3c497429ef4dd3d9f396fe84d25f89e0ffd99c2868304d720fc9f64f218d68bd807a75349bde29d49b2e682ee5a4f9e6c53a5223dd2b9949cc70e2d740f2a2ff0788b53a74cff5d40e5e09e7eaa7b596d3e604fcbecc3972b8e213474fd8860634aa61b06ed7cbe3f0da1346d5481e38fbf4627061279f722982fc0d39b5aa99b4489057622c9f5a01195cd4867261a0c3de323dc9a80e7215702422aab22a1e90a5d7ff29204d4a0ffffcdb224284698e24747981db1b72699a427fe45d819c84d4ee34f49ed6b11d796481d02c193461b4367af015412abc4c199dfd9810e47083e42ee5c2a8c41a930c1e84d065dafb62b7c621089260371de6c467eaa6814333c8a47bd6716576da1cae52c0ae255a617cc6e728987242f9ed2ff997779d533157b14ef26f9ad9a2684418e9cadde5cafca4417213001cafe8602921762ab46814d0fa192d23a352fc4c382e33151ceb83cc55d9ff425d9da5e7c26d342c50ebfc81db5f519b7871d40059428334b6962376e8160b31bc396fcd08bfa71fe08abefa95115a4941033187abf5a4942ade7c7e5a81ab72fae63ff65d48e77fbefaac0748081890602bfc5196c78be79485862f20618672705c75f20295f35be07883938f3a149b2b54fcb6537d194a20b8d20290133d727c5daa2b2d657181893402dd6ee02d477b1a7cad726fe884b2a173a5026d79719f09c9796ceca0793426d01017395a82aa191d1dc24ad3e938081205a3a8bf72b8fc1ccdbb1d069892099851808690ce462c50a8f15ff07f15282e139cfbb372e6024bfbe2c88f0758d8f66b96af00c408d9dbaaef4b25a47632243d44694072f78512914d3f6cd17a9081180ddc1499cb0997a24e2fc6a501620e474eb99b72e78c56e844fd58e92bc7b81806a7e10e61f6c241101a6d2b0c5c61fbb11909722001b76df6fb09c3fb64d18a96fe33a5bd21d28d68e6eab884bc668816774636e97608492c42a20ad9f2fc5b405c8ff035dc33ec88595737c05958a30e526e728b6bd2db41ce45b8c98ee95833510dc6809b9f7ddd96f7fb671eb7643fd7cf64595b3177e60ca44f4a51c9fee557b0e390873ef7413c5a3336eea78cf6b31a5eab89f23217f5f5351800b1a3daa8641a23e3803e5e2debca659d5bfd271c5c725f6e0ba1ee4ac375f79a5d0069742069746ab1239a6831e4540583e5e7ebf80332fd784698f6e4fdb966178ccca296fe6b6e6c953f174e228b9fa9691a9017439ba406fe3ada4a9d4e0b9f10eb89413bf12b19e6ede2210438830541235d0d72167eba1df40b6cf1406025f02010ec2b5d1dd94e4b66957aba0f14b9956e9c3a02010708fd0ae78b0e5b07f42882939571f51578283a580ff5175a761f016a22c52db39c655954f854d638d44e888d0f941c41ae082d23d991bfbabb395a201b4139bece13e181be30bee61cc9f4c84d192b58d2392282422cfc2f9e1a653e14efc0cb26e9d7eb5c12b3496a806a6baf0aed5961f4caa8b2e99524cc4e2b83723f08784b1cc8647aae4f29c9acaf3bfee1f89dd87ad51b3f84cbc6c49e7b9d698821dba2115237987054f3c899fe631153f9c96133efcdddebcee5053a0d230508683256df7fddca4143bd800b9fa63b4e837b6c30a28f5d49fa81079042e9364a88af7675be72519f32b9d15cd2531387f9b68f7d9941d6e357ad676400a214d87fe810a12ad8fd991ed56a730efc8a1c6e87065a80045e0930a06853a8de725dedafdd2c03e01753e38a285d3112285e5336e8ac69c1e87cb3eb8b5a662672e4015a81861201a73a9b0059824f1c49163454cdcd163d17e1800e4f95e3de72ee1fdbd7fc7fdd788772fc4f466fb988918ca4de597a6680902106287e757472c08a366a7b5e42c2afa124de9bd05b11cea4acff577d36fcbbed9da6093c8d53ae35019733807730d30c981544939d230d9617bc5f0de1f19447d1a3213a7e37aeca4a115a02a9b663f5ce1629341f07ee3b3c012f299fe69f4ab5ea06ebb272ce01a8705666fdc1d57780c2d02d4520a0fb4e6f6ca45c5f42ae4f3cfc620f7246a4580fe41865b1fddd75e36c426eb3b62aaabc043e2520a112a8b4bebe02305ef989c4363fc89850a35aab781224c50b6a3e05811b204791bb09e3b3ff0838b239fb0e29897cd65aa60cc6d83244653e9a5b48061dce3f989cd761c4470a6592f4ca997c2c097fa95fac76046be60fe50bd493c2b3e70dd017e441a197b14ef85f389c60efbd6ae6e9a1f72b7b1e858e1fa71a44880cf7d52e0e3b2a2acc720b78291206bb8e01017b90906365142409cee7db01cabcbef1384d9ba2731f72ef9b3435d5994604f8b476cafee04e81f6d1092db327ea738364046780ac6b2c744030459a57923fcb06e4d9eb0d6f1baf4a22e72c5b13955c74ed005b9fa34edd1b4401ff382a31d711f44b0b09f4dffd61ad693d988adc23ad925e43cbd8152b2d2dcfe57d2e201e77b29dba2ae4215f5b2d962d5b7527fd3af348a0ac480d052bfa2dfe952870684b18e2dfe87d43ecacb59fa9220ea60fa01e5c651f1a72e631780cb00536a40970e7527c27329c5d4c9eeec16041fed186e6a20e2626654df3b9af63cffbdc11b40871b217ebcb5728b2d1e4bfc261610ba53f4f5acc72e7b73d89944e36ed892cc9a09b11052f88230659fc04ae940d87512cf3098572ccdb4f286f248db765b63a3c8ee87c541c136f68913ce4629f3735e1c9bc3632049107a322167ca0510da067976f55edff484d5b6106e5bef6346e40412f45726ba53d3cfa2d9912931e6ac914e0b3453ba2165cac578a56fdf623351ddcf872ce075017c62b5916e2c899b96ea8511d564f01fb8fbfb0bc8aa2273c9306013a124befc18af4e8499fea8b1ad5974512deab479a4f12c9e38b4d6285973e0a72b393933d322372582b0289cb7755354164c82090bdb7b2435c1d148db1f7675f22b8e7688e763da2c12c18396508f94c37a7755c5829aa7b9faa7aac12653907f91ce633dd7167a4b95333a5355f3925c61484b9e7dec6ad3a091eca64cc95221da33f572ecca51ec8ae7d42279b598bd6653d4deccf355ab6c53db9afad4472f54ef49394a445c6bf8bddca79484c978dbcbc0c1bc43b3478e088101936962569112833a7d98085eb4fd35b32dc846d94518d8babde2ea0c6b2fe84529d2816dcf8d47aa615aa034d39ec1dc1ba069ef1a8c791880ca3eeabf90288b6a88013b677c797d71a7185d1822aae6f5f82a66ae3af20bf7f08442dbcc5c27ecc9c07985507652e5c313ee2ca272d0ca5cf2f7cfd5c73d29aed7da292b42d87fe861ec5fa82b5c9fc7739c5e5d3a63ff94b1be19b31cee1f972b034f7eb6457491172759bb00b7b07ef9d79e5cbd07c0eba9443274d9bc38601e1ef6a41a821783b728912ee8eb24c6c79ebe3991670efd3d3ed00b558897166d4c3fc686afacdb822d19b0fe34fb8bff19c20632196a85296fdd7c8696429486a7d875092f47d541b5cbcbe3f224a136c8a54ac821915365db4dc4baf4d3da41e55bb10cfc8438d72f40df137696e70d673f26d67c680cc64fc8c58440760e9de295f560d29603e72c1955ee239e836c4994ac84fc992b38106a71e8bf021fd0a5b25790e2a46d01a3033b99bcdb15a003a1482bd98946927fac21c5d77e9bef381197d20e9abca72319dd1d35d212c920d6d3510c3fcd5644f31825eed102246f5aeb05c252b7b718887c9cbe35627f5fa5f766ca3182fc83add6225df6d100039f1a1997d60357262ec39c9640028b8890e4f02f9ba769ee049c43955bb9c65edda38d1088b5f43c5c67f79322843830e507a0494ead27fd71c9c28153e434cb890e3ea615f2200e0121dbb472bde4b522c0d809d8dbf12cd5d4cefea8de22fc664fe20004d111a4f48e8d17efabe6c2a1bfcceea2a340d8b181ddc773f0663752e148641761d721e19d5ba7faa80de04fb932192816a8cd6a33c5d282945546880c97352ceef23e3b35da961bc96acde1e3dabfe7041ff7034e173211b1aa27251b19d888aaf72dbcdd4238334d372d18fb339dc987f75ebc2930202886787b85691619d8b5d722da82fc5a0ac6ea4153cbd2b7ee8c23dc17012ee542284dd742106e5b2129b72d913c36827646b11ea18bd8e7f99b2d0b78d5e37f6557dfb041277b831616272dcaa7b563b6e6e4fc2a2e61a9a568e859e63f6d4d9c0fc9b1b6b693657dd50725104965dc5e97a1f8501f9b6822f9227353de1bde8795718d49fc12990c65c72ae71ee004f72baad29d32926526a6a566c730542ab01309f3ae9902f960e037204aeee1af45dde01137d3e030b16547d3a302fb6bd1f3e36f7b1d7cfcc307972f1919456bc4aaf5305f008b773c4aefac3585da7297b8c2dacb9f89d4b8167049f0b68e1dc5ebf62c297c5ce5a37833f278084bee107ffa75b02794765f2d572e29efda2f55193f39b4c36ab5ac02d05454965e498d899f5e7fcfd79e0491672422701bf62a7f674d05d38217b746df4b3555b68385180b30508ef4279037d448af08f912e807a83a9435f785a59f9e63c41cc62513505c53c0e5b06a946b872a09995b75b1b9d6388369a8ea2de27736c8e66cfb053b149b0042a17b5560214e38c0353f294132e864c2387329e9ac8912bbb995053df779e5f4aadfa56ca6bfce1bdd607cb7dd3ca858ecfd31efeb2950a9dfd525d72276f3bda859afea6477938bbcce8fb21d71e6fcbf7ebab87fbe9fe0411e2f2db1106e150c95e5ad35b84cb9b41c4ac2bddf24e7e19716c7fd8faa3a99ee8039bad111b797a5e9b2372b24e55b75a04750dbcfe4154e76d25e8a05ab18c7c3c586a844512c17ce9eb5449c73fa8fe24376b26ef2d11f099c3b4d42f124e0f47d28783e88c907de50f724b42725f0410f36e7122f1a0e8c1affa9261d2a28ef0cfd7c1e1fc18b7efb6723c8b2a55a62b524b9e14f75b31c0432ed2e14cb73824ea6e00502dbc6477f139be009bb05af08ff8853ca88a1d8464516db12d634ae86d75952a53aa505acb33751d0afa01412ab9d5ee1bd6ba9b5ee9ff40e8dbc583d202717bb03e957743723d6bd07e24fbb08a94813b55ee8ae352f3d679d8586beeeb3a8111d2f1c28772711199ed8a37b2f48f16cce8547f6129887a238bda28f15f58f912c77e57987241d61c4ec47510af868484cba00f33fecf39c9535b2e55208ead55e1a3d39c72f4986a5fb08d0b88b3a49ffadf9420f880a94b640aa1881a2ea48f5f7d50ac723c9c5fafc69d6b297a231f46eeaa958cca4d15bf053644b797618111b9403172890becd5195fba288c45044da1bc4d26128e154638577b4da5ada75649f143725f97837ef0d25afdce8646a74b9c00c80056d4b920f7df958f4f99791c822172dac6de33866e8b366511ad85ce8a996d312c2a439522458fad76950cc03e60433d8a2268a5aeb48757b31e72cf8c8f32156407ff423d18d4504101fb5b6709720846c76c41a81f50bdf01af8f4227c33b752e5a06f7e05611e982c4f5d80e22304c613b31f0895c4622d293f6411d76220f8bc397595ff4f3b0f3a1044b6e319ce0e970b373958d31e67f38754247f4a95d344c2cf145ea803a0181bac61da72bc5505630d7d30ec7e667952821f9127ec8ea36552abfa30afc854d5250385580d525ef98008d0270180010cdb18a3699d3d394fb2cbaebe9d430c5a79a61f7278a32d071cc45ec07983779305e149e3e68d434eb073f7a9dced0b01a8e21372e59161824fa64b7885d53698bba6469d1f44ee3cf1518b44c9fc0835342c7c7276f2587c6017aed28404d671387e92eb02cd1f427ec2af4f359541e158174b7278ff167a60e133f6289046629974300fd7c5e29443805a4b436f54de225c9b700a6a47bd7a8683ee2bf433fd90acb26a9adc730b4e8f0adf32469c0c23a36c72b9a0bb5ec5fb25d854a466e45fb4564669dbe13a8bde26b10092007f3d2bde721b5006e35470fea94539378771c74da5d22fbc8f61f515375f39323ec5d8d6630ecd40adabf80fe8ab75dcd33dc8b2461317afa793edc139469d4af3398a837272e29471a245658244b81028e2009caf7984c10c6c3b02e80fa274d2edb35972a397766b81d2135339fc7278bab81d157b0657b94dec6481500dbcb77b92ee5cb91fde2811e389be9a4743ee34a206eb19469d279ba00d3e64fe8af9a7d41a22b56a3503cf335b0c821ae31612c88d31edcbadcb26a82bfa54cd403329eb207218988424254831ac71fb3500f5ca3e943ac5f16331adeaf2b50aa19e9a937872c6c12c501c5655ac82797657a254842d28cfe4dbe3be47a1c61f417e76a62a1b9a2257ed08be38b3758e0afa60834406c9d028f755469b882bed5dd0c2d0f972f60eb9a45e6ad24136a43d02fe9293b6bf95f3f7f1bfe38ba578532375168f72bc36e0da5ac6c36ef4f0cf250382cf10a01f93411c35b56ba5507d015667075734f2f334ccc7e13acd175eaeeb6d4ab2cb04ee3e637be69a979d38c05e7c647215d52d8d34b726c92fb277a483e5b3791ef4c4ca8de16f45d3e35e861ac4627209cb3dbc20e4933a1f2d6098517916ab5643c62d9079a8f99028ee7e2e2aa409fee991281021cbec9a14c005fd7ccebef34f172ef57dbbf0252c63b9e17448727be8ac8b1a81dc454c1d3273ef0e2a214197fc7e36a3e25477d8ccc879f4af7230bb371d79868a86f9148fe7695b0e9c2f8bccf87a92dc07fc98bc9025450f7212f190847cb278edefe76855aa70edba1eb80e955fa010777670c60f5923d24d9a324d1ec5761c14c78287058880870bc1d7aae903398bd25d806d6c1ed344726166e469b748dfc4fa0d7e545e0f5d15c6685372090de3f4f667fb377c45614ee4b749c13440c8405a078092de20feb3fcd24a4e6e4d8063bf145ceaaca94972ef7ac80acec4f075870179198bbd9207df54a8c485c67720ae04ecf993a0cc4e1b26e6ba5ef7368bdaa3dd720f4583cdffabf610531ebef66be9e2a2e860dc72424237c0e2867b3d2823a3e0eabfa10b851950903b878189c703e14bd2ab9b3ccadf2370a89babf542cdd58ccdd3b3132096f9733622c638d711bab24abf785bad9440a1b29c13a393dc67abbcf2c3be78f779bf7978497e2d440acabc49b272943f2cf02fcb39fc6565198e8da6e58543e34877e476078f0cb1e719e095a872d560e31d1fb2b7a7079f674d9f95f29133931a91966ddefa9dedbce39e5b2b720adbce0ae4d1aa64691c7f6fb6daedea245795b424978f86d93299be8672a2723c113360844a96da01de1c1cf80aedaef869e85e6c2dea7e401103bd267b7372ab9a830029d3279eceec628a98e7f1075a2722c8a370cc9fffb41f1fb2a31372f39e8438be18b9136d43e6a8189ecf0c0c333bdac04b954c90ac0d635376437278967a2e340a7dbff8a81f1b34cfdd192e697bc2e614015d7332710531bc35725d9d9950e278c89ba40f641304151080b20a25f2db58a05c243eb4e4be449172e9017898aa36471fb438b72d83567b8899fcc8a8081905cfb346083275ec9d7087f5953522e4ed673597f68857d1cb9dcdede357e1757adb2607bc71093b4f7261ee15f3b257cf763b19ed62a879deaafaa133bdb2bd3d3ced052bc2f031b572f58ddefcc09378e74779b768d4a1ab26dd82fbb2b2fd01221b65e344404da072618391a6c8e8bcdec23e6dca554bf56c00998c8684ce5765040bb81fd9151d1b98ab0886a2b06d63fe58ad328b3dd030352cc155c7c41651be1566576087af72806574698216a836c3131789d4b61e538d5e82ec4ca47e64ac59f55faa314a5d64603bfe12b54e9a4723a144ede3127b1e98ef6c2d3a3f04ea19cc12aec7755dd74d603d98a5e3a4956587965044d809de79aa340ab81f0d555cc41d536d4372e6ee4f84e14df2dde7a1c0de1f4daac5177608fc67e53c9fb1ef206afe2c8655f3258df29f8f5833c23753f9eda1f8a718829e1f125f41463f4ac9c609183a72db9dc5d5f47047f109fde9a7f1cfcd4054b3a67a975b33b4ed27e495f1a09c655ad0e30971937248183b9cfed0b0df3e935362794543b3631343555b23abc27288d1cf6957f498ce5dd87bb85c30ab584df084b0af3488d4984130ff355b4a72aab1457ea0548c2d0eff9eb6f80ac2918d822429ed5da12d59f720aa32794f723748dc5803ea132ea38339837d5d242a8fa8ee6e989d89fab027298151967f721ddf8b2e4b7d09b7307c4b5fcf97415e976bdf6685160ac39ec6c17248452662458beee144d5533bdec8b0518429a8525655d88a0523e40add743affd1fe3f72b3676863f6bb0dd91d758d52436c48fe5f2d70b54b3526d1af2867ff21aa8f711f6412d6ee793912baa2a09d21a53516097c1a4d642e4c30f85f76373d61c4728c6120e8b77657fc638127ee4ebb558c3b6a364125c9c149f1f9950612e2a11961e9339a169e8059b39fcbd616ac6b8b0dc98c3a7efaed318f3dc8f0fb5d7d5118c6fffa00029f7ddaf2a99fb625294e5644cfee930da2c4ce4beb203ca1c872191efeaeb601fc090114bd6b3d83b2521ed2b697d18ee3ff52e48252c1032e720551cc05731ddc7b761efe843b5271513c3e6bc87696e4d114c400eae972c83736544e3d04f5bdeb4b28877ced2c563fb06dc2fee14aa7769d13b1431f06d2729c21474a159f41573b629993b25dfc16c879211cb74950c777c905cd5d3b3349d17b76315c860ce51649bac43fd1b607e47d286b21ea5e1299cb9d4e7eb56a72347af0b7e2f1162e8ad6d69b502bb541ead6406a84b34e6b9af9d533295e4472fbe444e5a8af8128a6be10d85f910265b70d748782dba6148464d835f35d86729b78070bb9e1c6b37effc1c121b45c88b3391779143d0bb841a4cfbb2ba6e036725b4e7ae2eab26c0bd2d2839e51f31240fc89e87025e27124f797a70fcd1318668de83e3be6675d833ff479aab667a0baa25d7609c7b3ee4ea3079f0d5c0c58e9b7bfb58ebe92d1694f42fda0cdc5a362bdf7a886f509747a406df36f440d7226e445fa3555473bac2ff609d3c322acb84d4ed0111fd33ca1ef21517392d01f08ec2cd2fcef48f3996aa5baa59c8f88d6fcaa21e3a8341bffc4ff359195041db72a20f6dd290059284e3f1d0e580a88b11dbff0b2494c9c789244b87f1c5a2567820adea0019a69350c341d974345992427630406a5455073e19be2393bf4725f45676ba45979b99a37e9ffa98a7fbd9d948488d2a734ec6bf6f21f06473e438beb892fd59276534693e8783f33538378e819fa45126dcba43b93325b252215c203f23017a801f0a5b76fb23cd0c177d4da8352c90b1ad4fa7450be1f724b721ce4708012f78525074ab710a833627afca9276448f211b86168ad6e80c8b75464b1a8b69da01a6ff584644e6dfad12952198f1512b4ea4d7d5bf142a8650872d7e7294f5ffcc0be95cdb88ae5e4a2e04288e5481f672e2b1815cb09a64d5a727943ada3a63ab0f6ba41e981b56138dd4df17f8dc836bbbd9e16903dcff75e300e320b301bdb3eeab8c0a61b7a553f4c2bf7acaeaacd968440d5fcd5033b0972a24f7fc6baa3c9f2c823bee479e7c14c364920410950c884c0ea9d48402fd6722918144ca5d2a9c2b13c11c8b093f3a5c4755a6703e83ab2b5969994a3523f3b2de7b917923dc8329ed9a70ea8d8c2a8fdde98783b4bf238b75a27aa9dca597222f7bd096b1891f53885f0dbac9706954ca7558e5aed6fdf762e16c9a2e5d172ee4625c446dd24a2f0a210df471d56123050e0a2ce85818355bc8f7ba190692560bd9e3d9aaac83a1bed441cac2e3a1fc0077d26fec4235ffe524d1fb9a40e7249a14e373fa58fac78d01db40a08a06a03d029d61a30431d733b1ecae53e963c1fbcd4c187f32ac010e8e54300d82e14a6797cbba3e16d910424c1ee2a27a072a3651744c07a84a2a5d5e78c929e7650db3d9b6e7791d70c07a3f57f81601a72fd27c105f2e30be338dd88d187cbda8ce29d6913ec7c75a02649cfc750cf5813e7fe07e51c57250cf002975f9eafc822c68bc91cf0c6bc5e23e537be6c6d1072b14d5edf01b2be6c6fa9d07f984f36f4ac9d360acf0af8a758887a1858e6b272f9d00f57aa2850030c74f176f7ec73bdd6af632c313a90a8cb422be79407167275e1d211b756c5061830c018725cf0403ee9ad9799d2a03fd0d62affce78613c7b08a31632882170c6c404895ac063c3680fa14f774ac882f5604621eb122b723e668d12c5a417cdd673a95ce880ed8483e2e322659b3500f903d2f1f18fac729d086f429698e63537d11e2553e77439b9325c13732ebb1429ff803cc307116d94df7671ba74a2ad9b608ddc197794858e23da7ba097458a7b77f13a02945b51762fc4fa93dd57c6f371634315cbe418b967a574281d80cd3adb2ea41a4ab3083312f521693cc85ba59e06062cfd58ea75c1fdcf42f1aad1a87883e778a2bb7241c0504752964cc923735908dbbb712c1d1ce561a0e55982679d440b5daedf17dd1ad9cfb72b7a28f60f914396b7168a988874b0616d040ae6e5d7aa276fb3480fb740656c00c4eb560fa48f671484a244b41682d1ca512078c1c86ef686f85a996bb9a71f23e67b2724556896654fe43fa5ed7b385c358a222ba2a58882fe72408dcaec2b9f873298e1b04795def350e88e9ed91b46189abb20a626960a70728056d5bac52b94634d824faac9921c0545b7a8c7e4cf8114f18dc7fba2abd55661a07d200b25ed7cfabab04b2b0ce8f6ef9839397f7ba1e1a973db08f665e221234fca32c55931f65e37975e315a1c5f428e59bc6c08721c2978dbe3c950cb72cf2b400c62dbdde5e58bdb9088ad0ecb40e649b3c27ff8c0674ce8927aec8572f537da17a69997cd3f10c619badd9ccca54d18fcaca026f93da731d0fe7ee35497b48b4492c76ffb38b69aae72107040db15e2a7e6618e2e7097135dd330c3577fefa5c9dd46815345389d142a17370f8ed6adb4f0dfcfb5a16228a1e8e571725e9202034e5110280b7d7065dea5c0f8cff5dffab9f3df24e321e732b8efbf5cedfe825c0c7a25c64b67f585d21bd198010569b8bbf5542192bfde9a444b6f72618c00697ab7cf0243d6977f934613e2b70031f9bd1e8cbe7012bd66a9cf0b72e56a164d70b80796703964769014bf7d15a1783d15a3fd2c00027e98bb77cc72d8ec6706135a741ba9a49b56932921fb4c237919bf02ccfb611842fd73b4e37206ec21ad49cd7ffea42bc9660dde9bd95a7c106cb8b7304aac466b3b306e017239aaac385e60f89fef1243287c02b297f05dfb6901cfd18216a479e8151fc77290d2a45f9e6065fc4ce6a46cfe6cd0efa30d2402f554e096c95b9cd555d26d71041443c1a5558df8552e3a39de6ffebc7f3eac64f97de0ae0ce487134d3231618a6433fe50906d0fab11f7401a408a780fd981a553cc2fe386f866db7a96a072b44c8f0d3607d7df745a6e2f13566eded247f6c257649c8cc29fadb94e56d372ad29eb0d74e31679571e5943cca80e4d594e66d8238fe398ec3c66e4c65cc5725e3a60c39e49f0b740f12d8e87b050c43b2f1f35bca82ec726bee53669dd5a72ca2f3c71a0496ee503315a4a4555ff6297afdb719616562351d75b08ad86900860619b6f5d36907f28a60cddb828a9df1de510cb9f0fa139b141b3f1a2fb6f0082a4bb032f80a7bb45b1abc1df6fa4c0a2d78a93b0dd8a54ab628a6833b4793fab6421c87d77fcba3827155214366c4130dfc0b51c44a2168790b90b4f434d720b08bd8163bc74339d132d635e2fdd580a0d36c4b9454f4fda94d75f8505c7720432b1b345eac3b1b8261b1350fc7e6dd0070b1add1de4d21d11ebb1b2e3d05c35dcdaa1fe1d8f4ec4966bfa1c4055d590e2c40960c86a9216c8d9651e19ad7252cfb56cb0d45c203801b06f48b1ad033d5725ab86b2ca68063bba715795d713e28876a0acfb086cec72cf5ace8bb0029512cc5fb2f10969fe6f043026beda726132c3f51c1ec3b5806b7480d6f5263cf1c60af4179e5197f07eb74d89da8b458edfaa28b4154d90a9f2928ff7e504ee93bc1dbef19a857c33f46abdb75fd772e785f8543eeb89102292c02d6077da577180e6d5e09a5a8b76395498f8418a5639e95d65a33a17b0bd4d0af25b535b2e233d44a9c7ce7604b5e037e885061049c5a19514a060e3613b8b2e4423b9a796eead26f612daba08f0428fac32ad2872f12fad4c83434e2c956d445084445d62d0248f6ebe509b97d591bd883a9050187601ff55a0fa226fdd4c4d1d2e66c564af3749e383e6c8ea351d9a8bf12b081816841524f014eaf7345da1be16242ccb5b31aec0110abaee0c24334d386b1b1094cb9d2cff8b3ef90439ad843214a45004da5e2c8ee730a438b7c2cf6fdb7307a476ecca70f91acf6cdcb8983a82290a13297b1759c32d704035bc8ce3454803e3a52e19e9b2685d3be3964e8a1ed47ed094c414332172969e9b93fd5ca64d62d6600bd0601159779b01a62b9434eae75e81e1d9844a061595a06468e246f60df4824fca5cef9b174696f6b097600d9d8526cef7140806827b2ac665d651d872061e4c995e6e61c013946820679c040c9e458eb9b5317e19f7b8c1aa4053b271d345fe7907ea8cae701d67e3d3a49f13d466e750fe60fc6d4bc55cb57f252472f5b7a30501e34c8fcbfa09b032e4aa88a68dfcd592f67fbc88a87c8891e4dc512bd34036087c2a2e22aa3c2e031c15b144985ff5cbc2f144fbdd0ea98fcefb6cea35cf7eec06606c539a057e1e317107c394aecc0afdb71734f3406a38052f7225e3b1a3bc9e41511ced2d3d558d7b1244df02d2d13822a45afae2462a842572a18b18239a14fd9f0b600b8745b9b6cd9e9d16e5146d8d7dd034e8e96997dc719a7e26314a733c83c36e38054674bd837175e0057f2735f7f63dc30aa5d3553c811123261c0f822c5d17b7d33f0e9ef0713a55faee8d51cf3e43451d51062372ab6edbfaba7509ee03618a49f2e377516b1844c487e154a8b9b4cbbd3383b2725302c35d5efb633809447c223e4d2510635a5fc9476dc87a7402cd73eda08772aef4cfccc2ece31b19f88ab920a3d7c67202de45f767012ff72add1d3fea297217a7e31e4378c83f536e42ec884acde68d610b5236dd272d83e08588dc47f835edfbc78d50a03cb45ec268c606f8e75cabd6e2ed6e66f307ff7caa9e62bd165af0737206ed1081ce97f92dad6448f96235697e320ea2fbfbd4379fd6c1ccd872f5eaac468ccfcb4a1340a8078f4e1a1c6467a580a3286ad0c3cde71eede48b7278e96ce804a42484a1fdc1c0dfd4403825accb5f28dce6809ee87fba7e8b516bfa357ecc85c7851336672927236601540029515a921e78e1474603df8fe35e723275459acc9b990c4fc70f03184149e576dcc6a88c423c2a74e5d9193fb70c1efd7c6ad4a27e4feb2780a1139bbeb634106c4f9063730ada06177d1f752cac72a61d463884f647080c5998eb9dd21d294e32771492f323e4ef93d89dba7f8f0bdda47c5484c3363abdca2be3c6e17b692284c91881301e174fb1011840957d3a7727df22620946871860e18cacf76d37036a6818139ab78f62b31c81f6ffea47c3154b9f7b602dde3816179e5e5a4ac957665b6de861ddd81cdb7e4d19d89d462246a33c4ca35d5420334384e37ea7967269669a00a35547b16ed5c76938f73b8601a82758ac774e2f4510df29ff7305ef7503cd01e56c03da7e331b071f2672695713b06fdb70a542ed30f45f97375a1f6ab329df4619dbe43a3fd0ea1bf1727e3be0416a295c779a399c48a28cecd647af0ee98dfabaab6f707e1cd0b5807266d2e2eaa57ce826319edadce15b8a4c6a0e053e49822d6a4b64617b2367be0cd04193d142ad25dc9325a521acc72410b503bab2d5c0d45dc253dd626950a0729d30614be12ea7d97aa5d0ca7c6a9c1834aadb0cea9af5cf4aca094385d4cc1fdc0ec764b728d1fa6b6961ba4f298d172b16ef55185bb3ea6a68225750ce3572322a02ee740f8f8bd03cba1cf8e2e0551da5c9b4fb5f6fa292d39e4b0de887725aea46fb46a872dfe712abaf3f47c1f04f1a8ce1817f0eb6264278654033c428b9b76e0f1f78777de1a1af9ace8a30fbb7cb54d12bc9f5fb236aba9ba6478572e7ba05929d6917231b4c669d3510dfd116f3af0b40f91e2c7ff4315867c8e172723966cc8124da9c726912636cabe0af1d590963d908ed356e07a636a4e6e772f80f7d672a8d91b60d321376fba1c1bf33b1e06954140f6e73215ca978ff2b02447a94d8697247607d08db19da6bd05f82373c903a10d189dccf7e2918debb22e5701cff07c4162d7f09752f45f78cddc32ed6095a1aff97ffe576893be67072e006a03eb786972e01eb320819217013f3b956b7e3011f9f2a5a7743f397fb04215f959a608966290aa2e1be3592ef5418aabefcef0932a478ff740919bcce72849c318eba5e9a1444bf3c328c922a43333750c90e5e403c2b2e94a5416cfd72c7c326882bea5e116bc78b457bd5fce46fe14a72a334f6511d28515cd3248372d87e119b661ecfc2a4bee6e39003ae11cb58914ebfb71120b7dd6148681ea372b9fe691990784da454c89e47f574421ce6ab833f920d76ca1238ec7cffa91772e6c2275d7080b96b00bac31a00d79d5efb0127ea6d814f88cc1c96dc75dcb372468b30b6a3d879712659f6a97c0054df339fee1a589a3b9462993213fa1fb172992bea7d4596de185770e54e3d20b4b56afecd9c6d6d5ae0c2cf42e02e7f3f72339f025161a3088a3fa828906b71b2129b2bdc71b06a1b7aca7d55efd5dab33a024073cd9215ab58584b5edbd90e63011d8e92e0702ad0e9943d8d37af402472ef338d77df17403eeea7ff9cf7416bba4fe16141f1f8bd5edccf3502e0837a7247ad5c0928ef37926a6054ace9db22d6e0fe20b8e411cb70428618db5f3fa3729e0aea2853ff64ad8f74bc84a93166452a0a266733a3cc1c6836daf3510bef7209548aa6a85c86708a74cbeb8d9c8e7798248a2f0b7ec043f5524c8c7cb8d1720ae224d2db12af39d3aa31076dc5779a2ca7b466301a4ccfbceacea0883b6e726a9772edb4683f8b3993b2d61161ec72270595540beb05207dc76dd87ac436493e5609a3c2aabfaa430c5d73c0290c02778557eac1881c03cb7bddcb6b39f46d808c64174acd2777ae88a38176f6dade5c82321860f4aca4afeb37deaa9e4272769a1d8a6dfb2ee7291ae4b3008f60cd8a75b9fed05dea20318b596a7d290972029585699058da4c751d339f65be5a463f20077498462b8f77485ae6627be0723322efbd837194fa64081182b3f5a7be276ede75d52ffa4ccc26e0f680987b729f5c9617dfef0fcbfa897a027c7aac1d84e5a20749e07e2bac49b2f6ff9e7e72e68e03db895f3ba9c2c830484946ed74a05321bf3f4f366e95da6beba25f6a725e6cfa4ac50e1745503e33d407bbeac64e8455e8710c8d2b9930b7aff4430d68be38af0c4e3a22af196bf79ad871867a668b57f9ce1093c90b561dd74921dc2c8dd3e8233b1308d7bdf95ffaf774a1b14a593da634fea3043d811a066fe37b7277597cdf64b274a91954070a0dc2ac0943959b120575336e2ae684fecb506e72c0bfd68761968f67a1517947f5211254f608104bb0259da6d5ba1e8df832a95f9c9e787ffc2debf895a1b311793e64eb19710d9044f1da26fa32914c5f2c7d6570926a76272eb9344c00bb0714f549d792335c740e779cb1013b5d33a9eecc72a744b0525f6538bb255aed8238d6030ca0509b6694aa24f5b2eed7d271e8181e2a0d014a93e1ec901d5484a339c536cb0f48aa50ae71b4ee1d64b966fe75c13ce62140667515ff8f7bdaeba655bebf7a3753dd8ceca09ae3becf85b1f3c7d972ebc6e145e97e1bae65c289c12ff7c9c6f06a391ab0cdaee274fd677d160ed672cda9ee5591c3b3de8ae82027ff920ea49add97e00150cc432a7ac17cd642e072963f255aae83291dbf6ee75b3b026ccc1d9ec7503cd0132343b4caefd723034efc03a6533876659a42ff7ad17b5a398287cd0250d372f8831dd24ee32692bf7242d71302902d06252712656121685d74a607fb9c2ec2beb68747cf6441eb79445e51513dbf493335191afe218640cf505bc05d8d9b19ecb36dda71d5530cb372103df0d62280196f3f7f13b0b25d6b930db8ffca62d147fb598f40d1427ef57258b6b6467ff8827ebddc3c60e179cd4fd8383c85860eb6597e1c912a3c52b572972a8cb5c9e0e03ae5f39a22bb519ce2199210e5a8ee643f4ed2c56ea7224549871633bd4fe00099e27600ed26a6a1a54a1d54fdce26bf8a71c1dd4974561272e34392edffafbc21bac46c5a24652e356b6b2cf409a712a4340da62d3325006d0772a880f4f7bf55785d73a88bad872b7ab64fcd2aa657c02673aaeb658f337285b30d90afa58a4e51b010e24e886740c97c5c582dd71a76336dca7df91c0b72e0c1cae12561e89ac21929d2a70d14a18bc52356a366e3e10d51725f969f62607ed1b631eca5e68501d8c223a6e1adde4d49672ad81a3458e12491b50605c7727d9a7eb0328648546249bdb2268f59d7b0b1652a5343959d23bf251a43da6b16da7b81c326c17c46cbdb8e5fdac1e34edf0c1fa2a581f2cbe65106f0906cca0ff905c77f365f20d96305239e753b1072ac32aeca4de933c50c80102024a0d167ebd695f9b3406a2a265d51d2c444206bb1ff78db823510826a47dd761a7e807297f469ef6424f78723ba8280ba9b84eb960bcc07d1ea746e9ba168393861637273eac78f0869fd1696c24ed6e3bf55acbd549780d0b05cd9049dfc5b0496e50bf9fd233fb3dfa9c00c9bf4d45c7e59cde1a5128b8ea70fd8ed1cd7817cfd7a7260ec6d06ecce1a32346d36e89937eb2ce46739289c03d38b3cbbdde1c341ea7204980a38e3acd138d88f657b67af29131c79441b4677c0bc9ec71144e87e4f72d115a952f5d81dab38ecf364b6a5602139f1adf834bf74afc29ab080e4e48b6a171559ff2ac5a7d6e7fef95e0b9232fda05a6cec4bfa56f69acbd289531187725b4c0c8e892a9720deda14574388d7276a235468f84cdf18623a0dc85f219b3f763c70c65e2469c3d2de3312fa8f0ec014188ca383f33d61f86839f7737ac0728e96883b675eb66e46e46ba203f6b767fb655ccd3c4a67cbdb23cb9eff958b72b8980ce1a1030448459e422e9fd93afa5dc4c29e82e1d3e4d34a12b8b122d672d9eaaa84d50b6c89a1232205d92f893b15866292a9063f90da371eb36e1caa7250af2e8d02498715325a05bcf6ca159436fa694d9ef7870a1ea58138d42a3a5dc19c2aa989b4ee39a567a60fafbaf9a9d850766faf2bb6d2ea9a90c0ac8d5772a7a1e43718a169cdbb05a578c47e184d2c235c345720b4ea1b9917e244df9072c178c377aebcb66ba99a2aa55e582ae29185a67befc6125f1061d26b8abfce72ffd64d3d759d1e483478e201f336eed66d2afc4129df170279433217b97e4c3e128ba021d526cfbe7f83b7f7d978363755d9988b7463652fadde19204485c3720c8c182154e0db96f91d517f93d45e93e0d1881c984403b7bd86e8ecade470722bab6f17ac2d2ad0812d3444d30bfe0960d0f2cca7955550fa053d970cf6e5720078e3dcd2c46b0f88f149fc98ad38e5adce4d64167c08485ef536b347a2c408b71cca7cb41b9f1079e7ecf32f5e20ffec07d13ba6403e365bb21c62556ac0720f4b23980ad99afa93381f896317b3419e810cc498c54b2f2ba7fe0ea56dd2727ddcadcc5c4fd08f9360a92672caee18c73b1919cf109189dca1809a05efb82f68a823448d5b878431109987deedea5ff3c51082d12852e859e75d858ca8741d914d6c915da41440747b2c038e71858cf4113b64fdf8e0d7981186f3cf333267fbb7f866469f59810838f7f0992fb89d204d9082b8194c15edb0ec8fd0ecb14cfb67d2ba6a4ea6fc76cb4f9e33b599e6b2d0b2c54f5f5cf090e1dd2148668972ff908e5d661ccf159d96b874dbb8ca92fe5302f1b8acfaea6ff1685383296d7214b2b5edaf924a4e40b043a3331c49e179b567271d366999d79c142578c6fb315ecced2afc8a99ed0c369a0b3bd56eda634cb9a2ee30c3f5ece3bf6539a38a72319e82555cff8da2e83022bf4bdbbe94b6e8998779862be8aad20ad96f5be2061ccf0f95166e5348b02d53fd05b7d99c6db9d53094fea65ce04bcd92ecb3f97274c61d11736ca5fb069d66fcc92f3e85dd0e3c897d1d77fa690949418186f172881e54d04f42c2af4280fa9d04df68a7dc194635ba3893ed390bf7a52eb1b47286a18176acfd2a617ba3011386688709608f5446ce03f2c6b25f6080b201ee2885836e1647f7e929253e5fdf3810191161ff9b17fd13c12146a9bec59ca5e6727e1074a2e0f801d9e60dfca4187e2d4094e1505249e2d3cd99aaf62b846b22687a015d5079a6cef8428d5f4faf065712c666460da0c44b6afa3f138e11fadf72bd23798230016f1f14d98dbeea8d4a67ab86d67c0897f48346639bdde47a8572a6f65ff91372e4209511d2ec802583035b018832e3ce17756af9cc6d5cf2387210ff95b540d6caab8f3a8dac7ad7cec56fd01e9ec0caf583d53de0b494f3b85c8236f9a23483c4af7e7c20f486b54b34c6f5bd6978ae704311bf8bc8e8334d44c29edf9a0b8aac5af93f418a7d4b264cd481ff65fc084234afc349c9e90d3854834682be05c7ce797fcd6a04b1a224cf9dcb129d58c6dc672f8959b1199fe5723dccf79ad3be353570f1c70a98f6c466a277b3dc3b48b70bee4d990333175b724199b3dfe7eca84b80055b4ec9b1582f06f28b69aeae828ee0d2842c9460d614055a3d36a58152992a1d0a654528f73196f4a9f9181c13a40a111c97c3cafb2fe273b7f4687f57fbd760e65d3c8be90dac1468dec43c89aea27786e92175e4728b10b93cae53742e2ce0d394d572c0bd9fb8537e1c7095a6f88f20d263744f72f34ba720559aa670e0e2054343a089a85513e43a0a6cd20267987c776f464e722d7e0605b9925b9a70332980aa14d46bd71c1764cf54b5d54ee257f9533e807226d615856b28bca34dcb94ee2df4bedd2002d5ff2598133cdbf1474e40f244722def701a0422f8c186cbec618d77aeb989572cb352ffd511baf6dcc92cc767725cf4ad58c67b8b69ceacdcb11d6efd10999bfbf5080cfc499bbf4fa57e86d430f3f743261720d2b713317455a3107496931bdc746a11d6c201b31261c965fb72cfb4d9f11b8740dbe9cd0fa1e95d44fee01f5b1050f9b4a94b73b0476771d072600765d083dc4708da740546e5917d7386108e89551dc2dcb455ee41a9827343b0f166dc882ca51e7d81da02d25d9a446ca383da902cc0a4a4668385a1f3ed7243d036c2ee42903ba01cd580dbe488e25bab5cd5093f84e7db048c8297d5ca534b266a20f5fb4f1643ea9bd107aea7b481b3b4fb03126919afa75546cef55472bf40b1b8f9e06b137d8e783196efa7d40856f24ebc9ecd14a43542e1e5f5d1446643bd5114c64f5b0f941ac573520914a11bd1aba557c14a2050adeef86bc472e0f842bdf38e0b59dc0d38d59acce8b8ea3572536e990ef1dedadb47fb76f2316b672de7ac43269fc6abd69cf382e1e38634442d6fe681cbb26dc554b281cd727764c658e2e03733bf1b6e4bfb4bd85c73f8fe7dca19f3c3dc1f480e392c3a4d6701ac7bed0e00fe07e848aa4128fec8502904938c197a7a94e66ac32ae2833a04afcc35a35209fc6378afca7e80456c0b9f09010a43fc21a573f7738e1a5f06adce1cbd7312e78cb893be8a5d7bbdcbf78cc521251a27d514da736894363f7243e4ca757084d29c40aa4f241f7c2ba1ed6e3fb3d436bc21c8d5efa9fe4ddc3ec5796ef781b63dd1d3c19b380f0ab434e4acc86b020205de90bbc2de1bd49472dbfba8d15b91f4f678ec5b1ee908055191e14fb3eeb49ece463501d28fd3dd729f84e287fcce5cf55660dc803a29a789ccca52becf446437c416b2d8e44ad272176d810d8aba11fd63f10fe1444ac95ba99095fecc4490fc16eec4cffea2867239b258cda0d7fd99d77ecbccd2a1f0cfecb5822b2b9d18f2d5983839d070a3729964c20246a5ab1847e634eb855b2b7b2f50c1a08667d40ade4820053c5fad4a35ef45df4d856dc12649ad701c4bb88b7e3b33eb3216ead121922b1e0c3cce7226788ed7b256c32d20ff86c62c08690afde65e88e383b143c3cd5a992158b40cafabf4f21e803511b74de33472993fbc57f3d0c0c6ae91248a0437d049418f722f61fb730129d58d5331e064f3216dc90254b8dc5ca690b2cf0851fbe6c1e97277d01d64f00040d807c55f55dadcf058fbcb4f04bbfbcf8f463ff2b391c88272b17524f8a29b806d32b1ceebd93833630cd676e96eb3892ab82a471ca9d196729f44983ce77b2d013ea53111e391b1ed0495959993d691dcb7d7520877d5774881ad039e3ce385b63c9efc17d97ee4b9e576538b3f8775076acc815688cd6d098e97bea482690aa6057abe541fbaf50c92bbd259a5701246a0a1ff5cff54060fe732132da00f1c133a2013a8fc1b84b3809a46b9ec7e72bddb1b5e4cbd3a5c729ae7e2f0511a2d3ae6cf1039cff9c0ecda25f0ba3030a8c637defcc63fab280108bde0b2318d2c90624eee1cab52c8c807da379e09a7325afecda9b04b92b939bc55b299f0c96e204af248e28deed34eb181c4a505cb029ea6375af92e56204aaa1e052f9a192493b616fdd9bc07a966474ffd11b0949e4ba73afdcbbb8e20159ee066c00fcb96c1ef1b6ce5ded70eb52dd41f49cc0c52ddae966e83e829e62477ed890cba7718078d642e7118d7d8e05e39d010261c0b3e41dea75a4ee707725c0e6421eb0b7405931851c0564069000dda0bd001b18e418236603fcbe40203e420381c887dbc6043bc9e5f9ccead86223fc90d296ae49d65b478203ee17a729a2629409b256ade70b2e62a4dd01cf42b85d3c7ada776266be9fb86417aca23ccd1088e9cd529bec03efdda29feb7397c857e57458116e156ac7a59a343a34e514c9d9e6263ed59fde72d19a3d90fdbf61b09bfdf4e5d959e0c1ee4661a1e72c91a53042c39bffe631e2ce7b5363edb57dad58180072f636f75504fc6e7237214b0773597e2ae7eaebbfa7197c54802db37706ba4330682da7455d0129e6d29e4cec748266568eeb416c9b87405340e201cdefcdd37019fa09483dbe8c0c92d144eaa140a45d7db8ec9a6aec79d723ff407ab67485291508b4bd407edb0442fe9e790fd7962ca53726f20d6dbbf0116e55dedacc64fad2f92aaf9be1e9319723955207739992f26294e61528f057f3f1c3b391dd4e8bf3e60796f0a4909fc40ff11535236df4bd8f6daf906f39c8305f713c8779c45a088d5872b244f2a50701fd3a70a628e7b5478374a0c57e6ea411fb69914bcee4ed5301616eb1096917282cd6bb89d6fd863f3ffe3fb56a026629cc1166d183fca9a6fdfccb66329612e8cdba10d0e2c31d2a6d5cebc94cc2cd4265796ae5c7c71656dd074ac5a65c47232468bceb3c30cb61a804673891bdab834997120787342f99574e7c14fe1a6728b9d80231fa433ede437075e8829d7c5c80e4f0ef48d15d125057963a28b322526381c37c720fd144527ae3e940d9a0e4a0ae8250101a4c2e0c39ebc5a67cd698f5be3a852a253eda7bdbe4559ff2d0eae23a2f6939351e50e4d1d8e817c8872abda4b525e57549533a44e78f1e49932d864a73436577dd7859593f5f5d4be3c0d5575884a3a59476ea02d8329d1b79fcf72597c657754ff8bbdb17bb2a4a172ec8d312732558fc4f76a34ecb2f2e2f5bcb023f7d3fa138ffe37a16f0268e1728788a7f56e363b07758958c1568b79031195036f0c9294612ff49ed892eadb01da257ace3abe7761b6fd81aba53aff2dddd4927600826fda806564e212d10239f767d186e9441888ddb1af5ef1ad9b0f8e41c80eee3697c225c51dfcbdf96772055dcbf7b9b81fc1f7f88e4d31cd9e0f8bb2595f729799ae817c09a1691d4d3a98cd76a9e4f9522146c2d2aabe3c323d1067b5c869c12b90a14fc47caa2d53725a8b2c7626899f93e9587bc77438f14fa19d648a9bac96344bc81879770127722c72ce96523f5002cc0e83905da6b72b258e31ba182284df2d9a44a2f5ba6c724c3551348ae1b79474b8074b0d1db653020d73a2e21b21090bfe5afd486a737202539f7e740b6960cee057aedca77af851fae4c9ab8d72b825fabb2429940972d8cc57b688cd98e517cec1987c6f9fa65d3f807e4f5e0fa67cc63fabbe7d086c029daa0068eeb856d43f4b7c3e91300bc7a87d5bbb180bfa2cf074000fb73772629d47e79623d83a267a618eacc99f7dd10b8effbb5c7bc1eb7a8c286139b859bbd6e43c5d78e2b5b20ab57a28643a656d3c2bb39c9b0214c6498ced911ed872c39a4e3ef9560aa4fcaf0538ae0df56f8c90430aacc3379444d661c1d68139723a2200d5871d497d8c924d609967546d1b9eee55fb790aeee2ee6c3c048ca01df22d2010589f52304ecd3a201d8e961eb67331dfa9579793a64c3740d8488a724ed7f80ba5583957938ca678f4c079c4c2a72723a4d89201278cbe04ced3047243844a5ee6dbc9b686c68d4b94cc1c71d0c049d817bf3a7072c61dd25cd73272f2cce7ba87b385d2abdc9f2c6795c1a482d2a424afe3ec0a40cfee47f862bc71003c262effb2cb27fbd3342bf7ebc33a30a6cf9b5e9c8fdc674ec255189470357ad5d81391e9832ac4acdd3df2cbb00c0edfb34d90b59b6550c6265d1dc0d872bc4d290d4abdfc410096c53c7c2b447b5c9e206d4485628088b361a660639372b1d766cc2f049bebde16dca02208719e66d98dec2420cd114e02fecf68d012728d3a40e1025c74e8e61852a287069931a3b099406d4b3562e7fd2756e68a32727e48602c3be13cee90688b36f3f037934bf22a45f226515a1bd2a9a2b5a192723674cc985bc2be77c7e28fa856a5f6cabaf298a0cbf229ff2f5a3c278d51527268ca3659e4b76dfc52dd4a4dc47859d38892de17287d5c69fc813d329387617285d5ad2510f57102ca0fcbc72087c73102d44938f2a3132b658a809bc2677a727994e9b38e1d26bf0981d549e71b9af7d7ea5ac3e99b0814e571a9bb12f9643daf3192a4f89ca824892795ff4adef560f2deefc305e96361143752816af6e57249ea473304680c2ce003d8dc0db5c68018a13ea3fe9026b10682e64cd89a5472e32eca18a7c28fb3d3a5540cba62d83f2a46947a707e8e4f0f9c099a4850a6457ef6b482424369a8b567bfbd980cdd03ef0fcff5df10f4e0bf8c78653826bd72de060abb4295cc3b5acb2fa080983d4b93fb84fdf8c9d2baf02139329a7c9d133739956b726b9e832f878babd721b403b369aff7139612eb89a1e5e0dbac705ec5eead68a297a102f00989394094bed4e4b53b97c9a06cf3f655d2c6b88bf653bbeb0d50c5a24c24795f5b7aa33b7efd33fdc24a6a064f6e097da90affcada72b640e97505ce59a88d3c9aaddf046e11e329384108ff05fb65d96a04c3bd907275eb6a6222e9a445a79b0281814a1110e239d8d2584c4429481db81d06f9d91637a4753206cee5dab4bd9d3f59309537d7ef805f8241cdf6c9592359e689f93df88410d63b368a7df4cf3ff8c5a722507b582d7dfb2b0c0595f39edc2a547f39b206a82604111b66e3a208dfc87c7cf864e73ebbb907e6251bb5f2864b9d9408a3b2d79c3891af230e036cdb0d4504ff24fe0e26fc54e1a0e1b1874b8f8e747276fd80ac857f819ddcdd53f8f266e5ea9693a54da124c4a9ae9633a5354da0729995f683bd5e283e1aebcf4a5ed8292c7c3ac1808f51766af58014ff5e52a55ecf1629bea96e6f42193d23d1e085374b82a8284a03cc21aa38db58ad92619b1d67c5e4cb8cba6b1b28993ceafb4218d6b2ac1c6caf95397f3d4f5a806e281c441c60aecf451eefd49410f62c0b2d22ea4f8b94b4e76e413ef1c8241f3984d372566e2fbd47d4edec8e8cd7d84e2eea04cc2c9df1bbfc021d2de4a326d18b41725e5a52ed62d833792ec077c6132d199742fe9d076e9c01eb904c54d3b22f2372c7a262484b8987e9f15174516e5eb31c0c46b3f809d5a6783f632e3478ee28610d421c41c7bead4cc21a09362b67af7206a2acde67621f258471a5e5aeab907207858b7d97919fe08ddfc402cac8b82795db92cf4249a29cf70d65dcc7686f72bc7dcdfae46770e6279c6f411ae29e1a45a017dcdd6616e7312a5e9668164b4645e219e3ee0073a5ca86bf0267beedd75dec966a9a460589425548f0c770a372a9c2b225b298a0b2c95219fb19f67f8a345e2fd9a262a9cc5ce68c7a30341f6a995c881d9a215bbab92f70f946f6446ba4fbe7101090ad0b129911a9d6422772b2122459571fc31be4274987442836b48de6eadf731723612927732176dd6a158e1eac9f6c1181dd8133e31fb13f00d1c184f6365c62faca29c397910fcfdd721795ee2185dc24c84bd19654a887dda184eda013ef75fb84e4e504561c7e7272e2b763915e3d2265867a68a296f28e4abc1e21e82365a8705e17c1ba21eb5767cab75df6fb493f80fa3abe2896d5556a1ac16b771d2d97b5f8cd72c23818ed0c3318a21c93abeb9d557b106f902f247838379adc021cebbdb87a05dd23167b61330d899d519424e0790b53bd408dd20b67ab5dcc7f9852720fe660ad9e2a3c726b5d386f804d21a8621b602cbd35ac94c904c5beec8179e810f151c952bb2f323e18f38baefc9e8dc31ee376de9ae012cca786e0967a4107ed05de3363894a72808d8b339ccb06d5596df59655cc40b7de60998e61023c6027246db042bc3b3359f3df41311bf1fb23ad06a56b0499f3abf8248273b6138ab7bfce5d86ecd96d3963d216f2f701c2ba3d67e62f75904cbcfbd33eacf2aab5ca13cc7c07355a723765496704cc83a566d5ae4a001359638ccca0ff7dea9dc0bfcdcfd23f479d72e05ba94f2479f7e1d8b5cf2e586e39f86c9f581cc30eb875831e6129f238105d5747fcd685f014e27a832776b8f2f155a8b1d6eb7f5532df4c0d51593efee772c7bf37df42b9be1bbd0dde4f7ef3b1486681c21fd6503077cf0803cf0e96877270b66d2e6eff885c4a93caa3147e50701feb1c53b4ed23c4bddd38d811fb0c7275927f96e719f4a05365bbaa44f12e906c2875a89cd21c03fee1727704ec7372d66cf7b110608ff515586ec85089aed6f1c036de0d1b3af874e03e2324b9f57254d87f246999d3584147d1669dfe55c0f9026ac94b52fbd87acbb00d891fc957b708989093ed27dadf9cb13c45c9a5e99743a0cb572fda09541b592af2d6a472d340eab7eb4c86cbfe032a061177db4b2a0cbb889e4a6d7d89edf70b751a8f72590f5becfdd2d3b5537a59ac804b6ac504331aa97662d4d00d37dbeff756f01a2f28130ebbb5637297411ea7c253e5cefbf007644c228db674075263498d44726d1a8d6313fd065cb869eb411d1cf3f4bb0d1f37f13dce303c8a562b6adffa7250690dd307c46d1ef441645ca7fc523e6143ab1d3b6660a531ceb3013ab25972f4c8776500162f3bf24f43844cef499ab8cc2ecc7ccaf2e41f0ccb49185abb72e96ac3fd9e23d0d3d8ff50f526e7fbb8805b289ce9f985fa8c223c2dd49e174744830b5dd877cd3b3157e22baa4bf1b4e01b7198764ded833717454c790af62e6060f810e4ba8756de2edfe12825482a60fd1d99bb6e621c660429f45fdedc7283cab8c017c44173448d97ea60f8a8bac035257443d46e5221081e3862e7325891058db897c3be2998597d3f86ac474ac14519d08bf5ebacd6ca6b4a56e19140e5846dd982fc3671eb441bdc40c7449b20ed2e0168baddffe9a9b43d49ef43727c274d476aa79c3dcb87e643db499082de52b42b8b4ffbdced89b680b87891704669bee5d567e987ce8b35087a19a07025d513dad76732ba1bffb0edadfcd15e4f24f4a353f2be4ad5dfabb863af41508e4354ce7bcff06298a65d64cf9d2508cacad57e902d5e629a64f38d4e829414005b2faeb5c03dd7d68ce36fd88a982b2ca82015ecf3d58e4909d3be0048bb9fbf7cbb73227b23fbbb3c124690ab0e7260c8543ec0cbea0b9d24dce8258fcbe1be7c3b4065ca40e441beee7a3bd2d272e92b37c6df9b548c83fb2ddb076326bb79dcf9b55758e849deea8e844173ce7228d5fc2a84f4cb81c05d1fcb357b5adac64f915768d8c47e7035d8edc535a54a036da8778e780c8a11a9a7f752422fd72384a7f2f9362b3e73314da13edab8721ade5422d144a341d8dce103a06f8be6ecb04098678768ffe7fc28093531803561d49a255f237d97ef4d5ad11ed28299a0bba89407b3081344ee25c2b85ffb654663ea8cab75be490cbebd8d32ea83c6c8465a3406e9c813f0a1642417ecba72f05bb561a6a65b13482897d5dd7fd1796a0b1eb9191f7b2febd93f6b2c2241474bdd944be43e6017a6248e8e15e327140a0721bc17b4fdb97631b2f001762e72ed023ee3e26949f317de03bf7a2df3c2c184ec9c8bbc0c5b4f6d750dba239b728cbd5353636167973a1a924fca710fc0c21674b2a0d5a67ee296f43c71143472490bacb3b837c8f37d0e4a76b74d6bda975c644e0137b5a2dba4f0a7d441f272b6b99bb4db5e7cc680f87d0d2b5d8258d32dfddc5018342cd68def48d19fd9723d9d91002c78b2e2a44c85c822e5cd7e12636f20daf5bf9c908408826d0dfc72a5b3099816a7445bffc614a76694edee7a4c1918d036dc355173654111f0777207058e3970c4639269512d0fcf9b9d4c8f2290f0164dec85730d8c34af03ee7297c25cc7a4a3331f8ed034a1683c13da1c0417ebff4151da0318ba5341c3b27258669a222f48d4ee78bc6d0c2206f72af9d8b10a83ca29439ad041067c376a1bc9342bfc826db1dcf152a32692bc118bb3b710844ef828f32fd90e6c35e2b600a60b9a94bbfe6a1ac5eb47119242392f284dbf57c8516934de871b56cf36113f4e869ef93a34560c6d480cff9a1d1f46f03840048ff527c66e2a5b69da8e1366afdcd4f14f4dc1acd41680ad640fae57c4e574d63bd254b5a96da5cd5f435d5a16a6fa549465c2fd3e6b99b111d8151ad75be562e0e08de0e284226044b6194a4cb10dd00b1d9b8fe3bfb7998f1f329ca8648685c20da559acfe77ffea5846724e1623faddfb47fe099866ae10a38f783c128f1ed3654200bc4898318c423557ce6c8d755f36d4e29b9271df41890d72eeff7d713170f0da541cf88550a6ae72f87e1a2636456785c1695c1bbe21faa0f8811508527bd5d0fd2430d5d2220072a25b109e689e86be9ae33bc1c5267189d767c5357d7abba68b70fd5a12fb7372de694075b8f211e0ad88713e65931c6ba368a803b1eabfccef0c0457402072727cb9fd41e371602b113ef3b2db8d7a219320d279e2636a0abbe01205b97887551c20e16a5ba7b6d409ee3e52fcf78b5fe0459bd686d8b8d7a5ef212e6f7f220bf816eabb2bfa1ad02c3ef741388336ddb6aead730378b8cde70e04ba6fdac472c0e9adbd681b38e902e0970b2cbd940efbea00e54f7d6fae2a3012eadf34e1726d9515d67b7b18acd987e487f800bcde47f4c86a84c04cf51e8d2b667f5d7f0305d9dae04297cc30319c41a3417f10554b0b2a32efbf438ddbe8ed4bd74b6d722b97dbd9acd2d5d3e4f12ba9aaba8075bb7793bc619751410a757c52e13e5272bb11bdf19b72849db6f2d4a77e33a07ae8bb5c465da74c6ad2a6e05769fcb9345e90652539cf127a2bab1b83ae6a1fa4ef4512205a170891004ee6d6bf23b113b70b5f9785a13ac35ebfed1d3a93b139d13715e07585a339a95720fe5e851472d6769f8603fecde73373d8e18dbbeb478656d0af748ccea5e79febf55143a272a8d888c89abb3cfcc03dfa472df195852cb00fb213b580f62532b6539a49de72ee17a08e3abd090c3426a883a3051554061a9ea0a7d7bc3988f111d78d87517205e4698be8703630aea061930ac5005e6c5af432895bd7bffc301b4dea214c72e20fee87d174371d059002b40b0307af7be0102bf809392285ac1ffe5c6bff72512ea80433b206d2afa08362a30123b792f9bd8c940c2a76e3aea91f71de67724915ad8ac7c02edde8b0b6046ed3725b54aa6bfc0c418a2aa555bf6ba570eb725f94f2b3f9fa0e78bdf6f0183a15c961876a5b96de08743b77fd66f6a103ef727aefb67dde70f53722796a86f7b4ab485971c826f13f477486ded9b97e7ac772e33fdee01888c1987ba4af75aef7e49dca8b17159a602209de84778c0d28883d795acf9b4c8b69c6d1297bffe5e78f095a0f14f54491ee384c15182c9896a1720f7781358369e8739eaee747941d323ab19f69829fd37bf2132f363e45b34b4dbf0f757fa33805145a7f2f80689ba7a1abe47c1ead3f969d640a8c43533efe72650ffc0e0b60a515a4cc87e48b5d6c2e98c582507a1b954daafcf9800cba52722f75e85e8eaa0b43e1bbd502bf7b56fa25fe40fb6cead21a2a6f58ae40bd9b72f15e6a1e7e6583e51ec15eb8b29b57ee579699ab464618e52994ab57d16344723ead0aed524d7ed0635bc282642a882b178fc9b8266965e2b32cd732613974721fdca7d3adb38a1a215eca60572c72e80381e174c565ac3cd76bba1c7d882a67e8b7d80ad8ab00949e18a96e4bd789326884e97e2487b54343c0f2de27ab17728cc0c80a0152122d1e66d76786e0a154827fdaeba150914233ce48374148be721b972a41ee64032ad34625574f2ecbd6835a720bde5dbfb74d4471c35b3d994f2efa9870984161dcbb04ec5c0aae25db38711967636fbcd5178e4bbc6bc26b726449899384e89f5670f4b0bc2c1feffa18f485b8834cec7a31aad6cdd6996a72ad9c37682b2db92ccc50b55e6053d0adc97c91df9334dfad2e1ee43a02365a7264cdab79644ff76620f866c7fd3872fbec37d265f905043987b68ad0023ef772b329711aaad66494c46e109a7824c0cec344d0aa1c9861ce745c5ed09e3cd4250bbbb41eb8ceea5d88f797017d1e83d0ecbe8bcde2a7b182fa79f60c6291217237e3e51211aa26306fd4b97814ec5901d4550f5ad5e174c5b77611ca634c4972e9498677065fbf845ea49be1275b907e680177b424f61475b74d062d1db06c7245524831b61f63a7570421d03a6a846bab206ca5526662b86b4e930a7db4da7233f57afcdcbc53abaeaa3174214e83fd293f799fcba22c37b0ff33fef129a56a42a8d682b60c46c1e2f7580f5f382cd0b52d8f1f86cc1a16e05f576475d27c27e0ad1be1d30faff251475809e646f95fcd1bf39889d766986e4a629a9e4f1462918487f9dd7a00ee539f95fe8c7c89f8c115fb09ee0283d1a2b85a1ccf97be1b645b4a058adb88d62a1658344ab70ecf0e95736e5a3a09927b5a10f7d878c072554dcafe37c34fce2a0bdc99ccdf10f657e24a554dc410987452fd375169de72a9cca4296f59bd51c54957d2bfc98d163b5d017d0c2430ff99556fa62d5e4972b6779c30c84d6f2d6abb59920b6e873f07292fd189810f03ff89750d760055721e2abc846a5d760d1b02e171c1d2743200dac547fc8c82f6c7ef2a63d197cb724c1bc4e8f01314b50afc6e5c43ed88d9d1747f9e9e1be133d8fbe603383ba172d09ad8cbb60bbba3f4fb21a12257ce7314c9ef1ddcf79ca99d36d807afff8e72438687190a6636f39df805958bb2a510318295f8acb9998a693e9d50fee95f72f1d5893ef89c59b94266d39da4d360152c5997691d5e95b998e332ae287ca61b59218fb8bbaabad2d30d53a52c4050564a2466c974f3734f40a270746a8ee67263e9ae49f2a977e7149d5b0ab665d5c5064d9506cf83e0958582594c482f0e5e9345e0df958daae1ae7988362168f7b451058751f3026cd3a67eee855f2baf72884173e323ae212e570479eaf3f8b16f561ca3cd7726d7e0a15e66fb46eef730f7297db273d4e203abf704a1d6589efbf845010089c8ef52bc94e3d532f34372aecb38eb9fad2ab11c8cce15bdec7773850ce9a478e487fb2764dff106080b7233221db0e2157d9e91842a96e53de959969f0b08fb7b5f8668d1bb7a2e3ccf2f908956e0c49deeac373e374776de37b30d6d800ee6c93684182b04c186d7fa66808a1586b3b7d4dcd82cbd54cc6a39de8e0749cdf77877508403486cf5041c72ae400d379bf5e0d729299f70edeb9fd53bd197b570c6b6dcf027012b3ec84c53b6559db2aa46d827ccbaa83b103a674e6128fb3a12cd6bdb08b6a5bb7122ae72fbf75bd234cbdee8300eaefde12f82253c65ec82ab7543b0da97141bd5454f72845c123aa44785fbffcb4b40976a6d0b3e25e279e2bba718cdb32534af1b0772f8f07d5a2c32cbb815e14ab13ee65eafef0e403dac4f7cbd5ebca8a7719dd4329233fc63389b648a759efe5186ba6bd7b122035b2d24785602856f593003cc72ea3f4f84cd1a0853e4b53a774cf25315ddfc82ab543688ffc554273e9bf8ea72b9bdc6bb852f3df9165625bc40539aee45bffcd6b1d6c642b77f187b4448d7724c8a5226359918f32ada2e9dc971ef1af321b22dbd079fcbdd998c71bd81fe7299778e014d96eefaf995abdf2176ba38ef6f40e93620bee1928935c153c5b672433ca0d3cb0bb13086b87fb55009a4e55a4027df20fb61c8c619f507a9bbc572e31a00c0a1a722a9ade2e358290708a99af79480dba5dc40c2cc8fc4cda4d472fe49d184115a0a9b3ecf799eb0bfd8bf424a4350d9cfa8d8a2151d5ad90aa03b6dfe668bef02bb69d7e6e2aaec0c40714d4bf67ce4d9155a7a6542884d2c8972aa6c6e8bec2e55ef901dae5daf13acbe2f4c34600db679a35633b6c44f399072839d0b85093bb03337e4df68d1aad163ab9f061f504410e4ebb5716807c5bb1276553c46dc7791252aadfa3d1d52c80cd36d7d6e33d7b5e5b25ca07901b15a05a03f8d3c4fdf12be92b74e23ee087ebdc698121028b9985b488aa3bb5548c57275b23215c6700314d5f26c21dd4aa319cb2ee701af192ef98ef17ec6adb4a3721d6cce06cd8afbefab8eadbc2221fe86ff1f44d32d1e84975ad2a2f2588e8509bc0c7fd0c6957e083a91b1652bc95e16f2676407988a9bc009dce0c91128987213df954f390a59fffd2c2335d18eacc94a22d406a0718248536ba60556a5a07298ccf82f52981e5696546ff42f80dc7d3a514fca673a096e165d5079f0462c7290da3ef26b666c29737095688da942f7920f2b0ced9edf154d562c1a3bcae6725179329d03648052a23ba324a7ab3dc33a0f545c4b13136eb7de7a92255b3067595b546697740a8d4d1443728eda27feea990ddbe713c866f529aa5f8ff19d237e52538dd85e4eacae2ad4ac2c99f9142d70693e2071c71a0400c431643f4b66d78056a90174d75b87cdf1208a3d2eec17bb754d3361ac6fe1298b9bef42fb3d8f11c59234a25e526a5b4791a85a5598fbe1ac8b681a82c3aafcb38f820e31721254cfc0696dcc73bc4d710a47fe0fa121dc93d59b9b35e75fb26d552f322568041d87b082a4fe7d33bf5f298c39b04399d9f4c57e8c3843c9ce01b302cf807236cafc967aad58fdb9924d665e84cc1c47adbbe1ff651bc03b1baaf501492859caee76ef0200aa170acfc58f74bf7dfaf564a698eef9bd3b41e5efd62488f043c65700cd1bd3d0581d152032d55166a19882ceda87f4f51c8db11f20e0b34b31eae7b7d721851a2176afcc474bd1ca7c2bbe33b147dc18b8af47714699a35c7274cffecb2d26ec33d30efcd96adc591ad92d677ae90bde8feeecb56f2d93b40d1554251704d8b5264c2c364ef43805747c6e30ecefb01d6a672f97eebc9396720388d4ef916239c5ccac12451512d5893e799bb4cd75ea8fe18a6f8d5fea72729dcc9bcdb7895df19044799192d56fa17a7b1f0be5d0c07e75404d0876648b72b84d65a4745f17c7d2ab26ac2aab2180555ecbabdf9bceb5a157e9b8cb9d954d1cd4c3e700419400259d08bd927209dbaf111f0457ccd529389ded728e94ba2321f7f0b2267e830cfa3235d875a07a587e6a5712b8e2b4ce84d28a29cf181372b7a33f301957d9fdeaebab10fd9eb8300954f6a4ea74d13b122c047f682fcc0c02a856507934ca9d8b99978658edfcd13f4f41bd11b99e04faeda3d4b697f14ea26e29702bb1102d9790b8b19163bf9b3fc4eda4b81a4919745818ed4b92bd72094e00adf87e209d3d49eaa2b35a20dd318273338d35f6627e3a41249f880a125255e3d549b5d02cae45315f8a915b29b5f23b7be123db9c5f123d937dc53161c652a78c742d970d13a3ecc7c2b544aa99e8ecd262db3cdc41100dbcf02fac721603b55d25704fc118b6756cf25d2de5881b5385f337baf3e5d6c87308205e720038925b30cfe89002fbfb70b11c471435841f2863135ad73294fdc8a396fe725ea3af3179792afc10bbaeb33f353215acd5768f922f9daf1ecf10ea29d9fc62d003975b0ea0ec0d772343ad1242ed828427a4fc65f2f3857b0476be8cbf16721213df184393803be60bd876d12093a7e96a9a397e85d54bde3c4d50c92aee72cd3f9e69ae2abfcca86e6beb983f80065e262a5c4f830e72ea96b50210646072a07e910954a52f6360eb1d590eec2775598c64b0e2151f535c127ada9893a50a1dc4c2f62470677aabc53a9adad52d5268f0b30361a0c8f5bbf440cfa824a172b686ac4eac1cecaea9e721f4d7f578c928eb4a51c8e582fb959f64cddefa5c7277d3fce0de281d16ad69d5fb54027aea460eb612e2c5c3104a2d269a4a3de672922bfe29ba0e28c8480b05a610c5729162a13afe6e9375068ae2b886dce9ae722d6c9901fde84beded2f510486d366d3eb26890bc186ab0e1716e9f04edfd12ccd3448c828c10c23adf2fe60eded7e801dc3a97fbfd1d87dafa733c350135e1e266ceb2c16c39177f276ab8b307dad7ed22e046890a3ab94662c2206024be86a37bf552c79e7a300f1fc19760a0dff19e2400a275eb6127dc6f36119ef118f1b988d44ecdc90deb4d308ee6ffa9085289364c8ddc1a592085c6c39484756dc72c36f709474a9cec0bd101950fadcd53f5c19952ea369f83a1a2ee2972d27767206c1439d9c779073f7da55eef4a531c7c541ded6048687d54010eadbabdb8b0231c43b431c2102a2a67b30d34e0aecd35cd61d25e70bffe28883a70e57b80d4ad7682aeb3ace6131c4376df0eb23a004c02c7822a5775fdd7118b24c1fd1a2729c6984add778389d1e6459946a0e48ce17d02778440c35e5fbd4bff215449966fe4cd49c92d95348cdbe79700659e256b704cf031da55faefd8c04dd6052026937f4ac486d4562d6bf378f510edaa8a234a2e5f32fe42cadf9f653500c1c1072ee73b4f4b2cb2f7489fe43675f389562f1663953d4f6888d92323f4e7de00572392bcce16bab99547f611e98fd4ab9b6eaa0bd98c31f6157e0c30853d905be47586c875f3f485bce48c98bde4752fe41dc21b8be3483c37f3cce6dc53edad37289942d5ca57cc393f50fbb14e3255248e2c269fd3ba44e25093d50745ea2a90eece9bc82b0714b0e04083a58e7d92eadc6cd0733fae5c0653b49a9ba889e5c539d0473c5150690ed155d31832e9979955d2327e8597e28bb670370cd462af172f7e38a07670edc491dd8bfc032ea6a1ddcebed2e95fb9d0d6be22e085e50f6368296de0aaa206b7ace32319b6b3146a4960aee0008a1a025509f5c6bceb5b37275959394f2f35ed1d4736243bb82bdc70290b9e9565248baace6db1e153c76724535195430b57daf1396e9281e7fec0a9696e45d55f3b30493e041bd0114d315108a9d952f19557320557fc70f573644cc6d478dc29c504fc1f785c60d0c7572025e80105c5707b3ae58cf043e5a0f613add2d27d4f1fa3238758d4c9fb63472076a608bd3c54a85afdfff058b1998f41b6d0d905af31b3eebbcea4f26aea572f67d49659a5e588870ad7de2b300bc84af2df846f046ea486b05ea7106dd12721c83cd2783c9e9a2f2b83b6528547e0964d129f55ce6a18860eecb025fa9ab0dc62f8b364a0405c26849f8b426a7ecda150af27801dd31de952bc4dc6e02aa72399272fe84be834ef2723140188c17e19a565cd77131562154217a2c44c0232cd6f82bbe8645d84f85c46315fdf56c6b288aec3fa912efff5f1f9232d810ca2f165a4c30e52925eefc58e24b916e935b9340b2c833e7ea39ed6f483809dc5d72bfc1b678b2341ae3ea8b1d571de8a343ae87047622be3100b498b4973552287256702f91eb3cbe2bfb96aee031bdee6372bcf82e62c97907c666d7df93150d72c03025d8087cfe19d2c8d11a65d2dcd247304df1501a2a11d9f7d751d0933412f3eebc9d619ee3f004043a35ba02e4938b7ba215a0cf8904117a38df0a25fe3b25d65f02e77679813eefa23b5378e20fd402b9dd5dc8ada2713dc39404797e72f934fc87206bab17403ec8f93fec27284380070f042a38607d0a18c0f377be72ea029caa97206052c3b46ee9977833d7c9838acec875a00c5ef04717c5f88c72ff1ccb3cb0f9edd3b837eed444c72d0bee9ada1a7543f73b6ed45e902b45e35b7d47114b91473911e1bf8c05a46d3fc462c0c598722ea90100851e96c23ff2723066fa2989aafcc4b2ea8a7f2f7874d4a985e3093648518315479e4bc8ec0372bb8320eee9433d1392becf97640f4162c50670a7508a1c9e4f5e260e1d226172315fb43a54abc55546f39d224787a9ae7fd54680c31df8189d29a4b28463ec72db1f830bbdb80491b746c76a8bce5fe6e1e5425f07448371b73c961558ca1649be04139af39ad5dd36354cc90b029816f08c57269a8958b61e34907f73c87d330c98defef0bad6c42679fcae5e99d87c2fc3f16aea0ee5b3804f377448831f05d2a232656d150613858189b32371a6aac9f0e5ecf846f1933b5054c2d9a632725c729f41277ee24f223e969ae17bcacffe0202349f654f2a20bbf6b70e386b723227d0e95db663661c4270ad91604ea5a6e0d9101c8e4085bbc15ebf22b7462aabed51dd4ac3623bece2e39d07bca1051b3227295b7f5b6cc3883d356cf0a47204828ce90cb5f573c696b378f40540872ff16d3d1b6f63573a42ab3b998e4d72a4bc3ed9f1ccbb9c0fc538bf32d931f0b81f8635b0cd1cd4c7c60c837983fb2dbe992611f4039c8527760bf3fed233b765979d77efd8f520b9b146a1a7f03e3113e4fb9c0db8c1a117e6943d1a714bcb3d0169af0a43ec6729b6a22a7306f77225f1632330f3913dd37ff0bbb3063a9d317dc662f45d7239e94c87aed594f657a45f42784e3ab0998d6473739a1f959c94e537b361e4d789bc46cc58905c8c720811a59dfbd4cd248d0fd2147278700b0bf5d905d49051fb5c3dcdddb5992b7297f17f0228484201398f7a6076dfd861176dd100bcb0b678d72857df6d0c08072a621104570a965bc3733c4b0a10f62d5de8ef1a2c1c02a2696e6e9ec469a1725402b1428d28e16286ff26f4bc47318a7afde7f6945f55e08629b11aeabf857214a72f2fccaa03b5ab79f4222069612d581e7e21f4deb8e22536ecd875e66a56574acc074ffd6d403188862038e5a4e7109f66b229054718f62388aaa22fc3287b1acc9f2d4d0cec7ef714ad02d32950f6cffd17b78b0d62142e0ebcfa85a60f9fe55038c71e1238dddfb56720714880f982acef8d44388a303570a434a992724b4a85cb0e2e7e91956f43b7c214c14562e2220c8a956e57521e8b8f280a9872841afbcc8f82fcded3a938c2f47369f6d9e6377b6be9dae455aa7d69b3b04c5688d9bb83a39601f81ddf9dbf9b3cd63c7097e56ac6655ef8785a8bc609989272ecf3b0cdbb38e76e5a72df05bc6005e60347b2bc8ff99b3434ed290a4062157247128823eb75ce4c5a09d9be30ecaf6e0b2686bfce895228a58750d70bbae972c04b1d182343fbc5beb2927ca415876354b970e4d3cd60c247efbaf510d0e6728f6af7cdd9a80c6c44720b504dcaefd43ee5de033f99d48984400d5a5ccf0926b847fbf884f3d105bb03880e1a01d13e1e85d5347ee434a1a5048dfd44e2a1020bee030aafc05730d6cbdceb4b4d745a95678ed8ade05843279b6a6e8927363babee3d857501661ddf698e34fe1ca1d7a2cdce011e92f306e9a94f5d3ca3e472ed02819d2c21b68e5aa77e122a5e51d30060b93f7faa56aa2dd045f3dcb48237c4a4ee118d9a315ce073ff99508971b65c41770e6789a9845a9d10b918dbaf04929def4a6935d893a9d3764bb38959c67553ab75d21a1584b0e16eb96bc0467294c71fd5bcf856335414eac989c6961e8083c109b0510d377b4ae0edef757d228fc79c3deee21c81df80dae2f9141b45ba944385450c4ebc61f4e0811b160336d3d64991106f68183b13cfb0fdc68969cd8126c99c085492b67aa512293b1672dc23ae6442ab69cdfcaea6bcb722d97566cedec209cbf887945e2de0b6a3bf72094f70103226579a17483b1fffe5d3ad7316d2e199ce552040717798317de0726a2d4a8c9524d6e3d4b33f6e5e205c15de8e741133d23bfde73da5ecbc2f955ac9eaa56d84ad3129d5273301542b06b53ff613ecd93b4f64070b165c4a4396726f833752b3c0ef5cc44bc357832c18c286e8ae04fcb67a9a7b0aa8749af889723845fc40e752e79dfde803ff89ceff0ca6b3eac5ad48fe7e94dd8e02762d16729acc36b98f1fc2bc6509681b95349fac768a13fed3d09e4b4d70aed352a5153588b71b5a1dcd80d695edd744ec96db12b42e53c9dbaef36403a647c9d34eb303aa51a7779cf64eb51e108860b00fd011e7db3984d331e2cd7fe62bf6f2ca687293449c85ce9ec8578649d503bcee4da6f82b0b2688d4b841f7835e12216cfe72509031420724179fdb947805d4e62bd6c263be4d9b44e3dbe77f05ef1c7d9e7266083ef7610d77fa671ef1c5f3e1b1a601672f57f1e19f7e8236259ee38b1b727b40df720d4aab78f6502d1f08b1ec3a01179cb5f15598640935a7de059c7772172853ef84bec71742272308b3df44b066f9458db29bb6bb712d4153234df1728aa2becf0930ffa8029d006c2e8a6a5d3447b03a0a4951d50e73f1d38c1ebf724748053f981283e7757b0ce52d51f2e25cb47988adab257ae54c60e10da0923dfd79fe57ffd8a114d91c5667171224e3fab52cc0adf5f7a56ca7b8f8210e7467f5a352fca2126a91260db19327ba52ed353cf1c2ffe07736038509a3124e8c417b5e0b2fd20469e100ba5a6db5ef41463d49903c8cb5f5147607e52bd1f64772dbaeb03376f183eee1cfddf01a31f2575ce18caba0c7a680bd6c38d534e8da7203942fd2e0915e5284161acc371a762c4f104a880bcbf7c81b72113ef34f6c72a755982ba0e8385ed06e802a91247c4facb29db4f6b1ec8cb2b8bc0704aa286344456355d0633750c21bb9e22f474f1d264a9b374ce9b75259d4e16aaa426548edfb9350cd3dd0b8203b5932a11a833515972be5a5c8211fe227b23f8052957273d434ac97e0dcb8f003858fa8f1793b65a8e5c5aa6469afe8c3b295f51a5f729e6596cc76575aca56e7bac94079641ddde7954cc59fc59f0a490911d86d8572baec9a5adcd0741c2aefaf20d96fa58adf013371d0c5fecd92040f634357fe72df8fe0495e24036593e0cd2270cc786d5f56ba3150f93ccddcf2b017bb3a3025c7d0ba484797e91631d59da6237943b77c532f1e7e8564edf0e2a5ce834f7d07175043c33622a0447273b2dab5a41f5715b9274e7818915bf037adac0608882d5331203f964d3a488bb46424cd0914cfcaca33d011baff8bd8aaa029ffea4f6762561947ec31517debf4426345cfc49c6686b2ebdd0ae44a11cbb94ca4ae5a724bab26acb4bc61c9267c89a9d51e61becae905ad9def2d81c6d5b4353872f2721b8409804cfb8aa19f3c65011270056572fbf7ff71366708332c0e641d46a70eb7345c75789ca339cb7f8ab5fb4f26dce61df22bb1eb38560d62ed8ac711a0729a494e21dfd39e254ccb77327c2e493b963f7c41580047ab24693a89cee7e06bcbd90a22818c59fe3abacfebaefb20ebc88d50178e12600be8d571514597f01bfbcb1d08e70c83873d504fe73b6840a205ed279915e5af984c34df998bf4ed7252c95f689dc6146e37580153277735a05e60f9a902c05063f5cf10997f94072883d921b3a715c2963317f63f471aa1d00fb099526677f251acec018483cb5e37814d12a4ca4529b8176652f88f26e4f094246f969a9ee0f464d398bb23517b7207121e3cd05c362effbc7d6ce241e04db08bffb92adc5feff8ae54439f3fab22736419ac31edc49a616f82d789ad98434b1c32a37848d4f137b33f36a48d6172786d67e272acb11d730eddc75c46bf36eb0b59b113ae30e2d9d3f062defe8e1c6e7600ccd91069f977b701091edcc05e291a5dd126bdcd3459d7bc6901361d7268d3eab8209d9fea80c8f4690f7b9e1423ec6c76f983bab508eaf6cc328c2e722cee3cc723c5091b8b229234748979c649a6490391b420f2b5ca4f1028461a72dc0f5a2d0bd3c53ef83aeed5b774aaa26a4bfc19210aeb555e690424ecf1c3096c7a3f58a22931e581f1b229a12195a89c753bc0afa302a019cb62e8edabfd72b9cf6e81d5be55318f0f0011e9fd41855d238b6cfac3b594ac2dcfdde6c33e1d3a863a5c8e64808a18bc7a29af8d06216d6e2e5789426e2dc3162499b8ec0f728c5da835a4a7256eefb9f4872bdc46f43e368d4be74b5e1b73d5771e624c0c72cea013bd7f847d9dafa1b9c59d5a36ee8cec7f7fd00bef3e79b2015cd39ab961d98d4fc1e2ce1011ff2bd7091cb8069a8cd401d98d3aa4391a976e3391e14f6573b235ae48accc1b52b1e2d508cd340d531a7fb4d33227be0546f20e53fda86a65936c172deeed9ec6eca541db48a69d6833f3a18074f11e47e776f3eafc4b1f8a7b8c7d78abc2acdffdcb0610f47e6986c0e304446524b69ccf6849083e727239086eaf48af9b0e21f77f1fa188f5eabd271b6066c662303f5989661f98bb72d6fb8c52be1b34361b82e857eae40ee302a43cc4c40db7ae28d57c00aee70c3d7ff0779a3e56a9dd455d965644534e685229b55ef7aa43543e2b62a4998b1e72d4e02d5a6ab15754e967682936cc4de479f8b8f8edcd61ade812863c49d090728106de18952330dee516ea33f5196ddc55638d442d1c23366f099714965a545d9825f1ec4eccac4021a07f1c9f446e91962454fc6d17c38dce9f63b8c606a77214cd00e21420d0473236f2a645b82039a80cebd5d34e38a011106deefcbdfe72149e93249066515792ed5b5900a98ee4199c30cf92c595a33a01fdb8647f70720cb6a997de41aa83e3315a91f182d62cfbadd9f22e83cf9763ba231f7faa67729251ceb03195cbb82646af4f3f710a9f68be5b801b2588b74858e5171dae49728db38846cabda28f9ace1cb8d394065f44decb1a15582165185ba65d4315bc7212d95a7ae61131b0ec435ab60f0baf37670e0cae7216b32b47689138a08ed8310ef58371bf2ab69f17d25b052d037d154330823188bb8aec10777b2cf2fcda72165ce6c5beda3770ee2125cce5f414278172448864af7fff0d4400c89608e17270d0b40b5fc316ea7217a9d0fc81b06bcbe0c7d4a8536b66e4ea1f30319c1c46d08db7250ba2b7d2d413f7df3a08011ec44cd3680dbf89847b5f79eb9470707262dfc138df716671f8de79ed0d3aebfb6a2dac1aea6d8a4209b6bd43981e49238121f722d613a10d51e8e9266658b0a18717e7143b23dea3fd3b7a788134b772cd98d3094d16c5a9c430e4f4aead06a510e388ef8002f6d1ebe35b2e07b26458d351b056bf5d2d33b06e39db264c8c458981ea7023431bdd4a4e29900735e1558aca75bb832633fc041491c2e72426fd8ce71389957922396169d1e803230750a3b57c1a74532107a98d9c322d1b870f47a798b5d6f544455b5d18a537bfa965c148fd92e8a6c84266f033048e16d2fbd45529b4a71dc5af369a0a2ab7cb00505d63f6656f9620a1ff3362e72b97a1bef8f98acbdb291cdedbe7df62c527046e42fc7365549689d9cb7bea687f737b48fcb19b52485564a37d58754d771ea6401d8ca17f11efc5b97e80d63cdfc507ec8ce6474f49fb5621a61d3310c1abfd7204cfaf7c7c1ec8c5857954f6f4987901dcdbbea5e289ce0473e492ad8cb39672437f8921ae6a0a6089457d796aa27116671b651bcc74623f74ea3bf9e83ea272ac04cf96c07a354f9d2c168c66debddc78f52629fab223bb6399d8c5dcff6d1d72944c1ca7c46b88042d1b5e4c29a069cc9fb67049f5e7af4665fb08d0a33172a33a3e67f2a033dbd2a31fab64ec021b697e00d32f3d3fdece38b1a16a918765aeab72b46632f36a5e81eeec50f53a9713a2735a414bfb39e2656f2130c3922ef73b381496c77322ab64070b6465f40f1053323891681cabbcac41a4ec79dd72afcd620b278d764758049b28a15a63111fa47f914e9adc63c4b04a917d2de6722f64a58635da03efaf930e2f2639a1a9b5dd4244e37666783f09553d1112a81078906d4dd2045fd06810b2b720705019ec5b5996704d89a9dbee8b4894e1161dbca59faf0d106093a2c63a8bb7661403de77fedef6e421c31793a1217acc5b725e56e48317cdf6de9e741376f7c4eeee441b3244ba4e768ea3069ce77d02c22ddce8a80c95d537714367913643583096931b71149cef4bafe032df57f486ee726da99b835aa92fcd795a75b6d00044bdbdb589bf19b39d7a045bfb98ec0e3630a662abe80db1aae324355cfd5406596509e67065980ccb72fbe47ea1a59198722af32784ee6261709b0f7d152c91733376f5122317adfa7199d4dfe1fe23174e662ee25d7308fb661fabd5576b946d59608a33cc0c8b0034178e1aea675e284d1ae991c79aaa2434df4f7598acb03d149d920d9f87fdc4ed551746052607f544f41e09d7c564f5e1ab0bbd886a13e631d3a908f1a5a9fc7084eb216c27c5d072dd88a2867bd5ef2f54cc7a90d68da51ec6de313cf5c175eec2201c13d0f586253a021ca7d64210baa46e204985d06ce755fd32586d0f8f8338fa98160419832f673e468fc6ce692095e4630465aab7b506bfd2e66dd236bb07698aefb9d98a0a26753ba93525ecf8cabddf8ecb40e2f549d96c9209614c6b1fd2161d26338a72e0bf8a726909faf9f6853db44fea158c8ff317e76cdc9b111183d273e696b620baa0a4536e53711f32f032f77c78cc001011d7162535718fa5c95329a788da72bbe8ec112472531ac829d34f32db54fbafd52620840402011cab65f06ba152727a28df2026a5c4f7a45d2cbde4f750b5215944a06ddcf8e819a5a9b06ab83707d795db52fd324fdd054bb3d208a0420904d5fc72124241fdf28b4343e8505e720396af34114d6be332c5101946e01b8a6510cfa7496029e84684257d28cc7f72f387c22a271b5b0bc06be121164a213b34545c6680438cf4e47162a567831b72ba6cf3fa09dd6d31a698fc6aec809cc922295399ba57e821afd6f6fcef03e872e7fe0ee0fea008dfd37cde6675700a1e72b6f633c69cad0146e4063809669006e6114dcb0a4aa964a5590ed3cf681f5d54daa8058bbe0313869dbbddf6aa1b72275c7d26d6b4afded84d9b2d1d48fd9a174e6da2279e480154942980cf51a472b174a13f2fa9e02ca460c091f6bc0c12e923cd2a2003cfd7f8a7e8619699af72750c74738e37f6b7c945aa355539c293f1c99bb9ac76ce803f51dddc76f1344dbd9a60ef45d89ff2eac040c7846dddcd2d2895ec9d38caf3776fedd045e55b72339e30ed94d6f9b953609680ca7f4deb352e474916acef436ccf08fb775a6072b33f252084e6dbca8e02d3a5c19092f0ad081039f56ab335c7e836f5105b820aefc8b52403fccb5c9f5889edc09d16e0f5151975d6dc8faa98abb8f3b2999d1ad0c00a293a07f50a6ed43c2cf3fd81b806bf91789ad45ef33314315238b13965d0d2ee376ad19467e66e15abe7eed84c92d95772a507e9681e6b063be35f9a7281fd6d8245a94afc5fd106bbf9e58676c3e50684e2e20788aa51766b01739a2b574f554d39fa8d6b72f94e8fc288a47ffe84253aa7d7103a0906e1b2149a64721068b48558fcaea9d171514aea1ad9c97c5f464d2c1c64a67991451dab209169da96817d7e9abc61f4d64b76b61b69957c245fbc9f76945cbe010b98224a7e72b451aecf2456d1d0cb933d5d5e11a95fd8e94da4efc41fef95f795d36af088675963508eb90b9bc90fef29c6397cac1e25b6107991fe157fbda3f1b1ec71e372135697ef6c5dadacd7689ec081b8ef88dd3540f740d189725a5309bd7b5f0c1b1464add11c2a3680048992bcdbbc343525a59ec9215f2befd330357e7da0e27208e6137d3bf923d6489dd0f070c50da6b0ac686a43647ff411d1bc5445f80f724b1326c340800f9cc0d0cf8fbfb212589d0d16143e3b84c946d15dac7ddf047257bd60ecf4c78524f803a1af9a4234b9bf0b36309bb0e3ab89186363862db272855e546baab3039a319bdf1a3ef59e1d829b36ce62d6b81c00c35eba624aa2093d7e2913c587681051a085bc612810e0356c29bdff4571f87dea184e285e5372621e3dad793263b2b53d99fa7350c1027ae58092eb2db041c33e7646973b4d689a9e51a40445ef330c2dfc9e18f291be360a537c2f424bab1b3c2c611507c472135500af4ad8922025d8d460e8cf3cb48d5fa543533f9bbaf4e9d06e672a3b4880dc7c76321c45e11e6d875aff7e2b015d03619c1d402339e55be81d2416c829b9f0b3a02b7a3043f8d6dcacccfb8e45081e5561ef63a5d259e5308240f34360e58dc8727dad7f28488acfc552725ad9a13d102cac6a461f35ccefd57e011a308e0e789b6aab7d7b6fe89c7a6f8b55b05d0dcdb6bc6685a332183e89de430272b0ecbe05cdcd0f9dbda64013ac78d846ab0525a3dba15719a05a94e906b72a722cfbff3dd44ac283bcf6c175b4b0871422bdecff05a6312e4949cfd4c459627282b070334910c0d53d8778b48ef0d28110387e20391d9f08e05c45912e9fbe25f076c9c489a9522b9c5b8508887c365f1ddb52f12274e5b817f335c854dd1e725b13daf9be6984ef065114b3b3dec12ca64c509ecd1746193728aa2334212c72bffde01e8075659c7c452eeddf679365de99a1ba9ee0f6726ebb26bfe8552f21aa8c4845eca160b906a473483eb29825ef7cdf34c78e4d3cf6ff12aff0ddd74230307d70ae9944e71cf8a18aaae3f99fe41bbe73d88f02d48dc63d9dd593ea72f5850084d108d3c463b828cd58d72afc9d973ff8429891f3680ad6c47c091a3504ea7de212de30da1f504477a0228a61baa0a200ce81eb22431727e4b862827256c126194649965a97975708b9992cb8f8ab729e1c8445cb11c82293e380f372ce2f066ae0321a47f5ec06d39aed004d61c6e9e5518d8bfa0b1d4e6ea58ab646e6a12f32ce60bd378663558f42f21c65043c218ed5619e51503acb83ad0f5f725214468927042d1f946360d0183b6258d5551763c4d763d6d7359d9d78ef6a72107029217018db6a7c73dac1a5c4e7c399fd6a683717ec0a39033cbd13da4d7209ab7f0c77a36a687ee1f6ab9ef6c3f8f02da7a38ae21c40c459fdbdf19ace2b87226afc79e245a5f63213eb9b9849788463c2a77abafbda6ec6373cf98a01727c68c5a5e9f64c17c721bb69cb75020bdafca9a146c3dfd4c6b3cd77de169972409b4856da2a84236fd6553e493d7670f1747d2886611a2a31ec0ec5ee4af51245ccce9370e7e5bc5a59f603f3572a49c20fd0e87bac6d3ef9441a55105625721a3f86687fb3b0b6aad679f1b9443501f324e1933fc47c713a311eb23e50bc637ceb08fba0ef600aa813757135b786732de3a35df124438cb4c05e2a8ab48d7288fe0abc60a146b3acbb387b9c11192763905502ee6bb195d2f5e5ea09fc7872129ac0f410dfebba5bc78fbcdaadd6d592f2d72cef953e3193389dca51d155030f96ac98cbd6fd12f86d1f66f15397aefc88241300df1c586964439e0f2e1672504879a25a603ae2a00fb0484f3a10632fea8e916106b0ccf6059c7f80283272cb770c556067335c7668756219d8209c10e792be6b48d2600a1153a1c79b3972d822bf8ec0dffd19113379c868b9accd3b080d99029ee3abebf6b3a507be8372f8611ab68d0e1063908ad14dc4025e2526ad3e44bb21ecc2d1c743d7eff86d2f31f1b04108e9fbbb618e2d205ce94ba26aa5617a8d0aab2e617e0186c9a173725fe3a32fe8c8717c1b366e948b854e5e1b51fc6436e2d6bb0d6d39f6d2841156202aa11455d39bb118cb4fece79b8adea11cab83a0021f2919e90926d27fe8721faecaafe2cd7ed58534b84d836b43bd6e1c08fb9a79a55884147f3939fb3872e1c2c28e8c3d281f75f3a6a68c496a6df6dadd7adaa19219129672be8f74fb181d748d99d27bdf8715da9bf6b6953426cd006b83dab71e9b5981621a6252fe32271d9b4ae2f576c4d9c9d845924d8c42cf40f1c64cd777485478c71536ff766b44cb24cd9f03425ff634532158ad94b8157c0ac3f0108157d0d9ddc2cc2f2572e4a15dbe324666821acc70bd0834f56fc9d048affa7daf3380594e5bbee62b726e9a2a1d7590bcee71dd8f9eb44657fb4188e6af8e6a91351711e853d5f3265dd191c45970d4f553f2a72dcd48551a3ff2f9f00fafa1cb645de7f3bd8cb48572a849c06fde639081448a2a4cbe5b49c6c9f8ab17587f48d72a041b60779f1a72cc0675597e4624d6c75f74161d490af423cf9ca3c6a909a7ea6d4438212a8a69b2a36d8b6ec5bcc9a82e5093fd180ac79e929cae804584f7fd1bc74c07c38172ddfef95ec625b6e8bf8da404105bf298222f9d09588a65d6df643eda37f49872619867726ee84fc0e08f3da240506e7bff80faba659a92595596149e21d03b7269ec0c969ab4f6a4c2e8cbb8e0e3f668b90c88401dc418b92447741d82806f728bda88a2a130c8d9fcc1f56e16a107c0b814fe4478e172bcedffc062b8f4482c92b51764768d0852481e418711227d23e6037ded4b6ca8ee0085e8052a696a72031c990bd07e9daa54effaec8ce9c58071dca00428997ad9e0406d4d637ef3723ad3923ecbe713251969207fba9d39a7d45648ff3c0e66970ab1f334f3f03572ce027dc26fbe27c12aed354c83ca33b7eee89d8a676fc28678e303950d9d3772c1591778c35ac22a9ac43362da14c63972f8e43e29b74d518e9a9056f5982315244a061334bc84c385b3bf34245d4ec0deff84554a3059c26622a24cdaa29b72fc85babb8c05fb584ef8babe5e82662ce93a8412ce11b2df583558aafe579b7117f4a5daf5df93d49091de85e7ad4aa21816927e1ce1ea97ec637dea8dd2767227f247bc00062db5e5dfbf71dbf70fc7d335c5ae950d062862e069b6b2628d702eb7638f9f6c1f39c38360017e0ee52a47dfe224353bc1494a64a5892d8d2805cdd117d19ea910c1f60c3210da50a7912eb7fe8fa9bd10c1aa011941a021be342b4dd3a4b1cd837daf31ca5a1cfc2c36507df127c20f63445d204a996b578037d54dda0f8c6c698ba361ab86c25d0ae4e97900990b29187d510fb494f55210724b50832c43f74af25afaf2d1cc5e09793179027af0b88a99fd8d2150257a5a166d9eed084f77eea8a49b59f1d91b281977eaab2a86a9f3e41100395fde10067292c3fa493233067f3ea47369c447ca06e87b7c2f938c3db318234875608fe772f03f3a9302d7f6d7d40ad3120eb8c1daf43dc621d8e7495add50a7dadf2345723d962b6002154b66b5d36904aeeb9a648d3be54a85d672e03e238ff1644b507270f0c8566cdac346abe8fd8001c9b2467911e267a0372903a1009177ab40dd72269923652953e69125a296957cfc896e1ab8df6689bda77c75bce5566d9f00063770ced62e50aa3529ab5371b8c20648397f2a7526b56ef430dea48e5da99b2d742a72449628ca29d24180950c0cc853be38540f59b5070387523052c2c1a87287b7eb3a43727eac9004527b829a2fd657eb30f2d049e274405be5e4179a5072d0b61a51ad904db9f2304a5f983b5b1a5645fa209a3b9f244bca9b6a8b15b772c17950a352a1259a5e30c49deca32531454011eca058b9c9655bad3f3d60807227270b89264eb66caccc57c75cabe9b69e1d69fb001dd3e62ecf29ec0c0ba27234c99493bb600ecbb668477f0cd21678f120dddfd45fd654c4534cb20e1f1c1fa4a17db76917efe8607b80f2dd926901ba31a11d2adacfab5f8a0b05c71aa66cfaabf38a7b4d99171ce11ddaa7ecd1a4a1dad790eb6878c85e1d6dc6cf47a372ebdf0593b0e0ccfbf5bde98924b2b5dcabf2d93418797aaf716ae3d4232610728be6e9cdd1ac1ac4e546b7f23f431e7698abe3363ed79224956c80db6d89ac723e47f77edc99f7382a70f511605d5ff4573f92817f6305e9b27c6ba18b18d31c52593a9e992622c38baa69fe8e8cd7bc9c89a215b4c0c9f3d8aaabf38a51fb728a982a16f3d062888aa60bf451cae7812f7a532c10739530db4b4bf4413e5f6bd87acd81a9e23878ecfe39c28a23306eac67a175c145abb39b170f8b214aa4011310c0656712869495c955cf30bacd80033ddcdc74cda3cca4edd2bb88405472c4d2e25c24043cc8a36c382b3660bbeca4de74e433b2c97554d46e70dca1aa72958c90b481d0cc8cb8701b4e58606694c67a976f60af5501820cc46a1dec7b3097609e30ec18beb7dc04d4b68d595cdaf08cafa4f2748693b0001774e02872729addf6af627465a82cc14e1879fd8a4aecd5c512cd9cdb702e310cf7e77c5a72dff97bb15648ab91921823ddde8b2b31139d8335b0ae706d5dbb5f9776730c723b06f5a7d1d2c97e27dacd3682f4e6819519d4a2d0fc763a0c20ae29af3a1c72a92105a32ee050b37da4ec75b04c38f53f56cd747e8686803e207bf0f3290e72065d3083068794b2bb83dfa8c2bccaae8f522cf5828d6a31a2667471fda5ea72bd556da4adf596deba443013129a7bfb5bced00b2f256c57ecd51ef300f2c27231c7fc66f9967bb095922415113da98808508424c85805d8ce9fdf95d0968d3b0787d6b7838ba6d92ca67d2a859c72a860a5f9f2e6d3b356bb323c14fb775272f0077a529b6bcffcd873190061850938bf6e0b63dbb9c96b4a57226ec49ed27205a161a557ff77a78396c21d05b3841bbc2f1d9cd5be24ae16d9319113f2d772c9958c81ffeb9e138345f708a2d4a4d9459e600068dc8f1d3d3a246969320672e284f8a184a105e24a3faababb32977eaaf15e08e11d5ac64741a97b19407b7232042a3546a2143ff5d2db04b7f0407966a2063478abd569d946718379ad0a727ece2dc8dcf7b12c61c851a778d5271091e365ba228823293fac9299e04d8f72ec3ce6fd4007bce71324823282d2b3c82169ee52876950310963d2d2e5cbc972b96abf040237e16d3cc8038feccabdf8d5a8099ad970ca1970ef90c9a70ea712c9a9be068eb4f1ea6da01885ad356956079dd612e7cd83614290983742c53106b832c726e6d26f321d799b356b58bad91a0c34d7839320cdd1b4aff1f2d6c0724db4760ec17533af50b40f075a9bd099dc3cb554233ef2ab69cea5b7b3c68472cd8139ec382e12a9d9daf624fcca650658c19bab1f4859a0432397466b189f7225661183141bf7f4860fe7a81f0b735b2a80cb2e7a3b59e777e1396da7b1617237c3b5d6eb7165aeed0654bb4222200c22377528c25594277356bbdb06381b722a55b9c89a0d70248a9b473936ef6029229e15c042886ca8b3a9c861891c2b6dd519c30a8924d2900532e67b6a78c06f88dab4b6b3ef5753bb85d77f65c37d010148ba89bc278bed0d4f33959fd24a7a254d1249a58531cedf0ca7438b92280fbbf447a44a449ff4a6943acc121b69b6918e99776cb9cd1bab8fbb93bd5f8258edba603419e65159ec0d0dec515f8ce5498d7ad61a923c487baf7d6555ad4e72d02c0cf1fce77336ae1f6708aa844b22655170b659a38d9fa7608b138fb5477222f947b8ca68d61f9a055f17153640992205f8d3b068398d64206bab959271415b6532380fe4d837043ff93b8abbf3a0bb3a55e6c848085b46d2f09c3568485c0224357772faa6a4d812556e279a6bcdffab36be01c48ce6ba490edd3ecb70720186269be2f56365a5888e56d769f8c9bab3d60384d8b0974a8c637d38c93372549756f09d92de9fc472db2af5766c9c4fdd05a05d2fbe7f361cd8588be3c27212ba3797c64a5fde3997446db1dbb03aa79493a30bc6d5d58b2b189647b4117253031d3315a21ce6562d39338071fa1aea8cedfdffdb3a9b0447681e47856900f813b338d0f3f703d7ad71c1b778b138cec32cb0aac36d9f88bac2ca61adc454a8c1519820b4455f37160a48364f359bf8c18030b854a7455c711741b79e87278bdc31e2267998aadb679a3d81e9ddf801b16dfe6dbebe00e1dc265dc9b04a7266eb9c171c6f62b4d23d9aaa93ce3bbe92fee0b5733f6eda36357a2611a2d772b44c06e7f39f3ca27725b9156f7d40cf8c13c60b9810bc9fba398c45b15ad6390e78ca22a20c405d4cac29415c5686a20fb030562368496dabf74fde1b67d90410cf65ee8c758dbcbfe6319acb0fe5dff6d563a80f10699c5fc36adb0198fb72d422354b8b8e9603ca11c83aaf99212956da3b850f8aa543ecdf7961bc581672c0afda2f93149d70d2ac2d3ce511a0fd93d9c9638fd4e98440ee48576a84297294c6a487dfb0e18c9d5e9815df6918bd2351ff9e94b4b1d2ad09b8cdc62e7272921ed1cec73e7ab8950b851be83c4301d9f144fb14c43e9b5be56dcbc1a9ab12434b6153396177b7e39459a0706d8751b217710df30be0e074400647d59efe5e4c21937274c41130b3be266a320cc000fd7b359c60b66e4d6c689e792479d7721aee46cc256a9c609d960d1050e1ed19c702508cc2c770175607072d56ff6607a84730b739a603e678afba1ff51933ed81823e066626086c02e356c9eb1aa92450e26f0814833b239a3318bbfb6d400b2e9353691de215bfeda3f35af80759348f3a42ea3c1aa1189f3c4288da9ddccf10be36766a0f3cc9020d8748ce4706724cb3bcd7840bacc247448653ee379ad67b19f4c704b5db403313e76eeeefae72040ec54fdd1fa4a7c607d1768e4df4d7a027211f15ff64a3a8760f1f52a4d972cef2958e1254dcad4654a985bf1efe13bd775bb6bddc0934d2797cb39d80ac72e43f9d801a5f43dd7af1eea2a7d0e847e1ffeeaba1ed2a7bdb93e5e34493040275b036c79601da3a602d121ea455d4aad90332c6b4c5a9818435b1dd306398724504eac787831eee8bf84948bf61b14f0f022aac0ee0ccab4641a9191cd6931abe1341c0358ee43bee59b563f0294895ec22f28f1070e8544683a50c7693b47221a3c0e26151a29adf7f1c02188a64c6649fd4962afddde54a87f3a8a60a0b72f2e16af20d64033cb6e4518315669fdfa09a468ad8598399583563f680ac0a006ec5712a19695b41b9f576e19539d90a662f60127d9afce46d12e96a95371b674de48e31a0816aa136917856af27881d837b489d8706b45c201bf74441d2bf3c1fea1a517e3aff34f07e1a1a209d21ee92c153a31d5e29226894510098518e72dad039e32f5825090709a3b1707adf4e5a3cce81ae773d9dcdcd8dc9a9eb4f722389d9731d67d33791cdc68524744ee5f0d56e9f1ad306ed782a961c77485a72860caf171b7330c453dc21a67e1115b3d478b43ab21c86aabc25c4276fd620722e536cc9284fa7de8c55648d1e84f692e77e9f16d1b41ee8d2c435482461fd72965b21656a8f4852324132cad5b674c19868ff68b010007fcce59fdc9a5cf762e3b1a6b93b0db73902b668b06e8e89fbb35dfe0f785589dee9546406ebc63e5c64573c4fc650d44088151454428c9f696d9e86faef386e19e3e89898e3823672e53b7a5d92c6cef9396c53b09e644057788735c8ceb5aff45282517c7477d772299b5d152cf3377b33308bce1496cf497344d7d15f633198a2323ff63add543ddff2bdb5ab3de389222812998b2c090bcc93fa338c4b78c2e98110b12033b266a707719a039ef1e7d7ec0913ea9e43fff2237a4efed01e8d526893f744cb30722405dc073e0263263f8165de15eb1aa72be9314ccced6fa08eba10feffe20016ef34ce2dc98a9200fd12cfb59e35b9a6e252631d1fa3db74a8cb061e8fce8e5d9c147ce2e353fda043bb09975518e2ea8f5e2a9707d33a5e1644abc19fa9da72a35b3614fd81168cffcefe8a47dc6dfcc823b30b582362649ea93c5265fd865a8ce69b47dd3bf7e8c044bde204e18b0ceb58826362555ce1248d7d5e6faf2152ae9bd72c12de3444cba425e58969602d689544e72e29dff129bd39f6e1bc9f72182dab9c68d9ee641ac24db6c714c09b618ad16e9a6dc39c8b3105671bea6d609f90e09615d6b4375f8d3f5e6d9b402acab365286500e950c60833828c8bbd2b873549ece07e4a1aa70aeb88abb24823badd7434b50ac94cee3dc63aa3c12657d99147b9d877fa7c7ee89f1cf8362ea6a657ec3ac1051c46025d5b0371a76a478c8536405322898dee51b35f60f2b26d845d7a17a95245f5f7f55b48dce6277233b6a4f6b300228db6b7718ea23c507659b5d08cdaa748dc3801ddff83a53e532da66256916205a6a4b6c4739302f1bd4b78de87a735cd99a06728df077131728a406bf71e2d77564d39cf34341046ba608e747ba3d173d23857e34b041c0c0357c660adf1b7cd49c1be4d50ecb6fa4b8272d79c58d94ee9474ad00e2af658401118f1454beeeaa0830db4863d38caf599ee51959f4248f66fd201404c45c57255163ef7d7486caaa4fcbf87c9e83944987fd7fe6a883017b4257c279a44ef72a9e3e68f070fd8a21d1382e68945294c4fd5bdef98e252ecfae922a8e316e003d049a651cea01ed1bdfccf3fa2214e0829ed98ad68a89d8ec6c8150ecec7ff72047cc3b01fa653eba3871e756b5ec471c2c2df9870164bb0d7f77fb148ad96723ad2794757d807fe5337731e19d7fb12873d2870f0f13de3caf9162b0a20726eb360753af94a458b7dc53ec2ac78d92c646df45977a84c7ebc7e44a8029b82658e9d1d6065ffa56f11a4f2b5be88ed185dffdb40a31404f0c175c6bb18aef7728bdb5b3a1eb29c5df72515f3ddf65af8168cb362935516ec9768ca9f91a0942c3fbfc9654bb18a85ce72245e8f7455683ac04b6cec7d8544378c16bdbd3521402d039ccb13bfb4cef411cd5e2c7616a85b0f267787dca64c35b45c181e107d40a6cb9d282f49ba3467089c0fa2934fd2ff7b85593da5524ca55939b46f5159720c1832f9abaa61535114b1dc1b0f25f9959472fda2dfcf08ea59acb15f60b872950fe8bcf4e023f2acd1ca54379a5154faeb3ab017a5b9b5fe094b0ab8b8fc7281d5611b2aa2b62cc2ae3b0e939ed52e184c5384a09a1aab249deabae79b40729123ea81ad2abb249782c0cf9dadde41a9a82f14a35eefba3dfe7e3267fd667268d2540babbf5e3fe9d73bb997ec4b549b020e8f264389a0e5e6a781b6a5b80125536304a475ab57038294fd443c2e16d3c1eeaf9f1951b34e2596d6e864387295365f2d4bc23fc4115c66fa81f3401208096edb6512c6d0ffd51f353a669e2cbf8b103c2cf2d92b23ec083e6c435c2a56bfe5f12f5f89c3c943404070a592723b98ee23700a74cfc9c3815959652491be7874a25eb1ff2dd6b241f2c7afb02956b6c5a64b1d919a1d23cd0eebeb5eb08c74a23db4e14689ea3e4c31c8ddc72a1bf92431fd8ed0e692644cc57512c9a833c721a8d6923c6e500bde74724413729a62ffeb8b872c8ef5c12f77aa6061f1beb08567ce249aa656ed7d93ec8bed0554e31f466ef3eb2b21642f7562350636c57105f653348238ae15dcb5430b4d722aee5a015fb2041c2a95643da9aed8f064d1ffffa2a4c86b2c48e760e23b6c72bd6f468c5b1bf3739f7d0a1c29d88f05a140e9b19f5c3626f2c4a6089755f44983833cf8069274298f1f3d3cb7b6e3b559b35df15ac1b82ee893a76d0768b70a6963a4600333746570f2dd009bb46f1d82d8c14b5070bf8a4653d0c6ad930d72ba558950da353a325e8e637f2c4e176b3428ee4edd60216e8c9703fabe288b183bfea746bd96beea430aeb1b88495c6ed37cbd7e93f0f7e2df99c467269fe933099a60d24b358099a77313cbc179a2b3797666b7ba7668bd17df7fc7b35441722f9246a5636087d3ec4ca451674fc5b256c616dc33ae31949fe1908923576e72514803f32e49064feccb1da5a4eeaa88b24d8dc6ed0bfef5751b1ea166e1901e63465c3be8401cd7816240311dcf8aaaad7b290d50c4d11359ccbc3ff64791603b31190a487e6c73cfa0361ad2474f8f082a5315938039f861b43986b0d4c672965881cf1d8b9d1baaab8a6b75f81555f9f8e6cc5d0e28cec5bd6e3c73b1161a9d379ebb5dc66b06b0e6d2929cd000c76c8496f6ad5c98712adc369ec8620e589e57aa850bb57823e203284f7443232a0cad8dce5fe36968ad910927f2233272875a263e355493a8b1664f6857846161e0ca85c07e8dc57e577a96c84e7e07056b562fb26449026f85403fa27ebd640b822a9cd113ed9e0ed62b941149f1fb72d9d111e774ac1f3e48f387da29acfe2550124ef98cb831dd2ff1fda2f04cc7728c110df651d4067cfa2569dcbb5f7db3be4667a0abd67d126f2699d7d34b4b727d309d9ef86d94132c4ac5ac13244a3237135c44741075a85fa27385f00f55728c930dace5907a1eb100cd04c3ac759ad839e4b91b143f921538ce2676bb0c72037a9f24bbee012e008140299a20f500871459e15b3e50a3370b1f1481dadd0f8791258963617d8bc210639cc88b36370f3dbb36e5297dd3c95611d02c68657201df0c62f1acf443170a76e9985b426489df1dd63a22bde6c40460591b14a472fc2e87926bbad7135a73f78ab9d78d38eb909b71cd610cebdd0ec90b7eae05721a0bffcbea27c805a902391cbf362c47264305386538507c0c1bf6e74b5b337234210113f760eac8757956dadb957a97c7535d741d4d39c7075cba60c5268404cc7822b570b4d985823af016751a0f7ab24e44d9ce61d0acfad6a5f62c04f82d020ca93d4a1ec4b9ac6f0b0aafa4face80970d72f74f8b46e2d0f7b976ce626c61965b58f2287ecad1b6a4fd1a36770b4f8e705d006cd4016ffdce053ab846725cdba30fdde7c636a5df07a6cb228614c85c20c4be9acd44df9b8fc81af36703bd509f8d155bf393dde51627e159bb3328b721cdd305a5a4f0cccf6f07694272a48f149aeb14a41b9bf988c1a2b14d4ff7e6bb41d13c410a2144d3cf942d8e7281123b9ffa7d3c8ba80d80d453bdf20ceb043c4d67dcf3239478935376aa6c724220d8499fe84ef51eca8dce214c64f9c5ddce296cf0fac33a52cddfe77d1c69b6ff00ad0d2d7462deb3260a8f3581bd68fc8fca4155b3b402a1fdea5e5c9e728da4c890e7b62c0b28f14195cb676585fe24c9db61405b9cfb31d6e25112b17209f54b44affe23237af1f315beb649f2c79830faab0514748558a4b1ed040a723a3e01d9143da7132238eb64462c9e3cb3c7a58261001addeb2144db4e1eb7687cca7d1d429839cebc5ff18490b45969c26252e07a6e0caec69d0219cc8bfc72ece7efc5d6fcd5dff12e4fef310baf6a5c46222c15433d07db1d963ec0e3b1451c226945ee59ec8876b1ffa9ad4ed9ec2c4fb39072ab46cf16482edac80b4a72cf4443031fd75723883bab40fd334ef46b79f75387c8f429746e613171350772d170ffbe250682475c6799fc8bdd7cc699f808ca1e4c810484ceeed622ad03476af89be35024f2c4ba9480cc70e6da6e1e6123a2a730066443583fc59f41697270a674015452b28d6e267718ed63fa77d25f0dec56976d2a8dcf20c543ff591bb8bef04445bbfd6e6e8df580f2b0c4a1bafe48d9a08e0e0ab10259117e930b6a5f001b70984e7fa2ab985ab6d3f38301e34e7fb621326978d0d58d514c5fde72c9559d0beb48c10b457725671324a0339c61605ed8a7305e3dec1a8c2cd70872712860aa6082c359c298b2239a95ffd4fd12c32c2887ce83c36e928c0fc09372c101a8a828d397aa79b90a92c7aa29a7705f52a2eddc2bab31c779dcf6dedb72c965514d041f26d08747c82c2aa662fdf11266ba34647b1b307a5a03b608054f35a15dd90e6982060c2c6ef5b48f5ecbc4beac603bf4faeb6008b427df5b4d72512dd7704ffb622aaa998a5fd2a253a28f51969604d1c0c30258b310c8dd5172d623941ac93b1fb49acd96fbe300ae625f2d3a6422dda41f339b0415606cad729e33b289d0049427dd4af490c7fb77b4dd0f92bc6494a7d9dfdf90af31300f2f92fd9d2e28ecc5272eb497282741dc6e220c111c0af7b9b02418f3468a594572b7eb3c743bf711599947b84074c70fa686130eb3b213449415297f1ee23fe84a5bf1cdc3691fda1ff890fd58236cce4f8d0987d1df346a9573db2381fe2ec201534a3d667803e325807639e4ab0e00c5ff566b6db7b63d2dbadd8ab901185f056c1995e7edb236ce6049e553d0ef2bac7b3ded332d4accb0da55c464cc7a415966a93b6f69be2056b64fb68f1d0681f909bad66b7a3bf13003b4e3bca470245fe077fcfee1169a8c0c20423a5c35916c52073f797b9b14c0e99ac77e2beb0a728d45f8b8c63798b3ec39dbc1a5f604f47ffc0c950ccd99b56e196588ffe19957e124d5e06b305ea7236f132051d00c32f874ba5ef261c53af4e82d065eacaa1e7b7ca115ef66041b0a0e54d3039dd7db6bf56c439e48d12f11dcb03649b3991c21bf3d05777b791e811cc7889c468cf0d39edd108775a9e6d7b8926e5e16d00ac844355e19e768ca00b278db034e9c1bced96722c04f32df6c814e8c9dad73727a0d97e5578d4a0c8688d5ff47913b2d7c548cee6c8decc89875f4ea0147017256bcd5787ed5895e0e6aadb53a6b12d2374add14b4432acd08459567569d8072c6a8a5b583e5ef4793e8349df71104846b32d412102c5576e3190626faec857234a6c3f6940888d8f2d39b4d8232943c5390af6ba8e3f29ca7dd6cfecde925722fe10257682bfbe6068f577788c9905e2881fd21d9073f3cf910e35589727a7293eba693982c9b3656b905d85e57aeb2f6296951f66d8d2e1bd7b95dd85d384f0708c31bd7c8798bdaf35c1e4014c2d9fa478e83507fa7e2dac2fad9d838407200d9a52d363f3d3afc4a66bb846b1d876074f693f35bf5063a9065f8e705fc7255840e7dbee3dcf07d7eb94945104de55bc68fe37db407ed7db1dfa64c3ade5b068bfd5ddd36d81da3552f8c452d8d93f327801ebdac5852f2f8da42901ce128924db5d9ac6ca43899f87287e95493576299696a16faec4b949ccb9556c42b2da6cf20be988a4cebc7076f38a75e523ef4051d888bf6a98c93579057ecc5c972ec27ec1408a669e92ac54d1021339f5c59ba154c29b341d7b6d5192cfb45a9721862249c37caf4ea86dd1adf09641701c3f201d7fbd11b642298ec25e4902447565193a4d08f4721aeafb4e011c9b57585c0ce259f1e4df867520e3e1a5133723e7917d931d8e63e7974528db00a290a3fd618c5bd981d0c7c317bbb39dd9f7243002ec8d6a0d1b62a855a45e088e462eb8d89206a1016859fbc91c8b2799c72720dd5d73f5af729c8fe4641ba127d88badf682332737b3cc73db7b04d38c3724e6389928090498c5b269e963c13c813c2c51be771a639b2fb4a284b56b9db72dab44f6ed7f29feb1a99aebd3dd7e06ddefcb835d3e4e743d2e0ae330ff80b7296562e8cdba8a33cc83d9b699ffda090310ee913104bb5c812e712984dd8320dc479a3ba54d22f945cd29bd968570fc13dcef2d1348bfb780afdf806180ab072dd87c2bf01435c4af874860ba3da150ad4be9d386affd7cd6fd707100a552972de2f7e399096f78f004a7ba50fdbb3f580f1c9f66e0e87413b1c997f896a637229492f62867e72910c4966f35204c82ba71323fa8860b312fd66add79ce2d6353721e4e42354d2dc533cdf007fdb0f45368323d2e5856e799d55a115915057721b34e597ffcc70edef68967a1feb4498df0ff03055bbbb16a056a2d7786ef9722c37c7a3390816458c2b6f08b54999cd4dd9a8c531c01eaf8e66802f60448972e563348b596e9519c6da4abb496e3406beb9c2848ebf96e6ca4ce77033a5c97270b7a9d04a8f6cd10bcb9ac93a882cb2a6dbaf34adabcffde5fc39d9fc454872faeb0f67cdcbde781f2937a83b4875e3741e6121b706ca05de99b4fdd1a3ed72f21457a6aa812952fa302afebd992e71113084c0c54eec8968c3c0831c9a665ab9fa0cbd695ac297a53660259fe809ca1302d7cc072595e171dd4df4a4d9bc00195cd321fd9c28c35780e424cddf443b0dd02bc6d18d3d30cfdae42e46eaa1725bb6d6416f90f812dfdf0fca029a39603dc9f98bf2e5e3f51295aca1e7af1f72d5c93637ad317d23e78b714670283f2275716eeed4fd108c6e411a69b8f896725104a950000f43576436b50d69d2480ac3b1ffb36cb963c853494c83f0d55742bd49c1d34c4fd8d8cda15c925ccf51e009d1318c39333939e810c101c318293adc6e83b32313bcf60d6a9931175c803d0b1b86e3d039812091f7a8d47a6ea972e90b7237030b345f4eb2fd945d5642061dea17787ff59e302d55619c3d7f13373a9eed565589b1845cafe41f5179efa048cc22eed1599c50bb2f58e7d2d3d57244a8eebb3275f4b76fb73c8a59bcb06f13184b1d41d7e08c3b58c1d483a04c6346544b6f87b38dc966a06738f950f1c5a80ab2be4696d918fed7cf7c64c00a628d6d28235dea9101615a00b070f65242d1b2708cbac38fc9cbfdefa0b99dcd6e9990f91a33edbfdd95a0f11dca5927a2157da3820e153991c1940af01088e6726e9e34fc426a6792f8659b2905722ca987662da86b7a18076c1798b8ca480d444c1d03cf60e4c96ace0eeed297717d86054fdcaa66c51447fa113631855f343681ace9f8124aa9f2bcbb89a747e127b18a4ecfdb267691ca659c86530156e606b134af8ce4312a63fa25320ecff7fd432a81af49f7876938d3fbe0aef38fec724e0aa6776071be9920a635efcd54e99644bf4452f1879ceb431b804f7948c47242aca1fd05a5c422d831e9b463b5947289e6ef67cd12eb889359148c905ea67243237451be0e425f5f54418c9705d38e7ba1466e26de720aa4066e022d68626afa7994df11cc3eb7067fb1dbe8ef70bd2f33e21e01b25d9f591101ef400dc57293758ad696a75c9ac9fa43c92e3d9f51f94baf0d6b9bc4d79b3bacbb37741d72ebba87a54db10b68c000630fc00d9d52d16e5f66f4d05e8b25ea10b8f8fe3a52ab4d90d57b005878a40a5ffc934c8f4e68452244388f186d59cb5a5de27eca72af93aafbb6822ba238235c3ea6def7c1f042af0697bdfa48ffc626636a14532c9b2e35840a0a2e532884ede98af6fa839ea419cc16081d118c2ec5702585b80d75e3ac64f69c3bbd23a1de3fa774cbf33678679559cfe9a0a5cd27a2cb342472352e8ed412b8303fde6e5a17643f1e2ace3de55d6bf6938417c9553f5f771d6718fd985f891eb78549cc06d30f21bb68041cc629fd86513024b5d7e463df3772314fe775ee725fc040dc89a4c73873c3c6880ac21f0fe7486340401d0ea3c05b9b4f7a36754adc11cc8d727ebe652dc56c036f5ef2436b76f118723842c0e87208151dff762ac5b046f4682ec8a6ccba7a66533d9c80534cdf453de6ac20b96f5a23597dba6094fc35d77ac86981fa013fe12548fac62819dd26d258bafe59001313fe2519953438bd210a52a121af042d9dc651271a8bb7a15e8b459e88dd1dc8e3dd8d33fae55e0ff0980819842ab508b205b6ed3e2579eb7c42fe75c14f727128fb7061698246cb027f51f01879317e45e9174bd9daca46ebc5080273d708b5fd7fb1b905537f6369b5aec2a13d66b9b459f8dbab322661bfce4443d73c2f779e1dcc9ab958d7b5ef29ee3ca1b9c3006019d2a8b74cf5190dbbf04855b672cd1867d5b4f49f75ead306edf226d349a7c5a9deea9e93409b1d1ecd29b11d729f970a2e8a8a86ac6f7a9b3dea0a7aadd5675a0da7b6659fecf154b0af63e772b988532ed6975d38125e1d5e8f81887242af4e7b0e83d436c11cefcf3c6cee6d422ece37b552b5bab7902d4b40c3daf60a0a84f73c6f15f69850e6a5524ffc72abdb6e7f0fcc9f5d6fee81e3c6166c93f24e1192d5282832ba65b4ee225a587260cd9ac1e1197cd382fe3ce72e9a6d99e24460fc63057e8c25de35fa7aac6172ce5fe2fcf09b60b746982bc16c5e3316aba177018306336343b25aed93ab1f728ee2a3f76a26b451a3919504a429a484531af9248c4ba0bc285ebb8069518b72c2025eb67933519c56c513145729ac5f85e90e65cd689cdf521c9cf7268f0c04cf7791eeb36a11a568ee2d0580967be127655ac67a5e80c116efeed81f4c991c4ee424e453653947b6c4a25523ac7653790550e1dc6e509556a02e40748d19720f2ba085691c2c2fa90331c6a0d05afd6e12c24b08bf7b22ffccc2ea2c71aa6847334a8b721b33afb0121f485d0a9bc6f56005833b93834cebc8d55db624b16c7434d2cd40efb8e4c10cdd0637b35b83b71136e86c2cfa2f5cf5db57d68dc772fc2cb71b303b24f8ab7531e34082e0049f348a318f1341257eccb17dd6b47f631b6cb7632960bcaa2aa542017f6e27199e560f5341e1e2cf45da9bf200875e729b5fd4d1fd6d96eb4e8a37809a6791484b9414981d3d14acb06061f21fb1060fea20c4b32a03518e22eadbacf4b21c9ccd7744d9d442eb4a1982920ff9884a72d6cea72cfab079f84603451008e67bef51ebdbd1e2e40569667184c0b80ab172be5690ec7f575caff89b91533b72ffe527d4ca44609958dd3551c5bfe5da9d729ed0e3f1c6af0ff56d9d31fd87cfaaad08fcbf505b695b6a8505d4cc01724272cdb5e252a709e804142f5ca454dbb46c5acf6ff629cbe5580958b8738bc43000fa00d38cadcddb32c20059088bf2da3d67cee9cb95e586c99e9da51352494200791760f56c6bdbd99c2fe8ddc68ce9cb39232abdafcc129dd4eb040638b4250ba409aaa275297ba3fdf3d15238729360010dff591046017cce882f5b96dcba72f677c0d1cb75d4a2176bd3678db736794ec861db252d3901148ad48f754d6f43967c9bd6f4f737b1bf1154121542b09beab11850de45c642d3a32b3c4f835e72ca3d236ed6284a50254cd78db5a8ec7b7409b849e4285d05816a4aa0b99d4672282dc61d73e3cb2cd502eec59b489bdbb63f933a12ad2897a8b44d6ff5cf6b72fe6d3f76eadd0b5709526f2ea2393206764bed5bb961444de6e70ca3bef4cf4070993b857842b35138d56c47a4b5af1566c780c2f57cdb070fc286a5c6b6ef6b4dacfd47e82d664ff4a5c34245c508957927c2f379e8b49ec4c272f303dd975ca949b808c4828eb177a2db9570d1fb0f1702637bba5c02eaa31f1c3b6abbe57227c3fdc22072a41754f58dc6b47ecff43c2f0f9fee67b4a4d87e8dacc8d83272341e325ff35181111db706f6ea80c736732953925e1eaa048015dc5546405c72ee45041bb81f9fe5a97784bb9cf26458662add71ed5af096ece39361a8b4fc72b79d1a0da604f07e743f6a629599e7f2e2e4680a2b296516536c7062e935be726cca6e0e7d397f539fb1d1e73461848bccaf22a36fca2ac370fc5b566c459e70699124107beb0b1ce3e04233d89eed082c1d3bd1aa0c46f1df15e0db35e36f1aa59a66283a8b52486f428d9a8ff0e17291904025ac0e32fa627d2c56a2a7f27235c45cfdc561be5bb56a7cb6c9dbbf6b487559bd139e699c40943f22123b3b728a2208ff01589380f3a0731a1e24c7aaceb4e23d2b0c17fe16af7cbc83249372731f7d899da9d1a6c23ae52f52135861ea1cb2d5a0d6b8407c45925df8f54445c4147f4406d504b6c0e5fe168279d158f9e6673e640874109dac37067419ed5997dc591fe458e62cec353f2a8aafb36ff1d80775a525fd005831ddccbf040872aacd91cf6ba9ab7d51e1dd900ccdbad488c268c23a79a12e53c5aea29ea9ce328c115c34339450ae6c835a8d89d072138f581ceea80147950a81b6527a9c2a7268e2a64f161eb111716b8c24f2fa7bec0550b1881ea98948bfef5ed569d74037d4499423ed13ab6d94ea13e5003ae1f203ab4382504e9f6237584c6570a1fe722fbeb6fbd990962910712e30f8ee6ed2dec9c63d84f69fd3fadbe17b73895f72816d854dab4e12de8aace4f4b862746c9efb0712ca61b8ee855b76f0be7a23458c52d4b3673ba4f329e1f7074b566a51e9fc0649b0090fe46e1ca6f987ff27725d48a7f95821fe6bceabab5608fda2a355dd00749ee7eedb7539474eeef1ce7229ddb41332cdf109746da28d63de152adc3788ffdb142fc45edf233eaae65a6dff4feacced689eeee7c2184f00fc8f70fe51339cf6908fa094a832194ae4fe7287450aaa7eca328b3d7be0ef57720433716025dba2408b03a0bb48ffcc13fe72520f654bae7ae8a470ce2f3148f68607e541e162e36f429c39e803689f5f43723ef7c52f45cb281583389836ae01a394cf73f443f4a8ebd0ec0dc39a94ab0b670c1001fa97844f4ba47d2118d28445e45a3e4f210c9c010df537c89b50bdc372df67a3bc8f103bbaed31db8f4ab1e4de0c275bacfcf6a7667ee26cfc2542d772e1590bf657ada1efced7f5d093b354dd2d51062f2a45bb5d9bfefd9fc5419b55b50cb66ffb8a83194f47c55233f8bc3e0e86a4ce6a46b6172c67c95e887fe04ee8b36c915dd5577ada9348daca836c032f242c7c86bf76703da1318254c2a4698178671c06165cc526679f668d1f935309d570069234dede8a05263972b75072a6c9eaf142c858ba4670cfd3f2414b487cc53c32fbd8ddb69a7c6c84b3b86e72b955514aee155b037d692f907f803632b5f80cf73eea721c921d6b72e2584c72cb50905b729aaa16bb352dfdd37d71bf3d1537a51dc6b7547beb9125ffed69725ecb6b1fce5ecb561f68a78779ebd53117ac68a243237207d8c62122f2a30d72dcce84e7dc3aad82cc21d848dfcb5c32ceafd4964c6643abe2e7704b922b6b7245876c8e3fbbc44e415a996ea3545bf414e16595e43bebf4a13d248eea74fa7276c9ad953c344a4ebb6f42173f6b5696da446a629c31353609f9c3c2a95450344480c6bc34741777996cafa2d210a891c4a18e22f2a1a827c8c1a169efca6572719fe9a880d455ceba9dcf1bc4767946cd2e57727cdc2c033a01b537f7f33f7248ec83916f0a9c53b22ea253bb383b2c449837cd14cff2eaaca01104ca3534119bd8d662e8a2e77039ef6662f5cc54b2efb2922ee83b7f9f755571c3417cdd0b40b8737b2f2eec938f7fdcba2612eef0147396ec9ffdf221206f3e76933636722c28471f7d1f98edd17328338d023715729880d6e90d0973b3ec3105b6b4db4df7c5e8569cab028863d465f6eadeff7f608e9e587d43788201542c335df3dd724fbc810676d213c53979ba8b1d3b44304ebbf567fdf599a691159435c6589d726c709b98c5c89ecc9a5a48abf142c14dbe43f8a022230f412314dada72bef67251f4125e44df962d823a3832244adbfac973413ee4ab07e521a276c1045aae3954b3c5d2b5a469eed8b382410a698348f02cb0ac285bebba8319f3d36c02b90397361f18d2226e3cd88fc92417c8e58059ff41e1fe4d8598618cebaf0bb7952751990c421ce8baa10e6e9478d3225f4fc7d9b2db0a2dbe41599cfbf5bb6d2172f1ed4377e764344133bc6bd6c94ba932db6b131b88b5db41bc71ab236c15c70c9c12bf6ccf6e10a9cd37895041141ff93ccb3fe21a4bd2beaab6d0caee6ad212d11153395d76a520c0ba13d9210ecd185a19c111134c11d6b11b2d3c3213e072380fb24dbb6e8d963a69b875e44b136b4f5d98f6b028891386b2cc43c9d9fa72d382cff66fe2b636daae449250ba099ed47e3334882370baa8cda6a9ccba9d14f0f4c18f28539bc072c46c70387324452707c6f96c81105e402fc51a7a2a692047d107bc3e6c2a12c2a525ad220e71277904fc786a88f638142760451c94aa48876e792a9115de9a59720ee678b180dacc8a82d0bcfa42c4a9e3f19abc8f694053ada19447129c6c4c9c49ebbce7165f7bc0bf7a0ffef2c9088329d0cd7f62728ce1fba2e6c3729574bf8302f69c6a0bf6f1625cb0d2627176a8a6a575f2b372e033add1241b7c1a47efe3390ab902d921b0f66867cab45df4e7744092fcf57274f32ba58b497c89fd78f6106336e2f69d1923ff51695467ae4d5436a638c765c23723e7cd8e191e78d35da616424e60a9500ae98832032e7f54966633ae4d7279ef84e43321b507be4fc2f00e602745158901a2176fef24eb46a20adf753810387a7ee451adaba5817a34182342e9183fb2bd2d9af5231ae91c87feaa7f2714591ea1bdff9786531387fd45ae5e3cccf4c0462ecba543a8823234fbb17e792523f356b862b009699a433f3e3e10088747ab56deb86830ff7a4d59cbff7eac5e492030d13605cc7ea30d6ab106760db5f9cf0afb2e076550975b479fe293cb0b9f866796984947e00ebada69d2dac928a359c6eab35ef9eb8b5be4839eb1797269ed05bea4c8f6a16b8e8f034bfea373d99b0e69cddf1995d6f5643b766ab372fa6f0e61e01405e35e099d298e264f553f84fc79b21ac147513077f4e9b48772e2b4723aabfd4b0c3dd846845e161cbf43a734d53e8c10835a383cdd9a02405dbd6cbec47d15eed1384b2195b6d01f07f3e0ad5b79aec3d94bd272af9ae310721ddff69a82f18c2ec68fb26e2cb8fe645eed6e1b2b32228c8818e08cd8e80a727c35e55662bd2a490edbbcced015a6096355d3826f151b5067e38394cfd2b772df7f84d08e7184207cf047662dc7b51d1c93b25360a9c517248031307466b11a2145a3cd20b691da12a7ab62ef6316c7e7b439db3e044604d08b03456fea6a722c13c57943d44cfb39ef78dba460d4857f733898c8e2872a202bceda393803727336ea4f3a45da937dbbca45819d2243da19f6920e04d9bb609bded5568d730a10559afcee74f2e2ced1d9b9b713b1f258a25639b618ef81bb6e1c6767dd6172d88320d1b8c5890008de7ccf5fcaa14f6aaf2b421939ba33cf6340e659275b725d3de1379a9010a310205465e1ac18ac4755f1cf20e8d8ecf268ee5f55f57f541f8f547be0e3b681db0c7bb673aaef68673c10e22558a956ae733ed289b042727f138c7b8e9e0365c398de8bd9055186c21f337816fbfd420fd9c161ff35e7258021c997e49efb85913e0411bd218daaaf00c1898617e2a1b198ef6b307e1f72597c6fd475d18841434e25b9b3b8321149b8fe138ea05aeef3a423daf0158f0d064dbbb507f0e3bab61ba069cff7c6b8075b3f70ec05030ac01f91dc7facec1c6fc889af29e9db5d9c0721bf92ff3ce31e336a7feb309a52e4bdcdd5e79b3632ee8b6ec3a2ac039ee62b0f9c58f20a39157cf0d96715a22dbc2cb9d11593ee561a97b40951055a0f43a271b7b6df001316c78b038668c9f89fb188c95b155472bcb7e366f8a1ad2a92a7288043603a4c478dcb5c010be4822ab4ff5001d88872d585c19290f48507c9a19cfda66b6b1144071b92ab91bbfcc5d6a58508373172b25a3741589790e0d6b1e819701e4dc036b0d0b7dc2401af60ac1c98e9e12c4aa7294ca5e57f4030f5839fd27447af656d38043cc7f6e15aa640d66c3506f772bf2d52d6de1583907a09792b30a0e50b965b0f4f41bce42cf45bd7ea3287301e409dec14b020ae06f3499604bbc4c5c3b04162079dc34b37f516c4ddfb24427222aafc33b01954da45f841500d6eba52c88c52e102e6015fa99a66976dea8b1ecb78b62d3b6c778c700333160cc9fdb0ca2b513e2ae697100ec5eaa67a6cd4724315aa0c00c08d8277fd8a77cb7629827dd926a88ec983211eff52a31b7aa4722b42ff60fcf43cbb21b4430631d998c686c2fc248af90199bc77b8e06cb9115a3bdc0d4b869a25bcea47ea319071c47f5a778c7247a8d6777bed34924b4fae723058e10b38248a8fb7935cc946f918e5660f65a9f5983d657673a4e9856b2972fd7f7c42bb12330cd642433bfd966d4937a9397544e1fcf34ac923110ed577724c3b51acee6718682b2dbf529be01a9e2c141271dd8ba38fcc9c54cfe2716472f4f094816ff725bb68afb8ba5b498594f230e80fdd8e220dee280b36699c3858470ca74ce1619058c1458aa116419ebe66ec61a7064c423d5e0756f2a4127672e62e9fc067c625dcc58e75e797bc1893b28e0f39cc8f105234315924e83de13a8e1f6a9b35b9a2a149ac7d63505f4b4700c35b7c7e41fe86f6f202807ca21429a378cb387acc34da5be8a7c3ab474a0e19efd57a85599c2d7716b2f75059b172b5dc7a6f7e94754e283086fc993636a1a2f806a7dcda6fae8860a9a5d66a9f5fb36f2fb17be5bc7a244bff330c1936c029c80fc81aa90dc3e340a37aaec3b972821e51684f6bab47b6f7656658d2a3af820d8ecf773840b81c1189f0ef5b5d40b6369c1d1a78eeb6e40cc73884801bfb152305ac59016a3059f3b44dc070d47227eaad05177e4fa9fe1c27081823fea5ade596928578bc5faa8fe0edd06ab165543e0f995c3094431f16b3ba6790c37b92786429e64e238324fd97aca83bc0729765ad1ecdceb179a2a17151a3acc2b73a24d8fcd699de9f33a41b130037463a7937258afe14372129f5cfc9c4ab47107f239a412ef50b0502761a7c403dbc3bc4f9b8b1bb68983ed0397812b505d34561cb937f897b6bf22cabf9bdb7a1175b71bed27424f55f1275924563e153162b9ffd3cc014d8c933c205b4f721f8043f573db9e369827a5fd8587897fcbac0113cb40b2de549bee38eea31522be203723b5daf65f41584a8a7a267b20adf55ba7f84339d9152e0f87634c864f89cca2bbfe7c53fb28f54a4fd6b997965f6226109ef3f404f52c59ca21604de70b5074422b697c5b51d62da2c5a3223dd26dd01a21939e04d841412d3ed688c5e660e05b79a5d5a8527c2b46c617d8d9b105e745a09220693c85329667b83ff8c03d472ee8c0f5d1fa8e4d6e40cb3a51233c2eb442061ae1143b34522cd3c804f9f6e72517e466ba0b4ec2775b87c575d2231afffae314a2ccf654aa5ae1093284a0347cb34777176ba27cde9ad2b1e61f096b161e8fb6b87d5d484cb8b414cae62ac729cf3221d8f73968fd3e4397ed30f94afda09158c69e3777a0450565f4a81b1729c27818a74eaae2e2634e618cab65ec2565edb41860b6ab1b9d759a45c68e7729d110b8484560c2191e527600d63b669085d0c5b1297b4cc9f151cae53545e720507883fb3eb6e56e7ba26552ab76fa5d8dc7aad48d0340a9eef4fc99b1df00b4748230cea0066fa0863c978bfd4e3de2296aef17ad454a5d22406dd7044821ad273524d112600073f6befe2a989d3e004b2c578ca3496aed39ccf01503ab53ac78cb279e5be4aec8ca1c5c5963b06bee43032a96338ed369990e9bf8a3b82724630512c8ce8d07431521a1d5d48d572b0fc1c1089ce7b56ec55d103a4f9ce72ffbcb7754a0fd073894e2e0450b2019521257c5627561fb27b81c2bf62d50e7237e436301786a083b59949bdc489b1a72985bba8058298303df588412f524549dbf1dd5c39053620babf8a2fba32ab7a7656784fb40dd1085907870c199c591f29270ff825dd60c12d119eda53cd373863fe156624007ceba37a9a734edb46723d07eef253db84bdf6752700b2120477c28f9573a119ec09b5f8cd5d98ef976ef966b2779d8ff509bedac03df720e92843bce64500bd865d6f2346127e3bcc6819ecaf7499e3e0438222855ec5c9dcf9c8711f3c4b605edfde324c3ea3a80806367bee413b426962fc8cb1cc15a9cee2b84e5f0faccd29a722a03a1b57deba72a9444a9619b83e0abd552033bd454e991e762e4fcefc42529b5159cae88e3072759e3db2cc417a221fc41fffe7b24a1517cdabfebc82d37d1277a28b6929b254422352ba97cd6464c4d06f3772e5659068e51b00827ceec1331ffbc2f641d908af9933529df891f376ca3358c523c1ee8824f4dc4b8cf3d79574e1b4ba910972ca29b5f8ba01fe69fee600fc14f1b6032ca31c4426654819a0f7259e21dc22721f01789c0f51ea602f6ea8e3adf45c0d9accda712c3d38e2464d1e697355bf3267e476f57101ac57cd92260f0e0f141391609b80d12d20d34300986d98d172576677f8a89358c668bcb97c881572fee6bbdf5cc64561e77825c5dd3e1aa9a972d93439b2f35227657050b9f067a978364faaa45abb6a9e87bd46c1d5c079b25614937b560acaa268344abdf9c3b73195fc2f7b28ccac48cd5e3933465c029602a186d3988f45de9c5b6532bbbe3eade68032dd4b7a17a2e9d7644012e79fdc72d31afda388c22ec7609d320b2e8ccfbf0a84bff031dec763eb4ab6ce279f197223f0630488dff93bb348625736f0c44e3739c4a5024de224ccc0c0008e85f272904a10cb673fc113989f29e989c1048adfcca63b2c058fb52ab92f85102c9e346d4626d33b4508adf603f7de7aeabcee9f39e87340a705b62eded58ab84a4072d93d0388f037e2896fef320f7124795c254165726de2d125d5d3ff0bfd0f3d72dd40a006a11d2c099b1cd78a9682dfd32e66e7c8614a7d6896fec3314a850472173bd4c11b10507a4540a86bb065c36772e92c3e47dfe47a9b2a9fca91589a4a608d5c2b92eabf4f0f835d5553b10bd7c942805788ca51eef8c2fac08af6c8725e4966acca16f4e7ad6ba7341bffb1d949ae6421c5cafdb86ab2edb508521872c1d2a19c99102e7178790d4587916003bbaf5245cb80592fe8e39133182a4172b7201092ea5530777f8caa1583ac13f7868a6f7167a9a5ecd5287939bbaef072a9c84827cd1d4f9472d38e4fa54341029bfa94e661514364a6b97a99037a4f1d0bc0022ee6235196ec65919b3a81ad3dbbd8fb8fe5814af2907623c8919052494a7738454b30d9e46da33ac79212f75c253e2f30cedad52ebe5dd7978ecc2a469bd04ed071faa02df924532b3a4c0f4a95928a0944ef4d256bc17d4664d87b4a297fc1dc98be359e5201661314ff73d45eb25c065dc2c8c4b889bb02bc25c1722950ce200e4e0510420dfcb4909638b106ed9c0b36dddf2113059fae9f13fb721a4c7d1acacd7437960503398463f48da8fa12447f0048e0be27f3d876fc9672daf300a1e049d18a93225bc526c4851248fa08453ac45e0d2c60d157e2aad8728c51cf7cebff01efdcbbff2fa3ea3a4a58959f15a044261a43a0dea7f122a4568440838300622ee62dc7fb2c185db4bce4c240a46d0f52a34bc3cb384041c264e0657f45c9ea643f556fb3472f67e86cc6a587750155bfe4d98c9f97edd4f172547f6c6091b773ed7e5504850d11384711d63492a88865b2471ae1ef52acb872ef6561688fba370185bbd130dca931db11b74ff1ca7a97dc2548ad74f127ef721947cc478568c76ce38e976e744b6f9458465ec42d3a2ec5b7e8da6b1ba717720e45953f33b6a33676bf1ae3d759c1a008639f21fa7ec54a0e7af46fbf054664b2760a5b47474d139a67c6ce6edc2db05e91927a299971f93239259b0f77b8724d3c926627ebe147b97ce64200c8f1d041f541606260026c7b3e908071ee527257e0be55f7deb79eaa3a2fedbfbdf8adf91cb3685941f9c336e547f0b04db272e14b62ba2e004e184d43f66737670467ca83042d80afcb6027213464224cba62af6d3c99148c9a0223745aaf9535c70c8ec6b7c497c80c698f81d56ba571e65d81a780324f24a5de0cd7dddef1da262441219910dc90fa108dae986b4ba05655ee3cba7bd4b8d1d173e6ef5d6a2f5c43e09841d32a11d7d63272270f48fac572d57242ae13d7b5fa7d3a46718c0e8a3e46ca9ce5b65c002dd5b51c4b17921c57ee0485c4c99ad7ec59125d420daeec7511fe1f27747b2405e68b16036f56fb72bfaf1cc4352c62443c5c4add68eacaa3ded94b6551212c1d3a1d76f77da6eb72b6932601a748d77adbf9cf72d6c1a222a7af40767fa837cb66fdb7d60eef7172b3ae2509454c5aacd78c2f3d6d278d9ee86d90bdbe607879ddf864a54d7b5372200dd35e0a145408e6aab5a7fe8d7254315c382d1fe51313a0803e503528193d6d957b098c8720cfd9e6098095d15d5b994174fb54257836720bfda1c61a3f72c1a455dae5fc429cbc1001ad02c25a8fe4f01662f706c5651d9ed55a1918e972c230a13fbc6dfec69a593df638f8adf90008fa3500ded4c4ab0f24cfadc86f7207c14e0680e6f46ac3bbe36c516cc3c97f9f04d333b48d958c8dba06413bed4214e1772a05edbf3a9bc371bc704ebd9d0f92fbc63d5ee45e1f59ee98a123bf726aed58cc86ba8d9773ba33d62389cf93d161e3812a690065b569168781750372795cebb3525e8175f52f6ec883716d392def85dd1c2d8b4772f7565dba87bf72368746bf6a67eec7fb67f3735c0815236618508561fcd625f749eaa95a814d13c46851b7928ecaaf3f55423e11e4bff0b00d44fe8302783101080d1e142e80721a13aca42ec32ff26de2b88a9f18fe6212bd4443fe84e4ea904557779661bc009e8a354cf8e6e1724f04d8b243f9af632df7b8e95539888a0bf490f2a7fb7772ef5bca74abb6c90b3bd7692b3dea8c23b1a51535d3fca2c0ecb2de8811de1a724056a851675414fa7a481a717ec461226dc62a9b5ff79891c7fc608427f975727b0cd5ea7772b6336ee1ba0a34407ae081009fc4dfe800fed1ca90f34fbec8726b0f683525cbbe280758211f9ed9e79caf2f8c602beffa1744e02cfebb3ab472bd377a134868e9c4bb2e01d6eaa4be3e276a46ae756fb086246f37516d839772a03296f3cedb6575b4418210eafa133d23a1ee3ebbfa585a2b5a6b96f1b9662757f70af6a145f5751ad0accb52e9efb5f61b24068ceafae2d8bd73a5236b730eb06b213afb7f2b0b04fcc3c5b8fa28d8f6802de6b7c5645cb9e9b446be4ae672f1af3de96319ae0d59e8d74d6a604cc91a75c4a9e8ab641c84e626379d5b9b723a029c62b4b277fa07d65ab31562a40b85c50be057bc5a7fd5a59fece96c97727273f0cbd1aa3551618a0ac1b1c8bda39162870382b2b4779296609fb985560d248ef404d16744e8b8d08bf9c318879e4b91b0082326930aca4ee40e6fe16b2cfa18299c0ca6ad892e80cb16efcc865b9fa6ca0b53a3d7643a1e5f810638491d0f76ae2581e7fd10a35babd0c5d90a7d436fed132eb315a38ac76342af2480722d2e93ea08e1db11c3ac4213e992022138ce087734972165f003b4758736a04ba01231aa4253e1f790e88801e7e61d6d4bdf4fefe2fcb747d7002f831cf18e1585ee28949636e895026d677db9318a2df24c64ad902512f59a1a83218b7f87720ce6c0193b3b542623354a6350724322ee813e554ac93a1fbfccf6030017920b917f6763377769f2b754b27829d9d4fc20b8680d864118ec8efcf668dd232572764e812932a2c45b2a6595d5fca7b8e589af51616f0256c1babc445faf26d9464c4a7be35cf3e049cfffea46e5304542d7695f03472cf0b717e6933a305db17280e066048c9e138771c2666bb5160798c1cbdde61dee31079bc416ec1cba086a98788eaba043c73d05151893e3cf005e107123120198c59323a72af14166f119ccacab29adbbdbaa83d7e15b4f05a850f37b7102f31a090095f092bd90198872994b86cd7bbf0edd2e715321fa420cf4313a985a0789f2405f15ecdee5ee4b723c67491a816badeddb0c66dd68322c9c6d9fbcc27d1f8c6742152388f2e80d72f3ad95f460f0632dc0736e20d7a0f55fd90929a4d1022707bd5689ba1a1540722789cd459382006a388a506eab2624c19c691918a095785074487bf5640da872c68e9f23b5f549f2f1d088aea8c8742a6c845e301bb2aaa69ccf36bd5e73ed1527409d7b1ecf663b9d6682e2f23a32d9049f50fac10f738d16df6421988ac572025ff63d7db133f1bf99bd9673736aa4887eb4b03b7ee2a8ca47ee6555ad7b214b941dd3e08f6a14b0181578fa2670f70126e1ce9579aa263285edd4ae12a9725a2f45e8ef2a992b2e77c5209b19674612e591bb836b649724a6148cc1a8bc6a0a5df025d9c94552bcb4af49c248c6d95de360f6cd90fc983cd5a185edc0db7274b9b676d1d00f967952e2395ebe652d8ab43a095651669a917373756accb56dee9178cd90db031898c2685271d85da29eb33088e2a6d7edd0352ed49ff41b7246828600f7482de10b756d09668cf6031b3b189f0608da66588492e178b4f37235cfb138e05dc6fd1db96dca7609e17053d142540afb29669e10c35ba9818f72881e84b4e8e04c45055334b321d105fd1a46bdc8f3c2626a0dcafef82c875472c204e26c7c6972ab5c65ae1e024b833d7c9a60f5bf2dfb271e9361bd3f0d1b72d883c4a4a2996cce5768c9ce92a0be4debcca752db48f44ed76648b48f70a572696f598032938589f33d61ded3356463d2ff4d6135fa79f1d0cd6bf51ee31372071639a85d827c79bb256ba7575cfa9537b64ef830089bbc3194c2b1b39ddb7235d5c161ebfdfe972cf72ab6103377bf9115c4699ce865399143af6dde0dfa72e5654e2e2299ac8a4b64d5a9df02495b4f2f413392a68e330b846440b99fb8723fcb6be92655bb26b61339f3c419ae3e0bd392daeb4fd88b716815fca2dd0b7226b606d64fbd07a6daa3aa926ebbcaa387721889a4b0fb74605abec5e0a280722703c17f94795954a152268612d92c269e08686e0e6cf81c903221afd66a630a140d1352e433009cfd44a4578f293d65bfc943c681d0022f3f09d645a427e27225b23362ad3063f75d38e9535b7ad1699c12753df18600664f63531679d3f9728ebe5b6a7410e26c302fa17908face8fd918a635b9166bbdad47bdbfd220eb72e7f7331b93aae553c435c6fe85e40b82a848831aec0bb89a4a16d66500ea90157ff375a2e330abdc12d9cf2bc7fdef6cce1e026e164391da8fe40f5af592f4724146e7690312b9c172c9a54ecc42badfa7360bbf4b5ff55dc6b310d73e4d267200a7a8e8ae770f2ade890a4f6a95db83e61a40e1ce6b3edc77a67db50f47881a3c2caeb257c23f9012ed62794e60b29b95ec33bbcb011c848a140bae24d39b726a763c16eca39389cd351b1d06c2cc134afabff99c20e7ccb1d3e0af36217972773816e5208ea84470165e95058ffa8f7231c4d19cd4fc1a5d1804756b01af25817b1e20c62a7b53e1bb5877fe8c0e941e5bcdbcb88834ed4406581ff4e6a672d0f5fb4ae081031aacd7b57b374592e3e120fc07276bd089fcaf0c2a5704b972a50d00e9b0c406a82d26860b4cee8357290766c4f14250eb161f7474a1dadd52fddb0bde881f62ef7b778ecb71874808bfc7c7afd2798a09729d0396060f061ecdde74d77e26533fa9cf15b13e477cc3cd194ed23622cd727248f83aac17e26fb528b36d74702ae6e3ef2c655b53474eb2a972768148a9ff67e581333c0709723d243403b838b29f47d65a9ab2bd1f58890b41998708c57346f9590dced70000b2c7ea062711296614661ab212e1edb6f89bd7ddf0615cbfa65ff9c95f6ac362cbcbaf17240c53fbaee02b2784d64a8a75dbb9752d9221170411ef5684180672b73865e7d1a5aad40fe29fd7203622987e4e56deaa181434764c0aa22fc3694546eda6a16066cae50139f585a5440ae25373d1645ad3ab615aab50aade902d72ee6877e9ed888ac961dee651a6750474871bd3c4299ed9352536546d7eebcf44766514d789a5c766da92ca12612d707b2ca2a6c0771ef8b7292d37ff87d493726f818259fe748543f253aac89d13f7222a8bc6100ced5f19fb2ea30da0f574720a9b0486b0806e31a04f947d41a6243d755f6fc230ca568661c1b935ecdacc728627b38829888f4a93136ccd51718ae6178a9ae7479784a1daed3fe30b532e5f93957325b5232ee4256e413b4ffcb0585466aa5deca31e9189de8ef2be33d867fcb6ea1b4afa091cfa596b03b8347527df438f6d98450180aca734b9db50ab5c48debdf75b56dacfe3989183c5df3365a38509e207d16df9ae677a9450f222369cdde4a699c098edaea8bc5339e8de4af1f8df8fb8178aa87f241545afd53972f0240e2ca200d5b894b602f53eb17d1faa037ce587e0f80db5d5dc1353fd6b72e73884e3a637c719818b98de36893f6385b9f0b7b598d5e271841cd11ddd132a96bfc508cb755f6a23ed99441f1a99e3c697d4786dee33f945bb625a564d341824c94b05cd320ff6c2fc8e725a8e6ec02c6d3ea335f1cd14438d9941367f7d72e20ce279c6eb5154a19a4306df61949faaf5ce7bf78563397d92a5048f378672dc7223283f452fb138d368bebe4609a3fbe42e7fb990e551c0a720c9105592725b5cbfe716f4830f488f03aae2709ffe1d2de91f12ddf5b64058a1ab3f39e236421e82e5ef63f31762c687f7c1a8e2708ac5251c3328f33a119a18d2b0a0107297e0e3f3e172ebb2718e0b477a83785d101c61082f62c6aeb773dc905d2afa04fa17dab0e03986f59a0effb4dc0c54d537601a9af16327959614fc187d096d53cdb559e6ce5d4ada19f6d2c9003ea2358fc29592c809ac06fed928d18ca5057280eabe9e098e9689e5eb80491ac3df702d8b0ff4f3469e5fcd11cdd4421ed5728553222f560c8dfa3d362e14d633d2e5070f56fa29422ca8370d51a35e6fb672af816b3debee4e56a12d03fa45c2fb3542aa042ee7d59449dfa939e3a97c6f729078ef822be5379caeec5a43558b9ff49f22b620564e65959237388a8579556e17158cf6b7f7978e07fbf94c67f1b00acb6324575c8f072fc169134ff28d5540bf379fb773d2be854c77444af45329e1ef5c9e11609b96db87c2aa48455866343f641b5759aaf5925c61eb0debd735a9fc9ae83f320525e0cac5598999e67c722a20a083c704c827f41016e86ac8edbd57a6faf190046cb45be9ecbea5b27072249909ed6180dfc1e7adb8a47129865a00198e1f692d52224a0f940abc6aa814e0dbe4cc04c9a3d5c615c33813393506c70f283a689f3de4a31a95271163fb05093a1a1e0936b84bd1346b08ad387572918e53519c592b6553975f6424a2ea2cee3c03f98c698e90457a44a842c9ed87f3bf9bdf8f23aa6e44106648d942947250992e930be18653fa21cb0c438307459d6ad72f295b0d215605efc584922940ba957a81080d0a4305b2e0b00e4a8d677dbbb235e24baf678cdc46355f756d6a01dac16d0a5963ac7399c6d7faf9cf91a617f1059f94c7830d0049f2145e573586eaff4145c04260bc530068b401f566bbaa8fe087b3d701cc74f756c3aaed72c462b4d4b2ea8b4e9664933d12e41ea0e70cf04c3d04c053b94ba97123609f58a54469921b2098e561eee8c450ad6c4c2f951bcf2005d0c6d79752d7ed160d7294047a6fe9091da28c98fd3b814605b818b4dca9eff9e9e50b9b3329f6163a454360698c8a771395fc189d987d8e66185c9cb1dc35c1799f13d106a352ce720714adb62febcf9394f441981b7815697ef1e469d3566f43af1418d2f9dac89572355649fbe896b083cf1af0ce93f931e1a0de00153c6d5dab37a8823fa4891e4504b4dc85e46812b0578f256983d2afb05d36126337e350eaf86f8436cada5a7267fdc7ed280643c63d1c93dfcb9b5b33c9f8fdd8af3aa58a91145bad960b9d72464a7042bbc39fbcc04bc35452fec8514dc5366aad3df0fe87fb2b947cd18e722b6fc0b30550b2cb3cb3318669d8dc002dd45c05faab18e023d6bd66dc4efb72c93b197ba7f91ea1300e2e847a6d81c95f2d68f9ea36928930571ae0cf707d72a3c144c7f2eeada442e638a5905c7d95ade68369fba5d51e74b3ab426bc295722bc16f6489a7e4cf35cf705e158a4ab08d350d4dd13843bca8c23c4448d0a072c9a5ed520eacdd0b46a800d9b43b4c740ffc509e182b6d2ce82eb6e4381d2572e946d3c82c50ba726db821d2bd6ffdf0c167f3732319a9a9d259f4aef8b1976b91aeccd2ec6a391de0e931da1e9315c2403c39a2c04b05176faaa36fa378b52a4b65e90ef48f1f89d3b2618ddeb3b1f60f8756a5f8e84d2c018e402f6ad2814c077ec20a0d02e0cd489a381dd55d5c3b5eb3a6607b07703f1b4864a07d8c6c72c635e29e568df786d79e65231c16a8fa2cb39c80faf81d58bf33dd7aa726612a10944ae40b4a3e210f1dff36a2940c2ddbf54c9cba7a9f1f42b45baa4ce79f5126409f72bbe2373ab18fb87d320b61d2f1ec2e592ecea1f335c1ad7766be6572331b5b91fd3ba63aa349c953139f33874fcabd395d8e8902ac63b94fd5503d45096c08a262e5d89eb3f2567f30904c19c81d7557b464db9943a43ebaacbf9a31924db8492139af6829d304c5713d99be5d571e9f6e13efc467811512db9f8c59fd10df7985bdb909341611859f35cce7c4c5a89b09f499a3f356cb009060667228bc401881137b3d3500f8474ba2aca96c6f5f9b6d02aaa2bccb76a0a3592a72153329826f0ae8a1c32d0de8c68043982ed5588d5a9e5c53e24c85f6c9c6374ac87330cc7b9aaae641e8bdfa7bcc87d1c4c29acbcd7949140e661ab87c0c5c2358c33ea98ba9bb1fc2a0a38dd28b0cc5c64c4c3644682dd99aaf7ebe27300372103f0417e1503a93bd01c58bb0f6646934198f4613edaa7bfd3d58ccf0970d499a3c930fefd7f750960293c29f1b16af812eece2954315cdaaf8dc3eb16ef30a3c7c3a2d8c66729dcb18ebe1616b3ff8d6ca55c1f8d69bd047c9594e51a0c12da91ed2127ad1e443c2182cea6e2fb19e4cf1ad5c64dd607741d7795542b0830a46502fce0cf369771f419ccb2fac8132cef54bdb947892c9e7c8e70badd8f65245c9d415fda0eef0d077b633d720467957cc04dcac54696405198a56aa4468214bd4cb90dd5affeb02c0e38bb0df3fdba05b8d59c7acd05932c6fc16868f05723ad0bc19df8f27285a9bb2157d15c38a807cb9f6c8c0fbe694da284a069f40722ccbdf87b8d33a21dfa07f8a64385bb6ad6ffdf67617e0d6d905133fb5b10150c264306e5327eb635d3a155fb4325bb3556c303c495e342a0fea5bed4ac8981250516a0ac35ed392da22ab999d9543e1dbe8fc04bd12acab972dde5478dc3c721f241106d207fec1e9f78b8dab8394f0e0f21068533e9d0df88c7a4bb9544c5af80f760970d6dbd38cf6faf2110143c3002f240ed5774a4c1e95127f5ab502378fce98fcac364cc6c2cb50ce771a18bf6d8e55769d1d52b1c4e4361d5e641a7249a98c981f6856a91eff7a45aeae1b66b1252b62b7bf93a70ca8768599383c007c7f6050a37f4722ddd4f2e0865c6ef764eb8da0bef40c169c589d33f22beb72efaad5a5358dff3465a0efb8f06d810bcb101088fcc8ef8b56055cbdd93f0c72bf2561b9885d5f0d947096737556c5390ac76ecabb4668be8019bf63ae669d5c232639096bb913eb63febf033a0d5da6537c03bf14a12af03678fd49e36d76722a50ab4755af44f9788d7e9a0cd71bd505869d9f1c053c7e77b69deb82af805be0389b9d1508fc4d63cc08f392a7dcfddfe60a7959e7774f8a9b0732e0962972c613d30df03c3b8784f7481fcc6b73d3906dc181d82c8021e3187368d4eea9677ba7aaa018840b54fb2a49b3c0ffcc8d4f471f4a495cd5c69453ff4d0585756f01181e94917e4738b12a2c8146a0544ce76cd6b2cd6c0151083f64145195986a3ca0e8d3088b4095c29952f06b4ba2611b9a24e675167e2dec4fccfa415f307231f7c2c2a23f734788d0ba5f3829b8e85bc7864e25f16b45ba0121f68cb954724cdf9197e34a86e1e6f3dbf9787db898864d100633c5c4cd27ec2a3026a99872166c3acd7fb65408911c54748e2be5676f58bb903870210f787a288f6fb5f37286b58919120ed2edd99ce4d9175c96fb96373f1553b61d5d75bc220ea459b772e6fb8ef76bce45132ad097dd95beff7aec4c776a550815316d2aae18d69f1f7295129f333fa30677f27c0cd6cfa953b1f92e5d88b80cee35aa5334947a0f7b72ebaef8a0896b2216e4d791a4401c803f2dd5a2d254b8ddd2932493a86b843f7220e4ddf783a3debbb8ad239901af631442b323c7c19d5db1ceb3f93210dbe072f55d3abb35747e3948aa0ebd6f4965fba1d1eaa33411a810a180b9fc2a912572e20644987bb15a0dda6e951bb4258168101b306d1939d94ad46030f77315bd726dd14fa1156e0babc34f58584016bf1485e37938ee267524625da5641883e4694f93d08468244ae729ccdb3aa1c79de073e9aca59061fc03e49c4c4d499866724cd30cc8e8b1da93bd3b546229b29fc4bb2b68ddc2efcbb936d3221a09ac2d6fff84704e01cd429917c3ac0526ef0283f72e3948bab0026e39202e788c95f02d1ede2c74eb52d48cd67dc67b5beb743b9120f806670713199905ddffd317c272e7743e8f2d38cc4474512848e12033794baf76a81fec7a63f54d31a3ea4bf34e4d5ace88945afcdf4f0d3e07398d4c5edcca965e481dcf81748cf339881b246d77bd1918c252882ad393c14c0470cafa0558162cfb7ee255f442bdec3d238e297342041be30c53fe771c3d5c8baeae8cce43337a2b32321d418939cd3307f872965a1842d50cc7b1662a370371d45a4e4c23304cef92a3f2242d85ff0f6d2e7247d0a71054c0c105f2d028c8810c27b6c74c2b5a8aa42c5a3cab498e633a786ef451726329b7a7196cca1fabaee07d65722f90ae1cd7c5c3f87b6cb10bc2ef4be8fd3217218a90179756ee192678f7034cbeec669c6cec2e25c11d702bd7d572694f187ccb42238826975bcd4ec62bf6a0cfa71540f659271db573505badaf729cc395f0b6cc5555378df1e73d322dad800f62951f30f3a37162fdf1eddad472f62433135d8abacd9c987c4a65b245d07cc2c91339817275824bf239657f4872dc566d23c20d9d47c95aba93afdd1f738f43a66c00ace4b8fe25071eff4d3072a328fb9ee76b6d22d34ce5ad28954cc8ab83207adb595d55d4689ae8beb62772001ddcff0c8ee38fa846cdf6552794fd4a662cddbaab7c8dba844c193a89d672273cacac0c0eb12221d8fa516e0664239fbd8608818cb3ec436e7d5d76ca7f10b9ab570cf11b0e610300d01683f111b0ddb0993d58081091a4941411a1e46e25ea72150bbc4d1986b04a7737eb49324274df1f49fe5beab087eb7d56c171807255760eeecf34fc8e7207564c80381284bbb366ed6e09ae84e1faf2b72cfa5372514819a5ec88cc2b7bd9bc273b1b616373dc13a2b883d28e2e3a8e798fe6e872e35cbf6d19c29071a692127446c4fec360edaf1b6c9103b552697f9aa678e96c04d28045a8cd1a5c7c388f4e14702a127ddb30776658cd2a3b35a5de8a6e2e681b2b30cb1959495d39585f7cb05befc4f6c038a08bcf3e50b4b888e3c5f1f23467a7e54673324f12d24d534214e1d2828904147c43cdff11331ff52343e88213fc0a78558285095ce7b79b2d4e9f6d6bbde9246ff3c9707ca30cc8e6087a2619af4ac5566e3ad2e0dbe4b91a8eedcb96248d93ef9b0bd756eea89bc911c7e77233d1de9132bc388fdb77f909a37923ea8e87724fdbb736f5b26e81324e8fd25dd20fbbf4f39a75bc0141c93cc4ad854b2c6231e989cc226e3c45f2a6b3652e567e22fe4febd72161846858184d568f8fa62e89d10cb49364e557acd81f70ea278d3008f46d3fb407239dd502ad0e0264611cb3329536ce08f05e613e4178fa7239354577a51dc3542d5dc1b34d6efe9516226e178ff247062c63fb24e2b90703bb7dfbaeb53c5a1a94155a2392c3833f94887fb58042e79e85a4a2e64c751d720fe5b8c03ae6c3aafce48912d4df9ddc907479883ade10854f16b795de5f951a55d9aa4a88be8f8e5cccf710f2e4326cf90599caa499c0ffb9bbb9794a1e84659db48ab5fb52620fa7cdf27b295bd9379d6f1db01e98d30faca0e0b26ced87356cc2834b8b0fff7bd6f5f4ca7414f0c3ded3f7dd2ae7eda15f127f2ed8bc157298ea2bd595463f1c40798980372d9b791ad29cd818e53f208fe2255f21d6ae2023215fee1d03e580061c675d904144590fd9c91d4358118a43b2cbf230592213ff56d1461d132e0f3ad9b90e46dd9bcefac6b1972430590c0efcd19e05e0f872ba4f1263d68632154c86f7b49236aafb2bf52c4ad619af9a4db95c307a796e1101096da04f812e465e7f6ad551b5d9c0e4119aa18740fc1cf9e3b5c69e25b7720251057ef646662d2c19e86b169afceb50b9d4e22b1436924cf656d5c26dd05521a716fa045e29e97f3b3df4028886e7301cc6f2b14cee771f196cd553a3eb7247f743e5f5b8f41f518e8acc3e821b3ff25c606725d7ec5425414f7ff7ed6072503e9f3b8813ba19e8e2f797795d691f7508cf556b46bfe230ec184cf0d141395d2f6f1cd95b6b70b18e4699fb4962fd0cafeba825b0ee4db5e24b7ee32ecb72a324e741bc01388ee6e4cb4951dbd1df05a938b3f57ed0565dccc5a45482976db6c74df5b156f236b1b41c43b1904444f9d3f9a28afd02d575bee50550c46072c08eb29a0cf706d03e35b8399774abec04c7f60a94e529c1661bfb40f11ef20e39fcc74d055476b2eab869ae59d3f3d0fa266d398c7984660534ed79c8534e2ceca3edbdccbdca7443f3638bb4cc2dc18e950b575c06132ec6075de48d382c186cdeeb7d2a9b8af772d834a52c26093e1354a4114bc546ed2825cdbc20fdfc727704bab87dd574940e62d561a30c2d2d5ffa83b0baae6dc7d0c060581740bb65c77c999dab8478d4babe1afc47db01545edaae4d6d5316dae0b4f87d10592369506ecb30e27a8df3b33a233a8cf57d47269027c0900e7a3d471beacec6afdd725ae7da8ced31f02eeca61181919671eb329ce0ff7b6236a8dd0ee47b2353fe2ae8d74fefe63240b2b59d89d657e4e4664fba140c2a1a239c04836eae6821ae028fae5a6d44f65bab3bf6ebdf2c7eda6e568e709e8579ee9f2d0f71f92f10de3b24df74f7d7de65bdb8b62d0144b44710b24e10697cd2987c18fd97fd677096090f10b8a562fe485d0dd15ca39286e7c6c403ee05edac32b25f131d8e5722b724c069a8d6f67631faea9e7d1e2c00724b7d7c55a06ee7f68db3e8c2997f39d97249e0f06dcbc3584b0649d9aed3b9699f0c4a3b3bc067e85dcbdd38850166d572dda86ed5676b67a49cce72032112b256272258054f6fb8ebf1a3e7b9d90de872f9e65be32f7dc4462b0bbf8a02b7f4f5e6720d95d2b3928fb23fd8fb419b624cdee20cd166c92a450a0ca26607cea9c828130fda1ff7f15795be1431809d70723897569f91418de816d4b5bc60803f761f1a099bc870ee7c7310b6f8963586728d79ac32ec9920cbb6e035d86166043f7f41ff1f31e0e22b6119ba84e171157221e4c7ab7bb0623e763dfcb6a845a49b4169f4032ac04c2040e95d3ef11ebb724bc06ff97bdf38374588922814871f573f509b98f15188b5b0092e915e56ec72943ac7ad7a06c084c7fb6db6fc7e3761a523c9733469dad579b716d2c6cba272639d4313ed548720ad88f8313fc3625c231439bb9add5337ef7c17a0616fc47288b9d9af928f5c5706f394e512d459c4a69e680f3809ed63c286fae38560044005e5ccc4c068d1895c1cca018443b329a1ae2a32ce837e3d2418e8ad18fdc822554314fdf4d5aaa5e0de9173f4a6cf419d2528e72b4ac685f2cd767d70bfbc7263518b6aa4448e49b0682e528772af8d4912ee2de3bcb80ee85598c8cd8bbe5ab4e7de60c42ef38f8cf4b8da79c57651b4d777eaa3066c9fca9627205d8de60eddb8cd95f7a717b13cca06ac91258b46975de3c0e0789048de36bbf410f3d772b0cc74a3595ba1b81d2c8ae5c0c6b0d3eda3ffd2221eeeea5140d6a6cf37d472afd4b64701bea4d7437b5e786c6c400582bf536a639b02e8caa3fa263e6220620927704925e21df81b86f2cc99e053c627c5fbe94ca9bf7d35ce791df964d772366b9b6a54a9bb5a860e4c6af5942bee4d7062499b97a49900f2deca021cf23b3816d2496113f6492c9aff89cbd7acb79646d2baad138992a75c68d2c5b4af72dba268e9a7412ad0f31bb8cef236462a011e7dbe0a838b85f997a3f15d355272d816d780d7a12f07def414e93490684e634a1513e04adf2bc9ecc75ad79ead72a744c14d805366df17e1957f3c2471508bf41eb83412fd4ede670a58fb9ec6724ba4fa19f017c819e14b246e520e4aae88844b286d5950c10b05ed44a6a720728aa44c5e7308610abd3a1c854509b798642c0a4a813292ace8dea53183edfc720a7c9f72a080adc03f12c4fa0d2951f0dff17e06c4ebdcbd7dd67ef238028d728017e19729de8b55f2b1d93e8a8ec04475bc67ff69e368c83d5ed2069b412d69072badb368892f96cd5e360d3845a4a9667593299cc002a2fe51d01d9bfc0372f221969d1e33bf2c98c12a85fb928c6695de07b000509c23476a46064af5823d2299d57212ed39ee66260a2c23e99c270ae0bcaeb87c96e1fe02886cd876207289f2050a9be621d9ac43e5836389617b7a419f3402b6e83cf19a6ee037eae57247dc815c6f857f0e06ced37a7b2ce10f7273741172108fb8b4785000cf9f282b43a131e651a7994deef0ea687792373ae73748ff21265f435c4c4b40e47eb52bafb68ee2798973c2b7cce3ae0ed1bbac04d78b9692548a30a6ca1d99cff88e513933827d32306db6a0cb0037640b8bf81a68d5cb6e6bb38542e87eb12ff58a51469fe268785d2ae0dfa1440bec4fb0ae7af19a48634fe48c18a6df38cbae923b86f1cc014e7188b34450289353581e0b8550687c6de6918b0cec6c7f446d56729c683e9ffbf527a1c0f951a0279791cb6c0cbd4c92297f81b0ba70634759be726cb0c0135f89f6fc49c2b2c3631938545068837538d54aa961d9384c02e9ee7279a1c19db16bb396d5adfa790d8fbf4f599d5b5f72e75023feaea6d6a36c2e721f2fedcad908901252642b4caf3fae04805a4c10854989922a350de4053fc01c86394688d8faec99798acde628f563f31b7e8f2e3c26bc1b6479c9252857e072b01b10d8a623a717e89b2e72d0f0bcb27b9af4e8616882f5fb3331619cac207209564394cc67dd00677fc563eeaaadadc98784071e859bb290d1f54132b2d00c68c5d9278feb82a0321c6b3a0aa48ce9fa8b808ab7583c1be99169e19bd42d72f648cf0ede3691d4cd3f4501441a3abfe257dc8efa5f007d6908783244515572ec17221bc8bcbcb45b99d043fd84fbe54970a2a23b68b47223c2fd7ffa27f16e3a5226120f2fcc6cfd6e5bf2c0344751f1e43774280afe588d1b2c65404d7e72502eda9020ac30b3bb8af77e45c6d6e3216c6c7fdedff268b96a09d4527a2872e35e8c7c8eba68054841fcdbec019c0d9c369c25ce47d42a9f36b71b3714ab6fde583353b01f9d8f7d79ee9d203077890e8af69235c7be0a7f6f2e4ca95c17643861fae6a8e6a2b9f93be69786774249aba6508ad0b745cee71bbf550d63a472bd83968d076a2a1267a6acae7b60fed3636867ba04f33e15b8eecc592ace2972731fef5508f471c48d9c69ce64b7cc1e9cfb836290b6c5517cfc33f4de5f4f4f0c10526c9bfab910f91214ec43e583801ab3ee4f0089d76fcb00e9b649d5e809eba4af96fde98ad9f8d3d53073bd2d909b0968a28114172904313136509aba7235dd1fb480eb9a97dfbe4a656049be6098700e4c68c40a53677d7c2f90e71a080798d8cc68fa8f65b8c74f162f5dce85043a57caff19663e61989fed564bef24c8ed285cb1f7824e5402e84855c1ea7ad40591632c50d75c2fbc9e36d11b10726db8e62c0ef4bfa8b612a54a8066eb8a62f41f49d4acbffec3e6edbda848fd10fc2a32736da41a7f634a173db682193333a8da7fb6e033f2638ae4954ddd48528d68ac5e81b5e644f11796c50a07cafa0a105022ad56ea4939e9c276ff5c1b72bc8c41583e841135b6b985808a5023ef12eb20a379025792931a095b8f9b4072420c78f3dae820d634edb5fe79d8bdf59fbde9762c1049bf2f2e4968926c8127180a756efddc5c818e70065d3d5817514410f473c372e5f7d1611b9af0a5d22dfc3d194b3f7dadb6b64f8dcfbfb80e72ff4deadd7d837794f94b4a6152ef2e7241f1552c0013ea9a94752843031d1b26eb2634c0ea1624bc44f6a5f4ce8aed72105839c66f8b4080fb7837a1cc573f031da4ced0929f9fa73d0d71a77e9024726b949b1bbcdd1e81404fc56a95332ac193d8fd449f507577ff159af72202014fe36f84cb996a3b4ce1c70d8035f0807477f3758750a16fc8392b919cb6e58708dff3ee0dd4a3409e7043570fbb10d22d21ce26bd08e1d99377edbf1308376372f0305acf775c6476bcdd0d71e217972ab226676f77a6dcfbcd3aa99b19984272b46dcf26a8a2939b218677a82b860cdd4f8c443e4c348fc899cba58bfe6369725cf408e46577f4dc66694ea4ea144c1b100b532bd5d9ef7521683472a99c4c720271ce8dccdf5a99a65a1e452d184dd3ac148404ecf53ac17193192d42eac52dadc3f1b476cb333a4f0ac06d14d066161ee7cb69867b40a1654e9a91de748b7258fdfdf394ff42d466923f2a631f1e4c32a28439c6e94a87d1f9db26ca03d17255dfcc2f64fc6505eec4e6a6feb11239259852b462acedc0bb3578754a541d6349cd8011ac4cf728757affcc0231cbbb51904cc2a2aa7d4aa2266215620510722e97d16555fe37cdd84c66f815e1eaf1b868600587d13c3c1a14734ea9e445458d2e4d57fe19e8356718786c11f3c2eb4057ec6041e2fbaa307f227f74ba614239f613a1a708c49eacfe1ae37a0b3da0b2ea5d7c2547a78166eee870a8ef644c41c2960658ace6bdb7deef3f13e21d2aaa2f9f8a8b6a8c1870e1127b9130f3134808949ef9d5eb4fff220b02fd0b32d12fa8d38a3c3a4f3a4e955113d966b95274029b51b13a69467bad3855c32b0d42b5c344fa5efff2b57a2e5a0af924dc72111d1766146492b36533fe5e40ed0b9698ed0e2e40621a44784e41eabd1d9f145ede836eeff1792b4a799109c80d7e8f0037c478c6768831f3866a46afa9877273ec407a115053ec3932ec942daada734980156555d8c03d332f174006d430726f1c7e25ea5e1b26c7a7d6a838de155326da6c2af83fb689e0bc913c62d2f96d12af5babd665d241ef7c59320de31b22fefa6d6de7991d5d5f1d91cff9ba0f6e864f950516401f0bf8c754bd059a99aacdd40468a9c9c83690204cf4acdbea72b33755f5e89f2669ea39daffa8d8773c3f4d290fd45e4fbfdc12ae00c1379c720452660c57726ee4f7026a06d3bda33dff4c8daf0d5b5466de513e80e04d82722a53627218db49713290e2785dceaacbd2f3dffde60d322122b13b53e17f546e27af43fdc33efbe66246a4769d8d30b7dbe3ecc8120e12c8f6c5d4e78378541d3ea0a92152182022b26a36edf35e81243e22ad0846d3632fac9f24793b93cf7230a3d5fa8c2f7993f03f1430ad08244c16d33578ac05ff63ba18c33d306dfa4beca835a82864fd91249ed4c2668dc975d23884a433f3f3c8ccbe2338f3f0281eb6d597fc7d1c647007502074bf5e95014bd473c4692e8c782f9a00ae80533e101256270bebe87e47795ee2c3a78314575becc6f4d723ead88ae1c72ad5bf50654d24f4f3f6352e313de0cd580bf0d8204822081af7487b26350aeeed0fb85272c1c1e12034e6568a3ad17e03cc37a3dacc427faee0ae87335f32ccbb452fa339ef5ce9f8a458f2b5cdf5f301ce4b7c08d359dd2143391f8a446e6b07a6a02d72bb22b66bc15c5ea063efd95c5a8acc4f6f9e7f46ab724e386ee92fb91990eb72d903f1d462270cb084f490e0d44ea1a8a1716e6531e4b4e0bddcf027bc179f729e00e06869dbfafcad157a6038c5710edd70b3d2ad7a75055818f8334db42f548263e2d7662b1d4c14420c4da795b8b6e3afaea86ef0ea80b821d94aa9e3af72a1789e773d4ba350323be8d10da3265786675143c2004bdd84f9d69587363e7221b6a9021b98da75edd8c4494817ac4db777014b5b1db4b1c008d1f38187223cdc53ca00e998488a36fd8423476c1d8538f9063ffebce2a94ba4a743d7fef072bfe284d6bdbbf4c11718b7f9e39531299e10f87ee6225c1e7a27f13de59cb572384790c921057db278e8c1d287e7dd014c2d35be0bc3a715d37f5f10952de3728150ed45166098fc229102e03ad99b42e7e24e81711d1334c7ca09ba983f8c72ac421ca079352ade43d09b9f5b0942ee73f1feabb77c8a4909081a078ba0577204c46e7aa951ed8158fa037a36baa4937b6a825484978d35e7dad5263b588072eae975bd940768a6ea78c8cec6a112ce2185d337a871f52bf37ccadd19371634df100df6375119d003d2c57f17dc17e184db88df1569c919865d07c81a89606b8daf1f752c331e86dc88f5bcab43ecd80c132b4be926fb3d561d8fc89eb9c3725839a817ecf7750813cc655036f5928f060c49a3e481b6ac84643fabc0243272984dbbd2c2a693ee088413a66a2fbb94968a3b64a575a318f81d64a55bbae14d5385b31e1af377798c07843b25858b5e12b78fccd9aacbfabff79a2627b29472e14847ddfd966cadf7e9f9e8400b018ff6137f41450dd1f7891ff5231add1e1443c158cb88b3ba9f7889fdb9ca81ba3a81aa7851a85ab6d65433cfd7180b3072b8701335e034708367b303d01bcfe6ae39862610200cbc1131462713597e9e727c77c83826a961819b4ddb676f05bafdcc5d9d64c1a665d214f707fa9ba789725492a10ff94ce4b6f041328a7881a48d6173b638edf1093178276585acc8df166375c8b21aa54a0739691b8b847ab6ddebfe0cf41a909e9a742369a0d5a69e51fc6445647ecb85bc007d657d351559494c6071d121ed2bb80ac89da8e2abc2726fc7e5edc7764b753df8d1f45127bee7a8151afc37df4f29754443d1751be42de11a14b64817438aec6e6cbcfedad540f0ec04f0dd4d2b73dd3b8740f4e68460ce393eb363241b5f39ddfea0c5101fb8c0751225bcae71fa6ac0c85d04df5572b104196d594ce74d87b7d9d585f4a4ba3fbfa109fff618027818fe9f788cc672708c571a083c8cd5241aa1b181c807399271ee0e00806a7d40b6384bfde1ac1e4c43af4e7ce8db318058137bc0c37eee0acd970c04b54810173be0af9c685c7287710e48f7db8d03d7d6462bab5b36cb39010cf780b03ab95fd34029a713fe72ede1a717c2f66753e409cc145f41130ed4bff2499497d8a98b9afa89719e04604d25bed0e188331cc9f8c9b9f7205ac31afbff10bc72b4e86647c9ebf2e90a7295f8b0d50db2ab1ec44be0514830bbff24e8567772d9942d7f38b0f0ddaa97090754b97767a540d90fe17d86b352fd27071732f64f6ef1bbc98abebc88c5b0405f186d41525a987e97e192717bf14b3127eb177cd2acf394c291d9fe7f9bc372b134bb509ebc68f7b78bdecf20259bf7b1ad77c56fe9ddf46c391abee7aa3d72e0863a56e789d4f34ca203a7fbe6f4489b83fd79dc1a983d07b647efc38c710a9a81c5c55a43d1717b05a6ae64ecb68f987754b689d6ba8954ab3ac63088ec7228f083cb063a61d6ee04200f3d405c9d4c16cb85b9ff50ab39c3cddedcb8bd72fe118b5498b2da374d9b280c3053811737cf447ac70b8d5475e9caed2b34281ae8b25326cc60eb5204d22bf7c7a69dada87bc148f66d9fe3763acbb06193c502613f806ce9fd65a66efa3930e477d804dd8249a2edf8884e2e0d54f23a6bda72a3f9b044f9d4e4c77d49291242ddff623f566471216bf4ed524d2b183d503503633fde498815081c01f5c0d53278c41ef34b802b1a4b3bc14156b146b1f6d00c6a9c86354555f8af0de14ae4cda52bf6ed34ed775f192695a47bdeded43738524bc39de53591d8fda30c64a9c05e3bf0a7921c05e9702728e4d2be436b1d3972dfeb85430174698ec257ebd70f6880782ebe7720cb2a38c202210119ab534e4977b1dfcac240ce6d3d21985135f3668433eb85ef82de72accc903f1e7dd028724fe4b49dc716bb6fcd932d5c9838f1abeab7676c486fc1c75b6a835f503ea872cf81d3ae5f12aca9b0afb158d6218cbf0b0740fcba6c000b638c69043024b572f06650e9a52be8a51d6cb7814c73610c611863f6e3c3993b803231576c2a77721d691cf086b22c3ed6e33057998bd4382a900fbcb44e1af8e35e26897b35e74821859b57c2f819c7ace20ac01cac58bfc9e7e4d6cf3b175bf7ba2adbce016661f8375e00b377f78da7bbe2731e2b4057ceb21f14d8f7e0caef0c8896cc86ed17cd48a8aee362ca728bfcc97e7672967663a4fbb78cd24661d5ac62c6055ddb63e2462ccea82396f1eee22040972601120928c2516cf8d5c10560293bd3b9d631e82912dc6d7c4c671a245e6a96aac8b98b5fbb9a3e8c42b06105e07c0571247252217d848023dbd79cc75cb6cd8921672e3c4de8f75d2bd064eb8fea328e90727f862ab3d460d0016fc53f6fa9a3855824b418be37d14f182baceaaa306b3272f37b7f5b009c1f57e698c8da45dc1d96a59b769d902e84a9190747442e765c7224a9d4f7ccc0a75c7c7d887c4239cbc49a1a0f7104a1c484a7cf5b46b4cc681dfa7f3e9ab154ee2500a1dd0011d724d7d0f4cf89bbc3eea626d51e204d91012469d764dd137cd19fe5e143361c98252e7d59967bee682458f511d90ce71e0021feb39a115a97ab02c83bacd586229d550ea49ee13b13e7bb965443d09541fa720481ab0fc9976785b5b50c0b1b30e7056aa73d22f760d5a8db8773a15137b072e416682ced71aac5a8b37b33e1352251742d7007ceaffc08f923e17a808e4f641a457b8621c81fab98aa8d625479440b08b9fe40797a94696ee502a4e414cc420867b5c7f026b7bc75bf63a3aa2b0655197e75c2bc30c575eb058007da5da7720c648de1be3c3caa0b797590700ad72609648233514fb3a0a55aa0ea7541ee2f7befeaef05080101e23b22bf0999cf7dd6109ce9338c1fb14aa05afc73302172f04a5832a889c3258a8ef5b0a8a2b8e076746f7010a556a5083187224572ba5a6d678c3a55b5a3f9aaa6be271b573d5c3db546ec3e0e18029d0271c1f4b7cc3c37b7388ab5fcdea2102ba85fea85fd5f7f29dd2e2d5a03b76a12cb2718a8eb472d8b50176f1af3a6509f65e708fd5d634c1e45dc132bb5a5c0eb2c91574957725472e31e3d7fddd04c87ae2adbbd3b7db2e0bef89af4cc81ee5c31d55c44780048108f07bddc6761dc5b312d62e48b7ec754dae1a0ccdef57208c1959b549b70431b19bcb34f30f17301a68a0ba10b88b12d7a3a8486883360fba6595f858e6343fa4c5c7f346660ea7b27f3ce7e97cc2fa4263310b4adfa9fbc45bc29e4a51d006248355e35cf25eb8ffc3b7d792bad23ed88a83b7fd0768fcd10c0a3fc0122ce536625af9051f737e521145fcb8d212cc7bb8fc8df35a0fc4ad8a29a451211f1e84c63f2119618b587811caf6801ff860bde56b4051a1abc7051038c0fd1722272e0a7fec77c52b29ab78fa280b24dff2efa437de83ed2028b966df2009b7279a55053c128e51a722cba55da8279fadbfda3e9c5147775abacf243eeb39f72bd3a06bb060554e6c22bf6bc9dbbdf0596ed43537908e9461d0c9010346dfe02ca29de74bc0213180d79cc8e15eb047f7796f1b4cc01a44d3c8670d7cc98d471b70045728857b677cba2e78b5e4c75911b1e653b0e0b8506256d133340133372b7036dd980a9811f65d5b9edb2a83baae49106b54019ed5129ea1d37349f697245cdb607b50d0d389fdd39f9db205ddf074378e6679defbdf4cab05156d87c7269bff8d4ecc25272385fd7461c350b4c5a5e2000d8d70c3d2e1544a44fffcb722678c4c164ad4737c84f499f15b0896f81ecf103495bd24dc16f7b1bdfd6d9239ee4badf92c08ec5ddeef459dec57a20004836d43f8a506c68351a367c0ad472db7611fa11b053bb492b9891a1d83beae37452c7159fdff1d6360056666f177220e769a6ec6d8909218294a496a3dd4fbcbcc78daab96a6b766da3f8f97ab5725ac760dd24744b60791213a71895383018f7a5eb3dd68e2324bc0336c0874c72dd8482260d7c006ee71456c82d72fab24dd80778343d700fd2dfd97c72520d7232847f7ece92a986beb9947c3527c586a78c9a1bb570ab0f89c67b3cd5693972ad041f5803e7746550104a9e6fb5d9182d6cbbfe35b4bdefacb7bb8cd669593715590d314cea1d7e0ade72b19a793a6a9ca20d32503773c15a6197141d6ab172ff7ce372f54d0790b189e06e81498b0be507e2957d5e6b11ff1d2b3ae6b3e772087e85675b0deffdda991b8f54878b80a0dbaf670c2e0dacb534a0a815dc54728503f6035c9b78437a0a88a40a54f5b39eddc3455afe323048ed536a6b843872228d094bdf22ebd72f5323ce3b989536fef64aa0e46e268d1622d73addf7f97235939288c764ac0ce3edd2daed4ec503cfc998447da9dd9f06e65b3ec665470868768c84e18ba0db15ce5b2ccc02f2a0676664597add1d39725a737e78d68872c1b1f15734cbcdfe03a56fc12392ef2074bb56e9c9b12538c72c5fe078d89f7260921f71edbec6f42af0e3db090718189e0152d9de5aaabc0a6638326b9fb472281854593f44d4df73db18d7d3060b19ea8d507c75de5612cfabae8e655957723bc660b485af0b1ead28f5a70d80ad0f89342534c7105475ddc1cde36ec2706892b54b8385fa0262ff4ac54dfe17f31cbe892a223497b39dd9f623737fbc9620e5ee7262d9befe65a9f307654dae1fa07f1e34f1793738fa0ded1ef364d59472817b06474168a9a21101c715f7cf0eefba84d7e757879c92900316272cbefd322997ea9fe169fe08ae60f2edce49c4492b9c45a880589035b6dcd08dbb1cf072e14d5476815f71dbb356556f8c67232359b139337e3028b787a80679bdff9072e33e1003dee1d58ee9521c9af8920aae1e29b4a0e77a5fc50ee3fada6534f30d56fae3227b304615f723fe0819afb116b9703a548aa2b5663e0c912e7fa0db725b05dd8756d4ec1b7b35cced628bd3dc9ad50a375b217c94ffa91785b8135d72ca60a5d00c9875484e8b7809a7245dae33b1aafc727a1b655cfdbfad49088c180a6cfa120de978426380c161832c7d8d3719bd7e395c35fac045f7b4dfc3864c954c1220539ca168ce4fb3a6a4b015fd644df2fc3ade944ecd775cec8bfbf72b818cda8ca3229b96877aa0b3103e8567200df5084e51a65a7342829e3458d737f05070c106a92a1e64833b0971e60573eaab42e3f44ecde8040c1105c097ed72cd656b831f8dcb1e7983a23173f51ef2059591e7a29ea720a9c215b3ef097b723e0164191ca221672895aed923542596ccd45b179f343819750425fce3e0574e3831f762139aa8bb6c260af3dcea4b195c6c43899b47c338cbb808d03fe90c2921980048a5f6127ab175b564c1917746d73c66d0457d92b27ca48c7a9b83847278fcb15e4dabb72f7405adc72c816fb353870657a30e0aa90abf00996eb0af722af760abaaa4778c5b2451f59a06f5341aca5d94f645a503e14536a43fd8ca725cf69734f84d1ae5dce9c51aff351e6e3418f073a6c4a7edb595a0f2e91ef672b6e6d600dd94f9156d9835b4ccfdc475e8709d54f558183540c36b55f5255c72a76276d4b5a289efa715256722bcb1e66e0493811dc6c6d97c573b36fc7e6940f03f3f80f3d66c249bb27b482c606fb93dae73772b2872ce185e67f016d1ca7271166e44eceb395d976df46f97727ff175d0ca21cf194d171fbeab671d2c555c6a380b3cba8d0e1987f15b423079e8ec4f29ebd16e9ca0e54ecb0972058a40453eb92c76f77b48a04736d87323e2d39bcc0fc2134e35b50e36bccd0fdc9435111639611838c9a6cd4bb5a9a603dce28ddba264b7aafafe226b3c1422165d3e1a0ed943f1baab3acef516d2ae6b25e04817d8cb54234ce900787fe5e3f555185b1d72c7c3bf72081111731ba7c1d9984b4e9b5326502fa5fcd2e43581f366ff72e8e9fac80ec6a0a14a0630290e24130b7a596d92044478004c02731276952424eb324a10436f6e071019a34fe824e9ca0cd51a351b1cd09ed21cec9d838ba83ae7069fd6d1239e8cc0806ced81c466a44c798b1f6cba9eb9d92ba8a7038db5371073db8df4c9fa2af9727f2378a69930554750fb38c82f846d0b041b4ffd6c0d15ab88139cf331b1f444f990781d7909359b8382d09389216ee19ec935aa8672ad7f03144c5bfdcb8e558f49e3ca66606c14ab0973543c59064a0cf5efa58937947dae229b6743419e33b8a96130b372f51cb2d0b663b321b7730cbfaec5e37210e0d854ec4e4977d19c4d8a833a18b52ecf8db533111b7dc8eac8d63c48e06eb0bdff93aae1abe46ac141baa21f9cfe82fd2e8b2eff0a441fd60c006c128172fddcced80bc41af9ef0355d0d0a1ab206249f9714d884fb51ca6ef358c424e24d29d28d70c2e8ef978eb49356d52fc35b9869db68dea758664a3df9ded12b77213db741a5b12ae3a1f66ca78c8bf06db08719797214ce9b08f16acfc52167072c13eb8fe425db67aaa43c69328da557ff47933d95a761071006388367a9c7461013bd159bd62953955ffdc4929d4adf886c122306919d4dcd06c60b2669d9047457b5cbd0894631d1f9b6fe41968b9e0d42192610e0326e641304b7bab85f672bed8702b8708e94f0eb51cfb9e9da60fc094ce94bd09321c62afcb4ae4cbe57248545bb5ec329e9997e18e1c0ec9db5f555985500160aa797c0ef4cc2bb25e72c1468e33dd87c4bca10e950836be08a6dcf4c3747dbf12f44d589c0995d83972286c945889a6704769f12239f0f251b2d53cac9fb755d9c0cc65b5e67bc8dc72c83c5491891ea8ff739819491c1a20775961eb0a2ee9eeedf5df5a725382fc720eaac82ce6b2b4a6faaad6adc91c1dcfef2771fd8f2e171985bf082dc53d1856c3e703e51b65574cc6d8f0ad6011c79a430a41349d4eb4b56cbe4220a43e8f42ac38d1f5a33008a542444b44e22c4d15168a03d67d70a135410afe6014834772b419b7a7d8dcfcb3ef0d669d2518541bc91ee305929e98ac07807d763af3d40f609b2161b5aa1afd246bdeffdbe2bf98c2131f3cbbec0b20fc3f624f8fb6027247ec8e1088624d23bc42db91564bd18eccfd59470d754e5c78c328ec82ef9772bc15b4f1b005bd3e69949e012fcbf5d18742e46d1bfb49edf983a5de976fb230ca08e8aa61e300eb655ec773be9c7180b84ab9fa6f42668ac9a23eb6c7dbc272c4638587ddb3c569e28653eee4abc5a4607eeb9fc2b227a38ff71fab3a67b806b11bd1e330a5940955567eeeb6b2239c1eab7bd57ab974aef6c2b6ba8794212ec849e788aa7a8c34841e71a4d06c7cf2e23fbcb1670fae59e5ce92c7c5ea4c725835a4fbc93ac570588af31ec6bdfb407881a85b5881a913eeba21ae1f1400720e0d33cf25ed086821fe2f3235e91ee1f754b63b5ef903637c30f3e6f0ff7d3fe4eddf36c4e2cede90ce1b8696f4b82fd37681e158e9664ff4e3c101cac3ab7245efca67c5418d389f08fc8d469ce05191aab95057d8e1f0011c1081f0f8ff2436691a7fef570c1aa088434191403d4bb4bb1eeca2a63e1ec341b6ff76eca21989868c807923b6ba93f0572c3763208a37a92d5edf15a9427fb4c26e143b0a723950831dc57694c056347787496e23a7960672145ed43f12f867e0345d369a348fd50d4e8b37df81df8ec903154dbbe726f13d386274acf2170c1fd0baca367230de0988f202c0c3a06b3d05a8484b1f140b55a1b0c86fc5567825de424e754cd1d8960391cb2f7ea0124a34eacf15d66760baedf0e291dd83769daf11cee972d9cff31b783ca3244ac60f57cd277bf2d56f6d21e1390dbf45f1f5db21791072af6148833b9f226c889a70649537ee880f0e464ae63470bd7d54ddd4e75301723bfcd1fb3b1f07a6551996cbf4c6cf0e9feff69b2e09553498d3c87015982172dff086dd6062dc564eacb4e4e04faa158c85a937854b6cd68cbda5285ed1d9270e1c3b73cafe93a2ea3a9eafc7f1edd510f500173b1b03923b435ff626502f0613716e2f1698ae918a74e86b45ca5281432856e908d4ea61701173780d2cf20fef347efc720ce5890f4d885655c914f45e2bcaf94442db8d09e3002f6bf5e56748a0a7fb2b9edfd439c126a865174b8953c3b2124bf2bdced4e9008d6e8dd872b8391d86a30e10bce29627dbf197f3f3726cba3182214787c93c6b92696155305e7be9eccab9700c2eaf5a933c0d56bd761edd613284bfc711fa93a4efcdfb1d41e3367d89f0bc66b06e9dcb47825953c5e9b64b303c22603988550bcc7f1772696663b8f5a3c8df6a574a010f74447451b04a0f89fa636fb003ea43c3535272de164ed486935855b8cf9c683dcea0f40896e6394a87d1d93ff2633bc2214b1a1896f9109b84e086a733d9f1f8de4d92b32772e77f67089cd6d992f3d5f0fe47f5905f67f52482ff4be6713ad618513098201364aec5dd28493bbd8597882c720bf723513bfd5b07db607c5125e2f501b3eefd5ebb646eecd39bf6f46ee85e2718fd41e926bb6ad9b18684c2468d7b66d2e762dedb1f8b23c2367dc2ea239e724a3068ec0120230df32f5c44b4292b09f869e5de54887f0920de69d7bb786e6aa856fe0199a9900adbe8d3efdd96e3b0f80b70db7587f45045369b779dfd0f72ec6c1410f8cdcaaa1521f430979fb1d812c40d48b5f67ff8ffb018b3568f5b5e7ff9fdb14758fcdd0a88c27cbd41fc62ec58d8f7d2877dc3538970dc907b5b72032b61fe6b9ba399d08de8d575c54ddb1db2c181ffb144afaf1fd70d1d1d8f72d992f28dfda34ba80420c181642eceecd4ab861fa30a9b8c670f94c9dbf15e52cc6a1dce9316ab7bd10259c1a0694d169da388a7e6a99532922cf3d151836c2c93aeedb5762bc350ad5ba7681923934a5ef4ebf2ab4115dc5d9e5953544f523337332db7afc356086465a299fde5dc97b8ebcc6395627cde2675bea4a71174290b79793d80a9fb0ecb65482c9d333687900f642f283264d0a52101ccc47bda09e3946fc15009d77b97bcee3b5cf45df59391d592c32decc7b4bc92781071d272f2b39f1a85e4997a04044f16ddedc6063ce0382812a73d4df6764dfca359847299011bf4e675b7a48ab6bea69359a8eb70ce5178853b665cc0bd34ebe27c86721847c2c38f6fa95a115e138f9d17b188053c0143c6a1fe712fbe88c7a0441372e35d2ff26d737bc6cee67115e85f7da0e5b5370c9d3867356ae085f08ee3ad729ebb2dd6720abd2f73a51cd12ea5ce273f2861ac15a9da03c42c2d381450f07283affdbd51f96adc67fb2566d7667be9862da402a3b96c325f84d75c470e6a72de030f96051d64d3bb4b427c64b5efa2ce2dd73c90f57fcc671b1b1485968f6abbbd0c9be2c604fc1e5bf401a71950b049fb6599e9c900e9bcf633ffe48d9e72aeadd112353252ea34d2a189a4d3aa53d5ce54f357850b2e213edf9d9067c372618e897329765d7f5b7eeb72bb79f607d374b67126148fbac05bc4d451d8b172a8015ae658dd674e95bc95b4913fc08310f95394e0a8df1555310c8c97549a53a852571bf364d108844b90e2b82d83fb566efddab0d07c0ff9110505a1f5e47182ac6ae348c5a67c0f2d6fe76bf23851a05d61ec9befd84edbe468fa699b5b69de5a1c1ef1346e48f20c42e5ba51692ff88c2e4efc442c5c1360645f253fcd72e0680e9d8947faebf54b76483e41566267dc2c479d600b0d9fbfd260dc1a2d3a4e3058cc61d5a1f0ec611aa70a6e924a9519ef2d7bd206d34b678303155f4572811873079f0c41088394477872f76eafdd929b4e8705a100290b35f8aae0a072ffa7dae415ccaadc9e546356e19c55ffc68a3a94f189dbf44eb459041cb6497274658d84f69b2d6fb49d0fe8e03cd08d6d99bf7fce5464049db03d7c3d325672e36e4dea10351c51beeaca533a7f3ad0ca6308f18e918038d082291fe76a3b72f3e3a72910522ca662030280cbc09e5084e951b2daf8e8c616d7a336d4d1ef02612bebc0aed097895591a9dff8ebacc3e4f20e901a317266c6dd8d68a6f576203f82ff91f06a9342f589e564bbcf9f866f70f5904b2aec38bda767da9b402872da65e207fac8060bdc4d6c163158bde52d0c8c05d22609383926801e7aca357246f2396486a84118c8008a76034d634bb554b8cf6e51845f47660f6840e6e706cc0759f059d9ea0828cef2e6a8bf94208937ba196cc2e17c499034d703e01972dbb4c2e50b2c3c90b382d895df59711f3721526c4e3339fda2fc1c183edfc02de4126b5795b22d616d1eb9e66782b718ceafd5a69a11fe6e443aa9c9fcca5972999372f52931525d65cd252deeaf25ad0d802c1846a004e47d836d0ececfac7230b4a994d521df8c1c54dc7d488f3e365405899e072c755ed66030d2b27a095c1ad568a22416ff883fcfb25322aadb8f1f52cc354155488da54f98df8f4a1745d5475dded858b6726782443f307be9fe93cc04c1353f0032c0c04f04065eca723273a9d48ddcb450a7b62f60c92fb9143bdef94477d953f3ab0d19b538a780721a06fcaac19b0ea0c3bf3756f57513923ae5fde7af3b50c717462d66c37f1572c589bc921f57f1df4eadc06015a03d4bcea4acb3fbde449bdb5e0f3ca626ce7296f1ecf7d1c5c54518c848a5a602ec401b773e5145feb90744cce450766c5f107745e6ba13d49ba7d82beea560cc0b720c93a930b525343ac89fbb44029bfb725dbfecf56e79ffce7789e501d4d3577df71c08f452caf6fb98ba61fecf43730c6241cb99dd5a6c2bdde01f8790b648eb953d23ec334f09021bfa3572a4c120725f6433231619de1a74abe2b29fc8403bf075fe1cd004b5cd4018b79d4baf5472b6826237994ab8738f851723b973d09e8cd9b3fc88326546ac7a6b51544f9e6515f59b3c8f41149cfa5d80b3e4d546e874713536b68fa5dd3ae2312a971a8872784ab44c025474cda671aa908856c1aecd4ec015597f3303dc4f0376aac7c4244e5ef928dfe46b765177ea634a94ab1362a8eddb8ef96cfe6c570e1cc8a27672b431a89310d71a31b3be247011144ab9dc9ca47a004e503b77fa7984c57dc472d9000fd39758006d983538f1bfcd016618c4017739d623c1924fe80379a88c08238d7dca4a7b50f004cc535aabcfff58312a903501a12e962e6b591587667a22f569b34429c4fd6e599a5299337c08b6d3e48ad675d2a2ff95b28075cf33a07216f6b1a8ff702586377f27677c9d537773920fab5112c99eacb0016de72bcc7248190b9ea69b62631de658f8cb04505e517d9e4cbbb44e35cdfc654b88935872daaf771589e039f3902de449ddb7dc5329b05c8278f6d7ebbce22c46408af0723cccdff2bbda052ccdf298c376e48bb5f5ad3c6e28c3ab62bd1c70784f34ca72ffb71e0bcb5836259f22ed9957790aaa768fadce6dadfa7fc9292a40c46eea6c588cf23cce0a87d25781160d297001af8cf09582db0768ddda4d9889a28e99721a667841119bfc12aef56021fb8750342e9b4a1f75c808d97c322217981011120fa2cefb61cca7e1c8370f8ace34a266747223cc580def938e8ac2884bad9f4a4e30b67623de14ba66987653a556f10a54afe24e7dbf0e83d58b56d497c3b20f0aeaeeb3a3d9ed94a64f022928f32c329c6de5eb0508fba2fdeec8fa72635f04dbb7453a1217aefdca40d4de40d127c6b8d2eaaabb913935b88f9ea5835ce246dd65af9a4fdc9674642cfc5390ef69c9a5c46f15c9d5873f7a48f32969d7b7590f2e0ecf347b07e1887f61ad47d5227179b3bc7d7aaa9ea3713cdc423c444e722342007317f7f6217b380100c957d7d6a3ac7af79e21b64f8338361848aa0347354d0fa5df7bca8b34c7480d290cb60fca2c88f5a4a68e414f0675eac96d9c722023a96f18d7458e0b714f845a53f2d19b71a140ccb80bff3ed3e2fc6131f972902e60f74d4a144d49f2ac702a07156c531d9a46582dbeae80cdcaffcd68857250646e89e870670face2b79b44e913a3772862314698ad454e922fc05698ae4da331f818f4a0b8eb50450ca91518eafac7269a060f1d5708b176a3db0e460e727c6ba1dd6ba12a6addac80488e2b45176097f3b0ed7a1cc55e0d1f8f529d3a24f8acef6c659918643d6d5b3aea54bb5bed4b09c12804f154135c250d9d0b0a72c0e9069012038c321ba964075d0e4b0c873a555780338c9518f23a74fe9b63462741fe962b34c75328124847739f16702c5371edb2a3597c2df4382c5ac86e36f2ab4f9eff2acbdfdcec29a7ff066210212f340f1397698186c7cbf881c7772fbaf9682b3e9814b7b5243630c223aac3678f7cbe25aa81d7222c628c5da3ad72c95a506dd6df91f72cb762e36b7b482b980aa513af4c4fb5eefcf14b3aa81a6e384429d92c41f292fecbad8f6b10bf17aecab4074e99a4b8d1be68df758dad7236e0cc50a31c14599c8c452759e8313c90c283c38ac7b2f310295bbe6dc1c072180492ab6f183e8447f9357f7553f9c49da798a6d7db07cfc7a642e9595cec7279e8c51264a28bb6ebcc0457e897da4dfc1d0d0b651c01957508f72a0be25a29358c2e5dadb31a7821b6599e1ce9c6d9ad505a7f0613814ceca14d4c544b7f723026caa467ebd9b02f815aefa33fe1fcf411938179c4351c5d8c9404796a867270c07ae73c2ac26368a489c108c75de9f51de0e3a08b6c3401620063318725641ced3d4ea98bd735e18286a0d9daaadf5b242bb2a5fb2b71f1aec5662604c8724f18576a66d70115b5160e1450cca74c8b6d6e49c00aa6ee33f1395d628a465db5c1820cf95a1e530de40b9a52e225855ed5c2fda9cca9b89912926870e07c102ce3653ab40cbff8d8bc4f89a09e5583a468bc93b26efe540c1b02092d857c5ac5f4c6f7f271168dcc618394cfae7ae8f1457a38c9dcd22e31d1b32c2124744bdd502c965af2b2f09288b2de861433071f74c69a7907a6ea4b8ed2e98cbed330a9451bba98c54b0bf705dc88b786eb746782a1e739ec26191f1dd67062280272c12c4e0251d8a565da75ec1f6cbaba957a15a3138769b51f509a097863372272af39fecef5c5538de6fa0a9ffbc8f45ef42870c14597b9a6e139867b289ad33295ae577b1f96ba7f21001e7f2a02f377e02fbb578750f786f53fa53998f46547a7ec73c07bc116b8c9709f960e8a95d6577c15a5bb096901e41e3b4e15f05b72d03d67763b4ace0b32784bd741ae577279a8e46c29a4ce7a49e035001aaf857296d743c3cfe6519817e333e7e5cd169f3b75dda5e22026132b9d4ce0b9c4cd624ea486676ec73fe54095cfe9a4c616b5277c2240375ade793c40a66584d34172cbaec674ee66211f0f3ed3ee3ea0de7ac358d00dcc12a7121ca8d96b2cbc615f8e4a1a007b8f69fe03245289189977f6a9ce5dcac2c1fef7cf4a41bed18e3f0583330f99bbab8626692ffe5e13f0eea8a053b680a5043ce2334fd79e620afd5951ae5680430ef45827f2a6d7255058b06b0d3a9d7975c172b3e07865ecdd07659d0cf29837794a11d2aeaa40a645c66c2dbb01145e5109686e90b2bc5b0fa26fd18ab08cefd108ee5b2f11cb8b0a3727874e805c8d195e779c0f36f106e56b54a0041d28df879049672ebe37ff3a7b9e467ee5f9e92fd9e935d96ffefa539372fd103479ed1750f69c46aeeb6c118cec04f1221c52d4bf66529fec47ef790113e415ed0d2045ffa0d264879018708c338634df49c9b849bf8d663bc682022937d200a8c4611b623c9015f297583ba540dc75ef0fd48d6c797f34f6691d0a596b665d5f3ccb73422a5a8c0549a207382a067edd23d0893b66da34a8c94c23cb17f61304dfdb5fa51fc49b926643952a23b420f46371b816a7f09c43e9af219b4d4f09f4fa032e6a667777972f95b0dc6cb9bedc065cf15c77363898edcff3b072d8dd8c48a86e44d0a93dab6c121ee056779c3748a7137c85e87787566cf23e5a44e960680721fa22dc0d2288dc40a00c8e6ff90e5c8661b7267e2988d682b372ac475b52ac023ae32fe7423580c9b0bcee72c69490ea94117782eae85af6651ae82e1525c78840c2deb92b77656f55e91198fda2a4ce1b1a40076c9d6c04ca6b537f7637ea6e8cbbd051cd0dd20291ba289946fdfccb165fd967b2ce5bff92720fe1e8a10445a52e4b1aaa8370d28096ac242675cd747eefb4b179125da80e5b0fb4227105a751920ed2c982e9097f76723b20f5443e719baf87bb4b0ca8d469956c91f03e7244c199586a1ec09d60167c7db4c21185086b43a0e8c8cd979635115edc2ba5a1ac3db69697acc4241091890a614764d456bfddead6c97825b3722bea37501a0c6493c0ae33fa65f405ad0c74aec28769ff0c0d5d912b56b12e72f41228f1f7328e54e4499631f8b29b4697035f5a92a85fddc4ac90471af8d76e6f840ed4b4a2558752a7d45b79e084b5011e0cc922b87d84b68a14d0c0534f72fcbe73dc2e119fa0d58501d3a23a80e2b784a75783fb94f5ef852ae6d3210e0c59d2bb2af691ba3d1fc62993c3fba4daf5c58fe7e7677f8f9c100587e3cb7172d498b8948bc8f9904a1c4403be4847993ca3f208341754470167cbf28fed9871fb09f5f7ffb2876bedc8e6ebb03b382bf78d127b7aabd643d1382559934e7272a5e27156513180a8b1b282ab47bddb28fde6bd2eecfc5ce6d81efd4e5a0dec726df980d0405d888b0fe84a8c899d1b40cc8d60d20e04c9c88d6c24b2cb7256727a99d4787476e9d5540a3fa5465b0282e0a7551b8adbdf86952ac1d7e0ac2072022fd21f9899d6afa44ab5e73a559b2fb0daf992e98707f3bb838c1bceea0f01430e9d7bc12324cf2b08c43ccb08ca2a98832cbfaf271fc9ba3fbd854e9695724cbc0ae2a1ca45a7d427b2f73bd2d43b4699fcbbed0107bf4dd73d43c504d7729abf4ba990aaa793812bf5c29c337eba56ff5fadf52438b26e6a9f5e826e44579695c9f247bd17b379d88b61a46a2db341b737d4948bdfc2eda8dfbde262220bdb551452355111ef3cd9f79fb450f2e6d90820af4aa61e8b1f6d2b01397900727d712eae9488e4fb2a8b250b74746e681c4c98b98b9454c3a475e2cdf1e1620fb948033c495164fcf798490b97a180034a182783cd98e6e8047f041c807440727e9cd233c34e726617a1747d4daeb809fb69e448f0373cb342045cda6f82460dacb1a850a5a66d2d832dd71b0bb0ecc6659bcee84a397c523a86408701a02372e5f521126b8b2213036c77776fb894d48ee0ec8bca8ae95532d75ed60cc9f472d1b61225218efb44cd1c660791d56f54631108aa44de6a99dc420697fe96b44634504f526dc83b67e6aabd958d193f16622bb673b8634a2579298c22a786f9724ff5f06d6aaaa6737b697619166236e8170f6013f91e1a5a4e9087ce71afbe72ac52258ac49f3ba9bba79dd8ca6a720d174eeffbe6667898cbdf1f35c02a83724a8575b8f221bb14eb3f18f13a8f27b5ab797f32e9edfb1b1d289707f5ff6572ef3b0e9f361b4ae68b41b1f7ef153d6812e9472cabe284ac4a631a803f4bbf7263314fddcd2f67b0fb98190726d7c2cd590593192af1f3c49fb3ef91ac941e5c34cc94e5e81061b3261291c1bd1a76cd7034ecefc3f18389a310cb2a50dada72c1319eb63ce6234eeedc93d3852e1b1a448c124a6b3d33ccc5baa2e4a6a12f480843af90a8cd74bed3c84657c33fc03bb2dfa815ba8a797714e04bb5be020b72a0ede7754a73b2d0341964305529a9a30cdbe96aadd507dd5840bb1879edb37263fb891607552f680810b09a9a56948d5b108c5e274bae3de18a4ba963b5a55b75ee2b2a11dd9c1d5401b594e77f4c1f690b6796688f6320bb7a2df2a3b8a242dd8d83d1604e4353284e6a3b2a4b57a73e1d5beb93f0785b029c0da9d6bd3a722aba3cdb662abfb90e9af6a2da8d77934a81ddd3809bdb42bfdd09c7e66da40053de11ee9dfa5a26ff1d93ddc5d038132bd2c274eedf0bc7016b1d947cbb26724ffa19ab056cfcd46430b610bf3f42993b4c15309669446767ed2b44b29a8272dff86da412ff95ffb6b293ac379202513a06d6e34ac5bbe64503bbacc44bab5a8809c4a498d2d516424900d0dece2fb65afc2324e037151c7fb45527cf2df650e003025834b4ef20b3e0f41d192e759e572f4b5f092ff41cf03375d026e83d72a9be71ca59fdf62fb62b226909fbeaa68282947b19b2c717df876cab7ddd267205b8b7f85e6a630c574c56b03487acc7388f662cd9e3c23473c1f23ca2dd0372262ea1e6356c1e4e9311bdaf5f1fa40a8e2c709937d2b3d32ec7d32ac905a22814c725f6ce22c6561012b4a9b647e180cc522fc0ef364c077f5dd7ed99e8a272f4afdc2685bc9b9e3c8c537c73517925219c73e3dd968c9b4da68b5e7b57784bbe2a5c878a5f714e1aabb8e2f6ed3cf3bd630b86e6daa87679c67a3c2519751e2bc5e18c2ba9bd479ed0474e83e982ffe46bd54ec13208aa21ef0dbe8f0edf725523043392c57c6b95e2d74a7ea7abd0203ec68a41be2b3af7c06328588f3a5c7e2a44803836a732dd8c59fd08dd90b13d33d1d705c95d892fd426f3d9eb4c72f4eda52f1b9a42e0290469260cbb9917f3d824f850802ea0572df8e564d9014879b6c62288b544b63b3c942ef51fe2931ecef1eca13304a4c1abce356260933ab357d68e1e987dc0b7791db500fe5160a2ae5b603e436b355947a685e3c4ca72b38578597ed997b1c4a8f622fbb7d63937fea7adc9b1bfc8cfb8a11a9d82f772191a56412f62805c6703d94af6ef8d9df106df055d4765a44776333660389f725c830879805345829133882b7673fa94854c3aef7478cf632e2581e44993c2725f99b7893cc5fbbe999d5628bb92f66dac9941cd067702490645f05db3681224677c3c81d0118f67fc11e3e02a2b34b74bdf95388afaf55aed6cd3e1c96d0c1603f36383654fccc2ccb21769d830b73d355f5fb76c66e3e3f558c32a4928a82d247404911455b77388e871959ba4c3a2366d9a74bd7abeee3e1de70a8b8a1f72dab746dc4566655fe2a011d5b3ba841d5eafee9aea1503e158c983ed630bad72006a0b472b4681c7bdcf847ac1142e16dee36fd46ca06259f5ca3e7b5e1ef672cd6acda05b25861479570fda42fa011ad5367d443859613e380a441dd9bc36727e3b5d4180b657abc89880da955c8967c00ec0a4d1588b1bf0126a0a0c173372820cc2beec8d7ec34036c6d5b618eb4544e9449bd09191553a6013cf54411f72c8d92464df3509dcd3221423f37ca035a4041fd9f019d83376d03a633b09f072888d6dd5b3e4de74568ecbe7d7c9bcfbecc8d450370c469a23be3b428b9c1672a79420467a628b28d83fa0cd35fefbc522a35b3ffcee7b75e6b02e9d4e9d2b46a8f9810070c7ea4b7c3b997536ede699db5accf8eb613fc9e2dc11e9cceff67274c9c74310da34872003ef2161071d3ed63a6b45c83488f2925664835e270c5910a83bb288bdac133ae4497d7e321157542c9b8b991d88cdc0e529c38288107209a41ffc5234f30891a405f374f191cd6cfb276838505e8546fae9612f2f9272c37db13f8da81c68be0f0dcca6c2b51a63f473266e5e78dbbdf0c81f7c17d572991cf99767954e02382980d69d25a0c75b08b6c594eb0180ae5538a6e7149e72ff1ba8536347da7162f4de8493d2a7b4496f0540a69f0fc1b9473ddbc341da72634cc6078c94897353c8935d59565cb68d1a1c4cff1bf67d0ac823a474e1c472f1e5ade642f1db31fe4c24ca790b7cdf37b7aabe33e2a0b07036ec3c6236c3725daaf11c87962b5b11d6c4ac0170b32cdb7415f5ef3e6914ac5a1d5a4447296d67d35a8d48a028034f37830e3ad0e71dbd4a462659f4dd5c689de17e1e8c563265e0dd2ce1886e03341cd35f608385af6289d1bc9e4492713f3f3e96974bbe72be4bf7c64e94e394c8bad0e2cd9cb53d96b79d0ba9e8f77af92de7c6747ada72610216aeb93f4a1acad56d0c03dc1df901af59ec735b8aa207500e3576575a7206cb03549459f99480adca2606599c535562bd9876ff40cca7e62f515a688b72d98e060ccf1447ebdc7721b4879dd03b779117941e22b0c0fe08643cefd92b721c382c19c7e5c46daea033d68f9948c9255cbc5dd388ddbf2de95edaf92a6772bcfd855b114b3cd994ec9c204e8445704d55942452f8c311eabfdcf60b2ee972f217602f372efb5933820d89ed1a8179808f244941251542e8270484f4db127274b9db3875ec5808ccb550bfaf9e11d09fca172501b4011bee152d35cda1da725a9c84c7a93521eb56e71d4d1fac46bbb443b04495c022a8a0eab791d8007b0260144d7ec939104e0bda1d132d444f83dc12fac3d0f25ec358cf781b1232a272134ca9c9992124866a5131bef2bffbb54af896055ecc15f2b71ce30cb09862724a4787cdc387e63eb641d136879327e9c122c8376af52b29b3d48335aa1daa72b310191fb6ca3549f0a1213e33d78da8062504eea79947515c559f9880e2b930fa7e16ba1593eb3b54722cb60de0554ad4d7c616cfa38f67b581a1d575102b72bb022a6a14ff84526a8d5572040b99a7ef77e3b54e45354e1bb4196d5ff25272c907d98e88e3484cbac8a4f19d0c49242d00d1b4666f5837a35831c2e0b0d172eca42b4beffc893760555be749f10ebd17dc6365f9c447457780807f2bc0d117b3796331717b83dc84b89a5690deec7314c0abcd85ecd89c9f751c73f7036d5e516c808c5c2e3505878febd14fafb5279fc6a21641c244dd2c4ec0f9c5646372929f80bf219d7abcb6380a653032f0d4f5c64ef0fde0e42252cb7c3e0862817291e8d5f303f04bdb704764d57b3a24ecd185508ab4ef9808aaefc048f323627213d8f1a281ecf0e45530588b6781dbc8871d0a157b1ddb1e6fe3eacb63e22372ddb1087b3325c450743430b344078529e91103828f46eec5648823de627c5072350245b1f56bef542d1349fa583af39678873ea7e9b39d1296b5685b5dce5472b3713e25dfa213c46de58b61e7b4f8f1e3fcf135faf36dbd0a92517751b0f70a46d429d733ea0402e8d25b37cc54015a3811a4bf4f6fa5807ca0d2ed1b018972d80ff455a83e9558f1d36aaa2db44ba318cf6064210f772cd87bcbc90b04407265a5da2939af704ffbebfda974c768e29bee3229c18625a3a7e279edf70c764a69097d47ca5af91ef9c132d92fd3b6a17b764a8f36168c6399c7b995089fa649dc2b83e4e612b30bd1a67e9c2e0ccb0f69d2ba2a8d10f37372786ab8b84942727a328aabafc40424fb0f8108891dc7da83675782a344b232b136fcbfc1283e58fe961a59c2dac10aec7f253b3405a9add3b8563d845e0266a4168a4a5c2c5d728af2ebc8cb790c1ec174c2054154a477a24d8fa84348ed69d78d24bc5a96a0720da2916c3367c65d8adc22b05db0a4887f32f35585e9edce47e7f1b740479672e5ced2f8c2d7b389728e7f857212757d47462a1dc392be613d3b8ace70c669629dadf088b22cf839b6ba6896e8e75dc9ee28a08022e718cfb567eec9773ef67266968eaee453b80d8f51ff35fa2f7507f5018feab880abcde11f056910adb836d6efd250b0779e16c945c852beea86a41ebd005fe143900c9ee3419df44651483ec5fabba17cf8ad55291bf0b29cc815d9cd2caa563e8892141e75920a6c6e72404025b28742f7653a433143d9c20c4ab5544a99023bb3960689a69b09502d47cfd895f02fb9d9d637e442bd72411ed1a845506c6e2095ddf10f1788f5807b6c645af09be04e55f824b1bb9c795ba59cbda9c0d613f57e77158f3de0bedf5f7243e98450de87cf72471a05a520f8f98d257899118d274fcc15a061416c882b2ce211135f7fb33e2e2169aec8d57021de174a00d3ab0a0c09f1a7780c3527ec669499e45ae2c508fb0a6218f9f387ddbc7c1df5580e3e631224628a5db07ce3708150ee9060dbb8448a4100fa9c5864445a461cb0086393d8fe2b2d683fd9a772002da3336e1bac74c0aa6a62eecb43d1083d897667a0249b8e43d1a02273307212dc75c5d6d9bb416418f6f53ced26328b3e75f56af56d0069d161f70108317236ff8001803fd74d17b0ca9e5fdeadfc3bfed7122c4141f4c34bb8485404097277a57c1f5718274515cb806b881590b9339d355cfc93171f07cd73a87ac4c905cdd7c6d96154a150f2b71b4f65339b0f8e85165550631d692a3e1dcf22e7bb72e82d0ddddc0f9d0c43ca072791d180b82edfe6087765f6483690821cefa29d1947a0711960c5ff944e274a596f068ce672f8288e1aa9cdc338b89c6737741270906d9412a373b1b0dfabdb9c623dc7094e5f1bd7e201e040b3b3a61ba5443f428ac62bf08a74404701fe96c4aab7a79ffed91fd7f9ba70be020e2c8c4435ee31ee17c6dacf54ea5c30bf1103a18888743830f1c02a57e175db7c4512f0e7bd72fb853a09c17da65a4879be1a208c7809e5d4c1ab49b074986942eefec5aa9c7246c0d0c94884f5cdb7961c3dfdddf07f0fe4a3bf3d876ec83b87bed1130cc872a012d4ab020b5db10934c7fd18903b4394aac924ce90fa2a74ae272109119d2f9aa8d77095cd9d94a4b9f7d58081755fd846b4549304d32546db54848841cd40dafe9bf2eb1570b396d19a3f729f530489232d50cb84fb84cee50b301659c272c6b4aac65d1ae7106de36a984f5eb510fc1ce2e5f47a94046b148ec1231a3672bbb665e058f2f6217496673fcb2466ccd399b39029614278bf0ce841f38c9e7248d843596ddd59139568d69ad114841dfe71a9a6246c4baf8492071d1329b01049ad74d07cca9f1b0111ad8607239109510d63f65e51275f5b10f841ba3d7072cc31f48db4b863fb90a53fc957fbef0b755a32b8c559815e1ffa65866ba1a972b043e41a7c2c08df86a080aca499819661d83b8ffd18bd550e4209b3335aa6720366f9c1d28a7dc2116ef49ae4eb408ecd7088e408599354a17616991af02472bfd9d8960d88005eaa743bfd13189e3e9ae71458152b58ed8916ce328afcd024f87b8dd85415341f988614ea12ef42807c539d17efa4ae9787280249cee81672d7060232e221234afdce46b85751dad9c2948b9a26bba47967f31fe4fdec58721362354102569ae688d246dc486e65521cf1fc1c2464ba5722ed6d4c34a1942ced9647d28f74f8aa54126365b4ee7803fe65f29a8cdc1e3729389a1f83921f72da2097be1976cf0ac1b11fab1b563b83d8b325a09a5f45221d9b9d3b4e062472d5072136a333e3b89152b6f80f9c5473a245af07f46ab5907f57160fad6bb12bd951e96f443e8175c2d9dc1d2e75e8c6638c762f87a9fe1b730040d77d31286e978448d1ce6b2dca1c5fd92d1773793f5bee6adc6cab97a071decd9771ecad35796aa4b862e7b799a4fc672a705336cfe3686638b3139a988ef1d5fdbe703a4bfcf53bbd6ff06a83af68305b77720e7c352514ded48a953e6dde7e5e4294a863bdeae116e4ed6136a2dc2a2073e7538a52cf8782b83b66fe7ed78ec5f09b2372355c867a4cb77cce89872d966ae28a2cee45504871e7b51fc3a1e9392f0ac37215c0c2c51732e8fe6c97ba98d39f45eaac34a830ce21cedb9ae44fc585de6372d7523b29659eff266b51d76934bc18532db99d9a6eaabc81f4f2028a699de1723e6325571ddfadd7bbd2f401c3fcff5ce0c130895cf169f48aad84010ec4572fb156bee47e329c03aa49253d1de0da5cf9309d216d336f493c5c61528831d15d92513c78c7f39117691cfb512f88ec3527d32b457a7523a9b749dc8024a89f2cc9057991338ff2f509bb3140b2a4aa8107af50037caff4d1d798f8da5597660f144950e92a3fdaa5a60adb8324b29ec9054ea1aab5c4be392ac62f1def067e728382a1afa50fe40c3681d3f15c5cb83c88be435bb414ed2587dcac1869f5c9720199e7c9f8f8759789ee9b26c7f09e76fb0bc94adffba2cc4edc10bfbb24e3606b7474aa82824652935da3554942572f05848016d8fdea23081adac2ec1bd02f8c608f01b6cbb76d0a6b22840cc5029af22f297e4eb1486d4704746b607b7023e9ed3bbd990701e42140e192fb93c948f44b23b23baaa5884eed5a9c02abc172cdd9c890c2164779bc6b257ca84764dad5e433d6e2061afc1c298fc26e525872979ed52987f18214237ea07462c2bf114bf3dbafc88e5dce1918f6925c9ee308feb463cbc595674c0772d91c4eda2f55669614a748769c50f71f1b538f036234d3995806aa5af208c489d76ea93f303152d7e2f78c79879759556ab6c1a71f4537eadbe2edf88a93291e0430f3b37718ca3ec3bc45bb01ae94af07a601fdb66f6cb4d3c9fdb5ed7709e8802dda461513b6ed5db06ccef216450e24ecf800772a00421d5876d87de99b7d8a9aa182d75f29f010bd5340248575be87fde080f8722e6cdd425330e9810fa1371a46a5d9c284dadcef8aaa50f5d3a916b4088e28721c0cd10152882ca3ee228f998d6908c30a0278cc257387c6aa3a3c42e2b69172fa962b9e1cd432b193bf9bb507eef4bfbe1a3884fb9dda00bc01be7a954c7b42540e032c6a0c0bef5477ded9e307960ec9676bc887e91ba273562669350b6d720db8ef2b4893c2d33fb16aa54cb0213d14781aa6227d369a7e6e30f8fdc75d72152fc6bfced7904ea10ed204b6b7f78f5e3f1777e63d9380b3858fb0d92bdd31cba89d4a05aa559ee16794e8ed89541a2eca6e145bb8451903b1a6009b2e34189ae79ad7e1ed42a6c04db2301ea338f77b7e7d82b4244a8f25e1537d4af0434ad10e0f1b5b3cd23ee5664c1a824432aefd846ad6862b5e5b2a422b6d9375a872830ce09a61d39aa35aacf174b82e2fcc62175e223e91aacc14bdf72393257072e50fdb73ae7c8af2b86580f9f163c00e2da9fe6e7f0047ad59cbcf596b57d857835f60f6b14e53388e146dc5db6854ff04b0252d08e0e22ba93b09413376d344f203ad5b4b7d131d213f374b89ac3d2644e18f47989ddda850490deb9455c9110702dd9e41c67b7ec5d45f095915ee8f32bb33f711d25f3d450675426f4b2910d3a44dff531d1f71e4be04afec5472543d87d9a885067809b7988b47fe8e8d72e3492c16a7c6976eb4078e18883d15b2189adc1be6db42854f0050ca03f74f4a8462b630ff57230faf3f046ab2bea0fc8f5e9e028ad64c77f84f8971a039d7123765d8fce341ee59d88d512467052b85f8c39ed7b1fe7c0347fe927285a2c572b61457407b7c09e54e8798120e86184a03b3bdc00986659b458b99dbd0dda7721a76a539f3b08a60313e6d8108d05d902ed14a137e9de9dbdf0a2753ef34b0229f411d1e8e9d8b0a05fc76a5c24b63fb4dc09a9278616fd3bdcc57338bd81b7263aab626b4cfdd141a53f89776cb0ca95b82b7e3c7fa32c26687248f17d3d229426fb353eb98f85a5f055eedc6c9669f337fb319881227b1f8caf8a8742e7272b6cc9bb7abc718144b5cfb22217e5a32fb4b9e9bc08916102626ed40293d074f2537edeb82f30108ffd74ae837df5b0269733712f31528d64e5a1e4473109872b0ba605c7abf996b8bd07fba1e71b63d9c73822b4c0e3c9cc2ec844e66ba3d0c5fbdc7a4e73650a5414222b3d0ef085265e42a716ba4387ed7e9d3b885fc837208f9293504b770ad53518f3ef7a50cf43e10564a8b7afde496f9b60024f15d725e562baa26750ff47ec40c0957ebc56d12a6c49d00c0e851c959d04c8a360e041999d602352602a9960d7c8715c7df7edb1ae5435f6ff16da84f041bc4a1037213b6ac745327f94002cf93d627a90749cec9fc0a85cad40ac8177aa4ae87f87239cd41a94bc9bde19ad9dbb59b81b01492254fd62df81ab294b52db09b3fed7243743986a58eaed9ed1c35fa90c07d88940c2480924593b20b95789cce36d23ee3594ecd34704faf55b49aeb6c3fd0d81f462b78def91f844ec4a5f38d461a726fbb67e00265d7068b064bbb618c06bf7065c2892507f83c0e0bd7595968a15d5c78ceb15d34569d5ebccbc90fe2d2997a6ce13e4b00938d05afcc532e8b3c19fec9472d6a365f714e9e9b85b1f80023ffab26c813477ab44a96839e07bdbd722b94437fad7df56af8981981a6c6e0123b09807af46ec65a5c9cc48015aa4272d0ab62f557583ed90e200576618b25b39020eac9a4d8ac94ef7fd5168c976408ee7903e24564d7942a9db54e4879a16a31c314aefb0ee7fbe1542c572e3ac1722a70e6300b30c9b705939368a38515fa1979aae0cb7b4d53e1bd0db4c47b28722cb0c87897ea113071d6b58b73b42942781193b029fd51990b88225393c1bf2177607443cea50026c6e15ac2d52021215d2f3e140d37fdb87dcc5876d6b69072cf757285e44a1f58dce8fa07efb056f3b9e966260f9351fa09d08bb7bfdba3470152dcb17ca66f78985b65a9d37340df29e5cc19756f437ff84f5f3134f0ac72b9b8f6243396de73cfee6b6f486de9f54a88957c646eb6c869c17d425310ac65b59ffd83936747b0315659f24d501a221e4353f89cf3ff3a34f17c90c16c807279b9b4edf57ec8d33b2c2a4c3eaf1e383f37769877fe3a42056cc9ff6a525b352615f79d61e3aa5c53db82bbcfad2ed334f0a5c35315c91517c4f8dd407d005c26d752d7368ee4e0e414a5cde1642c7a260275109b58a8d21fe65e4980802355f2877d92251673a0d4ece332aac1ccea335ae79513a1e66788b0c612a1ac594207812a30c6b82abf9223ef61998cbfcbd13e68b3550a1c77cbea4a23f1e9a34f4322ec1098ceba9d6af9bde296749476f0b69169988a240777bbc858d8a9f172e64b5d4e1eabb534089505dfd12943b9724417d37808b1530e72239360598f3a4aa193ce8298aa7264600cccb40c9aa22aeaefb84713fe572364a4482ea3c4269bcef761c09437513fe2106fd13ac76053f1893b38981d9b0c89496396f9c54dfe2b46ae21ec9777f314db42419af850ed0c84035746ed0aa6f47394cf496172732478f9e39e7a945ed1c6e0c274215c53e5fb02f97a51c46c090e66f032fc72be2d7f333b5627cf34c0c260d03040e0e096b3d60374ee65ff493fd518404328f6e8b1590f4b66420fbac860622ac75e84025fa6bb0d5a12c9359c6427a3243ee0e5ea7aa5fb219ff9b91644c76c458dbc06b36c728dd104cdefed6b321ef6728ee27af745c9ebdff1eda0f438169d6146d7c8a93a30c3e58740cc2fbfa19e721c9f0b790cc8dd3149354003ab05ca4ada5c23656e4528e1cb5ed7f437a1c247b2128474f1047476ec67877e878a9258705bca3297fb65e1c5a6e3c7bdb8790ca72741a63c4c11ed9b09ac0527049ae3f531ade04123bf18a5f36ab36a8317726067dbbd38dc2ddc6040982901a22b16b0b9e7898334d9cbdf7ea2afca67da09c26b7405f2e2120a4babf099898e00d5b046818d16c92d278fee9f73d878d372592461d1314366cd3fd19496a08d02b979361f17a2ffb194c4f0462447173d72c3895bab257af15c22a0365bfc22ed9e700b8abe3e64f42ede05ac6a4549ae2cb9cc7f344c3a647aa6c4939ee77df9d06cca77094d008ef9b20b9b4d9bd68771cfc3426d668851f68fc437ad67f0c9bef5e276364111b2e83ea3b41411a240246d65234b755936751d1d05dfded4ed1d51b059f01cf93d51617dab05f0a5c472a204e66a5d066b4cebc1ed839f166a76c77f8a3ea7261c9946aec6138acf98620d0f73d7d02f15fafd482f46e04a619c29e25c08e518760eaee730439c03950cfe4fb2f6062eb6d038f8ed3f0835a0c08127bf3e5b665ee9dad177446db4c372551d91e7e700d2ec71f8f332eb5e63b6d4e95f38f2d3a325de306e1042015a43b495185fcb89ee525613e8f8689931b8533252f527895aaa53738c1d9db21272afb6d47841a72a16843db0483d63df60c8d01186a923ba08b5629bd4f29c70726f1c27566e67e88353f24f493fd887a812dbc1fa88b8745682bdae2d16d1d6388ba2aea7316868b377aae4226e38e5062aa585d8adf6b174bc608c8082964b7223f456b46f42c3e81311a562f66471d7650e03f1f7f233da455196f1adcb48094e11a73f7f934f912ac661ef97d176c6028d364e745dd04189228e9e75457e37819adac726cd900d68849234505ec7c0261774d3d205bd9656aa9a453a495d227bb86927eb561aaa9338b8b1e42361f15999bd104e29d05b08ca9e758a39657248c4ba5b5f8e9370fefdf90d16c1793e0e2f9d18efc7c81df4c671619510b5722b718f5b2f01706b6e17ede602ad3257d18d0113bdd51bed2d2b71474709b67223417738d265dfd0e78f81b585b4ddbf44d7a64b48245c54c0ff057a5dba592a71d8ec95fcaf0e9d26f8a073ae71622c20ecbcfe266e8b793550bdabecfa5c726b58ad156dd46ad838ad38b13f4f052abe24b9af1ad1f9b5593a653a83d3b172bbb95c20d1f28a56841c45e14e54208fbf01ae3e9c238a17011997754d45ad19acaa82ce9d73eae9b9ff7154b685f93e8071897c5ec17590489caafb1487e372875e40f92b0fbc7dc5edabb1446abdb0e0ffd743f641b13046281e588ae37341cb91f07217196c3cd000bb7cea6bf9c8f3ede3eefcceb884ab029581b16a1072fd698a84fb5a82c87359b18aab97b859f2f2af69c2882ba5169c4200c0fe77726c92b428313048f4fd03d085c19d9615542d90037c408efb121ba3f6526adf1e2025a156877bc0f6939c88f5865845d38e00366b31c03851ed8f5363196be17295a46d22b69d5c22b4047d3ebcfbf34bac9a4db5804ca2a9d6c042209c0c8172350e280068c7a56aca37bdfec18a38ce9cc8809b9fdcf7f0214785dc3abf581e28554d6827b7bd5c61d42c4b67c521b883309812d29572d735f75fb89678f172b6b4b624a1de4300c775cf1d82722ba9bbb24eec315e75ec46a28b9af77b04450ee93ecad68dd7f9ff18b582fc6d2bdec66df8299c4f0ac2aac49c390e9cd700177f9e59d3d302a2bb19ba51619d7d5a7d7574c2727612138d040e49c2971372327881340411d055972110da0f02456deb44422e3c5b04712189e22261a55472c0985390d00fed23a3f1066aa24278755ef547dc7689dae3719d07b32c86cb366a145c912031016c199ce210ec68e9258120f1770fbb9ce89f5fc591b584a06fe4fb2b0626b0a8169dbaac8cfd673b5c4386984f35ac4e72a516ad99758a5a7240dc1004b68289b349ef900175aa88f6bc513d0ec43c8c883a7bb027626d5c535ff82acd63d2a6d3b003c29817de6ef1ec854b07f35a76621394aa0fbbc26072c202d8372d50114d20878b1e15c96c06f7f249f0a5c7884d1c2afd4766afc2720e46c057cb809d167a4ce0439a6ca555d3865c3e662d7ae09950fd0c74243b3b357bd407a4da9bda4b229f903b5033824e1c6294bf1c5b760bb1271fba5a5d7246be985ec80b23a80e84894d31d5c10999ce11002d4ca030b495d5b90369d772f19cd185a6b11cb52e9f85586a74a4f44463c6fadfce10f4f780a718529df60aa3496123e5ce215f673095659112fd4fcdfe78c5fd6ad9192c3619ba108bfb7228e51668af7e7b5527de375322d4261e18e6bdca1e326cff31c1a20cc3212e2836e74399c7d95f51488c0fd06cf840e8583374ac4a640ca7d1e2b7c4cad87972406bb9fa96263eed40bdc36ea4b0b09f37036906ab9826dcd2a9360efebb2d72977f0ab64c282fc52aa13e14e5ba445e89885ab989333b9e507b94e776982c72ddd338abea066c94536a27a7cb05aea480137364a7962c59d01e01c27fc2f22bfad6f871b68be84c357475ee48504f96e5941e014477e9d1db34b7f62fa3dc6dbc9b8108dff74e929ea8c1816886fe1d63c3cf8fcf460792eb0286bc65c4e172d70074ef1c52c93e12f9f9017871b13bcaeea537d672f86ecd614a7cafc3f172bd77ffa7f3fad71e0d5f1f80d71ae522f5a2ee5bf490486708960327c28516729b81e33c80673987aee096da785c5a44fd43f213e285f4262c726f77ca2b1367eb0c3fce5c620e6aeb100b54aa1e6be492ce38eadb7048bed6a0d4443c9fcf7294469a6e16eed0d9a22a8bcf29e0d60e6f84d973721e28bbd705288c1459ae08145db85ec4caa00d591c0e5e5c6cb7304cfb6133522a3638688172f4561afa38dffef0d1ec8a818805a7e1ef8e4c6122c0e9d691dbb02bdf1f9a04d9c99e70389b5c3b092618a53dd714d8bd32a4edbec8dd07b2477266e8d1a9dfd38be92d72b3054ecf0577a48536f26f09da7053c208d7483d744d1d07797efa759d28f9724fd628d6b60f5a69b39c510ca5a4215d6813cea2d66bbfae58691500121e5451e89f6cb353eccfd3e0400997638718ca57474f635f18ae12b37b5dfb3872c07205dba0997f2841e55042a89cf6bd08d4a0e31ff70c7f288b73163b5a1b33730cb4916475ef0e518a6b24513fcc4b6327f8d9a76f8fc50b70e112220a8fcbfa721a36ed69884e8f1546761879624f7be182cda3beead3cdcb91c8d541ff1d2f721f3a91ea44c1e7b633f61404d3a9af1128a19fbb137ee201f2e5a15ba8e7166a3623d224b2c095ba1e95c336735da036b4b742eaaf21681e34385ef46b022c3df9f0836955889ded01c0c7f5393f13bb04558f58d5df8fe45afd45507e4d876d9923c1a6b7b1223cc55b118bd3b782f78743c7d36609016814a7ff7f80080051cdce553cde1f868dbeadfe209852b02a0f9299ce71bddd8a0ee0f1d9a9c450729195aa61f0f81ce0d45f725f45af8912ac16670ed308d351eadedb4d5c137d1b80f170169d608d31abb4b9393877d3ba59ce1ee1365213ff44b49fe70a5d04726abbcd177554dfe9db5c97222b391be6dbd94be54c7646b06b0b33e1925a0d5e1bed843d799d1218fffa5b3f851b852b9fde31bf1050783039c812578526a172d5d83302eab1b0c2dd02fb8c06e4f7aa621c74a8b66d3b87175db156b1d999723e62f19821db1338e24676648c935143a677eb2facb8ea6d626d3dac5ef798725699911e127e3cac5266201007d22df58a94b06e19892014f1395bb4bacad81d93372dcb7e90f3128fc04db0ba83d0f77c66e911f389526bd76063a71361223c822956e4b3add94fd6ae95ccd5096d242414ccfeebdad2a1e17ac4fc2716147210c2726abe4432f7e091052e4a5340f243412b527e828613ec1726c34a30e272174a0bf0d0b881be8a64d3527a68e8b8a452a4e0ac509ee998cba4ac5b5fc12a54d6d22e8d4e8b65784b7f35f2d9c5e37ec8ecdce6818a49281f27676b2bc27209079759e378f2a45f58c12affda070492a609daa162197a01f46c3f7ff6fe1d847f0714e5df032547174ad7503d3454dfddf595391fa2d2f1b7f00cf6fdb1080f66b53f0b37fcd57bc9c463f85c8471ae4ab8349ca4d2c29af4b137dde3b623c55f8ca7ba8794555494e4ee45accf072efa00ef825283864ddec39e10a87172dd5046ad68d6c7a1adf9e5077cbbe9d0762f524ab2b87ce42be2569d44c5bf2cb862fc67eee50654fd9951586a76784fd4055d4a425a2779d6e554db5d916843bbde41bc74059f5a2603d751177ce4315e1d67cf5141153ae802e8972387ed54e8c56df1ed04f097b25906aed587dae92e5a89b0fc2be914bfcdeb926d09a1729c6a12661f60692f7406b1727f363e1cf7953ec6cc293af64d37de55831b0e72a1a10dfd8c44dbc4f603c183d8429b884fbb2d0db0aea8abca5a2796240f1f35f71fea41f01d647694922cd3778a575f0663484b07693413cee5fb7142bc6d729db597849f34745dcde98ab77d4af6965c0a5b0dafccb49658bd4d7daf0cfa72eb557984d95acb5608c349b965f63d8e952670cc0b3ccc130966131a69f9391b70d523c3fa33552c93b61a38f65cc0a80f0cbaacfabd9a53893e73afe8b8ee30add828e98636bfb425ef8b7999eba73c79ab7ea93c6b2d90e01ad4489242286d142422af376af0aeb2f9cfb77b55d49729da5cea9578548b21cec5ef609647728863514cebfefb9b11213cd3328ca09387ef9228a1b3cb081327ee55d3e3b2724beefb6fc9e98374876ccff2683cd3073b0e4ddf32f07358db41ee4dc0f1e172b8025a7fd8bae368595c52c36f4d4516b784836f85e7d3f5785d095b1c7f5f107de55989349917e648653c9353ef396481309a891213643b27f7d1963a3ea644be9682fbc61c936591b68a1ff62ed0679e4ef3d32cbd0a30ddea384644587d72f2d0542eb47c1f811df0d2145c51e35a3aad4d7972488fea4cd5c20db8209e7295329493cce1e08a4565aeb78f414ef24fc708d4a3d4639d7888ce8157295772e0622d8783c46c46a771c48e0b1757d0ad8bf596fb6e15cbcf824eb03f61be14a87390a250ef4a52edfdddb29d11592f058a74ee33e6973acaa75c6761c88c727068fc483344d3c8dbaad5d1929f6af804893f9115392a7a5394ff935b8c486bb51a1e805bd6ec6d004bdad2fe9cf2e69f8f252d470698bde3198b83f1c031725faea8a97b947c6941c03666c11d0766571d8c7efe207668c37a1493ab87da72dab962ca35b8b6eafbb8a54335743c047e81b59567666731833015f37a9ea506eb76ffc2f4d2f946bc76b1d9112a5aec6c3d27960d5e3e9280f110a3c8664a72c96bd96dc96c8d9e06f1fd9304f70a7763c0876a9170e59469279c61cc8e517271c7f6b58748174b6006fb9f2508e65fbfbe22bcc777ff46540d603ea47779195fcd6ce1698406e2db271152a8580b3c0c85a3695267b8d37d585c8df658f94ce1538492e8daac797642d5212aa8376b6d659a8044d08117f9ef595b5072a672bc3b81f3116b207773d34b23c46177af8aad241752e2a14629d8dac214576372f9903c26b97a6184370f960dfb9f54f8e0b17f6d7932c8b39614d0e91f0f0221c6489c695f4b627228b8a1d449e73fc058b7440dc43632ae42670b7bcf1e7a5d12e19a12b8d3e2da8345b3d592ba25b55b7597ca3070dfeec993228ca2f2c4133b054798279da530efb5109b79bcb2261fba0078d93bb45ba0df8c5be9860035d8a4ec220b03e4af0a0c750b727970d2832787031d09c34a6b272756f71a7965f004761e022aa0d23672492de8dad642a3ed525838cd3141d972a1ff2bd2357262f645aa586f6a1409ee8f99a3eb8846746c7573a0ff493ab85c4c4d9601584bc3fb423e23fe294d270e5d8be7f924f48751f80871f6cc0cc1f778044a679800c2248f6b3c81d842dc3e2b7cc9bc12dc8db01d2d21974d3b4261370a3767cd723e3338a22a546aaffc9984e08ee108a5e388146bcf3e50f573ba4574d148fc7223d967f49125d21aad788a408030fe24fc8bd33234e7d2019469a7dd55fe9c72bd3c5260b841aef01655749725f19ed02bef5679a4f526d8bde9bc2655654e72a4176a10cdbd25ff71ccc7226e697ed573d6612a5850e2289a230581c644907248c1b87cdaf31cb8e2204995ff2df5d4cdb248f0511e947dab13c40856875772f5054568abc00495e1a5f12b69fa720a787e4df0b6f12f356ec1dfebccd88c72aa81c0f5089e7da68429c0413170300e8805d10e982e94a62ed18f33cd0677726d52c32d1071a1b6cd86c03b86ff07d1129ea1dda0c00e351da887ec7e1d63729e7c11051c94c3a207351664afe9349d4ac8be44f384f3fd6d1d9a51745be772420daafc43c0b881ba64fb126c24aae54cc886a64e645edda740e74202e85972ef2acdd40986e5c2727a53247aee750f6f41ccdc94159db2aa973b1d27c42a72344e470443be4e7d38ec454e7e52a621ca4630370c4501b317539904c56f4e72b7d3f352a01e5dd3f2cfd96d9e5bcab2ffe0cc74db0aa7dff09542cd2fee903c3a0647d9483c2589b63f77ec4acca04c9d3806f47d5f16da209ad64668f38c1ec5955d1274c76a74e444ed3e738a7f234c34e6a9a4b93c51529a5ffeeaff2842d9892f9c8b50b472c54515b319557dc5a3debb9d629fef9065cc960f48526a2668cfa41b5ab84a33b808e98f10c40d88a69bf6de1b136d909226b9124682bc72646a49a8dd1bb25729fb762b45c4e2a0426b65b6c7e55dc76f720aefffc43f497bd1050b327f40f41b3dce25750dd18934cfc5c189f49e63be2d093347a60272e630ce7fa11d63fddf314d2bb24a3b9e0bf217c94cb56f194f01dcb7da0ba6015d12c2a09ec0907ab9d6d57f55f6cee8a6cf7a5dd33c57d878ad817d41c2ad083690cef3c45a3a9616e21afd0d1e0ef3d2ae734b7f882e75030e9cd7194591331776a32f0c5769cafc385f62b65a7d7f220e47729d85a1c1f39237bb04ddc86277790d28187ddeac50611247910164aae8630221f86b70365e3ea1bf2501797268735c1b0d1c8034cf5a4a5408e3a7bb399e7c6c37c503d2980e61262d2f425b09978c2a652fb9dd3e8dc9a9e6fa1d631981ef84120ce4aa6aa1a11aa02f1861d882c890666f530176a35f9f4fede15d6f54d47ed40c5826ce05c037994b1272167cebda79c93333b171c0271f82e492a6e7bac6a5e7dcb7dff2887e6cb2fb4de564923d2fec5e1aca14016267423858b3b8ff355036edd6d83f18bb7ec4907280ddf6e318788620f0df29ee6356cc5548755dc5c111323bbe0d3a413b57e536e71c7ca869b49b82c1410e169861bdea035fc3a8fe85e44caf565210d04e453d01d5ca3e5807d38663fdead833362c98070e68058a0ee0bbb50fb1befa51b46b57a90224be42783504389b533af1a1e9214234266d8c8fccba90ae9d24c06c7279aaa6225907cbc53ae45347d3daff1fbeb5d797c38a70edd42542289f1c6a720ea075c3a12a4046d313d0eced61f90ca321fb0fa6007e29d8cbd3b72a582032499e0b33caf05190dc874ec8153a4d416d3d2d75027b1474d14f60f85e116b0b280970145ee14038e039dcf91dd6543588ec5e09d76533c81cc2d633f3cdea720329a4519515cbe319e2f6057203de7aac6a0bebd14d9a57b828dde0c05b0c728d98b140de313c47eed75dfb90eaf72f490c1345c03387a5aee03677400a3a5e4c9ec0878cb9807baf21ba26a4d7a99cc9c69c55f2d117c9395d7ce04c1f8c2254ff70270becf1f039b37e2710aa1680a2d25423c6f2d779249e4961bef58472c601c7db483a16291a08bef11ea3b7d3e107e6c545e808ec5f4d68a3373a3272bd199fbeae022bf1db702d27d555754aab0f5f1c4f86afc583ec819814021f7264992dbd6e5c79b3484219ecaa57315257ebf13425783f80898610307416ab5989e03bf86715083e7ef4416cb83daa5f9c6d1a6beed8c9d1e445d093f1d781611ac767f7d2e2cfa90fb0a6879e0b25d06aadbcbb21f03e2937bfff41160bd072e8cd02ebe75fd6e102b2e9f0db78ed751e3a71f32195f6526ec52e292a502272d8cff91fe9ca4d6a9354d4a89c1d08498e66d1a7ae8b02b975cddc0eb01a126b89e424d6eee42094d2c473381fa4777fad47241921f8e797f20d7059b5aec023f6a1dfc2bd7987fab9ac8fe6e4404a805c854bcde39ed346784fdff684be8c72ded66ea620200e54c3849694012c7cbb71af0ef3906399a113bf6ebd917c6b72639d491057fa48a76b7b8b92596ac4b1a207a82767b3650f59ebef463a378972b1c289275ab14f1d6e9f3dc2deaad4e5c52dc3ae6a76c0f097a638bec2e22b54d7e93bbc908eb6c0948614a7cadd151cae0df77171e51a054a758028c53bc272ea63b46be6c0cf2ef6870eef5c808d607e0e932cf47f83d212c276ec342854132b77ac7622a2200aafd7795bd3f003dcf2e70926af7ec1110960443a4297fa72c5facb640303f0b8716100a864d2b0f85cb299bed0471e8a8ce6b9b1385a2d47b39c1a8498c98446c4e4b80228dbc628d36c4e9f6c8cdbff3b2bb8151fb6d5721ebc672e3612e06a0304f6d77afde13efbb1d9002eea3f28543290470f0a3a72a917f4ddd0ab3cf7bb28a74cdd721e25aee26c64ef1c5dceb8c4cb9e63a28e72fccbf036c0767e5c346fb6a47c9b35d898e563aadb7b51c9f72a793d47bbdd0148481db2ad1d2e10f33070d32897e32d05e02ec2cec6a887f40264e2c13e5172c74f9db6845c5df216b1934c51f540ea0461e664a0c563ec6c56bffe663f8672ce47ba2fbeb2c006cdcd462310c4f9d406d0479b91e05fd2dc5bd0485e1f5c10964f1d001a016f646b544c03c25f55c9bd2fb07378d5996deef8e8c46d16cd72308e409327376042bc7165204e6bd154217e9daae1167531bda0f04522971371b8b768258022ab67c99a2c36134f1075d1d3dd463354432c59f05f80bd9cc2101ccdf5cf92a7aaa595d1e04c341967bc17b3d0f180f223945c6ac078d6b4151b884c25b05c1a0acd5f25abb41827115641cde9b1572541d57601ccdbaba05c72c76a70a7bfafee6dba5dca5a41c4c2eafff548e8bcd4285bc74c479de1654e72037bb8d92e9859632ef8df55129abf33fa198a6681466e8ed0c9572e2dbb550c9c4f7befe7bb7760b05db7c608519441a3d0e73166ce9d82776a05db7a710759b579d36879dd0fc9623905d0964c275d80cab6c5eef1bdaa4d5faa0b7ad278729a6c90c55287917f9cbb24aec25b036d8a3a2b997b7efbd079f9eb15fab7a372f54ee47f3e171c0607aecf1e6a0b410c1e199aa70ff4b8a467c5bfa5a4be41486f4390ea2a08ec6606c9f213dbde61b30623e88d5fab6ca0615a08314015b872486806a0715477250c6fb411266c54e36b76e32d9914222d5093b6b6dd69e21e23037bffd314b4444e137f7ed5f2f141d5bc0887657b78d7a4514bfdc1e195726ba38ece1e6fd97c277cb0839363be43bbff16fd6ea42d57dc6e2c11ea53cd72f1490b65240955da2784fbf7f8aad6a982ef67c00771246431fcd40501b60172474a1540eff1f6bf348401ca4052255c6a07f2a6a9f344ea5aa9bd7b94cf8f729a2e7d8a8e3f13be0ac733cd487886161e50d98da404b642071bb75966ef2a7211085e09e29ad9f8e1fc9074da68ac8036a9221b66743dbb4c31462a49876372bfaf0795e8196aff00aaec3b1819c38b5c9cb242e71b8bbfaaf3bda2de41717223350785bf6cba884b367e4b01e4a6961d7ae48a6ada04cb240480e957216319b5fabfb10763c220a9ed49928c0ec6884398c164c82d54242aa207f52a7fca726bee2021964f74439ed7dd745e5619083219757496e4073957d877968cafff720a3739570e2ca17ed62ca17fa024831ea7c315d4c69e332891eb20f64cd30672a0097ed118b1182594fc819a632366c0485d71dde2b8db7a8cdadc2eef76c829d7cada301c58de4dc0be4aa26c23f081213b16e991ca15d931e6fb394d396343ccc483a2dfc96da419ece6b0e38a379f749abb29b484fc833b9c4a1f01dd5972d88d6aa5eb619d72aa3cb32063a1f67a2ca95e470192d92ed9d24fd04c9ed0677bb0ecd9cbce0855fb7f4d349220fea3dd07aa648bb559afe1588aa042ed5a725e68ad344700b795c526f914967f3147a4d87e6fe97fdcb2f65ffa8bcfc67c7235a8c3a7f904abe5c2cda0c29506758ed9392c5ce9475d81e793a4b949dd0f02aea321700d1ebc32a9436f6d9a4e23ac0556c624b9f70c2dea71b892d30112729f2c100f51e01afb078036fcd165b218253a4cbbb786e30bcdc2de2628655272ebe689e4a526d0e7182ceb7feb3817a3ac884058127f040262bfd1fc7966867260273b655d0a6b15e3431f1aac813168c33b42f2508394650750bf38da98f17277718389fb188312ca9f736b950bb9433b6a3cb17a241a8b2e18e89cad4823720bae5dda7d596930632e01c6d7e50bf71bac1c4237c9f9e93fd7e252563ab200624bc032666e1c367a3406a50932182d3ccbd4f715156608ba44770b92fb086880a0ad1e4b9e1bfae0b0448b0558c3b6846493cb53023abefdd25cc45ab7f7720d8cfd58b21f0fba80c245dafb2427565ebc9561dfc180ca8537b7897a5b1b0bdf76316ef81af0f8931e03886a65ef65624c965ecd646445ee79c836b85a8672cd552d0515f3f10b55b6eb18429a58792c3bdc1538dccfc5dc673cdf7f31cf7266b98e9e1d36ee80105ddbd8144119ce544ca0423581dea8c9d5b40d66554f37af66818c112a6ce4332061ea0d1e68b4d5d4b4e4a6668702ae65210ba97f1a35fc965f89a8e95955dc8c03495b4da5025fd6814d7b2d0d9618ca69c8ed54c72dbdbd5b7dc856aa88ae75d7d623318f84cd741eae86093d9bf14bf0213f5ae56b8b7c3e84559a4a27380c6577e5a00549bf5f332f9e6f7e82eec5d88686594972c5c41f8226ebad6887cb8ee02e7effbb33170230f167a8f45ae2f5ea4a754372aa0e5d05b6b38df2129e798acc7697978315f2b328187b89c8d9c249930903148b778930fa56889266f84e985384feee57d4022cd7f4ac3e7ddfb60d1cd4986dbb24d13e102bf7404acc5555b06923a9f9484e258a1267d2a36115ae361f192f52e54611deadd25bc3b3d607f1f88f7f32255a37357695d32ed144cd758c374ecb30547088ee2a0b9109dc2a9eb6791fc92f896bd949fd3bba3d6c31f5ec82299c40ce9c02e35fdb3a38663168d2704ec0cc53b3dab352ead8792c3cf423c15d847388c8c12f34a28b9fc9eabdc091c9f190e69444b448de56e7fa34a77dca6ad285e202d764ec9d15e6d05c5fb31282f84c864dcdcd333abfc2d70bafd2b2370d9cd860d666bc5382d07fb06a97f444f5302ac919d3d6d8a9d37b028952f5128cf0a70da8282695ef6c1abad41a04dd3132a7d5ed6bedc14e6899d207b493505a28e497ca22d81d6f778b42fe4ab9e876f6dcaca8e0aa9c6aad18c86137816fcfdc5c26181e2e5099c0e1e11f2c78a632c87b65eb197ae8507a6b6a84649a7272ab8be2ce72689a6ce08ce47016c46e875fecbd7e1e3f2802db452bd9fe387224c5bc26d92489e509d0ba72e4c539ae7c7b877182713fbaf4ca06e2527d8514e4d61492fd6cb8c03e2c25c710485e72ee8240d2a4e3e99cb0ee291d5a72da72709397dde94e4b20656228b6fc693f4c40c2296efcb10af06c043c9b814c6615d56a8ca05795cd443f25bc3ff192b6cbfc080004729dfb9554579e98ebe7230f0690d374d1a105a9a693babfb43a7078de1bfe62e6568e396821fbcfaddf3f1050ecb4464e6262eb6c81a697ddf2687ade7f8ee919ebedae5234a91c6d1e6f72525d2a7145435ef2ef9872bf6af6b7b5f8dcdf3c608fc180bd4810f0ece004728cfd4c6df714a0259b5817b4649db1ba469184ca74361874e1dd7a54d050b472ea27197f3a8e260e2386963ab044d85ee38f4f2d96569a2efbac255deed5aa72591572b9aa6dc8ad44a3f8fb514aa6361eec32e592b90c413418183cf740445efd5b29471fbedceb2633869655f5051a9ee3548d9a6999583dafa2a08bf68b4172c83d30d00493c21d96e76a01217162d422c4871609986c269c88e532be510e569b7005af5eb1bc44320d88527891849d2ba6e5899462394b7573d98c919b0a6884e6dfb61d8c3b6db441c28a09bd754604568877aed0ca4ed10ff0f96160725d5d47b1513fa36f2c4511e51b2ce6b302a6c3f0af72b010446c681937b6ea5042230439f1ca8b169443bcb41b946e27c5c688b00f89cc715de4e675b76f7372b9e915e638e54d47bb77defcf34e7e2c91d25667f891f7858b5ab535776ac720cb4e59a73740aa4cfa301dce870c8b30e64e10b8022a9d6124902336c9147772209fb182f562da9e80f69ff7e0f4b1e9c16203286ac7eb7708918e2d906e7972e461d4df3b5781a221f32447d0ca0274ce52195c1a70cf8750924a8da5655c3e9fd6facbc1fbfb1c2bbe3d4d25c21cd1c5725fe4484ce9cf096f638ec6bd7e7233bea1cb4916c5efe1fa29b1aa9c48494e9d6d29448f690387d073eb316e4d5e54a9d7b61087c12fd4865a7f21ffe2dc59ac72cb46bcddcf5dcc7c85f0802072170464d74952f2280156b4bb63bc15159675d9edec43a983fd5ed347b076aa7200c8961ffae83846db341bba49c78f84c2200846db82d9f8ddf9f3f64b056f32146f59dfb1271ec06f72233c533661a33a3bec7df451da33950534805e9d36729a30b4c527e2739c927721238496e0c74b5d7d9deb2a5b4c5a83932097e01052b55dd9149609d719eb19c4c6969c04c3ffcafa40e7aa6af7b2438ea018782c7279e8703ca499f5356e91cd76371a16ae3619939c9bc545dc42d48b6fb4c5740767867d26af1abac06e4965706fac39c8ea817eedf080020bb56d007ecfdda272ee1b65eaede00966d1466094a1f3707711962f6a79242fdfb3cdb260cafcfe42e9176f0dece2c04be075870f8a5b59705de8d14ebf6fefaf25783e1c82963034aa562d775ecca67dc83425180dedd01fba78cc60507f9b8c237e945678ca805653d04a0a379ecc9c83800e3dacae2572377947b693d5013ad6c401bebbfa6872a5e90ae40f0af87b798f3ccca68276b28a7185da31e16a1e3547ab5aa91a5572e315a1a93791e6776a82e36fd80c524fc6d916d7036136c0ac9808ddcd367c274ffa0ee7f35f0e52c4bdacb042878ec024748b94a37ee296af5f8608120b0572a7b31b260e347c921f8766251b28a6fc71636a7154b01725827658d95b43b0111ee1fac0754e79838f0e0d68e85eb7e695ead3ceabfd047718356d7e0934257284525423f5a792c456382acf5dd076600bd39655512ca93f506d5f6edfc86c726cda2a91280ad77b3618860a68bb811bb690ce62898694544e3f3dbefbaf2671e0a28fc980ee6093d75f86247eff7aece20b02f38dba1d552ebb5a99f569376e4207fba181e2a652aac0139df567dc3f4d8c0446b07ccb9f985d2887b560b356117eb4cf9bb3a6143948b91eb7a9fd9d398edf32398df2d21719a19c14edda7218a70e8d26e2dd63fe84db8fd44908e37be4e3929bf060cacdd106708d9af3427ffd3cf7e376622797fa95167653d7d807383359a6ae7880dc168955d5138d72afb5860fa622106ee51cdc859bf2c758bd018efc5b745f7921a39ef2991eb2379f8a8770ef8d19f4b3fdb058f201b96132782b91df7700164f578a53539f4c372afddb1553def86400d130f8e61d453cd22701e0ba7a017be5367a3d10e437720c3b3b4ddfe2030f490e61d82b2df5ae3f1dc8a24d72343d62233e4f5808602c6a00bc7d62b195ed9b67897aec66b117e1215d91ddd463b71965f9248067dd3bbabc5726d8d8c78f1950bd6531723c1ba7bafa3b6a5ac166704d5200204eff1161c5432bad6bbf0350eb171aa3f31f87c3edc4fda8c247a48de75ce8e7b53172bef21849ba8b54b55b007e4cd8165771a8993e9a7ada924a7d4a6b8dda4f8f72caa7eed63f36c1adfa91433c1c4f890afa523da6b911621b6880537ae12a0772b51a13ac70cf8823e814cf96b22789250c4724a5db8a37d8a3b2bc35e56ab072aebd6e7987f88d7f32452b6db213039a98d3586e1f0f9f675c570d52c6fd8c724e28ff07f7f0dd352c63be7e1cff92f8b438df0b3d335f61f5b3998a2ea389654917fdc185e92ada89770fc847fccb8138a46d842234262417bd4839773e6924516b0755d33e4a1e1201866d11f1c106d98dddf06c364257ab6358621bfb55723c512c504faa032dbc2a0516decdcf26b7caa9143e1e2bd1d46bcb24d6cc8f25ae14e9083720fece6eb94037208cdba1e7201049f5cd7c504953ad018323f672c3f8185cf951960fdfee6478699490b1e262fa7941f3f480ed41cc2d8b5de1613460bd703eb8cafe6a8aed71ae38912c2e6d1a4fe243693add25d9a82605b3726e391e260f01669951487bda54ca155f3329f0e336eb042627ac7b6f84718f723e43e6b76c63d4a4a9a7ff94ed37c986a00b2df9e8314cb5afae4552c9f849291bac8dd942073896a74d12a83ac70d3bd4c9aa56f817ac512e88c13641a4ad72ab3bf46f914cc59f9ddc9ecb46a7cb749f5b58016b1a066e75356727ffdbbc72db9a5802c5d7adde175b8fe5a5ff93a19eb5adcc74349c2947b5c8e1672ac572acd02d374581eaa0533641999f3df542ee589436fe73c7334250771f6113805fbb0aba09bac31b632aa17c9423e2ef244a1ec0b38a3eae92876b1554151f4d1fa602f9a4d709009c9bef90dfe67eee49f38eba34f572e3fe90ba0346f735cc72edbd18ae7e5a401d9f950aa16f0c6c1bb3aa8d13dcf73435429a8f299048c2726cd1d7c0d8a39e97e900dc69f3f37a954e5887362b5f1a3516293ceb2c64a9720af4306a7bbd476172d6a1550f4e43775fbc5e9fc13608d6b5d5a4decc84b5640993eb03a6915d32efdf8a9e2bd52470acbf7255e7d28e9405c36765c62a3f0378594cb772b4fa5ff744021c81d46b68255ec555370a239953d3feb2a8eb1d725cac3f63d07ba7f89b533460e5a8768166024b43216b9f162850dc428debf703a70bee72e9e37b8d0470983e35fb7e98242c24fdacd51d1782067c390599f1724e0f848c24b00b7ec2045cb7352812c1a9fb67b9bf4d3436c385bec5f316e972bd8acf15a76777423339f9a37358b27e091b885024c0e2f8625320f651458b72d424ce34f5098a7034873b12aa64126de776c713c0feb990817b1f6b10b07a7232625a8405c2a70d3c077c744bc841891677a0325d540e3df551b57625eeb33d883b024b53dfe221c0070d4fae35c38f02bce145d24e9c8939e73140d79ddc72da44c074ecfe6c792311f2af1af06a5125284eacd152f686cbe20b77a023c27222b4f79d192146714fa68905d4cffc42c74f329541850115774ee82d0ba3a47218dec14662eb185d0286e2227996b0c1b542fe4ada4a2dc87221bb6ce041437202d9bb5e66ebc108d0da5feb634bcd03a04eaf1ba2693410f489c56979f38b55b14c86a82ee541d1bd65c6153c6dedaf2680cc6dd7423ea20b8a78b133c57372074343708aa515c3f550dcc7ae9c9a329b8a0be3e0a686f797c875fb49b174020486c43d12ce2e51e8a898807e0481055b9d5bca906a27548237137b9ca3ff22cfa889abbc8063e7fe272efe7f50aeedbc034c1f25ff2f492e0bc691d4f89e7200db2980facc40d9f346b4547d69b1d789b1d08fa8cea55a8e2206196463e43bc801eb0d7f8eafd2de0f6f72930a75a9baab7dbc2f3babaf035025647ea5e472f0f8093b73bc171552fd4e35511af4e051520ae2837e306e3765809e616aab72221dd0974b512cc2f0fe2af05a076fbf8adf1afecc6ae8598c1ecb28731cc0476201c350b72d9117cd025d0c8f22ea1c548582f99df509c942f13943a5d76972a658e88a38bac8343e084a9e3a615801aa6b3d0fd58b16c933e422db7e0ad7509bd0bd8646f20c6e7e0ee7d8ea9cf633c11cb42861ee4f23b1165a94a551ad72e8a6d1c26d333b8701848ff1269c47560b77577aa99a8750152699f35b54a3214c54c5c8c30388887dc04e257a9957136390eafbfe354579f1db5131d1f4e26e2945f8acd07543f416ae659e573cc88aae0c9359de6cc0be00c1e658d24b61724cbfd38a9e5643804af022364bfd4ae8629f10f4d227c2d67892ef1da152d96f5ec4c7040e102802497068dd90640d33aa9f2105afa0ad4fe235f439a0273d04d858ef8bef0e1a5ef3232a4f3620fab8b0b1e7aae99a70e194fae93fb3ce7d721bc4313a15f8d81e1600c26c80dc45e41bbe3f1a7f088b0ac5095e8c639cd972dd983f1a6fcfb8e561254755e167ab3c557b2dc083ffd1c5049b7a094a13b972f4b207934768f11d697a08dce3e3dc3e22488ba5a2b6cb5600d85a2eab3d9d72a3095e84480a51abd1e2e3aac906cce547a8bbe8d2798abd417f31a983465f7292e4608ea2ffb72563edeba1ae3e55d2b1818cfaa8abe20612867c9a068c3d72185c0a21a99b2e7f9ff2b92f2f5d8598bc53cd1d14cf5fda74166c33f2a2357233361cecd29b267ae76761efa3a0ccf43cd9a94ccd92b8f1a764ecdca392c072abed88c6d17862b1080ad609e25e398ee5f1cac13de1c08cf5a2701fd50dd1728f0119f0f3bf97e2abfc0b15507e34fffe5fe6b01a1e23415a57749ce71f6972498badd427c61a414155738ee49419f175d506fc6b619912c8f889f5a9b0037204cc523a284543660d8015ce2759a8be800b100723588db10691516643d88b130ef883a72b275f0919ee2a163ba2e24673668f600983478042e5f8b846522a31c51d54d32fa8ce3f03ded6c51a3cef1ef3b9ce584ac5b42b2af07664be27c5727b789900592e2d95c03665e603feb08eeed99ad19cb23a640668f51d8a0ad4149143aff5e0e9aa5dbfda9084316bc5150f00b67c363980117d3a8b47f3c89372701d3bba6fc36ec49285f2b478c1857b4203b548414e76a99ed68a94fe990572206184424b5640c0ff743205835488267bc22e253ff6377fc935332a9a0efb724043bd4591c7509b8edaa835891e1d1be400df412c1c8f853e902db7b99eff726151d589695c877a9be984b4ac6efc950db61b6d06dab672cff2d68fe4796d72625dff204cb8c11baebb719312efae61a93790e03f272a543ed2c9a47be48272f5d47fe151360bcb22b5f98e431b3de4cb0b4ba15a1720e4178a8bfe1ef5ca4dee31f8b0ab6adec8200f937808e0efe659082c3e3fa8e4b5c6996f985cedf472fe38e111ce79ecd9b0f8fff88897f1944bd2f6a9188b1b6b4105646f575e20403bf49e3ef32602d3d4db92f6e819363fa5b0dd59fb5db9b4dc944e7f3efe6b2be02ec790d5a6979185b52c6f1fca7f1ee35936394ce23fbdeb9cdf5960191b09d654da8cb12a70ba00c2efdeef8a98241f5f3fe684718e251f9de1fe1f563e7284bba25d845aa0cd27fdae2afc80a60c4375336e6a31b1abd8a98059d5a4b823bcbcd48183d6681ab4fd68e277b6d7a77fd89f01272ebffd2d6c2aed3c6fc71b10fc8cca8feaa51ecbbb31b409c9fdf7212a481c45b096b0ec6a224c8c675954e3061da725f128dfc6c51f6ee9a906890b571d81789d76408784228be2c30354d622e796bf0e0d80401e95527d55e59d58c8a5a120f84adde70f7d27ef975472377b2560e521b3414064dd4340ebbdad16d00e6e5239f9531f07309844a8f772e83a36cd46eb212ce90ee4386f2e6019382f3dc23968e6f19cfad3627b6d4d7270a761f883f576648bac83fb1f307b8eb03a5f7994d5ef7be3188c4d70e0137280d6b71d8e40af1e8055c71c3e703cca34cf923c30997efc651bfc862f20eb4594bab0821fb70ff0194af7b8f1a4ef4e4a951931856fc9ffc3d1025afa0721198a866355c22c62aef44069201ffbc8291d71060d0eaa8a2994af1ae884c8ca096a4d72c206eb75be80351b4805e11e8d70b5ca0d72753d008b80231fee40eb261acff273686a840c26f1964f2186c7432817d55e884c58ebff0fc9d0e50807135fc14bd3760435bf944ff9e4ef905607a1f8342992dba74fa9dcab7682e64036ccba05262515f558e088f2cefa22b2550bed15d6b24d5a536f6421cd9987f41a14d0dc6b84c4e56e21d183f45f92de1e1da65e7ea5045c3176d6571330296b40d3c0cc3ff2783b0a1cf5516117723942866693b82ce086c2293113124acf017250db0b0e67784d8def8d53771824074d10e926f787e7f45ac1ff987c96182b72fac02077c81046ac657b602ac1c04cbe866210156f0ba3a73dd230a40c045b72ece509597fc08e96138bcb02cec916acc4371c44474e83dcfd00e724691f597213a0c9e3749847b2e94a551897c54b52f758c2b2546e75e1736a69ae30342f72cfdd412590327696c0b2fc165f4d055f49b100a71c42331f702dc83b13f889725c1a80329fceb298ccd313fa75b9267d61e8a44a7af9f71b1c0bed06a6bbf07253202cfab839b8b518ba4d6f9724d45dc6c5b34486012ba6af54a0e393aaec1f547b885ce849e2b82e8c1a6192a566883e7b0dcf9dca34a8be3eb3c7f707de2a160e9dc01231646aa50b2af94fce66c593eda5d4265736094e9577f2251c78189202dee81153bea3382ced8a519857b667db6b5229ff438fddb6b4016a8e4a5ca0453f3896e633055d442a575b9b4555c01b6780f285ccdc0f5d5b5e98101e3af739d20046571718c2436c3580c478e8107ec915d8854cc0ac5d6e155f6d3d7288161ee2c58b7dc5c27eb7bfa0243e35d26df5b3dea46146bc064b1dfe707f2c6bb2850494552cf03807b9d70c3c2cf3cf7c5f30b5ece95c722f93cb85bddf5431a0464499b536d653f20fc119d10d5360307c88c4919ddeca0888eb86c73601a1bd5dc71f01dd8976ac26a7f1b625ed36b71fe5a05e243938fca7d6e5074954efbd75cef04b6162976fecc6a3472196db817db268e25eecfac76d74b46bf372afe7e4ea814da4236ffe2b4343664493eb55a953e8bc21e9a553003e3b864e2cf02bbeacc809e83b0c3e1ae0fcb8d42001a32dea5daf1309ef6f358bccc353729ee07b798e85c4dfb38c3e276508d549387f477f76689e2e5942bce0ddc41a72488ffa4a88d8f51b1a6a4d590cdc6bb780665e2331dea82aab90b29b6ebb6b42a7191407a7f26e40a02170d0c75d7787ef923cb699c4745c45eaaf4782c4b747d339ba85d0a3fa1d24d83c24e7db4c36db892ef6f2f760d1620e7f8edee828204e732adc240491c1ff3c6342b2fcf5ae86a502a59cc72b601be8d7515e7dfc0ebdd6e1cbf90e3b1b7dc247df7907496514d06576fb3aabdaf09a1099e85e1272a0ace2a5e0e458f6fd18955ac3ac123240b0ff3d0d994b7090405e70c622ec4ce7108cb23056d1637942bb136cfe0ac1b5a0f85b79802b3e8f9a18f798304372af0b4ff668be32dae601fcae0ba287b8dff010712a6814cb199b5ada47ddab4dff35a99b4341594cb57797ec7dd12fd0f562ad06ac676a230e38396395ff632f23c4f1cc410c030d9dde9563f89073d5dc726dc55bd2a8b711b8fb625accc9145ef01be8bd310f444ad74c9c2d43778d69e0db6d6e09bb199f6eca72891e6c1c7dbea96963e2d4540d3ccd43fca4b1746b038e8087115325e4ece659d483d65376cec330e3c267b86f4bf4d6c4672d423ab7556b938f80ebb84649227bd95f224b44304f185a06c26eaa59edd076e0346bc853fcfc8661c64e0b8e46ee1fa472b53af58a82b44740f4b50c7c9980f3bff303753ff5449ae1e5c0806a14374d7250ee4dc1860a5d93068cd60201c9db6c2f56f0d877a50a14fd95eb8bcf220f128fea7e21fab4c4c2f20a9672eef6820fdfde7fbc6a5d806d4a30ccd8a841d022d39bd2dadc538d7b2c12e4d6790bf949ac95aebd0d9a8cc49aa3f1556d4a06729736e9ff66dba1a1c34601c79a65ec0ba73ea7bf084ba1689eba2a9a14905072fe0b30cca5ce4992806371b27978e089e30c68588fe6a4e5630934dc5c966d2c357bb49ca2540e822644632b21c4f5a736287576e4d1d1ab29a833c36a40a272a4bc50a9a7ba33ebfc9f0f25b1afea5dba232573b659eba7825bdb0d004d0072bac5b7de09d6db0688e5a6a11c0a92542263f42f4d85907eae4ecfb9232107721e75a0bb904d010f506b52603c6ee16aa247ba35e83ec347d07608e5403b195fafe36865d7e20816fecde4db652ca55fad97cc32d95cfcdb4d1b79ec2a1eb872427ee00126672ce20a31181aac47296121960c54d45089f5300490d1f73f2b7285e9911a2c667873956281e0e01931710e5538ea5a8f701d768d759c8e9c75727181720ac9ebd81e1bf11ba892f7b048ca3e748eafad0826b6d2ed77437e404992dd0525054d945d6fddae5a9d10361c6dba66f6a05bc84222c447a210ef2b3e0b6c2c94c8098464a62e9df03752f1e812bbccbbba23702e311456e405617372e20f5e69e45d767945b6bbb3c3668bebc8b50d8188d0e59bbe826e7551dd3b72d7e2d7f5c220c6350720077d49d38a32fe249f3f07810bf5577ef4aa7078ae70b81a0ac010e39ed472b511230b618fac53803d2e922d6983897b9431fd758f7269cffb5b8068aacfb373e43c47bcb2e3b964d1ae1aee3880e342200b83a8e372450f0131de697db079da5a0c11c9706942c9b5a6227e5c0db91aae8ec57e476219c2c1d5df3db1308eb0a135c08c7ab76c37bfcda1c87eda6024e8f7625db949dcaee90137df50a81ca55877dcfbe2b9a088d90a95eeefb9c582ccad541ce73def9dd6c8a978a5ce9e043153d23ccb2428f13a975fa5d767b1353cf2b009a12f7917f9afc463a6947c6eab4dbf84a5659ee80f4cff921dc8b619efc98f7aad040e05ac30c4a044788b1d1a81de72ee3eb85e1706e42d947b4dd7bd3e89f0df728c54cd612367d3da3ed87698d28d15ed70b5edb8e532a823cc762a85493f9d721102b7570a085574d7ad21e79fd1c64fc5c88d0929d7f2a84fe8bfb2b904714750cd13798dfabe8ec2f71d35e1916b492a44e47c9b26ec3fd76beaeb31d32906cd7babb14c61c5103f441aa4e3671862bb18c973e2e352e807a9458da93cce1f64c0b066378798d8eacdb5ce5317000c78cda3aeeeaa83a1ab1e00a5b803127210a81e33e4f2efe19d0671f96bc47f38ebf11487f07334170ffaf7134db71972fc99ccb064136c85315694f8069b8a1a66d9c1d604a9d03be561382817bdd172134d2f8b4e06c335fa5bbe46b8fc71fa8ebe296f659c3128d1b0bfe399ba9872d9e24ff340e67cb420a27bf0b26fe2a81b9ec4e40823aa740d576e01892a3a721dab9a752df4543bce0dbb1cbae6a5a2c73eb37059b5592056e65b439bbe6d72f8dc7eea0874a864f37e82dafdddd2c36a8b80ac66c18ca59574982a02112072a6e8c9b166443d6673b66544db63a56191cd4c6031c7a1c12e42a297a21d227227c15abc8f800bd01ba5ef4da77b617a50364545c097762d7d2dcd0db5be902a42e2c5f2294ddcea5c32bde7fb9f7a1ec9c6794f29cae536c17b8ca77cdf172b2eb7b044a986e88075ea2a104ac80c85ebf73649c9bbcc7aeb5bfbb5d0641a41c812194f485961b141121096dc34b83721dfcaaa4eccd7a9ff2b0add6df2d150b606e7294f5885397f7992c33f074d3357512112b54328e84dcd62099ef04d7200101ea2feab0f01c1031a76070a6d34ab71578389c26ee9484ac1ef83011f7227b2060e1fcc575302dd80cad586526f3f719a4f5b15d9eae5720fa9208398721816eacc2615fdff5822cda5357319a64247d0d64690b7fa4a7085c517f1321c6e3efa6c6b2590630bba04cb3e4114eebe95d086498f4256322da10ff0427c727ab24666fb712ae0afec64e90f4bffdfa9e23b6930df7129684b78c4bd7d0d725f81ecea7e902e9a1f767401cf8a19e22d216462a2f20afc3ca91739102026573de129209525d0e16a0009a7c37bf8bf30ebcd07dc13c43c729f9253ee60bb72b9836fafb491522a64a4e250370455654b60fc4dec668e5f47d655d718ebfd29f5ec363950d1eb55e3db1d04a63dd1c9041747602e7236f89136bfd2d69ebc723206a9b69139dfac5195eb5c7190e9a5cef9199ed73144a810c5f48b378282415fdfba5ac5ef36a7bf8361f317fd0c2e3bf3cf5dd00cf1067e2a0a689f35c3631ceef087f572882a4812ab51cdbabab3ac785e4c18efe44ac715c70aff7098729b3c224b2eb7b927c704430ca80dddcf176bf70a71eac81c2171c05639dc3e724ad0dcfe791ec315d9bd15f75bd1f6f126ea063fd1456b7334d97bff3ce42372574d9643b2083db0302b9373cfd2513b87d19950f14f947efe9e09585bb54572472216eb9561b9cbf41695c5ef9a53b0fc4335d6784b1b0c25865479bba2036b38682fa51595932029c80ccf743a3d7a1dff9fd86caac49623fb0d7cf74f482971a239da25648f43ea979a86b3a1e0a14ab0691b658ad59886585bea49ced872df7e65aaa80e3a08c0c8482631e845177c55b141f07198cd0f6f802ab3e27a096a12ae94bc8eaaf74bdeb762ce1570e834e03cf35d429005251fb66509f979484d7e982bfe450ac7e39822670d3cbc2962167fb8a93fd484766f58aec100127214c98f8fa0caa588af6912509e76ff9c6a107f1190e03c787983910c10fb3272d782e6fed749749470336ba7e086207fd550217d2dd4e42887714453ec4b776af49321d7db2d3d0dda18534c8c3d0c608d2f463821bded71bfa98840bfe7e472e6b3b93e342bed268a28066b320c2ec45080b4ff846f46540cbd8a432ad34f7223d3a07c4a138cd13488d7c12062e143f02202a1425ff76087b89b106ca0b9087d5e79a654ea4f2ca9c97ea0a76e0f0e8635a4ee997505c07f5e96571b02ed7278117f8e61a5fbe3bfca5f6ae62e02e210165a4e7bd1e9188647a270635380724563ab26b554f544ce9b79b44fd31ad199443a126a82bf58ef7e45a88b990d04c42d1052f0452d8c415e1d1b870714744baef010bb9ec38f44084963ba12d021f63aa20d06454bedc47096aca9968e9313bf47e9505929c1c2793c66107e8372707db80d5ec057f492e05e2def5a715c509fa808bb57ef3863e6f660dac42c729dc69366b1d0b110df5e2aa56793eeaba79f32954d0edd9ddafa76c8232b6b1ae5597cfabe2852aac1c662525a87bfb256c89c9ac3df77d78462a4d8a359a554f7cdeaf0ff0637b3a448b52b65baa0cb30004b2749d40e4bf1860613f9d58472b26fa1ece7ae114bb5458db58b3a1a2431542da371bba76b86137115e4554772193fe278528dcf7f81497f579f74f40529197b31ea8a0e41306726f86fe28b72e0937c4fbddc3924eeca843272b1c4dfb598c89c3438cb43858c1b6adb3388339db0e3db49566368f2846747cb1dc5b3bcdcbd5ba0a78fe6abdb67d255ada0727d5402b956e90674c0e911ef272dc9edf43f1dcedfe1eb3ca1a74ed99a0f0e60bede509c05f483d1cf03d90b32c3afca7716e00d43bb719cf55417c110750f721f048bcbba93a13545932a34467816acdbbf494f3f20f537683e69245060891c0a03cb675aaeb74c1d814dc05a9a2fab25c78f04d85a9a8fc61eac36f84ae572a7d177390ca358cf8e7b038c8e4688eafceccc88aac40ca5a10f98884400a33a0744a78572e507fb4ad10dba54647c508102072f41d5a52a4e16089da3ec60728a5a943da70529fff5e745a6353d6b5ff6d7aa7773efdaa2cc6d3a7c452319389e7fe9df804e571bbb2921a95f0a47cfd7eb0898476bc730727cb25bb63ace72c979276cce7241c213ba00b7381fe1076911cebdd0b69e6cef7eac4477f2241613e88ac1f665ab9ef33c18f3e3d171a714346d0b3c3c499bf690b4ad8763e035a1bc7f08d7da27c4acdd8a59f686f5332dc7751a19f83629f4b29ceddbeee772c5196cb04ca0d90518c09a2de2ea6e01cafaa828bea48fa10558b96217850c72886065738debe28b4e54936f8981c402b8a54686e3eef981d1b2a274f1c4941ed57fe3622e03f98c5d77455a84d5fe31c0de4ea4af228ead11dda26c02db317280fd3959af0776deb49cc5bb6f40bef5fa2c0c433c7a3df02c1b785c687db83f23f05dc5e6c4f84ab042d1695cd85b918c57817af62cd2e711ad58063639f0728a1dc6bb006a6d60e5f90a5942de2a2b35cf68290fc241c4818e64e257ee3e49f485616020a31f0ea26241bdccdce98e70d584fe7237c76f052a8d5b884f6c720bbe1d017f09be3fec8c7878be487ff4ec61c05203f7798147279417105bb172233aeb8e111038e4283435bae22bc0e84ae51f759a90e55cc0bbbf4793852b72b21a94b5a5dbec4273e995885233bc0d7ff0a39b6fce99adf5e85ba8cf11d4720d1c5048ee10d206371f539a8849ee5bac3c3839a66e105b8828495dc375b61d52e8f2ce30dfe79df2b588f5a97352ee56cece4a7ed6be4cbe07dd9de4fa137263ed6436fd0003eebaee6e15ad2049c89c0487e8d8da0945533bda19fc37225867ad159b11bc76b672680e14f2b29c3e8044365db1e281899d651fef882495571e0c11858bf9840caa0f10a9ca3e52cd4a0d377557c5140a3f3e220118abdf05eac88eb1cb7184affadb2b3194b98a30a41a38df9f495f2fc5d91180c919b210f61fb9e90f484177b8189523e4985602c9850609a4aed53174d47142d3ba5b72d233ab70b6c1aba1c24ce290893b06017c2df01f126bbcb336c6148bab685e7251ec071474948b518e43058d195b8a511867de37768ce4aae66bfd9e47784853b0d37de3ab5e3db9fa7b5fe531a07918d2b8062e299727918a0bb0a7793721202114ec622387833112e6aac00e95c188020aba5559a03ef24b817d727ea50772590f2052514d99958fb0505e2d1abbe997b9b4ddfb52c79b69b662c8c940236d7ee625e0e4747229e183c59cc3d5becd085cf4271d342f3213fb37ff5f13be72e18a581a8876ea95a8a2bb0e719d5453a9666a5cd5e6ef463cd708b090a39272164ef31c2fddc41419b89d52a0c5263670a4091e5e7134c404d446e742b32c720b756dfc7eeb2360d0aae2485bc81d1a99592b0e60f918500773fe5c1e7d39722beaa260f30eaeacd12c83b7706ba5da704237d5d4913552bb0d52457073a9436abf1d255807fc761b3ea203b20cd879a8e500a356f5b2999fda0cdf50c07972eedf92551623b7547ff9a61fc1952920fe11e6fde1582e9f8c44e709956c5f6b6c12a1cc7f183bac6b99ffc1153bda1057973b6d39aa306a2ee4cbd88f631a72eeb90bbcb18dca7a1ed1563e46b272a9affe4bf99d10969a9de041a93f8e5d726f872fdb4f344dcb2023a31bad507cb2b51333e1d96126e8426fcaf37943107253381b3220c80e613663ef33903ee5a89951bd4ead62365583ef03bd0d0268728dd1f6da82f75a083d6c222afbefee386d6df27874f3c1b99c36a4561c1ed572c1b4a8cf964cdda26358461c70918110495a0b5d67b5bad48b4d63bb76b2a87246689fa7d4cbeb37e64de5f31ca41de511672fd1fcc842d964448590656ca272b785eddd4fcc96af79c23c2211539b35a13c4a00c5cc33863c1334d4491cf672c0285c68cc5eaec532d016fef628e260153f4c18e701e10a09f5dc00c32f225f64008ceb5965efefb2db69ddabd15620e7b2b6484d6e56a05fb287e29cc03a724f971903c232647bdf6ae93ed08d2ee5b5eeed5b56d5bd574221d3ac213727727ce0f177c189ca0ef6e547a628ed60e98efd4e779b62b10883943da7cf1fa772f1dbb854998d7498984b6b7ad0e719f985ea2198d18bd4f4a7d9e1c49402237279b02165f4c69189284503ef2d74b38c0ac104c9022897af08efb2a932515c6f0893bfcc79a0515d544589ed8d7994bab3e8a1fd1223eee651401deee2025c2ae0d4e1bce7ded94d595e1d6f6b83acfe6abf002353d73f7861664b3743f5a5317f985b2a169b17091864c32175a3ac2ffd724f15f5fabba280daec621a9dab72166a1fcd8c61e8138e2a1a4d4faa81ea729dc2120f26050547592ff94edad00b3079a8fd6c6b13844bc8350a439293cca3f21b1a23725dc83d33eff974f59c48a688258f41f8351ca294e3d089d1e10354814ebb0ba7487bffdcf33249c1c34592947edd6e7cc54c4c4f14414d6148a3847ec7a61da2c80863ad109a0984bb723685cd20f71db3012ff2b9f05b70ac97b4a8c28f57ad0e2a8d7ac9049e1ff9727d46eb0dec29756be394ddfea042778df0de87935fdc667c5451c571fc5d4a72b5045d24becd1a68870aa5d562dc613a320c91dc430795e151aca9c9cac2a4557e757cc118aa5d133af38f1250ca679ef738f51919e5b51638f91905e11a77726e830585bb4dca6b4414ef71b6fcbf2d0280c37802cfad0426235ceb7b844f726f3672362f5fd828858d00c240caca1b6780e8ef634c28fc230e5d39fda00c72e9a34a76551c4990c3ad7f56e1dbd9d451f4a8c4de508f5ec0938a1f4d56807218d5760ee268204d27d945bd81cba18baf6d9addf3cee6f53e0724243e71ca72be986168acafbb9f4082e3d64c18af54c307d16dc8f7807fe6fc4a4591b01f7214d2acd5c54057d252e066f33058a6b68d0dcefb458727df4d77b9d2cab51b595e4c075562470156d674b0a9071b22eb12f2369f22bb19d7d49c63d47d07d47244c4705dca679c06c9c61d3b1867479cfbf63ec2a5251547b073ead7267f2e72ab9c1c7aa11ab40d38adfce92e53a327e3adb963e507e69d611bd7eafbdeed7212f6c683226c5505eb1b1200ba83ff6a8fb5534c02c9ea0370c7101a06db5e3f613c944302bda65f6cae896988b816d39ddc0f7dd206f7b07df4b8e3b99d39567d3f47ff3df0cee1f9aa6000f33b25ebd5f016438bca86a5d0d49c55de5f1872b0899dc491b56f84a50ab780a1dbc40f947253ee8067dc61ff010fdcbf55e311116d15590a18a46879c9cf63b5f4b0127be11614ee1b0ee624ccf386bcf8a107558c5b0a812a0c6a1c2ec6c52786328debdea218f93c3d3ab554468b63f1f37246fff4fe94fe3cb69daa5f5ee0d983b8942f07a7a2c634b39e4b6ec63cf34f72e7e6d0319fc98ab40f1c3f4af00694860603124118a12a2cabd1cd00b2a59e72968ecd4bc4370b313e038f22fb1d03b464d5551a4dc6e2dc9ec644f94b9bbd147f8717325849381ed61cb8f550c79f02ec6de5d5bbc6089e6e77261c068e4009beb1338b627628369d9da3550d0ae01b1705b880a02a0f0dced77e0318e6e972315c436e1e41a0929e55e3a9ae22e7a13863b0286c048f8d8d6f90de85ad4272e6744df89d7d7067bd99c901e000d003c84330aa05ba347877a04bd5597f81114efdfa7bd6b34f64da46953ea9f825faa1ea0be4b296d8feec1b75c74cb782036bdd0a1b63aa3ab61c151eb60da99b35055909cfe3dce9bb33929ed14283e0001c1c91662323f08979133f3dcd435934a76e038dc5974bb0484a91d42dc88513e9281884d64080ff93224340b1bcecb24346514562b65bb6715ddae52ed52072a505390c33015bbf0e87298e202fa80ac9ce9d03ff3516611bfc643dee37c37268e1161a199c7c004ef34f55a1a85459f98b8b0fe6ccfa44785804f08e45757268457dc2837c65cf9c62055dc03327173b435efc0c05ccf40d65f30071b56b7289b7ff596ede01e082776d9956fc94b4cf08f5589b90b2d7cf70f1c9315fd42d264d6d20891adf19eacf90931479a96de37922cab94b8e017deb70dbd82fbf3acb5be36d031e16184152866323e1a767b3fa6d042535cc1dca61b5e6261a920a5448cc25deb555d0b111d5f8aac9708ff63c282529fce4e590119b0c85951372f12b689aed70e58ad14e70cf94bb79e88ed92e690278debf3db2ff14f656a472e6481664306ce86a5810fe06e11a72dfa227d3661b6cd32c5b27099f6cd4d50d5f03c956c5943204cd9631921ecaa988e8ca542cda3b7c8417a9d037f7d02c724d5c001ab92c3442704e79a1762a5f1f20307901f63fae09f025d5859b7fd07261386806df0bdbe4bdbef7321d30ff0c939064c0860257c2f42dc20abb806e704d66f2aea2ee3e514e24c2eec51ed9c4e86f882920d241107e610252c1356b72b2dcafd5bc7087803955d757031d3e722d73b613976cda64d55426c81ed6cb72f9a96c8bc0588804a8066b37d3f614f72ecac4f3d7cc7054a2e77f59a6effb723ad043c9d2920b12eb3d90b1ef5fa5240275a9b1b0e2c26e63f41a53ac12b572156f2f70962c3b27bea1e8a375d7ee6ca0da2fd35e37221560145aa9c10f1772322c128ee4d7bcb6f5c64c1cb9069148f5e8752be1052d199ca14f145587b97281bc08558bdf8cc52f547c4f792b047662148bab524d5ea9693056fe6c287d72da27993af5d781a340c48b84f7a9d6c5a0d0f4676a85f1fd4148334bbbc901724a4d0d7bbc73d68511d789514d76f59782e2f082ae395dc80f009a75c6b9032200098a6459075f2c7841cec081d3e67861d617125e89e33e77ff88cfd5b45472fe59c6a526198d913ef5ad3a25edcc6e22635c51e838231096bc18e28a30a572bf8eba5e419539db9fab200ee26268f5a4c69727b1234317cb3626f92dfcc5097c5c5acff0b17524c004df417dfa4a16179cc7215a8243ffd22ef434780ae9726a161ebbc8cef77bbc842e65998a4758be2dc326163d9c088ab4e22b5a07cb72714d5061ffee591f07c9655c65d8ea93c3044827543624359bf0651560ff65722996ac52444cfeda3549cef5b0c7459919e312a11642a4798bf43abfb97f3e7206f80735be08673043e9971738d4723f5cf3307bed9856be3b29e44e9afb7472f9fa20bc90f67eeacb9cb2545f77582266d8d344370ef347ddee444b5954835b35f8ac9309eba91bcf6e3e27d61a155246ccaae3ca4a8f0716100c0b4cb96a72f56f4174fb65e21e08441d293bd628d0afc732b48785134556b2e00aa3945d49d4038ab6b18ea5f76d8fa5f805c73113542913c8004836403dde15395aed5c729d0a1039c998f186f76fc4779ab5213c6c89b390440576731243c0f6a528670db5ca87a62d53fa62d2c30a211e80b5e6552a4f06bdf52d9aacb79efcc5c2b4727d4eb3fdf8e0151fece2201ee7e79274db7815ebe8594ed984db1c2bc7bcbd1af39b8779f27788e9185c07bf98768387533c5acd60bef3bb18f6b9d6bf260a72e44fbe82c9c20c0cff24c7b5fb82fadbf18539a25406fe2963b9f2d12e785601a9fc031501d971cd4057bd172011ea3aec3a2ee0ff2775e09b6c9c2cf781350d6bc91fd182bc3e8c26fa933009d83a2fd3d33176c48d4cd130aea2f172334c72ca572edacb079cecfd9980eb20e424989f38dfc512ea724646368f89d5438572fc748c55f5a7a695b0d2f36632c1b7a125f95787e34faffe0b20af3bd71d5272267de3ee46129a32c6dde67f4a28deb74ebe6da8cf08c8c69fcc156bf351790f4ee3b45c28f1d0dc7886d043e086a2852a484f8d0b33987965bff5e8b3a106722a799dc26d497c9310c52f345106cd1714c4ac007d60c54039c1e3fa582e5c729047ed8fd60b2e9df273ffb82da3b922189a43135d83596a8aaa6d9e76b599722e37e70145f5cefafb91a334019f73f5f3c8bd177094d83b70356d95d5b2b8727017ef6de3e7a66065bc5b5b1c4d5b11169e9cdfd85bbb11ab8e40f994806b729e03944d2c35338cce2f28785cf32b89ceb2c1fe187d96108d718fffdf4d8242d8c1bcef9920c4f8dd30a558decd6df7ddb2db986e10019f20ab83005bae661b4869586ae206983c2f9c4dd7caab607203fb1df69dda358506284100ddf02972f596e0097a406767dd33dd128c5a8ea3ede9897a1626e2e9d9aa76f731ba1372d8845cbe8f0e872ab0e4d3db51b1583167cac6a1a73e12915e7f608991d96f62b40d25f272a5ad2096cf77a13fd1d5785e256271e6cdfeae0833e2afedcfdc724a93249bd6fb0a03aefcfadf3ac3477e327ce3ce0d3d034670546d5e0eb0c1594e405b63aee37c300dbf1f4f0e70013cbe440a26a96bc2d950a81e1f5a9b8c477825386d2cfbf1fedc238e4d511088bddd54a8c4d8116a0dcc5dbaca58862f66c8fe48ea376c2a41eea458c64dba6411e52c3f6e879d591c5885c9eb0138c859620f15bda6d024ea3d0e0352050b6c2ada3b82c702468e200ba70f7f3639646386f07bfb05f7fa267a78c83158f0d112cff5b729287a361a82240baf47b7bb72b0019c2ea5165813227e051b306069ae27eeb70ca68af5c241ffafae3d590e728f060967c21fcfe7dc4edca57c088ae4e597c8fd3a9fff4b9c2ab0cadaa9bf39c18cb8e5204aa5353d7fbb480a9d5499244bcacf8a072c65a6e00b5f877b1172a7104b18ac930d8daca33a63e970fb7db554adeb74bfeeb0af4b952c95add0729753a3245194f8bb1b11e7f5d027ae02afa2f4a96cb15eff85f06451a08b7572c07a2be4d389c94895ce1bd2cf818be3f57317480c8f349661be19b3a83d06729e384c9235539c38a0c4816e5402cb5f3bc45c28798aab2ba4e054d12f57491b348823f21fe294f88ea8c88783fdb4fe24aae76f92d30200030ccfa88182c9725786ca9d808f7971b4ebe23ee84126f9fb603422364c52a772156057b344f272839d0a027e2945155023dbb5497ede92a22216e310cf25692925de23fd7954727348373d32ec951306bb5f1e8dbac4fc15224b6aadffa980ba85ce9cdf89c130c01f072cbdaa8b6cdb670a37ab0f7a253552b5b015bd1ec94488553609fecc2988322e8e58c1e7b2698603441afd372dbcf49dc24510ae000d64fdabce481c29b71dc999afa702968ae6533cb0e58965cca99cf82f9b7ea7ff590a5e0997d772283fbaf9e6e1da0e1a6d1929e4b158a0a9880f17bf386262b65fdb9d39e94e72f0047b261e4c120fdeaef359a34d9c6a3b480b5ec9698c52c7d28f6f7e3b5372780e81540b69dc6c595cdd4f1dea2af3bb14c63bbb19da8b99be434545d3fc72c9594c4fb3716f5f6b4f6f4fa87e2474271ca4ee6c637c484a9576552f0405729796b69cca8084de3eb2b93c4c593a539fd03806f0274eef6a477edb7e4255472df2af838c32b485576dd9c21b40635f77bc15648cbdb4731b2e668e7e9a8a0879d48045ee8720ae6222d7bdd947ab69352004ab569c2eda7f85bda59426821d042b346190f1a763c2b07d4253e56beeb84bbe230a8d6d8a8c2ba9dbc39e2e41d4d99eb04d9589fa455cc2fb1217df39ed294c232c39d9c1e5cd6c266b97d11d279f4fbb5e08a1778d78f21c89a929059ad4dce1b9f21bf2c50c19bf12cffc44621b15ed23cffb0f520d90a05ca957268e11aa22a189c9a7e74fb542b398d950bf7cda900e914373628d91ae9bac3d375e25824150509a00a754ae17d1eb8c72324658ccbdbc8d281b304c3c43838b9f7b13261b67f1a76ac2a8bff0310ce8723bb814d8b33eb5d6634a766ab087e1b596528a52388fa4330d39cebbbe589e7201da3cbd065385701fed758037b3f64526f517d7ea0cc7ccd079b8f959a55a729429bcd84134a2966363aef5a7bc55fa356d51ff502c5a241f9ea06e723cab72ac7e102402c478bc06d58e8430ecf309cbd1b7b6e90c63faa5b8b064d6da12213fef5a0294d197d85aebf99ec15494c3459758badc91e73ba89629f4dff5a06b0e4cc1e4c1a74df34e648c202f9bec8d89b8f486fe808d5e6bc01f1775860e72a07bc7fd33143c28b3934ea849bea3f358ee7c23524b9b5235cc8b88bac24b4515e41ec65737f2a29be46a65fc8e320069059863ac899e08f0f6727d08829072bca3b88834aa82c87bf08f2eb6c1615c077682e27d5261ca20cd6419c760eb3357e7bd93b0a09d477e98d267643cd1a770a6abc245be6f702e01d8bf14fa6f2fd2306de9415059d6ded67fbdba2ef92da4212cfb2d67bb84a57ec262e56ab972f9669d83f71088a4a18309243a12991808168356435e9491955914da66ab12445eb68561347fb4190d8d745c88f90e6597287cdce9fe4f95cc0751713001442fe57a8d5e7777bd7053b8a7a6b99898cab45b4f0dea915fc1fc6894d5deb8864afb3d60da105ff72e242b617fad140ec7c4acfc6526e88369f238dd2739018f0780f09699b2044f9ddb9981bb3d9dc467060346f6aa4993219523c772f2c5f723dea9d0f28ce961329db09d84ebce34653ea05e572e0d81b6a74e6279aba3026309bdfdb10e7bc8793d53cbf26c4f9cfc4fb877e10e00864ef596b313d25df672f5b1cc4c2d71c7cfdddbbb2db58ca2b436849bc94b47f5dde29f9fb08dff8f72727b633a3ea6d8cfb3011ad5c20ddf886ca78d0d026c3b90ac3d92a1e38f2b72bb401daf18c8c2d224e39f7220a8f83d72a6eb7c0dda67d08a0264d17164737206465335eeae9b284dee636e82dae893b55fdc207b7c15e9161c13987e8c92707033b06129bc4daf2314aaa16d9b111835528e0756138bac2e615f614f46723ae222c15d48a0ba68567612a158368be4309b657a63afe585ff298ca19851e2724437b46c3fd3cc3d198b6299c429b199aeae15988a4f67a756ac3e956a53574c1e95b102f906e687761fa67ab21f57ac94df6cf2f809ebad7578b6b1959e7c728b7f7f42a85a8b5c85067cf60ecf915210902d19ead225547ed6ffd169a1a072918bf93ce9b6a900b2ec70605949e7b9a832676ce9143824d2cb20790be63f5378886da09ca47b5a817b4d6ee576e9b23e856ff8d264176037f513f6a3b16372d40b05715a01e924e161ed4372514fb85b83c68913678bec7d6ac207a60d3216b9e3f14f1cc505120a3687142cae4c28f3656590d734d59ab79f3977406ed072b0230f59a3778652cbb0a3c76f1614c378936ab7de03d3f98ee7c424c0ad7c0a11be192365d71ce22e204c4fd2c5eca1a3fdc70db5d3e722ec32e08c96c7f61175c4b462fa12b8933c8e7688a12f9e88abfa0c73d55f54c2f0f272840b644b6c1c571b68459228a92c6732019a5cc5fe4f81057fa35f6a2ad15a86b7df7f9f1678fad3fbfb7becca46eb184785808c45e935adfd9a0a0af616f1ab0fa6239c1526398855bbf9611c174615ac95285ecc220ffa3e47e42685e06f2a49a218f63019381de3859a54912285636e0b6b51cf0f238fb98ac941a40f80550b9a464072fcc5af63273ea34b4596522d6469f98f1191603942f12fd9a460bc98b48988729f475696ee4b674c31f1c5634808a62edfb0aeab7c029686fbc36df0219cdd16744bde1f1e05955cec4521818c420102e708a631da77ceb50b81a253d27a2972e8319ef7a30428745ef752f681bc1e81ce829b4db7b8f9f50cf5b2b4c1694572f1cc6483fefa30b92adb92f2f4836da43d1380bca4704b8452447743243068723aeedf58b599f70cb30384d0b911f48874869051ac1ecad916f156b208dc9a147fe3d61f2d1563bffa97b83cffa9afcb00bd2a638dd852964d113b080de3e63deb43fc38c0d0ebb144ef330a9c2d944174f582dd5f1678f714ced58bd08b9454d85a6e7738210b203c2ede2c5cbb3b7a0b8d9bfe53653d647777e6e0187efd72bc7fb21ba7a269332fa36ac24944da808ba33faa046f652936dc5a9ca0e7196cf2a2d6f044c25b65952541c008dd1f5b6ee910da54c92b41fad1723193fd0f6a399ef7dc70e749aec567c7b8b642d1b4df88d32f26186f31c68ab514651c83187497c9653858f1756c15f4c18b3888529e8e826c2290da004c86c7f065103d5970bf7a9863eb117867d8adeef85f73db82894cd4b3f31ae2ec88c07a05fd387256880b697bd9dc3ecb80932ad8bc896fa7e45c1e9b929b884cf718ed3a8bc8724a24e1ad58f4ee53649497cc64994789458993015e4415c494caa517e153ab0412e2be16f4cbec8fe3c94c8d6cbe31dda954c949661bbc8dae0f1c414ce73f727e9619aa0f9b379bc0b68412a75ac839f347338e9b8f4764c2d6c8783070b872077fd2f9de59f6550a8e90659865f6bfc47e5e5f2cd66e11f59c13ff055abb66fda48ea237b223128981464fe68017b6b8b7340b4e6513351996cfe581c7d95477f3b934d2cde6ab3fbc4ed95754e320bc27592ba58280ec9621af9b8971b57240ae3cb072d56ed765760552c706cb13636dc856c90d0d70cf8c8cca083cf072d3f517d1932c903f26e2b71c7d3ccc0200b9b8b2ff01cbe5e60929fbf8730c7278de254fe75c67edc3c730e0dca67b725c484019bab263e8c891bf357a038543fd077c92feb1d1ce56331e951dcfdb739e37494d336c2180967b76de764b4572e897996eddd296dd5725e09e69d2e08807dd1570d0ec06d862b1ecb6279c5b4c012fe95c8b30198cc922c46f580d23c36a5b1766afc402265f4e72c96dc65431fae2e28fa7d008b09399aebaf9d834a646de52bb630f56e7e3e16cabf562866bec2457c15ea0a664837e83157b5e402e4005f62ed9761a87796197c4197f541c654b150135e5ca277e05b80ce69c435d0764ac9d4e00198fcacd1dd8dd9d5b5861559b86f8f5b0956813cc06c1bdb84e892ecd2702fa1b6e7d51d8d4774a3e722d0f41c7a0510950623ab326678b2d2a4f4753e4541636994973d33554db5872dddc0ae09f57e7fdc50a31e782d26deac13996b0781094a709a1f1603082276c4489d4c90f955cd04d6310cd14af3db7fe831388de20ce1a5b14d9fa4fca6472ab303ce49715ae01cd988c4aa2980a31ae8012d7137370fab96a09c4a5104d2186973b234e56e2660d7b76f7fc0278744721b416290c3548786e71f51444b07222779249c876b2b720ee18aa78e03bd87d22718c91a13ba7fe8d06d4bde67c67ad0e3551128faab20a20a3bb920086b46e1b47a6971421b70f2f91e044a339553d8bd9ac8183b8a623fec3327dbb3db347c34d16024a03e67b51b88438a5a27217b894ddc2ea25e897e23915aae685d51c08d99186e4d8ea445ee94cc7e81b7286f521b9791798137af3b182a8a7974a575d43f9ad570a95eab97f2d4dd4b172ae91d06f5feaeeacac5c02e565235fbe6e9e9e2d71f0948a41cf97c9b885e46134d15a5f54904fe56ee3785426777510be6f2eba04a3b4c26972145e77d7b07234596cbcb0c4e28e686b8bef01f9c994a8dffbae077f89fcc2e05a846e00d0724c07c0f32a502cde46079244e79d227e694b741c1782e6c668fd37760e74a272d5360453d24f971f0edf418d1bdfa4ee1c38180a2b23be2253d40c7b1f568c72c6912a252523fd79801c03b05d9ab026af8c1aa886fb062697ddc5ac6bf76a7219977b76a6b48bc892ba9ac596216ac64c5f13cf329cdda97f2a47bec268f4723fc1307e0d1bff5b85b1a9166a59098a824cb452955e494e167727b1ac91133b327e097f433e11d3cc9190b8b13ef551458e73dec8ea8ebffaa203c63b375649eec3ac8c6cb34865e454b26669c9e90c33d48e374a035dd81ee8895485971e72457cc93c270a04d5911d7ed92b76a697d311c8567b7afb5fae620e248713a272d6ebf10f622c166e54283f36bd2111f1e24ae22123777f88c3ec88ecc6b473720d3ed8e9a2d02768f6f7d4b46ff937ab275ba232fe8cfe106479702f4f7b5a72550f38ca7af7c4941524fd7d29e0aacce31e4b23d6123b18f70500e146b35800797eed6094b288672003364e517906ea86716d3362391b987d2df83789c335726d66bb9682857bc0e6b9c241f46426708197810a398e95f470aa2d3b5d3acd723356d3e1e674462db82e6a59633922b35a43fa78f975ad5ab5ea97bcfe9ba71eb8ccb6d550b667e26356c2321fdaa821c5641b2d3265cc9a9057c03e10713a408c44c6b8a00df7cf9468e629a2b9aa93c70f9389f47766a78fa2f131ac8f5872da13da84631a728d7c886e4b8b841836995a3349343abbd2cbca8f00adf62138fda5c0287aa6fda0b23f9aad00892196b4c907e8d1497d773e331a9942500872e17e20c0273a1941b4d342487e01b019766f72a5e9e607cf2d529c848f17ca01f8c3ab5189e0c9aac941f7ece60b07094563c2e868ef69e8714e2642bfa950184c02dbff8e66dac6d29cca8a0084e8b23fc86913628ce6af9766f976df57533d493743ed253ea4d0173c7b7eadb25ce0bb7f537d911fa5fba368def63fcea47220def5eb452ec01dd7d9b00b8b4a51e80e907dacb1e76176dd2b23878ee3324ce2b34dff982f905a599e296b8047fcc0d7e2cf38d3b072d91fd90adb556f207298878d4b9218fe862fce595ff4a7c1ae8c1f37d76d2542b8e3e1666d77e2e2726bc4482c1f05fbb43e1ab833ad01ba1e9cde3d695be5cf86af62a4108dd8105c380c62354d02ff898371d8339c9a88c03e8fc55128b947c08a5c2f6316a82c72fa85e77f07e3992b0b0a38a0467169c291af132bb0b2d868fae89a4594e3c872d0c7f55115bc45f5ea6c40dea70e6969e066739bf50cf2c6549c0ed6bc23864878a3d1a393ab42304c46aaee4d9579465f552e2f071b816d9119d60cd1870335749b52f2b768e2661168a6a555d7876eb139b4b69a5b629a9bf3354813a28372659823692e8694914e83a92cfd54dbb33368b4f7cc02481987780d969224e872c60ce06bdd3cf6e8a33f5d0a27ac11260bd32fefb807e98284abc1d8356aa45fbc7db8fc214fb4c4f338715fe659dd406952748c3662bd474c3b500578459071176d9a4f3c73602c1881f3f0f611ceec3556f1863f259ff1f8d82a32b6a2261c3212eb00ac64fa474a0277e1f78b9b40f942445aabb426ba074b74a6d5b1bc725cb6220362738e034bb507d913972d9fc898eddf04648e5d36921849de19bd725caddc054b68e74a5cdba436b7cf89d7cdb9e5521ac06a060495751e6ebcb87249511efec252ffdce8f81fe9fd3256aa676df7a07a5a5397ec751e906f728b2c351621a2fa715d5bec890f3d902a27abc226a45efff5a14363050cc3f8da613f4665320760939c3b29af76d46a5ac26af285a83d6c26f0d9825a05970c9cca72a8036a76ded244a556e58fbf2afebdbab16fc4a3238a07c84869790e09228c7236e9d9ba479f59572d258a3eed96deee3303a6e85faaa77126aa5790be495a722dc4816ad0b0b59b4a3e9640bf6a7babfd0e65d2886cc33a2a81902c57dc212ecc64cbe928be29d8b5286fe52662b940d57f727a22760120e274428b81c952727636203c0fd31f343781be455caf12f5d99e6010b84f3a8a8360cffd383f2c35d6926d6254f6f52c88c437b70fc3250faca7534d0a63bb5b5a8c62634902b159720d954a61132e2a2e3c80b03dc729451b5b591b96fb9dd5832c81bf455e68722e3ec728beead9b2280f17beebcf93fb211919084444c52ac0c15809a39b0f669dd9a4ab83664e3875e494da990dab91fec214809dcbe04b0db378dcc7aa30727bb7989905184c6be1dd699f2f6fcc7f798f94686fdf70b15d604634a73b59462c89ba7e86824ed2516f7d2549cfc76d4556afc068faac2a45fa8db2f100074c6dd8666fa35092dcfe602723d6cc42b2049fe0409f0e396322603c7f7621de1405c3e4901ac772eb2f386ad0ccc9a80f6e088dfd7f79818df6004c7858dfa772e408b20e714a5135b5c940c8cecfb4081baf708c23ff17ae676b9b7875b182037ea46e225d1006cac79023533657bc4f011f894a865910f3819f0f885b796972240f1170facaf42ecfebf6bdbd7f675e35debdf532494cc81dcaed7c3b80f836d80a8cc5afd426d9d823e51372e2030b3c8f5ef1a17e25d1a0ccfeb50b7ea672211bd7a535531f78b9abddaa8e6d8614f48b2d264afada6c892f35c0c4876f72efd9fe6105d25fa0a3852dd052ceb97efd6e26006b7036bb4e92e94a7ad28772cef2e9d538c865c5d497f45e6b62637068d8e189c0cc2c84bd1bc049b3fec97211ac16b58f656f2b6a4573266152cd5cacc5a0f090bc136501fbcbe60c6dbf7236aa75d2d335d2476307f1cfd2ab578dbc397a8dc8f2d8911161c6717db81072ba9a7cc91cae0a61f34ba5eab704dc2fbe184a5bba5b6367b23e3eaba30fb27218fe00cd664e0bc0759522a2fc1462f3a97708de27ffba111ffe50694236825c0c0fd351f0bdcdbdfde143d968cb7f089f9254feda10e02a8fc65e2630e8ed153ecf8ce5f327091f37b05a4b4bdb168228dd3b89b85b7c9f8a640f952051480ffc1081661d5d406cdf4a56ce2436397b6134d10fb5a7d43f8c5b6fe7c5495e720aa384d6028457ab8f18e57d20a9a7716a5478c10575a2457fa77be0c8a0ee7212620ee8a29574c9f03f784bb5816ce73b5f22ab4241d559e50db3e29519b472ef0b47416bf9a9bfe995f04afa2862e256b99fec906f4b0efc4ff8459c7c156d3fe25399f49a10a91fe315841f5bf052e8d9420df27cc0f7dcb36fa51b5cd8721805f87b81cdf52d1283698652db8752f24c8dd75ef3b90e37c8d230ac54bd72fd373e0e9906aa83a0c3416933ceea9aa0ef43cdf1958f53c4c88d7c6917be72d1188bb7c5bd2a36c9ad8c1fff32eb4a9505543747cc581b47ead537027cf872eee9eb048093016f0725b3cc34ab15305df021301bb88c2a74e8cf7bba09fc384b3525ed925aafe86eb4ffe3c98bf4d12398c02c10fda6799ffd470c7a3994118297db24ca6fc589d845e45bfd13902b51502ea149f9e09e12eb2c6a249bca72142ade26ef8e6c978c7fed58163d05fbef551f72df3b84237ada7053a4ecc6207af9c5d08cb7290d9b293584c99b45c00d86aab077a6aeb8d049ce80ab0930205fd48ea163f83316021a3dd902d0d8dd7996d2c893325129a25da20068f99572348ee08b913cbdbd02ee257edad3eebbcdef8d8ccae99d0134fdc799579c802249a0cb71b80481e8ddb7150c0b37fb1838f4811053c14f2496eb3180f9c08772e3f4f97e9591785be3c8575fc550c07b16a12563d03a054ef1e2531e52866f72e654388fc3ef6e4ad310078540bfe80b1d02133cf03651894702fac72f1296362692b2fefc8b062a8c537d52aa968dd2535e3b94bebcd2707d321c040cc19472c49bf22f9c275747717ee733104b4ecf6d5cd740a2b67304603b5bf2b59d9970e4d3ac00c4e7085126df2056352aaa7b4c6f3c8159302a141c3e39dc8d7957723853b0d69a3ec97567dcdb6522ac1dfbd5f2afd2927c8364f1cec38c3567a972726d67a351071c4113f853f06a738e8090bccc4f3545bc4122e6eac11f9d96720322e6a217690be2e647039d7b08984c517932a777ebe4713b623fe6598ceb723093094eccd6cdf0c3fea3ac21672b10a04a835eef291af002cb5a9549dd3372bf5ae092e3c57571196e01f4d8f170f2764fc9ff7ebf8ea061da8b885a232f59df664607cc2f074907c640206e0b3cf892fb63e62dda883bd5926ee79a149d57a38dac00f5ad19c4c62d2ec3a899d21f4d7133d15505973041f301e703d82872f3d349fea7b15f7a0e700f70fd40b50504110b4eb0285be0e4a1ec995ad4a23e77fa6b3abe30bca687aa241d814d5a111d9fded7a75817a730feb787fd975472771f366ec792ec9440fb63413a5051b03bd434482ce68d6e40667445530b1b7286af88036df58e5e0d88d9d18261d1af420301c2fc2bb770fc7224e5a9f8c4729bf5623eed42e6a562f788ea8604aca147f5de422d7bc890ea2ce4ba5895000bb55b1267b0d894cac1f1e1d0e718d8ea5d53b51483d81fbd6a14f8302531c872098633f4b94516062c70cd136ece5b2f2e338548f635da45a95e39d34fca5b41389ced505377568c68f8def3d04f49d33565eaaaf74372ac97063740787a9b38de2f4b14b8fc881fea8a51b88a44c47da95d6d089c6f6d38d2a24327c7a23b729ef07d4228c2f401d4fa3ef9c378d8028438ad7a50aa8c5c24a79132a3045272936ab2b755bf492def7b27c5dccdc0fc23098e801cba3bcfcd345dd858a63d0db3c95807867b75ff0c404903a3baf9ccae375a770dd5758ed59bd213c0ccea7274ce0147bbf68d7d093af476b3cb26ecdbe559a9d9a81c1d063a2226d01c2c0bc2ab5f2d2ae82e1d05c153697710642d06fe1dce6da034d01e774093af3dc35406673d232cfb530213dfc57cf7c0258fe6cee76e6c3480fba5ec45936d5a3972b790cd3018c40c5a17d76f3de713b52e81d505b42e354990895d5fcf28063e727c633f89a0b9ed3cc16b2ec25d0f3d5055145b07dcd691305fc84757f7552572d23728c1b3afe41ac3174a15899d318cd1e13e963fec28155527e451d4599672bc2fc3e08de6ca47c4078bf375f7d11d9d3f682ea0d77ff0263e8ab93b904072d8c31054a119464f6f5d0852a708110f8334646087f83d6c750f32f584223a594edf1c075b875dfa97832b17271da2a19fc1f9845db866da72a010c83fd19a7249083cd6e97f04e5f028a7a13a9048133fb69ab43425ec7fb56b5e738b1fcb726cc0ac6c28b9de9a65d35a50f30fb412e9669affd2733379cb5a029e55643434ed618b02799805bb7e6b2378bede37e9aa6fb2e72cc88098005b737330ca6f72f3a1f825d6fd354401f8688efc68bbe7a0544d135e6acd1cc1d63c15e3808a726ae9d8cc34436085aea97d62c2dec0461e586133a6b886408689a65c294feb72a809f3b89886e3aa41173fd32e01f9ec2388daad47e201d89e113afc8ffd3e724b13b7cc507467b79fddb7acf66f626a06ed5f227344fa27726c21a093381a6ec63a9eda5c06532ba34eca04fe78dc1395057ac8c00baad3e237bdae686525723f56770eaeb4a8ba6fa4d6c1d3087e3063048824db1a98d83b5bf5785d36dc18eb5f2855dd50eb8016642e9add1729d129e7f24658b860c6ef4f73a1ae300e725d0c7f0444308d64c846a745d44add5fd09fac1a2f7c0a2f3dccd67a5cebb372f4c6b5d71e19f5dd24b8c09f74563f6c14ed561a4c5dab1313cd8843f6064929d29b27532347778523ab5c30c28378062e5665e9f0fd105e6ed5e05ca25cd5725c371709c658a263fe9218d692501903a2344dec3847b95497990d40050aad69eeeb92f17c304016c02bff7b3d483fa9942b6fe0dbab227112cc137d0c85de4f22465868b3712078c92cc79695c02523a2aae335b8e12340cb8950a3406db8724858a50025225562abb3261b158f3a48677e3381cd39cab8bc558889dfbfe272f894ef1f2cf81aac50e604c66292d3b0334e4ed3b476253997755eb85ea427722480868816f0d2756f354d3d2cac034a21eb9e53740f24f50ce0032b3441a210d60ebd244295464b614b0ba4c1be210180ab204c12debf1d1aa7b7dc2dc3fc72b04405ae093887cb6b4665541383b8f89bbc225372a3fed36c5b1a0d356de52aba222ebefcf85f4e90087af4e7787d7a34e5300fa5176a344d6ca2d0da1d321b940a647c4771d9f32898c0b5f443a96bac576bb5986d2ffb6c8d5e643a8b356ae83e43bddfbb07654802402d39e2d8de218502c406b1a79a186795d664353f65ec982b3b06a255518bb00fd50c2c3ae0a90a21643f274c45764a50dd2e7b6b726884b5789da2d0a9a24fde3a254789902667e4a3543a7659dca1dcb252197b723e503f6d6a922f158b4ca0e11c0576493085cd9468ae36b779a58a699ba2152d8c66bee448fe457228a160c0bc9beb9bb777b0e7d18e6b7e1f39f2b34025466d2cdef32895833a3432e079ad28f6ef54aeead6c45a77986c56cee30e399b7432e7572a490ce7d24e30e081e210c29e39ed8cdf35965e4bb070df49e513aa5b6bd5d8038943655a469dff2c24ea44129286b74e7dc9baec85d44bc3d09dc63172897456263df8f81efa7907dea8c711566ff2627521ead8c03095dccf7036b10dff45591cae1d3a99ca93d8dbc187727d0615eeb28a20ab5da048effd9a52f2724ae78541182c36002a64858f27a5dc0a4862083ff30dd34da582b69c6cac9b7204bc4bbd3769bf2838a033d602df0a510f0c209597f3c91c121892a6d3a41d72c2db48142f65570b42ba09b5c76a9920ed1b3336d176cd8377764a9413ac65318c4844c305bc771142033a0bb1034ccd412351c3d04aaec64fb28f4ba651f10025110119e53facd5066fde87e6d4fc71f6976acbfeab703a507218129897db0beac2fe424d90a3ec5eeb65a2c759e7ec3be1a86657003b756338013c7811f32b0da753fcc4fce27bc10f81252a6414da0be6fe251498423b17a1a67361eca86ee8f44bd5854d4605925ab25534a379e2e7982e4eed5e30338ca6a90a90aea0722e95983bd11af9437cba5b372dd650e2ba4906b276a43d13f262cb3f71fe217018f2a51998e48c5d1917d71435bfc6647a650565c52e69c04414546305e9e4726bcae708ae2f6e8e6135f9e1fe52e1052ba2f1291c2cd0781ecb5f41cf80251b1b49ad727cdfd3e6ef8e6f069cfdb8f6d1ac2918c4129898121a82b6505ef4727cca89d71a7fad0cf40df97f3503cab003ef72dfe3b1a652aa3ccf0b07a3fa3050bc6483fb3503460a37a0f3a2f48156b0d541e74a6281d21369781767368d2d4a7f10c60aa0d2adef9c64f7b1be27a9e155a3c6a6599197b358116143755c72671faad420f6c2a3275f666d7add392ab5427dacfe44508ee07aa5225e19c433a38fda6c63bc221d943816b20cec10dcc7d68348325852b457995c7470b45a72decc460c1e8108824ca0099bb7cba379d06e05c11de80293129f027e874a564293a33cb48cf5f4dcfe2e0687a43685d276d09fc6839736f233e4b81fa8066c3f871ba06552cc9c31be11f5ba78ba046bdfc5a793276205c75dcee2c83a294d724b3c4847f6145d28afda9c601e195ce9d76c3d1d274f136a0610be6827d42b1fba336918740c3aeb6cf8b433d6a07eed413ae9bba728645713e7a8c5161b4d7241bea62a9fa70f6018142764c028ce010654dcb40bdbd7a0e5b60a2cc24cfd7204c6847f74fecc25ee3ccb4801220c1bfcd527295035d8857337eb3c71225872b07f091c9c78efa814dff4f3bfdf7ab3aa0e9b8fdbebb40b8fd2fbfee7f4407227b601e793814f9517992d188798753fc4837212b76c1593c3e723477f9e3b709a2f3da5f9eb5d2b303fe86571b0f1ec61c291f9cd9296452bc5448f9399983c06dffa44cd371b81c80dd0786b710da0894609863953be1e6d11a09746c03372efe18e76f11bf1ad053c78262b3788eef3b877c90454806567559a9db561a70e230005f4eb0a9592adcd5535adb97a77b4c2d1704c9e1c59b3f344d577e8712e910b2e73643db3afc13848251f6dac06e54094b7ff65a5a434935a59a4a4d372c9f4402aae0357029dc7ef26245ba25e1039da418451668a5915d695b79ec73c21c1f869eb35ca407c268eb3ff3cf11fd07d809ec182de76dbde0bfd4d948072c53aa1826cd50873f15b799628e1d24614f86a43dd10c0173112b6d0741649727e2d120bf610cb829c0b4cb190374271222e32cfc42f355626eaef85755f38729fff6ac117467637fcb3daf2e44466a699d7932b797f5fd89ec3a85f20c59a7218e992cd4deeec1657295a88b4b472265327228d219962854449d282a1d38a7297d7bd98b893056884439760b5cd2d9b3a5d1f36c010261f94600ddae9df827293619efa58a41e7baafe669a7c7118e26db08fc25a36a6e382edd615d39cfb7226a4384a32615243e17c8a33d97172473a48d498cbe2095742d3ca6e7adab872a354cb94837e69a167929ecba125b8e1f65b2f1751a157bb60702ac4d18e1272ea79a1ec12bec9a74b0fe551831f43d24194fdd31cd7bdcd294e0d6a2817e1725f45e24f85d087f2aec3dcd4f23c77c5624fb13983600dcf957960790b148372933df6a66c24169f107c5964f91bd44d700ede82865d203f6b28cbdca8a3f2728b5b73d8b84543d63729a96c0b7b60640902cb7aefca3706fa87d912371bf707f308286461001797ab725e03bf84957ab1989cd54cc4dd336a7d33da6606533ca532445eaef61587a4f5cb33dd4118fc4cfb720d1227f3a833acfe2940b2c4728c832ff8b99b09d7ab9ab686666931e97d27ccdad7b4a50a8a6621775522fe63b3baa15adf0e753eeb306c0ff6575d3949f8ee539637823e17aea07aefeae4019de982fb3ce7a305c5ee6ca186eb9a8e94c43158ca35d5aaa1741bbe11159972bee322b4848118c57f6248374f3ae9cce08e687d66f80784a56523af9f496d5843cd88203bf276402b44e9ecc99f9c720cca214e881b7f1661bd207bd90eee72851f6e8fef3ab3e2bb0a95d79818fc2e2be7e288a4b4e99eda5b7a2f231a7420b6f0d0e930d0c13077fdc2b3057dbf7b405d9c17d471aa5f135fa240ab81b272c98d7270ae8cfe47fd68bb5e5654be6e8f9b2fbf90e8d15e673886d2c0c74772475f69e426bd504c1e9bfd3a256a9618e471cffe15811c6e441fee21f800eb72af3cf589d87999ef4f01e9657e850105a22458e0125380b0b5de106c3ade173b436cef6242528a07a74cd56d48793143e097ae2d8c14ee33382290129cb61c72d52d2608548d909397e04fc9565a0e120b23c579ac7855902b37fb9cbfe8b233a1efced73bea05a5ae79ad2a6c6110d48c343b6296995a106183fdc950a0115a78281c34cc5543664e16002ce9547a3741e7dc05e95d525ba9bc7cf71e896d72e89f278a10c000c51e8b2fcd0c3e075489ad7c9db5bbd3eef706026f58b49072fb13e52ce8d0559ac52333c68bac94132ae6ced45733ac78816068de156a8072c1a0382612865873c56408ad43a42f78f6e4f905bb0960d8dac2f03bc7e09272dc68e4af432c9778020835d781a312f7007429e7892ef68ee35ef936dd0b3d72e1125e06ef1b14ec08674ca1d8621537d142f4f2297799c0b613217080b80772877892409278a8a8866c6059ace4f062863870f189784b78663ca3ae3e8fb13477ff7e113db088ab5f815f50648dea0ffe3613e0cbc390651e2082a420f5fd3614ce7cc500e65fb3deea57a574bc22d9b47274a3d4323b4e5af2ece7cca00c72d978bd5db8b0081758ad084b035773cb2c19be7dc351fb18c192be76e9f3f23d4458a22639735b0604fb464bb516fe6c74683227c2786acb2c0304c36527343b2c5130c079d50261673235cd49cf80ad77c1467b1ccaaa45cfc32c25120fae72aa8f34b7b27d7d01af35e042b99700cd84ac72ca64a2eb799144ee7cfefc6572286d7dce43b45d52894dfccb727a215684fd98a33385ef5c682a8098037fa47235067f6c3e65f570b21e478af90b020f72d20605486bc22fec1fc5c39a68fc0612efef545c57e99f93b102af3f43e05a14e96c4f9145ac18ba1d53724126a27243b34ed607f495da3ffd2af238f488ef3dede0c5e5fa4ef53aeaa94f433930725beaad40304dee617d7bc91e9e1b1b29d9577b66e83bbe4eda57e0fb9485ab64f1f5cee3c65c157aa6c2ed88ee5ccf95d4ce98269927d336a88fd11fd6e87a24d9b154e1bc64149161055a5be18a8fc08cb05a1a18e21aa62ba4f1b7a3016f5f2c061ea054a5d6bf836923f0bac6b87322740554695c1bd012e5b3ddaeff737218927d316aae456452ab2b17aa0a2b77105b32515cff2e3ccfff98a2dcd64172669d939410244bd0db230c4dfca2805a1370f33b70573830cb2b49e3e12680600fa25f091b859344be3abdef59e6da2f7b0ab89a4850b5ef116b2eeb5d57d8721d7bf6e1a7c9fd48ac8bbb2b3077653ac25e33d69e568a63a91b63c06f3192041d3d7a71b84c8e878f8d146a83d5c5ce8ac422437ff4885b61eb9318aa74fd72348fb63cb72236b4675cb91cc59838ee9ae820e05a89a018192194395d19611223e9878a3e322c73d50b3e88256853b7241d72b0bbad3f01dacebade52b90572d5b4be3600c65987c2212e2b70a4a8a1b6b472f189476994af0d4cda8556a37238a10791a0a406cb90795fd87b3aa204038b07454c75785ae338623d94a8722ae48c67e144055e572c6ff1bade47277450c23d5f55d8eb7553f250110866a36887c70a7ad030a2fad658ec316338835b1d3354b751421aea9d817460f69c657250086274f4eb449f4d661e3a7daa33e864bccd376e5780651329f46123358572ea439850783f41ee0d013c27640b3d23e3e36fe930f9edd47a925afa05c2ed2586c96b8ea1a5538dd1f6731416f54f654ecc9948f810fd453d65b93c3be49a54967382537da43e8ede67c8d08b4f1ecbce77763e3c764509ec1d63b3cc2b5872d452741f8c8fe0ad51f7fdee8411c7dadf79d9bc49dd048beade6846790ea47210bc74076d5be5d5bab4fae2611f9ba3fa21f326132a50d8a219c5137fb8997263cf904136c115cd447de413f3c12ff35204979da2c68da68b88885f344f147200d6e48ccee7d43c9358e81b4035a6eba6dcf860b6d6f0b32e4b5343887b3f7262dfc4a34a86b555049e6692b2aa1ad20a20df1fc828e82744b481eb74753d5d67a6ac40a9f793ad26d021446761d0194b3f900713c4fb87e8ffb59ba586f30b8f2cef2065bcbf3a2532bced68834a731b7b1cd8077ba39b21eb589717d1b3482528d844b87aa0c2406ccc818db42db9b5fbf8d317dd70483d47d29013cbea725bd0ce100a0c536033cdd60059b2cb37e94e25ecfab4957062960afc90d60172bd01924965c487ca61e548000d505de3bf2e0ca6b1fede7ce46c326e9004fa7215df5a788fa4273858ee907a092b8ac3c497f5400b1875050d60836fe8b4507291033cc0fd003865e7b67e08521ac46e230d079aa5efaf33e224cf267678dc176890c027dee110cb981bf154c24d70e31ba6b0de7cd33db031024924c86fae72bf86d1e2069b242434700cdac4068fac737d6f0ad0148b61113dbfa92b06ab72138ca3f7b9dbac50c57bf158510ee03e5963e58a5f86415fc42a0355a62e420f7f964cc0d7d937bfc16f93f95ff840e6129d65cedb88eddbbb230193c1cdda720548724c470c114f351d678d16c3fb942bb8210d39361a0d61be6dfb9c12ee2554f18fe98e3c00ece8e9b85bea74df63b678c4032ae2bf5c4e1568217ea99527ba1aa4e6e4daec38e79ce4e754be24ad7eb3a8a5a07a8e0098ca8d6dfbbfdb26b63d37e87810189044fe7e19c7da31a55f4ece64e58026b60d4d34250420d672117ccd4090cb28a43343b56aa14bedf6065429846f543a60391ef12c6c2e883dc4fb9626e1f738a0c34d6f91285664919e1ca2d692a77c4a7dfb912933a05e66ffe15520dd7be2a57899073b0f5fb4ac5293144102b133616a314ffee41cd905d20b5ab3c226c4b1b2ee5c603a97715e5816c50b0e8ad0a4b726c0bdee157372b6be1379e83c81a08e035780401cfdd8c8aa59b52c5cd03890aa677f6db41072d3a5c8d5f89fff33eea7d90f06ea3915bb336589d5be3a8495f6ec3e3ac1ae72962d3994a0c41d24e45bfadf7ddb6cef3575d2f1b416348c3fdb821a295b7d72e5bd52b7ae515ad6b8710f3715e5c336f79d08c5fc8f6d03e2d0c62b9808a372c6f30538ae503806607d4296bee4bd9dd55b8b5683b64f88a07b13a109a3ee6def2bcb52895e0fd094cbed07000a985160e958fe829a0d48cc2e3d0a78878b2677d52d24a6c3f8154c5711fb11f8050617e96e80c6e5c897cc245731a493f372b9148d315c576d833d67711b9235d937bb73241a2d7776c45c09144e92cc06720f4ee11674d263dd96c6a9558f218dd0fc52f669f53caf30c3bd8782cbe427726d66d58dc2717e23da01f51f63b9107c23e4fa25e6470bfd229347aab0f9ec7285d94543ab4dc03b5555f3de56d942527a332e539882c404d0106dec6d61df4f1d74403357d3f9ed313674827480951ef982cdb6018b97facfe9daf53524a022c92b196a94882f2710bab11e0cebdcac24a70d29434ff556db3dcb5231c96572a17d2024dea949e3e48404a89ed15835739641b1038217ef7bedd83b712fad5036fd1f9906f78a1cd6b3a12c052dedf2463c8307b7bb59a7622fc18fab813b579327f46a9e4149357b2563550ebb238726f379c22e69e53c4771f8e546207f727f74ba6af218dff99763f7f9284d6936bc22ffcefdee70afbd0d8f5d2f13e0720aa22f487d6a1f1a7d5c82408a12202b388bbef8d311fde6fd68ea4c458c601a2f1376bad1925e95e521e51bc732d707b7d751157018ca89373895693c606d7237d2895ee63c39002a62af1c76445bcdfa453994bed4642334ea33d63dc5687205654fbe56e72676fe255accc9be4ddd0102f4b39a6788b6fc1f8850d324e87287f343f41a86e829a80a986c0ab5ede14c2ccb82824f5b54fcdd278a6ca99a72d9563e08cce1f2234891b051201a7c41dc5312296f7e48d7d00a841863de7b72f9f6472500ce89b2b8571cec1e026f1a5e2cf090e5f1ea6480f2ae916cbaa672f9c2bb3d1590d9401055282d34cbd98aa97d5e59bb409ecff1225b17814dba46aab747ad62f0dc24e0f0121fa4ed24ae2b068e1e4401bad61dfab0d6420b47721b4f92d53165531d75125040bccf9296be924c90b8ce7765ae93c0b73316b872b375e3a4972ef13afc04d13d3817143d9501119fb383d9991e0f238d5f1a8e4ec74e57cc9bbb3d4ad648fac2a817a51edba6d2cb91f3f04275e25130eae9ee586303f5e3e0738a4e29996d22115bb7522bbe8eb2eb79e5334c5e0b822bcf2c72fc31b20fb6791af23b8d644357a78c4b953d3b7339699e6b87b052a5fb841b72edfe94e0c1712bed58dc3ba040992c400ee3282bd0326f4253e8ec6ce9161f152673f74683480a0cb9ea56f43b322fc2be98b91eb91eee89dd819783a248bf729325aabf066311ffd3822ddc8176edd71d412bff94ac0ad9b01b449d5650760db48c18c2105b9671f9fac1ad923ea15d41aa2f0a669241c2b17ceebd1750e52b7e5dce5eb889e246c239e635fee8b82ae83feda263b9b172e71d47f60b227372deb7b5f74328a2d76b472a632ba418be8241b632994d717736d358cde4393372ac2d1ebea4d18c4618956cbebe665ddd72e6f7dccacfe1929894926fd5fdd87209e23f2817fecbe1a99cea6e0b8a51786c26013942ae6b4a124a9820f05ede720c9d00886377b81285c50ca125ff99e2e096612c383112ed60c05be25ca0f172fd44d8575eeac41e7a691479682b6e3e883f6f13a22e3263ace591948d7f8347a9702fe23d74c8dc96a5d1885a85d74ec3a688e1d0f60918e9687bf9fd20f4684237aa2357ff6ec0f964f87385b099b4b2e1fdc60ec982bd145c07b801e1e766174cc8752c7bb96a0588bb388abc6fcc7f2499a0df8b63707f1444074742f0723df5f2acfd1d86c57e375b615e272e03d9cf81d7c7e5472f17f818eaa4e2264f6951210247d25b3d9d83bf3a91b9d5913ee5abd1636808df957e556f2b0f633e39ace6ec190ef0f9bb161d814aa96ab327af7ad3bbee766ec07fedc7b7ee491d9d5898ab39338313c1742ec8fce5e70ecaa97f355dc191fa583d2677410bb47240ee208ec599faa52a2d853df5464db745314d27ffe6c41bd45b7b9483195811354b54fc6113041438cfb51a753c5bf8df7af98cf68a941b703b4f8eb02e7c2f1b36a98c85ce6b3fa79928c032f86f8274b0cc82fd4e51652250ed115a5f0c64dd46e1fec06ca4b726f2aa5bcfd19b7629ccf7621d390da4419f25720fa91672b3a75d0140a4812b174ff2ae4aff035d1240e48e9fa43a6e559c7f54f48f684b843e6f8905dd57a4cd24036c8ea029538fc0fbb2f173c2791ec73416d204e20a19e4d10629d34697aa041017caecedb93199fb8140c9ea8f45b014757f09454733ac25f7a535bb669644f0010e42682784bbb9de37dcbc8dfb5c8e03f886aa2383dd77b64647ff3aabc471f1ce1ceed9223724589349adecfd170072412fa372bc290db3db7c6875b014c2d25151e5ea8ce8a2479a34a3204d2a59472d9d0a0fcd7e28b0d9749bee61bb1ba9d7fb97dccf2ea8863375934f02a3e18ca7cfc126fe0088a722b6f0f53a47cc99b62617aaf607358d182e304c221c2348f1b985728e71bf9a76424428f96bd0a84289d4a8965cf1d0ebead47d405e7850cdb1eb72c2d3df8a59b1c430cd7f02aa34812a74cc42e086ad81a6220af433047b281c72e2cc86e27f5412c6f3bbecc0caac46fe44879f05d029081e54dae77ccb8f09723212afbcbfc1b81a4b33da6721fbaa77355354a3bd6512eded2ab003c3a0f12c06024a0b6d826633d56d98f233c9fe533bb0bafcf39078d5192189032b2cba3a952a86bd9f080dc166eb7ff4b516d9f3096f59d2b1340dbcabb1b37ae10d9d02ff44f2c8d17b03412e8bd3705e1c7cbdff855a6cb99c6091d6dd09a0db951d7256fa8d390006c3bc775685dc15d5512ca4e64176bbab522264a647b295a19d7232ed5c7cc6726d6130361c575238e44d6c91c90233e25b02dba5148a378ca03d2733f9de2e134943aed0b53f94b8194daf1c35a3c235857ca4c86c6caedb144b66e21507791ce0314426ef60de23b9d3d5d1b6fbcacc62b819c0195af38df1397223ed9dd5538ec665c9a89a524420358447ab7b3a5e360686916efe24f6427246e2d2ddf6087c952ba57bf1c678385043db9570b2dd35c03fb09fa4c579c41c89c0effe523994533d6aa4f1e08ad440aba86b3ad63589af15d11cffecd6b9724919a4d36e13b45b7bf1899bfe0a390923d2d094e0c2c3cb1e761c69505db472bdb581dc6ea619f50e59bfa80b71ab6fc894d1d6b4f32084b7538ca711cd3372b18991948f4dc993955a739b78a0db5851f69cfc4eb080255a3eebcf5364a072aca1d85e335ec0e4088861894103cd48e3389a2f1668f3e74dbe227a26695472016f7f980e09b4055ef264d7fab88eafc80e76a74ca346cbeb37b25cf2428b72b6f61b3b744ba7a88e2aa54841ca662d6b4d28a0e3c047879d4caa4d75a822721b3b5aef04927d24b1df294473f3ae14f56d9b4de43c0a3d1e6e86d15dabcf721636c60d7d5ff2e67e693b7e5bdd9f449544cbf77aef0291f0e936482e20666da26cc1ab2bcffcbe34403377bf1a198e12de75169162be7a23b122e3c77a5c72f4bf36ad2f890773c8c9a46fac9798e8892fdde3f3fe1187a17531d6bca8c74ca25de509643a61f080cd02bbb93b524c4ff58648b58d6e58ffca15483e22ae31a9dbb34032de7fbcd881b128164b6015dcebc27ef40831eccc3e6234784268027c45a8d88553137f41f3800c8f53fc12ad05a71c3ba704f503fdcc5d6ecf68726e0939f78877d305410c5afe20c16f5c4936438d20b3ed62e8e36878cb900e1d5066dbb43b217bfa0f6cf7ad6e8aba46b87ca0ec78e8e3fbd9f0e6eb2ad06c7272ab17c1f9fc784774ad0a6937f2971d00f77709cff4ddcacc5fdbaa55a90a7293e8504591d239efeb46ce32641f2e42a5789bf3a74b4d7a7820bef0f9c8b372e6eaeb7e32b17351b8cd6e4ccfc48d86c5cf966932c4d8df55ec7767c8606f726dab3a9bb27a7e7dff51a1828a11ee9e5df67c1ef5561929aca6e05ee6cbee729f1b2f5e68d131548275771b41d62ef9c6ce0e0a16f25b3e5c9fade8e4778572a65e91663d07e2301c52bfb6734fc929dd66f18bc0c05c0aac446a791d07577255d86a49ced8defe54734d5f0e0a6c87f037dd6de88b97ca53147168f314947231d6cfe92a58d3841e2158418b638c705287c5fbba4adc2ca5f695d61fd36c49b75875356b09a5ded8c5840850c391d1eb78710391f1f64a86113ab79e7f3772d8b408f4cf5789fc51ed4411ef539ba2c77b5ac44319c5d01945934f2edc1f7297c5f4289d7bac9ed3adcb39108dc8bf436fb45c7306acfbf91f213412a3627240b5265e4a5d8951e9478bb020563a1a963ebb7fd947a9098d168456997aa272e95eba83651def9d2d21fd8823438663cfc83d3d9c7f9f433a320acf27dbee7263e5a6572ddf2ce9191c5f1ffa1a6cb2f50de18250915ec0b1628e83f06006728b5163d05519e2ff8cd6f80ab3787527d0ef89f8d6d1dc2b386664a5e111617289726a05b5cd3cdc56ae4914c1548ad5db1943a05dbf2f30eecee23ef6341b729139d3a5166813103f366f874f75efdbc6546c6193362b93e876d2feb89cfc187eaf3be7cfcf6925c7c56c70660dbc44d40c9abb9da7e157bfc87636bfdd3772dbd2978e45a87bbd777be5d670049ec115ef83fcab0cfd32304e22ee15d10c6bc161e8f773c26a691d9a81e495e49f00df009a0b9d12e7939821a44ca592703f7bd7bccf10e423d743e1df0f9c5790f60344abf0bc8ff4b305a7b059bd458572669fdf8210298a11de4885c40a9365b8618b9441892aa32efebecc82067ab9553b7715e37ea367d3851a039fa1d7a68b21180cc0a632ab4b40f1c951dcb8425d1c1a1427757e8b785ae2e01bbfaceaef89e54f237ef82ee163c8ef2aea5bed724d062fa6192a39cdcf2b11f71367f158a192264defb8c84e38042554b8170c49c348aa5f224030c04e522cf315c6fced2deef8dacd1ff8328f835f73932bec4527a21e2298ce3a0c355c2610bbf9318334cdc2eda74b7d14c997a8afd948a5ba02000072eb8db89fc5123ccfd49585059f292bc40a1c0d550b860f24f84efb4760fbf272c1a9cccc2b7a33fc46a8824149d7e2a36c5c45fd7ecf11d063837d73afc967727c038d6964685e95dba5a368e01e2b332e2061d021cf4eeaa9e2bcf2ce30cc07be208577809968c26371b42bff864171b1ff45b607a17bef9d5382577eafd3089883ecfb97d6acc0a80245fd58c490a523938a4eaa26c5cda58920f542e00542eade249af2b119a55300961622ea889c661c26b04620f3549a1670db68e2c20727384427cd2e13a229fba863483342f0bc9b254ada879da02ca4fc31670abe4f4c9130ccdbf4135804ce5951521dfaa24aa6767d1a69a15420cf76364256a172560636be92828ead5121d493047dc77ed3747247c01a3d8441193141ca88145f191637b9fbf127607cc784c3d8985fab3dfd2895b3cfb1bc25f6ed11883d3c729e12e6be019e8e4a3b33fc7be9e9b11194f28ba22144af61fd6e185c66c3a6344b17a75f4e3f22605031aa28a525b3a5e21136ce7213effdbd8410c050168b7223393a9823ca238542f1a1c978235316bbebb07232e273b198364fc986907a72c4fd4d771652391dc2f879eb258949470847c77644feea524ba2037c9350d372559ad653f740cf3c4e9b94d7776a86c8496d3a9c4f22640169ef3dec86f1041e14e82171d0ac0f8e8b3cf167f4526d4ff686c9e38301f5c7ebbb500bfd440530005c30e6cd2c70501cb43f6b67434da604975e7219d34e762973d366a1e15f72c394b49ce7f55b6a646521365d390076d4cc1fbf4b3fc207ede9cd3980fda530faa8615dc836915edab098051ac61a2f4ebf3020a148a09c50f417d8f8f174729156053fed1b363d6d6b29ccbf278242a8a369fb4d84680e2109066c38897872f8e71f5b10da2930111cde05cf4c810e6ddc7b67239f3dc75db8961e33f0d2726a6683d3b0ba1f51ed7f35858b1a8252d91efccd82d55ea734a775e5ab13e472da1d2115baaf96e6bece5f13e8312586e48c44baed0bc984e1be799c9371587224b7231cef736d9ea675626ac79da1a6f72e39e0dbdbbc708cbfbe91df13325d7b3c371fda5b9044979bae7aa30b27d5033d7cb720eeb4235b5e8a43b6217f7287e2c98bfed868f8129f7b575ebcf543d6daadb9f77ae0acf14eee5538af947201843a78654788cfdae33a8510b8205f8b18ce679e38f6af71c38cf21e93d07295810c0e176c20b90210200d82e11661cb58d8c5d200405e9ad0f8b21c0ce856171fe123f85698e7829c5eaacd69e8c3550c42c07a809c7831a49df305e0cb464759fc94bf0055d52bc821e1bbea670954db38747d7958e7a8d0fece29282772f4f1e315ffcf2a0303d6829ed0680d479d6c2328957d2c8e4abf2e7d21e71772780ac732bf2d35af94ef20cd9e94cb4fadaeff275cc1bfd7aa2470d517625d726afec6890852b8670c5cc0663c611d17b26c82a26791c07e7e560b3c5dc0404d89e01607e4060542a791067bfe26641a1f979c12edb47db4546d2a10a264b07256b06b62870143bedf5e50eb5ad1f225bd94640f2a36ec0790bfb4fa07487a726e4717cb41a0779022f3042c22d5a037664472b97ac923b1b610a0d602ab09581695f71c7eb682ef7564947b7c353ba859f8ef769e950a157eba1da93488857233c3758c343de3824c5bb18d95c92ca2c723e01593bceb192e8266ba9c62d1725e4cc8ba3fabdee1375ec98f6e25444f2629e079780d2fb84ba11302e719e7720fc90ae18bd6471f82e5cce109970379f1435eccea93554d3058b49593e42d72dc405743a82d9c80b8e515d7acfae3af408ffed59816d33f2b42a36131aff37217951daaa7adb22ac2fd3c1c358afe664a082f7c18206a3f2389a72c0fb2ab1385446cbebe94d3538b6d280a1f8f4c609eed81f57bc8b5f136c4eec0f2f5974e6d659e7d8a0f37a0fcab53ec0185fd598f86a5fdadc9274daa44eeadda50057256db3dd7b4633344f195596be70fc63254aa7ec67aa565231504ad6089762f7297fd34e7af3e3f67887e51797c2f4c46cd03efecbdf7e554d1841e581258a0724bd0ddd9eeb27484fca7398a9cddaa479c47a316c631b08dde6e5640db5a296c9eb86d46d0c0a608914183cec04a91c004d1225b841da693833a692f917b3f652f7a8270400695da0af479823109a141e12e57bf3280ab7250ee2fbcea6d5c15f9a28131c74bd833bb18258d85a2da483b5b90496288c77e8f17af99c13d1a3f249ab0899962cd12cfe9175b90806b6470b8b30e2e247b57c2327ba65d4b9372ce6584c4eec48220fd43a682bd92093b40f0c9401cf1e55995a1cceaefe7541f46adab6b777c4d94448f1aa43f23533d96562b47295f06d808e73b8de11b034ce4c2a7787657900b22de644230ebd59432962508ac82d4fdbabe9ea0bc725a722d7b4d8d3a02e7f11cb25f78a788e80129b13200619d1d6f552c37854cdcbc727755ace259dacda310febf114e06441b72c3dbd4b20c9bee6aaf7f0fc4d8d2726104d912ef7d3767f62542b241bc00a72e75c4990ad18b850eadf2083974db7239d0627f176b959982580e0b10b5f72c9609b821ea7269b6f6753ea4c7f5a219019457c7c8946a6106c4feb2a9fccb553d8ae08247d93e57103f013848fa2e72e3251e525e2f5b2f16d179540c742b4e8ee39737c52c8b8e4352372cf202f1508eb024ee0949a4a4bb4aa75483fa8ecc0bc988f95ec627903accde85ab994b5141965f58279caf43906e277901478efe35f6d7f22ef21d8bf59331dbb1d71d723996746b7ebc3876502710a1b466ec020c4ee70727fb848f068c5cf3e7529972ec823dc297567e9eee79e2e6aa173298e0eccff511456a2a0995f61f2dd71a3a106aa27c286bac2615db83a8e9840024a651892eda524b3fadbecd42b895b472bd8d86cd1467f10b8fb29b8d446d040422542c984c4a3a15a7da6db51d422c492407181789ad09e055b1cd8a43f3cf16347ab66099e2ff7bde41d4120fab4e6d54d60b7c7ff63e5b3fba980f5be07b648ebdab32aa684074b20f21978511bb0b31d8911b8bc03ec49aa12b4145d940229ea8907bf92b305b25a2e22181ec697221288ac51552c35938a678dcc98bec1adeb5bdc33de52d66d9eb0be84dcf3f72bd9f900ef115d7d5fdf4c6f8a86ef21734f34b74a94e073794715531d8961945d478ca0c4aa4b8a2f03702ac862b698e91d8d536cbd4ad48b1f21a3c1935f2071d3ec2c8be2a2522dda810b302047bcb585a8f00932e3aca97444334cf1be272549285818cdaee55e684946edb6a55978929985999c320651c02da9bc557ca724f76d07525ccd6ba5be627a56da199c38a46c41b19c5382e7ca9f3008cfef3721f0eed598a49499373774dacc8a5d161b80110d9b4c949ff50fbfebf7fba0571e602a00fa3080d5884ea58fd73583e07d57165554942182391602913821094386bf05ee149637c110aebc8637feff4258174ad7204ba16410cf5496ffe0e24010ff38d5c81411158332f744499d26e64973465643527bb65d4d5a419c7122f351f605708ff77eb9fcd7fe579f50d7208999f6abb4a4b1088d791ec34010b55413914b073c0708e8bb8af10d294c314034ae303d72ac1e627e78f0ea680ae9f2e726bb0db4b580f80a7cf854511442652d5c74c2c0613bc9cd072a35a3247e7726e98f09b0f0b971cd50ac8005040531c866be3138321b117e3c98b735ba92c7213390a6b4d1aa25238571d2e847893976f140130dfbc3c5ba05a5e75adcdd23283db46ecfb68803e6c480808cdf7ff76b296d39d32cd702c5940ee41d268647264c8970ba8d40fbf87f37f5310ac5458d4ba086ca487d0796b34a065327142598996f2d6fc57dfcbef349b133a2e68c245a7bdc8802ddc9b9e375f62caec7972ef7936b5d4f572785c03f9ed8004d500c15d79a2a96dc2a777940ae686d2e272db1e1a8501ed92d1e0ae19353e09855d36c00f3925816444aaf1f0a86f786d724a9570317d4a32d1e3998d10b7b486888fd3f69877b55cd87bedead9c59abf72df44a16f35af9ed89cb4e8c74e2d500c6be53973b47d816ffff2ba4fa2b5ff720a70f383a167e4791d0a7057aa261f06470b8e1d1c92474ee2ce2a116b5877191a9e53fe7c0a11dba4099dceadff15c805d3e3110109b0cc2b0f98a72bd9e172570e1809d69b0a7fd9594bd959dfd7471f878a93ea27dd0775b91ab68660d47259e161858557d20ed6debcecdcb8dfeb9d6ff4b3f896fe65d9e5b053055ccf6018fa0ee773f344f2977cf6911e9addb05dc3df7d21a2529e774641d98b742d4f1c9ecaf2b832baa6579c48347d7b10e5a109e7d8e3eb1705ee0c5d8b766a2b3f6ae8c0963738645651be064f906f37a95a812aead722ac36549d517c81b1127275c985bd2259d1893a881973a093a0bf909000bc0c1542692935f5ec132d1d729cdc6e6164bef84586787913a15b0474ee83ff0676fa0899377c72302d48ca72ca0283afaf49f29e3d647422a1e0a8b65bbf852ce03e6d886de1de613168bc72b7b3f8644c1e58c179c823b4f3c3cfed20a3090e6700ff5d793119101c089961d514439c867cdd04cbabce56714f3aa113e2d813c833cbf8ac08cdddb5beb5728ae1787ad6cce8a36ed706a1791c6c10b4d24ac5d944454e26e08ac7de5cea387cef72a77d9c904b181c257dc44cb8923a1ac2590d0dd22a2e632e702bebaa72a5383bebd51d615c8c148d90d702328311cd9cfe286ea86116b98f4e25f23972989d365da14e300ce029a6618d4c4b26c98aba8114342a7d1562ba97d1aecc636c3f629b4210ea39cc97d833c28aca771b8f39cb1cc7a6c1a4612bfa82aae12df056c77ff3aba660dea6788b149f741038939a9e9190c427a511ad29dd8582176ec8d4d374edf702270a3ae6a92c1637cc669cbd930348f8ccbab1c4c0756d0f8995e021b47ed7ab1491ac553b96cff81731d8c28207631be86d8dfd0b8fae68125ba7841acd9972ed678d0823e740f2867f8da83c03cd5c10fa80a208aec76b47ec48c5e9bafda67ea7f7b9e1da6b86eccf25b70ba6a632b424814d7f7d64724bee2af0ba5a5f3a2e2c4f43f4d141707672d62c56150df7821b5d7cb25cf3726faf1c46f202dde6f4b07b8f176f7ad8af5e4de5353e061b28998b4a60635872fa447c81275ad7b0099c479be73a599f4fbc96d62737cc8791fe7698db78263fae503254f19dc82c0d9c0af34a1f07483a1e8bd4f8c79459ccc32d55ff6cd772ad7354cc927b91224a5ca5febf4ba527baafdb171d87de83beea661c58fead72c626821abaede31aaf48bbfdd266f2d205fde64c2f2bdf618e5f3f00d05e6a721f1706d91a336ca7cce4500efe50f85208f9926e59a2986db52bbdc4bcc1a97233448fae60921114e126d4d4ff7c059a770478bdbb6458c66c045611b12d737226dc5a78970728207159f754641f11174d2849180adc9e1b419ca6e7a9d9cb72630676edb53a360b2bbeba22545cedad51cbadbacd701001d601d6c6b48506729ae20a00930694cc81318de60216ef16e546b72a7bf8c789b3082ff981861e72a011daa542d47a9dabded9ba1d0be3e0c06ec999055313db0b49d5053b1b7f720f28eaa24a606bfb845e0caa92c14c9fbab39983e70abffebf40ef228b1ab46aa184708fbf3eab1a6949c67e1fe03971c66ef1c985c1a35313c106cd36ce3772b03963ef4fe37e4e499b2f140d26f93874d3be0cfe2300ad52068347e62d305a35d39b5484d71ed093ed348dbee46f4ad102d21edc47d98d88726c841c576a7288b799fe9e296c06c8006ceafa2644c4cb03d28f4bcbb3aaafb8dc60ce6143726a2dde70155863bc0b8a2d1bcac891cb4df143144d013ac97772114850abe41e34f229da60e7429376824f59a7a211ac1636d56b806cedf7d5774cfafea1e121ae638a41ece2075f7474647648e0cb2804aa5b64828e9fa9093d7b72ebe16e727ae18c7a5e77df45c274c9d096d5cc464d7c116c10f38a7ee1c802b858efa62388a9e0fb11d8076ac9c126194bae9447c457112e4ea1b0566f7a0dd4c4871e7146c717e18c54b5ffcd6571d290447eedca6de3362b27efc99171a727a5f0bb727a867c24a719cca0023636a0bd1cd7ed07d91d3971e2ac3839d412b95176b57245cbbf894bb31de8c4267261c06c75ecf87a5ee5311789b6df9c9efd6b90ac5d05108fc97b15dac24f41713c3dc5720449432be4d2d69b98d2242a23c4bd2f22005a909c7b7a4a92f45bb65eb328b908e498039967c79b7386356b666ee3157256e0acbea57010d184e1b32b7c486d3b78b3a57b63c4ca72500b197dd5d79026107c463fcb4070e3e7543f06703fcc8ed1af717a7ffa670c15820694b01d15722ac610e187da398b505e2dda3bab758475e300e45404196faaa856a1be625327506b703f9a03c773bbe6eb81cb6be4b7874b757e2a062ed5ed8ae559aabbb17247922886947501ee6028de0a155683bc034c09096f7aa13941d040bcf929ce722fd190f258c21f1e0faf69a02327ec0a93b993ecb270aa5a9611eefbdb3e733a41f8da4752ba926d43f1b66ca01658276b9b7f4e358346bd5f9b8d49561c4e3735ba7944ee914c9ed13afc01b320c7a085c18de79940787266a7447cb87be172e6b50daaefa0054b0bd696689765d89148fbb115af353244bf02829646b8c772a7c5a4ae742216059f3c8363ffb2fd6f5696f065205be00107661349024ecd7232aaac674934bf1518e8eb9b51b2842ea2f18c8b34c4e72828ac04862a09df0a993f1cc191e6a6d4376947789fa6730e2c69b61de3c7471f81e1c4459224ca7297b45fdb2e1060c8ec5825b86c8321a52fe730438cdf7182f55788962006c80e6190effe5cdfce031730c80c94d2ba735dcf6d7ba68a8844714c38fb9c949b515519783e4894cb5c9a31c66df5767132336dfe2a35a9c5735fa3b50dce811372469f2c016507fc726afd91562c366146e46ae482e0e287519a75ed4a3dcd11726a6ac9d2f54762f83e4404a6da782fd517e919e004e8bfd348ca4d7c54e939729ff432edc778f6e4b0b519ca2adc92deb64cf05c8e006c701027985bb352f726305c7109bbd0855a0da245a683851255fc7b98579e37b3fe074be5bd8bc79311764b382348f4449339d1754802a430839495833076afd23e6ff830f4740800726e7b0a2a9ea6f12a7f981042f0ea7b5e8c16c1f4c017e9034f0abe2cd6a5567256c51a8aa9f1354c5c554b200f30a2041f8669658932ab6a5303d1535f7c327274a9bc347d54cb3316109f73e6cb125d3ac3aef172fd41d308cdba2425a36e215b185e7e62d5db9b31a314ccb2b26a9d935dba2511c947067813e157e0e86172d8c094d7f60cd99ad729f95d13a34564b9f1f8edc03202d29b89c77e5b4b0c727b8947a2388dffe71661960b59b01a8ab893072266cffddafdd51a59cd817a7201d331f1adfff2c2cf39b354030fff18d4bbe5e31147baff8e1d5d4a01d2a903722ac1a4ae6b70bb323e4d943a4226ee7b0e713230badcf99da8dc61c57f167251bbf897caee4ac02a76f87a64857f2a92da02e90f0c11568f98306fcfcf085f8b54e98013e30514edad049439f44cacaed285e6da7d0ec7102bbca15397bc726730ae812491ca66a7dba1f9e741b6004ef36a37a2fd43b37516b620257e92233349c8e006a7200f7a2d08ff011d41df59bc4bfb352b6974495cd2ca8548305f95fce02534142aab3768ca73e2abf00f585b13073699a89901209da42930eb728e5b05bdc801256f5a8c0f8038be0754eef9899a2a01f02274e76c408c86e53e337d10f2680b885422881acf7d8797c216915048d0e16ab0af8c3341f8b3aa72dad03bc78673078b9ab56a3be99c0ed0cb503c3b8122d5d0e4b94461fde65d227796685f71c109eba3ac2e1fd9bb17e8c15c1358df55acbebb2cba6ab3e03472b2bb9385b4c4300d2a950fa9109b79859c76e6550a3d0f626a7c9f82606cb8565cb28e2d43f199a90e3db8aab3cd2a049a0d8b58efbf84df25309ced4f0937727c8604c4ee19179498b5c92d488d226114eb5391458b2d180a5d56bcaa43ea72a96e521bf0c94a5d22272c55698b05aa28dc3dfdd0645852d822c6f3cf79bb4c70c7c2a516f4904791fda0f5140a72cf3cf3b93434e82d2bc02dc3b90845257206176e2f3511e30444f1fa0bbfd5b1335561e210745d880539a39cb69649907206e1722bc25c36bf496a8345723a148781e75b00dea6a58fbc10c588b40c6e725e1f158b8d44c714e369eaca5faeddc6fffc0b484427900364450fb05dccaa727861912cd8a724d775a40d6b314cab50283aec4999065e99ed7cf458efbd0f72cfe791abf412f60d55f106b3265b63595fe9869919cb8e0e59ad3ab6a190be72e84e0419452ca4513b7adaa29c62ef843c37686d31b480f475a94d5e5055b0522a6466402ace4631bc3fdc150d95985d630b7c888b7ac5b7eb62e20e3fcedd4a74fc2a4d34b1b6fc9bb3a4c5234963fbf4816de5a27d07f034f8a130aab430586f7f422de417bf3089e24fd13e2e595b12887ea9c80efcbdd1256bb7a7242a7297bd06058851191710eba97de2ec0c95e3867ee235f5b21c50384bdeb30eb021886a4d4160778bb9076c9a57850660a48694ca8dbf01cbb7fbacff217cb99d5113c6cbe9cdd37a9de346a8f1114df7028235d1d2abc80560db132a34a2de1928e1a628b71ca7543358adeaee528e49f3fb57a55be21b6b3d1ad712338c417e6fa537804b20bd80406966b076e35e0ae3e77e104c00d5409c111f5d92c2805872d5412bd93c0f5942e0800399bf09392eaecbbde3a87172c436128af4c39b0434ec99c3b86e17b48f48b47e196eb6eb27882c889dd3b1ce6ef265e0a5e2682c08bc4cbffefd020ffc55722c3208dd03bce9263311509cde0dce394b8760861a72c9956a24a7fc05cb3a70df5652d9ec5a34d0ee9acece5a1f0f682986702983352ea0c1cbf57e2e0f91286f4d66b51e96f08abe2c89f56c96c6db8269338def6fc691aeb4a7f5eea062edd19d9d6172c5c0b9d15d472bd771dbdd003f06c5d46d05fe6e841b7762c98c61ef0574426b0347001a3114253613607eeeaad303311d2021061ae3955570dc1a7ea2bec5b186f4836aa788e32bedac85ec3deac44872420fb393fe54bad6b616d6d3f1c3545fce289dfd17a9d3bf4ccdcce743b36d57c891806ec47d0ac1b68b2bea240142d68c2ba92ace17af4bcd2bc6e8dcf6ce72340d8cc8b22e9832a9d557797a05d503f411a91a28f7562a2ef591986315ef66d8eda1ae3806fb44f3a6941051bc6c8fdc7d585c2f0b93df9baade0753be607227ed757d10a7021cc5b3a72e99e43500f32fae7993768b53ab6940c824e366724036e3e3d93324347858d008d35e9391216ea8850add4d101df0dcd7b1a8955566bcd07480674963f29aeec4a08e352fecb78e42d17305cce49807e22957f9728c94421e2ed670c55e8f4bea1d032f8259d8cb38bbb6ef54fd9cc4da24c2dd7280f7d190f91b0a168f818a8df43ebe9212c35ec63a6aa27c2f19b83252ddd2721101f313700b365e5d96862f16c033b017b50141610f1f145265685881087e72cd17462745649fca549eb1429695ae3ec5e762bff9a8a45cee118f75acb1e428a85dbf7d92f07a6bec8d4f390184a30358dc12ad3af965d07e210e4b6d074c7202b38faecc65b5c5eaaa2d2490dc4b1aa56951c7e9a8fafe083b30dbbfc834722d476133707f4be5fefec6e50e6b6b3da701f4ec932524c974b738c7b98f501c3119df449cf0909ecd9d4c63c9e8756cdc2600237887ea4d73b3a7a3f67756720b4295e63836d840779003638f218dc2b213615c25c7e690fcf584a61b7e40352aa61c4b19e64285ec7533c61b7ffafeeb84c2f169961541314498317d444a0d29fe1a5e964fb37d8d1ca705686fefff8252184790a3e8c159ff092adf44e372723ddb6de67ecc2722a97555c630e693cd0aade60c4a0cd83d3c4dd34b610f7257b896068b816321e710e6bd4c6929b75f896f51b3cb8ecc44079763eace257244fca0a7f175e232f01b30de01580076244cde3fa5a60a81e6e719569f7cfe725fa34869b5e78bd6857e78d3576e91a47599de07b2db195ab75a0a4be2f572722c467333e27138fe3f4610d348b422b94d656e3203f1f402372c810eb8f67c359ed72a58416ad03bffb2b017a1c41eaf50f59682a1ef42ec6a978f5552a3b5203b9fd17ebe18c21089dace041f7789a14e9f0bbfce4acce02c2cf6169123c772e39a167683ac79462c02aeae2dfd75f576b900cb1019735d9f570878d6637672312680a1c51978ffe79756ca8210062863844afadc97f0361da1cfd207637c72de6f334ce41964d641e4081f0020b26f65c75edbd7404552482c0f84fb05a967c6ec4c0c634d720ff592add086cecbf511650441f6460d4b4efa5343b94d7d720f184ef1d216e53824de9e359b13a74f56df7a400fd3dd78aca6854e650a6672b37d24fd3464519d028200d10f4d414b0d51464d7fac6e06db37a821b3ecdb72ca3324373a5fcb8d834227babf31bab1b44e891bb813c5fdff98184ac966a549c8f7c2f388a3fd867a3dd8299b2fe91c58882d6dc844513486bf25d63accb81fbb6c1245e4330787cb97c596534748baef2f75e7e38deaedd63b50a9df3b89623c6346860e75c8e0328ec4c09332faa5254eabf8f74240b7889e4b3025962a299370fc2ebb4204106bf18b716042a54a7219dc090d03ffe90c2e02b46b34e05b73b87b3966f908bfe33a7dba2cdd34d5a32ad12f0c51f3b20f886c15d6da3e6b1e12c2e1df23288ffe56f591ced7a94ccf116111949fe652786ff35089e38c72516eb56025882dcaeba2db7ecd1eba23bdc3cb9e06bfb7818808e98259c5b3622bf69196ac4169fc94d42c6d8cfab885fc103b05bb9bd639c0c62f453e6a8b7264252f14e0e678c97665da5d2a09fd4f58f522c9cfa6b7795adb9f193ca6bf1db63cbc07aa51c454f57913e419e8777dffa8869802a705ee71e6cb87186659088f7c1a9f08bc42a4125b8a5825b51a2d5a9a24655e9c27774baab813747c4a727a539ca40b0cf8b479f38fe2d4930d9031edd668d34ea3967fe1dce8f2360a5ef91c3f31b4ea952ade75161db2f000d760c0252f9c68553882880eb7f8b84222072cdb195c691a09570e84d08fd77a60d04bf17a709d8aa901f325a010836372850b2010e869f6a429b102a68e8daa2a477742d6df80da3eda67d487c8f28004527556bca97e569787740a235b509ae784729ca88f2e335d8826f76f3006652df2883beb60721c8e96830c0761734992b93f1fdf6dd2484f9317d29b17f2bb720eea42e4f00f149abba1076f0a126aa167e8c5baf65a78bff09bdcb976ed385dd396cff0e1cfdd2fa4d7c8ad76db86cfe320afe002f36501d4a16f6d9685a757fd579af2b2514126937d8066e9d35c1e967347b657be41e1c346834c288c2072b3e1028dad9ff07e02c40aa34cf8d4edb35b48e91d5ce4d7242012554a0fb82199135db285df3a8008749c87e15d541f9aa9f845374a4519b0f5a25a2df16d4e86167d4a12521cc1ce088bcb5db24a0d252ca27ee09b5172f9d2bd62ff5cd31e773fec284b79da8453cbaafc87dd604c0297189c97aa2cf7d788e0a19dab6172848714ce7faa3a75c6962b2265d93b7e5ecb785700dfa287870fca10282f4c522636da332942195ef57818bc1efa00b43553f3efdcc6e254c20d58c72cc1c572b58e35163cd25d7ba1b91190e847c954c415294c7c93efc24ef5de22afddc9727bf6e442e961a32821015aca7ebbc82c51f8fe781b6cf9774fcfd1a18b97a172704720b85b8d203851f0f992d0431700c0d5c00b7ad3579c3aeb53ff980526071212cacf04257f8a28100157659a985af155b2ce0ef42e6fe0ae4a73523d527208bd16836d02ea77d2e1ad5a3264498209f3b52407ce1908bd59da094f69f95acdceb606d22925630bb0aa4dd8d4f146afc1a00c22222035f9596a5cb5b62c39c647c88e9c230c665173b9c4927433988700618ee8f96af5c8af9869132ae172ea0ff96343678ee4b4638bed2d61684225d2607ab0309131d4dd68deacf5472b4cb8cb9813b9e9ffd90c4789b980f09c5dbf09b6b4803b2e15bd3d442748bc394dedde127175a3ffd9aa37abb04860ef061b0f57a85a7caf007c870e8285367260db2af41f3cec8206fccff9846ec409930dbd986b5c5139ee6e335e75c7507213795d0ef7a9d3779d5a792866e7540f86feddffde2597c6a23e7c62f9029d724afe3bfe4807004e1a0c5acb3a2ef55a09f81800408c8d81fdf3c61737167d72ac2e906916d2702a175578927101efb1ad591dd8cee99e9f7c0e2074f7c878720feda2c3a787926afc024e2dce2a44b074c1d8868d80b443969dff21d943de34a5cfb4d88e983a7aeff8485181d16bf56901d0aa4bb6d751999bb67075ed2a72bf14b77bd7a287e6e984630f08613869f11f2a596c55c773e243db19a2ca215e89baba348a37909bdd49415f55fa7f59aa36db3fa8a55d760884f597a01fc072b3858df38288ce00a03e270ca10f52dfe33344d0b26a1a07728d9fd02d705372dd6cfb9b743ff0663de014b8de00799124ad2dfca1cb318174381af94a64d2729ce6d87711eb1126170436ffa504076a4420f203462d723d5e02cfb5ce7c54728dec22ff534e645982fef70afa3e98205623386c6775a3055a865d18e2b64d720a55683756b309732cf72af0e9c09c72396a2cfc9968eb8c650b49c5fb2daa02554fe10e491edfeb8dc54f83616e70ce14adb347baa6779068e26e9fb7c78372978db03c12aefb2fafd3f4def4f0fbe71c7694092a6464a0525843d1d2eff00e46348e5b42789ecf423fb0179db85a9f09a0ca86477084e9054b7998d76e8e7252c24e049f0e602d6d69e08f5a9391579e4fbd976f5552724a06e183e8de5d72452694feb3e8592434da02b0c2d19cb0713ca1781a0d781921dcf0898ad0c672203822103da58f31825ee0adf65a90a143871fb0aeb7e8b1bfa26b759d0cf11e58f8e5181d7f4bb0f111eae86451d3daf1e1c6a657d97eb83dfb4066c3602b59827f4d97340cb0b08c069d240273d5f5447544791d28e166042b9d365f095572b9fd48ad1ae9470fbb42e734c36f1df62d010bb91c4119da7a4e385c7b833a280afde5c780df23cd8cdeff5409f741850d3cc0226949b725a7a5b4fa207aba72023507293a98848bc41d693f2b43535b77777e649d7ad4eb8e051b2eb5fbe3729de85a4b091bbf9aa8df4dfb9a552b846d21083e7486ff342160be9be9f711015ab8b0ca389ce96a1c242ebd6463ab8e653bf38272998ea431768d3a689b16723784d9643e8a07157f5464f84160ca459ccf99eea3b74e3bd86ca7d9297f280d7202c9d2d83854c5aba4ef94810c790a78216960451fcfe66b148caa3bc23c72b73c53f795ae709057775efdb0c1c27d720b80ef13da8f37c91a397854d69172b03f291796bde68d5b175efb389ba28b939d76fe4e57935af4b910a20f460916e51a1133296e8fd2f74626306de8abef0005b097a9487849d4ef51b7171112589a71bd77ce0992ffca1e6c9f282bec3a95faea39d5286e9e94404604ac42043572336bdf0e7fab973cd763bbe1b5c0bb654b9ff6286237a1618e97d3aaa12e72881b37bf626dc102d0313a355d5ad14ead9a211c20aeacf58a7b38af01643972ed04107a06659b3de55c2f743961829f77e06ca905c8dcddfcf5adc2d4360e1078fd208ad5416b0d29435ff6de42fd5300ce3cfb1a65f46a3d4ea60940db5e7245b1edb5ad6a704aaa1c11aa1cb29f70a07a61e2b33b95ea5d3e25316209bf72ebbc7a9a03b2405bde636c4500a55ebd69aa6da6458aef68d892cfcab9b27c72bea46dfd9b62c583fc689cd27a0fb4bf5bc431953650395fa296b84682bf5e72da3f24c25d5e7d4e92da4714a1d61bd9153688a6e00c51830e7c691d2338d9724f48473fd10d8c6d81d5b0e657ab08251a517771446614ce800e284f5b27190314851e2f63623a81f80e4c625479ef8aeaf09427dda59a66f41c180dbb601e720e6b5e0bfe9e8b8a44f3ab82e030a1abd9a5d7b06370221d859599464b81f67234b50253138b995419c130fb94a49847c56fac6cec22158cb30ebeda336ac372798320a5b367214983ffdeb1fb84d17596666a87b7e8b1874bbe19a461e44018b70d22650f822b087b6c0c5c809dedbcc83903c3cc2f5e75b3e910a5d4acc76a1396728b46ac8c21968950e34996994923c7d51e0d041fb7c39d53d3dd2df172f2efd1183d198108baefd517c027331a935711aa81156d8825cea30185ba42650ef94d01ede0c724f28f222fad12da9177c1e4a256a263f665b3c9e8d2e4a659a1852a68c06f5436782f53bb3aafbe8aeb098d793c56e5872ad354ce0a66dd72fcc2e1cd71f1f7c4831726e56aa35119b55ef0a19ae4a00d5bc2c24bdcf89272e11314d22113ec632d9cc11b270b6dd4c85a18a39b1e006344980bd49815ad5a9584019bfaa2f9bee7c3269c1e02ff0a9bae0c811150e4cc2999f4a316630772124c33f4d6b32ad5a694b17dd1d7f07f1a1b347ef71facce5558bd25fb408872b981eff87df6393e0dc802d0c4eb66807001d2e7c69a41ef54fb161407984801fd727d38fb9beb1b877a95f46de7a7f1a53f56db4eb59ff0c718ff748fd1353b9cf0c237508d0f352c8571ff3037064d7820e8b653c8a94ba408bf7825770b723a865c4e47eb857d1bf3001943e5303d2efe6048c6058333fc4a73923c8c6c7263fafc53cc4bde56adad2b88666973337bd052660f5249597859d89234f2123c820272eeab51a6bfd20f506d5bdb79853710579b75642d8153d3ca01546cca5085502fc8cd96952ffbfe26ebace13782586d299f2c1347b32ceecaa29e18436a836f67d64058dc7de59e1a502a1baa543d68aea0d130dae5dfd4614dccd344728ca9d11946ff7cf77acc53c476569b1d1b19297a9c5625f6da230827676c7772be6c2fb3c5b297253041d86f679bb880e677475b262211aba3af7c4beab75f6c4b97ef1425d1124e0fbde4482362906864f6548e5cf60ca8a082e635bb5623355eece20df16dafa83dcf2913c8a93a135ca455518be8771db8a62f33c4439f38aaa91cd7c40f9d3704bf00e5502d9ebe2037420cefef0f36397d4e94d6871b56b05ef7cc01ef08362b07541225c88359098538add761bd6563c65a0a8f04e572c61018f46c053ed2ec1f660667228ced8f4360097952e93e1c53d5d3c733437235719d19339b8aff8fe5667976929ecfea21fd077a3146304539313423f2095380d5e44e534d45a26878a3044878f212ba7b9a1253e62ff8593948b35790fe72e2de56e715b1388d01cf705d3192b7fc6de18aa4717acdfa9b6d0973858c607251073a18fc849ff2f7f0102f169d40613968d900c3d0b69b3c8f5b5bd5c9ae7254a9d121271fb09036964344981b8681f71c66e39349581cdcb01b3e349e885043d98c799031d7829909440bbda7c28e1ce2569516c0f40bae6c0d2551620f721087b3262acfc5bc9fdf133384bb807e9be8543e7ecffb2f7552fcf541847d15097e859620d7b03ee6df1be521f0a3cc345ad8ede3d12ba4eaddecd07eee9c3daa6cc86ad543dea7c79b55897e1867dcde0739f59ee970f19c8d3847861e83723b4b7f6c6041366146694872768b1f7b4ca360745a785c4660d93b3f70902c72fd7cd17c7fcc05ca58cbc40fc2a08000cc496aece57876a9271b84013946f43b133e61be6d2a1f4804e239fd140e0d90459f2677020629e72df982041cd96772e4513191029eb82b5b69e2557fc4ff0c923a496ccdb9bbbdf0a3aafdb644db727306a5db8817563aa8321788749d014c2127fcda45ab6cc426aa329ccc24ca7228282b870841f5a377b63b41af10b775ddfcba09ed9b0bd5c6e4059188fd2b12933ce70b115ec67db2cd74ea0739b5d2911854fdff0afde56521f84b8a666a50a3403e4ba53bdfa7027bd4ada2717333154cf3899512cf65ca199c1ed735c629b918d0919c6fe96f973a6688039d106fdee7188ad8e24696541f4cb7e7788272b0e77d3cc4f84993d3de4bf867200ffad7cdcea1450060e5b9d95bd5019f847280d0e5f908c18b4b11a77b215c90504e94c6a6be3567a2a8bb0ccaa43952503449264d3bcfddd3334e96e57de4f5dc91e3728c6086cb4da6102b6d2b2cb52601d2cb3fd8075bff7c47d31fb48c1d277677e028c4b16bb2cba604996b47b01a72bacf4ffd77a5583d6d1dbe702d9124a3ddd9830dc0c6f58aef1f06e2cd69cc72f8caa919b5c4e6646a14e503ee6cae5d3f9e83224e39a926536d95c5f8652a72cf164a5f4b1d4078192af7b928833fe4c4d8938582ba220b5d05672e9a626772eff4b01b6eb35b9f7c6dd7dc7167eed6bd55cfbb9228d9fdc36f5d873e580c023523b40c1acf8f8d77ae14754811b01970aac08a733ca67808048a11497f7e4f5a9c6378a9bfb5e3d325507d40a1731e2508ec791e0c457ce5e945409bca9472a876bfead3f2392bda6cf199668c8a6f943b08b8d2cd1f9314e9cc43195b5772ddbe2bb991b54456129eaa9ff6248e4ba41f3d8035ce018914044416bad8d172bd9a2bbef3932c88006722e0a680f2d204343abd0d6f94252599698ea8cb0f7214ac4fce5e9e524caa2fadfa57512997d895e3db5d83f62519fa5560adb42e12ffb21fc6f878993e947e22422b13439bb351072e604c5d12aa8962579e419061696a15dcb62dd6cbce00ceab77142b5dd8081234a222cc658d6931f69cfbf865258fb6f4a059c8d446f5cca0bfb930b95237cc51e9299b2e59ddd8f77265601ca7b8eaf755823cf0d35937000efd324b0039549556e7d50547c8931e856a6958e59bdd87cb47b89cd30e8dd08cbd92332300657a685c6ca5135e3290dcb5f272879fc42c39db299ae0dee5c76abd77c95e9c6afd1082c091133972a20c5ca15027e6fc0a0c73c2d5fe5cac397e080250157383349338ed41e0d63e799dae0021cb2f82294c047a53b127564223fe70e4877fd650c875fc621aec26b14f961772751e5ee665f1a3fa2493b23dcab7019be75ed4fb7fd8cbe0591a9996f14f3072913539a707ef7c5698c0bb15918f4de6933be28bac48c158ab411e25b76d176a80abc9c6097e25ba469875403b6221c2771bc67ef2a130c38f9f43dce9a4df72593d5b07e22d1c256917b1047d9828a705ed24e3eaec35c01fdb2771ddfc976f166f028ae0dc77715c9c9cf00bc845c452efb2b5831edf05268658962d75a37263f6e48d8fdc44eab00cc38c8f8be3a981c64790021c2115a95d8a4c97321772af4da389e41c2e0a30b2b1a6c302dd7a33c34fd791e7fd7e760fe0758681e272d257956b25891d91b58947e28fd2dc68ccbba3664fea76daef0d65750817d9723dd5167e0c200d69e66b5dbfe95828c57d6f10c625bb16fd9ca77f0f71af5772c2a1146e5532fb82e956e26b609423cad032cfb15b03968867e24ff00f61741d6e1603fc3684721fe8543f3c6be0b1c2b5dda5e361cf6957ccc4a00815707e7235514ae0c3bd9e2799a248ea8b06e7f839cb0629837ac5632ccce9b7a0e5c7488fbff93d312e6acdce4ca808d285b557a43cb90caf1f724a7ea6045314ad467274c32bb283bb8160f8ea4ccdd36b2321038479ee676a20321c21c740fa681972305e1d3df869982887917013b740dce468c0157b558539ea6cf1eb6d52107772632c5e349bbf484085e683b3b5fe8a9f04a9eb56e7ea1f2da47b39ac37ab2e7289bb7717a57b963691f8b2132b85bf6e6685e72c2bb5048d2846e0a2cdbb684b782681d78df6d4eb7081e9067f3f2f793a3240e7610445786869c69f370e7d275c347c0f2e388a0ee302db45e513d05419d3745d6727a3208633da35b3525572f19feff2fe318e4d2e2c97882a63792d4e80732554375ea9209b133d24974707187b1e7d4f2a5aa885b750e35954428a921f4d407986fbb733a9f99b7df80b72bb849b0f6ebe42b59306f16820838afb0325b2ce091eac82c053ded346f3b717f300700703e067ec3f4eddb9c7f92f6528e71f9038503b04789d48052b707b7207f7abc61ea3017150fbe4f53479dd2ecdb20404e8854f9442216855dcb463724bded03d0724fc5ba55f732e2bb5c51fa60ac49c2c42b25d61bff9a7ab4e7072f6da1d4cc2a740c223f86ad600e672dc20fc27b0ac82f5e48fd1c2174cda825de90596917df51034d745cb920671814036782ccebdc0cf9e27ebdcb028dceb722026a5cfa608346576a957235bc023cce45b75809acec478eae5176f669c28729b7ce000423f278a97e39ba8eccd46d2e7ef10722fafb44059141ebe766c8561a82334fdd0ed601c51a5ca3acc602c6cd519900da34bb14e24ec22085ef02341d7603cb98d0361f235bd29fb821c4872330a5344f4498e703f5fcce70d1cc27217982291ea506639bacaa7a6db90ea1f72bc40e10314d7e9c50197073c4ddf038bddbb58972163ab6ff9685616ca9963c6c5159fe36acabe6e2e2ab4e316937264f5b5b8e6906cf66c90faec201101051251dc55f5139a55612d88466de2487268c01f866e8db54da678096c9e22a9a309186dfdc86be011a25d24180d118c7217aed48d058041e2d9e44181748a95082218c6fefc2802568100ac1a268aa872ed17e4db3d06f2e52845c28cc1313845d52c5ed4d534f89574e54686a44df872bb16b48839b8d0163110008b7e6aaf923a55838ffacd0beae69350b21cd0361ddc43ac9e2addbb961e1a651c716ca1e672c862ef1576804242defba27becdb681e8da6ac2f77c2962325a60e97cffb33f8aa70ad25f6d6455a96276f54aa3c72e3f2ef738b0c867c584668f4df8902bf55a2104ac635eb9a9055c3ff8e6dd472af6d407e54628a6a8c9fb16555af07cb77d89ebad1d1e08c362dbd7887296472f8b16dc4354abe3b0d65e169f9c9c6edb5a546b370b959d1b775077132be1a246d4d541ebdbfeaf435629c0d4f2fe805a52e60b74bc9289088aa00fa99c6ef2b3c1a0c2264c272f36f26cdd82f381ef4d5a5d362ffcccee59de3210705427e727fc8c42910b9a3d978588666dd1811967c8a60218b24ae1cf23e4fe75eb28d641a7485dc78fe5485d3bd1fe0839cff9f6ac38af88dc24b286a6d5de1d487375337980626fa25cfbe2e29edcd44bc01bc4f23785f0cc8fa1f945e4dc7b22b8f72d16bbbe1287e9c1cb3235f68b3aa6dfcabd906490206a4d6c7af07d52dab322e049fe9e34e7b0dc47a9387d5c570ee5eee82ba2443c7f34f94b08db1051733722c3077bbc8a69bf38f031e924d5ef38b8a260af08206dfc56878d5bba0a3ef24068786fb6417485998c2561741dcea6bbec602f64fb357b0145a19b51178871c7c81c64cfb201e6c237ea0507f3371a9cb13e8bc26603a159dd3302391a9612828f85f9726585ded183755d6c2fb695d36bd7706deeb7ea174177bc57b5fd64ef4dc5773fb3cc2519bd209dceeddbf1a45e9e5d1c6740367530ae2000e014772df30d5240df4dbd0004e26b478de4e1af4f8aa8ec5c4b5e54d9ac39ad97e3c10d087a8e6fffd18bd0207efbc2f6c46393470a64a86dde625d02611eac12dda46d5441c8c09c54504e019f415d231a44764179dbf7ebe26c227914ee68b94f0211345c5d5bc4a571a61d4aed366db29ed11f6ef10d05088ba97f600e676fa5a72db5bc85a768d0bf9e9a427117668a273af8a827020d112ed13e514f1a587533de5a6e722e0229c7b8a96a4a2c299c363a29cb4f9a644175467af932d91d83172ec2a1fc80f079a898e016dbd36241e099e79c2e14dd2757fbc2924f4f87f9356ae2f6cd219bfe6a0429bd06bb2f8f9b0685bbe37b2ab1e9c7c1ce4eb01057c72fbf7073457d812f5c8c0a026de276173101a5b1c35c3bc42cb88f4bc523fe67238eb692ac849bea965cc4e283e494c1c9d2d691a11c6fc9081c2091449d5ed72464facc4c5d2d31afe9e2a040ec7706bf9b0ff3f094342dd79a243a03b5e1e720bc8cc5e7fef7527851590efeb36bbb153ffb674840b8ed2a5e4335b0106c7727e84390c0abc5e7eb68ccf0ea1bbb55044bde42e232bdc9ccf2f731539eabf636cbd876130b22dd0149c17b12fefae86106e858f06398b223c06de215fb8ca231c66179d7bf5e99dc08c3a9291639d821af6c40eac1d8f0b1697345bd7b9ca373942903c2164bf7eccbec64d71862d446248af1f899d01fff9f313b33a27c62cd9d1e44257da9c86370777d449b77b93dd2111a730debe8084b0674b52437a5b7c665ce5677d1f102cea44be896f6617a984bf79814413b0845fa57b12245a72369dce731993b9a7d19f1e75d8f7477a3a638f6fca260d7cc3e89ebb2a903f721a29bd04c0fa7a1a5fa709d626a737b3c19b0144c47d30676f8bb65098e9b51877ddc35a730096be0fc99c286594a02893f865b0635427772b57b1815617237268e757e0d9f0fc4c03741af987ace05b70a579fed33aa1861d7442be1cd4ae72caba5a9ec2d43507439b4f678e3f05e625bbb59d8f3f05219062d71eac8f42725e78e7142d7b2f391e047f4fd57c8a980323920a47d6980e63134d162dc43472dc9a948239ecd8b570ac8afafa7189c6c41a29a13722efc63a2d0ed9d311c64fbfd8725a3ec58b8a348f07a84a7a8a9161844b94cf88ce0561852b51a5be88376874c7d8034450f3a75d65fd309fb1cc1ba594ad7ef7dd36fe9dada94c14b872b47e2c10a8f2e20eb62720fdac3e8c3d09635f09209dc9db4c4715914b9d785f987f7959f179e586439024f2154e0672c7af850fd145eb3acfb11a34de1767725d3e2278634c69a0968cd6ba8d0081898f560bdd59a6eea8574fb11c93496c5b3672eab4ab1cd8596a2e48c40eaf144fe8d46f894922318a8f094c6030e55b720d29c8d6809bd241522ea8053d6877e516750a27d3024895566f30a418cec3716a9b4ce3959d8ca231ff8296a6153ccce5c0e37de73b64876e5450e760213472e81db4b749a21ea8a7bb33f1eff3bea81a8d71e41966d078804f281ceaa15772aceb2b907d7c7ec5f0aa58d57d1387b5a99b62216a47f906c94a158484a3275c8b3177d0aa3f6489b5449ea980462c2fcc9adb26607308ea72ec281e3fb6e914e2c2d02ac9ae9392236dcf9fa6f71d5d3e70c846ffd3263b119f5f39e4659645afe6e1d498253e85d4efaa3729c016d8eea1c0a67e976f7850ef43475cbe204ca86a3431b36d3847d1344c6339a4b150d4777aa09ecb2b0fdeb09751b90efa016437d8529e9e4faed796aee8dab453c8f09d76ca0d75a5900391e4e69361e538a464686f9c7e5915e7efb1b09ea2de07ce57e51fece19c12d606601ef445a572a6d5ba9c25cacce33df79f7bff9734583d8436f89213b0d4635119eaaad8af7233e3dde822a7abc0554969e06feb2cafa97cd8642924cd77d9af330452b5d8511836e6ed91a02dce6af4b19af7ec4a2bbf084e963d7d3222e2da40c2c444306e60c37dd01e51c1fee118d8bbbbbd0509158959ccf61d1c9e2f83b8c86c296d5e70e8d02561e68ce6015606380164d7c8dc8808e30b6eebd72761dfe415a6dc7237be3c7f8b1ba0776ff6032ecdd773a3f9e1bc4d96765742cd4f978f421f0272baf78fd63dfa303b2e8763c3d85596f6b189195b4a5ce22cc21e3ef964e09c72e00cafa00665cb1fc14432fb46599903c6bc16de4ed39dda4eb14311b0bc2572e558b1abcf37dc9c66790911bc0851979b041c695caa1f82b84a2dabf3972764e9bdaa05aed9cd1805cc349ff4b08140cbc87b32a675ac7d9d1a736c80f32922b3034a73b9a5095f952bef63cd8049bd7ba62948dfc0824fbed5600469af336acd393b8a80d501d8e42c8279a1fafc9808efcae7d5b102c37d84cd0a399f153574f576072b3b422275c9534e77c8f09eba464163e207cd55e9804d1224637126b3dfc36586fdc0fd6476eec2122507c3321078beed367d16859da6c15b0af8725b882f9e3bd85e3f57f1b9eb827b0fda1030dc8745f7a6ea1c9e764701234a725eaf9e7cfcfa5e93eeedfd560bf37d4b3b725612594dea93aec5f9376ee21d7286f6a15c905a585b0d18836955c87fc8ba10512f4613ff6186612280793cdf72a9c069a32846d2b07eac244214619ad3785e5e34ef5fb33b9694babdc106d17229b18851399fa801cc7c31087cb496aceb9562417e5e6856b1b500a505b09b4beb093405d20e18244584b98e2f7546cf59a40db055281370a095abaa04ea89729ed4df614acebfa3d7490d422e51a66a765599a46b32ceb27fd65c69dfb10f60400ed4b2f9a6b0bc6b4c54bdac04a05294fe75403314e7494d15320d0b227b723e87c6848c013d4a39964c142fd3f1b9624355da96a667f244904d1c8bab1260510016a287a590bd6a30cc830410586f1cea7d632e9ed424ee26beba183e104fa5b840343b978a311f8fd65be65bfac23c8143339ea6bb9cb240378c11124672083d9a26baedd474d3bf723ff01acf51fa7be4a3ed3e94bf7190abe69685a165389d75bd50145796489d6cc798535b2f807cad5147ebadc4dc1a44f60abbff1db250c2bcb83f773437816bf0cb8312ad81dfbdfaa7d86571269dd2878100783e815ca5dac5649e12b8035f232cb261927d5792ca7173ffe53787d5c05fbb037254da4ff49910b4a7e847c838c014de57ca1cadb94f1c4c672ef44bada5d22d72339834660b016cb4d18fd4c9b82ee87b58ae42e7d3a38af75ade2d7ccbbdf142ebd058503766af42fa0caea506a69631d1d114d94a9f1b92cffecfcc30d8a245954ed5d1e097e32b691ddc289bb365ec7d54afe2d2804b589f42a9462d83a8056848cf97d63a795ee93e9f7e23b17153647d40adb764c92c044e19446e60577212e60f8afef7dcd4bf3f1509a622c98fecee2ac3fd40d1fad0b0e8e9260cb272d1913164483d35e85822396e837f5f202640d5ebbf477720f09df8b9cb565b72b684e04f6e9c31732b9e3d371b4e072c9ee92f28413f5749f17b8f8130816b4eb354da391d725485356743e5030128d0d86d32e9865bacc99fac667ae9fc11726f440fe8df08803cee0478ec41c243fca5dfedf4e4c54ac454958a210bb010520b7ce9b7d9d6b0eedbed4aa272ad07281aa5cf7659d537b5c6a3c83253f3ec390f1358a935ca1e78fc6d9dab22fe28b130c788ae7323478d1313b89765ccaa3dcca5a9b24240ec9bf873ca3d7c6fd65bffb930f0cb4d2a763055f78a50537672c6f065655b1856db563b781ec5244ff0d394cd6c1ac5c49bf2a271b639583957d06436943bef1ac8d53902578ac36e989a65d41ded2d0adde31e73d68f2c5972c8d7d7f0edef60e57176de1abce1bdf14a20da430970d21e11fb2b596eefc25cf078ccb37ef9019f03d68acfe2551a69ab88ede2b71c0e30e8b31d6ab6f2603eaa9c3091ddafd5f746d51c0cbc46257107e3487e5bd245673e1f10c844452e722dab3ed1cd9dc69312621ad91ac23d8dd65429aa05c64cd72ee1a1006fefdc7204280321b3c7099adf900f8fa19c1cff024d4f6e76f3f2f0e1e52fd23a577672d6aa15cee687b393f44424c8c066d1dce9295d38d45d8f2c016829ac7e22cc7203b30293edb894eb6bb191380575c43426cca4cad75d4dc5f5f94f05cce8f9115d5f69d1a02d504e20f630051acb458556fea46e583e8eb2d82e6f469de2c672b78b4367a4dc0b9c1b427cc6250a7853147543448dfe7b18e93ddc8b07d6e6724d9dc438e45cdcb1f84834b08fb9edaa138f626800914563658f0e154f7d800e2c359cf9fa35001d76fdc356ef6f842f660d8b1e016fee8df44dd0d90a5c80724835c23102d754fe868122b9bde5df4e58b6a6c1e6d07d026c3400e1f6bd95726fd0f17c6645c8ab3860f77ab384017d0ba2ad40852c05a37a005eae6574436a734611de21e8872784127f7221023e1a82590bffca77996b148501f7aff2412ef55297b602893c5a9996cda0e4f0c1ac1c155e5e724595777bca524be84a3272c8be0fbcb4054530c8bb30366f0d9a28c0636827c10e8ec4ab2e84cce72be859859bdf827b968e7fe404fd7e31e624b1658b5428b019e9cbd571e6f7e6c7ad722e303cefe62ea6722eec3c404da594cfe2471cd74eaff95542b3c30b5925567252b720e24ad140259b99bf4f2298ea4664248db690f76e6a4bf17ae83aeaf81248ef6b1ee78d4032af277fbc4e36772a8a398a9c216db52e50a25bc63e863a725b14b8e1ebce03794a673631a1b68a797fc407641f361116c9d8eb61478aab72c5ffe11be1da1161b60a6aeb7ae6b2b818ccdb0b2cd693bc0911d1ed1ed34072500e1201ac4ad08c0efac8a7ad815916769b4ccdbd5ff90089017def35f57c729801adb3a1a61f5b7005301ab9e1ecc58d22a9d46a95f00e71d1573b734e561b55b33e28a70d596d7325d5743ef001746e261c3338c441b2260a6142cda99672bd3c4dc18aeb94628419c3ed342d5fba29d4fdc90e8778db8467cb158bd649546caf45f7a48a251d8cb9f9357cc8dfb881caa8a141eff22cdcf2c54610091d5a62545366a7e5c3a9bf2ec3b3c1ca834a571f270d1d6cd82864b8291a919fa90e588b71244ab0b897d0697c4f68446472ff34d319f9ad1f272464f0b67795f172077c858518456df1d69a975784d2f1a5eb0e85eebd308f8c3e661068155dc16316ca23f95fe911e7a9865a5ad39a7c0467505d6b3817820d7d3a18d529183f729a507a0553ca6571497aa05f03baad772902d20b617c6cb440dc9eda19238c728f5e5dde93ff116c2415e1c3497c9243458aba0365f8b39643e56a3b127eab72bc3f3b954b418e7320cdf4349fa6e481601efd69a4dc30099f60291b397d0b6356a2fe126fa6378d1ce33b2ae3674118506b1ad68029793c2b73df6e215c95016e2a077d1adeb41cbc0354d7885a17876a8a2b348ce695ab6b300240c67fb6725c59de161ddd296a888b4c2332cf6bd1bb8a96e7864bde0470ccace2f0dc0c72198522aa9bea419c96840f70991ee00ca71e9ad323a63064fe5c353c4cdb0d723bef90cb5a9600a7b1f5c5176c46917ad22b67ee7db30bf8f09d3b945e218218a774d46b2abd6ca843f792d637df09a863b12c90c7454e09d4151b38f89aba72c73c433a2bcb6da895683abf3e59fbe6fcfd726c6d2e8a347bb403389c35454f679ebce31f58a567fb69a6af0c7b53069086308f318ad5dbb235e6fc1fe925721835ef6ee414eb7a83b33098de88ed2cc2fb207a967c9781b181e1694e53b7042c59169dab906746148c52a49cb20f06ccfee87ad7a8e53b638145ff0c777d3e7765724a5f96a1ce2cc09736976de5ad9ff009b0cf22ec049838a6fc8c306472ba94b2892af549f4f987928c82dfeb83ff1ab770ce3f452b732cc2b9e25e99668a342b8f8449517384ab00e567c45364fc4142dc91d379e34adbd236f1472f1dd8fbad48be4d8c94ac5962245174915e1cb8e42ec25fc631d13bbaf6e4923c72d45ce48d0dd0c1c92106bc6aced995cf7f2a130c190e61ace777b726d889bd020ebc1dc55d60bd32334e2469a77a024c49ae4a982e78a5c62b488919d1f93f7218daa2a4a0348c64ff36cb4600946e729f3f2205e070d4f9b0eb9c48655cf74a43a2bfec049230956b146dfe9b5162d0d6570491035f5e2f2bab59e19b2f704049cfb6494bf0578fdada451f491e1aa36ce2719b872825eda710d05a003c3a728297f2dadb66c1b1f4c7ffaa37a706104bbf397580ce1d37e2e1c4dfb88c4e511562870de5ec276181baced68230cb475bca70a75ba98f550d10c5e6c4b4997293398faab7fd90d72b372b33a4787bc7f61951e3abdd9c9904f4eb1583608f6f7bf34687406a02a17b549e3e7568f6c3863bb83acb8e16c48fb21a7f2f19d27286a937b50e3f652e9dfb228ab23852130cbc336677b9ac85b42a6836d566553ea7788f95b7dcd6ed062d69a7321c37794c2c289beb3f51c53ae37d103b84261f74e3b5dce851d247b2bb817d00fa4e497ebf531a2874e56946d708caf442d20b1af5b3d216ed1cda7b7b5cba2cb89740ba6a81f7120dc02331ff3ef4b19f70554515cc4d6a69e0d9cc50f1191d60900cf510881f769d2d461a8ef21523086372845daa10c86d680422b2533e121fac2b89af6c64f554aaa253d423645fc3d82f0bde21912412c4aebcde5d5edd304778ca130e568ed219dc43c839965897d1727879e705e99644f48b25d6c56939777a05bc63377cdf6c6845ea5a7921f550232d862b4afe2e0f0b7a16a2ac329affaf2e445ee66403237e22299f6dabb76872f99fbceebcb42f18cd1dd788463eb5cd899ff6b39162251cf51c26c82686ab72c734f81e742d7ec40e4b2b3abec2d202f588c64a22d33f5c493be4a6c5ceb07203daee71a8a3d115a67ebb825d91ca79a1128f20393eb9cfa91f0adbe3155372d4cda26ba70197c37682265694b02be28382fa905ab941c6b4d47305d5ad14724204b3117134cb88158e8ccf48c641c16ee22563ee48add8167841c43ad7f35834c83841480803fc1a838f5c41ec5f1435d1b7b678fd0d5bd39598c2f40af57263bd93b8c26cc71378c2aca21056c524274b5bbfd4e3178aaf60f0d1c368f972e61e27d8f923a3a000a6daad76d6ef48b6aea445c4d824a181ce0b8218c6aa723c53e237ce65fd59571a83d9de4fbc32a41e18eecedcb4cf84be77677c6e3e06882d2ab1fa201bbe97f455be08d7ffc7a925cdf897a079aacdd8976300872b3a7396fb01535908406e45e6834a60d007590cea6421f2a955317663b134c75f7269e71cedb20db486caa6f57d6b3e336f6b7db34f189fba746ce4ce63465a5672aa75c74941259b258ba0a77aa476479a8da124c3b6ab79ae77e0e572ead84e728914d9acc2b1fee9732c2021a0b2e2be0d53ee915e14050e841756eea5e384728109e39d06f36330503e49afcdfdef39a611bbd955430ac359367a23e7476e23146dad8112b657eae228db9d3603f9b47ae95748f338ba2fe802d9ac70e16172f401b38c339d0c0b7b0ef154026a15fd032ab71a76119624549f7cb255a9d0722008df4865b9bca8e8a403c1847131d9573261f1acdff00e70481c4a3076a5725a6050676d2c56c7e0d15061832563883bd8fea6fa5f302342cfc0ddd7ee9a7289ab4da7cc116d647f5565e9b6d6ef320d86373b373dc6c14d7b368cdd887c7268fff74306235ec4d51286b4ac3dc4247362c6ee630007c0e5c051d8c10e195baa3117f59f0a2bb7c84a77e915724fd2ea786c9e560e58c9942030c020fe9c725e5cfa9ee38628aad6dafadfcc4e97fc22b62afcf424eae6e64b62ad9fd18a720d74d274ec9d7d005032b7f105d90e7aba7ece8f336b3ed57686b31730957262915a4093c30ca431c75910385f6a206cfe6f43d1711f27816149d592c2adaf72fe22ac200b19c112a62467c355b0ca5ee9dbdd6515cabb4ae6340a8fb3402772c19397223001b3691b446c9fdb8647f2f874a52e9cf75d7b7f3d77f7adf17372a13644d2908671bb8288c38b126f71b073d4b152f86e85681cc64405b62a7635ff41b128199b3d950dc052d78a7fbe9b871c4e54ad555f46d47dfe10437c43720839fc9d67eb163cc98d8c1091ccdafd03d176a35768ecfb0ab55d1a94ac7f72c59497eea208efeccf9a3556b41a6b370bb2e4fcbb6f16ec59f32e49c160fb72b9244885d065ee6b1051bfc969d583fa12d57b0c8716306cc63364fd1e821a2c344cb05ddf186ee537086e7a7b6a2f8209486714d1d8fd9bdb3e32bd5eed1472276831a4b1f5c8577610029f081fcb87d11c1f0748130668c41496cc2b00ff30971d959fdab4faeb7616b473e9280c32dcf0f2721a1e1f2e3a0ab097fd2b8d2fe2fee68d455001fc8665af85142173817b4e6ae93b65bff56a8e5f69a6a93972b3f5b29b90b9180386d08df72432afc4af4f933529aeb4798adf5559e6f3b272682dee5dac49582689ca44508aa4c3292cca83188ed9391f29b19133030376724fd37bc8ccc644cc47543a56be25f95ee3a976908c908ac1e5e95936c2696072e7d689685fce3d2711351f8e68526d45760a133c241c414dcb73ecf8b73a0d72459f490960e8b4533b64c467a5c45eac24562e84af4b4c7691dec7d182b2091fb8ef5deec311a3be2174f385c74341434aae0fafe0296d7b95c18d12f642a672eb453a88b1b3f8fbe142e4263b82c72c3da5529d0a3c5ddab89a5c19afc8fb7222db44c85b3d5f4fe4870b827ce6adc2ed324c9e93d4d3be8aa5525d2501f1457fe4032759591e1c83d84db38fe986d42ae05f0235f505cde4683f53664c4d6e5a536f3d58e0203cb8cfd72cb850ffd8633d5dc3733b096f94734b3bfb980872b01db8fd51ddc58e90533567251b88954179148f0d296c79b24dc0cc527ee772393398abf05ce203692e8c340f8d333fc9b1c9fef7f2f1a955bde961e757b504b51b89cb7036091ee680e66724507ee809855410033e15795b77fb73433db1727a62b5040a0ece4e5d49530a21ff657a143303533fe88a50dc826f7aec699a6bfc41479757f8923137222fcb0d83ac147218d7c7faf4e809e7e0d3d99690bc724e1acab013cabbdcab9a75ea5eee29b23366f06063a0b8ad77dd90004e5d42118e61471b89f47a756c8d3754c0fba9504c2774edfaae171cb2e8fffeb16c9f7298f2b4dcfe6dd0546387adc1d7dbc31d2566a12d1c7b958344ac7c3277a43372ba7f25329fa29b160193279fe697f29f5ca50bb7bcfc3a45dcd1eea05dea834696d6f6cf30f6ada9e870027cdc1ed5eb2288f3bc39dc01e49120768772c27a72cddae45c60165454e774b6cc1a8d002e632bb8ab16efc69a23a208327e5d0172376a9422e56330cdae9d8dc3fa324874708874ec27512f8ebe85ff0c03b565131cca0c25cfa1826561fedffae5e51d4c2652672686351944e666a6a46b62d549568fc0c7578ad7a39a088cbc9ed6018b03b40cb78e33b4fddff9102ac97fba72beee244dedc3d80b3562ba029af66cfce7e65001591fd1337682eb71a08df4652c3c3f2e5d2eee8445ff89bc61599a58916e15dcca54c58392e2c50cce921872c6c2dc0ff70455c7698c0989c1ba5decc94ef0ffd63a6977b0950a948617a46fe3582ff0901e6915d598cc4495815562f8bf083bc1ab86a1e17741a0282d9c08b77143ff62e96f54b0de4ad19c5c413a7d89b19ee5b192ba23ed98e235152946757cda2101bd413e2bd2338bdeb1861b667c18efd99adfd0b9b3de015b2ccc036d0d8694403e6172026700795328a5c7355fac473a5e0bd7748e51024bb1fc432a608c7ad9e43309ca52872f675a445d3c4bb483aee65ba4351b90bd318435144f04e6061e24e43a5910edd804ea287e0155a8224bd07221c0adf9965778c51db9ea62fd97ac1a98d4ec254f94615056fa6f59028f42ad8144b2009d5dc9e66f24fbd452e0329100d6cbffeec2c8cd0e289beabbc593936ddb77bc34b4e2b67291ccab6d9c03c013069a666f2222ae08bcf6af56e97afa5b361587b43dbac0172114c2fda762bc3d2daf1c729eea236bf8cbf2b48d208b8501bb0717c0693072a79a9dfc99d3e3d748f4ac0285929d8919c90115cd3081cfe6a782fd53f0b9555493955c2bab9d840ef0f0eb5655955968524fad4c67c30701a7339b7a63a572526e52cb7044f3b66d3bd0a3c131722d212e86777f0c73032f6a89302510746acb509879e71fb2e05bee36644cff72a04817839d2f764a4381f94f73acfa2b7252457d80bf851509411ce48ac64d0cec409ac9b4f7ce536809eaaeb0538cb1727400c058f685b8495c7b509d570b163002848e5c7a4ce048f5ba98ae3a539d72aa53e68e7cbde6646ff8472c0ac2aa36aaf9a01384243226afb2944e071f3d317c1a6abd7b7e0b5973e03717fb68392afaf35eca6ed0eec7957dc6fdd14b6d724d58af7cf273f259353a3db0b7327937dc6823c6938ce40ac5a01ea114da0a198fa645a48c16f64c2213ff964440fae5423a61ed8f94d0923149370668b63d1f48e35fd23614f2b6201a28f40e10692eb3afd3ba2985e20e3f9c3741b2312d72323fcb58e239f2aa46a8639db8b3b38b22da9a67b4392df8dbcf735cad5b9c72381e98e9f14b596dc5851d5325abd11fcb1e8a896b710298f6fd0e1031f900725b6433f9b45a9de59f0e383ce4c94632ad797ec0e6bb28998f1a29c5abb6a9419fbfa5d304311eb26cd23e68092943e4f5badbf1fe1c11fea2fb959c4edf50721cff1d56c58902955b3107b293a077e85db80b9f57be6742d623a5f680ede70c5bd4f50c6ea40af36c56c65d895262f66694db839e9c712c1799d71690818428e5d32b966130dce98798acf572aa673ee9496cdc6560ac836311000ed2c96c40e50611e4792120fcc8dbca108755d1bff38ecc1aa8d0e844d9fcd2a1458deb721089830eef0f2544bf8fe26a1f7a187b7186918fdf80ac8d4ec9c3a85b2e4a725e06c375e6ae0cb62702158e1e8464bfb10f0b8c62400a8880d3838dfd1e0d2c1b96d0fa164d69aa971966535550f83095771c82263aafa8a3b34d1ce8773d723dc359394358fbfaa11925b5e786f10ac01cfd3b5b75ef3a433c57c10c9f1e0085580c2ce53bd83d92d819c7c3513c2ca1824bd110ad293ee05e79036ad5c3096fec3d8309515c050f85467484180b16df4fe6e62f6c654493c0bd27d5c2d7627fb82d6d7bdb92cc1087286b7f72946f6f468b432d215de02bc54cfcadfccc5871370b3d12e59eabbb89200d890df625abaf0ebab1a558b976eb077e30a86772cba39aa02f819a7658bd62519d23f1b4c5ff98563881f0303b06bce6c28a3972b6f712a90a08a5e103046c076cbc57102e38436da6a8665497150d66981246598e55493da9f2e4ab2e75224f6e682e4f2d3a1698b8c309881332d9dcaf10a3726d7af6a5aafdbf30341a423d4079a076de44bf48b7cde5fb09c0aba98a784a7249f019f34ec19a127b912ffda51191860e66e7da1557f3f900f0e5f95a5919123a9652302866e7a22aac168bcce4411d2d5de01cc7ed8e072e4bd754e7d3027279551881ca8765d6a2900602345d2f140be634425ab55dd8293c29c884a76f55457b4bad9f8ed5901fc9a999e56e64230a8b9364b7e692213b752ab8fca256722b35a271a7b772305787de4d7e28108eb992a60311b09de24f755494de762e720f9f1673a0c3374a418cfddc549f529aab1af09bd1970413a7fa47d0ce5cf672249b48caf903d1aa847f47e273cf7a1ab0910fb0cf624b96360cd81ada7c804e28d64b1695c67cdd3abe1c5bed819fb319b6265349843eefb7ec49ad7aa6fb728e1f0b48e736cf9c1d48a52407921ea52628e18bdd74a5fe259f8ee1c1a68e6e64d0b03ab75cb9ee7e59ee512f655de4ae86156081fdd2c9cea4334ea7879d7235a5c5962f51b24c377377ce95a21edcaf91f391dc045dd191f3568fa3046c7109781b9b2c2d901ebfdd4ae17c18769547ee7ffbc2ca5c0e41b8991e050da3729d91638e03eb65144914391472030d2b2516e0cf0768e12500c0cf24f265881720b22f0b1755c17ef46286bf55609964d4e71a96599ee0191d70e5362fc3e958f620dc9a1b90eddbdb5df5f750f840a82ae063edbaf9f1296b044d51eb19a5055dbe9c4d5a38ba7f482eb372c677ccd65844b96eb32b9e4367ee36878d66f772fc3d3e54e8e6eda335ffaca76bb474d5f24477c445ad5c434cd653e81d75dd72ee4e8f38fbd7c34079ec9e7b15b30eac8a6da7bcdbe51be8eaf739a4f6eee172855c98c060c2b7b440c345e7194d98c532d5578dee0d514b1b9d17a5c1718658edf039567f082dbeead9646df87fd296ba35238ceedfb138fbb3948476ce937208827e263597c7e0fba25e030e14eb3111bfe8afcefcc35349074fe7e03f7672ca7a874be32135d139b0c5d76a260e5e08765a743df3f39471377365af08a172b3bef89d017ff2a3e985b1310a8bbbdf7c7c78891524d840b6d542a24116d1401b458676dcb89bbb146dfff99aea80d5cdd04b3409c60960e10498406d52025dfb896c232f824f3b03c9e85c7f0320cd5d91425b467b0f5a411c0cac6d9ef572603be70cd1ec3d2f83c18dc947e6368010a9097ef03618b9778c8f188a62ff7279ac7805893934233e43d8713b8d24d21a3311a16f57c4d84cabc218632834121e2b210edad02496fb25035e2ea0565cde28ac67af0ccfe35534601c7c778035ab90077baa23c467e64665603ab9efcdcb7d15b0cc8c5bb354800ba7a65e3d72f389594a6af533643c059e82edb60335818c9534cea8ba81c39ce57372a532729a0b31ea740f2574798ae48e2380a89dbda534c1cdaf5d421ba9557530761e2b7f76bb5256ebfc965270161e62ac5017ca119c4f821720aa26edcd05d4f140531763c8d60a96f28207dc8bff6323a25e1e1c2b013f3bc981f561716407125a7266c2dea4f0a43a270009b1809a83cd8e09b411219a3c440c55e8b1cfe3c19217f953987cac5d98b320464cb1dc353a798a680182b4278e10b2ce8e3d2d7b030fb6e5f7f7c8e1126d56992b0fbfec2cd7fbe8ff15ccaa2181526f839517899f0f09a131001f8595bacc4d0b33be64ef7a48746aa9ad37c4df7e6296e6b274893cb456d900d1805411125dbfb095bf55d01b921a69f3c2bdd09f00680e116ded2fbdae67e0d24284b972317b4f6eecbb8ae5b03b1959ddd58451afa7f6aa79e672ec67fef23cf2158bd0b29560928682baf6beb209ee3f26a9dd1a26fa555fb972477b1b0c12b8f0ec0df04014f50887ca365d1c9b5a8bbbccf5a679eda0c9de36b13347ca0a143929297a189041ecf61bc5890260e69cae9ce1229e644c64f97263aebc374de4fcd5400161bf25d08c0d7593e40c30be5d295824031b46162c728b7a222b14446ad13985a69e82f25bf1d17f7d0f4e885ee72147513843bacb46584e820144f97601dcafbbeeb84bb9727183a288aef21d4bda88fd5e0245b433b9f3e76815030ca14eeecb2217e94595977253dc6c03379bc2e488377e42fc57da399ba245491d7d45a1d2fb1022dd65e91168e03093def44a2149ad5d5ce1724fb42411a59b7e7475f9d3ccfc6c20fad7c640702a7e6aece591818873307a72050598a13069632fceddc50c31ed046ce5eb2ace68c860222eef792d68979d45a227a529bc82028cbc4b5256198ca38924da6ce40e7cfbddf7807b66b1092e72b274d5553cee7886766a89db3cbfa3c399a05bdfc9407c575d4d29cbcc8d214bbc1a1f878f07433a9b0ae9376e93dac5ea5de2a31a2503a8c7c6685e9199b572212bbcc213dc5150774cd55c2951fe554f561e0d8d48ab8c5dfe9f672501c372ebf0c21cd8cae3f3bee6e247d6828b54c43902409cd0242b712f4127a9d05272c6aeea1c16d2e0723ce28adbb195f9ac8ad86b74b678fa8ff245facb902094535a033a23ecc0d4e22895310949f71b575f017c8c0c0e3d88ad5b378f79aba06bc759ff120f6d2b3d1f2f0b31c17dd4da9212f33b0e0958f78368760453b8352165952f6f6d21bd864dbd4cce63642ea97662809874a1d8998ef8b8a390cee172248b9c3a585d9ac32e170838f5b87be6adab754d6b8689601a102aa1e47fce727444b19a05b8edf23cfacafd579e786c62005c9ef8b2f032d7a69e668c1f8b72de931356e1170acdef31301360ffe392324e4751b30bdcdf5700b0eb9b268772e1cd960cae09c8f9da6f83bb8e540675b49041767fef83a366a7502ab2ce57724bf9608deffe78648a777a0a0d784179b35d1a59f50790b443322d8187dd4e7273ace5336ea872a0519beebee11797ade2204707f33a1028e0e3e5172259a5724d88e3563cafe434dd68c5230bb60b936bcc1369e3278fcb01f6e8f19d0fe172eb17b936c8d2ed6d02f82f5e5ae3e3d4c1d386c8dfddd8cbba3f05403ddff23b289ff33d69cfb57fa987eb7b78e3b27e309a6f38f878f6853fd7714a54125072dab4b3591102ecf6630f49bcf036e93704ac7d060c9609db8278eef0d392b95af0d7535fee35c3bfa66bde92a06b2f29188998c36c67b0d651791b4479036b7229bef46600e982b32367e98b767182366e10724f2fa11325c6a5acb61c1ab472a81ff253c537db3a14bcb378e64e5a537400a2234ee9b8001c18ff25fb70a25a06adc6a7a223e4d6512d8505cec753477189cac5b139c803fea179e73f0af121e60e5882c523909112217dd95107862fa85d6f945da7c8f48f261d1ed7e00872f9aabf19c66136aa87c019739fa61860fa764e64260c80b1dc593072da340772c6e90c4c7f5a80b3b710c14bcc31f0942a1f2cfd68e84a58fcdb5ca58576e572b5455b3618c251e716546ef9d68e1ad45cf5e7b15db83a717613bc83bc194d43fce8d546667b04a600a926ad8dc0b69d1cc18d1cfbb42b03843d2853a7a2ba1dd2be42a48c62e162958857de39391324fb9bb08548b1a00ec338ca4bebf0fc2c6965abeb7faada0ed98ddcd17a468ce7ceb181f75eb37ec7e5c47cb1028fbc72eb46d6e5aa87f3be17ffe9fdbac3d050739c70eeb86187d17d31c94fe799e3722669c766df9b5f30b30be0efad27cadb9f3100e131498c1da5de9384e83a285f3243ec873e96d9e3db26ab4bf2e5fbfc8e6b2ea3262194114527ed62c726d13dbada5887374f0b81cf1e17379c08bcff50de84d902f7a266abe4674c89ae8372c69abfdc4b1a53f1e42407bcc1ef1af391b99afb3cf2fd64cf3040c5c876f872249391341195cc5f51a1288190fe761917d47321989605b94d079a63259c686341eff7dde8569af84b90861049ef74ea728cc46f33ab625098fb4831b48a16302ccfa8451ff81ffd16874fb3639b8e937a093d145d7ba188fa1edbe9736ae57210019e073c20d4ab16a0772ff40416c1d39d76e7b689738648d76f3b004a3157b5501e429534e4d854bf54045afc055e74668b4854fe9558810c3729e531cb729170eb184b387bd8d5c8a26fd35d597e755b1dbbc7ff07972846429de5c91872e293b980cabf1a954fad58082a4c7f4ad88a90d42e9d6797ac3bce4882d3e3721b7001b2e53167a5c9b9f9f603eba1f335e8eb8c5b34e59dde3b245ac51e504c2b719d9c8334b949eb1379eacbd79191653f6338c1f9d194a4a8cb7497847472308b3d2c518e4d6164323a2cfca24e8ae873cec44498f3bf0cfd1c763a970472c5bdb335ce681f4586a6ebf1266cea7ffdb21019bae0c89d2e8d649edfa73072804efda825d5a1f9e1b189cb61a2a1d41e47dc918c35f01b37746d779b0fd85096fc9de2d1117cf4e74641f6279b84ee8fc84861d76b3c76168cd3d76dbead1495a5c3afc1656b3128516ae961de16f3111e38c277861767482c7b974e6c32720e908dae8e80ca9233eebb8f09d4ba8604c70f6054d8302b221ee0c56945ef72a15dad24ec724505c991467e8b69d09f3466bcb29cd26e740ff6a89dd2f7c472018a44db6c4d51372e2334edd4bbc0a9b44bb926b1e02070d8e6e5b11ea76372b4c36f7c1f5af90064a087b25fb5ab6ca947275e934372410dcd4f8c18637d727f2a2cde8557bc4f1f116ddbec19c2cb6f253a0a2fd13c7daf254b128afa1c5dfb13c95da9a2f7e0c9816a63979aaaa3c2b1efc3a515fb756ce8ad28b287a3587422ba0d576544f9459bee76886225bb1fc94579dfb50d17ef10c86e15786072aed361377c47030d768ff3856609b32c38d050aa62c87979686bd5ffe6934e12b5d5049b75c32bb69cdcf5ff72f73eaa052aaf2d121e037b0694bdcfe3a9cd2c23bf2b238ebce6c3237db56470a3e20ba40bba9104ba35b4aa5efb2b8226f572758c1cacd5a316f591ce0d6e4a2d6318fcbef612a1107511957293e5ece3e572a3623c247f716c2db9fc6cd6069f2d0e8cc9bdb9041738381d1009e0bf267a35576c30336f1d722fe2f7371c57cd5032bff5f86863b5a0733a8323ce6abc6f721af4a3b4f8dec1e2c2cc4c36ae1bfb21c996c16e7259bfc2cbfaccb03ad59b4c48fc91e86b4bef3f10809143f9ed67af5f8bb2b4ead02d59c045cbba623fe413e65b40e47631ec80fed9912e0591dcdf15edffdb188b5b343580ecdc482147724a7645ec66ed3ac4930e731c90fe1d6926d8cc6593db7ef8682432a1b936ac7236a3a89e5f81c77b151cf24222a9ffe91263a5d1ae858a472eda29ac1948a872c58e640ae876e66cca9062af833148165200099d692a565ae1e551cce2188772c469ef6314cac89b2f1e81b1571fc8bbaf006f1c89592bee3ab528df902e9d72ebb312f899e47fd9e1a99ca9c2ad5ce204c52449eeca2fc0185a7b0a9c35477250a2a6e1aff8e2812512c665c11e987f9a8f889c2dfc379951379e929337444361cadeb397a8a1067c6ec6ad749a684e2b6c559186d4cf17aeaa15cf5098fa7272780d447e257bb0b69609dd59b96321448f90a23cb2854b3948e450627ffe0e4297d54166ada71d66299d0f900b8e33a77959b4397c541bef81603f5ea0407287de5e4c38fc04e352edcfb4266db88170db48d449e283d4f6e0b4cacc4fd4721bc3211b128120917c7feb226c3730b53ec1414c9fff95bedf1730b3e3898352c49dcf32e92785f7984d56bf610d0f9259ccc2aeb4e05814d73059390d0e2f676107df3c23d6ff8f4a7a11cf42743d84055a052d062db5409ad3e9fb21f3a1721c8bb9edc79f75772719397f35ae2b5e4e2b7e0501219348aab04c84b7dd6c5076506d4ea964defd44900f56fd023bdc8f79e269978d237acd8366363f8d9936de2806629f0a7c16ed6aabd9494077d9f0ee93a9df3b7f71d11a5920e8f324222a4354451b418aa392e1b4bab52153cf382b7d348b50457aa2fd6a1d73b681241a52d3fc7937ff61d37c93938c67539bc78662fefa9a41513d888f8bebed41490d85f441572f4811c643eef6848bfe181abba539651394e6443b30162974321b4f6f2b07d154682d6534e2cba325684488e211848babc1e2d67b8bbf1cf66272c61e31a641d682eba911f38c751505026109d55d009eb7b0229ea1a51e980742ca99e94284d1c343d7afc2dde15fb0cab2a8ef686951ba8b382e3091428f7027e6e1e95af6dd725ab39d897f9396228516041c3a8fa534abfc7898b28eb70072ea01f0917358e9d395841e364456108e856596bc5c7f5e2adcf3c175ba29e159d55458ef869b937c19eaf2658f9516e93adfc9192eea341908e5bf649787a107753f06e49b8c3bd7b80b418f261b88fe4f6a6f5bffa6cc2b84dda055db37ff381d6cb13d94b974530faaad5655b3137af1fd163118a2620b76490b28115812091f6f246d6d292450066450b61a9b85726beee033c010c69253ed26cd8439b76e9f7bd4330e3f44d1a9c0244b8289ed2e478230bb55069aab4a15e96be2580f43d717cad6c334b1abc29077b3e36723795c144953d71fc214cc5e1275c25a2d139239bd2939eb39fcb1ab955194b54ccfbe9175c366e820b9e2b84a506995721839cebc7091e16f6a46cf642cd5f0711f6fa93950482863fe686ee5c2bd5d0672d9a4893e9d146b7911c9e78eb28866019d559e50acfd3475ae5b0d9c6d63dc729db8bf77acc7a3ab42ac1a83b5a18047d23c6708953c752e3d40fd0a41123c2c9d66cc0f5b4fe6e3a8130c5282f167bf17d697d0ae68406f068593a02d14d872c990294137ce31aba3fa3a5d5098f2278c02a5a22b221b3adde814508b9aa060ec09aaed318d4d501c951b6d667c1c5b6fea6cb1b244e73fb35112949209237222383ee91c31ecf579b41cd97b1dc6c5cce5238ad84a16aa24a73eeb72a7046ae0b4ada9112a76ab634db90dea2773029f3247c92a9d0157de20c6285799de72409bb0218cce725dc366c2ffa455087fa00b093d448a04f13cf7f3a75cc0b572acc4ab3030d0dd68bdc7db7a0d83afc59c2e6bdf6bfe901468a3f4643dd70972c96925a81b98fa0344641a9531074b749d61b10c111d017b6ce3442d47c78862de269bb93f622270dc94fe3554122ffffc5a27492516e1df6d6906647400bf7295cf72c80c2290aecc356b1927572a2d364768f55475a8c44b7d10e3d31bbd72f0ee0eb23977dc6c918dd2c7c83ab5c9ecaaca03c4f5de8a54db15300a19b008d1480454815d940056555485ae2fee97db4fcc157bb7eb3535c63470de655d28496b738d6d076d31c669879e338e309c0217b7a4b9d09ae541109e19a2e5a32137915bb20897520ca155d5f813fad35424902e4066c8cfa0751077cd7ef86c721c515e4b751d24e1656edbc0dd3bebc52c37e58cd23c4cadb5fc536302f92772347232ab026e0da797da303d2bbbc3c484793bae5d6660bdda21d8fe9e202e72d505c5874af80da9308d97b35d0e896a1c26c5104ceaae92e50cdc0802487a729d43bcc031f1fd64a4cbc0890b0baba9f49274c6cd380e440f47173148824e66c48dbacec3c104a83d3f01e24c439de37e0291a48d88ba9d06f21369450e0d72f73550b6fb3aaccf78d720b65beac9c2f1f3a2de79f24be4e260efcb859c5b729521b24627f6df9f492f05d386224cc8fb72db699f147789a5afb63f22c86672dba0d58f64cc75ee2a1d6906b8bc65c01b27817a68a3b238787467691b376a1bc1c85290b3c2c0a825596d31694294a5986c38930904bf696294b30d46e95158b546c4f1c0729f2ed2d41efe209af0a6c4b722d9cb6153b5bc400ca4040dc93f0f025040edb77b63d1631844fadce61f29febdd51c48fc3dabd888ff54053772fc12f8ef6614a1303caedd04a456a3202b9b92df1f786f96bed5ed006dcc54589e25e936c0d07c8fa4b4d6b74aa792c3148d9b1bf039d88aa82b69199caf0260d3e2d96c53d2d23c2fc354ebef2151cc4c26ca2d06cbeb62232cfc178bda15214d066fd25ede98c388f99f7f528d7b3e52492a2cb0a97c33074c1bbda920fc230689658f88fd9c398198fc76983804a2e09bd6ebf9ac6936aff6a596cdf61a72fb15dc4542e786f7652942df820d7cb527a51782366cc73974dafd0083eb5d72e0827418a093356228dae0f93a480358a053fc4ca5a02d086f4c1c9f9c0f7f48ddc5d51019f857823efab89f3c51d94505c573a6ee5159e89a55327d2256007299c02746efa21966d775d16349d4eba8e3d52a0099a78b35e4b30ebe2d789972f9220ee10866a2ba4fd2dad4a077f89525c1a212a1ddd3bde95106a19cacf672467519b2ce0494d2a8356eea2643c03e03b1e3e4a8d5ae962bd1d804e426501a2b9f8c30d30de5d8fe2335d440598189661008fc9971dd723fee2313e0ac0611f1e76196ddc5cbfbc0dce77a2b83c089f99c3a3edf1ae02439664ce79da49a72669833863b2d999c8a6435ac5eff17ff397cd08a819a092b946b6d60923f00720089a78eb917312351a8f572f0f534789bec0fd662cbf59343a92f9bd54cac416af34df6a1574b5021fc41e9659847cc2222ac2aa2144de9c1982d0d078f5e56c30e2cc69f1cb0c6c78ce887c0f1afc73a1b2db4316c62fe3ceaf5f518783072a5e293eb3502090168b7bd4f15ffa83f9a91455d8b1cd1c920a91c7d7b623d7298bc438d9db8c3db5e5afc46ccf850e8677dfe8364491b37acf7455ba175a5045b188d9e43a5f2604a4ce2aa70a09e205133ee1c64bf7735c87c27ba87879d6c3bbf2b220b8a2089d612bbe9a345f8095e5f0cff3a12ced3d0e3d4cf2de8fd72bb3c4904cf207984a4c6745ed11241ee08676d94a6137c08277c13442a4d630989f347c64219ae0cc188e6e8a409b6fa5ea2926093f34a5e65e7558c48ce4e7234e47567002d162efc802ed617268e0e38727384ad70978504c0aa4309af4f11babe485256f10ea5625099c4275317e8ba61afcc9f99cfbeb31bf6d434220c72afd34473e8bcd4e850b857fccfedf2dd0a1d60d7b8549d22419ba85979a066724aeb269cd99dd0990022f2c2c16cb29c3da31054f0b23cca14c072da6ce31d502c5c3080f75b142a21c3fe5f15fb1edc6d2b7dc0fa79a509c440a5dcc7d59e1b235d5dac16e496d9b25a82af5c3eae14a01976dd2a5aaba7a9c6062b2ed1d872f4f758646efce0d2aed91656503d46ff7c9e040787dff33aec76b8a351c67172950df714b096b154c7bfe4ad09c836e25927f115324f244ad7a25ce34ec66372efb0e56b06c4542178a1b027ed0afa26da2acd6213bf7fe4d24419dec7476856040db592cca9387dc551360d22ddabb79e369a5d1972b595458fc02b8f9f4f066821d79bffd3e9445794507482dff1c9af87b6b2d29f0f634381241703fcbf728a7ffeda334e56e15abbe00ab4cd2fb28251be88d4d23b4ebece5574563d3772bc682ec00ebc3d35c41f80774a65ba11b48f0d4a11a657604346ee0d166e1e7250535799a8619a2ab4cd74a5248cca73e7cec5446f06708a9f4ae516f9ef6d721b10d9df4d913737a5c9a77b68f431ce97243b70b622ed4cd2cfe8cd6d74da72b2c417e6baa20c83b3de813951995588774239af49c9a1e214157252c6ce861902d98d7d94e88f2017b5099fa85db39a3a726e2ee624673546031af5b967127257b3f2616b3d61b5d545a4d64b7f9e95183b1fffb78b689da2e7de9a1417692f95834641d35f37429ba4006dd67eed53a59cca12b4317213db6de468efc55f7221a01a8a0d248f3ad35687e09298399b8901724a873fad3995fa9ae7736ae049f4d447a053d89b3767330ec7a98cacbb0cd8e32007b9e285e8f0766052469b72374b4f53aadb1b64a80a6b56f6472a96a1ae644a3f161f2a3a33d28ed09e2c041d8ba4fee817ef3c75bc64c60123f33268cb5b09059f3ebaa064c655260ee5727c72b9699afe2fc86293952944101d3e1c8f7c6eceb4ae4e9a8419309585084158481d6f5809d411f9aa81d4e3ea59185a9b4a86d4947f2e3f38c20a52e73949dcd20921972fe16f7033acaf9d93a181fe6cfba92fa38c1fd6fa77236da075722aaa5c65167d6a438cf9984fb7576e4fe21cc1d24c2b6361b4ced97b1030ab72ba0ecaea11c9b17f83c17392da2431d32ac91f4081d893bf5e21b2bd3dd16d725e68b482cb100d0cc7c9ce303f386b80f083aa9c5641fd1588b14edb9fb5cc72232220c95a52afc0fe95fcf3729308b2c9e495e61a53d3bb99a08c5c66dbb2720caf91acf43248067e3f8f3363bb3d556bb855e36034a212c3d2d4f7e9f20872fec436735e9f3fbcc1d5964db2a7f58037aa30c81098ec5cf1ecf4aaf16d4e72f884c51efeed79fb2befab2597b361ea5e82c4d0a6afd84978a05da99013b02a1a30a4c5f83173673ef584ff5239cabf98651b0ca8a2848c9c7d803262558172cdc3e576c5c94fc39756717f81fb63ea0149a394a1eafa541a1cb17f84d70172874989223ed6fec3ae121e70a16a935858719fcae79c5b1719ed9ce1428c1a0d14cfd99a9c760cbb2e7dd3f938cfb2b6df6678adeffa8161c9d6623a196ef97290626cd593b2bc0ff607f230387af1b6b0e2d6488281e69a6ce37636d1f4867252404626488cb5f8da57492292d09fffa605577f478da564d88fab70528f520b6d1a49d70438c39c17a9b5d32b25f3b147a9733b5c74386418ee3f7221105f66ae7881beb3b4869ca0e5fe122ba9a98568f285ff1a74472ff860a18f5cb312720506ae950e2b0e410b78ca2d60196beb9f9291b1f65955cbd14152849a7f3163a7fda116e6e68872b0c2e58fa8659fbdb4bfa15b6784b0e1b431b3a4b2db2f098b8857f1a49399de64532f0052800135897996fa334583635d9568d351edf172ebfc699abfca70d4ffe01c25bb3110fb1057eaa87cdcae07334a035be042c972357960246f65e15164252ad83f75f5fee324cae55e048713673899ba9ab4f52652b76ad1398fa31e9a23cf3a6be5749e8d8789ff09e2ec2f444b60be6995cd6987cfb9ac5402c2ea673156653ca100072a5f9f6a2694ee7a1de2d785d2cdcc4234a8e5ef5daad401c42f6614c2b3a2aa98f09f8f12d43e2d9168521170337571a399ea540f85152eb682f9c98cd4178be323611c5faabb556573fa2b2173cc72bd8942e29d6ac23b4c696e9ea973e4947871048dec90a7bc7d5492de1415b772960e0d4add626e2321049ea039edd529d71f541091621e6b36ec6f5d5f17a37221d9a48875005a689ad4e7a99435f472832dc455eaf06eb243a96f9fc5353d38e2d2e42e45ecb55e8b6f5889241c7573c3ab4da4e5976d5b96b210d9c36f7c65fbb6100ea12197f9e418b2b5fb2009210c70682b5ab53158e59d41b503721272cc55bc047a28f2fb5209c8781b722fe242e3ea642e17c2d2f117aac9faaad8726c2190b9fb27f670b3ae16de3bf5beac743541e12bd81541720275d86fe0140f304cecc2cd4edd2a2d3dcbeb0872d0d185872e91d09cd4d63a218984a1e242726ea142ded46d92730dd3b84a5356eb8caf621ae3e5ca1caf8dbbc2042eb02d50c8e0357722dd081a86e3cb74f8c2b09dbdcb02c82d55817a19f5653058a07364da9143b0c2afa8164588bee98b0432a918e5bab8a3bae60e8166a6236ce6f5729433e67c41667b5f4866215fe3b874ccbf75d0065e3e5cd4f673dd757d6b8572af070b913084c7a949e6f72d676eda3b90b15d67e255c1ce397332aa2ad761171f6b6e9219a5d2c01e2c9ae4b1b4e1a9cab63f8afd67da6387c98e807bd59c724a07c1f9acc81c8721d7f2c3d77959599d4fccd4b5dbfd7cff30beac04669a725b28d79a248ac477a0ea2ca8e9021750ae9218e0acc44bce9da0acc043ca05550f133d4d6f3e1ab97d29c5f25a514526e4db37ed8191f40d85c72b36018efa72cad09bbf675b9eb84adab84ea0783f99ae180a3cb1866148d5f405220e35e8029aec9f5d80745b54d78d2a6cc98d77121cf4815bd93002285c4ebc86d203c71c6e170586fd738da35cb64e480bc3f78580588e863cd982aab1af7aa1e0971726c149f5720eb384e75145fd067c48631da67791422e35bfb98f0c0d56e4749f2e6a0b792b923e8380e80e2ee4667aa8825835fd65e9991ac853fee340ad397272ce1335cd65b1043293aa3f929fe0383beb18c56e9e025459b7d3cee58f0047727023f204fcf424d6325f68c63daf14c8f7cc28d5a9ef91b5076f20b21799704d2bd42675d6454f99137fb9d212b627b1f440c830d18e89233b333883179f947286e6118c75a948a211f2830767d9718f7c594fab144db69098dcb2b2316333723dd0afe3628fd75ecc57b186acb4711d130f021a6f9bdf79d8ef913227916b72ae8380b2a0f93b7ae2605f772e05c23d1798de21bd52199c3797148bcd7cc072d1e998c65a01bdc717e6c543813306c5ece2720429ea4afb19d0761159912849969b275d15d92a6c37f9a5651ffa181f2f63258b45298fdfb6f20aaa764b1f3ee62d0a9ccb2024592e22efc0ffa768fc14d6da8172dba2f9c79440b3ab8ba47278908a17edeabaff2cf203736faa1b5fd7656cf9d6db3ee0f58402230025ee7232bb784f3c64d3a1832c3c867aa930853a68d09f6434cb68dc03a69deb0e1572ec29f9317d8d66839ba85c396febb3610f960a3096353271a9d65893a2d8f07249e3675e356d78aa0631175f7ee72baf5ac1b3544ec8308ae31d720c0fb91c30d690648f359192f43169e1ed9168c8b0f393b5c27b203a09eb3451a01858425f3f519b06b5cd82efd675d8d3d402ef1974c79084ed13031d5772211bdfd46272de8d7bd6cb02dd63f0c8d8aec17625994c61f754d3de44c3ab7de5090cfaee0b79dd7f3c4966f6a636cf95614028577a460bbddc303f8f1cb48c4025f90fbc723b51d400c00502e3807033fc857a74ced1782f144d679cb4b3248ef61ba45a72154811518b23990ac1d88efd0642733f8acd34d4e351e37db6a615f096b8ef7269b8c0696d6694cadea59465d41591c36eb0379e23ad11a637f8b0b9650acd2793b86485dd4d2ddbc931114808f4999cd52527081dcc9a633807f97c6de18644bf017897273f0eefe61500a55fec7418f4ed59c373d348f8be78c9fb8fac4272c55007172c88b8980683e1bb8268699774c216d6b538b3a8f9fc2d1a173c6032157ffc64d17a6c7a38754670a864e13dc4dea6e11ab586f2efcdf312e8b9ab723107caa1fd710d2894812ba8f57f8b72c75f658ce67b9c5741fba7a39c7d6a2ff1d010f16a4c62e2043cc7c9f0f1565a3477ba635da3b16ea7ad627db207af63c12b857cf7a078532c09c8689ce77c56c765e3ef95b3dbdaa625bfd5e097a7569250d8481e419388649d9a434bcd43231efab552b498309f844576c5b41276727e0f43ba752606a8ff06b822305e940c6b0dc34bd62d4e771a72c43b5eac8d7212eca28f7ca285affe2a95d7c34b78273f4f41c335d275d32dd1f1189ea2af1f1b8bc131bec6cac55596fd4f044cbe9f5902232878942aca1f0cb9657be44b72ae8867ffc1793e21b83d710245bb961677f3f225df7886e65eb094245cddbc493502f1fe40e48479e7fabc38e84c1e0f50c708da41ac88f9cf917efbfbd42354f3ea29b92fa805cdc473f1357f0927f31913ab5562fa762e69f76b9a55f76c72d8f64d46258e3cf07e582c9f2dbfe7b2fd91e44785f8951e630fba50a656f5596ab3cf310fa1edbca081e951dd8ef84442cf6d1a5b39d7ccc0b2bd88d9609d4a913247fc97ac21e078d04b8dfdef172ea5cfb0d60a727d983ae3c1ea483952726d035b7fdcf5505d557e8127e80b248349eb6d7648c6708b8dddce21e165715f15690fcd331086da3a64290e7ef1db26fa8b9035df4b0b930e0d7319f9b925145033b0a98f7e417082c5bc91855601c13c5c3f45265a1fa39f8db21e133e7a635813708509f42139861ed77ae30895e4e9111c461c1f93d1103d2c2580354e729da1a08ca4377817a863796c469630d99d6d44cb580c55439befebfde913d66a2ad24a66b5d281dfbc38d20b1681ce020ef5d765e0ff895b501ed197589d130f0143fd55aeaf768f102cdcb74750b81c0f4a2290dfac7bba3f00e2be4c46e743a4c1538f79d2c8bb3588120db17251b6770cd7e9ea29b4e4e3310a43d9917b721b72e403cb71f6ede32843179bacb19206ea592ab640b788eab48fa2fff706606668a90da37ded3d0ba64a47ba4f86d5beb4b10def107d6770a1fbb5aae4bf721393583372afa9da1639660bcc6276ddc6680139453eb96f6340ad62aec40672ad48801ab4b2e5bc98ab060274ce3045f206bbed3a442cc5e39250c404cd077295912034d95286b81a0cde00c4f10bda7dbddbc06b831eb73569de3603704c72ce9148f15a3938d56b17b525fe059af89a7fa8b0aab91672c2209690d9777d13e5b00c36b02c0a596d6069379fed948c9c0240ef1ed063b938a6902d5384cd0ee4843cf1a78441a3080aaa8478c884aa390bbc487e8c2ba2deb99acb998fdb72eaa7069d35ea652a167d6b1ba9c2393c8bf9f60fec5d5abb4d5dafa4bda60c7215f3c5c26468846bc9089f7ff166d9c277700447403da8f901aa1b71c69f59450df7558262d6e4f42aafe3d6a20fef0c71850986eed56bd2663f18cd6989e072dbde4de2a5cdf751ce9d8db5fd1c7260c5c20e3fb19fc8b2183b5b02d2d4342d8ac3ce27b67fac58c656ffe6be95bf4f655a7dfa5f73999183a6f737e0b90772a57131409326d9e24a329fe1463a857cb7d324a676eed3a31b664463e2a53772c23c5fab9e147624d3785405db31ffd148fa48e2ae68e89221634aecb59375726eac8a3250601b500ea6aa0b062fdaa7e0655f6e5f2138a738e076dc112ecb4d6da07ae86660c204041dbfc808f517fa25ac3413f5b1ad9b1b0acecbb6f9de11e89f3073308a5759c7db56ee0877c83c7f61867e5e9242c4672fcf8afc6bf9064e3ca4e58b7e7e9edb4502b003d9b7c9078c93e461dbc865b9c35afa55d57f406a2d8d393e67e21cdd8f280d6569978c631a931626d3b251e06530baaca4f05b51b98ae9d52f9271e14d4a6f838cd542f240e7d05d1643196648be2345d62f72d892f2cfec0cf124a0e4405a0bf9c4e1f8d15241ce6511294638d7dd5efe97720b9274162ec31d0d73067b3eea6fe9764939a973aa806e596f7e8e1959bb0d1d2f0156dceba006a391a503bb24eabd2ef14065a550b4f07aa4a0195f5762e616ff1bb832797b50c1c674434d598d7f887270d6a81dc61f4c5bec963ae4acd3727a23de8d406194d57c618ee7c7f66ec9e7f3b2a2cb2ea2936c37fc1f4953b4723140acd8351f954a0ef23bccdf10b90b63e92e0c48349f488fecaae2b2180c72de45dfba63371d087bcb190b0191327898e65645c662c0196ff5168c9d598872cf76451d93b8e8bb6523fa4eb4b2380babb89ab193c621494e79663af629b80e33cd68b43012b363ceb3fabcc88f7448fb72b0ec38dae9a0d60d8bd0508895029210a2516ffb53486dea112018d442e8b7fb327f43d4ce4ff4a696b6fe8be4723d90d0df5c226641fbe5ded834a4e9b43961545c6b56af0f0041991809c3ae725aca6b54464b71e7e5926a32c8670daef7df867ac333fbd99d7c90b407b8b93fcc5d5a86e3f8bb71f2116c3ad7c7e6d0fc2fb71346ad0fc3260c0a9f89b7ec728c22d510319dfa6eece103b8988989cbc0a56988fbda1a290a65199f77d9be2c90c115ff724c7583469f339bd88746b2d05494422aa1f3b74a824f7e226e0072383808f8c93a852d1b31e947503d54e60ea17455157d19fbd43e22b30788754479845357b93f92b05ace0e8c12f47e2b1e1c5ce3d09acb7d0977e73b58030872ab5a1f05398c01e078f06ed401c631c76be3370cebd39a375321caef3a1efa4bb9fee0b0ccddc871d906326cada581affdbbbbd801ef763b8d9f7d2474b9650925f08cd4642db588b5e9c8f001df95e54003cffe5abd6c790c741eabed332b7256205e363d92c3e25552ccd53ec1e0f6e947459ff5027813d33e40a21ac10f726c234fa2fdc51489f577d280d60a241e2444c3477a5c4bf73208bbfa2c962912ff758829525963defed5f775354fe2f92e7ae2494411eef4170d01ec9f33537295d9bc4297c9ee1c5cf2ad772b804e80e1737bd51d93940cbb4252eddd90fa723f76341791925beae216e47ef11283fd1e150bf274f1d466d36d6f63f1a2523b15220e6cf6fe4d5c77886ec05d50f9eaf077b059fd80d5874e37eb10a4a72c189f1ec9a417e70f0d7edf88524d93e08259da0559b8c108a100559878e22baf726b1409b9a1596c00568e13dfeeee53fec31da159c4d3547dc0632e1592bc0472db4c07c4b682b1ebae50022a83297148c121f9a5f7004c8c59f8cdf688032472a98e3e1ce84854f2f7a07305cb83c7c8403aaf6b16c10487fff2b94f8f2f1a40076ec0217b77eda46ba236bd561d128f93e80a639bbc7fc5d9ea0ce4e261a51cf561aecfaa320cc3080d49c94f73aca7dc828b93fa0a43bbac1be2d1fb8ea272e86bbb0c3ed2501b00d0f288ec36b06584cb29c787754f8d204709195e2f9d53a8d3800cbed6f4a6a9295eabc9b777eba9205abbeccd4610cb618102c7fc35336781e2ed200fc30d0dc533233ba14f5471e11a6994a0ab18203a7bdb22d43d305362cace1ca6bdfde49816fa984508b6854ede4793813c02422631e5b75f176b66568a9e2c9c2da572e317623a6551f25b0a750c20dda9bdede701a30a3b7572e02aaaa357682df7f45026237def11263f4ab7262e1eb2ca368925153c1ea81c88da4304d39e832b6d0de04cb78f18835962a37994d94d20f28d617abe35f842fa84f3f63f41e3e27c1ab6a7dcd18ce99ca8c501a36afd1adacaaec5f679db69ab49afdfad1e38cff542d3421eec160989ed52cfd7709682457faca03a905d72cf9e60d28a6e057bd7c31f466c495cc30f109931e0d7304a7986af09aa10b226f23c0fed335cfdc92b694acd20b8228fbb293e5d2499985ea2d0582fbc5d8171b1845f0d6ff82ee58db73eaf32a2d3d2ae3c5759c446cf6f8a9b23697e6e227220f07499ece7cc6e69b3ebe401da13638a0b6910701bb1b40847996b7daff172b8c517f71b4303533f6d67aebcc6fd2b9f0d503431f0eef67d1b23a6f42a783e29ded0f6883cdf447c3872c44fc585b23fd13f312a83fd3be6e5d384d0b3f97209066bd6381b1be1dc63a771641b026516560d5cc236f9bce759c8d987cb19725fb49d6e6e700186bb7d1a34fc63706e6bc2ad465227ac70352f60b1647b53722f2ca3bd76fa761cc97d151019906b94c11ac07792d88165ef1f4a6778691f2043f697493fa35981dc2e9384cbccecc10913139e6bd24e0c100389490a5c433a00e02f1478f748dc0fb9120de621896abdcef5132d39401246c891626f154a72a1543bbc96f9b45aae71ae9fed26895547e3047f4af6676c086736ed4e79086c94fa3a7ed4c77e1f049621a745d6d10aa87dec9c6763baefe0988efd8e01506f28b99c21819ebfeee7fe5b1bacb24feba900a013a4630e1f2b6db37486bf36729a3503c57d22274c9b4f2620a4a78af0698c1878bfdbf6be7ec6c12fcb74ba0b497799027aaf0b0d5d67f3e868ece545dbfd24ea854d747194488926b00fb0720812a413f3785a5154157dd2054df4f32eeceae5a42afc92919f9d3ce729ce725784068315f4fbe6a7722a1dbcc9ea0c716bad20777d31d9cb6a9b6c702e0d1cf1d5ccf2c8c1cfc90e28ddd0faf2c4aa6fddb98854126fa11432d14d1d6e2e72a47de56660db258c236f2eb7951c5eaef8185861903d153e8dbeecfc0075e47248b38b0efb9a16b40f895edc132d6acc5dc8ffbd7fd8689cde6f88791faa8872ad9e0003feaeda3946bd586eb835d4d2d623fd18b66ceda8bbc7c4ba24ec0172cb9ac02c917922332491745ec16c61180813b594e3aae0d96395750f14dbf67252e7a05435d5dc428affaeb22644b749857afaa7ee7d36465ceda15f03cc1d50cf010f85b8fb06da9461828f3c56f31404331dbe795033da2396d337a989455f5df0ecadfc8de5d3db1a9fcece7166a01273ca74148e4de6fcc0ab72fae2bd3974c4f24eddce759d721aecff41de534df25ddf752453fbbc36c899a170bb9c3718762450e7082bbe892f7b45ad6be27e11b6d93478714dcaa3750836e90a9172bf70f7085b21b7241ae3266cb7c6593ca069b3862fd3ccac68755f2aab0c8f12e5327d4c45bc8775c16ee8a46d430ed76a6ad0f726056360879b73da61319b1e0046ff5f38d98741c1840f6e1c3baf13d2c4c63968b1d3522936d114d9cb6672f97b586f7aec383eb7af224e572650f9bea79f93804669f08f73c266ccb3f372ee301830d230625348fca5e1ce6a142bd5d2761e80802d713d254acb6daa1f1541c2e12be8e0b4f1f0abfd7282e045ec5d611a7506872789f5392685809ffe72356605babb0f12777a07cf48cfa0eaa0e922e8497210247a4e2a72819f86ff322dd13d7f07054f2a28c44c002b5c2ede949c791ad96a701faa260226bc90ac72e5cd239c90e8f4aaa71341bb8bc1f0a6e4feca548cb8a73fe439233e95629b72ac21feab2168f78d16550537444c54744baabbcf623db8e4580daf2c5c123e67c0bc873d2437f5133510e27c479c586537da651cd542641ad3694d7a510eda2a606b37e9cecd6c56d8c171bb3ab27cc223bb1403909d3f08dedfa1df1b959472b98373fd3b6ccacd53abea6b00ee0e2c68013b9fffff9763c3f50ee84b329072a9c07f399a145aeed35379d06ebbc722aadafa49cdb515b5c755f9d46bc597726a6b3e40aa706664ffa73fa2a3d96c55853a9236732e7ad11d79d69e7abb3861e359e979a5472c3ec55e132bfc00e99303fc89c5500d72624c704a7bc2fd75726977d73904e4c4ec5232cb1a1ec956a45ca548cf053963b0410e25b1fad01a72997811c9decf014a9cf91c0d3919064f34989295d2660aa479b2f26483e7ed72f9455597e95c17525b7ad1ebc4152d1d4614343e76da89ca88f1be1e1edff8728a13a69950db3da2615d5807c89dd2715462d5bb416d82733c8da87735f7617294b09797eae879b0a194eb264002b680d45791c2db0f906b201fb394f35b6872d3e7da55c098f18e91e1e5cae8784ae8e5d1cd83a93d51070e2e4d62381f2272f060a7370073b994bb7f6e7f27ac7c25779efac1b24148d6971966b67cd1a06a33e4578a8be3ce0a74f0a6e08bf314e56880f2ce528ec1053389b0579685ae7226435145105363bef0ec76e298cfa494c6c5c578ca9183a1e2441c947bed8472ea8de8661a4347dcef48154b108a51ea8e84bc200189b678756203a839aac8108fe6bed341760702c11062a39ec422cc3cbfec33af0bd266e105873686ef4372aa75241966c15debc57996aa7f1a4a4e96ec14e5d9cdc8bd0746110ec7c3c772da63ba3b29204c940b61c2e174121688026ba065b287c53f9dba9fae61386a721f9b98a2d1c8366d0d80dc92d72945b593021ec04735c5f6533fb9a01a639239ffa7c31a8a1b7ba2ab1120d9813ccaf051c053fcf186cd82bfc9be98dfe572724800b8091b0e22386e38b47b4d6dca173056b700df9cebf65bf0d1075ee67f55d5e50c846443e97f979552b5011943cdaec78b15a0b8676d391f41e7960016727c285debedf9411db93e4f7fd7ff8a0d36f7eda7f2fc21cc0620cb05dd8eae72251235a0aeb380a6cdfef6706e4d43a65d4cf0ccdcfd8b157655d302e4340972cf7b1b2d0e29f20e382a477c6c1d74f2c42943e1ae56701cb0e0c67ffa3aef7229b44f53dfe66cf94d8696a9d42632bd3850e70822c2e980cf4bd72247a4fd2c0b3a5fbf0f83b34a21c850f49b2bf809a2db52f2954364649bedbdf2f4b94c6e21146c593453a8e0d373bd65190a32c3efc888cdfca04188bccd7abe1322ee72999fa22445cda752217f9af8dcf6b82e14ff8b037affc1a4cb1c47287b677472e05208b9b1b67323fec0320d5b3432475a6eb70170fa74eba11ce450a89cfa721997be6b78209f5488c63f9d4a5cb2219ce2dd5aac759b8922a4a33fa7d6fa7270e5ca6b5067e4c437e2b34024448161cab3d6041553c5ce97874927c8f9767209aa7fc12c4862a9e9aac13c973155c7a25ed1aa40c9a2ffc404a8bdd1cbbd45c3566b6dedd1916d2249cd9b4dc34db3a59862766c6e6c40971deb689dbf1d725a6d62ab5de8f6feb0591eb23b2d9880286be0d39dd07253e612eecaba44b428de586c41b0dae74964d551ec4397e94a982ced73f791e9824baa1a7b78f787722dafe2a58bf8923ceed3f17f8a9df51a1c414d1f9ce13f36d8c508c7e91e9b375a20b24e03daa0fca91c2505c1c4ffe3031ae7f7c00b1ca5f3c03365be87b972ff00f1a4c9c763f8dda0cc067516ca92e3aba00a5b27900338099ee575df48095f4bb2ebcd86fb9347439aeba6fa3f8d55d8e0b41d36ae79088726dcfd7da652c08b2a9173a524d913e1a8a745a1bd54db3b8f9e7c90b18000f1906fbe89f172b53e6c20b6e1f36de0945150647f3b42fae39d9e46de49a8c24d0d75f5389c41f135e90d4e911dfd4377e0a4867e37758efe2a2912f4359632d9bb7465f5eb4b7c489555c12bb49e86a4a8b07c066f37ea33c458ff3bdd06c7a8e01063bd1a139285aa876e22ef6750db6cf1760eb8ab8b67af60b8fa2f1cc2f1bbc03c038c7205b67ffbb0612674e7d4662717277a7bb2665ba740af4dbccc34f6921655d372971628df73cdf30f1c9b8dd80c2cc14e20e426ab3b71b255a003c1509c735143b6717da9a496ec2170d2e151207891d0985fc2d88c7fe3eff59d85fb37b2c272bfc8f619219d6cbf2fbcbf838a3c1ef005b493b71ccab2774f041ebf62f84772f4c3d2c9742bf71f677dd6c0bcf7b7729c325685d9e28fa4d0c30a30020d8672c79e089b70cca0cb00b05762ff4f3c3e24dd52f3f6704dc42b814ce42816cb729c6751e34a76cdee4aea55cd4953681b9a0d19c76f2026afc626a863b09f997296ee1e01e3d8bf317ff9cac0b86d3019a43e95c8dfe17052daea3122babe384574348c595bc1e51c2e70945029838acd89d298cbb4da4377296196a228d6e3728749318656b2a2e85a4a4515ea894f3e8c1938c8574676b1c203dcf5f6d29d728ce65a6f44bda806f9f96f817dd6feac9c361c4f53816e81633bc2366d49567227482706d99814874e8218d00419e4d48762ca399af3a962c26b106239e4d1632bae168cd793b51cea15a74da3e2309ca6b04479c195e62cc4c4138fbf431572562de26c323e8a710df180b700ef11b37d17a551c6e0c9da8fde4e09717be95f19dbccee0a2e5b08b07d9b90d3f6d01ac1e1bf3910ecea4a2038a900ebbaa0293a28d0325199b81db47b21c6151b04901458e4123c3efb93b4e6150bea21666d4176c75819fc2a2327c1458bc14ffef5454387c5b5049ca57fd0df557f80eb72e5a695199e48772287ab1197fa018610a33b9d4a3421e33fdf6b061e8cc600105cb97de542e7f33e63bbe98bbeed5c0dab5fe535faf200ef7877dd6a59c37e316aa4936416f328ae82af5bbc674c6b9d91f7cd784f21856d05d72bc82fb2c47287284d5dc298487a0e49bf536dba76506360f80bc83107cf6ad5b31817e2d372e114618202dfb5a2ac765d834c5e12f9b6eef15592ebf73974918f05393bea72142dcbd9cb39bc43c18b896d754bac764b4c8d5782d0a79f73f9f2fa4d69ba726e2c0cf2b97c240451d09edcccdb2e081050f9880d3e6e7d1fd5926941be06723a15b6c2e4b769ef29d417d25b072620d0bd913fd46b2eb89c28235a89461c720e19fac4272a6d4fd467c7f546a0c8a6a52d2847f6feaf618edb0e9fa5664e72d55f7212d9e0e3652a02022c574f00276549c3f604b33772bab72f60b9685872bff3c7af4b7c6fe9cafbc67703be30d96711be914c20746644851a323f398e1d4bfdd780799aeabfb1c135f2dbf498fa5066018239f436c2a51d4b60b0e76c7208593e02819f24198e7d7c8a5f4c196ebbc0040555308b203f8c24bb5c7bc45eccad9c61fac41800435fc532257834913ea60c8090af5a9454941564b8dcbb721c20e53502a20fba5af0fa23d0c3af1d809f69f40bcd9b438e5f05ae80deae26d3595bb8a6fe7038cc5e26b2e5abe24d5ee261f5eb8125edb2fbb2c929660e729accb5d99f4f7c79453ea5faffc5a8ef08de8ce99507e43316cc15ff7469a1729bd6d134c3b09ba9402c048401896867330ba5a7f22b623275849283426f037274340d62e478d79329bd15121f013fac06c5e58142a10582c2d695ea09d0152a6be225183b9a84d611ba1faa4a2414a1831c24715741b6fc3c1a281ca174e77251900bd0bed0ce89d84952177f3debb6dfef647e2af68bf5dd155f42a06d3733977926f25747964b2147b2877b604a5f50f164c12a15087dce610a941a6a96728b55e1cb2437184ed861f81931618f0fe80487638bf16f37993d3f20a5c57e7248250433c02d5b7614ec0f1d8e2cfb131980315960b5acb9ec384b7de393954880b54b4a6af67b66686fda0660027ae6117187e8a179abe52452582fd73d8243587341968acb3546744a25b23b62a08579bc093e08b38520c92d12acb043cf278b832a00e42d75bb287bc3e6fbc0fec1e50f9190f60de05d7ecd25e0c756aa6cf204902c4b76d29e3d62d8fd77deefc2fa07c3ae79f8f420eb25022831dc6e409ea0966f46fb625d257bd08d9cc9989e510448c6627fb7dd48796dd5e59e810ab3d896d7b461f4f40054a4696bed51f89772bbca70b65735054fd39205de1072ed52da45d726c0acbec353376aeab438ee7b871bbc2b5b25c1963d8f43f3511d1559b20557b2015cbee39acdcfe7ee2dc0045fdac32c60b9fbe22c86ed554b414861e6e457f90c85333c788aa0d5c9a82da9427f618b0543f3bc7f5dc2aa6c4da6f2e22a73ca804027e8357cba399b688f88437f37199680cafd7a9933dfdc72e1dffbce549a30209a6dc4010d091a821057ba7e8b59dd96fcab63480a155066e2dd279e9978651b6cbc42aa4b7003965065b919ffcde33b4429b9033b90f17286be1b0430ac9a1b7613264d3f92e458ca2e0caea89b346dda0276ba1e3bea72f2ba74e68e08b33865e24510d43157b6a71e22691d3fca0a31312ab1f678c160df1c534f6dccb7e2339f1f489feed462f23d65d0f30ccbaba4b9d65d09045827643ea87c1c13469a9ccdddd3555aab21c0377fce0526af86d78dd681bfd3f072aebc2947621d3f925904b8c23b5aa2b3c9b6796e4ccbc2d857902d3def8716134aade27bb61c7977b98a674db7793ead0206ffaa586e72b45b468e1a61f40d22d1348bcca5853c811e3b5ef0e4fc356e54e8bc964feae754ba6eb7bc78e92a2e2057906a834e49c87055e6a7d64a52c604b7f9dcc0f644f23eb8518d54b1161e5604ed238fd7c7d8a9711c7addd88712f822a72d03413d1a1c04867c09a4c972df4bc4460297109002df1d0715fdbc2a67dfa027b7ce72a363f8e0bb72166172e7bee519df75fdc7329303e427502b1c7f2747474721c082d6afc54fd5f6e55af76d59dba9510ba0a5cdf5f3793591c1aa0d48ead6054f573407530adc1191722d1be4f29515c353a35c542e7ae2adbf9ceb2576dd95fb695b838c4a9c90707207faab87f20c8aaca380715687d4b375083e8cd7fa71acfff93e26d25fed3c72496ace2b0b1800ee7527dfabf25442dc28be23867b8acf14fc36e4b92176754f5bb1a477ae0162c1f8c466f0ac81951522c1036c46773ab59c9c6a57d20ccf72d6e13066cbc2cae6477d4bcc34a54d70750cfbbcaa4dc5d131a375508d2278057528a015454f3458ca1742d14ac39b64145684c648892caa13350ddd8c27b772cad01c60c05c2d1a9ad2a21b165f0e3c563fac7baa6860844ae62485d517be72039d7e267d680cae82536475d9377ed840f9246fb41ad804f8180275c698aa721873971dfae933ef1a66322ea8ba57c51ac8d6772219626212cfc1e2090f3604ae442012b6d86afe75183e14f804532aa4de11b5bc39fabbe1e19f81ec936a01c0423f0beb441511c7a1be85d1d9b39045008d7e6c629e10c03d14c73fdf27727b8fc05b878249534533c55539e12b3cf4a00131b244a03d71a9daa76ddc0872e8cc0a38ba59bf608124312444eccaf24f39d65c800a99e0ac02c34363269b723b3735cc957874915d78a7bc4411ab32bfcd6b3a86ab03282d4769c3eb7a1d72e5b8c5e555b5e728b85756e50872aca9a8ed48874d0fc7f1834916c8f2c91e72602f52c3b9861cfa3aec5ccd16114e56ba9672e2e25566173ed7ac4ad2115f72ee29c0ade826f53e6b87e461abd3ef061ba91a70c062fffbedc22878cf9b6612ece8e736e61ffd1e93c92c7f6077c101739443350e8e724a014eeffe64b63a7258357ce953eb8da64c74dd6993ab9a55900371d2cd96d5d43878ef9717f4ff7220f3db996e67032833cf2e51be36ef7c5bab7e96de42f6f4c8cbcc49b38c4a42b51bc9553975b57ffdd32bdb996d61cf9c77ecaa12638d5b4ecb031d94cb84727f96f8d5a57cc69134ae5de72237c5b48f66c54985080c05d7c56eb9049a30727016d3034edf3e22f255dc5102b33f9fc38a7700ed88f58a192936c77cd05a72c6f83ed9ecb98d4471fcf1359b9dde77cadac809942dfdf1fa1dc3fc61afad725b145aa320c757157d7f570503bcd5d63478c1527df5d2c5d703a735013eb2588ef279cee44ab776c9473474b360ff01221c1cf39b0b505d53d396fe3df4864cd42a01b4f01211a7782f595e6571bf262680818836bdd41e637df00e94219f2843e7a57805ac6a0f77fc09a5312db8ab8768ddf1b2afebf4bdfb7d6550f800727e75bf14c48b532f393edde7e372a7870d9125b4352dcb3ebdf6272090ac7b72efd38a0a52cf72f4a53ad30e81cee5d416e3fa2c89b03db820f7dad23662bc02e3b7c28ba35915e5278e00861a5527d075fe213611da3c050cda5d10fc0035726d62d218a3268873f1a836469664dace1ea39b248954c26135ce76b3c2a359239c00639facd0788251b7292658ab1fcdf896ff0014129bcff818823a284d9531fcbc48c25ebf9fbc37c676e2d5f04f62b686327d43416f767658b3c3ce33bd72dd1b04b62e57dbea298cfe5e11cef3787399f5303ef826feb7fc554bbab3c7721784b212b35eaca6a51c5202575ae173d7943716195e7f426c7dbb15df607272a5ef38d7f3a2c1ec0db7587c20e7fef46a04e4383e68b9a0a18bca556bdeef72939efaac583a4719c3ba4bf625025229c6228b8d40d530c243846ade20d8ca5ed67e25754485e89f64a2f78744abecf8dd23821f682876a1b90c6baf5ca4f8724e2a714d761e0a1f7b5a3586764a87c32e4dd192f15227ff18ba4f962d3e117261bad87f3e154023330ed40a84b97f4f18d6766eca2d2ebfbe9d576e6cba8c09896c3e2b187d8765c93335e79a64b5d94147ab7defb7749fc938cf6c79a62a1172be00996e0d619c6948736acbf712d25197ba3a273be82241e7e27958bc1a36b4c59d1fc889a6856f5af4c1cb7fba90f50b4912bd027472656daaa3e0cfc9722a2704b498728f3be8198d34ed034f499ff9d80e54651e92fd1687544603b144105ffcf055a1d31655f94b48097f6a61d7a86ccefc66a6a60ab9f0c7c050b972bcf92109a0fb2058036f65691597a1035cc663d3b37d174996ed2f33d2009f359f4fa8d07ed7414db471e19575e3ca22ec21fe7d2d9a34bb96358e8f28c5286943c1188810330d86501c5eff34b23496738e4691ddcc3a17234fdb712d798f3f38af78e5f2743a0488ce585abe18979a8e65fdf01c311d7ae7539acfc83ef672449cfe4229fe4b95fd25e7433580544e3a68a5707920ac5c1de33fa077284a7218238ecbb0bd2fc3e6ea42e8207c44bb65f2771edd2228094e3b965bc0ce827208610729a3c40aa86e2864e120e4d8e59c99630d4099b3fefeea8665fc1ce75fdce8efaffee4f3980eef94fe35586931ec94931da2ded0f600e229bef93e0372c72d468b25432121c9d62bd1d4183a34d52b222642eeb9e8e5e824bc7096ca722f3eb3a5dfae92e5dc235ecaefd8b8fee9381af6dd879e70e45fd65c1178ff726975e91d7867b474343a6b0067d547f6d34270eba7e4ad05c5107cca6f67d42fc9bbb890ae67e8fa4dcaba750679b2993f633049a58b68a9b05cbb638e8c16727bf80b1c2473d0a53905fce11932eee664c0c376321b2a192875be030d2bbc72741f7d33510b399607529d84755b14ae156829f789a6d5bd4e88ed47aa8fd503a949b72ad240232c12cdadbb4f0e03b2a24cd17f466a0e920ccb1f1d2d97ac7255c830e0f9a969046d68d5faa9a44a71b3d41a3c3486509855b186b8ec71e172e273a19231a2c0a614e82d3d605ccd16a1a0bb4d39b9645acdfc15e48dc44572f39784e62aba6a88428876f972d1acd2750825250119b35225ba640d4f4bf370ac05043a1624e6b96cdc68726502574548f00d758933676164f6a089a3822a72a6823bbde895b26ff61ad012cf0c1e403040341e9636a81f01bfbd7e08a5a372efa4d817dd9bf72fd4c3fa985e3da740b9625f7b0e032d4e27a5352032a7a60ae4b82f93e39dc5bd94d0619151c1cff3a9d2953b101697b6128c7fe650caf272035693707a42685d8f9817f7b207ac9c27d8c42ba092276e6191f93cb54ae672b8a2619f7031d56d4b9298c8e0b59aae627d8e7e6f6038d3d3b503cb0fc25728c45bfedc92101088d693748801d20a780805b836315fa78c1aca06e36ad03072c0a408bb758285484a999ad3325bf866ba8a14d5a781b46f03d895a078228f72d92d7ee218a78ff5ad0898556f64774d20145aac5f665a01ef4a3406a516060ef62fa955497234f5f94aaf276ce6e074ef6e162f8a158089f5a1a5c4796f3472ddefceb4897c8f2d9809247489ab147ddfbe271b6424f85a041aba1af9e22f72faf02a7896b986c52ab038905d8c19911c0774651fe7654c45a255e4ff984c72f9ae5f3a8ed5ef2f0c079716a1bcfc9bca768c05710b556c7f9c4731d03ebc4e393d6695f0da730e720f65d4e86c2258fc330fdb5991e2dec719788ff920f872f452b3c52c9eecf2d041dfdb903c8789539379eefdcf47b48405cebf41dfcb7240166c849f68fbf067c4dd1e44d299a4d2067079d8304ef7c8d5fe1eb99b8072b9bf2b897f99c2e9af03ad8fe2cc419dc578def521294ede7a4f643616a888721aa472c5a0204ccd2e92eeec94543142fd895e43e736b93db504c3c07ef43d2836e1f2d66c6f4ae1b2b41f9cb7dd13a4eba51c2cd2372a6087f0a4e277a5ce729188ba5e326b25cadc8bb5131ef822e584a5eb999a1ddbf44686a2aa8d4e6472f15ad9894f71ea50afafac1be7e5732ac59d0ecabcc895ad7cb498b750d17a72c5ef9b1886182541345417f79f2617df2c9d31d8586cc67cf006191f5c5b6a4b74c46328c01f1db958d15ceb3df054aed72afa1c332d9e062c917b2610181b72a66e41b4f8cea32716b256e65d58811b2df0676506c3a5e0d69fc3b6b7215a72edcc8c6aa3dd4ab4d2ceb22ee03ae31b8d00379b89d4e8d83ece3f538aeb1d72d08b0f0dd878b3ed37592ab8bb7bf9f4813cbb8e03b493f29b2ad3541f3c3757c4f33117421e7ac174c5e9ff8d0f996ae690ff43a5ecf279606bc7c393320e24ae3fb0cef48d39f24afa42791cc6469a9d05ef4f61b17efee99c72677f24aa7200e642591581ebf33c92c9714ce595ad7efe1f2ceb6246f179ed24177c9bac72b5a699408815709a14c1fe7a16698b03eb27d00881822681b4b573495bf2d36a45d92e5232f91d743fc5e162b783d875a61b2ef6b81213976c43ca5dc5c4fe7208952b8060a9724bcefc515749585f23bc3197d12c42da4775a9d230113d863b734a0ffaa435798eebb1e6c8051adddb43d310de7bf4e72675a2a715a3aedd72a885e62183a02737bceac803ec95103c22492ad6b7c22e749bc468fc491e65269a61e13739c8e4a305496d7e6c58ed918c9a6c6d7d9cebbebe5df14d57c20855e34c5988b89d5179320d12ff77565810410815ff07e2f6628401586b081f877247169f5984a921fe9d4a50ee0ab73a9a774007e7846ed9053421f155f567bb0e8d1e59481baddd399568a50ecf1299e98b425b5ab263e216c658ba6b3260ad1d88fd8d9976545fa079d324cf55d81c38b2205b8275ae0f9c1084605cce01957291174aee07bbcfa3279db922c040b7599a3eb3798173c9bc54019965739f6b4de0e9cabe17fb70a9214267d0c842b009fa887436716289b8c947d04f343524625691744fc4fb1e315119869bc85605259a26197508c12814134c77bf84abd77282bdde00e5a88fc0be525fc1f37eb31e5a4c2d948e62177a55ce111c1dc0df72a9a84a846ab60e6bfb8091e7ec14f16723dfa109993ac2b0867351b6216fb3722937f2ec6a4dec2dc0bc6d3c5c31791bc874b9171eb21756ddd6d01c10bd70325c1a2d8211b111a304f67463562c1300e5d4ca3e120d041b8d3df4064c9e5342499e3f068edc625673a8891450e8d48ba49995d03ac0c32f419a8f146c0d5372a38af80ca24ba564376723f1750383ad9204cd18fc252e9840e70eff0bc458723cdbfa54de16b34b49a1ec0282d5f4e629da8949da4fa6747a33a200a7cbe77295954cebdcd8816a4377c1f2abcc35f88300621395638661ed8d9272d17bdf7004c348131ac48251cc57cdcf22b115f55c4fed181103e0919567fe87911221723fb1baabac16c4661c16808e1611fb5054c3de9f380d210bfba337f01499030a0ac1987ed297b865ed887bc599f2efacfe2b42afa17d03f28bc5388a99e1a05a5c38314510ca9e77eb23a9921853a0a92a8daeea07d622ad202e1dd0fd264021aae8dc384a92b5fb9c03822fd0e0f2f5c042264869757f1f2a219992b20eb872de80c970be3cc28b649a0055166ced44928a49f7d352462136522004a490f9210f6f231feeda2fd03a363c4d169f493980d8de827e3f5894fd265933b439c472ca2e15524831d84c789c788bd7ba31aeb7254c751c5bd7e64b3da3a26c6a25725750e9ed536294d0da586b7c70d0c139b203a2be6ce5fe346081865e0835385473c79e8391c0062f96d3b6be78af578946c1b6b013e74abdf5d6f7398de75d72378dcec57e553d45ffc91f206b44e0c3009cbecacf30bee8e22870fcf615502da8b818e5d38adb03a0ab57cdaed1463fd8e099a8ab985f77c9ffa67cba3cae7271f20924fedc7c1bfff89fb3dd3d2a736140489c5fa35a667cd841b8ad1e3132d16956cb541b10057371a2d67dd630cfb009a52732248884176f10994f30a745550c43260dffd649c381db7f0af9d9d4202ecd4ef362dfb7f6a8b22b48f4547259eeeba86510d7927472fdd6fc2dd49dcf2b7ff459b31b17837a0a4ffd632572c366e11f0310bc5d9125b67339766ef4e8a8174988bd20985ffcc0baa16d061446715e76f64c400b31ab5bcdf3b6e19414ab27c8153140f1290c6e869ad1007269f7cc4ce5782fd3743c84d2edb5fda3af9a37bead3894bee49e8b93bfac172cc47c5b59c57495cb5ee612ab2fa365e13c6e6a80f0abd62a099dadc16a6571723a5361e912a5f3bf0a0206642bea830c74de9298b9f5bdeb743eb0db4acc2f72f0e95d188631a477e8152fc12d23650a9c470686042549214f7828dc316ed8727e8aee18d723af515f168092fefab573470202e0d7df6331d8be1122dcd494725c5e2b230d965d621df841baace20855c9ae8292636873edb5943d7ad89d59726589b66fc28a3eff5fa052fa8c493405f50772d3623fbbd512e357a4c4d4d572f1de48e84db8bc29ac383fa0d16232940d204d59e40f3719eb7195bd618dd972222f51e720dd762752ae0aef2c96d67dc2feb9e9001c238b57b10f046c08457238954f588c40baaa00c4742a905fd664c548aeae3ee9701788779b1936416c71b170767513336fc84c47bdd922abeaa37269084040fec626e8a97124864a6b721104fd6cad22b071b598e463db94aa935544d891b544e94a7ae48e1a4539441bd886a092dce075ba18e253874d178cd1b910730ef3c768e205beed7290fcaf72a927e4861055e399b441535b9fe1bbb0e9b60bbb9f442c72d59b1208de933941078f9e3d24f05a2c9cff6ef61638ee3eaeeac36370c60ab6b96d39dc3825756f65d353b701e42b0ee27aff1420056e8207b8abc4ed2d7fc4f9e07af927bd377256a3822a1a57291bbb4b2329776ee4f0a43702853736891e4dad5238dabed44538b34b023e78487a70a99f0aaa540d0621f2a9249164b5d1b9c1911da8d30f5ec36d577688f082374afdca5e26eaedab4b56550fe38d607e84479a8f6506b0720e1df9d5fee9f925381df0fdd45253f996c34b49a7e5ec3c8716015d875d5c2261e734d8a119efe4716500710883a69e81d5068352483252d9cacc5dc736a233d049a74ce249be0d2a63ee024c80209ad57fffeccd843361d9d95fc13982cb25eafb5f2889fa76294986e422ee7960d5f480b3d9c17d769cdcc8418e7ce26a72146f0dbe43478866c58bd0a1a0afcfad75d0d193cd8892df555413acdc64aa72ffead18292e5a3b11cfed259c51e44af50d0855586ccce13b61fe9e139397c2bb80ffc19427995a972809af7e3a4fb3985ddc83c32b0eef74efd8f609b960d09474cc8aef1706921ea6889b2b9d13524b399e08e08e5a481c0a5204b7dcd2b72266f272210d776b14603a26ad1f97ca2c1dd23430312cec781f2aad684e2e972b7117ea0e75faa0b6bddb06936578abf8297040e982f47d8009665b583ca3a72bad8330a1c3e695c3ccc4733904289a6ac40b6d42b6b93292d5de44138f39547ff5162642d12ce474ac64ec70e525381ec65354c725676c6a213f4c7d055d411f615ca13d37a2525d1a9c179285e9244ee41c46a0acaa6b55a548d1515c299264f86af4f583aedbf28f85f5a9a70c400a59553265acf755b90139edb44f8eb7289e298b833845d8a72e3fa8d837d8385e98e5f0599985b3100c241de34f476354e54f6bd4804f5ac2a7003544fca5c33a17d236f825e404f44c4036a8d2448720c0a4964eadf10785ad188d9637a2ff2fe4934de697b32da4d03707be9aa017248b2f3d4fc59799f29ff48e3ca13736952d23a3a5167351f2a9aeb9cc5ecac26029f2691232883c8cf32fd69a6f8cc33d7b7d488bef5b035b89e02fc62b547722219337407b52d930acfe8479cd4a3bec0e30d4353b1587fad49c18bf108aa2db4fef44e0168f225f1bf170af0e0843948dae27352056074df22aa8bf1309f722ca5f63216aea3b0ff5fc4276bdd371a8d9995541cc7268ca3f052c9aa4c18721ec4b89097f5029983e28699f6bf63ff18e76bc5108dcd01b2f0652d101957330b27309be0d30dfecae0ef55b71d74a90dfff9c206fb5f8a4b942c6ba7406f72f86902a24c6e801c31bfc588ae2ea82541b24c95a8991a3e57102d2049b1c022aa30286fbf9c3ceb0b80274ab9e66e47364722dd505a25a93ce92635f9626e725ead9fc0a602f495aebc8547d6c57e3df4e4dddff279e71fd5c1e31f36f09452dae737643da8ccd3bf7850ea45d72bb9a570f50465daea658ceee9ae062d117213e30d019c0dbb903cf87643f9091271e9572a032c888669ec662e7ab667ff7236c85e1cab5d2160f01e3cac98bc778a0489ba4dce38125e99a207d186d35c72d1ebdae979c21050bfb89dc1ac47f4a1c6cf9f177dea4c57bb367201ce80037278e4ed8bb3f854f89f8331601a8e765e0176a4d2e9203fb3d6ee4d7e0b583f4fdeee4a02ffbef6bdf27d7bba5fcd58e66d1e7a0f385436549268c93982ef637269da54e428d2d5e51f8ebd6d21173e87645dbf199e692f0193bd40c1518bf9397aa2e28066677ad0fb2727b18ee97928ed1499538a45d55ddb61e54afd69b97201ed8ee18702aae2de02452631b95abdb6a5b56aaedf3b18cdd87ea0c472e40ff9e59331a4005c676293fdd8de4c9cd8c7e101518bc1df102002f12bc6e4ff64236f3ec7a35b5d30f3f9df3a699902c0a46d8a168ad6d161569ced9d0d5fd944a17e62bdd137eb7828faf3706835ac54df6669e59c1be2ae30e8f40070a5c0723b777c6e17ed2af9c03cc380596c520c2dd7fd1eb88a9bbf8e18152fb0578d724c9b7821bc33c57319720226365d0802558e1411af804a9510c4511695a63b72e9f160810b62f422ecc291518e4a4882235dbf612a46da0951e0f0c2e9b626725f8ac4391be332989e240e764fe286d7c7610aeca132142ee165a3f0c631dd2b80ea6e7504954d3b1ffc9758e9e71a1df17f0a452ba1853ad80242a9397c9e3e19bb80fa30de0c1262e260f7e68d0ab08faea198a9c9254b26b7c0c50c670d72967837ee444a54dc9c749c33bcb92523720f6530898598530bbef8f81456693b926856620701999bf23ccdd257f9735676cb10292c27270237ee5f01e12b59721e6a8f78824172bea93976154257220a931a1dd1058330301551367f4485732d32ff8bf88aa2971d6fdd2eebe1775e0972ac8b461c113114be3966ce6dc9ee1163e0df04aec4a87080102de2d8b43daf2fd936f3b260161e018f7c70fd7cf85bba09d2c7e27b36f149ab0ab057fc962be7bec787f9ced82cd9334de3bea28272343ceada939b530cff172f5074fd6511b79be5ab33249695d92664e62c94d12daacbcf33ae08e3d76a08c444940e7d1787f916a1ac6cb52138b62b1020353d1b07980d53b8690d3224846c622180c271af40f68270dceb093e3b31bcb493ba1a62f680b86a795b37e1d7e043a2207fe4b232329ffb96cf6d3bb3826e7af97172d649cf950c9a95f199c0676f1560aa0547f3b1fed4146ffbc26256559db5697280b2991f7de8fd5349148d67e5ea9ae069201fb608e6e51c5265dd126161b272214b79e11b43276eeb04c6fb0d125387a461bade2334f54d81bbd36cd3f3f806aafde6918086cf01e3c4fb34d6b1f240cf88f434e4d50bcd8dfcebe676b07572d5b39b86250e53abf23e9d0106f08284b233cf61db2759e5f7938b6572e3e407a28866093ca1f0a5b945e0161f016145819e242d47193faf1a49bb7ed627ad0a9ffe116f034a6e674d377e8c237cf3db6cba8ed73abc49a2ca170d5b14adbe7299d7e6793e0ef78262c65efeddd1f278439dd2964d39255a5a785a1d7b9b57729711a8a4fcd2260b31d8e5a56d4e21d5c6936f9cadad5a96e00383891cbdef72911348de952e43bbdef3b6fe836211f9b0ccc75ec2668c617b2ad1f08a03c100f19088b1077ddd5f2d83b1effe855aeca310f82673cc3a99e0947a2043ba7f728135ce5ba148690c1770a7c30d4f899311b933f46ca0d5e88f5fedfb8553276b764c1317f27faa0cff3b9bc34a54f2fa454ce6f8ed92d4c88c1781ca5570d7081740b10f156b337294a50dcfb37203e45c1295b31b4ed785dda3118a4c5ec21c3153d5d0639fdf83c4eb50a7495c8a2e823b074f8a5a432cd8ff72c016387e724c028915cc88b073101ecb4f30e202770d469e5c27c7840b537b917cdda27c2989d97635f5e1c87036dbbe2b3b5f5de5790264fca27615fbe81398cfda6a7a72f9fc65074861f855f2a1587eaaa8cb9b0aa0dd9226392175361652da1e2918728c33560867f843385eeab4e8ab9dd88af87db9d9960b86c4947e77bf1ca02c22b0d764adc9e70b5e0ed5d07ea630f33f0ee747b765b08a40e9cded215cd49750fda1b6da150d52b43cd57ad52119bbf0a299e834e85d30126384a6726a970435f617c256d319b5d896f282e31c1a5a53aa6ba4ba684a9dcc9ba9ad5cabb74a72462b54e84bea28d53c286e74c4c20be56de707df6a32cf1280323ede59ad495f474c090519fcd4dd2a92297ea0c76fee7656aedc75a1be82eb57a520a5044272189454e298064689ccc4af253f2e434ec75e1c706d6d572f71a8b3bbef92d7729b43f32f5acaa8adfe8c466ec71b2d6083dc90149c592f01786233be13b1167292698b05b35ee8021435ea648ffd2ba7a7a111fbb0250e97929c67b7a6985d727c000aef11a433c00ff9453a3cf660c158ac14efb2b3b170cf15e8706af10172e95f269b3c5d376c38cb879c909b97ddd2091e88afb1d6041f6239af28f4f6729dc90111930b165b79847b2920ee8156308c4118a8692c42e1f2b7da8e7ef31ca177405e3a6c43f662e1dea8efc1eaa4827cd9d96290f93e9be2a06b79ea134c781b2449bcb4742cd10a3ce25008baf26c48bc29f42831f441ba0d24a6d5fb726b28f1593311dc47d3b50f348d07b99fea2ca563c2f8d293396cabcdfae3b572855f9dff4e48344b2ebdd74ebb106263cc0b79dba346129ee29bf77e33c77e722e59724eeca05469d0fc0efe07d5533b598ae9ca42fb5c32d04e1728d971a42eab7fb7df9862ea0a98d46bcd7f101930de8d3fed24f4c79c1ddb19f6ddd17b1185493a45c87a798d8b89a3b4f6dffe47710566f1a48d0bf7df8402564b6ae334b025a7f23de1a4a491e230a53a96acab4d7bd0a7c6834150e9af4c9b64cf3e320c630f7b2143bf7330d976f59ca8d477f17316dbee5982b9f52701ed95c3a47236bfd476212c7d2bbcda91d8f6ff037e5d6b86b9e8c5028a9c15204963cd5457c9ed1b5c2444a6ce7a696a9d95fa0a27a732959a1547586237f9528530d56d55d8a8981e4fc9f5abd0c46f89d74c46c6a9b019b9c5e4ddef9e9c469191f87572e47377eb1456b915f2f590094c13abb98209e72a7865fd6cb06f9f6362c6c3723661a23b2208bd662a1ecfc61484a11c9aaabfedd7227c9615e5d9df48103b3230b24d3cfb960f2aa5501beadd270ccb828c620ac395317f7f90559717229230a08846b1cdb3f52b5e0020411464a01bfbba7bf9c1ebc6038ba390e002228e0ed6b47d5e2dd9fefca5d2a5b813ba4b95a38d8641fe463b031b5d9621400da45004b0c8a71e4573bdcce3ecd412628cd2c08dd188f9b1404caa397f0c686aad72113084b94227fa81b780a3b38f89ecdbb46e4f379cfdf32fac072a3507c66b34f83c82b1eacfe657b217d6127969d15c3cf248a2136700d40b5e82676157aa725e772b7d05dd9f45cee331d3f8f6ab3252db8b6eb5af2a805e31ac592eb30872f6abd1763c1815e093b2ce8dea2ec109a7b16ebdbbae254a575a018faa7d2972a51bae0037215755cfe65f088704e3ca4b6ba5e6b2b74ff7744adbd3d4f58772dab47f856ee9203b45390b947e245f13ba72b0c64283b4e5fa66d771494a1022faceca96a45c476011ef7e2a2df8e7dfaee741ae4fe3d316823f8e37b208a4720ab85d10d3d0e6274c70a515cc256362ce03233fa9c2e55272fc41eeeff0db72177185bf947cfd35858810e10f3f144ae5e385d6037cbee566fa678d6cffb21fa3148d5126422ebaf33776c351ef1a85d57295712250b6704ff0233f2beea91ed4bfcf36868692c08258bf2d1d0f61040cda1dda20a9a34d19d9e366bfc1ad605ef41c50f7fd23d2dab968a9fbe37fea45cb488fb57902d0838f2bbf2fbcd17259e3ebb8db90af4a790b509bed370f07ba63ab8c601c6002af3c19cd0868857279839da88b1705b964063f74830affeedfedd5620b3b8f6abdde0231507be55d4915a114dc6c41f92448c41ad2223ba1bf202ea50ac0aef87a9ac1318531063f337146a40f8869cffca9871b1fa5bf057897cc6025aed788f285e2d12f34ce45728507e4cb82af0d9714f16cacd64749e4161bc828ac48dafa778d949f000c6da1fd60512ff889adcc0ec204c7be53f935edecab75b8b715ae1b96ba097bf339a24a84f7cdd66516906acbc3c7863bf134148c14eba5644167aec9ebc1c7f872f50b1eab7c5b7add75d3315c0c2b0b72020a0d0d49ff4ff61ceda68103400a726d5d969d3e13b3e57d000e6ba6b672f451bac9ee7a33c1f674c04188cb51750756c100fc7080312012476a6868107170f408daf178745369e420f22301affa727a7c461e8babcc0ad526034220f72d2afd9fd8ea23f71300f77b4c033460176896bf1ec62e3f73ba43920846f1e81f71a88f4a4ddf6f4396251be6876e9c7a723e920cfa37a55a66844bc17f44e1cd04500f0988e5278c71f251c0b51b4533726dfa655b5e7f8f001bb2403f70663d09f4eecb53d2d310942c14ae36c483662d7c64defac9b616f92a45cb5d2dc3db73da5bcb28446aef26c474ea2162a82e7225d63536b22f67adf087d7aa5c0390bb5780846d46b3134c55324d19a4de0f45c4a29b4b551dc79c3d834917aa59614903fb5ab98d5a9330d11e71162915c7726c2e5c80c331c9a6bdf03332f2908bf5b0cb66ce538b6de48e8c8fd9ceb2a57246a81ba26da45f199e6d77fe05d20ca59d524de95d8bdc0d7a319b084bed43034e3d2fdd22db72438fa121f6aa6779ea1cc128345530b51c68f72612fa3b816089ad27ca5658b5b2faf994bec6a4246186320332aacd81607b644aee175e7e0c2be6c1d8961392cccee2efc3e9644a22aeeb6776a8dbee7180b279b6b60581727ce48b188aeddb6a6bcb96d0fe255340bd7c594f11d4c3296eccd7a651972137427684da8ebc69b380c8232592012730b7ce94dece3672f94e2125e7c4fde650848e854181fed9fbcdc89991e7cf03ecd1491bb0eddaa56ceeb079a1c7822c21c450d3ff289b168656e75da99db2a7af9d015d3f4b7ff020d8f8ec3206f7765a107d13936061b2cf9ccf45751dd185769952f462b62ef28093116e5dc881b4725905fd7615d464e897ba7a1be1b9d38f0a7a134cdcb9a5e76e39dc6348b990728801577fd1408c26635a89137c4c20cacd37743e24223f9e4b094098e35a7856ed8eac598877c141e5367fe634310662373f081302df81576addf47dd7e0947260785e59b3be627e457e8c90ec33d086b7ded7cee326f8d9922d623f5bd91f729d97b48d8bb149a1fc9a602ebbe47cd13b28730197f7f617b9a418c4fb08f2729aaa896d114dddaef04d57c7f78bc6f35136223cdfaf19f8a1b710d062ee4e72c8f4cc9cc5a8b54cec0faa691a117fc11086f5cad77449cd744b4873a1b6827226837e78629ec6c632571aeb993af39e0c343485f2205812f666f03ef4942272abfbd023afcb13857ae97c95cf9e225cac79ffc84388f71c9e7ca7afe2a95c572b8544947e170051adecda27c4822b936fa1906e852f6e94801e9909477beb72fb5b5f7a1562c09b85389c1ffaaa654a36d936b12e9cd4d1c2308d9dc55b3c72ed21c67c668de95ffd7f29f4f343185341b9090480ce86b02af6820d94510f6a392ac3bf1a1ace2fbd4100532797d3f1410a6716eef76333dfffbc133573a04e05dc2310d366ee0d3fe400a6f7490eb10de94d26e84405d9bd866d6f1055814e0391d5748b8f63b7c81b3d0ef7b6b2530e4d2c5e578df2d79ca6b931ce9310725dc26edd9b0a8289ee054eb7d1b8ea21a8ce4bfe2daa5c99ee7da38d61f1412f2c35613aae20c846c05db53476f78a5fb7ae7c76c0e9ab36d04dcc0fa8a03017e364ebf4c5073807ba565ba75e84cfd49e84ae0ca273a23522d82b4e3b68e62e0e97e0abfd201070b732ea3756c7f23e5530dbe2e5eca96fc696aca94e81f87283c2b4e2f7cfe0026075f153543255e3f01ea3d4993291b7159370982f106a720d2be65752236067532404048195f1fc3a7feac473ec93035ad7cd7163cda572fab823d35625a1e42c2eaa53d89902249212e3c8ebe02a65e7324ee6f1f1627239322c8618a6adbf1ec7c4ba20c4c0409ed8ca6c18dce2d4ea0a2800d245d672fd6785e05df7a9f07314246ec3dd40716d77d418ef8fbaa8b6119bccae8b05720f88cbb446d82cb768f81c6b3d5d24b4d6258ed95a8fe958e08a04f3de105172d8e998d204af92360e24385e22cb010758c6f6b29585975686299507e7451a6342050a97bf7a5313f1f183b98ffad0a982dcb87d8c570bc22be46ae1148a57388f85c4657e0cf34d845f338b82512702e06418658ab8053c9394da2b1ac78472b55cf705bffe3c52a0f2d23b1accdfe39f86e9309c8d582809a1e10ca183fd72bd927b915d54ab6ec2511b67e82a062fce5e28a2720248f3ee08a1b4ce64a5729ef118536d65f9d2a28e712eb6019c0fbd71ef26154736d3861ad578478d47729bafe720e79c2412684156bc6512f14ee09144d64ae8862f39ebfcfa7a172372caadcad4d67460bf1bddc956671a10f6d85f28b16c8591de133a74f7ebe10f72ceb27c9d17d36e63868e0c25d5dc381e72b0b1e2c06a74af53887d45cc7ebf72ee6b642917b931d761a0c65f1168f18895bed3a7d2e7bf2bf1646ec039858e656f8877d7904df6c4c1f940db4f4bb4275ed5fc3eb9178a7d81ba89a6209fcd41208776c38696312c673e77ae1324e6e1586e4e52bbcfece6f12d408c7eaa290d588d9dca5c50beeaaf9d36dded2a66eb40ee7a115439fa14d6339d98d3b57472fb1a7bb6e564a4c899329bae23dc07a2c5c3e62a7e4b76c0ace1ff704430287263cc832c3889be53609bfe3f8523c654fb7f45a3b404fb386d76ab291895a028c2eac400cbd78ed49278c4bbda062b8e82e4255ac3f3c84f69ecdadf826038409d32604a26408c4e97feaa7ba54f8067c843067b9e6b68cd2c03738f4803456a5625413d074dce3cb444e38b83dcc4dab4884f2d2ef9cb78299101e75c703e7290ba630a9e90b1f3300b637bcd635f8d76ebc1b28197953f2752e8bf1cf00d720942b8fdd229d32e2dd72cc525bae0ab7a4493dfb1ab9055169f3516c7c25e72de58e835e778375f4de7830524e9ce884ae095fc96cd946aa1d2730813ed61725e2cacfd43840e72d20d473268263b9e6f2ce4b66aff1c64467dc00524a1cd72e7d6533678c9d117dbce3fba65fdde4d53512f3dba22f49fd534267c70faf419fd425d27c2d4bb5372c7845212af4f00848b3cbaa37e4ddf5dd7c3012a3fc826d5d2b0d0fd1e59d90924e0398b68cada19b186d7237c341e520b35015c3b47688c951e3bf4cdb6565580c8849dba4c4a244ed2b0de4dc62a6f1dd06b269db632b4a9e9efa8c55fe8188da3ef4da631d26b4e5d40566e5548c2decf140dabfc5bc657fa2ae4a34b8135d44b4bcb4df99505942e7b3b66b9d50e9bdcd3ff9b3a72bed07e431d303a4347239f00896251375fff7d1fb960a0c453d23a5dfcb43c72a4eb4d1a49ab745d6a86e8e3edf3afa90a90bde2c2a6366937d28460feb51415ab09d2db539313e1db7940c2af4908f8841b1c24da9dcd4ad63ab1ea8ebffc3d6c112116184259ba689bd9dca294eecb6c986b4c5de1eadebff46f06bce54a7234ba35e08a27d5dc1e73beb61cca1c423e08f2b8da9fc36a9df8c0a18e80340299e1bcd2a9da262d556dadf46891f49323c79d6ca119092d312e7c5aece96872448741b0ff0bf6b772a813af556ad02e701b7f00014d2f6b36a0efbf906cd7287d8f1b3f661bc915eb110393d08536181db4ef93992f61b14ecfa1df2bf92172ff920e26cb88364391c449502f6efa2545b262509f0d5f1848ad3cf4dfda30486ed222937aeb73502f69f52133f17d4c994012eae75579280fa648a8e9651b72ecebd6889050c84072f41286c59d5d53f28296032a21661751c4936c56d56772b35c00666ff0f8cd36d0eb6cacf9db11a73f38e5027eaebc7ae919a1ef716872e0f173ebeaf3d617d815e079370aa36bdd3f75b8597205743a7aa93a455dd53b3bebf81711a010699afe0d11f705c6d1b2534700f315ff765f946ae7a49b245197c75722be3b25f88a21a2dee2050461cd3a0d1208cfaf68adbbcc5dda902472a137e184e1185b26eb99d767b0d449326941b7dbb37e84e4b238802b8fa209720927787eb169f20e9c36eb8079b37b9eca80646d226ac001d58c306dfdb048318346c6910dd970226347f3994042dddfe26008ab6eb3240835b92258818f5e724efc22edbe474c9a111b15b2cbb12515eebf2771e2d055192efd3f324f7d0472b2a6e0f2779b1c4b80a475ec770c0ffef3047f37e3ed2159cd9cae39d9a3af72293922d7bfbf92967fb0d7bdae4ee0de5fc5babf919d529d56c14a668314467253a5ff3667ca1d42d58e602042387169989070ba3e04a96ae44e672ff284287228e2d69e3c55f5f12f67a8343895a2acb6b248fae0d6bcec5c0a8dc097ada059631be4f5f5f2157f75e9306d7e6db6bcf3c800d99759b58e7c14f0baf61e2f728c1129ce04c3e0435b07e07e54c9e3010a1095c1f827821036f67bf124624410ea21fac9f27f45b8d46f4cba4db4d7ba31f96e2cef3bf1af4075c3b6944347725444ec74956bda9954852abb4e3672ff88a96b70b499e7cd65ab94fcd707073b5053956068a2ee3a4b81572371e311f96b656327199fa32fa0b27d5d0e704a72d0cb031013dcc9f5fc20213d1e493209e3f939deadaaf6e2474fd86401327b72d2592882c9d7b80d465b736da0a6a7836c9844b456ad78a194c661b4e4af0572d98a046e2bdf7d139ca5f35b0cf86e2ce8dc559391e3dbe1350ff814ff1d5172bdb5f42c404c783b9d48a2e82a67df259d4e1ee38496d9735b9d3f1203dac7729483d81a74129d3364a614142b90c76b822cc7e80c6f69916435c3a9fb792b111601f2dd09c33f745b991a05676aaa9ccbb3b3304a2f946784a8556131461523df8d1209566a99d7b47bf552561079ec068eb25d1c072e633c9727972cde3871f5c763c5ca35d0b3418cd1d4fdd9cf6f24e49716ddb092970ed674331a5b43217739ba99cc9f19f67bf74267757fec1e51160a8def90a11b925fea33a3b9986e780f2d0b572d1b4177d64699fc1856b5998f8bd112d62ae0c405358a8554957227605d42f2e313c4beaea077e8e34f687467751013917b39a2bad08706f48942c06d12eebbc39d13c138de7ef25f13597c6d6c980e271db014fe97fed203257297b7574fc9c35a6b9dd773c39b8932578d18d4110e68a6aeb614de4f11316072f56720dcaf7828982c162347b5b81cd384320834b12a811b900b846bc530817237fe91aa3b287542b9f6ccce3fa195c926876fbe359c7fe9ece218f4bc05ff6b487c6a5361dfb341c8f58d5458c40e1bd4f34f717871c762a0e36dec41d8054ba53c509c775a19523f078aeec97851b09d58d461f94a6c664026d965af2c75664afc03b31660285169766409b4d81ff2c0f1df0bd99c9088f2641e16ee12a572d7fdfe3290ccd2b4f8c4e35fd60406cdf7d959a45548adb411f989cb22c834729fa19ec43ecf2ac36009c593f0a32297cc99f828c0eef3f0e11c924000964b56ab2baf0f30141ca2fd543d93ed1b3542bb5de0e700a65ed76319a358aca00d5b389233c4859d0cb2acbe8badafdd0ab4d1c34281bd081e08eccc09b3cae7c727befb53b97a83d25fd8055a01aac025e4ac2b076359c5f05a907695a4ea5763724207344256c7c48ca52258c6b9420d7936940a4a1c2ab7968aac74798c3c1e7200f8cbe105f50f39e04ae6ae959a8a47012a9f93bb60c359e40034014c53cd72aaf551fc527f46de53334eb83795fbbba2cde4958dd2f70ef6f2745a1e413449a3c577a36ea7948ed41c3262a264946e39338c49ec5cd00c78d1b86c5eb3913d437bd559f3562641534c6af2dba10c1cf8f8582b703f6abc7c2c4fa409b65d2b8d460f3882959201fa90b0a714199b037ca0e1aff12176c6947ad9a7481d40727cc68817066163546f97a020f99db3b5c489ff643eeb7e73901ee6e8feef7272a6d0c2e95754846cad9c92035ecd25fcc9e09125fefee1f287cc2eda9ed9837248da1dc480f95b9082754aa16004211dfcb464ccf36b1c500f5d84960b6e9772be5f9d92217703f505ca859caa0e9d7c0852c70ec4c23c2dc26de8131aa3c917ff7194de3cc1022ef6486900cf9cfcb3a210dcb4944b177c00418579cca53d50e91e4dd703cfbabfa1997dc7770e91d6139d749527fdce7342edf08cd4d50a725991e4094c6ee2abd1d293a2de4b3a220c4965204c1d9c001e3898a0fea9ca62c66c3aa3ad557a3385d672c7753aa7a05aa3ca9ae6b5269ea2e53e98fdc141725f5640f64ef46993af87be08fd23024e6e5869082b2dc8fe4fe44c2be82a3572904734601619fb58ef5fe83333940dc4466b764b935eb927b5254f9fd1659f6af8ce3339fab3f5e7a951b9f9dfeff9f4b55d654cc559a2a13429e23d27e1f872598478039388a7dc87539003a650447934991452103f13fa64072c223d1433720b9dbd4fb760b7ec3fc32d78e28cc567aec8d86d32cd1805acd8782af94b9b5585953c597d8172f15bc9a46bc0c7b48d95cb94f692dd05f9bf2002e3d0e6db725aa27045d0df843cf16e3dd41bd1f70140b906e858b3491df34c72739daead32131ecde6550731b5e00f5a5dc8bf1f04c6ed598ec22ebe72767b6ec2ed3c9e72d49fd3a39ef7bc8c06ff141287a2e8c3126d9edb38bd74522daad1fecb7c5f721d58928d2bae5484d00b38051cb675ad1030e5f4c1af8a6b3fc75c4b6d416872a2acf0df29374df6efbb129b880f22311623d3ecff4100db2d013051bd16085916798072d0283ec06ebc7cc8e5a0cff1b3c8bf3e5e48bdcaef77cb12e66ace72ad1eb213cfb950b0126ef9d2c03ecc693c3cb416e39799e0a2a88461ca1da372327175f36aaccad8c48a95783d9524bbca65c7b93db13220edb4b923db89f772576bbfbc4214ab2ef3aa1d92127a5a0d0214d0747dd321b66a90151a83c5ed386cafaa9e847c97d0563ff64135e379796e3e34514a32344a3e1d657780286f72e1175f3855b42bd86a001fdc078f3c64f4ead0f6b545d151e752bfeb3429446566dc7f5550c342977cdec02d30905b8db1758e08b862ca053acecc31d977e84086c6c074ccce4e6340e09fbadd67b0e5382aad5ee9f5c7ec49214eac47730e5d96e31c36a07fc155b81b376986bb9f9d07b6abdb92c436b1d8724b0de3733723cf65c7a93f50aab69250a53cc742f0cd0fbf86a0c1e7813adbe667cfd8952a722cfd483649ba0ef016c28e882c384694e22be0287ad3bbbdda578a15ad4299724a4b70854913b35841b7029b58fd2dbe87c5107b5ab9241348929ec127e74807b7a1468eb3015f9a40ff1ef6ea47aff113873657ef6940bbc7aafbaff9930b0e888084c2dd565f578b2a2f4684237c36af9f2d4f8d3e638ff8f02c33eb57b565e4c05676ad60f880af8dbe27be0084b05d6495a8dba118dc67aeb8c552bd4025505f07d2c1ee8288dd773b67ea1c6f6fe57caf9948a69aabaf3a9bc26fedfa72adb0eb82fb8ca2a91c5c964964908caa955af45ee1af810f7ba5ea68c3350370566d33c55574de134a5b64bf8903845bdd41586a7f820626b6e2eb86a5826d7220021c0986b9f195969d66aba7c45679d94a5a74d4d378af609539505f3e5d1cec0e07cddfeecca2ee697ff7323092fac635db71e7379b70c6d43a015bed945f85480c09d580cce972351f14c5c4dbe942e54c7cf2f20505fc97feeadf297d4a61d021e9346910c9deb5ec90fb5367c4e9e92bee9a59a049838c21661a371872f6523f98a46f3ba82dbe151222c8e2c5e792e917396c1a751c46c0343b478e72ddf91e722e846d6cd11fc702f13d9511b1ae3834aa4d29624ad39b15e16ff9721da74ea8b6c3248aeee80157dfcff64ca71d4a85ee2ae19ba101118152d522644eec77c8bb834e8bc29e9c2a6b9a2bc7e7622fdd25921b1dee8b3c087b181372e1629955dde89c05c619dfb50538c6fab7f6edc306585be3106805296e9c49729fd3ea271ee0e49cca7743805471175730e3e16af21601acf3a13cb5623b6172d1af3c86860df91c507a34226bf943316d4e6ebe3720734a354280a960f5b07268d671cf9ca263c34e62b4da7ff123a0d6fa411d3122b2768dd1d520b581827223ad0871217785e58e0687cbb1f151866ab242e8807b47d07e5f7c5d7fdd5a2ebb0716a25cdf6064ca9e3e9d5419e88bca56424891229ee1358007dae1ec4272f822f4f47a563787ebea2f568e58ccb9d3e04e8886af6af39c0d9cbc402e32720b931aa18eb0a15dd9945f0a144e66152f2986f9ab313a299c925304eefd2664377b48d1b8fdcae4f6950f5288d8f625fb578c5e064a518ceb7e72939ebea236090e8d89c4a128918fab95a5175bfef479a1f035c1b88b51050d389bf144e1720bde74d490a17b91194a29e38e2049fa03a866ff5a9b68376869d58567a5d12e65fe61fe19d6a4ec11d08f0ea287b7ceec7316a0aa63f199ecdd537fd0ff276329d300610a3fe24947584b2f05a4088b14895aaf99872d96441204065f25cf72c85292ec527f38efc25f4b77d41c21e9a5201fe4ba3d508db5b88b2a8b270036e38714d0e29a802cdf753550c3259289207ec9aed45843ca2ea266ed0010e0724d0fe774aadb468b153f5bb442d32f0d62483083b62ef1fd1797c56a71b91e5322b2ef032035594558e9df875b92bc3c0792ab60380fe9e5915efaacde7ea34ce1545bc77c34519b54c86092a1f5e5ea8e3bb22c4c31e3772ddea8dde8ec912d8b8bc0d14ce8a0a5ca0da48c9bf16547fb4d919f87c385631caca7c876250b2c62f362ddfb3582936259ded5906da896c5a7257aa2087a8229bc30ef9177a212d0aebdcb82e2550a616ff339d6dca4d1737657ccb9ec6031f668ef78152c381156bd6efcb73035a19869f2f2b34337256a3c40a906a9b864298ac53dbdde6f7246a89f95fb03a003e16f71538e8bf399a829d6155fbb4ecf452089f9c9356c2b34744be692da9f05f3e5b9e4f84ad8b1b0e05be2a36bc8fd06609ff2fd2e2472f03467ab252fdeaa89d9de15c142fdc0fb57ee3095e5094298b2bc820b68b1727c6342319175b2b21009d3b7a7c12387203ab1d747f4b7f7fbc056b1dce761598e5a17a4003bafbf659a600a73059a99010d1f7d1cd52c9ca267c63fff00557263a0a1213f4b897f01fa41a10ade6bfdc3fe6b34ddcf0da94f1fc61ed38b43300200a45c3a496da7e580483b01f81313f1ac6ff24ffc5981adc49a75c3f24169fbefca0ec86cb5678c65c5911a45de11d48b2cabc3c42d09a3240b7b5ea596724ca404527049d284b531346db8538335da00fd57b24844a911f3d7021f37e172e514926981a1e95d92d91787b47aef526a593e69cd209a390a801623088a0f72164092ae30ad73483eeefdca1f43e506c9814bbcef64c241579682165a8ad2067c536cbaba14e8d4e2849f1ce74286c0ff781f66b91799ee04d17e32f951b667ff397079f95de37323ee393a270f23e1ddc6963dc98cd1e4df5b8841a8d4801f2756bcef90a1d3b25ac8c5781f5e0a71c494c4875fd77a642a1974a892931d721a33570f09b240ccd6b0213e2b7ad3ff49e14a6daca53dd64d3882bb8ffe25728de152e3a6b4727ead5dc94602d1def2e85b2ca66ea37b3dfa537a627248ce726e149e2f6e65db93f1c26fa03e97625431c7dddd1818763c3f06f03ee58d35727ba742576d3f01a71948c4d1b5ad41a4447b82ea09a4370f87bf2276ced9cb3d2d6a31402cd5195aeef12e8daf6aaea31c47e1a8c4a2b41787ef5ffc8dde6a3ceda487ecb3fe1ca67a7731935311d67ddd41d1030af40034a6b46303e0d1ad72f2727b6ba08888c73fb0ec74e89f1258562a8a871d1830ae8fe615baf7b9ba348d4531c505d933adfc60a77a779fb9ee03aa0f66f1924ce4d1736917ed112b185f761e483b67148b0170f5ebd96811a5ca8c71be748b15b9e43fd526538d381b758239961d7f5eef5e93408161b4f8a43290f0479c88d767a5cb99c60e81237246d63fa94ec6228807e6c768c0b7d53fe294d72ce650faac2d2f0e413303e372ff6f10a62a3727857aab4355a43dd0931fa7f802d19a250684f1bdfc8283e96e8f80e3d3e30bd5660cadcfb0390af684610de08dba64f54d3a8a356fb6e93a7235f04f9e5424d56f1455175abe1e866a476d43ec7c82dab71675d0886675341797e0ac059302287c8c018e890aed4b567b0b18d5baa06990106f7a05a717a71dfcc9573f52e70089b3be75125edbfe62367a3517df665e646de9c1afac5df3284f4376768325a599e56d0144fdf0e2ea278648bab02078807bc1041f51f6a772233eb141e4237f06ccdf72a59df7d083a86f727280541959e62ee0b18426f27256e93590671bfaccc71bfda2f6e5f7a5bdb96163054e9520834e2005aa43db5aab223f2d2c1b83411b8d92ad36e880c37357b3786f21e9b4ceda8c7a00438b729e931a70c58c63adc7dd1f8aeaefbb642aee61e37cff740b797f778eac373f4d149ef16ec75065d958e018d7fa69bdf453bb2ffdd9a077f77750656cc6e3f6727f410ca6e3a2e2592a78ed8f1cb5d2a5dda1d6540b70d01c64ccef39383fd7726e04e31b01179f90f810838343c64ab9e2760f8b2b0cdc69ec27954edd985e72d4e2f2d18c3565448e443d4b69657db7b02919fab29e0c588fd895975af50b7200b5a621a4161e56a6aca4371123c25bc1cc3184123fc910bc75171860e7226cb2a8256707e4db02e2864c3ac0d6212c19845fa213fe757f70316fb3028fc9721f7860113d9d4eb5d87c72f5e4d8db5d82f2caf502f9579977ddaaedd723ae7237f9218b7f17487f2c22eb383328112cafc7a393890bf2441f1dcb5e83b01d392904d8828205019b0b379b941f6461a5d672f3c83b58463b809abee3a3dfef724feec5fe086d9ed534aee6b087ce1d45f23f483341e4bbd1cba8589ac767a3729428337e15e16a384cc9a336016c0a41ea541351c16f9dfb05c84376d49a9b105a1896c821d39baf3bdb6e613fcdabbd210506fecd727aa0f3a6265a27efdc72bd36d892a53278dc2216c9e7dbe07f3b519ed284e96c776d480bc6ab897a9e72998ecfc09b47049ec2c7fae067b4dca5918c26c19cfdb824b52a35d21c7c721bd5dba708580d0a27157dbaca5fffa1389184fde4323dd9366218026f73068b529310917e2883945a2f26b81acbd8dd9aee5b100b398ca81516108cc73cb69572ef78bb78c4449782b1d4613e964353c06c9de480ce212e6b2898a4d1b67195512dff50824ddce3976cc95fe54f337c611ad2f0936fb9e8a55d39d567f28c9c72ff66414c903ba18319262b204b961182907b5d49198d7539e56e5b682580ce724f3d9cf162fcaed52ab7d4a0fde85ee8c8e3b6e8b0e9075173fb3a7e1694556df1f90e9a4de7a484bd05baa76f8f9f2d1004b5c97f5f60677ade5a4b2e858e722449ad05ee1fe3b8dfbc96390cb94f6a47ef796a830ca8e6ec3d003f723345023384a5a70d5399d0e95595964f2ccbf748b3864bd67b8903aea65e0952f5b47279e774d958c62303be8da6569b0bbbd63e5703a06fa13fa7e3473fc5ccae0072c05f8da1bf1c0c3ab58b6199b2ae91695ce2234b1f044d55e86a9e1e9804967257b02429a6779bee397bfa5f993a11b60897d145a07314743255fb58d84fd572a1770de99110593c2e39ec18ac4307d4ee29f9c4dc0152fd589e6217d8cae545ea1584653b61e377414853d15378d98168b88524283eb3145c7201404fa3e872b5cc477a400e58516403993461de5f610f22ef45b7a78c0551e716641b1e3b72d2fd7660a2e627e1c935beef1b774b74e13e4e7cfa0ff9c737037de1aca2f83b2b689eeab91c29cd549a0ce09eb74da9f2f9b4b7b175aaeb3f9ab0753880597221df6bf9ea95cb7be4f4f7f5f97e4ee353479f0d117bb21aa91cf7f2e6a5ca23c61e29d8fd7b32369e802649a8dc8d1774174d876e1525951c51530b95b2382afff3387870b02ea2b2c24930fe06b42b291f713da0988f7026877f22b82e03613d9262b6240ea7f0dfb53dac736ea03f15e8dc5dd49de46451ae178cfbb4a65df89e17ca86e30262ca2692af7430f59e07817751f66dc084318c95bba1063d46b6d21ffc7e2f9d48cacc150caa09f0806160a238c3c8051c2193c5e27dd41017a6566a6d03fb187229f5fff59a4a8bc176efcf4cb663d987e62540c7a91ba639df6ef4da775893cbb8de43dc2a9fa0a6c822238efb1c0c4bf13725ce009c4d7252a735b6450468a97be331bf6b0190968edd472d89460451fedb0ab4470efa7289b7899f48239e6ac2640f7ed4f6f9baf22564eee22cf6fa6db20c683d0b2a72618eb095872a8c78f6fd057636567bccc1245558435ea3f39bba1f2d687da6721061622a96463f480f64ea755dfdd4d54005a1b5341eeccaae0f9c042834f272245f3d17dbfefe6cacd321cf16f0172819f266f9fd06bfd7dec99bd8f8656b72d1024346d2e469b90566d39d6556a5e0f04d7359135a7d83d825263c1cb751405df51f68e4c4bb37aba4beeca34ebf0fc14bf6e5bfbc7b789c1736efaf6c531bb48f95df2d0b24f87c0825e3a28906fd4c08d71180d2a04210b7f13f4b52c9720be7a9ca0410d8466c8a25ef48280a2d72c83886bfdb3ee82bd28c89500b3d72491bd881a19b4f72b45d0a07064d076af10e9fae26feca5a957b35d8aecd3372164c854abcd904b8fd85843871e1ba360331d4f31f03829dc782e682ddc2ab7294a4615f9bfda70f91618fbdec6cb7ccc96e8b534d9ece4903a841d348d71b0922953dd3809e58736c18a61741ac10ea5eba1a2025367e6803943efe8eb95b726d17c19f42c507a8961bd486eb6c3315872de197ab683e54860bcb86cc197c72c5c2e0757daa36c63950cb6d37e77046ceec3f7ce0b0749bec0503d44692c31ddd9caa61eb87e7dbb19280d472f8ed63c833109762a541201a03c1dd59012272ae7fc68e641c8b333b7640c902f33f1a7e31b408c1bc28b78e74139f8c207d2004c42278097fc8847d7307dac215fea7c5254e233e7bf01b4cb7da0b764fc14f40570a8a39e3ac6a3d5a532f0e9d693f1cb4c43fdfe48bd5dcf689b06155287298381b086d1ab2a7ed5ab18b875fec9c193f42859e0d023460739564bf20d972f0308327edab7de950ed947f971ae353b232159ea8a8c6884abadfe9f7bf6c728027d3d928785320a303e42a05388262b7baef286e291a2edf1f35748c2d60083feaac55c9642bb3f3d23ab33f3fff0a79a929a44d61bb08cf1bef4e786d3172d2301251172619e817b98f772ed77efe8d6e7e9855293addb22600c1d6db4c11946a28db23814bec3db854aa99a2abba2abe635f5d0cad6b284ebbd5568301727621493aa3180bb3ca2e40501f88fb7b16c5041881ac862146680cfc2436e07231425d846b4c7478b5f9db37d8847a6e47c9389310149c62dc40097377af8172ae31237a64b4182e6cb55e9b59d941d4dd392d238fd4f45598d3d9b4b274ea72b49754471c0491e858e362eac7456e3fc7c7e6e53be4a26757bb396909207b728624d4b1bf87f48599d3e1518acc1cf6bd461b18dc6d3970eb16351fd16111725bceaadcf4ddc3256ca9323318a74fa4768cc429acce5ffc4c0f03e744763d08256bb2b7bb6365b805bb72184d43e66a01ee38c9af5647b67e783548a92c77721f5ae457aa429128b1e63e6e12f473362b8400e1e16892ae97ff97232c1b1020b8b25b1a357ae2a771626668dafc6f3318164b2b57e9cce9052acacb5723915dc89611d8578e8a86991a839a4ba48c8b2cd1603cdd14739b736fc085ddd78772413c8af9b66fe22ca6a03fbfac45d039a1c338a41da119f492d100d6d3532c7218ff2d57552231742a135605ad015077e8958e54b53ace157e93b4ae3894c93a5ca28c75a4b62b55fc2bac0cf3eaaa9bad4e666aaf62e7c4b6236aa6fa2a85675d57a6e8cf4e83ff10c1f671bc2ec2202797f75604421e4477d977f999c6577256bbd966a624d6bb9dc55529637b89f3a637b133f5db9e1432f29ac6e0e1067292516733d9b7dd6bbc72d992be9191d3f721d8b4ca7d39c89e0039d0bdaad0722e712e619aeaa1fe0361b2e58806a675c843edad038a7600d40e117f9e37d47289e7b3e3cd6d0270d0e1f33d86e0589c847cc7778fc8b385653ed5ef4437752a1224c75fce030f39a6a2163a9f79dbc907134b2b0b2d4b5ca423ecc360d10e7275028e4364089e4e16795ae03aa02224d161bd92d7ee63e7f0fd0a11439deb720b69234a1e07e88b844f67362729248ee4f738b8f354c0010109cc7cdea9ed72b7e8bf6b1b013b0091afc1e2942906586a0ec90fc985d91264fba1013a4ff24cb306f27c208ba222b08c01ffe62d281f7836f5a588af2bffcbf3be32c712e372c32dd1ebd00bb69abffbc655965d0660a87153368131bcf0363ddd6edc248a131932aae04277bc63597ac76a791af0e7624995156d390938df89e3a4afb22e2ee7a204cf236ab877e4c5656738c9e4fb6f4b2fb8c854c259bf461e1034f449310a1cca65af45558013038b5b2d74a39f3770cc9b81c34610980a41f896b6b2729f315aa3c2fe75a6fa93a21e085620a76242dc41283119b55fb5bd07c20c4c7258b53a1efbc4e911786b2b8e91fb296e5b3cde599b71a7556c5431f0b2ca9919d036afe3477ce78c4e9acd5837c6dfcf7e0bb1377b97582c6ee681e7effbd2726b2ab66d8aca8acd36d458546817ce4ac96c1857822c3c639e0dc0730d30a61eaea0628864494b078d9b7839ca9d10a556cb2846ff50516ea3763757bb423d725efa51ed83ad5b3cdf9f9dc54762f39edc9412fb7039b9972704c212f3be3d72dbb67c6eae2282331e34566bd19de0d4dfaa1de10f39613d5aeb1a4f92b3e64c0cbc13b80a996e96da43e475afa0715ac04fb3b0990385ace4a137b7e52fb421e4f72468decc84d90c86ea1234f47cb5f555acd12899d4a3d4805bba7c98567256dac338283fe0a0c4f542d62787e04babe57a77df1ded8b156a6f735bb13a152ab0bc1d2823acac7d8ebd3ad80019be2f1eb3e2e6f3662e7104d79b9b5d677283f13970a172a5d3ead4176827ce17f9c8ae5c8e7b62da63b123035591857d72619fd90dbb51e86ffebe09a366792815a84e1842fbe71ff3ffb183c0d663aa722b8a3be3fa819641920a206e373d597428c7e2b209a6d8652b23c62866c5ce72bf934134051246be7f2896d283c2c22872cf858717d9e6f476cddd3b3f6fd8727622f027e77e2c2c87008dcd5e056758a3f8e41a3cda12249c84921482d21d510f21d68ad1fa19b3efb417a7ac1ca5c519a0da90170b0aff5e526697e1103a53200c0b82af5933732e86922613964b3f813ff283398c9c7bffab71ed0ed55b3ef0e88df786590a9726d9920e44380e4232886a1f8358a89200b83b515f8fba7203f2f875d2a195aff09bd0e323bc521a53f80f17964c989e326ff02c9a9b8272e461942d1958c0bf7e0209e3828c7688509578654f401ca453eee407b8212d625c47a65eb730a3968af541498b1eeb172cd4167cb9fcc29860b953a4a7204b5138a80cff451dfd5ff60a6471216d0680e32150969c081f9d8ad19610e33297212957a81e91b6dc2591080fe13b5bbc9c052a94f5cf417a60623b0fb43f4e8472af700432e93b950a19dc04d0ded0049d591c832b375ef83d67b7e313cd4974722cb7a95693f706b6e16c9bbe6161a22899a87590109e3ff53d7ef1a113231f176211206d6ea84f6b9a8113a4c29abc0c7128feda6e409dbd7f7c73c854ca137203b6fd9c61c174c49ef619d3ed86d0722add1af4a6e69f0b7124f2af31934d72068944735bb71696cd03f1740d59901e1baa565118437807ec1228497b8384523302e53d03d43c0e8e4c6536693ef7856a9edd3310d651ad85aeeb1bcdd2e31c5715fe5f35f94d4c316ec1df06dc6928d1ad66b2c36c718da976450db7b06972f9a4b1c0979b4892e52d87f8c8db00a53fa55299b8c13832ebb548e229050c722a8ea23a0271d43fa60285164391f3303c73ea9bb290a4ee306ec95a7888af72c0d8ef4fec9e622b94126018add5f77a1a5ac965b3935a97f16224dafbd13572db872a97e669c4d9d212a1cc537839e0ff0dd5cf238bbd11457c63df943bac724588dd90d1e28d878cfd45d8e89d028ccb35e3dc945c1447e9eea7a952b483725d0e52f694da4d3e69ee876d4e07d64ef3964181fd65f8c094eab96878aa69619cbf729346357a2d2129db39f0f5e10a9db3832e497e742d78979f21411f5072dd4aadce15b30823a2138df6ba04e219b81440e5271ce1c69feb04b1fff9d0248080bb80dfacc26b322695b40a4b2cc864aa9cb8d7c466a609aff09a0e2c24726a0113908fd9eee342992cd29c4ce8728b3e3c6ae1cea26c86011af5ef7f69720b7ac4a370cc70942be879433b206ebf815bd00d7174e37b2daaad09dccd5372510cd2398f4a796e7555af65c54f7a5c9841e9085bbbbc5819d306ed950bfb727abc7ad3fd2c44a6874e29562b53c9a0557898ee668609d78d796153d21385726579d26486e4279967301a07056f7f07c293a0de5ff22dddfebf9688329f46694afa96ad4ad75295ed481026b3c906fc765f3aaf0054ad6d2ca06393588726070a2c8f32b1b049d08572f4a0346c14de23ac2b58b4c801d489ea1cdfb8f36b72eb587870460cc20975013d44e83d323e9969e168577f2e78f5dfab81a8cd3572260f281c0278ce24884b6cb02ec4ddad57ac2ded31db4a6b8b1ea5f68e796451508549d29dddf56402eae0233c61b5d37c29eaa97ca235ea2fa0ff88470d8a72eb79204152d1b99f8b9b063a459f250c60392013c2f555b816b2b6e8170b925773052fe872074898a7797e405aea7a9dd53318dc3ccdc32b62ee0557ec43bd357eb0933d078c2905f0c020c5361446e7c44de9086e3583796667d594d6874e72a4b8096dc4d73209a33763c72a1eb88729cfd569906cb1d71e519356f52c82696dc3a9791221b61c72cffc0f17e58751ad9d4693387d35576a956e7b2b20d4721d2f343cd87cb2043428372061066bd4b45889015fe4be7d769f8e09faae187298915b12eee5f734aa72e0f21d338fd9020a6026364cf9ff605d4db5077706729a06618682fe6025b464aef8c42433d64af483b62d1ef68c649a763de900bf72ebc374bba6a7a034f871000a781403f7957ce0a67e23bba27fe94f9c86eed761e4bbc34d99ba1779f3b60cf32f70cfadcde0c48a77f21b6ca0d0ede5c1165a72cfd4d37e084b34eb127b3ab76dc45766f4983921eceef6b8ad997fdabd5bc972117599bf63a551ed61a42380072781428628a1ff866727e21577c5fd1ee628171dc3a22089080056baf1f2f3eca89395f99c37906c19d7cc3c9adb8bacb83672cfed33d84acca343aab77751a22a747768848e2dde5eff665df636fda285977277accac572f5a0777917d29050501e6895f53abf0a9437b50208b352f43c241a7e1de55020504f63c57d405d425ae2931c51eaf3b6b08070060cc6187d23e072bad361a1c6ed076db101876a840bcde8f991edc57c96e23e140426009a31104b2dd73a68f010b072116413361531ddeebdb0279747a3db0c5b995a3630c1b8618330b0fe033e6ac8fec11272b948def24161e24900c6c61445a5a2fca5aca0727509e8fa1d128755d54ffcf0d48a181c3ba226203870e375c6d22b1b61957672b3bb909caf36e74894cefa7c8aea3d920a44dc9e919f3f1e3c1c6a3d85e8df26e7c35b1886c76e5ae090d0ebcbfebbbaa49c2335dc7b24b6b184a72f5d7616493b613b80a6be5b1835b013e2296dd202455051606b2a64a7059936651ff071722823babfc9666734551fd26f5ca93e32d11d532e554080311c80ad3b69845572b0a40a28a2269e8943cfff6e1638b4d0ae7f7075fd076e037c3abb8394653272cee6ab0413ffcd3362757c3be43807eff83304581912005007a3e75d9b3b6f37c0d28091d1d68685245e08d53514c8e8de5c0fbddbf9ad987c1796ea67fc7c4e2791ff25438e6e3b8a6af690fc2deceddb32afe9e3496ed0489d8cd6727fbc72e7693f39e60e6fc2a0c8a6116fd5225e9a23cfbccf6b619e2a075b3bae27b95cfc009c133074811b7873f65157150905044251ec6d44e8c0c53c37558642927201337ee2b840ce288d2e13c754795108221f7fa100815a78b6fb7f827d07187237c5a7078a9b0fa23009f866e6c54ff05bc077efacfeda0acee949758e1ac51d5b236243648da78a00a97c16aafe9ce1a506aba50c5d5f44d4b8558370a91b5ee374e105d7c41b9a0714ef37fe56e63b7360434d45afcd70e933176aadcef34d8659e02cd3add800a5d64d16254def4be0df4b7fe7815b2444b058f2bb61866ce28fdd6783fc4fc17cf351acdfe6f36a6a4dd23e53753e58f3cd04b7f034425547a1e70815bd16ce7cdb9e4a1f9a76a84a8c79cc01f848233c88487069c4e401152d37da1cbff5639ca93fea2e6b91aea4fd0c341ebf96b941336f904d47e772d909af82a93551aa43670e07e8c71746b483b32be7711d6d3eedfbb900b299646e4193e7de1ec55d0b1b09f5e469ab91865008aca5a4ae4a284527934624356a6c34675e653c545fcec4bef299b03f6b8e90eed5bdcbadc2d14333dabf55721f5f87074bcc75e107445fd63ea210d36ded7c273a547f9f8acdb078066b809a72b5d8913491a8353b7604a2d47d7e5ee1a1e969102f5b82be322412adc4624b129c7c794baf18186676f53ec0534208d605d4ae6988fdd64e84c93f6ed0f65972ae34d3ffd42191e7b533e5ba6bbc7cc7d18a5798c79bf75e31951a47944799201df1cae8887e0f77eb81d01982a36864fd0670eebe209843a8b4e4ec953a9f72489de674e0dbd2c91eac04e08dc6289ffedfdf72ba12c245d70b824621c52c006db77647e9e661e0e1c4cc5e8fd1034dfb0160670aaf0a3038b47cb2b3127f7221597b86fe2df2073dfcaea733254a3720d823633e2289c8379984d8f33b2d722ba45495626951dbb2c6f29ae99f5316d5b3dbb231790b2a7c18729b096f893abe998d3c1b6c2ed994f2918574072121819356986a0aea358b98adceccdb3e727c60e3701d54176046d5ef577bb61169bd59fed5ce7b00b4155047312e2b057264dc13569d7528e5ff39f89f7deaa709c2691d4218b670bedd67f7664128494dbdef20afa1a89f3bc4f7e13f8284a1de7b100c1eb0e66af58ab9c3fd7037a57281914e657873de62ba5bf5a9c798876f8b74b4a14d9f1fe5326e2307d7d8e15155c59b46f99f70e59b5002514274740a6e7581def1a7477cbf1a6eb41a90f03d7ba474ec3e89c447829a00bc34461aa3b36aa7a3687a2b8ce1c0cce51c6e2672322fc40c4c5f1817f19d7670de4a26eca71de97053e0544d7ed353a77066c9336fce6ee4a0558c9e3b83e12a717b3786c570d3017ea2fc0fa7c7ded7978ca472415d32ae84a1db8cfbf45467d263ef0de5e3cd8123393f3a178e37b54f37fb5d9f828f214e7b2c215c2c13f51f5edcaad053b22fe4fd5ac459078dde472ebb72f55041cac2f5ba6ad63ab8477c3ae38a72d70e172d1159b80d009213960a352d3a31f16f9af0d4c9a573d6f503ba72c552ba1925c9de850f78f0f508340e6d72ff348c5ad7fbd3e17964d8e43e3f18978ee24361ce2f6da22713e01bbd4345721c1f057e49d2b95f8315c1309844ba821a069da815e2bd10489eb03e6fb04233487b5a5dde655fad0e7cddc59bc1c9a063c8298bc639b72aa6e950de92a51b72c5ed478e6d8e154c589dc31105222d790f1d074140ccadc2dbba55cc988f1258b2f977f4db3cadf167b33c1f95d966b8cab23b6c57cc0ffef50d00f89e24391133537dc4ef4186080afb697aa9a525b7179048addc8ebaa1fc974058c40c9e723e0e3449007ee673c71353eaf16d7b099a201ad21dffdbddc5e571e2104cad721694701f30abbe1c8c9ca73490e43ff56dfa31687d0a8f2246699739c2c094155cef78a3c1b3ef7c93424b5e2d4057872a501ca2443cb7786d73c15e9eb861255e454a2f9355ab2ef96ec98886967e27d7b930d537497fb908e6c54963496059f9892d1521417b07aae2e70dce4eea60eef4131f22332070efb558ab6bf3af63532757eb37e260b0a085ea87c3aa87c51f513d2a698eae6f54636d26ecf2537281e642e37dc1f95f9afb42c4ead4d54feab0a1360bf0c27b43afbb136b099e722c63a6285f552475254ecd9055e26eb9be29a7d685abb961e4a8abcd5c6ced7249d3388ef50ac2ed144aec980fa0d47117c7536473b84923ba11bc1831ac0634f427aaf95010ec4d53a0b9c43ae5c2dc4311be8790cfe3d7474509028f835e72dbb63d6695fc38da14cfea09dea1551dd4c42e99877ea31d7d7314f253976e72af5fb05151c23783edd917b15ebc08e3a3882a0ec7279d6b3fbc68046d42fc57e32cd75aeffbf87a43611f8ad1ce3b1eec9c04525ec7e05f3e766b7a66efb6658eea4bd65247224268a47e1a782804531c854706d6438818b73a6d537f2e7b72e05ff2d92fa09496afa04e33acbf46491775a18b8d4f880d4ddfa3a7c573c672339af162dfee7cda3a8413a68955d8075443c8cbb76ecad13fa4c124f52bab72314eb4f5bb9b16d000faf7a5da4affc2b73be0d19c64eb5d00c7ce1c26e7665cbedc5c1b15e6d6230032a14f24e23e6eeae5a973f0574bbb3eb7bded4f574032415d4c469f343737cf1a075fa65f3b34ec49c0c4555e59b5851e6f88a5a0f572560c122ecfc652a1cd3b13f80783b54a170ad4a4dc127457214f2cc3d0fab572be77494912e652fedf9e23f8277c193227b4705c4730cde83c70aa41adc54a72485396ad2fc863ff8a9a47522eadfddaa84e47fbbc54277107cdfdd2153ac972a83d3221cb675e96e01e3b2651dfc11012b83c466543435e3c74e5cbed5b8f3bfe67513fc2267aa1f042fbb575c7835c5bc8f082cae53a62ee45e4620e2c90727eb2893bdd12bc5a409308563830ab1c984b1e93b8daf2a23af5c7678700fb72c4f37331882016400f47c2d7a194bb8e5c9d0058efee05a27c4d244c97e2830b9a0917d0d65ce2f11fa9fb14091b1c584761a0f8a111da8feec36f342b42de702adc59010d6d850e6ca36760676a6b5fe0cdfede93f1720a92ec49fba9a43a728359439413e78cecb74fa5fea078b26ac688e5dc8036922d33de28decdf84609b449cc03f8195b5142716e5a8ea7352af8267ab7009511db6c1eae9878959172972398316c94832dd7f0ba75741f1d8b7eb21a49662912aeca8375166d3a4e7218a4d7ca1dfd86d79be119e65854272fda26d5f63882afdc829c534a97df0e72767905cc235f132012a5b836f867b54a3f83a2734332499f7dd6b09642c42c0023f32d3de8e003c4f4c26ec069e42c9d50770cdd2b67913f584dfc5ddcba8b1d16658dd383844999d1e6f446cf335abeea19583a1d14e7b741174e87cde465728f30d605f01dedaeb476f8a91627856b2b1c25be934aef4d6858aa9c78836d513aaa418b473915cd48e62855ccdb99fc71ef74295c8b3bfe99d9d047e5a3b772357cd4ac942e554a7fd66311201919f3952e3b83465dd8d4388a38bcfd6884725aefc401c91dad3cbf483b9f743fd34edd398a771d0ad526f5b3a0a0fe4b297011704a106c499496fb0d5f627530baf71a6725cd900a42b8f7f61cda6f4dba5f5bb146cf2916d0b716f7609af4ae7681e9d1470b57c46c5b424a25b9fef8ed498fb9745adc0a32cd954d75b819b3cde0d94c24867e7027a114a6957ecc9822677bf93e079ac4d523e3e12512deba8d3dba7d04f9ecd248984d51d31366e2e46ec9daf091548de5e262fc3273f2c3db443ee596606ec8c83ef7c5700ab461f972e1fc3be6de9dac1a53288f53aa400203f9b3c595a1aade1055e21af48216bb667f607b704dc83a86e503f874a04750c55e6e821f37b2e03303eb0380624a5748e4792e6985f7e050ceae201327ed872bed2842992e4c087dbdbb15c7d0726d7263a567ac690817dc1fdbe7ef84c48af07c382be924cabbb45ff217e492245632903676769cfa673ca010d464ac8520f9bc970e631946bc7adad4d38bdb67e96f67f0aa1d887ef57b082bfaee37f5e062ca5e07a2a8ba7d6eaed48f7f47e3532ee7aef923d9f7ddc4b63105660005239289fb323f7cb634cca8f02e1d1a151c7257e72572db4c6ef67320e82f4de5b47119eab78b64a110bb9f629384fba02f69dbcd7bc93b403812ed197de5a0665a112612de758d43a2c012b613fdd6092e2f434adb2f8c107bdc843260378a5e6dfa574f24f163f063c4ed73ea15ba724759bacbb165b48757d3e07d35e9f8318e8640eea7e34049e854391715bf40e81672b2360b795e566b8fbed1f3a57aa855a9367be51582bad8ae3a36b97ed215057294f61f02419708ea6da380023d45d5c676033dcb3236a84cf095d9af7bd6be56929b0f7784f220a2777b29d1840eee81180ac043213956357687d051a8e4322ea9bcca30289368f76fbe0c9bd10619650dbb382465aed7ed40ca6307cf8788725eae28b29895907d48a3417e007c4d807ad048d38ae845a2345f4955f9cb640d69fc1d65c8d3a7adb58d38db6e7cb1dc98daca04cf3f8a64478254ee01531c192d2abfdc202692188da017897277f665dd3c53237f2a31dbc87045c44b693d72d65f420c8e99f029a658d93fa779cfa98173d46ecad837b83c48acbcfc037f72616da54ea5126e79b0475d79fd7e161f4d3c4fb0666497185ff3a5b9a82e7b72959c3c04e830089dbbfb2cb97adea8526f47e0d526e01a6d0710b0d935f3f77250f70b365ed10605354b08a0b2e8e1dfa59442edd8a3318ec353d2b7033d117216b188b32b4e172b155d37ea3ea9ebcc362ebb6f2d03af61a44d37deec9b6a726347abb0df22bcf1a8f8ac6990b94d583f6ccc3d9e0f733451dbce67df8f0118cd370b6f5816aac2e70f4a6ad604ece5c573d3574885ce9fd5a28c18179c74245df18ad893113c00fe1f48946ebafcf8deaa58ae7f8d2b51b2e036f3783bfe726c1a273647ac7f6a70c27c9c688acd581f407693e1751dda5fdd25cf9287ff721b3b8285f32bc3f77d4a360a76efa841e9d8be41cd4a3ab4903470ab46647b729e4bcb7281042b43f0dd8027eebd96724199da30b2c582a753d2023829ed5d72a2565ee1c7358fdceba406f2930d321de95a0e8a80baab50a2aac42a19103e72956422aa42c49d598a8d2878a23c3edcb6f92d3818238b0f39e3de191129e71e43fe50dd3b873fe533fd40b615c78a5dd705ea29c37c946c7133b974617ff83f4f61c4c4c1a4c63e4a0f460f9b8eb47dd0fe7b3d3f8a0186ea50e996e736c549b7eb9d37236c76b129d82814e4016117e4ff823a39245b25e45879ffa7f5eb720143f1788fba8208cf648ba4b58f775e7157eb43d60896a9bae9f69904bfcb130df8e198e58f166e4f70773702e59bd6ff6ddfb834978b022419b4ccbc6d4f72b054d22670ab2795a7b60c4af46de4f0dcb454aacb2a8f45a4bc92d1289956729893f1dc951cb7914a1187d8088bc5e6ff619218dc13cc179fd7af639a3487727a144b41795f806033122aa173deefae89f5af751ec3376874392fce6bdfa40fa53957bcc547c6cb6fcaa4e5f557297cc88c142b128716b3fd979fc530343d242d5fa5342db1c4cad705f153e99be133861eed83d40af96b5e6e24eaaa8d07229f7b9738063a183fdc832d9797c7daaa4583035ed5e188e0eb1b2bedba12a91da41f70f7075bb31137848e35dee4eb6859909cce23e605d3e310f211034c24621aaac42e4317c75565b4204037952468a15c64f1d121b3227644bcff04184072d0bda48691102fc5264c71d722e7abdf2b60a645a5cc1f0a2ed0f23634b42911de92819a985ea212dcd2f34dcb6d37ccdb2b65de8b115faac446cb6fcffa8072e323cd613f277727bf2f36b55e02825f0fba8477ac7a4fa3a93af5cc61495972877bc0bd9101551991f4491808fd82bdebb64ced669641275ec2821dfb1f266a288a5700ebbadc74041598c1b3f2d13e093167d7f10ea34e60a77bffefd0a972b8947a7a3d33f57e737bfb9ed2b8ce4d6295d6d7a66e81915f8518ec41afac63872b0a5d59278b4096b111e99084649f0ed8fb327a3b7427e90d0d1ec03c3a4d12456d4857f35b3740d554305863ddc0b9aa410cbf13779f480537478f94725ba7eea49cdf99e9d6ce523a2960306bb0284305e6ec402b5bd66ea60ad25d2769c5d6d1422897f7731c76d7e79532971f98b78c6441d07f46d6ae52a5cdbcac722541bc9c94c3a2d4f824f27f20f881ab7f5ac26f409b4d8f96a4725bb024e8724f05604208314a7ccbd89ad2e5ef1897f121ff313e5254c909261e96f3700d61c3b8d9cb4feac1c7f5ae6ffa5a466c8913e1aff2406fe5488fb281309854d008d37b43a9646fd7c5eeffeb23ebab4566b4841cccf23e1af55f55ac1e39d2f07258b55c892aef3980205477310a852d641a6149d12c362cd5a6f5f838af23cb0ebbc59b2067f7b70df3afe4fc81c5d929859daf9a2fda25c91c29fb01d5f0664726d74969ff8a923f00624a9eaf1ffa345fdb8e72447f7f6fb8349ca6bd49177287f16bad5264b7f9441067375f59ec648b14f02baf39041e4fd3c46814cbd84c0ed467faec3a78bfc1bc3666412435cfd87b78618ac46a1dc44c2f38b0366b724cfd66e4fafcf437e1300021f6d112bdeb71ba625724423d3701ed7869840c7259d82e38bc95c0357f846a3a2a996d2a1972a7d89ac769327ccda2be998bdc72a5778bc30d1b8991d13105b61ffd3b4b3693303f53603394d74d42208665174d4e2132544beb6e0aace774f33d8505554b77534ef27d7396367bd7f80fe3524a8d4dc47fde58160006bed07019c0316a527aca89a504f137457d0d4f4957e472c92051ab9838e708caa59af39fadc7e5a8d01a99b900fa6bbfb480538449d872a4e6cd63a572c9e010582674ddccab015cee2ea4483026cf287e4ab86ffbd522e5f4a6dca4ac247463f83fb6dc76ef0d78af111d51d575f52be18b2a523eb272108f73d7751395feb507fba8464228dc2c0312ff7e34e232a1df436315564b7282722c3b57cd73c538990077c3c5356f6a75014cc61945d98cc61df5aa54087287fd7b8877a44ad18c20a2607458f196be50475de911ecb0b0c31f20a2b8ec1fbee4b5a1266f2a38ca7e91b9b9ad627a4c2680229219013b9ab8429512b5be3666d784055a7b9318cdbc949c07474af4797aa769ef71e45d18875b3c9e48c8727c9ceff4d085847571718ed1c1346ff19bb86ace3714762b3e6857a0aa2f226ba4251beeb54221ac99d67a7b9ccfd603a31d1f7ab4019b3e98728f4759ab2e72c69eabed296dcc8f09f9b5dea6ca9940ac5691d1f693424f8afce578a899fa7216667bc63bc6767f6406e0b793b50d06cbc31a522fe385d43820a611d762ff72072f592ea53386314b9d441d9a40990823c425f92f38ce73c09fbec51798de6df0528721a5aa1e7954e7429d1a38a1231c3ce71d77f1b2d11982fa63fb397a37bdbd9ed3cfe0472321ac07f54083503015ff79eb4a9e7b88579b9b32ac97af725c185ec1c57911c203bcb1040e15c569c71c5aff801384fc6a33c3c1eed81172b6a271520a4eca0d808ee4793dee2add0119055e0a396a2c54208dd01c7d4b04c72b742693e3ae74b916ddb8d76cd2a9793b36ea17aa0e911e579e4883586272c7f89b310e8bc90bd248abd592f7f032f55c93ad3427816e7ea0ae7b8235a53b607983c70c3de00d8cfe90c193d94eac8e49a756fc7fd07c2886a395e2fa4d1d31ed5eb029c8e6273f9a1f1cb3c7bb93f514f3c94ad7bb5d7291be9c85de6f4f32302d7ebffddfd1fe4dd2681886cd99f14f12cf263c70c6bf1c809c47b4a669047d6cf37a521ab21a5929c22a52b108bf6a96e878c76a91257ef53cba1f075a504de23dda1fba0becde9bc938876dcca2db07249edf676bf6bf5700b1cb517200f3c1c6774e931bf1f6990f0604458f90f75a1311e1c129ee8a1e5e90c2ea7260adb4f1505888454a5b31ce84afa7c92693eb5e87fbad22fb94ad1e6e16717286a3bbaf384fad5197e36fdf6baad01acf59075c57f582e118df7d8ac8776d72130fb3d9a6ed99430bc52a336bd66a4bb15a1322b938d64b559f126340618925b79598b57e4407081cff5cfef23799bb78d946ff986d1307a2aea65a651e3a72815f6a37b39948d55703ae2d8ecef61fd7f2b4fc9a9f05a4fd089b30bccc7b7205864faefda44840350e328a31312b5106a6955d9bb789ac1357b28919d75d7262b50ac6f71313ddadf2995e3b4950b2ffddc73c9c53747211d268645c83f872cda54fe1c221c7b820de2fd46243e325413867852c4e7faeb8e7400785b4e672726c6df301114ba2a17784f71c80acf5daab1abbdd9fda49823a22ea84b8fa087cf0de58fd161af837e83322438c8a0342b5aa687e5425d204cac76159ff1b25641075aac09a32d9ceeaba06de29f285768cc5ea4c0085557708f77a1e1a7a223935de2775e43ac2b1e19f27f5a7e0bf94621aec0321cf12bfcd91b81440f748b9dd26c80e1a774d66f1fab7455dbb71bf80f97e90e1120179b1f5e262fd104571fa9d488dfb6acb89184c59499dafae8fd539fb360caaccd8bf376b75f0287228ba9a32b50ba2778248a2857ff69be618a543147115cb9d12861b1fd0f69c725edbf1fc22564b075b06565ec1ea1df99265013cc5b291a3d9bd7c09fa586772423162b70e2a03f4682c8996059b13753f58a30e04f492e903eda7f67f9f0872bc0e1ddf0cefc458617424b44305b233a4ea5db25dce7d5a8f1aebdc98a8a072b8b9c96d6ccfe853fdcb2444724224ddcacb6ff1f34a26dcaba24d4b5b5c862e235f92d80280ac4d6fa0080aac75fc2f79d0a8b175206461d1247c1b7ceb8e48f9f7f08fd2a52b918044ab77eee164e9e4e8d7e7420e20e2f0ca51dd3d43e745a4a60dbc624a8315ce4ae6a852e01c0e6929cc1d27cf400abce6d2eaf998b2653735a16c550894544d70f8fc3733404475f3a016ec21b13aa91476105783614588fb94b7fcd260879b16c9ba4b4cee4c79176191b9e7f50e8fd7aa16bb0ec103709b15400cc6eee80257579a3866b53a04b3c96d0664f23b82ed2df7bd432c7237d6cd7fbdb85f0908f90daf0ab93b466667988b32df0ea20ba12b0a2b3b837220654743542aae0ab1f28e686980093df3ea84e5e75284ca012aa5620179db728e780efa2a10e436f95e3583290362406432524e7630516d27c8fac05b18a5672c0b9176a797b2853b933bb6ebdf846988dd264fe4be161406d9bc60c42f5472269d5e588205ae1d1dbd84f98897e1b6e6ac3a855f887d2cdfe92dd40137c272bd80467c032d0f497721fd66b6cc39406c6b3aa7f9aba49c84ab62930b69bf723d84c666e26400574d09a37a34b4a29a81f809be5e5d2edefc854f611d79b709ab21781b455bbe74726099298ab3271bca96c028cf4ea7d28670153c71e04b72429cf0e70e8c33ef6da5f5fe2495845647da1cd6d80045b3d663c0b980228d3d1f0a9c4ddfe1aa88a87540ef50f495d8567d974ed77e84a76d54c689cd42cb72731f318add3a9c42d4f524c06b93467cddb205859651dee1b7e75afc330c035157f87447d07406157ef4c92d5d79f516f32e52b86b911e7a263f3466dc2fbe170e32b799f4f0d5542dfc03d91e90c6b9cfd92da997c3aea397cde3e28e2a1706141c4f92d98afe6590f6e1f64c94b807d33b17ff080094a418d3f95e91d2ce721c117a7504d2f269dddd261a09cb24ecc1419e299dfb5c7dc875fbd443345a19859bb47a0946366b28f06cb6dce4019c1ad3771a2f09bcc20558a70eee2f877281fe64e71b5cc250ea27cb7f88db50e4735f9b37100d438375b793b80e3cdc7238e7675d1bfd337ade58f5e9996859ae317f3d6ec13f0eb9731bb222d1d86624eb7e23ef705d744efbc0be706ecbb19646766e594168ffb73330ecdb25f394723ab744098fbf2b4f6c835cfd490ac120084261f818f25f20499d021a3b0ca5725fdeaa3a04f77c156616c18bafff1738d26edd04173f8476b98890693caf9b72e303944ded79dbdbbb7281f1b1b52e3e9bbf356f2da616b4f0f5bc7634768372d8d182c272f3c5a81945c9b1c73fd4cb89afbf925b8f90d2047d50d4b49471659684213252a0ebfd1126f6472c672aa40936e472c9cc1698fec6e24106975137552a0411f0eedcf7ec03095bcfa2524e7fd65eb57ec5495928b1bf153858011bca79faf0392183848e2c8e0fa8f9cdb78563766a46c5057d254b74d85f7d565f3c3559d3b2aacb410403a4290d459c52a2fa4f98b564635ca1b8db86c823ce117c73569d91344b7f5a420a9a95a19e3789efeffb5247a438f75b6ca2b6eaa77245fc9685a3a2f01160118d10067d30f45fe5a6a3666c8869857c9c50ebabd825f37dd767c2c709db6f7be77d101f2de04f5d37b070650b481f45c27ff1a997723dce1fc5cefa89e7d14637aa63894460bf232cc71fb2c022e40b9ea733b25e72e1ba0d64392fc27f750863febe9cd4cfcb7684529925d90cede14a08fb6bfe72d820f3791985746198157d5f5cb039b2d716f6d2d98e52b0b54afd1896a6aa72237c11e4a4c157a7c715d4f211dea12992ee4fcb6ce404e800450c52bf5ff165f341e32580b53f62f074bd8120091fb9715e3a0abb8455a80147608401579f44198381846615e014dbbccb5a304bc9c03cdfed9f4a23af72fd5f7c6b7e3fa072755eedfd4711ab2904efc9eb435b8883839f5ee1f7ef7ab0608e7568c30a3e3ac5e997a803ea4fef89f114be4c8a0cd28b0e10f0759feee362b3133311047260cd16f87d99c01f919e62c4529f34b36c52076a1b639bf0786a822fd3c0049972b9e161e43f31283d8e4a4c569d884bc96c4d6537c9a3b83c61d4dc5615618201e7a8ccb9c542677b4f73d1c3e0e21bc0ccbe2a27930a06706ec9c1dca5de130ffd6e9e9b2ddbefc68fd193ba191fdd8fa770fb1a1f26e6cfbed9bec0bd085e580041ad8b4858ad58e7ae532ec7531acf1c2d23cfb9bac6d8b621e40bf1a6fc32748465c82c9fb7f5dfde387f953792d6569d68e450d9a0fb5d42e06cbec70207f720afaf344d9ffbc28aa25bbfae0c594544478e4ac93b7e55d777d8528c7d729d55b0e9a54688662c105210d0c281ba71d4e045af346dd610d656c165c79972a47485a61b152b02e6a739fc4556c8325b851ee317cbc8c7576cb99722b87b3f82e4b143b7121549a9ba81011b9173fbe1014b520a7cb0e029bcdef4bbe1327244da657a7bdc8811ffadb2ebe18d74cf433c97efa0c8fb3d0901f721b6376a72c7586773efda458a2d8fad7aaedbd44360c9c450ea45a6039b6d32d0404dfd193e93bdd19f2acff1614401b56629a054328caae548fc01ab2ba6f03e55c51a6cbf22b815b783131f372a34f845ac26760b3849779b78074bc7e20cfb69aaad37171b5828e518043d481839d3da7ee24d6eff3c81fd30ba23e50d3ba9294fdc723ed192921d35d8eaf6c35c94975af8880e146452475b73c26962ae2318b90f568d0818e8349c4a27bed860954cf54f7a61731cb5c78181d1bd1acdc646523a3ffe3b8479bda676c824f242997b6dfb62b1155ac3f80e723c20453845571ca821c686456a8639ced06129d09ff8cda9ff9c633330d6e489f7ccf2f91222652c72cd2b2df8864f54781fa2c502725636e6447bff0e318e19e01769ffdf0d82fd72b096abf202f8df2af4031ef8385c2609207daf90222f686f0eaca7823d0c8e17d7fe22239f701fb056a8ac527d4a2e59d6d2e4bfe6dcf0457779cf3633a9a172f27aaa13c0b9bf7d599831adf3fb8848db3a6f91d05a4f7994e85cb5650ac5729c1794fffd25f7b3a6ac18dc15d84e8181e0d02b9f732db670e392150cf1b272ddadebabdc4dd2923ea786eaac7bfab3224988b24d54fdafc58854e2dec6e8728f8344c66b8830405b9ca096d7dee303ae8887c36323fa9376b77094c1fa6b72be8ff9882b66c658261b715565a66840b4d647ed4e9f1266d7efe79f48a9ec7287d3c0dc283621c35c3d5207569cfd37e3dcebe01a24850a8e8c3371c271c06d44a185a4713d344975f38a0b5e3edf11ec3306aa137a24ca5cb1cf7f764fa100f61bdccc06cc9d9144754522f0f58fc61965b2c3dbf1fa8bb70cdfe1bd2e98725a09c6fe4c58d70bfcbce779142155702035b1ee77e5bdcedb24290cc4c22d7202a962c7c18cfcc30faf09ef9cab27255a18b3e41775ca953bb5f79233365b1fd5d0ba572e4b25f36b9bd7514dbe4ddee945681d656056aa31ef0de432b851729364c7c409558977186b77b9d5225ae17317ef362dfad61d833dc22a2ccc4239371ce1d8492350a61818ae92b2df1dcaeaf2f231be21874b4ef225b25a764972a88008f01bbd15dd58d4323fb45769024f724be8f4306a20bfdfbd62265dc772bfa3b0e16682abef5700303a02d1135fc019ab643803d286ed8ad5f74b667c2cabb9067e946536b858e71983b2d92a44f37038e9691b288c988e007d9c6ae5723f1d489101a528e1e60e640c637faa33ab32697f8ffed9090d5b43d672960972f70f4b2951553b3ce804ca66c8848ee0d63cdd9d3c8a27f3c95d8442e76d9b01b9b2e66dd2b83d15dd741c1f997c9cbc6af654ef96501958fc462f38d3d9b472a3cd872acf531c8d29241bb10abfd3bc884cc0864f7a5d4118dedba846915f728677fcaf3cbd045d9e25cc9e18b5c22599fab56edd281a897710788a2c745f72131c7d19d7524fcf5f55bfeb5afac4e7ef3991fe8242d41483f2976d08f3c329116b9f5a273aa985b25c83153168bbccf3630ea43c4a5e999f64b2925f655f35f407dc79dd052a04c8f0336b404afd812505dd57a5380f7b13b747eb8f8d7e72ddafce1050bc0b663659ee4b872893f7135895edc321e45dfd471953e0a85562ad18ab5b1d529d0ea5bcb8980e612f1a3af2941a8021c80b27d084a1043795727786a4a548ed4b30da17a2feccafc1c7ccf5501ac215d39b6184cf73d88ac2720c935f825f194df906eb15599b0cb73dad02619840573d052e6e24e42e0f942316114578378f682ac975ac6624b26004df87fd2be3ed38ad74b17b3191593a721c69253a4ee800be9abe011e2b74a553b052f5c097db58259c0593669662a165b3ef653442577a21edec5c6dd1be3dfc7bab91c977b3f64db9c9ef3c330ff506d49d0119eb0ea50ce7ae2cd2efbe8b6b6282d132b54284b0f2d5a013e1572330c42838291c39e16627a90705472d421e04eecc46ab31f27589b4f9cacf2d6c481a383ad5a628f01e8587e2ce90e61d52b1575e00ebb46ff5c2e5b04f56595a1597797742debe38d101b37b50149c9af26324864dc4be650153d5199159761f72cd9dbfc1ac4ddc94bcb15b49eb330829639bc6648fb5158007a3865330321f26a73e5d47c9c1f38772e2c3fb8f0d48e128e38d7566b25e79c158a752a0cbe2584aa709763949078ffba927f5ae4a6cb4edc69526e2ac99e0232b30c3277933721461dad8502305f6fd699d8625a21b8461d10ecf86b9f819dd84e8733804847280b5917b3c53c9c33cc1b30542538302454d41de166b8ec4de08f09a228d7e68abbde2d468ede1b71331474b205772d22aa58dec314144c4ce99a7b5512247724146ed16069f122108bcc56fae78bda5e558cca644bc38d195983bb46c41b472344ced5d470e99bc12cb0fd6a30e844826ad8c8c78f57a3d2414281fa6425c7296ed912dd96763d8acc5e77c2f1b1c23ff836853bdee7ef4c4d10467be701d72e7dc44bf40a9b6a34294dee83f8d41dd5c60f52c4c16fcb6086feefc49834c20e4bcaf3c6de477256db667993b02fb38290f6785c15cf7dfceefff5ff1534d7251ec5cb37e3616ef1d16aaaeef9d4dc36ea4810023b51af226a1c15a76f9587207240bf2d9c11df489ba88069d4642069099417591ca53a1cdbee9a82383cb2305b088b6050b1af35cae4d909c5ece70a61e5c123645de5199e3f17a67b95e7268828513e4201c62f7f08ec73736df55cfb530941c690f0c58af982b2fcd3c514456eba99396159830ba675be18e5678b1ab87461a2a9efd84022cec96405a72d0ed6033411bd4560ddceb2174e593d114e68c5067653e8ecb6d13def5caa2727824d9af1c391399174e02842d114a545341ecf507e13495ebef19532068aa72f96f5babec8727b5bb032680a31ba7a7a0a37cf88a79fc9962de560eab2caa72cb4c522c71d02afd2a263a19949a64cc477ac6f6725852659f4176c6a371f5720f9f14e0010992a9c4ab09ba25b7e8facf3eaf1f94fd9fd27e8f80cf519d0d72d4d124c0169a365d285002165679555454ad5c321f8cce4bd61e1774e2098044962183d1af24807783a852b6b4ec876bf42b3b0a50735cbe3c226799123d4052b3577f6f33c443c1334a86daa5c15de7dbd16b0c4f7748d0e875a1c741547572f82ca2678974889b76a7d008c2a510cb3174c89f98b323ba8fadbbe6f1769f482245cba4f6654c88866bae1dafdd49d999990b9cdff0d4ff9d2903060b7657728aad7dc69cc027d46921a18c5263a0f89014ea3aa8b03dbe42ecde0937389c72e4c33d1601a765502cafbfbdfcc0f471a4e8b0180ce0ab1606dd3038190725215c05be194d03bdd1c469eb2d65cff761172a9a2fac9bc6f81b87a17f9383b672d3ece16e903e30f6dc3c33575e4dcab71e660b5cb79c81d8b85754966f932472dba5ea8a118ec0e097ec7ee3141bde2598046dd32b989b72fa934baffccac07221e0a5f5ee17d976187cb4372d7e7ae59fd84bed5bd0d5d26509c3c7fbb84e67a4d829ff619fa1f817ebb9e679b1340fbe2746ba7ba9eff54883f17dd07623724922fd82d9f0ae01f49a1b8d096b7cd02606062dd71e8c4c66ea3939b4566772c61ff8fb9381ab70e7e83d641f22703716a0aebf76f4c11b36c2b362554d1100e1127150c70dacc3030f20cc533cea0d1190d051e4a1ee16552d7251d5c02802e72b15855124eb569ca055977b8798437bd45f975def4218c7176745605983070e60d3f3bb72a8365692e428053f6c28ade894636b440c7358661f6f9010236a96aad0ce79c57197bd1e5348b18a4c909f2316d5e5ecbc2ee32bbb29f0e99b5fce4586058c9acbf2d818fab4b135ec997525fdc12ca5fb2a335f05a94a245f725684707c7d0e783319feae616279a42e37a3fe9f01b291d02c27fd3c718aa5645d5b9cfb8ce291d823e0478b692e5f4f7e76b5080839665145424580bd805c726ab680263790173716fce1559b1709ccac97c70a7190e0072b0db9d49fb7c753026bb88a3ee338440927cb28c36b6cb9bddec0ffc805ff36d190b3e5d150ef7251c186d316c348454ffb667dcf9aff815550ce277acabab6ccf939342b43ae72e11c9aa07801c2121c73764b025f28623b5e3e7bfe22e9a8aa067f4442110a5fbb28592ccb27495064e65c792c1762502e86522aeffafe5fb9df1d8f4f281a68a7f1d01ab41d33e176f5475b9760ca571d1b61f864895815277ae768d93143180913cf2b5a09418bc7c708784478dd05b7e2b92ae4bc77b27471b8e22ff3ca72d20af073a8bdd3a15dc4554c1037a2dfeac52f7b7452a10937d3a34ae9b26705cf46b80d09e0bfeb9bf388c41399b60c974a6e6c2501d1ba98c3e679b19f527221c7a9c4a650aef325cb454d3ba093135f60ab114a4a2cd01f541963944917728fcb0a29352ad98af603eaf19d0fe77c015a5491ffe0c43ade26c026ec79b372fe465dfe285e69edbf985b3dfc922067cad795e1785f8015910f5d7374a89b72c9c0273047a49a4f910558c718338ecb7cadae9523d4ff1440941b56b18bbe72ddfe66f013e711f401b27cbd8f65df3adb2aba2296c8177e4494f3041a0450720291d5a1e84b806cc30600f58562f346d471e32a9ba1864b96c320ce2034707263403e0981dee99209b78e442a79b565888529d350bc61ca6b05634f532f0f2dabbbfd011f6ab78f81947cf13ad39c6e8844687c621edeb278742c6350d6b872788b9f90fab03182dd8b161a078dade662455bb77c8371c366d4e243c8a8406803fc6d46b78c05498616c5270f5a75965a9a386231fbf3638612f958f2ae70040c4ba32d48394e0f7320038a90126f6b590459695600e96fd328e6f652da830c819812f234b6c135486d069261adf3be489d57a88eb3312113fb8f03d64264087513d1b2e1210421e1d36f77284c121cb1709e86b8762ceae8ce5c2cd42441720d7f9bc9f72f1ce9c9ca95ab25819fd862e1e204b1fed2dac46bbf69d2a61b72d744efed7cef358a6faaad2bfec9ae7844cfc53ce76dbb632a0a43d32e8fd372c736c6c264798a4bca1b6c36514d7e11d66a1f0bcda6544be7302e1fb22548724860d8a579124441e493b47ff173861dc84982faa1b943819f9b52d2261be65f79174c9f5d56816196435ed32f16870c41d0a4479568ecf5e3485b7725b34a280bb21fa636c38d4ae6d734ab05ede0937f31a7904718984026db7c28e0a6d7727e2cdcd8f933717c95c26f0a1bd97ec02ee51f29dabddabe893a2997dc6bbf19c082232dd19bdd306fd68356419ea26fdd0da805910256cd77c0b45c3821e83d8bf4842efdfe8310c0913ca4566af17e8bc39d8a2e1ddce1c082681b37983d1f6ff3e4438da603926c3ae1e445a0eca56332ee425d61492d5d2fb5e8ab93427249420c6fad2f83b03e8ac3bc7d84c3d8bafdf9873bf1427cfe766535eedc7e04ed7186922aeeceee3f1afd11c2f7cc3e114d34fbdfa08cc05688f48db11e841d57d09986df5151616943774bcc9c725715aa3d4e6b3b1d76a90a1b26aed1294c3d73607e4c014ba21fb66193126fc16950d5f9728da4e3f78d50ec5ad515db1c8920e6aca040aab481faa9961228088ece202abb702192e72450cdfd5109f172ffbe8b8073a110bfab272392cac58ca0d76464cce2e33635af42067dc2e4bd72a69cd9f431e12197c39f3acfbd07a1e9a8fee9600a20e9c3f5b3469d569aea720f1747906e48371ecd9353ca5297590dbcd1511fd56a9903b0a3c2f7cf3980721f91ee8e0207e9a56824c86fc9abb7256cd0a76c10bc45d6da273d8840cc4b29a03c5d5d83404e633700b9b0e81bd61e0076bcfa744158c18500497f7b362572866901c96756fbfd20c5bdecf7f5bd606046fd2d533b47706932fe463537ee5ac82c4787de631101447a41f7018af81fd59e3936bda26d4224754cc888d753722210e0b1b43cb35daee8e8ee7c022496204b1bf880b9fbe83de2576093dca73d23f92903b5c024ed0c7e0ad98aa58af9a04a79e3097d8680283283f55af08d72f8b75735d9b29e88a05e29ac1cf451af81f71637217dff989fdd6b01e2ba1953479028b25cf9e8cca3173b3bd4db121e1b1c5e77e72a6a0c24e5513d1ad92c724b6b87b0de30b92f5dd41209d0bb851b88ac46d2302e3c72cc2518a1023e4350e166923138170ea934830910c6d62564d74625123d3a3756d35275d197fee1727840e3e9fd640000eed3494009fc3267db52c47a0d0e3643e3e85adb3002d6728e85671d517d36dcc30aa01a22c3a410dd434ebde7c9c624924f1758c4ae0c72d324158a7703734f6003af9e8d325834ce72161e2279f23dbc4f65c9805fb248ac552a07afd235ef70655a8d2ed29f12a1d01258a6768a267f94b56e66121272657e50677797c7ccb02247ba76c614d595607968c99145c83d239390af00f2727438536163a18651a2573156550ef6272e208841e7b59d3195d43ac006bdea72a95bade8c4cee2cc12d785a51ac108d2d6a4bc5b369d2a80359684b0c3bf56722cc5b56c018212d8064a857e7f77a870a553eb76b6cf7335809c65ed4094f63a215d65010481e049d439f8415c3d8e70ad51c894f4d2d795405a011a41323872ea031c4e9258d61e4c026f941fbbe77ead1d3f2215d1b772008e6bb62f88332fb7ba77055f7be34d769dee0c6aa1dfabf43ca48868159c78455f350489ce7072fe7517fdead107e74f7e1f259161bdb51255e71497e1a5b75cd739c02d05c67204d9a4af77b888355b8effd9ad56ea71c9b9c9d4e4fbfb2fe58154092b2fa51b9b4dafd726df3d43e44c94947c1644be0bb69affe42679505dc46003718a2c72f79ce19563737b59350f3c15aafa40b1b799669cb42391f7e78834a496d10c727ab001adb17353d2f9a7afb884368b83bccbf07b6f6c4daa42f6b9b978d1db723e5da8efa787fefee2de391955cd5f5e1a5dee297e1e97f72e4fa53dfd416662e9ec592eb2e2cc3df0f798e72443ba18a1a544a5af7ff1ceb522f517f3471c72d3a8fe4775a7bb42703d706c1b9f958bd739b653f3d8ce5b7761a5eaa1535a72a3d5247b494d075330e8dd66d4940ffb9f1def7a7ca7674e12d4d05dfb01bb72e2d53160d5162c485b7b1d59852aa31dc700c4a8536e5358095874a971057d7263417807b5e8020ba8ca745cda0ec99012985fe41e57642c4c6f597c6ae106722455f51dae8027c525ca086f3949ca9debc73278c7a9aaab04ae02b79cc329721917f14336396bcce63f54cdf91db5dbfbe0fd1d1d84eda3db1ea782dd87bf12c0ab504e572319cc247ba3af959b538d251f4c244a0f01ab1dd93cac2f1e970fc215cacb782830c57230644c3d866358dcb1336cc9374ed98d75e4138c80a172bcb28f53419d695910f18ec604002b63bbdab2cf46aa8ceb2cd61765a50e666fdc7b0a1ed82a143861cbccfe814a7dc106228465ebe607f799e3381efa684072b07aacb64eb5553f2df07fa48b81696e8974784e6c88c6e8c60b85c457cf567277bb35b9e01bb2144d2fb5f18ede9ac1d8349d75f853bc4c6c9feed6e0fc5a72c8e03504ee4551b817f82dde5af0d0d3c17515731deeb55e8104c6843721a26558143ac5ff7ea723bb827b5a265f553db8094e09457adf3520b1b80450da9772b2a0aec5894c3c1ac92e14a92e765bbc996cf2554bd9204c18b04807c73e4d728713009f85b651a76d1357adc4d43555d2e3eefe42c4a26394f6e381d81b8c72261568328e61f7b8b43e431d4c6fe32d7d5bb091388d62e7d5e9e3eb2b43f8723ac789573f89583fef3ced34b337307dac616e1921c0ea2e52b34ad607ccab4ad62cc9f8151c3f5105909d2170341e03e3b0cdaefe80236fccafd82eadf4ed63d8efdbdcfcea826402d164b5aa37c309908886bbcb66ad1fc9631dc5f929957256b172242c63613aa15cc2040ec747541fafea8d7a9ab7cb51a543695b2cfd49b360ae867a1f68a9c71636e6bca182460d88fdfebfc33c9b71e1f21a5d6e891447e38736a803fe8215d67f8c7fa96c6c40fd1329bd2d7a633ae83b64d8ca4372d25e08e6f14c333341ae749c3871fce852e33a0a4912c5e5e0cefe5bb58edf725c5f2317359440064322e1ef63e8d335fc1683226e384c2929090127ca43333839428823d6e37d7f9d2e723e8e0bf97fa18b5dd988881ed9f1db9429f15e9672937bf6b8566e4dc6eb0fb31d3ac3e52a7e2e6737f76ff17946219e23d8a2400ed7850a5c641bed0e9f74c0e11abfd624c79e875b8b950143fae4d643e7ef4872d535e76c7c6d6ee4bd47b63e4e3edec07ad2e90d1742cd179d048bd998a54572ef159ca510dde963c496c1757b83a3960426a12e5a836eb41db1464ebec64272840f1dac51614369eefb7312da1caa0091fb26365a40406a55d373835b2ce2726e122533b6942d1a1e42180889c074a0bb4915534d002ea57cd011fdb27a8f72b8556fe59471af50c0c06b4a396d4ddc065b1565ea633200b76917403387b5729eae2a3d84e56fd66301d4a9a9597358fa77492186aae4d4f4dbb27efd51ae458e51ffc609bdf16da0debbfa25336f8530b7c32cdc8151fd22f7e44a39cb447290cb295b79c03cf6c18d840d06a50328e659203500c1555935f467badad049726d2abb433cf07624a72c555d375742d1ba2b13c32c4b3523717d49e6db8f9672aa902432739ff15d5450bf083e74e27748c166b5fa4bee121a80a9f3671ece44947343ed4ab005beba62f26e2534623ae01e9643a0ff60902a251d0819338e382136c0a58bd73221134677279604f5f91548e40250c9d0b5da91a804c5ea0572b3eb81d6441f1499d66a2735a447149543d90a8601ea0c6170f44007da37e8724120dc9925892d48d0231cde908986ac1794f34682d7af6ea4d1e35f89ee74029814abe957d6bbc105f0b01aa1ff6b4c063e757435eb594b62c28e7562fcb50c5fc59e5380f603fe2194df28b00a7fa84e954e2d85b26f73f90d93f409bd3172a6e40905d251f0a9496da4e3b22a0201da78c9a638689b163155d6983a65965ccfce8a24e84cc63dd979ecac59825f5719526e2077ec214c41ca8bc12f4c3072b33c54c16e35eb205fe45be63e56b0f7d7399f88e9fa0405b296010be3c1db728fe9728e02a8a952f19d048877d76cd23b868190fd5453f5267ac807101225725d4f1e22eb78ac398f9b5124e12b6601c86df71a5c237ed864a83107defabd145af04745764c053763eeba9d8b40fb09c98fdf2bc98307e6b10174eafd21545d7f11b8eaedb729a480950a58e27d9393c5bbafd8bcbeb5c7d473389b0838c30154aa78507b7624d0b8a12a2ab3002c9eabb398809218db78ac03d96f36ddd270d2ba5dfb0b8bd8d48dc4bd16f14ad0c6b12bae3c684b9c43822efb7195e13a722103e538607784fc6c61960991a2053689f8ddb5618f3aeea315eec298a3e572e405c20e41f1a4ab94e485953c2e1c94e65c2efaff9f5298952d40a66268ec724398ad0ee801e6364492c11efb9dab1428da73689ce5749a21400262a9f5f272fd964263457261d56bb6c130926b3196c78b99ec8dc00b827a9cebb91dae04644f8b1c186c30acb1696d1133186bcedbae3803e019ed601f4ce01cd1d8b7aa723ba0948053c4ba164a70bcf5fe49a7972ad6b28bd7e837b759b3dcc415f0823666559da7bb65bb4869ff5395ef1dd87f7ebfc3db8d93765195e92a502300db060e51ef1d66eadca25dbe970ce9517e8952edb6b6b36731418156abd60fa3705317a8ce291e6f44f72627d54a097d31436d0e6992c3e378399c7f2d0bdd5fcb72a42903911b3e6d73186ecb3162b0d611fbb6295563a3a91921c0ae3a80b3fb72c3eab196e15727e9cd70821ec15ecb517ba561b00f10f160c64b49ba5e1d187280d48ec39ab4343d637e2ff9445e214f45ed7de1a259f97c0c6ca27a9c9e9172c1662f173678284004950328c4c2f3b250caab6a4a7e29ed23a5be088a32cd7258bc8a44d4e8a5ce9d17889c7324edbaa49f523c521fd4abf4b7958b3f8f1d472e498f838dd1b00ba1ec59434abbb3128db7adadeb8e676740be11aeb0974e30f1663cb6287b0c832fe2692e1faf9767fa8ec608245d21b43f37b7d8d1b964193c01f7e1111228a53671407a44c276d2f70382d5b4622519e08cf778eefe32724794ca847e7fbba20d11fc221cd2e6124a5b7c574e13d617ffe408a890d4950bc218438188f441c914265deaa53ed0ed2d928ca79950f4a2904a29aac167e8728e66a9cf5ed3266ca43c4a261f35bf07bb8f6ad2c7f650a32a8eb81c2941e372d0f26f7c5ba71dae5272de3aba17c1dd31d9ea1338683fef0561f49e114898722fb5fb37d5bbd0054921f6ef93e9e4e85343836140ed3f2f6c9638d5f4bcdb7210a1d2c45fc4622f9d58327456036f18bc7ea0ef9f147698e6b24a0b6d436f2f5fa0f815b806b8905c00e8d5c1ad91464b605899fc69f7da54f440940b1a735ff86055162843fe079132ec76a511f2093389b2963da4555cf1e506a0f8101a721ae0328ee6abb903d9faefbf20be3e8d937a4e4ef1a47bf56fbaabac0f48e77231829357b2d8070fc702962e404f35dccbd30c52d415bb4aa08f68929a09f7729dc97128d2e01bc422ded1299e00ad3af1e40200a4adafcf292f19f71f03c272c6bedb2a5505e783f7865fcb7390909ef9bd783d6918efae0c2f9d1d14c619603b817a905a44f34d2a285dcc9d638fb5e53dc971ec97eb1679a4b39780bb0122d2fbb8778d1dfb8c9a50d11fd454ed215b78741d09453a26f06a7eb94153447286ea50b365b3e79215e76cec097329eb9c9511700ff70fe8e5953c1667301a726930678ead97c257f3516197e81a756271c327b84d32b77894b27491aab2537274ca98ad1f8298aea080f330c7a98d2f76394765fd7c11678c2e2802e3c81e72bea51cab011d3a6f4adc58907cde2bc596d5693d88d196bedb886d6e88d836729ea49f297364b680930e930f81277600a964d555af40e498ae572bb63b341a0b505690a4b4a692e9fb6b6503a0eff8bea976af9e97483f511e5a6e1970482d376bc72f6130d7153fe74faff85fa3abfacca582380323fc1a44c323f555df9b727705a4154b473e86972992938c1c821fe8bac8a05f7c976363d77d1f418b8e727190482bd182536467eefb4b8fb81fa1c525c21840ceb3c8af6bd9fbf982ab3ff8a4121bdf24cddd634f863df04bc4134688de8c4b6492dfd0a79930f9555348f6789043c05fa8214768fcdeaaa2f410ff79f0aa7aae7bee0f08e0786b79df720190bd15b0425280ce4763621cb2332a71ceff090a9e8e11c35c154b35679644365d7b083f01de6effe5d69c6940e68cd5cab4989628c221947e9efc80b90d721442a03ad1ec5321f37b1130f78816fac10a261c4e74b9d6b49a35afb99f1c72b18abf00a80e8bbc3e4b4aa3297067d5f1519aa709c288b5ff30b90a6d919723f62e7c3fa86929dc273c87bb392bd2e57c69d54e4a16cd2670fb3f017076dd2c93c99ed9239bd1bbffc3583d7e418a45fdde40b70436ddb882db07b059e006725084b79f25f08678b1f6a752cb4cd62196e812b434e3d56631df907af2c58172e26ba3141ddeb7b24f796f0d42c4b96e91f13d92d6ac904711e2e0978b7b0972c0dbdcac395f4732586fb687f36a261c3e61dfc79748b9125465917ce4c50772d750a5ec183847a4fb0975b96683b4b4dddc11ec20fdbfef4c80e323c1840e659af25b0b796e36b60e54ef97713f7577518cf8739e1cd9cccfc5895649318272ca0de82600b2ac06d75defa2140eb01f1bebd76d1e52a6cd82da197e10e5f772866e73df53d93391aabd434d6b09137f7799435b407533bc24a4f5494c37bb72f7e43bff00e614c674fe4e54df7c434c0cc13ba79f9c94b13645ca93afb5f65314d6733b599a7f15fce892f1f0620f582fa264a3cdb83e1a3738ae005a0953726812252d0170ee373c46c4d6eedc431fcf47a8f84260fc2574640d611789c372a01b09b8bc296f5dc4d72a8fef9a23acce358a844dc3abd1f7a4534681464803bfdbcd47a1b2304603bb60299467da18ea02036c542f62bea3c49af8fcd5bc6bc04fff1e922721c15ebd4db5bcb24f4455c61eb9d16313cb7b4e0c81a466c1726a95e617efb6e61d4f33225a2f45de34031e085b7c5557386e184d7d7e1768721a52c9ab1c3734add0eb860bfce2b116871333d371df494fe1f74ae0d268da51542c2a7ec0fa16111c5ac60aa8bfe366c8736a20b8f3f872ec35f0e4f0425d14f50078e5ad53241397348ea90f900c7060dbf701017c79aac5c7edf9075de8721f6d39256e2b0a4376def9141582bfe1a21c59da3836c804b396a9c1a7ef6572c3c75a9af7b56a17ea108004d8d759569b53296e20ebec4dcb2c6276e992bf72b6bb96a5d08502e5906a4ffd68f372adc09292e6cde8fc8a82b5215a05208172d40d694f48e0451705a4be4816d42feab71f741450fa49337a4143b1140d0372b31c30140826d5e09ad2996b84c94de19c38b2f33d134713104aadf122a4bb72983f53a048b91e11b126458c8c6fd62ca546b5f6d2435b3b2feb3dee265ac6723bc440bf25b8ee3e0011040d7fa69536b34b1000149fb58a570894a860dc2472249b17a9e8bcb2bcb4edccbe2ae87a45063c3b399858f4a3a2825061159ca32190582d55d6fb4f068e2702a0faac59888c729824c06cd52c367c6a800c0fb76cebf914aa0403da8b234fb9a4697fd5008ea9e7a13c0f63e2267a268cff3dbe722f66d2081635ea38af73a4cf8272d33f669919ac952fc830047ed86011a4b07250421b901b1a803e698ee9ca209e1b6f1c672642cad5d7fefc3363c737fb7021e1a8e2d98c50083a0bbe47ea851bb03719cf52acc8ca8cd91206368a2de40c72f299862f58e6efa17f8c593792c9db467f115de25f5f632d1b0feade18b04b47b4dd6fcbb279615238284fffc1bbeff8d41daaed075702af2e9d85c2002fa2726e61932b6649cdfaef5db85f4734e2f2422325e2a5568db5d7a2699aa9a4e272997ea3f61884be14e3fecfdcea7b70c80d80755b47cd4ada25355e3448668672bfd6f46d54ba3b929265882b95926b6f57dcaa998df5a0ae8de45dacaabd9637948ca27ecb1e2e3c49e094d436cbb9ee2ceece0872752983d55912ddbf60677288f1cb706ec307d94cfd003c31b2cd18ecaf6ee4edd2cf1f03237ff00b71b92e55c2478631235e014eb8ac321acc13ee165d0bc3f1bc7d4b2b16397fe64b223f83b9a55472a7052daa9e618e23e675d63473fc2ddde172ffc31dd8135ef0e3726428f24ad8b4c61b2ed662ee42c60f4edbe8ec555e6ae2dc23cbb93ab902c40646ab545f9798c663dfb65a5b8fc2b9afaf5177da4ab851aa873ff9aba4a42672c402669500d6a3fa87e8df19337614ef1ece97cf583d628a4a5e229390f8390b7467c20770a63ee558edfd741bee658dbd76e9e40f176abd3112e9c8a1e69015d758626ba1052ad2c993635faf432055b33d6259a9cc16226a06b68dcc2df972bf1c839f96de1c18d7b24296d96b1fb248b5f85856b4b8a49b5f9c343ef643729e28356b186206c50a229e386b7688ca6fa8b5f62acd82f2138af6a12040c7729b764897d190138d9a192b579a6fe7e9da6e4ebbec7174e5703b1848a992d372b16cee7d0508f347dd6ceed0a7ca00ca8ca6eea2db974d2dddf648d0b8e2497248197b8dbdcf9d75bdbc3bf12fc873b5fab84bc743b1aba560d4687eb98074256055c8eaf679c4cd05c4c50e7aed35efd23cef76cd80c57b71a111589052ab10bef31e316d471ccf58f33334107a4721024c50c2aab80d8dab71ed79df11a0728fd1f24aba82e71b87d5fd832146fd594103cd717f86daa60f2cb15d2bc0d772c461a2d198eb7f5a7d05bd2d5ec8d98611b3bb4afaa6eee67162d75742828425a85acfbf4f54d7894970fb86f3a99e2c106d58131474cfdf4f271fc51125462787f9f408958118d7f3574da52b82376dc0260bb24a46c8c606153035a5d1b95bbf6411402fb01550b8941ea9e50cc98d036a44b4d37e3a0b5118826d32a19a727b20f1120559929cda2d4ca09a0d644fed945ec3ec4f72cdadcd3612c0777f7205361253127e41d16f020a88fa07c955e0937b30365b0067ed51387c1c11eb722dfbd3d8e87e837834aa1875928051734d9ba129d0579a2ada519d2612ad402bc022010c727acbde8acac56363556065a5d546839b1e26a32938f3e117d6417298b3652cb9aadb1d4341ada6c45703bfdd70980fb96211ca02921649a4ec4372285dc4b98c373f44171c33ac723cc38a08b124a559eb107712f29832ab741a72c5935711998a27de40fe615907e61ed05975ba3ef5d82593a1c79bf56996a0721cc7ad5b3e633a8f28dc6b8a63992ae08f5185edd3da9920b757972eb567c5535afb07aa409cea6083cdae7ccd1fc9eca23ce7c32196b370e303c9450a1a7b72bf3a85d159e48e6b7f38988ec7df31f6f6a984f6e5fa8a1179c61b70d3a2aa721b3aad0d72adcc470cf4c018e57a3cd237fca4acc58aba9ff0f0a1f1377af418070e47a582703f799eb85306946d2ffe1d6c57569f7c514a350baaae3d3f1502a38d0e3283c0583fafdda2c62a3c606e6a30ffd10118be93e0f32a69859ec2725f2872f2e474be1bd308079dfcc2879823fe0600cddfe82c61230e6f740bf072ed132924c010d1c900a26ed3ba09a97a03d53b70a80a63864b50cfccadecb172243eb6d08deb25daedff3096a9c68667a6ea4b8e55d168283731ac8963e4bf72f0c25a62cf6d8c1027fca5d52675772d09cb462267997930531fbd977d110c725a9cc788377bc3b9c69e522024f07326327968f03ff1c681574c8fff8edbed72101c6fa92c5356fa15353b088659d9b8a75f61500a8bc2cdf3717723a76a91723d437b68e7140bcf42aa1815e6b1386f929e1391a24bbf00690a1f1635cf3672e4506150ab7ae7baaa0b506d3c8d888e07bfbdff27b309b5e5fae6ebcd599672d43c1f8f48c845b569b3e119e37452dc95f40038d7e02547bb8075aa34fd1732b14ca3cab6b56aaba6324155172c20aebcd768a2c053dba20e438a7806b11472c8a10bcda4eca71759abed0b541569670db56423a89a05d682fe26c3e6f7397272913c6d9bc9c04112199a705bfc89e165066a7c3dd02ff1daae21d6b24b0d729ce4f02897ad4f818f2ec720d7713c7c896713ba7d6e04030160b700c2b28772b2dbefcdda30ed93b53306730ad459b708ac4dcd36b87478d375b334960f2f7208f5454d8fc2dbb9697057c9968deb266bc839a6bd5d46d6841cbff89e16637205f335957c0b792e638faa7695468f7ed92428cb481c42444359014dea13d03c192e63f4600b9c593150b365c158a6bdca8e2a0917d5619363e82d2f146e8f72592ad37f8bfa127aa5655fdb993e501baeb6fd07b6b751d4fd6ca4ab99d8b47269ee6b615214db65db929d1868bbb2d9d92d2628affb5c9d8a3a9c918ee60672057396e7c02e9514e5d04965727681c8e38372c781a87df4952f8c790e218e694cc847eb9a5b425052cba9e82b5d28c37144b39a6f465d933842da048e414c12beef961689e9df6c81512c9c9761bcb1bae05d0ceee7429453942adaa4d2e37206d3fdc60429422a14a2d2bfabc634d63639985ed8c810dc93751c4db226014cdf4ce6c4bf9565c6ec09699c4e4a2c7c9ec247177b40c4455aef177487a82d7269aa84728d0521c7e85e466d8ba466078eeda27b2045d3e6c2508a07f7ef9d54fbdd5ce20017578327bd74ba59493f626326347cb12963774adfa1e32ca0696f03510f0890a078c9f99d6a79bc82350da310c4afab845177c6878f31a5de5f7226db707e72ef01592c6cb5fb3f109be4e2cd0b11eb4041d58e3b05453ab61e721616bd62a7d7483d90ca71ac9f246bb9286115940ad6c442f846491134b9a4729385905a0809dd26bc09e265ffb5314515acb048f8bad9f87c5d3844a70f6872b4823314f58527d786a367cae850a7ab143eeb6ee4494d1771334d1130704c162bbf65cc529dd378018d5debfa209b888065177867f1654a470827dcc572d068e408e76f68cb55f300ccebb20588f60051691f4a4117450c90d0903388084e421dbe5413b6f544759b353e7b6f49d89d4de99947948224a7a3412ea2a3aa114a225a2f7b4ecff9f27a2495ece97feda23354b32682b816f8a8bfb665f941e472f5773da6fea11b79db2edf0810cf7a671879c1047eed70d66b5562ea05b4ee49b81fce2e9f77bba5b25586bbab1776cea436561f3512e99e4e6ade8b7997ae4cbbfa5b1d13fd2e8512c18177c5e645e551f4b26cb8941e3f8db98c8abb41c8138d7fd8a30b47978c7eb548095d852967dc83627f01e6a6394488a19aa4db695dbfd9831748f6a8489c519c4a22e8a07b97b0c4f61bd708875bc1ed857441822bde0a61c05fd5cec5c4aeb3b856ff1e377ca5f6cd1551f9f1556a90b5d36b3f72e91350eff5a2a2928b633c8318ee832a902ce3397dfd010680b565b47bf6dc7245a9eb262deea04e169ae3b9ff4cfa1ee8eeb59f0ee0b8239bd6ef7539912441ebe1f5214dcb9f8f83eb43d7207f5d57f99a02e96e7b69fa9144fea66047a424f80352abb93db1f587999353f62da8918be857290dc91ad94625c1ce66ea5363aba3c16b03026391059956a5c9ad8827e47948b1fd0a6f27f44bd2e004ad52723460514a28b3c1d7d5ec20aa164c61ed3c33e1484267cd49d8a7cf48bb379d72f3db45b935f30ba8576e3a2616b0797a479f3dc98e8f416d78b6022a9a5b11177c54ee654d456e781f68fed999f0f83a0d1c0c3d6b9a46cfd2c0c7ef67981472ef67b6b0c9963fa6ba720fa360f5ac6824c7771c006456a58c4883c448d15172d3a09958ebcdc0c59c5390524257255ea6e4437fdc1477edb98fac00d7f78109359c5dd0395f0e6f8fd6776cd0db4e772bbe13ecc42e729c065fd39b91971a09b628b20b1967b0640f9195d25b7900c2794a3e107ef3c7e6f945aa741e9906727977e20a9ef01c883e51c460f7639788c834e574130c890c6fed9b1452e46c724f808bcdf0283e3dc783e09fe8bec3e6d2ea065f3ceb783bae1a5dca5c9fdd44df2fcc51b2932907c2c85865a87c80dbdc0cc35b17e0338c04a6e313505a3b72cf80857828fbb0a6be38ce7f6bb88f44e117eabee21f2881e1aa98dce883e658ece2b2c11b6ec66fe9615354427622a4273e158b79a0d4b27a95789b47b8d972a01c08e2b026fa17ac601491a95ff3810c011a393208154f3dcbd237ef0ad849df28368581b905b662a7cae6c228977e3c29a216c165e27f633b9b198db2075aab7f8a37d31f46cd0c9f8d63f3d7dc22a8cd21b432f9ea18a93c713788703e7258220fde516f00e8643a7c52b6ff031ec8a71c12000ff833c21d0958be7716536391c2d7a4acb0492457ea45d824dcad9437c5f4f80a74268b15a6b4d785a1727cb63be9a1871ba3d47b0144741f306f9eb8bb31f5ddb96bbfce8f853f31073d54abc4fb1558449fea32da09a25126b7ba96fa9cf4d8b15329b4d0768b91b872844b6c2528e4129de80901228f461b2709df0f2b724595c7e5e577314994db72851e4fbe5ce6a53b95e04ef8924d300473633ce34619c2b237aa308333ccf0726a948567e7443353d1ce95db29bc070e3f0f93fbac4318ed07d1e413e3261f72a6d2884bd398e44a007f36a4a03f51dd74f451838c2850114365ea0acd2378726776384375a2e0843fc77379cbe43cf6b7fb4f63d6fb4da2a6a72f3c53f30772611202e21e7f04c1b38bd14a53c034eebdef131bea4e12e01fadbb0e7c8dc172118ad20a7f520ca057d0c64a791b3bf41904d81bd3412a2008477758e2cee65fe17f1e35b25a4a93a42f83dea73a45ed7605deb3305476c2cdb07b3a31a7ba72a739f88986b545941002619f595ba00d4d5407c44d60f10e1d293c95dedb5172d94431f99f4d28a8c6c5de00a1c85087cfd79bdbf31684f1e12d9616cb4c1272ffbab507043238e9f5d5c40d9e7f4b2241370d2607c6c903877b08b0c3253f721031d432bd26ffeab495bb53f57555b8d3a9d3ae958442a0992396206408057279587cc41fa068094e31f826c481674038b16e79f5567664b96581ea4db10b72c8a54ca4eb608d41e88849cc220f61e443e4aa63d7d49d185707b42c84f190727e501e2f9c88537cd9c3e86a4a95ccb93cb423c723e8432527c147ffe2c12272c89671dadabe22c43b202a5c61dabec6780916e4bbac894e22711ced3309de723856c7836a8ac3ef2c295afddb7668412144529e3b3d1b62015c0cb1ffb73947f7c1245daad2d0ba6d40f8c6158dc3084eef69ce8cbcb6290152b26e3c8dcf72ecf7dcf5ad4527d15950138c90e81b1c6a661dfb917fbc221d26d77c6966454bbac3396ac5d7376e5ffc2452b461e3cf949884ca326d93f6dd9b395e73813972795ef7483cdace8492ac21eb94d2d19c4e8645c44c9f87ce0a4d3b3faf9aac723cce3c977c1a6b4added4a363759af48435581f782ad575e556100b972ff567205444532e9ac72b07985efd263248eacad136a59604b763e0ed42ccfa6b1d2729d88c5d36912b61a968e807bc7dfdef2e8daa589668faa33412fc9790f52f5160e7dea43480edbd84378dbc6565d3f904f437dae7c6d5cf971a17bf1cdd4e272f718882ae6f7a13dd5d302bb7542ac1fe101a59a7d9261fe85baf6d510301e36e4497e95cfaa4e3a1ca47425059cf37b9c93740f954d1bd64ff3d09b39d77b02048455376548cc2c8ab2d29d67d225d899f156a31365b512d2e86481a98d6172483902d2816e3811a94855ea6fa57d383427829f7f8c12eeba29e1734fa6e1724d599d69b7bf43e2f201d707a2e4a4ad56cd3c43f7ca2f05ce52fa50f88bf7725e353a911bdd567ea34c469430fbb511564552f3dcd5178e73eb68888f4c7b721a5419ebb620948717454fc148ebe132ca21ed46eb3690040c725f01d8707007a927255ebe6dad404b3db43c74855d1b2e13ad8aae96adab9997d6807341c4720f66d6a2769a1ddcf644e574129d19bdf8587aed299468ae7f0554e9fffbe47221c80ef63342fcd535dc914f050150c15265fc27aa067b266017762d471be072e9cba2c17a111de284bfd8dd8a2afd5457acfaf0d76ce1888e5c69788a106672588aa03f6962102a9d29a43edfccb9c271125662d4742536071b32e8fc7c2c64022c5a1f0d683b766b88389a0e070d68b8b8d34d01a78822e3de8a120cde850d04fe630e75ae8839201309667ef39754c5471f9c9f47658ec5a640eecb55b47212b9f84f638d6aff704544e5ca444f8af6bbc92d55e2b3751972884d6b7cd63d92fba3ec5c20041b11d7801939f06316aebe3ad96d9b23d08164fc34a2cf64689ae1bae16e0470a2af2bdb555c3a23b0fcda029c8920ddd0d2ff0559d7449772f2c03c787dc3266fc6d824a8d322ee1fd0bc1d76853bf71204a061d7684d6323832cda7985923ab538f28275c11bbe4f9e789feb0da988c86b11e7b5979e5b61bdf59cbe5a0ffbfedb616a9e5ad1dc20422f545de381cf68e9b391272fbcbb5ae0a2ee7bbf42e8de523659d07d62c17e174730c5626dbd42f5cab37157d9df0eae4c0eb778521b1320b5c8b8bfc1be5c38546c3a69c2999e400e91887e57f2728b72edf3da784cbef457a6393b3b0dac3f75d97934fce0fb56f07980f32d1872634829896ab6065480cfaded6864d3a26426a446e670376875bc7fb85658cd56ab71dc886fd8ffe20ec6bb247f9f0ca42305d1a0f9d17b7e7ba0c9ab03af99169d09236e49bafcc6bde910d4755bc6938ad6bb5fe545bb805bdc125624ec396549276d469917afad5a1fd3ee1d90929fdc6037bb2f9053848b5867dc0be12f7286bffc820b1054af7ffbe0f0c482a27fde2fa64b8dc68b28a26cd1daad4b682f075e10b8dc04f100616eb1a0b63cc9a18b8b53478dc3decf193a2a7194f5be6cb1626673275e4625f91f5a850e90b07c2a947e3b7cdce7a37b916188bbc40d720fcb49a8888f371e8bdef56ebcd1e06ae805b74ce6cafe745b384c9d2705f96f1d71fe8027a4f1f57fdab92fc48af89c22256a9481ab55a346082693d110ba3d2ee3ddbc03abed256351ebb813f0883505c78c74d3fc91fa3943e4044a12bd6f336f9ae119331446dfdb2f6dbc47088c0920f384846460e445fd49353d17694f504abd1aa3eb0d29dd89337c84290c08b1ecd8d58c7fcfda3cc89d9b04efde72bc87bbfa13d7aa9e13fdc64305185e2e91bb36c0fc4ef7014dce04ec02a37172c3a1cdc71fbf99bbfb201bac178f074a90ed8cce8b402f521ffdc27ec7375b02120241ec904c3957548cb8aa8055e0c88425fde869a1c32d0d58b21d7ee7c5721c3481433153b9287181d1307f3deb6d8b387ba93d943a5d9e742547cc1dba72647c6d3cac71d81de9cb01ddee082f177bc9a86062a75f986f7bf18c3ac5e87295d1adf14afd1df5cc565afd26dcda517ea2cc45e304ccf0568ff3fd17fa037228ef45f99d5328e1767ee015fef5b236d8da9e60572e7b16cfb108e766983f223fd42c2c4e8f47487cf091330e16320e5c0c17c378a0b6aac3d886a6c8c0d2440b3b75508602181cfb903641c22641c001e79082633872fa6499274943a6d00c08974ddcb79465973b1346cc4c39b41a93093e262b3055770366caef165eea6924eb1cfa4f9600bd968102095d549422f3f27a9588b1b683d482d50a759e8f2989bc824487a8520785efd01abb9dd39f97271653452ce69d7741c5e9519a374fa1698c3ed66640976fc7283388ea82bb3fe383d3816da3fd266b7d3d4edf4c72502d3b99b793e14bb2817b5278cd8fb14c3901b455262c84ff09bc5bbc211f7203a3e3610826ea3ad55281f78eb62a167d282497dd73e9b095e969489ee7a872503cb2e02fc69acc985921897f068e59cb1e288b009e187d646df3c7cac845725e395708a555bec80e328d334b2617582e7e12994a79214dfa364321b0972f66b7a08f43040fa47852b479fd26b68d88bbe4b3f6768035a72cfdb566177e004867360324b63fe2d7e7bc358b66ea86e3fb93f02750ec0398de8943c2c2b187350b0ff7c8339bf1cecac3ba386ddf86fe435ca182707f2c601e860340d7d7214c4c4bc95ec263bf5bb2cc1b5cc888e6a8e8bbd4cedb69b356c08857fdce27aa72c43d2e1c22a9826c2a64e72c3d9c568a8979562774913637169d76b8d4092572b8b4f69dc9e4ae04074c33d601f34d2f684b9de59f1cb95836d09ced92c7663cca3a25162a336211c90c049fcc3c1fda1f3d60ad930f0fcf265f5d9c3876d272451cb22962c52fe3485693110e265e6c412c625042a9ccbd04cfe222f7fb7772429086645f44fb3de1dc18c07be64a2d12787bd4cfeb5b550a954f254e8999496d97d6e58030a4b8c841fb351a3570c1036eb881ce7999b701d3331cd270ee728ae285eed97e65044a5a25b1b36163a8a4370de728b1839e9cf47a3ca2a95d16e926b65710a4280924c4557ab41d788607ab8e9758e967422eb84a72ca89750ec94b17bab016dfb2e4da42c989d2a2544eb30c37a592e193b3e93085bb13e348e6fc65bf8a2b58d40a51aa8927f67ef95b391b9ed2cc0627838eaa97f8dc0c504748b66396f1c19d008864961e545b672996dd1701c2cc5bed74061512c59327f04ffc6ec4b406b6610e6e738d7d41b27020334fd6e548db2e6112cc31d9fc39dadcae119db69be2dce0bb778c24ee323e69c14a0dc24e37e94ad842496b39725987b9bd7a6cdaa66e6d65d6014f4f1c210a7abb5539d87418048770ff6df72f2964331bb16588a8bd09a9fc530c387bbf0cfd1b0e3104c902d50c89648762723da3801a5bd683ae002d7fcefd58142e3bf20f6a91d5f11f19bcf6722cb403721d64f91aecccd954f0fceab3b3351359731d0f65ec3f48bda8735d4a734ade7209aa4004f93f870828ba438b7d39cbcc70ee4c453c0c277f65049248f537c21d08afbcb363ade8b9815f764f00692e201fbc473a9c8cedb0c7622639a079824105b7a34233a1e2bc758aadae45e7eb9aed9c3f31357db1663bbd0f4d9d4d344e575dc25e0aec0598ec56bf8848d2dfd79834f93a06e86f5fa675afb01999c14a45f7579121a77e52f67a12fd0bbcb381df951142be20d6e4c3787bf223f7dd0f6b4a3898450318a2c399086a579b2c8609ee687c8d5de1d771939aceb2199f3fa2510956b3b67c2f519480ac1e6dea67a40982fd734850293fa8f62559256a17d083e61938140e8b19452b3d110fea4a60b56e93cdbc96be3a65bb3b3f345f611f67688cb49c5e23ec71ee93bfbb163ea055e03a8c1662efd56fd3515428cd6a47c8b597d4f26d7af1ca6878fe4d1873c700e284cf8ff4b36329ebba1184470f6a70c1f3f78f3ea74640d59d3da8c51b00a3ec162c5e927ee7da4f29a8f8587242afc9c6f4e107c5fc6bdbad2a50391ef36a76add1347cf57e84f69316a130615a9d517de7200e7990314f6f7c12027914ab82207237a0a911429be04e974826099119ecee555a0ee94653d78ddf0175efc4f245390cef5a08c4f2c44b06d95e3a49d13ed13c5949aba5acc760f53e7d17bfebfaa3ede5d288044309a8c2d872a273e346e5d6e5a68f0f218e1324f5fdf76e3b152e099c402b262110833e427222f41a0dfda68e661d7fe01a79284246c4b4cadde804be768ecef98f8ed63e72f8237e1c71b0b6283f8fb9e3fb341db3594749735ce9f386acbd02eea3153e727be01465a96ad2393dd381cb39b3107ce18266af6c6cd850be6a05b90feeef3d74f0fa003e4e4ca891f245ddb9cc4f0109f1a09e3edb18c7e1ef4b8ad0d0db69ab09839320af9424e7850a721d1343ff04d174e8981f13d62f1fdc0286252d56e64afcbe50cae1d458e624ed63f3ee3d80a1cbc9aa8a122a2aa1c27a956b11727b82630b681403a8c5edd4f94ba62cb919b65af11dbb986034777dc5d1313e72960bb493bbeb64a1a048f2846113c3a202fdf11d63307eb35297fc8a2bdd8910e30637552d95e28a7075a2ec277bc828defb130ec3e44badabbdedcb7e55e8724c584a1519793528232d0c697c073e564f68cc03a692f26f503e080bbd008b7222a3cb63b5b0d7cdd117f83234acf76ffe8615e67ae60cae21d5c086c83f6d4c60fdba121e3c06b5c5277bf0cc703f8da4fd5c821cddfd8b4c86829bf0b83a1d3b57eeaad7e3db8180d6f1f126adbcea3bce2e4c3c952ade30bf4815d298e10ec577c4f274c91dbd72a0220dc185331a5e95cf9f61764c6576eee0fff18d4c72d45636b19a9ef204be362a72494071dc0929fa20d2706fe2c775771aa058cc5dd46c0b5ba34b973f27fe0695981491680abaeaf0d20c4a1b1fe132e7485d0c00ff6a186758f6d21a3f849d6a817c4453db22b0e36f3420825be13dff8056971764595fe572696b8dc033a0eca944d3d0daf6232ed73f2429180046ddcdc8e872260a3bb0f28786ab578a247e0c89bb585e18346b14849b94e833dfbeceb9cf7214b246b330d7c34c53cbf811c6a890f28a6262f30c896fc559ed007454019137173a46e75db1c91d799597f19fd4f508c66db1db94024f221be8cacb567ef5618b4d5bb03fc63040d9b82f04da5e121617f14a07b0ba34d67b6df130c5134c23df05ee6582e37efadf1bc19938205c172a6bf9166e504ff9127419aefe08f77285bf56fa6aefd6848370949a07c1a0c37517c1b265890b964e9f90783deded721f2cc96eff7333bdd0096d86a93bfc3a8f1e3d0bca44f81004a2edcbad657872d4f6494eb7b30432fd941a332fd6dee4e53128b55f7b5545afd5a38f5e49a972ba43defab3885c9287d0c42dde5736d91b1637dcab8b3a7641805b0f0fc5e52c03878cf78d777ffcca1005e08a20a562bc264599f9a55dcee520d6139e0fb572f960e0fe3ba32c884293b29a946a39cbaf1e9aff3a478e8af2a7434bcf707672368a7d0dba3880af2f58b4b49c2ee43742cb1ba183a651974e3f3eebdc587672eeff6b7c584c779e6f55a32131855ab63e2201ac91bf63396c17f5951e6b9e728980b856e0591e875497b246244cd449859c30bc934783ec217151e7a6ebb17215d4f07592ef8bc9d905a126d1f6d0bf69a04c7b2aae74294868b8beb56b5b725a002f55f11176de2a0d7119a12c70c64038a551c33b3d3099647c292aa73772345f7b7971812fafeddbcfd6ffdd3b58d2f9fd863e4280540bcf61589d80c97211bdec05dccd5d685dff584eb72aa66ecbe016148c26eea6c904ac6d1f709c72e78fe60e31269c1cc116f55ed6fd43914fcf313f5bde31e796476e4dadda73727923f95b22961335e2720d62d12f03b9f6e41299584f751ce8cf70d89a57af520e9b604a79ef12d59a817ea82e0708b7feae36e8406ebf4dc39e52c72e680c7291aec798efa768640abb5d113101918bfa3bd5f9a065caea987ac69f9dbd1d72f5294f8a5eaa8edf3c039adf7da1bdb7bc9f93557bf32f755f085c7d47b68072ad79fc0321d0c15b66a98aca17ef77f7d9a64bd3fbe9a1fe350a6c4dc2cc8b721e0635fe37117d1920fdc4339005c2024ff071fdc13926d5ad8eb56f4e1d6e24782e5481723ee07e2b91866c3dcfa97c23e4c71320c98ffa62897a9f6bebfe7205c83612576fab40cfbd1b3596567b0e9b791a5d601e094893a1cfc76e8cbe72c5751572689564cef6ebfb8e4007f1ee4edb552bbb563e354abb484f544d063b5869cfae612e2c08a596b84ea7e4958ba36178d44065275a43416cd5a207f1723663c69345180c5cbc501c8fd3124911520e4028ec80a9524477a80889814c72c56c675b071754f76d1322fe59d973fe017d398602e46d0bf10a0c060d743a705704a31ffa33638e617de8db1dd0d942a70694b217a7f1fa075acb975fd8c4728da23018bd52e30b9980dc4b86a3338ea279b1485c2bd75dc0619507e73b0472e4686f0a3939fe04a9f115cb3e0f3e2e2cd4b588e3688e424540bd966ff087723b759a322c2183423c154ad57e991287f5db3c558ad2280adf4bcfd13866ca720cb48813958f94eb1569b6790d2cc4df2809884cdd5f4e721c12a8417f3d56728c7a012d159efedae0b523b3c26d05a86793c028effb987ecef16da671659972b7f2110da4c978b67bfad17dfca7191f13c87354c7f3b3c18020d85da86971722e015d056861352fc7075a3344ecf768335a0e3cfa23c1650f07fe5693cc961b8ad66570a85d82134162e5e697c3172cd4d75ab1df23d99c5b58fa723a4979728092574d4dfdba156389c842a68f06378a0ddb35180b40f296c391b725da067237cb9d41d27be4cd7fc378f6af1c2e093ca6cf90e32d734750e5993e547d647205c5306351cf416ad1e50e1a8b4a75ea5f4e6ae9251e0a2310ea39df3df68872b8409fc6634537fbeff75ea14391fcaae0a45a35fbef5d734f7dc87218984b721442d80e703478ea20b22c47470a1694c15111634728501c36d3ab268cbcd072c4ec2d3bd17ac9a95c4fd226278dcb1ad7cfb367cfc2035906d574bfd7aa7540c20eb510cb348f9f7d29574a8ca6a8f15e7f68d01b8854c76eee04b7b2e59772b96dc32fa1812486bbf20c8fcc09f48ab682fe5e5baf7dcb7c60bc76152a3d1de02750bdb7c000f2a8c4cf928f83fd47d88941f1b644c2136cbec11f1bb6154136b103d81d0f2ebc28cb910c84e728a2f07af4645844a982ca75c66ab0cf62728ce03207dca3939e47cf3b63de1185a8d74ea7bcf8a56db2c754ff0e0135d772184ca8f9f3fb06f14054dbffae07c46b36d3df81a716d37b7ee2a3d6c7d96772f96663e1a594606630140ef12c2b5f46a2cc0b1b1c18af40ab298018507cc2724714545952ecf8c7bc887ae7b58b1f20be54c32365f2f618f3609768d121f13c3a9bdd35bcd156513b2933174b68405f24bc8eeefa30bff22a9b226715646d7262f78535daff2e525adb3d3a81cbc52e77a714bd4a82c083edabacb492f1e772cedb5434c370dfea21d0b83ead853d69b8ec0f7ab429f5e29b8bee6fd33a713a306ac87d3fee5e0e01ed35fa2c736e12f6b1c232b0b10f2a677748e37329ef17199db212ca95929a28afcf0a03c350434630be06f7f06e41d31e7b08a8ca1c4dd3053a13641bb46ce1d8d0254b3556500e72f45713987dfec343385e4db63009a43f537c5c399087d924248b0405461dad0a1b2dbf516203837f128e729cd37206b6159b6768559771643b5078e31e01c5980037f37d06880c7c52dfb7996372cf246e42f08d1fe493267a75838a9b65840eab9f3fa7f28b2dea37784bbca2722cbf1da802aacff53a79a75f361722bb8ccf5a6053cad5d7e051432acfd41e1ff8b4262bd378052f0b4845904a496c4b1fa75ac03a62878524e8a0ecb927fa728a1ae87d3538465c2ab75a794db60a3a4787681484f39f634fdcf340151c24723475b740764bf1865bc98c58928e4680fd94b480b9d698cbe893aab70c05b43fc7a354c516890a783696ecc63b1d7bfd198f27e97d96c7131874529a0e69643f804f975a5238435fa49582a095a3128d421b7b0b55754f6c0a5265a4bf5bc672799c78f2640548cc389e8bbb18209f84026a091cb27758fc5ee87418ef9a3f7270d6ca1bc7a2eb45d574ddc2cb11f4b6aafcaa08dffee861293b4048de99265cbf0253bb41c865b2e6127370bc115ea41a7710bc85cd868c0ad4155cbb07ba185fccbfc8de2413b2385f32d0f338c11bca4187e6cc53eb4b545abb6af60f387275463e618e6781b129e6a4f43c58bf57b481ad3bcbbc8ffe9712c2ec7d9035720fec1f532007e8a6722e01f7a7500c9c00b14bc01bbed2351c394eedc5a26272da4e9446a7b2b46651480d857083d1ce5f3499d5775bd40bd120cf676d960f72b0bb6c0de70d94efdd98257bf04f0fe86760a36431d3e17a7bc4297c4bb0d372fe57ad5dd21fb3fb664e0b80daae81f9440b4d9dd105c0667974430fb8a6c072dddd514d80e72083f2f749a07b781cc08304902c73142a3995ddd0f5bf495b267ac7b402ae6ede08edcd983b2086901e8ae3d88513db5bd0123f5de98458d55e2150f41e629a105582e7c0b33629cfb4eb64afc4b6120e92b609a9732018eb72ecc4ad49e00352c7f48f8af6a4b64729707b39d11676a30f11bd17fa1d26b45e96be71fecd9c2686bf0bca0f6a0cc9030d3d7974d5b30b64b8a13fde6805b37283deb81eb838d354fb2673ebfed17cf464291046519b668d4fb56d24a2eb911c8f2bfbdf2bf6a5cd88570147e3f51d0dd29b167c3ec48b77ea48a3711dc307670d7022f9e2b458c292df3f7247ffede8131b34f87ff0eb6078f4a5ece04c3f72be700cc76904bdbe4f7ab45c28eb77ab15c565180812c28b93e084599e239c4f93887abdd16058e971ae60f0ff9232834b406ccdcab146a34669439d6c096b7234093bb2b74b22d68e73e8c386647c89b51edb53a5c4dcb341cbe746209d8672ed1d0d92207716386b77477a9f15a20c7c15de2acd3d27c277f6b39f92463a72c515468b2d2c5400420d36247ebde9b82c812daa873a9a5dae67c5531a50d23a8be671bfdec8773d2ed3061e498d290f54bd031a865355820317d74a86ed5372280d84cd9646ca833b28272a023c6e074c31b019279195695a94baa948197972b44d722168f10127b3d73d2d845e74e4ed90db6c717613dea499d332ad9db0689d2b6fc2e9a0b1d5b4c68f22d9d2c31607c34ab6cb6c4aab4dd51718f2eac15dcd9d66d66bb1a0d822cb4890c9f841f390dafdddee1fe45d502fb5bfd3b10372e9c0f59abd8b6014935ae88024ca5b10b11740fcfb6c418d06710d199a898a515709555cae995f511c0c3a959d483a1c231494ffb2dca4fc80b4e89e7cb84529538ee33bc5eb1fb9cfa43d2fb011110fc7e53f61973f2cc97edd471ffe82f47261f0ab8d226b54685c82af5d07ef2ab20c0451cd625cd2a2a0a58acfe17f317202f3f4f26b858cf859998482c094ce0addc392cc148a8ee9f0d4d9454652e971cf547f736c8280559dd3ed2d922c4cb01be35edca07f7d4a5bdfe4ad948b830699c542e620104227180bb0d7cb1cff08854c3e9d0e3760e8ae3e546751a10b304535d371c14afce2721d31a3083ba9f025d4a35c901499e7226de05e334b933f60e600403346728064af1cfd1d5e1bb6c9d2ffbe0230093b3ff8f29d972a090a96e7033c238386fb468a86ef84004c889f27c176ce20ef4558ce5371b0674539de9fdd1a36365d605f47a3f9ae5f3441b8468998b1814e9530cd37cfad5a4447994877d105dce8362fbf5d9fbfb3f35b93f6b747e357fe8bb200121ceb2a43238a027b89aa9ceb1b01d23428c74dddf292361dbeeb118a000fbde52ab422457203ec78e372b061679b6edaa056deaaddb5c445e82baeb172d16249f1fbc366610d86ad6fff9768e5539527b83c17b60dad70151c3c49f56225af9ce3f68f2c5d2ad29ca6bd72244716eae2050c03b3dcd8c0cf8503224f14c7f0d778a26022729acf03de191f49afcbae05fd51179a7a47bbd97f07ea2da99a83c820831680082509f80f95d11b68e90f560494b529219fb45d4aad34637160b785746711217232fe7d655200c4d46b12585289511c4a0304cd23904cd5efa8d55a5a75d557728b7084482b63594bbf4da21d26653af86ed33225fc7076b41cb0482edd89b16caa70ed234fa9feb1d3a0ee00cd9f037b62a26c71e42d351ea30db19ff5b2c90d2d1ac93da961cd2491cbb695ae3dde9b5d14d2cb6f4945634095b7548446c745adb101b2fd0b62998c84316e903145c29853c7f44b0222cd3a740f72aaef5c0e32820d5f74c77c2775050889cf82ec567bf328fe2e544bb8d35a1bef3e139072dcea0476b8a9151cb93bed29eec5a2c72998044a61ac2fd79f117d5336ae7172ed9a3e2f416058894cd771aa8199c5f6ba5e1d945ad48949998f4625b1e7e25dae10cfdcece247dcf432938a45c018d6e0ca7d99e84d6205993cb113b05e3b31ba0c4fd32754d624a412b43872bd14a5274c2dd7948460a39ae160f7dae73a7200a97f65824d92b6b1f2e219ff74f3c1945d0249a725e077cbbd8fb9c5d51a723d15901235f10877ecb4dd7539c83befad86519b79525936e3b07af0c800a772db1fb021f0971c298a30c7efa664a80e0c058a4e990239999aa0dd4522996172a9f55b450cca3b43c581bd8eb86d2caa43fd51ad6bab43fac3211ce1e6070a727439f4ebdc1b24ff377c5407e865899fe0ce9909a2020c915ae51fd0b970ee4f9c2e2052cbbe5ac07e0d90df27fc67ef0bdb49bc0bed00d4370e963690cf19727d72b14319aa879d542033c1cb19e819e9a70c8eb6bb37fb734162a1f7ba0b385281b198253a7d1883cebf8a9281ba7d4e51545832cfaaefdc4d15a13a265d71796e66bd869b9b3116fa7adbd7c8f4b3ee12448792fdd2a66b63a70c4bf73936036b301e4bc36745770d13bc94d520af7d34f304a34ff1cb42fe822ecbdeba469bdc2b531866259ea57d7c78a38add22b696f62e2234028656231de90b8fbc724d36be524c3efef29a7cf9c6b10ec0c9ed9ecb110192b8de2f5019e0d3212b5f51d9ffff4befc842d71ed9bd70fddf2bf0d1b16e2d28ab12775088fb23022372a3ccce7efa3eb57843afb5636c420ebbfa35268769f0904120aa24a81c9e2872302a22890b95182d365bdf9dcddb191700dc42ffc66903718be008a50365d234eee34f6fb234e9a2d8ee5d961e794cab31169e02eb15849331c07e2a1faca37289fc0181271941bb7d265f450e43054d91984cd650f57f41c1d3944b41370b721f2cc30da61f57fc0d346ce75f663a359133c914ad1c7ed79918f6acefad8472c7f31a0162a38eaa846d6dc318bd97381fd6626147cf26c00aaf9ba29202c971401fe44de698deac47c4b589ed614a94d6d77e5fc5b4a61a9c13cb6c4922b372aefd3914a8522076546fcc348176ba8cef9f491851c9d0f9d3d233331000d2721d44adbbf1a1c8ecfe977c0eb48a06cb6852ad32b623194c9e324f0c749c587267529faef65694f2befc06771274066dc61aba3a0b2ab54e7c7b10fd76753f2f4d6a61030dd48e4578e042c6160fc1ff5c9e11aa61ec55c7662eb1b7269e56725be0742c083ea8e6615bafcd4c69a0b6a63c3733c57216c965599399a9e7c5722c07845a6b1eb73c85626030168260adb22928a2db2c539e0678691ac9ec7f1ae25c7cded695478aa3d6eafcc907b98ea808519ab33063c5264b43fa9011b15c348cdcad7fd16ccaee274286e72b77463c1bfbe26e411b18ae3340f18f79d872c6e4f8d42c1d947ccddcf58f20d4969f8503ef8872dde67ac790aa3cc5beec72a86a4dd9234263bb4722430418a641b42ef3c86d4490b89f6c1a536bbb8e047299dc31fe3096250f0b6cc75ba259cec9eed95738ff56876cb6ae8f457e5a1c1f86977f3e0ee8f784ca8fc5db9bb7adaedd79309aada3b03b442eb46624b7d07205be4bca3903726cec40495d4ec63909f13cc6018bf639405c9cdb838d1bcf33c98178570fdc8f647a12ea3c8197d8360f300436fc6b27576321ac4b7ad9dc72bb1fd5bbc287135c6b28bc6c85ed3c8961d4280a787beeca8a00cb46bc12c40279a39db767a4ba85767138a30c2347e69d02f1c0ee9e8596f4475691fc4cbd7243283bf2eabcc142876141a9439218ce865fc452f971d600810fc03e4890ad2f11257a7727e30c259e09bb3e015066c2ff96155a2c140c96fab390a3a8e32e7297aca83de5c77f2baa2b115e65206623cc95112e5438e3edc945d3fb7d8f355f887a96aff998857d7c6bad27c18c10eab0b3165f53d574ab6e60f5562be20a72b821cdfeafd9aa0ad3a88dfb83f31a2680ae8a1ac2a1ddd96eb6d736c2e1917273e6d0d37e54eaa18691c47b2d08c37797ba56c6bec4ad9044f2c7037514dc7261abce09a8de940964de9dd09a85182b382e9fcd76da14b26773545ecdd4eb03ad4f6b822bb58ffb7f2f675205d7ac8ff7cf84b031b5afb11b30334b84297d729e6481a2d3eb5472bc193f17dd060361c3e9aa92b1e7b6e8cf638f7d956cac7295c46cbe5f01a2b774f754450256e515a4584c1d68ef4c8fb1aca404b3aa34720ab3c083537f07c85d0d9c4d50a46b7ee650dde4e7e3241a265829f1d7a8f87288f47b7e4acdf86edd7acde3e6731be2b67137e1ff4065054353add3da0d664a90a886522537a60e1fd44124fda00aec406ce4135876348257c0b1f50d2a2b4281d2fc346af8eb9412b4cd89a137498d707a76d46a55508ffe29c1e0bc69ff3fd9103f63542ebfd520215bd1d15d3d607c4692b17a9cb92f93c8cf3a229f4972a3ba9b2895c8fec2f10a91faadeb1903393744fa1c9a43fd4eebb9691527f3380ca401de951664a53b9e6bd6f71e1feba72e88604e361070ca3cff84e66464543396cd23f633b8507da6bd95dfd1472e226a5c04b98201c9543c331ed52a6c72dc3dd33fb04f2f608252a7c5baeaaeb3ac919b065f2ee41bdc43875f379165621599b3d2cf5d46d66695c8b2d9126a71350b9f708a773a9a3c74750e771eeb72f0016be323967e5852411f4f8a1b429c181b64a2d012b55decf6ef906c378e7251ca91cc89a8a28abb14ca2fdc849d021440883c76d945b6fd439b99e044ef3f3e035ebe58a4d6f1a53c1dc7777d71070c951ccee0d50e69e94c98e1685b276f23f16db33cad81d883c2a4a3dce42c40a0d7d70385e2d152e0ec72b38c754372cfce151e7518ee4848590a7dd8d8fd312edaec798f8184c6a5b6ba1d5f99f42bf77ce80624ea2f39f585ad204a24e8f883c7f73b2d34206fb650434e4b2a8772d97e725fa58fe3a6ec033783e23f34bf64a6690099636995f8cccefb664066725b7f9b60aa531e261812863719e3008e16e8105adbf1acc307e60abf44d9b2727f3f1dd34f2cf63cf4c3f749f0744952cf5d7e5bde2192ab38b426e43518e62d4d823ff0df340fe47219df50023f4c635bef5e5b39c287ebbbcfca817929885fefd574646b99d4390549558181d101fe74cdc9261f41ca78f2b6c71d5aa0a91141a7d2dd7f31d47af1cfef71715270fb6d03bf33b2ade116bee4f7d6d5e7f0727025a5937e26823a4f570bfdfe0ede43b58c4fa38d69aaae4dcdd804677d957258ca6b44596731b403a3a0755c0bdc18c8fdedc75ef254c71b1c70c6e018b072c5e5106ab9a7a44acdd488c6866af715697592d76fac19b8ef3035f29300c051738c61b043c1f24c85726b23ebc5724ffbe554614057311322a69f8082c0127211152ef129aa70a1fdbdf681baf7701d3e9ce7f2cb6585081ed2b6b6b3821a72e867ff32648c1941b0048fd7f51ba2a75e8dc81a122ec3ce918e8a7e3c07ce7255e165fcc8c261d1e115c8e6551aadbf2883d85cb3027bb23c29afbe76ac8a72fc1c71f4220433f780b08ea0e933b81f06fa41e74324c55673786a19f58cb3099c2075575cf2f119d8cb02a43988eeae8910a4573c10c8386512e7c3e194e6226c5bf09cc8449931c14daf1d1686a560670a81a2c091b4f908b585b45b57a2552f9c9ae6e57d668094d4ca645d72eb9b41a6dc98aa5de8cdbce69312a0f23b0a72ba9c9f463f35a025f5a2f81623d3cb41dc9f4c4a881f49158dd776a2de3662ae8a464d9bed238962d8d3b97e8df61cf433ab97fded3c717817b1e08d199f72270e41f5f52d43ed2aa7928cf0e03f14dc3d40502d66481d8904ba65b0862516fdaf3cc47ead62d364b563127724ab301ae185728cb98e1b36c6528edd3f1d72a6a02e7238927e9b89d0c3a22fb9d2c8e11425579e427ebb9b900f31af724a35d899fdbb3e8766b297c6c81928bd0b3a6b3a84f9b3f2af47ceab3f076206fa5718811508b025a70deb27b3fa9993187ab40330eacddca75e8d5ed415cbfcd06a54998d413724e9621fe4d76d6a18b60c7f783af33dd1e9b95548ed0540ef38726989b198728ef82226c6f859ce1a5c78c311d508eedae41e74d58bf94d33ed684130c0e203ae440becfa6d7b9b03f11b7cec6ef818fb903823689894dd256872eb4ac996d8226e8e4b0e831ee193806a07621f42fdefcd7a6f0a69926d85fb62cb73e219c2653587ee7559622efa70cfa9dd4f0e206b3473072cc2605b9c1572aabb613320163cc30c561718015eb95d911c0b48dc69ec811d24c4585413913d2b38b17ff4fe71457e1064f3f9b792ce579f0e43bcdd7a75b4de1ddab90ca32da07ef2ba4b7c99809bd590f1612067e40b98187cf1b2451a3e3ad8092ed1082397abceb6f69e25709cbba0308fd1868c98ff1dc8666f625e1778498c078392277ec13109a2bd967a61fe0f912dae683a30f1d780d58c9159a94b3b6a72363e726fc1b2233801ab9ec1a2d22f1a19d59dc5d3a22deecb72abe5b28272f6c07e7217d53c22057fde3e9b54ce3d639521a5ad46905628fd3428f7fab62a0afb20727fe5ce428bbc83222e05c533f56148946604310645629f764f66dd2822a33d72a4cb686810ca849e07a3602b24d17a27873192cde543cb6990ddf56d4579ce72787b9158f3b988918a1dfcb70d6b435c45a346012006dcb6c46ca52f55adc859c169287249dea6f3935aa99a2b75dfa5cb07eef7836e1cc84c277e38fab90235838048b6a94406c1ce6e1cc40bdaa9bb34174fc9c0f5fd3df825587136b68b72a07e284155ad39d5684457017c409d453a68cc2059d103bf624892a4ecea667234f43948e870fd65db740f0958b4ce3178c8904380585af461e1d5e2c526761e365ebdec24632949f1f9a504b6d17e4b9f679862282723bab94e3482b259b84e8abb011c349530c38944606fdac666eeac2f10e4b359b2296fad41c79e74a97297c8fa024dc87cf8dd26b1ec275d0b770b0945a87299b96aa3d8dddc8faaa77294b1c7efed31dcb87ebbe794b72b7f7c9966b944d26163cce288848959bac609281031bdbb5dfcc94297e16100648b66f0d2eddf0b4e6ec5389c6f2f7ec3fc5571f31457f9685040ec626427f54f5fed9116b06426932eb213b5df67c16080443a9f765ab41ef774773d0b6855288f75cc1dc933ac1df4dbc0171611044908389daf71d880ca336803b7713f9b3eb9f59dee8313c9d9f361696dac792bf68c72f38d1f7a7928b1272716aaf52aada0a4c60d0dfbe08367032869c190fcd1bc0bb0b2d2aaa105505354d005d741e1c47879fe9bed899a30046e8524299c09c96e423eb35c40622c420c335d5f29a2e914a270fc6e06289e6ee4177cbd43cd9920187ae7b8b76dfa72e9bb0cf68bc68ca3412d5cb66a3c937b68da0ac58fa75a47a3cd68df995e6bd91b1e83438c014c86cb047b8fb289eba98c18e3cea1dba172a57192d79e17f0101189aa0ca00e8565c6137d59505649c7a963bb76c58a8072b4cc59356200494ef850c1f3e8206da851f9c6bedd72f2241a7887d0ee12617262935fb08f5f85841304fb977edefd2a2e2657980775957a085b5d52de742a7218f0b35a264d34b9b6344f6b46da33668119d0194bc1e55cb40f94318ebfd42578d1f54a8716bbda91faff8f08e1068c830b947ee9bec7fdb6068e8396371772a904d4b42a123c370133a8b5e1b4ff10ba6849b4e7c2867ea710b520405a651801a8c61981e79c79bccd04761994741af2fbea9d5238caa3a915c4e71fe8a32985f3a6529e6ebd6ee22cde0ec1cb523fdcf49d79b184bf0d7014a58c3da6f37243846f60cce9cdc82d1df8f360f9534d94624f649710efcff4e5f15f30d8853fe4170310013aa1ed1f7cb028b75e737b915cccacd94f42552d091da82b26f406751afffc165a177bb54333ce2c01e834e0acf1f611d8630f58e173c57a700c571020b3301478f7e2400b3585ab29462bd17cbbd8d719c8b744cfc75258613c72903b53d0e9c4942201cab40eb3207b918257189b047d340bf7034fa6e9be1b72f0bdda083f14132e1bac182f06b8612c5a87f48066274c6be154882631c3ae72d71def2c8cefde1ce02b4958f2ad620353c2f1d74d60ff6849c98d0b54915672cf243a88622ea1688b09c70e38d20aac0ab95ae02e265c0ab9c3218b04345b72aeda33f9c83e17257c5f6f6e554a1d2029e9df9d715146830ca6d2e9f006f63e6e5487fdbcb9b604d29b5adad4d7c9cd4ed62f75a3de6f22eff4a11fece2e772cd4d02d12cf5e457cd29032082d9e4e8cfa332110b668e628146bf7f373a9672ae8e1fec6781035dc29018fac71a79c74c2e73a00693b0d7a1a309ba08a6ed66304ade03935892c4fce64372ffc6cf804775167ad622e7f3d94a78d23d8ffc5a138e7effd74bf27956041a2b01b1caec168f5da41d284369ade995ab3ba78e08cef1d590121b13a79bc7a8884d09ca1a52d511e19f130f9c4b145e8a589a43404789bc0d4a9b2a75b67101eb6d1a0ccf1089a1ff4ffd4e3d51052872f425db26e84c4e27c7e78dbf3d03052f3bbba2e8321fc87ad96d185254c043d613d8e34cfed29c2b71d123582048dc19a3c4c7bd3516f4a3a832e98379205ab10c5395722e48262265759c08ae78326246b0ae1a0a3ba9a35f2bdfd9517d55d839e69957f8bfb249379a053c7199b50dd65be5443403a615afe023a3f74aba81a6addc72ed99a060a75c819cdbe29c444fab8e05dc51a388dd248925d2386558108b877236a82e6c8faf31c1140b41011ec8aabae55a06d29152f9f5c392d2c25d56f36b3c4f0a37b64928d0c80af3e9eaee44baaef7aaec5beafde4fe4648eda248e972421bbd3401ffaefbb252351e7cfbdbafe3dba805d969412a3b66237a1283720b79d3e971a37461f622fb658ff4025bd38c11fe6644260b846640bb31d54d8572080ddaef85fed7a4ea3664a75d5ccca23dc91166a9941104303ae72cc7c50760ef8efd00268f7557a618a3eb4c7d2f4c475916defd5327bdc9e80f338fef71720bb9917f16bdf138f212c51b6dbd1ca921c1a9ef565fc35cc93a195bbaead648e4f05ea68f33c7763277a57a3b1df2e04fbd832649d4153adf54d99157cf243a677d722aecc6773f87797a078a1f9e81e48e72b1e02806aa46fb8e30c22082726aa17cf6c20d886e19d7051c8553dc02ee7f22185ba3ceced336ecea5ea86c39df281a4ba6e6b8397565fbb0883f799184c78afd5be1adf3b81e850cc8f134720cf05f7913fe843e3dfe853083de0a1aadd531a578bebeef4b05cb8562e2cd72b2123235bf02f27c9c2009a353f65e7c900e5720f560864308fe7a2d251b0534c4bda66f672235265bafd8ef2f2ec9b23c0dfae6578c9fc1efed62cbec6361725ca971a0fc55fc06640c52dc0e2c8e0c6eb8e868a015bfae59843401d3cde22d9d615c58839887035e4ced8da3666d0e6ee0b581fe97b6457c46e77ad9caf172ccc925d7538b2ea7b85654010e64b625244edc094c31f07208dcb5c4a6645672d2cf01f7fddfefec03afd214d9ff58fc1f1f90ad5686f1896f32b4ddf8eb1816c6e0d1418fa66741481d194eab2abcf8feb6388cb74897276204c53a3ac18a72847da4001aded12ff3c80cf1d0f3e85157a015da6dc484b539be327a9ce0f9722d031637af4382177381cbd48d97f3581bf5acc7936e4d878a798debe1b6c772e0a606583da92eb5e9843184efcbd6cfa9e70d68c31b06d4f0bf76288a8d52096d27f8148e1d24a08a0e3bc21eb5b683ddec56aef19c740c3a576331f6f53553a361f9a1c6e7d185106e74c78acfb94870f8eb406e63962e851a1c9f22e9aa725770a244bd539bc2c4f2d7be9dc18856245a75c0d847949512812476e6b8ed60ef3adb68e15f6a2311200a05820510b82c5cf9cf9a2318f6267e4f1c38f2971270cf65209cbd84ad83744bbc12ae1cfe8dbd71aff473598f677924a176d7c2721f3d87df9754783675a8c6fa5eb2f8a100e1012bd8084e2dad55e3dba99bf07269860e854908d38ad36454bc59ded4f9749558512b25bc7ad40683bbdfae20721527c748d5466872e171aa32280b3dfa19fb38ff834383d81b850379354eb349e239493e943d4f2ad7e46bd4594cc2888a7f749a8a81ab38c79ee3f223f40f315a559fc4280505d76798be28943d21236661f18eda859e41c5b67caaef111d1c637329b2ebd4b59da1616598189e279a46a1ee13fba9554b8575264f14dd0d72a050c4a4278b8161d5c41a5fb1e021466681b513b4488a0a10df3f42caa92072779b54b4c7d04dede87f48f017ec4abdf70afe9178cdac34d37073fdff9ebb72e575967460286316462beaa6f264fa5117360f1b59b22c64a01310fb00d9cf7295925ac5bb8f109ff1f9b7e0616894b00f7ff6916a46a1086f81460b13b2814ac530754d6ebe47ed70aacecdd9ab8ec4dba834c9bc69c0dff4c005e0d2eb8672231fa862fdfc2cf216d23ef9204850251c75c866b453e3eb6e8c3753ba8e25721e773041f568a0bab86110e1733bd9bf5e280d6b3e0f23f8fffb1c99e9912538b0bbea665cafc2a143ad43fe1677d33e5432f923e892528a4c32f704db7ea5270c90f120e8e6a5181bf3e36cb0da6d8a2115ef160dea2b6414465fd45739a672cee8c7825b9b84fb8cc1a4196981f786dff568291e6edf9836594c2385fba3045d915aa3ebfc49ab5f57140656109f948019f56f2adc2f4471e56904616bd02de3fa6f9cf185ee77c4d80add9a23d89e749f74a93e8b29dfd0c89905e6544472535c99fed8a9cbeb54b20afb570b8d259c0c0ed047df099f95ad9cd160c21a616efa424bf13e2b7084a69d9d685d21b6a722df278a0af70b90f11364af3c691d9ab988eb75670dd6d65088fff0e7f89651e7b96e56cb58e9048f907bb8dcb104a7b59ed7e3b46ce7482a7a4dbbfda5fbb4f68ff06ec5de24b6eb3bb4a432c472dbc64f8b9db48e19990beff7e2921192447242172278a61c0834d3087e1f55723e32c2d26f76f75015eb832ace7e9f735779dd1d7a00b779bc7d976807153633d13fccfcf5c14739248e43d4c735c22ac5324b9f4462eb36953df621a2d1ec4b8804c0bf3acfa028d7a0f1788fd6cbfa71c9de5424015a4a10eca7eade232672400bedcae40a4b4d4c8e4431ec3e1641be81b0e7a3825e5bf6a4f4edbc7e110e9bd513c37e97039a2f63352823e230a9d2d37787d7afd98e4dc78871ac775d72c59935e652741819665b91070b8982cccd5eab80ab4770189956694aecd24b14a22dfb7d6994cb56b8970bd68e220f9a96ed432e56af87582a7a73f9ad313b7203fb88b049cd4845669ed5f8795c16ba21da6663ed9d4c637f8ff8e0fa41d072aa29e50372364646d4c7e0e43e0f84d7e7e20e2bc80af255d8b38ba01e3593075f1db10b9d86617b71e6bc63bf5ad2a1a20d4d099cc8420c8f275d1164e99b075259c6e9a27f5b89f50a0a997379e5bf64b14584d219b6c16cfcddb5ada82f72188c61f90eae602a7116067d02840bf12f9969468164b5e066ac3f99d2f73a72f0f3a60c288dacf671fe3a6cea872bc77ebabd2fba356e6fae0c9f96ed499b722ad59686ae7f6de864c7a82dcf8222157e2fb744d43ba870893393212fa7480efc2220bc29cf9536f470a67ab947cbf07a18b87ebf25982ab4785afbd662b8257245339d11fccc144afe3e65de7edf582905ad497654b13be2cfa20cc52bca72933df668911d4d9a0b92a90ad6c63c6dd8e66a3b0f4c1cb02bf7ad08420fda724663ac2852ec3e2ea1a83155192b61f475843ba9c7404d42bbf5d9bbc5b5f631fe69718ea6750915cf77d8da80483bc934842d3c8bca2f51a195ea9dc447ff19fb9ac20d40c04c0f66d54cd165f9d56e6e5f9a711be3308f4d961c7dfed4d972c6d3fd2746965671302934cb5fd29c9de5845f54c95c77720d8d4e7382aab7018355697cd2ec13dc0e994f05b4dab2ecfd38e698b5c10e8c9b5b5c6d8f5e647244f2749372d55c0699dfc3186fb9c6c45f7a16754e51a6b7e4257c57ed695a08598a68f8e0660f7941c0233f60e22400bdc03552f6575116091e5f0ca4dd24542c233d998fffd5597121b74d2097cfb6e2272de82c3d12d36c743e3240e1b8085955ec6b8a81dfae17b0619d708c676364f5814a952dcc50bf93fd374a75a0729fe718cf643dbc6e8262485f83c4d841f6e8cc0ed06f5aadbeff01ec4f335e725b533131d3eac6677132d4a8c0c25ec743eeba993f8906207401c93f12a5f772887381d74ab336ac1f3ac22e2569ab711adb219f5d720da84236e67dc8b62672f550cafeea31235892a3812e5826cae423cbf48eaca186f8f942697787aaac729f31a018e1f7d855f609218f7392ffd17377391141e03b08255e42b9c9cbad7263f15c4178dfb19f93c9067ab231e5853e6c8ead93ac8b1e2694117bccba273d5612ffe8993716ae8f3d81ecefa8f0b2121b0b67775603b0ee2fa12109847114c858aaea98f82838cc5dc21147f7aefb579f716cfccaf404c1d84dac3397282a785c09da7ad683f08af44012e8b775ae06a103e250bb9b9139de8e830e39f0720e59fdc645a1da23592760bf1b6af53561778161620032178c5b20d5477d4051230580469dc6e0bf438a292619af32d2013f309cff68b055fa42fabadcf5b351c6556feb34ec5467766e850f0d25c61edf779f5d51b03972175ab7a5c2bc8e5a188e3be3b78adb88a2d8d35cb96f768e36859a308ac76c3eff77ad221a93dc727e25f50eee63b1d50028f3f33c7544cb283165b88967f118214b9541433ad33a937949d962fbe0fd2470101074fbc10ddd8412cf9479fa528d57f66d3dc2aa567912b34fb70e2fbc5bd3e0e40db29ad09d203987255946fa60f544e5bb4d415d4bac150d32c162fa4207976f3d2c39dd3cb60983f6bd661d45231d06d193f648e635b61b9477c732b35758ea58fa8a98f79012a93d3da886cf3229ff309c2836a42da9a81ed4c1143e0db541b79ca591ccfbf3ac0c84b4df3f7bd9c5139039729b4163b427a8ac268e56cd41677d095bb1f78077fed4d17d44d4ca4c33b4bf72858e5e6c8677c8524800001153e50a3e5a6b55de9357126aeac3a1288c214f72aafcd64a846e9e0e0e2a9c3678e8b2468246685c0f9668041e19a89771bb91720d87345287b2c2313ea5498fffc5138d94fb66e8655b1895fd74c29316fbec308fee0e8e460266024fbd5b9c60eaa57357183e3d5b9c604ab0d3babf96b2ad2a9f498e5669b4ad5c8faf1e76beb582852b67eb7c420214135ca52810c9313672ec418bf659d1dcb497fd1b8460ac336c18dbc07ba7bdc0c452e0556378dece72c1c55b1c1b837550c8c06d21d47552116c26fbd175a0399bd4e0e3bf9b9dfd0f1f2de820144d96ff5fa3470ae765a9bf87bcb573b7e7609245bd1518e7962c72b6f4b3ce03af35567b13aa8a4662622a2fda7b814745ee6e54e8df30693fd54a1839fc1366deff08fd30a4da0d3547cfee86393b62a29c7c237ed8b989a59318353b281661b7c54f44b4847ec8488b37c453bfad40a1c9e996e10dd3655687728188a24f9acbae297452fc7655884ea81ca3ad40eb8c1b2df2a14e1af51f054d6aae5dc985a76265dec3aff4a7dc9d0abfc779d470c80ee88c370ea985592372281c68ee173aa056043d47be05c0177496386a24087c3c56c44916237d40b312ef7587cadc7868a6b8632d9a730fd68904e623c40863fd16fb5b0e2cf271df3c4351b88a3db5128c8df262c910817f87d70e535fae310203d6b75c156fc75572a6626c026db218570c3b13efca525714142687eea04da8d37fe2266a72a20a0b923bc20281294de8085f4a8b057c9bfdde6a0747baed64ad49eed4673e3bd0508c8af2f95648c07ab869fc910267059a8d65a9dc0ac18eec192556913d614d7208f7b7850273754db47530bcb7e1050348e772c971c271012edca6c02724cd28f331c013a854a95c3ed664c847ea8c6bfaadc8c34794bf850f19e6698d33f15934100dba35c7e59d04f54fa08f71ad93d053357228bbe0674dde60b34285b652fc722b677da5123cb417d6a2c94fd711810b45b2a974b88b743a1abae5d1af7250dbf00f9f391e5a21925642fae1aea8e1672fb8c19cc2d267e2dc7bb9647e09b7a489c6936079677aae7ec6e867928d35b3dce9f64fc496f7eab686abd11b6cfe419158f2ffcca03605bfc2055ed3f10d3cf7c048b8514446776f613de394727daaa63d0fe102d07e546f31e5454ebc6f67d137f7f3c0602fbacb399e1ba20c579d0ea70c86183def0ad9869ed8d2cc57623a096d2d74d86a01e85f17b252697ac324dccac299cf8bc8ad765e2cb73edef436226f2aebd00f70fa52b1e52a239df7ccdb571edcf621ed3c7c486306bb4d3b5e02de20a9c0efb5d587ca42175232cc4e1a2ba7094f2ae318530241618a36ef42fcc78a8b43ef8f2290252e2a2a14609cff9e216ed7d0be3fb8dde71bf3598034c62fe77eaac3f7c65fa12011144bb09a4899242ae12edd1638647c0bf77da9116503c9833e17c0ffe3d7c2412cc192a4cc0c52d8e3f1fbe0b19487b0238b00e7b2414d6a57c7eadb7454998072cbd7e0f8bfe83fb2f9d0b4ee783818aa93eb9d6e970a701c6f85bde3ce35cf7251af965398ebd7491bb117cb853beb569d33eb6d91129a8b9344005cf65f8772052c467b1f1b250772cf02d8de2dc95ba430928e2dc7f7e3d5b637f62a1e171ab73d5dd6b87f0b0f21de49e67c487424aac86fbb5e89d79ab3e52f49f637842a5f186186c22d15a06fb4393a2c6462c3545e1d8539c3aefbed16b3208aaa1537415363db26be323381fc6032e2ea3f5a44694995835a39c302d140d9f5aff27226baf0ab0f7a0d26dbf941c89c958c4443e57d2986de7343b53619a3ef0fd67296bda7a01ea9ba7ba2c73503db3c71959f96ba798ee45a06a03aae33ee3e9c72ebfb641c0d8f24a97a655939d6579538c481c15aeccf95358851317d2cd6f729e8014b4a7193267e1132e619190f81d57eed2b80dfb1b709719b557ca260e87293c1d529b728a19a990f9af46785496353824e964d31203f2faeaf4e9bab3572bac66e5dee54c89cc54b5a644dfdc8462ae02c006b0555658a6de498404a627291c57d36ef67d511777158aa1116c92eb9cd798fef024bd10b13f7e67df9a672e07be3c2f41869ef632156e50e89fd7495d5dae2b8004ff0b4594e883691c2726234cb6710712f76eaf88fa655d1ba450892bc85c80ac55fbc7440f7ae776972754fb710fea7485ef2699abde0a5b751b20adc65dc7eae18bb54c87564e952727e27fea8954c7aedc2f532dda3639ed4310bc0fa247a52a054ae01c9b8feb3726618d583e4420f8e5851a5fa73294dae226a4c6605967bf099664e26cccb0837fedfa15442f4c78155358faac8ceab60ea9609fdbd223fe20e5b581b36574f721a2813d7a0b35a82b34574ec2487bbe7d2d09ca30e4c1145382f23f580616868d6fa1811d7ae64899f666784ba2d89b90d1c7c3b3699014978d91fdb94b3f3725f7bf6de047af639d0db32ab8d4a69a546413e87ed5b9074675b48448b1e037237775cf895284b4c409b7ecb8122cef32056b14f7eacf4d3508f073ab45b562851a07ec417c6659252e8e7e899ab9cf8a5a795094fe5cdeee748e67b76b1b901aa601334581a610cf98d6b5a286b460c248ab8d84a1c679096738c1bcd1f116f6eb72c63a6cdf6b25a17613646e72dc2c5d8de63f0543eb65eec6e85b79d45327d1b26f0d4094e6b5a6369d036902f8a6b381f9bad2b8661562bbde65ae1d072bedf8147013ed7d42ae363c0e2b2a76d9c3104f3cb065c66df27384bdfe3fa72f9681a7cfe993050e05bb53a68ae28822885ca5a4be7c316f8ebcfc05fc4ff72094fb78ddfc76e3d9a96afdd3afd68db3c772cec5ab8d466595adce21a7f3b7253523c2b95bcfbd119521a5815f440347f8092362b7fd63b984e5a832a5ad772959361c3049b7c149edc1f96c9796e7174e170d0ed6a7d71e357533c4797aa723b0d5f5dc3c889588167eadc95d698808d8b2ec2b3609d4823f90841b4fad7465c72033ccfea8e2a50418ecaa42c49c41e55c9d9fffe815aadf3eec17f02ba7233da33020de84f992b06f760e39319a37b3020148d85f67c63737a75c098f50492281c0b61f81d96288bd1481e55e2e45331f650755fb59f52d0a1c75060c072735797e158f6959d5227740d8cd38da0840a9bec776f3cbe611a6a257086051a2300f826c460686490c6d796d54112928912d4c458d340ddab698eea25280b1a14eefe19e1b5e9e44a4652569e32e523c67b305c2c845c0a4d09e08ca2d6fa722d283b2ff60528a0779a2a4d2a9d808fc9e680d244156d65dcd7569e270071728db4810fe665d7d12ea011aa903f8e79cb5e557759fea94ca617e9ab38093a3e77ecc747d3814fbbb5362b20d87184ca747b238abf5a58c9edd50d0da4af38251d485f5b737be4165284fd16133c61752022b32bc3b0258e55a7e210191355721ce8e37f398761a494ff11419df386524019a511f89d09963db908cbc728f072bc2018823d6d8cc0bc11026e74cbd0532ec7d09adad41647308af02a6ef74972dd42218fd4c0cc595aace1001450eaba95c82352030fffea44d18f7b61c17e72f2ee9e4b18af32e60b13593263bfe077497a0773fac1681fa6e6dacf2a89aa72aace5a88fbffb59181e4616a4cd83fc8a3046e91eb42dd25a6a9f1be7a33d51d665cf3fdad8461ebc2674da3c8687985af2281b61afbc475de07f184e1d8547211f7afe2ce0fb543497d3270ed5218902d264d040be896ac19d6bafd6c02913eb7040f5f133fa0a8b5b7f34212a20e7f78681121b8e508c3a8fce6dfae98a4726cd1bc3f7c7b44b61334869caf20474b24cbb09d080fbc3022d155ac7ab0e60b77a4995a184d2073dbfa664bde5be9668428dc4b4a11e12ca368348022ec335d52b7e1b2d21e0131923e2cab1d7d090ae6f6644be5cb1e50e562ba7505446472415394b0eedcd0ec6e5dffd5608ad46441150f41c50e3f55b5773a5f0a015e296bacd6160fcb1792420fe96fb0cec58a5901a0a4224cdbee2bc29e3a261f921ae2dd296e7269e517a16af1d37a102787934429c25446385dc7220dde3840737276e2be3296854fc26e1e6244802baefd8f89cacd8a2686f72c8988177a6d67728dd5500a2aa8d87e6bb5c9c6f76b1ec602895fd263badfc20e715b7886a83e3dfaf29ebfac6b16908796fc59be8ccd80e7370eff18b48c00527ffac5affc7272b229937ae4fc9c1a7dbc48a62b5e40f50050396e8f9f15e2de03134643160b720695a1a5fa8f7766315d800ee323da531b708b85bb691a32202b18e743537067b99f273da663afc4156f4add5f01735dc0bce080a7377e4904bca7b42a81764d2bdc7269828f153f61637e0633db1dd88fd453f9ee07e8962aefa77e524bff3d25456336569cf69b2c3f4243364011b59469b03f99f832d9cee4f39c5a07e97242a9aafd55504882e6106aa83f9beb120dfcb4a3a1e8e7548cce056f63ec31729e88aa1ed9a276d1c8bb3a09c3e6f18dd18bd7d3231db63b1792ee1083e5d5051493f99c035cbd9e4dc9db38a48ab440ba3c8c8cebc5e2aedf8d304d321c134ce8df18462c97f141075dbc8ab2ffdf25b8603ffe3127226d43e27104ee3d9472fcd56b0b10ed7820babb54d7fc93079add16ac08b80fd3f441ba6e351c2be972682eb9fa59c0ab6b5421da070483ae6f1132a8dcefd1c7560ae2b5e691f861728974321173c6c904b264f4533e4abc795172c42c26e62456cb6108b62c1d137285392c641f91d1c3e7a5369359ab8dced73335199fb0b1b7a89ec1ecd4a3eb2dd402f593de68f7b0a0a2f69ada1fd51fe35fc040e61a559f174f4deb4a2816728524079a33648c2596e4714b26502e578c314de4ebb5f8c84433edd485388372986e02428c01f8ce0686212b26fee46a65828a7e6aa1b633b7d75496830a0d00c9ee83c9b26e2a1cccfad85acceefb2dc54aeabb1fae605217080aabc06c2550ad1a66a8022e695cd5fa9706832fd7feafe0dfefa9f63dfe05385e04b97cf672cbea751acd99de08d46c9ec3bc23fc289fb03eea208fe9093a2d8f63c43343722d08bba9c6c52ce63ee61c6134ab9253322b9b17fe1073b11f7f0c520b34c07298db864a851d3ea77b7c05d403564f4c0ae19b180b58ff5b6c62b0baa250b5728835422f3db6fc7be5937dd80c97bc2477e6a7b80fc1df7161145451e5a9aa7276538444cd0fc3a4257996647fe499396bc443422f95df4fcb0cfba1cf17a972ea9638b9de2deea942827d2e9199531d793138193c7de8324eddae056f69197268a9b6e5bf43df7290c73d523f0e4fdbb216d5113d3a3c1f1112602dcc632672808da2ee94d1b4f3b13297755f6db2fb21e686baa959c643cd49fb1bc5eae472516bd26670e2898a7acd0cd0e53f4168245b6937db552253c147ecb99453fe72c5399027d014657fb66e624f0da91b54945982785d49fe582cf06d2994f4a1721afe3776e28b1138f38ee3e24ac7d4b1e41335be4a26c5ec81fd58f4ec54391d13cef41754bfdbf6cc9530d5bb4e412d4480a2017e4153b066b3b2c81960267219b7a6c0946eaa8b32eefd3d7f1427120e08636161c3f5703cf6da6e8c6a707257b2b9ca52c6d8e58a0603465501d7198df0b1a165962b86cf98a9e11ebaf872854510bc0805537278150220604a20e9da33f61b1549293c83944d95ef85e938410e4c3ef457d44a78e14de31e691bf72092a9381185d529c36a38832339d5722c506333346003ea7e34c4b831961efa6dfc3602fb747b3ecaba591266820b7223eb6c58f6cd8a5945d465978a27ccf0f23360dd51a5e81cdf71259a0e09f472574ffc68e74555ae874138d5141260ce03b9f3677cac549c54930b042415d172f18b837fe74e1ca54685fb7bd3f39c5ffcf6ad4ae68e15b8f4927954f9d79572b251db5643a9f0248b619bfdb988fb0269e33333046a6e48a53918a677e76872f366d53a67c76ce1d6a5bee0bbfc38598f6c1ef9d1f94251d41dd5b8c5e63272f6e2361b2ec177497c0c5a9ddec2c9c0b75bf18c0e8d37baa68aa13291755255bae8cab28378a3d7f19648efc4f5ffe0db37bbb1a58f288e6bc6dfe0ee7df97261e9fc32274cf1c362b8caa2a58428e2ee1166781b954f93a96a09e76c70ef7266f7d72996707c2dff94287a39a9781a839e93ae76ade312d53ba3ccfe155d160a148b7619a9343c683cce7c44a080212ebecd41a5775de961204accd570dd72bffe052319e4a4e20de6e68fd108f32f840dd1f082460a99d50c66c2fedd1765351fe46e59095351b08b2f1a0c6fdccd4ece5ddf453ef2c1ae4dd73bd38acb12cfad673758dd62e1e381884ab2dd102ed5f0f1bb4b12735f5888d87c81c0b372124657e9f4680543abc0b47049f1d9e065718ecaba6bf88ed74e31abd2822b2b28afa0a38646ccea046ef561307f26adc7f84090b994fd9fc272d69628352f7274f81ffb141263870faa15e1408db9f4b871194fa4d2829a061b217d5c274f727c486640ef84d184197fd53a9105b72491419dbcae910e5c3f80675b85b91972961827c3ff539d90ca8bf24fb9f89c858e56895305e99f473c2a58e6e5fe40725d71bbd9e2dae6b0c4d1af6efc07c07ad7ea4b95c65cd5e9c5dde60efe8f327287ba9f0ae9c1b9319ee5922e929a85430598c3050d84d1a6d9df7997653a7f72670495ac72731645b6a7d93cda40b68e51c1eb3557365d44ee77008fc9afe45b848bce9fd0ce2adce8a1e29a835f6ca062c1a3a1dd22f121744eb06d71fd9e50e6b4de2e99da4243f2aeab06ecf08601406add38f91f98bd4fb9ae41511fb872104784be0e709520b429cb4759825a5779db09e225168e34fc5c6d246341b96c640c6b1d0eda8f840587576f028f56e044a55443f6cb430bc68ef0fe6a596d06fda1cde6b6fe7436a34f770509fa7372ea5c089acfdb599acc771fc2796c3d0ef4efcd8668227da61da391815e79500e9e5a46625e4b263bd5d6a9f0d4a4607248f83766cf2c988b081a5ffa7cf0143c213b915fa46bb6d1e965dc884e9d63215c10835dfcaa6871dcc3207e89532fb5cd1491ab8522fb7e4fbf055f3c165a70f997305a6b1bbaa59080141188908e80691d5b566628e232667402724a60686b8207bdf49322b9a2e71e5823fa6d49be288ccf9deafae659b14c437b4024c272e07a62477bc9f781c070543d3c448035f14f2c4001e3c26436233b6139899f2124d559e8e824f253e791f8d2a6dc494bc6fb6de5f76bffca3cbc7e8ddcd2c472082bcfadd8859a2f848fc4af8d05feb8fac196580696721cb1a71be28d8e0503bfd86bf1b32843b288366243f7466be1bf7d439c65d20eed1dbc0e52ef36742772414085a94e649e4fcb70ab7df5240a36e52849215a54e7d9aa6c13795ac11c00eb8fcaa8284a8f278135c31d9a89c67f08ec7b6b09518aba736436d6179b345443613a8ba9432c9bbb90600dfe721416e8f63d59b94e928eb0448c617728728cffe1d5f0ef2219056c50b71924ac34506b9f6155412818d40d11ea7bf8ba723828c645e8e206dbb2a52dd125879272099c64dfaec29b5bca42d1dca7a2d245118bcaf85580cb727c0e3c2ba3212c5ef2dd5f7dd6c1bd9fb8bc64f18c6f8b72a74ae25cf654ad9fa49a5260e5cb45f98d18cf32c0b07b80a5aac7ff7b57ee4be703fa2ac8b7ee0fad612f99a033099fbf85c4d114bce467b95bdd261c2bfe720858267a740ff31f286776124537612fb37038329f1c794087589d8799b4284eea0ffa9a13c40a0923d7f6e7e7791d9a296cc75cf1aa6de2a592de6a3af8d6726130a0bece7b27cbe1196c1ccefaac06b9eb81b4f9689409896b5b3c40541d7248c9822557049ab541ae62debae557872033a74a880bb4dc91e4234f89ddab04b3b21f2bfc97a9c58dee10dcd79e3b63f8adddf1367df32e0a742acd1ba1af7264e24acce12134c3c010002e4a99e9f682d9413f07e8b122b5ada33d83f8e172e17ffc091c42a145c0441b8f33151158164c13016e80f2068fe20822c5aad472950c93efad0bb1f14a1a3216adea805c7d498ee8c4ed4a42f6ac15582844247299e94159193dd8e85f3635f41d8d47bfbcab708bd3900edd12d6352f5270400073de38764c6c8d51d164eb49c012d45b219953053edc43a08c9ad1de5f544d638b15927d7dc3794a732fbbb48d4e3b5f0ea821b28a48ef5e63384bcd421b24722a1ab741b2e223bf5e5423212c59b681af54ecad3a07459e0d9753121e10f77272cc4b0812e6c2e7ae9d0e19555fe856a4a26509d73bf8c9eb557a4029e5d84098035c4d45650651af1f253b4e1cadb11f3b9268839596e6b8b4f3e98eecdd6c3ce92f26392e832d30d330da2bfd56b5345a5255029f40d99ca7ca40c9c5cc723750a056b445c779c8e64655b8f74a066340f39c39adfc01933f69cefce5e63319db197c591877122e30ab07ee2419620ad997df42220d4551eccfa76316bc72acfda5c486086f5c3f69a9daa8ff5e83499fd09e16727ff67e334a6464784d6535b7634de5b471adcbff6bf07af5da899b6f9f4842ca86ba68179fd839ef5f7217d2906ed29da7f1b79d1f86b9ba9560eda28d04f42207803f8a2b8d43ca740a3a6b66f8e3e761267018949739baa9a0121061f1ceb7f21104a1659dc563c13b7fcbafdc8b828a8a656989dac75aeb9d9863e0cbecf1476de6a1af5b3c5fe5727957a16cc6ca74a912015808ce17c25525228e72a19adda85fdf7594ee4d11726a9f1a0105db473aeb51361d9f936fc1170ae9ee1071731c7565646dc432a87216ed199293ad6f5c3e7e387ffcacaef29e53b934bc3179c07e1d088cb5d4f53187e37ce92f0644af3436c30187a141692fb251dcef808a13e9f94d3cf7a3617243e31a56ad8cb2830fe501b87b7727404e40e1046ec20990ce531e1bb23c12662d88f0bd5f339470985fda83755f0cfa6fd0f24e3c1f9fb160279a85c90019725891c4005fb3e24dfe9b390e94380d2d4c27b485d8a7c811bf22d795803a5536f91944b02239256a17a6a1bc362338847beda01f12b0a5f1a2afd52d5acee77211cb727ee697c5a348a19f8bf4a1e230af787d76659a4cd52d5146e582f51115819f0c754806a97ee8b93b4172ee715f95b53d9e715940721f7e7dc29fdd46724b25857ad7130a06662ffa3ff592b520fa4a562b3c1f9b46508c6dd872c6ec55d58969a68c687e1815ed930a1e85f5796c5bdb9d6615a6ddfd029baa5ed38c72819feaeae2888c0dbdee1e5b9e63ad2bb2a50fe1225bce1ae14b9bbc2c4941721a6095876b4151a0dcab1adcfc4f91e641afc07a1fd2eaa34e3de59ceea4457253412f77372c0219b9e63e0480438e84e2efb3d2d2e962ecd2d49d6867afb76cde09f10e25cf28dd22ceb72b13c0fd2a5a1acb70aae2ca7c475375822b008f721ce0a88c4dd156ad0c8cdd99214a1a7d8b8119aa19866a5a025fc672f8fe2f1c90bac1fd3e3d1cdeeb6f4b0a86fe15c5ecf2a207f07b3482af280affd5632c50eb8acea9f37d5c00dee584f2bf09b1b1b0b4dcfea1726bf8006daf34a6c6052d613294822f009f57bc00e470696f7f59825f01882b981f3bb945c0e6ce7fb8402b8b393ce1b354c581a0dcff2afee6e89db264b7ac02025ba0130a4b86d7097255ac4493fe710b949cdd4b678014b1bac6a5eb7fd0ec743966b365bdaa356072c158e5cfd47973a94830f761bc5324582265577a20636ac18303fa9782719d7241da6284a2cc12237e7b19992b576c535952ff4449da942e2732155a9544597213c212000c92f60fb29d5755cde0e7cd897eff8155de7568a94bd88db32739721a956621c817b5c669ae26f1818482a7fa8b9b3f54f2d150d3aa2056ceaddb726797ecc78c68dc2f606a5a73d726e07e7fca56d74b63ba04ad7a43b28348992e53d23ed978d3f981c530b32dac6326c3ace742c5e971d9e3e26929e5b0a0a42e9f08531a40a81ba51e06674fcb7f478677f9808dedc3eedd585d87c381019b72cf4a8f926d817e46e52be50e02304eefc3dad8df85af354f7782b9e6da14b90b798f8024063345e4d2fca41f6185d4b0bf992c999fba0a7f98d305ad7dc95f060e94507d4bc368a0af0cc64c929e63a89ebc4467b4de8bde4b39098bd6fab3043f6df68fe0e0daed6c6abd08ee4c0dcdef80936c36520f7f531412511ff1a4722e141da047d378d0b33086efdeba1abcfd0e8d670cd9178233d983291d1a2b720c9f1b4962e0668595292b1827e3f2ed4407964f8cc95ca47ae072457349876c7e37767f7431de1e364e476f2b4cee0aa579a2abf9f95443974e6e4b701ac1723c6162a6a9177fde78e89d8f5f4d1819c9357f0649131e6f826c64185e1db87292da66969d371adac862bc1ca0b497e07807be9ff745bad8bf9885569c89e372a26ec3295541e3c01b5be2860ef2e84301f85a45502f64354fb291708c57a472c26b96fa8ff59a1ae1a164a87e981f674f6ca3de4a6aa4b96223d85aa0cad237adb4daf2ea0d17a8377f2bcdf82994cbd2a81f51fe6059cb24343e3e34750130978d07f72a30428055991863827471e843a0c912ef864cbb3b6e85378e6c11669b01ca28799f31c1ab88a003c0886a3ee489259dd8c9a05b360d99c2498cf35e878d52001a5735f52ba42b231738ed484678324224707f810cf6deed1c87ad723a7d09e6187451e4a1ac1d7de07c478c1baaa11eb7b5ada52cec37570a1221720d7b6fb0a824fa938e0df56a969bfdc76768d50996a865d37d9caa4714ac97722b6e364c26f95382bf9739481c3ff0b7787e0277d68c6a27e8aca1c7df5b7d72c66dfe09721b034bd517cf163c9c47e020f535fdea778b56ddf78353f8a00b724015f6d5c9eaf0c565a61afcaca603fcb1f5249b4e5768856e8013b83f6cca5f32f4d802892cb77e1b7f843e919f29a2f80abb3246db2d54796f4b4344c6bf72b21330a1c86cfd8e7d7317edac4ec9aadd44f7a942ed03faf33a408ab0cfae366c59481e89caf38d2b51d5de97f26fdf41af07911ce909229094defccf208572048ae544a129936f88c8e55b37397f1398e834d16b6901c1d17eb75c23fb5665b963f63996df5fe43feacedb055cbfb977cefa6fd9144c28cc4e079f06ff0f72f1356920323ec27bb9d22663fbae5ddec76b29dcfa8e5caa2b13ccf558334c2c8558ca743e52764a2ec3e87e9f7a70f8e417bfbbdb9dec9457f2eb6a4252002b28723758da208c322c013039ace0c5582df0bcc4e95a9caf506e6373fb5eed234df9322f81a119cef7bd9106c2f2763152443ba70e7cdf33abcef1d108e47d3eabb8ab0273488362300111f24e64a8dd1ac38024149f728f96e46d9b23713b108f939e56ac555fc7cd002f837c6b3fe18e15a8499cc0e28ec111aff3d3e13646f2c7d047610b3bf206a0a7f117b896d4c6034d25ec6d1cf767fc987594cd9c4b5229d644bef231cf1f62e748c614f82550ab07a0be42b9e8eeb62d56b6c14640e6927c12a9c34b17e49a00dd580ce02f4c722abd967cf991c3091a80377d597220791bead2fc23a393ecebcef9e7f5b720d375475b398b1436a6d7a1cb5f6c33c9f3c8f76534672139f56dfbaf8e5b8869785d47ace76a278a7dc78da833aa00f46095af810388184c8e599f413bfe150945135cae16ce223bee01e6448352236f93635b0fcafa0d6f6ba06edb96aa1126a1399204a042f5fc28812a80be4a2f43bd1dda16704c340162ebb3d27842742b27032f4d6c1c477e83b621a6cb7272ce92032067a29578d1141201cf207054eb1db13f2ce2ed713e5781e3a3cbff72709dcb1cccd281bea1440cb0af552fcfe286c85c2a15cea15d7ab0a49c746e72f337cf62687a4b2d923e116bf431029097dcbdc8be2d28fbbdfa8b016d19a972df1ddf657357ef0f0a30fdfb64c946ed47f0e84be5d3836cf5abae00c768e872d7d879372ee7b73d39bd732797cdc8b72b77b67161a39d23fdbf9a2c9239703de54c54dadf671dec2f96c25dc41eab21d7c34f7f63c186e40811d485e8ca6e5ecab7896e27266dbfaecfd0d7ab982acdda9e5f59ca3eb879252c97a44ae05d459829d4dcb289ebf04e46449eb83619d4b12161700415bb7a3cab911de8e5a67279ff662a8b300f8e4a29d8bce06dda4b8c824405b62052e12c2d8e549f1cd3725ea555c6642e8d240f00d6b3daf1f20d35711dfb4cb7ca347625ab1be6abb4729f5ad8b4b472472d32f675b7c05d0e75fb0d76f342db4da453b2886e22b3d7252189afae45e5c62407aaa3074e2b9f214d263fea74cb395321f9a8649c913972ee2c0b964467e6d7e661deb106b0f00eaf9c0d3b29a15f4f95d8f72722d89670f60edba8ac2a970b00e48875e89ae515939932baef82bc55dc39187405f1cc327f3be324c9c2b7db8d8bd46037d8f61176e3f0e4e66f734f49b9826eb7c503721a4864d6cbb528c49957b7a6fb4e8932749fb98e560b4c3fdb9cf48acc13a372eca00dd7c437c1d2e7da9918fb8176c05f3183b55479d92a7699864444a1412f53aebc269209d606e5cbecc6257ff5b732e10b4f2210657d0c93f3d6b5e0de72a12171499605e09e4bd48db1c2f0f5ce162d0cb0a6eb2cac03afe7d655bb15726d9fb5298216560c5193136b65d2f7c3e4791ea4823dba8942ee303331dcc66fc9c022c01eedb2f2ed55da049498a85871ac6d49592e32cd6e07e1b8193e64595407e4afb2dc630208de47cf0fe5a3ed7ad2c86ca5c2d5c8acfdde2255cdc6728841005ccbc0847574b6a5b3d8bcff9e2564923509b785485b09322812aeff27e8dda906dac2acb993fdec48dccf569e0ac76384d243144eaf3ce03b71453a72165a9b71751a2fb7afe079845c91dfd5d7dcff2717534e256d552508844e8f72bf6b327319dd6888883ad74f1721afb39edbca24df4533b8d61d0f82a73e0372dc81b03cd97bb66371fb754ffcc1ace1745645c119b0ddea53348d7c66a3dc449f91317dc60ee2eea9cf3e7cae970e20fd939e4bf0677ba19c60fed6b276a772461bf883ffa7a3e768b514235bee9b12941bcea60bc641bb208a56a898122b0aa10cd696b90a841fdd8cbe4a594b839c9580926c45195ca03d59f1e11f725b0718e2d425ccbd381c0ff5c0838d2ef48d1ed577aad9b64ca381e40ded30eb7d72a086ed836ea9e75a3a601d28b590772f24896839849c1f92d7f2df0ff89eec3c45cf2fab4d08acc4d690e2489c5b0faf4b91765ac3d5dbf0438a608caf6676587b2d65fe0b82718bba24f4b2e8323193a8ca223a91bc72a57457bb18a5077972de33480063510c073b9bad3616e8d84ab66503bb002335ed2d3580802e51a172c1ed4e7800576fae0b8cf1d1c6bd35e84d6860b91f5eadec10380223e586b5724f17981f6e25db3bdd10220aa420403b5b7e0b055d7f3dea2eef6919fc8a2172d1eb9f6a28ccbd932fb746680b196d392614f4abb0c42cd39b3e630c2a67914f05036cabd365009cb63e50ef0b0930d1ac7bdcb6b766e59e6895442eeeef7672a678375dd1206674eb0f668865b4223f25c6d836cf0a2e0317f0629bce4b6a08ca64b78f57f7625006548d538d13d9b1b22eb8eff9cb4b06534bd21734f833722651b36f795e0050aaae8abeb5908c73509ae3cc47f5e24e621f63823bf9827222231b10d3dc1713c80a3288cc486128783f5de0a21106638383de4508ee90728589310df9467e0c3f929c5b965753e4f1301c892c2ac5e9a9bc171989d2f072919148ffb8c06c0ca8595d58c8a3603f98feaa721b339df0f760c656d380287269b84c0b45467dd9836254524e4f4a42c21ae83d3ae6e6f1c1c37988046d9b72b2e8740a3aff59e9ae35fde4cdb113386cdd9dd87fea09a0874208bf914f647262b3cfbdb41da89ab475843b2626919d58b67c50f4cc91edcbc589844cb7d87291336ced55cdefc305155c86a1ae19aa167ade6c3a0cd8defc741111e3a7a5720b248e29d3c2faf8b3e9399136c584b4942d5902a08fd050d59ef9fad8908948575618cf7e18f79f3771d5129eba015656ee842583a57b07a01d82569f6441235ddba5e30457c51c48177ab85549a34d4c1f80e57bcb8f517b07795b100cda57e78254ba749f1940d60baa65ac4e978cbc0c8d4cf43792721e62d7c51bc34c7243d780c66b2310fb641305bc45302906e9b4069f5f92fba08db8d73e18d43e396f3ca0ccf4ee675653f37642586f90a6d4768dd25cc16f2488279c80a540ce420bb71f07642d071ea2437a42864c39303d0e99e3bf911c6c8918a456e4f7ec2668359792a978f3e8342146df281360984de1fa59fa9b2b3745cf31a4b64d43346a69beb363f9cfe90a6440c47031085f0cc86f1868dab5f7e4b2e965df66b372a7db464878ce9d69b52db161f16068e847646fed60d8ebd02d78e20d304402669a6cf69fcdb6bdf1e068785c5408f2471a2a16d3d06c00c5cdb8ea7ce0df5d72543fbbe9b6b88e30c1c26070a84d9483962e71085167299da89641cedbf524725943383f3d808e5eb5f17f22a5c06b8d66a88ca4c9b62457221d4798f86dc66c6abab601b687bdef4ed086f22e59d848afbc46bca1a9900e798b3ee9e8600a020bfabb4c36a57fc936dedfec590ee659ca0deea0c22810323c46185a14fbda689ef28c15bdbfd1424fbb4c720fc47d8dfbf1f8ee3895715e50574a51680ddf72e525b618ad710552442d8609793e664d9ffc40ed699403e0e111724f717b5c5ee908959ac1fcbbeaf6b46642ae17230798539391fdc273c849b33afbbeccd947d02f7fddd770884cb9707aeffe30904847d36b0d4527a38a07226aef25234872219c1a0e7923fc7df8232592a65fde53263963b894646e4356471acb08eb6272f11d932f0502ffdba530916d8bd38b191d254e114d14e6c46ce1faf27a1ce6721ea3bbd325c276fe0fdaa78c883f13a392b6df5c7c3e7c79d61253e4297be6729fc76a7333b57d5554fa90d820f9de176512ec108d079da1af5241493abe30377a627a30309a9e76ce1ad80a26c1f1de43a5de5ae59cc10b42864857091e5940de3ad18f43b60ec4618414407ff191cb29e43bad3b66766bf48f9e44bd6db8620d9a92a7c2a0dd0a00eb76c293a4c305c6cc24ebb9e3417e6d3f8d8e89c4ed72da32367773b36e19d91bb109dae8479b78c619e65f9c2a51261987b92cc01b7202fee40e8f1d2e42ddb9383c0e46ed992c4207d538ca75264b119a28812071725c7ca73dc5f50616e6962828aa6eddb167c20b2e22de6ef2066a7fd0e52c4872a2b41c8de0e3188dcd6914f48ea796ce165abb5984cde6b18343eda680e44f54b2c1e657712a4ec5056b4a313c9cc6c2aabeda5993b642bab687a1bf720da4507726afe70efef929fd9439496b417e1d78a8b8ebf0e6016f4fbbef97f7dcbd72dd16f7d75d17b3a76919daeaf8168760934235747fc3f4a5c099c0b605e7dc72a307cc9aac8f5fdad390dcfeb9d042d812b31805a4cc46e3dcc67a2dae8bc21f3c80d298304b9be4f527201cf0d355f72e02e27c579d0d555d3e6ad02823bd6170c3fb257e9ab1b3779f465bf6d2d8595a6a46f84830fed23256aedcd043fb72295fc6bc8755eb87662f52941cc0c8ae25028987e6e20e8cde0ab43ab95271729590002ba5a877313dd9515cdbda87448c1939cba57dc5a34820803ed8f9b172430e9e69bc84bf33d20060858e1c8b272767666b428d17a7c376d87a43712672144931bd0859d509198642a01b4a0b24d47d8b3c9c94544f77441450e950080c7e9f280c33a771496ad3dc1bc61a2c6889b4d1b6d8569db0c4546e9c4a8d6c7237dbda8fa5c9aecb890761aa55afc1b4bdaa87bd5c98dc36431f27ad65901759457c0529b83a000d5350b5edf531b4320e4b1eed81d81cbbe0e9f20b6c2bff666c341ea5b1c24e92978b91f6bab4a2765857b5c5894669f8e9bf4e2217e99b4e339f54532358f075f67fb9da9708a1bbc2438490d4197bef40317c9dc1e0c71af4477dae5c4fe83005cdf0006d666a2dba5da3c9644efde612b72d1cde06f24993742f6fc451880f705fc976f52e1ec76ee6f2301ea5a43d956280ca8f69c67217839b7cfc33646678ec144d6c5b41fcff4385040cc1cd488232971aff9109722e440bb4cc58d1230e013b5ada56d6a322cfb2c77499622bb3593289c43cc13d26fbdb2a15e87a3887c338df18922038daa7be0a5f9bdd68b71b2250386f9f72cd9e5593722e594d9476c9ddefd1fbb7049a525d702752663518b1df9b766d72cde6f99fa2e2840b89033889d609c27a6e7c2fceaffc68d0b83df2ccf0e63700723c81bac190d12e08044df91f51462426164be06884edd3c2333a029b30ab72adb4e083eedaa9acb3932fffddc04e6b9731482bf397144aa63bd325f08fad720726014e8c637ef76b06e8237730f0d22491addabe7c508d95228c94a59782726662e14833a91bbb6e65aaa269db43b4e4a80609ffb6090343b2eb575421ae0c0b00deda57b8c689ba81e06e78b9558ef6e4fc2a790d9d8cdd860086a023257251f0599acfbc3f2a000e892991a99f5d568e5077cb8fb50ab309da3c0345fc5ddb0cc335eceff7f100ba7e696070b023663ac3ebce0e9365ef963b99005419721ff93fcb8f422743fc508f955780ca7eb98857c478f14f2d0605333f98685943677145b9e12e6d9200198d847a41b59050b9a7e346491d4237e5de07580a8d7273ebe5a9634b21cf032b794e5109feb9d918a60d5cbbac54ded79d303c09896c148a9bfe9a9d502c844e44027d1dcbb854435971ae2d552087cd194c2256e072f977e2906714d5318d2a04c4f6d93ddcc62dd191147edc297a00d6d6fd0f117246373ebb2510bc68595e1682f20c8f0739e093dc1d908099349c6b6c826b51314a5d147ba909f0c5c586758c795795c1066481e69c7fea52b627f639465eec3cde3bd62ac7655697d49fa8b1b7d8c554d35df295ca67bf7e981016656c456f7218fd77c0d44c8afc5785a224a870e0ff63a13f12e1cc31446d3f20c755d450722a5d3a813358467dba6d17f9b25f4b99d162a32cbaa1309b0f999bf8d9b79d4871c3805d2768d7bca6183eb6eafa614934b736252f47013162e52147c851496749fcad285c82bbe26c0e87677f9c6a1bad0e560dffa83dac9c6b7c41b18ea772233d85d5172ad99aed8dec51da63519deccfdfb20869e11e5ac3fa3c902af6721e10cfa80abdd12c15081cbfc7efd6e3a5744327603060bac7267c3eb7b3a272b6ef1b87ac0c0ba3a21e866ac76eadc25d1c88ff6e23a054ae10f4b0d790267207fe171ad07c0ea92b84da0c1fdca014d73e4db8c425d22650b1e7ff124798725d5c82d39087dde22e5a52e2238a73a4db81a93db783afd29d688abade90ce7200c705be88f1e183c271bc3c6ffa7d25d55b3fbe3940b9d4619a60bf235231512a89e45cefc167e4b0feaa1e07a2541b21851d35cf4d484f4170602093190a1637e6796e4a528e1b36505fc44e535267cbd3e22da64fcc98da1534a15a9e76600f6f32e64c92d06a5ff95327b60ebfe49a48c7dc13cd51a757196581bbafa97255f3eba319948c22054dea99b994b8ef513ecb8ac3ffe080f1420eac639f2d1c0b04ebe6cea1656d64d81bd463430803ed54efa51cae566c912e7bb4fd19d61bde25eab7aa545a449f0bced53d01e2aef0d06ed0d3cd350b725ef402adbfe7726067e4d5edfc0180968499f7b3f1afdddfdec5790c4886167fcd33ce35fe7b722d30827f16c1b5ff677b2dbd07acd6bdc94fa2886c808bd8af4263e8192e9f28978e414cacf0e51ed4a0c18ce6f5dba54e4e634aca36c08cbd3cbbf062c79172ab167a0fe515cb7b53affc805a7efc01ef12dec02bfc0091770d8cbe9e579072696c2b5f48c3f23bf21784c4e590d104ab315e6bede11df8e49e4f3744d29572a17b44016135cc380e67d557aad3669f8b8ffccaabf21b5039afbf1ed4e0cb72a95a4a436f6d42b08e8c742d0d0800f0f1e576b0b3d79e67626f604a6a608f10921a691daa881b60c41665ca48f8b25527c700c84bed83ffe16bcf6e0ecaeb72b4068f87c9b4bc779dca27bca345b7b145303396d29646f98e4b3733393041721738554f4fa47c2a2bbbc223f404e5d3c802f2be093613f86b1116c25e0943723ed7542e434a892518a86d001263c240c6acde20c22af3f4349b3cab6a70c65a3896e1871d636a5e4328a682e74e00d9970711f314c67b00a39d2fa13102eb727b0e85b70ad71d91c26cfccef12a5180db1840d923adaffda9b3ac6c12bb9472b635f17b09e36c544d3c80d7941a6dd3a42254c44d6f446fc770341b0b1bea6ca51613c4c10c8e5d84400d2704a24cce1bdcf864ba7efc54249de38efc26b072fc2cab84fb69c12ac80d111dec0b7e5c9fdcd37ffad749c4831556f7702f0972458cf74ccf60b1890db3e063a2e934561cb30ddffcfdd61f7b862e6e0445bf72c370ce6a22f1d2131bd6a9c41525d2cd2677bc71d5377159e25976bfa3f7b872015cf2f51599dfa688f20ab4570fd098ffbec8b40de04bc1bedf31cf4524c77225c9907ee7f7edc627985e08f0d84ed6da63062efacf8007878fa5d2c67aa506464d2f2c8851203fc9c2a21ded77251c58b790a99d0cc056d2bb237ea5ecdc7299163fce82a0198406ba0ef907702f618861db329175f0f03244d4018414a17265c4ff50c7cfd6e8199bbfe4c80856066c9a26500893401854b90a7a3c1c0c63b13303a9da06a1ef572d77503840ccf1b512f2bc06e0646dd20546771d01dd32a957b2635309ec69f77e015675a4b4f8355caba7c8a755da0444fac0a572c86b7b77004aaa8f908e8ab4a082ccbc79423bba7727fa5d64e78452adc3f52e3965025b1a851b6cfc05484914c52acdcbeb7beb2d54967bbde5a5377f4dcfc2ed5d216835741cea628779e47ee53644cbca6e346b9962f6307a81fc9d04b742cf14aae7a98b9db04bf1ac7bf1d18030b6cb4abb5f932908bf8c3adfbe669ba204727c72dc6d0f7776684f5af17c3240a0448e39132205d649469c9ad104f786fd72967a8d29c4cb3b7aed2abe58be5a6a9f3270c1f18f4892442fb28ed1568ecf4c9381672ce011489307b9b6cf90a156d1b4421134a24bbf05dc7e3f44bd5b5672c333f3f4354bc09f59d2c4a2b14fee5e2bb8d5e74e3224b5e64e0240dc67f97294d1f5e278544084d22f8750d481065adb963e2102db938953ff6aa5d21cdf6f8d20d62de155babc0667466f69dad96de6938ea105ce87924afaf34adc718a7202879f9628d4a14ee0fdb0a66870a742577b850a94d87cababe948d88a324272d237ca3f610d753941176a73bff00a3f5d985e53838561c913a0d06b34d0943d4e815b6b1b2c881eb31aa2d04ceb7beec52621762f2f599049ebf3a5c28c9801d0b6a1d5d3da24e0c49d02f8c69819e1a10c2c1f9338ac96db4723782d51707248770aed811709ca04e2b6697447f11661349469e400266e5d2a3c610df78672de31782a721fc53ff6dcb6c441b4046b9bf7fa4872331ba8c51351384b990532d652864a03a03bd9aea02668005e50de3ce4a9d5b0af05afb75bf1a9c138977209ad583de1cddd329f0d97ef2ce5b0d5d19d7fe50e66186438cee583d209a972383f69b3ab102a6025a561c36c0cdadd6aa1a226fc87270acec45531ee5beb722e834d0d86e00e5b9e2738cf4bf5b254db2f6a306b5706786978e115cacbbd179b01857c3f95cc3a72705c4651180e072c03f74621b52c64ed16fce750ce9a7205e5fd39548746397cfe0fefe5283333ef8a8c00c1b319af82854362e5414d6759851b52906e037400875852225f7b7ab7ac2ff224cbac4ca0a45ade79f5ee72c5c5787964f0eb9adce096d976426849c3491a0bf78892d907f46b1c973d1e72a8cb360d83507906650e9479fa00947365471cba057009c4fb1f5073dccfd2723667caf4c7d9d7b2631e13faccc884289a69744206e5ba04e500feaed17a15728f6144f84a580eb4fcd29731804809a5d28a7314845a8b45c3be072a4408b827d6dc9dc9eae5b29519050196c22c0bf9326b2c08b08e8af43fd609ac08ab975214c432ef0567b41f8519baa8cc233086aeccf3ca90937f98a3543bcae248595c18488531121ad8e837bf41f7f66659f7b66e2925cf3c1626e617278c46129872743393fcf24c9dbc70e49ea99b1b4722c2576ba873f44ee461e597272c21f6720c6d062cb7194e6ac78f6ee4d7e2bfabcefa8ade60f84a8e62d1458e96dc6c72542dff7a2de07e2d68e351983a80747829fcc754f9c41a789f71576003244d727d1dd83064e01c31b6a98b958a7f3012a28cdb8066522d4d055f329237b55b72b9b9fb8a14b3a3d7a0458b0c00f4e0d8a94aaff0761e557ca9719b5910962672377181311796f6d53e2b45f402f6f3f28a8f785d21edf248544fd8e5f0cf2349f0f5d2102513e5cf3d33646eb4c2f1ba5b1f433af2f4bea4358e804e9660be72fb20c64746c17f691924db7cb98a3dde6b729225480c86b60536fef86bd53e4b179e7b8ec086d51ff82066e0e0fa9223bf6f98433b2ed0eb651e1d846af78f30d5de6f99483872935993b677cb25fd5244245a90b4d1e62a95cf103dfece95729fdd473e3656329a7ab4a7b9eeb8c4e304ebbe4036c97a58977ceb6ce8f16b725a7b55940b2d210c7bd7df30a5cdd09cb5f23d0b39acf28bc57671e14d532f72a83b131c67004b0a0630668fce45741da9fa22d9c642d2d40b3a9581b481a630a075020f7991368661df03c1775cfd78b72061681385f67f235658bf4f3842706238ebc6506ba8381b63abd8b82a79bd3a5d37adf3e116348735c10d93b796724239a7163f3447be060542686f79603030e4e1599beacc6559b47c4aceaa5f243b597f0c478b465ac746817ea980d503f3d9dc1d703314f4e96757594ed0f27224cf1185389f20cbdc5edce41679570748a22796455bb1f89036b4b184f94368988c34f6890b093db4561c3c79c516a374767aa6b127747610a62849afb648724518cfd6827c29f899e734a21905e7e885e77b1b3c7cb29c72c847b1fe5bee72a66ad0ef756d507e2429625167f885921fbc35e7fa7c6913e66c89e997cb69728e15fe1091d41b270cd54edb7c57da0e609055784b324343b3d7e679e76731178c23c14bc611e1c8f14dad1b017204ccbbd4202f516066ff34dc6a128b859372dc6a26d533ba8ad4126cb3f2cd6ed502c1c522b4d2ad888411b91d8a91303372934b16268927ac60d174a628f41d449970cc2fbebf208337c91ecf23691c57723685b1658a1181d056a679e7b9b14ed602c6cd57ae9ce366dc2ab3a9c178ac7289c9b7264b4b18418931ee0ceddd5ffc3c74d196cdae255f1c704cf27c6f867272614ead01fe66bca47046a858febd61e31f7ca92a9d57ba8d32a7a0a48b1172aa4206e7c705eb9e5dc1266b0fd36d55120c7db851b75f6a75850748102a04450314d012b9644727fd5cc07da2478e51f046bf6a1526cb79bfd0ac49aeb75e72de4c525ae6dda8d835e70a620afb5c303d544afbbc136d0e56b1ed9374fb414f8580bdb07e1237da10647415aa9781e705a9ef88a65dee2d72c4762f399b672f0e21cbcfb0b06533ab6f134328cab8515481ae6a43d2f620d74a2c8c23142634f83be715ccc9fe9f62af2e89ec704338ce6062df159f77f9d72d256868c14c72397eb91f6f5a01d51bbe895a925d4244882358f95edcb3e84858874a1dd45e72c1eadb8b99abecc897428408f11a1e96e25a2268b30ddbe0be6af09fb4710c72ef58b1bfe1b55bc3938796c2ffec93edb8f9c96fe22337bababe656b64e75172aaf2f9ebdf12d59f509bbd035a9b628613371002cc1c63c66077122632b3bd39a50fa4050b89fc4308d5cea7d2fbd9e562fcd48b63cec865289f3f3ea31b7772837c59cf65d67cd75b6d1066f8197e21e0ec30e565fb8007bc96c6ffdc9e90725cb94a649f97ad4d1f0802bdff9720ee2e4534a99288739d2b9fb9198bb01148c59794308885f260843a5fe3dce3e452c066c022b09e550f32fb98bf7dff29053834eada0b343fab1b28ef9178850c28db08748a76d6007a37808cd96d7ca149e556fdd4607b3f6dc5b5d809435f2b56982e4c31f9004ae99a2680937f217172b814954dd1bbadfdb24c7b07dac3d78da5b3a89a47029a1fba209642b48bbc695b7a9852f091323f7b4abc9352b24b8b892c40ffc8e1396b6b4c008c7fd19b232f9fb2ed6930b2d837e950f254b17df810ae9f6bd1b5794b6b02893eae9f5f5d42e729fe77cafdea99b59ca998d45552e67b227967562713842e535f32c5c37233c956b5aa40c300a97ce030a379e6b477463553b07527c3cbf9825d5944a372849d7711950cae06ac1a995959668e8c12b762a9766910d66244c7f292659d514b00fe5bac906bfa2592492f98b73ef49b9b2515a4fc2b5fc1b405b724a1e172e4831fe90fa10a35c972c8a8df887771767dc7751ce8b36b81b24e784fe8481d4caa3c94a49ccc1478836721a0074880dd016fc534bee068eb9496737e329772007e99fc5623df716fddd7d7545f09d8d3dc39c91afff86121bd00f07903ea482f8568448b4c461ab924a47a45caca96cc74e173a4e3f207252726e36aca605f442e14a8639a2ecaa6a6cb56ccfb0bd90f294808222b6aaf5555c71d0e034b2f6451aea5d83d6b3953f1b4040daecdb3baa68b8f8b46510b0e78d0afde42621fe9269849df6ed345316fdee0917ebb21403acf5b9e7bd7caa6e65a93a58dd7722470dc856e831ee1ef6e5a23753679dc4f59c317f7c6938e263b7b8a9446bf7211f6c89b78bcd7663e891014cdfa2457fd57967c975f30adf3130f7ddf6ca22e4c34449e23ed3c8f5ea3d233e9f88f5a3c64dbe65d0788f331a1b5f6a83f6d40b4adeafaaa95aae79d79354d83ba50ec22e183dc99ab258f96a453f7c32f19680dd9e364f31444b584f602341cae6f0ebcd002e3a1d07790f10e1e6fcf399672aafbcbbbfe8471b9b7ee733f9ba3f21234b9e0529545e87c38abcc8620c6cf726031168fc57fe4e56a8aa9706fac86745e80aaf9f39c7eefa64dd3c917702839c137588a4d1bec2cfeb84e10eb54309773d57ff73735dfdf583f31a7aae9ea72ad5e1f9592ecdd970b0c87305726c82f4c43e6b7f5a08202de11cfc08f57ce2d688d77695826977d58eccf6b3dadd2f8d0f7401fdb8f155be0926ab273b09f12c74a6d771823099fdcfef1b6a483355cb4bb16f9ab81a605e8c82c65a3e8c77290f67ca59264395bd5c2b629a0d3d5777fb7d2540c645d8fd229f3d672a97d7251335f47dad1f3a48a216dc3d3542d369730330d571c5d8cd8f07d8ec06c52724a0bb3605fc353bab155b99613c123cf489c3c7e77368de681f62e34f9b495644ef3242dcf1a1669e5dfcef51dce52ca4f30093d37a1f154d747ad4eb2f69972292eb82405a9ba7c94c45452e17bc80ca2bd8a8f2a777b5ab6e50ed3b20b6c72e2d491e826598a81b1f67853b6a6f703e6d5d3a54440205e321740b99529e3721084324e671f6a6b4b990e15373c55c52c68621634c9c3750940f693ea48e138f730d5a47b2582313a0752906de35c7843e8c3cf9aad72c4f031fc62bf6caf722856f276e1ed51447aabbda0a8f1ee673c7a5a9d9da26009e0d53862b55d23725771d00cfccb12acd1f772bdf165f02025dd8e39787588a96a3df5842f09ef6ee1c3f3b59aebc57daacc76296d9e8823dad2eb12bef541d76a7f3a8f16a84a1f51b389b35df514d4491090737d64c51f3091e724dec7f19ba17a327d91fea972dbb564ea7614b1c7ee8cf2dbf436595630c6c1dab84fff5d195175e0ecd2ae54882eb2ff81846b279c840994b0501f0c6ea9608ea2bdd7e42ce15e10db07977257c23c3f750b89da9bf878f87776d6aff14f91ac4d45c9f371922aa4e4167572ed4b982c0fc837c472bb9209fc6ebfde168a6fbe16acfa9fc51f05da6e7dc0680ed5e85f51de2f1275d8ef3390931a3e2badd2e20eb490b8535bb03bd5aeb34043fca7b6700b833d1e8c07c799bd0893925ffb3771c78e4f56ccbfbaa8e26f72bdab2592bd0272d7c8368e618f933cf19f461274124a01ead9dcfc692db37b051aaa264bd493b38308a70ffb8d8a2da5b337e010411046fbced2240c3d14567232d06bb723c522a65e59659918a384a439e9d076ec214aeabaa5d01dca2e0a727d57a9fe953050c20d9b2cbe94a784ef356f3c178e0cf730043c5d6818aa1222043aca51e659d62cdb93fbca5e9e7174c0b44fe2770f80a638a76173eef799729838788ffedac1831554cb06566eebb4e5eea0fdfd98e9c333a2e05e6e7243531bfa7e8e3d4250c057a9c011a54d38e4e19efd1a923d885665087ddb9285fe723ea28bb5854f9394a6c66e5c94d816476972b494c9b2a4ca2a8230ffad2cda01ea1a04c947adda4d1cb5ce8d518be89f4688eec3367261f1ab0f41a31c56de11a56cf07bed5f9d89c3db68eb093ca7f3c61986c32f65cfb1ace0553e718012722a86f5496b9674e85f91064d91ee55fb813dbee06dadce2bd471a624d3bcde44e5d5f0bef7a52aaec257a24053e61c8b69af8570d7d208e37ec09ded500c5015cfe5fa492458cfc1e180ee2c2bb9eec62daab78fd0fefa84569e76b6c6a28372db81885f5cba2f2048953a3d14168ee8b45b7fc4112119fbf19dfcbf57223f722e3ad64fe85f195ff1eea1844478c99b44fe9dcfe0b001aff48e731077a7d8708fe6261e2fe91012656561d9b27810a7ca55d232eb883d717fad034c83c6822c6c4523c525f8510b659f679accac938eda8de9eb7cdfb8a0903d12fb3e206272bb1448d5a39fd9551c43135e1c2fdaade5a37a1420c2d778eec1df897d081a727acf4febc0180acb547a843ebb1a6349f7f28c1e615865523e98e258120e505fa81842317ba1494b34cb8925b0888f317d9d54b958382dcae45b742d95404f547d9a0df65d8e8505bf7da57725e93fe866e75035d7e5528135b1ff3a0e884b721146e21de8f720aa54e9881ed4a16e6dfe79be813af37efb2f2dac314a36487269661464252313e2cc24ad088754823a72c3a12334a0b45f4ed18862388a7972d914ecc62dc8f7d8277605f1099e30dc303a3459093bab4ebd28fd9965c86d7239c94a14c64d7419b7ec6790814e53cc4bc061671640be5e88192d13ce486b6c180a13471808cf179d13fa34e1260be5b90dc8579283773139a0194b2bbdb215eb091b8a07f3d206ff3b43bf2e7a8ee79fbd12550d51d8c8c6d12bf5b2165a1bcb5e9b4548c0952b4da140b7caea8afddcf6aa3676e82e27c66e3cbe2999137297d37c03740de8775c9347ebb6f1fc669b4158e2a197375dc9fb9ba94693d2491a74218a977e8a93fd817d6d6b435e118bcba7fb9e8af3a380e24cd629a3ed35c2330f6379dd8f15ff8605f7cf4e23ebead06248b851896affd3399ec6ea3d72ef3b67d9483c8646889edd8779803d14848d630850d0f68588ceb28465ed8e5cfc59ef2bb06833e44136b0bd8d600474346c75a73823f6511f6838515c55c9633b5897d19cb31bcb215d53d38fa8bdc2d32873ace6aff86a5c9098618970d4082217768046f8ec9dee2033db1f07b860c6557dc282e61a68898b093c9b891d72c24b062ff238da33ac0f3ec9432b4439895374235db339206b642b6e48069937e957d192f14fe929660c5f39b57843795a4b2ab9eb86bb46844287c8ee343872ddb324bb86a79bce165706d196d22cdefa867a84edc97bd60faccaadbfa4946f8571cc6839e1377109320c5206d2c3677351874fa97eec8261801ff24be7b735948e1f22ae0d0021932eb38f02357007fbd0e00d56a323fce523d547dd7ac272f39fea1df2727f5a6b7226f0f76b20501e43a3b17d39a187cbb39ae64b194247b45acdb15433b8306c92089e5cbe412336488a9bdccf25a7da91176798c63672eed341d6ca4e65041fef623702244d6c5e396e075db431d29602d23677d19a5c3ec3965fcefeba091f271eb5582efc5f1352f00486f3675c3b8503372202242d0e4a4fcb962e960d83f18d10b131933e98f163063cd71fc1edc843e718fcec723c9be6aced8d9276cef79c8a2e95cb79c349a5c526957fd6a941fc391d6d9872b65eca3e4bef18231c998acc62ebaf094e9b2dbdbe76678a68127e73ae24e572ddde623fc12b66d6bc7f10a7edfd122663bccc2b57a6a48ddc917a0bf31afd352c35b9d6d42b1336cbc7a36a212858025154a88edf89bbb26b5a80b7aeb3001b976724878ab1023e5d7fb829203707c5295d658174ce00d8be9cba9c01a5f67272493addc955cc3e105e9978c0b294a1c823d95a52295ca2cc83a085835f271861cb6a8e153112fc8f17bdf6d2b29a21cce3aed07fd56c0d47adc3335877830302bddbbee43d7e25829f9817808301bf70d7f2f3e155cfb247ac0478dcec5b725e446d4b9a0abb567cb60e7030ddd933ac80b3d6fb10c5657530505b9b2744728d728b583ce7cafedf7a4e6a1eb11282ef2d00df556dcbefd5ad299d7b87103ad85ae893205b41a68a7ad6f37ebbd38f3769da3a7c7bf21dd887788c83b86a721daf0798cdfedbbbbb19e40a729bf5e5ebc1d790ff68c326a19f7ac4e45b36720264c22c17764ee03708061ad4b142c6fa0f0270e72f6afd2e3d1eaa62774a008fbc9ae0719e2bde40b2fc251c11b39d961a9e2c592258d0dd3eabd20da3bc3d13a02c77544675e06c9256f5916815448e91fe5deea18f70144fc6e30664535b65eadd57cdd43a6e1621bdeaf8a9b89b3a2a78f85f68fc4cb2ce099dde2de05d9eff090f0e5d6950222f3e1d02a85ed082f371cbb9ad5188a4296255420fb9505b30b66ecc2e765da3253905a04d1138440bce9ee4eaf0fc31f686589328a32b13073b2e4186e0aefe13dbd606fd1bb0e58dcad637624001c127065829174645a420c4221c27731f3228d6138d94f88cac5e6d6bb8a13020b123ae97cd5b5f4e9a20c2caceb159b54e7cd821d2a65d1a45278376ade5d90e60469c9417e76c72f3e0e3722c50b61b6a4df29a3c8aecf2fb13cfc56471c1bd9bfb4a9a9207a468e1110960257251c2146c39b85a282b459fc83516181b1e65a37c18d12445ad66f051f84c05d751a76749542aa3971b18028aa2fe435a9049fa911124e36ba107539ad21e7c08d861295c338bd8b2955f455f24079fb5a4a4acdfde990d201f4e5b64ef00a52a65c029e40e3397adc851b12963e3e8e5051115c22475dd5854725a25d0dc63d39a9277c03edd3c05996425b99c90ab915e336da596ff93a0cf390d22b5cb7ad7de688c83ce59e96af3f1ebe6571134e1bf6cc51c9423c0999b7279cede5719c5854cada50ba4559bf1cd1f0f4d13935d35cd23a60a36ed74ba33a7cdfe9c7413e337bb51808360bb771092ca381b8638c15fe3c8e1271586117274df59525a9acb699a99c6955354b87620e5e3603b6fb4dc3eb189496f2d2372c197b96aa35906af63afc6cf2939f20cebff7991026494fe04d59828a1e1fe72f079d5cf385997f937d791355e86fc69e103d099118a4eb38fa4e3232c75cf4d864c9e361fcd8376360df24f0b2e8020b00973a19a2c99a2c221e3e96cd39572f2aa913a5c039e0216038fed3221b5f10fe1652350039a676709781b05d8a42c0e70bff471630fc9825a5ac725a33f81a3bcb2e1fb9c77b5891e1676b102f3336e4b0d80f2a95815334a2fe7abda93ac26a5e32883c7372ecb45c788ee2605720b93e8891f3e018244e77891c326e339c5f4edea5b66cbb0fe141277d3b08f2f689f7f4faf8be68dbebb60526bfb132055f6e765c74005edbf49e6f39cc3790e06c6de5711369c4e85cf407ab68002cdf0ad48bef7cea323f73a31ed14c5db72cbb68ed65259d206fcefe9a1342b5a522d8116ffc3f895a068629e5865a0a9721097cea2cee51786c88aa89fb3c17f82bde8b6b596549c25dfd38cd71c8a3e72ae2d2608ed70a6ec42c1a17fe4691f6e773ae502e7ee38feafe4f13b40d5ac50ca0af067143566a211ba5a7968de1fd59f5902d4d613b604e6b1827498f07558e38b4c845b6ac549f8ff6fdd21b1a7ca2a06474cd658e72d199189b9d8f4bf6245e373c05add80d37ccac001e7cd2173ab79dc625f0bb669d8d7b4e38358bb6421ace6679e479472bac4745db20250ca45297409aa3ebc503eba6bbaa16b64232be9c0015bf6365dad1b9e8cd7141c00207bb7cf6acea66fed9075d9818ec5729fee8d446f671f99b009a917cb527edb3de2f989217a2d6ce246ff09d290391d2eaf2287ede7ace28714bd5ede594c3c82d798d8488e606129247dc9d27e6401882648db79f66dec39843091e50c0d9811d1caa32010fbdc1e5506d14a2aa74f6b58365299dee4d95830e4301599c5530e5fc84e1c8765a68bf9c301353c06617c785bd4abea553b1b37a04862365c98ada880fbe72ec51d47a72de378dfa772920528961df16314f4f80201d3dd8269e9185337c1fb6a1c6dd30bc477734872a3f866cd62449e67f12876b21ae53eab4cbc11faafcf247b9ad42540dfe49e72f3d849ba7400e70eda23dd7bd43cf80d3d636f6b8505cccd611a5041c643297217d6c8d582b99a040286dcc159d1eb5962c691a5a0ebfa86d5703c242fa06472d27959c8c5bccbb0604d7a168cdc5a915183344ccd63b76da91438c7477888726f1eb981efb8c9f722525a06f7d9d29584bba7f476c16f9d50febd7cec826c3f59f742095ebbffa54cdaf7bcddded4475f2fd7f0e7cb28e12f50d6c9d2e3690fde79c6813cfdb56e0a0f4aa9e286fde0a9d299b36f41b0b1e463f4747d3d7072254eefe507207aa73fb47169445fbee5593034ae37618f8ecf66918268409e441dbb794df56ac5a5cb3b0b296812210010f444c972ef38741f5499bc4c245b72d6ccbe28c8dffb2d52016f534ac082926510121050964e28ec1b0fa2a0f5c1728faf701d309d8fd15632717231e499171e3485246633ed7ee9164ccd73401172c525078966d3056501a0bd01e9e241afaa78e409c778e2cf3a6a145410279b72f54b523b36dfce8f6659f33f3c6ef020ea492feafe5fbe4286464c827d5a5f60801fbb5dec766feaf896813e62c80097ae63ec410be2ba2ede17790794beb57212fcf7157ceee1a4f8424f4490d5df023ed9944d3249e62d949a006a40b60a14ff0fcff71cb495f142ebacd2bfade5d8c77cc2259c0af8fc26f656223fb5b060b334b229f798d3154189fe0bc3f1691442f409db67cda78c4c0fa769f4ddf172285d40854e251d83c879ffe90214a15375e652e4af33d221203266c64502b60d1408e448e416b5333be77b3495e96dbc6a395bd93d05e21110359f6f400ece159d63031326bef1be04eaef5524a67712fef9fa86f9f11977daf94f9cfba1ed0a872171922ad74fa8fb758e7582b24f80d3747025c78e09ed5210565dddfc3772a424f00401cc92a5d5222834b8e4cb75989d5e842ef15281ab53136e528a98017f76d3b1caa5d74701e7be3b8b2333ca3c9edb7a7b3a7e4ab6c9392558fcfa4d6d54f0cc1e42795ee7d3dd40d9f1e57704ec4392171f5d134325539f1ec0fa29b952337d2203b6c6467eb27bc5b9aa44b2a6db963d619a09d32ceb84b061a07292fc2aeaa018eaa92e632adbd890c2bbd60dc48be6608d98c2c69840f090d572dbdea852c25c2b58008f5fa9941d566c2a036440e9c7ddd9b04952b75033d75370eedf7603c2b74fd73811150919cf0595a8b9d12c6bf05d0580c9983f8d34092d6285684bbfb4b9a1a20d6aa7ae21ada0e29bb217aa35ad55c4eea74bc268724b1aa0a2a7d8bc4ea68d557f6a6c67af222b023ae3d8071e50b0ad5ae0908e10afb8a4413bd63c1a116c8d1eb995a368892905e5d96eb682aa759308723d6672457f779300f88373632a06b892f88c850e0881fc65894b0874733f8f50446372a2b97594a58057c23fe2120c25bbbc5253223251b4d683ad7dfe236dc692646e1c746b11107f33a5b2ac26466b152ff3400ecbad0e283f7c2c349f68a1689a72f0e9f6a9019b1acd1ca720fb81fbb8088460254257d8e2e702799aaf027da419a00bc47be567bd6c503a0c4aca7e09a5559149824807e9c9daaa4b7671a3267213c4fdacfc27470da9ce9649edc699dac9ad2c2a8de5ee8e16fac39bac2b103607cc7dc01e86417965ac7b48c4118410763ffde2c9529d0f5963e7f3fd08c52db8f9431c4fb491acc219b2069d381bf999fc0292ae42d23437d31dcf0902215ec2e685bc9a58a4b888c1aadde08eae9e6139ada1edb9cf4241d1cdfa40075739357febb716d58657ce5a9e19ed098cd3030c2350d088aa7f4042a54ad04a3a321c42e1a879632776bfedf2e35410d0c7f77bcc27d406b9a216b186ee36d0e072e6ed3dbe22a1cea66756e8afe7f84a8480f177aaa8cd533ea6d26702d4550f65bf2ae9ec5fee34b021231fc9f8a3ec47fd805e9daac96ebfb2b371f07a3562721fb03784d872182259e5c04a0d1978ea37d1868e119cc9ec610829fcb933be72f2953cd1e2070ceebdba4db4e09c006d477396353244537c2a60c9811f38c172df6f0a294463060d19c0cb170a4ab665c9b4f74765412c5eb814fa2095dcea56a8abe936b407d900e20e711fdfa8e05bb26b3ff7ae94ea515a892f0b216a0a72b1bfeea5497ccf29ed289b10f07191ad0558ffbce4a723c5a1059a4835bdca366d0a9ce0d43e6ee8ec5b89b30cf711cbe1651de0822c35e8aa1ed38e3a1f550f04772f7d2ceb211b1d7d5ac4ae12043fde28a3c0f73bc1dd0aa9a23eca5207417197a88a68e44b6f06a8c831147b95c64b849f377b7cd3ab4866d430f117f3725ff138d386626209191b0e33894d0d25d68d9d95afdfafcf8dc6a15046669d723ed880d13bf299c4a21dfc514bbe44f1f8e4b5b8b95d454e55fe928d98ec2b726bfa57f0adc67af5bbbe97a23b779d3a40b31acd95d29123145953e648897301ba08babbceefefb6fc828d9d4534a8c3b5d52483aa5a7030726383f689233872c5fb66f26aed2d0c874b8f3f71b3804b71670b1e8d7a176637c0bf6a75bcb13b03edf9d85ade8fc332b84d46bbb578c29a433fc7ca9f0f9bc651dc2e82687f725cd1302cdccb8b3ebf999ddcd22fce8b25cca2a70eea27825162dddb0624264302e07688d89a453af3dda5164d920513dc3676c10d3ee9e1c1dd03398039b77207c8695c8471268d2e66ee69225d52ba0262fc1e1efd25dadfb27ce88b8a702f22ba1c2ddb6d935787e97a90e0aff11e96335abdfc5c640474d02af3e497137246a8f2fe2a1e0eece6d38dac1c625821ad23ea68d3f0d19828e66f8516ca397222a535a12c82a39c2b52e7a9fe085194a1062d9fa68f2686780ec7ff74ccdc725d11322b0a6c51d19d32ee47fbce5c45589d604099f82821d8e0f1a341073c7203d2624a2ce3d9695356949f4ea955a0dcdaf916188b7b69184f16aeaf2db972d8b0b997bc02393e1e79e9168e3926304bc02938c96f4f0aa94452710d6c1472828d378a29980aadab75fdb3aa0d0be5a47b62bd38a78e3686aece3555664672e66ff7ab39a698417d623457b2e13165324bcc2e3ee22049c0066505a7f0cc722caedec40bb261f02de1e652bf42dd83132ee76468860f6e6f5e2e7d9d35db5e6639d018aebf4e038a24ed78fd3e97db92da7d22ea51aaba22e51201486d0b72729e1bcbc709e90ee0deb646a29a67cc4bed9d25c51b67b88cb726e0ca78250d2ef312dd438437564d923fe08e2e73616367dfc09edc8fd80d16d3cb2ad500226acba5b98f22737c8eda827f189dc895879bdd61bca34ce1785141e8f2e4a4724a7a2877745d7bc569b8aecd37e1070448ef33cf0ca9a47d5beb22ff7eb0b3641f4ab2637334ca45d0ce3ced3b71763d0c505004a5370d1df2a67a7e76a31572fd0fac676a4e7cfe17f4591e9eb9e3fd4b4d2b5b4d702cac64b793ae4a960d72853de356136a0e31a698ca73a72dbfee52cbd23d6bc5b2ecdd95570a242842722661e62ad1eb16e5aa9f42d8bf928858e204cf2b5beef04a0a1962d9f0df8657957354a9cfd5a3362032889ff0b08c3dba82b2d746df12f93a379d2416642472ead317897c5b487f5d53cbefd9d68f178c925a782f4f702aa39ab536285eaa7279f79ba0db8958dfadcdc9242e0d2af5e6ddf89a029197483706c93df107a472b3f92275b6f0c2d7b3255ce05d88cbb6a958851852e1e18e8f9cd8e13e3ffd163c8188255cb265ad4ffc5131273f2cb504248ea41cabd45c3a9285a1c7ef1a629ce0342f05870cbeb15e61ecfafa05e877cf5ef758e3f1f542d17fb8f21e977230e4a6ad825b0e8c7c088f0a4680831d549f42f16c803407bffacd6ce73636727bcb9357551b74cf7cea38df1eedc560e20a5fd0ad9149805c13e6f5638927720dc994a50900f98a4783e64bdc40aadba75c50226ce5f3488d2369d119af1467e29fba5c68a3dc436df171b6072b5d9ae603b8973bfbbffd048891401c185361c3cb0abedb27b895b652bc00b1a7d3f96a909926f89bb13580267c0e8406f672726963f7ded95c348b6e1f8251f787ed6269b09885566c2f6c4c0430a4a22a72338e8ccd354cab948d0273ba72cef37db1a89f8dc12938d4b8087a02f93f6e2dd622b168069379a9f72d65e6668fab6c2a10916527594c383eb5b1a9e7c14d40f6f90ddee8ea2a5663df1f4f7ddaca1e35d5f553f56a935023997eb30fa21a72982f2113cc4b80495b69ace05eb1e7ce7923d0b191421573fb1ff19a771d7f7285238c324112462e4d7c2c6cc3e6a9367da494c75f17a6f896e62bfa5b9d8b728d7f7a270275a57dd40c83a4adeafb8ad593f21a514438bd5b758e0437e6677200c794f7c96546c2d90623f33022474fab043c8f7b2fe02d3807303dce34766073d0deb0b16ec428b3e40ddeb5fcb1a46a087c08615e76331eb1e960c1187e7278cf395f6c069ef1960f64867d96621d50f6597e6977b542b6cf83e548037c5bda7fafc84d8303024cc5b234ae7f890f0d35384fc108a5ffe0a3080bcecad561c20b309d14054434d85d381f2faabab4dbeb32d9be30edc501e8497b1392814644d707e368854f2becd659290549a0d4440f266f65bd1712d152529e8ee36a725a83969d424ec79dc6fe21f3b5c52d07b93e3b8e1ab346c4ffe094b6b5566b0605010a827b3fc5084317257a0310709fc394d5a5f659ab1971b09453b46b4c72160286f8ac589fc2f2cc0fa6ef195586eca834c2206ed8fc1ef36950e725707220041651b3fa1662ffd2f8f169b77e6d5a2fc8b8dea7f9eeac2f9805b238ca72179080074d301305c0c3e6e89ad479f75df2eda61077b3167a6eb2e045c6e869324b7687ae3c44fd6aa41e14897ac6b5c89b1413e72beacb32933843dbb72e4341e0fb09c770240a092b533a84a7dc6d9e4e663aa6e781d26810e04489a05505e3c45748929426284e08031e0f5730d5375619e13a00d979fac62bdd53265772bb6da5aeb0e74e681be98c928f0610e5567ee5df70dbfacbe9b8245109022a72bbe65866143c28f2c97ff9cac3c8ce7aa430c124f3d6d36e6307bd7b9979a672f064d9b016141b1859541ab2716ff0f4e2c2dfd78ca69815e1b5e70650c2f27298b4f1487ed3ffb9e7c91173af3d120173ec8582f5d27179dfc38b4e0f6eaf72b610e0b31e991207a62602cc64bc3ec8374befaaa44b73e70f6e9e718d033119be28311bde298cbc70bbcf1d0bc712a796d5d8de5ec29d68bbd7181cdd65ef43adb0a5f8ba98a26250b29bf60d859a9d0970f4c60a2763d298ff24e9740b5d26db29afed341ff44c7e3c13e647b2084d4a5eca4fbddb638a53d7125aba8bd272a7d61e5576da9de28e38f1a5db4c41ae49ae1d1e95265977c17ff3b5b23887726b42e32a66931fc88db7b65f4c01fb5c1b826da6cb5b9a275c6ce5f31fd7e436d1619f055f9ebdd83d9b6ede3b876d238d271ab4297f596b18831e67651c6b68971979b3ef1d3d37dd82f35b77b2dd022dd6192261db910b96765459f0ae667218a9a98fad49e08e373bef6003859c4780fd2c0dd753b31810cb9a0b6be2d000b406045f8b682e05b96f3293a3ae38d53ae7e6ce97d2e15ef3f995ddb5529e72a3c5365ea7483107e816f22f012fc30f832cd2a3abd0bc5d121d2cfc8ad5d27241094b6e984bbf2b84a9bb0f2e7c5a3e28dcd2573ae8d98b2771f8e29696297294dc4f20776e5022edf89ed79f309c39e9d6637a11d368ac6b9510e0ccb6731e06f1b0181c62d51da10467fe715145c20e1866986116d00a08b16aabda2e8f120c65d421a639aa9e307e25e05b17e713023934a44dbb92fbdeb1d9161a104f72164674bd6ef3f980e25acb12eabce77b9912b88f96e426b7912b83d02a3716495094b83dace59e6a1cec555132c123980dcbb1efbef58ea2f5b0bd240dc6fa720cc314bdbdca7b0ae84d4f854eb9e2206aee6c474b93c13e1f9970aa68c93772643ba5a29abc7c6ea43d2ee901c38d98b315284651382ba04d8d29d453e7902a98b7fd997b74d0bf094273ac1cdd6c7d8a18a0f43254f3a39da3067013f4f372c906b4e8e0254382e4c8a0498aa470a0a7bc8bac1eea0a830d6d5950575e4a0a427f5317a0609c00c68ab1476313e89d018b146d6b2c50650f6f4a14fd8c0a7298bf61176cec0254bf5bef2bd4813d20fb4f05a72482d298516be5773ad65a669e7b002822e88039f724dd49435b08046bc38a1fee35b9585556a7971a8a2a5f0cdcf962ac906aed61f47a2b356b701a70995696aead9c51fd7448e7d7a71372ebeabc5c42c9f3a8e557c7070d707be06374eb2cd52fee56e3844633d1fab46b4d114cafea7d17af1ee617ea3bf56393ec8199e493b4cf14f7f529f583b35b315ea106aa4bf6863ee95f66b8b2b439cb81940fa841d0e204971428a7c567ac72f38dfe06c35f3f4b49694f6bd99c76e8da2f3ffc17ae0c331e5986cec9c89872182995ac273656367926bd80fe339317e12e34ba3349a361181a7c5730b01a72668fdfa1d89b104d1efbf1cb0d2fe444a0697974a8766f3a262076a8a8d84872f6f69a36aff1891022d73e5cb536104af9f1d6e81f1d5f845a8a0d838d3e9664dbcc59579c0a3770d1fc691ba7d0401972f2814e996174b76371d519aec60172638a9ba9a6aa97c8cbf3fe5ec14923d44b7662f3c591168e01a75ef981b6a4723b84e9b81601f10352d83ea7000459d69e848a00cdd51fc7a997ed9efad98d698a5645b0d38721205c2eea010aa6ee3b9a68790a07f678829370765b569ffb5a73d52403dd83b65eb63da42dcfa10eaee2ed17cc2f8c1dd0dbecac9b0b9d98241cf79ea94d3ed112b7d88855493c0d77aa53ad73aa61ce98ea121620632ee66a9bb0e52d9ecd463ac81a9d7ee3da083b56379586350595039526ca11c3c42d04fab30f63d15a306f119597c9993caa44a2aba963f2385fa1b93a114d41f0f07213fd0bc32615a4a4082712c1771c622184b6a04523d556341ad1843f1b62e672d5a4852ae28fc3cfe76ca34616edd084dbf82a29558dd1ee1cf4e06aaf7ee70c67006eed91cb73d8c13d256fb26794202ba77253c820a3f4536621c64ca1b47233599151281791d0ac49bd1a9d565cb4b2c10ad2479cf5a068deaffef756aa7243147544a5098420555a99744dc44f5191ad518f4690051e4da68e8e86ec3535fdb2c6567aa8d87f2f2d731fbd674a4d15dd6b0fb830f749c5781e8cd2033e72def34181c3612cde993d5922cfb9eb1a85fa77f642cd5ba6230f88cfdadbd572ef77cce1a916c68a5e8932c5032615ea37e9764559263dd7d30dccc21e7f36724f0e48b339f7023c0fb89df44be48563a9539ba1468e20a00855b397a939c77203cbe56e7aa6edaed9d42851c9873ac78319105c3b41bba782e2e954c0647972aad68c15adb5351b5e010ef2a040c4ca5582e3a772e0e4200d1f6105a166bf72c5f9b2123fbc81946711229e85f3935a9871095ac8df75a8ed803a7c1ffc7952bd1398f5deeb48662b5d5a2ac8a0cbb3a577cab7542a7612ffcddb991b932d72da757e12c130eaf39b3bbea3a6352b8a4bb0ea5c22e6099931e3527abc67563fd1121413a90ed6a0b578ba62863015a0051b4d7c29e49665ffc6a839e16647720b1f32316b5f0399fe54de94b19bf056a598f9c884c79ccb6033eeb037f12703352bc184bef147fd181fd5f037648439fb2ddf41a832878713d0c85a4826a072d022dc1f19db256ba0db5f6cafa45845b0c4e3bf0dacf5dd760ddc2fe6cb3d72164b43461b35135558506cf2b8770fffbbe83b85a350dbf003ce30d18e281c72848109bbd585d4f3a21ecac34719d45203a385a0b7e4201a3cd37466fd321e6a2aafb89a5dd04eebe4de687b451649f4c2049339fdd57d3b7d617959fe378d4f9b432ac6f482d9559e083c2b61929422a4f08c6785da0dc2db8e16e2a8043572baefdd64f3cf4d865dcad06847591e8eec0ba802d7d59e0255739f06083973729ce8277292214575bb041829c08ea5e8dace8bbf4dcf1b19160874fc89a27572ab9f0848f5a1d143cfaa94060ee3d0a4d547743d3c33e8caab509c15b553062e4e920f0f808d160f917c574c161bd3c211a5ef1a8bad8cf6b032fc1a785ba33b0c7e9544163131a63b313c41bd39745009d0e8b2e2804eeae312b4f9cef3b15c58092473d842adaa2cee9324de7c87b50cbb913383aaa32792e31a83762c1c4fb8197933c22a7aea95652a6862e6108369edafbf506bd11268b13c8970e83420d64545b509c06f54e692f24ee7fe598839a8f24425b0b0b0fa3e87274a502622eb6bc16241c15c9a8772f3055f1c2bfc1598d131321cccd97b38199b31a7fa72b9a2c7ae02d693618d756cf7dc5f1dd1b9074e05289b042d60e115076160233d6c3d9696856ec6486e0dc60bb81264c47a77700e2ce2068c06ed43cfb97efc56cbfb88d6b1a0d55e027efddf358b9c02dcbd8d507e7cb19ebad6201a01a4cc6f830d68d3e162de4f4f442fd77cfb5739e3f8610610b31c346a6f707eca96d96b11a20f197b7f8e64f2726f04bcc444a3c47a6be0081ab6b40d94d815cbfbda2ae179664b7631d3dbcb9a84a7eac4fc5520d51e5230233359d8624db84ae69a72dfb9d1e1648ba5f62d3e35a2195543675e7282c4fce9af2997def69c19150c3ecd8bf5edb16beaa65eb467fdbb2b07166c87f1f49b48bb4b28ceb417ee129672372cff769c89d9cdcc29324fa1a6e6c4c8207e89cb83889763032d3b723e53727b978c2c558ed4941803236f5b38d4564a42120ac68577eccf9e89580a4e1672f9873f1d2c5fd1617bfa13f61d06f78c36f0cbf44e4cdcdf603f13be406510554ff82e264e4c0b51bd6f7899252b7a13aa4b1cb9d433be98541f6c55e46fb7479790029b45c1505a6b7edc6565a4bac8771fe4ca7938f5afc593a573d6aaf772487ea2c27dc84ca6205948e14cceb11213cf59c0c79d2079a9f72b2c3bebf372dad2e88fc6762c953549e8b2268aee598c460a9db49dcf03cbb8b39dafbc4972c0e4f94a3862e53dec51d36118044d599eb8ee35b06d3b047dc903faf64e3060a6a5942c581e7c0fcb1f285933708a5c54576d93bc04a611bd850b14caf712127c5dd09f10e550966e4e38b850010a9413280e54399e7927d2906c3f43967b725b5271f9315f11240324127a85e15c2b609992a523c544a10781e4218154105bb98b32bbe24f045f0cbe8996bcacc55e72425e5d8b95a034d663fee41e5c5b721df655063f490b4dea4f9bc1745c8e376f52a04a41b219a94c69d545a5665972b3f1a66e96531bf92ccc273341b0695dd820e69483fd55f23d412b529bbfd52f5b8d156f39a5c1c4128abc9ef0fadc062dd1f3c547aefde18a3b2fbd8923f571428b77849517f0e323d939fb4e5b73ad578d64f24636e6efd14f8f0b930e1072634f646e4bf19dab6a62073890499e6bc160721de4cbc28fd065d9ab47141a7216fa86cb0e46018b70adc9f017352804a30d71a378a3123d97c3ac4b462c8a727de70473a4e053a22186ea22415ca1cbca767f56eaf1248860b9625329bdc464fe31bdd3c1b707be30c098f3eb905dfed32d2ad98f4393fc3d43558a55a75972d93c97aa5529d8ebd72172831a21dc83026c77abd2aa50a144c36448457f1f09bab5a35285b53a8172e59e3d3ed116462c2bc70f36ba062d63e93044522c5172bca5e95493f07c82d9650fe06f7b0dfeb1a4becc8fd48ce7167cabd4478d366669e01b6cd461464a29af20fd54034035ea9853bf2795fe011a1862202ee22a18282d8e9d1efd36017c31c0b430cc3b421934b88044fab2aaf8163449c693ef201bcca431b701b897d5f10871a230ae18c5305c569df882916cf40d3b9422de724fe287282eeefd0544fae87b68c8b7c664c57fceb0496e99f659b2001f06c872fb8c674aad99d36fcab015d94cb960f5e1d412cc7871b3fb78febf01169149725a8fa2d9e7b6aec83c9decb228b79c12c3491c396c57a55735a4698efb7ff872144b61352aa64c0a68c59879389b224d8aa26f24b10fb60d340dd85f20bda9525dfcc64097d2b8bb535de3cbeb0b21d037fc479675254df878cb741a7ac36c72ca63ba7a5ec43327781c693090dada4199e3dda16654e5b2398877a57ca79e728f3b6a33e0332e3ae9c3c8d0037b621e894f8ff6fffd89ecc10d96c3a376607284f967ce7f303753c7f5a652c56c1aa62e26df954f6e578ecc69b77a0ea69701a28e44318fa8b39c4427cb0075fe901c33d53d7064a959c6807d4ca40ae9c37296ef80897d7c99a80eafebfbd1999844d70db10647d47a6a9a643c9a0a21a53ae1e5233576f04a832043619d127f52b453c2d5c0b98d7338de56946c5934977222559c6978958cb1de9bee612cd2e2ebb2d00d2e9958d71e6c0f767d45170c6e6276b1d52a468e0f2b44ea94e861ea131398f333a7f2fed1a7f63e3d5031b172a674b3d91f3ad0d79cd88cae0b4707c17f3e8cbe45342d568a0cce0a1204d72d199a8c1b1efe2dc8d2dde9f7770dc3e3cc793d9625f6df5a92b8a5cca3102672996eabc78f57f364d43a0b61b6286d7e168ebaf5a717469f00026b1158bd1f6ec67011efa9dd81d754d223eb73ee7ab7606efda5e95da124cf3e2d5964163d7235d82fa1fe62404edc276fd18b5cb5c9941b99d7c94e5eb6812e608f16e2e2725a5a1fec1bdc0777931d89f5bd3989e6d9f3c257317fffa1c9e56326492a5472cb8346360dbdf7439d70e71fac5195cf0a3f4fd67203252cfdae73f1184fa1720695412eda0d29b6b4f83b7d1f77b95d927f77f31fe70a4d7645308614b3ba728c823cc6d2ccf92fdce99538dce59ffc2145b14cffeaf7682037e294f6ba4d72a471c56d34d871f4219563c046e28ba6306340affc04337a854f397082049215173cd9976920f6ae34d05523fd7399c9d27e37dbbbd9b88757b65bc2edddfe4dda6c55815e194e22565bcd467254babcd398dfd633dac45083b2488f8efd603bae151e2452e2cf75b0f798725454232b48f2b4ce41abe81b5098916f2c97f86eb7c766a44dbafe012add63f110749103e55063c3acdb7f80d6657dc98d87ff4f1e13c481268bcf62bab0db262f4b277d7d54f2a9d15a5fed90b6f322f72a8f72f2fd6aeea613882ade0b62bcec2698e418528e03978a856f6ccb4b1f351d9518a8fdb7c1609837a7d2f1ef05fd6a48c7919a217983b115b2de9f0dfb4a9533726535d0519ebae041898848bacd68a70cc5f8519c41f89a0638ef7ab5b3ce6e728309bd5ee68d826cce623b8098f0bdc9739d556657e27b6ef77d5c8cbb5bd07249c32672ec164e4cc8430db4e8259a390125590d63655161079c3ddbff47b00129438db44855da883b64ca3b83980f16aa1253a5b830380800e5e7e083183472a87e2b06e2288007fddb4beff0acb788fed62c02c144b109047023b4ea4483583971df635b4f4094d8e5c3d21c537cb45278d95d3e12107c2abc5b921e3a7a655ea304fdf293bcb34930f84bac0f983869b9923b7ca0706cad87d9ab2f80eb7288632ed5135817a02926f867a9d038062ce0fab80283eb21899d83531f58a472a8d0ec4e64acb3c4d77ab58dcb4a8f3cb4010fe17458b67268f15c340e5bc872030fe15f0fd3ca9a3dd1b59c4cf65db62b154cf43bfcabee75afad066fad95723394f2afc69b60ae76613e47959f2fa247b4a321c42c0dbc7b96f808ac317772eebd48e727e48a48f298b6808af6e63f6b54b52c4405b671af86f090a3d7a4728685c750e89c858ef4ca2287497cb355b30fe67350428913d430eae8167b82729d289450b6769de01680f6980f51fe453b75e8d6a9f408e9b77979f0c8589272d00eb4e30701fa7a92f9c46ebd48387af34c8be18c2e6c5ee270b37e73b5e8723466810ea2e6238ae3dd385767b27e3e237237fe259ec3c1023722790d3e4472a77b13879f36ddf94ff285288266bfe1f26b4843d5c69b8ed54f85125ea3fa72a068743305af97c8277e3feeeb42ee14e3a78247b4deed20387acba5e18e37721286dd7f572236c663277cdc47497af316d72f1e327d12704d3f333427eed7021d09d1d8cf733bfa3dc0f12d0175b981626b6b33637172a15c9f833fa3479a405a9919b451bea7b2c80c1f53b25dc1d3dc2cc725cce11288302e7f4145c6995966a1d20a277e9936ea781a3a72ba6dbfa2238d6f7d40d067a691718363e6ee72236c63de5d59caf5163e65b0edd4ba90cd79d55685c6723cf04d3d276a3fed726dc653eb554c9436db38d4f1e012dbcc9337c1696b78d10c439e5ece0cb32272ba5552d4824146885ab25bf7548548debae52e78d438d7095d238b4424a290729288b709963a376d6ac57f0f457606ad72cbc2a897f8b402d3fe8401694989244fb629d56615d0d0818abf2845bccb74abd9e0802c69cc076d9038352d48e162b288bc21d50c7f3faf5cce5f85b980fc5c6e663f4c0491954d85c47a94f8cc1d55bc9010ffed1ab4b1f5a7b7a46132402ac299cc439296f8ce5e678de191dd7237abd8d725195b1a23972368ebe7cf28721385af753b34c46bdae5b7727e427204e565bdd226f7f2a2f0424ae8c1e9dbdff754093f22edd5878ae7757496ea723a8534858abc793de694b5a1a358aae1ccf5e624a9738647566068e25296d0724bcc81663a994a7bcf94faddd6c6c6b7704f0956a19b9467a10e76f47da3a772a6c45ea75c313193569a348c30f4f514ca10bfe82e60a668436f86ac30cbef723f8bf61010a5a08c3537b33978695d1b77fdacdca706cdb554bf8c0c40e82761430183138cb335d9486d0717d94ec499dd2c8748240be09792035d83df8703726eb87c6325e760de249c8647ac721742c0301ebe26e766249494971ed44529272812c89b6881cc6352f170366733bb677e2641d60a1cce0e3937cb63f19f585dce89bff71d419f6ebb73d8cb83ef4e2a5f017e72d61ba6a1f6654a69aafa15700cf1a1ce6868d8bd6cc34606ed634c81e59b1770772c938cba6eefded738a2723a7d30005ed77b04ca8d7bcd27a466844763f2667068affa4d37d924ee938e142f650a67798dfcc7465d90f4f719c9e574242bd1eefc4b786ecd596426e74572c1c1f3595e934d5b270d715e7440f849f7adb78cdc8d2889721a781b7e4da51feeaadaf386d18056f5a8b2068356806459ceb211d912a044d048f6bad2bde77204f7390f339ecd4b7b9a7ff2bbe8ef84ee160c1115e5f233a00b9894cf82b23862fb2a6ae67ceb854c77597c0f81502c3798d87e6f39e4923b00bf50c728c27263c8f71c82e3e995aec13115dfaf8d34c42b016de394b14b96c1eec3ae52c872c8cd7d7d659e5646a295115a3bc8deb3b653c6730f22bf321ce4b5dee7eacb7147ad19c97ec08a3aea7c90170411457f152ad1eb7a82ca54aa9805a392d0b17226cb8950f71933ebeffb8a55f512cffa831078f4ae2748e86ae94b3fe657de72eb287f9a429bffff97ecf06350f8118e4302c189bbf70b8b163bd3c12770f16070059928e561883d2b992a007c545f5e70a31ca27c34587133cffe0d9e2ecd72f17ef56e6b36831faf9ede29528ac0da69fa3b2330975f99c424b956eee5e15548db4408fd0a0c6953d12b07bed5b2a185ecd3738eea13660f97dc67f070286f6b3d30f662d32e30abdcffd67ee6fb578896f788c6af4a115ae11aeb1a515125513b1cfccbe0e7ca060963baa7d23c84b22692230c067e50227f46283455c3724a96cd97d5d211394886a783eecd699edea979d88e9d9d07c0dd4c29a6eea572ce722ae07ff4972cfaa999084087df17fab744e22ba738e735592aff0b246772266149f0cc19c80ab5dfcffce733b56a4b913ab948db5a324efc4ab76050f1728e3f5538b001248d48885cb4295b94d05ba7bc0c8bbb8ff79828cd5ed723b9130e45fa05eff3fc6a3d1340a0f16bde4998f09ce75e050a1662bcd2958fa4023e8b2162e1c83c27cc6a83d0d6be71babaaa9740a44fc41187c13d96bb680767722a59c305bade560e53106df1a7e6a5f32a0b43cb503b5436c0248fafebb7db39a8695a5252caf578da4dae05b416a1a7e84e7bfc8e2a742b715749df54f34872bdb2236f1da917321b926fc1b0fd5359f140d5abec672a6cc3ff27dcf77a7c228a5af34125c4c4040e68407cbea6a5755afd25179d00da61068e53227b4ecb2c40f96059a476d5faa63d30a8482ed971360e6231d9fed1ee45cadcf6b1f86859fee9a85edce846f101e069cf345bbbcef513ea6a3d998d2665aa53c9a985156f9f722ea5782187f26c2845e75a7d4e6ad0b601c292b50fa4152178bb5e667772f719073e368e838e16666ab4d0c088e3236ddeecdd62b0c97dae6adbd326604cff195c77d99e96612c91988447de76c70a2f738678ae1fdf95cc6d7b18c7327236a91237be2fa0e6abce9eaa401ae6832f027d4b01e55dcd1f330abbfad898611ea20a7a09e67125763c76e0be8c9dce6ef80f00d08807b6cecf525e60a78a72d3927fba515e65e31ad670a361914695fb5e5f721394ef772930049abe0eae69e26a9f983d5982250652a5cd9d06935dec087b355475323431d6303e3ef1394cd43685c3fe2568e5470cf54c6bee0797259f0f6f988da9393ab633dfc5bdb209ff284c5ed8b154d59eb3cac6a865fc40cb44f31e469c087c6f2d4cdc233cb172a0897a0799472d7416dc2f06258ae35e1f92e8863ad059f7d0dae7725ee6326592eb5f98f8b8b59846f8738847971221551acacc4a89d0f032dfe1794873ef6bb5e4dd08ab5d30d5b27c8e82a5beea97fd8625e86de787dbf18003e12519b572d69832eeabe389027539aa572a4746f7f8e9bdc0eb91f3fd8105fbac11aeca2ed53942f99c02d1cd19a172eb101870490c5bb215ecc6448af58637082f92a3726c2ddda12219a818545cedfc2d147f6eb07a9e1327b6a5c2b0e71f0e511ab930f7ddfac0f1ef5813afdc3092fe4aa2f834d23f0b4b3788732a7cc3525ef74172ef9083d2bb1e40d84d2542cd6f3db29717e6571ab59617ec5959b07f0f8e2c6c5aa5e7c9075d563b597f4afacab2b79d8157da48ff784e6e4323e7e2ec8922729f41410e2fcf8c8f31bc55792506ab3144e312a35bada80c7c58871dc6aa9b72c374f8a99cc42cbfb0cbabd90d87b2d1572fc060a299981840a83312d359ff56d34435431bffc8599deae38b3d7be43d198527f37042ba98aa616f5ea44d6430f952ac6ec554930032d742645f78a0513f4578f24b3cda10bccdf82cebefec593295d7f7b799daaae952ca7553843c3f66df2b35db40bd20bd065b6d75fdee34af3d19fb0cb1d7219cee9bf65a0d8489ea4cd43ac99322fb06015d395127a572b7c74bf58b9bb1af8a835affd9f3ce1eb81c7a62435df80c13366d21f65a9c4073d29e08b8f5ae6b14f9a0b9d825ed751528d9e00482f14c8739fd9de1416972c7cc9fad87471ce3aeb648a027bb6e1a76af88fa1a49772b85168c73441da2728d464d9d470104f73bcc68257d18d8e526cefab06fdc25ad2d60f55465ec765aff3b4de2a6d5998b522b7da47c00106380343f2a97c569a45ff37edde3280e72014377960d7cf7447cb92e48ff55ec427537fb8da0dc021bf6aa61b636d5cf41555efc7be39e1b81010eb01f3c0a7f961a502afea736c757c2d2940136dc5f0c486eb607d09e015b54124224f83e426b6c07acba2a6d9b7fc70ba87021884e72cac5a143e5ac2b8757a238fbcc4985ec2a43ca52907b8533d9d78c1ac30eac13dfc52cc8e1dbdf5d8b6049e562453549f02213340798e77519a9222717df7e01574327ccbc4068c45cb51925326d6d8decbe5b0b94a6d5c488c3c36fbf49cf72e2b22c9901f8c15e2da400cec26cfc2ff3b93f0fefcd5cf5ea25d7dfeed1b57265bf43b6bc07c5d627fe22f34c0c93533adf1a7c88934252c141c1dfdf797872341a1f5b20bb546650e78787b08dfdc30d50816396a50a3989ae2d3fc1ae45727d3e9ca638dadee2532db5430d43daddf37be2c683ee86181d53de2ab59d2147b3834f581a0a21fdf92e4341092b4deaa520d56a825b25bdd2fcc5a3e495e62cc79e5061ca75b9ad752a2a7e11e658b4594231a4bc98f8234f308b40ff4f0f72fe067569e56683a317e9bc4d05de4ba9cda42e44d76008e3d5ef93649963be72607c8ddd119a9129ae197b53aebf6902cc618ce892e4f548adb67512dee59b7265d158a8b3bed25004077d0f7dbabc47cbb856b671b0ea183c35b5530295cf549f053c08b3d7d4cd78144349da452d958379b847a426f5f04032e5c83ecda156fbe525ca12cbeb6b0dc1e70c7fa37ba6c0fbff502f2b9c40b99a006f6783ee013049cafaab055defa75991850518dc91d5dec9b8b743570b8b2a5e5f80626a3aacbc1d706180361a6fc8da8ded5bbb32361873c2868b8eefdc52cbd2cf8de872cc903081619c135145dbfcb6e5d5739e15240948e34d952b933e2d5b12e2d27295e4ba3fe8a7969a34cf9f86e984c4f5c6f74be34bc45619964578f3392dd172724b504fdab88555ee4571d1820985df4c77bd912121fa88fd617eec66b7a40811f21867d69b02749ef95237a8d3bb1db1db2dc07c3a7cc320b0c564d7ff4b1eee3f92378bbd02754ecd7ebe5a30c400ccc9073799d82f8289991fe688d57f723497f7ca6bde9dffd63748b5e9bc47236f3495cd3cdde7c4b476a31a731d9e7284723c8447a331033a22035592df9923e5129a3e59f17cd8160270cd257072028108d9a131685a0cf4bd967a91cff12136079cfb1186520f7c4aebf795d9520e5fe6c75c6940f45364beb2435c813a3d3fb476e941da48c865a52f51a278f869c6b0ac206a2dd7a765a29502da044570f80c604670ba7f57bfda208f41c2bc72f41c0fab7d0acf4275d25d43f858b7eadbd5702a87891a31127b7ae16d9e6472eaca346dd80a66493a42a0a65fa67708cc1efab607f55d3999cd15a44dfbde00e3874b81364a1ae6f6c1c3029e1fad608ecf0151d70c6024de4ebacc02bcd04e8e7be3316fff565c129f9c6cf4fdd9ae1f365ef1cee521deceb6a71c92d49d135223a48f38e6d3c3558eb597ced05c8e7576e6f14005abbb6458f79a22dca772a3e3b9185d536104025f39dba82e92e151bf657afac907bdfae762f90fd1de721c618ab77de7358ccad039ccf70dc2064e90f4e566af29df0b301c9c27192b72338b5e711fb2651c6059a2993bb34c285466ba80b658362832bb9d150ddbd6726570160854f8d16bcb278e5e8b85f20e0ba2217fb5c299525752cdb063111672a72afde2fa362b3b932b028e8aafe5c1b87a8beef6626e4859c8a5e3080c6d263342e9c60b5c774c8db7e78483c58a435f301282f22d14026de074a7bac7220c576a210c416ea43d86b810038103b0562e140c4ffa7de89a15a3b75cb8f51a5545c7c802b52a9c9837005c8ed8959b74be2929d0680d0f5485f5b5a572b4290f0be6c74e13c75f9947933a91d90d57ac2e945a3fece3be2ac0ddbd3751e2a94f508f086fa71842e6786ec58fb0ccbe70a4b161e59770de60182abadbba3ab17205ea9c44b935fbf5090bb5e8f26de84c98e2e4c5cf67354a987e7248bd3d2f72567f228066a2d0199bbf013f3c73a9a545d9b76726096ba976de8f38acc50851832f80e9f09abad38baf6b6bd05df5a338d44f14c106a5c152878a2f13f0e0727298088ace31648d005b316cf910b411add632b50b61d818e9599470950b7172c7232a8eda7995cdfe7a71a4591f5f755e1004b32cfdb5b3b916a93bdc572f2f640d04ffcd019091b3a3d62d7db8e9c5117d088c822e237dec0c74b3e1617572c507c9930a89d7c2b06a55479313abc3354fb6b531a26ed30c997264c0f1b4728efdca64d1c01d59e5ac259027d1e0c5ff896c200d9f7ae6c704a5242468f772a390494fe36cc80abbcb6432c340de1d0db95cb4076002ab98888946f98f5e52175f595083e0e768728e57629029f3ee7bb55b675984793d6986fa68a68e08728864104449271fa9fef591dda403f7975228d51ccb5c4e8fe05e7a8b3794bc7212352e326ae92fed5ec69e340279dd6cbdca88ee0569fd04e61bcd8453ffe4060644445c11aa6656707a10bc2e239b9e7d4bd6a2ee4eddded2dbcc67db06e37233b67f2060ae7290aac6eb569ba754b78d3d10283603ca6316c707e09e5f60722dd26a8623dda799ed722295adfac56db0a5c4b6e9fe24eee61c5ec15e89f74de514d5b8a2048a88553c2a4ed1d653b91636a5654272e951dab7662e621ccf13193dbb30125fd077ab4f1f074be1c54d25526fdccd5e311635185f9d665a1918ae06d8d86a16b27827729a5d47ecb2b4b1da6712d19a06add5dd92dcb766c94223015bc077c076edd58a6515247df91570bfa885e163515a0317ba0bbe27eb012036776c2a63b6152c44a015f33c8aaa06c333993bf0cad982d7605474220e4f8a67a603d76ce4d03069aabcd32aa793d4d1cfddb838d65a5e3fc85d7cfdf57224aaee463f48d59c484b7808f8199142beda98e05326e759dc10996ac5266b4e8ddd778af6011d495955f3bd07905636401eed06fd401807278b66c74fd09e725288f15a48e2ad3fd8c672b7041b49cb767d09429e13b349ce48c242faa4f372ea73488a01a8e3a72287c2fb9921ae09a872a01800c8bb8f4ef4b141f1586e72417d7082b9ed32a9805bdd28478df65eea20f8c1e1ccd30a7135acb515f7dd5119a36daccc4609f67760c15e53fe965fc9a346035ffae4926f9a019bc5f7352196332a3fa3bbef0bac0ee2ea8b51b60016ed84561486072a64b0cd148fe70a5f30d83699c3c9a5d13e3d483ed4f28081f776157e70b7b716002a4d650ff2cb72068eaa9b27d5adaf87a065dac08a10e3056c88a3c3cebc78a352772afdcd7f72499c2711a0f415fae82b4a958f2ee82e42437e81c10c9d29f0e9c46e88fdeb012b9a14186dfbfeb21f009bd18c9c39824edfbff5b9519d4f5cde36dad3513172f3f186d7664742aaec889b35903b5a8e0583d72707f4d5f446fd799768dd3546e7ca0662cddfd5e40eaee376b1e7badf080141601372c40c9c5c40ac169e6e21100e9fd932e26692106bfebdc3d2763e6dfe58d607d9f9065253cc9acf3257723136c129ed224bbf8183490994eb52f766141409544811d51b644e766049c772adb46acb417b3c76594f35a5bf59c2f72a6c931c18ad2f6417e08da1a3a6eb727a38ab5d122f4c1720c71aed84a92a75d82a1890dbc862b5bac8dbe6328d5272931c1e6728e8dd4245edca79dadc3d2242ec3b0cd4f1fd6f315e078580d83dba0200004c0e071832d527694adea57b50dd7b2164c2a47c02940dcf26fa07c44d6d222a72a33426875fe631e2af766fc3e66aa35809d3fa07521e86c68ed5e3165aa04772654ce15d40698b4a3304e9958e0994b42ba277743b6e439df471909794070972126b70110ae2c9537adbef51101b0779b29524208158fc1b3f5cca61409a92228a62657ab4a9c0d7f2596ebc36c8d9783cf92edc59981a1803ad31cd174bed72bdecf733c719972968b47cc7d9a1f8ecbed8a5b7e51dbfc30af0b68b98aa4a726d325853744bf71a83c917b6801e9388a94fa872469e0529a3b39f222bd2627237c51cdd333aef362a81fb71aebb24c3475331ff53bd76158931888b39906c52eb9d8d6f6a5a27fd45cbdbf2384207afe217ddb82fc3644cb10757aea50f23204f0e350d06db5bfb3a0130a5830697bd5ac0ab5fc877f464df1c3c9fdcbc3e72b7aafd30df04662d0fd5d32d2ebe233a8e0be7aa87cf91452f25f3f6f5a1cf7215ba9717a59970e95ce8633fe72c7780dbb210d118acf96772bc6348637eb95094d4478b8e9442d4fa6aa77475b53148f16ae748265be77c37a2abec7096576b0a654ceed66a2a7c15dc6b9c66b4cce85eda3b20a08b879d1e08622b7c7d9d729a78754cce8eddda1449807b887cce816a38f4009bb50a4e3fe5c6e36f56a872eedc645e50c256188d8775761fa9381853a30bb5bd9c09770e6c8a42946c58721785ec18c8bc92f63de4462a207880a078b9b8c8860c4e43587101206a06f472bccf168b83c42d37c79443e9f6f4c2ac2a9faaef3a2839835c47ed6a62e94f7244fe24be267858cf6761f1ef0af2358dea9907a212519f579320c12eddb9e972454f446b5e8a3e694d866d9cc69722b2293aaeb5b120d94f9448f81710adc172b31316d57706876d6fa1c51c68cfce759dd6c16b8001f32785b1422c11ac4f726d3b0588856f4b486392a7e4d222cb13d109900e8b86ea66a60a027e2c3d6472a37ffd83a60dfe8b6dfb98442abe60bc162e7d454426706cc6e538d9ab96a95ccb642a86abd98895e0988857c572f8452a4848f4118faf99959b77427e54a372b806d5fba42793a2ccd4f6c61792439590524b7cb2d3a27f166d0f95bea237385022c02549407cb4e64a338166b8c5d1922e6171b26b198d808fbc2879b49625befa8406a7d2939a52cd53946fd8bb82ee7194aac4cb32cb6a41c90e777d62721fca68b2dfffa33834b605c7f4189dfbee2bc84c9d431fec24048d9d57ca492025d6d3cf62d5fe3f7177b413c5d1f8c3a5c6a64e47ad68657a87a1711aabc672bccef66c90a325320d6af2d568d65f082031d630cedb1970cf976d07f9b5ea34068691912701e16aa062e55d4e417370e4181d57634acf97d4e28c12449ad47204b854bcab4f134b9c9fb6fa1393e0410b34b850adb5b72385232bc711db58575786a046dfdc90dc57216a8a342befacbdac5106ac8f3c1544447a4ae86bc572b35fc914730e6122fd4d37a206784d06af086cca64cd9c20f14d0d905a9af6656e95b7b0b862e1ea47df1a702f09e816eef7ecd5e6eb11d4d372c2e2bfd28a61fba229166eb3ad86ca34392d36697fea19bfb2fec00b6147400fda7198b3b45b77b9d365d1c2296c2a47eda93a023298f31a35c7478faa3ab842fad3ab3f2b0e890c1b197b999b93d05f4c2c43a059be2aedc6e13521c179b3d939cc1f51b87247c4926f5d6e49d24bf456f275755b1424606289d9586f3e75f494a5a2b7cf29a7d25323441b8c4a9ac2ddb03659cafb5e0a131f5f9e48cb8d425b193a523c3fcf9c2add772613df33780983678b2f373f465d9fb33e3c8b6a5e5df21ccf564c453ca11b8a053d15b713309003a95d02ad5b08e4c7cd1d1d453991ec65bf26725286a063de4a4356d01feec3bade09e5b409a946101a1b127ad060b85e77877239f2ab6bfc4da7cbaecae77513cb0730ec854d606ebb06ed461e0b6c57fe2b0d86c336f3738eb5cdc559b7158721b04e6cb634ee02caa8b862c8df35671867097e946336db08991b793b6dec3037fed6ceb937e5f5fda94cbd1be82ee0b57672b48594a371b8d8359e60bdd3080fa8392d9e52816dc83faeb66c920b4743f372425d48219db7261857ec0f50505b7b76e19e9da047d22035edb1287abf31cc729611694c47883cf958749098d87c30be72de8c4ae67ae685219522536c4b4b39984c29d170b91740e13795a4eabe4d4319ce47ab1101830c8c5dad75d8b044513f9fd966e29d6d1f95fea2f62979df2d1273c97cd09223071af01e52b69c6d72fda89eb467aa586c9758a6c443b2157b44fc96a80bd74899d4f9a214bf584c1629c4e4773ec8fec9a058359db13e88959e0ac44a5b6bf2531816592295dea872f97351cf81b73eb196df6c5b51a8097ec21d9345bb97c759d38eeaf02c29b072aa7c957967673c1579622f5a66488caa38341b9bfc042d6956d697359e5aba72c7d2cab453abf8c205821ce01ce806fdb87c76b705c45d21c3720541b4d397727377a925d0903cf4088baf74400c2b5f6885b50393c93e8721e5c346133b756d6155d3a88d34f62fdf25626a4d4577c2313b74d88c73a3c9981806561151e172d902cba9b785ff9806b9cfe1c342af5b4fb44878ea5078891c7ff9e2ec13a2723503e08a123af68447d562b090a1bbb2f2609fd1541f8959ab6d3af9156a64068a6a718c76c29df4f70826da68f57634f0f4457a30f0899aab72deb7d5376372eadd3a508cb27aa47e2750e05ea41d79aefbc0bd95936957e670ce74952c3372929361015f51866aab35ae66b0fc3c8497f3697048d49b31d3c5c2b1a0ac17726c8da98e823ac7563c97047c83a89b53adb48dd5fa5c9bb5f63d85ca64bb9872290719ea4c3d062b987fe052e19d398051942191b881db7845a891a5a8f7b77267431e2f3307543f5262fdaf7b702d45f7c2a256c6bdb228c43bcaef58e5e2173357f548f33ac28184d8ee63142662834083b342de26644c1c0e981c75dc3872e861bcd1b9d5f77eb1af2b9bf95938e9a3c552f9843e1bb101a1acf9e92e2a7299773133116f61b559e8a64feaf0a14dfeebd31b892248b80025c15ea0304d170a0f0ad08a94ea412b485a76eeee46489cdb9c4b3b32b71e692951bb7e76d572db31dda0f6354259a02e15c6281d3cfdd5b00d3152e0d07525f72de4d0773f17222ce00c8ae636518100e442b86216831e9362698c0a0d6d03dc619ef6b8d607d1b75b3b56d35be9dfdf1ecf0819a755b72a4e33b5352b5566c1c19749254272bd710cb4b1cea84ca3a4116716eea4a5ff442ded4860f2c6bbeb6d0116d77a726edae0c2236f8ce3f27c9d6071b7e64f72574ee190841049e5a08cba04dc217250b8c33f738408f3dae3893d26066ffd0bb4293e15747680c2beb929eac7d346634f52c8dc20c901b2d74aae64635293e517674f3d51003e4b01e671c8a15c047c5516224370194113cefb5c8400959a33ed64024dd6caa6a86410a0def124620dfde129bdad647700304f052b94ab04893dcc41618040ccc028bea4857a8e724f099f9e94069ea740fcf8a5188b59b38bf2224d95f904b3dc23293bf157b272ed4fc210d56f437c2e848791a1eb987ede14e6ec9c58956fc74ffb2568c52e0833d24a741c1c269f0341daf18e984a81097676d474f4eb73ed0ff252258ab53468ce4195de08dfaf3c584a7af389cb9995b987d16850529a6a2e5da796c6f322d2b5c8c226613e721b0bbcb07a2628eb1f17f5e2bb7553e416108699e0568472f77b4f7ce6d5b148f2df952938ec4dca484e033cb1c06810e594230ebc31513971a1b57ec0509a6eab23efeeb66bdce7abe1da04fbc6f2df3c8595853af9d771d5907e7ddbc4fea1d481c0f7d4793d380799a5c7d08c9df75307409cddc5e42198b5d27678dccfb324455b39da1fecb151b8e230a34be82a2a14f53f2281377262ed0acbc0826395045db99d3a99fed6630819adf6e5d4d85267a13913cdcc72fd7ee905a5975ad6a1e45220bf7953427e32c7d9b7a635e3dca60ba5a69e2c72c9664480502c1b3d3a35d6c9808636cc1b39a8eef7411ed00ad7e901c91ea17282a91f80be48dfa45257c1c679aab4d4d700321e452354240537ed2679bebb6d8269338f901db1af912766d2ca978cf83edbf083963eaba8a54d2010becf5e722ecd027a25f9d3b84786d20a0b1077b4fb6c2045350e345c38120c047192a7729d20cf53dd0cd6fbd7f7888533d1eb699611487009851ede01481fb9327ac6725675b4d00722a1e6cfc17fbd53260dd3e61c1ccdca16058ab6fe108e717858729cbb650503e1aeec3aaab59169d60eb77f390f08b922e1971e11bf73990a074e2f421d1e7d693cd27acb6191119198d3f13a989d5963e4ea566adf50c13584727fe4685d7dd3235bc8c5553522d37815f3aacc0ab57cdd46bd3973a481c1ce111d2b77df52fe34cc62be8d1196d96dc4897e2cfbdc6d7b3ac90057cc76ad344b8f13521ca7e6171bdb9a6667502f8061ee08b563fb1fa3147052912ab74c2f41336fe1dbd59b0ffd17a7cbd793f72ceca4fe9e7c7ba7b9360f03493b5d12277262777b9016882bbc0c18c6f38c229c157a8f5954590aa45df1316ef9d4602072d681e588b5d3355b2a22f335a43e96da3189dd93b2a53b372fd70d61ab07977280d9814dc30f252a2820ccb5cf0ead26089d96e60075a10dcb9f6e62c99943723b07c6f15bb9749594b2cfaa03ba963c689a9c2a0396a41c77724c9b5f76e8724d755883e1af9083862e504da25d9423f3316e9db68fe3cbd96cd050c8055a38eb153f040577864d260a0cc1d81d6da4a28815f64ed671d4490a8b097f875f0aba950482bcc0ee4c482923932aea7cd115c28ed286367c5497874a8e89e53372609d7f05b2849a53bb4f88158aa391d04dda8281de765a72388f3d6e765528522c1efb0987ee278aaae250ce85b14b339898800598f44e9980099d2b112435729eecb100e3efc2d8e04a77e90c61e0416e2b02980b7a1974f36e824e474ead72c44ef930731a260b8e516a9897c899bdb1a4d133e3d1279b95d8c194783d220442511b00b6137d0ae52561d60eb2aee37490379f35ce944e65e9c4dc1ee43667d6d2da2b0dc8a2454e9b43786acf0a5fc896c3c610b709a261eac071f5d59672e4c2061f17e11d525094219728ee153cdb54e54c8577ed1a30a5b3a9c2e71a726caf2055492eec30dad5cb88418ae0abfc815b999d9b1b2ada14a16dc7a194510432ad00d2518fd101e966fe90d5d076fcada480a525e4089556d93f6d413772fa85f65340e87b73068dc9a895af6ae2b9524dc8d30eb622a5acc608d22dc27289c6d39729c0ff8d5563330549ef1d10906f560a671e5aa0c0c0e656c76b44721f4073fca466b6fc2589da021405830ce8c49f491169fba000a7cae760d97072995db591381da58390a16070482b50ed478a8505771b8c170a7e31274092ba72744591f658e7de0929900feeb62c42559b9a27e330ef9364a16855ecd5cbcf7237ae3a3cd8c6179cd4b80e1de24bf2fb0cbedc80eccf68591479d2632d04a91c1ca23511d04b41607d2a3357ccc13df66ddc65b23e40a07c32e715a818f24d7290af7f02ab684b829e6ceff7ada7a667967b7074fe18fff8dd754117de00e1074b971010ef9ba38aa9198165c69cb04a5c07d1742698d23539368aa404931c28c3fb27fca8a5db33834c4602f5ce6f8b197e075220dd3bc892e2f429ccc4af72186fde016acb951c5fea2ca0ef3ae85c4883fb42b4ecc52a0b7f53013aee033df847539d274721b7fe8a6fd54d68befdf1224fd354f33bdd37393eeb2a51e14590cc885727898bd68c4fefc265420538d7c8c3c18830ba1dbfec994bf7a709729745c0ddbc9b8a18bae48766e84a8e9ff706f0642604378a3cc09f7fc80b5072872edafd08e891d283b199624ebd998523a583cb88416e5d88d28176d8d97b2e0a1cb0c471a62382219e2583e5b37213e6d715d055fb6a16578e2489687a086f299acb58d0030a7574c52910c1b42e18a40b5a19970e4d933bd15052a0c63572a473706c7ec4ac8b1ce90e44b33dbb731f5459892193fce382fae55b2e7c61631aa49e923c65565d651d6605f667bcd455a2076565bfd3a7dfad965e94e31e29d45cc83927c425c4eb3f8adf880b27c345df4368e0b78bfc7d87e5f3e4bd122313d45889c3ff5958282dc1a23ae38acc94db58b64b2fbc77a0a73c3258c72d2316afc9a27c41a2b467338f414dccf4bfeac84815251b005abf59f448ac3f17721f4caa898033f18f90da10eed2ea64a020fe90837beac530b5c1aad9c362bc72a355ad0c67a79cd941aa08f45e9e919b06a9790c5d5b978e87816683d27c54393002dee405cad0220a78cc7ee2dd66c57aa486a50587d44cb894d37d707e397272288c1bbd11d1bcf0e201772ba48bd6b855bbed1854de6b3466d774cbe1c06912b04e5e3689d9ef4378713ee3723c775b722f2dae954f133ee811b2fc9cec06703b31c98892de5b43a0d170cdb70d656f5980c3d8380ba9d8fbe96a23a6e072e500ebc1d0bd05e21b7635200a33d54dd073d2b1630ff55062a2a37e4a077f723de345141718ff841ac26bc9665014635aaec4fad676bf794f2faa618971a672ced04461a797b4f8f2f568dc3c29b7d8c9c593538f21f63079e2eb8e18782972eaca856580361c3395b0564af3c28ac12bfedb11d7c22f24e2459a1f578def2f3215dd8e80e269a35a220d5c78e88cd662c72c2d33833cfc1ae90354389ad772a914364a90caaa6922be9b67ba5871ee8ed011aa3d057c91b574840c4d94533cc9fb8e17b65c9b6412a3e3a4de4216511c18d10f5a18adc4bbb2ed9c1a18e34786b15df10d427f7b46215b368532942ef4f8174007df1eb0eb6e7bbe33c5e4729748901153f378e43b930fbbc7373a62d557ef6c48d5ee3e3579e93b3ef17d470e2d6c904a427b3e5286b3a5d7018bfdcbf7214eb94b745e111576d9ae918572dc3a8e53681601f7380ce78e07e5db7baad64a870407088056b157c3748b91724f5b795eae93f2637becd3b3cdb62632770f7f3c5826d11ad9a7c0c6c5b2a145fca0cd0afa4a3a03acba0be438af4f475c0a0d7042749e59526325fcd6754f0f38718f44b485eb214b37c66ed4a2398bf29fbd66a1da285b639f99bb30070172deb7262e06b8211df2f3937d3cd76d77389484afefc07ccbcc06f33b0f7c9d72ec8febdf7b2f11640b7821d0721869e5eed111439dacba870f20cd0a29a90865301f28bd1e7c26aa9046fe149a7d5c1061a3298c28dd22f4cf20aed24e0ca522d2f371c592052d194640a041a804ce231aad440f0ce8dec5b8938b272e046472a0bca1fd315667133a0dcf2a50d98d2978b261cf66eb27f4b02c8a5c829b7e72b38b7dffb47d16f566f6a9e68323d6cd63e084fff0196a47ba6541f057232d723c000cb84c3548b8cfd695937fd41edfea748c6194fceadc46fbf4767969c87267142213ca85761694f871be12189d1c2a214ab83f71306485a24ce9b280b07267b0d1b26ba5e56ac5ab108ac158d314d34ea0ab0cc353f7deba8889b99698064e9c0dc6cca3d0e4cc5b956534798b5521ef299d018cb3caf6dbad795cd86d7249501193da75a8132137005a322715f903440e293e8257a244ab6dc619baca5d2587e4e6a120a355a1a1eeec87ba278c5d8272ead7a0455a9144b01062e5a52a987faf66610255dc3b4dcc5d46d4622ec57363c4e20109582de47d54513c1372a20107ff37a30aa54d8061ee737e21b9ba8f74abc640eb6f99775e1391ef001bbc4375ae95ca766378c54f5ed0bd1b402025b8d78ca3a7d11ad043ea65d7af72dff3c450890160e8fd7c2556aaa60e2f02dfe8bccd291afde5aae3acf29f21046dbaa5335e88d534f6d429075bcd9c84c9f337bf92e344ef647c56aaf22036724229c181a0a8e54821c2159c359669e3a1a345f24c80e93246b8a72b2521226b9f1caf5a0c498bcbb695d3ac0e8c881b01f8c5dea991d5e0e5a07eb6213fe9060075327cd90d1a3efc4a06738007c8149fce7ff411c995857b65308d70de257246c9671705001d1e6be0aaa7c1d3faad32d4a9be94289f507a0f3eeb3e7ceb0fdc001cd09647fbd5eb9973baf82b8c1f04b28a273ae728655c66776e35b762274e99e9805b7cbfa6a002beef38f206169d7e6f7d46843ea3cbc4b79a69fb0240a598ea6ea355ed7bd381ad3378931325e4fc9f41e7d083c6eec7cb7c28ada3729c381321085153ae90b3b4649361404818ae17381e687b503c5b524a000e5e7220e3014b6ba8fe8790ef817afb85f3d5b60c8133499fd2820af84e7081000472a2f727546d4b93aec7eb24c1a7d88c66531b4a5a9abcc1478d40fa439f1c3e59736684ee0abf26aec72a287b8aa9f6d940224342e3869067b5e9e94ac98b49720c67a6fc6a55819adcfeae5c460f76485b36a645b088dbf7b1ca02916d204c5d0707cb41279179df165912076ba74813329f1f36d0ccb5b71352be635e22ec721890be446463f225f92a33ab0d113821eefe57eec1b205944d5a8b6287a9f272f081588543d8e9dc374e3b3d16824e8339dac19ed6df910855054514a03e04203392b8f59415f87fad43deacc36102d467430950568f892a28eeadd0e575c5721f4212d8934d61eb3fd589f988e435f6070bc9f413d53791138ab8902d13bd561cdb1125a0b1755684924626cda5440a26ffc1a2b123644bf8c2f39fd1686672e8b2035fadc3a7057ba342b7190a1279171ac87e4c52acbd660db439227eee29006f631d545a9119b8f6c2437f176e6909cdf0be5718ba7dd284ad4e93985c726f8b857d86609b3d3666aeb1345eb9095244711d5bf41c5d506b31246ec49872258adf247ea5d482a3ed0a53560130de61df318507ea7ec3192409d5134ffe334b84ed5ab3f584e0271eb1091fe590581cb3ea2aa56f28702ad10e6d22aaec724e9f31a3e5489bc860e0269fa1ff364936eb302fff124cb62407c0927aae1572fa4e9029a4b638b1e05663fcf1a956ad304499260cb3c475a331dba5aec0a632de3bda562efb95ee0d963a6114eb521037c4766b2dddd4b659ec096e8bc5e10a54f002399b398043c5842fb3cf5af9d04a72101f1bb72e7956b002b741a8e17213e748bd151be3749f5a2a62c1ae50449e24028153a793ce96f48c8dbc949c3b8c2328501d4d47f0063454df0d1b268f8e60ceb04960652c565fab62283bf272543c25eaea66618d73d4144fb28b1f33104019b90a6a535ddada0787354b1f063202c42a9ab99fe150cc49618fcef2d0f0b7768cfcd7cb11287b43c0226a9472a4261c5faf0b116d83119b6868d3d162cfe4b5480a329a1f56fec5776c9cd2724babd1893c2169fad6c3c5a28b42e88fe003a9313b8abf8f0518a662397dac72b5e1b0b6bd0bf5840dec8a51f46dd0557d85af4954f82f046d6556ead21a274ef9ac507beb2cea5a4219ba2ea39b83061642a17d88389eb9ce9341102cc568725a5042a5ad6af475be1abd5389f5cfc499a15a24aca28ce4e99aab148706ad728b7db80475cdbcf9381a25c11872c2204a12e4992efc9c53a5398c37dd8c2c7287a2de5e0450555edfd6031eb75dec48821a155b52cf55bc81cc23b8f480147261f30ee014f170e8ae46a79ddad9d145ee532911eb5cad89c80d1f5c3f6eca72a88880803097930cb7157ec59cae4c377719f92eda0388a3af225b414fee7172d0369ce35b4bdfb09fad396fe75921334c0cd870d903ab4a3610734bb5aa3c7276971808ef0ed899c09955e6a30162f7d806136e22a2c351983eebe159af520c47f0f363c4f023e35f224a53d8121fc43412de8843f5deeb9c6e8a6f2339c239c25ece1c6bb3b88e7c2389eb6258f1c6d319b95db6cabeec48db92edd1b7703d123cddda30f3dcda2842f89fc854b940adef8a3476ad1c251853164e96765d72659bfb590a99fbe3c0d2dbe0f2917393b13330a2513e3a0dc0371db5fc9c3a72aca562f7191bab6a969142bdd9fd6126661b9b893796cfba7ee37c89d77136275809a7617cf28b6e7d558d9fd07237943d909bb80fbdabd982f60f242e9cb372faa102ff9e7364b632d3d537c0d861cebb90fac2dd50d53900967f02ecb6aa512cbcd2ddb4bd16efbb2aded8cbd3d16d504313e8489a2dd822b8ced46bc76f05688387bb86706e1254ace9f07da355435152f16b0d0f2bbac8d3feac19cd133d1ba797012d2b23f40e75a7e266ac60668a48ed129fa2fec48db79e0d37dca1725213377c434badfd9ff3e3a8659a993e16e06c4307ba819269914425fc80a572d42f4f0b55081bf0a7e206e6a3a8b6d7e0b87274593fa9d80ce49815d3133e1dfdabe70a1e37164317a01cc7337f6c504a9b509bf90bd2464a4f97dfb35c8805ad37f3ad375ecfa7eeee0b094882572242b8a6b1c8765d629f13cea7e63e34301c91183cd36ec32d53eecf5bbac0b8b1bed06f81e1c8a80761c08549d38b1c72bef9292330bf64dca007e3bb5f8fe8fd2b21840e3eef32d96480c8cfb9ff12246c75da21f21ad979dd47a66af80e5eb6d4f90e8e110275134806a05308da18724dde15d3379101579e791681ec4701ddc0760d0f3c085257cf9643f511bd4c7225400fafb18847eab15fa4b2f0867db9325427b1693b8882a4657f09738d170e3c61d6b00037d4052e2535e8b32d8a1986273b371fc0a05f0260e5a3f8113e72c31eb50c70f80002a8208166ba7338d0a6a6cc8bba88f2d698833ee32b8d3433eb3a36d244d6a62c7e68b30eeec8a6db6646476d54cf9a36c12f9be940c66c721c567a07b7f3234122c8ee4cd3418a51439c7411be0f30304808ab21b248b7228441490115f2ae714f6d1ed2cbc9a225bea1e42a44a59db38eabc104e9f338615fd99908226641abe3ffccd01e50c8cc3562f659c9fe4c53b27db084e4c38072e3474cc21fe71210e3b2755a25dccff325ba48d05320540ae1c10ee5fbe02472141d13e3d54fa262365baa014ef546a585b5650b198efe739d0e2761fec842722ad30b378cdba5dca442b3eb4db705c834893c2e963643a18edbad566650b272752a240ddc4befb36374faed4a8797fdfbeced5d4cef1ab39c832c979d3c2172a1add9f867e6fbbbc41a43de03ad8b26f8a71f4401084a201d0abcd5785c4072de65c7e4549608770ec32da9e4d1f31287510ce28ff6f1d0415d70d87bc0005e84f0f0d886509aaefa52177ef923f1bb0d7ba4009a5ed6523851524b89e18d0ffb80bb5eb8704ef21b91f29783e6e664df19e7cb599ef2bb6eab893d66189072bee060bdcf57c2be348d183411bd13c784c78fb9a9db8c352f8d9d22f691e272c611b6acaad6869b19bcb3ad0d08023e6d0469f6b29582231204950843cb6372e063d554e21a6d09d7bd0c54578542321b36f22d4de1b107deee217808139772c2291096956ff7bc233727a1dead0f2211b660438b266907dbcd4792b369324053469c410d23abb6651b1446596247704f1b7723fa0ee59b6e899acde742137285ad2f82f0bdc943739a04ba273880633b9992768a386aa387cde4d81881067281a789cbff639756bbaf904a548fd70595fdbec400d2154b0fbe9f2c3f47e7721a3dee622c17426cf827ad09a91e8dc09df6b775ec8435ff366ebf28411bbd7240427238acf3dc507af2e8bcc8a13af2ba3f2ae4e384089b1469fcc65575383feda9fe90c2c80f68daafd21055f392b30ff7d3f52cde92087a16b001631f59728d0e019e8a60c7bd7a16b6b9fec008a4c2fcde12fb94a30593d84045aae947728fd158707fd794beba117962cd04e123b122f250d64c79519e29497c59cf1372b96708214ee6b9793d4a5969744885c5737c68b4760f2a54e7ed3734415f67724b755cea22347b51ceb443d37f0fcf5684a4666da49089436992fa9a0eeccb72f312afe13614ccb89d87169c252a66d48cc4d8f0d395a3df1eb42c6f1864624c356e16467969da78072aae97974d8a04a94d9468cc6b11deadf6ed391393ea7217f411ab73c4c02a317803ad464eb4844decbc918ee4b6004a6d4b0ebce86072873db6c2837f6127c201354c1079a1ba4445240c05ca1a680f83e400a29e5372c7c3fbd3307d987adf1d4437d2c23c3a75f4c82ec77cea157af7d669134b8f724f346f379d1c87628b7d0a41656ec0263e2439ce407f6c3942840bbf8b812a7225aef284ab77d368bb2d790f469af1af9558301cb905e79432f0a79885e4a3723b339a28ab08ef1f5249810d6dd9f2e9362208988bb1b33fe0e2fd03ca752f50795cf1a77e667df7167e211077e5a89b1fefb9e4ff122ed7eed2d0e2d5b65c67ea408c1b287299972a97ab5a5fd9d5c2a4d6d40fc26a2ca6f9477c19b3fed86dd69fcd12552b65a21d9519d52fed15bf64912f09add2247de19eef7187f60172c6d358eb3c758c658de8c36ee820e1fd394a749a7657e421ebc792cae3de106729da6ce034a4364ff26babda38233ce3f4b6f75e8bf9fd3e928eaacdee7a8672480ee4a2a3c669b662f5ce75a28d0993e969acad5365184d6e9ad7ee860b533da1f51176dfe6f7c372d1125927bcb7c97e18fc0afe26bcfef8d2009943a7347231cf413bbeb7716d74c97312197ba30dc7f8f387eb998e6a427ec42caa0aa42e889dc1e328f73e97de89a2f9db4f245efb9a7d59eb58daee8455828beaccb872d1e4edae2e54444855bd82a914499833f06ced7f60d9a5423cbc9586cf2a733212a38751d2d004fc0470b8aeae85324ed7bbbf6b4d9811b381a1a5f2196b4865da33f3dd31b7fd8b596516ac8751be0d43a9c5d64bdbb710cb67f5c906a23f72d8a704f9449049cd709c5b2975dba3b91ce5c1b5a884d8b95616808ac2a7f07245e14b60babd25e23caeb8ab5f8d9abb6c036325fa7b056457d0f56b38af17520ef5fb8e1831be56999b3e43b8ad9199cdbeeb48f9f2253c4c78594ce3068672cad106c1a1fba9e958623baaf4e3e4dc805b9d6384955d1581d98ce019274f724b5511e8f06244ca51d6878fcc6ca566670143ae464d05747800ea3ee897f872bc04a2c9d6ee99baa8073192734432520db1219ae8224355d3df6caa39a4761d61c8eb0de6baf6b0d48127fce3e6d079e96c594b86debf69f81eea7c59f8b2729e2f063263d29cffd77c005fc97c126e293697c2d6a38b2d2edacb8a7a604372b55c08aaadbbd0bf2d859a6daaf66867caa2320114510bf64326f52acc899f72f7c6efb81a99e24b516a3d538469f4c29c13b39c7dababecb1d92200bd3a2f72caeb2c2cc98cfcbc719f7e449e1960be18ca6685b11e818969ec310c35e3277216a6e55345f78fc1bc2635d08f1260da9201edafc6b29b39e59e019e5cf292723705801e707cc83749c4f375e68ec38355b6c39a4e7f6c8adc0e1ed29e530b72790639001d2a32003d4fd17d982a5b050615965096c80709c0002812925e64725403d4982b02bf89004f5900dd20534c796827dbec7db4b4d669000343be4e722398b7bdd394f8cd800e52b1d5bff65a6bf2add02c5d962d99ce52eb73f1f272cddd794de4f5c88a8c0d08412a4068c543e9e0dfdb6271395e218205eeacb17279770e27521943ddb7298a1ff4fba2a04ae842902be919979a16a5b29532467244f315382fd54d1761ac7728e34e4899ff080aa235fb0c6c848cf4c2c3477b721fdb2745670954d8815a68f0a576b321b3b4fbbaab0092380124bd13badc17728353dfb90ff68d4762970d6e6b708196bd18945e27a1fba26a3ebbaefaf71b22676af6e0fc107022f5fabcdee888d7a944eb4c217370550b8d715cd47020444a31d03b219e1ea6d8361138699cb4f7e226afb556a1d55f4335513f8c86d985723ea9861ff1bcb231ed069ffd2c3beb3d3c630c174f17dc2bbaffce8fb238a6028a8e9e3b3af6ec8796671c4d77c5896bb0850e02243e80b459b84900f50f9e2e2971c931bf475d2d8f29d55681733301c3f6760d5fbac7b4ed88ff887027fd72eb4f98766e2d50536f05e8af6c98d3dff58db0141fdaf19bf7c3102f4a7ea534a272131afe1b808e7eed7ac8d7440cbbf5460151d9b85c638840b0aca35e9772f05716956ec697216dbee226dbed3b978600c7abc98447cfbdf4ab2e64782c5e36e37fde6944743f57e5b13b31db9a05baee0f92ffe76f39c7d5301573dc0b46fa50fd77866ef50afd625ad69038dc2bfc0d53671c1884ef0c0fef52bfa7e972c3b4a708e8a7e47296f34786551e7ec0cf5cd673b00aba3a1f8369cf6b00d33cee2a0f73a73a4a14c7dee4ccf42997f7f45ff5a52bb7b4d5b813364d8b009e722b2c1aae488fc06929310cc3af2ed3aa2a7dc7b181afa90ce817e4b812a62f06f18cf2dcaeeae7e8932b0ba7c811aa858bd2828333adcccd7e6fc8f27582fc14a3f9bd7edbd2d8a7eac03032290bb04e6597527ef8ce4922ceae2793fbae6f7203b034343ba74d56c7f0843e00232c667025d4466f7b546832d21b8508d207727b5163b4499e2479674b12ad4eda0cb418a9ff0c0eaba504fc9fa0cfac331a72b450c13b0424d7c39b15d4d9d7872486e853177deed1ff763b47cf8f2d23a072990cc75cda88b736d86889962960ce2f7f776f6cbc8efad4ee6285c4f3247f7227bdbd380b2345e806aa7d1e67ec38ca79600a3fe1b932cec891f340de7c567249cef128edfbbbc659f945f71570124e71a45fb4cb2567314174d01f66e54e72c4eecfdabc0fe6d4bf3a7458232efb02480344f04bdd1915ab96bbc2bd8eb369d49243b2a6baa51fcbdb779b061bfad17778c11b258bfe782af47d718e204c1ca983565d2bd418607afb6f05332368d66c363528fae8c0530f846fb3a3d84872bef641f650f658212036ebe6816791d301e4a383f092683a705f8120545ddc57f156390e8c0d9e8008c22bebd09b2c5a8453757606dfef591362a4abec5e314adbfe548a199feae25f3e3548938cd18dcf664dc3a501b4b0cb0f4695ca834b727832225ed18e303027fec2a3858f39e170b732401c89b2e9a58ef0c6f904f17286763710ab48d3b3e205730aeed7f1ba3727ceb3090e6ea421725e1113b46272deaf7d271b9799e266c65b37b884fff40bcb327a0abd4c9c150df05ed9396e72c903f2cd893f92203d3d98cbebd7faffb2735b3a3cf713cd8b3af7d1ca80736cefa940128fdd75fe628f121c390dfbd55277173a113ad1b84fb73568f8da7872f95c4eaf53efba7aae5e689305a97e830949a8224b0bcf4241a5e27c9cc9d872a6826eff44942ac7f91d12d3b37fc0711ce77d9ea00627db7d74be94e72a9672bf3093bf9ddd528b88ac5754ec6d056bb1f65f5b1f287c2f71339a8a2e4f9a727acb91d4e68f2ff9f1412fb04eb073178a80b059c1cfbfacfc86dd551378d3728caf974d08c12776724523f3f23107e6588f46e497d9d04e8341b0099950897266f8a1ee766a2807485e603a40ecac9cffdea5851ddd09228edf8e2f2cafb950e35c002d24d4583d960ba517b18caf75688da7d2cb9c58c47ebf7055ce885f72b3a5c9ae017f76e25d5fd8eb08b993c6f0b3f77908bc0a6657c0c57375ee040a8c28212536016561c0c155b17bb5a6f1af1123e1fa98d982564cb4e7eb669e72efcf5358b54d0c52a3cd364545961a09a2b724032a42e92d5cbd0ab232f2e238acd7701fc1b165b57e69b92610b214c20b6705d472b1fad5491cdc439a9e5363c4b15274d9857adce9236537ac13356f673e26a028ecd075827ed2c1a973b1722cad2655935283e82f0847ad95e9990a42d4fa781a22f1efb75bce87fdef01480c2e85c41e805b453afeaab05b6e4cd847639b4e4489bb657dd859d0dbcdc572aff7333d765d7c704dac3131074a18d40e0f87acb3c5dfb74555e1218a9fb158d7dafe6d1102ee7978ccd2f6c4f24597b32da8555f0e5113a7bebe9b05ef5e72435bd8b8c8bafab9e64935015525b224e0783e890f5204a1685ebe32569df2724a23791c7a5c8bf9c5ea181bff85b85ee465f26a77c28ad0555e2dcc1df6a07250dce1f62582190e713740372da773d3ce2aedfd6d73ddbc54ca21ee81fe0a725845046febb39231fed4f54e64ab247a7f9c7b2dc7f9c5af8ccc72a7ec5cb2724b4d7015430dd1649fddc77392c7dd1a573e2872986596a383ab7fa94706a472897ebdece48a31abd4458312437d093be3a4ab53c357c6120c99317486cc5c33ada8c3c03c3aa5a69e8efd5d5ad8c8f4c3f2f0e4445817a0a7e47b9004924572f3f87270913824a36cee1937bbfc72f36d74156834e55cd09554c527d14f7543e8d188a4ab137c928a4472500310168731bf7a579f902ccb36a8424a714404252c0cdfc211adcdc3992a66bd76b59ee8e119365e320160fb59491561338842728641a3b3630e43538d31c6fe77b84a85d1cd38861d1ebeaa718436a4cbe14e7297c85c6344505f3c19143861df517b5201ba362a12c44ae4c6773603f8da42726e22d9a8f9caf902f011e9a644fb791efbea5c3faec2cbe23cedc371e7f85c72db7cb26a433991ee7d5534765903af08f0101e80638e9b46f55d0ef9bcb8cb72859a8168ab86965b8c22bbde43e546798b4d46a48ff151df23926c610bbc4b727ada118cfba063e7f85a254da0e32d3970909b1634eb002b6c7024385042907262de081be1718f44f25c0d7b3ae962f0117f8a25d356a8455dba2bbffb611812ac44630581d891d3ce4526c0b10b02b2a5c634440bd69e285a8129c439aa753191a5e452e29d4073b199dfb3091fad381543fa3951ec1fbaa75d2276bf681b72ceb8e218439bf800f8d5025a51e2f862c56fd6c68ee1e015a4ea84a90f8fe53de58607361a9437e8a8eb6aed560f233de9307b366964cf1c461238fc9f9053724622de0520c0b642b22123f01259bf4f87d92f6aee7aad9f83320adf69547572152cafd2ac21d65557fa1c2c1d167f657c82164e093e6e7d6feb124325abb05d2b887cb6ca92545d813cdc8583740fdf570a61812cf61057e2c7b81b70d28036508b7d4b756ac571f682071d5495d3b5e014806be9b080c9b53862846db2db721d6cbec34968f4be50c041d63495f4743170afe3a5e4afd23f0ea4ca8cbd79728ad37cb460c3f818258d667e6550f318a550a2e2afbf4be8a965184426555b6f77f17f7c8d49f7868c323c2e64449ff512be8406e67ea7fc334e9567cb9422722973f3c5a4800316319043b5e0ed2f58c6648a063be429fa80a34510522f8b72825fc91c166b98136afe7eb89d96743e6a3b77d5fb681199145f5225a7f3fa57bb4aebeb2a25031470034d904f1a138cb91a0164f9c3e4c740cdedbcc40a1a30869735c5c5702c164ddadfff658798d3504cb98ce03aa9c56babc329d84a6e7210aad5ad60396b0634ee4eea73b52b4f4f336c5056a5971560c68bd8a4ccd3562feb24574a17db949efbe98f6d9b3f18642c2b2cfd635bcea93ff1054dd9da7224b03d789529a4c42678dc0958a2311a0993077edc75cba169d1314fa13d1372117d2ab0f81b23f7a41efc5f4d7b5d44c17ee5a77f0a1c3b2d26a3be4b833f276cfddbf7481bb88f7ed54824978ab89de8754bf244d6cce98925c24ce4594d1b1990cdd494481f3c81d6c8fe1b2de10f02664ea7485fcdb243757126bc3f9c7278ecf572ef11ac57bd82a405f919b07240c6bff5a60464a2a584666206cb3410b8691bbbb54a484b952c1c2873d5eefba5a5d0e891c564bb85bae34bc9ea552d866d6b0592a11dfc49bb532537b65690fb6a75c87a5a2f4b6337325199f32e2e0270880f32ca4880c3b47a20f8e28ad98d65e10f9afcd37cdd5f166cabd1fe5d6004f9b298e31811a5e3b943990be42b119a8df2e0f7663fc0ba7de643ecbb02b42cb5ae513de0687c892877c51f65c7abccac065b77c564b4d09b43abfe112f39e2db7c457c29a7fba553f0f686927ca07f9611a199a1d40ee9cbecf5cc385fe2899fe71aa8af7da5a2bad4c8a073a0a6f00395cfa1e3fb9f8b92cffe1af832e389e5a18db65ac849f3c5723208fd4b8b68c60c34ed911cba7872ed9cdbd76a11a7ac4a209fb186360d7cb2964d61cfc7fb2785538682820dd4d647a12a4d1592af2ce8417f93f4a5ef181ca3dc0d8a4b648d20a018d6c81e4dba4cdef6f472d0831915326be6caf97d989e20a22eac4e290c051d6f96c41900dfb3d66b8a72a8d323e9e2d8d4a2a5438910eb4dd6ccfe02c113c5295280b658277f12c636726d06e6b980cd81a81c5bc43dddf3163694f384da3ddfc1abe7b310d2a5b49f692b2728507222dc2590599288e709602cbdcd44927bf37e6d828e0f45f574937207a822fa4ce6b95e3e98ae281a73598c58913862f9285548307a2cc842c066725211e18b0bc5455d171cc86163bce64ee358cbbac504cf3c1437502c8bc7cb72fd19428d560493ac46a4aef9b410d83cf8f6b646142d227dac798140fd0df1659dcdab94dfac178d0fd04e4ae489294f47845575e40cc3ed3bbea46164df1272e8a3a04409fc130c82b8d1aeabe0c72f9d6d321c2ee064bcd8f964e1c6ac9c72a4a375d736171d109e27399a5d1c4dd3cf55847f9206d63694b26a95e7871f6311ec1af53869c63d627259f902c072ea95d8bd9aba20549c333e959023c9742fc8e04896eef47d250285de396f1bc0d0d6499463e12c818d3da1250dbaabda7226090b915a73baf3cc2ab140b4cdf15c412885785d55dbfb7a708d0a9ba03513cba69ad84629c0f9ab447748090e778c6785f91f5e25c0c75b2b9d070d0e8a403f353d62043c187e071150341455209885ee4fdfc3c14b578c3ec780c1ebd4723a055151472077ea09da82422829e3fc1993ff753dedc4d80d1a14dc1ad84a0f2a5bd4085b7bbe142a94feac5f3fabfd2c1f519a0690af08a8a26331ab9b6872cfff7d08e1c4314357de4c2dc730f701bafd281e357d3e0285b5390b95d3427217a8a525510dc26639b1dd44321317131fe790859481a8f9f7b2b51c25861462a1ec8eb630c361011d7e2a75591b4c558d1a91f4fb861087285d287c64a11e005405df4f4ee0088667073bdab9301e819cc536f8f2e61f53053f6249c3499564d1dd2dd6d2043f0b44b24195872da1a89ebef521571047a724becbc2116de37286d2e50422eb5bcf28a46528caa5e65a8999a1ccf2e6532266b2b5a07df58172fd2a6a63ab39e8473359d5e0aeab5c03abf8d0fa741231872e9fd75eb56e197299b45f541c9c396cc8f42e6cb35fe4d10fb6aa23a3901cbba19bfc9e765d5e4bd454d2988ef5dc1534ad402922621f5ae618e705b7a386766f52794499d65d728aaf0b55c22c4c36ec122398651356ed7400e97b2b9f501053fcc1f3a21f1c722b9a65b10df67fbacde6f7409289e73d2ef95619afcbfa4b391b61e9082b897095fa719b04261fd2d72abeb3a7f261acb7cff7d900bed0ff5ec045c369805b0b08e62353e16d71ba5edf621934f6724d27595f1464ebd3682c34de813a25ad44b6f2dda90a911ba1bc60b1c6d7b549a45ad8b9f80e6e3e747071f24176cb25720cc4468a49bacdadd3c43fd82eebcbb1b87f8261fc111ff1572832c5849d0a723945fe2101d55b8ed3406da9bc6e0787206679950354d22e3d4c2f1e1907387261f5b50fd7689aa7f8f44e944423bd7ed4219ede883bf5900ae713233be0c87218afedd8467c8afaf9ae57107d6d683ec5ce54574afae0c685964d7f3f6e0f723429969642a0aa494bff0c9cd0c25e93fb2b6eddaf166a00cc4fac6bbb872372c72ff1af8c13568836f1d93612b411359ebb0100a8ddc965623ee864cfaf6b72da6939a64e24d644d4f6b76a7f796b5c88505d6f0dd70932784a09c531cfa766fe7112114618dc66b9d8d45e3759e2533dd3b23abfaaea6f854be37982d0d42555312d11626d8a755fad37253546abd9ddfa54558e222b3d3f6f1fecf1f0591316db07f5c43e00a0d912961bb5dd2a0245bdd5d4c2a5d84a124f72c5c7a23572dd3ceaec85074466cf2083027c524a801658ba66cdaa22af951039617b69c2472874623b5ce34bcae019a9b53ad611e3c4127ec847247a66362017b22b21533e1d83d37515c49c6576b4efdcf58f829668fd8bd32b77523c101fbdd0eab0d072ec134b1cbfaa2345b3c9097284c662567dbc19ecf86da49d6974763326493172b2d446f0808a236ec830d207b2de9a912fe6f2820202c1dc13a19eb376d2a2727e717e552403c933dc53c3db85dfbe4b7934ed28a1e67dc4c3cb6e25d6853718881562a9fd0bae68bb701941f579ce65bb0c158bc079c89c490227a4d377b8172cc1cffe4281e3d653869fb27912474fd9a19ccb67fd2553da3517611bfede72efc251e7c310838004e850207e33eca5b9d4984da95fe5e3864d2438511f355ca38ecb928bd807dffb18c0b061ad3b61c489156aa4850d2bba9fd0d3c28ab1723a58822d393c3b6e982b7391c4b73a0193e3247967cea8bff0d002a274b80d7260f37454f625e0417c64ef14d552277b4de7c9fcb0f6e6b2dcb96b75f84ca856b20fde0d17ffc7af25ef0752be34e1ed23ce44df4657703463624d3a28b1f826c2e8f9d1599d4971fff9adf82c8134f10746b36244c388d881cf6ec15e8bf372cbb76132c316b29518b93381536150b37f1a3e095eda7ce3e53fb10578093d7099a15d0e27f7ab50770d9f23a07ea33174e5c196b7c6c04cb9e0e3de41b53772c5afb1ed29f8c3852a94492cdb22da069b4ef4d2970b2321c7499401df3d7f72cfa015124506fa0db0fdeee67be8cc8e19425893ae934115922bc8c48c8eae72e6bf2f9920bcb6ef2db588882f3e2fe6b827f596c236d956750207204fcf7a28151e52be6e97bd6989af9662562bbd7be0d961898cbfbfe1b8a2a4f8d8193249903baa1fd428066e19ba2c75d9a330cfbbb17edd5414b0c302dea32ed1ea8f72a8f72029383a10b6c5c4cec2488b9545d20650936ab3e124c72b6bd6731d3e72dacb45c2634b60fa9ac7a02803a365c8973dcf31efc7e38619cb1f5b3cb7327113af0ed42a961d97603b40bfa37e0da0fd4f04b00451362db62e9629e2d6155f71fac0297853120c42a8ddd80ae2cfa9feea60897f8864266707fe1903fb361700c6742407e311a29c1a3e738d21ffc22470aa018969f9666b251a6cb9adb972d36bc1c7fe5e37fc9727600b4b2c302a328d3ec5d9240e0812971be61db6f83d095268764355facd7b17c95547d87bae4916b7f83875c82adb982982a32acc499584b933f1cb8ddb485628858b70d692d65eeeb15aa0504812c2cc4b1afc2d72fd8bd50a1a72435e0c0523107b46e25de038f4600f16729483dcc941ba251772133b1cdcb1ac410561721a33bfe14881aab5208fb175a1e2937efed72473492eaf15d9d96fc12b37f7345485c6755e5025ae2feba89ec7a907c52d7a23ac8a53e4348cdb27d8a0fe7d3afda38041331fa43a8e1003c6ee407fcdd69064e20972450f177318bf75633fb757fb5f0049bcfe33d63fb6e76a2e5215237065878972e52eb804234d5ef6147a2321fae28908c1193fa0cef2821e88410e0a3d36dd726531db07c727dc8d64785d184a458d5e42ace84f0989f5acf12fb2743c6f9872227dc8c08a44680e2bce80b4c08d1edbbba1bd37b8f09a312f548bfecf74840ffa99de3c75373fb147db856be3055f2683781e038d1932fcf9913b1563fcec2f1906d2f6799e86fbab4c608d971e9a1d033efc43d356dad3b65f5e27525a4972c3452e7ff206f127136f9ccc3874e4c7e75d8a8dda08abd52d19e1e7430e4f72f6231b7d605f2da83e2baf5f0c49fdbde15d7db150f3b2354c66fd566e6642642169008f25f1a5c75705e53eb1d7a20caeb1d8392fbbcf78c4b937f4ffa554727ee0773a1ac3e895e408d68b94882154f78922f60f4eda1e756998532623c772d8bf2a2b1a36366404f1fe8886b1c1c4689ea2abe4d6733e7800c2251181967254e60eedd39d78d2b5ad87211a6c4d5da213fda54f8601d0a4fcff313c7c203bf83de83886fc55143fd9c510a3ba5505a37939da7ea915f6f37a4e4d3057c3067654b771ce62319f1d75be622ce521f2d68f79a6dc54487410590e6d6285aa72ac07a5492fb421cc0e548fa616f6b2b02166b83401757ec04ac2e78d98ed6264162bcdb223ca3409dfd37e328f26baa226529ea37bc8a8af784101f4db810938d8a47be1af4b4049046e1b2c36e285cf73e11ef1b6dc4c302f33d3ab42cdc8722f6742f737677f67fd97a50ba7575fc81825ff33f0a694207604c2ac37595b720be0e406334029186342cbe0f72734212ff3f3ecc18b70e1f8528fff40435d65a651bb40696997b28f379f1ca65525b22c3c0d9b60cfb9e8a1d72046c668634abf33088b1bbf8a242aca72ea7388ab8ac4e68ccdc9e76393e680554b9173896bf298b08af880057a79fc09936cfe518e08c69da7d8ce20a124fdec252673d472d5c3d1f9dfed4655ab30c8a784db1f954bdfac92c14eb521a9e9668bcfcb3904148f8580781b6a91b02c09ffed21597726ea6bb9daab5afdfb4df467fd275d72130fcb93b93052272f24851cd531628ec25063b22c494da3454806c6550a6b40140606eedceca25d024a82a3943b5d41ff21223c03936a55d34e8c09337d686d291a31690bdba245fb583c01190459cb402071eac6c48e093e4a3449fcf21f72ef50d25ac1cef9abe6a3f23d4f2939fb63d873b4661f016e2063f0c1a9a2a072ca0c54da2883260c93d7ee744fa782761e8ec1508f953b03600efc5ec65322309826af1e664ba32c73ec556746066f2c23cce6b46cf91ffee53dcac62be5ba56860fb0edcbd20b0ea033ae2a1040e29aa22bc2ab1c0dbd780b2bcb14cc77447208e312085ec2485773b2cd5db5487fb528726191be1cc7d6ce131dc5dc97811a2b7adbe8921d15abf71a80e39a8e03b8d40752c8927d65be583b9c543e9c5a72910ad84d3a8550833c4346b8f16aa575875b36e733762c0d89663a2457c1497211500a67d2394d037d7c0c8897a1dd761272d94f6bab3435df0c9fe822808b72e8d755fccaf90024f4ee6d7492f2b45396ec6d966e62d4894f7c82d4f48869727947dc9863d44e87f03fccac933b505df19a84c3d1f86897712250a52ce31f6c84ef4fe9adef4678bb7130b5e5a9ae62d779b093541857356fa92ac1d7e0e67206daa88bdda46aecea102f681c0003b91e8458d4712398a000f3c5d10eee427213d835f5bedebe98280daa1cd8db82d6bb7696f3c037fa628a4c74ff698c4c4e46c1c3a7c80f9ed0e87a1c6b034fbd1918148ad2c095944c8ac5baf7098b012cc403ed1e979932812393f256a9bee763836170a8273cdd25674189217d3b871d219641ed97f0544c8a7ec01f68cc5c58d9bda5b072c2b1227bfc532c5cc18d48d9a8511bb297ca60646b5df6301a40d4e8cce57d00521f55a35b1394da089d301edcf778904ca9293e7d4e825e2994d1bc8e4821f105e599c4c5f990663a2072ab6a921c3ad2af51eff3de10567db799fc460dcb0877667e531d2ada4a7b6b723dd9187ea801fc4bae80206bef1d24be454a42747ffbd2cae064552e88917b72794c3728b40b3dfeab0e26d36d5fab3adcf9b1d8e4d9dab176043295a58ab6726b6b86d509075a7d41e17a2bbee9bc04a170f0cf1d5419a51c208093c1d2a672804eb1dd5608d7e61143a3cc9cb3a7412062b11845f0fbfac2edfbde5b338e72883c5e791af27cf686e4bef153dbead5b16a62ce6767c0629cf134aec13733722a843a1fa616231ae356cfac1d04eb0b7615c436db8ac5b0f29884717f933c7257cddd427c33e5e88e1f0f291de364777668e29564be970338d1d0f7ebd39f142df4d5434160a0c07758f103b547bd447e3ccc6529782884d11cd1985007ef721bf123f419d4ee4742eba8280d26e4d344675b2950318bacc0a461da47686847e39046a283bb5d9bcd0b26de90814878cbba0c7fe341297526ea76256074b0032029fcf08bc781a06a667512d8f0828023e778372be0c5a6518664593b6d0f6427b647baf6e276af099ee755e0459a216e42a6f505f0ab6c9ad9d29ec3b343362c752a9ec239473bde6353d08112c28393c51989c8871556d309a1f64353a97273b445e7bcc0d4f84d7294284ec4f1932b38b08ac25f1f9cf983397997e72b72f1e39f22ce7901517a8dd66cb8fb23fc75a0005f0ede48af56f953556459a6720cb92b0e6f2fd8f3bc3a02243ee5b2b016d993bcc4ce3400374e81de9087b67267d8a0db2c635f0ed7d56f2e5896a0af9056fe7f1c5847651d2ecf6eb0a2287277ef90aaa47c27ca099953bc44355b07f14c46ac2fd66ce476a5b4d70f2395722fbfdb16fa743e709570aaa53584b4612fa45b30c36236a03a52e7b1bd31237227ea32e09ecb67599fefe1f556e2a306f708fdd9db4b0eef8f9aafb90f7f1272b4fb73ef73e1d91517d13f808228ab1ca03c2d36c889e3302f2291f52a3ec472b06bc40ea2c0514f1f09a65d2adba33e87a51b9191fa32fbef8e5d0e7a099d7288e4e83e9e79a8ff7f82665df6b2e30b6de50c1b5d808d02142eb399778a0450e3559472ecb19f915713c569a2a9f916534b4badc0b006921dfc987419783f16d07445d3f8d89cff387eb673b951d29df63beae38bdaa82af1679c819e7140210120fb77b483e904f847d6e1d97de5e7f3c662bc8bf616d4796c61c20739ac093a34fbdf5711ce5cdec6c4ac7eeaff7beeb1fd6c57ae0315dfffda6f074f1a72f749797b95dd06e18babb6b1287226a872d20f17ce4cd1ac8b67314a7b59b922461eac5f6ca0f86d3df05013f4f05951e879c2ad66f40fb74b2caa4d0a3c6d721b440cfbec59c9312902ab1dd75418e32f804910261c72b0088f4283b05ec7359d29367d9c53413371d2ed229ae9e177030e686396c6c83c79c334d191d0e4723bf22505c8f4624dbcdec71044b09007c858a42ac7ba4988ff858708903e256a025c370e0db1ce3ad07f060bc47db8327189e7e844e70b3042dfdbbe4046b372585966c2c578d41718fb112862b2824b4cef22d183bb3000e3b3a3f7ec824e6c0746bd3f5ae15f04110fea7254986c272915bbd0177462163c75b242c0ee1e720bda4c187b085059d31784d0752613c8220197680871b52f3f7c40dd6be51a331676ffbc032a249933be0910e30f7cce27f65a80339b17fbbb4ed2bf5c3c9e5ada8df53196dd970b004235e95267d1f1c513a2f5b8e3da8c68f88c315cea32557c34786fb4939d9af25395197012cb39682af456f12b9b892c7e9e4ec10eb5725524d4839ff3f42afa6b23fc2a89ce6d2f1b13c80facade35f760a30f18d3a7223899510672642a7107a40d3a1d8cbbdf9604c5719b73d5d1d0e319f6becfa724ad0be14781b40e588ccedbb5736d4b7011e08d0f125091e1217374e3a90e15a17ffde25ebb87b715b3aeb66c3e7712a054d9b97d97c3427d12dff1822723a2898875d5a6be928dcada1e3b0076b98bda1d8684dcb473be2e4943163291fd7729454b35e1d9d6ccf44f107b2017f93d8361fe5561522037623a7ada911c758726e1c5609014320217027f516bcdf5e3f0b9e29ad7f1833e676178b8e9593cf6c3559682def5a831d3f7d8c26d0ead9d4b7fb57d9bb7146f3a1f3c9cdb7573c727e4cf49bbccb818a9e597bcda364f94dc46d053cb125e4da294fabe3c302267281ffb16c10a01cb6b3386c0e2b36198aa8719a66130195da3ea07bdbae7a0d54c040fe9d64acca02062cb49cd1549b0471beb6d4d57f6185678da34078a87472ac4c85727ce7a244cc2e4dd0b8c9363fb7778977cef3aa22fc6cd4db9f7a8872cc70e6f0f6b436ecfe3051d31caab38d89ad42765951fed494fd0c7ee2b0396de54ec3c7f742906227b2eac655e50b4fdc5ef1d77c7a1f3b275df7083e976972f26a709237d598b1a6a63f9b2566c83c39fed22bcd28135d15b9a2720a0cdc7236eae4a376e2d509cb31cf156c42f30e70680196fb049f2e7f2d4129b912b472b45cb4f3dd95732a5ba2ea170118487aa6bf4e2869f3908c26249a8124bc5e69b5d19158b5b6d8d4cc839a9a56b7891b99bda22e2cd361ece08de8d112a990727e627910eaff90787880224574cabba16f7efee0e3e765f464115d33e835be71c7e34a34e26d6b92e52b768b6ca21454dadd4563bb68837e5ca363da2a311d520da3a5162300001b46088c04d64ec60db67a6379b21877087c009bd123d7577219eadeda1c54e1ffd218521a87fb78cc838ae44c0914434d7e3c0661f8b71303e59e22c98bc9efa5116d4dc37551e9c686993b4231bd16a093ea86dc39fc9372a08ee4f36471b0a44fadd1f6a0edeffca100b3968dbbe170fc8149f220060f72ccffe8680ad3d81d44dd703ddf66447047242a67eb2717926ad1584e6b4709729487c99bf057e4def512748feb552995f6b0a9bce30a81b9f3756e773f4aaa4e3e8d042285236ac5240d60d954fdce02a50ce7580484c3b729277c84961c3372c585f6b84708ebfd41857476db0b0b9c243326b8157fc02e10299e7336e99272f236011fcffa038f17e740d1ebd829e04686aed1af71e2247a63eeca63660908a70db9e5e4e035ab45f0900bde217967cf63882b2302e9457013e743121c64721678baa562e0c42498512083ec93d07b41aa2515eb2d8284bc81cb9bd1cf2b726e356f0b51675a4f2930128d268785cb2c4ca4dea608e41b3ea86352c9fb962885c47cb1cacf32e3f78ba0a188112a1b9691be048d61a5f2ac2dcb02a580b672bc42b510d1172ae561d75fac068f9e965b4fdf76a99f50150f389f7b3af7f30d84f8b813f120400586a94ca8c0de5dac5e87e3f1e306eb8bf1cd8fd4f9ed347254df1733daa265544682d98d1813ced155f7b4328f52b9e6572e05f04f8adf7240371822312b403573d095bf3b654435236b432a9843e0bce2ecfe57077d3e235a313162b4fadf17933749cc2aa31d538886bdfc98ddc3701b3142a5690b47723f04ed3f25675faa1b8bd80f587bdd388b639774084b4ccf7043ec75aec65772c1152569a80910a73b40d689f6497b90e39f65f4f163ae3d6f9ccc500f6fe70b89485be2ed985bb93cf14343c7d8442dbd39e69ee2aabc7770df66f057f0d055cb2a9ab63181ec4595148046b8eee622fcbb93b2b67e3f0cab3b93c6e635b8727785cedd06619f7428ea50444a8930782a346f63a6386dcdee6358fe0553f3728c01633a013ef5436ec53c309bac31c7db8da35be940d0a53a0f51f9bc78ad308c38391ab755a96118aa11a6ac541ad962bd464b3bd3059a9a139cae8f84cd36ca004e91eb962a51d135fa51d7f27fe15500bf606468b7961b873a9bcbac6d6569449be2f3e2e8e99452f3a1592b7a7e35b4cd07a73ce52e8fc973a126646a72ade95c9371fcebae88a04ca525be32f7b4d7a87a1aecefa5c147d5631d643d724249e8cbb12ca2e8044bd234e110f870e73793118f2caba87acc2d17eb7920429397b65190dd236c4d1fbc338d242818e91c307adba3f04e5fcb492e812b9272737e9fceb176f2f1561df32c1eaec8179c18861abca4948266345b40d85f651db3410e677c8d6df29f6cd434eecd97dd570d827aef7191f152768f50b276094a72eea6497003123b9273564fa5ca3babf6c7a3375523e757d5288747735083721d4057c53430835ffc22678620ce84ec1a4c6daa970ff188d93a81be2bb63f0f7332c349c5860480d5e60ab0d0f8b5c15eeb07c13a9fefa7a9b3254666dfb472d31119f79887b72060f2fd93801e8897863bbd19da2786c62b322d326d4ceb72d1166657f04184f7b3fd8f9bdb434f535c19cdd98e1b9dd55b21c2dc7b5248723d9b594095fa5819e97d50e4c277f1ce2e7d932c871e5f086513ac852f0b9372c20869e5b143ad5c3bce6e5b6d3758e182700a107414357da2a0949ef7cef15ef8846079fece96c4efe4b4f2e91802f83f021d702211175b7382225c97d45e723aa061392daec4572a7de87c49b5aaf2c40e839aa41dcde2a4c32d3f1c2ccb4250c99e667adb37e83fe427a0e154d6f052a17f6eb02b81c9d5de285dbbb3d572e86adf6ab894479f16067cf01b29efc62b4d7c34319dba9812c064e88f9b2a7276c856ede2b635f58d6fff7182be76d6993ce2ac644e0974b0d4d0d1755610721d4e119834ebdf466d2773e0b661d15674f4546fe5dce411f6db89f9e9c93440ffa88d4348cd81cb84172e3a79da8ea5e58efdd10d09f3057f440aff1ffbae342dc50c479c870ae8707ab562ae806605b5c1e00ac09935d610cf5d8b1621c30829aaa8615d0fdcc9c0a7a26fd05eeed15f59b1d850593633344d29e30259d047b8285a0a0e6d6a5638abe1d9860a73475d589353f7a99b3d52b9b7730aaae51f6abc67607762dad7abec8a9c4b88472bca418519bda249d606b6a46389d34772b29884aac1f519f8b18e9d85fa9d48fa7ab654140dbd83ed0ee9048f2e6435728f632204dc15fe173428e7dc18a87fbaf4dd78f81dd7f5efdefc58e1ffcd7972f6fcdec3de804d5fba2ab840b67f70de4fac9886ca9d0143135e4231ae1b9c72bb17ccb104d06919e983354f8a17814cc3a825632dbdbd1b3888f1b360a9b30100d6d79186dfa3e4c4dce6144b7a72f2f9a4b4478ab75653fb5e2bfae47fc4133c299966d5f218cac7f915e289da9ac342ec28d8b33f669fc8c303f5d6f6a13a1242f0fc10cb5539b9dd6db967a9a15e101d23a3cfa27aa100815fe991fea47268a49fd6cea20a9ac889741c2916be3e3da91c2724340d6e5593f1ccbb444a724abbd3b17a7cae981698695676b86c5e4d42d426fce35acea6b3dd039f481d4a8ad84cffcc7fb95db2ea6b6d8dcce1a0fdff15ebf022431971a2f21418365e283ca81133f6b3c2ab8d09fa87d628f1beaad5bce4cede117fdb2db76bda965c729d65fcd716c6adf4044d64d1ef1c1caac1ff34d16660566467e81ef09c2f513ee64e6867369010d886cb548686aef464694127c9283a50a2c29920ed9a310d722558df15ec405d3702529a07fc694a827942e7ca7ffbcb494c85d54d964f2e2ba63bfa310c12eda2a648f01a3d7d392f1101fe749b18b9e4afab5e65215e70724189f13ef9c6cff82fdf0aa28cc8baf1312d8119e39fb557e905660a3d7ae4289581b77abb6e901ca72708169235d58874364ffea18f9767a7f659379f7a810396a4700d4f1521c749c8cf1e8dfa08b7ffa37608f83e4a84eab9d438d5cc787210d6b1cb70c0932bd55398c12b1755c321cd59e74c090aec3d7a142fc5bd67724ad3298b09c916e97b4fbf9ce40a08c0e338f5a900bce6dc86decccd2c88c0726d2dfb733200c1a674f442d4431fdd9d91884367b8066e1a39cc24b7c1268f66f8d9f8ab9350c9b17959167ba2a330d57881d4df2fcfc6db95ecb3a8db2704568ca05b4d1f8b13d785a9f3aea01f443a3ac364e0b900d536e08ca05e64725d72c8a967140f357f8a4b5f7c41250881ac1306ac79368d16ea523404f1e68e3b5eb33ab8c62bcd529c0d310413a3e978d9b7766076da2f68c72dc809f9c6a06e0be065f0a86a9d0441035ccdf017585dd7f59016f985fd7dcc93778cab9779b91711ccf0278bb850231e5806f7b41e850a055c846dd0049189f1a7607f713d2b72c0f7aa5d03b9b15c6129a9cab112b43bbd1633a2a52b0186d104f8196ed21772c1a414220610b701b38fb38c232ea81325ad472c9b214d9e8fd6949a0cb2103a377052d1c0c93fae5931136ce4955d6c7de8785865bebc300766760a2fdb79721e27af2347a7d9784d9f7fc53a452adb4fe21ca3ed1ef2479fca1e5dbd5e97666efc1503a83f3e99b4faddd9affae69487fde93d47558a0113ab94c348dda5727f3346a528c04580f96cf9c4f40cdf947ac948331158793179d647230f03d87232b1f06f93477850573c19304076c4595ade1801114be45bb802231441ca372a310c7c6f980f5f055471748553b587bf0bca91adc7b6c80b404834b99ca88e3bc9062f341040ba8242baebde63c287b4617c49f75c90e28957dd73eecd29461f0873043b14a653a53dd5347dac2aacd040256320ee1c4b143e62bb077a0642726fb6e35583d763b4cb207ed3b9f7cbcc701e71c67ff8199890fb9c4696f9ef66eff61cc25b964de5949eaca4cd1e077b1f81b2691e242549e9859a86093c7e724ff06d2833b4899efead30e9063c2b3ec1bba80deb173750e23d34bae8417772548f451b06191387a470ea1921faf0f98c91ba2b47ece37ee46f42961837162c1104504bf28f7e43302e2661af7d6e7236f6f38af0747b6d44e6abf6e6f9ff72ff483e513e3e3c3634c0b2ed7119e5804817b39c97e266049ff7fcda2e69a8724cd11f13e82338e7934483e67ee887695095050a42e1e21f8bbae4cc0544a172c32eb2369e2125fc99dd013bec8ecb7fac2f6964a112616cd5ab36fa1ba07f312c523fa85633762a8cb9929881245d8be74e6127d3ded77baa360d97ce531972d7ba815e26896e2a1d6d2d920298b8a13012c49292e5ccd973cb7692313e9e0b600973d5363a899799ea9ed6a51799a4ec67601197f7eaf9b575de742721497200d4be3efb9e22331fd269f035119021fd8141c70bbd2eba7f5e173f6e05e4722ab8d36bf99c0187309e503d85ccfdf91b28c9a9afc5dfd1c7de31b88767ff72baf83ee25a309d514e80a8acabeaf8b282fabe550574eb0653c204f9c2cc2572927de672f6e96bf527355a1093a43277ced15ed5973d1f08f76286b57b314872c6b18676846fd7614417992068a3142cc2411d75765f81f10dae6b78f8c61772f00e84db128e8421fbd23c65bef392542952eb01fa40d56d72a313ed710e9472b556b5e11f68de5d405492fdafe683df5b76a49d6a8c88ff813c0ee0f03c7d5f4628c13b796e197597519f38f0ab6593c1efbbd2ce71371b17638ff2595f08721af45995f9db0c3681adee7b5a0fd9633b12aa12bbdececf5a7e5f600b12ce726697f4e3cf42eca7231c96531d21557474505d66e1b7cec82b54143feec487726c9669feb02bb06dd42ddc61554f9785774ecd6020a3aeba6d609a59616b7d578c40f078723d0007f360a03616a84eea32cff7b11537efb45e1c144b7051897257230e8b2860d93e77ee0d3023b8a99a1c58cfabca311a617702e637cbf52e72a25716ea0bb9101dc3749bae9aafadaca547a246229d3d57ad4f7e0090877b511fbee42ca8ccaff7971cb458e6bb74be8127966c3efcdd3d629263082446c5727a43447e749933d498629dccd3b412519f72d9cc65ced4528942706e234fca72ab4e3cadce1b9116b04749a5d2a895449da3742523d6ba17d1b0e65c9727cf72579fae4213665e856df583d801524bced3831b84667d66fa437fead38b8e506e746780bf395ec7cf384aceae40adf6ec4815acc023fdc906b104cfe945028172bb07adede7b15dddbeec3b52d63d932b4407ab7361875ea6b069ecae9bbe4672f949608f582f1ba1677a52d19e2f82bbfe2627505596ab518a3fc70d9bb9f5202218405a0f985625fa44c45f5322391bb9a1824146e018fc0ff86500d29038565ef83c29bc8cd216eb2f1d0a567e6016fb336ed071f02d369b8c9ef2fb509872ed16f3d10f542e919a3b0344c54c0214a4a501c6b8c87fdb9783539745ad33348c2c76c710aeb0bb7c44c7a53a750d5861c3b54fa8a85c93814b5d608be757727d1bae2b2e8244097a5cc4a4ebda0164fa54611b13b22818648fd90b8fae1072f5e0d4b21d0c9b491676631fc8d95d29ddad6e0bef37ebdfb09ed78773e4f67297ba01bb88b1cc2a751741dba91bd523cf6e4df73ad32398b13c97770e6c9072d7605591c7e5cc83b361d187cb441757a19ffd79649404349df4daf20a01e17229c16e8a74b4e047c58dc3ef584a26a85c48336c12dd9aa91398d6ab2ff35d723c28b57797bab32eb676a57a7ec44bca482aeca9302dba30be5fca1988365135915bb322b3a8eb36cdaf95ee3ada1f3aa423131516951b1496ef90a67f60b872daa9b008a344f2248eec78516de76a16f793699369dee82fdd2f9b4d022dd01ebb077d1b57e9901baf76b025e13a20f1a86c2665775f47f4fa884f7f470d12721d3ade709cd044dffb18c6efc304a5aba2ec89ba16c1bb2770e2cd56e5812e72eaad7b41e79665de9b7d8b8a80023a0d28ba81c36827fcde815b26cd6a7075724736ebb16976023c6a571a5971e8e13fbdadc35e484162cf82f69104a6b270721eb4eb3ba53282d13efa1bb7fb4b022d2e1e518077d80420fbf7adcd8d224672d81d4239d2873c14a169b77a8d7d82792d8cffe2e8008ae9f2e4dacd2dbd97729ce9c5b8e9fba07af673a9f997cb72df7ed60410e536c8712b4beb1c7d5d2e7208e454069285376f85da7385655635d3f7b69ca05b114ed269498de5e17cd372c89f9dda948fb3f7462c118b56af481333fcaa1d0fcb3c577ee2abe046349a728292825a33e3ba8457cec1d5afa20bfb905a05f840481800aafd3b29391ced4e795f40a257652e36387740ed7a24feeb74fd40f6f86685c1256f6d3abde2a618a345d2d7c6d73c57d99ea691ab52d9652d4e4f03f49255b92e0357e682b2ef5a4a49e2f3a3acc754e36ac8cf7cf2a42d93bb40ee95e17d8bac3d7ba7ce827d4bb013bde84542686a481ed1089980b289f2a52f96e0cf4199e2e0447fa734f4726070eca6e79568d0707b6299e6890cadcfa4df237a6dea6ba97f0cc1e9407d72a9d12e9ce55d0394c26742625de13a0efc157a03857924eda8b860b4be9baa7272ec6158c36b9384d250805ee0917bb6cef0b0c6f4cd0c7cbe3fcb5f39e3ca72d9836ce99961fcfdb201080fca3e1d750ae163b47e58adb8961482623af34a207560b87256475f90e2dfac2fe9c5e3d24ce6db06496e56272bfb123f9f0a10722d95ca0e5a80a426c52764b18e6068eedb3c4b867f3468b335fc8f08f1836321418f0540b799efc64c9e69884d16e3ca4efc4a06141bdb2485ef3e01fdf42a72cf9c3d2f426bec1cf1efac3dcb5d0c00ff71b7ac97ec238b87dd3b9e822b441642c01871e48f00f18dceebb6ffa8ae2912d5ad5a4d46a278dbaa90673c3e3072152d407d0909336be485eaffebc963c6195c8c24c23e58b73dabb260a254b861c8cc89f67af2e0faacd4efbc01be7a5e23853c35f74280742c77387bdec8b52cf6f4095248654821d4beb32a176ebc1999d46cb98443ea2587860e4cca0fe831fa0efa97810c4350878c758aa8a4785e5a5542b46bfdd35c5dd92772b07f694895e3555f8e3a09a293ce1888a1c127da1dc6a92c0122692f4d3285b709c49e5c6d81de351b1367df6fd484d10d6fe851f2ae0ba64732dcd0255c549fc303177224ce99168791a00c3b6499332b1e8778ba71c8670faf1d067e3cf7dc7de13d0b9e0b548255f27996536596b85ff28253341a9c6d055a1c9ed3406497af474572395ae04efed833d46da2cbfd8b8fd536e730298e474b933d4ff76631ba45305d28332e56eb748d21a6314fbc9713cdc5db2b170c48bf6fa270cb8ab1da3e9172ed33afc51bd126c8965311eb9065e458c5c0e49f5d9a972472d2e15aa0778772f2f06996a0effb9fb740405dcb8d4eb8097fb68e6aa4b2c64d873f7ec9f3527233e0fda44b870bfec9680dbed953ac60cc2bcde2dab5422d5178cfa5eb66297227bb464d0a2f72d6d5cee8710446aa6aa6f9f49087489023ef1161176f9a6920d97b1f73cd3c5a510c72ab9933317eed2b69cf2b79bf193df15c6e98afb352606ce4964614cc00368bf3a0d6a4cb048255ad01346ec95b45b4ecaec53e2aaa2ff89d7ce8f34c998557f88056504426c3084d7d7053fe6b361f781706b62a17729c552a6511284218895775f530ed755668428d6cba5ad59076432f5edc238272e28714d2c2a34c577812c79a6dba3dbb667ad2c32804de1087272d0bfff68a7220f7b8ce04bbdaa45fc8e63d522a69fe93b97714356766f7929b36640214db72e6a0acdf820b945d1b4f568454c895007eaddc96b621b7373a690ad413997472dd398a6bd68e434361b2fbb6df480658e0b0f3b7c5605f841aa5b3319f373c72cf743338106083f29b72af372d86406aa1e6b84e1c79c0da05d227a600406b72c7bbd21af43127df703936854551f6039d39686bd96501230e7416b8cef533727c5eb92b26f44de742db458a87afcd007d20656e0f432cc8efea9a8ee56074723fbfb18458e274eebea901ca2198652cf4cc53e328ca197432c288f5ee938a61175639f9dfd891e6dc8dca51a7c969aa95cf1c328ba64bb71ad6557cf875277245eccaae9578104a79b30ecf7fca3ac0a82de57f3031ba3452d50e0236bb78722e5b6968d2ffa0eec21d1859257d713cf7fb2959fe661adb1f48d3ff8eac1d72dc8fdba30d4de99ef84cd7fa6673c964f484f1f57321e0f034026f3e3bd1c83d1892daaf002ea6f34013908863b50e6d74d2dc06e5a624823268750d03db7772b4d369db6bf5b038f871a6066601c0a5e7ff02791836a7c73f61e9ce6aafa072408d9dd3b28b50470630afa9924e55ef14fe772def365333b252849d6d78667210ad85631e04ff82896bb7b61f0ae4a84b063f08cfd65f279b41418bbac85a72045fa801e94cf60e7606527e9d9aa6415bc80cb40e775ed5a0cca4f644bae772879db9f2aa0cd4b5db8ef1eb8ec763877b10bcec10ece11eaf94058fc2e1e42fc6eaf3c5b942c2f3da050b6834c52a975af8782769e7ca670cbf89dc1cfa491653a4840dd881c12c3037b81edeb0276546b03741be05acae8c44fa682604a75d13e9ed054d5887183831c8157ed19e3160828a57434dd1c6d90fbb4684429172a20edf795d9b661671b41494d5cbe2fcf842f8d435d26193bf78d09487081965d46cc3a619233f29657d60ff4badd4f8e2e8b1b85dd3c15bc32f0642edb20872203c2659708954e62301c6dac0fd5efc44c098694c557d9dc94a60d6387241722068fe83d0042e6ee8d5206a4b32bfac57111b48ccc85ba46e56c12314f9f661b252ba082de1d99a79b787a063c47e3f0f9ee58a539e1946bc53aebd75bdba37001ad61b4771e43ec0790f41a194a62d35bb3da4500de7ad408da1ec78a212722a7f70105995bbddf2fbf005e04099881834d672b0233bfd535d7d98de7e582d4e6cdce2676694fde43163e6dbd17f8ff5f18b24bc3ead65f7d3c6c85f986672e879dd0d2b750b547cb087a4724d88eaa42ca149b6d425aabfdfc68ea968d1724af1972b52880d0eaacf3269a907e70c11dcb7efccdc3b60488c80ff979dd172f7895e4ac4fb529ffd52b9b36bc579ce1596abe1ce3856d24a18d775357c71726cfc33167fb3bbb9b9320d3512756b0f6e9a3079400ecbd8d38a6706f2788672d6fb38e9ec07c6a56c84380aa5cd1abc32ffe10221eb6faf14ca227b5d2f8772a0a254421e49b78929d1c60a8df02bb3a068990ba6433a016331a4b8c0a0d570e0522a355f517abe4c598be27c9f232249b23313d1267ed703e2ee08965c07720e3ce554a8f0e43e287be2038e99d5b3d0a840908b8406a53575dbec2d558172045745654edc81a1b6f1d800f440e82ca709c6a59fdeada60eae3c6065ad7a720ed7ead26c1e56f4c27c0054d756b4ecbaf24f1c68dfda2adf2213f21a8d3472e30bf5b05aa68aecc0bd16005dd854cd1f097c247ac901059e30cd87b361ae727cf15eed716dc11ac1f5dc77182d0fd2db5cb6ec2f23a432c9cc1b9880efd01b8ed708d2486a0a7c82ef9d15819686a8d3d24b5f64a2d818d819e3838dffb1728612f8e34cdb3ad626db886f83bc54d75e331dd63d776d32c9bd740f35c5a429ba2cd27c9c7c497165cf1e9471940460d9b60ba2856492ce648bc91214649172cbb17e3ba90c645a4519525393f33661958e39f8e273eff1d003013da434487209e6fa6e2feaaa233a9d56dee7a4cd14f5a21aee73d4d9f8acbafadae2054e72c51ed7cf32acd6bb4a67401c55f7eee8582e588148dc150ae126c189ba58e13cc66732983eb963e7fbe9fa3c2970a57987dc9b60c497132364d724e82466d55c6eae108fd671155fff24fbf4eb4308415b4dfd5bf8761a890ce85772d3128d6147b786ef2b24e382b785a203a098f6cf717255222cc095586eeddb82151f397142a55d93d260ef95b6a35b2d1a5ee4ee7cf1fda4308cf8d8e26368914ec98446ab085fd1c76b629359536ce90bf53d5d634b7b81f25698dfc07113ed2240537270647c8fa4b0e42efd0244797032a200e1d54cbeb2c56d212c6f3fc795b930729aa8a18dcdce679e15c6f504984f4cfc55e2cbcb29e60596edc14512e5ba030bd23032c91101ffe28b74e7a596297cec8b4fec85e6d1c86b9ba9105d3589cd725b6e5af5a6c7da338fe17c99816b2c98aaca047f4ac632ae7996a47fc1ff173697445fb6b499650853a036948baa5e0c7884bb49e9f8b8e098333fb7bd48c2723f5bc796bf5ddb42840ac440b2233120ae824b9ba0e627175629bacc0fe66500757073d04963119609258573b3b05b86e643325038afc737b86bd6ed889d3c727f97670b3d815303b39c408495013111d457c1f016cea812b72e24ad59c0db399d2c4086cb92022fc35f01f630b507939988a94ccb426079c55d962e0e411a72680f206e393289d4064a7229ab09f75fe43a45b1b9d36d12ca686d063d10be053eec6a6ffa4f3212965220d01d0ac8e2d88db2933efb1015e1a38357568d5072ad2cc2db207879f55564b977beb677b94ff44d4a3471dec20c714885580d246197a44b972967b46d4496e3e0cec49a41e1884bae0a805b56a8e7aab58ff9b77215170f27c96b12e642c8e2b78bff430a5b12997668da99857ba874a27ea48a317096b0658edd1f9721b990cf18aeb6fae9b096474891aea367d6728d1f9c721112d3b3e46a93107c482f201ce8014d11a8b1a5e7720957472af29fa29abbb27268092ee286eb24551a4512372981bf47dc07c40c8a510c4d7145dcec0c73657289340349e71216f19d87347f7bb4c9376d86ef58d6f204472786027ad5b29341c0d0ecba5bd5ea2565a96af92e8bc29b1f7ec338339d7bc5017e6d6463219a6963b51f4fafb8ed645c3242d28611367e5bdff2a80ec0bceee1289d79fc67f811cc4d4c38b72164b4f5a67d831cc25e551f91a56d51ccfca1fdb60cff605b9220b881122627c833c28a93d79a4dca00021e41b837778d33829c4eb38cf40f05722bf3ef8ddfa9013314b3e175f08be1aecd3644ef1b316e91411c2689a74a1372b9deeeb1288749d9e675e91fe3f2b4cf3cacc980cb9e25f76ed1f1a74bcbd3729658189ad82c7d9df8f46adb85dd00b559e3597a51274e6a6079d7cece3df9012dd794fdc31b266b7e36ec1acbc0864eebed9b0debed508e2bf55204a43ffd723656db4ab70bb73b6d4280520bb1a9895fbb741e1d6763d74643578c11adf671973bc1ae40f64f123e654e55e5cfd9c965a73f71ec01f58c9475cc93509caf723993359fae48cb0f41c85b603435aab92e0de70443839247c0c3d592467d5d728fcc6bee5dae095fc48bc517ab1f5b7b798c29c995002b05895c6e48eb4ac872f4ba5270bd2ba20037f10a279a75280caed1f4c04c67cd0471e2f1006d0b8d72f93ceb94187711597e27f39631ecc898035460dfe21313ae04c9f975f36c4813c6964990d74e510a00f248e262ccfe721f8eacd3db1d8f9980f517abf06eb45fb4b5b20ee91ef9726e16b215b4b6d21ff4fa337f45c1a66bde878c3bd6cd0c720a76c95c4bd9609e35e7f35673163cfff96db38285b3b334c0aaefeb8273a17266de9ffe47c64164f51b192f39f58390fa6fef04ac96901880d24f1ab83aee4f7215a58c31283a4a20c17d0a53fe2c383f90f5da274051bac30875a132d3992ee6b8a0de9253826b706702b849eeac5896ce2cf356c1f36a2d1c02215362ac72135f6c44443054098e9a25522c45f3b1f198a73103f0d98c8511995a1dc7e0720273ddd92f28b9628b7afde1af230c114558e2464821f9e4be8ed3f5e7d8b82a05ca5341ef939d1aa415297e099307216634b0f5e449fc93d75711b02257c4281f381edb7fbc37ee36c669a9c6d8e57360e39e8c1059ac1e8f47ec0aa464fb688264763bdaa5550e9a337c6f65baef038cf6fd9586895c46371e4c32d4b64653837509f6a8a292c11ea24fee65e9884577118c6242ce30457beb22e75d8e5f6c734243bf84f717c623ec9f519b8cf6f5d61d7c6ae445abcdd7b8eb8f619037723e6373282cd83b76f1d582ff6fbb4d30c1362d332dbfd7756fcccd3efabd19721024ebe16ad18d2a349a842a6460776941b4a561ec21b10da11765905ea95472436d396a05f0016258cf2bec1a94f3ea4081a2f3488a0b8db3fce4d25619d61e396f73b640946b6fe3f82bdf0df2d701899ee5d69ecd13edcbcf6f524737726464989bea52b64c21400a199e6c5d6694a003b426ee35499e23f63e93278b8b72dad49fe040c283806c87491649caef9b8d6caf7f7c5103d352b7b2dd49677e72d05488e1d84cf6bb8f0156e6353ddaf17e4bed7ba1ed8ff23aade369624ea7722d208021130dbdf2c96b0a20cdbf28a90fc44e49a0bfa5cad63dce09ce4c1b7263f447c0e63cd50d53eb87a3d66fdc04ddb703aa61bed9c857cc95c4b3c0a330a00d4eecb8541c057c032aa3e9f7988a25161daf9a2fadd13baa6d55a4635c72c3f29eac98c77db71e2ca33bf259d342bc1398f6b4aaf157f316aba8f5c71172d5f13126b449915aea3cd9d21085ae6578eb6aacc7dee03804a0250fcb2c225fb560a66c88f2b7739dee612c14dff15caaad93066c8d440696490951d8e2c02e819626e6b72e15dea51d5ae929eb159712314aec3ebf2e13f98db4c8702cd972c647d512a7de9a77804945e8ccec691381b3e25583e8b5fa5508766e351f830a2736208ca35030ef3912411a7857b292fb563360ebd730ffba08bd90392e0f72126d2716be37c588373117cd8d45ce5c4717c57358f02066c12988bdbc92dd0ba481ed5f35150df8760065b08a0a4242949b0e4b33d80d9fadd1d413f5bc1d6d1a3ad184caf4634cd1ad2b5d25c8f2dfb9bd692b2a9378e46d139948bc7e2968ff565af0bd09a12b81697c87c29378ef8f68611f85dd1eb9364e6d600b17872e04984977b6247ca68517c1275d601e5cb4cdca1fa2826e1240ec200f2c16f372cd54d8b60b40cd2be28ad58ce9c15673e63c84b86306b113c2678f5aaf5f663c75e3725db14032db05f4d3cde3601866fdd9fa924467cf38e4b1abd36867347235e7e8ec7152e0d3ff4570b7cee2d997d88b62ba95a8f1e5ddbcf389cbdc5f04bc2c4f09d35f73fb3cdfe3e5cf3397685c2a2c1efc4b9a030594e2b0e6fc911bb48cfe322971f65d4816fe731a0fdc69ea6269751b76238c0e07913aed68fa14ee103060974e57278bbd4ae73fa2e6d9452ccc5321af29d57cdcdcbd2d506272f6e65af8d84f2b1e4d1aa19723c9eeba6821a26a3ecc74090b87d8172dc834610657dabd77b5d5fef3d8cc67860a2c98a2c1e6b9ff7d9097a537380ad91d317256cdb7cd812e01db446848bb4cf4ea35858e92d47aceb48a75fc5b58ca2923178c762f018a7b80502484b7c1940e7eeb6c863d4434563302f013543b3edc3639aa3e863381d6e181022336fa729239f5063359f60c69b16415e8be745fcb88190716dff63d5dd46b8ed218c1ad40e08d11ad1c27d9bb18877f6eed02b816b2725416976d2d78f3be2a8f270a26d76b3f628d546511ac698f1dcf65a1bd9a2557aa861e8b1c9cc2ade0411253f88eff003a460e6a6ee0d9c75fbae14c6ae25972aa611f0de4c111ddca004f6ddd827e63e61c234b197bbee6eebf429ae1bca17255066a2d0903f8036dcc1713f350098e259fee68e35beb03979e6fa7959c3c72f65834a7d02674bfd29bea8de79d410b7c60d6247abe95c278e8aff5b8f5b208d55f2a4c9f8f73bd463629364a74306761a24afcdfd53ce0ae7934daa57d6659fbfc02e298842ff649f9c802778809281e405aa359723845fd0ff378fbd3911016c53a5baa71ff5a0b228f1043ea87a6eaf180bb03c48c49baa7207ec911e772d620dc359b78e686dbb04b85ca0cd8b9eae688249d5b636f47d8628dc58d6966534dd52fb0c383a3d43cd80e4148a448b1a9127c255a0a708e1edef1d39bf53d8fe1c97dfe6a1c00ada612754cc9c4207ca328c8b0576726762ec3e02165cb728bf4689c732b9919b33d383757de676ba18716eb951c13df0db560af03e2d11d196f0ce1e68a65a4d68d8de368ff7cf093c7b14aaec9a35c4c4bef8ae8780e42d88e14ecafc2be2ebdb00cd350128b0a081bf90588f28e35927b4dd64b25de43d25c020ad62e6016e91127ffc17ffc47ea608b753158df52870cc45e11f3c8720c333f954881a177541c869ad3a69ede920902e8bde91ac6d43b3cdfee7cff2c4e66273b420e6cd47af9940c1746692f129273b035c69f551ce29bdfee97e556385e2000119c5878b6ee10699f21629e6d7138f28d5897ea2041841026063b47db6ecf26e678ea2fa26035c7a31cb96382f3b33a18767b13cd65ebfd321b9072826e29d0ca3206646d4769252d04b1970efc0eb1c20f919b7bce39cf41197c51c04fe5fa071007eb6816c0a2883a2832b2815f365828fbec537974cec25fa2725c87f916b98b7ddf417e19da550450c9403315b136b56a2acc2545596cc44072686c6b88c65f5761396dbca44b25f453366f216de4c149509f9fc1da5412df581a9fc54e51be9c56d97417bd35660b5c2945708056a553d8ae07f2d24682677246839a688891e1dcbf78e117f40ae23daa7450f5e1e94b8cdd9cbf4ffc3693561ccf03bdd756165a2e0a7397d42afb5ea314a5e3002110604169097f6730f4727eb0e682e8fe647477d43f315babda2053d804b2ee8c276686c17710faa8da1e5f2a179beaf5a7091229d0c070c55343e7ffe10df0062a703e9d0bbbed5861726afe770298f0c79b753c30a032f6d5c92f9d84798de20cc74c2009690c6b4562ad784f230e7c94adff50e244651933f854607d40ff02432488deecaadf60825c1563092a1e75e362d5c484963ceef306fbbe58caf855e05b55e5872b9dfce172a6cf19ca47ff6f27424c9a5e443a84157d9b5e62d6167952f450c08df61dee16ce2d4dcbf51aa52f6b94ebc89b7d132fc8c08f18237d8b03cedc367e94a9d1105235a44d7e47e95d2c0006e0f9d5ee30d0d44e0355927633346c00a6d25f040494a5b47c172a8add11da3847cd5cbdf261094d366f085ce614da9bb1b1c3d6596f583c370531b3cf8ad3a87652c53afa61c669c56e7fd4ea68587b95f1072972b2f772127865d707a4a0cc0cf5ec0d8780b89068fde242ad971ec56fd9b3f572526bce23f4586f09ea320353974169dbaf1c1e90e49c11a45a5c4f3b0dea5901d08fabf1cb33f7ac0b0928b6f5e4d66f8913cbe80775f82d0cb554d7481a5672abb58f43640d5e8d00fbd45fda2d4dd6c954d6355648a4216edd7c3ee53bfa23b8e6d0e4a74812ef250658056a8ebe0c294b1651a10fff3913087c6d5c9a5972fb0f78ae592ed96a07e559038d1df33721ec7adfd43f98dd2e1fa5d10164fd727e4d3385fb12e4773a93bc6c17d72f52bb10d9d41b18bcabe8ef11469c8935722c08923e9065ce94269a05d8edbbca4808923a1d8c7d05a159d0318d5926bd72352c8fcc82defce43b1ad6a0d487db139513010adfa14df9913a6fc2a46adc23fb28b74ff2784ae9b8bdfc50774c8083933811a79be134938edf50ed255a6472d3661b51ce0e0511f4c94c12a6faefbac45d98c8c9064a6b96fa91adab810f72c4b7dfa4a29db73b564dbb1f25b2d0e29fdaf2fc5b03e3a2ec749a9f3aeb30729826aa46255241d50fcba72349632f48242cc39ed01bd3ab02ae65ff0e9f06649b0965de6a6f24529ffa07b1dfc581084d4057ef504f6c1ff1a5bc5f315bec72423cbeb65813ab5d6918467c2484a964b0f41f46ab1a25d1ea752b79599f6e56e3728e3f1ac3098e9cfc826f4a594261abe17065361b936574d07859c2b53b1e466912327aaa1a912ff95fd5694037e7d65f7d21d25dfcdb8897b599f651ad5f235c2d74064f683a2125cd66c3e13676592d3895a58a1288eb2b6d7d29288d7274f726e27359803e6aea06737686e4d54806f35a602249cce99a288eddef9c4ddbc995704aae687c7bf99c2e2a2e77b614582ff4c05a8d73a263385e0d078823b9c4e58b218076a673c39727811814ca803f1ef81a49e7a2092897dd0247bd72d606be76b33cdd3235b3b6d22e25e2ebd4f40b5177590a89cfe04fc576f3ca722da2a06a20f331745542d6abb75a3d740d039222fb9d5122803260534a5e6f305feacbf79218642ea2ecf3056fbf217eb34e84adb6a0ee77d9067f9263f39472ce2ee9c4553926751ba2b8f2364d99e2ca8695e1140a3244bd0069c293f98f21740b168f0493559a503503b9f7dbc0081b3fe081c6e181101976881b74c8217257d1e709b48b7f79a95f2ee3084708b6ab51729b88a34822c6db9c96e7b000415c33bfb2976607c50e1431101e5f5bcef37e50abfac2862e1a8cf8be18e533726cae19d49b766cbd8dbac68debe5e54f0d9adddb202c5f2d3c25837eeb8c9872aada462a0870ac67468a70e5a6451d26d894eb41fb95cfc2b10fa63707440672e75c77be34122e25bd1825dbebb9d95699f66645a95e7a182841836c2185d072c2d075f8c62007c20f7c8a3e0283ba6743efc4aa4df87c7f1c3ad225e8dedd721b8937a4c5dd6cc4fa7ab03fbe1f60d410ec0507ed098945cb7d7426e4538172d6d29e6d5c2fb91be970ea53140260e1c097ef28ad00c32a1c4618404f895a45d6f14caae0f7cc8e419c56db07ff80d1f2802f8f26056aea0bc67ca543b64a729598b6a65f729a34db9e539823ddf3a61efb9134525d880f69b0ace35e445f72c27582fcbd1402875c92f224dab6607e2b3986ff1f4c1efa5a0d757014f1a845a27a1ddcc9dc2123c26adec37395067d882c930faeca9e3ba533edd28a0d403535d9e44ebbb5aaacbeb8a3524ba5489a1695a1bd87ea0b4db12837a2418eb6720849657dceab2d0a80b595e4397f073355a9a71eaa144f8602d81a2b5ada0e72e90ece51772ea935f8be34111a900fde881f0ed7f0f0217e18e37c4b18808472a757fb1e8091b9a1b102138b31209897af43a627bcb260aa9964b2da2c0b95163a5d2958a8430eb48c81add986ffa6f838c73fe14cc8be3587aff1928b89d342d5d98345629446dd314cfc93c2350732049bdd2b9ea101cd420b1e990634916f9724ddbaa541ec8fb9e6a92f947de4d466519b83d79386ebc79bf5efaf9e4772d94853384a0500095078f0b5c5d7f69638a89ad231784277a54739a126e154152f98996c8fa9abbaa5619e1f79c64864cf9c4b79d031673b0f68f0a4346ca372d7085fd6c831ab0dbd8ab83f1ca2551dafd761a725cbecfbea00e3a12a38a642137c97abbcbe73cb6da320a11d611848164b3859a7669b258f1e7fd265309b72e385442ba3db6bc5f0eb471b068dfdbf3b605ae753e6ca0a12ff0f0a31c9f5620a378dc5c8349aae1d7d6f46d779acc8399376d13e0791911d68ab481d9a6519b67e81561d7472aa31d449a5a3f5dc8a1fec1dbf16974409c1521e6b18e5e815843f47a722fdeed63e0ed6ede7282d6c2ac3ea7e1eb7890fcc632699a76b8927a514c1cac3de7e39d58a280aae7218dd94bd24edb8ffe1054c9efbac3e65646a9762bd8db7da62b7148d23619bfc0912172224f8530d93a5066bf9103537f5722ac3ac361a54ccd751b2d470e6898ffc36a39aeda6063e8ac27fcd16a4215c011cd0a1b98b0e669be55078225012a40302dbbdf22d9741d3a0fa5613c238d91667adbdfc7bafe5fbc869b3030b82e5c3933031567cc7081435d5f94564e702541d84878d49e6a88309111db2b22b322990efc0c0bc5b04dd078bfb3e557912727ad9295db2e43163f1f1ac7ebad7049f0f81d7624c58074f5cacfccfa637cf45b20d9f61f4d2a3774990e25af288376259c6ee6fa4049be8fa416197fdba7072a82ef0a931d0cab705e5ac6bfb4b86cc53cda7531a1ab71c985ca3a57effd1571c5352967a5dfd6ed875d0da202b0790907f5b1eb615792941080dfe05329c7223abc0686f05d5b6446313faed66a84b1606da2bf63a7be2e02022bb0e2a0e728f6f9f70f66d1658e8a5f7ae511cdb534ba9cddc983dc7cd2dc4cffce0fc8172f1ea69498a9a9f668ea62299c101529043a35e391f94135e00085f07d1bb2472c4ab746afa7c527aca5806e5a2ffc03b58c8902355cf1d368c18dfb75adc650caadd8974ab0021c0cb299e5cb5eb2ad19f615f1ad8b6e85eb74b6117330b5f18e9e09ed02c8eb88bc303c129eca442308f7514f423604079266adeb7bf69057216b07685cbec9a39baf85f02e7cb0a5ba1b3ca4ddfb200bb20aca511f3b2c846fb20c36351fc0beeffd5b13eb56a1072041f7734a85d855e06af58d35d07c272daf9ac68688713be0ebd3c9a9253ba9234d18ab56df887ad08519c887923f272a35b35f582aba0c8c54827a6cdb8103c6be1992674f54d0d70fbccf43f0e12094b2bdf43b02fa402dfc2b682a66c086876ad38ace17f7b19b70f937ea18e1a5b29d862064e58da9466faff8a428b8b1f6ca32a792e51bfb8f2447419e923d772a8b274a5356bf714e407e01b3eb09b6b674664072a4e8ec0295399ea68c3570b69154ea99fd5fd6cd35bd4d4f2f8676091d4109f27d61b8985c85398aeae0413654a744fa82bf0364ddc64cb19e171a36e7185877bb82e5ef55e0ed579bafe72d4662d520c05d9c7885260fcc41bb128535e93feab811c207c5dfd5826235726058c567b8c51b229e5f9fe4586881e81e5dfa96f4fe966b19710aa00713a5e7207e230858d7288f1dad16d1db30bc8d3eb0a13abd7a5bca84f81b6bb9c5ad86b26d651264a2b9522b7ef65bd5fc1c16c47b28b2678f061855af14d48446622164869bdfc0363add0b8243fff771486693616498217bdec91f105a956ac18503741204fe7f1d58894d948a0d1216a41cf9387dce18b2856b0ea08e0919fc35672566ad793d1d8f5499e282f1f058ad285994391a1657a5c854758266d68469972d98721283fe30dd51774b4f1b3ef14ab04dd484821c65831abcb246c25e0b7437221d62284bcf38a290ad245d96c0a53fb733129bed05d5a03714e9399741d72e7c96fa709977e7912a1242806f6c62bbd61356e5a3d806dcd91abe86937b372c14f2c1ebb5b42ff3abfa6b8b8d4c3870c3a6675654de96eac9b29a8521229109037bd5db634ee05ff162e4e1b9453eb6582d761e34d17498ad45857f38a2b727baf663e965a118dee9f66cc34c2c873e3e9db1f51d6a1dafba2975058d8fc0a4a241cc73efe234088457868b48bf9862cd93f978b2985cc6be134404ee7fb2b86a72953dfe60e005b2842a6b54e9c1174f18b86286969cc4e58b80eb2497d488231d2b24535a14236a09eeb9381d1d99cb78bafc4bfd4aa8aa370c53b0374473804ee45f5a05c73f823e32cd42c19934e7409c0ba1d55ebd12bf0694decee72aef621985771e9e5dbd59d9e8b9b6fea5338e4109139e2b31121f57da5c360722391e136750226c60c2e0a97bda8e992e723159434577bfcd85192b03d1e0f72d87f1860f540787616376e1ed1dc724ab3590512c135c945b0fb1a82476bcc7219a466a9aa16490f0f8eaa2c8b66d3a6f53e33332322d3ca363611c90ec428729c9362b9c427f8dab2e845d29eebdf153f5c15b6c91c544d4d5925685abd54726b63dd3748fed26a0c3c02d00fe7830d42e13efeeb2f8d56a4f5a21b7719c11cba65014c5fc1bd170fb30664d0520d8593ab4a4927fe0860304ddf389d297372996b64851dcede4bf6060631d7ae045705fd600fea042c66903d7861e7e278724e99200f0b277aa39a298deccd056847c9baf05d2916ab4d2bba8e8be3cde20cfcd8e8fe3a0dd765d1677f3e6b32ac1208a39cc3b1b9d725097569a52fed25409dfa7bf83d2162d6c2de7cf252dfe808ec407c44bc6c05dbdfed391cbfe06c65db84c1da8ef5ef5c4c6875e2b30111751683f0420ddb1d58ea7b0c399da98472e3ce006beca51ba65ecfc24a8240e7b04abb86488469b725a9672fe7a57ed0722f356cc3f7845d74e6cc7b43cf5f90f58afd815bf0bbd1d6d98bfd94d9e07672f7f3ba04e38373e2bc3094ac1f8ef5d8b4d20634ca9d58e35273bf1433c42272f8d9c0dec404c9dc8e8edd8c5eaf12b7135d6292dc6aa347884b79a8e35d3e61f98193902d73f14892711e842b3877b6fff5403daa7a831c293696343d0aac724c1a93525c3dbbe0614a04098aacbe4f018848e5b44e3ec5d3feba4e875e8971c3c3ceb7ecd6dd778783f1fae05f1425978121713f230791fc8b4cdd194023725f33904edea1de60ff6e050a7734d2c25ac28b3dc6610168ba97c022bf1b2a72e4a550dd511a5d9cdff7b8be89753b146f149542ad44b2fe6ceb0fa599b374723f508b18f72f05e6d48b31967f0e7198e2b69cfdce70d1e0fc532cd08801ce638e81386f1d4c3ce70ead4f4e19906efe7d6a2e4d95b2378d45b759224d11db36386de5f7539752a4e9f0628117b3a6dc434177a466aa6cddc48cfdf20aaf287267707372ec165ba3b93b0222f3e78a6f9da8ab2ccf4bba16141f8328ac84357265bd555c8dad942f2217cae3a87046a0ea983b4e46da4e7de3a78fb3c3d9fb0fc499812b64c785adfad428f7b1a6429179c27b312837f1d0f2f3e2de5d760772f6698e9f145f70eb28248b42388d1a5c4fdc17c336e8a75a055aca0828ce6d51b36984dca64bf12433771e2370536e57da0b07d96477bb298a2a01da5c78f97234e30f13399dcd3a9c1f8eb24cf541c89ef4974cb6c918d0f3f25a85fd415c72b6e6b734cd0e0e602fda84100e403b1660a9a72256201bb229fcf0912d082f72ad700ed38db36b8f170f88a98bf37525f35530361b44f72aca37858a69702d724f2abe7986bf806aa577be0ff66fa9de101f06eb6857fa88256610d22a79cb6e8a3d38cfb5e0a9c3d62df302238a8a09c08d27d3b01521a96c221be4356a9a67618b16feb8f86296b191eb6accb4f366b874ee2aa3bb8b0f7cc6a4291097e3543dd9ba473520438ae01d910bcc60b0eebc089b72ea7327a7cf8df15b79317738bf8b82eae28479c571b3094c96985f996aca4db83f7f1a3d7138b2a350feb04d1150d8e059b21cdeefbd2469b2a94044fc91b9573dc44ae7f63043a8f76fac582850b705a3a9a32709dbde501c2ed6c31e515fae4f006afe1a55506da565ad72fe3af7a8458dcfa3414a101781e684079cbcfbcd695940ff3a0d8500d2c636727d97678b2afff7c3e768b1c52c0d59c296f5e2877b937787502afc4c8f627772cc2960383b81d28143f2fe5cf74d012c6879ed5a0bdcd2465b4c68dcc8bf8c72375bc31b5ac5820f8caec393d478807df981894bb51fedd1ad029ad963cf2064f72666a98f3d85b790692b9ec4824b5f7194d4e5a77a971aaf18fbdf75f810720a5dabb9d73b4507bc1d346897e3e577e9a7f1e2834d1c41e409da5d5ab11c58413dd483a0e23e92f750961e7df82cd543a0134c683e60fae979eed7d8b06272bec1d46b196380b1e11b9433e7717b9af21d2cbadf4e23f427fd2c8e195cd772eff4abcd64afc16ba72d140fd623b7c031d0cbc258dc057b33e138370e4f6072b15fdd5b0924c359e40e233ed81c6bf0ee3a02384e11f0bc6897990597104d722858ea8e8570359ffbed42c439e47b0fe49327422b39b9cd3a2d08ff9b102d3bc068e41205b3363b8e6f3c2e891c306d9806c59c180dfc57c93d533b052db172ff738c3561a6de932f19b17b6339003870b2c1bd9b2e0ef399a2c0d089649b39ee4625e8cbfd46e01fed18099b5fa6c468965f0a9645dcf7da2aada8b0f11a28e47b52e922f33f2fcc415cb1c0d2929bbaed391eae68873804f712e1ebdd1072cae6d3b0168bafd05e0885171bfe986eb1f030e0bb5e67f4dd8af69ad7b8e072a518f7ca218a886a02802dbdfc392711e80b434691b06e04de021e896fc04b723a5ffeea25139d50d2cd37820807b4c5f6da0bec9023ca1ba7f164a9640ada6981e5bb8b154bcb2c77bda5b4ec77336460369515d28e9f951f662a684940dc10bdb5af5a92b6874d55ae03f690611ccfff14c6bdc68546458921fb4064d78708c51222eefe7b85e5538548b2d02ed2fb6937f107e7e281386bc197cf547e44377913267364916d6233626ce325defd43a00904486dfed603686f581ad55cc54c789149a753dcda15b13044876b601c5402286ddf8acfda35e6eacc09f2f9c872a4710c93c735709dc317fbf9bb9cb6db216467ada5c5826fdae4c19b85cda272bbeb128efb54f82b198d77533c326f02669accd18f721d7de61e1362dcfe345f5c078abe0cdd48d68934422e2d6a37bf5607004ac9a60722c2f26e04a2c16450489b78216273f8bf35258ffe3f78d8d52536c31fcd8d49ae84116fef12d98e72ed1bf76b2edb58c8c11db7a76eb3fc52e3f3ca249b4d2df18210e8a1cfb65c720de2cb33b45559ec5bdddaa2ad9d541d5c9d7e9b6ccca0947032c9af7bfc1671d0bac2d3988ebd9ae2efb1dbc3a5f8c055a1ecd452eed60db91f19573a5ffd72529e37fd94606484ae4f9053e4f9f020cb7dce6a8df92066af78be9f47a8eb5b48e44c9b4fc73c6b27e7ce2fa694b17400dedf641c0329bcbc8b9a2ced21f57258775441f01b54710edeb7f7a79d1d0e0e0c64076396caf454793b9712a3bd725791c7dd274376e1750d946d7ce21f562e806de09d7949924062eab366de7858dfd728c47eee5bc749eb42d1ec193246df83cfd77be5f05bf0e5914029117472616128f9138ea5c466e9d870ae5a0629747ef4a2427c022238483daa9bf6b02b23b11ac21c1c20259a54f5a424480e4b608205624f90a49417b95cd9ed40cf7270d0f7011d1d1e2c33971714b6cbc2e9525b0126d75686d457ea92e29c156a4cf25d2e2c265734067295aef8d5782c94da651cb6853981f373c98ef198473000530777c920ee4e07cb347d955d0f61b8665be719102a7b6bd3968c72fe607172bc5614cbbd4edd8505ade40246a18c63ffafcb0455fb80650eedb31a29c8b47245f347670fc0784ae1fca320c2db5186b1132f87ab107b0992efabf4e63bfb345134b6cad524dad11e65ca969e2225c1f2dfb4a5fbc52123092c5efb99bc0c0fbc96a8af2fd4757d27d04cada3c90212a6fd922ec13d7197f5d4eea7c273427265b46e58fe407656a75a483419e3d67ffc7b337fcd10a8ac649733d907ccdb72c1e50de7acc923a79f674f1466ee276ab1d21bbe536c58dd2f528ef731b4e86f08cbb25576e39d09423dda37270906f9a115cc6ddc0fc0a1ec911256202fa772be0ada63cc0062de2f1258027b1e1642a6cc874ae3aae5ae2e376a5c24c315729f0c0937b616a0dd26bef07fbf413d933f83d20a19d2f0b54506067205e9d03315ff92558cd8a76d3557368d22283b7901bdcf1dacd58d293fa76fcaa2275772b4d188521386ed1933e20d1e0fafd9bc0f604e7364470c548d70074364e9f472bcb8e782ac7de2f5b45caf4fa3518608de44453d0d5e9b39d401372d30ab6672bc657396e499617d7b3ac9f262f7532842a2431cff6e71db3212f82de3ecf7721c712061f9e96230c0a5c72585b45e475885b0e141b2da3a0357f0a2d5ecf712fc47c69776e78bb4c8e5f56ea33a6c810d31dccb03918edd08fcf27b9b6a6e0ff9367128417d2a646b38d3fa527d87d1a7c8a6751c5986160988dd74fab9a472303d508b23b05b06263e66dec440f2cc7b81d3f8923d9442b3fc11288a7f677232998cd709d30c7b04f3f83baffcb4296ced0ffacaa6b2ce86cff8f0821923724fa003e18abc987a98a60c5a34b76f0cedbbda79ba2da1fd4dc85898173037724da6a04acb0a72ba07d6237b8ed14779343e3f0c006c720b76547bac8072fa5eba49399bcb0d220cc705f656ec00e55fb615ca25be790b4a138ed9df1ba0d80aeb2df2632353d3b0c40aa86fb04b3c1e5dc1fe2afa8056ac5f5d191a0157e17298be065ecf195a4700d48156d595d3b7c98a894c14c455adf6644fc87a58b40b2681699213d5aacb6388ee132c1f2c9fdef0d9710ca4d5d102ce9237c2e0a1182cb7b77b3bc5ee2f74152c62696ba235ed4fe906a140471ad64a384ebc284205637807ca3af88acb0a989f95beeffa3899411913b9d66b22ef7193c2e49ae072e4cefaad9f3d54d6a27aff9a288f1da7251556c871d8cccf90bffe7e2761e3728891ec613d1fe633d60c58ab7fc80d072dfb5c4a9a80607227cbdcd2b85dfe72d65a51db7181fda74e96982ca05aa1fbd32cd8307d74539f41cfab2dd9452972841c3d83f01bca317510368ab8935bd232c81b1dbd752ff60c87e1abc776074db61a6f16c3ce2278aff7d8c9f365ca467452f9ec2396b21ba9b23c9caa6638137947e6c3dbe48e87033cf5f2d676abf847abd91e111fa49e16072360dfb0ab2bc3743957ed7107a5183594ceabbdfc3a48a3367af40561e5187b2341fe64c972b5ada76fd02e030d419efbc5274fde6257922645bdf30dc16399aad3d12caa17192cf9750d6553397537fcdba1113251dbeefd87ed401d56bdbfac0acf32d672a6a3b1b250be83becf3869f6e4ea92e118152381ddb2cf22c15cbc1259eb1a2f9185327173709c7b361e32522b4182f0f75cf38edb65b538f40ea8d3ca1c45722d527fb358f2e913e0b85e2bd18d87c28965e1d70d2bcdd5289bb65e129db672c3f3453abb98a5d0e9ace1986b7a3dad948190afcb23d43c6dcfc26f6741472f59acf8e039a78be782911d8bce5639aae488b30530ba12228db4f1339bca9862c8477584565a8708e3ae1888854eb3ca672422c27d5cce63e0a528edc7795c721be9d176d828934a9d05ab3531433de92c614105b4db7a92b2681021d7b6507216fe89101aa252edeef92b4f375c8414c0efaf63093e32d08a46cb31d2e8ce024c3fb4769433e244d66942f0606f13db301d16a08faed8eb6c4b58ce14217f729800c43da4f5db8d29e808bf95f3cbc292f32c2cfe57b95673b78811b5bc7072d7152def5d03bae5909e413e541f578bd45f301c58b270a31d05e7f88297f3259236a3b9cb841db0f5999b8870af4f232a4cb692d17320c0e39b352181325e6941809945cbbe8cbd5c83b350d46b33fba49c165909e29cf222e7eac52209606bc68ec069b06b8bc2144b536cf4b4c133a21c3bb712165b648b31854bb71593727d642dbe1554823523d3c7d5dacf55822a0a5790148f11764ef0ff033a09b172fafc619e28d18c0421f96f234cb8f287fc3f394563dee123fbdc765fc9f883724e4b9383d6651ea16498b17c5c0691042e8bbfc04ba4bacd2fbe2acf0f2d5c29c1770d1528c88fde881f731a2782d4ff44672a38a051272f31c2498f94a236727edaf9dc4d382eb6327ad1bc2682db557dbfa7b6eace2b31eaaffef96a117372134134c6774d0578558fd11cc6b6a2d886892ddc634257a0dbd289d5aedf7035d48c06d5b43b21ef0d6403754cb238897c2983d2f71d07dcc6c26d0a29a4d42a7efa1b853d10222a4b7a1b0d373c05f2363675ff559c1957f412bc0727f02412c54b1754f5b315e1156c5ef4c822e9c490b5ba071e14e08c9e17db901d65020c8e57b053ebef20fbb5a2d7dee7f198a2246d6cf72ab844886d959716d8a4a072b2b1ff1d7e2175940adf2fe05b6d9eba22c5f0c0731f694d79529ad6adca727225ab2ec62f910b6ea3f3f63f904c56672eb3936a10806de4efb3c7b4a3f0247237769fce156dfaceb71d6cd3ab90098b1d04d82da95d179649b8a553e2b48e619d0cdcf25752f8febb788145823942aa97642fa3f7a804372e3b78a227152a72b8bf1447e54bc73f1bbc20bd7f28f4c7d677fe44cd9a1d8784409f5e7d07b64921657e7d96ff061414fe987c56e5b60823c6d2df8c49ebc9d7635a96daf1221b5f0476862b2ab2c16d83d93b6cc8620aae9292fa8973a973abaf63179fe588729a4826d54213ce811979b45dab403e4ea12a65d697fe03fb9b7c7ce36a6f3d721ce340c5aef6187e4636ae38d453c932d54c19d04002e15a2ae5e2cced12af1c9cde02594f4015f54d06b257a355e389dc250e00f2bfa94a2b81c189a9c36e725bcea6b4e222c30900c6cc8a322d01a5a3569943117bcf20d1df156d929640466f381e201258b466969a17f011cf9ca1bce464b5ac8fe5d74d8dab6c7442ff1dacf6f8f1626901a4921a74dd50b04142b336c16b8dcc01f3eccb75214ef66c72fec9810780672053e5ac0931897aacf1d0af63a863b309f09340548089d7ae169b662628ebb8aaf17abaad0ba7d466bc86e5af16aa81ce3294e784d3eb352d726eabb5f08ec4e4b0798cb14ba2f7eca38279b72f587ccdfb146f86680ff2bd40c9f57425e347350e7cb640ac350620180cf938b5db5886408bf93ac2f29240470083ae700db61468a90fd37e99b24b77b4dc8a257cf175f02cf139a92df3c236b8d50f896a9f2b956011d8235539b553147aa39d0a52e8ee9f2db090a0edfe2176a5db6d0a52853ed3dffa4baa97e0240f948b565b3f3e0646eb8fe9758ddf72a3cc2e6597d501a57b59b34b1abea1b686ce84e4c674a8f21c376fa2bcf27f4c65f8e11780955f4432be15b7e1872ef098b62350124a815532251c619c9fa86ddc0215a25411f17116eb4b06783fe0b8447b6e21c62c69e12c9553a45a5d1f723e420103851eb26b153eeaeeb9cfb4e9559ca74f71a1d030f72329f5e656c56ef56621b7b82d2e8d3c97bf8c7ed0879808136c232b0149e6ee91498c1da28d1b5d07a08f742613d3279058aff247914f48e493cd6ecaf2e8d7c11de7adcc29728d1f30cc1383b4ee7e1debff5292a15317766340fd84d55fbc45b8078c5a343f51c6d0b5b0d09921015bd28385373ac61483a4668a43f292607a348e0fe08772c21fd9727dcabf03860840056ed9243355c68eb40d6dc7b34ac10429d24ffe72fc9e69d29cfd89262851549f0c75103cafe39c965969addbec85111f2e3e3242cfc93498c002426febc58d5a7758f314d21b3d4cef68b079a46ca6702a531b72593a71855ec9f240f4994e6d6bd22879d32b458e65a2154d0d139c02ab6af80f079c26e288a48a1229237a6c623b688c51b26a5616ba052b4b54756a069e6a7206779e8d87b5da64dc6fa778fd658370ea8664c2483dd8de5b12f6057d710472073bdcfb6fbef0ea35fb6d0de52f6853ab5e0cd3d7bc345529ab788790c8b0322e3e714658fcb81fa7f7b5771eea18ca73df99bf4325d0ed1de8d98d3c43af72714bbcf47339040159a901c1417d75971fa6b1603d9b2eca8d233bece185c734ef59002b275eadc56d7c07fa40a60b03ac4a6d9a38c47fa225985674d86cf57248d39b3d1c8df369f9d5593f5e21d9eab5302b5aefef4e5e3c33fe0e3ad8347273608b5b5379875a7b5b9bc976c2e428df08b51f48f14273794536ae5271a1724ca0f9ba58a63dc96b261b371a32cfa9e8643cb1384370541178de7a274757729b952031cdd405b8373372367d81e2e4525b1b20f697ef44d3a41e3ce3c84b69fbecd66386400231e37d6582bf86cf0ae923b0ed2853ba544dbfb7c99c0ddd720020bcaf1b3b211d5cfb7e9bf0eb6d7154ee9329fa13ebe556c84bfab7b560721c4bb367696e91df591d283bf06d3ff2b4ad81a0b2f9591dd5447bba57ecbd72f50934a43aab76571ab8a04e3f88a395fee791560cbbfcff3c804e762c58d2722e59d67b8da6df22630d9e4d6934df16848442f7e8168b196cde144b99788872c2ffaf509111ee582ad01888e7969db955ef85591d3813d124da4eca02a3a747b17e9a1263d08b588659cf92374367720aca2bf6b730aca90b8c8e613194204d9c3c922270cfaeca9316700ca77fbaad14c562c96a3db40a471fc0df9bfeb3360acdcfdb6d27e9e54fd8e424ecb9ca1ae5cc544977c980bd080599cef18b8f72121bef7d1bf1d8da59906b8b1f49d3a37b964ab3d240308a5714c827b97c0972d6cba44dcefdd29f701a4f9beb4c1e023c6ff3ebfc1361d716279db7adb53b7264332db8acfdae324cd8392a86ff28b3d9f7421fb64ace5d09d378d07e6a7b4f5fcf7999451ec21da10c084a66fe790d8f5cc7014c847c7d4669711fe4631d7205b7dcdcca879f03efd31368e1302e106493135ed9e05d717ec0b3c40ccd9e3d2e8acb350b4402bec725ccdec33c2b9a1e30a0733c9c8aea6273be2fcb3d3872e64c25dd5d84088b83d86e41847cf3847d6dcb723fb5468ef5fa9845446bfb72d43dd97162fa3b6e2681e09aa8a7df2d90b7450321a031b7c20b331a118d6c72d68e816de361aa2e5e7da90018238c1fb92e002683d4f5c91af7f53551e5477241526abcd9bbe0d73677507430cfc48b289f5e92c1780f6bcc166cb341c9ee728e6742f36cd4fab048f32269c3337732654030d52bd0cf12e700fd823d763c4fb4093d3baf1529e3bf1ce59a786d0e5f19693b75984fd75a5c1abb8b418130322f7e4a14ee4ee940bc69a80f29f50dde9934f4b3618f28761080a07fc1133b645bcf2e239eee5cdfdb88229682e97f436a1e28744a33a4dfc2ed929d108c7c7205d92835a55cf1f64f7b2a7fadb5eb51a06a1c65d9d117f7b450eaa435a5632e8b114c931494f05d98cb49c62c36e6072ed35c47b890ebd60d41fcb14420012a1129d844361f3f9c1d170c8765b57b0159e413d48a68266e7d5af5658c71ae723182eb53766077ec71d58e39d36357252c0373549703b20b08b86ea13bc1b572af7ac78387845389f35ebd7703353c434224b0295ec48214dcc1d9eb16b1743208faf7e133fd7e72c59058fe13126884315feddbdf6fc6456b64c8a5eb5a7f72150e772f813b5e6394ac0a801347c32006c6037cb63ca8f04e09a0da96949c72947ab396765ee56d25893f4614420400f7d5050aa916b74fc245d94a7e6e9212d753e94fcf31cc38546dab2e37846b375d80c8beecf2289e1d642f3bacbc8c7267566ebe1d1a26982f60dd5ba775996d9e52524f4acd644bc0cd638048611b599d13c7f1617a4783696ece8dbc2c748f54b97157081517352ec3427a55625f72dac161709d5a313e18c6036bfff12fe006eb15c704904299ab32ddc3bd6b917265256629157007cbb3c35d761eb65cbf28890a52e8dd2365ffbe94c2f120fe721c962814e398673a97c8529f7fdcc7a7e317204c89b475193eb0bedf8feb3c72a1d3f1dc9e83991717b9593b03d6a62f72488aa8b70f48218635f80a2433a172cade87a519a7a638c53a6201f4a6aa7ef3198115dee2f6a493d46ad20eb860723d88f1b5f93fc40587f0f08a4e5a484b882a5f24ba5202c62bb5dd37d8839f008feb273da6d77fd2125cde7ea5ed515b617fb0f46a20f4c9b5fc58d6694b0972d4ac725f944380aeb360baf4b31addd8e24711d0b989bb73a5558fab1b9feb4799b024f27d10e845316398e962b20e65eebff81ee433bb6ee615905e1b92fa3f1360a2ebe34c800a7281f2d6fc46b90002d624fb032d1bc395508299a3d916728a7c284f8051238b27ca686b1811ece4f4b1feb26c4dcd0ea4c06c3e7fb05572c23459e0dfe81a3ffd06c60867298cbea74e08f4e5dc2abfd434e80e0c92815f68f97ca42230865a5269ee42617117e8dca2aaa9222e181ae5b913b373b769608c261cabc1b3a4652d4858b62b7e7aaadbe38675df9584e081d888a427741664d6b8748d84ac705eed630d40fb24a1fdb67530970cc3af2da064e73242a31272d0000c12a663c7cdf781f2541fc482cf3d6cc2e5e2b8e50198040b58f48fd0726a3202a925149a99dab9e3046d9b0a0ff13e8ca85066fec99f9c637a871a926368645ffac639e3797fd3376198a5017ced843ca0fa2813a674b06ce44b52686300bd93ba54b857bc60dd5abe33e3d6a64006ee321f7985a4f11aa114826beb72509bf2167defb92dc5c8ae81851c8776bcd48e529dc4164cd2e853a00c7ada5ca458f100672db57f1be2da79b4e767f1a079f22e7f03514bade26312e5baa77270aae27acdfaaff61c9d67563577eb3dda870becc92aa091346e2abf518ec060679d80788d878af909dc56e52e0097ea819e9b1c4d688872f9262208eafb91724e5b025e88efa7b3bc873216e960aa683591c6e45d6d53d6953e95faadd5617242b963aa46c0a78bb867f6e6dfb647e2369c8ea4de5b83c9782222c8f4b52572679f6c650a173278e05f85b8af2875685b2bde3ff5ea1fc6c0298f75b33872722bf2ddf2c18685c232ba37e2bb8ffeb00cc9a24335c3fcf34fd13757772ee632022d11e06ccd429562e8fa8f8313f99920e6710a5135e8f7713c9b5c2b1ddc1fbec6bc40d5a6ba0474681e6fef4512a2cb8e7196341569879c831ffcb6fc607258cd50d6cf33bc4f93cf8379002273b109d8f0be2f5cbd3c2b4245bf837f3172ab2444f7b38c99faf2e711c5659bc9064089760a9840b18202a0e291de3a3c1a12cdfe0687e47a1ee9477ff06ad3f00528af76e896f2e0ead90a323ff04bde721063927f79067aaaa17f9d2263abce5e006889882b0681073fed4698097af7086107a693d4ed837c20b04f685b4d24f9845252992f6bd27f67962ebb4bdf7b72539480a15ac11f7b267644f2eed43ceb2f9b99ffdb750e92d40dab4231d21d720938926c2e783485fc5d8a1322a7517f85316ca1d0e5f47d13c635965b64bf59ad99a648b65012cc716d821fd81524a2d9c5edf6f2ad387f292d41a041e85713a9ceb5916c1283cbd70690cc1fe23d129b2964c4ec229b28be6f60f9edf0f83ef1543e06f7314fc0140fd828a52018e2b9c485a21cf73e6de899849c59fd0f72c3a8624e631ca4f27d776c6c09e6082ac2c8a52f95ad2085147211603d3c46314a6d9e8cc47e5db39f1749f923289a938137340e8520be272b3192aa09e1300b07fd72cf2200a4f8e68ee94dce4ad5dd8915833531a1c687aa7cd65c8bdf9d722a094894c9994bd6bba80c91781bba9e9ce2e67be0bc1e9bbe868b2ff0ee8149f35dd887e68d818f2d9d4953c40e4639eacb4fe909edb22767dbcec213e19872ef9a413d9923e1857e47a242ee941824a034f1ab5839090739bd4f21d0d592416d863f0d20a7c76d2393861eb8dac4c0ffe5616fb4cbbc20c024dffc25b855723bc271249c6ec77d19f1d882996727e773d532ebcce0a45adcd45833fd670e721d1764913453791f79ad232c3db046479e769b509f534e85d879cf42094e997205ca89e73a0b6e1040dc60abaae509809b9850d6905b4a219a3a3d48574e0a286749ac35679534d3c8284bf4fe9170a666a5ad0ac26c3e57bf5babba0eec9206dd2183f7f87c7fc963e26b4c14dadf845d165062b8b79f285fe1d2e7522340726bfccdaec874074c65f626a4542263e63eefde50999539169a97f9c16dbaa8726b9dde313bfde4f84f8bdf00ae2cac2c09e1d9adaa6a4c395557161652f71628b9f180edca17beadbd27df486815aa3c8b6c28f1b1e791b57fa61451b966150ea2de4a252661424654d63e3af0a9205877df5cc9f342ae8e5f749595c3976a43e0c7b50ede8602ccb87d5edc934bffef3a3897231b0ad4ca2fb02b924417ad723afa768d407e9609eb358ad4cb5a2cbbb54b16295720a6d8d22f4b862508f372637d1758e15b3002a99baa6e2418c19845de6590af0d3c79d832a63542cc167245708c2b791ee1e9972bb7a32cb687591acb4044a97966b21ae91d215eaa2072ef0f2fcc148099327c78c9b1c416480c79b199cc030964046e738c9ff09fcc72cad52c8df7a37fa610e5dc927f6c5b12be6edf27b6c9f6d9df66867f598d3272ba37cccdf3dd9ffdc8b186f798aff24c840aefdd7e6d5daddb3126a56b295f72338df16575dd1e7603c212ccf8b7cba4cdae382c866a363d0cc12032c7f86520ae7d628c1197025372f42bcec3b6bb0f4a57ad35b477457bdec5f2ef52992a72ad336092247fc4b9ad6fe3eaf78afe3963f9252e3d4dd50e7fa0dd1628b49772c1e514551bff5b3e38b2d99cf2eec22df90406106605283a71fbce394246c85c50e1eebadadf4aed88deecabfcc0052dd2bb857069e1c7e6772efe9e85705866001c5d135b01894442f7f36ded43bedc669c05261e3d9759a8be30f3944c6a72a4a60b03d578b35e0e8bb4e0c7dd0959dd1de39b4608cb1d5445adf22020dd7263e555d4be1e93d49e759f2594da6af3bc99c44164ac650aee1208ea9cc1372a41b2390d1700927666093f16ba84ac9b60bcf0abd9f38b5922e49ef3f34c767290dcd9d732fac0cf0f1d1c89fc46b55666cb402db4b3b173d1181d19a36cfe2c1425d0461e30db4282fa47208d55cb1ec2aadf816958be08c7e20d1b8be9ef726ca9b0d1ece866f9eebeceb3a0aa4c8c0e7d5c42d9db584cdf29fa953c72f672f57057e3e9cf37573b4b5fe286738086b5a0870d5dbe8debed9d38dcc80a8172389fbf21dafb97c2aa3b45d4bd10f28b0d16899dab435247e1ea514a3601056febeab6743ddac82d1ae01d8f3ece90eb3f16b53d7c8aa721f9b50ba3f1417b722a66f7026ce6f8d1a3e7201872d25efef9ddf44474c64a2300b7bb9ec009927204804624d8d8fc60aa58f1696dd20d8184f376bc0888c08eb42ad810b2aa13721c7f11323ed00de521ddfde67be5f3a34322f9d0cd61e0e026790a05d8821b24c6b6b0df52ba1830fce4c10cd80f5c40de009d9c617f6fae0fed06d27501eb5e8ade8af48fd8412207f003b803a32226980867c9b07e25fa241d5eb4de02b2326e3db4c0cc0e4f81d0ab5199518cbd7f89c343779350754f0a6a127f54522c72cb20de58cf9ea457d8346f2f7d97e1b0f7f2749f3a9373d447fe6e3c40dd8672a3f68d38a3a3441464f5e69cc6cc10e0974d3150971e44e59b8bfde00b03bd721625051d7dabcf794b8fe615fab37c92e651ddc4f36b5f785dc0dbad816db93fa34d2a7821e9725b596ff82ac07c6928624460a2aecaa73ca74391f4cc14cb72ef28550e615a4979fa36d717e8ca5d3501185f23229a9e3bb778f06367248d581febb5fd391119bf0cfd6320007a202f4dc526ba04afc1b215a5d66dcaa9fe720d036010fb94a97c1043a0b6a3a6644cd00e434d61fcef10a486ec9a23a16672e10fcb3bfbb89f757b9cb034c9c71965a513c17b56f6c73dd1518e45b1e9e3728254e8c4949ff45e875baf450f33050e4220d21f693d4e16cd888a22fca67908d1192a450542bf4f7886b90d46b912e097fe376d9c3610cb3f80ee9630d8735b4253b598a9fe3746d756075cee4637173e541fd42d2bb0bf8c0d57cdd441ca5031c18c763b18765d5ac031591429f4e527abdceb5bdd121a79d1726f97e41e72996399e724ee0fc0fb261be1dca65fa6c37cc77c30477afff9b8c6abef3a6872ed0d4406fa16f9bae8e78cf42b74c53d081f86a94fcd21749a6f9db921f3f47263679114ef0dfbbd5a56a84f7cdacd48b4e025fc7302f51bf72b407fd60da172a4c4f4528e3d38c748263bb92a9f7a0b911a796e45c64e3b0062a456d907e5362007529ffb3e4662f8c644a45305079b81d410c94bb9b178d07404d1e9a8107206cd74415c017c1bc64ddd910f578413815610367c2f768f57076c170eb86f6cb50390a1c5ef41329960f1c7f58393bae8358ce1541855792ccff3cd765cc472bd192c01c68ee6181467adea555adff8259c969702cfd1ab5fb462dc56363b38aade0df3267d461e53b9fc0a9458e6b2c978a985edc9553f5d3c59952053f672cf09a31d7fd7e595b260d0e26d11f04c254db470feb37580652663b30da53672214772f1bbc8b51d467145699e4bfaf499694341773557a9637cb547b9e39f722e8a58109ee7281d81a6c8ee93d3c8cf5e30770cdead40737f0f95b861f6a300baa053d9f397ede99d8a3c79810cdc24211dc1d1c5a2744b6082552bee8950729ba1942f235ef37f92d4fc4bb8b37ec495fefe4decec4f204eb79ef98d5dee595ff83ca13d14be0d9a6e8ec37c30e19cf3dbf668ae1dd9fe5afde7dd481b9472636e78223364f02a2e10bde40c44e83a2066cfc18438a5e3c39a5fff878d20729644163fc1f24f1bdce80395e028c75e131d30a4133ad4ca93f2d7764bb430729e55425227d073e1649a850504339be6be72033f60eefc63bacdca605dd42b7234a023f27a1c6487b00e1c6e2d11c325cf062ac58e8609308fc6e0773fea0672fca663cad99a56c47a6f172fbc3e0003e003ff07f3845d1524ef74a556dcef0368a33a40be494169c4ab03498980a2b8617ae55fe2e1de8137de42730e41d272dba65c51fdb6f0b5b2bcfdd0ebd2a6c54fbd4250572898fe7d28b2d66b7786728fc99db215b02ec113562549ee7a8c4c9f0983cf6c9fa325d48722d1bced2572a6bac13a5ac51d6a289535b0578f0e831263b128f2c4f84ff2541ac8fd85eb1d2f084276c7db13ba880f5278001791c3a39f94d342985f31650250aeb6679f72cdc35d0a6a4e1f61e03f4b7359c16d13bd087053ac2b2de39f36d1fac85fd67290b009ffe67277ba88cb4e3fda1f0d9de28f99461fcbcb70bcee4581f050ae15ac2a6d91feb105d82e4245599f38fd9274805add49a54b338d8be6f6c43e1b4ad2f3ae6cfc7424c46e4a445976f9572208e2d6af7d8a8e118c82b6e3a44b7c4db87bf50cf58e137a6d24967b08f5e8737f4096297bae367d861ff44359709b728a94afc2b42c0353ae236f2e46fb68d8b2f7385fa44e4d2121adab4974c7945ba15350bfcae454fd32dad1809acdfc901bed0a2d41f22ceba90ab2e68f38c57256d65ffe4c4fbd6952793baf1175a0bf5c96500b85fa20ebe0d70ffcd5a355725d13b3855e475937a9b57b939be01d8a05e6e893f61ada5cbad399242f8388720a36da449f26c682682155c117c7607b54120af5fb433731e0f3bf9679d1437294f348eb5c66d6b61928b71b37ca405b87c56828c2a97c97332fa3c2fee7d160cf9991d09d4ebb892da2ae4cd810b3bf138b3bebc811f04c1fcf4c52b1eb1626873d032526e3bed45c136a8d82788aec54dcd83d5edfbe81fdb4f7b73fdb727285748f24e89444a29effdde8d8b0c847cc2a1bee0f72d51938d25c44f64ef35d0b8643aee4c5be6cfa6bc86792ba298683fc5ca5cbb904717532b608d35cb3516ebafa3dc6115fe58a814aa2a0ee0f7259d248c87c85ff8cc4729aa22fea4f3fc25acd3ea0620798021fff24cd3a64f191c24a5ef2a82c382546428d29fc77725d9799938cf5584388aa9bfdd4986f3f35b01be434c66d60efe590da2e26207206c935a7f3d426713cebd48517c192e01614bc950a398bf545522ac476cdf147e792dc76ec3e5b503e801b19795c6a74f2b5304b67eb350e439e9cc29afb6b5640511a3c187c9c63315550aab43083fb5ebcfde38dabb9222edcfd1634113972e2cf0ecb7ff080ae8afb1113773ae93b0d542f823c6b0a5c2c91ba67fc152a087c8df5a3decfc57bfd1750a45071d28b75fb562fbdaabcbf0748aa4c56270c72edc94f3c3963e0f471f9f4f525985e0cbe8cfe24d144e04bda0cd441b155df7271a030fb64152c1813f8610d38126a6e653821e3c993a9d9dad6ea16e66b1b15f966b885b99fb90dc706cd628e8c0b55409bb72ccb3c946aeef0a13f97a054724eef3e83a5d59081bf0962dce528ac3120d7dfd9d4d17a25a5094b366f655e5847d0e1259587335fbdc91bd0acd2d34056a02a20e286c5a68ec0d67895525b69c14f4adbebe44a6c47414d0bd63c51e901ba4f7bf01cc8be40ced0b235b09f100f920c64dd67e2a1a6789e660c70d5951db5c579aa8351ed6c1fec07cd855072f6e381e6acdcd2f9262df2db52d8bb6aba43599167d179c0d7ec3e10b2dec171eb8ff31b0afeca17e263ee8279170cc46d73b5a3a34126b4e83db7fd85f0263e61f01943c66299de36a8ca7e79e3d75220d9499fd319b3afe0a0d5e8b069c658e9238b95df2debd764201e583d29e92e083be71f0cd20d505042371180a93f721dcdb564982e35d3976c69d8e6c34c6cecf21626c718960cd2c3f251d481c172a5ede63346391df819ddff38f1bbe448ae13b9fe92ff01f9dbf642f668131b07de336b07751a3d3b3570a870966d67fa0f5488dc3dfbff9e9e09aacb5cc5e472d0607d35c82313169692ad7531c8c316451b8500e40934998a8e4a851c345972010612a9e263e8f4a4a56e4c2dd36e0c812c1ae3f3d572b549b492caeffa4023e54061800a3890f0fc05589e68fa61d227bd78da90493e034044d2b044c2ea184ff00896865380015bde3edbc7250713bd35dc52ddbf4cd32f7de309c9c6197271a23d229e9e75478a4db79cde1f318eb3cbf34a496632e558eb92150bcf5f5d72f092db805a19ff886eef94e264443f00722590aa5ba315100dfaf406a5c6728cd3ff46f37502ab85d13511aa2dad551be168ebbf15e67c5aecbaafcc2c3472785d1b31b7de9108e178dac53e4c3a01fafdc8c0797718486f5ce4da41d796088e40861bd28bd9b607992e26e84db9560b181765fa6bca76c010de4f197ee072188c724465acc6b5edfa99325aea5db93b10717995ab609f95013789e936c25d42a64af4ff1767ad0d69fc65fbacb2f2383ebc4e9ac679c41357b92084f625722c4a368f72c36e6fd17b1335e3d996f4ffe41576ac6791437fdcf1508d9a012c55c0295367e7c4bf6fbc8fb15e552a4d0503c854e7878d3e2adef81d1293c62f62ab4b45ee84d393489563b1d2d3bd7cc97347b5d7d7072d439b6ed90a20566de2733a58de2de8729685e77173682a00a977d174ddcc04a83d9c1515dc04a372cfee571b9acb19ac2d42f8c4d763c37897763eb05066830564e174ceac42df6bcb31521f4bf0f52f754f5ddf5766d58be193cae0fed195b60157a5384780c5604c8bcfe32eee3d09c91a668760d1198e6cb1c5c89c3f1b1940661a4548a47849b9c555110af7e85f5b31226a9354d0691a2f8e8afba87e9fde9743afc91fa407ed3492179c0e2eb3468ad255c1a4e08929fd04c216fa07ac7596f3c3678fcb72d36acedd8c219f3142fa7f28a3225a759a05db29f397136f7a5ffedefd31af7210b2e3a88a69ceca846172f817897891ced6c2e3a4b0e9e6db1478c0da023d30df789b839f9113063824419438de69b361813c5c1e05ee53f91acdac7266f572643ed7bc1eb7ef69d3220d2da2a668f59ffce993f0a1d0ac56515a7580c5ed7299a6aa2d2b388db160da05941394a900645ae86b4fefabfec36657452d7a1a4c26c221cabc6a7f766be8776770aded22db5506adae2b885af1b21a69f868cd4f0a4ac442bb79781d9b85b6e331cf062f65a80fc42f844cef989d11d2d9a8d37275a226c8425f98f456b625dddb47469bf942c6446ac07a0e9e67dd41b93ee1729009011a1b2881b4a60b3dc3adbb9a21dfc001a006e9c6bfdc03c8b550fb6c182d7a72daaa8e117aac60b545276ad0c4e3e47f381a0c8744f8c1c59c3998c5724fd5bb913167642646a14ff09eda79717d3548d595428946363221ecc839f83e72ce3ddbf1498f9252b0795822a9d5ce8ce9403291943c9f4c681dec438e4672bcf6f5a0136774b70952628c2b8fecd5fd8812a248481b76cc60fd527ee4f972b2d0e1afffaaee7b3621a1ca354956ca368a061bd0d637f30d215f53d109b172f924bf18095ebdbe5e6d312ad2e93f724903867d62ba6c88c59fa8674a15d1725c5ff6549ac85d42fb92f4d4a39f916d6f148d26ebbea91cefe953295efc3b724ea016ad25d9b13afd132f9f4c1312815a6d6d98b0e5a6c90989e900bbbcc62687cc1e90bfc7aef39447d3616032ebb0966f5f1c61ff45a810aec0550bab8a412b8a60a6bf7e7dd5c782126ea890803813d0c0f48ac0963c8025c6ea5079504159ac832f2dc521ca9c0a95382de0fb3fcd4a711e2b2eb152ef45f2f09548951dbf02190228595469b20b836855f339dc492a8a34a5ff14807ec940a34b8c1072a9327f0bfae50c346743f1a56ec00d17a58a6f08083fe2f1f30d784d1f534946760f6568bcd69baa0d555f66bd3838282ff9e6ed9bc44424d159925441ca9d728f46e433676498509e89e952a760c60feb73bd4bc040ab34664b621e32b08b72cb0211b2dc9819e5613d63f13469a931b8f7d245084c2d2716969bea6d39a97245528183d30bbb6fd7dce296f6fcb5ebfec7976cb2b307689011b2dcad00540fe6f9a315a8bc63f48c81628d7eb5c2ef15ff084dba73265b1d92a5d099ca4f5a9e217c1b6ed5977b1aea9117593d11b9008c11c5dae13125e9df92a0e27f2472ccf181dba6c51e5a2b5cae4befa791270f40c588f251c2dacf6bcb90d9234a14a0ff3891864fb6741fd2d48c1bba0e0a2231853105273b7c75580ee35056fc72808e352f6901f39821dad5770e342c1b940ae40e5230e6102c5cc61880c2032fcb925b29e55a10d990cdf5f7c71d4e64995aa28e7da9891a07a95a7292fcb03c497728dc4615af0e3b83405cafeb5fe1ad67ea47322b97c06764a9ea45cb9245390e0a875779de48056d5fabb5f9e9ba553926aceedac974919dc5cf475d300f9ed48ee40dc3756b6e8f062e865c8348d4fca50e06e063a4af0c2ee944f20706b5751a3ab6e1d990589122ed673e24a102c413aced50f200d37847fb99589f72f7c6b0457c3d7a66cd7462378714db55338cadf58cb8c1f81c76c90de773cf72ebd8afccd3c8343e67e74e6fb8b7471d4cbddf0b1763d55fb938d618bd30e911d690bb5bbe12fa330a07a00b8c857c7b5e0e4ccda986b2ae384e6f404009cb7211066fdf611b5341c6d6c46f43100a8ff4bbb8f1ef3ef4ef28aaf62053087672419a08d217094a9292c48aaee9c8c10d08964bb9353d1c632f672aaa4e95c33d6ba3a78d52d0fa81d3e4b834230461f8aae8dd9bd65e95187a12e54d703b5667fe29f4f97accaca35eedba4e7729be68884bc7a1b868c24fe75354d4dc7d2072d7adf7af970327367bece754d2f13f81a05581eabfa3ab2660806c3d49102e0bc9b47ef01ade7284455b62ee6d8afc4459ace92bec343e55f73fb1b1d3ee7972c0a393d2fd7a6c8ebc8ee6c6fb540439ce55837a34bb55f791031d27bd1efd726020f43e3948ef4ac8f4f2091867263eca5d34e6d6f127bc7708794624620572ad1426c1d334bef1b743769cc276a886f0823d1ad8cbc1d8e32efe16e107f472f6be1bf4b1d01f6145be4191577a381aa6b7d17d1616e5d6121421c8cd047206d89eafecc61986b7ce76b265c53d63cd50c9a33b6f3a1b1be1ccee0dd6558372a3d6753bab2d2e7e6036a912a4f783a34e097d1f5e13931b9d93667044714c41b68a7c5e262b8f64e5a706a482f2462790eccc6a4a60aca2423e78e22130807205984d8438829222311d780685f9e014620d17566d48658140561b8d6b92ee7271dc6e6678312f40caa34c4bbc7a883190c01d1bc92608bc65348b61f1fcff72b61a6c9d2780b7a6202a5a821ba52198c25d9a31441d0d72573c5a0cbfe87672bcf56de11f74c212a8e7705d66282406e03ee3b13a4edddc2345d590b57a0968aaada8f87bcee78f900765981c3c8de978df6968544c08e4f2b51f031e6af27246a344b628c15b4caf6d1e6b8d41f5bdcc87af2d5e9c3d15a78d7afa80491a7239e01a5e80e7951e4d7573e6feef103d4b707bdf3581b72e2ac7dc80c3dea91b0a6a4d0f179b49f345f8d7e86201ff5b5c8802aec261e939519c1cdbe99fde72795930254e0a6a2c71d24d781eacef0e98563b99dba4c63b14e139ea9215f172b52a33baf5b9ba7a9bb7697694e99c5abd18532a9a83616bef4c4565c07bcb724e0f5cd29b6f5f9d20bf0b06d887a1af67b00e55feb28a3dd6cc2c7b1ef3253248a8bfca629b0e361b512d9b771fbca1abcc692556ea8d2b4f77766d9ce26672969dfcf730850e0dc50e6e2b798ef1084a8967ddd6480666c0f923093c6a1d72c3198aa8527e0b7ff204e391c2450ea852a89549b7cdbe52bc6429423f74ba724e04cd6d4a14051066da527258ff2b136b8bb81ded2d144e711194e5c3722b72180658585c3467f9ad9730b6688ad642aff607e41f87ef0c77884f30ecba7172caa65cfc7dba1f3376001b8405c2bf7389653d62e79507ea0e7a04a7e672a96167dd3fdea188db8c93ffc6ae917cdfd1fd85f89e049f3f12205f77a129207d493ce2ed005271cf3b90a72d9fd32c28d4f7a8222d3e67177ff207ab1bcd3bbd7221d88dd20c3375e97c6447917d2b3c26077c11807ce7e94dd0879f9812fca372ff8602a123d852229fa4a5561b4eb6a4a9fb1e4c2ce271f9ad2f8db32228dd007e617a02ace07f916fd475410d6e8d5f3b184a3a4000508e953d5244921eb8041259179741551b2983891cdd950aa6d42297db34b4f4ea1822d9e8fb91150572c861db67e143ee9777bc1b599f0dd39ca79c33ab608c5dcf16f39d380776af720405913ba64161d85f4a049f87ca4f22728b5752273ddffeaf0b9f8aa35b11722a1d1c7451a73d0ce57fb403d346c1937943aeb2e4b67f21c8c9aa6894c24772117889c4177d7f661bceee2738065d8d62fd3d1d7fba6225f66960cbd7bd4d728ec97cd97f2e59639a81d83a6301752b467e5e875d58b0bcfdf22007c1816549d9e5943464606ece07f2d73de02b0a067bb199a45d4c98962055c23f7b704560c9a66edf247de144b99673ab86a5400d0074544e218a3dcd052288174377f972b8f03aab063ce3a0a5983eff6d889402069fe1df40fabce10cdc2439ee20e07292404f099bb7cbd4a35c1e3001c7d0e0fc936349e53544d1334639b20c62ce72652d2db2878cd8d948b3ca08cdc8582ea81cc97616ee0caed90557e7d6ee697207158616b12f74692c93bbde8dc0fcfc01e624535a348da4b55992000e368227d1afcf7f3e56fc7cd653b1dc137589c154ab721dfd80980914b9231f7550d7707ddcc1c9b0e90fe2979dada988f86971a98d551883213cbfb2f2ed7f1eaca872c0e777f9bab2b010286915f5d94dcb37cda16564ccfaae6f1a6ec23473938b7279d658be1d88a56581c35c0e344112d906098f9b556e8bcd05a4c131d4e16e72f199330c3b115a58dcf2fc11ba4cb971292259a5bf01f4c9ae4f2fa1298b0c0c9226bf311ce9f9cb6b6c0d18a8819d19cf5ff1c0bfa9bf25382e359bd9920a38aa152bf9bd43957b3794abf58a295a541a87db2d732e7b1ebea3d5c151ccae235334e66e211606321ce5ae82aa7401c95ecc4d1fea87ce885ac079d1f3a573506a9c54bf741a58670a0df812cf5c94dac147a6ebd72bf5bf379c5e30b8880a7287d50f2ec45a00e8db3914f4d2bbe9b615d38a8c7c7d9b2bf662cbbb9c496f3e2a7ec950f03ad3ba357296ad9f388073f93e1bfe9953cd68d712b849e0fa8072b7b5289fbd5801590a08120fb28d3a8eab3899a3eced69516b6815c76ff28272065f5dbe2246b9fb47c41ebe2bb633dc2b8540767198b30101cd83f03bdd4d55d7c2904224a5603e5f615f48ea9b7034b6a34b0a9fd3b15e2894508ee765825eb364491b675c66869fb370e63dc199aef94d96c66491fbd0049e43e808e59d158760398deda345b734cdf0b4677d8b805c27263bf1566ce4393f98bf1acc4b725abb4b6f321768e0580cffa881bd4d1cbff5467f89bb92b67fb56127a5abda2e5e21e98ff3b04503893304c0854250dc2e2dc78d80ab2ad2fb32cc215ee43e7222971857cdb5e99ea5341aa0a94778a18ccea9561a4e9580cf753b46262208723b76ad6cedc13ccba7d9d42ab2df15a30c8591f6e57dbaefaccd9572fed037472e23e4a8a47ba6b0b2e11cff194263be26f811d0ba2264367046394da4b1215b1ef6a97e0ac635d9fa968af41598d39cbabc2a728846f742de83218f83003a2593e10227a5a29501207e8e9e8304445feff2e314e892e85cfb6d58b2f0cff572ea3f05d3fe8f6408d496961d275cf56fe723310394b9a7d9874f0cea8bfc73721b9b6368d5e70d37d05136b907aa01c84646dee832d8e58fb10d30ea3d724d72cbbc2f5f299f132d39fbe169500d127094f5204184a879ab221c212eada9257256de08e174a4dfb78747534d695edb66a910bdcb0f7a7b464f1e15a901084e72f71073f74fc198e1c17b946aef88dceafb28939bb20c1d1ae1514b02292f2572d1e9c46b8e0c04156a829d006cc5af6a008848927f3b6748cd19b9687983c761d1b651856365eed9cee50f18ebcee1a725fa5feb118dab38cbeeac91845a7e68dc25a958df0ed24b5cd5a7e3f7b8bfb77195c8ed40a48bdf3a486b407be08a729499b6941c74ff28ad9423dca4cf653c3ce4151b918bbf75728af781486d8d724e12d4ccf648257bc32ece585529c2b026f456e6d4f5cafcb105b2cab7f08972c992a885946fc7111b1be89c6f68e47ae74361abc417801075bb83dfa382c957636ef22cd9b6a222f84accadf42002fb4b2aa9d37bd9e92a8be10af4538f6a7225c15bf4e7b214908f9004bdaae8b367395e69c5152e891846e29b0e16649c24aa6a1b91023f41b430abf2b5e6739052d50aacef8de8d1fabc8ac69a6c1a9f6058d8f07c5c341269bbcad487243398c5f8a3733f5f6349638c3d6698d3702d721ee0fd088b22ff5460fd3d9170eb01d2bfda09d65e9f0ff3601e7c6e7876b7729abc5a769a2e76384dcceaf4e54c16a393ce760328efdfcea3e4e4762421007277effb21675928aa1c4e20743df49939019648635f27c7ad62a8f5b352791a722df0af2321fb0167e14a0e6753ab218abe6a3bfbef2489edf8fe3ee9a0ee047231d45e8df3b02c4bb84c9ed753111b5a08752ea9cf7b34497c94c5c97a3fa9722b0423a4f930e6c3c7c34a9090857c164a50b7fd9fff483c2512f2144cc81f0b1527a8e9725b50fb789617abbc9eb85224e24f080a9f50f1fcf6b1a000b1b772b3dbedc60a68347740088a640441374387e680136c9cd3f92d22276971fee4293cd5a64bab66cf7e7874d0b30cf864a387ce171d2c60965e50d2a6e0523d88727ab38ec6b7c80a43627d46ac71b5aedb0e2d6cdbd3331d2c1c66e0fdd5f1da5230aba73f2f03eece095246e1a0b3a6f0bd3421c903fa3f6b58074f97a6848b725d82dd676dd738cb2f86cdcc825c81697904b51b6a9abd1be14305c7153372631f24e20bae99fc7fe78997974ab428c58368d5f48fa95926edced9a8f22274723f494b93e749c2c3102f60377da9b2ee2b55610097525499c36c3f6e26413372aa65a9846cbeb07215104a5e8eb772ef12459aa8b3262223578e5120f42c5618cc3d5f4b348fd1c8e8755cbcb31dd7a87cc98c1c09ebd2fb7dfae5412eabe272d871649a3b1eb35e2150290d5c20a70366bb4219e7d4d813a31372ada991b67291405589c50abdc6a9ac40c82cc6472ecc984aca0bdcbef1b4aed8540d4ce872f5780c9bc3a562313a02956600f2811096331762c80e8dbe7c644a2f91c5a772936e8609fa09447f90103dd0e3d88a1dcf34cd838b4b72493fc0102e24021a72fa12ecbb9eb9d8eebe2f8e996c0de3dea528f7d85b458dc3b2b3d8c064f91e7202b64d6c5fbd8259d41ca359184a4e8aedf3462e9b1a808b86a64caf0c7f4972180f48a813fcba38969faee804741a39d098fac43b742f62c71773919a53424b2fb959af4c19442b50bb8058eed9e670bee1ac5b986cd2a4d36efc166fbd8372e06404270efcafb7c8d4b4ebe7ebc9c0e09c6f1b18a9fdb0e11bc678db00427230f967c455a9fefc3992b1b7a2e523aa0869f86a751087d076ecd46926139b7298b8e8660778de88fc41b1852a8d1a10513c1c607837c238474482c641b61c7287e95e4e81060716157537ecb109de04693a75d5d8a42091dd1f6d3a4a86c039e31080c935b56f287d6bd140f8787f79ecf5e94ea342cfb784ea171d5f323d727c1b8b9ab6f1db9e995af4146a49cfb209c508292c5bc68ac7d6166b38b90c726595573a7af713aee501dbc1604badcd5748cd0b5d3f5d01fc306ca8dcb79c720119130193b876f6a0f1286e2b1594f2cf2696cee18d2c70c405606121955d72e5c265dccd65da3d39796196e7759888a16715bcb625bd90282e046c79d2670d13e22eb27cc6a5aafc1222564c42c0389fa5fb9df02a4035cf32ec8d7a4880423447c6afd01a7cb6214fbdcfc3c8e7a6d8f8419ad5feb8b9cf1bd1aaa45227729870015ee8cb393b17df87db1bc010d88852db86adc19a7da870365be191320e15702bec949aea1b03c76772d18f8b12431724f01d5a2e0b164d1893f1a71572c48f1bdbe6dbecde92c0325ea17e12a258cea1ba80c2c2eb1a582ec9023e3272a897391952db13e6466115d056a2d8eae09231a2e082b0a68f4f44779bf3a872458cbed432d7d4f869655f9c5e394918bfa8c383fd07dd2f68ab6a2a3797df5ca4aba2ad828ce264ce24c5e6c8be1a85c9f9bf7535c8abc108ddff2ad1f7e8721f4a3f0fb46b0d37b8bde048ca9801e7baf4399ef52941e1e1543bfca1be21722d0c1ce60e06fefa24f305ba32a4afe54816836204e069ce150673a66c8dc209b30da4d6220d63c6314b4c2b45ca89ed0f4f0a06a544513a587e894cc75b177294509b6dd46cf37e3e3ab0f03f5a640778118825487d69f05cc2f43822e258729879d5dda5e8d4952371cc2f4570c42082634c05ad47d643f7e01340fcf26272f6c8bb8a50402221e9e02b2940d01b758b0ae7493045df56ae452fb9e8e6e1542c06d244380da78b33a7d31bc502a1c28b68e81a4488b3f39390c84f8ce15a729b381d4bb183439ec71c37226dfa84df1365d8cdd98e69bd6e001e3bafc8f84e5b9e2f9c39cadd4fc768f03d07dab31da4a3b841f6e1e2f5cd94c3398316f1113a4183cdcf9bcabc972a6c5dc1d37429c1884d5b61f87282bd757828599faa0783788b7c9c6b26cc1d58190347bb9f58d50ddd231d1265c274ac0732a2b5a5483a12005c2a9148294ae628c21357280e39fc34edf6983d70b7a4c35fbd27003ecc4cb334e579e0d66a29b5643c71a4b56236f5252a8832d1ff4af375900c4f6007395368d1cdb90009d67a34647d6515bdd519abb0525307df78ed51b8dc0d27b63a6d4b5b3e61e1095221de1fd6934a137815ee64cf61ee92568316f9a84f7226c54e9ef2e820b41a77bd5f65614210a7d409200ea9a9bf30b34390809c4f42e8d01abc8be9e0f8a8fdc5ac332e25a299cb8ca0946e3ed5d81a771fc9bfaf14f0728c0311df72ab8416504923ee15e295dd32806e960c41e6343d09e819a4727cb14ac479e14819a7a38e2af15ebe9289e596ac6ba8ac31eaf180c2d44b517227a130799b4013ad42cf106462ad01d8330afc49db5e22f2d611d18e1a4e6d720af9a50ea4ae514d48189b6a3c1726447a918394931d1ffa4a28b0ffe9a2d97221bcbd94fdba90dc0085440bec71872a4c131586e64b8f529283b8936aa26764d740818e859c8e9c8920bac131efbb4376a35247419f5fecb359775d4908d572ffeca2c16b470e676203da7ee2702e3fe6c1bc3dbc6b91457a5d4bb644f16c723108b2038a6402e07a4d499e4637a14bcc288c9261bd229f0a1653e73ef48c721d764129298140e7f449fd3ebfcceae2e22a7f9da339d095cc3b99462586d672b0cb08460e79260cda29a8f70267ef85dfbbfcb72c0982110ed7255189c6a13c45cf230fd6241a9bdfcc703c1a2e6d9d62f2f9d49f8e238f44f4e30fcab9ed728711c5564ae8f0cfeb8fb914b1eaf9925e63ef21938f77bd1fa8ad2171b0fb720dc39f92a2529aaccbfcafc3f2ceac20414315b498783618924cd4045e601251a03a771872612fcf412f3791ad4969273b4c577a800f28ea9a36bf8d145f0d2546fe51bed49b8dfb20ffb2dfdcaf693d72f01072296969e447685566a299fc049db7efe679387d4d4ce67b1a24be83a5c44143bfd0dd4c91423b74ac0fdd9d7252a295603749f19ae5b55322281fb642d47c7651d25c1fe3aa9ca66a9f406a48f94c2f3dff06bc1efb84af3c45ad6674d8c4b546ee26c00967d2b91ed4bebc15bbd44662262a502dab427a994bd65f52ffeedb3c0b3a3791a7d68198c1571572828e9bef15fca4a01906a5291189a70e372ecf0df449e38b82973a8c255f9672741e4640bf9cef194e8d4286cf1998d90b1467f245d34bff98a8896f070acf72329b17e3f1ac1917d1f24599128c9ee50379a3df49a3acfe66a274bd5daf1872803799295fc19b7abdc696ba59161eb344208e65e169857aa423b143664f70722d86314f90731955308aeae2d4906ced559de10e9006068dcec958b0b8f7cd72c7d1d6cac6636801ad8cb864f3f46b2b28a254db2c854581897dd01aa456fe444c131b3344384da620f9c4cc21f40bf089fede1b65eebf8a44c0e6e5d8b1a45f38e52da3f8dbb1316531a5adb51b0bd6fa4ee598c3a9987a5985214c7b17b1726a5d686391b8bf055f13254f63f57ae3317d8728606fa8a6ca53064c072fa86b5c587a260a8ae748373fd92495ac763348bdd7ea1486f067eba3ce572359d67222dd4244892a1c1705d0ce3ce0f775141e841a9ab7029c588ce8f60ea349c37255196445b4299238d37b002c48ee53c8c52b5460dc01caa5c1bcfe079e7d5a72413bf6e367e416e9697ac8c30e5260ef5463c1dbd0d0c9ed36a5e2d11165490d2d8800bc754f0a6dc998333d0acf3a72822ad22a960b088789ca62085efa36611e98cae20f8d3576b529c341acc01bfbbb6bf5c003b474a40393fc1a6daa3e72762bbac3461e96fb708db371736099a965e9ba5f2c4f9dc48d5d763c4089c9727230894604d7235f6bfacca174797bb7c66c752b83b4f394efab445e8c2dbb726fad34efd1266fb57d3db64f770ff5af41d5fb70a7df7af1a8c8bbb7fcb7757238ba7f5bebfaf87996e12dc3e336275110ac92678afece7791cdb308b4a471046af055a99c0ced4d9d5f57d019b3efe1c22587221c19280a0333a02963ba481c7320f2cb3282fe3c3112db29fa83acc70a90236f85612e5c0e99a4d9edd5fd723a05009afab24050f17315519ca2562fca9fc62eb86958d44a160c918aeea6721283d04651704818d0cddba3c2056bc0afa2bfaef002ec8fb29afb687556573ba87490d722156f0551a8475968f7b31006ce1549dbd9bd8a7eee38b39cbb3b41d9f9ad0826cf9d45327815202ce0f170b24ad250a3f1793f2235068b8bc0d17263a05c3f7b961dab0550ed5fee43344f4ba3e5c9b6e4acb91e5e70bbea88ad4568f2b663f0521dc588f2758e8bd31ffc46b37a1c0ce6d82c556aa712606ae672de089cdca3f2bb3aea5542e41839e9a0bae5ed6b679110e430532810934dab72f8931f8792b62f6f99d9add61503b89b233d66a3ebd7c7f6d56aee8d87dad07260704751f7f1dafdd91d5e18ef094b2d085c254eba7da68be6b462601a6c77129c036fc7c9aa4a2e13872fc958df430b2c6779bd982eec1d88784638912f5b088988bf8363dd94d35858d5298d45115044d9a973073411f547a61980b9f83b720318dd06701ef1fbd3bc648a865529d80e22f74c8bc1de859a684ad5bc32e04362c8986f270b5927b83e794e746a6d046d3e91dfe764aaa621d9f8f4092ad60858c790f1c4b0c67b2f59df37379eaf517c44a09bfa0e9714ce8d613811ea0c3b423717c1025b70b61e022ac54035d7415bb4d01a2dc6c2b92312196fc438e872d798781e6989888f1142a76ab95a63b9f7c9838aca2dfe53d9fee5ee5641c1727a867c2540eae055bdda9f41d5f45ae345c18ec1fa4fd98fb2d4c4c21d1cda728ee677f4344becfd0c5fa661482d401a24ffc3555268a50667c8e4c03a68435095a87ba4d87f6105921269602c1965f617bc5cb4eb7f9904efcacfeed0f2c8726e881c96f323718590a1bd456124e2a62882fa51d7f95d4d932d78b14160d672b1b7f32c81d20161ce815ab4436f78a1f6d796bcfbade533e11169e02b8ea571d02ed538f0a0115f4295ba2b76d09bd479358108116a29680ba88a113cde6936190044b2138120752bbdbff0fdeea9ca7046ef30cca2a62722d24d3006ae3a727dbf7fc15067d725567dba2f43f5182ab62283b2ad642b00f9e87114738f9560fbe7e1585ea409771fac721130891f8c40b0db48f0affe469c17ba72ccb00972e228f4d07a65fe40e3a97ef131a445296b0cbac81e41bf6c3e1fc10dd0c81e72983bd38f218ab60c3775679b528096f2695a93f8a98ee6b8bcd0907a02577072513801f1dd79a9ddc277d5e26de21d0a38e7b9df4db7fecc1c70ae9a16390b49fbe8fdd7cbd221abb96663df268ebd52869a39ad3649cec8b34e611a6156b32714806e69c03f2c634beee3e7c87a5e1e0a7ee04e83a53a33d312b6568aae550b70202b524a12b7d9a3fb13c6d775a24229525934ef998fe49354c995cfa8b0727501ceec12ab745d49d998fec0f59b2434a8a649566722cb9a1a4663118b4e7228a64df38469ce72a50562efae4da571e41f4de0170a8a5d3e790fb29ef893723a6f084470a694294b0b5f5849d1a3de239f2715ad3628865710ad1f692fe3721dd581a782a34f7b15396bfde44949d5018b7aa756938ed058f164fa13ed42606b0471666ced1b314c16bcc2950276222575cb90da16750806922498c75acc72d7710aada85fbed76159deafd393189f48440caaf0d0c01701d5328cae14f4726839a4de7c19feefdd6cbdbd9ff884afb2e136043ad1f98226e867141765a67240eb85b14a280d2fd12635b87d72b6081d4671a9659c64d02bee33816502314c97f8c043becfab2c63a67d48a709e6d821e437d98c19bee6c2dfd8c76d5e0372db4afcf1977aef3b7a72b1cbe749b4db3d2c5f9a9b6f4ec8beda26f14d82fb6f4f3fe3bb6e23b0d78794ae125c1d8fd894a4f1dd72f941c930c3dee7dea41272c9cafe34f04cf9cf848915e613e89cd694c00e69386192c50471a7027db77124d33a94e47134dc44d6d86a946459267c6e5d266dddbe8003a14a0db00e59db491897d70fd75c73c1264af5182e1abf032f60ecc1c013dbfb40d60c881cc11f729436f9d22a6257390495b9134adb4800c7fafa8b908106c202c57a7faeaf433ae67e0c94ccca1564d493caf27c690a030d96bde2928f6b84c03a621bbf213c72f18d22a850b72b622e42946af095a7b50c9ca016f0fca904cf23287e4811e03607b1c5be38efff44a38530d4a5c18f3884f3b7863b65b91a73eec3d245a8bd728b80af0b5555b04e94bf49f07089d5ae8caa80142e3c98dfb67f9691773b1a5cb3cf28a4b2c192c7f6a46fdd42a7f1adef62ab534c054264169687fe6f4e4758efd32f640cc746206544305f5ce4620ed9d668573da17b1ffc020472616ad6725c6303952391d748dce27ac98306e4056f173840d285a21e3f369f99e700f31bc3fdd18e04704cda18e65b77eb6f581d6de1ca76dc185f66f1004fb9fd83f87299e421fcd280f84e48c015d1deb61b3e283479c0f9789d8eb399bfe58c9579726ec8bbef3565fff1bbdafede03ee8a70ddcb01f0d340a7c9809b13619cc9cb6ab30d96855dbd39f558bd2ae9a3f2c45c781a82fdecf32ce91e9ca613e1edd8728198eb9206ec8af6dd5f6a4a4372f2ed1dd087e2614fe730130c58d7dbef830858abeacfe427d1e08c456b0396ca8de5896a012c62aebc9dbf9632596bd6195801ad1632c0ed44bfd11a37b8596c01e2ef9f637dd9c122975b34d44643c35c72efe665424987560be4afd56fdb2ba7e01b81e5d9aaeaef5c10e0ad3e838bda04680c7773f89698391cfadbc03d262cc1d85e9845dc98f479ffca03c9217ada3bb35db400c1325a73a52fbeb046c87e04aaa68ab08c8a7efece4bda2c84ff51364fbfa3835bb5c97bed3c7475641aea466b463e89cdac77989b7ab4adcce13637593ddc86e11c2891218801c5efabba8c50e5e2c494d8910c7a92a7e9d2654872d39a70eae411f26ab9a123c2ba5cdb1a9978ef25b28701c21d67fa7b32cf4e08b24832ed4e569d59cc67dc155048a600d3e7184d3b3640b49c53b66320f2086825e775c395454791f7461bd67394c2eea2c0d7c5486920256e5d51d41b2df472aa75f4a031a9f3f0e63ab09bc13952724784f9b94ff9ecf79e2c02bb0ae0bd2a59311bb611fd56f28d4d230418a120eb444c330ee73a3f482655eb48bdcca572bd775a05b701e2a87c49ead844172f5f027199c0b97f3b9dadb803005c60ba724c526f80758d851d249226fed55f98442755a0d9f87302942d1b9a22e5450f72e215c80eb743a6b1e7f54e12d7f246fa8b78ce39fb0b7145f46d2a822c544a72f00ecbcbf62f081fccf67c169d16e8b2c45bb5be4a0a489ea402126b9232f072af7f2e5580c80c5ac0118ced00b81c5f37a54235d6e1cf348a109c0b45119872ddf201470fd737555ae7fee76c3d3aa47ff8f8dcbe4f1d04fce2e8a03de62f139362127b3a420d75f53d335afc36b61ddc4bcae8e056aff7110f88371bcb96391f1c0a9aaec4b0d337337aa2f285e022bb85ebab0e4716a9b3e67f795d3d586a6f4c5f61227b9d51e3cd49939ba945042234b5af28010c8097e717ec1f6808723626914c2dfc0df953373da0a26a901138f34c177974257101b5942bc66c747248c08af2dcbf845ee83544a4c1cd69f01c481759500ed70eba81225627031e37ac47033ee6feb2fccfadf2f648508da1f31db6bed49fb96eb2af0ed0af7a454332dc519354d64339f3c0fd8445c7ea208d7bfc302e292606f0b7bc8a37da5c3a22aef28b6fcc3a7a9ca7ce4a78da6ffc1dba95acba906f11b0e8859938c85c133bfc59df528b10208d44b8f36fd9b65ea87462e1a9a7ef73f7480ea3615b0d47422c059bb5de75870ebf0790a070b20fcd42262c2b40a2e68fefe0391f7a007247341098b80d17c8909b20b92c3dcc61a810ecd10a757d586074b5c46622b550ad869dc42b8ae5af40f2f0f265de91fe0e80803545d54c16e87aae483e926772a2c6f745fa0014bf2c9b1e4368208c069a6c7f96bb7045d0a8ab17e1a7846c726e6c29baea4a674db60b05927863a209e8f6b156b1b8bcbf9675d89ea4197272c9833c69e7e1a596a821525d2ce67b563826fcbc1984d871aac14f8248c7c27227b1c6f363ab71719c46ff7e93a1d1605801cf2685009d52a058d8bab75c9d72f3cfa3ea86ea3da3ae0978f9476c6ab934811c4dad4b2c0d0fb76e248a703562ab63e3a9dd5ca0f681c7e680183b1da0d77410b92d183d0a4c4a546c147b6c701e2248e287609f9350cd384341909c925dc444faf9f81aeaa26ca66704cf85168f2ee2dcc5c52677186cea140bc3d333e4ae0e5803b3551a2850e5940a258e720e0e36e7115ff710388031143de8d27711e4d12368b89f62cd0add6a368a741aeadf2077209eb5d81c1aef3f8421892d02fe0ea13e328fabceb8574702d9f9640ab66e0283d1c47daac90014c91020d5c4b7d574f73e7567147245cc18f6725488a2c31468535b4315c4047a30b2656d353de933a6700cab2b95f2b9c9ef6a723d18c8d933aa62b0ccf0be7419fff48168258dfcc02b362541b9d8075be67e7238ad555a4c204a0bd2ad740a137b9ed719c041428a1a842089b78e8334100072537bc7706d235c129a5a288ac3e84124eb67351acb36a8885284a1c592977172b352b971ffe9d4bf54417ccbdc5255cf368779b5674af594476a31b42169803653836a7e78d80ec654202362944af1ab22a45e1a97480df15eb518c460e9077206a702cdbc81fad34ac70cb472ad1a8e7b1af0e613392cbae490d5691a8601727a89864a6956170f46c1dba2098fb8e4374650b96330a66113682448858fe9720efe6c2f9f2d6fb79bae3b2eba1dc60499a0d5ae9cfb21d0e03e66d70d8bb57276c3004610ba55b172816457a7ccee76e39a563334e1b5b240679c12eca49972e5418707acee6ae83466da8fa407d00f03b88dd6ac5b5d6911c7cf5ca1786919f4478e0234b3731a52421f7cd053f60a4a0e75d0553f2115892e6aeb789da05f240cc4d689df7cf004703f9898ced63cce43a8d0cadfb3b969026ac50b71dd1be58c3720b9a2e49b6f02cb11cba8a24db4c451378140b98b4907d6f0573a226aa8482e36f236c5e88c1ef42a02e3a15f15b1d4d8e2d115b37f2e6d28ca36a7727f9e6b2eaa7140b85339b9cb885e6ae44e7c96294203acdd0f39a787354e8559de8b1bee0f8a19ac90aff26b835305a0f0724a6817aa5b0457f40bc840a76006737b9ab654a0adedb491b75af071c5c2edc61721b7c04dd4dc92c35ca4006e3dafe0fa4db48d87c91b0c0399589ce75502fec5e8b5a96f1ff13a5c51926d05720e1ad894ff31304de54be08d2c8b58f6d8f1e56627a636c2e17774a9a9be41722c90be05883a942842342301b42d3f8ff4a91170a32c38aa230ea9a4b0c8197267b6842fbce6b5b3592e3cc6eafd95d67ff9ce4ddb605abc1f8a97cbc7e01f2c89b1f983491bef169cd5d2d4b35c0812da0b0fee264578b9604c2846b74e1135f07d0445f2866c4ad435c7d7df6e33653b9a3a3d3f3908f4d68ca34d4b972a72e9698ce46dccf5969ddbb00ad829e0b64e047e1bda20bdc5da3eff9947b94972809af907bedf383faffc2584670a88d6feaada8a2aef0c9aec2b8c1426ab2c71e0026efaa8f98a33727506cfc76d78f745554762b2fc3d40caa742ddf830a14d6660999dd728280f205e0507a35686d81d89f093c71f1f5f5b0aafbc65ed6e7207164e894d1c97f1197675dee9d4a2ae034b5417f69f8c6d6be8ee1d933deb722d1e57975749e9a3c97f475145433548247c7f9b0aca3d9625ebe02f6fe6e672b7940fdfa3385f398aaec80a2d15297c9762b46a4f642a77d79815b94bc57272c9365775570c17aba1428ec4254b40586fbe85dd33c302b1443eb81ff45ace53a2398adc97f0f7854a0db6420a7e0167a086821d97a1a834c9da943e4c0f64723570dd3d1e2f70682bf519f94bcaa62bbee881ddacf288892f8ef92b9aee5f2d317bb3c78de64b2ea05ddcf1704bb80ece9eab59626429afe0401413effa5772ae9370c9f57f88edc0f6012cbc582faba82c3ce738a2f58b1e7bba39c1393b5328d2678a1eed8f362378d7269ed74bc373f0649034c0c8a15c927cb89a8efd4a45b9b5b76b2168f5dcef7f7884b8d653a177d8e20d55d323d92294eb10cdf2724ee8845133c77aab2b7ac5c1a0900d0143ae491a7e0680f462e437fc6fd5ec7282e001aa3527796f8edc9dd49341446cb720eeb2dad5cd0a75c65e5f14a27f7262125e4765dcebce81a2d88864c380a3a9a5cdd86bcc282e82820ca0d28c2b5d8fb3df11c85324dab9fd96347b31ed17ed97dc9ed1231090118e59158de5d07298377cdef40e8800a2b70ffc93726e1f8902d658d39ae1ba8f193c2a15736b4a9ddc42b60c5e367763eb06305a48da910d96bb1cb3973f067217ab29c8d31b720917c6ad6bdae76e729c9c5dca847916cfb4627d811d43f9663d4a537f2678727e71efb7d4037c356daef1d1ac2871dde2f9ab0575f7a1d18bc8cb1e2988684e4ef1c80f2d90aa159a51254ff3d357fd4502fe23999371f376e24b2aa7c1017212a9e214fda416704c2315f8c69d478860d3fd54d2ff46cb4614b42f3409ff729455b08a3a1a554f30f2908523e204a39c04296ecc5eddc07fa254ac27489505806a04d69939c630df96989c34735ac5c1d40f5c386c1292f1de12180b7e8d720416e1e0867479388982a48d5d7b5ecb923f754cb516c7d62793edf31a5ca172901308103df17759d3b7608c177e4e02068be7886ce4b47ef97d7f4c6b004a724f0a89d820cdb234a141f31b00a2942e01f255bfaddf0df7a16f4ff1a56674723eca1a8bac05d2d022a4701830a45a864fb92b32c7167a2c9092e106ab3c7710b66152faac4bce695e426736603c78682887e6d9b41f18fbd32ababd429d117216b7afd61aa1aa86a1d521105cffdaa411448c76bd254a532dc3e50c656f5f72af0c2a69d95420d5e8e7ce35a0608d2f3ca7a54fdb49d2d2a7ad25c01db89472e32a42f0a96d3b6bb5df1dd825f64504d4c2de8cd26c6244f8505dabbc137b728e6e5e8b2f6adde2b00c45a39b8761824d8ba9f0f3564dc0e0d65cacbe4b910cd9e94345c699cf2c0893cd17145e1583f6cf293481fb86bcf240a56ff881ad7258724f96ff261da916e41355a87d576dc71e27640e8d14e3e4a186cdedac1664eec2c4f3ffdfbc48d2f10b2b9072e6f6d657f7701b41dc0e330d4e16f5fd82729be0aa2132ecda91cd157a944e32875c108c82ace8b8795f289ba5bf058b8072c8abe5a133e5d9f9072e783c832fdb61d40cacd6c07a73fc65ed10dfa9bacb3e50c27622dd19a0c935782015a622c396b43ec049d9c331db44d9e51b7d976672e2608cf3dab668c2bdb9a51bd6d16d39b5e6956c75fa95f10afd13872a73de3ea4232a3e9001a98834f333bf5de5b8340492068b383b76fc6901af031604fa7295979ed1e4010bd834aa6469f35dbc040321f229478cc2304102baba602e4f31dde54c1b13f505b262ee004f198bae1f66a35b4f44a7af2349b5a1e53ab338727c36b19557447b954acb63c6fd95e43405d186ebd2dbcc2d89b35872c529c358a3784eb94844e6249688e953a6c9cc320ae0aa8a328383c8632a73eb15b0de7294c11917deb9823736bf1b1724659fbacc47c86f9b566b9ff7ef7d4f7ad206721206558226f903494c309e1417edd7502dc19e95aad4266837ea4c6cec2090721d222ffd26b49a22b6db4ebebd33b6aba43087e1d178a5dfbd10ceedbe317d5ce74c8d53716f235c3ec29a67a8c13c1e8d7fbcc36d5618f8344492dd3cd49872fdeb99a5ebbff3c73d8993d687d86faa899909bdd0434f05071b3935a383e7649d9edb6fd2d209700418d75ce793ee6df79914788f2a6001d81657222e546c72a104cdc04ffb06c8c1bdb8c554e7b2a1eeccaeab596dc2469cae108d40f00002efebf5cea7131f4d8462e0207f9e677e158537bd8b16ae7406e6773d6f71b472e76f507419c74b50b7d410dbb93e335f88db1ccfc7192a7021c46754eb14872b6da05afe69f10bc83fe6b75662f5dbedd089ce0f3eb6b8ead80492a08481e1728c196aa1d6dc72f08f04c86b2e9048e64a94288583772ef87ad01740db1cbc44db7fef9e7dc9a3b4e3cff785c6e657afc849975cc1b9ea249b14a794c0895e7273dabf1c01e2fae1999d992b5425e7663d9919fcf64a4cbe2c01e89145b42015c212e8b1f5e2facf3d3facf512b2acc065aead63fa7f76388ebb3d79e55c317298a48c0bf80cbb98062abdc438bddf6cbe12ff032a229fb63b88dbe582f1a7459c8a0504af5a1ba8a0b4ad3480103d2d068cf19722122bf0224c3df033afa9727e7c03978a35dc0f6aff0d007a39075ea8702d821cf1754f9468251b4eb42e34cd6adf7dee0a6b862ebaa9fe8423d20e83e43c6f6f4684187ec15c5affd0a7723930cd3b1866cf506e469c11fdc02a48cb34382da274877fc4c944ba64668f1780682b6c2f3cfb20b56ebc6e66d87c65412f6b5ae529b6b613f2416ff9e1ae72078851cf1b6a6f0f54f0e7b28312a47551157404c5f79d903fa60d84a1cc2d722ca9910bff844f8606065ccc14f7feb4260f8fd65c01af1a0669400e75f1d872f35d84cbb838c9413739279bd3b5b9e0c037eb8fff8d329c4c6eb29670e7d4447e1f5eedb19274f1eabbe1468b6d90cecc726867cec2b8d5aa88bb2153dde572fcba4920c648416e7af79a8647079796de19b0b29d786c47186de047eefc57727c7471810dfde7b7e2812bc5fb32550881f5d3fa702b01f2404946f58ef44772b149983661930463e44100438525e0eeb1e7240497aa5504ab815c399ca7a47284291df3b887ec25d39ced7f2726ed6379564770849e007f309dcda86886c9722d75568b13890130c10ef84ebefb3eaf887ca2f502fcde0e0f80ef95b88f8e7234b1ca9e23be0d27f2f443a9ddb1ee28c4d7aecd2ec3f76ada330ba683d597725a03e11c49766cdf0e6aee28724f30450e75a0ef2f0ea867e9faec328d28ef252305a91b30f645c4bc7d23f81c0619a3278f22922de4afb253e62044900e975d49bb1b57fe7b29600bf7cb75bc69db88613ae4209a8b7bf2a46ece9601733f72264c50907d94947879f7b5d8b90c16319ac570774b1ae8069512bc88419352686ba1fb3336a3d9c32432533cbb74845eca2aa5a5858bb77fb91ec3e4f95ff62a382c893dbc2a3fc8c47bfc8800dc8b0e51dafae740f43e792bfbf35a8fa7a22dc7301d79e60e133be6333f96b61e7efe15f1c8f324e5a3389988c15fa3054b7265bdcf45575b025e5567f912285f25a4a4a525de14794cbf6b077369ae020a72f03700fb5df8d682b51db16d304d1973c64d841c624d72f72b30d2c3c4634b72e923c02e0022d4f55c7e99e4d332f9bd15365d086d3e5e1beae9944f0ea9be4f022d8df30755509ef8afae42223e4b8b3a6159e60ae8674901619e42aff86572caa55f3282d33b608d2c92d3da4c859e13c63ce76c0db1247c3ac4da3e6d0472ae35d0b7bfa7b099e2b59b264ffe80196d804790bf1829920aca62582c488305c4eed0aec00bea5ca67b065e2b5e5c2087be258e25276cf75cf01a4a7a15d758bb67ae0682819248cb2ab4e0e6e39a014311bbada5482e67ee46ccc700e297224ec55d0f18c63c99fc77709179906145113a5c35fd4f48e51cb05b86ef782672c6b2f375acde4a2048a2b4d86018c8766e271b126400f89cb9779d5cbd657a6c52a6e1f0f4efcfe5ddb6d96ed644bc045a7388ee76fcb58c183391b5c1bcb9724169ceb478f3077a495115c48f1446a0b557e056b549b2b0f1a828826b0e4172ed551a0ab5cca816d39ab0cb79b63b0f43a20746613ae57031339bf882892156eef9c3a047a44e83ac605e64647f13db17f42d1120ef9980d8384c68a52f59726e8038d508d42373dcdf67f96dd3c79662f503588a7baf8e5fa95a7f337e9f5b677b14c207db71a14b8888e78a09141277fe6968f99ca7070382e26b7e6bde2383a4f729a343357fe457859e34e1e8cfd2edaa865f4391ae774998dec64a08720efcad825c96344118dd5270eb1c8f0b2456dc166192d7ac8108b23bfc6cd561204ab2ff0c2eaad1fbffe0f26448eae085afed0d48dbed0ed7b97ff269f025724ea310c2f8c7350e7efe6dee664ce1506e91ef4d0088aeb912f495af5e22b638efbe762049f9a9d25d7c9f6e31b8285dbf11729471fa708b2197908981dcf954a2b9877e64631caecfaa9e759939691cf4ad9c8191ca775e34ff8a0fb0b4bc3afddf9b19c59351d5ec734268723b2ed02234e07c6ab21d71a3a9ad5af72eb24986e3b64859471691487a6faf8ebe08acdf2d8eb58ac4c1da3c4d4e8cab341a2d6add2e35617803bb43a99c5f3f79795ad087ef932c5b9d52b324f363106cc77265b78f5a7b6910d1f83af779d6df6eedee623acc7890487287ba8c73ac4c587285af676e5097a43be17e0a46bffe4777344bcc2d04a649de2ab23766b239a43b9fa775630177c762621bb329cae9b45896a6c02eb40af56590ce4533f8c48272e22a0ecc22a5c59ada5dca05f644db4c93afaa50ae832cbd15556a4a2faa287272b4fba2e8bf29302b28a93f9f24d5962090e6f3ff16aba0ea28a086c4e3c82d13973987ce82bc626fd29a6c57513e9e8c80ded3093af9bac63e94aecb6b34729862cf71d072a73dbed86431b064c41b2f15b0923308c78975e7114dd4547d720084baa3df13b3020d9c28ede45146fda5768d92ed6cd44c107d49c5d5dac95a7f7a7bfe0010be95884551aed8ae412397a77d76e66f39005ffe64da8dcd21489fe7baafe1dce833cba32d4c6a19d2c4070f46ef3e13a027351f61952732b57219d82054dc57438c5d3dc6d3a29d5bf386df64b3e34282e6f05b90b7a9330772cb99d6aa39b2394035a7bc90ddb79e169ac35f1bfdd137c524ddb31165bf6258b9bf3bfa494baa50850fe15f01b2380c45b5d5327da2540272d75e6af44b0741258262520bbcdf843b01a81996c2a90bfad6e379e3fcf14dcbf8505f87b0ac72efc0771062cb658169a2fa22a7be73fa5f6bf4140f1eadfae73946d6c9d7f515f1f0dc48b06f31a8ad19f93974a61f68e05479adca1cc9ecc847e1e14ba6ca33a91d0219a263c23cd21691dd7353cfc998e5823939cdc8e471b17eee3c109372d2874427e2772aea6f8c21ed2e2807bc791c123235c3def3000c6557490222016592568fb66f112012aeb6f252769f05a67962b742b835e6173a1bfefbb5ba729aa21381189e241188a66f039826108636eb51e9d582bb8d594440936796e972a20c21ec93ee1d568c2bf889f10fa23af487631841ca82b06781dc420b9d1e7270f818b03ff576b7399e7bc4963609f83ccaddfb02db7ef68ad05a8504b13172aa44740a0b01b896f9aeffd1a85ab9de8bdda00321bd02a7242b38199e05b672f540fdf6e4bd423ae074753debaac4bcd541c9b8b3fee751f6facfa603af55724182f515ac7aad005facc870739c6a602589bb0367c88320aae22b5012685e05eae2fbe8772a847a456e1809220752bd6b9f58e65484a85cf6ff6eaa272cf2729fd1e8479d080caafb99d5afd6d2cde0629ad20b0cc2ff45c5e148f02a85c2294be5352dbdf6d41e3b4913a14628afb371125384b677f799497679395c7a9345bb9446f718ac5bc9ca115dc89aaa308c5a2660c19e2cb44da12f97dece13a46590d72d5b81f22482ac42fa778c85dd6991c0b9dc375015e7811c7c2d461a2d4b5475fa1b2c7623d04e9df39809dcbb30214a08ffc33063e705ce723e0c995e72d8d0e56e48b6a551e95cfdb8f2e426715a5e9190cfbe4c2c295f1a011029b0051737302d554c155dc071df7081580d42c94b381eda49f610de5eb461d173d1726da7ff5cdb64803365d8ece093d6b7a4df7120c2c216bcca550b9c78a1fb0d5f16c7fbfe7df77bf3a43b9753a774df0d9f66b0c2d34395361028b7874899cf7221639203827265741115761e1e65cbd2f905272c9e38158987fbb4cf169d3c729623bf3040ac8f6a22849fabc7d800249fbc2dfab8d91f0109e395c438cd63721cde85270788ff8055235f47459339f7773c8e5eb9c0fcefd02ca06dd6993247d30afdc652f5a25438c0ae2a9ef1ee54c33fe91ba7cafd34c4b58af15cff6972345f744953e94c457e6fd40583314dbbba59868f5fb80369cd4fd7bfde0b1f721d40075b2c50279ae95e290b758fe4414c9435f771a4c25c80b2eaf20d878572a53404785d90a6017b2266fb733771d45f6f6e0b21238ce00f80fb6640c02d39793af1c923c6d43923e23127d926bdbba53d10a36f5259f4ca30407956abbf29fbc3514cf2272dcc2e561f07f86d78f484168dc18cb5e41e746cdb47ed0b58727af9f2d207c310b3c9e490c1b9cae1294360f18fb96ed3de040e196d229a6072598f9d866e27d7dd6a3ca89739a991a2a687109b2c76d7315381320b9920183f919ce70977da4c92bff3ed61b335dc4a87f819c954258955b2db8e4c8809807261ebd74f68dcec0ed402503176bf291afbb3f275a1bf01616fba592509bcec224a417f03f2ba6722ffd282cd79c68fffd4474197b68f51727eaa656e5b51f97215611417dd17c3838cd217e95ec9f66cd403fc909dcf815b08db4669c7877072d508bc632d2c01ba5477a11ce77091c189039686e1c620eb72063ca2b23e6925ea3e2bb43e3790d4869502c4d20f1cc15c5b493144180e63f9e7583652ee3369bfdc1e1f809560edf72a47c24946cfa7e3c6222efae3fd67a4f5a92320c9881b848752cb068ab96a8801e066b1947efafc4b0af82a04ab3c894ecb485cc4da7232ec7ce29e6bb0f0711f38207ed6ad57aa75c9f8be47f64d871bff59d72e6e5dd6111c13b474d5f05c837621ccd58124875b7fd45710b74439c12c8cf10bae0599e9ae558711f1d6fccf8b5bd41a916ac3bae488a2028f097700dc039ddc0872f539ec54fd1e8a0575a0bc514cbd33f9479db8498ab4bb6f9e13ccaeb4c512721bc45218a36698034273dbde8dcd24917fad7fc55fbd1a796a02855b14db645d03af0b7d60b61cf54bde3dc0532e35f4d54e27a678e8952db91fff7e6c699f30eaf3b31d7cfbf0b741ccd6329319a6ed6dd8283400a86e87be01562efe336572ae052d6299b57d17622e8960af7ddc06b8f63d6d0d22d92747ebf758325d83723c01eaffa2d21d99af4ae6a5f12f7962b1af28245d44a144efaac98953160a67f454edbb42e97fe20009f960fa230d2c99990944798fa379d77821cde57667722803d16888c017aaf0f8daf069eb9e149c6c4b7b4e3e0ca656bcd234fb5564728aa55d94f93d79ee107c23f588d83bff1dc225ef3a8503b9e696af4e1f2da6070871ef064ec880bd1889f1bbed2a8ed18b85ed8754c3d60a285c95831e7b6872ec1210ed8e431bf94652ca46a3d475781e46a54bf6efe5b722fd93a770791a7297da33342b81f348017a8c23aa385eb8faab66718e22afcb5a3524b70c4f596501353d6414c070a76a10a556009aaf9309351bd90e33edd1bf5edf64c8e527724a3a401320d8343b68a2f9693450e64d4aa70216c1a834921bec8b72ec6b5a72323cf5ea6424d55c20ce385c086d17f7e1ea147f67d956986d80167d9b3b7e1d583ce6f508b6a8052eb01a12baf9ffb283264308fe14b1396bb2a108dd105d72a02bfeed21d9ca8ce2dc7b3389bda4b9a40604df4ba93e44af280ec30afd5b72e0c394b6de838dad9fc87a572d83cd2715597423cfeb7cfe5e5cbd22b8d01272e5ba5cc37cb1bc58342f79a54c34862187a3b02cceed0c994f7174e348b8df14c60c3bda6aa0fb0a4c7c4aefde92372b8997db3e3da1360ac0ada3f31f26096569ac19a4a865a9273a736924c2bc44fe65371ea5237741557f95bbb77feb664c1138506d9ad0023d63cca29398efe719b507f7843637211e28b5adc1ad132d72ce00c71394d29eb6cd2e9ba2d15c7d3e0671747429ecf51bbc2aa114f3d9b361d799d3c0961afbdf1bc15946fc58a3a65d9a80d17d3a9cf370ca17a2ea2ea83da8803084d6bf75049c99ccf03ce2e585dd38bc384a6a81ebab83281a630c0312f5e7a3417ea1123effa0a268e6e2f6f65734fe6bb87cfbc402cbe6645de8cd551f814ede6d622a6f25983b265e811da2abc0fe80551618d68287f5f29788ae727b4ecc5a4e7618a3a6834f64f1606fd86965aaded739516fcbb2885818737e72f479f0302fe3d282a333df0d4912736b3a1bf9bfb13f0bb54029c3f4258e0b7260978e835871d282548b540f9d2fc23872b57e70b0f6e0ab4605670c8500af0bacdb61e07c45c62538ae32d091800b9e7d6f1d50b37e4bf83b3a9451fcbf34725665037bb442207d352d5dbefceef8b737132fb4238ac564d1a5b9f9be19c665c3f0665552db4620430d6f20907f9772aa5535ec4e6086d8579bcb41ae819b72d5c9701bbad09d41bfc55a80a7e95f25f9f08c76df605fc36a8bb5d05274131cf5bffe80ba0c01d9d789842cc0622a895780e380d71886bb24acc0fb2a8a8b5071e1d3e2987a43ff4639a2f4119bd945de84b7782da27f4499318a3398f9f604272f10d0032f179bdf750bbd3bd6286411ab8e66e71f135064de457fd99fe572ac59314f7c9ee485f9e6d81691286f0b71667ceb1f4d829de01b8489dd9a3c700762618500b146e7470028546dd2981cd5abe224b1476903e33428ca631b371d1b3da431ea9bda71f08e5eb404fc6650c424fbbcc448420bdfbe4ba4116195721c43d620d8534d1bf324be10aa6da2a1b5d7fc7eb5706a0353b989b7cf51f0722b399071ff97a120742d65b788fee9ffb978bbc3c28073cbc9cc2575c763f81458b28be6c8099eae4d8d322c91241d214305e84a23a73308190d337a3d21ae45bbf435acc6dff418eabbb98db8c2714b61a8b115b211222c3237ebdad90840728dfde5ca4503f7587ff2b0c6175722c5b567e600365160a2f638922bb7adf772b5f156326602ec83512fd19eeab2697c549c6954a29b99b85a3dd84ef7e0f154f1e5abe8ed41c7c07a5ecf1f3d2e15399fdf0e193f1af209993eaf3d540c5272f1df10d79b009445526db7166924126e1aed55a184c025fec528358daaf0bd726701424fb284b83e001596359e1c15db2924a4cacb2441e3af346ae973745d621f7fd6cd2d888d3b1f08d20ef9ea9b63a5fcc52e64ccc4543b005b79b4b7155769c18dbadd840b200c47e70cf13c547540a70378683f4287629b87c70c0a6a21ca344400befdc71c0a6716e44becda6399d22b72ffaca348f6bde6f991ca482a185b9e8b2208c291ab24a1f21003ef6a5da4f956bd702751241169aeb629f94b62d40b0b7109455972f688063d37d76a7d29de3c3a3f346c2f1168b0ea5cdf72ae08cb016a869f7ed7a4ee339527c1c932dbc728fee0b16b9469dfe2cde1117239ad9886c7cfeed7e0e3fb3366a0a495164eab42c009c4b106ea75c96d2f557249d614fe619d0bc17df2495bf5b8136ba6dc22504502a340963f83b84346fa72f056b945c737f99a48fa22b6b9c8e77bac3b23f64ee2ebdb614711c8c36bf2728d3195e9d7811832c2670f6aaea163c3aee8256724d107c0ccf63b1506bc9d34df9ba168e6f603b57b4f6f90cb353f511bbbef29b8f76edf0c62bc8eafbef67286c933515e0bfc20f17c1c01ec2a9b1de71c0ff73d39863096315a76b6529f72ad53fe90c8947a96abc35a1c12454af79bf36e7ae42f5475fd01c64a98518f726b32b0a91a47008d371f154539517267e3bef760790916ef6c3710ee4ec91c4ef49aac26b1f5dc405e66362bf5ab9aa403cd468fb13eeac3781e4cd0df9d5a720e062fe9abffa80029dda3b10102bffb00ad2251318647328fbfc85aaa9c4b2ce8312bacc1b78d0cb80585a269932c2a2c5b0e4c7a0dafa15f662c3b1cdc617275bf8726e1c7ab9eee4b56dc59c848f296e59ae4272c8fcbfb23c961b4e9733435e87dd59b73a963e0d463cbfcf20bbe5bb20e2cd43ea68abbad508891305b72a60c725392e913031e7c4a8392ba415077cccdb2b0f67ae6e75aed9c911dec2be3b97b3cc1d525fe8a83328640368b3b0ad5b1c24308b8139179e68ce36b25624383113d1774d8eb18e831ab8df0ba0472ccb725f92a7c1dbe487bf75b8da5728f195510253e8da609ba988d9e9ed423f6b55283035cbd461ee5ac492e6771037d00672e9fabd14523dbc631375fbe04ae9d80d55ffc08dff62926a678ff4f3a49c2392b84324de559271be5e881265b83111a26a41cf3f46e12ead14951bb30b67ecec3480448f29cc480e5dce32fcd1f400a4baa668deb04a0f19eef252a21619e1e0e7f75ded4ebddea7b5c1610de60245c6e960e63441e8cef749f3cc272d1e14a596d829a3b34e9cdcc399cfafce3537eadba4c20caebb8729f915bb872b4c411f61b06f57cdc9088f007ebcd589a7cca0a3277c45e45a39484e752e4728ddc8b015d0394cddb4da879b07591d34040b1f8341b305ea37a99677fcce936ab5ee733edbb569e351b95223f20c5e1a02a9c623c714379cb391fec5208961bc2e1a70579dd8af0847561795cf4425ebe8d68dcc44279728d77429cc735a1723c837a5c1492adceecd49c8498a555f571d2cca3b677b5378783f4b7f045b07239c50c7bb2f908d332fc522a3fe2adff272da39c827310b573e9e0ceb8998472189478146e6027c50bb98876ec3b56d8d303ce370a4b2e318cdb76aa0f108e36444353b67d3ecde3a601eb3838d326b8a5697375b4ac1f47543e15e1c39a43723a3fa94a65e90158156601e9a7f5cea190862284e234e5a2c8e4e4fcf65d1672fc9fed8edb28dfd1d00a26470d6ce90a283f5b09818c994bc9f21679fda70a11461c571f6531087bb517741e4b2726472b02ffcef82d920701749a472b395972c3a921d79dd62bd20461f3835cb714f490498dff65c445b8af9ef7aa7caea66833c8a21e06759e7a6f7fa4835dfeccb33def2d26f77ccad1eab54059b41f7c7209c9cada085a054ad8aebfe43f75ba4d23ef2707ec7ed9d5752c85c359f792722d96a6f28a71fce5a1318aef116995516231f2eec00d85ea2a421c5ccf68d57282de1740c73ac032868cd2eff9564df0c7629ba355053c567ab521b82fe514723e152c80d1ff212ea941ef045e72466addc77a348b65cfc5c11c3077123443723c7692d26dfec867815a869739b45b1d668cc4a836d397f801ab84303894db7295e682ab933cfdf4aae666434cdbe0d1d0f9f1ec0004ff805370d30a1d272672d6df771fdc7aa13a8983c4e9a11f826b4d87c728948c5eed13090676dfaab149f2b1e3d8b885796eda96456c212a1d60d46b606cb584fc86105ed208040b8767176f864297a95e9338dfe2b24b6c6352412a9cde20803810a63a6fb96c5c1e72f2110e5be08e890d9e619f4dc65fb9b199b3571fbb4f210f1bbf697f54f3096bd78065f1a272675769c81784f255d88b8a45adda51bc4723dd8e6036f2605b724992000b5eebdfa49b035e180d81418e811bbc51f8812356b7140c21aaa17b72e2d6811f5d5faf2a4f01b456c5ce13d14bd4c22a20fa65ecab76f4ee02fa6d30b88b35ade51f0a29987ae96c81ae591f241cdfe26dc6e61c996426015528da7273b4da86864d941385055864e6d1beb30cf6156bc4bc40a40f866536fd4a7910e0cd1d15f3193edc6b6201ad5aa6e90fad8d32205fb1b11882925dacee16f272a5b9618d80f95e150e47704a17cb0d6b05d5c7c5dbc01947e98d85c9c6a7fe0dcbf3d717b04bd145dfbbb3f50a506ae5ee01acec0d26cf1fda10cb72ce10671549c4b403361134d0c14b67964cac0e042369251de049c10e7c3e285a8b5f4e722b89688f96defc60f798633fe21c77ca8008e26693d3e242818dd4b32d4e4e72f6b267fa0bb1d92137e7da215ba811fd9bee8eea9c4395850255c1fe899399050be7b89dc97355dec1dadb7dd92e76f48945a9fb71a22eaf2ee987d9c5f29b410dc78069d8379b0cc2cb32859d03a7b54c4742532af0b8c0257bcf11cf0d8c720c6e5da184d8d87b6ed62bc7a6ab11c96559573c2cabb8b218cd025b4a92e37204afd326b2185e241c6720d87ba3ee7e8cb56308a809f544dca581569dbe9f3fb58198065fadb325a808de675da4367f5523a0ead953097f0e2b351c44bdee7203f296497bcf200c4ccd72264c981eed83200f38394a7fe7c7589c3ba8a0b872ce96734ab7dd956c788303c64823128387522ab6749f3f488667623b124fb21a0eedcace67fddddcca7f5076254643db778aa3a7f5f9ea2806a13952d6f0f911ad8c8688c4a4082bf8c05058074252f59d009baa41f749d763f77cd35a8c440337cb6801be2ec042878cc71e5ec4cb07fc126fec4a25f99c14cc5b4baa3d296b81efdd2b91da7abedfd2c9fa6f9d5feaf1c4ba9f3f29cbda4da5464a0fa427143f850ca4225e4a8537b191dd8c659fa5fb9e1cff7627c56e8118e8c20f98e5724659f7a73021af612b0a4d07bcb4b1b62ca582852ba3ae567f4b812ae2ad9772a36107d9d6cd80e8d53ffd1481dad3b043783b7a2dfa962e84b0f34188db620ac4c40a9d8845077153fe13a90285334628f0319468f1912289243b4c0dfa357284133d0ece1606fb5d5ed2f963db985518c73b709567582a993affdb84ca6254477db14acda94be41da4fbdefff843831661bf73788f43089106dbe9127c944874480c122a23577251c997bdab86a32e335328c7399708f4574fbe5ac9c90f72aefa1d25e7004ab7e9a344b62a8576a844ff702baf25518c8f87aa26e8e22e72c7416fff06665ae09fe72979b3e3acb6f6466778bd9de17cf20736aa590f7e7272fcd2af9e4dce7261d140b1f11947bf982dc431dec75c1d8c7847c7b010392ed33b3206f052883fe4fb03b72d27bf08938260d3851910e9bae1a4fc6e976072c4f2b3b01d04e5417f870bc5422dd72a8f4bab922c3ad2f6ad917bf5c5c2a05c78cd720829ecc32a8795b65b0c920337840d347195aec321abcda09e0c972072d49aa29a053179483cf89925a24a6e1a59673f613a4c592d76a0bfd75e8a64722374c34a94b3158b3cde235bbcdcc2ead6582a29622fbf7def5af1e511fc444da1cbd516598b865510ecb7b4c4b4090b21ebb2d108974b040e5822fa39c7cc7252b56db1b4905a13ee9424cff0504e1e856202bd5d421a35a846771256ee8515edf5f8c09b136c5c54d4bee3488b7ad347445a8d2045bd3d899545d02a30480daceefc93da1a3959a1cefdea0d91ec454eedfe6e80bfa005717cb35c19dc3a0b7d5ffa2b9e4bd91ae203da053be804d1284e074a951d60fca1cb62fea1ad2b72cb5a2a17764ee80b26281a1a6915be5ae2d535fb801f432c525ea3656554c172ec717f22c026abdebdab965cce924143c7cde22a1906ec5066707abe3bcbd572af886adee3c4e069a61995580b7c8a46e30171fbc6d9a61fa3e14e9b05a8724a169d0bdced1a7d6e87f2b2ab4a5438cbf111f434496dd39e03619a0e7c5c6e4330afeafb111c1fed4091d54e0ebefa4e36c8127c525adbc6b11631077c92715fa10e565d47d1fd45450f435a538a3590469587b3de2dc9b8fe4ab3bfa224cd726f4d40710c4ebd00f91c644547193e7ac0ea371ae46ead309054b8d91ab0702852e9d79642edfa53738e575617b3d012a587fa439e929a94814cf364380971725e17ba87a4bf0ca3bd5c1eb60096613538354684272e90d45512a791bb4dab6b82154631835eb422a13a8ad5b09414ff7e58d979f3f6ff47a532b9802d831a72642ee3aed7ba7968f8a1f64758ffe4b2b8d6b4c38c0f6af870813eb59716d5124c907a7f4c6bfc22ea9688b4e95b050f17ff1a604d8b8d6c0fd57c488ba66b50d012682ade7d8e0acac15e63ddb4bc95a398eb2939ff0661e1a852cd0f651772a209867b81b135e401026d9d37384e7ae0be154f52bcdf22eb315ffcff1f1b722bab3ce43b5b10cb70a23b3261a61c8683257573dc09b2414e3c23f4b08ac572e3abe11d582d2f0dac7c592208bb52803b24dd2e54c6d6f7dba0830c2bb85672ba2c86ba58adbac19cf3c365d70762e962206b27b2a869bafc9e61fe4dbb057277b72d23d3986cdde50bfcb0ab3d08b9bb374d8fb54c4c61d42089ce70de3172129880005d238a7e32c66f17fdf766694d5f3731fea440647186fde3b9ef6c720f28ec069e520fc8c4a01ba234ddebc8058ad052f14a1b635076ec70bbfe5e6b4051805be843f6d95dedfabe79ff06deede258f38850186094bd74265b6a2272a531cbcaaab924819a6851bd9d43f76292bf4b830fa366dc34c6de78822688345acceccc24433ad110226937b7bd354a1789f53aa7e844e3fa2ffb975e258a1afea241d68b35e9c04be4d8925208176a78866da05854276bfbe9a16a0e508b720ec39cfe028de2ee350127799b22aae261de5b39f99dd5dd138be1d75d419f72e10df31d2bc4bba8ab1bc7e03fa216a58733780185addd80d4b708e99487092d85ec51011c0036891f03c1f777edd36b17f0fdb76ba073c1ecf14dad64c4a9728dd41e8baaba99c2eee25e3b9947454df3f55107b9de194fee8ff412129b3e72d35eff57a723afb837a5b0b6643aa03774ced8b024fe6593ac99e5dcd5d64c19681337f56fdf3a270a735ef741e5c90d283527b500cb8c479c041908ef45c7378a09ed3415b34e71c6afb2a5b9e982aa0ff9030a7f1ce9ad7a8e67ca751bcc72e12d84e6a55ea7a75f0f7896ace6f93f75f4f4d168b1b97561e7bec39b679672d630d1310dfcbb79131a4068080d0dc3b2e73c462bbf3443715a07f4969ef60a9ac6e81afa4feb7ddbf1aa0d7d3d7c1af291e88b08fe6e6e6a3d37a91c1f097265bdfe5c0fe5e8e9180a5cc3a3fa3699057d73444894fcc2b76fa58c23b02672ea975b9226878102139352e0b1c1c185cc1f9218d8500422f5d178e36341a772e56a49ce94b76864470a0c4c364a264c6c7216a54c62cfa6add4899ff17f9a70cd9a7e2a79460fcab9f19817bd44261d62d9458d0637254d24f1e70b153c0b725c7ee2ba0613b202235453b8b6b1b1ddfb0d9550bf7e977f91304e8c99093926de568cf28abc1a77ac491bd17c8b9f5892bedb95bde186d6f7e9fbaad0a89a727af91565742a6ba5bc20573431b1cbbca17db701bdbc1f6ff238d79fc4a5125edc579540c83709c4e1686e971c1607eda5667f9e6ff72a4d74ee39a8116b20277b8f7b91ecf85ac423dfa18d0c0bc564ac2340b61055ee8ec9f7945db56f6f72aac1c46d06d9261a0ccc98a459f8b1e05d0101d263b82555c1e315d15f9e864c64ee9ba328aa56f9eef30408574a3079992c7a68afce6e5deb0362d4e18ec372df4210379c1f0e3679627a21d6333eadf18d8b933f3091b0f3f08547de5351727de5e1229dcb456481750a4876d6c6024d2e7a776a43c9bc6bef12c8d7bd173abafe2d5886d7925c719e2bea19fd59285cb91872b66451791173f8aea02123515f7eb5e28ca0dc6c96baf8019dc596330202c029290c77079ec1a105406b9172f34e3e96e08658669878654583837ee571921a50ff4f4ad85928305927d68572ab96ed063398e1cdbc0e66d0ee965abd8417fd8fc074cc01be8c060798d44d724969c3cdb42050a7ee00a6e9b03a64acdf37c25d65222b19090dfc6967fab6397aa8441118f46615ff705e0ca21b2596c4a4f97dd8e859992530b3cbd3efab72ec39d1beceb45fa73e47d3df77e216948b5d171c6c16d7cbb060cae49138df5582d8ef469c8e38bc5ccd08fd44fa020be9bc8519c80d857be4e8a8a550faad721d6bfffe08a08e823ea254ddaf182ce231e7863d85e7e5f3813bdb04e0358b72aa94cb9e198814609a036d721f66087c8f7b9d521e32764d022fff804031d072bd32aa564f4f1d8cd4f6f94ddaac8691eadba2222bc0debb8cb696fa90ac5165a403ba7397d0da999049e1152aaa311e276ffff674ee8ed0e3f5a4b1b9ae53140dfb0007281bdc822464aa02e2f0f7d83b583a6422ce4f56e3a28f048201e5726da10942f913b83289e44b3ef6f7c647db3415db73cee6a6a8d4377424735f724f097eaa4cf6197529b2e0a9bb2389fcfa26268a24efb0d7e3038122a8fd7a72371bba6ab5a1d3132e59f5f64aa52bafc40ee1720794e217279bcbf5c6a3863fc282e5aeea9dc8948f0636ab1cfb5fcbddcfa1448eec75b3a065a3787a9a463ea03946da0a7da59333e51f6296d073ad2e4a3059ae01c42d412bf3a0c2a86e5241e4d0014b327a8f89d901f23b549306287f431f433cd37d1c23aaa5f5040972017bd7e98285481b1ab6a513fae0d824f177e9b05fed25f6075f05333febd203532380de572c24bd8b2af7de6004425ad0faef45744b34e56abc90c017429872037a183fd13fc196f0543b0d8be7534a2e0512c4ba49705f02eb9d8c0dead672b04942d8a5ce77c7f785e8001466ec3b85b6f1854cf4fd17de8a41bcbb575472f17adaa2f2d9f2c6682b7b24ffaaad9e4aff021e7be2ee7f8689a9c1085ce0727f032e00fee529e50efe57c17189f1f3adf038b6d25fe225f72a62f64da6be72fad7594d2c9f4271cde08b18a2c1cf41dc6ccc377f5b7a4feed739d59081520e5394fc0303b7d0ddc794b5b3449d84bdfe52f62c9a68adfd92dca6c339c71c724b72625d5c6bdd58e8bb39b83c1fa890a1957310f9942f72360bd11b6501c73c334f5140008f953eaed979475a5e01dbe13d2ee70458990795fe83fdb89f65308016e8d3781ec77139200295e5c15fadb78e7456bd3e9dc1cc83c489de6d4372ba7dff64ec5e904c7dec3857995891b0adc8e1944ad6b9da624b55f056ecf1728c0fd8431cba15582b4603ccb61275e9f74498a5fdcaa4a920a24367c43d845fbbed8a32c6ab050ae0aed13c10d47de86ab8925ff88a1d17097b6fed88db98495127fbcea49ebaa9b79ce3c84114236327e283fb6a5b94cf7cb8a79b2d29b272da39bc0f9675e41573611553fc818afa555a62e22178b25e37941173918b3a724aeb098a5078cdd2394dbc3c9b6ab6a1ddab23e56c85b6e748def8e3514e3472f7186458896dc187c8d27e0d52ce58d2decf022c09341350fa3330bd25dd525c2566caa98ce552a50170a474eca5cb3a7e7150d7a2f78bb249665b5f10513455a286dd27eff003db2bcbc2669f82e3fa09b39c0feafc25a752182504cddf6272becf9c7963f16d850d175af6cd2bd6928fd3d336c308f4748667959685bde2729a722ca2af4befbb0edd6bd876029543d24b37821b71e08f5d23ac2ce7958b7276ef33fe54aff2e5cfd7eebc2f8ba29d71d9fa72f9f820aa240a811171ebd972307fc7b69ad9e86a07977c208ba1757849acd44bcafd98f339a6fdeac83c4872ee6ac970e1db2eacb4ef8c08704a897763c905970a16b9a293ba304d95eedf7219d6b1827ee670535cc555a6092764fa7b49482899e37ba6564576477edba4720695529e443d5e0dc66d5f88198808025568cf630bed392190da5e19f9ec7f723854d0c497e9d6b077c54ead0c0231c372358daa5954c19e7ac5e316eb882572907fa9e02070b289901062cd7ba0e49c78d63c43a076283e238c157c5a2010722d59da95db8b7d174ca3ad6ce32fd89f77170cfcffd0a83022211ee81b4e6f7232c2c491833af37a111c9db846cdb8467c7c79023d48bb0a892ce8f9b2a8bb28ce944de90a4de3061334756ac3e35006750bd04cff215372fe484df1e66a1b72ab62fa4a2187c1d4f8cdf818064af99f035198514e4d1108d70c35c1c1c3184a44440d0cc5dfdbc85937e0cdc4266863c2283e54eae20e1dc4bf5810855a91724112b3580c6e259a9a0dd65d96c621ff5c3dcbcb7788d1cf5f898303120c71720dccc8169b342a5bf6bd32c0c043e6c6185c48aadaaebb0102921cb802b89c72b1818099004d18d9568376451a20c362292b9878fe8abd8a8d1d1647da007d4405d53bcdb97b4ece3f6333dfd574e36232a86214bf2372d8f1b3403edbaf941e2b3d6048cc9f89cfce68d8439738edb985134ce20a1cd1488aadb14cbb43bb722fcb73382a6fcf989fd8d8a66173b9b37584dfa0a712e21166399d3e77a70d5cef47917e8c79bd57bb9d10c0a5d0839628a6f159b5101d0a74d2cc2c9ee615720c3774700e2b9d404633aabdfa938cf3b10c1c19a42eab75278a3bfb14c02c30e4d976e6f813794fcd89acadb11452c686a173b8f43f7a51f7c7f5b1b84176720e656492d26a6a2828a7a7c24bc64042bb84a2aaf6aa5d3a3045075f5a85927254f3ac7ffe698ceb028d3708120e69297e09eabc736fac77ffc33e0119ccc0168021104513f20f8facec68b6cea22e3abe8a72299763b0223bfc5c19c1239872233b8fcbeb07dfc55db7f2b61b666f47ea6b420507722618b9bd9eaf2e5cdd44b15f940436f413437af30907d188c41f8e6c84d452157d0be67de925ccaea872b777a3a30b03faa264122f4a17925132b83714f25061db5452ba7e6d2c334872753971d5fd74a708ea0e0347c8d8befbf6df2715c30c8a1745648a0505916e72e4320ddcd63205e2e71d38aa2b7926056543dccc917d0439e2d9b422b17dd85f353e48f175bba5b378872008be9aa9775c4b7dc39b9d8f1b8f9eda83ffc96f72d8453286116f671ca4e1c8625e770692eb8fc6be16a8f0c45c2bcf4ed7335b72218fe2a581a71cc4cf6f18b7875ef0547da47e16fcb314e7ce8201f00b0e862476bc0b613a8921686220ec03be3d8e023a8ce3eafbe94a885afdfe077db221726bc5111308da406d22fb0c47add085ccd5e5583ec6e6747f2aad3a42d6452044e19fccbe7a8aad3a92d7ff4e1550556831c7fce15f46a2fc705df7a07a81300edbde5648ce43ab099daa2a541121264370862c49c6b7dbd3c43a30848c2bbf50eeb1e479520f4b745e90f4cc1a51bf45d7130fd5a8f54f3abfc320181b74c072dedb7cbce8cd75e72437f84d15d9895f2f567608815f30298818413463c3507296bc8664f00764a9e68dbedccc3373ae8decfec8de1f173d24ca2684d4db3872645fc3fc905c1ba51a2952bd26394123fa9f613d3d69bc1bc9cffd198648db129b1416e241aa643f862bad652314423382b901bbae4e6af80bf5175ead820b70516a078967bf08e7ad52d58b9c9a76eca4a0a53a4bc52f342a1b4ec942035f484c378245f307fcde7e3afd6b5a7fb49941a11bb477b9f49183cf190579c88672398b3f812834bb81d0e1b3ba4cb5f737ca0aeeea7bdee03a4a2815db8e5056331bed2e0fa90c90f1060a579fb68aac5df3daf4bcc3dc212dedf87623c797b972a322a6511e38272447424a8ccbbaa73dda37f542b4dbfb44ff850fefc23551728a3dfc66a10e2392b7ae2775f29314c301aca9a54ff9e460781c9c5251472716174bffe2a68ddcafd4615f33d74328fbe9ec8034d2ea489f095611a0da395372316e3e302b7430a8f54e58639b02651fde42ab7bdfff93c3ad9db6a98ef0f917ae13ef8b6e25ba1b1232eac8abe69cca1a19ef2217b2d6ff3a91186c26b6b86104ee71bd827e0f1cfecc9e9c47dcbaf1cd1046579b0719e609d5e5fbac541e3cac7661f61194645e50e3bf9845ec5973b69f61cf8c91fb26045fc44a6f97fc065b0006429870e5729beb285da5adc6f12ba1e12b95a8d7a6893dd8eafb60a335c484b12e25e8988215c814a27f76c577f09e81251c7df5bfb969f9643be0f572dd13e5c75ce5e29eefc1693e8bb7b92622c22471f9ba6190c337ffa60e6e065085dbba518782dd2867e0e1a213a58152d065639dad8e8654c39118dfed77ed3b3d0ca27ac9dcbb26835c079784fcd3ce1c20b6d931eca24786d24c365085e2725456f8bbe9f9db2db47fadde381b977a4a42e2e69f0e40a4b9ae6d4d559ba772399465601362669cdb500dc1e6ed59c442d5066f988856094dd86956c4f34268bef062dca09fb14854d4d402c4d0b36b7ea836013d7954f8dcd60ae46e3b4911193ac7e7381bebad5c0ca5968d9f1e6244434420ab78ca5c5d144e6d98ac970f8b9520681efc2cdc905eaa9fea5ab86e4f74098170e552043744de3af1bdfe72cc3b4e8f03af3fe8b02974bece27ed81f50104d8750fd78f550f20a20b3c8972029688dd29a15cef93ce86a133ad02529cb6e325cd4583c3d8a07ca65cf93172716ae10eb141cdc09a0601ce58ebcb74456afb76be85b1f516113a08d7966a721b45acf6ea866acef9a96767d5e98920923c615060354cbbc023b16363036a3718b9342e03b3cbb2ff101587257ee7e48ee0f27a6eb42a0c66d441efea74ca27f7e4ecf0a8a458169b51fc6c64ad30ac656ffddbf11c7bbc31286b0bf9af7e2a6d33efa137d2ca2bd9e21c0a7fe01b68e3d850a86809ba2472bea73daa9f5f203d8a24b9313c985aa551ec520dcd7692e76fb4d6776e51f909933873caa01c63717b840e9a7d34ca634e54f9c78da892d755492f8f306aa739fc7f8e62261b7276d7eb79a9c2f8c885fea39237fd2e9d74ab07de100a22c1cff153d7bd776a7299768d5cd810b373c68c44433ba80ec182669187d61404abf9ebb1524ba47d72409066c6f8a34b25eb56554f996a311144654abd33fab4908f972b9170330872b9d5c1af229989d25f1beed6c9ed5352729e4b0dd171953962b9ffffc437f105b80af2d2333d11cc34fe2839b779fc57b12418dfa84eb2a6a7fefe1b8eaefb7286152fd45da3c391e1c23c3906e9c07fc1025d0ba99feb0a33c02c6702b91172d43b86e5b8c1403491bd64eabd4e97b055b9cb82a40b93608da3ee6a4bc7567201e194776511e3a8840164f11e1abe5f21d532a89a86670d5f933ad12d607172a67f9b0499405671052d87498d93510e0d4dee0ea76f024fa721cd7971358f720c729a9bad2907196b43d63fe58cfc500cc6a6f19d9504b6be0e2b1b47139b72f56c7142a9b669ae99c3566d0a517dbe633867abcf568243b507fa2d568585723930b506c938ff4891f7b4f150ee164cc60e71babdabdf1e24a9a781941dce72d6078e5c6094e0371a45094f4c198325be20239828781f8229c3529e4237807292b866372c3c5e779db8542a6ce7fb30db2ac4a39fddcad73b3040e122d8c1725dcbe439565a35c6912169a99e1c6cfb46074d337f9345560094703febc8a270b481c9e030542f6f99127391253c9656dd9dfd2cf115659835bf242b3c7296142bbb86c348075be353511cf8696455482e3516e91ea292ebe804e9ee0509fe72d3752ae34c2213857f291e83c0d5fd7428be93660b10b28440c937da81b1de1fed36c0b0ed112d8c9313f8dc31368f106cda3683e31c800643a82617b4772072285190095d54bc316bf6451e78047076dadf35a7d6cdd6c36755c3de706a105eeec6c5382ac3b87fccf8cbc78a6a28dd65a107c1d869319fcabe6be049aea62921b6f5d548ffaeae34f873d16820f3ef595a859743b524f5371052b91c6237720472e740ecab9f4de3536a89f6587256204ef3777bee76eb817529be5ce07a70f0495bbf2b48f2c697959172eed370b0afaa126b47dde82849933664a9674b518d60eaf742c68f012d055d06267491a80bfa1aa3d7aed5ee37f293fef060867279b260dd2820eceae07ebbe269606a4718d93c5c76b47d7596af978786b4ac27d69f27c5c8c6d9549717f089cbbb6900b945b452ccc6424a19586b4bde261872901087e3f5756159ca212732ee1008758f73b37b627a5aac5fc2fed8330eec729b2f0f84b16e4b64667afbec8469cd929a766abfd78abc9028b3c5cb7140e1720349db9f5a945e501bbc6e7909e002185cfbb27ae8fda5ab334c5cc8e2975972d17476c2ad1f460bc393984ee5bb8cfcb18238eb69afb18a3b292dd182616d2a3fa356da161b87d271c64c0cf6bb605f664922c320ab47f72fc2f45c7035010ecb91a4d08eec99c3cf8e19c5822e60d97560e0a914f7424155dce8f52d90836c553fdb35e372b4cdc754789832013b2b62c6a3089acb729cd4e821bc83ce4072fb6f9079a85a275f09405018f68074ed638361477fbc34ac5f724fade5a64d72ddbc95ee67e15af24b862fb2d47b09c0ada2de6dabf14219b74e8fe7f38a74431b1b22a3f5d391fe5f22b4c262aa19f82a74e60562f275d805b0ade0b6cb13723f599a0ef4a78cf7cb443e4cc27a092974d4fe836ff1d19cd04d9293f3e58d72f8d38c69ed15e529fa2c45087b9b7181bd32011875ea4b54838ad698dadf9372e18e7916fbbf1a08f7b002a69897d775b5de62477a5f645e6bb53f6a7afae472f52e0f8adcdfff3b37eec0973fd46b3ed28654a1e7a8e4e232df15bc9d30ed72e535795303af02b186d942da646c8525ce4a3e7b0055ee2c52986c73ac17fd72781916c91390509b1c6c935aaa7b7a8ffb50ef4c00e3a47ff1e16c066b65fc4acaff588ddcdc326415d2e838069dc874512b83899579315d8349795d05313a722a4f56df91615e4d0f3256f17d1b2a64ed57d14e1bf518e63894c4e6c8fd8272102cad3ae4f86a950b122efac1008d205b04f2331f5b02190c8c673bbcd41972bdfa68f82a804a5e625f086d4265fb2db81e629e47d154d888236e5af6402872849fe94266a1dbf8a8a2d402ac916987a8853632eacd6e74a2ddcc3e1474ee2bd63cd265f58b709382c0972d6d526431161be0a8c29ee70cc54c15c7f79f4b72d59b7d9266bb500fc01a7f93be0f22bc94f4dd4d07708ee3b480446b7ceee818fa1cd04cd90675c13bbf2b92108d2a29c48de49655390e330cd8d3058650861694cd4f2f11435127688fafaaa76444ca20f889664e8c3ce86123b581f73d0772399c870559fbee0d90e8d5aa90933e60eb3485de094f889a66e585e8a630287223ae06818a91ca8e89d4ef44073a207efd17fc9ad3f31c45f4a1076da57bf340c0eedb2bb437fada69bc25fd5d71f948766dd8415493d0f520ef5fd9b57e6e7209ec07c76c6bea9bc82cad140d9ed631067a57e0ef4e597f4ea5fde4b4827372c4562d2f2db571e6f902e62f5d6748dde2a42354b7c6390c483c9a11e55a04550eb41682ed36bc47fcc9bd2524cfc17dde9113f5014f2445c808f54c2dcaf5723ae1838bfba63203b89e2e33321c786b391be8fe942ada924156f00540fbe972ecfd6fe8cd93a81bd1dae42b947d20171f3c28bf8d1123be1cbcfd65f7f97072e00d0c8ec78cff891094847193380bb8c143603ece266a97213ea5c6bdb827727e4b3d848520b5116f4ffc621d2ae865a2656c71e00648a5370c2321678c8c72e8b4e3e485e8c064c6f93becb23e62f861c6c6cd71c59c54c35198bb458327625acda00c88d02ccd7dd23747c558bc2e3bc35d1c7198b315924735e3b7002d723503a9e91cfe6a9d8fda1467213e3e20bd65b3f64b9d5661c3dcc03fb701487244ce80dcae87110a77b7c875403d403e28fe10a33fbae1c5c8f58028954eb120287a250b9f27829ab41d0417e0110c4f9a1c1c9f92638e1cb2ffe943474b3a722d12fda8a5209f9c0839c42c9d56b9c4f4858e5dd879f8af747393ba313cc9723b9699e5bca8dae8ae7e93912a63b2aba74bc5fe2c61cd284b223a760d78977219b8ff23e21e6edc5b08dd375c3d45f483dfb6f4b2b20d803207b0ad0dd273729daf133530a30485d687efbe8b167b2b5641a586caca627512e31133b97281720430e3e50b07e6896f2129890f3ecfe11266351e81f4b12a2f1639864cdf976a876da60c92f13fe5ef5015cb71e21a6f2b60af3701346bff1133bac6bfab40040116e12d4a25908830e90eb0aef06632a396fca8204dd1ba9b8c44f4e405c47209b3a6140f1744e011b69794d34e1cc0f316d84447da8b323695d6a03ef20f7281549692b4896013e8c95f1b0c02a0e92dbc984e1a6229c23378ee74245cce01264d03a23cba537be85d847a15e536c80856dd3dddd5326e01a720e51a8c47729f1f803fe345d0b51370eeb7464ca993fe118a6e0e0216899dae560511ccab0a5bcd8e9d85ed6f7425618f93cb1388606c61da5f4d76659239e5713c18333272e18b57ba89a4406a9cb5baef1e100635a4d7b17199e3b9aeb37792bca0276e5240ac0fbc00361503c1532d57cea77d27524a1f373127c5c5ebf5e11cca08e572b2ef09894be9c65bac50e2c92437f6662d6b997a5a753abe0b4d226d39011533438a04ece5bd383d173d0666f3c507ee8c2e1cc5a5a87dc628613ac10e5cff09f49fbc056a63024466530317a2cc642b6e53be28dd1ed5053011b9bf78a7b172eb37a4a9fd6090048cf2b3379cf490f6211daef843f4fcb4f0e47a17dd48a9720484af364706d080133df7e002f87ba933b8b2d978342ae2f8d9191c5940ff726675666ed90e55b38480a0dca8e64c9a2d084b2dd74777357c65a047587a18342a55c56da3ae5ad1ed86ed3e2f4694938217e879de770aeafb3c3dc96d7b6a72c6b87ecfbaaf3d89aa2adb9fdacd964edbce018e948cd5179bd916fad9de5032456cbea96f1c2c9a57cc4980d143e69ced450e2e9376c395a6f49784730cec0220af09f2f6c55297981ce69555c5bb8b12623bed05c32aa0882f0a11c31dc872e7cdf5aba825fe0243e0b9b2fbdff2d4d0bf0760829191be4dc42df42b5380723494a383b0c09102cdc07096476684bad2ab6d546663baa7acf133853669446737a869060afad61080e67e97576cd69a5d2e38718e826d744f43a82bc9e7657237009940614f4fc4f778e19f9fcda6802f22bdcc5a7cd5c4c3fbe30b675ead704a97ec40caaac9a150661b39aae418c6a4ed0bf6da5943e8a2e1b9b78c8c7d2e85b746ed93846a796726b9a7d7a6c9605f45b58628f437cab42c106d880f4a3ae314d2246dff4948d684eb31d897af38bc032848a23166c836855eaef302884a2be591adf127ecf0157d9f9f6c0bab9f99b686d30fb0d0c235d0135504918c1691c5de17b96b1ef6f98d317c86db7606ba8c28a10fa4b32c285043ce997074048e2b495f554efb41df266796eeee4ba7ba940c2ee167f0e6782229c81b5161728bc202afb0092bcc0d4b8ae4041a4a4b2d46dd0f1ba050dce6c391a3c78a41524e69b342fd7970113a17d679dd0d0dae4894f86698d0f4f97a3e56058b0c45729ff0b9e33250494928d695d7320ef6ce45210d1cd36ad91b53e04cd77bbd947291afe1219f5902c25ef0a3dec565ac890a7282cfa8498a3263811b1d9f571472a21c830a5537bd7e0c29d4c4bc2da53e01791ad2ac7bd1b1772e6483c7d92639dba0904342446f7812cad316375f50fad04976bb4db5fbd23412b6bef9169b480e3070047b353b7710edbc638f15d2fd86d2b905fc8ebb52b19b4cd3e67a5472339ec9f89b643a7046c01507ab41daa0becc2c83955f81e6cd0d0c3791478a72052ebfaa8f5d84d8f3dd15a0389e7fdb9a03cea1fb777a1f52122c9eebd9e072b811e2526bb942fa22db9aec24669899980565754bf62d16f0b774efd879a55a2b075acf02cceccefd8718dc970ab42585b9ac5cf5c3cf5454057fb4e6ec9372ade2fcdb6f6e10c6f58f88c482d0067a7ff6abb3930a023ff34695e560a6a87273e6d18c94a141cb1493a56c2374aad7fd428d376567a4cf78d86c14a7dd000cc0604a43304361fa26e94effb1c9f247aeca2bbe1e05f98f86c8174a7830b946e457fa49268183eb3082d3b6a5820de2db9633648722218380043c86feb2f41e8ae7f570c1223e7035f4f4c1676cc300d442e4ed3a84cf9718223445f0dcf60139041981f6c073174c42984ff62d39cbf7560b84b81242256ca0bbacf77d3c7286882531703378986199003c74fbe08e7303916f3632ff745e827f763c8f7472fb16fec6d682b651bc9274e8ed947f1834e2a93b8e7d04ba27356e3db669695e0da7fd86768f5089900620e661d4ef6b870d43977831c6442e75e515e70c977251454f4f0bffe55630dda5b687408a8267a7660dbb2215c548b741fe685c372ce6c7b9058930f67a9df745d5cb6aeac325bee3dc591899e7f2e5aad1b51a83503f8ecfbf27526cf52e28bd7d670da8e16354a6c7d8dcc1eb94009c8f2d5be672d4790268ce43fe22aa8f1ca30e1c2704e1530cf98a4abcbf6180ab8dc03d342bb700c5ad0372335245490bfc25bcb9f4e94963f2a7ec7bf674180efb6c113372860f425552dc1fea7a26a8e0553446ade82e9a4869181f3b24eef1279acfe46db084504fafe926e71bc1db8abddee5091a1e4cbbe3509da74f964325a3bf1e729508be47a9d941b7061e7f295ff9dfaa677d0ce2ebb23c1a927f49773289d672515392464e3b6df7ccdcc8cd51f88680c38d28ce491e62f2187e59ce04575307f948a9bc195f03dc6c3fb516f2ebea4a4de0c91e139406427ce0153304fd4d03c6ae88f1bf82c91d9c8d9f584ccdf27bc4528870c368dcf5d960ffca9475f9720e0ab9b43e2aaa2efa27dfa7992bfaf9689d2d0af10c0b66db615178a2e20b29225d27ea356aa1d6837d902ad74dc288df4a5845142c901929b35708364f1d72e77356131ec266cab715c5794c4590fa7afea7c06234c2838b4d080cc3aa2a407355ca11a50cea41e78d9706d8b16423f33654e6af4746bba54fe5b9f3a283723421c2939194b5a98517768aa2f8f6d1228c7953988027d5ad1922799fca1372868cbef986b02155f081745bbf04ca9e50adac5d6b6569d3e6f33d9160c87072237c1e4f3d0485611b798cce2687a673e8998131ca07b21fce56daa6f785ae727a3d6c4baa0fe9fba606a3055209b5f5dec3ae9b764577d83ee58e73635f1c723f125d4a0ab3f51d118782952fc6a481cc59301afc061f91d1021e1e24b0fb3a008dda96d7287092d5a93ae72cfe71d9292709bb93e2f37c415aee6dabe46c1f95a3980e5364ede7b5a88d2afb4bd41a42d77ec00c31336008a343ab42cf8b460d8ba833b41fa0095cfee79ea97a22e60a682b60490e4b5af95e13b1fe3c3c722ca664aea4c53db6d423924e95ff820dde9da63afb1c86752bc9b271d7cfa972b2e69dd458217ae1e79414a6c5814af74648f0b794ae71c37ac5381c89760c139868bd4f8ae8799b29b754b880f39e3d0f08ebaece3cb31a91ba8450b0a0bd30f088cc9779ce8c8e3985800e657a83a023c017169e1c0461a1741f4ec3da74642b9414c6279c9cf7cd55abceaba465b4aff5a1ea0d7c1b0c149f78122e875c0dd37cafc4192f46335a6b2967ff0a54980fbe7e8e8b7e8cf16a3139aaf571ee56bee3e646b54e2615af0b54745abdc2be75909e8d37897f3027cb2ea058fd2772edab6fc84e8b920023326812a87b69cbf0a934da132c4aa9e6388de24ba19372ae1245a03a457eca4a2c3f05e50a932b0279ba9992343a5d8a51698aecf1111b81fd59f23b52baf61cdc04a15c5f24fc08b38545520f3cd74193cb2be0854572c768d4c4f1ac435b5844ab7254f7837ccdb93324ba650204ad4e088238ea68577291251588256daa39ec0864df35f695e44a9780da4cb13266e267244d06f2721d6cf5fcaed52ce049d3509c9b0ed4aa45b636a7ac22b2a8d3bdf9c7626d4a56d4aad09f006976ec5d34a11a7e1cb7802c68d471f5ff7587bdf1ce920e3c85661b415d9c1717925277309d1f0dce8b4d16336d225687079bd79f4832b4115537d810dd1da439cc6d858419fb2727388400f535a6d545da6290f7763c231ca17201fc195e7dc47dc1bffdf04f1ff82bd8203c89bba8b0f78672f1ea4320e42b72f0960c91c6c6cbccfc2fd3c7b4bb381eda31104cd01e60d75e59e8acc521037207ed49c9f86901c54e39fc568cfe71cbd70efd1e1a32dbffd29f61a68e3c8c72665cbc7b0d9740b951835d236b05803fa1b29b2aec4fb832370ccf450b855772d2490b08d7483deae6a3eb73464a178db8688cec2ce75495cf02c1cbefca640facf92c2418d2376824b7458a007e5738c43cb0140b36c4c7ced951a8e4f1b713c33151b69d077b639a780bbe74ceccdf509693ae0d34e6d50d69edfc32128a7286b966b9d02c391bb90f0d9fc85f648c33c4afd0ae70cbce530e6593141c1872d085610a0cfbd2038b4e0b6d58cb6c2e8967b03646af5bb2e0277f757a2c7072a01548e657f01e4ece7d8b81309fdc98e973c6c2cbc2d563e9eae4dd50e2d872d863d45543f72c6f75c3d784966f208379ee18fca0beaa119280225df79dc25e981d52770aeccd54ba2fe463b63fd3ef7516efe3a10eaa7a50dbc246c892bb72be83c6254e7479afb0b74fcb1f1539d881193609fff3f78f2cb3733cc593fe72876598e4bd7f8c71104ec6b35585d4f86fceee158f224f50638ebaef0f4b567294668ebdf96c7b540a24addd6ed1548a34f9afc8c7eeec5b7bc647f83330a37217b2ce77c94c360a812c8e02e8ad6130676e35e01b3c5ff24ac7c8a72191bb5c59ce5a694529faeb03416ce51a1294e18312eece33abbd747daa0673a5382151fced7a36b7870fe8a94cc6bf38623ec732d12b07613bd1bdbe52a289eb44e072cb5882c90c741669fd5fdc280473b9dc6645be0e1075299498a284e9ff572872c2613df7e43ec4b03e16ce673399f88a7636bef6047a02f0f2db32e1cbf8a07232bfcc5fc026947c35d3cba9d91c2cdaea77600e69ee311cbefff693e746d77206d7c392f9558e456abb56599eaf67b80ae612958b4486a1688d3e6e9dcf5d0309dfa24d3fca05dce802b6290f6d114e4d0f713b5f1cc784726e7047c802ef0628ce62e9024ceadf9f2143fce38a50a8d89bda91f3179cc918053a65b118d019636969474f47ff61662ffe8a09f4d4d9a7cb58e2d80a268b4bd323cb521024721583939ea2e88c825b255f2c11059fda85521567b50ed69fdeb7b90dbf1faa721936b73c7ad762e40893ad368dffdf15681e3da430214551ba2bc055e17cba722bdb53f5f36321c83e67582cd2236e3a1f9ca774c472999ce1223f101e8de572543dfb57492207a4498061fac246cebed79d5048392eb5f217d33b46c445ed5d9b9bb08d2d1bd24ba62c0c687d9b4ba85189196a5057106155694b04a8572556744396d9ab6dca03dc7e877bce4e44f3989a3d8188a2f5605addbceef8530272b866e354f58927c0f6741d32ca95e2b4fa6819d98444ceb9d30768d5dd99d472880c9ab06e0ee553616150f7991f0868788023483947d130cfdac3bf0085fd1fe4498d3690fc680033c7e46621308019bffca1029d418f1410fb8eb12460a572a13af7016960b38b8419e81f8868ba674fc6d195405ed7aceabb03cb56b2877271d9816e48ad20915be007a31a52bb7c162b7ea80e75023036b81abd2a122c4205a7d92f62fa75931d88bb3c0d1b9aca9280a7c483764c542a4dff5bd1552b124db9666e3b3be8074de22df58dc7d9908cf6c4cd1f69ca1d998d1ab565f01a72ebc12b45324f97e44a292ce3506706ef647c96810a4c5244eb0352925ff89c72804288e8a70a961061f0054822136301e2fcf1f33db40e014d6743a6acb6bf3d10e6a27447795e1ade3e7d0e93e3392a6523855926951ce17557ea1cc7713772d0a43364732e755668be00f19ffd03a627f60c90c07669bb54f70329ae7d51607f702f3b09fb405bab5214843fa0a96350c46c5ab5bce83efefe3ea89a51e54c7876a6518f3b6feb30748ad4eccbab5d9a15f5de0d264e8a66a098a12303a31a18b1cc3c42778abe7191add11c16012261489d7e9c52f31c11fadc12a58ee9720857492d701a7723eee8552ee8aa1e9ef964224a5621f708297db99202505d151fffe1eb87323a76fa2cf3f73e2833f357c4256482719c80b4245e1ff51c2872c98f7464eddb3cb10d93dc3e0bd3c9cd0f2ad368a293d5e0af8e632a0488f101142c8ccf211f9346675b33a3e4ab6fb9923b424e0014c6ae913b13adcfde647214f2acfac3c0d5d180d7501005648d98220f141739fee7eb89ca8c71b017f472f80e140725be2bcde97c4e0e6fca07b3a666f48aeb2ba7e3cd3532355c8d330ee76af8d8f31afdc353dfc3dd8b2aa85b494fd74753ffb8d9f4f14feca21c217241552bd300972f8359926b0b07af5481e136a86f71d0e1a128b70262be2de41c96c2dea3bdd42c01bbe0eacf0adefd7a7147fe3b8f84c098cc38e67e50f4f5720bbfa3f53466554e4a15647e97bf96e94bf92fa05fe9481f421b9d753aed48728969e0cee9e9b39eea4f2c2151a3eae3128b7edaf98baf5480af8c67a9e9a0187a00714ea9f10fbef12a712421df640e16b713623ebdfedc97dfff77f0e2de69a5c7c2ffd5428715142f4b9cdcbe329c42ef5ac00b3d04abd7ccdef47352f3418d99570bb33e36be994efbd1065d862fde54157107f0cbeb59a426c3050a2972308b05abdad36013970d3f94d0a30b53bb2c5c20e78d2d4324eef80587abbc13e248db121a2f81a65de33ab73dc4b350a8d38e1ff85b790120d7aa8cbdce1b728392340bfc9c08428d51fa251f7625d18444ae115caef61bae254f110f822772eb2f8fbe5c3ef944453fdfda8a513c0391f1b01172606f5d268fa277c905dc72713ae854d38679ffa219d37908fad2f9f29d53fd3b546fe3a7a41567aebc7f0b262413e2eb7850807003feb1db9f5a38b2b174a934c65bdfa8014e1f644bf872d8b6482dc93f1d56053044a3c81076d5dffb1a56470efb8311027a4f1cf50f2848b0ddaaf917b135be393bbaa199fb8490e2710e2bba1cbb7a6751b80b1aae7210f336c4837493a13c577cfafe2b18f3f0f83e58f49a42e9e9e11c468d2f6b722b6e572c0be6a13850c40a131ac28ddda422094ac0342c6a921396ee92988f72a6772de6725d11894f64f248a6044db4b4bb8bde7da19672da6ae29b830bb3710006821b2e4039e97ea8491d3080fab7a05abf88116c6974be385a2e342389720c666a5c47ad0d3ee63a60f99c18216b560c093ada32088c8e15f88d0e9660723072e79a6dc34cf522080d4a2c2057d19c6b644c63e4847afee8e5fd7d4a394821290f6cdd0800d76d9341db21234815c1c4ce55f8eab2fefade0defdb31db07c87d0f5a7cdac611752fc51a86f1e8b691c0ef290bcd73731e25a377dd56e6727e1735e800d0024b912cd23d611767aac229b39687be3e1ee4a4bfdfb765d372f163a1baecf4f46b17485507639b2e710a697cc216458636f6997de0e9a09f72dddedfb3b67723a8e1249d984b6c5ffe7ca90b03f3ef9fa6ab65d0b1b77aa872acc9566e4e73058e4cea46fdc47bc74b4d4ff5e8317903577dbc3dfe357d714ffee577e025c8ec4518be42784c3ab69b8836abc8375d377ee737a0cd2bd810724848ce06a95dcd7a7bd943c7a34c4af61c6d370c08cf90c517d1f465fa347850de89cb925cb87e45b54c4bf44f305b270112765085b26b157f55bdbf2c0edd6cb1bb8c05352404e08ab649513da78809f41375697e15cbb203f29c30fcc6b55ca7e797563cc68c37d3826a3f2a1eccdac10f73cab2d92b6991f48e2c88a7627238dd9303d7b19d1df4ed08c7efc2091d34f1132e6bff60344e6a1bd7f9c279234d70b789c13e36ca84f32704df93071099925d0a767167c60f63b62b5937b67286c5f1f0f79b9d674fc03d1b5e628f7bc218067a54591327fac304ef91a70c2185d6a5b5a09d2506aa41eaffeb91318420dbe03e13b323f9852f8164ee19ee1823e8fd8009163e2adda0dd3d55b030f2c3787036938cd56544a1d8ddb0d9e74c9c8de31e894bfeddc419d76974c5234c293d6074ff8a95868690bc6553ee08728a0e040bcf9945cb93540a9c60b88662cf0dd759bc7651fa1e1cb82fa9c6ae180ddd82c0c5207a00c6abed5a95727ee66c4cc6c55534df80e2e17f43e2cc070a6e3919e19c2311a57d621a6af1ac212dcec54d6b13454f7787b97a43df3d8833e576189976b83473b9e32d5e3a7af55590fef52a137dc50084a32259807c040fafe1d45c2a02269bdf56643604dfae24838ae8e6d36bb593935ecbf9c2ab1272c7e892541ba7bdbdc237fc99c296eeacc920833728001f5d6dc8f1eb1c3e12727abdcc8e7eeff52e5323f00bcbba84dd06ba472d0e56c6f7674b29375bbbcb716d9f94dadba04a841c752eeb3a81499eb55c769bd62358ccced2720294d45a721a4536fb01e42446c679a71a40d2813e21ff9dba4219031e5d6972c62d90bf5104bb107d51d07db1f7bdcadad16238d7bf1c74402b48b4e44c738c25faa834540b1d2c28126259cd2e4d216c0ce8f44db4e75d9b02be23cea8c777a4724e574dd2d6eb2cd7e92a1061c9345aa407ad6483354898c41d036a0da55e9be381187213b3e574ba6b9368d0269a6b1bb22ead5d4a8509b1a0dee691b9c38a24516e20319a2d012ecb8fb4cf080ffb0f77230355ded8736bc683e6ba9465660535de7279806a1e5a0cd2628e5ec5a44e7f4bed8b9964d818e4e9ad8a12fa7c7ff6ca5dce3fa9f35413022db44ef0e544d60f4e3a92b518986f9e477c580b90e55e566a97f6c6842a7997bde1481734ae96284ab9f6ad4bd6af1b13008f577bf3ad9b72a5c2f2ac6c4648db83f762f8d2e56c9dcebf43fb41a4a8ce68459853124ea372610aefbd577cf947938d5771d2a6965c30a0665f0c6ba1f1217f31466d10027244c8661058e078c11bfb3c0f905e6b5f4814efc101ad58813e235de0b1694472b22845077de8cf9627126ad0b3ccd0c076a1107cd8ba8911e182a175d7e7f07289f74120f2d2cdbd112bc27ffaa831fa3b244f184fefb12c10bd3a7172e52402730ca8343c7e8d62ebd4648fcbe4ea9ed27d4df29db06f6a63614055b36c5e7206e6f815e4241922158819842e8c4533df2e794ec987cbb832ebba7570256808656c72570cb6252bb8908f6e1c089c72980868d483782bd3314836f63141f41bcf161fb49701e7b854e1d00e255c848dcce3954282cb3cbf5d29892352df0172cb5f866e3c20f24e77633deacea858809879448cbffba8fcee017d7664d23e472ebeed9f36033df02996ef0a48cdcf3fbfe460413634e4c6afdd0d16b70515724035a8c73d855508fd891cce0efed4c379b039b4aa895401e4005b57347fdc727947730e3a1796664750f12645cc6d130c830971752073b76747556d52aa452dd515b0f2458c1a97a2ba56507a6581b82093ad6a7fe70693800ae3af0091385ba2533682402cdc4c5852db9fa5d9451ac16afefd136c504e1628af49b66bb1722a6890586c96d21fb9b6e9097c12564d22d596c5bbeb34836b8f372f8ab57d434a35a67b2e6af91120bc5ff0daed99fb3f7254405880745ee7146cac1ee8137201634681579740e9affd37d3b6e14995729414cc7b369e3a178546126608277276a3a75139ef9a670c44c65d480e22f69564bf5a42ca02ecd1478901270ba2596d418c9f9bc7ede2178fba813347564eeef8c13f4db0d2e83b049692603a92037a4207ddbc9ca906055d50b513db9cc7af2abb1028c6ce6dd046a34b099fff72a0d50fb1c2d9e29904437098ef096961613bff210612122ba341ebad73c24c723ffbe2665c8c4845dfe2fe7051ac865a563a8d194dcf273065bfc38681777d72d44480d169936f1add34e3b08cc8f6e3b0b3e0e70c20de40a620326728d60311410c141c96697a40d76b998a89c585cbe9b704240c07565c589203fe3afbd10771bc3f38925ad64ce781c01dbb09c03e2cfb46640cac63986a4e688fce7a7572dd08a3d715e15d02ea530826fa991a7eadaea49351ef0eb542192a6d17113d3dfd14c73e25568aad4155f2373e24b35c3c36b6980f0af50a8c1ad414bde03c407397a3a6e12a6f1da94490178d87cf313140aa051ba128672899cfd655af2a7205e3e5e0a4609d84d7c98e57db139e2d45b7b7d70bf244d2855b6dab3e507f332611b5170cb73ae61f1436e6446d41fb385d83b22b6bfc5b7bd6a89a1fe2d672ea7d3137d56c6545a75ec5aadcb650405064835f5322e6f3b5d647b44444f86bf82b09259c66a79a9c400a74ecbf9212001684d1d23311d1e1787d7f2f333572ecb7058e7de66b9f67fd866fa14ef428246f1a66a9d8e44e7dd508fd99cc967248b40faa1a3b1cb9e41d120e47f1f00d5321924206258b1f21fb50f5ded198727cd4229e0dcfaa6c3c92fdb4570fc92377589954f34586541e165620e4b43f3d75ffa76dde22077f8b8ce9e9d920b4f1fdef29a4984ce9a532901c5630bdd2729500b2afc25dca6966debd8299acd0563225ac7c3a8120d01b753ce2d14705419f2567500d560915beb5abb8c110f7050f9bda630900f70a8e88a3b8685355721280b1cec3ecaa076b3a56a5e3149b4801c63e76d9c8bd4a15ff24a9e48a654702030e2297ce0165bc8b957bad4f5a1338d69e7cf27f0bd90e51c635a7f27b5d829bbe1dd83da541253b77ca02c38c99d9dce66ec7ac2ac0056dbd734b1dbc72fd2f1ec757dece50c27637bc6f7d773938615a0e5ca243933a74989eaf6f1b6e4b9753c4581cd462097a2884ef01f72289d32c631174a95fe629e09464a9610e6aa744e77e99bffc63fe3cb322a1d8094f6c56199088ac705bf60b999278fa6112469d3172722f380d1a86f3690d5f85b16e8ed3df75fc1050072e72b1bbe072102127c8fe76f989577e53bfbbd592ba02e766b4204cf9b262772be8d615395289a21f40fe67a79186023e1b5909a55660aee404ed52f7059679eceb9e05cd725e8cb2df3b2bc47a7d798eff8e261ada756106ec884732920b74c0b222d87510f7effc8f58a0e5ec928867e0959ca012c45e08bc5a8b0b320dd3d2c8af71dc72bbae304edd699c7b827809c31fa20e5e4c6fc5fa423bc1c665e64f771e1b9972d6b668aa16d4e5804749cf5633631eba2fad80ccb2cab3775065f29eb754dc60f519cfb9e0da2355ec778b4c5fe535afce7aa53a3d0c472858e3b25b30d22072ec5f90962ce73532fb9df308c35c57baa02e8dbb1900500d3384f879ec36ee723940f8c592ad55eb749c6db272ff6e765a644a67cb510ed7a48f061cb3eefd5687e695a31e78bb25d08c72db8b04ade7616c0e4ef2a629e2f748d4a92823ad723ef1e6c413b5c206b9a1f1dcbe7b50ff6463f103b8c993f7dfecb2aa3a86fc724533df5e6714912c284875ab4fccd85dcccd0095c8fae39657c7198ebbe23c2b646cc9411c3d2475e2b7858f1199ea8b3b7d7468b4dc68519371c4dc8f1e0f72a35a6c419dfd4e2f2b8b30bb6b90a0ff9d9bb3bf987f3a8d8fd55c8bf65267726ed10a87639195bf1ff6b7ba09d2e288a91740132cf1e250f74d56748847a772868805b00a80c03f23ecc21ea066b980796ee93b6d096dac840426e085220427ce839dec463f436b1af2d89049afad5cf14d42650a05b8dc2344cd043dc59c0a2ab4205113f98fa09896101aa248273675060adbc55ecb52ef9f18fb3128ae25cb8a12f97bcb2dfbdff8472b20e3a289115d6c9c5764a696f87b64b3b8283772890ab4d9f812f72a8df515c33d3135063d4e35e9f0a7289853047178f981ca2be8709edd7c92f95243f800c6fb9b9762fa29dbca95b25880b049372a1eef54720b33bfd72ab71d12b77638e756773c725906efa0fcd44a98d536c15a06c28972c6e5326d3ef3cf98ac42bdd96cb8d35a9e60201cc58d30d98a49f2ee18b9227223e00c734da6a728ef96de36d01072e9a244098d9cbfe224c54fa7d278e7e172451ecf4b50b232ccc8eee40efc3b389d829f7d49dc53b7011970c24e37ac9c7243da9ba9d4b7f769ed2a98f447bf0225a7037551104f47bbad017430be0fbb28b4bbde2610e7453f09219626ba627137b8a209c4893d3b5f90e41ed37dfdcd72d884ebf859999b6c46f28b4650fd8f45632ebc10ffcd62d2be411bcd3bab12726684b40f305c259dcec82610ef310394a2e210e6041041192efab1d5cab59e198d16215c9725adb62c9f54404fbadb966fafb46dad4c3232c1787032a3d7137287480706ed5940b2fb0fe01f664b57237c7a5b12079e0ecbdcc77766886c9d00cc8adf8c7c49130678ae16563bb8185cdf21be7f32634cafa0136cc1fcd82b7216614f943c7e66caa0f4b351222493c8e34e416599d70059e5f3e66d1bfc722d2435431512565d44023069b54889edbbbed7f15c6f6ffee5b0e487afd4cf7a724fa166493d59d065ab37f53d6c28ce10e02cc598eb1b7e4b985560ae790a0e72fa8d2f07466b802415538bee2b74f1b88ca00f3b1a910da23836790c3399387250f2c2310e638deeb5b5b6916d3500774fab39563c5639cb40848b01795c6b72a0c7e783f19747b801e157eb2ad684692adc54889789c14687e0c3dbf0b29c72d09b556ee03be24cefad2dcad301a65ed0e59e8b3a369b1766c7c197e8c6fc295db4e8fc28ade2db8d1a3e99173b6a7c000ddfbb3b275e325d7203d55f09657215916f92d9473acbfe082a838edf8a0749fe6993fb1e806339d485036ba8456f2472351049384bd9edc84160aa3ca931f954481e0309106fb0f3db5c161773725d6f404b2bc54c2739963c41b617d6fd4e0c8be9bec3ecca94701a94b8f28c72263480d951123286da906680fa08c245c7e11ac36e89bdb78381ef7067af3772c0bf3168444eb8eef6d6b9e408b48076d27708a0d8d0a15a990d7b5b9659f1209d1976c2844207df2d06d9350d2f6df143953d824afd222ff3175596656cb53eeb45f5a47189776662e3dce8d6be223e686620449594c939044c1c82d4647972e824af6a1b9ced16ff1c2ffd4ad6e7c06b6ddd159ffec685a1d821914bb6d40f9642f8a135564b4baaf73af4998c9d6521bf454bdce0945fc827a6074a54f27271de59f787b8fab3d172b67ede05d9b5411ab4127019de8874966224ca4d00727989412d420e732ef388750240f5d2e7734bd01465129bea084bd5700f680f721d14152316a55c87b0e20aa28b5544959996c37f0daf6de477bdda528928e5596036a1e8dd0b642a582e54f6c5a102b753556c9c0598ee4f36777fd4edaad672dff4d6ca6ff06c590a8df36418708c1e456de79e6cfc1d8324e5d60dac887d1f545dbb6881e3b6ef80e8d718bdfd29fae2dea41460e4b6e5cfb95e8dfb9e8572e176f4401b4ab00b7866128e5c7b4b8dcfca19ef0630e1ebdd249cb27b325f679031a1eb21d4e0efdc57193e2b462fceb95badf523b99e0abdee1c051d449e0875d8da054648cd1fc34cb89b25c1b99667a1b80f35a45ce385f62a9db2f68a72fcd6a4e0e6314ee212e9211c793151462fd6fd9fb942d3aec43d0820557c1f2ee3ff346b4b6cde7b02a7cb4639fd4bb13377cdbe3097b3b96f90d3c12f50ce44ee429039da48e71aa1df6fe66a816e7c740f7025043941651c7951cab98baf721568fa2c742f1902a6ee1b52a46313fe4decd88c88d78a64960aeb88d97d4072ae1f194b30cc971c5058615b6cd3e5c64e0330e870a11701590b6a7714b0e672da31159b2dadc1cfbe753d7a1899582b8215678d053e36d513b6a937bb29421e223048bbeab4fc29cf53cbb181f842a089b29a63a12df0e72917f1dfea6c357227a730ffba5fcbdc751482d072b209d0c13f6fb0c5e99b67277ee3f66bce2472f0995d0a8d114a8ac13662bcc9eeae8a9cadabd2fb213c1ad347bef76f54307258d643592329dcc567308a458b499c730b1e69fc6c6ed07d48812e1e44e7c8353ca6fe2882c8a08e32238e512bbf060f9e79dde5f4785adcedb1bf34dce453724c821c8ec1dfdd078f7ca196003e6a551212c280990d0beb6eacb63ee8d50502058c8a9bc53626f5e49b8293721eddb08ed19aea1ed3f0f8f4a93b6f25b1d30de443952a1d65c691e75e6cb622f05f747d26e20b4a27641b2d3a72a39e96f52fb2d1ddd8608e13d4033f2c83da961f5452ad08468a9c3a4afa003dc445553507ed04da77e3fa8ff341ca1a6e713d2da3962e400b49ba9a744b67c8e3c3659e7202222ec58370ae78948881752c53139c5fdcf21f1470a7a208524f2d18a9de384371f9629b1e45af0c983834047e3599620eed524118ddcf914ab956a817f672f7fee452fe6245560552c9f113df6a3d83418a8005bec2de496ed046728d7672876a4ce865c0c273d660e8588fbac1b92dcbd7891792ffd2efb064b915c0d7723b8fa277d00bfca9d0f99ee9ac54d2ca76698d0733e217ff08027e8210772c7272b6cb3205877b88852a4e440af9ad587d0625979c464de718a40a0076aa1b7249cb5d88b7eecd79e56c448a04300ee674dcf3b0724e1aa539419ea566ecb4105b450adcd603c95c2d8ac5222143c0defe357cd8ba86fa9aa7e2f72bb449363843e2f26c7cc5d2a434d0eb1eefe4aadcfde26007ddcd8520d29dc5626bd86b48f031eaed8c74e87c3d473d451d75ca135f9cc732bed55557645d8753341b35727a358e55fa252bb48469fa6a44e065954d60b085f4c369b67fc38406407d5572e06340277dc464fe91786b475b636fed8e1a9f8fe28428d09685fad7ba5a8272b60ead34c362491c119c88b8d9434fc201dd73e54ed1898b457e9553d2ba7672c7ffc2393569c0be72c903ccf7750d1f0af957308daaa5c30de6db0ca33d5c0c241a36c474e877e3f6ef504f758cca42095c4936878ff1d34455034c2ea8390b4ff45c9a96ef1dd48871f1f060837ffdf1f9405a60c72c8cd2c85a3cbbe2ae72d1e1152b106603de0749a8a2e78f6b9fe4e630cb28464bec9c8a61481a41a4729634f565c0be57adadb0aa49e86d137f078e3a02e1228d3c32d6308ec893a17293a0f9fbab21f43691729abd4b24637524174efe09d90cfc32653a28969b3902ace4ef6f7122c7b553136a91bef416667d60ab5a159fe0acc72fdf905ece7a44594ecfb9b75514a824b3e58d417d903cc0314c258ca5a0242049e10075a603729863bfd15e63e7fcde28d2480d1bb44009585e3a6ae0e596bbd57f71278d2e3a2550316434bfc32590cd0d6788fe5ec2238a10c245b64a53111f3fbe0b79e7726e18112fe2847a34dc1c59101c6f5d926946f99dd75a6b188d747b7a577c8b72127d3097b7300bd486067971859f5535b51512807f96dc23a02276cfb08a874c1354d08f3679a8311626ab924fdcd870e62b34ed42fc5e18030ae887a85a32726d7dc032e94a9cee8b8539f35442a3213870c4fd19a955891caf0cba3b6b8f72a06d186ff30f050360fc5bba192edbc1dc0d0f2f1f98abc6694e03a853b76372ea4b73d296707bc39ab2dab6a21a793541cea9d75832114768dcd8ea57cdbd62cdbb76db555a5f37dcf34dc1d9a6c5e80ccbf860c3cc2e133fef341c264acb72161f84a11fa5d01d5c9a695a78f404982b8347b656bc32f3b902490235f4e27242897ed6f37a1d8bdcf01bd15b1576c47affa646b3271088240d00cdbc7f2f5a5376656b5016c9b264fac7cf1328a6f6b62d47655c9405bd40b29650fd8912729a4cb8c95a6b6cc8e7f129e5ea8935a0fd89a3b8fc6a107a4da1e570c1a50009963fd64f5eef989bf3b0ef5004d11126a5a5cfe264b87c94b6a8b4f9139cc572cdfbe2cc5b241979e4cdb5d712e4164edc2477b450b2926f1119cd3fb64964095501ca71b686beba326d48c4b644fae0a01fded67f6f352f7665a3cad058637294145400b2568ff849ff999c16f86c31ec7b63082c09c6b4b9438c6d397f5d5c30e9c33a708063404e263b9b484a1a0f4f223ebf8634beaf361c2852cb4db7724328afe00cef5a3699f9cd7795f8b92a40c2622d9e2b1d7f3eeb0535759a84720542a9a52b434e9c53ba9d6bb989a78378f854f6aa4e788eecda9b141aa590140f5adca772051ec66ff8741aa7b0c635c7387a55d43caf296bd7b56dc3fbfd72b89f1b802d2953f13974cede5ca0daa044537077120e1971d77eae404c85bf72f6b40356370cfa57b894132b9f76c201cf155a78cba5f488da3dfa6ed62b4b7226592a9d1dd668aeacd071d157a63d359702076dd01051fee3953a3f5ebb8c723b56e71f5689707a934bcce3fefe3c700fb633677fb06e14def3ebc4b08cf972e60dd8da2bf0f3205c4d331e18b8e7f3e7b1ebefbd94dae9b0f8293998bdd146abb717b2f7292dbcbd87379d9c6a2e73c4b7e59d934e7e6137594949f4b07a65deaf553b5993a4934a54e50f759c881203c1bf492d9de9eb8f0eafb00403e446f8e6b39a5513acee7b9241ae69a327e398ee4389b1f0e04292fef5fdd69dc572b680aa855fefdcc2d96f10c3d8558b08c74134bccfec767e42dce5bfef66f7729874312f1ae6cef504eb014476bcbdab97f98135200823f071b2f7e0f84da172c6622af4f8339b8658c90fe727f7d8cfdb7d469cd5371fb071abec2c8b092b72ac4d01afcfcdbc6e17c171aff1f8858a408e6aa7659c960b8bc91dd5c587c37000b0700cbfb0aa5942b21b706fcbee38a8921de4adacb4355a8c08aed1b02b685d84a005b60b909d90268d6848f39cb362bddb0bf6c3f66c1417837ea9f2bb7214ff5972cf3edffcc5e1d045ad65f5c5cdf2821159302cff8509c9817eca2e4e2cef37d0b19096e5aae35fe2a94d0ee992af1d707786d81b1df683015afe9c3b0b951ebabb040a1b95a65db3d79273e9fd1b5245067443f445e2621323241928f69cfa84bf441deef569c5e7ea4a64df3ba09068c5edd2268cb773501999b01405b45d7345f5bea1aa35c387cbb42779e00ea0ee898233c800a6462c99585c5a2c0e09c275466bbd72fc528099b3fb7e4f26a0ce1377bdb037bd4a896eb2a872f420542f296a0484913edbc381db6de90a8b8562dfa56323db01abfa122a72179c27ffbbfa3a74a8da1a9149037fcb874398e4783c16d849c00cd14d11e70c6fc01856cffa8a69ab21a9ad7a6e79138ca5d268c644035c0440183726a63be7729a1ec1d06eb7f9b98755fc0c23fff829c40e0f7876bbdf56f154bbeca9fd803cdc4f0f6ed518beec3e74b21d009c56d5d435486108a9e1061f90692142dad4465378bb01ad184300b57af1d9881dc24d9b057f5b0bbaf323690efcae66baee72153b6ca91bb1f1d432dff56a7706dc6dda2745ea4d0f91e9908c684272bce9721a0580db10482680b772941f5874d72f9d9058c0b76a598e18f3beec982b4972a1fdd525cc9086e3338e62b17a902ebc3a508166c9cdb114ac5d7a746fe3916504852044b4496df6004f2f48e76db4b6c80343af1ada6626c896dd2f9ffd6b6bc244f87b762355df31739f9e90be4dded2f8cbd6d640f5bfcdab42fd21f8640e0021022c3546ce8c183123c6fc0e1230a4c7f633dc54384f1301418658f756724bdd12cee54d9b2ca128b2811a9fb6c932656cb8ab3e6dd64ee2d5be63ee8a72f1ad37be9621c7411765bacfe5b9b52da03dcca805a667c5c688cd6d6fa0d2722b91af7a65ea89fc2163b64e6e4b8f435378f3b7411c6739cca5344b52dc8372807188c313eb4bd406c2ca425d31fbdc76b351e52282f0b73c13826f6308623b944b0fd86ba52980b3cb6f7fca72e3ffbdefaef60429cf1eda67a7f6aed3406291e4d4de2fccbaa312e8cc38d9f31a66d527b58948f83e95a57c75aac12a6e72faf1c13773b6bb442a9f5f6f8679c2744c11315b54a9cb9e5c889eb2f0b2e37281f861bea052194e3f7868d885e46967d18b4ba5caaceb73e160214546629d7244ab807f254a18038d3416e065ca1e8aad3e7f93af394830d4004203e9084b2792cfcf6b93f6bc0a24946ada8fb6a7cf8315aa428cbc76e08c57cfad45b090533237174532365f1d0ba906b7d0e9a0b5f8a2751d356731527b28da998787f332805113382430be934b09d9486c9d80d88574a35026dc04511b95d75a33dad3720610d5e0d3cba4a1be1058981b52fbe524bebbe98e7ee5f62a4cf306db3be13aa2eb24eadadec1cfce338294577514db18e9147c304c0b75a49cf09019a7df72a04bd317980abae48d258aef2d1bf2eb5be40a51a166d64ed65517f0fc4e5a4596f7f45a47d80dca635ec3ce2474bd052d4ea488197a9d196edfdea81b473b72324aa519120108b0f2babb1d82a8c32859a4370f7408f69f5001acc7d36e5204b1bc7ae7478a8beac714c16fca3b168eee5b25285254a88e4252850edde3b67227cbc557b75bb04b548828c3615815355df789eeb833e2a38f929990a2802e02cc103fe227331cc8e766a9d9584ac932e960d8a2c1845cec5e0d7fe49ea84772cfeda8dd6be3f8db42446a94ad140f5886c6d526ecac3057b5569303d7a95c5724f4d948be01fbd5e12330814833f9d53be8a85d67f459ef6f05807c4801c172b752664fb21bda18c8c1d32eaeda169308b34b8604e60c37260852c0128ae4724ead3b73bc7c08fef61c3104780a3629a3c4f7c0645dcf0103a9519cc40b4272bc371e852dff4f15a9e8ed03ffa54a4bc2bd3edb958ef216c029b43852c4d53f6fd2c4ec3fcf7be7e0570081c17927be71c441337ca61a4fe9d262f5ea31fd72d5b23311716d0d30c6be8ae694e22337eec6f403e0929551ffd3626c8d9649133eb96bcd63330147d4a82443432d8eb0df589d4e36ef370dd98c0fe13a7b5c235c9125feb1923c4c43fbcdcebf44f5ae7572aad83ba4668116000476fb54e472f8adbb971ac9b18304a99277b31cb1a1f95f4b85899caf69c604a9f0a62353722bce5da449ff46f0438c8ba8d96bbeaf714c3829c1c2b945cfa8a937017ad7390976d1fabd8df331fa3621af1762805200db57f102b0ab372903854ef4c0a372b8fdbd9644b032231213e8369fcb7fb6c41c1054b32ffcffb7e58c4f0330da00fd89911f644863364ee20adc90ed26ec1416b4cca20791150bcfdb34cdf6487222e9bf7d5223754ee90a696ed57256c2925124af6ebf0566f6cf67cd35073d3cca0c947a075dd67816107b310698bb25fc962fe3979bd59db647220861244a36b42e7efcad2b961cb2ae0e1af6794a30cfaaf59451218f40076c8440660b8f7207d938514760c3136bbc390e8a0c9564069b8cc23be23371ae55c153f17eb270481f68e6215117195b398556bfdcdd58115d2d88ee8abaee88f3271333b9d664ba176f48ae849eab8fec858f3b7ff7abb5d66c3f5f55d6bcd2540a5a7c242272442ea0c4ee703515d16c47832404ec694d17f3825e50d67711d1ccb8f95a1372cbed87c39d5cf4e7771f4e7502a5a22a8bf8a0649a00fc73c99a4f780fc65c7226c38cd187b730eef2f77a8e706b1c95ed426e53cad6466af20f9c9ec875a6296d368ada92a8e546579507fd77201054b480e2c2d117e7933f2de4f2f8576872d31a47979b94be5383cc938d2f5095b3d5c1cda645dffce53161ebdd7d231026c1593bc2d5c77be122a3948acadb283e60b936801f8c14b1fe32c38f09ca88723ff0d66b77072912fe5ebe050b8bb972da9928c2addd39939f5f214c3ad977726c6577457b405c1684aa16e3a0653829dbe3816c816772c176d365dc7defec72ab94427fddb0f7a1a801b801a40b15bfe014c73b96e7ad5ca5bcd5e13c2d8072acca508e421d0662d4d2d147e6269a713ba1f7611ebfcc91b18484a760cc84694a7a71f1228786a90ec935d69058ec4dace51e7028325f0dce602a7f3eebbf72b4a1068cd15b44d60096da2068adf2270c9f9b172bd7105c768891398036866685dcb2eded3962694064ce162d9e25bbcbe7aba1241ed1b1d774c585faaea87208eb90620a4000225045aaeba0a692ca9755cc58d1cbac18a67f464150cd2a727516a94517ac66b5ab8550e2d05f3e8824f2d14a07b1c3ead827e5837159307204a50d11e700893267df2bb8c4e20f6f4fd531671657c2c940e1c1aa7bcb980211f094abd00db01cdc7a6c7e0c7d4bb9b4800c97d9b4f94a4eca8a14769ee83806131c3d8ba052be30639e670d5a3093821d58c012860096633d666fe90e54722c22a35750d2442e04b1f3f0c84fee62c7e0eaa6d8865e5c7f1504b0f6a2b218aa7d5664909d0aa99d83a1b5fe17ed76757bf608d4dab145733e4aa2bc98d35d226a61f34b5a0228f58366920528457d442d94f429a671097bbf11948c9e317268bff27101350629e636771ff26f35f75ab8aafe5b29e7aa1d20a6853ddd081d5428d3f84687d1911795fe5858cdef1d3c692f0ba3aa6adda385b1b2c57aa83fe67fd29bbfa8d8f7388b248428a8f61728849758709c43554d487a6dfac38a7234c92a7cdb5d4327acbcfe18e6008b30a156ad64d273f7af1bd2dc6f64148672978404cf90a79b4e0b6e2fe4ff04b9d0ae6d85350f535f2aac36905289cd4f72c9dfcdd5cb760bea3582fd9a22549d15949f58ded34cc5b633d0316c1869ce33c159243ae5edb92a14c8a26fec15a4550c47f66ac7365764682ee9090245c072bc68f08eca8bbc9303fa093e4e0b12d3c9ebcd390411539d36aaf06ad99d7b726ef1113e5a9d65d703cabad7fde7d8b8bb6434211cf017bc883ac368951633726e92ce5f76db991a1095ba4a3a56b7f08dfbeb47d28d71be4c67343b623d2772a3351717e4dad346d7755232ef01e6bdcb4fde8fe58e18449ae403b2ec257a4818043d963fdcceb0fe03bc698bc3a0cb38f2cdd5805ea52139b83aed11acd75a82f9cd55d66e1c15da369643bcf660276ba1d130c57d06e536a5b8d394a1ae722d924239609ba29018ece45287470442c6e3a151e7d5392fbd9a7afdcd858972dccf78e88a43555c9ece0042966ce6d765c7e8e34db5d07ce8e5dc7e9f24a61a92fe623208751ac5e7dd7b27eb555dfc3115e35cfe667582d0f053f5d968c6728a3bccee842fc52e2e67455ae425c54da8376c42f1c804a9bbcf68dac918117214a28ecb3c53d2a2f413cc9ce9cbdcfaf258e3b725d1a5afbd352e79e830e372109dc8d85d5601b4d9189672a952718251b5c3cca790107a8ef9e8b507c19b72af4250a4ba5cc59f1aa6c55ed19cfd32c9d7b5a14d1f5e914df17e950df41372755a639c248ea3dae83e32933f91efb1d42fa0a2a099f9b8973040b97b6bad7243bdea0c5bda5a2d40af33c13aff9fb1eb777772d161ba337ba8413a6651cb72133310a096ca5365a58cb4c2e8166c2387e652679238bc182349bd95372fef1c5413183542af3d758d671fe8d6a863a4cb858f8419f7e3e1bd458f9b1ee6dd6a3538e2afbdc2dc337fdb07ded9a108c37608d02716e4d7061ddba2ae8ec64c359798e0516286768d2c86cf651f144fa14fe87ae7354b7ab3acaec8fa93d88372be5c5a96fc958df7dc7c5d4399a4d1cdb459c1ae415466ba6917f61f629af76ba80f47d6c7d742d785d5499078e363c8e2f58b97e82728c003b997bb994683720bfca243060ae93cb57c5f6b2ca0768dc3ec1bb684fad3c7580a862c85883472606dfd0669e51cd98325c030b3e830fe307c10a9a0c352cc841db41f0b6e77670a0e0291a254d351cfc685130465b0f0431c9087274395bda2a844570a929f6f597d3d0a28a6b67752defe764fb3caab5a3d15155fe146d309e9578a8be1314bf5d59fb0e633565cff0330f7c2094e9f9b248f20cc359090ce9ed76a80204541d559e07c458eacc4169cc38bd975632462342595aeefb6d48f01e9519d9db9662a2853e75e9ce6f3ba862cdb3a92798909f2047ef39af25fd488abd273eb9e724cb1f5249029fa2bd7c39584b7511efa09c631e012cb17064e4290e15fc6a75d992bca4852cf7985a3acc29d67f08149b548c07eb212b929722c3ed14519aa72cce7c754c0e8d203f0e52aa3b9f8ae2cdce13075cbe2b21391003c233d2bbf09e993f12e71378a997870c575e276999afaa62a71e576b6574b21ab9275b35672a4c2bba0fe555b05d977db8a384f2f59fb978c3eb5300871ccf530ffda4c701bfcf18ca2217f9a6e4b5859bf90fe9ffab1db05b15a3610ff4fd9c7c2260ede0e82436ad923b217f2db1ad83c0f8b90d42687e504a05ab49f32ea26de7bb33e724d15b56287057d35af809573ea8dca8baea1977e904f95ab64b5dd52ab433d72d581dc04c2a4a9be24cc56b4357a28bcd5160220e9a292c173967e4466324c723aafbb24364e0dcbabd9386c0547955063d2ddc7e7d654575c454e9adfcfe45b7758419268127aa1256d065abd01015ffe20aa376846fcca4dda33173e4c6872dfb9a7dbac577b299919f69dbe2ba630a65273ca1f4ea7017636d87c16d1fb294e052834f330c9f36b1f7d01e993438793ef36f612e319aed35fd450f8666672695ca7c72c4ceaf2d70a299da608f428d95226899afc29064c66225104951d642fa53f10a0a9878eeb480a1f9457f60dee05837e7a0f385d5ec1c447598a430527f510aad60eba81937c75b023024bedb67fe26fe560f5ea61b9309328a6d672482cb57c1a8dd51bdc54a128a018247af71c3efdac855133fcc652e548d5f8107f15374f90a027922c926591920f95b601a7c67a7a1688199f8545e261ba1e720a19b3feb452c24afedbf762d37c19cd9091a5ef9e2f4c3452ca8a6074ad0e72badcb1931911dc6889ec9f27c1986c2db5131a017c29c74089c0f9b72d1f011c5298575dfe7ee05cd127685791bc104df048e3f1c3888a62acfb4a09f063981db5c42c3caec23fa2602d3695655803198390c20ca8a4961fddba049769716e075f059d68c74575d8a3d53eca0d21c905049bb0477486114be04c228b67958572a280921cb9425fba96c88c04f177a1935908c057c5a0db8997735cd82ea81901b404061015a58d15a7018cd5cc5675f1fe4a55d2375eeb67f6afe48a124d2f3691495d28671d266035dfaeaf3f7e7617639ecd2ed04458633c426aaae3d30272a51d5da8ec2fd33198394396c513fb2b6f7cd82516b9ca7737288d50d91af872c71f18c3fdf4a34a8220c882c87f9b6822bfad0bfe9809a688bac156cb95f044d2d3397ecc7973cfe1487a7ff9e00b4c8caa077efceb0ffc29057b5f3e5cc072e1730d1c76c297ec40de664b59734d99d41a72bf988ea577454a09331f4b2149d25f8e0be8738489a048779f9db4dd5017fb3a683ee6da1bba995606f6d590727203760f36b20b193d9e6cb0a96df510b0d9b782b4e66cac0060829d9ec3cc72e74a5a376b9b5bfcc4d70801a6577eb1bb8e3ae04dc5487aa8aa0b275aec9b60ba562cedce6774e7fbbf0c18949cf882ba48c0f0e49df77450e5d3735a8b4572fae3588d21b946df1d0c80128bd1992f2edd2843c9f08c919c9716ae6b8d5872e639b9abda34284ca6da9da9048f32ee7c447b647916b0b3f7b14b5bf71e07724fa989fb6c3e50dd43ba4293acf3df5e10226b40cb2c6f12a4a52cd32888944fb02ef742b4da914a5fbda5808fb0277fafac57cbe8b373233d6d921a2749e56a87be163b30d2021d78ed797497d6011bd0688222a4bdb79b7a801fa9249bd472c7b0fa2bc797d6e78174be8dd0949ec2d318256e58fc3ffe634a2465b88cf62a08637ada03002e7ab6ce8af4aab965686c7b8f7c568662d1599534a6bb453b72834966b3c59997263f4faff9c9907b057cf9892464551e5c566d4da29f4f197271bd41fc9fd539d55bcba929825927123130a8ba8082a281fcfaee69c7c22a1f85b611052bc4a8eeb04dfc0e1094392a1e0cde886a78a94deadddbc966ebc0720c24ea362d1d29afbe5331407e972a08270ca04e6af6f2300721f347597b21119112a07c61b5808fbab33feaf4bf3b9f3ad5a297c3e2fd6d8ab5ffe4c0b6c372aa541711a8ec29269ccfc2e15dd955892abb17b860ca452ad95369bb6ca20f2f92b110849a0873ada9e92309d530d70b6da64b7e4f5012b06ab9d8c42f41707236a9cb306ee5d0ce78094e206e33798237d6b0f382275f845b1e416477badd72ed8b081f1542a08901c8bc4847b73bce673c43547a6710b321e9dd7ab228a472f09863229e2d81f348930155feb0de26b94237b519780dfd4f1eb61a28e9337241423a328068f8802b95a1421dddd24b8e909996c315cc1f05aa6310876221378081b124fb40371e0bce237ee798a17de082243e12ed22f9ad8f7817db9a6b3286a3aaadc4c3c4fe77aacb79040e48fb0b0b1bb718f4535822777758d812cd470d239b2e127eda7af69c85edee92a205416c0a99e6a4692cf7375057a1a31507716e402c957876951d7c40985d2056d9f0a1818ad3c254dc18c858c6644f287255429a5ae930c02c5c76b509d7cfb13421b8f6be6d4246f74efcf56c344ea27243fd3ad4bf410eb615cd7dc9f983f7d43780e4616ed61069b0d5c4619183d227e0488f721a6f834ece6060bc964a427012775582d2363d72a71431d95e098f7292c8debaf84b1415ad3e74c7b3210d9e0d69419f642eabbb84814d78a185c41faf96786424eea1d8cbed3dd616a2d7dca35b199d3ce70f852ad5267f69b65e72234dc9660d1a697e445fff675e466c40eae38d6af1cc0457326493accd4cd372a894be8704331e907f09566e726a162248b6684215edb4c5698afd2b0048aa64869e9ef376bec44ef9a80c21d4ec7eb610b96856462d6fb6fa18d7b59f674a0fc6a8bb23e83ce8ae57a342c0f67bf75c7e681b2b99bedebbd41d55a1fc27fc449f7ab5369815d817b589994f9d7153611fb94a2db8bb7650f17e5e81f083527294d6e4ade55f5deb31cb3e91a6a847a53c3f7926d4a1aa0f7b59e0c5300d64197398e019a1f22931b89e11b44f80a00c6d09a334c1c92d7e851fcc18fc5d5f72b88908a0b0603079669af63705f83c3164296548a1155e995f81359bdd369f72d98d960908c9668c9d170a3baa26704412bf7b1288f1bc47178d461bc94c4d72bfa52eb720bd0c7bbd3e1e2af4ab81e55015fbd54c8f23e25b8fc5851b76741b93dbc625a87c95170258bd8f41d596f5323068f543251e618ce1aa6342581972552a256a6fc1f15f10b1072fe391fdd4349152ddcfab99bf0a792007b6709a720084f5a0ca498fcfdad16437e323f2611b5e0cdc6b6fb4ac12caffee9b43c07233df8be7616daba04ccab175c2d628295529a386c191a7e67ff2ceab4e75c172b5928f4175810bb0afc3e4e89412d65253be6228aeded1f62e3030190d2d3272bd833ecbb0a513d6e1543daacc0a9e58849ced9f7eabee5519f2c9a2a3a41107378f2ccfb7aae8483e6b399c534ced1b2d19217c2def0088c8ac77ed59635672c64b465c22bfa4afa8a53139792668b30f0c93fe051f4a0c899ade797b94f372d5b243834299584d6ff2daf15d1553ba49c4f42582b0bb7aed93c350816b7023cd3e4066a51f2d36c2da2ad963cde9f2b4de5b9aa32489dbe77798f1c054e9727c841895cd35040aaac73cacb95afe9689b857efa45f1c6ed3ee5135bf065a7289edea50f47a155f50baf4dd2b10fd52a231d1398c4d12a6349a28cfe342b072d7cd51b3e7e35f8ed1bf176e0fc5671f7899f9c01a87a55c6d41449844d309720fa3f0f8781f6dbb151c09a0274f9432bf6f1232a5dbfc39255e899e32ef65721e1d0d72d6097b6d5960732c8908f332d6719d48a361b0ae4261f8c080448d72059870e07bf334c1e725689589ddefee0b9465d91b204ff34ac9fca647547b727d4feef97d37febfde52e602643438fb0b8554f21055ca19c7fc9f68c17b04728219c5a7b2c3569b2f2a8830c0c1016153a9e1e03444d623636e68968f48352556a41b78ffdb6747a9b5abd274ae0a2c4ed7046a92ef57d7a00fba90251cea726f1bf9f8e550b32d92164363af0fbf03579df77d45e9c1679dc57c48f01ae7724ee67a6bc88ad3eee1579dcc5fbfe9b09557c15b57039cd273eaa389465d3e1f0b4509102c42b83a1fa65999bfa5ad7f480c6f9c5c433179c319499d7745df72e4436d9d672edab9ab5587eced4fe10359effd80fc8d83da1505e0df13ca6d72c855be07a24885017c6debaac309d85613dc86564297cababb6cb71412fca913acedf9ac573f281bc3f2b7478cf77229959e8cb174edd600d4c5160efa846d3af4880997f0eda8f5377819d30137ca64abbccf61d51cb3a4de05f997eced2c14b45cf4527e1976c481343b91699b1d0c9672c79e44b737213bd2b62f24a9c3726412a94c111935c8f9c0c461f1c11b4ac87666105a47099ae0fadaff2064a33990e0050d4a41c62f5980be763fc73e047fc1405255f1d590b77173e2ece8ae72c5aa014d4c29ea88897377fa5db00ab30c397018fc6d279934f222d2dc3f96489dde722069d5696db9fe814791c189bdecb363b6f06c5bdb0276b4c00bbe5372c29b0ff8b35931f643712e488eb6ce48a93a99e3135ba151c87d0e1fef710d14d6c366eae22b2a1221fadbe4a979a27107f3d10576a003e33d6a7f4ab843db49053a96d10995284551d59557f17960d622df734a3b86ce0f4aaee7a58dbc3272c296db64449751301eefb675a28766b55c4e66c3f305aff3429939f65f7280729f856f2e1ff21228db12dea7e2ec810e59e708b6918bd6a222d4ef25c6b69672610b58f35aea7d412587c044acbbaf26f455012c61973367c068a586b3a8b85519cdfabfd253be1ef075a4d1babbfa0dfd558421b4899e619a30d47ebde44b72ce50f0b64e67028be821aa348fa080451a120eb0c89d7647f1b8f99a003153728b98d4816c52048a6db647972c08c9a35615297a9a60f78514dbbb9894b42a722f9d7dae8825d03f2f83783b406c0cdeb35a4d0546754ac6b52ecc2498f90f72e9783ceb3c3792a00dabaec42124812e20b09b438cbd719374c8c7cbfaf68272f2236a038192da04a3bc896e2cd444328509b7dde1f57b0363f2f6de98b34268a902d56bc74543b2633cfd284a9167c4fc04bf2c9241d40b98bfba72aad3e272918cf9aabfd6e5326a21b1d7fe2dfc4bf42d424ce39a5bf2a71f991929cd0f04b8a66a544bbf937485123f2372aab967f61ee027081e530f887a43a229c80b727110720531b637f481ebc4c21c401410ac3afd4fcd36680ca7a71724ad9c6c72172a3d1d4879c67b8b904ce62a694a72eac5402a9394bf25d6d4945842ee1d7253ae02543e1b59a9d54307605e0f74ddab00a00f2c4fd50e55632d18fa3bfb726df259535be734d589417d952ca347407c23978be3597000a2c720869dcb80426fc484f889792870714ace0bd390c9d10d71f2a6e90ded53054ccb5704e9d74fd9f9a64970a8f80100664a7b0538e063b6765e6ac3405cff4010b21b63a69072c9908f9a44defcc4fea175476bdd58d88fb775580089fcbf9281ae4a11229772304030d00c276e4fa26b95d779d046737279a3229d9aba9e3ac3a1cc9753790abe4a244128c584f3d4799c16eb0fd44f7a42a0d2e845f2b14518964649e3f4596d4589fcf0dbd00777973347b633b30c68cdb325300fd9c11c031e759c59ce726b331836cb193f4b9a2822132984dd1455a98e8bc7fe9dc04af655c46677e072ea5b89f1d993eced32da825c7b27962bd42d0201724c9066ac49b01492ec444154da0caad39c9cd8191869324538f91661d1d37cec25600d23c56123e22053727be52f3dc9ed2edc8aad7dab7d654763959d3fd56d18b3761330b2317a0abd206dc18d1c01f8a30627a9b47b3affb3230318161ba1d500d3933c11fdae79987266c0fc02f08dfb0b25e15674102a61e4affad282a6bc84c380f288bd661e4772031062c2daa623eac6cbf902efc39a8912a94a20637714efd8e496200571a41294a865e798cdc1a7beef022e27a34d6a499dd69e49100aae8375441e3eab4c2d7d773b264cb98f50c62e1afa73f292dead3cb158a1dfaf18760c46c00b99b9721dc3d6aaded3fbd20de25c23727aa87f0fce05421874ee1f33287bcb60f9de72b3cb9778e919d440a97ea0a4664499ce00bd938235b7cd0e8df4c4804cc25a72f07bd9a9611a15583c82b3ac45c190436b5c05ccd1d1b69a0a1ee6316fb074279df75643cebf6f2f6355d2e67298fc333e73e6187a817c4ac9db6fbf030cfc72e8db7289424cfa7f5ec6f575159336e0e71b31edf5a5e33eba36bc20088826145a836360553e445601648aec0e4e5d2a02ba518b0c236b08e280549d99f419506eba9e1a04838ae57a899a20e2f042ffea8c37faaa63e20d99dcad34a6e27972628f88607126c55057e4dfd07b1bf5001066e9e181056c8c5c513fe5b41923474023612ef029d27f8980c507a93c0e5e9b41e89702f1867e9761e994a7c7b870a27cc1a0b854004409f77e8e1a9547fce9d4d7986497cb1423ea883fa072057281a7f1dbfb66fce4640169b7cd10b71da7886d83a7dd137c3afbc32fb9867672c5106cba90371b1a88519fa25bca3ffc029fe8570bc11a8f13a07d5388586072e85ff5e1db93a975c637ecaadcb36bd3a2ba40cc692ebbfaf6462a07cc9f0c45b0ab8bc5b3b44fb57243e5102c2c9b0b180a40498c45ebe2d42cedb3c397fa7270c69c2596b3195dfbc95e5d44a884b6c6dceaaf2b4e6001343365e059218f72702476e683de4cdc14eddce849d79a248e498558c64e4426019069f16bb692545c0d21a10a3a10a8675bb400bc066fb4c8d2a39fb81c37f5afa1d4d003628d726e9e5eb409c9f26642c9fe2b2b3b1d328033dd56a519a6b0435e4de20f40e26ed403fbbff92a689de53dee95f7899e47932cdee3a4620dcfc2bb0851f8dd1372a14592bb687c5d8e919703f74daedfd4cd05b6f22b098ffd495a63a4dcb3d172b3f8338d627cddd5113c9bb20bb98e7da7bfa2d1ffe827bca6e10ce9eaf14872b3fc5f3fcb3d198ad7eced8658e440efb52452f01982bcdf6b4b3f20bf57657245aff6d69d1a8c14de69a87978ef28e09af50b19de0a1537d089fa3219cf46723a6b9539fa61ab7e8d76abb894333d3583e90ccee456a13179875585b8a14572f6cd846b7e19cd2efbcc0209a5cb1dca2a3b453cf400243a5f529c1067b7cc0c3121b2fe624e28734ea3dc5bc9f08e338add74fb60a0a96beca8100e8bfe15728a737b76d47b95ba7dabf0da951ec939485f1db3e7a51455f46f73213b99346e08dd4786f49f8def5266e71a751c58e741adaf339ec61b7f3c73c2e8167b851a590815b32fad546c572c4e8b0b042720648228a7ff619f2b681b4afc812fb6103c181a21cd4cb27bc4255d82aa2c401a12c349b86055e5a9ab1e1ebe598ba472de66e0da904bbbcde1be29f78f29cc7e4209c78205c2e3749cb312b20d6898726e94101c85aa7fae2abf8cee9ed63103f61db5cbb5e75151927c2c9e2eb40b2f3996bbcc0f476ff0a8e6e6999c7492c125f094a6a1acd6e467f14e7a7bb8e5723bfe2077bfb694c938adc882b0cf379280a7ba956d6ca20a42cf657d7354b44457cbb4b624befb1ec6ce34da822254923fef271dac5ba98d5c0613b2dcc11472d0d7ee01d1889fa4eff50f1c542ae65990ce790b4529f6e126a41e443dedea722a07dd3d57beda9eac001443eaad9ba2825e7fab49bb4a1e7a25d8d546f3ef43cf19ab9aaa3bcb3888b22081c2088f90fce97863e7132ea60c22a96fc48bbd726e77725130319b63c40d0577dffbceeb21fbe6559ad0cbae3f94e4ede1c47d72595f335c10370daa27324230a1a601770232978001e2887ed813a5b19492a4727271262e8cccef4a7de3ca6b535870ffbf9d0c90f57d4f2f76bb906ebd19c140ed48f9f32e1d902bafa00ab4326a9121d0d214b6d47d2a96a813e8bd23f70772ba5a053959e9ad1bb3b4fe179b0b1e99314387725a990550a16a8c4c036a6372a7dbb358bfa4d86dc68e92b01e97096a8e0522db5093da6ab8fb49bc0cddae72b1d43caa8def4b4898fbcae5088abc25f776368267c6f34da49b4db8ab19a372892771b4e78e42026a95001b1a842aca2a70f99ecd9bb9abd221d816c9920272b8d351838ad1ad4d8062f7d6d3ef669ec67fdad058a38bc48c8a881865086c724ebdea4da407165fad85bcdbf5122655234355fb622b886021c056f09537ab718f959badc53baf0e2cdbeaac0c32990570a8a1a3096a9ba7efefd9ddfabf1f7284dba01a28e02b790e41f600719e0037445db978e637bd616363dabe934ea64ab43c3ccc7a764325dfd0b49d1a8399f041f28d3dfdd0e1f926d710f82fb2d3419b8e338a7a68800993dc29add4682cee8ddd5ce9e0726a9d0909719a4450437249197cde47e6200b9a431af828f0d6bef7d5e0c1e598081f8ed7a84bc1daa96de317db99d611b44af01d8cfa0c0b14eca4004986afb909d4bfa363ec1fd6f272306c039fb82c216b9d7d34d99282df9c4d3a41677961a2fc10ceef5abeb09504d0769388f277262c3b16cacd03422c6aff48a6eb98ca40f486374c13f711f11af951c79caf2b6c3fa3ee1d6aba1a864f972c7e6d3bbcbc044035e5f8156686662aa243cd1c5ef34a5751644e9830e2a5eb171d0b04c2ec4266f670ac115c1972c1b276e2b87bbd1d187a87881b6beac1caf93b039b0d6a7f17cc936efdbc950582ae48226b53e88fd944af1446715b7a0773c3db6a1959b5f9cfff703bf3ad19bd6f2feb7a2e56247f022d6a402a09f8ace14aff46022c5dbeb7178d5e295b624124a38c232b4ec373674f6375799b9eb52e8277c7234e6040eac8364a2ed94b9a0e490ed0b4cdb89ea776e63fc8fb08e4a9542ea18b808a0f060edaec7a5253186bfb688991526b00168fb1e066aac882f94409c232857fced460d3e5e41072971dfd5bf39c77439b898a700af29821dbfbc13886f763b80b3bfe814ee336721ec0f9518f4b1c996b24648d4c5dc0b32e86ca16dc33378726e20b51582370292ca80fb3693dfcd6ffe34500aad95830d7bf2fe5b05b3ba93bb177c0f969ba72c71085c9ba53d6dcef84ed0b087983c4e2b4e4bb3b2412aa94fa793b9dab7d7231491e6cba0ff91edb741a77a54fce6f47d52872247660bfcd70e46c58effc722b17788e4d1a5ee299c4a762e4bd82dfd9ad86d9baea092e743e214c3dbcdd721ba3d39ce72f0431bf3390fc062257250faa4ff31533f7abacda2cc571d12f72a206aa8c1a761fa69be8273124d0c438727c7b6d1c6304d5d5725cc07d1106725d4e96d2b9ad8325677fcf9d36e42ea12b8309f577283f118afcd45e7cc577729957ccad71b3bff29664ae4b52ccfa1e720f3a564abd09608f8bdc7289097531c11253f83eac8ae2bddd85fa2430aac471b4a007ef342fe4083d16366f0d1172c9ccb9cb0643270cf97508575ac88379f662b26b563dc45066252e4cca5a013db9542335277b3fe133468689c528ba63869b47769d0744ba4d0ab22ea28bed27a4c9357ead063224a62947f8e876b1dc15d5ec4a318fb84c354d3da2e30d2272b7bc070cc4a2b1dd8b1cc901eb6bbd5d2f4e9a6fef2bec016e4991c3f56b5c720782e69f99e196de59440a6db5a41d75f8204340bc74df19fcf1b51189d0e25d8e8d9fb44bc004aa32a8e2d7ba057cfd00b956732e085bc3fcbdf71965fd5b54afcb4995bc3f633a0667dda053dfaabe5d25f8959c00e28eb39de6e6241b2646d505ad59e49cf302688a8af6aca548da16a12e1a3740604622fddc1801c63b721d3119aa47ebca8274d7418f9d6fc7cbf781284f3ca819618d5df9968011db1b96130b5ba29d2b5fc59b79949aa6b44c5dd1bf500342a1805bead74f4a5e5e72e8920d6997c9e221c51080fe219224fa2018bb431c5650807574d96ea19e2272ae20ba269b1c779ca16857db6bc1f52d6f20cf900b7bd75b0a0fe3617a8f1b5a03eb4098d66f5a2737a1bb254eeab3115298fddb2817713dbcb529d369901372b73a2eeda6880eaa37617a3e7f71cd5fd0733885517d961a671af46d634372729aa483ee29ba10b2b06d41b4a191eeb3531bd879056887ec8448757f26133d723000e6fe7170c7d23c01a6907852d337430a347e7becc6a6ca65846845d1811691d5ab8c65b0310320682e8fcbd477f72e580e6a3a204216feea3916b3e4ed14d0150697290e124b94626aca9e89e5c31d12a951239c73446c68ee08a32a9749f3886c12d352be194890aba7d6282e8393cc191fc65238b154833d259227ab6f64560403de1c94aaa3b109d3ce503ab8f13f431c0958783a824da0dae4ed7e03f5dd5849b38cd2215b2827021ff4fb3527b43b3728db60a77605bc302a601572e1756297de4db3345b3a7236dcb1c3b98fb46bd8825d9c62759852750ca7547292ef384c1dbd3efc72a55ba49d027236bac441f381a72aee06d10114c98eae72b7eddfde5067b190e4fb5c9dc04612a094d0053e3ac3330a404efa1497ef072196c3514ad302c97ed65066cacbfc4081f7becf78c9e2152aa7a3e2531f50fc128861d960ebfa4bbde632212aefed83a6a7fdd80afb5de6ea932cd5f166139c72f9685ff632c1117e8049e6221b24b69709fa042b3faa1fb6d6805ff588cb6354552ba3fa86ffc5d9b0bdc4633e0d5b3ffb058dd5a1d97d3c6a1cd137078e0972abb0054f907743931f0ae70ac1df00890a277c2b98a38f9d9c237264b2c49a72b71749bc8e0009c8687ed0d9119bf94a7a782dc3e6d811be13b18ce4ecd871032c06b640316e178b2bfe642e2208658d49f8cfd725eae31467daf4d425c8e472d7877bc45d113405019953b437926997817d28e46f7d8282f6d6113d9270b7724521b5d982198208ef6fe267321259252d51e8cc384b9c8969891e135688cb37a4de328ca407a358537a56dd01bab8a401f0901e10258dd6b569862a1811030d78d3bf0b243a0a6a5854af3469517bba38613368d6815d5ec969d8cb40dd8a05898e119b4ccacc5fdb69a21f12490cce294d62bab172f820f0207f2bdae578724b0342e3e12016d7404c5a39c033653796c29883ec8cacfd16c465fc6633722e70d2ffded51529728d6ca698927c674283c8409c4e3e557870e2aa101689367222e0e4591d1644f66d0bb7bfde998bd80a97bd328a21ebfffeb82d1f68dcfe685f9d412f15d11419667aaabdca24137eec66f9c8768049313bac68b00e2c27666e2fc77639fdb4998a399a014062988e8de5a0b7cee731cbc18466ffc357e168448066e4b3f890a550c5293acf94f8dd05d59fa92240e5531404e5df3f77f160ba6efb3ad43702a5d7bb40737c962c16b65a1f004d63ca436eaff1031b787c55988d9b3e56d6b1ba35a307342c98343f11bfba07fe3708cf60ba6af1d38bd163f2f8ad443e4294d92a87d34034c1791ddbc25ab6f1b2ee9b6608cf8be827517216b8fd6979dc7d66d40d13d7ddba3942a74250cba548a77e73ef1ad05da5b55969be1bf68226530e9b7fbe9333b49209aecf58b2abebd53df3399710d31fd6159ad6d03daa984b30b15503b908df2bc13e10a8206c1c1351536a95e9561dd1726b3c4eff1ceec904f0861a14a9cc7577fadb66497f9baf6d870f1a9f469f8a255261dd18a5bf45145d701ed3a485b89fd9e9eed25a10ab8782b2597e082c165ed883289d6714901baf417bf87d74eef36f919103b1485e05b9f7b97a0f9ff272c0196dd035d6f3474321aaf68b9d5df4d5e9a3c6defb893f6a27e16b5ee396345d0e984c2c2cf1904e1164dd554da19f7c99646ff8e4961e9ce6ffdca2bb82724961627551d7d7b31392b131e57e50d955eb534fcf6b86ac05cebecbee28e72f45cb130a112b2338d60f74d368daaab26fd51670ad2ed7fb01a3ab6424fd2d1802e82ed9f08e0bf4e32bb71f92913a1bfa744487a086145890660b6189be35726b0081a95ce3489948a46ae04a67013bffde5fba2e621d2d4e3807ba60b6254b7393d9c7619c59d558989a97a144d0f7cc1f422adaf1191df010e0886a5b3d722688bdaf2ee94343dfbedcbe787a03fe63fb4d087c8d7cc2527e00fa0152ee72a6a78ce49c69b434266ae78f752989813c52ad21a6f03c37952a7dd7497bd072c7114a06b4521e9b2a1c960f81261aa7b3e94331dfcf46ffffb7443e76ca41720f03692e1533a026a9f750b6e901d25f3db2e61c03c1eee1261e5e6375cd9072e4ecd92192265dc48a216c1ef444d1291b1cdcc57a27c5329827887477acba72572d1e47cb93f22789d6e20c09441752705ef588ab8b332f02619b44ccc5d86345b0c792bff643e2ed0edd375cf1fa25c47eb1e41bd58d9bccbff1b3bd10a07235e042d3caa636051ba04b3149834c2eeec064dea34cd4227a1db0d2a9bc637210b3a0d10c975ebe8e3ee4d7353a276a3891b1b48e1def87dc6398ca293d2172698005dc72e4b53d589249096fca4d2cb2b67607bd09bba6337e1595adbe7a728254248f2b73c52e3b516607f5cfc4b6678488437f72e1f4cdfaac34a1864164e3eaaab15fd2ca343718b812815d5efdc890a0a94b40e227d9651ac61c981f72e34e8b4ea2f710ef37241df2819798f8c4cc8dad41986feedd1abac04c131072f7c6c991bfd162bebedb2b0ad232828f5fc965b08b0af3c96978275962bf4572fc97a48dccf183af38818330cc388dcacd60f67a7a455aabffdaab5ddef8d807bd8f9ac69ceceb37d849cd58f82a6c24ccfb9d2068eab28f53955398bb833a093a4f676aa70a2749e783afbe3edb89fd7e6ff3d6e3d9fabbbca320985dae4072530056d28f202be9af15ba851dcae24e829384207993c6603d179b72f8e4a27245a7f32082eeba38c65fbdd60f5a2ef968ec5371e0c02616c0eb3ca9caf79a3729f60b6bbb4a947ff9c8d51d90a9f88489047410d8d998630bdc878612d7e772c6ec3e2ab32948fea171fe5de7ac0e5c7d6529f596b636753f09e137e927c5707abae50ec76051de892ddd4143b0831165f350da4beb09bda961f70da1482b720f3433ab3c1a5fc67bca7da9e18dfe6d2ca5479339cb29c57ad28ae29fd7b72f21c72a9e2a47180851df4f8adfca64452409218b0b8889b5cf0eab626e3a78720ceb36ddc96e5f906811bd8f19a5b0abd7c6f4a6f2b40dd146b45d3c98ac9c728ddc3274bde0fb1cede71b56aceffe289ee6fdaf4621c717ad302a1ff5508d51aea3fb84eb982da981b808aa9041024c2d968552f8896acc6326e97e65722a23828561853a1350edf710bb6349c12124810dd8b38842cb56a806e39fae17d5728e46b09c672ddc4291dffb60c7e3a6cbf30931a08549ba804f0ca6fda5ae0572621c45731a9f864a9504d716e4b3710368197f7f2e533a756b96d6efcbfaff673b9562e2bf74a43ed9b8b87da0693b4da5166671cb9dec3972d3108137afa872e4e2b9a501c0cf860d94ad6ebe126e7bef173b674133b99b713e736915bb5c72997fe3af21cfb4737e0a44bb88184a9042e418dcd47b2288f874adc91fd253728351131cfee413809edb2316a52bd300ffecb6b91bea64fe935107b709999672fafc0dd4c5cb8a05cc50236b93b0035090e3f44b091bba8cbb2be4c1f1cfe2725d1513d1d79da7450ac2517d9897b98d84c5b8a6b69d031548012b82ce219f2861b53ed1f9ba554efd8be291ae4e8ea989ec2b2e83db3c2cc11a0449c45a2a729afeb3b0d7095cdd9ffc572ce1c1c91303e573da167b57644aa196a43009b806e8d86671c000154b600ecd1e0b51a0df024d8cea5fffb413197e0981ef1d2e11edffdfd0eb5954183550376637e6629b471f888508c29ed136f9008038831c50bc276ce26e7f824dad3866aeb327962bf579405a2896a9c6d1a7e87bd0e3e9729cac2d3211ed049d26980188889e7f3d8f400c2c5d9fd01a0f286cbccd10620784c3f0335fdd814882e3b2be81bd3bfc9c75d316139952c99fd5682cba5c1550e1ca94540b856992bd8b577ebded01134b03194bfd827b632af3c6e488dbd772b9304820fa857a466601417008e23ac99a5aa39004025685c9aaf08a2e83e072670ae47487dd3dae0d33718b4cf103237da5fe2549b78f0035fc5c07582244722154309acee6cd728631f110ffee943574933d70f07cfaf97aa66343820dc872a6e42c1a2f872656bfe5d75e451bc10366164b6bb24af63275d81d840738ca726154a6d464220e11be2f9c275c24217527803e88e2c51488b20d8427f0b3c95c8a49b68b4534abae05c42fc5ed75e5354d2eee6db144ac307dda506d8e5de272eddedc9a1d1d49216d805d3bf00359157df5e79d2a21543fc587eb54c5d2f15a1e30914b9ab380689474f37cccb8117337fd1041510fe964eb6e9a74573f8b5fc01e88fea48cbd8c3d8ffce9d985f9c828c941b02caa3c8a49c3af1d66a9dd432a0dba83469508ca325e36b1d6d3454fbed67710b953abf789b963f861b42672cffe1f293f542cf2755ef845b938e53a4ff24d52d98114255a3979ceba2ac33bd1ebd230fad5acb26349988880f8d037db76b4ba1e436c9a3d85b848e7a68c72a0fd4ff429256fea28f4a2de2d246646e86e6d579520b447fb77bbd4b515fa72484525e2f24d208e2638666f013218967dc49c16014c541b2cc5f92639bb7e5f672e0f80c96f8dabad3eafc9bc7158fc1affd8471e28585fee0813b56a3b00722c0218160731788de07b79a77363444eb7222bc630bfb95c3b2e0eea4c990972a83811945563f4974af4c65e3075afd7500a3649d9ad7ae5f5980a8cce206418931fb5f21af64eaf08b7004180411e735a595e373e47054d499618beb37a557288143a2d74ffa65eb9ada9e2d13f1f47200da00e42ae8fde6ed7da6670bf4972fab24e57911b409d2d5a93236b85e6c9be857f57a4234f626400eeecb30ed272122bd11f2b1fbf389942d3de5613e01fce2966b90f2fe2fb715289733762d772885f1a6336333a63c5463819e5309c1dcc332a517d594083198408c681688272707713cd3e88e2282d3033e9c5053c0034a6169cae5ba90d7fb1ecf5dff7535e080a8864328227201ba998d89ea33392a4f5f509e531d406b18faf785880c86521c0611a361ffc7438c876a9bac6947f7d2b33deecd9b8c0b613c9ae3b3997723288a07ee1290a4193af2b38606d979b903e777dc30229f48787eadb49228772e598847916f120069006ffff901feb0dbab042dece818f9aae1995acc99c3b2a9dae3533a34472c51ee0bf43d479030308c46f1018e0e05211ebb3f5d4052872643e336eacd8a0fe28725a6d8b68f39b99f4bb84f7531899e771339b170aaa45e85d782bb4e9eaba08d373f3a4225ea9533f50cabef8d853da069f6faf46322dd92103bd4c0898bd61c1d158d85772c5c32e3175ce56707f96b22340e29121130ffe8422934b72c6ad0d5fbdbf1726e3e72b4d3381aa9cacf69f8388fff6ae1a0a8233112efaea3d92395dacaea8f9a9b933611bd29c5b160bebd5135fdf65721bb37fd25e77befe6ec5916c51470ae50dab9083d89009832a0e22a007fb480b5a2fc59609df70a33217b957f48cccb3273620eb1274354f0a28bad62f40003eb5a19bb4f3b649f749788e1d7eeab1330cf0a0df3a199b8bd5932066d08f2616401783a1294d74e63b99dc8104278246e4acb9c80509d2cdcbebc8ab21072d72612d63dc5eaaeda74b14a29204c906a57ca1aaa6c31f6b2262de5254f00eda72b5168469ce369184d5e772fc57c249da76cd4d9271b2908d9cc35c1b2df0d57269f8e62a418d8e667916ec0c673c87ae0e21e9df490ccb7ad249712dfc700872035fcac0983e1a5e3ade5ddfa6d404c388858104452106716de030a34e252d72793478181c0ed869e63af9217716fcdaf97b36682b7674e52c461c34ea10cf72d3a113ef06a7196cffff049d3040102512635abbfc50206a8f07598d2e97dc72598316eaf4b40c533fc9ea65c7a3cd535163e900ab9a93a692565950832acc725f7347f69d49c94c21cf05c2743f7cb6e2d7bac3b513f23afbe8f245623c1f72228dbf71962426f1fb9e5236e76eb2fca6bda3815f85092e9c3430ef005951722bc2d3a146ec1758bd1f2bb45f5b034aa4fe9abb5f06bdf0a193a43b10e3794c2fe213a3b4e69c3d77f7a2935d553b8a5164afc9e2357ac73ad346aa0bdd0272a0d19aa4888fafd47e82bf6f14d8a21f262512376a34582b26258dfa36061c578cee9ef4e03a7b7d1ff46f748e022ca5d75454995b4abab810db78f4f4ca3b69fc17c28e54fb84b95b9de7f4d72da74c4bcb7153a4be7de2609220dfa9ed8112e34a6c67ae4accda337c8487c0334a73738ad76fec3b51453cbe679a7bad5e72dce22fe4e0b8f6b1c997199ae3ec8bc936e54fd9a618c1c92dc6be7764a2d066b2453f0877248bb0efc18c91eb3dbc47f633ea1941ad22d84fd400ee8d8edf72a7d911307c8cec27cea60317eaff56b9f3e485a54e08a944fc09f771460dbb7207036c788754642a3cfa20a476972c8ffc758de1dc6107417379533fa7a4eb2f12e14a20967a2b7d6dce3f7225ed3b6a38ea89698b21f2e725a390c7ffb6e2728d388b590c8f7cbc7be8d1d7cdb7c8c890046e1eee77dcae4295ffdda0322672f1c80f8d04f1b875bc0b9be671d1b103e4de8ccadc8415ee7cd28ae7850cf8722d14db72a9d0ab54297f76b06ca7fa1a07cb6820879d0cf658695827b86dee706eae5f1090ae89d441eb6538f56172acbd66fba40510764660de1388d8712472f62e611d5d2f5c2aaca51a01ec421925672ca1894612222bfbbca54d306af814be0a04339058f020c47cb09663be7e165daae6bd05bf51522fe4c2ea02fa382630ee0d613fa93861550b1079c6754879dec4b12124e818722a170b72c9b46c26484897f989472bb09b3b698ba9648c58964f51636d30899c17e2405a0a835a71883bbaaf1aae794a401f4edad498749c3c3c5e346ecaec92a048edb6974b297222d49acbb7924ff7a82c75390525464bcc90e3bc21bc378f8a8914c3f43a5b009e053efe0ea57452ece7f5b14f022a240374cb472624f8101b36e5e4c4b3c46c1eae0b9167257ad8568b65a2f1151711f7e24f9cea4724b7a6ec7227068221725759854ba0196c77a4a93ad2d5ac8853d53feaa5fc1b614d16e39d4d616b30729d285c24db5f0e092006d0a7ad57963759b3b543257ed7e914adfbb5d93e4372b2a0be1f2fee3bbafd3801b3ff9fd6bbefcf33b17d4f756e691b98cb3706bc52757552ff6edbfb46f58b8faf6e55f14cdbd70fc330d3b7b015bc1e92621e2d727f54330b60927e85a0f8030db0a531a34b410a9843dc703e82d72d386800a372bf2b8d3e0eeb1d2ec5178ee552bd902173e21d2772970178e260c35d138f31584cb662007ab22fd976a574d21b3a6f3cda1720035e5b1829d1217c8b09732f723f983a6cfe8665dee4e9b8444e091d7413a566efc926e2708a49c81e7f2282728ca5b29e0804cb0b8a9c2a8e90bdd137f31b3acb59cb579cceb6c75bcd6ecf72a040e6b332754f01a0a021638b2b5c5c0aa87bb5ddb939ff62fb3eb0ec91d7350ee64b12af1cad8d0290d70c75833d69df5a947a7a6c8277a4393bb4c3b04f7288d8f62556cd81c52991ab74d3c5090cd944046459ef65c23225f64539a1c86b3ff4d75b6101562652c6dc8a334362ee57628c0cc4cdfd3e52199a236f5a156b741b35356a3495fe4e2117f0d3e875306049a98a97448b87c6c731d2c836f4403220fffd8852820b9e0e81ac107e16f1e812a4f29ee4bbbd37b745c7997aef72320c16f4c505294715e3f07fee55ef921c98f8758667f958447733765f4af12a0255b53bcd234c6e7c58c6a9a9a42473e4ef57f0a46d58a2365895f114ec84722e3b8875cfcad537fd85c80df28432fdf077fe880d36365adfae71138e380a721e7a09ef452cd005957b575e3a17dd87995204b6e4744344f94a8dccca4812723357c1f41e05c5b29c00b905948674b0bb0f8235b1453a162b59c1570c5e937231d7100d1baa37a0627a5da033ddc60b6ed57c6e344ae0f3440c6190cc0da772334f4df996ba9e1548bdd0c2a04cdf870f37e01f52cce2328eca976d38397b7248a905dd09c539f7b1c45e5cbe0df24342be922735a9e5fd4c18005f5b5c033f52d956bb35fd4dcc913d5cb6a6c70359ad9af84b262243176a768201ee0df072bd3b00cbceffa8a8ebe9911c84f7ad6ab47bd4ad6a60efd6da59158e96b1b45bb060f32ad7e7606cc346d6678e3259b5a49bd5806cb6278bbd52d19250378c1690a8b5e72c704bd6a6aed91ce8be428c35b3fc20f691df790fa2aec4023c9a18f3df062975441667da00bdfd9bf2a261f80a6f5f44f5c64620889438898cf6727d1dcc5e17a6a6dcd8d05d5f82cb961c6c40691c26a48b89626a9fbedd168e72fe7cf8c4b4024d88743e95fc6f00b2c4d76d5c13f8d98202a450ddc36ce92865b5c1f079ff8cd2ed11586973224ef7dae938a60eda0c1c0bbf1b7dde98bddd1ccee799524936ae7a3a54cd5a4bae78cccf33cec2ca220b85e53b5aa27d8375729dd3c954a21f3c776eb7d024eebb73e61f48a5f8f526d70c7fac7bc1cc94ed2f034bbecc57267ff0c9e54463aa109a071f317445cf6c05b8bb173a990f28276e4ed23f50aa316e4d76e250ceea15cf744af3e777362704eafa1af6746574eb04ead9b1ff9c3e1ffef62230140d24c6f5449a1ac6f97f9ddfc4a7db069ed650725c5f7564c6dbc9dce8a55bff374dc2714831787f41c10100a68b6e07629729724f98a09ce954f4699216c765ea9e9bab7fd35258e6e6aa3f8cba70a96720fb148767161358d848a3d385095044ca6955f3ae612b24184034d63c0dacd64e9558f52a3f0b060ae5e79b568f2ee6a5b51a55b1219a9f7942ba4e3ab4ec483575726dac4aa4d3ad01288188c9a3779b619a719ea20d0a8d510105626663aa92c03ded6b1b18fd1f61aac4dad8806368bc5de20b3876bc80c8bcb0eb2511e6ef6a38098689cc9dc70833951368b0d3d6b9f6f78ed6c0cd6cc279d8e2b5de22ceb80bba48ca8a8c30194e0d10c3dffc640a9e4a7edade459a4d80f63634858a012f72f4f7c3f3d6895cb02d14cd45b48114a2e86f6ac9b15636f59e706951cd7a7e7230b1b4a8d2c015603497784302b61a715861eb691221ac446688a23bfd30e472e464465dbc67ff6ab922a8f8687cda49c27c3f29ae3f8aef658247d5126e3e3360f15f77242e2c707d490e679793f55f006a452357ebad5fb18f890b182e6211be75ce698538861550a395183fb5d204d34b224aae1f0ee414724ec4aac9780dcb9c84446b460074505686586e11434582e293e7dca9e3a9900ff74b7717e172a80258fe52b05a3bb2c2deff5401e8686b7bf6066f6a852337c3c8c336081272ee93cf193b3fa9b8a0f17b429c86534a4702f63aa3dea7146ad096299bea1c720addef655d98786acdbe65a621eff9efa3bcc3dc2a6da5574ffe5cf68f8af517875e29186d4e45b9ea7f74f2598242f403bbbd94d5c410e171da4e543481225256617880da6c962333c8d0039e649a41d7e21ba9fcbfcaff3e0191737ab87d371ad6520751afc8eab5290345d18709a7baeafe19b6f40c3ed9a88cb820456a72e9c4f4c8842741c20268e108a737c6e25ee073db66334a05d30a875c3dd6f272f100c2ecbb6d7b7c93dc243ff262aca864b103820d65288b5e161478b3c77c70c15e699b7a781875419d1d018a34eea6c0ac0518d779c0067640111ed6dc8d567da4b476fe40da4da4217406414146c7f8d8908c762dd34a1c86ee7a2bff341d3dd1c2dbbc4b3c27d84cae055678cceccfea65a30e99b99f95e079655631965d1d818283f4ffc7100c84a8fb2d1f9f1d62dfd89a13bc7f7b58f4205aaf47337230f3922c4e1b8842c3bb091a1642a58c75c9e23a820ce9979550dd5809d05510aff654ba25affad16cb1c5cbec18fde56f811c9567642e36a226c97f82e5bc31c06ab79940862d43f8cf23496c414bf5601c8065aa8e300458e1a77c9de9ea16730b349f492755318a3b82335f65778545d347b967e51bef7af48db7e7797b610f284db41d856d228049f94a006784b80419a316eabae93f444e44c9e3310d72a0554903ef330a8cd18378c905a4d8814a204e6f33e2871c7447f5a1fef511601ea14ee26f3443404ebe79d36d77c7e60f113d1338e5845de90c8720a1652a72c2f55630ac15cdaac4aae0afd2f63b402b7cf49f964e983a4f1ce04117dce912d94ef2e94a5febcb79bf85f26ffb3b59a5e6285e047b3d90570d09df8f7e4572bd30d8370a5e74cd51fb25b2511448dd70400ef3af82edadade2098a9139960c1833ae48966bb0ba2545519100073552cacc869a71c5321d197113f956a57a0f684d0a45fdbfd4026bbb0204fe5f625bfe91fe81b2e4f93853d660cc8d5ba6722128ba1d1f45272b5d612ec37651882791f6df6d42db12a8b6b7ce5cff8f6f72a3b6083c25590f91ed604ae5a0572dc6cabb1474569570d41dde4125717e3f3ba33dd8e6972580a8d2123365ff074d3d32c280c61059ea1adcacbf1879e9366efd2f53bdd6d1083f98324981c7f8cbd6114da837629e06d7f315643172668f16b02156cbb89a2c035c84b2f26dc2bc9b67ac0e96baaf4164059a74a071de4115fd7a01cc978b864ba70cd269af998bd71e419f29bae9055e4bf1bcd0ed0e024f8bc125f110e973491a61de089e5ebabbe07d107656548295aad0e5a3a6ae9072fc6b4bb408dd3e066091290c6ddcbb0a871ab08995fcfeca9f1dfb2a4525014426fc5cbf149d5f2987251edc529093cc3d282f8136b0386ef9509cdcc4596b1ec307776eac20884afd6ff9444cacf46e0aa13b9ce58e0503021ab855d61d511e7fde099d702e2cb14dc716408a9f946b3f86866c9f6cc835377a832cce15fd56f1de0f7c365d2f811aa55e7743fe40920e99d087d60b1d5ff0ec0397d9b5307223c11e9bd1acef009c14aab44928fa76932b004d4c03fe0bcd899fa891b28a729d64f4652aa0dcbbabc300101f8d2f5b95b1a33f1bfd7ccf1e2fd16108c91872368fd6eb14b2342240b36ffd3da088068b51668116fa8ef3413e3bbfca2cf472c406922d49409cb513cd1b194a0b277dcc72e8ede6eeb9bc40dcd4689f18101a0916188b864120bce664d33100854f3059081773acd22ede9024f43cda72bc726d0b81e14a0a1e32890bede2debfd3d706ab75ba5704223800a2b2f297215f3f2e06a8a7b28ee9b0b9ebedb46f64e22c957c47b95b82434c50d0b60c6081d41ad23cccd41c37ceecf9f9858deb1e4903c72a30c700da03f0f9250a1f24bc271b3121c50e5449b95796523166dd532523b8400a570fd9760baad46768e8d7f3725fbeab467f1bb859992fbcd302e6d5f6395a7c884f42bf7cd343d1810afb10728ccb7c4618d79beb798f8ab2fbcf883b7b5bd27024c77677b6cbf7aa0a06fa69889494fe459307a4d49dd86aeee7640371305908df207c7a9b9805dc8ad67451bec8d61af3aedd783b16eac422ba99c2a01f34adf39d4a2a352572ad0a39ab7276a0ac044d82249f9133c5e6689ce170ed9f00186d31c765018f43e5a6b67272b7c187e9d7547ea73892b67005f7e55c8dd91583f62722f2577fccf751f8b431bc5fc49faa4c99f7c1e5d5927ddf61212c3b7a0d5fe20a807fc1ec526f6bc85e8fc01cbf8cb69c1347ae35c00b06341f960a4c0f50d7c981b2250f4c13dcf9729424e171e3b60713f6dee7a3f80e77d9faeaaee094a3c28756e65079b6fae628c4d7174d614dd10bfccd5ddd8776f37b6c1fc9ccc4c6fbd1d8285e80ee721a16cecb8d4e747a4d67b73e64d6dccded469e6926a7ac242363ee2fe8a7d4d20a583a788ee513e0c16c47e79468502f2c401d0d95a50f5c8fdd0ebb51ca1bf2d2721c606b9563c11302d684422c26e899f3edc97e498b5126875ed8f7610dfff2726a178bd3b1544b7a8af4b4e125d588680d6545e61431a06fd51782a57fdcdf72672dd273b4f6e85001e8b4fa6f9f881c8424a25c48367258fab9326d9ad4125c759c4b8b61f0302daee52248f57756228f620b94f0541d456dea08674758511296aa6de782dcb7e1c9973ddca93c980d59eb3b063b3a60d2caa5a96d621ad70f7707c2da98c5d0b03147e96794152f431ff441cb8f6f7ab15f6e5de840b884725f3e509674b258728f4b2f35bd1db63e4680f6745af1e3c572a74e2698061072bb122a586179e3fa7dc74834fa6d2f975b5e7976cb5a7bd796b659568e038a378ca811bf9f5d78b23fd5a68b6bd76a100f38ad434476f09c30e2396aed932472f35e94dabf1aa8f6b624aa378aeca2fe6599f505c01ffa1035e06a430770460fa954c17187876e33ce83b6f3a7ddb3f1cbdedc9aaf259bfb107d48c21a919872fba1a2733d159853b0642ddfc47de5272939b05501091eb203784f271635d6131db315860fa04a57d5c0c502abeeff7be0280ed617df9c9c852ee54ec3d60e72c2e66131044d98c468b01166f0cfc7eb3092f034e1b9453cf09ab68804e74672589272a528bd1c513d30143cb06902c8537232aa6fdbb402489a36b2b52d3f72b20f9aa13c132743c6889bedd4c891f0d474008ae3a444a9c7ab4ffb75b7917258e3dcff97eafea0f16c312ceaaa7aa640e378616f836b41ab590a36fad4693621b2a8008f58f072365121b0ed66af227d3b9ef8a6fad575af57b19c21f7a072c4eb11c5dbbd1aabba20ad8cfd74f675ed74cb05a807559277c0f353ccfa2148d320731da8ff1ef136314ef1b84e84db64866a009ea6c41f997c247918c64e093b32221fc372277a82d53d8428e9f10968df47fa020a0bac8744552c4a501572610bdbb4ffd2b85ec6bba19501894c5103075a02aeb5d7a860c252938f86b07211c35851c7b983e8ee4e5193270148db9f3f4f2c5533138e87f12a0ccdcf63720263ba7e1f3a43b87ecbb632311398776de64639d046dbfe934acf8f10021261b62a2a7e21a100fa6b819a244376a9e9c114d6a6702d0329418957e175a484720b3e014494b18a96a38650a85278472b3636097a2c815e71c10993da13a5c3596b4ab73afae429a8ce7d713c553d95832d11e31b3383864cfcf17d8d3fb7261ce7ed772767d1da254f72aa7b011f4dfef3ac0bc6ac1132fa19628264f56e1872af7b8ce0204666d1b3430feed52157ca32319644977546e49b67b752c3ddbd728b18a4c345a4f5cbb3e1def03909ffe9451d12adf5bcfcddca9846d389e4751c1b4fa8897321fe4f14531a27ec1eef51258b22c443a5b1ca1ab8440de9293172bd63122f7f9ab9f1075c6aba9a2adcea4c9155f22eed214b1be0fd0365ff1617f8174f4a1dbaebb35685ff11f0a8601d62a4a17958d1a267e2d1dcff22b1f62cae61d77e6c5cf05ca0b923a0016bc509d540cc2a0a9146433dc1b1656abfd129adba31467f62d6f5d1b9375decfd78e9ba84da12c76cba36a64c555e4d484672a5fe1805f98a8a26276559487a7222d972ad429bc184598323af9360c948ad72fa139f6d0c468c52bbfbbd4fc5c2c692e6450921befad8e1108de49fcc15f67267d3a730ccfa17046b83063141c9290461231badfc13827aab0307e749aef872ac77c70bbb7b8b1c4b1c94cb3ee0294a7e80e73be47d47cf7242df478dc63072ce4dd7585bdb7719af203cdd3498acf2551335828d7e4d5d826e7eb6507a1f69acb12ff62c0bcdf3a04f8f9e61d1b2cc722023ce315180392bc13a232b898a7242700df56b593985ffb3b58537891d90113eb03e15bcdfb0df5a9750aa2bb8725d732aa97a202b3a0527fe485c896e2b1ad2aeed78bbb1dc8561359e68de3572cbfb74da94ed50841adeaa0ec91f31b9696118579b210f3c4d3c4e2925f84f7241a21829fea2dbe1a6a1cd42a922cbb11561aacf5ac3530be4abe453a6cb8921d6028c4da8ed2eaea884a03c1cb1af950ac74ce845225c78f759ccbc580068518b278c0955f5eda0b654781d673823d04126889a41c01aaeec0a7a34a862f51f79de801951403dc57e2e91f64612e7037c05d731a4d04b07ab88cb0dfb62747234f56e41cc16cb5f40c563051ad1249d64743677726cdf54ed49edce44bad3100c82b4e3f5e08ad755c6e36a8041ea8418c46fbc4bc9acc102535f24df7e4172428dc85da383a0be72a3a10beeda9befac9f31908a2c1f29206a828fe1752053611a402ec98d26c33d4fb23c9dc56e88f92f452e71a52228405b872e143052721ce78754a83e35df222168c43ad623af01c174a58be096876f81664d4f6388728947201439d05a57f328d62aaa5d5ef4fd745b271dfdc5b6bdcc75c695f7485dc96e1fcb2b290ae3553b8a77ad334e49c5b15fa09444513471cfe4bafc6c19721750e380c3f3f4e2b0272686596fb32290a91366ce645a12c46fe9bb4f55876e0962c34e03553f98d7d2cefaa39b899908f00c6408cfafd7c0e54f25de8ead726ef13f08daf97e73d26b087912785d9181718d584698f95696cf169b2c358d728213d3a8a45a44db2fd215952860d236b657e5d6226f2b81fc882889e06d033b572f5ad02c916de33b6bcaba86821f3368ca799cd074cea35c4700da60028e72688a2d5b2c418381bbef685297c8322a67ccb6d7cde8308c2aa06f89f00b6e40c87549d3514c4d3a907d95d529ed514d66046627216d1e5b8f471a78ef4c442d46efecd3d32ba4e225e90d1cf18d20daf0219f078b0baf8457a7f387c0cd003555b5e8dfb636e8590e96333270e01718ff99df8a81951f1a72ebd7930a3f896225da51607d74a4ef9766c71a2238e35fa1541968fee5c8abbf07224938605b72862cc57f2a50b8bce0c3568fe2489b8bb95025cfbac317a182cebd2f29448d72517d8ca2aeba92f1371a157e31e82d99b852c5ac596485abc8970e01fea61e72eda514fd2fbfa205d500c5d6208c342c7de40222de88314bc7f08a0f9d2be6725360372d3543e1cc1cab31d916b8ea1a34894160cf8f54a4ada2073eddf76172eb9905de83929449fe80d23d65646fa3d24bd266c211564cdfd91cad7bac0072646b681de1d1bb6be01e748c6e80d9d5593c57cf6010adef8642225098457811d8c5f268668237b3b557a9878832b1f69c898a880f44cac582366c2025bb6c0d5c20fecf6ac29fd9a26b80bbd4eecfaf6c4ea0227bf36d96354bcff1c4246a72ac890c1bb977f9ee1ebb517b8b68f0cc004116695264a22cb48bbd52f7c95752e7b7727f66d0e795396c7bd4a1ce78369087ab7a70af26c0f8889b331f944250426df4d686ae7f1eb28be4ec0365556efe7a37b14c1fc99f77fa9212ca43f77229f706aa2d0ab3f12d365c46a82d543bc6707c0d73294db1de5ef36f81b6f97217bb87abc1c06bf44fea1116056b69d1bcfbffba7a2593acadddd6062ec52c1570d470c7a0d07bd274a3e8ee38354a096555f416d58437f92af9fda71bb2da729a8b7abb3cc49bad33c44fe7db874efcdd6e43b099590edf571ae07d21999b51677c6bdc0759d3b839eb16236cb6de3aa8216f3e351c1990cc963c2f10dc15723e90acdaa1cea4a022f928c8a6ead4e91db7b8b308204d0850df21c025707760393cae2cb8a7132ac24cca0081eb57c8ec5415ec1b082431c6fa31aa67d1cd72de418ba81257e2fed45c8efee120f590fa96909c0daf72a0b44e44985fcf153cf81d412b33a714ddfc454e62e6dcf0ed10582263eef34b30e534f127641af672353eedb7d9bf01ce1bf65f410603197c17d65df136a8f3795c9c9e178b154a72e51fc403e75ea09863de38c6a319fd25480ea53ee3a938ea1b17fa4456eaa21bdd4b95b0bf248c18da77eec61ed46259104074c6ba8a3d8e71bf17192864a439b1e6a42032453e06700082b2727458183557d53b5837970ba5379dd9bfbb6072bbd4115495ca593a3b6b051bdb53f98f822e0393c0c850af8b7bde36d25fa70cc4e0b6e431f2d88d1c1aadef2559f176a23185d8a97e3192d3830865266cec4560fa6640e05040bdacb227b3b4be87ea0a48e0eb3bff2de2c7e117996f28b4079ed2c1cc92716fb0d78e2b0e95907ab83cc88d1dccacc43d22cea2ff5b8ed872bcf89c1d8b3fd3c7da7142da08aca35d286120f86bba2da03a4a9efd4ae9ec6dc93c823ceac7531af9811010b7aae8970fb433ae0183863c270eceeba9a2564238b267d7df1aee5b1b359570a7a86984bc2580aa8db5b168990c5b56ba485a72740590c553309a80e51e70e8db3e45ba4c5e30c0e8de3d4ebeab9937342bc3721aef95eb598babedc660eae9afacd414cf3c55138d3b45ec8f9a2e4794414a7285117a104924de49d69b79728ba125687d64e293256ed173566523d00b001536eb344a0c633bcf68534e598f8964890aa2e23aa1b30ec38d88868f48b59eed7210d4571fce1fe69c11c343f7ce0b1e5ba8d1da1a0be6a24c047d7cdeb450fa2416a09d5444ebc1259b3faefd8d3f8421a6f746c7a96eec9b3885d4ca3fdce0728401723d325981618bc450dfe507d0a6b98ba01d5bcbde77ac927e2cbac8747230152ff4dda2751bf00477b048c0da950a40deb7f7a15182b90089f232d818725e828c2d61f10c5af151772c19c9ea0e6ee69b8671a6c8a11aef90de25ade10b5614dca028743992e3efa0eec3513db4b8ef46f46d9a006adcc0680503234361b4083193ff170543de8ea05feec68e3d5becfff9f0739b310952be08466978722064ab921d668259c80a0253a1859b0d017be1744cc9f7f83a8afa08f6b6e672940ef5c597248ee0a5b52bc671f5275a1df836bf7419b0ab8e8005b13c612c1be0379b785c752986e139eb04451974826431a5890a3d3a8df4378b4cc705c80ba20e56bcfd84d04b9489979b403e3f4db1751e9edc464187b43a71d2da0301030bcfaff574d27812b866f4769b14e1835506150bcdaad8102e945429a463d93c20ca1caf562887665bb8d465985a4d29bb943f4c26ea355d86548b54c809917224042b250cfce8a0f7cc0d71d256ad93f0c62ae368b32bc3ebda7b8b13bc4672bbf6eee36f7ca74f69336d08bb4e7a148d5f3aae195f9891193ab50a36e67e1c6062d843139d187df63753b762fed35e4b913f5b0367a076911c3f552848f11d7f1e702d0add7d67791ffa1ad14c976df40125123f5080284d3ddc89318dd672603d3531d3e37b394e7369872f3f274e3d9c4be63385f1cb86ce6f1c65b61e72bf5ec0ebf123d640eb28b77b72b8c52ac09a2f72a331de0890ddcb3de2f70058df414018f95ad74e9b518d7f1a319aa5a6e51791456007c9ed65a92f7f359e0903d40ba64b55f71014f11b865eabe882d1211f8cc8d3510d9a8cd6b94374f459c6da780bbdf235ca26cfb487dcd9adb12ad6dd40859d4e75dbdd3206cdcaeb72c4cec6928e9d952a04531e85f11ae86c50f43920ce51a8c897e709178ab294727963928656399ae850d8f224dba5cf347f7b4f9eb5fd3e623fef8bcd21ca3a1c258f25156cf887a016afdc4900c258941f0c0a83f6ca0b4571f44bf8c3727772e2ec4cb859c41d3237a4ec1b7cbc6fdf4991518f01cd096620733bd595867c72e647d382df7761323e72f93667a3a0b3a6cb328e16270fe8d10a6e1e926dc263f7a4b169a0ae16e0872f08128ce81352aabdf7128ffe53d289322de9f21d8d1def4d4da4305edcf1371ce1f80605a134bc1b0225e2174a64bc1c327b8f054b72158d7dea02e67c73af71c32fdd4fbac70e2a337fe6059973e84c0d0a5a2bc45ec8a59294aaff31b8acffa7e8ce7e83b33478f59c60304762d394656d3c02863ca88d9bb04bb834da89549a13803390db39c9e41f00d8dd9447d2d7b1f41d952ea164fa16f5cf140f5c98c4828b14d0aec8786463dfe69eaa5016150fa1703c288afd4b33cf5bba4070de35099f81a04e815b1759347aade655051414aec08872da9b0927bbe6163abb05316e7f6a5e4043306eb57aee1aa3424ff365531d4972792f3dba9b7e5a8e4948bc8e3d81ae2b6c2466600976c3df20c6d20eabd60c72984518d41d33d0634cf244a0e724cc088edf1e16cd4951842878a4c50e8e43723ec06d78dc3a8812ccd38d20448b26572c082e1c769e0d81a8f9d2e96151574aa599aa7b52c60a183eb1678435e6f80729ed0471f920c93ae85cf2fe85037d558722b09cbf1f03d5908b593ca5107bf98a9f7d2beef2717d86db2d2b1a79ab1776f650a1030405683d8d94bce0512a943e4d1e0a0e0db1778dfc210e7874253025e3532a58b477e5e96aac42ac46a8d2038a1d7ca5743aa1abaf646a37b6127242f1a561c4c681bce1cd6a62730e2e422fabc3da70befebc42db94bde8b31f5edfbe7173386db36affe34d487b8e335ef4ff26992c2e9a4f8f96d4efaccb57724b75a29527627c26f56178ccf9e7ba5fff066bfa6b508a77a6fef2c7fedd9e72fe07b00181acd42af695542c8d7b09bfe49cf5331b67c6387fe44070dcd9e1725c81b16ba62d94e93559712fc923b1218579a0ef93b22202ebe45cf028a98672a56c2d5bf5cd88a94cec4df8c4478f5c53eb494a840352db674ce3348650787295fe6d3c37c571087dd0120bab82129d3b65a456e6c669289fdfbbf3f314fb1a5d4401b855d04a3cb0e48b2c0bec90eeb1806cb5119d914af96a84b86f650c72f0fb4b280e73f1233644332e85c0489176b621b6b1b2a18aa93123f6c2164e02f29387c534e9c214afb2d206cd6b7036535a67433ad4672a663d9534df7fbf722904414fb3866167f7f9937af3184874cc8e92e6efc14201c398fe6c36d3a72da4511795f717ff1e89e2ef0e8d9a88b3a1be9e690884f54f3be5362af07a0772e387d780fbb7e2cf767c0b037e36b47218a8555edbb925a96f1b8a669b81551db878d8ff582c6bf74f1ca566be9451c0a33f9c0732c8f42c8ab38c10fc1831728db25d077d0e3e826cfd30735dd67caf69e2c3c4a161733435be9e024c1177729d05ffdcdab98d7a4c3adcda46450a6735d243655989356e198770628335d329b3cd7c6008e8d1634c1d8350ef852cbd88f72cb7eec9b897c41df952f34c904fc830485879df1955d9fcfab9cfb2b72b52ea341aea2332d0d6ef3572de491372fe9a516d0583ffb236dd453092f3328ad1a032fc20f92619a3924fb6d23514425be91ad928f2df268e4f5ae0192aa6e4a4ba5e4f69dca404d9576dbb59fe497201617fcdd863e5a962d5c97ceb8a6e4b0469cd12a94886a2d66d75bf817cb572a011c7174a83951db1739ee9144dd9dfc20996edf666a398e9d645b36a185a3ae013d6807493334892ab1b18b97b1bc34ab644182add87a97b9d5a0f86e089728aada14b2e4151597c5c52265dd8f299a247faca9e5f8c4af61695c8984dce72c374e5a4460813ccb7c3e16c6a92d8492514d20668702fc349a3da259eec0d72c5b9baa7e0d2568e106ad74c60452d1ce6060ee9b6492a3fe5c45913c0f47372b6ef8e171188c28d87301a06399b58c2ef5d3ded5b9872ce4a2feda7a1339a72ea0895a1a1de1ea540d75dad8433bcaee2f1efcfa476e2ad2d99bd94e48f11724123f451744e8a75e2baf7c15eca25a43f04cd31f9b34949da899845dd133c489029c044636ee8ea9a83958022246be7ddd36d93f60a94f9c7ca1f17894c9872eb214fa917476cccd1826dac886353d08cd3f2f92b5b610b6d7c65a0396e207296c55c79c722b0b6bcff9baf0f24ddd10a133022fe59bb2244475f3c9f254372a71dcff19aaa2449fa815975f36dcf29f6ed49b9eb9033ce01691ef7f7279c18cd058909ca7915d58e0f8b9674fb0f54981cc2f713f9976e0468b844f74a7272278d66d4d4fa0159013f74ce7670b29555e17bd6d00dbcf63beda529112c942c1925434ab1a83b4f21d8d07ad890e6daf50aaecab68f52e34b97c0768adb7772a9bdf32ff38e384180f17fc96b4bbf99d6686db5a24f8f6eb8a1c8ad3d5f7c72e7d02a6d405233978590058e6975f8d8c06d3a752f47346851537bc032908368dbd70d95ce91894e39fc631801f01d479cd524526fa18ed8ed9c7c10a0d6de72db4450091be742d23e8a80e2094221389bf234f0a16302f8df79e8cb6796602826541554eb0691dbd7f17d5fc0c91c431246ff1b61263866a04c41fd1b279c1b54bca8e86ca38e169f8cc2ed0610ad2bc9d466df81082277f8204565e176f87287ba5e3d8be52fc87803dc491890b24ced1c6c56d160b438c0b2e1186d21317208408d0fc67f4e8415470846272f33a2fca08bc4adff63cddd02e488771e5b723231ddd97ff133c3fa677b59ea24e8cbac1452d73f844590623d637adbe01417d33d053b35d4a63cf8e8a100e22a28ce20cec18f07577ac0ca317570c27ce543e185bb34d13769734efed6977108d739cef60f0c7cb8947486b0dc2aab36ff7265dfad00e4d1d978b880668044732b12d3b8da49c1673f5ce1579e134019ab729e1dd715d90a9265a0781e8ddb0b182dc30f7e68ef3d558e55a4b013cede80573cbe5fa6965a867692986a43e6e5d667026141795ab30e3e61d89a7ab709f1727cd01d09c024bcbdff6ee55792c17f8a3bcf8be33751a03e497fa5a466497872a49de94f71f0f3d6eeb34e8555462bc129ef2b1078c5f798fcb8b1db99093e3c6d6b8d23af1f24a9265cbaaa985dff5815945a43210d25e5dd35f9e672b87a62fad412488c254315868ce8151fc680ed0d812f1376f10b8fa9fe7fd1a54c4f68f3f32394cdeb48de8f6a0985ca3ffa268229d041fd4463ade6264f7d8ef1a07284bad8a08205a72f821268de7a691d717a7bc653013ff96bc4cde5d087295f0fe43333562cb1b07bab2ed8b4e6e44ba85d163155ec1230099f3a33b6671963720a41196c29f7420d624592967930eb107f79ef3b375e7890988fe9ca05466b724506d0fada836c2901a7affe3b1597078beebcfeeb69e586647a651b4ce1b9728ab3fdf7ec8e4fc4919d330c2d2fe99ebc5b82c0198d0c545187400dc9a5a903a7707c77f14d6e636f67e16e6cdc975dc6eb7645a32442fe0903396e820a7c72c913c81d491c5eebee89e025cd2340d38ed17690ad0a42470d137d26afb16e72d9532a2c33d7297df8ea91d6d7ad6c15bfc28907b13fa9c953b3b369a4eb8253d6356fdf1d5c60e54e8e8acbcce76544f4a3e1e533e124e297abfea4ee507d5a6b954f33edc9e4714a417c813064d12832a040a26084ee856805130d5757cc72eeb7d512d9ddfa28d19b87692f05510bf41926258df70416f8035ac482d6105b9111ffecd4292ab565c8dbffca901b6326f12f9fd99061b74cf4a5aa70574525636929a1b638257f9dbd3b63b8c3b437ce86f8a7341d5f3f81991dba4d04e972705baed7bf85b225c9cd8f5721c8716d065e1b8f982150c637ee965e0489ac7242ee607f7a07c66fe444be050fe802718d94c7806a8b0d244c01458b24fa2e72093d01a470c7d347bec7fedf4b427df69522b39a139ac95ee6b0e369ffd85d5f838ee927384d934716212bc93612ea539f0d9df30968e16ba5e0c6e523101d45170898ad2ec7cd246e333755fab35c03d958e72b7050ea2f8e7839fe992b1a72f390776d3704295800a2a8b516657d1c1eab395a12fc0aeaac7a1ea54f0eda2c3f205e2d97cca701c528992b4bf242f83ee77a5e8bbd50d7e0bb58d6611cb36098bf68a75821520c2358c5f7a5593e6318412d1bf2e33505cbe95a364b75697247d37b0260c11fbbd699d537474aea434a742c1ed9457249628c59dc1b07d5725d2d28192f623a9c894c71b124db42fc3427e58f491d4e7682185cd08e2ed4070643a314fcbfa14f90ad3e6e77fb468641efca4ec5e8f9583ba1b278849a9357c118fb1bbec40d581d1ed812897a44a460ee3e6fccb20164c6f6a1aa3024927232638e0458a79c871593f870af0d243c66c26b7794379e7ca65dd059191a32727d9ac3b0cd2eb4a345cbe0fc4a7d83432a2b3dffe9a614ff9b4070fbff46fa7277af273a8d6a228eff14a1ac235ee8ecf15755e6ba061c367c0600fa34ee16720f4e97aade2a426e2be3291644a3fb2bf507bb2c38e32fc7a6ee97a254b55c727c2d31202c8a6e67a26648252116c9e03285b4255e7a2f7c658fa50495d4d166b1dd1fc5beedb4fad3fc1b3ab3a5348255c19836f2ae471395bcd9c48d2c51721a02fa99eb8b7a5c67ced4102b67488ec64d0ba29737f83469c77a221b5d7a723183d65b8d4dc8f4dedc2100b2fc02b27e3af8f68426fcd0ab2c416be75a7527bbd880938c1a41d40f42fe9818e7aee3ceccb2b893c3bae487bbfd09f3c91b72d199a81640d9f4523377b1d07d2144ed6a787f7df29a1542f43102cac9de7172cc50e84f25daacdd4024a35357d34b6a31ea052ecb8f797b13cb748353bf8c310f74f2f62a8ad23d298140ca15bb3c2b1531906210ec5d055616683161b2cc72b9fb71e75fd94e72f491c009453ca1614bb334d4d98c487daf85b5c87c407245287441b3db125b5068df13b0224252e0584997b8b47a33403004ff1255b2007295854b1591e950adb4ba999389fc026de1eef41917b7c1c4f7a441a39e5df37227092c507f59c31abd380fae38832f23af2d55add6d6618f1f24c1b3cb1c5a7263a3298e61eea3089feb683d9c38b01fdd20592580766b474847feeb2150a7720f6035d53d2b571a7591d8581b98ef1a8194f1e89e98830dbda8b0d5c51e481ac895177563867e997d69f6e2d73982bf7d52bdb28fc996432d425eb2a9ab432e5a409654107b8147b5ceccf8aa9222427fbf72a43de73dd0762e231781904172f6420c5a46ad265c1192b6639e2f6f0ebb9f435c2ebb665444d3cef66aef1d72c71af8e48650803a23deddf0072792ae2beeea3a23fb49233379eff27988a0727726737d6a319c3f58ba8402cecaf9337756783cc561a361b55f90e30abfd07277cea6081003d9bb9eb0a96544d77e54b531a1f4fdc347ad0761b43e3b2c52724e16c5de8d9362a591861de0c7ddbafea9437e40240d188f8d4130b66e7398721c7caa6a03d1ac87035cf8dc954e5002a5bbd7fa21b60a55efb214d4abd0cd6d4d5de5b5b25bd6c67715d32ff1de8a576eee878ee273882d9c03524884b3b23cd05a6ff97c857cac2651f7d600a358c0e26e0a10ef7df0cb6632f7115de8987212ad771a492c1140eb64b9809d14723c9b53ea1566845e0307c33a6324ef937216b7079ba3af694661ca6e085ddaceba6a745d1282a7e43d99c3aa3c14359e5a43a3ba34e3c599304c280f565e081a7276a85187495e88cf42e3951bd0615049428a5dcd282be7495442c3d971412b44f7e34c619ac399d267c9745b534df46234e56ba720a6d4a2208c16c2552e85114a1d96116af300533d2e10852727ab7224b85479301064759478f825a0e7a27573fc73799f35d55517fd77b6fd6991368c1d46c6f6237847be24d9c4c7fe1bd6db08229fb419b20a73b0f5974cb90472a4ad69c305624cd4edeb286109025d8d7ecdf75cae85de5d76b8d6c465c25c35faf939daac9eb49438ce563016f66719e3a30569b4375dbb1f93c2bfc5453972e695993efa7242561dbdd4bd1c4bacc86518dd2083c7da3edaa9eb34f30ceb72320229cfd5836982af02d9e7a3f9166d1aa7c29abffe5c682bea651d3b1b1a72271dacdf12f5f7a154710d585289da8fd76b6ca3f0e911b30bff9b607cb3fd2788e994901d9cab08d08a9e79d83ef50096576a0a12d6523d47c7363576af39086fdf6234be030f93c8c6ec9637ea55909711cd21c83073e1f0cfe88d76e17272837b3e05ae6d183389b1c9e3e408dbdf786d1d928907a8b562d80be362d31e72fdf10b294ef04ea0d667ce83e0c7d6025ba616b0fe3f35453add06c7bb546a7265821c0bbe00e440403c930a58978d7b7361d6d2596bfaaba8f015445595797263eafb66b45b0ab058cdbc34af7af42defc6bb71564faaf23c75797e5e8707586b8e49bf44b46b9da99e1a34d1b81d5d20b4628b96ad0ab7e5b6e2cfe515a272f8ec540e8b0cf1b3ca90554df903479d86f2bb19206f1202e67875514c35703c5644f010d8ebdfa1fb119522b090f04cf37f6e6c4e669533fc5ee6efccc8ca72385a9cfd3ad767779b07bfc8e19880430a6cca56c709a885201668e8ca170c724a8a86bdff69d2389f7f72dfca91aa82d1dc16a00bb8689f6250791a824e8d72f8f36308e449ba1285e925fd5c5c56746dc82550939de1edd52db15c86e71872b4500eefbc37d0ae13b981c49aeb79be273200d35a97605ad41bc35b51ab0972f4e2ae83ebc38483c434be4409aecc84c11ffca6e097554274d132af7a34f96668ebab87f5cec65aa2247a1a32b2b3c449da76d521ef2a5eeae28608d6e11372fb619c704af8e4559833d14c357355536f95d404709eaebf4ddf027044f79572ffc15758c643c531d3476b650b66bc62c32d4ab183f061fa6269fe0f3a738b728341222e19fab77d09d8ea92903d917fcff5baffc60db355900494109c430404fc85e90d9d0ed9203a56eac9c2531ff30d6f08083cdd58379789272ce422dc72024dcb2a3f7e39c1905c907b863e9012d3743f72e4533cd9c1fa3796b69a4a72d5ecba7b6241b60bb9ba8c1bb259f006492aa7f82f46ae29933661eb882d2367d6be300447096d88f318bca8f29d44b9ac01e59278826a7076b610d0f2629672365f004894cf0a87b142d581ea503e1d99c3d16686061bff1eee5dda0bec8872189d64a6104a33a2ec1056f7153ba84db827e09f9327e001bcde3b58bdd37d720813ad9e8f785281a0e4244e7d854febd9548da887709de994519ec6d7516d6d7565638bedbc7daafe582d5efb427edb3f87a65dd696b399b3cb7bdde40606553c84de1d24263a73587d81e2ba5e5a2076801a2f9b12f921304d8beecafbac724380baeb29cc5294138fe7d170a7ec396582c35fe274fd7e496e8bdccf7bde7206d65c2a6fd5fa10333a912f3c60eb5bc93631df7f403619120e2f726ae798720b31342f175808278d464e129eade39d9d28cc7414b4e27de217aa2d0dcd067283d230979d9ad1d700d98fa5c6f89e24e33cf2bbdb46452688225b6072da1f1e3d01e57182c0f708a201c61f8fddffaa311e36cd866cb847c930e819ad62b772f87e1cb4a988a08e4b51272391390a360dda1a1e49d7c246d511fd55cae6cc728f9d1ae85b9d376977319588ba952e48c95e603a2a6ace8c33a98c5686163f7210c0fb65266480f284f42bd1c5818e411cf5f9575562be53b265683c311a4671659fa669735b5b4f64dc9e5225928b69af8cac798952971d0a627afb5fe9f3727d5dbdb49e532fb429df4a8db1d025114685b25aa2256be1401138c35f22d772cf1c8f060c3de2535ea5f136ee13298ec18682559d92a365eca5e8e099c8254a9d4376b7293d105e7e5c8ebde1b56bc5f8ae85fc4e0afa101f4288b89f27921998218c5cce7e2af6aca1294b7851cdaf6e7d075ca5851f7362cb7323a29e7b72514e46021da3b88090fa07f73fd934627a8c64206c9738923797bd48ca36c8524ae76e082c0cded935e1e8a2e90960c017b50cc0c449d954b74e66270b71fb72bfc9502da0316115c438f0be4afa3c976dcbc2c4213b4be57ee2a7eb03110e5b3e46bcbd02a398c57f98a73544bf63e3fbdf556f70007646518a1d3c1374ed72f59c3b3cbce3858c59003d54236f25c989b8a10b30e5b2df23372f8d976f05723fda963c9805f328c342a322b6185a29ddfbf3dd2637e8e21d1c86f1d0b22672b22d22a7e45be0f0be1910b8ff7d61fb3cc3243fa7376ad53a13586a3f298801f253262f40645371e0aee48c129ba24abfdc2c11ac4dc061941d6490dd939e1e53ffa00986c4913c9572866f86c095c92d745823e7c4f3e8392238b64c63dd72364b9b2bd934580159b5f4791c3725ba75f4c2c6368de25f76094746e082e6128e58a31494e83da370cf4e8f55f4a19ecf6cd597626b3bc3114014d92f205a3b27da0ca97c7da0754992a5671fb858f3578dda3b294b03b9106cf173d1260c721d1b23d14d1549e4fab5510b042bf6c7f3465a8207afb54c318b776f10cf4e72a109b83ebde593510dc927215c180580034ec4a66ba0d7f4835d80ff117a52728a7bee12dca8404a267ed8cd2b7e410c9c4a5b73ff3226b92e85e486d13b9672d36642c599ab7fbde06ea0cf8556306445b13b24c471bd2712dbf09855a771722053d2d215b04b6466d39e18a63c157dc044083931964c8f459bf508a28b0872c6bde0b93cd12ac3c4e2025e147958161e5e8bbaf60fbb8ef461e6bbebe7d72f411f82a350c949fdc8e218357449228889e7b0649a4d511ad653ac5d2a076708c1dec093686a09504ce02d08420f5f111f7e8ca7ebf904d4754120615109ff372974b06ff57bf74651714ff2cafcdfdeff3d8368147a49e9fa8f4b239cc78e6cfada656fb73a2b10ac4812695cedfdf56f7b920367b9a2ddc2b0d3d9a4855a72368a2bf258289e1ed84ee4bd57857f2cf41961febb20146c68bc53ef2b91691d64d682d6ad185a48bef25aa1e61b93e61cdbf734a0c062a00681bec6ad178972789766bafd8d3672315da42753a4d803b4885a290763334533dcba9bd4349a5f7dd3792f8ead8f4aaf035cfdc7540888b6b3d920212cede8fbccb8affdec7747301ce9e72ffe78e26cef62a2659f1108a0fe762008f75a1122aa8d74d93bae720b80799032530254b8e53907749d3a5ddee6c080aa37f0e334fd33112f9d5972442f88b7cf61735a99e5e0bc00ab2cf3551f205490563ed18f27d805147ade72fd05b311f49610bb9883f2d789c06af381d8b901295b6ee0aedabf392d0bd17200b44c71b13a2f8e91403650fe23a8ca37cb2efd86710718e812e278c2d8941e0355c3e5a065b42d7c72d46edcda90ff8a08d69ad9ce9886b472a0dbe72e652ca2c7a60280f6e14c5bc047a99a86b90a6b2fcb0515361e58b1252504b5c36425e3f68d6d1d6c3467c0b20cdecb79abe1da0c40c064f66b4ffa45b227b64dd1720d1a18663694a2e3a79c23cbac9b4b7a5356a30d633319cd0b7d2fd1c3782272941f4c04bf7489787f3ccb6d59204bcfc309ed39b00f8582b314aaba9e686e7255b717dc66561b31dfe917108e2da0911d8c21158f53612d4edd3d118a3e2c72f6147b7eed64136ad681e02d3206c2281729b5a46dc70962939b66c1ae869372936b420de43343352cfab44e3f495d62bf6ec960cc1ab45fa5921b636cb96546f4f96468bbd8e3bea42eeb163c09b2b7a7e87ff25f24c8feadb6261cea420c2273972ca3188277ae0f3dc162f90e0b5399f255710546a0432f689cb6f68c69728d9fdff69dc2c0c5d12b5eed4d45ccbf074b2e4ac00a317f27304feebddce8720f9ece143cc52c35a96c0225c54ba05de9b2976927dc4683ff6882492a004d145805468604ffb8f7d8ce200006b34f170be4c3de982ca04d06c51cc1b009700b1526870bc9986ab312363640ccdeba24e88bb9495427dfbefb6a300a3e129b723e732b35a0707d5e8bf407e5872aeb96372093efcc798d4d26957937984b2c53babceb07515e00cb7a5a7dbed411bfea82fbfa50bac63b8b104593d65efd4b0f9b187a4711a4735fb0de69e0f76a4938b6b700a1a48f5a35e9e0bded1fa06c728d6e67e5c7ce84be7592a6bfc0599a6fe158968915ec6014dfcdff79671096721c9bc9abb94cc76bd0cbc0de5652c8c33662cbc022e4d5ba1fb4de4c03d60d72222e09831c8c1d2a52dbf0f23b6b80b29db6b069c5b1c1d1ef71496ba0571472ffe3c58f6610388456436f51c1f86e6229b4b009388ce01c28374b5da18411721ba07dec40418f2a43e74d7a10ebd74a33967ff35b8cdf649b2793634f7c36720681b7564d3cf12c579c7e315789a85558a78da90b7cf101a4516f09060dc465f804597843a562c27d403efef33f9ecb96a54afebc06ae3cd156cde19a6c58149cd9bb9117f2f8f30d134da8e194fbfe573f4c0298c724dc922f96cc2f3e88722c9fc5d8335c8786d4aaaa1612da1fcc2a7925b18035b8dde0e8c0758e8e6c2446289c5df211ff6ff2dd25b97d969238ec12e943b7e30400ac94dba765eaff72e8249c1fbf74157094fc95b937e5e396e943f30678e70da1293271513e3f6972e67bd26def521832afc5d494fb86c2debeb757abfc568e28e110c0ab4523357281b6aa945c4fb6fbcc39ccf5133f5c44741f789164c6c846dd6300694e9fd172defffdf9ba226ecb48e7bb8c4074319dd0ecd9bd79df7ac2dd18190d290cfd5b917f3d1dd1f58c2405bef433ac421bdf04ba349ba5c63a714eb72b154f8a2672b1c9b998dad768799f7aa13ceaacbbf2ac759484ea795d8577d6501f81a70335a4a17842481cfebd8b42e1e38ce925ae0c5821faa0e06bf06f981e01806745724ce8d2dae186f1c750ad72265645455f64642bef2273d00921f45d71f716bd72c698594b676d43dc24c1c6f62f803cdd2d46a52cbab19bdfab04979bfc77710ccb5be2220fb857272884f33355b795389abf09a5d667d8eed97f4d26c0db2f2a0d05dfbb8afb5fe75e96e1a74dc05c5e07e83fb54e7a572c247543862998eb721f5c6a10cf4422ee86e1116093f40eea34edfb694dffb555445ea59b33ea4c729249a02a8335d9916d72fb155fb7c8b29cf3bf872f79b624d55b8d880d7777723c2cae9f1614383aeddb37ab8ae09ae4dd3772e773496144c6785cefe02a8872d636c42852db16acaad50342ec9909b78b47f73cfb2d35b0828ce7c95819933c9d7cf653738575b48069bca7d47fd2b5d40e440d830f380305c34c8522ab60721f81678af77eea09c867d724fc93bce403e8b05377ba7ba517faf86945c2b7721c1f8282fc5728a1ab167f89da90c03a8daacd6175cbbdbf4b00c77c9137933287d48b4e48830545b5e88b90dd2e4d85b02e11efa8e2350506d859a1622258473b77ab0a760626e84b0cdb971f83301873d9981de8cfeffc61de22a6a3f1c172270d9055238a0888d58f0206f66fca3fa2e3fbed0bb837e50512375910781c0991383e79181cb0f0cb4fb19b464e8709ae622713b791416d2eac097afa3d1c555a9694278255654f082d01ad830e8e8658ffce539a39cd789617a31a71dedb6bc779f229bf46267ee511f5d20e4f35a4153664aa05d1573043701417d9d0e5720a7331054d7893f1ce1d9e6d864f52eae62d9fb8906184c92dbff69d078f2b415e413601c098f2bd5a37c96101025c412e27f29b107c82ccff0af9dfdc2461724b858bc5fbdfee3546a20b2050cbb9e3b58262a0826f4e0ca46a0777541e407261391c71e9166a98fe95d8230f58c23dcbf4829f3a9282c72686eb1a3546f84a8645c7a64f3d1713139741b746a1a499651644be52e00c69a8e6b8449efdec72289f2aa4094574255c480ad911abdfd6b2ca147d506aaee7ccba292e8e302b47eb028bb9416c60ee94c0ed493433da650d1e568c124d5ffbc52b8ef925691e72635e1cebd12ab2b68e4a5858c3fa639ee55ee00989927bb82c80194720f7ee10907b8cafa089c43cf50fd48201a43dd89757b127da52ab86fd74dec78db9b7062720cd4be64ea2cb6a6933975c339d447f78f2da5f5d3c38a3ae63d1376cda721e0d3c750c907d5c9bd732e6749f19e1c6b278a8339542e695dcf144b0dff154b5e6b8f61a0df86ba8534f276e17a2ed45c92f613412d2da28cb902a244eae729dbc62b5eb67c38bfd47c1a09b3923b647203ce81fe871a6850ad013a0a00072eb92946d6010af78a7b6ce85aaf184265cb60834413b953826da425cfd30271fa68e0f7925c2e57feed27958dfc45f692d7d538ed88898645f420dd501e1e47220f52dd8e2e9b7b7045cc7ce8d54ceaccea233982353620a40479081e27e001b84d23d9b8d9be0a57540bcc2795a321097c5a92e349bde316fb7afc646813572dd5e5e1642110cc1aa5bfe173d93e3f93183ff84f108b8f9488d5eddfa441972698318e9d760f5c77456bd5205ea05b6f4fa3e2ec1b71bdea19507cd28bce8559cdc1eccd190233df9a531fe187c7033e46fcd68c38e38cf5e6e6db111ab823703d197344c10c90c23ee731ce9d0b259a4a8a81751e9c27db7e42cfb3ef1f072f39db3284d16162451eb6d85095820992d1012e1f072735acd7295bf5f2b05727c6db6c18c7c062810aa3b263df5e4c1283e47ea152fadb4b2457f59fd0a42419e58799bc762635b1a50a68d71fcc40f512c4a28176848aacf1d69d25b96d672e7c22f0428c4ee9cd483a5a566b1227c2569341733a72f76e16d555b511ced46608c43e909e614d9d7f970b7ecc46b4d02b044b88cc6c16520b857823668af7293fa102f9fc4d6684c53d1b7ba79fac9bad846dfcf10f42e365dd8d1543cd67061edde6b17adfab0d696910e9b0f0018842be7bd3d936d7d5954a54175f4bc72aea62a02da1ac8c1bc0c8f379db0ff4d0fdb6f78bf16d907210b22393f6a9f1a91ba3669cae184a4425a6a6082fabd6b107338394da4cd629c930f0d0872b84039c91a38f3aeceb85287f487ef924d01874256e61c354cd5a32a735c4dea996e3995d51b9b3c005662f7252af9928750b05e93c34d793c7128ef9f9315da75728b02b4627a1af480631efeb7676f61728ebd182747b15fb9b6b99b282680d0726b1d2ba2929ed648255fef592b5437879fa9316b6a82d11a3c311ae9572b1b21a9ae050358bb9e13cc4c3b3bc1edf5999cef20fe5af3f3997fba88b75099811a20b42260b7631aef7df447d1af46090f984b58caf3971853e41587174475777243f070b5150211e402a9ed1e53c5a9e0b5fdcc21805f6516949a409041eccd72deb9cdf51e767d8aaf77c15b6e1d4471b41f7a546fbbbfdbf8ec27cd9bdb1e17fed1e73884f61e51d4678961a0766582f38f753fe14984cffd6e2da57520e1722bbb3118be6dd9a346b137df0094932dcf5fa0bab7a59f195196f29931fe21727b504953e679f7e4f784fd8c4ab441fbb8e168b1cdde8980075280d7a0686d72d9f393839688b93d03b31bf39c638964ebf3f71fc8dc4b378d5125f38c46843e1e0b9f1f116327d933d6c088a50065d89e87cc07292a84297108241ad02a192cc325abf5206923f5da74ddf1b49179c72c309b702f84a4482a70d72574fbdc494e0b3a8ad52910c9dde48ecc07b48846b44962f1b7107201d99b20ab308e7824094edf70b18c898fc5c610ea99ba5d5e9ee499ef2d0e5026c538786defcd9872e2dae71665b288a12d65e8341fd466361f5fc1ef3a9bf859e912709acf6f2f0261fa3289cd1bb87c366017922b1545a6367624a366cbd0603717ced44160293cc8e061852a2f59c9b437893f06c03d3d3b048a9f388fd2b8b5fa22dabba19572739a0c612499111e2e9b6bf5d1de5d54204fc7057e3577c9fdec24a486da61721f8663ffea9000be87d64c4b232d0b9083a75b27991d1b6ec886a2d8d0c0044fd2d418d149cb26011ace5d2d8fd35d0d29be2ad5b91127aacc05a76761fb2f5ac9054cb1a00c4b38e8fccbaac94ca1f62d34560e5fbf8a3db4b7cdd1ff5ffc3b13cf4427c4cbd6322b599d43fc89a43d07035f9bd25b5fb527023f00d3d88572f0c5c51e3510d370c51bac2095c3c860af7baf6aeb00debc194ad90dc643c2486fdeb0d00f24ce5edbdaa2a1f838fcebbe75f0e7eeb6002d94526230cd5e1d72115027bbf1aeb4b2e5b7d80df72e379e0b1e27fbff76fff282cc05928b9030724ecd9f46e1774e4d003735bfd81be619c806b377828a11b3526f3268cdba63729870164892a2ddbb1d155495561166f3576cd33fe51e5982089051ada078fd0f0a2e5705efb55b4f48ec70787a80b639f1c08c3db9e864c1ccfa2ef48672b50602c9f119ec85dd3df0561f84fd273fb27801c686e1e54e41a2c55b1464256d664fc83a17b512b8a5a56b5e2348df8f878a7427eafa3cb112dd9b983873bf9b72bd31353ce814a906ef676792d5e9d1e1278a582fe83add232bcbe83244fca87205570b44cccf158504fdebbb985331596a29919e1d7bd459a213720247df660650dea25419ede173f47ea6348c6edd929443eeb8d940d5da7d0e1a877896722d2d4bb186941ca418f73051da0921cb49eb4e1fc5e87a0819151a05cd7a2567727b2381464b770d42c9a17add0602799f03f5b44dcc07c7bfa487785a41d1430fcc9b5936d43844aafc551e001cdbf25d4c92cdd1688dd332347d3ee50eee957217219f9476d2ac1e323f3138f5f5de7798a1a8f0b3d7d983a11193c87781ff72179d7dd424582bcbfd071a8e5f41b0c4ee9498c08adfbd9d951330be984d69724297582465acef7560804494d5d3493296b4db1e5fe2b2b283822e963091bb721b7700f6d02b86c47fcd589491645190fcc76a409ac396e7348221fa51f84a72627ec1f0aae3aed89e8d71878967f938e775c6e8ee0c81d72a41424a756d0d28196cc90f6f86945e02e7aa5f161f54ddbbe692a38608c0d886abebd08de9d72a1c89f1716fe7ec9daff7b00470bc7cd99df382a383759b3e364328abc961b06436b10865fa78fc9ac8ce56c7bb63cd8eb19586ecd37a57f8c86149fb73b04e28a404a0efbfddb38efbb2d68ddef34886e492658bf8a3366782ea0250c02f380e6321cf2b4f4942344f6458d3d6f0e3ca38b4a39e4858d4ae3592c55a94c46472a5a7b02c24c6b157ed0616f7429fc0e4106706c07f6141dd079981411968cc72555b734cb623b821f2f1599305b6020ac06bf13d30c0855f70717a2bce00a67233de53e7933d86b9eb3e6024ffdc851ae66f4091ccfcf1e35d42297f054f1472eace6dbf01dd5bdfab52909d19fc2bc339d228c3ecdb9e64bca4e8669102a6722a8beaac7e06ae99fa90d37f70cf89fc87f743690cebaec219103c437fc49a72a83b6e30fc894655302af77bcf5737af11de1a80009c9c485fa3bf8e3b595372c7888cdfac7954ad318f096ac2c7bc1f6e873f31ce757df24b43bfa353be0b721c16c19b0554443f5e14c9de8956116170395749dd1c06d27b7dfd2143551f27912909484709229e8f934ef88eb491d0e3a5bc58801042e836b3f8f2fd369c723f6483baa0cb76174bfc0fbc920179b29ab8fb514d7ee3173a216925501b22646531936ca10ba7180547bf35cf989f3ab967ec3f840479317f5f10003581fc5792b3e8dcd3f9325d7e9b09eec822160d9ec527c2a9b2993d3ec0dc39786db82e0813e5d218dba58f2dff3746978bc4177a994a51eae81643f501182842eefa42e69e009753839b8293816e02f733191dadffbd44467cab62e2ac92ddb39b385ac0cb7c8fd5ea150ad8c4b44d7c08bd8e7d5b68bf1aa3f83a50fe9cb59853245453400b279796e1e00a217b996ad4958d94bb6e99a62cf52cabd388002352f85b7f3bb0f9ce6ccf8957beb82553db30673832003f542f71c88c501b4aba938b144c8d745546d64ce1d61e354c91e1bdfcf0ab7677125e9f5acd276ea7e4a0ce03121663420f39b8cab6536f29f1a94f77556dfa27a84ab65913cea81b2807f55ee725c87a51a8a848a0b0e187ef371340ae5ba2dcb561c594b33e2f5e7ce6591fb24f89e7a5941796de9a92e5d73ac2aac4a6aaeae5a7f76c2a9610d71495b434d12a29a4d58956b413097ee853cfec8b43281f452018b2a5488aa541ac5297724d5f86a06758661e426539d8a4d84486635a0b3f96515282b0c06bf9977e7e72191f0af0d82a0dccc215ee6a852f3ef2eabbdd489c203ea8a4048d2eeefde0723ea2c356f397fd4b82b6790719127e38a07653c8c3eeedd2d67074d5e7c7c372b6a6a67ba0d0e1ea29a23891008263cb6b6e71663fe46a99323461028c1fc7099254432f03679cc452f48b2134e9165bc3088ccaeb94a3ed9c326db82a03a756b233ac8741703c46c9786da2b0ab7ec24b99c5139aceaf44e6323afbb5ac35720a466412ae94e855979a4a505affad791a304ebc6287977bf286ade354f5e072122abdac4f59c46e085e2fb58ea6cfc206586357a8111391054bcda6d629da720eb67cebe802ba16da8f156a3cc4c483659d03d6847cd159fc6967cbd1c5d87219d1f2fe86bb6e1a43f1baea06ff6c275053fd2186f26b7d0a8c3564c2418472deee09be66e31e308d05d78c8b46e5f9c2f5355e3b50a7880bb95b0eeaf0f4333422bd88c0c3ae5a9348e4221c1277aeb787916983fa2facbab72133e48e90261c5672ad6eb2128bce9f5b7f8846d002f5c1f5b4c67f24cf291d617b370df872f28d0f85d970a3b8f0dec5aa9e1a349201512b13b16bb81463fe6edb965e4d724e7ffeaecaa3d2451205e63a5e8d4e9ffeab9cc23155ede52d57be0d3b23f7721a81e3c279ea758011cfaf0f7cc3adf7079137369508f1e8fcf436f80d33652bf9af4d9515fc5236e9815fba3502afb76f16dfed0fb612abb408a881dcb41220500dfe9b636ccf84eaf7a4676219b19f245276adb3f549f3891e2a7f0062532754ff7bd72989e92c53d9423c262d9973c467b6acdb384876753d5a74ff8c7f419943d91eea60d3b713163a91d8f56e8e36d0966fb945f8ac6cfd9e78643f4d722df0424f3020faeadadf5b05ac5cfb275c164899f3a26189d7ab7a3062ca3172ca67723bb35fc606fb3c22d5e997a0990cdc7ba58c9a1a15ad0294c250bf2372d766c8c354baac920a319894c2153699f8e4bf9420eb8d6b33b4443755dbec72a944423e148cf5e8cffc3b757719d9879f006b0ee32bffbd1255056c6050977287a72e9ef52939c2744cd551c20d379549877d761f2f05a0d87ef5825a94c6721c5510ce697609ab90e9b0c7843ef9256f1be8c2693e3d10697d300f4543e0720a2f644e4ce761e3c92d51d41d6a19b2221c615b21dc4ed946bff8577b92b672cb13fa6769536feded9de141e0110c07efb65a72a2a07dd00ae152b043fd6a72f6df88449dfb61348d64f2091cf7981df32c7f0857d9fbfa5e58213074f1580dcd8b85eeac5e47bb6f968ea24e2af2b18aac1ed6b18dfa2108a114fc0e1b785cace4756c6a0e2bd2473508a8a17d3a822024246a9ff9e91a6c943170ed4e632b165065149b55255fb985d6ae69a42fc7828fde1046eb768cb1b05527cf16a31448b4f5875afa483342bd88c6bf14b29d55e0ecc381204413db56a8e3a49e7f72c86cbc7993d572cb78f820558d7c0aa387d9b352875da0e11ac7e062435d3843c042e358964d05d33f981c01ac4904017f1702f7185ecac2cdaaba8fa91c5e5e0c9f549450d594ee17a1ff43ad7046c4b2f2b17f81b78350b850a25b8d8e2326f18d8f88218b1eb96cf447702f3346017750b38bc17ee4d91c206faff6bd77722480ce78bdd0a7905536471b16c2663ecc90198cb93925ec0a4935ac1d8f41724948ad4eef41f52d47a9bd10dc46efd9d09c400ff7ea8b1f0d0d4ad12745df6c444b81ec159c074bc2a6559dcc3161be101653c5ae77964cfd7cb33d4447d8729a7ff9d5378ba6e515e45190aef2adb4a1a7cca0e72bd5e00743dd80060c6f02f5b6eb8b444ea7e6fe700335313e817b5b164f3cad04abaf01218569a1da70240c7a7651145c2b91c1e8cd914dcb08e679fb1ac25cefad084298443bbc3068728ea7ad66010e29bf4c2a45a9a56dc3221e235d8d55fe7bd97a3c85f910dff672b4ae717d4d7b4f40ac66c9e42ee5e0ce5417bedc3e0858a659931ba462287b72162488ae7b0acd0ee89415c4bef1d25220291b799b06b8ff978abdf5ddf4a6723b64f45b9fd241b81fb971b13a6436a0e21628cbfd0aab6430afa65031f5a7009eb1201802c91aa2e1673da937497a322f567f7dfa4fb898276fb145f996ee72d20b7150b4b9079a34c1800adde036bc19da5bfcd73f7a27c1e7dad128d8301fbbcd26233ebe8a4ea71609408a7c8629d2299bffc1c3beff2dffa0c4c6015466ea4fb123f82b63071c2c8eb2e64e20fc02a866fb35bd223eb8b711c201eccf3a88d4741ff4d38fd89582eac4267b9ad81c04a55dafebaeba56efe5f1202ea57200b69f37a9f1d8f0d5a641da3291788b3fc1d88d3ec51b8b79e7b83cf72feb72e490325df9ecc5b7431d0b076fbdfb1bc7ddd4568eec40718703144a45cd663530d629ba8b8cfe284d6f6e67cdc0dda253e8f16a5c393166829af107a503ad72b92b53d826f55e62a3fa9252a92a884ea323927dcf13934b3ebdc7a093869072bcb01055955d0b55bc12bfaa79557c097f13c8fe0e057e20b35f4b431929f6726a540320a9910d0c46b3a91b8e9e54aff92afcccbe2904cbc8dcb957296d5b2ef15ea46d6c24e78e0ca48b95400a30b30302920a665a45628bc568a696d136721b3af7bc0218cf8bc21b04f7ef54b221de33f5e3dfed90f18f90b0832c13167203cacf0f7d567fb7e236d2b9854d36fe96fc87c6bdca5989c50e45e73691df144099698c35a8429b909bd9a62685d3455517f38562a1156aabe3606f28626e34ce8bc78d95ce4f95a2604ecdacf25710519b1fed803c2263d429150d0dc173107c0f7e6aefb8dfa68074dbb338b8053bdef1aa523cd8f8294608cf84bd155300eabd5c7b0fa85ee41f49161ac95a9ea0484e3b1d2990138b57953bc155ce1c72813b2bc7390e9e0e243371627ddb90ab27f693f71f36d06da3d2a73fb6751237010121f0c6b6af649ab5757d40f0bc76c56b1cdf670c9b6f9c3ec0a9d63c4e7242b95b8b752da27c1e10acc5d4784394a100a68d766aa5dd56a190d165df8a17db24288ac67870711957858521f6c35c1ef5a70cc328bd75922a89f6b01621725860c3ed18cf7a5de5876d5316ed8ff252eae42c0bf238464afbd0cc3ef7d133de7bed91b5a1330ddf61931a37ccc5fe4974b958221eb7a986f1bca9e74f1470a996302de9938cb2cb30a342e1f9e02ef0e42c5451ce896ace08249bdab9d072578fc0f817a3cdb4f831b386badaa9770d8859e2cedf7f1c9c94324ee776e972842f3b9fd7fd26451c20c2237e87ceecf4d5b7fb0fe941bb8e05151debe8c37293283f1f294434c60533c04a6cbb3c465831e8054de37d570909d041e620d94508811b900ffa2878b923823be40e48bdbc8290ecbbded5e2246459ad7e8f347255347bc8db8c9f99dfdb4dd6b8fbc937b21d0af126cb0ce61201a82562531b7298fc467ef8ec2d5648bbd9f781b99e995c06e137ed953261844f3630cc8d5971c2c180d0d1b918d602a03b000be5886dd35eccb7617ae793288418f169070915cec76ffc7f3bdf5f91b31ced4c6ec1dca342d3639df94bb73babded9a52f6372a0fa1a683653c8ddb536b07fb55078276a1f55b1046f9dd08261cd9e84573c72874a863c3ba2e4f12654dca8b2cde42558fcebfd8e1ec35b6e022cb3a53a2f2e7f233fbb18b221241124b6822765ad98480672d8adc6156236dd244644fc3272486b8763f30efec83a6acc1c1a8665cf51d7d9e38179940bb8888b7a8b719e72b31e58234c54f70164c7cbb6be7f0ea086d37b2b89fd7cd86c27bdbecfbcbb72843b04f1e2d829256a20336e3f4559fa3621e0b42a070aae2491b6b0a55561726ea0b55e2fa792e8736b7b46f013692b99b0e56341890ec08e0f13431e6eeb6e0b90ce9d7b61754714aef0fd34114b82f24a786b1101e6d227f7593fbb5ff3727717a82710f85dc2032d327d0278c4a70141db0e0cfb82b518ff2fd726b37e72ab2f282fab01c44a8ce687396d22d595cd2fe8545a2686a1a55f024522492572e92cbe8955e36f67126ada479ea8632d780601cc21d2b1ecba3b04b1524c23722f05beacb3c92c5764abad327017ccb8dac2f23fbd9e58ad879244733fb10a724d1519c35831d707630b880b511d6695e1d7f5cc86b5d0ed5852c4867dacb772ea227bf2779e9fc7d45465f9b1b4817eadfd7e4dc8838f4fe4e14f04b21b186005080f31e71f02b0299f1137f9cd3db0cf9c9c49507ba0bd0f942d9f5cbfd872f51a566d00e329837940667884de430e60507ad40b86004b1dfcb913430706029a665d536b6b196be8329da2ff0c2bd16ce15b71d2b67daad4a7e3e3d7895a086b0dc56a15ba352224f141764abec49df2451ebd4ce00fd24f980e5e272c583d5e2493b1919d539832d5bc714a3de8a9cd9e7277cea94169f240b3ec7b1dc172c9bb3741570475945fb3417b9a8245ccd6c45014f80e1862531bd4661955ac592a81c1a97bde9a44e41630a40810e1e17979828961298340addc126ba1b2fb72c0421e03a2fa596b15b22481a3a604664b2ecdfb197ac575a00fd18b7f17c3722639c998b2bd11dd9922244c388ef547cec2438da66aab987e3935d19321a372aa289771566ff83df4a620ca7cedd0ef5da02fbdb93ec2849a99ca08cdbb91720b09805f864276c7e8c3130f77ceaacffa24b690c2ac697210ea9bf78ebc9b7233df82b8875098393e0937626d5ce7bbd2956640d0f446630c03b74e03d8ec72558ce448d111efd09112ccc713717575e195c6344b6064c46923a3ac6fc5956055148cc313ece93328e8098c8af246c61a49c89c481bbdf726b6107b38021a726d8a7329a06fcef3d29e2a090b553cc4982f8c82d5a2781caf7398e38a7283333b4cc1c6d01bb93c14be881c11c03eddf2952d17f3bcbbdb21629525f53933727b40ab6f83ac4498305184964782fa56ab02224aa267c3779e91ac145cf15972a4a70fcdf1cbe438b2d445d772c5e38ecadaa6232d3287c9b37c70527340127247a6db8af470205905561939b99260973042b54c798c1ca786bda2e853a936497b50289c65b1bf0054c1a6a9056f7986b10fae3ca5f6e5cb9a6da712cd5ae90ad19c6cd87455a430fdcf73e23fc4a1f3f4c3bafb6cf50b611674385f12a41e36e576e5c3ef69a4c7c6231c87912183da1cd4a48d614b51b945d81977f15853726a9499e879fea8fe4c60e00d6218ace775e81bf341bb7ab218b8fafa71d581725a8fc2f04c13404e187e00e257a760367cfdcbc61edaf19a4d78df3ab6050472764f6555c1923fceb5c4b7feb29dc41293d16e3632aec6c8bf5f59a1adb63e72beb30744241bab050e04daf2a142336605c97ba6a63e6dfcb714cf19cf256b5674339a125035edb3adb600b28d609bee623ea3db2bfc4feeb2b19b4e57a852720d0b323eab3c73d61b30aad76928c4898e1dddc046202df76a90b153278df5724eb785471fe700ea933b505f26dc4589b56af82c998e033e743bd83eb430b172afa886a3ca163cdf7989e5c94aff19b2027fea49befebf890bcb257f69fc5c355c7663deb1ce4ad207a5e6599db784ce3a4b2bd613d7ae062a1f4ceb9364137259eb834332e28d1c659c3a383580036b8c91d1b1352b24a22ae7fafc6a913e36c111df962f3beeab89e0657357c778c4ce38efa6be40dd7ae67c45080f412b728cf80c595659fc6d884a32dfd181eda01c3a75c124009c76b1d70756f1b1e2725b327195f7426d24bc4261ec0dd03a955b7647259e6d78db5faf3ec563c2052b5e11bafb9872c6fbeaf5fadb38970e16a826855b49a8825ce60736ca1d681f7205402277bab7fa21580fcb15011755f23b581c181784b53d4143f2b534753672bdde342f1141db59c5608c4134ee2210959f8dbc44a7ec3092f2878a44590d1cc9883fc57b2e221e562e9447cf3796fa0d37d3ba6116c00946b6e5b2b14bab72ca0fdf9ba24b174bdd3efda94126fd2033e9fcf3ccf1434254592e42d1ef027297b2cdac011b442fca3bedc8f5266d24bd6dc0b9ae4d6ca445fdd8e55617fe728535e31bb22f30f029f8b578d8fe201dd0a2daee271f8c1a5bc75cbbb74aab597ac216370fe84f387aa971ebae0c797247f274b88172e46e31affc4f4e88c17294516008f2dfe113a76d135687199cabd3cf5d9c9952d451c22131cff48cbf72065260d301dfe51a4a11355b7a0af7ced29459828b33e5e7745cf616b09c39720ee24eb3de84b31a4058372872ce231868040e5e5059e36bdd183e8bbb311f720a7235dd11a1795628d3662c64958f9903ff85716a3bbfaa91c7db796c230a2cb4113d27dc844e73c04bd176986b8e98acc90965da622e78d087c06d832ab872188736ee083a0ea41676e0059c9a7794a8d675c91fe6d04eecb914f894489f7205f83ba0a5f87b4a244e177ddaa26a74a2748b907537dfeb78a24816cd8008720823a7a520119f5c787cd6792c09a83fbb2489545d87bfbebb8266cc10cd2d67c0746b9d16370d1bdbbf10399f1adab7e58878abc4eb1152ab833887d8b06c72b758f43acb0e21365026a63d570f2258f1f6361333aa33b0362ba39d4284d9721fbe78fd0dc583e8a4ede55dacf199b536670a1a89f8699b0d44e5c006d17d72227a6a91486bc63b5f99cd174259e1e86ee89a5ae4cd4b1c1298ccc5e8909c729d6049f71e20802aa8606a3830584ffa923fb8f705b6ea34e135f4378472b715a5f4ddeecaf872e4f475884e7a4ef0dd243ea6d0f03a205be2e6abaf51a78b6f7fd57d7f145fcea5211d2d452c4b747552ee0fef3ce17b8a7ad0a306be726872ec5a36bd8dd2075959936ea7e184a04a6c1d807845f2122a20b6c323eefa9872342fe5995259e4222e488b6bce66075c1c6ca18fd12f36a26566d54bdd66ed027baefad17f28163ce38ea3282ac85fdc6b4d6d0493c3b4703a1ab8221b880972e706f1e90307488356fb3a9f084dc051ffd9e12fb17dfd0ce022cc345dd95d72855e2ab081d4cf34e33e65c44b595152edeb760edebf2baba8d123c993fecc4fa92572eee68c06f38ec9d4d3e2be6534ca21e2034ae960f50ca391008968d5722e943f02fac4973a2d03ac9cec1a895909da2d88f363dd7f4b7c8ec73d73e528a822c83ae4a6ed9a707b6c0e0c286334d3ba084a0eab152911eb3154f2e90b52fe70e3c820cb38914583335eb742ff13c23a9e037bd0550313a0ead43519f572217c1bf003411e6f76834a553e1a0dec8bcc2fb10ab16ded49ab8fb83ca1b8726b64081ae76b77f69e645c1133dce0ee1508326f3ff5ae14b229d392a0ceed7265af5f3360200d42feb7267a2571c6937fad6a4581bee216bd9e8896cde34e7254a8feea085c836f8503de5b29ea5d317b884424cafd3bb3259a61fabaf7c02554b6152106933ead7fc944fb6c9f617c7de535ea8df44ca6f0e5754cbf7a7e727707c162112eb093e3d071c5a231966742e21429cba525b0b06ffe44a749ae72b3fad47146dad82fef07042bf19d95929b52e60768a48d5d6a9f9ba7a58d2d720ae905c7770117edb9c103b53056e13769b59ea224fefd13cf9d5691667af9359b12ce28440f4e8c2277997a2f0949212e0bcf0d7a5f407b2e622c17027e8f72bfc29161e9f2e55e27fb88ef27a32ac56ebd03e0af78711a5babaf9e1fc2e6724c251b502285ba5e7aee947519ea0eab640fab1540c0eddcf20d0d9aca57645947c8365a550101481ab445a17929249c36db86e85d2b65b33ebd4aeabee61d7254c63d0e8967a23157d8915231ea66b95341c22e5acee71ddfc71d76ccf26f307e3e9235382c02399a2a4a17c58d7af3b20c12fd14a1185223913b581d3f0972bcb8558d2f0668b8e2259ca9bd64de853e9f9a5874ed459ae30bf9fba96507063915c21de2418a79ead034f33e43a47a5cf84f404331f56045c08386a7137672e3e4d07aa91aeb316ac83147f6833adde1015b9db3aac1a62c6bb26ca66fa2729ad635f20faa36ecdfb0a9752dd9485ea0172514c5e9d1fe8b29c36fa1ebcb72e88cd68e56368f679fa3d26fcfc55f207ed9aa2da2863aa56b9d152bb9a71e721ee2f2a7d5235450b84773fed13601901395ace11960e9f38ffaaa042b672672f9764d0cac83bf07f8e5c59ea06a099f234ff9b25c88501f47122d90feb16172d040cb0157135d15ced736af40b27f4ad881891b5651f132b128224bce48e170bbbab87976922950f2e58a1e230d69df5fa61de5c8c6540202a08c44c9680a48cb06cb61d496e636d332125d1e9af23961ad8ee029d9e204aa9078c7be02657284c23a4f2d17c85525f64af4d75b48117a1034c48b463f77562041fcbf6d0c145907b9b9ce878c0e3b795c6bd30fa54d7e6209d8057d1e4972d716e0cf63ec72a2294309570f331bff5bdac4c8fccb0bd723e7c3d950b5a550d2f38833f05a72853a35b986a0eeb5e390b94c7ef2f81f0985b496161f3bab8f5fd9079284541a2e47ecdfa25308e973ac17ffc79ad486d7435f004111c306ae252c74cd0edb72e2261797f8d7c5da0afaf9d4afe2ffa08bd86c55db2022271149da9b1c66eb72202a86ee1d66b3b1eb8e661a61af35cc669de1ff29e6fc32a6ad5492cc9b1f72371df5a488eceaa13fe5dd41339efc3788a38372dbb5b3e1d5b10fff50bf2f375c162f3f08389069d4f5708e3bf35d58665bf5dbd4bef024566b009215136a720908611852d03d28d5c743f633e877bd3d8c5b29d85b46e5b6b0bf9ecf8218510b660f2af9a2186ec28e3a2673d0c0f958499b8fa976da469141db22f054e967127afd14efce2c082e43f2e194969b86b49b7e1c739a2df429b174bc4d6797726a7232985c2ec0e3baf9011ded0bd00f6bd58e435c33b36d54897b55740d1c47a5042a40f6db1451e13415646fcd5567deea629947c1c7c3855b0534ec788d72f44697390fabad85e3a6cd3eb628594be8fc81be8b0b83634955e3d3ace1ee2dfb590104df4dc9fa1d68af8f4cd371cc015578f832e3f6a18f1f4049384bfc0cb9170da2842a726313a3bd1b5505f97e92df8dc2480e0a6f69ccaf82b8438072692d47968dc4e6c92d847ec4e5008259ebda994f9498237f7d0937eebb62a14c8a8e9219a9c31f0d0d0c2bb8dafc0a96c308d81dc26bce42e0e5ceb247154872f41d2b2ce5ea5f730b0297a7d727d0d6cf5cc94aa5f5343575783172aa553c683b881230b86eed54a35386f3d304cbf32196985771b6f3834a876b7b7fd37772b89649ead7eb87dea004e799cdb5f2e79e328c184e75874473ceda55bcb66708597b5ea63b9ba9f8c9cf5cdc632401f4f58ac27d630ec7e7f128c67ea5cbc02ed7cbd67eaf6b3391b8a6b301981921d4ea31a873eea87f4873cd77ad019da2728a09d9559fe34ddb667bfc317278233109bed4e6069a382c501103e93c8960729b20f40fc35734d0d1625ec21c0e00ba60906d6e552407b4f3668ad2edc2557226574e025b230cd44bcf2c020e1ca8a428ee54e4dae2825341d6c4fb398c2a72f00d797dfc91c10558179b8830895a8a4b689477ad40bdf6911a4956f4108772949dad1d54614e2cf4a8dc1fdf2b696547e03882aa8797eb0182524d8ca94f577de5ba56a24cf4732020697b2b470d23a2aad4bf6240ae842f9fe9db0e00db726345f94015bb4c8e29caccfe2658aa1619edef39ee888d6e19ee65ab3c84f4727f6fd1e72bd3f4037f99935d32d1be7a33f2cd670499efa1c140ab079c157a72ed05ec8edf37d1110c61f370f1a59999ef3a7541e36cc6f634b2120d6e35317208920e7eb0d8eb411b21f8bfc24ab9accd5dc20712048bc692c34f5f18c6c772c18d2a21e37607122f0464c0c56df8fcab1cdd21f9edc14e287315122ce17049bae1fabf2d24078db5d7e5cd45060d09e9f066c2f5052f7890d4812d38c4ec727027ec9f73e7ca594a43bf8363eaa9b76cbd044179311da6476f24323ac8667240c02f1cf015ba1698c59fa3ff2ea492d9e17697f1b5f5accadda36304c3d932aea34246184ff2d753827422d327569e5bf47971b26362bcb8f4f998251d701ee6ed540b71db7ada882da828d61e51bed4d011b179a394327dffe4c8df6d931b306df81c6533f46e0f42ac23db7edbe727a7d375b9052c8e78c024c8c14782054e7c8d6328bac86951e7ede7ca28c437c1e768f5807959e0c22754deadfe4e352c45380c87df4fe625557053caf5f72e24892a5b0992b9333c53ded9d18de972f24aadcc0b4c9944be66466ac1b3a7c6d59e201cf7d00a68e52a41e5a79b8172bd90a8999897fceb67b1e8c909fcfc2e6d3855cc4fec0e2953034b369036a072b5f7ff1a04efeff09bcb3d5c626d2668237a27fae5c9bd00d3980d78d3b8a572534317f7479b348d25c26a6e6f76da09ea62586411d99cf0ea8b51e1fb9475727c655e11590383207fbeb3cf4901293874fe89516c0f18a29a9d3a09074eb472b1c06d7322a071e26abfa1f14216ca37cc1da29f5912fec358f47fd30bb739721ae727e0d6f31a22fe16c744d586f2b6ec298ea4b09f3454e6141b6ba107753a0ffaee1a1033a7b604310e6c574077f24d3cccb6734b8e4af30155e125e1c75139830f754571940e05e03ef60d5b37606c044ad064eecd74714bd3f92359fc72de5d9478bf0aa28072204d8a58de89af9f971a3e82554eed37e1a7b1db5aac26c10f72c926091b69ba8b49decf875723a779bab5b575170c02d9eeb79132b531c0e6d6412a496bb268302b8920daab0aec32935dd2c250178c89132b94ba6b5e7816d145e6431508e1f302cab5be0409f0cadb49394e71cce1d09bf0b23fc4725568742244716b63873b307648183651c5a5b08f8a3f09940726adece7afef727d17ffe12675d010ce91dd63aea9321b52f967db37a91414d422b8e77b9b1772522e64ddd9b3945d98908158402ebe4d6460860ceb454b43dbbfe35fc904767299e4b13139cec9ab7710703015c896a5db2430e1540e99614b37fb1c60b42362f84d0783cb6e237e941c54f3e3f4862a7dd17ff2d49d5c0273792360a5b93d72d9a00896dcb30a8154dc42412b9c08ea84f49a060baf4fa21a28456aeb718172e3da0ac4f9dc252cf14da099b2dd11acfa63fbb9c335d0c2f6e29ac03150f26516b266d380363369e6d292224f02b8fb634d6145c62ca309d2bb6ef3fa288730cf3bf94bcb42a013de017ff987f69db3d1e423a7b9a88ae4b215e299baa290441c59c3feb39ca37f598f3ba5101c43c628dc8e47b9dbbf50a70409f9046007723216ec764aa1bf25bdd3d47cebf449ec16847edd1c3c497688f0682e190b810d48a871892cfbcce9635f5fc13fa4c2bfd7dff63ab7a06bd52c599c1db8ad2419ce04c36e51bcd687f6a3b3cee54cce80cd4b7a2d0a528d3916d3006f3dd26264d0904fdc570fbb7ae6f6ae49edd48ad427535bdd473122b3256638dd5fe4c446e9f4c0349ade757b05a865bc0f512b7624988569d18a70a60d982b9ead19a6320cc1026380ff594445ad07bbeea361802dbb62f0b6eb4346875be647ff20e97226e9653d96df88ba91cd6d85566e6da31ca83e42f50452d4b997fd5ebc933772f951458f681a8cc14e7e1415bccf4391aab83b19c3db98d26df4b78de882452ce1cf15db0f25d20e67f7ff232a3a1ed6438e090f58697fc1b0e52d3cdbf66872411600dd168958bec9e7edf4f9975627312339a1fedc0302b31060c64f35840638f6e03e0b5976ac4fa2d94340a9e6be3bda5a3b0c03015424c814b0d6c22a72972f52371e6360af22b763cced894c37cde83b6f1bd22dc037b61df33834033b420834e83b12111b9afeb1dcd7f90f890a46a60b01f2487664fb150934164046b9bd5f42411a109ff55008eda81759d42d56356ae4a19fe5ba2f0b0b94687b72057f5accd75ac8fa786db2ced137e175ef2087ac4ee4fa0f6558b0f272e4a572036d2686c2258d0858706908dc3c0564b87e558ffe787d1548100fbbb475d213970b31fe92199ac2c48fde6020b2e1c5044fea3753eb334f168820ea92eb36726e77c3182fca7de322f72737c70628d5dd4d9a205d666b8ebad436728f6efc1c2589600bb59e6c4f54f17d8257027f2e52fffc57b394abd0018523b65015ea7229559daee3ef7ad6389bb1c38bfaf781beb7b631b994b031d72128cd69897850e8ebbff4653361ee49ea3ae29b879d7aff1426a00424a703e5ae4faceb82d272f506bac1228fd4cddcffe30ca6e28700ea07f0733412e4c8a7c1ea02a30139502383d8be1cae1660c3a8945dafac805199626667f407f9ff3f5f27a0b27e807252a87e678a26665e7adc37ab3598e4169a211f281ffee81de921bfd35e5b9f6318745fd401998f65b1712c6dc10634deb17deadd32231095987f8f7e16ec55721d2cb456e46b16e5e3b8d22dac5f7caf2a77c5746abcdcdb315e170dcc79c47288b0c875a7ae87504fbe638f1d9f1f03288be294d728fe1d22016ff80a0a253b74e9af684dfc0929733a3fffe8c94f1ea78d79b927af040ba61f735ff16309728f20aadacf87f491b8679cf51bf825a34a2d70da0cef985701539635db5df472b881e0d90d90762691aa2ec8fe346f7dedcd01a3ec76a7990777086c9e7ce76c924371f6d85969109c2b5c4f2dec3d20c710e07be1b489190df2f4b5887def7259d4a8089a6af9a5557531307bee775ab6b80da6f20bdb069a81a35f8be0da2636f2c94de3959fa372b9ef1afedc47b2d68d1d8675d3d50615f2b2ae6e76bc725af13cfa507ed060eb7a39f2c3719d888883100a8a80dacd52525746c96bf27206075d6183906a4c91468f43768f7d98792eab620bff7aa7e68e237fcecb983b466bedac133d2a62e48e9388124ad7b6ebd6d0f9c6f16e823b5cb99f6946ea72e8a17318e96e2e328aeb980575932243804ca588df7e6daacf7977faa36223482fdc77e80d12f1f86069248cd09c863c36cd9fb973d898b55e91c79e530b6d445d2ac072f6112f4a73c52a6a6f10f8e4f8a703d1baa871502fa076b8759e0519946982bd0be5ba1d8b1f460230d6300c8cd6a70825562aaf64b929f72350c1727907ea96b24c95827fc733389aec4651a063f2762c759e267e1e3c98d803c87288a58349a7ee203bc077f4ca2bab190cc1d21553c598bb28a9680afb07b74f4f3b7af139e94c6973ea993ad4bc2e01555350cca14352fd09b6fb439915f01d3a296b5a8a552bded641b9dfb50a451ca0821f6ce8b696f097b33a68976846f572124961e1d963f687b02cf744f06bbe284c9e348f43e158bab5f7f378dcaf6272ed6bc11f3763e3a25c28956ccf21f734c84984bcb77909b1e5dfda824bf09872547309307a4817a9eeda41fff05eecccc5d8d8042a1f2ad4858e892fcd49b8450ad3ba730039b644f8edc17de129bb0eda9b213c56a7a964dd4f95d9789d40724dcdb189c855e6f8a791509455ff16cf560ca5c896e8d5a738ed514602e23a47873d4efc34a9eefef07feed282c5fdc6e1f0fc1c07c2faf5e54e9ce111185072e5d3aee34743917db48291482e84e040b8caa110ea194c593b078d1974708d727aef45127dd9e16c5b46e53fa7680e01f7353c21a90bb0bef670bf0a1de449726b7324c35b7bacab98f2c656f9f1aaa7d107fdf9aa09b54404731420eb8c0a38e86e951f973dbddd3e16d249a8235ee311c5cb84a3ef10f4d1befe0b7c6f6a720da815982f7c03c7a991fa8383fb4d470f525f6659f497f938295b5884e60a7288d374f4462640ad0df4be70ac35f5d0e65dc57d30f48b972ae6d4f62bf1ee7228e784af4778e6ae66cfaac14dd30442bc2ecc2d4c55b23d44a106403605fb72988868bfa4acdaedc17e6ee7519d462e41dc02603ce723e519a464e2e1d4ad72f78b235fba13bc74c8138a1bf3c73a4064a197ae2da0532af473803982eb412519fb344422525fd37bb7cbd70087e37960f60b17f660ce27221b49553572a94194c030c8fcda7f291aed348138edfb1fa210076fae843662762ed2fcb002b372ff678ef7f46e045a6850fd78ea9f2f0bafcc1f79494f5c76a916d318c703a32cf5db50b56060a0b3795acb0a41579aea889ab5901341b6fd3c2d968360a9065f37de2cdeff20b14a45a248f8e950a2cec05a09dd4ac6175bfbba3409df2abd721cc7901b8934754f66a8cf5078d1f6ada9e9b1a5860532f626d4d1ac90d25c0bfe1219252f4e057ca85f0edc07f55d963b6b3489cb82daf563703cf1796a877220af7b60127adfb4d32ca7bc078583e7e48536c63768a8b1252684dade27800a1d63ae85c67a56fe3bbbec51776fe1de5822ad3a1f8d5df812b20c5a5f3d70727e4420ca512d098cc523786cdccccdac769103b2b1a5bedaf99657bba047d872f2240414689658b7c97040ce97ea588b706c711eabc30c94a399d646418a447293fe2015084d41c41108bbd1a4296f81a3d45b516406d1b07dab23da97c9175500112bc8a25dc17d510cf143f7d6297d33bacee43865653579f9d75a330d722752dc919546d8aa31683f655aa6a52df20429a402f3718e9e8cde1e06c172f253fa052e2d3d071466ed3d836c4f650fceb95bc89627330d0b8038aec20046c81384972618aa23c5addae9b5767047a09b2bb58be0f5da37b8b6fb94bb5e7b8572af21c79e7e5d5a0b679d677e0a7c14f95da4bf7e4a30040fe2995d150478707208aea31c76c94eefd723ec5613ee6e2ee49c59c0819587b3d63f7b6b4fa1be5fe0b9c24d83ab408037c829227cd69a10656e1c24c3087249fa2b9d516a9063003a4fb6831a2fc7a610e5429a0816afd0425e8b0f66bbfa6eafbec1d3532ade2b4b6c503ada4fbd30e65bf264370cd2646c5827cb71640326df6b55231391ed1a79e7dd1447b0b929315eb5484e54ab70dc76db616b3f5bb3a4ea1dd9edce310353d3eaf4d2b178c0c91bfabc69f5b9311e42dea8c9ee40e2a7d27a6973b5e6720b493d37008d1ca23a07f7a2b7a01fff460ebb7d0b5340e7c6b5ca501187b5729be0b6a4d762c317a549ef9cc99053dd190ec3da555ca392aa9aa0fb77093572cbed338eba367a71c910aa4a94c370a506f0efad5ae06360422c7746dce76c52e83d81410eb386bd7777e1ceb845c0f8aeb420ef406da64ed7e441fbe797bb723c65fba82a8f2d18ea3f928dc93f5ba20adaeb0d37b15c8980687249d625a4721e8f2fee38c23154750dc4a5c864ddcc187a29f26b548338289d1dd6887a4472cd1c683aa7c3ab806416d05def0c2170c9430f074f7d6f9a31070c7b6c8af6721041562fcf8f2d23bb39582863fdc17dcf3625334cbba449b28f6fed768f675550a40f6f67a3c1078abbb5e63ce783f4dc5b9702fe1eacc9dbb78864ecaefa726b2341c9b95a2443600619932e1592a1f06dd2a32f980e90a56a0d8c0e4bef204ddc69457e059ef99534365c6b50734a2cd90acf09362505f15785f8ea59d07271838cb072ebad8b2207b7a51ec77097a2317965bd41ddf1f7496ea077315814dd653af46ff690b66e44ae9fc2167f77da87cdfa9259449d59d984b6e0e87d729d12434769527e142fcce8a74dc57b28b10ea387e05f0059c24ae915a4347ef90126b0822d2a5d346f1fbd8a182b7eb4db0d74710e1b2e241321e238e18c319bf56fb3f44f74fa0fc73de32ccfcb877a1c3c82b0934e95336c3c3b055ef8b77422d76bba4f5136472be7d4bb203cbd126b73fd869bd1c97015cc1a6b423d5de05afbd586b0a2c33335631a0993b08568b8f4eb729b9b307d9f8a08eb8429462f41b7c605ca486b09059b9e8c471da98eb4000e7381b089e40426048fe69e2d26f92a4aad2dcf57952a38912e8aa6093ad237ea6276fe3e80dc7f1c5ef0400368a94f98392a14b0926f831082fb5182c23f0cf4bf84d46e13ee9300c5e976076a16f7a05a493054ec9c6037322a1a14ec5ed796aab5ffa5b0a84a2576e361e0954b1d75a5ec2ce718b9d8fbd986830064b6c961a3f12e4f2a89d92a3cf49fd64be3cb128a12e4a7aef90126b08a0acec060064f7b4d8afbcbff1590de931a07fd5d0ef602758a231efd8432b86b6980585b8befcfb8a423f823356dc8b0acfee4b69fcaef947e5457351d3b5eef9501a7b9c029f20650729d81877315a7ebd571154d063214f66069a4a80fd7a7b0a74a9c4aaf9c7e7481bc6fe9fa5f32d3012a1bfd59705542d76bd46e974a01060585b0945168b66b48ae7e5bf4f2944db0b170afbce40db19fd5e0a30bebd6957002d05821d731a84948d9f578351301022d9121250a463d898e2b61d142185fcbb0a61556d5792994148adc9d4ba81365edb50e4e9cc2acb6167f10219cd4579e0c304dd5d9a37e1a7312e7e66d30e1868cb08be72c732c73560c3cd2ace14a523fe5713470930dd2f36e068d021dabc40ff53f826b97a2137b2f357dcf07e20a00c7"]} -<< (9ee7c86c) {"jsonrpc":"2.0","id":26,"result":"0x664cd551053d4309ddc4df8bdb617c89eda3c3988118ca973e4d914cb397fbfb"} ->> (9ee7c86c) {"jsonrpc":"2.0","id":27,"method":"eth_getTransactionByHash","params":["0x664cd551053d4309ddc4df8bdb617c89eda3c3988118ca973e4d914cb397fbfb"]} -<< (9ee7c86c) {"jsonrpc":"2.0","id":27,"result":{"blockHash":null,"blockNumber":null,"from":"0xcf49fda3be353c69b41ed96333cd24302da4556f","gas":"0x186a0","gasPrice":"0x6fc23ac00","maxPriorityFeePerGas":"0x3b9aca00","maxFeePerGas":"0x6fc23ac00","maxFeePerBlobGas":"0x1","hash":"0x664cd551053d4309ddc4df8bdb617c89eda3c3988118ca973e4d914cb397fbfb","input":"0x","nonce":"0x3","to":"0x0000000000000000000000000000000000000100","transactionIndex":null,"value":"0x0","v":"0x1","r":"0xe540f5f3ffd4e41807cacab7e6d35fbcf6a544373bc0fb3371991f4c4948956e","s":"0x56f7eb1dfb537638bf2ceae70259993bc4e1f33b11119fffe4d5ab6449acc9cb","blobVersionedHashes":["0x01a8a4a49dcd1b91c376c87d7d6a6e73ee3792205864bf61781e8e3ad19d0092","0x01069693395fb9a698b257e6c25380f32393bc0cf17a290f9e7fcea3c4ae7b8b","0x01111dafbfcc0caa0803fc22f697fd3b9171252504e14ec0443f04a2b3288715","0x01d1dfff9c15b3201980d8f9b958a8ade73e3e1e0ca76c54785a33e09519be7a","0x01a7b4b55dd68d59685abcd629708c5de44c8e9edf3671602538eba23375893c","0x013586020d67ab6808e681c2e6a2d1e854c84e5bc49df2ed34e218eace844f8b"],"accessList":[],"chainId":"0x7","publicKey":"0x95a6357daf5d9f91c85bd4e1f8b6226cb18396d772c35620d071660400a543d8b51f13cf95d7191e958a12c6109357a4e1e50eecd92db513dab323a5c1fe7ff6","raw":"0x03f901350703843b9aca008506fc23ac00830186a09400000000000000000000000000000000000001008080c001f8c6a001a8a4a49dcd1b91c376c87d7d6a6e73ee3792205864bf61781e8e3ad19d0092a001069693395fb9a698b257e6c25380f32393bc0cf17a290f9e7fcea3c4ae7b8ba001111dafbfcc0caa0803fc22f697fd3b9171252504e14ec0443f04a2b3288715a001d1dfff9c15b3201980d8f9b958a8ade73e3e1e0ca76c54785a33e09519be7aa001a7b4b55dd68d59685abcd629708c5de44c8e9edf3671602538eba23375893ca0013586020d67ab6808e681c2e6a2d1e854c84e5bc49df2ed34e218eace844f8b01a0e540f5f3ffd4e41807cacab7e6d35fbcf6a544373bc0fb3371991f4c4948956ea056f7eb1dfb537638bf2ceae70259993bc4e1f33b11119fffe4d5ab6449acc9cb","type":"0x3"}} -INFO: Sent blob transaction: 0x664cd551053d4309ddc4df8bdb617c89eda3c3988118ca973e4d914cb397fbfb ->> (9ee7c86c) {"jsonrpc":"2.0","id":28,"method":"eth_getBlockByNumber","params":["latest",false]} -<< (9ee7c86c) {"jsonrpc":"2.0","id":28,"result":{"number":"0x2","hash":"0x90c8b9e1e8e51584453f5516036bfe0031ebb595e47c5fd664bd56471ce618d6","mixHash":"0xf8caa5bee858bdf1581f3920c0a700cd25923f194fdafa96e47fb2198779c608","parentHash":"0xf9e9afb4b42515283ae292d4f9982cb0c87d2338c717af437fe39175fd5e5fae","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0xd51ab9181e964dcec962295995b1fff437d9504a88ab944324bc1227c0c94bc2","stateRoot":"0x724b6cd36d03f71b4c088b69f1db0a61b3d9789546c24ef32d59f1dc2007041f","receiptsRoot":"0xd50521034c860197d235df5876ea04b9bce05f69b7e89b96e597d9f6d35b1492","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","baseFeePerGas":"0x2da282a8","size":"0x408","gasLimit":"0x2ff7d8","gasUsed":"0x17a25","timestamp":"0x1236","uncles":[],"transactions":["0xd886baa4d7824402a508487d94b8efed257832170ecb5f5a85b8d2e15317728c","0x3cc701f8f4e4c7d32e1a55dacbf4175dd4a61b4b8f26be373b8f7b0bb4c430e5","0x6208c8da6ad2d0b72a65110f972a38861ab4d12a45397dc6026d07e5623f748b"],"withdrawalsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","withdrawals":[],"blobGasUsed":"0x60000","excessBlobGas":"0x0","parentBeaconBlockRoot":"0x062367f0b23e2d49ad5e770d9ad17b83c0c1c625c3f9a290cd9572b3fc6cfc9e"}} ->> (9ee7c86c) {"jsonrpc":"2.0","id":29,"method":"eth_sendRawTransaction","params":["0x03fa0c03a6f901350704843b9aca008506fc23ac00830186a09400000000000000000000000000000000000001008080c001f8c6a0010cb2e32661ba3842016565ffa890e793906f85cc6ad70294dcaa5fd6e2f9b9a0011f7388728abafddc7e1e4b724674d0ba7ad24ad8dd9b2398ded47b97ad6252a0011e5f0b6eb068cdbdcd1df7ae20fce09a3b7ad23c53f6e0c2a36614e42aa515a001fe604cdf7bb13bd6207a3b73d10acd94813c7d0c9fe475006f9bdf2593d30fa00113e7b8417c5c57e782c7b643ec9483cd6d8d852739cd4843e690ef097ed984a001a592db705852ea7c9a6ffc914c4ed81b5ef755378a21c76f5123ee0c8a6e5980a0369cd08bf56b9421dba408042ef493567a8a403c115b1a5d62f5f60143d8e3ffa05ffa00eda1cb30fab5795b2395a4846144372853155990cf45f9fe78f5d120b7fa0c0018ba0200005924513516a5993435ec4a240610304aca7d4acf1f2de5ce6812a8c43610c6e67280ee9a65f702a3cc50acf7449944b13ed977599b268a29fa6ada0c99efd9a67266e57ad343d262e13afb098cfaac153fba3252b9763a70e14dadd22f29e53044dd93cef0fb0f131e3be76ec5628afd2957181b6c8b78066203dd29db82d2156c35441f47e70d26a1b506c8dd3e0481de2adf25ee4485e568b148040b4cb299727540978b07ec683e85211c0439131eafa07d29489324ab9698e8e27b6a6f3172250dd8c2350c53457446e1571ab4fae3ac6cb2cc4468bacf0027c1dbba004d72c620790062286574d055bd9ab1ea0bfc25eb6af5e2f3ea0c878ce40de754fb250afb5a4ed84b5cd766a514ad2a82afe36309645b732cd204a2933beb65d5d709814a798f04e54e0384e051b028f5a3ed1baf3b328d82769707e413fb4a4b646e3fba53f2e25876031a60cbce36931e08b6283b09e41a6f39ee9546dc25eec50f68c4d95229d76c6cca1938cb5fbfa57f6a789be5a1a924d6aedb9dd9b7a63872fb5fe02d4333ef8147b95551155c1f378eeff6a9af9318598acd3028f0281333a1ba3a5df172b07c99c5983f17d90326a151f5eb8e3f3554dd02e10b25f5b57289ad475ed7fabc0d61c8f5ccd1c2e035a783dc6816aed19d067215535c53313dc753f77aff1f0928869b12797412112862b509370a67fbe424f5ca994f8fc3728a6e707ba21e70ae82e3d8982a947dc21cba854cd4ca792d17a70dd166717100bbe018683af26defdbcbd3be9197206372cc8a167b826d86d492c2e93c025d30ce9d6b9e79fc1cdf492ac094850b75c0b9026bcbed82ed19d747304d707218727a7d2e001bc902afc8ec56efdd409b6fec56339f181a42678654e9a0df3eb242b9663ef0acbe9bbadb90ed0870b00323c3cb46dce2978721a907eea164e1b672d1e931029f9358ac070606c598f7cf402317e536d1be9b883d7142aee4563a1cfb704435aaf30f5e7ef57d8af8b13d53a517be4f671726b7c72f7325a668603d8f12542a4bf63e4c906449c9638140ad8af33740e2e582de5e3bb10642a02072114ae65c25c3c2b503c9764f484b6b3f7e38e283a29fb014841ffe5233113a158e4da93cba897b604c33a03e376760280f0a16282ccb028766628a8fe40dbe726fe47565510841d277da2de6649ae865106d181d77a4f7e922a2ccf9588d8672ad718bfb91588e2a782b8484e48b0e0a345c0b4e750c454ecc96a4e0e8769d725f5006bfa07b568758bdea6d7ec7ca53426a97628467c6386879dfdae4eef7364647fcd4fe61f587663c7ed2cdf75be62a93810d0239dc17edc05d83a94ceb72da1577fbeacac8fe685ae7f3f90a7421036f7b269aae1b685c0188b9293cf572dbc84f3012a4432610ec17616e74ca85c1610f3231391fe0eb1788d2fb30037249316359b35c330a2a3bb2a77df44d8d2a320be864880354ff6e428eb3b4c672847786364eded4ca5d994f4e75430d270847b53b1cf8f311223c89e0d3f2aa72c95ffc90b319f86b7c33055e2bf84fc6f239905d588d540874fed3a3d6f106723a1d345b72b28d99ccdc74fd530b15eb4af0a9407f6e9f05f64087d94647f12033fba24928fdc5d7103624647a0dc434c2f6ee38d3002cb892fdf5de725387722406b53ef1a1a7a353e6ff5c581e79cef6b77762add50f322c5aec1d650a16727a564c8b62927ff5e73ef69169dc94119546e8779b5bdd0dd52c77203fdaba72adb1fd0a28aab40cbc507c511ea8d76fda4ca3473627504d005fd0e3f48da743298d50f3386b9abb03ad41d6ea8e6c12bef20476ae676c77fd61451242347a6a22dbbded72fa5996eb3b7e8a08c019936812d76ccd7d25e0144833303a9f053d6eabcaa9074617440b92c9fc2eb15ec1af750ba58452fa6604d24beb1a4eb857936a5a0a1ec3d852d586665af48b4cd9a566e2b1c213434d1a09fa773222b272cc0c74e63e4cfa76ac6f140d7fa2a336f8274b7feba676a60195eb1dd711c65170ea9bd38968210b3d8c9571328ac4836472ddc489df6465e6cbc13d15cc1a0e309944357c9750d4631ebe753b0504273d229bec03e0d6880ab7f5f49238273eea5dfea6232b0f4bee6c0e297ef7ba7b0a0d270f22a7818e85367fdd27aed972177197522635f37d5ff74364fb1933cd8a3311c6434f46572a8edac65c75b572e11b9427af358d74a7e2b6897497aaad82d75323c4fc8268dcaf8aa1f0b1e445ed798c4965031a6e6a153b394496843cf305f7a69595fb6b83cbc56ac3048d391a8c4e6b729759287b2c54d697948443bf75c19af126eaba35d4c2f9767351723102bc71cb426688392a82b8a66394fa92786369e8beb4486f64d0606446c772f75ee83dddd5f8246764808a39ba55fde7d8760d3fac13ec089db71e07e51f16cf83a8811aaa9eca5bc812a311a6f223577c6fb9c6efe672e5ddc734236b58171057dc07c425300cad5dfcf98fc37db179a89f67b8763eac043e8ad52e9c996dfd5b380ad50cb76cfd888239c1f5675342fb496fc145267659178d68f76ebc4aaa7ff43e0511c42c83f22dbcb2cd322cc2f6a49f0293ee16c0825d6e23e6f5498843635f435f3839d3cd5a83ee7efd43ec3b45c100c7eaf950c3870fa9053872a1e27d07d2808a14ca03dfaa07d42091d7ae5ca6cc469fc55da935ed8dd373729fb70c80a334d8710d31ff1187691a198167f32b9ed0d080f41350b2a5925472adbc80541d8e93e4242e3b3eeb6895eb9ce25813a9735c1af6b01cb7079bcd72a9f94ad3e87b2db91626f0a9cc3999017ab641bd82924ae83a3b8b2a1c0cfa727a28c84abecce99357bbb11252db432aa7c1f3338bf8b745ea66d61421ff5172786a6014f787095c1414c4fb8345a6b0fb81915a6260f905e626e94145078d1a8577bf99bb14fc45f7de6aa19ff9dbd9afffc842903e0f432eb52edd00149172a5edf7757864b772d706912f41cb870e4613808795eaa6d618baf64ed5dfdf72bd34f8bce15c1a43495c9fb5ea27060cca281e0d8689923669737d5c0241d7727fb20374f54e96b0726c592569499406ce7ec6bd8a96a11b3f32171a4073d05b45117ecfeea1cfe7257db409245d60bb7f75ff37180f714b24b47c304d42690c2dc3bab37ffe4d007c2fb5d6b131c7126cff4ab83d9f28182b1ce09593f2ee5a42cfc8e35a5c6a091367f6375fe91787993a344dc587e0f5945100e99c582672641d45964ba5da9e8c59ca39b43d6d49ff3bcf0492e9459780aa3de9999fcf72a7fccb458c741f3f3f16e9b649df1da61e960397c39ccbdfca52451223e57f0431882d4c1073e350928f650610f578e1cf5109bce7877a81d80cf1f9c0f33772caac7794b7692b2edc69a27ad0849c87201db252de6c94b6910130a89bde842e734595acb53ab5ba4f03cef1fd5c9274118356e02efa8dc3ad5065a103a198117af72f735a8428c9f53d32040f8e9a8b9cbe5e504c12d03d01b30472fc5c5872a52571093e38ed0e6f9df24f3c9bf71bb25a76ca33cac783395947b3df1f387297146d25fe6f1d155de736e886e9a437ef6d9f5cc2c24f46f471c229c57bfb0745b712a6d742c5bcbeb68996457a8b4aa8309130bd49cfe080486edb822f0f3208c8fabfc010d2f0e70d8e261ca748921afb13129b8afac14a3ec45f5d8e2554fbf0f10981f8d49ac2872cc3f3e4a4cdb4e9a66013850c8ba190c59e19a99a19e0941f111007aa44c7ec22c7b3127cbe873227698d5bd641113d8e2daa280c5500190867b5749c8451a40428b34f2df6f9f7d5c29a26258845b263f1f1981c7273f8e99ee040a859c54bef8e3992e68f5def16185a4ca7f33597a2b3edbf2072e0e5d354d1b7d1fdd9692919acb0b575791e7d9358033c78154f4f98da614e11b32256c7033234a5f0cbc5a83d2263cf81a82b96b9c53c2ce5e91909adaaf372e55afec079a88882f1b5eb13ec668fcceafc11b3d0a3534c6b596322592ea872f3483115a07f6777771f385e0344997b7a8211e649846a9619d5d197c6fcc1726076b4d690338f167048c29cc8af2291406bbbfbbe17840329b5640ec9e37a1c82b27e79f3769ba2df5535da6ee73892389d6a4d6a0395c1821df270583386724ca75a97cb753eef1266b5c286421fcea689116bb132ec8768f53522fa23b034f0778d9063facf07e406892d40027ca6e78f846dba9b4e01dc82d09dc1c229196f0ba800e7e8bc988f59766d44414b92ee39cae8ef48eb841331c011f6f57672afb696ab4ec33adb05eb98390f3650d2bfb902c4ed02f2a9ebf7d730ada5fc6030546416f1839b7fee4c8230397e0b7168caa7d4c86f094e4c5d50f2618abb4d2ee2f4da128a01f88b013b7f984c65df0fb8371a127e6066f16dc289e0fc42727cb302b1fbcda4273079ee70aa443ab4c5339ea4aae1b6cc49dce72a86205972626009e5b5e714d558e79707e77f5272560ff2d242e80ee5f091c5eae1b3b672daebfe45dd761f0dff13ec1cb0d81dadb5c7011770f18759c7ebb0d1fc9c373e952c43f7da255f24d4fb2374910cc44c0b2ce79389dbba3e16527b14215f7d720c1f8fedf86ef5720f5ebddbc745f47782a3fb557d2523796bc7db47a7ed50721aa0201b12a062e6219a9db78d9cfb3b45d3b9520bfbbb4f1c22177ea27bb24686ae062e85d7afd0024bd3c6dcce22df6a41e54104206d223d459757e6d81d72a0f77ed1659e36fc1eb9c2d2623a25aea6d2d48728b951c9a49fd705ac79ea060675472d46cf779c70f0438766b182e8f76921fce17571811121f247d4a254721e849272579334d6bc8350c9cb6008e1edb0533488df725bd5d54faed95acf727c9d9a73e7a3922a701dbc18d5966aa77c7ac63648bea61a16d5446f5befff722b603d03f28c186c8c61e9674014e11b0321e8d44bdee2e5ca61e6945a98b3725dc4591ecd5f3533002174bea028cf8e446f9b6a0ea2f4e922cd980e4cd0a3727a06736e091df48d36fe1ab1e27d5b621f1f1d8244a4d4ebe845977c3da8157203b50ba3881aaea076683208d06b67d275aef19b4041b9579d82c14291ba0c2e442e77ef42a723a6a9dc0020c16e024e47db12461889a3cea863100082f5f65af70886ca7c4339b9f339925cd17d4e0ef14afe71e21b2f2ccd5977f068c69340cc56c359700a361ea287c6ba198f31e45b88ec3360deed3ccaabaca21c6e07725207ab8149709f23d43f135e14e43cdc9576217319bf55eb8e5eacd8d6f81b17a94026dc2c24daaa6695e86833b11c973244d261492819173971068310d3b572b7894a193ad5730b03dc2fffc37b1cb1cc19a5dde2314ff30d058274a1f46732d11e58d6a66db0f269614c77a1f5999b7f9fafd4ad1d19c8fa45ebc6c0cec10b8ffffd848ed3a6e9485b6e7f82289585af82b913bb146fbab041d086e3b5723d7872acb52b3a6736babb5a73e0c8dba54ce1a70638f17a23b21372815fe3be34c9bfeb7b2b03e97eeded3ada32ca0a5c72dcfe29a4c1f0a5ace3a6c102c12506b3d0ae573b3dea10fad468876746a238339203613d10ec8e67a085a02bda56727238d3432d6a181e453159cb1e72f5d6fbe8ef14589df6637bc632e149d24872587d6aca3fe90b45c0efc427c246c8e1acee4e4ef5ccf9ac911754ee2cbbda7290ac286cafbcb708eb0bae61508949aa324cf5d4e03af8d3b93c76581651d072a4d2bbdcaf8bb18fb95e4321362b18de55d9b7f5b7f9514c74c2c2cc4eead200359427df838f9a0eab4d0bbd634315e7b2aa1861749636787489c8a33ce93025bf610cbd72d9d0e9601494dcb04b4f19387cc902aee877203660c3c5ac433333a77f087c13b7539ae84c001065e13ba2a66f4dc0090efb7f36a53644073f8972a24264076dcfe5eadddb01e2ee7d3061436a179c0d3c0e6499bf812731fd7c72015ced1b45c366b6725887df1ab84c9fad5afb0582b04bac456e658a8c7acc722ec1497685f5f988e8f3d1691a3824b1260693a5594690313a217371ccd39072a0730d5272a1f2298001bb61fe0d8aac92e1d19727123ac2a07209b16d973370d7d9af34d31268635a2cd42ac660974730bd06a87863fe2fa9278c14acff9b72ad08be97c63b500f5739639b4b7a5f97ef4b20527f7696ee1ded8e1a66897f72bdddbb3c326fdfe413df49eb735faddb50ed64994b058f6a685d992b9c3ffa725dfe2441af65ac4f19ff55a2ab52b3099142c1cab5f14b5a06e578cd7b002d7201654cb5f54b7fdbbfcd4054b2fae7c905ac16d6235ded745283f3315cf0927250466c6db94bfd6c6be0fddfec6199c6f6b094f71728c230c9898ebca94dae721a4cec99f96b2b035d367e361127cba2465bba0ac861d064de863895de37bf704151171dad2633477c58a39779c76f65ba423d82917cc0de7d5724eff9aa0f5e98430526fd8769c3a3c231442332adfbb096d6203f6feb88760467d433819d72256166a67ddd19ec9d0cc587d93c754b5af24ab60014d2a618adb9a69ea50072df319d78cdf8962aac6317bd889a1dd3c671c981021aedbfbed36e846e4cb341fec5879af418e6b12efe861fa73e643c65e2bff56433239e870ceadc060b6e726d8076e87a7986e9ffd15b8673673e7a973a727a7291b0891b65a9ea1c206b72bf9934b02cbdcdaa40284aa6c1311b4d868d69f0206b81c32ab61ecfda123b72ab77856535a8f59cc4dc401625d7c38123fa1e7b6eb9a87fe42b06d5005897489a2e223b13e9c8ece3929fc59867dcb892b4096baebaed472c180efaece46d30f2baf80adfb3551de323ad1a7b7d961f1f6e253892543a3808e183e27348a7722132f61922719e576d7ef54acc3098d7b2de95c6aefad98722edb2d880d346720d959da600ec7b08523910c364da89d4b2243c070e7ba08962075f2752c64a0eaa60f9d30a06cf33acf22f20afe6980bd5d69902305e1ab67a56599f26f63a72486ee4bb8b9e1347ce1f08985c5312bf5e9d6530813e7f75218b83da7b5fb372b8441d2bb07a9276c42b0b01a44bb68e87e7f398970f75142700aa7a3d400872bf319d0070513b4288df7de94e4a63350b597e67e92afa4ec5cdb62fe32f5972ea09641118e487def48b4d21fd7f78eab84ca6b6d81ae188d4af2bdc503f053c5fb5d95da4f828a703e3570b8b37664831f069190d4557e1f6cd019b59607869910596c5be02a2ce755bde9be69d61ea7ea3376adaaa8d9683178be3df186047cecc167b432961187175cff33d9b1aee69bf4963a598e6b5e9e2126bf82a1172a9c5bc0ab28d5a861de0eded8388d4389123bab3b7fccd59d4c0d6d060d0e5722eeba0e94194e6fa072f6593163b0e658177907d098aa13b7a6f1d5968a68a37b6b705b2db28b514565f0f3d89ba3da1731b29ed15a4509b55ddc8db7620664e10608f05d71c0bd95f9106cda5b185096d8ff8bca4f51b9aefc41971da07da723258d18b6f7c5f298e82c045a79d9772de097d8fc96233bbea5bf23d2a69c772448f75351a322e1956a1c12dd15ef2014f42da4181cd3e752e8932f306b58272cb9cb186a2ca2dfdb97ae01f781bb4e1b91ffafd2c8b41aa8ad66a3b57ec37573841d6cd4a222f4f877a2ed00f27a014727c19f0f8409b7811127180230e8e43d52de4009f127080dbc92f424ef0be7998657fc542139e5c9dfce42d2b23d3722d4365cf447ff4f8fd9377ead911ece600dbf1f80f38cf161610152faf9889726d8de8757fdb186c3bf918179858b02090e31d57fc2b659d3b900a34ff803f72f9be49e72dbeba9a3ebb9ce5754b05f7b6865d16ff06ca5b976a9a4b6dbeac728b0c8c5c5dfa2b618a6706c3ab717e14a5f01c4cfe9179f626fc587a40f8547260a7124b11b1ece395535541eac911b8ac667c54159797b8f508baba604651051cf2270a3172421c52832d36622fddc1e12c8d6c999436b0856e0a8cf0bc1f72191986aac6e572cdd7ebfd4d3db3ce419f2865c6d5452c0f7cdfd165771a4266b26bfea37f7cc1d72e397fda78b8611321e5556623c92aeb97c7f64ef8669d72b98df31f0a4b9d419b50ca3e7bdafdcfab9bb843bdf9f534a0a61d28d5c7096c51d253beadc5b82fa129d7212ece6ba6a94a10d5c37984ebb8fc88dbfee50102d3e6e8a1723f10c01a23f55f9ea9534f19396e58315c72ed1a2b1c2b47dfb146d182d46f41a6c9ef9519fb757f716c304316dcdf16990af1827584e6fc21e218345bc0ac048d5b16179a61560b8a1f1466555adc335795b84ad5a9a8917faf724972306cbab9f5599e39289a87e4f50e0187b83bdbed571f420a663dc6173c72fe491ee2f37a608372ba5ff97ad6490628db934229aa2c677680377d773d8e723c4fecc4135752d2a237a468ab1664fd31e4a5def00613c0f8bd6e3db7d9bb727ac54d4948413eaf848d0169112090f0f2cbcb348a56d7b1fa69d84fbbab0f720a89201bb3b3ef63269223871aac8884c2c41a01b202a981085ff5459dc92348ed19cf3095efb92a2bf66def11cc4ea30694e1ecf9300f256a65ca226bffdc72cd4caeb99c7cef40f972c02b259ab4d58bd589c1b3bfa89959b9a9924da8cc722e49cd52df0f656ca0251344df6cac474c98e4d2593c639cea6b059914fe36121535cf82fb59f5986d873cccf62dfba53cce3cb40c616ded1e4508a24a08237226068d0d5978a606556572517ccd63a690bf2fc029b3ad25d9851c51d338b3722b447fc2b9edd8b063095676cd42c099c0b7d95a885f3a40d058c6b45c9c067254752d158c8c6a668803a1a21870c8a3c9a3de478645b5e0ed4c49f8a342513e53c529ff6f4bbb25120354e0a4560fa81c599be476ee4fbe29d3c4e925087f63e6c1ee9ed89695ed222006f105a6f7c962dc0867147f8a76311f8c4f01abed7235230f5ea1a27a17cbf9fd5fc7a2b35d1ed56766386421126034f24eba05b372727b777bc57d99c2d6559863f587af3d85345808c504a473dc36323c5485c272ff023a31bc89d05d64b237c1193cdb839edf59c646d9a0065fcbdbaac43840729a0ba009b0786549dd366ebed8872b8f1865fa72d298ffcf3ac927577c98216f9e74a5cdf0f142f7ce51cab0b21dbac1a642cb207161e62946b8ec31c6e35072a4135971a3ef84a3a03522fdd3cf91ef2e5bea34806301b7d93b8604074e5f58ab396beae180dda8ac356b24d14b4dd398ecd5c362d871142f7bb1437b9be372cd18a5f77ff2b8ba4d4343ec8c26b99145e3abb34b7e030dea2b153f6f2442570772d03817113ff549f58c59029f827659117dc5881f77ad294d3c2bf3ea81727e646d14f92c4da705c5b92bbcb7b7b36af20a88ea7051b32d488391c685ec2d889fa2fc1331863f3d685c63788431ee8def349a314485e126119cc249e01672caaf2cb3a376fdfa3afce0471a173a4ad6065167f0c0c64bb780388245c8220fca8df6837d85acb59279974ea115941ae4f7bb11bd3d60cfbe56de75440a5a318063c9f3bd55526c706cc100666676db92e0d8070551cd5fbf8996c7fc7ef434ef56dc8204b55c68a41063b45ab841c757ad10bff9bb28dd48c46bdd02874d72f83510d647841c1fd906fc4fe41d109d38a898747164928c6c952069befe1a3107a831a1e9a9deba3476946203b6dec8c3f60703b2530f4d0642c5036d0d7e4f214f7bf4df120855103d3916de385b21cf4f5c6785decde3ec14f573edb2286dfdfa5f7e0d45b3abd987cd88407cef514a20e7b5223256a63a42a5586fcb65721ae7ab33cad4fc6fd6ad1a0572bac572af6a02865f85440181170e30db38af72e2c65ce66beb3f8ee09f2c151b18df3c1655baa6c71d2288bf18ec8d6f69006fd63716d7021e0e6a7181e5df98df77c2131ec6ab060892d89b24372f16778972876e649ea877eb6cbe621531d2e76eac4fecc165b2c26e3e937c140dc3cf3f628eab116d284a29e5b89f4da74cae6530ccf74c34e574fbe0597abe38b9c843670e2a0a546c4662b5dbb6dc46bac404a7447d9a90b784e404e89c5793a17f0072b6d8b24b0de23f4798d418e1a61fb9f8fb620d949bf8f65e77534d05c51978253bb4b446975d10b7c4409ee617c62dc540387da42ef1938af455332ed0c13c304ef3322b8588a9355849e1051816e40e6af518f41e20a1ab41449d4b5bafc5726a8630bae559ab3f6dc96480bc7363ddb5a4bf859ababfea67393ff2ecd71372d0d7356fb882c1ac993db2b3ccc9c8051b40db53610a1adccd50e973f918df579f3e4cdbb8d365c771a3cc79b82d11295b2d94b651ead7a8dbf0f00bb44527729d64dbca45d3c2da12f9acfec7476a021ea79844b8c6c4e1e9c111d4db36d5720f714eff090b7c23004a63664c60e01d30106057c16256270ff83529839e3172f2e2f3f9ea14cb3a06c4cd973f7b0ec8393ec7d48b85ade3e950bc46324c0c729837e16134bbbe2178353269d02861c5bf162b9b325cc2029a44bdeeb0194d72cdbced84846ba291c23e69fcc32257d90bba0342ccf1f71099966620b47c2372aa6fda747f225c083c56aabb822f7f021e995651a23c5b7480e7758a590b295be696c9b2c98b0d29b23954ebddda06d6590a3f2af97082acd3ae101a3df7453c9e56108aa443ede9b801b2fdd056bd9d8359b56571cb435a86fe89e9536b0908674ee5627dd3c36253c77a51a100aaf357ff9b69be5451e8551366c33e00a70e3a91f6cd8b3f076f0d6d23b5e87a972a1846ead11ca77eb829b1f7cda7f13072b09c0e700c66046541e9d7dfebf05af55600c5db35f4ffdfd5ef0aaf8f900272f7b85a3ecc2d9236cc0af868f0be6f3db2d682a7bc134bfab23361a9bf635b723423cbc71275ccda2a9095246cf0b7d873e8b5f8f7098f070bf80f090bdf3c72e08a810e8c1893dd02725a120aab7f60748a713a4fc264b9e7f84f8938299a729dab18140f959b6b745033234d8c1a167cd2e86dd2aa3a19106a52109fabf11b1c5aee57bd62fd7c8243790a9f13ca6f7a924d2aa79f5c383575f4a626e9ee723f609fb7b9cef0bfe2e79bca51a0aef0733c8c91142aa33a81995e28abbb4d3bd3da66db817d5766ec466420095440346719148aa699879853ff29e6baa178728b4b1ba1cc8d9436eb0e027e12dd1fb7a3b5ff9fca4e2b5179d9f47f44375807d2f58a3479d9e0ae2e783d3a4c57bbb1ba52cac01468b33781bc645d5ad79142ffc36212466df0d0177e092c39554fb2f1ae636a297337fbcb0d06363ab174727cf9c228357234b7759259a1586922ca5cacf16bafb252fdb91070f7ed78a8691d54ae13334a936a615094856158322ddb1e30b44919695d5511054d4d710c723032825e481be2bb7f02139f15946161529c448653dc57a2a6c92283211b3513865059c16f20da584b6720b0dfc1bc043619652d4b05e8c8976884850e401846336764bdd5321772ea57ca70f59266019636ab035c4e16d1c3c1d3d64f6e113db9771165a2c57468ed6a54f9515562b865c177350c0aa4301a4fbc77e1c2e65c476847898418e05b14c5907c9c1280b93d945e8acbf64f5ea4fb17b0873ed952c58e719e5c4d4ea36f1a3d082e8693521108ae54bd9e584f94114b579440aa6941178476386ac7774228d9f8e57195a8f24bbf728035c99f4fd2b1149ce5bc66de44bc7e2961d176934b88f7283abf92e2eb1cb8cfc4327ce3266b15b8bdc57295df269b6ce79da1c7718d36ff7873dbd264211b4b74d520d4871c805ed7e87262830b01f5b6124db5181c70d85f2993a08ad77d7dd63a777679e8ff8cd6c140e29c375514d7136c126d9b3af60ce3eef2e4f8251dd3f801a208656ca8589872aeb8d7a679a68df0979bc322d77975edc098097edf67facd7612d98ec00abb166ee84987ea48e7d6603a33daf3437ac07459fbf074ef9325378e9dcde18ba55d576c5256380fa6e3ce724beef572d12641c648ee1edfee7086b167fa46794772c2ed3ddfb2a0b10412aa49cbc67368f7b13f47cf511e0559e0c200d5b32164196d7a4f25ac4f141c6ef9c62e3aaa4da705a9e9b7275ed664b25f482fe7189667ac4bd8db0a93468d87dd3acd135fb8afa296863cd4e02b5b33a189eac250db4afa512064f3ca7736070cf84bf8fdd8b88ff4dae5328b4c8bf8a74e0e9a01fe274469409c7563ef66b439775771c7090afb4698acddaafa3315cc6d828f22c20ffc469d87c2569cf91e8ef37d84a6d1a4ab5761cad41f73df1278a42d750dc362e56392d2f2e3d6837f23b68c5d64024d7ec0be81c643409cb7fe3c3bd0e3a3723804f140891719c77fae4b96d733784151770d15d005f2226db91a2874319e436836eafbcbe2242d3b93240b1de2ad3e72f943227075dbaa26b85f3bd1e3aa721dea6d7ce90c2cb3fb38973d3c808b032344ec531eed11458edbda03fc7aed7229c8fada77e1f64fbeeda50e5cb4ea14706a37be7fa03a2696b42a77ffc991720b54679abe399b7fa7d3bb8280fdf5fa8afc9eb5bc95a3f3d1a72f0aab43a90dce6bb08dcd730c769736105b23f9dff10651a8ca93a0f3747adc830670007f171cdec31acb0218615899e1513fb9a71cab21fdaa1ce5e937b7dcf13d9bedbe726298b871ed29a5a9a47e2ce6fd39e1bb36152fd0fdc50445bf2b1f431ed1ce40723fbbea78527b1d1d733ee066f83ca84fa1396afafb59d1357539a81e7a25723a834ebe195d3a4891798362c6d36d52bb9c97da6348799eb4bf0acbceec4472e5f493ad68c5b6b240ab7b4fd7dacde95c3cf14110ebd2a1ffab0dca0c36b672c3475f1a7da6a416108af5bff4d4460c7a3443318164dd8c5ee5978d3466e20defded4c3f885aa3d90b0d40e488d4e336f36f8fb0a8738b87ff879b6e7f951729921574b50c6b7ff7c966cfc4fe83f07872498ec11456cc9480113297e8f0c4ca50c109834edcdf16dea571831ee3f3b04258f7cc4338aee49ac0857e0f1eb72017fc7d6f4e70704df4850612dd05189c2e54387e4f6a7f68be951f4c371f672fd55547a6a61dd01c8419248545c23fc9c19e92f3946dcfb02bd4fe172e54a72419d915b9a59898b952337ccb09287cd4885d05c5ef073679a50bec493dcc772196c691ee4c138bc110fd33a654a473ecb398f193c0de67d931600d96c1a6100558895f47f0ed658d1419f3bcb75cd50759aed1f9d8f8f4ba021ed8fb38389724ee523415080a9d267790780db2b8c6c23668e514c9645e2aaaec90c21a4667292d00fa4b75df004bd91264dd07a7142d486b9d34300766fe3bf57c8cb17a3724b2b3db212be7e82dfb421ecb831647415b1e09d2557bb08047fdeff87c77f0bc7a30b1e41c2bcf6358252327f8144eb12eec66334c4bb93a725105842924a72f2796c73bd4ba3ac38ff5b32ee7b1eb43fc6db75efbede142cf75463a0794c713eeed930edb1cc5e6bbdcd131f9b2fc8a36e70dff416966d3bd7625c9d92b1359e9d025a518bd1ee9a978073eb12ab954d64f0039d33ed5b15b38ae3e0b34362c123211f7e5e3a7f21aae9538b4986b1e0a63db41065c320dfec351a9828a872cd54204e9df0178a0a615e5d58d81f0359f2e1314fc5ebab034ad2b799eac1039607ef20148b8a86708a8b47c61aa022f18b824d332e0fe41cfd866bb7947772c96fe19fc69b93521e1822dd62c8a01384ab7028cf32425fce9b7111df2c7372adc5e44b14792019d060ece41073014bddaf7dc3cea9b6defa5991483a37452d9638817cf1a7d15e50d2440f5867cf7e46f8d1765fe33ba0690eb19e56d33a7280ee3b5c774e8f18a3bc0c3ee7700282b5ed31831282c352e559e5bddd4c6d72442bb66f3ed9db064fc488d2c62d00e23450bfe8bc12787c723c3c06440c1950efbcc0ade0f3d99e0f36efcf1fe03881d7e14b51f75a5efaef2fd33193289e72fa2abe987db75fef9b1a36271b39be3a40db4411fab8df41ee625399658cb0728fda6e6977a292b6db0fd7b844c117c7e66433f6cad3ec37a4f11d782e738572811e1443536c7b08ddb11b2623584b84cbd990d072bf1ffadd663df7572e07199a845089d16c867a75400833681b5966c3f6a4ec200a25333644edc9d574cb7293d2b720f0aa4d55d028ba9f6f44668da9d920010dfb3147b0a1ae9659f5497245de8439deeaa69368a382cd59f429b03b15c8db9769742ddeaf7e422dec1772d14fd61fbe7bb34e3a159d479a75dc5d4668f6c592526a139521581463822172f1e6378275d31fd274047d124d0fb4060df9ad3e341294841ff08c003cf7cf72eef274b6f1c63ef9432193209bdf685cccdebd44217504dc8bc48e7c5bb31e72158a57aad14e1501ee69df58222188694ba1f2b5b84262d5896b52f397c27c7218a5d51a6d35375fab38eea4c56aa541348771b70711e7b9830135860c419404d5b6885358b044f8ef3c9019fce83a87b18d8b1833ee0fe1152ebc4b96ffbf022bc45986219b0f0156b4982f85c5c37289abbd160ae890c426f4b83a10e616721fe0f1955edc4f6f070334849cc41cc04e1090855fe512f20177b7de9f2bcc67e85a98fa93f883c0e4e4a5357d390a83b1136799e3078321d3efec30691fbf72ace13930ed01118be88a8d9e44521eb1701e3a5e3af22ae54c13a50a6545b4722de7e3fad3a53a6d0f390092a0a8dd18cf7eb9192bc9fcb2315536097d89ec70c3478ed69579e8a3fead53b627513f119c9092bd21adcf9bd599641712dc257250fc91f5befbb93d7f53a269fc2007d330e697fcad8c48159737eeb9e83a04726dc03a11a00e88de65dd4942dd6b9059b8d4e543648fbec860afd7485c105326726060ea6c783c7cfec7771a82dede4d214eb5cea26c4ddc85d932824e2f9072e88d6cbbe12c457b95d0ff867abfc4286677d219f61b37fac5017abcb5c25c72ba216411c2be681ad37c92ad64ebe6ab4ba85768d43b62d2fc8b6be9a652ef578bff3119190c289788afa92b5c2d30d2325e06a728e0cb37be4c6c7680919b72488141dc6de77e4950e6ac9c80c4c505f9e336dec54386805360f71c719fdc72019be215017be61eb2c771bc3bd9431eaa2885ffbf9ddbfaf1777a00f76d8e727b1b3e6d07a2d83961ba20bf047601e4dee318c4f84cefa15538850c86c417641933662b007a6b2045c4e165d40aa3087af15251e111d20bb3db5e5f602dd65e40480939903373ff38bd1effb8f0409570643631dbcabebdf6d0b2523740bd4116aa221145f2bf4e5ccd0f59d1a2d140690717c2c6fc600c744043490a58717215f532a5709c210d7b1a9fedf210ab98f631f907028e35653e8d450a6010ea72c2373e1c0ea6bb06b3ddebd3d5b34b66de96dd7d5509bbd1d0a63cc022f01c51ab413c2dc3f9dffbfdb4b1fa7235745f4dc64f6307c6ba4eaf2d9b17efaa4272f42a0062448bd3ed933b7ecc99b683ddf04bdadd0feebbaa3830978c36d39f108c983cb1307f522b60c6db25c4768f9820a607dff516bf4d86879b341fb5991dc120075c8a6a6f1c0ab1bc9e918e6edc75452284873b502144f7b3c3430d3f727ac6b4a481572dee5ad8fe135056c80ff82d2b83fdedd6f78380f2e42d4a75032181bbc105c002dc749f8843b59135ad768292b09a6588d5aa1bd7e5591156161df5da9532901a00e7bbe3cd0f58d1835647713f5e20ae6daeb3669cbdd51f4d1197290434906b9d9d83f10ecbb3f9f34d39ce01774626a3ef296fc0b8ea6e4af6a94b0911483a942e0f22886beef666a92c8b61263306aa50fcbf1f921d493da7ece9d1bd1889432c6c5a6f1b189b90501ad75bc22e7c07b79ddd7000117c326d37464e37d1591f5d82be0af1cc83065db2f7f0418c79ac4e3c12f4a7abdd7224df76b6a8c99aa302fd6f37fd9dfdd7c1bfe306f38e44b0878179a0067cdb0b76baf6a4d50ae6e0a7f4559bc591e47d7342faccb3e123bc86be6213edf542727d9b8e60f8a5748350603b6bccbf140d67e76730aa18c1b1119442271a143f724776d6a40135a6d8099f17eb07c9c4e6591fe23223fa507d860f8f98204f7e68c0cc2021c4f292a036e020b447a958a788066f0b7c7b208215315929cc6b1863fd2b3ea0c615f8d85a398d13abaf362d866f35883655a9abf9fe7c9748834d72ca24dd06651736bb74976a232322f59e382bb2f301467b4a720e5938775b3672812fb2f9672ad738064e00dc1bcbb76dec1b840fd55cd8179773ca697850461ae74b1393e4647d103d8873ac32b4c3dc4b9fb2780aecd1cb901433268d62eb662d1de67956e574154283a9d638b29166413d241cfe1effe32b3d108af792fd4bb56557b864e5cb478beb6aa2782078c8d8e0290d089f291096b5444f41fb7572946648c2cd9f8f9750fdfcbd565340e561bbe985638970b58efc1a7845257e470421c97d9a294de32fd6564d3558342b35dbef85be1ed8ecd57f81424b856972afebf7e281360f34c73fb0f60f2e8e921c2f7475ae615f0a566ddaa9a6717572d61118fb8024f8a45678ff68afe7d9de2995cb92d1b2d1ded968b090709979727f31ad3d6dbc53bf69c5a918ced6f6fa972a6af44943008b5d8682d8a6a9c4720c41f69c1a6f7cbc6ccadefba0009af9658c3b9416b3bb39ec84ad945856bc208bd92cc20ec7336110e202ac2189a75fe0c9f5bc96c6f2fe5d43c834b0827672b2d80e2058015b223a4e0d60080d4d4e195cd98564729304a2d2c5146ecad572164811746431eeed4effd19b1cf30bd3283469e52e43ef0700ed61294855d00bcc5c35b0943c451da364c08adb7a8fd001f5111b68b8ceee5dc263d0547bb472466f8ca1f69c0b5c0ff61c552917ecd563c7656acb02d26e3a32d9bcb61c4722bec82fd19e615ccbc50162117eeb2fb843daf9efe0285619e332db75a4321272f810538a9c4e5059c0d7e21869806681b451b286a24f974109f34e2a5965f7721378d84922093baf5a3fd562640e6b9054ed767457662263df05aad2c5c1e0722070aa6ff20a2c0b74d893137906b9149486f7fd2fbbd5370b8e5a53708764721eb8f99fc70b3a2d0d9d57772b995e3564ddaf99621fec6a168ce2b69cf2e3549dd5e42b8f1bee2240a794dd3b786f618b67f1bdc7242ed854044d007f1fbc5438fd3e54502bec560db5b5553dfde1fd04642c1e4f95677cc5a01d1c92e82272d313e0a950e72d078913ec6434d65a31e638b5386fc5dfbb851fda5a5dbd216ed66e2a41394a2314746ae04d3c72ffdd6a59ae2a50b9627a1fd45fd3905b6972f212fc8994bf8365ecaa43d9378a7d52df54551c171b138a6dfb90be0b36e55677aa9ccde8efbe80163aae87a61d1a80ae2e2baf5db0ef80dd51852e1341663278a74817cf1b80341d6ffefd345f7d530894b8d47db15df3e0b3746ddcc84354509ade34dda78fa6bad22f0864a65205d8e65d212c2e8baf18ae8f211487aa46a8882d1a35d6614b0e00a2ee3757e5dcec252379cd6bb5ae112afccf2e11537265f0fe1863be9c9f9589c5226b4c1e2c905534c522c7a5ac795e88e01ee1cc7286110f3428c6f93d951428adfdeedd8d75b19095bdcd0381a053c87243a079727a34a458d33b1b725a38f9fe045d7b168ee1b5022f3669257f2ace9224ccb87277fc318a0f0ab53694f2973f5f05a4957f691b56d98ef7499a6078909a6cd1727f88f3a37250eadc8f7f5dc421c8dafbd6cc1677b2e1fe19c8d3eb614ef15572cf848caa31fb9bfc406e6a9bb8538d385939d5bf83d05281a214b804d3a3292d5355b78f2bf6c44572ae4d0ada077c5fec7dee47348283c41383be675fe7ea2a7955857bd360d1822379df3a59ee2f0556bafbd73422729bec1d0633cf3fd672203689874da754da1f244b6f4c04f69cecc3291c554ba63774f983825cf61572d93660bb5899f58c35ce2cc4b71193b791bb81293977d7b869a4beae4877db6f40a21fe87a0887a071513b0c89572987d5642496cb29c588487e9060c395df72d9b0a1af039a1bd09575ec872c25918f8db2a0a80d95189712e974b20cd6fa72a872b2b6f79860f1b02a8405958d864f978034a7d16f404f8487f133d78b8020cb2bcbc640f801fa81bf45c061abd184181ddd26f4fd9f61d555cb1ca18a2e4fcf35326b6185cdfbea5302a2810244b721d8b3a25ad63bb65e8fd8ad1fe8ac0e35dcafbf6382932ca134832ad68cabc143703d6b3a01d901f59bcc6658e1f97255c3f3d2a3ae7a1356a53fb5ad426f03d83d55a5831b57917d3d16b5ed68f5720a2ff6791b93590d231e5fd40c74d3d43c1ae05a94f9ce66acce932f792e4a72a9eb014a86c03ae3de90b019b0e2016f44030dfdd0c768557bf2443a9ef29b72b429edbe5e55210dfef52e8475e1b5ff583142d84b922870d3de61eeeef85313ebb01230c1fff42a6f4db212d42ad24607c135fae2ae6122e2bf0c55c4610129fa548815f9e5448911c6b42154adbcb93c501057eb0a61e6cf15934992ae4f6bacc519ae6b85f796860e86b17bfe753123ced6679c6945b65ade2bfadd2d7a4c36f86f2260491775ea6ecf0a00a1216e5ae4d2ea6ffd011d58074e49d4d24900163ff178019cf0a949a6af516fde9b20efa391a07d0e831f6345d03cf8c84e20e8d613895f9866642e127e4efc361e96a574fc531c2537c2e71a122c3976b77269b0002ca061036e5ac2732526e3b5d79f0f9fb7a8c4573df308d29550f2d52601bfd48a31be8eeefcc6cf0eeba07c8097b7998ebf186781e4c0e1318596af72b981f5881d2fbd8dd5dace649aed79c5e5b4d32c2c520d4758e8b845efedab0a3b6cac36a83be1962d26b8892b98ef24dc44444ea4e28c4b0462d67640816c72e9d49062c35cdece81b25c5f788335ccfba04e1900bf084e89ec3b6e4386fd7249fe91233308304f289db6c6c3e55f1b32758bd607931e138d024090aceed872f28ce0ddfce0d02b0988a5b911aef66ae1587cb2aa7188d30e5734d352d1192785be3b7bf225bb26a2edee3ac3ab000d5cbb76e8a0039ecf4d6cb760bca842722c050346578b1f9bc3b5de660700f95bcf3761fd08eeed02df4fad453cae4b72276718aa583a0a67fdbe3c321896fd91581f86983ad632d71e4e6f5d49d72172747884b4bc731b0e4e5918dd16b3bcc9d764b3551091c9c57526757ff8814c687efafbfc16bec2539a1fe64ff34271d075c7a3ce7d86d909f1187e287f8dc67258a0dee439f320bfb2355c7f89daccdadfce2b1434194c3321535b4721be0b07ffe8dae79ab2bd2a6e30d5a96e9b6693257c365a4471a7ab5d54c462f41fc17257fa32e0c2518f1bd71aa7fbc98b5ff230a8be6023351c8d1bf283516658654adb7f2df7c722cee4f4c047830035af8e36ea155963588d40be82e691ee778d4f97a0a5afe22963bfd9d41f7756b63c8041d17ae19d0ac4d2fd63540a69853003372972b369d57128524256d15dda4a1567d14c167036ffb137683cb1c42e810da24d75737fa78adb7d97df7efca9abc62bc9107d7ee0c43f09b686b8ebbbcb29db3713cda60ea4b0556a441d0531b2e43669c4999f3663a39331753736c4ac72997637c53d5322c132898a7f9ce0d2c2e81fa1d30e9a2947a45a569c424bab72f888f965f3a6306d00767f75845ec90415400bdaa27cb80b8d3896b9ae9efb6cae3c0482e4bb58347b74c7dd6c7eb0f8871a10e9b355a9662c324ca6e88da572bdfc69753a2c822d82cb76721ba25af34848047e2fe0d9831bf08b55e6f32d72534f2ebe14160a990c74f2328c7ea7f7d24f346674df3d3e1e2482294287e2729f4dfc78f862db88bddbcbc9d21bf72d14bd93701feed874905c8605caf1c54e95f91cfebf324a875e3f00ae3be9751df5ee4439af04d3ff7890b44e593b56726d3d380df2a64ab62818d2657ee038808f2b71d229b19a942814d2f182f9df72b20e40e9788f21f4917281dec920ffdd267f732bee36ef4d5d77dcc677296f1e5df6330639b97bca92d485d240e0eb3aedaaa618cbcc7005cb36b3d54b1c8e722c4876186a5806b8769f9956b66c91e7a8607c725eaa7b81eba23688dc4eeb72afbd43e7f4b957134a0ee1e930bf296c65954236855fd173df5a81ba6af661618745fe03910f99d838cff68c3f107b53772fedd79ed4d34d9b33d1ed69945f06e3091175bfe4851c77892beaf1e2d097e3ea0f838f57ea79a24afd67b8e461569d673fa7fd3e04808e1142d0759d35033ce541ca6948c3e86039c7ea1757ca3295d205ed27668040c4d15f916608c72db39b65a3806a630fc14db7d55a332572312c4d055ae59db08bec635aafa41967c74f61010beff69281152ed9ddddd90b40ab780fc9406befb239f00cd2dc55727824d740d89f135c793ea740eb9ace3b9f069745b088bf2447483413af23674def14d067fbda165bd10b302f4d7c0072f523bbec57f46599f98efd7e8747bc56be34f75c45dbee5c0934d8e7e744357254425d251a346b7d44f8b7e9e5333f9c8b58121f821800b5fec641ab21bf2972cb666414f1e838da7daa429e9ec5dd7cdd3dff7900b52f404d27548dd65a222348ed8f40c11b1b90985b770dc01b085a0c4a9f5ca954318db4fd18a30c2ce072afbe54f2ac05590964a55cdf1690cee55de66d6d4314579c37629bd1329c047235561a91e801d32bb454118a8524269b83c76244880fb481134af026b5830472bfb7775f12e10f1a910f70a7fdfa63d08c862752a4559799ee3b1a0538d9c33c61213b0c7b3acc3ddaed2e7cef22cce4790db95346058e47013bc7103be10c427a145ba19494c88f62a843f6569f83ff57b4ffbc823f7479bf97225d932c6f6e6d5c39a5a801971b42fc7ead235402a611a4848617bcab10d7f863e15609327222cf42244c912dd9b36b540094d0a42e4a93f1c6506163b6140186893d304e72eafc897a06f48517e3778d0cb5b87febf42ab88418f1ef5010e2685096712d05a1d5ebdd7e7a4cd14f14c93aab33cb0559fa1f88c2fa29aa53e571d63848a827e2cfede896ec11a24f2913dcfeae941e8fbe9a9accf03eeda5d134bb6d60e9576ce4859767877a4232c6fa1ac1d1ffbf1ac656dc26547e04d7716c28c624a257dfe44847e56c5b12cbe590da6cd3565cec6c021edba51c1d5e7292b1a6f8474fa7973f9d77328cc79f94bbabb4a3e9cc1b8509f09164cbecaeeb8f1b029c13192b661beb988a6919325ba8650043471a579684ead60a07ae6e28a7687fef844acd1f5c272c0f83bff773144f62303bc63368fb4017c788ebcf5c36129781b70bcc58ec6bb884ef268ff37621dba118d01b524e54159f4469f4955947070e6f7179f7e64ed014154c6522b3aa830d21184c13219a8035fd0f51da48a1e62be32c89d67bcee2006fa35a7ca7a5c396f9191411621ce7668d7f8ff5a2ca314c64728c384a47fff48848e9e815e5bbc900414f3ebd3800151b2ab5952e6df90a097235ea6119c9cedfe546c0b212d6ef34af019da026e02ebc17429d6acff229181e814e34580cf1272863f42c9dda046b83b4996274748cf361fb0a6096c5bec0724b469b7ec5eda40194ee0fe10d5b6a6ce941a5b69c8ef791cc6bb16f5837922a03c90234b33863ae24cedddfd43c0db5f45cac7064aab2c63f7edd30fe603272af3ad572818ca74ad54006ada7d395d842cd9e536524746f8a2f8221ae22857257f258865b80e6ec3e6a808a7b9e6d75775b5e7493b4d8d231ae3420be116472949b86d8126f8ea9e4bbe4a548a65a66b3589701fb6f1c793568ddb521a5a7720296c1ec1728c64fbb5daab52175709f172594fac7d225bdfcad41e5260b56721d006742ac9ea4cb8840a4506b9c41cebee25a8a435cfde489d750d133a6d84183ab1cd3c79f58ae247718f3b756c02cdd86ac98831372a2c1362b52785ca7363cc42ce926921392c18c0cc268859b1fc74f543fda7f81f4b0138c8b28959e729aae1c0d0649fd53729e878293560f764ba563b1ef2c305e6cba0bf7329df0729195a06d462c0fd93b1f88fd911c8e1b987eaf8e403f0bce8245c9af0186db2704337b3f33ec00e59ec5c730c5b33ce6d0d232c27e558533e8410ae75417875558fb3b298c1c5dd62ba151d42c45a1b7f82813f31ab905eeaaff58e220a6a572b946b83f950814342e65286cc78bdba27ba37b97c9b30da4f6d2c2c0631bff72ba757e5a933b93b0812566ddba71a7ff3bafd2f063d1f589f8120584138b06727f05115991b6b53f5c53d1b2a86d082366563cf6b3c24ca3daf07e97e7edd51472cc48c86a8ef79c763e4ac52364f18810cc11f8bf2b4d517765d807b07da172ca073039a70e433a95d1734765e96af756a6a76b0a55c2c11bc7b8865c08385fd396d930f75a1ff124c097330cad26fa5f9c9e3fb512209598f8857d6957135a5391232f9bcdb88ef68371b87e154f98e60fa3525dda4fca7f819bbb8e98514a9b2d016b76caddef8441ae5a6e4c57f3f434439f61675918cc33ac0a2d236154ca5154d10f4d8d08208df96bca512f7ab6002a0425df79cf6cbb0071d4725b72c2feaa5a26761e4e3d52d30c135e579c74425859ef16c763a96d92567faf8972b1925a052e69dcb31ce814f79d3a48c685cc2903d53cb654922d4737f828bc34d18fd8191018ca3e55ef88e54f1d5cdfdd0f563fde969b1ab6d9b66ca7cb566ac19a0c25a3e317cf14f34b1c2bbd3fcef01bc2e893c0b800cf16c8d48162e1723699fddef5457715251eb9742a9aca0338549172fdfbbabb19e75e20b933ee04d7a968f265987c8a96392ee7e5fe801fad9fe9cd40183b641cacb2375b5d2b722ba08d104f5e7c976d34d2e9a93db4117f38b12390e62d34b9616bb744a87b724b79fba03d266fb5b6520a54a90fc7cbccf53e8ecfc98435efea4ddc8b25cf726daff7d0f7ef02c792325ea08141b146123f49d110a73a5dfd73f059049af772719f4ad96178fc49b26bb4870964bb3da669e79f1cffc7b8733218e63f786f72f05cd854ff73a1c2e7ae64a8f5f41fcd1f5b99e56c2a564758fa3216604d8872b1d8a8ae7c2c9f2506fee7a8fca8d28199222be0105536f38c5a13c095cc2d0253b9477e01f513e252487acaea26e34ed2db9a673bbcb3cde8f05f255ff89a72b5fddeef46333cf7f8dc54418251e9015fcd4e0d87fe7b1e2e6dcc3a747d2a72aaf27e521c5eb66d4873f220c69f516edfd10014b7d7c05d821b65dc2cf7cc72ada543c0cda4792303d6fdc3eb6fca3be63f429c58ddd339e5c2b2e256cb1d7213cd66a7f30fbf5826c194f397d6376a2f946aed72b7abebd129f866741cea72a3137ad909fae546a9ded895893e2f18278c3764303c69a9816eff769958f97225122d15258f8ee86b1d70127c4c8b0121f320e616c44b38273c48a4e2a2dc72ebd83739ce549556abf95a66d905f85faf59aa88589d31b2ea1bf580924a255cff6840c2e0e2707c9381e57e872120a880d7a9f5f4ba61537235e7558c573d03ed474a7f139372159af240b83ba8cf7231b678076ab743bd81af1c5db840104e72a6e6c6a80ab4e2e7b4e8552091c3ab4b6cf895c71178e71089acd960091b59912bd659226516e0c5f7c0cd4b98547cabac7be1cb01b9ee0e6bfcb3c59ede727539b6173d2f9a2dde16ee5e8512744a341b8644891bd8a4bda6f5d5e836c5725c9523b4b628bd1bf4237890e724825ef48fdebe98115caf822c05c61685b50e9ea966342841542b9da8e08cc6036d9a65e2176371d85ea5dd322866b972b971970b84a8b28a6ef6540693c3b42ddda562484cf53b6f04b85306176ef6597f728283d6548ae339d0df406ac951c6eabc8a53177c161c5416360f75521a8d9c3a1df2e9bd559fbfa9cfce283ab4714637e897d2e542f8e39a7b8fdf4edfa0b8720e0dd01bd1cb5ff2ef5f1602ec835b23fade312225216ef9545f8eb7828893233fcaed9a5f3700676c7c1ef4c11cc70524916a56e974b225eb4acebfcaf063081f5f8ad8385222ed15556801cf2706bae39a3cb2d444ca637aa0003c8faaf97263f49ee57ba8594f30df31a28930023b0b468527002bc71e6a5635d00ab23f727ea8729b4e9b2cbe8ec3cbc269403fa14a6ed31c3d634fe440e6844d0b74a072118e23f2351fca172e6d7597336f5901ba687180f70565efc9feb6bc950b35722f4feae720020455d34b12214b78445990a25231c1568968f309ea76c4e9a272c098e55f0a9461839a4d8fe8f02da423b143d60e92a8fe3758da55c94d338f72bdc0af7848aa166b016d2a75ec35aea5c60cc6faa1f98b4ba4e66acfd96e9a638930bd53864c3035ee49b2e72dd88a417cef2ce4b22dccdc198b43d125b8a51df6dd4d34c1e9c7fa73a02e895ab895328cfdbcd09718f43e19a8cedbd8ad6472bde6c28562f7ed5c668332d882142884064af7d18121d022846f5dde8bf06a72de3c6b600bd0b8907b34f748c17752cc1af5e598a6d00bc14a25e22e98db4713c101ade512c79eeb5306837d1c99bad5f9bb9d9e6ad51bf486a29db0cd0b3472b043c94e7ed0dccbeafd97e50bb189fb7762b175829e605662518c114326e672ffce05d24b0c73e1a1d6fa2ba0f9e937bfdf14cba9bb7470978a6ab58e713172000840f9b8a63158494419a78f07126834e6dab4226e7c36ee55f837f92c735c26135e768a6874e764b4b2fb568804fefdb7066f76da8ad03071622a51c27459dee20ca0a4bbc4ef157fa6831a866f18905860c0b6b8a20ec1cd569e112d2872a45cb7c781e247b186a9da1836664434f80600559d2c34657ce7ecaf2e9f1f0b76251f58ad4f28a0974e44b11f3f0719ff373c9781c5697889462b51dbd6600eb17ec991b43109aa2fc10fd090f6c3dbc28bd0061551af1999617904b93dae726963def2600fc5483822ace5d47eb9627469a9682048bf8b50013b67fe79587214ef82a0ac221d57d16d8a8a599ec954ef6458e5cf9f7858e2612e4db318d260a2750568ce14a9199793c076a8edbaf0e4c51c2311f60a11f6611ab2c110ae729646de1f1acd97058939cc368cba608d03298df23be5c690b1c2566f90427772c3bba67a5a6422f322ea29282c6b8f5775d00f3e5b4a21945d0d57f091f0887274c4ede11176f7af97f9c5afaedaf81c0bd78dc9aec3d058180ae3afadf98a72e0ebee6102a810dcb35a23821113cb1d49e22295d4aeba9046618c5fd7e7e3722b062b6f6908952823517dc0acdae6d1dc2532f90d033401fd8979d12715f572679b1def529278be6a7e473b9c2be49468ac21c57bf20bf746fc5091731c4b720ecff8e38d8ba386b0f0462676ad04ef19fbae97cf78c0da1f220ee18fd8e01d1b07b5fafe2579c2ca7c1b8c7ee24c8e890d3ec39e3503f8564647aead3c4d72c733a76dd5d07b530f4520c8ae517546d6fb48e06ff0dfb0480b92f285b5c872d808bfe1179f2e3e0cc6379865b6a80b591ef4da9555a3bd9cf85cc429c17372da38694ec4146442cc6cad874b0a014869b113edcdea768dffd2ab7b9965e303a60cd4fa5850cda7d50ba73bbb751e0b4e3d5ce52f9a3392d12ecf87e688f6006974c19364b3dd9af5e4aec65720ee33090c5c2d96a756dcf612a2ed49966f09d303c62be91d2e096a4126640ccf517915accc40ffe22d279c675d10365de13f1a5885cb9559861da3c247a1534b48433e0c207e324e28b6c51ab7893bab007244a23785c1494ec29460b3b688334b8db7329fba3c83884a0af70e3ae4f73c2562c0b7e889e34f231784763c06e907ef89ca76470160329dc1cea68580a15f728a184577d9bb1a9ad5bef48b239c97b26919ddf37f6776f5cc299cbc5ab2b005bcfff5eed39d4afc56489d35f721815cf0c4bff5f7c833796bd11cb575fc84723ac47125af92c43471861344855e71930e68564a2c85e9bc17227590d8849a7253fa3a2e4642049626fc4debc9d22778a93e8584c4868b3d561208f38db63121cd16a3e697bce5bd11cfdb19276a4b6d96c45562c5bbcaa7c5eb4f85c229f548be5832ff96db01360cd192d0bba32bfc76c0595cb56a7cf440d60b8456536d721a30ff0fd1a70fdf70a23f14df0058cd9554921f1288351ef24bdd7190ff265872d4b7d8096c02194827e7bfbd49ba07adb89947590d4c0b8079227a58071b7225c3eb33bd3852fc8887093d290aad7092d1a9d04318b2e4e3a73ef9ee135f4223f79b5b47125bb0dc225ebd64a08e0f4f8f04e4027980f0b8b5deeb7c100717f1f39b87d8d4a2bcb25241961525553a3ae96d2bc1f42aba0fe84cd701dda212335ae100bdf9f74e3db2add4e038c26c279d67ea8e439431179ec9c09d59dc1ab1649d7d31e4c4b5ee843e7591f8b956376d69e1ebb2357a7351fe2d9d301272bdcd162bb289acb7e0bc19339afb39d50b83214bd5d6c229d900f2c14b6fc82044da347988c2e254eee8913cecf2dac2028f9b6f19e010114afa68bc50614e31a7687abaf2e875a33d7b6f6bf8f5b82e1dbea6d5adee6661e34de38c8270273f474ddf43074b600b6b79cca130e3117d99ba4b0a1eedd70279ae96df32e6e9472fd7ffdfe14d236fb15769ba0a928b70b23503a2c8610c54d76f3aba22f3b772fd707e4788665f76d91f42905cc5250f31a7a03b881058ed053023f56afbfc72b6ddf01bdc8fb20704311c375ebde20526f329d16b512396ceeb5090d7ed5a659c154e7f7d8cac3f812a1dcf70cb262a3b871b1de170c0d1489556c855c79c7233c645148eb4fe59fd088a0e88215f95a387934409fec9f06c364cf20c7e0338b3075af35bfd4b0015707b4898cb62c53a3359547a1b376bb0a322230a2a5d6a79f5a334f8726cbd29ab475a2d85cf9e5bf09d0e3e846f8ba620059dcf4aec72e1d41b02d0b878b956838ffde5577d9cbee437d70abfc6b2382e84ab2a393d721e04ae799ba8590dcb08d9bac22985b919a232a95c323e23f22781f7b29ca472a079fd519c8f8f1423025d077305c14b8497591a66a8706c605f4db018385472a8c3cc1e59f776c962c7ed9e19396309070d000ec63e90cb3ab3137821a29f2f0c661d877a374a35db858aa2ca6a870ce03c596b14b336701ca5de07cefca372f9fe2ad21f75512edf237ec6de69806c7f9e22a830b4891d73b5618ee7d59039c4f5cfc4cb6b769db27739b69e0eb776ff9268ff644a7acbb68aa99ecc3fd672766c41f0a11ac9c63683caae1ad6944fd3a3d8bc905ecdb80435a94d64e3ae62b0c6aac8dacf36f98107a1ebbb9cab0b5fe9bf0c8fd689d25ef97b43bbc66e146ea1de51293e46b1f9efc495d18892ff374a0887bf487415556ddfc8a6038d3086570e4ced208551faf8c5dc92988cefe87c53ca4f4d020100cc8e6c6cc85f7238423a069ad705349fb9c2dac143d64fe311880e4fdcaa688bb12ef89414b17296149bac70ea7377af1bce682398f44b40d323f5d52d2d70728f650f51da5c47f1b3385a48ec3b730a46bd872e5a4c6955d1a36ca7d0188abbbffe97d3fb46723c520c5fa370ed17dfe048dc2f07bdc810f89a7d25214ea95cb653b22163ba44ed22bf38177d9a65fe716f5abf7bfb3d5c1b1fe7517203b96571f82ebd8441720582ea7a4714c265d7a0e2b3b362cab375b2847d3b7dc315bca789e52b35845661678af76303f6d4d443862dc5dd65792cba31a8e8adc1eeee09726a9c675063174b1356279f275620a8952df3363872b9a5a2917ce950565a4a61f8d2ffda23ce2083b66d7131fd07670c7d60452e4f08bed3ea3b07a631078038d32835477204bf12563a7b294b7fd63a1306affe80ea89006dbd8694ae5e0c4d33a14d3c720b6079cc7188e6a0949880dace4496a583166d38a742186f03d4d4bc74aa754504204176cd505417ebe350bde724079de691417f484c37c248bb6ed64d0d326c50925e919ce9e0fbe72cce66b7d300a67a71f2913e6042f65a25e740335050724d9d4ff50984fac3c0f6d4af594acf1f5e6c05920af694399145f26a43b50372ebbaf1c0e775340f0a3feb922b1c2962b8df3e6a4846492894513138346ab572ae576785ad0cd6c2f7ac60e55eb5dd45f3ffc6da8ea08131ee8e2795f1c140724429110ba6919c79270b5db94d3d6520c988b62a1a933f08bfb8ed9131e6876370987c659f05df482106c78452d0899ae635e9077939f231be227233aded1b72a0b3129c9c72956a7478c0eb13c35c012094749a73be54e40dcc232119a4c272cacae06fd5293b7462b12844741039c6f19ae9d6c8fce062f54eb366370e8429aeb1c294b90b7bcf5c336e4572c1150299496d306d47dd416c03141078f85f724f0da7c03a64280d24483bcc1a776afbb4d26b65cb19646db5a07d1494f52572876e943e4defc38cd06689cdb817cfdf03f678d22a0c0db107cbfe8b90f9302d74e2f93e4d1aff90a6e44df53b8a3749f893f332cdfe115c51791d87cacce30bd21dcde041cce09200e952e93fd7d786a86a34a59e29cf70122c4bee3f7c9b72641fc8fc92188e5c7ec15ec02aafd2127d0376fd13c0d47b306686fa91b76b258e4ebf4603106ecaf678663f1359dce5e570696f6ff808f3a41dbeca7cd67516d01ee9a37f845dd05263e0b41881dc518f01a0e03298323c7eb1e65ccdfed911b003f66051711ae493718b2f66e95cd4331a0881c15a6ada30eaaea370cbad722325a68e13684848fe292eba3c6fee8513920d0f324240bb75f05890140821721b1bca4601d498b80e893b06b9854f1a11bc79994b2cbe86d39d76f8e608c166fcc15477bcb0a073487dede16124d1cfb493541549496993c0e53abbd39af74474a2e67606ade17b1847e52cccee2572f1678265055a179db596988ff0c5112db7b02c0d928dbe3eb41a9ad8993fd36b0acf1fef222c91b0b04839842b4a610b7511fd6fd8e7d600912f14b856be2c3404ee107b323dc65655652d6fec567c72c1f340948d334bdd333366ac45f2b1c12a9214fccdecacee38c043a8ca7be7723164a5ff3df644b7bca9bf0259d40fcb6fdd4ca72c3cc692228e8ba2093b25726e530a0838f9bf6e97a1727a327c731fe66ef8e43973c25c8fd751055a54cf13faea76972eb8ec3992fb2bfb8102a9a4b1066fec38cc0ade5e583fe863f36272618782e904a7f215c041ac84c2e64d1d000a062cad8dc6265c50f515072be772bd4ae2acfb78dbf52a3dc8a1ab13e1b7f0d5b1bf81e8df5d4fc28204444acf72270838fd714b2beee36a11af3166dad64cd625db5c0c75fd6311b574ca09d2023130ec06a47a3b945f1c0cacc4c5b92cdf60a763976df2395dded560326daa72dacee832df5e69fde6b6fe26755376246f39b942dfd2bd2d91af4bf6f479cf7222817334f8ecdeaa04eb3e70111e154c2fce4eefe3ad2fae29e05be4a03671720493a038025fba3d8cea7e10d52775881fbfe1e6ab865ab52dd154ebecc0f1532e17505558fc8aa37563508f848ec7480e852cb47a439782fd5d8fa4c9492771a199a60d30a5326ebd0b79ae1236e5055b856241e930c941ff074258442a0072d854eeeea75a338569546777764ce1cce35f484e002f05f74c977fa4f366307294027274ea48efec880877ec6a05e733f3d9890014794beb7a3a83b5e2c04972aa051b767728b9fa7f19cd8430bec6cccafd40b2cfc59dd8631fc397149ade723183a52ecb0536c6362bc6aaf7ba27a60248208d402bc7df2c10a2254b2c96721902b9a973e35256956783b68f0a9850d14fa27050d39b5cd43a06d000d5cb035d9ba93c5640fc24f6b85a4c2acbbe13bebec8298bd4988ecc5b676348f8e47224489f503a45d1b101620d154f628b849a95a4d67035fa57e80a4055c1f8a671eb60f7fd7e9578abfa0937747c8ba4ac288c3c4ef037f2d1b3ff5d933d61ec724ae981f76bf4edcdc4d1e5403f6acc928572dd360222e861a4143bd655b1507243d294fda374bf6cbce40a547fe2a59e02b59bbe2fc91b4887124e9ca30f81721ab6393140f4a6b8e721ffb7885d50cc388b39f436fd4fa0c636437a7b9df1725d8dff56b5aad499c5aabf6d6209b4d3ec9677f11544eae7fda9c297c20e9d72aa6b81cd1e6a44d988254ac1fd0eb636900717b97cd376a3d6ec7dfcd0fb9d6745116d6ef46c920001de14f1a53127f2d776cdd9ea55221ee0d100ee11da857208f1753a8a40b7abbd35091c6b550c2deca2be55e0151a6c9ec5966ee7922a1d30757a16e234f2e45b2d948645b152daff25aaf2f2f4fe9ce9744744578a5172fb29c8141b105ff4af4c68706b7d6657e51f57b3b128a8e84ce8c0fa3099eb7267cba7c3b4160e3c2afdfc05a70ac0cb73131625cd0f229f76642228e99e7603a8972cff0e09636156b975cb83354acc1754052b8af60e8bff099d2ecd3653339f743ae4d99e0bef4fe0ddb6fd98f8783537eb98bc212455d401c692bc993372788ed1b7e687964e687232f613aa023593d90f8957159a6aefc7189477462f7295d4672c1559233520777dd0636bc19b96639dbfa65b7f6b83000f46dc2fb2728258e95100eae9c6202c6f6a7e7c3c212b9974aabc4b37d9df33794788e8de72f01661ea20b2adff85bef00fdb8b561115a674d89dec01ab37e3f5ee1b370e07c9c3666728c0865bd5cf84f8091f48e8f067c34fe742e92a610d6d04c93ea27291176323c650caebfcf67ba7ef4af5d0d86d060ae366cc328ee4511eeed7917233f3e2b7c51fb02906737cc5d75b9c4e84548974b3895b6330530ad134e6a572b11a613cfd9f4d02d6b9a32464f0fbdba31a4ac0aedcda05eb5e7f1bdf3a0f1c27e01b23ca280fa16c5bc2f33fced029e7d59ae6b142c9ff533fd73db0872472bd2423d21eb87804e1dfc1993cf41400f8a0cf8cf2927b2a3375334585a59f727628f156566aed696cd45a56a7a0a6c96d888beaa13b594d8ea4e30088e6da6180fa0f02d5db15ce7efdc019b105a6a5a36fa4839c97fe6b0bf8db34c084df093993e0e84245b64f4c164b7ce1fe7be4cb4e425b9910589cf1e19a7f56bbe46d72b5681ad734331b2e31f0e782d2e4844f9ae12cf05c6fede770f0cb64a3041d4162a712780b68fa908cdb15eecaadf7f01ecd09cac41b3fda44259073424d722dfa01b9e50760b1785f3d16c8eec608db7bf3e9e519c0f90e20136acec716727a99c26dc362694be015c4f252386d22d2164a038c7134fe59a2cece3e7974728f052a0c0a12c456f5f80550dcbc827ceec6324a369c6b673868608fdbb5bb29eb7fb379dfe980680202a624b0c486f26f5f23f25bb920b765cce6310a4c7572ffea0a83d221934637160278bcd664ed5f1c7c1865aae0e6594a1dcb2ae3fa584f7ed7191a20bcaa49d777ae56c0e004e682d1c24e398074c01418162fa62a4e984ae9c980a55d4b7eef6ca48b248b2417ebdb87621ed0e7e7d561275c6f397260060d39e358d913aa96c8b8d4da0369eb7fe068a950647660aa9ff3d2137972265a6a20fbf9bb23313d7d8688fa4fcc0b8284bcc8b2a8d69e02d533fc935453fc81d590ed72adb609be1c164bf2ffd05bf0957fe9cbb9ad0c7d461b59c8ae72851ed3d1a4c9a67f765c84973fca11e0a610eb5db45d08f191dc8e84bf28da72bc1aed0cad04e72ef9aed1ce9422ef68e9110133342b12946634595036e9997228b4a22ddad511f6520c7d822d81a029b68d291056e8e27f063bfdd138a1b43e2c7116038547aa21eb4f65f1dee1b526ab1bd5d272213452865d45d6b8601567179f5ea2273605383e7e69d0a7d6b46d13f435766641d9b1a0a0073356c27072df145c83b3faf2a8c52745b807381e5c0b096e3e55ab085f7396ce99fcd33f07f2e739f8183717e1360ade191dbf256f83b78909daabf9ce3e5370ca3a4d3b720beae6646a45a019cb8baab68e0084d70e374d0ffa77350c5c05d02d17e952724a81048fa7ec777a6670100407ea77739929346886e1f4abb81deb4e87d1276d3d12157bd51bff4b4e1828d466406dc7a607f651e6ccfb1e1ed09e8b64235a72f74611d8d161b5235decceaa6b8273b053fa87ec36d82513eef8dc832934cc72fb3a5b8958b945bd966aa4aef9e7951379c70829587da295501ecaa8a19a7653861d677a99142ea64acde41b1377e0cb467a64eb7b4ef94dbb37e416f9c2704c1f39b5d320c4eeec09e5a91d1e8c332d6a45bb3e1b19b7723db84cf4dabdeb2fc711ba0274405fca54b2da18a7ff6cfc5c71bfd25e8a9b1623e4f3f40c871472fadf0373b44fe170ac4be75ddea640422eb8de3d1c05aacb005b3120391cc64482193d796ed4e04298b8b441c300468f497ba0c75cae18999abccbf19bb8b2729c56ea99c40226b553697ed066bef7ead15e2003d01e47d0cea8876b96ef7872eb074b0106704a75e1fd9cf8704b53dd71055bdc5ba9bc8d9375a26745efa37098f520f2655c9ee3ff2becca73d653fae9977d86381a2d650a41c5f69ff290724b359e855d9fc6ee7a6ca088591e1e90946a2a595107b5ac58c26e041a5c090bedf5caea370446e6e3669d93beb3fb0c20d505e3063d5c4d15d8f987a6313b02dbe54352dd0b58e8e982dd60bab41a8545e88ed7a89d482ba53a3f8316e6d9728cdbfaaaba930f97a9fa3a31d9c5c2a9f0a84bbc54a011aaedb28cd5cf104e72673c792a97f47ae1dbd64db54a0b3ac051c19faf6ef4e4024fdfd25fade60024b05094ac68de26f1061f84e2522085538d1b897bfbb105651eeb051de48b2f62810d9830026b0e304dd0b786af344faacd0ce8b54b347b2da4a9f906bf4cd81b0df8de7514755faed5b3f3e63e6adb186fa00d618eaea9752f0b065427ebc272bead02ac79b2c089ffe585f43f35ff874c8fae0c7921fb90e27f1d9dc92afa261eefc2acfdbc1d582646b290a2d2023f8aa49d9fcbfef8d0d22fb9d3d081de7218eb84cf1aad35bfd6542982284e3aab347032eecca75aadc6db7bd931014d727369970f37e380be397b3ab565a416ffb0773358fb17448315e82266cbf496143692aec3a597b376ccb2a3122a32178271c0c357c7482f5b85e3905e77d6fb72b8cd857c55188b185a9e40c820e231c14bbee860a87cb1b9945d2107221c2972225fbf30d24ac7c970fc94cdedd60712e451f81af257854f80f2fc9cf40732721135b85d9dc72370f96b4cff76ead4f9cbcd7881a7ce2eb18b4e81534883dd4f72060fb32cae924fb9ce3ea98bd21dad8d148e7c03b51fba113e8827102db1504715e4294d60fc8dc8d8b8202d2f3744461b0c8b01f862bc3b2a69e7ff40b2236b56354b2ba4d89a81adeee44d54992147aad7ec6b70d82d41f65c6b2aac786651c4204dfa548bbd83076d293c439a5a53ff23b350e3e4ced33ce34f65fa3872d47faa1ef8a07aeceb8d8056bb8d6537a068139c356fe29590a46c95ae08fe72fc04fa0fda734421b5a156f4d548e3f3e669566e5171ceb5ce973fa710bd4f72d2e29bd2f791647b1f512a39c63454c94d65b78c070e896ae7405b0b60e3de14df28819548b5f2fe006494a18008b2cf3e8122b85c8e268edd474bebe5f7457294bd0d69693e4243f78177d01cba13ae0dc3b386cc24f2c5d2f1ff5e07fc586fffd27d057d3b10a2a0162abf34794664ef7b5982a04c50761b121e4662807972d3cec57587c3b556c6993e172976ccb6738744527a34a03be9af9d11b6a7a072a56b42afa322b2bb2ef9c92163980ffd09671862c5b48bc2e7d5f371ef0b06602bfbd2e1e7b3f978a264c9313e3c86b0c5cb8cf3c897ffd0f952e5e442ed9c727506cff0d126c20c2175ed90c2de2e7295e8c9e83a6295e8bc70fe5cda83113c877f03846c30de41d1e68f1f1b51cef918b1f81186f9ad160484bba4fc32c939dd1bfd904c0c16f19bb7af492415ed441229ece9ce2c9dbd62aaa34d4f7b246463dc58c0b99d3635e311421d473d4d53d3a065c0954b54f814413bf3cc26db3135c70678270e73c37641e6703c4fde0fdb30c32be33d423e7fa5a511af75fc436f52b7f2973804aeac92109a316d891b15a3493f1314a784ffe25b88201a4a0662f3981a47854b8e5a2d7a102a7216abe906362b3c2b717d947638d0a5676068ba87a814e99b9872acfa3692a19f1cc5f343b56da4c2da79ffc822f342d90267c56307c1fb00daea68e69e166182d6d0b0de8318d8554f31786f05761c11db721a406cb0337d3c3da380929e8894815118570379228ea09149b767b948969362a1ffc7c576f79ad5b0548763fc57a0e7604f7e3bd75c5d4bd999f61137e31c29c3a4d0676a8e47228d00d0d9dcf1593bb66eb01077e6cea0fc3c8a3950718629334f8f089dfefa8b4fbf17de10d45a0bddd42293e12c70ffc7fd3530d861833c55e7fa4f39a2994b1dea8225d5a8e5a1635298b8e3f16399b8170cf09a694c722804490260dd823b0e3001644d515ea04c029a9c1a7a5f11f4e356a5298eb0723dca402d23fcdd92afe68d3004d2a5c92fd40d1d202213b2af9d94bf949ff472af65a37c14e5e88916ecec117f73a29dd14e3d83e8813bf1b51127dd04b4f572be6c04bd26656fdb4aba1ee0c5cdfc208c6184dd9386c55c75dcf5c8069d267234edb0097820583717da63b33466562437d91be541ff390d475900bd842cad4d7708661bb57f95090b864687d52e401e629cbcdcadf353be6a4c72bd555038229f1740353e998d8b3f261dc679877027368cf0f11164e4079ffd9d7cca802a3cf95fb07ee01c28423d269bf953f83e8ae67af72298e375388a53c765782861727588d3cb4bd94770ebbb206ae6926dbddb9e378a013b2bb6b2a4cf96f43a0e58874dd26fa4f8e5cf340e9258d554d765309a2429cde5bb4a2a8b4af576318172a7671d8c6af1218e0e2bb7971b1d7a2a26800a703b3d20e4434387b4ac44a0640b4739243e6d3245e690e316c41a38283a872374b5ccbe20253917212f2d5c51c7a74cdd1ca8820e1bef1fbe66a547e389b8e6696c9c80cb40f9842bbf266e72935f26d569ee3a8e664479ecca68c68ff4cecced2bc461a672a957b678c65672bc373cca31c29815b2638450510cceb79f08d13737655286e63c3cfc1b12a372e71ea942855c85efc0309e1300b736b3200e83dcd2d1d0b304f9ae0ac81da172cbda1133bff570a84f46fc4ce9784d0a4e97b2241b1c50b199b1e61856a2c7728e3bed1534b36cff8f3768107b48845e100854606c46611660cd011b03e9fa537d2efb42740aad7391be2e5913413ff9af6d56dbf487b2e55ab6d4c75510847242a0373040f8e9c0805a89d8f1cfa585ae6a5a4858e9a57d56316bbcc27dfc0f6370abae134747ec8d858c8930d7802b8cc8b3a0f6fae912aeda7fe21b1ae42ee0036c4e8ae973e01b2b50392b6724ae143a885fa6610c54c659bcdee66eae72f44ff6489c7b38e510d8e3f34187d4c5825550be689076c73ca8e39d529edd14e09fd274468719d0a2e544e3971a939c6c86d266b972e3fa280ef0664c7244721d137f8e312185cc245d63f259674afc7b659527bff38b1f6dd83a3b2c066b534949ded9bb6c414a6c811fa6a157e9eaa77e8daf1d2ddc9d2cc44376ad30a5725eca505903aa71a72fe1872ea000246cd930f2714af62b26083bada7c4995e15caab9426319c59d5d53c8af82b4691db7ade0b9b0e3c01d46b637be918372e5e61ee092abae9b2a46db746a5b197a8f2e27f78fec9c61de8e4e1166b4e235e6ca9ffe4fca204f15aac2690f0e09756026bfcb406e6b8a3610b05f2c362d63f72a823d83378e2da3b0328b94f1a0d93a1b507d52b6ace3d285b71565c28f5bf72faa9ea61dc64a582adb9383804d6eae28b910940e704e757a9b479feeb2b2572ccc8319aa727f1619062a0e920b21212f76d856ab320af4fb6ae53d566b24421682a097b412c13887fa9e2b0dc11aad13e687f1a55df570ef8d749ff830e747267e1966b6755571ede02dcbd0856a440b512a72c450dfeb933e2f141a9d5ec72f26bbdce17add25b513f5290e5eff5858926d47b1fcdd2bb76ef5aa608fdfd3e943f3af6cefef3f927c22ca22501ee683cefd8ddc3d9513e603b5359739fa81380f389c3878574a55608f8956c691ef98ebf324e25a7ae6a44911bcb8972741c6b72168ca91299efc6e3699bca16ad9b45e3f0f5fa99229e1c2a66005a4f4e14555205f1b6241a10b56f55f6896707ed5228f2e5aad335d79b4f04ee7290aa5b214f03ae370fbb9f08d77e28f7193d8ec9af60383ae528363799c47458c360728223036d42f14861c23fe69f8adf188b113866ce7107d60e1090fee29cfec523c0ea3664d53e717b79216e4cff7385d08fc06be590c69f9f46e1dccb02dc817294112086b7206e90ff7c2a9d4da25d02e8cff8db2e0ded4562b3f1c409e30a724f6ae940848cf9e8938c049859e357672dcdbd349cc5d1bb7c8b0fa73ac3a372720ac859030929c87febcc30a5e5c2be96ef7d5aa174351599421db9dc60db72e33e95fac635c84262e9031bb797bbab10e0c7b3bc7063ae089ae1b1874fff72ad34b23efdc097ea8046f05bda3f7774f0b01a6ed519e2efb05b3860cee41c6b6ab76c49985f9378dfa011eb4480c2542821bb41cdf6f86e9484c3a74afe720f5fc5eb2e53fcd1bd3b0ddcb9bebd862e9e685ef43424120c2167cdc13104e0388c2cb8c2d1e6a8af465d209145416a40b9c62fbbda4a52a8ac2e1c07d8c1230989c50f937327fa56c2c0c4c7340e487359da16f89aa22f0b869d62f2b51ec867f3654262023ec14ad2f1df16cef4f16626f9b77084c021265ed220ed75e86d72772b9118027ccad534c792cf5b0400bdaae2a205d3fa8c48559828bbc0e27b6e21b6f0fef9c2a11a200e0663e1d81dd7f17321088fa7f4bb2e239b9508ac4d720f527673196fc80e458c3cd38ff65f275ccf1a0b8a7719059a7483a3e0cac6726639bd1273fbff8ad1e6128a28d419dcdf222bf20a3feb9f85721189c0484f59ff30930068fe76c0b75588afececd155e8035d249157e1b670a52a66f7bf1172dc5d165ec344e8a42da600c2d5db230fdd92aef14cf37e1a166c0aeee9aedf7245d2848a6000ea55df3ca80790c8cc37fd8bf36b720724b5af708b26791b4c72b8d163213ed3503f084707a951ef19b354ecd5b2909ed47b3dc3edfee4f60d7257a10f7be9440891f58e818c5eea88e7251dbdeaf11775760f00dac11ca8bc72f6a51bd37e47e71e28380ab93ba98d1221d3531fccdc62a07c5dc09054bc2c72b8655d13573e70c775a0d436f845373eebb30ebc6eb688435bf3287821ddcd72940547f68428a80b4b6f4fc118bb655424c43268c7554e3d6023595f9e1c3a5f2054e1f420413a85bb2f66b2519353d340a761cc26fc88280353b83e221fe8637f29976ab8fbc5b0a2c05243857c61e4cb7fc03e4ef42a4bf9655c39e763d641300b1d37d9d7282479882fef2e59db44a2221c9bd5d2c795bee727e65e6cab72bd7d7c0a6a731321f8fc14b3811461952d4d66cc0dc41a2745f0461e1fd6f7727746a256d3376c0b325094ac6c58745c136556a9232a09f196e5e99a92d03c72a01631c3f30ed46e2053e8c474ce3c94c0df804db0a0ddb685e51e56e04c3d729a7a5e7bcffcd8b2990561e4d61b0360f9b699cf5bc4b2c3189879bc7c4f7e72762d702b73e4cbf81d0082692c1b0ad6ced513fb7963ee5b6e8641794376aa72597028f381498fcf15d4efd79947116b3b4dd5c5b1aaa0f9fdc7521c03b6c64679f0abed2a2f2a26530616c8c704720e3c8faa9745816415c93b2d0ef613dc5e2af2e676f1a0cc42702f397de1b50e9ce26156046accbb39ba367ee2f8ef440b09dbb8e79a7ad5d9a71d8fcd9f3a199d41ad6bd4a248873a43f73439e3e8ac2e8dd10c71b10f68b0503ed62fd95b44753ed261223fd924c17b8a625c922374341da855c85ec21f9241152a3d1df59909eb39bcf82c7f8b55164bda98d65977728a5460f9e8ed9eae5eff94fd5e0eaaa0d4611cdcad9d34fad04e5aeb80bd087297cd193953a8704ccca895c54e9c94eaa96f02bda6ba8a53140cf8514ac8ad7223751026fb94a1626e5d1139f09cc9423f0f664c3c3726446d98a472e9422972306f3b09f6e3d1a933e9b34334a85e2dfe1705d219d823d90d3b9adf6906657299e910662d15ac1a71192c3c1669fc1f175c10cd140710ae7543d119beea1c1fd488b9fb17f7a35d81ba6063806c9a0ea653b379f42b9f4dac7c55ef266f5d0b1e8dd018e5d099b5965a46d6b95edb9fea11a48a437686ae7898e381cea39f72b8fd146b7c11e3863d19a7a966fa71ce90d2df893625b4257b460f9513b70a7225fc35b5c2a9df4b02c4b62a13633b4aff95dc1a6cc5bee2400fd144d54fcb72d4da92ccac83d37cec4aee31536d841a431ec19eaaf8e35560fbd4e3cdc4704fe15727a601833a9405b33140177bf0879b881e820c8ec24a1fb1c9ddecbbd63030c57a0a5a17d9894ec913bec80a5f9f00750c24621e63911404de9fc7ad844820be2cca78dd5db8c262307dc04d4223326a1cab372741e160b79d746e0f5d723aa8f7cec3557aa84fd80fde34f292eeb95aa67d9ac812a78dc24fd29fe8f572803a65de51eede16f1a26cc7e105521b761e7840e9e214865e58212e042c1e72d62d4839238cd295c193fffd3e0dea9d43b8bdf082115ed312a5460f3fa9693baddc60b7c4c27bcd55741bacbab345f3ac7cf95b6589e3937a01a417a4698772a7843a909931253c391e41f1a66b38f58d6d34e730f08d3b30b74883e383d67263d588ed572aed5efa54da5592dbbf399b2bfde01beac2ba3c7792733f3bc4019dd67e488d0ffb62fd4c63bc58ff561f2b400e5a8128507f4736f11af9949e3e14f944b9694975961a1c3a32453c671526e3e69a67eba2bdd42d93ffebdbbc3e3e2e0028d73b75efb770be59590b1c4e78bc4ec8e2ab0c312bbd5a7702ec1f72bba7689cc0e7b8dd1231655a474deca519311cb9c0e56a4d0c988a01f1978a726702a2fde595f6695b806890d85666df8a95df1c0e48c877f9903e398aafe61fa843081316b4fb5cc43e2e064444e25c3dd8a72b729e89d88ddcb21edd5b6272d617f88be179975fdc3442c5d331c6674039d089be863094213648a85888cf726f68e4532412674eb61c4eeb667d7ef29a4acf91a5a08980dccc343c0fb1fa2cd5351b63839f6d5145564abb26003cb0a49483d73453d8dad331ce1b60d1fd72fcc8b63ddab573a3e44bbdc0bfcedf653c034060e9905230868f4aea9a2aab0e22c32dfcd77b735d8661c0fec5767f582ef5f60d5553fbbe62b76b3a82a1880a5bf0a4dca18ccc72b1b7730454086848ecef9bae58f49cfcf8952f037fd39b2fda34d55c3acc19ad8d1c384f6a451623b1e2e94f9ec3341767bfbf90c0f419720f37701006a817b656f31411fa96e38a052239b1af8182f1742a87641bc65d728ffc20aa33e9e5173e920c606fb3a5bb6e9d389b4c4e0ff733f3b835dc6ab072d7d78b84bca6237ad013494f7d71e41991348ea8ef72f51d37ba1e8c8ef04a724de8fb70c169d4e96c7c93dbce469529352b00bd9d62da99208200aff4058f4fb111507881b1ac7fd8e68f49ba88c31dbf50bf27b0c28c652cbf0285673f407282a2bd7d765366effd6f477a501c60e60ba5ea09c9a0edd06922ab1ce6ede272662470d7b471e0d4ef7262088593135f1e6a125552a461d3c4672a48419d76720a693b6187158aab7d7f095d2018251cbb7ea0602a8b44720e357cb4d97388548eea39b11c23e4e67bc7384033cbc7d295beaf74db6e646d1d57bdc83e7f256d425b7e4d17dda87e2d03a8d02e72dfaa4321de5b5dd28f20773eb080c3f57a32304bbbc3a53e5a96653f038801b2982718f3a4ca0995da28cf96577809a33d62baa01232de65e2d7b17a852a8e8e87e4aafd7786c38eb79ea6f99793af982972252cad56885b7a98c541171d00860bd923d77af07e2c1ce043ab6bb3b1d7c87297b2a620e82c0cb4b35eb0ed45120bc28d890f528a6599e2a0e4e4582caf8105f2dd739508ae9efafcc9c1af7485640422ec40ae854ec795eb036255bbe6dc48b5ffa3f2656a24b8b182a0ef11877efa62decce6b643ca6a68d1abc16e982b7224a8030dcd5a5d552361bc6f547ad5e911663ad2004cc405036dc67afd4c3319df323131cb27e4cb8f4282ba3b9b372247ef2fd2c714cfe176f892802d6e4772c7296723e1b4d7f24315d7a4cef8b0c1641a3ed06496deffe6eb25a3174e74427d7a9d5834a9caa69477d590a347ff65f4850797df93bae42a908d4227fb80725fdf2a1298d3bf894fff9bb57bc291f4f42383bb76f744b3befb4a92565b88728705aa5bf283006281d00309ca7d589c642f65087f17c3751cc69c0677503372fcecd82c615627c37c3fcd60d45c8596c5a7afe88b291d06d6239cc694322a727b51dc4b4423a3e262790dc580402c120c08fd77d60f23238aa1bdcea7fde9726e3029b2c0e1645aee963289aa2c2c419d704a587759141f011c9163e45bb3725d74504e27862f5932f9a99c6bf18933d7c29524a5b3db1ed7721f1a6c486a53666be6f90c91f0d8dd4c94d1de9690647bab136259f9e16c6c1ade8f6246cf72c56c16fdeb73950efb7e69a46d7fd48b0aef281038ae29ffd8981861aaf468540bee3fb9ab046943f5670615be3b6719d8cd026954691dbda46d23744d9e6a2988cef7819999e3af7a5137575dc09a47fc31d95e990aa914088c51cdf8499f72d2f5d137f34d5ce8806a1b78b55403d31fdc7982fcc6e7b40f6ae9057d13703e6ba25add7731e9ef10a09b355430eb6b0a343c7cdff519b91d5dab3c343d565ae6d02aed51bd194fb1f37a84e261cd3cacc32c8992036a7a72f34561b1999472d10f98e2eb7e180a28c8c4747411c43b5e6c7121fe344485fc42c6cde77388722bd9c1b24d6e679b2ba65532021fb4ebfb84ac42146bbe846eecc101321be472defd4f798f52f04112f4c07c1a9f34ea473b9ee87a64eacb670e607c64eeaf72834c9d1e366339920ec636432e90e7f48518f5f3028cd5b524ae70b83a052572f41691abb27634f74023f194f15b044fb7bf8b406a4683e291231bee6b44b872083ce04e995bbef9c10982acfae11ba26aed861acaa570af96709c0a83801a336cae85f54f4dab2b01344297007091088950b0eef2c3b76e303d4533ec1cde728f5816590f1a78427ac66f5428adfc1755685c16575efb55333228eb54482d72016b5b0b8770a23be8c87733aef82f5b29b5091a63838423b437a7bf8e812172319606a16cc5273fc8aa5ef58a9957d3e5544b28f79a4c59b50de89dc867ea722b8394fbfdaeea4900abb5fe2cc43850e3a70245daed31601552fc66a284f26d84d11519dc253cd73f5b5e4cd2b6458d6285eb57e7c547b176751814346b5172f1931d2b00ec7260622fc410f980a9890640893116f637dea16bc47f9b6ac172c7cc041fde7f07961cbe787e94f63c2e1c5e3fa149deabb6291699c8b1cb7247cf55b0415e77613db31c310f201da862791092bce7fc4183460be1a226dcf535f53a4cff4f3d9599a5beaa3acb5d191038ed90aac9ab8e1148f32af2b04bd772f851cfbc4f4eaebd1705a94bb1ad1ff0d724421a04a98cdec59c98a3935e800bd27e7f19f481fcf1023634e840a018fab794d9428edf6a6e653a1773f5ea4150e200a8b02ef171b8308099824f3682fccfe6555c880041ef361a9430fa8ef9617da4872bcbc08d6861b19fbbd97dcfc70439992f88ba129024a1be65ae568172d01a5969f0549afb5a8d51eadec6a0989db909dc3d0d78ea98697051df07de72fd9b3da8307d00d902aec036caa140025a9245ceebf80e7209e075a5b11263066000e9489573d5c213dd98940c1fcc112c103e65797767bcd44a4d2d29bf4b048fbdfc961f7f728fb6100043a73a203a47db2dbb6d863053b6851908464aff5337ca7965958e4480d57ac81ac013836781afa21a285671b02aecdf7ebbfbb872c892eda9370c60d0a5217fbe568da2766bcdac9cc68aaf83490a183b5dc6b93de29b81f6e4e186d422f8ad9b5a95d52fae2fe04a14101078dd4b37c29095c1720d2a40d36fe7a551808b7bdcd7daa9cde1bf3948c3976804983093734d893e2bfb10b4eb68e510c54ee4c8b8afbf3c61c2eb94bab88cf473b6932ae49d0fb072ed69aa0f8fbe52a11e752b52431eb7ca0db80d34d43093011f9da573c42f467275442256c881638816a2e0a3861575a84bfd61fcb2bab03168b4e72e3a523a248520fdd6eaca251cfbd2383120dc855f7b53450393ced422a64dca14dd75e9727d81a7336b1c7036f606a994b6d7cd36929b36a480cbd8318c23a5ca7f03cd72b97d10cc0b9edaf668c0a6033f49814376d50f119eef67b1da0f8cbb31da127259377d48214bb7e2b396b7f30b78c018a829c1565bc807f18442480cc683b172f256289776479301f40b4cea64f2657232a781a555de335203c0c3a628cbc4620f31d2db9da954913454cc64104ce6c59e56ff5f943567e0826f85bc4c18b172872e2fd0a3c7ab28cf743d3aac7986538fb0820dbd66ba1d03d1ea35cd4186637bbb1672c5196c29c88b3c3358f05d173453a239abd6e1208467d293c18cca72c4b3eef9950846005af7d5d456ab79a948f1c3a8e7f0a09fb2814efd0294f008dd6e0de47102c0d0f9fe51fb6ee0d024fb1ac27fda9fdd964b93d38e9c025e099263d4db120bf5bdb3c3791add997953191a6ca22c2047bebb492cf6cad5bd6bedef7985748aea6756593b600e2d6e1a46d155292711500b2c210db46394d67297b8ebc4a373f10fb0bfe8577a5f399420c089147aff0625ec2cb129f38d8051a51bc30bb1c3a3063c42629d1c945390e737ed40aafb7909ec7a450a251a3472fad609aabade3078503e4ed000a7f58e61bac447bae3503baa67f422a4599b246f84b393545027bf609ce4c86a1dae2e5968582587882283ec9d3995212bee5fb3a548e777948808cc79163ae9e82d213b794b247cd059c21a00676d119cd52909127710180ff6f90ec734922195a72764c0aaa08107723ce3a15636fc6f87710ca324627d9e3c07b77731784ac96ce6b651991adc0a239656d1724b47ceef725ed910a95008bfcb04e2ec33a1d64322c5435d2aa31b7df126336bb3c25989728692d7835ccb4a89bbaa800a01342c3101c68609cca4e79084001ba42ca01c35349137210f630c148dfa40d36745ddc46ec01c9add643e9182cb835ca5f5fc72d8d63647e0bbe5c616ad6c9951e1153840d3833b3622bcfa3646dc89a1816972e1452855e29fe373d7ace444dfbce148b6c05e3353c613f4cbbb2e9b1a1c563acf7f3e40ea6a1bb94143841d30ca1e90b4aad0ec18d685406d116aefdecee1677154a7ae711f6866273a31f5da2caf5de2eabccb00a37c035b6d91f1bbf21372591f9080509de1ec9c5599afa03c7a5d6b9e234ad3f45a9f17d6fdd00db6796acf23a78f3b80dc31ef275ef7951f6623e5b06c516e38ca8215e171384c4b2b726d55573d62f24cf5195e8daabe5464637a9752cd713913177d7aeb4495329f7213f3e32200261b46d7a0ab47763b667840111842cd04fa9dff70182a50eb7f72833eb2055007d7d5c532d3f188cfd7c2a2db195dab336ba221192451739d1d7272129da0a8448611a846c61411b877e00ebb479fd41a2dabcbf69b19d331557283d0af7670f63dbda946a2b7e01f7dfdeace59a2f7ede85c466e57e167e6be40c3aef9e35bfdf8509a8518ff28297ab8eb5cd10653f59df6c8fbf337d2cbc072a4cdb49042980b5259f24f8575cad0c3b3d0ef324774bc275295eddd82ec6d72a028df178565ba38c377681895def1a288b22fc62c9d0a796d45b5b4935efc72f4f6b4b6ae290a12cb75239e90fa9e3a39fd3df9d2f8d00ce4e8a0a5c414d71880f695bf141565b2a4a36a69ec8b0e17d62eb835b14573363a5befc6439a0872ac6e8149dcdbcd63f1d9ceef950dd8651fce3de58608ce5b44b59211ef53bb4aca157d7bb69fb2f4939b76d5f0a4d1a80e746c0e7759b7b6c9fa81683b295e72ee87aa4277ca5e83fe7927c2b92a7e135af8d7d06fc34b71b3a826e58b5dc67241e3bc90305b54bed382c8c53dfd263b5ac3ac6c8121c2c6f0b9519692c8cb72c32c05890b8b43c22e7abb2124070ee43d6daa8d0c2196979ebd79e6afcc0929f59f306bba6259c2d99ca730c1bf15835772070f8f00f8f13a0a7b5a7466de7283db588c590e4b41d7ea6958f09e0c719693936c211e112915b26cfec3bae63cbcadf03f4fa657edbe1d29ed73c908038a3d242f1f9307031d47b40f28884b722ada6e1d28e218347a4728c3858a8b29881ee56b5fce21eb08db5346a9dda872e03f43ecaee61295a063af8cabc3dea55ff77212a4f4c10d69c13ded645ce672a1d13f3df08c610ba9e9f6899f14dfc30047d7f0826af0e6d2796986d5b848725986c67aab4fefc53ec0fb46a3a23521715f23425a4d5e4140b91b6cb951cb72c5f6c6b3cc55558d889a56ec43ce052d689fa97ab0d7d9f56a44d5141d894a65fa0115327298fb3135ac8511d5aebbc819153a7a225082d82c7512cdbc40db72872a587428962f98233a4cbb3ee3a3be5ee57fd0e7b714fb3f504e5823c28115d6f0b37ea35fa1775545836407a9f2e6d5e934f879806cccefce42ef2ea83b723b904d89b2f7faf0b0d0874a327c872d9348f66d89b5a5c39d4f7dc70d627116248390b62f7ee241ea0b89b7e8e5bdccea714d699e9e7c85dbf1d4fafd086b4b2c3d769344b2f76dd5e5a4909ce0624010a61c371b30133e0d179b4b8616d02ab51a62643e12eba590f301e62cadb1e20a31ce119d9f5540aec70eb6b00574728e232b38519d0c6eb97e098587d2db04f77fa8e0d6db84319408724908cb9c09f73bd65f205038a76d11cb9189c9158d249c4a66e620cff71ebc1c8e72fe5d72c00e81d475ccf7e2a129f6fba8710b65b20b5432a4994f982964bf3fc5d10737fcabb6eeb194b22ece6b20037774bbd440c3a1fe801f391322c928a79b390872b74b536f48118f20d61d4a94370072a627f087108565a137b136772f331d5f423ca15b8d33ac0970a92f73f636517d8ef25f70de049ba80c8ee294f2cddc0b4d225299925fd748db43bfcbd0f9958bd34d02c586db3c6982b03b54c6bde9d467c2c83f22fdb031ff10b33a698f9284d31baa15066a8e2b6bd6e3bac8daa0b865a7b963ef7aae2bddae46547d6fd3ae0bc89cf932b19dfe609f1d703abfe8722710b84cb277c904b99bb0c6898c010c116aa2daae062fd7d4912d010b3a83ca669b83113b43137b07a786ce41c9a63afec1d6b0124760b4820759cce4c2b8aa233c07a1e64494c62ce472f96d82d1ccdf874a03c341e2a152a0cc0ff0eee99372246015640c8a83727c4c3d16478ad0899628ae456c3c7e20eb1eac16924f3f72590e96b873b491b3161547789c523f06a38ee07b0bc7ca46a0ded3d45886185ba91a00e961974003512903267dcbae1d9ad90d270f1accdd27754f82471baf1ccd98663c2f9be8540d0fcbb87d8d39868864d6cd4c615f3a12b90ae203cabc7231e6312e440666b6972ff6b15dcaac2468ddf31188b6b13960f49ba22e02ba726d6e0d69c0ade7acad153269a40bed4a3ed44a1d504dc141f8b3cd419e9d5469e862c480af55015d5ffcbcb68e52d5cd78252886f159523a0b9d67f14ffd30724abb98f9758d602d605734b228527134f56f1e8df60c1a43c25ea3d415d5f17277a6fe52c00243bdeaf52b237edfb5a0b52b33dcde731e834f158c726efab7304a494736b6603b19e69778294934acdae23f86e8bf2c90e34bc4a9ce282a24729fe4b8708b9dccbbd74dda9a73215904c337b98a3e043e970326c9c42f4bd9722fa09e197145797d26918fc19ca4f0d5d689163f95b59a7f2934b757d7e83e72c6dfd77c7a7244a792e5fda4f553c6ac6acfc98fbcb30fa493862546956e987285c0f4e0db1f5f79c76bf4df52e4a1344dd2cb0e47e2519f9481da18dd97cb72b2fee1cfa8c78c5de9e40b093d2748e265ecba48bb41cd82c92fb028fb3ccc7230c35eea91175ef42db7176c8752872c3b815d12340912669c0592d4af44c11d75cae578807c691dc1bf5cbfdd2a13e4a90c5d693f01dcd4578e62f2623b2f3d813b88c2f8100dd93b0e61ceeaa4dbc4db603cad4f69644679b1228db62c973169ad4e07ee6b1994555fc4b7f9db0229ded68ec6d2087b3373ede89d2a40d1620a16eb32b26e9a5bd8882b262d4d9b99492bb05db8bedd16c09bdc088d45cd72f461fb8cb9d60c9279aaf368a4321d1746f230ad9c7859379c3828657b5c0272ab3af1d2f3a1b3db166320a334a0181169ba65646aae11dfc8b20edc240c4f7265e959da0e91d0f13d8f1afdde5354f54c6780ca1e68926a87100ed1789d8e6d7df62ee3e3b52cdcc7f02cca85ebacbbfb6ca2fdcb5ea18bdef7d88baea6ca72afec9f25fff2963299a7e0cde834bfbab358f6c99c617e1849741811b09f6e72985dbe2bd4e2c89107ce3192d3f4f17168eac93aa78e34ab048f36bbb371b7390e8683048c050992b5bc6fda8e5958c6324c440f14f617aaa830d5bb0b4ac672b5c225238f6af274e78f7b0131a3a996b93aad2f18d56324211d619f29a60c034f7e22275880750a3eee49f100f32ea214298115ebc27f318e6b9263948fd5728f28f9f8016fc2c8b97ab4155b3050cf48890ee68f9d146d0105488aa6d3b072204fa9b5c17bfd400a71de47a61b7be2f7bcbc14ef92fb49d6c94dd704efa21625f39b0a21448e773f39699ea726740f924104cc0695e9064d097c209f7c1c72200678e06173b9342cee683b10bdf3231e71e90a817933f9fba5ae2ed79dfb3d90aae4f243ed64fbd2052525e6c7c533b589a7b907bc95378c00e6d619419572e4314d9298821557339aee3afffd4b8a7aa4a905c3469fd590256389a296cf072d6bf566d8209e5fed138b33207820701443852175f0beabfe923e8d0e69a336c2ef0b7159db27f740ee53ced6899cdfb16a067049fdc1e815da9e24cb40c42cf2ec08ffad4c6927734a1c11434120d1668bb9ac2820793281cd0b7d8ed1807240f5ef40d4a496063e2c65d25b48d61449409a7eb3c473250cc549de97c34b7271abe6daa39e290bd3762da698437ff186fc7d9d61ca4e402f139c2d25ec4072d2b148a7ace4c9e1bc2525dce51450047c340cc16bfa6a0f147663688ee43352d8f3b12a2049c60c60cb052bae0e995fce21a6e4215c3965a644d4a13b538d72d8cfb4ebe4509cb89c2378b5c751907d64369446512650f0bf2b856cbcdb60557ebd692f13690e945ab9a87730929989a2093a01d7751644b3af1c451653852eec8db50523790a06d6bbaa1e2a834b612dab219216a0d75c5fd20166406eb172389d906e2f4c10d16cba99e6a2435a96a84e3aa7d067d3ae761f780527f7801a5564071dd6d36d002740cdcad7e966d32f013eeb2f7d74aad7786ea96efbf372ebad9a667dd9f3c1dfaded50d8979f275fcb7034f6b3781fb5f1756df78646726d5c1ca5dd4e4fffd78fc7bd16b616a741760a223086578ad64b094f3af100728331377cb7eb10585ab6cbe76194c5429fb0dbf7f0b61c67670d09a0386dc7234280c770898a609a7dac5b87c98bfec1f78b785fa1d09e186d1b442ac25f5a6e294e7e693482a5b9eb8ddb4cc426c5eb5e5b99e78fe726643f0c43fe29a10672213d750e05b2fc48aa121c0fb5dc273521ff325b14fd4c09cd92da6b3568c07249851ddf4f7716598afa5208aabf97db033e22c5765f2611894cf15e243bdb72eb262c4de8fcbad9f8c5270251bbfa3f7989d5c18bc5b35eaa4fe139bf687a72054ed3a5fc9ebba10cc5c2eae7c4948579c1bbbb7f30e0e992e9ad5874872772b8bb4e8d51a4bf769e975f6f5e6ae386d5f03b122d56b262015e6e65897b9c727df7c7ad86463326b0a1cbe5d2022c72cf3c7d21165b32f2f63d303771a6fc7235ef2d17a66339358a9006074a3aaa059e0d85f3aad6e8b505c0c2981bea3b33aaccd146a01ca7ae63dd538764a2d52edbbb6ed491b411730ff04624da2baf71221e7aac98bbc29979fa0c32508bfa1e4badf582d61da3d28151b5f218c7b072f656d3eed413c08826738483a9886fef85b24534c9361cc55398a948322d457249d6ebb9849859ebb25a8443a9b66ce415a27c570bb925b1e99f11a253b07a720a56b742d95230af22acd1d310d3b0be015b844d8aaf73eb621b951946b8c3720e10b69b8399d59ab1ecfbf8240ac8bfbcaccb4371c6f48d2ce851cb64876272068613ce1413789c9dd752c3cc3f4b47fa0505ceb8bbe2cd84d11fe135e0523f88f680f5ed5bf5a4eaaf1d757a8851c3745208202d92e1667979464d1a73e33d64c3233bf81d26afd6a3410c4e92fb850c73b0e0009691ea7eb65deee95be472d71bc8297321bd3aae794a743443342d70529ccee8d0c6213df132aa4e410c5bd53527efcab17dd6c6c7cbd131129b17a00939c261c5f3aec8f4c825fea9dd627449d146fa32e362d34f35d9f4422b0f40f8e9a1aae11f01b094f397cc410372bfc60863168fbfd879279e0ee812ad3a174f58d489793f643547364c7ae5c272a1336a476bd544414582b366ec91d5c20a04e2131140581fa7f2cd8f709f8f7298da963cf7bb6482dd52324cf8d5c424f18783dc7657f4697050383a252aeb5bb4e67c47eeafc344403d81b630f2359297723c0a462ec7d9c9534437aa92fc0ecca8d8c6c0d4ad4b41dde3e8be8c969cda31227db1ff3152c1b73f05d60ed747d622b66edd4759a320c6875348e63bd2c48472ead47c40613e68f9f77229997214191821eef70df249dd4682ce3871c8bbf8efb78cb037144de87ade5703d972fbae12fc514001724a6471140c387c3ead3c00772f932cd0ac883b9b37cc4e72903d03658988c945df3ad4be6b2304cba977850041376d352103df439e25175d981f2bd06e8e76f8a76519590c5578b47e53df1e00176ff98e72b1983bc5277272c8c4716e242a7e96949e3e0becc9bc7743e43315015286571bbca979936a3e811e9e8db5985828080cc0f069d31b11181de1a6ba551db5268a26be1ef79172757da66719b25784b623e0f6cb915dbf7434cd2852b76864623c7681b0105a723e962b6667e43a6150b20303533e76470b8808775b68cbc83fc2319540e7b06c38bba7638c38aba86994b13eeed849e7deaabf869144fa15a32d481503b9673b06c1caf85762f066247d20d13519ec83e49ae320c8f1b39a19b16efba4d36672db1d7527a13534fae93dde4d704864bf134303c71fee4a401d836ed6a937e102d9685c8a07984303c8814a8d6e5e4abbefd03bade9910e07f39dd946faf7b872a40520a118432ca0913e534ab35b92f05b6b01c5c65bc0ace42be2dfe2216915802b31c4499d0f05354f5a603a81b8bcfc247d2c01f4337cbf5f0c97c2f3c90febbf45ebbef6941c449e7a9d6c891d7eb76bc89f6ff866c6a09edde2e8c2a6728dc7b68fe8395593ec341315574753ed77ac42350c93671a7fe96094d211a372fc8458565ef1c92e91f57db5399312b4bc3485b306d3bcda2cbf4cab81cacd4be863f2c9c47a931cadff5e91f7679209a3093f85b8a279b201a902ab1b1c6f72e3756e7f70b6d26def1a0ee3b635fcfbb892355e2464972d833f6e7df15a597042bbdb6bc15678487f735447f2e08ed3015fbb1ecade5c58e454a9edab97e04c6ec3868134c5678713886753b0cd13f694f200c8935b7955caa5731c48592e72948f6582da5d4d25bdac98540b6e3b4de4a590ef7f3207b8d9adc5af9ddf01725bf9a6d4b6269f16d82a1634c182466bc9b6a7be1ed7b2682fa27796f810d6726177a34fb273f762fd3bb7b3b8220c89c1b2ecd8c0c03b709bc30f12ee1e3f650d2fb56949ac23a5449606c068c43c26492b002aeb1407cb1987f3cd5bca410a3e8c697fbec40e97b8f05551e52195186f1000a9c2e3200bd1163bfc5141001c8588ad1d86d587190c812af004f611ffe1298c9236e000180d798ed367a273725986af093873fd32f8cf9e6dac9cd89567b3a5be32e86e7996d8aec5570ad872795f2c542e3f2e486f583bf3e6dc333ded9e5ecbc262dea699c21dbf8738f372d235984db7541ac3cb5da8b532fec1fe4d54bd7f5f8c4ae7fd57cf4dbf21e8146d5551b5e2d02cf5f86b4fb002f5e7530aeca61b6db192b7d6f653bbac7b780c49f52aea1b2862f41735b4884393825f4b3a8abfaf44afd78d17b8372db8be3c98abbf1e2523d33dd4addd6a81040ae179fc1220d4ae4a55b5f4f5b9cf0a6a61908c905ca2d3323cb5fb9158ca37360bfd2af1e9d19f69a4e298e6060f66a03939616aade09b9ceadcd57b0088b1210e9b9931db46de083b3b8f873fda5732240f90bc5f2171cbd06289621292756c081623ffdb947cc7160626f88910f0374691d2cc2d0cb046f72c2fec01c08e9d5a3528effab320fcd7ca71d4ef83bcfb30c97a1acfb8e8c9eb81ff9d513dae8f1bc2905487458324f73fb1a973529fb2727ea909dc5deac137a96b419846426a85663d7efd9eeb6c805ddc41a383730a72e0c4ffccb4f728dfd16108d5ebfdc32ead9497191970e329557a485bde65e93420363f2797ce26fa461c015ce7c80e3a16e666263fc721b32e420974f0d0c47287c9f0f0cdb28c568df915d2b762c24511d5e1baaf7c353f45a55731683ab7722e093c429e4467509493d48626c2b0d11478193e64c27135c443758026e1ba72d051ba95c4bbf9405631faeed565ffeecb07c5049191550ddb7703f172b19e61a41163614d0859429983d0869c1990f0a131a137e48bbefb3e0c0994155b4b72bac150655e88ff725308fae5a243ffe5ed013996dd37ecc25ced6f15addebb08adf92a85c6be97768f1cdbdb870e725c11757e5039ffced020840c898705a67215d788ce0457231178e9e08d69f8e6ef0145249f57a2a706a07b5c205bfb5f72390290c4258a40a87767ad750e17c7ea7154885d2bc7aaa36d15c5487a6a47721317acd2e25daa4622802368d7d8caf6f563871aa840912abb7153d2f3e175729341aef3d7faf61cad29dcc9c42372c94d6bd5be78a8bac5bc5d22f268853a30c6d63adb82a0317e246783666e0c5359966f5b79211c04faff8aa95674d60172df4b5e6146761f5bc54a79915468e5bfa184e229364cbe0fbfc34aa1ba99fb125a6f25c50f3640f75d0a86faa65fd1f518be970c4c599b47e889f386259c2f473632555093a96a6d557d513ecd28b811d315a8fa6fa276f48709f6ca89331e728d39822d7344f5918a1c57ea83e515dc78718e701d2b18256b0b82b4dfe7514849e467480e9c0a91ac1ec34f5ff200b8a47e8dddc31abfe57ec63ccafd48f6487529b4523e0052a41b7eb94a7e17b67798ee129215b7b54be958d66659647c72eb2e1d466c25e7860abcc199186e4949d4e093901501c2929d2429fbfad8d463547e16acd8e028de9ba8ea335cd10548cb8c170335bda39c6483229b539d3272a4f54f7c9e0d2437ffdeb4c7b2a064507d8648ac3121cf78551d81e3e284067238b256675ca3817379721a634973699b7c9965c325282afb98a960924a202472d4fb144fcb2f039cd9b4bf3d0ccae89f918dde20ced10a85e23fd53e4cc37972c36aa6337bc738eb548471338c900ed6c31291ba780607cede67e959016b0c3e12958ed32f275638d54bc2544e4ed8df7b63542735be6b5b65e65989effb7a721c5c6a004df981a2ea0377702bfd8ded20988f9bb4b7f0c1f3bacdf1d9e0c06f597b1b9aaff408564a81bfb205050fa4a70fbfebcc3e6c5ecbaf6f9cb02c89665da0353f40d2a4abc495ee8383797cf40e13c2358535c41d51f703311842fc1bc16ecba4d5ea81dd95597170eb48a3a0e4bf9586c73cbb5e0602d0794298371f6a059cae7cb361e4e3f07ceaef803d26d74e9bda5e67eebc49e0f6fa2c52a011be2e9a7363b844beb41a6ec41d6aac8a9cc35438e7df57b27a6e448209df467206e3b939485bf04ce7e2531d2e43cc186aafdfe6ee5222eeeae99af819948372fbab84edf6f04d479f777ca5dbdb0bca5907ba472abcb6151e65cf2da3935b4442d0194584596e8056c09ab3f1b99dd04e1ee240c1629f395c64516dfd2cf772401b8881ba241027ce7b2c30863943fb85c684093eb08a356611de1b2490bb43a652a05b1b3bb3c89b4d6df2b5c9a619201da99425b4c1c1c46b487157160d375beaeab284b9a94ba2f32c724cae3c3363706307a95402ad921a55e15d94ed72058c37d04349c98867050e215d87736f84809d0d6b5ea7871bf686d414c1a772766a7e2e7b1fa8f3e4cff647c5b6ab4d1df9ea995a0173ce2f87d89eb6458272c4040cb2a03216b3475d8354c136f511fa1b5b7ac3f2bedc3aca74a11d09d672dd6b5fff51c3f5ece8c90277fc54e00e2fe97e73b3998d7c08b83b304e0b01727dd3cd94e20340b06a3107545d5ba60333d1a63a16a07808a3155aaa4114e672870fe39714b220a6b3587ca7db9248044b8d96663a3bbf1fe781df6d37fb6b2301aebc9f723b842acc35d9a931082ac77edfb7f5e0209e27e7e3f86c22b2f872d54174c8d86460f9d9b91c8b39d649f829d213791ccf551db994926cf3044c283fdc07b294090a9611d4171f00b089adf3b8984e0db5c8eee1f643f0a6940e723262d560a319e3c8692c09b7dfc4b86f3791d60e6cf47b09307ec3062d9c183344784589aa1a99c220f5c6ce48a5567c204d0f9275f809caf0b625dd811a45572f8c6ac1a289eed4af39bdac1b757dd9d4034aa7ef07f79f0dc6e291deb4c7721f7a1c54a998d18d10bdf8fe05a73d37a236dab66fcbef112fac78cc0cf06f7256a590f156d26686ebfd8af84273bdaa33cb6aad52528b512e3314d48f5ef672bb1e4e40d75ad6c39e728955182497595ba72eb62161517c65f78d04c6420f7043849a847efc781ce324834012db79207d26a433e4ea24104ca9ed4f89c46439fc8efece3480216e2a7aa48423de292bfa96024e0e14641b7d03f0e111da566d71f320409fc3abf215e183e7fc82267f8112049430f03c9eaa66c4769be17972294519e5cf580d21ec411fab2276494fb340cb023d06a4a0a11fcfbe362c15721eaff259a6ae60e75f9ad9dc713be44398da419fa7706ef228801b28b55be12162bc716e7c9f997891da718a451c3d24af52fdee598204e729d27968f6693c3b7b04dc4aeb73b1d682ac4b82d92e169c5af533ecb4cbf72bfa09aaedcdf80c723432a3dc06f78d0b0e3cc14895c80ed83fb7e6f42e790573aec159d12e140a622b51aa4647e8d429fda16711e9433eaa89b095b359629d0e7fb0edec576af972ebfcd1c63bf3161f94db1a664e627e6e600d092cb6db6c0ef14000d61a1fb203e5401d195ee1d5b3ac72d6d10b23aeb6015b886e6ab261affa16db764721d272e6d62cc97ff506156672a321a98fa947c02550f90e571613307a7f14e9589872d5972d16f8d8ef9ce854df708f34d5abdfee9920c9c65a6ffc7b69ac3f75054393e671b1fe297e477218b9f26eb612c4d49254b8b29863c8da5e1d1aaa8b7c18dbe70284fe19328646d9cb91c26341f89af3f444554d65e9eb54cf9a7d01b47221f6cfba13ad3650c355ddd74644a74bcf27378a9668b587471c9b0b1bd041236de22f9f65e61c27ae398f6b8a59253cb92efe9a79044dc818ab9136b3de2972f7ab6618b42eec16ae7c534b31dc34b545b6e446fca36d822009bc351b009d72f432e9631823c3a0339c30b7c4a4d4507db1bbc61b0578c4d0677f075c8a0d72b0ff1fcb995cd9c288237135301116002e038d54e59325e33c79ab6465bc0b0698c789a1eb2ed047756e7c7c74917fa682c11ce864bd199902e7983e57541a583e585f1fe3721c200e8471b92850dbb10838f81d060d805cfd91add5cf9d96729baafbfcbdc5b5ac3e7ea59ee1de853f6f08b8fac6830b2a129456610ab0e0722a5656e2b35a81456e05e7343d4a0213c5e0af6a07a1b851e5f0ac678230307213f08b0fe37464768c5baf94e09de084143ee3bdc94592b6ea36d2205ea8b53faa389153f64b7f5c900755b3cc4b9909c4a33cc0051fac6bba4f837f9580a041d057ec20c0539a681fcc2a6944e4f76be33c8d27a70be29057fd49f2598bba723c46efaaaba923823ec71f45b2c8c44d8ae64eeaebf918b2c29c2490a8803955dad93fef51154ff26381aa7bd4c08880cb7341bfbfdede82442e554ef4678e72b33c6f34e47ce1c8c62819e5bb293600bc8fb2c7f0a4e13b62e08adb3e104b6880eb740835027d4e66874e0263b2a4cfe43d18ec163b18da3656363c82c79246322faa5adcefe08f65d820c1e15962cdd7e3dcd354dbaa917497f0829a20c2727b35f1a49a2ad6b2a96d28dc990e347d3e4c8dc78e40a8f95644cf2c8068b66bb87fb2998a6e95ac36e8f3d00618e8daa5341d7084798237b1cdcd90c44e1c6662321fff532c1d16c728be6d265eb0ef74f4fb7793aba3dcf8b885e5e2b1a872f3bcc841fbf9676b7371799d9d6a0eb73559542aa83fae72855e37d04877e9272d553b2447f800bc2cfa9db142e3e70226cb6c80a4faa8265ea237ac920b9972cd1041efe6cbcafbddd4494a658a64d55495ffa63e1596fb05a61a120cf32972f2b13eda84653aacdf80f0687c32652a1923610e27a84cc11f6b39322f81e91e19e7a48cf8ef56bd7ca647ee96c1c6d4c6d066d3998009546701a0bd79df9360963cae03883e9b9b25a5078bb7a8e9a7ba4c5160a2f41061a4766c55a00f60722fa11e3f3b87b0990df7415b7616338b7f4a77f0a1c6bdde415f9542b6905672de6fc6453a32b88173309c8bcec9c06f72a1fa54dd4b8b5879c95221bcf35b350d5f9f3acb875fa97e5707ff13cecebe3fa96a741fcafec437d009ffe6600a08e12d4b0549b9b5326c4a66ce72c5c75cbbd9d3c8fb0bb109f6158956570092722cda91273a95e18c19c77d441cbbfce50f00460aa93ed9565eab25a6eb11df72ca4017e3817364018499ade7cb5070696340a58555236483e1cf5c27c459c21f1beb32cf6ae7e26c98b99e2ad8a164a325f3d8a97a59266f86614d35d82c714862849a4a02e300cc7d10b5afbaefd134626a01f8153a89aa6dd3cf36c27644134df6bf9c447b9021c2adb6d3cdc272ff33ea4b9937fa93c7c5ec02e22b18cc496ca1f744fd574f4a555b95acd81b0ce47d5d23531d545589a32117661d1e1572bac264999cf68961d4c45c01240ccdd156aab5dbaeea656366964bb7a5740e2fea9b62239f1180c8778b8dd4198b01a31ad88e2bbd1e93e2af0eb17df8c4d42952dea79f638d9d8d39a7b8818839eeedd4ca655b6011e40a61e1b000723a98728f9a9e116209ddebfd5d6650ec28a48a9335cce543ba019321116334715e3d1e18de7fcd344c75a05420e3ef5c9e91ceb4caad07f15983c259f3327bd24e7372fe4904f9130b769be465655a867fe4c40af4577c96e76a850f5ff79f5d1eec3cb04eb67718243c316a826c6d1ec024d41289194d4a47ccf08e6df2cd05998a607be8da3333c17fbf3b7a1c92ae53d01c9b3203de2f23acd0528228019510ef44e864bda27751af5ac36ea478e71d8110fc0a0e17ebef42855b5b5259f1d7252906160d2303b14887acba95ab2ba427ebfdd059ba9d689804ac8c07a8eec24072af6e1c06c0cc1ad4d04d4f90434fc858d62c2a6756322d264e6313274c63f272674b8a0ad0807b1f05387aa5e7ab022099added587313cc5df9c589ab8f0a858624ed4271bb7ef2f96aefe06d01f49d615aaa09e6d281c65ecc5a35a2d333d53f90ec46ca424361d744b9d121562e6cc34e939bb090965a3da3d3aee20701a721b0080d30e35ae98f9e84638452b9b7066f647da69a90358b0fc419f287be672529974c1f3e260451b504817dba90ac4c47c7e23518dd8e2b840676195af8d72bf844511c2d57b2b57a551f97836c1ac39215dff47b47a34582ffed9b5081072d6f52892856c6bef15e8d9f0d344c3bc5c748e2fd97150c8be606116fbe3f0469ac14ea69657038427abe45488d2d5d05b812b9d0f3512fca51583ca91e1e97204f6135b7ae7a7421a217c6a3bf3a2f92741c1c947c03ef594ea19b09a23003632ed8906e6c9c9d7a31d67a1d99cc280664a7c8d540d5d8915243555bb7060308cb5a09026458fb07372280655784ca817b435b34b5a3c7a0ce0556cd57f731b05dc4dd8240ad5c0edd4997dae5db79739ddd8e8bf8ca0806440092d7dd7ce4f77f0d847f5b33bf4a26d4e9789bae50827eb9e30dd0d06dfdb6af49bdd8a7a28a7e6e80241cfb4d0d08faf45494f246a25df942e8157fbd2c60dba96c5b531723bd847ae4e1936bc907b9a6f6e7190515c580ea504f3c15cabbf819b188b33720b63fc57ebc763730af109835734da1a96245ff7a5064fc87fdb0b9b5a54b042d46cee8735ff129c7523308da0f3af2c80b1b892ac6c1bff61c6f8329d88f572c075f312d1627b46ffa6006efbbc55101fee66028bdc3f297c61cdad8684647294627ccfbf7c6dd0343a431878e4ce3469620ae2a7c614a18aeaca5230298a5ce1b051316d957a56b44f7e3f483a0c979e70196074d9a2171f5df1ea5ea45972e59abd12e604bba4ee836771f73326df78162019818c60388645a2e4b4432e2ee73a961e3c02071a12b622dc2f01482556630f7cef9e03e708ed2f8371cb18720eeb56f3f2cf4932c84cd14e29cd7372e00a14cd5e258febec74ac139988fe7289494baabb17a518dc27fba6467eb53c545105a2c34d91669c71458ed6ce3b72621316d599f51eb6a4ed9053c6f30df8b5e9bb17f28269e8ee7bd752ccb62d7268710a04f123a0b6347a4d216472bf81515c5a1f4fb76630c18c9dfb38722108900705c549d31a40b5d8382bd317a6499cbc141efaa155b5f5d9eee72b970a72b2abff584094a0f5de1434ec2ab53607a8f83d91f116518cfbfa064b702584356be0503cde8856bc2c1f51fab0d749e2e1f8018c116d78ab9570094a44ecc40ff8a56eaa5f2f7b870f573ef657bf347bdb38b9438e7fd65c66b5b409cc229f72f93bfdb4fb2a1eba0f3c2cbe9d849a4c2fe3bab1185c9a2325de4c2d13e95450ded2fb379b284e0e132504c4bce797d19a1dab93dddf29a21591cb5c20e181728b7ef063f9f8d6b0f54720322852f0b12ff6becf8572ab3e413748270ce339729bf1333a2ddfacb6da7be10181a346750f155f833effefb84cb65871bb42327076075500633ca22e6fe6a51d75d9991160c783d7e6af214db2e1dbe7358fdb72dd26c0e04761b3e20e96569176c554f9ad2d1327a1db322c93df734b9c83e17219988705fcd5617e7913c2a9c4422eb5405285a5c803396da07ca8d6cd5f73432da36a24268904e87eb8d95d54091f50d5cef860a782af434c5c2cff183eae72bd0e7756a822b68bdbfa2a665aeb1f449611c6bd1de7c9440a40322a3d25204ec0d62f897b35aef461c9cdf23eb5748bb8289e7a8592ebf31f1ad81e670e4e72132dc58353a9bf576abeeb5e46a470e0fab98464920067c247611ff763d75f6c0e544965facf8898698b1749f171ad83dcde317ff38c581f490cb334792f386f7b6f52980c8755a2cd01516ecc1958679adfebc98d7bb570f664a2a094289472be36566d87cab04a8620b56eac00cb27546abb6a40c6a7ce48011d6bb9d60a725e447b8792774b9f29ed9582f0b3682bc2f2d6b861fff1e42615e95111f00872cf6df3ed5463261b3c8f03eecec8afef1784981de97b48e394789994bdfbe172dcddbb07b1a9fe1e075193565e69061c596381a14ea3fc976c59376007860c398086f5a90359b14f6c0147615470dc33207d6113739bad005f585bbe53859a7276c537ffca13fc35065b08c9cf37571ea892c3515a3597533bebd36fa2e5e13bee83da562e4c596ee0daf15d9b1126ee7a88a5fe5267d3d05d88c96e96f6be72547dcee6c16b7dc8bc63339701dbcc51ed90d4422d206289c3d60c4b2b2222500459b3c3fcea9471325071d0f5b2cfe40a7b685ec1b7aa3685aa48fbc39d8c7283ba62c852e723e8c6888ce27db495080db80eaca7b9080cc01a3123a25d9572c6a5c3a547f13e3bf1e9ecef54c12adb12a4df84facde0d1431fa8ea8bbd955cb895578faf7594563e1287e5d3137647c2cf7b8ee336acd359d515bd6d8f66499030f14140072918c9c4220e283f26f38fa1174e42e1232d20024a068126970f1c7cca7573f23fcf69a06cc15ad063c5538e8ed4b7b7b26915566aa5cb9971725070233cb80e51eda869b21517533bf4b4bf01dee1459060664f594ebc1a266ce19c9036ef33c638a07633d13777c1282b51a4b07e5cebf0d98531d5f3f096729ed8cf60ea10a1aeb51a23523636fa4d5ce523d735744e677e91b9836aaa997295eee6bd535d41379e1f6a141872e6da33d9f023b948409c43f816fef3b709727463e04df1c852e9783284340fdc754f62a5291a2a11c582d292f65c5c6e26581dfea529eaa4430bbcf0da18de3471ce6f5112c6501b5684004ba87cac3a1672fde4f1ab97dd1825ca05719b4fbac684f52659a32f9b68ad752b257936cd847216e87ed4e8da8e8c1c3660c78d7c60db628f76d4283ece96a0a167d84127dc72313fa6d5036d66813e64dbf5900d882f5da4011f7b8013a8db78f2277a22fb72c96dc13c87fc6f3c0488f55179e69ff0d986b70a6de39bc7fdec9772dfae8172a118de3c868bd1be540f660f05041c7574e45169a1b7a0ad26bd1355d7ed4a725ba69caecc52dc0eaed14e4f75988ed5ee68699f0f08c8cdbfc528cbafe77b721f5ceed24f709f5fa6a5919edf77a2b78cee00785313250c129af0aa933ea4723225fea1047d6b001fb4a0ae5d9449ecb8a17bee61552263ced1950f91e16112d41fc9d2669710dbcd9eec932914f02ba6df6379f21572626f1498f65f832772381fc5331743afb2938fe1953f443cec80ebd8a9e5acfbc124026af538729072bb6c468ff12b0100d049442463658ac92de835b91e4c7abf5cbd840fa9baa2728cc4ba3fcc44ddefc4a964ab390fc26c3788d99db807cce289c38220057ef62fbb96faf3eb3be6708168019cc455e08723150ff258c8968b586b8fd007149a72a429d768d2e61b4f878e4d0c6f09c96f30c6bc76e8a2d485d9faa5bcddbc8e720c0898f00ec32b4ecf9785f045d851f61e8143d7979b888bc98849cc051f9472ca44b7d82ba3153c56865fbd09f13863ebde3f0208ef914a85e98ec900d932726837303f32fd92a057ecf434d003dd4e8112461acbd235272e3d5dfb8d9fc412a865ade24002404f4e7034d3b36a07687cb936b4d0bf057ba6428f9b90ea5072099d412d8bd7377edfc9fd72f4551efa6868aedf18bd1d218a9850f958475c72b8a1d9727d10d20b19c026a941d9deb4114279071c8a6d0cee62ecf61b0c9272256e02793bae2a5950ba431c4ebc5074c8759ae22e13bd5ee68e89683b8308728108f2221b35bc11b19161619a9f9b9232b69b53f1f1108cac288edda6967d665080260425ef78e66ba2fe0553fa2759be47bf474f7561aeea2c47fe7638ba72c15a8fb452cfac30f65742cbb0d7ca2eb73446cc7b0a161520e9f51e14dcc4478cc2697e3fae78a59fd1118c16c9be4f361949b078dc340e5a4b6eba4dbc8e723db62c85a833dae7f23111d81d830089f898a769ddc2ee018c45a603477dfb72595892cedac8119f02717752de87df159aea8f0b19e458642e69b8a32db3d046b28a6344b64fa2c1da7a2203e14fbfa27a17a64606d242551e057e36f0fa1472ee9d9f18a72aa4f01258171456f91e56193bb9db05f063d40472f593534ea9723e9f963531a0ff639ab65ef67d2f155f744af3c7eab989357e26766ca7ccd2727bf8e5dc11fe79321ee7e909c70f3480d4ae843b03c0ffe4410d2654d2db2b72ee7cee3f4f0ea2c3f929c32f5042c49523b8697dcbe51d490ec220d4d2c86753c83d5847406c66ad027715d24a083ddbdea95c256bd993b8ba0a2d42cecb846dc2baf2bcff7e7c07a485eee070d4c1572f1a559fffd7489f0f6fd751ed548272aefae82deea3c4e60562c943c56dd0a01c6185f315d997ee42db55901d5dd5720d2810f63d4ab9b473018fce208eab15e6e08a2d89feab94a4be4b0e3d4e0672d2b0a2c65ad5e1386bc36411363009a8a657a8c31c9a6b091b978542bbfde672e80f8f89b7755a1d79046f2432194ed269c211df908d76f03ef82d630d7eba5c19778dbb5818f4302f47b199027e6fb0f5f5dbf9e2415dbe4925e1d1461bae72e2e6135e8cc47937e273b0857756c0f4a96546de9510e9cc6d81c199f74dc62017ff46cef4badf85fe0ca36e19b2648286dc50cb3a4fa47b1c91810016016a72a2d5d3eaf28a58bc299bd53aac08e8132eb5296d91ab3b431c4d5ff6e332806b2af4caed27c7434450aa356c4ee4405d74545c688bf53839db5f6af234f56f1116d75e8cde538e11357693e478739c049bc6a6217f096ad75aad52709c59970c4d0719be482ad3a5d9aef3f88ea0b769d368e664152d3f16bc266aca90fc6c72b816db05953c24630b29490a4c2087cd78b25854b43a817a8a59a4a0cb1e186ae3d80b48186d772cc10ee6bf59a9a2b5988f9e90571d727d76c0c7605a47fa40043b5ca7990d6097e0820d85fbd76f2beba0f13064e286d721823f0401cc3972e7b7b314d79257c722f44887b00cfa2f1e6338cad22c8c2d0a1610a42b268570596570f269c7e197d0f35ada3acaf6eb19cdaaa31254f6634a4f22d768dd21722156ad78e9e1960ae6ea33616c9c235c88f80ad337df5595a094ecc1f3ae3523feee89677b937fd171d1177ad31db57487b1ce953d870b624d701fd13700385a7a2a4a4ea3952348fd2d4acd62e1f45363196947e507e0124e3c4bf5ae4a5872615083b47d0af6a897ce9f6b44f23b698e0f6849c49b182c3acc164a47fd2872d5abf8a6e8fd94fb2a0c4b9ff26807ec877d58e2151523a4e4edc3724ff266729234791dc367794ba4358f91c48bc61fdd8e6688bba02a3d75d94d593b49b82abebe5b1ab07432a04194fdc83de90d0f533062d96ed7eb2d3b08d1a6b925187286642d8911b06b6b41c7f7fe559c5a42bc8db1511be32160976d37d41d2f9472e4c02aaf97f5d677b59714ea64fdd077557e74149bc7e9c55258624f8303524e200aef98ea6c97b9f275d5f732e417f6dd56ccffb770d10ce3e5b88c270c39234ee2bf0d71a219e7f4d1c964799112a45ac1d82e53ef52239809bef0c6ffba72a2c8782bfee1f27f9bb7c359e4f03abe89eb8c67d6d12c6c794efa9bacff4e72c33427c352a80f2956bc4b0b292b348a2f35ffe94e430dc38c7fed3266c6200c636db06edeb1fddb2d63d4522d617e5dece3cba1d769c176416425974664057252d8c75241769636bd488fc75848f037ac14f0ca28aa7e0f7ff65a8592dc8f725f2e30a0c3e28a3e1e897a244407aa320bb72a1c088d81969a33e97167ee5a7209e33e939c804df405a21c42749f205ff94702ec8ec93abcac59ae7261c63a37592b79749112597199cb42522fa7230aa6d0e9054ecad892644e505acdc45772787c4a5efc6e69b38dc7f7baa398314063483d76a55c283a4d49bf222531e72664f65e3732a0152507aab9db6a15d96e290652f7e03981d2279e29d92b9152153f139a24c65f203d092118f0d3d891af6bc2a6b700621aca2f3cd8b0ddc238421894172eb8b1ace800c8ad16bec219c609d054e679ab04cb34e57777efda1b72228b83af1d51c91becfb505b14389bcbe782fa2cafd16331c735f607a4502572cec8327a3503aa0dbb9b328682e66129cac026db64e624be7a34e7f5b7d025721289386d5e994fd505a22dfde42e7215a040eb8a35f85ce3ead3d416bbaeae5ebfa908c22aaa230b8891e52f6e1a83ccaec3b9b96dd750ffb9174fc8b25fe73628051e9188113155b1e83c36b3eb2707ba5400962643e0adb72d110aefce77274b5cca40f37fc41525364dbac312c1fdaa5e651bd4ec01d11893f331c40aa4729581c8947577ded9ab07946d7247f41822869d0c2a9423535f0632662179986ecfc7dc54f65c3713f08af1cb485df94c72dff73f38e0edc3d44f262905513c263d1895091b56fb0369679095911f9fdbcbda2ec8a5f61867f91c3b7a2b9756720bdfed1076ae09c299d8cbb9821215206e6d28b6e04b4c5c5540a23211599b72d4d0cd9e1343e03d6befb348fc9937f611a7c1b140cb9eb644ec3d8f9dcf8b722714a3f46b4063f7ea537d11b17f057c0bbbe9cf6dc1eee299e25b45a5ae40727ab946e030d9e672108504e94e6445dd49480d10c2d182a9a1bbb3d97fde0072bc65230a28d59c09e80caa8867d0779d914ea3e1d0f3e2be9da6b0ac9a5a437242d709811c26ddcc5c5c06b3d8d2ce673d1daf0dc1155f6a6f1af74e4370c472a9cea0265a69c57c6e5a5e72b3387a8ab2822931d97e46f7a55a50b564a8ab473828d2f4578a3dded4a8e1ac36c869ccbdabb027a9c2d91617423be313f06d720cc1688bfbfc9ab2cabc1cbed3b75be93343cbdf5d7660fbab32e935121d611ce0b3e153571d8cb31dd84c4344406f063f2fb881c8672dd81c565b1ab020ae23d9eb54c8ca65859fbbddc9e0a0e1b3b5ca500107f3b9e8feddc3a2ea518bb572166291fc8dd5855b8203aef133844eb17d5131963e0bf15d693dbb190395b2723291052fd6a265d5349e7e87d60c542356957cdac4badf9e97d575d34bbf5472c28f206229b95fb0de5e03eec23040d80bb31edd25aa90687fedbea6da277a5d759f000a92284650587c7bb4700f509f48648b36d6b81362462cd4fe36aa8672126695724e56c512f63f7a580f7174487d316fc4ca317f174e8bde66cfbe777267e4fa1f95a20021ba559887f2b330ac60cce190c9ed59e5aa3a086b08135e1b6b998651d3c18fd185bd902c4b21dd5ed56729106c656498d83010c8ceee4b72b29ab192d062302b30bb5558706bb75c395320e3775235a3febb57a3246ce07285f9c9e78110f22c4d60786a052b0d8da181ebe15b0fc20a1b2861a8e8f3f1215a249d5ddfbd7e16c3eb7cde8f4f60b9763da98b66e2cc67b0258b1d1afb0f2e4ae9af9cac0c4146e5e19834db3f93948de596d42b21cd909d986e7de85607723203eb7fe8743acad3eb72032067f6b052d9f1ae4c00830c5c85ddfe8d431d06dfb5fdab6b62ce60c94b74670cb6de6bead2e2ed5f179e440a9be3b53fe2fa72900b30c2aac53e266687a1a01dfd5024554c6d9bda5413f418d233ae6161975d0e074d8d21de78d615f797b8627fe2c8434232a3f03b60aee8577282f25d7b7250a74efe006b8929ba83d7cc9ca97e6a6493e8ffb0444958134cfc00602d90728a149aec879388d4e4462fc684f9cdad8aefc0a052efc079428dde9c0d8570723c97e9c27bca73f67a6f17041d2b01a4ed6a7636f8c0d579f2474f54812ec864bc820adb5a863778db1a4e2d6ad66b419a178c62f16d883b1f74fd6dd7d075729c27f5a66371253123eb12ed00baece339f6d4ec9779ca2fa099734aed86d80495952ae83b8a77f87f7bfb6781226fc65eaef6971d90e65fb8691d53ce7fc83f237c97c1103c953555b323bf993b37cc0d03fef13c42c5404f8f2245cc8cc7724e8ac7207bfa8900ad37b20870aca7d20f7873c2efd433e348bf892c4a647439d155dc3991a1d54584bc22fd749fb11c65cfdb1b0e995b7c9add1ba0e5de537205eccc0647cff35712863506992c7924292a0b60fa63e81f0d788545632ea73c6ccdba5afb48d2f9efb7e636425a5357403c70944c65ae8ae1338afc8d891872828063ef756eaa53044179d1b31149d8ea8873e80ecd9a14a75abfebec512d06a9957640f001914e72756e0ad9a422ec1661a833c53b67e6535905134499510997a3df45fe91d72a32b05301b2fd66de4cf105aa017a7a7b92a22262d5aac172f6add45ec6bef6cbc17d8caa86bc78c8cba15e39001bc5714bae180a58159f7221a63f7f411b6139055e2f71f3838a33f7863174bea9c5e4aaaf7db1d54fc672e3dd2327a3b38986c6e7886a9b782ff3df25b0c1f9abe255a7a7c26feae5f87285dec745116b928fe602d1ac32147d088caabd8d7dd45e868d0e12f97792ca0beae6c7dad5d35a7c405ba5b9823e79aee6c185bc3e281ed4c77d17ff35ed77725570b332ce0d76fd835c52ef77ac917f47bfebf5c5192b2928dda0b8cfd75372ab35150ad3aac643187b982b386a3f3dc3e4c7394c36b84d8cdaeac9dc9f8072f4b78133b1932a83cf7c250f012a7b81431e2713281bab8976e71f1ba73f87156bbdaa888de1ec8ad9d45b0f9e48563110ee55cbcc7856992cc7537553bb2d5ed9725774b6b6dfb42c590974a7625ee89411226aca8fab146e64be01c6a06872468602dd5b438f1aa48a8e0c674bc19736c835eb2638e158fa8ce10aea2db472fe4cdc36b7873abb9d59834cc1589bd674bc81e8d96aff842c27cb7e50530c721a8636715649f82c00a6d62acfb1cc41f2a266d35f1464958e531847369152721a81d2c4f0762379e69e99d0f0eb5d75e03a37ef0652b497e0f1e13138f30a30a57546c863a3f250b1fbe9e9c4c22118e1abb6da1b4fd8408bb4b31c26483872f4451dac74888672b4de6c9c2a61532a895fc0ac803e777501c52e2def50257214247c8be3f0fc31e7441f9865763cd8b60b15b75230de7bd49d05efec3cc272cb556c291d0cfba03b54a3febd69ae0abbfc867a8ba93d88a9bc082af1d41a72eb5d9f3b83c1c8f587a809c4975c4097e6904c122d219b5328e9f4dfa5ef3f504b4efcea3df0aaeb88ff088e85ce932150be6d1db36bb2d582457e48fb233a7218aa9fbce3d4f49f59553206842216ad70559766292ab9c7d03096ad6adb221f1b8df65f007afe48a5f7456568e51eddc940fe9208b7bb383f3c31f16793ec04312d43a8506b5ff42f12a5942e7d6b39528b6a11141747aa17547a9b9fcba1589dad1758d3310a88153be235b58314f3253edd0296320c96325ff9cef38d0e72290e7ec69fcf417e8b404b466b4b5530c96a309f0673f4cc09675a158e973072e94cd3c2ff6b0658e7759d379da83f4a9bafcc4c9330ff799d059946c01f34723e7837e34bf40e7c02ef29d95aada8a54f3a5ac734996046754324ab633b4333d3008c6378a45ced40efac6d598ed78901133b56e0a4dd3cc49ac841f1644b725528aa75f81266e9020f363915a604701d8a7a7c41d800046ad45ccea3acf21e41d65b8a412e81f66e33ce7e1e67add8ae1d5838224a8f85f138b4d6f368b972c37fbf232f539802df2b0e25ea233f8b55c5089df26dd778bab10b6cfb277a7203fb5f4b8b3a7adbbcc35eea4dc101bb52fabc8a42720992bb5aef4af8d8e137d51e1cf5cdca6bca7c7cef9063a8879bf5304139df2b567aceb9e0df880e59450afbf54b66011ed3331a4f23f5cd280a6f883198c867720d9164ad807f738c72f173cd1b39ad2345d22d630e0a7be61e0246163e66f2bc844a5426f0b9428172e4d9bf95c67c8327b54fa9e8b9e53978e6c48168f370255b034e752ea8f4d472bc54e76a247ed9e8ed351c7addc81bbff02c8f3a29558b3882eba527471ab77235ebbafcba7d40957dc7ff6628120c0caa9cf5242cec60bfe18255df82cda372062ea264cf6a98a773f9020d690f5f4f6d9a176bb4640cdf6dd9e4800d15f572106e74f20c26f83d0fb96270a86219cd9aa6864f1e72e574ebdceecc30ec4a62e5b489ddb0ac7d0f134fcc7e067edfb1e6368a20efe185c59f1fb38a73d63326870c87954e1f2420942f91716f897fda260bbb5175513006c986a957c50b6672d9545c24058fff0dcb6c1d8fdc025e42687967837808617287efa26270085572154346a6259c06d189edb81ec7e594eabc6e271b070513e0b49a2ad641e75b683fbdcecf059d516d7513c5b081c74d4f5e4e2342a8a879e82795a83eaeda8b722d4e4b446129b9f4df5e87cb1fa91f4f3cbfbe9fd6866d251c9463d3e564a2728ae1bf0b31ece735707a5bef1fc856b51462e79189d51e0898ab291b545e2a725761c923c50887b6a4da79eb8e218bf49565e1b0abc436c8c56d401a4b0ab67235bcf08e1f3d698746fe3dae9e5c76cb2b71573d6341847bb18ddd884ad1737227f9ecaa9d0da667070c347ba6925888ee34c9ebead1893db40f2588b13b66668b250585b726acbc38b1b98b2f08e3a6b49f0db819bc8c20d87a87c23307465e95edd4de046c7106b85ed454f5190529c65ab23842b8d478e27feaa6f1313672fe422b9f85949eae251f7d3e3314f03cbd0944c710bbbff53aba1259a77101726936f6ba1f932258ec467f423d43ca18514b933ab748eb6c3332d47905eb7972fc79d8a03b50642dd660ece43149fb3b71a98a7388b65b1d10a23c99df272b72c7feb54d82dc04a26d2da46eefbdfcf3ddfae1f1ae09c1954b6e6401e164ce726b54157fb221a90a0f85193ced2b4fe8d7491b9aedb63d6e14256dd74b069172d4dc74f825c09e1ced32f237262212764c64f308e6f6cdfd7dc5c1485eff6d723ca9af03d693ae28a32d27212b4cd15441ac825d595f579eb924df500578d43c39876ee5884353b01e4e59df847721ee295b550baf21b2672d61fa1cdf8d2f46a9840a28198eecdd0702577540e2ed55180681207f3a6d2833409bcf0dba76727c5090a8c915216ab00e8bfe45ff1976dc1df61d8317cb350a264f862394be729964a1be0b040e8e459b4d9ba98ba85af9fa8c67ab7c23ef37adc8fc9d415e729fd0063b9ccf692c399f81702c40c31e208d124a487ddf415a8342b04bdbcc72cf44c215c417495a1ab8828df96830aff781e0e3c0ee3711b6e2f2018a932072e63b682baf82e1603c3f0c214404f37cc0bd3446f5b2e28e96f14b325e20f672016743121b15f46c8815f8a68783302295966ce897652f10eb103a4439a1fe7254816461d4ca9964b94b5e0356866e79d13a748263c8c99e982ff113287b2031296acc78c8a095b3b3a7904e021b25e33f80fb89ea13345d2acff0b213f3bb722b1b0e0bfdf7326e6965c12c6b5d6e57c38bb78e46695996773c1b2a84360b722f46a685f62b9acb2d5066365d67bde2231e1acf6f6bf7ae4a722a02f812fb1821968f2e4c84c41d0d46bafe532b36a99749db59b3cc96908c709e2e82a5a4641de6da4f064cffeeaa348ddc38326746e91720c2332f5ec91d0811c7d3c8a7728c5f8acc4ac5c58926314066cf3db821541dc226e886424aca4770bd6fd49c72f5d181d87660aa63a38d95b3f31c2c422dea6000954d10b0a8e707caae2a15727321e80a9fa001c3ae16fba8d119dab2a8b3bc6166fc3e0fad65cbfe3d0ee57210283dea29c88dec4789924e2ee1bde4e6860d4f52afbd4156845066ba3c9b378b9974331485d784b4e91cd29758628837e18135bcc418457f94ee716db0d123f49a82bb578f75ea75d75897b64d3e0cdb5dc7747aaae545b2643cac70f32172f98dc7f055422e89c4834eab119725d72aef4670f0eec5c06c7defe17e70d13a35135e9e9acc76909eab13c81e3f13859b69eda7149434d8d56e2c3fc0f38c5f2e6f8fa0888f1f813e5e5eefbe4fabf085f4bb7b25e96e89bad16d404177c15a967071335144bde5c502515af42c8fd2b6b5990953073a07e4a671e53d99e8722e742b14fb8c9bea3093367db1e6d86d79f137309f6c9bb9b39ac2928b257b72fb855b6fb37b3bc438227a0cc604a2082d19fdb5fd202d87151e0251eefda630d652bf4c4419241e85b8703c34e4eea2ebffee580ede59a1caf934b5ac6ec97297ddb1e224808416ad034260d10543498a874b6f2a46788f5a5577178a95ec0a4ef420c194fa2931d54afa0b648a8f07c406e7f6d5d9172b09cd8a8db0742f72e0392dbfd76af955c479326969621cbb182b58568681f8c077fd696e503eb009623c14d3416cc97c117eb04fcff776e3869e6ee1244ef30eff5fa61beb9a46621a0e22c057371136f73e3708ee5c0dba6e18370a31222f2dbfe35cee699a3d728fcec84f8997b1056249b7545503bee7c3cd03f59d00a93be8612b340a6d017265162547a8bc9e22b9404db81af394456dea316d26ac7e632c15d63fcd159549a8772bba63e051440ef8a58271998ea0ed9c0f247be89d29e95a8c6178574b722a84f61e58f89307ec69904f4c9a7bf91bc226a4459b31e3c4993d5b6bf2af068a5785980e19d811aed6c0160e5cd48bcd5b367155ebe2c149aaf9eae5e8d74903cf57c1e78d458423a04823193624b666653543cf257a930540ad2081423c729c194dec9c20087583d62b793b46c5fc6ab670e3f55de9da1256efa6203fd06b59f795de785bb5b759595a68cbac65ae6129dd46071a1390c860c9cd377aa8725575b0d88a3c7ae5665f6c258430fd129a582f7628ead0b7cfebaa59154b5751caee51c1fc5d285fad3e37b9af4e3fabff0da6c6aa83240c5c5ca3ed6f6764667058e133d1b23f8ef257e543b17012aa56d1cb261b6a0708e26c37a06cc0370bbfe71e47d4fcf007fd7258eeee24f4d80626c5877b50999bfeb3e03ce86a8872371128ce9f0c9e6d4654111dbaf86f3231c2b7956e655f895ef2fb4f7e67842b976f67c63ef4bf5a5303571f186114f0cb584c7dac0ea7ea1eb13d58eabb78440dd986254ca36959bc986bf7e340879933bdc45f78f8c1a4f0e9152eb5dede632e51cff83c0c0334fab666079e8d0bd2dfd82637a1bff3cc40ed85b96082b43b7ca53779e2cb7293a5627a60002b1ebbad80b967c19140f879f0b30a74217672f72af0d4d9263cc7fa072d864531517295d69681b5372b086ea4a081e8f86172232a4904a2e7d7c37402b808b082519c45a6e95f69ab177c2d9f380dc6f15a72e08f0e0fbde7869bbd2e3446f4d0c9309d275b964db044af3d0857901ac8f855cad64ddcb748fe5412037b187f72283d1831781326cb7cbcecdafb6a2ae74372544d52e5eb8c58c02f69f3f672baf783507ed8c518ef223458039c306e70085a31909b2614980bc7434fb167202e1df7343379d7adbeb7dcdb0fc9e22b073e024856872cf06e6cc573b8206c0a06c2da88ffebf4ef3800aa14894eaafff6bc725d48fa70a614f48705286a787d2e3e540268d90b3395cf48987ef9c7c544622e01659fa8782491588cd7281eb121323bfe6d15d082e4dd7b7e73d7d3cc34ad6cf13200c22ce5f2639efdcd7fb3ade16130f3b14ff41be78785c500ab2f309772ff945bbef68e9d459bcf3f5ff78f0d8158070655a980002364229ded63a73f729f7438d7f8a3ba61b2b61a78bf579dbce523876883ca1585a616fc310be3ad72aa151f907103dbf6ad1e1aaa305d3074e409b8867a10a9626be374ef23660d72ff6c1d15c670d615e06192919cd8070eb057f2e981121876893f98f08e6de572c38f37c247ed2ed1034a24b0ff7abb1634a198490ee6b11182415457aaefc872c9f3015f54566dc9c30c027e99a579fa68cfe6694f7cda83684d4faa0749e141fff69c200bd7a3cea1db3d656f70ac6e110e0226753ee061dca0a2424d482b72328a2d806e4d0a906ddedfef13c4b5ffdb4390131762b11cccf82623b674615b058aed076f06f031f0b4f23aefc81221e4fa70006a830d8646cdc8da76154514cc0f399fcf682b0ab8dd46f970793be7d41177094d9dfe39dd7c073c763e8b247f4143d79aea85be92d8fcf9688f63d828a55fe64d023d948b244a130d95ef724e975ca90c2898fbf3c98ebd01806829b8311c0465760045d5659b4cc82851725ca38dfaf44294fa7ef12f8487b24e10acde6c8e04114a13278c04872de1a0254362dfe4b65d875f3e1214100eb3683a90fd234e9b10a53f102ceafc20427a7282d5213b00edc55e250c7922786411fb07725d9f468d1ba6d8074bd6bc7bed72661057aab8845ccee976165dd288addb853cd0b744cc4b52614e637d94e5d339b8434d95bb4c9703bad2a94729c9fbc370dee8bc343758fb023094cd3c140743512091e4d9fe76950c7a90fe7c6a080adf090c9925294e76afcca1e4d7c01b25c02bf7db6dcfe07d2a10239ece93ddebea5b7782c6ffaf2b1b97467820201224847367de8da74528ee1df883c7c9b16343dc843d88781156c022a450f71b4c728b80f96523e0ee45f419f4414afffb21b57f1eb24af41620afe7beb84bb84f7210289349285d102197d4c5c045dd66faaf6bc613997484da5d04689744ae1d11d6721417a898bb511b35444d443a7c1c1dafeddf6cd4b3b2c021222d047e8172a360a9ab5d7a2b0c54bd559b8d654a04dac3d9fdbe2595ae4499877e56e18d722e833be5bd6a807fc0a580e4d6a8a1f338383b6a6d015f6d6875b635e0aefd727b9216b7949f76f26e29a5cc9b6dc49f896e5e2def7f9d937fc18c4d702b5a72380629ff11cde23a0c56131c4463cc55a356b152c1cf5ab88b23ddda813e8872a992b3ff9b1ad1edf5f9f4cfe46b8b8f33608b990d9a35a57dcaaecb80ee632653e599b0fd8544fc19490e2e881dc339782f2b70528844aba6984e65f5d25238db2e9fef74f5be4bcf7254a8de48377d0761a2c83019c41edd5b7c963f3ff772d145f48b2aceeb518ac4832a8f776f4ec47c601476975901c0fa2bfb3d71a272feab51513786dff62ecf4a878ce9fdedcb837f52fe3da054455c0ee2249df27296212fd23a7b59f2c8989af57d058e9dc5d753348d4c824f63e5acd92786e54f54a66182577629ef0c4de7f4b955dae3716219081275245c4d6ef3bf80835d72ddd4e462576bf2f16828e2ed89e08be4b5d9bf8e136c6f7a5b5db36204c90c7236eb5e66878078110af47f7bedda05584c99dfbe205a0d2630748eaac289d7720e077aab6817482d5ea8b1d36ef67eba8706890ef4a90090367e9665c5c0ee72a41f2b69666efec0c35cf3c7b9fead5198d98c40dc17371ecd06d79477a6f772a5c60af587d1a7cbd0a2c151245fd3beb0d3568844dc8f2b39631ea4be35d7724101bd3a6195b357a3432cacd1a032eea686e9871724d32504eac5c29903e8722c19a747e3d89f3f32b1c2d830f89168a69feb7525ec999f5df27306bcbae772fca0ab6d4ff782ab44eab57dacc9b28d5f48053e3c40056ba0bb9e121c56bf72cf5aa6a86a1cb68a22050ae2dca76eaade9d7e584f528ef566562efe6747857245ea973a710940781ccea1766a86989e0dd154b7a39a6f73bae551abbe93bd728be34e12eb0a1cca8e4692db7f3a814891e1d7b7e6b9ae766580cdef244e063376713d5a19d5eaf7a70700adeb4d980cce99a7e72781d33f1c8af07d9c698e2dcc25182b7a5d79f5f5f05c8f874a08babf820cd8d9f1a93d5f15438e7113d8725b59a7a30b3a527f5b2b09c5ecd2b129ff7f1822484ff276ce323888b3fe635e073fe6010ee100580a83306957fc9dcf8b71663f21299bf9668d26b4e16ee3721718d2377fe37559824fc0e0b07403b856e1e3bdd97d35dd379d9c4353bc5272b37fb662b8291326341520c9da84b45103a24d4df3bbfb6f2b8c955e1b479245fc9223fcbb603040d53749ee114daa99e88a51455cd3b680173a37eb9b689472120d054c8d8a81f98faa5c8129818ab1896b4a26e3aeb7dbeea6d8697fc0d57262ea6a0f88982cdeb3eec2d72f032d6cb7e04f070d328e6566875d9cc69fd172bcc2d62b1de272418bb5c3b359e2baf27f151a8ce5328428f069a82280652d2966905b293d4d4236709b294d3fff3c400788db21f2c9b0962de9152aaca52a454248ae184cb70fd764ca1a557c23277623ce68073289c6608e4cf779dabcb872bd77cabb2b52da6706e161be4f93d80db12b0605d081091798ca850c70d3782f5179cc7bba8db6515f90b61f12b7e0eef2f2e59412c73c743618b4c39a18357295c868bd028c697ca06793bb4e5d5bf4d3837c50231488f3c1c549b526eaa57230ece00e63af48300aac866909442b57e4ff8f44dab2599e9a055f897ab2bd1e4e4f79aa62df7050f653819d84e175368484fd1a006f96b81d12a5e7eb821772690ef0aff6d7286a47634c8a573c5c3f67f7dd3b4a170dfd6fbf6905f1fd556ab09fbe35adf1d79609fb6e0523476847876cd8757b3b0e350f8f04cb0ef0ae30f2d34bd10d5b22fb5ec7f988c6d9633b418505725aafeca38d11f5e93e2e51725363613547534fba8ea3b58fb77840d1f5239a6eaa50addc2abfc5bf80d3e3709380cb58591df52172c0ed11750c8868849a698d1fd9e764d3cba39f37896a72a8940e5b24828ac1731bc769dc7bffe608625639cf994512c82e3d8eaef469721d58ee4ef4dd2aaa7d2f3ad433540fc4d738625ba63e7393741818cf4542732402b8260d45e0128e6ddb7ca4a160e0120d93cb62aa8a7a48f97edf3a0d2ed5721c92aa9fe2b113b3c2b6f073b58eeac6411b2df8a59c38d2641611c23fd9e672b6ed8d3a5be78613384c32be5240d04c8becc9ab0591dec2d044557a7acfe23d776d083ebccecff28ae02582778a90aee1e24113a08aea0b9bed2a6b0681bd72d2e56bde3aa7491d753a8f906530b09cd97d2d9ea52fca1d2fc92a89fa22822f8e073f867871bfdaf015d6619083ab7e7aebbdb7a177e19d872d3d53005dcd4c720f14141286a1c6c7eaae8efae8bf0a0af80043a3dbb0452545931a3c3bc772cfef12e601969bcc6b0cb7cc89011bdf41d81b7ac76b8e1379ebe0087722154a84cac1f6505503c06c5fbe3ba23127199f973109623f912c4e717187c9258f72af721224adbd84b33ab476d97a074d4dcdc2e80b1b1478b8cb1d493a6a9e8572fcbbbfea2d124634b86b51d74d5fb5c47b926047da8f9d0295fa1aa0db60cf31911ae9757b6f72b9af16737b9c8c4b123587f08659b77c9b2c7c350b84abb6726f639d5e867a3f8f1c0900bf5b4f325215f88d3eea88a2d894d83ce9eab7684838a96146015d2ae91d343b2d4b1099b8ace91f6718675e471d8983b1363d3672399deeeda44a5f251bdf5506812e6f2fec9c7d97aa548176eee5a1afd1c3492e1f5d4ef4f7a5688ab5a1b0533d46c0bc65b951f190d53c1f48bb1d227ec8d472960b73203d0a7a6705449475cec56328cf556440a98ef5c4ae5e36cdab633c4c3ae68630415036636aaf6e9ec17a9112dedec1e861870bbff95e282b847f7f728971ae22ac6f1f72148e7c62028584b400d6954add58bfefda8fb29f11367400e70da69ac0bddd38ce821b843234d6b40f7547391f68a71c7e452c558a59dd722492b87bdbdc16b0bb18ed082179c3a2741cfdf268aefa065cafdea12980df004d9041dbd2ae7ed9c6f0d636b1f60cd77df3b83e0165da5ad476638660d2e072a6f3f53f3116098e1e0918abc12a5690c92ff0225a38af4ea2d2035cf45cfc4cabf05fe7cac2b1ddec5cdf3b15b0f71ab5807369d138388c7a68f696dba0d17285a96d11a4cbf84a86b416aa43188c379a4d238f510fe61ec6a48f8c31e6157211fc2852bde47dcc0b0fa07228594896d717066edabcd92e4a227c342abe3372a8bd6caa8775416f6fdc5c212edff3d3ba929b4baf8e94a2e588ad72f27bd772e462aa9e1e6329567add77c8f6780db5de6feabd31d504c3bcce641838ac962ebbd887bc1db8f7ad6825182ff31162e2e1640033469487963a807a1c4b19fd342426cb90274494e38106b9ced2c5a1d22f15926e0d3dfe3b21c2b16efa02a462da51462af9f63b778a4b118640f3c84c93bee22219036314daeda571694ee272976f909f00a72aaad278467d0b891b4009d913cc0b5dd1d23616b2656b3b5672e76aae716640c39e04ac1666d02e833bdb4017cce750b8af3eaf099dabe257724fe67c77b348184054581cf870b83f74a6aaa90c75da3d91a24b6b7b81787f72415814d4e18cdf0973b15ca3dfa1dd96a80b53e765bc52be5538666f12aab21cf74a90f5157fe968463aa084ab4c095af18d2b4cc2febd67f595fd0a7f1047724968b07e4df746f07f7b2b6669e39f73c1e28ed4a78a1d84590c4ad91c795c4e7bcaa71c0ade54de807b676f1e2fc9d3fc79eaa49ccec245606f08b36750402374c5031ed0931adacad02fb96a642322da4dce0f60f7237d83a7fcba97cb4b7238f8f91db37e58beb2ce79595c9365098b63670c1c6e82aaa524e1f47d627b34e73263c7956d6c72e2ab7dd7d01ef788abea28479fbd61d62e45c8b0c0004411fae3c6b2d7db837d478144e345558868e90ff111287c4fa14d1ff29eecfd1853296eda6781a1c5f65fac481225419c79c05ff367a01ae5444905f08ab7932d2dca469c84556aee09c27b6a21abaa689a827d98a88debaf241b210341039239517a57ca112700f23a39acd7a95e3e792d4e0792376ef9c54f719158b1602cfa72c4df734af7fc239d9eb49bded366162169b0ee4ead33dfe9dc6d46890ecd92580c5aa19475323e8e34bf41d44f53005652efed3c64ffdb2dbdd9d59e6467ee6d8061173442df3f0293e9100a8f206b75ff5989590bdbb9f2f9d8772104991b72b3a006159c46f02768a1a86e3dcd554891a416005464638a93fc4fb2bc27730598c283db7179ee862a09c35f5274adf7a13698995682690731ce8812f8871c47f1e16212be83b6629e9478440e755a6fb1a382ae14b85203a88225323b29ea724e3811804bed7ebb8b48370890455a51eaac684db36c26f8a9fb24b7e8c74f72881ee724503c820406ac296caae1a8d9b1fc65b66394d48193e90803f28aae723514491c70abab3d3d688625cdf4c3b3433109d31fd02c682d9e3467995ffc01699e23d325e1c6448b77052e1b8ce468d9647a0ecb82e6f4d64cbabde5067442f062f4d9881e78f12148288dc09e00b06aa656ae5015b1ee3e0be1cbf1e7f072d896926f01664bd721f1cdd47bbcb184e0d437a9ee2661b0190177de217d7341ca6942d0a19da689e8182d75402815dceca8b2d28f2e4faa8e3681e225e56e723bdef67004e89ae012bdc50b95c174154333297aa4d11585721ec9e07b01f9724dfa3ad475a4f52a03f945b5dd4d0081f87cd2ca25ca9c1e6ec4572156b97e7287bdb0c49127fd4f42b21a19ba028ca1fe0821240c99a46cbd25cb7b1bffd57253f2172d30af156c1a54802af406da28d93dbfd3f2ef42079b89c7be6fbaf97248820cbf0edac0ce3cf7accee302087ae3807793067577127d1be40068b46852382cbb9877c5786a79575899e92eac498e316fbb34a88c8aba4ec6343ad81d3022f237d5a801c2eddfb61b7a789dad108ec12b2ce600e81b65ec1277cf4b13079855b8fbb0fdc119a4ed21c26cc3021cf10f9a6d35171092e4a9fd063fca8e7222dd35ce30bdb8f5adab39228b6a055cc6ed70b7a245454a9b39c6a0cb02e6283b7f3022dd8b80682e95c3a3943d786df517efdd7541c846dc3f105d93df8d727d3fe55809c7e286b9314fbfb33a70026d09300cd2b85c2e01ce990a8a73f55d349c27de1e800be11a4282d04b1547fa2b795484f600e4e25183832284e55272fdbf904519562694b96e17de2dff8daac9051c05d421de72132da224bd7a2872c47072faad912bf921174075de261c4aabe370f6a8dd5fd8811ae005499521721f23bb977ce6a819cf75d11cae6a3dc9c646131b928e81a63d7ffc95ec65e872ce4f2142983fd9b2a466607e6622db76777579db10bc973663c8e6f1f9554172d2db5283b1154587db0475f2ce5514b94aeaef84eafa928186fe47575e74e95e03571fdf0e7b497aabe06417e1547862b48ac737b4a9154f6498462e1b9c4f156ed7b50e409405103150dd75c2949db4fe81cd992908f3660b9acd15d82dee72376e54706bbf5ec7c07e4cfbcb073a3e0f09c7d5c1a404f88e86c406e283f472856747a35982a0f50773c0de61fc712c7ce47a5d27824984ee38f8a696ccb072d91b8826f42d21cefd8cde9bef3e0f2e45b16c4c5344e336ab3ee93b7e4025728486ab732f816ba0172da72cb81c39316571f8ea02906f2583564329f5fdea72595b691b01fda4ff3704cdf35bc3e3c13b476a58ef33285c0a8a904a71c1547207184795908293110ad391828f2de893f7af40d5cd2bddb33cdaf1ec0426c37289b23de3bf67aee9e7ea78d401ba396c8e30d74b9db4aaecc5e86a14d2767072e2363fa205da6642b6c16eeb2c844b083f8e1c1d72ded50bbadc4958397ae52cad77429293a44a95cf18b42206d2ae052f21ca8f7898b0f24f10f8fb36c8d372ec280d4e3820308a0243248b65d049a0f75ab48472cac4b1b299bca7c7472b2c457cdcc8722ca5cbd5f889aca81719b713f52087d2d56754bb4c8dbc14196672ae04e2be7f7eb5a7227c40b6309aad7fab525fd1a0e04233325c15b92fccc4260f46a6f8ff69636881eee74f4bdd8aa59c746df2b63e5238aa9df06befb944727766caf13d41e05b14faf2176962b3e3576d3ed3d1deb790b274a01060c0a872bd20ececbfa7d213d06a41251e5526e105d1c890f58021f13eeddcb0d2f32f72774d44caef3fde71a3f93d6d7fdb1ed2b2b4a2e7d84b44d63384e43ea3002810fbc51e4f4eec4a239e8d0c9ae4de55c81f30c5f568bcc3d0b28f476c93c6bd1c0ce1f73a0dec8c92f824908c5d3f0b5f0210c196a334053c035928a01e18a27232c9a799e9ad655baba877c16fe0d8dee6cc51d367587c213e23e64ed6ebe820ede21c4644b919e3d2cadf8c4e963050ec2610c77d60427f8728e19260c2c272019ee47560215f2f4307dbaf03bb481f8f860834ff77a2e8e160475e24173c728a217831119c905288bff9e1644ebfa9e8f104c4c41ab49f4301a052fd5a030d4ae9d870236c58ba199b830460e7078cab9bdc519ab47bbe18e5ad3d512cee72498ce764eb014afb42c265c7f76a4a5c4c5729e1c1c1d132fdca6fc05b4cdb54ee7bd67c305daa450b037bc530b7f8de4cfe4f765b8a7f55e0510cc1244583528cde9de2b99b69e17379391c806f48d1f62f4b2c1dad31e7df7cdad1390fc67243ece05d715ade6ca66a59782bbda6ef9d1f7a8dd0a44fab5ba9e1ee926f51725536e692f747fb8dea81acaf1b3974608efbf798d1b03377693582c67acf7763c8ceda5489ebcb73793f3e4117cd61a522a61deeb1dfacfb599931f5c1fcdf72d755cecec70cbdc82af9880ff1f1a87a279f06349412c8ebb5e343b2a172d9128b619c66ea18b8fa69bda5801f97420bb77b3b538fbe1e8a0afa5e45845145364a75aa7fae3a459a09390271fda25b14bbfe736742cd4bd7dd5d40c0fcd2007263de71bcb53242b99d2b60b03b13b56164e4a058f3bc9986327e8e1743f74472da5220effd65d2b9e5cff22c194cd381e3b994b6f64953402cccbaaa417af3727337461fa8e126d12c26a51ce480281bceb5f9cd681f0efa794b40ea27e91e724de7f9a2e5054bec2f8011e77d743faadfec025c6b1a060da7fd312a76116c72acb3e7cef1a8555938a44ce3d86f4703fdcb18977bcecd5838a05bbc17e25d728e4a23d17e940e71d5b158c8b80dbcdde38ff301e75b83c20e9b5d842dee0872059a61cc617337daf30b5a1f0a4a904ec12d262114470e0ccbe9c26ff5cdf57291ff3c13e1291e922817ebfe0dea68c78b24121c87a0f9aacfd552d30baca45d85610e6a0414c42bea97b71eafb8798ede52344e2b75dbcec10218674ad57572b5eae6020070def7e20c0af8c615ce24ad67159ac6deadb2c54063f9e1b9b0561aa72a3b0e585e2dbcc7eb893f3c783a02830c088356b3ccd4eb32b0e352c972852f8b9690f8cac6c30e1f115f1d66d104811481d09ec6296b8e1e62365ea9033976c9d9d4c2a1602f276efcd2cc9663edeba1e9300c66085c46f248c7d5b416e4e271089d07fa6851965ba6e0fa44fb33544672c2c3d5a3ddf3929d8a750e7240886e113a5ea68260e0e50870b049954c8081eab08a4492eb84e9108d40e272e2cd9dad103e4f11c2983e53ba8a91cd2f7cf1ab875563998529c35dfc37c672a557392f3a32b8b095a4c4744eaa15edabf62257f02dcc7ce764d7a9a08de27254f7edf48713214bed6d956e4085ec3d6e3648859ce4b15ab19c16f37f8cae72072af8c0baf45b532ecbc195bcc3b2bbf077054b56958645dcfbadd8852ce27278e871671c42029b56d46ad0a2f9447fc9675fe7c33d50669cae7ac00419d472a4a5be1b25b275ef177f341b30ab0f9c724c794330b58135d8417b113a07f74ad3aba0c6f34636e46dbcda3128e20bebf3fc22ee69605b48fb7cd3e3dcadc4721b83a26bb7ec0869a6fd2b494e550c28502db5bfd0302a1933dc41e304c9eb296a8e20fe248e3e304bc0b7a098d811ea3ec627a5589240f72130b93709541227e3de120b48d521fc8e5be0fee611780492ba2c6fc7c93acb082f59949cdd8e2184a31062048c0c628546e658b6128a5442a00c148fb2bd8d0fb7c705b8c0bd72dada75ea7d6232e5ec23391c912ae182ce47762b96df3604bddf0a860b8314619bc42b10b6fe4069d5267273fac700b24c7a70c21c95ef1dca7b8f8aebb8bd72ea57fe2522e329b51578bb043f26ef58098947687ad2cdc49701bdbbfcf8d50f5894cb92dbfe5e0313b869735583cc669c50cc02ecbb641b24aec93b48ea495f27a5f40e3b324b03a3c264342fefdd24e352b76bd4beecf84146a6e490be7b69484c00357b526b52e5197a4132287866c0ca45cee6ebe81b6273c589d3fbda51ea2027d8bbced8b99744993967145a230f4d2cc5d98fe4683fdc9cf5380c6a59815d2094fbaac400f9828f3879390712adf13b68f9b8fd54765b2ab9c8f0ee7255e485bce80abadcc7e48e8d41ae16b55a17102c88da9aa0d18b7ae9e813f21b1a426c5a1f2e62b5789a427a23f2bee565b7b423505b363aff52771f3e405c425d8104cb9cd127c1eb47019d315d3b895ae74bea71c32a560ac45b891fee9c10a42d60bcc4e8f11317310cebc1ec480a9b7fa450473406966213eb63591d34729078b3e70989db932ad73af21490de76e558c63c0e7ea2bb153a7061f35e6872f9bfc8470abc9b181220cd31001a231f50e01b666da14e84f9a752a261c594728f64c8e2244155bbcc02c3a2681dd8390a137299e44040c4de294e7a25b77107ef21f020b62f58e4b414c354f1e500df9a21da51ecd7ab00aedaaf51a134c17139f52c75da6302202d8e3b6dc890e590149cf4fc58bbd29b17ab6c8484badd3a8b36efb4fdf357fef6fff90cf66f366fe56722c5c086447cd727bfeae68b0b7247b4f87c386b8752faac508bcd4bc486cb709d06d15030383c64973615c46672d2a86d73237110eb7e6ea5402fcedc0ebdaaeceb7795a5b9e65e06629c8a442364a00928cd0fa5dc6fd39f1b48df86a6c7300cc0c1cc9315361d7345d40eb57290b0e104ba9e9ca03c9fed792935d4149ac973f707eba16dc484ed297482dd725d101062e49f5d5b2a373c99c0228fa538999ba8398a2fb1f6b63ae5632b5672ebc32ccbfca8fd5a5319c5950ea0106a8d581d6ed020ef8c0a03997c4ed25f726d309ed0a14bb99f2cc5420d28a1bb3e5fbfc74ba898747d76dac44552e74a72fa6ee2a7a863f12f240319c118440ae51095c841537f45262b20b5c52f619e72c8f4d315d4e2b59b1ce2d6a956e4ea95d4b019fc79da5533b63216dc073c07724cf85608ef9c1acb802b9a92bbda7eb75387badbdfd1b5e5558f4c1717cc2229cc4b9e082ac0a114780014a0d3b911152e0c8bd7ea54726ae5b820207c45067250b4f9cf2ce19d572d49a5449a5faaffe2df90e26e09168c32338df80e196f72aae5e6d97570f9a7d4663e12632b8a23b48026d78c58c84a3c2cdb334b40a713a3a6538f6468d8bb948d5437f66ffbec521fad401701a7ae3a9be97835df8b069239a9e1cb9eeb65ac59843c609cc8d933a3177b6abe451a968b49ad6596e56993dfbaf6d6df763df01b0dee63521babad0f9583da606847ed60a82364dcb0723d1cab87f20bd346547a8b4533ee26d285865a0a78b43b23827d66fea66bbb5bf13d632891d580d83c90535c8d19a0be38ddd66aad09e1daabda431445ad6a3e955d026db6c372cb444868a359a890c714684746d0b4276240eec4de39e9da72adba9e3c24ec6585d2e4ae9612dfe7486b15d58a50d7e8c5eb48f6fec580845dca39d0b3f2157b02d8fc4596c364e9d8a55899c62c4e3eb78b70448c400f2a640cb9ba3ce9d197ad45a0fdc20617799b5da292da0c2d5d9cf33fb5c683b52872068b67e7174444aee239302164a8d91995e3e4ab929ef88eacfa561a01c6be72d576244c2d238ecc3e25d26cb7faa03fe0a1f6d62717c8aeff031875897f226db0854f7a3c8d73c439fa329c26ead375f5bbe2bc5bb09096ddd9b7c3c52dce486b76f7eaeb824dcbdf592f58ebf605ac24616bff7f420f713c9742491b7d3e728d5b29dcd1aef28ec1b423cecdc5083c8d74b09028764933c2cd3c814105a772e1f3fd7ae7365b869c0167c27872d272d8ce88d123d9c8168e0a8dad0996770f74e822c38ea073fdedaf54d7ac123557de67baaa6850f31053c1a4175936a372031d99ba43e8188d461b7220d56e77fc94b6fcc6a6c09df3e17eaad690b10c13e517be4d272849ed36e1679af1bbccee33a92bea3795e6ddfd035426e1ba2a4214c20e4eb4390e3410258310cda753ce62470ff3e05f2348fe7b61f15ba3036b18cab2cf29aa22c0326f2f8bbd6b10212df03aa5c53f4315aa7345a2d1a975727ae9e4d8bf50fa748cf069d0a46afdabde6040ea7b9e1a8428dabac2cac65c412bdf3038a0a84dc9e0677f217368231ed6d346e908bbed37a4ed5a88d5cfc331a30cfb8bf8a3cadd3f46486b89c451197eb3d205e849bbc0e879444ca8cdb11edf4bcc3f444457b255464ae9cf630f00cdceeb887168092164b5b61adb78ce72c099251ce4b87226209fb964509375b8b0c58d4a035fc15e425d541529b1fa27b1355b05d7d00e69741c2417f13a4590dce4160cc01035426abb57b22e651d25eb6ea5f20eb4e3aeabcdd48ef30ffbbe7fd56fc45445000bc616275032a88f72f276bec0725f086181f0759f0676cdb0c536436c21d42c4902d6736d75ec9c7244a781a724fc0f2e6b82d49f272001873919f79dbf83dd2299f4470eaa17427205da095338c53dda4e9c2f9fad02c5148859fca7a1ff4584b7b23c7c260fbf3ed7835ec416bd2699797f7a0c4d7997d31819fd199983932b102640df6530da2fccc412441208f3d918e4252a2566554f42521abaea1ff9342dd3bf80f8a1677264fdf1c92748439069fcec7018cd0846c148cb3eaf4df1159c1254fa05eec97234e25293914518356226772f0b3160d2a72c80aab5bff4af7337d596b84ff5028e3c7036b14eabbf004f2eba892dc46b27b2fb03420cc3558edaebf3d54f5172ec343b614a598892cc0af30e72e030afa362d22af715992c33f7a2fc23d4937272680a2d57c95d6353e61e0c3bf1fea2a24a480b1efd9f6bffc22bfde40d8a729c6fc59d7ff83defd36ee38f35b5bafc94456a6754831ebf51fb2fed2d2ad272a11e3c968bd44cab3714a7ec723003e10cf335a0235807266f89b5e8038b1e62cf8cc3ae2bfac5cfccee9b80bd8385cfc8314a152fc8caabcfb7e518b2d23e727844442911617007e487c6f7222c1ebd33df0f8c2a9304cb03ca72c857de3c72d413f58dcfdc18b9e61ffc076635cc6a58a8519fa9675cce6731e35d6cffe028d51d673cbfaa90abc72afd2590f9403ee40a17a321bed16e24a1fcd5c0a97472760710122d52c1c7ac54e0662f31d2365fa9ab69cfc79bd1836acede1f4a6972a2559de630edfefc7aedcf8315fd8ed548e1427367dc9a4df605426c4387f0722deee3eb39cbd233ac0f41b40406606bbdfa1f306fe71fd403a6064069967b2321f41ff7da9480800d4d698624a9fcd4e4a6a76e85ea1200a3e6b5f08fe8f3725b1d10576f34bb64f63664f90fa2a919c703ed6f66ce3afd970647042d59f1728fce2d6eb7bb2510c423db5d634b2ef86c1b1cdeb168c876704229335e912e65d91fca2732f341e97a8e00af0bda19aed63c797661bb599745f74aab74dd113b7a1db7fa6c22abf2dd9f495da4fe1fb73418ae550ca2761b03b3e333d8a86b72de30059bfcf4e6b49dac64517b7a8110577c7f836adf023f004eff1dc7ff2835aa8851a815faa04746956fa82e61de312ef8a2bcf7135cb8d090ed5c6c807672fe44ea3a6bb49c5b36f6434d99635252907c443f9329fcab085f0de21e57c7724df2a48002536fc8b6b735e55cd1044797556dde7e4e850bb29fe1f09c9018722918aeb5a421c2d53dbde2a7104b8a5938d4dcb665b9894dcb79477bed1563156aa73b313650208e999cf69c95131c6de8087d831325cace2cb292757db4694b2761dcb786a4dc8b80ef06676e8e6915aced25b5d6a55fcd46cecbe2a096b672b1890413f26cae2ab53dc2a15b056cd67804010926bc2e2a1c4e6ec99093460e85cdf1ada77e95c6ec958c71d50f8f5b5eab04145979a690245785f25bc02006170ad06ef45a3156b5da687fbbdbcf71cccd680bc7e46a4328e40d9c0b6da6186a16d34e96a1732ab4df2684c600e86d767bf8a690e8f4b48d6767630ff05d72550db6f4b80be7c3cd9e311a0d9c6d5b87aa5308a315c064459f3e60825aeb724f3abc473adaf5255f35b7c03b5be8cc7c38661d79d311d70abbab6e49bc677200e25f6ed3542371ca0273e873f14950fdca21c43f1a91d6b18f0ecd1e4d917275c8438f4211c07b3e2ebf0a97fbf385181d7bebe48e2271b4fcc56c6523f2003055c9f12ac16ba8e672050725c0b7a359ede60f00cb0c4f7d961c7773e6b67216390d7ced328675887832dde879b2649e5219dcc4572f22530411a401790c6bda749d8dae5b219091a24298990c1c86c7b59eddcd7f7ab70a98169823972c722dcb3a4564bebb55132e80678e139ddef74afda16a8a2e64de73321c669d0d6e72354c84a1d9bc319dfe952c896b604865a3fcec53dbc17c00d9ca9f8e16eb72bbcb5f3099a69ee4f4df49b2c886a5143bdd80bf492980c61d8c1938a5d8c60f9fde9c1330e56948d5e1ba2de8f42cd32b948f2ed7544a334c926c7e624ebd72d7574493b2d6342b86b4c6a0a9c64e87ab7f468c8406049cf5418c4d8a01867241c0d7ef4abf74ca50a51fc3be5938aba26977898abe5a47b912e826bb09fe72b3be2c1a399f72c3448846f22a6f2772bb2d186b56a653b0a332c6912c5cb7728bfcc16a5cd5fd67f47565439a596c49ca9fcf3f1d59a49192ace5c8f76c7c614fad209bbc1741df7d480df9d22696f063df9cfafd39f149c6004c363da20472c484ac4d495e14226c4b4d24010ce47f93e3ef4f39624cab31152bb47c010f724a75454168ae18937a7212f08f848ef6a23fdc6a28460a44a6a4a7f2f2c8b765c9c2777bf6664107c7e8400a23e517ee1ca2bcf336ce2ef23e5476bc70930731a62f04c95f2ad54b879f65244bef88d77740a4156398455d65dbca4630d33a72ef9e1ae0c899f7d43fbcfe38625728db611b32934cdfd7ab7a7729cbeecb8c727907f977706dfb86e703b3b5b9eb9032fc1de0ad52ac47e0ef652855e5ae1f7032f968ad6f079886b2e20bcfbd4001f2a7ab4018462d7c8b2459a0f4314cb172cda6e543afe2b1310d876480d32eab3bfbde17a5ec341a93520481e61124c972ccf39c624d6aac0a66c5912b22bebdbafa482ce17639dc4d75266dc91e6852725128a0260c8ed8e53d13c8978f1691cef4360013fee9369332e296574f40d700bef2721c8bf704db294f3c79eddc236cb131c8c2ea258a50467612148a6d0e72899138cec128d8b163eb3618080ba1864e176f5b00c2c88d19d0f42392f6530cf9dfd6185f461903e520ddab9b1a9af436d3d44550f9da9463a10ed5613a6505cb780c962fc4fdb8e88873dd5d7606b5d7a4f52c602dcc18f917d06fb88582723b9e928e909d09a07661d61be3a94c31a3b4d69ba8f93afdcdbbc9f670122672f25570f94faf8c66b7309d675b32f9d84801c07a6df0933b39a3ea0b476a4222fb67e20cd1595c2444bdf8381e18eacf4d46fb9f21d3afafd42db6d71a852d72e1d9d828c1afe4d1b90db3b95c13982b8bfe798ce6adc96b344af40a1f36a0725f1331374b7593ba5b76eff86f3237ee2e270836a4a52aa7e13bfec13f0b98721e850d04d1b49f242bc3da7b999450686c540b24a5f97dc8d674756cbb0827721512e7e5e35ac90afccf616f3cba148718f586e8db859452eb2a963ce37ec9728e3809e2b4103387e936e2bcb0c1dd87ec1f85ef758452600627a1cdf966b072b8dfc767af181e1862e7681d12d780395b64928ed5169f08d82729e189eaa672d542149ea220c657f5b17f997c02d28c2323a1f6809ff3bfcb583baf4b51f1721d32e68d63b8b243612ef6c174a4093f88f850409fb50003cc6b3e35eb23b3728a0a2d7d034faf4e76ed38cc0fa91ac78425b3c95eb386b98c5b47de8633a4722b9c6ee1f0c406c097fb516c11a4180a1538c9ba646081f466fdd313c4390f72d4c1d81d895fb5961bb00f2dde0e6e60ea3fff735e5447d94f745165e1cb1f56a0ed259278e91abf0230c16d6c0ccf6aaff66fe88034d4b1383bbb0bb95d8f723fed1f64817ab5f391af7e3db18838ef041cc98206237ec663b94dc01e1cb6722f1edd9881abb3ae934240826ad19b50d9fe8cce0be188cb9684d1c3faab1472ef077f7794c598f425d0bd188627f67392fed4daff950308d483231f466b8172f1b9c1ced895d56bafd049b28925479064f30b9d833a430fa33a9b8ed68a4937b0febb2367a2f9701557a4f7b180806f24bd969441dbfd2bc5fde027b125e072224ff4027ea9a7a854231c9842e1cee12235cdd5d213e0c4075fbea30b11637256d89d4c657d5b8f0462c886ea019f75896bf4a2e3a34aed8ebc14e1a0f89c72582a7ce46f3f725eed6a28e28e05ce805687c922dea3eb8f164062c70b225f72dd560a595ad68f461aa7ad85823aebd09ec6103a4c58887116255ee016c39272e2cd94fd35bc20690a8e73766d3eb53507eface77f7166017be097f9003aa772301e2d5de9c17196cc922a0d3bb702fa0ed3b6146cf07618ecb2b1ec9abab472db10a24277d6f685a58d69fc053f9c534a8977bf44487ca120e9b5a94bce654676a280d1dd9c0e6140699078cb16ee2756f36d82be1a3835eca7c13ee709b526f9174e965797347e5745b16a42042a09a2747efd2e3a4dcd7be1cac82f11e46c936529947e244f807708d51f79d0d1f1ec5130f22903f20d0d65c69bc6a15672ec199f276c0f8a9c9380c6ea629e0afeb43d3753e06be22fdabda731c68ea672a8304dbaf74dd85c3ef5464ef068e14ab617e24a34ec5088a340801e0743251d6abc004a59430bbda30cfd5a9d6a22488ac03ab708b2adf9d12e32312750477263207ec7e426a019b7b911441a5450363e75eed7bc78108289c582399b190d720fe8b9a7296823687c3ee5610b2303c4014c7d61214d6126431ae53e4f0cfa2654de4e1fe6fce1a946508ace5cbf3b3b81c6fceee492c4e503bcdab36b71db50e05cc1eb37f71938bc84ab27e19b60d26d47f0cad51913a9a6219f0894981572f81f9ba9bad00f9a7f88bbd74504697b8c00fa558e61d4d4ea796c31dc6cc472221765093ce8a109daaf15f1c5293d668e07e89900e3fef398df74074d36795fccd0e40b5753888ff002ac51b8238f9b4f22fdaecb4d2d39876523e4076da372607bab0321f520ece9d3da4e61ae171f3b5b0c14d87178cf8c8709e3e3bf37565a099bf5cbcc25049131d6834cb5f2fa5373f4094794dd99d14c6ee330ed2a72cfa0629b4ac993935e99c92c6ddd67dfbbd001c8caefe54ab2d0ab1fb88a7972a31e516d4271b13e0183e8315c3fbd189c19c8bae7febc61ec18abdbc5c7c929e683bbfbe95da48ffcc5723bc08f2172a2f1cb222b437aeeb10a7cbf5c0e87048906e1de0e0ec72ee18b37d1977e1155133f35ab082159df29c76139eacf09724f0267d9ceae1be1f5e9efd4efdde15e968e164821177ce1ed0632a970fcb772a15c2473f3eb18a03b8ae396226a3957d94f707ab595c9bd12d7a8f53a14364c8d8e71cff6011f771ec771725dfd03f7143b5c2628200eaa1d97a94a47af867249ad96d077b7f5417721a3eb1634b50911b4ee5259da2081462334493e898d589b9475e80d63888946b65865c6500df22814e9e50667a51ab2ba812e995cdf2b658d2f105f1e6158a4ef6dc66a5b6e41531ddb6a99d5395695c62baee784b0729826f91fe1d5695367581fa1da2f87f9209385431666984a5b4dd5fa86e6fd1da12728cc6e1d1103eafd09c07f3ea66aa72af31225af9aa6d1ab3a34eba97e6fab3a92cf1c8feb30fe96a6a068c7faa17514d766270e2205d674636559ffc6722ae01de7b4ec713770728d69d1ce7a5474b27934a3b2d9fe647cd6d7f2fd5b36f5076c0ff603fb3698849a96c912a551766642a253c974c78b42adaef54066720429ef160871368a995b70358dff5860bfa2e2c1ae57621a4623ac77d206124e8c58967b1879a17a3fba486eb13859c036309473eaf31afa874e15345088cd72e36d15493b57a7dcf1049a4d6dce7892aa6b3a02f4fa27422abbda4434c34d72439695aaa92bcce5e29121aebcb8afbee2a084b6fafcfb545227d214f2286135f134a83d03f66d31a8ead59d25665ca6148d807dbb77b690d66b2092a9d321721df427e604ab92eff9931965cf3f3a7c22e1ed44a7bcc49bfad53fa09fba23728c033b747154540bb876bb63f5458b268b5f9f72d32a9ac4463a015c3f0ef86b4e28d6e2d28feff989d647fecf258994aedb021803096c22efc32d15777973728ab1f125d12741286c37f3a80415a88e93f9d055c40cafe30ac627ebe021cd7297da67334cd6027d60572005e437007c6a0d5b8135803f055d7d9172348f797200c8bf7dfe9c3c1dbca433afa2626e35616fc1c3c37e4f87e5fa3f97fdafe67212cfb6795cc4ec2e9d79abcb7e23b60805eb48b65418b5aabdb2baba7bf2cf7251b3e1918ae4485e600beb7689ebc324cbad8d9fb462278584d36a08ff2b88728d0564c801fb54db9e498b1abfaf59667622103a3dbf8fa9c11c9a06c338297218fe4fd2d9f8cb3070bc58ba0b520a0153052eb884ba2a247107185bab7dea720f93d82b8f38f38b3626e36869ebbb403d4ab759f7268f374fc59e9ce0ebea729a2e03c296ecaa738b4fa394a41010ae9801aad498d6fa1b966c0cfd8d970272e0a95eb5d4e82bf4df7b29bd2c4934b1ab5b73054115b8d983f4ebf0a61f8f06a2ac3710e2d3b1936e976a212cc60c4d0504f44d145225650f96a0e1b53f0e19a4499c350073c3df1de0abd74492d2b43bd38489e51223938447cdde0209b072a1deea28e34e002238bdd27014f995e00d666a16493dab4c9ef98d4e977ce36c37a8a645ca3ee0515e1b41183333eb78065c796fd6da742895b2c4eeb4b9fd72c1bc8dc29b21750cd7bb84f9f4e0fb8dc524a132f6e98a6f0beb028f0a8ada2dc157ad0d9487ba0798ccb8df6dd0713d4ceae7ffa993ae38ab5f65b317798c723b15430040827e47459332ae7a042c4fe37472ce9350257395c8e3f83fa552728fe2c72267ec822b056cdeaf71a3b44d831b6465b9691ef6446f2c526dce1d0c60b38471e913f209ff20556893757d761e43096fa56a00e63961fbf28985fe72ae23785a720c5d867562a29df23b3869405739d6daab084eab3598fe799f8a724d10f8fb30f6fcd0b65c697933568433a8eba01026062657d5466f8169bb49720eec4bd031b6c3b4355a0f1e9784c37d862feafb2475e7d449e0a5d00c9d5472204b6a888fe04974c429d3d2b8acd62ddfd2e06b8353b36d688627960f4ba02f682126ee4fff204a37d2ed623e2c5c3559fdf4c3de61dcea6679143c395ed47279104ac70fc7d34c74f03d91538a3b5e5ac4493884c1d75a81f59a8904e86372227f570fa4b0bdfce91451e32fb7e2e2bc4bcd46c92b9ca8b1a3f63339d54f72c112b3bf14a91004b3cea2915821a0b51a8a847e1c79415d89c32f351388c472f54da8bec351ce7c4b8b696b0ad2ebcd55dd38eb32e5ffcc98c3a8a30fc3c91fd41f6a7b2f0c15e2e8b0f0a9d2993f085d7dbd9c53191718d67e45f61cc06172b60beac688f7120e0f70bf3c86dcd8f3e7a3568eaedca92dd6d9bf7a27b97a726fe9a9d9fc82c425b4224781633b77861ac1578f067931c1e2cc0e3a849e1f2393c4fabae3abbce4d0b860574500e22d3cb0266c5181e38ab722a71be5fa9e72255ef65c9ffa91e7f0b9debf8e3eb0bec97bb138c2fd91200c49b697ac77964b5e1aae81acbb3eb3537c9ffd3d5624028e6f36297a0c3f0fb8ddc10f87cc0c721e30e6a203c4fa2c18198e8dac1bf559b84506c0332d19d2a1edd2dbb4db107276e27ea6eb395fca052490d37bb9a83b8151846ebfc02fc1a21dedf0e7ed2972283adc4988676602e65376e7714080d10e2342078f510d5baf2cac2c71e96772c5aa27117a06561df7ff4ae9f2008c237a1a798fbf412211409f637a68097243f496481a55896b94d2d1b402c600939d2cd7467b4488858b42e793cf7172e051637f46125aaa7f86a7cd89d8f24ab40ac8e12cd03358b1be8a38253efa23b71e62c82ee515e231e5e9cbd891e2f38bbe646536c156f20196bb24f7093b4a00728aacbd99b780194be6e7b0469527c580df10475111e298f9eaa36ae83164996f522352c93726b1e9890f4f10dfd37b9f30ab9a09358540bf2fc436ed0eea1d0fa717cc9a67ddb4a5761dad21214c186ecaf823ea5da03d4a195b1c4e64df22721143fa5bd59363bf4bde955338c5df050e2d337ff82523baea6bebebf47c603ea96274be7c82a126099f7dc265eb66898705992fd10b9a56980bab16e798a03511131a91ac1158d1a7e9ccb651eefbd7350bfc291566ebaf19288b4fbc651749c433d36efb2c947358427946dfbfe88cfe5979488d167eac4c07357be92cbd3e3f453dccf44b0f61a4eb3f2848e09ce9a388983d45114a84263001f8e390176a0ed541842fd74bfdf8777afb22539d6ad3056eb2a525cf5bfa4073c1ef81ee43c282a04c7548121c524367e9d972723bb2344830f808ef2262a5aee452bef972d26835596d7e5b6df08693a5c5d62dd4fe6d92a150502b57a60b384e1add1f6458702f2a98be90f76c1a75d7d4a163c95d4f5a9803e66acc4a6f0b3fc878d97226447b8879ab9f4c5a781660c1db5ae9b5ee66fa5fc40f25d156026840ddfc3a6060271931bbb4730dc50975c7df8ecb8208910118abfdbc264e55501f1fe372676b7664c1517c8a6e4bb8e0559d7746a7684f64844091f534ab968f6385f472e21eb2a08e0afe62353a53aaab53348c06b02a563ac73fae06d12b414e8e17099b114a8fbb0201650bdbf38da2c7ebc5f6a63ab33456c9ec8179f15ddf07ed72bf12cad7f5b29f61c1cddafd021a1c80a92e54debedbf1d817eea5bd1ebf1072859ba696aa8f86ad4dcde98db89b9ab30af3c1e829924a494c41cb836d2a3772b1f2be2d66f46ae8ea8708683299d4cc4b7d11b96131cfbbcc1fb60e348e6472c57301dc960c4d0172b1b7c9893a73fa525641fb1309e1565c150f882f1c7d72df352378c69bd23062f1d8eb651d814f8931a71af7e01e6fa278507f3a5fb47176b3ab90a33b1a92bd0e8afa12ad9eaec2079bdcb765c2e49ac1529193fb82725aadc49fbd1936a1cf1bbf75be31de130dda6352e78ff2aeb81424b0364e2c72762668757f23425e1cf83707068fb9699c50bef623f1ca2a758efd4fae44dc7297092c403a099f08557c94937468b74ab78f12bdb7bdbfb339a2f8c0ecb9ed0c67448e93fc7ddea63f7d516a1eaa610dfc7f47cd45321611c2360b6c39189d727af52abd8e3e29da9abcfea2d5b6af73011ac7f3533e221846a7eff532da93091c6895db1cd4128927eed78c038f9b779943da5e3f5557424a510a34486a945fd84020eccee105e4ab52ae636fd3ad2f38e37c7a7a03a3ba26dad9b860d9f872dfe059088efc91214d4d558ef5d35608e7be13a0dddb2f946003a59ea627293b311ad0051f60ceb800d0e8f862bdd92cef5dbe11d6062893210c9e759c82c37227f29cdf449e3f7d847d494afa4f70eb9ca039918aa3fa56f49f78259d5c81720835bbe12a512c2022eaff497599d8e8f019f4f825e941a0d273b4dbfa1dc1723e096e362b99a82332f385d8d42eb096f8361c907d85cfb404b4693c2ba9272cb855c1ac0470f2c42dd47cd165db8a9ee99039e09fac47c38a583868d7fa3d723efe7ae0241e8eb0119f4c8445b613f6021bd6e7aeef967fbd59e895c09c77354246b56ac97aa532ad14393b90e544bbaef686f2acad8ccc9b24dd90fc99a97208cf83aa305e2dd60eab8fdba86e7d09c025eb21fa79a0b5141e5a6ca983897267784686836a07d50432fd530eb6b0a759c096311f4a3c3be4f7e199c4f1863db69fa0f53c23aefbd68486b7a8b72e4f598d0cd44bdf7aee3d8543c23ed1d7723f6313a7458ef6cb6b3af310f5c1fe33cca027ae20c97dc4e4b34e276237d04c1bd8faaabba879a07ef806303c6c6b882fae13d2a0bc3e1e27dac5b3f03cfe72ef9ad4e0263e2071c95a3eb5c19ea1784a093e2b6430f831dc24b4af189ecc72f07091a9956602bff4bd5f44f6b0cb22e9aa3b8388ef0924397ba6866b08d472040bc51e45ee975a7501294b030fd8e9683ff654d78ad10e2f408d40f839c95f3a0aa1a7613ebd87cb65fbba27181029dd1402d17a6d1de6a64411e089aec672402b9c8123ac91b803fa0ebf4faa1ae9ed03c31c6d4d0f68cf0897eaff49b01c90f5a6c4d888595cd0e556f049a93e0b73a9139add0109ef1ca32dd000c6ec51e890dc5ad6b99deea0a084c47f546d27f566eb3e51a13a1e97ac50070a5fbb72289b7f5aa3ff4f2afd4316f51c16df18b4c631fcf9f4006a24b4794f58f87772eb23682e62d4014e8c9a7c1d5f1e76c45ddc321fdcc2a97de73ba118db938c4e25b9f924d310aa07ac5208e9b3bf0ec945d505c6435f00af4e699480eb854d4c1e2da356f4f0812155097e2380d7e41ad09e930354a04d29364b7eb8b4f2e14d1f62bab2f429fb8289d3bc6494415893dbdb079d4f6612b2be4ba760a160b42d9918e528ff859ec051ddb874aaf8035bdbcada21466601622ac06a03ea4cc54fe30e267fc1e11f2b645f2da497cdd1c35566cb1fd40887900a2c0b2d32aefa72a995a09fd1e20b26d61c71439ed2e301b4e953cb8a6278923558a8b717642e00932554121e5770e3eecfa82be68a18a2c7e24c28683f25a1a743aee9ff93e9151ffdbf940acdf7902dcb845961c4849e789ba4e84a81436ad07191a9d407804594bc2dba50fd1829722e588b4f7d4943520851d733390f3625a04205a13f9e448677c5d78ee2fead8aa3de4161f7cc60d6bb0e6aca8bca2f2e2315bfc567a6721bf9eae6ff44ad1cd2c9d840a721e23ebf1c3a26303c2c8a3a50b0848c2ccd7261ea504dba79b59c2b0feba0854619903b9a769afc6ad4d35ed2d2fcccfbff26db1ba11ffa88296661176638d67f3f3228ebfe2a98a667595a20dbe6284aee2d0778b30245bf16ab6d12c06215a28691b9e32258a7e877f2d5542ed3cdcd29469f682d52072b50e9e215ff403c2cb5007485a4cc6cd62b4a25d472540ee4ad0063be4d956eb5267bc1ae434a404b8b9f089b9ee92a7e72f53c58ba9f9e14437263a46b73308146b0e4ed8a8358802f61ac33ed9ea428be845a3732fb3d1367714c0c9320f279ee3e152effe4eeaa9587bc499e895212c85210d6fb7b2180df72a7bd6905805a25ffdab6ced8891eccb3a19e6d9a197cafcf6224761dffbc9272552a882d6290acbe7b7406380e3b27e07bf388c4b5263c97cff7af1ad56414722a6a7b8142d42be734b6b96dd9255e340a376a0210559a090786687be2cec639f0126428d3da26cc4c070dce23c2107957c0e007faeabe0cca69c5038dcc4552509aa414cbff0a8628a4ef935f4d30274525ddfb7553066435c4510e573a3772c071b57c6ce3232afaf9a55c8b6680a933a6a7d2a1ea2144b4d44db68bca0a56d44e26d5baee893cfe5750f5c7112ed7cbdf44cebd1f7c4ace654aa9753b091e15ee45c61e286766c68102e89a84b83d5e0ee09daa973f35228717d6c352fb72f8a22296239298307b1263d36811509f4a267db7b3a6017d57fbb381c5f49351112e8b20fa6ade29d399f99f7be19b579875f0b559633a305140c6bed1057e727e098b82fe36690055617f4ed6704fb6195e869c2c88d90b635c0cf9445c0d6eb73acc7047cba016baf2adb1315b9d57491a327f1aa3fa983685bc877d1dc772e95f1621a3725484eca8d504915c70545f06cb5a044a6748f86dfb84aafe8172802ef20513a37589cac774a864e7895de2a58ddb44f2b9f383a2d9ff9a56d5724f0aee87be72a7747230fa6a1515e04423b836eb56e545844c63ef7e700d7426c4b6118ade14410cff1d370b836a30427bc8105bfccd117b74399b19bab11d529ca95614abb9ea1442317e1ffca58c0d1af604f3f5475bb792b9b813198bec00458e38b85feb64fa26dd252db838bf89f21128ff525867c8f76f8a16f615ea7279616e47bb395351fcf7ec78c322124fa12a22d395bc0f5a40657c6f9ee2be2665b1bb9ec96f3e5d27c4ceae48d3f76f60c60219981d29ff15fed52e17b69472d0d8585a0d6ac58606910da56ebff2f8452bca19ab965369da5409954fba19433296ccff42daa630d1cabfd7f39131e86eb0388b70933e89da7b0646a20819720de550d058d8693361ab48b243dc6ebe866992fee672ed86c0bb7b9f92cf5f720dabe4377e7ac9760230299bc49136e56f7de8aa2b61632466f2cce188c99b72fa8d39e0b450e9e0523b7bff3b7e680ff6dc6ad92f43a283e21fb08aa5385257cc8c5155e83a594c405e6faa4e68d6e6dcad2dcb2403ef25da55a75d59445d723a37ef074ede4592a8297a23ddb68e2cc74ed1c5708d3c359ec7a80dfac7a872149c63736f397c87237204e56f44bb07e2f210891e29fda2abd649519c402e597cfb5fc74f235d991c429bc9a8f8d6d90379e8da86b9b08901e2bccc5a3257217ff2f2f3bb06bcfeb0ca949c1c801e84168901f404f5e5511bfb70b57a56a163f08cdad5eb94ea2131c1f6e03e5d2e0ef5b7cb6bcb6eb138ca83fe74b075c2727b72c3fa8807fca85cec4dcaa0dd7e90d12f2da196dde10552562eed85492752217280c6a20907a1e6ea2e2c99ef4c3da1e077200eb775754e082d90d9f7ff0252c9530a3853e33e53f4bd52400119e39d477bbad5f8020b949bd010f0cc7b722642842df4b3c5ef640040b9c50223d71b549caf8b5049a915aebd3bfa93ef15cf80e65d975438834f92a0029b3487722142c0c9b86ac410b879a1524ac07f39921e1a3f014f2ae518ab4c182916dc0fe1d73e3c506e2b642f5de8f30ce7a1720bc6a3d6b39d78d6b2f9d7ff74a0f748edfa881016c07f6477a54de3bea6e234c0db2fbbd69f9a17ccab29ada7b9aa53e45dc1dda79d325c58e01e61e9f10d729ada8459c7b0b45e538cb48fe8a9970d52d449746262e5add7708cb6df46eb72a378f0fa956d162eb8b55014dc825c98f36678dde85938122cee731f1b4446723fed89530059ab26e3d34405cccbcb900fc9b49965dc6f395096d817281edc600bdc2c02e5d3672cc019021b5d1936d0907141e97ac05dfe3250ce243f20d635fde9ba9e12e8163b0a7a4119660f36b7b67c6b9d80ddf7cf6fb0da2e48668351e6c6559835b1002b04b6fc26250defb16cc4620f1618b08f7115df9e0a325d722c74d1af5fba7cebaf1cb72d3203b5307d76135b1a4724353048eedab9590672f9ca74e2c5e24edd95bfbf2c60fd0afdd77c7f1d0487e734034b5c428f05a7723620486a662445c32d4f5780fdeb78a4fc15a1c876e0cb2f80451e2909e719722d317bcad6399ef72cc392e2c5a3ada7910248d12bb457234a804b45dc912072d1b76e579f7c835dbcc5653bb6be85bf87266a0ecb9c4f60a848da17e1f3d8568f1d71e6da6d009dd8ae84c6e8af738d67d77e55675bc745a4ff54f08acd7b720acabfe7fe5a21a15bffe95f22f5bba3034ffd50b785836eb5fefd48736045729bcee7ef243af97982ccccf644608809a4115ecb60bb2e25f43ce6869c82927223551ecacab3dc62509f27fe0c1165c0f81855982464f2f0cd16018232016559e6e4247cb4709727e248199ffe55da5eed883c830b1f3e701056caaad8804603a31a859def47672832253745402059844039a82102e50d0022e7ad43211fb972bf4de4093397788e3e11883ae80c9bb3c67900a501ecd9eacb59e7f2716d507289ffdbbb5b25c6b2c0090129a6e1f448a7154b1e1d6f5a55b488c0cf44d0b072c9ba9e6f72dfea40556dc5a7d013e491b77462e29803379a78971101613bd645588dbfc5f4f2e7738f0746f8c8a210fa5630d05532a0c7f5ce258e818dbe493e53ecde17f91b0a3a33c35d75ff17d4db5c20905f1589f147362bccd0eba464671affc841709936793757c72f046c8b924b380db6d2db8eec0acefc3bde403e5d5c6e9645d3a079010f39a7f99c7e77152c42aa8d01b60ed0579b248a1e9a9f5d40c9c1955de82d56a1fd79088305eb21e04df5ea7ed8a2aed5982f4d5a44da7231ecc4c1bb928da12aff0c76bf20820c5704c223b5e519593e8651289abaa93a71fe916f00e302dd992f1828d9b0dc6542fe5f5312355852db7d4e4542a3201976f10df5a2e6ad636f52f773adfe4d61dbc2995071cb614c9fcdb913f63a38417696a6cc8fcf1288ae33d1f6f950cf4d549da5fad7ef711c99f80faf11e64f72beb4e6c8445aa97389696f288736284fd8adb1b9264079ceca4cb147d9617171dd08d74b0b8163e3bc77a1cb69d25665408495b72d589b12f00db44228e6a372e82dbed1a4ca1e31ef129f5a7146e84140d751453ce575a92872c54a053ad666ea520ba3dd62189c43b36cd25754a9ac5161400faef34dd0e2f8b46f5230ff72bfe07474d0e3337a9d3e5828e25d52d19d7e644f30fad345666b39e7925d8e72999fc3d01731e9347f43812d3373cc7ad113c9c1710c7525f123e864063981720a5e6944eff1bd34ce1d6fbb423cc308115d00a6cc49dfcf82598a0066247031bb310115caad6c43e7811e60b8494b9fe143ee4c6396df0b85da9d39a48fa64b838245dada573c5ddd059f22e0d01511b84d1ea52f12d4681b57fc6095dba7726fb9f07094a62cf95cd92d654d5e1f72e1dace707d9254f0fca35954e6393c7213dc141c1c4708169143c2e765e8e64332d24e52850953eb89470e9cc60d337283351c7432c59374cc01176c7fecf7e92dae198e2e7790a69bc3ffc9a2c63f525411deea3814341d2731626d435c8974c41d0195a14799ff9d4e27d2a0bfcc52e53682aab1087ced17d2e04adea03eec1636e4a9832576b12c68123b1a4a6c128fb37de08ab15e17733770fff2b9b3048bad92c1f03f85e33e226951623d9772840c534a24268518c00e5b46af20d822e6ca9e13cd6842e735435c33b4ce677254d6cfe70f0d1a599b8357efabae7db252067205e6ff30d1bf458fdbe67567723077ddef74677d476f6edf8c76800b2b2ece0e60ee8db1ff1bf9dee06b9179727a5cd24d3fda1bd65725029a43812a332cf6e3617ba42caacae65018990d84720df5dbd4b8cbaf6b7b13a9d415983ccf8e16064b467cc2c927a6aaf5ff68de09b6a5d278dec2d08b8b159401cc4742e349202c3eceb58dd626e1b22be722a9729ba24a9b60ee79c798707d0141ef7965272fe4aeb89c7388631310c36ccde872e58a9f0c7d5c86593d962a00504203f29520501e56767e65a2aaad1a66d1466cc91d909c5feca59eab92a67608884c2d63eeb6da80e17f0932f66aafde956e72db9648821f78af0212bcf2e400edd8eddb6ffff8436c1c7b392e2381a387c3086bfa8f1008606d8bb74cfe8c7babe4abd583b03bc252e4529b466dc18a7ac072d7acffc4c01c84e0982cfa23eae87464f489f2b0893990bbb617cded16c88872af236ee53fe292f4515ac33f5911ca75ad25204486779d08e77619f94a46c672d29a7bd28bff2e97d5db969520564195120089bd5707c0d91e3789250a696b72482257f2a94a2aad5a2d4358b7a3e8953959c9da2567fdf69343ed710bb0fd2052e11d0ac0d4dc609e3fe69f81b5f587e367a951043be4534f74420fe7f3867243bb423bae11c659cd682384628bd44685a4b44da14861261e9696f8b7b039726d0560a916316343323a2d162c70b265d705d6f3fe37a86cad40927b6c144172a1f170cf78612ccc290495b9b92853694419e5e5a96cee8124fab13d44a6257298eb5318e0c423ad80238c4a689f956b82af5ae8af4d30c1dea80ab5c3db2468bc0158ebb1918950eb29d26d06277a643281f1f7222d719616d46517d343ee1c09e3e2038f5d490eccabf1846c4b75c02292aa5de7c4b5c6a915b0bf921c0b72d1e947131997c48c13784a86a542a91361cdc09ba2afaaac093211aea927b427e41acff8e451f398394921eab97031c98f32973eb6c04becc7dd2ab964a1ca24329894d43661d4ca2624260da87314dd6aa12b046091b96a918eab22935b25722d0a2b17c5a3c3c80f2b5e4f07957cfe05454ca7d5250cc2e2f98368bdd77a72179d435c569f6a1f943408e6a0c68908c0818c9230138354cd16bbc2bdc107012b9b57508e15e9cb0806ee53d9137fa43b19853d30d99be4abfe664a6b43ba36dc3a40dc8235d05ddd2de778e26b566addae911f36a71548fc8cdd2d031c637293f79a24f4a179cf3a387889d9176769f04cd7020303022669530c89fad0960717575430cf649f2f60cf24cdea2b1ce62b2700fd2bb5e98553012522e6c8321181ed74ba8174211205a0ac8dbc9bfdd3d86a08b8d74aac1659f1917ef14a314a47adebed2e2ada60187128eb0b4fd2d7d7195390c4a0557b8a651f0704974e0e0242a26f447a2fdc92e7b22e47a48e538f8f5ac33d9abe26ae59170695163127c521b7914bdeb7f6367eab7b2c6bb38acb3bb2496706be18d04ccab19b368e228f8dcea135f305f57e930feccad96b9aa11b2ada0f63136580897eea8911571d7b47be787eb56c601d3e9e903680a04c601be57ad2733e0b1cd5d0d48e45d30d1f9b25589e61e3d3cce94757a6b8ccb5b558b299758e2e7a0b6c35829325d52813dc2f28dd9b6625488f2db6762852c0ee4c8575467a1d97970a057fae91892c77513eded32ce40df9fe62a7cef9d7114747f47e6ec3a6a8dcdebf828bf198722743430ead1a296329f97635da622b309c77de1a83a241645e1a2c72b388837214ecc544c50efefc1fc0e281ee1ca0befb636d88d75daebd04fb4d10bb71be72a408335fc05a6c3156737dd086d3cba4dbdae4a3b8616a539da3050f0f721e589e71ebe95d748604d499e8a4c20b7dbcde0209fefd4a9dd78faf08e46e33e072c581df2c21aee219d65befb51d365396a1a31ca27dd00a48ce7cb6788bdacb720959217c356306a279514d2ace83897878800e1a80e759c24742ef9740ffe7728a101a79f194e88298c1c7395ea3c946a3458750fd23ad40c7d1fc630f5c7c4949a1cfe108c3f386b14c5d4893b97d03f0aaf7e758b36c8dae8473fadbcbc37255491d47b000975b04a2e1ec123a74f0656cc3e7722520e85c4dc0ad82c5663dcb597f0696e5fc2ceca703919919ffe877b4957b5e68c33ccf6bcf5df534c4728fd3871fa72793bc42399f7d725e5cfcb0a659eb98be57d1f8a78a97e8e1b40a33c808b4cdab7d3ff5ce1e5e9dc1958552e093b6db96942034e488624803af7250d49264a980421d7b32c326825b3a871a6c9094d829808867a98c602fe492308e6934a4e0d4565f856070ecdf9e553a74ae5eb0444fcabe1f6ff53372fd1772ae334af6acdaf43a79cb428fbf56c05f8d33f37902a5171b9570969c16de8072fad74418f17bdf4fb98c686f71a56fb488875a77ec115553aecc3a00da4935725d7e3f294d76b4507cb5b1fa01cbf14304f0cbcf95e074e8971f93758f35f0721666bcaa76b9eb35c595c5b17e697f8333b640b550ccb05f512def2d25b155307f9a41157e07bb92d3eab7d762d5efe04e7449f6c7b525d1b7d08b62d6f19d724fda79b495dbf0416d5ad814e4373446cec13960c302ba368c2711f7f6aa17280eb9f6f56307f1709b792efdfc34272952edd96f41f4c4405da641788056d372926067f5e8601396341b5aab46732750148bbac42bcb6f2d1027b8de758d4072bc32a6ea8cd512e11a6adfcdb7165168324736b2a2ce5c9a28d7154da3690172f83d37dcf14ab9f4700b40d125761ace216ca04fabd5d7bd0e77cb0bb68e4372dc2357a8a7b8685698a241047ccdb19808b49ed11e230ecb003937fb61c4a20e4a8b0139c9ff8d57d000fb987aec8821e0d9740a6139c7108e9ef50b7879c172d2a3ff42c85cf9ed371b0c51e29e33a9d5293402b50ff1ac3a5260562bee6d72fa62b93c50add30e25fd3866c60f307af948353e24557e7498f420ea95581a7230b398b082b1cda626eae8f2ae0fd7dfae492a96f267f471e249e0f9f10a752462fe5702e81e3e824dc207779948a7e4ae8bd158d8bb9f8e907aa23685d362001a1b635371c1025452d41f1a314c76438776d309cff7305e031c7440ff016d72def63dbaa4260224cac972741442b88eff9c15c4e84b72928c4254470b9f00726d5c18d1ac7356405fb0c9ba2f098367b8d965d509c8debab26ba180550b224536ee3d5745096f84252feea09c19148a5a0c85ce29aff056f094bff50b53fd72928a96328fc589d6318200c6504e9f531441a43637c4b0abeaa43ef676bad24886d897518ac5c5aecd9f572b1aea2dab5f6b60deba442c5091bc40f682f01435eae13323b4d56111b38295915957c160990c9a808835e9661732114eed89527283cfafdd7099203f3fbd819c4e50de5ee1517d9308eeb45667069e6c948335726bde92b0dc5f66cc12b923bf8c3f9126c0bb50abd9179be083883fccdb74cf7214f47e0dc06e73fb04fd158ea7459996aa34963c2a3d84dbe8feb63d464d7772025f92ef0a44eb8638e5bf6fe03448cdec3b85a1e4705f39c3505a5877dfb943362a214dbff625725baf6c6e7d2b2fa128c86e97f3195c96b2c5e113680b3306a8ab1648b04f749a2dc3db698aa005734605f1723acd8b77567fab0bfcfa94723bc308725a3c7db809f25cbb1707363475a32cea5e739c83e7586e115da8f105c12a8d0f2c90e6ae8456705bfc533781f4191798805a638d95148854604a8472bfbe2e01a07a76b72495a7cff4ff5e6a0fc35c8b208f95d239c5764026dee872c5819ca480e87f8eae57865f7c311fe7ad11fbae0ad96bd674da602ba01df90936b2c9f525633b30be0d2b3f5c0770fca5e2aff7415066f24e7f1750921084033d8fa91323d24110c1e10e283f307a44b8cf6ba01c18b0df973262f078eda472b34fd14fe0540b386e22adc54bddc1c1c25d14833a703110fce77694f060e73a976c42b96b49da0445d462edddd0cb5bf5c4a4a0c929ef7987dbd317e2a0d2323f93aa04559781bdaf2f6f67fc66a321394fda90dd3059f3a6ec5882b01bef67c7a31e34d7fb5f66d65f3c61fd1740476b99aa37477113153815e13e8f39e072b2d38465033c68ceb2efa006525d7ccbd7111dd9978355fbc485c029f8211a71b6c0c6e2a1696a735fa08b2644e7b02e4a37bea5d97330666abd090b5d2a263fc85e48fbf6ed5ec1889318e5dd77b9db290f3315f52776c8b20b14dbc2edb772a289d1115b64daf774fd15f8a45eb92b43a7d3011ced52bc2966cbdf4e7e287227ff034143b755f036a4b9c00eee0943243451f1832add69ec96a155478d524575aadedac4dfddeba93f69eb9f2a4d49fc182f6b1acbb4a53e904f739a98a41ad15f35e1f72ee0308a8dcc63fdfbde87450184fdd850cff417035a9481cbc17215870a4945e2352bbdb9a9a4856d7034ecb3b8047dc1911f91de1d3be63dfa7256ea61147d296783a97a7d9ac8f2921e90d23ed644ae989a4a930045c0cfb272d8011b283798b9d135c0a152009251f4ae68f3be67c07208acf2d1866cc4772149d6b1f4654b62bdd15a70d38b89b9da0aff1bded60d689519fdde72a1c06505fc89dda6415d97d7e6741b2b939fa02b6c3abfed82fc9bca19afee59235b80725da92d255d226f9f71f6bf299361d7941f6b20badc17558e69407d3ea3f9877243d593c95c8b7ccc15ba77342f70d25b22f7ecdf5672cbd23bf13d31bf1424720b60ef1c6c73adeaed17fa1e90629a1d72339b8bb9d4d125a5e8d21ee030cc2f57b817674bb9da32aad2efab357adaf678367ca00b815cdbb3865fa507645f6e70f72d4362a7b72ece9a61048b561172ccdfb875b79869d59897a20c7af35472e2f108535db8b089f99793d0aa56d4ff242a6000a5b723912273dfb7f6c527727ba4c94d55a118e65a7a790ba3f4c929f782e06f9ffd68ce427116d31473dc72cacc169ff1a455e71e8a0703465d5f641327fbd20de9e88ef9dc330285bd671a6be2d37abac19b4222030d65b2dd1e54ee94ceacd057bb32044110aa9bc5bb31d9c0478370eae80e99f965d7158b4390ba05b21c737f3399678ea940b5a98e18c708e14f5da453c4721ef777176f3d1f4fa8f0152d4aec0e50700a6d7bbaf05c6e2fff8a0bf2a4af40cf0755903f75869aed745c64174e695d6bad10cb1597077f4758c70f9e7103b6706873db093d67e1ea8467dc30e473c60300675fad5f314f9cb346ac7f00b29a5983f99bd5b2dbe83d410ebc56751573ef90491ab87d729f9773a08b718e1a5398f2235415ab57911d4dc70ace81c30eecea5007f3d40fa027e7e0b502bc79112902fef77d9109133a9b28aa9d89568dc5c20f0e4319458b3df6a5382e3b79de365fc5f1cd2676222e83576fd0659803524da4bf7c7d72154ec79f26ff316519c35c78de134595ef4223f488e1c5f3d885a7ee2154a572d3eec59cc1efdd4259a911446b1a390379695d8e3dee3863f75d9abedf3e3872448ea99e12dad2add3be5dcc06a80bcba21ec1bdb23f47de9ce0a808cc6bfa71883717b2b5aabe82aea6aae5e2df28b761149b466b111598dc5f41a068f73172f9a9e657700fc5c39d116a9c57e3ca6b5e70c6424cd1c2e3d3bd30cf9a6b3b72088b0429cd0e2b9bae919787ebe76f14bd17f1c74c846d018ab24ba70ae73d60155782a1962632f9e58c0680568e57ef0fc83e8b89503b34d43a0bda04d2377290d53fb8e1c2bb2413b3b5767b2d6489887548bf547ea1968ab6ada50d9ced72978d8addaabd2aeec7e1f47e5a4930fd526781070f866cf8ebe88a0b26b796725b5c6b0971fa2f9d98bf4fb57f50a63af5bab5466f274bbd6a03d68df0d8ce382682e179ffbcfc8d2b70d7641428a4e962b6866af809966bde3395bf8f69d9725ac30260a3604d474c5b5509776f6a2017f2350cf6ad3a730ebb16bd6178bc5a374a41d152bf5e19e57b793c90c99d21fcef23fcaf9f9796740b3082ade7865b5334b0d179f636c419aefb13f685b19e1a3a9ab6c8c86c5b33fe1231ce32cd7218ee6269e98bbf514dca5026a8acc814f86127d78d38e9591f7f1aa52513f672304482f0083e94b10612833a87153a70d2769662a8d125166253240c2012a172ae6a0571e577474bcd11947c1fe3256dcca884ffdd71cca925d478717f652c724bc09c9a0fb5d7ae1b095dea9cf705d8fad44abd38b6a59a096ea6f32ed1d42422d8e247d89f4c3caaaed6121a270e56ac33b6373a1ca91a5de5934c8e00885a4295677decc914fd5c5a6b51dc51ce3d4ea91bbbcbf564701aba85dba5ebee725199c3980196705801d985d541b28d64b79ecd87b82b2604f320793c151753722c7307fdd2455ee1f3d9aedfb677c22431516dd93079d454c258f970f14b4d10f72d5bcede03b35c25ba86e2c30615e14adc735e8c56ede5adaede71f4e80b72ba06b3bf63a256f909d28bc2c9c26df1aae9d6c78284f84399d5dfce70bac072f558899ad4cfebba081d618d195ce54696203821fee283db75d4ad67b05146549e296a60f1e75afbb97dd2e40b25827a8692e8c234d5250745e6f833cd98be7263fed1436f6f0f2f4d93fa2281ca7a947cb5e7882148f58902211080c171937266657d1e2d299caed8c4c6ad92ca12856e676cb6554a57c9eade9a64ef213d7283d6ef46f3a16a3ab7ccee4d70fa66e3e94db831086ad9edcebd264d3146a0721d1e66e4278492c61c34533c937693b907a97d356b6783819b12a08d93f8f572e507175667f79664c51f26802f1689a14a8625978b00cdc1351770a3d340c872a41520e6c347aefeae0f2ee305a40efd43d02399b79014d4c795398d6baf5972794908d71ca6d8588e564d0bb49968daa6037ab941338293a7d10f721ae2f87240c89c80add35c40f4bee4bc4d34bfab3ace6267998037b98dcbdecdbd15287267f6bd156e9d795b28ed009ac83dd2333253610b1dfbe8d501841c81c782ff726cd576dee35a21cd260d46202ebfb96640adf8645127cc4c0f99f08ec1c95a723582789f2c8a07f1db94201ba25ecbd1f777a7ed617d78a03cb76a29765812409256f8a3b5d12feca9a0dca78f50aaac783cbcc9c32cfce05758165b4b233d722532782d19b9884b09eb5f79256e8275c1d1dbceda155730309c3d19e792d3724701bf416b29f17ae5948731c280367b652e7949b355d7419ad84c60e3c8c1724cf4677a5d6b5545943aa99883b68a93f6eded263cd180714c7d81a82764ff727972d7bb818165189439ab11e6ca59408f350e205763d5ed6245d99b24cfc564e83de1d38c962e60b6f6544b87fa1f4d2ef377739220799bb461139d4ed2357260a9fb0e98d14e5e42c5dda71f7a8f7a5cf785920f408f23d99adafdf2bd2613b6075404f3466d7280c1befcc9ff16f549141e6c29b1683c4717fd688ae3797222b9f66432df6e8ac1626a4cfff4bd6373de7b5fce1579881e6081b5004601609d38062efc4763a762e3393a25e4a536f41b6c92cb96fc23de67ffe945fad114177109969efcabd8b69d9e8310953b0197c09c48e6c8d455ef9b6f161c61bc2550e94225ff6e0a4a7c03eae4a73d7b51a10f6f2bef976eeb8c01810e5c434702bfe5a8f9b78ee881a64416cadca94708bdad0fcaeddf950a3f8ec548ac59c672bfc7416ca4911feabd9ce2e182b1a67b27ab1bb1e6924dbd35db776b85632d6b273c0f07672d54c7668961e0d3f7841cc9b26278e531d028eff08e51f3a05a146b07039531b72d4867aabd68ad628376722074e2f829d32e1fcc64c91cbb0816aa15fa58ffafbc86824a949d54c9d24076157922c8a1c764a7f0e7d038482f72a41ad59ae300c2b5617e743da3c7e861432bee53075d7bbb46127ba457e976728388d0f26c863d2b3a48f8f6d003e518e22aeed61ba5329af09a8907f3ad4372e67c25fe97f375bd278bd611feb01e437fb6c500ac499da50208b9d6b662dd72cb04c0119c4eeb4447a8fb270efdd3449fc8f0971c2bac1d735f1028d6d7e772c915a8285ec90029e7658eec327b365e87c35a906705489bdf3965e5f207b672f821d0ad1145023d1bfa01587e7e54918b1c301c5e43ef752c4820e03ada1028f59e61f3b8737b54bc090c077e9dda25f7bbdfcfb0b0716954c35a27dc7b9f7205d8b626cb9a4db1865f5423e6ed2c1eb57503377811942bec54931583ece272f98d926384cc34f79e867bdef2184a31af90e9ffc60943f838aa38b17a93217268b4426f45a23d9a75a63a1f77dd3fe48bd5de5658a81bae7988f4d071bb3872a18f1a44240e39e4eb099629987e39d501734c73d49f98e1db25451cc3ff20722cb9a33a891800953de617cdf66e550f2e2a5631d2b5c0c1930e72c05d29b972373a8ba7fecc4f3843bf5c425b7895051490f21a4b8faf08bc1e52cf4233a66dbba861d2ffa09a02756b4ebebfbaaade773aa19f3e3358cb1df14c0a27009672e49d24d08b0a06f5588b1bec75393d0db7b1943319353416640d9618ca93c6726c23aa7988b157d9f5ed5de370f522cc511fbc571f1e0511136c2eb5135f9472961450717235af33f55bd2442a005f14d364cd5b1bcbbd496ade9f66f8f75072ba355a424dd9b1fb68ea9f6a5e02edd91fb05e549c99fe3be2c40033499ceb726622b9444b7ea71d9b05e5afd39b24f465880df6794d644f6c3ff83579ea5404a666b3e5b5545bd2052d10022d30b1195cb5ebeca9405f4c75d3a48352c22755e5a17154fa22362f2d62d7370052a1c4d32bc3c39125550d0eac099937994b47ecc494555785f3cbcdac8a5ce53d6b83fb367cc5a01cafdbc4bcf41f44f352237e43b4013ce5cc46c5e651436fb852c040a3e28a44c9dfbde9902ac1dabc747299e5f376b4a27e818a312278eb341131c28e9ec8a3451f73ba06a5058d3486729abf388f518c40a9b2baba39aba65dc27b5210b97d04bc9715798a7675dd8a7271372e812f126dc24301a66db8fd9254a57a6101af96322c04d159cd573d8c72c489a390b3b5199ab931c0e0ae35e7da8a95625a803fabcfd6ea885dc0f2ef57786bc55a8041c150f3077f8906700a5cfc8ae0e5023d5c36286370e5f5f2b772e257a466856482d2a6a2fcb48cdd9fa44f3606a2c1e084bf71b10bc005ef1805ee2c0bec166559af025496a5c94804a3ee67ad24e9b446a4fa92b1cde3b851720e101c5239d74ecf810802eda787ca70f39bf2e71b7271d6ef8a812d4a82d972e5d6de8babb5d9bd48ab154bf4419ca5991749ace884924d6c031f2a919fda1e21dffb1d397c6396cd5575423038852ecfcb98656a4c7cc14e4873a695baff72fbc30eb7de33ae4ae1e52ce69667a467d2e7eb9d3bb723e9521b7860e3a65f728b1f6f6c1843fe4a12bac87eb2b77a75d93f230d980974719d086a54bed52d72ef106892c9ff953517d9d8b6b9a88f917229a5c7c1c15e20b9ff6dafc246c372c2b251c8d2742d11a1900161d87ebb2e4ff73e1d4760f7fa171420f8733cd60cef2949462b0d373f09d043c326a852b8cdfa70e901edbf0e0439726e1c51e66c5d259d4ef0340f1043a3ed274ca28c6addaf927e902e13727cd117be432ba52eb240ae1d2f120137cf1bf66cb876736d52de11f515bd281f601024a908aad872650189b931c95eac056c8a494dce9855ac8c23b392250bdfe42904d80f8cf20f0e4e96e61413b312afb7f1a4274212d1a7ce7c2b7daee994dcd7ec2e16eac372686311b1e59cc8db9f69ad7e55a7756fb15d36ca128c0b9aca87f5447cb3967219cd95a3cf9ede03994ce4f82487e38b0a1cd56d6c0f1bfbe0f3462fd43fce72f81ed2692310d00b8e2f0b6bf1f64161e6322955c5825698ec287bfbd9daa57260b54b50ad05ab361c37c2a85e25b4dea92e9119518c1acc3af79c2c269087617da924b54948037dc9fa201dd000a48937b6ee8115b1acb8ef429f0dfa2c7d7283542c993c2a3cdf0a4ea83fc36e4661f9a3c2b30d282dae8f168223646aa834570efec805359ae43701ea72b4bd74f273fae6cb79916f24234e4685dd1d59725f7d64523d9cc99e008837337913f723882d0aeba91d16f996ac09eb00cbff7264d9b4b68c775041c86a018f80159c4cc949020ecc356a727598613f4a28fe259c1c43a9746c865236134ae537fbcb5ac32871f480cb2b99f3fe2946bc43d117e508f05cdb1b27e5e3a7b00ff3267fa3494295a0c4836fb248a4ca8dd446b917aa60f32efc4c3b48560fca1ab2b8948ff4c4ddf887bb13e166e54e46500886638b358f7bf55d2271456e2583e6c3548f07aea48ceda8606cf8115f1c034b2b7245e1a5321e4d26f1580910d8e20caad5b506aaca60e98b21fde33603e78383723e93d512e9e992dd3ab699a13588d21edc09b3f2ea974dfc552622a08bf83b1895c1f504a0c9ce29214126d2074c204dc1ed94346dea68cbcfb45a24a9bf7972bbbbcaa6253e60f57ef0a6bc570cb8cd81fba3b2df05ffbf6f1c4159875d7749568bdad822943e3112464598b70138abb035419e917f989aa78e5e0bc74f73726f9094a73dea0dc16ccbc2b09fe9839ae232ab447377e5c19b4484ed8625301d4c05512d793cabd0e430902450acaaf2c4abe14f849fe0fb531f8317721bac72a252521767ab0e046e4e137a17d73340c2571bbaa0163f4fc7eb644859bf8072de8db833bcfcd94431238697309162e43eb4f8846f9278b9cdbff1291c5cb272ab9c998e799ff603aed079ba6f6511e1930a6e3404e50ac29be7999a4895243d74352af1cf73901a6a8d7755284a9790a7928e9725676278d5dc27b432e067729e7c7b6d1d8db92a57b5749e55d89e3e88c11df29bd98ec4c648b52966efdd724b25346d5297940ef0699d89cc6fd7ac9927fe9dfb2cdf25a78f69bc28361c72c25daed0207af4debf101a7c774401e6b7a46c9ca5192b18e4521e6018ad00727e34c142e684966e4e18b7141b628f93a4bbc44e0a31a402d924a0790c40a23c5b13bc45fce2e6c3b43ca7f2d3eba9237c22b8c8caf934e87cd7f1c5d1d65f17bbcb9d7e8c39a5abf92cd1b41c79f1b3b7739ebd3fded9e35e390c3d41b8081fb9b724b0c7d8db9a266f05ddb3374471b4ad4e83b35f391cf6b716ecbeec5172748e64d88c80c54741d0491f8dfec86d8477cc9335b2ddab2731117ad29d7f721d6f604ae4e82b4bd8f7020c8fa4b6a74d49470ed57004424ec902d3abc6910593fcf30a31441af9c0f7997fee31243ebb66c26aa2e105865088456596bc2a72ca3de5625cab4643a38a9cd1edd74b0c412c892e94b8177d9973fc596812d372faeda4f4deef5cb37fa5a1babecdb18e27a663882b4bb0db9975668a876ec037beed0c354abbf8dab2eb2b22809ed67d3a7a5be3d705f233bbc5d2245071ac72abb25b9fc855efab72c0ac3e5affd3139693a5d599c3b7d57f271d0b2cd2af72f0d5c505a9d30614344ca53b20aa476152520713f551554618b277b68f748172502e42cc3b0bb22d74df21e6c25c5c2435c4bf74bd45e5a7d443914aa6bb49348049cabe37a68a31395fec9917f5a2e5604bb0b8beffc06e93e2e4e2ee85b63219efba505daa526355da5b19222abb78c164f050b48d25037bdfd1e52d1449720795f9a95192731f40c5fa2369ec7087851200c176b832a5f9feef1e81ef200525fad30dc228a0cd034d2700f749f18908949b17eae4d8fba99c785c240f1c7280c0f902333e8ca4356cf411896d5596f689f85047d0783a4c0cfe87e64ac17291b82b28870808a85bb209584caf675152bf23a4a7db8a06c3b6b779cf0d1d720694b318b77882ff31c97fd1d365d645524424f85f7b94f7e17610ecfd456672e1b1c18ba361139e79b3a9b2a6bf37ea5b2923af264ffe185b4396158ed4a57228dcd5fcc072a4b4f34c6409e4662fdd878466b2f26f396fa287de17afbd3172137413087d1280b04906b9a876a9e586ea3905b5c75fc2421ce4a97de13dfc2301558e570e238243591a753c350dd90e4cabe6cab979c2659d76f0223cc5e91b6fd6b05e45ace490ef794508eae17cdf4a23cef32a7852dbd9091f4ef11e6c72b4c63bb622baa9b4ebe45ec3f005cdb044a4860c6934a4cb41b83a761458427299dfb179b89531c05fc8763843906cdf673f4be05f29f247ceb2869a02cead72cbbbec4c56545ecd327453b5183cc8fcdc91c218d1111623eaf00b92896258606a680669484e75264146646f201d5db1d984d965d33f57190e44aa03f5fcef6ee4c51b296fb690650461214fa537e68e89ec78c9bc2b6feee6af2047191f03725b4f0ae35c3e333ae664193e3062f0413815e7952df8f7296c3bd30dd5073d72907ed133cb0dfbff23403b4cea16221db550a01192f0ecd31f48934d537dc6720e9072c5b10a39b8bc4feb288b8251815c11adaa2f872839e874944279b5035ca4b5a6c8e53662211654f5e3270faba154e4cd18fdd085a8d8796822cd10c572030bf105e8c0db0bd90baf6d960174bcea25f624972e72e585e3cee2da72e67279ec3b58d179a2be6cd0818bc434fde78ec9d63c90c70313e37f70fa0962291262865f5703f559576bdbfc7d4c04f4e4f511b2edf3e0a603683b2db43088e90dfb7eb70bf057f22d8f1daa6e055b4d3a6f773d7f55abb0f9761e0ede0ec9be72af3f3e72a72dcd5394c13ac0223e6bdeda8193817cd29947adfd4dc2e1a6034b2670bd24ba8595929a99d37eaddb2b08ff1cf08110349096d0b6cf7d664e9272d3cceee45775d704caf2e31af20626ce93000da9f8aa06493a1999db78f2b4633be4e3f1eba02482af07794f1ea9b89d6c828c8a66f2dc51a53daabcb402667245616408b2758d0004acbd741603daa4225b28a199f4cd87f091aee2ce45b272bb2cb72fe695b2e3bbc3eb359cc4826ef57a55c3ac42011c66db012db6588d72801ed492f674d65d4f52dd87fb91ed0970af3940c1db276dc420bf33e0d4f11dbd6fc58f341b5d722533093e5d8a20f310978c420b11044d9bc0cb634fe42c72c1a8b87141ba16d663860245667c51eefc6fd8f5451af958fa73227c027885727524a2b6027e944d097b85b3fce8f57dca7b92b795de7ea490cd559a03420e3d4869c0d36c5770f832877e8351b75e3ecf8aab3b4c7564ad37d04765b2b7fc72ed2bda3034912121005c9252b40f6159d377af9230e2fb41b928be91a186ba72f0aed9591d3e06cae52423ccbd2fe1d3de64a709e4341117744a4523e58351651fb295c59a4f8afc2cd5c92df8dd362f939445dd4e64151aeffb53f67b2b44724483b9124376f20b44c687c18dd9163b97e0418e16b776e5c99a2a2036873b32c88e787d1a72817bb51445b8c5602e996306b1ea4f35f874555f2aca9810bf191f78e03aa9d92fca7f5fe45c2179bf8ba374725e0c018d374f2b64717db9cb729f6c2e51d984b2dc5c328f481b760920a960e7c57bfd0c113587077edbd6787278c9ead3645d8b131f1899e9b8f5c3406e92776e971b56609cff2aee7f87ec5603343a39d45f2707acc8b3e9866d7edcf976c6405db901e64f1137d61d379d72b2a4e24cc42293e6390b0e450fc0416f002f3abb3cb5f3984275e648707d5b72c4b507c1fe7676edd862920cb284ceef5e1361cbb120ba0795002544fa651372625b5cd3ea1c32d874e903261439478b54b0fc54cae085ee342ffa46d5630c7221725e1d7aaaa2f0b1027fd0164275f24a43e326853b5fb27c0ddb7d5ca27c726734b5b9a92efd01ba5382d893e4596d43121c86a16f639ba7a3d71f9ac8407290c42a98f3ffb843498f977691ce7367b1b03c221722a809f8d71b4dfba76d53f8f0294684245267c1963df9b909a74052697e3b286bbc6087e95cf8a76cc77201f3fc22eb6eb3ceb2d7a0526f1a2ba2d0ddaa817170dd1d3f1e744fd2949d72442407dab15a6881eb79dd7a48cb9e607d7a62a3844575b0d37e00de4fb0142df10455a05c8deb9e710cddb8fb6fae21d772c4e953ab7f8a28085cc28d007572cc231f8a479e8e72367f7debb868ca77f269e1e591a7b2163940d5050e539f45774e4efb7fa774051283c89990202b46ab3afae75cb15cf3e06edd7d989e5572c132d44f44fe3cb2b9ef5f432e1b810f5b6ec086f97897d9f2bdadfe2233ba72f36274b23db24650f902a71ead942f93651dfd749ad6641130473da1c618b426f07c91c8927bb01dea130939339f823511d4276d681e9ed62d5aef8490d87072a67810a79898e111453e543a6372ce93e9084e0b1f03fc49a04fb2088c9b196a987c4ef9c0eb398d59b7f5282770e464820346877a97a8bb3db929f54f25c96ce44f5c8212fad1eef3e81418bfff558675830b26d86f5697e81dc72a24662472e20dfbd017940088425f1e1510612e25fa343fb04485eec0c55100c1eef2d872071b0f3b0b358ed484795633b9b93aaef4b1a83be4534054f3fa66903347f52da188c92087785bea734dcbb63f4f4f43e2c8bfde9001ef8952215a90a4c06c61cb7e54faa05404377f73aa48b4a9b3d72af974c00ce8a9d22f626e683d7383720c73f758ceffd0abdee0d238c7ef5b0e4e2b46acc75d5ce25b739b46c71d5b72a277fc2944cfe0d861608589f71ea0daccee8e86324aad1b9b224838160ca872a47483ad61d2eeec8c348bceca8997e85b56bebf078f2803142b91f40fbb2919544bcbf6ffdff6da8604236bd34d5e5e00476ac3217feb0b0f47cf9858390e0e6fd3ff70b4b7d11d52c7312ab48cb3518938e7340aa4ee2aaa6dd4a9209a417244a3101b14cc7ad5946e5769b870bc70cb9b2b7503b72c5246b379dd0836ab725ac11fb53637a8bc6841314dfd52dec160b8b9f31d74b3e922a5243d4b38510d43baba32edd96d0dcabd0788051d7896d3e4a0acccc45ad88da469ce60734772cbeac2da07a4b90b468dfeccfeee09569e34baef8d90b574761561a3e08ef034e608fd5e6d66a8f2cf8506ebc9916a8c81bb9f1c04d2b69cfd067b2c0d7a49725df65ce73cf8cab56f6e69cabd64d28de907f23a05a2f58738bac615bd270972a3d3c908f5e971d419c4f9ba3364340b6425a0e452051c6a89912641a8fed16bea67a055a85eb4b90d6fc4a00b7073b246a58d5d5d3080ec0b371d29d0f06d0a3a593c7c894ca7b72f5ea01977538e1eced44a611df4db87214e376050614472cb689b07d8bd734bfd53f877fd84371a9865fff9700b6604530904dbae7ecc638f4e95cd59285b4ae81bab356c65c560d20476bb664002f137a3043076ca281a5e0f2e3fa9c69d7387ebbadc9503aeb9b46e40c75d015b8ac42bdc385c2ef81a8e14cf6a5c7131241a326e14523c75655442e60ce0e55e3508829102e6f19f72971ad75a39d45cb418c8b0f02c99c899f3698b7588c05f38bf4d30ed39240172ce97f99b4ca99912c68683d6d1247c7396f2a6f9f38699677a5d5324b517ce08ab5c1d6939703855b447f894b220c23326a5eeac7b32d92f32a65fd9bfa586721043b77fc7e11a333ceddc943bba2178f70ec496fcdac63338108b9efd7d82727a8533e4edbeb0a15fab8ad2f1ec51364c026790acff595a9e5957c3b17d9b729a7b2b18e55de1fcc3a603a81483dcd49ec51a9ab3144d02d8bcf931d5b34572193cb366f340e177a6591a0abb0c398a434a265162e97d971a2b7574e2239572289bd45a31cdc12958bf0c9666c5a7ab5ef62e3c00859193cb06c03866d8446e4ee597ee1e848299fb61dae12d6cb7531275031c08fa00dd7309670d3d99a24c3bbf7481ef0ffa4ffbfd8eb39a6f34b5c4031a3594f47d5ea3556eabd4f3d246addbf5ed9a3049e31053ce2c35df7cc4e854107c91403930f7ddd605e70d986682da5e8c540fb15c2ef076858f5791bdce49aea371a7b04921f1681304ac05724d0da7584042064ab502fbe3810d03b290573d7cab4d73c3e74cacda1b3b70723cbd7f98908227defa3656aeb14a353a6a5ef559114c2d9025fd67f4821afd12cfa2aa205ca8da80384bfdad05bec15001767d844a1827acd7ca27becbd9c15aeb278de7e6234619febb00b9a560614dc83106d1c7653c03a28c0a5eda85b21f3cf7085e915f55c637c2515717f01b78d9fc034f08135d9e1c0381ca35575f03e2a1080bdac58527b5aceec0a9907d4c7f95d9e877a8c730fe26a0608ad367729af305baa79dddf98f94ccdad39e182c1eff08b71cb51d3d682c6d4ead9b603e092333d803043056cd2de2bbbca803d4b899e96c9e88bd2b3f96adcaa176cb18de5839079d71246c74cf187f9bd3061c5e8f6587f23b9a2da66a44e157d8ae45d0ead6e742d7bc323798a48d020c71f651f5f7452ba829a374d33bd512b8337296ae5768aaf0c8282432852b4f94daa2ef6175ddc9390789649a7da48aa2f353b9b2c8804916b55ddc8570994f046c1fd8f024fa0ffbaa7bce95870603b6994bba992a2868cb8935b56c63a6941e03ae90f0570c70ff92df9f1c3e59595d5a5c01b7ec9de3fd947898721a436682abb5002e8cca03fc0c656056078b492bfd3ccadca00a1df7c45cbfbec360fc370fa56c485c53ff9ca64b92ff01a3b2b8ef1a7e063049541e4da725dee65f45eb2205a8436633aa333a14761a0620035f2372960bb789e5e6d2250b33ecd149f8f9638e54955db41c72506458b7b3a83265728a5afd7677c94b25c981d4767e1aed9e685819eef2d075dc15b90e8973908f726058cb3005f1abb7242e68dfc113dc75f56ec97a5cb679ef37b7bcbcbe7dcb72b548178787a42a7a01e4053431afb0fd0c456bc4f27d213cc6f84c4a2f11fc72be524fd19e1b8a01c0b96c383d41e0334c0b20222e9c3089d317a6fe944b326a5fa6b95bdf4b35be8a1a65651916bf0ae52dc9d824587bbf61c421398c7e4d047fb4fffffef107476716a330472b64eff677ec6a16f0bc0363f2b532f8797b72b65c7396366bfbfe54f9ad31eabcb4e94a8ebb022db388b7e43a66f0bf733968b7d780da3dcadb7cfc259dc4b9ab261f1086256249d202c1099a5ea24b25c972fc7ed02382e50bb4aa2ddd119e06041907d170299f50f25c2056e9bffea27a45bb7f802bc5f9c779632da5bab55a2ec91fe21ab6f33b530b7ad66bc5304eb0721ab7b66d3a8e69ca487422f4f63d4e87d371e3dbce0e92725126c2a465d2fa726983cde09b9191d2d4323a3b6842e3184f0ed4030949d4834feae00a6c039372ddfe72de476127fb4b8dcd7203edb5791864b3de79bbe16dec56b60d18a2d2726b429dc2988c1c90ad6388f6e354e8f49afdd5d75ee9a6f3fc9e246db1c81412efe46f4d131b025fccc72c56ef1f58ab85507f54e008e496283cf9239ed19611f4fb8c7577f348a0a65b131ff065c964ff57729303b6958b6719d6664a76ff72e88fdb114f1affea1629361b6a2f645111e4329fc6685910934aaedf5053d15c845f50ab14965e55691e541a32ac2d5b3f199286b83ddd5bc530a1e49305ee722b12a5b1254d12e6d7daa5eb0c1b7620e331069e8737e32adddc0b4962cc354201144f0dce17ea4a79aab294240d3980ab095994e79043388f6bae783bf14968821f0a4698209339fd121c02fb62c8796cf3070f71399055b5cbfc6ef0330c72119763fdda67684af53e9d0bc6acd27d38e5d7ce8a74c952cb1e094cfabe33171f3d157569ada35ceed8465390d52c24fa41c82a89ff8be5942337d508fb673387f704d1ef8524ddb0dfa82f771de312d7691ea2009e69e43b0d9e0d54c1c61755efcaf0e13ef018a0af6657609830850ba23bba2fe2e9bd92d140e1596ed46bc71895a85b93b885f4f6ada44e793d15231cb9b1d9ec2a9e8c147865c5d13a5df12ec3e1c2f44de860c4ee077b8aa6ffc300f23ce6ea2c8119c047f5721c5b72631ada3a8fc50826e1b18a9b7b1a7a6f79bca556414fb05d153ad868dc8a921b3589adc13157c8a43f9461080c0d4988d9ffcb192da0d074cb761a5b069272728a530854d314d5ddd64a0cbd33633c18c4206c0de59da7847fa4c04c9b76cf5b4588a3fa91b7cb8bd9ab73fe5f19c9b23b7acce7d1faf66937807f51ebe8bd728403946ee2060b261d21cb14dbbbcc3eddb3be78f5a9b0b753346bc054acf7426c2e456811f93f6981ab8d57fec83f5e1ba117c884f76082420c28f4f7848d72468536a6dc9463e14080b600e7ca759beaa2cf06f959168abf4ee5d30dc65c108c2f67e6f717556b8aed7455a0884ce3068a26bcbde24fa5566c3d0b4b492964a1c2e22aaa322bee4c0474d5c646f0ba0ed0933cb3560037df7e6630e7b915722b4df4b9473d586ad17c1d504e49a8aa190f675c4f43cede3932bf43a55fac70dd00f797b9b58a381af4852f0be53de36c8c351f44cfe9d6d3140817c25abb4dd423eb1d01710792f8b495dff6f7e11301b2cc80c8c2f6d25260b250f352af721c241cb14aae59eea7f847e29414948e8a2aa85a95d1083383f02ca0603a4238de2df0455c1e092562c302464c3796fc4d80cee459d593ee10bbd04914b0f37240c997cdc17c5224b2475cc0ac96d57302d7deb98d63dec091bfd6d29677f772352e6613b0273723cf195a0b0703ac13abc9c990ddf2d55e8779a17dbae488407e8a129383cc960d4b4d24acc1798d68ca8aff854866504d5021a40ccbc31a7245143984348b853ec5ce99a6daba9102d2e1736fc305ffae13fade443041947247c0621c8a9eaf3fc8168d619c80692ec3603b272c726987899e6b08dea66d725cca7f6640bc5443d70a7c319124ea95e7a320636e02373027d03cb1afbb5472acaade124c419b7f9f08af95c1ecb4ba8d5a0a466ad7cf6685e355d8580bdf728703ac8e80615953bb7827dcda65b062d5838fd0c04a6cf07702fc78c9f35c72449596ff3a4518a624c975e4dddfe1ed2bbefb80302f63b78c96682cbcd3ff7241c75bbbebcae40327c3e61bb405e2dba15d0cef139a895727f27257ee77267274aeeb8b989c18b66de7f1c13798480e03ce132687f124c7b380e2bb476afb72744e53890804757065dad57380c939919f86c45390eb2e954318d91d9ed48234033933501ca87bf98c54270f8d0e704abcf465dd4c3064b1689b24f22439c96139eb88312bd585fa9a25d7e6dfb1af6e4d31060d500b7bbf2b91c22b08a85f54369a838f20deb746950ca6737025443dfe315ff8b9f17cc29b0ce76cded5e51d2e78a0732d93cea7daad1afcfeb4723888d6ecdaa89627c57a8968f40ca2ee725030800acc4140ab7c2ea20b3db27748339c3d205b80b0c8dae7c7df74d679727dbca6fdb7d0c6a910cec868c2c607934ae870a095c3cbff5e96052a642efc720c4a567373d3f2e8b0948399f685dc1da44902501606941245a3956e4521ff48ca4a25102ec882a1b7b605fd79514e0fb117e5fdcacb07de71f56788ee59ca329f89f64788888b3c0fcabc0b861649ddcc2d5d275e1f7e1fcc3dbe5357b3b029b381a2a4c7496456e801b01c7bf1253f1cfd59d5c87273cc2514c8ace1b55c4e300c37cf5f6818797955424aec2deb4801ca0c524892d4661b1b9d8a73b4cc72d65e12acede664b5c5e328961d1768920d4ab50e7b7ad19d4b77ac440b5d4272f32f4246228a4ca889b25fd4ea24b048f995da5d92f6ce708d56e4f97f001672166defe684e426a4a44ade7a7d3a838b4ca7ab18b270b16ddb11d623f9e44745f796412de951c048d0571d9c5fe67902eb29513ce0096b6a74ac812c51c2ee04916b3214dd1b65452be37afe52bcea83560238202eb530f408c5ebf9bf5e6432e7b189cfd1dc9d9e5f6ddd38a5f50b90e0e3972aad9502520b4aa2fc0cc5cd67902c0c7ccf9f39803f23b38ead09d423166e0069d88d5636bbe14ea52ee84e72b90b22dd8b3460cd7622992090762ad662ad858ae6d1ec8e72e568015c697d727dc3e277354243769fd2bb91fd882e73ae2a2eb939d47e0d78468ae30cc90e06e0ebcdaef5937f39024de9da5e8cfd5561edeaaf82b20d9901f081dc85ce5b7283faef88c9f6bb2b10fc4091f07ba9a137922f648666a0a5c9b157ba6559fd7287b40fbafdcca2a32c01e3860ccc4c39b575945fbb7a1c731905fa9addbe773a27efc780bf0dbec2fd3a3372ad1bd02fdeff7288f127ae4462e9187362f40c72e087b64d867acf1cc421cdcdea5aff61e859b7a896bc4d017df55d9e66e55b72dab14664ff0d998013f31f8b38535c3738e421c38d4d9093c9daba4e1b6dd802bbda505183e111913d42b3c8a4d07abb18384859ec3e52e1100f8aa2e2358d72dec3f8fa5d318a4245107b782877554c6344fbcd19c490a3e07184d9a2480772bc2a7233af062f3fc7816e0147542e11544f390681d4865502521959b572df0fa8646df285c91bc665fbff8e84a2973f901f4f47c0c7897b724cb6e756fec3722662d61977eaa8bf8cce2955c72fe69d677c616fce736452a6b68e985dba07729fad3e1e5d9208c3ae17a82f1dfd607c468ec005336a399734724b48ca5be5657b57a05b20cfeeca641ea20c5249c65b7c7f8a57b68810d7846f24489ca5071911f2a14ed8ca56cee444c2123256d8b9c2535a86d2451ec798485960da5fdb726ee0797edf3a7dbba5fa594e6017092cff7ec9c9d3f8d49ea8b8eb353249ef72a0abb3a69b127e45f88ec65c0037506343af4b9ee93c341dc8f3ae253c0189724a9fb1bfa1251b3b3199584d07ee223d3e28c2e4823b0daa555bdc6ad6328f14b82365edf229c7445bb09b44f3a3ef12c3751fc55b5f2ad7408ab094cbb4f736e07b42b065e9f13e7b9d8d0bf5132d65d26660b434a25213c6e10a4cd21a2a3b9ebb17c3c932505ada3c330c8ee1b8adb02ef5621e8c093cf91f844977df4272b55f6365e4ebc258f1c3d592c3340ff0053289ff7439b2370340da51c3ae955a91e81753e203c5186bf284a002530e48c95d54b6a4d338e9a20af6074f39cb363ef203c011727b686a34d5b6218d15f2fb3ff535472d56d714b35611377396724c3e5316f6d4c0783ca4b1f3524741542876908bbf6901fc1bc4876b74d66272c0688a857c2e4a88388ef423364a4691ea7044f41604ed8eb0fddc0d786b3472de58d4d1fd59604fe549990ec7dae2e48649c9386cb4817810017698fb9eec729baee88fe398ee856a9f03bdda9e30f047bf1c2466edfc4ad9cfec1ff36066725629de54868742032cfe44d8d13e4dd00492479826f2d361ccba2a690e0db5724e660a03cae09dea7747537ba8cda6781de8d96c2c73c648aecc5e6704d07b00e076ac3969558211b89d7ec69bd9ff5e1ea1d4fc18925ff4468ccb2177ef04210ea067947f9d0882d1bc0d62b9326a1a4681361ecbfc2f5209910da11f74083fbb06c465a8e44f4bcf1fda637d6018408b71b77407a216b0c2feb56489cceb36f50a8ffd9c6441c3a177d1c9ecb398438cb45f5bd7c51ca9f029a2a306d99f724a44ef92e7d9335f74b72150261c63cbd9f8e0331a620a10ef73780494fdf67211badcacba63f3b8e79c3721c177473a825bb6ce04d6e864e6e95f746175e2699bb875ca0fb4a379d9ddee1565583bb824610115087860f6e6bfc4d6f2532f7219a18aef6ac5db214d47820e45c93fa71d5cd314331f1fe71f0609fecf58d072a721f1c7e1a6b7d1a06c44dbb1051027d3f388a8047d54be303f8724bcadd92ac377f7a3220c26b37b891cca676f41bce9eca31d9c52dff5d21a0acee3c9cf0bcef8c18759c004dc5693dfe0450b1a525792ea22d9747be6c1dfe6322c1a48729ec96eaecd095eb5de9e97f38a7c63d80015f225cacf40465671842c6afec7726df591fc6a761b54b8ba2e4b7ea619d57eaf0f5d0257a6627588e08097313c725467606236cac6fd6bffb7cc7c4536461b0b609f70958d265e4f55ea6f2a502e3da94325ef3f5d7ae78fd009de949731881933349204c7ab1f3ce5535013b335a58cf797249173182f2084b05c97a006ce56319120ead9830447f53593ea037242c5f3fed48fab7fb2dec82a0acfec57a5c738cfa6434e3e7908c6d2bd7d4a72f512a30a4df307dbd0ba665a3fb27b70233dad367b33529f11a6e6741aec48723e04d324ed7845eeba9615f84760b4011fd9cedfc8eae197b8fafce0c1758872f5bf5f537f2ff92d10e719e36134f81e35af4381fa36770d467038ffb560a672f0c93206616b644e0d0510441154c1fcd9c97fc32f5336ea1b920a4ce189187207a9cd162d63eef11e49a398174f0f7a6dbaa247a6c2134b976ec3a19d1a36725273035868646d1c74592306f993a67fb6f0c3e226db253cf60554740ad37272c66bf2a0936ea40ebff6fcdd7de94a2ec002c6157a34611aa6803ec6fdecd95a1b4965c4cb258c583bc0b510b91e2cb2e724b561102d97b6268640f97f8ce5610bbc46cd76245ff8ffb82434794cf4cde06414073e57f9d9d2f219d5ff7a6572d2f557f8fa64baecde8087130b49e521ed33498c866839fd0b91fab333bacc14452aaea7e2771992e53922dc21b7d4bd2eceaaa2544cf2cdb831feaf41395a0db95ce37d7f13a80b24166146e2eb2ec01cb29889f874b925644bc93c1dce47724fd3cf5053450710f29d195dd275f8d4787bd8c5440cc798a13ed3f075af8772a1d8c268261c4807953fc80e1af4a6aeae2a478c78bc396693d4871a69dc8272ed696fbcfb563fe9101298d0cdd31f30ef2a12d46a7680f53c29c5c7f37172725d41fa5d1ad10a8f39bf5bd4f5315125f286779afa64142e57a6981a69013f41ae9c90bdaff4ad463457fe76e46b6dafdf19520747317310e4153bbd18b4322978971b24e1ee02658afb9726ee19663961b864772e3e41cebc52c9f3e7f073724f58908f1de887829949ae808a585cc08319b356e56e4bb8a4f42bc4cf6e8868f9b617796d5da6b6428d429cef59915b032eb32b6b42454e2f1af1dfcc0ea572597e10a4efb7516c1ea9a25fb39d378cfbb120dee55bfdd21b89a2e9e5e886724e2825b0cf5355a0d7f172bfbcebc1e7d804f6bafe9feeb67d8f275bc301d725293bf27d724c4adeda14245680d3db82b8a9e7ce24ba1c51f0eb8f30a8a64972e086c85164a0c0d1432577e4467a6faa1aef67f5f49dc7c48655db4ff38fbd72f7c8aa3260ad5506d01ad56f3e3a000a7d0e8c0fa43ddf2e2ac3762ff3d65072d9c472c318bdf232a7f4788f1d090f8a9f5e4cf52656711fb3b4558057cc98726de138e1b7847a6e6f55f394f337925915adf88823a80f11ff45f128f37b064a3705f7f191b50c8101f7c8e8a1bf10b7fa1277cc314d812b646b49bd7533ed7260072b8da1a84ed78f7b29923039b072fa2e8892ed7ea1b82b9c0cbf5c0567464bee742ff5ac0e91336dab9192677c2ee0e93ffedb2cbaa33fb96935e170ca72345b056803087086ef1cdd9e9d1b2e64442aeb2673a198b0223a7b7d30804172b4d4d64f77e2dc16d70f476e05df52dd6a461cbca0f8e021d8a262752f827636425be3898e02fb89b2709e0d4223991961fea68ad967d17470a35752cf6601722b9230684c3b8f1fd935a8b79b0c23f2dd764e2b1a9786edd1022ea9fbddd04e991464018ff16c26e831d404cbe2694156ffcce8a60d5c8e6bf028f1a26f4f46d5083976d9dc4d6d59c774916a21aa05df35282b0dca4da374ae967faddc2372553ff38166286aab3877e637af6d55f07def80720c25ab765de3ef31f7df076250c54e47f1aba004d802feda684616f92f327115c1da5a1fede8279d994686729be474cd9a0cffb203a39c6a2779fb03cffdb0f8022cf5bec4492d5aedb4fd2bb195272b0a4b7307d75d77d36f54f9dea1c7e1b2024b1dc442d402be5f523b7221342c2257e602e1b1d14e19bb51664f12cea98e6ab6de83f833387b4e38d5729fb423a2966fcbd00adb0b4eb92d355aff841bbb3bf4f0809b4e054408b1e76cb9dc57a66884e7f5f1da8efe5757f3b91bc6a5f75d1a5fc47a6aa29156b3ae7264849a72e9e5df1ad897391144e20882fd33ca4ff5a2be4eb06e1c7ff45252720f4f10ab77e26ff1fc73def80f0ceb9d7c58a4918b84d2707395fcc0f26ee77295ffe18b8248166cd412b2958d015c3645bc35350d70e08cdd4ff12e9775b731c9a922565aa689ab93efafddd1eb4ad05904aa9c6675891f3e2680271985e820e75ad12cc210f32d1a1dcd6debecb65e25a4c6e6cd221774f707564257886f722f6694de8c2521d7ade6a77f0f6fd2609e7009efcef96b2bfebbcae6b638ef7255fe78e151452fdeec6cfc1de2efd634a5cc28d1b0b96f90455a9c51e162c429f1909f4ac44c179679332c83aa00ed900cb2374b24339eda59d9b1073e53d94b597cc44ccf1c4a28f38b32e409858c66d8fe1057a7280d51a028b568618a3c72bdc696d15ac2f73938fec744734ae37f8b97ff32d8deea1e5f75d589d895f90ebe578b26f2beffc2116e67fd6a00f88e6308c3bed4df9df06da067d5d67d2c562e8509992e6cebf672568db73949b8e8c14ec22d9083c69f91d2301babc7fe72d48b082ae1db4f341f4c70ce91ca50dac819188d3d1f2892d1c9ebe4f1cb2064c33ead616cc3d3e222b145c437541e830575c6d719dc24a54232d9a84e966f3a84bb0a694f288ee01fe6dbc0a0d791b8308b44d3ec0914a3b9b6b9af6129417244930489f1f86157c5cd8da61c6ffd6572b820fe0c502554d9cfd3d9bef57d6327da43652775747feed064b4424d43a647e3c7c6f6b037c8f8516816cddd800b80dfb07d1073edcabc44daa29207f8d1090226978877f963482b89558980e56dfc99c533ee022c36f40151db10c732d8e5239dbb197c297ae8dceb6dce5a1b23a596f857c8ab0e184e26577f2950e05999dd43cf93ebf13ef1562607cbbe40723bb6c9108f40a20ca17d8bf9051b1812c427ce2cb14c392bba5c2dec0a08f531a1d030aaad4503491eae9bb5406b4608d54b229a767e53e1b7cd1c400462d2017a84da0db91a93373fb8208f81e136b59e919412e86f41fbef1ae01b8f226568e2ca06c175b4274e0079e7995082532ddbf02743519f946dbb4ae6d024348f7218a92ebee111b957c9a54a83fd820062c885778af685fd62f3ad73b5b0dc703fa978ee2b0021e0e969b3965a61d816029ccf783617582daa772d38960b4cb472c9d36ac3204093aac152697cf6804e37b80fc103e8a4179070b29afdaa330772d2c98087ae50784159017ed455dc151dacfd2ed644632c69747395206a89320b02f7c3f281e03636146a0578255b44b59069dc96bd4c75eca0749a7ef0b67a72258bec0f73faea57ee00c51b2947e4ec6e1005de2130b0095ea973cf6c042572e32a3ad9529bc480978b3d202dd626c15f3810f4e5014ccbd5828ca2fb76d7728b46a606dee51983ea9d3999ea45427554aeaa8cc1c802008c906c097dde85575011d46db364c7e5edfd6d95e8a6ca5fe8ce2f42f94fb68c95eb10f9403d4072918797073091850b980dcc7b544230ad2d90f3dbd0254e956fa9b1b6bfe5a172ff692397ba234f410c8b4f28e52de0306d606a47f5bb70098bb7f3ffc6f428728f2f34bd7f8cb83cd4a5ae4436633ad986b7c13e420f633f33b9835e00785445b498a1d284a7f28fe262e9b159c66690e7b44a6fcd12f8a3b6c7dc499dd46b72aa58137174937f7bfe4090e57bd79812665cd1ca2d523c10c0e97ee34cf80a7260a070b5177ece6d99a7656e2989c037d58ff828e4e1d0d7af250ddd01d5747253bb0695db2201d251922626835a380539ca338c3e3131455b4ca551b6df3869ecc7fca46b393508d5ce6631849487debfbe8f991560787ec1d0175995747c092da87c54573ab0fa6593fa5c39d6c9a4daa7b72f623a7cc3c00553d3468b0d725ed8a71300f17e426295f65f56881e3eadb494c0a97b9283c04d0aba7325b56be6fd34859555a870303db92abc660c50898da5064623e8fbfbcb7fce061a8172c8c9995eafda9e805dba21f2c6638d23514f9129b93397ec9f076c71be7c9872a13a05b5d2c56d15ee5e5b63e3f4fa2948c9f15e0ef4591634e13ab73c878872b44136d4884b4644b3486b7a27932395da5917db755462671b47ff5bafb132725c2f0bc9441aca5a1f867ec5d4ed1621484ef16ba0b05008c4420ff5bf36007247f5b32c28f802cb37a593dc0d81e7dd605b4e54c8bf376b48ff580d98129b723028ad2f66dac2942fb635efdc3720dfd98d49d3d8e690f3c80e70a33925e712f28c1d1e8f7aa0c8923f8be874fcf2bd98d6738b9c170dcdb7861ce256bfbe72ba0131ce47f27c76d8c2b4dcb5a9eaaae5326d951e208e3457c1ac3e5fe2d472bd31eba4dc733301f4dce74fb550daccc012574722334b666b4ae2d66b95c272e74c5d6cbb136f75298d3b7a96a8a661ea30b864d4e81bff6f6fbf93bd963d72e36332cee36595d84a00190768275425c096a8855f3136e49e1b7d832058b00245cbeebb7d8cc9593db452d588702fe38e042d226ed4a9d96af38eafb10a02726ba9bd76c4cf52335a51346ce2d9d06c71360b661e6534eef7e3c977f10461723b2503e439a732f7bc8e3a49b9224386b49e610a10a9131111b6130597ffae722ffd8c0543e8ec74e66a6811d09c58a301919abbed2661c2b3a4ae33797ce364215a6c55bf2e5f30210a7a4caf38faae91796e2f631807c2aee5dfe6fe1f854a8425fcadb45d56b746177264847425912225e86e97ae425cf9bd85433a89977214a584efa070b77b89b7d348eff1ae6899ee878b5eef2f13db3505304905847226261a78ad8805d753b58be5b4b20e40087d351e561733b6da6323e95339bb224c2d2d6bfdc61b6b87889226c92b1970873b3c613369175ebe5912e1d6b66a0129162a2cf7c6d628811749fb2d54d0c3aad81bf4c9b2e42965bd0d19c6ea5072c304e3edfd6c5a796d7921c0b6a6be906b6ec62eb5ba3b98c1f1af11a9471e72b25835bd2d0b2b96b2fe6c0a476cb84206a004988b73b8fa32bd40c6a49e66723422b9264241216e07ca1c259eca1632213ad80680dcfbfabc6041843385bc727f02e5f8b3dd73ecc3495e04c2990ccb739cde4121d0c21c7887e4cabfad265dda31b648f9060f6b4bb16c792b71f01d1ef2c961dd680cbf6955ef345511e63c50199f42d5656a29188a34264c4fe8e5ee93b609c98def4de815b6aabc868e7248c2530b60d0c6736ce028ada9a9814f9da770bf217bb63aea9611caaeea4c72aa8c16bb10d74a318fd34d76e07b8738f797c1545ac16d35be4efefd33201a31efabbab043107119e7a7975eae253e8718639c64f98a5efd893b266ecf6c677287138812d4342afceac1e5fea1ccb43836449c0cbde3bfff3790a8beb7b76f04979ba0949d389f52d395c75613534de08cd39a54b0da1def2908a20a7506c3722ab9ce10d7dbd4c1d7cdef83fe120b4fcdf4c7029e61dd7ad0af10a5e8f5c954ef47319f8d2293e0bd1d6745e9951c1e3e8a989874bbc341836dfb62dbdaca72508a8aa87c46998924688008406f7f0d255be89f9a8d0f7d49bec02bade6c25fede8bb1474e969961b524952dbec37eb25e419d1a0c1cbdea68624445a6d540835d639c1e7a121247a61293ba36643f8e07960f340be77a397b114cfa8759352967e52c6fc959ef1f2928ced2cd7b70dce04aeb6457d03e620fc16a8e1ae332ce694365b23c876e51a87c79119c0fcb032e1890013b9eec22613d13fef027472297b38f7eee59e056b6aeacb6e0b6d4b22113ad8992342a3bf961b19c7ef177299d6d56799854d74abc8f14710dc767d4239a4649b1f7a81e8241f55587de66905acefd77f3e18bf63449835d98152cb7716f2fbc8f3a2480ee9f284da9a067208e679bc1f8228b105ab87a137047172f3cdefac663770c460f506924ce42c729cea1e09161e5b52be21aae3ba4bbb4f14dfa1496b3a58349ef58e1e083353728bd1a009d044c1fa1149a0de09260604e39e1ea081b7ea64fc91c5861aeabd72d17f96f66aa6a11c4b05769207887d05a075d90d869a88477101a70855f20b72ffe23684a4f93cf694ed40d44a84fbe7050bdec361904b1d9b953d735f0c0e7285281942b51e8da304674976d5c24341576d57b886c81e5194481770bb3fe0727acae404108e1b07169e4066c2ade26b9c6cfea8c29ab5529e06cd43a054477242d25f6f88532d4a1e6e2ff55340d310d24037723fe2ef477bbcbb7a2afd277206d7469bd326431004c029995dca20c8974fd612a7f51f33467b9d209d6c7747d3ed60dd9a8b543c9aca5cc1b690764d03d484bede42131860c57a14716fb1728d8030f8aecdb017b592c5cbde973fa2138942cc01ac4105d153501bfa88b4723655c26e3f2dc7a25f52acfa0446879b642b3041b6fb9ee4f5d0380389d0c67269b966c2f5e674994776f301ee78eebd8350633fef1f55f132f82eb5540ef072b6e074df1c43ae0e411f8c7e517915fedebb30afeaef9c789caee26b4a0069449917ced28dd9658fdef9082f6746189b4db5eb779046550491bcc71e74ae930121b7fad3ec152c2e38000bd763002d20ea63a6a15785f04387d4d181850c60726f7e96cc2d5161ad7ff9e3fb67ced0d07f6218bc2fb539b703d44be1a8e3e9724051bf9803772689057667f5edcaa66b99dc4c08365b4f20453c5d4f744b387218985df4b5c2957e5312e3ddec00434087fa876b6d3349fa7a8d813fb59c2649fd8ad5d03cb216f3c4581ca2c0f2bf0bf4aa8c9a9eddb3b208e6223d47d674721212a9d42ba0b6e79c1b3682dcc9dcb19def2b8864de5e7725ceacc501c47b7216c2874a1a4673b130311e6d42c2f914ed6718431caec4d2058f23af7bd6d2265709bc665e0062c0d03741c5067ad24df8f4ae2a20dec2403521fbaa77906551b7c21e2623b24ab0fd43d09f12add7abb581f21897ab8ceeaaa4ba38a5e2f572c0d04340556c5d83134879d63db80b3d6642023635ecdacd1749db1d5b01a53cd29898d62e487713daec289a1e7a14217e2f4df6e50f377669a86a5a6c139c72431fa6e563b4ac5ab6b50f4ffe1239acac01ec01483728074383cc719fb5447271f64439853fd0975dd6ac2e11aab8cab6e5e52cd77d94212c1df68c91eb1372fd218627240e21a6d3768e5792f33b7f976894930306ee38efdb15d8671f7c0b21d70887742c52d41a2f9f3768ffd1334fd3f2a3f525e03e2672466a02c951294c97d67e6c5eb12ca0366874952aee377c8c484db98691612ca3c7eba9f5e472bb27bf2d816762a3a1b004a8aea146046d0842dc4fa150bf1827c627c9d91f65f55d70ef23c583e5fbc3c378a5c5d014f07019aa6f68da4cd8a29519969637724838f7269ecc7e3130948eb7dc63a7bf2caf48742d8bb31f71b21a6c0cbc62194994bc3f8025c78a331961241061c62d10dd8600d44ab719d0b7f05a751cfb720c5c422437acc99b5fe1a02d9c7c492f75a90ab66e98d14f2f7e65ce2b9265726fca62070a345b4852edd52f8794abe213c822e033b088f0a8d514ee70d84d58af10c905cc446f86b67596e301bb414426f69455903726a7c0085339f2e046726aa3025140b16a7da62349db2b1800b2c3532aaab58d5b8c6e6c2ba907b877264de64f632f1b2699a212cc0524744b5bc95c0036f829beb87f85aa05fb19c97221c685e6218440b2f494e41b8ea6cda5bd584d89f544d17760c13314c8ed2f3f77116b765206dc853b3f0631d650f95e723b250dc14d5713b943f3c1b966704357fafbdc29ca61d61d936dd7ed8c1908e5c1d7b081519b42229dded78a531e72e47224400dd1dfe8ee2b096718a7eb0f782d4682d713d3e3e2a730f9a9ddb2721b4940e61637ce092d1efb5ccdc6a403db39dddcfaa5ac83ce9a80473491fd72e00f0046cc42ef8115557ecb61c13359234aa760d55a335190f429c0d5ba2749bfbbc42cf2b26fb8f63b5e74768f538eea3d33758650d6f7c9cc0362e7dec0726f603aa45ac3097ce7d15ca9c4161a38b374ec70efd8e435a65f05865a30ee2b6ffd7201b454f22b167f0e4df170f7200ff80f3c89ae42b9bf4631b6866dd772ff940e9135b11e6f6b9be4df04c60b1fd1ed23bbed3d92ec60aec29a400c5e726d28ffd3f2a306be9ffd225e0dbd1b9d75dabd4b70c05dcc2e366120a2110b0cebbbf89afe343c68a40ea42e9b9bfba3e04ccecc3a12152b56e2367830763872c01eb82f0057f6efd664b509c889471e32574ed42443c1095aad15dafe2fe47286f4b3e79ccb357f8fde96f69899888d6a357535ad2647d1197b499dc5437872a69b06514537b6923bcd278e1da72dd3a9a410a8f9610e2b1936887161d83c4b64ae6bcafa3ea386846603dfdfd8f03248d4820aa220b3b383811a723eb9ad7260cdbc904ec58576ffdb6c2ea6118e6a6ccc53f47c3304cc1016604ba0606d72f7e661e01fb5f6de81eacb12c3a1c2fb4b2b321cca02adcc374ca34342ae64720da13dcdc6a37607c19409583a603f938ae1c325f9a60d11a19c7afbe25fdb2cc77c4be9e4875382709f049aaaeaaca49c0395e630adcd452385fc13f27dcd644a643ad4a6daaea5c1f1134eb0fb1006daf5521c9be96f468fb8f1da1ed5ac72f22e3e84d9503b59b02c6889fa230c29864918c8aa12793d5601a1056e32d96c086707bdf248ad262f22c97447df8e10954d02c045d0aae5f16cd737b00fd872214358915bda033f6fb4e3f9e27219554d68ccfffd18edf8849ddf9fdf12cb3b9ebbc76339c209ef1a514299b4b3a002de6cd3c40966cdb9382667f26627194181193b924ae65cfc98fdeeca062330e152910781c46c8af6d44d210d649f2c72d642d9c18351b6a848ee2c42cda92566c2d20252c8fa63f01234e668f764135b1f9301a88e636904879b9145911e6d561bb121430a9a5b15650e453abd49d17240eaa18c9e57dda5654e03b0aac386691462a3d3908e5797e2762a36f3e256379448f8704f25632e888d6630546bde19ba165809ab1e966bc7c5f007ffea1f7244368fece93cba71269bd44013cb216ef8518b401e5daef9cde765c8c64e72720fab55d39d98e7616619043588a834cc05eb6da2da77010b20147442d23f487205206828fb97115a03ece007ddb7090c19d65078c25fd599e9e70aa9ee11a83b4b88579b7493ee76c7481c28ae7f1937cafb16a7b4fe4a325c4f6e47fa39917223682c25ed279657606eebb91ba00c05820194a486e2415700bc07fae9232e72e952f87c42d95402988f8590f194515f9617a606037441314fc72a96932436720e4c51ee39ea7b17092fc7c74206e0ca72f778df4a95e177f32d34d2fe46a4245b425ca14a89682e824dbdf4cd997dc1a2370151ef91944f453af3a920607c63d5cd70fa6362e244208f3d492cc77218fcccadd80e1c2a2d054662a2da25e12c15d651036fcb9a85c74e529ddfbfe0da24ebaace5a16087efb952357a5b2ab721143b71ab9b2669d29d8dcbb09dff8d9f5e3e901c869eb1a468ac9d846bca772a71d5e98786f787b390bd10cf39003f5477b571594b1971f44a97d3c750a6b250446ec0f4826c99309dbeaccc9f3c9f9b6e532e308af4bcde5c744dd614c2472fded0f2784aba4bc8747ad9358e3d9f9b73d613f71964cf79e6089a1c5b4cb53a9cf73c570cab825910da1f767ff9545f9ff191234a20770aa6a739c96ef6c7220cc2ba2152e3a3d10dd2fa86d6fa3ecf49dc7d54295334970de7f7268c23c7277bfcecd8387e283a6cf042f6bac1657f396dfb50e286e3026c4613c74a9012b48be7c574fe875ebfc514c887f78f9781dd98e65c1634ce946c89fbcfdf7037205532b1d45098c96443d5d37ed02863b0afe1254a1959171b940de5bb1bbf7722c6d9d22021f0c8a6b8c912138a1e71aa9a0534e9bc94a3926e6e485bb6d3e728ce296a7a837bb356c6dbc7e5e37c36346c7274fdae4ea75c633121f6295c8389d880a2a19f9c306607af17b97d89fc343dd85b11ee5c4f7ee80301a1f7be472dc16c3fa23f1f0c1bcc92ea0f7907d16d66e85906bdbe26d946c7318d78e8923978cdc7843d5fb0ec7766bfb4190a0c6bfe58354f79e21b39278cfe258421f72b819bb98270cd60c218a1290a225151b7792cd97a5c29666da0da612ee65a814f865f74945f2c5568aa709e766ec5948a8c8d92d548ef8f60a46dc2aefe0d772ff6fa77ddf8bd5d77d89b882851cb3f592aeb29fd641dad21b366ddecb490b61ab76e18d57feb5caaa64abe5f3b768d5d1ba9fa91e039c7ef9be22b065701f15eae76175695955ded91dd94a86a00c8343d4604f7bb4c78392941d2d0c6a3e7221f8bc4924e04df141c3700215de45a963636c4efe5a0001ffdb9d4364829b72f1a52f03b41e8548e2ba58a2eae89098f89984dc4f7607bb52bdbad5c4ca9472bdf2b2daccdd63417d8901721c0186702929d28c88a42c4ae3e3e06db77d5e725ba64de2b1a03688ae57d5701f3822d88856cb6f7b9efadc9b4657aac8739b72d798cf144249910320a91b2317060634894ad1271c01da7d0fff02103971b97251f8cea54322ec3bf9094e9899568d470e7319228797caa56fa2927911263872a0747520996427c03187a92f38377d94ec03bb8aa786f25a4c5e26eee2f82709ac0e7fa855705813b4dff557b7cc257c8aa465b22a63d00a372c5bfc79cabd32b5667d8c31603ee1713e7b169fd488b0ce28560aeb2e599972a461a457f5d9725b2c50ad8a2efdd15d49d8ece36a1590921d6251acb0a62a7787625f4c30e4707e15edf6075f4906637252e470e92a4c0f36666affdf0a693eb62bdede50f2728a3d8543e3c9a9b08110969e877c515a17faedfa3dfc1610a0c2aecab79ca752d402f4da51c8388cbd82c4d5eb256666c278e07ed535eed0d2656673dcdfa41cbcf1fac7a43b37c7f46e335bcb27e530e7bb00d199c6a0ff5ca2358e1bc18472d6bd13e7821cd048470fcb5b1cb2febbbdaaf47263ff4eedbaeebbe35044b6395a498c5f3386551146204e47cc0782b755d9cca974b97fa2216a3adf5c0e1272dc5f6090e219b872dba1e59bf281e181d2008bc6db7ec40759a41508689c67722732177e610185cfc64195779e8fa5b5021ba7702d2d19da4579c86df009be21449300647ee73fbd3b3ec18140c81aed5849467fdb16cd8a78349de9cf2cf86b30cc4c104ff1755500fc09d684ca53190e1ac9498c199d507b63752511c206723d22958745e85a485bc65ff85c6ce9f96928d403b1cb19885d3e6aa29545d8723aab093a74637ca4548489f6c161c45a94188abe8486263af237376a67db6c72fd8ba548134a5a1d4a094bd4a56c6a737a828fd37d6a815f521c34202b8d2d7245f9f90f4bf1484c4c2fcad5061f9501f1f27cfc8b6766a7ccefb62404133c722101fb6790c3216e10a47dcc17852e8f3312bac640d6074aed0dfa7f0731893d44890ce5184262548843cabd338c5f5645af76f56dcc78d01c83ec034129622fd52744e215beead149974dc53d085e2bafea4b7fc5f89b42aa04ab17c5daa705ed67e2eb1c5c8026f1bb32f2f14ced5093fa08e8c8c48517152c14707e2cc3664f22acabc95e1dfdef71b7fbb7f62c50b9826bba9327e6f4306da83edf00b51e68e789a4882a8063d8ab482c8465a247b7388dbfaa7e59d5368ecf040e712d6686caa2147e8b4770e2f91cba0ddbb674f1c773bea6216598a02ad5ce9c58764eae9d15945bc00e5e2993b159bcd98829f8334ec2f33f9ca5fd528fe219ae3572e5b484154a476380067f3e1a66968cca9cc45a9de6d5ae6d2df0c8a608a107226cc00e8fd65d8595330dfa4f04ceb425d1576a21066db1b7ad7348761f62e4230d1d56ce70d54d297062a31684e010062b09c45eef354eb93b8f7cc9a3d893721dc9434873200270453fdefad86672545f8ead9145fa442d657dd4022a6fcc7214c12f66cd93528ddcb13eb7ab4df2de438458df93e89d2605a1a48c5fa6c5722d3c08277709895cf34bc9226d71bdebbd53b7534f82f9aefbc0de3f3e0e4205e141b892503ba263350297af2a62a21620956fdd0dffd2b760058c233ab995722ec827c5d3ff936351d066eb1669a47a734efbf3a35928519e6e8246052dc17295a44043597155d8ff64f76d283f2c0021fc8da02d869d781e0aa8bc1bf825723724c27c8a2bc233775a4621684b3730390c664986eb48c1061ad20a4e57f372e37e6e71f877bd134ddbad3e425dd80e71f86efabd94cefca89a24afd19c3b728650c3396633ee707854636fdeba29317204d57ce5fccafa3e2a5e90b7aa7a722a031129659e97e2f5d5f742a4ebc41964adeb56ab30013c652d98e71b95b37251d07621318b80875cdc1dc79e0805e0ca29eae984a9c66e7610a0ff4bab8b72f8e95284229af0bd957831df98994437c6ef009f99e7cf56b62bd1214a6645723e29cb14ed506e6a0adf331d48472bef3ebe8f977a0c3225349feda9aa4e3f7223b1a9edec5fb43be651cecbefada16cb24aebb3227a4ffc076702503a0f8072bdbffc7776cbee1aecf3e8634933783785c1410dd825e0f1a8b70b7b5985cc5f1dbbd4686cd7e54bc8276ec172e4943ad431b23e1e7ac16775363b99f6012b72c71d22c9773c5ec5086131dca72028a87b6fe18d21e6ad1a44c7f620d3bb95728626381e2f7a5344a3af48c967619673cba900a703e1867a2a8d7988fe3e5372011947c3e01dfdd653b8affa08c2a3d13686219fd5d5ca4410d367f73ce6143c9660502c9b9d31bc29a928cfb58b1ce7d1e5c2adc316ca85c978da1174a44326e73ee81ebc667ba5c4fe55ef09291b71fac8ca932bf04fd921c79782f2649e4fa620c240505928faa77f7736ec56415bd9ba283c4f87422aca011f1bb8eb04727c3739ed09043c1bc9a72a1990438f7faef364233c995a2cd42f3494fafa9c72571a067086f5c2814228cbf5c59910e519a5655ebd6238a891be0a0a2341ce72f536c0233d03b4da2d0b042eb2a1cf832834333ee78d1f196086d7d177b05e117171e77f5f9e2690140700c74d4f6058f94aaee5cd37f526485ed13d416bfa729df3a0cf013efc53bb0c04c46826994de38274087affd6c69ebcd2108f9ca40512bacf3c8089715ea9c15f6052620cbf926ae0b5ed957dfab790fcefc54b96728d571c1549a7f724004a13b8cf24844674eda809e72157afec351931e8f0a12cea7f3e7dcb0fd0cba49bff4af5bfdf818835e90e0d6716ef78165965f42fee694b99a0243f98e62103b4c4a3868ab793885bc814bad767b716482d9c7693fd724ee8cd556cb97146a99aaeaccb39e9d2b0aab6c6b11b6764c70aa4c50d137f455b84761f1d552e1e135c9a299bf3835d686544dbcdfc67e87f59c10636760072a7a15ebc83a5045790cab2f44815f36fa0772a0ab29222a8738ff2872f7073720ec2b3f773bb23068f9ec4df1f99583cd16c1b27bc4c18a739b1c503d676de72e18555646d8afa4673910958d2f93f2c0207f2aa8c8cb797bc8f74c1eafa37725ba6a8144b1ffd20cd03c81dff6771a468eddc35918459b4f6e12355d5d1871386ae87730199c8b4682a8218c37a9116ec8d3ca96bf91afab78720afdeb0a56b5aae822279e6400f3d970aad18bf0f584434368558cc5af6ef4483e99d107772282dcefe1423888ace17e0534aa9bd0026d93ec8043ad409ded2e35b750d5b1c6b919bec375799903ba4c8c82a0a4838f22d828e2af5bc1668af931e72b50972e03921f52fa4ebd9547917d92af5eab94b97137761c92c1bc1d5cf5f9899653d0f05321279faf2025e70c149cdb1ecdb52e5a0151d7ea417a379de72661019723e2a73ea6d286925b6a9b8cafae677e8a0a0d2fc6cbcf70596744c588087f33b4557fece29d91adcbc7cfbadb92a38d6c693a39f7923213fe38b6e5ba148995cb4730a72cb88026c3ada84ee3ade7b8c9397ce1574ddc39f5842544f14f79872640324943fa5a28358be84aad166795254a8858e7110e3602b40c9f8a74a161d6e906c127ddbd3c6c44010581835ba9c222da4bfe72cb7ecebbd082ae339ed72c0abbf7b5bce01aa80e327428d431a1094e0a5dbb66649028584898b42879972157f72a9d6399f0254c7d92b9651ef544706303085bea4c91e67cdb8fad034723cb28c19b0b48af9d13bb595a7df4155d08f464ea90069c41711e6c1ea93ec4883fd337387d4036b0c45d15f20d771a7e94f0b3fb9542c0f58a302b0e69eae7294e498cc38c21cf52539f5cb774e01e5c4dd80ec900bdcc7ed4571907140e152b1db78b81d8f885efe38cc57e934698c52d781174cf5853b55392fac75d61872f0313802e5d63c04f883248224c3490db012def2e91b9d61f73960d9b7468c6f0e567807218dbe16ee61124ab1e0a4eca8dca8a248a0f60bf51cc4d99d8d0c12bd8e2d7ae9a0ba4da3b37678b1ceaa724e0a47d2545c0d16e9f4c64c6eca3a46f3d0fad4f9fded275f0eab866afa4cdede7632cfb66a3a82c20ed87f408f44722300e667fdf4969a2155daa1956ea020099334094e301677ef1401949f463a63de1c4c3ee4bd859e415f7c359309b2cec1a7cf952d1d2fa97dfc1e26ee6e50282a46c99283cc948d1ee58d51dcea8be11e8ceafbdc1fde1ad2ad57a8e3d3667259b9a4f8ce5cd1a321d46cc376aff01506bbe2ea9d9345c944032c5daafe483a6129d313722b32cf3b915b14611cc1b9788dc8698baf7140db4f2fc091829234b908a3feb12b55e6bb3679e1ec08205936e86b67d6c9019b443e973f3e313e727d722c20f42231801d263d3ebc65c7c5fe6cc6c0eae81161374909297cff6a720680330eecfea31d586e26769d78f0f17fc7f2b71a0de96ec219d4adb4f64e2b7d3d0083a6667c816f7fb87268044e495fa9ffb667f9914121ab74ed005c17729eba8b8cd4bd0c3a018b9c7659b3fc118782e0a82c51e3cd2a861b05fc3d252de82821574bc1bd0d36dc2b1a24d5cf6a88e58bce0b266788152dc23b1fed3872d43f01ef8a3baafcc4b618e810c488e2d128870d3dd3a739c5252e283a9f40237d8251c2d1f2e90830782ffd355f1197aa5361ab07528f49a97fb2beb82a7b4fe16c50082d990d02f98865a9ef79aba990f5f59e5030b25d5c428cf2c86df271e2125ff049c7bbcccf207cc4291e1ac55314b06a85c20a742eb067510e72e9729f5150c6d4cdd2d926e2c147279cef6d27bb73bb6f23dd6be6a84ffd9943513f1a4975e67e6842838e437b34c5b107bf29fca349c34d816dcbc889f60f730372d177659540eede727e090d7b48e21071d4ed643c1792d42c1026a0ba359bf832425bd8a9a30095890b3fc8f28df4bb3a67276502658ae96e27ad549a166f1d721bee740c72b0439980064102c8bcdaf91192fccc154bc99c5b833116f3f4066da40ef0687dbda32d84a514bd912b435adbfbad7fd8f271fad2baf8d1c7a669728de5d3d6471bb3d571a03f1c5c82c45f92f40c5b658ef4fe771cab4120f31a72562fefc32eb2d5162d72b023d8417e1e78143ce984f1c6b6f7ce3b04892a3672c800e1b190f7a07b331b1b4f2a9c00aa96fb3242b999594d2e9f5d21de0523403943569a6d33b7b33b3fef7694dab757862f91ba9efed6e8e58bdfc868aa3f42227c0977faf34beb658caceb0a0fd213e724ca6ccb56315397288800bc55807267ccfe5d8069c6f11b30e43ce4f6fee6f43a3283d3dfde3a70a87bba87eb9004586867a5b3de5803da0fcbea7fe430a8bbf8d5de32177cb687377f085f0e9e723f7eab69358369c4c51e7aba5356ef9beed96ba8c3ae23e625b2a249bf5e243970fec2f1bb93138db116bffa3ddfa2be5d35c4ae446d28ba77c508f18bf17600c5df8dbabc89e1e76f68bb76dfac15ba1c1bcb05d657541aa8b41bdcbb16117231f45e0345fe9b1517e8c965238b687858456b3eb31bd825f050d705de13541738d841e1efb4f685c56cbb8b1352fea0661aa7722af8f3df296d0e18e1a2ea72ac7f3b380e403e72cc898bd05f4c8110c57c1d8fd2b272f88354b4c19f26a472cb85af8e59b38aa2d245bb63109ff2cef94bbc027d0021b22c43770c31541e7223ef25d4cd89a76a5770799bab6eed5b5a09cb7114f3c865b5041b1f1624853b33da8f6eecfc356db37a5e4ff884a5eed9670998f6ea34ff9caefb0e85c0677270993d8891881887339cad4fb274a8ba8afadb3fde170cad2a30c46e9b02a4726dc821c568588b9b5de34ad239763ecfda4811372510fb5b289439ccc13cfc0d6c707545ff849e007271dd6f9414959130773a0b20fef739ae31e77e7554d67222bd5f9f6f1e7aa4a62d888499ffdbad19e138d9acd70b11b40e41c5b2e7ac72056044649400d7b4b262cf75d3f20d75e613a21503e3a6796a033281fa57227247a1fba9be3dd1302623f71390cca9b094a51535f4391a3d25410a554061a343c48b3028c200567e580dc1916b9f13f32fea1a71e2a55ac9b94e85725a0730723dbb3c165e102d2ba77fd407f6c8f50a33c5a0cab3c20c1e34f8658836092672859d7458c75e4e00b6dbe5d314c6019cde6c95ecc09e8d456c1b1a919c01dc723a28b064f396cff729dc3816c1af2865a0eab0489437db486e73370da64360020cbc57e716e56d5fa3aeb4edfa1e432240e2e1d78e2346d75482c2420f88fa725c0a66b6218715e0aab54629e2e62a35258aad7e12fee2f6e7d8efef3a0a4172f3c4b111f21d2121ba5a439518c563907ae6d5ddea879bcc89b2985c61356c72652b5a1aa4d862474a30e79d3c6e37b32123581073b38c841b28b72449018c07ab198e02f9c4fdfab1e7bd5e18546500c76316179942f53d7b91529c796a34723a63c8d8591bc8ca0396905da6e680d6211099b49cd9f618f5e97af9184b847243b10eb447c521082d1eaad8469f9ef36b5a9473baf247add81956c05bfdec72c89493ef3727da72598a6d129c9135c709cd4e2ea909c068f8c752eff8632372628cc3a97149303284ab1813dda01453ca5f780f299b039a81f70f7c16eec972c0cd412263ac5569ecbe499add85524bee5e606adfe1310fa9acbec353f80922f514dc48e1bc539d4ce1f6bf9c7364251168928deec757dc5d621a21ac1ef95f0fb4409014f7bd5b433f81cb0a9238a04aa110ebdc49e3fdc452d65a45fe5a4fb841092b7362c5b2edba2288fec13ec8668e7cce9e4b8f68cd6e420c6bbcab72ab8852b86f414441e402d56196e4b18c4747d9326d06e0125e3a4d68c3137c7238460dde5d5a3726e7f62d1855eb68de18ed5461118376b43f9664d3a72d9142c33fea8712bdfc1f64be1c5c00bca5c7bdde0639b7affc2d071677562c76646d7c9888db45e0139750168ba11f40ff763ef29df2f1db7866fd300496897d5472b3e3a406c89b4eb9f7b717846eb3ae38dc1388e4a616c0e0576bfb88a4cd192e3ae7c840875ed5b13f0c8d1cf664aeb3dd8d81fd01534300bd20194400d8416d242202f6e0674f5e2e93125a6aaa950911855a8ea9b7cab947fe580d2c00861920545a00ff6b5786b4ac99d8b37a8a6e74ead78ee04140ad24ba5f1b92b0e472cacae361b0e854b00d5ab00f49b5406792402d08047927f55ad7da18c306e835c6a2c7f7086acbaf72af4335fb4d3221ea821a5f99a3e88526babb7e040ad572c089b4ca8077c8dbcd1c32b286bbc5a1c9154eb38f0e4825ccf4a7f3c28c3f6ec3c4d850d61a856ec436b301466c91185f6c8cdcaa01dd620f4b11097b1481722be98144563e99bb85c46c47f54205f5b978874f0c1c992c54d3e52335246f72a366cd86663937a03317d5cac4d5bff2dd89bd3fc0d3a19cac6dede2634eb41e98d988e77aace0b8c5d5dc5b8d7d0e305daff1fa2f3741a97e611a499583060a28cd14f4970c39843e5ce4d729a1670f1ee11a6a50a76007b6e6f4f694f80b72eacb69b5305fb959ce961a93963bf46ba690b00550738586ae792b80fbc5b5728831c5b7d713c690c5d8463c4b99d327fc1c1367c94bc1f982f7224a7508ab72f26ab452e166d6efadf003db2c5e8585c17c572fcf33fdac89d5f8d632b88f72ea077fd00a348cfced0499351c7a0df87c68ce20ebce10835b1fc13492e3d0721b7d4288af4820f506eb6e73fa4ef0ab1686a1222d9ad8ababaccc0f9b3cf472f94660391737909b204f46e122041549e45b6cdd1b4fd60690de33587b08340449d45eb4a2ef52d52aec140ca6bdc5c60132bf3cd2479757ff35a722c8f6ae725c52a451ce1eba9ca7207a20d580389176c9ca7d23cfbe06da362f904432297255c8d45f43432d3a8686e8d77b539b85c57c9cd6ee629f68136b94df643b2226c91b1968091685adb9e19fc578f8c1ff54bcc33110e2d40284a5427c8c4d6572078944517a6d8557457be97ec3e17024dd6c43a90eee16f2777fe6f89406aa3d204e062b2f8c65562c97457c814e927de7ef2e2c7e5c97bb76ade95b64bd727290c1e526f8c996bf720b46d46ba2c85c00b5c181339288833e9868be73114b72f84e0218c96596492352b8bcd1dd9015d8c5c67b73ac36c5868ade7709dd7772610d5e7f8e40deba9ce757a26b5eb58e3efdbf3b5a614a9bf467d399dd810a72e84b425164982a42827f4e8ed1517674931a1756f7b421ecd5fb1d3e8a140d7254be6273f9202a0e30cc4020c49a93312dbd26c9076d456b37a6763a6c1a9972fe9947458080d794148a9c0fae6d6d18617796753ef68ed9d040666c1015271e7c58e2c69254728aa2dff1d22d15dfeb6d59486035cc80ff589f55030706f2720e3ea90e7ab94e786db24512ffd92341d43cca867708da82321174e049cd7d72e8a8cfaae18c769f25d4f28f7253e15f91e077edc699e0430dc4509ae2bbd2219f227144587a24cf2eeeecc9b5192ee2c536bab5343fc0a5f1452ad9b9128c723ec29c9e57c1152337bf5525ced7b8c50e7d97f8a7e1e9fa78b79f1b1c901872603728b5b5e7441e4d994a1c5df7ed4de417b0e96ceeb83ca9aad65f62a5f5000fa5796fba1b3c3e3ff125874ca298edaece0de05fb468ce82520c7b175ab2724471efe87ff9e27a99c1b3d66b4b2bd084a46da6743fa2cc4955f31c66414272256d6dddffbfe907bfb8d87a5218d7d42bcb2f5ee35129810cc7252ecb0d6772f7a43537cb9f4053f4c6e11a0ad47b17a7010731cccb2327c9fc5c5abec79d72ff69d77fffd278506df2cbf27011bee30479cd638cd69025f07e99de5c14517234966c0241a1e8ae701dc0530f5791e7d6fca946a6ed6543939ba3930b3fbe727212597bed083dc9231b5aadc890b2638cfe721eca64be42a486db63bc276133ef92c6d2af84b5a33cb6e15e1573d6aa9a694e73a84bd1c47a598229be0a55726f502a3b9e61d2642abaf00cce078a969446a84454230cdfc645c99e9a446572e89138b977f0a07c3dc43d3a50a38cdd879c311c7bdea2cf9a9b46f2622b1d72d674983ce9788f02236a946f1f0407f7b422e42bb54d77a0800aa71816c334722df8f27adaf55411609d5d7cc25e2baf4bfd3b2233b05fbbe33767c894342355f11471795fecc6844232bff696193f88b9d1e7261ce561093baf96ac316bde724636be09f3bd82fa213ed32583dbd3b4aefae7c3449dc8fa0514a5ee3741dd5fd745c9af89f0e6902523ec0f8ee36149f1063f038dd6bace3ccabb7d6c621607e6633f8da3d0e5956472c92fc3205a238cc2390feafaf50a4aad8a9fcb6ac1729004748bc53609b20a3dbae0397d05e4e85fa6fcb0872b7e0ccde74343aae63443bf86a994d1708647a330916aaa7bcdd12c82191b828cc4f2d5f3d6b61f311e6913f069afae8490ca19777a40ec1fe8b0b75513392956a2572fe627a9562772a9bc87b2499b3c9b59a8eacef51338a56b794c37d3eae1ec5c614deea412097235df62b7bf82aecc24662253ca8d55a16f5f95bebe86e42ce7485aeeb62b702e44612b89fdcf95e8adf366586ed3f1fd8e7b193a5dd40091af4a24c79e091c7213b466f653c06aaabe574d6d8ba8055aad27b960cc991cc21bbb4680f6909671ccbcba7cda43506e17f70f58f116bfdbeaad5143005358cc58e9038447520f72e39fe0a28e1bc901f230e2d4b777b858bed7cd6df8873204516832066abc8125ae1cbd52e11ec34034fb6c9de601d9fa6971fc0cc10bee0425dc474b4ca2a0720a9eda59e2de50a9753adc1f12633333d2ba3ca34b6e305d2aaf8366a6f1dd4acdc3d191d5d39f40dc87060c0fd22b3d50da64cf9ec8855178f174b6b3f17b0bb8ffc9b30b7d478af7779649b09bd3afe1772416034e8eb7091a3d057fc00b6175d7c051d12eade8b127eb26ad16e018015ab5d15c165b6e0a2ad6c057427203d1ae91cc53b8ec1b31d18b643cc85af590f7fbe973bf6c5053d738a2722c760686017e2476a922353c5596e5012ece34243727b737bc6c0f9919a5918a2e69004996f36fa5a47338511b8b36393d0cc3cc7385d06600ada5ab2d9e97a563df250ae4642e4508832d400589d3d6a187d9532c70311a2b6ca82aacfd63cfd64a131290f420fe499de5005b7fc203cb7dfcccbe69f6ac3701ab27c9a7e4bb4dbd72e8ba81fbf205e4b1bf0e843b6db3bdd891dfaa87edf449ade3a9eadcbffe5d722294edf3f92c4f32600f7b740dca6a8118da94a1d85e88ff029faa6e05129572d20cada053b0f7f90c08f39d2aa02132cab7792403b40e79f1faef1790d67a72a628707ef4a572cf634e015a9a15afb2f8f64029a93b7e586f83f3ea8ac79a72290d66d7ddbc194f92a9591a7c692197edd0e39ff9aa9ce2fe172ea8872f2e7226f6f1a479f97c943edbaa04e5344fac918f44ed67fbf74f7dee2a293d398872d2b41176756207533904197d2e05c5da6382f6d4b2a60fea986ad61fcc356e3864d15e7f770b3592c1e8a8510d92fe513a2c24140dd4c569ac4febda8e477072486e31f2b7855e45ac5c343d2a7c9650fdd78e535de858ad1095521e67e0cc721c0cb231f815be97a53b0a7484448627235832ede0775eb75b6d027b761a4e722a486e6f864d4912093b9b4b36ed88c2a4779fd3e876e58b2531497ba844600310124fcf42f55823cd0492fbd0264958034c1523ebd7dce5b9363e63c8363e277a51285547935a1d1c08732c4bab8bc2b2b14a79c64e8228c9972186e63e587226744c2a289d1df203ec7fe6c59c66bda992cab4c5b9b4afa5e32f6d7488c44aedd4a097c4873cab9d3eabdbdbbf06630b967cfd1fad025b24a48895c4c1e672bfa18d55c88ce7b2d62fb10e7390bdb83026c6d3f7ff37cbae7c11931f34b272f3c983a8973d16c575f093b16cf105e53839d8542b1c1a04c9df569b25e52b72fbd6fa136509fd9b09feffe42c7dcab1a2cf5db7efe9a9714dbd60a07926a750828426a0ebe9d4f8a8cd2ac58e83e458f12c092dfd0e973075aa108f0250181cc85dc7b6e14359da98c088441b732878da105c7be0f5ac9ae0cd703bab88060e8c9dbff8efd2bd2c08e53bb8aa0dc6af4d664ec788e515d6be47cfc15c867272a771fc5400aada02f5fb0e1746619f2272a0e52cd680bc7a419f3a927eeed5510499cc6c0116be8bd1cbcbc72a71b5b31bc3fa2f60fde09d13ef0d31e331f7150c1c1564dd5d1704174af4fb4c9cc3e09391ada05b8d2a137f67437382f227724b1e207b4627f7618e9b01be31b72d9a5815c96c48570ea5b916d9321024d372b2c79cc1271599fc492952f95565413f55d10af080d66bff7af1b1f811964d720bf26650133b4b646777a84040916751ed6230c622e36e7a800590d0e9888a3aa5abd8402039262f27410ec8b4b2c89c9f4d84c38c5c3ad964e42826a5dd27433095b22f5d59a7be95678bb1d6070158d757c02367cd21419faacda67e4a9562c905709f274485cbd3e11cc311989e822b142154709f66edc80d28252179351a8badf23e8f9cbae9771fc36d3d72cbcd4ef70e3fb6045e824e7d3e705387714ff16edb8c2844a545916a4d93b6eea0d5f260f42190307eb5c57b3b022df1b7720e398ba600cb719a310ad702f231f4d0f22b6025c672f0926d2b60d5f996f217f96426b447f428bb18057ff6700a264fbf527f51c2fcce6dc7ec4197c58fc6729beba20e47dbce64406a5ae337795a8b77ca3d71e7c4307dd766c8e521b50618158a4052703b18b8da74182d7bbb3cadb334ef0a007f7f2cdcff6228ab2b6772cf92a86ee6c3bba06417df6b0322db0374dea5efccb5e669fa4a00e48dd89c3874d47a18f29e94f126c3c389156b0b5b4767d3040f4acdb332ff1e80d94667726a14e40168c0d023083c13aeb53d2bc8b3d68571123d55de58f2babd1d0b8b72947a2f874b79234270570bc344897af33ca109baad15f786337a294dc0820e72d7c32ef1802b1ba9470f4232c1d2a0a6a549eeab18946bed195d141e5c318237a67112a2416ea73df8cfaffb5a5a23c8f07ea824a2805e9d0982f1746618c47298b76f38dd769c02cdf557b79bc67c499c820f220c59f85d80f5dc4692ccb972d5e4956365b0b7015dcef7a0f1c4abe6947b7cbd7546b3bfbbfb31f3055c09719edb385dee1abe112db19d476a50c05f41eec089c2a1ae16633a8a7422e25c1f813655f3e3f314c0351be69fdd35af2671f092f189d7ce9239775c1ec82ca372c6a7a7f4ad7fe815c45e09888b81c76ea1b9084326d3cf90d03259cc7fdae4726f82dda9d5b8fd8c90c095fa6a4bbd062112ae0c034edd2b8b6fe7f0cffe197211202f1af26189e1b11ab8fc977d6e2fc38af9dbc3b6ebe5252899b5ad2a6172c450745e6709b62a8968686ee19b2c53bd155a1337f8c5d86173ba3744b91872f8f72b92c10c26fa26bde52639e68d3ac3ee32eca60e1371118c2a9fe2ebfa4936d04312bd346251d66b0df672e323b91ffef4413aa92b5bfd0c179d2709357286b539705f63a668a24dfb72bc7ac43fbc41a160e0b9e5c23ecda851c043591a7e1339fb8242ccc3f5dbbe62b37fecdcb46e9ed0aa5ed2d8f59812c94dc6da7261524363b455478e7649de66be484ced0f51ed3a3e3702fe9c62f7f568ecb44b767185a6f7fa7404d996b9b0d911c7e893d1abb71685b26f554f2d0b96a664725cef29f4701ce7223fabb8bf271177626b692bb4f185e12ead612ad3904c267291cd52f8257c728c3331bd9972796a666d901369559b9d23ab4353b5462fa661482ffb5599443df71faf5d0d3c05dbff2cd4a492f413dc6fd5e313ae2580a5082b464d4e8634a2824c34bd24a6651eb16676b34afa2206b19d7335f115a5b3720433cfb42fbf95328184c0a9817493f5d5331b152ba78ea3a8c65f1606977254eb08628a17f2efe4bb55124fabb3daeb17737bb50c1a122fe5c6f272578ffa576f57b8765a6d3fd8ac7e8c075a966b69126fd112a89e63fcb5719b985638bf722374ac503e936096c90fc89a139855a9f6ca1e2fc1ac970281a5f42519fbd672260ec873dc9dffadd5117acf8093222503373e28d7fead2ffc5a1949067a1872ce12a93188dc17e32358d31b7189408e0fdb23378edd1298ce0a20d1483d527250753b06380e804667d76f7bd497f7f9f92f94854ff759fa89b6e89b1ed22c2851210c0278cee0ffb90cca69f4e8c51cf336f9ba64620c4a7afb4ccffb52f1729b240d774a7e87d10d6b3f313d47aeddae5cf787e7983b06671e3c3c7e124072dacd5819780c51fe9ac1b29c176bd1901794ac19077360662a2b650e7831957261316b67e5f37830beb6a457ccb898330d0dca86b18aae3e91234af3a7e0d7337094a5e39ab2f199c7092ab9fbb97186d56064f49e4ed12b813ce0b63f298b0dd1dc59f7b206fca02700f481a0444f48702007dcc56d5c5ec666e0478373de7203d36d6baf290d88e2d6e14b259534f1e3e8817450365934afbfa40a7f7d5b7249f4fc5fc22e6c8bd02cfc610896b2f186b480401a250341672804dcb1f8cb14b4dbaceea9ba45127ecc16c4e7ed5a67450bf311d38939cbf2b00b44ecd1c27240f99018c1ffcc5cdc62136ad6928ee803d9fdc8d7c6b0058e80b5c797eb294d46609ca50a45e37fe57e797e83899a22330b480d7113cbccc0e7a755394bab4ad772b5ef83c6c8fa365437b41c37be03577249bd50d07a530506d240d25a420ebce764bbc8f21564d957c1a6afc8e5e227e5a1cd955187139d627c8ff2d0362c5a186021777e2b6c073c50eb8f8e1fd7f058cee495c4707b2a34bdd05ccda6252c5dd610f772c20429086bb20d4cd6e2b645e0bf6ab51b5f21d835d8a545357235adfe0340351589f23ba62999177139177bf43809439f564a599f37f8a144722e94c80294253afb6ef168b274f3bce6e2dd4093c94409f108440163756db81f5113567db2551515f58db2c2e7324b5b195160ba23adb8589ce4c6b5cf71f32919eff6d4f09cb85e2bd4fdf106bea65c32b14b0d42aaef707da5d97f3543e81f3833f57f2e25f48f720427f1803208dce0c54c01eabdcb4f2eea707e53043c7204a935693e72d276e4539d7f683ae5833f32ceac0ee05ee18529bd1e7d9e0b72020ff36f166008aa782434dd35c8165fbc2be89599ce00a9bdf08a4d886bce7268f3ac19e7803fc00636cb0965eca8a826e2b7108b1a36f1d273020aa2a9662b7232d18d6e75e942d072ef7ad01654c1d248cda5b83828c28bbf1bd99275046dea6a310888bbab33b8099e02326303cac5deed337621539e81428104097a8c72db74b07816a2d9142fa5ef7168b424f8bd45d6ad6ba6a1619bf6452be6d2b53a05301616faa638634815f630e34a4fadf3b57a5f69a015c7352f493d41bb6b729ba4810d3f62714d5cd6f95a8fd0f19aa701c7f2e0817e626fba4e66a18e8672cf23ff5ed9b6e882c5b171afdc72aa0356888e9d93a07830b8210ed2d5a00004e7c03fbd49285dffa44c01127e2d8c8c02b0acdec9da0a9514da21f94d1fe6543461e334def0401c005e0b9f61abf4634331f787f44fe3d03ce357104f70ff27b19b3a9f8dfea723fbf3483430a0d16aa559ed20e515d368ade06d1a2e5f9904412bfbebe9719abd546025dea013352a98e1f044cf29df1a6c8e2d73eaed0c4c4f290b22c629a317d6d5e44065b07e9eb3f5429e8ff4942550fb7d2e7155ca72a604f65a916e24c9048eb2a2ba62478ae4e003fb2390359cf4b22680beb4c45d366f69167257eecd088be805bdccb38760c03df82948ce67de31862b27feb372ae939d1918b38f3163c30da085d30409e65e997dbd6b539e5e3bda9090b416724ba2de103452f0cde4a69137c4ccabde6615bcd6b03c8e4719535b064905d2149d71d0fd84c1e91182b7266c766708b4cca80bd6c3eca0615ef09968d66b2972e79481fa25ae6f96491b8d0a3f13bd5bf36f0643a1e0cc4365abfad80a20d972954fd4cffe00ffeb7ec512379b528b89623e9234f8b27d1d378dcab8779e034bb004b030e4299988ea2bb594a5926d81d09d168fb901cf5a1c904ee1ff534f3e1fa2edefe74bf88de3db4b622bc2739d6d5f98a79add8ca7d86e35e57d81050e700c2291a02a31ff098d61b6d2e45c3183eb50fb2ea80b7060a75b276554233aa2d833393ade3c9c5bd8b07c95bdc2d269e20423c61264d1bff80d9bc230cc721eee1f7fd8cef687b4044fd490be55a89176ce2fe59019fb4b1d1934becae0723c5963f9878fb46818c9aaf81e705a57268dbea3299179cbe9959fe29de8a528add82cf1dc79df80847582166bb4213b8f1a50b7f763f1e88c9064ec4077957224045a0eaa9d0620655212ad150c2bc967cc25414bbb5b6f730031a037e2394afe37fbfe7a03be1f9e4291abe6dfc67075e09139bce794841a96e165986d272f694671b1664747ffd419ce7a59d679c055d05b3f26b791a85a4399a54dbeca5ae318142c9b2b3b7b149e4e16c7227bee48afec8beb03fa02947d426d33bf34647c1249e794ecc2fac7aed227784578736b569cceb7a406919b70b7e8490bc62ec0d8aa3049e0ea26efa84ef64565aad0eb6c1f14fb2baf7060082e5526b93332bb98ee18f3d143663e834c83ef3348f552b3feab04a4b878225e3d0b18fa9f370924b657b29649b0fe1c838226f1421fbd1de9807669d918d8f3f5787d5e34720aed915daf7904ec3c947e19ff55d520cde811594db7825607931ce669339872b8e740c7d81dc91ffb1a3f7cfdfdc8f1542678aa3427ad07972d885a74d4fc4c9787aa94a6f0ea75dca06cd5bf27b66061027c5f462ff47225d750b735abf1727a8c35e8c7d3cf20895744712829bb41bd0e803653a510e26c4d6cc4d23adb72538a3240528877be865b7c01779ff3649e7eb750f8d9a0c044beafd860c61672b97a16cc7a2d64511b2dcebacf757b3e7edbfcb096bd33ee463188c555407571037a895e671ff235549fd7026214db17ea756401211ed3eeb4d9cddeacc8cb72748dfbfc5b5be7f272c11ceb60346b398566e181f9702c80fbc3ffdd547b4572d8e70634a271aa38d40a92798c1c904a810a5fd4f149a5a6c325ae2043832072b73fc325bcb14dbd691055e49b2c3e81f89e26ceb951cf9cc55465f10362732b26c4fd9e168daad9e0d674c90ac554cee13fbd10d00afeadb26112e7165774348daa0e20e3f69f896d10cbfff13098a79f304e799f13c0f1c3bffbb5a5536972d512eb94dee533292c0ac21bb63cf3fc7186bd4bdaf0dabcc995ce65ee5f0e72d8f2beaa07807102e055a6b1ead82f5283118663f416f58f5d8a14c2bc4bcf72d57c09477579ac935d4d1471663d1c95e20a57ddda15fedd9abc3fef56866b7256bb2f4ae6f4d40a8263917af10beece5924c597fc8a50c8861164a77ed0fa0774233ba7249c6103a1ba5c975a86710b1abbcc5ed84c7b700452eb9748a22b6fe7b41aca742523c6d3c9272834508c76ba50f8572231383d6248d7bb12d43617e85fcca9d35ff1d8d7157eb1b811481e2d520ed0efa8ac3c2c75b5ee639aa272c7164a2b8d18a026d6062cdc332004ca51ba08bd56fdd979ad6dc6a0c3695472ab31ea8defcf09fb63e4034a2ef4f015a5f60693b7fe1f9ff627d95c27c4ba5de7fcccbbdb56ac6f287f3c16311c9d0623125021cf0c5dd25be6ae1f52ce08728c4fa5f31a953373f5a1588e572af73febbbf5e5e9453908d2e1b2b2677307728bee77356ea13cb363136d185688423f6fea4c00fe4ab662d44882ee261e70726b0322cf40cdf4e0e821323da1fc2ad2cf128c2514346530c0677f134c263a72cb9c050592f19668ca1ddc498240c96a367ff33ffd11c1c073ffb48a12202172dffe6bac61988cf9c4e9bcab6ee6baedb02ca0c64b38e4e48ba292a86be7843fb3019451b5df3e3c35c9e88e1147a5bb688a1b2bda71b0271b6e3c48ae12e172dc011e5b679f2c378244641ca9d2258b0e31bc3d1ad01a0471f7042d35aa47638697c5615e39b87491aac5d96fbdb83b6991862e100f94d606a7af58503b9d724ba9a6a92bc6813811cdcfb96c64fd193bd2bcd44dec44ef187f1ba2db0f5648040f84c05b4bdfc3cca0b00e3a7626970562b5411a2c5d9ba46eae48056d3529ab4100f6e197403289e7d1ab69b185cc8423016d51653fe33345b61d14933c72de2dcac067d4c036e3955e9567f708979b6639e779a93c8051193b7d464d2945429a6cad488dc66b60b98cdb3f310d0b6320e1c4517b327b7170b229783897480fa4899256623e2a50bf9b3747ce3b924520ccbb0291888a4718c04ea85ffb7238073080f7ffca9cebe86716a6a242417891b05b49c8cfa91fc601487ae7da0c7cb12fa4e812af008db5fc52a0e6e51a4f928790c54b8824f903e3dd93caf071627f276cddb47a8b2f348004cbba1c5c2bb4a063503558db6c6ee7b076bb1e4f6053ce69e57963ef1dae2a06f694098e78ec3098d7f118cfab817b3ffd85781aeca91a65f9fc84c46afe7fa806e9b50d321886b22c74419067d322cef54072589fa4f66231e8be373682b74c174237db4220e18e76773c1d893cdc8aa3a841728ef614da5bb3f240bfe2c1832e50566dae3d66ab9caf98d3d4765ff837fc092503e522f7bdde4fa218c5fdba56adfbf22c12d2d1690344f10cb195bb90baff30acaf5bb7c5dbc915f3688d91c50eb04a856e4fef69823e4dd3813e8eeab1d250506e8e8fcbe6d7a402590a0caab2591265e46120f86e5069209984817202966d9d5ee196d810b9028db2d179ea4beab179324f4ee750f34d02668202c333e7727a6ac87578d4aa6feb9f1ead971f23fef96555861130b19cfcb904cf9f50bf7288dc2a3cfb11868928a5526dd7c6c0be91af93a7b7dfef4826b3ca991c223672631a35d66c57556703117a854d3fc06139cb2fbf0350035363cdc5234a36df1aeadc80791ca11e09833edfb7771d985144cc9308d3e22e7bc711aee23b3c3314e24365e5c8f245c4820679b4bb5ed818f86089b31326b1a4c88dd733e6d1fb3d5fd0814c4b2dc306b22667ec7ed8524bafaff2e466c2d6aa98cb43892efc3a6f8e65dd7810d9c2a296a247719e66dff54bbbfe86a06e3df80a259d60af4b2072a19111a443155dbfb492316a596bb85082f2b1eafd5fa44e9f634de08f4ee872758caf829132c76fc9b84dbaf5d604861842ab9296bf8065edbc6c87a3f5f3724f76f4dafac81d84bfba804b91429cae6b42bbb8ce9a854535411f4aadaeae69f76688bd7e1ceaaaf9e9d836082aa7777e169907cd074db7ff93acb1798f25723093ceb455cffeb96c1fb4af2a568b2a5a5c75a94bb2b8b7f5710fde1d14e92fd68b57089cee697d0fe5c161d5138744432ad2a94bd058e1513c55e8ef98291d6ad4a25f4f407b86bc57bf67e1acbb5a6360c8d25a1154e9053f494fb11ff04c9fe1461582f347f4232cc14865159a77715c7520e67f6bdd6f6f10cfbb02ab720e742b274fdb260306f1f87953410d92dd207b50df3d1cd9c9171b4e7ba550723cb62fbea86c37cf3c0a9c98b7f9462e4ae4fd56a72d11aab9ac91276ab0bc7221d473e14b0d324ce578dd7c19bd2ed9b822d1c72f396498cfd2209b0829c172315cf97c5089885372b454940caad4137aeedfa268cb0a1eb4acf8e06c496c2771e7116d79f910c5bf1f93d4e2e252649001227ac3f5ee4a39b8b187fc21411a03a5006db734dab3c0e999d7525d3ae287a5e6f0ae248c5389cf625758cfa036b23791fc52753fb0c6678e51cbaa8738484301165480b4e234baed11ea786f7252c1161bf15a544b0cf45c4b5effd013da727700d13f24c353defc5e1e92c214ab95ad3169e38565d16247f5a845b46db29b5c036c517f1a57aab79133357616e65516afbfe98e44a5b86dfab147f8aebff9b95aeb7fcfc116e5f4474945925fca6448277be5fd47b7bcf1e4129895c24672e70433dbcbb377b29fc6257f5056de82305a8bc1c800d38563d2ed1a69364346b9d4bd55a8d7a313860572642e72cf68608f86345f319627a5cefa53f8ea18344b458d927f9708b15ca115757a695cea4c730e49725552ab4dc2590c9edc63af238825c65de5b7dcaee30a3c2f727ec5c1b3986dbbbc2631c3fb49c62e5e3c1b86f836cbff971b3af94fafb78e2d94825aff1846e1a23bcc80ecb7ff560e907f732e84d2d228276901d83e49427277f319102615e68221e81a8184b9fa70fe8d205c1c52f36b5e13dfd2e4dd3f7282209879d9bc5785bdac5bd4d37b08476171dce885570d2a369f01695f4eb0415ce1ddee17929f8583c0c3fdb5ea6bfd4edc71000945cc7e85b58dffcea1f772ecd24cbd3887b0918dc2b8ff1b9c4d5b596f979717f368394681c20d55b8877205910154c5db09ee02f9a9de7ba0dd74b853028f2366763cd72ed7737b46e772f64c12e209d07c31020f3b9cfd6a8ee8e3f6c872ed1ff041e30ced5f5118214da2d07194323e5bba429859344a56bd2ef2125ac4c2a274c1a275389c7d88e33cceb47fbcca6f2052a058adeeb17bf57f830cfa53693ec48233294d016e30d5729d68be5987189350dd3e4ac6cc6cf24585a2fa8db8d0c92d57c69b1ce7950c362bf326fb4170621bc09aa5418e9ee0028da90f140c9d9cd744039616e856d7496230731ba7cc4337c5b9e55e8cd7f8d82465c6eee2a449c4798138bf7730d1729d4c16699ab98dd119dbdd778f2ee19b688a7b88cc94ac55ef05498e1a44e572ee59e567bfd169840314da58f2c4c1f6700f8d665b6804a4712f41579f190c72fc611f5442f52ce20a18d0ab3bb2f758b1ae88f66f6a9cb4e074938d7b14bd7254ffe765c7e842ac546fbb0770b2c5aab9975f1ba97748ea8b44461c47a86102eefe9a5fc111deb974c296f91562e99139ab40929ef0e7d914181ec98ff01b72622c97c0f417b1491ed4365965812fbf46a7cb2c681a0a7edfc132175c29784787a789e375e438e06d8d617fee35e1887ad1e5f044fb1a5000f95ae579f6256c5f4204be0f7434aae08763babae70fdcabf1b8436e3f9c0cc545679f00d0243606a553772e30e2251c92086535be53a9db57c88056d6a09d8db333af80f5261335eca7d919f0426e239cde15e8dda6d4fb00ae7a8c00cfe63951a1939b99a52fbfad393794667c3660b3c88123b26b0c51d36373ed9c4e3cfd0bcbd7a754cb72b64ec3ee172a506ea8f3eee67a52cb0ed6033cea76db1379c8a224cacbfe5072d105c0e423474cfb2f125312338dac6dcdeec5c2465cf3d9364c4d248d17f572300da44bb888dec07dc71125cc4c982ade4acb4e9ab97aafdfda260a3e373e720d0bf1c712138c620ec6e77b6e67581d6e0dfb7668dc5d2c18c88ef62fe48572beca4633bafa2c57a899e2eabc89d6e7d349e9891141abb3ba4fc159ce1afe7273300bdcd55e76fd092134dbcb8793d61861ee00174e33f033c9391e7618240b49b94fac1c0abfccd0c395193702a786577b6a15a566a6188a9a10a94ff44a72b02930131650f806cbecade5c1e5680dbc88d0fc9d2f488ca052168d1e3db46a6139707a9fb4c4631f939c1f6af457da5528461109360a3b75dac9d4535d8e72088bc5defb58e64fbbb5cd7f560ed33792f94abd2ec4420fd07b1864bfb0a023de7ecd8b9761aa4db27e96d6481b1836f03f387be2344cc0b78e497b7ca71472380a55fcaf2e393b11e04ed99c94e1635078233a20d4a203041a0ddfa195c70155a58ced214bdebf268a3a3bcbecb6e22416422b0a6b362d13ed7257647b2406f43a4dafab126f2602191c5087a315cd049b3d3893c1ceef46754efe839012722db92cd5f2486658fd31a5b0b744dc96b696ce8b6fd528af2ac31ace0e57df1eb39edaab9a2524d5a470bfff21fe3187b6e2db827e1de226eddb9787b4ce20724b14ce072faa60fb97c7b8c5e652d14d8de8747664dcf5fc6adedf979215fe0ef75c6dcd32f5955de0a6938367e1502bf405493ca0b250e5f42e7ad4955cfa7206d44da06a342c899a12dc08bd7bc29ff555feaf20f530d75dd6dc999a3e7a728f19108306551c944fe5287e3715619bddaf60de0cbece4e2451f861aa86b872d9d7ea3ac074f5a24b7dabcfba5bf39fb63d28d7633130a8e907b072f05cea70ecfcea024ab3efbd3a8f3ab5ff6f5f95875a5b000c4fb60a297f6a4de8fed342ca611e1db71c777e0c28d3189c2294a7a061de3c950339490bbc135e2f309d720b9bd9dfb8c54dfba4908e6b42e247c15cf6547abb1bf5aabea99901e6f192036ca898bead1493f5df7f5b4694563b75caf0b27efc858e0bad9ead27b1b4ce7048e51bc86d876abaeb91bd02d6f2fe93abdfc5ab83df464cdd14826214b7c33ec215f17b7529f98b009c17a780e3664f9c9b1406c2f23a28eac10e19ae3bb024a42946251ffb0c57f7717469a804bd889cd86da313d0713720c85905c82d5d72c37f8f7991add0be547c5c75cf613dc13c326a045e0668c851ee630aec9ab87219221406a27e580faed0e039c2bd2a6d6494489b6815d2d750bae76f6e068901e15905b648835170cdf55835ed016db42138e2204d507e865e543a451903005f414f9886f8b35196ddb1c46194b9fb967e1e5b3ada0bdd09696a38dbff244c7282289e6bf6b7df9a7ec402245f2a0eca6779c87f99c54bfc876be01b7ef398725017a315a6568761c7c8e3455a948c89514dd6d8457e351b1eb425abd5080972f5d81007df5202f3790d360baa80c52261affc9b181019e1b7858155ba2a6a0660ce120691d55950777356b7c94851812983ec7e1f8be0f7b2f02892cf4b7b5bd989d66eb76cc73db02e76bbb37a65244c8c57340157982ca3ca711f972a562ca002d6be48b3bcdbd10232cfc1c9b04b31333e9fa2b3882d32fece1caa085b723aaebfebca2c70aa4e6fc735ebd2408936a7a67af5c1676959c2f85e193c70725c0cf9d19df0e4bc1f400fb1fdf954a0d8f949d17332be90715791a79de45f72015ee2169be21f39501eae3defacfae8201fdaf2fefabde2c434b47b59ca86726e4bd3a72a83400fb023d7fefaaf02408c870871978e9ffb5305d52e755c0d72cd4bb821b1432a953cd55126e7f5e0759e353497fa508da1be2055bb47457572ae184b8d3988708a3a1f9fb977a4fed4cdb9a5e2cdd808d44e4ca7436a6bfe725f04f27066df3fd163ce172e07fa084c7ddd4c68136abdb2594a130e8affcb725154f6076afc7a08fb42562fd9bd6a96959574ae8f6fae6401c2404245230372c09e10fd4c30ccefb5e1d806be783b0ce9964d8c1c1545e849cce282d8f8b51ae55e607b06889d133531f870617b55335093b7342db4784e4e9ea49579d04372cf6fed2092b37f76a8b27b62ccc5fcc076892f26b1dae0bfc25b0a3ed5b6cb72486a162f9f26a5ef4d52973306bef1897a6e44dbadaa5b430c4dd4d4ac95f071c80c6af681c3be644a7eb8d19d66e54b8cce8414f3c4b8956acbdaadc05a44721d34a84adf07b55c7581c0bc7ca0e76f58eff2f853bd551635d47d774cf3543d1a7135a0ea31c331014a40da721d5a2d53ae1a6962d71d021f5f0bf3c8577b41f438fd4f3277c2b07a116ce023aa993fb4f2ae072d076641deb241929c9b1672f2339a19887488b5d5b011e0f9de32acc48fd128a287c576508fc397fffeea728a48a9abdccbeacb1e82b79f0bc6b9b01eb73dcd84d311f77e8f6432bbe5d73e1ac197627698498e30398c973dd0e76b8253edf728feaf7ccc99661e6106c22a0090ce6bdc86b9c72919652358addd0a28518c2a8351e6233eead8a477fcc802027d80445cc10e3ba5587757467ba4de07a362eb85c1b6ad6a80e638ff00c04a515e54918c7eb8603b011a93bf7f4c471ab380d0f0b4d99b6e722c9ca05983725fe7c5ec278c32e128ab40b5ee9d23351b23360aeaf377fa1577a729ebfbba72438ba9233afe6efd83cffa0876edbc3fa0a1c74d06703a3c6707f397685b190e9ac3b3cf34184c6ea5d8fd064e7be0e0aa0f5c256b30ede947d62f834d201c72a03491a0aa67e5c8f704e7440a4d09dfabd9c7ace7283d4158f21cbe764fc37263653422c04d28499fd4485c68a01e191a423a7956be03404d2319f1d6a94342828205290c178f876b8deac00081895e9840c30ddde93f9c27333dbf90269f723897e3893a6cbaab92aeeaac3f2a4ca2db2f0023af405abf7374e983113c3d729baeafb6b0b30b399b4dcbc53fa824f1c34c2f2394bdd25f2270a5f30cb272720bebfabb31cc829b5a76aeb9e838368b08ba4994b447e854143d0057577f45720a80dcc924deb640d73f6807141e39de762d5d540e8181db4e004ee973433d7251151c4052f1a318a30b499ba854f0456e648d2961f03a2b969f9f86ec9fc40066f852bbefce03d49a435baba937a11599e5d22ca14d9008938b3159418d4772ac5eed99be6e84bede27d908ee81731cf1c3a066c2ee8400fc29806c1bc76464f7c78a071f6990ad280de0bbf3626ebb5dfbb1a76b0261dedc162b938104e83b8939c8e8263b7810c5ce106e82f1110ba30aa0529b71abef7bc13592bfdf0e729f03180c3e36efd7e3f6e3dfa1aca700f935b0d9d2ec493926c2efdedc6441721edc5436a380f3e44d20a198da7542739741bd0462020e19074ed3f622711339f9525fcc85b7039251743307157cbf9634001ca640eacb3634157ffd5e31405cde73300e2876056f3eca08d16496324565266e122cfb839210e081ff5847aa72b880eb21b7b2e9dd940257c4f86537f6f7b6e9a63ae87083e849fc41e6e7635e58c96a8708cf9cd4ea55f58ede7cfa8b0e3080ffe5d3d15dce3aa445a41f1c7259a8de33a116674f7b7190985c209cd0a25b427eb1b1112fdd1362e7d07bcd01eb6be99df42809cdfc4a86ecee7ff6afa5fd84e203c7af15d4b6533705fef472d5e4ae6e2fa68f5b06b362619d0d3e6fef4037c1532de07766e400d6424100519c860e04f8135284c28f7ba3d4255e5af35ed3fcba116759cf1ca9330f3abc5975f728d98eb6c3ad1223d4e9844b6270815d8a6845e357e38d33fb9fd8d7e372aeff161fe742f5684cc443196377d1f324b11e271c32f0aedb72f8b00f14b325272ff94ab2930a645b05383987cadcd13d579eed8af87826d02197a1096d667222b05cc8c3d26967e7063b3f9ea209ca321702d12ae87fa3168571207f519c72696d54ae3652c616c49013ff39cbf94d8384851ff7cdc14be2e4aefb96cb967273c5ca08f1497eb912bcf9cdfbed70607d8d7424d08f648ca45a2da0e67ae4270cee061619353e9e73aa34ef9e84b07455606b4ac20863c9d1350044270409624528ce0105b11ca9747d16c5e3334961b91af7bacff93211bdc88fc6c1d681723004c52c9acdb6cb607caccbefdbc47e44d585342587a4da284bb0004cddbe438b6f01ebe464973d14b3744354fdd84e93a5b2f94f5075568bf1e24981cc16721af4ea87191e358503c9e4740a6241a7d92750766422a9e91533963ffca13e726d2638c5977bfd681c5d984e0fcd988917e4918a1a57af791f3c43588bcb703698bd0bcca679f9adcdd443e26e7d78cb7bf7688dac2a8f5eb3b0ce9015a6f15215bebe24d1c2fe50693ae8f206a9bdff8573c55f92e4808242cf4f42ca58ed2242f292e64909a3fd522ff553b47a26efe162c3068fc82d655b90cfab1b39f572e48903d06a3daa70e382317433a9135a1b2597257b6dba2a464ee771492311444a70bb355e144236a1ca2a41b82ce9465b2215f033be2f5efc9e07953cb714062f426bee38c64a4822c395807e4c2bea8ef60d8fd01432c4c0861ff11390f3723465ad2574935cf22f451c946a09ae88ef5dcad3623aab8e4ca6e9016126cf65aa43a271af4815270c97dd05d588a0646b6897ff792d03ea35fbc8b4bd1c7e048b77f555898eae0d2e7552827444f913866c524f8488e68284ba6748c2f80c1e1eb587a94149eb1b893402848d669a9a2d7f0e2b17d7b338ffa1f7939051706c7225ccb36cab83a5e0e29ca87367afa790d3ef0d766f2afe72e18c486d6e0b72b12f618e008ad8e2def4e9f5a4218b7b1b60eff21311b35755add18780b2a4727a303dfca8b406fabf3b9630bc4d079f0cbd1dbf45ffa26ff2c2060113d331722d01c84db07e131b6c9691e7d656a1969aa7cfb03166c6c80d07b5282322dc44b036f5921abb458894215b4a5096f79e46b321a403d738ca717c38273df61a143ce06e14796f8671c83c5d075c5ecdf2bcde304b76b1aa10ffbf8b9359d6ed5f173ba377d225edc541f2db378496a45a3a6b5bae7253c920d5d0500b46c080721e734bfed927de0efd9d898d71a5c0ef1e18bfd941f1ba0b9e9130c3a639233578e90086029c03128e36e4fbd55e5f189f7b87d9c394e67621e2a6cb4e8e39724807f36c91863c3a73acee707d2083c77bb1bdeec4b491fb43511b73742b1e72732cbc3be8ec3179e0e68b048181eee8d3df1d2c1ad2ce20291d87293684b9083267671b86cc9a8e0e71c6477c6e7ddabdcbbf01abb5c08b9505eea941d69872069fdcae83d374baefce5950420888ad6854b8efd00b4548e24f800c83d7cb38c1630fff3ba057a698492535334ca13e6895aade2ded7b0f80c470ad043baf723262d60e7447901ae65f25ee3b70615e9a695fb38d2b7562d6b42a8ca9adf23befde2c537e8866ac922334af6371dc52bbb86865c7c89510938e97f49e944e6760779bee0ff91d03bb7ec4e86817db3892c6732c7839c8d60c324a0df41dd672598607fa82a3595bd0532e5c652f1f71a433b8ffd9d2a5b3642834d191fd187260d232fd8dacdd2eaa03c3b8673b168bcfe7a99b96d0d64ba463a5bcb14c1164f7714605a90ecaa88fa8da9f5620754dae6658fb4a00cc0002d8cf1826b65772b0a8392e9ae38b844224785b00686f3b25dd6ef2ffc85931bfbc86dfb2c8986b6eaa270fb7f8bbcfc39f293e616ac8757c239ab986a377ebbb5292627fb1af6e0839ec01b69c0befa2c3210409ee34113782cc592f3ec031a448d8cd635531723aed9f260d939b64e1041c0a721b0b9e1495af0da556aebc61cd72ff4fe3cf7295acd026dafdc4ed487eb0e0779a62ecf4bfd03c9278091c35c0643a23328006004c95dd5fcbab0b00c54bdd69900310bd4e47aad5107e8f1cde54bd67e6047238206781ee49525e45405e9231720581164edc3aff063242fb3a70c428b81572b7787da89472ac1f4851b794b2126c97a4f8a035c0eec2dd4f73ae8484593972bc3b48a907e65a41def687be8f5a3bb346fd644bce20c016aaea2de5cca1ba671a5c29f4ff21c8afe61b222e86ba92d00f4069d4e506f0f8797fee1f3b0c1272f68bdf220974a5ae1d917f36a188a4869654056971a4d6b21aec4af8fb2a8f7203e6c80650db63eab0e2a573dbb0f6d858543edcfd8789004b33a8209a7e4f3e9c617aeecd10662063c2f500d6d8e0860511036a08e5577690ce1151dbd46d72eb203f480b9a9c781989706cca360591287bfefca57f620b921df7e2be03297216bc26a3c0cebac81c47f517c06c8bd32f5198addbbea839e65a23355238537247bee0e0b1ba3ec9c96910012d486f9cd6af48304cd84981e5014acd1d5f960a017844b08de081bfd9b3277bc1a9081926215056bee990b52d82c100f61e575182e90cf61d49fa76547ade127669dd964d429ebbcc3d2c7fa03b8d2a223e76721c9e1ecd2378707151e7b98ae090199db5ea5ed2a38b6e8713f325762082b872bda34e61e69a386fab966728dd58c1b616242de32b3e9e80d769c5dbc6cb4d6c2f14a464a1e35fd4f8c0c3d81b2750fb7088563d4d2ddb713c89f9b814399f728a0cbdc309f0a064caa9bfac645ec9431180a8765970b3b66461cef6e99cbf725f9589777096dbe93a92b7a5e948f3179133c97f7b23031eae7cfb3340367d21667e92bf1279d2146ace501abe3e6e668adfb93c6d45b796889ee6e32b4d9a72b0ec11d5da581e994f4f02f7a3772f19c0a95ed2d78383fcde06c204a7a82a722473624a1d434f664afb1bbfdb5513ea24a5f0bc73e2af15e859812b657e9647551e9eb4e4899c5f3488f75737a967135ba1d1e97ac1fdc05ce127f959b22572fbacfdcb87a84d36559beda71b6ef0840dbd1283ec858efac121d4a9c378481d2792e08ae2f4b15648b8f74790224cd79357f8d83fae3c3b44be12d859519653f1bafc9e5d6aee2281e9ca2282e3c76e862043fb15024d952c316702bfb9a67215a6c2fd61c13edef71887ef05fa4c59a8ae9512da58e15c93199c448541c918a3644b7f64d193328ef6c5e52ea12e19b3c17d0bdf1b4c129543f41fdcbcd172e2d29e5ef15e11357fb5241ee86253af2ba89128a9dec7ba32951fc79b221a16d687cce5a8eab9afa9477f5646acd26db818b68d469779492bec5d5275729c72d612210fd31fa4c0a506010e09f71726d9224674c88fd3957d34bd5ac4f47c00768cc46f947c26ae50feba5b880f64bf8539205c373bfb1317e7841697b56472991074ce06e5015c5a31113ce1eaef5ff3f41e12590eaa76f34a98a6af80cd72f1cece6da0f43ace1f8dfe29becc2fcc4a3aaeb72a2610d6f12d321b95295c232d4fe920b7fd212c548f54f932560933d84f715b47837772fec9140a6fbc7e72060b92899f3ead9a6d6a3571569e00b64806b1f2f7b2cd91806b30d686ba1c729cc0d7dd7a6d6b5b29cc8a5886a9fe6d0ddffbdac2edf73502b6a5f5d031c972c27dd30e9ab5fa1b6607d3f3d3590760fdf5f4ff12302736cd946035ba2d9872049e7a7d98c64d04a214504bcd6227d1ac829cea38fbab95c14fbf7aa30ccc7217ed621f0274a43064c306e323877c0819e3aae2b641e3ed5035d3f5707326094a9dc2ec89caa6d4746cc2dd848471adeefa7497b960b0293d51eac68bb58272fd3c82dc6aa6060ce5a00df10f004a65d1d4fe415668f15bd8e5c4095b16c66ce4d32847b964d43181b3b25cebcfb725eae8afb1722491720a706952d18b1c722fa6a37dd96fa4302dca233a8f95b4557d8e3602f32252cc6397dd7ad0216c725057615e810fb0f82972c0349c2fa56ec384ed52fdaa3c66a9d364dd5ac4b4727ae239ed2ffa65443242f10a005252ab2d67b87acd3a16b1e59ef89a353a0b61cf0054e2778bc3c0bfd6678cd449240a976553dfd82fc14f4b4b6024c888f272c675815b4ee6bb4a86b90fa0a5771addf0e7060d84b778abb171a8ee754cad72123e8dd490ab90d2fe090397ae7caaaaa3a6bfc1983abc7b8903179f51ef24641d40ce690f946f33e73e43f92435099920dbe308a2aae8c0c1fbb51a86f9f3720e2aef9f637f231539e03bbb196dc5bc2870be72500fd661d1ea502fb5d0b2721de25eae923bda741c5fd46e86abd5a725ddfc8bdb6018e31d2f770ef9775a72d09db1f197c4c218168546bfa20b251b3ce9fce6a6e7a1fdcdd6802946cc306bac68cf6c40679baf689d3b300c9b48141e4d5caac855cf7aba3071976da10772431a8ca337f8b785d27b90da8cb88c6e69b2bf9002de9ebcac3ac3017fd3bc3fad8949e0c5ad539343e9b071a145af112a379d44702cdf6c88a6eb3949ee5f722715277393410138253493d15472ebf10c6a708259c32a51d923bb464e499f2faf34623a3208ca926bfd7747a6b3042ac8dfca98aec931fcc8838bd9127fab723928245bab0133b6284b532b3dc65228cd2e8d7b23156e32ed11beddc7241157ba46796fce5d5d1e72d4add5cf6e625ef80151511c08f112dd0d93228e37b219135aa4f154c7002338792bb930db4ae5cc22399eead5970dcc7fced418757b72230aaad41d161d2175f73ed7541135c37723527fa64afffbbc8f4319df35aa3f05152a7ec2bc361ee09b9f74bbaf9a5c799cc3a1345c903b74c120964e6d9072b3fbeba2c28bffabdaf1dcbbde064be984389a1b85ea7d176b7c3f70fe584972c708d00a8df3821970cc242b2797aaa678d50f50ca1bf4e2cc51415e17f92c0b12b7b8a33239396211ff9de405161df757c91921393dbe9dd0969fabd2c39572f65d70cbedbd9f48fb043c0947562e6310a63b7a34345522159dfaa6636cf9720d7ca4afaea86a5cbb0606eb38fec3982ab6b9c37d2b7ff0573ebc4cf9744e7203e6b4ce953d7f4d79b377e85d15c69eb262874f5a5e0adeb206ec56bb573f72743a8d94afd5c806c76219f97b65d07efe7bd3a7d8f3b003dc5936d6313d4b0c761646084ddc2a17bec819f49300aadc18984c4a71043474a18a69af3f13083152545dcffde779ff21c3aaba076cdf18e82c39b82b157890c7ba3a234561d504fea1557f91f4724b4ea52f12fc19932e75e016a39bb6d292c895da620efd7f3c84be5fd4cfbb8f0089080447d8e987a66379a1b2d65d845f390abac56669b8726d3e1a73788a286c830a996e424f4497ce671a2138909b4cb6db50b090875672379508d8b04e237d8cb63c36862f7a4ddefaab518741d4293b407bfc54a66e4ba64144699cfbd2f7b55413f0e31afa5bd0490914226f45f93b3f6efd2ffde14555b870c64eb4b0e20248afb2a7654000937cdca997060dd68b185094d2ba0059b3976bf60371dae13f2a85ab3db723e5821e444f51618299697d42ea57d71d721600d4aee1a82bd9e07385952f220a1bea34e6701e6ae1110267dfad950d5128e94100494a641f0ef114e91c009b78a4450e9abe6b69c5fb73b209f22da8b8171b224fd6e130feac3dfeda40a1b6f5db0a910374a47de8a82f444d95c2ecfa2b8bae49cebe0ee52f389b3ff38843e6d86a78e80e9d5b79e46e75887b5dca6f1a0737f9bdc8a2148cb4dd866b047dfd05bf4e6df415062b8c635969dd72f16c707ab538a3d2b9e6b76e01e3d77b966bc2f3c68e9ffc95a89c2cee8d2bf43f2f47645ab04ae12098b2237a194d8992c58d8fbd8669e5688b679fbf5a6715e56872244a375e1b6302f5799d12d10304b38e1007d90a38d1c889fdc7b0b99d10285e26798642e8de4cd3a80a98e5cdd7644f2561908a9075fb373ba4b26a4a61534426d32b62baf159800bc1393532dd94fb92392b7e259fa0a8b07a7d0dbde95845e0dbf21f67b8a5bf677169c6ff6c501a50c5fe39a1eac71c11a3777751863764d454006b60028beb553fea5d2a6f94594ff271b039cc00631df8a12ea4173d3bf26bc85bd56ead51ede81c03e8c4d9e4898d873ac891832829563ffc6f2e443d62dc6ac535b3ca0864e3310813994b37219a444dddf23efd0fb1c5eb7bf1d87254aad8efcfe6d130cc9f106ec209214d514f949e94c278f0b1b5e523dabf1909310e85e4c65863e7abb0f7d0240b3ab9a0e73d20a5c9f240880c83987c2901528860cac1513322acaae4c56369048669668b1b5ec5722f6ac9bfc1d5a92835724fe703957943b90a3c80fc38340ac728b6550bb23c72b30886b821ef7ed94972833fca7414b64e31ebf9f1f61bd9620840f520dc643bd8288c40da30e9285e728976f12831ac17941ca338f0d288f71469764eea2fd77ac7e03a828d3af55472f4bec34d0833342e57036a189d465a782a841041fd030793857538ff18856743fa95203306ea5cfebb41c5923161ed64459d64d1dbb4df334fa4575dc1f4ad72b19e643c6c473a3935011ffc562cde02467e6acb90b41bcba064363d42916772b9d0bddb676884c0c346d42b9abeab152285499a6b254fdd6b970e430bd70e721594624ec71e1f9df07100e02eff58e3ad33682ecfd8cc21d3e4f2bb250ae072177614ee0b4a3004aa59fa22b7be9bee55a2be0eeeaacbb48b1b474303289072d0f9de35e3694db957d13ab0b5dbd401999b9bc1a61f230197ce68a36f60854bdd16f1c3f2d60f1d71b6319b47cc1fb75d7723fbbf25913e871eab8537085672a6debb8bcca51ba5eae8ee06abf5e9b05960283c7c5e815551292eeacd318a70c425f68809229fd9b396c35603a66c98a9f9fe0e122531ffd3398a35f1668a72b8aeeb290abbbeb3529b08fca842a20993b4d68e7beb855522573fcb36a2a1719fe3e9135e5cf63a3e16546734854b50bfc14d3f548ba1d9fa65f84fe0e9427277e0726630f019c6e24c4ff2c724b2661c869274e7481c1c81200f4d4e425f3f3010937546f2cf705437593ea858b9b8be953e4617ce2cd97f51499e4adc1472b486e30e400c32bd5063f1cffef71e3b308d72b74734076b1479c1619d97596e19c222880874363f3fb72c572d49f517dec60f49c7e41949137534194a054a728ad4f8c3c9696a37dfdb68bb20240bb96f9dfa81807f5ebf1b9ee8112ef15c68dbbc6e6739dc42d33696156719df32a5c04b096ce64fe9e297edf2984698e4200c31a663665cefe38bb26ff50edb01d2a2abdabc3e29285113ef2391487bfd729e9713ec6a5d80f444faebf3f93d648311ffdcf77cb263b598ea09a56a69677201217e82bb938d401ec1a07a544adc85d4dea88aa9bda145637a8b5668f430455717e0b6daa757b5fcad1d982ec333da14ab7376b1fc0d0aad7effdd969a8a5811bb5b72791531affedd81824bde889248314e2b0c1726091d4ddd8dd098e14562afec68f6c9f25285b6ebb5e6e26db792b714d78eaf8f7085baa23a38399b72362c0e1f00f487a4cc3e7bac85e27c1afdb8fd8af1b4d51f290c10e041c7f43eb25cc49bf25d15c9bd432743e25ac68a306ab8c8b94410b407df85a1f6a9146d2eefa7fa29e111dc413e31758c8c0da911a4e4af3cb8fdf6eee37f85c6fd7472ca8c11ebd116ba8241286789922495cb7d0bb6464814648bd4f6e0ac8c5636580de85670dec2493a3eb4daa93453fd4a663a3f1fda9fbef76f19b32afec91b72265dd8b795a6ef829de9dbc989c2f7a5a5cb0e5dc4f0e694d0ecb5fc9c84692ff7a8334e863f9da9a3a3d72f5ed8efc0430034ec0318b33d7de636ec45a55572e0324bdd83360d04280a9b304d15322340071aac741cb8fc641be192580e397203152cdd065990a50867bd8bed76fbe2da7bd3da8d8c497d24d13a9a93235712a592e1dda9855494caa75bdccbe58d79aaf8634080895ac21a484d8676e485596069cd781b8eaa473c06726792d9d612aca5172f99d2548d8e6e91c1307a3f72a6b2467b6f66e458020c71ea0a716495452023446840284d6de6ac53b10af03bb6d1bc759023447c0d173ddd7b2b1474e8386c6a31ccb34f83601f4966f2d3363938735bcd38066036174c9c0b6afff62af76afc3a838d931c65f315c5a298722dc9dcf9a3fd46ccdca739caacdf127ef39dda5f0aeb63ebea35fe867eff74268c2e612502bfc1f6467dc67d6ef006af3e32505dfeb83bb675018a46b5032852e7ed9fd244250c40c1de88598e8a318ba03b56d2fd2866aebe0eddda55026368aa7bee5a58eecf21a2003d587182f2bc3f07de765df06d1566f8960fcc8681726c76e17ea3055e2b070d84d402ef3b54b7b064ce79adede1b75277f9a3afae725e2f4d816adeddcf88544d44938ab2846e7c5d9c9f90092183f13cfd151c737236e981489156052ca387feefb111a87f6bde6c3b1c09452b38cf29730b2f6338dfa8f34a61e64183565baf736b88f9cb1e76754d5779736d1be5a82c9401584778339135ac5c0f24e6f63a637f74e00193668462b04e4a940f4f89afb6a34272cf1870303e6a13205c6fbfb8b9234c4240f322f036b9176e0b47ab6a86cde0070554a835af1e63c1f9a228e6a58cadd4de9f54fbe6d6106bfc94f12cf7e24872494ad66db1c078ff5e63f564ed4b26768044a8240c43ec7dbc38164a1d12b472c2afcefd3be21ad9b625988f5efffd9ed5fa9b8a11500a401751d18d3deb962f6f43307c9b4e87d801bf5bbbcea41a9f0e9d03e126fe0667a4f5ad181f31e746816b09caf08f0b0d10d5f41e1409043559257fbf1eca59da93b3cf244be83872fff617a91078f4ae3afc3df9c381ea3f5e365b39ebefbc2021affbdf8e7525641cde9917ceabfcb40df7c34237e07820e65f21fee95b30245315a761ac685b03f37a910e493ce1831fc0a1b78b835f26f640babfbf54f63e39d2900faf0fc3255079dde68dba374477dc5ea262b0ccf0e80eb32b3da291622b79302f15bf22722f6774b62e7a0e225a1a77bdbfc42d33036c239601358e2b134ae1d3ab34ee726d9e3ced9702f62d1077d235baafda32b56fd1d233d088c930137a509ae8304dd8247bac47fa7dad5b7e69c3c274bc36b7129e287b56d06659ea06f60546f7727bdf08abbc4e3a5efc468003a822da9d6997fe2cc314a23d12472122d03a3e16ff0c45b7aacc480e28c2bdadfe3ac4c51159631d9a7087d002623b06d2780135912b90b481a96c0d4cbf1ebf1bab7bd6a1b324b25dbd58ae24e13508a2d7541cf0aeb799bf594f9aabadf7e1085d976b551fdfd4983a81c5036aa139c276bc56f7f8cbbf690fa979785e266613d3113975a872db2ebfff25864444c6e465120b770e6cc3946d64c401b766d4396b87402ab634c7aa2aad76485f75d251cf8172f3a0f5df2a12296bb6d43c8f47928f0f1005dd2dc082273451054aac7adbe463b2858b3dd78b8f034e62df844b4fcc02f41fb9b5141ce330de0be63c74b0997251db97fef0ea4efee37f430bdd9df383b89e7cdb4903fb64bdeef869e5be876c975a379678bad3a242ab3baf5953df09c170381502ea9a71b58a257aa32b61720e5a9194467fcaab1146d068fedf2bb09fc72e616bfcc8b86c1eb0a9c3fe1472b45236f5af1e8bf5c9cdfcb5f398457d15a0ed872cdd194ddb033f88d9d2d172934a8c216fd8652d16ae45c5b4dbea4d1d0a634c5f367e982bfeeaa70ad0a541b2da1e0008e176f482d8bf322de9a8ec4201b3b2e81788fc1f52eb468be16f1f6fa5bfadd915528f09c33971c0a40908de990455f053c109edaead207d4db672093668f989eeeb96b00d25ee043564d113f3658acf62255d567362954221d9111169009d066183050389a8a41a04878fecf18c377f8fe21b62318ea6295c4b72c0911cd74b00db325f20f6f837bb001f144d26c16d23127c41500c75bd0d2272e6405b383026b1ee6138762af940b5062ea77e1b4bff5926ac5cecae98e39b7221ec55217a1f2033fdab854f5ee0d6f7098b89e2f673e02c3f1eef4c691fb02a37a1c334c1974e283f548351779790aeac8e2dcc61424cc8254241a0b13d7d72e87f3a59ed5d52073198e38381dcdcdc44b50d353e279d4db63d6e6f42c25a72a4c1000944e5d05581e232a91123e1939ed263d0a57e22675071af59cb164c726580e7a015f1c29652ed77d8125133cddfb4c6edf9c3e00694775e9a3ed812641f4d464c635d0730dd7510dbb918666446e0bd6de251d3325dbe47d825620268b57c3b1fe9fee1fa5945efda2abac2e2806c85460f61732f54c53deeaa1db77212f038ff79a0aa665a697bc83e3b3afc20ebad0a03f9173cae197e4b2fea455729df38bff29a7a6a4505b0df9784341a60a613a0ae3fe0df6e3ba3fee9176b0e4ef9e4b81ca2500c6fa8f913895cecb6f60d032dc38e8474ca9b77409a818a7212c738a44aca192ad0b6f8abb1abf09173c20caefcd47b7b1503ce2cb78400729c7b79fd1be5858c06269e0b0f0f5a0ace41af326b92901ec7fcabd4738d9072fa52fdc44bb188cf06f56af528b282e92d38adfc00484153db94432411a7c56ef2a95af3bf313ab08147bc0c0f5fa9dad87d4761b5961adfbff68fa5e7afe70361a43c22b2b3061bc15f6af3d3a505d38d7c4eb5ba4fd0c71121f07b3c130072ef4a7e83e27aa82fcfcd844c924563f5ed63f005d0a4025c2379709b60d2857253a23f68674bb8461b1f792deedc80472788b3de4e09ecee613609b9f2b9e172615a0b9cdbdd3e5e44e7d93c36f03fc8519ce456281acf8801923f5ebad21772bba449cdee4a85f561636582720b4badc5a15141abb0bb250231551fc57e1172f1f2fc947f861c3d5b9215023efd50864ffeef2f2da8abf3111688d7aed1a81a0aa006254ab9d108bbfc2de3eba96f4705f74ab874aa1dc61a6160b0552d1f53159c07a0852299a16b2f87bff5290f3832b774d96e3a595c5a25304adfe3500a099bcc373ae135f4c4cd297c9cc71754d94cd40f3fbfe5ab17dc8f394b32023937f21e1f4cd0b425de72c6a709161ed7f48c7bd99782b3513e2be082c4453272cc6667fe6d99354c16c66a1e789c7a5b9bbfc02d99d1c3b367221d04e74d5872b95d9977ce6725774bd543b9faf8a00137c1adc06df84b8815c9a9fff4fee3093039724f05a2e6b97fc18e761e9d389a790d7fdde9fb5388b068d5ee879ea4386d6c6ceb0326f99ccae56def31e9d7863fc244969e5bbf21a6a9d17e57685372e5cf85cd7e3d4cde8161b64283783144c555a2e4545e2e9bdcf820d40b505572d5ef40d061adf38c53ef18cda130156b74fcd016df502596b6833eb9e60fca72df173725c816a224ab5634f160147f5f97d9c88d7967e37b35146042380c1972bc84d4820f19287ea00e2a78fdd3d815c17ca35c0c6124e31ac5a7a6c10baf4396ab5e861524ccfb45f0cb4e9476a250b8ef125a52966d8f3894868e5f51975c44d3916b810c906453c6353716a94b36f3fd172e0eba6431363905d889b6d564642f1005fa95f529f9280623aab3e5f73dfc6b4d4fdf4c1dc25c7ee80b892d1becd24d2a907bbf50e76b7b84cb11085310e91a86f5a9e21c2160031cbc955d72831c63ff797147095966df423dd2f0f7d3b3d22d6a90b021059e77c463975f29a9b5a72cdfaf900e062102aaa272489c3fabd1ab9ed4bab7485decc147ceac726fc1e97c1d381c58d05038c8b06e36e5bc49bbbc2ee23a6b81a03e6901eb1e2dcca23556ecc37f44af1edcd8bc5cdeb4f12385fa2cb70d16d84a9014a16ec7727603a7f332fa2c2e3d5267ae809d0a9e9a7eed71a5886395c43e4877ea494a721de80211c3c3bcbe049f0d041b6f1f8211e39bf0b7e2f469a3dc139f4a83f0724157fbc70e59abc7f4293a8f9413357ec9ff7b4b43fc0ead77d5598929b8154ff781b3e84073c4f82c7dc97b17144516a58ab28b48d0fa6f882091dc67d85e7283a10c740c9dad9b165c066a9d3c0832bcf5e5633c64674bdb63fe6600fa0d726d94493a368f49f9d7dd1fd9605b0a77080cc483e2550f18b6a52eeb59b43855a5ab13406a4c5cb5faa30153950cb176f8d4b34d439ed83a7f4763ee02b3cf1a8a349917d0a5ef87d06440a5ae50c443681ffa16d8e7bad9e50f0793bb890a29d65c83fb827d6d9c0d3c5463e0ec3b49784e2368033e8711af05bf23a81e3a725196095dfadb6a57028bd82e11cd3d068278e2566db7b5591e6757113554df7268d140008f615c2aebd215d9c3188ddfd835c2007189471c42f77ecd32971705285f26bb73aae6e917006a63bfddeb26b0013bc434cde50d1605e302627d0726fdfdb2f05cc56d4f7993b9305a723751c8b86261e929ae3e1dfc74380002f3025123b65a433ef700ec332d853de414216813906f2119f34447d92ab3ba1556303bf4aa45f6d16c4d6c8fe85b17bfcc7d058ec9b6ff91abc37e7042e0e774dc5c7f32d7cc4ce483ed8edf60fbc641cb465a4f18f78af20a39832b855517efb13331839a6a83159eb976e6db3cc34a0df5a0226e9544311cdde73d18ef7527cc723497245728a78115f9892782c8903ebbcd91019105382dd26e0564ca91d81f72353287aed7562ef3e51751e10e02960594c1a5cc58b5dc6eb9aca2de75e7d30e654a86badf60a12c4b25e5ee8d3e7d283f5377a650e867d3abcc1d996a3d0e72450313d9b3b22ddea2a16213a03a0909b8dc766012e1fa555a6af30f25501a720a13604768a2f8a86bceaebabc8a5f4ca3925b24032b837dc1d64c847cca794bb5c109565b3e90e2e999fbcdfe8dd82609fb9357e030db31a68e6dae3bccba726af9063ddb868773cc5aad296d3caab061490bfbd79719dc79dd3574904fba22a743f5a63e80ddabdd103dc3a590851e6e617a7135da98abaf7771ba6b3ed817683fed13bbadd3b08e176b5029e5927d80e937c9a814f2653188ad5b4425a84a6e3b2fced17a9b990fdcbb5fdc8138b8447a8742f0b868109051fe0d03dd3072ed3f96a5318cb48550765a74ca4660d7a81393816ed68d5cbf18df4f8a24f772281c7cb47247d3d4a780b2f776fe17bdf08fa756cc7dcb134eee2a309ed1b86c46bbc372e32e64dff719e5b11966e2f7bdfe7652e787612d072b9207dcfdba29c2ee8ec603716747cdf7dd29feb46261e128dff674aeefd3282ba6c99d5ad772612ab2476c2d3628d76cecd64dd5f13f01e060b42a31a0b4e2dc80f0e3a806720c03a3649f97885368776ca0f0a0682132275aa401d6f02753f66e0d65879772375ade36c2d195e0f66d2cc698ff0713e93d1c040ac16a8264760a51cc4b1c729df0a89179d9543fa99c6f856811c1593a223d924e5baed75bbc475c4946ac1c800a305abdbadc598584511362669dc7716d2376b70f5a6b30a94f376f6f607291d4cf2c4285adf176ddb9da0f963728857a62cb3342364cd316bfe3b1e17772b6ceaaeac313007208fa2af3e1de61aa09eeb567feed66e826df233591090772be637c68bb7222222d3a97d22dd9ff72b79807b99c2f3cfea6a8a151f19a127237c04aea18317341e98f86ebce1ad1f35eb23bcc70b33da17fc097af37d75a72d96683ef30b519431b011dd479903c2416b62e74de9130d1c92c2029c7ae5b7208293b8ae75eb37955721240de2cfb661c4bc3e20c46f096f068976c0ccab472f245a2b71a5f55ac784adf8d53956563910b91771b23ce3c77369a426ec337726c914b1ec558ec7ee1e09bb0d0ba0a561983a59e5894b92a6fe9abc1a0235a7204b84c856c830c1f4ff56e408e17f020de4a18df7aa29b893f9f45e68bcf5172cd6f0b21a8efa8fe1ad191a853da52583f022d48971dbc4cc7ac95ed68ee4a7256ed5c8aa4999af5bfe9338baa4afc8b0990f4c9f6d8dc5101c7b607fd26892e473a2c4db52062e1f6027ae36fcf60cec52b1cc73039c58546fe9c5c78ca487268f060eaf8257da92f71bf287f38d6169d55fa8f6dd3858fd63359673c8aff5ade75b8f070a83dadac592f316aa0b48d0a03ffed58a9e612ae96131ebecb4457712489fbf092ae24985ed2d99a771c054d51134378dda2488f9722ca3a046c3c9c5fe950d50a6cf444db90f7754b3735433633a0f33cee90d46c50a79e8c2129c80b61faa4ec5e54fe35dde94229f4b090f6ec2973084a7538238757c872b172574b46eb88251a9cad7e9da6486fb842ba8d82ad51812297862d952f954f49725ad801c75d35c563f733c6a352934f96786263173763bf3ff513ec8a6d9500628a1ee8e363ff8b164c18b28922d0831abbc4bae14d74e8a864e51ec4478a0c72976e7268da57e2685af82f535d9b9eed8bb884680eeb0ea8913c2cd78e9b9c0bc65448efc0ca0ef81e6d1c355f81c3f0fe701e31739e1db5c9484b8514b4a6720fd0c62073212d9bd229d5296ccdc91b465387cc4d23bccc7d8c8c289b866b2ed1576940cb3afbc7004b6dba6c4daff01e0281a245397ec8e54d9bb4c89f4f20e2b1f3749c84eacb60d215906d22bae65395ece0f7c7e099acf24d595ed79b5f198d80f352a64c08e9d23e24f5902c94139b8964704cb5d37cab60c53d01a772fd64b90d4076b090d2ff4bf43611f1a2876635d187e0cf7afa5c5d6082ab13727c230ba47097b25a26fc2709594eb72ce9114dd124224c5c790a801d17dad972d0b63d4f110b628b0cfdcf121d1229c86addc1aedbc6cb67841a716c06345c72aa568b14bbc0d917c427d634734c581a0bcf4850367b1249a7e566d48582fb7214bc0bc47b6c75d1748d028235b313cdff72d52f190d6888ee57921cd070945ee7cc8241505f8ffbe71299d9818e878c72494028231aa3cb87397c2ccc429c729adaa25b400039de2274cc71954d9f3b7bce142533370fc3c2301f894fb32e4e04eacb547edaebf32df2940c56d7d46b5320e0dae0b8e330286a5875bffce016d7a135ccf2adf099811d350e9f80265e317978225333346242c2e21db2c19c72fd6a1f336d3482d5c6643669d7d6fafadc15278148bf7b261e5961f51b4fd04ec10b1b7155aea012d518ea4dd05f96eae27e653fc66c291ff36b566f9283f11880d92d6f30c1cd570f21accd6b50e9c2dda0a7337943ea54d48660686ef2157278f244cc74ad90009fbb9ace6ef59aefebe3a73c3257a5b4a35427c8c0493c09ea5b44bdaef707909a1f5ecdcba5221545e9d8437e1b593fbc3cd01b899c211322f2ef11b4f5173b363fc68e498724d4634e80561f8bc41537491a4876d06634226df1f04fe4646d633d3803c2d00d6f2f3afeb7644da40bc3838f4646a60224577dd8fe48a4e81885a818bdaedf74b00ffee80f6faee3d893b63d16c19b66294e73654d737a4fc52417f144074ba174add69e442088178eed37d0c23f327a48efbf2fce5d2ba8f9b66a7ea99c05a15256cd313b496f9230451e8d3d2da7bd7286b45ac19d474357f4f67ff55b2f364ad60fce3eba13ea7060a0e672236673492780ec11ea66a83abd4f71f4c8eb8994d1d76f1ceb5486d2c8a4ec651bce0503d135164cfdba49e4b08474e5e168700a5947c993c4e9e9c3a5609868975176728e38e791dc977273c7ef21b821bf67cfd3ac745e4728f3210a1d9a51a6eea872ea6d5b94272352c64de44a22be5c2e255ccb84c398391be6536b11ca84c5d272b8677d89729b2c966cf30429ab64aeada0fe38cf9a60e189ded5acf892bddf7212907894bab72fd74ce0cbd15c49d6470667eaaca2780dcb8b2920e3b1d04472585bbc10f0f7e4f4ed77334d12d7d51436f78a44db7f2a08d361d07d1dea7c72c6f2af4fde161b26e11f05c0d54e24fc184998465487190949ce6f22e8c15c2b324b0cfb198e63861008279da42ce00ae51b8b319c33933d895ffe6613720a72cda3ab0a0cfdec3d7c7d2983b293f1a0f7fa93c2dc17178b97ba45c4c158462c5324c51effe9707e4aa9d39e499c08ef21d2f2ed3c9bf3d16d78891694415e724af4c24661d1798d2088614a6be0ef05d865a2dbb6cb38fa255e7535c7ecaa72c95248394b82f45e9eb984974b3934dc83aae69cb67e5b763ab6a55ea18ea60053c0cebacb34397bfae28ae71c57f1304e3caa03aab471f206d33c1726d0e707124528efb76fbe665a2983697398451972738a61b50793e205afb46712f8d9723146876e38ecd0687b1c67db03c971de618e294f63bf3ea931889eea380dd672d73db33c7cfd174b0bc416cce9503f91877ce6303ddd949e820d697f9e4dc072c10ad49ce10cd1db49d0739924af506c5103f9c852a10107c47bfbef40dbfe7225d3d3772aa4259559fc4ac89e968bfeb525e68a4ca6fbb74c9a9c750ed9102be0987863c78c992450582ce1709a811b61e7204fcdaf1c48cbbff7854c0ffd2c611bbafed42374c1ece97a28f9e703f22113acf05afa095cc85a03212dcda97272ccf5b5f69fe3211f108411fe11f33fa91c2fa9c6549c7efc115d79699f3972c39f610f320695454e436f6406ea852aa647af51f098296ff3a927460707fc5fc3d04afc6260789fe385fd6a0b2996a17e20ac2bff565e2ed64df91bfbf9ac728415361c2f30155df627cc9f0b9456eaef60eea89c2b7f7ea2af8707e48f1a5b1b3b22ecd7186e3a2583c50c0bdc40e23a9678a1735310d93df084308b5193729d1bdc8bec776b5c12bf8c4e75b30b2c0fcd6aaa47dabd215b808de7b3b93072d5e36d4b8b7488444cefbb9ff68775bf4a0c210afa3b5267841be44cada760729c1a3e4a3434097d71bbc7d7908c4aa9e73acb030cd521ffcbcdb32d33e8e172656b35352ea436dbb8fe0999a690b373ae2c37b48e552a8e1c0bb81484de5c2d02929bdbbe3095bf7e4536f47ca1c28c762d1f323a6d6654ca4213b0d09ff764f69045ba1efc7a6e2e8ad26453dcab10d29f49103abd9d756bff1f91d07a0e72cdf142ce8474a604fa99092f661dff91146688322f9ce82a4235edf6e2af52599e515ad034c62064d8b3a1413b050e45c78436dd3dbfb72f0d2abf0ed4e1ea4dddeabff728ed78e09ac9f701628862753ae389977dcb83d9abe062367bb7e9724c89ee77e6d924db58274703e20979dc6a8145696a7619e0024a1dd22b19ac7258cd41a2db0644f5b7936eb74e9f8e06bfcf667be6e92f1245b02c4e0fbe374c0e6b5054b8d6b5b141e3e1f9b4de794d026a6e446ece8deaf68a29785aa6350d3a8e9c29be6bbe103180afb00a4471fe174ffe28906c73891111067aa22d8672636438c8569c52288cc7f88d2d2102546f7457dbf9ac94fd04a4afa637678162d3c1199666cd16d01af4b17538f32ebf6e4ec9788676d29f8bf18abb7061f60a682706714c6ca3fae878d0c41d20a1b92a784c3fa3fa3ee2269704ed549f5c726842a5f847f68b78a4e801d37a383821c35a65c45f3d3c22de91848f6303a83c9fb17a6dd7e0cc92be216c4b0a15221c606e74bbdc03c545ca36906facaa8a3921f13c4714a95ac82460c6fcc7f4a9909cf308d27a12dceeb17161b978e2aa720b0705a6fa56ed9733bc352ddc68dbdf12d546475ec4aa28585f461f9d96b42f4d7fea5aeb2c7e560192c73561f3f68cf16e4552dcc4439d1f6f8f656d80e3729a403c637c4ff4898226ea17bcb40f363f28177fd9cebfb62cfe31216d8c424b6b7bccb7b5272f67163a120c596d3b19e1bd8f0561fef97e29135393a6ee5f72b2b8eae11f16637a369f40c096e96dd8c6de740856c6de63e23e677fb064667228c80d18b85c6970b5429c55b5bf7b7c1de7091d813cbeab70bbad1e8bb758724daeb4261477ababf926d40b45739e199c129c8a02a6e90863704555a8067972868b4cdab2b18521ecdc3c7bbf154ae9df052b8846ba771f7095506151fe787271b9aa7989bdaef4e1e8d3ec4e546ce91254049e318c846ac0d2c88af8a22847c8f266a2e8fc2ddb6c2463801edbe07702fd239f16f3e218a963f84b5d9d5d7216bab1c2c419afe05116997a73070f890ec100414f77a7ffe9a7ca1f9c989a1ecebb24961161872c74ca06c6caf8a9da5b5cc2c7430d8187cd133f7f49e57872c5c46e43f93b543fe1730c28bf56a52083444bea7f08f86d8aacd134682c302211c6a209f79b9060a90ac4466fc7018faf329b02f1197e306b845eb6de2e101a00994b1130f2d4942934c0dabb15b27883d31a46eb1866c7a7f56d018a70aa72308a48a8bc0b93308f0cfd3b6be4805707e1373be0e5a00c791411c996f67a7294b3b2be5867b2328dc926a6c62066b4801e31dc1d5b815b5cda167aa112584d6c966eb893d71ba36aaa03af5d2296a6dc0806f767ba9f3b30ac16c02a055572919576fb8cfc0985b860593b312960c74d1eadf9ed1fb6be37afb4410000783f4f1990f0771f9ebd6c4ee4e2953f31090a399f04bd335692bf321253d519e372d4d689ebd51f71ef220d8cc9b19fb5acded0e5ccf5374aea78247b28b1a3a37282472a0029b73b590d49487eec8552f38d87f77bd26430f74ca9c460291b9e5b2b7b0d254079340acd5a8cf6586a84e3cece24241beae70ed8c0b4f94cd0ed4e4c22c5127c50911f5355227f287e69068d7f5ce1869d1aa6e7b63a9f60ba6c72117bd14d40701e16215c7e390bcc7f948adc32f3f835663927542850ce31ab6661642a6b05f7d3279701c3cc281a98981b49d6c47f79b245e238b8a9d9ea290180d2e7f8a9e7eaf1bcc34763b5463a82b404be36b8be7c6e7cc4754c0c47e9726eccb7334c9d0d466c134c3f882686fb86f7fa5f00da75fe7e5fb2ce57da5f2dcf55e245894432c67c3d045fbd2e1b789519533dd5b51936732c4c1bbe6e9c72f0de95d48355c89a34393c25f9ff4761a23df03509ee3b9ba8f1fcaf9b132672001ef93be74855586f55cb32b87896d849956e593104099a30cd2d3ec95fb6728e58a12ee919c82ce8d6c98f274127b876b36446d29c27eecdbaaff9e3c51e723dd6ce14eb47c84db09c92ee897352deab2ae418b315d1dac7dcb1cdf475c9729c6ff1525a54c168006b261d91d06138362816fee4a926e93f657759af95e0727118553830ae5140eae3e906fb377521bf9ac9255371a3cf0021cfff5b9c592fa46c6b385ca1634ca99302aa63bee4ab94d8b1554c02acf503ad67eda989ad132faab3ae3dd54d511e261d52773f525a31da6a7497a2ebdc2997452ba4696c72cffdc97445d4205d3dc28dcfefae6bf4973094544f6ac61a688f620a4eb01b723b5bdc91564f4e7c2d90d67c46232edb352bc8b419b7c3ff94488d6a5c7d6872453eba32d4c711db1d17f33b0265c73f7dd3e97bc22282d9e4075ee01d7e6a7237102d711cf11bcd04b4ed1d6424a0e060381fa91c8f7a3d165ca51ac89fd5721741e7f42de2fc4b2ba863f26498c94a5d9a7904dda2fafe818f2275e9aaea727935752a8c405c539ed10200405549ff1dabf650c4ade1a60ad1c92ee906fa72e8b386a6554f88349c1e2788d35ae8bb09e6ca13145e37e293687927e0aeac6d94706958f356eb941a0ddb8145d0991888a7ba0d70ce3eb0a90e8f74ca67b672985342b2438b8ec01466d3d5b05e3a7e2fffc9d9d8f5ed453d5f053eb4d2be723b48a6c12e4b65bbbd63e888ecec0cbe485a5f59fa69fdf51c000b505911291ea7ae08b6c238a9be14449625db46353bfaa47c74f99e01a09c7ee2ce137c4a720ce940f57befdefb27ca054b96741e5faf9b66ce9c700e99b1ac4d07143d6b6916dc9e4ce4e2b1d5a532a6f15dc43eba0bc5e2b99e189f173f9f94b65f6e3e7277da22ffffca8b8ec453f779b8c5b63e80e7208eb0cbe97f7dc2668aa999132dc189e60efdcebf49f84ff8903fcf063898350983979de96cbc9a57ef9edd7f7276a5b87363d960ce03a0ca2765299855e421adeb4cbf6c379d32a8008c86db1ae2921d8b169f852fd5c01c88df34d500a6a8ebd6b4863b61c5e6a4dcf4358522c6e3bbe2c3bbc66cd0cd62763e72d57495abdf81919ca5ad7abc7eaab0199e67f5a06e2c70029bc05f814b5205e54cf803b0250fb8cbc547fcb5c4ddbcf19b72c39f5bb81f9f030b0fc1076f3561c6b484a5b943c168fbbdd7e58ca1543d4e72d2a068f1358b7842b9f1f1cb4d506552c4d15206abe4b2c1aed21ac27ecde37293956d154c324883b50e6c6834c00cc9d9332dc3da753523522ae494ff846a72a6fe26c5b95e79634a53a383cf41c51b11fb57d9930ae6078be3ee6dd8d89601a2e475557b56906d1b991f11ff7cffd394ef92297e078bc823c6c2c1a182a572a1eb4e2f46558bb05928c97219428809fd2ae77fd1f985cfc59bfb6974418f4e35a66b795217a7b11797c774ad468d493fc6fc78683693ba6d75375694f07772a8d0280d11713068bfcc2c8f1ad12b204a40055f6731ac294a2cb37f5b3a4c520b5c01e85ac83994a62bad81e069617e1d43fa2c56654be44be17dcef92b327246a06a78d890bd89fca8a44f2724cee10f5c7275eae873b3c71814b3837b2d72d75f7aa10eb0197c1fb4cc02d327587c30fcf491b8812bf33f03fd3532835655f03b04b17e33865a825ed6c20d24ba29b347019f54ff79447907d0894bf488722d31dd540022e2f947b7250ee3f34613d273e2827860ae6ddd5f88f4660cd366ca3fd7cd77507d368bd76c47151dcfe62b9fdd3263621885c46e85119130cb72cb55414acbcc1f95ca9ecbba96ba32005841382163662f094e898df82c589d7220cb0b4baf9e54ae8695b7d44fcaded92d74a6dd36ad275e3d329d77a6774939952f10e47f973528ca39745647e7781cf17cae588514d7ceea73a4a775207272e0b206123bca7c8c4a984294b42fbc6874a7162aeede5aab34054a436c58bb724483f0209e45ed8ab6c076271d285a59bcc976d7927dd9765ed790a24986c10050d38537c212683bda0ede2b243c58d92f94627f7332929190e817c12f3325729e09837a9461a4bddc7e6532dfae64d34479d3627946415dee09eaf1958354723999e0d145fe5c1b08e69dc02c8a25662538c4b24958d475393599b136b9d55887b0495cca80289f55093fe47125f8925e0384fe9da973c71245ca555ff9e0725abf624e78d4d13570d74d4055e9ade5c7292bf522cff3ef40304005c0cde072129fa3b888f084f16f133d300c4e428607a3fb0db3b321a24d45e2509576e2208eb469dc26f4f7e9fc36dc97b7859e5e7af68e54d682a30911b5ddd7456448725d4b6cef1f6434bce0660a0668ea48f6c6720166c42a4d5ed6556fc8a0251c72d935e6a317c7e0cf36b1c8d5cd31d4b90fa7bfad9e0f23c5227c0f424becf6442fa83786d45d3d9f0ce4a0916ad3914ab13d066ae655570118fad700af573c72944208a916a2f33edeb131f37af3b48ab25331656cce7fea5a957593c8a3f6726565256f8585b43a998eacb068580e03da516b959ed44fbe25a0011fc26404720bb92afe0b63d38ffa9b8b2b4d1f4b8c81ec4663bc58b016acd0cdf5264b28594d2c336e83baa5accb04e349df2e84f43e1bc433853d0a0d88a2d03c91d88e7274896084c6f02b172b36d669d2479670ec7c511ba4dcee53c489f231e61a44727d8883db0bf0c8066b4b578e8d73f97b84ed41cbca48b2044936721448f97c3617317a4538b6601f5ad6c3f8d1470bff0cbbfdee5fa0e288fa6323c088b158722c67f5c017af9f30ff4c3eb91fa08f362823684329614bc42b71fe9f3bfec64e641c386d9d6363081f7d73d3632ee0f0013180810ea4e750322c46a0e68bb93fa9ac1accceb18537a32deade55016ec2a37d82437bbe81dff268a34869a09c72311d652d59d1b39b6c255cfc1e023c7394c993600ac362496c9156860f83e4404d05d1cd9644f1f4e20723033e8bfa3b1693f37abe8f84619330d194d0a4ad5153dcfd5147449a220f7edd7d9200285d2323ce49a689afea42b0db9d2bacf70f0558dd78bac5d277f9427bdcbcf3cdb070baa4ecc50c3dd59ec5702c5a501e4ea1a2768ca7216cfe2632c2c5daf3b8e766415efa2c1b4b5c2017246b92c5762931849de7d85e583ab4241f2b676f4c3543c8dae6412f72e26fea7d4f4027203a2f0ff1d1d1add4a9d41b7e38fa89dd764b3a02019c503eb2bb508268e329e33e3ac5382e186f747badb24791d95e13be4d7f79d7f8b1ace007180af5c5bf38728ad637e15c42d7dc504218fdea015a8b1dc74db27152e7cfc85ffa18e6546712d00b805e7761b3d8615bfa39650ee00c1050b4a3cd0bcce9791b7d3826faf7725a69b3fdbdc06656e0a9de28a54553867ef48fde8d6565bbbea61f608742c71ce245572fcd67ad3f4424d97fefd1e38877f2811504f0c11118f045570ade4f723a7fb4af13ba6c5883d026d9ee40cab793d49ab1d443b4e800d6722b53afff7269eb15088c31c9a6be7a1d7e420385ce3e2ce693e5b5d83a29534e135f7efe218a726a615f6ef4b4e5cc1ab74a722573ef0399ed9c098d81f38afa9b3c1a80199324f205fa053a8729fef83133ae79ff19f21b174f5375df7a919072e6541a7264886faa05986ceeae652048ad3d12047e59ef8d29f61c43351031dbd448187222d2f9f189fb98322fd839ce2a3053dd5af329dbe01d474fdeb630fe7acde372b5ec355968d6f8acac964634ccac1013240d5f105123b9f52859ecc500f339364d016c0ddbc40f071b004b25366cafa2ac0610fe54c52a9bc963f4b0fb21083cee1474a74f6f9ff34190482249e2d92c3292ac9f6f3efeb5fdf19856ac486b099ea9112e715ed9c54e70532362e8558024fe1ca5256311c605b0b31e21feff7254d28279d282f6056f74626ee9513a70b95be5aa8e4324cc16453b6063ac3872c89c5fd8592a6ced2df298fe07661996fd3ae2a74f6df45d6557fd52a7796b7222ad0fad49bb27366a76591ff6a6c58d7b123ebfa8fedf27edf3e4f4025c347211f80ab443eff9f4d42c9c498e4a8c9aa998a5a421c9107fe93a9aa0d2e6d9725d4a0a84e5632306206a5d02429756f9267ee41103c29f72a8556b22fe5d97725afa162c9a27896b603e912aa617b39c08104eb2df21c19fd0ee2cf307159d47905d2f1fbc987eb26cd754fd0f56286a89a0c11cda8f97bf98f4f9c5321880722af6eb0f8807936bcc83a5c4e54d0658b78e16c1b4c00e414f43db729b1c9d3f8d15d6bf25f5598d1fe28ffb96960da03e6d63c2457ec1402d3022f414f04f726a67f76a65836181ff28983d7ebb0b68ba99f8fda67a5de9a4b976cbb5f44272cb8b807ba1d5be3501395deeab41fe5d9d970986303cc459825d9e2702928c72672e7035a509878b9a62e648a0f887a3406e53f1ccec0b8b73069c8a1965771cf85210e05c55febd4f44c52873ff5f69a97a078eaeec13aa7a1bdaa022d1e64bf11a8c9698dfb797ad0fa19c1536381dd01c5917f4ff31a3e78ee639e4836946f7e1e9ed6815f4b36bfa20a0444799fa5157980b5caefd34893d15009632466ae52e961eef7a60c364fae807c44760d838ba3af63d12ffac781710b23639b212a27ad72dbeed139752fd6e8b621b44c0062eeb36e24555a89232d69bd7f6a5284314e447561ec56599350f9fe03a0f00cb1666887a4141840ceb1e41182975720a6199ef18ab54c9c0702f0956c42dfcb6fcdbe1913227b2f50fe7bbf4565a72a90ada611565cf69d5878af0c025df97bf4f927dc1bf4334ba442da5ede1530bfc90be0d186f39602b12e64d01bdf28537062977439dfae1dc531ba625a62372b9ebd2e1feb906af271aea6fe91be7e090a1fdaf1f4977d2a48820ad05b5ad610623fc82b9f52dd22af95b04d9d6f52bba5348b43b1c320a6b8c4f17616ffb720e7355e47227c9f7ef8150ec0f8f62f25892237f6595ae0429cc1f0ee1f29972803a651ed5499afefe3e83e8b3217482402d5eac328f737c2a421684a62b6f42569a6fda72617993e690d999083b9172a74e98d99bf765c36a8ff9457bc26b093cfbcc747fb8fa9550c20de653dc0ed65b399c60ea97527b6e6ead8977256849e701b42b61d1cbfbdb575b4ffd0b526714819e34d2d9062195c95cf8ba2cb972f5469a181718dfcd8867762574c46e42ca40ff154e403e8d323f132e9f80fc72d3588bc42484687917acfcd4ac03b0576c377322f3b8acc212272bced2fd8b72250423f0c67e466fe7a0e0f1ae8ab93e1e558b54066e6c7fbc8e5db7a1f899725808b94f8b803abebbd355c3aba65f25206aa1ac9086c597aab1a8e06f7bf072ff0779cdeecfb3ec2905815af6cdf3520346f154742ca04b7865263eb1c4ec72022146b3a4092e3a05641b4518ea9eb9e3b32b70c43a336cac9ba89f33eb08727074dfe2efff53081cbe71ba8fb494783e5f7e54c23b94db4bdbb7b60459b237bfe51ce7deb16578ab04c241a90323a6565a0ef8c6eb7cfc4ba96cedd4ef65724fcbc7a5c17c60e1c309a90972207f0ad5446a6849565a7def8599dadbcdd739bdbaede46c258ca0101ddc9091f6b6ceb1ab35378981a6b42801786f150bf9725de51fc42491ecdfcc413e89a3a813cd9b0ac8665b7b709e7249044a2cffcf195d00527c56dddf1eac322d6483b979de0b0ffe51edcac0bd20e2aa21632cbc58ffcf88f8c7124fcac5c710c0f23d32a90d63c0548d3036c20d995c8ae9cb5f72dee9e15397ba853b5aa876f1650b6676be35c2684811b78e0d2524a1b7f51c410243b84a1b3ab094f52d78a6dd2ffa3aed6c718d502011d1aa81a7e2b8b109721c4379cf355934be09488f27625c720839e965b550ee9a1472bcd5abc9ef8d393ee0b52cde10bd2cda40eb939220d797c5599bbfe88ff9b0f9e81401a053a27275355730c6fb47a67f196e966a37d1683dcf61ca705770e361884875adccf1508cbd8e8d5075249c2f8b895048187387df8da113087c36b1644a9c5b9a7b07726eef1008985a7b415a93667242cabed80aa653492f7259d5b77d1fdc5495d172374eb0ed80be09b1127adaf709267471c85376ce23de1fa245e55199abbc7869d4edf696c6b09a9fbc151d9ac058a19fa4ddbf64fa47cac7a25f8bb94eca5d1071de9261a7d0d44ff264388340396da739e98229fc4c198bbd1b38b935936e477a743a52c2470345e271f50dcc9eb6c3ca045b64f19e1563c1b6b4a66bc3b372988e2c1d856e9bb36d671071966241124ac81598ec6cf79d75d52a6af067f21f3f5d79a05019d699b1945163c7fee647056e512f28f3f0bba05a7f4cf3ede9725221b086a5345aa7c50b865afe8a350a1bb69a4dc256ba29970c9e3e3ef2ab729ce8f4804c95a431ba351a11d0b5bbafa982f340274206ae4a88714f747d5572de93f8c3f58251eeee20d1f279ec82f6dc92b0b76b1a7c8dd4ff3423c2f44b7230134e105cd5d2ece01e0c07da6ac00dd82363b0cc06160a35d94e5cd70749723128097cff20566c30bf4bd03ffdc11124f13f9ae9e39669f27ffb1fdaec327225a9f03c84d32a843f495be538a89b03765014fa9d1fdfadfb6fd35b2b85a4555cbc72eb99c599ce64c3f53920ed31adeb43e062d94ed297877f0352cb263102292b07efbb6c614164e4b593d9e0ca40e5efa8ed71d42f38788d80ff4147563c74fb9374a237dc3708565198817ed24dbfc28aa097c244bf1b7b21a195d97e72ad7f2555f6a13399b5c2c4a4089b30da27c2353823516bed83787a59eb034f72bad1466c62b591b9a726d99a35004f9e27cf5f5cde41b562fd027b2aa98f3e7255cf43d41ad9623763d1f3d2c29bde892739710c2062c402c39c94d83c3829721c4f906aeb3d532e58ee99880a5db7565bd350b46d954799409f65eca0e73c7202392dd501ee55df86f62f3bc2c59dc10c8ff5b1b8abe7deeb0d56df3b03ce72b3f94194cf3e1303a19ee2b1510de9334280191b265dcf000aabc553347bb5729378597f265ff5f0062cb6506a8d2eb73136ea635baee6fcf1bc4f2bed727648ea03ffbca2a0185b3114f16243672cc12cb243c3abd1e10959f295f9a1d245253aa6ac7451141968eac2e6f8a7f281902c1bf7a431b985bf3fd7f8b23d9f5772d7e36b4eba2bdb08b1da35cfc087596dc7d4f2c41dc4f4526bf735c70a384c33c06997f3481223fce7e0bba09b6bb6cd83d98000adf1bd962e4906d770ea8d7266a0bd6c86a5a49c0f32c96e63a194e92c95ccf4a78427ad4c05638381f89f436870e6f04626a30f831bc9b697da8d733b2fa7fb74e74f5345ca9fbae8690907a22ee3d0fcd62e0011be363b7c9695d58ba7f9f266c3b14e65cd2c6e4cd5a47244c47fa1a7627caf496744fd00391f324d190de278acf224cc37e300afb136480985ddca41908ca18f1a4afa0d971d542990fcdca7915e229a75fa27e1f46e72988b9ce2551e5b1d641a2c5a429e04b8bcbade609c961737cbded1138f9c9e722caa9314915839f57b8c57da2818150a4ee8ca0ca9e604c730bd1071f80c900e9eb2e2ba1ba35b77e9e7ef16e6ef7806a50a72467f02deaf2c52eddab2f9691f10b168600bd022ce53eabc53584818f50a5391cd7fbdbf1e9edc4a9a61ef3657022661f1f5f40b649ae4759a891ac62e15d19512f75c1000b1f6884e3764a0042237d9231d6c08fe9530195f59fd4949a16a6e0215b2c8ae27569c77e9a9f9724b7b84ba65c0d6da92ddc34b90561cc964c182554937f36596ce761fc37f013ce064c650e1e5f6c574bc57cc0770ad870b90eb3ec24f6a16d87e4ad01822c243e3b4849d3ad6e3d5338afafdf0cdd8d23ce4eb8413a4127000a1772792379d723568638922bd416e7f0b2be0cf53fce01be1eb2666d0d2dddc3fbea99ce81072b8fdcdde1bbe66e499b5be3aecf7c893a0c410acdce2fa3ad3e8f7e1d6aff672c37685851400d536b5b1b13812726e57dfe21508054efa7894f3e9d38553b472bcf4cc62492e83b4b979e482ed35a8bd8d34218d5425a4d38cb2cc43b295ce72970bea574b50dbe8e0a5ff318475b131930c8832758ce68420208ba72ee3447265120e9e29799dbf272098b9fece44829914459345c4156a34790017a8cee572d20ede38f0d0f1ec7f6d36f3cb4a12356304f8c8af024b855d312063860805491048f9480f3604b88f9cb754af35e2ba4770b1b2ddbaf782d8ae97ea0cc94c72152c34b64eb1b3af7c5aa3098f445a6f8b803f7da16178226c50f4f3f3a44a728354597655c67c8c53f380625129cfba99864d184f1973c53ad98b07929c2d28ec9347f5833036707bd2aa33d8de64a69d267de90edbb5f1ef2a064b21797f4f9c38765cfdf982403ce07118199daf1ae57f9156252011249477b8c1ed49950ff3b1d9f9a8a524fa774a4e0f9996d45d98515c29f55c680268be31dc3d390672d620134e428d6eff004cd983763783a60d1af6b771ebd3a6be40a5283234a269907f1d98311bc1d23e97a05d5d0267cd5d444e035ec51af3e4cf15faccac8572c3b454669d3fae7c9342a44fa31f2981d2d56b996f7702fcbd2274993c85ca7273a1cd74793b2eeaf01a1c37a080010599379ca134ef39e942fe47796e4e99661963c50441277894dbea7b2c624dfa4d3b954bdf2b1ea0b57d70b98ff87ded728d41ef9a5788e16ad666b3eb8e39a374b96ff014a104a075000e318e48b53a6f5025b5ad2f3efc7d347d30e45463c6dc37b3375af857028365162c32c3fe0b51ed51151d42c76f8b0affda712a7a213ba4b73a69c95ee4bed04e6e7bd3e28472c177d366203a5a856d6f69bef8f11af9432e896536280b2a743f3a8d16caa9718ac7b2a6f6a98057a5daf2410238f150f599d00f8e7a991297b1916a9d9d8420567c91101eb86c9fab7f07cd2e738defaa09fd9164641515625e54b3640a1750dcbfe2f571add1998607520d87155e36aff628ad091ca40a1e09d2da2ef714441d7d232ea4e17a510ca4b620a832da77314e5bf205f9865de8492dfaa83e117256361681bbce137341e3a3463fed8ed7bf30a48f9cafc64e318ab3067be8352d5ab590a5bbe4dd0488ef9cfa44ab926ec09a295ad62ec8869d827b29974f4c72ee13a6784b1596a0a3ccf80eb78007fb85c8a2323b02da1352dc6aec6189fb72fe9b67c717a531f7dd83ea0efe68ea59d811d756dc265538d0fad3d61f49586d6d640d4a7968a62345c11e7c3262004c2a01041301bae17bfcf71b3aceba6d725a263f96e8643d1fa5ec7401bd255e0a1f17e6bb42f6b0d756d0bd42628d93140a00147dc77243a71e5e72c0d91829e3fbd68c780891522e46164bd853b1fc03f444e207d6f03308caaf7989fa96d45e7acd5e8a93c68f700159c32f77c6b31d272d0867469abbbfa38404ac277fee1567ed8585d15de1c8054ee35b88185f477ea9661bbbc5575a1e6b70e68525b3d5cd1a9b326b3179a3861df53759777d2d8a74764533b4b415943bd2da0aaf55c9dc86fd6632d1841926812b1941522d72b722560100d30c8e54ff2d71d5b7e11e9a508a3fd4864d28a4f481366eb11c72e2c6666bea5cb76308adc91a2787d30edebe52c42a7c8f021d26b3143a9be672d6df1d2ac975bf3e6bfaef7e78e15b0e13ed2789b44bee783963008773a27c414b64a984589a422bbb8d765e50c31bd604c4cb638111fdddabe13a141f68205b7e71463eb427facf4188c3ce8cd0b9d1adf143431ec7a7b498aca98c92b29910a90ed92a90c07e29e7615e9f06e53f34c52038122f03e75dc4495d3844dbea3dc18e69450bfee9127d081bf34d93e12d5718773d44b5e6813fa050dc18aaca3bad871371a988d58defaf8af135edd42ea41dc99c46c95116466fbdfd8119797233b4478231def6299723037ff08f207ed66bed1e6959f041997fe1b2f636ff72ec3be87f501212f8bdb8b39d0286f11420325faeee25ecc40e8dc3a42b781872c720ee4b95a62be90e1bc3f3fd614187c3359bf68ad8a63689e5a30b99eefc7221d0852120fec49f04d7889baa0e08853a4e28a2580af70727dcf6e2bf92047251a4b093a92fabe8dac24f6b9d7466478b05ac43c73b5546fe767f9eb096a333bac2bc287a8c8234d3ebe244aefd1b2eee5ea12e19c28ffc6cecc6d1f5f3bb5d6cf2864a67a5beaef679b2d8f1840d6f4e01f54b6242e0d7d3b75d042e243d1e92fcc2098bea310e7b47b3426879c9721450af3941a9d963856d2d74d97db56ca1ddd72826757a45e6178692079857f6f00258dd598ce38320b536436a87090d1b140449b7e84d4eef5e05c0f8d8a21fb27e8d5344ed707c3a58d434e4aa3e72efefc05b23c0d9da2d75da4293095c4c02ea1128a9abd36a35d4cdaa53d6cb723dfd4959efc664b3fbb236bcd2f3a338dfe5d0726a794e33616e7841e59fec727063c19f4718096fef7a91116954deab1ce0d97a6ede7c0b2c07d012bb96083cac9bdc856fe67c289cdc32d3654e42b29a08ad644393a1dd24a7b27ef68487720f577702bd088f68db0bc65144283edd5a4731a24cdaa5d643f6990f75b3b23f6d6121c679ce944c1ebb98f49d4292028ac285abd5c62b08cb817004223747560a8c8be87a57669c7174e4c2ae4cbfee4230d71d5fa86cc3e11d2ccfc42e3d4c8816ba9c58f080a62b5eaca23777aa071a0076a719643eb8dbb6be51b6778172f3d929721434b35ddf1139c36a5afbabda32940b28ee68eaa218fd2844f39e723b21bc046c1890eb5faba1beb99862a314dd910e86d26052cc78cb00b1335b4163dc2408c5868c1c447daa14326b70ef7cee901a6572cdf605bed53ffae51d72c3e54034dfed3c74ccff43ea96c6b4f8577f24c2343d0e760a37a1dc96da9072168e8a55b7aac3574fa6092993a964d1fe109085a27e206013409b79c3ab76724775fe137fccaa5beaec47095792048c119389c23fa4834b91a6176ddbe97272a7677d7779cf86ae7a248076d6b8a1e8bdf3faf72654dd49d0cb819219884d38ccd10e370e10647d3bbf47390c4438870dbc67f548c9a650b9b3eecadbbf26725e498fd24025522a4a94e4afc1b7451b7e32b8fece95b00a733f077abd6c2e56617fffc6e8795297cdb9b8c0997ec13475a41522b561334eb442085eb6c85a725ef16c8032a87bc86226e2e22eb964e5f29abc6e5f3f458e0d782662931974722bd11cf4d5d4ef5f7a3aaba60dd37c95db82656a2439041148ced4233007910309d5cb96222e516133a8d9e1fbc0c112f824b014a4006d4e29563715142bac7223013a6de6652514f2a16adb2914a4fc4b47a234d58e5da71d234d4e4e9ac049e694d84c81f6bcccc77fb1a0d96accfaeb884d0fbaf85d218531d698bd5c4872e1fa7b54b5718f62b99294c3879728adbcf2271e845ed27d820ff7ef7f6faf726afe9825f96408dc50e9b7a1ee80999346c56ec76cbc23a57d8f88f357ddcb083f27bd3a9d7ef7c28891c3cee6baed3e26591a3a5032037015596e1a6ecb2472db1ee81280356a175665352dc32ebeed66dad6c8f91a41a9e921386929bf1e72793f89172f59bde30f2cb33225f77070619d2e3a0873aafa53250624e2a270722cd91c178280f7663b4c6c3b34fbe745ed828def4b050e1101dce133ba8bc9729e1471eebc91ad3351d624188b6e00bc1b004b540110994b0062f62004b546098813882950d90d295f37c162e4ece42277e47bcc0417cda51f7b7d5463ade77262d8b45887149fd2e8853d97c690797f2263d549dd208fa2753b6f283dac7c726e6a2e8ad873e6edabaca8079a40f4c6ba7ad90ccb55713a187eb0e59585a047c9ea88b4fd6c563371ef5831e87d72ab83bca7a01bea8cc09e8ba6a18e0b8b725e1c8dbf374b345aa33833b3b7c21aed14ae911fa30912910b6aca68f32ca072c811ca533b7aaf124a24557429720c221da8928bbdd3c1596e73f2de820a7d72917213f3a339517256152192edba3e167d4a254da074e3576855284f2d55991a246f0ffe368fba542573526fe635db46fe94d8d2ce7576845f190d44eabd560c0af676aaef1d81cbfeaeb3867a1e2ccf59605fe8262afa026de7b6846199f207a7724a95f5b2cfe0cc9b5ccf6ef464acc03095b95f4c46c33a9c857e259c1f578bc3cfec5ebecfe489aa0f2bc87faf58137bb05b300183b719f18068efeebb724edd431f0d5e44a3ab5b89c34366a1f71515ee8653c338f9fe2b1024ab17e1630ca6313229468be3331797b4a66166360906ffa32c8cd806426938ca16eb1d059c0d3ed1ee0454b2e3b82e2cc9c1f315a207fdaeca2da5c24fe3a6df6158aa2c1af26485037fb547e1fcb802bf15d570afb08dd1ea4433178b3a4cfe4d3ac46e4a85e4eef0ebc38eabca24e75d51affc9bc596461bc74ec34d3bec7109721104483c38364a9b379c5f54f32bf31fa114c01aa2e0db17d38b2511b271289b6672ed12f3d00f182133e1efbc21cc69b85a698cf992afae244ecc23729fdaa79172e1a145ef2fce1821bc8979b114659b2871d4de08c211dc96bdcae4265926fe7204a4beab023e25ab3bab7b44afed05e74dce4661f2c742d1bc65e86196ad56721ad6bba19d4f04e874ba5c71388708aff9b6260a3240b6d1316aabe2d62f4c7245a05958f71ef6c668dd42fa826cc38dbbd27d47fb6b64efebcf31cee7b97672da6721384987b3dd986242e1c6630b2f70bc977c161ac165f5a558030ea6d519c505bd93b038010029d60168cfa21528f35584a00fe44e85d603bafc2b13bf2bdce36251b8da43755e2cbc94a7f7392cb5f8ba6f887653d410588ef9c477cb2bf16021f390964dfa7c243431abea44cb9842b84d73128f6831ba5642a9534f72c83eba37f679a90e7b4532f531b09edab22c57cf9ffed9676a1c276397d4c06f245f0c828e62130170c0ca2bdd1b0febd061d43eb6f4786b67da301c0ea4db4ba9bfddad8bd29ba9351e09af5f9bdfcdcda57ea93fd544f60e059a7665f97872716c6f78ea64cbad9bdd20d58c9fa44c16fb7620daf6ce43dd51608f199c0234904c86f2a519de1185b665bddd4769502ed03d79a0860d426635fe5ef0e9ce72f25b818cfcfc173f6913b3925e4919645ea46e22cd265af906c9d3debd59700437e6c80598d4357bf421d1651b57a544b54da7d0a92c02b873fc5cf1627776ba0200007285f8467240628a94819b26bee26e3a9b2804334c63482deacec8d64ab4e1e7723524a98786b780bd640979dc49316702799f46bb8029e29aaf3e7d3b8b7c3e70f4fa145d636e145e170778205c6e2c8df0c62fce3148cf28e10435e8f34bc30e9f303b408a626444bee38cc69e3dacc558a3c6cbd86d3ca20b24d5308379f072c3ceffa21d29f2cf2ea6fb07eea555c93ce804475b801b53891a76968ffc3972defac0571cee8e5a1d9918013fa0121b4f37f085369975bc8fad0d9bcdc72372ee59c5c669a2b0fc9f42a2fbb5385c43a129ae680813a9c83c09bbbdabfd8e72c8b6810b0a11faa9ff146fd95f79e9772e9340bbdbadb9142ad4381fd3a2da72970eeb8fac810ca4b7af57b8e1803695cb5c13cf6b0f1044ae468d752494574dfc3677cbe0fe16a4e76c633aed427b9a8433855b5d783c41db1d436e6398de1a04214ad519de770ae996663f7b78086d3d4004cccea61b30cbfb52c0cd5a4c4399dd235f159ffeb1f230e1f4da91075c81381c088d5ef8f79dc6520b2fc020728ffe5d5c96487f434916166583a85ef5e8b1483ce8fc649b4e1a07dbc9dc1672426ddd080297da2f3727002763c1b5d5e55e1d280ec97dc2d867f43a1691c6505ed76d03933f0f9318f2ac81c7f1dfe9db1054ac1ff3977111d954dbfce71172a3462771fc2c44c8ce0fbf14a67f9add01fe266cbaf722234bb9e16313216f0e8d02f5faaedfad38f69ddc145b6544e9bfeafaa2c5df5334a72f755cd76fe529edf35223c00d4ae62b88027817e5a345e8bada8f24176183489617d7ef45a0724ddd286d5b48ec9245ef7fdd871f5a714a01fa327008104c13e22522b116027257d997500efba16cad2a9118e831ebae1a13f6418ddbe6ea0fe2a445e2fc7838dee3ac7963478e2d7424557388e34c1792d70326021325d9631beda75527e43796ab05b2e272e4bc38f02c3b613ad4b096751ffb4353c3c170ee76583bdff57223238043bdb859853b8fe8184b6ea5925eeee4c9756a767aaf3be112874a167270cc7930488dfde3eb82293b5c3c05a818aa387e61d33452ed44b43401a30872e643ce4311c3842c30dcb4b8c0ac7a5f494276fcc679313b9c8eb64cdca1414e0d016e866c37469b3b29f26d45834006deb3b5eaa216df166ca7b25adce420727d7a1b19f33c351bdf1a036aa50ea53e883146d66b2882e390e198c52f4be346e96e168806da2e21800f7e18899701c98d28eb5fafa8c1a4aa912e48c1516c7266b4370c92f6cc2d00c42b2e403784b00abcf60169c5774ea098ce833bdc4072db3426bc21cad960e4156b81652a07e1d324c11ec80357f1c1484dd0c5cc777205a8afe886a276c0bad3740bdd098918b8fda405310aee9f020e32ffdb3a3f1418536a5fea5d0d721953224990ad8883f64f9f0d4f4e300484924ff079161272138b37c8d0095a90f38b648944c56879c62d743c956bc28bfe45deeaa6b8535a31eb53dad57c1fb117708dd9ff6cb0ec31f9a593dd5080fe70858e1956dc1f72710fea498ab7f5b4bf31e265cdbfd4f8984cf6d89d654dcbc8e614a04652491960cf1db0020dc5f79ebda8c2e4a11e326a784edf67d8cd60e358671c810f0261f0fd0afb4da8387dadca56b6d29669887ab02eb1792f861e634f915c55d93a72bb28d8af80cc008fbb26778e7bbc8a4771b029ffb06f3c9a4214f077465735140ad7f3a9e470e618afe04bcc92ceb0e58132d678467de7c0f4c722b380d6c272fdd3a38b07f7b6a0f92842ba4b9e788692bd32031a8a1ae67d492cb4b766206c33ae1ad0f99854c14adb354de09daf93a32f7767596240fc5eacfd985713c530ca2548167f69dd09143abbca3abd77e274d21082c3df4f85cb8790b5184ae724cd1d4829ea29fa1439e695b7c19e81d6ca8c89408044a0cfec5c9726bcdc2e7240de4031f15c3bc763a564e490856b47ce0d492ccf20b684b90cdd22c277931055a9ae70133b79245f37be4ea51c30f272267dd1c27a4491f724d73d67d4bb5d9fe2c992e1e55c312a8a73be5edb15d5f3314633f87fca03c4210873f7b5471915fef02de531998e1c57389746a9bab8005c55686a1da3e4ff803a46fc604547a20c66f1ba2ed7d4ff2b4af94e3db7074f7da44699304df030badacc5d87c163c0f3a6462b157d2f6d01cb0120e2745db4b144b12a075752c2fb74e2397250722d6574be9983a031bb1ab52b5b6fe1f98101976e022528023357ec2f9e14062a3dc03f79581af93bf4908fe46d29aa3de22730c235f0195b182de95309b9966019d8db369ca05d070d0204063896d54c00b60395cf5803f43b44593777627d0ade0aa59cf0c88e64a2d4391b8e1051b01cdf8e59f1e2309ad01dac5a75f9fa7294f89a835e774382915a492031a5fc3d7b223ee0469fc6a6f64e6ed294d301728d5ef0c123154d7bd81ce4c982970824161111207f1fc451f10b062f1b82b972094f59938bb3cf3173a107a8abae633c199bc7b3e56adc54bc61a647d6f32e28c89c86df10e594e3933ab59113aeb175b1a77f6ee41162799c203a2faf29270e86fef338a07f0d3d7b2d622b94b38931a2e43d8798753045bf16d70fca753524320de414223dc59fdddea4d97c958ba5a20d5f833c0eb8586230904f3dd06a723a270ae3e6c46749e3f1a414dea22d47fb11fc79711b335fef14c8009dc7b872a235261f5870fc9f1680aa6265654182a57ad5a3b9f07b4f21fd1bce1010701f13ae2ebcd25d71d8e3f50965f378d25b1954a4eef215031adfa45db3fb11f238e417a229f04890ac4ecb6b25c12927f4fec229956b27590cdf3e8f5a7cf728721e2b3fd8f36f5a1617c166ef4b369ba67149994f9b01c26c8b85365403cf9072a47929cf75d96a1dde7c857da14152b03e083b84362143b9c1d08c8012dc6772d83eac5f02e03f92e0964d90f6952b21febdb0a958fb22fc3d7bbcf4cfde9b722bb3bdb5a31acfa0d84b7df013f02dca91e690cde131f73066f64d5ab7c1d10673bb075ca75009c4c085cb4ca3ebb512c47218c8b5a9ac036333ff30a1376b72920fa4b7aeb5ce516f31dca79a2b53e1c8f29a9976a13c3e32c6ef594e1a7a72af74d0b6a36e3758df21899cdb2a13855ab7c019dc9ca2b37065f436b2f15872661f991bfcb44328d89aa5d33c3cf9cdd0b673901d4171057131526404c10c72371614f87032d30d66b4eb855f5f65838f2533cc9be916c214ff737cae3f0401b2cd6d0d7e269af3ad830c31db19dd0bad6672b08b59392e88d580351fe7ff7226e25ec15484c387722d82559253e6ab28367547b09f4d8df17bcb1f8eda375f548e7386cfe35f708607ca8232ba6d58984c0f3f9f0b8754fed2c01a49c3f024d5ba8d6d29c955fa58ec82b21ef59b55e7ae7561fbe202455b6f8b24d3566c6e0232f2681e90ebbfa9045285606f54898a1770ad816e7a51698d8953efdbfd1fd4757ce88f5f030f4849276785640759db62aedcf9cab67a5c901d596828c5537872decb1402780cc92afddca614c07d30ad381f45376d9b674682aaccd69217c31d1cfdf5767583640ef80146b8b4a1bf11f8961ea054fdab310c124a89120f5e840501982e14fd5caf06d8448363978d6b3e82ec6b8dca46870e96ef5c5c47191b4706886a98c2bee1d621655a3ebf7da88e91d91cde982792039eee9f7d729fedc7641b54bd8ea11bbce6860e8a9e0d1bd6a5bed1fda7e85e48a1fcb8cf72f400027d0d84b3e92d023ac837b62b04d4f82c53c02bb0a71c47ce1bb9fb9272607cf49deb72d87a8797d787a634051e1fd089507df2abe27f8013fce6cd4272d382527c7bc0c037af23bf8229832fc0f02c3c9dfd47d31d281ae3f2c8b82c72480f8a254fa59b2d27c55094bb60856240b543809c8c4e7906b0de0d51823772a3f38c763a6a7729d44f1eda4f5b77a0e08f5f413662cd2ebb78562841ca0772a679fe7fd66c182b00a589d8530d7259fed9eba8e44c4915d944c4b46796a55e46f5bb4783f20ff80c1633bb9ca04343325a94c13822d8a478d8ea984b330c470292780b4449124eb21fc25dad317881f1386a867f3fd97045398d18dc4102003366c5e5d0156ea54f7d7707accd6ad44207fde078aea07edff1140cbbda6e72ba6a8c8c6ccda987b1759aadb088ce34ab243f92c628571476fb4b5d9ccd861eafef41e056b37c478358407b04bcc007c5398828a5d6aebd60d5aff3abd2f172caa2a84a21853f2873b27bbd379bcdbc391b833ea245a77c624edc49c1d5aa1c9fff29ff7da66e1f226c8416206b011bce67a0cfe1bf3ecf4f25cbfbfe110b72c77844bef2810638e7f0b71a64c797d0d294eb0a7958bb1a9226717689e582724865fbb5cad967dac1aaf4485cc258ac98e6cf543c243b85c49356d352a99424a9b0e51b8bc322d0479c94efab44ab0372a9c6cbc43d8c7c90be9168a21c437293f9777c9385838e7eacb1dbe3c5c18847a1e858982d7743a8dac0a04a21e072244a7bee9c953f8cf26fa6079bca126cf47bc490b4246488c2213ffa9bcd817200dd5366c6e97fff9d7375367a412effbf268e5c892d1d6db147ca2177b09c727c510c17edaade25f45ddce5be5c5b70dad4a0d665dbdeb979d9769afcbc55723d79867d79ed3210b55bf50a50bbe096c762bb6537cf9b112d964d34cf4d0372e6c59fa4a9471582dc18caa6689b49f8c402269f1337b6670fed5d0b6de82c72e395242cb9450622a279b650885bf48d0e9e7f217b82483139c4c45a09d162727028cccfae43476cc28dcc6b0a020c502f174af77c867964f085dcb79df3174cd4370e6eafe03cd890ecb73251b414935d933041b40357022dffdc626d4c0172798b488e028c66dfcef75023d1e80ce53c7fe94496d60b459840656e04da1a1c4810db15158d672cb45ee6328a93f9e2c56d21353317e8f3d4ff711a4b886f12c40e4eb1f213066ba9fd8a71807be50a09cb46fd44cbc5b2ca7bc110525d31722b355ad74361520650412352b108a7c54af1a467fd75bd1cf5adfe1c00583272003e939de49c68221a943cb794fc521154af26b9bf93e277049d116c03e03872694af805a6ea0879e0907db3aa19138846cbe5448c0e3cb0601903fa44f2ed185e3cd97439d660fffcd5b92ddf19a2b1bef965ff63b6c17069dc3d1e30f78e726cf434a3a034f4c64e91a4dea0c0829db3800e7bb677f131a335fb135b018526f38718ca322a048df47e0053f12576a047661fb5ae3e8b300070f1b16bd3527245d2eed02f682348f7edc0015060d0da7234f9a102e3fe604e0b15c9815c25722ff5b81f73b7b1d23379f879d81abb76904a9c60362a62260258f2751b7ebd665d1a33f873b383707623818595038e1daa1b40b054b2679789507aa0102a560c2cee210404dec462dfe2320c29b619c86768e02dd90064e175fcf7b57979b672c82cbaacb12f19de1d80e8c199c5cf354ad49e98b59af9c0085c73f23f340e72d5b30c48e839e56e5c7b79c1586f92b71ac49e7c2e4f2c20bc2313b2fcdce772f566714a024bcff211d3b194fb2a83043071d1b3eab51870eab86e94c7375b70ebe7100e48f3f0cdbf79b3f0c871bba9d1d4c8f2e08a26571843c67073370c722c7ced427865258eae5cae314d336b1196ba2ffb863cb4b1bc94c81ac5c02472493d9fb7e826c3fb5e62a462d09b83a02e1f3b5daec3c131491b8ecfeff04572820e9da21963854ef15021d836c9dbd9e02c0cd9894b381276b3f0d2b928a8722c94a6257280907eed687350a3f880517e41796f995e13a7dc93049893260972c1c21e8b09b6d98a6b8edb263bb86b3218c0fc8d25ad10364314fabb5de26972f9f8f6fb9ce10f2c9b4293733221a9cf20c78529b269671e7f015def7ef2c7073db21fa9de77732fe406c0d4f32868b9fdb835000a742d2b90e4411d6c52520af96c659159def49885cd8dea0ed960f0803f57f7b3e3fbf3fab9a0a36c38e772f250f54940eaf88b87aedca1a84f5b4a13def7d58b7e903149dc7ce63737e226fd1f329437d9b768564d040d0bdd5f84d99058432db1e9da2c755d564a29ad65d88b760e28c1b50b2fc5d6a8200d2369143e4d4a5f606b72e38f551a8f96df2ea64d1aeef9b3030b9e7b33c8d9d94cf237809d02b9579f9303939e4114e2f2046417b997036ead435852b662974380afce78f613a88a3627885b1942af293a7241fd00503a04019e2d20e5138ba70e1ab4ce1f1e6e43666f45abc9eb2635794ea091197e2c5d364dc07e0d2aca978099db8f290d713b09e0a7923b53e092bf725b980f3bc209701bbda00435d85aaa5d0720f1b3c7149e0e5b9373f79d7639728076a31379b710ba21147ca099cf2d0a8b87446001ce1e356bc07a8892504172226e8bb880b17b8a6290b9bc5407a9ed2d354e80b72dd4d68a0e96b1c5e538062c660dc4e26144cdc62cec889259812905d25c234fb5644cbcef5baae956d572ee4008fd03b2db437f3d85e4b63a26c9b930a6b3715e9556653db144d38a8633596b7faaea3c3fc49f1a2b42f731027f1318176ae7653e557809ce435b6979722fba2c6c2352340150ae08a06d461d525c542c51ba2f4ebdb988d651cfad7372369cf0b6b72da9e373d47b8cf71714fcb56c77dcca8dfb366049b742ecda6a72de91e4450f77fb69a0d4f03b2bbea4c63026a35b3fccda4de460f378f8039c4b8811dc839bc0635436c08d1ec2f21898fd878dc1e5530305ba6baad680a41d2bbc3b904c1a59d85d00641c235963197f85a1495d88da582910eacd1a0345e47225403f047ee2d520588e4327495e3582f45f56c47de21132bab47d3f893c943931aaf28e8bc9c4f9d342843af86bc3f13addcbb344f83c54748ff03cab1ef80813383d35193da63609282f18d6ad565435637b72d899717673e19274712b087274aca6beaddcac8f107b52023e0938aec367f6729a51143be874fc6120092f727f3b2c5d53f90b955153f79fd806d400a97874441c280bc65990579bf8ba0d325190f175ecb72f6f76e690c647ccacf9bb8e0905798ef6cd7f7d7333b99ace22ed589bdc386502c5ce918dcd6af22f90602103f1274f481ce8b30f1ea91d757288f4cf950a35d4b8037aac6b1b864337dc56869f44601a0697500ea00caa3b72723912c72b93908649824fca2b99db9ce8a0c97e858955ceeaee84c1aae0267231ba96c3458620aa1a0d74146bd201b5901b396a5cf1d51d4e1edc6ae2456c72345e172f61c30e87952fd29afc0f6181e3546405f35d5a78fa60c8d12cee582dfc1a41f3be9b9b0eaf027ac5dc0413bdf8ecc13e24369857c96383e733af634021e8e9f5102f994694ebf1a9417e1471b5e41eb268f8c6eda3e5dd764e80c372dee32a3b6e32b0743c0b59162fc730a7d2e79b4e2d8cbe56e8ab00bc78edd42000b5603948627dbc2306de0e333e13758c01474ee2fce17c1c8a5b594a5afc72d33fea0ae04264eac6d38e1857ea5abdbe6fab146257d41661516120c7887e72a55beae716d41c6750b2d44b93524ad4345dc23672a1813971417812103d48720b26d03e68a645d0c498507d97f46b8e6d9bef04e5a2a06400634708f7630d12901d357c1274e9a6cba2b8dd2d7e24b5dd2a455bbc57b12e68be706141b72472da56db6ed17c4b56fc9f92de2955ad783138aea600e9fde9e63cd7f7276b9d729d05b52d4a44998c512d70fd831981947165609ec967bf83b76e7fed16b86b078a9828fde5b3ba52c1833886ad254b1834c190839ebe91f55cb7317511ba4b36516f3cf0e2e1a4bf1347bd9e428cfe4a51b1870acf7687afd190a3f1306d4d7249cfcf10b3466247426a4a212d010bc858f7244c5e8be8dc9ffe5fb80518cd72cdd97c8abd54dcc2c006bba55ea8411d5b909425c7011bf53d701df9e63d7b29695b5424f1eded25e37405b6420a4feb40e561c7188edf7caaf6d6b2b66cdd720b93f8267ccbf7922a8ff7cd8bdb3926b32a03baa3aeb2667797b0af93e57e7260777594d9149d31107244d6f0496e1642434ac3e717214ae0d847b4088a6c72d2b0961e16f945e48a33c129919a763bec64b3c3697b8fd5041e810c2e097e72c50d91503d3c62f9de18eb695580b534c35bedf21be2832545141441f454dd72ac7111927c1c6957fbec893c6583826277923d00a245e58b9966fddf1dff7a224c92c03506c068ba4375efc128ded42d3bc1a1c7db0a4426b35f510e230dd0727ee2eee18b60a412cd1bf3690a71b790efd2ac20e8438422466163c8534c6672f6ab373ce3fb74f81cb8cc12890650dbf659e5a457efdaa8b11ea1786290c872f2c6f41ff3dbec36ff7760a7a305f6bdf866b07f9ec6ea41af18f67e66fc5817dc7c4f2eb4ec3834f252b1170d39951a82a841eb42cfb266681a7a296a86ff6224ef1585cf5c3ba1a8e3f4b7e36699c4e59933dc2a339de7d827ed71a1f37572c71a54c0c0f7997d662ebb3f8e3724d94b3e0e74a9030a41f9146658d70e5305ecd9ec24595b4d25662051ef6d94a0dafd1c86c30f1cb8435ce7c92c2415886ab0a9fc15ffc84b2eb1de9f91cc7835f628fe0558ff2f7fb569d5f4cc434b017215d39ff9f16ce1b2bdda7a19ca141868fd2b59d4e8c3e4aef535c53d0434bf72e165dd83055cd36519276eb1c797aaf8ba23fb75d3a050068ffe8b6a20d9a972b4fc7c1b6a4f29cd43e563b6aba9d670252dbc3ea022bd006f9a0e4b438f2c726f34caa34df95e8dac70121f256676087b406fd415e172ba939e81e5aee861728af40706c061afe7ee3aa460a3f062bc5a7619e2d19a549f2d9be17c3d58cd538c31d7da0853a25844dc4fa2476455df81358648ed7d9aa727630f677f2093729e7386a26b19542e1fd18c01395026f3a417ffea43efeabaed70f33f84156b72b46453fca361e57115f044b16d00708e3c6da2ea810a3db3fafe2d1082ce2a723b8846d184d45696632408eaea626c33e9dc202b28e0784c3cee8e1507ecbb2591b88a3e904d5c4bb24360e8e73b134c294d359448a1aefcd9f447cee995b3728da5710919e2fbfc0fc2fae4a72cda81a6f9101ab7d5375b6f19d0a158047f72411735bd2a4f41417e12a20dedec884f3e32e34623c75b95829c62243d0bb119c987d383e085e4e79d9dd82b28e06b1c342709bd673da5f966d6bcac35d4156800682f8322fef53f85db93d818589075c6168737aa921e82617869cf8fed4e1e48d6a8008cc06d4a9b53dff23a20ef62ccda0d7a0f54e619cc44a4965725c472b67d47fac49e4f5d7485da2687e65f4f074167efc30d99400d45bb18a9e8cb1d06299d6b59e2e4fcd01919b3948a9a0e31f5a22e73ae2719a07d818bd3d36c72169421922e5277c5a9dd7dee4349ee839c3e5bdb00d5c78492affdcda45e4e42c0064e43c4602df2857ac5def319abf8cfb4d5216b18da0ddeef8100807da36cc636a91799331c6c85d6be71277432f8ebd3c91fd220aba26046ef9881c38825bd99e2f092a57ed654a55fc46e2b52e56245e52bd04b1eb2304e68c3a9e3430eb19453e70277fd6f65bba593cd9825d4a301216016044309dbc23627c0d18c343e0fd9ad2c38a6b6ffe4f157caefa3d48953361db34c99e7a4af39bd668c6207b18eb84c88f0c8270875a8a9d2a0d26deca2ee68d7dc65230342e56c7be60e72f24f27ef1798d8bc641f6dae914da3a3364f5b1b2d9521cd7ebcdb2fcacf42720d598697f369eae27f2dd3a39bd63bf7c1a2cb5a039e6171cfbaf8e60f6b0044ed32b381c7e10f6bfa355b9d4b7b96f4639fe568a3cd66bb0a2eb7ef5d3974722588224eed5fddebeea6d01b572b791b885bd4e4728e7d175859b48e3b41f9725f7cb52367b2d3b2db174b0526d70b68260a602852de31761975dd2f9fd88c72ea12d0669280db88042e6f9416ff6813b49fe4c348430d731b621d814f4d377259a276d292a4030deaee594357c8d6be607853143327d820e32912ae4006ae728c7aa2150bc213525d9b347e1b699d44392bafb2d9fb0f85c590183ad5a10972ceda66ef28990bcee9770c5879430fbf312f24c41128ee4db32b478efdf426720d6039042bf943e83a1c185aeba9aebd56ca758655b76edf6e9c80c9c7a85f43f9b6f642e02c5016097d4bc16e1d4331d78328436a7201a79462f8004b48f872322f69f058a4091d3f707a21c724721308e03766df74b90e92a113420a519272d955c53da8785b727c4ef5e34facab8e6c3820a538606b926f99df6a7c624128ed36c44676f18fcda9186bc2a715190c83506099c5d36385dd9f43dbaad1f2516e742e2c3cf64a00310fca3add39eadd96bfa8462bc0104195ae5239a229ae7208b091230253157fef0ddd74cc97b06897db18dbb814243e8b14bebe691a10722e12ac211376bd58f4b1994855e8448be1e7444cfcdf92564453c700c6999c721c49f905942f27a47fca8dad68751b9c070ac11ef0686eeffda8feb8246f39576f0d4c80e8f89824a2e82c3e918bd61d58139d3a18730f588e75a20817694b404139344e94fecec54d6fcf3fa116bd1d1c13fc18a361209820e3ebf3502b70722b7c6793a42905db2a5ec85e33f034dddd3f4ca362fe59f7286bd831b3678c729d8fc3e028762cce56ec90d017ea72c9fcbe4dac7756c1e45a04393c0bd61129a6fad57ea4bdc4c3cbad08bb2ee00c7f86bd66ce3f9356f68c015e5e549abd57927745d4c8ac190518fe593bc477fd5e416f17c27a684987acdda182051d6d5d8572616f0513b5b50991e9a39d79635d4be4fc8212b76d3c1851481018aca02e6134218dd7a20ee5b615e1bd5ba13aab0c659c8901cf592a2545e66ff4de3b1a9dd329baef5674a7f2248d224d9541397a6e1bca121d5be0d08134d2bf64ba20abb831e81f23ad78746f2b57eab9bc9b7c559cf3a3a0c63e5ff0f9e1ec3b1172aa04e60cb8739a6e80ed415126fd6e4400e3070713efda395dd9ba595834c2720e03e6280f3604dd7ba541794945dadfeac902c992669493848d1847675e1961e6a2cef4fd0f142d135e44aa40488c3e55495bc58ab3b2f1a413ce8ddb56c564ef6eae0829c566d78b555d843d6f042f3446d2b37568af08b492109c6a3be172f54949536c50b7e70b22bf78a53bdd4029945e4252df9092907262f29596a415dbaa7f89570096b771cd838eabef4e86909e0503d9c9bfd2ea4cf49b4fff775643f885dfb15da95687eebc6bbd5687ba448499659a886c0bec47c98ee0f597727abcf2de232dc161ae1fa5923aadb9b64863a3ed4db27de9abe125c68c040872aced4376aea3e9c5b1a37d7d71ed3d8a7e68374a51f759b0f2f54b9cbde68f72e1e347cac514f142ec42cf90430097bafb2720e6c4cd2bca2526dc357e5fa6723bdd3e5d3c94d3030bf3ef6eec2a8aa285d64cdb7408f0a79f80b5c56f711f5e2d03d0167328ed478b822bc5b3edac86a47bb99241be9db50dda7d0c9a23ca041cf6f5fd7dceb56a15f1ace034ee99a15a0da8d4838887613bf9c5fdbe5fa943f867e5a7973f2b95a0263e5226aaabbad8b0c3d67648eedd1f7bfd5829715872b73b33e9f347d4706e2ba7912cf6ab46128ae909f6a81951833e8e544a19f772d034f6cd01f0ad799b6fdfae9ba36b679c63e39d611d1492e43de2311b9bee6a4844ef01c32b63fc6c10a072ed8cd402abcc9eaa7d620ce313f7a2e1d49da8724a58f2e987541f20682a417cbf5711bf36bcb429d4359354defe60efd95c845e71f0717d1413df5a45525d620471ef6af8098d947cfdd57b41688a1666319b72fa51cab32d2e3ff88d4b26e39cc4e520b0320e66499031d4ac38b88caf7cac72d8d552615153515c322219f08a1d7440eff75a13fadf1282c77ee919ea37505c3632cf052914a551ebea6301c14e512a4b7d9f3a9803932083fc5937e3b5ec72180e7e8b8e65df7483942d83f581b5b2f82c955ba31d176ff9eece1f04adf30cf880a8bb4dfff15f7fd99d039088ee98bc3924dbedef0ac4004c63efc743de72483b1f82cfc4aa2a8f89d7a2e53f44e4c3b72031d1849acfbfbb447a57733b22a24be34fa36771aefb93af9817879c652a7d0d5699205e39f40c72e84185577233d44769f8c1c1700ff3426deaff820d49eacf2a7e2a0465e0bf4ff2b31a8a19f11d5afe3b1cc3cc9674cc5520fe1740b9244cd45e66f6bb130f570a4078c5724538eea9be09c11ba6648aeac2fa6eb131efb7d251172c96c0a7e8aa47e7e172811910352b000f8de0649223bc1721ab0f8a604d89a8dc0b7ebd752cdc842072c953787ed68102293a3be03b9bbc1ae2d9aeb7a8e6431178f27d2a6e0792bb5e02efa4c683a1ab7784e120368a2648871ba3b80bb018876eb4d8b59f31089a7269ede8b144889bc5f92129a8e696232457389c86d4468734130e06eda7990e727957d5323e508b0b34371e46af4a7bd1b5095b4356f0bbdffc756f781fce3572cf4985acfd0aea27360cc29a70817687f692b961beed3366cc5e7aa112b9db72072e575f8527c564fc55bff93c3704c304abf3b6a882369c3afa36376ecdb572f8fed96b7c4c221267cc961b79b5ed443cc7324ec7606f6d6b85fe410878ba31aeb5d6108fab1e016586e5981915848508a5c0f71c81b28a108925729bf8d12cf61f7973165c49a9c20a24296676922f6e3f759b0f74df901bbe4676c0b7cb7219e82b1b2da9e7178cdf7e3e25b7ad74c7e725368ae8a7060c6a16bc3df6d272094900e2af1778111506b4a411e8e74fc3cf3e494039700ba1258cdb73037c4a2504347cc491c7dd9382fe80be83b3a037ccbc1a43e9164708a5bd256b3810722de5a4205c63cab302eac1c07fef239d801e0c2fe64cd80c81d25d85f3ce05726b9468018fc545247ce1f33e5bc7f45813a766859761f57c909d399f0c9da3722dce88c57ce4b433d48d7edbad9a34541f1d93b7228a88b0850a23e4b856d01e045c0f775856fcd4eba819f5c75f5a91d9623c70d96b052b7c57ddec7494bd72f1b6d318d5809496c39e36a6f8c9d427894e9c639f4fbd8d7a55ae9e12b85372951110d9c394910e48e6113783b4137599279cf7b3db1a3b7db89cd9d65b8a72ae43bfea5aeaf18f9e24ed41eddc1f93cddcc45d92df9945300967e6dbd84f72bc09fa80126558f13d1642964020b564846e12b30fd5a45f25b805b2e7a231728ac8bc748cf805597882a2f70162b230fae83b58d83b448089ac35ccfabb082bfc75a8250b318ace2f83ffa54f825f0ca826b73cb4f50a27665f0e62e8a21372bad713d5dcace18864085d3e1ba2a1097a1c48da4ba35d50eec6aa9f13d73e027af0a32f41b2f0b5926196bfecc3b10cf392c20661eb7dd938d2d57f3cba0c72541c9574c0c0d6575567175e344dd7eb0ada3ef9e3173bbe8dfdfde8e429b27213f2e37aa9b6594f7be1f0ed3a2d3b0be0d1553da4eb32616eda680b30869833449b066fad0ebb628658cfbbc7b83b70054051c7f269fcfe6144de9032b12f7221d66ef428ca5795c98c0f6acf104eab411dc6b4e8d2db2453f631521a13b172a28a83dd27c036f66a2d485a959ec2e364a48649fa7b15a6a07194b7308fe272b87d67fa5974dfadf7d6e80d2076f518f6a6a2a05dc43900fcee3bf060172068663c73e7a9a7fd64cd19c15f002a9f3ce7bb1a5d99657f909e96b38a3a097a727af9196c5606bb9524a8c61827f6d2b555d01c62c5ffb7769bc357abb6bbc872d52a91d5790b473ccd4aa1dd59f03be62fd328991ecf55dd62c97f3da38b133a8398f43371b8df5b78ec499b5e4fd17ef3f94009ffe38b935849c43404f3f62a866e78409f069df86f4d94e22096ab81042424171b0f50e96ed2d2a2ec52d0725f3b0ce355dbf760c76f12930ea18be8e61f491b7d4cd5015d6d9a3ef58e3272debb4a36297a5ff4a4f3cc37208a1aaeca05cf293c137c20623725065be0462ca2e38e20a68f24366d03596e33115e0cba09433109359afd4ad5b5ffb6e3025a87ecfe135ab264cee334423a5908b716c55106ae4b59cda7288161f577961b722c460f7cef89f6b11efa821c0685484ebcb7a5c01acc0f650eb4f583627334724032f07793f0570e5afc89665ed13af42e38ec8aa56d793d045bac06ebffb87262eaf7969dce7e97f7af46203437039e51a0f8ed14235d58845559fa2a9fdd6ceb96f2a830e6d91a406e086b5da94997fbbdaa03e06dcfb7dadf7f7eada65e4663627b73afc15d7da55571cd45a3c02c1aba0664d0915722df50df1f87826e72f86ca7a5a3557dde245c0f67edc144b2932344c6a92d05c14ca8d98c64e52f7264ccbba429f18966a620597ceee8971f8c12acd8b74e73f16f738bad51ebb7721c074bebba013c32a123da7c4d7d8b8db801ff4634647b6ac12082e6a9389d356d2d85d4bb8af876592f583ee1f06d15407d6441f9913613ed112d4fb17ca829a73cf618deba40f2b44694a6e96ebda40ce70925f82dea20cc6f2ef90e9a041657864c687f70f79551effd675f3df569678f8f7161e52071e3c604d5d3c2a040b4fb59e999f3a1844d541b640aee4f9e88dd793fee5287d3a5a2892999d5f772b612796a88d69e42eb554193f283a00d78fff75d5d05487d6806a7785e859d1d539cd1be6d25a861b6f8569a1afbebdb92dbd17fa59be50d96cf128b12a71000cbb3c0657054374240d4a1b44644f6300e7d500e8550e07e546a8d120e162f0c59ad4a55389f5499d0c63fac23bae0aa974530fde65026e602db6c043a62cc251f4927bcb7fdbd801fbbe98f670dc13178f387868e7ec9185450f83d191d80560ea34a265803dbb72ac975db82381a50cc871465fc1049fdccecf5b8bc985b042b55062bcd59879c1885d5af3f8e0394b96d6e050ac73bd75909c16dd9d31172a635af86bec5f23732a6430fa48a752d069820ee070c854828337cf3c5dc3f492ce3c2cb20752195c10206bdf3eedd8ec4a2577f8c79b23733d55e13570ea872303f4a8a9c06c7a8d9d845f27cf4fc7c6abb8fc0814136104b848ae500fddd6fbb6d67719c8e966fdca1b75ecb29093cb906060ef2c6c2918b42c7163042aa54811ae3729ef2ea673cf2279ce059f41ceaea7843d921c780d0194516c02174026748d5516291a649050105523c682c8695c6087b715eca724252132b31eeef72b38b7a12e1f0a09f40abed16a22847d5e418b47393be247a0e9436ec6c14b8729dfe62ff7c6bc709418dd4cf8650fe44ed6a849801230169ef78cb603617e661d3466db7d1fc6d542a8835665e254b363f294acec4cffe2c5a2db8395b2f7f72c569d326cb961f1c378fb9e7d1b2f9dfdbe46d91fc0343a8d1f4fd53254da63c1e99810e7f1dd9dcbab9262db2fed7e79977ebdc524767ff766b0e7aff77481d74d81e946e6f186815dc109ca7ace5310311ee2a77e6d37bc45e00b3654d8172971c0f234a9b54c219be9cc83f39b560f4fb9347c5d0113adf7d253c77f48672a1a48fcc423a6d98e3111bb7a5e3bc8f44d100c5840d521cec61e90124b2f272c90825c04cc57335abd3f78407c9ac5fe419e7ad5782d27fa5b3df8b4a7f4a720ce0788ab9cb18d909f504b49d73a10317ec85725d83f759ce61a1835206076b7e9a9b8ee25c5f1c7e07bc5473a2d63aa2cc35f6eeb35fdd3c2d600df43c5130570410405a06978c1563f5379c7bd171532a9aafab2d907e86dc7f89293cc172e2068ad4d42642abcae87b5e382c5bda868d81c5465190aeeda5393cf1ac7504f7b8fe938c01454fc52c017acf5f8e9a7083ba204c53397e0aaa5025d6d6f172369c6b56023107ec111837ce2684580d931b76c16e6ff09169d3c1d97aad0372efb85470de96f63ce6a0b5ca1fbe5d2ab477c63a4acad6f778eaad0eccedce72f7995715f3dd150a8936f63ab7e14900f9686f55f8283fd18319c3bb903bd122d13a8bb5c9f8c0c0edda5aebe3bda891fee94f53043698970880fc2a1e12540a3ba34c53f11d6e4ab74733bc53afe8798cc6e07a13f9c036fb477b9c4efac47256ddbc890414686e50b06f3470a0fae55943ec570635ec7f993b16e1f4f318724a48fc5b21dacd47837caee606c16ac788e7124f6d29d7819564a9e82d259e1dbcc2ac027cec7f5fb69b0f2b69a5f92ff1bc7280163009b9818c6cc324f7b77290fd6c05b3a48d3c2f00a1f3b97dbc9153d27c73d15c003799ff4a05a62244723b0f3d85eed1a871ba408820d94fc5ac775c388117a058a708b20800616dff117fc83f37fd1831407f7342eaee8c5d531b48361a752fff7e8a99bc9796e2e6720bcd75bd81ae18ccebb848857489c494a58f3e944d7a8649f807e7bf0f7d100fdd9aadee0d2f17e0ebca844498791bd617e6e0157ee79cca8d0f4387a757db7299185ec542f15f5e3255a617dcbac3e7dd543fc2043d326edbb0a4e449f5585e6e20c132efdb6e3ce9a0803c8ad5715dc25f5518ebbc9967129d52c8320836728651d8310f4f313efb1bdbf6b7b0b5bdb2fb5f62306782a9ba57f66a16292b7234a56499a214e58465d9af38474533622106d59bf5950675f4caa57c0c8cec72d8f755f9beffff36a64bbad90c935a87e4817de699b52ee88561f3015da9f615352b9c557b56b2df9e18e8cf9288ed1e64df573377e3d7b7b51a268a2a17a872675cbacb94048ef765a6fe8d647853acfa3ab197f410fa5a43c7ed8ef7fa16729d08f86db373dfc1db36492f01045c9cade12dd673a47175a83a3df1bbab87724f04bde940deaae2fe4234465e6f7f5c4acc54a584975ce609cc9c3cf01fdc46a9e32d35ba99594b1e1330ff0b35ba99b990449d8dd06e24c331ab1336b39f0b29d15c0e1ccc890aedb91eb4d8ebccc025cbdef5727cae4f22e0f3b08b5b1b2655eb13c25e204c2a6f00210dc74eb5d08c3824d0dbc3fa45fcdc7ceea216a672e395f764d63652b21a29648cafe8a520da8c45bdf441eedbb82d9b1d41cc3b72761fc175bfc04000cc440101e5e40c7edd7a8a8d277d40be4a48b9a5b0650770a3ef93626d9f382fa57044bc59bb1fceb111ab716850696ab7ca1141e2982563981280b0922f53a8377b7ac1da97fd37d01b8e7697252eb1f907652fd493b172be876aae7094a913fdcebcd76cd7e4dca1a9bc11084a084df20e7b91219ef56f289556889912204dad9b117446a47c94646fb4c61d2a7648d7029c6d6de7e072c6d9d6e66b3c695d3417d72549a3f8dde3f7df89c421121c718e779c5ac1ff13ddbbe89e0aec67e011be9643ae1bcaf3b5fd8a9d41e5ebd1bb741b624ebe7b72dd20b251f519e2eb43261245a06dd1a5908e0f31e79c7527964487f001ffaf72b05523d291765c2191ae07a9f98a4fad8ea0f1c1701258c73574618644baec22b33387b871703e1f1f6787f49a45093f52825995676c177953d91d3707bd1272590a9042913a47790e2789f8b7ffe6dd1872e7342a31fa3460a4301eb3705a11d0bc75f435dfe62d175c74fd1d78492b6a667f98b25107a60035894244c3b91ecd319a8988b268130a6ade973662771ef230eab6744d92dd83d8f738ee16bc72c686b9145d5f865fae49ba4709ecaa453d52e6f393d454c59037aff9f0f0ee72331a7fab5f9bafe80f0d1df027decba3a0651f214f52225551f7f5303b4242398881b143f5bf8c7ffa8d04fdcd9b184fad4d62251d49adb355e8c9dec8671610ccc049ee86c720f1e61451cfad1826d62d02cb8301a90789a651d70efd51873a7d393cfd209f472c97537a6c49ccc2a5c879534370a08726878f3192a1cfdc5fe4c805b65f82cd7da4c2481e70edba6fd4df6c8e2160bc876ff32fe21086d818a6936fca3c6ff7d813ba4e62f78a76241ccdf6c63d327e3981300c6ae5244472b833e02180dedb84e4518f8b1b1bfea35d98c877d720ea149a52d0b47dd6ec2ee6335ae8c41737a636533220f5638ffeed3f74717d1210bcfeb7594142645850f86652c1af396286d4efbb067e7df097e9183fea892410cff248a1b5db070260b2fa9005d94345023a594aae80b0c3f8eff0a024f94e02787fe468ce2f741772d18672d2bfbea7206603b9dd3e1ce679c7e04c41cf0bb55ffc2c6ff752b69a1ce9dc9a6dc5902e5a3fdbaefc112398b69882014d8199f31dc269a3443a972e72b2de7c724a0e458a651f3f8f0af895c0089c096f4833b688f08949ca23821b3a1f5502ea39ea055dd146614101b9f3a3bf38122510a429ef5034a7d0483f7072452f25c79d3fe5d294bb5380ab5f3290f3332bdf2a8c805612c12310c5ae8a7276da872910d58feaabfb352a7937752d10848a6b20e7feb9e76a827cbb7d3972f6dec84d3fc51b4918d5a2fd1a94e32ed6a696cbe12d4015d297b158577f0b3112b5e40d01b5625b231ce26dcf6b5d963f422351145086417a0c053111eb9b72cac59fc9fa5e9f12a9bb6b873b034584824b9082427305d5b292ef74662bf4203b3ef9817ca109a3034bd86cc83da7bfa9036fb297fb36cda73de92890dc4772811743eb9a62932d1313d37bfc42120f19df88a5f442fb1b28df971389cf7672dc5a7d63197eaf6b4b88855a7da5a5d986ac76bcaa68d0c01de5d396e44e8a726bdb5bbb0dd3d7c5f622cd795ecde2c545a5e2d36712d3a6dee5d62179c0d54d273d0318c8242bcb6c4a4e597e996b287e262d6cc994f7e09a23212b6223364d4c3d0aeddc22ef1bf6b0a5d382a40bca36b411bc1743d7413b575bc10cbcbf72e7c6366c3a1d478cc7e54726ba373b7c3d2c8598721d885677f5de6d5866fc72f8a84cd064e1e3a4c1c93e126d106c862c2a05b40e29824494e1f2b34e77400e3cd84bcd25d5c2a470a164f3fd504aed3fc5963ba71f1cf0fb863aaa615b822095082e45ac5ad8b34574a9e71d66542efc086558f52aca3b4ff2e39509fef07217d5d53509a24061a66475dbdadae98dab32a069ae3f523ce034e3e010cd173e9b4d18b6ddef97d7504420b02c6f42db3c57f73cf6f8c64d94786b52d8730a155ee58561d2d4d9d079006914da1f078111845654c19dd40fa6e0f08699a14c725b1d61f18d94e3d7a1cb744084202ea1e16f2276ad3bd1fed5a497a0ae744372b056ca4fc0f8bb707d9e5790b91163bdc338891c503f7bcf8c41ce34dfbf0f7246d4172f843cf9d382ccf86b7f51b1f5c4bfd72082d7b37e356f3e71daf11772c6f7350e78138b42a9cc903d0ffa96127d4b483023ea8644712f0eddc993975a9f6ff2c51bc06a170657132c17add78f1e3e22fe4b2b062575f132a3d37da972918f08bdc16d47ec5f4178d0b1f9187e291c36b6f9fb53360ec9f1125d077a456ca21dcb1b11964fe4454bc727778003b042b4659d7df657e08ee39f1bb94472ce549765f3722bfc6692e6f7e3ec3fa9d4f8d0ee77eea7cec11185cd43c8c435a02eda4dddf8fce1173105592418f3de2fe8c2ca5be849554b2715dc0a9a0e7246b6fe2c8bbdbd4d9de0c592658b0d32e29c9e1d00e30246bf15a064630f7d6a433f23ccc30d7250069df3ec211bcad00cdbb4d1a5907080ddf6ab692a36cc72a8c669ed1d2f3acd0aac8fded2838cbe913b4dab683c46456757e69cb03e530d76763b98c62e9596dbed5c17e962d77e3a56be36d48f779aa1e3314d8a0f985c172750cc868443b22bcbd151ad61a86df9f75a767aa9f3ca4f604dc99d4a6a43b482872bb6ad8a88d71800b75123a76642af55e782fee0b0c026b92088385d726bf30e8b8124654a7d37943c448c5d327e4f05e991ab3486666d9bc554808a722d2f4c7b45375b7ac6c4556d5155c60f236a7dca51a33592447ba9686f778f72506db5df552eac006fca6686dda14b235fcd8878b3c2b33509d3863acbf6ae72d8f2f27d26c4e1ccac17258423d61fbd6b6d73b33a933e457873726c2651ec72b5b86f8d10862aaef5bd7d9552a661143944fa77c6c880e8379ba663a6823a72bf7607f0e66a226b83a6ec8f3794254a06cef6be29e8d53e6639bde43e47553262c2170cde95cde58d6737bcbc6e914c0e113ab808246c1c5580ea7ec6336572d5ac87d6f8e8cd86a208195ec43ae2b26175733f02deaec8cf75fd47262c48724e449a2d0e3670bd52464ca883a91aa5b55b52ebcba27d61567435166823b27233f84398ce8ea64fd3d7c362d3560b2d13f4ed38ec55a7001d7930ee23fd0f19cc8c3b4480f6c74863212f14597d5476d460b321b0cc1b98563a1e37a885f072f6331db35e79694e984979bc2937dbec501eb45832e20f687ee5dffe0c5e1a28bdc0fa42e0fa3057915d57ca414a82479add99d760a3f675b4c13a680bfd26436daae29efbbf0591ecea7e8743724ea8c2b54e223e2335458964ed4b581b9572279a4807dd0ec02a4231e2b9e8aa0bf4c54453360ea561f4efd48c0e51e214723d5dd1fcf71c63e23c88734b577774633de4318d7d2afd67ff32282064b821378bd44f91fae879967650b0fb8a22411616d5b78166c7ac9c0ad61570b734366c1d1e668e86639cd7a3b059e9768a65b7c16ebd9b3489a48b118d25e88f850b72590c4083f4d3605fd4f931957b23d7f7ac356b4481e18c0b10bd59daa717bf64595ebeb5653bfb4d3c8ed2eaa0b1d1cbc1d93650dce53a28b462b2aba907807253487e972d39a11a54cae5b88554e646b9be1a70ff961f2f1103db9c672a24701171005379af09fa610d7a3cf8c69e8c08f08d542dc3622c440fedb3ac1b8472460bdd41ea6a55ea9136e440378e9b1cfa1b04731dfc5c191ee0003a80aab872e86a9d07e8796d8076fa4ef80c0c7f68c8ee02871ace7899ebb057cbb97d2372b3603f6cb86436903d762e88e5ee4660e06cdf6a5995c7841bb8854a5ca5de72290aa5778c67dd88fab9914d47fa124bb1160b9f0a1b9ea07f20b1a2c0b9d272c031fb97189499cbfab2b2ac4d4993f4786e6b56ee98a7be98081b2275a8cb090f750865dcb53af8740181e40f5c9897f83b4ff2c84a958451d7fc17b91194657ec0890ea52951b97d3f30102a971aead0755d36b01979e0ca0e0a16eb395e6f5fef530db3188a5c957c8d5304463e9f29aa5a81bbf7ba85b2cff1b36d01227238e12ba5dbc934651b79d632e29b824130b738b13b73e9d21153fd585f00d672eab92c9e79f4909643ea5fdde60b9a54c0ba5fab08a1908b654cfe0b94e81a72f37f2c592cdcb806327f999eb7791ceb9eb37a7442de3c9b4a65b8658c13270ff7fb53b9fbbd8c078c21a030c21bd1b7d9c2978a86a003c44e3e8dea1db117724da0b6a0d78d5aa3658e075fd941ee477b196efe594865bbe109762cf9a49772f3348c7f52890f43af5647a65d321d37bc5033c1ba18e82500d6dc3a9445b572c815be6fef225b28c0fc6ee60840274c7da91f29d13406e951592902b7368a72df62733e8f6fa814cb1405880774fbfd37bf732647259fdda30bf0632164f6723c26df09031a383688f88976af12c00eecbbe07f5f5af6ee0fae717264515948433c523baf31bde65676e0a99735fd3d354752fcfb95e59c0a85acd46e964b12a9460b17b39a1deb3c1a9a78feedf4d964ee1642d577faf90ef135b1402b8f3953c1d5501092eda3338ccf60430f7a1891a8155f5c98112c174578202681e1726dbbc27a3752c53a2473eb23d2f90c238e23ad311a5bc1d01a61f19fab73f87202d7ac173a6ebbcb720c2099ca09851d127ac446463a9e078224795cec05015caa6ca6682fe7412c31c4bc7aa74a62960e79836e0062dc9b085a6a6eb05c13725da05cf267abb3fa4b44daed7af1e43321188172928e60e033c72859cb17ed720023ff126b2cd62ab8b6375b9fe9bb957ec87f54b03bbff49d9097d48dff285bb8860b8e148fd0b1c01f1b27d19c204b998abc1621cdbbd5d402db4cf1ab6972ec11720bc1d3451b6a29331900d5dc0ef6c0f788b5cd9389e61712b1b859c6455be68d24ed1725867fe429e9b55358a49bd3cdfb31f5bbe6f044fc7f3b97f072fcbda57f869741b71072bfd55b6fcc00bb50c91b6c04811c4028f7d3b2345b72fdbef03a8b99420721b9f4a0f4da7cc7a8a1d2e7cc42ad1c303f5fc3c73645728b8f01e1c35b0545103655c2e1446272e7b9851a74256fefc6dfaf8624f7da7250c74df7266e7aa74ba3f4b4147425cce3abefd58236dee160a1048a98931c723e107a06806b23279543a6c227eb404b946315bead8c2592e1456fb524898e538d6b5544283ec07ef385e6d40685d4345a5e5e78439dbfab46a9d92cbffd8a7251c671d00108a305c998eb37a8ce66584004f48566147792a23bf305ca956a29f77a7258b5ba39f04b0071a4b77e27b049bbd96ac77496e530df196db7a76772bb5b1700c1643813b57bcb0a0609e7202d64280f7cb09b5354ce469b81dba572dd1665e46a655e023df012dde02c516d2acd3d43808fc793a8b0b8f0d2fae10e09c7692669f4e7f856d7be8c7f9a574b5db22dc9c7a22a8ea6aa535f28e4ea725ae6c568f6091bebfc19ea811f73b5ab2a21483dd85fb86d8df2c1ee5221f272d680ee2c1a3e96a919e4cfe23d0afabd517e5533ef53e379211ee43865914b06ace9d45f962389c31b9213cafbf40a2c57828b3b49cfd2ce9375af2f614dc6215d88b921959b1f5abcfc87939c1638350e8013bfce54ccb181b7d3882e608c72921db2690f9e6750e6c9c49f63d6dfcfbb0f4de77c8e33a19659257e6ef78e72500a3633d67e67bc11d07a9b69dfbb94cc3f150bd9f72c9c1574ee51ba10b8723cb36a7fa419f2cff6a0dd6a0ed56a8ef781bee20036307ec2bb856a7e133672ae8013d07f5a1d70f082e1757416e9273d7f5eceb53faf7a64b2abe11d50764a71bfacd77e66857d2a21dcc61e976447e8255e1327f9431a759fab7e232119729c862c979c699df94c96cf1e54a7a987db8d48b00dda7d195aa81dea4649a61dcd3a64610dfabf6c950f9febadb02c55c743854be0ffb6926591ce2a7f411072d85c430e3617668f4c14e68ca7244ce30e89228a234960d2bf9904a00cc5267225d5f1b7f4f4c6a204aff735c4736aa21fbb10448f2a1e00026617a857112a72a12d0fbc9504b2ce9881215111e486c1e173b3994a2af4ed7b88c4440e83403c3e0c0007752ce337ef87fac5aed5f308b3a3398c5c264331131031b188ea47728b2101a3d000e4a97002e4d4565e6cdd03431d541f7f6a301513a83f7d2edd72026ad9032f55311acd2335b3d5059630790075cb2ea0d2ee57a115f6d8ac5e6ea1edabbf64c035bb23d6603304700652db19b58e4b665fd74867ec28366e647204aab081b5484825c1617694328c06023eed705e397a62146111a35f800db17214ed9d3df433234970c90a0ad1817789fe5353f64d1569206748794d46c4da2364fe21277a812a4770a0cf83cca593752b2dbbfcb48951c90f4c20e62b06f57211528cfb5038418541b5ce308322399b4d36173b3f7ae5c882566a95b595812df86cdb001a22789e3557739a1f182f82638b399a9765bd31a91f341f6a0eb556abc562648e2d62bcb4f73ea6cf9884126899772d261b82b9e94df3697f07457216d2d639efc0847344e842ae3e74f7cdcd6375b8574f5abfad87a4a29c1463691c55b8dd1537fc0eb0050c5876fdf4113a989e2a6b1742c615bc4bf96390980a4b31fd765c48f11d694555c6e2aa5984cc96df92624e984ff3fbb675dc37b472a541b4c6ad91170d118f6b55532f9db732df30d06f2a5ce3c6871036a3f8f472700a1875420e06515033c5df071f06171e1c5b3f34b08d830dd6b6fabaab2772b2150c604976d4ab623a07c9d257a5669a2188c5d7718164b65e1f291a274c7269e1195a4ec0b02a0970a949ee0a0d24d8a09d486c17a8b0897c155b52ab79722501ecea1cebabc54b8885649bed2f949db604bb8b8e377953f8b66c7249fc7254079bf4aa75ac2009089e1c1597846f414ac9351d904fa38f432eb21b224e725f09d38f61acaf904b83310550577b84452b43a0e2328759f1f3e34950206e7292e9b71b88831a05bf03e9bd7967a539b1e3aa4056615ba440e26fad1813b972fce9fe5584b010e8e5438db840ba2fc597c198c62b41c5adace8f53191d840722915cb2c6f4d5bad7e98b931c1dbd896e6d250f8e58a423a4121e2b13655cd32dd22959e72686269f73e90f221859c802cc97864024df85dcba888d50577bb6ca2ca3c0490161d3075a2258df7556b5553e66f66a68c325185b4bf3ce743316ba6de51e99297ad1382e35df9e8ede87366b1afb1478482a5cbe41710e53a91722f7c0a7f8eb0df97ab9049fe852cadff21017f29affa9c1bf81f23e6207ccf3463155844db3ebdfb7a2fc08c5457d47b1905c6deb2cdf24341f253f64f77371d35abf6d7128435b77b9347aa1c677f675d30b012c0bd3a06fea2e6bb0b0f791a58f4c35d67bcafff53286a990b50ad19fdcd62e481e8e1b3113cf45a50fde05f30a5e1099a630462afc864d6382897b240864bcffd5edfec027b763de67ead29b4f1c426870071f0191c5e76b108d0f55d055b6e37c45b01e7e759ebd56a0e723e8a9171bf988c551c2f6a82faa152eee005f5bddd47f1c85b0f30b55ead6d72362c3d668627e49550fdb36d0164c1c49a2d08ead51b2946049e8b6f2946f7722245034b53f9bffe9681975df01f16c91b4c9167ffb5c536c5c987e73956fe7267699bc3f7d31aa5816985f217ba4246f67bf6b3529f676ee829fb53c92b2b72d6f641490aa01479bfa8ecd5c8a4fb301c460726ef18375837d305e482f5197270ef6c7781fae4daa6d8d27995821ef6890b7bfc4a44c756b3f56914833c6f72a410ea98ea1dc4651fe74c66c3a75d9ec11d0f31a1a4710e7c773c65f7f99972edc22a8c256d4ba00ca1948e965f2520fac1b9422239235f3e2c143b287f5a5fdc6ba44f2e2aa8c557442c0865283177f1051b68c4a36257007ad61e9573d2725399ac6e64245b3640d3fa030ec0481a1c42e230a0ce06fcb7e62e53ad5dea725d68c620b3beb0a64a329211e386685f61fe3a9a07432aaab1637417a2013c72d442bf1bd182998154eacbc8a8907020a83d416f50befcdde153302a37790c7283ecacbdb4a8b863021ff82231c7ec2ed5f79556009340d9fd8ec7ade07e5672c2d275181937b24d247171caabb614bcde5e534d9f93b0dfdbc0304c4c0d0c67692178c8cee8d2caa8e6c35b9aa446ae3f6779a36e08eff4218f80e61209064a105368a5ac1922840ea5e78cdb40f6b54fb6ad48b3e622a1f858f9b8aebac420854127febe6b4da5a436363ab072c14166a906d9dddeb0704a34dca8a78a7961f0eb56bf00b20ee2ba68c56c4402e3bc34a5142fe894efe6a8bca318d40b7d3a34116e7f882637e840d0dc2c477a06bd91a07a5a1cdfa8568f22fb3baceb4472b8961e446a662ba8833403411ba8f707e991b0a3b87b128ea2d99ac8df57af150bde5229795cf7894e1bc9defeea78e6528d65b442bf88bc7894b5fe902e7b72a27eea1c19daab118dedd614aea83b4021decb24684a0c21bad36cdc36bf6e4badef8f563d05c37cc870e6f2e0dc937ae416ef64e8dcbe0acdff8413e801822453be5e5517da17a660fabdcf37dfb03a7f71f53e40dc8a5088b1a8488d01d6678feb04ed4a40cf07bd16386e6a24f4d081e068547339843b0768c09e20692c104078fb5b6f1b80da1cfad17000379bdb9bf97742dfca70728381ccb9d4d84759e3ad232940224ca4a96ada104069f084987b119aec23d69d87c381f20d4621159774248cca31dc028836c25e95e1327421a8c03d4c15afa7d28567425c84212aa5c72cbf45aae7306fa66a0a6bbcae4b14557f320bd13ce1f362a3ae8116165a4bb9f3f0527422f22997c27107a39cbbb3f89d21a811bd984c169ced7073f53e75c7163b4da96d3e702ac071c19f6c035c8302653623e7493ed6d2dcd6c6b77255eb9385953141332bcb3de68459dd614e14070a753c503b21626a9b3a66e0724221534b0d88b1162b1240243321a9e78402a11ffc74cd9cea2442e492e3a27201a2298fccbe7873b85e2f1a3a7b05f508fb25322ae33c0e96f4c4ff32fddb7206b64c32c92d22c373340fb85f8035a7c6d4922077d4d3c6a11dd47acb846e72a0bec92aa6a26f51f81e1ed07f1a4b606b905d4e129c1536461a77b6272ca87257beebe0266fcdd255cbfea944bad045bab97dc84dbad0b3ab9659bdf4d20572cb43ea7641ebe9af71c5f91ad4bef4c5bb64b33681e3a111ac5ff15152411f720f122336ffdab32bb2968ec83c6352425e1b534532df17116f71e5fe8d616e721f501996476133493783961e93aa272aad9065a17b0b949e198058dbdcc28c72e0a5ee138c8519c6175601a57e2c5284cb97853a4d084875412b1af9ce1e697299344447927f2e31ada7c9c0682a48bb2bde9ba2d250e1c38e16e593ec93e96694216d265a7967c829821fa18002db090bd7ea76ada6b63615860ab8d38913721e37a703d9109f9b1f00c22c27676f9f9ffb4faf1ee66165a4b299a5aab6154b1975fb92c20a3dc455332e7a8a9215bc2a1c8361989446e4b9c6b4b6939638202c1b8ae9e203198bb2858e300660c2e461ce9f1e4b8e65ba6a7d1a0d1277927214e02fdea95a7541648678b683cec6ab3ff62989491c63d15c3129f2207de271e8196318fca33358f9d3f5337c05e691ff6c4954680b957f42252aedb489ef72c2d00ac0a9ec6d9d9dc788ceb021b5de44c2b812cf475331d5990ec982c22672424008691b88eda6731bd8818a52073cf4646ffba5990143adf60b1d82e81272536c3d8b800dc9de6dffed3042effded06672f253c31df88155098b553168f2a71299ecf35a7fc081032ef4cf2f1a1cbba381f40d3ba6e88d64d2b0cd97f832c6778bae0b83d967f47abfdb3f0fee278ed7a1c243ca58b98c825a4a6f64feb7234fe120fb273aaeea9d35acc91f5ec0fd60a6fc4a98e40125fd373754136a6727c501a0ea226c6ece4d1e43f5968e35145951fa9f9f94c7db5ed73b43ec43f2d9cea404818e99bcf136c69a976c11d102e7275ce4bb1f6f30ca020e17afc7f3e24fd72a007ca68a86aea7d8c26b98474116cf6031c703340e2d7c9c4be1ddb729ab7d1be47c8a9c79c869e9a123c0b6d2ff67cb3e4df50a8d8ebd55a3660567278becaae5d88b5e10f3c6fd51fec825074f8297fbe3e71765786324a694d8d72088daffb24f833bb60dd40958e52bde43aa60b5385ede35486655a28fc5401411efcd99ff4c8311ae361b59be0eca944a4b4879a6fdd0402ba2ce0253a2a8d724370b45a50a576bcfb6e0aa216a9c2497998e770df4b42438060a79b510ab74c8923d9c0c7d2e6ac96ed81f3e2b752213bce946c27725c4146a8e6d2f0bf8b025c47740298b38b7d67bd4c881d4f27b433ff2f3f2ad38e3367228106bb9709724e486a906175e5c5cfc94d8f1a4b0cd8ca7aa824f753bcc220a38a845c71a87215cbbf6524893fa1fdaa31e13270f2b182ea6368d1e89d6f3d1e29583cb03172119843736d09234ae6fface341d64df848793339222ca6f2e72b3e3617c93f7220ac3cb3f58e5afe24c2c57a074efd994f89e54f7c721d4ce41dccebd5673d72035d3fdca9650307ab9f40efb2e3d32b2ccc2980d77626a2875e01f211bd0f725d7f9b2bae83a88787a0c8fc9f967755c8fedb58ecc4e30dbf8c44f7db810172d5995c658767d75441611451d1b2e953092789db0d45581edb4b909a2d3ae972d99a6bb2763843089e458dcdc03000215d83af3c714e9306481580f8b5483172ae977a64ed6b362343e5f23f4334a68ad768d65da41e21f029f3bf4164a5eb51e9ed78511f3aed9b03a7fa94f3d9112ca05bc20f71b147920a3e73f7af8fc204f2b5f17c0d8bad7bc74ed3eae5e81517aab0d2b4e36bb4fe468eb852db000718c32789a19e8f57ccbb84922c2e73a8850495d47b7eb633f67f4752a8807f1613b8183792ab3fc08e586e2279ad189f4e78d0cdf38f40812828d901758bacd6438358133ac1ce28b05003c6595c03c92ebf1782ffc47052d60214ff76247734723b9c078c05bd96e3bb32364c9e4465881cfbd11b6202ebe82d2e63c8d86068720e3b820692c578a8b3df9ed1b04a504341c80af3d0791ba5931edf811f5b1453d105978a8fdec816619cd0b68fd1cca9428245edee73e2591d66722c4b8dca727f6474cf2bbd3b278106c01e34fbc6c6df845160ebdf56a7c169e876223fbd72d01de41e8852e937361e75916e19cfd81f2b7eb1da871bb4448f6180f804fb47edc712ca5bd33488fdceef31c1e0b84d93c50dbd3a4c6b78ce10e171385cf4514ccb383dec4c82d36df5cc2b667c4b137fcb396e08c4a4602dc97ecbb5a60b7231e441d2d1dbf3e5d91a2941d8811a0440d0833e109ec08b7905403d8c5eff2a98e8be6a1aa0d672943809ebb3522af3fded87ede28fc9ee6ef6c163cf1db472e9385f093e695f7f8e729c99a9ddcfd8f12e91f037e8a3e8fe6acaf131ecb972e5b06c8ff75fe0b1950da4cdbeb2e08ae2e5eff8fae6c9ed0718019246114241b8bbd42017463e110cd2bafe6ce4a4b49407d06b6a5d3c860b07bc20af93ae729fe96e462edd78649cd7a476f053fe4ef6cafbe59266223a5c6ec7c3c5606372b9e35095f9cc71de788a7a6d936a76bd6af0941062f20129ac739ea157bed60d4d2a05eec15599e07b780e996beccfaf21e9016e8c047fa2b8dc3494f7340b6cc6c0363bb00804b5da91454cf94d31ef2c93a5fb7cfec5321d79da74c50246727981f5335e94a05dd2331cf0cf910f95d1f91111058a1a2eb923e2ef4c60b372cf45472333ffe4cbe5aa22253ed18d3d9bd05daff089bc72beffea8ed029606fd3c54fa6353884fbf7f725ae8f0caea28db2c10192569df2b23c5b15db80fe547876fab51569ca0378e516c6dfe67632e7976cdfdb6207c52daf8d283c92961d202ae7c1ab04c40813c9fbf2bec7e3c1f5ce1f25ed1281ac6b5b5dedf253c16459bf11d29b4c022a84ff531fec1c9dba33f9abf2282e49bafcd3d2b010f2c5729793f911bbafe0fdee3af3aacc607665f714d285ca5c6755ec5123fa3524e772752fa2c9f6bdc3335a6616ad73d48a63d388a6cb370567a03249acd348772072798d4598be87d315f5a948b92ef3ed09b65b4781b86ff77bec43380917b61572c49972f7ad082e43c145494d662454acaf0ff8d8d552aa80fdabbfcf0d30050d306338eac65e8af56d99cd0dc5e7f47b3bc8161648165c8ca66e1524e04d95722329c8b9a5b3982429de9fbe82580b0160a48373635788c82d7503f9c8c5804f5e8aeba9e14843063cc94ac6800bcdc70cf8730df45ef49008eb0ff6fa87961010f35edce143578871e5175c04c5f1a719d9f58a5e71fc673af5499a127bdc720d2c31ecd4f3052d479e2eb56140f9a892c3439533918a4541cfb01d9dabe5620bbedbc224028f37f03189480bcf01e926390a3da32a8b5f1ba821426562ff7202e70cf684bac5b2047935db09d303d751cdb8fc50d25d9aa110a1a83f338072ef2a8b8f3e2a97cfc6cc54b86ca4266b19d8bf557157f66b5e8c1a1fa2694472541a3b0bba92f64a861e6fddd987f35e7d5826572278c6295b3855ef43be7310f6cc8cc27d6f4fc2eb72c76c480ca417315d8081bf0602e588bf8db4679ce272f27524af4cbd4d8c18793587ba5fdbf0ed0c400a8e6d272bcc0c22c80b7df972d3744fbf35d4808d89d894c385aa5f60c3b6cb08d351ed6f3a460931fcc98d6f35cdb30b465e5626a4246bb009e8788273c9f3dfcf3765806c846fdc7499e2315e142406caee90e60a10db1d83e78857a6b4e22718f2566f2a8fb47b6ea406729b3a0844fae0baed9bed4f9928285068f8c44594fbd96efc0e770dad69aee072ec17d25059da4be06868b1bd8f25fd445effd4489f2a25906549f21797af1b72f122e9bb56a13e1d123e9d1c6e0be5886510490c69eda10b8d5c2ad621911772ce6884339c2527181a0acf129a47543e24bc11be2720a2f10b6689d1afeaba722570c6e17107b5c3531d5fcf32f1b63c5ed3813519bfc8e888f29c7eedcdae720d896fda00409dee19302093f663a0b142eefb2d656efe95e21f4cf9a897383dcf3665ec5e76d8cb2b21e5e504a2f94c8d7d64685dfedf6084923b79221bf172ae5ad614d3d6baadd315a49d6794749caec756de6c363303a2fb78f1da973e7280bc91589f26a4511687f4f659b6737204663314f377347d2e5e4518483f2c250984b2558349daa6ab34ff73f6f5eed660710921be91b0364bb8d99cef599672438e0bd9d0702b10a3c80a3710c0a4362343706834c08a536415731e0325764feb9c7599c89f396442275a9d14b19576e0f66ebd348fee986dab4b426c32d26f1403336af4549ed3e9e1a167937d7faee3c2b3567413a20d191e0aeee68dfe4553f16a227efbb66b046e5c02fd4d45b5325f1f930076436945243cadc8d082720b44b809cf458abc139120794a821806ece9ef6890d09b6cbd83de8c68207572b9c8dec402b9a899c988fa4ac68bfc33aa950e7fb9fea8059d8a9f4430620272d905ebaaa2877bf2ec1809021221f80bf3748b3acdd1e407c5ec321ecc695653a9ea031daf446d6c5a5733278e3b16c01da3f04d4949d862cb4e76d986206c725b2c46de7cc68c87faf62fc7f6e9b08dc9a649076b13bbf0afe45ccee286847237c6b5aaf257606e8984c3ee7b1544eb546476d46ad3973a035fa16346e8a7533a185b738a12a98497c6620f2b6da808baaed622930c6433f1711b6307a21e7220d5f3d4ad3f9db462720962b23cdce8c378024215d485433f206185edc28b727a6957900ffebc3b03a4b1a5b02e6d1c2ab55f073b6e513ebc0f5d342259a47220694743604227339c854ba9ee632ebb76e8f8b74a37e37664bf62af6589f17283391acff6159a0227170b9063c372f45b0970bfe7deed1b4c66c80da83c391d38e82a445f59b41071436700b7c6a174fcf5ff037307d3ff6785018398852b57204a2db0f056c48f4de8a6009a23a32576370eb04abed70b945fa4728b623b729fa58d30a2e72584ae3b010449854ffe951ae0a8b9b87d17da2451ad66434e72600d3bdbfad71b66e96d57924d11652db53fe9ba3f158143e394d757d1093a2407c7ed4386c0f9dd7680436572167dac171663a08b21f85eccc3ed61438e84188cd5e0ba0345df2b542f194bd7db8d0d37f10d2dcb05c8e1c82e6f71b073f92b8740c0db653130dae61014a2fe7a0b2d865b3d7372ac96d9ff7b70f422365572409d77fc89011c3f3572fb76f40fdbb33259b4f7d86d10de5d0efd5487404e7200e12ea964ba64701fe60a1bb37a4314a4f568b21d4f4fd2bb49d776a5295c4b83915f70b890d5709b34627b1c1b2e5651c25ec4cc0ecd1d1fa08959ac70af72274fa02d665f63a3ea466c5e7d742de4fea63041ba6f11fecdbd53cad143c672ce4965add34e556fd161628de3fb00de5653cf13ebb6882387e03c77ae8779688d2bbf76c9211a26568df12e5afb2d598d3ad4dfe9d91884111f017766b9f472f654e8324f408f311134441f69536f66d638da93d24f108ef6de637a778f2008624cdd9bee1c54b702f282a6740c660fbf9f5ae2f5240e7a2fb10593695fb118ac0ddb4b7b4eefc75c43874feb0c31e54c5253d2361eddd85e88ea06370c5272ac3c6704ceda16d8ae7fd407ec37c658e9cc8a1570abff91e488dbaaecb5e64f42b7f3d6b202ee4c5234dc252bbc9fcb4308875d974592aabe980b8df09d3972dd79272789fd266d29607efb2e14a0db13d6070ce00ad40d24ef82681a1a3e481af314b63a0396d56b2a92157076984f4850630bdcc91c91d959fa00b615464d128f75376ca24fa07ed09d3cd03b1e57dcdf75b702a15e56db28b5909eb1b651a430432d4fa85effe6f7ece211a5159efcba18aebc9e6995b901ec37d2aff06908b787e018f6c39ec7d891c4644306141733af74ea81ed3ffc8c24b64710b272e61e8453dd58ef4b8b0e8b8848da69eebbc5798ce77c9dad75cbea6f9d552672cd5e31ee78baa5f6214e91af00611bbbb75171b54dea9d80742e954b2c3f5e72ae420e2b11682d611e1d1cf343e47116a9c593827d7a207d5d09cb6da9e138720188d51498595ae09b8f0aa5b1cb5ab9c3e60a2fcc2ab7e66b08a6a8ff2c9315397b6e159ea5200e1034f0cf447135c0f5d3b4b55dc432c7b5a32ab3df95d672f771c3cb17756eb135d5c0a03d6d1687b45d5747c6020ed5a993c4f50d5531108f3fd9e4d88db99b9973904eac5a0b4dbed61bb9847a43efc2a9a5b55813500f03c24be793651fccbaafc0b891c713d6c6932df7ded8cf1b29e1663b1cc9602a5e15d6d0a800f6906e27998761c186ee79f3926a545f74eb3c941688ab5cdc72556d828740226eaf0139265081b88e36e1ea0d2f7abbbc19375095233f8e82721c75b7c92fefbc16de6424b7ea825d38ac006908686282456970ec582687e8720885e670c512f148d9a3dc2d2848ad7b31cf2c680d764e5ce82abef8a7e563725f85924c5134918153835f72c556fbd37a81eb2bc2d95b743d2d03f64ce3d8726c3f110af1b60dcdb4dd00a7f76a9a7d994b1fe2fee86857e7b8ecb70a1eb072b43418c425a635693040bd4cf7cbd244df0d5c5ff48eae4d205234ba439cd8671e9a5f7809d3a348e9dc453042c049c6c153e49714713c46b62d3eb7f7518572cbd5e42e27e94dc6de420841db5259cc8478c53cd8a53d01e5b6ba4c2eac0f723f7d2dab798d63b4121e492f861ed079e5ef1dcfb33ed81914808764551d613090762dba1cfa01a66f5e4f5c9fc74ca0e7b65d922911e03c2b884e3f56a0d0724bd52b48779cbd78ee3513c3a30b465c93caef037dd79822ea42b95393515e72c5b4241a3f31e9fb099fca047a38059b2d75d5d9cdfe6bb42ac50b90f751cf722028b5c6f2df36da912f82ca5deda8c9ee0e25146fe40b8d6763cb2eb3d53744045895769f658b72990ca94628f8e155d40af39af2612f2edd84e6fec7b65e7278a6e714b9402bce412c753465d69bfd1b0f51699e5f9a65572cc54e28adbc728722734c34840dd2d0061f46b7bd87a68dde3e8d5fd6da39b0844d780bbae6720c4d774e23b23dd4be9aa0b1d357a3ffbcdd5bc6153ded2c9cfc1e2c238586727ca991b804b19134c808cdbc9e48a5a329715ed76f5dcbc555c2a01768eef36388b781f720aeef3a2453275da206922f6ed5939330f748d7a610ea27e0713648ccb9fb2c4a291f33d2ead53ccf86a0d52bc40af7c4b2ba24ddc05e00a4b07b1375794f3cf656ebb4cb4831e13525aad002362eec3c9f50aee8da793356c79a48116a68b029290caada9f7524b5d69bcd936060cf043c40458e788bdc576bff500ba5d8ed70801985e9495c087aeb647aba77cbf3b89e7715cc018ebfd1f55b726d0c935d164add739c6cd6063b7daa0f01f661edaf91e88c9029e7f08b4f911d793b997d8f0ebfce02046f2a37a9d8733e19e747eba5fc1842de85d9a176162fd2fc0410d2cbe998d43bc2b59b49261a1e59ca7b7d0f710251022b9c16d919671dc29abbf28750a63d24d486b650e17a67173e78bbb760bee415f44c8cbfee3aa2630b716aac328ff27e3f70e46d82fb8b25f0355382799db5574439a18db543b65a7f6b4067388a3395894dff30dcdd5f0b47807f6090d992b69cd243b0d97226090a9ab0f604cc1b9b0d7aef9565d8d65a13d4dd9129e7ddc58a85642c8f72f5d51186b7cdfecea1dc9636107baeab72e8631100fa60e3aac94c55750725074205673c530d7697e58d5eaab52307e923bdc37cff564329a70ec1b0e503f605f1994497330ea7ea77b496d43f6b946621697cb1e57f234ac6fdf7d7c94b155e72f359dcc0f6325c16b07143672a386880b4da99547af671696e50cfda85ce4bf91c1629febd201e1b49009340d897c5c9da2aea90d074e2b34af87d4dca4a647d9d2610e5aae401353a9b60d9072d162a1bad3935a163da7351bc900fc4e972ff2332396ae919e744430fb5433af7804b0cc4ed601069589fde673d05394672d3e232f5ba32ff8adc133e721a430003bf466be377430565f94a9a2801115b0edebb9c306e8e810745f28732fbe71a31302a2fd3624f8afa58f2497d19d84a2c5bd2f89528b0177ccef6e11e037367e1a48958f4841dd7af063a68691ada107288f5d360a2b20812560856af5e0ec0c00b4e9218a830bcd7bd6cddea02790211292f9be5384f1662f691b98199e9fd8ddc20d5e26b56f0224fb723537562ea45d4502af2553ea91a33cdef4a780583e51d99151cda0f58ffd5be98698d873c23fbbe78c69cf0539e72213b7c34ccf62f4112f60f6db0fa300928369cfc252d72f57a0b38e0ac50ee5abd1cb43567b99c78c1103cd63d090f13698b670cb59d26a21ee47310720cd1e09b232f7c6fc088ca8bda1ca1055063a08ae0d068663072747a23605cb0c29c89a452bb523548c56a4423a178ffe3235ece7c7336c8c572e5dfc8b6ce71dbaf599eaafc5642841d0f2779e7b3e44f93722571f59414ee3685fc320dd1dd08ce1b112ff56a63760b3397bc57bb20faf3b4db026f0f362872cc277be4228bc670129366ef7c9dcbfcc5340baa0311cd2fd6b897556db40d36940e78e29057002ecee90571fbfdc341cfd5345560c2a4c04d7455fc56d5b9728812d6578f7ddf43606a2fd9c3ba2a1a9228444105df770fdcc2207b3f902f0c73b2e237c04a5415472d7f64208d35b68ca90f274e0afd5b75959b15af579c1456f89a3216b0ce8d08dc93aed55e54f203c60e8bf7b270bb202b0996ff92161bbd78acea6833080c5f8d04763e48c9483ce7ded196c236a3543e7211f5378e724dd2c45e84679cfbfd5800c26551fb58c9ed9f1668228f872bd23e268eb2a4727ff50fee552a8c08d8d253ed620f3380df7435a1dcd309980e8836abc80037721bb687fb9ac7db243d71e81d5e679b983c1a57f9204ffea465598b32c672e570cb5d2ab12a78006988d0482d05da0d119c07aa6a513bb71162f7d142e05de73f9c6d6d84bcd9ef9616501a97e1de9acd9d53b4901a87c33ef642062c2eee47156668ba1f1f3a6a16b0c74e0623c1dc400148ef98852813036964bc9927fca33d60715aeeaec7e5430955f7764a2c8979cc03695de88bd8bdfffa3eef5af18f72fae3573e672b0c24265a459087dc40d840375f3579bdc469fb7dc66aded98172644f5f9daaa22b2ca4cd2cd9550308cb24ad38f9462007810074afcc2e1945291900f04a523999ab37a68578935f3a4b1fec9b668eb48560bc53d34573c8ac72a615a6b805dc0f4c149fff051f32da47d4a6d97495be64a4ce92d6092abf4c723cdf3b2af1e705212f76e82d451339c902d35a9fa0710100274ceacbd9f56a2713c0ba3ad174b86297b343e99183130d618d9994a6e644ef71679a6fd27f3c7219ce104019997e5c4bcffc45135ace25e70b8626477494081c4e5aa000fa617289fb84d3e8755ad8b0f556f975a5225df08dade34c076e11e36636ff3483916cbf421767c3f23a06b6e965d0f95c52fadee27062f38a1547c06a748570252d19d25ad063a1c277fcf3ca6f7b29d953adeb363b0898a4cde6239352a9f80a1532b955357805e8b00f49d437401550123bf9dd0f9473985b1c37b8588feeb6ac56879fd5c04203cafecffe5986528cbeb476cbb96bc76aafb30f1192af4ee2c62c6383941695570ee0d937c610e5bcbbcc0023b8b662e469940e018d5d0156922b0cd6c5362c1cf049a4991abdf5836cd34c1c7e7ce1fdaa0793961f6b462de76847cf5c4da16928a196b4b61cca7bc37bf6f58176e8908227ba98faf5e99025729895d3e41dba97c0cc469d8c311e1a53f6ce5c8a40cfc9f4762de30604ab5a3bb35825cdebefe3caac8ea7556acc6298d91587041f73362b8557f1faeb8463729ac94f76430ee996a8f06a0bcef86679e9b0b20aabbabca08cc10ab6d3753d72530da100aee2ddc18435df9fb4a752bcd0a369b0baf5bbb3320bb945a0ca847210ace1f3b70cf2f75607c4b00af10deaf379dfc17c76fc48b26ce41115d9547256f1eb7cbcf6ab3c6d4e19ad8aee7ef7f3ec096c2bfce95542b4552d7b2b07727d9ebadcc263a478a18cfe4432dcaac4f6a38e5f90d51223367d5251728228723eb6a55dee42eafd67381d30439dad8ab7ab8010389b13e1565f2559a630b572e70780f52f063f7bb8c3b6c1469d62c87e57b694d6a01400c4824b7899471d245e2b5c926cd44e06c04e85c3f1ecdcd97212ea17c6aa1299defc798f0e574d72a474df518cd7572e09b2b3d92e6f6c1edc82dce349ce85e04db1411faebdee72570bb8ffe23d0269608f18e7abd3508b1707b53b27a99836e757ab6f336df608aa9969463bc1fc5ea0ea966454954574217e24236e8c7f580c957124776498724bf2fd8a0827a5de53f06f033e9aaec22b7ef8cffa4c26a561767f1ee1bd9c7231fb04477cfa50b93e58c8231a3e54190019dd5ee35203f7810b1d7ddb5787610b4f0d5f5a1eda13a740336093ada86017ac612254cfde625453e77f7c77de61b4d0ed5106021bd827827ce0e305dad1d77d62f760f8dd64e52e763d8e8a0272feebd890744a42aa0714c710bc423396658c23772f45affb7d126b63dc01ec5f49478212d38bd716bc7a02b9af17d59c58d5ac58d175595eba02905879e56d72e46e3790006f247519e43efb6632a3de291f3854c049d52b2dcfdcc0a4f1197263af9c7223c6696c0f76644a3c3bca31f357300b38eb1ffe42a8f41c9506e9725e46f6e7f04a873ca5b7ea9a53b05ad84cd161eecdeffcdf59a15fc2aa011872a97ae87a12128a62aa8998b39b9b3339cae6946703251a388ba2441bdad20672a5b2574478b20e5ebedcf8919cef9d4e322e42880b30a0b497efa7d28edc5b4027ab7a40d9745f86a2862f98ecdf0adba984fcbded3e35fa9dff6c4a8638257256cff19a216f329108220d5d755c352566f07fed2d8a188ec3caeafa99dc8169c0fc6abe3a674e983c1969781ea965321c517d5cef14990e312fcdc85fa943721ffacf634054ea07ef47f9066c0dfaed281bcb5fbd167a3cb43764a1f8e8bf7213bc521d51e8ffb8e38e0d49f7fe0f55ed00386a7cdb227997c32b906afc4572b83c838a316f52b87c54dd65ef1f81c62768e124cc249c3a9fed6da07188dd72a92bfdf1d290b2c0b6096ee4eaaf924c1a5a79bc9107f4655f21e0cbe022a47228d3ec7a4715daf4a2b5b60f7a5fbf9d02c147d738e875f8e7d750cf5ed70472bd27b3645ae6ee4488258d80ba16a0fa6f168f14efe19d078c152678490b6c682f8f0ed018c89ea849aaef273361a4bc0283eb5e32646b9659894569f1a644722427d0cae69a09e7abdbca390a541a0f2bb7e6a282449a1eff3fbc3f40c4a9721da15d4ac15701d2c7be6ebea63872f78615f55dfaf469de90b5cb41034d6555f4cf9360206393f112f11aad24e435e5e61607ada0db7412efe07e390e0ab5728a1e44e95e71b6a17d117650277a016537735953c45f4215e2d0ed40e6782372ea79e2dc658ffb36fe25193979ed0b75571d903d5771114c604eb7312e41df7284e32c6aa81f6140d580d7a6e8b95a79d49a040bb6cfbb1bed294a1a5ab716720dd7386ec588ccab0f98e8a94c8495b9cbf03de3d6708171790af24731e0a87291a9a74b0c51ca4b707ae4a5a034e1945eb885a7267183dbf0ecfdbdec3195728417bf285025b4dd867ed1004602b6e12925cc1d3150f386e3f537fe0a766b72ddf1ed5e9445744c6df4291ac0a25ac6b417b229ef4542a8ce48569fd19f3672cb28a7a10f20bd59ff7a6d70d8236fb13da1cf73298fe74b7ea7b76fc768be722d3da75e1afb4fd40fb869ccd17de79013b29df9a9b59bb66b219cb166a64972fa38431c0ef051bfb38d0e7f669846def044443ea5d46a454ae54c2e0b31ad304186877b606129d6e2086a4cd33a247cb40423a2a41b70ca9d401333c653fd720c19ab8ee6165fc9cc35bb66807a05f3470ef2536cfdcb1bfbe1bc9879ad2205303a819bf9a5716757d64cc44693de9e0ceffa15cf8476d3203ad6c65366fd13adeb4176b9f7a04ab0ba8623a4487e8e606b1805fa2fad907676352826c2a4723c8469ce9929ea915716cd3db39ca0a34d0ef41f4fef313c06d02b46c7567c727bdd93920c72c64aeec3517b82cf3946fd07c9c41fe4169c4f66a82766b0bd107883f957f1ad1f9937f51a2160c7eb8ee2190b61f0443daef39891ce514cf65bbea4bbf5a1c19c82a73bfcf57be4d31ea223ae6be4c3a21bd406427c03203a642e4d2771dd0eef8c24d69390f76e4985c2a7401a09f392075c21c40e36e79d72b7685ab4ddd8c6fafe7ffcf352bf387ebe174e18ee0eb3a7baee32b6400c1d385639826d9e606c0cffc36c88d8463d8deb14387115fffa44835d703c2c32e843fe3b9944abe87faa272d2a5caa13ed1881e9c5346c777e8a20a003b3637afd637015d9d180f88971252db464c6d1206c9ef4d98b069f8b4cf7ea5d2897498072ac81fe605c51927878c763735be8552239f1d01b061166e8f52d440eb9ab070d6220f117b8eb4f439e901680ab59bca5571d407bbbdeaa0fcc1590862f5abd72ec35451a9f0faf3eb4fae652070c67c75779c4b131d1c8e34ae3ad95ae3c8467de5e6a487c43ce0202ac443011e9327da40fc7ea2ce2a82ff350aad7919e7f723636d7c0ae72d73647d633cca6c600c7aae6dd0f61892cf1632163d81d72be612e2408c4c3437faa0570e7969f85b8448cd1c047749ffe552febff06412a4806086dd0b65f26a08667853368c7da55eb86f8111ae4b3cf802ccf8e17d024211cbbaf8d3ac06373e75b074ab7ddcc23376895d2226a7507d4083a823aa6e9207256fe66b93613d528283e57679ac59bd2a3d703dc5e1ebc761c6132aed6a8f672100f12378ce035d58c0c09d847b2559d355da82eb6a00417ee795c387393d405c45f1d2e04b540729fb15fbe4b394601dde3fa6197f14447b4f411b344f1994ef4bd6abdefeda803070d6e37d653d0b82cba273a073abeac25081df12f1d0d72c934b8a48b462e0e21d467bc077107f5d00e712cd549cc817679c49980aed672a09692d344c8950d1d7faad858cc0fd160d9872284e2f3423dff79bc65ac907261d31b48d2f8829ae40d21b90ee95dfd7c77e19d517597a456a8e5934b21047255558f9872e45d2a1fe4ef5df8f4cfed3e17efa7a112205410ad8f4b07230314a97c912d1c14cc5aba07c48481310acffd016f20df80501a88d8a9f39cef3172568357b54e61f384ad00b6e5bddf66e68422b87ad2ae8cd10f24c533d8b827720c20544b2853f494ac2b06cfdbe8e07265002344207c2838acd21996a8ce8340671ed349d0c1a4b5f395dee32bcd5f4ce383ba6e4f754c831cb94cb9381f8f7208074971fd9cc7468acadc6cdb5d821f58ab55a7cb90cba63c08cd516c5d1f72494d83ff26b7cf587cf9795d03a75d65e07f4ea657d71ed8b9e90ec2eb534072b493ca73068e2b2e7bafbd5f7e3fe1bea45a4bb1fc639f4a0eccb1869f39ef307635b677bdf027ad96295006927fd5466bd3187491dd4114c6d1a3fa5a0f9a72b8e2389651bd11b7237d6f7e066b760966332d31588de7ec0e134b44863ee53bf042893800ee358b4664cd8e3318ed661617ca8881522f3a2d37e795be76f3720d9d2a2f3cc2242724e754124da5216f73d6dd6ee8341802d49464b2abcc05155d20b732f8a2a3eb7028826dd8f956d90a3f76534a77e1e1c362ded821a411134ba12c0fd63cd5174a479731b86ad3f6050d1a77f3d43d301663f0da9967cc727cf626c72457e3212b004a663e6a5a9b76156ab75dae1f4d269dda052f82e872a02a2085f3a268e4c2cb94c48a2cc216af3cc92a2736320e2451140d9d5e527281add6cbca740ebbdc5f0704f0b8208b31b96706b60bbe8197666bbad5e1eb354bf12d2554ee73fcb6b224a9ded8d67576f48a6e1bf1239a323139902f480f72ab10a6195ea6294be4dbbca01bc368c44742eabaf12b2e1d03b92ef932b63472da64a4565436ff151481e49b0c463184b5443b4c6a7fbf95602cb4673ddb29168f5bd940634c368ca3235dd6df5859053e507a3bb1bef720147aeb931d8df054150b489536ee488299287e16c78f64d99c39a876c33d7e5aaad64debc6a9fa19f25539dc97b369142ab6b4ce65314a85ff4543720c31ef8ebedb43fe511ce972cff8bca9e080d64a1a8bc5a69360247500a22a3775e0d32fba06c1a8a618105361f2d9563d9b95ad8e6a55e7727dc520d803e0f46569d534112e1b737ef175727d8c0cd147301e9aa5980726eb325acd5d95a589eba8aeb5085414076dcea84ecaaa37371a1d08e001c12341520c908bcd87ee7d112ab2abcaec2fbbc9b98e72da5b9f96ac96cbc62bbc2b83426b5a784ce0c6c911afdd6fbd8deb69f4cabc72aec344dae3562d5a67823e4864509085e24f027ae0162976a36d21e19fbccd6dd1bd0a71800128ce5ee6d41a970b9cb71c9a3274091a2abdfbc3a7e8f1073f474278200cf17c02778c3edea5496ddc007fb950a5b2a58e1ef258106345a3a64451aac002b24419ce386eb621bae4beea8657620fe0903379d0e646a750fb657236fa9e185cc3ce91251fbf4e04d16512c2cd6221ab78bdd1fb4cbe255b2b965e3587c8f68a3b7bd1ce1a3a17cd9e5ba58e9175ce2dafb459aebf73899615c472f7f98bbfadb80d14a7a5796401c6daa2f39b4c3a17420dfe357b16ce2fb61d45eafa2e1565203ec8ecd159561ec3d104a0381f4de9aebd5ea26db8b13066a82b4744c57c6527e643ab632f0fd2f70adfe02396a12549c78f0bd035796544b772497f13e8cc394024f51cc9c4686a96f5c841651069a33aa8bc1442bda7f2147235340402421e7e52e0f329240cffead4804b97035030d5f64a5f459434e55d1991fae3988cebcf394fc5f23a42836b3f997878bdcc8e4b7c26c31f04f93e9372d4aa1ff654dbeb82816af8c3cba0ef4182d62c7c152b685f325f70cd5b262372a210594705125ea155a727586a58554905f48d5be802dbf2137f3dc3c0382567412b20f37c59aeda1553e67be3f1cac3c080bf552ffeee9299ae913573707672c973d9e8632aac865a1f22895b5236e055ab6dd6e0b02f26f8f1730c8e716272b942fb00485421d29500642c1e67b4ab8e8150c2a45fd6d86d379da6c2f3733bc266b5b2fd5cbeb2bd8212e85952665d03ffd4d00bce00319047a2b9e88b6f72065801df1e954c8aea0353bad9870aab325523accc36e665fd5c0edd84845272c93d0f0ee333af6a8cc7d0760946d7aaa321bdf3f696a28247ee650d7cd5fd310e7ed4c9d037bc2ce9b516a8d0d06cdf94e38f75343d4bf3d7672885fbb16c7288fd908f75bf543699e38e6b2659f4bb70a58c8b1f3bfd1f1e47b9a5d809b272a576871b18c368e75c9d17697880caaaec34707d6353489c4b234e9756102a3dcec8dcfc235e462550b0c0291a17f05909b55ad3b5a76bde708813d3244efa72b076d42ab8bec92933f08f25ada6612349320600d2c0ca8564ac1e352468be2629e75a59c03cbea8bbfb75cea9e6efb2fbc45874c37746ac75fb447182394b3c0e47aac66b8124b24e85a5bc588d2791b6fd1cf0bb9ef13f31abbaf48852bb0245ecc735f8912909bb35679fc75ca32faa751199c72a4817988bb71c2aeab834910b978f6d04775c78ffb41690adb4843c3655a1a4d3e72f9fac44e108352c7218fdfa26c4694f07a85956cde9e3c6e63d4989fe77b19fba82eebefef7f0307229de137d7057e0407fa7e9d0599cfab934c1514e714e7a958abdc0e231dde419ac76aff043b6c6f14b4e9bf9fb4e2d638c4a98d39b45101e764034c29f3800721f42a2e83704f67ef0b7bcfb4f0b2fb0c6dfd6038dabb6df6bc6ca6c4828d472d92d1da3f8cb6ecb57a689c2774fa6c864baa95b84654770c141c13af947631967530b336f64525ceb8ab53d2fc6f95a39529a07cc6b95d699e60489ef208272f201182fcf3a105c5530f91c5ee65e9c7a6d067c17146a90a086fbb8573bd372c7c4f92ef33e2a70348b083978b3da13bb7e17048412a118587d061dfa5f7272edbd40cc53e53a87789f67f0d111da3064bac87fb47b2677b2e7be23d475e20cd0bde04ec3110ae4530d72c1ef5c5ba5e15449ea27b12becc0c3d93a821c8b210d9ef72f20dbd41d6f9cf3ea1fbf9944b44b9f9025c53dab5ab3a5b49c6f272ee9804253b84bee7cb3c9d3f4e97de961dccb89139d5e74e20a2f795fa080bc724d10d1af3360fd316ceac8af65ac88731cdada5c7f707233897b684ac4926d722ed5b2c31a1d825a691ed54f91fce2126252bec6b7e5f3d8cd9027b346b1730c464243800316e95bb192b87f6772c7d402de9b4febc5f927c5f9fbaab8afc7722228e94317dc942f22b4d7c417320d8840325aff05027291a838450efd9a3d7267c37dddaed6065db4a9a50cd13c697ced10af41ff5e49bf097b434789b80a7296e136e0430bf8e28cf25ff50517ceeab74498457a9bf761971a334530cc7f72ba084a4d64f29c52c24b2a5f5608f486709703d202b912fb2a163992f9435872e65089644765d7c9f1798e45bf1862f2de0fe04a43e0f3c89f1a55ffacda1572731859bc555bdd50dad49f8c3e1101ae76780c5316a0a9335ae499fda873c0723ca54a922cc74cc4f7cc80cbadba13002154aca57d0376a050386555f499cb724629776ea005ad26dcf597bee695842e12222b841a8409ac39495b7dbfc54234e873c5f4351330bdf889f520a96b3fadf5cc7ec43255896ad6e79fcc5412ca72aec4e2f68bf713353ec2a6912df1ce4dd254bdbd3e96bdebccd722ddb0df53140acb7e1f66f50af9bce86a204ca9d5ec13c33a361230a31879fadb4c2540ed6ebc5a2ba40aa12ed143e76189d1675acb5d4129e5d719b9f3943b1bdda71faa335f323cf2014bdb0cb9b79944c05993ae21ef13b77a09bbd4559776af6abc8618173c8b1adee1dc8e1c5124d9c2d81bb436d664c8f911028eb9693c5bc59f5f72400c9f3f17340533cca3cbcda1ff57ae555080a7e39d9bf3f66d0b3faaad9f724c336677df9705605ea38944f918c18d5c4267c276a79c4e67037dfea49b5572c844b054e6ff882b597f47af168c64d2232e3b7e71739965c174d29f4391352aee84e1b28c654555cf1a0c479e4f5d2ae9a33b2003ec54a95e5c698f2e456172040032c7127eb218b6e2e86c616d12853cefcfd6f74a0cbe8a925a909a135b04350380db965e2b32e35204b81a2563cf326061e97c7843a93ead7334dce21f72661256ba4337db5966f9a6b96e9b29f91862ec869a60582c0d56a682b05db7727b3c06c9738c2b21f9a750f1e73a41bad3ed4d096e0793a43d7f0a81d27bd440074555bb39f347dbe2d18a2b4fe03f8bc88c46adc64a3b33d809e10d7e2ae10879722d1625437a46fa98c15e6cb400883bbcd4d484a35a5eefaf48f862f92872c9a24864af6a65ceb5ee3f97aa1f772d753a3ee8066b47e56335ab432bb4441bd0c1809756d5ac9ac1c07af498ba6ab1b3edbd43302b0a6602a4c49d6d2f49728bd54f0f9867c949b7ec5dd81fd7dc262f2f617a011b640aab1650d329512a72d28382b7d9f19037d8e358955f6e3fbdd9783846c6012f5a72804c63e62305728aabae2fd0b1a3ceaa18f7316327c7beb04b3edb888b3b47c43a734acf8fd5727fd8dcf8cfa5a73d0f05f4a58b019b3e064ce10af933ae8713a0482d3e383371272dc4aa739af58bd8ee8976ef8c75e63b9bc78fb33c3232570c38b55d7498724df14852379e21f5fc865b75b09de7c5efa7031381dae5e7b40c2082c2d51615f0ee1e5de73cf85c741f21bbdf362a3349ce74b0e971977ee9de334483461063131d5316d98e10aef581ff73cece289ec123fb95f68b500b8d1cfdcf20bd095598e0ea9e6561a7618971bc9a3a30353d9e05b5ae97c70d6e6aa0a910d180c072ed2c6306e8be004294216b075c03b88ac9e8776a57e35c82d4d9ae45ef06671131fd386ce07401cf2c32883d4f57cba676fdcbbe182eec304ce40d030714667212fe5551fc903ed0333e85fd39055ed78fc7ddcb1a6c8d8c06dbf08db384427262fe29ce1f17cef9a4419b2dfad0dc1abf2f391fdfbe5192a615ad111fd40262eabe5b704ff0288bfde7c70cd2e412c45815a2bd6d8744d25ec64ddd27a2427219d845a8e597f32cb1dd6801c155a617ef19077c96839327a5cb6da25ff1110e2d91901c4abf28c23125c2b91c3768c172632662016d108847b317ca60d76407a043d202f112565173101e63b47af79b43fca49efb67900b583b313d84795008b5a12c980d3845522b4e0b8a4ad92276d8fd08ad352ef4f9713f578c3f5e9c0226c7156f9b171804322d34c4c5782f829048d3d7724a04eafe04e456d7514272ad070a4a7d6816eb69e31a8c7bb06689537e12fb4dcd0a5a7e563c9e431fc3726e26f9029cbc1dac948e3afd189f0fe1f82743d91d88122720dfa5c2c70e8c3595c45cb230f9e2d3ce3e41acf1bc7f006fa6d88917b40763f7433d908474eb40359be29117bdf2789d02fb30821ed9ab6aec2ae3a99467dbba54a1663fa6aa72230563f68128d33101d2bd380ba1cfb2ee4d411ecfdf8302d0b9d32c6b546a509c061ffa360a678d1c1413de450a02a29913d8b0809b5e7b2146c65c4ec9fe72368c737bd7b1032a9db4a5f269085c1210df0295033fd4e9607b85adaf40ec7216961ac8169c196a60d9db145da48e9b0d1ce84aa8f228726de652e70e56960450bbe2df8308fe2eee01a0cb15b688b656ffaf4ea79cd272fd8aefe54b8b347200a32c66f1ba55348334ed081c50fdcb9860eeccd895e975330cd766d3d33872be3fba988232f9aa60a279718878ed73ef41779a0e53c2e0847b6c92c1c2217260d40197cbd9fbc179f9c141c987a931d49467dcf59e6dc0e6fb5a464d09ed7281e2d6289a80e09c01933c856ff17976930d89ae03abc4ce2bcb37087026524d370002774d5ec704e778938db8cdcfedcce26ec0525702955d384bc48c67e6184ffde0f8e70ecbe44e33a7f8a4451a09caa48576ea67fe748a70fe72c99d745f2bbd03978c2cfb1f53c41f7f7762eeb3ec9e1c735904839a0979864ea3899d72ac156ae956e0e332d11cde415709b272efa9266525a28a7b497e10947c589d5ad5915f03ba59fecf2c39d5270d627efc99d8f8c05738babe96b496a0bb3e091b6898d6e439ffce901a353fb6b352c757533834b369e611d5edcd63699df7882815be0b02313fea8c37383daa6c314abd5b657a2a71773cebe7fe444a584a2210ce6520ba5277434567d6d7f6b42b79df507982cc68e3626ccac1aaa6d49d457275ef4c17376a6def72732d897c4fa37c964b5591cbc2f54c14676d377a400c72b671792fa5966b9834fe53e529b4648aa1d3057de2ebd31b727152fc5c24ad723cd87df2adca1b2fe672d601e3ad4374bc1603038ed3ba14b905acde12e26a72b054718f861fd165f80f2e9ea28de50bdb971f343a97560d0cd4c0ce82dc216d664d1b828590086ec2809c448b6d21544b11326ecea6587ee6114a940ae225620fc1a8e3e5af4ef204fb3ada495c8f7b3ca9491c5b0b51acce1a738523c4490297c9eef0163d6fcb45b5061089bd483e334a102e1ba17b102bd35cd19d699549d6803939ce691d7327187e89fd12e7e67bf1cf8954d9c287651e49a8b6e251725cd5aec66d7186dea661c590540dd5d4c98db82c3622bb081d8fc71039e070213d7b47ab5461f59fd1588cf1de4a37328b72134af3766767b6ecbdaabe380172597fc6f00693c24bfd2ce71a4157000f73b378b5ce8e86f828369578d516be7260aaa782a86e3b866643df6b9f0dd38a7f9bbe6d6484f76f899f73c6a57de307efdee15f7c97a02970f285479b14d130ce0f0d53668738fa0c18ec77cb5815727e34d2dbefceffb2dff4792bb849f206c5bb8fe1cb5da8789c171159baa8861119298ce7abdc2c5c08f774c32ddb485ac4ead6969716180d437f0aeeba3c8c725ae0c5bdd8a499e308af525f81da976c854b796b909336ba6ee491e1d8cbf97220a4541dae7333e876cdab96aad2881a6ea7505570cc7091ff97cc8fedf2d172fc6f290e0da55ca4706e47579ca41b5dca91668ff3b98daf01b4d7a68ac9f9704759bbcb5112d6552285ba368738ef59931e05638284782966fe8806eb808072b9f572bd8862b816e6209973f8dabbfd058b8999d363f6fdafcfb5435d7a593cde863334512b4258254927e5396c280843ec5623bc9908a13726a9a11f2fdd723a4c9384428ac27d4f3ae204afd1b00d12155941349f9de276fcd01a1b693a727e9a4f99e0e08ef991bce0dc690c0a9b8e92d50a5a48a118395e27489cc6077263d557b4899a6210c74e100adaaf7c93ea46c97ac091cae3f9ecc9f97cfd76722edec5db99b9aa6c4b424eebb389de382c4fa4d0e7c8379de210a56dafe15f7258add7c7bc8dbe1e58ed1b34aca9925ac6f844b1ccf261dcbb46c052c4793d72805ada9d74224419e0c6cc0d9489d552815c4c78f44a0bc45e4f17850605d7397c802bfd7b552e4bee2b15a7a621f088477bfc16ad093e54578aa342c2f1f84de2bac4992b0c12092964db8200cd1ecbf6ed840e884486dd112baae89ee6332b4b6d3d06d481e18f03b4e598b490df40396b09091a1d11e005c61d9d474c3b333bd6f74cc49135b63fb17ebb72d1e9fdc27c387978f92fceb6f8cd98091f2f72075159ad1a6b5a1dfc2c5572001c3228b7b69ba00152ba3b10a5c6b15c10065adef62d4b60accc997affc226cbf1fffee867dd6b8b98098fb46efcacf3268d72d2b4daae715bad9bc183a63fe95dfdf4a86783613b449e8055319bc64a743572580300c24c0c2089784c7c09097bd5d6c3de92d361b1ee193ee10de02238d24898f9071232835e8793d3e13a35cd269a02d5f289e1c7d692d8ffe9129b7af0727473fb3da34dea0859479c912d2deb27b095f570bcb5334c12158f647c034b6c156c10fc9d816317de0b1f62d3cc5d19130c034ab69349cc16cba78c79917d72e12c04a66987813df26f4fe8b598ebc53e5b8cb6fa16c1b8b07a0b409ca32b726a95c0f508d3acc484ad8ccca9ad6a89f4194a2e2e46394f5a0af2fc185f1872aab042f5d207b4bb95139adb137fd1cdeb4acf82ce65772809002f376a3f1b72c07934142789e28c48e783901f415770112a87b302807fc7c243334cd19e3e31b6a240f5015c6b111ee96fc3230c0b42206716e74c10aa9839e58667f3353972cf4522ca63323aea0b46d6e79f575eb7506a27dd2495a42bf40cfd6918288d119970b872cc08df2522f245481ad04e26f8cf8607bcdfe569e4b75c59fe0e727204ea0512bb86da8f1b3e8494ba3c4ed9729090d96a2f598b1b490b94afee8041dc88296a0c207b56b13ffe335f1905119e04388c2e24ed6deb64deccf2cc977218f66e26485188a02d1f2007f1ced137cf703268cae627e5ee79c63afd52ed32cf99a8e5bb57a2fcc177603d35e8ad515622fdc3a91f9f2433b7405a730ea2727d178e540734c5c292aa4670959a190c8f5a51600bcc64e3e2f7366b15f689729fef945f9066511f4f93509a62e8e461f1c7825bfd531152e8e21112348a4f725cd6fc28e0709b17c9fbd71d86d7b3bba9e79ca1cedfe5e51c8aa74793307572ad0f1949e7d2a7e1a2fbc500094f2db61c10f46d05eec1759575855c71b6f75feb716e072744772c829c4950e82e15a10ed6ca5e05a3552aee341d2431d32472bdf23a6eba7fdcc8e05149f67f85c643c0b64186e867ab9131ea2dde9a69f872e0edb4ee9436fdd0e3d825871d8ac2dd9a99acbd88baac5cb0afd17d646f1c7256d5edb7acdf474eaf2324d08b30525f126ebf2b0d19415ce576bd8877df8e54c640ed31dcaa25d84ce0287b11bd6181ebff8c9d9616b246e028f1d3d24e5f72f60645ca8ad7bc1d21499a6c7549bc57a91a02814abf3c6d9d434b8d036b1a721dc585e331118880ae7bd035cbbf1c2064ab7f50308650481ee47b391c9a017248436952ff6c2ac39bfda93341d875ce10f212a747a17b2edcd3ef0daef04072c5f3f302e77080eb0b005d2b1f89ab06db82a5b6fd902513febd7cd65e6b84562baa06ea5f16147a3e401515ba134c44d486515f2892f96e898171c9477c8a273471aa0a65e92a0a7d894239d32d1cd6fa5525a9846f7fd6da3a19948b5924729b1a0d92af730b9bdca6580827980f61c842218e94983c7ad4f6446338405172de97310510d8ea01bdbcde41e5025b5068faaa7f5a061ad72c02d011ddf8600f769decc9f28f0c0dc8c2fb33e53e600885071b2b73dd4ca50232d5c45433a74135388a6f46b1007be353a3932546a68c450418f0d190a419989d58992cc4de6184f9b2c7818b3e84b525fcba665dff0cb304642f6a6fedf646b8ca07738eb70ce006068e660339915d3842e78e2ab4cd775313b4a9bb7aac969fe54e2880534370a4c694db7db03baff5cc9869283fc74617ab0ff2bd7a9ca3f60bb6bc9f6472c5bf0bb7369845a7d5d9464a74876b34ee78d3dc4a490af12a2d216053f26972d60606f57b4fbdf4b37286956f64c30ba6b8198794dde073ab925f668e654767a25de20236486fad6bcc7ef2bc01e74768e25476692473939f488d1b034ad57200fe1d6686793c5e8dbbda4dd63822aa7e41b20d0d4fd10899517acf504e6d19132b6dea7908dea9fa8de145ef54213d9f61e4148f3e3fb9c635446ebb9f5345e67fea39dfd27ac3c9c3e6526536acfcacdd3423ed021f2341e3e48db16b72729ff7d5bd7303807fcfc86255c4b3a97c33c542a59d1a5d600daf77e77005af72662223027cfff787e83218da3aa007f1c4b7e0120d79b1e801e93ee172751720942d1dfbe1680e3c7e59373dc239e0fb3a6305e20406d79f320bd62918321c72c43a8070aad17cf2cde777f74466c0d2c7568d9ae245a613b492ff988c2f1a7246b8e53eeb4a6e5e1bacc305c28b8270d9c8fb31c3980b3308ff925da80c377205933e984e4b3f673b486c62f98e8143d46f6b337db4c4b933bb3b48afe3d3462ac589d8f4a0483e14c32e6eb3d34940f9bf723c56af38fc106ea1f724207472348b2ac117a13d3f57e8f1fab41c7665dc9cf44bd74040c7d80970289b983e6be4d3669a1edd632f1c8c61f2206c546dcc6e156948125092540637abaf26cf6c5a164f8bb15dfe21c68c8461f754a816c6fbcd2d1a23b3e430a28d397a6e9a72a0436be2583f190e66e0563ad8f9a28de5f496d60275550043d268cdfaba3852c3575e1e15ca1194fa0ee0d609fa1e25cbffc62b93629643fdebc686e1d700720851dddafb44341b099a68fdadbb1db9c0b274caa377d802176b06290f14e3721c5d64d7125fc520b0f1be5905e7918132f261846c23aa7f821761399f97ba729d03975cfd732e57ea6b7062b48a18be0d902935aa2452d5e2a30509841cca27f31299c301d74e562552d662b74603dcdaea9c74f2bf7a99303c518d32fe93721ce8812888f9faeffda3ad593d35c8e16926ec4a865097810ebb79da72cf2008f838897d8b64472094c2ed18e87b89ee89b10ee6ab1ea9d8763b8cb6d9e3ba7242baac96799417de161ce476cb4b813544e2a50f030767d136a693181ca4d172316a8561725a78c31ffb990bb088b57c4923c230f3eae47b3299b57576010f171af2d62807970aa3df3f81150d90021e383ed3598a75765d3b92c80b502ad17208892a3e72a31a90a2fea397ccb6f2d0cec87917e17cc1374516bde58cdaa33f78d2d943e146a25cdec94576f0ba76bf01cbbe19cc034d5d59c867e78f50d472f60cd99386729875b1430698bface64e75f1c37a9bcd75980c9ed381d6985a725e061efacdc208493e76fee4b2918bc8c0979b5f061144eab9f403bdf32312720ea9e2815c8458fc5a1229cbd469da33d71cb7bb4c8f61493a358e576a03eb729772cd72faa9785f5abc42364d2277b49118000ca98b492f4ce904a258fc0072449a10931e176a8b574937a7c5df82a03df4f321bc78e91856a21c074807eb075cdfed802a65075e71bc1145f1ac454738eba0d20a44737223238b5f016eaa727dd765c97302eac0bea7306d8e0108d7805527aec8a4201e097e0aafb8d00853ef91866095f5fe37fff6e5ff7401996c850a02259c753b2f0ae17c9ec2044d720b9417d26e68781efd184bbdf192805c0f7b0a4b8f6b58b296743d2e1df8f9726f1ce0ec65a78c607cc30f1538b429a09f3069ae3f573cbcb983d3347dd31972c794b9df8041178cc8d29867edb1bfe4d8503315c3b93f0a87d71e3c766cce36555d02d2e655fe590e803d763fbc9eeb6d227d91f526647ea16cbfd267bfe872cfc24de8fa244895f47accc129ed9bcb0a04a4489ebc51338debe84764a6d272869d5b2a0679903283316fe0316f7eeb21bdcb3841315c9130079db5581725720d61b84ee282d84cc8ad2c5955ec631f012074ca366871402a83ac67643c76723f6a744622f780c2b83d0efc6e9885d5aeb1194e364ee33f989a204c729e011f6ecb4d5d9ea128f6d2a80e08396834539a542cf9bf8f4e6d603c5ed7ec12fb26c48b75af0d2296fb5b382cf9d96996679fac0f0af1949a7a01b4cb163cf192726d8a3d9201d96bc8dc7817394065f2482a3a0705926cda0d6f1a5dbffbf68e238b76cb10a7d0912ead162ace76cf287232a6b79be2485f2793aa1f57c2e7214928245942921e9e9a9a7486f65c3e73febe1940bf9a29180afa07c89d12e5f67216e1681980c0e7efb4267b661f680c9caef230805f4aeeed25b5633c84f1847235d16460b24327051a82f56597b0cce1b2e99060c0fe1878c9cd1ab098706149b6f267546e09240c30fc1709a966aafd503786ed534bdbfd0eed2904828a1a72efd90504e1ca2e45d792f255f481e04b1a2f7fbd92a325c9b9bce01aa887121a09aaab1ab5d94372e8e3da1c99ddd0821ce2cc67c9c3578cdfd9dd400a621b133966961989efab93e635061afd2b9105a87cb90b0f7d1b640118deff208b3b72a7860f48d9ded9a0d816129d0909c1cbd134a53a49907feb506cc163c3b43c726ffd6ce05f0414097a991857688f1cfd2bb145e797a7f0f971c830a7f9700620871ba3daba991644c8ec06f27ac8b5d50d4aaeb6e9a23d1366e49492fcfcab727ba5fa2f187c632f6bc33d233872b636b4b36a0ac62f9fe0dca57e96e4cae872be449ac7f6297df0ab560bb7c1510837e970a4cb61627cc0d583eded6c55e7727fdc8ab30d9212ea350791623795f54aaabb06daa080c4e44f9a543c95b61e01f60757a789abc4fa72f1a343e4c86d125d50fe94c29f1507546506b3e7f90b70b35343ab2e14168bb3131b633898431f767f753051ccb4e99f7d8ee4d46f3872b76f59bd2410fed526bf2265dbfe648d9a1e6a608e74b4f423559eadca262372c526cd2a1e11a773a73347b6aa96e0cff5d33cf52b9119b661807328620c3a72743e61dab59f6bfe4f1cf3f66191893845243a8c478e05fc859e7d74e0de6c04edcfb80f3113767bdd0476f885cf9c2aba891467bef50713d4cf5e8b2a3df42dcb80c5e1d1471620130584add9fe9462eda64553b0388ae769d294dac16710720f0bb3355cb2acd9fe9d7d286015c6dfac975c2bdcfa1d7578f64afe9a94f37231d918ca067a104e4edaddabf08fb3faac53572fb03bb84f746d7b62585f3e542d4fb931fc7cfa178581ce9d695214458b04a0d7ef1e63385e038fe8de7aad13e559e5c574d4776b9b3f95d2e754b82a492c7983a3f0f2c0ebf71d81a54b25220baf6a90539cbdb160d7df21e57da4e86ee9adf97a9ade30c74965cef04cfe72e0a1e8d6479eb18fa06c2509b9c9ee4f04a1e52c1f29cb3a3573622b67336c0df4956096b45bd45d3acf35beda4d718c01fc7a1a0f33140137956e2e4e668b726b380dbf1d4d534a911ce276d165b3aedb82c16fcd5b52e91eb676f5e26a0372e03d5534ee529605e33e1cc43e77f9e0d0a8289c8943573d0036573fe9c79870de94e27e7df078f3ffe8e9cbe79cb34ea4ced8db0ec775c8381b057a13fbc27257fe12ae47743a046d0ac4bfe9511790770c683908150a82a95b328b845f1a0bbfb2f864c3e856abdc86bcb1c6a1bd79d7bd4fc94844ed7390f0d5f10736d972978d6791acf7b06f470e1b9a2c93ed7b849dc4658aa9d51c8fe0ed86f5c6d167b3c40ec02a0b5c54ff18f5f1d9fb7536d6a806e37b3911e6f908349cff904f61884258f83f77e7dc39f78fc651bb0d0b5eb0cf54123fc1b686db54e37f08cf72a3f33bbbfee3a69eb379ae8a822195fde8ad7d399c5eedd14cbadd6b66bda57232ca5e01787123f9de3d2383c676f6828ad87cff936531cd65e35e05bc548265d02931ac87cd6081441bbe3c44e1bfb089980c8a24f4497207843df81e636272bed3b6b0cb8e6606e9a27690eb8157062803fc3885e61c6a493619a1bec5035e36d0970ab67eff5555727ea199363ee7bc8cd20b47877ece2d670805df7b8d3de770209444854cc42a25462323e7a5e6294caaa4af0613d83e7b47e3380b8972a10cdcedb5e14b865ab85355e5b331c48185715f0db633359e153858871d7b72c9a27584bedcaea360cb29260c2442c9dbf332fe2bd69528c3ed0e2f278cec147372ae491680bce4e2363a89785ba2a6cdf7f806503c438552f769c12810d872da6900138edf079b88560f4736e560bf525ff346f11ded5feac8d6015debbf7243051c4e6c6001d8df358db68920f70323e17cdcf7f3d75243463a8ccaf8f81a6241f044c28c335eb141a3628fa4c6329615f3343f853fc5a7b542068f599a06fcf229a4b661c35c256695858e668cd8e865ce31b7518ce90f4b0761b9e448330f03abffde99b715709fd214670edcd53e3200bb972c6ce3cdd722cb21bdc0265c7861aeed5069d6871211733cac27b5e17d84c23838c0c60004482d7e4ac77298bc0c84682b1b7aa2fc584e61a8cd25f479f61c5119c4706915571b0ef1d172e399249651f5aa898177709006ed41a5e6795c41f1041cdd29832fcec8c4ba724795f88388992320c7f2cd63437040b0dceb5580cb0b1ddfe7035996e5fe8272cb8038b7971c79689a871f60b1640e5447a5a7adfbdd7816d63f342eac60665f322bfd44fe297a35b0a7ed28efafc161a8d4e415661ce5f8d7caabffa3312972aa0c28bd7bf3ef205d1eb54dedeb3af12648d69ac66e840b59f403c2a99db372b36394357586e09b814704631aead41637035b95fd694816d98df93f89304f72fb3c0bc6c9a20295ef00126e96cd5ad691d2480d8de971186a7079fa74974872eeba72e903a395324d3322ccbf49ff6c5a081736f4fe4d49f28b7122d478e772f41d5117cb67b505a0162e6515224c82bac0df6c6f82d8ccbbe12cddd6da7f720cdb85ca744bd0291a686d778b448806e7bfc4bee6284804a59f2eab5dcb7964738ae1f54437590c7337a1eded1e986ec24f33a600e06680c86766e7f24f1172af3992ffc81bde7b5ff572364b0ccc000b52b8d17c5d24779d66450d2e0f6a7251d2270e1b583874ecd2b1a57c60a3a71dd4fa7733ef3e172d6edc5d1b298b7274f44a5afc1df77e3b92cd54f8970e767089bdf2b6fb23248a37aafe82f8ba721cf1b44632908c971f95fb31ed0261b41fb3612bfe73181df9860c2e45909372ec974f162f0ad962b9afd6e38a0edf87587c23f17faa6964fc73aa138877f62080adf98b6eb55d3196e2d380866c4202a41936f3ecf3326180b7350f1650545a3ec4c761849fd996764dafa6bbf5acd380ef5d50ba33b292f1dfa5d68d7d91222dfa07025977f778caeac88ec62e10cdd6fddee9ee375b47fbbd23dbe03b23728329fcfd73d29e82a5a6ccd05090f848dfcc9176ade37fe734bd601f600d1872735c8e700ccb6dc1965bbb6b50a21ad4d9c1739bdf57d952747c75f5689f330528d4f86670938a3d5b818a3b71c2c263a454dc6a054a77bf026a47651625bf2a244203b5645642fce7ec68270aafd52d57f91ff034757a3998054615f860427275917c27714ce7a77895ef7c07a4084e6f1fda0ea548729e8bfbd9f17d4c04211d85cb72cfe092df73a0d913a3750efdcafc0139892210f13c9dff100cf25e72d855cf2bdb3cb4b9025ee007bfc472b5b41bb11c7afb41d4dadbdafc66c1e33bb3e05f1f2b7dfca140d5ac64fd90e5e38bf76309957a8f5b52fe684c77349440506b102f06d0fb0afc544e928462ded494ae7cc2cf3bc85a0c2c1985694cce725fa25f82531bddd0a6e21ea61c5a7ecd4bc711d217c6afd7c9f04bbaa8471539e36634425ea9005ba6eec75f2458cbd7cdbd00a5098d5e694d7657c2916d9b5c0e0ac99d135596d537ce308ac19288cfd3ae0c37272526497187682f702d8b721005f312e744bec2e8ded2a02aa77c6ca47af881d2f89d68dc6447abad798c72ca97e5c88052db93df06b3eac18b7759a9bccdb876450481701b52299690b706c4d51aaa4017e32118617296cc05a1a137b28ee09591f5aaa82ca4faeed7e67205ab933e91e57f72e95d15e541a3f45a566507cb30384e1a2ec338cabf2c9872aff2555b3a29c64a351b41bac29f79223401a0227989f1217799458bf9fe8c728ef2919d391c338e7465248a0b6aad54868e897bf04443e68e6ac2ac9abdff17d6f65ad2c3317e38c44c4e696573e7d90399dce35a36f487fced6e8f23cb9701d6987e825fe3a3953493a362c7f3e920259205a4f147bbde1cfa66fba3f717418d719082a3bc40366069cfe3d019001ec0691389c4830d8e8a55db23c085e1725e72917c04662a886091583b611edd57bdfe312a04ace61bde88a6c1310b0972e124a3433acd38756facf7a8f50c630cf8a5553c84b6c2277a428bc129298452e3ee80932800c0c8883dfac7691ddc11ed300a0d5664df1802a55bf8895b0528bb8e5d15c0b50638f3b03806212bd2c48174085bb2674e4c94dc4659b1cbc52a6e147f3181d62f5b5b5f12a53f9ab3dbdb7da1bc8a770089565acb0f20d56f4f82dd175a74d2f9ebc579e3a1828cea854377d6ac6b32ae0d753a2690d0544172909a59f9b5ec0991ae9d72103ca5cde0bd5acac33e17064fdb63bdd1a7936c2b861a54e74236d6c0a9a73fb52cc60eb21c654c0efc77fd7a7d9f219d9e31323716d20a36f6b0684b88cbe3bae26ab7dc7a5318990994071ec0300a725cb20172d0015b909c7fc6d9c38198ac3fb45e6301ab9dc794b54286f038f4b536035a720990a93ce63b8514448b5d1a608f4aa5409c949086e463a0a5b74b65e2b3d772087ea3f64cbdca8e668b8342d5ec611c0a31d0a2c060498e38f8516db2d6707288a5f403aecacad292d60c15124b3c00ab35868567ffb3ee7451197ca47b4168370bc634bd35e9d7924c28d0a956463d38b93847fa1a4fb4c2fa43a60c0ddc652b48f392824aa62a3836cd194e0baff086b8b9380de3eef8ba14072d9750ce1c791f61466e4e164463fd996e181f5b1db91c25ece82ddc9f1c8a8fdd7f9f0c09565fc4d0f3857f481c60e781d9f7f5d87b83d4955a937c213b21b33f15b189728685d296395b0e5997b23272a3c5e3797c972443d836d81292954a185c410c5df0a5ac7f6c5d06d91514b0ce6b9886374f553716759cc77e8b7b08392e531805aa0aee779c7f895f4e8d2b2ccc7999549f743322ee96c508c99ec67c1e0a3c713c74adabd4683561148cbf73718969a75a3eaf5b5775eabb0864007dde3cba727e1c784f0314c69bbc9728861054e53e426a4ec94400d4053d7db6309f968d72a0c9bbc3f3039562ac877e672f9cf74f8afd5c6f407d702c850eadb1cb6521186f0fd985c1afa9c52a4a7e8db058557bfda4c22e1254d948253327a977158f72b4281925342eb7304fa042fff37c3920eae56feccca5256fd4bd1768dc382272336bce10face4d665ea9c605d736285ba8d73b662c475705415f0cde4344d672060e44fc94f60634d5ad63b100b674e815a32e65cb2e203384c0d7a086e33872d54554616f348f2aad0f98f39f0d11f5ae011c45647bf1abf528dc7bbcf3e672cac6cc1afa8ef0b5d3dd0da0dd84d812bf227bb1111cb94c4faa097c9c6c417268926dba9c565041635f2be137c6dbf91604bcddc0383a083ce1fd977818916b44ffe81d010cd1d95253f82557fdd227689b129471d0d5522688e9702d03ce72c05aa0c25e06f806eed0c5d551dd09634ee4c555188253a0ed84c2e3958c13729060d9ee0c99dba6f0d960314dbf964b97472e2011e9e8127c4d7cad387c4572b879f8f8d83e2f1642c25ffbd4391b17836fc55e21b8687a173d2e9daf24e972fc7efec0f4cebdff286116ce2557c0b51f909289f085b85a7dd532a3ae979672f8cb5cf233b82155289429c68afdac6240381c187a47c62f2b7ea538f9931a72d1d9382da78b2f3d9dbd7ab44d78d95a3f9a2c7daf610d873e574543003a727270271cd8d7ee5a4c06d8316f1ff5e10d3f4cbbf4aec921367ff9a91fd543fd071b6a05d01438e440e8dde0febc63d503f23a961ed5dd7c7f93293ab43402067241b5262ed7b38b188bb556947543666bacbb5e97c6842b4d2d95566b3fd2b8232eb07c2b8f739adc0a35369acb27059cd129a7389a114b762d7c56c360ff6e72dfafb0906177aabef87ba1320c156d320b1e7db50bea72f6032ce8c964220d31d068f564fc6abf563153f01bbb571656a6006bafef81abb8aad38662f58bf572b1077dedf69d046d52e4a65dd1d5114eee7f343e88ba1776e4aa127328460272b1c40206382acc073c6a3c5d4817d41a8d5451d07ed3366414b75fc1551a5716e0e2703eb20272e6e74ba6e939c519cc3bdf7947e5b7f84a1d1647c3d8c235722b719f4f12d15878165abda3e918cb005ac7df632a839d6bde71ca3bdc8dc24424e134e4ad3ef2e01a6b36390621ffc0ea9bf9c129a5053da7de2e60585be6726e1d8bd037dab580080ca6e8449a8685e9a694b41fe33be82213183e12274972c0ce788d35c5d9d641442d5cdb126fe19e9a69fb8a78f49647d0b7f817b68572ad6f3bba8dce82c660404991d6062fc1be30d3839bf1ac840927bd0484ca2e384ef3572a0e8ce8741d51b26fbce684e10e13daa6d36cb7d6ac914f28cae39e721abf685cf90fd6111ce6a8011c54e90c277315b68531b9b3dce90e5bde203372a4bfcb74c907ba22c3b3ecfe5bc40623ac805092d586c40c568401431cc8930fa7ba333ca8c93037a1fa369a1595fc72c341defdf493965b3e8595352682a97208be83f853f9adac60613927539dbc61ae453716883c5ee71566b355f6669172de66619b657719c69f0560e9151362dd9bb02f4e7535c4000338921023357e722f698a43e74cfec55565adba7b996ea45da7808c72d4ec7cac1f588641893b72f2e0fc0fb7b30e8a37cacef58e5dd9610928a9b63e6460944f1b5e93d409c372aed2388102fccf4d946a756641deb1ed4e0cb39ad831d1fb5993900c59a34f723dc1d5ff9ed086477dad1d03f9d1a0a620b0bb0914ba3d17641a102d61e2f972e0e043cd685d9fa806d39f6f6c913b3e0681eea46d6a76110394eb397ae9d572e74cce6550153b2ed32d0b4d733f35c658ed6c17a37c8df888b151d4e29cfb727da3a3fe5a8329f7a480b9e24f521799669227cbb81132167b1f561272a284727ad963c75987636a5a0a8e4e593dae85f01d3d80d44934e1ee18f796833ee23ca358ee5934064433135340b983a2200aff2ed916c0e471a0c33515b205a28972934c63598da395c131f2710d6aae4411eb2cbc2b47e3fc44c005c22aef38c1727ad4744724f08e77abfce1245ac61ee052d6a31aa11f20a95acfc8d396dc677252aa25b6428e83f161b8bb1ff24866a5b14b7739f9667d1ceece112a6d8b06298b7141f6efb653f523b5a7e01beb90b698a5364d50194953681e66192334b1727f62d01e24e3b77ee26f3e18a85c91583b72d3dc2b67bcf3e1990598471c8a56729b07665385d66385f5f662a90ba25319ec3122f78f7a435696b2c307c24b72da3c8ca96f33a9522a0967aa930ac9e636046215a696b790e0a23ff5ff46b538b7d73e0f2de085fd6dd29421d93214ce280eebcee9ea901d8bf0c151893e676fbdb56a48bb80a060bbc8faf9a4ca6f7ae01b9002d41ead302a34d249cac423725579cf8bb5e7bd96dcd8bb8bfb083499e392626eee8774d9a75dcbe9789e2403e93604aa5ab827b1dbcfdd9a82670c2ebcfe3a6096fac44d5c158def0dee71726d32cfc5e69f282c6bd9e465e6f05ac33fa9006384d680023c19b4f019f38b7272ba185b2db93e7fc7d7a09b6908f67ad64705229bfd4f273fd08bd08a81407208ed4b2d6f414dc3e4d7a53d003c2fa2ccbf53d82093f0ca293a71b4063d7b72044a2bd7a0481e3e7aa588d1b3c86d1839bba208c2ceff070d0fd01341de0d5f7ff3f32a8d863269c23271de646f2d227c05c3054d50c12a98a5610ecc39f43b65452a84a58b15554d536e6107076677adcaa6e6efb5f99f25327d76d354fd46b9d39f259206a448c4538351e519e77f6ecd46728094cb24e2209d5d6aca061fe2d57bb9759b7a87dc3144ae4ae072ac2b0d2c2fceeecb06d3cbd8bd56c59372bffb50fe358105e5f1fba488748016958e58560dd06e67ce88691db788f20472725832e2552d2e88c48064c9c910022118377be19e96010067d6a41e261b9c72bc536b55710d03e20402c88a3076cf1bb51d6ede89dfecddd4ff7c2ffb9b9672d7dabcac9fc7b15fc85bdcafa10cfe5f779094c55542e681e2e34896be284a7232e856f144d2eb7f913331e58aa328c9819542e9a3e4dfe0a42a8e173922ec040f3ed1aa754c8e2ff08c12b6cae789e40578c52303ce26ed41de0afef8412954f1a83b7f9632bfa696d48ae0e4de45877348ca83d798925459399bda183212079c01b59d5228e830259948b2c7992ccadc44a44a77964c00d53388cc2ed3eb7211153e8244cbae0246105859aa75814c665da80a9455dc4ead9dde79e73a8264862a6f63128f0775e2f6d2f4002e52c982f0144f4349288a7edb7e4ad94fdf72ca41dee3951bfe3798a4d728f5d45cf80b139b1b23f3c955f3016afc4ef7bf7283289a82b0fb4faefd2c4211ba54a9f6aaa2a3328f0e9ffa817a9db7fd51601cbf194800f78f4ddd6738e81f9175676fca5ad0cb4515eca7eed7bd5a3a31e1722d1d02155e1ea989ec2f28b931b0919d77a537f447bcfe09fba7db6dcc6f117257d05d838955431ddafeeae306985ccf063dce54f57518c685c55d7c21ee6872d5e9fa7cf1057a2b7e18a09d94ebee24eceb6947f2b66883b37ed9a2c2ea2f72be4e29a59e0a09d61f9f2fe51a3cc9ba298ccb18614d41867d7d3fd00ab42772e9d5b206346221f9fdd5a798f726554a76ffa6fa66b26667e268848a024aed46d012a494529b3fe8c40e00f550ba1758a813d5dbc5fb81025f4696dfc653e272f0872604ce97c0133b274a4da787b8110ec5f046d9eab00bfb04696dadce0b723ddbcd7262516446c090dd4b0f9b7ef5a8c2a2ad11360c33a312b1360caaff63e8efcd2fc628b82b76aac838e2af4ecb020c03f4ef48136c5874d00e0db436723822219da7f0147f33c7b7d4c960316a556f92bd167778297673b9a8d1b3e82e4519ce088acc9aa01346490e55b2f7d6718b8300d3670c9135a3644ab232ca72ecee374e863c311f6c20876a40a346a97c96dba2ca5114356aec00c4523d797241787e5b4eb639d054b15bbcff986b9bd2f0c1e4be9724e65334ccca05d5c3729b1e166a27e727c84073c6758117ca98233de0705920dcaaa32c594974a03e72152789146a7cf1320f79bcbe332c83dc3bc8c648647870e919ab2d64eced8672f8ed42f27616fd35d04da6eaca2f26afbc910b4b2784b0b3adcaa5c84530864fae8654819c97af0c7d7a2fb8033e851256b662c3acc97b1cbfe0624654c62b72da29e0403d47592ecfc22cb620417f5543c0e9ee404f24456c5adae23bc1304e6892262c27c6336dfd87760a4fb706e04706b79c676f8a5d06f461681dfdca72878c4fbd0f8c9ff3641f08128c1d804ce807f5a44091116cd19535a67aa784380ddf28c96bc1595150ceb905181c8cf174817f49f5cefd794eb4096031c5f5720deb7d9cf695ecfc7e525a3dea9cae67b486aced1c2cc7338a9667e20f86be09100789c4e7319c80360ac9b52fa3863b650871a82ea9993f10a55245392d7442739eec1a83c6c33f7449329269c503df37a6b5800f9db6e93c69ffde7e20cb683bd555efd06a34a7db73fc29b38e66f7017ab292dd6515bc18a4c8db6c965372873af46c0f0abcb4c550a74118c29e7fe8fd7b886e960dc9d038b269e62b77720287c0cb2de17f817abcdcb60fb5703e748d2987a0d8a30c08707f462a26f8729f63bb219ca3fd2247117bb4642a3706efb97572362e1420731178403c24dc22a19df201f2b2d666561be128e0f6627297b2ace18327465dd25c2038b04d897263112cd8538d53eaf35b3d6c031bfc97458aeaba2a5706fe3e74cbcd67f9b2499630ab524503e6a9dc48a6e5ac9f48568b9966fc7805540dc845eea050aa1872d63e8f20f5f0a9af7c809b93186a45ffb23180411d2d246b21f88f4b77d2bd7288f653708aedc9504b344fe503fd23603b3aae347b9568b22fe11dcbdd23443891d41a869b981183e894e022750942b79be9ae6a175c7a30ee5942ccaa48db7218fa953b2a55a2165ec92b4fd93d65384ba85acb1e464a80b786b046b54d18328c72b119aefa8e7534c7eac17d54a5768efa000683192da51d60d0e3fd800b7246bb0efe260e640ccddff9617b776c6b4e664ac950f325b309ac9604a759e872b885a3bbe17cd73f086e304fcb2bc157de7a132c20c84b07b05342d21c18ed4e1e83ed7e4f5c20249a3063efc68579a6a27c651a292f95ea24c30356aaa3ac53a08819d63fb0d71c0bfdbaaa0d74ce86ea45c293c18f63fd428f55edc483fa0938b43615401eba247888327a7b9dddd88c8634edc210849abf236ff42f61817268ce54480d35a9036ff45d18424dea51438e2e1c061755548eadd4b509f8ea20c4c3f656d23a19fd6c441b4f27d976f27bedeb7b4d58af28f211a3cf1306cb72581ff550301393066d76f918af90daff2c788c1b958baf7b5694d4254c00dc723119f8813620422282e14ca8ee9bc13e9cfdbbdd5206ab45dcd0e28830467d1d9e9529005dcf13f0b7d84cbf85acfe69c95cfe54f5ffc790784f0759fe9f207225f2f8fcb8b1f085fd7122a510f577b6570d712bb0b77faa2f3619efb9f6c427954a46bc1f68e56f9cc74a3b500e82596dfd27519c74dcd42e6be42cfee0331fa977b8e4aa0bf3dbc108622a09416a0f3315e8d7db363e2e3ceb11668ca88350dc9fbf3a3f0f0f251cf9673d062638beefbcfce15a38a7b60fb370ac3b245a72830fbfd33773974f18ef16258a14322eff5c3997acc9598fba04c216f45566425d070c3073e9e24450f6b712295df66ab03e8832a8275626c7fa2a867ab5702045a3d7e176b6f1a963a237160557b93b683b815ce505f1a576adff08e18aaf726ea9b144a70e4bd5d23dcbdca7e53be05c66c222a3a44171900a61ddab712972d6560d678ce9f8cd120a19bf9accdca158504cbab1d08e8260dca68f722fe8061d4f05cc410c8ffd0443c3aa52b539af51065dca1d7858b5d14da3061932fa7278f4d02ed8cd1283092a0e858f9c7585cf25f76ec6b2f184422e3db6cb2a14720d605dd775cd7858f13bfc44565f1c1b711a739fe64bfda86f5cff617c71892bff7666834bfdd502afdea20768f5fbd47b52b36236f3c460d660442dfc5441725a7cd44dc0a65ceb00fdde32e6e3d555363a4db23ba8abd40e6c45420f2d987263960ec3def187ec46eba7854d0ad416270d89c763bf1fb8975802b6a88cf5723cd341ecf6c59616edf98954a04b54faa61ebb1e645c01448d67baa70382ac72a6aa2eb44debaa9d75d8da2d12da3fdac7a3bffdf146909d1bbc24d2040ac872aa639ee0e761acfe6a3ae213f37975b61f9f519e58498709693b17ad897248724aa790d4012da26445b672bc88eaf37eb040f8f93bf6b488ea0222637ea1a9725116e9dee5bdbbc523c2618e4d9a9c6259f7488906698037b29f2c172da40f72318746d313a327d5f36a48b39938264ef96be53b6182f5270370c67a45724372a56013bd8ea4d4a297e993a52fc9051d2621392551653dcbb5b6cd9f219a997244c4d4bff32aabe92f5cd895dedcf14aa6da0b577fa1450953a04f8bc15e4672ded03181f8145888b05271e7e4dfe52271dd4b2d9e36d7243e73d4f7ce4a2272780bd9a159d3718083687aa5721b2b8f6c9fe85e49a47a12896eefef488ff472f897b67f8dd56a4deb71c90ee53b901fa92686b063916b197de02d0f710b0f72601c884848da006d80b94fd73457a6c0ad57b639fc70e722393f9db3f01e53725bac45c83e864d33dffb91ae820b97362aed8a2b5d632cb60bef27f1e0252f7133193dfcc841e9de42da32e9efdb0939b120b0b740d8f23b3f50c2cf6d0d42407732269a2e9e568dda302a79b10c5763bb43fc69aee63adb816ba73d61e87e567ef22bd57e7c3677411d7bb731c68006f5ab5b79b9a4366eab59f27c6c46f372197042087aea1f792cd5186e4ec86d5c6b3a11dc66b33d2f5e87f5f5b24f32728901c47f7524c26acb01e1ffcc57adb6eb0f8d558510d69606380e064582725f1986e5974a24741a04131d180296957d1670472e7208d5721f706fe1ec7dd64f932050a2b393dc9f8517c7495095f088298f6d3f119cb009b9949487d56fcf3bcc61805f2ad087932d12a80d71467267db94ca5f79857aff68e01a630b8c8e7288ecc022154cf7f1582fc7f4a36580a06b060451227b2a2352ee8963fb20931c7d1c6afa25f3d04dac2c10fb4dba957229538cdf7cec6f9e4852a36fbe5b0830cda4af888cd64c66dc21d3babfccc952efa3b2671bdace80b770fb3018b49072015cb4e2f2ed91dd62ded8310d215eb489998d7887bac10a5ed99f6c7e27f772b1109c1e451b95b052af67cc0823ea3a07104ffa9c244348098279961b892e72271f51f9b20a52443db2c53b0b090fb66295a92df660b1bebcdd4cd1ce3b7072fddff671095a351fb6cb06b02ee2eafa4ec8c9ef89be80e57ec7236f6d5e5672a94ee910c4a3596656fe922e52b45ab0fdefe34483b26bd92110fe09be474066b6d933fcd09d4d2e449a57fecad97284da21aba60233d02e89a7e3a30b5b6d1b078dd144e661e6f5e8523a2a97f566c8a2dd58c90b6c05dd8e9b20e3323ae6729289a0ef1cd03901fe211bdb8916b52c64753f124ca3605f14b1960ae9e47872b6096362fa484face602928383ec80aa78bb40803f9ae6f62a42a71e89b9ad10285c5a8058293c96f7ba4f2c92f303b3cb40cdcbb194ed1af3e42a8abda92f7226e4d0250e12c3c94adab62650bca08d7986dd1cb1f18255247941fe7da1b072f414f9bcc4516f060feeb4ced0f4893309a442b6e20981d312d9949b68ad34221b6cad2a5d40b587d4b504c2c39bc51ab7d1ae7d4f566c9d25e58bc15bb22429a702d12c4d8b62be30bec59e1d00d8908c9d5fb2ec446d936c0961af126a087286d1320a37eaff24bf91f8a4afe3c2df6f3d4e66951d938228ce842e9edce7095bf31fef3c7075f5f55c082a7566c9330517dcae35c9d1904d0a2a12a4b1127206e83064dc3523b4d9d9936f796f6d11751b922b1185648eedf695b06f6097722e2c39b5153e41add56307c937d81d95453293f4077e89f9a092f2d00de2d1725d48534486966557eeb3b4ffbf9be4b43634615ca4af2243255009fa94c34d253b98fff11f4460c2cba5102f28fda4f2315d600195c606dff5a0e4b5c824a07215da5b1656092e7be82f8949ea9f3e089ffd4a8dfb482fa40f80d018a0c3df722343101392ce09a8f87e596bf07915c8e4a5f4b998ef02a4f4504516fe9e2372988bb53509d8985e8cebb7c0f8c9162f68fa31f7731d54efd2bda596bd3e44725267b562caefba386126d3fa11a8c55eefc67f33696deb735f648a66c1c59d72d9ec78a5fe59b2d820430a9388ca17039f09312750df37d161e296cbc0c0a46179ecd32526c67a1b085e3e7dc6d983a96b21ad0158db166aa008894b1fad1e7290bc8c8ebe31947e289eb1858c457e3bdccd30b0532e5c5473bb5bc3ca317850f139230c213629fb77e2e066f98d86532106d028a8af38ec1123424f8ceb351372b5f84589134050735a39e97d7446931a39276cc14f1507f570c7826f5e5d4704fe4cd0b47ec8108e43630fe322c0040b10ecb75b40a9860562dbec719e897238f19220dee47bf489da0dd732a9777f5c8b34bdf094835dd762e2f5eb914772db451b6f320f6b120a99bf5b710c9104abd4d08fa217df23f09365d1b720774131e828e00988a466606a00ec098120d1784f92db7025900ee0f19d01a145c37263ddbd3c12b71ba868e8fea8f31970caa35cee5e0732e052c91ea164d01ef572de2315f9906aae3ae34f09a4f4cab6034e9252531e69260a046a6aa85e187f10af6c44fff85c8897ac75632e0dca4bf8a55b3f1e6b321baaaec34a1e1fa22972699a61d23a0758290796a6f10c6f72cc6207f3e1e53a8a7d4f285a26e997f8722a63bfcb0eef8149869007e1f345e7270c72da1307a3ad9332c5c76a36c95c2fe5abdbb81b8a6e8d25a555d7551b7ac0f7736849e847e7b5bb59b18339e07864894a37e34650ce30e34cfebe78c8a5700d1a5f9759ca5fd6e0fcdc2fabf0451fcd06d52e3fee6eff7265000c80fc98171c73c01a3a27c164602d36da5ad55872068e4a68cb6b9e9e9a15ef0187f52be7024a11bc729182c2b19b71a25e77597292c793bdce8127276712efda049d890024769be0b83128db4f79b24b227a736f9c291d48eaaaec111ebcd1c9a26e3ce61e613f076cf76fb7e5d0d3bc2da1ea3bab858a6d0365500de06d06eec80aa7044938d563a25f83164bf51f84767b182a626ab6da0120e9276f4a2302323824a1b80194dbbfa9b8d7cbc699fca4387872a9b036f6e8f884f9eea3eece9e65e884dde8471cf744ea197a518fb9ed15f836850e464a1bfc73d5cd0639436d6ad88c0e86d7e792e964207477544c3ff2427282780390d1375d7aa0848d4b2b8ba5bf03b7ce16447feb913b91540a5ce2d2063875b4b23f32699a4c4aa3b1f05c4748d72b9afcbbf4fb8761dd60c21673dc721bbb327debcfc059b958aa86f08f3ebef22b884d01a9721d81922b1a93a9ff72ea50e4c42f3047dd878d29daa3aedf84d84990c4fd16d6fcf36698206658dd0be03c7e492a8d2f89bc793dc5c037e3fdf40a38230567ad4e38fa6c48d82e08147a338c8ac2a5830b1e1305dd101d825f5e8f71f6a2edb17b1c0215bc250aa3725082443c1de503fc224a72f98d3af7fa890fce10ab4f50d3f5c928532f866e72de8f34636146ed861f21ca49269a8de01826eb6c34740328fed457dd02722f72176125bae6def41c0b12a94f5ed8bd55d9ae243a77fca65b74c99a6c6f6cd43e104ebee6c1a66dd5ccf865978ac075b187e44bf075273f1620c439d16bf2e7725a62db90a1971eca4c3a14f448382a51cfd6bc78247ce20dcbd3316592ec737203f05bf4423bcafd08a217b664c0124f6ce832c53ce032e6897a4e5a3224530999c54899fe7e46d764fd57d8eda5a39c23c2da9e3980a9a18bc046024f6c0572ffc3fa095732a33b145e7a96b4f4704e7002aa0c3d6d2ab4d5790428ce529316348f243d9503f4e981b52c594ec23b2cce38a26262942ff345874cc6bff9b5449263f60f54191a4ddbcdd9d57635272b6ebea9948c19fc68211878a24560e84e2b788d5ebf8768d2fa10a9bfa3e5f013b5f0ba69012855e35e8a2722154ee3723199f765f58e37095c8f771fa1cac37df9b08f382132bec95a2a0be906ce0740cd119a27c9a91d5e934204717e7dc6ce4f595ea10654da16496cc7b3279c6f4bfe0572462f783cdaad80a49d4f3a5c1e5eda3b1408478e464a351e97d00e8772f4463601d06df2f93bb884d584c53481a4aed7bf610fc2ace2543d41e68093729c496aad9a9e5bd0fce8111e90c3560582ea3cd62adf3bab1f729dce4e80f85e8116626d3e3aa8ca70a7a52e6e47c9f8cdbbb863236c039fa12be8247ed6b939f11a908566db67697ab9dbf11ba2a7a9f86a71c5e442c82b501a64b7cdd27b1c048ac91a941153edeaf4b7af2d17deea8ab66a187ff1a8dfdaff3dda37929972d5b643742f1023281473d8146a1389f06ef1d372a9ce83fa814ded928580b201083c5726c5011463cb173ee4954662a857e5e9a3d57cc3a9aa94284334c8c3171a7c96876fa89f812758df2084b6fe89c0d4d37b75488826aad882cbbf341c3d216339d39fae81f98d6deac80fdbf9839f0c501603a232113dd62fed184a69727b0953c92cf4a793f0a518ac4bacdee1ae2ff3caaad3a4ab17245a0df0452772fa179f7d359e64dd960bc4e7d34d859004818bca3d24b0cfd7f9e8ea8b6cb8161d536f5581633293b8e6a8568e0df03dfc1ef6540b6e583011700ff4cac3d96ca92d7d8cda8f89e8babde0b2869ed57e0df3689040908120795285ddd4c528723fccbf471635e28aa4aadfe2e66597bcb0fbfe919f7508c51d162c3730a58c6c11733be918ccd73ce819d1cca2061dd445cb9887dba6c43522c6cbc1a794f252413d518f3634d7b9d09fa0301b27f0a7d89458d02352b306f0706e34cc259a72dafe3b862b2e09f2916379cb9f910342be7cdfb3f30a49753d338a15d7c0e8336523c25f48a7d8555508f5db57cf87495f468c6692bdd6b5e63472f9599fff727ce6e445b0fce9dcf8dc37752e1261ca571f9eb4ab5f2264a3425eff441ad8724e941be9ff6497e40ae4723aa43fa0df0242922cf9851f2f2a3e48cee3de937242fede62f762beb6231e9e041487bce3b2a80af22ceeaa4168fe89380cb32072019e5cb58aeaa33dfff7c10df3164aea89d3ff32aed251c4018c08347307e154981582631b5138952f5fe6cffaa52f4367c66d9f00a95d8628bcc73b37175c71203f20ad54e653286cab1fd5b34e3fe0e8594259452b68cb86a2763f1352d55ed59c060c464b645919a289bf54cbcc55135271de5e571992f0c29e2019652f62e47bca7a88c2f3822b90de6f26f8043080e1b027b1f6e40f2bde981b33a9e772a3548a84f107cb11c64e64aa36a1b85d57b9bb1fa9dc054d66fa32093fe10d2023683caf2bd9fdfaca0462a552d3bff64d25783a77061ee6d817dae384bd4d18000f8f90d7caa6a46f234875350d870f63afe578b39ed4252332b18f8e1775726d639f2e53dbae095e2481892a95eb7a0847eb6ce976cf38ae15f8e0e8cc024b54383978ea4f59fc44dadf6c7b7def695387327b5a1873da80addeb5562fc272169061e693509fa86f8c6b8df835f9c88b25eaaeb2fb63adb97545a432ee7b1bc24bc08cadd5479a273364ac7c38d7a2dd61d946757e829efe41ac7bd3ed7072c1fbf4f4e6c3e4dfd4784a94283ac3eccf663d3f4e94eb6d04253ae5878fcf63fb6bc8bf23ccf6e0781f26cdf3e71e57402cbd6e02cc7d549816df849f4a731d688411a3818fb715f5fc6f2fe01454f674f2aea288f2e0d63d935755db85ce206ee9d474d8b40da508cacaef8dcb47a6f14c54e8e488799b1b09d51a28a6d06af070f4fe4f41f2dee626efcbea3b63b652db9a60d2bbdcdbc2fd548f1f3ec372c9b517d27cd0480c6f4a6f3455b5e56ff516481cee9de6057829b85f223d1b6420cad4f17d8a789ca46cc753aa71d5181b53574449d35de70111614fff598d4f0818ffe7a730ba4cb660614f76b2434dcd27aec2cca7d581c8206b86b672e5494b632ba2a7746b2e7664af29d757bb09650de56e503faf5d8240c2ec7fc9070426b2019a8ecb42651f30a35678d72b5463e6cbef8ccbbfc8559c5075a1609272d8570fcffbd261406cdd956ed0085add4ee4999365a594dcc6fc4579003852721a4da59ac8d2370ff5232da2e5544a216fa1fa895517c531a4f361841123d01a6f713370cba481781ba9ba3d9d6a3166538e64dc23b9c82dcf166e8e341c7256ad213c6eb79de417e7ce5307e955bd4c607c7e29653fa71c2c7ad8aef1d2e26e64c31a24f34d3e57f8a14723aea041ce84e435bdf2613f2abc02132871164623a8a5b3a4a95abe20c13b6a1eeca08cda96f534733f9e30e552133ecb0a215672ee8d0ddfae04d342b89d4c2ea4a93cf3753be3c67318844be364c58028c9f0127154f6550bb8cd70b17fadfb73a7206e4e1c8d3d194e04b546609aeff4f5cc7283dac113a384e93b3e80e0ed6b01a9e4178936ffdc7129fbba0394fee7da733cf8ac1b2f860bdb3d9ed7e8eeec6b440baccdcdc0666bfc0a6e66c8354bdef8720ecc897685cfc671ec56eadf4285f9e93b284a233910ac5587afd36baed3177259bd86c97d12258b151d0d7eacedda6a5812e2e5f890e22ceb9b574c747df8726207f4301133e1523ccc1ad72a12d8be2ef10eba934855fb9d3d777111af1f1581ca3165431f8b942f41733f216ab6a651e5e81ffa7dbd1bc6b54b994b9f71724abb3e0db9be7abb46fc24cbc4d97241268630db0007c57671dad92a15eb9472cc115ad14fcde681fa902c23232773a5f9d8933272accc3d08279b245d1cda72abe40247919aea70da4783763dd61312e99b3f80e17dc72fe17e009ba3e20372fd9be77f00001ab2108862eccbd812c07eb9647c35931ee11d4446b275588672bb40b321b41167ca5414bdc506225f89d999839a92a82fa40c96458bc3ba365cddf8e5e77ab14f288656eb2f08e88ac2187419ad25b299d9381c649c180c1c721aeb619583dda616c1bcb4c805821535c806d611079db2c7fdb37fc086df2472f29e32d760e89acebef5e0fc9d25e7daef222a409be6acaba7e558f64604bb721b6aa360fa5347eff906c21944d793e8dd2c73f8736efeb30e4c5c5f2243d30a3eb42160cc6d65c39022edd12aed618637ed6601cee25a65d3c73d6505c40b72e70108a99d8bd98cbd76ff99c084894195123eb312438f8a149cbd36820a1872b1f4389f7c19641d33cece0afdd0d505ac2f42b11a7d692b524bb2af86099405b13edc96adfa7f736b4d266e0d447aab2b9e97199e3b17280a4457c7077c673fb389eca6f4f06386dd94a968ab4dcc6a97fc41f024add3fb40e22659c9a624720a1eee1c83655b2b32d69d599ad2f2066a12f0cfb63ab0eae6496c7496d097347b0167177d2d457bbeaea6176ea7a6085cda7d4777a699b3d220106d931dde67c795f3a639ef43bee99177c755d5adfb26a4349daeadf33d5f7f516ee6fc1e108c6ff5f548f33b2949afd39b23f07f979a91c7a7bbc2e71683e66ce5c83ca444408cb4844c089afe0706e630c151175c056afc181e8d4fa03a48d1807c8a1e63ab0bfad89f8cfdcd8feac257e4290cc7b1357945803d792437349d71003f0b7273afe817ef7174a2405dbba925b42300f6e240493d3437740461766729b773722074d2b2191f3eb9a54f66b7914686409ee26b86ef9d4e637c41f92869da5f06eff0ea9525a2da004daa848518456e2c8a3cdeea331bc154aaf59579fc675603d3ff6d21c199bc244e089daec5076cdfd372e7377f68a9dc13094c9c3add44634269c49de7883fb4a84bb22cd9d27cad6bfefa697142b861abca298582e048425ae6bbaa6ea719eaa17f602ef830ef7d7c5db197c0f08984eacaecfc13da1d728d7718ad1743526ab5b2115bd7b898f73f52a616615e2aa7c2490af946b985724e725d158fd240ba46be1403fa8993c9a44e4804a21f9a791238b46703e9c172cc305c4a43b632cadd6964d61a2409f312678a06346e181b471b53f3c3d6061fbfa7bfb3d13a71af2221157e4352ed2ec2c9466a69eed9b2ffecda84422f367281e3ec8af019f085159adee77a2d0815b020de91df61f513d5cde6b3ee8db172c07f66a3b13f23506e4659255ce0a1b3d01eefa31e791f8bc56d9b50d119f60e6d2a6e8e9567ee62f9edbe85d77af20e2cd45098ae09116f2cf120b44556357236cc552d41f92ba34a443bc0c484feb371ce7e59eff1529d9e8a3cfa62b09c66a746fb1efc534dfbc0bdacd30aef9270a3c2468083920703e8f2cb2bda0b8f12df93f915950739171d3dc0b66530b48b8dcbaa32753e0683e54bdd18da47a272dda784e7912f8ca49c28a4dbaabfb8f5c44eac0819c8c830cbf5aa54ac8288413955d91f656ea6dde4efa278c0645b02ee89c24465a2d03283af51f6eb163736f97e50ed0390500d8b57e88a88e874c9a3e0e6f49b065efbad4d9c238a5fc2549e6c0cd17852c275a17f0a102c4772d1c30858d92b5c218f806983f2b37bd54ae723ebaf02a464985e3ad40c227ec14544d3faa4ae479152595210d87df6af51c291e67a51eb23e26bf28645a8c7471f2dcc2cf761bbd62106a064090cced85292e862e8a5f5cfb0049e68d9ce8e132a1046345eeedcba2ee0692b86aaed571bee411e0db079bc2d8772280fc5e19789df4bddf73b29c7c01987a82037c78e52ad2c3f74b0d6b164e7988cfb586a3468b0a1fd50e026f0465d452cfd54141710e0f92ccd59a57af2289b1eca57c1f80d8d19318adacddec25c15c78ffb964933980e84d76247e34aa5c4c409b5839bfc4ff1b80afdbad21a953db95b0e2c674f0e37f6af9225fd5ec283fb3df1e7017afb684f3b35191a93ec391d11dd4877721763f631773e275740f04e706c22c96b4eb0b0484e61b0ef5e2c6a91dd667613a1dba7d6acaf12156d02d6f15266f36d2a75f2ce263a4f5530f24146ea4485725043f378c0e672291a81aee767310564a6fe8a45be280c4a63f885d6563dd53aa6b35b515c58d65bbbe1c420bde84f61ff298224bd2e7155d464c74743f9c372d9aaff3b842d05b6cde7836cc5c62cdc7fabda93d759956dd38c7fb8350b62720a5a0cac3e09d2a75ab9a985daf6a0d4364e6a8af7a95cf3e0698769f9e73a12ad3491fa2e18ac058e0d4828d0e6205878306a16764029833747809938680d72f72d92dab4b6a33100819780d497bdab0acd1a8233d61629160864fc6c46dc721b14b9c603ed6a6689db0c91f0a3b5636f0df004f99895fd87b159edbf0ee8723756358260e013a0e6604162aec8ea91c04007edb21b79f4e3916f3dcbb9e972539ee3ced7cd1dcebfc30a30fef013f4335ee49fc742d26021b9ecec5d12f772384af34b065e9b3429ba1a09c1f1d4560b8793cc8803e6ef41966cc6590a8c209f15cfdf7baf73d673acd5a6020933654f1c27c9fcbccad682a862b282a27c1d96f7047630c8ec148e4613e6ce2d4321bb080f29eef3098e87662368141ab0723baf6e0815ba2751aeda55ad70280a646710d856a4b635e85107adb53832852aa5bb2aa47134e56300970f3d0da0721c3f57e466dbace15c7a5748f3b13d4d30bffb8a921b42404c0f5d51bedb60ae5c58cb4d7a822690a2b278b4cf744d1272aa72145a407724478963de65013f136546cc173d654a82723b7c447e86c9ad729c818bc24394f36b18d6a60580229dd9f3f5bbf7fa8f0ed7a9eb767adcaa8b4f26b8b3dcc6cdb297b70b4f0538be85caeca08a9c77a70fee38f986728d2131446734476004cafcb41d6d387661fbeff25b143d2312e5f3da44aa83e2e991f054168a214cb3531d53f5f64fca7b2d9b1800e0bdf872742fe93840cbd0541cad72050c957d91f008efbd6fcc14c1f9c5a92f0cc472b0e9415bf8aee5f0018502723b9a57e585368d17e596e90d1b3a909dc8e366b5be2104f1d4778ec2e7496b706e8d1c3224970cf4b825577932cde1fb8a6ae698bee7dd0008eaf77ea4a7af4b91810da35e5bf1044912086d44678e852b2b67ac9c6b62308d03bffb61da0d4936f1e0bc9d36a270a60ac625f2d783a93f25a0fdb0736fb05f55553980cfe57222443859a5759fe60966e0e99fae61fb1c12c5df9dbd9ca0c824238e925454724f49743f96318bbbebad99d57a93aa0637e7d9673ddc9935863142665f59a402ac5ad47fbd04d2e7375415925f2960ef82a31d9ee4575f276bdb2d69742eab3310daa862400f072295143d4ce23afd25e038bdcb0bec38aa92353434e8e3a472bb5de70fa0e5802480f6954c7a6206c13a0ee1052b5048b089215bea73db4372de9b0eb05e08ddf3c92e8bc52a1ac6d30437e698c9554a07cbceacb9800787728b279e3edb296c9f8ac9de447e38aceaaf105ad24bd5f2323af53ae369cb2660b71cf072517b8be0c6eb691d2d30c45541e51d1dca05d833e231aa2a5c519272f01738be16d70cba7dc5be2084367deab51eec8240b0bc754f1ec87d1e50dd26ebd94175e8ad47b776f62497f8a50f12f4d18f46e512154792059e619dbf1959a0fe96f5fd897485b7830249774b7b74b2c83b10ef81617cc3431deef93f9c721fb774527f4bbee8c229a5f96a83480bf8ae4ba7fa2c168130a8d187b9f6c87270b2a572ac4218f8a998ec1fa70388f16967e79c01b4679f02107e91e297a8724d50a46980fc9580227bdeaba48ec65d7045fdb5d0867511bead0ebf8956d072ffc6ea12af50cfb38c2ae2f2e077b58fb92252aa5b32c28192a0cce38d2c4772836587aa11ee49ae17960a41e2efde05823369344c11e2b1fa17f2e84ebd3872a1e38d2c0adbd8ec57f60bc46fd3bb04d985a0fdb859e9a12ddd38bb8ecc69724e0edaaba8d516fa26e91227066c28a4f5f033e97cfaa8f6d06b3793ef106b3876cd0f4093b31889228f8971e31a03ffa45e39e0424603c311eb4dc408f9ae725127e7b8cf3606f210fbc46415692adde009a3ed8b0983b8df1eb4c79624507293ade0a91c13781c39c45284f21d495f36e88e99848bfc006573d14bdd2db7507fddee7d7dc2e57d720de209f5025c7c362ced479e116fc65bbdcf58c1df5972cc5f908337b1f56f7363f897bf09405bccf2c5aea833313ea8ff598c36be56725eadb8336dfbd3103edb7f44b4eabcd05449789f3788eaa790718f29a39ed92e7c239a96a16bb72be41da4b9883f876d9af2a04ec6d2ec8aa7b91f9f545570342fd80c3cdd3cc74cf45f662a7c7c04717602b091248c574e27b702ea792581721000e22c975c0228e2b824a165b87f3aefa99a46d59733bb697cdd0d8dbf362172f26e1f906deff7df44155f16c7facc2ca2cb9cd1a7106b0788518dc805567217c0060db7754c63ac6ccb1328e3a428f5fc916ed77fd68b466b1733297e2472f7b8eb9ce3c5d9c3cc9b0edfb3011cd92170842607e128153a9b0027a21a900a3c02e51d4a920cfa469cdf6976c663ef3ee380282b53177d760a5db3c152017224e5db867c49afecfff766993a2b8b8e38bfe15b4ea41522c8616c8262c8f563420d101fdf22498e9d24256083ff062ab6d131a4ae1eac48708ff9c6896fe2576107d904e097fdf7c3f229a907111fed14bc76adb04f6af37f5c26fee94e823087acd67acfa122ed55833bebde9595ead5c2206cb815850610cba91141288872b55c14c77de98080ab8d9dfdf882188a36711ab1e305c619a9615b3b2b3d7e5d68fffd22dcea2e67e1083e1f23054f69bab76684f2adb31f5b6c04c66a3cb414316824116cbd3d24846f2865103774d5421ab80dda6790769ddd21403dd6147291a8fe272ff5f001f28f5595e6f00fe104faa3b233a9436e371c0189aa0daa63562cf9b54aa75f34e64cd3b19735d8a9cd9ef08204c486d00ed20e6f12cc1772c66faf294f73f1757500a199d3d8ad4840d0c490404eface9e15209b7c7d70724e080ef2752b5adc0a4e8ff40523e90f1cd4bc5ab4ec69b8871da86199ad0a4550afa570f1ac5a06c835edabae2d7a80fedfdec32b588f5a15f3d39b09fee072ab062d0e0b64d123c7e092fbc3c74c04b6555dd834d410931e7b84e427bc0272cdda5b5ce03dc006bc06e897e51e2210699e3d1e36a3ad87ac8874d793883051fc44331effbeb8c05c311fed3daae30a4aa46ee81f840a5fdbd65bf83b1597727fd5bec0eb5180506eafd059f7fb16c0d18f2d107d9871b3121bce84e7e60019b1b7696f58e4cb3c22dd77e097dec1a171d952b9a641f79798a3f68de3bce272948dba3afb700608de305f83e0f497b19cc52fbc9d2b1dd8ca8b498fd4831b72c8097c428a45c9fac9caa30d9ae8d7cc658527a3d57b90379692e386d983887250289f476699a14041889478d5f782bf0b4e88ca94e1afbfe57dc1faa5a261392d71a2bdaaf11e59d4359b2044f66d3ee9a3dd3bea50047b3796137f696b3365cf0f694d34105c99c71a780c5e7fd03aa9a5181f133a9299a476665d9d22db0039001adfa49166716893d3179dda1c08ad46ec80fed47e56b364e766c61dff624ea1d784a16b98bc39a9161178d1b6a91f64e3c50f0e123b171d96f2597dc1507f682e6880e89315a2d5906e1fc468ccb19f8d958bac0c8f8cafffd397e129722432a73a0228ebb6b31c923c396e446f3207b6048aaea0437d1c0de7086bb26d69722f8b9254bb5982d72a0f303c3942ff61600b45420a57bd17dd78caf6fe7258795d0ad1f5adc4da260e1db8c2d24da7272d0765553840dcb7ef393e0ee63b95c71e38c4f5d0ac3effe03f1c09a11fdf2b05f148a11c96da5f19c33b217972e7181411c39d75fc1ace49fbe6b948cfb9bf507b692b8707fcb41f1a1c778d72d07e26b603ab18bfcbb6c4926b39d8fef032fd5a4ee1f031f7bc76bb9bce68144c43b427617fa4796d6b739957a2d952a9f6965c419b2348002faa3b49380a27580fccde4f7227c2fee04aad24843d21c6c1fae6aebc905ab1fc7c4b16d69672a75b55420718c9de09604a7b6b8b17ec66f6958fb2fb6075b5fd32908c583772373da90093c78c69e5a2c0658e5eae3ac540016d71b4ca3084d54bcbda13704c5ae1dd663a81943eba7598fbd107ed50b709af6917f9aeae05c7912b13866c72cef2b9609aa4005370b734094f6410669f7d00f0c11699edce2b725943602922619dba34b4b44548b95a4950928e409edb1ebe12a44b532137a24fff307efb0f134e6b72a76de482feb6b496f6cecadcdd7c9cc349d1b836ddf7984a0c6d9372792791dd262d9dff4aa15349998f06e0df75acb3a63cd8fd212b2b811ecb6b728c7a75362effddab49867e62fbd7e605964f7fd1b10306e201418a2111aca5299c2cfdb46e5722fc16aa38830934d8439787d5ab893205e7ad292053fe46b350a52097dd516b9dc218fea158570b62b83a376f25ba3b8d14d13c41ae9f1fa372ef96b2151a3e61d83ed3d74e34a2059e61e5d312b74798720f3e40d615e4d0720aef10c63f842833d086ac76a52bfdb735a92a4dc2cc786f44d23cc89b12340ea38abc9164ed34698a5232710dd94927e21301f7e88eff9f1d6c6022fd9dd3439a324e2e47686e7ac525d238c9b1cf5e9070d18258c37d49648debc299f56e721cf9a92be5dad0f0acedc9629d042a697ebc6d642a69283b44b06004f2c0ab72d0720b499c9223ad1549179e323970416d82285d995bd229d4fd20eb9da43572c7964b78f18a98159976fc7e0426efa7ac372342d88807bc613743390750053608a5fdbde03b04a2065898229be3c1ba23c706ab80bd9ba20b3efd00de727472b5e58055319ead91aa87159ffa8e298e817e2c3356c7ca951f26dbef4d48ea722edacd9d1c2bc320689e28407b0148b07de4b6b5761742b772b7223c8573b17238fdff0a780324e49ddbf225ca805cde4614dd901a9dcd9ef488a25d6bbb7c72cb2905eb194e9b11cea8ab80358254ebda82fb2c51b6a662796ebcf969b19832f656691dfd9018fe7d680e6774b251064f9e522b8c3be4626466674b15ab0d37da59439cf92b23e81e277c302bf39c183633f1c43de3c3bf6eb7584492e5947233dcb7bf197fe91e4c12740e017bf1cea1e734a0108840fc8bcf57b35e25786bef934b17b55c577243e4d6e520329e3b75c8c2f6f5e41d05997007cc0422ff72f13bc8440ba7c44e3ed71f688c90fe39c15586c7d92c2541e3c65a7c33a68672d4ffc56a2c8d582871c3e27ee773b488fe7ce8be71ce1564182ad7eb1cf33872e750bde477ec6be7147c1e54c8a14944cc514210b7c62e6a23b63a9548bec6729efbaf26b2703830f807b96ccf58cb8950dead4c53db581c69cdfc71303cc6724a2e2ccc1ec96a1b8a774889e84ca9b3eb8a34ab86d3ebecbb9ef60baadce536832dc7adc0a92a1ac01615379836a520786c9b9e3f8e5f9a42a29c0a4682c672a9de36a4a300bf572bac996865fa9befb1b25d69fd3fc7381a754237b4cd74725cf943a54c520ca6fd18d8cf540ecd2d71bd8d09c68363b294f020daf298a272b5434773467ba90d350a871bb444707d4b16f0fbca557f745bafb2f0fb9327366a391e12fed4b9b4296714a007b52c6116a69b8527640f0d072f3eeaf681487241b236d7cf3b72bd3d04e7637105e3fcba42e840a74d92150caf569b6cb29249be2ab3695a929268bee28f28d24261d57d23af34d172c815f7580820959f2a720f65c3f6c4b7f90e982fe36bcddb1254b42f58a3af377779209f93bb0447fa72a1687d743f1b0600d366b69c19528e2f23de5c14746189781bfba003e14dd97242eb8a890936f532bb4afc49fb9724d8bacd894f7aad065b216ec998f75303173e4c0737b957039f0d4bd44bffafa798563f994dbc670a7314345397016a4d09c0ee0d74ac890bd64d1dc961f12535e42d94268cbba9d498cfd2c0c49facb172e2cbe9bab45bb0dcafdf6e3c19a83a0552bc80cd14adaa86ca64ac9d64132572a59854d57f5c1626ad4da0c257453152323d262bd7b2d264bec61e995c3b65720aad71a6114407f93a8fdc379975c2b97c2479808d8af72e5f641d72e92ec672b62ae515f5a0ef0eb8b559a7596afd5e074f06b59a174a38bffa0cf5deeb0c4e80f0ee30aec21566195cc8be9d7030de49357d40c0a0af89681f9016542336657f5fa53b73af4655c09306a6e68718fde656f88eabb38e7db9c13db5b648a372bb09d7f53b7f123b299af80144e442dc4ccda30b4f729b4f17298a1df08c6a7209453c21801c23127860fb5b39bf8028ec3d4918d6de46e4a9a24c3dcc80fe72a6fe7caf7ce0ce5583026286c6b4cdd5233574ea224b1bef4fc930996b7df3729a4ca2d386782bc4b0f73706fe6e081543e947bc3285070b44f2f56c6181cc6ad37b79c8d93c508f0f09dedae4eef4273e082c64eccc8dabe7f5973fec4517042bd813c4fbdede89aa8a61afd007869155779128b4ae223da3347e85164f751cf626ba22b0a5d0a83394a03262e890a687d23a0359f02c0c47ad57270aaa097288c236fae0bc9c7764d79b60b457742f5ba4ea968fa43cb0d6e1327b47975c15712e20f76d9d794ffa5f400489b31e361fa9b4d9f3b69dc05f8967920746b87233dfba6bacc3a0eebe4037872c21904ec42de571f64ce5434d2dd1d44ab51d72afd9aafdd5edc654f5ea5c9ad8159d4ae4b1653206334b4f46a3ec9d4fe9df72321ab89971fee6e44500baf1668dbfb8eaae57d7c85e277bc4236d1cfa1e66725b67d68f7d604659fd5cee3cb1060b4c6eb3d19021e35878d19b19fed157ce7255f956faff27072a0060b9f7e4f24250b172311d660798061835f10ba2054d726351369e181af1a80f56afcd1214a6e2ce1693b271616c955bb54409cc8db342341ababcf47af6ae0e65cb00fc2229831e9d175b0c07d00d83cdde966d9cba68efc2922d76fd64b135b04ca73e6018ec324f56583ae1b1fffa582f96514ef91d5a7fc009723e70b2de379870afdc4891437c5d29ac4221cd6c725f1ef7794872d87d5ef4cec4ed3142dfe9e9aa0df220f9a7fc7a8335919aa31b7da247e07572f1644ea41ed4e89571969a8176064d5ee0c2210326979f4d841c4a80a5962472df95db17e000fab5d76d7a4a50fd578760971f817db5ed12e805485c83d0df726d57d70d2f528718027a6562ca9a42dda2a11b4517cbac7422675597dd34a37218274a674b6c6611b8bf5a89e098d4472231a9fd39709a86d76e4a208eb47372809b0442d99f033a6d26e43c06c2c19a922ddcc9c9e32d9af41f6140da17e83f8944f9ddc19f7b3f9363afd0b34fa6d3c55e3584dfc796fc4397a5828cac367258a9f7bd052a45ea06b2dda7a8da9b987007f2897cdf6f40855fe10237b6fa723c61649cd98f8164d939990e25f97d2d99b6d786c5ffd2e88b9b66d3801c7c16f4f188dd74ac3b818d48d9c28a7ef5a29bc6e6604e2b0fdf5b7a3a657275e27213745e7a27233738410e402ef3d41b3443cc60facb4f416bf2f4524e98f073723824bf5161bcf13b12243c87a7e0bc4f1e811bf54287e9d1e7dbeb78ea2b6e72b6c3f668f6fbf9da61ccdc1681d3024ddabceb4b5b1a0481e3ba6a77f819c8725a9ae1f4cbf6a3005dc747e0f888ce81115061084261bf2e96db9073cc47983be3f3cc67d88ff0e140f43c18cfc5247ef7517eaf946ba2f158208827f0d71272c9163832d72b94f45adcc961a1892b65869f26ac3d3b23efa3b451eb1fc22c72ae13e5e5bcb26b4fa707affe9a5da8723ceaa060cb33a30644c6a620be02045d7cbe4b13c25fb5e05944691b0a5db73b7a261b77f21f34bb8ac195e44efff272bb34a7dda59388410633bd812990e604d8cb784745f3fbd0b441198583b6f80fd5a700f4472464d9babc051b71681d858becf7a34dd96032aaad9eca9b1a6162cb1280f1733efd99b11e6f8d7b3e6d4abefcfdf31b2541f771a62f4bdb6c2a726415aa84465c57074ecd4f74420f63acb87e675307ae8998900e18abf42acc72acb729402f72cf5fdd8e1c7e3f04932ee73490436f6d0509c5a95e4f069ad519e22e2c7228f1591470b335f8640f53dc29dea74957a988920b9ba29cd99bd548de1a9c834d5db5d9d3d59f8ecef312284818b3518c9f953df41a9ac93fef3c72b3e35e83be5ae77338661b555dac4270d34e198fda2ea79b7292ba0333cbff72d6b651a760c5d454933155f05c61c0b0b18049cd442c5644145a9f58bc344c389bd6cbee83a084fd9ea9cbf33ae5e23dfa39703217de712ae6e37dedc5174e2834c7c944772a826a5e3d601f713e78e44dd9157b2af614d3916f728b4b560172bf04a988a60a76dd21d450273fc3a1f5715c12ca8d4f3f6079dbbc94fa92fc1f8bab89c011662c8901a763932b9ac25266d41f8ffe050b7cb7b747b6786ca2725120d115a07695aad5a23c7e3b8b2345160b6476de992e567ccda3157cce0472c6726b6e83517a44c89655399ad09cbcf4615e8bbef2e09ef7baeef4404cf77241847e081452b55d6909301c3b18b785721c7729eec14864ebcd1acfd4ebf5724658528e5eb74adb2822188e4771ca3a9f6b0ba5f582d6857c78f364f738b672d8621ab9c4de17e294d8ae1dd98a4eaa799a3911f2cb89044a4bdcb86c91bc72ee37ebdcfd0257210006f963486a87f31f6828496adffa60a66abfa2b21972728d4ecbced7daef5dcd30dc9a7da23d7564893270029db1a7e04d06b8551d0e72ed6816d1866a479c1c399a21582ac8e0b34835b1f83319172c88a46b2a34ac101807a206a4143c39473705b4cfac266d064b6904d6c358f03f3768f3365b9372edc0d93ead5c8992c2a4afbd4ffbec825e5b6e954fed3996b1a89d02d54f0226bb0dfceff54dcdd3b565a406e6039cd939f5fb8848718e42cfd56c6f0f4bbd18f1d8ed084b8b8b990dc7d1555fc3031b7fb1e8ba1db00c0cfa98ce95ebbbeb723240a6cacbe950215403063060cd35306bafb706dbf0e8de90039f119077c772caa626bead95764f77b06a9e44393f6795ca932bc2e44cb63369d629b2e1a25888c5bfb8f769c7edcb8311ef35f6fd34aafbdd7840b6889d7e592c4a87304672e3d5b03c0523d7f5a9e165636b99c8e7407a88c108506154f8acf9af6de086046fed99b8a5f3887c9a29543b7a6693d2b65724db6a9e64d287ad00d4b36f6e0cddb362ba44f71c2ab07fb2dddb8c6c669502fed84aa107199edabf6a79cceb55770bf626b5933c0ac39d5417952c480a50e44729c43e8cad36e4b87abb5fe31681168eae6f156f706b8390b7a00c167e92183971985ab709f5e34a5b248e8572f403d7c4a84665e0d5df9313243324eb4fcbbd83875f43b8ba03c9386154a30bd258dcf8df47653300d0390890cb5b828879e29ccd264c034aa4370883c41527daa67827f5c93e4e38b13e4a691d2e66baad145940349dd8b32112d4f7527d5edf07086b4f106bbddb59dd5bc61798dfc54b28cf9410bbea0a6f0f4f85a1327256463d35b106ea13a97f88709b6b68d34ffa749690ce9f8099996018de22ba72f8391e449735a6fe26338b56dcd3cf4b54ea89c9cf2066b5bde365094fdfda72aafca5f9affacc524873420a338453836d60020a36be9af9407c76ebcf22cf7280ee9929712aa6e7d3bc1fc5b894dd31bc4b7e0f71c687db57f208fbe7ec4042f43e36c7707f9fe72a5b207cd8f3e71310f249fee40f51de76b31b98a6042c72d27cbd9cb3bd106bfb9e3b216ef599432278dba4babfbcf65fac9eb8e7abe9722ddb41df08433925bcb98e1826f9024abd1faf9b30393101802e4995cf414d54e3cc9cdc029d9aeaa82cb8f5f437af49ae66e86aed55f63bdd2dc833a26469725317f3eda34fc536851c2f2aba1c1c1c3c41db06421fdcf03064d64c19605272286d3556b87058613938b6b15ceb5a318774426ace883140e22b4a408733f972dc7f78ad56a66569caa83f69c1ad4b3e5f3532480641b097ee58e8cd281e5772bf6ee0533bd9416ac6d2906cc045cb12d1e6c286e653653d8cea7c56bb49dc120f8bfd0bede6a2b0467d79f03a53f2708233811b0d50cfafefcba60702c43424b2b141d8e110c9bb7d5b8bbac7da9d4f7117cd4c3095ebf5db27fd3817216c497a96d42836218c772857512e15eb7c96f23298e2079e4cc21bfd7b8c10d2c97239864a5cd7ca967fcdb00d78f36b77540bb12e879bd60f364a9e82052cbd4a02da9441cc35189f4515f91b94aaa7ea4d9f61300e2c5446bb2fb034d6927167464d2882153e91b53345495f77de95db2ca09df5dd0c403366319d2a3d9ab0941b936f1141511b034e95f835f221931f2da31af73f4245b57da54099764dd472721f41f4d97b4aad95cc6c2f5b0cb2a67ffb71734fde9a5d023e53d8f1505f7472e3ef6515babfcf2602719f47771bcdc230c14a839105077a5d37ab355c3a461db2265751a409189560cc5e8f1992bc12f6cf4650b321f9880be94cbb2f8e0572c57664aa41187a24f9c44147849319be18c704ed8336b34745a5c0cae4ea512a90e11b89930c1a4d5c8a081dcc2b98538f2b6ad95d7afccb9dc5bb784d12f60bd58978c79edbca2cf29790da95ba41b88b022ecb7a2b078546ea50c222b2c7016b472a3986839d9ce306c04a27c771d16bd788f6460227e994953a8c62f62b0da658f697d0a0506c9c63301c13a559afd7dbd8b9decdc4da0a766289a883f5722d9695cb556041d97264ff2ff9e6445f3c345d0d2025f0c6cecf91b48cae9572eee2bd5309f0ed1799bb24dbc4a874723417cc4aa8b455fb2892c32dbb9358727106fd959a588e9d4a1d1f378a97ffdea52e67d74b5974e6f251eee157718072ce3bf084d0978f9302cd80b8802433078f9aff1c08b0ddaa255e95617321f972c831fb51e219abc84b2a2e4181c359bd9ef7e9c1e3a1cb62425eb2ba67d5b11e40160d588f042e1f088b69ceedc62f14658c0b328d866bd5182a666373247b72d85332c6376dc76b26e26c1c730ae1993c3c4b24b45f3e629cd59a1098cee672e47693f464dfaae10d4e711832edf652c20f2a978ecbdc54e68a9dc697af7572ee2fe4644f34f65d84c9744c00f495eb441c378ca4f6d764b8c7f7d92964791fcf6f0917a9e3eb43f698074ae39fca5f16b4fe44e0e1b95f2713ee0707df05726e0dc0a3e5b69713915679d2027a34a96db569f61c7ead131662fec704ed8972b8ee8bc08084cec3521bd1c8d131c167277e8d8d03098bbf2a04138648511872d52b8f3061b86418fa3af8168b9f84495a94599f98de542dec639588fefd747235235b2c5f02be272682a3305caa4fbd4a0c8c86c3ef8949a34c4f8155b36d1bb2499bbf97269e0e1d9503e7a326c0b8e23eb30d93819fad7700f08d536f6c7230999eb19c5d11615b38f8014317bbd51cbac96a28f2eca1e33664be8807421f094f78a0d6dc7a86d0259d852d5bf24f86026638c4c31acabe5f1d1819756d72d0d0c79706cc51f35f7e687b470755aa7d3bce3a00b56556a4f8e2d33eb7af388c80df3ee5ca9b2d8b87b9bbed02c82999c1361d2007664b83bcc60d07eb6d726e0fda2e05b347fe348d8b9074417dd084e230bb8aa95d89b7a91829b6278d72344e1d03d786d912deff84d62301c5c11ba90de3a23ab664588dec456b485872cca042609e4f103d1289599bc086adcbd988459bd80b6da3e5e02fe764347927eb450ddc77744d8b6eece3b6f39c2d9e87c82fde97c1b904a2863be56e9a1d7245a349a7571bec67556cbdc311dcacb10c7981aa0374d253c208223c4b636a72708a051acbbcaccc3809f3e2c366bd73684357a8197aa276a37be5839bcc0172587f59798445970d356cbcdffb6a0c5f8b911414f680189379d8cd67d3acde724588555ea3829cb1a711ac5d55365d59e457b94d2ffd0b21d5a712622b46aa72921e484f868039cee5bbca64b6de159161dcdda2755b5b5a1564c910bf78a354261393fb40e4d618c6fc1fc5270adfe8524bb97222127504ec7d645f51f6a3727b68919a1706d325b6bee9f532b2acbef65cf20699fb259809a1572cf2d5b747e90f41d20d79c4e0fc8bdf4aac0a69d41235d462189852fa397aeb19723d4f72a8e9fff667d284cbed800ddcb60039df377567ef6134c80565257827b04a380a5aff73e3bdc1971e29c391e1276c5c479e0781c6fef5eba3472acd9af20fc37230d458f549ed993cb06789d4809669a557e7873e44d8a4a56950cab8f522af3bed1eb9a66a7e50815513917ef99812462c4b07c3357816d76def91390b0ad972fff758f19c6a540c7e392bff120807bbef23e32b7e40fa42f6325c5214d17d729cff3e90b5e760bfd5fdecfc64aa67ca94d0d45cb68bc3fc6e21b48c14dbe73471ac44ace53f144bcbc6d7301bd56195c47d386bd44eb7a731dac5e77289c772e4a1a8fb6e5f420b20a35db93502219998a2293b7428437948c69a4af9223a7297979f45bcfe03acca5641d92241a5e8ad804f97c64e48ed42b0c15f8a40d53d3101dad743948285034f445a4d8ecf36453bcc62131160a2d2fef8e2b4f855686f1d85ba140b3ab6f01483be453ac0f308dc6ce9e46a288f3fa06a27a0833a01eb74568ba67928e6b63d06f29a19335e99abdebc5311a507ddb2ea3a10bc70367d26981879155e4207faa301119b17cbf75b89d92a937f53c8d278458387d8726e247343bd1762c162947d8bdcdab10ee4c2bfdfa1c228248e032ced1917df7266b181eb59ecc4a460f65c5aefb22f3249898ff8b3c8ec2c776625d589b87d7283f72478950069693c025b466594106a7d7d4d8afe589739bd295791a11d8672ec266bf4bd12a41499174544d310851648f5c55409e0ce380f03038ee42c5872539ef66c780fb994f010acee68946cd7482f9e792ebe1d2cd3c255afd33ec472f0f56d4747757db407a56bbb63694e46192d6f26acf5443b6104d037d0bb9c2a5eba97e8f7b214d51d9149176af60cfa8d752e4d1eab7039053fa3d7f6b7164f941a33d038cb096ad3614e173b154fbbdb66f2d392a6860aa734996993d544722a249b455f199929979b2c18769c3541459a1c3d312b476b5af3088d0efddd72e40f145dd03add2c51d0166a9bfa94e60333491bb9a363b66aff39d0675aae72018f31dd60301aaed93f834d2af2c688445be12eeea836b9b1d6e85541707865aa19851d8d0aa744d1e712e4a7d22d4237c03501f0b238dee91df3f611bf7072ceb8826ce32a87eab185b446e224fbe2b9be7c19df311c6ff011116366274e7275bd237019a756f7bae8922997c42fe7f395f123ec7ab6cb87b1a314bd867e2d84b0a8362fa9e707d71a98acce37a13a4859076e3108d96115126d4cf5e9a144b97d30c8f7f439263172a8b61af235fef2bf3d5e031fd4c17d9c688a1b9a1d2c1dab5ccc99d471cd50db542dd2fd8b326eeee8953d6bcbd411f47a01a4781c728522b1fb62c8ff75b9b28f7677d49548d0bf4631e4880965ca598ac26b94f92c1a4ed00890863468b9a2d96410622464e182eda0cf295384b0c2e47c999b8b72147953e03cd26255171ec2f45fcff5d3df6c9577d4ef9811f0173da5063966725ebcaaca2d1b653b1ab0de67cfecec0aafa0474a4f1773ec6a7c0e778ea1577290f5b660dd66eaa04c1d674d0051fa5c5d441c6657dcf010e3ed10794517cf0dfbb827e0caf6b43c6117465ef255530ec9e0c1a70838ccb625c73cc65e857d72dc8062c948d6d5872f329b665da2c0234b1995df6b326ec4ef895a390729e572789e75ec90ce826912c4b92a23167da78fadb3196f13969102fb621f5b35084cfd4ed2df6c167d0cbe077308476e45b07f8db220fccb167b86e01222633b8b1db9e54e113aaee53fabc86735f2aad4eff8858bacd3efd26bbb615b0083381b09c96b7d6af2cb60ab154c90fd94f660ea323d60f450cbf2ee554db113eaf5211bf3bcf688834cd760b5bdd9e8ae25bfebf21aca710bf6e85590aad3b9d7947f7231d3fc9bd9a419ad8d5b7472c2dcba64bdad137538447d97184456823b90e272e28f8cab109cfdba82612cc582214548bdd79eb27aa182a4dc1afb82c67d867220e5ccf7a9b541cff7c2368386aa31a4f526a3207bdd60946173ab6eb5d2a67258d5da48e06bde01d438f4d2dbb8249db96f62627c7b72034fb3e0ccf4240f0d9e3cd55bd36a200c06fffac2209d1cfa0929da793346cde6bb39ededdb7d8d4cb65097fcf5279b88e1507047b38fd09e8ca9485eb5f67720913ab0650f0f8c727fb1bb0cd5f09ff325865d746f1cbad11673bf32f8c7b8700d8e358fa740de0582a1fa38afb19514525b612ef76c35cc7fbf1883afec2a834ec5f4e6a19a2a72cb109bf546d2959d893fb9e634f0263df66cf5fafcbf59c77fad643e4e9b8172a77d68c02642cb9aed0091071222bdd8ea42caf52bb191cc027414c101e1f5729edc1aa3865c5c3c8a19d3f593e65f6e4b5ff8dd7298f1abab8b65a28bbe8072d2976a67eb84774ddc697f9b705571975d6b7b3d8c5ad88f5584ff4e1ab72e7292a50c83088176ad7254ef0552fb0e31dedc16d4a11e6a9613b9c8d2aeb848723a0311ce9d6a6b96271494bbf40fad536434243fde4edd0926cfe1ffacf2a372ab22970d911769022c77e111e419f28081d634dee37e76aa35912631a7b84c24d4b968dc8e9657accaa44caa67d90b61ee3876cff43e25f9e1fbd76982575f3ed5a6aaac1d7c10be92f4b9d7bbfe2deb11b838becfae62625831aea5c6176f229e0efdd0b82ba1ad91ab95fea53ade6607b83ed667ffe21488c564ea25c0927258f00befda5558043496e90ff3cf7df89fbeded6c2051f0644e671691b708a28734e8dd7adc942f09a89f6f777bc0496d6e60c4982c489b64454226033bdc3729c6f829b6a39f74e0d02052eb2eea2d3cd7f2497cdd615d4206bd028a7462172fbd8dad2ee5e855a9c4df7771851513d6f277b430316e677146694eaf149062ff16c55972a9436c62a717de37b7a7eeb6f2efe2bdc1df48879a1efdd3ff8ee51143ce85bf673d34c0348ae9bf70e71c5af4fffe9ca3d1b1957a8e12519cc7527a2b9915c64c5b3513111c60eb8ade3047bf219849a65f7651ed89e44fe4a9e5f01c676614256f658f57be72b88b7149189e1b6dd8e4f5a92b8f775ee473bb22e95ff48f4f797448987f1f44e640b378e3cd45f86d5b9fa4ab9ba1dcbf13ed153ecac451f78ef79f3b473c7f9f332cb1c4d93dfa2c529d357033e4e1c8351b0487a3db87fe67ca2202bdc439e7760497197894c45556162d3abb608551d1d7972a7d286304ccf0793480d9c2835b17130f7fa44b55a0b9d27e7e2b8357604ca722b001d2d2e2dab6c87caf5396337560b86d8cc2d2ef8e67224fb45e97f4ac572e59b65939192072a8cb02f445ac39eb13f2e3d6cf0c16b07cfb4930d9f827926c573f3b7fc2720c613b0e73a8979d42a54b426e570e2413ff254e7ee27a5a412914a98a1c14c13cf3d90e6ed5e41a235b1cf059674a1bde31f9956131313eb72b2002056228ef548adeebc246be155e73de0c0573fb255409c16d911d2f2b92be98e390ed3ae00ad9b1c198c485d07016bff80691f24a2f76f40eb996486154608d0ffd9be4f60838f1fc5887c2be4e92cdfd65aa732cec6e2965a4588d6b772673b4807573b275573930ccc30176704fdd164e5f5738e72555daeea4da6f31017cb907e6e2862baa0555d7a92ec286f3a6d5c7a5f04486b83ec039e9ed46072dafd7bf81c866b29762ebd84a938e2873f8e310b217a9a53709a6af4ea3ba372c45d2f4e2c306e5c733984e89579f8be57248d38f67d50da87a9aae37579526e0de17862c4e28050d28b8d429ef6db3892da4f9a2a84e6bacf7a4209ef5ac272765edd4ed83be0eff0ddc70378677d627c29cfe9cb08259656315a40a2a18f4842a0029d0599c5ff645d6e48bccb5dd6d0f525db30bca93f25fbf83051abb7417e901db6c005d0b30916f6f4fa4e84a74954a5b8312ea854991dc0c7564e2b08aa22151128ef57ec62d6a8b1909b94d050f60aa37e6ba40d82cac2e9fbc9ca724f93a8e0cb357200bb8312905b7dcc3b716f2efa85b57e680960abb5ead30472fde65bb154ce165fe8c5ad8f470d25e777d5eabb58df6cfa9350c20173e96b30c7b5599af2611ae9e4a44bbad21f2d9ae05182f24d20f5e17a2487eb3334ef729bb61124d51bb5ac8472846649a3d87e3cfbd668dbd7e4bb1a188077b814983a2f0f2a42e63069c95fcc54330323e5f33a23610f9c180f4dcc398ff88eaa047272d289a5c4a3df3cdc9ad03cb25a9f7a46ba68748e0a53f6a43b635a3452364e32a4d5abb79015167c911b7f26fb35ce9fafbb96ac77517762ecf242e8c1b50adc92716d72236eb53f204cd235fd7e026173946136784e192b13418213730d68c97b9aea91891054d576a5019f1365205116a6376e521348fe753da44186225fc1b276c9853e2fafc0980c2a6ab2174c27f776cbe9c29cda31ad35b0b6844d27e1bfb8a236bd4d655aae70f5b36108b4d923ab4e6284118c51adffee22e5c072f6fcf0f9bfcc79d1fbb6b1f5794d87b332e282dfd62247af80dd03140d3b1d72f1a470c3a755747061dfa32d4fc3ddcdf2bc830623f20f048dff8c4bcf259a6672c47b817a3350f5448ef436f01f37ef955c466d9cd507f32d71416a7571c0726c2a77694b3d50f96662f08570766d014fcdd1d78213aac291089a5b0db9ac7203874267bbf3058754539dd88f845861d53bcd9807a579674d98375799c7fc007b567638220203ba84030d14c642d9c5bc333525547f83731a179e29909c7d7201912c57ac97ec173e055b6a4c24a9a55d43a6e7ba1475eb2bb8ee3d3c92df72af1a4526ec920864e86ac094bc6132dd1a50fbaa5fd6292e8c9a1756fe659307e6e13fd3bebf647f94ec12c4dd0ba87ea896cf8ed09e525f769383285f695c6ff91e301a69a1f385c0afadb0ab3ca7b2a42bf3fe800e6a3b7ef9807555df771cff179643dbddb938e51d6bda63f66aeba6ced5cfef2295abc125cede4c411e7261c66bce36006029aa636f1a8965c0926b2c94e3803b40ce26ab51ac0e37b32bc75d42ec28b9ad3015aa08ad308dc51ca94095f7fae2dd12fe776f10c8751b72a9b0c7f54d6ab180df57b0770afc110125aec29f3b8396ad60a2bb3c6f169072d193fa68a9865824c6203826062dee66abe8a4cbc2c30ad25cd669d38dca4727403ca5aa53146dc9450e69bfcb203b8c24307b81bbe25d8e84177a0655fcc55e872f2e990fe6555e5f1d256408021b6b7afe3a0137617076fb47d409a16fd5729f10a407b622dcb7f7a7fa3308e8d984a9a2ec8f1f8d3c53c68d687c3eb18872a06aa7a6415519ad129561713506405e6d6429a238083f1eeeedbd42e12e8b7293ec49e6f46ef5ea191be55799e63a6c51620467e9d649ea5c5e97f9c81f5172c581128bb4de371bfbd8c66dbc40cfdac49221f9937d93a0f750c6de4d705f72617395335a7dd609016d8c4e58799c3133eb3b7867fe9bf22517ff095c8eba3b568f41882a2a854a90c276eebf0f6914b68564988a60ae0a8829200d9fce4672bb46372a22dc1d5d409c9251f440852e0b65cd5993bf85c8448c74167232d172202476b07f490a800554ff1a12e0fc2b830071c50157e0165e0a2bfe5ddcf1637d68c85b24f1276d0e5486bd741e1f9ab340efb6d8fc0826a2b1797fdfd80b27f54138bea90bcc47dc6e2d996dd1b0bf66d8bc4499a29d9692f38c611481933f9abe6fe60a0ea9e4db578411e3e33ef5c873f9d96926a69fee6978cc3d4497520de966a590d599113f0933af5df55a4e02afa32acc9d368b4b6288928577627200a2f2a025548b5a7a5d38fe8a372e9213d83e115256c5236b1379de492d6d4411b770023a4292ed7d3df82275a148bc97a15dfccac43597e0925b655fc43b720e1c2372e53336e19209a0617cecf5261c5864855b29760e2e2dfda072fbb072c8304d0b3ef8e3f22e42b8b37c0ccb761607b2f47a4d58da0c151633348fdd722e0213467c8b8fcd4a84306665cffbd8b77f45985065c89946ce006cbf0ac75973bd911c2226f242f5a1296973c14a494157df975d08467b24614c405c5e0c2704cbd51d9a8d4646319895c66566f3c9a85af7c6c974bc3889fb10e650ab740334239d655df7a8a2b3a7efbdd090a753ee0ea3398922798fe0fc1811bc64cf57a8e0f065f1f1360ef4a9c979df2d83c998020a5d20b28558a8afc84dbfe97d27c7dc014ceaa6893b145d89292bc2daac2cef1d4c7de89695ecb012a360e4b952967876addf24aa90ac930ab9bcc18ba1f4f7eb5779728fe82a393d4f4dcc3472d21317ff311f8644a74f03c5b85412463d5dd4f25cb934e19714e554fd31541391cb8439f74e829eea225b34847b098c43064272d77087b6c05c28b97807091500bea606e6b9a1cd42fa7f50b7f1e224140e390a324bc6c91d1432b9e4d67a1a6f5edd07ea6ac3f3d4a972805b9f46e2e188057387f201750d71158863f102722c91331d9086034b54a1e66b331c3d79846a62a07edf8f35945e10b3e34ffa50b71e21309a61032e34e40d81fd2e392913ee98b0379ee6adcdcbe97992564c3683379a751f0402b248bc296fb5fb60490b90b5c8b295a01dd37e6e2a94b0a872f8ba908e20f19a17c3a8835d3554b360dfd9a4a75a06b055b7b75c79f33d7e72b00d801fa7454bf6361f84fb9534a26fb69af5005f2722aa7eb0b2b232b8b264bf3947ff497fa502eecc0ffedcac7ab2876257ab40c0da39d2fa0e5023e50d0a6cafc1900d33b4d423ccc84714ef306f1e9f5f9e6bb847d5598fd93541b0265ac4cba1175b6f2eb65ce4ee182a94355566c838dcaba5d2d4d2cddc25b26d9b303bd8f97a51584a9b4c690983833946cbd2e5d35a516c07fc9ea268f6294ff97277f1e6273d13a9d9b51e89aea4905b5d87538333512cb1ce517344a7db1cb072bb31d975923e2f46b802c46242c376ff96b29c61c385f34cc381f297dd4c18720e95009ad9941eaa695421c04d7988a7ece27eb7b284e7ee951f2a086a5af14c5c814c61af1e5521925dda8c0f78408d7dac44ed9d10daace3aa00bc17d5de54c5a6b4df24de6679626354d692235dd545e6ff9708730b782b832f2508502e1f355a80a9ff0d6cf40ac5bbbc3a9b5a90cdd49f57e7e621ee249fd2190d2f827255abe1e011db4cb4289a7f3ecdd4914501dd145f03c803bf5c7be4784ec06f72cca675ed12c8e82eead1dd9ef992fc4e5357a27500e331c48bddf6b5ebc54d04497d2fa11c522e6c709d52e25005075f9c282a5eeba1e9360ae65b0d49e744720840aacbc7f56a6bce999c39c425ee40a02ac38fe906723b47c44a6e0b3d4a0d31530dbbf10fcd180945cff7ccd299b5536b1dc655f8320645582a9d74796038f0c3158b4a01571a02e6c3ba2bcfa0f191673be88a8b10486b447f648e49667238c8659cc4f32248db8a6d481401587e0b82b5e5c8089a66df12491e91bd932852b7a04c78364faf72fff1f2fd3e823ad2c1efb9ebd472bb9f8c6a32762d686ffed9bd85f5f48ce21d15c55c3e4c20da8f804956da88728fc87a782ba75d5c2a29bee9991f2a63c40479cca17a5010c386ba828269cd9e4df4ee6d6e8a4e857243520d20a85edd3f72e636f33e1b7c5754882b620cf15e3bf9e247db6f70266ab5d318f6c41ef99782ed677ce2c891d43f3f3a5ff798c107830a07cce878ac72be74bcda4ce39af8360a05ab9341149a1b1867564f00a402866a0c0d88a12f3e6833d7d9fbf6dbb52a6e192fe496dea4ae2bfd1f6bebe2ae7bba56e7ead7b566cf6ca104567d8403c1e7ef484517ebd4b8882b2e9cb2e5bcf5c8f300759c901c2a00d4d52ac2e01ce391ed622c63f92a95bb9efd86222a14a093db20f3af7d0cbd7c7149e1d9790ae48d9ca0eac6d6d50c71a8ff6ae6739997c37965cd255808bdcc3605d59e0c7574fcc7c060666cee59723710a3ca2f735fdd64b00f6777723113589171ae79409c781dfd9bd16747b6b57ce0b4bc7de98eee970bfe761c72bbf3b6045fbc2ca22d014a871f3f0f86a425247f161e495eb61b9e71dc63d0357595edb84d133d968357a0b9375e6f7aae135469a14fa347c630cc9b54fd9172bc95bc33246a86414526d51a658e39d29b3e963948756a91c0deeffb92bbad2ea8b52e1fc62da943024948d9dd2763d8580234af311740de715c54256da71a72df21939501ad9a5c69dc518a61ced9e61e15201776bde2ee02b65b64dacb1d720cfebbb63a16a36675302bc3539055aef01b2db6bb382101cd05f1815b1f507299d8bc229260b39ed887439933ad13bf19aa516500de7688c237aa8c9208fe72fdabb4d1eb46a4e1c6f6395ce9a82610936b48201ce78c14cf526a5f116af15fe4b6b1872fcc059ca880c73735cb6932db2426672b0f3710a2131fcc4fea5272322fa98bf4a024e22bf90486a8f15e94985f8dde63d5cc7296b13c3a7ea60a720c87eb3b0248f52702a3dfc6c6aca3b957f654c20f8d956413f540953e9c8c7218f73ca30bace3afc2698c1ff8b5dcd5160201f72376ff8df940d4393c520b4c261f0995af5d7e6eee06c7d50693c03ad54d4e0a7c4224ed978aae61c37261723d5810c1e3fcb03c4fd258f988ddc3eea0834f733fdbcbef5fdf429a054ce8724b4e7f48f0fc4372809b0e5e9e7e853a492912db646d9555854eb84cf8a4ea72d782e3aa91e9d98b104d3892436b84115ac80f62ca0e2d51561465b6b33db37227e9e952bef03f78a55da21f9a68c4eb5fd79a88ba9a63732dba4d95fe99ce7265b12e23044345e63738cbe97c0dd7efae1deb3567fe5c4c0fa1f765583b041a6a106b0f7ca388223e73704f58a58db68c50a32fa1a01789ac87a6aea840356cd7da00be648cb484beaf44e98335cf71a811726d73fa0efb357e0c2ccd75177296a4aaba06c3a570ff7c2c95b9568eeb978749ccd2fd3aea14c93ce2a9683c2011871404d45f42e3c1a1f2d49af21815a6218fbe7ef6167cbec598ccd5389b72bd61921fdbe3fb0a5230e4475d18467d6f82977cae37657fb60d5ed5a55bb972940bebc56d712dc8a8a7b85bbe8de89620bffa75f7ed7d21498fecd342dc06723c6b979ced546fec347ea3f8cdc2d951dec758921079a22a075387219b34c4143bb61bad7ea058285141ea12358cac1ce379aa49915039898c12b62bdc2bd97226d2d50177bb51d3bc2cb21b28939d5dab1b6f44c98721ae4bdcd866dafaf972425dcb2cbe029bd4e79ff795e3421299d52ff6d2c64795732fcc280fd6a3317276977159c9e06762f88a07f5a458d2bef97ec849ec395ff91a9316bf9f73417227a783f8354b31338c640500d1a0bb8edec901cf241abb92380903e33863e072898d7902976a71d1086a0c290cd17cb28134a3f851b250f0f240af08fcf957721fec72c074049d841e865fa1dc740f2c1aeb617573e60371d1028a1cca9677721f2468ae3d4b6ce6551ad150a93f40b8d092149b6b83be4aa5a513e50d67fc0fbd080936420e3c440e7a1ec0bf34811dc5d70a98c202dbc2afbc3e8196cbe734438aabf1b3df9d4a11598576dec3beeb7dcdf722fcaf5add0e55ce9eac81c67287838513fd3bb9e91cc3f6effa357081446601a9f5a65ed57ac6fbd8df302c02d0ec672a70c32e541c977b5f7af8fa233c5b96fab7e5b4cc182649af17dd25547aab07eb8b4d6ad4c74101731e5dd69105c8c3874ded12e3cdfc698f7f758965964d5b1a3cbab3b65f9e15c2e13e5e8501e99129abf16633d52a1fedae6ca622270771d58894a9c62c178ad6b46dc8494a5929745d2deeacf38d2e008bdd7e726924dfdc2a5ce8a2f199402bdb02a80a90320157c1d78ee49b2a890bd5502a09d944b146943d7af0fda64ca3cf1450aa2968afdf751bf02111d8db54dbc70b729cea4809c0f65c0f91d4d6ea1fdf40979e670ea3bea116c352df09319f93e072c84e23da0d961e8cb9f53d6ca3a968e854158915df518ab78249955e59b08772b7fe8cb7b0c8160662350e1e0cd4fef54f4a499d22b0630de672187517d39d7209d81d28dc61e5e73fd63df3c0d11d54e076588ad2fa3e98afb96accb402176aa88f8010725f7ef76576bbc3c8cc9501be29da9bd634b1098c4857763356c572a6df05a458b88793939f59980b0160760fbc28f7ad8b0e7027b112169bdff17241d5996b44129c0bc60deae5a2f08cd8ab55018c4f323c57342c50734964a23f9e58b8e4ed39edd7e4e227f7a735b7fcb27ccee96b0c22e19111baa8323c2a5da8e831d9e50cb260a3eac13282a3cc1ab5cc6f088bef51c36b044db7c403f47290cf8a91a9757c5a7465fd0bd5adc529aef96499fc680a74cc282be155efcc72fdb1db7cd5a255e91c8c30145d04cac60e120bef4470c58b4a30d1ff6cca227263f0fc5e65b96bfe81b9fb11acdd960b72403c8f5535236ec7d82268f91c011e82cf7c8892f06a2a6968e6fa235579e8cc9f89c4634d7b21d5a62504f141607144817ca9d4f4b4dfef4db93526c24d0f27e8cc691b28985921e94c45feb48b72ac7ea5d01013d215dd5460a7c910e61de390b8309cccb614ea526a59813a9472f25a31948a72ca2db9dd44378f82141d73f878f3d4377edb01a16407626eaf724096ccda0f02ca9b32c88e6e32c6ef5dc7d3476755cd4c84e5154456c6a785725916b0a475b27332189128c034031f391c25f76fcc51374a54b6b79f87dde172be75eba27c094f1d069ef36adc488ef218dd9c9a6cade4e28d10fe5e7b48f9726d356ccc50ecc3667d6cf3e1452b37da17fc20da0f7e49c7f4273cc4c49d3772bcb076644ca2f7ed7cf4219254c027a7d01f267eb3b724f133c82258b6292b436a8dac3ff40dca04aa310a50213667f1912de80c5315bcb5ce2ae362bb1e1e720a4311a2d7a189b5ebef062c9236593040c0a970d0bf0292f4989f4b06dc6972dedfe969e590d15fee21e2d1fee482bd6e74d9f7f521c567f0d6e62a21f67c72157ee6ffedeca204689df75c5fba2cc063bd4fb8a5822a07c9b7caa4ed7b4d4c6e536a9d1d8f101bb8c7115ad100b814be93cef070601ffa77805ce6c2e3704c081295fe480b25eb6b483840bbe1f3d8bb93fb18ffc02726be229e4245138444d3dc095d561426e88565b1fff9e4a5c5512980b263a415c4f83de24a8b18f256152368012534cf14108477ae618c58c87c70b46cd2125e0e91456b49ea31230c0f70d0362b651895194a734b6b17b1dd3c825680d4bbbfc9fba5c91d79175f7256fd72e79003d24136290b5a75f6a4adb400b1a27b4f4e9ab4422e16dc00e43c52d2bd5d7253201dd8db49569cbcf8cbcc6bd710a692ad300d720849cd40ce72a6a855e0f6899cc5426de228586ac455b71eacee5b9738f67bc1ea53ffbc0072ea8617adb3ece03a58fe8fd38cf3b4e071089db6efa16dfa3050a3633d958263be1b443168ad0254b555dac891f559217202ef62d8ef2a18feb024821f523072e1e930f375ebf6853e1f8dff423c0151421c19fede23162d7eba50cac2eb4e5055175268f2207b33476035cac860e5a709cf6b8beeba3cac33ac5732cfed3b721cb6b79bd6e3128d2622c712510d9c16ae920325edb793b01ed736ddb01cb172d69e3d58a4f28f0eba68692af3ca53638d62d898154088f42e8d43e9a25f1739e9951d10cce9bc0ef4387be477b6f9d0af319a55e070ec752f34d2a28b0dba12e06f5ee389be75cf8104960138d7349cfa2a22a77c55d3742b5f463f9f51f650e5539501a1ede427b99ae1d724ec10b6ef51f0a88d423c70c76e84ab7ee9bf7238e5273f8833a030be2f1a30fe16fd25e656ae14f23c0a4fbfab1db3aec3557298e467e4aa06d8ac1ca2366f4613ac45894c0ba19805f6ee4ae182036630e1726c85e53a82caaf5903025428bde7ab0d36aa7e4205ca4ef9c51942cb4b95e05bc7034372dcedf92568c611490493703cc0e01a6580cdb02dc485b1c6dab47f51f3b6d6d26b4ad1317c11e397d90ac2a60bd50d7511bb94b16f8d29751cf6f872110e6c069fccdfab44ebbec1ec0ed65c787dcc6f95e776498acb884526488d012d7db2a06c25a02e09a62e9f129e2a051a202735312e7d1ab3bc2930421e3772195679efa38e23e710e7c396477a77525f920f7a67a3ff9a2328c7a60d09b26202eb17e2ff79b6d416059a4ccfd44e8a4c4d036c05454688fb71aaa752051a0f8abc699f06f649c66df9420564f315631f97f7fdbf4952b25fd704033502190cc82e88e1d80caeb4722a9d3846fe47e40f1d50635e7cd89b7d9d545be8ba6936c143a686d31dc5d0c31e68efb6478d7476a3497826b3c7ed5d930635200384726ee7ad0a9d29f0cba13cc1f7ed472f63d8e08e95d64ea0143bb3858b57343f4c345e3ff0cb543f19e53645c6ccd558db8c211f6d73c503393c9a3294b58f4872c865d9cdd546aaa9e1f89dc80dc8e3508dfc544f71037e86a7ccfbf6acf50b3a80bc5947d4095402601968d6cb986f73f65289263bf2cc17d550c435c3f829722f4df006323720db6906941a7a8d2d3eb3b7975491240741c5a503a3ec950203418ad2fc5ee5ef95cabc47cb1c357b74f5350f132d11bfc92a850f597a6f0e0c013bbc3370dd87007d814c9a95dd8b271cdc35a5fb8d671b552e467b32c44e70921743464f5fa34795d84b8d8a759e13819a14d9583c05026b90d2c5f8b927395a43955afb16ad42149019de5e37acc4dd498cb9aed44a30928f9fa0a731d1729daa6eab360dfba86dc8aea8a0d495a746ad4f090c47888c07da8866b3e4a57293be0e2be77d8fabcf2ba6a49b22f85bc4a493615f3020ea229c1d312f3623091a88caf51e594a286fe9e04b0089eb585251de158daf34fea00d8233ad27bc72254df1071907d4acd952a2088b96aa5b717c73c10864e7884608f2bb13c4a172ad1432f5cc1827d2d8f9f2391a388af95bb08863fc45076bf0d399ddc1867f557bb3c979336da26778bca5e12b260bc5fe2104af29ae199982cfa03d5e5e8d181d711be5f8ddee7227967f2411311a79ff0241b32216da4d764defb902d474723ac618ce5582eefbb98db3a98633f90a79abc62fef2ed4cc7d0da3b2e2320628e814e59bfa5c335895f045809ea61db5ec8212e0a64e6783c9ef94788787ee34520b990919a01c2bb6b1030fb8dcae13912fa6f0db154e10c51426331c9b0f725cfdf9debc4dccf9b1e3a8d4dd4de47344d45f0a82cab7c66dc4fefd1074fe7273ac3968fd02999ad1f4868b90d4482bad0074306afd508921270530063fae72f956dcaf64880480b8db5cc4fa7eaeefa1b0f53e8f2777b1387201781c06b472a2c41d86bd94fcc1a49be9e32ab55de57c3ab17667ab95ba07cdf284f0d27172d119e318cab71eaf764b5d023be0bf6e539317aec66c0636f0ccc8f38993732f827257d83f31c7d240fd8c7b8cc83a15cd0ab85ce4bcfad91173025804f74b51370868bdbe1ad892b7e9543dc54234ea2661e24c042f6fd1bf6670de6e92dd2e9edd24c9bbee5f5dc3623dca264e0eff3eceef74d33c07f43efb961da9864b66ab7ee0750455cbea071bb550c04d05e25389cae8c3f06736f8b4acea154ac9725257eada23056fd906841f73558a77e73d3a065485154acd261789153f12f15d9ea4f8f855c43061f4f309caf5da5315c59e36c550c2ce2d5c0f37baf64c31722c6d0b119d0137084d6fd7a1dc5804348b2a044c19db1b98dd19473e7646d528397e3c2d056a6a6b3c232aeea9f77eec8eef268a827e617f828aea727b5637512753277d2016d4607df68f071a4e702a68ddfb0d06b2791e8134d16a94f77e72b3329f477a96e4ba4250db27a49afe24c4414d37ccb2fb986892bb1da5aec3298984694a35ba0a9afe412d64ed04734b802ab0f9d5d86970eb9f0c36e7c2717247cb2551bfd185428b2c5c951ccb4b42ea43720349b974bdcb162e1dcc92d1720d6ad56a10b8024069357787b1fa2fda8b7cc2578b759e86f60014711886346fe37a8064e0f6c721656bf2e85a122fd22def9e2ddc86e49d9c1ae64a73411863d607191edb391f87b9e4f5b5e6ac6df933a830abce63c6a1eccb84d1b113077233a39039134fc168d0b792dbb7501181dca6c937e5b27ec0449347209937f3728ce84ae3e0735f3263fa92a7e9ba3de8ab5018b23a4ebd02d2f2b419e1fbee72c3f15ca38f62ce5429bedaa04c1ae99a3d972a80393eb3907f32a1793c7fd81a94fae454cd0168e17b5d93a3f9e77745f564d51fa08a2a98aa6abf2ffeb782431c912819158bc2bc21f92678cd1609c30668febaef13591dbe5276b20c2a170cc2b9a70a4a914944c3801d4341f10ac7aaf57abe36da02c9b8e6edd5c464ef72b9624b959d90b67f5de69394a4ed36d2d94d2c3d43042f92b1bfb2511ec2904f7f1edddac588165ae2ea11205e6f200e7e57d34c4c9b965cbcfecba75c9d36726bd907e423af1d8ce5a17bfa45e1af4d061a4b7f003fc247ccc9317ebef6dd065cc6b472d354f6eec13d1116b6ef1150ecae907a3bf1167cb44467fafb77872d63097c20b40a09be12145fa2babf8bf575dfbb2b3246d7485921242ecd98b5728a8390144a566945cb1923717923d569914a8dd27febdb5e45f2daef747c4c3bd79db29afc6b230e4621e4a32ccb52b5a2e61d26a215bf845b0efd07e83cb062a93a9508b04c1fd4a489f5a010408e1685327eaeabcd0c53ee4a0c9c5830632ba9deac92fabb8a6dc5feaf2575a967490527b0a6012b582ef07b075ce587dc72b5808cfddbf91d9d6fbdfde03340ef613a1bf9c04ddb61a31b326197bfa91b115dacc7d51a6d18b2d4ada66a959bd0cc656f6f1276437e203a6463a2540c475f819c4e2d75aeaa651a4ae31eae04477ed9cd418abdea78d643311eb2971374125eca70a2dcf3d4b8aa81efcb259dc3d9721c3bf17ba543e8ab604f066755dc3dbb18d0fbd87d077875f232a400652c0681c6a636bd459ae2171fb9102916fc7253f7f8708d495577da8a57338ef10fbac2228050905f145286aa075b37821472c7e0c803424574ba67426e9129b7202f02b3077dcd85147fd84078d13bbe25724d5cffef820bfe445e987a643ad58ea699a8b7bbc5b5228d03a2479e953cd772abd7bc25f8421e407cdaebc02177fefe7e44b0b31b333eb68ae9da9fff51c47222e8fce24e77b1078d6bc840c2c149a61d6d0e6b6fdf05707f28c58688678c35640ddd1d053a0c7bcbdd8cd9b5925277bacb854a51d58e7f8eb9a20151990c72478e75e14505c23a7aa2c3edd18e7e60eed668b1cd6d561a863f443a7b0eb472abac0b04825666ee440074ce1e0e351bc27c3cf4e7e91fdf86044ca0574c28316ff89afe0b22550645ab80104cb0b4971e47e8bcac66efe391ad77279cc95e72d3ca0857e3c0fffed13059ca30c59f0f741016364ed8bb58ba049afed9db4a3e03069c6a9308ba529545a859765d2e2fd81f9278abbba42d5412e498cc643572f570a7221dc5d2586000f75f80d6d83e9a7609dd2f92b9b1cc6c2961f017642e1a4d8aa788c7dec2eeba570a930de637a79c51043e4aca6300a53f2ce7f27472f31c95c62327c77edd9d450106cdb441d774550213d5123c976e8a2e67cf93727f0c5b4d7e56e778c00b3ad6c66dca780a0fce4d68977c4000b3ebe56035181d5064c4cda8238d219f7bd5a8e2393660fb3341b101b8f7cb895e103345c6a672545977adeec5d35d793021f83b2387fbfdebe8c798b346a43bb2e0316b467f6aab0aad5bbc0bf8bb7d0b1bd01dda8577b1c43ff7d69631c8e2c3c28070a0453d32aa800f929eb7fbae85b0baa61f5f80e8a8e1403ce1def6545b13f8dd0cb372b689d7e6e7e7555fe5ba4550c63e9c664ae305e661799f09d12d6994839fa4108d11e32e8870c81c42d0f0ede034a7613ade67b6928e78e352b60ff73903e03a9b727d3e1bf1f5bf70a8365cca5cb486c2b0d45e7d61965befb3cc293a7e152ba4da92eb6c2aca15a4b450471db55b71aab4b34697114a617112e56ffb19c946635d9571d80f5abdb5ba63a63af95e67e697d8a172cf43386472a9858b09d772645552ba72abd582e6e1b70f3c84b0cb5a87b64718d05ecd7dd7ab9d545009720830bc7ef7888e3ea328e5aff1c1a5cc9fe91791ff5b011bf7f68c7efa7d7772e48ab59948fb8b361af6ead5a6c0f7d00a7c66e8095b09813400d6b6e0df9d3a94728ae41423ecc52945d952a46dce5f192a226b7e83f5a333197529a654bc7263e7d0720acce30647ffbe7f862e4805c97e88ef34a4bbd7d525a363f1a6e21237d666103b67c1c67e6e07d615e9613ca94460daa96b26c810657549c825c74b695ee54145301503b61f4bcd3b217e8ab71a3fc5aa0ebc2c05677439538fab3eb7bc52814da031af5f7a96f347cd8e75d90c968ea69e1576de5f2e31eac1767251b812c40cb297437980062e12c209a806aa2fea84e128462c82207eb808397288a5343a660d3c7c7e74e87c07d2515f316db6e6a9c0615a76f795e01a774c72a6730d74f402e1a5b218fdd3c11a31b978f546bf552c8b8ccbc9949cc4a81c723347d84a3aa3182f54d132758cf2f85e365d5691eb270e452df92ca63e058272b8ac5d5c2e46b857e2d8e9b8861e0b8c9b1e00b168b1b7012c708612454d88729a6ff704a20ef7356b14a25dacb6a20e0b14f5ba2c46b6fc110661b519d1627266a9eb366d1360aca530c9257bf6ba0d0fc0e44ba2f012d76a9cc1527fe1035a2a57520b750d82da073ce069b6d5152db8a0a80c1f2d1f4f7b74d08898013358d0a158a8558203a02290da3bee61e8345c6d209efbc48aa8dbee8eae68fb69115609996b9b40db176e76b313fcd174f0022b5900e473933972881a63e7ae1913d4d423e5e3b18c58120bbac8409d005582f82de9893a4b4e3fbdbed150fa95058a52b212b3cd6b9f59a39e863cb6f0283b12e30ea7e8b3f4251d7c5309fa2206829e7bd1c1aa07de1708b1dfada150fda0e88988515f2bed00ec6b63cd06ce5d0c212e036513dca6acd1f73c550406b951cbeccb39ba931e117f2d189cede81d17b11702c069915b4f48ceea2310bae53c2566842a8042757bfe438de1795b7292cf43905631cb14e24af90d9de5447ff955b880a5e3a0fc19efc5cdb8f9814d69c37e5106a86ef348b24b470106e4cb9f5fe3af2a03c66dbaa0c4c32d96eb358083e66fdef2cc3aa0cf0fc4c834b3446c52397682d8dd5f30421732128e45722386a0841638adb12c720bc32e1dfe1495b4cccd8cb046bba7288406ae9f1e0ac7eb8dcf0f178c793114f638bc36c8e14f4665f3d20dfec80981ff25aca0781027b4f68a85a3a9a26de380ea3c9ede4e3b21b81571a7a8fdb191705ec638fd72ed44c2a116edf92910ee41dd2f20f786bdc230cc310f0cf1f2bdf0cacd765a1884795a49fa4387a7032e0334ae9c4c62a013922906c6ad326547c35d9745a272aa83d64f35d16fe4a76e606be015fdb2fb52851ba21df6fb7b7a53ff9607c32b6900a87899409c8aa7693c1fe4f6904c58ac063e4565516e0c755876fb9dbe40900b984a0898ff25714bbc6413f041666ad6b1369809d647d9f2eb0410c7a0349fd9c1d75d32863bb3912df7a9ad8ad7acd15b916411773df1bdc67a33516e72ca456a20b43d29cab654b3dfaec6d7a959bd8754503874ba1dcd15903e5fbf720ce25d34b59d22cf4a8411103651d055fda863c22fc18c84f84f4f61303f10723d3fc84d2762eb86e8f71182de4e8fa6931ee9a34ba11afef664383b1637450cb525dc6c5153dd3d02ef2599564d869b75269daa5a018ba74ae54ce6c6f6b0656639793b24a064bb37a6f2120ae8142f8bf18cd930bead9039266d7e35bdb87210dd74adc83ef20d33acc0eb1b721dea3a1cbac1a041f834167ec2d0f0468d214ed964fa2fba5f8e09f9207c3870ce62e53ee38f04b4a77a80766f42947c347252e43cbdf98fb142a14e51a6b17d5f81962fd7047bf5bd28089ba4201e194330dbfbcbf67451af018bd1ae2fca4a689c1437b50f3e2d7890be2bbc499218d830082c965ec776a03009d97b98ae72a45c9db96b2be4302cace4b723b3bdd4c472ad92a2f13d9ac61fe96b6fe40c56638da62bf66e0cd6a1c3d204e6e8215af672d4f193433049c036e906d923035490e9948c0103f497f15e35f6ff76a73a32332a4fdcfb047be59b242d9694f14d0107484c536c59faf885123778214665d74e44d183d21354676ae04f74e0ce477c1a414906ebb782d90562ffb08181549065ef8e4b962abbb4d1fba0abfb60d8e92c4e29bbf328398526048fc8c4f45c755b09f4a368cd40ec3a58b37b5d4891aef2e0057891cb8033d0e08991df6af1cb72de7453cea7ae61d1768be6d707cab17c94032b728aff9027ecac01b486b66905239d2973afb82070df61450ec9b7449802509aa3d76305894515b48822ceb4615792730c986651af50a049ae2d35a47a3893b5cfa3511e2db70b3f0a0b0f8172e298b17e0e5b86e90300638ab328b941214340daac0d60bf2345abae5b00de3c3bd57bdbbd1cf82aa30a3ee7ef85e52272f2dbabd00415711df77f89da62c14991944c19fbe0daef54eaa0ae3cde91dddec9bf57fb8c7460000e7ca4ca2495729e9988bd921c580c7e941ba408c83ff7c1c4024eb424901859d0488cc2257872bd04c22efa2633a87861aa0bb670a941847787ff0f24ae2c0b2e10da1426a74f836f3231aab350b5d25b6faf538ec5526a460b140529602a77e56481b7158e72e361e68c17d56d0be9b7ed4e116d5684174f790a44f60b8576b0ea8e0b892c7239958d22d60e3fd72976a7d79f53e3bea9168ff76140b404eee6f4f6d0839172d5c85370a6cfc2e640296f52710a22cd3ce0b9566976f1110ff572e2f831f4723d40f459dca202b35f550bc52f421e5456cccbd17066aabadf919c042a41ff617ccaad46183b889c1fcf594db4dc54c452af48c90521d7ed0e773d6dee90ba648f3a7fa65e1a71e92deed78b42d9fa6eb7687a899165b21d5f26dc98eef9be72a86b2b15a7b430040915bdd2613d3b1384cec790fe1627a180b5e0b40a41b67209d18bee18cfa95e12c550098569e8598108e0a2d823e95bfe725675b91f9c721deb74d214bb88d16ff26c8adba9fa482bece9c7be589236fc61fba6ad564630691d4d4601417ebc0685d978939eda01b9ef92e0933e156c22bf9f5ccacb4772ae87b9c65d92b797d8b0b0e270962d5f60ed250faf06b239661bc4d29441d472a7318fecb692f15b55be8ba5ad1e3dccfb4e9e4f72535a5c30a6ae3c085bf772bcf6102dc5bf35ab2cc1f1f528dfb87da61d9db5415fe7e44d53afe01fbbd03cf20df7e40997b045ca464663ee570b66673abac7566f232984eff04455af817278673772607a7fdb9d180cb2382a9b7992ba6585cedd62ac66eea1ca619b8904ae52f2e45de40f677c17fa5fa3493065f66c8e58953bfeb44a9623994e8c55153af9f9899885a6753665b904550929e010b7e358dbb9d63bf06b7f2202c3c843362dbe4e193215227cc2c80ef8b9c140fd10b6a27c71979727373ce0b1f4c172109d8d897e52712937333f6329a39bab6378b38f8464f65b5c4d8a6226610d7246255609a7c5b8234b718e9efc1f9bec215d1366c8d69294504a0a55975bed3d57071e2ffb28cfbb63f5129c4300bbbbb673d9ce832affe2a3eda3698321e572b57df658f94a3dbfa06d23623fbe4dd8b65457672661b779fa07a0eb96ff1272dd22ec9869bf281a32d50a2d92a2c1d0a5a11620d2b228f0f9d9ad88fc9cad260c0de9adf116f61d7013bcee3ff697b08e522f0e522731a09c0a6a371975b42561a35d4735d0c0c8340fa9ab4617ca2fb37e315a1f9b2eed2c1bbeb9fcf5ce4b353daf8e9423d10686cf9c212434c8a89a101899532fa288ef6bcb4e4aecf834905c1befdb045925bf7a4aee402031bcae2def1f2748a9c0904719af40d1b84bb20453460cf0afb387794a9dc3df6e42c7fd428e87244e7d5702c7c274905172e7184959d1ae2ea266d8e2f112f8dc181d8f088b8d980f0ee31ef341dc28fc72902c893c71969484a6ef668cb1e5f32e00ed5e0cc7e4cabddf60b6fad2fc4c502efcd71de276ee45ec66bae21e5b9e7316bd3c15a4bc002256f8efeca1d61c729169a82858ef36c516acfc1119a216c868c2c89e165781b9b649b3b556732e5fdeef4380bf4ec15cb836568e8a0b69317f5dde0654de691a594874ee73f84e72c0ad6527e3f18ae0a6c850259d9e43b7341496750da578cf806c1a06bd25b1728cfbe856c755cb2d88cc3442e03e86b13b4ba05336f105be61cc31b373ed2b72e4373de769fea9058f2ac40f5f45bfdf656824a132bf78b73c3e48dc905b4f661735c44219aea9c122c232194ae828200ad3f7a75ab17059ea88f3ef4f510d721a08c88a14b1c4c5291f168feacce4115eb7540c7ca58ecf82bed519caad156a4282267477b8989761cfccd5d1e972ba4b66c50105d803ac213bb03e25794f7244c0b51808eea9f6444376f020507862dad6e87dea41b52a29d2757383944172c877f2e03a9b6f5963b63edcbb641720e6874ba3bfc66b3f99033a56f5bbd272e38e7b0974eed8020c4dea732d6202d7d1e927a1a4c9c1e0740bf527746bdf6b2eebbc8e9e597448fcc2e6dfb456850a481b6bde30792fa0a879045afa52e0068ee8804e52d6c504fbba25cc068e0542e4ea8d2731932af26c10427da08ed37287fcfb175f5c1ba546e09e0b554d1f1517c8c03675aca83282e64c497e36783303530f89ba502d9c217ba00666005038c8f37ba7086b0c645f5e9ab9cb4a687209e86761b45b628d629ada0013c1e1d240ac0fa58e127da75222467a71c64972e15a48736948be4b753acbc71f585128b87d668ec5145dbfd3eb17f6a787766450fc01b73ab63f02cdac0abc16ac04e0b866e2512e02e6042b3c2b6f44b4482237c4703a030753c3649d5c0557d49c2803c3717e9c76300e58f6697c3d6b6262f74ff9696f1bc7bda14062f732ea8ff4d2a4f05935c2edc967828c2d2e18bc72fc6ba961d8bef62ea9c991889146feb98e835f0f29cdbe8fc01201296d114872ceddaa59fc8aa45f042da6a96adf2a85fdad00e2a22b644c0cd14e950f313572c83cf27f08f88bf3653dd3cce4c4a2e5501699858b7b90b6d7aa2f73d5443f6071d912949a0a513bfbcaef618c87a43d667dc6baf836cfc3f04670435d3c653007115d99151af2c1118b781aa379be103edf7c627f091e1537b9a3e78b1d3b724c430fe9bd54cb1084fc72a3eacdf85afd6cafeea894cd79aabc2a5228524065b2a53954f2d5a5b65d52c43aa04df4d21d728415b3e750e9338804cbc0bbf872dfe087930c030da61341f73f59613a64f7dca558831b74a188ea4e780a6b6e727e4f6d597fa66153acadea149bb31b2ef3a1ca9930dc63a0f2f28985702cc3723dd6d0eb63ec15bb5118b10c1b0b09cf791dea1042873f332b8601e2cf5627325c06e862e37d5c1956c6d26574e7fc26bf4e0c6abf8f29fb2347359a9c59054e9a1e2f5559ab6780cef22dcbba9bfbbc948545d3b70e828823c0db5803be0b52c9b2168f1e9fb55d8c7861e2ca4f0aaa38e42b61f651862fccc3163a77260f72a9360ef4d2b2276a3f2fc58e83927bd6e11b9d97bbb1385ca5a673ebcf29a40b1fd0d5ef0c0a3fc11339b6e1b8994905a1c15498cd4c8f944d4a80d3b38795726b3412ee1a9d05cc76299c7bc025b72d34a64214ae64f7174fbf234ad67cb972cae485c3b32d362fc8df6ece754ec7fbbe53d7eaeb2408d691896a23721bce725089d22be5b5b36d807a2f1d8d506f508a39d93bd773012108e703906a0b307252f7f4198ff81b884e3588cf53aca1211bd535c7f0b8a3debf06756fdc271b72b86e3d9a19730c4c80b846b317dbbadab7a59d241eb87100fd936901ae9b541406aba4b820606c127af8013dbe71840231b6b3d6a08fc3bd22319be9e95616725be81bb1e0dc15083688171091142ebf875110bbcb698ca4795c0d8859528372986edffa2b444fb7b5a6351e74d74b83e803e5f3e8672a77898bc7aac5e17e72ec7e7663443ea6d4d02c5a1f4a79e9a60a55475040159aa24a143f9e38af587289d3487f8e4533a1d6c2019a7cef8fe2a5da2e1210545448e2bc0c133fd1882925297f053992e40bf83b3faedd31925e0e3551315b7a740ad529fc529875cb583c05533e7e0f59fab5555318ea00f8d974f192bfd0b3bdf3216e90100f227f721a03b9de59061de94ce248711b875af499e6ac2cf02baba9d69f77dc45b5aa72314906dce32dac77d33b26dbf2ce7e19205f8f603ca74943ce21f6ed606f9f0b0feb6dce2447ceb2d1a1c8145bd44f5d83e021e1bb4697182b05061990f6a9720298141f863b6f63fa7ebf7dc566622aa10c57731cc9ef62b52ca1831f49847226cd7e145aed26642d6293228e22ad9a244882356b0e81e07c0f2f5db1039172316a18924a74bd890e22b0826bbbd8bf813dc0763ad2bf49b8f1ea157329fb51c049cc4beab8f20ccf8d9bf2b7f21742a0e984ff30bfb6297997aa73ff55dd72038d53f84e3d5fff872874af5092a1769e0d7b2e08a82c5ca18ab1ee57cdd32f7100702c3b3f9249d45dcea6e7dc4acff68fab20dd2e8e89452b8d7a1bd388722685a11c1f5e96b3dcffc96ab644cbf8a652c754fd8f7c7ea438d5638472c220a862957d80a3ba2cc0128e9cd1b0554fc4722049e8204eed7ae2c6d52072d7494d4e7ffb47e6ca9225a60ca9f14f458e00eb45e8569923c80cb2dbc37d38c44981e38aa0f0684518f95129c2256357c77993561df4d2a52826048e25cb0fa816bb3e51597df018a5684ee7c02df2c603a094eb75c4f8a13b02960285ec8bba725457f392d8b2bf73cc7e451377d48d8fc4254187fb750036ed7d25ed5ee6c8246d287f0b9fb8b7052df86a78d21cf1844f0c12485a59aa3af7d418c3f2fc6659aec0f16cff3ce84b0417ecc763fc8a7efde5d27457d50f9b64ee3966510be17286f269842e4c8b1483f0f1c7a779f97675ce6756545447276cee5d7b9977d3721c662cf7d151f8403ff16d1dfd4f8b1a41431b9e8b6f4c583ce494463eaa4560a805c068579e1d877dbcadab6cc0508f7a3fdfba2ba7a6ca105c13b95e932772753560bedbfde6e0a36bea091d911b6576fd80fb2258230ee9de7c1eec80ff728cd80aaa69fcd3fe4fe06d4409273f542750ab4994ad5ebf359ba38a01891a72c1e882b6af42cd8cd1316f9e4dc6781d2342d030f92f5b768b954943a31d4572a39396461c1a7d8abd7914f44aa9d949334ef4411c28232c44d34421130b496dd6bd2fa15031d1fe2cfb043fa6333694499dd31799f8e6b7e1eb2f42c5e40c72598ed28712609901d44a6087988806f1a89202ab0ebcf05f413fc73a30b45565de8db0eea259be32b2fd017cf56bdf39ab4f3c38f99261130c786cdd9064b40aa983d8c978830ecafbd7c733b8bbc73dea7cec0d1f7a27506f0ca143b0eda87255b80e5c687c934c678099df66ec7305b417994cffc2ec342983b0a7d298b172ddd3bb579ddee0076bc72a558899d9f53101abf330b6bdf571fa020c0b239d72b8e6057265fdd14311c82d6c5923f44cddd44d11f3489c70a58afc1515d582720c46d9f549d0dd1650b529ad8edae4049f161580e734cc9db99d121432870172822730d29ab1b68898d1012b8f1646d79544f9698cc71f2684ef2d6a002457720b95d989e33fdff532bc371212e28326badaa15d9a2f48a90e83a07fdd4c42249349d77447810b92144804ab211c23dc1ec62437faee5535f4b474535d100b72f417adb10b3c654439123a2d09518baaf34a48d36f007b6f622f7c7e669b0e720458e3495305060e9a324adc203a2bc0408c57361ebda83e0333d972e7094c5f60f6fcb78349ebd448bac0a8f9573c19d5d80ee8eec9f41f38751b6e4911af7267ac05b1b39d3c39ef41e22ff800274a272956bd1f4530480f81a15d9c0af07247bb8ae195174b02367911ee64710e30393759f27691244405bc10d266c4f272867d4a1c5d53f5a9579fe7f64a6ea3d3067884c4ebf17a8bf041514ad5fcc272c28fc8c2a37e7ebc4646307e748a02dbc75e2e09e57f9d5db010cc289cabda337069c2bee840c3673dbace7647b45ffe6927de45cbad1f170985743887cc552ed8e683c3145104c6353b041385d6a35c42671b39f33e19881b89d6f467e061725bad56359cbdff23694f094f6d7861c593661b18ffb07b6071759aff720e9772812f11d8ebd53f0f1be8cbbacecb8ffbaa3128e0d4acd13a45f6779b7fab4d7237cc840e899e10a14fd9fb4a5900b02a700638dce64f001a3e0ece5596dfc2725644b94849c98fc576c80bcf4599b0e9b8e061e0cf5630fd63feaa6636dc1102954fcfb6a837c92e55399e6007bda3d92fec1004f37362afbf122dd1af4c07727c08fe3300fbb201c5cd7e22c6a76f36e185cb3ae4c0bb98f8e8d952f190b2727c8ecfb7dde321e5a05bc2b43b00a283418a2131ea85aa1532cf4f31a9ee7d722308c87d27bd0fc7b91e2c5e6d72d3427419dfaf6db97be873af2160c4ba5d72d5bdecc65c2a34b47e43912707742ed51b76ae5b64f832bdc4a2ff03a45a3b726ccfad64e7aff2df63cd57dd310705905f6f83950cd565c46155a6bddab58c722c8eb492567e511ede69a6af156795348c3129897e03b15fe356c8a7bdf61d5481406d5e047b0050cdceaa58385eabdc3f45e36249a3184e1d18decdc6b5b138eb9c48827bfeba79fc8d2024d540f59a3cfd215af3bf8e4142f4a62009a75710614de56427cf096d07ea150c8b2ad17b4521832e53335e8672ddc9bc88a9001dacd62cc7b3bf165a37fc85e7e24a367e7ce6ac5d1eb5704f88fbdfd3a0f00f727cccf5de722cee5db36c1a6edaa867ff85421197195d646fdd7776f70e4e2506405b69e34f2c4e5b02af2088f643c3b92ac37662a136c82fee011c04c3382e299ab539ca993186ef55a4a59ed7713e51b1eb4bacb412c6e4ac9023076e32d572e37379bd9cbc93a36a34826eb5e12a93390d7c96f6cb2830f8470a1b4f934472dbccd065cfa1b66bad7cac8b9fdf42e451eeb71c0322d1771cbefe9d4a72f810e82bf051097490d4d64beb7120c610b8efbf91b8d950ad2552c572b6b94c8872e29f18c0421511ad2958b5bcb5df9a232d2607fb9f8c84e3a6f498b8c3b7bf34ee2cb94630fdf601266a0b731e230fc6db1ba7d8d3977cd367ab6751b6d4631007804bffbf226a0af27ce81908b3c1694fb052dacfc019edff9f8e3729ce5d72a36a2260954809a8cc00c482e7eb4a2aadd7807ab8df239f65a9a76ef7260a32ae80e58814990ffc896c6932c4671df516c0efc0bba94ac4226b690457d3f549bdd3d95913a14381daca23695e17b91832462aebc8acbc48ce7e867ac7b71f7247136aecb222fda0ea37d4f22668e730699a776122d605ea99b4be0a15621572cf965e6810a54bebe9b8691d6e71c32446cd2f49a67fa796f350a57b4be6765dfe06c68b10df7958b7d3d7ae6e2e269932457045bd0f73995a2711dc71651f0509a6968aac7e840575bc1d39d1f825aff1637a4a83744e2c1bbeb759f8d731657a9ccace7d9da00bd192e06e90a19bbbfd075c01a988bdad9df15044fb472f35a96512c2fccbdb2cb909bb48953180b9f518d77e90327a310d22ece083a08f37e00a841a558331ca4daef59d700e484487c1e409138a03dc99c95e148122c5463ef18c30505d551de7a4807f9b9adee65389951fb2e4c45ca11dcf67dd61891d22e3b6a0489e56751a2e9e20b696d40835f490f725cc4a38a40dbe6b45864345924c274310c1076740a98544e4143e536dec8e8b26e97e671f16cb86d942a672c72472f41c97244e58162830fc208951954e3127f22e7ad0fde201318552fe720881a2f7a5684091c8d3ae5966ff94d0d417066b2407ba30e0bd6bda4c69b572dccd2d074ff535951e3383bb8ac035e06b88991b4942c7031dd70d5522a14872be6b8928a69d703b91829e5de8b15c9b53460c06b093b575e0b6c6a025b93072813e17f117bb4370c4cf798903c5b416921ec608a8cb339b92d3d9713709600884ac4fdfbc38098f4cb57d2ce014b8515089d4ee1fdd85b8d112691d2bcbdb069740d62141c795b7d1605c57343ebc6fddbdfa10f5cab9c2846ab36d5898dd721d352b90d223bb9215d70d5afe5e8e1c92508f45509a0f0fcc49b111dfcf3d72385899b69dc09380aa0802a79424f24a0dd4265b3754a3529cda6f0046f33a3734eb067e33bfbcd83007d407e6e57d42721ec11f8e0703e52a15734cb684d3723e1a85cfd60141f6067bdf2b9ca7a5b025a78914a8d27840bd822cc8558de43b38e81620db0b4043254e811a24e67d5afa1e99f2c067e503a5834e829d3d3d7255fdfc79a452dfa20fb65013c3d0c1d0f31f420098d3355f53621b854b947c72f87bca2c3aca0ed4808ade37669d201662f47eadaba922ce30869f3972e549729266cef08ff54c746c2e74aa39e510fa2d677010ec66fffcf47d0b3cd537e472e93eefebeda7c39f37d36b9ab60c8d071ea2bec29a4dc0d99172efb63a8dcb306d05b8a938b387bfd7235ebf435f087c8d2af970ee990ad22a62459ab9b8d2065b3b1669a8568bc3720ed2bc1df77d024617e9fce4d7837fdb5659aa156910166e5c9cdbefd6ba4da9cbbfe99942709c9b619f1a2bfda772559e29a2f434167272b2ecb756038c8523a4faa1543510e253d9700942355690ff35200467c0ed0a376f074329d3f0dcf75e26b5582d0208272951833cd115d7d9277d32a334731512e7aa6acca976c9c21a1a9f66760c5d55ce41fad88749f0604c1127c27c2f513a547a016080a8ad9a9fc24c9031d6b2f1307e8bc25b03e22e99e5c37fa61e10f3175bb4872c3f2283b1b2b25c0e6c80158504cc67cdba97d6fc6efb6250f57254f76305e6b5bbe0caca735af43c34d1b9295152302bea5920ef81c0cf97ed1c4216556bb45155a3852bb256c5eafdae0bcd3cefb98c46d154226bf5918c95542cfb522e60f09ab4ecee9304d71a95f6320f5ad51fc3c6974b3f6b1e5ac61c727867352a748ba984ec20c6d435fabb98503e5f0786e884b60802d92e3f7a3913739c0154147d7d2a1eb111f88b233f8708f5d312b7d23c2a3b6782ded2837c0ed8e4e52846e9ba7ef6b79117b614e331ce500e96586c636740e69aa2e6de6d72cdadf0ccb6fd00d0a876a178bf2d2e6b846973d7e7bdede64071dac59c0376722944c04c9a50bfd764e85a3a2616d6724cb44e893da655b19de78d2946882e039d526a6f12cbbddfb62497e17b1ba87f3395fd58862d0f41aaeaa9c67a5296728b2da42fe77035fca45f2e09401e6f39526b9ce3b7e5ee311215fc9fd4a8823211fb4f2678122d842e7a0e402eb99d9463c22cc40700d16fdf5c75ae60979772a49e400c0ae40019afb779ff8a1c369dad7743284a80d37b5887893eefb7b972cdd74c59b977366b6e1e73e57c8d709113e4154adcced71dd280f86c8c286840e6121ef026c99af0838d7bea5360599c31fe81d26fa686d63d97678ac3ec5d36bd40eb299589ad2ebdad3c5590d393c4ffb2a967882cfe7e6ae5a4666599e872fcb88a04303ae2bcb2a3fba173d998f58eb5b652683fa7fbe2f186a026104e72c9c52039c949ceb559af81f1c4a2057f06f4311d6451533469f5d3d6a5f79272881e35f104587f5f425235b0c84e7fd08243fd4a10ca9e855fa8469ff5efb372da890de09700c4dcbb0c5c39a1c50a11b5a3d90c8423b7972606b250282903610262ed8edb48eb79c5040c84bdc84defc1d9fda32b8ec29071630c47bd740872e58b1c9a2d6c358c6fd86a624e269c07bf624112f9472fa96a900e7c50fd14720ed6fca1fa6534ceac62cd6e2f152cf40e5cb418801ad16e37382f6bd7eb8672a7114e91b84879e8a93b5c511b0ad762bdb4be7fcddbf7d47d48a413311b2d72c1b3acab5e1bb9edee93863e2375d83b2b32e6409d9e3c6424f8af480f6d971984dd38bd835d877337d3d979cae2b76b37ac9eaf186b58dadd9d20d02a8e96720553fd0d8a40f9d12ba5d8fbce21157b0ed2be987eccb1904ce0f6ac3c652572cac0cdd5de120c219fc6874316baadd7c503c2f2f1102e6415245c2458a718729d650e2020517ba4b582dd035d8e5f868db33cc8c2c1756b59d61f0c7436df72f2c8d63e13a9a20a8ae76ea0cb906e55136584f69e633f72a82b85754b957172fd33536b089ba42d28d67f877a879fb79827576043b6511910cde9c42879ab1f73d5e21eaa55d0a1c7bad9fc2572db4c39c3b859571158bec9bb3f2083ab4a4f202291b32e284988c8761d117da882cbda8a230935f423db285961233f64fe7291de4b76cb27ce232f5a099ed4afd9f91c367d2f5b9a19a46726a4f655fd7a721c309ff54c5049e44a73fd5e4e4fa24a42c1274543216036662c27ef94dc411c47d125a685b2d906e9502d377c019e48f0c6685c71388da22c9bd664dea69e729c7df8a071bb619cf93ea83c5734b989f49703a2682c5a8edfaef5a5d2bfe40d8b67a093b7fdab7214e238222869a6d564b3d8ffef04c3e6f6e20bf0c19ad572783f91a8fb48012802289c97260c6c8fe4263cb6366a7a3e15cc7f97b855c8722d439c41d58589d44e4d1a5505bed6b5836a9019c74cf0031492d842500dcd3db8fa5074c0f15dab40789d965539782a35933cd3a0b7365ad9dca6b670e678721483e87b15d89e3838cad5e0234e6360e7d4d623d7b4bef9644b47d579ca0a72c71aa7beb3db1f76d97a25e670db8631c25ed018eb6c489eba11ee4aa3d1ab720680c05420b3bfa02e3530fbdd7cbf7b15b673d4a0301d6baaa0002a4d6d1a7255c7e2973ad9287ac0320aa9c95da98b18500e2f4b09a565cf298ea02535e87235fd6bd1892adae2cba5463e3eb93695245066e5ba4d4b878cf0827e03b0b9721d6e8430542eda6a1e74963cc2f775e0d982ab48ec1b4489df7e87ccf7d6ea1a9984cd2ce52571cfc5b740eebe5f62a0e704455534e1f2abf3500365c920f37291db1450daf302fc16287bc1c2cce4085487ee77868974d639904b864cb6a863bf77cda656f89892b1298b9846271d22ba046081f5a28e81f9ac4f648313a37281d6a9988a516f0ff5278ec5857e198ec06c2b37f7e239f84c46c171e5e38c09ce206a04db260d17e127cfce54bb13ac87647d03dab2dd9a283144d98baa3972db6f7e788dc3174df014936e4952c5cc376bbe73ca58544a929e9503e30d47726aca4afad5c0d9f7a2c3ad0aacc74d6bb72d0930e90b6d99328e033295281b520921104a7accef6233652d8bf8cc023a629387766bcf3f0899273272f147f2726567be3122633cd41d1c94b6c35544ee3cd5e26afb2a97216aa81bed2820d558954f4be11efcf846b728e785a92e332de4a205c165f86959eb1010d98745a872b6b0ba19cdd3956e5304a18dd7eab657e7a11b1378b640febb05c6f126cf6c72b0cc5b302d1c4042bcedaef8a114c9231bcb118b4443677273a203a0ac37d0722ccedd6c458020b1974d2760bd1cae982bdb55336e48a70c3337ec6a19a6190d15eed485b34ec7b782190301a838e9ecaa8aa98a9d53e9ef920aa819be20886dd0602ebc11faca3ce4a389d4a192fedcbd64bbf7edf33b31072368f96371c672352bcccee9bb6e85ebc884216d601512e89fcc1124b63e4d7eafb0d11a3dd472f7034d0b51c329f9d24d774fb61c90882d123f35e2ed9030408bd92501cbbb7286d7070b7998c6899db5c12262de7a89d8325b04f3bbec018815325821c56e720ac2d7390bcff0b99aa042ed69e914f62226c52136dd478ccff737642b20e57256d4574b3c5dc5bedeb14ede42993d543d730e542dc9df5487ba093b3f0d9a729cde461f071dc8634fd9e5a84e0db46bcf961c8754bba3114641c88d7ed46972cde0eeeeb66d35b0f7e8ecd88ea35935378a001892a3e4093466a9c5dbd5977210cf0fc9d28f833a569efbc273b51454cb14b7cf2fb305a54cd206f3dccc7072eb65b4914e89e419cf7d288d7dc33987758d880bbcbe0bd449dbe85c19e5bb1f1c995acfb5c522e938848987b4a5e93e6a8fdae3b5d93dbb3a17e04b7cc8df4134e2c83c9f73c3bcc70215f406d547c8e16d12fd59633cf7069b63ed3fa6aa721c00989a4aafc13dc1a916f717e92882d1d962c2135e88772ce784c0b3b479675c5f0f9e6976210e8a37f2775ee19ec5a3792c352a9cfb0b3f1923612480f572c740fe0ed65795cbc9f47cc802b0f558c5fdbac4ad0f0b98de1d6ae94e78be6a20e4ce0981c2bfa3fd2c9cbe7d1aef69e62d4bea65ae509ff01ad625ceda7a72563a2a02793be716344c76b63210479325552a406fa07a616f24ef32ee47b372916beb50c720a360792acca6c3f3c4e5d3674b11930cc9efea31085cbf76a772d91ff928403d451c3209ed75545dab29b517a30c2a22cfd4e5dd3bf1d1f63472e43a5d8c741fa6a5c2cd1225141958aafa873737a11e55dbea515fe80776797215cc24a7984b5e940e4bf8b8e3bc9250cbda63f6497b534d1ba62798f9df61389ce0f2b470adedcb8e503b3ac39dd5a33efac641632e3914a787a9e3bffcba311413e3c9c7163320ba708457f057ad6c66d3452eec998b207ae0413a52fca472cebc30f70755e120faa6a059e8188ed810060ef5cef074634e23def7527c175e9466fd2777196f5d0a57820acd387a9684f13cfbcc3de7a7e31c83143430785cb938ad2446ec474f8279bfea1c5760c0ac68264bdd2d4ee135f840d4eb7d95722a4627a2d6abdf4ba8e3fbdbb2fde6b618e358f7464331fc05b3f7e5cbba3572da97f5299eb602eabe7f0e61aac3ab75cd8b9c4eea9af2057e03ab8470097d3b9537da80e836a28e6dfd5398fe719ea1b6ca40d3ec1c2e5a0a8beb0a53b4a1096e40c5a8734ea37dec23c131c2eae97e728eef073435fef851cb78122e7afd72f9ec7c8bd4c4d3f4399ae6a95a3ccaec4582ae12ad50e6794590623ebe9e5f25dc9477a9e576786e47be7aed45184bb645e7a2d1e2e19ef0baf9b73524a2506a9b95d5ac486363c10c8908bbe2bb7cd33b3724ae5f0c1e59e09af6b48b79966c9fd65d18368eb68fc204245ae699b3470a3fe8437650d58309ec7de27e5b184884542bcfb26227171225eec4d5c6430dde943083c5ab84fcf873bb0e6be35c1e884a4bb3fa1093c9e9abf9b8e1ea120d7f1de2495a8e967bfabfed6640aa2b301c197a43b5ddfdaea6a34e75476388d47d59ef434ab0e8b3b46fec0a2ac54472b6fd7db7fa3890593644e504f920490fcb6fbc3b92ff6c187869a425e89e867281ae5458b786e4f7bf5de5dbfdcfe5ba94ac772db6ad5677855afb697a4bfc727e65c899d0987a5aced1bb21f62b9d9b563a667c793ee426a534afe36a625b72616af474474cffb1915f443816c28709de0b0c90f493e3d936c2b717e0da2572bb2ecfe9dcc27ed89166a9b77254359be201e6ecdb0f8ca44275e4882ba68372955ace4c111390a9618de71dca19cee671c4ff076953f18ac3caf1f26acf514025fbe0a2099b275480aa4342d453b3e065f86415c91010cb9afed7230c0d08724f203294ccb8192552c6787289242843a3385e33bc0c54702497b2672c2e533872ec322f4ac94d32dcaf19c1f01f398da706cbf8122af2c1c5a3285646cc813cb9af5a727d1b1a559e23ec22dee498cd0fa8d20535f11c4bcbcfb5d5c872f3727de4c0e818c9b2d3b7f9be63f688e047d28b1295d1ad51d77bdc8509fc9bdb66d03ca4d561fc830bb4ca715b862323e9badedc3a83d3de027ed5031d9a21084015bbe3109c1582d78c8963dcecd147e1d1e687023cb507d2cf14e33cde5b9606d95414198d02ae61170fb78b5ebfe4b735cd0f6b33fbf7b838eebe50051e23726ca49f0c903b8d53d49282cf6d4d9848ee941d266fd44df7e5f6e2c2fb8c6c727275602a8da1215742e9598d6980318d19ecb948e28704e3ccc87dd37e098a7261d08aa1e60c16e9cde111385136384567c2c0ff8e508d7216babee182e70466c299ef79b52be6ad91761f553b9046b7503190ac60e51e62c99b6def2c245a7247aa1bb2eb09710d81fffeccb63cd6c5d8cc67826dc702589e0f330ebefa9972a220efe7520c4e8a9e496f796f49e042aa9460df4f92e074faaea00af3bed463e9a443f7d8b60bf671a51aee044ad6d19b3a52725657fdc971bfc0491d85a172d479157ab8cd80ef2a27c9420eb3f005853c59675e2f6865a9c44780f7caa4725d0263a901e49bb337560ad70b80da6b3748d5b71c395ffe48b765678c17547210e901c5da2234c72be2694a6bcbc061defd49b8aad282049a60ab7bfdfe4d0b0dd26dd78081194597a33f78a049ce5d814105bc68996c6c0bb14157e2dd8a22935e145cb695ebac61bd0192efec63297eba60fdd6035baa616c6db07060a6728851dd299f00f47b7fbfa0f27b34788ce9817273d17870af373b00dcf3821072e8e663ec3c57ee36d2315b40352ff19e65bd804092da03bb63e5d951ff8ea81e34fefe528ea87f426b6fbfedc894cad4bf807ccb867dc8fc44245472c0c87772dfcf9048a96f4f8352b89d52eb05a30f4154816ac2ca9bf86f3a08c5c1afd23b45515fb9082160258c9d1a59b440820ecf9d0a24ea4280f7f1446bbd492d3372eba9e2770fa66a91abede1bef929daf37022b343830fa91f576737618d9fd2191168db2f61e521a04c01d881be848502fd938ec9e122e2a94657cb37cf96ce7269f12419eeb841a4fcf2a4b1cd85b4e6418d94eb0de6390c721b99e170070d72055aa441494d358107eac2946142e125b8997eb5ea45b9e30b66b3f8f2ec292677af5aa8ab3581ff0db49edf454bc80fa34159ad84808268ebe8693b1b55ee0d5af56d505ca078859653404f27958cad658b291cc43ee8b82be17237a833ba725063bb0dded91a84e12614effbb35ebfc1b19143c109b7260faf961ab2725f72f9858c812e96608310c40c492368d76694728cea1255ef2be7dd2f035dc03c219664aa105842ec8d06767bda66949b3e56a4a096ef6c50f979eb8fefb221617234fb5787eb26bae279c050ca06174d2b347e8ad9ae6d58f1e531fb857d04932d9d46e6f1b504cf968330f9230f5bfdf4df08fd847701e5a2b5a7c988914cb3599b239bdd11bb3d37ff4c24ba716245494a890f628021ba67b9463628a5335172c67d2723caacb450dcbb7e467a2a7c32e1bc60a45175af152c0ac6bf3ce61c72ee937a3dbd38371493d390461c8c2a7e29e50c79496e9b8aee03dff767a344729827a6692eae0452331e98c7afcc5a8850b4fd1271116cfb5dfcf9db94aad472d03c52c86b3925cc1f7dcb7b04cad9b7a54f676177489cbabb2a18d3f2c358726830b2b3ae43cb27c8d46edfd504b0ac207fd68a77ac556ddb72259fae091a605d8e123cf2708cf057f9dd0d3f3cea0f401e4925e90ab5f58edc857f33c4573f653316d7f11784577f68e346137751e4422d22bb76caf8a69cff207c7790fe72b408c32a3c8a1a66e49921da7450101e40d5c6002ed21c30a4478e4461489939e481326645381acd88e916f4a55cf5b9fba4418b61f55aabc4aca934f52da36a61e8c521db314d8838d1a009923ad2ac08322fad38f050044fa503ecf10b55596df61586aa5aec6fb0dc728348e71f17ff7be86688a2a584c656e3eb764660728198e6de8d5b7685c2dd19df9eae1bffd8e280b5fd3b5a18b4ce7a1aceeb4e72dfe6ba03672d5ceb39eabb02cd56cdd26aafa3590c8f45913972ed9bf4beae53c2751eabda6a6e4bf446cd83d1f3284a57589b42e2d96df5fb4d04bf6e6db372e7e92a7adce642d86b64776dacbdcb32a72265bed0f241c13a714af5677c077244836926341376add3d9ebb21451c8f59596f133dcfaf97917c14d44e8104b3d861891e81131cb25f9f47a37d8474b69d41d35a36a179c12708fe41eaf4b4308acd8b6905d3f7dbe86f2d9bd7b6c1575429f1a9b8222a336fd52f0eb7b4a4272de214eb1e18e0bd0f8696c5972b116c9c01b06f6414499d468c0a0d6a5c87a727beded7ed57c16cc1eeac3a414ac65794afb81b93f8e4ed2bb02f42a387c82723c0c9d9a57ad386e4f31ca0568c1d3f6986f7f5edf0ceda72dc670ad9d5b731cad4b9b33014ac73a2e93c2a7eaa5539d32e42a099558f6c51e86e9867bafd672e385d1153130bc9def1a8542c5759c8abdc24f399538d635578b864023729d665c7b7a565db432c86b06209de0088688a668da30e6fd3b94a0967d5680900d1b0d394d2fa92a3deeb2b1af711ec494c0efef1ff27ae772fd96b6ac4cc0b81072666db9553cf2f6740a7e6f4ba2db3d6df98369de2cffb85b430ffbd945d1a53cc8c0e052a9e5ac4dd44129c46ee0e864808a96d41d778f91bb41533c846fc072d41bf80cdefbf7057eb86e1b9816ba4d1317563fca03a78df35fd4696d28c072c44510ceebcd8147b4b9f3c3d20fbe3ec43bc8f9548292f82c72a93bb813284d0451d1bb1e0458d6bf88c5b0bb3fde448791323187716e309c11675de07b316383dc4840ea81a391cbd6d852daff6ea36972448f2e27e1814a239caff747447231678b4881e1e2362264a9e5470d9d2f05b96b9755e20945a09326b59c360272af7a290ce02397aa1e599772b657b4eaadfd8633a2944a6f6ecbae6a47c8fa5bc7478c980db8648da85eba4274c16902991c5cf41dfc29643d9d37ba14f1de7253f0564390ef033d1dfba35e1862331293851a4fb7b0f2ae3fa31a22c3cea8729d138fe1e5be8a6cae3f5398f89aa64cd3b9a774e98cc753e04789f323d4ed725b9d1121810be7b98b73154fb9e2729373bebc98ccb7ddfa21d96515819a6272a730afe5a5cbb84a522168d77399e11d2cf6fa56bda21002c52f6b97b839d26e65c5058fc256e7b48d72f671f9c783947e38801b3025eae56c82ed5714a750329adf2b941cc7e8dc43ce759d992a55b49f7f5c92e8302fe501553517bb5fca720f495468165e1c6f2b058d6db3985e6bb9654cd84f8c993928083d86e12795579cc99399605f1869c5a7034e852abedabeef24c4ff11bdea202cf3e372b80e1f44b3ca083e83b4894a71de8fe0a0c277061be7af1db032eefaac9805268cb5722eb4674dee13c0ab6e0a21dc0c43f8254eaad3068c9df307adb8758e0c1c777289d2fd99f2141df1667424ea78b56a41e81834bd40c0f91a83ca4ae7b57c753e9e2e9b40e1a86546d44bb7d32753eb12ccbaff9e1ec95d39eaaeed25975ede0a4c513803e18f293c8a1a0aa9439b879a76691fc4092047c5ca5c1e40cebd0545e4fb4c3d01dadf217c71291d9461b17c5a368f6d4813e06415531258416e6972f26edb4ee0e4ef3367e918adf32330fc297aa8c135dde412ffda8b0b4b933443fecc3bc7d5a3ee5e4fbeb7de6fe7d2515d412c7309fa6ea7deb1163e9a57606b3cb3f1e28b6ad5cc962506687ae2ec6ae2c95c805a3edd54df9ee87d18aa204a3ba2e239e0cf1f123fa42959956bcb3a19155357c6bbb18321201db24f7f1572f398280dc25876d3f7669192a8d0fcf3d64ae7fe94ca6d4545a6c19eac17ba72ba15206d92673f84e2f0a7847ad8ed191f2166f3fbbb63e2756a9933f458f872a7490b3a53d5c494c172f57dfb15e40282be397aeccc34b93ecb291768e6f922e6450a05927e7819ac336bc904243fa8cfcba21584a2d135556cd6aba27fad72837202be105e97973343c920ef8baee65150c2f54becfe82489be4e1fae4f772edbf3b75db42f25ab56257c205043f4913fc55344235450aa6c808e52b40de3af438e41ee729c1d34c1aedabb6a290604170d834ae619f089ba327de335a2172f561c5aeefa91fc2fef45ccee24254f3da8520d7e70fc5bb4a478411af913672c318e5b8ed30f493843cb5b693662e834e6895540b311f8308f7d3b1ca22ab177f2ecebfecfd418cc8b76f6c14b573cdfb7267c8ba072ff3eb1c7bd229b42972a52d986ec61984a56b6b40ab73d2829522374463d4487667f34a5ec0a40f2d724d7af28f3ff2d3761a159cded9744ddb2d77305f81655cd7e84dfa9a8a01bf402e5b6abf2dfa63fbc42f0c9c4b8a7ff6e47f2f0c206a4d50f5e2bb90ecd2b87243d09ef3a2430676a071930cf1388748d3fb147870f345cb099168ba18b8d772d32adeb4a4f79c6aeb5e602f3c99449ed35a91a061552992f2aa178487024572509273b300c0d7fc056dcefcaff8206ca35f22428031ca69a9c2d794d4426072f4d18ad9533508c31fcda3e812ac39891a6b8c49fefdcbcc4314bb5377ba6b6c14bba77bf91c9c86c52d242a7109573e6749f7462f1fc8a5a08d7f6964ec324a6ae75d5a4ce3fe61c1317c8c17b98f4cb561499e750f261f876ca318f6f11072e69e6e6f36f6ea6566859bbc5f012992f1ce8103e762401ff03b3e6b630ec0556370ec6736bd94c7e9f9aa3725ecee03d41038b12c732949b20a28a7be364172d9d25c0eab8ee1daa6cbfb75b22e77ab4b19b0a028803aec0d6e62a7b3d962725204d3324f35c0f049fa0656d8cd84f4c6166c9441f1a505cf57b54914c9ef72a0a54de497eb48f1a9853e7825b7277b3dc60940b9aed94599c39f2859f068721874e1a9356a98a73e03ea298eda55f7d56e53a7ea5c25efaa448128ffac0a254bf46c0e7e081c15804f3ece24fe5d2c9d26949d8f4551afda13d57a8316c572c789b613a228eb1615dca520d317ff335c047eafb4bab16e34f6ee2c11f94f72976738fb0466e5f816afa8f7c9377324a339d1db639693662550dc85aeda4013b262aa0e9024726dda0ff6156c4e8ec17a4a08664ab18b279131917d7ffbec3c1f312e9859d27c9f821cfe36f680dafb00e465968a64249a4c22ca00e76f7272948b090cab584c2604271e013dda3d15ca38f45dcc58796c45429ab4aea429598b4c8f1923fd1e476970f0aaecbb3e00866d008517261e826c7e1dbd18f1a3336f4522e0c1a1e76b083a7190d74213a4cd7586cd2c87e5dda0887aeffb1f483890eb46b49dcf9875447a261bae9a61db898be5342b57de5c1dc79f7785801272fe21a8c74ea016ebdfc36236c0edb303743ccaaa41d04ceb4fe1df1abb9baf609156ea7900c70878b457d0924d938d52e99fcbfa35413fa35429cec26123986f9e97087891530f8201b912c8005a1d8c21a51ad794476f0f7fbdba537b79877255fe9c62b3a0a48e759fc4de47beb00ccb87e3d0d22934a2667f9a145feab27274c1b3db1adf812b5e3b1543b6a9919ee185db886eb838b1afba32111d25337260032f1030881aafebebc612e51dfd9deac42584800db2107eedbb538d2b717284e04e60334f13a2ce3306d88d33440f9c08e596d70b29157944cbeb5d43a272e5ce9b150fd2412ce426d60f685a4182dc45926b19ae83470ca9bfefce7e6272cb16b9dbaa4c21f896e077534038554292171568b523669d6182c107ecca9c720dc8f7fefaadac0a4ce52b0ec89e71d35101a003aba4a9c3e9b09ded7759c3727392a30eb14c3513f8c344e1d3adc55deb3157fc6953834ad42ed43e395aa972c75f7b0d66ad336d0ab1ef0d30573ccaaf4e48b9344030436ec69300b69b4b077393720dad99eaabaf5490b157af78b447bb1d894ef80af2b2211d6f8e33f621621c0c44dda90763126d42d528e918fb37491390d6ada772b84ed105b2c82b72ecd97f3a8a2f8e3ccc90a512451de611140c1a30358d855dcca698d8d3beb07231c1ca83b56075499501f443b2db24a15d8f75cbf8c7718731cd475635df7d7247378c008b57f4469a39600c551505f487c1a56066366b373c0f0202800ec272a8ad66581fc30015f57034fb159a15e333c3a45f337b4384113102d8c0cd5b2c421c32354234cf36ee3374a9fa8ce9f9120846613ee220e14b9f957c10b0ca36e2bc544b077d8c2a9bcb0df4be1dfd66de767d8df4bfec4914536821144d80724f82b234110c6d0ff69555485cc640ddbec2605d4ab7a2731615585e40c5bc728031237b1ed48785411928dce0346e631fcc2bdc962d5f53f8536a4ce0f3817222dc5eddd7ae85847e94d2444e966aa8616f74000e85b616ed1fdf1c7efc072fdb0f6402e88a02f769c16ee178f3bfc088d4f771ab8de6329e9852832a729a72197167e3136f10f5ef7fedf2de318928f9430e42d87aade6cc41c2e89a19081527d71b8f8975235800b0d7c70a9160e4f615ebf29a5fd22cae7b9517a596fe72134e0bd98196fc958792d1da51f5c05b8d52dc69701bca3e4f26daeff9f64272e4b0d0badee7d8519fa4acfccb35c1b6f3321fdcc8a9ab564453b554d2dca144ac8bab6346cfc70d5a0ca2f4e7ced4eb9ae896a01a91e40cdf3491a9e8be2f720ecf0fb3c20918280b4ac0fd2a7cb429f916b37b9a66b3d7895c7b47bceb01484ac1569c0af9eff33429ea3abe8524b87cfb3f42fbcbf746d447e011eefc216c8bcf4a80fcef2a7b9d342315f38bd43913334eac266d3263d85edc1031f10f72d929eb45b02299e00f91a042d3e085ea024001163ccf9e494adc5f453103fa72e4af523cb14b9c8fc2640686de6bbc3bfcf248bdd81f7181fa4f77d15168bd72bb21d274f6aed25b94ca282afdb14bb44e26b55056615f9789c01a6b5fd52a72c6846b559abd82044e6dc112a83d322fe3ce465550ee72dc2669f0252c5d88724f127d89ed970d45a00e8dd9bf12f4347fea5963d73ce46dc0aee61ad17e8272e8a382f6b81ce7aa01bcb6cce93c15566eee5514d294af595c694ca077d4f60e7273ea2072bae400f0e5af519537ca370367060887ebabe3cea614fa42774e215ddb7e68495d74074e1ec49ae6affe178f10e974d1f092eecfcfc24e81ed9072e85ce7a79e15e11e996f3fa5ec222460a955834931f7192ff9230d8d023dff6f66facb1cf5b30dd2d7f0e5864a13a2cad57a770044cff6039298766839209f72a47d90fc3e55eed08d884c71021777f22b0e145e91db38051ea849fedb1cc8417d401d84cca4b9769685b4388e5656f4b9e855ac824ac4e4da882e41d05f756c5842dcbd4d6f93fce4e7399775e099e3d4f07c13ec6199c9fa219da16b526629dffc7e2d413aaef3b752d69d3a4840a139829eb10417819b6d4c94ea4b76786cc58fae09cd295e52de64531035ef0d143483f4752c2accecb21e41b5d3a0183b7b49335650a5b40d396537bdc909fcb93a8c60816ddbffb28bfc05a01fe72012fb67de4b5b703895e150faf84904f3ebf53c85444e0c174d437c726eb2cb561bb3ccc7127135fcab1eef6d80a63f0fffd6d2332dbbad8bb4bcdac944daf6b77239fdb6fc1959c413e91da322b273ee65ac0455f9d50028b7b106a9f1e2a67972f8f226e1e50e254f7c46f6b3a51b969fc726c5285cd9bb6a218a98caea2da17205021a314d1b25ff3358e696db21c9385ba01853abe79507d902619a38521f72dd3f2f35c034fed70242deb0f8a6141f2a7540ca84d5c9e59815a3c60213ee725a2cf628fb2f5682b6637c245645639b486735ecff5b7b736b8bd9fd8ccce6720cf6c2b2d9f9d6edcbd4b073a6a629b319c7a2c5c391f3cb029aaca6fb0f3472e22e6e4dfb97905608e9f2ac0973a91cfb35615ef53a50f6885588328a3b2d4c97d6f6ce3f3c4c8e406bc65d66ae689921707b9e904b85cf6a5581d9059bd12f7d2e2925804801056c01e40853880a0a7915578c24c9cf84f086c54dbf515b729389f9ef06ca2785a802be03f17c0a103b533951d0736172952e6b6307fc3f69a275fa6a66af0326c153a475117f16397635aa4c9796e6c1ff53852b50be955a86b4e2de7f6bad916d9afa133d85a3ae1342c3221f638e84d3bb26517262872afb90ff7d2177569b3bca30741d32a1337649f6c5d6527514ffcd6431da549372ce413d9cdbb622b5d8dd14e47dc500fad6ba71c6a83d44d5209d91520184d51ffb019448bfa57e66abbfb0773b7a22a0a4f476c9807428161c65141e4bdee0726a587348b4a8c46f546379dc9aa18b30538fe0564e788d042264aef3bf50cc2f15b0b5e1173815cfa3084d9048048db15e8f45b0c8393e3551ec57137d3c657230c5c607d145991532a3a40700572a37ced10415bbcec15029f267eae11f7a7265a00220d1db9c8c46ac97112975b1a1ab6bc9c168503b54565e673b822b5872ca1a245fe3af1af5554b01e4e7fc3b2f06d94c198aa06ebe8e88079e90d00272590026277f85e3bb64a3fa36300fddb1897082ef876468184c36e774b7fde2723dcca9c9966715b1a69ed1ea65694663940a25497ea78eada7e607406a5bc372b38a46d4631a15d61a6c42b0d8fddffa1f5efdd1f348d536bb753347bace442de708d8fde02c9c25f90f114b0ed5e390fa89cb51b776eca00c62dde5156a2c722569c0e1d149f001753674b41a065dfc1291e1eb3aaa3344794d26da4846d672dd667a5441078e560c205623afc6ae859564eb3065761ce785740f023747a2234f19cee9889c18bcee4d1b7d11e7bef95d150e0ad58306114b36b324b3fa737299940095271e3ba8f788253a581286cb4c094a9ca7a25b96da336e7399306172034d3906483b8c50e29dc5c1803ff1ca327a7c8035330649871dc154b63adb72b5c8c7cc1b484c800607158108797ea71a8acac827796028899b58a94eced070d37a9b10d4073924d071ba01e337fc4e8006c1774965127cad43c2f1c163bf72869877da5c238674cc1f0458e1ce1e8f206b491bbe11249980e5759ee7f480725e903115c0549b27f12fabf540caf18f439c5f70c0a85b044991f339f6b28c20e27a4719c3f89d9ee764b986e1f9b3ad9977baa50262ca36c38314c7f40c22725e7881e6b51141b3547ed458bdb84256f1f77eae99536aac784793eb2f14a7675e63ad1f453b3e52005971ece11a1dc5295d8cefaea054f07f9dd563371842724a71fbee80e34dce153995987019be2511f8987beefb1fdcd5b3d64877d74872b895d9474685a6037f9ce35031ce8dd15293f4f74bbe1644846acf7490e8057291000304fbfcf4fd757712a7039ef018f13fd6bd5d7faf74fd8c89485020027218557fdafc63ce96684a6b3c285ae2ac4b2e0403c9c85f4f13a3b96e41f7a872439e879caf4e95fd80ce46b9fd5fb32b676d5936c844c76ab95e782ed1d24d1c56403c46f8416a49169a2dca48bf83ca883fe0ece6c7a074e97086319ecaa57242ca0e4285b787152b670189e948d5d8102e3a4e618ed6ac6ac387d7146e1569e6439259b831b7edf38a97a072307f8cf4e2ec5d1f985a78afa5d1954b8b8a726dbe5b6ea466072e853d111d3f84013acc8deca57079344bf687bc1df35b3272fa7616f39cb29995f15a56a9b92f032474ac996177dab0c226e3f378c3d8b97265249da7c1a09263f755cf15991fad30b1d4eb8bbfd94f723ff0154904c9b0729f07f8e65091720562f521086b4c5b8d0e55af41c01fbcbeaff71442964a08726092cc8ddf35ebd92ad1ff37c217ff80a47b7560fb4c0dffa6412b93609d855ea2feb47b37053092b06fdbf0877783e9c40289ffcc2cf38ba1b3679e8594ac720db3afabc1cfcaf190db2e8388328e53df128b6067c807ac8e0a6565ad688672914762c905355115346970e6b376fa7ac216fcfb6688673d180a2f4c73b34b3f1e292b922d590a7718f9593e0a018177bdc998e7fe12a210801b4e79a84631722119d7e48b3f31999210578ebb21e85ba478f3387f13ea97a71113ba8fdd696181e95595396bfda3b9b5ca21ec6e85b11284623d9f7c1aeb88fbd6502ec0e172496ca72539a750c7a64ff535e861dd3ae88d1056a9f77cfdabe488129adaa62cebf79e6e493d7a0e3d05579ba2241b9c63aefe867303c82081171ee55bff4d727c76f7b6811778502c08a3e0180b95f273bef6485f25961ba9994fd41482414ba721da5d0edf93b330d37c09de2fdfc0f39eb87316bfa5ebfbd957691cad46724c80026136ff6b3491c4c55fff00d213b6dc26679fa4dfd233c9860ff95b9b5666ffe68c2f9d4e07f88345a8690a8a76c7bb460d196c9b2ee4ab32bf98ba8d72071ddb090189ac1b073903e172cbdee2c2c9427a150ef1ee6cd448ded12df1721e9526c9d4d661757f98fef26feec10a51c946119ebc0041b1b89404255dd73d54eea63a406803b7cc7ebe9501e9a7aa48bd2bae186ab45b67f64b82a021790e6d26d3bfdc84ed229acc8ac10e5e5422918a0fab536187ad5f0e723d9e083b0368a60ecbfbaaeebdfd3a03fbc85aa03e310881a5cef1da62444ffc989c0eae725411ba3b1e9aeb20df2421cebde48179478c61ba18b89c10fb3b00902aa1f6722beb49dff4d10ffe0f49753b2e89551d1666c1bbee47f2e944c15a831b656811909c960e9ed5f05782e7064849dc1ae89f38547ff2c54fde1f1f57d8c69a17729b5ef887689f0989fb181570ffd3c8421be52ba7a4821c494b786442cd02d272ebcbc690fb6acd3cb6d974469fbc1bd6005f29ce9ee76a90d52c953a0d45c10df52a77c364d772aeb9c7a89ed15e2166b12892f723755bc20e340a2e9712de7252049ad324d9da5c5f924fcdac2b7593bc9a500ff8a6a5333e383b33d115f972d07e30d3358ecb3e8886b36cd8487d23cc5161e6a4b6d3d0aa8f865dd792947218783a3f70adda7fe19491cad569bdc993484d5de5e75893a5d54e0b81965072aee36048c80d4d9503ec8b9feefb19d9451debf7f7ec567727bb8c2ab662777247a219c6d5bc34e90a0ec3424ef51b341d5c014d63f36089641ddb2aa579c94d764dec1a67c41dd98ecf51999c6202d98820d5348e8641b142f363e899f82c72c3dee6bb1663f4dd16c7c3839953a32569f1b724b6b2719fa2a4de2ee147922728ac7943c5f35f81f76ca61898e95fd871411f35ba212d787c90965440cf2172a2caf1e901fbacb946e9f314e0173e224aa471e69290a43911c921c3d294e7728d95680403f9d48f9819fba467c070413357ebeaa1f8653efe5341632a77f17267f5ecc58721f9fb1f44b465992c4d5f285fd18b94ca193720d9eae4c01e7072d0bbb090f06f8eb736edacb5ca03ac798c58b5dea2959634a89b2c9eb835ef0b25a36f67c78a37ab09b85c42b777c5a7b5b5dcadb1011d0e230ef79130ce8e728366198c24cef04f0aff178ac3bb631bbecd4bc544fe0d8c529b7ae99dd1e06469bd4f7f40a0d864bb1b2cf3879b630894c0549381f0f07d51aa314d743bfa2db3a1fa3fd215e8104feec96ed6c29a88e61807e2a11fdfcca30620776a6b7f721851a2d8b35558122bab7605d4aeb88b6c8aadb46570165d3831614167a8a2728683a9ff68ee79ff875b2c251acc68267a24c0dff3970239f405e306d56a9a6541f2ff54259612eec6fb3980a7656ab6f2efde3551cb5cc93eb9f89d6876b37261960afd7fb8c6c4766db5e74c164fb5c57b9de7049310b14eea7391ca88ea727c1da7b2fc7ef45f2ad36c5fd97256e18ab030038d7d19db5d416f3801681172bb03ef43c909b53604e17cb88f917e2c83927b17a59ac50fe9550b60a2d26572687275aa76b6090c83650a5de36bc49e46815ca7b249840737156c7734f6ca55f0aa87e20344dbfc5c7f1e4ddd204702f21b8fc58b1a63cbda4e5e9cda17457289d4b8c3c3e3ee8664c6039dfebe0d552d0f7ad45c25302d14b283464aec977290d06a328a6e89f4f8ee5e33e1697b59feec46aa4b32d992f32798ee31c1ea72ce400a5c1b724db19c40c5b7ee73286c12d0f2ae8221c77e9c58ed80456baf7257251f12ff88f111c4b72b76ddf7e5056025e3f419f5f1fba5cff0d5767863722ba8b264ff3839006f41bd8fde035d742b44bd5ba123ca4de06bb41018f5293c048f9de566d2957549f70d29c9a709a695ea80e5ebdb24f49f6b15534ea61f68ad6280cf9db60acd5816152b1badbc68489893b7e840e95c3dc1e9d05c9ad5727f7c7099825b6c71f38efcd5bf1bbc924586c1c12afa7d331938f53cd6e2e455e7501ff1d54002375e264194feb60a3b131e693839911eec10d51df7ad3cf8724d9fc09ae50edcfd499d4802e02e4128acd2711afdcd6ea85fa8fcf47c15d572d9b85ac453308cbb33851235ef4cb6952dd1fa52f9abcffee2e25d60a994f4727d387112f9b0426517ec92f5575b85a975601c39744b66517ccd4986291c827218524db7859992d3e07d1dd91417190eae4692300eaab216c2ed369453421e72c2622aa509b523e31596541da504cd0895ea93b214d8ad4fb978fcd3ee89b1236f8aafec285a2e6197ff76ec32647aae89d047d3b19da74c2f10d8371c3c2a72fdd2dc0aa00c378e43ca11d521f4106c222af094dd108d8b024c8ca274520f3b19f6f019fb1a1f061a4dbabb5fd9130f2e3eb48da3869c54e4d5902428877e72409f3f73bf3ad4b49e853ddf4e780fb58acf42e57731f8a7d12e4a86d2009472d5e587bd6a23d2478cafd18defa774a39946052481ca9e108186e347fb83af3c2bcd2de8fccdb3681b6001555c1168edeb0769a7e9ad010deb484809223c7b7261e6fd2a7c6c056fbdb3474d5477b737c118caf27173317be00fd4f7e860dd72977e18b411a99f01cfd433819cd9a77e11b04d8700e39de07d896f54120fe27260eb543d225df88e0b5c0e711de965cd699ea2d53b5e5c90320a0aac5b743572d9c227cf152221ff882a3e195f8fefb2f442da54bc803a40791b03f94c3fe7724b79f905ce48f657f179286f15ebb443bb99213ed8249754fe83f8912e2b5e109cd4662e2da0f8a25c673c67142e410021ac48fa21a7122fb89c2fd124d51572f90de8fcd12f68674bc0d8d1e55d4904ae101970df40525044c873165079886f6ff018c46da1ba4e5f0cb85c6ac00eda0ba8953def522a16a6131bf688cdbe72fd7402c142779d80f2dabf27cea9bffbcda358ff139eba6e1de233d37c0eed729f1b02cee04ca947028b4a79e9a0872d8d346819c32fa06c7ad7f63df4ccb93e421bc77eb5050dac0de10b35c7630ae4139a6e7ad238056d8ef7f71d7563b47291cb99d43a8b6a7dbb844d4acfb9953e62820666842d9cdea912ff334799af4d8cbcff30cbbebc2a5073bddeb8b8d0b95455c55852e97039fd3e57484d489a725e0382d9b6b99e9edd7fa3b4464c4865622b6b47a417a1f2da27ba49c722b57257da49cda55179fea6bc3f78f4cbc099f9f27a8b2bcce717287f1f23a7b919498dac69c03bc50b75bf0b7e4032f19c8c3c3170d3e9392d7df4ce106805a63a454917e470d11e244b964dd674d21464f45bdf8045a2922b9a283841d205eac2720fc39746fb54e56c7b9a8e752d885573da622e3001ee252d009a568bef875272aab339f760ebbda1f538c2f6200c7d15f31a4266408d0edee58c5da59ccc4331c419479dd0af0f360086ff70ae9025ca2db3a3605760b203e2034688d047bf44fead89920aec879cfc701811e4247f8aede5db1a56d66ffc7c3b5325d71e1c182c94babbed46676cf56d83f171c926e0f252e4dc59e3e49ce83b625b527fff1db540ab291a3903ea969e2f1943fdf89b6bd694ee40ddea8c1ac5e350d95df672355585353c42c220f29c3d86ecbfee3ec45c9d9c9f431d23234dfe78d1b038726ebcf2c3ba6746229e67b9328eb2afc521ed6a0038a137e65808224a8adaed135ff211bd21de5caab68f6026d0784918e48bcb3568b152962ad2d342d7edeb72dbe643577d500d7ea16c373be80992a64fbeb7a0b45184d2778e9eb7296b223a6366f357b7480d540523269c4f95e0a1a3a1efcb35e3ff3436aa2135b2c1a86a67efa6bd67ccd3e5a9998dc2e64abb8afc7b4d69faa411398aa32cc05edd3572a3e1889286b4cac2e5ce6fdd57ba967e54b4832a546de1275ef0662c9f2db072d187c0668fa661f2efabc84f20cf67dc73497ad87775e6d22ffdb1c04e67d01c6d62ca4c4311021ba51076aadf8f9bcfd2032789e8e5a2c2a056d24868ea5d7237ee05745737e3cc68dc55b98660e13ce56ac742c2a2924324737b3a20989a724b405c5ae7541b6bf6cf013112d777a6954c6655cd65ebe82273af3586e18f729c25a5c1cd1242d9ca20cb24e7e188942c09de4040fee6c3f11a1376df1f4b7215d50d82cad1848814beb98ed526ff3cf7308c50f8f127d5d4b7766ad343367289dd11bac0e6d21be81965d33b61f50ac7306726739dfba4dfd49667101eb972c599480994acd7774645c3d0ac3e13a3e9546979b10f7b431dbc72ee6613df72107550e2aaa95d4475c80f6c7e098473911c4a84b3d350a6f337fba334fbec23b0b94a05feaf8dea8ba4905e31d093fc54e8ff3a2a78764e8a066a811ef39268bbe4d22c36108674032476851ca67d111565e94bca450cde7c1cc4e7c7ff3e02f1d8c0e24e3c2869cbc2e39582add5feb9c910ddb129fd20b3a5c6d59e16eb1c5636303b3878fedddbf0bf40e9038bbb16877e742299053a4da48f32a4f6cf7295a6338901df87588d2759afa44792014c50c23cb547110defa8c8624e2f3d72eee74124fbad6e12e353cf29b8698aa6c74c8a4a6fecefdcb0574ff65dea884ff6103efc26a78a06e1c0179913d7be3dc79e96481afb20a7357a58aee66251722fc678a228205405d7f3dbfdc510ab44c29a8b390613ac35e3d866abfcd7827281f5b65321f7ff462d1a43c0f8fb9d6d750f14d6b3c28e0d9bdf0f2119679872701e0a00c4cc9b51539378802606fb7f5013ac950bf52d706f61fd6e2d8ea472549156794fa543f0a679c379fed40b186d95f3e983c3c4688e382db96ea23172e71aae01881d19a673074304826bbfeebadb2981b2597a4eb734473215f2a972be1a18b41d31c545234ac7620c93d31292463ba3f12b350aa76dc6bc0c165572ef43fea2caae277ddaa352cb27f8952f50e7edb7e68daa98987fd1a0e981726dd0d91042abfb29b1a477ec6137989b21a66aca6df4de19e90739490b19a0a77274285609a2d95864d971cacfc5cfbd3de2561e8dad28c5279421403edc19e35b636146129f0d8c71e4a2984d7e5c349aaf3338c886c856c0e19cf0f12ca0917281fac40b161f4ed65912e39f0db24383160a80e010644aaf7062a60c78f92972dcd68b0bc3bf9a759ef40d133fcc2720f3ed81531870c7217318352552a1d972a0e6a143656810d2042358e6f1da99dbeca32435b66d5672b81eb8ff3a268d7250d415645e7a08d58e4cf7c17690d766f4ed757649897e8c7220aed2b5d78d163c3b2a395f681a0006001c993a6ec27926fb78362348dc3b992bc0b11b6aeb721a7e222ea51a9a8100b3cac37f200d0b8d8e5f6b6bba51b1c8fd13d88fa27f726c5a9afaabbc48e94e286632a1c32b4665d88922706bea66c1ec90b6c69e104638842f75dd881f2d8417bddd96b48475089e992234d50ead3b5b499743f38a72ed0d2b51423258ce584e2bbc7c84b489763726f68024c1bcd15741f04cb53e1f86113a9221638b04acad8602abce32cb3ef902ba53a337bab06b07ddab7c31569b31c51bb4b5c66b9bc0135ea1c7a1bf6ad5f2ba0c4d3140c4c6f03f76af294903305328a8d582243693e606e2a082998c5fecad830f8687795ef890505338729ee3d3891b6fbe97a0ab5b16168b7320d4d4817dc4d89ab54e2d714146885a7257ec782add831b0e165475a777a0ebbe173b3434f30b5009c02eaca63008b9726bca78ad931ba9b837b9b96ee07dca73f592e3a669c2cd900cbcfb21d5a6b97205558bf053e90fd19d068d912bcbdbcd966818c9fc25f8eebec149a26c320e720befc33a110432630ed9d7ceb5b571af855184896373fb21076ecf6632df2f2750bae548d8613faa8c133186cc99f84fb19073ed1e591f8a8e1d8d2bf694ac72e85539dd643bbe183ef9cafde7f54af57a1d8365a1cd479a5ee881d716b98872c607f8395acd46c89bc7fe7ef6e8748ad4dd6e47a39a940deddc2d7efa3094729a3fdcd8d1f0982c81dd1d7cc192e959f6b8f66fdc69073971761efcf338f52596a1c8ed255901b63d7bcf292ea91f362e7a33a53a4869c92f90fbba4ee02e62cc3f27330a3fdae6839149b78afe8a2089adf6bac66b3c6a1a10c037310511726ca47a0bf505c0acd06950686d9153d0292aaaf10e317f3da4ca296e4e7bb272091642ad47e4b04ec75d13980ac0b72fccc28e870ae5b2f2a7963e65065c591b33708be1f877b6ed89305b750d7d2299b6f94e5908713cef5c33f00d7885db72417281d542c99f176cacbcf65f74fb32ceaf83ca0510195493b529970022de725142b13f834efa28e8e20d4d678025ec24d68776a6e1d91b5d45b529f86adf72ae633267f987f924ca90aac08e00d5db8a4aa7e980e048d4ccceb584fcff99720bdff5381d8963dc7e851170fae5771f7efac4400c2220c0ed81598fd4251c72602cccad5a28906c3c851dd55a4038a3757755c9f351123650559b87bdd33372ef18a3d379bde75ae9daf502d9ae9b310054ceda452bf9d7054c6a0b7a339c7047e4ca635c094e2b4b7fecb3f267f88056b592bd87f2f99b18a8da4e85b3d36ba9f8f71f744c33a70618b888e79c3f9801f4801586890cb5cecf0179a67ab472521f8baa56ec8a113b3b47816a27adda11d615a53d2e8a31ac596ca1c8452d110d1f711511a5da660aecb2dced5cbb5579ad9ba9372cb1afe8d54fd67a4a1772d2a0e8e73f5eede00363deb504b8ecf4c74f6e120914e65fec52ca400b2ee9053f6af650abe652d9ed5f6efc0111ddd710e0d88efd0553c8eaf64df29f328172c186bc45d048936f9ef86fa5b93c137b4ff448701fc7c6d4e34f9a96655bd869d0e01f741dc0ee2efbabf0dbc0d89f36c3c72e7944f67ba60836a05ac74bb3729813f9ad4a986d7a9f634420d30d2edd9b13ece0028b812ce6497ef3b185ab721781315c731d9e6924a32e8aafd58be5d1c6802fbcb78e94a9b21ecce0ab43729dd8b0f473550193cdbb68f5532ad74abb811ed2845b558016e391740880fd5064dc81003ab3ed81b084ce11320d11d0d799806d321f65dd857a6590e5286f2e0d240759834ee94ed9c3e248538846d56e7a5076745ef2df252f819eb2cf707263bbf648693027bc4cbebdb838bc096bd1ea6aa42d90809712da7deb4eb567724d9786193e739870c8e67ca48c94b96ed3859f97197bb1557ec26977f9a1e6723428c8a421ef8970800828f5aa1c8af8cfc6e2a49fb02116c8e0fecf27314b725658252c83fb43dcdfb860d32c47be45975da10996ee2e2ab4c1474c487bb372ea356956979b68222f4093f3ba6cbda64cb053701d30cf851fcd3080d989e710200f515d14bc1e1af22c612fc6253ac426f535f5188dd92f0dcb48d25e6fbf07c16c33f0d6c5977376aa0b93a37b85b9b8272d1d2d4adfd9ee38cc43f902f6721c71af00425a32828094c0695130e16489c06136de0179cd41172b6b3547cb6f9598c6d8b1c274fee41d28223f2c730c28614db9588340c99acce5a1db7fe401e7ee66c3f708817720332e4079884cf9fd18ad42c3ba716a3992747f6a7f0e72a3780322f225be57455bf24b3c97f0d79c743191634cd3309838d2bc5002f5721970fc26813993fb8f4aa86a0cf1f6223de602d1a6fd81bd193fc20019034272fad25d715594953c951424798a170d2fc70797c3e9b7aab83e8224c1d6ee7a726549304a0e633275fa64a02d0088e01c88107cbb1013701da86d6b47dce493371ccc983a68ead54ebc0a09a406e8caa4ba1e87f65c0173726ffa0be0d02b7b08721fbb7e09c98faa3d7a21e33b2fe4a358956f8d522365bea9813ef97fa01f7298696fd3e05926b53b04a2cf700544471a97051f2ffa1f12a21db81f12f2fc728f3de5b5bf57e8d6d7dd26f3af266952f0c73a1c52d773b532d5502c9c19b07296403791355fd0ea16f80edde35ec892684d014ae7a1e1eef99092bb52813c7288df0eb3f2df915d258d6b866b121545a9af8509f01726c743ad37448893a254894b51a64a95b786f5ab0192790db0043e3864f71220ce3e3238b9392e34ba1fae45f9311d4def15580c30b70e4ece9e635e72e5b0fa79e988eccdcfa055e072dfaccba9d4f1ed6d9b4d0a2cf7f9f35a47b3314bfcd0b28549ed13e5345e81578f5c45a146ff0877fe6166a9486a6d3c15d6300f5bf187b35ab353678c35bc72e66c99bab9177ec700ecb9b5067cad26365df010ace8a4679cb51334853095310b752136ba99b959d3f5373685c6e95e66c31a5b04597a9ad2d0bd5f717e4d162b5069f1f9bf2d06c6818517d58eb0ad60023c14753ebe7c8a4889bab859724a7b3f6128f4ebe40b59b89327d5e54d928770455d67c9477b205add7845c0ce72b0ef4440f7f75101120e275547202249fc0425b29a8c95dfabcdc3120520c13d39855853ad456c24edbe6b0ec7a482d195cfd8edb5440ee80013f3f5e3be2d725444c409d0009f8ccf2834a46a2bf2adc79b79857491a72e06fbc82730fb0f7276f81cee3ef1c1d33b473a63578739044ecfaaeb1db61406ebb3c363cc8c6a7243b7051e705db73f1aeb9414e283287449dee9f05adfda916725ec0f422133176e8eddcec0240766d75d2083b765fcdfa46b88d87b8daaca895e8646360d367299e7c2f91486fdcecf6b6ca83cb5cdf93e03f3c22583d81ed42b8da3016cb372ef8ccdb9eecf7f3d39474916953f37f41c066959f7300652c0dcad09d153864301622aa5207fa0cd6824bc5dc4637de692b54e2bdbd758dc80a51fa5af3f14057156bce51c4f71da1756589ed97baf86ca08f8897a2b5b2676b5fa36f75853088074738baca0f063cd2314ccae6eb3e16a7f7b0f3a11bbc8802a64b99138e21f4d2ecfd3e3cc76f227825e9d95a66263ba8373290cb457eebc45ece715e7a3720e1766c57edff6ae248f36102d78386daef55b6b2467bea8d7513307191b9f729c6dacb2379f00c155d131d42252076c0bdfd518c2f075c4a1157eb47f2a7672a90df5f263c04d36ef358ecc0be78fa3e8bc8b0edc88fecb5d7c2e1d377f947207133ac969fab2b1cbe4791e9a1c2a40e7e03b79f1b474e751ca55ae087b16728d894f03a5c374f1e027dd3f14cdf6b6838cb0e1fed36dbfb8f4a1967b12f3397ea0ed177f0ada502701edb39cad39058fecb239fdf3f6ea716dfeabb86c5e721a3a5a97c0feb37471c50e532fe7c0f5f86f355b5e41b788c4ea169b8520da638fee2f76ea4dde341c7dc6dbe31c22918a8bd7199b5616903ab0b0cb505ece7281bacc3c5de7c47d05431ea20e4445c05a4334960f3ff553832a854caadc4c12c406f3f95c8f185d7e66d2836a794b34f14f46de2d0330ce3167615aebc4bb725057f513ec8f9d0abb29ccae657175f00b244ec4ab96d9a10e66fac5a9f10c722eaccda2e1c33d3da37c213826cb089ef1290b53f6f675ec4e27167f9f260f6bafc324358713b39a0816530a73e5234b737fc6137d5d6766be08d9b8107911383b49cc25ed0e72cfb22ab7f7fa3e4f830f5bcc5cdb974d479c722c5519b546705ebf1196d393ada33e79d9e173f411dea4d6032fd715bd762be34c6bb9670208b7de1760949d77d5c52eb02b706ad4e3c3cbc7b7e4f2930f6e6ae5bdc281c94c1b6b5b3a4c1902d054762ac3f29becda892965a50d830f4289723143cc450e3b132f15ece629b2605955ebc4ef9370cecd42f89e686d0258ada2417879a0f262bf1ea5e940122be2e2b579f3c91d9711248b88dc7f3f1fd60059fb2106b8dc4d2f65758e6998bf47208040faeef712f9963d78eae25c39b1b0a9dde5a0cd422c9434818c9ab677bccd2d684a652cd6bfebe3c04491650a325fcf3c731f61a704d8dedfd2f3b49ca677056755953c13c0b04acb44981cf6fdd3c005dad546d406fb575bbf5d2e578746ea3c6774bc392653f97076fbe515263e7d9800d3840f2ecfc9d170e5040fcf4966add58a8a320414ddcc1590e8440508ffc880f1edef72fdca0406e909e4ca53a2a2fc4ad89f56a0688b19bdebc61419e8db1adf583e4a1a6460858c41eebc07d07591639c62f08dd899523b7b83febb0f525e34697e20acc6b064b542f12bcaf0545cae961286cc22dd31cce29db87b783d89e4377b729cb5e4a96ca3e56c106a51fa98a5bee8c2cc2e3c1a951ca9b825b9f24f0d1229afc06d75a30c7ae45a6e0a6fc2dc0ddf34bc2ae80a07c8cd3fc65ab8ac223172879a2902f4dbb73a16b17aaba2eccdd40e54f93a6c141c474c6a1ece3dc014729c6ce970048e364550b7753c13048f43e8add9cdc89f5ca0ac9b11f7804de872c008ea528b0b0c827dddd600581f6b5f30b4d41362eec1f025c14241074b5e3176c15a6f247414433c6ce63a17df5384418808189606130f046b868d47240f6ead431a6fe76deb3e5861a430396b5dbc1efca134646f5e91a752f72f61b1d86c67457711ab1852e038af07337ba3156c8b5eb70a44370767dc34b6b4530c582e1f1f3febb1d99230951b43f7ccae1917cafec9e2f3fddebb6c689fc53ffbc77288455af486dd07fb9970af1e16ef34a39b6a2ae2b16ba454c656558dee1f4872a2569b24e79a687a52c77a79c88bbc94a39013cbe281f07a6c3dd0b2fd4426728c26cba4f18c8294e8a742d99009013440669b9eb5de2b75d78eec4f782f20726d1e51a2f8e0a654b1f7c8de549caae07824845135b74af3e46a88cd322a080118a10c2a9a7fa50ba54c9c9e5d168dae794853b4d70a58428510c10dbf4b5f720ded8978cabd59d212db0de328a9f7c770321ef2a5e8a44200164ded72be69134e403b4264d9d135f4834995dc34515ad6ad5e5391fa80bd2b739956370d990cb7b83f720e7163b45afb896c35854bc665f076d9e544fd247a17a1408e441472e86d27d66ff1dc546a35073ebd8994a535ae3e67e8af9409136492537f8d2e36b8a05884ccfd7b84800028d215b2ab938a6d0f78ea681ff74e466f16468b2872c0aeea2e788d306026304d19a3db4f4fe5c743d950ea032199bdc3d9b1e7e3066ae6e7794c53e5be47a47d373b6f4449e59dae47689cd0888d8bb0e155cf6d72cfd5052207197fe56c9b7b8415cf6cc6395aba6c9278fd2a967706ad6e79b76c81d8ff65bfd2d21461ea63a3e71a788a833a93ec06e6a0618a41e1eb271e9a728c829a5e03376d0e391a218f6e65b79a1d31cfc21196d0f2900c9d90a5fcf5724141c7e70153be83c2d62159a4e189c8252c13fedc05b074d8f9791610bbf87269247bd248276abfb9c1f4e71a18f342c63afdf2b472a42e7afa71f93d7e3d7251451014a2c6ec1db24edca519cb8475d21f1c2ab7b253c8956641e5884e18604f0f0a650915681a94fe4da9cc89be1ff759de270a906d7ed5fc4618675cac3631b1a55b24de8dfea9f9e4fcedee83593e03d6342246f31fbdb9faf439dc807263e39b9091ecd6207b59dd3f2ac7463018de08e2ac8a0bbb43589b4e5508d031afefb52c62ce0e3e830d6b97335efa38242acd37a986f100220a192ed3073d7263b2aec62af584cce58d7393bfb8b8677a930c8f3aa3580670510b144ccace595a075f1abf30ddeb8dc6216eacc7aae08c69dccd84c099c1dd9f3d4b47ab4e725ef731793472adb1e42acff4c43cfd4487d3755a5d3a2ad0e0f87f6da430fc72fcc69b9c9a950e97f6606f05c206f225cd12cecab026e64d7284692f20f501721ed8b2cfd12d7149765d0116506ffaf2ce146a1c552f75956f54fb2acbd1967273620d75b6376043bfb66bdb05b05624dffc4faefaac0d87e79a3a6dd91ac30b01d340c5a0b495a49458f901375a83d4ae923594abc30e680f8f04249c3974729a2a6f2fce78c9e8ca1bf0b55dc87e609909f69cb5f227bca89816301576452e10d84ecfc8f851dfdf54d9f32ad47b5ba7dedb488c8cd8347064e84c864c057222a1c5de603498d8c3bcba977c976e1ecc3d6a673e7e31e008c3cd1ce9601772731f455ec053cdff2d5469f54e85d24e9886142933abfb08c851446c777ef8722c89a4ca02f8bc30a2abfa10f7e46e369136322287df15146fbebedc1cf80d3b1fa30fdabeb97ff1f3fd32036d34ed467bcc4c6ced250c9f28526474ec5f090fc11acd8e3f5cccbc9a9d566252b34a1e3b80876042f99c428449008bcd1b77675ca9a7944420e907aac22a57a5d0a07119295acd9869e30355498c4fc9834e72abe6ec3c9a9bcc30c518e63a0ebc78d82ae27a6403104f780250c0d21de1f8729df2361f01388919334b3e280776eeb5de54521e67537247a6fc9392a7f719722c0ad0809cebc9ca6cbe5fcf7fcaa322f832efb56ef39baee22671df5f2f9401f56641646585a9e68f77573d57e795a4c3c6a4b332085dec30959a3985c5fb72e1bd5559995fee568107e74ea12012bd9d14824ccf91e232d7ed325ac536d4725b155d075175a20de92baead0908d020af5563a06ed351ed8ee849da88a2ac0d804f1f98982d851616fa9e26e62af37a8ac12c70f9e493b4f506d1b59d239c72d3ca91cbdfb0780c9766b2890d108553f57d4d8256730c1e45fadbb8a387d91046cea343b6958492ab3db1b1ae89d93b90db48bfe79515fc04b70904d387cf699305d1249450501ddd0593146b8279ce61ccd44a5a43dc7184c7bdb05ae92572b4d8dcbb7a001e42fc46d8904415a79a962745535c6b585f9f1dd24bfdba8a723a9c1cf99c82f16fab7053d50f79c0fbf11d343bd264b98edd47592bb8cb1272c5e654999d84603cca55c1f496d68d878ff4b70372207fc6c1df9a25fcda37722864f7f32eabbb3ead9258becfb7ac652bf598ee5528b94859db340d8e49d422faf818f2023ae8336b104c9679a6f9a931ac1061aa86a8199e09b376aba6e272551d072630ab89e55e4b11d3bfd5b2bbf11bc24e8a2e2a031ba6089a64f112727a76d62114dc6b80ea7cd8053ef27c0e543dcfa14b815dfe11ec5de25999bc7243e263eb1bc58eb9afdf3a4497438bc0d7996f9981ac772943a8182f14cc573e855d141c3a760b59eb18a1c4685610bf0b413ab606a7b795f981869a95f0f5723dc18f882fe6a9b9315dcccc49e9fe7adb31043790ba07debd758f90362a46729d7582b5ce0e0558e6e55f8c601b55290fad91baa34ded27c872da69e86910207332a4e2eb0f324acc70589ca7eec1fc1f9d74b8bcaaa587be9eaaca4277042c8c98bbdc034300c979fd9950acc33deb38bd49dc70fb8c40480037b4f0da854ad09a856210f54ad9bfb4dfba585511385554e1c11f4eed5eb499e4e4c8201a72b85556900a2fd1a825443b778096ecd8556e25e2ea59f62f7bfbf9130c407f720facee058df1413ff52e7a878f5f07b477f5ae2806147f0f6de51921d5aa9f02f3d2ea34c7de1c3a882e93e3a25560be18ccc7223aa9d6a6cc3099184f6349513887bfbe1a569a790986a7a8a232661c95c4be2f2cf0a9106f90befd9af253588c1f02caee31ff5b0f03f4e3a846a2c2044d5c1d6c0a4434335bba86d7106d3a75289b7418ac52f2760e8652158c8ab91c0266c16a31dde3bad582c6679fe37288c576cdda62c75e3617284c71858fc3af4b9bc94fc0e30fcef2f6d54e7f9e723bcb039a5655feda824338c498b1d2425e0aa57acd5e3ffb562b4169d00a6b32ca1a7d1ee34f753495fbfb13a64ad7004a79b2e8ce3df976bfcd69ac37c337720a708d4b6cfb8a818b13c3f7c48807a9f2cfcfc1029dbc7ad50d21a778342872494fa4b5da90e7cbbc46c387575b4c84cc145a49e2cea68380bd49cad05ba972a471caab3be0c157db45d0cf661ec01f78b70f23a7900fe63f6af34c240a9f339dce2162ec177920324660bab4ee6a5e02ccdf3f44d7feb3300a07e843865e7266b9ebf6d0f417aa6d23ffe4f348f2a681a484cdb00827e4e4a2e66439feb1726361722ce2f3fa9303d384bbcda7cfd70db4150b599b6d21accd71453894ee724c2c441a15065993c9664e002957a1b30dc27e3a7b95b371f7ef73936506652410890b3aa7c811893a96c76578f63e8efe24efe50c7e6abd78e3a0b91e498f7256ac6ac5409cb352f3804c72df8d9c2aaad19093bf58a3e7965de2e352052b72eb9321f92df0c184f47e40267d507a59e0386ea39d618d2f0d0ea924b096207261d0a0a360f5e6d34122c214382d9f84501bd3147a4d278481bf5383d3c5e80d3da6f59e2e96b8d2627a23d5832816d319dc0136c3e9f342cf24a06f146713214210a7f33b94ff7c34351bc7e5467e6c0fa1f2c3040acc0f8167e549ab3e0a723c3a9c4c2541ebd7980aeb0ae361439c90091a6a2793cc46a894a32e4d9cbf72f79ec9201962060f1af13d41a24429e105f9b157547a50b06937aae4883736725461e4e5a5298a06ed695c9995fd4a4d9a530e0c8c2d0516563ca4405e8af44f9c0f06e5b50bbe04b81fff85b462fd20928c766f1426e6629bab9d2b01bc006bd9f5e7a8bbfe88cf6b7b1afe85857106aaf4eae8f7cd83d6ebdb102b71f11256ff38a3059b54551ba806980c4591872cb2d6143c3f3363521a4376916855f64782661ec91481a5e03b2177fc421e0e6034a87628c0282226ca30c08baefb0b0ba52aa431828a647d1ff00a62b5ac8d31e2decc12f69ca5fa7343558120953e72ffa7dba99cfa35936a9ec6853e9e2e82a3d7d5c5f7cd98b6c4eeb0bd993227728d094c4c734ff95670414c9813e474323ee49596e8cf6ca9f0a05622d0675f72ec9d0401cfc48445e9d3dee24e383c50796d159a9a9e4cee326e608589224c05439b151c6e86c7fa50e2b27b438f71a63ae4688578a7b3b30c8a9a7554a1917290f62500e152744aac22e09b9f769609e140026231df86a2557f3e503f3ed33e986e648338b1e1410306c93f33b2c7080f13def15477e45b14b32068ac6fb372ecbd902e2911587cb636dc9fc02185a6245f50930d5f883c33140a93981e8c72951995634e6d9aa0d75f115b78fef8c3abde8fc1b20cc8e7ee73a92b1949aa724633fb8487205aa0df5a50b781d27df19456eb182e9a4e97bec2c61f54ef5872ca39123229d1cf12204ba42bc9cb9e8359da1cb07ef195765cc0ad32563b0034b73a0e6ecc8931893b29cf51ce565426a16d7a16318acaa5c512bd0dfd405f7270a7debe6d106ed5fd11d47c9be7777ec5da091ab1d497889a384eafe7de3a72e2777803abab12ef0af1b7197b15376710a6298a5c6b7cf78278aa1ffe544e722c9da9a119821c755c3156e4f73d7b1aed35ef52094568c6a70e64794a26467252c703ddfea22ab1e0af745783c23f6dd82832e40deb9eebc707d0f8bb75d272f50c96b24533825773751be678d102d6eee0e6e18ea70d4281e73998d94c14654748069dcb012ee147521236150f330d0554fe0a51e036eabb3935c5a67f365387227f598916f809e3d967dabbfda419c8d1ed7ad408a03e33e4867d4862cf0db88cfdb8a52da63c550705fa43a92a7dff7ada9b0c107ff3c72046e72f04af7221d263471e83d0faf4353b067dab40541a560dc599ebebe459dcebdc15cad1725d63946348c05c317d6a30494bf820636132552ee9c25a9caedcbd853070397291fd3318b22ade3813f0f3c3bddfce0699cd6c0a07becb8b43ccefddb47f6366727e36bb7078cdaca5b0478ee2c2d37784c376879c01f708bc072b07b5d56372dd771d3960c8f97bbafcbba2c2fe93a9f737ec80228987adde1fc6167f2c1b7250f8e9e2dc470e586224067cf0f0daab446b89e3429f30564ccb15f86c28b472ee521bbf9ad587b00917da5edb6dc110fd1ba99ee57e205a6a055c0c6478a072ea6a3bac3c0f38cb98805270866935d5df8cb7ef1fa2323c18e6eb19336b3f72d586d77cde9934fc926a399f26b875fd63f6fad528e81f6912c66ab31a8ff072aa08ee9cb529bbb477a5af628a32a58ff3bc612aa6175bb2b167721a759eda725a03274e6c21ca53a434e33f19b1c69f276cb990069db92d76a229f2d7d5b24e34071e2bb4e9fcb5ffc88b65e9ec3152fd40880795bd4ef9f8e3ac477b257d724b7bde0852daf9ffd0af3e8bdb5e1c37b5796f2e279453fd5ad321a81996d572b2f28e7b1cec5c28806343ca85f08cedb20b42c27a53c9e18e69846756fb8c72f955ac66e9726bd693fc7598b6b4a10b05a9e097e1d132fe79775cede4f1c6720125c246603c4eec1361a85d77f96a0218a4283d76f1353d0869aae8a6c20272832cd2bf8d2bd6c5bfd0256fd5ae03ab7a6ee1b340fee1fafcbb4c4ffb048236664b2c877b3b62c922739dff2de96350b8094f98037412afda4794ad96a6d47260b569da48cc1178b400ef10d538ed8c009cb7f431f84b245643a8ef291ee4723eadd419367e943f2a66074a0328fb317a63fbda26619483ac12d74585b8e372971a01ed0841635022cbabe5e21e7c0cf49e1c59147ee18564d72c432db78f4202d32c856fe0abf3966d97c93d3c643817798e7711bf6bbc43d653bc09942a209701df4cd0737eaf4fede3e4ab7f93e7e26e5df2fd0253710712c5f434d31462758fddd03b63924ce787d8d84d4a6e7bda283a707c8c4db9ca9db6228d44a672d4cac7772ccc7e9ffa0bc18edc5d443c849100096b924ac0cfcf22ab42ce3e3c65dce9d80a1fc83ee057bda1acf9409ed697dfecc989e5e11d5ddd9e6359ed72367fddff6affd6426d2743f43a4e70ddd0d5482626e7aa524a96b3e8e39dfb6dd727f6410f72f87a75ba3a11234e3b531b72c872de74ad1124031a23191bf07262dec8dd5294aa634e30909e64819cb0b3eb0eb8c29f691e6deca0643bf0a126cf0dfe0724b9c8875953f6deb65ea1dded752e10e2808a1e6a8a6b0c9d395a72bb92e2ff7cbbe300383e0bd77bfd52003763715461d9de63a645c5747315a65d17c9e877443e96cb4d07917b89767d901cfa8d540757652607d8e1bfe8ac6c663df6c7efe4bdaa7aa10f7530b619780c8891f983af63c1927358b00f00ac2a72ee5c9e04f996b00d983aadb64a351861eb98b10b74c362199cb1129acbab7972b8e3e8b7fb7690369401e1287aeadf0efdc26c3b50964d3de0186d086237bd72e873d07830827828b5b494d9ec42e24c531d7c2b8d36650d0e2774c876114f4444739eebbc3c3c7d23714bcdc564c80591b0d27821a4991d7ddd0ddbc7353d720465c39bf6d0f824fee020287ccc2c3f12be6fb9bcbf126ab49661e0dfa69f4837f9b5ce78d59eb117dc07f339cc102c049ad3af59387dc78515e4978da035728627d66fdc6848efec25a13080ec9fd604f7f91959cce2ae95d2b9f04f565c39461c320619559fefc6e148853ef50b145f003244456109cf181f25ec75d4393ce0b10acd8e9c9f09ef2ef35022c93fd09da2f58bda8f7b42807ec5a12a26ad216ea131838c24c25d8a6de6e2f9d301b9db1f8ad5f955c9387bd05cc97348ce728127fc330014b7dd33a38c56c51be95c7946dbf2594ad393aeedc6a61eac20400e1b6c28379ced45b75c55941cc210f9e661be37be8781392225e972c66d1c72dbc479892219aabfe2e5ee191bf0da5a6e223f2709235c9efca23afadb43b47294c561aae660c94d1ee5037d403a423cc822c0d18c8d4cea06e05064c63ae65a03711d12788fc95df8cf144b4a6df92a34199ac45fa336378fe1dcef32c9b372321c1530786655b85a6c392e850bd712070082de3d96ff4f50a6a3d3d313dd4579f4ee36ebada7769bd32d4fea4c45f38a2713c41ad652abf6b1631bd55e173f1cddc96b48e1f9d56b58350181ddd0e1a114fcb9c232338d0392e9f71019d9724be8d3b4db23e62c52a8d80cc927972caedb2cc9f241fe3f879967a9a5734d5d77accf6c8c9315776bee46a45a97b8ee95a03e5a24d6aa45ad07cc664f1ef76b9dbd998c1a66d9888c81a9f8e4cdcc1ed2416ae44eb9b1cee77cac5008f33672611f2f8ca878708741b5048afa591dbc2c616fbd46daa0ed24b8971cb2b96c72a1f5584258d9076eb7ae5822a4fdadbbecb183fbec11ffe267ffecb84ba5da7269c00159d0fb0965036966e2dbd1b7ff666658301e06c8268182402dd17e43309b1fe1095070ec35403061fcda131bf3adb88797ca744b5aaa79dfaa0200164bdffbd2fa4d6775815bd3dc677dcb98565614ca2346f562354be2fb46017c2c56c9c5e9dc8991db2e4e245dd69d26d4790bcc220f106360b2c9749d4aed46a172425cedf1d0b21114d2954f3fa24ebef6188c644457a90eb2bf5bae3f4296e04f2c8a5d1af6aa6f4a8d08d846adf73adc9169208f40dd36179d31bc9600eea518b6e0d23a8834ac7bcb0e46999b265694fd890357d919a4f1552fa8fd775426721d199913566c7fc5f3d54aa0edb0ba3438b925e9ff173037ec8109eb959aef2108d11f40c042c92db0952c5099222106cf74530576aa5ecaca0fccae287e43727586a2044482c5e6c60b4ab61641b5b2a2213227080285126f1c43dc21858272658c819010ebc2b56d2daf3f4c2ab0f7d1f514a7c98ba7aaf077110e96fe4b4a609c51e9eff8ff3cce75a6b4b7de27d5febe4e0f8984530fa7da4c1c4a5a542bd623842e91dac918228591c1b6b11d86fd955ddcc48717d1e22c09f4bdd98f72740102787815ac7811ddfa22a03522cbb625bba58248411fbe93b62ae70e5772a5d6cba94dbd3e826e384d4bf25f91af95e9c4788e976cef19015435b282f4729b9a990ed2ba3ac602341e955b86ab6a0f927799b0854443c9a82d6ba5b5797288518baf7290eaf5328ec72e069f3a4b57250b7e0003e52542940165cdd78472ad23f1c5b66b776e7f722f31e5a1c7d9f98376f3b3260e1ba7914bc948e46b729173b3894dfd18dc6662e6698b550516940f3353562678d2791554b36a3c1e4d9c64adfc93d526a896b59f1835444671916b786d2ff6f166c67eec2d2b7f81727a68280fee32bb85d2c5d07eb0f176857617510c857f86baf671a04eff2376728a79257c9a4ed8fed55e62dc422a933ce6635fc400d5ba86005829615fddf072cf5ec4dea172d3e4830c3676eaa206e438755c53c3ec08533c970d259bb17c3d361859e748110f7dba838ffb3bef52d295ec000e70b945ac657ac94c832fa33d278200f3350fb471606d1b586a4c8f8d7f9ce1bb4dfc79f9ee3b8adefddf5e72d1e5732e6cde3439d97f6f44cd63171a68bac317319b73ce8a8960e7363c2c44e6eb25f515b684ddc687d04647953bdd470e9dbb8ec55ecfd163df0d97254d4b802ad16c2a0943ad2a4078a5527e79921b65c5e10740523a3ac4d069649add5fb83c5e65964bae5880f9b9a8c206cbbaeae5d6ad11d9e26be596d64803975c1b6e7d7b5af73c1643ea2c6a4af779631ec3a1aecd5be59e03b4c65a01b53b7855c2c457ab00a6f65a0cb5d919f27090adcf7f9d903970f5ea93253a987d45d53ab38dc2355cfeb4371a7308d0261cb4329e3f8d9ece72ad9e90989f78c4fbfb72b134d1a3834fd9730b455b64459bbc3b170e77e040792e748fc40d0e55b5f304a49b9d28f22f494bcd250a76ce293d43a1d64cfd93f8ea86b46ac8521be46c72cc9f076a20d6e4e576e5fb918cbdfe488a3889c65e7509f6a13a042b658ff472e9fc869662c91e272c203ce58e97c77b9684b5bcf983da92b968b82af052157274313239a96e4e5746daf362cface56262f5a00db31dbcaca88e9c033c4b073c7f2a2a1883988d13102f5032a0cfae111ec5f8a327b265e1395c267efe4a1e72ac0ca6a6aa5f0119fb2619eccff47c5c08770b8a17f80861db81ce9830fadd0de4694d025b3d32b199022c7609f37d3407c89dc94821138b2cfdc1ff87317372cc92a161c7dbf16b030c7254bc98a8a266d7390677a5135b3039c9361f47be08a26988dbcd65986062de7b152c13d0a84bfe3cfa727582beff7e668817ad7d721f35b17bf46fcf815949e2aa0d86fcc741c4a1aabf010beb5483bccda604dd7237154e41de5e0a2dea5f6746f592da3848a19852511189220084fdd97ec7137277d0aeb101ef4c429b6b87b579fb9d08d26df1d1f14f1a5915376c6202ce0865373e80a6dd29b248dcb7d981e70992e0dfa6e9644b4f25a0e09cdaf07ebbf1517b2252ae61d832b9948411df2d72626a413244abfcf5cdcd9d773ab17b314a72019586e8e0aaf62d0f4670eed1e33909b6259240c1644eab3c8d167269de904e9dbd3a0cd6d35c6a7a8c8e9cb5941aa6fff442a5d154a93b2bfcf7f83166d46945931bc2ad489bf1c6a6d4448ba3d2377451a1f114542392279246c52391141c01f268d1e05012b5d70fc4e49da3a43bef311670f082072597d27b40f3f62b2ed264692088ac7c6eff98f429388a3a484219bc6796d0c9f62ab772ab366e341ad7e5d5da87341498a2175137c1bbc99eb6f4943e3d9d2e51ae3da39d5f8e1e1f32da914ba84e871efde1c1653e04d623000a1a5a9dd931dfc30d95e8fb40f57276cbbb6402c627ec73e8e273713373a5d28d1c315940d6137b4c6697ee924e72686d929e41a21a696c2eb5d9fb58a4d49d3da3f9e73465262d46c7b1388a5f6005ea88a82ace3287b5df5558c87d0d03675dd0797cf886e539e0e428c22fdc72ebe934adcfed931ad8f88fcae4c43f9d5180904b499c3e93846fd60c3d0aab721c619e19e4acdd2bdd83a41d4167c6e7d5d1db49201a6bda63245afd353870387a882a14372e7e135844effeb677c95b82c45c45ced1c9506103e22d6f6cd37270ed1303c05702c6fc80f454e05d74027dd3f124cc1dbec557f4c322ba9d1772bcd43b244789ba6471404378760bad15d9c6d20e3c5a0a44bf82001bde6cab72d0f4b3bb46f14bd17c6be595ed8505bb4e91d7437efb916556d621e5eb59ea6dacb0cfe31bbea9f96873f4185ed15c2b0661a802d8f56e7fa01866563b3ead722ed28de63212b8b4a2f5dbe5d797357a9f987928497d439a86e942329de476726fed3b92c61a9a636ec8af7f69ff57016d13fa8d12bdfc2406e46922a6693468a3ca0df35751de61d423f50eeb7269e63da02110b283d138f831591fa4bfc37292744a314e47e04417522675289b8cb44b6967c0fcdf61c7ee8217ee8c97b372c5f20324c7eb33f8a89995422cbf7e23ee329bdf58e92e917fb1d9e1e37379722f59c6b177dfb71a8722c26fdf7252fee5bd5988a9f7cd3de0dcc41b916aed2297db9b3ce3ecda48877b8200662a8961d5d8ef11b341409e2c304e03d6547d1041e259ee8c544115c6dc9c0b8df53c92c40771a44e5f66e8f3af1ed10373ab72478a942b9447357d4fcbee9a8d13a2db6e647e4298a6abd0ba660e87a4265e722e32c77c9531f794f7fbf720d345a2222654e362b53a384e690d232cdc7d3072613d77d791555cb675148ff90704f45b39ecd4a5c3966cd61856140abba0b461dffee5f9461e448162d6b402a268362a26e923f827bd85f5db43018012794b4d11878de6d78ef085b1698d7a406398ad5df23c755426cd33c7068b33f8338e153fa25b0291383bcfb5eff930722ff1d3dd83702e9f5639a42bad8146472ce23d863a9d420fb6b146247221377cd357295e9c285441388590bdb213a0cc9ae972a2fe2c85b33448b980f0f29c1c0a8ecb487d9b3fa37795bef5db275b84cb7c72b2fa9b75eb8a819c8e251245bf0e7d4035fad6a97089109a8e88bf47bd1672729d54c5a20b4273c01c12b5854f7216b87cb92826614a1b6f3a6053afdd57863e28551615a578d3b0daab48be1d49bac7d7e578c57100ff9b4ca306096833b572a064beea6afa9d5577b1b78da4249d38055ba089b54604ef588f55e68e552e721310e54e103369efbd021e5109c860ed40df47afc9d7bb2dbb642e782e2c2337463cb27b0df4ad6105db16a2cad4026b927dca2a28d46c93ac474b507b7a0372527bed7e2aeb8b7c9573ee327758bcb1c3ebf8dd2a36c362d0f50e2d663f0872b729349b35df5d8c00bf2799a1238279df17d0b0729f0d91f0e4dffd26d08c728f4e80aa05da430ce587a1a81eabfe583f91af6b69da27e2cbbf3392cded5a5401c4c606743463ca0f93b6de177d1ce119efc6f0b0d12f0bd290e2b694b01b724668ec39127c6a13d5ffff30ead7f1e98c72ad58b346908176d3683ab37c39724356269b3e87cac1740fe8485ed1d643b2f0ed41c81085ec763d23529b2029722776320d4ea79400215cc70cf009fd8eeb90ef62ec69f2793ae93b1ec8e81e4329f8f183458ae66cde4900f21228f3b8d8a04fe73f8e9405f692edcca5544d721c460e3674facd18a72ee944e57196417a9bf193cb28f4bc3a80e7107776a60cfb024ccc77a7647bb4a1dc86185190aa1f572e8955301e3006a447c50d66be50cdb9d50eb50f0f718c95723a19a8a2fe689a569eebffa8467ce351eb211dee7250b820a0e323beb69c0679c26a4a4b73ddabb7a33b1ba84b9314ec5f8b4e66729e6f279a86d54fa271357a6c9dfb7c40e2c74a8752d5a715a82a8a1b6662d4728a6007f6854fd93ae866c061f182c93aa419e1d59415eef140f680a318a56a692e37b57cc74aa0e4c5f236e9345eb0d42513fee20136ab1162a55d2dbfcf1472a9a59ccd4dbfeae1c9c6265d8ec16077df17feb8970d733a91e9295516c35f727e41cc192eddf835fb9c4d440a7c86b17e9c5d8b94e2c6d293b10d235d26212e54441f6e3fa71409c8c682d30041750ab45fc6a6b88be88e0f85a47f77b6837210984fb8bfb98a40516b0ac119c9168935ae0b446176b9a982c44bab5c899a72a2935ac271e7b320da3aec9b001484f42cecfbae1597ee9c9716ff36071dde72cfe47ef088bc7764e8a7c0495ab8e4afbbec0c76dda5fd9b3b154e838d61f472fb2765156c1716afb9378a52a2c182e96584c40923db5dbbe4bf787fd399d17260e918c69f3cac09ac43e7e303d0e4dbc9f39556a16d656efbca20eb7e456f5d13b280b45c00b9ecd788c6c07ccf32a1473615b7944ebe9a7e52bce94d740e729a001155e58bc9dff9d64b14335aac57d1c22e54e48356659995c3ab300de509e699b80e5d92a9f689ce746ffefdc7fdb957accaea7369976c4a7cdb2287ce35d2af0f32f48192f86e042bfefd2a8ed70ae6f4adf79181cf34d33fc3c6a71a4789b0922ae437b3e54940a718363687e6e90e160c87f16b82fb24797a26f7f572f56e7b221a669fd0ad228384c8c2d86d247c29b55f0c2fdfffb3dc77a79f1372aebda18b723d4529d9abfbd3d9893bd1e65f43a1aebe749e8e35fdc0847730723ce63d70c85c9e65523514c352d3b0aef45168780076e6322c5ae8293eb2002106b548b2f278d7a1d1cb3843ca90be9ade78238d8a7df9f67bffb2384a656672e7cae405a3cb43c5482923cb1a8cc1c5d50f0c4f6b3f09f636601496b2007e726261ea92db11e899719e21c446ca5bba4667c7c2e90a78a1f76157e60c5ca9723e9a508761c74680eb4c6338926fba1ae9bf99aa091a43986fb161cd399634726540d773749c1c345fedcaacf3f57e58f1e3761256ec550441d1e88bffa4e94dd6a15e4722c29da98c0c116cd1c542e5ef310ade345fb1084f4868a74e2bf14faf035de1d54e769493e66b2f5b06bf3d58b88d32811eb2fa23c426aba78b2c47e5f56a2fafd00b1c69b26d5199cc65abe5aa0c4292ab3730dcb8a6341536a91f8ee1b6edb8ba4d70cee48908b68e7b17999f54c9d83cfdccaf50bd9b9f13b6720fa1c288484e4707538099b3110704ce45b7abe83d6fe65524f4aa20d69099720b92e6cdd6418d58b1db6559b6501d96b22ef52b2f42c794aca87ea1564d1372a5a3688cfe9b30e15de88946c70c286b065b9be4a25c5979f4de5ec994ef707205eb07ced8e833ebc74dea938d7ccbd351e1906f21dfc4d47cf90a6ca5275c1bd7a987bcedeecae513da20fbb9302160dd27b5f32ec0feac0d258a3887123072fe94111eec343c7d7bb19975a1282793e7eac7e4cf4940cb5277b743b3850272a0b61063c061c4db24bd6beda851adcea4e6f546b17509bda2facd0c4f078e72c3c9d8d1cdae019ebb586375d000580927a84a602300c90f7ae89da732320b316e51c5806ff97d731e36d068e91872b91e9d02526b1c6600b553cfc7e7d41f03bda0f5ef8d24156f9e6e601798c4483cba480518211882e54c4ed3cb87102e13e10f90e14653798371f6a86fbb005b869b704ad49092a3f852ac1e201d930a72309afb397ae99512ce17d10b3bac74685c27f801c511cb8ad435f162cef6953a7866fbb19b54329a5b4588b7caf51ae72494fbc03e1f152a1fbe422a8160267249b7d4e563efc1397d21edc4616a423749ea00f04f1241ec5f407d534ab1646e731e02fbd0bcf0acb9cbe53712908a859e4fb88947d267a452ef26d7530173216251ce5a39d11182353eec06701ca39be09e3f97a66560e9544889cc4a0dbb720389cf5e2bf0ee7c2751472328ccfeb3188c94be3b3b5cf3761d5eb4f4064572199ac1569450bfe21dd69fd5c3ab911f170d306b6c76f34ba159c19a924ad57245cd65f6630c308d80cfde87d7ff72c2da5c261053d600de4bb3376916515672dad20db461ea06938416265c598c9d67449f472debc88f1678578c5fef34aa459ae4b8665700759bffde62f64e35d1e1419299882bcf0eaf943b27f415feed5da615a0b9ec791b19fce765dd70ea3de8b6aad48d26975384cc3958c20c0282348d09268672055611d0084a032d45d7219a332dc04bb17639700ec16516062e728ea47b2af687ba986bbc4c468893666773c7b34b3a67f451fcc0cc72863ce323a66af3b42813904ddc7b1ca076590eecd4400a5bbdc4ecb27e7bfcb635e54e726287a41c3fcd8638b773c9c7c4ef14dd7a40532e97aa1a90184903a742cbc57256e7976fe0115102ec01021f6fa1ec0e6ed309d4add65dbdf04e17f30f781e72339f1f5d4ac09c466eb68fa10c94d88b55e7d17eaddcb07ffbbe8b3f71cea372364383cca2ab1deaef302d132ac676b305e388d93dbfcabb926a6ca4c23a9f5e7b206fd7f9de65be6cb3aa993f4794320d0907a066bbcedc82e78c88a388d46e13d9f19fee9ca44b72ac37cd7dee1f61dfaabd1c74431eab4b4c0676d3f57007bb284a3bcb044a97df14d346663a25d240f05f30a4d400cc73f7d37dce9973522122107565caa7dbb237cfe55b2dcc0e88a92f7709a5f9b8c3a23400de99cb720f8b97177a5eeab87ac9e4eaab61d2e50b76fd7b6984771d3494db75c87a8772a85b83fb3f46f3f7153cc70a5478997fc4044f9ddf2c19b6242df9943b551b3f01474200899748d30fd73b80b04de7de7bd9557178794c266b228beb85aeb24ff39813ac5742d5ccfc7bd63241e4b6d1001aee8c656fd806342d9863592992724fff7adf33d51d58a15f4fcad412ada19a3b9c70c538f64e1002e449f1fc1372b585f6087db37d3e30afc7045cf042e7da4b3c981328ef925ac005003b2df8722e8b4fe2a64cff566758b33f9d431be21fa7f1b23612ecacf708941544e4c01ec3cfe221388495cfbc18e324e71025f9ff7952aedfc4af376b1888ecb6fcd872743651be00d5666d834efc6d94691560efd40338cbc9fdbddcbaea8a1bda1671fcf89d779027757021cac9a6dc8d41afb5ff9daa7bfd3904e7dfd8ce0c62a7080e5ad1acf4c918f811cec260911bf09dc36cb70058f5e903bbd2934f96c2c14f86de717f1e1552499efdb9d5559174c54662cf3f834ac11d4884583f4b1f3072b636111eac5e94610765db1bad7c97d30583bf23c6abe940343a4311b8c1b20e9c68e1245f26110493fedb0a96961d3227e67d8d0b76d37c436d66823ae4414660544638ee33744f2feae96237279bbbc5a084dba3ff0a15705f8bd66cf272726929bf6cec4cc29042114fe19c38db4757acb7d98530a4e1a561470809df877294be93f96905d7596e98a734a1621a797147b0a301f213aae1aa3441490db072207a8905765846dc6da14f2d87e9309ba04da193a157fb45a30d6f13dce1e2450536dc05c4e05829208d7b0fc5779591ad612789bd40d4a43d3a603b1445ba72e7679ce589de22f1deba2354507b2545a4f0502f33efab6e87b552528c0a0072da355902b2d2f1b0511cf9a200931c25bb82aba149ddd7cf19a9b3ed3b54ef5bf0084c6d3a6773dd49e5475f34090097058f49a9a29e9c29083b6fa274b494705c835ac7197821d354bc1ee64de072f776de71a40ba56d7349033d8fba94037246787908c08869d857a365fe707ebf6e235b4a3bf146fa5aab6d333a3a62db72b0fe9a5f71f1f3fdc83afb5b6ab5a0f58066567b3cc035d54ac6ece1f142e872c4c94e2501b107d3847fdd57b29bdd42727edad79f4f848a94b5ef14c29df772e6788767d6b6ab0b0afd9ba5e015de0b073ef33a38879e77fbeace6128d5d7723faa8f749e14173cd652d813d463b1739e2bbc812c19ef72c046bf2ff83cab72ac2689b298085949ba89a924fc63f97a85a6fc2cfe3b098a901b5ab1bd190472ac4db3d309bfdd8ca558042ec2aaf55a6b401cc01a3e3e041955953d6196f352d546cf2cb75496528c0a6449490fc68de64219f7ce92d3bfc1ac50cdcda8bf727c374b81966f5dd3a45dc76446fb4110ef6a385f1f3c16afac13913f8b23c27251a95317c191790eeaaa1bb117e537efbd694ccec57372b5e3b73dcc5b1fcb727a256c40f7c587dd601f1a18898b320faf031c75266948b054e336bdca4bbe0c2f1e37ef84bf26d61ae9d0d8ab3afa7a5bf83ab423592655fa56884e59036b726c75b7a7e72f6a8c05ef2be874c5cf1ce8cb15349a369896953f2cf4732bc062eef5db868b3e6b578ff278b4e97e61a0b0f9469589b0627ceb995e05dfc9aa72b136c6c0569857bad956d841f83741a289d9aa039dfc0b630e637ca86fec537210731567a3aafa28e6db4d7dd6b8be6120611603e0ece3c0232ca9af7c38744cbf4cf954e0e794f3babce9dc429c0f1ce415798b7b27e872f0d493ecdf370d72f71cf5431112eea3b297d35c2e421f29186e4213ae230b577100b7ed041ded640d8f15c85b934400ed00af835de7abbf3a675eb4355361a9b07c3f353c0f83721390f5d72e0a3813af7fb7519fde8f5441346bcb98425d234bc78968dd31f67205e065e35531d4ae7012c265bb4f049fe7b828e2b9124c8ee2e34cfc5bd0dc4a14bfd59b806e7eb31d20ed902620fad4c7cbec80e753c930c9d6268952bff717f062c86cf9db3f9cfd322499a462f35b405d0317a0ae07836cdcd7c344f2b37244761f3079a6271536e9eaead4baaf23c302f61310d7fa1234830d17f94c0f72ba54ba2bda9ca9e82f56be9f23d6023938fdbd419fcd0f1bb839ff8c6bac0622246a07c1f9cd6739a491a549450b0ec4b049773b2467aee781076a084d2d4872010ceae24c0a037019607bfebacc719e57cbdcea0e420a14990a1c5a7c153e72d0ca89bbf79d2d3c4eda0777081f1721ff5be2a35b552f931dca223411b0c772306d89433dd4657591fd1f81856b889ce47d1fd74f8c2964b6b975cbef3849720c6fbb8c759f356e511bc97ec06d827c9fb6962e806667ec0192753ab146230507ccc2167a0d54c274ea7ad8215b8637ff390db3b0b38fa1a5ac886d1a8d4e7279ba7dfa528098a09b68123fcc4802e554d1471481d0f7354fe3459da40be872e0e9d6d4c8ac04a2b2054fa134b507e64f2be309455e43e4aeab3d5ac97c66727dea8d77c3dc3e784ab0593c3be2739d6a226d2880c994d3fdd7db8aa2937672cf2506a8d7192a4ac9afb8964620fcb8bdf1e1814cc110e2600aa7d9e864b34f9ac8a53755b3a1900ad20d8e26b701d8a76f3fc8360c03fb1180ecf2acb50f2319346b01504c808e6322d9230a615b4a27bd0af59bcae1db749c7c59504c32259e7b0eade4be4107fd25df5c6b47a474682c0704ac186f359a8f5778d37ca6721c7e8dbf3552ce92d64706b8ba1e02ec7c77bbf62475d101ea1e838933215838acd45da1a5c23f79865b0efc1d36d691b2ae56dd31a8a51d07855d33972248728f0bc0ddd8a784e73a934a8c51f1f6285fabe8451fd0998468cfb6f5d71b531aa6ac36547353f8ca78de398a01abe7ed45daa5f049600e93d0dbb898af7c5d160caf70eb54fffc1f7b061a86c5155e0703801c19bb19583113a9c73dc0777b72f4e9448c545d28e21ceff835d4b68e156ac49c89b420fd010ce0682d144e0372a1a1d17d19c1b0f22925098c76ee99db44b0b05516d1e8db8b47e85b22902c1a30b16998cb273fda020235d3465214408fd06a5557e10488a7a330e099c87f729504a9ee72fcea71366642ea12f8b6d5d8ac4fa5d58ae1c9d6832fafd6d95f17789dd92955d6693efd9445fb01cd8bfc96ab24cd2b46defade1d3b5126359122d2ed09d9a723f32151b22293977d11cfe4c57538fffa39d59f0d0887bceea47267c1a90bab62d3ddf543d8d8da71c585456780595b9697a50a2e71a8a22aa3729492f1b8daee1615c4a8b7bdca88de6fc6b33c08eb99ac4f7f6b5b623e8b004cb4a006d886d4ee63807137e834d8788bc15c91e35010d00e86efd1c62afb507210216e7043237348a56a2771cd5f8709dd6a9669a3b08d9dde2d388bd094c95121f7468dbaa78705a47a7cda25995f217697ffdbe0dd3d01a9b708ae520cad72a9d52cc5b4561d7aa548056ca59b79abfe4909fdbcf3615397ddeb446d8bba725bdde948188d491f08e230640af4ab90c68f24a9bc0efb96bc4541e02fac041fecb117fb63a927224a2185940996f50f4e4e4226025a8122a07d1c5e1ff5de7262ae452d513effb55d2541b78766f1e682afcea8ed5688a92c30ba72c94d85722d02d9421eff4023421864048efb585227f1585564ff1b0f5245968896dca872df124ceb85bdac768e3bd1fea8f0f0d29dc64bb044a568f83308a08a7497d7087c2b4058b924b193078f2f8dfb944f4039a644299bd6918818707032ade3ef72965f333ecda9ea8e3272078f7e6f6833754ea144dfdb106a64555433c6e9cb725e45954f5ace317704fee1b9263ddcdabd43c6759ac14b917ee427c788c5da7207e00e7d06dc1aa971a464396a8cbc832660c95b178c60e7f58c7a6ee370b760a394b325ea67215917dd2901f0326eb6fd8ba711bff4707e987674cbd6fa332932c207135441a74a22d51f6c78637e4a9ebddc34020725e20b71a5ba8a4bef2f9f77df691eb943d7a299013d5a57849458aca53ae2bc45271a12bc2e3ffdc3720d077d3b8df851a9fd5c370d887e967240db6eb7a0ebccc0cc320b529d4a68728a33e4f3560adc71d4ba3ca73027215e1f2fbeed01de4df268db4582ea48f3723a5df43c79dd19bf05e295f04182abbce67115e1453d5da685292a8064dfbe33c069b06c88f35730dba13cf4ab39d3a6d39d60959c1bbbe87c19e9af67cf1372a9d0ee13a80eec2187db53b79a424cf3ff28d00b448a8904e9deba1b1ac83e72e43bcf55cb2a74205bea8be2b31bffaa9c4721500614ffe88642775a841327082d36fb8b6d23423ed204725af962f001edc982eb1a8ec41d4da493f7101e61727103140515ad86adcd6209bf61a3910d721b067ebb088d92d1418e92de76a6729ebfe129a9ed8893a8f8590c001ade8d24b44ea107c872cfbe9da7759d6af272c75619ff70bcca9adfad732ba8b663fa043f1e97e0e3ccb742881c204dd6b872a76d89b0c4bd083e192fe1f1f41124594c3d8f9fe4215e865e5532777ce737723e9600fd72a9f1e1e25867ca78c43e6b9a3c97203d69aac09a5c8e4ede9a5672378868296d2914d514cf0878fd769519a52681b8fc02fa9725aaa72d5a4e4d72ef994598c2ab3215e341a18909f69c1f8a60d2fc716c2f7f63af32a473c30572f784365e56c54caf205599cf34ceadffd6bbd602bf60b5242ac436bcee7a6d72beb0a60a89479c1801c4bc8f223f05e382d4658b07bf0be2956d0bbddfeef223a105ae732c98072b0691551f0b41842ced642c3e194b35494a4fd4456ff86e5048daff1e6227b12774dc89e52c936c909660271d795239f5d21603334413fe0357fd83a5c5bb041cfcdcfccba82dbe37d7f1b311bd4131b104cddff8cd9b227209e9d5acc17cd169563865d0467ec2bd2ce9bc7ca6c40c2e09bb341bb7da8146c9c8cd38b3fdbcdc69e79d5a060ae3b600726b47e9ff9e447a2ac8a2778832722c9f3a290f2a4f3480577c69505ac769ebf413d263d236f43c90ac6103a80c0dc51232023c98ff676ce9b5ba859c1afed0442f655c2452820eca5e44a964844c74b6e4b2eb19b06e6994530d73cf08d6c44e6d6b2415d500e431b1ddc8df8c72a8e6f12e434d8b3d5bfe1001120fe6397d97a3651b133df675a0ffbf2ef8b639111990eb20c2026b091a72200565f3966de3d648fc97773693403d8913138972892089f7e242648e227fedecb6619fb68756da37a2de3740dc78965610d161522c3eaa40e718d5d297b2de72d6377fb62ab9a70cfd493d0471ca3f2566a2b5728c8b7930cb798598576865bfc3eecfaa42f9b15f800b208fd58bea537513ef63cfbdbefdfb8174fb68580731e008b2796435d3c9fac0b20802a190dffb651272e975318c53b24718f772d031304045ec5ec040a48678b864d1fb13af8092c64235f0faa7d8a0a409f05348210960c919a398631b0babc81c498b8e80dd4d7d1f22840afc2abae04c4bde0c8e313147751ae29d1b8387b3a5992c27c177d00872b092c5d78a45a10b8a58f66cf82ef0df9899e041d7afa8a39a35418887630272789f0f708914aacc4bc3295d2fb697b0b544c0acb26f2c9212dcb558dbf53b727d0a0a04f26b30d46132e7609de6cf499501f973fdfa6c9dbec62912acc5bf3e4b3fa1bc1fabc4ea08f392a794c51b277dc26c8d57c4077752c99af8a024a772782fce8329630cae9c987f33baa57b21a13dfc41c80231132957c9b3bb87d2057a36c148a32289d0a42eddabc3e5c84d50cc8b04cb568fa4bbbd473cd2dcc7726403678c5f3bd8be69b142a397cf3b51cf01913a7879cad4812ea6d23ec4474e2480216ceab2bb141fefaad3127025e7ffb6d7914f90a0fad7c1dfe3741dc8726a28e8348cf77c6452d8dd45ee60262597269837f27fa0face53601bed98e572d78108b2a1aef4af571f6aa93348a8ab999930e11fd381556ae1cc3ef41af43ea6ead74b4f8d327c03dd0c6e8518b4d40a918de3c25f3a2e574d2074c38f9872fe1828917cd2fbcae3709a1a9c6dd8d5f97052ed03089cedd8b35273c5eb42724e26238e43edb82875f91db69a3ca21132a0f0768190c2e795fa2ebae692652643db6f29c98366338abe71a3a5f75d790d73f065bd455f9ff9f66f6df095ad72972d0029584ff94377f2d2bc0a3c6d5f9d49945deaca2ffe796748535fb61072218e474bc511505737fdfb5f2575a4687fd670e58a0983947b27ab28604cc52d77eeffbfd14a08f87cb5efc419f0f3729462028bb750888a876f69d8e6aef560cf047c4a645e847b9d2bf44d63922b43d97890bcb438a2a1dd4d577a6ef191723677245e1e91db887fa57ffee1412ab094fafec2406531f071f9da5a69701a72dccac0048d98b9a3280c4d2d0bfdc2b541332ac689f6dfa22f6d4763da374609d56948a2f58a6fea5157b002640d9c0efed844ee362d664564a74c83a6834e386c0bb6785451644d4affc382b8ee002d5d59298d0885699a814a45dce695f4726e646e96afab83d97adfdb2afd23b8e01a5107ddbf328762bbc3b04700d7de72df4a936c518065dd1f78905fbd1a3cab726e7f00749630e23bf2a6e72e1891721d90ab54cf933aa7b0f9ea51c8df8010dfd05f05d610b9d490111d1f41e316721c07c6adc77a1fa344841108ace986198e537983c42b429ced4da2c567903272314ea5684e4ec0ee2e6950b40beddb8ffae2afa1450e3b982c4093f94908ba72ec2dd31413d38be6c5bdc819e4ac24bd4f7c473e13fb9e62d5db18d9664c8e72dfc0a07a8cacecabdcd2dfb04e9c8f0922805759a6c7ebf02f9d7872b7596d7267f4cd83fe7628589faa0b0ccd148c10c05fd98f14eac032f45fc134a319a54feaa82208fa548a1a97676888c6f0fa3ae967c5ea065124334315293e454dfa72704822d93f4c0ceb8695e5a343f84c3463b3308289d5ddb32a18b38e36cb1a3fb3443294e0c64ce28da52792f783778f17e4a19d1f4b34c868bdde6d5eb1ab5a92e2a94db1db4d0043946c2a15919046a3c0e848560bd697161a569bdd075c72af6d4d7f046cc615f7c625a26ccd6ca5a2f28cc20a2331dd943c700017885272792041bcd931f8389c9240835be786af6f1bf034b11019c596887c6805e584723bbf0acc4ae424090e8dbfeb5cc153617f9b4ce469f8d604235cd52ac88fe6720d61a441309339d6e8ee819fe3c6414b0c6f0a2c9948c8bafd3c69f157aaee1f5d2f84ccf8d63c30a526bb4da66300c781cafe4228fa6eb2a7f12ae7ec396072f0fa1f17422f9363dcae8c6fc6fa140793727ce236e74dbedaa886ab0128a172060c7730c0f2507b8322188f5840416f841c2be951f07f53ec293afab2faed7206a5ee2ebb12d9c90a7429cdeb830f2fe5427cedb62dc1472a70a705fd4d1137e5de49652e76f9a9d80e0cff7b924801dcba075c403975318880cc4f35c049724e8e4b1e9a73329959dfe842c54b4f0d1870b6c124bcbba73685dff289b8ef726004afaa84a70fa8f80dabe7b6efaaaf03fdf4bd380f136849f3f326054a0833953736eb23427afb699ea122a3045f9bb70a4c673956f81eb4cc280551e1dc726c111b8233dbcb03afd84e7919695357dd6f0a4d95188c0fb7434a2d34448072c163c5b8e8b70b537ac220bacb02ac218471ad42d37831e6897a98eb4a707d088a0ed9e75b95bcb9672fd65cb9c0b10023b470afabbef14fb2ab2199994c5e72aac573856ae82f96d911cefca3c0362c403e63c8972c8d4c4c0b982d8d73a1727ad712b50f65ce7f638c7d68db9d9869aa845a877973e1d1b1f2a9e495ebe4313258041d8f43c16cebe0de80213fa0a1d1a945078de7feb97d050db4a983747227fb0ea9dcb57fb81d0303b614f4a74644f64c07a5cefd885fc1bfa50cc3453e70b083b6134d41a6083f6a1d9d01bf5239e603a2ad98974ba9f89dcbd0b5e372ef8321e50443402a110d09904ed8ac67ce381914c3249c667b204c56fcc0cf70bc41a6188ad22bdf4ca007322b5ff7220d6d7576fb9cdd3798f801202ea4fb508afd9c5f9d1ec96d3b7c7e94c84897ad111b9bd04120f500c1ea6cfc1c42fe72700349cb63a7ea5ce527f30eef4fb8cec264502f59c4287851e2f9069e329d0d983b9e3c173c3193165e38827c4f19a1807669af364ffcf5682aa6ae246cf8720a601fa5eaeea5ab39f06cd01b6d7bf98e81f93aaeafc7d66512f641471bff729bc012844014cbe8348e96126923c6214eb3d91c845476c0ec190776280487722cdeacf867994609afa2dad2c4c1fc467a8445ebc39fcf580d61fcf2b5492757e1906811a46a7319813d71a4da99eb60d9ba1579bf42ee3e1f35be138cc34e72c8ca0634bdc5fad6ee4219cf6076762c39d2dca38e292db599b95c02c9dcda1e717b52d5483fde9d19a667213d476b7c5b085bff13af6211077aa6057ed12372bd9385821d9ffd2b8b2c5707eb1fe7a72f37334694f5b82964331099a7246e72ac130c2ad740b9f21332e177dc7e28a1d7123e33b4e91f8f03d7b6fc92d10a7203a7eb13dd38f0100ba60cf7a4dacfeeefe0253e2c1f6f6bf29e82c97f05e3726e139108c526d917329de2683ec3bcd54ef9326fd58b6ca9c2426a016467d82ba865281d4705c69ea86803c13f4f84313136aa5365e39b55515234f58fd241725cd3c89c56b28c16d3a0c3ffd4fb31f6b9137bd7aab39d26fd8cfc18b45500724d6e963ee7a55233446d44b2715b2457ff9287b7c8e318936066471371380b7297b62c5b7dfb2a30959d78da4bff7d04ab63689e6290fe833cd8973066ec5c725229c15362e8d1670384bd45b2bc7bce55589685556556d2615903bea5c96d72fe5be16e7a700ddb8ed4f09403ddc53f9ec4c3639ae184b168a9559a44a7bd7269cb48c727fb3e623c84a9f6617e8d659c7c2921aa29fd43c18e78f7e97c8d72c99eb1f49691484655585ba81225ca3067c180985e681412a44df6ba3ba0c07228a5c1f5d6c40eb2c93fd5125e01557f7d5d48a8ba945b2167537430207dbc708872d81a2b125b047225b7de616b2fa295a0bcf55c0bbc55f1e5d85702a6812c822831e381b16ab4a896ddd311da62caed45bf31bb2173ecf5a59bdac25fcd4f194843f67dc6242a1b73dd4f609c517335ee6ad693942223552134d6bc639125db68c0ad7db1948e5f1226c94607996211f6eb7782e3e15741054ef645a75972d6e8081cd49ae0a51ef2c9deb691fb54cb7059adee7905c6a3749e67fd41dd72e245d33f5e896c5b1a4ff989faeae5260adb851dba33fdedc16a3e6ffdb43072e6e1fdf34c474c47d9da0c0a9f34f18af126091ea414a2943e157a338a985e315147d5e5dea2c394334349ddd201a1fd0ef6148a8dea9e3318110cceb77df05ac981279d903eee40a7146307bb626ebd06a49cb141ba39457953a84b57ee187214e18436d6a5071411d9635fa576297dbcd2831d18534712c3b1ba815e2086712053a6d4a55ffac21e892859d087ecf99d9fb294113cd26c4c71e5361c185f72ee64e7420fe8d43892e0557081d18961bde04950bb927ab72b09dbea8c916558d4f6b75338fd316235bd9863e3558c70013b829351eee84ad5b240c2f32eb404a6803034972f06db67f8422a517df1f06c5b99e1a6fb2942edd73de431304e54b0a72c877632a587422c0f1bf20a2848886ac67cab954bed15dcc2607c188f72620ebb3dd2baf74f52d6b8efc1a6f615a30eb357e5f1dfe8b25ee1c199d34072153e279d0fc687372e3e3a73e185175eb2650175446727dcfdee7582d64f867266a94d9d602772abfd782253df274bbf185e6ec53336749b095bf8b33ab17172acdd68c9be896475ec80dbee0a6434733a94f154ea817662446697853ca09f58d2d0da9e16092e217efee2ccaab463adcdf84c4ffd05d7a41df5572139dafe72cc0f5a196323c33121ce2effa4012e03f6b0b76069aaab22b524f8ab5be2a3723f2be5b2b6dd92222d1547fd7be381a5a17edba1c000e057b92dbbe158c6c372bc090c78861efcafa40cc496b872378cf8b1682024e32b0997866235d792e672c340da792b664e45a07bd52d1649c7ed49b82203820fe5ddab1b0eba59b8447253e40c985eb8a293482db2e1a7ce5fd1fe208c136ae64c55ad0ed49c024f67720607be6f0d371de3632b83d18b2679dde0d45fcb0d2619d42791a955ec2650728f7e80062e38ea70016ea4f22b59e503cb992f6aface64f6172739cb93f92172d456dd9adab76ba060f7df724e46e44ac4af3a58e9f32e0341412a17ca7e8d460d7c4725d316f0ba693a730657a4782aaaf869e16d7d77d1c327565d4bf7b056f89fcf9dfde0108f8ef229df92f6c727af90d829a36ae001a2f4d8e71368e372f5756356e6429a18de2b91e84fa9554ca5ea880b1699b9b234a1ba5ce020f42f34b8256c4c64d0fe1ea18bca64b203637947c842efc65b8c03635d963e423272f9f0fcae793cd5fac7be8629468c18cf59ad739489ccb821eea235e35a3a6f72d3ef7590c525d609e22ae793c286de7dbb19bf55dccba5a2c91348fa948cbc13e0588a3359d78da6c8416447198465f20c201b87e73c80d86616913802410672f0c829156afe5fe459f8e02ee2833c845ef4a3d6b7f4687504954741bf6f2472e449209177c1bbec161d243346f223d6ace7d4591596d41b4a24b05aee418272d6ca5eab4a2b99a8c8114f522fb233d03a5a0b1646185104068fb836b58a7e72d5b2bb6986562df4d8df9f00cd4e911922179059d401e49c8e39740deed1a072f1e0c85a3fb8122a92c796f22edb286351cd7cff583f303c50cdb7b6fe8b1b720ff1202a0b401ad06275797de77d0fc19e218bf41809b350a4df261fe08188727bc6d7a94e9072d2850bf8e3d10b8cd8d957e8525706aa651030a100ee3d71666ce986a148146bce676747d9dc25b4444201241cb096dc7cbe4e2e0c7285bb72b4e14d7e33a5a7589628c04a041c6e4f4ed1dfca2a9163f3a9634b557b0da972422c0be1e2029ab4f2eb47a139aed4f2cd668133cfd53bb4a63f22605037f5727d2e33233eac145e77135240350f7a1d36e7bae5f2ca51cb7598256f079793445f3ed394cf7dd7dd01f121dc44600bfee1fe43e26459cda88f310f1a08a8a9721cb67229ec31ec5abff617140c9621b5498ad47933cb1472ff3af7254f76434813409af2a480a84152cde1413fc5aaf14bc19a5568a49232fda889748688c2727dfd78ecb2947b053aff3c25c0830a18e3c67aae1ae15087cb62d570c1f69672b55617faca1f76af70da404cd684160711660a45eb86ef34b9decd6092fca41463068493f83aa6c8a21ce0e26c210681d49df63fe57626b925dfc6adc76c31729599b215bd68c6d803b31402ba4d8faf67e52d8474703e0f96246863b5d9351ec2984c65a20177319a3a67400a689bf64b3cfd45bbec605be885f601a0d8c30b6d41194b184744c6a5e8230ebd86c4b7d26003ea75bce7d818fbe4c54d6601728d6db870c82cfba5daa806363f365c6cea30651930f0381e1eea18f95a7b590c6eb8a7c3a8e6cee81d5a4b5ff42cfa810dd0cb354ab4875a9ccc1cd4be04e5319709a9c85416f47fb7b48709d7082339af2aa09ddefd8010fd5fab9c49cbbe720440d46aec313077d362a17b39348cff33dca6ab5dd9abbdff62349546841f2405f2f88fdabff72277cc60a7c58627b99745f6f06d961c9a69d30b58e1119272a9bd6ca9abba5a68b152746d1b48eb67db9c5bdc8518a1b9059ab0c31a4765592a921f0442eaf36aaac0b2f7c74389937839275889d7fdfa545670c383177372bb6855f2ebfc6422bfec9afcb1ecb987efb138824dbf5d90f24fd55f855bd85c761fbccd854977d15bcc7e599e8e25064ad4b8e7f12ce019e4f2f2b0bfef5272138ecf9a954760283d1a4b524ed1f9a6bc3c2a24e3391493f3733a4315d8162d41281499f14a06abb583dfb29e7f95e8cda26f712ecb2b86e3f58b45b03e0a4ccb605097c0121c2239b6b72a22c93066d905375966e6a5abb48da3dee404333f58eb2c5f246b8df1e557781da9e9a678a2ef7650d9367f67f5a1a0d7b4c49972d3fcb9756f32e5132815c650a11de4e5c011c75c5f5c754a0b77d1bbbf9c7872a4d5ae56f85d6b12fc83869e4b56e8a5cc2d834ec51d79602ebab23c9360336532ca237b2ea92d57766a0d0b62aadabf9047d5dd5cbc5cc33420ff17c753b4724c36507b7c03b80ba6caadfa7ecd1606febfe7aeef2c49b51b3411371b9880727bf493feecad5e0d7f99660c455fc22656305005a4b098d54787c8bc76d31c727c91829557e498499e681d26f2abeb4988270d9677d9cf78fae593cabb44ad1038774bb8ccf32399d88d8e1442144d9608041b04e928f7a2ae6c09a42d549f320f2531d954f5eee4f6a46be7a94adf821ae65a28391576d0ad338d8d132da1722e092aa60493268cd685afceaf7d469de491ed8496c997b39a7a04241b589472dff18da8298cb13cf8b1b47cd046556c6475e5836abc7c5e196f017903963115b089f1be88755473464578c9678090828d9bed0d8e4b9b98fb5d8bed724a8a721f3bcbab88e7559a8ea5112ef8305e6930a3e914292ba46760a8a5f2eb315672a824de28bb961dafb062d78209d442b1d7ec2ab1f3f2c4e39849cd9db9f7397217e53762b50b8b00670b245575075a1953b0314175687844398a18d30df13d7251642c2a91d2f4a84ac0efdc584a3ca7cc94a032958166bbbac3a8f5b2ecc92c48ff12721c9f4885152a6705844077defb6df7e72c14ca901efb973f99cfbe729ff8a50ede745fbc950dc1477b292f793173e047771f896038673e53d3a2bd72a664a2b652f18b332eaae9abecc1886b470e96ff0ecc60bafe8d6ea91478f37224b2693c9397150a8a91b6b05187722d0ae9aa2214c7232f8a4fb93d1200827279620f319205c88c05c8792df5a39da0d57a0ca077458ac706aacf64ca00514d9a5d0de123ba1b6044b418729576cdea9a1bf25dcadcdec4be037ca34aefc57224dd1918fee57efc138cd8f5cbe4007bd319b71a48963ad718e353df5ae9b872bed373cdafabcad7e41b899aa2996c5ea1422b9c3457e3e5e5d9610159e139723e1a125720e1a602cabaa40f39c31dd993778aa1b3f3b0959ba84eca44bd2c5e4ee8ba0d1c18d921c353c7f1a20f9458c4822c98a5d4658b73c7416706d61e729136e1aacc6bffd5669d4dc753c7576146d8b53a553fbec77482f561a92cec5b18ed32a995af8f68a504664e8d5ee0cca25c39d5a7d001a0ebb6c485a1f78872c1fcd83f2fe5bf001d51d23425b92d8e2a67678c2774a223c0eee8f41602005cc74005ac3e34f6e4734a2920dee16f26ef963bf0c15d01d93a47a6aa837452721241c2c52b7d65cb117b7b0078f79dcae1033f10b534cfb288697e0b1667093844d0fe1185e962c234f62bc40adc690813d8b81e45db0918dc541111d34f1d72ca2f6c4b4ae6471391cc0c363d51ac9cf381d888ec72a0e31ff24bd66285ce72a9793993d41a6c3758e4a504f1c548a1ff1b54ea266aa2960b7e7fc5d6f223729e6db9a61b484e8c6e6df4ad5df22505186c636db4f2d01007375ef65e0abe722aeaea95e5e1765991aadfd0c2929b84f6a98bb1635c4e1c6ef0aa36f3aec0724f808158c2f6f655f81a36abb87bc7a558ca10b94bd08c341b7c388df1c6f272bd8c653a9c1b865323b91a4695f378f8b460da20841543549dc1265464927272d2b482c36be845b2678e05053b6cf6f5733a1b4e4f9600215f848b71b6addc3b2cc57a8c89ca0e42c940f71c733c61e050d29475aae4a693b34d16cf5990b861127b7224468422f5ffd57a27a5a419ff4b34704d9b49100d3de0dea590104b31213a1055e38aa73b3eada0cfabd71cb835cefe41abf748444e15efb1515d8a72b49da043a209768ea429637cb54d1af60897c3e4c567304fb3abf8d4060b0b72f4a178ef21fad49a54cff1a45e407c3f0497ccf29e1c6b59eb9fd1b9c8983b720e5641e76355897a8a101fcf142111fe0b00e4e58756d56814fecebed0a17c7203a14649c3cc578827e9ee83ce80d8a18941a6c4fa689f0a7aff646ec213e672bb268bfa308ceeb5f059f73e77cc58c4371cf09b475cfafb6ab01d519b74a37267cdc6f03589f2aa3292d0df88edfc73e9c6ce17899a6498833d35df2b4192724c2076b30dc26f0566c23f42a1f68e43f91cced3ddbed57050812830269f912f6a335a6295a3115fd1945c612fc7ff13937e3fd7b10593c911bdaa30f388196af53e5a70d6a8079a65d34288fac83865af02264f97aaaa37b6da65df801a645fa4f83b1c1c07e3497eccf515a24fb29d793caf3ba4d9c1b68841e6dee7032c5a6246cb76970573ab53fb13503caa68dd0b548899c3fa3c9a912c6cf4a8c68b22e83c01de64f769012e504a7c50d5c253a6f71105f5c73ebfe59c782eb31ada72d452b40581dc8bddf1e10b0113d36c0a9a8ab021b092a56978d6a854e633e4170c565b1c1459a21596b62e7c2f1a7331ef6fd7206abc5a45f8133efdd5695872b1b28c68458dee69a49c81a40a9ffa8f30153e1e7bd9d1af9dfa1330e9f5d815943f9de0e190b2f2f7d18af6a2690159fcc57424ea9e41cfd9af15a1b11a9d7246da17964cf11ac579c85c43511fc36372dbcd0713db69007b881bd44d740d68cca3db4c4539e7876650b747d977abf097584ff9d2e1305c5e73f0bbcb364b72dfe18cd9ba02f7a31f0f2f632ef1010be1c05f1d92546ebc28147091b3d6b57241860fbdf1583a95adef8c42fce3366db0db8b68d1c5815da1415f96b6a8a90b8630da7dc0e37e858dce34121842dc3e6597e2eddf7f9010748d7794a46b123472cf0151746e7bf684b1beee6ec8050df64338dec27facd8fec91849529102720bd187add399b5dc84fb29181a9feb9ebba2b53eb5bfb1c90a81086471730e602737e20b60faeafc8b345b6def085facf4532edd5fbe743a1e989ed201dd6f2924a3df81057a7a66733f2b458e72ba085e472bad1091328176d7a206ca081c72c1b278ac1b13c8947b2f2e7df2f98c776333b19544bf7d1741eb1ced81ffc55f94e4bd240457b05c1c233f15113f92a66b712278bf582b951eb0be2a42ddc172343b138592085ba48b0b61b52d798ef1e1148b1f1db909cbd61620b94c67f04b1ee9b08562113c8466c12146056b4dc999de905c56f922cba1aa85b503dc6072d2f967ffa8cbf804cd86a2d2c8f0049b48f1d6c1150c7003b51a4a5399cbce72b9621723aca980164c5957069fc9623dbf89dd20c3aac85276896de9095bcc72c86c4bf23bffa3684b6644691ab81eae484b943425c5eb07bd6ec2ba2852d34288a9135a916c7e4c2e6f50f51bd687292d3f18e473a9475e2353b23fc8cad87268d092a0bd44bbbef480d6237ec9b22ba4ad51cdb47190aa7c94d8574d8d6530fcd91c278d1dc1f7730228ad693c1206142b6a8b22764e893068ca61725f9c3ab9c61534c919af63849cf9096f8316d9949a3009cc36a17a7d6a3d0a7c3028720b330cb55a8302b2c4e015175a471c8d05baafb684df9f5663d49967c5eaef72347d9ed875b81412beadb5a0da778cb87e04e81fc9e2913c48bed276cb154172283dbacce9147347c59286eac4ea56f16d1f388813597c8f824c216e672b84294be74373607947f62b6bc7056308437ee311f20077fd9bfa3fb1fb451d5e8469a6925f0c7626eb897b36aa35ee420412b86ca946cabfbcb6d85dec7ab8f0c7722578646017cf90b16d54562b359edc1e3a1e73c53dc80e55ee7d4e7062ebea72f73a193065cd2b1fe0ee9f428db9ad4f71d81b6e92200c6262482f4fd075bd6e6c4df8eca246df3a0a3742acaf2610a4dbb1abe1d6a3024ed1f58a7f5f71c672459017f6030dd516b145b9e7aa9d288d151a80e55aeb4849105801ed73385472701cb5b65d476cdf22f5f3863f02c94c2dedc234f0a3977047a14c7e13cf7e7280b48dee94d309fa70bdd36f5061b36da83819411305288bf79b97cffda8b072c9b35ffb89cf176549ea898bdd08f1c8fa9dea7b2a464c201d7927ca337d56728eb4d6427faa9a689c9a8de8d9d4772abbc8876744a38a1713fd547399c0bb72795de898c64b41a68b9298af5dd193556ccd1b65a12a6caffcc88690ba92006187157bb818aeab0a7f2c71e743105b1899ac9e0962dce95c95fc0b6f5b698772cd837459625741d47f84b6769bb2c836850ef4d4c85195cdeea94b6acfd13d72757b7855c2885cf18618008cdc1817bbf7e037a64f1b136cbc566ed303528c72bc0023c66512c76b586b49d194178f28f37f8a21835805f624063dde270a15722aa1d67e0ba70ab8ff31c80b44ef5762b990732e5e84b6ddc6a45bd7571a19250900d81359ed8c9607d63b8c4acf5c7cc015c08dc3d8a017ae2db29ef2f2682ddc0863bf54ec9217d48a9c83c3d0086b583ca557db594fefcc8322261836417223dddf4fb2de75e82f9686babb6d0d04420955e6535e9916a71d04f05b944872d5076b7cb9925cf9faad6cf16286193e586c5601a30cfc56a78ed38fb4cbbc15768f79293d339638ddb8d76bbde7bb9b7bab2936f09a727b2b9dcf4f9f60c824df1b49488d7767e4648433a799eb2274211b9ad863f45d4b15305ac1b4d5a172b21ee9ea9fce91378a0d4b2ba1b51f7a40e56d59e3fe839e8d1bd91fa286f41e6c3fb26058742a871bbff78e09e05a36aeb326d345e0433d1f876475fd7767721caa600d5c721a37b3b25c337b3dd4f678d94b32dc8b45669b410b79cc1a32726fb7bacf4811b56eb2a82a7c3fa8fecd0bc31e007641b21b4bf3623b99b8b87215a92024e0da13a0ec2c8031efc7fd3c312da114741b552517050f06f14a7854a8707792ebee816752511362699484a7e139d9d0c746e320d964aea2361ec324d524dab315c17ee68a9d6913462705856c00bb97136fd301802606b14b665d1e3c762b1a29ee3ae4e8d7c00e6483791f1aee34c344892d6d3afb01fa433a874c94a7c65e3bedbb5f5414f395939e41dc59b141cc9e6b246b9a88dd4d342629724c07abbcfa314b4c44107fd8a73b89177cfe9174424536e713e13faa4f14482e86b011a29aafa872141b6358720c1cc15852eafd29c06d93d1c25b96b8753d72fe259f5ea8f627635091f85f1e6911d8693d0849480c49192bb1a26a48305a50a75bd1c86ce1cbf45f9257ab2c360e734783336c6cbf69df7abf86e625847358de260a0701c532a35e1181bfbc2f40ea5500e6d3ad1efa95460848a506ebd17298c832b31841ddc0d5281bc2c6e027100592a45b1bbfe73b611656ccadbcc94064e237a02088a471f884b643335cce977f0d70f0b54d2a444a853b6902a8f872dcdba6d4c26634c8870af90d8411a1bd05a80eed8ee2681741fc746500cfbb7270c689becf89987237c34954a040eba860be5361621129b1aa9a4ed8cb098f72a76b88180e1433ae43d89966efc33636391623947aeb8ea1072764eb57d58c6916bb49bd61cbaf9b5ab327697d9951ec5d1d5dff88aa7d3ec7e4f95bf8002d198c9dd9b62af6fb16bd21e85be5ffed50a8730bc503154bc1a90f84158b922a728903e4333a83d38f1477385ac45a88daeb1edd47e674f220eb10863d6f8d0c720094b5e6d7722a2f1410a54a9c6f062e4a73fea2fe2024695e11b1ee4afb59722e7b8607fc36ca60e90ac4c4c6e4c35f232ab4317714b5d4be6c71d8560ed4729f46673e7c1cd9988b7587b53438fb1b22afdf9150465a7971df9a7a22f7a704048efc29ffce871bf966fc8ee30535e20a1b07401d31f21dc2835e713fea495345e362fb2e96e702dccbc10c5dbd11f3986ed009166cbeaf96ac9ee9f3baab72acf3bfecd32d2a7f6594a8b0c067b6f351d0d02a145c2acdc60f4dabbe138d7270be66cb448614d339edead6b232afad9b06246153125319deba9cb0aebf0f00fa29395e98375fe9f538f15f21f96968147560e7c7f37b2515e27724395a68638a69a9361764a3ceebc59ae113728b0712f42c41ef9d4ede24a97a59eb533c72783bf27b93ed20f2abb69bfd2e201e4466ec7f5abc1a23359c3a28fba3d682729e1a35ea936afabf1aec51faf271268d3581ba06601433ac248fbbfc5e64270212378fd34ee94894dd3a5b2d9a1d83c200631c9c153c5e954ff6a30ae79d3e72a7d1e7df1b528f9ffd793131c05bae481fc094ee8b08e9c7a10d164d7e9162725163b4f002eb3f12c6c088879a15049da515e4983b40d9c96d77fb715d796d09238f680e986dadc2d6ed8a6f29220fd9b560573edba066b08c5cb69ed72374729b789b3edfa2986fd64c49633a4fbb51f74846d47a7fa27d9b2cecb237635102b675de077e58db85a88341d723b72a3e740a9e4fbe1b936870bd2a3857ff8953d97d6dc09bb4bf0c35fe1e7a98f37ffe83fba86f286adfecba0b00d86470e030eb2c5a92755e5f4bdd577729cd2e591801be0d92e6381665b203cc57212efd727eb734de95714028bcdf4737ef3beaef174f4d7f54b3fc4019b3c3757bd1e8721738c8045bcb823beaf4b85bb6822a2a26717f4235a6a817af64c10010b905724d6033c96d4af594cbf7007f79d4bf3bf763ceee05eeea84c649e965b3f2340ad945f383f380a2abb3a5a74e63fc0f6864e9c3da871a3eac53069acc71b33c72053d08d40dbef815619af9b70c7da8b04140bdec859ac454a25026456eda274a421338cef570cb881d76e37049c2ebe9eaeec89dee8d82175a25347b0e56ba72cc8e4c08932d2c24ee1031a0046905181e7077a14abad2d2e47e8c7128932e727214e95c9e4fc708fa0890e8141685ba295a29bf7b10b0e3fa35323f3d68b87235b0c98f267e53a83f5cbda5c64afe7c34f6f0ae38eac00f218489a520f2ea16f77365b2082f749f60b31701fcc83cdef28cfd572595accea70f7613d78de472677f5ea6db9669a9d08834a646f655c569c54fe1ee9bd5ea8584b382aa0efe6347565fe2e3a6a37897ace7cc639f292df479a0e6e15b7c74981c3df1a2b3cc721e1721704e1a21553719f15fbc2926709904d9e14a92a96c1cfb1f401be9d955015fb4eef59283483d87d3c064e300d8067f2cdd62acd3c01e049d297c8cb4619ab709f3d6c082ac73df08357ec3f212cc7b3d6734ab3235e0fff845dd81e172d366d4ed7fead9d9db730f21eca7e77da8c30992e31b95dd00af66356b1bad06466ebf8ac2006eb1b558659748219081fce211eafd66c8352c93a0e79cbb9f396df94b1903dfd354945f93972df6f2f24548c494ae3a2eb7e6a507eeac7bca72aa939af7fc58783fcce5eb5a0752ecd538f66741f3861c1567c440f6077859721424551717197e8133025f92a96c09f00124f25e521a52e48b64c33d389ff9723954fcbce13dfc94b1ee4ee2f1541975dd18d92d149cd14f456c77a4533dc272943183cfde3f060977889c03befd7bb49c43209ca5b86ebe882b52aecb86b572b53c723fe3e1b0fa292838a829a99e40f97144f0b8a20f6aa753068800fc8621f67f4c62dbcee49164844115d98c83f16a45b00ff06401f12fede5e87dff1072591811f7db233fba0443f792132551bce5d394790869a9e8f8233590d1f16c02a2edd38d50ad0faf14b3720f18515496d196385c5fba7da2333dd67027524b726151f31d444b849a54a69a068ec19fc6d370acfe18889a5951be40763a840672dca786af3b36f3a888a740756bd16d4ff447a03f9abf48e20ea979ea2b78f13900e03adbaebb35cb4f1a0fe4a1f20c1b95ed6bfe20df6310ef96f31ddf0bdf7252f131c8320a18e4467db78af6423630b6749a53d382faa05d764fb44a1b357289f7d65b4cbbac7e4c4e1bd5f8e3bb803b6d3e62761bd533498b828006de233789825ed13834a5056834013c14f1d0cf0ed72ab5ab3a22093b6c035fb212a55d051efbe3039798c663a2457aac2ffb610dd6ad76b74c25399d7a3f32dcb70d6a0f0499a59051e5ab85aa71583fc6ab91b118de50137ba86430fc08f4c46c9d72ff2730aa278323d776b6ed8c60e4df55aea6696cad6c9bb500c13391390f6772167228110da2ff5d48608ae281552b0027f64ba08bec4cde259626026751634e8b93abe3fbbb49c955e18dbd0b767b8c1d8ed0a31edad5a872c4e7e628931d23b8a5a46fac0b49d1e4ff3827de98bad6ad4bd923afff945f9bf585ce3138fa4ad1a673b4c0c605739498efef7c321fcbe3c129007a6968b0c8f6e1d183cf481ca12c9d2853ec28871a09bd4cbeea33918498564c57aab43e9d8c8a3a7d488a728de2adc371f349156c07b486f158c231e5da6829935d00e76665588ccd1a99514d3a192f86a869e257b43097184434d77ac5545d2ca88612316b72c7fd97bf0b86aff7a5dd471fcf05dcfafd9fbcb60a07252907aff1c932908c1f3867923372f66572caf781d98fef580114afaac74ed803859612855fe3d90a47f706299572ed45cd1308c78d48826857ca777673841b7eefb2fed045078e6fc263050b2366b1a89426059e5b7ad491dd55471088aeadf158696daceefcd1a9dd3e6bcf7541be42237906328a4d5a6c1c38e83c80b9dc06f91f44aee532e9806250b669e772071dc5bfcff49a10ef897741baa6378cad0d617c033cdd3ff4a6e4ddd562516fb3eabcc75bcaa770656c86868f22ccb11cb1e34a2ea62708695a92fdbfdfa630b02edd5144b90207da81cdb979b146d9d837bfe8457842903208295af619f17269bfac188f5bddf7f1d3e0b8013eb6fb1cfe7d1563c04c3ba777bb4ab1e3e87285fb21da739393ee7d03c36e3b935c5b211e4e37ad6378d4c02880b856b53b1bab0a8b1757ca8fbb4112d91da224ed320740dbda00b881e259be0f24aa4bdc6654ed707b08171a928caeab0a36c6afdba9e32606e6fc405ea700ebe869fff1722c2654b86bd609dcc8ebfa82c79f7920fa5fa30e7df2ebb07ef44ee2c00edd729962f215c75b896b5938908a7c92078df049393478d76d1631d700febc0afd724034e22b289f295f2bd396dae675fda89d8ccbcae760289d83bfd0e6cf55fe23f97e728fe99156db6418af1d6848b4b149f3a2b50a45a390ac8ab91caff230724cf09585ebbb26d1c92e5807c1d6d2bb663218239641bcb852f86dc0f6e08372c6e2c530d682b5baacbf5b8cd87d8ee3e30ced8ca8d5ab5e592b59bff56c5c72af99e84b88ed065e110a8fc4bcb7034ac265ca74aedde71cbae9143a37f036723329887ef7798048dae3dfe4dc6d2268c4270f7ba863acd16637f28457f2866e8ef3395a53f0345701ef8b589690aca53c141aceffd800122c73063a3d1e517295ee42ce5f94428e3a6d249536e7eb1ade2b9f58cd8328d15626f095cfa6272ccc84460b2e0a718e8bd888531a470c9313a1a8d353b4b92932c61b06e0a52219840a80306bebb49a7beb61cb1fea4873f27fd78bf4aaa93b2956e02a8b87ac72554da23a54fde2e6941d3e17ed77914b1f41fffafe36a3bd6374e40b360454720c78fb74e87be130ba7dca8703944798f19457fd282ffd41d9e66495fc8ec172c8b52698ea4f0588a2b72d5e38d62263a0510873c75668708389d067b3a5847219fc6f16246801019314e98470b7197f613156abf81a507c9c960edcbab14e7291d99abd9b95c52b1bb84ca206d426e094de7177dec8ba7b08e917ebcdf9f4387278b96b44f3d7229f249acfbbe0d0b9baa761019d4344293476bd37cdfd8e331c69f373d0ed7ae865ad76f238b57ab0e6e83a427eebefe5729024b7803dce405ebc2af1fb5b9413fc40f1c837a04039c275debae8f59806c274f2552125753d70ec97152b44cc5dd169569beba1c57753b5b7ed15f9779f15012aad6e12b9134ddbc55a7e3ddee8d061edc97a7c923da00a304bbf74730c19f8e55a4a5f31726e54e471adff92f7b22d6bc3f26b8e9ee98d493f6cc69d9e86e79d703f7221722651de5d1064587b680195a7abc441357d604961e4e6e5371d85934da42fa317d5033dd5cdf3300510a38bc8ef136c7f6acd75cebbd2cc996a0e30dd4a745b72e5f538f1d08f08b53e75decdc802161cde97179301b340b57bf3719764d96f72fee8f285056122cd4a686ff3cff409c15f209e3ff3b28a6a2daca291942d8f2803ea1101c9506d00c47eb0d3f459565df67332c33bc54cf27923d53aa1fecc720d599cc7132d9ad461f8c469e213120d4fa0197d0c0116d154f3722b81c22649d501e851012ad9bb054414bd6b7275b92a820667d76c208f42845a5eeda4dd72b6a16c4b3528348125107d695793104fb6caaf1bb7457e1fa4d99af67e9c5472b9e12146e31bb756281ad729b5ab7cbdacde2a36dfc8c27866dfa46ec7cd77051ed9ae3bef3244fd7b9224d6040ea33019aa8387e7b502acd4ef44f6a2c96272e8a5c4de30a191dc5244b5958fb3b009bd84c28c8668d9c64f2ad6c456d72072ae0566d1579c9da2672519503262620dc1511f2928ed1c42f4d726be7f38147242afe955787856664340e391f3a41ad506210dad3158ea24143116cabc127472db596fddd6ca070f463547985f1834b330eac30761a138fb38f462384e4e9b72a766cae5261d0c907082c7d718157d96f03da1cf6a0d2594f1cb25c40b1abc720661b9603024da35189ee09f9b5d875b42c16fa98abe14c29631764f7ef19a72363c6fb5c66d7e59edffdc0635785d9ee74e6f2692236e16fa98c34234a83d726d1be47490284c65deee55e3c0c38c7d54933c910841a0f4e2c98c43acdd31473807c795b3c9d770771bb3be9249ed54cebbc3ec0b4c42467166d10b4a2dc972a3eeb58c1ac42e99e9c84408ab02136e0b4489d2c1bb1975da3018319dbc114186e3a4d00cf6aa88ec6f7625212b9ba733b38d3084fbb5d24a3ae4774ebd27729c957d04999da162dafbaf5fd9630bf81018b72509e319617a313bd33ee760722e7cfe649bbabe5732e616a2d671a458140d1ba53ebef3ff695ab15f4ef1152e0e8538fc5f269e74f7c16a8c8d6fc06bdb770ba623bc7a9db38ddffcec03207227e12789053d664490f61de74d27973a81df154ab2786aaf60ca1df902a8ab26cb2485395559dc9194875998cb93fb5e1ca8227f3fd9d286078c7ce6abb34c5a93382ac29f4871d6dee45bd1ab25bd8a2d5b61d4a9798775d8cd7cb0a02e457213ff0a2473dcad620e351988ac235daceaf65f41cbbbee1e0f4044e8637a497218666523de2660b7579c6c3c80e8422ff28ba61cb6039b3207c735fa6196d64654feb183851d2e7be896ffc3da4b9f8771a938b1fbb7dab34c827e6642f15e72d930053dd90a6feaa9a6cbda2be0c0e44e3c1c5d7230d48349c85963c860d67255944d3cecae479ddba19a15ec164628f118c457806900084f6bea3f4c1e087025ec6e34295441242d2dfec15bfd7e2ca0ce10d7d689ea7267ecfca60dc17f72aa0e1d8abeb0ee32a29d8bf74bb550ba77162affee33e7e554215872d98bf225fa4d850786d7d07418d141fc5e2bd8eb080e5e6a38a79e79efb192f10e9b573e19e8ba700416e793c30e0fd133e34335a4dca7b3b8dc1a40ee2be0f35827945f5bfd7a964d1731859d4def77378bbbf8720bdf024b955fa1fca039de1fc8d872ffda5ca23efbfe3958af4f6eb34b7c0db778fc3069332a4989456a62dfba177067bfc7608824b14423333d44fa994cd9ba0c05f69da6f0caba64454c694887687a91c0b495ae1ee06d89ddf510c36cf0d09516d5c548fc269fab60b169e22c445a354775b49ee565689ef19a5325976e42a94326272f92602304d85133b2af726742ebf3d85ffb366476f876d61d6b4ff267eb1c7df8d5b6e028aaa7779a8951cfa051902b3941505500de2dc85d1ef0114601e5092f27d5aa8fec9bf81e4472665715a4eda5c623d41898f7785e7d6a28a5934b163d9fe7d0be7d14a09c8472808f942366b1b2161da8c25965dc4f732da2a37c847b6d6c9a99dca9f2d37b725eceaacb60b1a535770257a32d73706a9b21364ac594534d5ae998493ac47372429337d7fb54c5cd081dc76241b3bc37970f02a3372ea7cb821ac42d4c3fbc17e120d4d7cb9136b2761cf53c631b56f39d1263f6a3607a6d4cd2942433cbc36b80ad69cfa9de5792c973a008df8688c8116a3aa969c77fb04c7a03c7b90ad05dd3d962382060e390f3cca0e21ec4d4788ca3e80f7489c0cdbc55715c47b0d665efb0bb1d4b683a969aebb7b1e38ce4b9340a053d2b4b5b4c3ac6d3236e4e5d691965d16036c0b9b42c000b8fb47cf3dc6df7a75afb4e57f4fd9567a91c82f7721326568f9054a05bd1d0ad88fb9e78ee4cab008a8574cb7e584218e3009d0d729cff87fbea6a3f5303345890af5dc5504b39895b3db8ca15c9116332a625107224f200813db9062427166fc339e009dbddd0d789fe3c344e370bce4f1d7f82728e7170f9ec47aa264e6ed0233eed4d4249c93f5040c19d7d1592dc7db2715f727fc10716781a5bbc611701a3808c24aa9d0f1c9f8882c20bee539a3d2d888c7246eb0bc23525a488b134cca59f8f7146391bd1386451924feb6cdbc8dda3226d5c78f973f2c824fa7cb182db5d453b57843e6328872c55caef45c13d38099672c922529d26cea937387e4b597060fa131ceb6931505f9fb6b0940d6e21c383727a788ae4a1a77f95ea5a263fef407c58729ede319ead5e0e475903aa6c45f07203d1c576f4caa085ca84c8116eaf9443ebcec41a868ae47a0e68baf219fdf102574fad3dab065be33d50a60d0e7be9c9dcf2b04498d30cba114f68aaed452c7234d2294437bc7cafa2f02485c4d93fc92f8e7a36741570d74ad36f409ba9fe722162617203266e781cc4248284d469061e64d51ecd3d2fd68db3d849c451c372e6b0f924981d07e409e87c91b573022eb1439fb8838917ddf654df455b140b72db5af467dc8ac9ab85634650760d986d1e610b36622f30d528b93109730e9e1b9afa2624f6b5c76a622c69cf535bb210e3968e47eae9104a8fb352f493e675042ae8227b5b654d725dc39255a4f10a8b54fb30454130606a8c6835480e6e1072370beec94a1d16652ae8fba51303e6b41d537376e6bd154251b90fbe433bc202672dd2f4d3dd2dc2c757edaee028ad4f5dd42785c5b5b69ae1c19fcc878312155e903d567aec340aeb3c87d8875fa41879fd4c3f0ec33c7df5ca1711d2be633960f4462ab9372ee383aa4db95f2dde9201d3b9ef961ad6673df96d1967a6030febd10ac9f4c1df4c2c83f755bcdbc37071f7a33a475ba57752b0468b8b21f072455585538de2eeaa931cee5709bdb51976d15cdf4edeced2716405cf07edea67103ba89c4fac937da7de6be1a287cf73af973724bc0d9e5947fa00f335c34c028e9a816210138e4cda643c8edbd56e1b42db4cfc81866e0e63914a3ed7ac991d783a781a5b0dff78e95bfe8fb83c1e88ae50ba6c988ddac893e1646f98c4c572c74842cc52e36aea8e5c90c908e940be1fba102ebada971389eef161096cb672d748723debfe596d331d86eba6c2659e14d625d89ab05a4713c54a261bb330721051b4a9ceaced2a89c6b08b1ab07008a8be24aaf8b8dea7e4154cc033b72a31931381df390a12637e6a78da03682ff12365221d2f051297d2d44ca0c7695372e664ddea73370370a8b2e5fce6f77278d700c16ffe4910a7334260be16750648e18513934d65c3a5d1101b6fd19aafa029d536911fb8faa36f369bc79d7c53721a06a636f65e029ae8360f8bdbfec6fc18823fa34281f0d2e616916c162436721cc46444861c7ab2ed5ad069187b04c515347296461c7abcc41cec42b6af137229ae2a55c65a68cfe8e50d950d58400bd8bcb79b936a9f8cae4651df012b72724a7a163d9428bf761fe4599713691537bf720377e9f1fd7936a6a25443a8f30f0b2c75f513546e21ce268e7e00f1a95735763ef9968b7480de892edf9050d12e7508763f7edb90e061fe85c4170b098c11b883ef0b9baac280128bc63e71cb14b741a76b23b725463b7c08bf9c41827ee3c1e89b0588c305bbb3f4b06d424d729f20ae610574ba06812c005d72c4ba9308613a1966cf8359b4563dd819211a0ac9a6c93f7905f1eceabddd257642cad659a9217b68a2a9dd7e2464660d60557202ebc8bf07519e50a516874fa39f0db2a19f990903706c060a8ad082d395c172932202c41aeb5e529221127ed988528cbba4c57fd820f4f6e61db92cf5327572381101788e513e1f12de9b76ec751ad982589306aadba4c4ce87a4d89ebfb372721c1819e0d26d174922edbf55f5ef03ddf78c1ac663dcbfac995ff576b16972593617e8b263d60ecd49c16fc1c179177234e115a53987156031f2d085165b6706bab3efac3d1efe47dfc37780e37fefad5028d00d9af354ed6d841b6c175e72898c83a951cfdf9f98d408956e98b6d0cb8d5adbd6d6db46cf64ca6c058b2372f9bbf2a42a620e5038e282a69698cda08e7cd424a4bead3e56da5022c18d5b72a1540d5fc2d92837f57ef80fbaa89e4821222611d0f326779f9eace91c8c26543481ef18c8efffe0ce3403dbe93887ee675fad925467a288009800ce8760047264bab2f5ed153a244ff56fdee626d3e3280e730aced8b2a7480eb2caca07d2728da8a46ee01c8da66758956ce98097da1b7c6be30f2a8b05e572ec3a4e2437729bf31e416b479839327d2243b49b13f6db2a4a72af41a0e446488383f0bfeb727101d37ba3c5da3ae313fbb7d2cc85cfe443e4db0a30e22f32d1e040111b3d720af546c7b29bff9ce45f9ec7b9bbca1ee7951653d0a55cf9e44f38f5efe1b32055ad15f94cf94950339893661406b32e19d8b0fcf975b1ddefe4de86b5be0128e3a2b82eca7bff2b078223c61784253ae3a9e67adfd5fee249a5547a3f10d37286b3e6c0b63863cc6ece30f2899222006b1994afd4bfafe114fc8d071b700b1c5e4874e547ef3e09e26921080b88ebee6b3b172a5761a030fb49b33993000072af502d56a0368fbd5dc4aa59680144d09f6463ef261ff9b9469648a233f03c47f00aca739c1716acbcfaecdf9600ea5d7ba02e521c81a614d06f711c7923a1722f5e7e092a8588810946c1b8b16a51c47f7b9d42be7a001175329cffadc5f42b6da6fc81f71c763d2f5192e6074fad86defd77aa23f9f0a6cdf3d9e31e393e33a70fb73bba3417fd5acb60293c2d4d4ef65d627c7643b9b256f82fbaebb7675a52c98b587604f3ba4f8c72f0fb4569e29ac3406484e25583ed34a88a53c95b7298e3ed29f0a3be282c1fb7b07a525f84e0f7fa27eed128e418c180acd4678e20949f91c906ea5d603398ed10436edc88c65ad066c72936d3a6887dd504265268203bf30a26a4d99b4d5202661aca3c7acdd21fb3c3dc8ec4c17f7cfff005170486439ea26a5fbb1c3ba9dc15233921156febe8b14d46e54d3285a3cd397d26373c555d0e12f0ad45aba1c0edfcc885a46e78628560e65384fc4d42b912d2375a0dc0aa06ec0493353b75f198c404601cc176ab1a84daeb5a20a3d4a0681cfe1684e82b52a9d36a224505a821491c5838eac4ef6cc67b36f2f67dd7ffe3b1d113131f7682fc1d861c61297c861e8c8c43e0d845799ac4588d8507d458b1523d72523cd75cf690d2100db21dd2df130b6494d953d689440cec9874f0950084db316ecfbdec6adff90bba3b8b14e4b8dd04e092111c51e29415d4279c8c5f79ff15cee948ca5ab24fe8cc01baa4ae26359250bdfb3c7b392ab1174e7b8aafe374552bc3201794dbacc56bea74b06aba3a9c289f89981f1d136db5624a234572d372fcea9c0ddd68feeacfbd57a5dff9cc7fd12fe81e7935622795adb57f6801f849a88af23248def990f4ea2d8ace2a2d72bbc6582b27e67e7019be8ac7346c0d72b5739debbb00d34c0f1c8b8262e523faaa45ecb2d71057294ad2b144b954e872de6975d9c8c58c7d50345cb76f7e2b7083cbb63e0191e6e2da1ac26a22fc8f721e321f491253c6fb04783837ca94fc4e30b5674d4c5894e588b8be113778ff0eceb11107db20280bd43115bf49550920846641babbdd82c683a8dcba43b2153af80dcabe64ade22d1054cb9b09f05f33abc73780adb13e6b13943cefffc66472fd0cf3bfe8c19d8d83482ed8c770d2e15959bed1575f2e6f1320b1e589b12748b9991fc9cbf37cfe0209018aa42896f7f2f4f78188018bd7ccdaf76aa07d3f72d3324d7d70020d7fb6844f13067cfb3b0f9bf65dbf17037c3db56d50f5f45f72dcf1267f9982cc8aa163c33d5e65db548fa81a50962f8772dbb01e17700a0e3fa5ddd781c9888e3b26801523c5a2689a343d520e2ab6a06b9ec8d0a1e0b2af2369ab8e54d1fa4dc492ad0e247104bb0322384db2b1383c4b0f8f13de22669172fca65e0f689b0ee91d50358c06880da29522090d3e662f9e57794a6c00c3d472e12228f74b379cd2f08e20fc320ae5bd794b6c1d5f109cfc8de13e8ded9f15729ad4eaef7e8e685c1f5f4b90c85f6489ce6a9349b3f1308591f7aadd1711e95b2f907c7fa004a7b84936475541c17cde611346295baf6029f21ae71d797b922641e59d2bae5e3a5b44aaf36ebcaf3fccdd31217dcb8392b967ca11f8fbbb7f3e04e96201030c17b889f2e8b02e069920c3bd4cec12056bc37af4f8ecb1f9fe2d5e83b6230de46292762e0b30e40273e2c394e7b05448a2fadc8a56c0bd680e1d9790a4111564f8b0a5b4d52795cb4b863354a31f880d2668be432ae76dcd451f22470319099870c9da290414cc10fbdae4446426f508c099377fba3c27d3001d9b62cdb4b4cc3a80c9dd8dad416faf01b1e7c677edc17425b47c54776c04e34586faaa20021f4b1dad28a5f89df9c58a39a164691896490c360b80b6d4ead5727172abda3313412b5f1f2f132d590b48fe79c3c4f1f7104bfef623a793e9ad72208da4fbcbfee07649eb5093b8a05cd7d7be1d2923fc51bb6676486c7541f272368e70bd4a3065bb0eb1fb6363101e58c6bb6b6ea7a7718ec196a6698aae62537ab563a339983d4354a58315738817d57b2e38f271b60a31810bd79914f73e439546cd5c21af00a15e7a7fc3af5c17e3c034f08a1190b496e2534cdadc3dba72408e644b7d78438c752b04e831cb0c4883619fb9adc2d24e9cf3f1f34e9ead0cf22fc18ab9536566773ff921c745c3732ece6fa57627d911410660575c3c0072ee484f966447271a8a78a9b2db163aedf77a9223738333085f3b513c72395c72217711947a1774cd987ff0495b779686727b6390e6888824471d6e0b67ec7d725078c615d5ff1faefaebe56dde26ba797b9df5a8bab5a04ce0ede5ef2f5d433c374e6c680b4afb7e7483b3f77649ce7b7b37a8dbcae4da944c65a72746907b7290c1754ee1ac03b89e2562df426c9cf41c5f7b28d7c64eb960e1148af58ef472dd8e191e703f3f0abab30dc760f533ced32304539570418badb95653cfc94c67ce33552b461a9ff821b3a36ca2b7eb6cbc93b46bb275ac0aab32cba058944621774e807196c64a2b9fc68233b60b7108534be5b9f00cd2c0538787e66c6eae0a25eb0ed1008f8c8221be91cb836d4c713d9be6f1770fd2b43d4d15c11fc04f4ef744c62106cfe347acdaeb2e303944537b010b9bbb12fbf7c9cea8a955be3f67136be7a84137191ce0b789ce9a4808e891f69033fe9208a72818a9c04f712a72d8b325c8da698e69b18b4742488fc2890b395a4dc920e3a449486cac8379a572f10581517ae9ad518f4ab65af153ce3408daab46318b8d3bce6ea9c62a2e4472884ee7a22b3d24054243dd7a62fe9d36784e308a9d3fffcb5ffac981bea0a459856ca823c6d8b92f3562e72c1a21c6f9c652dcd29ff8fa53bf77b9b7a3d4971dfd3a4165d1a372e3d2a9379e2ba4fb806f853edef422f5bb6156332b43440d72bd4bc50e1045d3dd09ca31fe18b64fec0b3f7017b73d1b221046359181d4cb08211c374281be73fb9c7fd3f9dfda70a6e628132306cb9891523c72f0c0270472159c8537f38fc6f4ddacc7d97f2313be0a27b74c5c9f5335e3be4f2325cce81df1fc6f49726fd602f26aa2c457620e24701d9362d69019062477c12a6bc4e572dec04ee907cfe8cf4ad102e99b74ebec327f98c813479a2bc88bc5faed8bb26c38b75994b9737efe9e114e32912a65a57243869299a01843dabd161c15b7fd311933c3eaa8b44225d86bae7aedff217031aab2e1faa23e0460cae5246da70117585f8b50801b55de861180ebe60e4e9bab8717fc0f539b4575239267e2f478726299bdefeff14103939953b6a9a88db7fefcf3f7dc1c1fbfed83ceaf4383f0330fc548960b0c0ec5dc3b5deeb73f7b3dde4950127a30bf0df82ab75e6e910539029747a4823c02ba772176a0629264b03bcad3ab2f5876624a3bde24a7270e0a7d969a68c05efe63c9aaf69397379637d062a3dd9b202cb06faa8d175576e97259b331b5b12d2aa25eda20148ac4662a97ebfc0800e1d6b1254a06c93e072043421c7cc2e6b6acfe0747b9d39307c798a4de4f5d2203738b1d0329a5e3ade8723447934311dfdb1fd4c0bf9067708e92a8dd372db1a6feb400395e5a0e86927288032378edbbeaab5b8ba68387d48c3a7e43cb4820b549d13cc1abc7851ec4729e06d70ddb495afabb6439146a329ce683bd7f223dfd155a40d46af891467d72628f7016a1441fd9f281bc494f5bdbacd2ecf80ad06da1639112e546e3c5b472e1e84eaeb0dea7a48a00ab5efe935d9bb51f6d8cf4f0c00094da4e609d5e3072b79a59c56635c630b4f1012058e7337249fc5027247e04164fce83562676af6befe1551bcef96b98807744d330965a07f7a822a7e2fef11b6f60895a89fd7c245630db0d386e680d7c8d2bc3f8007527823c01474dcc93f9592dd55a4b01ab6d6eb8226c2ce1a04f102c3b04b5fefd5eddcd664e947d4c67a567904556b0ae72530ab186b6a7b28e9f1a5a1a89330159bd2d55924ff5b663c2a9a8ca87f4ec32401a4f9be82aa5efff581af3b9010c1c3721de8e47cad91b0647f9edadde0b72bc800615cdee9f11886490026af9f834f4432338d4ee398f9bb9205eaf399072b742c3ff21b4f115b5d89e0960a226c7f05b703c2e1a1c11ab4f4ad57011da1bf9781447b6796f507db9a99c3a2e6e1e49424c9c3f6865ec1cd9d5fb2b578c333834d48ec935aa186b4258fe7a2486a804b9ad4475c785f6f9da17a1695f7f5434c6fcd3562824612c04c57b9de543f81ed2cc0118c296d35367c473e9b17f08d209a5d9b6ee6cf5343fd12a74b3d1f016e5ad08681522b90b7cf994d6077e7271f62ed7f29e120e8cc8d952255e3bfac1b774dbee58f0ac3809d2247d59ef4775672dbdd029606ce6fec5a5bd44c464c78c3135e68d3f8ec5474b2632bc0b72288eebe1073be211c6a2e82fddaddd727702efd4ac004f8f41eb608d455c2d58a811ce3b632783b72f791dcd88709a3dd3d1ba4afd692c3c0373a9b72cae9b727a67214008bb2751399c811ac6194a252bd12327cb2d515d69cae2f2b47df372727ad515e6419d454b6d8a2d063576a46f32b9e17329abb4e413f4ad20fc0472d5b8954e273e823446067dc492045eec562d253c33f80e61e9d789c555be0852cf55f50e7c9ccef1b5daf903f1fad63a197308ed92b605e3128de48d1b0ca8724255856780b759ae0d92c3192aaaf9019a66e1ec4c9a9067dca12d076f2b0e720e6aefe9caa2c362d3d0dd7a2d5564515bbed6bef7416ba32cf5bcdde8bc6872eb86477ccbc2f52e2e452d4564a8b575f8d0e31af827ffa0a41291838c0c9764c56fe187db1c94214f9c5c100fa93c6ec4b1477c8cc923df2f15674ff280cc72d3f69f341c7eaeb34a45a7b77b596372e05fb43d4810dc96e252b07f5b61202de69f9352afafd360dfe8c297b60ea4cbe8d5a88daa4a89346c07c06cd046356e02a420d72f11b5b9ccedfa6095dec80ad8b72b91f9601690749a00d3c29b8c6b6dc5248b7e0d903b2d4ba3ab23f7c78366d4187ee35bfbee63ff667c9e44c5515d16a1121940fcbaccf78ae95262193268fc340d5df885cef0e4df90bfc74472842ab3ed84ac687d4877f0cca0adb5c9d605cb4364d188eb44bf430c66cc6d69ff62300a6620430d18d98ab7f6f91b98791d82bbca04f1faf6dd42a302b6d072197292b954f8742b7910926ebd34fa618e971dc7819fa0900701eaf6fa1f8b722ef5a894bd0e82a1990604de2c88eae3ed1409d5638541e996512645c0284572e1b25b9d06619ab8f148667bef9e39a9fdeabe4932789b2ffe43da8b7ddfc772c0edffd56bfc856d5a7b365b9d4d57ac34a9b65190cf38f3c4d8004aa82870724eeb38d119b9cd4a04a70a3c1b143225f556596a85dd07ff23f351d1c402433b7ac09c405acdc577d9dbe0789afa23eb25432280bd200cc00e80196c5915046907e4298d454859f30e3c7d3056c27787da873aaf2875e7c2ed20316d41756372bdc46e7470ddd21d60a6701509c2672ec9d4131d202bd4b35ed5252fdf33984e1dee250bd2d92313a8fe2432af264e77b78333463ce4c9318e7e67247e07ad72ac9c6f34129b5b14d1de94a2513b4d1e6a26ee629b14800527032168be85312c65d646b82b9103f2b5b377807f649cb6cc1d220247835dc78c3614756008367206214285d9e07871572ff818f821e5442a8f11595a6cb83aa99f72fa7c908d08b1e6e65e4f3aa45ac3e9a54dedc1c58784973eac8f77c28534ed50473eb747722fcff02e00ea72a42cb8112e9a2b5a0e405d69141f144fb419a4536b05ae2072977abf10c47c7a977b7fbf2a9f3bc1dd9393029fbd840558942b678392436e2399f09593fbfba8adfa6106e1f0d35248dc533bdd1b20d9421aa4978fe5674e72a4848db1d5fb0f03e5c511b3afefb866026dc44dd9737e6caa472964a085627281e88e38abca0bbd3c7a4a257fd2c6e6a26c057bdfe1e306cf1c76eecedd1e728c26ddad8a2c94c30546aad8c82ed2cfe0931277291b2b350859d127a96a2455fc3a027b2357a885eef1570fc825f6173612a974b627452de24212f66d424c720240db5bc6094ebde45e7f082fec23bb1e58091fcbff4acee29b966f2d873d444d00fbc1df2af11932eb80a9f5c88f24c53421c73ede71ed8eb87f44103ac172c244e30da6b28562bb399eda1a0709ab79115b322b0e97a551f19e42be11136c6e17a3743c9e4e3f2db96e48b158f664c49d87567c7a0087fc76dcf62b9624722e99e5d386d0c676f5ccc45ff9ffc3e43245f5a5def18d956f40fa7f5c6c9c273572d2fda219d1552722aa1b52defe54c40012aaa16c6b5ec54b24a2a75f1e5f12cfbd413469312e61939bee27538410a8745a199c101485c1ce4875e4665072a3966a82b000035ac1faf20a24fdf80bcdfaacb6849bbe38b7d8b4f894cc57126a2b739ab340a80939d0a298ff5803e6fe68a4b999704062d903568019796007d78fac61c8f01bda3ec24e01d97090acfce35a52c3a50dfb31b3e722e971f972531bb7d8ab92ebbec586797ae2ef873255587e3c79a88080a3aa17368b78a301aab8a34d51378c705266e53bc65b951b84732d74f19bb5876d808319ad6224729000711504b6c1176884fdea455f2e148249ee96ed45912c79ef4199a0ce9172c58edb982394189c36b5d20ebdbbb1df376abd0ae6e2fd75ffee0ce58d5f472932dfa801fed262e33b37903a5f79dc3214cfb166a3d64e2648646b4930dbaf72ef2013a8a8c02eafd68d0caf39d8b79f75e3cd2dde5c21e05dace11ebb8fc472eacf737702065e74390e8dd123826e740a5207915d0d368e085aca07c08f4b724aa81bde27879f22fd9d7209f85e3ef0a8accf1922d018f571427f00367ef442d26d09ad6730230bbd2558ec7abfd0a93a50588a08b3a176227abd2394413cba0200000b5000b73a53f0916c93c68f4b9b6ba8af5a10978634ae4f2237e1f3fbe324fa0b8f53d73d32342544b0d8f3f52ba98d9cbfb703ede7f21f7c6a09b2abfba5c87217884fb19f878e3386a7591d9f304ca479c577867daaa1fe5ee6d176954d08725d7c9f5cbd0348bc5a6c23e018a9c73268add98677d3cf73f02c5891b73012725d7d816dfac72b84e7dac1f8513aa8fd45edc5bd841bc4dea4f89f1d01f2cb4335d3b3673a033e7ba8c71f77c5bfb818152b69579a4748da54e8a66e182eb0116fa8a7b8e7e20d2fe2c797ed7feadfce826d7e93daa19434069ae2605e24f83d1fe1d43c71325f0352aafcf3cc37aefba86c50de27986fbbf7accb4933f12c7292c9bb0c54610aaf5b2a16ff3cc963093bfc2123693d7103ab4cab1aed965372bf1fb629b0238fd5b91049dd917b83da17b44b604a909223685138a16067187208f50019a116f1424104d6a49a0ff2fb7e27ffb92975a8914dc95131935b8669cd566a1632fabd11cf2543c3833283c305e753761470ce98032bff6e8d3cde72c7398aa21b9aa4cc25ccdd0a871197f1cb5716ebfe43bb067bceead617743f28a003d2ea82dbb8fa9c4ca834ace09ee16f5bcf5c419a9680480e238f06d6c672ce3658f50b535d3d299fc328193b90b8c2f21d4751d0458e45ba5a902f7c7c7291749af72f94110108e76694090ed06a81e2d9b366c64009816b852ba6415b72c5b638bc98881b442ef1a7192927173287ba47e348dff546df761f7644ec0827cc637b306f69fe210721501a402cff5f829b4dfdc213aac17dd5b68ddb27da5f007255da690c883f9fe7796ea4f5b788004c290457f8dbb82ac5a780ab18a872e34a3c755b123a7c006e7c714a8e8d3c5da30353578b091eaaf9b19f3150d872081b8d328ef627ae156c449fe615cacb62eb53f384364788df1e6bf9fc02de72e085c6cc63f8b432c11de8de257d59515bcee101c63cf8565aab5be0d0c9b972b583478de5e4d7a2e94040d8889817cd84fc409bbaa144af60e7b5e5ef197972dd0de613e343d4197e98b86f72311dee9bd396bbf640beaed5cef8efb97a0072c394a9262bb466e68b2c57ecb1d768462bcdc722ce903e9ee55ef32e4b77f0725eecf966045ca1023c50af1148e02dd586ce8773a08c1b67ce86d56006ab8972ad05b51ac32a473585af8bdfb411cb4bc5762794bfa0ef6510661cb5c19b7d729ac96093537821c2510f33bbe4ec58cbb0747085643fd43d9dc11dd25aeb7c72bd8a8dceb0a5e0baf97a20935c9a1f593eee451f8501e6efe661bd29d7b2c772931801a5c6a44a8d4b884da82aeb5927248528fc1919bc9d1a003a7cb9df79729a57daaf1d5620da503fb3c6f9f745d211e309b6acbe3d75abde60581b59f67214e344ceb6a0044c146690d660a56916472e3850fdd1ef09540fdea1fbea8f724dd4e0c1db85fbca5554621d280065defaf0e2cb005fcb3d4075d844368bf872f0a778b95827973c3902f7c80005914edf1c5c2b8f4b0ed664259a611c5bb272c552602405557e3a67a550134bcc427c1b888dd85d595d035d83b6b7c047b31a1d1c4ac7c425f86515eaf7678c2b0e9de58e6a2a97de800047c6c04f66bcc611382cd8861f408194a13c5ddc0eed286750cf66e3844a555457018cfb1c96714e51b3b3abd0567d5fe42d7bb0e817fd654bb90b8cc56940f3d8d4ae6e0d84b47285c4b420e572e3642cd41ff9e9f78c572592a977f465c5b9fe7883b42e4da507873912fff78cc49a4e5373e3e570f26f0c1b95ecefdf3c786a99eb11dbac365b36ba93498fdf12ee681a12f95359ffebda0732126a46481330fda3fc2bbdfa72c26d01d27f05b4e93b15b9a4bbdc8f3b7247e6944dea7dac4ac0e23b68cf9872debfe4cc72d36a9e8512e02245cff442782e6c24798ec259bd6d5b6d20879b363b2188aee87df47a79889be3370a7c80187b704dc44c8751cbf32d8af69fe80a36d13fc9afb6339ac81d5a7a89c59d77d2b43a039ee45cd969e874424a08097230eb076f9192a8bce2d9358df69abbf7cf8111b6656af469d25ba0d6f40f857241a847004f170865821067dcd4140e0b9b831a1cb8a95058ad2e99390ef03772b5144535ee3338e198632c0768debf829aa96ca57ef1f39f7a0f22c3589639727811ffb060178ef55a4c820f5a15fbbd56f0448f36b884a267c53e40806981722ac083b8b35e895fb4d33cebab531777b8d645f54e040df3ec9a7e1eb5902872b0a52ac9022e01383b0b05169b0de27e9eb0adb60c7290893452fba24753935b1a3229efeb7e1577ab30763a3f6e9dc2b2fe4cd9150287f6af703f343e38bd0d4b89bcc3ac487547042f5121afed8587e061f0cf626536a35438c1cd96e59a51a408db26b11ec1ec5c21ef661ec7cdbc41b265c413b1fd752b6328c8097214724ca5afe5b5a3fb4a6c2b01da503879f4c942c867c4b86cf6323795c4012d434743d34ecf484c8edddaa52b16e701d3f248479e208bd48c53a131b2dd10b83972900a97e49dae265eb3e5c121549695c763d9810a2059ef154ea2c17b7c0d1a14e48aa89bf5b514151f7741a520595178293362f6a692fb4676a1daa56686a0403cc205048f1e5db81b81d2261a0247c2475324629a6fa726e278c4ab59b62d7205dcd4a22b51674b9ff1655aac1ef086f28165a30587e884976246498181071868416f3033e8369a44114ab6371ac5229a7bc255e1f672903d74cf10c6bf6f7286940674064421d5c288e3ad2255e6305cbd186a62509f8919b91c00d7aed472410e4b9350bde930b671ad2464459c6f0bc22514db6e380cafffa89b8911a326e1de6de423b11091db6bcc763c1ff485ac787e87c64f3ffaebadb1dc17ff2572b06fc550405d4d545fe5658f9383a2ad2e2b482b933c91ecf39e903e7509e5033ddc49479e13cb9cd6bcba446b089a79f611c45cdffe9cd14b063a9f57a84c7212a79a9c111c133bce230a53af5226c20823402606f14906236911cbff8a9b72e43047f22a63765b8e5f71e8bbc2eb46efdd90c837da102da4652bc04580cc72aed29d7431be9aa302ccd7a21e1b3674a421c80ffd3e1a11b3b29aa70b311820a8a794bd9eafb962039f2035a6775fd0895a77d79da1f68e93a35c94dd30b52f97693a05ba0bc315eaac4a7ec043884f7324f8a58f959f9d2aaad0cb623cb8729d5b80560b44fb1e602218209243ad811d5be3a0a42caf5553902889ed175d72771b22867ecbf7a9351a023e43607fdc25e664de0d565e20425d51a57e1dd826da98410052da3d4fc6542ac506697a511a82e445ad0192f5ee281a390ae8114a3dd13f59411d45882cfe97030d9d0b67be47795a6c336d95c6e78311d9fe4972d102fedb8dd0df1ae24430443ec44e568984d2746fd804df68f8d016b39b58596810b64618f2badf59b793e35cab6afce785492f6e4027d2510006b6e3079572fc81921a46ecc8bc920a168c1b73d929d67ecfd4436d54fbac7a82727d06303064aa57634867befd690a90c003d4b14d86381a11d79a5f84953eaecc43e1ac7293642aaf4b0eeecad658ea46e663c5706fc32b105f5a84e7936efc5d205297102ed122c07a94253865a03dd6ab01aa60d5295c1df11c182a17118df6d2990772d38e51a1ef70a6e7be49238da68d898590db20d10132744aba283e41dcc3b072a43075faad5ed24b36fb3c275fa40629913289ca4b7cf069f932ea1abf61be720840f18ff0720036dfa85debfc244bde3b8f4cbd412c17c631130c650ff1ad720e35199658334965b33ffaf16d54fa74b4ca53083950b4f37f44291dbb2b416f8068f6718c3ae2f89f97c920021d83a8bcbfd64f4a438b3365fc303db8d949385d47d367f814d02f4d032ea0b71f5ab747be6746ed0a1dbe3947641f0095ab22ca1598b089de3f4e7a07550177fb204e059d226326a989e246713f80d5d7dc72eb9671d68beb321c5d8dea21583454814000a144abe52aee769fb3589703bb72ace880ebbd2539df4df453d92f9e367007dfd8d6207614df0c1c71183452aa7211e2d6b3b8d3482cf50efe00975d7717c2b2e07730745b8fa934ff6d27366f5a6dea36c13c3f8bdf190705c09c04742736ae8dbb0ed4e46d4fab11ab7c61493e511892e5a323d0a26d5d5a870866aaa06bdea3c882c0a9558ed16db63f021772d1cbc040084fff982ab53f5f77045185381d228f8f52daabce7e6cf333a6ed083f60cb52d70285fba5e9e39ee0d4b0e9e6c16ec4c5187b55db4a2d1924f86d724b983d56d6093d7e57c3632e0f5f2974af874189fe7196446418a7da730e10415fe37e04cf8546030bd79fb62ccf9a4de97d06e0656c15801513085ba0aec25a54ac4a5564ae5d64f5579c8645b004f8d6d0d1bc27fd32301b04046185a2ce108a8953fdb3b4d44ad87dc5d3d9e20e03ebf572a64b936eae59e0336071e2e859075cc070063d715ec39ad874381b723ffe5145305915c9fcc4a86064539f5f7217e9658308a9b8583fb15aaa45e590d3bd5a0749a70fb70cd3a2b69a13f2f3377d730b3972e33e19eb5e6a34dd725d8a97551c7cf328e59eebbefc5d6b6f304a77882b1fb906190d4a3b8bfe88399e4b66ea54c1557b45105e409ecb97a3ac72340be82a9f1cb05092ff549f22168f44de1d668b5cace176ff6e17ccb8c3f626f39877f9e698bda1ff531dd207f954cb116102aa5568a9eb1e7bef2c2c979572674c7dd85aa8cf34f9b5e05d5259894e9483d33963d40f964a2461c41f728772d734512ff903563b982c44bc7aa5c877f271257e39be829044dac3201116b272c8d4f3eff3e00ea92036a0b59185e2deda18f9a151555288f72b95281c3fd0550824055a549ae72a03248eddbbad3438cb508ffd7e994bb54ddbe27f31cdfc3f7dd1c1b70efa1b1d7a05eed5b004a83fb62acc36eb6724fe16f81571fa59de012ca3a244a0ac2827e6c91aff3b1a605f9001628b7f2b29b9e5d89fee23870a19eb126c8a7edcacb7d4be34568aec4e6e256ca1c229325a6c2648b9400dc57f3e629d49520266e786e83106da50d816710ae0c484f7efcea97a37cb745f051472c3c8c609fdfee5725a12dafd74b91d3d5c84a90ac0a5731fb1e836acbbd33072d274aaace3f8bf9b983e9c40a88ed60c31d93ef63580f55d584a0d60eea4ea0e86d396aea02fd68a0c3b89d0fcafd33356ac2b6fac7fd1434630ccabf6f2fc313160ca838d513e89013353a0a0e7d871ba7759faccb5e4b1a71e78c10c216c72da66a5057123ca8a67dddf87a80b1a88a88e9039db9a386bb52a5280b45afc72ff8bcde4183243c707d2df831799e7cd8076e65993035bf22912ec03cafca03ac74e8c0138237a73d299d1dba3b30e6574ff1686ddc6896a5edf8d0bc71bc9727a340d23b0b141abbddc09f25e3a8e466944b9bc47b8a55194a2c4f42a19357294b3432c674ea31d628dc0b94bd086418aa2a214ddf02bbbfc5970856dc03e4e4ee34b40e95bd6ccaf88fafe56335b07fcc8133ad3d3539d8b7b6979ba611972aa0d2ebb8b5e0717b05dd2d0229dfc1a88b589918639c5a844eaf96a3239ba55e3a369d468b6cf1c935c26301ed7cba08278d56b3601b77e48c5fe72cf2da472c9c1639611dfb6a926e991726915af30944c1a14ebf7a3bad56ce980f910bf72552a4a4d67cc66dcaee7521d190700724c8da11673e76b4c86b530f8912403720b04e4fd59ac79bc354002c65c181fdfb7b5af514ac8eebb3c6e174b65bb0f2315eacd774753fb5f717a3ce5ecc4c6180b28b324b668c9f73a5f70014409663ac910caee205257c7049d976ebfc53c7bbaab8fd8b754c6f4a7307e73ebb7b272d382fea669bec2cd2d81daae0652f9b6098e79d690ca455294372d76e34f641989562ab92c0b52dc137dda7598f1bfcde3f3b5847d1c15afb77b6e7250e62c721048fe3d5c1c78a1f2386a232217a96ac40466afefcdbcfc66dcac083795744249621213c2374f684b7b78b4e6b587be8a4be89c0fb756104bd4b92a569f9365693e0745ea94f0cca30f3298a5937aae18324b09ba7fdf9f2e53b4d1b0b7d172e1381c0e5a03a6ab92fe8e93fdf417f0d1db0c408fff617b7b4fd9340347eb1b31348018e4b65b429de80b3a235b057f23fbc044e450be02695b74ae5ec0d733e78aa9295e8dae16e25b9249037cc8e3d3efbd56d0e69ffdc2c919775407a80bfde0e6a53f6d26571f3066fc2790475b972ece4f97e6b2a7f00fd631c0e5377239df1ae0f15afd5b4de7683b2ed5b0ffdeb26b4d6b5946d187ecba8b4e36d972190b99fd49c398ba514abf9bf18036d40aaafbc59172f850127c2933b351b472dd25c53d9ff1dc70b8f51be5ff15a62d1c6305ce34918104a3f4da2669a6b3723a007c74826a60dbe579b6339af6fde1a9636cf7cdd6fb30f8fffb62e1bdd035eb0457526756d583598621e822f0e2306e246dae6f93e97f9a3f16d68563a611b0f16f23397230392dc535c50dd1cd4eff12f0edd8c7a84abf59306c27f9e372c6932ca6fef4ff96c1c46c050d65927ae4c68f40c0fe6df4453b00ded69c5972f34185c86d4f408a24bf7999a17708c35f1d9fca8def67beca896eb546126d7226ff62a7ca745250021fda0cb623d677fe237e921b1f2002eff22ca28ff74c4b5f718f542c80a71ff610ceb013cb0a12db4600e9ca94349cb04d1840ed037072f02e1fd5ddce3e2587656efad60d77ece219e0d5bd2878e78275cd1e9e968c64f4ddf8072008d04316fcf43b113f1b744a035b5d45601b20871826cfe7e6af7284b17da8a99ba1ec5202775c87199b3c147cee792949e1f20bf7df1c113b3d72afe74d6d4d7962b5f95c6de9665db75adb9abebb276feb6f67554f6d7ee05b72eea8f2f79fbe61941410b02064e173d3ad0782d6e5c0b50dff9427554193855cc52b24b7e09fc19c6136c9ad36c5617aff4452835a37ef05a453a3b7ca722c7266f001b159053006b2c5bfb8e5c5c5c2457a309c58de92e205216053905f3e7261aa103aba6ecaa2e801bebe4cc8f3df6c8dad3065915ae2e0115c8e2764d50be4df9731efa12df90db9366e06e855a124c026214e5d5f566978bab8869bee72baab7e2fe3081b1a80c167ca1d89f604d5a2fcadbba54c304ad4b16abf1dc27292fadad7b852ce7db3e5a29719751991ec8bcb8ea48209193e9a1212e85ab87237632440fe1892050ff5b45080429ca593a3c6b8aab79dcd22c4109165d9a6720345038df142ba8ec1473ea204b5969349e2ec53b0d20a5da7697a352de85372c96e256fab838851e3cef5b0aad789ed5a82f596e697d6cff4a3f25e18cda7720eaa49ad930110d9a038e4e4e8e579ea295c840715d4ace5d013546a95c35272c6e2275e3ccb26ac086560ed4bb59e94d344ec232866e3978810c7a9faf61b7250bd1dc6f059697fdddcf6d092bad8b37a8d8e3c911f6f4e524fcbbeeac0a3582073e1421c93af303fbba697e8b4ad1db380f4d8be630ca4f1a6a65583104310eadab0360a4ca6f750591bb7bf3bec076687d93a3825e621b189e6e8a4cfc357da45b567ac3e2607507618c9eaa28c8c0bc564d653f3247c27fc17c99e223472c245d4622c2f00926cf30257d6b0eb1ff60457c4ba2a5ec3bd3bf06910f60a7230e3349e57aba90e26615810f9faa3e88507197b29c31f7492d428b42d394272f80d3207023fe7e2b5400f2c5d4cd0ac14996511de6f1aa2fcd9635ce9a52572bb11cdb8a98da05e82084d7260a970bc4a1d928bb092cc5963ba8966a396ca72cfaf82b831e2392ac19ce0bd4a4570620966dde1d51b25ecaedd91d474a0cd7022b792c61bb43d452f77f3c54b2ff17918651ba57980924c5414a4b7bd148f2d8d30dfb85e0c2502926adcd43c3ebabfbf9ab2b3f154e5e15457b3e7c7d0e672558a8583a8350652df68e73a6713bb14701bff4e1ab4095925b72d18b51bea722a4c9db6a82bb2d9ebed4023203dbb97137d9b6c9cf1d1a2610a5a508f82a27265c6f5f7110ec49fd9ee651f080ce23fc9ef59dcc5614cf0be0777d73d95cf098d8c96ea3784bf6dcbe210441da391c72540d5739852b01a489a7bc565400c72ae847d820f291c73e4e5c26acf2583c5c786d24e8089d1ecf55c13cd1da4aa48740eff9da89761c11d60aabbf3d81b67ca2faef022dab42f5e3b310c0b3ad93098c6b6b3ec259e64bbd5f5cd5c6e9a70917356151fb169f88f18bbc7b77e272e8a51cfcbd6e40622d37ef8634f7fb679eb399736243b575cc3cd99db342c4a0f1b7b466279eecf163267d1034da01f3975e64ba69945d679b7859f2e7c19db325ed7589ef1ec2c63421af09cf94f08b6cec7343a4b621e413dbd72e44c0b7172f79598527fbf13bad442fde7d4207a4b2606d8fe07ee3c47595cd01edc506372f52860645b234c3440f5dc0316113f71b61a4a311a574ecb944066614af41872349170044927546b0959028a90a23ec143d63f65ece3e34bc2362ed476fc2572a20c3df3be29209f15e10242f414185021c11fb9297229b5aa635749f61bb9724de6c6b8fe5d6fbcc19a870472f861af8a5fad8a28f3a960de58e5d1cc273a138c4e7d763578327e454fc0a055f21a8f18cf62b6418bc8d6004a0b2c4e70d672fe1f03fac7923118e2e6c51e46367ad18948b0594a1464b6c9d7d9253a79727226ad0188f93a3c463ff1a805c2a73013fc7f4a7d817cd215f376d5a44243ae72fb3e64af166c33d6eee97ec2a300e61b90c15e1d65bbc457bc0d37b6ce718972ad62553d0cbf9ffb386904c8f4701c907832cd5e281ced32ed7d241e1bdcb412e73cf50895104e27b127cdef77eaa8283318802821f0a107ef906fe913031a72b48f32681cd2e85b943c319916afb30c7c155b86d7c4f0bfd39e1faaa4a36b6292d6541b3ed6b5f97d39ea0250fa731acbb05475c77a227476f47d85b3bba662d7f12e5a0ab15474c61433dd9f36d9800f308c4e1fd6517472d504ff3a4461728db699f84e55a20b394695880f3e2c932993fff6bee077dee453f51e59ed6672210b4bce9ff5e0bea6bbac7aebdc35b66045ae406a8fa0a734d771eb54f8197253a3698ce418cd13df3f48031a2a4d2b9ff70198ce0e54e4a282b9dfc13635723520a643871c04f7bc08c3e115109ec188eb9b68df5c12f5eda70ae179ea78721410fd356aebf5d97950f65c3ebe12e26fe370a4176e43cd9d1cf431e940c9724c7308f18b867d8334ccb436e99ebb62d4f08f99910e93a505e0948c21a7b62c27ff60f8f4d48a7630df86ebf6297305ad3a89f803e9a8edf2a3a6f6ea7049721113405cc819950d9e6316f8c769f4820deb3a0e28947bd7bacc18a36e8e9335b2c45467b259eb5b587ee9438cd3b89aa0297953706742aa10d4933bf0affc143e3ac5f48ae60771069a92baca038cf0ed465f22ca894646f200d3ead9b3a012cfe5a44e6033ec5be737619ac10f3439ed7c13163194e9f5b0705a37852bd46d613f14e8cb66af68a9742d0f3e13533b8fb05313a0b463360521642f7cd84f72f234d2f702624bc7063f7ccae21fc6847b20a6868beb8baedf484fefe57783724758fea27a359752d419a7ca6d4036579bfe73dc912ce9c44d6259887dd15a34d1a8671a0c8db07d6d9e222206673f133dd7d5ac6a03e27be3afbfb7c37b4972b006b4394cf86986022e16b9e95667c33eb8553880c17d25152f933c817a46720b918085790f3c9b7cd02ca5b5a33af38e4ad34a9b83e49e9f112e3b807b032f52194021a2206bb078e73e4b942df0114fadaea7576f1b1104a961ad99eea1472275650f5b41d7b0d9e218173ed61599cde114fb5486c5a60780dc374575d112466a55e7f1425461ad25d289ba418d333432f9a59c159f65b00dbd6666732b72253ee3333873173a91b474499d9e60a953fde807de9279203fee2df7b9a79b72b22a18333e0af07d0747fcfb17eebc16628c29dab60af35d1e6a9665ad60ff72b64f80b5fede9f8d6cbe1fe1d57a4dee9d411b17f5bbe6b2abcb6b64b3cda4060bb86d1c9faf1e32f1b335cfca7bcdbc37f24f8ddb179c363796e2d044f296721002f4a20220b959b9e36618d82e6148969d03134293acb0da11c34b4264487264fdd66e9260aa18b26bb16ccdc0c1c88fe0eace5f4eb2083fc860e65c17bd3470c588bbdb362776b99e7f1c3ba70230933d85b078903268cf27140ae50eae72b534136277926cfd1b953632300e5fc6afe8188de5fed0615de38df76d3b1572fc5f5b922273afff6a1c54104c78a30e85fa55591412cf8089e6584692125872f77a3a18623f277d84b8fcb28483b27394cb982f725efcb2ab48ce0f64f036725351a99f132cabb723c2a3a5b2f09f2a45675927690a22d5bd9863fc93a92c37a20cd814ad5c3ca1681d3e5eaa8bd0452fd2548b703aca83aeaed98196fd80698c00a8ff20cd89a8e34c40512db9a98ca3231238bfaec7ed1b60e984ff850d72adc56042a6b752d94d3d9e9bcb93e04ad3fec622333738ae3245d63bed52b17261665bd05527f9213b10570d1b25049dee9a2c5bdca04775c2e429597169a1728cc2e23ffa758f6c72dabe9e89edddee945c48f8dcd60b870de1b2ac41467572908c873c22d374b07bbb464b62bf0f3f9e4f1a7640fa46110ce2042be9d53972c6e8a1a21226a800216b1f12cfe0599116613f4ff5b405c2d53b65ce3a448e71d2d686860aa22804e6c8bea79660a94a906cedff2f2f7b986b47e631781d94538af128a04e884c56248873c06760575129cbec16578422773804aca53eb14072fc22873ef23c5757a19e6f4900d6e4daf163867a01f74fb1c45fcc4102d383017e70a87f5d466798e0e2eb7f26c80748ca8aafa4a64e5fcf51d444eceb4b1b51390d0c66f491b2d00f6e045c9816938c097905aa66538d604a7c44f740d7d772a6f2f4f5723cd300b7900b21b888b12742ed86cd7741c0fc419dd8b8e2bd52729ad918cbdf54691588c50af8c13944d0374f48eced74bc0d047d3537a6f08c255487a0a2297d5a9ed14c7687c9d2c1d29e78df89a3a0ac472e338436b43a8f66e8a309af5998565617d64c14e33ac6d9de61b1cadf02c684361281958871386d350f5798f996e4c3f6fcc1f9d2db90806df6eb604db5f6a5e98c4fc7d897696a8bc11bed4aec44c21cbe3a0ec215a1d3766013986291becc44b4472a2abc2072151e9e760b430f4134d3ca9aa4529188e48d320a9c894d5c162e1ff8e40d867200be244fc9a2381517ac41704154e40890bd9494ae934fbb710b42c0d6c1ec13c927cafb63141f5428a3217b4a7ea33438f5fde5a4ab112bddf2dc1a39bff272fda803048591474098e75794ee63f2f1d06f9488d0b10244d56d3005b479477290f638444ab636bc12f236358911f0cded23dbff23d756730a7c5e0b75beef72b94edea369711dd2e95288b93b8971128635bfabe74a118665ff8eefe637b025e844aac749aa88b43f78c1c768476f7d47f78c72cfa6e9a9da747ea5f6e10872efe72aa185e340fae50ff8d85fa9c0e1eaed37fc52c2d5e5d1737314dd76d86f436911f46a4c6783406ed3907299f7019c5bda489af057fda4c490642975b0729c7b5571364ce296183ed654809be2f19df6a01c91b24d5b7dbb57fb33624072aa743c8c33a3975070676e8dd3614dea33628c2358108d6d3843a49b44617a438ebce0d852bbe0fa3d9e493b5dc55ee7dfdb42e69560501b120a6961dcc1917222fc73c732b97a4ac70a5c24fd3fa011cbc82c11d7e2e6513ddf72eb2dda5b726593a1a9aec7f59959f7c2fcafde6a3556d19defe2f97ca2975ddba6e02f5b37784f8e96905970d99c777f41eadbd46a1260856c84f917ed539640ea2c02fa313d1967554fec4fd0d306171f7658de906f932c0088b6d5f7f15c9ab746eacc13d60bc783833b96cbaea0dcbb864d06c5f0d96d9699462241be34f82376a6c24c8c615a10e413fb61a64ceb2971ed396daac13c7add456b657498315f55b93a72ed38507417b4ea304cad748eb13474458fb315442fb2a5dd9b3a76f50f6c4e590022030c70765924ef33a3d530e8b4f93f66c4ec9bd399a5f076de0eacc25f72253149ac080a8ba341912cce8fe00cdf1650ed723a346be1719fb190d66515726e1cb73d3be646657fb96ceef65784c416e8ca1557e28e71fbd9aa81736bb0161d3c2724f4f556f8169ee483e4b1cf7e568d352f3d9c4221e67e7f032c6386727b1139264d4e04b65ba0e17873b7f4aae7afa919f5d377682a40bcd5fab7c25bd3c206ac7e734d04ff4f649404fc8fec7bfe3532b837d4735bc7974608770764b8ea83a4f67d29a75a955a247fda2fac0593e3bcb63b41d38321553d207fcb6a2b857a29a8cc7c2ca0d2bddf26c5eb1a395ba32b878e81e8732289608c1d0d291c6d5735da64a050f79ce6a3b7ac4cc692a91ba6a7d6bbc1438ee571f69b895f756f9b4d766a1f84c1eb0bdefe68f1616e3c6c30d832272e80bcba2f6f1db47221de9a7cec3039a7c32ce51f1f6872d324a8dd52c39d4768e3404daf2ed26772bbb877a604ec3497b4ba724a8cdc89e03eb8b12546928bbfef90d390426898580d50788d7a81dd7392a9213fb668584565fa9080b07b5c7cc79ef578007d16628620f51d0386ffedc6e84fa91a3762d280d4cee6dffab73da41398494f2a827258c2a6a931cbc60289358e1c28c2bf50e90ca29dba5f5b1661b543ec70537251000d6e2e307380b85ecc4e3a30749f878c560794d03b55a837fb8655cc962c2e2c6cd3d40392ede8ee95d065a99a89e3ca458ff8219d30699e467b1539ae30721c824ab97136647b155cc30252f572d0d408a50495339278e1a895831fb7374c11e0bfcefc76a8ec8c5026439d724684c1ed49355d1b1a82e8038921112d8a1922b6029137b81b1d824629b226ad97d090a181265aee78e9aea220cfb7ace372e6b8f38f69d1cb962c1d636bce3b3ce350353d8b72bbdedffcae2b1824a0ac72d99f37d7e5c7d8a49258383ea7c3cd16c40d138c2ed5fef06224a0bfde4bed72bea910a60ba4a54f360cd9a2254beb42a75337f941b6f6a05c62df104082d27249737d9052bbc13778abb4eb21b58e0b80b9f2a72229904e3cd6174fbf728a3f2e005001ace456f44f993fd9fe10c6cb938fbe125164e55b902ec822c5cb7f03bd4d5678e26a153192b8bd023a84643913f004f0f1667f4004263204a73a2d7235d6ccc38f043599ac91e448a673c7db5036109a35110e5e8a9637e7b6a5fe72366460552a469314de9c26f4dbc07002f17b20b4eef52ff8a768b3262c95377233475748fb0851109d709c8014b801c9c29501af4709acf5c0ca665c821a0d723056f4bbb60d5ebf693cd0b575253deee38ca6c2e8de3f64e2f9cdcebcb4f31a6062443eca063ceac6e181e2ad97d2774b3586a6e0bfed958312d5f4b0a1fd727c9dfc8e440daa0c0f23b3d7a21438f7cfef532f3a1f31e56d9597665d81e372712b90faafc16c9180a065892fdc78335a1e947c68e7f3182b9ce56973d2a20277f5b092d06ebd3682bb522dc5a26df35c1baa542aea889724377ee99cfaba2c220f6bb61121b7c20ece7f1e1c26f5bcabd7bb204f53b3db3e6d0394fea41b7214532cd7a35902a26614f1031bc4b59832eb0886118aa1872d21574e927bb43262a8848d2650091875dd42aa91cfccb20bb01ca709c7626b320737b817642c720d37ca3a86ea7f88d921ad9f42ea7c41d885d12ee84216b6029addf23006a8725a7e434614a28a0093a172334d7217f286aa986abdb07dd83de97d8f1b9e2672319ea399a97e1239350d767ca5c3f1d1bd6e575e29ecf0b0f62679ee3f2899075119d7757d75354f9cbd17778957cc2032a8c9b311d125e30b3ac03da3c8647204278c877661baa3822d2920dd6524e87b99c984f5f005bdb5484312b986627249076db4a57f137f786071f94420f84d7663e37aefe055b8f0fcc8c097db7008dd2a781d7e834ef20bcd4794254c439af7b83ac23a0e914e3870cc69aa461d729bb4034d8781a7d414d5d6cdc42660484147d787847accb6f274c8c6ccc1442946a7d66e171f1b06598837e6b9d10c5b54cc677202bee9d47ec6f43c7641ba148ce09acbb6e99de6f764320e0c1869f37c5be8dcf5d2853d0a3d0248d5411361766e0464e74d4036b702d197a96afcbf0fab71d9ca0e47d09eb37b39f2ab5172049b65ae01ff44a842d6fd1d93aba78016f5b90b0f74eb9b8d05e80fa7775a72274618e7f2158cdd0fc252749d86114b11df3c4330669710677fb2fdab3a133c80905ffac81b509972138311ae6a0d70445dff583e07421eb7962527fa5d21724866b8865ac2bdc3f9dc22d915cacfad0e29fe18354d75013208c07f0ea53038c39dd3a222a783b4cf68f3eca3a12240e3d7f2fdea4d2e5f6160e88ebe45de50e2461678071c3c7d0141f304378c56186c9d00442e33d1e11d7aeb66541d5e72f0711db64867725a79364d09d3892b9f6027a1cddb405ee44fa238b94160131c93ebcc3b4dffa655c4ece1f28235f35e5483974b4ef79e8b77de73e7385dd972af74145718a9a1209e5cdb919b4cf02a6d82aee8784a1101b59be2f0d82e9372661cc4f8991a7d5c6fc0bb825d5ec6daf88461af979734d844d3833ee1433e0e8d16b632d4299ce2d1511e58fdefcf08b3e88b67f8ddcdd735c41f907ab49272852ff3a4b36e83a8ad707de168ada3a3dc398ce617782f9c05c852bfd9554a52334552916e8f3d5db3c157beb8f534c3aa5a4ec13e5b069182b7d05259aebb7221ee905ab01aae3dfeebd93602bf7e59b5bc59f8f30e4240110a80d690ee123499ac768c88aa172eef5652fe557cac18d57e3adc16e6f5cabc883c4a1816357258e942d418238dd2729e451184c452e55020a01afc5c04b1a7f537d53d03d35bd2432d9402929de710867c0a675d2fdda3561b9eb4074d8eab5986f892067472d65ccf794d181daa7a3cb1705918f0bfd1c416bad9b65f3a15b43fd34197bd725cbe8599620c03775d9c80270d3d07dc2eafc946e2a42e733b9f50860fd1f63e8aad007c16e08fb5e175788271ff87e377fed6c209bd5f522f8f44b525510b682bdd47a73668ff20e7487c74616bcb7deda0403550f9bc16d7701fb1d0a13872e631317c45e1d6cd978fa3965f93f000fedd54b4d0aa8b98c5cc0bdcc5d5ab1e983919bdae7e2c123baecebad89be747c28100f701c3b3d0a20ae686d4be617215ff5f6b9c703d9cb9195e47984447f290323977886a1ae18f91d910983e1b5513ab7ec0579d06064e4432afca5c8be5649a915319fd14cf8e6ed93d409804725d81654bb70b961e650e6a3078ea53960ae13b6f1f522b8e5055b0d921cc0e724041d63854cc659a7f78fdda7bd381714a52bda8efea1cb488972d54762c1b72c87fe965bd313a6a88f6d39042024a3c8354241e795a6b2c8710c4b6d99f0c725df9dce31c230587289eeee2a705e18c3ba70885c7959c6054659660153b136f23ba10f957ea7aca56b8fbef13983b05f703bec742907a03ec0f6133e9250e72367a8db6c6f7ef6bc65f8d4583b147bd82ed359613f229a38c9a20b0fb7a8672291c3663f0bf6f9362d0dc5d160d25e5406d17522b0bc6524686f7abfa997b72d59798f8cf4c7e3b0fbd16b8596558739f8e99c86bb5dbbce20bab06922c2b720b35ca73b98cd4d01a2144e520135bc27741f470b21b77746e1d5056c8d148727f2381acfc4c1bacee906c2f30ff824b25eda9a76be695558f0b5876d51154728e050a377bf5efe7232e74b65507400fd352dbf4f72e73f1050a2eb1e3f1977267ac9d3a719783e5bfca72fdfef044ce1acdf07115d0e13f3900cd7be84a747208f7a2370b284e56ae2b0d3865bd049b6a3400a8f1eac8ac0e71656d14424e43813e7471d02a70eb29f9095c6b54491a9e97d3dd2feef4086b1efcbce14783728a502735adf04c3f6ef8e54c1e8b95fd33b986a822ed5d65a2c1f60310471d48e88fba759a2bcf010d8b4dc904b23865fcbd2967d815ec94cc6656144a384872985b2d7d0c17c4e80826c08dd88f5dd209a79c3d689741b3f3a5b69f94c9d920b8486ba3131ffeaff49f6f1131df2c15a87c6043b0628640bbf0f5d624277b729f7ec4564fb0c5fd9634adf78c09a9fa31c894b4bf11c79251eb7b9aa21637331d9ddd77b2578b6d04e61dfb8e454f15df76ff734e86c5190e9bdd91d9cfd572051bfcc8ea5e5f98c68dd0a35fde35e036aa7807044d58cc3b39597325becd2bb0730cff218aac9d9a422adca429419c1ab5f15f528c7f1beb21748a0f3b7b72718e0fc801006c4811de6157a8fdd2ec9599cd576baed356f55b13b8656c1c721b89a4bfa30a8b9ab505f4df7113e8f5814f1a3ffa4195c36055df9a5eeadb72f55eeb5d58487c1efcf69dfeca5dc99e44afd022194b323eb3d2ac9d56ced572f606f2b695adb5f89da2438ff342df5e0e5d7abbb481de5cd89c3ca9885b503f3fb002bc89bd797d4d0d571f6209e9a675798c95e7f9a5ec7178ed63b4543d6457c0725062d3445f6ee6cc1b46ed52a9017a66d64724e20522fee20d8c338272d7c7898981d13393bcaa56bf2781b1980fc6be742feefff3e8eb901f4006de722ea3128e38d91df4b39d41101eb92b311d84c792a3fb0b0cb634e32f4b2b5472579fffcc21b4c59489051e2c5cdbbd004b51c1adba66cd58e3ef92afa5eeb6723624044972992bef1004a72341482b568789628e66fef2c26f5576e05f6b1269d0aeb982a7b9873fade05352c0107b36cb493cdc61f3d4138c744af14abb7c20e70362c7e04e59f6b40a17739a6eb457a983cf95e7886db7ec242737e9880c720d28a71f3dee3d2a08076613c3e1183762b31dd9733d1ea60c9f7ecf59f4ef72d49bdd53ca2c1462a5b0a2a88bbf76ce2d98a858a88ee94f941b52c89f7f6e729428eda343e7eba4d5cb46a26fa2d6008b8339afd0725c42358d0fab91e5ab72116c2004acb566268e46cc5ddd31cfc89f99ba6f4a9ed9908389a7e5d2d34957e79c3787e48c10548c729e41f71988cffec653bcef64dca6a66dda5ad92fef72edaab0a9a3a17ca87700df88f5178b9f0bae43cda13ee19833b137657138c71840eff8245e40f67552583c707263842e822aafade9d301cb8ca921ae4ccb75726a4b14a1e6f7bdf5efa5de196125a3f90a9ccaccd2ccf0a9f5565712fced39723162abc77982ecc7ea6269bfc7c66a97e732cdb7db5c90180c68f6dd0aedd7282b63b445b38ab11465c2a37bd6870bb844de844250614e0b57e5569bc136085a85767bbddedf9951e8bfa5609b2cc1fba09a75a936503b847a04271d6721e67241326e84129af0e61870efa9be678805b7c67a7b36194303c0d8a7c0b40e8b6b20d0d3c938d99d07f26e74ee32b4206725e8738c914a192f997010f4019afe56d0f044517b2ec41f9b7b1a51e6e4944fe3dcd094286693e9df25a125883aed7243f8b86a5d65d065810164cd1dce869b209b3d32d40bb05bae079cfd5ac86d12b75e0cb3e54432f52bfa34b495fe49ab32c1c473e41b38f649dda50effca2c32ad4f9f079bc4cf5f2406b9b4a23922ebdade5b2edb967ee69c55cdb08ad638728d084a1948ff40390f4f647373aaa14395d126931d8883361ff31a0062bb222e719a6fc286d4720db46f44fb1b1d96c935ff171183721b589206d0ed1c150360a57a9786214cc297a6136694daeddec7b6ef73ba7c7462e4b99f5974b8436761a10dcb7b09cc876e838ee04595746a1aa5db3ce8662bc78104133270f600dc1acc3b4e46ffe039a34942fa51965b81080fd6e0750d0e49420e1a587d4b2011721277695fa32de224e98eeb6bcefe73b7379156d09ba87248a569bbd48e062b72cdba2b457a20efcd4215223e71872524af53b7fd49b473faa1f82ae6310bce43b9101484c6fab0263d4df62ebcddae596eb2309e21f49de42b1ad18d731c0372152be9ca0fd88b612a45260b47680d67626b70f809e2fd213127f240c5ac894d3a6061d685e39d429c4e21123b03757c5d78c433e8906355a8eb27f90162527260e38ecc8ab635ee6115e252f9d428c15ea42fad60fb897f63049fe8559329125ce0436c7df31cca3d883f6c5774bebb9d3ad47bc6fda5fd6b57d719fe66863f69534a7b8a1409df344c9c8d2d69f25377613958a5b6767a3165d67a58fc8c67a8aae8eef1dc9ba3abea9900d5f8a71c5818abd92b7352085e263c8ca3a4b3161c279513ceaac5519551d204a57f92b013c75ac8c3ce070a812fd2147301396a40cf17e95b8bd0511a64f325291c7fccb84066a44db6b9c27edfc560ecfca536686e10e80bc9902f6b322dd75e68d8a7831c1620998762b937d9f49ec5451f1ac8ae6de2c1b35ecd70672171ed945c0f06529adacef3364e05e2f05c0b4df072fe5b917af8a648b8c0bbd36533e308cdd7de52816424dd805024e42a4309c50a6d6ced6d6f0c7741c0965f2d527214491cf37e7229c6e523ac1ac06f29bb3572b329ba1b97a10b1b59bdc980dae48a74cce129316c77d809d8f74fb95b921272751bae0ae5a04033b3d947ccbcf805838b55418745d721cb2bca5e35bf66ea43c5bfda4043cd052329b1178f7b8fa88de1bea134c55cad4acf0c8eaa20377572717a2e45087e57fce9577baf547adb31fdc08354af6d82817f6841390984d21e114378bb0bee3ff4c477123b4848e13d14bb599551f433d429584efa1a4beb64bb21f39bfa684b136a9a2827cde938ebd9abb387aa4a2530339753c3872d10727b8ac087ad4b80e6a088cdb82b7fcae36e3b2cce158f54f1aefe5d245f5e5272c71e6d4c37cf4f87c912541bda7645ba4e18801af3a46c567b4a263971615172626b3d861882c286a80df35173335aff3948ebb246c2fdc8d46af0a3fae89b1089ba85e882a81c1df45adf7a046ec6cef601f5bdbed6d6574656b8b47d73ec39f296969a7470fbba5e3a63bb45d84e78f7969a24cef4a6eea2b60cc569253672d3be32310c803b171e35ca834f565d4ec4986d964fc99e8decb437952b91da722f83b5ee5be30feeafef3b0670ddf7cd64d8a23196b69db3cd7f4aaafd0b6a64238f4068efa327710e2e6e50ca354ac5f04e9f3a6d78314012c660fd31c1c372cdaf7be551534d289745b2f07b68bf7376b2b4c69113ed80968acfcb8606354265258eb296036e23fe78767af3d427423e348419c01aeaee155cd4e33e4b56729c4473dbdc40ae91fd39f9af46a0344158f8692df4671cd57527912f9f5344725087aa343eee1d55f76442bbbeb42c452d0c777be0d7b6ac5d7596adea3f5772748c73d288e1247d8338633b04027f91171997e298e1cb5083c8c4ffce65c37287a277f0258705bf3a8ade10ef9b310fdbd49431d0efcfab12c80a1b84ac863a2fc6b89e442bec2178d60ffd87ad6ff3e4e62055419b1a7eb4f9884be6d8947216137705d85790d868b533b13fd3c3f88190da05e4358ca0dd044f92fc52020e4770c4f302170d5fb46729e5c86bd2321e697e1f096676dbe82a98db4a676b72f5f447a24e676e1ae9b772cf712048b7824af3c9ae74bf903c633c638bad54729da0290537e6e18317f61b6f3fb49aa1ae1995c616b6c56ee7964395b90b7f727c5e8202ff5f0e38c62c67de6b0988dc1c4088a7ce3307322ced74677840bf7222e1e99fb0aa9fa0595887f73465bae4bd98b825ef329ac41028d989c081023234f124a20e46806c737231139e5b623494e7263ffc6d66e5f9fb7958f8cb9272b5889d73c9ce24951eab88ff5c985c8a8da978e415045dbc2043d5fa81f0eb35902076e2923e58b1daafad320fd5fd3eb6cac73bd53dac982bbdda78b583ed67f4957fccc5a295e469cd3c3078f38997629fd39e6141b4bb562fdbfa8bcffd652c6be452d40c24233455f296fd052529aadfda99461cb63adef0e735515e367276df6ea2c772b660698e3049bd0a5fc49278d603c80c1b9923aebe66e60c1072ccb3066be74c7a91eac89a54b28307219048d30b0887fea22c614f1e9a2e1635156504b459a8347e1ccc7e958ea0f91bbe2e2735db26f3fabe93e5ed4b68fa52422b6b2e08a13c2598644ca41af68fbae82987a11d78dd9c78b3e0df5adae17215f677fca3652e7d40df960e384a41a702c42bdcbc806afbdd8ac501a1c9dc7249bf36f7aebfa1297d8773688fc9fed4d5a52ac836f6c1305e955554eaac007232ed7ffdff83b14536b26f4061580e55d048c805f347d15b84e67630dbbe2a7277b4dce2b0b8281b11521d463e754861bbcd998dad47d0b4e25f2ef4df529a57a1225ea1d7e810cf6bca835d5825987b300e8acf17032001595f05859aeae272536faa14f0d4fb261be1bf8fc63d7f7ca31f3652ce864aa762e4fdf653687731083700128546dc3c50ad1593926d7f75e3b49b0356d6dd3ee24f9989188ab872e13b8e232fe68e37dd9b0682f6d946f755b0580670a52ef0a649a8c90a3a930771fb3a8017de17a8e086740cffec07e63234e6e9e6acb9eb67ba1f6f3836a35d14f1a1b05bee5a5637c23cd1543c76c90c612a7a20bebfdb74b7966083fe6a724292c606b4e5bdf0fe2280dcfb85dbf252e720ad43599d24e5cb1b0485ba490d673b65bf52ebfd2a14a221a23df35b0b186b04347c7f33a8150cfdaf0d17c40051fd6f50560c494c38465ed5bfdbddf1a2d29d2b88f13fdb50012858c053ae2bf38797e025762166c964cdbc5b96cfe758755f6f8a9ff249861144be1a316c6f70ff7ac8741f06bded14e1fce54fba39790374d10367597483f27b442bbe1a72f7d0d845d87612fb649eae223bf94806f10387e41dc82fe354c58014e3a4e072d6dbd21e142fddb3e246b7ea8eebee18b6dab614ea560e92add58ac76ea6d352bee373afd1a0eb8972ff7dce68fdf98fb5d25bcb6da582ca2df6681dca1b206f60c33ddd1096289802605fdc5c33af8f06847c8556bd8a65e3ffaef54c16f47211792dcfa85a956ed6228c1d705f999ca7994dba32f0b5741d7f5716a619d372f2c450e51ee051556b77227419381dc88f81c44d18e031f36ddd5cfed23cd57265486c6bd1edf5d79f23499d7fa00feb7d2523b56825ffa83a7737b2b9411a65dbf8753f8857a183ce995e9abe8cd6fb9b4eb6645687529454469c10b491a972383a5b263415dee6bd169921251d13d460cd19f00447f68765cad08c8e81967248f52659ddec63961e9b18b811811b5e44d2ebf9f9c4de75da2645f322fb8f7207203cbb42f8d2b90439c939ef09d4cc5018a15e4fb66fad22bb69b9edd3cb727f709331161d99a67b96f4b7b309f3810e45011858c97b08d5eb014d0f3be8724a193f5bc1a5a9e00e14ae662673551339fba533e7428261fb80ab9a33058e72a6b748e9d6b30586d1dc34220f24ad081adcc5c510f0e7deffc14464c51ae472f725e2ecce0ca83ebb81fae16e7c4cd9587e881880d0f486f7827fb066114a722ea277eed51c22ba2744b65e4c21f2f35a78a02a795e76a0cc4ea8b4960313724563cca4152d083a2ba42a18057a33012053325e95ef7fd01b5ea7d6e0d7fd3005b2f295837f7b1294397c6d188167e75d1158fa5ee93ea62d60f02133e3b872b1c0e48c507dc9d8041d261af4a722376ad003979a7d49f622f8a5114d33a672cf2c3c25065826726aa840aa71a759351a767f476d12a768d8b10839c8c14072b386afb40e456e19f2f85ed62b82f05a634cb6861387ea2109aa2dc861d99472f99ac356c23491064134ad4453f36ade5f44d39088d767c1b2ff123707b64c7208838b3f88319bd70760250c8dee26e696cd61c2fc7a27e3b6b40a595ac5e572528e439cbe72087983adcb97a2778ad201ed36a40b33b82569548d16a6de74720eac51e63d0438d6d7b9b4935e46c5c25a2b0809fd575340975dbe340356eb25ae809ce63c11a318ed3ced6a96f9e65c4ee61ae086ef08345202039a8bf56772c90fa948ce7916ef2c5d68b5a2a3310ce574731ada0f99cda165e231294af149d861d6af07cb46649fc18414f66aab433e7b591859d23853adfab0367a398772b363cdf4df55b8086dcb8bd9bbf0bfebf6e3cf499c570705b957ef70ed5bfb7228479dd677c9f52d727d49daddfda0d254fd8b953bab51d7213741b3a1be70725da22f3b2916c995d1c8f5e777abb841a96788c1f0577978a301ee705df2bc6e79c818039747c3f58325b116372ffd9054adf49239886bf5ec796f56c96abe72cf33ccc70c15c1e677cf4dec8f6e92499ce4a4bf21563d2affc55069457a1672b0e0c08d3136db1f5f975cf78a8c91aaee87537be191e6aed074181914ac77725591979956091304717f946c1b9acfca1d110f7103089e50fc72520a289e87092f41911dc014cc5f5c64c49ecc9af5d01e4dc1f56007cf89149d1acb2870a372f4cce0ed12af2d8687effc3d1bbb0709a543489dfb380ee8aca50db3ec4f91723ba8d89e7c050c6bc9870b6546d00befecc893ee2fce87fb59e38bb98b011b1e8abec873bcb539b1017229bd3f3a05b0bf19687503412f420171a7fb3b692e5ca6abbcd054833997ba59786cd1d0c58e40586d099f8bcb306b1daf9dad4cd71ef10d2c4b9e4816160e36e244daeb16eabcea80bf6ce4ee42983f7cc5632e201d11fc53b7312b92f5e4b54480a60e2d76174e7f568492d757f3a7ea31d8101272f6213b6024d28a4b4434516677d03ac6a47d1056b348f14a9a40516c08f57f3f701626f884ba2c21b03cdc8348a40ad724487b59c80f5575224e86464451fe72d99290bbb14bfb8413f4252968b608afd0a5b5b51aad6a33afdbdf01fc23bc627838d83e4587ce4f9ac1a1225a4cf34adb56be8441df8459580a6dd20f4d097222501bff13f84b8e17459920feba0f9bdef1b43a0a534ea9987c527a8413ea727e2784d331beacdb71fba839fba726496d9726faa15bcfd5f1fc663b1a6c0943f6be7a6e6c3b0bb09e20aece722f4278e3973a68b45211f493d0181c04032002978816e05f5922aefd2fb4979fe31fe299c5bbb732dc666d7310394120abb24112b934e56b5363f246f35966a8e814c7faf3b0eaf293d5a349bedbf16d27c2091145d082207b5bd1b6bb6225eee985845fa9ce1cc1269ddd725d6f13ff7150729c2c3761f459cfc682c902a712539dca8fad88c9b4a767ea07f61b8ef12fd972744589c1b1ff29d1cee40f01fd03e6cdeb1ed1eb7893df1e1161e0d4b64d2372078a4d1a1cdd9f2be5edcecc9e4148c33b8f836889c8124067bebcb9bcf6767275a7103cfc4156aee048edbdbea8a584dc3163a3f17f3329754bb6de55211272dabeb282b63111958ca0f22ae37899cafd0f1b3c0ec90a206bfb6dbdb9dce801c7fc92f6e6742764d2df8c748897c7550d09349fffef9eba3eb3032a4e41037273a8e4ae04d6f5225e5589fb3553baf2a4d897524bc6a2c66abce13051004745f421627b697642aea6e1212d9480df5e95e2f8d41dd18f51e66a5fc6adf6923028f7cdb77963b68b381e6abe302e41a5e0ca5258b6d0f246f6258b4627ec2772d9ac669eeaa99e65a11ab29c1afb228d35334b0336789c040c946612617cce7277083ae8377647426bff03184b46716cb9b55515bf3ac5dc9a6e55f5f8853266a4f6066f42b5106cce880aa19ded7935652c6df02647415f383783254361e1382444d654702d8f9752e837b8c5a4fff09f1573d00204ededb8731530ff5edd726e13b6867fdf38013fb5a0d313570d4d6891756e47ca87a089f0423a0b2e24162e0bf3a7827fc61a7ae5633bbf567f07cdb2d8b71ebc74b6ee915ea8249988635259cd04bf54d6c74f363773044082552ac4fb3493422c5f0b52566c26fee0726f67e49def8a426a14c3077856375d754d8b3958fe4bb9d584853346b4b00d72bfe435dde0badf2097fed74fc0693764c978018aa4c5666be2997495e6c2107212dc217d201db85cfb1a887e258095ea885fd440946f8a303905e2cd202521724e0cf0c3b172d81dc8c1c752e788266771a82d2f01a74982724af7d888c9a27249d32c2948b485dc1465db32f7dabccb2fbd7dcf7aa46ce615044383d5882a2831cd7762c397182c245215b0346709fe6a8ec0c3fca525b88f078223e18b6635c1595b5bf036d4c6475d330c2ea0258ba476760019753b1a9cc8ca25bb625e21604ed6045ca1996ab3952fae78fb7e38505e25ca4590cb04a7d608d461a9d33614e4ec10595bfdfa8da9270281ae6431bafb5bd7a8f919d0da9776372d1d0e722b718a04bd843666ac7f2d2594b891a06dcdc88c7de1d665f93d877fdaad6b72ad16ca5ad9b7bbccab3aa02a7a4bac2a2ddb430772b2744e4f20d6303ad0c87295e080a74ec89f0b6389540743b203f7f34cd8930a426e150bef47c10ca71c3c1f4f517fd96f69bee964a94a267743ed5731abddd7a08a6ecfde0669ed97182a121f2c32d17648d56e21e40fb6ea00be5210f9dcbc981633fcfe7b317cc34f24a32d0cdb2aa296fc4fe473bcce745388815f52a8c5da8a39dc35004ff421d5727c96c60346ce9bf56b2a366e1c8f3c1dab667b2a61c168b8ecce0b8df910162c686e3dcdeaed7bef060dcbb42ec235ae31a413086e89f5b16f3cc105a2f53839e07ade93880678ca415902fb2a0f2d2c90bc4a3fe1e1a750c66b43c8526e52449eda5f5118fc3eb8870357c60ad0649cac4975b17fcd60f4efd575a1e1d3f93a237b6401d098181f9a90144d1c39525e06afbd7245e111b1e0ead371bd34e12201fb81598082b131d9abed7e4e92451b5393cc0efb060647fe83a6f781641f06fac8cc3c65998441b57b53a698682f6d35f21d88c48a26e6e6643ff5b4a4e84c7b132cbf924d436625a691ad27921818604be50740565b4b7162919513dc6e248511ec67245664d1409934cb507832d1b6aba27812e46581f55d737eeb7cb0200b5b2020bf3af20f04670feb861e332093e42f2104728952d95ade9e4962877291bf5c9614f9d0dbe8c06bcbf5c07acec1884bc8a7d3b9b1f079afc2d61d7a0ef47dc9b548452c07f2340f0086ca4a21a8287c0f9f638745f90e3a097d1f6772d5e8ab76563bb608c232afd81f8ad51eb735c0097bb0c16b2f358c7b14d2f57214cf4ac74ff028695230fdfefbffbc3d0ed207bd6db9c3c748f7dd42b237e648cc32b6a4211b6d0c418ffba1f471ffc0925c5d9ef8008ac654c357843606407277a42de1f0f83a2b384c801cc1cbe38aa6417a4071f7f45878b0c2f17a93e272bb0c7b0a3f894b79a367cc5d033447b4ab2159c21548260da59cb43144e43d72c7805d2aa9f9e715e4ba5cd218879590fa27532d80d08e471167c18e3ba7f4725b7e6560561125ba255246747fe4173f9b2dc209fb0952799af9d4b4f8b488727fec9cda9ac0731f285a2c4dcb8bc3da8bf86d16cc89b839a9318e2c854bb67203a7fb00c1f9aa0c64d466eb9bae7424af97cec02cc5fbe30e0d7a85ec15297240b1266766bf5b86de2613325d377a6864f151caa123be86ccea4acd59804272ffe900bc1e3f6419090899c8123fde53755be326a64341bc1f366659ae9efb4854207fb318a699f55aec6566f0abde198b8099345a881ee0ce1a858576c37b72063173a5a80ed914c8fa5f96e3073a1b7e1a5e7909bc297984febee6068a017236e6b45ec21edd02cde1050b918157cb09252f959a8167223b4018c2e0ff790fbe32f7c278073dc475c294b326d8d51d86b7e19c502034a54eaab3c53610e76748b10697729c82dcd7c894c40167e87508d8d4739a5d623805c02d5f9bd49b0c711ab8467f7577f56c8e78e3b8e861e353696d11da0e7f5dbb584e01f2a591727a079fdefc027e577af54665157f78743871cbda751ba1e7210293b5e0a52d72efc9b07dedfabaa2433db4b19bcda6d689ae70ec938201a03bb10388da23d972642f234593fc5f8b8ec2a6f2b29bbb6df6f75af86f1848fa432035ec8e95095b91966a9bfeffba0eff7019eb517b519d0070f90938a51efc7fb89da13ab91e64a93ed98b904b843c7ab551de484f0b94dea1fc2cb259cab1aed1f9e18595e067ecd1f579385e81811fa459c734b864f70cfb0a1bf459b91bf5f09d12362e957266c2152cbb5f5b18deb12bbb470da2251e985a29952d27eb8b5ce2856bb4ae72e9e79f84fedeeaf7a9b677252b1031eaa02da1472f67314d332757bea2db487253503c67eac0240e4857118f2390de891ddc422e3da8a2ce08a99f25ee45c172616f346d7da68ea76fc2432d1ec0099883020215b36f11ee670d9c5c00d12045659cb09079cffd1eae09ad7e9f6614c888aac9f0e5aa660bd9a08f8ce3407147bcb5a7f5c8b394f68424afd5a543009fd0665dffaa8ec1b5f263a104a90ebe2e81ad2c1f957add5054a00c39f52d2b8a1baae04053bf3ab542dc6f095d7ec2723ce560ca68c499a9a1921e579de0bbc4f428b537d3d5ed95803a07628fc59264c65d6a80b1446ae6a333a08a424634ce68c652f2f7f99bbe8ac2af4266db6d6a00a411b9edff0a14ea269536e826b87566e4a0c28fccb73b07e4040ad1766c72118a3366233c94e56b6fb69e2d8ca357543532565c77c28e5654291c560e5c03aad4b0dc32826d4e66018f31a10489f4faf1e4f540ed0d73054a2f3052346651f5b0e2188952f010df6eb015764669152fe7a1f57bd62bd4c93d22dd8f88c772b91c7e8c679f5cce401bafaa2735d3a4685bbc95f21b600762c719805a48e372f53a6f04226eddac048e619136a1d88190e17d35ba97eb6a8a30b464912d1272c1a039d08992d977296846d2f554fe2d208fdce7b4619265edbda0d8f366bd726ceb62a773608c3460121e7a040271d3664002754c5d5394dd6fe7b070a9c272c4d52ada67182acd4f368366f746f6f7cc2bf0d58997de9620770127ea5c6a723e6152321291e859b0e60b6f6edc8078a2f2550f5cbf0220ae146da7a06ddd3db054aa51b42c4586f6e4829b4ee9782586b069583a24f9b21c94480e56046972f183e383094e33f3fbc6b33c25a899fb68696c92b7de47cce43cdddda0c759725c55ea39a9f410ca0d1c6e77af2dc3c5738f474d36ca05d67e523927e2f7317267b3b5a8b25a04bd587599654aae76d6a47afb6ec30553e6c993fb8687e0fd7299de201d2236876bf7816c6633d45c962c9803b8c8ae3a66f3f6de0f46c56972c6e50edad4358514fef2f48d0b8a11723ef1ee9fc9e905e9e8dbb80d95532a72269c023c0d35eeec86fd6e66db24a4985967f8dd9619a74d5146dc2fcb2fe57201e48040807b9d6f70975d79ee5d84f9b4545676ac429fa8516e77b2af85d072c00b8b3b2818e14e53750089deebe112109feb0fec0715669961d94346f97805d95ebd6f7cc7fb6c0d58761afe31e76e071845329d1b2a2b3c1b2ed316737672d396af1bf21a38a13f38512fa2d696e3544239fb998c82ee2d4b427d2f86f9723f6ab7aaddb8b4cd20454d6fc3924d2a1466b80c00783f89a15c24e6029b171c17c1dce218c69bf06f6afad7756aa64e52c4d0093a80a313acbb9fdb873d01725650591379b2b91a0b0a632b293ab47fb48069f533c616623481a0e1d6b0af01b226dcb45e6259a72dd00ebc9447ab481f71ddd9c8d17bc6dc4e2ac75a174972a06b1272c4825161bd46eeddbc0ae11afc324486b5ebdbf5959ad3cef0764f2680ea42758cabe20efa652eeb6e27144c05d9d2fc6599f8c1ad92126f0463ce72cff4d9322190fc4f57ba535a085437b1cae88f35e293003905044a15add5d072d8266d146bdeca0eb26d63eb7e12fa847061722baab915c3bf93de567b08983f641dc6529d9f54865a3664b530e9f29a1e317063d7905e24b34f79ae59043172e770f48981f907055f9f1eedec50040d41d0c213870b32a74bcfb589c2775b576cf5d04a035120bd9256abcdd8fdfebacbcfdeef972948d814ebfe60fd551a722dde945066bffeda7ae9f3f98601ab3f4f77238abb90b5daf7191572179a4e5ef8d21de4ebaa0bc3fda105d63c79f0fc4341ee042866043278a78e4fafefe536722dfb480a1f1fe48363edadddc22536ed0eca06bd4b739f23ef3c3ee3decc2c3db250b81b42186480a94dab3d52cd4bee91eca5e215fbcccc288cc66aec1672826a158b72c43e6c43df4bf40b36d9f7b634f42e6517ac6c7023c96e4d1cc172c91156b1aac8381f79e1465e1bd8e2a9d1575d31b64a3629d3fb536ec14e9372000760afeda198cb2a4dadb0c580f89871c78f7d1fafd79737c6c32cf48b3872d0617ff077775b075c7c01ad908f17869811cab821b579bff81b8f1244095d1dfb384ff4c66b3ce2413742be125d8d93bb22c1fd2f928b07883b3319fc2ae0697b46c88c5eb179e800900a20e8630d5650d787862dc3d643691b5c700d76bb18a6b85f0519c79e6cfbfff5b1328aa6a17fe4a0091be8a66ae6ce656ce0a39272e057ef8327c3f7800ff9209872bf29ecc38fbea85987ce00a9036ee8b0341772a8cb0d1896017640e227ad31e12f35aec1d6577898d23d5c23d553f1f1b1ac72cecb44d7080648b27d3175e1619abe4c57e21d01da324a53971bcc1f8bcb5762341a5ddbc5beb2eebdff25d411e92bfd5ac2c708a30fb28af4c649a29c54d872dc91afbbd906357aa15d094bb38c4030d6bb23cad19b816e8f31dc8b6a207315799c030a52a524e5f50b3266eec7b58bb3cb2adc398fa1bbe0ca415f1901ba72822be9522ce0db2874e587e02c9b4d2cdbb19337475e9c0674f8742383853872d6f7efd73fe170f7fe7ebc7287b7d891d8c4bf159a91f648beefa061ceb8ef4ac3464ecdeea4629ad70c6459c2d8c28922beee1822fa0ffa7c215d0945ce2b72df3db50e6cee739709251bd08fd6b1f3fc51b46d64f702a71522dc8e911bdc72608f8d60bb7835238efaad40659e5a77ed393fef41bb869a7cd7c965a068c072f403a2a6f366923dd9700f1de9195b896a470733f44d0e90879df8facbd87a725b66ee6fc3c7ef098f94d3f10584c957300f778b242ac907f7ff8eb8c194814c921a20034a699a74d048db7efcbb3b2fd2d0d17419256659cc1cc46e09cc0172ec14cab8dec3f8e49aac5e922b8676466392d3067a9d833ea12fc458caab6417d5c211bf5cad018e21018dca11ebc74500f1af539f346f38141a0554abb61b19f085f5da885c0ea98a92130ca464eb2ce4cafe56b8ba20c7470c526236f2fb721cfab79c4b0fb024df3535bb4aa3b7797377730c1c24d871f6b7f4884cbde0177aae01d85d1287c371a915c04d83a18e76045ba32f3a22d4857608e257d2497268d6516a3f2aad55d542bd87c73f4bdac738582837e1ea7cbbe9dbeeac0a4a5e4108769464c912727e45baa8f324078cad6384d18c88e1907a3e8e4d67c7bc724d89167946a5c71a71d3c0688364f8a2450a0c2a43650eba6fc2e98eface60576acfa2b18382deb258f5cb8a2840895a4106f48859900633d2232143d6383372fb7d62f0beb798e241a7268b829c2f212dc95b4a8532366db0936e59d104f6723b488bedaa8212200da03a2c561e3669776917e0e9ca649fcc72b6717bd77e72e9711c45028ab2d4716d5a9fd5781361aaae775605c56b9202292ab03762ea5e184eba303733ca900338004a71fee9af1d6d8ed40b02f917889d374dc8a55c728de76c840593f6555cd47a0b441aafa65a3d90f48f9ed59297b8ed0e61de167200676af260ac8f3480bc203e6cc4f6bf126df8cf3a860d5b16416bd2ffae0972f9f13dbf41447518ff9cfdb874655b51b06d090dc9bcdde905b55c9c1e34695fe0100bac8d9c7b07d4b92d22e2a908ed331805b226d75d1ef96d45c7d36d894d0bff857e7dfd605f16529faffaa0bd4dcda892b528318bb5db0689329001fc1606cc3a4fba442670b95fb14244604d11ab5a03a34ed0349a949825140aa1e9725d5a9ca429c87a3852a286f7fb7f23767562c60e5d5758222d819dc8bbc519724dd4fe86b0d0f650e8afe9e58011c102b5086bd32dbab7e797b4a2d75f74ee722aa2928a51a3e404780060d9eed896b7baad3349929e56992d40228be67ee772dd7509ccff412d2367dabd2a4a93bf06b9c37857a96a3a88bb9d5e14b95bcd656ba759b01181f2e8ae90d679927deb1394666b8355932b954cf709681d12c472520620e0e96a3b8fec2c2a865be4a83ed3cf0a0b2574cc838c127f8b63b1fd725419c80c037bcd0188664d9d2896cd686e84f556319efbb2b91d1d355efb53652161020a4848a9de9c894dfbc48adf81acd960407eb154dd7001e7fa8f1c5e3b5c1cfe5faf6726f041d3bd7adbe190ec264bfd96054791084bd52626a3380a6fe5cc79a7fd613960a576d31aa1ab892ae59a48520d4d15775c2a819146fb4618e444a0b24331904680faa36ad82e251e20621e6a95ead13c614a0bf94f4cd6644d9a7bbcb2b721972a28e2657221bb19d2602ede5592d7dbf696e27037a124171b0f08c9cf0d86075382ee7732c8059136cbb0e5a4be5f85f09cdb3d4d2da44313460ba468740fe4161e616f95adc7792f64c88122941e9780022f8c30a67e728717f9ad6b536f79a0a48da2c949b01feb1a04361a47cf90498f599b56dcb91b32d310809feb2421a07f0686b779bf42e7fdb0688d078f69b814b1554c8391727ea6b0b3565c48b83e22e87f7de428df4e7b9576c2a93e5c1806e62a98db117252d98ead8bada5e03f14d643e0981b5033559374ced0956e96b0666e82ee2072cfaefda8b7073ab46b120ce5ae1276a230a445c858ce231a3eb57e043580637275f2abe53d464265e729c4bc85d2ad82be2b3fc2ba6dd6cfd2b62ac39875107220c835687ff69d92d2e67b1873d2d78c9d9fb87dd8c7b53d175284b35c9a627283c1a193fb2e782583071400995aa35719d5cafeb05c7ceb293df169ea92006d031bfed90a140e192062c82027995c61dd111072f15de465843b4a8932d1ae030e122523113df04d2b0e43f876303c6dfd44e3c27ebe1643e011f40dde7c3b7204a0dc7053465c599654b80b6946b15085325fb5b36c2f383fae4cd470a7d57213123a3fdcee8195504ff8ca6a01b6a2e43f26c62d1349fb386c1f5c5a6eff72843ce4e8b098c6c2b125f01fddec7a76d07ec9d39e8b4ae3b5a3ff38a888cf7269bcaeccdf8ce8504040c18a8c0be83fab3668e7b81d77cc97bd3c93d88cea72cebb7b86fb770981810b4c7847dc4db5818e4f78014ce1ec22b6d29df018b4399103e3fece09e18bfff2890f0189a96d7ed559db4fdc1771b1b6d819093ac6214d1965b97a787abac4091d108f1e09dd6949c9d94daea4b85e7725fbc7d2ff72e4b9d13b798d4be045fbf5b9d8c9e90ecb50646e1f78609aca1ed1183942f12f4e1fa12f018fcc6318bc7209f1feda25077fef72f1a12a38de2072e01a7c52332a350db04691e56a874a010625b3347364065b9b6c56040a9f57044fd8ac7972f701ab65df02258c7e089c4a228a542afefd0dc502eeeb0fb0ec6a05a16a6a5bced8b237083ebc42330bd530b8e6c5c843f7fb38391287b10e69643f4744c30937457fa2e4774d1846dda8e09393d4d0d00a9a7b5bc92804d5548d0f04db76722fb8938938215c6cca75e9b034e676b3fca1e83ced22a7e69e0a7ad5ecc94a729d7558e676a47ee316c8f89c55104b1fe87489b19e977ab09ce9818b07fe80403e83b63199d451e2e1bc1b746300f73a092ad2ae8866947a8164a2fb4d77497215efb2844315ac8847cd6a65b5e99ae5e7d148ef9a6c2c621fee13705037e30a01f79f69f5bc54dd365aef655b601a9f8daae43bdaba9f8539be1a5207c8f8146bf5c881cb9fdae8258c820dfd49bca8966c0da1058c01d6a898f7b10b93dc7219d7eecb17a8a464f487f6cacf2d0ceeea968aaee89869322c871b456b57803dac442ed07fae6d8834528fc1aaa328530621be4ea87015d7dc21eb6134e08272fb9d01f5e12f2862b9a28c6565889961328a284b31811c0b460a987410cddc66fc70e2adb74e07cc1fe1a59a168199038b9ba331ead15a6b151deee3c9639c214cefddcb13b32311dbeeb9482e553cbdfbdaec1345ddbb4efb5ef27016b01c72d4839f40e27e5c56cd877c42f91e57276691720a105ed6a9d4b2b0a30b2df272060119fe0e900cddfeeea38ccd7ffb8f0d07b3f94589d86ecb458c8da74a145e51726dc1bbb931b5c97019bddedc790d31cdf0bcd308ae5e89373eb1af440109f39c82d370df6eacd98d978ee1e372c46a5795e81ec557866fc27f9520ee61729a3564b008c7e0df451092d270794aad79a2c084dad9886aeb8f8804b71bc33f4ca3dd4f09a25b48bb7c25250060393095ef0315a5920fdfab182b00591961722646090963002296616d1be98e013eb77cace51348cffa4d61d89b3688f9086a8475e460f36fa4e9f7b47fe640487078f58952bd6a735d0fa0bd87c9de5e4372e3e0006e51620491ffa9995a4efab0eec7bea4ebfda0ebf2f106b0e13f5de8721337550dfca90ec7edc7c2fd1563c1ac7b73fd121113a419b299c8462635c45d62f2a36bc2d00348e1961a0ad1ae2c474dd5fcee61c8de8b9bfd900f0a81ae72e18c556def814d16d51161c9a218b720ed1b0410a276239e23b14cb3e6c3735015d447d0b7ee0046c53c8f5e046bf9cb13e8a953cf59a7eeeaa246ccc090b6723db937505daf8e953ed1ec8582e0bf2f9679cf4b27f7bf04716ce8c840c9613196645b878e31142fc4042da9b9780f9e8b2511fc815e848cc55d905072c095287ed7fcfdda1d8b2a062a8858ff6e77c4fae311fadefba22f3ab452934efc3672fe348b68a0ed309cd55114ac0cd8a32f07b885aaf7a225607edd93588a34db72f609fe82b63b37cf54ff137e6329ab11ce74d779d20cc68302f53801c862955fd5eadbfc256b9f2d18e298c7ca6e4a1e7ea1800f512d6910f4531c3526ef50725a3e111f0e616d63fdd96950e233c34b2766ea71f27b4ca77672f223bc7f636331747c05f81e6425c93b65f95367a76ea6a268421e8f07edc0d9303ec9dadd24072aa681091d2ff6424170382adb60c1145da7ac3794f0d933ed4d1e05e98f0ff510b510609cc9c829ba97b1ec7fe9c33abb3eb8058b08829a78a871d3f3c572f5afe9963f9e37cd59bda40a92f6c514861196b6c71457d81f91a82e1877df722982ed428e5d0ab7342f04a3c6a5a053af5e339338bea913cd11c1889f3ed84ae51ec256c4e631de656be4f07a1d417d00f8d6a80998e7bda9dfc75573568214eb85af463cf1eee2cffc68b52eb3354d9c0d10b8197a46ab68e22c5e92a1e6729d812c21b5ed6f91710ccfc69b9e50ad02915104c7102ed0819188cf7260337207586ad49320514585120811b0fc226c4dd9b3246e89073966910988c4e5792146dd477fdeb797a111374889af79e93c484222ff01d495b1e5943ed2ce259772be76de31b94cf09e529a5de8e5c89d519c1e3f69beb1a300432a0bfc74582a5d047e0bec62f486222cbed59f02fe69e4fca745bb539cbf98a6a8462ff17016366361475e015ea83a0bc873e763629b8321e532bf26f92fccc1dac5b796a5da224106f0f18f2f9c23251531aa71fb52907bc42302f5c637783f72153c79eef728d836a10d95b494ebf86a61c8f3269b63cef70d0c8c221f84a3ada0f5d9352c72a31eacd010797bed738f4836da1531b9277fae74ab6c96386312c61d39da3d42683e27f5a37d898141d11ef25d078095da6c2ee5f94eefb86183b728ce959e72b60261114721edf43353907d109bbbdc1b168f10490a828e3951a04fcdaa6d08fbf695498510c30013056a1a7fdb3022a77207ff04f156c5d3901f742c0f3836952af17c9c9be81ebe68a07a9ad2ef50660d9774d5d87cbdae8c6cebe20c27503739e8363479ba74759686a1091d00db7f23c13bbb96d11e7dc720c71396d472916da15fc5a1ed985b611b761f0f54aa1e21fd5b4c8cf2b4b0318de89d684a53a4c905d7dfd0079e57831246314afa68b67a6967a8334bed0ff89d6cfba33e72e541e81be3ccc45ff78b5a98972803ca3be872ee402b36fa3f2269369588f765621117c50ef3415f0229a4cf9d61e378f2e5c0d287f6cb25103c23772f10c27218592ee55a7c1c5195f5c860e3214007d4ed078cbe1d5d436dc8647de6d5cc0ee180c14899c4b952cb2ec7c848a33b80485b8acfd1c7f1bceae89effc7aad20107343ac2c7c75a330b43f1e0b6e5979cba129be434952ab2d765e76863c38c7270c54d11fa75425e798ba158aae97e420985c215759ab77fe34a158fe756066879758100ba8ad3f9bb52fe90825e69d17e891dcfd1bd52932421f481abd857729def276e4706eaacd1cca4292adfa19ee60cf85c9441623101ad676038213a7258d8bdf278d1a2196e5252895cdc461139bc5af0c567177e9ed7ad2729f393213bbc280fa0259d498a17ca281cda56dd179012cc234318335192c1537ce77b72c8f1412775a657c31aed4a23c850277ce8bd832d608bc1458076a01e7c94a172ae787d5f536e9a771f7afcdff9e1c76e37789f20ef230c4e0f31e17dd1106e13dbc1221a0f79498445dc0cd8c80267b67459405ba307118131f3b20ca3bc6119b9e5aa99313929289eedb826772c5c31f85a1581c4a967d97511ad77afa7320d6753bcc4a729f1b6a921aff886a21b7c234db363a9673b7032f55b63e120d3342ee201da1d63b9562c81cbaec3a4291bce714ff37fa3780984e21cd9b08e55184803b4ad738af80723c810d8127467af8d963e6f26c87618382ffbc204cccf7278fece32db8bfe6c50e885f6992bacd955457b6c17dbc2bfcf76a6d04b00dc729ad6d68eb1760187b8baef1d70e7418de1499f97e914d4167f0f92ab61f6bf312b5703fbe5743f017b72a227425103af0eb6cd36d96207969478a9e2d2e0343137cd9d0ed9379484efcd1c060c1f9080dd2d3f0f2ae24021a6d6d9401c9b1072908ed41df772c2e31b90fe4ae39ea393293c6840f4ae423f699aa916fb08ec2442499082844463508e8b11c47543de32f5c5399ca408bf3dd84b606dd1335f1ecb3219af962950cc233c138db955aef4065ec43a367b1f0dcf5c1c3d567e1c72361479e143b3e7fc5a517886fed7d0b28a2560f4740476e7fd710c24a8256072aa53b92042ebc55557c2fa09ae68881d74c2df1a08b60ed13e6cb5ea6744b772b2f75235aee5c220c8f9b01fb655873e86e05662c0311e2fc992a3d9998d4272bde8fbba8ed24e254b4ef8821871e7424f7da976e27019fe64839aab4b7ceb72d69ac70aeace604332a846fcfc84001259c763f7e678420f488bda4f4771b068574fb26b12f185d86bc8126628861bf3a69845c6196c2df405f5dc8d20370072f739e9fe53dc703dd56b3bbac274469ae3580a51a66186dc6005cdde00d661121df1b13f9f871cbf149ac1773c45faa57c27de9aa61da8d220245e73c723564d84313327b9651bd5a133d858a19d5ec604cd2be0d109e6edc74e21a1977363726f69f31f9163578ff32b74045fbed97d5fd0b73468686e746f44b1b0d368d368dd730bc66dd0044b93964ec1a65c60c42212c078be250eaaf5939f970632160bda4b2c548c77a0f9e5366126498fc903d493e06e0f7262a19e2bf7d99173b372ccf128804a69e84e3489ce95ad912925a71a9dc1b53e98f4e95a028141e38b72447b03b773f2d37c93731b7dd165e49bf3030ff52ab8f02c992f6bc7ff1bba721d7681c3dca97d8fe2e7a1a1fc8d53ad586a0a1697bfcff84130e2b04abbcd727dcd2b670e3b5b8d9722341b056f20b551e3e4cb1f026a021e1697c3246ff07215c743ccbfd5b30abb12e045e7f2a1e78dc4fdbd595b82412f0a6e687b9d2355411935a093ca86c31499844ab0d0b0357444c4936663debf1832b5b50a5aaf727f79d511b08e8389a69996bcede93d8eb72451518619e8925b551e43d8cf734a3a60e25ed1b7dbcaf2e7773e8893d7d90de15c159389b2df5e3ce5a25b9644577bfc53b2edb99cb9d83a11a650fecd61dfab07f1725cbc80bb8eb058f57e7519421fcc5ca1f7c13a43f20878f9d54a3d5410be4d7b4a5d560e05bc134d9dd77264db29ee7906df06b3b9efecc6842c3b96ffc1f50e10490d623670f65dc3fb7266060a0d5bdb6f3d8a5b9d5128791a61e1d9f3f87204539bcf3153873fe0bc720edb78e0db49d66cf271aebc480d5e067eb309086aaea0a3ad3739866b38f45819ea43424406dd5aa30c24f26e6d0c2bdbb0a9d5b6894af30526269f1749776798b182ff71c129cbd7ffaa1557864e63fda928d74879472b35109d8f3b106f7295dae7345f4fe6a45b4851525a57186e0714aea3a3c0af91c7ab445a6bc3e94e12d06aef75fcfd78203c1b25d3b133dc3c74671fb4cabf11d16fce785cd9bd72e0dad3de8e58c79206e54556405904d30c06cf64e841ce8666edb043df527c724515fa490506e1d9a77d8b9c2f50233149c1507f3107b4f06d91da7cb0b3f57223fe2efd1b949d4205501ba0fdaaf140167d4262c20299a426b00d7402a8fa7224f63261963e92f91c3af51d1537818fcd6addbbe16a417e49e4f2d009ff7072b75416ab06cfb3186200ddc17156f73004ba56aa217d24043a6fa1e0f6bd355f8c7af170b1c3247d6538de9bb41c09d3f362fc6ad26dcb7d6a898d7de907316ee499cc163be34270d1a1fe006bc29bf3193258e541f16eba87287d3b2b76d4722483169e211a1d0eb4505e21e0315090c1f02364fd16cd6d0992ab15e85e38725374f9c461808f4e11334ea116cc18f3406cfe7412b640cc89bfed2e5a51fa72f650e6d301f70daa92939b5061693587a17ee1a9d724c7838951b0f0d912887222742a73b2b03de8ffc83856a8d73f277475ea3fe0d879d61f7187ffc72ff972f380f5f7e0ceac4d5d8afb6b5a3dfefdc3068845c56c5d2b5047882916335854655a70fe7b5eacd2b4ab8118692fcecb403dda1dd878bc6c7b93e06d5a6e1a72a70336ae2571b221e9344d109225efd0d6a3570eebc0087037b5ce5c5edb6c472c25182b5b48c4cd3defdd2f5fa32b892457a0e1ea27f703a7270e27e690fb723fd88110b1b20f2c1c5aaf78aaca5d7e9a1c95505ef2d490b1f960fe2b881b72fd8351393e707b2b24e49b953327310ff15c71894ec64856a8f302c120b9d2728ebdf3611e9277a4eea9c0aeddaa8d8e13b1da14a8e92a54af2f9aa57a793a7201130902f036d85b64e4cdd810548e6044c4cf9fa8601f9168c91599b1a957727b464221fcd2052f4aec923fe1c164a64b2d750f8ecaf5cf3b0045ae43b6d7727a29339932a8c73d8101edf84b810c2ed484594efd3f4d935deeb0c21074da7245d859e1a207e8d7af5770df8f21efba1b513b1dc23ce3a88a995c71ba197572ebf0fb8a0c71f9502ca0710ac0843919962c0c69f42d7f5de9105829da84db72edde3e1eec11762ed43c2d51d4a51eb35e73227b5ebdbcc6e4e5a87497055e7287b1dfd4adbc1cd121ff6eba7e27f8903bd1fd49c504ec2f339a56cbe8d5223852df003d6b960245d6c6ed527782938234c8e0d8bff638f30f3180f52561647209e1f7b4afa0d680f9d73e1c3bf5a69e71430ebfa97608da12540a7f1ce39d678a6078b528ed90318b744e28857c6d88b88bd4a5f9fdc936cb95feb5986bfe2ab8621afffa087be9f83da65d647b391e8f445ba5a3db402e305b9f172919841b3babf32c18a034cc4fafbdcdc0221f9723da919c23b2483229be5d48423e5a1468c5fedb3c16a4c1800f9e088fcaccf8c54543e3f8ed68c6d91592ee77b38e710bd0920f70013ea1cde8d43781e01fc8dff0ee972081f414901794900c79635f590438f622cb1554d9a660f2c7ecdee25cf40f08a83453921253aff85e239f7274b21e517861dc331d4d57c44820bc1bf4e94e28771c547368d56691ce0aa628faba6662276a40a23d822d47a8f3ac9f1f139cb679cfeaab505ac44de38ba0720cef2ab8f43d03c10d861c39d29a9851a929c8c6574a22ad74daab33bb878037f0b2c9c5647218c4dfa549025f3242ae2ad3ee0da6a2a7eff50ff354cbfd2616d685814374f121eb7213612e41df1b637ec660febd9eb9584e4d8d1f25c5fb723d536f51dc54de9a0be76f43fd4d5cd00a4ff18e2fdb5ae1c9aaf47eb6f3c337a3aeb416f4f2fc7b894fe8af01d670d8b471cd9d7a855eb37f5c26392fd67c728128319c75f128e584cace811c948959f1336c8356e556f383635e914468eb6ca1494d6b041748a7a2da3832e4732cc99f3cc86ac1edfeb4b971b6d581b40072d514647e5bd4cc07a7893f77d78e75c13e414736a69ba01fedb4db38eb426e72e8e20b88d3591cdc8f5fa1cc127db989f233cb2efc4097c9e7b60d029867ec7210959162a8750a1d6ddaf52ce47b9958162ab2013a62530d12d6287747c27272b5140ff250267a26ce833dee190d44d6c4dbcd764e0f2f19873cf13dae4807467987b69d108604e314cc6c99fc03d3f0d8d6e9007ef6be7cdabcb73e27b86d14c86f4f1c3cb047b0af6451e409bd7eeb0bc052db656da0c894e9ba98aec44372e0e090ef13cb5897b6bf1ec5fbcf27cc99f36d36f0da5f2cfeb3ebc21e5212679e2a7dd24ed0ab6946192b37cd5f1666016886ef5329b369a7d27c8c08be58509045a16803406a51dfc5a5177ae126238716a4943a0f05b377c02c674681f606b6e4039fcd30887f641ed0f05dde84486750992eda6982ac001229d1d7a6572db224fc392a35b9f70c776744f5e60df954f13b1d52dcf7c23b821ea757645172aaa755a6f30ab192f4823c8227848d1886693177ebf165efd48afe5a5c266b3a0f7f1607a9fdfbd171c193ffe6eb5efc19f88af24f35b5a0c9a3581e9819ae72abc475b936e40758cfb1a37720b53755f1f5c39b5b45c5a564a8e78dbe36df2c42af431bc75334a42c6ea275fe56dda078fa1a29049aa4e5c2cd95e2148f287204dccbbbc2cc00d116b52c32a0912a64aab41682e2a144e28d7e80aa5bcaf1724ccd0040511505e4f718dd1d50a61bd63dde7d03d7e1c426a745102beb226672bf0719de9302fae962b90a4ca783a021b7bf3970cfb9b15465a8d03872988f6c00e87bfe9f1aa7249173acfa4043ff301bced2cf78089d203c512a7920634d03e1da75e434beb595692f7f17ca1cef1bdca0c09d521a71cc972b265c9c75be72e19120b404ec86b2d594ef7d3a42a284892e4b6a747ed32db612a43d61f9b37221f5ff173762e98f645dc617919be6a7165579cfc315cb7fad31b1f5bf1c4e72a29fef5a342d096844b862b6aaddca5edc611b89f3b44d202aeb02e8ddcccb72642dfe420b659d8a04bf42ab095324a83a0999668aa756b0b8371b7523e7cd72362aa6e5e2ebe473f9a18f307b131d61d632c2f09aa2fbb89f96275fe7ed0c118b6dd0c8afee15ad2e15c467d60e7d9029fd3408170ee39d5d06063a3ac7366d33b42503971a64dc8fb2ffa6c468fe4079fa8f66fcf13ebf91d1647c4a6a7872171ac4a36e48d28cf0052f64d94253d366a1bd3e13ee951a10ca81d383ff2f72d0281c56fec850279490184752697422e2e739a38970550f568076e7adf7737244c95c8714937f0176fb78a78042639daff15fa1a3331075f938ca29335b0314820de5ca039d154bf5e6ff5b42176d756cfb6327a2d26c9e1e4d7ed13e41fe7284dcd5204a879707d1367f84afd22a3c73b8921cece720d8dd0412463008362b66082070f2f93317c13a6128bd10fd91da29fcfb4c7e5f838b446be56e330908c4d61164546ae28f6ab05bb632d4ba8910ba4c6b3e98061141fc6a451f83d853ecbd18d6cb2df2bde68e1e75b6aefc97f23eea3e583becea7e282c3c1f3470138640ffe061a33d5ac59a3e5171d07a3f2d34c0aef5bbf3b3c4a56b075a2e6a720c5e3c8f97a0b710300a66e4d7b691121fa08b754ac2e4e6d2993c1457c9306f29ecea583e7ad6e71537128e3f769f7afbea3f0566a8e23df704fe4253903872a68f379fdef794f643b052d4366c3ef7a294c10b641335e7b67bd78862f0e072f07286e8d7d264f7cafffce0bbc5f330175db9f9b3b664a837ae95f17c57067283af6f370fca84071f341089864484faeae7dbcc63c6d711fe21d1c11ca6d472e0d5b5b492bde0f430f4987764f4a9ef6554c985d0060b35f98261f644905a72d575bec628741d3ff23a023f5adb8c7fd25248bbefbc21298a21fbd65c69671186c1e14cbff70b8da37b4c1f6b8f85d5b35d095f607920a51ecf5bc3cc43ec01806779d50455be3620910baee555492169a3be202c8f3374c0604026d0b6fc5384ac317701d74f3a92d1a4016bc2c40779acbc6258bc1f064beb35c49745714a1f241cd6eac70f17e9b37fa116211c5eb689a907f97daa385c02031ef7456c147f218a1dbb69ff108203469c75d6c923f3771de7d09e8b350eac07e07e6c4e1509dd37de071d3940f1039d86e90cb7ae7d0abcbe7efca43c93c0d6b7b7e960400b220e782706b38fc9984795ea2212ef911f11168bd5ae35d3f1c4f519091a729de95dc0b25c907980ffcb634d0c92ab802302ce0d5bc1d51319f1423bb585724b2d275691b3fdd57b67b1d572de2b6e4279405c2800c4344fe8b9e4105bc14383e94da4d035e29da5c8ac349339936f59bc94a0dcaaff20212709a295e9e37274310786d31c5ea84435d169f3ccc9636263140d313e530c6add23f21da87e721d1598526007f51ca1943615dbf91f3dae106184b1741eabf94208f2f41d04113e8e6c299b33cb0bc5c760be47798558f33c1f3836f85f321f96226fbb3740427426a378d98b05e8c4bddaf10ab682a9e5017efb4ec3353158c08dd6cc16707279cbcd368fb637c0c1ddff560d1affc8e823d6391535bdde9f98a733e29cbf7284907c6a6d8188556ccd97e18d3a878c44fa6ec3a8a9fb8735d787bf24d6f1720761a63592eb66ab93b3f885ee38a08152ed664815ab02291b718ce5f789d148edaaf8a453f5cfd3e69aea5e05b381c65b250fa31a5f3d57ad575f0dd834b523eaa3825ec42fd386cd920dc090c2964c958e25185961b94fbb02c1d06bb317722cef99bffc2cffb96847a837cbee5ffd38aec46c1341437e1cbed63fa9cc53720cf3fb23bc8eea3213912c60f99e00792fa0d24ba31d406f23acea7f7ef0a872d6102e4000bb9bef8cbd3d54c9ab28e8c0d679b8ad5b3b2d06301be5dafd0b72ddee0c357c44be2f20917304b8580e307b4835142349d2b8d150cc252ce0c072d149ebaa17b060ca1f60862814719cd851551b5a44fd2b4e31ed678fd63627230cf7307b974cb39f45d03ccb7a8909e42e2c955fca07c860cdd7988c7c676272439eb3edb0ebb834f24a3c344f8052a5e483450ee5608336fa0b13e4a709fd72dfb65085d8762c43d3e25e7fe918ccef6196cecb0016317b8ca30a4f66162b0b53adf07335c0c4fac3a4e3163a1b1a2f3da9541ee34e8f5e61e86ac41089c10b323d0604d015a35af0e0b29b33c88bc71bfd55405dfa3067e82646797c514e3373c662bf89b6e5556bb3b96bd7a88cf854c1f0c57ff6b95a8aa5a82676359253981153fa9a50aab638e908072c767aa6e37c1a042e0e8c34144fcb983b76502985c8075fde3e6dc1f0d01a5398e5f0e364db8ee7f7d72a85d925da0cd308d86f21c04f3373a3ba67c0be74bdff61af7719ea452117fbef207c0d606d3009037249022f40c1107c271541fc253d9f89d4119f57510524302ad5a35d5f9a99bf7203fe52a6b0807b9393d924edcfaee9268fa3157b4d50aca878994c737426405fc8614b4bce5a56db994c2b738c94669637d4903ac19a546dfcc0c43d28b68d29f017b9a087507880b6dd9bb2d8d5117fbce81c54ac26e6a41e64226d9fc8be217b64906bdc418bee3955f3fc518977f6df4db56b049a8e4c6699418a893abd7202d8aea25347fc0c69792970233377bbc7744a2f790cffe94dabcc61e60236725898def873f6310f57deb4d04e4155fe682a53b07dcc1eeab861f6b972f3de72185fb2790abc1ccaafe6a0e831ed729e866c15ea832842419cea7a0d81a19959fe5b0b24b4a1bb0cc887ad91a816e077ef9a193582e791ae9610265ce99fea72a4472af24324a30db8aee711b1cfd15ee9ca25df83226711b8fec03398508f477eafde2c159ee90dc33d00d767425f9ddda2d6102abaab45ef95357ccf0130725ab2630ecea62cc184374094d36136733a8e23d00f0a00c008c30062fda7894a4f8553a0374e14717b75a43254ad43046696ebe678314f2b0a01bba24ec15b72e0dfd6210d7cbe7151cb42392d559cc8375639d554e67ad99a43c4691002322d4e6f00533497d9e1622618af352d0fec093ed2c4ab171a1e36c72c82dff0887295612a2dcebb433ff6220f8ecdd8aec6137a460e31bea459949794482d122772d1ab9f41758fce3de0c15654af2927cda29e560a3c69a147a97ea2dc9b8a39724526b2fdf2743fa1c03fd491eb98c8982b68a20d79d2900ca110f9ed41b77c36f41d0df8ed6939105cbf8c9c462f8652064f43aa136efc707649ddbe72374b697699c264625af8a0a24ebbb24f535feb9c30d3b16a064e49a04edbdc37d9f17218d6aba923b9cfbd7c09db248d07f2431e2250ec92173c87362641cbdbf91b4b0b65cf67b34ce8618a07fd9e56158620640b5fbe30178e7216d547f4c44d24721a93a5518d9c4bf39ac71f7292632a76b91321dd56f9b1f586f1e6426d07c8411722f457158c98781e09b7b77a2f916a06d381bc7361864563ef92800796f372c66a238c4b103637149e92d7a2bb18f7dc5d90c3aaafcf37b405676ce6ef197268f33a1a375f1ae02f93872485635d1b00f4ee6d36cfd9c2a446dd169c44094c8941ecd6c5f17005a36f98745c1f5f95c0f72949426142a673e1e845dcee3736d5c10abb821d8c5158ccc28cce603cb399fb3b2fa0301d2b8fa85932dde28f35b71f5d2d270a082b852df1a41bda853eb49dde1b38ef2cba40430382dafeb9720f977b2ed5314989454825e9b3b200fdf53279c2dbbfc93f5cd9fe625fc490729c977994660d38af6d1dc132d20c2fc8fb898a9b292ccfac20917b90d132e37201558a7aeb0b93232d6e9850ea484998cdc20cea3f17c0ba07c4e7a1162f512fb0772c10624c9f5162249c179592cd7837cd4dfc4c3b92e973666ede77ca93726eecfee2c79ce0823ffa0df70510db6ee3f315cccde5f1e1b1b74290d8ba7772950acfa88a36f9ef7caad9618dcc9e863a52574c0abf35f10325503369215d5076e63715c68a56e2084e972b28f370fd7f62d209e29ed262158e765de40312723e80dd8ed5db04bb7bce1fb85b0c9304638f176af6ad26a25e8c3bca8e422e451952518fa4dd68a80ffb1c36a28c60929861acab5db94812832a52a243609b722ca3dfdb215a31fac073cd203af28667bb756b8242776327ce99c3b075829723e780926e9312e4c76ead4ba24992ad9fe1f2230a3249f8d0cea3001954c574156e41a3c38c3c3593f886283e961438c4a4fcac0db6fb20f0927836bdf42d3c729833a173eff5e7fff2a9ff89ad9fd98d35fbcf827e2210521f18b223ee93fa6667d71e942345dfba2ed084a39d29c7eec7d9f3ab2fcd52bda5ad35ad44dc156f3556aefe64a426817cdef5ddd58e3f9d1eda244216aa6af8e402c0846de87d72f935ec9fd684814822eadfd5120879040fd4021848d51d650efe03326f8d593531ccb86ff40e9ed6448627c5a824a7b60a134587a3318778ffdcf55fa14f6b723e163aad4862f779109f97ca0e9cf4c893a5c95a3ebd9abbfc52b5a41af25d7249d18745e5767c546f419b56e9791dae9127409e9cb372c441a3a3218d254d72d1e452806b9a92e511e0f1c94f5fe977908db39ca3538ef44ef0405627b451721fb5d81522f2de21191cd29f984a59a0f1de03f5e034e9811ab8dba552cffc54216a721a7e72a890feda542b2600298a5c39a5c6d79b2f456969091b53635b1e5830e13b4c3dad0c46aeec83060627e2448c092452008fbd8928cc926b8f9141f69a798476770bd8c9be4e6e88225e9ec011c276afc16470a3ed255863f12d7288c28b8490be603cdb23f6e4aea75a297d3e48e291725b3ad8f22b5ddbf9fb72ad2fb099e59b627d2aafc8cc41d4b2103ed825b66d43e6b7bf022d9338229b1956fbce69984ae633af4234a9a4e58569a690739c8c88c62b79627622ed92fe72080b556455c23bc3db2d2c813ed56d4b92cdf0d5c0f5a910b6f6dae3a8cfbe66ad974fac7e1b143ba18873991002bf30c6172be2b2faa0a88aa166aad89c41696ab59c0d1d41b17b5e2a09240b3f447c0c1016ff56980877eb6721b909c94c727253459130cac54a8251ef00d2b414995b1126f958bc62405cb4f547a958df7299678cd94a3ab2913d4122a8a7bfa40274127ae3beb4f32f727cc63008595d35d8004de67dd2f1059cece9d57c53d22b9089efbfd689798bfe7eae3397425e72c4dda3be475dcee58f0068e369f9416240b1755a39db161cb556f525b7ccdb725cb4a2c35095ade688191eb070d01476441a32d3e0160afdcd97010b0dd86e72655796c235d407fb4713443e87fa2b59ffde0552c58b493f0601bb2d5ada8272048c67d1b69f1f18d8fff580829537611d55f916f387a2118e43d610355f555aec91e77563a54af4cd6dcd82a3c896de1c7e8f7736434746bca328132ba158726e130078f3114fac7ec3247d6d66d01b025d06a20c220f985b6e3174681b3172e73a48eddc67384c0c9e14e1df786252a473a3a9d20aaf16c29db813187e615306a7ce4939a3b6aa535aced2275fa0ebe02e518e7705f336857f5675bc9a70722a6e7aeb4c063e2759e38dfb4ce9a6f7ccfb3d0810b8577216e30db89b89fd729e8f2a5565336d5b7b26e081a08105ce6474a6c0a9478b38282857601fa3d17268990108b2bbc96f95c8a149c16a113d9e44114fd4f311b51d3805f06fb63572387ab7fdc08708f8e1a15092685e57fdd82647240e28673efa08a8a5dcb68b726153456917ee70a98f99099f2e9dd2152bc019cc3f3ae5807141802a4837bf727fe5bc64c8a15bd5cb3cc74a6c63f0e26bacad3eec926621e28fa4f1afe47272d516eb6a8e9cd74b73990b3ebf7262b052d866edb8b2271d617c277c2b398c7254f628260e22807fae71bbd336abf7908646c2f2788b69f008689f28e87195725ec8f46f21f8c51e85e93c01633a4bcc6a06e6dd1b942f9419032e77cb697c72139854a9e188dcce6c9fbc1c0e39e0b810f6d5b49019f83e8277681d53654e72308ed19f279ea829baa1b028ee3ec669c52d725e611f6a2a4334c5b74fc99772a349234bf2ab6c69467e93bfc5bb7c9312bac8a91f791acf06517889a43c160e0b684a0ce098ffdecd20718f4463107a152b80dd95f84f7c3e6d983031c5b072c733e690e61f1459423c3de49ccd87ea09ed9ad6319197f11a1c0dd15f807b7233d027e94d14b9b2420361802dcda154457f9c14670a3879ff984fe18680c23622ee12131d4813d2c5329fcd53990655a9103e98fd8d1c311a1c73d5d94d5b27808395018d19ca69da11241ea58c9eba0a6d8d809dc8d1172a1eba8ec2956e242e0c05d30f0b59fef314acdf822bbf6896b6eaa4bbbb5ed1e12659c77deab71187276a282ee1d45ecc9ba81229bc531fab855544e049edc63547460f29cbe90452f15fb58e3d37676e7a6f8a01a72e22c5f43a06c2268a212366a773c8f56c6cec1413141e7f71b45bbe25cb4757eea9211b697935408ee9cbf5064f7b909c65c24433fe8f2586797397e2981dc26227e5c59f86d9a656f0eefaedb33d695972ea2f5eb05fde3efb0cb29db622bb6e7d495d167ce18d1d1e0704079781e81d72346b9338f5caecf0679f3b85693c70c7f817873fea6e950e883356e423a89c3391e2d868b720371fd19b67ab750281ab8811d650472fcd42bc44810ae8380e1f0446ba10e71820081e737f33d6c9543e075680bb44f6d15b495a4e900bc706110ba3d6f37638bfff2cd9dbed54b7da59995c458972e2d8fa6ae852525b174d727ecb16ff0512236574bf3bfc3a1147def8bd00eaee10fc38be83c59d2e9e793f13757861bd0bc69b6b8fa98aaf54f2c3aaf7ca184bf9ecca92b6e9612dfaf956f726c34414a5626d07b772cdc79de83d4349122eaa9b2f710efdb635e32f221ec852faf6f79b0f7a6b8bd882e73a53159600941b35504f0e4370f637782e1c721590f853d2fffd369e03564b8d9296361e4c441d2ba4e0f0a15e015370729e72616637b611081d6fa017ef0e2c2bc5fa344388945aa994fdc37de7bdad520848da72d0cbb6dc8465bc70cca1757eab2a64c2f5049c947c6b5d7b4f0d5f6abb72fed827290a0a9362cb005c2bf8babd32766396250faf2571e1c1aba1774fda7241c2e9971940493eaa77285f80a5508a6be37c4cda86f3f10eb900e8d87928723895b6b00f0af41a455b858bb470085cfd6b2e899a89f0885b07332c2fbcf072d55337933e78c497785f5e0027dbd6ae4720af0dc8f5670b2eab51c28185cd721ae46295405394445051ddb92a7928f938d97a59fe000f1abc3d889a3fe10540555a36b5c8d51c0165de4666008974726ac708958caae89c98315d01c04ec8726f55ad464ae2842d40ee90729633698c7343c63d6391f368272b65cd43549516915a64af21d7612d07b059892cec86868836acdf38a724e8776d89704218d87279bf3b7208cf979c0cf559a52cf2ee607f38c68e137606a5e7058979f4af0534dc12fa72b83dd677d1cd235a2ecb4fa1dfd563e7b0a11aa7ae8cfed0cb833967e5290034a2f9c96976efa71f6e069987e69a69813bc461361c377d77205eb74af766588613d8add9d7db6919b2e417b389b71e43fd27312ad2b705b92ba9533c0756afbb32511fde87ecab9a3ab691b5c572cf83b762a4916e86718c386a372eea580953fd61781468f6a9d2ca8e4906e6f758d58cb89c0f7810e020ffa4ef1e75459f10c3e45e5d0f342e602829ce1539d405b704ba065f61edc9c7f9d738070e5ad51748b9aebd30822247c7528a50ff9583ce86f11d7e25101fb4f4e65a72b870ad488966fbe8f64e01a5ce9e7f5fdf5e4b584ff302a349ae8020b7020a0cb71cefe0be04355cbdb1eb967b8caeaf2f88077aa5c781381ec5af75abf36a50fb7dbdde5a4252429b834cf170f2c9217acea5001ced6d7d3a50b390a6f6f8723f2f43fb0f2ef1faca7337c6b90860427bfddf3e66f0f7ab0ab133982a5e9f644befeeb56f5e1171e96b6e3369b04cb1a7fc05c36d66df80c18f52489e1e9a04d90b6063cf2846a85b7b550b5ef1637debfb2a01224be24a6048553fae38d572857f281eb27d6dc278321a21f0538bb55f8553a4ce2418036924383fa7533e72598a3534a50ea800cd6bbcca52b5fd6911376381cef7d49c81c79c927d3c3d211a39cdecbc9e21f836e87a92a9cace304c0166115023113dd1f7a0cf757e67363f364cc602776d0e25d6203efeb8d948fbed685909aa3e3f9ba98f0c8eb86b7238094302fc52da0b83583ab9ed8617861bfb14e0fb55c371cc140b8881464472ce6dfa7e09fd79f164bc5d1bd0996fa6512724523d87a377b74add9260879344429748f28cb759fb7466232c118fe822120fcb0254db4774ac4c4b7398f61e6b75a78dda826edd2a7e746ded8579b83181a9c99332bd454bdde1d2db7b9ef372da0ee3b91dad483287919be69891a6aa6959e19268154b83e75669539c1f590d01034c7bb40be9273173fbd8729fe61bd84136a2f9a22dffd81018ecb1bd4837b8693e5bf2f1b4dadcf2fe574fcd3c4224e10a1ffbdb8f84cc076713bd330c721d9fd58d62d0addecebc018a075af7a490bc60d7cfb61a443e57dcf79cc1c3724521b6d8344f79f21497dc387fa5945a68112a5ad86b3f3baa2c3f52f4992372c86f40d286e933512e1d7d00b6a93f586e6cbacc0a207d80afe959fd093fd37228653a67acd6b7a5b7acd07cbeafb21dbfd42a94e4f966e9da70cfef95913a1689444600938cdd06ac82ee69b9193779993801ecacf4544ae0af9b7028e27b7259cd14649ced019a35dae5333b3b7c37b1b06b68620ef83006e0d8185a130772e1bab42dd42c4a481cddd4263c379e6a752914e1b79768a04a8e6baaf1035b727be4ba85ba41c75a9cae5b0302105f4ca631d8df77804c1719bbc20b85cbbe72c0e1967ff671d3881d40a2d50074fb06e3c22f3e0964bf74856e3086f30aa72a3174a3efb7acbc649f00abc4eb71065c7e7c2fbbb26f746430323518983fcb723345ac1ab4484d4560b5e6f0853dce99aa9a892d252d8af5edeee8bd66bb6f462e9105aaf0c629553c251a0471255b033bb146991628510ddb07e2a2ec303b72f6584e223ddd5c75a7a2433f06e968d5bc5d8554999be7b0513ff23b4605a07086f45113eebc30dbd777555a4ec4d770eeaf1a2764bcca9a4562bb24d54eb907d9320bdd87380e53784e93949a33301782bba0046f44a13be0bf561388c4460f9ee1dc9bf9c5981c4f0b3220d29dc8815d24279b9327bf00edc8e1c300d51c107465df992cfa33d2b37217a76370dc0feb5d61a8f8cc9317d74a892df931e2720debadc56bc528af0ebfd78bbf6bce6870d379fa848074bba93f8a5810319f72a449e2b9b26b2d72184506ec0cd5205f9a47340cc6df0dc25e8163c644adea72e5bcab483272e04f9d830e419c3a339a6ec0f027e38b8607b1173ac4c89c8e72f78b562e37756f1d3ff4b0198fbeaa67126b6d327930d50fe5b9e011b70e7d72191fc8c801cb5c6cf64d248f3778e81f47e823de956d06c62e563bec39124b728ab093d4fe228513720d04e99af0769e2c6b8a583da8b9bc48b902abc5ebe47251de8f671bff968450bb5b6b4f4502b4d2cc019351e3a7adffb3e7e2cc03d572019915b12e3261a3dce5b2590ce88849d8bc498c3d07fd7faa32d25f66538372fc5026a73323a9d215c8d4f1b82a4f8ea9b5cb0b3e64ecb29066ffd7c2f4f311fa368bc880f7ceb171becbf0ad587a5020ee20065d6742e431cd2a6dc9700e72a73897407f87d440381af4516589f94d42687f9e52bd0b6140902b2dace57772b0f4abb36896f9181d0f65ea55f1737caa9b30eb4f3458144ecd251707350972e8ff1ae383c60907a4d8127a3c4e2c9effb684548080ea7f90a544ba44a08c3b242cf7f7accffa8d82f6ee4f13ea0dafea8d2488e756269484f5f6f5fd4e0772afe9b1498bc208ee38b560d02f4f25bf54a54f50cb45bd2cebaa6fcace5962473f8906187ae50088606e213a3f1fb562a3191e11bd2bae7ea1e7b2bbf9da946833d7f80597d05dd9380f578a2b3d522321cc2607c958d8acfa5d7fb9f68bb77230b84310368513ccc9fca68e12d36e9c76de2f4849d346aefa5f188ae4c3665b4fa91ef60f24fa1e26cc5176f04257d52ba2bd01304213b6b78e028f79d6ea60893b5a2fcf58c38003a1663e24b838e7a4ddb38da2d74d113e0168816a920b2b3447e4de19d5806e1366e6df3dbdfe6a59da81128d0fd08d0a0c3163ea5e9e42782e30898af793b14bc0798c59cbbc669fcfafaf3d7dbf9092ca73471787fb72aec072e4ad1ed67450b6d52efe8877e88bf483daee2995ccce96bd698caab0722966c14b6ab00bc97e9143269c01610ee6ce51f8fa7e8ec2ef45d804d1a2bc72dba763cc12d87f7005f49b00c02b796fde7059599f2170f598d345a325eb5572955e2aaf0eda6b1581a33484da679d897551a813503575e97994bc09eb15f1722e37a8fc3c3f5fca2505764889082892488661a550b54fa5291f3dfbe0899c7204752da13720a3292e80f21db0b7cc4899e9144ef376034b9eb13a41950d5928bc81fd17fc7e656c21ae6ca3c936ec15d4005add19fa109791699438dec07972731ae3da41a18d00350aeb6dfc807276206172f95fee3e9fb7b6257255b61d0993a6fba9edc968f0428093a57e985b67b65160de10c5a7c3f230641d67fc2c72575495c39f85121054de61c21e4b31e93843fd72b4df62625cc59ab2efc25a7298b5887d1450bb50dc38a8c8fcce39af6806b460b7a9eecbbc08cb03b337da493e38e9c714c47a4c1c2234dd807bb0e82e063d08b698a04b888fc238bf469e72c9f8637254fa2fc616e37033bbc58173f606df0c506617803eaa8cb68014d67263926ec70d53f55478d887e5c58f0fd1dec3cb317f463093a611ca1df07ece5c129227e09f9d6d9aac17214497c8fa50a2dd70d04f45f5da77dfe010fd97214e650b93a10e439df945345a89ab84fca2f78dedadaa8960e6a70162202d8de07285dfe42d37ac46ebec498ea27abcab58c6207af82c623055ae2a1589b7d4b82023f5bbb1a45b0b457af494a5cb7ad7214c2196acc594a1ac0824c29c5b225072794341ddb6dfac8d5900c02350ccbbdc22580f547324c29908b80cc95a600972f3f0c2a7e3f4be332f3eb96525e5a888efdf40206f35eedfc6f791913ce33a720a03a3bb76e8ce9aedc52b4b221037dd21c72d5ebc06a6dd5f2cb6756aca9b3449453c193e4ded368919b95ca8d09e886c146c16a416990174ae5eb1045c0872a4e40dbad976dd2e137f1a5aadd91897a3116e21836eef245d5865e8d141347235156f786372c1c87b4571e1424a630887cf3c2fd2c4ad0e4b1d73e96c8b4a7231fd45297d0801ec6b95a4d2bebf0e750d833a40a0a0a7d23d99c56447b74f72580b56f89673cb884d2e8231843bc81080ce1d1cc5aeb67cd5fecc19dbef073cdda3fc4f35c9647b67d6fd52c7a98e181c3254f081cd7705188baf5daff8c372887ec30cdd2ece8c881ea019e92c780d0db8317a06f0a67062ae59f7d6ea1559d8ec730b3b74199de89c355654012e158af0e060a4a42b85f9fd8f241cfeb1725050faf92fce4fcf077a9d644c7b2f56016c41666dc6eab00e400aabdab89772dd76b3022a9eafe0f23f3357a8017018ce5847f948e7168f96595d64d5c0b4727e12d5e5c2e486cb1fc604c148654f3df97ed20fe3894c892e0da232681b9172be8daf2988ae64f3d3cf57a02dfa3788d5a7e72198e0de3f1a61b1982e212b7275268945732b202a3ddc9e9eb627ad097b7ba2259e092ebc61ea8664a78def72a475c95469575fea988eeef637b4b2fc660036bbe5e7313bc82205aa04d47a720a5039ef2c7921a4d3760dd4f136594e624822f37a54f11f437b154a2dd9e372ccfd706fa7c8cb5b6aded17363ba75dd95f609b401c33e1d6c59bd44e4797f72565819257be628ae08b1a8bb6328fc0e26a83c08d2a2df1e1c1190d552146053140b366819a454272a08826deac25e97e2f34a5b1e5af0d1d89518e47e63097262c1e970abc57ddb9b5afe29129c0486c173de7f8c870d0a4bd5145720185e722dae5aadc9950cf5309962731a9cd57b29073ece5ad22dcf9d0d0da1f2807e72bffafea48ab81d139dae8c031290a3e92d26d25dfba5e324d6054b85defe8f72cf24acd187c9137761fa8c796624dad6978c769dee11523ac25407cd7a770172a25d21efd2cd97243847e1f32601a97462a6aae0843a7c3d40798f870f8453724e84c046a6d58481b5f1e396e7de336e01b4e96e699266bb0c6667a84ee81f7217b3a9baeeac61a5ef148e9b8b2c86c5858176f4b50b8d92408d7ae43fec6c72ce7638e132418226c8aa22de21f8064bb3a3a8fb18c5070bff6a4f7afda208724a7d034adbb63705720e73b4774faaa98b3c453911d321d1cc498bcfc8aca8726bf52fe90b0b43b5c24972d58175f383ae4e03490275a7f28983f6902528bf5c348cdb3f09f6717445d18657168457a5903e7f195ce029d18f726d25ef04667229d903ce018582c22df24cd8a63f408c86b7163dac3754d0e5fce9dc1e72447293f50cb77e75af7539ba64c884933edfe2239d4c25d7c2f54a00692deffffa724f5f48235aa3bbec992d2a16bbbd88e908b34961b0ae243792d559b45190734c271a2da2d008aabd1f63ffb6a0e9c7be7916ec40a9bc69f816458b9b0cc2b00088e68e95e340e0888a1a280e335e32a3147d6a148cedf36df5123156219dca722a432b86148612fd1589917c94db35e75243818559fb63c8f99c344763000d7201250fc16dc9fffac0f08c71356c5edb608c9c86d0f1306623c0d49f9d68bc7231a73988de6cf583d737aa91c812b41d96196ce26baf732bc9d38b8f5c1db7720e56b156ea826b636d4ddb4deb2cab5d449953c75a84aad0508fc7fc1e527072ce8045929f8a20d3ebf3e8ecf2f05e4dfa476d4e824e3a6d1c13d94094433134c3d3206e3452fc02c8532f6ece0b396d960bbb18d24258b2c616b6b2a0acd07237fe10e6d5c6d5affd5c4540f3ca69bce66e5b9363fc6ca7ac531311a523a4056d902389f6d589e6286bf0ffaa756cb3ef0d5e69cec7ebd0be2bc93c1ab39716d916a4f3044c38737518970289b74d0a73de14727ff732bbfb8d89e570bb3c725d355b1c7160bf1ffe013bf8703751e68cab7acad5eb2d99a90b42dda042393398cc6305debf4937f7f901b94bf07f828bf81ab6eccd8ab6526420037fb7c3222b9f7e8fdd5f367cd1d4fd63677efc5e26f32e119d3519bc20968bcd9777de4c743db097bd7f09f7d7a128383a8f3f80c467905b38ab8307acfecf6e0dc9673b78b9488656a8bba28c9913f523201e6bf1191ae23e99b8219001c54d2ead0e728da76d0998729a56231a3612739f9237b766ec625d2117dc6fba9dc187ac0c5e168da86dc97eb9c728124dd77c6554220ff121e17c125bad491e735b9bb2087248ae2e8465e3ddea2d9805528a53f1a7a8563f693249143f32c55fcf44368b72ac4734cd6c4d8dd272d83b277a4ff73db5c3d105bfe650fd6b814ffd408ea4722bb95ca4ea054cb9c93bddba3cacb229fd58d680c5e7d1b8335a84be53f5d872b59b93a759edcaef7021b8773c2343bff581e712ec8e72ed97c7b1099ff27272594a0ecdb80b982df29a04ef321ed11be2f8d2c6ff3f159387e93c0036e31a5e6e0ebca88fe27a08c1011a5255d53fb0038244d9253fbe7dc2a2f6c57364013684ae264676b87e48717d332983b66cd61475998c6c142ca1bae7a5c8e01c8819cfb5962ea0c4c9aebb96c2c0b7ed6cec6a25d237ccf0e5881e8aaf241b68cb7204f9196553836157e0677eacac2f4a5f01f76a206ddc4c51c13ab9487c394572c479dddd99497fcbdc9172c13e3f557dd9b3cfaa6ac11dc54f78c0653b434b72c89b6a8f3a408cf3a1f3e3a61417c16ab3b985be78248da2b0e7cb555543914028d2736c35799c1e5fcd657603e3db6628682f606618528eb6af27a9449b6372bda984756fb8f3133f8a2cffa746e14b651060ca110e6723384c768e71e79572e8cdcac7b002e31d71abd3617b006cfc02b7a8f1a04b66985ab6da77f77cbf7202abc552097eebdc27d3cbffa01303c2d98d2865d547b96e3990b9ee38e90f72f98376f0314274a0e2e43a89e3f18195a41b76c4ebfb200600d755dd98190672734beaba8a1d1786142fbcb472fbb895b18ed1e8172044791f2c29e7047f1a161ebfb67d76f9008a98e43c57ca6e135ca4032c60cd94b20817d844e50a373672c4c9ab62467581a0276ed2e6d97f6a24c80b0f3bcacaa8949c71fdd64e848837799f418580ece4fea1733671b5f555c4ac95036bf6a5cf003229d5e779052348574cd6154c2f35eb2c20fcd41f1e228b960ab567db50fe33baca54937e451572dccd9cd05f0a80af69989d68adba265350e93729ada9e6a756b48c3f75ed01722d25e315ca120f4ebf7a4a5000f1cd3f8a735179c16f0b316c73695cc1e4290e57b66066973ecc9d8641066a78f0b08f88e13f392d08369f8ba755d0d564da079130c0932f9c40cc9b48a90e0fe95c3cfdbeea3903c994d43e8d215ceda2f77226081a8b08a4d6f38a08e3672bc64c288fba8039c36e42351abaea85b1e87372e3ca8d186b77679b33525358d21625a65a96b776b6056ea0edff9d428547941cae031fad910d2ff6a11fc0c1fea04623ccf2771a80c05d0d2f97d07fc47a6e094048676aec50fad15bbe471256a8c7fc334f0168587fb61654b6b6497473c7725b89748d4038c09848242604ee1ad5a863279b1537142f99827c052e563ddb72d3b11040fcc31dd0f520cdb595f04ff164e7ed1e91da6d5d7d92b579f8845d720922ca209a7e0168f5f86b73c63abd8d42f92c8a103fbcdd9d23dbb3e8a72c72767a76286aa34e5e0f0e05b23b097eff710f39abc996b2843f103a1ccf72835ee20c1a0b4acd2e4dee9673c7bfbba624b65b5098b92ef7ea479f5809f4fa4472f614636662c2d68ff1913ba603a2828f21fcead4dc1a7cf93752e9b6bd505c7206d1a6419e0e0a72ef613df197d43aba1dfc5af6695f200f06d9afeb0b3ff072920afbcd24bb7942d1708b592146f9fd7865a8e06e96f1c4824101fc73e4ab72cfb747070cf5d45980eeebd0cb59a6fc3f5311138af4691c8ab97f74e15f1101614362041b7fed527d73993067ec9e4314a970079962853cad9c8b682f8c78729d544825bc50ea679a410d4fd470806e9fffd0141f6dfbe9ffa4ded83ac60272a14ebae2ed0f891c87578201122984e02f6fca235b2cea056766807f1b2f8f72f44b1ea0202fbf35c01c3bafaa46796be70571ca4c30a1c2a611362c1f27362fa0465c113cf183c2ca2b7814123b67a40043ec216fa23666b86e0c4d4686e56d20bd62e9dad7070b5e6060ab67a632961f2a4eb4f2122ffeea92ca836f61b57290b1c74cffa9e40bf47ac92711ded44b27d5a2fe4fc35f850166944ea0791d720b61671af4e6e97ab106f774598f2570a8ff03aee34a8cb953e145d4ea15397282b1465dafb55d393c6e707122b4d2a0a70003c7172e9c2094afe6a445df8d32a247e6d8f1d6083d4bc136f737f7be081b76b5d4c4f40aa5ad70385ce1127b2e6735bf0065de38c9daaf31f16f520369b93bc7be15653c8cb4223aa0047ef162e11bfba2fdf68787774b4e83ab4179b49f33dc0e673d47cb64eeb58672e3396f0d4e942b163b9fbc2109f5744e1e2f3fa7b1121251598aa174fbcc5f596c735f7e590437d0b9bfadeae14d912af3c51addab6c64375d0c312330e94f9352ae6fdbb378ba7aeae1da020e953eed7b52d8859fa8f527dc662e6cf7632225e06f7298b6043189b68ec14c05848f521419b0a3fd9637b912797e9d4ed7361058d2729f1cb44c3dc73d1028ea779b400951ae1574a4946500f414199b317413ef747236fed294093a5b4c70e9b03bf209d9f2668e54a3903bf92570e31c4da52fb57256bb63d3f94f487790bd11a962cf8d7c2e9a09e51eeb04395f6248e21367461c839f9edcda8a238030e657a4a62a99298c3de77e9bd490d35f37709896bb1d728c56d92b0a055cae0688b2ce4552206fe3b2d3514aef9bc0a4f0297587115712e76978764365ded47e8176065f59f4864bca1b22f74b5952326521b060298863f481a8e94b2424a3724de625463031636522ddc9b02da5b4d48f00818a2da8722ffb272a8936efaf474bf2920a81ff649ff946fcd76e45d5314383d5556f24117226e48c76a4556d0b24df8ee22e6e7c8af8263819759e0d311f0dbe4af0e8729f79a83a6503033fc21dd40a64c6ac3cf9f618d875c74bb0723fbbfc5fd0c172a7eb67233b5bf584b70719e571a45a443d6b2a95b8c939e0bd18133299929372fddbf87128bb5d586b24958d302a4a142da915056e764d53b7fbafd79b72f6728ec8152fd6f28591a871a905d27d8041b785d8085b9ea006f3b827d6c6124912f1d1eae2c7738314d19149a0330d2f7b46ea2e7c059d7423313ce7b19801516ecd3a9b7dc098c0c8d531f18528f851353c656c4b61fc9a2a9eadc5b7aba99b72722274420a3297000b70c38cebb5501fb8b435029a4bfb776dd13c4b79f9f636e9798fcea8eb231faf4f5422233c09ebbc5ba905e1c6ef09cfcd2ed9eddb9e2904445f5b2bcacbeb52206c51b236c0b4b453e808e5871424e4cdd3e231911334bf31cff0ca1391fad74fc535c317fee8699becf9446572bcf917fd47d032516b16d959391510c9c79e908becf4a72301f6305242ec415ae8698a547087aadb4e593e01b28dcf4deaec0946ac7df163da17fbee9f661b01bf069b3d9eb2cd8c723f39f5708792516145cd9d4d60fc5747d52e2e197d4e5936c7488fe71119b11a0ac312f8cb0d04bf610ce69c719234c654f2425d7fe87555d5f98260f2308c59cf9ba4a71b497f135a82dfb76e184201470cd1b3137dee1ef0dba12b4a7aaf5e0ddf5c2407879e0bffd8a339ed8881cebcef1d45b5e443a702d3ac5b99703c7241b7128eff85c3d743b4eebaf0c88f4dc2b1cfe62845b8dfa16585a030d09c7288cf6a9e4bb2048cc5291cf78a5bbe558f8b01dbe8a340d1289de778af620f72e6f77cef01f793914006d71d96182eee2b0466e3ba519ea2efb42768f897cc72cd7863764ee3b3fbe98ee654dd1fe9f85ce5b41ed8c4f0766c7a5096af8ace724443a9eec06f86e6cc6e20d1f17c11eb41d53fb73eb05e113077eb7516067c17b0de4271412d5a1593031020f7d6804df0f22e789db94e1b1e184217cc13414b8b7ad8646472be98cd29d084e5c41155ff50dc90213ce361932dc0e072550272f26404db807e93bb23f8568da21f127f249722382f0e67ab8dccc3966ab6d766247d89383e81260c20e0b97395c2934f12e57f0f8af31b0145f13d445efcea65067fa8b7bf4c2c75f9568e1875f62f1abd19c4023023e7c453783f60f8bacd47dfb5b1208e02341d9bfd8bad1d94bb13729e69d0a65bb3052c771ed9fe80a827d347792ca1d68451a9dbb18272eb2a847a7ffb861bef183a00ede3842c6dbf724d3a5d21f040a8fb085a8b4d73b3cb4b421b7442639bdcaade27ed12ce1be072e25dfa09c1ccb084874b594896ff10a0751e5cd468a5a40fc94e43185e5cce4f0e5d225dc2e501f53dcabf7319b886223b1ddab0a14f9ba52788398be8a89d721ea57ecc2f7f99d2d48d2b3887a7fef2d2798ee5058ffc3ac8b57bffd5438d5be3624515d11e2dd6b83665f427952dacb958070bfe7f0c49314bb00e409ac523d239b7d0df2d939f3ccc3e79450e822f330d30aa474443ae7393bb588b3cb772dc8b78ad778b7424bb986f670241da64e3b3dc4f656301da7a40efa4d02e44725b2f7b5fb74316d5e9553c16994819fc17843d43203d55a0e04693b330796c723077566ce4120fb6475f7fee4a26ae5b61662ea1d3492e845e9081a09ce993723570bf51569656b5767d45afddc368362e614bc427b148dd197c0a6c17e7bc728b31e28da6a1f3d920498dbeb4dc4cbdf734e3f6dd50be08aef4ffae0eeee70c7f808879dc0b95f87c27e6b5f61db8fe75a78f01533893ce818f7efcc89b6e61adb7a014b11f7c25bbd90b2e87da138fe6e2136457c335975766f6c4bbadbf72e619b0d477ca60594ebf3a0e50e162b3ac6c0d2c4f35c22f59393a812d0623573f762ab72a53338d3d2a1759413fdc6c5600f5636dd4a941d23381c311a9bb04bc7b93d51ae8985f7da751ba99143784aacc64953017cfc0b288423d71087e727b77e28e0ec8005e426fd65408c5740e935ad08c68ff56025072778a4f99eb10e9386a37e2b5fffd4f8b6afe1b23a4fe0b215bf0938676a5e294f0482a44897258a60991c1c377810e082ba7439fbda5b3a57709e1ec6efb3fb0518a29f6d0725317367fbc575e7cf16477c7cc6f71de57e9c2f744d4a68085f3d339a939f172f64cf185dc7c4a9db73a7e1db68595ef316278d9b235173b94abdb976e4a8a7219105398828213abbd5aafa498128f4558b6caecb184a44343a74e8b78bf18726062f474ea7da5bf66654364f1d14437798742dc3aa299f1273f35fa150ccc7273406dce2d2cf34120ff870f50ff695bc9dc3a44f74196a4172dfc35ab3d45728da74170e44caa148418d6c956740754c07aca358672321e40f08b2c09e2b1720c2307230547b564f8b8e9b65545bfc1ff9cd93e43b1914fb71584ab87fd36358e13b5cf16f02116a7592271825b9f3dc8e66f36b2b61ea7fed3c04f1340e3720038b454362e8029613d7e530e3d06ec3d76eb2401378e45d36f99d7da5abd1881bf2e539cbec0307d9c11dadb6e07fd4476e5734f273c726b0efb2785c9d572b6618c10d5297ac5b21db0fb6536b3dc695c3c71d7da1c91698c26e8822e233b0ff313e09e853ea2969488c199923254a20549b2ccacfe3f6d3217d0547b5f72cb002d6836ad49a4ba0d367dbc6dd764f9849928343ef1ce8df853f7ce78027256b0b52770d1d67a0538a5ae24ca6d1ef6799532ce8dcbed3bacfe7a2eb1c27238724b24c1450f48e0b5456a6166630982f2478d5286cd8449b45fe13be9ff54da2eecf2e31b1acfd301d3044f7a3b01c690f4558ed56d59fa21c6f87f1bb5103b28dc49be0d1e5fe4cfc73326291b3665f6ff6a88fdf7ff146ccab37943e17258e994ffdecfc9fcd8d8747b427ac92363277666289b35203a321f9f576142721b95e128ab25518e2f787c7a27579ea382a43a3eaae49addfdf9c4dbfc9e253419ddfbdcac1813676706a04598522e7116380fc4cbb7cd47a1427e10e0947c65c38238db34fd2931dc3ca7993bdf335e6181a2d92919ba64ec0467c5e4928657b7a48eaa40d9a24a02d5a8b384191f456a09338aa3181b84a47cda51921575724923bf0ecaba839b15e557ed3f3ec657b58423300bbecad6bcf2f7f2bea509644b048925f6d3d4a162626b97a28f0804238383d6700491d63cb2b2283612c372aeaa96987e520ce38c64b11aed9484cca004bf37da201d0494c3eb529583d81ddc3724e8cc2b86722e387d7d9753be56dfa2e8140e1827a69e8a5367b795a372635e5356d108bc4b1b4486ec08d4efe4a747312aed1b2f724cf0832d51036c72f084a5716b50634ec4286f4bfb99717d27bd7f8c5cb7ce2a724ed6634baf4c6cd2669ca8b950116f182d8c03bfe972b17fc18ce559158e7a2bd8067e04d37772f85cadfdb4aee26dbd8057732f4fa68d3943dedb386890b3d776cdba923a9f27bc7e8befcad35cd1d9135f7337c4eaf87640cdf490403f08dfcd3138e0765672ff9af9fff7907f924a6a5df38a9d8962290bf554acd9899079d5b965bc81cf72795fdb3aa8d8189a016c86a795fe8d6be16149fcfb0c929552405f48b9fc6f72e58dde7865fbd5bc3e9ac3a3675a39516e86268cf9b66b40ebb8b79cfc4e8301861b85d67b3c503fde3cbc94a6465b175ec30849ecb1b3ada1b7ee3bf88b4b728e2e8c517c2149a7ad58e4887d721f6e569408c964e4080e970f8f3f42c1be2eb40c0936f4be31e65bf1abafbf0d8169a779ae774092057fcbc12b44a1818872468c85bd23a1b1f3e8accf6ef6959d4673d37831ae28a4d3c3a5caf123f29e723a910adf454eb59b055c463ecdd038bf31cdf250fb3dcdd8e1ab724f1a26ce728c6b8a08a01c94f2e83f797973170c2edf5d120b868a0a881a36aa398057082eaba2994fe13aded4393616774817167f76b8af5f86f5ef9d0e9cbbfe75e1e63c1be0fd2cefb91a01c1b88ff3da2fe252a49213cdca13bf448e9c16b2033fdb7292cff792417d812249ca45932c15e839cb7b1914c1cb3cb58a4eee04d5ae7a724bf6cb53dd5b965e01153928d33fc789eaf1f5e6cd535511e0d5e83a887756726c10e94b92be6c2269702a6af8f4747c6b210189dcf7db474aba364deb796272960c5e68fb901b6d79755d74ae4b38f82f5b8f13b3ee9557a9da47966318a7027da3dfec91bf4956e8cee77020fa85f32ad0d8b59b6b9c65d34056a759d9f972e37522fb97705d037df82d5aa7521af8ff7cacfe7c65d221a6be7e20d21f554e0484940dbd6d318b68cae7d5cfc2029d8977c629ff1744beeb765be498dcda144de240fc363a271dda51500a25765128833c2204a14afbe5058f1dc92b4c97042078b4f40b748755bbc2afa16518195f8f7d79fc4be07bd9be9dc2af5366e472f8dc955c9d24bd4e4761e1ff83d7a260448750fd6c034ac04d497b9f5b5ce772a1782639fd2a185b35ac648114f2e851b647ac4a80a541c626dec3e6c33f1e72e467150d8b50313f5b36d754e9c403c54a520e9187860031f2f65003f37d0172de2add7a1bbb3bd4ada5a20ef71ac1485232d351473ed19b07daf6e166c1082492c83e6f2b7ac34d42a225e453729f95e2bd447a7388cd89ac76ce53a577220207f0788caf52ed55d93f4b16e3fdf12cf96de563ac6aa0dfd106a3d92576df72d7d66dd0bce9f67161e1831274449abe1e6b344e6978ad218c04dc1ccad985508a4084fa0ea3b183f844f4d22f37eb3ec9c651bc8c4a7d98c659e4c52d7057722438c04a5bbb45198e2d24c65ee6783da2586370a18fa26e0f38f1973a097c725afe96457e139be69ca86e44b6d8ae3e3f8eee97429ec9ce971deed4276461515b6a1202459a80ca1787c8d39ad533a5989fe8d985bc61c8d6a12415c74cc25075993e7fff1cae984bfac7becde75778462e4b628dd41a285bf25732819a4348e28dcb8aa2cc0fbdf5d7fd311337b1855db9919885c9a2a91c7a7c71f84d0c7205c5dc129fed968f7fed7abf7f967d26ed107124d6d8711da0a249f2a7706651ab094ad431114d938417ac4dedc522ae587e6001b6ec076a2ce76c287ee2b27221334d0b6b0a0d0c62684831668bae50379d17b3a7765564cc321e3c9f3f7e7237dac5a5d926770cf434b4d2f47aa32488640031b49f2c3a899be7ebb4e94c0722cf9056880b4e7d745d8bf86e7693f34f61994d8318cc039d94b6f9b098c4727ea9ad5539409094b137aebed856305d0fcafd62eb6536e4ce37fe78b67a1c724113afb741bb2c558225151cba7aed0ba2e166cb8616662a0b3807b32973bd72df6e594b6a1e899dbd92514297ff7310dfaa32aa36cb2430765ae1e5edddda729e76377b998bf109ab4eed0e2109f01192e4a16c7df74e6fb9e874bf218786284e7203f0ca200c626dd229b5d24d57173305c5d074f71870649966ee50a43c7231bee8b4b2b7c354c90d77d5942ff96db2c7bea0be733cca1ae828f3b0f52258e2f08a27df0d0e56a9705a3254c61126744ef90a767c62d221d04233ee993a6bbd2de1e79b050b4ec352b850fe44b6aee41fabfc88004dd9cee2c1213b307c72bd40cb2af646b52ef1462d2de70b7ac5e59e17dcb93918fccab7220a407e5b721deabdf1726bdb740ca7f58540e953b226ee316c988589416a1c530be7ffab72490c65197c4c643e47f3ca4f3e675cb50d1581be7500fa3488527f7ef009a06e42f24373f4aaff6dcfc21ba68b02d3889db5e08c878b8227fb743bd9a8ad722c19d2fe655c9fcb4d17fe0efbc05fa27c942ef8f753e02aeb802d54c2c05cc872d69746392167e68be268e3bebea487c80a7e64ff127faaf785163a438f795d72b2e0306031d4da044bf95f0393b6c335ed6a71c30a39404d737d6909b2719a72c15d36f661f2a7915afe84e19df3eb02644fd20c265e8ca6708581636f26d5722ca5ea66c560be389f0156562047b3578d380532e8bea9a4f34fe509d8e73b729cf70c8f1c56dd7a1eeda710ca7b4420bd4ae075fcc1df6f50c413840f08bc727f82586b02e822e9a1f5a3125f7ceb08e5508ad9a9d05801ac58bef7637fae4bf85449e4c520c4959f4506064144ea6f39189ba532da1418c0eb2ea65eb4a623c4edc0c0476843d20ff54b77d66617879ff8742766d3ddadcb5a60509dc8af54cbe352c7898751e38fbfddae4ef0a2f73a7194eb44f751416e3422eefcdeea6f6933aeaf3cf83cd13194e7c70edb2ef1f3da03e92a991067f1db430e769c0433b53a9858ce95024033444ec1f662e5ed756105b3b4aa3d5a82593fb3ef340f72f2e5280149a05266afabaa23a5e96e434dc44a300516a789d5bbcc558925bc27582ed5b091c3af3aa7f31ab4a796a2399f8c042aaf3a7cc253a22dbdaf3aa0460167f61c514b28e0c3a21d8ec747b0af16b3c1258e86366798c49dfb4059667212bd21ce4c5896c361290927b1ec74dee69f9d8d89056251a9f4b0c611b30f26e500ab00c94b0e4d8cda5039ae351971d5b47c61acf90b3bde65fa3499bce14b1f806e31c434933114a35f07f6a9d60351829b5d7c184690b2ab95b26e809a008a6966117e5c6947d1296319a76c36147fe7bca0459bd3de1e667a3b631b1272ddc45f1a3c22abb12e546b888975836703e2f8b3b99fc0952a8e11773cdc7b22abe49fa39162035f2926c150f41fc43924e40f46845398c4d3292de952de3f72073cfc66e8f1085ac557b723c03f000713925fe5cfaf5c0d2eb945a6a5552509ae778d70dc1e78cdf91ea074a7a3d876932e114ee67e33f85753b90b7db48572e01988c0e598fda6029117d049abed2322e7dc3f2442e889e4ef71196681dc6cd1d16483c998edb16dd48ebd0fb3522562d7be28a269dd9631de582ab13b63721e2cfc6f1fbb91a6dbf932ca2d26c15fcca374dec3d34e7cfcda57be7742617292e099f8218828e91a9f2e4329ad2fecadccf8fe00b6ac20d77b54185e722c72325ee828ab09a28bde55061765f5322ccaf972b339230d6c052b8e3dd07d951d6779295b7d341eab9ff4d85c9282125667a9818eb262f3f96a268a3b24466e723fe2bf368856936739c94b8de2416a185298cbcf32697f58282abfa482f508580a721b4bd868a0f3949e32e872158444beeb9f84d7e15527c354d4f69ad45102f5707f6a53795ce3b31860aa5c758671151d4dfb1a598f8725ab65d477bb46411c229cdfe73ad6ed4baabee6d37f1b12484dab988ac984a61fb0af2edf4c21729b2620cb199a827d3c6eb9d70093c0a2f1fc55451f5c911e2cd3338628ce0520ffbd1e1444f427a95aa4a3acf563e20acf0e1909daf1e60eab3018cec00ab00900cea91f4cac32513238ea5d97fde929606fb09f540391198f5b20236502922a940465ce3da7f035d4081d5737cb49b99f012ed2220ab51061702e3acc399d72eb21ba76a92f60bec0c71ea53ea432721f26d3abf79886fccb6b93b907842c7221e04513d6ab3f0dde0830d67829c776037519dfb53be8f8c1eb98cb1002585e17c3bdee1d8735880e9c808575c030bc2657fa847d4ba23035d4f04d2ee7c84d9fe173493c86d1c3bb4e540d7093a2b661a9c78a46dba1a59ebfc643979d97729a7c7dd2d51abebfa4c3138d16d5db478f2f3eb580260d8268bae48160c77072894a759957fd5062b6466ec695ae62eda66fb0f7c266bbf35264ea24561ab972bd04af4911c0f632ac15792284d4837a0c92ddc972a8b07381afffdb6a9eb87263f2f81b06d2bf84fee4587694bd65e2b62455143f9cb92a49c5d80250288d72fb8ee9f5b0da5350fd65fcd81142ba43708d8e78fcd8a66536250f8ecc342667505047adbd6bc5306facd188544d8d51f154c0c0f64cfea5cbe8247da495ce72907773059c7e7a9f16138632ee9477a7d1c1ee2d09c49e7aa0de30dc50bafc72a26f958ee4054c5853b94b22c329b506e0b3750cf755992b902effc93ba96d726b42c0da47f62e052c8278f111244166834572bb40f70939919c958997188672ca650cd159a964255763f5d2265f9c1403e870be36484110193be14d21f404722f4dcc8eb2ede1d083e9174da2fe90822658f05af7944e39c367aa771e547a7299ed3c5477d2da26042c6696c333b01ba3592eb939aa6845aa9e93791e33eb729a7de74be43b9261cc83f908513c7bf75202805eb7a5ce9fe87748fb1b318961c187c4753a20d4dfbf7cb1754a168d09a25cd647185e29aa127b8b917ebe6572d54ef6e43f82cac54a56001f0d41ebafdd75149ea7682f060ebc021deec87658266a63405229fb41c20b57ce3e8eaa32c722aeeb34d2bc55818aed2df575f9722d7f46d9e2f0be7b2011fc210557e1a687ab44b065d380f71f2c7935a88fb527ec7bb3689c95ed15850e1e597066c9de3ff37ac626cd5db8ef574fea38cb404765adb8a2671b14eda17af78a012ebed668717555ebddd7e6fc5fac85ae7eeb5e674c611f018fec1def9098617fb59b2a88cc9b0496ef441d4421e1397fc27f72b683c94656a0172d5e266f910cfc34e425807aae26b75967efe8e7cbb58cec72389b117e21eeb6e0dda86aa5be69e4b117b7f877fa8fd63c8e0f5efdcd585d65aac7b32ab5b5c8d82a3b8f098bff40f436f2e976b98a50ed6b8b373713b464729be8fd28cb0e054ff08ab490879e62738cc0ea3eb03051d6de24fc640bb78e6c62b4eda2577eebe366c6380e7000b1430e9b38a0acae3719d37774ec0380407290963fceeb24c9571fd50a6ec2da649ef817d4e8139d26c2affa11a4fe8e903ab850e6017b0dff51d25f0c9f15eaed560ddb851d19c78cb76150857e5f7ca9728ef72058b85eb2dc0b81f3062e4444a073c288cd93712eee49852bbc2cd4e8723dc676cae187d299ac168e927103aef55a439805bbfc1602ea5da2e593cca9729e55e8daa01b501933a548235d97f5be69f4d531bd778f6af944d95ca4b55c51508994386dc4a60e95ecdbdeb2763b227133527d06a1435201fe17ed61758a4655977bb67aea895fd76995a28974ceaddc825236e6fb726012eb157edf5f32727eb7b6180c13afa2e51ab32237d27efb2ae87435262f2eb62acc8d540d6c6e5be7c15d4ec9cf84da87cbe61a967b02991851f5a75c3210d8cd5ce111fcd39472af71dfdaeaf70fc7e7b928fcb1635e32c3fc998e58cc5aca24e6e58f66970072d8ca1886d5e2697091ecf08984167c9052e4d2a162e241dcc96a994e0f2edd728d22be910c65664482f616bd62a98f54cf248874888b60235e18ad666e01da728f05db76d7aa48e9ba53d64126f94d88831d6e7d9149107a9350d12fd083f95e66631ba2053e450b3ac0bfd9ca6c5bd74667e9a9645985f63e3e944bd5ee937276a79ee35df3e4468f31547c15327137acbcf12a4be7908017f09659ce3f6c72f7f8a6b317423a887dd198c687044a1f340dca59bf9b1164e47d08753ab3fd721bf11c6f3e72fd6012be2a9acd58814ddbdcce7e1673139d2bf0bd765ef3f0729626f05fa4fd34eac29fbefa96c63a969af595cfcc0d61cab252a6b2d1d30e43df2512491ceb44a3ed83e2b54ce8319ff629e5124dafdcb2f2f971a1fb5ad472488af89059b1516ace68e5e4a5ee5862652d6c58df3d99ef5edbe81b2a5cc27281c16438ba5e060fc7a65ad54d24ea6d533c888de2b4754d930c287948db4172a1152aecf6bddab6bcfe67cdd9ddc36dffdb80957afe5b6cd05c706bc9e5c572757ddbcc28daeb24b6f7e2e62fb758679836b20c50a42d20c7e025da9f451672a0e835f4f2662a9daec1af1d0ddec889ba834e91582da42a408a3ef2071fb86dc5ec1be0beac390e7b38d4b99ccf6c98507c90c532b263a1ac30b637db9fcd72be8d64b4e4f8414b7b51ea1c2a395ede5b5390c40759f324924f8e3b3091053cec2a716f09ffd5096edc02956918f62a32313237884a8690925efc0798921a4c2a67b56287ec0fd9fb33ed3a7722e8c6fa76f61ff95ec3ac634a4cab4de5c672b738b6460928a65160e7c3334bbda2aeae8177382f12d037ac1846ae1e7f495d6485451333c7c5138bd5d8f6177e612620e2e0d442565a2b889c97ff27e396674ea0d19617b59c8a7094dcaae29a675c8331fb9966bbb6f4fa02d98f86bf767222a35c34ab45200b669d1ca0ab143258e20f1a2cdc706967e53368f71b89be6110598dc761f131f865c8dc6b54cb210ac6c9c738e9369fa8be233806f691b054bc4b2d41993a6ae3b1f3b6ea055b334959da6152596197a843e810b41464b43fe25d51bff5c38002e4fc01d53ed90de28663b234e131917bc310d67f1d3b214a8b9a852eb6352adcbb963959d047834cf2121c80ee2011e96d94f40ad3958a7253b6649e931732670d7221c82db9814e5a6e8f611cbcf87c23816022c2815272e49c926ca840da265fb4984de07a625576c0eb855c6d00441d45935655433e088cb42cde2edc7c286fd55e581e3037cbc2adf4e4439af66d7000eb2953a26472b5f9aabca67348873ee863a84bebf00f63172801968200c4aecdd0f7656cde7245e2484ee87979943ea0d00e11164accd7a770d1a3f7247630335a7ab7b20872332097e2fbf057b879cc7f19b3e5ff0ac2bf1062964b75350e672348477a8072d8666467db625f92db1f49af605dd43fb0b072acc3efcbda6198bdbd2765aa72c449a87306cfd0d6564db2f0232667c989b6f8b53e83723f8a8a6c0b1346e772c4c03d3782bf6680ac9804a78cc183673b31fa9a3901ba48830db036ddd6d272433837e24282c25369f55d07a08eef3151d2385dcb600f5b3eaeab560949092fb5aed880480e5a5b4ca729a48ddec4286603520267db51c4c043361f03331e72665ffb80bdb59c39431701ce0faeda5727a0edbed48cc575e8a5236b37073b27380f2ecc199389130c09a4cf73470da376e7de186983ccbdae246bec6c7817720a23233158800befc02ed577d8aa774f8ca6f0520c11079b8717a796d513c572c5583151ec23f08ac2f261c2d598731472c42620b2d396a80bf81014147a61722a6c030c6e9c2a51f5e4d2ff344d867be8f1e7e11ba05b33275df38354af991df2d5f1145349181433f1173fe8b01078b4dddead1c2020849f3024ecb50a211dbaf65ade106ae5ee93c62d7d47f9728a0674bf280dcfaa0d6e1edd4080616c72d1d8e8d49d3c6eacba6bdfe3304514285ef441dfc5f9af84eb5313b9b36441729243dc250f2f7a43b56694f2af0a13aa0caca505a96f333ae88efb278eec47726ec7bc9dad57065896c89614970791c0767fc7ef273a964005225631b5644c72bfcd1afe215df5d839d0d9aaa157a73570256414544fbe8e0e27a8c5a86c783aca5dc087b543f35a57050c337297b9b2eea30e0e3fb199b7828da13bac67a772951d7cc0d7cbd181ab70c869315f8e88b62f0f673faeba67deba5b73970f1a3ee1e79b200986a22eca8fc592644053e23f3bffc0a3e57a2c372b9957a4b35d7214f2c25a2418471bf874993cb8af88cc984a73052f40308134b83db7b7abdc720311808b598f256cb6c1f9b4f80dc335b0e12b88427bbae8c7d0f8caf5983e4ef304b6ebad56d257a495e53b27886afe378c2a292a85e43973ffe796fc1f7072135dd5a33a5831f7386a309884f97b8c19ea2b6d8e5919e72a86b753381a0072ece6e338a365daa0fd4b49f23faf0026925845f155cc5d56f7e8d46bb702a031a7d646a239165d7bed99b5e26ba0508e2780af70375d90a9f89f4dd64c149c37c970d5653247764dc037e92f7c58b71175526242205561565417fd1e78947c49f0eae7d7a0a978e4e39f28aa0aebffaae3054972f199a0e82db31ec7e335447231fea188dbaa37e8daa61d2d95c926398030cb19747dcac3c0578d05fefb257109054e75ee985d400c2ff3517efff85465810536b34e2dd424e3be3722131624a338f6dbfd8d5cad1d296b7ce3ab8b1cc32812918d294137bb6e17ff82093c3d9be684bab7c3fa91cbd1463b0522861c70cc59c6b3c53c8967c5cedea9c2f35f2c84d5b4e145ebe0b8e47c03d166ce8effe6ff6dfc8c7d75632bafcdb3d9b672c84cef824d515430b43daf8f65840c686078d9f608880031d884b74ebc32b37246482a529f3c928b96378a248786c6d94d54f15a2f99cbe9a074fbc610caf66e87cb7eb0849780f8ee4c34039611011cc2076c8b2b66ad931d7ee4e1e74bb772f706a2c50c82800ee0b3b7d6fa468a2eafbc2629ed87dde1d90b4be8d1b504654a390d6f6b1d5b295e825f9cb40740ddc77b71ad4f54da431689076408d3cc72bfed01454cdee8a4cfd69476920efc277b7702e2c5709fd7f59c0bff8211b77270f0adc22fd664b883dc453e994980834d45c62b8d86e690b057964c80ec4b1b3bac1075b0bc54af9e83d325811dd770cd99670160aca096a7a88102f510315346bb1a00b490770682518287cd05704d086745ea308d31c506c548502ab2ea72e9428d16bba11022849289807ac8b502a08f5c7f3cc2b63da132054f39272517ee14b81ef39a794ee13d208d93aaf7365381fca02cc3c0d3a1a9ba45eb01ca198286e8b3ee924ba2c45c715aef2edac13d830344fcf9aa5d27be8e9aa409f81f841d850554594ba2a8b13e89ac1a991a384a1f962e1999562fe905adca4c3872e2471c5d902ed0966e66448fdec5862876fa155feda787a5ed445b2aa88022721dbdbb81a75e87100fef9ab74750fa8b85b3ab61eac8232858eb49ab5e4741062bf2c5c787b3dbee6f06f52e47d01fd1c89b274a44d68e3a2f14e144214adf720de24b2efedc1fb57a16da2d1bf39cbda9bbe008a96f2c2eceec554567d4c45847da294370c69ca3ca8568ec46653eaa2a48100b4eceeb3cf3314afaf9a42672ab2017574ba2f5a9363bb557d6a6dddc534bfabc1b117cc221166d607165d672a3ae5df7e5f001e69203ce713986ad5691f644a97a90fbc19e81809ffb203c0970581126442bb056354d488b0cb0b515b1a7b6e59c321fc8501b43eb7a0b6972fe6a96766787643344f20f66468274a48da464e4f5e73d7930d1223ef2a17f7260223d4a98744a5b3a39ae72647d5e53179cf20664ef8d805e336bfa244e1218102849477a3c0f958503793c7d9b3c49ac4833c659c5981600d6f5df76f69226dde8734c3e4afd900098cc611d62b1625262f72fc0fc9bd14ebf08cf12ddd772566b2528c85f432d0620c9d0d8ffd963aa805c489504fa99c82f18b27519ae7251fa7c33ae9b6b8cc035ec97a2cca2928d28f9b94164cdcf5e5140bc91c1de7252f9e93132b0a3f6e4b143ebc88f89e137110b271fb4738f2500254bec9bde1646d3f13119d87e370a2fbbd4b1f8a0c2fb606b31e77b61da6121aeef42d3eb72ba75facb221ecf8fb8a682f3668c9ea07f73814841fcfa022ec7c8f11f5f2f6b8d1d14605d6e6059e168ddd8c4846166f9a2d9119a833ebfe298caebc356d572e7cfb5f269805e82dd2ba29f54498a54d4fd4da0819ead1a0e48e352a922e8448373d026b1cef9828ed28830fcfabfe53f8e5749af665ef295d0d536b7afb54ed7bce86e4b8c9f41b51f59d1ff1d896945ae508b02dc2055f84764f57afe3648d49417b3b68d824195fdea2215a5ac557da759ba0c2c9e6d41e34c84ff9f5772129b0576921f174198ba38e6ac77d9d5c0966a0663338f0d1fee6d7cbd326004536301b25191e23fe50f5cd3800e9882fd329435b6a2ebb05ae2f935ad1dec72c5f79c4b40001b9bc799308096fef08451081422d881befa35433c0f49862d72ebf53a56709a48ff9b55d81ae21934b4618c777fc3dd3a0d559719da778b7572a428beb7d409ce87d7f3abfda3d271ec4d55bb3104fa593c33a3874755652154ea131a7ad294adf80005d9deb7e60c5ef8afd0f51c2ae1698b28d08635bc32729fb38c391f3c9b18ac1280bdf24f7005384cfc0d36e8252639b7a34bd5abe672bf222265ba1f10d56aaca66316081ede58407bd0cd81dc359dc78c5a739cc5041123523708133974f3feb8e7aa9e432739b207f5edfa43779eea53fbe029c1594988a235550a46532aae960d4323d65c64907fbca003d11555a74af36bd289722e9e8d039727ad5075e6b0e72b2328068966a1c2b31edddaaf40b85028f34072d70718bddc050f622609e71fa0ec99b426e534e40534bf17ccd3f82f17dc140a07069e9db70702f069504b460ef28b1735b0b734eee8aa25e831a747d658c068c78a3745c2b390bd7b3015402e28aab47574d898acb11e7e5f9efab528194f0de19bc85905cba5117ea4c75b768a99287bbd65cd45240febdcd0937b2855b472298869f72c872ed1b02b8a1721bfa9dbc04aa35741ba3f4bc28bff5088d40905344b71681cd37a2c4b047429c2e47e263a140b04ae19cfef3ff4d6bcbb819272d31f81e660fbc349bb28fe9c4a570c7da737cb47356e971d909489aa41d9306e1c7f0f06fc9843e32ca4770061432b0cf97cf5ff58ad0cc2ae23620041784f044cddc019642c23e8273296fb646846553fabb3ef8f7caf077afd0cb027594a253947cbb587f3d7598be9ea7ee687274168ffebf0e7deafdf7aa21e7d4741297220211e6938b90f68e030f0f8675b069a6c9050202b26fbd3d438aed087950007bbcee71ddeb58b435a157dc0de49ed5a17b798bf0573f02214c839c9aa23dc7275022569dcb61b334aa14d0816e4370fe5bbbab7c4aa82928015611f976a8a7211458ae75b4854a3bee869ea9d7e2de06454f97902de2b0db5ab614b26b71522ad260e7c5f62eec2216a09634bef3583970b32571ebdd01991fc1f23fa255e72c2d3bb1aebec54246cce0cec586a8f524dcd8465072449df9672b993867ed272a796b9d77a8f2280c01c27a85b97d5d55ca49f4d5e7ddd5e15c174be71e5927247afb4fed8c623380278c53b8895d5ba5f25c91092cbbec201bb7f556cf3c8034cf134926cfc3efb9ba864a6707b8368ac153bc50bafdb49c87e59302d497545a166f30d06f07e73525200b89e6813eadc04d94219e424a63118bfef2e4f067256096877ca40494942ec526b65e84f4794d22bf5cfceefbdc84c107ff4a1d66a4081ed499642bb7bec5f01fc48e7dd8cf0a1c6496e592df1ba008b3b7a0bc572ac0e942bcc056bd1ae5fc06ccafb7e2d6f8e938552e23cd758c0fc9978430c72fa27133b41e769b6d9923fde98f2c88c322fe373d012af9f5e431ce30c023572dbd13be201995a3eeea481d9bd8e061cca52e6b9c149d375d0d37893ec6ec772cab6c213fa3af26988844dede103db4c29a49a849ee8b92facaa843ee37ad65f37f45100badf3869d417f637a186144e8ebcb0538888d3726aa411e7178c0c72163be194f8c89fe09779307fe3ddd7129bf5b1f41a39697378692948d70cb83a5d0aa3b2d778cd23081abe3c4c0c316821698c2311d56457fad3fd482acef2721f3a6bee68d620b43d27046ceb4b38caa05e0b53b5100f7574f23779b645d73061827f65e54c25c01de7bf20bd788543dd5cf040cf11393c5052b0320da0b1390d133cfc7b2972ae492cc1c291bd91bf0859d992216995e10249193df50bff6abbe5206a5e73ce7a528b11689e80ef4a435af9122fd19237b043fada23a18e601ba1516e31b2df8d9ab47a0e7de69c0c02d95d86fc3a05b655715cce8c863e720ae5ca2f7d84e9a2a4fca00d2c4733f7e4fdda3e573921cab9014c0d9980a172720782d6930532646717509279373d2be0230c578f6ecf99984765aa4a1065720ce4df86439e51583a20b461a6469cda9bb04ddda1167ae3c0ed68dccfd3d4439490fc33c161173dcadc34d5ab16c201dd9f106b18c05ef407924d786089a22b7131d76346d3c652e46f135269faa9c579ca5333216a30066aa76d9cdf421c7238e0045db4838da4aa25f1aeb23739a5a8e70905e6c58ed7fce69445bb893a7291e28163aec3fe716127db620c1266bfc35c8b98356840d4fbd2c7910cd2616836c377193cb56c3e686c68a94a6a3ef865c69ad923f41239e8fc01356c392f72d515824ddefb919c46299bd69954104700fb023af385569c16dee9d9557a5672ef6c13514ae7612379a0ee88e1cb94156c7760f09db162c31803cbaa9d544738409739642025fd06e89dbd68988d9597a70f2c81b95689dd5ba9e61e7c8ede72fa5849ad5fa4a565e919ffb71ef8946e6cbc832f18fc1687a13a128898e77436cb52e239b6e2c8ae999c67da499f357b4a7309c3fb49c957c9f33bd45992f072908125c41c25d3923006ac5eaf99ee0ee9bf4b09019f40154016ca5ca3b6737228bb243bbf2f8ab1a849d9a9faca66767939a5d73f40299a79dc748f1be11172d155e87af2aaa36875521d8f4499c27d9b5b768933893d13a8ec018c3255407246967654addbf5e3bde2f3c320a24cfaeceda6ab700a4ab0e75c782e7fda106012c7a7a97553216be409e61c40b2f4273a9993724768e46c0f57f8ef8ce58672e82221963595136f94bd01a1793ea3836e2a0646f04ebbb32e92654d1a6be2247c3f463e09b078058d17b66610f3555c8221731f478ab53b6f8ed050d4f5d172c65d4485ca358907736f04834bd0ed2cfc253369aa5fdc22c6e568d7ced66c72fc9b9b51b569394b8d1d6b45004350919cf74ac7628c3ed7b8789e653a7cac43ea55d6161915ce3bbadacad0dec776a677ab9b25fb3e6c9888dce2cf482430729030775fa0bced4e67776423848d8b928d792b8382ade76e34074c84d38bff2caf78a645ae7518dad8bec3fafe62560f9922df14ad17e82a8a6fd45173bfa324332a2bd0cedc359fef324e4ded921e70ead1bda174469ba3a6c45f7baac69a72028bd95c2e9818a3911d34e9daabc916f6da609cfdbfafa8e7e973193210a87277e85fafde4efa6b9048ba135f7681b45296c727c3a827ac85dcee1db73a4f20ef24ee18cd3a348cd86459fe856d32403a04031d4306d5fa23aaa7152a1a8132517dc628a576b8ceb0ed60256f28fedf6d96db52ac67475c0bb6ffae4835a072cd06ec66df4a31c64bcbb6ead0a159131deca768bddff897c229cb29cabe1d72a36c348f66be823ddd6ca27856465f7652959718079bd6125f32cd3692c0ed729d4ff55f3fea7590c3470df94aea5cfdfb49e405e749a8c07f969358bfc943614a90690538754be0c1ad6a307768d8dd0b84db219dceddbc3231f31aff8da5561ca2da731039c0382542e83d63a80f3f1315b3dac7e2e099d2b22a84db29035b1926cb26e4ae7a95af563af4818c3336502359eefd8a8c6109d4fa8acc07df726bc581d09996a91381ff3107703976669a3c5114acad6ebb879e643039962e1728dc9065e76420f1f164aa498f4acf260598a3cc0de3b67342e9be116c8aea725285df508fb47bd8f62940aeea81af93e6f14af1a78b000ab27252fc4a2ec072b6618287f00845ed2e5be7ac2525274b2254a34f8d1030ad1d212401985865723e2b7a2dfd620683c091afff452f8d003c76708bfb959bf1ee941d043bf32b72b4999cadd8598e508f704489eaad28220e6313022b90738158de7123c235972eb6665ce9f1aa0a70c248d2a9d27df9efb2bce797406ab0b5ee2bd4537bc3847295a8221fc12dbe1b9e78249297b69a35f2bcb7ad0033a30375c46e4b0153f672d3538554c6c557c83d07efe0fbb0ab97097d5ae06350ffd821bdcf6681c22d1f0993c5505bdb8f2df6a41fbcfd6a13db6d053a6850dbb3d4449b1448f9fe5f6b6c3326e96db39d37df7553937f57c9a6adfa4f8e55e42a34aec27eba0995467210fc9a617b851b6bd1f9d52ef48f050f46ccf6c7f03e30e7d4af8fbbd1a89372ef95c91a9567b78b623f904b651a8d785cda2410157e63820c21613b9824216f7ade8cb841d869a851c7e55ac75d4615296cfac586f463a64c842541b22c4c53b439d268421ff2b5140e67ea273207a64ce0b298f52e584d37fdc332f14ba17220b5b0e004ca4b4c1820736f9e05ebe02e1ab958e13077644d01abbed9980a725ce3a5de420a9dc08ddfbf05197c89c93fc826dce93536b6bdf1f27dc43f737217d1a26848be512f0c6e3f52fbc115c8f5eb21288f8a0667eaac635e2394b06fc25491ac1e19e983b0393238aa1c12127e5c8d3bcb06fd9ea087bc565be3f17259261f5c11ef5ff48290cbd42828741e1c8187000b05a74eb9f322800f70277269c13aa140e86ec9f90c8b2af955c790d7f7eb0a443926c87bc0e56095bb39723a7b4ccb0e942e769633941666c3cf8f53245bf0cb5c41a03c5c2f04fa6fad4948171480ebc0608cf3c52d7bb87e8db6c79844851263c820b125b89be70253196a278a9fa771b00d64009d21b0be686934467e892b1320f5b1c07c955d0669727fa2df4a997e1f5579b14ab777c1f58b10b5df95a17ff7b5d7a4196daa344a1bbf57794924042cbda67e2323f0396cc25644de983ddc1bc0390251addc30d525105d4b991017e0e0cd63fd6981a1da146ed600174781573bac4e3652121032722258bacdaabc7a56b28013ca063ef3c31a602193bc977c132e85eea95143727269d4ef6cf1b9321e89a730296d1da866d9a6d5323136ba9ece21b5807e597772f3f70772bce4546b166c9560f5b3ea417898e3affd86385cde7a16ec757e9114b0b2fcfa6a8b2a44fb0308c91f76f1950376a47600ada67b3746b33e4efa827272f2f61b5f0acc0fb2974aecbb7ab74a93c54c0cff20bde2e20a0e3397db0600977bc84797d908641e29b6f4b884f70018f2446c862376ae5a87b16eb0781072b3ac4a32dffe1541948486d06c8953a2a183b81b75abc9eb1b0cc06c24d3fe35bc2315ed7ee7a985235d658595c260915c392f192ec3f2e819271d40fc86a36c8c479d8c6bd33d1aa1afdf596e91556bcbd71a80c89f8f0a3f874199df7b7f1a0b797a4d7325ea7c657e230758bfca72c555403fdfde3cd61c55eaab6576e0451c06ad74c5ca409d585e68564911bff4f847a2f4a54371ad6f0a20ce87e9f472463135aa039f9e7e649bcdbc545fa763d3ce241ea9314ceea4a2b876e883a1251707c1ea4a526d320efb86c7fedd7fafec3ec3414da80ef0c3af829d7da36244f9c148ac8660b805752c562390a8f51fe797eca8019030d295aa8f626326f7725f8f592ee0593aaeaa7bc04fd52ecf198db9e822a30042c000058b1003849d2831d4a4ce0428a5769b0fc0e98095266402bb8c973b15a8be3d535b9dd05c3672be41df23bc30f3fa2ae3dfb40e205f286ef5fa440b821352dba31f7dcc443272b66139a911e80ca495e54941cacacbddaf228ff047cfa1b4395407bfb34413127ac894bac1621036278171bd9277e5fd25655a6f1ff5091cd3c8b9e47970bb7231618983c23acd84cc80fc9183c03a3c2b8e70945c1d69e9f80c5b7d19055b71995427c365bf60a46f354053cec749d52a7474a5c78071f28ddfae0c97998b080519b6153caa083e8dcfd0c16f9ccb2953786ae1bcd2bee9b4135ff32551180ae9b72d7d8984e02876dcf2b4428334a1f132c9d7da97b8a504b12b11c54fcb6ca5a74c529868aa9ca1b2c8c38bc1c738082831de84c8b0edccd8c4300dc86472d9f37cb77fee0804df0308b949af88049ab7308b02d000fc2ff7b79a3ea1e5729a0e26727cbf15353dd7b009aaab40d04f46c0003de9235746d968665066fb726da48da25fbce9cc6bef8e8bbf69f2072f4092ee7b070c2b6f6a65f847bc5824b8dfd2cca9eced38a2eeb3094c569a6ab058ff4d54f00f92b0928fd35eb1dd720c6a632e72238156b9a2cff434ec25733833297e78cbe08448f586aefb2ddf2b47ff7bf3afaa839da3f7efb4ab62f1fd6050529a2b1e3d05e6ddd828732f95722d647efc640e42931b18b3171115e63536b1e63bae3bb8bd74f2641e3f248d70e5aea405796b87d3481a231e86179ab1946fb3859d49244a0c170e90368e3207a80ff83dcb7e3a1feb79de164a45056a72dadaf984508d44f2bf684efb44ea7232c2c2da31d9bbbdbd957c3766bd0f5a58f0e0806c021bbee29716b97fd30872ca5a9a8fdb7d31708ba1990deb1e93ebbc39be65a8d324fb352baa846be95b5ff8424f42571829f9c6eee475817ff9b19f6f79a2c36a02f138aee1ef068019021c471f043461f078465fc89854b526aa511e551f1c223b3c5e8c717aed1d2b72dde62ddabc696343cd6af574de8703dca3edf36d439c854e561e2a2088092f722f6273d1b73bab618e923c82336826173c252aa86f19401b9ac22c696c3ea072c070dc75e836af7242e1fb75f6b0b2807e496db378d5500ebaf3d405a65ac65ac5c37c7c88355c922392eb86e2849d899934fe62c50382db8e1bc3ffd85f4e38e5929c209efab3e22ea0ca39c128f81925742c770e16623dd64906c31edf51722ae2d1a1f2256a33b0a618515b5fed706ec9aed2b2000b3679130a9743d48d724673d4240bc93eea265cfef300a5cd69243ee88cf30445800551180c3e0851721e3f697d8ff4ba5ce6a8a81c2588bee7e021fe834720cf416c3b9bc33e05037203bf34d9fd6e74ddbbb4ba765034935c080dbab930582e8d0d1aedb0e228fe720ce7c20cd2fef1f545578af1f8c8b9840831a846410b7ce357cde355f50a8772eab4ab4a3f7da6a9751e085f3da2b3dd32dfb227d6a1f9c2a29287c48140ee72953b24292680e12ae4e13eb40f5fbf51bd728e57769b3fdfc0fb1cc54f36b55362760cd2f8f9a9bf04d0491a09b88131b284428e6f163e27b9594e69cb3ebc72ae057ee3df29f3c0c091948f79c7386aa6f43061ba7e98c60744a59c3c779e07db8fad0de481d6b7a48ae4aebfff4819bd11565f39214d4e08e6feaea2f55872aa97033be9fc502b51530ec69b8769e5432f0237fc30b3a674f73f28dcaabb723af6d6c59e889539b4369e9458f26bfc434403a8c2af0b9ca03ac703b8aba172e4df0ce6acbfee04ce7f7dd55afe525a26b6c5feefa250c7f13f3bb151159672d3414dd413ab1d78a7cfe11a10418016c908b1ccee4f0d78e2487cbd8d95d5722ccf0c82faa250454defde0e375fa369cae2315f59d9f67f17bd88ace94df34d85d22b50fbd4d6b6d0ce22a9023f65138f1d588f7c4380d56ad37fb7f7ef9c72ef4235b4f649d6e470b2e24d1129c7065363ec6131c4599c216a89e5ea4958385275bff186b328035e3b08fb5819c7c7b4dd9b7c93c54f758d0fd5a2c37cef72d2f622b0f72d895bac7dfd9ed9aca52a8bce45d90354ead54f27a804c84417671f7d9b21eadcdb36fb3472bf938149d4e7c9081752f3b8e45d045f80e9738f72987cea8dd037d5830a78a4ea959f2a21d813634fd40e0238ea07ec83993efa6a8ff8f4456de1a35e00f744122b84ecf6df9f91871ccdccc6850471e271e9902d86aae98015e835b17068aaeb0cf672800c3794da0384ae04e8bea7e1d9f541725d6718b096ea355e2d794f4861506d8150a0ef5a09001f86dc473d82659dd37256dd2caac5023c58ab2619bca0fbb5c53cbf0db5a919532c6ac7265b8da60f53c4f4e30ade947170f20e3c2f49807554269cc528ab651d6ee28a71b285ca3f722bed8fc139f9ab10812dc1274291510b5d0987e2a2562b9626720d016f6df372cf329a419bbe3a8bc8be3bb4285830e146360b9e145d0923f97e401ee7f3203872128abf940a88a48730aa4a59346c4faa6ff112e669f72de86b7ae2c83c6b72bb65f56266e341c3134a5bd6a2755e5ce0efbdb082e8f880cdca9b84e845447245c81f8edd62e1bf25bf546f272eecd278b07a69959426c2babe1a90badd3c72044da55d62bdd78c3cae6d2f5b2d8679aa902cc4dc4e70a4b70954ff74dc176ba7953c3074ae52ef54950b09b5cf79b76ed19a09eeb989f94a6d54ee664dd772213745e5c73750e833b17f27976a4a9e9a097d38b60744957960d2946aa4b631631f2a8d249b7d72deedbf689de04ae94c13f2a2606e0ebc7b3caf7d78d68841bf51c61c071a517f13e3869c05e7658efd28c57d24e8ee7b1e4a893512cf5772d2af1ae826d96bf045a1134b6cfd752399b785a8117fcafce882e466476b8502e30d49842357beca2f895f0455c02100d3b8b44365b67f4816db4869f030607257ef3ae59cadc4518b3e38f6cce9bbdd3d0b7dd3b00a02f807063cf8ad799c72cc59752a42fe9ac451c9589c10826a37aef9ba9844d30627d5df424dc590183d2e2789f50c22a656255ed3edbd72d9c93ac9d498dd2d4fd0572e0a23c7a4dd72adad249ff4248de034a890d0c63ab164e9a3b8ab7ee32c5f761f3bbffb00a372baf02e8659d60fab619a12e53aa68a1b6dd88e86ab8a6c6a5e4cbaf293f6ad725e368d8215dac97f9a1b662e2f1670a1818c3d777c04b7eb355a0f09569952580684de4c8e09c774089bef4903f7db9217bf9a4e73cc5ae92dd6c14e1e06541dbc6e63de3bf236906e8e4978da0b2fd0787c135caf07086fe3ef985bbb271172212e2592d692f9a4f8c2a6639b3bb9fddfe4332e668c77665291fece1048df122fe138014a611549f9215579b187f4a0b652a666bee8f73732c09be6ae4ab472998e5be1dbc74ff8958981e75b7c91d4dfea896a2e97c5358d04e5bfa33388497525d4f9709cafd07a54f65a05df7c4dc73141cd86893b9101d9d7cf29d7f872d4a84a827aef9774db14e49e2c2f8b2b8846408f962232eb36dadcd49505664ad57d0a59bf3dc9c1b8eeb19b5ced3d54c741a1142d89cf0eb498708791380672011b00df09d041eaf3c40ae8a48e71544f7fd7f66ff36a06fb230ee3a8ad60074f8bce0ea021ada7beb3abdacd5a4ef3990a6f6861b38fc1239bf9cc79402930526379647d01e4a45911c480399dbe9c716500819fa6d11585ebe791049b607233cbeb2dd39642f4919f665b455979a6b58385e15c90b23e3975a6337b4f167210a3a68dd02f504a7e34d10a337c3112c34aa385b3659f7bddccf551c11c40723811dfe2a4535d99f7fc3355aee3460e63846db6928cbb7ca18f939be5801862ed8c9e67801356d8d82cbbdfa0cb8727b8384745142a158cb020c1638eec7a727e603079665fcb5de0815b5e72102aef4fd2dbf76b5c7edee1531031e8b56072ee32cf9d013cce104ae1f95c9e9a907e04f8b4a032f0b57d9ea618cacbb539725afe1ee992f42caa4e6f21575eb1503ee81e38d78bded1474f2bfb9501488f72cc16901fbb038ebdb210c5172ed49b115addfa2bc5a19ce43c2510a88a930f0ab9ba044376a12904efaa0dd08d0ec7b7f0fcb8031e1fc93d21200d790c531a727a12564af046ec2e27715f02677354882aa00cf086fc7002f6832a9b96863072706a29ca098f5f1b3687934f7f65a9874f1284f4a166d869990c3da23a8b7345c15d7b526d9c1bc5869721863dd1d596aad54a0ab5a67993201e6b35cd8fc772a414a019131a2356c178efeddfc27e3466e0660c2d4f9d016b553a0efcd27b179fdf77448e7baee2ede7edfca7754014bccbd8ddd2d1932c6cb9476ee2ff8d7256871ca36075fd29681737003f5b144aa99a102eb8035fd8ea93c242d6779b7227d67f8dd4a07670fad3a69316f93b0f5ef51ab06c5c69aa8a9a063b583ffb72f6427e25d933da84a1f6ea16e993cd88b0d102e0c604d481a232ecaa898bd97284758daaaabb8bcc85f552091d748175217763de58dda285eddcfa95354c0519c8433c83afe6ac8391595bbf6d44770fafb7b08369adbc7e19a28b4fc75d3172e8114120bfe5758ae0c90f858e81d2e4b545233e5b137ef594cba945b276e3711a5fcb3be610293278cdfa80b287b40545f70fe533082c7c81058da31c5e85721dcb005a8ca83afc0ae7cc71ea3d079ba9e4b228c9953932bbb3342d4cf77c7214b0befbdd43e294f3da08435b03c4bc1585b33a375783916097cfd09e67b120625867dc6dc98986711d752d0f38018d271caa3b0977e3d3dbecf9613a9a9472c74100821dff43bae3d50bc7b402bfd112349787387eb04bce0ab422474a6369c6cc9b91fe0a586e02f6517daf38be3682643b15c808c9cc717af9d6a00b4c7264d74cc93581f6c5dd1035e8226eda1a14829501242333227a8f6d365fd6bd7219b2a4b5a4f09a07a7f2c05b771815e97109d2685ce33c5c4d4fc6a3e98eb172c3d0e5eb65d7e11c078dae52614f7298f7aae3d66b035ee7b715b981d564b754813abb6e740ba0ef337eedb77e04782763e662282df29a8ccc58062056447e722dfcd773aff5b4921fe27bf34f47ca709de0dfc4af336ff91fdeec8eb1bc4d4bd9ba41564ca3e2fbda0339123136e57a9d565cb73f2a5781f226807638e210727dde039031cfe020cbd5ff0375f2752e594d1c9683259b73669bf1f689424810bb6c456dafd4a1638206374c2f20ae3f62ea7705ff09bdc7b3e61feb29943972ff810e77ef6cb5918aadd7f7058385dbdafd7dfec8a4a0fa5f962c0e4d55d7727fc131beb212be14909bda005f6fb63d9f8ed3e6ca1976be75546adc80ffa372c451aa0e601276ec015ba7b07f1045f394fa7a0358815dd89177962fcba0302a387fc161ba6c9020001398078dd8847877c811219f83ded451e2844b06fb2472ca6e075f650aa8291e92dd4fcc06d283344a0ad54e2079daaf53c1ad7eb83f72cf857f2df4b9eb526ba093d6efe89787e361e82e07c0585703e45a66388f2c61015962b45af03034fc08e6edecb434fd24391cab13e4d1021baf786d2c6505720a8b4834bfeed27e9aec8ce1f4c20693d521f59f36a78841092f46173c59ec72408053b5dbcc4ab1605a6c17944d4c5bb8f1ce3cf9e321f5fdb94ad05974e45f37c5df4ef153c676fa5745d84ea3e8e104c909053bc0483b5eff51316077df726f1331784a69c24e0cd75c461c5951ce781b789d6fbfb170cb70a21e9dbcdd72f7f91da6586d9d06e894e418a8aaef84b7ba40a825ca3a3204f7e2577253db724ebb544957ea3bd9b77e83254cb71b49eed120ba61c7e3bffe6e9cfebec49868337f15be5f8c6765ac12880bc8c77dc0d0a52f5da40e60af859edfdaad0507287525975fd6f10e7d8d92c781a91ca3917d14757deb19c1094a361526349c4036f49ee712a73beb7125fefdc008f9968f1acbe876ff3d5cb680b3f834ab89db721e487d0f403ce312f59e1c0eb6113a588bdf342fd16320a75a2106498e2199721a49fcdcbf483ea407e5514f79fbdd69ea44452fd86a735f7e769689b1565e3488a83b30c412b81564d5cd4fcea8ce295d3f46ba48d70332713f76e8e176434543f63c7357ca6c8c805a5aaafd5ed3ec0f3b05bda11e1cad52ddcb0f77e5b0500206f81132bea34fd10bfa4877856e0ae652684890d5c034a7d7bbeeb3d4b4722ae1ebe2a709706c32f5c4697a570d762a93afd2e926356756945f23291a3c5bbeea0200d902603083cc162bcbe02e449c7d8c4a922b0aaff00ae8b2a60bf872e7627cb31b6a01ba128f4cc57586b2cc663e3ee99338bedb7da68077f5ea5f720f8f466b2607dcb5cdc362629d9bda27bedbcc0ecda762e56dc2d8389833d272115e9b785050b23a13ce27159f9310250e8339bcf2232f406687a81949b90b727112b1f4fadd33e49efee7e3e2f88a517591f4caabcb9fd0fb0e34840465bc2ce0b67f8c8fe5097756fbbece0a5ae5c2832ccc319ea8875fabee43faeb2c2372f7520b92815e06a1d576259475b7b7977ec9b82e356ddf24bb3f9fa4f5a00272a4bd2bf0b917cbd0fd14062196a1419b13548cc9affbdeefc109f2ef4626db1fa4011c2d8873e93c7cf09d05fdcbfe569335ec1424db99ef820b1b6cb2381c2e18b28ac59366afc548e05f44d157fc8f984d5a969bb4ba51b7614cbc8bfd95723b53b24d1245432580867d6dce98ae6402501c998e113b98d7f7315231b1850dcf51d7834ed36b2d9649e5fe79044119c223377d64a49ea40ea30d787bc79418c404147de653e6d63e9ef369623718bccf35910c92ab888ea9da48ac51dde9432063120294b346944c08c4ecf8b26f0a104b22aeb637388c03099c5ec76f47083120cc696c20da3a44037c9ada3d48eff4c1482bc232bf4618e0edad45fb3f431752e03377d04774cce40d34a3510f13db2fe5ec21e269cca2ec9deb79bf1618494a6d1475fca7c6b520b0792696a7794e314b27d6bd23dede4561dc20b5e97213bac4f1cff638798ea04c76e718e6d0d6e9ed038ce4566db405f8a07a953272d897d6a7b71f58b95b33fe420bc8a9eb8d73527c0a30bc38eb9e0bc152d5a272806f3831c1fda29a401aacef50f39c1f46b91f685290c8b849f16eedeaa6c34e04fb7632141b529ac41546e466a39e59d56266a033e40fafa43dcb7471f3cc1facca01412e9e6389fe3f363ca8ceaa9b50984e410399333905fb1bab9fcb3972b86d0a4e6c04db6687fdf3b4c2fe09e8a1945973bcc3a31d4a7c13ff75286e72678a0bf179d5e021f957dddea5ab90745796d8f116142969d989bf4db3ff5f72602e2270b683af2c6617aa5eb4444f571fdcdf008c3a1f588f70004482aa825b7fc526c69321f18a885a75e1209220fbb06877e115d628f655e07cfbd69b160249a4d357cd2a40152dcdbac6aa6e415f656c04bdad2429b763722b486ad20b680a3269a2b709da198e4f70fc395546f90ad61198d378e854e686a6d9b1389d41e4ef7d6cbff60472357be23d252fd41c63be2603091fb16ab7e28064e822856efc9a15b93a5491b7219a8a79b0f3953aa284d8139020e05ff4b954b3737c1133d0d43d8290e35993fee2bf99b97a12869ca08f1390674d4845a220377808d83269edb130c9bcdb768353d8db263e5643e97e22c967e6adf12aae82610954a114c53aeaaa015f470018f7b7cc0910d5d70e591a49bbf9f1e363f2fc753d44761f2692556eefaec4e43ad4f2e4999d0c80a4e2711034c2cae56c803bbfbb139e72b5e39969b06eff81c071e30feb2d53be49b1ca93261df6fd299ac28626026072d55256f48475686d2511932b22d38e6c0b72f8a8b3de84e96375c36b2e27003bf9cddbeb11f02e1d351ed8efb9271a3e30be9371db1f56d169a3f559a1d8e67236e9612ea4cd72e8fff4992f8d1bb6c4e91b051654b99c0b1aa0ebcc9af5eb72e55cb55525625097d32656b3cb92e312b29ffc297536e6280b5c2821f8898c72f0a978f04865ebb62908a6b680a6fd2d137ef65d22e420c5e4cc48262ed64f597fe2602c69ee4073d3f19c82c23ebaece824ef2cf0948a5fe57f1305e8a5e0728dd2f2e322a56379a3a181b69c9329c2dd7531db8daa377d8de528f996540c723ac291ef331c2a3c0dc1fe9e82be3fb76b2c292f6512f8630f47936fb0206b7276e0efa9666eb5faad802b979b7a42e8870d7e7fa61e3e45b2cdf78b10f7a17265f21453d15ecb3e4089cce4463208a78f1a18f291db94f18b8a8dce2c0e8c728eea9708e1ac14496871c77abe4ddad44397f1803deb32c33170b5c56ec7483270f2c155f462b1921113fac6e51d130728233a58c3dea0eb1034e3fa73c9ca6b0639a0dec328a19bfc2d775cc565ffb8e5d6762c354170a055b02485a0d0b872a4266c74561140c19faaec6f9ab31a04f17a5cd74723575b09ce70b24f393053c72f3fc96eb89aaeda1e48959f8f4c9ef0d50b65bec7a95cfbbd1584813e4600ad722806f8e6e10119aab5e5e4efce7362a502ea9d31b0d682dd4dfd689c9d72faac0565658bd620849f7a8cfa42adc645e22f667834ea9abad8fabbb0a335416643fa017c8b06fc3adad55a841db9b184ddf6926e5beefec3787bcdc1d25f72b8e24975841b21d2108bf3b2778d5792ab1b2170d12cb05b60ec727bf9d7183569f73f44927c1ef3f82106276aceae608ef2bb75bf3302a58b19b9f52f8ba7721b615d833fb2b0678d71262673f15fe09f8d397ca2368c8d7eefc634a02d9572a4422fcdd7e711c9bdf2744c38c88692738323d8c0341fa8767a496c6de6de72817bd668319151ffec70064c404ce83bb1758544c567c12e9659fa07cc5bd86e468de7252111f1a74e7c7871142ca15a52872d221f24de3b8c065605358c08535920fd0bf4291102fce7dddad609f382ee5d937491e3f0e412139ae64352e072394b04690a1ac0c6dda7f63deccde4f3380cf489167b8aebf6311fa677b088720e8fd2f57d6bc6a5cea88db7c80c49ff9223c5e79553f1b8700e4cefd52b14728e41d85eeb867c80a8ec5f1af9368e306af43ec64cd23d3fa01692a79ff13750564fdf1e6837ea3d900c9fc8b70fe1b5dd6b67f2f1d1ba1bc3732ea1e1f0007250b247fe95baf0d0f7ab1296915f98617cae59816b735fab4ca028e0f7eb3172d21889fac2b484e4d8b058994f1ad55f603df3db346be03e4454dfd55d3b883a0ba8993873e5c9464e19bfa2952e5941111f44cbbd02c0cad8d6e138780e4f725cf13e4f3493a541deee1d79bd9ba5286adcd59632163acd2d4a18d46217c01ffb279ae3a491ed06e4a8fdeafa67d5703dc1e2d6772bd72d6ee226f8d8c57072eca6542b63ff6c50f09ccc28a62185c05432657d29cfb2876290e7b63bef6f726e640fd5e928fcc1ad83454aadc91a885a1d265af84e2e545332a23a10e18d7282b3001c796efef6d89c2312823f30f842149012f7433ff891d4b79296739732d7a92e9d55b3740a198c83d844731dc296a1609531bf04d5bade0b15a3633272af3edea98f8bdc3a0aed52505763642ba0dd0447a7999a5174602ed1406275722f76078db36738fea612884c874eac4ef9feaece3fe751986d94ee1683697372d15c55e9847ea4ca0d6b761fc98a6a60ad6cb4f587c70e63a6336738f59cfd268b7f187f5e53720c5033d7ac24bba7d072e785d3ee3f2d7658a0ac12264f6c729653035762f5f06586d3e81994b794ef68bc10aa94920e2c3fc7ee07a2556832008eea2abd26d987c610c6d98c3474b8db4d8ff81fc433f6aab506e2eeadbc72f899299e997e44b99df4e62f71e3bf55b19cb83954ab4bed57162ea74e4d7c44a4d77746549cbff44a00b438809b0c4b9d812e3a513305ceee977d7d649b937213a3877e28d40f8941eb44e0aaa456bfee676efd4700466eb9777b95cfe0267235de621114c809e648c5d72c785595fd79d1c25342f810b65d608671dd2b5372a278c50a1b8b21901ecc81ee4302a03ea1dea248a4280516ca3fb1350d587072c55c39804968e1f25350cf6cbd14a16012786f34eb108b686eaee14f8def55086314f1eecc846f0162bf63a4caf80e0fac0c6f8dba782b952b0fb67b42bfdf7236fe3e323b2ec6a1f314d7018ec532f259624291a15500430495b06913e85f72f3575fddd4a4cc51f92d08f8fda23f31c1804ac92518b9f4f01101137d52e572a6a3ea05162b04a846d7e19c83c65fd051dd6d1e0889370f261ef6450d267472c14490f893677cd37b0209e25420985f2fd1954e1f9bc39f171d403e867eb4725aebc5eb6c82cd00d060c2db8592b878e463ddbe441a7f6e6354d80b6806997288d280d065ceeb21bbb337e8aeca598fd7ab85391a68476b632885d785efb072a85f39f9173321913b9b7ab47cd223f021388fb555e7f0250b4aae350a903c721c19b9f56a4e2c27820b7fb8f32d476a1e9055ecf72a690487e8dbf05ffbb129fea8e8acbf2a831959702b911765e6011e2e51891babbc71bbbc190fac46cc63036e1facd2df2d6665333ced857cd02f27387fffe7b274a68cf1a4ea9f8a4772487747231e63feed519ad93914441b1430fbbe0e979c80108e420fae8bcfe13070a397794ef48d441bfa2dde19b3d4f8ba6e507cbba5c760572ad364d0ba1572645ec27500ca3d3df4a226d32e9945b5e20e2afd82d106197368d363222d8d2cfc54bedc43a4578ce4730af9784e65f18a434ea0b07998e088b16327896f7e722c91c96287f91bc44cdf2ef6489ab58e0e342c708a44b1e4fff6046ebc3e4e727b2f11a948f33aabeb0649ce19fb3c7ed2f4c98b6eab4e50e733273a537dd2726f4f309b71e00b33aa911261dca17801e287b17732dee6e4042ba9490d8a34127af529a6018b79cbcd80f0b81f80ad8772cb34aeb82e9f13daf4b629ab4bb172bc01e343163ae98acdcd3774e3f40a99d6de061a5090ea60661bd6b331c1aa7277c683e3f9a90ea419efac213648d94799eca84f2c6688f9ff178876f4710b72564b9d9ec0e37894444a29e40c26e824879b7e987b331b2e01a3a5358de5aa728fb21511983542b411142f084873e0231c805430f837b981fec1c8b8390e191f59cf69e62cd61d33ad4dbb933e0509886a4773f3b1cadba2f793a92e6f38b3724839fb13e394ba78918699a1e0b579f8504a1343e2d55680e20d726afb1c35729c43ffb9739dd1d44d06a87cdfdcc985ebc811ffdf89001c4e0b787990ca1c728dcab18696725ccb2fcaa3da4e4e341200f40313155791a3f92fc07d0f7505307d237b134498b7548d1e87b123a008a8f43041ef37bfda28041d7e1a1051ad36feba144f4bfe177542eec264834f95cd20d20620a27e3de83e4cc00759ca9272d67b3321f9824c50fd4100596dc534d258cb3eb96da07b6049c93caf8eaab8726fd7fc623cb69f6039e9a2d2879afdb602eeb186dfa2f76bea41e90da87c5b72b0097cac52a5be43e70127a05c8102b4e6d186638f067b89fdf509800378cc72f6d0c1b20f07ff588f40bc57a175363b0b2ea2f203d5d5afbb95734c204e3b721fda2f1e524901c0b26ef6cee957de2b6232f5c90bcdbed836ff29113e741f0207ba9ed96319693ece0a089be32a907f5a922c6a210a6bfc638f4902fadb62522cfe62476f30b0b05b8651435747b0a5ce099a5f41d0d681df59894a01eb6a643d1f2ec1ccc5c43d79621c691a094ac9ccef31b2614bea92332487f981a50472ff4334010ea8cdac40c32c8c62f33ce9caf15df956185b8152e597877b53d17227cb266143d871eb5dab38fa87b12cd9de3891686460772935176b5b503966724f8ce608b5ca950301b76a7980bee3654f1f80a9e5d33905ff90cd0a6980257210ba6bf167fb89ff11fbe929cb64ea368357fb307dd3d7ceba9a62000d88d5724e59cfa22e47d81dac8c72f1ca379ebecd0835483fca5a85a9543daca265b0723b508217f02a63d000ee63cbd770e0fb217f740a2d51d4761559d60627c5511ba7ebc2ce7b644a21c8cae4cee2f2ee3bf61e3443bc90d7ff0514dbd7086f3172fbdb15e4672320f8c0681d5c763da9e18392fd9707628b865ef2e7bca6f0cc72c64c3117c3d5753c6bb3b4f08efefbc8610ba0b82a1167eabb5b7893ec269f727066895e8418dffcb11217738a774630a06a8349eb63c74e38edd4ba53069c72c89a01d45da5647e5052732da143c1cc3498d0b4649cb48ed83383c9d895df08117ec5b48b474ef26e3857c8365158e8656d7842b71f4546f3edae7cca78cc72a20086776109d3fdd888f669fd0bade3ccb272ea21a35fabf73660f3045a887257b845cc4f1e44f167077d89f2745ee08e45ccee1fc5d905ea72ea9707d37872b57991a49b45586179f985c38b5a8b2d315769bb8ede9c2832b1311c4a59ef722507e5e3ee279c339a02666bd085ed22dc5bf2fba041f8a3dbcbb10531ce5d7241f9131b544b3a3926eb07ffa66e1dbe27f7378c9b0d3c4dddc47ed4326aed1f06c0e9ad335b1c7a922093dd3bc4e1369f1d2488dd1c00c9cdce560e78b96c72226dbf3850653f0d48c90e0775dae99ccd2418f416056ae36844bcf9e3b46948865990cc97f46f0bd3da6e8a0f62d4816aa927c9ba60e2af7676535494583f72021a7b924ed451f76a1fc876bab89092f73d17e7712fc9bda0cbdf155656b1722ee2de87b9dad4e15d76cbb5a3a99fcb3ba7c4020c19629442b2551854c55772168f710986b8ad58e8813f8db125cc99000c1a486a2c6e8f56f4a7f8e629c7725d9be788fba1367e8dbf9f32c67847fdb807c4663359ece5a25c14c7170d3a282f9b04e53a717d3efc89b0d35939cdee5a295aa185ffe48c563d0ad893e3665b9f1fc4814c9e26807034dd992cdfb777158eecdc59effdcab13cf2b012abc1725faacbaaa49dacb6a8125da9a35c755ca71b07e79ea059931d9a46c23687a772788d61554aabb6f2b7572fc64f0e4e0817c1d9337f991a6b9a0d4cb84f704a72bb1a780ac46eab439a395c6116b098691cd7d69696deaa51c712e03477f0b047cccd62f88d044d7983d81b1478575d6521c1599c6d616ec65d773193ac9ae03b0f23c5960f7a0423930031c5ae3ec6084088a4be1b706ef848ba938d7d08e05858b675f89f0b57efd915f7c25ec0a79e79d995145568a0725519119aed2f797247e926008c759463ae5311a1cb70e9553e6abdc3ec59b4840a301db83fae05723f30b545f1a2461cdfde8f38c7263c07a8399d7d836f92e9ece03a28598993720a8e546a6cb4a82dcda121c7cd637263e1a47dbaaf70ff035e535b7d20448572f12e20a2c51bac21d09e27ec04247150f86488ea97fee48ceef0507fb65c7f721444d4bc37ae695d9e04351c566f02a96aae9d037c4ab5fda1407e34f5ad9d6c33d6bfc51bf7d56bfac935635f1c482023fef8d7a5f7b4317cef8498c44fb64aa18b7fbd2ecfb339680fd6637da7541f33a83f2b4fd4d4cb820ff35f5d6d62727e733f6a48ffa1fe057c948592e120505d76e0e3b0291c2debc7dd5e9525a35f0c9e54919dde6c8645b6601eebd40e92ae8210f543ea86b67176f6e772d8672e3639749bad2a35eb6a1cdc53ac39dbac8d775bd0685fca8cee6ed436d63e8a6bcb822ff838859f125f6acf2ea4dae293952a64075c4c812a39eb39e9693d777295e0b1a21a4800f013e0f869b62170d6c155bf88f52371ff9753273a300443723382798d514328d472b6e31af87450603b8a88001cbf0c358b1f140f536d5272e47f0c84630895f3c637a8e7bf3ddbd702bab0b199edd673db01635a8149b412708b75e5c015591c9690fe2e2416aedbc0e3abdeb2c8d1b8d7f6d62abcea1f697f7862e3b9e9fa2bc8b7a79b8b3a154d3bf6dc296a0aefe18050df7f6d532c722706534967597fcbb9e980d0de4fd79154711d36dc5a1a392150e084c9590f72c4eeb09241a44c12a5752ecbcc9e4a071fccc98a1c807d2265c11c6b2402c6724e40f6a3a3adbda225814da4f6d5ad9144fd6bdf747f641e9c64c2b59f625f722f5a8b16790c2ed1bf171f5aa9b078272161ac1f9aac38e0e27501e5791ed072d90d9a49a64434894e7f3d9d231e43e356ed4f4b6e2bdf67122fa999854547726dda58917aa3697366262dda1112d9e99e773862f07d814f8eafba86340f0942e3e33e2d986617d580672739aa929885d6ab64ba81fb81fbb63d792ddf52482692d58558722fe98757d43d7e2010a994a8ab76d3db0501246cf533603e19f66e3a8c00304e85f7e01312d8add9315186c7bca468dde8a37c9f046932c11b477263e1483c7ec9138328b8c4bbedd2ad2331b38d03a740dd12115b6ccdb1b6212eff271522c890b97935394fb23a72312a2e2bc323fe3223129420a284b7c16072e15f6d00878e58bfa3d9df12b08399569f4feea30c2fb4af869f97082f30ef722c3c56d7dec63b33072dd67fd6c95f28bd37a32376a9bf54dea9bcc5a69afe72c6b02d94426d788bd40cd6634d228597f046a9ab0e23f6731a5bf40e50d4fb721ad354a0a5edf7fdba9884f905dd4fdd54304809b2cf5f6e7a840ded12bb8d72a8087845a9e24ff9601453861d7f74948a3050fd3b2a63aed1507673a5343872f7aa72ae66613fc9e40e2b738d56678e042bd194122431f6c162fcde671cc8728b823f31722cdc4f1b1ebaf5143e68f8c57b1bf7fa5871b73259d081e5a73772fe1a376c3832fc30d691e66f74d4daca42ed72ef36480578bb058e9757cd7472e04c4ca6447d16b988963c252968d252b7719ad6b7b2f1b05f0e2288f505f74f19a6159d3a2db491ecf52ae73c8a768f488cef89c47cb2d689fc0d58caa2df72a23e59523337b75865fcee2b386daa17ee17470bc7731ccd132f39b8598cfd723e129b98e70f2391401cd505870da57329844060b33ca9909597143ffa087e7230fdefbe5ce8f91bb41c5c86f9ca568b18405b3dbac15a771469d8a3276ba764d0c466141ce8191d2ddb4d31e4656011636eba165ee3546599bbbf1ddd67ca13232209c8e6fa30ebfc2a493b26308ae766f800daa9cbb8899510c5a174f132726ae440be4700fe73f8944a957435f9ed0cb3fcd510fab9b5ce14486fcd83995d1793c2d975867fe492d86700ab8028b454be868abedbd5ecbbad577e88010e40c27c929c762f0d2db9c99031fdfc10fcfb88b76ef160137a8637e3e5daafce7211fdbc85f86bf2cec7d91bf6d0614e03a1640b952929e12b7215193a74fde1728a3584ee9d8e2cf802b958d786faf6a86b3e43f555faaf954c6acf5166838a72b0a31937094f70f182a732868f670a3e3641585ab8117df8093f58691800d472811f50162cfc4b2c8668363601544da0cc2269b8c3856654e816388ae06805721620f698f94cc63a1060da6227ab33a5ce48456a8d588c5a9486e3de68316272eb4e52248ba15e6ad06f2c7f69f686985e029bb122364eb945e03720c6f64b72adac7704657c8fcce5f5c6c1ee96c933596ac9e57273bf9afeba42cfda2919725f265d11bd3c868e345b084f78e587cb9628fe04785030828d0f031c0571b659d64e8e9e721d3f22ee86dd411ee348339015b80e38a18c680a8100573056857215b105b9f861affe7d96e3bcad00f0269f552dbb98a76b9dc125d6e4ea58d40b5862ef209aeb44e2c4aa9d6be9af0d3426b1e0b2b53f32e31c4e244a4fbd7b0d89db49e88c22fc0bb9a4427bf8c88f540ebb23f9ca9406412db06aac93e1227246103dcf8393fbe842c86591996391ebd315e64d74c498ee82689a58356aa3725c1319e4ba64caeae9e60857f51a66602e32d87af06e634154fafedc2a449572113cae11b2caad985328be4a3360db5877cd75db5f60938d9cb3c99afcfa6d72f7ac631594157a60a288a4e8658340326a31c84bede84f74a7aea224a1fd297270c69636f598a8af4778f63eaba54a96e5507d1f98cfdaeaf1ba7a70122d73724ae5714352fc94c7592d4db93dc91e0643f6796b7e096a6e2191ea793d120b6b6c69d597b615209b47928b2e5ccf04294e8c594a1338fa71577cdd2b249b37722d5c014fe96b22e5fe2824ca562a728d5ab1ccd3dad6d9e417f2e1f184628d729af970418814af4dce8621c71655da7549fe99120e5879d642d3da8f6a19872fb0467e0e75076ef578884f05a3cfec7b58c38b5936fb46b39fbe92d0fc8c304b92df1c4892388751931e70e5e9f3aab66339db3d4b5b87cdc49ebe9b5e13941675d5f7bfcbe7054f14db5f1ce4d8f71f12c28486d695379a28b0ee57fbcbc55faea4624c9945a5bebbe1a35b56cca77267566937116418092983e290386664725ae6510d7a58d61e8fef6bee13c91fffdb75767e20411acc58e5685044fc0923174f74d0d17082e375f7f5ba765db7fd79386f3b11a2cc7629d95410c5cf0d726a796ba6e774da021ea68105aa1f091ce253e486a2f86efb0f94caa27248857255bc515cd3bb89d35bc40cdb86a339ba9b1cb9caee92bec38b2c4f09801dc1729200eb1b235d38bb1e6c524dbe420e3d2b411d675c6110feebef1c6e67ad8a568571f0c64a886c3f00a1965cf80bf66eb2fbe148ac58750e4706872b2df39c72fc7bcfc9e13fe787fee252fce7356b4eac8b7179a7c532fb245d7f0845bc3c05185af577fd48bb6d194032af29c9d239c8c7d97dda6e0c99e7e1ab088014fd5406d2c8f15c74000bf22742aa2887710b458de33cfe8a9d24f1b5d4caf9d89a72d0855d6354e3c111c9a3141d6477a018168302817b4f87c136b2f215c7cf3170f2d5da6383728f36739d94ecf839da29da32a399fc28be79f6b8fc33fcfe723af466761a79d447686332cbcac74b4d6816dcc1e268f99fa593aec12640b93e72f6c92baeb5ce8bb92faf82427f9365ea0d09d9693efa66a833e80dd5c427590bd160fa62cd0a304c1ed0afbf941fd1f49432363f48e85d1b6f81e3a77e54c24a1763e0da671d0db3c82b572b22fb079bb79409378351b1f3d1612c5cdc78cc72b7e1e88653ccfb354eadcfdbc984072d2758b9becfba0e3f0776353d608089728715c5292dbc10644aacbd40ec4fd1cdf3f66b1eff0edf2d9be9cb313eb60772e21e33e094139c5f043e0f34d45f75b9f53142b25a90884de57f8de024701b617a6b90ed85a2155afbd982fa8aaa14638e8334f4e6d43737185487ef52f305724bedca8a0fd1dfa43b196b2390be34a94f3bd6eace3947e507b9601b0abf803bab0fe376ccf681f341038f2c7cd8dd4c15e8f1abe6ea3b269a5c417279be3e727a3f2460500156eefdce540c0aa5859092e7474d6f0f21215b009ccf8b17633046d6a17c53dec8a0ad9634ec65d51b0b6de99cbb074a1a0ed1a4f952a4e8242db20b9d161cfd34b7549f81c03431cf2424546b2a4bc72710957f3c6c69ee072ca6614e37655b05c86838ce6904e7be98b40e5919cae47356f868107116769933d8efe3b2cefb2d4ef4950fd9821ed2ffcf4cef29862b59b0e1c9cabeaad0f54973a5a1533cc15443e62e39eedcd95e88c934d0baaf5e98d5c8845fe40015172bd2dba280b9011808b36d14e31b4a2c50074bc6805cc6ce0398ace26ff0380672ec1a071c9035510d27513a4319265ad8164886909620b13a1976e7999d6bea725e4e7402a0e3b8dd3a0f6db445ab199d114eefb7a5b82c374fa062b5d5376414dfec0da2b2e4ae5105663490218b0705ff904b716557a92c51caba9ee03bdd72aa36d1f0878b955991dc2a120f30034ab2d0b8d28b6ec3f2dfe337df8c827a1da8c2170d46b9573ca599af16cbb23b21420a21459184c0a217d7036e25d9757299946d3a038d6ea17af2fb76cadb305302fef114e58812e63a5fc78ce265fc32d29c7c85e4fcd16227ed90a5a55ba83baa35ec5d02fa1fb147fa599dc6c2446a219efa067a8e04e9a2c2dbd74c4ddc15385696ae980ca3a0ebbf1c2535803520609b37cef7cacddd68664ff8cdd623197f4cdbfdd10cf301f045166e93e3be64dc077a2965dc085c346ca19971e3d5837b37e5b187f9959edc4fa7518716b2528dbebd3480a7ff257fab8057c0a9cf9248a20f34fc7fba625c9e1bcf8c778e727dd15877c96a64299be57accecab52809124b3477e0941ad5edd3e1f5b4e415c3e48e93921fe9465a77e76351a88d1d153fcf8bcd74adf0ac6342aec1a191472fccafb9d5dca83c43a1a8941f5de5871f6693eab485cbe7e1d4ca5f84f1c62728ee023e2d16c3c5195d25c0fad28bafadc42e9850519102c907d55ec172f433dec52d1ebbd095d0402b116ed3afec064dad64f06818cc4a045a54b1d19bf276f1b1149fc8d10aaece4c1d94c1b79e79552d0c5e9f82a6b9e2aa1f15738876372d7aadcdf0982e758f05726320ae1a12eb13b7d2b8f2559cc16ceef017038da722eef974a0d3452a9dbf2ab0ba0ecf8461ad9aa523092078ecce53669e5e2b17206a5c068a3f8658df2396b70671ab1b783bd0f0bac8ebbd86c2ea58a9c6ec900930a982b2756178a57944df60a87aba34e5ba72d7d330571ab449c55aba2c37202e0405c437d6337600f1fe78c32daa532227146c9893861286317328817616a42fd9e7e91ae938c7a77bcb7ae6b58f135b56b9020dc84bc9f70b35ba6cf0e72e3d07dca8fef1b9e3cb87c8e7b8537e038fb9da74ea16f3eb666499d6f25bb208b64a5ef34f41fb7c3a92e8474f46e596387d7dea858f947f3602622c108ba72a5b80ca18b82a738cd5f3cc44a6073469ead1992f57d95fadacbb22c4143957278ba9cf3276890a3b1a2174a38c83e62d1a0959eac89f0542c0d63451dcacf72b7250cffd4750fd30703c712f26b415ca6243935b2a2f5444139913a3b6312154864082484afcc3852b549c96e3c8bd63fc8f52d6144125b00703988107c3572380aaca5dd230b57476024c8f237393120f194f72fe17e7e83d284bc42535a7244586f6d966bdbdeefbf61738360df9ffccabd4d8c664634ea2824077d7dc56e2e3b569a5686966f0305f5a42e584c24dc6b189b5bad371e8aca8e0f9e2a4d23fc565509c535084321f602351f1f708c78ae2aaeef0cff3e632ce6caebda3672a6b482690455aa4785524806a89f3d1a3726a08adfa78348e068edb4b738dc72a9977e69acc1e05f9d6a5fb338d83b9b61f656d4254c85cb5fe7264aa574a37284dc4683e6b5abbaea2dc2c891eb5de76e6255be68ceb67dff4571253a83b472abbb55dff44fc4659b881e72fa7f87cf302f273bd6d0f64967493c1a422f4c72649fe0b419128e2a4751da0c4eceae938813827cf5cf5aa5121efbe8fe30cc7277c9e01c4b37d035afd23395dd84442e32c71aefd0bf5bdf44b85fd2475f4b72462b5860ba7f8aa488fd0cfab4e7fdc9af83574a9bc98a3c2c805cdb4f61dc724024d6ca9986cd67e2924c1632462253d2b9fc5e4f9aa0123d0f798cd5a9233926ea97a605ecc8147cca95c856150b482a5d64ba09072986d92b11d2a2ab9672c916d97cdf14154df8a428eae1b73dcd1e3c982daa7ebf665db42a832719055bf230d754d9d826ca0d163cea9794b9b7548113edf1473b6722be7b9dfa69f87261e6a49961122635406edfc7d6746a82eb3f43df2780f9b5b918363b957464722c3ee5fb8924612809daae2510ebb89d221bccbda400165a17497b1966f14d7224fd5af9df7c69d31d0229ae912a3866b2da2807bab1d25e692307b7932a27640de5dc5ef86fb8797de83011b7c871bfb819d6278c3691d728cfab25c1337540b5c60114d8f4b84fe96ae17a206b32aaefc89d968df87b32d842a654a5be2f728185530602d447a3b95104941b9188e3b1b97549b881b59bfeb616580b8d2f570cc93b9f786c52531f8cbc2e88e17fe176a33f111197267dad91db13827c224518ac33a6ac10cbaad806e9086723afa569d1c3be899d471c411108f622b62501c1b391e867cd0f6fdf11549ad5c9126b6bdaf98a72de0dec313b29b5c9dcf17241e6314687bef98e8c4bfa354975396920d2e4046aa8fb78be4ec215cbbd2472373664f979eecafcfbe2c1a8fee4baef394eeb92eef81ef5cec0378108e6a3729e822b5cf0d3af52f54abfdb0277426b8e487f03a7f371e22c111f9532dd557260d8bfb8f6b49a75d2b47d3ce543bf388b792789598c622b566a3878a93707721bc507612cecdbe4e69d0515d142c466b2237d38e5c84204be6e3e3bcd9ce36d480b8aa875c7616f722b664315cbc599bff5fa7bd2304de5a4bd30349b01175cb35800ff1d07c176b8ee3b27b65f5662242af78ea72370bd6b1bc203f366be72cc7a67704f3e5f154cade054beae1535d3371d78f899ceaa4bbaf4a284418d0de08f0900e0cd56ea7459cbd23f81e1cc6e6a532ccc28157e2cc324217230a850c36302d3d6b0dd30ddcc1dce0d9a70165647a3be731fc65f1f468b52846f376e92c852bff331a4e77621fc0568c302f0ba010469ee712e6a8f1736957b9959723b60748c0e6e55c37e8a44ec77e242427a06d5d0d04316c79e074b002be1816bc1bae17db998850577420b60f66226081f3475c6da969a1448a1e6138d33b57263ade74a61210dda7201d8d7885db199c598e5ce423a5433f5699426f6c2a3568ebac994db783500a8e2b1b21750d95e995049f99711bebfaa48de86d9d5be000fb3d339017569498aaa0977a85c5213db5da1907714df9259d1cc01a600e00ebb937057b43ae325c071e380a1ec8b5600ce7c33cab717da062d1cb90a17d9725b7e6b585a79e5e6079f156f192c7825855b50cca1028f36b061f86b4e0b160eb6229d952d74e9ca7c806a73b99e8a8d1563c23823aab2c811657e6409fc320e10315592f5cc597543b34d8ff1dbe18977d1f74a15317a38af4c1fd70a0cc0723b56cef74445c6349b45231a3b29a465efbc7a5bb61913aa3d5683b672849572a89342e4886b1eecd273b53ecff03ab7543373d4029c300d75c9d4e9fb6d4772376f9c84965b8a02b576cafa3e5b8aec397dadbf203fc0eae47b9c914d7216727104d4419cfdfcabb29b4a79df2be1b91c743a07bd2db46a9e503eb95662a31fc73f429396b555b4c89e6efc8a27d1593ca454c1f4c842975505c54e8b99c0548c7a2ed0d1397a044a90caa0269168297db00b0869fa3054fbfdf05a55ce4f72dac644a032ea15930b71403f948c95276aba516fcd6af511549863ebe28c9f02fcdcb9bff97ee885b150001e0252209ae722de75f01f847d1b96b6ce5437aa720141a110206dca79bd0f620eeb12de716bc33761881b4c7ed1559c39eb00f572416bd274b078d16b8b7df3de6137d964898280d1601cdcd72f5ef0e8b12c6772d2ac6729f9aa847269677721d3868dc48259aee83c772fe34fae7bb5e502707254f94973797bc54bec15b61ee811bf448276e7389ea56e03320a4eedc479e86fd896c3b288f2f4c9ff386d89f41dd2614feb16cd70135a4c41c8f9cdfb6571722bc027bfac2061eb6bf02684ba1cda8423b03ebde0595e0b2118c3d907b9395956152f57a0e51595e7c01d5de915bf66055dc1f47721c3f1ceaa04a7720fad193a446a9748d1b68f284a5df431541b3e40ff883d46c8d585507aa618d879407240bb1e22c2e1c4217b77d0de502036ada5aeffa1172844b0598fd7ddcd3ff472c7dc8547b719b994e576047d7994d3ba5f142e54ccfae177d4ecb2dce445207283a23218c82dd6f2433996c86bd303e7ebfb34db4160f4595bc15750fc150d0ef4c2492d6edc72bd7af1945a463c49c2a49a2689cc16a88b25412b650392373d1f18dd9118e4397d155ebc15aa775856bd5a94e08075debe9a1a2caf532041610c6bf0344e890d674af9ec8d67822ec6d65cc835fd62f764e646c4259b30311be7074493b46347819b1ec106b69dbb408544add45fd549117d8bb6742fcab272af07546f847c72383a200a7ad10f612e009a53a0603d56644624208211c29d7220ae964cee5a2fc1ddbf9507b2203873f73876eb93909a12fa9d724aa775b272a7df49cc2231f0ae388a174dbfed4b39e823bababae9f96a7924af116ed8a77245ab2ce7b345a686e4d2d93e20a51ffefae33576ea77908d97b7aa7f9a28d272847c4e13b42b529585ed0e1efc3f62399b886f1fb76947de2447bb71f820c1723549cabe20031ff38316fd30d14263cbf620257e66370df98fce284409613072b985813ae48b832b537998f46548b6dab6fb4fd3b5d49e669ff514852723fb721d6776b52853d1d45bb8dc348e9c1a1c7877c0dda81b1ae3b2436c0d261bf5726b596509da886691745dc77562e682dc47b7dfce74bf93ba390ceea9e6a2161a50b73cab090c9310f76bd670b1e941547fcac1e57b099ef3e8be963e538936725185f0ee78283f00f6b2984b6bd7a8d791f55ad2109aa00dafb823785d0f933eac425a3f6956d6ea7f8a0894590012c3bbf4b8dcbc38b26c5c355ba2f22f417202022acabe7233c2c7dd960734d9f8b732dfa57504bc06a7505f3e2eabeb2333ca4275d795c791d782eea5c08d197715e8a4754c2efd98c9adb8027fc5f71572d8c87a274cddc6fa73a326641fed4bf915a22dc8b0065e42fe77f6bce69c89545db652184e37c878d72422ce676876f9e7b63325c47c865e4f74be126f2300723e7b9d7cf389c33c5f5e03d38f7b607b15e149dbcc8d940809f7b63f152d467207379f6638c6fea2ea20a55d87c576efe58009411146572c95021a9c37e00872f1494756b70206e7dc21e85c0bc841af83d2890f73cbb71cb59266ed85451f5ab9b7992510db010f7dccb1e8979c583ee1d675ff4e8b7efa8420468409061d72528087350b54d907551c7f60a0132a770b314d329664701f88ae1b283307cc6ee74851ca1c97f258d37a8753d9fd738430c3ebb56a1147cc18a518cd45ad6572deb975599fe34de5826a52565ee2319fc895310b379b0cf0934aa94c08f0aa14a175f08218b77c6fd546c7641f57fc5304d8488ef0205fe69e2649422c9d2872b6748439ba3ad6e317ad80e644188cb2105615ec378314162d93c681c8acbc40e9f21532bbfe4c887d734de41baae3417fbfe669882e7022099baf4a5158ed291ad9dc6a3c01115c426d27d4e9991d6c4b1cb2e29631d250712ac383922dc4726a5d531d694f86625ba8b70c308d5f63de4523142b0f558a06a147d732056572ea7bb9a56d5f808dbe284f41f2ed6ba141085a16e381f23af7bc5f3ace64bc72afb3ce7c914391e2a1168cc64a58a641013e938274609651611596da51c8ee72360ab3dbedafe695236a4fecb6a08c0b35eecdb3c1251ccfe4c728773fcda77220f983a3c2ae10efd0b66bc13dcf57a9a0547e5a2e256298d5af838536f33a0f4f954afeaa179e780ec53d4dae7d027bd70683fb8ed02d522506e9637d4ea10366a70d00a3a5f14176ad671e05d5255f20bd09dbc406da84c0d3957bbc08987249be68014030f69b84ecd9ab50279c66c8a43a944f1ffd9b529e83a83b516808f94a38c3b81e3960c160ba7b9bad53282b30d68266d7deec5f9b3b47bf00f713e352414467017aefce36057a5d8ea05f0a029e4f8435041f779de3bd9b03d372ce8fe144019f65f6131ce14a5ad4502e94a1897fa978388b25e238a9252cf072bf1f29169e92c7cc6cf6c7d91f852a8fafd1a1c2aa9a54fc24f498b9b61ab9721c1f4651f93bc4f2558b1e1563398c9e783bdbd6d0aa62357ff12b681db53360dc29d65929f48d14bf9e64fe1e82dc87360a07de268cbe6b5416089b8da54772ede49c906ac319efd4510ddc8af5acd6626e6e806dbc176e90c5dea31e27f27205aa19185ebf49633baae0a2715ea2989cd04f73ee2374d8484e009ddd75a6722aa52ce4fdf887e000e2d10b01aa6f7c6fe120fc280740113ae251509f24f8717212be3e99432b440c82b571b378524880e1330f7f6bfb88d50290f9847a62610c77b199418c966d8cb21f3cd310bbbe87a2b26f360231730a727b2eebd710728f7e58f9e68fc1f16b8c8e218ef13a5eb448b0cf43879067e0d71c6cf6f91c7223ae1cd332a9f737ab1bb5ab8de11dfaa4d058ef34c7f13300833f0a65c91e7225e9da783a4a20774cbd37ab6af513bd36c0c5583128396368040ae0cec47a72c17d31d6c188192daf6341d3dc2454f6987d09d0c52f49d0e1392d15bd7ff66f567f6ea2c4394de2e2164a79192006eed922924ffabe5fa3405f0dee9f00821fbb78536fe1d8683ec4ac37e0114773d2c65b40ebc8611e87554c6b54db1c397280dece51474acac31795399f8dcdcce7846f92cec019a65d5cdd0feb0b7b2a727804b159297c5bb86dfdfb93b40b40bbbbc552443b69ae77d4968a7ffc74fd72b61a74c07452c6df3f0390f0b48434a75f6d02f1dce067959a6166d4a20b1c7295d28df44b035b268aa31d6e98b7d6493a0419ace64b1ac2131f58502c6471580f7bcd7be924e20ed844d437cb4c724ebb13927ce6ae1637932769eba1de4072de8b47dc5e781494f3d3a790df2c433fd280a0732e9e444b23ea1fdf9abfd305555509eae3868a9cd133920f72919298be3d755723495f12eed8940888e39c722a8582ca485eef2a60726ced7ec7ddd58351491546c32294f64c4b49ec1e1372ddfecbb4edbf9f3e4d9c61e86cfea42a8b6f2bad9d95902b1a6563da473e7572d774462581e1468f57b9e08925f87fdec37dbd5dba1a794f26126a2aaa96e07288c03f179e5908282f3fbaf737a5cbd70bc189cbed917607bcd63ec7ac078872ec9ee28c6da1aa25befde3e0b5aa108ab8e35f0c29e71f3cc76637383e8d1972c9cfd3eb37c9fa990c19b09f3b2347a719dee892679a72d6f56389603d0e7068f3e7f96344870f10102291deb1c7a459f448e7fc72a12ef67cb1c4dd21739872b5dfaa5f39038fcaacc22225c3c34948e3b4253092536aa675d13facc582e140aa17472c61d9dd0a1152da5c9db38a47c310d61caf64eb4b6190347aeb054672ab49448830ce4269feddba36dfecef282b1203b27e57ea991476fc02b1aa1d72b2a3b72ac9576a01e9fc89abdebd13e49d533411f5daf974870afd2fe0344b7245aec39b4d79756c8c2fc21ee0caf19ac78f466ef8ae619cb0cf14dd8bfa89723e9f77ffce42336b34475247fc5289c9e7c91055d768cdf1b278787329b7e7430cdc395a70f1123f93ce4429050ea0b0e21142687ca7ae595307402c544191135ba0fea787a06c08ea116b5085d37864c7de5ed91b046385846799d19b324e72a16da0e4403d67311327de1c645961c021e20160da7cc3a192359ccc254acd72a5fa37b735c8d0c8caf6ec04835f133b2141a566cff2b6b2ab60c4369b8a475ac3f1972bf5a8de57b09aeedf0e7a0d98dd0ef14dbed9dfd4320bea506e0cae01fd308f4b19b2117370553ea33ccbd284d0b46855c8e349ece01b41c5fa0d7b477573e70d7f64245b451b53284f9f36d3e2b438711cc5e65c3ea05398b220e86b105bf3c9d9ffe88c9fdc7f22f7cc0a07279ae6f15474b2bd7c7eca545542ba72d09da125b9798e80664aa4782869e98cce3393b019203d1880ef948ee2d5f072b6b952586daa181d51b8364d768159cae70216c6615846df4e4030c909697b721a6d0c8ab11e5435a673db7bd6d8132543d4aaca541ac1628275c76b9a28383a7fdfe65d5786976f305aefac4d3cc7825eb8a982368c9a604fc2d3466ec7b2723388299932bfa7c7c6f7458eb78b522989a5488b148498a4a4299a811676c246eb5fa4c8e25bce021486c9aa4124621e232c76b29f7b6d9d6aa3d9b628f2401932899374e267e53c32e05aed5a6b57df8007d028cd0c2a9a88ae966cd6745a0280c7da8084c76f3692bf0aa28636e676c8ffbd90d43afe57b4eac1c7552e0f13064ab7712d938d2deaf30c4a12bbf719dd235a665220d38cf5e3c14780682a6b00a719064b67e562baa187b7b0280d4af07726f84c45c7274fb2ba319a863972aa358f2e50f3ae46b617e39e9b75ae521061cccd44ec570049def01ca57dd872eca39265242947cdf3cfb36458e2714714efe8539fba5ac58c70617138d557720f0759a81b979dd589549eaad4a81227a7ac6f9c8fbfa1874832afeaf3f3ad6954917436c30a1ff849fb698b694f9de722fcae0d4420bb872625e2dc7949664e34bb1077c86e52c852539b622f0c6c71d60ef663567bc8b504ede7d812623b16ac083d552502e7ac80202a4e22bebda146ed0ccff2dfb931e2aa761973e3fa11ca42648ca1fdf08c1c3f393dd4ae006dc767ff55dd53663dbb74182b1ae1607225866f2a262afb1a7e67f72407eb6197a64a6e6392e73372a50d8cbd8b5e045bda5cac41c105662b4bbf19449fee3f4b14a0be5ad62796bc4965474d04a99172e3d78054c4c84c4854b9519d211267dd288cc283edb90f14f2992ef378ed9672f7697e901f665d3e90dd4ef9f3b55cd34736d9692f012712a14ce257580a3a72fa0fac6766745ad01f8a3c9f810a11895ef33b0a6910e1f4747f930ac036a77233a16e6c974d9559879a02772406f8316030f28a1af7c058561bd1abc44fe14fb28e973af4285211af5ce4e83027a863207748b10aaf85bf4480716e451b5e0d605a4430c6d0add334ad709dcdbb4488e252e9654be55f591484c2dabc316a52bbcd731e772ffd3c2756d91803efaf4246aa75956d15871e681601f42eebbc5b4922b1a2f0426d10f151674e05ac5a26c48a915fbd148332dd7d31251638a47208d3786745d1d6e9011c4224348386181459aaf6220e38125b76a9df1fdc7a725e6ef05481075ec91d2821c22d8df680fb3dc161495e848eacd1e7cf3bf6a872bf69d6e8626480285577e4651089ea014d9a0242fb460e2b9b586eb1891819721fcab4f9381a1ac1d2d3ee6a74b6a1070af337cefaa6bccb39ddad4a72860f42b8593c8ff871b53a0efbf2dab03bbfa1a1ad9f1879c4aa843947778e33122458a4cab1d474359cb04d93f351671d6d7d7ad3597ebeeca90a5045549b5c163672743a9c9d5ad0ded78d9641dda77e028231149dd3ffce4223709757d07903d56ae6a5ccc4a05bd80d12204b2a71c5716b63adeed1b122194721b22ba99c2a340fa89c25ceec0f103692eab3395b8745b9f8a163429059cb056a4785a82377a1726bed72194a634f053db7dc89410bf2130266abe47bf69ce30ec5ed471e3dad4178533f2c53da4c7f7aa58ad8605e09e8105401315192caeabd714a71b047da72f093da3e4de0b397e7f93cfd9d6d4fcfd0d3da401b3eed1ea89e7e2dd4f7af4ec181f23cd34adffb91362f4f82fc197dd1baf4565642d2360e025a7f6fd00a633cb0cc4cb641ff6469be0baf179b9cef23aac0e618add05542c4b2b565525a721ce9b4ff607ef2139a3adb03a89c38f95c9a24963e660684ef29ea00fb5f440098bf67086f5b4b92bde5da864b1c782a16f83e4db7252bf574f920feeffa087277500bf567e8b2607e6abfd7ace27543cbec1b5a6f1b40a3179d928e4ea176429c99d2f148fd63e9ebd7251bf11eba4e8bcc1cd7d9d19e49e22709ee5e431f1c31cfa00ac8ca83374fba8de575aec2cd7e4ab99d3dd747abb2141d94c1659972b45686b16819578fbbd2073317e7e7195bcd29ac082c4f82bfb1191256b984727ff5401b749de214d0407545216e806c205fc4f49adcdc2c4568e24063713210a2fda59166ea6e8e5fd25c2c733bfa4ab51f47b9cf313b422ec04ca7313e9c1ebf1106445e222e0f21fe9058bdc2d7eee02fedf9c5e4ae489ef7c11457bcfb721e9547bd91b7c028932020ff172909c365eed9c236b138eaa43d975993164b722758bbcdba33777b1df94298a3543d0a4bb5592a530a65c838a01ffb4634ec3f01232ecdfde56d733b6ab114ed4bec557957a10ae4f897fd9c112e4a4f6b217292e1b03959444478b4fc071e6419e2a475d15509789890f64446824ca9c46a72fd2c7a860ef9b77502ae07b690e97b87b596ea9f04ec6b7fa03042c6470b414973c99ba9cf917c75433814c49184cd141b4d836df304671aff4089cb7f07ab72392336af5226703612d04e6837ca61c951dad67302873b5c374b9e2b8b901b040ca39aee2c466e67b518b9f8b8b899bdebd669b9f0da4619a468790f83ee6905da58ee0932c88fd7dae4244b9706ffcafdf555e5c67901ba6174ccf8d720c7729b7bf7e351668f9549502668f6173b7fa7988619ef0c248495024d44a4221b3e075eec55460ec051557820de1ddf26b6a689302a946da0a2157459efae07fc6c0c011342b18d642bb26d809f0929f7754b047ed774e2527c1e808eee3b5bb43d6548c832d3c238c01f673f89ea7eca5a6459210169767ef807be2e666ab54b451b5b30d0897ee12064148f473a4389c24599bd2a079493a61f6def2470624f07fcebe84f62706a0b30f946b1bfe1b511c303c35641459161a943bf6bf1bd9e728c5080dd2476dd36e82a28d3b37c2d5b1b74e07d94461b5376c26fec4ac3323f077fde079023f7fd6787af75c1de4624772c67d556bb4474e0f52c550004a50ba856e61e3fe4ff5f81d3a00dcbea8bb5eb2be467c18521c938283d7fbbfde772751b83e4604d50946d3ac3a708f863bcf0acbe190f8daab2eb0faf947f9aee729a16d10e5cf761f32fae0ae2e638f762d120bd6e83314cc706c13da23a5e8272507191cda6c0b80e4f68f0fa00030f329d58720e7bbd5da910076f55b2da54729e09861dbacd4ff3103a3badccbe96216701fbe6e0271ed529e0f33d228b5456cde89637e55c010fa9ba4261d4e1dde56138a2a4c50098b85ce153eade4ada52bf34a2d3f5e68d44e1ae5791fb9bb5eaa3eb4d41bf528c3869affd3c43ab0772afeeeb78a638ac754e175cd8e02aebe41424b7cb98c7535e95e790c6326d047274ead0d27af2437a1df766ccf9f4bfba85975a95105558b7cf73c039dc0bf772acfb8bcc9143bf33bce4fb0db28004b1eb1decc2fa56cfb61b3c02df61910b21874547a58d7d42b65f63f01e100d9b0f541db6c23491e20f01a28b6b178f3056c1a3b9b9cb1d3f345f6e266587224d653bdcf955034023971d33f17ed999272b9f069a1a524e9e9d5684a4e7ee10adf966083fed2dbe02b84b5013accd723e604622ebb235c303a6e4eb380ce9e74c45e0bcb7a0ecd435b737d9686784cfd572e56f58a31665e0931992e8854873eb1e9c55e83ff145d791fe7e1cead3499f0c54a5bcca9fc342af06e32a38ec2f8ae399153a0cd0a66316be95753aedbdbd72f9033e417906bf9f051aabcba2c6be6aa59b593d1817c7b5259dea3ac185577278a25fa5ca276a592b060a74bf16200fd239fd04206c9fff0b24512bf251034a333b548721e9e94461e1d202a97d52e953b1143ab6d78e5ab1f340d342968972718a03edcd52fda8379130dbb84f4f0323136c4dce3435fa91193caa78143b64bc2bb024eecec54541958604fd35cc6457ece65f2ab7f8ce16acebaf07ab1c1932add7a4e97c018094c9fb7b81f7743e6cdf19e00ddfd761551c35220a1f8825f1a36fb3d3a9890d1eb13e172e9e90b0503bc4c201110b33bb1693951645d17288d771e069b9756c709061fe7f12475b628228eddd783479ba49aa22342fa0389961a9d55db3cba6fb809adbb6951deda0c9f3712a0b097236564d3c03f714720e13c568a7a2978b99d4f9031aabcf919441f87ced18892d5360c7529ac8027246c03504044df25b114074d67c47b8a93b765ff8f365da1c4a7b88d9d0eda172da4d05345855cc4f2d6f2387ef4ef92d58a78c1c99d552a529f213bcbd3c6b161b54ce0f1ee0f71ad3e37225b44b72beb1d507112c7fd7c257a3eeb83c9cfa726150a72f72c158c8b8dcc74a13dd8b8e92b6ab3ce7c9a37372b4fe932a41147205bd13c9aa77de3107605be6a891341c39d941e6cd73d3161d3d9c48bf89a872466ee3f364c4f7a865c0cdda7f115c756cb681dd045bd1f75ac0b235cec24a39de51932c65cd5e348e9b8b98a19aa58d7bbfe748dc37e75b01084b49530e833c1ea46abf0a71336c7d988cad4b8edb80718d975429aaf9f6058ff261bfc3de269ad97e238f25b120828f53ca7ceb1e75c4d2f7af8c507626e296d526180a8b12b4970cf5191309c305e80b7e9495c082dd651d7f2699cd62e8c694ee2b647c723f600506adfe6b74252d2d00d7b3e6a60b4f90254d49277f9b6877dbfb188e72b1bf3b0397bcf94c15a48c94bde3e562538aac831a054bb951b5aa7d9451db7203bb12c24e9e9f94c24e3f9ccfcdf87296a4a54d14bf151161e7636b1b04b172444e540b5622b37e0def8cc6ba966bc37011280270d57d84ec39df19cb6aed727f69022e1b7284459ed04e0e488af27e636df0af148e9013261751d88493c37293bd698f2123af3d98cba9758215920f170d3fdf53cc7c8754e9ba5e9558db358aa7b6a9d3539399e4ca215f16f46cc13ac275f52a49fcbf40fa404807ea6752a58fb6f7fe953cca1b6118cd9e5de3980e2c1f87a8d61d6337328c41587eb67290dcb148b0a7c5591c95cfb26c4d3c5bcac0662bc123bf281bdcbba3ffac110528670b8d6a403053c5617f95a1a933a0f1642ee2a3839180d2809e04b46bdf72e7c49d7591b1028ed641d2bbc44768b8684ba399c23da81624d80cf013dcdc722890e81ff1d0a516092b50f92c73d832e6c413889422e05c60b408e83c1821727f379c46f847b824ce7cc3e785fc98622be1214d35aed16459c140546ec6c372cc21403f00af1e5637c0f6ab20e9aa9b006e232eeb3c8c610629a66c4477897233485c53442a6805ff33498d7bbb42ae03922cc28b69758ffa16bbde312a1272101e59aa015577186401483864e3b729f570277390cde5831b9d0c32598a3c7299912e0c3e8ead749a42e63bcf90bbd80fd5b8c713888663ea0db561a89a8c13de323589f56609885cc044dd6a4288f36a67c2636c3dced92afef2e5a8b7e239baab92f713ca1a711b393d127d8748f4be5c2b621a7a7f2d4bf4306c26c218721ddefda2817ad3bab1ea01dfdcad3984537ad7c35ae7fb0337b478415eb3f20dd6a428d39e9374accac722800bac46616766a6e8852618fda77cf3ed61ae4172c8b7ce39c4773d9e636647cf95b867889a56cd34d7abb63d84751b7314f06e70a75a28807c6117e14ae2be9aa0da6c9fe322cf3304e5d5d2ad56912f0e6658727bc63229d8ba14276aff0a6b464fb7bf9f1a0b5a0c59877e03b0b2b913bb137266ea9b2a24b05512939296a483636c9d345a7bbcfab7b4cd46f5fc7610c8a672c6f2861040c3729405822c10d317c0a54c09af81c3c9a7fd887e9bb22a826172ff7b37a8b15a339f79443b95ea0a75d376a1914790788eb353e6012ce4696b7285cf81ec3b35e871801dc15871b10d90b3a02b71cf43c8007f8a51d6be14f77231831096e93ef15b431456880d6aae01a230f4fbaa237d6a3f85e6a1ab6a3872430ba2c8d5b2e3c0dae4d5a54d257bea477bed62afda3492e66aae4186952672fad2df36d0b9f734573b50010bcb8b7fcd8e10f27ef489d18d76395284cd2572a57cf3f5a7e4ac83239bf7ae654627fcf51c1c3b51d584bcd174c8c295b5ed5ad3f0c633c8c06a2e9fc6c272be240f5ea26a062e21ab330922220d4f2b56e31d481c888ab458c2148098f587d7edd6f53dba6d07594037b62913d63ff3f15772e47b7045fbaafceba3c22abe46d2c870f9bc18c4a6c12dfebce91f5e5d4cf772bae1b7456e119a8d9809fa7ee436a42248e9548794ba0a54addb43824fc40172f0cc73e999e3c81e954cd8013357ea8b24770d80c51fb5b6bf33046cd66f9d5cc86817c2c876b81c9c97070899f53f454f4da182cbcdd001dfd325584897711207ba9a49ea568089d37b3dc96e7073547f41eb5af67c0c3591ecff75c0515472c9cce68177985635a56e13033372c6d585bcdfa6f6903463c0662ade26550b72234bdccaa64fa2a98ad41e05fbbb1de08d0d6961dc606872dc50708fed3f0172c0d44637feb6b6afa4cccd6d0cf2fe2e429b45feb99a110d2ecf1c2e7e7dfe72612a8108dfd301235a164d823b26c68883a66a80b44fd07d2ea44f224a1ba4728e5db0ff07db44ec91f6dfacfe288b0ee74ed1c907dcb9239cf32a72a0f97a348b2cafcca8c46bc7073363167b6fc2bfad9debb3316cf17210f871cee409b47217c23b74e7e2b217b2fe6f33ae31675619a5a889402649c24a4b8f7605465c72ffe9494b665a72387f2e1d23c0f779cbc6b451345ba26074d0d83f0eb3d91f2b6fd44555a836c0066c0db8687678cc0c1542909c5e9301437b2eb3a4b122d9728b3bab062475b81a17c144ee6f324efcb19271a18ca5e9e2c62ee8dca042ee724523febad24739f5eb8acffdba04ca7601c61632dcdc584c483fe51022347404f5d5f1538436a7a72881b253adb5a83dd23d4424c160ee92ee86d55ce304a36e6db0c59bcb844edec3ddbc4477d2ccabc0d6a6d8138e1dad7d16e8721cdc1072c147985c58622d27d342de93c7f06ef391446592e8b0895fd6c5909b03f24672176c75ea7635a2a83626d2e95c0907d9ae8d38b642157b5b55e801f107d5852e23e199e934a5a1265bf869cdb7fcc37d6dfc3a81767fe4ddbe5e14ff5dcaa54e7018d2752015b559236341aeb1e2d172301c41a46f1e9e6a0e971c2fae331972c2414f903cce96e5b0d9ee6fdee60dd3f3a193e47ba3a1c72d8ecd8f4d41b472b95451cf8d0f70c16f326955cc022c98eaa83c6acb7a6803752ed9b417637f52b1b7f54239341d6738f9082f0e9dd7dec95ace67e893c820247d96a55b60b8437e4cd9b60f7003c486234146e1f8a7de2cbc4ef3f61d534b94336d347b695f355e6ec6e6b413b3e1af3cd83803bdaffd44547b20a247f7eff8b8e3fbba3960724d09afc032fd30af02dc18cb76cd56adc9df48f2f38d9b8ef1f6cf090d53ba72b40523b35b1f51936c07b122ddbce3365adb020b0a45e722c1b2e983e26541048e61c79a5ba0aa22eac1c4363cdf24d6176735adf9e40236a0e55dda1c871504c30b4cb14f8c8368d681d4160e0c7fd9813dc01413210caa9fd61b933a1e6b72c441eec0db32f2ac47e54bca9efe3a85a6bcff6cce26da0b5ec344812a161f175b7b72e1d2fbf5d493456d6dc00b085016251fe6b5596fdd4042e1dd22542f7282514d8af5d94474453d87c6c2c2f12af07681f3c7da9c0b7b048b144770557219e56ee38a41adc5f347820c637490752b2a7af7dddf7c5a5105b505dcb86835654463b06bb46b48ae777bf9294a27a7006cf9e6b3839f527a0c0a43fbca8b665e23f8d4ebdc8da5570f7ff46d3d8062d6692d9acc45a5c7ea13c670b39b02163c31fd17a8a4cd25de9a7f0d13e29327980252a7dbe853b72ea8d253c56e7b2744bd9c293ea9ae22a01fc6a3e5eb6d7db7121bc9d0785018c0a433a4e0d82872269711c54b3a77f6891c864d46dafe3cc88067f4986a2a05abfba3cc0bfa2518eee62dd891d62cfe9dfe74b41156242a447aaecc32c839ab80b8fc9efe31b472539e1fb54a15651981643f92f2e36a212271cba4ffe3e16350536f291cb56572d4c8889076a16c06a174aef99163f2a52514d69c1a4dae8510b911ba8f4779721266fb1a919a57cfc0f355e16ad14b80e2f5db092ef7655ab5ab75817a0da772d4065d2a0c1c8034b932a30248febf2b9b4d0996202a588abf99ffcb1807fb14491d73f5c4a127ac0f783ba1c80a9cab5e26aae4b0ef1c7194376dfdcc4fe821de383969aa94dcd75566e7d23693e2b9d8ddfdca9352f949588825c4af90b522fbbcca2ca8131ec435cb0e3c1343282240c313bf9e852fc3d77b3ce7a14097533b762201a17b9f66d7c373b3a3065721b690eac05f75e3ec13bac4b935135068dc058a8f2188c6707f8b0785cd088602091379104a8d583c032c1318b9e589184e123edfdf46a71d86a126662ab0b90bc0ce4fde74041eb50fe16f3e3511f872b853b8915d5d2e6304d1e144389cc998eccf6ae50e44e0902057f730b183d7729baae0ed9ed16779e51d12f0401ccd024849eb47ce7765210d4244b36650cd6d8c56680a08015a298f0addf06c234138eda3723f4b8155c338612d22c1a6d7727d3dfd1d56042cd0b4bf8ac23b88975f3541d42bdc3e4a1a2e20cb43638a3c2ace84a6330244c47282c66dea51c2ddb7fb8c482121c5150fc5485bdf7c84a2726a2d196599e2f31d4d045d211d56dfd9e85df10daee121497e2b0424fdfd9d72b9c81566ad3b0068863b035b42b4984d00e50e7c9a06dc926e01a4724617a172282e94df18f0699b052abc6f74008b494fa907404158159748a9d2dfbe2b9272bf002913eb6529cbefbef35b92012284965efd03bc89120218cd710faf8945728c285b544f7b90f5a7621fa1da502252579659bb6f4533643f344a9a0f846572fc06bd8ee23f9bc173f6ce1cda680705841485dffa920268512fed6c8f02b472d722e891332d1a7f4b5293798750745212c3339f11b596702a98fbc9f6ee823f8839064645bb2bbc4aa7e7288f32b70261ba561c24fb0b71aac61deb6bd9012422f6868c75d66cb62449329be12fc2f651352f0bb1927fc338ef58b87ba5757226542826cb681d923d5343c49845cd6f6631b29ae68be47de53c5749f2b59856a04234c743d0bb6ab93fc026eb8488fc5a45489b9396a0170d0d59c1f52fc672896973badd2aacd292872ba4d00f0a981eac6c7bf4ab5b372a18620db79f222f4577a385bbe2b7a57342b1f039e11598d0a27c045a9ea92591643361b5e189324bca0aab6825950bf9e2f4785e011c91c5733690f56cb985eff5992626885b72267257f00986bdb84ab13a667840f4bf96fc6a05a3ccfc6177073428be87bb7277e2468997b9a3879468819168a04c2a48ffafa0f1bba98e95779ea6ec1d6772e68ee924e2ee46a9a5c1541a062c5a7b3d5f898c1210adc77aa1206a5da32d72143ec4ae5e438c8d7ea7d7d98f84d6dc1e04212d8c6559d58815f5c99404ee7230adbddd1ed96b6f03659f2af0bfa4c4b414376dde2469cdaa6092d4c6983472018ffda63db35c086d4816399233a0f5dcefd97ada5a89fd59ce8600027a0a723dc93b8630f60dfdb55efb6784d4066a6dcaf86c9ec9156a026a1d734ae0c37234388829b998b2bed7a6aadeee94e2dc1290ea7cb8f3c9f64de11d2c8bdcb172b2ce4761b9189114d9638cc70e5320cf0ceee08e1336c7e1b1b5e420496d0572945a4af671384b7e70b1fd6ee2698d86ad48c5c5ad744dd9a2a31b44d0810c086c0ee670445abc47cebafc8c8b8813f0573e543d26c826a2f23b7cf33a91b272ab723799d15660da10065a872b3ddeeeb71cad9189968d9bda0f0daa7e473529698aa51c795ac313304ce47eb588067d7ad72f6b0e1853129153b813418d2d6a466620a02af24123405340b89a0108c44205d74fa559289689aee637dab325052b786027fbb52cb9c0ad41baf87d57254ac7351b8e656f3b690ec1f9e8011772f245e319b3551f2209f80eb1da009aa98456befc1a25fb9ad05d7bbb2dd3b75e8873c9b27d2b52e558427b83e835fcec15394077b579cd9c7b3b054ce201aa015face70f9e1833aad7c1f100d35944ed3d5f8d4c71a5212583f821567f8e7737b2ad50e67a630d8e07c01922dd196d70d511ebac79695fcfe32d755b2b3e3761465e1f2fe6b54ba406d486b8d9505662c0f05327eba5c0047a2517b45b9dfc7289c7419ce0419d70d0078e08f4269caf9576bc2e44d9e30d5c56549bed570f72a4d86122dea633ea3fd6da42a265137a4cfa82f86e32398ee2fe534a8524ef7235dcbbafedb957275828da5a9422c3e234372ce94621a9facd85309e1e418172edad5ff548d24111558a09e2b9ae2d829bc967bc7cbdd822a1dc99b753356d72d700358ee13ad6e5f3a7a1bc8cd3eb54eb6b1a671950333f0ee15dc730a23a72c43437dc5ea877de7612f1dec25a2d18a975fe4a25ed01e85fab9556b2e52e38b1cbcecb375106012c041d4aa906851ce40ef1628d9354b4b61146a27470cd470d287f33f35e44c5c792173c8f725eb6a4bbb4e33e6cd9a29d081da66220c101b1d148812bb1924818ff9c772ea413793f4c0ed906fe519e7756ac39917a123fe833595da81e85caf036b9299490754d4aa336983b66310e45fc751ecb2cb572f55b02abfbde6e3a4bc90f211c3c55dab039f55095339c207ab768580ef2b272f0b9797433ce1719c1ec98ccad17071f5d98c8560ecadd7a5a7874cbd88e6c36a97b88e40bdcd24a2930b45ec5833d45b0752bafb862dbc1987e16c1ee345c72ccf71ec50fbf6583920c83b177a87df5ce5ef3f4fcbc8e95181a9ee1f8671772e07c7aa95861cfd7b840d10dc90801457415315e46652d08ed310ea4bdc167106f5613fe994be3029dfca8b8d6e7e5b7489211104f62ab7f54cee83e57aa8017bc94ee74b5e75f71cf594c7d369f069295a36989ee8dec6f0450fd8d8645ec252133102c4c6955880d3d9368e813ed6e9ebb46efaec363a0124023eaca5674001fc91d1b288d2a4545992e89d05804fd8bf72eabded4e93fca28aab20c4ea372734d04b96513f1b4f2bf1ce108bdcca9ff6d5993955e3308a6011eeaac29c45590298483e6b178a137ef441bf0c8ecc58fee7daba5fc80b62f3eaf424aa74736f24f4045df300488472781b84dbbff3ef5235d10605c5aa1f06bf69d1ae59e665ead8cc5e0b1aa5885f4e781241a047d3ee3f79119ea9c072fcc5465eaa47672740efe8963654dd8001ff165c1ccfd6c45a26c6adcff575989928bf596745d72f1015e69f182172c949a0de01dec145b7c2d3618fcacbae31515598a0bd0f6258e5218f836fdd74df5c82cddfecc25047d393cb11bdcbef98393422c24796a72cdc8c2799ffa6b4a7c341cee42f45410bdf04d7b6bbfad25f3d775ab4b8c0e725b28b6519cc4acb2be6f32952c70be629f38e9f8988f68402daaa7115565007246cf5d1c51fc60d55f9b6d52f68a53ebc3de0af3407b1a55afcba298abbc5d0d02c7a1618076482ff356eb6869c3b35d10fedd89c8a0e346a39f5130db5a1b2eab9eb0ce8fa580d5c3abbbd948809c00cbe30c83bec02058d8114e1cb78d5017cd8535a51b48e24d4139770c6a35b484ffd7c490a20b5e8554060b51e9959872239a13fbebace55735420d2254fe73f0f41e266ca8a9d1893300b28abb15f07278e5cd65a839848137ae0f2c68fd403c3d23184cdcfb12baac162a7e126dfa51df5bfd14e8158bec26ac801ccd313a1f4a4c7bd067e074e2c1ac8347c5c46c72200b5b7a2b3be788cc752ae12dc15527019f0a198efb4d1b1f57253cd7a37d72a7c1f62baf91dbd5ed65df6546ed4bdbd02d4db1ec49ad88461f7a48e1f03672c1617b51b2e3780b93af443c4dcd8e9c95c5d484a36041cf8727fac991346a72c1fbc3f6aab2b8de657cd5e520cef9dc788f83fc12e29bef4166a48f22f82e2126747b6f3394b0fd7ac45b9fba4bf9b4855402789efde95d2d5ac71380ec18042e5f36f68a69d419f0e63ebdf711f974860582d896d0cee486ac4a3c01e71620662d924723e46c48a144f5f7f236e007f3c964bee3d09cc2379a70f7c7c62e72be488f3ef9279e09c816caa25eae0743b3c3cc8b294aedf13b90df0f6720e972c5a1dba6b24a74f1ee6cfa658db8c8474b694ea9ebd0ff7660b6a4b474bc627245c12b1a322f934a6f3164f4d047f1c8855a96aaa942856446df4ddf428d5e72ec9c08a56600126ef97ea957ec690e1259265770569b8f18fd7679f7bdf0c66b87199d0e6e798f0dac9857654d79c3d2e7be772449e36ce3ef692e13b12a62522baedd9abc9ff77b0ecf7d86412d6fc89e1c519c2b7f8063026ccae5cd0f257227aa9ff14fce75c1a711cbde9e5e0d1375a9cb18f5abd58d66bb33af77d81512e3b42b055ff5912c64b7c5e9d1e2340f15c639ca67f7ef64434e5c9632298a70477c20a5c4647f81e4148389d40f0ae10f39c803db42e1ab295ff9f1a5584872e5826618f6be4dc8e84025b9d88046aec71aecc6436ec5d810bb99eaa82e5f1bf6c3325f8102b5cebd01825de8debff6850bea0dc51fdfccac8310cd6c26634aaab3edfb2e9a149aaf1ba7c1346ea7a556080871a822a061d5549c247e2edc7268f5327eaafeb3ed35d89960e0c04d72e917a7e1947eb0ee429eac200a3c243faab3faf4c0666587510afa91f94927c767022dae4d0d8daa4c0d46028614504d1bb5352a43ca67319da5e747ec55f8688685148e90b6591fa7d9cb8d2412200686c3d648f1c72270381f885a88821b5fb90bd51571b255feb1cb174c381c5b72137b65591234140d248d8ef0b0b128c1b00fe337b117861304b67f59c59a0151de3ad8efea14b40dddcb35150ace8678d09c8015b9b561e7fc0ee41e306819467b18dd9287be81273ec2afa4148e63fad0001c8f681a1f2146d8230758bdc5195de2718f684c15188931aa3377f31081fea99f271683932ed5766716660f9d72ebc2cab366bcefa4b772fce8bda0144fb62e397b028bbbf134da3409818b54729d11186421334d3d98d6802e8547e5527a1c5a46d8852dd50a6c97d52ee85b72ccf58475628d9ae604ce12839cbcb63ddaa4050016dd10ace51b5d89314b062607175d99ccd39f37255adf2e5ee353d3f19792274af86f883accb792e0f88f1f498211ef5b7eb2ebfec0b931020e7dd2c3e9c61665c9a6432fe3630c6eea467297d8aafa2acd3dc7ba31a471aee2cda1e3e4f062af71dfdab963f991935d20725134cffc3a38cc9fba503bb867fda63eaa465383d32d73fa575e323b0960bb630f980c3c46a70248789e8f763876b15cf467a75cf31799df0206145a12643572dd8a9afab30260c9bb73afb94fc183f7cfff62be7ed0c1261f82b0b498ce1e70d150c8dac601b6ed72a0e1119da5b1ba2ee795e1fc6e36c6169234ff162c544629c476fa9fa47030ebf37f6884fdddb6822aa18869cebd585e2c64ff7859c972c132c18cc62d4bec54a7b739b8ff4342cc4474053aeaf4e603d01df8ffaf796b075d1d2e43f392ecf562bcee045496aa17de904e651b7fef2214b48bcc787272c67c6538ecab15484a317f5e0731f3ac2a8ab7e5e68d5baa2be7fce11e90b97247158b759a409bd1be05232f5eea74515e9109152b88d239ea7eef2ceac71a35c5aa29ef52eaaf0c5d0a983cb89b0c59616484efb2e0e3306e253bde35428a2e7327812d8f9c2ed19e2274f6c5f712585675ea07780303112603124021ba6f7236e397bee5f36464a95a329a3abfa9960d1d63185dc9b7dfa9c2434319247472dfa225f0066e9a0406509670d6ec192b40f514321d5a1ae3d68ca0a5c7889d72320b8478af5a2c4f1c4227360065953cc76dbeefe76196f12209a94456348472d41a50a3da141781be6aa776c427211a388332124ef7635c115870994ef2f018a69daa1a255efdd2a5c0ea128ea22ed6dc25b6be4bb2af9ae8bc317f4f013c67996b510e8dd3a541ff00f25bb9f04fbf81932865c9d1eca719894e9880aed472192404e434ceb9ca87e1fa04b1e64aac721ac4f4077d8683437cdf8612349d7201b19b64183ab1cd4812c5be45d7ec6a7572a0663e27019c3a51407c7bea2f72e0045eda6d1fa6a46011d5bdb1cfcf1d868d9fe18b48b46782f4f6e8f0310f72be77ac88c53fc53e3fa65e838eb7839aee293e2db65cfb5f73d32a9086c2f3707bfe07d35ff9a7e8e227f10527f7dc619714f41e59f0ed5ddeadeb263e3f4a729919817cc02109801f330fa500cf2d62cffe6527786547ddb10aedeef84ced72c610c1dc7bd35a984c70bd64f371d9f9dcf2b3a94b2dd90456f9a4f03802cf724dd5dc71e38f9e93d4e4f2d86ef4f2ffec1da642f9c7f88f6aa606cab5071872ae80b0aea95e68007d620f620f951c54e95411a1e6bd1f9caeb4c50f5c8d0872a4cfb54fa1f107bf9a86c9ec4012d703a74ddd0cc16bd2c848408147d7fa3272e111a8f3040c010c4a874cc1518913aaa89768b458ffdaed9f44b67cf7fa9572f797df2ecd8f9ad5a29009b6a474861f196e10ef815e88e0afec6838856e655c1eeef1519cec92a1343e92e09a861cbf0431dcda3311a5393d2cb776615fc172d609eebbead56c4f21fba47d5a4801eb65cd0a8f7eb4950ecb2c56fb39d7af216e6b9e524e86f3de59975e960435d275ab757a6cedac98cd9f9ab2b73e7312131a36ea63c85022069f8a322b2005bf673aa45aa14132526caf9776cdd5e12a2122900fb2b0ac59983faa9710533c207552219f5913ce715c71b3d0a22bf823305fee41caefe3ec622c7adc4fa06e2c1f551c5719e3977350da236d6c5c13477288ff52c06b3b93ebb46a611f8c94d004ffaa1761a85d4e47602f78f98168e872cac7e5a02ea7a47e4008cc32c1e7db5f7906618bd7d2431c575afef0bb420b2d030bbba2bfd5801a50a15c3979979afc694c0ba9b8e9f220f8a830b04d60c31cf12bd4fd17d58fd4aa9a97a9c191989ce296c9aba26c332654ec178fd0c762495488c270b306f2458401d1ace03a457c63d201d2da25d4de458f1b24516e6b724370b1201b355c3beaa49f8db1a6fc13f9d19c143fa7fcf8f871d361f342773ee717f219bbe2dc4284e9987c8a8b1d3df982bad9f1659340fb13b9c8eea6c06567823015c03263361ab5da537b3cf4325d3c36bfb82f3920a3a84c5c4e6eb572dd78fe014a2baac478123801ba2dae6828a428d25fb278f65b5148de36901953446c5a2b815b19121ab0770d92f70cd481c9cfb7d0752c90f4d4bc897d8c4d7296fe3a4688fb555f39c889e3f5679c73a8e9de61979a6423acb8162d7891c4647ee3bcb17af9568f2b06003e9efb7856556cf719a3b7c3933dea62df953bcd6ac1af62a3cb65e08f49de7e018052030edd3489e329776a5771bd1adec5842b5c32be2c8c99290d0a20706956ad8e673e6774440d209b27e862225c61590e3a0c0199f99ee96ed4990f81e8eab3a42aafea0275e1eb1eb5f49399aab2f3db52086b6b005eaf9ca00d4ec3666c59e0755ae3c39b4c57b578f5dc7890c4b25a2872945985461318bd012ee3003c16b2cfb7580b21a4066bd24a0bbd193e6b6b2d72b0c71ea3b37fb62fe2a2a21b2a6e965961133d4d037a911a1d727069ece8700a0341114bb8a65fd6c1ac15bcc93d8f350cb09164b0dc8258c7bd2ea97c9ac96f791975d61beddbb7994c392fa3e1da8141cb37f593e3791bb74194c000d33d264100e92e04e7397026b1995ef0e2c9e15b0f54fe1f8e2ab6c6de8aca9eccfd11d69aab68246af45ebb83bb7dc7b4260f0d60ddebac67480a68250274ba8b3372cf280c311e79f50e9308159b101be1cb67b945ab00bcd597b362b62cce834d26c8c369a245fdf29822ed8c7cce37c969a038456f6a29b770599783bb933b2c72dc3a3b0d5b88fcb91e6aab0778d24c91976af5ce09706a9d957a7965b96e5d72660683387d1a8283298b647870d048213daa468f21aba667b63c2237418e1872b98e59369b5168808514a8801881a9f2b58dbb782ff4adb5be130b630124ae720f3409f6a6488a97d26b5ae547d83717688ab1ae8c300e570915e22a3a59c272177a7cfdec5ee038cd0037134fb43344c584a325457028ac33ce7c8fe416d2722787835f6929c76bf76bec7aba4c9eb59aab744840956136962ba770b383c7720f248407cbbe4275bd34bedb6a33c18194780d934d957d15ea7205bb6abde922f7dbd6965a9f90514c73732d51df6f71f1c23f6f8454b6fb8cb2a0394ab59c4a7edc0a6bddc5baacb6ca6dcb7f1b5a8aeb2586eb058018be1562e65d671e17504b7e80c0c84100883d40c9f1ed1ab7a287c36cafe67fb7658a6b536ae9529472860d3e610da2c5ef2ee907038f7de17dd0d4b5125c2af0fd27eb8d1c9042cd0128654efcc62fe9d7f3adbdb797ba93dc1d822f073ad2aa41819dd4e13fd11b00874d6edd230b79e9ebb03fdc0da0155904fd2a1c32f28c624557e582ca33b36db0099e21f482dba73cad8d2dfb6b9125cf42a7ebd17e9a9725d74d2efaba657249f366cb513b1039c75a01d23512dbd398e45ec0ec1c76e3d524bf2a4e1bf352fceaa4f7fda7387cf568da06ef05f57288511a312f70391db7e8a93233f9e8023e6f82dd4c7565709bf348e7b8e42080e50c51319e10192b88099f63d770037235263580fc0b23659807cee0c4138e3118b4940dae94c07298c393c3f52d55721885ec016b029a62749aa9fa48b1f02165eaeea382dd796e5ee572bf9e3a86723d46bb0009d08460021167850071676b211ffcdb7cb5959224351f3bf8922a727574b1293c4bedd780f6ea9f43430b109fb11edb9dacf546251f7706084f8172af477895594cc168171c9a45d820e70af6824fdf9a685a6de7c231ec0ed4eb1f83aacb0ac9ce40ac16d1361ca3db486d3b6caf46bb7d2de9a76e0d8ac2f2c91b156087b5bf8d4214a54d3385a2d0cb4878cef71e1375301ca21e5f66100f85695adcb9f946076feb126c90c14f328e9a4b54d8f0ec30a629937a299b72792a723767fc232edac2876e489dfea3960f8526871435d06a92a8d212138f3689d172b822d475b351f2378bf5ba995eaab44a3ce9ef5fddacc89ed175dba47a617a05b0e06add99b91dbc787cff254099d892929e4fa6f39c4cbc051dea2e67c4fc651cd808c12e39c3b7e7211314310e5a0e9c03d955e360fb02a03d0ff0232d8b72f61d41cc74655c0bbb3dc38c6f290f01c2d3f7e9218c7798c9e1b31c548968724b7046e7a84310925be30829fb241cf90ab288ce587a978fa9059901a3875a72bc411e57aad7b3f19179f77d0f75080936060524e7c52f9b9b5f9dc446a91272c387128bea41fead2240efd3c32d38ab1e7a11fb53ac1ba9834fe091f9565868fe81ef3b57750f52044742cbe91d07e4a4ae111dd6c9d01ed2c00e3af4982272c1a2920d6b6d210bee79dafbdb29f8a564562e35cef6b9c2f96830522c777a08985ba33b565a403b8ea97e1aaa0300a4a29571cd1ee3dd37fa743856cbc14d72dae5c4387743891926fc3984ebd61a5e07fa542856cf5c979bc97175ddb0fa72a8438faa09a85692ae9bf6ac5e3c3e1b7414140527b8e7c0b0788d8bab3fe32b383087c19df2fd07f45fb7f5f76ff313985e549d913140f95b5195b38385cc6aedfa0239418bc86b4256bf497b169e071a8efd10341b3ffc4ef9d19aede1ac16f7c0a0b63e685fa4ea70a6d3e9e3dbf26f19742f145a6f39d1125025f2797a02bfadb870a7284f5865de6ef77c500db571d6f56212dc1a78a88c76d40dcb127234ae0512b1f540d9c92fc7014e442d1b745bfab517b2c88b4aeee9f545179872391b5b804e95f90bc14d35578c93fec691a14266e9cc6a23803e4900e690cd45054b1bbafafcbf04ba0da7712917976fe08ad3607f59a748ce36e843ca529b72f549a52d078246ca514f9a3bc43fa57ad10a5633f7a1e2856b730d3b0a552134b148a56b9562a79a4e4809db112d6810f09afe581d9b03e52c23c320ece22b722726cef523113137a40bc4929381cc6b846e18e1b2bca35b408903247127bd60ee3491894ab983f3e035b0281481a8273f8f30d6f21c4d426f90a57de8994472e71402ae2ab47b8094385f32137381a422e0ba0172bd509db7ddb9837878ad729ce8451cd9dd5450f768c3f6f0f9c9b8c4ceca85bb60b50cc5e475b45b054a29af27bbddb4bb3c59212e3833113db7304f8892177b9dd49033dc3cef93aeff72a909ed5a298bdcee8c9496e948122b630eb058a486fe0ecf69d2920138fef272103ee834ca1c7f7558d4c9b279bc853ee56ea35832113ddc5591ea5c38eed85e65f97cd7467c581d83f5393b2e1d3e8008e1f0a7071a0a40a9896fdf82291872e4e2857fda676e359dcf79f5597729c17b3a85e2b7cc55afb7b16941965a937251ef412af2267a122cb46864936632086383571ce5b04b988f5e57dd0456f269d5c6741ce95cc9cfc17c009efe11dc1841b69e4d281f0fb88fc0415feef1ca59e6dd3f85a8b79dbaa2ed7605728302005bbc2c4bfe08c385bef2f2707ece6202109e20975a2117f2260537ecbcdd40dc3a5ebf1795fb8f4ec9af599c8eb607722a44c68e1f42b6b63063adfccc17a26b6c90d7a396962f2ac0a959ee3ac864290b65bf25e6be7f045c0d56da428a5aaa1b9e3ab78697b6ea95b1a8d507015372d65e993ab854d973bc36f568e7f289ee706d4d16dec856704da4d09c2b6894722c451ff06f830a5d8d549d8472cf0a53f087dc6f137fa3e254d1290064412d723e4037d5eb120eebce31afdbed7366b9c03e5421d3596891376ee729d25d2a7244acab6a6a241423f30281005bd0ff14a86dfdbb97197ca87f71fa81bab9ed48ca746bde52419819742b56b7775fac167e56b340a59c5a939571e37064312d721ec63ed45cc00003e03b7060ac955d8d9da1cb68e82fd5fae23a21044f677b2cdb8c9f6ecf0c9ed15b8c9f90b85e84fbcd3a7e03af388a4b49a0340261e83532c701bbb3faa22a206c1507b5b13279b7dd88e6c813993420fdb539538d6a0818710d461d37fc9b67df349520d1aa76e66d0adace4e01759b147832d686d44972054c18d966602e7a06b222ebef12d767832cc6fb72c1385a47cd9836a2a61b72c1b684ba747e610273a7c6420d2cc2b9fef3789cef915bc52a2b0a8560b9ca72bbb33292b80b85956f1dd88eefd2c381fd47b86780a00f32b13eafb2bd1003225c0bda7acba4ec5f24b253a376707e201532e706bbec21d9819b5d12b7bef1720b8c44a0a9097fa60a39f8cb86ce5107400168d043d4923783cfabd9da99925efb14c5ba76d08c3a97b8c28490ed6b208aa54dea31f21de8768fca1c1cb3134cb36ad530f42c45466cf6690826a047a04d953b0ab18077a71886eb988302587291365234088ac9e7e90f6b6623647498fa82ae330520f7f7e129ceee9dfc132662f49dbde148419f37454ed15bf63bad7485777d8233ad75d00bc69e6bd82314bdb513541d5c12f4de126227ba966cb933eb1b9a18d43e07a864d51820ef6d72cbc6e1ab1fdd9d133aada517bde4f7dc1377cffa645bf6b87600f3ed0e79197277c52e8241e3f1acbe69acc5a82fea6cbaf82b067b417507fa6ac1220ffd0872f3e915253e572360af25894355c33e0ee97031ee5713bee8dab7005f531def72588b9203969fe9c625e185ae924b00cf44d4bc66552a17418bc91e7321429a3ab603595ece3b1697f95ab8acc6960699f5d7e6674ac25bed52bfe00de47c972b0fb55b491df385f589cc2e9f9c49f175da2242be2eb7f323680ee1dc71027008a90365112bf4801004756ab8940715c238d2f4382fb6021281f99ea52c41f14c028f97d7ae1409a984ffc65f0dc74c9c6903035b24052259416d6d77171baa3feeb6ea135bc008667cda1cdf1187177ff3f4f95fff7252a54a9d988516fdfa2338f31a7ae3a25d4c5a8e847aaed9fec258691f15552ef5c18c82a0c4881ab80cde7dc10c68179d93226d00d4c3e5c3eb828e705d25002fe69a78b8f132fd0972f12760349177c28a85723da1d74748d4f362b541442df8a8009839ce0e0da4724695f3f73c6f4519a9a07c12f03c328df4e4eb8b47e869170bbe0ea49f276c641e00a2bf47f4311bdd26990038b65427d6d4eb8a3c63ad44d3ef3615a11ef2690fdc1e05374d25cc61171fa9cfc0312013a0b24a8b9b13191b8c8c87f6857972e5eb9987487901f6994033bd193b9ee3fd9e2d2822ea04da31d3ff71a1fe0d58639582ec9046e1e3736260dce0d485393d7e4a7ba0af82ea1cf3cb452867b8725d5cc586f7097be2ba27fec6a406f80f1f959b61b34ed68e0f7c81644e7f4372b69a16d04c0a4049b1d3f77d1538bc0435c57f01546f2fb669d77ffb6b56f972340c016bc47ad8bfb678559591b188b34041f9bbca7112c696e0c106df4fbc72e84fb00302c48dc2086afb23136dc1d8211c919bb3ac42a043550cdabb489b72b20fa15d1a408f4e343329e30feed8019beefa22867f007913a748d785a4721eb61c203fb6473143d0c9f9fac6ceeacc6b4a804d559a48ca11106f60ba745d7268131cd9bdcabc402f199925a52c326e4cc8ba2740368d5cb47e5975f311ea725b15d54d4a198ba39aeb3fa2002b3ffda9410767e6f47335e6923829313d0b727942e4a90653c8ace8cd865d71b24f043db2428b7ff35c6dc357af321a8fe072cca6b4c6957af2bda473ca2d4ade8525a9d1708d9d2d77a5d7d18e16b0ef3418ad15e31006c6cbec185e5f92b3c2b20a681f6ad65a7562af1f86287916733b727056009c255a719456d1ae70356a996a02081a1c064d116fcebf9aa44994c8288c5cb6f93743a0d4d1f36a0a99de6d42cec523eac9acc9ed901998b9d54d157274ebe5439679c5539e8096343d318298aa9d0eb200a2eb8ed46e76c3de43a6630c2a4cde8c7c3aa73e5b665dd437c5d3c6af3e25b9a90be36db2bec4b94225728bd6d22fc79eb982e2d453cd411476cedd5d3d6dcf3b239671a40b37f9914172a741282ab56c495eaf8353210d94c8de59bf0e3d4118c6e58069ffe8940e983913118b1acf81e7d0c8c210d101861836d69373a646a14af82f293044b5f1e427937cabe743ff80b71482a7a27c17a24dc99b220326d0fb86a83eb4eeca22a0726a1d97b20c55c24ad8ee25bdd6617fc42b61217434abb1c55e0cff588bbdb57223a4df969dc7437c3ba6d8345697529943f34817ab151fb6a2657ca58ec9422d1b2f5ec72b0d00384c8d714eaf1dcf96555b406ed573b309be0285ad893dd36f97d6749a6a8f97ad3b457f592b5fb140d386ade56950732a923f69ddbec18b720bc9539744784cb28f58a09bd22e62bf5b8774e4210ecae9501f6d4ef799ba72ae4d137b45020ca875409d6bd20e46e48c3e17c36700ac9924e03a3d9bc2bf7260c9634da57ce2e93399edf0ed8193bb540aadf5314f6618140d5ba2ef526a72242cdd23dfeaa8c00366e447b672f297cc1a807b970a485ce8e0b067a85723721fe6806a77c2fb81c0965b482d49273bd7cc8241645b0c7c5d20b1e1b09acd61c9549ca359e04a96d5aca82a4cadedb1057a9c44d023212423a900439184456a8ddf631cb49b513787f64a323e81256b19d6bdde880a2ab782d31171e7e82f54a20d7b4c59bcee714075769ce8ffc240ecb84f0e93735208f5d171c33b6e19161be779a0a570595db1fba666e5475cec67e690ef254358f6a25016143d0adc1d77c6786aea623661665cb52e6ea49d0ee3961bb35de63d2fbfa9fdd88f096d7258433cd29fc38c9e83e08e963e6296036a1849dc39baa3f83c8ec1718687a072ea890fd658d92239f50758beb6f311a6d7ec3cd57661ac575a7e31d5c3bc1b72325b3144baedfc7504858849ddd89117763f43658431a593b027d79253fe4172aef4d4b76677ca012ff2e36b0e1a61095deee3d68e639bd41fb40d3c2753cc7209772c3f8eb4fd9dd23e16902b758e879f9ae682ac1162ea9cf10d5de1e3bc724819dddf9654c343770203eb000ad70166a3349451ba9ef292a5bf40f2535372eba809b8a06846a80d22ddc8cc25472102f4df26c64a2f69d3aa8b499fb6d412bf23d1c10e6cd456244379a294d4e2deb1e13098f4c562b7a8d4e40de31a8472b36e89b7b648362df23acdfc58a43e205456d9578657c7273a21a66d700311033c1c686e3f183c88d4c1f8754b2b729f0750a13c15f43d06ac2ffd59ebc3f44cee326c5f81f0af02e0953c0c7456c392c9115b2ecd37b9f6a6fc71685ab201722908a43332fd4983d5bb4a64e3ec1d81a1e4b6d4922df6216fb80c10530a8830b3ad09e6e5909667e3a0c9620ed92ee74d4457c2bec36da3ee3ff6c265ef932f2afdbb8d1bc6a76e1a7bc856823a2869ce2851c77ad82273c1b72b20b7c765720f128981a076747bbdd93bc190b4db8d846bb0e77e88b321dc31444e890245508840c5f11b83c20c81979f53dbe868c7d658760a986a4eae07583ac971939b6b5c63439ebb3c3ac7890ab6e060a6996507cbca366feb969a86efeeff56bf3e72d692f008641339ac8e58a9c46ca6d259d91c912b4e4b3e438bc9bad2e142f472c4bdb0b656e9515d9a15998b56af309bb9f85d47cbab9447778376f4ec3d424d66cb8478f1960228bbaf92118e6974af2483bef36fe915e7f23391444100a757833aee9f3cd661c8a63a013577a3bc5781c2770f492a35adda6b9b98660e65377eb4c4d892f48f12728c30506663b45f8606e48d453a0eee7b12c761b50829723c907f6ef781dc4abdf3b23e96705ac37d5aab401988046511a1455c421b88721b2ba392098229eb66df126ebea6476f81301ef790004a0bb02ff9cfc8e5143729898077c2215e2b496d74b4a4c4b0417f6968ad32b10c73a3d94165c28d7072970b5bac508557468f9ca38879903481582524bc50bc3eccaa18761aa0ed757201bd79ac94a4b03cb1744a60a7202d063692e0c79cda1cb43883440907e8bf72f8f7dd8c6b7e19a33bb0ed898da5b48e4f8780979ef676168625abd16a0113698b099b36a2737eab54d2368f3662f539abb348cefa8914605bf02fdd5aa94b724da8bcc9f0111ff2ef5589fcdc915621da56a7563f71a1979bb1961df058ad72f0f47dc596c25e4aae9c469ff62dc09d795c138e12aecfab60ca7aca72ddf616e6069be3f025061c2df0535b6b7ae32d22d16fbc78b25151ce128d1f72b23672a99f2631be22b7a0dbab5f729ab9769b95f29e5c1592b132ac6c46e7d52726087ef89f1c608d3993755ec11be9eec9d2734234f1739679cb4b8350514f1a7772c5517be3834eeca01dde751fe6a461454b022855ac0d3c7a8a25dce22a48fe14399bf1e528e2d924fb7b072856c1da31bbb8227c52b7536f95a063600732f77283eaa9280ff5c0e7abdf7302040ceb8daac8a4057d08fa883607ed9858dc8554679b4240a0eaac132de111914f796706e97a62f80d46b2ebde7902bd55dd98724fe2738e70e56b2ade1b8625db63aae5314662e9e1b758060a36eea0199f900b0a361d4c1886bd5923d0126bcff7b4b11e4dc324cfebdb48c38634f348915b146949dbd4a38698ed68ccdd5cfb44c3615ddbb2e0fb30661965fd3e85e80ad44322a46b06f593eb178b74eeb3bb9dfba24e72b74359408c52e2b81304851a134e4fa21cd27b064c1e3003f7a6bc071a51a71630f24b672474e42906abed42fd72c066b8a5ba9e5f862cc63f40f07c8057f979f88c6e526668e291d85289461402c1c1c56c47040078a81bbd8062f9348aaf0c5d823f36c23b718b15b0f4c7c97284a8a17b86a00340549ff93063f10e9cd49a82211aa54f5528edfe094f9b6b53c2af9efec6ddfe8d7cbd8223cd7e818aef4cb5374d59a54f1268b4bb66d4ef6d81fe73c5149f22a02ced51f7d909bc62ef3eb4526ef3b07dc2fca61621eabd72e1b8f2cc3eb7d59ae723daea50714d9bf95916da4bee0b82bea7bb65075c736b2939ec2b7fb9f15f346c40507210268276b31ae3379e77777b1eb9b4388250723baa0d85a6f4107d6a842599e65f5f1c57f5657cac07b0cb5a71ae356ec2cf6e0f58f72077800fb55bb56f8c5e2029b4cdb4ee80e26ce645336c4650c2602472369d6de982727f541daa011853aa069c3518c3cd2a07c7db2a3b87789ccb0d7260ac31c08e9d5f44ca2bb610e78ff3ffcce2791edcf493257a3aee90aacc636f09fd87f0bc9482c936ae3b1f8a18c10d18ed51dfb9784a18574a6f0e547d8b7229d8c87893b87438184d8e49c45ed62b1907840950a2fef9d67b8fa48c618c725a59a3a516b79d341cbc147bbfefcef06ae51f2f4de7ca8c3a65550ad7bc6b728ffb64b83896e8c17a3cdcd88baebe0e0fcb1314050aa2d1d8631a292ba2e2727095d979bef70e80c7e6d8c97b05f6521f3bc5368e8cc77a8d6c17aba277d772e93f4f4da73ce9c577fa348c8dbcf908fbb4fca931c7c3a87bd81fc8000d4537b6c8997c0cff6d2533ffaeb038b454d2313bd84fd43c30b7cc01785b735eb7728d8cc4f7430ae93a0c0d304e0393601466fbf22a615f4acbbc1a79e57da1d87201e2d7cefd1c2ed15efa116eafa8533df976fcb0bf4fca488070cb495d64f8722be5dd454774a82dfacfbcba9e5bc144b354636286eb12b488007e630919f81347fe4df1d30fc39ff7a374c0ae39b9300b490149080029197f2bddac40a3b160d1917ba2a8bb653fa252b8227b7dbd28422ab0dd0d2a1252bc976920f160042dff9b46eafe97c3b8e7e6c2e82f5c47213b82921feac9e538eee17997ca33d47271b5d0d0c9bc795bb3985ee2cf2926a7c795b4a2251dcd33defbdac3753f0d72e9cb8d653aa885490628a31439138781e6c4600f8eda385aa5134069dc0ecd72271450c5b8bc20e044d0600c4baf039befb983067312fad2b7849d7f40be40723f37f810142be23923d4d4ef21a794bd5082e5cae8bbbba8b1f59ba6825bba55b8e4e2f861619c146a03767628aa99f655130bb71e8706d0a9cd9330526ad92e26fb62e6d059f8034fe5af4220437110bfe3ca923370cc8fb4dba604785a4672e95bbbb6f870a8fcee4d4998db7e97a1f2343787eb026561d56f33aa9f4c1252de1152123e764d39ac950f577702a4266f573c1c6ffc92bdab717d288a0ed9048b44c7a2efcca75a63d2db0006ba63bce1ea4c0240a9f7dc0bb6ac1541679972d6e76842d9014824ef231f803fcc621eeb839a895d2cf4b958a2e3b7f856bf7209a5f540ea675c7c0a31b37c1c4d3de4c96bfdb3392bacc99dfd28e6b1f4ef6618dc1bd42ed56782ea9b039ef6cfb1c37bc4625f24f2fec4c8d346d01128d54e82274057dacfba585b3d41d71c04cced02e88d515b839b39caf19063a83ed472f18bc45c36d92bc037a6324760279adf198ab962fc03b6b1ac14f456d78e3c1a523da3288936539770d1e4c8e62ae37e1ddbff074228c4fe526a5634f29a7072225d412a38f46de23ef61ce152f71c5cb7941dbd871792f26174bafec89337721c145eb2bbfc3128d087755e2eb27950094269b4f1ff4548d62535878125fb379378920abc9e3d01432bf3eae61b46b865e27950d657a2d6a69fd2ce8d5f47726be624e5740b496d4beb124bf31a59fad0ff0f0136d430e644bf30172439a213ba6da19b0485e735cfe0ff0e1267f8076b72d9355629e73d47b65ff4ed86af69b4c5459d0cce4850646732b968a28b10b56c4dc3719449c08f0a4f7f32a92a370e34bf5685e9deed7e2ed65ae3ebddedb3c07c7311de5debbc8d4fc13bbbd731aee6db99c2be0b318917463b36b8a5b045fd3c287e48bb6b03d584d3787e13442e8416b5cdcde13701fdc7c449f92004d47ba9e6d666925bf60cee75856cfd6e33152da6d451411d80932b531c56fcef2991ddd27d04db4740bc90f040d8af03a0bca7e454d1ba5412c72dedbd69ff77a45d59c91f6f613590b2ccdaba8658728ea5c7d85109d0300873f03e05135ed91a1568feb0741b16f5734ec36858175b05b787ec2d4884b62c1701e41cc7a6a8bebe3c916ba821ad15dc9e437f4a2829434b0f91b0a18588a10ee12e8d5bfc239b266c249e4e45daefe20569f2bdfd70d8f32feb8023d40bdcc46d177619543224b1b553e2c26f831e2f973ef4295d68232e624b01059cadfa1249f5fa99c26e2f91d0d6ade08d401804cadd3f841225dfc12d76f74c92f35f608bbe97229b8b46bfb35c0ac60ff27fef63019139ff7289960c355d871fedf0ee2cdab961757523b39c2ac2dfa91b4619a9129ac9c6725012e83ba65566e4dd5dcc25640058494561e625282a624addf1bc7b9c30cd721f92d28210e7446348d1e3c146370c2e1dde3693d48d4fcea4bd04e94f8cca728b4842119bf9555ea6a2ed15a4f05303363638135aa939530b5b162e3e89cb72892542f2b929c31e40f6d185ec27ba832e726cd1b182e0d243af030ec615185e7d729a4b26ec3d0b2fbe05443011b1ecb16a65aedd1c7c30adf96492918f2c2b483ad032f8e1a884049d2c837878c6a2cc6f44aeacc612cb6c81bf5bd7bb1418319e3d7d025679666773599dd0ec22132a48ecd4e1f21cc810997c17a3249372bff0c8e07f3bc7dab1ff4926b73bb22af3642cb7b19dd802386a15384166a3724f22de3cc1e9d07981c6237d3b03349a379fee48dbbb11ee696125b265c0f64bcd21e0c8ff9ca4c7cc429e9d2806d7c96ce7e3cb4df71b277a9cb7e9510da4622f36cbd82783dd99f69185c993794cb78daa96e400ffa5e9a03b7f98e8aa21096edb8ffabaf2287c87a8d48d59aac5fc24d6bf7fd09b56bd4efc119372042931acddb31bde5b1451cb47781a39bfc297a4b08984d43c7fa596674015daba13316c1204f2d14fc9a3f1f95c1882ec323929274cfa492784faffeaaf004af3b47288411ac6f362fef8a241c482b19e8bfdcdb42be8a3a52b9fd441ea7c4f232872c92d608906e19a4abff644a67d8eb82cf8fb14d2312b650bf10bc5f919db352f1afc381118135267be8260c6e63b5ecb6e62500a1d829f2cf2904cdd90dde042248251e1d835b9cc1b9a1fde2a124ba163d0e43501ef0c3a7d425417816ae972025316f4c39d58beccbcf8e9a9b582d7a663a60221efbb297a9e8d21ea4e9d1301d008f353b284d9a5aa0e215f7ea3c5423482d12be15681dc5e7a6f0ea43172a39c519dae8fe446ee97db11fd30de387e74f8c1bb0c7259f18dd5411e9452726cf345655f6df75da4684c266efbb0e849cb5387692a4608c66e8ffe7a05a066edbdf189c4a3cbf8435b8306a859dbcd0a72b12dd5500bbd95be6ec56bdba414cbc8921ba628fb80c3e479c4e88630089e3e6096dce97ba5e50263e71bb58d21b2bec6af236435fb0e9aaabbc14efa4d2413d5d18deb16d9f4cb4684d097e830689b302be5c1f05e63828e166cd3ebe631d41d20dc0d93ad7e70420a39e1a921af119df50fa7eb15fc8272d95bca9a39e3af7ac72fc8e245bf5b96be325a18397ba4ddf718583cd2807baf9f9944bc90ea2217bfa17ecbe10e7affa8ed878b5df5cc79803f021682be1abe95169ac0ad041bbf1bb6ac765ee093bfe88075644b04f66dbd79c15542c56439f851b40b554493361267b43fef3d0b236a617b7c723cf309291102aed573e196b6d0de6c3817d1d7c164b5c79ec91a4f5e15ccaa72d0316238b2a0ef4eeaf3f75486c0e43fdf830d2bd659faf484b3f78d1f3cf325deebeb98ea846cc299f6ecc72fc858778abf8c28cfa748eeb19a1fa002c8a44d5b16c99c468961c94c11291d19b6d6b01da45bfbbcbdadeaf62d88edb4a4f464d826d74de69e0b53c04212d14640b75f2fbecc2c0045aaecc0ee13cff5778715a50c3cf96d652f871ffadfd10bb0832cdb46fb94baf95c38e8fbf9513bdec80e3d2ec7ec2e20e49f3b397958239a28ed63e464f07471303b1cc6057409f0a2726ed5c6ec8843a88d87ae08e91e086153ef8ef55d220c5beb228d7d63adcc964d9c33d76ec0af509c0c89db424e9984555849de1c11cdf9d516785479caabc172a3267a929f2d264db187b78bf4cd8164a8c18af4466ec1f86aae7c266dcbba3892d1f2f0e897950736505e05417561d3afd83329d789bfdfdce3790266bfd6726e3a8d26a0954af72ec991784c47b0119884859ed06e496787dce35c2907ac5a43a2ef254a3e44b3648ba0d31ab13f3bded51883f49613eae749c29c76a825627f17ce33ccd20e7fb81da2a3849c1ce1f15f3b50e862f4741284c26765145f173456a447d9acaa59480d59ca79daac8eead0459711413748e11e7cbb49ed44002ba0b44679358b83f12c80f278ed6b1aa8a08a20c081253d93cd289be3e6d472f88a01b1b59d750676b43cd74f543155eb99a889c972434f436d70ab4be57343731e543c560311d4f1337d37e5f5402a7702131b01fb503e88fb381a3e023a72b387e7d65fc30fb22d5fa6963356d3009334310e4b19d5252ada6f44a63e701741f1ec1b689e8e2619e7f8e97d79cec65710df8ae950c15826b760e38f5c4c5e01b789fe12bcc14081f61b0d4976b477a5649d122fa8acf29478843ec862a772b34048617fb425ce64491b1b080eef9680494e0483976c629ea6f461a8b7433376c51beabc1a5e2519dd32bd90e2e990c373909207606ebbebd548c220fc6a726a5bd6aa2f00e2c9ee86f1fc174b5d1f473c3b0ce3bb4eeb8e94d160ee2c7972437a41fe5c78f0da83fc022b5959ac8a1e4229edb9a12b65524e1d34e8633c25fcd83af2271c53072f751093238b773ab895fef1806a9742a251c7689f7c32693735398c230f94880c1143becb0c958856b92e9abab516f8f56b5a8cfd37e34c4a090e3ea19a44714d374116224caae894723759b1b0ba300f98396a9627c8723ecc1c918bd9dbda4342f920040ac3a3dae45cd5620cfc0f59aa70f240b80a4ea8b8935167619d8f5aeb365524aaaba33bec0e7cc787bff459704955acaec21baa14e96f5d1320fe0219664c7ffbd7f49200b467443801a58c0299d04640aa72c4da5eed46f75a1d8ba1511de41514bd18b454c133283fde3443130e9f0d4a2acc5262589c045af0944ddd230b5bbebbc1b80ec787de38ec4557cedec5389c09bfb5a78fb7957006c6437f388253953fff259b2b0f9a3364a4732cca86612972c7c4efb12b0c7d1584e39b65935cdf95603bb7f78ff4ddd31fd4c9285055f67272e5835edd0bf070eb3cc82468d68dee1ec7ec39863dbcd1139d93975d8b2a073b00a657528e7c68023320c402666cc13c2f8f1ea4fadaae0a1c7e0a8b980672d6831b7ca5a197fb1721eb326bed56f6b08ee201d0ce566b636566ac10270b72ce0d65daf71673f09207a545ec850fd177e39530cf446aff575ccf644146274b59ff0fcec73aad00f6aeac5259d3cb92455a8155a3000fcbd699771c687fe527f44145c7635f1b9e9dc537a63dcc8fe5226f8735276819be253e0d8c888b287204f4095c401db61a844dbf368fa673204d015fb806020a5f2e812654fbf93b72dced758283ef68a2ba2e66fa994304db1860fe4d0a02d7322f4599e40deffa72a6ba1e5b9951dc22d1a29d12be67edb7d5ef63eab078fe023f84cf8a480a0c13e598e6fe126c78bdbdc19a23becaeb979aede0236a24667b52971fe40db1267219768093a8f994b929bcea7d46cda6421824b2d0c841e0b10d77905e7dc1457254ff7946ab691bd7f0b5907303a4eeccfa9edff800e47e0c303505f2f11796724b1bda35898188025c29cc13a2e0f00291ab1c415d8d794fc7316bda6d595c27ff7b0cfa211d5ab8392adf61868ab2e1425d6362eac75694f55fb0bcaddbe072eb385298f9b35df8c358c2f48dcb0c68fb2caf3ce941dba79c0f1e49920949720abb2747ea2abdf42721ed429524232a3e0385bf256a97e9f67322c243a4df72b6acd739e0627dbc5eebfd7fea40ca5265159af999652ff6cac1eb4cc1635d09d62ce656b5338b95837e09070bc7c81aaa0dde38110a6aa4b6799588efda6972a6ff62333f0c6c0295a2ef6b4a138b89d5886446544f1d568301fe9eb5775e72ba5d3e997bc81d64838502a8cc6e1bd9244b5631d012645f0b613a9285dde026a17ea253e4b3908d3c9aa7082444b8d0535d0717636570c1403458121cd59672ceb02624141e729096b737ac0d55e0e946776168424f8ba69c99e43a778d27722f0c3d23b4fabf445e3a399c91308d6f027bdafa6d5830f161471cb77d791f72b3a1b5f5f1e7d99c9aa6f347c6729a7a1af516d220f067aa54e82cf40521a7729269a64a1b14e290a4c2a2bfac2cd847e32ed63b257312ff8aa67ab114b464724439aff9d2d8a6645a73d8a54ac58f44fc3ca20cb635a43712626a6fbae7e272579cc301db2e2477f4de50801d7d5e5c0f76c0031b2c78d558dd3845d5afc37278da9f109e9e169de4700f0d843aa7bad347824585fcb791e5963b77560d26342d1f15440b0ad21fc8ce1094734e32611f6a7ce39d64c957af4289eab7745772ca4286764b15e8e1027a7a96559aa8ab2297e0e601d3d8edd3de1eb77187f37258787b437c1eee2205caeb8378866ae4d1815f89d4a00457d7b5378a3670be729e650bb9de115b51425956197c7f3810fe6af2b9075d627ec6aa64be36f7db72006bfd59133fd7cd019e0e064ba158a879051bf32eac4b3c79fdbd888a593272b2f05e97b8dac912b9bf4af5977e5df9d99946f76c4c5a5395df94821404490062f37af9f727c34fc81ee6738ba65af4067461630c1681474430220cf8505f72a650a503ee22da1da8de87e0d63f75365722b30da42fbc8eb131c241194fec65507875a5d70b7ad5c5fffb85f6abb6c923fc32b243406b0ad8e437fdcbbd0172a7ed085dbcd5b5e503165a7ca532211464a4b373b5c1bdd77da6d7ed09fd1272c90a020d5c9a5293bdb38b5d5ca314e45f96ba5331d9cc1ecb05851271577234a257c9739bb518bc45b1b02d7b214311a0a06509d27dc0387aeabaf7e29ff372f70fb0a2b6df063859ee8862dab3392dd56f27574b63c3102c9af1fbe5d25272f74178212a5d1e7afaa85732effad15c1ede1f5eef865b451900e26bfdaabb722b0722981091fb7763e81689711274dd6fd016d8ced82561a845712f39f2e63e004da5fbe3071f4934fa90a8615bdd4e6078483ee0ae8df6c2dd52cfd384af72ffb4e3980926b434cfc1ee00fb5fdc4137942a2d1e708c7d6c6482e3126e171e0b8cc7d542c1114f097c22b4d1ddab57e3d720a53928e321d66906de272a1e72fe7e4b091cc6ea10fe61aaa4dda0066ce31330f5a98cfed1f49ae1caec22e81973da45afd6e850b66adb074af51350ae60e711fecd08fbea1bcddf6786222b72523a56bd1a247635508ec95805dceebfc250700926d442cc0213cd306b419c72dbc2e6fd0d7795b09ce959ce9ed440f8cc1c22b87319e3c9e87e2e8c8ef5d2725e086e9608552112aff9e76264e42f39cf15d19f1113fd11b1eddee83744bb383dbd2bb954b38779bfe14b97cb70752439d0471e5dd5ba0d47eae275f30ee172a615c32c8d06b3615a826b8872630befcff50db2d68c6caf4b9df6b05ede3f534f5af3df20f605ab4270acd9a0c4d8874c2eb19a7cf95503b28bc0a480c17e727ea5598e7351535a0e43e233637dd2a097bfbc17cd3e797f96cb4eee126b2772d7cecd7d8032a3d16a45da50b0c00e44186261b3e4d14a23d9550ca477854372b4a34246c852f5d76f99867bc95483b4009e9a5f6ad111e8c105b57008c6c565154e1b42930aaa18008d20abe2f0c26019c79a7fe965b5b68d73c87cecc3c124d4f57d7e44166483f24059f1daf2cc5060b1bcb1a296c00f8f695f6bbefd0c6fabc56f491530790434f34887d0bc8dbb160e3f0af9b58d1dfb7e2554d0fa9c721fb699b22d4cf04370a1272714dc8371ed8920e9ddd9e2043c2513ce221a1372c23cef5771d67937722cd725e82cc99aa67414d6a58e109b4b327be76fb9fe7288c6545c84356bab34076c5b289692d4de638755a6336dc10956c26c74c06669ddcd03f80379d33831b7f83c545c66373e8134842f36a9f528a20a749364f07282969f3d75f78a583691025c355ee6a5ffbfba57e95a3a30f1a34e5902b0c37283fe899397940c3535a45cf1df5548225a71d422030f322fcdb415653f92fb3d630b0ad5b52599b978b818414c8bf4e91ad0847ce49da0954d62134ae3e529465206bc9f5f38c5c8b2e78ee207b8e110117f97d24b5782fd29627a471526d872d488e5ba1ed5f1e406a28111015f2866f3593b9e080e726cb5c9a9fbd6345272a8044a2dc820b3639edb516d5d7be6e32f5868d6e527c90bc294963d79f25972e7931958f4d57372e038a0834cb3e18a5f94472ed12cedb72b05322dff57cf72eb35e4b586c8205086548605245f6eadf24d40fd2d33efff9e578ef159b4be720a36576c6d7c8a15fd9f7feb4b87416ce515bf93d9ecb341a1eba4545293ef14677d39a0e0ea6db2fbb1c774c21d4c82febf046063cc2be4acc19a5bc0031272b3147efa1cf30a650c8eb28cc32ed9023c1e511ef90652ad5b7c49fff65aa627eb053c91dc08a7990cbc63a5eef90ba70227fed62b2db15d7501bac441d3db724ef4372397c40dc5826fbf3e90dc4f53fc25e0986577c0b1a1b234f43e875e72cd49eb35da74747c93269f7eb6f33066137300ee16bdd6b20b57042c5183b77241924e4e44c3d185c4af3737fb144282aaddb1ffc2cf569476fd5a550877f8728fc351e80019eec7993ce3603ac16d1b59a2dfd0a9e624a4d11fbcc2bc943b72b4c371e88302412ebfd4fe67d15fdec35f8aeb7d37460f384d9dbeaef8e529532b16da6acfe43f850bb8b378c8b889ddc21801eaa69f41fc301df5a81d19114aec86d6d5e4cc5eecec2a35a5a9fe5d281ad7cfdf14d8bf8f01a5dac17b4bd872dbb2c943e94625cd0cfc0ad6fb5bef6ec31d15354de3befc23695b5e859ee15aea7a97643ce520af6313ddb6e1f1859380dfa9be89f02645d7256d2d2afc7e72bbfcdd16210fdeca3b3b3fedf3f6b92f494c04d773d25aee7971b9a356448f7221dafae47d786c270f4e2260f74e0e08f7e5e93f6a3dd0a31e0c4a43c01edc2e8c964b75a9c2c64bc113f5d3e543d9dc73a571ea0e6bdf941ba70d0ef687e772af8d52f3c667f8387cfc2d817f954ee525525f67bd9f7e4c95c025bb2d46e90d4e89b5220228e45b1a313375ec5f02102ad1ad64e889d367a427a7b3bfda61343b63f87a498280804250090642e52ccf4fac1749104b2e6d6ed8113912509e7207f7634f35cf0ad3e8eb13e127ec779f78379dee15a606b2226a75cdf5d8780879a00a0dff8dace7c72094c133f8935de7cec01c3877bf84badaeaf570e32f725106fe659d712c72b86730c75cf541ccfa7d131f6d4ee09c960527ada3230c72370efb40aac0a4dbe65b23adee6fab6a83c540c9f4d6dbedb351ac02863f0e72b5136b5257e64464b96ad3b79c47ceb3a1686492da458a955de474dca0700e72887c6e8bdd9bf44b4a032b53a039609c8980de55db59714ca99e9c971bfe976cfbf0831c1e4c408b69951337f8ae9a3f0698b774903be906df455e90c3d944567d42f470fdc33bc5abfdcad79236c246084439f67633163b4e77d1cb70215472fff8f94322868fabb073b17286f50ddcae0361168bcbbb9f91ae68348eae107213117feecb7c5052ced9c274e4861da54f69c465c44fb78507d52ed1261c7d48d819102c539f36a1ddb17119e5038dadfa4684c39cf07649dcf96397ec627237ff7b6e7e2a4a2a54581e782c197fda6a08ecb9084a370dd12db4c84e09a0be727bb546e9d5cc33636f8c1f545c8d6891461d16113db800cc33a9dec4da2da972f96dce9377f64e4259c04268a06d910bd68e11be39365d358fe5582c71fd217242218c6647faaa05d13c7503fa0f32f603132b4340fc00e22109673b325cae27d97a1ee0ecaa33d4e258bb705baea2d3a2746830cd11720bba8a986666d72667f19dcf1c5240389547e3862cd324e7cb3be2dfd855186cd30da3fd436e86b10e68d3ec344c9b45f085f76fdd0f1b26e615ea261e29689a62ef04527f961b4172115e3688a9c006998592256be58664db49b85328f731f7a9664b85888d927c0c8355293cb4cefa9739ffae5b80192d17559b43440562e31d5f9b7cbfebb3fd721552afa569cfba62ca41d54398562d19fb5b1732310da76278a48e489f83de726fe19f603f31e9fc3000d078d5f3b310e017db642d7dade48d8d3c00ba5d0a17a0528b4181c6bb30626a0cc28a7e9633d4495bad53f7b43d8204cb586c2e10728587fd06bff968343584c6e799c88a6458fec03d5902e12fd60741c5cefbda0662639dfa9e1a87b654d04572b8f39d39312d26eb5f2ddecd1c837227edcc415e70aaca5597df4bccf8e9e05943bf1258ad4a3209e2c7345d40c37d52a60db770eff06ead98d308397a311ff939c5e7c237a7ec32ed59643247591519be1a6672a7e7a0ac3dda338d112500aeea0675fa4d33318fd2e206bce3ba8862e6b33172b0d4aa340936ec04724a86972e317df7d88517f9d660b1c6cd5fb088ca0f556bfad581e330547f653ac70f88f15c45ed441d8c24491db077f4c39906f1c95d729666037932fcce6dd410cbf4ee053114a74258397c72b92fb7980e561bd19247e431c60dfd886c31b92255aa1f1059175b97812b21b164f31da075a2b12aa272bdb90ff8150b7638eb57c2d4e4907664fd35684bc75bef782e5dadeeba1600726f9674e8259fdadf3e31e2a99400b0075b778b6f78ba72bb518f123cb74e547208e08f9a59275a07b43f60ce7f60572eb5867340fd5bcfde7e6eed77df5c0b43202c96f075d3b0d8a6f8516f1fd84eec74ac5f0a037e2eaa9b0666366d5817725a25e4a3c6e53806fffd23ffc03e30de54cb6aa5800ebd3ba942feb161209d72d2a89c1cfa8fdf8ea3844ae9ff78c6abd7579e5a7fbb9ecabfb57180f978647216d891ea1c15be99b05fcce7a61cd51fb3bd6dc4ecb6fc9eda409614e6fa5072d6e01d8ff0d57e79534b58148411a3861b45e147e6b0e71216c7f1406c078e40bc2cc1bbf3082801b3390daf81f14bd0afbdb325006bb3867d8e452c1d2b7e72160b02184c89f4f12e33565d6b2b9dc586af63d6db714f1282d466a3f819e561a6d6a94aa7784edf39cb21e9717f10513dc6d5811b50487329017c4b76ae37497340f5325d074905e867e506ef257183f93031fd117c521bba766445a4d0cd3e7fd739d2516b1c2a884043eacea75fda095f44a4198e91b703a928a40db2994502ee48159b46e0bfcdb2dd112a427ea5263027eec6329f85567020de29776a39837f8ddcacf00939b8662076c27b2a26c405a62e57667f3d743b9ca55e1db47230cd7067c835f1c9fd151255b149545e6204a833e94cd8bc91a88b1c08f0db6caf3b18264109b130d893456afb837672a8724561cb3fab32d2596e47949d6361fb8234d37b8ca93c90d9e6ccb2473f70dc8e88bcfb729100df3f57770c642b72f8e3fb2c5147ec272bd1708c365434125ba75bbeccc3f1e1f2bfa5c7f2d06f7216dbf3a15fb3f0c2e083c14473e1c23e42bc696cd62b239b64f3e88e607ba2455f8e9e3592d70bc3995dc25b37d1840602119911dd510788e01153b9b01a7341652454a363ab4ba0bc1ccbe2d285b08050e2300e73ec32a820a6a1d35991b772dccb102f57d1941663c3b468d41027fb27430adebe58ae94c88fba0bbda5af7249662d0ffbb7a6882fcf77805b733eb1fcded97b74a4c600db18d00445ecc7723a798401d4b8e0c3dab15a887439d8c57dfee26e9acbc363336b914b2eb07e72ee82e61eaf38535ac0294950b768d7c73961dc87b4608af468035a8da9da0e72424f2dd73647ea9836b7f0239e7e1a353509fa257cfb5857858f0845f0306d72acf538390be04c6d2beea6a746946ede4765ca51a774b6d36930f7f63542be7266a3011ed4768a5ae780304d62b7dfa631e9c431ffc06faaf2dd67c2503189722280b4b0ff8a0b95d3d15410121bc4d1784f4c756f54504d43276f3c5c601f7240b6d654cba703b41590b8a1cd47660d4cd72c29301b45a5ab53667330d3d07244dd889baf73cfd309fbc59b7fe03da37e4e04fa7141eb008bab993dda794c72a54b73c5624f754b99b488730bc7483a1c3af129a03881c6d760586b04ae57672ec7ff2a2014eebfc83611db1371c00667ca8613c5eda45d7e1e4cff08868c72818bbd22ead3a86b8cef144d4974d7d5a103f1b23839c3a0f204d4367d3fd17282a78e71b2024837992b685833cdde067e7bee60694fe964e4a23e33e4c3c608eb276476e278aa39a6b228efeb8f7061e67d70a40e001b6a813135e7af3356721e5f70aa753df229778e5fef27223fe19a9f2e864939b58be90e4f48cb5f3e720b9ea1241cb08a1dbf83f20730cd2624af1cd147fafd76c271d89175d0590b549af355a49a736ded423ce71f6fae40896ee4f9f2024027a265ef66160edd967237686f7f36ad434f59c8a4bd196568f393d562a7468ddfe5198d8c26e704051f69a54d793d2317a624cf15d693c3ebaabc3d3242ae7601dd29e53e0ebba56a725956e30a5cef63cdd8a3b2069c2f59b19f756740ae7c5295778205669bd74072aa50e968d97afb42c682bdf02bbf56a581f26fa84732f2dfe5166333aad545729cc51db79ff6cef3d598351c638938409f69fd5d76a8fa477048e68ffcaf1872974b23b8d89fb7d85f8993fedc9015df2008d92a308708f241ecc5be85110d72145df9444b3b0b4c79e7d342ea0a98ba57d9a7f0a6c495f6759ad6e0ee3ebf580ece2cf9ea68942af62a22bca9f6c3116ddb8e6b87d3e8d29cb42d4c7179c3725dd0e34cc4340707cf78cd16a607ca5761108c07a702883c15eb0ab5610665549f0b4a0dea2251daaebdffb767d8f3469c8114fdfb3b5268b545089f720bfd1c977c42136689d07373545af92c93dce6ca7cc41715e398251a3f648a510617720f85f4223d066a934676720de86e0a1d02a5c595b21298322f07af1b9c0c0a72def1e083cc4e285f865f8eaedc45708a86f501e5423470d8a679d79e302ca272ff52cced0d1dc00cb7a67c1aead553c611823ef62c83dd733d965bd97ba04e72ac6f36702c6249d64970da8c8951771e621404f9db28730df9bd496e138fff727a7328c13e4df22b2ef75ab02bfeb84599fe1f71f9eba2434cc07b3d35dda87283e565859327215e5f27786f5e5c28075fa677222a38b3369c510537ea995f722cc1d20ded2c803632fdcdc07af7fd1a1642a20b43d4cb5d25512c7c1c7873226ab7c781da02dcbbcb8c38f3db8d8ffafbdc35a20616dfd72993dc418bd70560217fd5b51300a7b022f3e3efc05110ddd5e81468faecba0929ea8ead38d6a7185c56db88e8b6954b8c02939c902c455c85574b248115aca1499227caf1308c720ce9a8d1acf7348c77bdfa19d4777ca6cc5a021cc88f4aece9dbbe967840443a2370fdcfbaa69c052b96f8b67ded091e9d2585ce99a9f39a141a0d51a9a28c0d40883209f3c09cee42595adbbcd352652dcf06147c81e35ca8edd3b1f643e9728d16fed107c0791fdcd2bdd667f6b0776edc53077a898ea24afc6e0f7b005f6c595ca866928f571de93a89bbdc81d364e76878450387ef7cc2e1335896e5ad723df9cf000cfb8c7a77c5f8422a4d2fafb2c6530c6080086d66f6668718cc4d5a4c5a73e0149d7b5e7386ce47f62e2bf5c237c5a5250de90b9c82507a0c4c477242afba0b2bdf28427c878c3395f39944cc15a725b06be53485a4d431aa45c872fcdb124398dac9468d0c768d0f9630ee12ef775171da82ab2207094ae7a6ad722ad90ea59900f99b33c386da3b42948c5c0368f582da8f48fdffd0cd893b0a2bc10876a05ae1c347b247a3ef9bf560f6f9f2c8e53078273a61cc14cae89107722f7a628b3c36ffe797b4a85ab9ad5f6524fe7c0af90af47a51ddf0780ca3f702bdd059d1664935d40525bea217eaa9df695f657ac6356ad0e5ea0f33184aff721b9abca372b4e5453101e0717bc7aee2b8bc8c34679756b18d57ac3c40e45172e6634407e65b863cb004c88bec410bc3047690e6a43acd3a05d13d9fb2f9c072000909d2dc5f3c3ca3f60d8129f5e4ac92b1878a4bc3d8f2862e33ab067511720d20023df218f2f0572a25d779828a7e9ec6ed5dba90195623fea5d671e83f43bcb8137309355b25627317f9cadbc34e2f9b040c62215157cc346150d1f36c72da1dd9097ee5df9c59cb3886c30471df8e9f4f3c1e7020afc5cb316c0118f6720f5526ffe17ae69784e246b8467b926196843df0ceb9d4b21c5e9ea7d4d32072a19c1dac45bd0c2c72b50129670a78d59498d0511de51cc9eae16afd1cd8fd72579273dbb3af9cdfe69f53e9af19e56f012d83ecebbfdefd0ca182a18485d353ebf18deb2fe805787947b9b9f7f5c12d9c7714babfc19e2f3a40c8e9e0e80c72ec16d36dd00d96a5b9ba0e2d588c4276ea140bbe5e4f8439d3b052bb57544572c362cba0e6c7ea8af9cfbfe75c0784ea5aff38ca41b4515e40547593feb8a353516d95458829fb7b67d3ab176a56acc45ac8033983f14c7be1752a9ca2184372403dd9cf465f40369648e2164653b02d90a82bf0bc194f95080bb5563bee047204ea7bb5d38ef4087a75007f5e33297fd36c6cafe900c7174876078bcda07b727103458c47d40e9a479e8d53ebd7786cf8f051a7fb0e66416f4906c483637423ae4ae2ac6de39410f755839df5aecf02bca94ca8504b6ca459a4dbbef7dbb56259ff784c8e33d809c8bd88d4781211ec5eb31dac720c890a0336bb0368737472ae41511139dd4815672e2e949539756bfbeef2aa12cf9544d8aa3306fd8d8d1ce5275237f2d02c1a585e2a6b5e5d1d464fe4e9d70c0aebd312d978ad316adb723a0206a84785a36fff13b586246376494179ae1dad3225cd75dda8333985721b05a98c4ef718d8d32299f0a5a8086066b84f8c65d127cc517b6e9995857f02491ea2887746bb47cd68c00b49ae199b86a5ca18fdf7b09cf3ec3e6f6d81bff5723e43c848089a8def8b1f72d77200ba2b8c8148f1a7288209c74768a19b61162bda8c68c3c233d4a45746a3132350981a7a1eac08498de60daee1bb21f58b4672034e3e14ac25e25592d08f3cbad160033d1970c54e61697c3f5845875f09040ea6768cdc79228c9bd4730fb04d344803f0ba660465a701abe4eee3d70ffa060148db3b5178c6a5039bf49df60faab40d867fac67bfca412e63c6a6bf2ec26a68705996edd5b70702d20de394e729d56ae0cab7c9242e8263fdc4f4f2fe539f7239412ac830a631fef6e0a00e857a04b33190b1b1c362b63652ce4118d8852d728d2689024c18e4313d4b47fadc3184bcc90f5b3c08899e786269a01d7f6f0f72feb87a779f8ce75d49efe25163039c6aa472f392c9a74d36a0b49c5cc011a5721acb7468361843cad4439f99697919726251715086ff21edfe9483151e46e227a41fd973df1acd7842bb5347584acdd6c9455cecf6ad559f6b22ef6a40634b72f3a28fa38a59cd21b00d14cdf21edb24a8fa6807aebc706bf1e57ffd37ca9b10e6e753652e157ab35f820d0462d50031d6b93c7da8448964114e9f86e4210b1725338efbc7f6dcca8b4b26d015312724a0f612798f8c5ef6becb01382ea0f772e8af4cca122f70db78b75a83cc1a855d17dc26df9621f6a37459679d7e5e957209697667a23c7476c9039bba7c7a82c47cdcbf519e8c1a1274610ec4c0b6d9720ec8171d1b48a24f5ddac1a21a08be01921646a19bab5997071b286b71d90f715542181cccb997d890b03c58bfd5a9ea8d6144fdeb072630b174b9b7a62fba3789327afa920ed4b1e50e79f3fe0b07b7ece0ceb26cb6db3b31076275dc9d88019ba0147e7609e7d1360715ebe3ad14ee9722bc2e8a51d5ba71144e42ecca08728a304da3d06b14083970d243c09fea68f0819083c2377fc74dbf0eabfb57ec441f626699b9e8b22b4fd5f5978ec357a6a683d47544ce9b334a8fd930a84e5040bf218249c4fffc57cb49080686ea1ebd3d910e559cc36c7868a86b6611200854928ecb76002e7389d8949604f03066e96677716e3c087432fa1d776071f4ee0840864b6b1be27e190df91944da914ac07420a537eeb049ba0315fe36ab600772af8b7d0de104f6c1016d680d7d8b861391bc1f5a2677e9f5cbfa83b1fecdd940d32ff166182dc572ab93a351733091b59e46010f283fc434ea3eafea8cfa550e8543e5f7cffcc874a081fa9c234fcce1bfe967a36b86417c8afe2e62cb3e163c53fadffd6856af29e3d818c32e86337b3d681118411ca170502e4780e3fb30725f031ef5b0d9e282fb72594017b6463afcdaf85fd54f3e40ea63fa94377bd254efa38683696d78e32a6af61e116d2fd1b8378da9aacd1221377b1c164ec8a372f7e0c8f4141a7ef18d3f2e11bfbfadb6cfe59a25d84f9c87f9e2ac7d677df02d99e73b200f029c2a5a62873a3e5dee7efd7894e726e4676d3bad66519afc0372ea9a03bc140f3ac27ec5870cbb561193b3ed66a22d096094970b96ee0e8ca412e01bd08d05ce3b9cb569f17b269907eec3616600069cfbf3651e556fd3eda1722839c03e478f85360ff652e446f04e077f84eeac9c560ed206afe47e3780e6256a34ebba3ff59841ee219421018d8bde586f37c110337f3778f68536da78452f8c4c9bcbac44b2c6aa02c78a831f861ce35f88f1162d4db90a683f0cdd64e711d8b1b6e6e12dd66803c9b2d7055f0defed10ce57fc027d15f695c4dee14d9a72730c40f4448260f3f5472b7a74f22c1cc9a7a3e967cf1104e171011eb0bf2e1ba719607df3847ba38b6871633d0a70aca739e3c43c7ddcf9eecf8ecf29f14d2f3127f2e03867d5e97c0e6ce6dd451278cf75c4f08fd10256630c431c935a04675594772c63f0f48b7eda681fbb8cf496d292f33c591c6bfc64166d9a9b3173724fbe8c8f69da8dff00e43f3b4b8f907c71b5abbefd71a61613be78a23ac8485fb9a8b234d0b993472ecd3b7b1b3fba92d2a23f0ef5dca9ef9102990b9088ca2b75460d3b8afb473d0958571d75cbc59147a4921b5dcb50181bb7a9e76c6c2f2d1dc74d296fa0ace66ccc29fd6f5e02ef407b3e5e82704b70e8aa450ac76e09720db73efa1ec271656ce85bb53e5575b194dfb3f72c76ac7153fa4dd381118e7264510eb9a24f459b19136dd214234c1c116b675d9c7f6648ea08ccdea9834c72b81d48b9802d4ec9aa1ba67f5a54838082274fe101c26b00cc34859552c23372a643b945fd90fd1ef5a7afd41ff9b535c340e5e35ad5e48ccaf29d1147efb772f2c51b0a1c0f3768d6f9eae4e8eb4cf4bfbabc5b40ee090f39e8993c4356707225e3563e2c4cc5a45eec7f72ae616a25b4e5f13a6d5122aabe22ce121617b15ebc85c9dd6758fcf3d0f7b0cd768aedb500763b62dad551b233838d4bf3ec3272cf7e967c0e24fe9db0aeab43cce98cbf389d8f9ec1728888f02ce1aa398d2f5c51333ae27a2e6fe931af754151c429c0698d9c25c4fe1714df8a29292372f57247df18d7dda87a326f7d7058b6599c5b32d5bd96be1922b2e34edb56ed235972257ff9f032096edb830159e7c8db0617f36c4a92173787722a515e5f0c4a9a06d8128a7c03b80a1c0dd9e55c4361a282d6ce33aa18df030b28598fce4880cf72651ce4a6931a88e5b990f0b5ff16b08147a867660888b78beb5188ffe331b672d398f776f691697d941a85203322a45cdeb40e9cfdfb61540c0a939f70ee517263c4478a503b999fa4b08db75f0974ab51e4e03a3712603a52f7e804c590d7026fee81cf9928107914e3d7aed7aeccf229745d066927424edafe865f011ee87236562952501df63d36f8b8ec6654a034fcf4fa311850957501ada3e29505a31aff63388330bf4eb96bfb709acb70073ccff7c6854bb811a34a0da281dd58ec72c83ccf82f2dc335afa180fd438ae18d6b9942756fdf772698290f86e2495491a14e6ed12f227fc08ee5a1d3103adbfc649408ab1d219ab7d282cdf6aea9c72720aec3860fb1c1a16e428cec7608a42b2a3dc7a3f80b2bf91fb0e39c2f5b56d72b9f1b7487d3ea9a428d6cb8561b01db55234b38c1a64d37546ab27d1403421722b2594588c743628d3aa6bcd428e66b887bc46f75c3061aea0a019953d44af32566e43255e0cd6b4c752e87c8e4cc6da740a3cf0d92e146a85579c28faa01b11d0a4c005b42744318e49075c24c22e3f5c6b6677c30f03bd1bf1b92a00ac447232931c16e48a58555568d2f8ab4cce3b27701839a3fe896b1567147194885572c258d8eebc61ebd10284206178071a2ff3e2bfcd96f017dc45ba1f48f82e38709c72aaa10f2a3126188f1cfabe6c2871376e21c46aa5026dc94463054733db728fc2382c70b9b7e8e3db8651e93d766862d261a9437aa66aebf7762cc6f7a35a1af67099f8bdc61f6fdfcc8c61ded5009331f12e63b6b6f1175e3c1d7b143a727b44510c4591316063da3b715fb716d6cbd59c16a5ead6894ea5419130b22572d29a51570ee43fa110ffe885105816a39dde5efb8294fa1b9c70998348fbb52552d37794ce9baff578672a48e7d2413e129e58544a665d4cc789c587aa79457280dcba6c0379fc95527ef47bbc789d8292b9c7aa2591ce9bb27fb00c8d3d5e7242812b22f3c142fdd5f730e157c8e5f593d83c85b8bb4132f1c9c5eb5a5a6a728e38293bc94d1d827e44ec073e90d59f6b40f3c06fc1c7ed9eb897a22e1936720165f1f2f477ea0c8e8a1176dd3ae9dd3a32514027b7f8151d447cc850619a72a30e38c997038523a4a85ea7dc7077e85cd09a3e3523c41ec9d6c5fcfd8cea52073e292145946f1876457ddce61658e4e95ec82c65fc384ea89c2143b046a15cba85c3dd36e57554b2ef1e51d92bd8073bb85a6540e937fdcb2f5069bffd0710d961ee49fd82df8a9e0c1bf3755b11ec65c16e28a4d4ec76e08bb61298c43d32975b937404a27f594bcd82ea683a01299c56682016866d1132182a49c703937259b461a5e7d4f9bb602e8b4226a8b2877b840e0ebf2527985a52170752dd7d142eb79c6a4081c4fec31b9607fdb362bfa23f73d951cc346c8321a2d420c938728ed161bbfd6e75e4c82076fe5846063dc355bd747830dd961e2c3728939cd07237303096a91b97023ccbd933f52740e4139d2faea94ebcfae83feefb4265b30df048fb6a4fd29013700829d2f28b75a0ba5bf55053783d2c12d70f9eaaa5ef7232028e3d90ebfb2951ec2f486e232fc3bada2dd4247b45d427c28a028f76900ed8636adf1e87304ebfc0c33674d5d2c1ce582965080e137c7a57725016105e7266c39dc6c8c57333a4a819605f9255d15f356cac835788b010d5320b598ee472e96d0a3d8d4d01ecb6f6e774b729ea0913e422d62e9c63a48c4560592ba4f25b4508434cd0fca206dcc20a2774d3e417baff1bdf25289d14e6c44ebd0295d1729d47fd6db14163e1c115381c27364a9e9f75525edda6b49349799de732eb15728863057e30a137a7b7310373812add17760ab23624e1742f619fb2cd13b44805036b97c1afd8ddb0a262de8c846c564d0577d881c193310167b568ffdbbc4d727d88c1f8e307683c61c0dea1947ce0fa0df433e306429a10b972fcc96c2d5300ccf045dc8154c2978dad1bc9cdad8a116a8b94d6ee11b2b2f491dd88adb3647266b35ab002b54df87d1723cb70782570aafb82df38f45c0252de4389d7c4d572b2ae0b6f3aad721f6a3fae7ebd922a67576478dd743011fe23362b9878531819f7d344a6ea22752c86434f02fa5b6edf8d729089cf9d962b0d37f998a02a81720a775d4c17c7d355b12517d396039c6cc056b630021fd2161ee12fea9e3de63cf19dfeb3c95419d79670c97beb87ca4a1d040efd32f5d8700c180cd053557b722dc235605f9fc2bfe18cc90b510398a0df47711c7d296d0a382c46dc836633723a19a0ccd1e686e8d4f479cb172b74b4d71daa8c5aa60574b67fd89ce077fa7208e43ffbee495fddd1b27e37f71612517a1efc8c30a832dd989ce25ab3f40072cc87bae80fdcbcc22a7f8dde1315379ad0695ce4b94f48b2ebba36be5b587857b3885ff3551252c8f994ffb33467e1b73181874941f16b8bd830cdd2970fb768ff7b8e2b3fc92c1023528e5f020ab0959961eda5c580ad1c3c1632ed57efe2721425d49ae9461f4395f1dc642a1dd9902a132a0befac1ec7a091331fda087572921bcd66b059d020e32181085dd86e29ce3829454eb1028055768a2014dbba4745d00b261cfaa0bd2ce11853d96df73b98800da9a909836409dfd19af5039d72626f247e31d933b524d5de62c1ef28af8f0f92b28ecbae68c5340e686f06a372ed1b4d9c294d9739db49a92cca8cf6b239ef5c9e24f4d0109a7e74ad2e244c72abe46c6f6254f6f6c3666f2030a811597db9e46e38c81308952952b7fcb7c33f2e75b7d5383562842b416d77b05622bd3371ddec92f32ce17984a6e7b39feb5be8222cf7a1853e9b73d0d0e0afe47c6a8448bfeaa1968dc90da25bd82342fa2158797475fcadf7c411235c6cb94625f4f5f83d1c4b5c3b90a82673fb6fd0a272d9975edfc4c61daa1adc9887b2e1e83a86f69be17ba364025ae2cf50b74aa464f6ae5ebaa9554488dc0239994113cd62342e19cedf5042db382eb3a295b24772d5a9f6386f42392f05a0fab79ce1f690df078a3b500f95c060c70218a168ce512f30315039bcff65af718c7d58484b44be35378ea42f78fecd84d81e86b54d72db580df74e68d325abf1f26d55900e2892644cdc82f7a620806fa77475c9e772a3622b3b8c553efb5dd5e025a775662a3816382556c3254fca06b916060e3b6b5aa5a4fcf5708e0c1781e513700f8e62afb3e5bd3c5b358c91aca84825a8b872b9fb1f74f28dca2ba488d2abf7f9dbe455fc9072c315e317220f5b577204447271e29a82a00880e919c9f0d9c6400baf2dd6b59e99b67d71390b8b4d41c4c9727f6589214e361a791a6e84634121125589c3665feeb75356d63cff8e9d62e631e6282fc87d925a2fdf46c7288c1bf429f15fca4c8c9c6f11533eff6063312d727e29e29060e4623a117b60eb9859717e8eecfc81ceeb4cad39ab0a5671d5cb725075bec83774326c72a72b5768853c91445c22af54ad4f0977d8909efc25cb72ea533195fec755aeca1e7bb6203ce6eded5ed81a45d1a92fbc35b7cd95307b3a4bf3c171ece88390c6809d45ffb21d3b5c1b1b76a6b25cfafd30109d10cce64e768d3d1488dedaa0d6028a080e901d1ef0dbef4c976a092ec7efb916cf1cd62fb3cb1cb7893529d4db72585ee340282839456d19d11cd90885a90757f585f123b212751484b0621626c4e98e7d741cafc563305cae3df2adde1cc721233eb040822c10855bb71d4c3fd6bf1f1c23163b56062250121d77c3c58857506f9ec67204eb14fe7474afedb08e0248ccb2cc2115658488e41c9fab430a2b56756b197265468ce7cf386268d0cca5e4827f4512e1ce5631436267a2dab69cf71bf3a96f7ce6084e6b912f34007d2df596f45eccfde5854d21eb7d760e8fcf57aec52972b4b3062b58ae0a64b5050efaeb24bf7b1bedea58d1cf217ceb77b5a3944ef772414b029b4ea8362dd8307a248ac01b713ff82ddc4f1976db90c63148774ad302d86e94663e6e250e1c5fd5afdfedebc3d111de9e24e411d8964a68e1bd029a377a909e4b655c25720dd468b420686588a745ff9cc6ac2d7a193c77b94cb8ea728b1b6747f7ccb916292f003ad3c2de5612761c4dddbf31e887d2a45dc1632e1a1ffb322b1ed587cca320133f2df714a45857ecd26e951ca9f7135f5458df9907f73b95aa9eeef260f1f03844809c9d96c377432702de2b927fb6a71fbc5e5672fb2b7637e041965ccbcec304127fa22928588ba2ce05ed57094e797bdebaf572f0f6785e3503726d0e5ca48c4f21443c8b24ac72a46a8637363917d0fed0f072b6a2b1e5b00ad0c47de57d7d398899a8fa7d118146e8db90116535045910300a15c378988295aea4b204c7d3d638ffa7fa799dd5bc16bfff0128cb9292c6f75838a673c7e21d0889b3740ca4e1d4f02983a8caf675e927fe7c8d8aa01a216e725f3df17ee38baf6b1d41ec0c669cccf6ccbbbeb991a1dbd50edf427a92e0f06166ff00cdc59916eefbb161c66502d7942d773b931268370ff870909ebab8fe72fc59272769c03d3ccb411dc49d886353979a4a2915761d7d4643bb3bf227ba7220df95522d7bc206068ca3c05936fd78d35c732244de3d774c4a11acc81dd00f9dcc32b91597adfe868c8507b232d687d7e9871d781635b63120dfeef824a1263d7341fd9b630739ef3f9b9818521db13506bdbd490f55e6053d77c7c01b6372191f5d9ca47fbce33e217655c66af0314478d3e211399f229e79dbd0896fd672ea51ba094fca9211dc8be05629b0b72ef3df84bf2ecd46c6396e4bfda36d2072e6fe812cd33d79b8c8e7b76df41e5d35fcd4cf9b16f5b332a30e3d17ab229172f4ff53e852ff547c9f0b410a587f13a4983ae9c81a3dff2d94a34bf4ca3e5072e4d4aff0e5604d398c3f28391197b26a043b3e880c32cb6a22c3d4d9278c6472e93297c05cadd7822b56cab2e4a3574446e5d9958eb323fd7158a844c8c2a53b12311473c736f3e8f8fcbecb42673d61516f9dc4b8122b6d9f9565ee6ef70b467fe1df58e8c2b2f970b8e2c59c583d27682f7d501412b88f15d8877c25bbd4373e51bf477b92efa538cdbf66cac44c68e19de4171bf693a5be753bb5255bf471f0fb1d6678b579e8b8e7643a66e861758fa44d39f868871d700f3d39c139b36f473f999758a5809a707c05743e1c07beed63f8b822fc3cc69c29851e48334972ef16b6e861a7507c5dc982041c096d2e8da5ec591911798ab59385e837cf3a3102e2bdf22c45d24a440b0e4ff662aba534edea11ea22c50a616f4e770782884197149eb40827659e5b127b8f8afb784d202b44561e31ec299b677fbcb9804f723e155d732d0aff11c900a04787303074f52d0293a14307b0fa47224c87654b353c2559e38a2e80cfb3c615e733cccc05a04953aa17cac0f863d8469f4fe490721edbb3303712a6fb014ca58f05fd024be3cac97aa3f2d8928d507f3b44a31c40c5e142d8072f989124989d2571b5bc8d2ce50053fccc64979959f1d0b8d6c172e7ce99cfa503f90c74d4ebc10e48f1d846ea7740283e87f0804ee24b31ca061f4bfd1d1b358c920d71bbf16b256dc769f7ce3e654aef3f42e4c1f0396de4617273a32e953367a5186e2662fa4bd1cd02e425631834a8b52079a4acfa7f0ac706f7f4d9d401e75f59f45bd0993b147b2cf156a6968065bb7e25f982e14f5a4c4ff537e78743c700eadb0abcdc79c15ca224b30d96556d1767d8fe08ad9609a461780d134458b84ca3634ac4b5b2a27189b00b36a657e422f3ac80f5cf145a6b1f5ebb01f9f7a61f5e8129e53121fbe76752efcf912d3b108bc09ff493e7133f06e6edeb25ca0145367facd8bd9efd37008c4efd5a740adf847141404e17d06049b7c71f83c1e20e6c45f05a1e253226b052f59b65761f5b12d7ed31960a441128803b498e1feeeac989bca25d904acf5747ba33c414eca33504cf058bd4ab0808892079ed330d3c03fe9ee5ce34e922ebda81fe9b4af5e1f3eb98518c8d7ed725c993f791b5cb841622c509559494bf0dec2ca09b16b6fb4095dbf11d4e2c105b73bfc9a3b1e01e89a8f2b430b6089975c50872d176ecba80bad2dcf643f06346a4aa2779bb63b524993dee7d8869d421feba0ffd720a8d056a97c04da194dc727972104aaee283abf31a622ba136bab009fc2ff20ade2147ad1f83d2a631c622219af5bcf571eb18e0c39d69299111910411aaefaf6bef906d34f3a33d06c14949eb834c5689c5e4c53d835f8a669ad1be8010084c53f8d78ea94e0441e03d06b4d3971f628dfb94e9ca2fb0ff7a7e2e27cedd7567238d29fb30187c0276ce725980bd5c27dd3a22ceed5659f5516d2497d387713e997cfc6aa8d699a7d259725e9a97a667cc2fe8e15f9bc1ecf807edf191f6d0d5dd48ce2980ea1cee5cfd070294f1a979f94643e55b28f56bd9aba54bdf4c7b19efeb19f435239ac9386b5a462ba78b82171e7395e6c5f5ed67871369cd65aa57f685c12fa5c37a5ff7d669128b88ac4806b57686ca8fcefbb493a1751fee7966cf4ac773ff548db66d6b1d26d91d7c01e9ccb611721b8958dc04f358ea4f0c4e4bb4bc7d87aa8e66f29a0af9a53d64d455a0ecfb2fd94339d4ac09c642c6b4b9fbf4853d968d14e9273352497af74221ae0564a6a3cea8e1bf1d0d62bae082c1ecd4ae648c16db6c10b8490715a9b6a0deaf20f646f5006ca6c1b185512a8a094bc94ffd454f3593e677724ce3c8c776e34c9b87f05dacbc7eb8f5bcf807fe538d4a915b90fa52c4608072920ab7c6cef3bc07a7a839210abe9e1232dac6d9789b0bf6ff8a990236acf272c8cba638bec3f82dd10efc9dc3767744e0f7d45e7996c9712debabd05f14dd72ea8bbe6fd7885e0794076be216b96b202fd596056e5a2ecd47aacb92ae20cf721ecdf103d7e52de4c079b5e1e82818574371bbc1087aafac07a80e1584948b72a7ca2182235d73db4cc34918416831eaa1c1e4006286384b028cd94139f8427279084d136206cf72b9d09335ff0bb4bfa3d6e4617c394732161d7e5594093372621a177b3775632d81a0b41f3d5cce7e91861e83159e53179f060522008ed772e0d0095d6b916246374d9cfe777259a6d3d72522354f70be9ad402dda47e6f72660ef9f8bd79dee64d7de9d69220f7faf984aa0fa3cb8a6164af27f2920c642050cf5ff2c38a7bcc69e9571b1b484d23b074198a303a9b1b31e0430af15b2e721c58eb91cbd23439429699947bbe2fa60a9eba1d6fb87379e0024bffbfd7a20d19ac51fe1062d7befc1cbff2cc5eabc3bcc25b934c19aebda2341e3a1c1fd87278889b41351144b0c04785cff19797330a0a9cbb510f7514d626c2516efba13fd140345bfcb146204908716761db4c4e4d329cd7abee9509cdbbaf6ed5435c72346c46ba0f60cbaafbd07ddc38068c485e68d8aee36e3b671c3ba4570cb4e072b61dff4deab90d94ec6d7dfb4b7004645e5d1b0b6288fdf0d1289bc5f8d6d321e228ebba69a42cc78229594e2645d8ece717b228c70b11f593e641b3d09cf0728c54e582ed1d25a6cdb22b60070787610ba4b989264d12b81d43434f1eb3d372e3d7e994702dfe6ef038afbd6c154e3e26ed167e3f447e24c4e9f437d541cb63592663156265dc5eff3c31967ffc7f441ccf702cc9df9f2942ad46920642a510f73828c1c6413ae63f7becef7d54bef1f1eb324bdaca3005022d78de4bff7b72375ab45a5e1a052f8a90c3bc5e629f42983c9d73e13bc0f97799440d10dbd60b39b5a60124d82a951a4107cb0f8c0d5ff19417641c238d7c4f5f269c35c4df2c6d4c2703ef5b2a25c351f485386f798867da0621e8d19d03e75c9132f00e955820ad14c58e7ac4d2417a13a57113e0bc1ac26d518f25ff00231b178d4699f472aa182f226f92386c7979ebcb3d3884a176c49c3a84494e04b996e82a19212e48766f5f15a2c9f072408180dde95074b03e46a18fdfabf1a0f1dda8eea7900a72740b74f944e047e80ca2488e74eb2e476cad98a345ab4b1cbbd6bee7579d3d6103a57d8960b69502d04cd0fb07db8e4dc414ed30fcb4c823e68b2a79830234136daa07bcb76789e2c88690289a858ef6a72c918a35d652c3c7df317fe71feb72a36de54d432234925408f3890eadc37957da5f4fabd64d5067047e1edc7525202eb77adfd3610152aafa7650b10deab7f7291ff51fb01a3583401380b1523572a474f91d28f2fcb13c9765bc1b6b38587f59f206066ffa932ccae851b68e0d366183d42b856a6e04fed8c6c6ccd1680370c13b25c662a951929cf9307632af7204fa7d0b83dece4ffe8e20ec9aabed70636eb679e354ffeca0bcf38e93cfcc72eb5da86a7c802809337b4724ad775554d0d7630903360553428f1bec6089f737a5491680b17f4b8a1e4069f0955c972c61e4fb65d553739b37286273731e381b684b6d4feab42282b63abf01c506e473594705abc1e320792afb4439b5f5be72c5e1da74810e5700a9a0633534273fa54da11ebfc29dc7c18b7539d9d5bc163ae9a8195974f19c349c922001fb4000dcc1eca4c1c6479d223c20815207148b0d653f5b54390b63b39c908f56434cf5d9a78eb93a7079a266fedf3c8a31198d503c84510cd67beeedbf7fcf6410c8adfb9485523bc11f14ae52eb1f58e3cfac7209eba9fcde110f453bd517a0e36fa8f429d99c3f96d90519976d4889ee043572e9f0e3a1f1f4f67e8d17306d0ea6f3a84906f1666c2b337e02b4796e00d87072efeee15f08ceb83703ecd9e1be7fa8dbe8051d8a6e41deffc63be4808618930075035e2842ee94a7f4dc2d53d55141b929230f2cd9dcb4d267ba8bb85a2c5a2ffea9038d9992f2cf046b22e87b8720b79bb49c8b35e6e2401f2f9b9b4a0d98722dfd6b79cf5784f0fcc8063b81685974194017f80f1ce2966b4823854210e642e7592e6f0db5b18a2aa49ec62aee1972d7adb36f7f2da374b8c2ef23e5eedd49f439e1412865e41db28d714991b71290936aee62c4396a4a338e0a2341f9ac6791437abe800304d8749fc09f815a4f2fadaec116d46812734db76f01f0f7ab1b84b989f64a824602c25490a8cbe0e64d26ee1e622c755857d6da9985f07a6c72ad6b54276ef139bd279433b4e5c95edf7ef5e5fd7b003855e909adf6ddb8df72d17b0214c91be52d41f6e0031a42dccad911876de4568cbcecada429224ecc726a04ac1203a7b068c96fb36e7b5ec442ad1f0103521e47a0100cc6101b28767257195afa31d0a5a7d4b44278afb753b5a0f4d9af1c5c0b3efcc1fcbdd1237d720c2e2168eaaba894848cf367a9aeec0cb3b634d6f5c01d1d901855417aca4b72fe04fc17d0d03f2f812931d5d206a22894a714364ae2640eb429fcc4f0bc997226c266fd0cb06910bae167cc4a377af663118fa34e25fdeaf8fd9fa3df0bdf728991889b257b69ee27954359bc2ee92649ec0a724571a6ced76c89e10d78f9725c6ebad2dab8247701e3e682b2082fc1ac4f6c10fc2308117ae53681a6583372122289e8cf4313595b1ab728c054bd52b70736e6c8b57065e42e347af5b4ca72c6df66bce9cc7b1c606e37c6d78467f69323ceea4acb1bd11a8d144cc0da4b2ebe5ab1363b34d07de5b19544c40e3a801b2b3db479c156ce8bde446b26f5b4726a8a568474b2e8cb0ce276e7b30eee1b29e9cbd1845f1307666bd53aae2262724671c2ff3df97c97f2a16835bb522757311cdb2600b7855be799c6c68ab7e572c76ddd5207664a60b8e778c21ad06b9d2f7d06b2980f908b47bb63bfb66e68726bd6ba0f1094f0c53a973e9fad64f37400cc98817bcec792f1e3197aaa815772cd92a75c43438ededc62cd2a988e6c9c9c819ac3acec77466a6b0f8e7342e43037cda4606a4e9d7397189d2284627a49e5380fe50f5661fc5b736aa36d36197245131bf5d0272f60d67c7c274978a0f3ffde689e51a3be9e863053c5a657d005f1af4add6f1c6ee6227e3602802b879aeabf61f4a6aadd65dff98617d9c0ed5e44b5c680461340502da5250cc6eb84ea95c639ecd9b083ef4ddaf06af390816499c6b18ef76bf863403359d6680ed80c0df84b808b4f3a04518877bce217dd0ad393379a64b5d88f45b952cb2617cf47fdd7ffe9f60934420fd1b63458be5f300ba03f1c4bdea2559af1796b07db1fbb8e1cc63ba8b843cd8bc7187da06e65594b465f3455b19d539b72caf111266844a000815440d97a43965fa876e09eff72c013ecc469e41cb15bdfb1823c4ec20c8416062e1e838e7c61739c4351fac572ed9e8558f442fc074870647c9f5762bd1572058dad5afe5926d3ea2a1494650edb8022593741609330fda98043d6f99aea4707fd8435de05be79c2ee7ca76610bb8cf0d26ad888e2ad2a6bf9a8cf81218ad16b72b7dc55918c02b34ac7d8f772fa7ba8f92f1bc32432ecea71e55902c8e9648d2150c2663b25fbc472215ab31c542c864c237f58582253f535918beaf83a8cdaa1e64f011f8d7d69a7b64047727373484494a5027b496a8412d07bc4a5472cdd83559c88f7166773cc83bbbb5d0d4c4b53b9a56fbad74acf527b179cd891b99b8e983acb98de537b9ab7250772100af487b9af377cfcd29704dbe5cb359af34a6f5d9dc0f458ddd3957b093b72b23dec3cbd6fdc134501b740007963624456c2cbebf0244a865c9861cd582e72fa86e5ca01e82233f9418fcc071dbe573e43a4ec647b513fe9850d9474985072c77118e54e9a80f7534677dfbc37382a6c0273b9045f10dab8bec001bc38d1721122db638c7303f938398ed7410c52ea2c72838d2aa500f49a9f21b3cbfa1913f45213d8eb97549fb6e6d04c35271124f479afc41855d5f64e3cb55b65ae5c72bb64496cc6565b7084446ea2ba3a7d19252ea9b6aff5075b44445788dcfcf97282e05bdb78601d289bc420fae1ea5357c618be93d07456fa8cbc6687e2b3cf3da224afc6db6e4854af984b5287a83e05a40c993976e8013fd2a8f3657afd7172e1adef6ffe7b58420a42f93afcefd056b7f8499b2bc105c22a37fad158318772e8f8904f34c7bdadb19b3caf3167b0ce3a76d78e9fc9963fb1001ef0a35f6072e85ced47f793cda5c2511d92e2ec684d7ca5c5b792b9ccfebc961f76adc23572b8dbfb4b570a5c88b78545ebc95245d4657be128c5b89ee79b56144470964072d3a9e67eac2226ac4212edae8a1bbff3662a719f121c38586522553508b43529c956833cd1bb8186afd66b3db255a7c629968d55b6b82c70ae59565dafceb872af5f3fc503d6f9c53a17dfaa2fe25c01484eeac8e4be4963c88f782f66f555075eb84d99df8e1741eceafdf294f5f1e95d20a86e39765ea50295339964cbdb507e18d32cf3d54467370c3d0c15b086898323a32a331d416b05c2ed3208d0e509e8653f9bedba9573f4964f8f661ed63d7653b6d058362ef23bab6fcc94cca172623b0929876f0f82fdf5a778e85e032c4f604aeaedc9c9b5ba9f85269c957672ded38f6d3deae239fdfaf29d041958dbbec23213c1606940089aca4ce6c39172e8c2e29a5eb483056abf9665ea8cfc0c715eae14bd266f37febd06ffab2616554a3bda80d8481ae467c1059a0b10f25cc35daa7a60862d9b369136b5d1f4a33ce4b2090640e049bcf130318d0fa599a8bb7881844208f3790eb449353233f07213f47b88cc18979a0c0645a9d212a7f23a947dc6ed0495b3c6bb9c5036b63e7201bfd15157c9b49618922dd0c51f4f19c8d55e629727f0bdfaf80479ed83d572764dec5eb230bf8e31403866a197f64b0b7898c764b956e0321bdab8c83a58728c3a2e663058abd55e88ea048d6adadc310428567a2d257c8fcc6550c0ef416a7097c71a6b5ba82ab7064df06b6ffbd4290f6fa16ad14b2de793f98df3d25672510b6166b821acaf01ff1a9e06e674295b75ccc995b6b9d3c019bf7d5453a472ed3eab38027708257ca0bbfd00feb51b379ae9012bb59415056353ea24123172c8bcf58a6ef8a2978777957edae7ed90c28e447b1e32bcda1605ca6e4a3f65722026bf75ad4d106d327d16bc3beabef79bc63ba94bbe9b7a6bf6367599098b72ab8c725bcba60d1895eb3a8e737fec3d395954bc6047e53aed542fb46741e472c8982d158f4ab77ba928f4999b279a5d0a99226fe8a78540fe2e05c82ab1bf723218fc125a55915083cd8bf43b5c3289a62e00d75ccb9f06f9ee22e67c2fec6fce677eeee3658883400fba3fb207419f1a09e85d1baa345a71cffa8eebc29672da7ed803b4950b53f06bed18fd84e2f3fab3e2786004bbceaa4ce5ea379e7864123a25533e6b646fe1073f2f98f556cf17c8a2a6383ec6cf6d3daae0cf8da87225997af5193d4536fd2edbe4c9d5b733e5115c9847c496b2286fc22b37ee01235c80f7d48ee0288d5d9d9555faba76290e2324a0e603954a4d4daed766e60572e4b86cd24a6da8c874a0f802b36ff87316f0eb85e36619a294ff2feffa8dc172c7691ad6a25bfbd0fadc16604243fd0c12c0224f062faffd17b16b9ac7958a72c332ba18be2d17cee026503a97967d6d5e77d835945a1eb66b78def23fcf2672d0cb8c2e5d99f237949bfa0cafab7b084452e3f95032b6d7ce9c5699c18ea04324cc15af485d33724fe9da0aeb2c4eeac35584d15d9bffdee059baaeff7a151abc269f9f9654749ece7d010284782642f00846d69ab52e12d1d2715a4095f85071935e8f0a34662ab8a98d330a27029161996e7d8f52fdcf41a710e0fc183272e39487cb85e83358a2433ab12c89af97c3c63a3bc8e8c04191e778210726ac6f0e47c27c10275b56dd2e1ae0ba4ac34c4c3e707abdd869e297334e5cac6205592ef608a9f012065975d2843cf66f5382a4fa8728d69071449c69a77931b6093247371afd42d35d23ca583bef71de1fb52ec3521b1f190835b2af3704e8fa6972815e29cb8a7ab8407f385eea6ec5fe398693b1c7e57788ed0fcf669f47f05072c0e7f5e3c8de4f8a67461389e86dfef8bbbe81b2fc6a1c7fa4d405d082265d62920433729c23c30e447cbe4dd5820592c1f3a6888a15eaaef03cc4e6268f275f080641cbb5f7a5061011753c578db3a506c1c9eae32ba9751e13cf0870874472e9f6e06ef07e5126cd982e4b08dc7da54873675b4d554f191a3beb9512478672ab9a6f325bd063445ae9a1720ece04d982efe833c1a72160c73bc195bb316772df95734036d6fa3f9f062a5413bec213081ea6a2ebf88fc64272a45e4cf7a5722fb573b957997ecd3cdff3bec86051b2b0f97fdf277128c531da8d739dcad56887864ae06eaec292de1bf7ce142e849e0d5a4759f3d89efaaefd7cabfbb2e6118856738d74a73c816eb9b285f6515d34cd04c027714095676122b0cf45baa872878182dac4dee57d1eed2b795ab586c69c70c2e7e2c494404b216b7f9ddc17723342803f922cbee2a352608ee5106acb3219563d038d9b70525582022cc79772ff95a31c99145202719ee3ccaa822fe5d49622860f609617e3d4066e7454bc7249b1f21bda297d6e0562e4554d230bf78834d9e245a19dd0cb53d5c4065d7772042dc976323e00ec5e6e7a7c4a495ede2f7856b2e584007f0b7c2f84dbb80f72b7465e1829722701be456bc5d270d1b3ab31413be9d2ecc1abe927c7a781d840980ffdd9dfb95474f2d11cd6e6fcee6fc86d1bd4f2b5fa744148b4e67c56e0120173b1fa4edaabdd894c5be03ab56ea2375a9fe668356a6dbbf3a6b8f9acea721e0bc2ad1cfc0efae09ce7fa7804470deb42f397bdcf5369ebe41ca34eeadd72540c9a66495cbe84a0bfd0c6a8b3c215ece38e8448c37ebfbc8db6b750e4227274cc5a40490a4c434a60e650dd9ceccf0190f89456cfe34db94464309bf64872bdfea6284e9d82fe29de25dc67fbe326fe91a082e8e3242fb973f24c2d823232e4f52a62385f1fbf42f9905b3bfb1bdd1171d73b4a35f3f5b0416d5a28300a4466899e3a3caba5722514388fbe9cb74a6e1166611b3ffcc3ab55b088b588e5411389cf4fb39348e41ce29530a7cd051acfb1b60b7a7a0f8f1172259125822d723ff95d074140934fe8b560e1fee8cfd2c82f6174e52d341008f3515ad0aef3725b727a0aa764ad73dd6283a335b78da28610ac9d582766e7b1d54381782bc872ddf31a4e9a0b18ad2262805483f156b1bf8c431874ae969b196b231b0ae87e72f7166a6812905ca7c4a085a226018fee75f3fc112186be56dda60fc5fe3d1b3bbfef7d0895d8c76435766bd7d8c0f0f4094ad269c98d545231675ca60c05d3723c557af814db68901e7288b47cd42064cdb1237a4decb8a2391f9ee9437ff94aff062e3617296cbe3b8fdbbf5998ded5f4875fbd64c1d8e5f8602f10437c7472f900b2da7cf0033b1630e11e49944296bf12be1d10d7b77fb136c9b5c0a23b7215add768a2279fdc4a6e27cb5d407e2dad65d508465ba704f38dfb4535529e7233bc091943e0e9592cd4b95171bcc4588cabbe9d0139cbedd9277c79e600c372c773a5fa73b50c3bb6671fd62b20f97b6a81e98468b2427c22fdb1da338b4557a5360cd953641a661f2de900be0da9adba5596944e0ea1b2f7099f687c947d54700ff2de98bd90cd3da1575187c4b8a75dec82514cdc7316a5ab198583d40b72a946bcfc12dd22c731f3a4dacde08be20f3faa1192c3e6a2f1f0594c6e47f9721cd9a3c0c1f9d5f5aa5f2fe47832729631ad10b2b02027eef38c1343bdc59e0a48f095924f11376681a5f590d9bfb82b3ba45b901a4a65dcc0056d2c8fc6212e8a7769a494457a7b37774a7ad145ebf064e831a99f358a5c85dd35322f7ef97237600b07ea145561131dab6804b34d0549f96829d87ffd4b6c304a9a79ad1d70b6a9e583f49089fbccc0db1c9232affaeea53c13d7188943b91271d8f7db127285d7283fa2fdf1d61c0e39903837ae5944893808b52dc89b38b4c908b331796ac20a52504a7fb3d77d602c05452dc78fd5d008bacf07b19f6be2fb73d6f7f256e76028ec7a9e521517e435303bb05b4c4bfd5954344c0034a6d5009d6092b74327c27f4b58baa8b29341a347469c96fcd44a8c85327bbc500f72c47f9d873a729ad77ba32d411abb13497257ff397ee918bbdef818a696f71eb76410c2835c677ec5ba891285abe74d40ee3f4e2799c301ac81e823e90b61c0292fc2895f6c30d8e920ca71d6f49dfc88b715138a2fab0a698218ccb4971b6828b0c343064272a1c5c5c75dc36fb4a61ea230ec8c7838ece315146c18aa8c4fc8413bf0ae192fe006de603537179d27ea00c450a040521f42f2233d7c934d1c96d976493ec67214dfaca34ff778b3376fa67f1483640f1d94be7aefee687b47d5a5b4ad1a9b72fc28d52c8e30ab813de8ec5149318299f1be3747fc088eee76414e7bc8add416f037535e9590f24a416e8ad9932adddc32a8670952cf0a1b6b5731df9f65d172f79401f7d29e15b18c79f2623ca76bd2b0dfbd025d15fe25fcd92b7f54b01053b6c8b0dabb7d1cdbdb948fc1bf6d45df187624901c85230dff6716a7595592722e165eb180d20019882548a65b74b6cb9e1f12a4c52fcdc5bbb241b83ff3ed298fbd8f9d606c1f7edf4570b6a552763ae67beec1366fed1ed35bf9fec3baad721e0f35076784828c4bad2bd72f9c25bae95a73afb9cae7781d0eeeadd2bacf208e1b7b39eae8df3d772e7ea78b52f9b61a9b38d068ad731c0f3f7092e26cd15cd85f85b5ee146407fea9c23ef97a40077ae2c832d4cb2ed310d9004272acb7727e7749471cbe066280496d3c217a80da430b98c0f18a78997fc6c61968b55872b0cf1c347c36cdbdd01d73d71293c94977153b8f99971db1a91c7c18023e3c7235d917b7cf69e2e30548ef5117e0cc34d0b056798bf54803b4b8492144034072437c93adc9a67f11917f0962a96c470822bc4e76fa3c62bd0de5e1e04fefaf4da4a8be8e12aaba6f6bd8169523096acae10176f495f8ae4916ad13a66cdc87726d0c9708e1b4389fb6ae3472edce8418d6245885a756e7e59991d8b480fb76214c13f389354e92e540915abbf4b0399fbf13e41cc2327be563997121580ade2b8c2cf1f5818d22557c875bf412fd3a56a27ea384908fe74364aaa55d69517c1678d35c6391eaa9a11a40212508e19469fe2057fd82f0812ac162684c81f4ff6d9f662fd58c93e28479c1bdb6dcf346f3d5e6fa758c43f65e3aacc4a95ecad372ee720add5bb7f221faae8de0b2e18ca76d06bc3ab893952c2cb925ea4756c672d229a476451c4c150db5c04d2e5c634a68df714fbd6c13aea7401996618f825ce2eba3f09574705a6d966800338bbcd2e4ef60e5f3839438fe0d3d24b8a2605a9a132d2a7341a19fdb4f21d34147f7a59b09c320142dec4ead182cae5edde90bcbeb5cfd817f9c88bdc83240bab08ab8a52472d148810c703d771d497439f331f4a3ba4c930df95eafd2ba1c81485936368be55aa9c511d6bda49f01811e7604926c11e71882efa3d67398789b2abb50bd64c83285cd6b01d4f427fe8d6b076652a5c094a444d405a809ebdc8ef007d546b184d134fcb67f7b49b9986555680303c36530b12e4bc7659cebb7fd52825b4a02fd31018ba331dc8fee296ab6f972b57fa47242e7b687c425d0b920f40c087a9d8c30246ed84f84c9f6711920b025c33b26f039df7a3863a508602c6645210f093cb2a4f7d0bcd606362797cc6b72735ff18974e7a45bc4cff6068fa86efafb068f68248027fc684cd1d3683fce03370a2de1884f723dcaf30f9889189639d1c7ffaf6058a15f81c8afdf74f52d147529eaaff55173f352cbe94130c0c4499c6f0f0f45eeca89f7d42c6b6e121c0198bc15aa752ed906073a6fafaab64728a8c4645d8c65f08af3e47f81eb67f97232d83f0999393f272a72dc9faa26e795237d64f7e397a1ee8b83bf7fa7ca292c8a937939ae3144366fbb2c8e16de2f0e2423378fa76ca3b80d6e8eecf46b0a7282399993769aa8dbfda0d2c1cd4574877c573038b402c4baa781fc49f63a037223cf909f90cb3f12ba17a182a3ed29189c914bb73456d3b2ada58a0cba9a011b60dce3a8e57edd61c15550171214e02553101c9e1c54d675e5405dea1e112323fea2bf8c899f69aad2d48053844b4fc5cb6a12357b4d3b31a1dc6bed17775e72384b322b88f17217449405bda69df9608915869ed2545ce53a048a5113ea8672a1a5b2a75174f54b82b8efd3f062b9e2d5d5e10cba9da9cedc70cac3d30f9372a06c10ec725560e75bc4aa351a52aa17ea2acb78192e1f832972714949b7a15ebbc6670ae7db97cd2d8b3f7afbccefce73208a7f8124b8912cbdaa554842ca72c541e96eeebe3f5d5983927dd8c666b992efe52ef75eab8d7d34b545b0c5c872198245470164accc5ff6252f01d653fd61e8247a2d7aac8b66e5ed03a6c2c27288fca1e9d5025507a45e4da98f0cfebe8cd4f2f7e83a6720e767efdd9c53066796b6241b5a4e937801c28fe826edd87bdf193ae622b6ad75492e6fd67db1d602dfcc671e78a398e04415b8d2830570f3cc0c86f73f3d19cebd795468900294420a5d7e772ba01728dccc678f6dc0f7899afeba35eefe4042db336c1d71ca02724893844285ac00b993682c8b492e991865cd7ab9514216ccf61978716300a6722db703d59cac12b574631ffdb1d4ea38744e39d2a2f70db8af1489fd209f2972ba0674e7abfe82913018996f8db9e939f47df1cf8bb7caadecfb5abb2345011651136e59d37019b9dc2a7667a8f96d6e89817ebe835769337179fd1842387d720103fbde560b4373fe2714b1eebbf1eb9b409ae9c67c82d78aa71fe4b42021729ea810f05ccd6e965fade64d1bdfcc029d3bd028cdc107ce9e12c53d9a04d472a0c0613f479f5d2dac9fcd85ab91c70071cbf2257c7bd7c80f89a1216da426078ccb004da182281a631e9848e9a1080b5b1e56a28c58f6402d40c3cb1ec4a00a3849e126396dcd184f97db002f1fd8de2a2a35967e468838a7e55c188e64511d4fdbdcbd633ea8f375b6b5a7c53e1c3e875d8d1dfd17a7659083db03fa248472d08cd704643991e277df82e8d740dfe2bfadbf48e2a50c8c6957e97db90b737295cca683c8e14859277cdc1934908a7138653964e529bc1073a71e9aea70df72365450bfa5b05eedc808e85637705f5c3804d29b5ea12709cdc277e4c45b0a5ed3fe132412206d04cd52274b86863c84ec113ee7bed67f121be56819f6cd2518576109b60c9886dc610f571a3e561835e7659267d712eb87da9ab342a9705b723830fff1b95a7cea237e3fe2e029e1ae84226c14ed91d91b0e276df04675c572badbe4445a2dd8be233fb55533e963e3c5d9eb44fe01aa34f078435b338e5272de819082547aece666dac2ae2df176ef107bb646a7a907668c3a5f45ecd0e172b86ba8f2c4225354fdce1445651787aa058e5b0c8d10e90192f7f3cdd3083117c44e437138fcdc81aa14d4bc9cdb21e25b2b1387e4446a5e3c9e2e57ad48bf0e90b91c1f3360fbe612d315231a698375e4f763a0a03dceb71b42a0a7e41c23728f2495deb9f5edc90842a5b9ac7b5dd07646ed3613783bbffed4bcc079a9da5c9ce3edf0093ca65f536fc3d75112d88e41aadf17d5aa679d2f760f8cc25f017279e886bcce82149a0caaa1283798ba8e4e28c45004d85d5e22e49df9e0a6673c8c6e5b5c513272e7df040369292761161da6c5630858d6e5a924b2c330f7895a5eca249040fa20742c9a28e97a2b99c63c3dec69de685ea779e748e49061e145f4ac8d4e11ede65d3e78f3cab6603203e9fa6ffee30cfc4bacf39b23abb67872edac505e7a625f9b93d58067556d4bec8dbf47ddf2a79de71ec080ecb05524462bd0a51f81d7eb125c1705a85e9061672d04e3bc2b1e7d5dc47e64958e172d724aed5ec32398b8a019f2d0ffa43a953afd04b2197388117b5c94e467d9424b722ad353e3637d5acb8c269a2da01d1d9f4eb1f0674ff9fbf63b5602be47d7754ff94e9034b483442be71ed42d2951c1aa4f7089ba1cee30ddfa346db8e75ff06b27ea0e829d3f6cd6efacfbda9c64e6b23ce2a9493d0e96abb01c358ed25d8c72167dffc3e1fd86446649300c3e3667a1426595122b4ec5e9284997fdee424b72669aebf62cbbb8c356409d2bf7f9258a288d86d34649f0d48f6153a4bc13df0602e0f089c5d3056204ef46e99ad93cb77ef20b5e6ab3c12182668c69386b2553d86af54454bc81a5d7b9643542f9e1176ad0e154eb0eeb9a5ddd6de111b190428903c71a15c14914540c37675bbf1e9d5e856e27e9e8a881028c469ff40d754e63f45c6e9e751787fa1f968f0560f8438ea85b7664ffcfa00e76edeccf70cf3a98de770f8f02e6bac482c924b1edb4e3dba2e51b7d7fed0ba06595eaca1a14108896a6f3a29371d243e412ed1e48c32d1f3ee1be79998c0fe0e8f1a471ea45728521cfbb5cc9efda61836f07825afbdadd490b31d4a390797fb8bb4dbe0cda7274d7166ced3209dd8dccb0a8b5e573f281b8646b6267cc384143d11a2b65b027f52ff1336a7f7f571cc2e047560a90567bdea472f25a4bd3a2148fcc11f96d72c0b1ef2242ca0dd121018114449fd3cb40cb55202855ccc62719fd3e455781729659afd6df05440225bf4252d14fc747b5952da7253f944e797f390e60c3fe7213add12a37baa4e75ad9e1af8743491f163f2a1a6512437e3622c14cd2bc7d72d009126fb76ee9a205af6f8348d6dad1be1c3fc7ab8db7d67dfc850164f31e72e9f9fe490cfd01664417312c2bcabe1a373ba2ea08a5fefa1130cef0a00bb972e062cd065d93c9a5cd45ab904c29dafd733004a422d3f6835c378899d6c06972a88aa1e7214186f29727b6cd1271219eb10620070f000e103651bfc3c7d00e5758caa8b4dc38b779865b8fc5a8851e97bb17f8e338d94ca8fa82f00fbfff7372d78168bfac746049132b1ef049990ad7d2f3fb921819a3bf76d00b1d3a9888725ea0c2fd2fe6cab681e6acda2a490dad78f5604085da26827b9868c6cc838e6472e83bf7050239f4900b5f9177af9f7d052ac91b214f35ffa0bb1c31f3d42a298c52de0e5be71a70828e95811ef92e2ed65b2f072ab2ccd500b620016401e84d3aab860dbd6ab6803d0af7f6ccd874484f633f1dde0e6650e102977f3e3d232edf3d6e17fecd5aba5457aa0ae4e0e221d24c5d34ccd6f099eb674f1b0eadf77242471686501d7dd0a651b4d453dbb9751c336b41d8c11b5bf93b5acb2bd23b72a39b1a1cd19648c7611690b20109e5f3ec5a96149774e47433eced58a77f2e4de9e36efde01588b6de415557ef38919c4ffe2b91ce7a7a63ffe2f6840d1a8d72b17a1c3a90c7c7aa2d2329fb1e9188db6e7f08e12e3ca80ae92ab568738c5772d6e3625369aa9840b606cb39b6ca8ceb95041bcea123aa130e967ae80ae15868709245b49fc7c03c4b2a1cec1e6a689b1ac64416df6755368ba1e4c9beb65672b72d638dbb0234ed9162b8bda7ca9063021b75550eab39f44259b03da4872d720602d717fd89ebffac35ed10e7a97b3c87199ebfd1d8a083bf57a59ba49b8e72ba1a8b85dd12799d9028f743bd45b6dc4f2ab16c97bd5e52bd19ec7862588871301e174ca1d5bdb775088930d108a14367cac99dbe1c2629841b59e4bc3fb521cacee35cf2f1ae906d985db5452e55a3192758a13660256c84e4f35236ea474533cf7dea77fbedaf416257232ac32909eae72713d18175df97553109705d97720b0fccd5edbb2328e16b3f88b36e33bd2971e4abb4850c82d8f901ebe3818c722c2c9121e339d087105f263f9cba2f6bece3845564104839dc727094917cdd72ea71779250801e4a519af5788cec8d71fd8513c049a9220cfdb258349e656772151a574c7a6f144e5e3ba3da62536ec462093dc760518709ce9f15a503ebec721cdfe32295704fdf61a5ab4b6d30fb5f2c162dc5f5dad3ad0bd75ce7ae07dc1435ef109961d7f8121c8723eac90b41c4f69bfe38b21d8302f47f53889f9ae668ead9e3829deada0fdfba0f79c35df7b0d2f9c05e6f988bc30a6d14c97d6ae872bd87b726fdf3028d66c94eea43b7740965b8ba13b91f7015471401ac1b8be241d3999c9410fa708187a221e12f2a44ad6da7eb39e284930c4bc3bfcd17d40a6d61a87d07ec6f6d97032e099a881f3f7074591a1e0ba8260c9179773e65241f7237e655de0621760ddb7a5697014685d7751b944c127c490da67df39c1bf95772c782619975655518a9ca5cd3469c4a8fb47016234e8d9ff91cdf14554fa51972257addf6e4099addbba8da1bdd2b7ff4f65c2441628c9c1f6e168e5e6c9bac67cfd7ace84a7845fb9acf5b2e15160017a68d7423f2bc61c6d2034ed384226320d63434adece8bcee0471a41c6349a9a9d28f0a99bbe675b5ba929722012ab83a2116f5a1e34ae5d8adca30e3013b63ce18f75ca2b37a8cfaab870425eab75f72c996472e916c3e64756111c70b92563fc4e978049878dd6b2ce29d0603a1cd3c1102a5c30561847e73d7d2328d2ee9725af23959a88220ea1feb8c339baa0f6b1b874ae4f7260798222748913d425a02d30d1ece04da826687035674d69d3972478501529b809d7c6f853176559eaf8de3efc7e42df7a4963e91df005855d26ecd23cb7e95f398d18f7cba6b57183e2559a3e81d5a46ee1a4973e54c0ef2f256d061ab9abed6658ecf1f385afc03e27b0cf91266d1c2f185d846346e4160c4159b787af26782db320f5529cbd07f0c8639b6088f624c7ce8ed228bd7ae67bd72646bee065ba2d32ba0e2b311a6b1e138f440e978c3137c401c50117282362e720cffddd0be4b6bca61a16458730b828fd20b7296fb881c12a70f0af76853141238188be57154da3512a2b580484789579b049051b4c5eb80e944b37fb7fc81726be2307ed34dc9f49712205be060e7da56dd26b517db5891a574dce68a47aa72e831570e490fa2bebb8a5263aef13be6aa6900b848490cde8d8c25735b3c0d4990b221c66635d6bf4ed6a23f4551009c00f1487f536650deb92e2a6338bd1b726b244ee382e83153b2d15eda635b784a224abcbc4290b3bee7ad21b56de0bc72695225832daa18000ac7219aa8e5f85ceeee0476405803392cabf1ab62b8cd4184affb72b9155be25ae1a3772689b9e8924033c60dd7cd49def164dc9d26fb0e23eae2eed712bca1c8a72fe7ade266225fbb4e3c3de8eee677bc9f45260e0e72156ee601ca7a85042115bd11f509702e01152dfd0b34e6f9e101820456037972c7e17cbf03425cea31387cbac50487433d941d504f89c8c7dd52adb9e43ba272cbec1379f4730fea653d61bb1caa9a1a3db1d90249c38ee5e8e8dc1596912209cc84809b5989f1dae05c7598ca4f930fa129b3e1f3031d496f1ee0ea8efe4472770943840080c931ef4905b2dd0d4db52848d0669b3f943f5e89fd9817f1690792e8488439b07598857d117df8da81ec6b0931bbaff480d84f1470956e8d7b72991760b0ee2357c9c2624d1f7717e0123946a62f49c008fae9a31505d143bd628d4fab3c98a4da95c8f012d1168cfde187163b708f7a7725e9a55fdc3f139a4b6d6b50cd29eb148f1fb157ace14cd0e7feeec9a8bdb1ba499e101b1540603972da283164c3dbff5741dcddc1262fc104086b3eb880e2a22d300d523255d9e415a2c059ef6902b759d922332ec6e4499cedb5c0a983ae64624d2fb3b6b2dfe10ea0c202f1e99a6f343adaafff387f882d5bcd374e2685dff1291d92b49d05b55d3f21c92510e97798429c530f26b00c9e29dc49004d459e79c6122cff8a36167266e9b46ca94005d98801063c592e0f13c37e254620a9bdd4f6e13cae57ec3472b43fda68fbf29f6c6b99b65ecc52532331bded0e3f70c4de525e9d96fe12b172ef47f3d602e8e9c19e66314d7722905de2458487d976326b08aae425df197972fafafc153d35c32268a017ad22fb9a4b637786d615d6e60264ea37e486fda14eb8dbf89c536679a9156b7c17aeaa4793c3fe8704da922c918e7f1da6cb059511fac7b2e1f75a1b56657f08665caef5404596034b100b0d43e1cb06e5eb3d02728da1a2be11cc13fa79572f63213c323e9ad4785eee31c441bfe57a2a06da24726ed13ae456cdd7e5b0287e8c3fc5ec750e01774521860e72e5eabc3dc82a0d7294010f118f916d569e4df6fb75284ccbae96e1de32a6f2e73f05a3d10afba66551a5142e3769f7fdeab6189cffd7373ac181076043852c9b2350c31746cc0172d17b44846dba033ba7b811824a06ba79e1525d20930481c2371aef8acde2b026bb3ac00ffcfbc6b4e23ac620ca36783df466bd4e3100bdecad50a47951f799122d04a2ee4d3eae4642f868797961e5ac75fde60a736a5eacf73b92bdee4dd34d29783a8bc27bdae40536fbe66fe040a19fcc29e2958f917055f28ee86221fd603826f954d6334a46e0b691734cf810f61f715f4cac4c46b6bb3f482658964c124bbaca81aa67f6a76a6c5aebc492f5ae40fe9ad91ac1782982eab01f3196ca45d281f06f985803d7614507067de1a906cedf7cb055a5e1bc7c3f23e91b6e6972629040e7e0002424e48ae50c58c74637bb7f7e492d2812dca895e6d9516dea727c0d36806311aee5fc0a878fec0098929bfe47e282d0ccb9c4c935715894897217ab3c8604037ac5298636b49ebd1b6449683b86f73ffb5a540dce145869ca72f89f5fa26933e8d42a6e06877ce2e5e80732d9c8b349dd6a138680f601118372edecba07a50368fce6aafebeb25520a40142e7fce3826dec9efa92e9ebc8a94ae9af11b1933530e1a85c42a5bc6652aaa773761a9a6919ebc5c4c05928d24f72d44b4b83d41aa8f8753c613d747653df2b9c898e4be8d67e93c80150a9b6ac72d326f198050f2b2e1d9dc5b64833276d9c21161d454ef25299ab09e9cf12827249d37f0f6c9f077dddd01d2d3370fdf41f2671c5680792ff29d2f9f714b4d172c8466786baa0db3fad586b693ba9b3f9e3709fcf31b7431df5d23fe287ff6738d67acf36fe647ffceb0ddb3fd91b3454273038153014f5ea39bdcf753a313272e40dbfdc5011be319c54f761f29ac24d80379d92a73fc8f0cdf847a0b4c9fc725d4ab56b4211c9dba4d0a6bb758d9889a56b936b384b13a4e01192e8f6ebf4727b2b3eb24778bce5bbbb7518ed898e027accc9e58b12c979a1ea13ca8e8eb621c8f0b5225037ea850a520e8930595172f54f94e9403448e881f837e2b394c7722011852a411b172d4e5a7f67ebc4d37bbc52df1599fcb9fb0a82cb98c3e7c9729846a7e7cdbdd03e0d27ea5593e3f085c8e882db963b24eb806f5777731e95720383a3f11b12c8f45d1929065fb05d3ac39e889098c68896c434277480fdf512bd5f04e137f81ca55c3ac7ad76af2aaf55ca0c31fc467cb27fed5b912ff46e720df4aaf01475135c97fc79710c8182bc1973d4b884a024a3a9b7af64f2acb319f632b226cdc7a25980980b1dbd7b8d90c270d4a4bda9d8316aaa75980066ce7296b948cf2fe2108f05bcba1979153048ec23c9b98b7818dc70bccdfd751ee441f3d1fcf7388ab2556b487dafaa3ecd2630c9b097fad1d863261d9f6337c02861f194ee428bff4dbc9cceaedc7efe880ba4f5bdeaf064482889b3b3031a11877260dcd4c9aa418abf94bb15e7a731108c2564cb04dbbd6ae4c2bda5591b7856119078800cbad941f857a20de7255442349b0c08b0ac2f2d51ea74393c8aa4a925a539c10e88fe43896573252e02f40c62383e50a3cc4c7be27eee97bc151b0153bb89ebbab28de8be9fd1912f12e8e090336721edc315e039aa6e2aa939316f72475893896675eae15710307e5e55352fca0b30870d2abcb8add9cd74c06fbf48f9b58feae24fc0f592c3c5bb930edeb28302a10dbe2cb779d6faf60c1fa497729bf56dd399f1e622b363feca161b1dd81f6bd62e72aa6a6e156a54a2f95f0f72df8b69ac231f232b5325e8c8baf27fa566a7263481b8b62fdb9e20eb0fdfe272a61d7eb1a889a6ac8c3b7b5245460483cb6e218d51cc2a795325ba47c624416621d37d8e82ede2cee3ce3915f4fcc7722cb9fb975fd808b58c5bea7edb78666ec63d84719f5f6455e04a130b0b772a7ebaca1a81a8b4e554df142d1c1511140fee278e046fe68870a518d380ce98c608492c083976a98e7f1987bd6be4af8772450fedeed2d1076867bce2b68ebbf5ecc5617ef00c23a6fae1fce5e685b752262e19dea7d4c515d874dd4ef8aebba9d6bbab17753d417bec5a6c6c7dacbb9a0e17d3eb0bac45115b0a1076ff23d931e3d6d771985e97a382b9f11501c6a63614809c59c42bb008fc433eca7a416c8233f44b52df729a30e7bb661553971ccb720503d50c72631fdcb18d4d68f1e6b94fcd54c2fc97ddd80c0b52a37acdcc9b72f2db18c21588e27beaae6aa14a11d9dee30939d1a3b1fea73539705cfc282972bbf222f181c274281aa521f73b0ea50a0329f7f3c00ee01389cb10a7682d75728a1403c8530dade4d6e04ad20a46efc50760a95c8200b9f94efce0d17a42ce72c160225f9d040b2cd582534e5d121097401751047889d69fe4185f2a6d742e72a88f47978c80d9d5b1491f7ed709dc416cfb2428bbc031e5ed23398b4a21b872ea4ff110cc078b6c9b869880ea955cd3111c5a3901f66c939738f154acfa011b250804451fe728972228579790cc27dbfae1f72809ee3b71eb88a48735ea7a728c6fe25c2c1b3969a5a54679f0c3d217e590a917b16a9071a5ceea950a45a67264aabfa2ec16decde86ccb6665cb22714a94a2a55dc775f2c46ad7a11eae157261776f266fca3cd866ffc36cd29c2a5335c3911f949aba8d37e5afdb93a20a65e09e9a7de999efc8991ec5f8a4b5bfabd8b50963f7e23a3c59a131b8f52bec726930170d7343ce273779ea3516b241ac4421d86a06eb07ec8f21e7e976eaf972b9ef370a94b880e3925456ffcada8c2808a2773e1e3da5c89e714c6536bbd555b14fcb83ca888c4b699f9fc812db64561afc87009cbc5589b56ddd16259703268addf04df928e8d5050e0f6c674169817018ae18ba5e0396221a269be337b772d66d76addde25a1ec3be15822197b6d02ca5904164a78de5780175f7c66954727de36065cce8a64e5f16ba112448d16450964b001d8b3c16f80fc9482818520da2625df419b35a0bea33fb20b7e0b03335816d88fb462394bb053d526a2578725953f0ffd852a2f1346d6466d244e51948ad3519c6935d51232e6061acbe4b0866347d3755ecbdcead352f8cb44ba4252772b6e4655ea30997e52c473f8b1e003494c481399e113cbc69833e67bd6f6a90820312695d9599c0ca465a0da7b77277ead7b36b9d85c5b3134101af274466ab11d4316e321ea5810901cbbb54e6258776edf984448062b1478a34cd5c382751d1e071c2ea4b4c19d3ab4b5145737235406a3d679fbab9ac8f899aa7e5e84af7dbc67988e99963b491bfc295ec1633f1ee839766aabe4be233c082767bd792436f106abf87913252aaba662e4888723d12cceb423b56a21d252299667bd30bd16f349a8a31d282a632b7ba680b5e4179586b8a6d35a23dcbbe1b47869f70e0a0c4226031d8d8c9731dd3dbbe5f46729f5e5318fd8de5a04882ef7a3a39dd050853a1f3e18489440960fb50693230598301efbcad80e2f9870ac519915e6cb54a4d4ce36822bcd8065d5c0c8c913972b1a557875be86625a645c46fa2eea503ffbd3802212ec56a4c5aa2145363712e1e367d640889c1748d5062b24d38526ceedfdb9285f830a621cd344da1e2fd4255533831099ed62d70e4758d8671950c913c54df993dcfed7f7858bb8bbdac0a1ff6df76db18e209bdf75d01638ad295fe1c7fcaffde87344b59e033d7768d60ad86bd823a8c734208ded16c6bef2a058c584a36bfdb04663734b89f930d1572c60bd7882bd90f8f4547c20b079e4af186964e09842dcd9caa5594a92f7905726d82dacabb168cbe69a72c4eb7fc384b0068e23d77ccad3590c0c8df82950d72609d9bcd1b32c88d97ea0c72ec149ec03bbb014ba879f61482162ca3f7544c7228537b756f7cdb2b9bb683da92a2a5df74ec25ce052017b20d96a10581ffd3722d4f8783803898de5f7fa87e62e1595333273509f23051d9657093282c15d6121176ed19afd9f10cac88cc0872b605b9a834432b3de998af0b5351943db2db038dc12b71b0a827a77b67699a3a0142585c63f1a970d49eb6e5455bb37a53a826796505f027ce24246d688d1069025ee03fd0681b52d053bc4f99c3989beba30ea1f6c7633e211cfbfe0130002e7d20b928813b51b248f8a40cce7b9261e663722c06981c73180f777a98d40a8ce731a0d11574cd0ec38668be66866c2c68714d38076979af76eafe944b909b83c47926b70a21e1592370f7e121e23f04fe3b7201c1bdaf1b99d320ae98c87a60a3c9876cdc6c83db7d91bc78699229ab8ee02bf7c599ad758d81d1822adbd903e2d842630da5c1cf9a0139d19f61c2c91d6772df4c3ed888970d5cff66372e63e611840286f7a14fb153e37093b9b02d92fe6f0059b10a9fa8505f3a1e9f8d5efb9fca6bce33137580215f816a4d3d2b2079550538dcf4733f5b6f3fea0a59700890c9c0b54937eb80576e5536c36e569b24721785bde57d291e3def558aa7e9ef0aac40ffcc34b45e5bd3c77c8b7d4f79d12d43a9f694119fb662231c93627d23f1d24340e72348ead2a6f6b60a4f2a5a8d6315fd199d08e0af22ddeeeaa436837fcbc7618606278f5d29c4477a4af882267243f9cd6a413fb19d515fa6a3a375227fa78a5d19e47573b94deeddad89da0f72c1bb3f0157c956f78d5cb0ed0fb7ea0aa3ee1915345ca28976baa85fcc41e572f284dfb4e8ad1c7bf1d685055c359cce8f4884dbf0b941a397268ce01b4c7412026d6a5510ac7949cb5c862361694f06bba7afaaf2889deeda454fdcf4976c0be1a086154273c7b9afd227e83d1fa97c6dee585108bbfd3414207f6376b1ef22709ffc9614679f323d25f5df7eeb9a81eed30014de3bd28cba88b9a45818e1298124c546570f946470ed372cace2e30a32adae451c428dd01588dd62abd44f72fd4e112a72be7f7abf4bb052da20a73f50531f59aa7d4f5dec234c4e3f3cac724885323d6cf719099cffe9d475f9d4973bbf1411b96a8d8d9ddea6fb62d6db26c31e1c0d22c536c746a6e8c1f84d6f36d4d096eeedce6f433b3473cbe63d62728c4861404a71d00c3ef130955e6b76bf87a74b25758df745b7b90a08be5740723f0cb3e2948f4b26e818b7d428fc10f595b1ae9d9f4178680f0d43959494367210856553c124fe2c517ebe913ea2901ba847f5fb31517905eaec39ea627ac372b384dffc7618ab23bf47bfa06d8a885aa82c5b4d3c5a774f5024fb91e6b49a72a41787609cbae89b344eaaa64c5bc7ea53f6c001d52a4f0c8b42cf9d358a0a7232c03ba3662cde420ff53e78beddd79894d48e991870b64f504c8a6d9de10943137b24ac5a063fba9d7fd7cbbfd0ddf456522e23bda5b702cabaacf8c4fea3246505c23440240ce92e0024e76ab11bf53fe18f6b94eff85687b9269a8907e36d63917975f4ffa65ecf3f494b84bb36ae7365d7864167b2ab8ddf03e47134295dc187050a29136b2847baae883ceebc948f1e102daedd69b7a1255146887d8002018980bb8b4bc0969fe77bb8dfad11ac3b920340d9a87f0e725cda760a5b9872346340cfa8f12a849215da1c01a8bfe89decea4b6b8cdb0c0ce0ffb621086331eaaf156ce4375005cbe45dc364869a3b8e88c280a2ea9bff1e4262c849da3b243571dbea5d11283847e61d1106f508a90d7f3672017ee0fd96e0f47d8078b533901bb3c05bb5d95b643b33fde1d896cc44378044a714dd4250000bafdcca0d644976bec547a67190589a00693f6e7c2fb55e0bc5272322688311b310ba84e53b0ae1cdde895a850152be2b43b1c4340ac4146694f5e7c350b2880054e24ee91ac3240856cd235b7f84b4b305863e2e966165ee4c6b3c30163686da5f96dda1721dbf4cb374efb5276f26285e4bf90a629f4db25a853e53e1d68b4608818e5060750042457c8ea117dc11400fa9c334491bbb7217c8c4bba1a98fe5433c6c3c2507488f9a7ea614f62cb32d4afe89b951be80eb1b5fd3fa5fccdbc89aa6b70a72709dc28b157305b64352d3d8324fe0a6b48fe2c81ecb50ac744f786f1e2f0d1bf767d1b4f98b50b33129584614eb26bfc468aebd1bca13576f2ff8c8c4d0bc70b1ae4b5ead711d573bfbfad74866c30932599b9d0aad211d8c812d529b611b540d3f8a51b563886b7acf2754a14f7b70f1063b548cabebbe28de68c4d7191d725fa9c05aa6651ac576c929faac1ffe3e49d9665a7471f2ce55cc87f463367545821208f236bdf6819c031e3ba8273483c857ba56864c1f39fcf398901e27787233ea57930334465dd0ffdd7884821ee041e41b5eb70a56097ce04f35dd0e6272aca2e5573b493733bc0b6fababd3e45d9d5ee96ece7ecc91b435eedc89efef72465196948e364af3151456f21f3fbca62d9e6a1fdb260fb0c45868a51014ef088fe241c9ae6b3cb0517268f8bc084ab2164087bc57e1883aa38de368aa018d72c7a7c0bd230c30026beb7ffcdbee6c8766704692cee7f61014cfc8dfca73b03ab27a3f204ea78f0d78903a57fe05a0370fd9138a93dca8e5c67457209bf9e669d9fda77e159d6140c55c3ebf6b87d3d43b58e5f25601d5e4b5b79030e5c1fe63aec6f8dbe447c4df678ccce5b26e43d408e9463d2d8d92ec05441e5870ff142f0a4039d7e72c83767e9b8fbfd623fde0d3efb27d72d915990877716070bea57211b074780446f28c88ee1e994303d5cb1e79ece3366280a0b7f1d542553db372f1e2f352dda304d4538381b4be97af00def43296c18e2ecb11f3556f4b54d26f30badb85fe1831814f5190346298b8e92a9940c1530882c4c297f8a76a0e327242cc85fc6fdc8b0fae39e13e48181624ba9e360cd14d7a18b6605d45338d7772b1bceabe56ba135b8b00eb92884030b2ba0e616432aa1e8a577b8df3d8121b721123b0786ee66eef9c498f891fb06b80d1e3efaccac348d25e6ccf117fe3c94e583533eafdc9e3676744e265736abf29eba9d1354b2e0914aec97a15c9385d728c0c9a01c9426330663bc6dda8b1851f71a3195c82605268e724c2a6fd8a6a65bbf6cfffdcee2a9a7e5ce8685ceba9b0216e550098c1f8f8e199fb5c9a42fc727131695f9e69608751a53518a5bb3c6f4d3e5d63508c5e6282ec8229879ee87279d36d006f6553b6c4542abe924d541f6a343c116298b1871a0e2236aa15c372423778f8c7412a093d3a13d1e1d1b6dd068cee3b4398c3cf07d7c4858f4d9f72dab5a53fb89217671285c35c96540a8b6862838a5e4251003e994c16d3697272bbd53b4928ff5cdb92f7a7f07b1679dff687f575821a444f471220a627b57b2a5add14947796e993e13511e526a442d1aaaad24759a4cbc8268fc1c12f096272da6c3da2e60926f7468039f20b83e4a313558536011073e68fede878bc813f6a85692e909831204b3c8b97c924ab9d7f35e83ee4f29ed465564d2ecffaee9a727abd9aaed807f181f56e44eaf95823713ebbfe18fd55f811b6c2c292c9001072b4cb083c546b60b8226b1f4693e2f4ded434f7636a864e71ef80ed231891cb22c8a4a42b255dbfa5c62514c22a97e3b6ff1f9d459136bc77c345124c2ae49001eb31c14b048c8d26d79dbd504378362ad7a79db09963922b36d43221940020004072902588f1bbcb65dfc982e0654c2d2fe5d79b7a8f9730039d74081e59db112516b8e5a94825caeb1e95ad01c5a377c7131bfed8dd0176e01736cf7e9a4b49633dc917b7309b2e739d131f41f7471e6ed11900fc520d87f070f1dc2ae43e48e08dd060401f967ea7fb11cc12bb28498087ddfaf403b576e232d29503511b0642f74bccd4ee17e641a9643cab5bb49b67609f696d19627484e0be192a9cc7028d37a7724727439912295851046dbe18c03ef1cbf2565fa0bfd02501ad23e9257d475cdffe81d381d934ff9819f2a9b75dee4bccbcb07c0f7f7605e31837987283b7da23a791af202a81eb879ce31b63a5aae76a3facbc41bc88d12ef61e7472fd22ebb7cdb992317964447743a41376ddc916bb183f3457b947bc2749f93672b460f6e94d0868b729c1fbbae118f8db3d073b7c7f2e6b7655f515667483b9721ba97c55cf419f44209f1384fae0bfabee160defa304de82665be8d45a484e72dbb036aaa908135de64a1460aafc09a3db7d614e93b17811ca6bc845a9b4b472f6c79b42d7c21f77bb3dce73973e520b4c3ad6152fdbadc7926dd863167a9907b54564b37271ad7e38223d5e0ab2de01c2f0bf94c1cfcdfde77691d614981f72187d87dfda5820b862ab3c6f70d8cfedb8a0b7c7008f96ac552b3e428c745b72dfccc53627dec70339c6ba8536d99644972b35bc3f7b27dc91d563dc39204e727ba922e8ce3e2865b277d7a4ea5d57228e1192ea2be1e80dbaf7b903bb8acc7279daef0e87edd0584ab8012e2bba9a69fa61bb77cb3ddeebb1638287c4534505c01e35c996c2a72322c4eb079419e1a942c72a2ae93c89b0990bf73554c6d472e73fdedaa59cf9f65b3bc2eab5cca9d8e34b26df59425c367624c8a94ebaf272bd25ca4342be9ebf2fe5276ed9dea53f978039735a11c814d2fe541758f3a17167a0dfb82b91603a601ac9a28f24f2606cdbbfc462eb1878b945aae4b10e55206722fbac7a4567439294642b837cb536ba5b43dc0b0f422f8d7f045b82571c6bb1b97ead8df2d67a7208f43e5f46316b81bd37f0380961bc0be4a260cfd40d6bb18fba81b1fc8d07f7ab65982278430225101758ac74a2f99ec902df72f7c6042c48bf198ab4f476489d2f843681a28e06d1d19e03cdd609edc7e8b61bae4024122e5468c3db54dc98168bf11eb5c35788335640d266377b752bb5ea5a1e5872fdb72cd0649427fdc7e5f3aee4d0ebd3aaba42a6231565573d44d485d5ac6567abbba09a026d1597fb0f037a4650dfba344cdb1ed3f3e0f4c28c749d7b14a84e2ef297dac3f456a142c9bc4477106be1331430b588548d728121cfb8889a7d6fe6aa6a72dbba676722cb5a7d62954dec3e8be7bb7855efb0292954efda192f18d88b75d42872c3152df5fa5906437b02ef857d6f3be0725fbac0b3a3b275f510064e41d1032a45e793aa4633f1e47d4737995763ecc67d8bf5d700ea8e4f9b725ac48d33b5d7d25b3233d0a83b7d3dcafeeb0769c98f05b0e8f1cebff4bc9972466d1784a4e234179ebb15fe2b3cadabb7bcdfe36635e13d828ebe9fb050ad7229100a167fba2466e7b513772f93921c0730f1386557cd2a16871ca14964057217da1eab7ffeb23f92a93df2fef50823341b0c67213b5be43cd7e84b035d4372526a6c3e21adf67b0d159fd5b6b1f1900533b6012b0b976347d4787474ff2f24ed11257fbe73ccc9ceb725498c84db8e90ac6f5eb258be0cef6dcd7bb901d92ad6691534051862835006749c923653cdf98dc1087c13be736300414745bb3f45af1d6c76a54f63fd5de5eec0b561fdc112013490b22532ee94db2d5cb3f0cc370b4e15d0d2e44ac55178467b2847b9dc06198013abbcfe8374316816715f0c7254e8dbf1eb79501e4d3594aab4895b3502f1984ee99eceb971e3d2cf22401e72c00609a837ec4c78409c2fda893fa6cd21eca50db3f409ae493f182c21f87872e07a994c4305bb8c650b7d9e5c02786cfbc1eead40947707e26d18563468923277cb172db1c7a8d25faf9389705aa60544d881883c2d2eb01db1e2dc4a1ec672d54a30fb1c0c8008daae55866d6ed0842e3d1e06b40f2469e0340c832badee22255c097aacd9ebf3c129c645c16b755286828476e9e0fc23363b58ff2b26187201c12d8b4febd6689092d4ce93079d7c4706aa1401e5bb8fb4b33c5c31030c72c2ee9e7eab0756b61ccd1027acaf83849ba21f0083d677d58c93bcbf254c6c72d730aa82e2a6e71cb07af7d0e85f3915d7563e48350a8faa44f7e4167e3e93203b0bda82148fb9089b0e36ab43a8e7efe5a4da250facfa2f946c5b21f01d4b72db5884c3f93ab286078df72da80c3c2ff1f702287d74131f93e88b13cfb95207ab85a922d49d88a332403ed16a90de1704b9d4e331b40dcfa343c1d23c721272f48e4bea0bfbad2171ccb19247ebc420639d7affe07e7564eebf26057cb7ac72b1b73aa4f7b3e931002c9cd918dbc92395f9abee854ba4dece10e71bcb894d724406e8c471f858ac62fdf0c38cd2e76f2c24bd3ff52e57d0d987f01da558930609602c1b6c803ad8eac2c2daff3ca403ebdeebef3a2860aac8a01314aad9fb6bcd431cbcd81c3a3accf10a70e0dd924d679a1387d2a3134fe0a7e0730f6a4d7248f67d0a8ac6ee4e376797ffe9935a1d04f8f6924e951a7df62ae4fbd10b1472a589a8687718712e933ba8d11409526e2ee66a60dbd12686bd8455e35bf35b19d08b088b8be12d23a9ff4e8ae1469d0c10b876dea5a4a23633a54c1eb1ad8672083ceb6d2e318aab9365195c9c9ebf41c7ea5f62f5a2b421e146a34edfa01f334a8fcb3772af597c47efbfa7dc5df9b76da4d79ae43f53046e322a7c0d51b37238e874003171d30fd43b4c8db2b35bb38f02f7e165822fa2349078de5809b92383df3c28b6e28f18a97fce45093862b31e020fd20b8072c3a60c8a5931f2032c4de47859819fd29540df73dd810dba6086d5877e96e02410f022a7ea77765672078ae0dbf85013302c0af3afcb5e45776c48919377bdf3dca391d1447f0f9c725ad5d09b74ba90bb642b94cbf04eb3aa25767485ae6f703441b3a8accaaf7b72e07bc483b348bf26e190ff03f2951190b35649b8a4ab21d4bf2e6bb0681c1e723daf4c262415b92fe93b34bae2659e29ed8e887af07a43ad8c5f6e463232ad726810c083b494f4bb5d4241961f031f4ca4f8a8ad2e4f0d2d02c08b9dfb7c8872ea44715660fedf46942ec0f564b8b50c3bb792e12c777d2736f4ec7bf6240267c70a3e1967d565f37930c42f271ca4ed51f9f20c4ef36f4be6c38902be92da72488d63f783719ebebe5b2f329fecffec746d3b210d096811f545b1027cb68172cc016a9a8538b403d4a4000cf4774035ff526af30d39875d182f5b8edf5746303d3110c6a14009344fd1f0e86e798a4d2af9ff9c5e9aed77d1da27f9ddea6b721872329466f04b6914e025024fb907e1d2f025e60861a77b45fa4f765d4d67720159a115f07683762aed70447de09b53345553c33cd3defd4e39d4ff4710fc2a0143b1728768ed54025fa7a694965fbabcc4da3dd063060cbcb2063c6570a07239fbf114a3a5bdafe8b4b0a62fb2bfd7e6ec19445a59710f73edbc55c21b1b72fbd87f29c806f8e479b3b10204f7b58c347af5231e6b5913015de86e0c09a77212ab5f6c14ca4a598a6e8c5e58653a4f08f935f250c211949bcaa2f0c565af3c759d21ff2ccc4746468836869a6e471f22f724125c1428b051e1169b71b8927219d884ae6dd8671e8474f9f427bafb1cd3f5f210cd1c20b7866e5e31ceb2b372fb3292dbce922bfc1b49f962747966e7231c8f2e08b15abf1af462edc9c9037223a2239187f78021be5ab65e453fc4b117f46a1142a5ff65263b088a8ade8d4de3a8c74542cf2ca02f02e5c5cac2860e5f554e55a6c7e87ce6e247d8d7eab4724ee69e00a10e286a319ac6b6b9816ff4c448f06157e0f9adb6db7b3930fe95122eb83ead34b771646689e338a3be5efb76313cf746b98b5dd5cc641b04ebbf72679287cb78ed4a80383714908cb2720d4a87c23f418b2052018b0228f750a458e8c0a2a0603a55927563fc791d7040ec80ff421fc32fded16a0729e3f2d8957201ef9ca86609e662dcd598100c18cb7286c74ebae5057e741d01439e5e11820bd5eacf58a1515b959c2e5d4cd42d2bc547d007847956a65ce1135356bb0e24593c35f3b23a2a227a05fff24e21c83165e3af8c905fd720ca0f05e4de611e37155ddde338ce75ce0e83316cc7af09950561f79758a971afece537882814f0dc726c55e3c9884c3c6d884ce5649b15700b5e95f3f7c67b0da6d3a8fa1cf99fb823cba1f9c4699bf09e83cfde202715be42dd4cba3788fe69acdb99ee0adcd6af48c6246db053e3794081631c3aafa9f6f0ff995f436a9c8a6dd73720cc55c13e725ca280a3b9c293675b613fe5750abf8a3bfe6767a69ebdbc21a17b9f2817fa353e168ce3fdb543e1ab97653ea570452e5125065e38abbbe96c91165993baf472157a2625adc4dbe2c9a67fd8e2a8b4cd6fa1ec2b2dd00f36dbc02ed642246f720c3808fb7cef8558daa2041cefaa55710c957ad496f7deebad3ce47b6e161803517222c8756cb942529913618c42d310506c0e969c8e76c810fc3b6ad8c00f720fe172f294497cab632862b9b543ff3908f6627b36fed1d6d9ca638a8956c2723795f42a81b27c4c1c7fac2c7c181299cba89c3378419f26d9542a94578c414529fe2e79b0c2d45099edaba7127563ebbe74f96cf18f6f96cb087c6226fbf8728d7969188e326b2cc9921f6774ab698ea4539b5b762e7b1cf52db0dcf62ec17237af0525168a21e3716abf20acfb459581ed33e0ed471b20a73a4f638f5dff2adfcbc0ddb2362b4d086872c5d8395641b02b62487abfefbcb491b979eceb8a72bdac29d156ec2bb66fc40351faea07a08c3785fe64ef7dfdd0b630b73a3439354ecc6f91084bcc18010731e30214151361ffa7f522a862af2c8efa2e75453b72662f1cce0351623edba2f0685a5a37717d1f7aeccc872f87207d210c9b40e07279912233bfe2fefd4a3e4bbb926d82f9c68b311ec8180f7c6629e58b1779495b4771ec66146cacced985189e4df7150512760bb3af5cc4c8b2d506f56825fd45ff556e2663dd9e479b3c3528988f5d0654196936a05d7d4bc2d54b9b9eb1192bc441f8d086a3b25c2c77483b31b71f5686a3a83d18a1c13948775ab0b24ff27287eda05d0a3e637a12ba3e94833ecabd1d5966c272aaca992668207d9f59a346f9d8aad5aaa4a701b5307f10c2774eab0dfafab37cc6b997efbc9110605c5b38c0660655ca656545134df13c7fab13419cc939cfcb5228c22462eda9fd9019725152aaef1715156c6085fbe2be705cf7f1d8a476d885353cfa82d32d5187456edac971a95b2ee11594a2b404d609f3ee8d2299c06468b8575a5f143dfa8c8572a0b2d5651a3905538e85bdb28bb0978b589e4c8b33da44e3ddd426de2d506c698629f8ed7aaee5183b18e4ac2e88aeccba110387bc5f6849a63fd913b999c866eeea16d037094d63f3c711a4ecd6783d0bf4b166b878d91425c4271649f8ac72c539a74594f7ad6e0a48e948928dee42add93b282093a77edbae5ec322d77172791fb75d2e92985a3f6700434f79cc2f2018890fa4adeebf5ee964e280b96072c6da2c2c07da52e802fb679a18b9a934c11b3c9fadad2a63d191cb79189f46118083918d13b65d7578bcd0036216ee352009e91d19b48125e8a0dddeb826e8723a071725d828a488837967312d50534645831b0fc3b6ed459c3c62b5f505dc29bfdc8bdcbd58098c9ab464b8f54ea3bc4a9b303235a62b521d5438b4827c2d67e3d4197484ba67bc0da1e98e139eb01fd1bb9a56652e11a95c1540184e92f972fac0e56a3f1cb2b6b7159b3f1dfb77eea8bd33af74fec32bb2a99dcefdeea172fcd3cf7f7579f6c9f25a200105054afc166f2f3a24d3ffa040b2165bc2727f1e938b64b80140a12850d9bcc55e5d442710ed925e8ea3f7076eb1e3f4fbd8a572dfef0f5e242057399e25dd8e4110a37d8b2ac1633eb0e4783d3a00594ab63672dad476a360b17c703161dc4eb4a5f9553d0785e0a37f9d683ed497e2f2390a72927d27c5946ae871754c44b910694a39975f4056eb1197c0b08ff177604f6e38428222923284daad4cef54bfaff9def2828f62bd5dbc722efb9176b357671d7147ee15204a5081298f8f25cc6c8aada60919f8608994855854fa534d67db9d0990cc73cb6aa42118b6462840e003e28ca6cac5d8cdd52df06c6366e1f699c66c819ff056ff42c269406bbb4e122ac00b95d1b4f3ba7466c9efdcb0728874cb722adb8963ee4801802a2f0c24cfff363f47c8992fc214f87e304d33614b655d637e604e683dfef7049699569d31ec03058c4e379619c6d3bffc300b067be6f84845c401eaffe648ef5b3366fbbaf795b2cedc79ed5e25590be9b52fb9b7513572056e2e4e300bbb08aba3c6da8b059691523346d53d997cfbc7e653a55de4f16049408b1f03dde29ded138d0bac572cc975d458ac8ac73b7f6cadd3132a992c7208371bbf9520e5b7200bbecc2944cf0781f27605316fdfb999253608fbaa3172e6d9ec3117f03bbf0fd20e7302ac985149266de12af15c1c89cd0aa7445ca472dd0dec4155d9386183fc08e27c73adfc864eab0f543e7f99f69690edf1ca0f46821d6ff6d87d38ae495f25f07bb789c53c6e4da8293f42d5ba2897df55e73972ec9908468d939077bfc077676f50c1398f12c5ab5f6bcd35752c4178f96a5c723d3d58c7b20cde9be68ce033480e899ff8ce0a79cc4c4cde2cb3242abf2e9952f795cb088c9e1bfd86b713fd22cfc20803d64ee1dacbfd3cc2b8f1f617e658685b1a50a5548f3c7c7ed95dbd6b4bea32bdf91c80ff375c0202008bd537ca23720a75ed9f7d6214aca88ac017389f4b1326d4e03c7e83c914fb82bc2bec21255435e1ae87ede759c61ae76a22c2876577ef11573b4f5b7888c91f443661bea9721197077884ef4a49d969d20f525d784fb91fba7206bd74d7b066d040ab04e57210daf06e3e9bd93b105154b7e125cf02abd43d60cbe2efe947466a9d30793b7282f554817bc07849219289151696c0b461eb35603a465a0c7079e5d9be8cff7220c799df406281905af9775e5a3f79dea603ad437e99f81f3e080a7e22e952724d055df7b2e46eb7781d7e0fb3bc8356855217ff9f6bef51c03edebf9926c2545b296c8920f518a38d7427437924fc464cb854d6fb682187405292163f730172048696c7dcf98b3464146bf56b1d0815320475291325580a20729874e2d83118982b34a3277741498566b86ce48ca662c9fb3561ae0d83ed32be20f3096ccf72331904213978740ac04d1ed5944e836dfbff091c653a34de22ee8863b021f840760e7760f0bb3c94be3d83f8d33fed40185f6559b98db587e271cd3e539ede1c24c9812052bf7520ceec60ffafee3f846ac0bc608892f1d66392b5d7203d252f636bdd7ff98ff402d2df75bb00b0725d6f5d15a3c85115859373cec39bbf400307a8b2d3dc6e853a23ffabfe200ae1f6f075f94140731d812d74e9976c5f903ee7a920d1611b07afb40c54cc45b334078cd36c1818de9b2fc7b29b3840b3d45809f362b2f03c41bb8395832fd14f8caaae58814bda34b86397ee258a3422537270655deb05e53a9915cc052b40d036c2f2537c11e4eb4b2da417235aedc0372597b33214c42f541bf62a5db0feb445717053a7c13f43930e170a61d655d6857213e385e85cb782638115beed23365a007437b89bcc7cd3d3496b6d649a9dcf168a59877a2aac9e7de8ca1a236b3a4755af82f5775b62089d19d3b3460525a42127e86d6e7104106daa7f809842e84e3a58e2ebb61cd58d876d4cf62bd893ba7245286cee822ccdf98b77c8b95b8a7e4140c91936e1f4169fe15bd5678bf9ad72bc2f3fbe92045b5315829f269859bf2835bc2db030c7ae9d3f9a4f3d3d9a39486e96f71b372d749254f6a5380cc2a42eac87dd0541baad99b9172360ce36e86ee94ac55ae0c2971ce7016d863fc1a26dede3273f70e5bff2edbeb593d9f8cc080014433ca22ee373bcb794306d4ff8cd70f96a8bf227e78ee71a832db4a49254f27c70c8a653de0cfa0d1f4b088c055ad01fe8755f7c174564932dcec3c4fb72d88c5295074426be392c2f36352c9aaa6888bb3f008ac9d2ff30e5224eb29c22b99d5118dacb6ae387765786a782d2b86ec84a8464b925d7a8df1577369115724a903ee8d3cdf35ee51ef378c8078918f5c79410643af7effa9175269cfbfb6f273a2a382c10e53938a6a1dafc8c23f91635e18c5f76fed2668a308d80f61d7201910db9a6f5056376bab11b1baeff76263ae332ab7697470e9c8dfc1a15e472ff92f97d5be99541b7fd7de037974577d344d6a8a6c482fd9bbd9745acc68b4f04bbce956bb475b2db4452a672ab406e1dc031f35c21fa7994d849c1f4e7c16e3176642495fdaec42c2f0293fe08181e0d391e2fa1036f7609d86058f45421710feb453341dc0b5cecb0fc5977232a62651d5b2ee806b88d3b30aad7a08ddf72cb008b200d212b94646f8f566dab83c8924b7055a8665545a6da0ef3419dc6720a566da42772baebc7d562b0618071ba84e87c211a69a0423848467aace0af72c001d783fe4099fe035315eb00b0a60ca86d5052817e3de4a417b7bc1c1c8b72c5cc8c1c222a6b0bb9c3c24d523a85d3f7f35db6b9c67a5149535d563194c120e12dcdab000ed75d5d1d014bfe6bf4743ff71d1e7190e4773a94055aca895248e146e9f3d3970ed401648e7e8cb676e61f08bade590cd6b8b707a6299c2d1701d3b9b62b4dd129d16f7fb025b80121c7d1480ac8046af9a9c76ea84083bfad72e86793ce99f11c66078341b5ecca02b5eb765850a28617b67b75c8f25283d672c5d59d4c26e0a105f471682c9f2b3a8286413309e4d85a2d7dabe81bf715c97289fc8f16c26993cb44b4741cb05454458419ab0bb62f8944ccfaca2e572819727e5d33620aad83f760b8a6f664c38e3f8863bac057b0843d4ecf16158f4e7e254b0c24e2abfd18a65df2979f02877be122e9a24247eb1bd54b938544d893187272b3801db30e05341463d2791fea1fdc6a002bba991144411559a60411c91444fc350b62aff04f1540bea0f09297e9c4eb90bfaea53c469c48905bd98c0575727b21b5ddbaa65682ab4a514e8faa3dcc26cb146f13931b8f0d2562db3ab21071903ac71126d49c14200b95f997caa461a375d83df0f932188f07f79e1de60e729288f9dc682a3c8a1ea0b8984ccce631d39d1fb6916012a070c96357b32035728814fcee003620f24a3e75cb88a2feb0fb1989690855c5ecdfb105f70ecab572e72e95626816bbd710ea2f1dc8eb3db7c07e8ef7902ab7ad07c74d90cd819b723d79cf4e386f56de66248ef31e85d97a00b39dc34d3810c87135e51234af351765cc19551c1d9433b24823f966583f2b685e65d07f2ec58d2932b2b66b360e72d4abc8c50d6ea5f76a3c4b28a66023d306b493614706f0dfa3d0eee694d9640ec672df59ae653ffe6d27399b12004b559d9a8e88cfc311659c20c59070f576720ad67a19eb81a1da6264b4c2ef8ecb44aa5624a2ec1cb420890108a68eba4f7244c0495513b8bae90e8ab2556454c47ebdf15d4b24d809262e343c96fdbf12724b36962727ffacc0ac9664b95efd36edc7cc28a91e585db3fab5a49fabb5d0728d2c6ac11464e094256e081644f9e97a3178946cb84423ffea584724e82ff6257e1776e4f6269875d00d938334d0a5f8f98c09316e388a3b5cdb25ec0f9f567251fd89967bf7d408a587c966f0f627c7a1d47617f06444770e780a646dffd572b6208aa5471617c420761fe3f000eaa0671239e48e2bf325f2f6f0b4db7b6a547d8e01fa87b8f94ef8f948b25e2ce860768a2b87f2b683b6c7eeb586156e1272b43655c987d567281816e4dd92ab7ba0e6986a5c624f16dcf1214704be795a6b1efcbabbace8de1494bc2fc3b42dcdc44dda671b64567907a1f3845b3e4ac3725c9ad3294e51d4946c5469098dbe2e5624bf2f37bcb9c08ff6ee5dd2cad23d72bbc61c9e2eb54b96da853e738dd92e822467e63a8f03a8195c3a4ddf4f27ed7225adeefe7d09aab3eb6263dddffa354211e499e19969aa0eeae1bf045d66507232b8b52a09626ba6632c3dae09873135e851570388dd6ddaec1b76eb003cd75e857df9686be552d9aed82505e2e1aade01b2768794ec55ecf5c3b244775f153bbf81b9d47e4757f3eea19987934a02920f7a8549836130f942bc8d827410cf5092ef36a8c9014333ad69179f086b61f1bf14820b12a76fd520a08b13fcb48c7211a42398ace24e191f355e733705016d46d40a4767bcba1cbd3c309b70ab8972ddaed18447852b6042707cf469989d08fb6655a6f6313e2d7561f0bb2dafa07295e19d6b70b229413a872cb7749eaf3751381d37a16382ac25ec83ccfa2f5372f665a7ebb1a5590b96b86eaea9c75c22a3b7e3f4b08de46b3663d4ffb6412434e483f1f1e77749271784f803961ac92bff3ea55b781b982f30de6c8d03c24a726f32bcb0bc1ce480e4f03f1680806ff53f22d9cd1d1376bef20d2f9cbef62572ab0749ec025e1dd49aa01a7a83dbf1bf5695408992f31083e4f22fbe365664720f505e65b52db7b239e8095a2cd7d2b1aa0e18b7a6ccdd8cfc16cf4f3880d02088c70dce43d5383341288a4c1552f9cfd608fd5e66dd99d5e19a42855e4e2d72eea59e6a84a03a8655dd02699113f76a9bab3bbad0bab7b70a12a56df6581c728c461d5653bfba07356cf72ab381aa6cf235f4423da592360c13619537c2ce157c4e9fa7adb3c1e2db6b7d43aa38e5a6594044776ca6907f303b2d59b4afd572d1a0cd200dcc546c34927ffc2c0f885dc5f234816253d0ca45653c3aa0418c726ded2f2169fa0f4724ab90d0bf1d701cb0da9b9d3610d9d847bc7e9c3705fb72279de2ac9a75e744c354a831bdc05a16d0ec170a26918b6d3089f47122ae3f370bac0ac2268ae42caf01468a3e0f4f92048007d183d5b50343989911b577b6723bdd0aa6c0e319f301b8087803b62134541c11767ae520207f6bf6752526267276a69c079237483b0e935cfce8058fe76d8e9d7d8fb906237faaae986581907256f2aa5c89ea0bfd35a18b44e09b19e4ec87949a45b0862f0d6752c098455dba020000727a8d9a8d38c7a37c922450a7a1961f138abfa25f5da3df2b972820715fa5ae615b52974d4b4186965679577d75d3fbb71c5a9e0fab525c06f2281b28f104ba721a7daf9a4176c389ba078b2374fd4f327d38c6deb271984b36c8e5603893952d82aab1b293731df241b99d836a096d2cbe2d8804eea2761a75879d07cf101a724830ca2f09128e7a9795b729ee71d181452be048e795c19aa425589999271e446d677a7b5ddfef8019ee5cdcc304fb7c5a44dd782361a8722bfc2dc21cd53a4d6ec82b12ed88f6d107688686c720d16da0d69110cfcc2b7e853e720123fcf172d55b6a850ce12056f3e26f4fcdfa049bdfd190e06acacd7f23971bad5b054172293bfeb5ac11337d7218edaf7b8fe04fe70783ba3aa8a0a0efdc2d2e821cb0728614ea845e13242336c1b6f1fa02855606cf8eef8d5455425d27728114a8a472669160570456c74c4e5ec9382b3bdcca861b814e0f871502165a071a96b17972332b03075bee51749d3c37fcad83ff3cf2e16f0ea625d145c6657e6399b2727270f3b8a881d8666cdbc11d08b7f37d569c581fecfdf4fbdfd7daab1c35b1ca5190d967611361f3374d1b1e0a9b09bff4dd49504a3b6b6cff1da65800a5e43f726ed789e916dcc50148b83c88de5f3c976ea020c9d3e77bd0ee6668d769bb0d129a63d404510152224539f28f4a9289e7b3c043ad42db2ce6aaa9066b41691b257384db18595ad82bbda9713f54fb9238950d00b5d200f03e5fb8f401f1c62f59898dc86e9f5ac4eda8a39ba39c116da2afbd8bc65a1bcb53c9d33f6f99c877722f8bd223ae0fa5923c5bb19e6e7c83c03751adcab260423aa1c7504422693672475bc9e2ebf54011c206afa44fad2c4e4faad8169e08ca612376be69cb345c72eae82cde3e0b8dbe57b2d169bd94a8a3d6aefe1db3568bb10762ab52debb2e7223af100ccbec734a635672db7c4d8adcf26135ecbbdc904ff2d645c0e13e5172cb8ae5dacce5912a987d9a22d6c7bad16a9a5c46bd31279d05bc039c39c9117233ec1139a269c6f7eaea273109c6f757e0b9a89ffd16d24649c48b8876d575723128df0c60e5d682d37503da68c6045b85c84b572b440e2b57e4827df6e0bb7250b36ad31e630dad48d99bc375ed8b294615f2fb391316a9b1d2c5f520e46b723d0da1d877c46f1a1b300d72a15fc11f26b573f84618baa32ba55eaa2e973a53c36eeaeec03f4057186c2b4363a627552b1bcc58874282f850c44d7b094a0772505eddedcdf978635c8ded355e291e591a2e403f3f96aae7f97eff3e63675472542706b940e3a6260c73a10e46a6d7b5600aa90a24a9ba407c6dbeacd55ac831b8500c16d23fc98a539276807ff38ade4e004c2c8ff25f4626a8e5fbae7e8e72cb31ee818febd127c9539ebaadd639fdd3957c4c1413b1a1bcf810d969d76b4d5cff7ba9ed152bdb7fc363a90fda0578dd3c81ecb16a103ee5f6e2a172969b72d84bff931d354e3c583feee9db9a1642645f0bc9799c9b2a8b3cf1ad1e5640217e8f68a0a2c6bdf63f1aaf16752c0242ad64fee0e70bb600bd623bb07193f572c39719876a3aad988adcaed9c0c460edfa09709833d0a6740b1be6c256df8b72d1ae7988692bfda396c9c085314a784ef8b3a4f8f6370015e85f0605d55fbb1860cf27f393f055fa42dea7464b99ba4e6b3aed4337c30ece648cddb3b54c917299a4c54ace4160b7fba047c8c4ccf5ae3b60157dd7ab57b535c8b91489e76e663efd1f632d0d08de7c54f0553050efab56ae5e796e71c2da9499ee0cef4c1d722a6a9d4bbd0b7033d6708e6aad1b4b555ecf691e383fa42f835e0fe12e27a572f6704d97b555753d239354c2f2954811253d977ce1e3caafe56d1c7a8152fc3396d82349a6e3647092c3dd21556605641e28630b2365efd42d5873cfe8ea496f0b4809e38a16187f1edbd32a94cac9ad207a4ca2dc29fe8058d17461b3019c5774e5cd22a93295a42e984fe58ebfa2f66c2c3af11a92bf000468cb174cdbd77246443ff3278579bdb78f9c6ceda0db5b4de6b9d9ff72a268d04c01b7df8d4972cd216c49e241d7c654c5c39ff74d00f76ff223fefe8b22647b8f656bd5afb472f22623ba153408fc076eb7a154960b9f343e01de8216ae9ca27fb16feb905f7269702693b28a2a55cd2e0d08a87d5d1ce81300d4852d4e9a764aab061ceb0805946bffb3cfb8cc08b179524dd5046a54ee672e457f151d23daf24e2ec9137552b90f22a29701fda00e7688782410063a7011252751fc0f6c0a61b2745afc16720b4c2c7cf2500fd1653b7cfa3fb8febab55327186ad868d483f1ec67736b75729140f9dc28dedc22ff4ec09e342370846873dd28b936de425a6cbbc1c8448d7200ca26d8720c127a8c6c449e8c5754cb9011536d94a9dd476bf17a33e2b737723a50d50d8a3829b50b702f2d8f1101a8255176538aaac3171c094386061d276138ebc2ea8e1b686788d75b174b2658e61aa53b39e08d7e7ab549429221a3f772b796f5b902d26f8ef3f7aa606b3434ed00a04befc98a6b46d25e329f2e762972c77fa3ba1657abc03270d6f10b63dd2dd3dc5931dbeb2ef6841f9194d90d2672a2a90903e5749bd102ea6aeced97c9e6aeadaa66540eb4d398071a080f666e72d631aa9ac554341bf7f26413db9a39caef7257c84dc7343ba6636b6c3781c46eff465062639988f78d2676dde94dd64d692a3dbdf480cdec124e95bf884a93296ebbbcd85465830bb426e07b16c78468170b0464bf63acc4bedee5b70f8a2546152e1ed99a4c69b94dbca686c2096064d6d3650dc0969600adf63a2d94624372041dea38a44b7d3139f1a9d13f794fd6603c72833c6b8998156a1b3c0fd30d72ce9c783ee53b40bb7100271d263ae8a145b0ec7613835618c02210667f7fb63b4cc43c9cc7972c9013b6b764172b42e7eb4b3441117d5e8e72fb3ddfc1c3c11e90a2b8f4a02876637581a645f6161800bcf74975016decd3ffe0ba6b54b0a5462374dd8f5109c39a4ae8de4992b611f8c0a95b6a40a560057b042d24d5e2ab72ea0f0fa6b47a6d62faad8f7efcde19606e75b9d7cda901c08438a427bb81473c97a94962aa57212c28ff1c131c8c4fa326b6db58b6eb9125aea1b85eee042572dafa3e4f6dac5eab19b5bfd4806c7d12eb2e5de844531a05eb56a7e9e7cbc172570953cd8bf198818667ddfa493e3d1151be89e76b956d4cd5194c0deec5cd263c8e7df85180ee80c921299038fd724aad83e2686c8c1d9b0a2441829054a25a61253482ef30ef508ec37fc07a9fa24f6e6772a2cce33dc33608db7d4828002303878017c529ac1dcd8839d536bcd5621984e9ce31cee9faeb9d6cf2c35e2e7293edddf5b55d063cb2f54aa146be75c540b3b027d5e77f7eed4098148aff17095a79560618cca45e632742591102c0bcd835ed8c44eed7e17e3c1e5d319663722c1c05f452d7eac880246cef084b794d229bef7ecea221af1b3bc07462a852725546ae2f77c5c29288af69e98b972fb303458f6d3bfc40f38874c66c514c33720a2d4dcc95a8c6752806600b75914ac9725f36b572699988daf66011409e9e482e39c3404c55b7a5b8a76b4c0e5a44a8fee25c9aae1aaf3673742c041420bc7251635fd6d0e588a767bba055751605013ba3b5948cb1ce4e20acc807f3aed830b4aaa814c51d7b31a780041ba2a55d42b75d035c7a06dca504f68c4728b8cb43b0f693af4ec2550b8d116a376c91b5061dd18e1c95c786246c0ff036b384dd7284bb31176ad6ebdea7e43c3b87645b30dcb1bdfe0c4dc31faa0c1ff27e408a1d0be4b7493f0cadd8b95314986743144e1a337084b6b63fbd92e2c9c8eb52fe728498a210b53dc9a8cd823bfae2d1ad65b3b1d3b3496f30aa548f58536a32215862c97c8fa18dfaa70aef0d6f884bf73a969705f12686dba73d132e570a7ef972b83c5f9310f1049e36d77c36b17efb6021d81aed539d6590d671eb6e65a8d4722fce19d91b18692c02045a80bc8e63a2cbb5d550ca32099bcba0bdfe1bd96e72de9b963605a49cb2f29ba6f7cf640a3405a6bffdca3b7a0b4dd735432b3a1672c02e950d49746d0604af499837b88be3867aa0a821759ac4b9c4d07af614f772263054022a02f998b72e6871dc8562941e1ce7c18558bec2a6cc6de289ba904d42eb35c691d6ba39bc7b0361143e96d8dbe2c37a665f284807dc988a2e2299724724ff2cae370baae54a1b934530d2b97ae78f0b344b943ab5a7eac11748e97235e305a8764523cbd6ac596344c81a0276ee2774cd3b52af983acf14cc3fd772bf5e016cc54ad40009d3290ae3f2665c44c1bd33267dbaaff0e8f12429f54033fa89607f82355707c467361b3d78ad944346f6e61b6cc6976bd6497dba1afa72bb3bd40fd82c351ca3051184899078e400e186431be2e75b89d80b4438ba6572cd96bc00331ad12696f2ebe0b738d82194466cec6b1c2c03258e5b5a7ecdb64c916fbd3a8df36d6b8c1cb44fed6fa7650228f222d0a1600c68fc8de438f75b64f0aa13b7efc5381663ee20c382a4f49325abdc3b3ab8d8059e6727b35526b844319db22d6472daef8720792cea622af521ac817cb997580aa27a6e9d6b1c6c5fa08265b918765d67f6a29733634179310bcdc00f032759e8f6cdf2739ede80721d0407c516d0aacb962a2db87191f1975d7b3e16d68a8c36c83ec6496821347250145ef7ad8a7b33c1aace23d833e497f9169cc73dc63586bada697ddb8b0872bf91c54a3272c2080774b47dec10670709cd51f3aceedcc642b2e0809247167252995a81538c18324f5d8d7e0bb5482349bd9cd0d3c1c17e57a4734d4d5d62241afef6127aa9317514a6f6e9c31cc325a852e9992c20093cc08302066a234d5a115803eb3411c33677157cbd6459d39f93063b2e08533d6d6fbd19c9369ed3726c30616059e383b130001c14d01538e4a99fb8c1280b968358fbe910f97e7f72b06376fb1d1cf7b2cbf1eb2fa15fafd04012e50b7f14bb17aea85e0feb8dab2b32f1c9d99c89ff6efbe6512cda15b766dbdee823327d651009e66972f80ef872ef9cad326ab20b76a1200bcbe1399a54cc526eb9de7b2ccb2651ef17bbf98e72791be081607525a326bb4cd21c4255574975b34468cec54805afdf95478904209fecaa250c5c235150778da1778720dd46e66a73db1a1ceb567b05101dcd4d7256fb3f54fbda45ac3a50ae030c173a1058090ffc540d96d4d88e0926d058ed066de85d34b8da93b881a8a10cb0591d2fc77b1190eec456a2b9451618b26208725ed17785c8def89a54254641b81a5f70079960233fbf2aa7dfff365e1a0035720c9f94e3c19073c28bd68c7cd8debca8f8eb0acae7447daddc04fff48718b6235b77937fa5f7927bdb33256b352125a2e0c07c16aaab6d3def0acae72b846072f25beb67e3ff7800cd0b6c6686b29782e88ef970aa71db4ed1fa70b83bd20d72f179676ec2437690f172f7aea83e0c9ce17f9ce04d65f016084a3dd4928db372fbd1d6db4d8c9cdb5ec1ed6f6af7df5b8dc201543e15459ba6918bd4beea452fe16db9db930f91e310b61dcdc42fa46cecf90689f55b910a0f0bfcca72770d0fe15da52386af0eb017a5da4847680d869fe5cd2285969215b989cce46b5d9b529d39f38f7806c743b2ae4cd7c66df5ea812f35a393cd3de2ee9b4d15ff813b7218bf783047c1f1a4c7275fae8f4ab4181d4f17ae2a206a7582d2e883f501516983c9bb62c9462148da02e59694788e417e173c59de7cea2f848e5c6b15b9c923fd892fd7b22efb37cb44b35e0c507b8cf3da05cb9742119bd47e347cb554a172180458f368ece30b1122ab33d93537b97b41519b9ebc175ab757bc95b541ca727928e3577a9e7e53b5a084ef33f6735caed682fa0b8f185ea7e04cc31eaf5b014317b4b265ee550c90fe040cb1d6d43a59db49dc0d2ec75283650d8712c4dd72595a7bd23614c14e7cee3a4ca42ceefd08df846fcea47dd6b8425d7793d4ee2235301a922138d4dba2f5021229c615e0da9f54db690998bde168168259d3eb1a8e45319f30b7a9af2eef9d72a6d42a8d6c0a431db21d07bf6d2c9df25e4d43722cbe0ef5c76b41836a883d9ecf3cd0f0363378189b21ad4a96e1ad491e3d3772253caee936df9b31b26c6361691248f895000c54ced8c75f0f6ccd4dbd87cc05e851fb3679c3d2582f42101f683ec0e52563ab8bde31ef6c8b6bb2ea7556d813d83b81b75d3c3df4e0f0807d9cf58e8da0ddba74f2653b9282be93fbfcaf4a721441ca55b312a5922ccbca2b64f7571d9830c101da525d0b08740a3eb7fa357256710119f94217e3ef007cc17b18d762defdea9f58e6bc1e0231c54335440239629161a264a11eac8ee715db66ce0ee602500f4818c151674cca96a3f8a6ba66bcbfbda2f9dd8e3495076516745d6cf82c3a0ddd58def253f6b0db87999b0842c5545764e595e1c3ee53505df778e31e7c8c1f0c91c3b9062cdf75afb1647072737450b5d069b129f8d5079d115b9010b15a25620017400e4c5ea5d43788a87296baf519ca44c0a7f4c9be443e34cb1bbc00b070de926cfc98990957ea881d6d6e7e819339b98bae34d09cb8faa975bdc169872bf433e4cea76df33eb4a322124ad551614a00ea2d9bdf2cd1eac39a8f468f19c0a3134ca590f9d642a59fae007d4787820002cb4f0d9c86ed30986560848d54cc5a59596995bdc972d139d53dabe009a40c03d2b4c0f4dfe78ad0cea835b4f754fa22b7ccf62497f2cee8f403f1fb8f350c6b5852bbd1505100af8efb66950cbcf8fa11545a993308089c977269919b4350fd1f8675916ae9357bc539d14909cf2fdd86c0ea03969b4fe91172de0ab82bace6ffa7e4d99d4296080cf61bc68b115e062f26c58ae24bef1aa9729f7572e9f6d6f636637e52eef3e52e4a96db76bab547ac96b68a80869f7f633a16272da7c169787766514db648299b8248430c63bea28b3cce0c74046fc1797246ca6d6c7b1df13eb3863251bf4dbd03bee9605400de741cd43f4a1906adc61adfd292522c256e149903efb1ae482de6e601570ab27f0d5ee81f53748b949d724d98dd5263b5f52950e00c65fca79bc9095a568877955d9c4f06bcc8f0d9c172ea50b3c5624c39287c77fe127889ece64ffee7c7f93ea6423b5cc76acf62d823c4b4c82e06945e2b607aa8bcbf681bf6d235183115480af2e1b36c6287f65f410a6defe7a3490d1aeca1a4379045a42f22e0d0d36bbe25d9400c9179226753726f4965d373ec82a5cfb387e2ca8317d508bc56e9e7120ba35356057d00f4df1ef4586bca887de49ddacf42c4c0a96ac94cbaccd127fb10ca1a39cc6fa9ffd272e4e6fb2bff4cb40c04917d5ee0d61eaeb5439ef74cb0b46b1e5cbf79990ff572d1e172017e2f82bb0f5d21e39610f0522122f7a3db15014961a329cec4c6de72e5d7989ab7f515922231df325fc50a2b6c471134ab4647b59a82e71c64603e72ebb13b22d1fc6576d4a1ef699b5368dbd1e00c294b52c41efd0ef4983272f9711fffa49d3b2e64f2e61c76b93ca54db335d2ffe9619e99ba30860272d2056e72780fdf995a00912491fa9e02492bed1f3402c133d574186b225572a815967a62a467995c111002d1548146b99382d78bc53a2df91176390cd689a8a8811767727c15f88d8f76a5a1198a653bd256ce393dd72a83ce597a8dd223fbf2039c6f729cd8dea34c9e0398f3fdacdf756881c11ca091be2109327097452e5ac7f80b466fbe532de01aa7b72c8cd0b7278ef9b8fd060d171bbd2377c9f8199906107c72d102cfb794ab449db67a0edbb2c74360bbe069ad30f8a3cc67bb620deed0d25d0a83ea2c6633c89d43234625bd6f4c15d0fe0c0ca326c01f365e925a90e4745a9a4e2173e8d751b08733836f1d753728313ee31990e04671cb2506cef891f057b00f605cf018095c5ed8d8459edfa5f2e87e6f235e3722278b31151856ae6872f92c6c77b7143c991cb65018a3148fe8bd858d528fd272a1349aea451eb9df72caaca4810985174384fe5c7bfd45fd8f9a589744795b90f5d002901b80594b3563127ea9e11a329c9efa41811d5f343872f7b25e3ebc822b3f2c571d8e72b57292f8379ec44fcc9fc0aca0e2ee2c179fbd7e219b5545061c8fa7db5ef9e5db7263e83e208b4846f771b6bbe1723f157afa4537fc0ba4234c09f85a944de78f2f2417c701b5a7c45b31d145517060f3d498b036c7d6a257ea518910abf6991d728955441321154fff1edfe3bc85e2494afbf3eb299ba0fc83de01e437c8cdb1720eac92187cbf33825e3471a69c972c8108805ef598a6f2b8d21a242ca9ee487223f440a167410b803a9bef7d5edffaa284addc3759852320c1b11e57a507430bd2abe816b0e0527266fa5f9a9a2cba683537a0fb14867d9e0821f1602d9064130bc8345e77e212d9dabcd44a95b15915efff15f730694044dc51747cae2ea2721d2973f058b2606f64ad7b908e259f9904088851d16eb5c55e375fa2f1a5f25e7e96b92fa50f723ebce9e58630980d0273f3ad3e702ae049c295fdb817b6ef4d9d287ebf873e252d4106824434ff58f08e50270b0289d1e596da690759cc55720dccb1f332be7904406e8498255f975ea3dc660e04780bf9b895693f5f094572b3913d7c169b71fbc368ad4db92e1e85d5749eb2039cd30e88fcf65256785e0c03054d8ef6e3fbf919ae41765face0bd39ff5514f4e4c356c574d2484b0a7a3fac432b09a74109385e981834fbdc84315c1bf9ae874436f9365c2b609b42203a12b670ce94e627f13edff318953d4201587d92cbdf33274e491139a692f99272cfb50170300ac438c96bdfd2a7eda2c1bca7d487c72804762cae4a081c365a72e2633ea80957bb5d6adcede793abcbb1da52a3b2df3a1c55da4dd79906a19553a030e9b04b5559878ab252bb2acc5cbd61de068788a620a4fa01cc55ce6f51723da01f6bbff7c0ef3b6a0d7ed049b33402280a1a247978f34f433cd3c2f995726e46d74986f785671d366ad99efb27ffb931470414006af075fe1b570a05af03e24d3df30980c845e0e8a773169d746b635984930327fe14a575af41295106720cd01c082c726f6c0413a00940d0e5d396c8de1efb738a694071f13a5653c872125d8706b3c88e23abb7169f22e9b4036aff80872186d40d75a0fff23e4c6c72acd8a9497922b37ceab4e4c62b098b5514b27bbe1f132c80281cac1a2d84a672ea68632813005c02604698e7300cd18eda5329d05fd58c771c6968a217fa68725f5f0b47d13d0d76604e3f009c7e535ecee170fd041dd2d2e4e0ffe9a7e2ff7290f8be76665cd170db06d4cc8c092244de9117fc6208ddb6f8f42da4ad74a2726b610236340366739fbe6f5d35402bbf404dd583df9c059cb08d8318f860f1037a1dad41643be0cccd3ccbba6b3f866046f721c0bdc8e52d9b57e251885b1f72ac55a4b2d4da1dcd7ac3a11407b25f4dfb37e02454dbc05142a98f8728659d0c219218adc6f237a132e93f18723ef1fb230ae99f7138a175183871a888871b724dfea26f552f633c81c607d4246080b3604cf318a42e98196641705648bf4931d572374625b3c6874fcd7d319e45135e56b4692a8a0937e239c9e036ad7349284189655c390431d506311bf68db4f23d6d8235e5462ae1c73613c70d6b65db20b44820de1228e8ef1a290a794b9fa62b0fa88c4fd2696eb3b8215a21f489597223fc38150c9e3e57ed4c12d94f9098f5beb29d80c2959a49c217a65f8d86f9725d9c941c47fe89e167d484df31731e3c7abcfbc67af0d36bbf7d2116dd31e072d19d57544a4b3ac4c82c2bdb2c6a5c6c74d1e9973d4a6101847128db686032174e13baf103bb21b59bf981ed933fec7aea9943c6444d63fbf24f0b7dba8764725433dd97256252f0b4c513c7e2135be221ac852c351dd7b5801ab443e68dcf19d642e16c055e455c2b24206da5d72582423b876a958f255d80e9f716df861e728a34911576da27fe3b78029acca22358d65768538170fbe528e3f9853b891112fc6f268469189d21065e3d6e3e42be7ffeaf8fe23ee32ab8fdefa15c83338f720d46fd265e921e04d387de7811430f6994639ba6a0da2eda726f8f4e983d7f1934e4d06d9b13ab7003ac9f9673143f11fea22ef0f9870c9ba50d1e1d98d23472f1e46a6fa3d026f9f94a437c48632f0d25666ce701424f71f2a6858c72839f088531fa68b33f2610870f2bf53ae5bb5d4f83a347688f50a9aec283e8c7af364cf5bee5175a53a79bb6e8804f1d0d70e1d45e2228a26f3ee36be082abda60a272fdc7cd1455ce97c2b167ed3c12eef43c84a4778f1b2ea4bac38905a60feb9072962ad188bb84de5dc15afce07d0c992415f75f1264fcd46d94d47c2ed3091668b887d09d65aeceb647553ce3f37a885b2eb5e92433e8b927ccfbd5b27d5b287234b832e01feab8dd687687ec64331f0b05af9224d32ae0a2b1b751ded06e3c727582ebb86701deb1ece4f3e2a037275b891535c623c7a46ece61ff378c642404969fc88f07a6b89898563d8e8edb7e8aafec962f5cd52db926ae4cd1fb895025cbbf31df377fa0eb86fb38d7314f42c25ccab2fa621f13f1baa70ecf088f7b64aaa92fe14a1ddd3632797f78d78b24ac7d394675d23c3d27fbf5e76079651b72b49966e0572fba770a2052ea54766bc23599c5b638fd1657bb4f5ae338fade7291327d7a7f216c7fab53261cea6784f9f1a531b64c792f2f30663b5fe12e8f720466a278f7330eb9bf643ab244e0ef9cd1e9958175db573d93fdf30a06285027c52397a4807e967e3edb9c201e5bde169a8570accd31e45f3f0610fde5951011965539d47244a5879da6c32d2cc03834a663ca9b4ef2ad477af0250d49e391725a5e3a39bf0a2ab28cdc03b80b79afa625d87afaa4c91a885e108065076a3472a4c293f62a8c7d4990bce11ffe2c5167f2ea2253ad69f114c6be280d9c0c3c72d0901b1da071a7f1a59cd551a6daab553a61efe662f37dafe01abb39f3924a72557cd100d18df168b35674e79b2be77f73edf4cd060006dc4516adaef32af872f82197caa47d7c487293f1839176ac67a0bbc112cc39df7981be4963b8884e7278cdbbcb8c0832942490eb754155d2ad5d8658d20070f09edc80c180e51e10612fb15f29bc6af3d2b92bae0d9c1749b486ab9c422631a8cd68f08a7c70460f720a30c51a7bcecbc5b1491180fef771edbaf93a0890ca20be5a4ea3afea929b3b25ca7574bc4360c5ade6cb742752beb0cb579ac5ada7d566cdf3c255221002726008265c9a07f95cc334561ceea4d638aa412620f6fbce3a5fa12e4032345b4d9702276a9611bff65a4cea01056227828b7d5fecdcc13ab5035e3d10fb1ffa6e467d64658e738e569ac069ed975759a22430ef1fe4844278d7a27f31ab837772ce52d1d49942c42f537166f1dfa22e05ad80277510a734ee1c1cc4ff4245e2722df6444c34aca54a92d4eaadc35c0b3c56a92966d22a690341951b13cd34a82dc1003d9e33c7f71f734a7a86cff3b3beaf80b5481308790a9abb6d9a671429422a35620528158ad8bf1de6806e0c3d231e10769757a5d23cb4a04264d7199e05a18a49704358f2d962d82329a0932fe676e784c52b70d90430a7c4572dcbdb02031c7033e29b139fbb57d3fc2c38cc500d5b783943d97bd6060abc4144221f04992175a7beb3537b8e3dc71dfb56d3899138b408a0349da5c20e17b7e001e06daf6b58ed2677bf34679d9a08f97441edae76e4f9b65278295c3bbaee52bbbe7293e2a6ca5568e7c76bf2875b3e343e1f411476b11d10dc5d7424312dcfbb6772f59b204107a9b90cd1ca76459d1c87d5fca957647aa1114d24837f8bd3121b720a26254b72020ec9ec2dc24f69d903c5cb77e3c7f55b61248a6264c9ed119972f31a4470a57ca2f5461d2649fa4429508b8a955fd840688dede46c39c6ac5172a29de9aa15facd50b9f3cf144619354eb162fa3b028e7ca5b381d312ce927c72b78fc5dd9cac0beb752073e6f4389d8a47bf4334029f27f4513b9e845d2c4a60af0a549ecdf82c7e3e60eeffcd311519aa3f2def0d5992ec2c4adc8b72c245354d29cefdd636ae15ca6dee40c67dbf5b1a0c02186feeede465cef0d8451fb920b1d2dd6a0bb4edad833b45ab9b774f0d8ba52d5970b1a1b54f631ee1c9530e72e391e850489107e3d664cb5ede9c719fdc01cb1e8ab7737f48637ceac1c1d072e1dd273d34442f452cf19144cbf8162e56b902dbbb3b76b53102d7632e5a617273fee7eb2284ad3f85587947a215e5f65d5f87e45f958a5116da62616a74d858e95e1eae8b046489705e16292926eb4a4a14b949c4e825f2f8ebe7e3caa9e447948242342ddb2aba9e47e7dbb837cc22e1fb24ab39f2092d22cc8ee5daa52e720b6eb2c77dd0729e9df30e0f1b0e28c60e38e71bfdd3c270c09987a818128a72692b1820ec14e5d8717cc7515d44b5d8e3e199b7fa0bb83b99703c3bf6c49a711fa7595f1b638d1091e2ab3eb4bca16fd5754194d34c0e61c70de56103d7210d8dade5ed220d498cd4b79fd42554a99932dd2f513bcc0bde8a74301590f17e7203eb00b7922b56e457f8ab2fc494599bb2745c3bda01780ba0baa93357b7e345b4dc3dcb7c319e618abb8ba32460ca2e227e1ce5b052be0f476f5870c7a0aa72906b37927fe1c6028d3a239acfffbb282f3fc60f1e202509fac56b54838027722623a3e11337bfdbee9c361a423fd667fa4afac9ef5ee71407336cb2bfbb2672ea2bee8d53c4fc29f9b5ae2666d6fb4971f6f875f5a9bda3173840bbd89de75217a6b719eeb625857ec9f5ea2e4d2d3c7bc089920f9567f7cd7aabf88832812c833b4ff21bfe95d38e58e33d968d54bd64db0718fef5e182211cdf69e84e2d4179f82b9a1deb2cccdf604cf00f98ccf1a73c3734ab12d7d3036a50a9251aa672c0976ed27fd7345517c3ca77961615a4dbf664a149bf38d8ff0b36c6f8cfd74a89ab5a909aebb26584ea74ad58c106b3f3bd88c87f05b9bd8b621adca294377243cfaa76ad55c5069843ceb0fb711c9c4fbb81790a2bbe4c5a74e8f9e8856e5a88d1d92c989e7acdafa557c497baee716b7ae7eb8ca112d5f94ec44d456fec0221334bdfc0cbe59a4384c57103850860043bae42a8c3365cc27bc2cef7f0d77239740373a18f4f354e7390c134bf3cc59967f26e72c9d63f9aad1f9d8a456772784527285cba43cdc891f5f267f0b46e70de1fe6995e85365d17fd3c15dad72cd0925282a20dbffa0c7ba6d00723d584bcd5a3b6729b4d84be38db418ad3fc37e3c54d3645f3ea67299fe44ef83d9d0863108bb788e0a8982f6f71f651cb9f726a0f0d2bf1b8f20050b10f8dd5868ba2bd05f27d3e157a646a41bd534b1a7510b62ab8d6fcfcf8ad070f14fee80297dd1afa839ef92d83ee5521e4f9025c5129119f3ee95f406b5ef410666aab01652a7b2f49a371a0ec2ef3bc8a4abf171748c6876cc53d258e6587bb8927b0a58ae7c2d6e8f5f2360ad67f544b2e3fa65472ed7959de49886f572f95651f9c136a72b66107f245e97bc36dc04da2bd366a726ae48fe08901e420d56944a8cac2df7fcd8a050c5a356c685d695e8da8bfe3522ff8db2aceb5c7bdfb2ecc02261d294deba3f13758abbfc4fa955db261b26d72c3162c6040a886588a109fc8563c192dc3fce4486ac430a9af4900680a9c925d69547f71c7f309fadb02cad097a1798e2a8d145f5fa733d9c60690769eec777225317e393c8339b99f4b3a4e68a0fbf1b6f9e2b334496899228eed754a545b72092243c053ee156512c201210c037a79a3047a7b514199c83e07162854690c6a5efb37af324780ef65bbd6a2183a6474fbd6c5b88257a2f38980d252dac6f1306f54ea0bb483d890f5bdf05a6c27f6c5a56c7dba1aa0bb22b0a235221bbb4672303ce8458847d6a0b763370a1abbeb2f0133a6ff94171459b1aa6cfe3df9e672cbe7a0e6928423ca9e618149c7e4be3f857130e67e8a62f61359d20a78d36f194425c116f9c687288b3d6bf36bcfe86e1bf84a4c2e14f6cfcabbe780220e8772aa3c06ac653cb52e8089d7c566711fc499a90e6c4478d41fe3064d1059a7957263b99ccbcce06fd8d466855bb6e0a88d46a29ea8db29a5b6e11bb1f39e1622724a37cdc4b1e2424109bd18485004a809dab6d808604a471bbc2b04e627462172cc6e5010c296d3439f4a782c3dd0b9f260b40eb74d070eb330f3d01402cd3672d160b031bcbaec624fef64d8ab47b569e538003affbcf8ad942122e7995d2817d6f022e5eaad18fd61c3760a48a757ac67bab1b907b70f78e9fe3edb8020a172cd32e6a7c7757d3387620d57f0a04677d258c5b27a04788524a9eba4baede871a1a16672e5c521b2449c1677b8f1520598f0a27471dc7a615da559ce7392465d3287a2d5b4ca6bcafeee0fdb4d628af364d113f7c5dd3d0c2d69e6841caaf72e0a8ffc564b1d982924eef1da342ff3deb9ecbcafbdfb87350399a1763517a4723ab008a81a072ad4f4e314f946e1f63d62fba7b8d537d43d8027514246a0ed7204e53f79ac8f46309d3b794a540c9f3f946f3d22f9f627cdfe53e9024418fd7266523b3619ab2c2dfdcb6e6f357947bc43e5a558226abd04bcc639139867e36b257ce5a5de93d6429edede237779337813ae5b5db449ecbf3245d966a518b572c603fc4640845c87ad3aaa7aa3629e57eac983ffcf304e4751270018e480bb727bd4d80ce33000781b0f836e3572e58435420e1157944e2a0374ca44b1463372df466a6361f7d3b6da079274aad0afdadad4ae8b65a5ff384e1bf1b6634f282ed21d1018a21b22ff833f52bd9451c74f348d385e0c3e1211a9656a7fcdbef672a39b912036fe908e3674c4b68139514de16af296767442a13a6a4395ba65b672c16f08ed41d799f7bdeaa8d68918c36a6a02fcfad3caed5e11b5cb00dfe38d7250a1ed3fcbd5de77447bc8413d2db7f8abffeb5cc117f5e0a12390aa0f2d78726d757f4d31173e100c855845cb2fd4c66520a945befe467842fd669693ea7b5b9d54694acb8182be31bdd9621b0c0ee43b5a5d0661bc810f3cc6057d8be71672431e5bda4e1e7fd18ecfc6a34f942d7a3e24b2e2ea113209d50d1114fd7f9e7283581dc6930338a30d75c6cb0b9b9e28c627e0c5d14420f4a62a9478ddcfd73577e379c01318414d5d070a05e618be06dcdb2e5538f2d30a6852d18711b37672262d187a0c37b832af3cba2c2596bd7da4c09782d1be44456e2f6c6f60a718072f688397d48a6b14a4497c06b0185ab5facd56f08bcc5390dc193984777c7b72a8a391edbe0582459705d1726ca1548412df06b72f496d58b36978642702c272dd3d95c04be0904e9bdb5e1dd6ec87238011f342a3ef04711c51edf9b2614972a099212c9f37f39222942c409f4290ad24e852927de58f2935125da3b550494b6be10257158095ca44bcb620eb7fee995fadae61bc58f903e2fedc90416548323fc62ea8d1a484476e7b4041543349c62968983f832c9c58968153883c969e33aca9ec87d3c231068f7039226b8042030df6fc6fdc608ed887087e0b4322274af79ca41dc96766ec9e56f930e626818c2b2f0ed9206d29f73b3219768dce28724196e29cc7e55be02f8ee704317a3ca740912307a0169a972aef0497beadd072e609ddfe35c60ab531a9230643ce0d31596df89f308aef584a7fbbc9a793d343dc871e9e23a0b71b19998b19fdd0f0a13ce2df2b2312e056b280cb842b5f207283ea4c2c1c6db04a810d15570379fad75cd946138445134c1e0afa3c6be5af1b3de5eeaf5d137348d951c67a32e485b40e8ba85b52884e5e9e7f392564e3be039bca95f818d6b7dbe7199fd9dd96728c0246d1360e7e6ff1429e0329eb7e6e725cf82a72300e0fe8e1f61ce716184e94cd67e6303c966a2413e74c824f429472aec13356dd7ea6ec03f2c8c98e27bf30ffeeb7072de18de4c65c7e5fea7ec972c9fbc24ccc420fd685e93bb87acb89b0dba630ef39b72709f802799b66faa41c517b4803d732e65a89c919d3e339a4d12bc3677ad7972342c01a1852451fec06cb4e685d713662734585cb69042b08958ac1e2753fa47199a479856d19703e72ff0ce41b48086a20a9dc32e2abf865d9b7d1f11e641cc725dd15f929bb43bf724a1fea288d5de17fcb9bc9865884acb4f52b9636d60d9ede8c01595a085f62727dba923e5d7ed9963ae6874934799412dc68caa062ca3b13fe03970fc11475727509db8e44ca370301c73591e2abf3af47708c5c5d1545413f282ea141799a72e7f13a49ee0a4b632811079234f7abe32b6a514187f41a4806a3a073876ff072322e62e3b6f20f1e919dc6a43219c5f19d70e558f8624abe0aef899191b603725fa398b22e8c908fe9083415f161b31c136b1a7262b76a79d20754a244f5a272c8da930ea8ce2f8368869ae67490a18c388e95a8ebc9396c0d7c521bcb612c1d4b5678b9cdf54d3fcd30ff307ba1e05e6cc8e09da1e4a8a85422b7d9cdb51272f46f661e1d2c879015fe0797c893cc726b973b48988fb841d9c298ee756c0f5aaeb557301388c35c17b3670a1e972048be32aeded269b3c919799f513d85a5721bf32a9591011c24dd9cfea6667da1022ec81073e826508db2ea4adb306b5617d3a3d4217a53996b978e9b26e7ffb499a6a817bc37f68c4119bcda8b5e27c87261271955d8e18a2a86173c8fed5b902710d5486f764224bd271dc6d6df37717266bdd80e16965f50a2b923c5bb9af65302a95b85edbc27422776cf23f70e4438b404d24ac8cb78542ffd5bc37b8b51d7fc8c72fc70e344a15a8531170024c7724a4876d2cd87545560a9079da637e8a9b9e6ef82a54ea515e221e1a5347b061be01e7400bb71d69816cc97a3b38203cdb8d46b9f973b0d32602141cb63514a7250bcb9c97ef175e809b090eadeb2018bd80cf3db473caec9192dd81e6f289223211c1f248db0640b903dc36f9675bbadd0d466e9e93eacf83f3c0fac87b81423cbabf592cbedb84967c8b914d51d41b2382010d87a72baa3589eef4fec24e34fb58578e384231688a3e93fa235dc8b61903bcb4302944cd92471620c13d1946b2c7d6f8f4dba7c260934437a16dec55f3a2e692155bc01f817d49e93ad652b72fa034d327f7308ce5a32a70b7eeacc97f9bfceaf40c9cae2b5cac610286e4a7231c180a960f91e73ceb255dc0d742b9f9990b73bc199875f1479225291ada872283448adfb855a3b33863f79741e2ad3777de65c87bd0c2ae91639ede6aeee7253a120639ec0bca0ffbc3d15e0ac3ae9517fcff0777df64f30366d3bd9e9e1729cc57488ac67e6e56fe109501588df0ae9b5468b60b4a3d9ff3782dbd450613d5cbe08aecea432dd4712a3a28713cd3330adc21c54e82fd721857a3ffe98e1721e8a1f64b7f9c4fd345c29ff3249ec734919c89d909b1c1456d83af9a5f92572d75f4ebb30893f4806c44869901b874409dcc27fc219e20500155a61892d62259e579323939f6f7af1456f45b5b0c82bac20003b5291fe88918be9342f09e27255292302fef3073e1f0aca4f8b743e5a0d098d7613216b318c79f6aecb757b37da1f3e2f651fa344d15878b256e1624ef1d66c500904b9cd96b9fa62b5bb2d583389c9239ea1593cb534a2f2f7855bf334c279d937f31ccea82a7b082c099472a451ecaf3a6586d232df42ac40af8fe3e2df8615218baeadc8b0d68ea4632272fff560ddf2652ca40a1817093b1eaea308227a44207f2c3957833d030dacd01fd22d712ec8ab03538af4601ad721ed0616ce6f9fbe27ed7e1d67d7fb4ffff668e829992aa1679ec006a3ee839834726f117feead85b4222d2879c6423d8f317260e1d279fa42d351269279bbc172f80ebbd8483dd03da6623678580e14db7e728a3f9d27d674e19b1edd3daf4f9811fe66b932c3a80410a7be72cca4ebe312729ea8736898e79214a05941da3bac46dd6a3ad77f5096ec39624fe0667a69581bf1706f7bc9c101c2491e295240f5fa1a2397aff2a9c526462fe7026e14c4aa72b4bf0d305af4128a1d0a4eb40eb56bbfdb16bb67f82420ef81da327b4ca6d265856582644506127667d56388a88f320590737aeb80a89fa9d2ae1066a94da2726339ca21e515ec62b360a503e004f6fc1fe945d6c5058e7cecf9afbd3d34974819c9b2c31f3ac81141497bfa0540b8d4a3768ad09991a5c12a6aa823ab6a9972f40f04586b2e01cec5dc181fdbac95c38bf9e4fa5ff6363b8ccfac555169767254e3e766a60528a3f0ec6d488c116370e009f7c73057465ca9f0eebdb2beea346f39863b33d097a33df90ac670039c85974337b183fc7b0914bddfe3995945722130a0b0cb757113eea6007c8198b168cafd8967b823f2675f1d832734e7457255173e4465053132de8117416f4bfea6e55c19a284ee2f159c904717db98f2139857fc306c5836644436578f454a0f01ff736987a29a8d4ebbac5693720dd6728bf68e82fa3c373c0c9fc396cf481e95a93f2503bcbf583ca25c2314ba34241aaa480f95330349b5fca90005c62871b80be761e3a66edc6445eeadf810018b72df6044efa4f3e85618685aa6e788164e3447f22b87e7eb25f58e814d29652472f41fcb92b58578c8e489fba231ff1ac45b636581e06459324457fbfe43b85772228f7d3b638ac719fa4ad746b5d097a455450d1d088fd7cadb0262e7250e9b5683389ce1c26237e1aad091d268238a790413d59dadf14617b6d2ef18ef38197201a7fa1190cdebce7e1e97f0d230d1fd2b232277758fdc3df8f059f9f54432158d67db000111825d61196d0ba0e3abceb1050101fb76ecd69fa2255f4e67e072fd321c00b0a3b67ae55a70f26e1d3777ddca97666c41a0b3a19b87cce23a3c63b1a729109ee96496b96b3479d2ae2f0bf37b0d3db598d7eecaea78509eef8c725e62e2536f2deb5c2c7201822183c74d620e7ff307684f4963da20d93efeed31d007a5688dae896bccb121b8e7ff9f5130b665914e57c343fe38b392ee9aa7727bb108d79764f2444915ce1845e2518abd990b01b75a1af559c3448540aa50728a5cba5faf672e44631671bf0184ca7616653d60928c05982cdb7b6057717023fbc29f739339e9e80761bce2121515e30caeafc990844835d96bc3385d0cf0722d609907cc362c5965ff9cabb748a3c427a9772e3b29be792d8c7bf1a792b30eb2af53b84ea67fab41eb9e688c756d8cbddb00488ea2b0c332bf257c38332a2890b8b9725cf14936739d75fd1fe70e9bb74f016f3025e4aace277213a7468d556b5fa6ab2d3f18d6fa03b892504b5fccac0ae32aa64255d98072159b2c41971cc5b457cb4ea4299d2c51a4488b4a5689e616b244111eb085a3b756453699dd03f293af83a886a04ca239b98664222b89c7d3df46d15172fe72f2ff4883a94b62a5ef14c3b5d1434f2668aee7995e7590ed86e4547f8ceb7122955dd45e88067274be3d60dd808be46c225e9ef35de189290fafd4290dcb059d02f3df0a13007244793b809946d485eb1db20448253d4b26f1567104c5ca781bad5cec34ed9d721c480359b42ff3f6a2174c8dfee587aad186bda6df4b66a9243250930e57007216a81baacb999ca2fa69b07730d578b2b1a5ba6c140b9eca99ea39e8aabfbf72410af329057f8250cbe3e199a976924c75c07549936053ce6a894cc614665c7271cbb93300f260784b0ff8189e3ebd05ad6ef65ed1f0a2ea05adf9c6f0ce4f5b64d0d5f0c12911805ad12b8afcc3fd6a5e1ca1af82b6b2b3de19baf7520726728c67c3a871ae8b6e4e50601c265b5d9d11eb03a4994cf9376684871d90a68f72785e771656ebc7a784eafc61e7f90713d85dea1510756cd460cf35d1ffc3dd4d46b185d758d7a82e31f486365a212a81b38ae56be8e3e99274f92e330c4943716a70b06be4877fd238f28b4847bf61ff030c144a5d8d8ffcb75acf58a2c241723035cf535ba10a4833bf2c55f6b5cb89b8e7d95545814f4c1bfb35fead246872f8d9551e3ee779fdf980293ec0290f77590b45c942b6dcf16655ffdb7254a27260bcbc00dd6933803aa0b463cf8f4c2a7a1d3e2707f3228688dc6c8ddf0a597233548b2a8e201e201978826bcdcdba542dd28ca094681df89dedca871ec3a606763358aae43bfbac6f371091b62507e174a9b3df03d8423606ef24620d362a72b8c43f9ce0cf243ad5c2a6eb14d09851fbbcc7b8b86ee1522c04e78fe6d0f714fe46b9b216f4708e671572362dd78c63e545e29eed3a1e5ed7fb98c194b11b697bbfed1fe282f08f8228ef16e7b3b9a00abbe4dbfbf7cabc6b5bca183fb3b823582051138eb07a20807f717d19b96e247c286947fd2ef4b73aebc7596a2a173e0a045d0e5f2834e16702d058281a17de8db0ad3adfb28f329ea49628b7b88d7210fc95ade65f92e8cb3e0a0e51ee0de87ec3e7eff6c83caeb2707f5e779c36728353672df44cdcafbe9ea4c0d755b575cad4586b6388cbf7ca101601370e6a4dec0d267b827d2da30aface36ce7964646b024476787ebe37701ab0254afdad725a9ebf942975bddc184220e5804971ff3345e2c5fecbd1d71bd248b30d8b79728cd160098c7e9e8dabbbaaee2c8489b4066dce22c3c1af73ac9457d193bc8772e520a22c3ece07d3a985b46b099b6a1abc29e360475def002dc1dfb728f8b2725b913237c490c5c98c253e3208a7f43a2fe04aeb4c8ff18693d5ad68fbc6fa2763056939def0b88b793c4857a879ffebaff6db911693bb377bff84cd4504cf226dfc4fa39141f245425639517852ac3f94b1abe5a27d21b7917bc2c288a7883b95d475d6172e56cd2c423a4c2f3e0b25c98b1ba4e08638d9130eeeea8e7321724aaccd25538c4c8ca5184c1ae5faa397d62b8e9ea5a4587cdd55fd9712b46c72a9334bdb43b8c619d60866d75484fa8de107ab581d68378ec67eb2e76be6f672723c20c60c82c32c5f9d0a3a1e92ca3205e7f85314fd15665e8ac1d92e6c3a722a27f55709193f2c3ad0177425907d8b299a397673e08caf7651284101c97f4ba424932a3bca3b3d0060d9d5d7af21f781c8384c9e6b935b297ac868959d074aedcbcc11368fae536e04070e9576a01dd860d5625a2d73cb3767c6769220bb31b55ad768b24c1aa0c1ba8c3274b2d1432603e49714001c9e8475657f2111de72bc31535b693600260106cf9a3ffe171925e85f3d33224f570bbf08f4a1d2025c1ac9d3f5a659f8fe6a0ba6815b68cfe286e07e7104a390cee472cba90d883272b353c1d2409d96194399dface72803bd7a13ce6197fc239c610802bd52a0af7268485739faf83bde86bd72108eafe492406031aaebce577e1645380e499f6f728bde64f557143f7778a38e59d7ecd9eef985e8acc908993fa0afb0ae6a288672843019c7efefddaa6b65267167e9383e1ae3bc85ab58f0920a76dafde73bbf415792a592f3b8a04eedd1902cfae4080f2834bda92a54af0e0d7e9152c780177257a262a1ec2fee3f57c136069c07e0f0273a1ea2af087b51be7e3ca8fa51aa72bde1f76923bdf358d5db83909d6a009732ac6029049c001df2363bb17f59657283ea45a1a8b0faa6d7ef96e62ceb558bba70b228ab1d9eadde9e93e65c61797250967baccc71c016c7a172f846bc3b0330a519f76da4758695348776595ec830b0c1b6db616346b2d6dfcb00276c52de6896f1ba489aa88b4a73bdabaaa31272eb55bb9537363ad2ee27e7441029de478ddc500e341e2ae1501b6a04f43616627a2f4f4de33f0b119828f4521ec72879a46cff101c9ea48659830cfb56085a72b6bddc166de186e5be132d7cff269b91e55c50a2fd7c068d2db9db69719a37559f664bdba986dd12aab3976b1f532a7dda4a7e5b1a58cccc52a2c38506905272e6db60f5c490b0d521ef54b1f425c113d009422d2b41b9fe9f68e792ab43a0722b8357f6a3736ace9dc63cd0e30d1826470d0da9d6213bc1cacdb06d0a1e4b7292663b0338e3bf94fbb8fc3754e90bd4f60699511deb212ff8823373fba6c820ad12045ff0b5060885645d1d7c84ddc775732b6f765ec9a1b188bb130bfc2d12fcba090e447e25602b154a1e0fe1d5e08667affcdde6ab1977f67691bd3a627225b5a98be7f63915521aa63e1a5312be6efb37957d60fd6dcdd0867b8cf26c283a2982c3eb6a9f91b7573d89d833da189ff2241d81f4e673034ef082d1c195725ed223e23d9bff94377511b4dc47f1f6404605e9fbd018c2c7cc7e40b5354a67e6fe1a52434a307604188159fa3f00c29531300cbcb8c4b288e4554cf9c1f836c86c7d57660418ef1b11b05e9df46bdd38f1c11611a7a567f3e1109148c0d91ed2ba260eb950983ea13257a95a4d4c71407437406dc81ff22eaf772b24cac3723ea5852c29b202dfce6b4ce2c04eeb321e6cc04768cb21c7429a3d1acf13641e6f54254eb118d82b81145de7643f7f1ac11a5de78a53bb40c3d95381591b25728f24062b0c6f78cb7f01b6fa265454db370e35b14b4cb6ab9ae3191212c898071f585b3a99d59155b2031ea86c31349320edeee40517b243d2f8e7336f19225d95a634031cb1796771cdabfd08bd1d381a988f32226dd628ef339f49cc85d67285349d1af1a72acf200195deafba923014b54414909110cc12389a949b20e172b709ef31bb456f12443b47f2319939362b19f4766c971be50df4d01e2d54b0395eadf65501b3374002234dc1fe9ed49dc7060319795b875c65a89b33e2b1c54d5746fa4ed39be2c9bcd586b7fcd2a90fbfb46f9d3e537a52b49cb34e618fd44b69517424b9d5196ed8fad4695de501cb02fe837363fb2409c1b99e6c6ce4455aef7ddb85153a1984149e69bb30437461dc3f80b19db4ef2172b9bc0af06ea4727d3f41e9a7f79977ad8347c0a53b449fa28e354636294e04e107e9c7fc1de9723aa44f176a820a7a15cf8d996f0aabbe7f3845faf35987d288345ad08561607249e915fe8371fe17d2e72db71baaa3c6483a4e1e3c1a29b8b9f12dd6062bea723aa7f1c582a3dd9a93775133e6221e82816959541723eb7e8dfdefd8a96a0c72306da29035d3993e8ff7ced487b28ee5f710d775532de9569afc604788f36f6e58f5a3a9233a06fc2b85af2cc40a45e5b625e4d4a7414c01def90432f7ea6b5924b4a84e57022313217d4628d42c110a1e2dd78869a9240bddf6e2efa2ff2312528e730218bb2f479102ea5014e41ade594e82dc44009ec390394141eea55358a7709ef43e204d6ffaaaa71b8c88086bf0062a44b6f96366c2807bd395a77a721beb005ac51500bfde7f6fe6ad0f0ca0c4d3b6dd17f42cccbb8c2bd5f9745c72726df2d462bf7e94bd0e35487b0554c0e1206ea7e4712358e9ae21f8a8ef4112c9d9460887d16f8347328b98fe9b21da505a801925b8484be19e90169b0ada630165c22f27586f587544e652d67bb92560fd37e5255388d5f163ad72acaff22a1b7e73e5de197043d457517139df4d38bbf406057c2843c21fc8da6bbe83471145758af0e156290826093b25eddf6777135456cddef5f53ca0b21df3f7bf87212fc25e1ce806e5d0362fe391827ad1d99bb8b0b8f4cd9394a2f25b6974fcbd687bc648553edc6efac1c0faf975e8d9c096b70d92a3e865871320fe6746d8fe375c8288d5bf3534b8574ba5fc9d757ec6146cb73b5c738f3ddaac7db842bf4f36c8ded23db86c09e35f0c1189197ba3ba712b87cb9793cb2c553059ca514eda72877aa818b2115e7e0d61b0f8a062def12a51362689de5c1aa65a13e1bac8d672b56b9fb54f022cccb9e977309b9e3ae2c93c819df49666a37852428e9d8cde3edc38d0bd1665350a4e7a06769df9acac4e5ddab5f007fe6bba67a29b2c9fdb7203715993b67c75bcd1d2f5bc51f01b77e97d5ca87f7cd146cbac01ca69cb9e352d16b8cbcff04fb70b9b1768dff1d306b85991dce9bfc8faeb9876821668ff5bab0e2cd6fab2a07e452b029deb9821fb3a770921ea59212b422d7fd7a28a7a1e12c53c384869d18e4fd8be641e17709c27cc08926af97f80add71136efb9007224efc62527246ec60eeac7a0b48bf6fabf0064c9326ec3997872fb625950296cd701820bd71f7e4c39781daf3aa80d7ef77fad04408806bd0f17b9defec53c725f2a879ed3f66895da84a11bc04316d225c096617cf8b052f293a5d7caf10323410b318b2093af5212b872ac1a44039b3a3ce64e08506bae44122847cd64fd4455d8eb259ae73500c1613599b7cf4e3a3e9eb109bd5a79a275aa618c24809f7282d89cd097bc30213fa6c5e4c9363af7f00ffa374d3675921e0b8fcf52724b723793ef80bcc88e24e78468285f6ccc7e490b2a105291572778693fa7023483722298c1d5c461d8b92568e7fec1891ff63ecc4f498091dd304b5936bbb2191772e08f20b1e0b9dd5d02f700e427231abd0260cd061f726d98f4a9cdbfdd14df728e6d32a7ab3d8d22bbf60174f8d5188bf363d0e8f543a298bc37ea966a60c23065d9fa3bdb67488b83f824594c12d6c581ff11c6136aac3921a41552a1a55d26aa54047b2c187604d1ceec9e5c0ac009fc7b5cbf640658182a1de86fb7757972e1731a055c624490b6bb1d8550fb7e8a72111e3d9c4e2ff85fc72d2d56c13c7218f584b668fed431be869909b1533e811f77f08ca0c2e3b5fabe99f4f17e5a472a16ba651ed3a0daf3944fb1b0653cfab052e09195ef2d1d0979dde53664ae72f7e9045b5b23a56661ea594d37025041b1f1bde835cbbf48e3047d5d0a289a722576909c7354b10d8936976c27ebf01858b2c4bf974ba22e8c65b8a6d4f01b3d753d27cb8d56e3ffd3590bb7b08050f111d00ec996538c31adb79daf8183e07259288d6502742482ce416b3a38161dc8a8b0843c387efe81a107e3f6c19b0b28ef674be64095220abe490679fba079e6b8c7b29d61306f4fdcdefa73f07e6c726329bb677a047e441163fa9b299b4350e383291687ccd48e97b70b17b989b3724d46e379b1d57a3aca10824e8e420738495f5582fe0b87dbd1b060808496cb72146e40546d462b53454ed6f6f8bc0fefdc1217a1733480fb700eef46d8fd950d32163617e0bd785af9d031fef8fca9c71c4f4b6d3038206fe458d7b375b9cb72d66a9036c2b946dc2eb519a1050ebc69042b15fd4010016769bb0517bfff3a7277b2eecc3c7e1e46bfdd7913ea245254d9da77cff8f35eaf12c73b1677f42b724e519dde752c0e5606aeee16071892f0378b29287c5925b9c2025b1daf4bbb138029c15a68cd9c66d9acdde310f9b88bed9f4bf22592baa3e0e8ec706c433e5c8f773fb03f2a282162fb3456b5da5f89a1e013525fe6bdb3eb94461ba54aac72549d1ada13d4f20c7e70d5c7a82037d6091c0025f392413a6b2c0b2c2cf24172e00b3092327aa1ddbd1debaa7e72d6cb23fe5419b0854c74d8afad82381c1872d308f59967296b6f91c104d3e1709d0305683c6bbb943de8efbe54dff08663516505c41fa43ad296e53580c6625465c60f9c7ab7a26ee23e10cb8fe5d37d8771dde7cb2cc8a0aba51af5917d97717d546e53a8a71d9a7cad307784fc5316163b8523aedc47baa15d131a64c3c0d7bd073ca56e861cb620f51a0bf069f6a7477275dbeee692ac861d0ec055239a2b902ccf84145c58004d9d388efc7ee4d14972aa6f29816afc12317071bdeeb4c77c7de665435962f4253d616a675c5fbba0724dafdf5efa0a737b5d59748afbdfef44641900ac01ef6d962188d284a271c972aba186552e79d2113b11176cf7a50e56d0661ae4ebcb298c8503a20a01b6052b9f1f3f7ac36618ae2546b3b27a00dccbfdebaad33383f97f372aaf22084cda63047dc9054866954acde68939622de05d58564bea1930113d83aaa3e76ae6e37227cda902cb952c78d61d721840483d14693d42cb16de9b6a13b6c2f8396957724302e90657d1c4f41ab9b9dd1ef18791f1f57d7cdb5897abccf054ca99859903e1f187398ec7ee4eb4a6cdcd118259147b4898579a8eb8fa3003f8ea1ca0535c5ef04971e89fcc3b2ce61cc2fc79e481478742ab8c52696bc8a2eb87ad9a4f72a5c6d8d989c02e3e5180bddfae2d85bd86b0f285d8659829207c48014944520f3f3b230ff3518b1fbd1c36c6330637804f3b579b18bdc9358833ca7700e058378d7565a9c6ebb663cf3621c26ceb0ec44d8361fe32e3b7ef435095ca7451b00e748acbc53628f143aaad51c8f2b1cf8db0b64f854eb08074e0a2a552160e5972608b200e080e1ae31b79d1e9525a3de0d7a912b1a7a84b9a30ad8e31855cf372e004911553f6bc8dfd8b00f16a2bd6c2496d14e7ce256eb8c081d525a67dc572b6c6c723b931e32cebf6f94a1b1e6cfd647196c7f15ffc506b3e1faf0070bb3834fa6b40a510998c12325e1733274b121f954450d0fabd00146b8083fa5c513e4f0f480b50d797d00f53d6e8ad72f5252a84526060e1f75c81b6deb550191f473e135b6ff048d354d58bb32a4fc3607d913531c620b51b9d0f91084e214cf021e6d13fabdeda4cbdef20daa8c788add3184601aa62df67244fc3e36cb783c7590627baf65f2dbd21acf3d7fe31e9a5cff93d03c374e371292eb130c8d57a276bf85f74d2e981e41f0e3a1d370d37d71e9204c68dd06f9745eb11b551e25b7b722fb4677f17dc19596814c565fed9c528d0516b0c993c0aeee9e06ba76c7319727fe1cfdbba6d40437e743b163c0323d5c5ba7a4c648c2b6755ad4b8a1e1d91727071ca1365f42a95f10de91155b4a1c4c304df5bfd4860a69f2784302034f936518714316a530f1abb41076f858f25db6139ab13cbc9754885c2cdbf4dd6ef72d91a2deb58d12176ef7da8dc81ae85baed726a568e3f0711934bdd9a9ee85a7299d52ffb3492c8886ddbfea48dedb777d0cd949117a65a3c6e700c59ceef7c2a3b174e4fbe5644a661bd6d749f73c7f1d55b925fd4e3515a550426e839090772ee917a6de7f6e53bd35b666aa00ad9c30d1be54e66ea6aa70b219a40ea03607233ce14e66811e15fb389da84a3278008baa75a6b7fadf518184d302970cc8872f60155e275585e4343091a1f0dd9092bc7596f012f1a32e8449f609ae00fe554716520b120348d2ede1d8378669877e892852d811760b69c3e23dd477b4865720d7fb0255607192db1e810705b41efba6ffa1106a55dc7bfea772779a5ffd272ea945ebf5473e3b7a3c3cf853a304adb37a471d2ece2015ae188c3b5a9beb01244e584452697b9eebec9c7e472eb5aee37e742a8a58c454c0f5ddc980fd320041330285a3226d670154e2af771fe2160d892fabdb6be29134b210be213e74553726fe2c825b54a9d24edb122c88b454a8814d6b46054d4f2871a75f4d9b338723ffc167fcdc77660008edb537a325f89a2f5adce6b4f1ecacfb83495110f7072518fcabfa30870b4655c7a8c8c460330ad78e308a0ce66bdf82cd7c8bbeb3a347f0195c813efafc14a77ad4ff663158e5a8b2111e422449ab5146441f8287072fb49b321e6d8868f10950780cf6892279602367a4341d0234ccddf91e9a90c72b2d2c7a3a03da764012d837c122b6eb852ac1f721a21cac5c7199567effa543e4ac32aae8c5e62fba06ef5ce5bc43b20b6f3d937258e868e71714a066edca71052b415d994624545bdd4c8d3879fea3ba6df2f0220f76b0894be78371bfc69726eec94bb9d5a005a4acad4e92237f06b98db1811e21ffe136ade988e87cb0672eb37f62879ca9ebf5b4148c07f89ad6e9d85abb8ba957820741b0af22aab527261a8fb1b7f347d933f49ca7edfe61d6b1b223e8656d17b91048cc3fd570599727c0df95e80453a712cf0d8f728a85272172a43754bdf4b650b1f54f2061c936a320c2e17f1aee02a7fa48af3d56cd30ceec16680f69065c34f743899065716726232012591004a7689577b6e9c6a9903a433564bb0f11242353df53f3cebb37253ac7b948f7be606f8dc6084cd8532f0c90dd49f1b760def7500b719dbdf085c3f8eaa913e884314a1500aa14b816c672049a846834617b5e87f47b248ec177202fcc7aeda51bcce74b6288126058259b43dd210cb883ac55abcd4df48caab72d23953c291e511677b7483b1188438106dfe01065071f7ed22e4e50f27c3c9729ef781d8d7d58d99db06da423d0700c263d176ce2a1bb89fcb08675f0dce7d08a09dcc11afb41041a26d847f986c85bc8fdc2db2a9243379ef40e7f73311d45d4fc7ef4955ec48a762acb28f292e2be191340fe30235ccfd6da1ab2f443ac46b3102807d1f88d307ef65eb0e7e6563d272b42f4eba52aa22d828ea6c92f3c572a989fb341f2d65795e7ab73ee0074d32b56ef239455a61cc21b3e3f6726ab41218f60bcc91e9f75bf4a732d82ff7f1d86c695edf10686cf78996bfd62a19f57276588b94b2459bfe2059aaa045b9ead1923ae700b6072e6d362f40ee9351d9697f7cf6d576c9c5bb3c91319490eb9a682604b44bdbc2b1006ef5baf66b656172fbe397bd8f01f08c3f3aecab52bbb5f1dc47cb60c317ce73438b4c92bbc8207285e8b28dd167a30425336d18ee1c76617ec6ceb0b4901aaa528cac8bc7aa7172be13d767dcdded14f12d233c2b966c33855d07235670b664263a8181b0df0472df5844d06a60a839ed77ff51cbbb0b36171a65e2e9afe8e3b233c7004f5e2b5890569994eb83c957c4e49b49770b04e052dc9584a38e62a3368c9e16d7186d72f0520ecd422e4c0a62f0ccdd5acceb02e056b0d8b46dceb05670120f19b59472154330126ccaa75a603535286fefa7031c982e5a19adb2a67c25a460f5c39e07cd924adfdd2e3cd4da82820beee523b06e62d9172e2bdaf1526f398995c2f61e660b4891fa35c381244fb2aacff1a5b0b2eaf08f93e401a9e7bada033b2fc272d8d4d858cb9ad5b5b6f2a8222640a95a95cc1962e39df0acb7e1a3bfd651137217097c400b10b580d123b137cc9461a8e37e401945049761f771af4e7f8422726e5e1cc7a9019ce2d8c88618ab9399b92b29f561d4e42136091781559e60ea11557031cc416ac750f5e653fe80e7df8cebe93cc00c5a8af67aae7670a1441202c7ff4e9100383d0de9a6819d53beed8a191941fd0fa10670a382628d413cb072521b73bbf0f770b9069679d7371e36a051c834b93285ef5babd5876cc2e36572c6be5c951a7bf8b3d4f7aabf70b4f9b886a536ede4601ccea2b691f7c856e772178c100b5ce30fc61dfd5ff59a84053dddab9b5feb2a1f9d49c9b582c9b8317218df4ed0c344116ca9081e593b92c9222027f4e6fab814c3c04731704ebf0272753092f5e6e7e130ea37b7272e38c16cb24d4dc172ced168d87531c2503516727014b53fb7aaf764e2c76fd3ad849b0312b7d5cfb2b8e93029fa1b8a9d2e2d72fff8b1219deeb0188cac1cf34e1b1765a7d79350facdad7548c3f3c94d3d553254bcd8531cb3aaf6cd73c60646e6eafe00f3b8e629905bf21e16fc3b5ce59372b28e5a2ca2fd15f40592c66328c74f8461239b571220e0f78a351edd2bc88256b780fc6c8ca776a7baf7d82cbae8800b9e51a12b3386ddec8d385f31f473ec19c7281e3d78658820b8a8a871d89ec0d12bb7ddcfb26ae2b2ead6dae779e88e72e1427b05d9f80b61fc6fb6e9773acc40b4e9367403fc35f4abd62937afccb86bcd87817d0874431cdba3b54ca43f275ed1f8d65c94cd6ac933b008bd5ce5f072e3dadc4f6fd423f0792b83d4aef3f4fdedbf1e16b9f0798ec6072a643fb90065c9cb582b338fba3d2d170a8fc078890a0facb4dedabf33bbf208710d03e6162eb1853e2f2331c1d762ae4ae2704ab0d75e83fff010039b1d409e30a1508b73724c26d3f87b7a4ba954a5fe080c6650d334d025acaab0f4171867c00fa9c99572d9a2640c62ef43974e749cc5cda9a71d7dacab53dc8f4827ee87392b7b7cd37207f4a108f014a36f58ef907a46434ff00ac4004835ee3a263ba200f6fa543072ec18671f3d4e9b3dbfd341f1a84ff30d700c0c97d7daab2cd0ed21d855518a721c084cddf37fd151ee8e2dcdbd895998c8abdb66ebc41f03fb1d61db57b6b23925ffb11a3183f7cbd37014e21c26811fea316b1753b97293b3a7255dc04eeb6dc3fae407fb0dbf98bd512abf9ff5f01e4133da5c8ff06d758f5f4834717fa572085e07c2091224840b42ed5db43067197f698a6220fe7eb59e69b15aad05ae72b024a11a2da0dec43aa4b5fe7bbd5906d4a4ec777dc6f8e592318c40672d12721ef12707f8b59094825d2b588a9f30025edb0919abbccd3e7b41c0b828d7ad72ae74a1427a7abcbfb55aa08733b2c5d3921e9fe7edc75439e0b945c683e779725591a98b929ae3ca856c7c55fd24bb584bbd95ec3fd87e2b409d1c6425072672d7850ad7788b531948b7bc9cbd5b083ded8c061b1fafa4f41cffc690cc998e727687fc1808aa956146c00651c18af944d24d74badc293b41a4f30663af85fb7238e92ca2bb7163005b24357ad1a8063a865b9a59ebb5afd881c841e8419141725e7bcdb751f70eb8584c9039e927bc414d6dda3f2eb81b7b774a09bff2e899722d356d6c57d30c6db1011e7b40d40a5cf16bf025569342dcea0d9be74e1f7f0e859f8cd6495a3336b6d2690754d25b239e947cc13b6c6afca9488116a9b063720ca0ca55970f230f36b34abdb0976432e5384a2e28ac24ad582dd0e4e3a94272b9982e935f862482fd89ab3a9c87a2f088ab8a0aa1326587a595388be094ff72423d533af3f477930c33a1433cf77d18dff2d0a01d7cf3b0f053ba533a48f972770e861cc570a693c68a8cde5b1c6f63bf74f3545bab5303b4e968855a1553720ccb15aa8b4f15bf372e15f7d66ae3fbc9062a18c799e98e69c3eba24c3e72119aa452ea6d4d9610c2d4949fe642daf6ead31807cb84562322f074402d138d723285ed4c609054ddf9c4c82204ced32f642e46d5c58e160853f2d4f61c9e5a72aa1a499d78c0620f31dde83b1665aae8cdb9c51408208333dc65dcbf458f843a7a24d434ba182734c4f7dc2d0f30d4a06cb0095168cc8db05fefd5811fae1a0163be8a5d72e087ed33266d90bde561cd85cb57a85ce73053afb67b5bc0b59464c3acde2b6df47761616059932245fd84595d535a5e0bcd7e4ffa9f6e0656f84e894b1bcdb269f2b3d84728c24bc9cf18f1de5e3e482fc0dca1afff13643b66403e075c18ff8ba2be4becde6d843356482ac38cbf3f8b039912b91a0776b17d3641d4bd1e9e3fb38e756f09b2469d06421c55ff922a263d792f068f8b29e5435de01450779afac70435653e3a2034f96cc80e5f002ae4be98a15295d1f2848172ec57e48cca8516273fde1bc1fb7ec4bd82772c54a014bcb44d86e832d1a618726870fe415b89acdaf74f0a73e4362541bd3d13a3dca5e117cbd35160112b2272aef5555da2b9da5a95fadde2967b93aa6570721a605f627f7dcc87e35e7b952ed74aee0c17825380a1d795f5bbbf8eaa04993257f61510196ede68d11b22ae7292a281c7c86d6b1984d71816788ddbf679955133cf05b7e6b8ff5d8dd397c072e3d5c67810a135de262b1b297c5fe6bead575b8c137a52c6c4b2bd605cca9072abd0b61a73f4ed9e541000b9999bbb8a7738245090ec12e970342e362ef44272e97f7bd0aeeb8413cb82f0268386f4eb70486d64d71702f4d8d5d2fff6ab780a9189116246d2278aa11e8d72c7023c9fcb9cca04ef201d0ed84a80b7a2d52d34bddda9351f86ba680b4d57741c834be11e4d9ecbf5d8d3bd7226f7bf622cf924e2173cc3104b4b94295008b59da683e06f03f04ef2219d279cb2bc773340b8723d16b00321aa4c49778339a5531f60cf482e43636b1d2e08e2ee7a97899424551e8650b1c2b8c9b80b52d75bc6ccfec77fa8e82f52e6667b720efa57f5d2ae725a73fc188294fe92feef25543b7fd8e090f42d58a2015bbc27261f8ddec02e72c42a8e89f49d0d01a73bd845dd9488e3b8cd63291355ac164c5f9d8a480be67295fe7119237580354d7e5d4824c855b7d375b36d60c57e08d9aa2890529ff045f22a19c60bf53863c42996552f2dd1f9489d0a36b4bf13a9f133e146167e64728116ceafe6d45944719626fd50f6ef5a909eaf46c9f9b5f2d359ddb5dcc54e37bdbcdfd1acf42099657914270e026673bbf38bbcc7a4adcc43e228e46066ee7244c85a1d26e054ca59e4ce2b1fcd21668582ebf4a9f0bea1e79a755ce0bac00b48006181ffa1ca31b0205ecce68eb7008fca82ce9c9b7c32ee5d1dbdef6add17d8ac2881e6fef1c93cf76cd94d321cfa7eb591c580abcab697ce7bd50be47972ab967a7fcc90d6b1396a61d70f943e521ace25a8b5575b15d34ab52e1164b772ca7d6b53652667d1375d64252425e5be1c105fe200f41975cedeeeb531ac2e3baaa155cacd5745c21156c6715b0173115080aa6eff390f7c89b2a71261197b72c34c7fa5ee0b72238d1b8a0c2cbe4e750c1cfadde77f252dc8820b14b8b84871021c9a946d31d352a12791e2a4229887ec69ea6a79213f09c0d6c7ec56a13472fcb2eaeab5216efc703067f10dbbfd339d817beb583cd88ece3612099b2b9e72f74c89fe9314cd1adfda79cc4cc9d2aabc5e9c763215bf7a314c916d5c4c663207652a767bba022cfd9a90d2b75efc402d3b6a8f79d66b6232fd6b3c2c3b920e848535a313212683b8ceb9aa7f38413755f5ad8caf20035eaf0ea91a60e9c77206a06c72606aefa98fadc4fbaeed5a7a80911207e2f1e501289080f78f4871724ad7878f0ec06def55710e3835c4c1783cad96f0f94476f851c3bfc7534929727b3c429506b31187c9e59137dca408aaea301d33fc2bd96f13cbdc732a9bcf2d0c452dd86a02c6a19b8b02c2e0c9a032eb4fa5c2bae6547b86bb5d486c57b7607d370c0d91c4a596f1583ca71eda66da5b7a774edfcb0b2ee12dcc21b8dba049567502e919b0d5caf838767695a2d2d916419089e046e353c1008bfb3162207297e086b87d3872b5934288b4f285606daf2c943a640e70f53ece0ce4f1b18c7265e4697a5d69fb2851136ca208b5df118a20fba27e6c5800c3000dad3f84c00829e44191e7585cb46d0be69de1decadb54945ecf465dca3f1aba0fd8897b7f721d272a76e95e9be5f845eef5ad09c5375f22b631b10d8e205ad8cb40b3c5f14a647e11086397ca8377dff955bcf5a5bd59eb1b2a705c9e6c3deabebf1fa54672bb823fb5dc254c6baf813fa5c279de1c898d53e7c425660a553e40724bc722523895d7d4425c38a84320c4496b91009bf180ca5a8f93ce9cf7cb68f768c5eb6c9d35ef51eef6e27e31f651e59a43cbcaa545946b3b0719067530e5571fcbf060663962361d5fc8308d6d04df150fedb8a7bf7f30d9b62e88a881e13893b2ad3fd6d51f09a5e6106211c512197f986aa44275dd26554826a5171a15bb449ecf72ab64d468bf33d2e4b1b8054d90b6edb7e7e9a0ed77a5b7451830969fcbdc2772af7f761373582caf0846d779dd370c8201a3bf8d9865986919fc9bca09456272c636ed7397ae6919661c7d987e451a447da9ba05b736f87a3f805af20dd72d72e73d311ed57decca6c78e16b2bdec7eac4f7d759541d56d7f4e5efe2599ba03d4e18f565b2bf9a17e9a60f91d4076b857eeb56f5822dd5dd3a2d2386279240723e953f3bc13f1d5ba4609acdf2f5be3e44c63a334c9f86be60cc945b8cd68d699203f06fe523958850de984cfc26e5cbebb59d6ffd11070343c0a8bbaf318d7256b68e301785fad5a044d011e703fbc65744107786c4ef99a982d75a0398d072f9a1f8513e8376564e900789b804f8e5d4133c3ea0569474f7dca4988a2ccb726470dbd7cfc3deac289e60ebc345f37d172a501a89b7ea8485bd68fa058e33327952c1621111233b76e02ce1679484209286dd97d9dba11639d2d4dc1c92ca1196020313b47bea59c2fb13395cefa6ef90c43894171276f089f940ab4d1c7024428b503217acda6cf4f4807c762c01e7969d781437d64a4cd1b96130deb531721faca768a147228534f6c38c1ea5c1b662bd18bb5879341ead0cd805e66d467260230db5ec682afeda274dc7205bf03229d42a28b99878f615570c92583fb072da6fd5fd740b68014bc65d804b39d51a3042dffbcc20ba837bd0b64246850e6c7130ae4845432c38131e253290a2df85e72866d4e294e3b43a2fe7e9f9b71f41ce11147c15295bf89c1c4ceb2c3b0850637856d13ea9d87bdb2b9a56ac133e727130251942fac07815b932ef5f438b906f2595b1d6bac29cfef31bb4286ad759da9e35dd5d08d8116afcd1bce0e360e13330c102073987ccd7935f6a1d4701620c4eb3ef967d15470d2f54fcbceca18a4afe85d421781be6bd8d25b0983fa772c3792815c00b9d0218bf45cdeff9eb067989c433241e30fa956a25488665d172147d77d23bd14f404f38e0d83b83b1ee95524db23706b0307c33c5d01b1acd72f5151c5023ed09ad648907ccd895e6878c083f2359390257b79c7571c5061a721b191918267367eb1afddeb60663bb199f5a976a11edc33ee7f12a380c43c810e4d100b6e5227455c4d66dd14fabc291e304a5ffddd6d51d9f7c34c38cdf835bca24c89b60577d3f7763d2b0e8dbc72c3e1edd84f0d0fd5b8fb9bd6b48cbd772f2113b1145dc20ed97a42b9ec782001c1066d3b19f0f0c5be612a9af9a4f013f0db3530a0968f5068e5ec80e9b710396d529848d7101a1efa9a4df19d85475725ba2bbb08865abc3237e241aa8b6cb6edbb5ee52bf2edd3f651817c9d633a7728cab0db9181397ff6bac72a37943bc6090ebec2c2b2d923d989df6ced4924072d20fdbdb3fcf9ab1f67d6a5a3de227added50248ea2e9be3b12f7605ef7832706fefdf74503edcca87cf93a44bb69eec245e1d7f7c8d7ef02d32157b05520d1dd2f0c617c36e935c392e2a3372d2f29afa315ef6ab6452b8996034f7cbf2d9723021c970caa357efccd56b54dd2d8375de1c766cce8db63ac7a6465cde3acf727da6f8dbf92221b78ac197d97672379793ac5337dcf2cec5ed556e6c7ca572725fb29ce78f16073c38d4d45ad6f09e05dea96b87c2af38b9b25bd2a05abf5807e2de2960ca62df51a24d9aa3583416e0f61954643a5110d7eeaf59d598bd547226142a6b01620007c3731630984f51f4c2f743255586b1e97e324c36d46cf172e4551355ecb05a9c69cccdf4e421d18e9aa10b5b80c45bf9d57f81366b27e372bd921718dd218b73bd216df23cd09e307112ed1aa01b557476c1d0f574c79359478759fe78f1a746f6c4621b24b67dd4f43ee7d0b1b84c6a356c95534a543572b79314970a94ac7424adfce3033b96c695cd6605f430b722169e45d9a9035272ed29db9803ebfdaf03c0e1b762b5445764319dff6695388696c7822f5dda1a2ed2ea29f72c7fa612c9c62e49f242d0bc288b4427d06df53250918825057fd6060f574f38be5a01cff261217b22344572a9259b7b56eff3c7e9c2b3f212dac7727720b8bf7d6636262990a6d7c765dcf6ab374ac8cacddfdd83ca3e3e1327dc72d57c0bddeaf905e9dc8519e8579b0f443686889299a8aa680fa40a7bcdaeec72058c35f5fc89f08f879490497152e95b8b9430659d62661dd8bc99423f3ab3576aff8d5c627f684c80d8a133109d777bbea694dc75380e2ad378a027ea25127219a019acafbe1cec25b9561311d4fd17b1ab53d41ec84ad6b284a57d82ed9972b76a723c6d38a3d28a4b9725f65d8c0dd9b3d2505966027cc6e21616cbbeb8728e0a72af0fb43148cffd5c95cf0efeba84ba43d0ad3379122909109b057cd472cdbc41a4b991016c82a24cb193c459b4f742faa747a5769269c8722fb6394565bc26b11fecb5d4f59848c2728a1256b8864b525e52445e3890d59714762bbf65f96d5c613ffa17dc1095a6ef0389b195114433a6c601eebb6b1bb9582031660fe424ec9c660ecaf7fb718bebbeb1daec0566a8c775738410726d8de83c0c55722e43cd5159566c13571742fb22aef42f4b6138ad6159678e57a58e4c15878d2fe876d6514b4b3cdf54dd9a74f39b2bf6328d8c8f5075c151a04752020a9af772a6da65c597f4de164f39e65f1de1566593dee474ecbcbb8d251abf158fbc210cb0b742aae90b40d8ee3bfc2294fd0f596b6034c1253c9f9571e524dd9c54df72a2ea4a5542d53affd5509e67bf7d0ed2888fafb10433c600f90bb2af79b96242aaa8ac1c881d14e481d492a857a7c6e2effcc000ec850845bf6874f7265811356019ecbb8e9fb3fb60463e1ab026deda012f5c64b4261432066290e5f3b4a1721d851482c613ff1d38c66fc6d68ae7b1c7ab68d8c9981ef7563953df4e33ff473ed857837e5a1595d1cb901f931b6fb3cb07f41db94e4c72c76921efe25e71724b8273bde23593e9dc42964fab175ec15ca4ee14ec75682dc3d699fb76488c5d226a6c04ab0dd0527672a00e2b64f0576edf25cbece644a1526d07632de63f725e8b5ca8b43855c45039261240057c7998606b3b9b35e92c6400649179a0dc60a2ec02014d381637c2278ae9f91f5c26aa3bc67d31b1554e5e4050dc0cbdf3727c3399fc8fdf0410d0cf2dbc5f9c7c53fbdb80fbe979ea1d9954ff39992f63486af29db1d26e08965f0f5d17ff0bea8b501b86b46d39242cf77ca001c49dff56269a3d6a3739508d440629c8914cf1ad78e0c0889491b9595f94c3ed57595f720f580e7fca2cf537a205eb2126f4f6411d58a18c1a5265bc581742d7ceac8172a7d31131f3d2a304ecfa652ac22865a2a678523370464178e11eef00327fa2108f545861b94e9961956af4d65654f9462b1cebc7bb9b446d764a07f0a1e7635c0300f1369c95743723dfd27ea3e3ddaebfebccc7515ca96c04225351f0adf972845de61134733fd01c6f4f1c071ed04c673db61abbeb4f6f4bea05d0bde7d672f933bedc1b62aba59b62d516f795e84585b81c66129a3b55b0f23431ebfdb50c28645b55f624356dfb17da359fa9170711099508240b98febb44672b8829fe72e7029d04bbeded77537a5177007799d8ce9cc5a8bfee88138843a4ea4662f1696392d6e71fcf53bbea86d88822f97d0ea08da2b9ead83610daf088086e6a170aa51a3d92db9f2e189bc7c4490a9a6269b55afefc5d7c30dc6993ba51cee68a2693b5e561e99231e7a1028e2093bf4be6b1eb1b3e74093154f75b417799233c39aa24e48aac0bb462ccdeb9fa0ece77ba66614fee258751f70dbc38998354677223d716c26337ff7ab42ff35aeab30baf4913d3045499b32f740e6ca370f8053228d785c309a854f46a0e3f81d9b8bda9a5d43d16971a35653df39fe98e119b24bc958b55ce68d394c1343751855a2217246af98bbd0393564b1db1d8530483433c4da2ff3b6f4bf33f4ffb79f18b057aa1f0a211ae39553c0c6e651c3a856972c1c0442b17508cc78a3ec75f581bc64862ffb31ae8def849bbf6097d5f9210729aec28c1cc0f0455f65d827ab7ab62079bc4431ac42689a6988426575134bf727e2ed6afc53b00a3874f617a3c174c480de03100be51c03baee42986c4b322329e706047182aa3b43c73717d77c59b07437857f166b6c2a0e4817b112669be42fd4fdea96355692a78e2ce4eedc369a9e3ac1b5e0c8aedad3e49d9b7c99ab372cc84f1b18919b3569337546cc445f8b28f8d3ea8e88d8463d8d11d279da3063253c35f28aa19b644052065d48714091cf1b5d2ceff85203e2ae78438f9d6226c684aab92a5f8d3a07330ec87c43eb36a486bc9b5befe10bd0be86e65ce9be835aa293c21108a2a776233d7cdf822805af40f3d09e179bd72e8020acd308e35724c9c97b467d8ca5f452de9f662b8dbea9b6098843d39b57f14184aa98642907297ded10387e3c3205ca5fd80110aea9c254432dc28fc66495319ac9c4807f172654947874adc60bd40b61386969a98d99e62f822f5904d060fe9ad8127bc3d721b65dddb1a721b3dbeed35318d03e125fb6dde61595255ad6fbe7498de5d1972d1d7e4a1c566304c0d2bc4f33f8fbac6bfc7fd06dd770b49162f43d957a2a272a719befbf000f175964dfc43730d0f6e95420ccf04d0b320706389c1ee615b3133f2e9dad52afd84576caf9516b982c40c9bc17ddf4dc8abf40066203dd14e72929e62efde3e4d210d1f472d8a86025c9c5cd3ac9da5ee26ab51d49954c37672b355b871bae014b779b0eaeff1e9e422f35b40d1f319d2dd6ca57f66e1650272e8e37825a8a9798547eac8a1c11deb73adeec1b2b9a1e6da3e6975599b4a3e7230a2c3bb1eec41fe0f063f1fd86761d9478fe734882cebb14985f2683e00ff7289c405f22f111824745712657616b74f930f9ad6f44a7613e1346ac83ab28d0b4de7c1a4b027a729e55017544f817fd185ff349cf48f246374cc9df89dfbe62c34a727e8ff1abce800b969a21c5ec4a3ca7193e02806fb4ff9989f93c78a7d4a7fb910429fbac7edb92c0acf2dfa5abf329f9b9cb8b5f38792f192d523ab61724956586b91c8f20e039cd4f97c19c7bac6c0ea2cccece98558a801e91e3f4863a4cfd74b18d4ecead02a4abfa4f32299624fd753bf55353ee3d1bc8cdf11ac708a8a0c31d8e07ebe9d9acfeeb30ecc52db44fa528397b92db961820268dee772eb0cd6d6eeb3d505ee175230c56ba233730b8c5a9d4acca6f3d5e05d9c2afc727fcd8a6b5277e6206880d70d892769f5a78b15703643879d523952d5b4e3184b57e1bc50e2778592407c90c04ecd9946774ebc3e4cddb006df9f98e4f17e927266667384d8dd649f6f6d8b1a0b584b37127f11231d4a6878f4531c3a08f99572ec29db7431aff973f56a97349eaef42e16b4974c63d87cf0e051b77c2988ee500f9291a5a1fe012f8b26cc27a2c92e39f810a5753038ad4da06a9fe3b3ea8112b8dc50c564b168cdc85808464bf62bb7c8399b882c6b2615b039d7f50571be59367df096f8d83fc952dc9044a89ff3fe55b31c0760d4210d8de4539bc479de72787b4ba26e48f8fd1a605d8d3bcfa6e999b1db11005607ef3927857bf68cf172ac17aef43176c2112cf8d05878d90f95b8a12283f1f7a220faa1ff16295958188d21eb8af7d3164ca643cd6718f642db682c62847379ba038d6b2a2eb8e9cb5da17276eab3c3e4155694d447169ea2a312d5bf0a0adbee0e61fe93bf5715434df10176a5d1e590151e0be85e1ed58450111df0c078f051760c911460b4570e39203c645405c87e6cd803350a59671461b43caa7c28102d4692ed250aa8afb37213850bba0b76248587276677bfbbb34b249813957787f3c98177cd741e2153720cba8bb1012e4797b253e9091956416ff3f97f5ec84f68752a1905a99ec87f723664aadf47f17504b572275ae008819c7d481e76f399f2dd8d9ce1677007de72b940f9ce3e1bf409610406a53441dd221e4e851085f656a37d2fd0376cb3617290acd452792da5be1bf2237580535484ffd6049518ad83f530eab3df5c5ca372cdc0f8d1de74cb708985ee5ff449fb9de7bf6cc3d9bf373d3b4a69f205e1190770259f04d7650ecfb8d478e7aa694815c5a6230a73befb27bbb4001be98a4b4206c5e89ad1ac3adf3093066f5c1f8be9413422a3c98c2e7a225f42aed7a5c56df971a39421f4787a9f1d8fe121b04ecead5f58af5577476271646d0e2ec475727f63fbe7fcb3d08e7caba855d89a17e493bec8159d195d8bf268972ed364bb7289d979cf9bcd17f9492144f510d3b53b22056e35f654c8d198067cc75f116972eec888f7b5ef64e2670a86b3c65bed427aa6996dda2e55dcd0ca3c27dd1bc702f928a520b408ffc671c43491b931b4b6a9c5bee4f7ecb64934a334d25969cd728e53f33154d8d956d5ab5d3ace9018626b3330be9324b0e9b3ecc66e10036a7208dc1f0d5f66ac5bf00b1cc7f76258e7746cae904becf0f727cfd9300a955872a6632bdb2df8eb6d717622adcbb14500de7226b2f1cc3f3a7ca62f98101b6a72114e0d791c10709c266c7f5ca34765be3dc154aaaf7ee8c144b577e19a95bf68433d1eda58cdb46f7b78099b32d296af5617777abae960495d312544a3735f7274b6a0031467c7fb946495641b91421da9257cd42c484f7a8662704d1db93f72a7f0b344f40c862abbe39654a109eadddec89a5faedd0c8b5e3d2c591f87f73e7c8f0d09e3dfc1fab5823c6d224385547c35ade1f7871b8823508975f32786729ae21057f6eee93643cb996c6af4d1da6ff36b0e477ced74ef96b41ebb97047265a9d51739ab1bd554c7542c6a65955da79c39e75bc0202e405fa796b90cd62ad798d47ec8b53e459db55dab0fc381cb7f15a154342da1cb1bd77001ec2a5472eca2c7b03a18458327b221de37970687744d9eecc9b4d189cfc6810574a7df664279f488001e843054a4650dd1b934100b9508e43b1899c8469aadf5b98ddf728c72403253871e05c110bd7bc0efe42aee95696b70b84638c3a7adaadb9c7972d22e5630339626d0058bf5919618cdb870d125d5946d851918a630e96b78a852afeeaaca4b891059d575ad719f2dbd10116f04815eb6dd4648dbf2cbca3aef7238b154b2fe6fc93eabaa9eaca9b846e1d1fbba04b7ee8114b582bd67248f601fad29dc33ecd2b5616ef8a5d64504ff3a33c1bd63e1157776d4cfbdb45a3373729d935385f7aabb188bbd2d6e367ba6e970e5eaff26f2c1eafb57876c62fec0281bc7e8d7e1163bc85870e5e368566f4c44d8de1af386a0e222b9739438af55721a8db6d18033fc3f2f1e8f7bc1ead21c033731308e320914942ffe1e109f027275045a58cea4af2f840ed2c8f8d53480364d54f3c75cf8bbcf0416a231acf639ae187f0722040c7fc23627984f2cc3e0484343a300139adb3eb25b9bf3e4f3722a2b0c3e205bda6a427795ba37cc02e5ae294185a5c0f8e1ead23a4b8a2acf5bc3666aa67a5773c14bcae2d7e4003358988248e9edfa4dd6c042d474078840720fa307f4d3ea45330f28a41b9b967456f521bb717e41e1504c7babc9a4f8577299772658f0b69468d74d61a34df42bad8c7e96e7c26b923a290964225eb6d772079613eb3f309a65ec46301f0ec7723422eb87946a6bd063cc1df56a9d3479137fccf4abe5619a76b5a15e66e04eacd276cab163fe60ec86c4dd971117abde728cae5c6bd9f5509772b5959ea18d85d0d6b90b988a512fb45c78aeb6f04d3839a5012c0eead0d2b3625082793fba370e9993ea9ab54f1393d15e4316e65e12185cd3d777ac3fa1de27d93674289b9c463f705f8aa17f187a3bf398167074ac72d3eccc861af865ba8c9dfc4d19d475a9f34c51dc002225f18554433b100f6072c7839d3f4978480eb2bdd40585c25bb8a0b82d20d025009367ca4715cfbf645555e8507da6d0fe25e05e9f6f552ebcf6d158bcf12af33f77bc845a3d3f38e233cdd2e4e6c55fe7107c33ec0fdb5a9add7c5581a3d86194fc027acbb3589e85728a509b1835048b04e2aa5f87b43adb474e324753e567b74e5874972454301f7252f7f5df031db1254f4923d16d795f0c2847b1777e5af291296fd6312c82c5693ef01db386def108885e39d0f562cfabac38862e14921cc05dfb9e097c6b761c2f35e8a2a4f8cb6de5f76c1cc6e4aed32a02d001d301644b1b0ff4afece75b72f56a4632c419960c44ac531df4182887ad8580a65c8a19e2be0e068159bf093e0b12c4a85b05b264e66b01616dd198745d42e6b1d0f25d3e6ec2332c140f705d315af4274276981ad74c90738577df9eb61edc90fabefcbfeed513913440e7672c0bf21a0e8c0f8a0b197f762b1723a293cdf873698f6ebcfdd072b33a12746f079c56d86543ec5051d081004632dab7bdd085caeb4a1b8994e05c0bfd05ac700f5c10ecac5135e7703ad79ad4cd347115f4c65878f8ce4cff765a028d9665431d1c86b5f492982f98437cc9a6116799af75008563f188cc59cb9b4618c4e272d70d8ab141a4f524971089e4cbadbaf0442928a6892c2ee15327fc91cd662572c46de9f3123a8f2b21c05bd0c7b28be5ff4dd15fd8a77913578f348dd1d3075b913d0678f6ba7d5278619176b3189afa5e15a70d2d2294c57efda08a94ef0e7213ed239744dbe8e4bc5c3992a8ba0cc3bc0985a260dad394dce1c536a3681c72e07a5cb430177a359a12af0112af01bbe38ecfb218d4cba18094d47796018765ca4fcf325e1d83e8f656a83546715e55c385cc0555a13c442ce9c7f5178f6e0b85b114aa2a5aa1b821dea6a847137a5d574dca6843d30fe79ac8a7b6189ef146ea850dd00f8954c787bf55ce6f8fdbb93fccc040c0ebad856ab557dba790005eb76488349c456998b6ea7eea489903e4e66ca4b20ef7497506e9b65d0eaf030729209ad5110997439f37c558c2003da0114ba560da5e28c2967299aa0c990b72446bbe64c2fc97afea905ef1d020fd9882c6fb81a7e35fda1a10a624a353b87258db16d10fb43d0931e0601948d691155adeeb088a2c23c0c39f097e865b2f20eda27cf1189d725aecd6f18def88483b234120f7df93666e91cea027b143e1721da92d81b2d589cca1c1ea363507433e501cdbac3d9b92196eb1dc8caef90b6509b296d91a823b00199d55495a031384764f3f8ccd44a77d55b1b05741f71f314243c53e0fd9560d5c5d0ca1a5ad67cf98be4106b196a454b65046482ef7586d7c3eac3211aa41f99cb8c5949e33d01ec7ce65070d6f2256b1a744d88e643e352df080af856160c64daee2a781d56a270e02cdf45348d837a5813a28bbafd172d747da2a37dbea4248207640d9ba14b7b964b9b473bf6f9e267cc7329e28cc722ed9a57a259a80ae6ac82c5ed68e9916a4d4e71f99caa3e6979e24a06cd8ed72fc3d4fad2ee738658968e46c43dc6f0a5da5c24d38eef27440a1ce53407ecb3d885ebc6eb6f42468f1dada54d4f0ba74e679212a392d0581fc16cebc57dfe14f31291336c55160da005d90909e90ad4b0ffcf7b5eff40c504192ae886c4a3372d783da6a8d3d933823985f9f3bb91f2763355b0443f4e008b5357f96410bef3dfd8dddca64df87d9fa78a6e6d2a7f849d1994c43e40d70e731a5eb5f6d8ca63851c5dc7756d6a63f1f8adc1dd926030792daf02542d294609e5133bc169f98724b024e35e0d1a4a756c38f77fb5155f5c33d39bc68092282c5ba35569f745c729ac1c01e4d8af78fe4492015aa82b04de542dc450d3154fe7df4ed9647019f72887da9f138c6d1cbd0e0e8726a157ac797992f96e29fe73a5db6a57cacb3972ce8bbba27b58383ba4e2fefbb4729d8a3266f738d8a00fdf5737b53267b66ea72c2276c63e09d057ddc8f0734b604f70e4adb0938b53db4c6787f613f93fe2a57af2f6f3b198a5a68adfdfc79fce404986599f8f7873f5f80572a792aeb1c2072fb083875eee4b8c3dc63ca7a331ea92b135ed23aa78533dfd6cdaf03aebcbb7266953ec4e53e5ddcceac874a62ddf8941ec2a8a7479065b43566b7edc4c23172ae233a4e15725443b3518f14567d542d347832af056a500b08c48dab4b0d566aead4e7ec175110a6d9e0de832fd6eb6d961cb536fe7ced9028056ed5c0b0cc6a77fe2d47dde26c8830fa032204eea4d2893cd07ad2f6247cae70d1acc46fec727fcc9f90f74ed8e3f684e58498048e1845a5f65a4af00f25a22317f089fb967244d16b16181050f2adbdb27b4190ab46e32c5b4c5ee7316864e926c9ab912f1d1be2abd8836c6fd263fe177eef7934c260d0e05b85cbe9f3641254b996b5d5339909fe06c8e96d35758410f1196ed2338a9767e7bde78aa5d7c0cbdf09a930727dd16dc82d8da84b43e02eff0e1ba9c22b388480d9745a06f10a49e3c13f74724a3eaf990140f9d77ac39177e12542925a398191c39cdc47de972e3f78d97d7278cc7c6a75d2a5727db3cffffcdf1ed4282d673b58f932d4ae6f26a1d1a9a4727dc65a956ffc273f45837d1b64c054f854ca234dd973a66fd02a467a717fde72ec5f7d1300494ac41b40fef9d1109354b94d57aad8016e330aecf79d4968ff5f878ef9a19b2cc573efc41e927f93cfb1863c11fb4cf00083db880bb2724ffd334b3e72e1b067def6c4bd52b7c05449343f5857f829f65a6accc67ec2ba9e5872be4809955cd5a047ebf6b1d482bb6e04640fb921c6f684e542a4a59dc746452f0a14e31a99427c3c2899c92271d173c856661ac97a0f659abc71bbd8e4decb720cacfb9af9c25ebd9a425450e1d041f720b1963ed5d7f3d36ac49cfe9a16334fe99aee02af6166fa2cddd32e719206a7433dd95aa642e569070d673be37eef725091aae61b2fcb37b958848cd3d58c146863d6c28b7ea2a64cb01bd3301e3c41ebcf332e5e5f59190921db39e04f441be7edff91420d4e61869aee036034f872b21dc76d986bb9a4573bdfff06a933a7adb19dee37c9b708c889f280dd65e7728bffb1711850097bd84f451bfcc26830066f482954511c29bdbc86eb01d78272de402d3f562d2d4e6309fbf5fe6cfda29e9435805c2e15796f0d2c70dae35f3c67d862b9a3364b76f2a85f35c3d120c97f0cb2ef6619946fe6530b1d9ccd9472bc410805166f95a7312174ce21fcc6b4eab5b1864374a7f1df3c733e3707297281db3fbc5f93c3044ecaaa1ec0df19012ad891011f18b746469cb4250c315b72b3acfd4fbef8580e8a508d4ca05bc2f7b43368a68b253bb0a564d3defb5f64418c6ab135183aa77d6165a37c67f12da7ba27ac8a44201d7a44ca94a9302805721641343de9530e8166d806056a1f663051e47c3c84331add468530fbcdce407202b60de11186f3555c17185abeb29b7b451a4c7a7137dffb4cac0798923d1c7267b79ebbadeef35538f3d0488a7d1ac9e68433d93db0a478988533b8cc2ee57170e285d507901b5667aad8c1efc8527b954699c3c3e884aced211805807761720fc56594e57b9abc49f21c1348d4ff8713618e2187d0296c283823daba0d5c72b4167ad43ade132a2bad973cef36209b8bdb9836e560e764fa427cd2bbdb6044204a496154f73d701e0563b70b36d88ede34bbe7dc243ecd2e590d871b57c672f535caff2d81d2b1fa06059c8125617f9bda45afe63df0fc89a92316c6681572394b877db5110b00ada59117f05359152d69d0181b4ce7eaf291ccad7692e5729468579c40e34341dd0745bcc17d10e2db8eebd95e254e68f2f387eff0a6f30ec492feba1cc02f8f3c18ff1705edfae9f5bb16fc05fdd905a39d8e6f90cab525ec0b68add80f0cd059c29a13cdcfff99f68f36066345a8983fd35f2aab65c272ee1a1c374b38095e2ae145d16312874fe1a932a5a7032208adbd71b9fa5c6e5c3d18ab5a9c6751d1567f77878446768f3d48cafd06104b67846a1188c2410420239911d0426265979a3f737cadf892d2ad237501dd307594aa64dc7cc1826a72cf6ea9536dc4a7d63880ddc6cd5d6bbe467267765a57e9632ddceda2bff8d6002bd3d2c95b8f9041bbccc5aa16964103be66151ff8d16d249cc2b82ced74d851641cd1b2a04d3614f300d9aad4b26198b67745c2c23d8cc19155c7435fc871621faa9009a5d2f945cb9c462bb8cbd3463859299b519c6c8ee9ae37204a86921dc1876893700902288400b385a72d2bde2553b1f72ef986251763a72e10ced14c1b093985998dfe89b76cfc627d7c12b3ac3ba32dcaed92ea3840220777bc7048c70d4c4b0d0390c8c587d1d99d43da0c89049597d04bdf3f702deddbc089d8726e69fa2c7d7bb4a10da60e1b84f86557c99f0a3af533a6a07e4eeb07425a355d3654b4ac2c805f50c2feebcfe1b33ec03da3881e693605c729effee27ed1d13d00343bdf49d0bb102b632958e8b81b34ea93d4f5da74f1728efe0b8ab64aa26006833564ca74e82895239e65af1f301b69fe7cdc1c1073433541ffe68cabd7668f670522dd9185623dabf3bccfd4a9b89a463dff7f873cb8cb2c95418164ba72dc74c59bc7844db1ce7a8f7c141e9ccec66f21c186389943b217e308212f2e72e0b273a9a0f7b7ef4a8a41bb7170806b8a935be6e2084568bf00db3ab0343e36ecee59e434a3690bdce71b3b7923940a32a070952439192f61a3383a5d85c4720d85cf76367bfdf040631b6d42e8bb52414f768e8495bb653fc2674d26679353a2167cbf8f934ccca66076c6fd7be97b396c18662d26bbc9645102f5dce47454ca7538ed84f3b898cdabd2b41503acbb87570f1087d5f37e8fe355e71beca655e45fff8b4e5dff14563bf09362b1f150e2d0974afaaf41e26282d10048be3e72316fea2a5bebe8d4ce4ba46ffd9ef7c64d01d2725687e7b43679141db4b70e723486dd5ea986154c12a9e80d46d9f49c30292d3ec002d1cb1172f7f2b96a5872433ea417b7ba29df725d4724ea04988162c88c0ac759bd80ef1f2a1b349118723e15dcef9029a3929fdc8f7a3a93a53c79bf7cc8a1f6fe8743506583fb4c5a0f422b963e425d6d0d89e0a104e25d335a19027f898ea6d45f0b4b179e84505e0849241c05f03a878d5fef9b0e8b012943ddbb9388724c6030e2a8b236e903db7252797dbf8b849e6623e618ac4176cca3d67cd29f19b2e93f3da9eead8cdbb10c89394165260b348cfeac9366108017ec6df38d0aa8ae4253f0ced4b99142fb72e544e8d4d84f160bf45b56786a12d6aacb80498db607523260f2f9008e7f0172a526cd1fde87233d4c6d213405f67dc307918931e8b0e939f069f7857b5048723d6e339bb3aad06134a39f8af42a42defc4b83023f3accb0bacb4f26a63305724bfef63af8c825a766a709c97a86a01d8f9e9720dcd8e4e10f66f1f1f6770104d810a9cd074fc8e99e4d92a67be55606c279a9f4630d32760ec210acc26f29724c0ab3e538b9ca0462522099add366223e5787b732ca9fc5a817e91221363072af7dd5bacc28a1e870a4d661d1e3ad5cfd13f97b66518be2bb6c776c649e7472b11dc8951c90c7c6cc6321d7129e4488bceaf5e08a9292a642040fc2be5f4572cad5475b7338e17bc09efbe76f66ec7fcd3c84a8d955ecc5d3a44cdfb8eb2772da3ac5ce2ad2489c346ec31c4018ee3404d875b69783b7b4a3c044feeeeadc725ad2ec55bdd03168e00f25ffa0c974c9576c5749b5122af21b26a3dc4a8bb0631b335569768da2a8f0e6bdd31248ab4c519e6e4ab2687fcef0c4410e88101c3008b0f1651b6ff72af5e06e21d2677bd4c6d09f36401f4bb7c59815cfe8f9a0076e92c7d1c5cfd034eaf15993798d078e1d8c44aadf6e52602bb98e2040bf8d72d156e1af7e0710d9078c609d6f9f751f2f41111986d5f9c6e74792549ed65b7201a1c29b487cfd9fdc3523f50d7fa594c37973fcea19327b39f94c16bf424b18bb6e8c4c834cb01bd8aa8d9cfa2c290f9a063166fca2abe515e4bf1fdf652b61406806c30a4aaa64826883197268b3f3fc98854884a4013864da5cdbbdf8f07292b939d52a320b74f7e10d71a1f69111adace11caae5bd4c3644efa8cc617e728e1ad11eea2817a40b56682b88391873be88dcbd2b015fc5147b6861679d560831feb1a21eb6bd68a316e668790b2569185e21ff2bac31325ca532558652da7242edd09f397587967778fe1fff0084a4db5713e1c6b1e1ba88cb3d96bf25a9674cbe301f04b203e721935d55c5bc35b91390bb7b70820499dac31e8250fd112d58188dbe6c090ae9eab92c54fab8b0b907cb693def78cc68a9f9df13b548f513ba650f6dfba6584bae7e000be89a5721a3f998f55c036dc101266d24971ded119b554c8455acb4da76956b829387768caa28310b8763516617ff37d586535072bf653d808c692657423cf738a166662bb1630b99040e57303beb48d767df973b906ffbf0b998c7b930b06e61d42b3bc65fa31f2dcc82e86592651609df249172bd65a91281066a181b93b978b782b0239833e0aa6c88d880362a04a7ec1dac72ca0b17e331e845069db6654a418dc75a56793321d477177be71beec979b4f05033f803f8db91b094073928fd192eb13e271cb5f3aa0eadd37dad6d2fc2e8c87233e7b852798c396f611fee46db1e872e0328c886f2cd03ae82a6b66b96f0dc72dcfd5ace11f434ad9e34bf9aa2077de0d5a793825abbf0afd2aaf352e4fe2515bab70cd6bd7ae405413b1b760e6990a0cdca11e7a1a7719b82f4b8258b6761724aa0973cac0cbcd70bc309e56132ad322f190cdc4e9b129838cb4798a0e329220ca4dc31edd95b59fb2d1d1f938e3f82d2b5a8c9956fcabc73fc4496cf7169728f3cfd0f95230726f0c7362700dae410ed04fa591dd0575d12617712a418544236424f9a4e483f9f60c485a7aa32a93fe3a3601f4bc8d088678b8a0d2cce19721e5a2740b6d3772796bcb810504bfc84a7c24054a7f2dbc8f092b8b69f393372c7497be9bdd47e91bee186e66f0608a6544eb81a9f24b2e000f2bcd07a34007287cbbd62cfc953da35b320dacea4c7a3154f0f8e0abd9665da25d7cc51dd7c0f64405c14ccc39ae1ebea89e23a57ef3429579d9f4d1cfaf64d8b85b06ccd2a72616573c15e0a0557088a9a57e2716be27a7fcca5b456159c7487bd1ddde05872a13302f84a6e272c2c52636b78eb835c03bd108a012233f2ec26fae34c36f13c1d11c7cc51c7267edffe4ca63f65b4c211d4a521f1ac024adbd2db07047de87213a370bc4602ca34e098ba3317aed7fccdad07b74c1ad2dbc5e808f1e152667228db2ea8bf39ab77129a82c62c757cae0d0e81dc7104a264f01b5347a9e0ef72662d3eeaeee2a094317f53b8b480e15438b9fe4ef0b77c39c3c0f91af84ba60469ec6b2b4903698cb038291e22bbf5959ce21b88d2b814379e2099a8eeadff2a2fd1353a33322f931ede1f75fb2d7c446efac39de489e7196515bc8eafd2327250f5eb194b09fe66bcb19bb9a7520fb2a842e6fee3d55810a840ae04c7ad876d173f62785daaba72ee88eda336eefe25e3063510fafdb16fef05d6a9b93d4b645e5ccdd1926ae08c35d4612f7e3eacde8350a4a3b743a7caf166a139f107410b257d2765e39b49a95096ac3939dd2b047aab08e0f0973ed715c1dad05cd75067d26a4221003a1d171f42465ac2397023eb460b7d10775dda500e18da4cdcbf067f21a73d09f617665f96c50f16d200a9568063b37fde12856268a73c978c2d725688268013fe66b07878c07d5c0e9b2a682c7755228fb8fdad2e762a04dfee12c5c21f94e16da40be4528599ffff71081961b373e414369debf156f8672b66214e4076c25e93bed1435968a6b53008e7ce300fedbf0506184988da99f22c9e48fc4f760341c24d1cd9c93c5ff30ba41f0c0b5d09388cbc9fd8601bca55909372844e15c2a5cbbdb1a6b7419197f3e0ead1aa1cf9c94d74069439501deb64c81d60a047220848a06e5df48aa379c4e1f705d6059c095bfbed39b9de5434adfe6b89949a6612d477d4bc5f310963a1dbc4bcacf8abf6b4dca645564f57fadadf1a5e012154f03ed723e276f332d1620523f0671e454dd4e3f4e19dbe66dd37a67288c4837d3ef478db06ae0b8069bdf985815365f1c0952cd0e3b881a84c2acc20ae3a46b4ef0e847e16adc10bbd74588dfe704ba4f4a19f1c87f368d3cdf12f25fe602f259ac7a1bf3836971bbef37d9eaa1a99e5106c8da08dcb870bb6d09e726b7e5bcd02030868b55ad4628ecb8f0da31e6d1df089108c20bc3e8bbcaa14691659a170d9b5420f101bec23c55e06f7b396784486abde6c0b1a80cbb3d97d727e5f67353bba9bc6c777e09b97a06a1c61d3a8c14e69f549d0edccd620945872c1cc658d5d0abbac1a26f712d33271c456ac5001f81058bcf10061bc2645467292b85e839a0b0a0d017781f991bf9ca982775561249b05145e18ca3fe2d48a1c6c73724cf40d20b2f5a6ba3ae475ff2f85166dd809a6604ee03d736392682672c38286763f1f5f10c3e13870b64b69434d3c1ce610547bf83f67ce6741372c72c5287721cff303d65176d8f45b133e86756c4331875bb420f22b7c14643d23728f527e3aed8f61ab7dfecaad114bd4c69d584ad63c4f9be440c956ddd08ce772ea4db2ffb378cdedcd3857a7f799bf106f80d0fd3866542c7bb76f5384afa9647f7382d154bd4101a5a4db5e12920466c87f246285a7308214adc65dec15c172e0b9deb24853b2075bc4c36275fb8a01bdb9aab517a0d3aa487415a1dfa92721792502c7ce2defee713805c60926521e96918b353f4e7a402413ab6f4be752720cfc6a3925e01a0db09d59f5ef3743efe9bfd3c96e20d8e0080ebc11ceacf02538caf65708346706faa842f434eb135827bec198cab8f12d267c5efa3683d172c51a6914e70c01d8cf1fceb617f747a0ff5abc59c1e49adb83571eed8ddaeb280bd81837d08f9a2a6a31ef3849526ad3aee09b5940e6a652c123afc4bfbb7972b72a612f32a6a1f3de5be1866664721bf4375c4ff61409058ae2125d15adde7240194dc457137afe08e742ec0cd9a76a581ff921c3f9a652c9dfc278e6276d6f97b97ab9395433a4ebef41aadbe67a56e14299423021a11b7f62b0fd79867521efb3dd7a1a2a0f02471531a78028a889d01ee072ece3e0b2561395e26e035b295c74fde154760993b18c7d84e734f80a0ff166c487956cc4d161d8de4e82c9066f5a83e93d6a9dc9492e730c5f951bd22d43c155d187d050823096524ac7d111f0b8b2d3986fec56a6552a72b8abecfb6ba9b57ccff9aab9ea58ce75e6a09645786a7ae24340fe217a62363a6fc69de912b4ba5ceaf7d4673a08b529f4479b1720e47c287360c87c07147c747b402eb3f20a89845183fc2e30da1a75ed5284724f844063ed96215f968bdde1308c1d9616617a7de6389d48301bebf66aba9c7260248a969c19e69934786445401dc89091163b558600a642ce1e470b0f750c72052f1437fb8e6c9dec2fd995aa144fc2f2e07e202652ab2f606bca13d033293d6db2079ae184ea6cb393f6df0afd485cde0bac07ce72bd83897822ca2128fb4ee48ab256a99455af367f3726d0572702e278268892514dd090e3d82e21bb943e87ab575f4945130025b47428f02d818d56a46625489c37f126a9730ea82cbc1953d7f8851e3f2f0f56ea3051428a3668da7db3000c629064236c595ee64e167268781cb239a33ee568d73cd4101aba7849813999dbfc2636da5e60bd4ac1c700ef3f453ed7a232e5f84ee16ab7d85f585f5c6831367ed4807e2977827062417220c5a8c8aeea31f7c39af82164d14a3534fada1758dd3bc69890a1b575bc0c2a3344c44b57dac8caa801fc7b14bfc051da8c9dfb2e4e936df56c43393d731972730cc2ae2615e06e35ce6d849e33271236d95703de4e47b9bdc31211ab04030a671bf75550cb4d194f0d93803b8c30b16ccc290467f83548e920e629ea7bfa7229af163bda2db11c704d1eb4274ea7af8ae412d2752b931dfe5c810de74e027228094896226d2abc7518af8f311095ec6ef244fdaaa751c47c00e34c3d7e9f7250cc4b5e74e6eb1dde098811816d46fa9ae1fd7caa5547fb852ab88feb907503ebc63b1fb09b539a7ce4b5f2fe18f36e67e09ca80e2bdcbcc2b3571c91e7b23d06335d508d19c9dc6ae246a1c6640fed761260318998332caef52290c48caf111c1f87f3f89de58497bbffa30f0674ac533e1beff10c23bccd0b7f885ebc7872f435bf57fdcf6731acdac186edf20ab5fa118e8bab26c5e6de91728a7ebd647293c3eee1271e9e96f9636d4b04f9fb792cd7f4e44f7179f92a2aaed4ff5c7572dfadc2bc67c4713ffd5357b01ae47a0e81bb350e74ed4f1df44a34cabed3e54f95859344b4290db3beaceb71835737924f3133c5742e06f55e2a4e89b0ee372f6341a458df1780551fecbbe02aaeae91fa61ff659643976e0cf08fdb732a6b726e5b748f817b5b800c89444e3acf642de9f8b8ac33e31a88b9fbce72a5ce3872d478e63ca643ae3fc974f92c51e35dd323c2b5ca2cfe901eec238f59205e46727d8e757e6eab9874c5310667e625206b4a30278a4a565846ab7d811f4392b82a95516fc378a590d4978126ee8c8c1585d09aa463262a932d903f35f57bb99b7224eaa77d08d28c24f4fd39e1d3a1b9922a6612da11732c40eda54956f22139723bfe5fae900848d92ab9bd5156908da9fb62486e6b7ec8eccd63b995a2167772cbb8ecc4046d462b1a7a8b554ab8d91286d1fbf53756ac6aa2e69e87d37b1472449110b4dcb419f475f34fde37572285a9194ec682d06320da1f11d5104edf3f103054225e31efafe9c2cfb3b940bba13aa81f0166d010c4f635318d9d020a729e577ae4ad1627adfddd3fd11069dd2c52d86d09033c3e1f2d7b340c062aaa46fff9c1fcb5563c30e3fbfcccdcc5e581b72ef18544da35c31926664e66401d7234b55d06dc26f21d0a1a0fbe9efc7bda91e0a85b10601ef402c42b39be825a728d5ff676e509ef1933ddd26c6b6dc5fb5317771cf1de17bdd327c929bca2ea728f4974cb7b2acdc25411cb1a7cf2e1f8ca4c99fc853df08d8733161520481172fb81de189faedf2149f8b57e685a619b2ee4084950110260704a98f1556a8561c33931f239ef843a0918de9a9326ae78a0545f4be606817d896300ab77e12d7282365881bc0689bd042c7352a9441b7799fed4b242e082bd2493db5381edb2728ad65730863bd92a2eb2c5b0f1ae9fcc4deb76c2ae418bc9c0c58e9e247fdf72a8744ed8e93c732aee375a7a930e9616558ebc6db1f70f800fdce183c1586a72e36e30f7b7e791bbfdfbcec6e657274edcc0d20b857375b7d7eb47c9bfccdd723ab8e9a06748053aef477e6d5e827b4255e86c7208a23917ef2a9e67f941c6727cb315207846e63c94ead1b070bac6a466ea7633cb58f1e7fbdc40b15e815069fa15df6526e77e677664a2bd91436a6b8441442f5b7280a00c32824e39883172b981343286dd64e5a0652a83e44387983938f873cc7456293d92bbfb159c346f72fb821a4a51830944f1093242f42b2533df602ba2cb5c7663d9050c5f159172e119e5dc82a6ad8f9f27bebb4513f1906adbf8a3e8fb63780e0dd96ac702f411ae0b88ab0d87155782729a50cd168f3ac3eefa8e584d67ac1a99661a38355860bf20b383589e58aef01a1586cab54d58193f8a3c0601ecd79e5498605b33a172d37b5354f929f82dfa94c8ca711d33e8d3a06f1d46e87d6fb17feceb2c603c43f6aa932da9b37d4c4dcf0f19288868c55801d53c90d95b6b13ee18451fbb106590e9396bc7bd55f2d5c38d3b8576b706e50ba0af8c29cc64fcff2a79c42cd66ceed0e894cba1208cdd9e0db66034b000683dafa874a98bc0db4e985ae4045872cf3860a334e8739fbf08cab67c9897bfd846a95a1820ddbd45d122927a5bda720b9f921f76e4969a96cdda50ed1b0c6528fddb056584ab9651eb0fe7fc0b0e72703b24cd62824589e57915f2707657cd63491566d1e97eb7c7dbaf29c99d7072de4a8682549027ca8a7bfc5dc0e3575a347ff93e6cc778139e4834f07f16ef4b5880fce71a236941c56bee8984481458bb2fc0f0922a5446f12fec017d369424c228cd3da2dd6f29262ca0e67ce0e729ce01efa769261e4d557ac146ee61b153d2ed8563bb0f7d5b4119b5283e34b7123b98e505694626928c17733f9221131dc06428c66dd999c5b7b8afa45aee49ba9a8b3e31f8a5f517b05413d082bd6f72232e7d309c1452b4e3a41a2aa265807d27e6939af597d48ec6994c64ffc73172f9bdd8eaf0ff22684c938d3ce937bdc7ee4986d465c662bf5244c2dc35fa5e72a65c4cbf7f13dedfad3ba9516a1313b3460eb109efc68482417d9f90804eb7726c878dd83a03513e58490b32d3dbd3325d21a83c6c0ad2f745871e1c2ceb8c720db8e8c4480ae0a8d4350e75825ca14dce37db974d0d6c62dcb53132741fed5e7bad1a55b7488372f640a0409529893ca7cda63403836252e24065569970c3724ecb729cc3f184b21e6a4a5ab9331579f773d153fddd17c2ea2116b937da2872a9a1a0afd8f8290ed8e2f2edd2e5c231898e9a4045d7439aef3597524756ad72854a41bf07f5df9d66d71749ec7d9f60578aa01d391d246d595686c55b795b723e2548422a6b408d876c06c16d8ea1462a0a4cab390ed0391b9863065ecd58728bbf5aa5312804ecb01f55750b11c2fef240574a2e290b4285d1e5f48974311ca6a84f9e6fea7d821acb21c0cd6b4f3a4a407bd6482790b57047510bea86f772fce9e61c89b0815a418266e870e1605893117342bf5173a51ce3ed69fd4004720f5edbdc6477957b3f3dfa1510027741260078b1eeef7e4267bed4b6840cfe6c7f493ed05aaa632900537b5f5ae8b8aecf2b533accfabfa53a50945fda03be0caf430ab7881d65027a8de68d09d8fc07712c01594a3ce70515c0ea6fc62f4947795382094dc4db30734c9798b8a25eee74aba0e6af000495950f9802ceedbf1fab9717c797778229d1bb3b291647e30be4cdd0f1d61e11ea0ebbd6748abb7d724eedff39934948a0529ca283773bfd582743ba32d546a425990d1424c24e267292bdac52d6beb8d0585e7e6e91d732027f47156e607c004e80116770d95d706409a19df2d9d6dc0094a0bae8aedb481a85503de6168318e7310de267c0081a72c7da2ddb3b962f4bbf5dba135ef006ee5c31651e7abff0bc1fdfe1a8854dff2acfd0a516e6e3494f288e398e4e70885acf92a0541ca1c9e8f611e7f04c0a9b72d368d8ba751db52fa81aa1595c7e7a904514b6acda0dde3aa6e3bc1cd1a1da72da9a1002efea203448f6153390f8644ebd251e27092159c013ac1947c7ae607277b08b75fe2596d469c99ba956b9c2383c33b767d4a9df79e322394c5be31a720ce1e6cc83c8a32a127214297c5714ed26a306f727ff41e1185093d5a3800272711ccc0ed489048eb4aeb439217f24dd034da4e9413c4c3fc7c77504a5ce9f02df97e8486c4fe1578cf9c95560aef5ab80245afe1557790a3f499b51e817446cd13c6217eb807638bd0ad269ef18de8be13349960523e5510bf32813051e483c0dfa3a1d27f963049b6cf4b3e7f94b977f654473b38ecf490f7073f2aeeb8b5a3635ffa95a7eb8003a524a570d3eaf2d60b496979dd1334685c94b2506f26b723af0058889738c7a2eaa54bd589a094b4b25934e1b60f39d6027df82c00f707280118fb9def8699adeeb6606338c2906f68d3f63edb221a928a8c478bd317b72bd9936eda5837a11bf24bc6bbc9953835ea634d1422ec464ab3904c4459ffd72fe0159a8be269e89f9152d3a6e667a98cddd3996ed7c8234ed35c75b345bc5720cb0264ef4e04195d4c77f68cf04f4864bf07329101f5f73f0378dcf2d4059720421e190e235c1bf8da2bc1d13368e89aabd8fd4d6389aad1deb47508ae6b672b15e72471d4505beaa906be292e6de7a5d404404b1b8a63b71b84493f055ec30538568a2f40526fc707586865876bad6f3d42397a6f30a78736a916f9227ec72dec8e13938302f7e383b8cce04be727bca7ec978a0a72517433a23c9d133c57266ef81b0c3f2ac1acb4da82a0165ffb0dee578fd34f824f69e61287c4045be226e3afcbde6b25674bf42bba62169f1f831bb0574548aa4c8d62bd7125b474472c0743f0054b895134b82b8fe4817630e3a92ba409ec8150876d547ad9a77995d24e1023b2b167f9a0584e86a61f58d2f45356fba9b3ebd30f4979ef7a810f2682cd1a77be68e6e60b5a8a36d244c2f92f02fe5039a65c792687dff95b4cc057285584c503e5f269a18b39c5030b354940d78e787249d85ac178590daa6d5ca4bacfe94e43c72a071e085c55444ce04392b30d34cbea8a9a9954537a7d5240e72d7720f0f76a13dc5d1c06ad623a3af147469a03bed62d3acb0ac50de43d4fd7267d1e5996231d374f31da4bab3c49ec3f2e532a4a92b7206c38f688524ecfb729e3c4a055e5f3ae3fbe4b8394b3bb69522345d9b4ffbaac5b5cc1e735f4d130937a1fc1ccb06721205d67aa943f62551b399243b105ad94a930e7db8c4096172edd97041df9919aea9278d52c93465041580fa754bc53b468944cdde6393c172160f8d38a9cef6a6a2ca05441f0bd68031d68e45720ed839588864b01d31bc0d85a429b1b1be52c6749c2f2218da4d78d9f8beb72f05aeb69dfd4697ad57f842171b2c9eef851b954c4468cb89a745ccc24c7a67b592bec1a4af8bfbb1a4fc72d258e75839f3636d15b1bf80b81eef7781a1d3130a54698093f51155b565df72cc78a9dac15be5a4fd5c4a9206a43451b1b8dae465b1cea7e84ac3f97a2bcd723b0932c27d7cdb750ce59666bc13d25c510811451a051ff2e85151eaeabb8f72b39d19ec9fdb26dc906ca828ac27effad63bee03fdc6ca44b92fcee873d960728f154135b9dcd6c7ec70b9a92fe913f94d3b73f7d4d086f3cd2d42486a38f70985621a6c98293a66cfe4b2d9870384676977e01bdb24ca3dcf5c668b4744ff72372548f16bfa44476ea40f4a5b10c320ca0599f90bb62604ee8043c931f1f27288e8d9b3576f334c780563e095c9f826c0491b766f1edaff11f4b91d80e3301a0cc01027ff57d0f96d33c08c0f8bcd88dd89935a677df479f0ef0e242f60c772f2f3ecf144d57a8e8b13cbbab5d19834cca6168e53f42202bd26bb6e0631f61ba840d21e28f26f22e7944aa8e95c66943d5ed527717ed7cf2e5abc4e26be5b723cb9f4f8fa47992cb9e01bbecaa5473559540e9689640b1eb6dcb702644d852f010a5d9221a8cbd332a27ec9302acedb3a0106001166f4687036064ec9491f5a57f4b37fdec7febaf028a68287bec25c47082c3c1fe4830766d97ca646c596721cb3555d5757e17bf6332e7e4759291c0958786861472435eef61bc729f9a9722f63dd74eb31a639564b64e71a7fb05de31234eb51e6326f705489c87c5e8e11facc19738e59c302d62a1c8a5311235723d157aa3c8b57d89ed3789ccd274272c9f25b20fc55b5f1caff06b10f10c2a7c11ede42ceb8a20615209969f9e4d61afae86e22a561888046af3b8b85266f6d752d56279b7d2b75a1cd1c994269554864210fb6b45e0aa113f8b0607d3ac786984564c8d2702753d7de2d5c094e21727fbff6b068995229fd638c5a5be2276bc06c7ab78b448274efea55923562e366c5c380216c6e43dda7bd028e556b16f3977c4b5c5b4e3c21a7fe239fad18243dee5402343fcfa3d71cddac5a4964a9f51911d4682f4ee0bf91688741f6d7983e8b68f7d31a4ac3db7b01eb3ff360b8fe3ff73d71342ad755289958d30261fb3827cc6a86548b8d501b6aaaa90281651f282f5370093808f5f08c9572c1b2c33afc44e94ef1208a80d6fb40eee7bd71097372c27f5e2eb84682ef513fd28e867228953804dc6a52d4304314aa0b62b50137a65b698d9d7b5203c90656beb23272a6926c3ee4230a716c511717567e093354e5b72787886be7882bd56eefb8a172862bcc496f703337c5f103a1aaa7c323ce8cb7c1416fd49f0c2b5ea0c3759e356525c625f5d3e46238eddb46290cd369c963a8a5576fcbb87ca1d72170a3a60e07e5cc149ac680ccef502fd4e6331c7126c73571b9ee01568da0d6dfe8005670b1776fb5af78e987c65e86da19c1a5809649371cc627737986b4483edf40a4720d97a08d7588661c1b2098391a9b698ecbb9e2dce4b194901ae125f8e8e8cc3cf30ca4554eb12a46de60f6f5ce705b7d42a1c4275003a687e25499d1c038d605acbc0e77514146fbb91e3433423efb7d46be5a8eba8ee9dce4bfbc217b498254453b504605b2d13b02960c087926198dd2c653fb6b7525807deed656120de7720fac51040d6e0f4cb6df4431eb117ab5517a86e66ca078c838c364f41518b072f22a147662a315f9b3c590480d183a7cae83f8b9135428d43f66acc15c43e77210f86877f9d9217a776a5aad53a7b358c34a1196837c9814109297165c8fa64c872a30690318fb499d51e2a21039366c39754ee680245231a308d3e0153f1f4c995249ba4f468401f56cb859788bb8fcb5f3ecd83d4c9a033861f0f4834e5572e1bac69df24307e238fdf825d49d5574b3702223529c8747a1314a470e6611729a1e6e739d10d7c1efe72999446f27cd425c6e40740fcd432627e960d587c1727f5a44e4e4060a7a6dd950e7b6075870ade3a5028a93bf071acd77ad65e68c37aa910cc1f936e93ca23ceee7f2af0032167aa14ef71f653e33aec3d3a33cb44c1c2f045b7da547066819494a1068ff1a8f741a5548b4baa60fe3be0c2846cc7219b4821cef0d3a9e2201ec26ddc5b38270b897b5037d1e38c1101dc2af7b2972289f51d9cdeb395d6c9e4f1b47296946f7e4e77ef26a9c3bf56464e01bf8884479a968bc87c5a927f599a5a3b9e2d19157a9c189ecd68293cb31342a6789e572f14f9b48e6ea6f5cb0f395da25bb65d69164800c149064089461c7efc3efe97236dea6d07cce8d5019531dd1bb6db2e2e019225b7184a3406acc7060a536bc72ddf8bdb707d4ab4973478a72090bfce601882afb46bedf38a682d57203a014642f4e685e2282adf8ce6396718e679f87cf09db3f31bdf6239e797084bf4b65638165b2d0d98388f96eb59fddfb775709c9c82cd034e2323a239ea2a9d6ece5247a2a1bab6ebfae2c5ec4610a54f86bfe26f01460d454f41365807b64040e3b5f2fe7a39da388c4d216bc54afed69a35203d85366620d5fc9bb4ef6c95a75366a897e9456b595a3683b8ab6a63977505c2d763c1d1945366af38ca6f41cde2372b674ea819775c14860079aa487897042ac7b7208f376227379f87b20f9708572853323c1a23c2aad8eaf3ece195ea94e19529c0bc6eabf9df79136dad450403e64d8b54afc97201df4582642f2d97eb1fede8ed163c09c255f0cd62b9fa940728ed060bf565d3de9ba6e1e32aa262f7766d3e9aa5d2fa9a671ec9e94ea380c0fc5d209560c2221a125390c4da320006c7793a5590a7668ea5d1866a447c6112d0b2a5f48956ff5ce57c4211bece784426854a733f0d58989f2a3df91130b2c721cb173197d35d05defb1277542dd6331c1ddf6fccf9d63a0b15ba4f8eb24b46b4678bc7ad553f476af55e20adc578b295b7707352144c710eea1ba6ad2d52d58170218fcfb9ddb8554b4076a8e5402192dda9aea6e73e5a7272d49df299277726e980e11b928ac1ba2e30c96bb62ed99ef237a9d471e32a0f40e5fa652a986723ae70ef9951b10ee1e6de22f7dd8b61b85344013109435a183193adf0c35b02b42b8908bf1ca804807bcc262ef82863412eefd670b4fb69fab123ed36b8b3f1da80fcd92176134f4418f6425598246f255fc5fd02831ee8c4c33ac1a8b4a847242591bce712ccee818fcca7d78c4d168ef3e096b2459648fd018c08a40a03a720ecca5f6cafc714f8c64e63aa7b248343787e7c884168715a3e188ca3dff967257ba1e4d6eef23228c2c7aead05209fc6af614e177e97052437a1c1a41814e7209387b93740e8f34fbfffafb48a6d17cb3f35d7c50f0c9317c891e4c3b83812030569d80f1d4b67df42c4c9a2944a979be6dfa5cec91d91c7f088428094c170233e898ca3b7e730a5a660524f610fbfc09418566b31dbc4301fb4bdb295b0972b71fdf57376db3bb3f4f059741f5d6c4a83612c286d5fc12fdf7942485354b3a97e0afc50162dfe978af3e2266b8dc7837383049e6af41e111ff5e69623520074bd20bcd9cc65c597648b3e458f343bd3b9c60e656cd190be2124cd92f14dc01f7c3335bccf8262c50244c0062adfecd7a98c9db2c45c4d502677d848d3e7b7277cff2d6b3353aea19fcd2130cae098e0c4c40e77b7d7962605d84c9d1169e723d87eb1ce3cd3fe8b6747b16b7c31c3e2fd6fe55c7fe34218af7be57d81b307254d595bbfa338ed2993e3af93557e9f9498edd4d7d60741f1aa9d7879626c07297f4d5435228a282cb5e548f9584c282b67f7f2cb30f89cc6f9414fdd0ae2a4f266d07a62ec21bcaf75274f86f83013be8940823b59de2a964918a9337f1e872d69f967631cf70877ca1342fe2e2a9f9492c8e4bbbc5a62addb0958aa8341d72bf141e7d4bf469eb97f025e9b2c5907ad0e8553f241da7e6cf1c0303e11f9c727e443448591466d47798b36638dae53b2c9ee7bfab596c97b12c53a207ff84723f853b0d0f28094f00449b1f90cbee6849207ac995a65cd061af1bbdd0cdfe12b0da9ac9dbb79b9466a2f4bcf257164da3de39ce6b767de0cf9fc9f8f401b72a2d20c8117e552133d3154148c6cc62d58cc5f0ce45175e73b2c93c04d66ee06ac297573d267a7d7d6827b8371c4695c3f4634216b16cf93fa099b485f4a7715b77080af0776b1571c8a87909c2a674c25a7fcb4428936a640a2f712a9c6f1663f8a9efbc4867d29f22ccbeb781822754abb8acddb058501710417485193df972eab531a0c6f00a5ff94608338a1f1e74468070636fe770b2f17c0a228457d67218a92c5f7bf86d446dc9fbe8c3ea7739d66f140d729b8f6c17fd7433d82c7563e737692310605edca3b0603c65f125ba81b8f7d3dbce4ab04d7605e296c530724e5179a846a2c0812216379a1bc1a1e58a4bf70ab669572fe2c6886489fe5472bf73868fd62fa6b413ecb9232ac0d4a5073875d2099731e46b4a3d6e1df42f7258877dd63198629e4c0bd6d7c633d2f9e43863b813aee1162d3d7b6a79520f24e3c7164eb0851b639619f2ea7a713cb1b4499b4d1931b1833dba547ca390ef6601a3bdfee1389446dc60dd6fab35346bc4121fbf33ddff6c396f8f9bfaa50e72d90464fe22f8527522bed19756773cb567ae8c8e8d8c8b180e4464a6956f5972aa091339176a74eddb8b26d4b784b3401daf95d3e51b43cf9fa1c32e6882d46f333211a9f505a9a8c1200c96a987863ce6106f938536a4e40f6f479470f88b6b67ca5ea3886db23570a7b13c82588ebeca9ede7e9778334899e22cd66e0d655d71ee70d7b89b1ea9bcbbe77d7a5fb258ee09f3ea5b20fb98bc8429b81f0fff7238ceb40e98823a0900aeea428374324d1b465f5903df8cbe16ea8226ff72dd720e80d1fa9ec7bd1612214ffa326f4a6e268449f4a0b60c54f7efb09d8c27cd43565ed1377d84d18675599cc9d03de9f129133904eb1bd411e9d71db97929025dacd9cbde9c9e0cb54ccc885c82c99cdb42a64ba311f4d8d063fee93b3609d472fefa3fd8bf00ad9d3b3a8c0765db2ad3196dbdff5175323e5763114387200a72ee054ca8df4674e4e9fa6726ee599bf725503a69cb379ed3e2898fb0e2a06d724d6f8e8685dc69cd443075cecaa1d495e30090fe554d865deeba1a651ec9e0720ddc5f26d039d986cff6df34c8401411aa03621af93e02b203f47e770d8deb3e7afa8b59ec7c41335eac2a783867f66504123c60ae2795578295ffcecb6b307200fc1d2bb648123ee55b6085f726e7329263049ab27d49a3ee1f68438765e52a4936c409d050579e61f60ba2e9d04651cfc3d63edadabd355fbda20ff2e13b458fa0ed47229370bd35629fb7bdab8803a9e65794b8f4cdafc29bebe747ff677269da0f9fa888c359f4f37896aac59687ec093ccb06cac4e15d9ac7f15abb8a6e6b068f152a9d1f60318650f544f8ae46fbc973cc365a8f36994aa316a1c3e872584f418fa95a7697bffc0d7ec2901f1133c3f271f93d85935765a8837d75ec1741ceb1847ccb0b367958cbb012353d4814755fbefd46391f38784240386ba43d3baf07b490b57f54fe3c5a1885690d14707f7023e19955e7f68a2f2a368fc372003159d8fa2ab6d0448eaa9515219e9050442eb862da5703a48e59c13cbfc353392cf1912a785be49e7ae5685026d0c03daf0d3905a4b506744f3154662d277205f697695a98d4077347a58d85825c469af95d8985f3874ab517880b08f1497271ef9304e1d66317746628d9b4e8d6ee3117752496cb4bda99d7747865d07467829b95d326ec045e848e3418f92448737451fcfc030be11c772c77845a8df132aaf743fc9a9a2f011123da4d29024901af1c6be577c99db99a5285d0fc2f0204a8a522a12aa5703059b7b57f943c6d4c38445480fb8ba030c2ceb9ebfaab1972f01db0f801a0d14772f84e5ee098b88498cb6152225fa67070f37e1bad1eff72b189e2cf2f27bcd327778e673c01da491b7f6775eb4f718a5310fe67f7bd6710ac1f3857c7c75d91ef397a4609a5c0c0aaf7c389902f5525706febde42224904e5561c9830b751ef6d82c4ec0ce1a9806096265f58c0f97f3ce3a9a7e9ad86729cd66b2dac6f654279b21b4ee3bb94be9b42bb3d561d6ea280af31a242b16f7222d7c8a909994a888ad03767afb364e2f77523108ce4ad0146ae3be77806b572948873a705f1d154a159b7c65482816cc11fd16946bc289732269ff9d363911cabaa4a029aa800b2d3896aaffe0a51fc90bec9015833760c0c2dab5c9ada32728a9e7512e400051e305581ee5539fc9e5bdc60fcfa9ae1e5f1972ade0261d472bc95d767b0e7069f041addede327b37bf3bd770abc95b722a2132bf0fbe5687239a67e33a7e8d4c8158388bc0a022e06fdfa7375696304f64d4368ca3dd3bc285d5802042320a1aeb9570cc2eed107ff89bdf8587efc1c607eb43218858844080ba5268671f6f4768f278b82a6b62859339a0c0bcae9404009ab6c497628d372c10a3e801d09d23b8c1aa62b6555d816dc914c7c8821c9f15465be296cc0d4367aef09466dc1a23f3c10e013103923f3cb95adf5d0e52b1d700e135147ea991eef1d6aaeb505f9ad2164f6f49cad8b0ab2e17556efea5d4746fd8acf6e11b32488dcc3d20d9ee25464aeffaee74f1325a79449b4f6f570d1bbf9525eb667cf72706abe0ce124454c190b0581f44769bc2643a9f0b99bef4b1c2217d74cb37b72a3c9a7bf61c9f5ab6187c699a8d059d9bafbf3d238cfa3d035842a3315a327363678069a82fb69dd690adf517a2266399f8dac6c328b8490947e23e4bed9237233d9e7ab4150271234bb236c51f538a38c81b73c85560a65a7724da4759689725af10a0dcac44a310c606927359b8c308f6ade7f33603d3cb0adc8ed615674062de1bc9871dd2f084b76ea15669236465fbcd4fbce732ec37553c6b912d9b7721bb8ab3b5e36968ae4822abe707f522bebc4952a2ba7b97b06325a616fd8254ad207e6610e26d1c07245100e7eb2b0f28a413bc693b0ee7aa4eaa2c5072f693acb7590d65c86d4e7d87547d62aef3281fc65a5633182724915c1b7c5d57abf46a73808f17fce66734a6fdd4c74992ccd92f697445f993af283bbd37b2b2f2f6c79e42fc67fd78bcccc56f1b1beee040722d0ec75d0c14a77f418787c5e94e7660873f2f53a850013c1e32bfeac80833d608e1633fd45b375b79bce91bb29983fad66b1e557d3fd86520b74b1db09ad8c0dead1bd1e3e0bc8444e8d6566eb304e413c9dfa0b51f1369d617e51a47b3b12caa57b445649101648f8e79dc3841b72f5b178057c6b532fa7bc06c9d80ee3c8ed7c2defb0d80d01b8e2cc3178102472c2e6591920a9c7965625434d2a2411b57b858f807413c70ad56bf87932e40672d27e46904c85c3c81d62c18a181b4166b8f1b2c0a25b393378e8da93d5b1fe19c04d2692dc947fa70f4433b070d656dcb0e87dfba926ca8c325828781542fa5dee035f6d289a335767a1cc5346b80b735f89eba417e3afad9f813c63bc827a71549cbf74831c46cfb5e575a9836547f1f1d1a41ca48b0388105077b7d610016d02c80a2241079cfbf7d7f0109aa764bb1d415f03d4c6ef2c5cae5c0a2e304e728ec4ea8b4fbc4e5fcf3d58146f205c6544f9cb33e3d7ca791fa284c319761472fea795840d9915481ca4edb4f6179f8934140b5de03e4965fbc9cb0f0550cd2d05b511a5eedb5c69a738a0d662dac9f0e94cd0eb431e9830b43e3fb8d7537d197a568a694569f5c04e5e61c80e2713f5bb4aae1cd21775a3619bab7014ee7f7259dbbe5731f863895f6e52fd2581614e86bacbc39a706d45616e752b451b5d146540ba766c709a8fb74ebdb0a9f3eddacfa4e852be41c12dbc22582691e6d3720fe1cc1e93cd8c6b168957d7695cf12526f12f06f39faa4f7010d8ddc5efdb729b6b5a079577b23e6f6b1b7326d06bbd9dc2ee93128c4a41d408458588a78072743589d265e0c3d3e3002d15984a0bf50f718e16a6f0dc6813590727c16a67728e2ad795af168535b17b790d247052954c01aea909ee78048b07ef55cc68b462fb03f5ff839b45efc0d27ac32017adb045f6033a130499304a4d46c41f296672daadeb027047079264727ee0142487abbb9349220e556d933452ffd8356bab29816a16d716db6505634a4c4799d7ca491f7e091dd72112b622a688976466b34df97db49acbfb9f21420fab48fa6e137048cde4c04e3bc6fa8e1ff3aa0b89cf010def24d9c9d60181f7cc91961c5295728128d387301176f97cee7365a791df7270d1cc7dc5211609499d0b0a83295fde11a440b5ba31bd2be1b0f5ea41bf3d408592d7d7cfb811b24fdce81c0156403b34fd520e2fb6c86425cb3194ff3c4572cbb64d6e16ceff2022e5bd2a12f6f525a8941ad584d63d5bd570b8ad0bf5074a6318277123996494a0a60b6c509d7e9a9627ce256727b3b1a0490325ebae6d7259891cad39c11947986ba0dc8cc19f03203e2c87e8e304877243732ad95bb2729334215de31a2af98d1e6113c41d9df2f62af20cf513dedc86bb9a8686681172904ba269b1e482d011fd72a1006f0a97e1f61736aef406bf30ff1a3e9db57972a9722112d7f1546a5242d4cc016fb904a499650833d19eae960d9c70ef69fb727a78378e73f756efa6068a128d3df6142105915261d06946c74b3fcf212c35233d18b72343bfb84f0f29ef800a56047f68f6ab2554697fd20f17186a2cedb34f64435d15ff8f95378b85d14da47f3d62f51e318661dc60ebc43f8e920e1aeb3c9322d62d16208ff007600eb4c4f62a910c731fdef75cf9f5a240fb7dbdd6320a55d0befb5636055edc6d336c8a8f51f8e0b09a16dbf80b54c1a78b6efe2626728a6930e1488494e8bbe6ae6a47a4f31ee63428cb77d2de61708bc58ce66b3f1f9e5d143a3be96b09c217b23c87ed8a624059d657350dc74aa4ca0ea29eebd6728c4521b50c28085b9c6dd8a6a15ce7ef8ca1811de0455dd5cf1a4060bea00b724a7ded50c88afe8f6f2bd5d56c76daebc90e69241e39ea8f86788279504d1a43be54cb7c0059be6e9132c7d01d1125879f4d61be39c20c8d2c5d1ac37ebb4d72ea698208c749a7d97c64b50de7b251563e955b000f6e10187640d0b8f38a7e2cce80bc579f2978894f9b557f1fb09f4140f4ecfdcfbe322dfb9afebc54029f72ac877261ff75618bbade6f08831fa20275a77a8849084c4d7cbc2a275ecf4a27cfb4e1fb764eae35bf2c36365dd4026a54e3f3fcdee43b00698a21d1cca10e7217fcb6d27c3fc8276d79ab1c24f84f84273be5394083f401396394182bdb1172fa941793cc7083ad3f3d26a64cb30df02021c77fcefbfa2f23702c0ba339a272ff8d292b86b2a7ce4854fb5646fc85166b9ec448d7fecb5f542213cda45c9b0a80ea1d600d29d34e75a1703e78d6c2072b9edf1988464900e693532f48910072fe8f02516c0f541721d13dc2a91567760101d5bbdeb9bed1888ed8c1c416052343b5ffea8601b7a943a1d1599db59cf0b8a0009fbf8d19f3c47404492e2eae07b5c81bc065d30c4374c3b734a13bedde7cd460525e141417d8d6000908e28c14ef499b433fe0bf4c623fea6f40e7d1243eb0820c408aeeb2df2ed04dee17ec729f9b7178d2fb5282a2a713316f6437c400a6d48c164e273f07391c10d23ddf72906ccc8ddef6b26e49f3ec607d232e07dc298e15a95590021a40297a32ab2b727b958d38d4f97d5775df3d956c76895a6055e8bb791edce48fc319f35d5863726ccdda48df56aefdaaa11538f37cb8c965c8510c234ed84236966bb1b94aca72c7b52eb62b390ad8553daf6f590acadc887542fcc273f0327d010a6e22267e723c7c2de6f8ee6a7f6fabf71c28bf946491e26a5f649f14fd0230f62b3db96c721b5e8751e7bf83c2f8ae01be01dee02ef26caa4095290fad504ad727d6ae7872c27a844196106a0cd55c301a382a927a4cd0e9940e26e9ab7ac0ad50f719e54856f32a18d5cf86666a8e624322950379a1544fe312497d1ac33b09b8b20e2872b8b0cad95f05eef18940f7155e74248173aaca192b386222fafb456d259c1572096fb0e6c79dc26381392b1f1650632b2f4002bfa75c844083b168f957e37572fc34d4ac1fe96f8fd8221fd6108a96e04a5e1bd1bb8519f90aa2c60370f3df72dfa8e7ff5da1eba98892a2ab4bcf88e485eee1c3ee23e257fd30cca687cf5a333b87beb85ca84efaf112ea364828792c8a506341c550cf44be13f1ab12c3f9724d1910aa20c969884268d0152bd88e87834c397fef8638d231a4c6895f9e2e7239fa4876758cdaa503cf741a117654c4226ec1ed065e97e41ba2b56c08c9fa72973219133d11ae0ec49201084e991fd573afee435d8218e56935f261869a5757fa09642cabb372f85d3a583f3ef4ae7d7d3ecc280ad5f664593208a4ed92167297b77e233f79bea71866e8a6515226ce017f3c842ffe65d68329f9b0134f1b721183530388d4f51c2e626f2af721005c88641e768dc1ed5f80bef1732142bb52b8b5978446c13800c67a98681a9f3e55263fcec20cb221821e3b20713b71360342f6073d7ada45c4522d198b127d9123c9de805c6754992ca17073483e01cd4cd96b10d6d8943c7ebb6be84fb28e2a1e2a30f7052ce3bec26b47395951e9507272442df877b6d6eb8d473e3cb8f3d81ed31d970cf06af968c3a67acc0fc96572d9494fc70427733f3a6b0dd45655520124208b7b7d7300891e767473cb294921105ecc8269ce39880b0b7bfa8c9eee5c2ec417a059e16292c9f3e4ec8781d272f0262671080d5d9f2b757622f337a665e1da1fc409fd81a992c68a19bbe4bd72a0930a607d5fa48a23a3d7ab067ed7dd5947c71357ec7f5908fa9360221e331e843dba0ccc5f17d91f48b6b3df581ab50164aff933ff7934eb8af790869cc772574f1a15c22d19bec7c367dc78e265cf367f361d9e18a01c08d7947c39dbde2799e5373df6bb7516ff66435c0c7d8324594be15d3f4d6d838632e1b7bc1b4172e43c8804876213f8f22938db6d0c639029286629ade4560f90bfc4f2333da672e713ad0032b11babb3ebbb8d59823641d8fd83393b9dd65479d60ee719f8f9729811b53fc4b8ed954da5382dc789860d2182f2a41c7f756c1c86c1766916bd72b6fd11556c5ca479a678a0f086d4bfd46bf2683047aeb1a1dba5490f95c1cd3f665a587be14328837f1536ff997aba9c3ea5988d97de7ff18a4bfd015c9ae06c3a1173111ac81b6cb09b670883867d8e0b987188f6053a4e13b4418b4d7b3072c0d05a1b6e799f943fd8361fa8e78e1aea61122e536fbcb4400942fd63c6826b958c3f8f347c684c4aa3842142a1577dfe72a77a65b8f54408e8e3f2e07fdd3cf1a767e16fc79a3821e3779c3e52025459575d4fbd9ad5353fae678ef50e99721dba1b8ded0d212bac31a464015d6f37b33256822a59bba133b43c6d82858f6b4acb581ed92ee83510b74fcb3154cf24a79f37c72033c3911107a9c670587e72fdc7bd3eff68b242749c37c8175087b6cbe63f34709ec8fec8dc69ae21732c72035628cd723e52280a31c8b43957965a5347caacc0ad4f99cde3e741275c95726856246122273c739bd6a9d38162cfcf0d1d470c782a7c96ec96b6a5fcc308724bb555aba0f8b6c6a4d947c63ac862fa05c48dd94f2d2655e56ed95293b615110e88a565e2c1bb24edcfc0f484ace6dc7f874a1b1761afa2a37894055e20b34d68f9508805ee954ee1547985d76f43270f2277a40b0564a656e34569dc0dc172414005e243a2bf2707e5418a78e54e39c9d7743f8b6f850dd0ca28fb88efc8725d0d18130b6dc1cdcb9537021bc5349052a69bdb4a7015a3fbac277baa19da725dc415bbbe210f1b8e6844672217b448a479afa4c23f0b842cc1ecb79e2a89720b0f67d3ff181cee435c44f79fe5c833ba1b131e9f05a1cd5a9ede9d35ff9e728c4c16ff706d01b499f81fe06de3f4f7d65a613d91eb29190f192ed78c7ff472693e94d9015d30d606a11db6c1e83d2ced1a0eaa6e7c31d8ef88d039cc415472f154b3d733824e665ee762bcf2775a29c2ee2fb285a0e1bb0b734e87d986143257ae9e06338e8fb75e734637a13a4d759aea6f76b0f24ae710488a6f593380720ed9ea65649c544636f954ee30147731e43dca7381adabf6a986a7e556ccfe729d15e04fe6813bc1a4600d390af4a3f35d504284785834e2e74a21a488a1393e3f1b28a46e389d4dace408c230fedf6244e4fbfe93436f2e96c8608b6d1f7372168e704e1873a8898b11d8ee43db2b8d68f682c3b9af45e0b8db8727f9f4c372424ab0bd71421dc6b108d3a77abfe2eb02dcd30c035fdfbde5d50731669a987211074a8603ed24a603cfdfe2dbbb7bf51dec5d91989ebced0d7ea6b8446bfd2b605ca4fea7f438c3a9bc8358dc176d16a714668562d6fd1aedc91b6c87875164271427b7c970f8b5eb79cdc1181509665413535e83f148ca831f29b1ca2e0e72f1319e9317277d4f683e75bf80183a50c40b6371fde809033da0edcc86a2df727dcaa175495e4fc378c321b8131f25b2574a9c8f583c1749ab8164ccc7fdf8727459308acf83dd718d6133e7ea300e061947645ba2f362ddead48e09c55d89631f90ce433e3cefa4455cbdb516170e2affa0231a53cdaf3ae6ee829fe9c96e3b9d50c0719326075faecad1b6948f0323526c8d07286f5e2b90dcad658fb5b10bfe0415cfebc82dbc04a6450d762534e36c0bdbf33b3937507fb08153f180665adf231dd77e8afe7fe3086aba58a6a39ee3ae199499b7f0ed9519f74a3aa60a3f742e35f445949970f93564285d24f265ff631d43b4f54cce5d78879a0b37dc72f364b0067eeacb2399810188dafeb9ff7145dda15da2ec364b49c04e31475572dc89b918226834c687bbacbf09c9beddb2ff1ae6aa16952e6f395e1540375b67f73aa55b604f026c7717d82c54f4b7d13eedf86b78a95881993cd453962cf1723f6a989f6943b7acd41a8bc34b49ca0944c11babf9726ef834d8e710f9b7b8724119dea936853ae06b974bacb14a3ca7a49e76b40ae7e944f215c917575c3556ae43951f651e16c287004a4c8b73ee193ff9b9beb02dc22493c4e444e2b3482d7bc596a13b3821eb4be22e7bf4320b1fdb52580a9668d76e8b0f8b04636cf2724cbce206fb26444b663674bccc4e2a969e35f60837e9221b4c5505e065202653f4dbef3bd607e9b5da19835eb65beb0d79e758957bcb8af68ed8e82c6ad9a65a8ab6a2b1759bd0ddc2f0dc3691ff686a25a265ea1457cb489579ebbbe95fe572878dfe4b1f8afe4ed73886218b1e66dbf1d58e4be4c9f98a38bba30ca9f8fe7214191debb14ef19d8c68c3c28785b127747e5f71b3877ce8718dcfb6b0bcea5237f9a77b3614cd7f79662c0f40d98100fd63be85a51d232bdba7b5514ef229467dc3c3fe06eddeea5a7522f3902334db9f5f9e7610ea3e0d94c28c58c1f41f72c2cf4ac634a0cf6046d5fb4bcea573c4639678b17ceab9a503291952a9f16c7272bdd0f61aee76fbf6e8ee2e0814167d9f799e4edbb71b14233833d6a6ebc47275f4c9df2e03b7827ffcba4e30af286a6ed61a9f81b0cc102ac31f69b652a672faf41041ba241ea2981e5da19154068709401f6425884d517a31bb828c3bdc72e53f686a35d9ddbad13a0e524ed3df96b812f71759faa1b98cbcacd9fffb9d70a7c7d3aece55a344de037eb2119724b251b2c54c5cb991149e6ac40fc15e077251046b2c8189b9a6fa334cb461887886e75f07ef14d37859f4e17a0dd85399490b73631e601c7e43bc057d73b3c9347e9450d7d5c21d9d9987d95168676029718dd12b589d82982aa79c0ed8ae35a4f6bc81ccd9f4c90749d4856d50e7e36f7253b0eee595fe6bb5d6b9c1284cd4e46d8c861ebd6de360faa4acdb043acfd25052b13fd5a82ebc370eda1c84fe966d6021775227dc21189b5c0705901a7f8e23b3af30615c13f9a1c0dc430d17b0e229b4951f9a685ce138cdd5b56f0ca62d6731b758fae15d7a9570bfb332f88f9eb99ad0c1c03f13e85d6ce634f963f9a57270737b23576cdb24bed231fcd7cf70bc05896201d1e61e36b80f0762894d5032280dc11f4d181d6639ed59c8f713c61689e1964458ce75d66872c288c55b0f5597c2d136edf2a707ce8310528580efd18c92e7d478dbe56660d8b32684e1b42d72641e464e873463b7ac3f7dcc9559151e44fd8846085d4d2b25153a2dcfe3362d43c539c763dbd63549df085a429e1feed1c7396480eafe460557502d63b71185377d77b54354dcadba752642d25345a58ce8a2d10628e3316a2eb04d07ef72b66eec80a85a648c6b2acbab3cd4804329043650716f7c2d96154395b360ee0994afa257a44a553cd5bb22082650276766bf05ec2d15e341f87a0fdf52555800b08884c01360d39c7fbdb022098a0ec92f6676a475e022b43a4677d247269f3ef0f8c2a1e16c1aa2b50eb2b947b95cddf189384e6d5a14ba41dbdc10bda989728d323600a725654acd37dd7045fbaa92db03085dd66d33bc0ff94abf8438fb72ea04516c2a56d36a979241d13b9cd437650d1d9a3e77a9c8edb14ea3f7693772aafe74e89d973c9a3262f2ec329a4fca16dd2eaa7180304d4077d4b80b03745f73009a80182f5c60c81f506d50f05b260a61607cc1ae11491060d84c71b2d12387cbb3563ed9881ca48a13e81d21bf9ae31494d20849ef2b1f363a0dcb289872dcfb3754ed5fd3e62779ee7b83157fac2023382c72b0b1f3510c3547be6ff151a331ade49d359ce1c1b4f5ec81019dd0ef5863d3a41206216d37540337f40772795afd43e8496e30a42a3c0d8fef962fecf82e0f0d19e24a35239544d44ed2726b690e76a0f9316745384c6725a9c67ae0aacdbc25195b5356440595338b1d56d5b21dbbbb9a5a9939120d2e9bf5fbb0c8b18d0c511be21bb5e3ea093ace7e724f5e7725b8d36e8231a76a4726b8461ae94feb076a23cef7bdbaf17daf2d9872e37892dad66bf1a8362011e3fab3d2b893c3364c5fd57193e89a058cfa6b76290b47bf1dd444067e1f377eeacb02bfb104a42ed741eb63347461e14b93fa4d2c9917ef2dca7531c651a01d8ed6897fd6f947fe22debe3541039e5643d987cb721f9d5353e745a2a4714cdab77edce2980949dec2e575cedf4bdaa5c9185170729f62cb1c4a3f831f7537008ce7b205fe766317be759abda958e5163f896b8b4e3edfb3b8a2a1dad6bd327c7cf43e7e554a83a0a3539dc726af53e6fb7f684041fdeb0e3f4625ad0f215703cb4038a81d2a4c486d713e24d6ed1f37d4281c1a5127eef2967f70e7d78f0934d6d3affe6e5e7f7c8124d74acea99220cda52b3138cd43ffd593cf731db1bca46f36c136c83f1116f83b8e4d603a59aa5a456a630e1b40bb1b53d94251c55b48a07d59289edce0ceefac79bd8eebd1428b03f53e103a5931ba76a38c48e8cbca1cca25fa01bbf080026e6bfff7bd9b27076cdb90076a040397d983880cc4a7d82446dba5374dcd0043aeec7400fa2c4050a051fd6b284fa8fa6ae62adeabbfaf5ec853e8cc924808b03dfdd0e0e48e7500cb30bb72fae529ee4c9ecc35689018e9f48a686f9ec952106a06708ea6bcfc2f656a6b2166a0d63c659c343f9446033cbc515a5c17210bd692a8b8c8e435acb4fd23c31695d650dbf8983bb36d3e8074ba88cac6521eb339b5938542fda3c34bfd7df77255d632b9bb33f044e1744360bd4307f246a18b0c66e7ddf2fb20f5e029bf2a72d99877b10b1034e6ee8726a1c89e421d413bbd7341bde7b03048793da98be30e9926b47df388323add81c5489bdccb9f541681dd7f018582a82b643e21e8c606114be0fa8a9d5c1a095ea37bbe6c0b3aeae93dc6d4b97c9eb9c030d2ff83b1721bbde28947dbb182891279917f998ea3bf12cbaf9d707f606ceaf98b899422416aa5ef71b8c7ec848d844028bf754bf643e72ed9f14abf3a989deede18368f72ad4fbc13b2cad0a322986335ec8b1e74c9c1cf0ee199c24f290a6d28b856ca72f48bf11da3b298afac4ad796a43d3190f1bf6f26a3a5cc5b19e1b77966883172f7222f9da2875352b5067b6077276faa72cdffa3748f22ceea06ec90b57bbc6f1342b910085cccccd8c48147c2cda726bf9158a6272ae06c7cba6e8c8c15bc031f6516c6ae3d8015c627505d9280af11bade28e58616ead9fcc839461adcc312f193a7d57ab46eabf44467e2325cb79f65168512f6aa83cb5cb4f4ffe96ac310d6a35250185409d3ed4fd076b4deeacce0b24331deb6b66485c16f1f96a0e51399ab528d2bae3264184625442cfa6f1f39d091087ef035f00da0c11d77593d72e21ed05aa19c8358afc1111dab98710a9420bd731f884336d862c4f25ea0b61ded1451af07feed6ed9efdb57016ef02637ce680358771b6751de38e235f1837277b00b7a7b7786e9117b8f74b55f5834f2286819b0c4eee38e9fa2771acf8550f19a02d378163309c6cf94062fa973049e31adec246fe2936f403c23b5034d6537ff1f6e2e6c8246fb7c79bed35d3a3ff681ec544cf22a5fe7d2387d3fc8b5487241982ffd8c979fac74605d1d3ac3664b3eecf2ca5731c9f13298cf14d3f322156e3694a6582179e3727f26c087e200ff0dec9db1a17c488a9e5672ae106472d2c0e37c57f6d068cda42af6cfdfabca79b1606e3995bee73ed553bc6e595f04494c64e5aa458ec5b22b21c017a99d603168f2a6da08bfdf5ea9c8200be56604161dd0fadb8a22a1b9f48c24a7320bc2c164a43c147770587ee56966430ef01017aebdc1ce95dccb37e4b40ae68480c268fff66ea3a077f965192a349606e4721a81872ca0b9d66345665884145421fc86fbe5bceebb27ba534dcfc149affb683cc7e7889b99c4aa13cd28db610f63e11c6d42aea54ed972c313137b1c9d184de93fbb6678f9ae5c2fbaf9d02c85cb3147196da372f03e4bb33773014c754272a1ed4f474f37c48a3c070cd8cc7ff8cc70eff72dc7ee2224597b1ece4829fe728f8dea585f247c06e84d0823cfaf12fbb6baef3c589e6a0a456200cadc4bce72c1b6beb200c50ce3b542aedd4645ad688fe1587e5932002e381203ae11e3fe72c382b3b5a0402a808c2d41b0a6d7d74ebeeaf036b4f8ca36ef3b1de75a839172aaa2f9ef6445ae3544c18cfc1e5a5084dd9293a0f92554c1ca67be9c32325d652ab780cc4a241c6e4bb8064fb0310fb83a152f04568aa275b6ee740b820d6c096468120504a231e345487bcd3f5ffd81ccdd14f8c65c287ed55f1e11f66ebf492b481721465d83b23280df3229e1fe7fc9b48f4cc4291abad33bfcf1e4471d4f42a1a8b9f0e24dbc5d8d5d61b62ccd86a9e48da27378b05637728cce24df8f20b841c4eb8524badcafc5000da73c0632866f130f77c748005f459fa83e2a3d720fac0eb752ca4685cc66a049a5e0868c336914e7b74a13177e1397639f9b4834c08a36f1c8bcc88e1cf633fb096627abb6ee5e784f501112d33a454e1baed72158cb222f490a3d6969f5b06979110db24af85af23f8b4651b5ce0a7afaca0f72922235350b0dd3390b9050386337ae8a3cd7ce74f3d29358d18c686bf606d37217a1b2e96e0360af97a5115fcc7170ef3caf0228748eb95430b966c119a1db672ea8c57b586c2c1c36298a8fb9292dfa35357d962b3cc36e2c91046004fadd721a36a2705e3ec644eea1ab7012eed29d948a45f5a2abc512d44ae3f32968814b1a86a72c190036c1bd8e820432391194fcc436301766831f3b21817abca291721b07f192b485cfe97267beb13557e961dbf5f038f1f461f5a9a9d4b8e909907085ff034c01b1ec7d9c9ec380311e3b7a840bc7dba07b2f13a56ec4fcc7c7597200e1f028364ea0f5b76edaf82d07aef0e711b8f40c9112ec7fc200a0d39e73723d84bc4efe2ad24883d941114128540a3798162fac78eb6f4eddebeafc73cb72a961dbf9b88c34b37b2947f7689bb491794898be224d9133664ab8f583fe4123e21fb50bb2bf8ff6a728111f6ac684a53342be739efd5755f06eddec52842d72c4a7afe993494652dfd545b4e164ec850ad972d55d62f15f9842af460ffba563f6fb60d863f3211c2f20657d6585acb324227602281c98dc1fdc77e93708c335dbf219459c515e2cc19bb81bb5b03aa833885094760c4a9b5a422d10e901b072eeec391671d2d729a317b7ad9496b6612ca8150e156c361013c6ebd4d37ef67235cbd0f1b613ef4cc097b0b3cc013aa4e834832767ee2448962ff652b1165e72500c7e2ad446070c5873f1765a4310ba5faf84e27c18e0c938ca4e92cc8e6c2d295230b47c5df0daea8555e45d31942a2dd07b1ba9507ce78c75a4d730468a72ab52cd81e3f8b98e38cb0629a2c2fe65debb07763c4a55c5c8e3065ae3ef3f6f9eb5c069b31404b7ebf2095919159c4e5708bebf0233c24c87677d9a7378bb728183c79e86caefca3fbd6f7988e7628f793d76ea710a6db76f6014aa4bc2c07234f587d3bc710e25c6bde49f66d9ed8076752860a7f3d5b94845b00ca58da6726b09bcdf3f6c307bc13041af847752b261cfda443b8bf60a5bfc033e6211f17254e347fcfb6d40df40e0a5790fb45aab5c5aa9481c03f764f3479b936967de22d5cfcded4db5857a63a2e105fb641b2be045be3a12f471f71b6075961dda8414c3225ccbbcde15c6b55c9f6ffe3d74aec89d9cee469e7b72a634326993f5c73ebbd0ea6955743107d1193f2de927ee745093a85fc1b50661c966a27f8453fc723d24b650901db9cd90d5f3056eb0cfd728f8526377b102fbdcd476ec73fede7249eed3acb6854e912275902e249763bc21150a03b8c63d0fe31f972c3c4267706e71566e0e5b0553cf88d5a351fafd9f52634f4dc7592468febedb2b33d868665ae0d463e3f2fe0ccc5fe96f11742145f130d083d0390b27b698eabe9b9b5322013df02a6bb3223d0fff211f4e4d2ddbe073144c1347b13c5f727dedd890f240841d48ec81de42e68f3b830eeb93e94a60abbc8a769a714dded3cec0adb1ef725d96a1489155968e9731fdfc9ad488579456c5913de17d8c4ede354e785f5872bda59d3ab31961912dc19d434e22e1b65a0fbb61333a8c1b512728163e2cdb3e27d530a77b87d56eb8cfe81d7950f0ac3561806c6fea1ce28bd4ee0334b8ca72dabf2f3e583a8db1a85a76aedd825a20ac181d08f36ced4727273c1391b78772e677b3afac59a822b4b4f37209773cb0b31ff27663f47bfee71612452d6be272ed160190e3ad4666460e9b80f43146ed3138049adce938566a274506c71eec72a508b0f34c73e8a9da5faca787574d3a95669c2cea0af2f9397392eacf14987226900e3ea09a50da0d577e170554772e38e9dcab9cb20313cc9e54d698cd0034cff9e0d6fd26554b5108ab72953ff66795906414f537c36d69059d52b317017260824fb3c6a49d338be9dec9c775d47d855b1831b80e78ed911134b11699df47529a1c7be8572640ba709385d36e78a4928049a39ec5fe9e0bf01e8234c19f0eebf2cf71aabf9f7c66bc55f581e48bdaad9b29483e000d2267146e44db1e14722662886c05ba8ad3373cbeb2f3e6db1f4ae18602281acb77755a605a2cfff172ad9c6c190dc2df292803bf30e595a99d6b2cb87d43a291ef4cce7e77ae3a5b727f28527b171208ee2d7a82040742083e1315b0bc497bc367f7b986ac9a87d372a94c0f4686e2ff80a634b73913f56ff74aa01059a4ee11b1f6aad0a74a1f4772d61c508e2ad7ae2202e5935dbd868ad648d605bdb7f91931d5b0bb5a8e9afd72a83fe264c3c11d77e0cd65fa31dce9e6f933dc85f7b287dfb6d8fc17e781aa363607fcaeeb63faab09df9922e501e6feef5660dc73b92b2e11e4ea7c4567a37203512e15e170552efdc3e122687d9c9b85168858e093c95881c40d13258c6b2575aeab0d36c53f9dde0ac1e2cd0029ba176158de405a2071854b762e76a8125d04214eb8b594301e09ee76202b55342b563565d14b32e6823b4dd78a9e5a26564b8c772e6e1bbb49af2a77f05897c59cd242729e40e7644938dce6dc47e770535538a7fbf890568d469abbe1806c0efef2d69063527f4dd1f9bafec534cf322a47b2f8116a6e218e77de4a23287bb5114c3945e0b24ec9bfd393ae6c32ddcd6fcc8ebeca4ddee4b1b082100dce7a28c455b713206cf53b512c1643fb984b247236bfc473a4af8ea83d892b30482eb2b72b708a4d1c91e7f3b4fe9cfed748166666ac9a63405c580e495a3bdb95798fe413491223bded6a14d12dca6626e46f72ea51f91a2bea1dbc04b6e90b1612d0f3c4042501a643db57d15f59950845a272a33927b0260d746a4e2b60b32202f722a74f62f4aeacb12948d17e7f3407de00497c1adf3ff0fab91b25a65c8609a40be9efd9b10bde4ad2e73add83f0476a4c3905946ac190325102384c60d95e43a88ba12daa5eaa2858d7b538fedf06d505822ed94d77e5c720764f5b91f81cb5221b25af942cd779d32f551c78763584727591c1b9bd190d1564ac1eb29bb4af0ad7c63f50ae90e3a0fe0513534989bc025083af5d31ee84bf201e60381d90ed191e743ad90ce8251b14054c7b2233f472192681a4caa325e1e2f6d8b8db1fbc5e9372220452edc57f2ae4ac14d6e53b7277f156d0c7927d6a1f9cc6ec35f06fbe8fc712b65f7386734d59c67a9f3f9d6fa1fe001dca56c96a7ade9a95064ca91a320e96e7fd671a36c254a48294472054a8ec173ad3cbb29041e19ce6776fd36ab8d67c0226b6240b7554762c61181f72f7de5ba7580cde29770b904bf9eb8e3b90639881b7a95228c296d4cd8c5aab72be0c198bc16e6f8b01cdc5cce592abcecce6c2c4538798c66153b3894d8e02460664bf95bf974929b04db299569f52deca554010f2009fb1ef0bc58c35815372fe37afe7a3d7edc4944c97b71ca32d48edd79827e970df8a5da12967cc587b72d67f3b3e70c71fe8302597e43c9a6e93719d7984c729cab7dc9572406452fd721459e41f522da0cfccc90fe8fa792222394aaf7d0856cc4803cfdb6b332d2e72618ec151c4e51e17a92eba7874584a2416305fbeea56c07be740e63e8a2a943e38ab0db79dd8e17d5b3ce42b73083df3c45ab67b457f78af96cb7de3ba194f72b6269c2b4393586030f395e7a92bfde8eb3087cf2b0ed6198ea738beb468f072335aa8027059c78712e9cfc8acda313508a7eb923f6fc340f2960d9a6c988f614a29e20d4a756265d182349c68edbd2e06aa79cc3fd06fc537fbdb5c66407e72bf94ebd912edfa5ddd7767a2c9b314e5173c4bcc3a0d2e7ad14e7355993cd6723cede9cd31f322c182407f162ed1a01e8cb1566404ceac6d3c9913654cf7ed722b0ca8814ed8d38ef48645bd02c917a37f81b5c11fb90f76c1aacd3b28e2ee723d84eb93a10ba3abf928c87d1e10956bb6fe9e3b235adca76cc38c71061407723f272f9760a31297706f0201bde74084759bc0e556865b4f37725d68feb0a37237ffcfea9419e5672d3d4470979aac90efeeaac846f09867ff078c816b76400c2696142c9d763f31ff3bfd2b6ddb4d839d749fc3e84898bf83b08cb63445cb0a6ff99416ba32a61605b0a7360dc82fe56bc9c0ced2630451316bca739143f22f130238d1af4c9689dcfb8075facf1586e959ddbbb165146774ca2c5dfb63be3f2fa03fd71704e08e8cbed012a0a6231c0890a30fbc359ab80e8c4df78ea66f7228afa559d6905e1d08528bfa1c5048dfca4d97c1452f944c4edf10f799c43211d055ec2245d3bd56bf55741b84bae84c9860f3f41e33f7908ea3f93b5d27437243df96782f9fe872228d948cf76bca8bed21cb1210f07189052b3f6f84b31f107022e865260547e20fb161c7a594e9cf062f671e32bfdfb458ee164169a9fb72c5116a2748e84fbaaff805640109516e658b6f821dfef8ea158e7387ec67e272653b3ec86541f6bb895483e5744359f49161baf2228fe2b74e9015399347055767a470f4888ba8f92ea1bde0b9f3d9d71db0195a24490b0fa1107fa6211b447258c69deb9fb613b456b090e5d372f74bb30c4c66a88d947524ad075a402fe0726ce868e0161ebce76486a9a0a83060c08ec5efcbcbf3ab4ca04269ae7b9c9372b8e6607c078702641cce438ed6c457f4f5cd9f5ac3a208bd63678b46d047550c413baa4a6ae9af37c624553ac17b3cad722ca862d8fd8f62f2d886290dd58c72a89e77b0d5db79161a5be60a6ee7a6e6ceabfe7663dd77ceb53a5f69eacd892b11cb1946fbfbd497999f03a6e0f8443b0075aabd6b4ae70f67fee57e207f18729d30cdc5c366c729a2e8b49961c78931aca8f6b86ee56a326a3bdb454f1f5972b197aae5d0185dc3fdbdc654d77cc984709cbc21984605a119f66129466fb704ab4cbf7b106e5206a9728081b83dfd7e265bdb44923ae16058baaff83b078e70cd36d139c6ff1a765e91ac75f8cd366d02f85046e343659bb07acab560ac451d73fd85ac9d7cc25b7cacf2c0b02574baba2df71780cb85ac3930086219633c72f7e504473c14055749a6be4853e5736ddc8ecb86fd1c34097bd9dc5106ee32729fd9aa63c812fb347310eb065764bc4007aadc1021af67b8c9713909fb7bb77235e1e44312e6f46f180923479cbd216917f437df245d6a8b9c21e92ff6fad2728faeaa69bde6135da711184fa0d3cabb01e9b3af14502c2598303b3faf8c1e68269a5151f6dfb9dc320b57d6c35e337db7516a8f7cf397ec6fe9a916930a424ac3301cdd98e1a117a5ed0d3f7f4f6635e817870217bb461978fce9cb7e4ca70872d2504fe3b94c0e9f0b4e39e7b75d998abc5cb7f80f051e78ed8d78fc2a6a7210e0880d0769c6b6daf1c0fda823840a843182853e6afa2b5565c95fe81b4e721d888ea3e4b6206fcbace17acb38dc4c4717ab63132a18ff90c03c23cf3d077281f6ac4e8fbdbe7a453eddb008fda4a58a3878ada77575b116bcf34b1a77301df74cd78733e8db6b21fe470b39dc8febdc6875f9727de6731b27995701cb694eb18221d0a6fcc83e673eab1c9eaaf6750a76895eedd9b2cd50cce7289cffcc52308f1687d723272d5860d864f99b40318e433605477d065ede1828b1b17b5d72b721926463349fd9bc4e62c42040f62771c3f6d35bc2217bf9411f967c466f6c19ef15a8372c0bab63ca59266663eda3f5565b075c26a9b16a8d4fde8f6106583d6195071e862d663f41ff2ec6533c8762dfc21451b74dbdcf54202f38d7547237224ab39bd2bf00034c88d77bd2bc422eaf2040cbe9b895229e28f6babf18728e4d1cd27146772d000222dc4608228a7d8f0bdb67b9715ce1e67b4de9cec834dbb136f0bb50a8e9452c25bf0274dc8a6cde89d902b426ce2e3966bc6ca6bb727274030ef234bc591c399a14730c23c07f2711ac782b3b5bb787fb7f018c1272ca55356ab3336d06f9a482e09e7e8b8171dc9b90c4e5fd2e510176cee0b87372b629821428c20318f874aeb705bf5363cced8a49a6ed6360c016f5d045c2e672d609ebc87fffb806577744db7ee6ede52387ccebcae01bd32e66f83194155c22f66ed840bb7ad1d22248b495eb088be867b0cf60f9e5d193403c6ee3bd5433723c7a3a2cbcbf97bb2c1a29ef613115a411bfab499778cdbbccbc051c735cba72b825918f896c90371d242b70066d50b6ebde164f7f7346635d8c78b99167d110969549aab2729e2f2048847bfbe7d5f6e72d65b622da20836b29499ee37f39721425826af34101db2670dadf9c4f7c169a72bae5d82303d3e1a75b569796df72b9ee72a367a4f487513af14563782fb4e7fbd6dcc5bc8b6641e8dad600824e7248bf2735b765b8bffed4cf88005bb133be1abb16c2666972c890717630589d14ca0aa36f4a84e61248ad1711494d73fab54024e7afd6f2e33af80025f944115c0cc827145ed38962138de288ff6643d7bef2b33ebdb72645cee405cbd0b4b66f69be0bba163808c7f2592c0aa2a4e31cd48dec77b0bd9c4813387c474dfacb549a959d27e486770c2f40ec8c811babab12a9ce6aa6ff66263eed283a775ebc72094293ec0db3b2f6bdceefb27bbeb9bdae20c4ef1efb2bf4e482cfaa57ba8872122211d806c6c14ffa9a36247f3e12b9f85446286abe7882f18f371e029b5d721facc566da5839a8ac0312fd64717a6287ebbd887eaea2146ebfa49285e5196f111c6f2710feead673ab183802811ad46a766bfeb0da345676d04415598aa772fba970bb8aac4cef9d6f77afbd253164e3a6626ea20efa063472564bfdc2b70147a365fd9c01ab77c6de5d39278a8409f3fca3c1a6b50ab454e65c66ec0ec472cbc8e1ef4edacb41f9e24e448b99dadaebcb6849be43821a9c81621c457308728a5ee5b8c4cff0f59c850fa96f47a0e0250f77b1b2f65a80c29636339b2a9c71dd1d3eb9c668fa6608020eb52e65225b8a978c4b3edb3d62b1539471fa68dd72221c3f8a5661e6fef7710a0f2a088b6ead0bfa1c673279da5ab65e2ec5128472519eacff82da77c13fed733a89da345fa33fbfc70f9476aa542db37b0404ab632843397b1f71d621ec3595097f5ec8cd0465c0fb22c34cca2535eb53caee4e7282cea530abc9f4ecc3a3b5b1124cb44749cf64c2e0367df5f71e4912a2baf45d949a510554dee769aada3820a3855884ff195b39bb56893a7997280de6876e724cda036f26412a588341e39ed926e4d386a7a91a87bd876134a440b021a3bf72f77bbc5f2281df8eb858752a7db2c4178e192429add45a612cb7d286c0dfb41f9b854fc9daba8b0b27608f06537303e2e71979055bde45375f60fbd920006672ca47c9a796a9e6e52539392ef84eb31ab60648b79aad82fd238dbe2926e64d72ea0c1df82bbe85bf0a8c0a6040b5f7b00271db4fc4c1fe796ef112226b960772773a5aff10b0eee36b2f2bb06867852c8a586bd280408423cbf2e6ab6b60695cb20fc6ce9d8c3a945b191b4a9f4f3df4f5603dc1ff5b4875cf6d7b9294ffcb728f45e3e3d774c3c9049a350fef9be66bfd31ec90d5eee93f4342064d6ea66d729a26a359541d50b61d8132dc377979afb494bac61a1d109dde38f8011fc7017204bfc3abc005b251cda447619ac533ef957a489ffb4bba403c404dc026bfe2212064f07150a0e344144d91fc5705190e0fb152f3f7ad9bdbc64c7811f181250af1a2d77c9761872c3bd6c4404e214e9b210d7ca8e90a6f51709fb5a66b697d72114042df46e7ddd3565af13cece09f6aba9ab88ee207629261703885c978a572ebb4662f9751dbbb9396c5aac29019d32a72497e32c13179bd52e6247379c11ed366b583a7df60f24e016cf9aabeb60297d93befa7e08403634d2616fe213d5b17da7162ab72d7f9337b1c703e0bf8a074b9b1854e3c997dc18d863a4fd7857208709d7e04ef49dd3fd89e9ad0e2307920253e60a83fab2a530ebe7b1b909c3a849b99dbb383d15c6119b2703dc2c72926efa16af4a825f430b65f0dc89e8072e1840d6177775c4415c0d936282bebfec5a0235daadca1333fca1716fcd3ed2b26054d753cea2e993a901212749f00ffaac7fab891b2702f54b552e9a37cc4729456ea9682766c25133b8416139fdd62fc2552798b81800e0195bdb34aed184c38e150290cc2342c503eb456950ae4d8fe653929905cf473bff66f882ee70d7204899ea8f7fa3bbe2da13a5ee659f0cfdbcd6a38f6d416b6f06c02d00fc6c11fbe15aa68ef25b2d905ce2449549c91b824cf97149d2a4de5288eeb1ac7cbc25445d45e17833666f7b390e0632e704e9606bcd6bfa988d0af16f7087ee3b603720b01ed00eb80b5220e5a54180a0536eaf777488cc26ef8542f97ebf7f1fc1972f6fae63ea54c061b19985429ebde45839f266bd1fdb7b2eaa3f2d596dd4325724bec5dab61cf554985cd505e969e3ec4ed3be1a5833fbadc77a54b7654ca6a3454fa169fdae8da93fcdb27c17a990491e109bff00eb0d79e08e6ebd7bcf0dd72cb6f4be72d83e38413da4b3e87228159008853b823d6f1524c8bd21a89887519ceface167d76b69390a3aaa849daf1b8bb5b8d6954c47708cb92767dd4a7f972fa918b26f3b1a5edb77a0c9f4d4fef99961b846072d10230359faa0a6ea580665b189bd0549e5e6c98b944e7725a42390d227d10786bdbfb296d873369fdf6315ca6da51ee2f988793646edf67ee98d13a00a9e06341e862fd8d14eeabd9b5726ed21af0df7e664e98070452c48857caee2d24b86c74b90868a3576e1fa378725cb71f4dc12360afbbe6dd854b87f4ff4b75659ef351d9b3a8ccd449e6498f24729cfbc07f07975f4e35abf1f634dd6cdb88f3f79898287e4a401599a6135c721d1f91f5f4846919a75947109b51c3955686a5f66fdae44d60f0d6311d2988721cb43b9997d6833947bc5ad12f2282c48ef006ddaf6215152332f9c296021372b85b094aafc75f8dd8e7fc894c40e3fdfe27fe484c0f00044978dc8b9f7193721305affc7702dfe6de9827af00bc1ce679688a2465a74fcf9709e01b092ba7723ddc56c6d180a60b82616a483c0557d976c361c420d8430ad748291992ed351d841b0fb27c2746112e507ad3314b06f70b2ca401d88891cccbbfd08889e7f272ad9374590e96cf7c91a0dda6b8432d3315b50bed17bf81b7781aa6822c92b172ecb8f05696c298a234243dfb5fce7807bc570d5294be5ebdda902bcbcb85cb19456aa6a554a837ec010d55c8797308e8da7e4b891804524ca9b19043f969795de7001725bac3ae1ad631f0963bd4c43f92c64991a19c548098afdfe46d519b726eab0ba647eaa73756d6bd4715fb48c09c750faf952b84305d16dadb2f59eb72e64b2579489c35eeaa060e819ee4a667629db5c322abacf980973985bb885832c03c6a59b6eb8d17e71bb598f169a274449a3edcc45c421d2e47598830b4a1107d3c37f4c4ac00b3b850e35ec621ffcfeee6089b3953897357766c21fff5be720d9eea138b1e40ca8a87735e6c03a3f8f4c8874dcc608a342912eb650363e9728690afc3d6e51920cc0cfdda608682c03966310e89514e2af56db5edff6aec726ff5e77e584bb50bc49ca55cddf6a08851d6d9ab3fb312bd464c6c1149bf7801ff062611c356a68a09e9f35858593b28a74943e6c61602b7d78711a943d81472a974266a46b94c11b73149c081771b2c19e55491c856957e348d74ed9eb8ff6bf5c9eea7a15fae6d6b8709dce84aff64de8e292b0079a2ec7837167956f31a54f393b458f08128fa1eb4c492cd51fe5447674d46a7b6732e9bf18cbef2e98f724e73489c791e6ea1fe9ed81e2abd715a99a44ab1e62628c78751a8453f1c14726237abccbb067ca7226ffd6a3d82e3ee9e90d281f1e99b9f3640f4b4c654ed395e65d82d46f023f07a8980c5bbab61244c910295c9755e75d6b5c83a6788887251ae54fb7d190ddb5e5bd64142f0aea88be9dca3406a550f95eda544962130438481b277b10773fd91cc1ff4e02fe3b5a49358952677d68cefbd4e05ea0f177270e77b1423aa7b1f677d886d8b8ca08e059b99c9e4b92ffa62f09af91c44e01a7474e450e7cdf933d72c363305db3dc4b943fd0393011946af48977c07c74f72554a93cc4a9768d75cc89f6fd1ebb38c415266379fb1bf64e50014ff620322726bd8982797da2ab5f8470feb5da73be05d5bd24275d398cda6a5d9d27e753b7215d2bc8bd8b35a5635d08250587407194fef64d29f464c1bd7cb72857809a4721c846fa5f31df641f057215be4ddadff0b6c99472d8ae44e9d8f66cd5bb448721226406189b5b9cf5c9afb95480fcd3d0c17fbe1bfef16942d97f1cb57d7df72d9aa38141d02c60389b738ea0b4b0bfadac35a4c7282cfe198faf0390dcc2432ce025da311583b7160dd9cb61202489dfe2b69f9a83ddca9b27aa80ef894df44c0b68460856e23a50f43316e161266929fc18ddee7120ffda89ec2bdfb4d790fa446cd8ce24bfec2fb47b66c8a0c9bac04d48fc4c64fc34e3cc67592bec497724501d39480a79c2e2730b8643385d035d881ab10696cf7fa94f0d7a4bdfb29721be8ee4a0559b797adbaf416cfdf9bd407191b1d312cbadb5a06022fcf19d872dee9ef529c7b396100cb81258855961041b5b27ba1a5d1cda964662c1dc2867288209d441b74861037289dcba815691362b4c4bd51b7fe4e725d266f6b482872152175285dd25302bfbb7a58e9b8c3f102674f680e3728ca2e033fde1c26cb72ebe0004856eb13e278cf79de54bf6c0b772b6f2a92ca7afd192bc2f4cfad3572c410171c83291d686a3436f1c1792feffe2e9122f0a0d66b3f3c7807912f9827127e460bfe5a1106eed14da3a435045a5bcbc0f23b9431e5f57e83fbb6013d72c498de53764ca2979b55c9942eb059d6659c90608c68bde4bc6eb7f3c0eae87218b8dc7c759a648b2affc9502c50cddaa95eda26c82d172de987487f2596ef72005cce8826a64851b380bb654e681e5b2a430c652f5d9910c88aa21f5d5a5272e4019038391f36a424c055685163758818709a37dc968a28db4f0f8e85484d728d01b90207a4ec9a3d00d6e4c48a337f6a740b5a5749a55539983d3cfc1bb672ad29fdd67e2a4486db22f7eef0edc8dfa933ca4bef016bf27638f0d138e910729db8710aeaccf5362089e637472dc0278cfecae30784d57ca6a134e3efc50a722d7fe8058a30905ac9264430e73cc380a2bd33fc7346162d7e6316a83b2cac72d4f1cfe0be7535055a0326e43a73b50c8d293baccd8c5bf4062426eba725cc0cabd1d02e623b911e33dc48c963f3579d992348a92c359165006f5860bc49fb722ac34e69ff0295ca096b1cac0a35b87f6bc58ee63c3a5042f2954b669fd9ae40d26ae308f84d555d8331978e5d3426d695c482402cf06ff07b6b07c6b6271f72490e79926ffd20bae4001d14315fffe585dd2642770e846aacb7f7e26c6e39570a2393d4067a612360221c5895f70154fe7640d49a55f88f33c6b13073c6574b7c1491ba47621bb85239caa598410a916b87f6faee6bb5e87657c783965c3a725c32cde5c6262a721ed107f87681ee86d921e86ec026d9223df115eda296d372ffe6e0b0e0d1a4549360de369a34af0e66ae8a4a1b4d170fcc5ef5e80e49600ecffaad362176d74cd87705d1628f38339b4851d5780e990bb779ec649f291606ff33d4d3ce182139a4118a8906ead8caa8e287b5fc1ece249a61be3d4cb7aa6c72975b966e453e9f70f14fa28fae0c3910ac69977030af441e91ce5bf906ea72f0466a8cfc37a6a4523e93bbd62959e665b742d4f2b23e56d11f46f26a175317b5c76637a4cba16b052622d66bfaec6b0b8b4f7d3f076cb8639e352cb5854b729797fb06777c34fb591a916a3de45a5f95e7d3096e5ee5aa54a648b53249420be8f2b0ac15d252012315b80a90f9305108ba571c3caace2c94691fb87840c835434aa80bf7869a68f21cb68940f5882924f8a96ed5791e0ab20a401b77d5747216a122c44630fe1914f6d2b236f2b1389583c5e2e22ab11e3dff4b03b8f0d27268032fbf78851efc77d9f9d69c69140b96e9f6689e9c5270ace45e747891e50df671a8aca88599adbe374847fdb457491edbfae1918067be99ee14baac297272ccfb51b8f0e7f234925b3558c52d5de64cf5b69eef4918c273a0ffd80977840d3e8d9ff68850e5e1d98cfaefdf9dd8b7a9dfd2921bb243eeb7b956282b3f9f728eb7e9c40d5057e56fd8fc79cf2b16b1674400a390948e275a2ce0c02f771f1ac0389846989fbf08f01c0d19c29e9648d7548dd2c1a7cf1dc7ace2ff37ce5f7209493bff65e75fd46f7f03ff5856f795bfd482f00b8a22b6be7ec14003104e728205dffd2f89c3ea7b131592fd17f4672dd33c9777db570bd8802f7e82419e09e4be398ab00929f50f63c3c8f04070e6b37239e3579d030099f3f89a39bdac289ef0a6f1bcd2ff33c50783fc8a957179943258b8ffb79e2f31013c3521a65957c689ea75762057ffefba0e91c7f7c5b55a30b9078c470df9c8cc43f4eb61400e47c4fefb1346d5d5e79ecdb9d00896978b344620352ba06292eefab6cd5ceb72776863fc84a9cf710b1accf34b1e95425a681e57e9ddb1e8e37e3856cd8d1172ecb9cc1f3aecc4f5f2898aef4c3b6be25518aa2b693d3c076e0041f2ae0e4772c5bd2e6ce59781a03e7c7a58266e159434bd13ed4ada2c56ede0f702365f617235c4b18913be23ac1cbc246ed84a82162412a43ab44cd0b686b6df726f719921ef30519f692c765dcce3c757552a0f1647ea05983ab4ad56b9d93d0b6088656a215b87c7311f953c5fc698065eb2cf9ef084b100c5685cfe5bc4e18a99f27b72706a7674b090ced57957f34dbedaa0a7a0d112f6b1de2844a013a6d09b12a07294b1e062b3bdc91a230fc8de7d9d13341ba338be90ce757637b1b9b28c571a33f81ca1611544cd85d6a35ba17fbf9d8d3115d586c068744ded86e99c65459a7267c49596ca534e7f257d2932393ea3b564cd72cad66f3273441b450f3bb7a95712192c27269ea62ce671f6be529dd15297d61995052908b8a18d5ff32fcbc5724d16b3d64872d654fe878cca2fff89b960f5b6dcf47ca12bf6216ac2da5b2444b4456f758278fea7dceb3457ad5fc7c5106e455010dc8190ef8eae982522ec72b3d2dfc4f82c5d6dc5fadc3dfb62d40a2a2987c2710b806f91a1d0f0e45c26721a3b3a57d52f0d28569c9245cd22f519d4271d2bf52bdd9199af0d09888e7e4e58ce456d7ba653ea53354ab576b678d857193f1890f689d62ad16df80418ac318d5cf9350ec46ba5a8289431cd67b0bdd0ebc3521a6bc46def16f97d4b497b728cfae2184c5ae854d1f834b7a3728b7e874060c633ede2eeee0705411422977236329ffcb3af895d6d27d0098728de0fc79e19590fb8a26bb0671ba26dbcc3728a55babf73bec3c7d0d911fafc2556f96f56fc1b9bc5b3e848cb8ba5d2dbc91142e1562b69760b4ae468cb810348975e9a27862b339a6f59ffaead2213812b5c075fb2c78ef97cbfc4a9dad9a3030ea0deb38f5666398255673c14ec4a09d35bc5a50c025959e76cfb3f1766ac6f66f9a7def8abfaec91a7402dc0049e86db48bd29d176769c2b5fbc541232af4aba7a65499af4d5c79a2e40fd8944884d4762c995aa1fae08a062031885a0eb1c2ff660a1038030f0c7569682188107741166af3421eb4c8ceca4187e9417d15f12eb117f7b023e2b276a6740e7af4991e16debfc37adfa0e0644aae4fdb234727565daddc05d401c151c5f9b0692798328470a8968c590f90141c27faa6776db63dd51ce96739651425f5f54dfa318e96b72941b8b497163e801feae264e3a4ece6536a574097e62588c22d841d9dbbb4f72196809358eef2d976f6463ce6ea708e91b50c653693ff88da0ee74194694b572f617e638e2ad7d5b46e4fb3829f6d734d82b958af0517caa926b6fdb1f51017268ef611a8b239eb473d6f08a074ca1b35935b3b965f207377aadc4e08985b126434de5cdf9831bdf982f79824e61fd2abbcc163d229bdc4635810a6384ad9e72b997f657ee209624712ff5bfe877495c3bbca95b086fcd1156c6c0ec0d285072af626d22c03c041fe9737c270e6ec5934d2097f2c1684491fd49addbf4e95063ea3274b4a678405ae462a7881719be88df4f387a26775c3650f38706400a7a7259303378a244d535054808b6b973ae850835f26f4374e40d50619dd4f481e372848d56f41ef35837b8d6ee00d13d1192dd3db849947b12fc3be36b7d245c777225445680e5a41ab49bf541c901526b1824827d03e5dc555031713ce33d71047274214f372ccd74b438dbcc96f33497dffb77f52f677fb0ab4baf20668ca57b44acf46b804789044173ceb7c7a004699f897ecb1e091b2147f0b108c305805937951539db9e45c483cd95693159471de91427dbbcecc6908bcaacb3a95dab5e2714722e0cc4db456cd48072420109228545932fd7589c02cb9b4ad4aa98f58f722f5dc8405c288def1bcb7981056c8b6365cc054cf6b90b572c2581fa17c1f747d1a8d442f54db1d52be472365412b929b77125684602c9f05a0848f540fc1472794be259ebf3136d684d2436e4e76e3834c20c1891fa0476d1850e8b8ef0383e42ffae5633ba4442fc8e6265437d49e637771c9b97f27d86b9ec8949d892bd1036fa341c479b7aacf3474eda16dda1556accd45ded6dae7c627db9a1589e5e70ebe14f8a1f2f3c6edd72084b5f64aa066996702567be2b6d76cb8d3a74f7d4724d07e003746a373f3ffb4f318c7311b66758ff91f89775b1e53913f8fe2d1854a54e5a12996a2507bef6b37134801287971264a75e2686e7649fea11e8b6ea7218836ca9819a1b3e5c1f235432c5e4b044413da9bc7cb172f7356e9406678272de01a20f5e916577af4795edd761584a95b9085c72b9307dc2fa59d041aa6572cd3f801b0e92aefa5049ae4ac409c01008687ee1878060d2e6862d0932e54c22c92caa7d5cb14b7ba4b358fcfcdd6d8b6479d65d87925259fe8935004255fe729c9e34b1a10357680ced8105d4b2cafbca968fabe7500dbcfc724f1293591b720b5b0099187e7016e273c466d4c0627f13b5eaed080b156ef88d10743e1081722113754c73d9b5a6458028bebc7ab07ebca328fb934a751da4250693dc08bb72b999b58db6728f5940c7c8de3e773a932ad5d42bb612e977c088a373e19bb93e8495d8f14910dd2709361886ac86f78694dc1f76a78b3d0320a180a6f503657248607e84635d9983f967dc4f682875f866b7ba0d5a88cfb909aab1b6b565f0025d1c0dee5b9a0f2a4d88e3756f69ff248cd382226d55c5bb7ba0a295a6de6e7208d061ad04f66e8662f68495b06e2d285a62078802f215a108160606852fed06953821e6e661aac16364e8d4b5cb0a13341f641bff2c5f99df6d5b5521e2344b4b31cc993c991c629f94a1218abdc2a5f7c9bba449eb6a0132d9e07723041421a3e370baa4ba4571cde0498aa4894ffcadd47db5f4f159d3e8bcf9b2d1f84a7285069c48e33d9e5198905b9740af108f2a219900c27a1444816799eae899e253c94c0930a0a41d85388faf4b149c4bbc08535fb0b37e91cb7544adb0d6676f724a17825d9bc45affd1d5c093cafb2c399a60c63fdc4881b52afb1ec970cefc682f4cf3ed640c2eecb16d58f278b7313e07098bbd8969f28954c1beb6ceae2569c686d07397ebce606fc46c81efac61dcc31d53304ca80ae87b5946ca3462137233db37e4ea519c6c4ea224376ba9c55f95ae06eef4a68ae40f63d01e2944a872a2552269244758ac93c3b32819cf0f42e6f154d649f0b628653000d5c63ba120738e20688d619f26158d05f4fdcc970a7f109e2213f85572f4ad010060a7e572ac2f7f3e08d9c8004d9554b2c736ef8ac63cd24dc8ed65aaa4d873cef86dd8422273dd3cd556c70d592d28f8ccb9c4e7ebe02e59833defc0b85f70b08a81b272ad87998872d53aeedbbbaefa38080ad819a64dcd3c3745a50b4b992ef51fe1724826280126b72feaa28daa6a36fc5aa2fc9bcff5f8bcf363322b5b0a826a956cd8b68d8285d3d799feb7b5aecc0b21b42b933f32d02b0437447077a2dc6ba81d91f54c96744714e6bcb083e60b4039305613c4161f27df41bca140574e96a672fa6da7180188b7237043e7a3593aa918ff4dbc3017945d1ed5293a13c5a69072c44866a5061caca7f6f59b48334101b6d60e37efe35f114606ca9dbb976e0e727f28281e13d8ccb1a836552b648c15df1b9009406036748a6f57488f93a9f45144ae22b96ce557c28ac18660409cbd37fb50cf60c7426a622372587eff5384720ec7ce0584a7ccd7e9d8e96e998c272a58b249e927b191073843872337f41d726cdb730c7640e20d25e3e51a44eb3e4c1156f7c138c9665936ff16b848e93a34e3feaf8685f6db25c5dfb82480d369bce71871ff5b81d46b7c16d06f3b1de87238945d7682349ff98593147415ac1de1df4d48b8442816aff27786494458b0007da28fab4b906c421ed1f36ca9d52da54912b44c83d3ebe051d58b667d169336040515f76ed80166e88bf75f74b27d4cdf4c11cd46e367a0137d34cf80806e72499038577da3bbc7fa04e7fa20c2812685fd8cab856cc7c19a972e44baa63006209b7d70adb4f8dc141e932dedb4300e672b42ad432581ac9bd4aba566b19a036936dd1fd31c78fea319cc9111a9da0ff9210170e5d055bb6f4560a48eac957222898c596e90488ff6ef49495a3bcee5ffa9a9e8ba5383d774d55bb6968f7d3c8caebb88b178477ff44c3e17702927467164ddb487a001b7f1326f502a552f72b4e946e878e4d23e965fcca4ef66c7a1f0a78af19afe18e86f926c11c49fd47248405eb52f6c05a6d67ac3487041857db14eb31e17af9ef2c99e5432b9d57969596a4e8fa63ad2efee673c1277398f40112edab196bec251e68c6e463d4fb3720901c593e5dd79bf73e84faa95c0d29e03f971635f3ed73e2384118fccda32729c2e572271656e3fb9ccf2a224d9d4306c108d78786267a26a456b300d701572456536159a9ba058d994608deb9ebf37495465f7521a489ecb48d31772de7555033de6223ffc83fc745e82fdb5590644c7acd41d856b79af54c60fad48eb5a194232c9c2f0c72ef020c5f1a846241f496a3862bfab03042f7465e3709ed9531d01ca59e53f9e28ddec854340deb0d96d56f39fd7c6b122867a7fca1359a33872140a950f31f9f610d039bd469e5ff5ffd87aa8e19caffdfe213581a3eb494472848cc333c873eaa3dc461f2a211ce64bd3e627dd13be27163dcd220ee51640729927ea87bf74921940f02eff5797c90e474218724132a71063205ba51ccbac72e89aea2cfe405e9e7d8756f7d54cb04dd974f6cb414f3363cc54b89cd7611f728e962eb3d2e7a1cd5aa835bf36e6836af8c9390d8f85f8abe01c2b36b9d43f72f587a15ca07cf3f27c95af1f6dc021025cdc82e18fbc085b05b97772061719724677dfd7302f2714feecdb25a6dec6d15ba8c8e672969dcbc4c2387c0e7e2807d973fe3566a6cc8bd00c21c676eb9321555dd90e5f22a90e22bd4c9dcfb42a728b2d6a0ec16a6d8930267dca8a2612226526b92b8707327f6dd21113b55be772bbeb1d2c9ed1079f3cfd9bdfa0617a68b776cb3d618d8f82d673d911cb5ea7726198f208a3c466ab495336eb979bb64228445d96fb364dca2307011e253cd10880b3d25b69db0dfb1ee39c46670c9ebde1e729d4cb1612c22b41fadd4e0bd72e32f734d29646d7195228169858d9bc851a38b63c0e17f979687535432403bd2998c15954ff8277572628bdf0a8bfb3d42d739203b61c509401b8bdc399c99d367977859d575331bec5b5620bfbdee5bd27b6d3769a8bac9d43b5625fa0adb672a4bedf127a65a2876846b256d609dab6ed124d7d04fe368eb2a767277315f23e5ee581a2b76b46205f7f52c9135af038f6f70458cf8a0dd53420b20415df4772abee58fa121ee00f0046ccbda1c47e097513216300f4a07345fb1f172fed7357761a7af8e9a38a85b612abad0cfd7ca069691000f94b91f14b625ebe0756727200fbc507556e64b0ce6f87d6c52257b95394784f2b6c745e1676f09b0100bf725bc638521638218a6e249e24076d1a63a890c0e54602f88934aafd86221ebb72382225f5278dc591631fbce7e95128108372b1342949ac5d2a4fbd2d71929c72f49b276bca9e3112155520a1ba2668d052924f0f867edb06b0c656af040419724ea017ab3c449cd6d56ff99243f524031233a3bd4424b90a9409578260adb932a47accf0dc210e878190c1b8bd74c393befef06cf1b1f4e88c28cc5d275ef70b3c43193605a7f56f8c6e0e4d12e4a56915f9a09952733598273c91a3070559723b6ecfecb78d6893135d68308865d8cb17ba01602a46445f7f7bb201c4561e72618e4a0a1101ac3937c8e6a95ef4f477d86d05d4b75d55fec2b76e260a071a72112eaba2880f1f9adc84904a1b0f8c4016d3fec6260672136e6ad4d01e05062e1f7599d073d2d74bdd5d23856d1c111809039cd8a65887a588c6a02782a1c57226a227c012d42b5e44ae1d89ada052f154f92bb368231a70465acd6f324fac4ed92cbcdd363ef3c34645fbcacd6d27cad125a044260b3fd8f3e0b5dc496879725a730ce9d9e9eefd0637442c310427d5afccb8a24ba1597657ab17a77f8bba72bdf4934077d2109f6927628db7c31b81fa324a91c2854e0501bc3b275f7bdf722e1c5052f5b3cbbab52b3b325160e2a2f7961e65ebfe5aa7fd74639f9597b572207364bc360c109a4cba7dea5efc703777cfa417f0fab1eff0f7f0e11058264d9aa45b9eaad831888566fdc8fa95d8b930d53bd3de0254485223ab88b55134720e6034302910fb1edd4e783c63555bcccc8a91204cdbd41fe602beadf1c471721e0368621ff9e09cea524feefad7b53054e63f40a39365ef61d0b4ca228f1272d979ac0cc241e20a8a8e15d001cc795ea96170cdea1510ff24607beabcc272726264e760e0dcb39613671fb9446ab05febbb316b7a3e6678bb0a8e19c16a1c19ed029236f2395ea6eeeacd7bdaca8a30aa485b0e341d95a52db3f43fd6d46b720b6ec635f9be69d054820b5f44a9ccfaec82e8c3d5a6c4835554164bb9e4c372ce4f855f8dbfba6b3380133722460164c3525323ce77f0a91fd598a1c388fc72d6de4ad6c94683e50578ac95cc6a0f2175c61570bc1710b4f77fd8f3544ee572720f2b9e5b58281d939707278e8c50fa402933fe6316a73c95c0efb2f7db95337e1b19ac94ae11c4a585abe64e95ddc8d8c8bb8fc564bfbd6e3fa844f16fa23ec397a4cd7184377e0303e765d147e79e526c3a55da0d5ba01ce0efa1f4ca50726f341389d9e96d6194549f1c9be177d3917c66e61c24cafa6e458eaaba9c057265ea2edd8d3ed3f328dea3a91d8a873b31d6e9515fad83d5247c376f2416e272527adcdd474be04b7a82f7752f0ceb4dddd10c43d6e33f22abfa896ca4a518725057223313a49b6d19a2610581735dc408bc0dcadeb3549f1bd7cdf1c389744f7c8d9a81fa1554438bebabe44ab07b41f04cb94f24a33b345be25f61d9470772643ff1bb36698c3879e3b5ed7ff7a04f9eab6bbfd342863eff2a79e4604a9472f193ce573ff3adb830f89364f5909f2f52b4beb339a6290f94716e089f84b272fc7c115ac4b344f4b43ad8d6392935885a074dca69e8cf821c1b55c6d499af49a2edb915b821d5f246e7a188282f45eac80680035b74230fe8a9c2426d280d0c3b0bf45389841cfebdfb9c12f16fc9686be6a80293502702ff5aa337934ea472e7ba5d617138ed57b6abfe7b3f87553f6e4ad0936a0b28d24b890432c4cca15fda96e08b8f64228d82d7b2582aa42d6db1b9baff95098adad2766b147ef74d72a517f842369eaa8769df09725c48fd6b9af908d8f9efa814aa17accfc08b5b72c6e6ca44c30950b18f362686d0caf444813da97e4e77e6dadf177257a862b42d8af790202b9eb55d0f5d7c79579041c9f6fb29e93d30de1009352addca0814723ac7ab9465771b008a7cd9dd5356787965c8bff9e00ff684b05bc83fd7daa972c894e54388fa50127753ded5e93c491408d71f192debccae4cd31b1c9039775c3b0af56550dbf1823f2afbf9a8824e8c8632d8de6a2c0c99957203caf0833b7228105d14ab00cfb53fac959ebc9ef77ced3ab13c5b09dee3385139d5b588ec721ab57b5c7d0e7222fe43f10c25675338895b4219e780c0815a39141d0c0a675535670e3632d1e9cc2dd0eebdfd768d49b65896c38adc840a7383bc87d015b772d9e9838ce516082cc652b11b041b4f9a68ec1c116f33aa71f1310ede5de8a072023eb11c0dd2418f4a1fffa061c0bb1b186c487b717328656dfc6f83b47a3672be6ec47e79bc23b1e48768065569204af97801e1b165a2caae978cf39319d2721cbf4e973963a7d16b93360368b2e87105ca92d8e45873f2da5d1956e1ce61722d20d37365b28078b3df96dfd140550786b9e0daf068978e531b213e45db4872e4b65158c8bd421dadaf0c5b1cb8065848d058ea94ebd39143c6df770021c20a474293c38ad7e3212f958d424a3623219a9c14ed89dd33d548785a9255cfd072c895bf2e2f7be96fd05c9e38481938f880127101b3772c371b4cc4a1957639302ce8e3b4a9e345c17d842b166d0d7343eda9be88e9009ef3464e0dd183766372e6f68ad1a2da0d7849acae19a6ea5c659d4c059986e6ebf5c84145746a641172560665d51a14380e0cebcfd4c548d413b06a280894544caa15e7967a7b09db726a23d35bb124e70ac96da95b5bfff8d630d20a963e4e98017a84d2a8c5b28c72a7d32dfbbe5b7bf2fa0825f90f8bfdaea8a65f9227d70e22ced394f30818835323a79a558661c80210413d4ebb90b41e71e9fc509f43e1f24a883b5ad490f620e89cec7b1d31977379b27e2ccc597a0b173e137ce9a73cc5d5e0a8511c713709ce7cf543c1c0276a2c4bb17380c086d0775f79457d03251c0c22dbfd2b53aa72baa47925bf7828dfc27bfe57ed6d93d8e325cadc18eaa00b9b60707b11232a72b6b76eb0457704e9c5c21cac93f87609b54156c56995e14b7b5b27e5ede89f72f5351bb7f25f8ff34f551da303da6c3920d607911d43125876a8455e5517fe72849e7ffb2cbdac396e9c70771ca4c456beacf08e0fb81143b097bab9c2bc9e72e47e4b17e750202078f5da96bd81c9a49a647ef60b7fa03b474b7b7f2b37c60a9ab951893f8c746e60f31f4abc9f409acc05545e7eb0da91377c84a5c8ac2172200ad697bd4aba3366e12e516246bbfb80187722e663cf051543c6a00dc83b72840d8a3b13ee8e5665c01e93f1f76435c816dc898cc8126810a42b7b22d92837ebe84fac4cd683afb9b5ed15d4ad272fa8ffae7f5f58de70c1c88efa941e3c3c8c78dcfccfce73a147166c64f60fab1db75370ab0656f8a26326e31aef51c021bacb78dd007f6088761468d39dfd1eefa5dc959fa89943dde769c26dbd9f4841927b0d709996d6321632d867b1422f34f0b3f0b052c916a0b06075329fc0f672296a403c6f590cec4ddbcad8c8b58e237258acf4547cbffcee1f11b89f0e0a729e577b5c7540892c548c861c6a25b75ae93a9a4499b34aa1e79d51800c54b81caa55f359d16234a314bc43484f53576bc7ca03e9b9dd357b29a5ba273af0a872661feaf12f18c905261df6be1632ee0baf3194e150935ef68fd49bcb61be7272b71f5695987f48b89113106d8e821d1c34cffd82386298ad4834aea707de6b72f86687169f6f673021b1a86acad23f49934500c768d352f6ac0165ec82e07a72b0df054bc4e966973daf34f15c7b57547431d362a14f0d5a68ff5efb8bd3ba0eeabd9fb366230705c1a6cfba49d177ebfc5ec33c48843488211275800311ee4dca47f83f900de44dfdc4738408a2a1545a6180f2758bf9fa84eb971cc33f4343a4bdc31dbed2b86243d1217450df54f838444085b7bb68d3b1a03fd69d62d8727984784766ce118bc28d205413de6a285ca577ccf6664aff17d4baad05c9ae539911f7b8d10d7d02d92bc9b7fc0fa7b9d5544f484e4d74d6cf385386bb46b172f3ab1d03624e099e5f1f4b31781ff5b6472a85c4a87b7afb68a2e0bd36d01603d7aeaa007e7c08f0b06f5ef09ac6b1a27df400f6ec539bbabc4c098590c78b72eff4e0314f4ba8d08abb814e6350c6883fa95118c5297a285c736dbf9b7c0c72c365f7e6e3c5b0857c914606271e4c26aa142e28c7e5629c018dcd5a2d1db172f7e975ed8e1c6ab6a038f63371781e54d5509c5accc172daf440073658761572f5e1b208cf2fb1af0bb26b483471d3399c4c596557a901eb582064d07643347268f351f17e825728a8981bedacbc4dc423cb4cd6a40f5be5caeb5ed44d429602a66df17d5ee30d7011379a27c3db32df945764d3a71013c1615cc85fbdac650b36bbe41042e1b1012a86f7d30c7ebf8ce7afadd58bfc80540aa558927c590772426b49ecf45c232bcaba838d6643ba996311705d53ddec6b45ec81ebec7c27707298d95c3e88990e21999983980774e613d890d4788cc60ee1f3c02751d67d726a47c47740ccde26ade6a41a785f7c58f6411f73d583343957e3a4e241c1c6401f24416039b20c49d345f9bbae43d1c9b80bae18b94225522d2908c800fffb5a328ddc06f873650ac009fc73119939998e8ede08a8cba455aea34c6a0541977208ba7936dee2b01cc4eea8170231b6481868974036e8f6e30411114c76becf52907e153ca2780593338c561d301b54ee43e4fe8fe7d2a574408d6451f2a263592bd71d15fe2172a49d99a936ac64a33768ceae147fa482f542c161a6537bd772461c4486701ed7c4ffa99be57c13b385af3f7780652e39ffe8ff3952e005003f600da3a72e9707fb4895e95f87c16768604a1c70af3cf939e6fad609491442727396240a9a82b7c05d015ee26b8cb3552cf4988cbf2f174a06d15f56a623ee729aeb2215497f7e928ce30b35d40088f2815fb4a360ac4114628dbf6f02bb4f7275246b1e00154924d3028ccafe835a7bf1ede4268c18f0e48b8d9d0b9d55cc72c0faccdc06e92b3ce9c7cbda16603503d89a5f46c2edf657990b9535fe27f1722a81be1b8b364c3a4b9337c470b2f5d4d150e5b3cd0d6a69fca96f78fb590972752029754be73e3e3c50218fc953d76956534d98c8b392c56ef3466d379dcd72cc5e69535e12eea6a90890d100d29657fd99d512a0f33efe7baf1ce68e50f81eff1196b88048c0a865924165064b001099b21fec325205728720925b52902072c2ee5347a4373b1953e3bbdc97600d80766402c3214b940e2454dfd3de5cdd729c4395bf29ccbd955a545e424ded0511864f703f4d34b45febced3e7f677d87242e6c277eac2883e363a017c97e918ae1362397a633ad6fc08df6270cc2d4572c75963610cf5ec9552574cd4217b140ded3a151960aaac422f37890c1d4ee072f4e8765b04475ce9d479b869a0707f1061b120748f1b37406c01ea0777679b72012d9146938d03c36756652fdd3a9b73004e9cbae6bf51e96f3e951b896dc21824d9af2908a2b5300fc07c7f149309c3ceaae37ded9e64cf835e81cdee4611727c56c4165874b4b0281f66f1168a83dd78faf4d832c72f7d7c25ebb6c3918650b31659924b14947deacb7e61623fd85c392b852b457935767b474bc75089a74eb9aa32ae55214bf9c811692991f3015b8cddf64d07f8bb29e87df27b8597dc72a871d494b418b926201a0047eac8ec83994e6b32a7c354ee63d316090bf44e421d54ebef2627e8d22537bbacdd06d0d1255a8831f60fba2bfc57dc63074d0a72e0c106d595e2ae539a40332486ef0f599305650abebe74c9e96ad1108169b572a7c3a0bed5e0333d4c2ad28804f42ec91222ccb21b7b3846ee29fa18fe055572f87b8189df408658b812251c73e29a642a0739da357a0f11abfec9ad9e249d70e74390df7e35e5153ceb597cfed5a6e0722100b209cdc7ed380112403098d861e1cac7a543526516444f1a97ae091acfcbdec8669b0fbe00f4cef2ddbf9f207291257286297a6663f3c1e73ce2b7b55fdd079e0f96f8c81dcd693d1d83d9b54a01ea1907ceb8a10d279735890be50596d72131c6ae93cf2f28785cd32ac91e7281dcf4cf3dc05741613342f270d08a8e37748012fb6713d5a3ce2ba1fbfa9b72b1dbc8c7e0b64482cad8adb37ebd7e8e254de1e17f963310053cfbec61efc61f239b52bac913413c807ab2b76710af360bfcaa01071e10f5613c5dc3f741a27283dcd4c1ab8c69a79a335d97d78d9ff6809de1beb137577a398702475bd15b3d1142da33ccc31e33d568cc0f18e4526c2a8b8bda1b2334240fef8683f16a7172a2761a4398fc5ad0666e5fd06caf808db55e12caf57a2401af8ba997598963721b82a18aea7c2c5e1c56e7f7c630309fd796f26ae764bf8d8782459fad524f038520ae1c3303aedde2283ea119d2f7600e1c5d76884459145ef6a0410954ee5be3cfd7a1205694de4c93207ea0e998ba6e41d1de881039ce78dc1910878d78020667d281b4f3c939385532d9f43cedfca736554557b3f775af6810660af5a71288d8cc8202f67617a89ed7e51fadec065719012219862583350ae37d2df64449cf564a91be4ef262577baf517508b1bef383170afc445b857ded6014207d617277bfb6a564dce956428429049baedac95d20654a632ae05b804a98672f86c56a6106e605b5efb6fc45f5bbc8e2fd20f678741d86e771f64acb08ff49fc680372f387104d8d80225b97bd572d06e1d900ab21f66e0e3dac81b43f63866565e072e64cac93aba1ab8e2821c85e012a6ea32e788bf4569b820e6352df405e30c52503e83c06ec9f42634445a319348462f70a5c2bd51ac18c7c3b3b44ed3cb45772fac881eeffa2d6316a6cf9c7f9ec3ee8120529ea2383de6d2802389cced61f15f40822339ac0ba192d3df4d25643c715799238f6daf27ff7d20d397491b8464a277e46a38444c734951b3006c4e87c053904e7a9a04fe4d9fb76b2f899d33e7279fc898f09ac117e8dd0261269a0bf26e013f2a72a93f9646a6c474b5914de348ffa9ac9ed3c3cf3430a491973545e000fde872e19cb2a4aa42ad8a52aca956e619b0a0dca5e00102087039ed9ad14b0c37e6ab9248bea4eccbcd065b61891723c24e16ada38b2f4d7e01927780b7979527e34c26ed42679d91e6ff342e048725a82a47b8adbbe9c20d735f20cf25b8c7bca6a97474b1575ef10401dbe41155f1bd5cf779bc84dc8608a4adcaf3fc0c0fdbb787cc7f8496da31f460056c07d267380b6d69b825d91892f35f6957714de1568f03e4ba8c7e82f1837f49d4e2572c35ac7a518890001b1ffbd7f9cc02605ad9e52c7d39c3413f02f6cfe422adc197b44e9afc65f59e33cdd5b45cef0ab6f12464b4235bd346f9e55c5d3ad0ef272542004c10473db5c47ecea87f44f4ca694faf4ecd6db77bfd8df1b999a27925506585f1195fb6ce1a3afce5b62543dd76335a7a4795f2db418e65f5774f68902d70caeda6f37773aaddf0e1b8966a6375f53ed01ec16e98bd74f2359257ccb7229fda2d1e4630aff33ac3903fe73b30d26b99a7a61ea95ef95b388e3104d5f7268f24f3b00c6fac2a1d94ff6d72d7b43b516cac250738072da35c93266536872b51adfd70abff47e7f7d0f517e2ce1dac665bab5720b3247db4ebd7ec804617204275c3135d8596b3058103da8b0ceb6393055a69a26c6a38a48b9e8a8589972165fcb850bf7a870ef7da1d7720157580b2088a7c23af378dff56a9621dc2805157a2b039087dbb89d7f7e1d51ffc43085c388b662875fd58da0a8d086412172c90ff16c69194b61675dadfee9720fca51a035805a89d89f6dcf37c2fc45fc725a878541c9784f5bd7e3a5f794da4cd42a3d9bbd8f230901ff9f78e4a28edf72621f9cd97a754ad1a238e189196351c4c5187051619f2ddd85c047cf9d2fc74da86bca694956c54b8794acdba0b74ec8ad796e6f479d2cae2aa10f519e81c2728ca2eb0e229d6afe7313b0453996973b5ecabcec9c4dda670668c45abccb7f728baf401d1e7a5744df1ce702d2b7945dec96fffd0540a643bea6441de375cd7235d55cfc036678a047acccb39e6d55b3123dd3458b117722d557ba74e8a2f270a350edcd7799971f39dd1ec7dabd133cb8adb3835d395d2f43bee45e6f356361db99de6ba9b39dcb57e4fcd9d9ed4d1b01d4c4e09963b8460f84b04b14f4de682a36a7103e277c1769d3db0878dbf8a353a0d00704ae9addb10453ebc644737203cb2615f090a7a7c53fbce9add81b3bc76b73e0cc8099670cfcea4215b22b3d0ea2a9855c0be79b1d4bb7d44c38ac9d9d9bc193da5d3d5028b40bfbd3730527f8b423170a85fee66606a0455c302e9d66967bd2021a0aa98df2e50baefe15219fd1f3c70a3ee5969fd428bf6942dba8b1ee53cd26a120a1ea9c74321c741938110b188cee1318399e4a6a37504dc3aff9cf58669872efe5ab4bdb29d1a748068f3b49bb82a414a0ba8cdbad75e64498763f94aa484742258e87c483299762725f6c304b6d28cb48bc19eb3d395af7817e1e773cba9da6118bea0be744ac5d72d96eba9430ac8fc581209b2ba5d699c4c69da1d9afd4f3ff7026b48b6ddb001510bef821a9b68b8b63849e6e7422509ad4a9bd02cbedee90d781f150d1921c724ba03955135ec9fd76c8729f67dddbb7148df82f17e73605da13ddaffcf39572e38ef9bfdd665bf23cd1b7b0fda3c776508107e3e5d6ca5f9a0ed5bfe163bb35735b321a4edf857c60c5067871b30b1053446c03cc56fb8d370148ede8af28722d2600f6425cb95e9dd2e23387f3fc324efa567b9a1f0e74c23b01d172763f72add2ed9a4a9aba999b05f8c982b44167b3d78d1373581ceceec1f90668c8ca725b2aff650d47a8bb34f5d7c412ac6fcd05c7262069b400619a1f4afd0a7ed8726baa7a163631e78f7269f692dd539971b27e6ca52848cf8758ddb620333c0e6b0bd4d08daa43243ab9376ee6468790c1f88343687161b4ab645cc374293fe9726af49710ad9838b85bdaa8eca03d45b617d2c7637ebc034dd3b39dec0198a97219d1c5b86a4b747814e6e890210ef27810d5fdd7d79e2ae722f8db241a369572a1c0f01a360c7b56c93f1159a5a7c740da6e4e73b9f0deefee06e050566d7b4396105bebaefc605a6c8c4454b1544dcfc2bf910119b92c1f0f5a6f6e68c6e472b9828a5a68abff5c14a315d0559140ed77ead2ed1142a0480876e5290b3ba55023a02cfe5d79b26fce3a15bfe37bd7bc1bf8d78ee4ab348acd951f235bfee572f412cca8ae1334e94f5ebcef3f889814ad8001069ab58993910d78955741dc3146bc6408f935ada0e9f9ce24f9b6d0ec2dcb8aaae95aa1b84f3fe8918dceed3ae37ef7082f2a11817e2c94bdaf29bdbd6481d5085a3d70db86d4d0e6a971f772248fdaf790d368f996065852e233a5f34ef988d91f9894eb5f2381f05b2d4831377c510dd0bbbcf6212adc1a3063052061b140a57be4053e5a21d0c520185d7207b36c9657a6a8271d8f32a95c26419a7e5250dcde51d5a93c8d8630cab83872aab88ad1d88e777885ca5ae71c98189b899757489b28a0798f033079bec419324a0af6408beb4ad4a6e24138ad84fd8e0fbc9fc7113b356f2b4b6fb80ba38410831098b2013319d7f926ddee03df99f5771d3b70d2835fe19b4f16f165882d72876fc13e6c5bee942269674dcf3bce32636cf46f668fbecd849358837a06e372ebdd7871835346d7e3974008f6f1390cb86823aa73b971d20a8e3a7a3fbabe72f6a89a74c2e20a477ef2dd293c1287282529b943a47e6c480770b7b9ab2d5369110f233b38b946c5d0f874d747416a7e6914f738138d84ddb6307d22589e0972551443df97fcb08eff266eb943aeb594f573881f752d3defecf72a799cf5c1726c1b2aa685f9244b2d0d7852f54385109197b88a22dd66f7da60b87f97d28172d056f50497ec20852c4c0ffef1775cb841c46497f973ce3c529bd508b393913558df8cc53d6e47f1202dc3918a9f5b16f1f6685c100bfa5304269ece854e37729501f4403f506c7c2856d25e1d29c1fc06f07cdfb9104549007464df74e0a22df169787d1b1cf0c525e896b31a22067deaefaeabc77bc4d11abaa640a78d07722f2baf45f9bc03d876455430c2735434f3c63f4498d206300753174b92cee3722393b1a542565ff471b3559ee4ad362150e757f888b36157e0c406af40bb690f37f222611c0d04f42c6e7f2e3dfbc99b63a33a9ef51e332a81fb080a34eae972511c3aafc171ad98d554a58304b2ffad302904c0245928023d094385e6c8a57229184c8199af20b4270620e55481693b2ba5cff2542d75e51d742d16be047f72399991ee4d197130953f8486bc52145f18c1e9900cda2b8c20e3c9683bff417253ec0e131cdf9ff921bfba54e55a5776c8e526a4580a6a05d2359478a2063e72b029766af635ac8d1034ab980990844d26ae7eb66143c1ac46fe25df11d238729d7073f673ec053829fcb6760500a0a38c43c9f9c7a120e582b98a9663ed9e07fa4f649f150871f6723126be88d85f0b793b464a9d7a71fcbc480da912feec722bda83e12b3bfa0635e4103a516c52b6c6e3824155d5c4cb922c9c1b09401d7261101544708238d1f604ccdd40c70752a3488ba02ef328d16ae5ae1fbe650072d50899bb8854845b850843be72fc266e18ff9fdb6e40495015c971b37497e83d46e42718f98631fe50af8f30d1869981e9b8dfa4df249818eddbb9db8f22cc0b3b278467313b74a54d8459afe4ab4e2aff25b030ae2de2ac76329b8c238f35723441feff77f440ca888562b7777b03ab8dcb91cf972e5edbb6e7ee3a80de7e1c06cba544686614e53d029b10cba1bfaa3077d0459f3612c4b7c2d8998b9b4f72be4f07e451c53478084bc4aa32ce905c5b27de2c137f602edcccd64fb710f11869dce04d3cc9224b551ab4c6a2f6ef52fc732d9efc21da5dc3f6e03622e3c8312084b42ea00e8f36edf54e42d512f9a479451dd108ac87d8c6d8950bb59c5172aeed23999fbb6e73d830239d44bd0b2fc9d089152ca52302568902f30e825a2bc59a4a63b56f6c9fa42e49791e204ee8897ed6651b66927c674aa994f02089722b4dd3380097db9eba287b6f377c4e7542b48f39379d4b99079b5edb21991d1820ece0f438b3d7aa13bdace463d225edb23caae7923605ff9d2f831c6a787472b067e5e7d6d258417e3969ebe739bb1cbdd451872221d69cc7eb10ba0b122f72d9745601bd84ec30381b1f7620d35984a5f008f9217a1d191481ed578ef1be722d5d825c0c0a1bf526e78d9f2107bc487a269657bdfa65eb6114de1f2092e41f0603a9fa229f1829c1b7f3a38e22eb2eaa338163edc2a84147ad6b02ba1ce672b26f8b7394aea2a32f5134bb6de70e408cd5f62feabee5459bd288c0f0a2d53245504ef420d5c98c6a6b8418076344771870cee2ef77370092d7b4bf1d3a0710f50835b76dfe313637f2bd780db50b95dc484b8b180729b8d13e44d4c0332a703d037bafeb67def577870ff8e80dc40ab2f3edfe090324ea523ee03f22ce957240bd2f4da29069a607727cb80c219c0667bc7503ea664d25a1c421bbdb56a5722089c2140ea2b5570420d42495cec6be08ba456c73dce6d70c48486bf1f9f37245133510c709267046780d07745e8ff86feef677f4fb949702fcccdb11b4c0726db75e31e39b13d832eaedd4aabe94a2e84f174a766860978b9ab67167b94f7219bfbd3964197adb90d74691717ae185a29d5c5b1ac7ed00f42133275963b872b7c7f2e615e8c0595faa550e5f9c30b0ed24f355baa4660852c0b54af97472720471e74b694b01f8d34f30dee396a5c0f75f92bc358126efdd81157a9ccf17325425f0ebffbbf950dcdd711c65fbe30377aa6c07747dbb30fd5a2182aa30c37241a8b7559263a05eaf9fe835e7494e3d3c913a1a9699a74b35a4588a25615172cfbd2699ef108061a70641e3c6ad80a604bae6609690c0e65fed6fd8d931e072d6e98fd384601a4bcaa8d719d28eef5050be69dbfe35ebb116577e392e180c72aeebbadade7c07a17f9cf1bdf2f3c1b218029fc66dbb3d470a049922f1fc9f36223fd3090cc3c1c380a0cb38f8db51aabc1ed302c7086e15fe9808e787d00972809298f96ee2adc597f329262255274dda15edb86d3d78eb600ee1bae1522972e6976bdcc3a24f73d580bb8281e332a8303ab92d2211cf755b4a8c45ab46ec724947d8464f3c775699f851c07053b57e3c46c238bc1c6c9bf6c7953eb43d0672610bdfe7a70000c52c4105a8fa30bde2a1e2926a0fc9ca526e31b29a70a27872262de343051a17767dd3e8b140490b2588a0e98e36dd3782d3700fa429b87772c77085e70df6bb269c18ffd7abff1943cad72e09cc6024105edb5097c6e83b72a47de312ae0344fda6826db1b7eeaffa696ea4eac7e9f1e4d2e61fe9f7f9545105573bd7a97265be9ef39daef3757c1d0d95aa69b88e2e034cb1b02011bff972a7ddaf545821e2931588c9c676f8290b2fa9b62b0cbd9076828c36aff301da5f40ae86f357d1c7e7c8f035d5b9709aa1c42886207b75e449ca7b2d6fcf9dc172ddb7a8f0ca874740d069111acc28094df3c7df208ffcdf1059de9a54d1af48727688f5f9d63984f64d5d3ea607151975256f44e10acd9d3fcf90a90380be6513280064640578803684f456516c7dc1322206878b2b55841efe8b9fd8fcf5e572c41c08b080369737467a74ce273401923f89c5ebb41c6e9db79d6a61e3baa455ef1b157cf25b4b7c3da4f7073b584e81019b8fac77e3e9230f584aef74c8d67279cc54abb90e37d2bb56de95904ee73c832c2a3c96aad18e0b23152e3acf872239e7698374cff55c3b44677d8c9499e86c1cddbabe881f77cdaf003f69ecc23710b5046b8779abbd4a0382da71b9ba5ebab87fc474414faceeb05a3bb67e6053b5e03dd7222d123e8f8ba7b28f7f848954ff8c97e8ee4fafa70ad78b6915db68e1bb9ad9c08dd22c66265a0dd2034a6ff99dff693f7e7d804dbaa9b0c45ca672f815f851f7034d7e3882452ea42f77fb065f15a48a52a68e5630cf8f4b17d4624a2138041c435807bcda1355f25f3ad36279aff86b84c842c82042426801340b76b4c095edee6a9d77b78d843b6c1d23a8b52fdb1e805189eb330ea4ec992b7239ada649dcb425d1805e9cfa3e9d17332be3ebd455343f1bc7133815b92b3a72f643d2f6fec7910e8c54e880c3da51d56cc5d8509f530ff5a928dae7439ac56a5662d8df50e16debc921260332e3763f4810329e981de52540004423f998f1724201940033630e1efb2624fbc705a59be4f54bdf3a522808ca6c1ded732de87277bcbd95bb455d51c2e7585d9811e8076745f65b16dc104858e3b425d142672a01da24b21546f456804f9780ccae33185d64b7ad82e6687d3bdc8ab47cf29c72ea00ba5281b6e995ba77f8484b9cfd56ee008edecda3950c627bc64a6695b45c1044b7297e8ad876552a64d72a048a37438fde30cba8762814e61622119c1347b03864d3e1101a30e6d5e24f4051bbe872e76b9df6d6360bf369261453063972e3f918f88949fed8096b7c7cf1a87974366580bd328ed60ebdb0869c027f10350d9310760fc3c1b7f1e0045691019eceda7900f14f4989ac4ab75109723e5472aa95e26fcde2d7e5b675132f325eca1424877c3967df13fef8233b31f148430c7e2745b12ba7b8e293b77af16ad68a138afb8c1ee7e7a5e638d5b05ad3143d6776ff254d91889846fd4db7fa4ebaf60a20fe1487bffac7e07fa620373abe8f725132b5d047d66ffda4b11b705eb6af2a92eb7cf6eb65ca7888de4a7a5b05547232ee6490cac3b45cea8cebe644f653a5b6bbb9d1d730fc860694bb24b1eccb1ef28370545b784c2c1fa10d731b9a44f0c38689f4b350e857fe490f949ade9e72c2f70b66ab49843d81d5889fd325617cdf23ce6a095e6000fe4794a959db49276896e693e2faeb78f52e6039c5ed8f34b36eddbff792587aef89fed6ba0e4435bf51b965ac8d10194dad52c3dbd688e374e7a9930676ec79676a0c4e96de777266e4b7626ef34978fd830d2953e051356134038a94a94ef54e71eeca509dda7293b352ec6f6dde513327052dce3d20579b4b82bde5e9c9f697c4f62ae44ebd30097dace2742d6c7098f90fa3c02d8daad35609a2723bb4d0c90cbaa3dff13123f30668217d0d6522aaa4252a427e47688efeb0dcab0f937dbf991397e8597b0f51d680f3c834abbfc00db77487f87570d9e0bdd4c5b94c742aec3a30841f1272847c82b4c443bf80bac6b0e134a7929cec53a1464fda857ebc4c5ce1ad3806728b21d4bb3ab8f50d08dd440c59329ef8808049fc9f314c78f67c288f4c334672d0b60b622ff8362e3799cac300c6c5ae7bbf1e406740d35c6e0ceb28c122e41800408b42baad6d97c9d5af953d9f223cf24838950a4628874688ea849db18e06f49938fc0a4bcbc0a8579e7578e25a565673a94308eacb776a83b76ff256f27262cdc736570618e2254de97a35691f086f2957f9f8f760b6e188b82dbf8a3e32878927df55596230e57140bfcebdf3b8a9b667a6e3803eca5501b5ec99bffd725176863d6a5a048f83c0d3cd3120e319dcb2077436734b0e2059fc4310e2cc46b01d99c6da916fdb94bef01e063419ad1576d4a532af1f6b28ec3eb81b37c072ec1e5d702ed7eeeb990903f7eaa4ad09d613fbd135c28d0fbd293c0b36c0447279f4ac2e925bdb650a716e2fe3a999436ac3541e2688a13488c5de77f049a772b04adb8cb43f0441ea2a9235357d194a52adda2b0bc489e729125e5d8f11930dcafc1d0c9353dd1d184223964b60176b31786e3f797f520f5d6041f38f6d8672214ad93252fe8071df3ae5cf861b5920ddb40e60dfd57dd74d281eeb960fd3722bc578fd0ed7d869535633126835c51ef774011b5b3e682d53d9b7e4201ba872689e6c5862a134117586986a85d68cc9e63f89b146d75198fbb29bfb9fa4c13b071a67ef06d07f59e4232d47c4eea18425f2b5d93a01a8682005db66dd4fe16d886e499705605269546610dbebdb26cb16659d05246045f49ef5aa0637be34728cbc0d195910df39d26c60a8f9ae273c56bec839056611777f79477644547a721a42fcfd78760dd49a9a372a92a6b88bdd54cbcd1b7220a0cad69b6351c81806a0fbe9b07aa87f965856c82aa73174165305ddaa99407a08115ac3d37d24d76dbe4f677f006fa68565415a111bbcc88888ec99277f62186b9df55932b5774972a4f8fad31cb6f7cbd68a1d4dfabb56ce756930399d9e3e89771aeae0d903d139242bc8ac5efef74b55a9686aabda139413d0aebe41320df3bd9b3e50d059115f6ed81e6a9a98939c432f403285fa65b7a2aa32c4ca0ab84aff08e3cd56319a7253de0f9d69cf4ac6e3cf12fcf37be9babf03899dacff0c224a2e9dd745e7e4723a32129ca21e686078673bf6161d88d89cae4e5ad2ee13111f8b3e47cd0b7522793563beec53283888d030334c324b72609483d243d1c92432ff4d5590272d480ee5a48f93c201f9dbefc9e0d18e6dc46bc2774e6613ea25c43813ab26bd7d723c0e9c1cea6d03311de2349569ede2f7c4cde57f743020a630bf820d8c0845726e8d67be694661f268c3ee3e1dc77ef5620139e1a12229965b2a11958e3a0772f55e1dc4e8adf61f75296f62cdb6e83dc82f52f65f9b13a8d269818f77dc5472d9588198192b36a23d38285fd1ad6b490efc4c22782a83189151b9f125050e61cf7f7c9dacb5919415dfcb90fd6d1b73d8178fae239594cb3a00f7feea659b72040d9223d21605a3508599fbe477afad43a436710cd395c7c18c7a0f55f4ec61c1dcac3c33cf3ecc8f16b98253a5a00b33d8cc73965dc13f1c876a389c063826c944f9e65e28283559ed7dbbf80dd35b7d235ea54de77e7bc52a07edebcf02725256fa1b3959fa6dd9df4f1d82462c711da17dd5ca978a9a5660b994b92f77725f38ac26fff9c04e1c1495657ed38b8ef72885320f36de57e7dd4c7b7d5c6672a924397b3a47319219fc59b07c444790b16f79095b57eb29f103f7a17e5cfd4244db99de908f3784952d9d9eaabc50b59d59b22c64d0d48fea25c2ed86d0537281ef4cc2d24b33388de9df9519103b7ce110fa85d905c8581a520536e2ef12721ea8777729e88b7250e8e511fba10d1b9f3841e30107424a05d41fb8526e9d394420d0c7b9c5a7135e7255709cd312cdf11d8783dfb233aea4179f7523d991728cb48524ce9353168f6a8d19b20ee6a28dd247e03093df635219ddd9cc07a9727d65b8b883ed0e854f2332c95cdd015f37c8a19cfb6bce92a82a1758033c06729f3a68972e7166ab232af514c9232a14b99a6ee6491b1786954af93d558a663a419a1d6932cf08caeab20f5123035c8ebc74bf7b99bb3c0b1f919ac5507e6c4211067747604dc6f733e314dbc97df9c613e33ae75bd6cec1f59f57b3b5b14472a23f9003db8efe42dd87823e95831b508bd984e0e3155cccad0b0eaf935d2d72c65071d96d6bd07cb11f242b2a2fbe088704fe585feccf3d5f25d95d23a1ff694245feb0c4491c80ab16d77cbbc1021bcc52f60c6199ca677d6357a8004c5c728d4a8692bac7ae15cfb5837c03145d788076302d916c8071e34ffd52f900f00b993abee96a36354b97bfadc3eba4529cc645033453fded91d6970f2c0b5ac672e9a69a4e81985eddddc8aa4a8993b449346652c7af93837fe445acc2ecbcd42de50c5b021c26dd9ef1d7e8f50b463c9e18635f11e8c388c736fb33a7e1748872ad5fccfd8c14aff3330d6dd8e9d4ecfd7a8799fccbde300dd5a21afd2694023d43bfefdccfc4012619656db0c25df8a17710ed1af208e97f758070059d62633827c8c6f1695b74711a2f0a337722ade34930568d66c48558d4ff530d8ebe1872cd2e4a3b94a248b27c8df370ee6a4cb876c488fbeed1b55e7c97eef17515df3c4a560d8b0e94fa1a3f1dd3e916233a56fce7e5300b8ad58fe5181f28578c413385ed5de4a7feb39c8c4debbed0995975bf114fefcd1419ac629e12a3a48fc72c3f894b044f6bbf8aeac453da369ebb71c8af2acb796e94f244385958baef8345d3390dbc6420944210ffddf83000e55163ecccf4bbad33fd675e3fab0f0a6272bf3dd9574c43e7821c44aedf6510d90c1bb1897df20d50c6eb81302f32786058b6dff64c38f55f793c8e03661c4af2ce9b39640c662ae2690cbaec368759690745c9b761b51e66ae3ac77050df77c59d78f2194926e3b8edde152b6c41f3da3b79318b7603fd0859c7fea42e43dbb2a572f255107c47d280afa11af0378fdc72893f0b49346f783d0ad6a8b1786d674536320d70d8ecd2e4e39d495100590b3d3de8fccd7e81ba9dbd37025d7da32934e542fe83833aeae66279147ab4592b722f0b64821db9568937d8e35f2467418f9fccc06cc6dd29f6e9f98d76e5be114538eec06b2e7a55245c3d0f5797b903f3a3c0b8f61c778d80eccb3031d27d97243565fa2f34fe52b1df917a3b4ec8e2d1cae9e8eb1506b852755fc9157bb11d42197ed9c73f2ca637c6e13f5734981df1419397af6f1d506192b300570534b370c3235bf4827f6e959465683546bd5046ef78fb040a59d753cbeb546889bc7d726844245c2c55fe9fcf0c6bd432b1906f0563c4064e229849bbab726b58778b37d5997bd305f03c26301d1aec89355b42045bbb878f9be2cea584c898859956721e1ffd98af2cd41d7610e0f226d9a92c5fb00d26e4747374ec524085c9196e724ada053e486c5afb15f908a1a3d5a2bc94c7e3c5645e0efedd79f569f40f8a72ad4915afe1ac03eaa36e4ec52eb4865e517602c5b752fe3764055b35e1cde272ff9eb39b3a6d4603a66028efdc788fd383dde449c9430d19ea4cc7b2a5e3fe7225705293e84a0942855810c51fbf2a224f6590136ec51c29106f6a54fdaa3b627b53be7885a2c06298d84321e901d26e8f4c90531afc4f322a46d6f28f422f72f8cc623b7de0023b014dd009a0b632091db5f4766c4eb629143dab3ebaa94172f3f7c15abc674f728dc0b529d817127d23d736717687185fc966235ef2130b6e1bb90ed7b1973f1b7a2ba5753b0bb8df7ffc150bb356658b9314123fa3978d72c47cfbb3dad20e27e397079947013c80fabb07f6ec1396ff1984c0a1c621d87206a08d4bff7fc04b31783574eeb69b582734b817e9d8b5bb5ad17da6bc17d32e56e551f9093d77047ff2298a5084e310cd5eadcf06e33bb0127c06b0ff7d8972b73618c4a7e8c0e508e99b5715ac01c1c48f06b565f1f53fe38ff6b6ea796772d7ab168ba11133e5f5070e9927e2972430b287b8c9013ab16d78d467b573a972cf6c212a9cf1b5277582c39738829e2c4e97d1fd3c1e46cc89430dbea556417265e182fb9f7ff80e88fd623bdf512088bbd5c0ce60e1c47933fe6b4981a5685ee1e151aa6c52ebbc72c252dc33cb056dc419383d164618c5cf9fc90cf1b14a57f98da0dd2bbc7d8a71d8d51ee7f0c1907e8dd2dc9690fa7f5cad11c1aafa4a720e51f7fc31e27518b6016e76266cfb7be41e1d78c632c7b95282ce3c161ce16ae51932a316ade08a9800a4fe377ef86d58fcfd6889e7391be970d308e089dc72211bd24a4fa88af1fa72be6ca1b4f0f8984c698d7eddcc0ff983a7453c1f0272291d8e6fd942efb63bf450f8d7d3b8372ed703e6626f0106dc033d11262350633ab9fe6173672dfee1d8f11241dcee47dd9bd7e66bf3fa87403b4146e3f6a972e9d564efa85b44ab3593bdb9a5f1b38f2b277f9af99f430693c10b1f3f77067286f6c613fd264f95a3b3e63cf8f4bfe9352fc96e07689a748ad20bd7c7fbde7232afbf169ccce9df4e40716ab23d0e56424ff48ce1c33c1a2b481f5a642b8c0f38874798866bb9087a59ab107006940a29d8a1ab734b03fe00d03de2abfbdc5b02bebd9bd1ac30838e0742030a6f97af0dab8475006438788b216e633153b121002c8a40d1bd893725cf40c3ed351e82d960e2579e11c4234deaeb190b43e168d7f729d9ad8a00416c49127e8566140d7bbd9e5dfee39b717c524f6fd1614972f1484f4c8d2544819c37cec6aab7ba79280b8e4c60b735c005fd74438f2b2d4e9261bf90f6c627893993552190df0d990e806086a03ede585bec2dd55133f972f37c39a90e8ed1bfe26db8193ce259659e400bf422a5430310466e145dcf193c5b9ba34eb47dd62630e453c7e3ba1afa96e1c2c25d2572936e8f675e2a9acb5a56f0fe3f31aa2ca77f708f27c2b7d792fe13ddbc93eff61aa7f7ebbbfcac4c728ce5b8348c415d66bafdb28392cc503b11b0f2a31441fbd623b8de77e68dfe72d7658cdbef14e88de8d6dceb4c8c0f5cfeb21b1f53faf8bf1523c2c53f69b672cb5490a8dafc56c7934fd15799f26185709be7e2cf5c15855b5d11ea92f86d72dca23bb55c46ad194a4bb0dc3ee2c44644dc631c22da587c9b9237b330b9a14632ec608a2ff5398b9bdd152899e58ab2172c50178e4a90427711854460f02572033714de60df44c6f23a18e7253b62393c0b3d97b779a09e80bd761f72065672184dc3320c53de4ab7d7a3ef7bcc68afffcaa898192d1b872163b8ad690b1c3aa67ddc834dfb5ad8209887b99c47abd3576d290c7d2f8b4dc63bb50056ee0d1f33e767f194852d9dbc8ed97f400869f8fb837fe193429267ff6f5a10c030175dbc98dab447febb381bd53a09f970e50dbcde9ff0eee1df630f9df381b637c8727f508af2c1b96cfd0d6ffa752cc65124b8a2b6e67cdd664892b8b8d2bdef7c7298a82b5741334fc6d61bded7ea37588fd449f6ccb67ab1e18441b5a727e3ee0ced1ff7fd80742c03321b057f32390a0433fe187de0acc4fd287ac3e457277c1ad81be2963e767b01caa8528f054c083937693a9a84cc35f8af70469ac1b69e7216deb5d8b169d863b468cfc5c817e5c89b701d238bd7964e1cfc383f4d79d472a7f4ff37cfc24ed50061f2a2696f4107e55128f69c60c9917e1dcaa0a6de5e5b4dd664f8d306b4b5613e46b581512f67f6d2f32ea20087046d542773dae47c2296de0e44a600450bfa913e63ee491c25a0fd3e8256dee32d0c7ddb42a99f5f721692925d468402fc0f8ed6be992d111e018b2728604dfd8908573e673acbf24189d11d171c35157a76c0f06bcd0d91e54427de51454899e9a79641316ffa33727e09248619f9a0caeb79b76a04371bf677399f06b916f91df495fd657e02d220e4bf4aba076179ed9ea256ff2e2a0f60d8082e7f0e7d39a8317089b000e4864e497f1b542e8d8b9ee6deee8c4c0abf0461c3e54df60be1af34afe7f5a05ac44c301d6bc3d21c9e880c3565fb748d6e6933fa94d08f96f7e8befb43a93ed197728e9aeff4934da03d8be2603c8f323eb8ee73354047456148a975dd0de1f41b1304c9b94664033770d363f017ccf3aa9a24ad71b12dc60d4eea393e1db8ac666adc4a1bc914277e1d0fca239be5de57e58605f5f0913201beda8ca5840e95187234391e0099ea26a60772c60ed75e571def3f57b1b8ca606439e2628705e2fe728c40c49e07aa1d8e5e05a74323356dd5aebadfd80e7b738e77d38cf4e97e1f4155301c2ce2cb125848bcb211a2563addef4b992ed4c417cb23e68dbea4a41e723d828d3e888fc10107a4f96292091d0a2f891e1cb303d442d54c14e7613544729b6e3a60db4bf7442cc6554fa2bba7b42857437f3b05ad3e088e671eb02b656ec824decb9b756739b0e6adefccb2bdd92231d40493a552d21b799f4c601107721a0116ae22525b357a69e6e4d7df8771fbc44ed9d589cec0820ece67663bc61abb67b3908851f8fb62727f93188d824d716823c450c0f1080b554450714c154d8f0b2de2522bf63a73d7b5dc9c3b48c06f264a22513981ac516771ef937fdc72364a907f11796f76e2fb1b68774251b1f06450ff772cb78825a9fd3827aa7526b1fe97f35f91202c3882087cb146785626fd00029fbd370170e95866b58ae372227bee9b1cd892ad1031e355faf7460034843d9157d6fc58ec5a2473224ce87239ab588a60040b887ebdc35f613eb0cebd2de634a4dd40c9e137b0b21991842d90f93db5d9caed45e4e8f860534ebf330bfb216a46608f0e8e71a11b26e462016b9c2896446b23df59740c8b576ab3490a320e4aa7aef51bc803fb1dbb220f72f150e80b75a1174abf64ec61d0ff9c35e169a352e46f440a416a2749f395047250da2e95e6f18f43835cd7d39f801fcaa0f9b63553cfe4f768b14c0f9cc82072d4d2162ccfc5e2bf7c403866b5866dac4cc886af3cd867f24c20cedd7701f64e3fcffe85a6bc0a7ed85941dd036b69403c7e9c617e8a9d2fee5d9dc0d7794b7252e3accdbce9a9d8b67c2a2e12e41e3fb6f2225112c50db2d6be038740326b7270586ab2deb5224d02c7e133ac5973e6a9b484462f8a5b30a6565fe6e60137721b5b8bb83864a2e3242a99aec3f28bec8c99af538909ab1ec609ff6991747903b3550e030949e5d7a83cf2f9920712a03ae8912f5a5f1078dded6ae077b3fb00cd0d277ec2bf99b32e783af887809995d8dddc7aabc00fb04d11bbd885d5cb43033e1d80f9ba87146847545065dfde87eaa8e2e90c57c6d879609489fa6c5072cff9ef4477583e5b9136d96a5be91b13bdaabe931cb1388ee717b5484c49b87293b8682cc16b584db61c74e19737b8f741fe7dfd45ace57111fa8801723d6572830842c19507c39907007934c2599d7ad527d6f6d42a054e68a5592d028ea9727f719fca442a6080ab5333face0904315dd2c6a2b079a79a20cd9b8caa570c72946e6729966cb401e6b5a0088668e5532d8802644200d2035e9fbb82456a1172bd2b60ca445ae1d75328ad3e148bb36c8d2430b512c0854170c519a1b1a3375611ba43b28329fa5cc52abe02264e75eda53b7f36d0ac0e7e66814885080aed72577165d2dc49d9a0420383b06670c6fa987175978e265b3a38c1a8220d8e0a666891763bce3ff0c7d2a136de5188313cc46f64a08209699aefbd573cb1a0766438e7cf4c96ff575bfd83914b57e3d406bf09db484e2a6f759ce65dc881b59c38c9589f4df2daaccb31f430e3b1bca5c9694b725b06d5068f36ff2c8a26a377445a8f3ce49f120da1ab974862cff5937c1b8e725807230e438a16858fd0bbf365aa46dcc1d9cde80cc19dca3a3d679522c73fe4b8ff0b4f794e36ee7e57c5791ba4328e3f81f161b2f6cb0341a4760a683d2fac32e82d382e096d1bc99a8fdc7277823e9514500f29cadb1bb5c6aed2b52568e2f40b5bf9e4e1531cd8bfe81e72e2a307cdb2f65875f7d176ef0980d37100f4e4745a29d9acfdd5481518d3d00d923da832fff4d57ae4d6366b2ff9eb355235f4f8d72cbba7652fe139e2cb59726da03ce60bab940afe9bfce0ab44254247cfa0e2709b017e66de8b070bb0a242fed7742d06b2e75cf2383f5d1f9da06e73d234aac3c9841f3b9f17ec7096fb6e3968f753a818a68fd9a9461d465791af9d8875cec6f26200f582148f9d113e72ccac0db7dc5963c627439155ec3cc0093358affed8469a17022a3e8b1225af363cd61a396affab94c4cab54e93b88a01bda2d9bbcc11e07dc294b898145c6121cc8f6e3ecc4195f9917da3a7ce34b5c5fb9725362ab4d04850dcf7944589b772adc9ff0ba92cc5664584b314092ee73a7dea3bd1c876ea910c98564df42e1f53451346780c734e94e83621c302ebde157b9f23794e38e37c576fd961a67c6c72be640b7ef454fce6199ea6df8ab15a6396c63aa5fef7f710bca5f3b46bc85b0abd1d9acab0b145e6ff9d784f169c93b5ac20db9f2719cde811a67f818afa4d7204225dc77d1efc4a050792fb57b6c1e92abd3f856d17cb5d928a8b9358c16a7239f337ae92e4a99f9679d342e224ab6220686ae828bfccd4bce7ecd60544921da33d7ac12bafd9cdba204326e07931d36a3076c6ea9c601caad2b5f114c888727170662b31550f4aea55f9caf1aa8badc7605764d4b1badb99e36fcbadb62372da803dbe9358bd786fab87d6302b27c749d31ce0a60e630e6d872b222d3ccb7231b43404bf7a47f25d05ceb24374f99ec76a4f703b9bee0951f4274c3138a1727986c1548d43af58bede7d3e1d89d84e02ca3409c3e490ca797d5d7ec3fca57265b68d28d7be022493c718802650e1973bf21cda2b04845d6bdb82931b3e920eb175e64119a7ad358cb7f8cd030706080323abf24a5ee96cf271550fa6e23572dfb17197e74b42a3581e9d171833dd9a43d6fd53b4dc86163abc48b556331872c7377caab76c7d559469f7b499a69dccc43049e0d39f055add45a675fbae8472a3609f53006a476a476aa4c9da56525124e324db9f74886ec8e0a3a54202ff72b77d5c8607e941cac32b447ce1bb59b12b7b343ec4b34b77a4e033656a1c25248ea225b1daaea70eb8f4cecd0dbf3573d50cae24d959e663d83472f69691716266fb5052e31d529a3da67a0b0640df1edd86cda8397c19e5b006d19ef6e781728f399a37363c411931fdc9d396d454aaa84da315cc4f7875031e66eb082c973668cf576070a07c28c3f7b9903a618e69f55ce5db7eee8a6a5d095ab58b9c2d1e867ca1c92fc6bf6bed5c2ccbbd4aa22fb06e32143fae17cc69410fba58da05354624eb4e1a3e5c8c84b7205b4caf5930748fa6601ef3e5b5794cecdb010ee3720180e00f61d15d2cc33a46a29f0b2a0d342a5a1622d0dcaa74c99724deb6734a1a3b268e52061d3a4b3b189ed0cc5d2ddc9364b17fac130055fce35e31c9e139fd793a375087b71c29764dca477bab8d8b6dea4c3e4e77d8b6b1472db2167b7283833c57da291b5ebd4c34dedcc21cee10fa0b0545416e911abe3d1bef1cec72bb30004049865405f5841a1c9cea6676c1678f3c3df6d3e975f72bdd2b32bf42e14736ca50b6eb42755660687f352741d49ccadb40699467c4163e90ab02a172d0931008ce4967fa1264b3c34987ba1181d74b4b0e7c5058b5b654438ecdc4725a23bf505fb37a10d8f0ce1c86f12c36bfc46d4d53f77a298d70032871e01c720f07ec46f4d5fd509ae8d9e53a8f80116b361d46bdaad7e4540cfb250b487c728978458ead89cd5b5c71cd925bcad85cb0d2755f88f0ae21b4c63637d02e0a722ca6606af5df559e935bd484f56b33f68694e6817277932e88137ba8ca0a0b72281ee894062f094e2ff14519bb482ca43695bca8c227dfe4498259574a609072736e1c7f3b4879af01b5bd4cc2f47cb1bd9c2f3dc3d38b811c6490f0e130347214d67401329a4bc6a0002b16851588a30a0931c445a673e122698c68fb5efd72a58edc99503210a6a62acc1dcc78edf1d0bb42a132113a34304f1c6cb1e76d723a8558b3570b110feb4b9d76cd2b3097227e5b0602d1d0e7d538750d23be0172c12ff08fa99979ba992e5a57dfe11d7eaba04bcee88c2aeda4b3f5dfecb58a20211517e3ed7cf07e14fb19a2398e01fcc39284f78b977b1d935bba8170ccda727fcb7082e4b6fff3aed95071038d0d40e4118c4ef24c9149e160785837bf3b729f0bf9a77e6af51a674c47190f50f5117c2aa3dfb46774a72a6d738af56d9d6ec0e7aa35b1cc75cbf8d45643a87023d37da04d5983ba7283c5d7e0eeaa605c592c6a8268a187f13859661dfa3ee1e5cf3af19a1a0bcd0ec615399c215dcb3e728f4ea5f78f1670acbca4bb71c59d6b6542bbcbad0708a3c48092b13a7573053882a308174a36ce2627cec173e6e6317cceab2cb921d044b99226f44ef1911f7225d9e34e7c9cfcbc924aa869e2039c21b1de4382058b26f5417e036b2d261972eb3352ec2731bf55886dcf63f3a8e6b42e2e452d1d9b5d4d5f439d59178ed224c000a59014a73f78418160f80f87f1f16e1ab9db486f248585d10a77d3be0a71be895f17c735ebb88ecefe27d13aa63eef523977be5a41d4de1cd32335fe2672d559a9b617dfd98d5bc4b31489b5a34cf9cdd1b2d53735b4fe3add6676d44812a4f295a9c62ad6d3ea17be876776c786ad0bcca18165e22253f0441442933e40007f302b509cd08ac3c147912c60b9e6f07101f43fa166514afd55a456a37e2adee071537dd4df078292a8555df7a4723191ac6efa028f120c760ae37e495f725d92dc09f96dd8f8be324fd31dae328fdebf72713c75dafec9670e1d5c72f1724f0c6d776edfe4756d04d175aaf2f77596aab514250e3ba086b99a9814c3bd7247154ac482814f69d63680c879174e12a684eeced594b6208236f1e3ef54b4720a29934e4747cd0e048412291d2ad70997762150159ed66869c605dc3b0979728a3e4c5ee243734bb0a89c5879d4481053ea5fecf32e0b870b86555566ea2039d47df26578de6e364216b03ca2e593431cc6e78beebd6edf5652fd8a1dd15745127ee86c69c63679a66850143f06c6a99f5b7c5c0a19916c6bd59a649c29c76a7210e5f98c3bca6bf3e7f3e84b2a2ad17ba620ae25f20777fb4056236c2e977225dfad394b1c7756a4ed3a9dba79ae498e8b441f283497d01ba5f0f41a88da72adeec09a9d33f555c62a5211c0b80d8572ae85a9e9a03117d43ecffc8518c5722892460382f19987c7c3b219a665aeb8667f3214b285de05e6c9a365ffa6a172db1746cca682526515876186ea3f64bda70aebd2a10a64f7c38689150eb6077200c87a32352dcbfa2ac82e457d31d1adf86f75e8164b20abc4e579e04e388c728da5cc7ab4b4ce9eea82321c44716a6942b72fefee63c599122df83f2a9c677203d92ac4c4ec6a497c5d5cf5bdc3162abf9b726359c5f9adc6d8bcbeb6327372ae807ac53bbe48ed20cf00698d8d28bf0b238dc21c506279b1862d4003234d4b460ea881e55a1e58d7b28f57dc3bfde93b52a3f1c13c7681e81333b4328a316b5ee27d4d2bfd4e5829a781123dfc6f34d9b96ce587f2f5e7814dd5ca03da4015a78b06494b069890e38d206ed42ebdf90b5ab41da2a8ed9df0b77db5def4f55e91d4c0fcba3015e6b222883d47336beb1fb7dac44d5b56349e738abc7cf56f3abf794f023a0d5272d7be39f4f1719d7e3ffa7dc120f3b914481d88e80c020e6a5886cdd9cfe1431ee0bce44784b0c8ebeb034b5011d9e96a383922268210087293412398a3520d6be1ac8da110b867d08b3cb32a73f2247f126a3636f779e972cfd33b6db67b4e9b2fb55f0ed6edbed553ab31b94a84789e007131e8a6d21e72ab886d26445376bcaa1b8f2a43f0e7a04204a8947fee9d34994e08e6ee398a72b47739967e23666dc641122fac720c86bdc77d05e5fd62335346ad62b269727276c1a84412e7b6e2b78b77144104c29944c51ac29ff0257e279f3737d32f5c72ea1062f8a2813b501f9f4dc7c97b70351ec0e28a065ec3bfb3c63299c670b04b2f9ceb00b6f56866634384fe1539d565b0ea8464e914e4e28c187dc99e440872a8eb73a053b2755b48645aad07c7bb718c677b640a50da0d44ece4b335b921724add1d44a3cb4c5e5135f1f4789a7af91db7a354f9e5860ec7db17fc870c3372c3d54777ab52254c27e173833922fe44b2791c910b7bb65c13a60875cce9fd72cd016968d67005a248d50c5b8c8c62cc98f044f644f3454d6ff59d3cfbc3fa7276a504dac3392b875470f5e2acc5ad8f4e16896d89e998c8ef47883e7241ea56ae6ebe33f445487cb293e09e563fa1533a902f1d618cb121ee419df3e8694672dbede307d460d7abf16bc1fbc002dc27c17b322f71c3f793b29cbb717747186a083b80163eb129fbea6ca69dc280ecbfc53d72ed62101124f81af85207af6b727dc72d22a21cb55ee811822a3cf3a07decdc230a29a239a4c11872a5b8bab27215151cb5b8c050f0d282597f90169ffc50f9fc0f241f4e83adbed184122e0d72f07dbd61405d29704ba24e73e23705bc6169b68f3a1bfdadee080c87e822492effbf31fb5a24a5caa0c07ebfaaac1e4831d1d73694628f77ababbdc556aeff722aa337a7a5fbd1a56a25aed4229676b3d845829f17245f9c6095fa00609b78725ecb0d03785a02c065a338d85c6689f2e37562e93e3b10d3c518f0d437fb9456e44ad86268cbb6b5970d0b64e04a3ce3f42943fbe108e40db5ed36fb20c53a1fa0deca5eca548d92173a66a907890a5229ebec877593cb4489b47ce91a4f47723d663e6ba07a0229fb9d6fe24cfdd77b8f5072b78a25616d600904404d225772fe4ad6a40f2a523b4a934c12ce90ad4b7f1735b986fb08992027d6f6083a3035be50abc1f5aebbf306f3136f995ae24d2ae71869573ac646ecd1d17d04c06172fde6976dce0376999be33a20f686bea15943fae54c476a61a3a0d466ee20ba7278dcae3a213991dc2c09f78b1d06cf0e004800255b382a258d25ebcddc31fe7293c543916b3bb52b7dcc4911593322a6bc2cf8b6f1dffa25d78694e79b55c07234bdaa014836263c0c5ca370813e2c642f12bb7bbe6c10579eb3e3c623c29d5567be61e6c0691961bf411ecc64b9595da5b6610181622f7983dc98cd0aa48972569d3f704451f11c2d27b720b39881201774f61b40d42f80114189ffe5a960724543174cce48e3c92f78c1bae248833532ba16f05a6649655616de48b5b82672fdbb15b5ae06f8040b922cefc67176a0fffd8b7cf7680c895721e123cccaca2852fc1b89deb91bedc9f69735f290dcf7f4fede7c8ab5188521455f4d21f8892fee0fd3307168f4882f05166f72746b64fcc68c79aad7875b6f3c25aa35cb6d72bf36353d4088d5ade4566067a7898fff17a02f5d4b454df7829c38d03f09cc2aa84fa9bd284dd8c5bbd4c8df28866ad4d5a4109a144d080826f5e4011bdb9963cf378fbdcb18d9fc17301571371c8c36d2196a1f082fc27482d41099eea371728495c8d8a52902569e7a189932c4bcdd366a19f7afe0a76e6dd4e57aa0188d7263272c1ef2b0f9b7976be359ed2c8a56ec4876cdc9a534d75ae7a8f3c1759b2e807e2e47e3abf312f64241a653990900e15d815f55be42314a4deca08c22cd4ae0c1b04c17ef2d5a52fbfb5476001579f8214b3dff950c81c7a1bcab3dbe901b7d3d3d98b1cf7167294c3e3b6696f4a1cda1f4a880ef936329611a001cf7827201b0efddef5703739b2ca8f7258c689ae187e95ddc4df475db69f952e6b4807214400e0bfa973eb001d6faf0031e0afd28304d0c081ca74abe7cebc47ad9cf243dcc645401b10ce5ef209ae3b6fd5343d5b3122cb3ee60691a4c5004c368cb7206e3a55311994abacb71e6691b314b4c91bab179f87e10764200baaa461daa7228a30ab8a6fcbdb7ae6e0221f9555603976fe326fa4a3d38c07505d3fdcb2072c527c32f39bbec17076652f800c18a26b2c3fa76f4ffc7c0c21ed54802c6e8626189548193dc53f31a62d1c2d617f81680567cb149c5cbc7938e6870842d1c7253754ddd49961d22faf2233674f83b3e667fb8bd9fb96fd0f3b4e07320bd3b7253fd6260e18614c4f3e7780689cb2b97d30373319bead662d6b7becd6adb6525d727f32b4939f2fced801e5f34ecf216789bcbb9e00f6184db15b6e9226c8a720dca20cd3ee69b2078037f7c85228a5c7929af13ab9920c2015be9a9551e65053972837e2b6f9cb868937c41ad739adcc4566cd2edbbbd247c041a28d0e4515b66a15846d74ec4b957c3ea4bef0588b308bc9ebfc25626918cc668f2a7a6f54ef3fafee6c0a2d5d234dcd6db67d33ef3304db483cc31e14f080f47489af6163f88d4864428e73084e389d6b20f24b6ac248f85f486936ffbdbfefefbeda8c2391b23a5b66c71e23c4374089693f96991340e730434acb38c57f570e1f14e561ba7c857402de8a8985c0eb952a6d7f271d2ba5306cc97169b03e72776b572965f24ece9726b6a000742aec97a2aad56be2939d0dac1ea1ce5a476c1b50253cd723b32be5cd7cc46789d36c7a9b39810997acc69d3585e2c6a3d18d308073e8b72a330abd436bbbad83b52f06bc77fc649f8f62dc8330ff7307d98af32eaf282725522eb269b153c573ba9706b37ed67299b0ccad778543fd127ae0177d55fb970509a426b762447ed9731e704a1f51cfab6b2ffb3d45f96510d054a4544e1e772b0dbce6408c4e5fc46684b7f3c95e05f58c8a267d4638f684f1c067c8eef817207cc40d85c23611721e8db1e5fdf7e481cfe235a838aa4001354601a202e527298c296e4678cd56b376a786f356f277298878634e1b9be573a67ddf5427be71e81cf3d27208814b095e50e4cb39cb907ed1a2175cb6311191edd8781d291b872cea6cb422257c9a7917578b7d39facfc5dac0c5baca24363fcec7794d4b60f09f22a1b758b9f652196b9ed682203b6d3b7adf7f12cda665b0f62cc1200ef576bed69d83b82374a1eed2ffde10d62edf6e881a1f40512ee2637c97b7a1a951015b7296ca522ff8eb6ea626d1db495cbdc79d7f01e671dd7357facdd325ee18e3f490e8f76a0c63479ea991dfd2a82cb83c8cf2267f3bab885590a5ce3ef1e4b725d76eb77fa33afc69e984fd4a68f07aa0e57fc46bda0e33ac22fbc56220482722c543771c1d5519ee2f0354d07051bb9ce25738ceb62be337476d1b1cfaf9372fe7cb2d49a716980272d76789619c1b54f85dff0c7dfdb266e26e14d9ad9357276651d197aa47ee9c659c8ec437cf03452145af4a3eaefd7a3a679ccc732387258ed39eb77b09c9d9b846fe1b111a4cb6f2c2d957b7d03cf8d44b9fadba0977263b456d6c1ce800e3d1c8cc8d6af1562ddac4eca258856be5e78e5a9025b1c72739b6efdf866d55e69c7fb7e4d94b747e0ab2895c3061831858085399b6b9c72753c403f7919f9941cc98b55f12f22fc32220597ffa6f602b87cf0c6c0763e72e29c6c46cbe2143a37a885cff165929022b2973c7ee258ce8c3fe2983fe2b041b16303d455b5901beaf77dcf9df41ea0cdc609841cbabdee5f4dc1e2c1709f0ff02728ae15d39386aed14f998fe0e247713e2c57688d8346305e26b2e1d7ef3c77fd4d106f3ee3592f86988990c35e0c20214542a24449cb223616a1a9dac2720944ff768394234e7ac76f8f0db357fb1a3f74afc427daf10e415396a25f00729150744070ce11cdd89370d3a6b7be64b031f127c30ccf1bf01ce4a36fc8e8728c3a51568477248d918041a521eceb311518def97722a850cf3304936747375e28f236535cc92ada5d78255f7c53d5c29187aba58cbaeda3e5c56a09f8d5dc647dd59e71218e8ea923e167d0f4094bec261e56982288f903d2a66467d3cee7720352ad760beba342870e3dd33214ef3a73f24387c7a6dddffb16b4d86b7efb03fdb6f90fb7a63cf56871276d4da56403186f249e7afadee920cf8924e919470893ee08749349ef0c80c72368a3b1b4c24b43ce94ed10759550ea356c36cd3372c956f9a8d509c81536f8193849f822baf0526cfa7a6dc3570a48f7f0b48f3b05d6a3647fde5ad55649bb8d4814f2becf4ae562c36283a014fcf426d1f6315c3656c5eb11a0b03701355ca6d57b015faab9d51adc60b54778d6fd5851abf4ff723a0128fa1c7ba7c4dc23f08b6d1c3da04b116c9ab73f6ae06d0875efa431017213173f6fb993fdea93667218b06b8266767a80324ea43339a7796b3412178a7274197866a2894b22dc4ebc3dd2026877c7afa67b7babd77697613888d571c8723a910c3126d0842bcbc89fb48358fec02809d8ffbf4193998ac3f83b962b45729849904ce391cfffbedd4ebde7a2476a4c252659868a0dc157d22eb8f8d77c72a37a181dadaf89420d8ac54c2f05162b2b646384ae7b5e352b5c1d37035fa712707eda0ee0252bf9b753aadddfb98238eabaf77310749d737926a6b0e93c663571ed627a58198bf8f017b6ee394c139e85f437acf2ac82221afdbb65deedf272a532b55c169d586d1fe07fde245624c27bc90ada8c030b3c1203742f32e9ed21330b15537388fb50b4b22f6da492e460c62491c0b0a8623fe3b409e6ad401f72dfd62c692047b4d521faf6f80890343ccee5a138cc1a647356b74f3f78832e6ab083e2b427ec80c9d4a37a5abb8d50bdcd874097ccd94310bf5e41553b597a727b397c87c3addc301422ff689eaaf2956606f87ece5217b9875239503821394d64b3142dc89a8a89b950b3dac647f908b9464d175cf2e6bb6110b8d7a9322f722b79b8cd5f1e0315a28f2d660b3b3cf6bb90b4f51856c34c40a60e2ae17a9372db186501262f2cfa95a3c6714ec96bc459244c6be199f6d9898e4f202d76b80eef9a6302091ad5abd3e05e7b79ccfd3fe891e9f961428f8497c1d8191efb587296e349b316fae89fb27f8b5d1c41aebae097ffd57eabc8a1426c5e61837316726ab60bf644647defb264ff8820e2919e92b705f52f195cf3ca23c11c6e843872c80471bb9a508db803fd1b195020a2d8ae591a75e19c07ebe632e72070cbcb455a50c927e2b88ff8266e7ae291d13ab5e85890f74e597e63324a56e95d4b0e663ebf3d6faa38daaddb7b0cf7cda61e7bbab39077afaf860092d2b3ac12aa2a7296a31988783376a8d2b2dc609cd78c531aa2eb89f7ff5935ec90c3f70d361e7212c47e46056502d1496dc204b738c610e0be5d5332083cce05ea63529ebfea72a353c770228c245076d2b125a7a325b2f9d39b82d6fcd2cae14092a166b4a172b6b0786a4fab408d9f88a9cc3afbab7c921e2bd732e1c28394027bc531d6a572d3915bf3f058045e279172205127992a79dd25b4205bc7ff17a79819abd1c55c573a1620624d24aafd5bcbfab9636147c958af209a99e895ea66ee3990b26072cf00a16e628e635fb7e315bf97df56ad578cb1424bd74648174993727be48e1da6d162327edbe08efbeb5d8c25936feb987dcae727d4ae314f01059a059408529950d2aa18eb8d157ced70b53776f1dc59de640f028ee70491e8c76acfb2032bc5274a52ed1a780f95d35a1eb0cbb0806628e56819c9646e1170de45fbc8e425f88b8559283b0afe584f960dae4978cb2d706a7c533ac2a7293cc84b0daf3f3fae57c2fa26d99db92e4e43617f54625097886202375b1515415a549c3b57bf729d761266c29caa994bc8de45d1a4ad6ce18c6ceef9898c2dbabb3669ff9a8672c39df13b5ef78adfb3d6cac06249cb6b0bf82867e2755a24d1ff64f018549d72a054f07271cf5f8f1e6ba8f45fe92f8c2953ee2cfc306091ed0f0b454128b95b7fe5067c876a09bad3a53ccaf39b821f0e48c730d37498f4aa47bcff3524ea3491fe939a8abb169a8b7258dd7caf0274e3f29a5ab5edb5a5caeef6c224c8c61cd95fd96a216378e3e94e4b6fc3c8d735a8897cfba65416c35d782fd772bc3072b1009df213892c92491fc2c17a99476ee6ce8e351b234ebfa4cdb5b635483d72e5c7c0edf9e76c19fc434e06c3b85b37466985439af11eb8e46c8a8d094b4b72370bcb2156b698bd5cccd242c6896517652c4267f1413e5cef2c4d5f926ca8727118641deee533824d777b4dff987a27f682a9022a8d28143d08f16d2979645f2fceb9c7301d077034a70b5739100dd9564992307dc71c7a294d673ef8e0247265f2bd969d9024f69d80175d01e9232f3e141961acc0926690fd7488d8b94a728bb2ed91fb45755badff5a02fa14ac33d66b643d1684ea1abfb01750304525729cffaf3a2b11838b57c4ce4e64867fe95dd3f12b419c14f98f9aeb8793082a729e4371cf1f9ad8a6c5df189c1da8d0fdb4fce330425756a1427143c39bd7cf725343e7cc5639ea23e4d4341b890efde66b2294966a13047e8fdf49a462aecd5bc7813f5f1ebf8ac113636110480ed7af75425db0a3785b6377484a2fcec0f746a5856d59b0c7f8ccbf85a3a77dc2e2e65023670b9d7ec9db3e90e3531b9e2e32dd3d99937a4babc8279fbf76ed12a3e593af7b66173d7d24e84dfbf028399a07471e2fcbef9813b97583569b4aaee9635f20cb66f53444af3c9a83965b6bdf692bc0bc3980762bee08dd8810d7d4afc46788aa924268ccd9e69d997f2c527f1fabeea5ccf5faa7dfe7549b984abfa2746e68f53cdcc28dd5b194d110242900726eef8e2027b5fb0c4dabd907036624ae8e3c162b3b218de95c7f04523c01ad2617bbcad74b47f2bc175dfdc9b76e064e12b35e585bfd67fd7986e36e9cf56b5fee46eaa1e6c079b49e7c96b9c4016d7fea963ca66c805e5e26be00bf3691d9722b0f9bf9aa858f0b6a8efebbcd874149b9f3108f56b683b141633e5a3574fa72e3b91f4c667f6cd3e057ee921ddc4769ec1ae63ca17ba6739f89a91ca59ca75f09e3c31767d7c7591286c2241851ad509318249e4fa410fea3707b31e17b907294e608bebf6f27ffb67427ca28ee7bbaedde5ed6f4859883c95a28dc30b60172e0607ab6ac4dcb7cf810775ca0f1288f89c9b31b67f7632cd787b944914182721e5f167d05464afb5d22fed940128f21b29c7ba3a7392316e6623761fb59e623296a97b392a2fc3b4d8c02f328880ea95c12c43b88ace88c7621a6b1a04e3830cd8d2c3aff15c13551e814004aef94663bd3df61d0f4a4f753d17d44186cab07b6c2ea3fa24425fc1d13851f490c77f9980c48f0d3e4e69e63ca56675581a57296b1179759f698b8e28ad1666342c4c123a25b825bda0ca213161020f2ccea729743b9a7253a8d994b27d5452d80b951897879ee19ebdae892d06845cc501b29cb7e67132775400cc5ff4ab98ff9c040f8dbd4b75e0be14cdf9d4f41827c3372c4afa36e56dc761a607bec30d00f90fa0107b962c60a46af79b8c26261b440720ae13e1459439d09fd2970c7713c77d9361ac363cbbc2067b5d3f99718bb3c72cbc9b29c163b48dbb5e6674876043dbad5668fa2319ac74be1316fe126d7f07251f6e609d83ecc02492296e03a1ba87ed402cdb1f361bc8e0f42166ac0c624573a21d28e79b2633cbe3b6ab554fb7a3026e3f0ece9fc7c90d86ab851a3cf8b652f1a4b853e4147ed53a65693da5aba71a1734fade8ce9546abf2a3c3b757c606890312f38a597083c35edcea219984ca39f5cc4133fc5a1c229fe0921782497272d4f6d78c99ecab17b40bc5874a50c430aec2b32e0fb5946da794f480a1272892df40cebdc746c54f114639aeee1b6ae072b8321eb89998a7a5c299b4b6f872f7126a5e921a2bd6da5f71610771f2e641b5a9c224708e8768314cdfc430aa726c9688c15153fc4a9044c01b5c2c2c6b3c0e3c702406aa0a962c359a63f3101dae22b47f4c20ba9fddd79789fddfd8f096181e8864f54dafdb9841b013040372185426e722bb61fba7184f140434686a8cc0b4202cba1728ab551bc6eaf0fa722c303a839db0e358c2ccaa865c842081bebbfae3c7edeea90dee6b7dedaaa3728f4d4936095e5c0ff385d3d96b212d2014b0f08096d104725206307eeb0f7372d8b379de2edda08b05fcb3b9cf4acebb322845819addd603e0fdb22a56757a5c46ae56e412576b59724ad9a9d0078b7ae20873bb8d199eae0a9ff2161c397d7297ffa0744844a7f2a83953c7cfda1d2bffe60d225dc8db112a83ec89f83f6472a4d23d0b2e2e8f140712f5b83cb1873b6817ec3e04baceb05eb206752bee1a7253e169d004263975e0a68bf8cefaaf06377f6fec2f0d5823cbb960704e65c04723154ef97c2151cfda06b23a7bf07c5f3425ddea9f4322241a4821790763fb725c029ce82ca2c4ca43b9fbcf72766296372102d7f387693e4a20ea966f498a72c6834c4a21a1d83dbf11b8c1ec03d10250a44d59641b8eb9090121cba74d2d725da4f3e8bf92b688902eb2e81f30f05f5b8a0ef04b7cf9f56ab9181ccd3c1b62dc020dc7ff1710b4895c8027499e76c3c4ca993bc7a66c17216c7cfa71782b478b2dadc2e012cfd2e751cb7fc3d635ae3c7171e81226f9f578d2a5e237c99e72021ad9606835d9e22ef0572099de0978a11b0c572f8346748b6f30ef70152a72155a487105cb441b77d3ad9c9892b2f30b3eb5a0db54326c90449af94cf30c3ad0858dcc01f9940c64cc1f55cccd24bb8da95f1f1e5db0b381fea6687f9d042fa85e626940f90a9af0995d596a86e039620feb0743a6a442b636ea08c29e9f72215aaa72f200e7c4d31ff89281b4d693f8243a7c7dec98887cf9b4957070b572a129d36db9ed10d2071bf5f3d20638db732a665de6b6d23aa12923d024d2cd72b4b0601189900f6ec01bcede9d6bda9a2b3c75be5296d7085e0004f999b507723aa8880699c0a9772e96042ebde096a3647706c7ebc74be71996c988a799b43e7efda21f3d86bc66860c76a9712cb1901852cf23772a277188d479f018652472cd331242f93dfec19829f3fc0fac19092fccee3b993b9c53cbdf593b32ee1b72a54165584d9dcf3877ff64243c22d2ca54e4ddef6d9f78fe11bbd77cfa7e966d08fd729a0fe8fe32174aaa7af5370398e64c6c1d9b459bc324a4c74c771f957235f5d84da78b4c05c13ae4b5243d650576cfb60a25baea7e09a3a49bce705808ef0a79ae65a65acef89b99c450f13cbfa173c67190ef3238d9d692e057a05472774f45567cee08c45591b15d81bf7079906aa32141b35bca6d29d6cc77b03772d80d10102b97cbdb885c83bb82d67228a9ff2b1830bffc75e0981b953803b14da08d560d976bed9c94420663463dd2a9e40f4f26e830060f963f9d0ad762da65e3da7562e2ca77777458fc436111136dc437ed5ff82cebb71bd051adff02b0729dd1022dc3bf55bdf92322a255f30bc09620403c57bbbb4a9a832c810fb74d72452963d4df5eccebb1f699072866125fbe9cc216a0f5774cf873a80194e1d572921097e5ee5dfedf4513280f8e9e6b96b8079247f46912e396d3cf8bbfcf026e0bff8bb9de6ab99739198aa9e8f9f83c31e72883eb0bee2fd594e5ddc18fed2d25d242e56387b04f7a893a3ed57d46f379c42c0c2dfd375714ffec7c3d55d4462a62d5f8c242b5867923cc9b583a7eb1c2da96d602de2b2e3a249c740bd7c06de108260cabf27ba8cb08a99f1e49984ba81e2b72902a91a69323eeace506d372f4893ccdab6bacba60e2ca9a2dbd717d123db031bd2b4c791fbd8f4e8fbc0147bdf5ecaa97d8807c8f91c6c1de4100516e4dba28f88eb5f2b196cc53e43b1672c7efd19dd702c9dc70a4eef264e125b8bd465150c2afb8f110c3759f4968051bc8fd5270a203c861b9b51d6545960f761e96a3bd460bcdc88ef612cd6a99137212c29b9e92d3ead1ad0847ba8f96bc1103becd16a374299f9f4b03b4c095e45b9e5df3b1eb9c159b3dad5f33f8cad732e28f0b40d22de58303535440938560210a44893695291e1e815d087c8b56a31c1de2a806498ddb12f9b6ac6469c822260e0cae2343eb3912590c8cc1020ea5066147a7011a662ff890040c0442873d3b6ce49a7a7bb9fb960ed90eb96dd9e7bb1bc5028334818c5f77e63e1e520a1043724035a64adeef109c4f09bfe14f21890ff5247f01bf9902d126f03619650572b7315a773b278b790623e7ce3bf9d5afa1741918b9410efbf583c149e0530272e3771a48f86dadf90716d83b03a3f9b5a10e3ec8e44b99c80b9345bb4d3fa2727b3b7b1561e7ecbfcc2969c35e2b2b978b70c016c2c0d9749e9f0862cfa62a6500a244e1741d74fd7cafcee966d8aef2da8772d3ad0b430909868d3bf5a69f082ed50c7408a5d1e9e51fafc7dd5b84ddaa3fcaec17cb3fb718ced65c44028c30ae26986a44694e1f4630e32141ed1940e014daecb60bb93381d578c25c82f972b978633589a45babc0547b2bdd423ad40c31d27cdcd577631b85c4b9b996c672a22d9a77dc6d4aeb65bb41d9ad56814e06729b0e7fa4feab772ed23b37029872101b300ef4251c94d178f5ef9bcdea944b89dc87eb47b7c2e0c4a79cabcb56722943ac2c5b4fb713902b0d4dcca57190b911e2291775477c80e73d5ee5409d72c2577a2f2e82b73e3d5947f5c57635dd9914c15248e40d875a868cfa37a312724db44b3e4bd424d0e2efcecb0239816a85e617a54a2358a41aa884412b018351cd12367c1453c38e9afab63d3b2f6eadcff66ad4c59291c0973ffdf72390b572b83e509fab9fce0e32d211a21a4ec710d1206b6c6dd637ae26715b5adbce8d6b0dd614cab7ebcd13f76d1e4aff58a39c8e458f0821a7d3c3a83020c86192e02ac3df974c2ec07e155ed7f43f90433d7e767c10383c76896dfe1cd0926a280c51838cfa4aed83e514e75eb29e4b35266674b5962be16e02e439c9bdfb1443cc2a24135ec0dfa96b02ec8617462c066e565e33288cf7b94abdf0d24b53a7b04e72c3c0c4a02138e54c40560515fbb28ab30fcd9172d7f858fdf4468d572c3202722eb2e71e8a02e2caad2fc958deb8bce1be29df669505aade0c45891a81f27166c0704ab35713f8bf466daba76729bcd8942f9bb46291c200e0854fd6e4ea9072ffe3155b77746dda391dac61264d6394090863be348d79e339824a482b4a044c3d598ada8d4a32efe55bfd0e2a643b993b41826317b9c2fbb8c3aa7e0663b1409024a1484b538b906dd23e66c4269634676f18579f9ee08fba7b1adafab02c4b505bfe3b47f299ce3e5c6ea5881b1869990dd9a5779e530be4e4e2b5d5e11a720875c8de2c6a7140e9f1fb1bda32677ec8df87b37c4b88eba2534578e541ed075850c30c5dc5fbc7179938ada7b6ab15f271c99cc6697c1b2396a155ed4611729db00df9d0533440197cf79e7432d803a8550f428940ac9e6af314342e4e112679ef2343b481cd773c67d2c04477683b76dd1f48c3b6a43d42437dffa25dda163ecb05dda6af64d8cb086d965ac35ff9b2df557cc1a0fb28da6e303527bf98565f47a9c70dc753106563b79ab8344d623b6c1690fb6dc6459448e4d3c07e1472d23b15476c4eb6433fa9c40fdff2e59b785cbb12f91972f5c6ef04498e78205a5ba6e7e1d9f2b1da5dda0d610e42660e5bc09f44d0c9667e34095893121d052ae42d9682b808cbe6c9312fa439a3f012328dcc74c5ee9fd3a577fb3ecd43571e3af688b27d504959946c36c69491b2bd128af7445c89c6089e3147fec7a8e05c5dfee2912fd3d943eb0de659a7299d73329163eabafecbe18e29afc1e12a147213d756450a92a53ca671700acdbe0bff87ee6f200b2d9f82824394011eeb9272258c0e4e102fe3f27f36c6ddb67700ddcfb9e400ee8326abab234e713b9c0a1b2b43a578460ed5d9a444dbd055e36a6968e1fe8be333b824516b5e9c03f583721faf32b50aaeb8aa3626770f77ca0286a5eedd7d9529475abf6a6808a272f67247e8ef3781aaeed78b465c25449c9af76006e899850de247a36dc9a402818a62de6952dd6b966099242862ad5907bcdc5eac3d96ba59fa451961b15a7f602e34b9ddeab00c2d696575d0d33fc30ef21ca04efffb9e4d34f8c604217410c631055502c0cb27f8d90c94a782d87280fd361ecd8e1f2863f43c1b0a560d78046872e7d708b82e41d1e519f4ecbec657e3ac3d591e1182119a0b1309e5b157df2d582f5a13d25c4840278979872fd8e963973e11d9a73fdaadb5d50845567d74ac4d45437aa935ee305fa0e3957146c1f1b57b7022554dbdecbc6b3aec710faa4f72871d7cc65e80561fd97b96e6def07b0157c290feaa52066c571f4265f5a3c87207c8ec26284475efe017445ebc06d073a5e01f133a9637b92f0dd6c594bda841e40660ff104831939f1c5ea70a14b540bf33b130f41a9a9c08c25595d374c2722d634020bef6d4fe5d3c9361ac8ae4575ebc2c37b4ae7567749be4e818eee5685bfdc9f5845e9952542cf66f88d9b88edee147715af47cb6dc9df0f8279d906b70c30d1cbeaaec0dd4b0c13a13562cd9bf6526faa545aba9864518a90f1312728151d9efed35576e0b826d08568cf31bb7bbd6f8798e8e4ae5440cb7a30614726d8a77fb3c82ee57afb9d041564fb487bbebb6dab699a252d3f289f0e2b90e186526437d64dfe551d649c78075bf5b38a541f8d241eff90f83bebcd4f1fa0a72e2c67779f86549099ecdb4547664d4ba6dcf8a3c30ba2116070169dfa6ea212f236215e716f6ba0713b6a28bdd2d164ab5d7e8dd1042c4d5190f617e1ab48872f5fab1f18f75cffec8ea951944c93b5196bac47a5e10a9c1ebb2c8459bdcce70d5ae49a8a934ab7cdde7c9e0a593ba5a7fe48ba4a1a00dd601ec16446fbd5a7271cb553d2d9cb6d07d794206a595f80caeb474d59752a2eacf37786f5dc1831cb5bf6f2661a11aa5c40d148bd30f2400f2307a7442758b8aa193974bdf18f972525c86d502cee071d04c1d05853a464e325708eca8200a74dbaba2345fe80372ee1fd3bc1a8456659fd45c737acdc70e4d72887ecf93fe848ed29bab423e5572a26094fbc0be44e8e9570fbdb796498b81e01b2c0cf3264dcd7341c4e25785725bc5f4b6268e846650dce28291cd47bd92fdbc85528f89ae22789a81d954441de25ebd1d7eda03f4a02f8be4c7530922839fbd668ad696bf2b30297ca2269872f392f752daa221d4b192ca44ca1f5b71cc7d46890fe4ac31673abeb922610172f2186ac1a8a1c6695b1f2a127dd951983656bf52d99307e020e2d59fc3e7437278481084337944c3931028c859f1f4ce9429219da3a0d562d8d6fa25ac731972bbcb14bee7c186eb2bd753ba3553aa22c8171dda1cfe38f0837879277e88373aebb93d3e5e95ce2296cbffdf8ac0bec0ff088909811fb4c65d2f172bcc6d784f37e6f46cb5e8f03a15e6643a6ed3e46fe8b488bfc31ea0a163bdce081b482543fdd0d1615e8f6ff4d0e25da55bf55a51b1819885227189136e3c61383aa9107219671181190560af106b0a76047f50f18c1aa357222b0a81f523d38bb73b0a72b8ceb536524faebb48798e41cdffa7edb160200dae65e969029d8a644ed86b079e83d19f96b509921d2dd0d66c63f8d7f160e1f16d3605821b9ad8873d0f0b27a0e2624c012a21a83adfe25fcb8e212f1ca0424ee9990d9a269225c8bba21b2418524079c270917ceefc9028e1ab3ad87e9bb116d2ac6aa75bb6182fd362e2720494530e3421074e4dbc7e95b55e66ed9c5b93f3277e95812ba800b8d724d67248ca8730f011ec9a6d824f09c6593fadb9f33f99810a685fdb68355edd1928723ca3b9304357d78bd0c575b1b46dc3e63c47922224c985b8f68cd3f16d53fe72bbe0e1b835190ede3f3e0b64d47c975f8985e4bd93a60897df7b33758ae59b1c7449b642afffdec63b2f1c441c8487877ca68fbe9102c58844b65af889abaf7209b2ade2b49a40b6d35e58a737a790427bc60caba0fb3fb0ce334971b13741720fb9e956f4f7263ca8af116d67f875f3dfd3b375c9ff4b3f6f8a8d9e22003007d4efa753014865515d33fd1a083b537f13d28c6bf5dcf9051c16dcc4764314430a19cc795f7932f24b209c57e6548b28efb034d1187715e8877cb10485e2f372148950cd5207124c237a54fc64ac16adc237ba561f8849f3ccec8f98ceeada720c226b842b71df5304fa5e42c4f4f6bb4c6ef1cf6333ecc4b484a3f8624d6e7222704f298521d573284485924e525238eed7b0dbab23510d0729b197bbf89c728b0289f8ed3c6de31d01ef90c8b93e44efbb583daedcfbbe8f80479775728d24c5b7121d164135ce8ffb714e449b1f90252d7e24409627b2403bef166ae17f7212752669f294cb99a2b4a5d94e9a16b15b91c50b1ec43b5814e064a28d4187039883dd6920f597fac6e6b451aad5348bf0d130aef4affb8685a8d0d71a515f72101246d43a3121448daadcbc121bf89293ec7a1457760f1b137c00c77e4b25720201dfb070df214967b698566f8319b38cc44537e561eb8db291577290ddd05d231fce6da92721575592e40e83cfaaeae511f2c941eb859b9148354e4441142790316f0719fa392231dd865ab1e624d6b9c7bd699a399dd6bd131df2a004b65954f0feacd7799adbb26f7618a30c90721482022c7f1689c4a132a01b9b434f437cca7a3d6cc9c77b8a92841b6d0b69c7bd64525fc48409acf755f73cb272717293bcb5eacfd149c10ac06904f17950d00f9a4e0eab82818dd8309d4d17bf1d58fcdd6e08bb2d32e9be794fb7eb11a991ab821f1caab388afda5f110c7520e7727ad765401e0c35135f9554bcdced535f1abb0a02a00dbc62a7d39b117540cd72b2ad700e36ad88f85a6feb6ab550fe4b238f13e8e21bf4ae73f42af720ce661c2bb0faea10025f89f2418ea6296d8d23dca1e80b869beab91e1906e0204f5072f9d482a52b886b300e9749e4bf91d5238460cc9ba3ec50ee11f2b043c54ddd728619bce1e40237a26499ffc9de690362c37d29739335a71cc83c06a31ada2572b863552077d5638761d9dbfcddc7adddb8491644878560efe02936381f25a67226d208c72483ba4064b37b420dcadb49a44f3f1b0f8691e841d3ccd66e409b722422718c01da45ae07ab1775df06e38a590be79fb500dfe4604bd776a263ec66c7ea9b555707b16091dc3eb0267c08b840eb52d9c11cb47b4adeca4888b98772f38f68f013a6c5c9dd4c65f9e8cd21dca2eed89bf168f88313dfbe85eb0a177231f1e3525e6ece815f0f2439960eaa6dde4070dc777a39a2d1325429c6a67a52440ab2f7d35704b1f0bd4e48da79263117a6e22b2a19a75db977fa95faadc27267da9b576bb5a3bfb98d1dff93569416680d0696361e5002128f9f4f8fa4d472058df9f1ff3077eab68fe3fa48c4a69e2c2b5b7dc31319ee2843cc2ab425974e0dc765854b3731f680e5f398acd0abab3257da810d26640cefb7b38dc308ab2164e1d295dc6508ac0675dfe7b51a85434e4ef75bdf5a84bac243c86a3e3ed272ca9014123d4274a5b2d1d78badd2abe139a08ab757462e634df09fb190ae991d783aa87ed8a84eb8b0c43723569952d218ba187aa8b18fce57660d95f487073ba5b03c4156df3142514d67ee2279d500424a679c1a3995fff4324af239f4d361cab6e8e266374ed05caee4dc6e6e4a2f367e369525c94873e36e6c625c8478721721fbde33b667019a4fb9e1086525151b5ede0d8faa570b1a2634210921fd720d5e2799d5e6f9b6000bcbdce56cb1b57d1efa3a5c0009590a863d7f5fe06d72cc308efc5b1a8b0890f1065fd7cb8b9777a61790263295f2ebd072f29228937281a438a4058b1300b3d4678d06f83da3c537dad10554d050ea2a1b441dfe5f725088731b6f76b128e63df33fdb3944efd1a855d69e50c26aaaeb2710a363597229f9e5d22677a6e383fd8abebcd3575e698181f17e54d726cde8d0f3ee5920726d3452160e8c43dab971eb31fa411f760f989894d7eaaace600c937b2a0a1e5d2da33fa15df0f93343579fc9a9a9634fecc1b99b9dcf4b4aaa97a4b6b79f627223742308516e1afbef7be28890945689209356c056776c2abb493a5c03117672f06d79537fe3b1071620e65b8416bdba4d08636996c10fc79e8d5be751be587261af1995c9d58167b2c7f67e01643b68e4fd45553b4bb3f3fd6633a45ef83972c4640433992ae9930a0965a578f9c83e31310cbab478d294312f0dd3f7e6de726620d8cb2818f6f5efe0a399a2bec0f8dd83c13ba754f8f6fe3ded807086a927d43f072c12f51e3da36ccd708eabc1a6d0671571fc63d90d1102a7f55e759672634143b0abb12f3f78a91f9e56a57648fa331730cfc64e999bc6453cd26156096780b1ac9d947dcb1d79aef436530b54db52fc83248bf56367c423d81be863658b6bb7fa45ebe0b2732f93096aa55017234cf3cba329804a6f476859ac1b93570e27cbf68fa84e9c12b5b585b4951c2f93eb2a0423dd4f66e255b8b40ed8d4723a5ed6553f2dfca9b11cacf08167ac91c7495f5b80c3d84fc7fd0815d85e7765bc84c1f2032a7fdfdd2960c9dd1e0a65b31c423c752e5723ed5ff1e5613a3072c07547d9fef76e4c6a7efef70cb437ba9c9177d964c6c567af829313f4fef07234756d623504745a40072b51b8da43ba381cafedac41cd63fdbea0c70c0887720e43e14cb4d6765f9a6c25f882117329cb45cc1960b86bee8ff9d6dc6ce2b27269ea0adf7ee58379bc26ae9f9180ba8333333c3834c7d8d3b09c777c81edd23424481888f0ee2a69e21257cfcae1a17f6e5c167c6cdfd4a5f7fda69b6869e572a0c3525df5f8ad21a4e119df6b1eaadff63a7e1a7f13caea87fd6b4d361dae63b03a67c388f8098e3381ea20113812cd06b971ed492ed8ed9fd846efd4050372c2bf3b5a8fd51dcf97a36596d01e337b5a0895795291c761e2c2a2fda000977208251ec16aae91ecc59b3128c401286bee7f185514dccc61169fccb082e97b2fe61a37a176cb04f423427129b3c367fad58de50999adc1a5af446091647c247297a27b7ee4619718adb9fa6ed6112cf4b4622765ac9a38eda5878b85fefbcf72b78da7421f5dcefd2c83d1834188ddae85ca3ec8a021548eef6411973555a372794d6ccce61410ad206d6a166a5dfe29821f65e01867c31113503c39f911d472dcfa361fd3f46c4f22c7193f53cfef71676ba887bf14959e78c96e9a036e787223dc6228ba95e16f749b9ee3036c4b90e7c42c0b5bc01e734e2a8d595c2c182fab60b45cdbe0c2fe202bed5264ebfe57644f846e8d8f095bdf083c22198b0f29b8699e7715cb363b17e1b99f8792faef89b81555371564553f1bd5924184974ed5b718e1bed4033a8875dc0c03229717b520cf4581b4ac7e951978d2e4ad2b5e13340ff5980336b9d6ba7e2bfd284ee60e12a42983899b0016c466787e62c372269ef0755afee8fcda6efc4f07bb606b944ef545e53cd05bc2da7313c8a23b72759a60fc45c466ca8565f8bdd8f0e88540faa754c7d7f3cf7518a469624bd9399e996eebea54d8676f9eb8a3999da06b33308ead7ff1010902c71af6ef6d8f369973d84e9dc2503febdfdd6247400001b8725af5077a526dbb79178cb4fbbc0721aebaa3021961f47eb7fe7508ac4985ddce8445f159919ed903c73cf9948b72849d4cbf8f50fcf7d9f0f9ba5f2bce89493e7b78595f4d6cffc1745afb4918175af5d4a2fbeb458705c2f3a702b5b08bdae2418f9a2dc9195da3d83c0e8e856a6cbbe91ce709fdfd03fb8f4f0af327a03e18fb2c0509f806f316f9bb693b1472848ec730d2480b4f518a9b7c912e75b0e61e0608428cc95dc128c1c899d1c030c5b3411086e67500616a09a1ffd7da19c6ae2803c0f95d6d688094227501690f209ce4a5fc8fcfa918ff5f198bbdb3c6999d279f5f0a640545c40aaab830af729fedf26f45fd1deec6d96bdca8bbee6311bf9af6e29e0b5fc86ff58e4a9bc172f74042af472af39378e94b925bca20cddf5a24b3f73921628e1af3bf1b956f727f293721d94a9e15569756c53a08bcfb82e320f9c4d8baca4cc7610e10cc8a72183f1479bbacb84cd10aa8f442e3cd1d3586128e34e4afef9a522d6a21933372e4b86d8701673a20ffec6f7fd84c3669397b5b6b79a383d5ec16f18eb4bc500ef8fbfa9884d9d94d8ad4e2f124b56a0517326791244d4ce8dd624c9f80290f7271f0b203f0860c3e6bce279bda72ee9a8a2eb13bb136f52c95010d7d4be52b7284551b1d21eb7ebc25c21003a6dd8fe77c063b999507a1ca542f99d5b63fd87291423ef31e87746e32bdf30c5cfd8caf19e269f23ada0f90cfba91fa3f1252721f97a1465caf2f941adcb9b53ecc7e776d740ca14fba5226af7738ee49452b33433d1bd9f7599456900554c020693c60939cc909ff28a3993aa27516447f12723fdc17eb120d84377a155fa93c33081c4a356458561bd42c438667f0479c0d5231078e21c2f54f9a367eb6f9ccd1a5f277bc62b29185da863bdded5dae53d372acc68f24e0f1062800f1c2c5bab0520b2ee2e709c5495eb4d2fb47250ff6c972b4485147094e1de99fc7643a2642ffcf9946a03ff2ddf3bfa4052b9d802baa24b7920187278e7d0e190a97456b1bfc2bfeb7639419f4e5fddb6865d53218c772b26aa4b093afda5c7be12ef103782a26d25db77dcd5e15ed62b8f3b6c02f6c72151027a47f3dc8c27b4361d113d52878110fe54b12e3dd9a920a19aa5ca8d24212e063f94545569e2cce8d2674c10d120ee62ca17a8e2c5d76c8c27c03e6857299c2f95a9121b0e346aca19860ec76ade9262109b4f408aa189abb03d0e154728a11bf3c400e0a28158ebad65ad789629b26eccbee7348695b4bbc5b16f7ca72fce91aacd0b5f6a2a2a76bd764b93dc3185dbea1bdf7d366ded74a357da8b341858d1956f4cd2528083760b5ab80d64dae5d9523853861a1e46710f2b1cfcd72c7feb9ebee30542802277148f4b6b289a4a1a6cb738b8d019f2d477d643376726b402d28826b5aa9e18ff8d01fc1ce4902418796ee2d0f71bf2c3b75b2e04b4a186baee5b53d031e88cde1f80cdc0b7473a113caaa1a4e3590b097a4e5a32572f6f54088f067a71948cc221feb1cd750d6f7f90cf795a558d9a55f88f616905aa38d410366bfceabd85acfd04ec143516191df35265a5d2fdbab0a60c7403572d7656d3d7deee9a1e1514adcde49558c0b62cbf9b4887b90284316ca3d9a1d72d227c23b72e28e8c8a6b888f02f0a1133c3c78d95a95384aa9ff90283ae0ec36bd91b060a243eb7b4fa9dfb6662c46850e80047ef67aedb5fe5b639910f1660a014885c1cfe922673bc6097e8d5b04131a711195116b9d37ecaa84a5b2a8ce6e0c3a91b6ac96ddc81d863911f8738281ea94af5d020d467579b92e8bf7497072f9d320578faf72cad7ed10713f193fe2f2159befeeafa680e4511f8e5e3c2a59b62677c28bd7f2640324ea5d0f68775281e5ec3207ce2e626ce5be687d4c2a34ec7a77de1d914308f1074028a633e69c43c69d928afb94a5cab32b0cd4eaa5729def4d386d9804da26d52a939bac8450db3be6a68a139084236b24f21987844f4f278231cd37814c66864c2153d33d397643f9da2c9a8993844f4d59f17c42380a736029acb3f3a371cac2ca183113ef19a56cf549adb6a96514b8253346be72ef7a2eb0edcfbced71349db83227ab1888adf37cc9ee8031aa4f50dda147e9375636b12035de8d3abdb742d5a6adcf31da30394ca9e56759f379ba88e4a53e72260148f4df2221b8f406b2ba8e8e8282a0b8174bbf93c416a3d717467d6e2472dd52a664e1b600d6385ab616d1fb598cff449c8b0e19b85917eb41d052692738810b3b75451d6124160722cd4eafac388e8a7a11a47163a4f777531197c1e972f31e14a45c5a9fbebf61b3796a4567ad44d1d9a9ddfcbef5d9968f21b017dc66e5e0434ee2ac7bcc057063d0ede5898b2b8cf545843adfd6ad1080b3762a2105fa68649590b95e308790a2ff60f43eb26b8b2fd7261630f3d298564f6361ed3c8c8d7a14e19ce601f36d3f9e753e82b755da5b91aae6fb9c2d5081f8996e5072f312aa3bee8e3cd2659d5b6a7ae7f0827fa2235f9a4e98ced368a678519e177226c15af00e6944c5243bd7fd679dec9a03891abb253d9ed8e544fcebc421ca170eacb023dc260c15efa26d3434fa1c45af6880162abec5438a06ff3d4d700772b425a2bc1781345b708073f42580d87a0902e703a71f7264a1ee96911098c672e1111188664be717fb3c79cdaa022569859523615d3d317ddce07347b8472a721a2201535a85d467365cc6f465f2e494a86572a1975ca0a7bccc6ba09ec0af729b7fe2d9d6a55d8f3a353f44063586d33fd9db5196a44509c778b3537a439372d678e17c85aace6ea7acb2b6e3174a3231798735f1097be0a55fd18b7d309729227b9846830ee149cee9d91bc465fc26b50c69075f3e3303ba3939df1d8ebe72dc5f2023e70a621fd5e5738370c75b7886ebac6f21788d3a8d8293063a0da47256641f8013582da58abb53057f31fcc3c35b8340f74610c855af554448aaa236b341b6c2143f986803bdb2cd0c960e25d598980aeb59296877f287063fd66f72dd52da357a4a6564b7f6c26baafb5f4746173d5f94b9b4028174eae78460831d303da70e2a96c7978460da1f1b270ce673cd8e61718725008e032a28d59a90728ae5084633be4c7c85e52aeccd42a494f340e3501e69b07c12dc3f30bf1cdd7249c429cc50f792fd79c33570f62714b80a6474a9b2db2fae752f97b36c1a9d728ed452c6d66fd322624162acfdb04e669d10ef01a51b44443eccee4f6742e557a6f2632569017026b723a0df658db39c63a91763ba55bc72861f2c4464f06472d22ce7ba67f9a2e9b6872813cdf138a20853a38bd51bc2a2b8f2894cdb704b61b41d4829dd33c9a73c3ccddae71ee79c14df3b3bf7224a22cf0806c0c4474072a34d561e831df8406ac4d6d3f294d0cea51826048fdf7c04f2b6d2a441607c729820d17b5bf60e5279885e0930590794ada78179289d288cca513d8e5a712972bf487f0b24bc96f513e7ea26752827d59e8939612df852643c3ecb3be41e9002dca072a31ee5887533569dd9d8a8d0b019d5d9c022af3b3cdb31bed179e3eb0691a8fb62002398ec944c0917defebf55b7d5d1e8a7d796e374c53b0d2cf87f491cb2b87a4c3d94fc4e622757fd4ebd4a5200679a24042452afb8594599720672263f040ec245ce66fcde2468070c0e351e2d6d8a5f8e38bb2f6d106fa1629a6a14d1312e727cad195f061eb5ddaec3f2915cb2d78c0e619b65d7dfc70fa73e721723bf6747e89d45f281ddfa90f0dad05bb34534ddf48cdb56631cacbd53942df91739fc20e4c9cc58493074bbd81366fd2c64c1745839d606a4c2dd102fc008a79f5e80ce6b2e3d2b2cc4e38a0b5ca75302f8dd65582ebebc7e418cf874a112e2bad6b601038dd72225f55eba35d41804ad360da87188a0a353c5c830604672ee4556e54c1e68c575cc063e8de5780b9c0c3cd279374689f56f00230421a66088e95f3be74c0eebe44db850e637de0023063a90f33148b75aeb6024710c0972c8e80034de669fff5de3336dae9b00e90de948cec1910d1a6753c6c6e5a0787262d14cadf2ba930b90fef92ba15665672580565c768cb983a7ad86db4d4227584bbe134995a6a003205fc8a98f48133de1efdd1e1905b76bb5726ccbc6358a7204874c3f5634544d6078a0289015f3a5de432ed12990daea9e7426c2e20d5472389408c5ac17710b8aa02d74f2c256fce97eb74bd2024dad752c3a78192bcb3f608760b608abbea3fa2cf670f285e470aa3286edcca2a7e895f864fb06de3972ab6a08af625b084126d18e4f108411cedaa757bc57647d0eefb7920c36fedc72ec50be6db5340274fe61b6114d6aadfb10081c28da9c06780db4479f46b4857249d720da634160119473449a0dfacf60358470a563300680ca0081d578cc23727b868bd3406c122fb9fe0fcad48e742838a61aae4949f150d232b10e4b346d729a4adc39f7b93f53be5532fcf91e250b86e149885681757f09c08a2a4d76220cd0a2aa5566771a33297e47c5ea059c8a7c57a95ce873e39d0cca20ea7965d672b5fe513cbfd231676de8b62aef40fa0e83163d38385f357e7d24db7c92a2f072a4a533a86c1220e0c716779a3534531772e9b1d870f54987c1073e97607ba30d1a12245528a9c023c4078952cc847de18895bbea94c460c27b4814f8b21400190f8432db5b82dc2c4bc249df105e533b31ee5ce2e39d5632e4d7d8a60ed618721f974e584657dbf0ee63b0d3839f6afea3d728bb76f16ef931e424be0766506886e6e473eacd015afa51e099592b44c80c081e1af7a7f6930d1701b558cf4072302814447589ea766e973e090340bcaf9b22a0088aef192d630069a8cc22f855f8b2930377946732e863881301fe4d4c162b7ee587edcc8edff6d8fa9dd94a72a5b514239ae4cda3d76ff90948f20b9b6e7d89640bd482d622c6329d4b8f7b3185c47789c72c483fa02fd775aa43dd0e940bf98ffebef9ec6f2027a6b842c8298c1f040d8cb8e60d60fbcfb4d7a2542e44c6a0e75861a8594b962ac611ef777234338d9592dbef809504caff40f937c09f1b74204866a0db199ea2b32221d6721a4141f664dc1421665b0d351f9795babf01e7fd5ac62ebfde14c95e1e6749103fded419cfdfc5f58e7bce6fc688d7a4120d10008d556cbcc189c8ce1b90e472907776d2b1db0e2e9cd9b0b39f83297de8fe3305209a5c64f3b559399e29c472ed3459f2e67d0d2ad6c9f15c501aa795e4868fd29ccd68ac7bad274f6e24fa72ced22519c1672a7dbb89ec6cb45ab39daed4aa5b534a26c6fb8a275cbe0feb7254c084cefa9371ca7ef7ed364f774150b9b7844bf691de0291a28da75d371672dda23a27fbd15567d71318a6bbbbb11e9ce6c0be1a1faea5aa9b0c3e93a9960b1622ff5c26855703dc1f1bf7b2cf185ee5cc54b79653b1f8bda40ae34a90e93074f1fd0ed673edc4aa3b772c7e938b6d8a0105969de267081504fd41a2a6927217e4d1f8cd4618601745bfb8b96d660b91c8df43af618862fa914871e589f372e0011de4e51889bd1e7d8d7e8bf6c0e3edaa0f7a15c1cd6b7adfd0c9c3c33827e889cac7ee8185976143b4680dfff8da1e75508983f51022ae200ed9adaa0672b15bada5380346218df1ec2435ffd7bd7f73f36cefd2ccb6eeef41f1644bd072c503fadf6c72927b1e513ea324e8604213a0885b5d57dc20cb63badd3e576072416e231c86253738e82fd23ecb2379a13c9955e1d009e6191d0cac057030e5405387ccbf9847d6d5fb6029d89b05220954f3bd9a7474b44346da4385cb989a0f0bf03bd3336e90e80f8463fde777b013286ff722685002b1efe1636981965772cf8f24aceaf7571c16c3520405b2c9d4e80ce2ffa3e02b740e6c195bf97b690846f211924675168ae733abc90a98395331f766137475937bb19de5d6fefed172c95cedad3d156eb1466bd5ecf8f89c12401fcf1f37fb334a255337fa0c366f1ba578d9db0d05a4c8675eb91c816e234cf113f982191919b4723b836fb37cbd5726845308d1d7a314d465db9114487dfda218658b66261fe4dcf4b6df1b4b946101ebe6c1e3d0e34ff4bfb572baa68db958764238617ac29545249d37222ca472de2ea3317fe23069d436c010ab1775799988d913a4e68a3767bce2a5922318721822059655710f2635486df9c3ce5c1509320079af9cb054f1170596dccce572c5b8ed3449b1d599f4274b8a9dcea31d7de61c34e1ba9ffc82ac47480583007289c8c62f4bf015ebbd760676dc17de5008d8f7892caa5a70721f182a6e64a64554b485ade8050258ba7a96b96273eaa3ab9376cc622e058ec95a23df295a686a4649098fba0bd76ef18c3de6695cde4c9886aa34a9e261d88867403f8e3c837288395bc6837c1acfe59eba1d72d9842a5ff9bd03deac5d5e46e39ed8d7194972ad4030f71a1ded3f487cdeebda771708f7f9f9cd97fdc75437f4e30c230128722c02ca7e7d95366faee23f837155d0cfc42b19bcc3ddfa1e8fbc92f3fbd6866b25ddfa118079f4f7074250c6e7bad2cde6b77d8c5110ccb7dbaaa12b3d490e728a1ed76ac5fb230b705d4b1abe89a1bdbda49cfe5c0980e1dd078b6d28f1091f6cfd73b42b9c5cba709cad716f6915b64ec929b4e55ce03ff5f48968286b7372722bb29d67819b0bd52a2fa45795941dd9af24db35774a11d8fc2416a9e7f20c81b7e1f7912307a80648bcd09e8663df87e8bb570a55af42ec96c6e347cb1b27720b5b475bbd71d2f1903e1869496bf6ac7065ea2058ae94d7f5534e81267d72d3db68dbe37d9313173a54053bf6941fe7a76d78acd872b67c6dac088c516372d2c658af1a6b66cfc708c34583bd47a72ec0aab6abddfd56fb8e49254e123b72ce03d43861cfb82aea764dffc2043af3ba5f5a704fe57fb16101f8b33fe98e721f9b4fd24fc2e335f4943e2b772a522d5214200cce28a76bba1a05912f89c56535011644d5d358ca61ef4a4a70967255b0a22d4b95065520fdc22f998307c56cd353a7ade9b59ea6a788b98f9ee27939cb68013aa1b916624df3b2ae2890057260f435ea0f857f6eb85f21bf3c0d16a23c470bdd9b3b32867061fd1e65382f7202d432dc93b6d582cbc2485eef2946cedb4c7e07bfaaee958b5c8c00c8b5345913761dcd745f22275d658b75db02ab6d80c3381b2a65b18e1c9d190459d0e042a9a4e8ee9e08b8ad4514fc513db82b0c0595c88ada741afedf51b81a03139527a62cf631c4d06f8bfdc4a204cd599fa0e76ea49b3a1600e32340c6cb8d90117246838d5c1419ed416d866a643bfe090cc3f04e09a9dd3d72625201946bac017286dd97fcd995908c775262f2155954fd888dc7c6fa75513aba2157e2a7fc8272cc0bdb8fa111010322f830c8130883a1de63357b4879be41ea6eeecb3bb608723741f9285b5cab3463dd525a5d0c79be1a873deddc5949a8bd52ad6d02aa4072a0ae4d5fc9badced31a5d214d3190ed36c5a6f3c14f3463993c2357f7f72644bd54991d8aadcf7d94b2ccdfcc50a8403972c6dda0b7c0274aa6b100c416b1b723e2fe4591f5cf4e332475f54636cc2954be4e4b2c2dfcc72d6a9403d7a296972b26399f19a0a1d1c4d96612508edafcf0d4d04aaf747eee58f44af209253a7721c8433f6f6cce9ac2dab1ad1f2b027c56795a840aaa48f80b7869a55b400bc728a9218a41543d09487ae893fbdc2ab9d0887b5cd99f2a78ebfb13c11c6364921b3be8868fd52c175307118cd692c724d322e78504b7afacc99491dd0383568173c3f45f3d7e03ea96537ecbed5b54292cf0683d2580f321c85761901f8a5c172946cba62c65308aafe48c9e3d33d37eb93fcdf7facd37d8eea6a80e6c38ac97222fa24796aad95bf2a198955909551e436720fae9c7fb95fe8f833f00b9cbc6419ae45f504d28892ad413879b6cea4ffa250f6dcc8c4485567e2052440a8c6070b9c1c71bec6a3e4d8871acbc99c23b1aa357d7d6a970606b161bbe76eca3372fb056df87aed98c31af46bae427601c4034bf81330afc6af9fb916350edf3072115db28c885207edc7b624a548ca068b4d4f0f7508216a13e269e9cd536caa721ef2ffff955feb6c096cd19e8e26746bdb5397b949daa675ea3dc0bb0127d63d00cdd1b3b48b74ea85c8ade93da654b53368c13ca51df620bc6eac912b46c653b748602479b02ea155bace60d65771e9867933b7c6726704237808c3fee6fa36862509eeb57615a1a0e8a303da4e196d4f97c3a9ec87459ab01d935d66386472d0cc6ff12ad8b419e0c3022cb86c489f494a87deab57c15ac976c24efb4809728f7c5a7cc4e0b24acede93307eb2adc142910d20c19694409dd64e61c499055990557a57a90fe5588722752d1bafc99864fec6078cfae8defee2798d91ee0b72c7ab32dcda7b30c54cc997cedad31d049855c5ec8ffd50e29563bcda873fbf72f310873f3be3b1221a3b7d7adc4437767b51373a92b307890c30c59367feb1729b9338c60d24d37547cbee548c7795a9d74c3649935f262d938cdf8d4c063b721363b0b3c28ffca08f318c7221f29d79edc0783517ed8ad80c144f6fefec5872aabe04a0a37754c7709dc4765495257cbe143d15ce0b22393847d183d5e92e724c5de96677c6232beb1a320c55863115d97762904a6901d4889f48966d45da72586a7bcb4077fe7af6d8a1533983bf7bc672adc3ef431c9feb0706adb9eb314b72fb7bddf0575eddc5cf92363abd3e7aff3986973592bc65139ac44ddeea86518e506b03d317275093aeedbbbbfa0331ad019d5136e28fcc60e20ca332077d4d361aa8fa3c73f1cfa65b4cb58f7520a5bc5ff618c38a7e3c5e399ab3711a087279493d5bb8fb6c0a2e089341b78921a399125b4cdfc6d3816b55da960baf7172326ab0ad3c5b9d0aa0f1c09b089fd874ea1cc3a505207882130f405be716e0724e7b6e14ca438bd92ae36660183494e590c8e17d350e1d35bcfb843261ae5d4a14f2e419a69546d381929c89219ffe5dc0a260aa0f68a2eb0f2a6900b9bab47210d40d0d008e339f3a4fac6e77cc8aeefc84b163689a2ba24041e64124b92472c3e6d316db62678257654e45b3da0aeb351838060f012df57324547c6b87a85aeb79fe10cc64eb0d2f788ebbfdc56f30b3ec9b413ed4a091d1cd1e6a62e89f1271602b52c47e98d0ef86e227e76581ed58c8b320e917dd34d0715de4d9a1fd542d0b1c35e268501be7e45366c3170e473a16f475635317ce8ec2ad6c08890772325a9b2a22f6483e5687bac2edb9f508ee14a2a8351b1c76455dc3cd0969414ade000e637293c5bfff6a3262e1de206e526c9f4643348083ad21ece8864cda64956d9a950d25ec0f340643dd0a97df8c73bebd7508520d58799655e157e34749b9182bbcd8cbfac631c13907664630f229d5eac2ad3428e60da97bfdfdc08362c5df38e660e149eb198437dfc6f80d9cc0b5d86319ab2e0bc2a2aa6e10c36c3e9c2a0aec9fc56d1408bd286cccea3f0102bc0806ba0e8c4945b8a6f35a081d1e0f2dde40f32dd0d8764c0fe7b7ee35fcd9c2268e3d344bea958f1eff1c817272ea4272099d4e79047b419797be8bd85f593c02eb0e42b724a2b80e4fbd6a110de8c427c6aa6a2e4150bfa0cf0a91dd54f90203f3081236cb3c369e0d31fa70728e80ccada68b3db396c23ee37e6a7c06904529bd318f93ab6972e6680f7452728bed4980c8cc6a216d2bebd1badc49ac2714669dbd5ff5077b0e5fa7e8c3c7725eb2d9b6c996c54347519109cc23db10b2144a81bad8bbb1b696de88dde89270e0872de95939bb26ba62cec8721e1a3782b089ab4317cacea9793b2b71353d72801f7bcf4c6b275829ff06d5009c19879294feb2850d2c4375649065483b024c29806e5e69c687aa3511ba1e929cd65306531d68b0be4af42af69ebb59bcdf530fd3dd021a3a30386674a4aa9044b5296c5c72ff3852098358b0e2e35c4b954fff67d1a1290d88bdc176f31c11ee6eea3d8f9d493a6e6cce276ea8fa1afa7443d19af93948a35ed75194f2a25e5c3dd41c8b82776fb1243ac15caf1b28e2e03432b8aca37852c61295cf2fe4efed43f85f7f910830f6447801848cec89e495727f08b334f15e4d4d0b0e566874d0a16ae033938bd3a6bdd6a2aa5131b0445b72875bbd28f254b042519ca861bae6367881bd4219040d6343c4899adf65f74d6dfe96ec78045f065bbb5947395fe1359503225ba52cec27c6bcac6dbc94e37b641984e5ad485b229aa772cbf1d9140defedc74951cace76ea8cf42f1b1589ee72de82f7a1038f3a1976b3cb581b5971db3bb15745567e7f8333c0bb5662df771d7ba1860f70518c6551d2e5524c60117591d267e298acb918c9f7793172757b5befc82ce210b78cfed6d3aae11f5fefa45c5b77d801ac879ae87be0413238ca24da3a0ed0c57a24ab58a7818f14f5331a05049a18ed465ac2d9b07438ae73c27270b3e34d22ba96e8b94fe03b494a98f2144c314023728b2a64eeb5743e9dfb72f086e689d0eb7671180c1fe05585e5755582091d0d4f3d3b36f4d4e123184d1309f7207f9dddd7f259dc36a34ea2c495197184968619040d56cff108c3085417eb87ad6a18671fc9200b8948cd1ace609cee664f36738a4b9fbeb83343161a72865780102fc699ca05d1fdcb3a1daef3c73072b0b0d4dd09366a00e1da7d56384d1ee0b204438ff12655b03c1bd0c8162aa302f8350bdd6554a2f56da768c133ec6c56b2a3f84bcfd2320c15000dc5381ba8ef32f472e45499e60e94e4c3b3722bd5070750c4aaa8e1c2ca3ebf32541ff99f0f4fd9d9199095cd938ac88fb968e33255accb8c932f26384ea5cea9ba6a1a33e18a35964f79a3f28994da31eb0af94ef04ebe18a51e3da8bc595ede6c2be142a73416d8b081e01c896952079b00e7d82692ea19243319451347f15fb32e27456a5acd136b797c59764f6fb5e843ba190d5a1291e34031466de759b3087c4179ab8137fe916ad2c5a8a1aba60d0724f57d16dbe63d6ac034c376b60bf61b8383c1bdab2cd1cdc21df608af0224724eff6d387aca780acff351bfc71440a72d61e24ed33b9850626df5e06e95d4720a5b98d4cb8713886334b1cde56f229a93f655e4377b29be0b7fd420fae7a35d134d5d8227693059068bacc859c7aabf392ca4a5407755c931e0b9e13526c16b7249ba6b11fbb4bbd5f398b0284dc52ffa17f081b4f71e5c6a6f12e633f380432d98e28f647c18efed85e6d4ac032770dc5ed72ac0c8628bbefd5d38f01f3572775cdb3aaf101f64c92f022dc2d165df28fe0cd4250763d3be4770c6b8249d18d7c6dda1afbfa0e400b873077c89d970fcaa03e96fa5fe67e996f31a49bcee7231976b759aa39bf91e6dfc98a4ed3601942c5d76a1f5c487d3c6d2e229f6a44c4748dc33b5a51efd111135e60b1657ff226de65f64a887c257cde5426098637289be924986e0182007f3ca6141fad4e4960ce721df89e1ded6fbb0596c01f872e69e411660481bd2c8a3b28279f3b2329e9a7f13c6924a4c1541c2db65ed16724a55afa6b7940c557869c2a64ced1a5a17e989ebf5268fdf3ca7284bbef30472b9de55ca52e16b0e74872cc37c9b4c16d0d4a17271cfd07978b852bae0b346724d3110204a1b01f12017f7c01cb0b1dd08eaa317104c1b322d47b4af5c5c1e0dd546d0557d8a802b93e126b6f0f9e1231bcfa336dbabd856335e1fdc2168926e3c300ab3151fb69ce479707fa9a4b614d725a565f5c7b0721a4613a577f8b872fe7cace9655c1f6d8f8adc8ca9f603af498acd910140a91a641df7269943fd63b05d7e0a9229a27dcef62ef655a35eadefa011701823feda6fc9196422d6b372ccea7baf60282c058cca19d1e3ca5124054e3f90f392a9e631c569918a180972105e6b6f211e873409a00eee39b0524a71ff10612c01ac8bf7949b98248efd6d6730c2e5a6fd55554ce92e09d9501849bcba526eae83b24bbb4fd9c2937ba57251888a130ae86a7afc0bc4005754a12dd7f2f4907f05d4622c226e135db5b918896fc14e3b4987076e5b78e23f4eba40fc35aaaa8095da4178ad6f3aa317fd2e89a016297511cfa0636d1a59eddc2f9b6870d44c013aa873f355f7c46922c02306bc8dcb61cd449a19dfecff5c5ebbbb4d825fb6758553d8a35085684bd24715b688ab7a01d2186f57dcf218ac69c6883d884afc12a8599c59157ff9e3a79e720e63e1cd856906937a8a9f2dea36584b152796a7399d4a0b4e8595935853f772dec9ac799256f38acd108dfad8a519ead8e3e98e019a93ecae580ff991cdea72e05bf76322e5f5ae5acc46611f0ab438b219a418e31d14025e0eea141faa791ddb97501b038c01e179ed5c5fc22c90e0f63df5e5299332b2fecf7480f8387472a8ce21917589522d9b1418050fb7a785b866fbce077faefcedd153520b95ce206a4ae9bf235f4c87cdd2a7ea4cd8623a9ed4e97708abc7155edc2653cc425f1bf0b7e94dea09832aef446706fc2dc5bb33daa08b8a34c38fb7d26c64d594f32f058bd620b0bcaa961dd6896fc7534a5bf2467431406b338ef7f38c75a754b0720a0c5b5a89c08c6b2fda73f08e955823393be03d1cf4276e745a0c352e28576b5ca25499836f716b12e63f132a832a71eb5a1065c4f86b4ddd8f2be195d4192bffdf2d259aad3d66106682c012f03e4782bd576890e1aedf9a13ffaf6c22434bd69feeb763f3bb20dddc74aff61888110972b018a1cacd5071af31eb43b304722692ab98c2349af438953932bcbc8c9315887fcbcd994ab547b17a79e3cb8f72501433072b640c2cf6d7407fbfc7fbaedad231be1613b9f0dd7f879d34d80b5bf91770da5ca7a8fca6ca00b562c35edbb865ab29b0e0147e6cc03cd631e4d0725a3571dd68b4d28424c2d04b8cce5efad1de597d7f51c4cbca8c0c7a1f5c8244d3118f3cccd727fd58b782b66cfe0bfdf4a6b66812e5faa36d8d93389dd3093e689acbf77083150c3765b67cac506708cb1d7d375de212fec02d363791f48e221c01f12f701260498cebe4e0dfc49f50ab0244d2d543705cd4d1633f4b0282496ae3fd21ecd5bde4cec9980360e6822af4af48cae094298da7069087359f332536ac663dd0b0c74a19077baa2ef39014c82c233533018f581082407d572c7672c25a6fe1ec79216bf7f666342347750ca77d0b6d72993dfae6d3ecffcece683208efca2b53abe23dda181f1c9f0144b72babf02da62510ffb54b40d2246fff726990099bbc70a49aa7805c3bd40594394689b9a726dd8c19020ac5e48c62b4425fa390658880c92e17ab3e4601d608d2ffa83dc5cff98a883d701ceed1dd9c69af08be1aecc6eefb55952b5c158792fbdd26ec675837409bbe5b4af99132064a03429df16c8f67bb5cd8b2e58fcea1e945cf363f8c793b309b28dde2ecd9e872c129bf6b64176e703ee297977cced6a4fce07e61980148d3b1ce5c056460ae72a815d739d424435be468c2d7f229634b03cc79cdef9f79fd7985f9d8b4cdc2724a86d3367ad9768a8b271245b03a8038a8fd039ec54b63ab2a753aea2143d2448eb610486c50d1d2f2ea7a1c2037694e02e4082fff236b7c266c2a20ec2d78728d93734f6515b9d8a745a9bcfa3e7f2aa9e964ccad67284250d24a5acadb4a1f4c404b170304a13801d5c1183dae7ea70f95ed1aa0a0555a5122ab77126c13559ac13cb733b17b01b94c30fd20c01c05cceb89519adf924f437e29b7d5c46314c7bcea32c393b74359eabac32cc818cd5c74750ac15ec68b19b98f32869d3c721ed39842a4bfffeb4c479c155198ef75dec3565fdb75facad8c1f88b9ef76c50832e88bb1994a1ff31cc7301de953ea35e5781f61b7f12bf896a843abdf5c90e068f5d7d0dab4f9fa98eed44ef68e95326a5e4edfa892ae601768caf37439f60521cb0418c241840292e03e380c40e3c45d324cba852f98f922463b8785ed80c1cf78cd818c3e3da627e5bbb4cd4229b592b18e26eefd02aa0d01367acb88c51358a956563c7e8339190f3452da53e942eacebe933b0318f5cac1ce4dacf530cdea7a7a3338322bae9008605d7a5b4ae10208f39ba066cbf07f408fd6b446e72c59b68a684862b1acb0ae039a3018a1de68c8809c5968f2c5c0d3dc6c9270d50e591125c30a7e1e8c3f351e525a68d1da87d7871ec44518b35128734ca625f72a6d922f1d28a3f1079e9e6cd03c18b91558e7c88f96c3e89b74c13e3b19e077236980708d18f0919ffff06535055a62849ce97afee191849c864af4bee9e6536226a5be2c73882069ca90509b5b98ea57835b11f4129e49a73f2f9cf259d935475ddbec9967d212fbe3ad8b9b4cd3fe845188c78dbaccdab52915d4a6bd84a26ebb07f3928efa5b4c5c1e9eee9da493b313a1dedca3099025d0fab543eaa3f723702afe9680d5e951bf19f3ce12b950c24b84e74d2d4e716652dbab23fd56b72756ac25b29b41840f1417dc962487b778c4eef96dd151482ace7d90952e5ee72160a21412df56c5209a7c78755bfc652e97869340704b49715454ca79d49ce66ff785121454d95cb0d60cd32a5a2a8c40cc474eab9a0e57fe2d06651d964c3722305d8a4049107e08f41b026d92ea8e0baa8d8241f13641a5b6d3612b50cce721049aa027f48f4677e02dc17edf0be61feb5e7c8fc6b2a886ad9d682ba961c72276449d867d3fbb1429a773fb426b15131ba6dd1ccf4aa0c88753dc48e354f72507ad16743a40812d03b8d4780f43e7436ac1468c683b79bd89fd258505b2e4754ad04bb32ff5549a6dc69d287081d0320d87de33fa160c7602be9a4eddda14307438f933592ff467194b0444709f13ca7d5ad406a110f3e812dc488acfc83709ca48368bc3aa90b140c754a1e53d77b88ef39232237dbdc4d991e4f6710345c9885ce1a61bef64fb995c4c42f1c5cf99769f1eacfacf10ec555780d44a52072a01e21e80a93d72d9753afd64d543ce98e968cabc1a1f90ad5b9ccf879d3e14aa39daf28f96d5a8a09f4f500d2f9fa37de339a8046c6a1ee0d950c6e80862972c0448d7ccd510c08761c385bb840461c0dd0e65ac40cf4996e61e5c0a3daa072c59c21bee5240325960f662a8307363fd52675fe45b26f16ce6f0c7d4ad95a722a422f0528e93804e7c9a264de4d8d55767db28beb992fc8aa814f65a2a767721d3004046cc25f4a05897754252675114b65a023879f3acab14ef1244dd4567218019bdc1d52853d521d90405aa9b56f03cacbcb7c5496362ed0b1cf9aa28e48f66282a1dae020a0d5558856282ca23a06c0783cef25fa4c4febaab6cfa90f7228a310d45f54ee55b83369d8a8c7acfa6c8ab76178d7cd392a5d4aba8e9ee5725d21078e078ab0de94b6bf38fd903b3f14353781701c0136d51b38d32becaf7250aef4e1d05c00cd0d5157009f10af82660e8065f3634db24a741bf8ad7efa6852d4a04737215e034e82aabc8d406bbab3c38fe557ae194010ee0825cf39a8724139f4de4f0f1728ba3f7856721a7ada185632aeeedcccca804cd41afe036161f17f0cc672051cb67736fc1195b546dea3c8febda91ddc776c4afbca9f072e05f8bba65d408d7faec25a6de7bedd1279a85d7bbe7f4637ff49308cbcb0fc647202171cab1ccd3489f4a31da10c74c2060513888cf1069d32f3cd63276a7a0f725f104d5dd31f180f1c3efa74e6315ced0fb0f59cad2c5e2190149c7a1a6a3424df23b1792b166db4f69f82c3063d1da72eb8eb5dd71abff020aa41d231e280724915386bdf849740db21e0d04705f7b97150f3db5f2e98829e7979087352fe7261ed49549e5917dd094aca45c95d1baf8a6820d14fa4fd19c39e3e3f51701e72faa1b6c58d96d331ea4368f52ee0da4b66d65841198cd308cbc51267e1fa085ca7836ad4b599680b8cf3861ad8b5e4acbfa6cc9b2733c30cb598a51ca53ca8728638be3223ffeb2409e842865876d6610a0465bb862aee3bb4b66cfd053bb170a079f5d445f82a85f0aed6a86c5521228b129ed11e6629f69ae44718aac3fc2782c5f424513147f1fbae40a09fbe5baea631b220a7f972023ae1d9ca8a17ee2ee4369d23211a7be670c256397dfa280e5dea97a1d2861432d7b76719bc57df05c9011f3481a0b0fa2ca1ec058087eaaccfc1c796ca81a86903cdc908e581f5728236c2f1d970c5a0777b0a14ebd067bc835bd27423ca247250749ae07707f8727e5955d9ca24b2e5f886b1de5756900a75cfedef886a23353aad5308fb8f17726baa91d31e66dd4056ffea6479853a30bb644e05eec126bd5a8d01e6bc95117272e9682780c608451ce0ebaca90d8dd7fc3ce26be42b4196a5a23df3fb20b37281c52bafbeee95d9e502e5d64479d51ab92d12ee7803888a6bb7954b247693729505d6cb8bb18367ddc828f74b909bb34b3e49a7e22697edf0f5514ec0299472e6ad02c288012bd9c663e9dcc24b5838100664862ace67c9fc78b59b7edb3d728878d903bdf80888ec2c1c29209d3ea1e98081054be0d2c4a40f1078c4e030721f40ea6fe33b65d47f5f8a7b031b9e2eb7af744f149a70e5af7e46a5a6ce4f65764c618ceaab09c075bb999d1fb5d58b52e974b105665c51ff20e9a4b895703404b887889eb97ef719b6f5ff66226de9bb6bc090c1777a6bac01410f746b2772d9542f58ce1c854dec18495d85938bf956e0396a4b82ab8c43d2264c0279781a9c25be7c4d6f7151bddc71da3403b57900f52f119e5918b090f8e74f5d84bd4ac143e3a8e108eadbcc3235d1b25a1cf1abead1481176db847b2b4011dadf210365c2e6b8891f3243137d3ae2950435bbcf4c808fd221b4c5f2d39558e3a56d058d024d7a2f6acdce80829caadca83cf2237b6be9f665b183581a47b81106e270c46be85dcd8d284de5a66479878fce1823f1c329dd59c4d8d00cb403c055dd72a5c3825954418f5bfb8f33b2e9d56bb54ae383040280e2ae48c99142c96fcf72a6ba6512ad365f14d13940c401e211e378a31ca7bc78b5c4e6c73cea734ca3721565517e2f9c7ddd81da9748e8058b617f218859e5804319688a288bca70b97273044cd131096005866ed3eae61b3435bb8e9331856d35ef7055dadacf112c607b2179b3938fb769ade63e6de4513c16b8c318d9840933c7707478b0ebd6af4d588c5f25f52d6f658e7d45319ea9e0a52bde4b783c1d1c58e5323370281c441404a669b2967f3ebd0fd24a59b881ab2c9885061240620382164ac22113c6cb72442a7d0ccc1afe7b59b7c796e65ad19463414d29e2eb0bb8f35b9f95dd8b7e72fc1b5cfa666c60760c8307fad8dc9652fcbd028efe06c0bc04f069bdbc015b7264dd8495bccb5ba4d19a441c8f8c5e556a9eda756ceb2beb9425c11c9e976d72589df457d9efe38ef1024269821e50da517e1a0111ec536958153ac6f9543a0b1e8f33e65ab3fbde2b4fafaf9fd2d2e09cd5329b556fae01112126ed51723a72667dce9dea25dc5a0097d980c5f9cad46cb86a90c4f531cee720785fc98d1f72853b4f4d1bc6464c7da4cc3e8f5b43b0a3af78aa09f8b278943d8ae6bbefc4727ecda9e0d27085d4047403c486df9d85fe2f475c46c59c4bf09ec30987818451dc436309e55ada5db69cef1fc4b99461d7d88999e40e576bf943757f45ae176013d5e4d305044d2f25f5ef576b7b165dabc235c368a09a3a06f2e72e11755417c7f9e75f0a1d4814f4adf72c43e37d3f5c49b7aeb5a107371e38c621514c1072e86bf28a50f9554cebdcc2413bb645c942e9438b0fa4bc31a2ad8ea6f25cc8277c6ee034ba7e275292bc35808e17d5653ed1e0c3b01a4d258e8698c920192351cc487b82b88c3bfe72c8c08a9ed5469bcf27b3770087015797e787da762afd7266207bfcc38ef9fcc0253bd243aa288b6dc63c5a0993d86f7215cad573b59972eaf3192f826e040c067fb33ffa7f8c35b67574e4a6d21914caefb17beef299727e65cfed2506a172d905f6f8bbf2db0787d798c8662869f34e98e36c1b089108117d7aba90e7bd8ee02e937796753444c00b8fbbbfcff32c1f945fc6a3a6343b5da6a92c0b0d596250bc1c3191fb8f8c711b8ed4295ca1d9e7d7bb29a1c7f9727d4c5626d48d3c5d43f48383f93bdb445fd5678332af5dcb54f5f91e3c1b04727bbda07207a81bf443f51354b92e7d58e2896f1de4d12632f28aca35774f037284eaf0a733f2dda900c5d76eb0fa28269f2493612210f0f33d71106dfcd2c50419c34c35d7a65c05fd6fe6be2a61b8e7e83ec77c15b6dd88c9cf4bc8c023ff72f18c014ce2fec5d9dc3bdfafc5bb8e31a6cf7c2f131d4cc15fa14464e4bf314463aca6c2af3788be7f0f7ac34b35482e33d6e80fb21efcb5e7ddd6b46f3ca21ed981a1062c615cbb70b8c60c177e30a340252be1a742616cd3898bb1b385b21b3fafc6ebc57c952d6658b03fe170c4ee69a8c1cfc7d0955ae4c0993d6f79c3177bcf11a215616a8374941da133f7b33bb4b1dd0b8642744a83f452e2032ba872ee17766ab407a3079b76b579bc0e145a737ed8ac930ec30ee927ec5be3f97a72b315d0dc2b40820b61b55840f8e0651e8bbbc2ac97a7ee8f0b300ca8ba109d4edd8b98eb6930f4340bb491b7af052212ba08e5e8934423c41b9219c6eac8ab2d8e5b0e6f8a1b07c8970c2ac490edf6c77e685ad049fe910c41a779d2bb494f0c4415909be42f4462341edc092a93fc184871e96a0ab842dfc2f72d0e279c7972c67cc4cfe13d84b9cc7d57b121ce9cb3df5b043190bd0c60c482c63882d3747265fbddbb1914dc53ebfdf2e0a4851776e39d89978f20bb232d063b440d26302e4d2cbf844c92b81b3198fae950126a65de449bb91dfe6a886b583846aa003d4b9151e856160bc099cd08ba86878cc0fe127aa34954d490dfee22f125a01f8d72226447bb640da7e9ee0a68a0b1a9c82ee06b2402b6f19abb49afb85683ac6b72376a26234384e22b39beb814c38a65a1ee2cff0d58bc6dafda1f565a61a13d72a43ec5bc33604bd9d9880d026d73ef8f0dbb5a6bf4bc26e3e0b374f1fa79cc72f0c74a62cc6995815cddd2a6c29ca0287daa34292c05f331f0258491129cd172d444588095abbcd55bd39bc81c53c24191d14d05002a8a6d85f3372e5e0414465f06ee7ef50ea930a86b9bd8abdf00f0e3b3f8c386fb12e2902fa9114e7ad47286629443448ced23b381e56cc1a5ca4c46183c9be662d47d2d50333913932e7283892237369505cbdc4859f5118eacad3f862b8dc7700a2c6ee82e9a74a069722155477ba68b33f92829fe96d182e152ff6d93d97190995720f2584b37cb2807326004fc496f0968eaf244f0b8d60bbda42858fb3a808f1310f1bae78fe0ee7211dd0a8582777c60ae50be531066ec987f92946b6c8d2de6c13f4893b6cf47722829c66e586964de513e9952dcbf74dae00e6d140c31b0535e946d56ee21cd71d0fc5d932b50693f63ab86cb972c640587b76ca9ecccb3a460e82c9d3549f87298bdf43e4b7246b4bc681639a510fb9902535e18e857dbfe2209ad143155395023ca6c2a1a7b2b9872ec9ad88a66701a4f16f619d11d8ea37e4acb603d4d21729116137265e22ca70ac9ef30828b0b8a3d53bfc5e14a891e130f3d0af0b0ec0151fd2ca1805aaf64dec06c6dc5201cd6bc6beb53cdd5eb0723da5a170fdc8f1cf7c692a46fa29f2e5fa2a821ff51203d2317b7c226f061be33618407df3d6472226b3bd8b466c32bc538f7438d874035556f3d12d07e6785924c4a0fe1a97572d7287c4ab77004e4556aec3c8a55e53d5ba2f4ccd4e294ae11f39a8a8be74f72a56a0155976241b20d103ee4ef3124813f2ce011f05074e50475c9ebca945e72d645b8b9c93e0701be747f8894fcfc524ea2b2696c2fe4ce748088c14327b272ca739c6018a31786c7f42969d67950ba6370d90f479e4f8d7bf1c78540b66972b943fec28576b951c79460bbb56c1f8264b12f2e30871db9ff10911e7e5a9a7264745ec597575526a08b779808d891dd27147ff9ecb75b28e7c886b1d150ba722783714b9e4b197bb2e1b94b2fb4e3f272af2f971ee8112e660e2c6fd09b472c1b8de075227ba57cc397b1e710d55a3ce2010654a5d1f4f6d7d3486e9f7fa672aa76e68fe1415581583be931feede4872d01cb4b13828a6297d6e1f816901e72d7e18f1477058100f17e06e68cb73a323fc9dfbffd9e283128ca9351edafd472b7ca24f671203f69b9147f80828b341e36edfe0ff1ce4a484eff15afe89114724635129a27b88892cf97089e1f691de2ee7b13697e90fbb6105a7805f4159d5bbf018cfa58990c2e4bfc757a72a4caa6941e6b036a0498ac0201ad5f06c22d72ef94af8bf1aefff803cf6167b230163968ae2aea1e4a68c6489609fabdb7fb7248c1906da01548059d62c10b640a5c2ef9288ef69bebb0bfc609c337d0b3fb4393573c3829ad6665d18dce0b2061323d22ef6371a2dcb04e4c7205725c9f161872059f2b2c477ad1374f895e50375676fbe1fd6fef27e696637994b64d0d7672eef1c8879e0e6a913381f8b9196aa9b006566f98682dd750a375710c19d64337c04baa333ae4639bb5b19d83f0a8dc69c06a114b0a46371310cb50c51c7add72ad3dfabdd8e3f1553ebaa74347aed3a725ef012b7bc7f602e7bef431460a70144d5919a553df84d135bdd27b5e4ef891e2cfed2d3f822a6642761e5ed57b4f72b2eabaf1b4054e65eb65a030f91ead9e2b3f209b9dcbbce3e403862051b3a346f581cf39ad556b7ea45a0355fc6b0b3e8257bc1f58a30684104f41ebbb8ad61d8e9678af8738ecae3524611cf440bed83e729ad88dabc3ea0e84628242c516385449a358058c46936bb756aea3fa00f4a3ba64e1bea7eee500c66028b01bee72824e806decb7c9dbeb1f85d91283fdf839cb8c873b58915282b2297d4342a70461f02a831b72eb32dde593e77ad5b5090b6ebd14a737c0ad9c1eddd89d180172b73f587c16729541e94802d0b60c86ad10b942c06512e358d9c4747bb19d72727bfd9bc25e0333f3dd69ece98b3a83ebb9d0fa74f33887ffe521310b73c6560df87dd4f0e1fb04001cc4f527f9f5f1e86cf2ec49f7dee052b3e0c20523998a72fc8c9b60a7cc33435aa332201f0eeca56644b2caf781405e8c2162912e189b6cd7d259662b9ae89df4617a874b9593197f400eb5260140c29a62e5a830db88637263fa35aa2110deebec89767f4498a230d6af0e09dc1988cc15efae7eb0781e69c52adf6fd63b0dfc8a29d59e60d7d3982b323de6b1a4d1731f4cab3851a072b6ce89504988ed92cd174ad9520644904844f42788f6d30a8b6c92040de39c524b80eac6feb1fa5cdf47e5ff0a204a2c90f1b5f466a18b55391116d9ee781872de39ca4077feb18a5c4a0d42412b1d023a8589c0f5df31863d1e131bbf61d91e556a2a8750c680add1014af3be7e0ae30554c483872c2baec128838006ddd75cc123e131d2c4d9963b87dc530ab43d805291e0d8af4b79abf6928300ef9eaf07153007cdcb873f5926b3590d9f01fb7da2eee6197e252d756dbe923ff3d7221c7f20d418ad517a3542250d0498c41a75426117dde7093bf9d3cbcf6579b98972da8ef84522f656689c800274a1b65d61bc89464af93a8fa2acdaf54ce3338172aa7dd3bcf8c7087e634587f3b8e71332a6baf55117e029b19fbead4a681fc472e66244f7c8f57c0655055a7dc4f15d9e8afaf492708aa636870ee273268ad234caa31601c377a86dbf038b70bc064cd5d0134e385ffce6feed72f4b95683b3455bda7a13429961d453132f383f14537afae84ceb49333a661ec0c1efa560f772c00c9b53d15d15ca750089ba0423debcf210960da95679e280257137cc99c1720b90154747a3365f40b2d8f68fcedf3d52b91c56c99f0da27750f5f9ed130d72474fcd151e059545a603a0384cf5364eaa5dcb346c03859cc87369416880445688768e0d6d924c05b53dccbdef2cbe31f459fed3299326b66976e03335e0080d051ae49dd92b0f738829781134aedaecf23b5d93af88da444e2e170e74fcfb6c16233a24f55dc1d25d7079fb8e37e4a974b8634ad2144550785d17341ea3e702212575cd6438089fb537a4b86ae63ba28c767daae9703f6dd5129f1447141e7251bdb85a770d51f0fc206b72a1e8668f71a30e03c3c68619270ca8b0a8fa5c72bdb2a9a7827c3476cb4cd3f7a9a9c2a1c19717418d06669dff0391ec593fd272992501ec9615ba905ea82264589846e2dfb716519af17e93b9e4339ce06dda7203376dbe623d293001c4d0197d84e4016e9f8fab551db1239ed73fc1d513cc29e190d5afa77fd3afb6f3f8d225c21d2dd2c06736835c40c5769c46e4d1b9a7720ec4c2243cfcfdb0c95112bcc80b48ef305987ff4f71415eefe2e9d6bc6d7172bbd98d043a62addc42a7d573de080a3a2490e8a2fff5e70899d20447e1f85e728a3eb9201f2af2d91f18687deb685bd3bdf9eb772143e55c630b545b6c8a2c720c2cc358315da3f994e1d64992913a0192695f0f290f4ac61e2141298e7bf27273a5c2bc007fcb7a3ca0efde85567439cdebffcabacea0af682ecf089168ec59a0dc8161c99c9373697531559152059f6e3b42c2b78b2328100d86ed850bc0725d615aeebba3e7ebe681d164c352133a08577083894f33b91e03e4cec1506f5282ecb08ba9821c1c3ccc58a963fdd3b74103e56485d08f086a54805026534e39b372bd2463611c405f6d2933c91794712614952bb0a040ac6b3ab285ab25880ee14ad7fcd55a16513ab8a7baab0c23d9e5b98dcd6082cb8b7fb47f60e026432d4e42a0357f52e6a25a7ced01b4ffb7d5ef9439e415f66476260350ebb50f7765a84dea7b356bdd10cc72ff2eaab3e8ca92d52f2cb4e6d454f81fd5158b50e0729d496fbe33aeb66412c8f756498e5a8a3e0ad311941645d275fd349fd8c5311d09baf1a177852ebce1ff8a331a374571b9715562a4d3b4385f8d978efcd57913dcf8bdbeb9462555a11fd9737e5fa93b060d63bf154e497b4ee83ea58ad96d54034ed6d7e490dda8b4240a1d9ba152b7ed960530f8bb2209cc1426ef5ee87f726a450225daf4037239f583629cd7ce7b254ea958827ae2afad36a8d7b292e6726ee3161bb58618057aa02b1c4f44d08d0609d64d294583e8bf7e791a3d70975697e492cbf40eee5c217c59fbd3f61ef3f03b5bf563ebdbca415022c2df195a72637cd9420c4965e10d5735ca2ae24bfd86800b5f8b54777b8a28917a2f11ba23b0cde65479790dc9717468292d70fc2a791bd92b6d2ac45c79b8f7102898347253ad85956d28018eb8470c14c021a155c1556adfdc0aec753a9e890396a9ee6f4f1d30159748464d8bc95f330a35ee8170deec243cb724e66794d6d418fb76096400ac62562be787f5561d3ba73720e9da6c73a51a8d7105d664a83ebe871e720632494770c7a0beb86e1476379c9e3b29d970058785501f03e1fbb30628d226c28124a111f40d559a124308bdf2a87c939d62c34ef8e815c8a01c7a59523209778a659d083a82b6b9b1c875e676490d23a2c79f66d03a865f900a4e19c51f1cedf60cd1ad56aa08c368cc328d87ded9bc81e3ca4d38c608e077dc57d7a797577d205fe2d4592d4f5a03f59b182ac5d2b747d8e5bf7471735e78f14beb57e8726e8bc34567593ed6a8a15e6607852f365bf41ff9dd81a318384196a5f3476c728add443d11f3b437adc82b635c0b73465fce1539d339c11adc8b82d4864e277295d6564a2aa4ee68c3fbbb3b69e2182819216e60c9a5751a82b9a83003a35970fcc0ce5f4a9630a68eb1079dd70fd9ff924bbe6bbec9e46d2a9153ce99ec3d5221fc00b9bf85776db844ccd68b78e77589923dcaed02c81bcbeee760c9822b728337e52219a6ae2aae03f76d448301a4d078559fcbdc84fcc6ed34220544a0721e5c3d2f2cfcd0a7a2f9cf4b3a69b62b8cd9d49e737b92898efeb6c18d1b1416e28d70c21f827ac6ffd77953de660f45c2da4dd1673649d41682b60460c243723b1eb555c1e44a2411ae2f25d567cb96f37329566e298319d392a51b7288b372bcb99a86ac0fa923be001965b81070e033b79d3eaefa606344f5946c620afe0a4405b26438d9ef46d79d6c440c4e2d6195340893e769d5c9f3029fbc524f655421eb8ef09f301dcb1c79980a0d31e4fd4dd8998a31021615ad304e59d898326e329c126b25ebe00de233d2f6f7691f340c2ec48be4322c661c8ee9641826e462b037b7636907f25ac946d925b77445cffb683c3fc59cddd70c1883d6a613ff7233d4f574d10ab72443a3e3564d4f1b29c482b93ffa5a7e1947cb2ec5f224d472c644eb3168fddb424c5b5b743ccd0b9c009b5877c2dad1d014335704e00d8672022d8a02b864905138efe38fc0342f070fd63463e603215feebb2507e047034919bafb21724f956ab32fc6e0d37ad5daf3f028b3a28ee585f5a6d9d441256d72a1c96c06f0a9c52be0e58c31702dfbc7baa1c0f97a681677e53519669f3065720ad761768f67994f69012e32f42dcf7df381d4be95a66968f47d55fb1910ac7274f3e435697021cc80c6dca396d64bf8a92a8b2fcde70875b15580ad74078b1288da1de275e18a4ba0b32617fc2ef09d8c2de11b2943cccb4b5c8cb80e5a066236cc5d91d771fae5dcaeabc1c5b696cae51f973dbd8e65d2b918cd1fa8122872cf728ba6ac3057b084d64216277cd826d7ce2ba509bff439903e02bf2ec8c3721f22b2015eb59a8ecfd458aed437b4fb96b3a2a4cc6d606d491c39190a0e4e7283dd90135d3877f7d144fff35c41abba1a90f6e10e0a199e537cdca63d93bb72e800c8b2bcb770d594dfe493e0147f10beb6eaaad9792b14bda35db812389172355281f8876bc1d83aabad1cf5fb21a7868003899757995a709dea7c51f89650f9c18bc6b0c7ca12f1f5557a30f2746f1a0dc4fa57681bde26ffe862aff69f725e9e2f4c8c02bd2d50d227cc40a5a2d0b6a35d77297ac972620863e612e5b47294cdd672dffb7ce7afffc46adb06a06552449f23b8226149212604ded4f39972011fbbd3d03bca495fe58e5adc5e0b2be3059e78218aa86c5933741ec97a03724ab96bbe72c619baaed8590af1339e5fa44f02ab5524c25e14e1262091c8e3729e6f19cf3ddc49708bd8d15ffc4e0a670ebb1720d879baabb9571f32ab2baf06aa2b7a818b9126d8ecbd1577eb7812ff14445027da97e299f88c23bd5b648372eb9ab2f1f54ad42ce5629dbb6429f8368306c4b4b50af2d681fd5e0a68acca210d1de0216a6597a32c6f143bf90050460923ef43a887238a1815895214f05d5e74379f89ae01daac1b4f16155456ce0d8f00b3184f9e7a6782b07c437122cd72702e92e407b0c57e7b17386b4bdefcd3bd130fd04e113e9e79a0610affbf0f72e65730990bc5f44f7cab5f6d85110ca8a06a0ba24bcbc2e6e87d2d588d09eb7202aa943b6f57889b7c600c924832bda20ab63984d780db2c37b383a0cf7dce6503ac9826b83a6e8da3e4ed1112e275a997395f75a198fccab01b6b21f4789c154371ffa5912cc077ea9eaeb9dd681d072e9031df3e607d11a29eda1e0456ec725bd175051ccf8a64e8e94916313c6df76b2e573a4617d7897dd46ff94f59030fa182eae8efda9b44bff45c24789936359d50188dd3be13ccd0ef727537b7615eb34aa39985314414cc6b21d1a8226ec371c8f92eb920e72570c3628e18b7fb72c24bd347f272fb1d2477b4559e856e2e36b75f1f42bb25999a77f151d98dce72dc3e10738921e8b54253f4d56df64be23af9896fe6e0585e46ba175e1407db7236cc249253ae96f96922d28e178a7af045fb9ba8c4f139b8192f2e5bdf9c58728ad16c752fdf9c97c7f5610e786a6a09512729d9c3be57e78547f9d88f722d72b9d651fa54c970e4cd23c54b58de26f93101a07e8e2c52840df3ee9cddf974726d2d3d3d405d78fdadeb37d0c6cbd7fb7453d7783bf300923da13bedf440c57144ec2cdfc8775fd5a6d261727b5f121be0e7d74283804ca82e9d5b7d4a598a1101ab9d4a272c3d1528e6d603580647a48a25d61e47ebf63e9db3376a398adc1c3bb172fb06fb34891839ba27f73e82177afb7192d6956e01ff374f8c19be0272877c2ebdaf1df35e76fb99551efff1afc0b3ae4caddd3436c2caea9d9480e864ed377533bee130f0dbe165e7ad92bf47142f269eb09bf12dba3426a058e8b719adc13c74198f8856eabc8a4fba908cd5cc748e1a7230afb36e4f318831d05f1a762a1c1120f51f216e14ffb757d02b7a07e0bb6e61635306f6d4c5ce33dd8a598f00fd0f2f177821a4e6183d6a294866f3343e7e71d7718ce071c78a3e830054babac536634ebfe19916689eaa57d202cfc29546ca2c8a28a5369221aa0a6a72402dbf1c71291105cd2cd6cc18223ed52e84fe3e1ecca994d134e0fb03ba71729e9fb958e8f1d357e9c4f304edc93c241802354fb5bf5a3d4f80f0d5a0a9be1b8af855ad77918ea95ee9ce62a7dcba78d014e19b63d09d375489e8256d4a8b72c9ecb75a47a02f11a3d98d1f12821ee6a589b7c4db6d25c192f4b674f6a82d5a47b562cb109d69ed73db74bcf4b3347449ef36bd7726ba0c93bcc15be3f41c31cf1f24b63d8757c37418cf5bba4e1bb9a91c1461f12f379e01f4717f569c267212163ffc7166c16e983d0574e6e3be46211309553f6b360053c59b5544a62872c7237dce13991b3529cf43dc607cf231b35cfa57b5bfc022f4757215d65d1272a1d83e206e4b0f52313d06610a77dde10cf84cce192e8259f20655103939a74f56e91f76dc8cc09c9805e3b406a5de50c9c4b4f9797b99b5412c43ff184b8c72de57c7e34fe6917e1e1b5f1ff302d577bdd5198d163451373940696dafbd004b62b4de3152976df593f838e6955cdf9b684bfdfc3623fa8cfe2b34cde9b5a47200adc372cbd3f4ee297c55d356192d2b2e216a4a34841b3409e240bb4524576a90aeb6fac184473cc47ecbe08979d230822d0099f92cb69f9c3196e5e04b115f41875fe3f738123a8565583aa1e3218a7b67c82f598682a36b06501cbce5597258663f34c2f2a3d270c82ae0776c7d4acf01009dd828f0add1dad343079ee672319209359c25dea2ee9c2ac2bb5d654ca616f3e531e23a004009e98f08161272e1d986046df186810711a3757bd37e528737b9f9af886741822a1a79cce1ab7226297cf83d37950da8907735b142de9d6e83c47c1e3b668260bebe4870082772374db82d7dfa66dd46b5c903f69ee4894d34e5427d4cd570e50e3aa0647b522319a9dbc7b30bc7a2a51443cae4ba74668c850bf3ef88b5317f0727dd407859726a3dd22e4362cfcaf62d4b75cfc21dce9472d3b8d9b24950d06cf2c17d3d542142fb8d964cadb181b0f681066353e131d6bf12f3d748bd04fb3a0fde782ab47211d115c3bbcae39e3237994c1f70e138d480d3ae2d3bbf5c7c314c2bf2478e7239e4e066688cdf618669d798e1c0e133dd496b3b42c9f4c97c89e6bed757ed72e3bdd38725cbca7a0f82e13a3b04c60f24a8f70c6d24adfdf3e7ad9032d51a0f9598f20c557716be5ecb1828563d5440e981ea399bdc4b4d86eb7f8ff4d3d1726ec49fb127be4bfd0cec996354b26f646027f833e6e23451ccf8fa9ff174d0725b2dcdbcde22c6f8f5f50bf17801a5b150b62a06b633489d5d13262f3f8ca772148165d73f7a742aec3c821fde23996d583d048a5250764991349c761576da72d549dec70a0e85c90bd6db1ce1555b804943fd7adf84721fa48c4b7595b096670e698691e16046fdb63bbda163af8a50cda3614bd77c2af52654ac8cfcac191e8efbfde9d03a090e3d144851fef2b4ca9cf11af828425cf6f62bbb6d50497700b90f5e421b427d13a8c608a293505982f939da4d0bb28f068233a39ca92131729a4b7ba6d127b5eb6badd9bc1bf188488169070969c7102a0ed39453e61e145067ac65a74f6e2b32094ac1626fbf4cb85bbe1fa0084601e6138e710bf7b73953e530e87992a0b9652d958ecf31aaa74a6bd2846885ae0828995cf89efab343688a48d6938067ed4e1da64e2ee078f59e9773142584117a11d970606b7eee0872f3c65d1266b80078a0a8594c570ab3fec2ffbe90d525f7f2b181d61d10ebfc2887a1089e1358589f85b813da5a5ee0b1b2bd7d993eb23b91db4af1eed108f57233b701dee5a2b0283a9eb30766eb72bbbbf5ba3d7803694556cbe5c455bbbb03a5f7658a1ce55e39e7c64357e4e373cf55ec4be19ad5b842f276607450c59172c1164ffc3c4a9c5c5fa75d3ccc1fb67a7e1f063a9f8f631339b3c25ea5aa7d720c17e9381603abbfdc0729c2144df96cbbb7a1f64315e1c79161e14748ee9572cf3c91795563e5581e160039fb47d6a3920e9bc81a2e3b908855a451d507400aa4d27039fdefa2174f3d40350213c1eb26c1cc4fd073727356fb880da4e56c72cc627934fb7449159ff64ea294593adfb6e29bffa01844c1cf3b5f79b5a769721574d01faa4b6d7779c26f1b895dc7aa47c54f291cac939f012827344eef6c729580d825deb730ee16bd6abe88a22f0b50b00878bb353d5c25268a7e302a3572e709e80771dacb92153f14d7243a0c12580c453be855ee0c5f87198ea77ebd03343a4f9a020ef37fb209cb25bd97113cd458e16bfe550a6b022b491fb0934e7234048dcd7a6deb999413805487bc2a3bb1703378442c15451830f8371b32ee72d6460d6b7dc62d31c5d4d772a56eb06235ae373cb442ce9c918209fcfece8c36e28ee290c0b431405c3d9e4d0f954437151b551b549ef92f0e5633d6872ac572a8f40622478047e59a9fbef3e7fa46708f4f04fcd4acfcffedb61d66c05a9d0035adcf7929fa6c8e33934a98139238bdfc662b84474f947fcf9ad8d32e67db701a8c51813463edb8ed35cf7cd81db9f7cabb5e378d85ca4f1de874b4f868b054984a84074724a7b52c3b0ecd8ee1d0821876110e457f34ae0343a5b615b0d072335650920b549644c0010bd04ee91a28f66d9be431c4902b5c3b7cba261caa72c397dc0bf81f62a96fbae472f23d5b6048ed32be0da846df14de58b2ce093472ab40d3af0a898a7677ebf66d35d941e6db5cb0bb8a8bd203c0c38ad0c1cd490eb19844e4ff6c8b7564169bcbfd5cd372459fd2a489b40dcf08f5e01786625610e54a5153ad9bd4382c65e2ce71427c5a82655780e97ce1590091fd50a91c5172ebd2fa50ca870564f8e9dce64e90390645c4a5e54ffe6540b5309bffdde8ca5d0fceb715b8db8cd8b853b09e7b210a9386af79d3792cb4f01e060872e1dade7288fc8a9467656390c0e17a9da2948ce2f55929d6dc03fd58f4c880f152b44d721c09d1166d70778dd5d2d5094625338cb6a63420fefe0feac206c24c3051b87246abcb9390c79f2d1f07b601c19d093d5c97dc2136d603ae6dadb6960e0be0721aa8e8f54a110db27bb678b1b192ee96119b686959398f8a76aae18df728f672f6a260a885625bd817d2ff6f794c0f6578c1e3a3f5700532c3a4d779691af172b814205ed5b6b1fe9ee8da7f06faae659b94030f94d7c7be6a7125157f25ca722f7ef644f5a64640b85bf801ce125096c1b6518e1defc253c8f4141700bfcc722ec3a99b9974405fad10e9fb5d98c1952dc8dd34c30c92774b47840634b28b7249f38a09d499267beabff827591c0186597ba6bcf5440d56cbe3ea0fa90ae600882a1d5b7bcb1fa49b75f64a23eb1b239d39c44635c37bf23d948437e59c372d0f42d882c1263249d64388f683121be41d79ea9bb20b26642818bf74899fc3729a57e47c2ce6f0802b3c97f114564be7f78576575a4d30abf1385d10973bad723148d55f0d3f88577c9ae3a07204c93bc3d0a5175897d7621f08fd4e1282f329336a022d1ddcbf18df4593ffbefb427746cf3dcfdbcbba037dcb56fcb35b0f72c2eebdc8b94fd8010cd2243b766c96ac279d4a77fe1e28851fe02c4500b30637d20885c0cc9f19390519d1b041888bbd002c3d123483fd794d93489d5bd0df11cd268527c51ee0963cdf1ba20ca65babaf06e46706d372bede9ebe795ec111726a8dc4de6e6835237a77ef720e01155cdeee17144cbabf0cd45d28ea4baf3c6a026ab403a1c7244ac5b5b4ef53b9b1675c6e969bccf06a951c47669308cbc37261a9009a7588ba9aa227eb795c32b47436de31ef2b3941ce271b5fb383340549ede57e5d80907ed26cf4df193785d718ceb36322c7115724d78b24760ecec74f3d51794cd78b0f0051369c536648ed8da5dbd71caffe959d685fd83ec7c62d1167865bb5f229ebb5fe8ed0a8a60746738afd0f807e60310ca6270445fec7fa72d4ed0eca0b703d22dc7b82f19590dfb43117e113369e5f5d92ecedd07dfbb772ab72c8805df60d09549020a3e77259d9bf948c6b381a34fa5bab2dd293e7bf72f37638dd98629d0bfc924398e6f5b0a6f8d836f3df09b9591c4875c2b49ac072813d7be4a16fb86c304ff2ff97f8cde4d266f4aba4fadf9645196646726bb348d7411f73baf11ae85811210d7720dabfe593c9dfab0013c78e7c4fd3374f3c5cc4334bf6893260931bd1b3412fd128c18e1df5b5a824c8bdeb78a8a91f98fe18af325cf21607f79c9bde0d08ce7d6389573c351db048cf09c52634cf065b065a16cd1e25e3224d868d52d78152ba8ce92bf0303170cc4e0564f7f7d2e2041272371c0967819fd2159e014b2b721e8f6fdfa49c833bb9b6d8ce1b48272d1aee721b194bcef2bf1ae49e6dd4fffca8e3e84a48e5ecf7cee8b919a48a16d2227671b8693c2c6030e4828dfd468139aedb46b7ac2e17a9d9bb3997c4f25bf167ed0cf44ebec8dd8f66afb57f816d742087b57e7f649e4a6e7c0d37d8c1c89745d67280ba00e0c7ab975e1de64ef32741c756627641002440a30f4b0a2cddb3891e226f46ab9695232685a774a7df9e7449350748448db5cc45c474ec90478f78480cf0921270e85d348e5a42515ea2dc64bbc872e80e76ccf89ef81e139fb7608627e78a8ad37c871d13c0bcf91f56426ee6de31c9e578c9f781b317fe14c3262e720060f7beb2838bfdc807cb376ac9478cc69d574f5e0c31af6974d60470466e7201d04fe97b73dd26e399116c8bc2baa3d6b38fd1766988a73fc4d4fb229295722caa071926632959b4946fdc1fa91869d839ade14c6ceb0a1436fb1c88579b72ee19ae27ceef368980e4912ddea57f1d57c425de895b6dce084944fc79bc9872a4ea801e7aac9f9a91a18d52e335bd9442ec20b65820fc5807638dcbf4dba930bb0956e3d05170082231dd0586718ccc14e54d3cd1884cc0be58b8131b534a72196f9cc74854d186da77b75b369345edd085990e1bee663b96048c9552c5376d6005868764d1d3b1fd5a01144d65fed565c8851f2ecb0777aacbf56f93959772ad727e08a899c520e90b02b0bbcd01fd5d54ba20f6cd8f5f6797b76137b0e46e112ed16a93f2bd95b8028c597769a153fe1fb72835bf6c1f61f531c985905e0f646cc8aebe69567a8bda9a619633226cbbc8ba19bb9c9aa4556b8e21e697c05623de1c950266ee5fec7428c9d8d6d3fb4baefbafca43a54037568ad3fe2c216b426b8a5a1eb5ba12976e28b46d4935e5addcc38b9cae2599713bbfcd1d2c424f3e6dde510f56bbe4dcd6efd2f0dfbf8c850b0484283cebde8d70b9b22a0d1a38c6efc50d6df93a552a2d13fc5de75a1f4abb6e7b19bcb2cd12a05a6bf6ee4872cfb9c1131b1dab37577a8acf9a7179a5c78d0ec080b5fb30e5821df0b7475d5ae0f332d1261589f763006ae5ac1454a48f667d4973e0abc2fef62147d2ed6072433918144ce17b4a16ccd0b4187d64a60112e1588323edcc4481bb011f640e7249d4496dd5f345f51efd4a9bda26344876a82b750965e62147fa849874b2a572bbd6dc128070912b1b8a357c1073ca6cd1b2e0862e4ada96382c7eaed59356721ba7562084deabb034903fff508808c28bb0a4e85c7574c8faa1fca6d1681c7251f383956b3de3631cf962d47f7308cad2345019187ab1499d993b5b7601be72b6b5c9ac6e7f8523f4433863fce8bb3ab18920540f3c3ec8c24ba58ea23b3c42ef2a28157b5884b510db58454f44d810a54c6d3ee761a0976ea8f6379ceede727b682856973b0d09d0e28272e9e9e398974f99a45ed8ad11fb6f561bc906a772d90b9c42767cbb53e0fb3ca7f81a3f42eb49aa8e485399c81a6826b2048f2c3f240baa6be619e2bd7ff7990a590e7c4ffce73e6a11da36ffbe6d025dbc9a9f7294c3dc623fe568b160aec3ba72c93bfef6ddf39de30f4fab499fd0816be5cc7275b7c5939dbdb467d45aea297226675566eb91e6f4141f471195d76400f4d1725968942042ea097e2306b0075ec092f751d543c06093ffdb444f5e511ec5d57209fd63e277507bd28c819d00dc7fdcb1a382f80f47332560913b778fc52c597220fd2a5abe99c2feb51b6fab66e311502549ea7c3a34a2fe8be7250295937f7271e0a1094c9a503087a51e6674a4cdf3cf5c8fa00d7b3ad564063e8536bcf372d1c6fb83003a95045f33709c6b4d2cc5fdb2ecbc1fabe39df99f0f8097cac6723c0b882a3a6f328f8eaa49eb7057ba38200e1f9c09f790a6b4fdbdda36db3b6654f9f5599d7c87b09804562b9ab2b26eba2a59c5d10492ab1017b19c70bfcf5d2883cb2331b921f62dda43968cd145b994b7c78bbc280a672c0a970eeedba272c081010f92ecd371dcd4cdd792537a307c5029cb89fa892ff6a8e51ff8cf1b3c28eff5d79e92ce65dc0e279853b6cc962c85c9c6a57c1d5d47a1be71a7b87c72da2580cad95477a4cb3d0b39b1f559481d10dd5295ec6a9f017bb02f71d8ea723fa564afd231c38b388e912d591fb370345a807684121d093fe03b97edfdc40561666bacc8841e830e0f95f690be9fe5ae9d1b7584b3833015bf2cbb3fc67d2089719a2ffc93d3773946fd1cf993d2274cb4dd725615bd121a5e08ca7a62b36492dac324c3ac05a2ed3cf3ffabe680bd9ac9ece32407b18b9adcec84a153195120f585e8ce8e35bc4ce7af4378d9d61479b228af1b1f1c8063c48e02bee0180c7f630ed8fe99d45831f2668e9c9ff8abd843ab2a618a83ff309705426e9017726e75b84e54bbea6c12304e41931f223ffc8f91d6727487cbb2e5a2a98c01f772abfa6b403ed43576f5cf8bc8a1c676fca90d26efc396166cc1e436ef3dcf026b45ce7b1623811f583909c96f90616618601f0b973f6fb809306cf5832e178b7208a29262510ca686b311d9c59d88b73a376d9a0ed45d3fd825fd94e8c07b8a0dcd2502749680c6d69e1ceff8743c2b9304dc51bd9e2cd9c260a3fb9ce79c704bffbbdc7943c0a94c2d8bfb5f35f38cd4a19e20a79dbf4ee186fd0ccf1eed2772d0b237e78010ba748331e959d6c9fa18360a92d5e40af089223fcb723c072f726a89fc9f6266ba67333db24aad207e72d3653d382bf3e750346afe2f212b33179b2509f57dcfb9fa1a6cfc9eb4edaed4e5e30509564b5de5ea20d8d05d4fb6123f3257e9cb5351df425ca569f09eb3e93e2584c4888fb6aa943946d1497d1472a43034122a4253ad52c94171e7a32f968f34077803350625cb136f1cb12e2b469ef91c8c8dc9fd204211c4535fa590da739b6200cb3910976d97bc826e2bc92afdadce527764f7b0e27c59998755a44c9c9c2c0b1292e6bef1de3e9e154a5465a60dab99eb8c6148017492607c37d09cd69eab9810653c2fbdd50e1bfa592c4c80ff5f360b981a8dc6156af9d8ef39398bbd72f5f7f2107751b0a8783325a0534b2206c2a0649c82f02c3498a60e9a6f2563ba95ba65fa13028b21a67e932763455d88d586267dc2820568b6757c2fc8365f8e885b7b91aaede8038e0b3e3972805de2364536eb3a6f1d9dcabf5d1ba025cb9a7aa6f46d20111c00c421b5d1724a5f9df25ded29e97d8dac67f2073de76a0ab40a6cb00fd766b8ebbe4380b542afa0233e1112f3b21dcd3bb91c88f3fb129f71c2c8aa2caa4252ff8ff4533272feaca209d611f87041693e75077f711ca04344033d43850415e5e5602c4e93272571ee2ead2de0fb6391ffcb43f5f400a747eb3dba6adbd06afc18683038cd59f0e6b1e1bc6649d20eb89c8247e0ebf5d10d6f82d42a1d8df8c781c683482272d3c6c6f3335dcdc250bd5cfbda0a9c8892f320a6336791e0f36c213434991d72ac3fe5e736930b1026026e2a0ed3c5916e8b205694253edef858787487b9312fe56679eb618665f966e329fc2de9ea23688c0bf852238c6cafd63931a6a41d7251c4e19cde47946f12eab22b6e5e2b1bf91ff9ea80ac78660f401e0b19c1d472ece39b9f9ac81045e64791d0b31d752021e042de6b1e185af72a93c57580c4024f826c2a3ebfb1cec7f3b9091ee2b509ee9cbfebe13e7542ecdb419a09a37972464c12d6dee8f36b80ce1304192ea48466ae67706077717f69fafe40620f1472b419bd9d8842519e8ce248a01c2b0fd17287526d450db84b0802a9dd9698f92d9d5ebad52bf498190c3cbee683f37a34dc4b53faca3aa66f5a78355002a673618aa204b86dc76343ee44ef2e44150cdff733d89013a2fed491caaf3b829c360650e4f3ebc46334aa42e8aae489b1bef1f7622ff69d303cc3f9ac0ddec9242c4429682ae05a3600642a0bd457d8edfd7855efd42ac6bb42a6543c7413693d15661cb7cdb6bd27df02b569b4ad45a250dce90cae0892f1daf9ef3640938744797241929768c3224b0ef9f5d3002970a9271891cb9d21c6cce6b30f0f2f1ee26372289d0355d8c19ea55239519398149f8f7a4aec1fe005d6703b9e65efc439ea2a62a30a2e892d62b29e11fb466d9fa53cd67ef59b3001ac10a6545aa1f7b28d72a6987b119b5829f431eaa1703b7286d74b1fcad3ff7e63a88007d0ccfc36af724ced66d06d69077e07c692af91d70f6993367120b22fae55a9aa0201076e0a7234cb337c9665e0b42191e2726bea903721d0ad54464e390b9594574f9d79853cd1f940f18bf9141a5cee63392724f93ce8a272c7bb131927087fd1481eaa4d72dc619cc6ab20a15ecb7909932ba7d8596cd2962be98cc94b8d51e580d3f0ef7230fd62ce3dd2b1615574376e9181e988ad77daf597481e44185ac6dddd79a37201e865440595242dbcee14350d22d6d501676a7c1984d2a0545bdb4805fc88463b153fe30a32c7bb23c09004c5442c924662c7b84f5e881ef3c476a01267f07258d8dd5d32dd4d0c1f998c8508d3f6fa982ec2e5e99aaad7da65c4e48eee3e7205e609bdd20bfcd73676ee2f69f4e26d1501cc2a983d09838d45431316b9a67241c6f157cc516ab48ec2b2ebbc14d93d32abc18fb4c59f523ce964267bb2fa72a8ad4a341346d6a3b73aca71fa61007117f80d2f99bbfa443929d1349a8ff4371db1f484f3be09f8b47c6464e4e1b1b4129844172e1ddcc84cb8fca3b5ce8272e895f023ce30dd06274283f2630b9c762509b13224d5f30df4126f62e6fcc563a8dc9b96c1016cc2c7106891346a4a5e154b76d2116b6c83ebfd0f95f98793726960deadc92ced67b7d44d9fc8490a7ea7a9b9769de2db371327f6b909df5d72a41645e8ddff7efac7c8790681a3ff0283640515e14afd58358e5934a13f3c3afd84cce63c2527956ab64b6808aad0257319f6a2d083792d29c8c15709a8c2729619beb4fb1ea03336aabec5a2433c1624e37d0fbc01b80033d7e6fc2379b27237bf05495abb69a3eba93e038e24541d178102081cd6034309692137a674f472129e43d554df86c551a33f6e36c9a7f3a7be6fa0fcf815917f270915dbd0547258fea36ffb34eff0490076edc48767db7f0121dd79929499831496c8d4f9ed723b642490755660197a02e978afaab199a34c614c220e3c60d558de92fc3f544181c04473fb3c2abf8e50f024f47f6bf1dd26bbf257f1689baf7d6e617a3e9c24dfd25f4546e40e1f03c2913e96f783ef8fa3c96d3d74a84715569ca0743cd872905390601670f7147ed53a6539101a73487b85827d2aada2521843473d3410721a2bb8850d1f46a3c1c0f3505a353720aeb3bd6ccdce16be2c78d3b78a21d372720cda97b84aea21462b8a546164a0bf985a550b33d489b3415031f85d1d29724c345098ed7b21cd88671cb510e98ffc3356cbf1a0732c756321f18f7a8198727253630cf8cf1a18b518a69c56cd442920a75adb8ab2899244149a4b2533287216f29e4cd1144330e303a93b9236c413adec249fb5bd5ecbd967009c72e8eb47e875e9ab150232382e34931569e2a9d768a78262d1f26ee3021b823b137a8f527082d8053eae21584f2694e24c3e2b1c38a99580f12107494e8bf2b79975eb162fc832bf047e42bcb1fb721eec8828145bba8a9e833ccd2b86c28ea47e0e613e9613b51dc19a2e0d3715d8f46ab00f8b92bde052de84b384be995b32a0e8842c227b5ccb0d5e89e4c57c7a0b7aa930572e1f77092b07ae2be19054375d83c2728c23c145dcc86aaa0d9d3e49f2c2b9f00dfaf5974922bb899df95220dd7401725d58e3f15ade2dc8602cd5a523b72e5c35d98eac086188d7784f1b83cbda717211ae613be6ded7d122bf0939407dee0bd48cf34edab1ac9115c8dcf010f5bf4140cbdcfb8a7a3dca452c2c3fa4470afa3f78d10bc3185ec69d3cbc6c4ea474298dc9242c6f79469fd4b4fd8b9e76e741dce6a420d7c88b854a5600f75c2dec722ec1bc4b8e7e3faebd541dfb9ec7c85eb37107ae8ec90d16e54fbcbe442f8c72b5716dec1e72542c60421f7d5c0e5e0f6753936dbe03821460803e175bde3521e5e0f6ed37f2f0bdeaa11143933fd7e881f7e1a62aaa5bd7649e34857dee91729e49a372a95277313d8676cdfbfd89593954f2bdbbf492585a149d2c0f835c2368e9eb397fe65eff0e3493d8570661ab0944a16a3689715beb08b5f5b83b1e429b630ad58acf23463373a6ee4f534deb41c82b5414e9fe4abf33eab13600d32ed0be55ca3cd3c0d20b43560b05abd15ed6717011c7ef45bcae4616fc395f6272e71b92b3ab5bbe9450f8871308c9eb4cade12654d31952d2c7cdeaa3e6a8257281a8ea17effb15bb4d1f7274ee71eebbb76e0854911fcc1eb8bb4d7844e1ac6bed663b3ac5df4a83548e8e2000f132f8f1dabf1d56a3419a896266df694cc03c1425ae38bda978ea987f00a910e1720e39efe69ff80c911683b53c01ba11261c02d2ed736bff97f3bc8753706bddbb75c4a2f0b9ffd52f2a5440be9e9d9ffc72f274febc2f5bbdc6f1c776c222051a8c5253d001da063fce4f7f5df35cc19267f18f85230c09fb9badfdbd01d56054594179d53d0c978c7ec7cba528eb79da27fd64e8841fa7a28d380fa6833bcfb69f3f8823342241337ed5ecfcfadf1512720f20d7b184ec4245ff196b89a81f707691e10b24737fe3186b7e4f2d2ab12172e567124f994408ef1e94068fd7fe8275bf489097e480b169c1594b567534a9721e71d5605bdfffb46b7b10c3d5c775186da6b97653107d18ff73c1c4fdfd74725a8ecdfd8c1131d9300eab58f23fe20d3705f2a2bbd1ecddc6d92f1175950c72705f0b8fa5879fd03144ec6c7a720d14843747ed66ea0f822d04f7b2368b2472b8ef426e8d6387d4b4793f9c6ce2883f827ba4d3b98d42e70e973584cc6d2a729e83b8347afefd8220c838cbb49d8a2b2ee5e9ac8c5a9c7c8ba0a2ce2b3dcc6d7e09ee53a1c77212204d59c3127946d590ca2acd81cc24ab1838a8aacacb255ee6884cf5c7ea9776429cded397a8cd63e32d6e2b36076b228909e84ad7c21f7299fbb353636302dd0800118f817cef6932e5b59a6b2f6d3241cafb37e840f32b59b8b62c8f11fc48b2e65827a0bc7f87c798f6fd9c2c73dfe73cd0a2609814723f3d4ec1414fe3195a7f845fc9931da5e233a6488fe9a546a45368866bf5a768990e4f1d709bbc0a6cb66be3eb5a7bfc3648ba0ee0108989e6514510d0dce372c2a6159c1142fd0e67a3b5984d12637888f75bd49913169cc66ad0264a292972c374475b077a12323b8f6ad2975565d37b4040332a86a21ec730730acd40ca05720c21223a5ecc11810a2dd673a1ec6e5531030562d73158dd2366dade2d567273f1e87d0524fd01caf481dd88aca05ecf2c9cbcd6c718391700c0c1c1bbc27220864b205dbdeac9e7d7977570efc07da85e63427254e1ffdc1b672ac78d437220d85d46c4d1cc9add7ffe4d95f3f3f4d68773f186f0e433eb493b0bd954c942d53cb55192237f0e1638a13c61e425a7aad461a6cb878653ebb4e1ce9a8c1b4be7dcd7e1438b147318c05d24e437def035f47cdefe66081a5215bf2c1b19592e1bb4922a46ed600225927e949247c64609138e330d8af086be099fa014566c722050f964ef5b1c18df94a84138992c9d94aebdc26d33f3f131f6f62aadbdb86b96d026269b91ecda8ec09ed5ae39a0393c621deaa6f7f6a5bc0937261c215a721980865a398d402fdc50a8a8a1ddb8956825366dd2497e2eac238f52626c567293b0bbb8cce409a1087111070f8b4a4e584703f4a2e63ac63e9713ebbaba0e249ba3a06e6293babf3853b59dd8dc581fea09ddf90aa8f5a85b52388e11099a6798dddb8cfc709be8f37596dd44e8aa37d060e574226988ed0a6d1c503a14c425fb69af679bc37c117fc19e046e45ddf1927024916030e20d828a9bbc225e90726b877862d6cfd1cd4217d9b93d5af985c8f2479f421a1473b342525fd2653d6c878435e7e6409f8c5f8038ef4bb404198133b147bce3489d8098174bb2449a72150db573a7227d5d6babb7f4297e24c93b79a4f95b05ce283557d1d7df50f472c78becf214d4f9a2f7e664a522a56bfc51294f4e32cfbe9e040879395cab5c72dc5a93b6bbe53d9fc02c04971b3ac622ce8c2df6c3a66783b0dcabed0f83817211feb624e052cce9746d184afaedfe38ae48541c69199d1629d0d087934dee57411f7c666e9de47410c01108cb99031fb2ebdb578acf5acb23f502790561c9729e0c5bbb4655308086749f37b3b683bc007c2862aa8f6f183bb14908e078ca720fe74c48efb5313a581ae87a516e5bdd344f7fa192538177c309836ba14cc272835e880adbe0cabc10f4113f751d25ff2fc9051ba1b86015b017b4c8d9c5a4424889ff43d607261cecbb8cb5e49054a502562e6ff31ce621092b5ee0fd8727126a7fd4b1aaea42963684209f0a00948738d51ec6b642033192229c18d94c1872a968516d1b63e6a5f5c3e78a6fda1b67494800b73014fd6ad0213b94cd013855f002d53d7cad682dc6f8abada3320f8853a9076d3e0405d2b280620c6f9e677241aaeebfd0cf4dbf76a9ca834f70f07f95acade691f2b8dcbe5f18fae11cf272695ecd0342b5079f19fa642bc348587bc23dc73814cf091b7ca444f714365e1ffe15c5dcd6a99f83a1f0b8bf4218b5a801a6254be012894e4040a5e53254d472b533fdc871092d8e3cd6574e07ee776aac78a24965b4c3b12e8be717db21e7726693b159d9cb1133a80b10399d37333f98ee0243188d0b9fcfb5b49de6d12772a191c88d8618184b6cbe53e956050607d9c0498f541663cdc2d50c34ba38d370af46b28ab725963ce1ad37e094d6ae39965f5e90a1ac9bb34a5ff76d9667a41423938690cb43e5ab46669e0e1f651a1029704e9fc36da5480aa9634d1a7f8f72235be777a2fe092e384ed7e73fffce1a9c3692420fa6570e503913bb2e82717207889dc42f945a565658b3852be95417ee02fecc5f933bb732334462ad22d418e0f7c2bf88ea3eb114b262f8aef589b3de0ec9b3bb86b08dc5faf1ec73c0b0070e61cbf7b62bdf6937cb7f369dd17dc2d51d74ae602dfc595441eccb647d604b71da86100aab41fb20e8a5ce2c2653fdd1de92c67f1cd802eaa27508ae9909720cee527ae4de1af35768dde729f046dd34a48225e8734080ddb213e89fd9f372e3cf4abee1ea924fc4477ef5cbfaab315f458283feb731806aae76a20be9f16ed579dd61f0223d88532e7f5eadb1cbae69155565e2d7617839781d260d728f635c19e6d57f69207dbe6092de41c4a920a6bf8b9373d88640938bf4fed6debe72d90080fc46ee1ca425c3a1eca368b248bdd8319c4ebe549a25c7d6f6212d5072bffb0277a24560f4242d3f2e9009095c5606f81abbf53eb708950ce4e1575272fcf5950b422e1e91d03f6a1c4c618e83744492674c5c324dd6724a82418525723acbf4829bdb1d39445b2a7691e5e058ba798f3b65ce0f7d749b92ee4d33bb1419d25a2d56fb22e0a81df7c65a167a4887fc7bb1258877c101480cb7996bd56d1fd823ab8d8c56bcff383651adc5c6eca34a80079190cbe808dd921cc716ce4b5955fb3b74ae230025f3a5fa1de0f532460b4a6373b9b39954f22c4bac5c067256b492b97c7eb141cf545af231c0bf77010bdc07535bd4f5befef553a48e9a248a22fafa0b5f81ec53d5fd8ae6ba03af0e1b58df1d443e32e32dd19ed9a8ed72c160b70025bb22a59bbf8c095b919e0e8581d590dafda4e1f1b878a5719c1d56627078dc9830e951dce037dcad560a4c6f721ffb8072a32c8a55cdc7605fbc724d8b95aa20eca8ff2e61df9ff753149c5a827298e7ffb095e3d69103d21ed972c8785e12a84fb8cd241fe30f1e881f21570feaf571c52e615a48eb8d2ea75a3628ce5a9e5992a8bd33241b1c371e5fb3f1f1e19d749e57a65dff340e2ac4241ff52cdb3f37f7ad902dce733ae3cd7fe4b44a5aafd6da1ddb44486773b01151727b8002b7c274c3cb2adff410e48dee589d7a77890cf86e1516491496e3b2e172d523d3defb70d4db6de20cb1bb74a6a1420f201917dee5c57509d0e0e6056c08ea36ff13194d630207585e671fb2caa558c1e806eccb0a2f99db9ac8050c7f204d1efc8898111f61d9f1b79ae484358b691a91e487b57459974e9717f9dd4f537e6c04ac2181b757b9976520b81f89767737bae49a76f5413c1a9c551fad4772a2da12a18b789d70ff57de29cdd6486a596c09ad3f9dbeed875be8da4cedcd72f5ee41566b0a30e61c4e2f6d5cd3b4038e09d58fd4426619196e00664bc4080ef01c25f4fcaf4993219b148006cb690893475f0c168207a5964f548e6f2e3872ec8a7917ea54cca0d202062b67fccbc1d6099acb9a0730b3b5613afa3479027292bd7c6bf1ec328447e3036bc41f80cc8091f890cf9872f83f7d88ce84f77072663fe54957b9e5c799727c001082a0857c3ca135b69944dc6efee557d9712b72a82d5ce4d8b086bfbb852b6425efca3a743bee9f8be000e81f477fb1915ba9728585b0de0bec3e6d049465b825684651846be538a4856a77a33d4b5274a5bc72e98e087e2dd0e4106ce04d0bc0d0f72c573ee032cf7ed58865dfc1b65430154b46b85a3a13ea964398280f407574629e62deca9ff24e033c07b4f5691cb601726bbc8d0e0bc6debc6d0e6e217393b0bf1141ce8bfcf15316e260e9188521b30ed7aa52e8495411380cfb877ed3593bf53360bd3a803f2d2d1e10d65111ae8d2d65bf3b9bce9e8e42173d5b235824dc2b385135cf62b4642260d4de5f6a930272da706d3f46b0f2293e08a809a6c73d88f103984242dbbcd2f9e09e2d6b3a9a72c9d9760eec883fe3fca0d341711e1bae236f65a47348456ac832eec40741cc134b173461a84c724070fa3b9d6693bf5d5ce945b1ad75db8e6a7e2692f788106b0c778f254f6dae4b88ec01a48b1d19ff4b73d29a8080cd52004553ad720f6c72e298f04083b83ff8304694a179d149c4eef42cb07817fbd7f03ac80c684c14728cb24ddb29e767d6dbf51ee6cde7154e8e9af71cbd179af20b4f674cbe7cc6722f9e8d9e4f617bdd4926e8facdcf2b7262aa58fc06bcd5b822f6753b1959937254cca5a8332c5742aabd421527988ac97ffd377cd507a7d8e12f124a81bc2f4d1cc7cec1174725d5fac9c5928634d23ebeed1baa9d0064a9a75ffb5db3a6cd21149c1f8105c4a99cc0ad1137b26bd24074e9a0f07698cf7c6cfe6ca0e47df9601cd356d634846efabdbe23de4333007fa03c0891ea273669721262a7717ef672981838e5c2f99b1283eac145e01460dff5e39f6b382d9f75c6170f03f2f5c37283f4188b4d240e59498088a86a17280364c5e229c90f9cf0f51d621b6ca00f5ed32dc0d09e330fdaecd787989099477b977710cbfda73e485f0597f0699c3d7206cc72129e9f47c2c976dcb3e0f153bfef5f47cce427cbaafaccbc16f6b2826993237a9065366e4ad3153e9b37a09c776fc89c2ea07e9896a210f3da4ee6182202d7d40a8daeba19c867d6a99919312efc69acf362243ddc019f192815c4e2721d5a7292cd705190ed02040bebfe3e512dc0c95301232e4996ff39ab47dfed724777eb1beced50845efaa9ff8feb0f2c245a4f0fc970d51f24e686188e2add7237617efd4b25b8e99c371df953689ba0a72c7af67da4febd5156a000af4d16196f5d9f90b0f7e8c37358ac34c3720851490784f243567f4e0bb3fb8a4d8e583618e89fb572632d37c30defc5f920cf6ccefbcab3b03c2a06ea4354a930406672947290f5b344c74d9955f06ff24f5b3ecef376965db4e7dcb3ed90f64f225a4695e3db47e16bae138f940f1d423effdcbf5aab7977558ec3820714c16b8dc072158b78f849fd63a67b2ce1bae8df86a1697a996c8b306c6b71399f038cf72e72e920b622b0ca0eb0ad5c4be367671a994f3522a775f6a73d17fc5b0821493a721e66cc6f440a7c18103eed90b4fbf5c663a67e1ee5723d2f6c20618785f2f06cf28daa4a69c47638abca73c85a368e388273d30786ec8fdd71d51d342f861514e8cacd0291b90dd496c8574f46b0b0d242ccc1d0fffa9d233239289d9fd4f072db5eb986fbad601538d6901bb63de72e5a3f68dcbab61be069ae0f5e5c729072c1eb3bbdcaace488dbd9ae7aea25cd7a309d12354212eb62467c0e5370eed4725f6537ea21a28cfae347d5b29afc0017f1a9e581832df32c52c0d93208831c3e3ef63af8291f5d9d30a244fe55ab3635333bc4f1a950ce78947eab5edd5b72726f03f6d5bbdb04003d512f83c36c326baed9cf7d2d420fd1fb021bcfcdde13602ec6f333cca7504d4125536c4c47ef70bde29d87b2220bac8b7685df080707721a9b72c21cd61abdf56fe5baaf275937365fe7f8646e9f03694e3aa88c13881f9e793e71bb0f99e8bb88b293d6abb7ae21f43b741d730e81938806f2c7b818723b2649c330233fe590e3bcb6293b6d2a63d8afdf640b736201108890931dfd7258a4f0b86984640f9c9b2a3fbf9504d872998381d6d7978e4ae5cfc5776b3172a01ef5e4a2ca312d45a6aebe889963d580d539f2b2de2a33276b224dc94cd4722f039d71848176ffbb7b8f5196244d2d13ef54d02f380945d9356c18fdec5b2e3b074e968ee2ed0d39bc347eb41f9d09e7c79a7563b08c86b43be07b32257f72df425885c24acc6b2619a711481812492a56bd58e7268d5985134d1700cde66df53b9b77e820588ee7b2171a51122aadaba3b038401907a339556f733a6b7472b70e0ff66ea68cfa668819e76869619a1adbf756d5f8edf4fdd6224efb1b15416d660b024751b1a4a651005a9d52c0fb31d3342e53e49b22121d94fa3e1b1d4f6263c7185cfa003ab3a66aa5d139b30b5ee817913eb6a6aa564b5ae0262f5672c74e966c34f4ef824f5570e7985af84be1a7c6d63bfd5794a9e1de91a3b9232502be82636e8566791fa22a8fc3b416bcd06f73192bee0fa263ad79828541d3725d664c0af90fb1c76fa99bfc956fb781efecac546dd271a00daf4c77d389a62264b2fdad67af6c59acf853886077b74f13fec958585a85faba65c54491f512721973c1ab28650b00bc7943bdfc125942639c9381c8d02069ba648188a4eeb672ec6b8897a9d4025f6acec2f84ba93f4525963912547d0036c477e062db5e553e3370ec4ff82fa3ba5e741defe3f392399e3f7f6781a177aa7cbf5c2c10aea8458d4aba745d21b90a49d3704d80f592d5ecffc8fb737a1ec5fef37ebb1958d95068e5855475c03c79fe3a487b8027a6d9c3b6d12f2f07c550e1ed1c49b61ed925fae5941b5b401d61e60ad7e07ef3af97e002c4d27b797d2abcc4541e0125406d271636190f892a7d73c5bb1edc11e94af77f61608e441ed3f61f5225bffc751fc11202305e28d2d876f0d22f0d46e7ed76854da7bb07fbcd8e8bf47934f9dc1f8bade985328fe9e96da4cf4a0ebce414b863d22dbd8631d5281e419b84c50758845f29a0ad88c45693e849f02bb0de56aea9d4f1e1b82d94f1d3e0f59ac73572fc1f79e0539ecb810fae847fe07241212e16bcf599b3026be8e571c059f4257162b686d51a5a5018739c85cd928ad3967b2c535c3209bbb702a310160799c31e8b936b7dfe45c72bbb328c9fea5ab229f1b18e26486f9e5ed0a5515b112fc4722a382e26441a38d076dbb476c68c7534fb06f8b7984f5fbbfb611766eef2801f325b65ffeb3b1254101e103d71c84f9a5b0c172ca34bc14b6bb9f095cda4d172ab6df11b68bdcda2d334538105b94106f80f299ce2dcc1c8938d97b5367eca72f9e45d959e4b2b738da6b9f47b7254565445897e2b20b48578092e96cc8d7e721d002eb02da0d50be661c202ef43b73efc2ea88faaad351ffafcb7add251346017e887b308cbf5dc74f844d0b0f289944a4251a703970577cc4af7246d848f6bc16b94a55c96903e988d8f32565d2f4d62d80565b60fa75e47e58b82541598463013e09322b6889bec966f47be6b619e5b0c0bb0bc9c449a633b4cf9f0f40872f3a13ce85501c93358a334484df7094df5f98a2ce1b756650c0483051770cf6f2302391535f6b0fe38a9db278b5a24c18c95afd9ec6998adae476c731b1c3947faaf37debf803e178709c9a722405e731ebe2683f2b8045f5bdc75799edc4f5a6a8cb76263e55bc08cffea650944b01265115aa0322bc48229e2c805c931d0722cff18819095447bedf4b599d99bd0f1de5a6a79e69310e678001a45a2b3f11c8ca7d4fcfe187e9e1701b3e1ac31188a6436190f6c5d8203cb31f8667841354beb377b476bb4dcb95a546b49dcb68c852b8c27a52d0b0520e5becd68382dc6724ee2de771c8e9f6abc612c8a20e75caab6eef5ea74f11a2f4ca61de91c525c13620a470e2e86d71cbfa118019617b4680e1753074246c53f826e4d2622d846565f54b26ca0843a115255c4ae2a785f9eb1e22fa195600bea5c52f95ee70ef9725d598b67acad37cabe8007ca76d6a13674c9a58f48c0a272882379cd6db86c1af36c67d8e3d89c4c08d0657d41a849c171f7b3c431b7b62c55827ab4ace75f38860dec15c33d700713a54f8dfdfe96c47b56e1b376bc4d8b789062bb5624132c0436deb472e2701d4ab6073a25058895f35bff1ee5356105e1dc8e1568ef8a6b6d01e34fcf557d3418cd3f2f0708042eafd5dfa855026fdffb0abb70103fac3db75d67a42f11a663e7605b06c13dde5a2c71ac0b844a5e822e450385b5aa2172a9641707a00482985b7e69630aebd9ef97acda92d4ff63fce34f3841ec450f720637533e13e76697e9f093621e3d4f15e7c4a3eb30294bfe95d37732ef6bd272102c74842282df07fdcda7537967ff5a44437d50e7aa04e206668562f11ee661909cdd000ca55427270c33432093713349d0c252cc605c0b39c4a3c896a6707262b8913ea3edff8a760aa735b717453d6e3b6c635c79ead6de811aa26890e4726702121206a5050c5d81c7e1b5a9865c77a513286a4f6e7486772fc9ba92674e76af4ec6a79ea6c742de7df3d6b05fb934e3dea66dd859053b2560d95f81f172c5e44c690b3b47629903f0171ec40dc28b76d35aaa68cd9e326b0e22cad84e096eab53a82c6d7c2bc145e13f18ebf4288f851cb25ae0821d99c601577576cd724894ca3e7be2437567f5d8d947a8415d01670be3361e180374cf46a01529486fa014c5fdfecf914b3ba88ca4ad1919a2536efe6c934b3909681628827ba6eb728a898afa9a1822266154d3863f0a1352cda8ed88c0b193ce25cdc3d445239a0df43ed910faf8152f621a2ad7bb3acf728eb7d77abb5008f0b033890bf53c3101f71744ed2da1976c529cce483b258d455f21e145887d42c2bcad57ee2ccc012e77cb7ec9a7f457c4376bd3f70329911899714b3a3dc3ec1931aab626755ab9728c5c432e0157e83b96e3efec0fcd24413e548aeaf89187302e0107257ca8da728d437949e970a8b999ec08bc6db933d07ba2e92e566ec062234d29e52ad8cf72989462f4655a59275e7d6617bbcadc4b8ed69a88cb67b325ca1bbd881bf8be72cc4fea8c4f4cdacf40edf748888c79e856359a8316117409bca230d1e9db8272f36ce5b94443182b3f3baebc92d0d9d8988d0a42d130a0f1491b36ef26e5c37254e8dff030253fa174d6a4a21d930cac857e2c0904c4e80713316497193899728ce84606532f457b19d71702a25d93972cb3c8e057c0477259f7323cbb2d9e459ae8c40e0b63501d40a4220f32a9f5a2d06eb52fdddea6fdd86c8ca3d9df1072e36bb27992facd828faccbb61cdf626af0252821e14602ea8264cd3b3930cc61911c43055eb0bcf652589355505418e6112312dbe846e08bf0ba46c46ffcb86d88d16abef21259023f54a11f1518ec5125acbe8d67526930439654a55c312f7217f81fcb7614adc5132d636dac86e210bf2c0560fa8065dbefbd93967407d41f750947702812485361a34f37c4b16a29cc305c301f0aa0c763b3c85e313fc951bd517a07e13bcc40615a163922c00aa8508dd0a8f0f6f263bde99bdbb531e938872a784f8682efc95e842da8320a1f8782167dce284d5022cb184822d743db3b51dd2d7e40f054b8324ff54c2bd3d879c5f21f8a7a97fd4d2a018d8b35f47b72005c7fac6b21d4d2605ac5b9dd4c5b419d1a4a6539b86f6defeb5a2ecbf9ba726bd54ff4cafe9c95f5f75d0504c361869cb2a88cfcc7f0b37a9254457be27342c3fae86032156c4821f7963cfa4bf83460cf9a620cc8202f41589b9c1445905bc018e50f124b36484742125939e972e63d75ca0d47ca723ff63b1deb01b50b54dab26591f9b0ba6232ef6a842d403a31ebea61890aabfa7033becf9c21fb3332f291dc9c642858a7b8146b4be6917ff79e46dcc349733f9b4b6e2e78036b1f2b5464110a73e4b7c156e63b733edf5c13f815ead3f7cec251583ac3a10ae61d72b5b5cfc980b803635fad17892daaab6947099b4930f722d5bb73e08c37c674729b421024176be49ba7b3e8ee7dda0db9ad63eb94c4f7e518c8da9de5dfda1a52a24685cb4059fc1b62dc89755aff0bf33cce7348dd4320717e12511054cb4e72ef807be625c9149bc177b142b70faa34034819852d6c9a49d7518a21fee30c728b7e6d0f6ec2afc1ac2ee6a89cc30c2ff2e8cb1043a9e49e6c22e818c89e496fadab362a6c343fc3c2d6f034b65ccc82f031201a9510c40f09c9538e70c25a72a9b44c6467d79c765f0f89cdeee3ac4309d7d1b8e334bd539a61ae3e81388104d67b76518f64dbf6c671ce01b3534d0cfbb2985bdec6069bce5564723362397256f9a1f1a6f38b433bf7c3aaec16ef2e3e36d3b98e5e6b9e6b5da9a477ebd572619c9480efb1dd3a131dd22e76a8abbbb251beadcf7f15f09a1b20993cf2a572a84b5157ee2faf45c7661250516c2aeebafa4abb9d0fd7d24e84dfc62ce456727b8f3ad89f8a005606cfbc4c6f6d1d18b6acde9655109a0fbccbed5a9f406372f8d86baf0cedfad86dd321172d4bc29063d589551ac0968c857bb1332650cd563bcfb4895bfd84089ef6f35505f41ef65903fb423de49db3abcf78e0bd352172c0694a4205ae18f430884cf1fb8186204b940bc01c605fd11669d735cd042437af7af0cf9221217c08e675e2d605a076fa2e826b25bc5551a03150d0e8f8bd725d6455c0e51455875cfab8be92fb230d33d0f39cb6b8c5800e200993be939c1688949e6c451f3ce9be1dc90be46b932655fa47fd74e7aae02310d5070c3f450bfee60d9c752c80c9a17731831fbefcaa604272f5fe124f81c22c85eb379b98ba0200001f5edc6f1efb165d45a654798d4baaa50e3b4d24182913aef5110a15580ebaad2d7e190a13dca10f7da04d5a332c9b1175ad9a414c70516c8da95269f7d4cad470db0d5260ee4b4034bcf9cfb83bf8704ea7c524db6a23aa31fcb91f77f8ac0372db9027b4ed35c505c8211ae0d8a848424edab47c9fa3109c3faa991f40853435e7888c0720ca27e538deae8b73035c13ded1b8a66f58c07e8a08016565ce644ce4a931cd4d18c8da5be36105fe0ffa68c604cc19f2a236b44abdedc029005d72352d112134dc5c9a41d40cd9e43923e60fa29b004071f70c5d53ce667362c37233a64ce3e1dee150d48900fe17af1054564929793d7cc80b2607c4992c0eae72d4d50e8ce8391ba84e9c34defa5c3a836d303b916209981b5f6b11c16a19d4728e07a12d4345dde47f2b9e41d37cbc2b1dcebb4793e5729c951abc346b4bcb72cdf66ec39eb6050a912095473e2220fb95c640d2bc9b04f6ee245b5f8f9edb7242d7c3cfffc0a1c455b74997380d6b739af42a0f7a281e58ff6862e1465066720c40a87f2df90399ba3d0f59e4b3b4ee721eabe8836e442bd523f44f7926687232dc9a1f7cf522a7112607896275ac8d1ecf41f8cfdb81a02d35b67ce56d772bcfdbc5e5ca599651b49a29d716aca76b2ac4c15a8e11e850891635556cf1a949914d98b8bc3a7a916d218778c2a5535f19b02d09d3b2dcd2f304712bdfbe7c727b2990aecfa4771382f515ecdfbabdfcf424986eb6dce402dd6c130502de825277e405a0d51290c3f6a4bc073dd7558c1cf49ebe0d5d4e795cabbd866da8234b5d9b02eb278ad4f3f695c011cb3270aae67058fca43d9082fbdc55140ce9957229d30fc3e9d781c9f85b1f5173c060ec5ce00c12aad20c1b6c3904c3310b15291bede2d1e1c33fca170773b6218dd6c9562a9ae7811858335b04c105e2f89472f24192a261bbeb04a7f0e9662179b615da7df5ae18af5d9d054a937b46ee74722c6eae6d20dfa2eb93b48e5faa65894c0c1791ac0250585aff41ed2eadd75572c57f14cad6fa70094f72ab545c6543132e7af54975a32c1b8b2df9044b06805245b81788c204cc0e7e644b543936f1c578bf122801854bb03833ed911af19c6f6c62d07c19a7c0438625ddcf9f86dae1821d97e8e6a415ee5680acd6a698f97263f46c428b7d3a3b1bcc265b24a64a60f008f920666cd8a75becd5275522a3369144f1a060e33f7b87bb61d477f508b6f45d323c0b7e3ba1fe429b8c14ba9c278bbbba38b3cf30cd67a00f4376f12dc72563497c95094bd8f60b52f0981c5a36525224664cb629fff52a8e90c31ac5fd5a5bf78e244bbc9271bfe69138626672a59b534c83ebbab685c3aaf62686e8efcbb15eb634789a2ee8f67bcc9498b07282ab6f1ce6d1ef428d7e89e7d633660fa43af6e637d37a0d60fc07074213d272e8cd6c32026fecb8838607709db8c56975a377633a5b7bee40033f5948006813a3f5b66a61c1bdad868b19516a47316eb564de7166e36166a3fdbbfcd1637472b195704b3f04557a3b2a194613df4e98dbf3610d9ef6da7d06d2b96943b4fe1020294839510acdd66460bd5ab06a06576cf4bc5e640cab90f2b84e24db02ce72ed14cfc813ebc7f29ad244c311b5da573697f3cb7816bb37d559ac7e2f9334044156dfc73b193c0b2aeb219f4dc70dd900ab23f90f150d2fe61add69a4c96e722c8599d8993005480719d307693c171ebcc262e8b00891fefd22b7115849e672e3dbcc393d182b3a669a3e0882079a7f040a2065118a18136338081f2fdf07729c5fe1a6ee5a6c9db80e5cc18ff1fffa933bee26a128ab37aa30175930b58272fa05b9f74117dc89296e08ebbc810e573e842fa704ed3dc976494ef1b732d643665cf54b82c19e54252731c1fe4dd80d3fcdb77fb6ee5e7e94593467510767727e422161f0f47a6308e97f386b7e01502703c0d19783b2231aa6b68e26fa5872506adaaa2da6acfacb6457095eee69c7145c41d886b677ec2c54a1eaf780a762e46e4d49a077e5f69f1a887be90a5efb34a165aacd8193af56a6125bdcd065725b272ce254d6d36ec15732538a09ab52aaa4975ccc5bc58d4be1478b47a5937285ea673ba8c6d947cabd6cec3d7afe25cda93e252375d44432ea70a6a7249b72fe343fcc1e9b73bb57e91671e90273f0d09e757980e490d09628f5859af124722355dca235b6bb28b997b4bd3cc302b196e4bfc2bd5c102e056bc2d3d621d31fee2102b9ccb882d38e679e97b1c6ba16748d246a2b0c4439ceb7ec236e445333ba747fe4b9bd46fd8a12397779d5c4a1168e3a3b682beb70ff26017fd3df8e7289de015fb5119feaa55c0f25ddefcafb36c28d3734a51e3b35080114d492c172c4566c8ba4cdc1595ea8458ef623fecb7e789110c70babb474b1bfa8aaea9145fa5d9ec2b46979ceb2e92873bf6b663914bf7db4545367861cef8d2fe89963726c47796cd0a676c73b151e79265b5cb7b3b9c89661f1bc0d9dfe68836f3fb967610dd7ee293b8631e3db4cc5bb67d715d81b5bdef0406eb3b6043e80fa97ce5ae657603a9e0099f1beda0dd874ebb1397ee23e2871d3c6be2313721f96cde572d9a0265f608d0382b2bf6bd48819224d128fa7055ed7cdbdf747b9889183c3723b4b93e1adcf4eb3abb003c6587f8040768fb84103d96245f93895cfbb55ae0e145b5b9a060719e42b9f6ce2289d83cdde0c756f146cf50a0dc4728223ddbf72749ceaeef1ab60aceb4242d5fa90e56ee27fc3e270dae765134badc893743272d21b344e5da58a9a1c4066d830f266bf2defa5b0905abaec33e211f190e43d72089fb6a59d780a6505a3a9af805bb13e1fa071ee708ee03363bb7633b81abb72970d79ef4a8cc82510319d3142bb26af5a3d317813d4fcc8daaba5e7593a9472608ec95fc1dfca96f54a998f2df3c165395f1fe7e33bc2d0bef66bd96bbee924d325d5fc9cedf20ead63f87245964da1e210fdaace5de8c38dcba5a8f176a74cfbd2978ac15442df586acae1a92afdddf1767917413094a5110aa72c423083728e1489d2af6f7f4f16a0a5907cb34f34aabf2ff5e4a3d50298bed0744f16c4724ca3aa91dc4ce506a048ebced61ed4da7bd3f1178ebe2df0fca350da37b0840daa5dbcba8f87ed5ba9d29ecee8a917be779ad48c8d2dc887d1f7e00f5e5803726e6c98a8808e5075c868b552fd70c17066cd4026f66f23e6a466ec52d34b2d7269d8a297a0077390afc081aca8c174a7e2fbec1e05a6de69c565b9564c371172110da49b7c3431f20ed3961e199589cd5b91558b6c7933234f84ccd1b8029172dec8e2479a7f7bca72667cabdea50c5ca303b6c6a083c41ddf47cbd527122172eefdf29adf94e6f8c493b68ed98a735ecb9ade7121bf74126c4d7718814e3d72bba5d0f52ff5e2cb365016b815aa5033805ff6276a2c0c0a930f5e5470889b7210835354b109906346f6b5ed7f16e016570dada7bdfd34a86b2e6f79b7b6155ac6105eabf612c3de16b0787c14abad6e324bcb53852bd3d985336bfda6df2072af04dfa4e4b2802f5a4ee95e72afb00737ea82655dd10ac8693a5c585fad076b0a06e01b212ebec306dffb72a7cc70cadf35ba61613ba33db2e7808baf8544720e21125f4e9bc89b9e913c0c2a8fd76c04fb7b3536a90e3b6cad633df88e5f72956b71386a2db6a7de42a037b3ae6c8e36260fe3a01130b1107bd0d6e87fda7215bfef9bff59b631491fbe04477a4aa470e31fa2b9ec3661b823bbcc0ae3b372a8090f54647ee23fd8242f725a3f011832d17c2b3e522ef0068f7ed38b082c724f064df38e1ce7a9333580941c6dc70e0704114a4953dd8bb831b587a9636e225c1446814cb9ee48f15c5cf35d157b351c69c4e87cbb0411a2e64bfb816c42721b757e7dece2f66166b12957fb36f3c52d27bf02b26829c9ee1159a08896a372b188ac114ddee9b5f1822e15306be08b2b2d0ed7a8c9e1a61259cfe06a2eea7240d419d170e373ab3e984faf94bd995dbf55c30866478fa1296549949806d172556fde5ff1217117905f6652f71a9f93bf78a60369130a1fcba04f58b5e92372e0ee17dde0a8f75787a68e8a866f78c04dcdd886f637c2911a1ce3bcababd7455c0f3ea5b38d13904720fa82fca5c5825c3972635bde1a5ffb0b4e5261db6872bc5836eed4839f3f35324e6d4e2ff54b9314261832a9d9a1c09fa1ae516a7b7287a462ce7700e700879d289394c8f16f73352c6a99670fdd7f19367253a77e7242f847987ab477386c6165e8f005b2f411fa65754a50dec7063f60d3189d680b5e382be55f93cfecdb94f0c23225667a8f16ae26749bcb9fbb4444b2ab0ffd20e902bb646c781436378e7eb618f72478ceadf7fa84b6c5033a0fcb125014e47295b15921c614ca400477987ff5e79685555b1b2169bde894e023742035f08f72d49d6dbb041aa2f25c37450993defd82c376142854cf1e4cd212735e15689864fa8f9b155453ab388c621ad9ccffc7212bb9562efbe0b4a1be1aafed1af28572253535a96e403eff9b3d7cd4ea6e05b70cafd3cc1607ec8d1213846d8fe24d713b4467e767c6f25bd9ea13e3566b3b838b15ba5363fb9505514d461ae2d88c6371504e00371e7b50031ddd0a4b2b8602b63fdb58b1a4e447a4f95485074b9d7221c0b62085c1d149a55ad735276fcdc99bad61b4f2e5e50a6cd7a92fe19afe725dd53d9d6c834381c2599af133612266b8e890153428c332a879b56e7576f74b94c38b32131b33faff1f9de0ad421079523de0bf9366e6f8cebb797e64ce377295655ccce16bf4af0348b0ee9f57c508895a14e4ba9efc05e5596b9c95c93957900c1bd92089d79d0baf0ff6be524d6aa50d2043758017190474215c179e6a72fa3e7b3225ff78a7655571d62e973aa7f8c3b45b8a060f344974a22505e460175c199877ac789bb4f8ca2d1aaffccc6a5b5805f72cc1e90926cc7175e2d06b72a5793c4769d3c20abe8bb5424e78d65f1844b9b5462604f3677df4417511915e960852216027eb0661250390fb0cdc315f95b168e2355cbd37985e4b3f06fc721dff43ea5485c49522f6f34e42da42091d7860ec12caf0a29f2b7595798c8f35fe87a6cfe218aaebc7343b7b429bdb7561c28947871b97b0e1099dd6f999a059d63feb26acc8996eae3c212d3ba85b6574499cf78cce91537abe8a9ff6fa5945b7e0c6308b87b110fd85f1c9e1e31cc7114c90d67401808130516653b74446341b3f6f2b2046eced08d00e370c2949a48165beb6204b746c8031f8e3e077a641ff12cd0fc4346d53149a47d92d89d729756a2ff79643df0daa6956b3ccd1f4720b11f301e86346284c48b0a70390c71f4b0fdeab4eb04bc50fa36498e37b0872387d61d3a379c229ef7ba92ba43ea02d1b5c7eb5dc9c0e20968da67d7737e134fc0701b6bc656a6900c5dfb8f6d309f4bef873b76b584976de1ddc5bfaa5ae2e89c77adc01ca590431309a6e9c61c6b6dd163b8d7318223e457c47f94b709072c486d50c78b940c4cab811a6fecccacbfaaf01c040acd093e58e90c47df6282915c0efcaf9acb5d4026c642c93b12bab7f4bb90b0e92f6d633d0a16112fb0172b3e916c9a62656ada65af186d967f9b295f374578c4b2327b24845450dd80572ba15bb8c04dcc73502661b22cd2aba8de821ce1dcab313a5d148643ff72eb5729408e9a9ef9f7ce8efc013b7ce928eea62c59040afe6b570ef1c8b8b6db6c972f921cfe6e1d44bf482a317378cd31d23ef35c90c253d83735e90a64ae9b80c2d895e5ecbe35c07d73433d630ddaa875da23e1668faa37718043713d9a99e4172a898076d5db3accc8058e135e0c8707af2cd08a0b8b25617747836a04f1acc721c9b86bb682ae88b127e6834cda66997fa75e26278f258fa613f32b0e36a64720ce4e4f2889de19754b50a61d440b7b22d59459182206ebae6852c4405b1281bf9374727b432eca4544dab59e17381d17f7033b2ad90bbd40e6c07b0e92733610f0401229a05e83340b91ac302d3c7e4894ea1d495793f978b751c22c5e62e72c24f3bf576b560c3b1891dbce5b518ae3c04542c916e88f815603247423d96723a1538af12f0e21b35ecfa7bd4eae35d5407d5f6cc618597b33a23dc61b212729c9e1794de23c3b0b288331ae026e76c987e1d796f5a22cc9508e1976d873a727e5b975cd700dbbe1e04e4b79de8a2c3f36d5ca955369a37169935d0a5cfeb034601ff5cadd33a05e372c5cbc7a6956dcb8c4901fd0c31cff2d0bbc0e40c851c0ba924c5feb8b3372e71c86d95779149f92bb4b67029d2ac6a9e1d9907e2343d2a6e8fc1bff8e20589528e3893255e41383c7532cf4f5d892850d77f52810772e9717310d5327b6e243e8395c02ec32029e7d9deb5b913dff3cdb237681116232290598bca65af8f6dfb4b00e7a59e422199c0da318eda9430a3c204e7963472eabaceca13ef6b4de82f55eacaf44e69652c6fe5afa0704b0988e6db0f40e51cba0286eebfab5224e2b2178fede13c1239c5575366402184f35c972ae3e253722a7c56a126eb86fc84e88ae4144e73019531b1efacdcbbf2a1bb488e98ce7f68f656e2dd2743ae1bc2dfc630a05f3479b2ffbd08f8767f6a3692df29b5b9270b52f72e519adc8017611c68a6086d6cb19381d11a171a23d5466f586688c200726db1ecafdfaeeffa90ccc7c231f857370f5c738404a7694154969978ce3c5c05d40821262aafd05cbf6b887c1a467978818505fd743811b024d9b24fef8b047290e2d03080138b2303ec41c32b6421e5ae317adecee73d9ca3eb9035358b434e6537037e44937c0e8f3caa15d8b7ae73ec65c076871f29b93168698b830f7472aad88bb04f549b806abd779cc52aa892c7845dd387d8a4ac3a044cd8cb1c0972cffbc8c7625149b5984a5214534332c6cb3bbbee338af2a458040fca36c7b5725a7adc4af5c87da3affccace17f1ffe092d7eddf213bddbdee28ebc53459a5726d8efcb775e2430424166f1ba29e08ffa73cb3f08f91a2ea7298a0b4ad5d4e1d0f58b09191641b72babf8c400540d6c1e2cf81b9ff03f3a0b3565bab8e88622ce24951490970558cb3feab18358ce9dce36de82044acada40d9de74d4b603940c7e8eaf0613cda9d932954745d999c4b3c7cf97272ed95dcbd900962bea0fe131c346aa0606b0a00bd55e241b3bae4c7c1591e5db09f579e716c655c9ab43d72b66ca6d8d9f8e3ba2e1be8b99802c601ed12c5ecea50a587b1dc57ceadaa4772577f185d97993f91107d04593fb80deaf0800837beec3aec12bd5b72d4d35759da19ef13591b301a4d736e3869aa4e7968d1292634e863feb6432440842312689109b5f7843032baf9825032556c97b7cddedf42232014783be94215f571303af470b2bea60d67affb56d421ea752f43a2150a319f1314ea32bfbbc9ea212a72dc6472c564cd79ff93061cf7a60cbfbba78540b074bdd78d05570ba211a9fb724ee27db5fb452ef7e5f423641b85f3361c91edbdec6b4951164f0f97a96c397251b8deb87a1c1f1abcc1651fc01ca4a6550b1abb0cb1ea55018d52b55687b07296ce4cdb742522e6f2e915eeb98856b14fc3649a0b8784a8af735a67a2f4eb72662df9fec8605cf8c87200a5c029a110c133bc04ed3362487b20b7caf8623d13b135b791a346803e92f048e04918cca488604fecc9446312e49c6141ce4b01727459ce47864a4016a9f8ae4d38c3bf8085470cd7ba1407c1589ce88541ed9f72bda7b2587a40bd3da21cef24977502f90b7c024a2da03a696e48609bea5f367283bde32c482bb6761a1bb31fea3d80971d5fbb448db133959b185d78a134e57253ad24b26eb908e2f2685c9c287139860da49f1b797a159d3f2b98a4917cad72a7fd4d0c7c65b446e85a3f230832506314271594ffde8b9bea86f537b8419b0386ee599075ea27402bd9b82e5fdf7fba27911aefbfff2cab95208162048f9572b05f16a9637ffb207f07aac763c7224b8fa0284e04ef6c1c36f696b437471a72231494805e2bbf6fa0ff14f2370bb17157929fbb77a9865b134d2c3fea04277259f6cc4ec933af3dd9d53863b4250547021cb8ac1ef29afc54c114ba988c7b72c6828c32a0544292b04c9ea321484921dc914429a3d3f71bf665133b53ba375f3fd6da5129e1fc7098bf26694207a5aa69428c3f580129107f492c674f8ca9724d5695ce56e6560821466c71c06ee437870a4dce5961f1554ec22cc2a4719f65408e224d298ab3188092f468a75d7e180c02601ab6d2650526bf6abc3f0c1472be7d3a252082f4ab7f8957d8a4507679fde24fd2d55c87e2f800cacae96b55721ff6ee5a725e9e7bed58cb2474c5412a5bcccf6ee72adf9f508a319a894adc726ba443f971741619bdb8d9db9846425dd7fd6f457724cc70872c16ff94487864c5b248798f16c43e79399cd1daf377c1fded4a30f27b4fbd51871c3dba3f0f1d5129b14433368f6747d319a0964873cbb2880ace344c52c056b5974ded82b920b3bc54869d77ba27661602d6cc2dde34b4ac0699ec705702bd8eea242384fd28faf9527fb540ff3c5f3eafc8488e61dfcddf512437140f85c9a6bbab8a89ee72475b6378223797ae2c441c728b94817e8072c14e1a59b8b92e0bdb4f58fe8e727c4e2a1aa6c60de362c52e5e79bddd968928a61e5e5ac2a8db3794c3246eb5724014f7b53fe535f894adb4574e5ad459e7b6e9a67d5d3ad46a1d479960838708f33cfc9cb65e17049e80ec30d74af0b4bdcbb24391fdd5dc15676445a4a7f227378e915d0422ccc3b0cf446930f96b7ecfb92ef78ff539645c62d08ec8a2f500023a2c48316e1991348f00b60576a868e9ceca860405e7fda59c1f02cb9cfa72b5faf8039fee7e6d9d7c5e0b6a80ce5c1a61e9ac5c32f65688ceaf1f0ce6773bea50ddfbf4a32e8ddf2fd9a1e49f2a831ede648056eb212e43afda30d35cf372164f48271b3017ca7e401a3481008bdf19963569cd4ec3ff606df9242a6261693061f476a4cd96a2c1ef47ca119f91b29d7f9a0b060560a85ddc4ff6573f6b6e3ed424518e044945cb06347f54de4fa6125be0c0c0eabd2b638604671d15767249abffa3b24a92646f8bf2464ae9f6d4ca153e9685b1d23659a365f5421c627217720fcdc45664435c3ac82984d29af8d40b6f4702b9892603066dffd7c3cd728504459ee56abf9e0c1c268043083707c2b6c062696d39ed90bb45e1eb050c2eb5157bb59630eaf5a7989ae1ba155b4fa3fea531c3a7d8d8690942e271277572054f4b642f993602587bc35da4611e5c263db4ac2295455a75b1a4044d7fc04cd492447511611b762e9ae1c5e7723d015b4de38bdee2f87fac3eb262fffba77207d11374e36bd17afee6f8a65c561f15584fe02f1ac82e7ef5ed790c26af2d72fd7fd8ccd7ae2c321dcdc57f5d02185242d630baf4b8d03ad543168a605d4472075ac42c87e3f47545e4e944bbbb940e12d53591dd14268d9e2411d8bc28df721edb8c85ec09370ba9c29541f8bcd54b75d003ed9223510c40d58022954ac27254e77414fca0cc54382af6b92ded9a26526bc6c95b57fc4864d9cf234fb52c72c4a14121ecd06e156badc1fd1fbc097ecf14578f44b67a47fb9c39d2c39ca572ef5e582d15a6183394e12cea49b24309ccdb8ac59e6ac61d3bc7f5fc8a71af2f779eebc5b4e75fc460ebec06048b372b98e693f76146e7dc196056a95da460727dca6b1c7b21e7f6c6e98b5798e6d76700249fb42a13409e6a3929a8f4cd2337893435af2bb79b81a99fadcb7ad18cc3266f1cd4f22402fe8b8c934f7d8f1f721ffebc4035a67718870a9cb440324c149aedabb31778402a3236866d194a160e2153ec4aa2338281cc56dae659ac907bb92f0e247e15c52395000e4400911f42369a3aca797b7c4222174de1d0ba6a678a7f434ec09676a4dbaacef38bc0d2729ddfbd8a96f8f3f03f7e112291bb74d682158cab6b7689cf5f78f802faf4a872655325660dbae5199687c7f346f3c55f98356b41640569ae13f433f543a74d1d72e695fa7e34d23920725bbfd13c839279431205bb3655aae2872e23afd6ed08d42bcba80bbe5aed855f6c74d68eb6f9ee4567198becded87b9858145070846b0c0e466cbf41b8b370501f0f381ad2f3b0748be2c13d69971be6a43568d9eb722fac753c88c2f3d7d660a87b4d77684df0e44dcec9ab8dee908395ed306b2a0dbb2fef006631ae55a7c4388874d381b280200d4bbdf16be86470a3b27727924f38af0c7506a1c64f67d056133b91b22cd2cc87689c4f7b2cd2c00e4d6b9ca12a29236e155ca3e9d3fed23cf2ffc620a9fbec6041ede65bb8d4f79a87dff81c4105e5f45f32cb88d7cd8eae6e44321ce485d7807000b5ebe15a09bee45a67d5204a391dbb7ec2556eda7df74009989342c2e81f81cc6561e0bc115099c65c6e4bb7da8ff00aa16d2b622d8118bc1a216e99e38ff3e441b582e10d523d5763ad7291d8043fbd4105d7221c070aa480813ec1ecea10af946a6013bf9b02c23cc37275227ddb410c445416a80945e8611f11ac507f2bd515fc5012674a45dba6ce72f42c4545f91ba5b5e5db00a2013bad9ba95c838dfb35a2b4a2e9ee2a588aa77299ed025535657d164fdbfe71db56d5c4cd845b734b4499e3c2b9907febc71f1e87a8b11304965a66012eff4872e6edde0fd0b5076081dec2dd3b9173a28571720c6af5d35d9c7f98ecea082867674d2199a0da3e1094bbc7536534f3cbce0372b9dc10c8d55103f072b637436c5f7e1d916f33aebc596b265824547ee39375724fcef0988f6c824dbe24c6cb74e7153d2641003deebc3045bbc23f1695ff2a2ded7876156bd0b9e90349148b5a4ff5c15419e331566b216baee46bc5ed0ca925fb35bd6902300eb3fb915695f368ffddfcb016d4297eaba850dc1e4a0d90956dd07df7817171cfee63237f665bc5eb9f9907a7a2e6fb802cc2197e63f02a0572e78225e7f6763af2d09117024dd29e0d26fffede89f809b3c7eae7b395c7c02b69510ab70f2616fa08fdece2edc57d28d20419ca60263cc94877ac77e663c072ab556937e7c0d3b9d8c5fa2ee0f36b407e0529f12321266afa0dd39a24660a371bf11021f2b5008926a5e1f71a39d2869fb5994032e091782ee99d6e3a8a671de4beed2149069c44082a0008b026bf72db75cfc00a6bbfb8fbe37bde8453cf5fa92584d3a4e5a679b2ed8571ccca6270a967c7cd8640f076c65f1a4371f058726658b2208dc97cb70d1548ad1de9c001a04da2a49528d5582d10108352881b720f730e57cf82e40c56fb4b9b1594209ddc0359519467beba7315c2ed8fd2785aba859fc51bc1a403478864dfd1848185ead9939f4a415dbc06f65c3392e8f7158843c167b0def16ddf858a5743f1106e80376bd86a7230e1828d24cf46a2b1727e7fe835f8e4046580cb92b34128ee3ab20f6b7a2484250775c08978ff8357726f755df44c1032ad9aeef544ad0d2af3b0e9b80db47daafc5a7dc40b706b51723d234e78a4e97b5d5d55fe7d850c5b1f64c950b65ae0ecd340f85e64369f7972e8658e02abf33db61c955de0ab4ed7028c20dea1a6e803a5b06f81117f9ea77259f097a4c04eb1ef2673650546380db6ce0c450b7a03e1fed0344cd06c29ba72af66d30c354854975477dd43a3832fcccd9e7a1e8b27f3491a59f58f7bd81c5d5647708988c5ad658520481d9220157e3ef4f7bef8a7d912600ed2bf56e86d7257b83713558e7f139181682fd6831db8b17e42cdefa836f2cf07ff4ca64cee171d09f2646e16294d721d1a80a582d46564d05c89a41076d3051be125668e46246b2221a1ea19f359d7dba145167345731ea6caa92d64a4e2a81131b569e549728224d6e4828410db99a58004a1d63eaf142a5551c7e271b9349a619dd9d5a0602a67eb99cd291d0e8f6d06eba204079b17d0c045c64be80077b8c786b744f916b5de4d33edcb127a877d789480672cd4e81e046dc8380dacb01020c29bebfa72564a86f4c907e0521f1a14dcdc94876ca84bc7d977c0f30c07a664dbf43316724ca96aa5f6f8864c6d4b9ba613ed401c0fc6e024b68014a5fefa71db87d5597142d7e25eda27732c76186d5925cdd34e2a627d5a25e75a16d5ccae40bb1d891f4f63d13836e5ffa7eb8aa3a2cb49781b35363a66fcf01e760cfd009d6a19ca6cdfaa0a57de7b4822dad5dfb1dd255a996b8aadb0d5a4b7293c315a7f47e4bd11d7fa39cae64da02dd8f269a8457dc2a917ea101d16d3b45cdd65e3714c9d5672a328471e4c9c772021f1b4ccf1e4c8eb936d900a89cd609167f2220d389627722abf08fe6631b395f5c4e95d03e2278612880568b8cb9e00af254e8c2acb4272c4a27490ef1753acd7c1336b5ef352338c105c7f1b7afa5329cd6b483ce67372dc3fcb7e6f0e7ca4374529fca4cfd89e6931575a2ceb4f66d26e35c7a02a0b0d2c6fcea60d96eb09219a2b9abc1f7cf3c10e60be9f1a40cf3df370ffb5b8b972cedef04f3c0713bb83e8121776a9c09eb82cf616209f2607a6545559918e185df8445040326e2ee020f166e7e44d0dee50e8c864ab62dec670430fa055024b4a852e85e2cf715de2308ef10c79226caa38b08d8c840f1a688453e840f4e37f485100b30c53e691452fa457d568612703fa2e0046f1feab9524bf82f53577f4723558597f00d38b1ef6c9a8d98a2484f054bcbcbac69f8e555d4a336cdcf0d17261e438f3741e9ee89ff6615dcb349f3cbaf28a54c5cd163223fe10f08dd912729619b54b32a00c2ce3a63dd3f878c55f5a3e5a40b177261de6c86379036131722db890c6db6d351dc8f1ed0e03d69dfa0efb14f55d74e8f87be47979d9a669723bcc336b6f6de98bc0ffdf8ac2067933c4683532caaf951499ca31ab72cbb0729ad0d37313ac5c3640455ee352c743622cc0a9dccbe0f10e190630f32f456704d9c73e1e7c28d7796671c9d3b99db1c596975bd3b8a732e7ec5e72f9b178d372ab270df6e74b3fb9cb61624fdf8c0515375788d168465aca781fbd3beac05272e33909535ee0be14f8c7bd1497555ebb734b622b210c8a4498eb26909349c972d6a460afcca0d9e29391be470daa7ba13c14190da23987d6de5331df31a2cb72530be0bf8ebd215ce31fb1478566e53bb19bd0f38bdc9477a73fab38e670ed72a446475cdd4a71ca70486cfd266fa00aea14990f9be6c8f844dbaf7073a1d666f2afe70c85cc508e21a571c5cd89ded7236728bc0f5554c6415ae92e20443f7252ca16308aadbeb70dca47b8aeb274dbd3ae2e4ca0d463126e8a4be07aa6f0721fe07832a5902a8d160e069ddf5caaaa5d808f4cf743463493cb7c8d65da033f8acac2230af284bd771c83a0d9331b9d5ca44ee1795b53fd3a587ce4aba942722f849d6016c0992034b4ed271b1478c24a6d9888c4192a043d713971f8e240211e5c87c810d342a3e29e99db0b2184e77cc3308cc744d70b60b355c2c694e972c465629fcbb896fbfa7d3d9140c8ba427b3917ea498a093956dd8704cc9b41729b1e51c2997458d26837fa03e7739a13580d415e6d954eaaa90b8e2d05d04d29794c576a07c04d2aa4ee32ee53ca180f788bc6bef0a6b93b4631082513f6d6390e3e4c90e6948bd9fccaeea62bc8994a4cf673cfe758dff938e9539089673a72ffee7dc065670469c3bb567d5aeecfc6209326e39b940e33fa1086fda0b36072fa39b5842068d81a8f6eefc80258bd465320f950f47c955b837ef8ae7964d030801eceb2748758e40f3770314cb21bb33ea63d5ffba2ec4bef7191c6d598d672ecb95163d20604656160fcdd01ce4980285f743c154f308cf4e8f13336e53a72963bc1de0eda1bb2b069bb97cda0392e884678d858d006e5ebc140f8bb8ab139eb7e9b4d73b73b7ff80569305195d4bfaab20f7621d74b6e103da9f65ce93d2b0bea16fe3543ca98511345a19c1bf69e834a65329c7c5ad2d89acea7e2bb7c72c063156f9393b1ffcd44241bc04744a29f349d90a808352b82ac26f2b20624721e7fe1fd2284f5b1ce3b13565b86fb3b40d2e753dcf1749da49421c4635b41432b6a323f609ced26805529b35d29458cf62e831c88b41821806392c0726b166842705ca30ee96a10f59bbe5a27c7627c244343c3d8d68f065e256efc99c20d51361bdf15b9ff7fc203e756ecbf3c5b41bf6f2dc003a0b9fbb5db394a904a2072e3171ede7ab4721e053be87204debddf779ec074855beac608d97415a96ce272877ba4546e4c7a3b8091163578e4134285cf4fff5bf0e17b814d419dc459ce5885a57cd8b2e5f71c11aeac2c76aaf01e8e67d7a57d1089c83cf45cb8240bf247e732ff8454e39428060004870422a3b42da2daae469a335c903f6f24663ebf7203b75f46d918aaaf5222dc5f3e45f1225673ecaec826227705d3dbd0c2fd3f72295416a73b3772e1b4e45bc05281b0a798d95ba3ab75d7923ff9670a9b437f598043d388fab3620af3663e0309ba6803dd465e4030c8f583c2c93be7682ad57202bcae5f3c2503ef9604c6bb95f33229b861fbeeee8bb3dc1cd14f397c331a53394338542f3ae3839add8b8e7e8eeea8c8139975c6b5d4214e466c45f1a33b728f9d041865f8babb6364bbea645115953019c07783dde79dd696fc1666624e722371a8982f4bd8626b43217018e696e3698c0f097630403d44d3b1a57456f00a98010285b7e3882f5d06117d95417a56c0c39cf1601d607af0a63a94b0600e31fd9fe46fb525f3a2c9ea349f7719a391c0bd793a0be71934db68e4d6e5bdfd72d617eb8073cf3d2e1207854dd35d7ef5d33187f812412ee7e12c2e561bbfed3e0b8b440b55b366d9b337961e2e9a20f0a5c3dfb38eed010fc26f025ed5a01d528e0429424d94c2a44e4a261b29ed9a085b634f810ce37497fe887ec988fcda09a2e482b32b39dcda857d0779ac9566320fa1c2051b2cbf4c6b4885010c30231ed0e549d09eb4cecd7e96aa637b44d6b477b6bd31f0db17d26b3315deaad46f726a2ab9e9d5fe81234c08aa8ad052df2122ed4ddbd76559330ba6b314800ab772d27b48cf335633535d4918cc4b1687136c2971714c7578b4f5b8a157b2fb7a72740f81688e351aabe03819a1800367454e1011d79075b9dea3c659c9b29fa772792ea5f1f4b74279ebe1cdce1aa5f048f3f119a94039613a63037106a814824134ace40389029ea6c1dbacf5026782edba20e07235f2f565966025a999d75f15b5258aad6746757e05f3fe1345157e47f1255a4ee085978cb31965d9df49e965f4396c20d3237a25afcca815dd29d9573b71000453e6950de6963d4cf06e090c2184b34a62a5a96ab5d8e08549e2404aa8980ee95d361d5e40fe2a8de2aabf72b0117682653ea6a6db2bba6c1740110e8b8bb5b7d34c06bf4ab8577432d1fe72e74dd8a4bf5ac0a9e4260f885bcfd60057804cf2cd68d82d14067596964128722cae8b00913b9b727d7d5df396e010011f811982a01524840bccb3e1131df572f6c2445b38bda3858430e98264b8f53fa0b400b0460eaffad4a626963e5f09226b3f4f6beeeeb06c30ba63fef366f13de878be6e8130b3f721779fd9f4e6b83c0127fbe3a89c23ea40e5e32959769e03c8a9e1aa9be3647e1d1702e710952472be1d36f92a2ea1e7c426c9ac731d8a28ada1fc29776eef8201bbf6df0fc53872b61395948c92ca6f72944a97ffe320f46d1676a02d9003dc0d7f3dad1f422d7252e393ac36882e4fadaaae1b46fb12245d15d3d4c2743e4349dc027f7d130572f76c7aed69fbb36e55a6e86f33fab8d70d294f9e6f4871ca8fb7a4ea02211372a988a260f5393d594491558795ef229dec8085ead80da0f45434730fb8efce64ad5db99762dd23584cace313c9520eb05822a3bb42ab50a695aa22c58db39c725c7c7205d0cb5b2fe727c92512386e6499ee79767b1d06b8edded1955992a30ff0edc81444d6147524dc4b8de850041aa9990035c2863ce5e969cfc1833e126a21ba5428a5b0570088248125c4698196f885c824bc607634e3fccee1dd4b437274c7dac162cad58103771aa88e90028430e329dd868327f01e9ee5a34215e07203051b19ba89e69786643b19ca1ddb51927db0e65f9edc79c298e3feefd40372ad21dda227c7ac87159bea20c62cd139ca8093d21524ecd3174e462d0be0444f7d8fc893f39fdd36b3507e21404d537dddf37b23c3786378bd2a97be35ec1972d6b34b628a8e456f8e2396b95630da92e974dcee9de1cbcdff5e2897a8660b020eb917dd91f3f61ba29130a47345481adbf00f156e82d3f6150228ae471f4c724cd64c43f11808867cddb6cdd563dc5cbd93b7abbd9cd87c03799c85cc39de219a922ebb3d1707cebb16c236932ace7041a402c5882d86082bd23aa37cd4963467bec1fcf03f064fecfa1b6ef0033733a07d35ec5b5d09f3104e091ee318cb70829d4c3b8f8a027f02ad90567de77a9d47d4aaac235910a77ab99f8f5d7e434747bea17248ff68ecff8cf1b25dcc74598ad67c990a06d171d547760cb7210e1492e8e15ba6b5a6021c9d5099cb66ba9de0eb2613c626040780177e298e77ec725a482509f6f99619625bbadb36a1373322dcbba2f55944358623b8f1c53a097291e4f32fde04d79b0c9695961eb96b7649ac04d447d878f6fb9053f64eba8013c6a79c2f08ca36de087b2c1a3e627f100ae10e118bba70fa2eae255f740ee32264c7a5349adb8cbea96d0fb5b8308762d37235d61b394c82edf3137ffdd0a672f87d6af5e108fe9dff7ddc4d49b70622b4724f895d4bffdbb30d0d00e2f81272d29c0de79524e1c76cfbd0728b03841e1a76287b697b7e57de4b18a1a5d45272e2262765f411b730cd5cb7e1ff980b5f3bbb5b2c9596145cd726f0f0f62a6a72311754291d8a0a414ece799d4558033a97bf63be9e0e263e868358d3ee27f8391ce98d20abc2b958c9f90e0877e234d75ed27ff02ad3d40ded936a8b32524d72a71d40eda8533046b6959110eb15cda356e965d2ab89122afd8b59ab34eadd5bdff48d0b97f8bbff7f04083dc347f2f7f28830d842d4dc571c5b3513eda813724cb55be0fa2122be987d9be5b09bd3bf7866c00da28c36664f6b91cfd5442f0b08a83d1650aad2616fb94c4aafebbc3a8f03c82c3ec67816769df7d970f909720e0b267ee241207b6531b68f31eaf6cbc64e8e8225aedbe1d7a87d22fccf4a726d07f89503d96629bd74c679513327cfcc47e6e55087b269ff8ba9a5d51a7472fe8c4c5625ed944ab01c0305507ebd60b3f8c0545fa4f3c332296e8c1527f951f99c82bf7983d3698a63a428792b66a162ad8a308ebbd1050647ab8c86b7e065e4b7b6b62b3b36455b3f053007b1ad82f7719f5c7a730fd07745a6a99c66e24fcba16b9b168aa58f5445d0401e1e98bf11527313de92521002128ed9f381d5230f2abb7b6dcd4acbaced24ba26c4dbd20dedee6ed70ab1ea270f45319de58606cf333e8d87369603266247af46ea4794a812d896841c3ee1cf4b3be80683e472d13e011ba419ef523e4f9169eddbadd0f9dd3dbeda5dbd293480d9d8005f602e95d5741588bd9903eb6342bdc6ebc3ddb827cd889f38c836046f1b7273208472775cad251a524615c02912eaebe80836b4c082f44ba33438315b02e962bc0172126d0dfdb8d29bd3ca57749b1b688a0f874ded2ea6dfeb63d0404c54d38c19720737bfca08db9b38313b376ab2062ea2ddfe46337f1eb1b766cbd095d7f2ed40f388f4136404d48a9dc022e0ffa3391f64d1872622fe16e7a7867de20e2adf16eb9f8ce6834c02b57966bbd5d952cc1aec468dc021412c7addc16ffc495be76146921fe6e8d00ae02c21a0bc61c352374ab45c232eacc51d7e70e56422d27c7268e913b0d2af8424d93ccb15f0a0d87ca15c23fedd9c981aa187ca6a20790372c6b18540aa5b2711560b0d38105eb42cea6862e9012e9ab057d62d9718fb8c7247fa40984d9e9c645531687987675dd1b73ee9724b3136fb8e91533778a0a772392d7f642b46dc859fe4897cfe2ae055417dc9705763e3f50819e8d98ad8d004f9269ff1b293ec699191fcce7916bcad1b78e32faf7b5d59aa54ebaa614ee261272e17de4139e2b598f063ea1654bec3bb83c335c7d2b7d183a0425b98df5c05ab72e843d130f713ecf4604c6680e741097a3e5afe9061f0e226cac86ce3d6219701224229606d75027df337b91072a7bf9f68aeb3a95423dc38d68655d76f72a62e144fb0c0d35aa05bfaf636fe2764f6876c942950cc2a91c050a8402c107294d8b433234ed83f6d9cc56a011b653b445b8523b0e1b3ae66e597930047b61c60eb7c6a0a83cd6488043237d5e2978c2eef187a7bce60f6156caca85ed2837240c8cdd8091d95fdc34653dcc7c71aed9e47a54aa8ea33a429fa87558c0cca72cbf457afa7d26e01927f9c0c2d251f5677c33338ec39b52c73aa69f316167572bbd31ee051a4301351bd67f98480bfdd8a43d384f22236f84af3b16430dad97231744e9d4e37384f79f7ea4d02ae16678eab9525ea26584311256d28a818bb725acd5f85b67560f0ede5eb0c075e585d1b97471aaed04e4861d386e3556d2b7286f36688c6ca4f905a47ed39e0ba58f87d4507c80edf7a8b69da730ce1ef4b4e769e27b1e6d9657da9f6f25dfb678b30a31f1037d06d718fb641f2a27e2b2a4abd8610ca6aad4532c82005ab146185c89eb2c6029aafd8f7949d16e70aa7911c375db65c90ed0ead5425eeb03c47bf2502482600c73d975d785f78b7e9ad2172f21e6cbaf9422d7e8d39ed819ba33b16cded2545459b706e9a424d2af5b9d572cbd904cbe7dbd80c29a2dcbc8b2785d1c83ea6dacc3fc3c450a8d09ad8335236d33447215db6d2691e19d6f23b5d06075e1d9813403fd07b049d9dc69ad8304ec441b7731a7c7e15c368050918055ed96aa5cfa2315ba87055abf68eac0e974bf7c656f6f44dd134e6595e8c62dc7593c630e530d672f3e382d21389f24b2c3df17524d17ed50dccb8f18d0116441bafa104204b2faedfd3eeafed443652f66139e922512320907a1a0705cdc7ca85d77689033a35cbd2e9993b5eed314ef772fad8cdc6355f5116087c05e7f61f89cca3c76121ee16977e75b98cad46ac8072bed62bfe99b3261f851d70004d46c2f22d46fc613cd0434d970e0e2e3133e372cdd7b922928bd77a0515452df1a1534a72bedae99f3d985747754640d1c2bb60eb24e10bc1b9bec6a5541da42f5d76cbb001347e752293c8357df6e67e40fb32ca95bfdbacd3a976340a0ae73a1b8fe87964f7c97ed4081636bee2583af08f7268059b52b2270ec84bd517d994a98b41d6a59837b907c044517811392bdcc072304773de89ae57dea0c623fd9e508ad0de8540e1292a0972ac63248d0901e572ad9d9cb31278d0b6021f8b5a2ab16a3146a2c7f7bed8145396182898d3bf8c72b1ecba2932b76f1a5ef04983efa290c5cf1f324042ad23c77b7e351add5c4f720eda067b85f846570d91be151d9bf8451b780c9327d1ab1a383f7b28bdfb567226c8aa1cb05fde3f40713c2a6542aa49b6dac1ad562283b1383aa8c8e786906b41f5b20a8232817c4cb827710bf6dcbccd270f63b4d23255d2aaf222d3d4be72c7f874259f73ed900ee7034fd850afc80d9e924b21f062bb7f56e0d63bc1677292c1843a82f65a386d80895efb4837685e9baf33f6b2c351337419672fbefe727f6ccbd02f4de2a08cb1fcd0ba7e7816a462be8b3061a79a31a315a8807a716f9ddf2310bf62471a592a659c81c6a7d666981517e68bcf755e3aaab29d5c0062970aedc4133b85cf64502ae5e7a98fbade1379cb22940770b4677e64677be2727b65625473ae346c567848897f5482d735bd497b4b332e2e836a07bd593f3614c40a57d68851f822f4a7db191d5cbd7c49e1de3b6ae9510b7f7c41844e63e072a6c3abf497c63b38eaf27058e1c157d541f703279af818b62cb8dd48a891327210897b97068490362b359cd20975f9cc14575782adcff6bffb090803abd546721e33caaccf07a1a7552b8045a9ed15477ae54587093036ea1d50fe151dffcd72c8168a7fc028b33abe935fd9640e6d80e9d06ba1758553e16c1cdb1d42a30a7240b20654996112cf9f6b93c0ea48ca9d92724e6866adba260104fb61320e2f7249acecff9184982e0a23429b32b58fe1a0810930c56eac2d836b0546a618d50ab721f20ba21e1528c43eec783984397d775f24076514ff30a32ede2a6be86e72edf5ce1cedbb7ad2002cb0f8eb527187f165edd9df162214c385696c54ead172d020d8cf7dd91100a9cc83d8e4744f597d19cde74c7493c5e86aa6b571d1847276b292c173b3400391f1e68491f78928c9c8f66bcfa1d010e10d7b0d834018726ae52ceddf04fea15743196b74ba06d9121d8f7d1f7373c11561b46d7898b67060cc7aa008eb979452e35c43c14c51e308081513c55b68301df091f21a3bda3e82215c980fd8f9d1558800c93de56583f37be408751c379f12f871c4667c317222691e69af8ffbef4930641a84eaec2ebdeae30fc37676e672dcc50b163cb372c91311d9adf66c866b33b5b56f6577cb6ac5564bd802a9dc4695fc4275f9bf4d226dbafade49fe8a614675d2f7e63cb79d3181bde3142d003834221b28e5687232753a7d1940e6a9567b0224ff9b82775bc8b3b4ab040f02f248b2479eac2272ff735da736deaa617aa774194222a3dce85cf9504ba0b20f631d9f3a85172a72a2b9a830da1e54595c07cf53afeb6d2e9337b8f5d49a5c242f61decd6cfe7f720323899985d464c476c589650c48667c5bd1d6818cdb88d7e4b0c5c0490e54314b6b923867ef3e1b684882ec19f65f9549269d9cad249e183974d31590f6645547a18c611dfcf0cdc8a996d1bcbc7434b9a30664237a4c408e79e4160517c835ac6a428ef428fd57c7f2738ea93dcc7e20960ca70759c8b0ec416dde8283d9726d8a9f9e0a1192c8eb5cbde5f5c4626edea7582899a1a489d0fc2a4edc939a72feb206faa464082b8e72e3eafac15bc0e889b87fcbdca497d1ea4712d9706b72b09155853c13bb9523e45970b88cb3d490d539326af1333e6edffa7d5891cb728f2ca7b9c1418692647ebb05185fdc6c2155170f0e29b0c2eb95702e24c0055720b720d83cba6915009c93f85de1c97695119e5f4cb777a9a42494092dc841725bf33fdcae03b862e9a59c2f8ffabcc41b3c86086483fa77af68a561c46c0272ce13958eac0f7fd9dfe35c323da814ec310a639ea563b5af085cff74d5153e14acf45003e90b5c4bded6361db44b97bf40f987e2e45baa41e9912c5a2cc8f1403b830fc5632cc05b33567bf777cea07c7bb658df6154f1ebe5fe8f3697fc5b723072f9cbef2506f01e493c05bff5122bd53ff6f49ce3142f81dd4b20194939724cd029c9e459ff083799e751dc773cd50303b06922236547c9076a437c59a24357ca9a3fc1f346f0ec328952f23a37219dd23bad33dc8aeda15fa9b3ac688b2ab72dd28733884fcccc96cb14941a81b1f3818dcb5c765c80cbda770c78eb9272ccb5f2579ec74580d0e71dde512341377a3ab60bcc580ef1d2dcb780f140687224a757bc71ae56348f185178b8073ab27c2a7b0a928b3f7434c0827fcd4a9d7268360c96f0126df57637c9edca6673f8b178229b9dced359be0762da3efa9372a0559b2990a9f00e03f8e42307845739040748940e3a391b26f8190cb7bbd95830240f0ef784700b6ef89411d7eaf04d82663bbd1ed23b7fd8122b1561284f72c65449ed7fa9276c056d2f030576c1a194bf5afe6d23f62df44e99ca1f9c5c23fdf20511b94d8b371c56a2ff10c0a871b15d462c0fd77421cbf1a7720d6d264f0ad1ef78c10728c84405371e7ef9f8e68d0ff78cd58da80e77dc4adef756fe7227a33207aff1470365b4d03a8e44db6d258ecc716e986667437a9e9722532e2eb4f5ab1139fd85df6e674bca8f1a2e7b7bdaa720c1cff5491e5133387c6035725c66a4f8fb7bdb52f201d34bcd4541e5090198a666e1d0baaa5003243bd9b03d35a859aa1af46004cdd41e053447fdae6d054f5db899ac2e9c9c066b610e1672eceae2e7fb523c89c6975c0d72289d3df0a8e77fb1f1ee8ef4612b82fa2cd7728ac192ed6eebb0e563ba5c0625f7a37ed65db47d3faf2dab9cced9f764fbc57212f338c9e02a45c49455b608f47eec61b1a9a18dea862aad468cc4f848ebde33df0c6ae294415f3d6f1a415ff05fc74e6bbfa5bca0d60cfea26a15fab0bf313dd831ae3dc0ad92d843ab23b542a9bd814e1a9c88abd8390618d2f709aae9c40172d1a2ea08b519675edb94f1d53f9afd8543d73721abcf700a47c7f2501abf1293749adb4b14bf267f74d694cd572ff07b3cb4c28100d8988004b0fbde582172adfde39288e53f7b9f0f4537a0d03e8580bdbea5dd04d70683101d9e0c63ae72d04156a42607b72c19c7f4053b64f39be7b795f1aa2299326a1864444959ad723164564919826182273edd98686b8dffab076b18711528242fd3db1c9dabdd39d1adb1c64d21d11581940716e0a2b68382f36e0173f2a153a7ba8d28bbc04a724aa396fc646e350ccf9d12b3c6a5ce1aa10a60e48dcf87ba7f703c7264f947052290ee38da34c7867c72fb7fbb34608061626c1fca4b0a1dd44869d667550e72dc7b8ed8aee50f14d5faef7c19c62213355f4ccf708bdd66dc66a0f94dbccd725df44e198d5afc8246c328fdfd64883b444daf69eee57103cddf3e4e11aadd727a9c33b330424e9ef0d9f6e05a7c236c6c4533f7ef1c42b409a993f10c2fd9522b4e5a26cdfc77c96b378169a63386c24f2c0f4816b4b10a21d3e13bb623d62daa419f89fed785b8cd033bc732d5589a9151c3edc48a8c1d5f17089e70cc0f726e3727da42050dea38f3b1c2f93c5e89bb439e7926a4935100e7159a38dca00f92b80e9be8dd4b90081a54631e92838da2b5984b9e697412cf103df6496cca72ca82db626e6e6ef011513df90cab7a69587e5a865fbf0d3fa7016a53e19f607264c72169fbfa15c0d72a0d0a14cc3dfecbf651a4786056c146465c8702f74d5db1387e8bd44cf69416bfb6934c985a14035be5437b310c8503cf5a0d6f637e0661f0f715727d0ae25003ea6405daeb59867d0684c54f16ccfd238c92c066e94cb5f9480f96045869ecbf549ee771d93e0b6e0419770027e590a2187935aac45338e762da5f975fd9ad7f89d7cd6760336d0fddaf390415b5c753d3e28c210c69a0672e0b701949cb98633a64e1c4899f5ebe08c66277a0987ae33b823043dd42230caa30fa343cef9b614f92452206a91f823f44aa0ac310f0cc8bc5cdc6ec721d5ea22e0b9d368a5575aac7fac53666bc19f82fddcd148a0e99d658ae2797727da90236142e89dd85672e52fb68704391c16c59e389e3c3b1c5b8a5922977728f79e5f52c862d076ba7cd0005f2a53cebc1470bbfa6bf5c7d95a8d7d8f5a4262191fa6d7ca5da329bad7c67e286f4d82896d65459e7dca4e18d6fe3cbb89e728ff4e9224a6ecadc3f41f1365a59eeaab43696579348724c0cdc62fe21c94f3d3bf4ed82248840ab58d5dbeffe42f90d0b66cce15cd0539e8eac96e49e0d7f72955d4c96b6470330b9e373e9677de1f4bfe50563905c352018ee3eac7e1168728cdf5088c830c616bcbcf948f22d8074f4dd583bab1f6cc67386dcef092edc54a77602711ee5b5f1c61b8d0854f744a2524adcc20c68b3063fa91d6b9e555e72cb90849d14e18a037c5e57405d29368ed03e51d12b0dc9db016fa0c4ee8b3b720dd1335403d257bd5d5d35d6bb6a8f99f7878fb681513bc2c5024921bc331072ba4609a12c7b68a9da5765bd6d1eabaa443561475c857222d7b928e4aa7edc72d4331eb829daa8eb89cf3e1e9b4cfa2b85ae242a578d76db0096cbc5576ac872f6738b0ef6eef9e00f3fd3225ca0eb2a43f0341bd4f75eda65bcf73df7840472a928796a9458d7f3ff6ddc49ee791aea8d7928abac2e29c910408c597f43f372769e51a96363c69cf301a93c7a9637acb4537bfeebfe9659b242dd34ed159572f8cd82bf1457732957b673500ae30dbf00fb0816f1350c2bebff4563d669b2729bcd631953ccf63aa5670eee0daddc253c79ffbd48691d83d1345056eb082d7209e015565aef7689ac42fb3c44ecb19f144ff3f2f4984df8dcb70b50966c7c727ac5f5125b473832751559c16ebe17b3d54a3e0a54f7037568c762c6df078b3a0b36cc3ceb253ec9b2eabb9b6daf4c21f2351d905c3610c0254dbd384a8c9c721703cd7a76aa80030df0d1d0186419c76f9f4f1a6bebbc0da4b2875049465d585e44292a4cbc02a3ae014182116a87fa77b28015ba84288d8ecd51748c29fd724f733df538d604bea92efc1a23d6fa781ed5f1cbad9a125c6fcb2408aeee8f726cb11abbf466b208a988ba6034b25f2976bba06b2af1bc6be2c2d9029fc7f542c58df38defaf8b9e392e76951e82afb34d2d51f499d6052cd23d289f6f4dcd72823561d25127cbffac63454f28f9255e052daef138ea12526e4b50ef05a0964a0f5762ab4f9419b9c0175bba4273b55d191fd2a9b789eff31b470b0c62651b72aa1e7c2a6c27c3dec7beb9ebde731451feff7bd4bc50fc3843bea9c9a8951272e90ce282cf635dbc9a7c4114d0989235e8280b675f8f59304c10d12f23758872cf9c322c76a7107db4976196aa2a0935f7ba361b781fbf59297c88b6105eca71666b9c06e4b7fa3073904a4c1b81f29f55dc2b9f86585572a943937f313aa362296eb7a592c660ddc86f281c49b4b926265ff13f33fec2622ba367899e6189310c881addda3ddea373442f350677a3d1546f139d13c4b2c23f8fe1d94c323672feb48348a3ebd38d538ac2f505f886cd403d187eeaaefd37c1c7a7105d2a0672e3f4e45ddc864b98efcda672977e7a2268fe6492719d8ef09a815e1eb0cac8390798af65d28b18bd96cf6810cc0e14667125c2c306e6b42af21916946e775e0d9bacd327f45a609053dbaf544b3c1a749cff7652715b7d0a10cf7255b0b2df3ed68f8cb58bb8cc307f8f758642c88cc662f6df1c42eb1bc8d41c9c56c4f8c4662522905377e8dfe5720cb2e4e4c7a1a7eb4ca310eecc7beccfa12f54d5d66972a7a4b5c3aff8c1db10059e5e1152064014a28c4901aaec86c769feab7b8baf5ca54ebca9169636c316542e4c0d70b7b95af4d2fbcfd682f689455c239162707289a81156721eccb6180f0b08babd7d27ba3272213adcead771dacc7abe38724682459823a7cf07ec899d9541c0d52ffbc47ca20077db4998d0e4a61c77128972700223f718851b56b9eb62f707d5667da4d2678a9358a0c1abee6de14c3a964dc7a6cf03caf6e46c6dd8b5b01be96faea231f033bf49c5ee55af28b4564de072a0c1681ba6b285d6349a3d1c2497aa677d782a8b08e821e3de99b11afc81cc048545f42ef128baea394c67a1540ed3215dc0e63c32a2d21d5980dfafb29d587215d811b9d85d037cbe8c636f86afc7850ed53a763c3a9a8cb253c9ec6c11954fb27509e69d517e9e5c931e8a30f837b6b089934559e019659e4c7cf56ce5fc728d8edcdc8e216924fe8a18bc355f370311ef074c2760e5cca15e5ee1d0cf93727019a360a2d22f5323b2cf3b4c998c72f599357fdda27ca13ea91ab99a3b96660f06cc3da871a7012d49aa365350bfb65564d48c1cbaee571dc0ee1841a14872838540852939545e8161722f5f5d45ac1b915fba4324af3788ae6c0bbf77e972345b5120761f4d28c88d418ae5e5e4f6937fb87bd43d929c441d3cc7e4a03f09c7fe6c8895e12a56c49d2150f79c158c8d1d5b6d1d24c854326064108bae64722202baeef19ab84f700c5b964282e0bec6e89c9e8403ecc3fa52aeee0bf81672295cf6e27d8ef10b9c69468b9f7b9e36bf82a05d8c7c19cdaf0733f169896e724edd6fafa98f8932dd14b8c57923ad6fee742dd324009e0509cffe26e02d256c0ba8f9e95914a754d799bdba4f359dabc8428f748ae54f0dfde6e569a11fb772dda431f7bf0d432851ba21b298337c580c9ed0a71bf42bea94923fb3b7f5dc6384bbdd54f8664f4091281155a79f7c221b282085286ae2a2df90017ea176ba4b491d0ea166aa578b7ea760bcf2141fa76c3f98417e12748a0351a2194e868c31fae42a0ec1870a556bc11495d46e59cb237cd12624ed24e3c9275fbe5540b04857b4d054d8dd4cf575b5f713238dad9ce3871ad8a27867431b578cdffb80cb125e9b0f2a12175453bf350d9609e56b64f71e137bc91abb2c2205608e481441729e9799a9968d42a3606887018e4908b26a3d3cd30ad45ccb6c00a042b2b87457dd0932017ffd2e2a0deb811b95df4b33289c4ddf135e3664f2e8c706079d3a72f5c05b689d8164ab0c5594c36a73688d356e331a50e7f67c27d76c57e692c972bb6803253f2672563573ee2d759158ee8ddbe255641e3f5d77ed0319e8315b1d23f4f96a246e9543211cfd3df61a7734f64f0555ac4b5b9527e81937af08ba724556706749b37a47c44f4d723a96004e0543b877c597b81c9e38569fb5f5bb72c0c16dbe98cdf2801807623a52edfcb80df1ff8556cfe3d981f4510b5fecfd25da2710d3e171d945cb0bc0612241d8d97be7b97af84cdd4c970d814a64808403cde1fdbea50373f6ca66251e93746faf67d28cb27962a3e7b1bd0e56385c9c72e3d7536767936309934747ac34e1e7710383a55383c698ef46ffbf0627de324083b1f6bf91a52d94507cac9b00a9a0c2258e4c31fd4d551ef05c70903ea7c535414950772112c3ef9f21c47a96adafad2bd36581bece1f99816c46099ab9386e3ed6df3f1518d5719c29a81e66a0240afd5d1bdfe9256eceb206cda3c259367295e8b9c49ef4801aebbeec62856d471d69ef9c3e1746b97e690dc8f2b9df2b4783c848230fa7a5417f4fa76dabf07321272d67506f8def1be50ca0fa83af141137a7c4c8bf3e0857a0068d35b44f9eb2f93acbeb991b220d9180aa4bdffde872b68257647a9555b63b156ace35720b2337e456e898d4f85fa44d10eb54e3bc724d9d2affd40b2cb2f73f5646f7dc2267828b5eee4b2c4fa6f83d87cbb2d5c0373dc4178df58b217d19c595eb5a26b03f9743db52b3f881abb170eb7b387f99728c9a019e29e0e3ef33e14e602a6d55a0a92fd0ecc305526de6325eb6521f306dc263a62107cd194c62ca1ff66a8c0e7c5526ebf173e14a9562268493bbbf57105163ebc10aab4de367bf1dfc0a563b2d6d27ac57dfe62468164bcb17d4ad8918f49b90227adb4af67f4a13e7184f510ab250284e85874349862be8d8fdea4c727949adb53d3622816a99b9103463ce5a74f3b43791327b402a996fe8c5b7fd723e33da8cb67c25d735c4465c0b598fef64b09ed8db11108da45b00da0fcf07724e0aba5fce48a80e39fbe34f70c3cfbc7f44dd368ced9356f367147e9b559f726e6e85ca849b84c9bb59cb29d7a05bcb3cdb957d764eee68a41173480892e04507a145e1d6c1af312f74186dda5e15242b7af99d8fad38467ba6ece01bffc072a8fa08ff8c55e6e9f68361dd25540db480abaad33d003c0019bb6cafb1e41472e932a19ea2599a8c0a47ebed045fc6dc76eda66b9664e0433508d1126877927249a8a732fd3572af7099e3957b9f918f7b2738acb5ce8092ea12ce08e73f3f01ce9186d38401ea3a1e745d26c437f4b8dfeb143e9ba0159098fe1df22c64087203d1140f28b3154e838d0dee0e6a93928c160185f5ecf1f111f6df2997aa2972467fc4d946d1c7cef16741c4040f2c8516d1bba10bfc5bc2eca6908783c531726643de2194b9e827552bf34c357d177adff5e9178800794a17ec2f454421b323ca518a0d309c7bc56aa362a7ee2ad82f26e00897a38ee646f1d80ecb86c9cc27579ae3d41223a670cb34dc2f2494c5d60db89439431deed85964f7c87987c6259738d7200f3c130881684b9607099f03f82e3bd90d1a31fa795203b4efaeb97258e61d90742947ee3869c288155505ff4bcfdad447fb561ee4d372be4cbffc724c4e28fb1c6ef8128cd70c2816a30532a8a07c70165cce540f89f3b90dbd2072ab970b8bd4df7ef1c8958b44a4d96b01f91fac2d6e37aa2a785c96fdae2ad95e80a2ccfbc69d7611075bb35ab81c14ecf0d7cb4309f7bb3e5dbe4f2cae610572fa172044b638344b884f30edff60edd63103dc42ae2bdfa697fb26dd4ea8e6010962f0c080243e5f01055991b0e70b7b7ab6d025fc0a0c165eb3a83d4e39121a65fcd964d575b4373d4db707c4b2087bdfe18ff3d9bebbfb886e4c393793541e8bbd39084ae94e38b70aae90dcab0f747341693a46a06cd9171edf9645ee64720e2b885f946766eee5fee4382e70d3f0ea4d6ec823e66c7c08599566ad414272095ad946f58c8c104b3d41bd659574db8989480b5360a6e801b376c60d456b5f6bff4ce4ff3d0f5713f485d0abe18cb4872a03842e4bd48c861161ede426d13d4968f2189f8a5e96ec1be87787f3d90691176963b1670c332cde2d807be81c693d963ea17d205323769e556525375fcfe0e322447c6ad70979fe24fcd54ce872f57b68cd5f9bcec2706ff97f5b72aeba529af537c9128b7ab5e8be469fecf12207f1fb635c2ac989cb8f079c066415406b26f4c1eacc2c90a4c023c730673e72e85dec20df9ecc156d172134b1c8addddc5c24a126e144f5a9bb8c655754ff7269111c315caa5fc572d6a1fc66492bbb52900c3bce39dd4447ef1ee6c9ab08720ebf59970d9774adf1add78586d2dd17d9df82cef33e53b8f34c74bb33e1d2729f822d88c93d687edd383d81e07fb8e659618feb97264d018605607ec0690b729be6a71f508f5ec05b8e21cc15895db9b7100f8669958daaf0ea4118557b7972764dca2609b9d7feba9943673052716b7fd921296a8f404c82cf99efb468ed0847d300c37d0167da30ab60e73eee167c22962821695a18d320a403be8a6d1f7200ddecf559b80bb9da62102c25855fc4bbf948fa88641095d4d21fb94ed85a72691a660168a63e2caa32858e9d249c53abf5c2be518e350f5180ecc329fa496a50cd4e71d571c178e6b0c2631b151534c2498c07a0ea4bb059f464cfd8e50a72bc58bc86f5dd28cb28ae4859f314f173f1fa02adcc342e629a3df990ac494b72c0b63ad9c057a52e862f395733610d7bfea9207a7306fa03e46e9729667b8e72d30c0945daad47cac11f48ab5612cb768a54ef8d7dd1bd6432e7a1a16a0f62725c611b9f321bb472255d371783ca525ed516df9ceb62a6456b1267d72589f30421aa530dd82f7c05035143af3d5b8e3158c4dcadf8feb586e57ba03d1738727280626e9f1cd6626bb43f180ed96b8d5b4958119c16421bc15bbf35fb5f4e92303291104cf73aadb693e2dbd23b27ca8910165907c24e2bce8daf13ce2710a4729e91fabe6c9a63284425fa0bd472097ff115bac205037cd377b375c82b83207244bef1bedd261849ab3e990e4fd0cc57630c3f2697522f902aa7ecafc0489f72a50e245156a3a10f367c3a44698487286b1264690a08251641d3a768ea5ca83f929fe4cae681fe212ac42a160d8d4ef6b2b36fb932a24515002dbfef1070491dd9d2ab34f4033ec499d5eaf07df20dad7bc42e8fba681640495837263b7f9c725b9610d267c71568ae1177364278fba69fe1a3f0bfb98a3eb22460a75bc6db725a891c2370e56a3683284c3f27ce26b30f57ab8570fe07359becc6df60173171a68113d04d811b5256deff96064e8aecb79fdbeda7a1c7a913d2358e5377dc5d553c2c11e62b75d218f6405487b5c0f318bd9b59ba63b24c9381aab556206872b1be7cb515ff5ab2674c119b538ea257dc29e8eb874ffa224a2f08e9c4830a72b7fec51c7963e5166493c6cf9f064f8fd12dddfe718b388ab43ba72d8ab10372dafd7f82702383cac6309676d58fab5b38ccca99b89951374110a6760cd19a7250292d4c61b4f6ab3c3b81820b5f7724f92ac3abf4624e1f7b11565a59bc0872ca904872791637826eba48acce689826b5aa145d566d76e50840cedcb48647725f5e5658638d29ee666b2483c6d9ad5c869e753401b001c98495bcbb02d5156737018d1e354e63f8184d1ca12c10280d64572672c39116e8a90d92f08c63ae723f730c8c1dbed4f440d18c3ec538eb341b40f1145833c39a29c315d6508ba31f4aadf61b70c828ae2e0b07b6e7d39eeb8c79c6f58cb677c9c96b752135277272eb18d1ebb384867d4001caacf3235755103dda0f928ffa04a642ecab9e951b7203dbc3af7817cfff18338929ec90f89584648e7a06f48e49000ba6203763c23ebd350c4c13580643f88f65f6b01fca411f23dc0d85ca1fceaefa5aeae2147c72ec6a892e5836bd78247b955520f9213156883be9fca104ca9c6598d682e5f6212b6874a0dfe32f14413936910c25e6c7f53f0d76a985d5933b9d2a1c12f51a72a30c5114523b8675d6ef365c877a1c7ee5810b18a53ee6f7ddef1a0bf773ad7261f3d09b124d6d141cc4223904e85c92d2f2d02fb8b80e9d2a4d28519363470df86dbce8d9601cad1b7f31fe62880c167fd54504a035d17e20e0f971ce35ab42903a6a6bddfa4f65af9fcbc6577d1c4323c6b9abd2375647ca90080ad9e594725b04650e855b790e3370981900aa9b1267ede01755cdf8922fff486a03e3a17270757a42aabc903109dfe8c2d1e7b6b929d7d5143fbc776ddc3525c60510575d9073cfd391ed165c330f392a7d5808e74d7e8b44b4db22548818c3dd43993560c1b7ff554334bc567ebfb3b72fdb6c75eadf3ae0122f45366c4b3ca826423b72df51a05f1b8b3ea5581b003511115c74a0deff02141cb3ee53a1a613e9dc8f72d0366cc504ef5ebcab278993ec54fefe93d05a0fcb67bc559d090a8ca904e94cc162d2f71602bf84a077a08a35a92c20500ae4d04497aa0d2f60f2568257da72f4a089722d1a97f23f432453ed15f79787b4ffaa4ab6e8c585c4092c3bcbcc72cf6066e1d6ebeb23aad024e441b6fc6c183cbc67e23c52e5b531603f0b109d72ed3a355985f18964cf8b27e106ce3ec6649f9c0e28dfa15d8c6b04f5ca976b1bebc5f66c2819edd8cf898f6a70134e3244071c156a13164431e0388b04053672062768c9c9974d75b00c0bddc18a371aa66ed05c93c9d0ae7a45c17dcec7eb121fff0bd477f4d8a67d90fa65fe329b7e8ff69459ae3d89f9d65213bfab8e83727359839b9d9a0b9d543722f68a471ed2578f7befff524a2ae3cc653c513b0e559593ce40c081e6a4f0dc4fdba470019f7458a7e28a91d8043217a204a5147c7212c4f329d318f1169e744b3ef0f2ebcffcf1ae2fa3e30b8929c408cb8440db7297453ab080be8076c9ef7eb0ffab7820678e7882b20066b8e2c2f69fcb0e4b3826a509208371bf179786eb6f40c5ce128546822078eab57cdb3b557812bb25084fffa9b41d60ebb01cb6fbcf463b711792e34aebddd4f1519500df8cd5ded454410a1400c87bc749e99a498c017d1eb9600be27abbdd962497cc7c038cf50c3acc4c00ecab8c104dc17d95de0e16c01e6db0bcacee98fc5848d06dcf4ffa8f1a70aeae7c650c150ebbffab47f92b8bfeadddccdedcefdad9a6018bc943c88f72453a90b789bf16894e9238eb08cde81600f28c4eb6d8339b5820007eaf10614c6701a7465bd9bf2ced289a298ae7d290052f565f62452d3e5e51fd3ef1d056726a3862eca3d46de2e637abaa863d3ba4215a06f056dd8efdc08c5b96a996e2720dce5e318001fbf7d50f79d0c9ac2875c810d5368d24aebc1c602e0e2c0e8e72c8b44e720d87c7288cad682367bf8757a4c39982c99c2d6affd0661ad90bf37252d086ff2093524a2ecbfcf2e2a6b4dd2fb6afcb9c2a707a05feb08d5b0fca1ad4f1897212121604cf29f515509e934e6c46bf69f03920c074826f636f4a3c7260369e10c8c7a6a720ec9ddb68f2676bc04289781c218ccf747f0f668dd2b272b93325567489d2303629d88e427522ff68c3df60fc4de1868d0554524eed1172896d6e002b2a736267af498282ce393a2c2a6e44957cf19eaf1f043923eae372bb0fac7c00e4d345a2e261bace6bc9af55e440074224b4a0a7faf49eabb3624c1bf475794aa2a70dd9057e59f9d3c9cf9d399026785f99f78a733a8ef722d072bfc852b63e8b2849f0ff497027ec2bf37a39358df0df7a1ed93a43ef191e7b72503253c5240f8ba39884cd58bcc1e72844178803b571bada7d6c79f2ea22c57210451988c9fb4aa113cbaed6e8761706eb4978d0b0b315d76ce186f0422508721eb63490855038713cdf3694a7419cdeef4e139e16ba58efceaa519c6486c97223dc76ff8700de95a015787a73a13153049afc96fa238033f75c93d738636a723cc4748ad843a1fc2e3c0d247dbedccb8c48ad12ed730269fc1e33fec5d5e76aa36bc897446396161dfad47716f94a1fbfdfde7e9b6ea52267119221300e9b725737542a4d28e9a4a5a59a1bd20be8da929943d5e89eab4ac31ce1353b523472d811c794fda09e1c3133ce0db4bc8967e19f6969a0d0606ece966c822b100053ce782274643c2e91191bc83e2c9dff662d93847feb02fff7a2421601f4fa7e6cece486df223fa338fde89bd93b4729fa8651cc47a81aa54769917ea67b8a4072735575a0798e9d7dc366da011b4d967104372232ae49aef634b9dba35435fe71745f2bcdcaa27ceb8cbc91303ad2524214ae35f49561b1507e63763ac5262309dd52780df93f84cfb8935e50fbc5e24466247e126a4a7438df39fae2b187b626c92259f304fdeac61336446bf3e087373152b6b6a56711ddcab9c9b1c5eb74726333cb8597f4cc7f9213e52b85e93d9f11b37c84ecf7e3b3d0a16c131aeb734256ec89f38c044520957230a02ba288588aca65d2d1a5d25200f3ecea0e739a720d29a447bd1a3dede6a6f9021284aa1371d146ac1442e3dba9e45d9a7b639c72d0b29e28804fc2c4dfe737b31007f6768c2431656afe753fd2c05482a436427291858661cbbbd23d77edc29e3f907147bf4ebe8029d997f83c1e1d92c4b47172bdb36e1ed951cc1478b7a8d3a71ed31df9fb76179ddeb91ed800d75374cbea724876cfc178dc91722b9ec7a15d5b9e1e82f2490c00f985f7705839d48a18c34bc0dcc10758a6c7afa9876fa32c768100755558d48f880fc4bbdb2fc791ecf472e56d4449b1a61eff7bf9ab0d88f4eb19484a8a9db62975ff9973c6db316fce723daf44eb456d9d188d4e6eb6ec5a2e95b7c7bc05ad2e016b7a62a04d59167526fc7b9bb65b41eb5e1b6542adc1bbbb88340783bbe2551a9ebd73348badbb577222bc5b14416827d6e90c4cea4ef638c7139617876dd6b77792d00f09da64b838472b2c630d4e5d8f5416cba7a0c76e71a9b252307075f257f8254a741515d071d9c35d65d429720f24ce6051634f76709aa15d37c4b0c4015aa27a62e808ac72aea34bf7578c9382a95ba37559f8b676f5d70107a82bdcc3491acb93d26e3c72d1d13f8f133d19aeecef507284bb8b80cc31b8181eb9d978f4d392c32bb5337293f049a695b5fa84577bf0e95c821ba8981454f7e3c91babf6d515990f251572d94f417ddd632cf1ba7777f1ee8a3c7b808279d8f0795c2585d1e1eaebac4945b19eed0168a9b0661c32f056d557d4221b8a241853bd7b12ea1c978a74a2997242aff7bb58d176e41f9466531b3d2623e070fd6085bd113d81160fe56d77bf72cf8580d2cbba4453bcc9acb9ec2b081ae346b27d17b6552a5e6020ea20417623ed91d2f16c71c549a26bffcf8d989fdad0f10c516043089eb18783b1cf247272fee2cffd0c02e1b73a3e06b411e41b8701b8982a4c96bcb6b5c02ec974c5067267e21fb646406429ec9d97ebcac6c95979c64857d126ed668882d5a7fef99d72a518bc7fa4d0bfa3f434ea048048d63a736494f0359ed5571b7bd5b0e32c3d7215732bb71cd303eaa3f152fb7399257bd3b5ac810cd8bb3a7a7b84b642f57f72a14f7a313d1b39498501a9ca90d0aac34604cca6b8eeb1ad41742a10677c60352d52eb3135e0f754a54f5c694cb44f53fdf15bbcc18ea98bc74ed875ef886e7267caf858b8733f356bd5d67c012aa55c9612ce49af75e4f1468ba6b6e7bab643b7ec8a446d92cc0774c8e9fd0807f54ff7eded63f8694f85e96a6db28ca1ba723eae2e4411c2615381643d86ef10613ff954ce542ef8fa4e3324cb753dbae36d7a03aa77ce9ac6097ec7a05bb9e26a211f6084fbb4fba2384a942af4bf9ae310860e005e485d3835673ad18f8d02dc958b1122629419b797bc882f05fb7468726ce65f45da060c3097e9961a029b53aa588b66e3492fdefc6454dd7ef22830100ff35ab1edea8af39aac8e03342edcd63916b517bf2cfdad71a98db856f0d1723960bba2737e55a5b43c4e4576f04cd53b5a8ed61962c7a149ae83b0868d7c7267138a2e04b55ef6d0fee407e1b3b0fb9831b8b8594e103788525bcd504d0472216de8f6940accfa101ebc3a29e11191e96fd5ade2578c00fdd69784ea622e2fd2b4b1c84b38eaa7ae343bf9283bf204538ae676661fef4bca5412ae29cd5549325ae986dc3c67e5214bd45f4bae5d479cf88cd70302c7ebb057ef9f37ebb37270eb1fcbd0864bb1bd6080b397a00c8b0226eb19976ffee98a65208022127c7219e0ea1cf2ac22f105e53101309774218ee2fcd495033457076fdabb3d603872e4196071d0a9a6d7faf20e61b5faa1629171617a6be3ffc7195af4141dd4a76a6c2f2e83712c63bb8ea1fd3679efc5435c21cbfa57f9be60d402832a28dd13720d534c880becd394f129d46533ce721c831e6bdc0044fec75db29cd35013aa72226a60f280247a95cc0455d4cb1867d9c0de1dd5f6b6e44908e3e93fb47dce724a9b27100ad0199952c281a48285cc5f18bb7d6d709cbab9c0d461824db75c72b9b7e61e675d88e70a0c9f0736382766e0b380410e779e3fa5ff495e30f7425ed64c411aed0067d6730e12f7ff979ffb5f8501128b8c3fdbf061f15d2575a74fa4bd4e850a29ba509546b7739777fec059f1443c4d8850acf448932166268872c88dd5c4dc2521df0c319d422e1941019bb334c100bbdac2349dedacdc4ddc72f0140e6251a621592781c9576bf48e2c7a0368f613f6e3d1d9d4efba3e3cbe46d9afe077079353f96b14dea3f14e7856764d32de013e8602fd4cc0bd83acbe72cf42a8afcea6936117241b240ae95f7728919eccc0df93a778dbe28b1942ba72ff974612eeb4ffec18c374e14c3d365a5428840bf8bbdf5b287e59291fd692721ee45d9fbaee18d650c0404b46e1ac7d55c4ee7db2efbca1d3d047ba435b83023fe2a57ae7edc7d2f9f6277177250ffe91e1774a0cd659a92c44482e84e498729b4e6002ce8a38e11eff7f83eb3ba0f0df25a25c548e023399a1fe4224e34e20b4b74bdcec07a77205addd79e129dfe52f06896dee7ef50aa1fdbe5245b6ed728a6a28ce5516348de975f2b5d71deb50b0bb465a12df2b49e7c98ec308c2f819a8707e9de435524c05103bb9cd1704618c5c388f7ac946af4f2d901131ef0972a5222a245e7b8441e3ea6f7b8705e90d02ed8360e914827f4f397bfb4cf54272ff3a97d525d0f334331554e244c8f0ba7670cda1327b583812e1f66951670272f6318b96dacbf566f13cdcb03affaa43be40e4745c7c61da7d83d6c835066c448c0edbc58b075300b5e8c54f7821782350951173985bdcdf4869ba0c8c7d6572e4defb20f5cd6278db286c318b9f962369b7a79c4edfd3afefb6f42a7baf9872d028c8c1469a119e1ad56a5f3061e9f8e7242c9ab2162959ea0a09a6e900fc72c4867737676a196234d5e267a82a07a6a6e8a5dc3c5d76c4e81f5c7c4376162e266853e8da3081a51ac34395b84e4638dd7a689348a37a87619cee2b5e812134138ced3fce4d30e026e5db3d6862d1f7ee175d63d9575472e395f43efe48ca17518e3a344d9db60515ec3393a445c329a4266f182ea1f07d5e63b661975e180827ee0f89c656583c2662a7d045ffa4198e831929699963b64af3d646459b235390bc3943f3b60f4046f23cc7f163fb113c9d86723f7fd06b408b035cb3f4926f26c57e22893589b0f33acaec7c3f3d3edff5b7146aa9e76e4ee82a2682a4097272413db3b55ac8ac499dc05cdb3f111626834715999fd4a714a412e5bcf54e7216ca3e455841ebeb7ee7fafde0f76fec449ff5ab1e269324a5d7ba0db2cc6c58f224e535348878dd91ff9c37bd5d97e3eacd1ed59971feefc239fb838b916072807c36eed8cbf0da62d40c484f1fc971a01c3f86847aa0d0309deea4241a88726a366adbfe4c075f83cd7dd29d16ce1b1d77753eff3a32ec403ae6f50adac6145f253636141550f5cecd7ce23d5e15dfd71868bd4be244296435ff4ce5b642196aa500dac699391235212943a97e4f3588b53da92cc4742c59bb6138b1b3517286e5f3b7af1782c54a7ea4f81b8282f23889669fce4dda3ede1ce2f5f2eb54724a32d50d61e190cbd599ddc1452c6ad02ad4d703ef708c03a851a8c7afcd0f4a7aacece59d8a6e56790cca14de0852fb55565d84a120438d8da1d47a52d79472d96170895febf9f2d15698b3eef22abe84b541f3f52deeb7ec004297222be472ba71e948b209e6d93ef90473a748652a5eecdfd6cf5ec5c4e51ad05c96b14d012d5fd14e3ecc4a83d2a33fb91bb4a188a0347750f93e7f2909c500971451fa433e20747bac6f177f814ae4dfe4f61b8d4d93ceca64a794d858b1ea350074e55850285919c947cfc38c96f3f3a348463bd53c31f1514ab6e4a4c76de65dce0d721b24a9682c852a0602520d499cd2f113509cf0c39fba438f0fd9d31e4ab73a48c361d94a18a1477255d01f51e035f240343d42f468e2d0cb13db82684fc7ea720e8bc244b383972f84ebe108a6113374b1ef356a4033ddb16df2a26b789b8326265ec71a25162c04a70e209fd283751b0b825197b9f7306752c885013256b372643cd2e5f2ba619cd5b53f126052554ac72e9ba89e21b019cd9cfbade7df0d05e0f1ce22f951c66e4897f8e40fe04575c925222ad5bf64f82e06107fef5b9f72fa24899e423a2827505c149d2126643898649e0401d58cb9002f3c6af5ed4f72a21da3339829076e847f8be83139e7ca0bafe337722d2c86f1c09d4016992561d0110bd3bd71a8df890411b0986a1b2a15b668fb2ad76ee3402ed267131d0172866b895d0741937a86642608cc96cbfd261f33852d8c4cc7f130c0cc35b9d861c0b9f038a33139c6822f4747fd88d2c60695a659ecb6afd0a7e7d3f922789b26a4492e8f5a0ca462edbfe47900c688431c0e099bdbe31c8f359b4786fe63146dc53133431832af571c3a90e9fbd48e1132d7174e192e6d4011c718106a78fb5eefcec43290107133fd35fe471228c89f989a5494e7deb639bc4db3d94e08bf361d43dda1cf133f0a9453e61b01ae4e02f432a468f4337e1a36269ed5a723b730bb568fc601244caf13949cd6730ec0b54934d516cf7a86759451462eacb2f572b779761470213186b80aab9e09dcccf38764401c3986d09ba97ab39b7345a572b4bbf32eed15b1eb42d3484039047cf83db0471a74cd27ca041466c676a96a4e82d0f482eb3ac68a7c7e4dbdd92dc83cdfe0e5e21b874ecf43c1e2aab4f2b272bc61718f4391e5e43cad7fe16976f9bf6660e9c1922ead56eff2af297b827e72f5d4dd36a70b1ad46bcb4b364ce909a8773170da7631efd118e55e5d64d149050ec950e6be026c8d23889b9159555557c5120b960c5bb21d40fcd0375149e8726c48166d60d1fbf1abaf50983d7f258708398cf0897a60e5a75be7aaca28b772c3c06d7388385987be9ec1aae67c19d246967bd3fe0197a54a2078922b563e0da3439fdf64485d67f2874260ae3a248b7237fc23c3399af7f503e20ed60b7f3c680b527aa11a6beba9def91548bf86a1ab0780f1515242ebac5ee2bdd08a1804617a4380670206300d9de2a5287bb3be2be8a5befaf0f4e305b7682eca1f0a720fd98267d4e71999de566a1750de8b52a452ab0854c9ece591d3f79511279e5ef38ea7a7ab3635b8a2b409b040264a7d888b56ae2f683b9f224fb0182add9b122d5e01015fb159daec67725ab50e8611ceba9e52c1550fff9ab08b5803ec3f72199309f076f5a97b3b276f9ff1f31d5f4d7797d0b39cb4eee103962d7990976b6e1658dfb70861d892b096ec150e3b9c57f49294d6e0070fedc88a51b5ec390a70bcb50ef01846c04815bcbe3e4f449e6adf248115f2a71f5feb0878bfa9977267d88957608c1792a6f55efc6737077668b015436c7e0f9df3a16bb94190fc72cd2ecc64a2761e0ee6ea0203014c1bb9440f7bbe5438de371e9535759d592d72290bbf89bf0d7d138765931d9ac8597160b70c9197488a7f1d544b852fc28172c933b2f4ee3cf399358f63177302a39fa1febddfc97571b281ea429c28c6627093f22ef38a837c27d90a7e69f74d787c3d509ebc110f84757736b28ba23647724a6017f5a0346dab6fad66e91fb61d713b12dae3e2ab94984f24e345b223b93eb4cc1f9b4bd49ee03ca492d61e80b566227ed13411bef5b3534084f3a454837288f058644a937b094a9db0833641cdfb22c5138658239d66f520676947f1e020821afbe20a3c70dbd9fada20c9ec0629062dfa7e9bbc60be541ffbee0e69ae3d005b6f9654633fbd1682833c490eee282f4f03042268bed79fda872204375b52a19fd393405b056dfb4f1b3d6d8bf27d73f7a8350d98837501d6c5a5a66c254b212a5440c3ca94142ae779cadf0c328eaafa39a9647a11ff5b99b16fa0901772e5c4f97ab79b808137a39a09f231de3f4696ffb75a7c8740fa26e25bd25ae052dcdb6cdc2120b2cbb1a020d30038f777ee4c3007b708eb360d4f22f56d2157721d46f3171c80ab57553bc00f9010fa2cf7d98ad0b7c0bc44526aaf017716903f22573ae6311d1ead8171a7ed67440bb95f563da379633497c13e8ef816f71e72d5727c5604cddbfcfca61c5b751d6ef1cd560fa8562bbf86316385c02aea8e72fa5df68468d0f8b95f4f82913d51967f7f01cc09947a1be080cf92cee32acf4c22c7d1ff52dea85996a004fbe26dda7072025a4fbc732d842e3c5fb1becd8c3895d747b8ef4866f0476fca51498eb8a33dd3f3d49f0f03c2c2357de62d51ed38414d969fa11831f204b82bd9132a0316f1b4395e48b9f08327b1ed221ce3ef729272d8a846f04910703ed6e88c851749fec41b6434b02963bdd4e4f274b57f725531651b945d9aa90f4ea2bfea8f5ca93bd4f92a3fc903f1f5d4c6f62d0f844bd21789f4881fb995676a037f5b6863be113e2fe0b328b8c54aae3da1b512e1723b0b32091e8832c69a622127cd3f4fbb53cc6f3432fbd2f7ac2aee1288599521fded7069a4cebe3c97163142f18866043018b4f0fd6492eec0f62c2302277b086ca75c757dca81419af8b188986e4f0f695ca0626d774f7c1ba7e1c8c5fbac72bbb0b6ce2c3907d49fa19d716fb396c184e3237f2a0b43a5c3f45a191758934ba2d414d1f485ac21d6f2ca9203ece9ff1763d84b69f0dc5e66ce31d51965f472c924b775a2495e3559faf327edfc23a85e1504351c1a936dbed5e829f5559f7278a289b6ee951876a8945525e05f0f674a834773dcdbd5125039ec931eaaf572718f783c5f7a66bb3cba12f926b5a906213659b7db81c5537a8cf34db75e3f11fac860bf2817d773060243e67ffedf14e945ed00f66490ce72bbe23e7d4a6c7260864e06f2838844a0879652cb36a48508622b426455547e9d8045411f398272b4435e6af2d2ee7c38371b866da0fb500f0e388d296832b26a5fc30abaed8f725c3fa4c1e8ab53f5ae5db68d975f6219845bc73a6f46722f77e474743b65ce5e57960347179b66fc4a2dc41d9c7a93e2ec6297699ed3cd4d6bb2e29bc6bc461975016fe4df70da47b493c807e41c0fbd2081ef26cf21fde02542553d99f4da72d27ca25915072a5aa4c430abd8aef2bda70186e518f8cc908f10a7823e79cf7255eb88c27c86be8beab83521e7c48899c9eb7335f32ff76ac930fa4f8f75302fc87d367269f7349d10b07aeb7851518c478fd95d2e77020ea960892c5314da39e9924b84845e3e4944954689a07a5d1da6062ab624ccea3d98d8cf3c166ebe723a4fe9ad5126f2e32223137879af0a595bdf97bf695115f723c70ccd7be550369c17b52549ab624bd82352f7cba45012f0c8cf8d96f0ebedd960bed053c3cf0284de4130881a71c4ef98db80193ccc72b760d1440fe528c6b304f5418912ec72b2220c12d0fd387cafbd2f20bdfac1b00948aae555a54f3662536dbdcd930f6fd45de3bef591a7de84de2a63cfcea7084f65576cae1db016665219a3f63f357213cbe095049ff528453934ca52084221dced12b93c2011c727db03934c0311720814e9a20850e030b5fc093b101a3d0069b481404abea6e942a2de8a25c8b472f25b5fb873b329a11b5c377b10f46fdf9093d3849e58991f9bbdf83ec717da726181c9b99da05af71609b503550a0246e6302366f20b61e51ca01155b4ace00d33c4ba64f9b1673a8bebf0e17e903758ffba6550a3a5c8fcda7df8a0d21e3f6d0ef8487be159f8e21463ac8436dd8dcb2f718637baccd3dc8c9353f03f20ad721127bf722aec5198de096db6f77fc9104bdcdaafd2d73d76c390d92d6ac38072b08880ebd8dfd2e6986cb40cde9e12f6debd78146ba41d4f9387e8fa4a26df088f2cdf85fe3749a48f5bb4d33b54713ded725eac3ed55de45b5f98ee36f742468e4c3dbd0d22cf81123fd2d177a95f8e03580222d6925cc8416490a8f40ff47255b58b05fc7989b79d8e5f932dedbae3a9d1417bf91027041e92612535a12772bd7c9de9ae0409087b27717640b99824d995d0d3cf6e6001478035927a290a726fca577eee418d96ba1add2aee45cdba0fc3c0cac717af28eb7486199ca95372b4f9ece63e14ea39d888d90f3599c2e17adad8f11655cd7b081e782674352172b19218b22b3a7ee4d8db67e185f55a90549e64fd94d9cf778f8082c432377772794485e4dd1860f04d2c123c7634628d8d599b8e6a3853dae68200967290d21b443688111fa8febbb20a103e4c455259fde17050c2480e735e88e06c85e7537283be28390cbf7e355354a5db5b9f3bdfceb29e0d51f1126941a4d76bbfb2f2180af4a15e42f86fa9985325164bc15b00f610f4f3774646a0b8861c644562d372d6b2e3090f61b4cfbbae6d5fb72f8e18fe3d73a35ae23cb8d7a7e469b4949b171670ae6744834adab56f89fea8f35b3c56f26935468a3f989e9212ad6b00bf72f3ab3ebd9456aff8ff24703ab7c98cebd5599057878528293aade8146bf7e60c6f60440a90233c4283b13365a200c94394cc94f3ed870a5bc3cc80c01e09f6721878ea7f528d049d195869073e226f77864ea8dc49a7eb6acb5bc650483f352865a952f3cf945d03e735c83937e0e497ef776b800147859df1bdc63648f611720a8b320b8de718fa6eb602dfd29f5ffd49bf1280441788ce5458ae230284eb7281d35d1c66c9edbaceadf1dec97df24e20977bc3666d2f2d3105abb9ee8bff72860d1e83e86457abe3a888be8ad6df9d753fb5ce19543a342cba8afaf278751a36e9cc17011d5056d82b7516d0df8d1c5ece2a5c31c27fbc10a000a866e39a7279b08609afaa0999ff99434d1ad38ca0616daad810e22e0be04daaf6cf9dd7720cde475032c2a1f691e431ce2e518136dcaca18d404f3cbb13609163fcde5c722dad456b6443ba3db869126482f95998a2450f39b92c6e0295ff6a94f49676463d48521365d682060e04ae50a613e9382daa90e95355a97a9f48839aa190a8729316507c548f054e19c1fbb559f6d154dfa08dd3b5ef6074cbf0ac9858e44f723571311cbc60cea546adf321b9a283ce72c2599e05b1410b4aefaf86c64de872c2c4b127acf542a74e2bcd293748dcfff63903e9af837e818132d4e70ddd7d2e33778349dd39ecd3c7aa61e3027459668e524b86c561d1592dfd5feb0850dd390b7d43b091dc3af25969b572f9de7507c63bdcf9791d8a34be4278c3187255725c383b5cd054f748999402bff34c7b03300edf815e41f2b5ea113485f2c115722d831441b9c615509bb84b9cb90bf36c3f5801709a6966439b2a42558000e872158eaddd063c5e9cc85f0a674d1cad9fd1b1a7d4577a06f86030f4a2474d2a725d871ec593ba1e8d77e004587de794126082f5390141a725e52bf4e86d21066e499c9570eba46417e9e92328dcc6d83b8a4f4199ef522e85674f083bbe3f4d4e109b9c87756643a20438926fbfafe736b79ede35e40a3075f21c79da51642872c6e6e4f70a9056563007436c8ed502fd7556c26aefb1ef72057b2a9758c201498f2b1d5fac87f109073324b91b9219385d9bbc5ddbc3f4c634ae68671a49d072bf1b3151742b675af55f0f599cdc308130da3be39580ef62038cfb5ec003c92ffbc300a8dd76187b5c3e85e665d38ae7d7878d5b4ec704ac8a0985c471f253725691350f105d1cb30dacc267448ddef9037ac3a77858a1c9d95f5ecef5dddf417a69a7850713da85c5b7b4ddc5c89d682f34a05f5ab626e4d8a267a0ed8588722cacc5f8005557cdc79f8cc4a078ba8bed9f9d0383756b5c8189d70facb30e00ae0d25e6b871917c5eb53f96218be8aab7a06998e1af6a50f73a915224fe157237494c3a1b679489ba6997218da50466eee80f62f5327f2719f3af1c32f34b2b6608ae1ee59100996277f4ef40ef51388472be73df018c02179d8d084ec22f72457a3e2d5f0392249ff6fac536ee9805e0619b29501cce7eae15825f053bd872196ed498ae7c0ba95ea7a3003d98f04d96443dc86f918759c96414fe81defd11c31c34677220f7ce1d4fbc936df421e2bbaeacfff4230d4a23bca7422dca497208c2cde2e1b11baffba7333aedc79ba7af1ebc7d6673c16c3bb936d90f7b6c72f56ae69580516fd005c094f79503902d55f6f72a26ca206663bcbaafaca0117256e026e12c7fe5f711ac62f2dfb5ede9a33d20666d248a9a679f920a4b4d5a72aa674158046933d0c2779fcb5ac3b7b37692b0efbcf74bf03253bfcac7535872617f3ac02f96670f69584a25e893a58bb3cd93ec1cb1317491167249cfd3e172a55c78f70d2bfc0497cd54ff7de7e3b887b0e287ba8aeb9e4d8b67a4db9f2e6aa6d14d6203acf958f4abde40d2284cb1c37923a4d3c8fa3e60a68b00bb32cd393befe9e93683ab336a810cb61f2472e1c5cbbf0b12226fb3b19c6577190f65645dcc7cda247c58d17efe2d5517ea8cb0a3f6b9d8a60f8f95b91af7f9448fe4720142bd00e95eb64fce558a61dbdb5b933190c0c62a87c1430f496e3fa1fe84361d2cf451461b24cbcc4644ffdbd887685c7e1f84e900e0f267e35f81074a367258d6f973ea6bf4bd9dbbafe3e03cc6641219d4ec90197afeea6726c9d42d0272f8391a47f41bfb864396381e354d2266d5c4aec2e825c2af6652a26b06877c5f7783c41d27d65f9217ca72ad0a4b1161ee9af1dc1faa543ae619121930fd2a50e9fb5bbf85b050bbff158e8f0df4b3e8bbcbd4659933829ebe5dc92f3a576f72825af277cd8b46d60572d5bce36b3c011a0521156eb51cc591003fb163dade6a8f4d339af5bbe03935da4c3c3b7d25ffda8fae509731d8878ab9a718f46c1a7223e58656e5842b27e37e36597afd560dd5463fd1eab7907b189cd6727edf5e7260db710ce84a9818943183e3d4972f44f7595887b38bb419aad3d6a7450f857285594605c481754c46c8feab5bd0c0f02fe4576a2c5ccd3b05368acac75c870f32f60faf980fef36961119e211001908b83d517f38015b30f62894a3a6b40104887511b24b451a389366eb34a75bda4fd65444db507bb12d73b529bfa6afeb5c1317dd66e270383d65775c030e5f2b7f123ab3f2ab93cd5cd3504742690092728f24cacf61586a6ca6c963fa1346e8b62617cb739315d6126314df5e171fc072a1bcc387f4c5b04c040f9381b4aad2152e10346a5c6f8947b04a95eb45e113725e666fd02c14ad9ea3bf5e82985f9cc6b52d123a136693d2f404bf753effb4201765d25851a5c957c17068fd73fd117828b62b3cff81248f182f1fdca281d0728b291a0d1671c148996edd3bd7a49a23556908b72fee6bfde70a0a0752ac1528fec1e7d2a3074695654fda306be795738f608a85f16fd9090bba7ad7cf196d72600e7c308146f5ef57be34815bc739a48e48105d9645b324e0818c474a5b6f212599ef1241c739cb5f97864a131555213a987790e6725032330845330b859b723b5fc3ac2e434fbdef935d59558340aa90b27535602c5c2fbfe64cacf9ce12728e63f1f174e337483bae552eb52f71221299989ad0222f97885cac2981063c7287ee86e27bcf81a6974a3d816830d493d8f787be65f9d4f2639061d95365af72d48ab1eab6949b5ffd84cf8afe651285a8ae76875bfd9b3f1edccd8b745c3b09ccbb6a283d673254fbb64181842df6b7914fc73c0f5985594c4c337ce51e25722cce9299aef40ebca9996a9be9841f045dfcebd402cbabb385d61c34c8358e72fa45fd25c71c3874ae8d6569d5d9c5975a464dcaeab08931f5fcd45b3deb7772bb33aef8da2be198c851d01da1a7d8f52d02d9fe225442cdc86719d633724136b7af326101bd1635e39cdb5f938b0401eb09f75eede70969968e608a2ef319553ca339b4641a7a91830e65f8e50d09cc596f2993bb9073465cfd8a180fa377040840fed8552c39018e49341f1371e5a354b50d6e2539fa259446c4a803ed692352eb0b10d7726f31ba93453019572b81279066de19b9bb99d9a546cb240e5f4027c6ab7e4eaefa211208ba0d97fcb82511678450a17b4a329ff22bf01a8cf03a3ce1e065111323cfba3e081e05230375051449745ce404ad50b4fa4ca4e3690b4e1c4ad37191d51c127af9c6c6243b0d6f278c6bfaeb7fd17d5627e03fe62b7226732acecf2ed89cbf1820f41d659c59ebaeff72d5ce6ecb40152753f2063638f3861a49f289924f4aaddee984ae6ea34f9b2239be659925e37319f4645014726dc7c0b7587ae7d3a00e5dee295d87bd0f0ab96c0dac7eb7c5c9dfb10af6c772d1f248ac6cb6264aeb2a0a2ff31dfd9eea4554d676a42c7212313e58a2897f72fe55b4dd76c5353c9ecd346a7d336b239e0f40ba996e4391f791b5809b3558264b04c26855c342c2e7e5728a15b093994bdfc5e2a8004c1480184e0eab8d6e72cd1750937c119f2ed90892aa5d1f3014ef9527b66d4cb24058ba04b682b47772a38f6224fa8ec8c83a44c75e689d9c40f07baad8f40108ad3e327c472501bf727dfd1968d9d34db6b3949dcce7710b320e07d1e2be8b1d782b3b5309fe836872c4f22a72719c7ad52e1c1f2c58d42d6551f3a40081d114f05de79bd71722e872c30ba41f5505bbf740d82a01a4d916b441e610f9cb9b4854ff02e9e062f39d726d8b243beb035ec3ce1f3246cd2b4c0c612b7ed389b98241c3e44d8804c8b872edbb0da71a405e884713fc9741d7787b31692d76b563c863ccd7794edd05bc722539b613f9340bd3a6e9ab62c2ae79712efe448c5d46f16461e405773ed0cc72cd832c26cbe7543d91909a5d2ec99780e1cb7c0432716d6bae33e62905099a6dc277007916e61ad6da892b6e0785336b8225c929cce708859f566144dfc4cd72bd0aca57e651763989b66de95e590cd315b392fd3b29863a3f66297c7c47463f6d45a433b5687e7ca0fce1e6a54ac51fba5137f7c6bd82af7eabfaf767063c72a184fe694f72799e91c553b669ea3a0b4efbafe7d937cc84ccacc68d0ce9b27255500aa30646943fc19003fe34ab3163b2a4bc21c2d54de9d695a3e24205687205bac0704ef909cc16a2779fff931c4368493abbf759c4ffff129cb06e059d72f4b5bcf867caf862f09143481a1c736758d28470ca1d2190a72fa9e745984e72a95bda6a20bb9be901410fbbaa7f0ebc466011a73a3cbb31effa7635c69fe272039a4b9ac2103fa7591bee2cdcb366d30c095d3d5b8511b684fe0b61a1c3a13b7ba92dec0c8a0f54f146c447636e4abc69c8c53868423c09224aabd59c289b7244dd84ca7fdaaa341783e998f48c232211c1364fe8fca76fd1bb602086660026c8b5d0d355fff27d0e5e145bc6881ad57c525cdcac07fcd7047b645d44fe6c721c5bb26845ce39b470393becf95640b1572253cd8106ba37ad98a7bee801730b5de43a57fbb6a5191cbd19f3e418bddd376af11ffd79b8ebc740706e2527a1259bc43635e791d27cf4d436b87abcde9563e71ea9235be1224c2d98e9eaad757282bd51c499109e8d1b88344b11d2fd4f68d36177e3cbfc79be9be49359900e14b7c25c3419c686fe0f6486f3ca4242fbb509171d9704833fb958d3cac7a2b17216105fa54facbef8bf9a70d6d16729723bf863ed36f7fc2044f0d3d9143e742b7b00116f8803df54edcacc1fe5cfe094646d30f44e47fb065bbf658655f3987251c5e68b3360278449c2340e6aa88774c05ad81d24698f3ae5c8061d412e477231595afd388311ba0f76ab4df0ac65e4da33a52295c837aad908ea849841c3725fa0d72875b50038b0f5722f69787078a3e0fb34bc1255232f8354ae8339ca729c5ca5a8112e17876e3b6068bfba3b93c490eee13a8e9d94c900a150add7ba562daf095b7a3d41d9c3d0e510cc81e57ff660ccf06557038a0df98f477ef0726b3fe8f5a426f3f2314f4cbd602f980b42112c32127672a2a1d7247864d30c6d7268123a0a79d10c7786f937bbcbf942c2d40db415dd68586a430083ef8b325a72a2b98c6b91e7c9943919d5f752443bf8953b4908ab7955b8796aa5e2e2061372d24cb690fd478b5cd7ea95b7126c4fe3146759a274175a45d5e6c62b05edd54c535ff560a426ed254a29fb7083f566659e1e18eb6eb14b3b7086c2b8bfc4a372b3706d3d46514121cacd447ad71295b219d208fe8dbe979a9461ae5da7efc47283269d7b1e4289aecea488fa6bccce07f5ef24e11d2c31fee42399c018afb972c686e9607eea9bf55ffba6ea11d05ca55ab94fd0abb89d43b1846f9b97580472344a6972a1b5f8f9a07545881b0f80c0b5ede9c7dc8eaf0a14dce693307c44729d8d0deb4af85e1b0a13fec98894b561879d18db84bec16c119dc80e5e412572d19e2da3f28e81e926ab6ebd5662fb851b19fe4c6b8ce62112545e15e7f4737202ae167a21bac21bf6bcae257668cdc7702d838790ba6d73afe29dda9085f7723f6b1974b9e823f6b9822f5c64b11bdb94d363039c90173deff8751ae8c1780ee0e7164d14090423dce52146f800b348b00c6fca4b56852171bfc361bf03f27201ac9d90f32007acc6b523f6759355a755cacaa99fbc1e2fef8392dbf3e0be721557cc668ddb3c7cb8e832215fa108aa27a915b08f51db36445da9d6654b927238c94766aec1cdd40e681d5aae89b4616f60191a842281d0caa894b463461872cd992cc13a08aac0f6319bff01d1632b77229985ff28cb78e56106a73093fd7250dfe0d1a91c6ba2418913b63b003b067c34cdabd188ca5d5847e396ee0bd5729bdaba0f80f535ee328e39c4f4291e42b3b12e9d4c6dba1926db1e782cd62b53966325430d23d5409e4bbbc39ae28749be414e513e51443e685331e4c78a94724a01232dd7ed2d5a94e011341c2c86472d43f03fdfd5140d99829d0729268a72b56673d5d890f28408d15cd28908b7309ae0754339008078dc7d75d25fa9e472a0d3df1081a6c49569e2186ba5c570c1d8629b69300205707c15401502820e7204ab94f792b6e887d9335bba1f0418571ce21ac26ce1f1a60e18670331528772ddc5b6cc2cfebe9d1db631c6a1307a1ab50ff071adfb6382ce4899408ac4396d95313bc0d07851c485ddaca5cea932c7d9268c32bc30017c2c4918571b1899720c76403b0889fb6e999b3f31fecca9c4e9886d293eebe8d4940608119dbd234bdae57ae1d8778ffb8f9450207d3e81f199620a8d3301079b0de560a80a755a72c24157df8dd67b189f8e4a7c6ec1e05bd01b84a9e5f0f5812f79e622a2066a720f5bb150a8f64594677c79c68a05cc7b4071cabd2005b8f541c008937d7e7072e62a8cecdc36ac275033f6d592c752d9c8480408a259b74b41b0a251f1359008089b7ab6f90420c22373d2996ae5ca0894addff7b7c599e1b1cb59a8f1c3f472804995f1c6f243554a4a2149e190e53af5bcc835b642901a76d9c9884538fc626d23286300a103f862ca2518e2f88003ac7e7427c668ff7d2618b6eb5298ca7280e4e1a74c7deb620dc48740b98184b59cd89eb59ff8d98199f564ebeff9f5360c26c7a1d9a7ac021b535eea4e1b894e52e9462ca5dfc8d2ce37dd4ba15e7636b1c11a994a476d1e19f466d2321d0d38bfb773eb29c82bc2a24a90019db42c727f977c17bb80dd174d5b779b7ed4f36b6897a7d02a699e2c982a356f6a97b9729f9984091f3eb6c4341c300769ed38e07219b1c9ab55e5d765d7fff3e2b10924ba3bf4935b957db87f676fa73a9fa0278bccc1542d528daa5de0a83755624e164250b8dedb5067de56a4d622b04dbb4be3f2e4934eeee6ed1899341b2999cc72f5de66bd95fe3f13faef40925e13b582a30e11c4f7ab4cbcdc3b833ddfc9e6724471b988ac7961d032a03521f02d308e1c61efa5fe8a5e5da0917bd65993f42cbc5767abd6c265bf37d3833116f389351f639f07c7a947269d69428a70200a24372596fb62f21ce93d69abac1f6958d976e9cdc437f91f4cb92f7c1e3653e567ee3467ace552b7b099cbf4ce027a6240936f18619525062a49ee237e8042123e13444941340f3fed9e68207f1f6931f7c74fdd19b21b2f9c1c96fa1d03c5ad726dbe841e1e66c2d0c8926cfc60d20b52c74c2d01a2717aa9e0c5172c0498837219e1cce4a908cd425a9966bb05bca9db09a8a825264ddfab69f486a11d2ba272e845ad9338bd01b67428caa105817825ec49b251207f8796fb75f0d70993c5727a329afb58a9fde2f61dffe09f307dbab15cc935f5432f93c1cd8d3239c7c56c994f61c3e7a3af624e61c48538c4f00fb79e7d0e69d8a07a4918001910af9109ca1b9a00f6c2fb08b8704c687a76a71b18eac115e5f5f67f33b21e0801effa7202357e8268e1d867ea1f12cd2bad7c47ee2c387037608a364ac5537dcfc3b318958f3cd03a723fed7daa1d0494dc9011ac2b35c91c1ebb0f64b29847cb541c6f8c92f38100f82995ef4f23fa6195e6dd2f363626714905a1586a62099424405b226e879acfae5ec428972dc692049ba016a44df90b3d2d92895df6c9d588b4721fd8c12eaa8e4a1e498283879fe087f21c7e2ffde6f1ad795cdd29616392d9728f0e6ac8835032805ba1099215c2aac1439394a2c7f302a6150bfe4646b8757288ae0b1213b75c97e3c851e04035297973dc20649aa637dfbfbe102d8349d472d0d1eb9e996ec510a63bed88e8e177dd0e8474bb4ea645e5f3f30c569c0875723ff8bd5d5f6f4fc31f91d5b1d41e36861229b8144a8ff285518ddf55bf76de72a9824be703ca0936acdeafc7e9633cf24dd9d3132f369c4530a7d50951868b72ccc2548af80c5e9294d9686d7946e3f271aebee48ca258a110f5bbe8f0db6872740c49f8cd423d91decc14f18d17803926ca113b1030d978914c6a1d0fed0072f0516de4db4c1f30e0f8b29be2a903c0e225e97f89beb3cab53ccdba10cc9772ba49221a635da9bad9d777f4f8af51f8670053165d253dbd4a218799583a5a5c6919b2813a05e2b6711973ac23d10c0f997fea5c36703c55cc5fac00eb28ce1f5dcced47eb253fc60b4390d6362ef675ed37b49c06c35faab8bdfba6c51bf772abb0db9a1581e295b3df36110020e7b0529539bf03548fccdc3cac54035be6726b1695c40ae1af8921eca9d8a670d7162d6f6261145db7fa4244b8050f86d87234f0ecb099b7823310cc6e52f83a616ad859ac7d78c2a61f15b236351c7a6a65edf0751a83acc359a09cb1bcc543660f735624fc703881eb9633f082cc13d143708609160868d74762ff0c57bc770f492a11c648a939a1175b49ea911d642e51d39e5e199acd61c7d261a9fe4c909a1a6b1959554776d817505157e48840f372483583ae75cf5b64d4bcc4aff680612f84c7b3e8fc30434ed5ca8284db5a0b31ef506a7d0b4ca2ae8ab5257396d35f2f1a561abc224f81a9e35364d200d72324f0843cc4e519b0c0b85142c20de3f23e40f22afd4a5035baee99263a2ec95e636abc9d6efad4ed0c9b8bd9e48e44056fcddb0e59fef90349d26b6d4f2faa7c72297e13284bf106bc9f8f9a56a84634ef91236531d19c24a6be7e9c2a4ef11372204ddd4b0920f9abcd893ae0ac7fb02b56579fc519ecc07d1d106e92d37760722f0fc956388bb881b4bf3625cec85267f9d866d8998858e844c78bd941296445f5fae545d8fdfc0928268fe3a32a77a6ff11e9de3ca711045baf63fb1b0bf472deb800488757b65a8cac11f66628584885675463247161ef087416987d85fa7203c6820d0954f863de87d2aea9cf04213da3db769ee7e5d005a14ef5f45d403de4d5d50ba3628ab06a08178679708a7460c8049d218ac5ef1091dc65ae507b72962b0b9c9063f14307c05286b7fb8a54940196a2a3d740e21317968512ed5572cd5c8dabec231bb9da2ff399c93cfc8382899385f9e1df4377dba040da6a84726c9f32eeb76f434b4a2c4e5c3a6776e5693d7f0a2261109e38f272523ce12372010d1534421f24ff6477c199b79ec18d6c996709e56db56bb329b3ac34d3c772d215128f7204b6303e79045683c8dcd29630266ce4da196e78d468d29ecbfd62752634f71f4b2a42486ff16e858712ad03c6dfc3139f318e7dd7dbca97abe15bc32b3f52a5ccea936e13bb8c3c3d33b71a8ba750358a261e714714d8e1db8a30fd6a4c40a62e81fb5e34f5018ca42655c564aabcc00a7b3fc8deb968c51997725d1223c8055d5bac2358180f75d7f6629b1ff96861b1383f345d0bbd36f7127219719bf44f419081e05fdf501794f427c2f43b6d6ab8debf7ad52582f1a17f72d0a91d7ec32dee93196180bab4094236b29bdf7321f96dba369572089b5f7c728bdc7cb261907f9c90e0fe48111755e93c48c01b6d84af534f3c0af2ca207a727af71971c10ee889b9bdaaa44b6b361d4ba2ed3be49ea67b93a694e1d680f10908f95073a2ec86eaa547593bbca0d8e850705599e6322e8cd0173d27bd9deb72d04a81d1d1c29351beb632654c740c1d1ce8081cf3a95296804f208599681d56b50a91159b985402e1270e4def2acd399544a5b61779c38a73d5ffbe9f4b0f0d125e6aba80f8db50794821412933fe77ba3af30b6df47d764153c279fd001172b2865bfc95069e5b48af7f001107a200fec13673a684c81f2d0a95794792b772654c3b527b743b9d6ba0acdaddf60888240e11b18af35a4230cc2d43bc628372fef56fe514723a3a0c1381ae81e0a06ef8613d863c916229a31bc794465a8a08f6801c7777bf59dc1d6a8ad2c0c686b86ea2b59a781f3f789aa968ed91fda413cb3a259fc82a28db02b5422b011ca37e23a8c84846e3c9c1e95966fab5282a729025e4cafed45ee08772b50eebc5e36c394969c7bb1542eefa0fe2efc02ffe72308c1808d620ab6c21654adf27ad771db03febf3dac4491b9b57a86233feb20e32565c61e92a2660e32aefcfde617385bdb2ae2e3f34e1b23af57c7f649afe72594c67ce593e088f11d82d02477500e8a06ab446b6f8ff56c941440066da74604af5de80fb25020ae7eb6d56c33b3c97ab1665539fd07a9bac7b44b91f2ee972115bf559776b1bde76a6a82d158c87c90ec57d8678363bc6f00d8a596def3504fd73f6e27afa0963da6d8b8ead81bb055e14e8b8a93eb245b55473a58b271a72f4800bcbac103bf4eebee63d62f79650de8ba7303d96e6b63c94c581d8f443582507f737156f5c384ce89ff07134bba765b4b65c9ee8c296665e73662409b872596b1b82b659d0b1d6eb5e8e9729363a077c314eb35c97508f8c27659afc237274b224e65d4ca4dfb85519b4f75887a84b48d31a29c164c13609dbaf0f7200722fbaac27216fb156f6080d440b71709a49b352ce5dac97290e48b3838490d07284f43e7bfb00ee4c3dc49b4d3bee15dd7985db11dbf570b5f05ff0482449131d3e3f35330148a67501f3b714075cd248f1eced86252f3df53773bae5d0ad3e3791ca45e5dc682d7e46c632f7bb773bccd530b63f48f718941f74c890f7bdea72c7815e0d4b0ac2bef382923ecb87d6fb9c04ad18c12415e5d25fe17625c74c34f74af8eea8ccb04c23b908b993ce834e78a00e3065d18d174e7b5b9fd675f07295795efc578b0f095293d4aae9b564c0694d5ebc2b4250fc86975f0c208c7972ad2779c7f81e84b27c09fd6133f319cbbd5a8ee5901d3fd505b796533a03a4497d6148ac4d07dae55f8581ca7e39bb72fa85989244ba5dcd02ed432a945a4c72d142b04871e264515e3e8b5807e1a62d3eef9bfd653066018609860528e31c2b4afb19da798f3037be0adb805f8712cf112aa6e00e3e238b05a0fa7aa086e81ec9d492e6c8879b5c6e0cec04b2895bcfbd10473fb1b0ed2619d68cf5f6479372cf6d4ba5667809b1e59ba79dc9c314580961cedde0682aaa444f8cc299e94572c4eb6ed5b9b4d89e37cbe12d3bf3a4547cb99eb29299e98d1b70e97419587a1ef2ccf076f4491ca42ba3497b0de23bfa823c20b949a534345c00238642bedf721437d39fa84a15734a869dabb2e8788b6b6bf3d7060489767250c0f92ee43e16d6bdae4dbff2e4bb7fc1292ce90bf3de7a24df874507a9eaaa716b8fc043453be28c4a12e51b979115042479b0f72b5ca8fc4baa91839a0a247d106f33ae2d7274b0887a52e67b2a00a81b6476994e480bdcd709c210fff5415b62c2f049eb18815d9db93a76385bb1ad19e2db9738014c513035bc5172661b2ce7b98682cc4966ff46dcf16a30f43689817251e95fb438e61221df5d247105298bd9945a447267b7d8addf945f4f790572aa9189e375edeeecca57c263cedea18ee77dc2903c1f9b68693f9528a82505abfe6ffdd31c73f9248724ff0f7cbca107bd532cf458ad29e780f9809f1b8b567f2ff5ce334fa7740910b9fd23582ad487bb421cf80fa49b7fd56998c8f52d687ad4039cf9eec7540f26037975f536b0bbeacdc85056dc2631c7c9ac1e7fe92e34e9fcc7b44d04fd0e8534e8842ffc03040099bdc3729a6e8af5c7fcd756cf267aa6f1a233685d15a3bb4e3610ef3a407da1baf2bb1c08c498ccc3e7b792ecd3e43789c0cd8706529d4a9cd6d90ae211f2effcf4b8292f06cae36027c00ba567f3a6b8bec4d793f7229ff75125b3da357be2cd38bc664675bc75e2e69a9ea77ad4c65f771d58f31f0e94c9d3df3d91cee81f8fa57f72955e788d6658fb8643571d5a2572381aaa694fa3ee7f8e62efddabcf4f11c97286abe0dfbed5b4cc0ed1b75b2f2082de5600e1ef022cb8a466de0d3fa1209872fb25eb0cb7623af6a5612ba65e54260dc0c5a9c76ec0713578cb77a3ef74d2722987bef66642ef52f392fe325f80727f491e135db8e72a048799f0a3366c4008fe31f51fbed64bfd71a6d02a25130140233ebb98c95abf2ce52f012cf5e65b0820ff52b6764006a7d7b4c3c9a4448ed28c43416eb4cc22c03ecb49db46fe3772d06a1a89193d1a1e9bc05b1875a5f0edbdc1ca567d3058edcf1c4c29b67db6721876f0cb857c663daecba7f16ed696e5e5e711f8cb6ebfc5b458225ba8f7685ee4d1d054fe7ccaa927273e274652c5de95bd1ca799713c61cda72443110ab472f3d4a630ec92244e961c59fb54830c12854ca3788e9dc57c45403cf839c010289cb2a1dc139356f149dbae5171948079d026415052260000199a32dec1698472bafef4d9d1bf3c6d9733f452f946ab5f837273cdf3761a2894530de7c9e26672647714f18aae22f7555584905d34526ee592ebc771c49e4905c15680b1f8ab70aee5d829bd303a4230f725dbf39be36bed3c0cf7d3bccad9f3172a85ac00d56533fbab84e0713390766029b3ad8dd26e16e80dfa0268897eb4d1897c92d9675e115a3f774ad42aaadb361f93bf6adb0506bad73206e86770e41ba0890683b7721444ec37a4d6de048dedc68f71efb46d671f27ef529d855e04f791b5c6bb060714a6c8e8337fef91052838a005ff998d26a6e412c785300d684c583027b48172665f86183d21e2c97eb595128b266cf18a107f454417757b6b94a76080f10b72eafea6d494fc251289381601008557523f97d126246f7110f4b73ad6ca32d8506c6a3af17cbd5e4bd49552c9d772cc9dc3c03ce9d8b8c438c19c1c1614ade329fa094421f262ad7cee380583ddb1c8d8744cb556fbefa6e52b7044f12ff06672f8f39518db4ad2389246ae3aa2753961db4bcae29d9b9ecddfbf33c24cf97e72e4fd1a3d16ac35af7bdcbba75e7fba7ecd45d59f7e6e7738a35d80db77d11f05e928f047af669c7040aa3ecdfaaeae234e5275934a1549f953d59db6910cc9727b5aa15445c082bf1ad136e8502ccdcbf41780f6ee098250a466cd4b732b4053ebaa2f6528f14d6a14b7f0b51bf93e4852a80f7150045827164c9adb2ed3bf32b4ffe7c9390fd1bd29c4cbb04101b040dc09c2b8ed19ec9a22b6c25541d46b7235f8603375d7a6a64286c87499d5987539081a890a2fa16f1b51dcd80dc662724bcaa26b8d1212cdab60acc7ff3c6614a3e897aa94ea25acd59006f2e07c9f72a30e9da4e99811c28fc8980367c37235c1ced40643a9d20c1b32a472abdd6f72fcf8f1bb19d3b8ea69d56ba2179741d5555b895e826ee388a1d5243cb67f2972c7333074c109424d631d2e719fab697bbe2bac5a045f4542b48ed3064971040831fd10442ea13e54a99ce53cdd3c6d5d844f9f4dafb480781b31c203d37c19722aa246fb8ffe8dfc9f41d8456735451917e4fde150377c9877eee37d89aff348783bbcb0d1f5f560fd7b5e32eecb0f2cb0d25ab0ac335d15e5c6b640e3440444159db49ed7bbf6aec34d9d5fdd5f6a0cc2234ffb51fa434a3c52379991e61a72f3999db4abe4ebb2076bbe63a070e547aa9a06ece450523acb6cc3d6ab956c72b6f0bfcb0cc1299ff937b74cadf2a54090d6421a980f999ca4f1455983098e72cbc83eca1923a202a9a166b5f4ea2afdb5dd589c98d5b67db1e65239ec9950511d3b16e764f3629d6ed256a34389c5b6737537bbd298698d9d8b56b578d7fa2aefda4dc4969944e5a6e6de81b618057587b85999ff63c844090a5ebf44cfdb24032b5aa2c6f1266f10e663e5c27d27b661662d9b972eb3c3596ce8c3767de11f6fd92ad9fd9abf242bb3ed17abd718404df7909c8bdd9d42eb105f3e1a003272e0b4f8bcb9248e83586d699d5f72b5b2c91463a798f1f6131cde7942468dad5b6d4e391b584735973c3b290d0c5eabf5f49883058cb9519c8a38cd6707cb234cd7e5f4eebd1805faa87b350d83e90b85d5748b3594148b09b3175361b380735f38cfedce5dd5bbd6845491796d6487acbafe3683c0bf82c18a85de30996bcc22b48f9f731f017ddddc46b3efcb56e0eeb71c302727f71c29e62b6f5e59282e72572922819cf30b7e933b6c96a1cd79ceba41b9a42d0c37843b965e2b945adb721fd72dab96fc74f2f6385a1e522aa5ba33c2289816ac8725fb1d7662a85deb3631cf83ca4e1402c3b73c19917cad0efbaaf78818aa788562fc0095c6c084807289e56a1a6802c22b36ae03fba3083449daa6bbb429dd0099548e7906cab09272894ceff3d895db5a169e399d07be6544239f299ad554e68f5e6ad555e8447e72391972e1397f70eb934773ceda7ede698d9ffd8b0a66451a56ecb5c55d69ea69b81bb9cf8743fe76027bf93e24271757f737e4e533d0dd668657e271c1109f5bd73a62aca0cf034cdb68e5ddfb03474b99368a76c45203044d5e63c45c795872cd30b283530dc9b055f8304f98268add3091f2b531828820d11238283b42bb054a8e0a453129f5808f276836ee3a1538b1dbbdd66cdeddbfa555bce9d0b16772984716e22fc69bb25f49e9309fdb51d14a7b6f62497f568bebe61be48789e97236f52af4bfa8c7027e19975b0ec985a03c47c5d09a756ce9cd3521804e9d3c7225c7ed0829abf055f29cbb67710eea6c46ebe50a49c3b756a7ed17c421a82a7257f69832459b4f9b80d3c833ba2804f7ac0553cc4b3953a79347ac86e0b15c2bd4b0e0294de974ace8066a4b3dc5af10f6f0cde1b14ed3d38bca2e7c8f5f0d72233c4474f57dafa8a59e76576b88a33ceae554658bece27b4b94f280edca5d54008ba513dd95a2e7fb6585e63acb2b66066889e2ef4e9d7279efa9f1d1e3d14179b7a82f3feec5ea9a0db56a8173d1230c96f2a76da37a16a6db844521a10a7217704d0720c86587b6bbcf2cdc8138de5976e8a177ad9d897a88306967b5ab359e50449492f1bd20542f3b15462f4c846bf257970ce6e46abab9c18395b0e83152a2b398e5d034904d1f07b08301c237e4de762d4b721d79d7b306be661a25724b93c0a694b5bc911a748bffe84eaed763d89705cdc5d62ea350c994e363c1725284df442d5831a4a8e688952cd49e2378cb83f98ecd0d70811d5c1cdc5f5b72e5ae3afd5ba81b77c117b9cbaa2f92fdfa4e70659212e5535f983be67ea22f33bf2f57c806eac2c2a26fbdd773ca6c02d89a70d17e8ff0862dd4131b5fbb832e27fece472e0d83d771914441712be58087cf7ee7a2aa8fb91a38b1c565a1017212818056126e0cbccda59230b202472cbd342853f2bb5e12573a4a647118e272cd1df5de5323a7c50a962406a416e642ada913fedef9b370a4e2aded4a4ef46ee8e38507cbbba4676c31cc7ec7c78bef216f35832ca3ab7e166d31fc6a3fee72f9800be7d16ce7cab5c148a53aadf874a27b04151dad32af4e432d22708abe211431262448e24651af5d118fb93609d72d260cc6b216b96f33a1412571af6e72c8bf44ee4e1ac680ceeb47a7e949030099b5324a4cd99898e631795ab3f0f472fa8cc34874c4e8fc678bb4669d38808ab8679b1497cbf202d9388aa477c2dc0df60666fa48595c72eafe6c4e45fe87f89786247022e222d8024d71007a58e3579323793c24888dc67d52baa0fc3767480b02438c8c88c042e9d193a395457003de830c7c31a825d4496e985433240be6099a2b839bf879f89f84320fb529eb72d26d31c892331fa1cf04a59c7b07e22f5c84ce2193a36366f14824c78ad1fc728392b9a7ca9cfd9854d78b15155086b7ec0ac65d0caf73bec854d13996e823727169d94935051df8132c759112c50dcce712b3b9d0a0204254218c54cd83ab0d4f1b33387d41ccd1941ce13f5ddc33cc53c82b3a857855b86bb428c2dc3de135c48b68edc97e4542c71eb67bfa627f1d7358acc4c730a6118c56348e4f535372f211932d56959d0bf36df9a66c168868a4d2e3d3733cd4b5c34c43df40668511831521c79e92f39cdbf2b156deaf7f07c4fa64e2d491f051f1fc9e6a22f20f6b85ff2b6d03da4af19e943302b3d27352f0bb1d0320fdf069ccbcbdfaaf776b2408f2d811c61d1a2e7ecf8ebd0147114f1085e6385c69daaa245f1cce5429ad1d0fb79978d61a78d156b5fcb14e742c0feee9e76a35efe4b897c60e9f46ca6872da2537c8307b3de33290be4d75bbb3ac1035836396484e1bb81ae684ebf54f7267b5bdbc533aa7d0864218168c2f964f4a2fed5f187b93445c11b1b166b55e7271db9e5a4f0e7760c528731efec5b43cc38c22c12c359539db9037fea4717a50b89f4c761be27e8549259f7e8a0818fce6c3aac2211b796dc575d8c558665572f3c99e466c8b90eed99a6c36dbc42dbd462c8813abb6ac1131d74c8d2a551d72c2af4daab8ae15ee07aa83fc5b097daee5a1b268a2b4f0824f0e79892d170f728719032949bdf50536b51dd4b66a450495b72a6d8bfe2f06065197ed763aca7240d1153096ff7acca60eab576ad16e9a7631455e9c58a771cd80c1ac80db0872b489f9516d5bf6f04122e2f1396cb28c0c386138974d3b455d03f9cd0321f17244b3044281ed88e4d01f5f9161d7bddabc769ab89c21fe228ad61fc172a5d4725a332ba20a4990ebcacf4b13bf6e2e7105b1d9df73fd84f6934ebe391049bb720ff833c5c5802afab26b445ddf96afeaf5e8ddb1176e16b5400842450f29a17233cd5ffccb03e45bfa1437e9aee931e0ef491d7fb84d2e509aed93b1e7c9af13242e7ff76910d6aaa5bb669613269d7d5aede8770c5ed7085aa56150ba6f7072dae8ce8d2d98361b8a3943ba7fb4fe36fb5b08a5ec732c40e35359797bb96f5bc967ab7e4de612db4b7682f0de7f117a6e7236e1addc85e267f1119b266bf31bc19826978a2330bef7758f98614af7dc742bfaadfa91d0992758a9c6a4deb93e3e466f0fd347ef0e478f3087c7927d09baa878f8f3f3418b32747c6301e761723ae8dbc7b52514faab5c26c5b24d36bccd5262c7731c19c91430bcf1f4e5c459bcd9ebbe05b77d63587683025ae6817e4460dd50b4717d7bb9a44d13c6fc4f221376dfb9873d0e788477add4343964838f9fcfa43806c9bcf4b10d8701279d72306b2b2d64754ad21651dbd9cda15fd04a336df877705aa26bdae1b1f4fef372b5a5f3f57f6b502245f62ea1a19d67903e9a8e13d5fa7b89a967e4c639cdc54080ed9484e314364157c63ddd61c73d0c30bc298b1855f067bc6147991ef7e072b598605c5f464faeefe6f7eb5dce546a8530832854d524d6bdd548f3a6c1470146a9db50d39abd6f211fb907cd1bdd10865fb825b2f7e9dc55545806ffeef4647ff9b94377597aa6c917d00970b091880c048c2a0fc10e1520cb3da3eaa1174e9f6efe8e5cea0a33eeff3b63a598a2976d615c75598ab8606b3ae781a7150a729a791d95a435243787463298c60ffa9fe3ad5d5c144a4c45634caf961ec43272ad88296c4e8690cb05365aed8e6e3f26d842795fc62c00361a94356b5169c972347224165f0748a80e240f9372c2207016e0468e47982854ab39f3ab1f711072d6681fd1f9a8a16dfa9510e6ba8ea794ef0cb6c0095e7ed2a2658060276f5c4b54d7740cac4647b9e99a5cadd04b7f3c350111463dd2a25b175d3e1efae71f72a130258aa73563810fc66d9fb769c707a2c91aa86ae14649227c02b3e0435a5e955bd4d7c8936053e6477382de18334039c11f47f1a8ae6fac71a711e14a22729804fbafebe20b556039ed2a2aec155b2fbd7a9705f75687ab08128b46f6bf7247222cd519a30b3aeb0c3de9619376c6a0d3e3297ca070edbc8b2873b2b6846101084d90ca86adfdbda7fc847bf8143369dab5819c9097b9da6b04913f9f45726ca865f14cfb36a578410e07f4145b2ac4c695e397bbd4113ee3c009c839bf509ca998e69454d1cee39c3db13527a05cb4326ad4a958fb341fc0b9ecda0b6f7200727e6bf475f70a6ff8b7bfbe21d0543dd80b4924ec248f4ddf58872b985566df5eafcf9f99160686e0ac75346a6a9d8deb75109f6560e3a1d0f140def6457296624d6d8016e630fcc0c662282f9a3d702fce5f7b5716b13a1b59e42d43d872efcf0bdfe84968aebe8d0bb05494d7ed634f2370d48761721d1348b5a621dd52cbec6cdf9d63d8bbbce532de2d7574bdda4b61386d4f07f20ffa4e5dee9439729b77a53c6f4685c38f93a9c07f8e0b383bfb7151665690b9ba831339edaea20b9271a4e337438d4819fce27bf9418bd2c3e7b424ac55b7f5c6c0a833d4257872641b4102e2e1ad4c330fbfb4c2f3629a3da46cee2b3bda7cb5d6433118878f726d4ad1b229415defe3a2e70cf8a4e40dd7371ff2f0a897d4252be9083c4f760592b70f358a1951d8aa687bba1350f2645d1d486996a9b0862b50aca7603a54615e285a8de6202fc807c6b7203cd2e6c4710372efd4c079831caa577091ed601790bced5622ecc85106f20ab63480d1a3f7723a59be505fd8a064952dcb0a875ae332c515843ad5fa2342c8c06f0609a6ff09ee16af03c03b2fd0d8bb9a3edc720e9898d483b653cd86730b7837a4fb571327310f3fe3fba5ae72d3b87c288861518648661c16b084f50b1e70437cad21883ff1331202dcc8e719315ecdac8368cfe5d11bf97c6da298cd2850f402b2fd5d70718d07fb24b6c11e27414e896943568871db4f251aa62cb5eeea16bec1178e34d449b6f704ba12291da9c3cb9b46c39ff076657004dc2922e55435139e08fc321a94fe8c5b31edbc67e27202967268710cb92a0b7d692f2ab1fc0f27d27378fd932b3d99f6f681c055c31259bc728a3a645275c343ec8630b7e9f4c7dee02fb5d3a47f635017fe0b2279f8eb7c725871ba487f4f76fdd1dae88dc956bc4c2cef3308e0cfa433aa827001a5c8867240ce085c7f8a4918d99038e8d5f218cca77f3af15e1bc4325d6087b7c534fe722b1b7454c89ba4386cee2330c31f75143ea81dc2ed325208b395c3c868499672497e9f08d7f593fc53332426834119e8a825e4e9a669e4b1cd1452519dc76507826311aff393b87cc532da0a6e3c08193190c4dd3c68683979153249b314be4a0cd83e188c84c3b6d42230c89cc2e51a7291a1996e9a71d90fab3e993be56147209894cc4346415d4942378e177ea15cd606d79afe512b409853093d8996717253de28655eccdd67d039494a83934bec032bfe01cdcb5e9e496947d607903b5ac333073e49dfcf297bfa7c6a32e18aa63751532f7114e1a18ab91146cab34f7203ef478124839975dfd798b3d7a5489a422c35172e55c9e1101fa23f44b1882b9572eae043cecef0432a3d04986408833107299d48a2bab34196e2afdf996872ec51fbc377cad0c2e24dc245513281640cec407350e5698eea781f794083a9729a31dde45ed70d6593c388c8436560eaa832b341e504eb307e48546f80466169325044b51622348b81f11cb395d24a2da992a689f8d77f47e330708cccf8b85cf88f4ed64ae512738873df83559034fb1d49122029527728f791524f32006a72ffcde44ae2b5e5164c3edc8b95f12ad2bd9dea8a46521bbd23ffda9defae79728c0ae21b7c29cce75ac4d308bebe066cd9e589e863b783be0a7661f1a3e08772a01ec19fc053deda42de8bbbbfd307520d881dd37d337c2ce283ffb5f2896410dc818357d73f5ca3bc289473f36ba0d39025fd3f43360c0a4aaa4d2ffa4cd372bbbe2931b7d4a029671e6766a01522995ab20b0ecf15bb8bf2c41bbfe513b972777f6a4562d956919903e4cd7d052e5fe435dd9dadc73233682fc170de6c304a4e76a5e6a2d619149077a09e38cbdc91868578033ec83458634b827da000907272edf7404962639af54ae7df26614a5a8c67f1e054955a31f0885f8b7370a5720543b11b6de211b0c858f49ed58ca2f0854ef2a98fb9c3b8ec0b46779be62a0bb86a6ba7da9230aa21622a372ff94b77f22731644f7a96182adc905d02a19072ddb1c2f4bd496e6139717ddc007beef8098a65d86506e8181d0baa9bba5b744e2e2d8111df3a414eb6821578a608f2c494604769092f9ed4243a5d4ff3680172c404671de1d47cd7ad34f6298dee864fe11036048803501bf028b70e41b9b872b83707ded799739c7cea0717050b1f666ab2ad277e6277ef2b865851c3a0ba72fb4343a72032aa1e1e5e30e71fff575bce18ecd2116ae9ea591bae8275d88c720820ed3731e88bcc8183812c84bd23d3b3eee9d0c51d01be3249dc5ddd332b7225bf1b532c66611a7aabba58d1a97505be11b10c2ec7ba7a0f15392a7260ed137eba5681bfd72ab083e15beb0defe9e35c13539f9991aa38e1c4232dcfed8b5b080247da618d6dd22b97be2af0a0fc3ccafc1a191015eac37b0d39b4374cb7723952360b7fd1035d5aa0e87a10f806497ef4fbde6051e42b036206b81cb0d763da897145bb557318636f63cbac84c2ba2a7890c71651912ecae86381af0a363b5a477d76ef23268ab86ebd44682175c0f300aa6851a70ccfa974ba750644ef72e07cdb7da216892f89f660dbe08d837a399170aa4c81b848907a949acce40f728fba0b01ef77b15480c1375708b92adbc6b8acb2e3e2df0117b12cd6436863620fd217be70733d77624e03c8fe6ed712b439243a5ffbbde9aad1821249eeed723bba83f1dad0bf5cec4aed207a585a3c3ac8ee5dc5889886e82ce6fe54b03b1011019f5b8833f8fb32010f64e6e140b5351de20e48276080a3b9df5245bba572fa41003b1d99cd79d16178c1bd10ca5bec6b4cd0d021e317e6e7c14ea95b1c397904fa2e66433b4e013807e30164e45fa624d2cad9b30eb424fb7f33b643513079951740c48a3af343d794df41d554ad0122ff58910a8b349388849571b6f9726a9163fbefb4e164ba562ff7f86a2431cdac193d4380ec2ef4e7f07c82f779722f516b9fb51beee968c5182b54d2e26ba55e24f5cad9e82da1fbfc5ba5642b7225c9b989b8d72179160b9fb053c2632428a712789c106b0d641cf0f3f0f51a72b1fe5d9ab4a6a6ba75673140f1793f54127c0898191b7e425cf393da2ef390132fc829d819da9ae66d856d9ba3f3c2e6f974eae5201d1829c7737b727f9665033e9f05e94465aab92549933ff174aaba29cc7690868b03024f261ec99fdca1729de8242cc32583b3872bbdf11fcb3f6317729f2defc4bb4d42bce1d9d53b510a4f6bb368bb424583fdd32b62c07355793f62db09ab9dfea48229e205e1e3e639a210b78d2f0fb36b0be91bcc665e08bc560e4518a2025e37d6e7fb60855aeb723eace52dcb23799adb07e14e8df049cf632437c01f9a77796aec31546b680b3643c555a385751777c87dd5a01caea9264b4037c1c0c251e739e70fd9bc9f846a502403aeaf8d227bc812dcc9a08c21378bc3e7db30e2829fa97383b54c30365ee6b8843dc1d25017cbeabbe8198f31cfe8d70b175036390feb9fa363f7c24c72cc75e690b64da5652eeb93d5fa0102919dd537d2e587f95dfa85717619a60a4ee3f0f7b9822154246618c7208aee9b1f30ba3a28ef1f0e1cbda7c35b389f58727626ce9af176e46387af62ba340a21b8c7fe4695c8985fc661a31f4651dd3d7215fcb8b7bbe3192d1abdec9cb3e3d490758ae961708ebaf10f1d4b25c7cce6326770607e5aa442adef6b3ad899513b1d40f7762463ace56bdfe18c8778cf4272edf46c13a451d0898c0696e8ef908210f9ffbf6b564e9c0606eef5a533d70d6ef6c51320818c8971afc31901b993a3fd37f34ef64dc8b052a207d86ff1c8d566c9f1ab52e95c1239a9ce4aa0ddfb229b539069c137494d402b7832c5021b7c72133871122c84b168760631a2dd2c7d1e5bbf3a830b22ac0399ccad98dee050727d68b063f74bca4b5f373e41d8003bff8b8c30e71dc70235c01f60f61b80ab72f5b5caf0e5eee6c30cb41fed3d6485aeeff6feee550f79cb73dd3117a4cfa84a86d2851323223f65d0d3fa6f9e66a560eba6121fef862554df0b03e28a41782b9b7c1f841a4dcbfc56dd4ef4d0da9520c39d66f55f602b35c07c224f4a0528725755cdfde2d26983599a66624873f139fbd3c78c9024d0ec158b79b9b62a8f201e907c4bf854448a3fc861f30dfe8933183f9c407861008a12189f137df72a722e35c92e5663ec3c539d9106b7113160b32064ee804df442aec48a6d9386d5727656f37368500519331bdeda8b9958ef90d55eb00a9b6266c4113264838cef72915cb98a16ad3d20f9ca07ef01864a9bf99b911804981db39817ab7b35755d720f9b284f82c8203df2336dcf833ed8aa110070aac7abf1e8c843dcb6632bde2090d9b9652605afb9248cbad586fe9df3c8dd8d32b9c0066075a0ce01414181729bed7cb0d4fc98a54ae26f6260a2092eae0ccc04fee88f23df7f1690e44365726c0ff6d7722ac0b665ee05fe25a4ac1960195763b9e67fc42f7256d22950f17254b203d6d02b2de4adf062cbd6f7a8ae1c964c96e1312cb2515298a288e70472963b5858cc7eff5d452bf4b4e5f38baa472fbd647cbabe6eaa91a7713e4306014f34190c25963d81ab98867de545c4db68ab47f433021be30226be8d1c45a50f13ad6ef2e74cfdd09e4352a8ead5502541074ca85ed0b8a07d8c21b3103c607210d067b5003686a86a61cdcb8e0e36700fbd52a889925c40f9473d46086e514e5576142e9e4e231904ce9e58b2c15cb949d74e93db79eecfcd79d5950f20b5721a15849ab53ead7ac8d8ff2dc8b20166174a6f5f0b4d281eb1fb1da32723b0728b41476d4164b1f2292617826a46c0e3642258dc167fd6003d7e6d5489b0055b3b47daf7d8372f0ceb392ca6aadf6c5061f97d582f0c7d9cef7697ca1e17054133297a753657e876f35368a3ab68fb06425476987bc2c2b6db65b1b9a04b3a0e00c886b9c2c767c96f239945ccdd30f56159c26fc49e9a0b095bb5db32bb4d4284198f5abf494f2c76ea2ad8d610f65339a779076968a2816bd123611a95ed4649ab004016f673a6013679c09979cad8e5be5372dd19e9232fddfe0713798157fda755309fb5b5a77b4d0571c2a7fab6a93d96fafb3212f37aa41ade9a0ae372673aafbe590c9c8634f66fcb2f06563b79ae864f2cdb8d7856fa41d9900b537276e3c16d5b1489dcc18e57a035701a0e40b00ef7eef2b39ffdddbdb250c33c7241fe772356a7b6c1af681aeaf815f9cf44b4f6e26b718107f2186edb3d3c9f72f0d1247c4dd9bffb98005d720c1dd7fb6cb5c1cca10b177a00ca58cbb8c57972abed5ef4693d7018b97628a2cf8b3b43c4d27a25dccf009be9488d95970a5972284e35b28440d1941ec871423ca7b9921c2e9050215bb1322d11d045ce832c722703b0fa347d212fbf843106eb668534b912d5d657a2d6db8d805de9b6f373725ca2019792aaa87af5d3a4612a0388379260ba5efe62ffcd80ef50ea9f76c672345159ac4449fa964797120f65f465a5bd45b1c567bbfdce665c42b182dd22729e5ed6ac69be52e7cc1444bdafd161077a60d5769864eae23ea875df724c3672e75d1b6be1f57d585f6013a8af5c6fa2c4292b9f0b2f536bcc190bf03ef27d0300a97f90fa2ce10f5f04bb0db17c886087214e4fb83f53a6fc981af7050033723ba0faaa71edc38a50aadd6a105196d4128fc9637dc67eec27f99440d3bd5f7200c75c34066fb397f378bf036445ce74d427ad3c6f018c305813d0dbf0c9c4726eaa7d74c165be8945df88750c7abae556945211fe73c4e2821f6a44a85eae7247e1198108c65ec18bdd0855e41ed3488b1744e723a69e3c78809f29a3b21f2b1a99a43d02b79915aa1349f18e49db7ccf87e9490e63d9ac848639066e6f56729edd34433b2c1c51603c8b53b6070694ef04d0df472af48cf3a2475e6b01285d30e1f38101c924bd46b0e09301809ddef4a81b4e9bc2c211ef32b6564e869b72d9a282b2b3f185549472e01180c3170e6baba106284eb1fcb53574ce3bab3a7268c67a21a8b80a0c1dc2fccd20606687ae4834243fbc42a6ca036517f82d9326fdd0e63543bafb905717a0be07d3c247f0f4831541fa0ccfeacaf12b31c21172d30c192f8dfd6c1ecf0f87eb72e67b61231e506785c71efb178536592a499f72fd26462a4d6b1af9f76aa26080e3bcee53959f2cc747a423768af6653426c772c8824c36c74bebb44bfb35dbbc3a6748949f55bfdd110bf262c8244fc7be97729b1a5c7504029b73dd39227b53e6520f69fc548e81527bffa6543c502d913272a7d906ce6c2c338a0e6bf050a62ce3aae9bbcadf6cc2af49286639f3406d5772ea9cb40614b6d97544dd51925c5600cc5f1eb866886f6e5ffac671990243a046db8d80710c7957995ec55b29399093f1740bed036e18b0a52578be3ff1ae354643425a39e40a3637fb35a4e0fa37f52ecd5be87f458d23c17705a6985225f17214a74c2bd522c96225afebbb0ad1f51b51575533f1284b7a9caa28507f9a91723997cb27e1525a8edb77165c38b5de0c7bd11f84d096fd83577530991e847672d18d911c329471f29bd69952a08b8f4f03a328fb7bc6a1ff083aa1310659646f45123d359add1d4019b76e233478c98d156904fa3b3b03ff302515297f62a6724b80b3377e510f3827935aec00c12ddc717d3024fa826e64c0a1d1926c38e772f1b4e051ae095938ee1eee8bacc3b1fb1d8fda1cc17f1cc5df7c9740e29c29225895576976ac1d56325a556d84df1c80e9b06ba6ab5d51f1f89fdeef37600f72c529c754046ee46fa9791ce9db7815723c5b1ee2115dd4af5bfb9176ac6dd40ceb52dd75ca6a7958776b4159bbbc307bc2b54732fe7dc95c4d07f5b796caa2726197d106d34957d429891ff2d906f101520af28a6f3ed64bd282533c211d6872daaee8643c0a1d1c7f799cd45959303ec458fd023a858da2f67dd2c244c9b07299f2a4382a12da21b861efaa9d117bd5f2c3d73e417623904983d2c1e7acc16edf729201b86ced5a0969e2d627ae18944ac44b06cf54936cb17d5e95894c36015a1904cca6757c06fd4944582b1daaa187bed51585788b52821d8f790865f772ae6e4f77258724256b3b9781b867abd2d82ae2a09f21bb92a6ddda352a79e3535af9317d386cafbe877223dd4c984de01c30815469a7d9688906e29c9ca2077203c3691de437d8a1f27f4823e87d9dc0dafa1a63f9e39ae742c71a729d36363c5adb3dedd4ce5189a319d4d773b1458089dfb12d489dc79bfabde54b3f027735b0ee825d9a62cefeed5ecd249044a248ff700827ab38fbcde948b5674507037250f5b50897c29f169ca2c0d9433d2d3560377714e5e0c796792d8f86c89dea7238e35c68cffcddd8fc37081c618ced1b7f5c2c5e745b3f741829e6920fe99068177e4af0c2455adde55ff146ec9aa6d5fc37bf812b6fe124355ce9e2881765724f6ef63fc54a8fe061dd777db285c6116ffa545749919c27a061acfd1f3f5272c888ce232a482d502a7cec564d545723915a5aae94b1a0f4bf4d171c13379a7260a1d14b50f99a4f6f850149bdf9a367968305a39b8f82391f5eb605cedd5d00d6d8c24b801944ac7c45fc52211f1f11cd8c853f6391b2843f0b5032bd4ab54a82f2012b116ed116163ad84a3ca56b9eb8cc8d785eb3e211b5bcf9a43180f80b21228708ca38d19037e11d2a9c7934bd2312536f4c6c24bc4f8115eeff230f4b37b9f8c6739322b24ef9e033edfe0a1f3594038f5893a896c4c0b38a1a875372c2d6a205a581cb33b73bdec3c7a5ccc47d77013d8466ded3ce4fbc5bc200fb72999daac537bd4bfceeb5347732394b9775d28a13529afe18b28656ad49ee1d37315cd157ef25bd0e608610e4c849720b46f87d434fa521a31a8443a307bffb39ec68a67388d349c29dcf16ba2b63fe3d84cbebafaf723ea59ba74a1cb5d02a721e4d2bf13930d44d5911557dd9d6bc2c08a4e9c3a0198885aefbc6ee1eed4972cbc1bc794556366c8439e3860bc3eaa2a043e89212eb32d45024b5f92b5d317239eded5b9c2165601e6701fb114eaaf808302882c23dd8f6c9ebbb7303e3543b12cd9ae301ed7ad7f92d204e100102a7c3a83669144642881d89b83d61b06e4c33d55c7a203b8ffbc31d7832857ee9f2ae9536b0290f031ea8c1a9d6dbbf10291aacf4b283df4bc3380629a53ef38ded6ee6ac2b2f9ee27a9afa7360298bdb7255efbf3c10873343c878a91db01e8da559172be63dbf44d7f9739ee4e85deb09bd50d7b475e5a13e54d6cf04c84877aec7a2460439e3d6e3d00816f45024592e7faafdd370b09daf380e14bed0a5f4edf65edb4f2b7cf4edcd38bc2255f747726dbd60b9ef8c59f7ee71db41e03392fad1ec8e832b192865d99423068bf25532eb65afe196558d3bbdcfb2126054b6b36a94f6d73c56ac2072647d9912810072acd9e59bf99d2286baf76eb906722b83ddd1f22bf306d778ecd51e5a6c099303bd116fbfb1e984614c3a39ea30fe2b2d6bdb379894eb179317dbc0b3c2421672b7330587402fec7cc66a5111009336b011a8fced530a23e40cba5128aaf1bf7228ef90be92fa2cb61c93f047a261031f561b02e5b3cc9031d1dc2b08c3182072b92f168eee4f7bd45f21c04dfb0bc64163e65a7a999dbcf693d34fc425a4897295b72be99769aca5578af5b3c282a31e87c4c7feddc515295e8dac0080f45a722dd3a88397fbfa9e6e205fd0e4f0815126da197ea4b454962aea49962b9a5e723ea0b04d90ddb160f9ccf55e829b5153e1f950a274255c657bf2d552bae52872b36873b5fe776bb8e1d7497e819afc9d389ff8592feaff96f3d34fcbaa22ac7292359d7ed9e454885dd51a046050a665c6f225d8b7336b0bfe0830577a6f0151b2f1e92aaf9747e0592cacdaa91db694a49e7e202522771405b6a879df21344a04a396dada34007a4ec89c24147b592c704d67744b1c5dc2c21949e2ba8bfb7226196c9f3f1a19418eccaf715d0ed9391761397b863410d1f03640c0be8a0072e4526b34ecf0ffd801491f588ad22304b6f6a88e06eb14e6c09bbec986fc1d28c70a260c80d9120699f446e074cb18e63e3afb38a8ddca3ab8f69a214349e43bb2583413ed727c6ee17ba13d1de45cee8354c7c762972ed616f25d015f775f5025eeacd544ce8b69bf60ea0f0b01e36428c701ee46f61346c9398b66249762724ea72a11f63353f3a023a0db7296b212a7e167df6a502938ee3aeb8072bcd8726bc8849ec0b3add4a2fa98102915b81db6513aa22de842c24cc4c2016eacbf619eb0fbe46faa0f27a55fab909106380bab52522ccf9b5031a16655efdb586b72822fbeb08ab3051eee74422636f250854a144c9adcf3b175c97647ef3539914a108fc7ac036208f114a3843602c0c3f52e3faabeb141922de4b4eefbfe54aa44610974277a2c43402e46497e22b983aacd48019f52c835f212cd866586ae5c7234bee316671b0c8e4ac1835f3807921bd0d37cb8e079ea4c4f78318859fcab723c1b928227cd4682f6fbcf681a8ac76f7c41689b8496f2fe2c2dcc9a06359772fd8a4240ea171c89f3a582963b76135d81d1ccd3807e0fd33a89d8fca13f2f3777f29f6a3b0880f89a937b8b282f51ed16ecf02286c2a3e632875cac91ad09658f8b59e988b4ce9603a0b57065c64d6ff0bd931c10fe4516b585a10f1cf9a610fd293f46a4866bb7d44e769968e8df9de5038aa196f91e46c16daf52fa5c294c2604eda6e1662c7fe382ff0fbfe942586eabff1c7b07f957a553decf14d6ed72aa86b1c42714d4306ec9faf496bb23645255e9a4193cfee99bace1e68d32c472bbe67101dec61e824d1a4a93b4cdab53d8b93e30e177c2ebbeb9ae8cf5888a2cf4297c3a1553646239deee0f7b211b0ce35c2913d1b1a6779dcbc47a7e4b81720983513c37650378bd77c90f1e23dfdb616e37fd8cad1358f9416d1f408c166f7e955c4e476c8d11941febda43113de9e4546d22ce3224478892b9f6d077811f38ff6ff0931008edc1b80768a732483bd5bbe2e1aad51c544874ea9500a99f64b1231e3818865aba26f5b262a00265c71543cf87eafd8f38d6082df71425201d112b34a28f7e91d664dc3a53018e0ee48372580023a0ddf97e506a169b2b3672f1167c436cf873cf6a408254748d1a0a90e70d45dfcb139acb3a354777758372024e39f1646ecc3b55e9eaf05bb8eb9f7c65b9c2ce32fbccf1a70587ee4dc5727cc1cb0ad1329868eada1ff9a68fd93e8f2ffd495db64aac352fb46c68e8f031d801dee150de2eef30aeca1bb9bbf13dd9d134bebfdc3d44b3f4a844a807691c9a3d8ea52a30a1c7a0651e594290bff8a648f6cb62cebb2c91883a35e3a36f5a1db849f91be1b43d2445c6fba32b0fe25e50473f4291483fd5efd8c08e7b9a3735d89cc3dfd00328d1789b5271a3f03281c5dc598eeeb37e32f1b75477990c1a299a0ef6cfd9b25e5bd3e78989d80bb11df1c908634e517fc52eb31f91c2f523b0d29f56ff75f123e6ea2c7129bfc66527eeb571863b26e08f157d3e8ea0b930cbc8b5cce98cb73c9f6a2c7b990948995203f23591b8db24b6ced95e05e613725da992407f8072c5602bcb5c60ae9211124ac7da9ad901f7a439cd16ee1e2c2983634e9b7fe1666dab6734303fea83ac96f8dabd8b0222b77e7fb449399b9772cbb8fa4b25deb6be8b52a0dc3aec99973890b8c4f90551a963a5507780b15272886dee53a03c307a4520858f58e5f938e4bc45197c08e63e55fe59696f688647753f56ef0f0a63258771bd81709b981f87806938fdebd8100df4d200314f821752d660b1b8ee7f05f8f0f8fafad287adb236d3ca1cf9cc412c92e42d07b57c09d333aa329a16473f33bf8c3110db307c6f17fc2425cab9c092087c77b43d1e6da0f6252bc8159f4f8c4f715cccf67be56108141ef6e71ec7cda9cc9326d9e42c6cdb42017ab1a63769cc2a42e595970ac1690316aa80e6954ef7f8d624b83c2349709f4df62c50d88e049d84a419b73ea20d886208e1ab8f1842ec686379ea046d8308240d62c6ff8a9172420670a8a4897d03791ad1c544c961cdf8efcc2772f68c481dab2cd2388ed0f0d5b3e7db9c44f3083fce7d7c2e484a74ee704e4872f6095f2eca3497002589afda51034649e4e801683c9ef2206796b1257f17480d6f50ff325bcf2e65a5d38438f19aa6a94e6f6eb29483a5d444840623bd147772098e6830a4ee155d8ab9d15fe7b1f0b6e3f9ec68a5a565372bf028f194695b43c14ac3dd83f86be96350d533ae4e24405767973aa3787aea59c57b98ecdfa072c657a0e3a80f62231110dc46e902ba959e1a0d9dbdfccbb9ef3415ce927dfb7291adac7c79d812612a78acedd3372a2b723dc0d1d6970689b68c8afb31f24135031cc7f10b9c0f9194f744e9de2098b5abfaf770f19cfe0c139ce9f4afc4d6729d93f36ca498d19218a29fd2e2b0c2d4720ee4346aaaf3a8d851e3ae1ea6d9724aef2ee07138e789fc859690061761d77238a3e4f5ea4b7e50157212100f534bdb68fc2c6ffffd9f140f8e211610cf5b16df05b2fd7ab9e2c56e6aa0efc4e661bbaf1657a92d1ba5c3467ca64a569020cfee717c892ab9c2dbc88a29a452aa12388459f8a15f0e09fef3125ca073580b3baef48e7e9eee9c91246296ed55c4724003a9b00e4d3ade90961b4cb052db873eea3196da50d56237869e19f72d7b72712c8f1795ada3dd6e844849cf608b910bbd786f4440faa020baf9ba6e5d8370a4db56cb9ea81f27afa8e455ea1dcfeac24220d52c303ef1dd59303e9acb92272d61fc20715e7e334425a42a7cb77f63234ec6fe9db1de421b85ff2ec553fc722c710a85ad29e2ea766b29c47a123548e2089440dbbbc68220060864a003ce7255ed61d7a77ae404e56f71a62c73d526968dcaf183bca2f8dc196f1fcff57d1bbde167fdd815df5703ad46e32ff19c753ea38df3205ae3e67f4399a44ee00d72c81d04da9e6922e21fd1735274800e9703f27b1bb69aff983aa4697396a738722b3e70736b92c5ef55f29e924ec72a10d02f395c68254821e3d66113d9b3b2726053d481be6bf450d42603a5715f2a9d652b30ec19ce95e6a4251287dacd9f72d182d3ee636390e4b4342aefcc41b7d3bdb5bdd6473c2c26c7eb3981adf55e1644992dee318f0aa79c4143dff752f0556e8213a209114db361a2f418109c437267caea71393bcf19d56d6b537292b96614d644bde9d3aad2a44f7800615449723eeb7cea0c7f03c5541ffec863045437e3f9e84d4d23ec6bbd27931e80858103f67ac32e2f9f4605a3f0762c304b6eb2da187b1df1944596522cf3616b80a67246e748ce6a4145f50c37fca320904185ff0bc2046a41832d577cf17b0539e97257247a88476a21e39310a40601e2bb225652c85b513eef304c06c3f547825072ded8eaeab6e0f24c5674caa6b2a8f91bfdb80dedba9e2ed0d3840326672c3541d25902bc2386291b024eb9b36f4b9e961ea13de841b13faecfaa8e50c8803f675b82bd083a4c862b779f6b2f9a97ed2ead0b6f1be7e8564475d7769e64ada4724f1810e4197a2e856270ba622e7e0ba956b516477e07558bd1a2f92cbc73e27264789620381892fb8cd649ca165bc02ee2e206c43cedd7e52bbd1da951e1aa72aee066a5f205d71384827fe8f710e4f6cb8d0c493b5813e79546a2a608a06d13e5db4737e3ace1e581e4b731224f3d41237e816c4c683359a1f36592789f6c72221267d5de44ec046762d864837f43dd438b6ba36379eeb7e08da49f2ccb3c720c2a943b34d5c87894a9878b839fe7cadce7e037df4d6ec538c413011cdbad1b80487cab1d828fde24d62939e5cc87ff17220a81c314029b238659b9c434822f63f5458d901ebf5ef23b9ae0ab73a8132cc19a55bc4138ab8b7ae64e26d58772ea910f724ee0547aaa43106a3ad331e92a58d25752a6a281a1dcd68fec337b72b793bebefe7534c482615c2ede8abe1117d03446520d49dd0bb16c515e9e876a2464d1bcba684109bda1fe01e5847a0611f5db6a6b33c5288cbbef4b53e0c8724142ec8d6579b5d68fb1c3909265b9d3077620cb6a755240eae19a8f571d1b720de6be9f18a7fe1dab7ade06132492bfb936601c8678f19a10068ae2ad812c72ec68992a142cbc066c3c86b4cbc8e617f0eb259ca162ad3e4a6c5095cd71ff72ef35ecc59cc9db4ee5e82fe79fa15511193cb3c9d6f9e6980b4325584a701572a7a41aaacf095b982da643e6653ddfdb3b5191252c41a9ee6fb2d1d44dec7c725615b00ba5fe9e1e201b245479a41e731793c8969afcc9d68552f03109aaf172ca33f50931de9ef9f8b9baccd845191cfb96b5263f69669a0d83aad8a995e60a714cd6b8b8069278a51b4e355061515c10ef770092b3cec5a60bdbba923056352518ca414538a27adc81617caa7515da38b83e4ebb128634737d258955e91b72935d2e48e2de40f0f8ed53d59d905f5d00f84237aa3f539d5c3ed4f7ce34a072a3f46cee66c038292790bc5fe7951a841fd1a608617ae25144a939ee8aa392586fdfc3796441b2b88a8164989e24fd05f23512e2d6346c18fbe145caefb5f5728976c7d15169575bd030a27a396f396abdda7ff3b88e936d51e34c14a9af3172677e8eca2a0c6e222a729f393b9f959f7b2f8e5b2190aabda383344c6cfe2b72e07f661769fdedd4e62347fe7032685d859574db8b46ff729456cf5676c87572c5f034ee531590a6d005415c0fab51aca1caed602bda6485e2286b6b9824d5726ef3731ebf1b256e62e51341a360af516e877983e6df2c25ec8635c4054f0d720cbdc01d3efdd357db33bd0b7219cf4d299fdac6e9fd6386c430b7c9cbc06672074d3e952b919bcaef46089a52bf781c241ddf7fd703069f3abd515df6e0a172de480f525994ba41fb65d8e124bebf9a13138f94347d45fb94ac2f1ad6875e72547de9ccfd970678134a0d760913ee9ee3d4890396bbe8967ecc315ec68bec42d1a6e68d596c2a249064cffaba3a3ca81bfdbde69282c289fcb0c91f6c027172c9b83d4868244cb210794fea79fe770de074becfd0cf730b847c0c9dc66c9f0f343d02ee28065a218c11fd89016838e81b3d59c4492705daf63274de41dd977241939ab56772fd026a86f6b7d00d3905912a27a72c59e2919189f7ff5c31c1728adedf60cdbdb457d357e0bbebb941d7048d282ff9cfa94a7c9026a0ca23c55ff1d47149a270acfd4a504825c4f8fe0a61468fe295f17485f1efcda584de397234bb115c460de86594a57e4c249780016c79f405a19c17f6471a4c80a2eeea722d2b10e1106ebea68b94eac93f99c48d724523ce64975ba9c8db9b0c1a038223d82afe7fc86df9eb3d1d04810fd962042781b9f166ad9856b3fdb54c7e3fd17261c6f6a134ba09a6f2908a3207615265612ed06ae3cb60274356030e9f677b7272e0ef026a6a08a33f3b9e4cb4c004e59d64f97280458fae2161b17e32bbd82a1892af2e2a1241641d1e07d324951ebfbb44f23e33c973343e64032fda6b3172bb9f8dd84a49bc28178536e2c7c110ee534ea53aeefc820c82ebb4134253764eeeb41796edb8f52aa9ed089b8ba7255e2f39d2945e7fb8ce261c84c5d8d95b72f1f1e58594c06fcfc6e9f01a16228ed9906d23a6d8880026db05284e0df1481cb0e7fa4404778b08638244418dc44108ecf3a48bdc06ec177afa57c05ad5c4723566caa4913533dc8fe874a1dda88fbd4bc8a52804930302a0c9af5f23d4827261f43ce0dd291a4ab24e1352655455c53deecc7d78c2b3ed69afe55b3937cf184d9f7e82aa22e24474994ee393daff28d96a0517823a9283676e3939547697127a2747153325f3d0e197ceb17accea200f8bb3376aeff93f018d9e403d5ee048c12b47179a5e67a81f40c28a1b73cee9d20cf89ed5c416506ceaa4b066cabd699ab85337a9ce9c8d79c00b64bd5880747ab2f07882e9161c59938b8b9a143433fd32352a731c1c479da9d88a20e01cc66c7fe65f47ad7bc212da470cdf110546f9c1f96b971fca72ff4ebed0e2a47471116857e92b18ad3be1a40bdc9bada760b5f92441eae089b99e18f712b8ec735d9d9fec83231609a5a9eea952ac3d1c38c8c95381d4e5a7562c461ee51daf47adeba0c618691a39db718de3ee04ff4454635e35a8133d88d83f8784c54bd01709d5d90741da5150e54b3a6d0f0f37526beca165e9d23bb0f1a5bc3b694e92c4519b0900f60a6939508b18688ccd3699723bf218edbb550108847b32b793697e22370ada84415da3bd27c9ee3c3d1ffe729cd03c1ab0b9a60ae95e574e0d7f89db72590df236af0b887affba21bbf1cd72abe6079ae0ac842a916b917a96d2bf5c4c2cbb2e3247dc8b14434fca2214d9727e954a67b85d6f3562d0aa7cac15ccfffee692af4d648500e21102cb69200f724c9d14dc07528b60f00c1ed387153b2585f2b2ed084658354ddf6aaa1a74a825380a4153ccd5bb9aab317c643d10be803cb1316d5670df0541f3bade0ca8014b49b3cb32575857388b4a989b5b758b3a791ac73ae889371e4eb7bc4117eca729c48543304eb89df42d52822fe3ea683bcac2c9ede0913c73b4293c90501454725e22d3b7b756db51e19893b03ad896a7ee655e0385b4ec3ec1d772f33eea68728686f5f76890a9a927a32bb5bfda3e08125cb12b2bcb07791cc67e2a4af5fe7243fc5dc915bd39aa1d26d76c27d90d376d9f54881caf2f380633f81621310e31b41ead6ad8beeb388b88f8ad891a2019e494c3cadee4b557d6bdd8aa49ca6072d92400eae06bae002dabbf7fe7494698dd0d09acda3a835d5aa2c8a3387ea0721ec968e70fc95cd3cf0a682a403609183167355c87e5c6c4d09e9054b78cab723776208b1300838792e5905fe4053504e21794533d0c62ba8e39dfdb5fd11672fd2c58ea5874ceb2588efd5d29e21f10392ad5a2587efaad6d2ccd7ef554e66963f24b0611ef4bf8698c0b736b0be25f900c0b2b2146677451c9a6a1a795ec722fb4da0d117fdec6d4de88d853a112742d0210c2b64c84362c3bf9c068fbd57266d4cc92acfb175dc241add3c1559439999a8db7cd12bf45c390d84862d3bc6ab47f9ed93ba940893d0d78a5b2a42b1da996f84a556055002af89e21b6185e10e4ed7fc2a5146766fede6731ee77396ce8ae0469f9900dba62a0ac91fe17df077ad3348d368f6d22f9bcba68fba44e51f02918ad84ce957f19a75b61b09b3a441469fc828a294b1fa9ad3222f625f19a33542660cc84c790dab6be455d418a72b3ab2898f9607f51dc7ed69c5878a3013b5164e0a5798de938144dc61b49661644a18d250a4e05d341e7bbf282ee2ed0b6239c0a98260fb25f39207cffa0ea72fce852aae804ff1ef4d11cbcbf82ef74e5799214439a23adbfcf681fb71c23724f926dc0073a2189c5c95f4210bb79456149c66f977c6d369aa94994216f4d58807ebabb50cec21a2f375c28823c5b47bb7f34f315452abb6dc048399d0e565325a04a195af7438a412cea27ea997632aae271b4b1bb9efba3636d4cc7fbe433586600af64ba7a7cc2cba3b4c10aff3e4f0929a576c0ffff1e106cafe1a800726248571b3b8a1b5762f93d630d1bbbb9a8b10c00534e31bc2c524807877e1134a2d745862bd5adb05c0a634dff7de7de5929a9627f8f5af3f0d508c9f44b107253a551c633afe510abc107f589d17d8962eb9eddc76496a44605ca92af1b6f72575ffc41d6050ef6bcda6d1c434408423e63c8926cdd2c8b4d4eb8193e3c3c7232a1aa2780e0c687d2a0d2ad35e87ced8baa434fb6b16c702a61caeb43d45663a867086eaf0bc5dac1f2a3d7cacc738eb6b1ca407bed8ff1a7acda749c7072720823bea25601a8b367ab0181efe7f0749525060592f1c82570d3e38238b84d3921721d1a6c5c282db14cc15d8db32f89d4109801389d8f3ffab5afa16c955b721204e6eec3995d6d9aa4add868b01b55314f267d0c47906403d9012bf08fc872319139a29358ddacc7b890e624c399dafd59c9d3f478c6380e503a31896a72726bb87350c07c3f6132b758e2fc49f7fd2d0bcfb57ad6bd0afeceacbe25c0bd1815463795a60e35a497c7de70a42203e15729a0141476383bd2d45601c66119347b40efe26f25a8b9e748c1488daab79a2fcf6aee53d85faaec25433825242001f3c70d38a52d9791170a44a34a71c13b6950be48ebb896c1d351cc06470bea7210bdf90a86ac33e658019a732e82afcaeb5e99e7a0e36178391d8c69d9585c722ce5aeacbce1154545b4d29476a908201870ccfd3ebfda879756c43d1ba55c723cea609ab396fbc2e808dfa9721face052afbf5f2f0840a8dc92f0e896aa1d618b86e8061ccced78667005abff3dcd0dceb4dcc2bbe9509d9c93ae39136fee7294218110a8aa3e570863d2479472ffb93815808af9cbe7432357fded1af151399d42e82bb5738556bce0451d9317b116174dd7183578bc9fcb3df7d58c6fd672a04906f6db55b38b33ad01757a3fa4b91a47c98ea5251f9ca1a2d96787d573008b15be1a32530688b4b2e963149b8e1bb1cfa855932223c058f071371a81ec727abff9f29f19286e50ca628a68931a6fc9e3cbf8e6cf1fb64771527725ab28364719e25a82c4e37f33d191b3d43f172cf2fdd3d2bc0e56e4356dd0605dcbea62e1ebaa351de0a9b3739cf8f915e7e3ebd03c50ec40e228faee5a48b2242ef7720c778334d8ad45c148326c903c88e17bf2e9d5200cc3639d6582e083d3ab21725928d5804c7c3e7111e7bc5fb863ebcd906b002b61691ecbfb032e4d0346c7720fb0baab25e791864b5f199414b6a1136c9862f5bdff74c06262d090900ba730eadae9c3b58037ce4073a7d1b175c8d4cb7fd91cbd9056ae5877a885317a8d647f2f4ef2ac023b41be44343fba3c2dc667e3c75550f7ecd899cfa0983c993f43f2c8b692a29866baf408f0dba15481943093552c228a678bddb33a9bf83b2f6c10ca0f423244d04607c28df363d521a186899fa5f332dcc7d859cdc04a3ae77275204952b85c674e63b49d4077e2b0c2121cc7e321b00dea72ddb8f522b95934ad784f1d46dfc8b650e59ed4d5370acc4f78efdf095f64d4000c51b0a4886004fbd184276cf5080e05506d4888c6028c62eef7093c0201790291b2331cce1052a3885fbe34a9232e248713d46ea33610213d3fbcddcd8ec5c215e9fc57120d2b84f61682c3d8b5699081098c5d15318f175bab92078bb5f126b80ace2be40d72d008a7df3874e7cda23bfd25d316c773c4fc87f9b2de12599004c5c50adc5d5aa41bbbff3df1d2f07f905b36d3982f14b6115e8d3fa516c5ab1dd9706fc73a72e88faaa4f9ef083eadbe02851b87bb24ad394fcd9e6bc2538ff4825aab2e09430049785500994ffc2b902fb48559559b66c3f69d34928a690088ea90277de272af34d0eeaab7eab40a7d1a1e47bb8c362d8abd770223272187f0e07fb68c251f316f15337d2c23d54df229242496f0bf2b48cb277acd3b3dd0192ef1d619a26b184a1875c334c7da856e6dd62fe8e714920ad1dea886df91a8f3a15c0027e87277a0deb959fc86be363a274bddaebed36e0235d6bd445905adaba2e0826c9472551f387f8149b1c965023c542385d508cf2dbd1d790ecec3156d5d7f396d00722ffccd9d59fc8b46c791fa64856514f2a87927af5fb9acb5976d07459a2c9972e43a03f51ba0bb7dd25494bac450ebf4bd208f25f3d7eb90732af7055a6ba31c63305a2150bf60029274d7faf442c86ad616a00b54be03e4926e9a397d5bb738197c7b306f279d7b26abcad1791eeaf43fc39fcf93b5f968675a53311a31e3722f402f9356ff69b11b242507806b9a77fb4835da719794b6fe14c9e365816e033ea2af5a07f54f409b47d2756b13d7ce64d8db8635cffefb6fce05626be055724db7b7d125af4da8b16f04e9ee54f737f5413e3c4e18a3b3f53408ea3ee147638184c1bcdced8474b334b99b9fa3792a2fa6462f175c629772bd2bd98439e86f78f6b8eabbbd35748d5ea4bae58b533d8f4522762174cb7486ed3d5911eeb15008ec6d85627751f6ea7238451918690a05aab8fd517e4e885652d1c4f678b572ecbbd037a3cc5403e06b3d042e6846e179da82e49408b9d9c64b1d0058afeb68f706be8b8149d1d5832af9f4ee49e8c8a747804f7c87b8e0c7abd3d757d08c72df836dd76e9732dbfed1147bd0db9cad443f02a11f48b3c707a9278787e45d727da75192e0432de6a12595e0d12e315512a7233d72e2caa93b3329b9618a8a7285b8ed9c06d46a19833f970aa99bb52b8bd4ed1a481e48aaf848e096d57010110721686350be5c88107facdbcbc1683e83374840d2f4732612fbec115818d050b6b4deda86b570c24e1b4b2eb3a2b21d8567941451862cc8b1e926c08f03047248959dfb346bf870b487e952107c4782be4bb63e273f3f70a3f9290011387d6a1da075d73bb1fc0a8367b08341b2b617f889d56a8e5e5b95e10b3cee704aa872b61c2e30dd8f3fd5eb3541a8ddd674c16f982ae506403359914793ab3fde1a720d88f488c88557ccc3b470d5093cd941e4a6fded8a9813c1b0afa9228af8f7721e939d0dd4fcfb4c8e21d2f97421650f82dd878034d01701384faade65958872fc718a759f609959c1841a82aa7ab428453ba490740ceef0043abfbdc45895027a074281644d338de98bd59859f7a81f212c704ef324c4f13898e791a151c57280544568593be26961393dabe39d40ed72ddba9f3b0c2cd6a201df97008ad644a1b06fbcf46a62d7fe12b50cd9bbd9c3faee5a0afb6cfefb144f959a4a53181673a5e240581b4cb15d54fe21f297c1187149885db14189fac65da1029eb28b53356e913ec38c10f0d75329d60c3ecf6a664d72096561accf83d5fbd40615f51f3ba574757d66f8e82d88e65d996594023dfea1c82c84b451633f96c2da3e7272f06057448ce6f917a1bd91ca6de7d36e505c8ec911bb345129908dcc1a49855018d2dc42b5c0eac783cb8b6a34575b8720d44c8341fb5ce96eedf5ca05d945476176977a235347edf311ca9e3129b7bd0de96aa30e5e7b30a14eb7200abd2c13eea79b4f855b70194bd62c1fc93478825710bade3bdd188fd1b1db22ecf87e726835aa61f7c2976877e70fac08056c5294eeaf51e7f7ad09eaf92f85191e574d025141ac70c7a4b859fc76b576f9a27160ead2bf4babadc74f1232bb3638ea72b2fbffad5842d01b602bef7e6a316941f5854856dbe7d093bf9f73bae9ae8272081e9e40b367efdb788a3184dc17986a3cec123bf4081b03d9afd98103097c728fe286d2dfc759c66138332f7e5f00ccb5f5389032c340a9e0766c9a050c08365583aaa03a3a115d1c301d7c493ed70756305ae30babd1d5ac7ac1ddd9da90726dbaab69d59fbfdde054ac8659954b7fcaf224c86affd06ba7c49634241b897224da945682200a47ab288ebc10da9aa1c9038cc3a2bcb8e00530c9e88a37732c52234e5b253646d4e066d58509894cfd7b03bb4e9a8cc619ddf98bdd55eb1608177b33369a28b74da9363337379bbff0322b3c024a86918421c4b2db7eefbc72c265c403148928e49caf7e85202ade0f17490386da8def96872d7daf6a92a91b13f5896ea4c1ea9952da78b6f13dd9b4ccb41afa1937afed22114f454be81f724289c3b628398f1502966c59623a189305508688f26ca24de6a0478de037b072fc8ccafe547411832964f261e2b3e8038e5d87a2bd7860da5ea23a70f5a61b3e0f8f9f802b84753c190c636bba73c470ee605386e7946db5ea9fc63780097072190d9238bfe057a0b69fe6c4500b3d45ce922cf5426a4ff16f7b28a22eae3f6e0dda38d09bec6a11e6ff40f5320df10c2c5064936df9a80f0cb4f9e4a2072172d1ee82b71898b9d7060adccf5a2981fbc342e251d634141e11e3fe78f41f786cbe18a52b097bcce397818cb6284fcca9e27e7c71f40f3f6dcd3eb097b0ba520a1c764db2caa774a734a83991e472485cdbc637e4a0811b839cf31266d74c0e6a1b2079c58a7defc03895c14db75e8527232f143465f366f950095dcdc2b61a72d52551fb00127aa8a055f38447d1b26fd49abd60de079f44983edafc0325523fadbe60743ef09e5f5809016ec6dfd54143b57765aaaa829b01e91ff932c6b50db0a4847437cca557cfe07accc51eba13789f10a6eb812eb332642924043366330b17f93a2228044386a02dd1bc1f0a908fd495382428a142b8cdf13126ab7272c0a66ac151e361a7e679d20945d88bf459365aadb9a1f97589801df98f6e7172a3a09bf7d1cdd83989a7c343eba31b14e27fbadaf3258f786708e7fd1b1ead720a3b10df2cc0c8ea8d071b91ec4a0e528752489fe83eea54774a0467a478e567728cf5cd5ea2b1e668be49f83f07870b482307b779c986a9cb3a9d27c828172e0fdd9ae965cecbd4ac8b7e04fa134e6dbd1d696618bfc773f5f9f1c719939617ca34bdf0f54b8b9c0aba2b47c229157f214034799fdc5209e5a9354f56591b034f2eadc7cdabf42d59949af08b03931ce4d0eba99fd168e9bbbe73a2ca479b720663c7c922525b0a3ab2c3dd202ac1c669e4abc41c37660bc7d35d324ede8c5649251a3173a626d8f3d71f633066ce039d611014a4286e695fca7d6a0c57ad72104cb89d766aed333f183a25a30991b6095bb7f5404170e84ccfa1e62856450728b33a74f2c13039d9be64fc866b2f362b7558cdc42d34e41766ca52db93a57237b65bd5ec9723d2088cf557cc3773630d92e18a71858f79c6a54c93644259688b99cb3a6fc8dbe6cd687e11821fa06217f13bf34c019c35fa3d8cc2c74f06722e2ed54c79058e884fd1502facf83821d01c7ac3bf9e676f921d0c0e9a94df1dbd24717e962ba846a2b442c26c8f09b58169564143d18de064d844f66d8b490f9322e10344feef097ab7a56bc649ad1c48fb518b982b6ff1e77db69db6e8cc72448341e84aaa4a69a52271caefa6c7a61750e9ed3d71602b26d138eaddd2ce72e10d712ad46f9c3ba55ccf76392dd2c207a06ab02489c8bd68298e20d491902b5fca84ad52ce3936c499d3edddca54e013193b1f4ab24f546f845fad536df619fcbf172e07abb13be28f82035fe15e16db0d193d2b8622dea1b2614eb366bb7210b0b698ade7e63c7252309a936b8fd13b9cab96df9b461d17b5976ca537b672e499f3024bbde68d8381eda35d0b0ff5130fed9250d1fadb52b95b1fb1297772d5efdd670b4ab75a222a0d33c30e20585127414697c6acc67f8b1282f750d4721e0e5ff3092baa8e6d6650d25eb98f3785f00f5fe32d88d31bc0931c1c9c9c7224f539b44f6ef34ecde0932e80563d655755be6b9ab4447c25505a89e7b4b57211636a8923b52e16f6c26225e0f51058e6247b15aa9fc7963a4f87916b646d7287ed7663f744081645106d7317d6077c2f0bae8ceb48b9720098aaedb52ea27223339e4f8557b81296ff1a197ece15d30baec581b3feccbfb2ff65c77c601c2894509cf1a5c35acd39d7b08083045c5108fe37982217b1d39e58048674fe891eb3dfe8924727356d4b6839bf05711aef682348bda12ed21d0a5e7f6df397ac0f39eaba0a1c54402e42cb9ff5f832563662ffe4a0f1b8dead5aa4df39c9d41a699f1f5cbee382ad85232837c872eb35b2c65d8d6a03f9eec146837d1d67a0a072dfb3ffdce262e5e59c95eac167a7b20fabdf9523542161744b2a50641eff2948c531a1e9fb802f97fb75493e19a1e0cb0adfd4c0bfbe6d7fc50a02218b6ffe06ac4f638b028d53e1ffe97ba3ca8e423fe40e907b0be06a56811861b194e17372b2ccd9585f67df11e7792f56217189a4a1fd7a944522292f56d55b217a4386021bc6850470019cbfcdfbee5fb0b6a9b625884eb85f0eab2b1dff14b1bf888f208af554832202e1e614004a18433562042d253a22f6f532a563c71daf4fb780724db4f05421367631541559a016efdb35beee42ff8ff06c3fb14e14daa32b6145539bfc1945a767140f09d2c2efe29c788ed2dfcab88353b6081c5768fe84f072f4c096a175badc5b163186a85b4903da410e96dbc5bb695ff5f1fc78308ed97202350b39f42900f93e58f63e6827233b576f0e4dfdabad34292f791b9867277277a6e03ee06aca56c56c82e32da2b97bc401c0f9a35aa04d5b6d1fc23388d1728f016320d5c96814ea3e97c9482ffa8cd6628d99118a60b62d7ce75ba30d9b0d281cc391cdbb2321a763907f27d5bd422e64d030bf2db691731d02f14470a0723a3b8c96236e8027862bd19736e7ef5716856b411c751a7d24a4605300546e720828e74b861e3ff2d562e38cb4b9b3bf8d99e3418726c0f38c7b2bedd064627288a46b462aa081ebbcff31cd81a24b5bb2233faebe8efb2b1785d6f845e3874affa53885f681d6fab93de4cc44687f171429fea288a405be77b2c5bf4ad7bd5b090ba159cd7e34fe637dc5ebf2f6f3d769482fbf24a8029bb8937990d6ec3472c8186ca50134650c81e852f0a549628967c6a1c51691c87cfbaa1aa2aa3618720899818027fa70e26208e83b502c238e228f6b1e5a1016755136f24059880272c24f12a7cd46a0f07b451b92263ad034f9e57d3dea6e995290b9d95aa231ab040f4c788197c6ce92dcdd482c695fa3b9c07fe8f8572463d90de4b2658db0ef4f872f8cff5fee487a1a8ca194f7f682cb813d0bfe491003f5e110c62cd3341b5939d1defa875c57b5358d9030f8f5af9c7adecf575ae0889d3e9530b656511c29c114c17571cda4e7c224a606af03fd7ba8b4190fe9400a396a5d76f863d12472d4d9d8a57f4f41ec86feaddfef499e5078d0c52d668aa2daa1f5b4983d600a48142a70af16ef6bb124eac69d471ef465017c40ccdfeb6047427e853eba869b7232d6de27f48ac2e9f179ea633b29541383749e157528fe9fd6f74b0a525add0e77aa610a7b2cff09921cd9ae37277db422f8066e2288cec93263ccbbd8ca4f728b26f97a83d79ea567b0b6211474fa258cf8455f348a71665e96a7f907d4e405999a7f1bd001ba240ca1e8f5587940679dcae5cde02051aed8847aecc7ca3372dd529d3b93b78927804d769af8ebd55b3cb7cd2332cec082b4f10370fd962701984affc8ba48e324d68bde800902a30eb0ed535907f234cbfc063cac84f86f628e7009e07d40fb91ae083e4ff4fca9ee514967da25d6cfe1b8e36fa8128f027225b04be2e49e53f54ddafaa1669e9866383a9be520a506a00c9ec86172a9f472b022c35e3e54c6617d1213c091428ba5d53a433af2399f4726fb58f48ff58e72d6a6f2394bfb5497fb1d187a494f0b441514ea9fc9cae1c5d62fc8199759de7248a8705d63a80f168acf0d64a01fe4cc2d093e23aa2b0dd5de322ce9e0db58729544a28aed86c660476e83d3a9a163139de34383a55f24aa7e229f5e202f2c72222be12af232a3eab6de6eb86042ad1661b7e4961a1c154971e1177870f59d729487982634453c072820ff2f8a2bbb5439e5228ff54b1205c998fce475ea667213a98bc05de702832be7697db06269bf68d2c83696eb8318558556d2f6d04472cd4bd9ecedac0eb82d21209380ce45faeea23c3cc1ec5522a2d47e2aaff68e16bef5c2c58d651f23e9424bbb6c8569e204abfbcb827fdf6c7281bfec60a81d72ddc835fc9bdcfe002ff86ddd54e0eda1ddc863af80067feaf5bded582c22f33baa1a467c8dd7ca77fdcaeb294ea0879767c6599f984581ce7637392cd532ef72c0d4e80efba7b94f5f23d2215f44d9e5e96f6907b80214d47544e5d34a8e356743120584afaebcbdc742cf8f5e4c61775e4b8fd5f95fb89436b070beb9a6e172ed5d0a4c1b5b24ecadbbeb1d605221be9a1a2518636af49ef326ea5820ea1c605b6d309abe3648bd34089390bc530ab38043b764d0fc2d0baed91269c624ee35732eabc55bf7db67e99827ba760c30675947c4102f8d3ea4a6a6026c66b4f07209f371b6b8d50714289a0e069f8c1cf17995f5f6efbed5c482027a0ade084072933b3d7e48dafc01687d55341dbaa0a76e20ce95c997017b590752f64980217265c3ae7733adadb0f92cdd864d6c74842c8f46a3f3806306e1adaa7a54c8557221c36e124e4a0c49b5dedc35db8705a937ca03beb9e24a815245cf17fc4f0b7281d208f198a290c11274b121d389718521bb194a67dc48fd1f1b13aa04bfca4fab7f85b605ee6274cd5014e18094f37a9fde30ffe5a5d49ae073715b1c81eb575f286304dd319e3a0b1796abf19398484f95eb8af0062af74cadb5b6d8b7fb72bed31df13a06be3a4c234dc3c52be7fe7c200298d218546ac6ba0b8ef09f5d69e867e829eeaa454d1bcafc8944f77c6579b110fc0c8a4de3659d4cc1e80e99139dc99e3e3ac1ea2e9cde00220dbedb0fa1f2d3a92fce4bcbfde0edffb8584e553d7e698652c9e15099058340783c7a3c668b5877eea90918930f7febc0967472f56e5a5a539685acce8b0a65342db89485b3919fc167e417b6f5fe4333507b72b2a47ecb6c9dd787e1de58092a9d6c63a27b0743b7b94a9e621cfea3486e14723c39c18a37f56ec50331418dd7d15ca7beecc968dc54172b6e31b632eb17ad7253ad130b2a86a5b944f4793593ca981800e52e78e615d572c33e4f0feb901420eb5b169250d3580e051a5096ae745ec9418055f4c21ac952761272e55f4aea525dc06a9ea80543039c85973c7573b7792fe4960767e6e57a82c21bc1d4aa7f312f77491d18332a7ddfceb8241b82d9ea57ef480e0aaf59c3ec839f908b8db4113008fbc63b3f91ffef9abf7aba3e6e4ad51c5cfcfd9254275b1cdc56e54995721823706a2f0ab61413b05d69bb6a5cf601fc2718891f27b70ad6be984559cb0dffc0d56cb4fee2ec6cb51e8b8ead3ead4f30fb94a6ea518c4fb6aeea00b2c46a0b7e0e1b77f2efd6162b7d08ec9c2f689f5305d18b6152f4e2c130f5785244723c03a381f6eacb8c5ede43e06ae1ebb0064a81c83808cfbe86e419e035ee5072d3cea3627a571b76dffecd5f3919e83512dd5d8f6f12ab27284e9c07a878e7726ff986e19e7020eccfb48ff349700dd79d75ecd7a1a96df4c7506d63af6eae46eecc7059e6b91d5d693defae7148bced16b69289fc1ba24575838449d6b97c72c86364439c81766085eac4eedbaf960be1daacb32172c1c6b25de825480afd72cd13966476dbc3b244e4225f835756384fa8295230a53415aa7de7b38c7dab7254bd0fda6272c63e2a281d428b06b34b43d279dd45200c5759755ba63b3938727fe4899b76a39b1e3323cb79998c809dbb0865c9e3d8748c60160569a4ca68724aac2bb7fe3b810da1a866465e2b265037409ee530532d69f69285caaa79534fd312f561058ad2d128883a4c7b79cb96ab5687618d128f92e1bc0cc8239b9f72098d59cf5783564c8d77e95667e7fc8fecce73cf807f068ac4d4dfa482c6507250828b99f6777e7cef9e2c8306d3d2d1a18e7f6c355499d7de4eb4906604ee0f35601bae4d5020928b5bd16805dcb6f329d435d69193052c00b99e8f36048f1beb8f4189cf483fc6848b717352c74bdad6288116ba9e6eb71be5fb5f970e6a64946e6a6e7d16d176155bbd654bb23da5a23d11ae8127993506c07883a518e83be00a66f5c6d28fc76da9eb2214b24db2de712de250273c76a4e6bf48ca620c72e9198344c8c0691a0ae35ced3b29e39b510f5e95c357e91b7717e625d43fc872aa45ffc07674a791e9b8f7905e409b4f5c493f8667e0f01d8b99a9615d4181312eddf9a052787165556582564282e7264b519d18dcc23519a6137e725e8e9d6131bc784beed76de56ef7899546508c49f9a4614977ffb87034376116a5dcf95e9939301db0f409a8af45853c2a6778158c87f8920c54352ff2c4e8758c9b1472d55c62a6cdca2089c872fea023b5a51538a9aca942530a81866b007f7140aa22bbed96808ba208afc6da840d98230ef1c06d3de92ce506a0c7bdf32281c03372081a54fe5825d98a94c3f33cb24f461c2c22dbad13faa23c7bb28b5e40884572501822cfde35d7ffda70d08dd50f14497907361e3f5eb22b338cc705daf6827297fa6f9405f78373e3528f007197d3695d19dacc66b426f6f3e6a8da6780c572e5bf773b2522ab1294bf2b0e1c164c3acb8b7c47a6fbaa6ccdddc14a2cc63572a7939567fba8f7ef40c4ad4ba6a7e2ca341d8eca5bccf41bd08f3b43f0fc90039828ad2a7f96b35513bf6ea7858460721527f63e362ec31d4c79c58d7ab98b2726eb3c1f3512c3eebcd9a044a30f5cb208490be14567574596b54c6e4683e97296285c5556364e9a2d08c7e49a4c9616ac835efa7c782b1b1128c32683e40a723963917a9639987a4b850c6ff95faacbb64b3168f192ab30b554f93b115e97722c56cf56e70de8c5b4cbdd50ad8da02e4e71ddbf3f571dd31772398b9a96d972948dc17440b140ad4ef8ca1d93bb68ae039ae16dbbcd3d34919e30b86410f4728c3271b62a8a58ff42b24f9fce66cb6c2fb86ad9ad26780ec4b3464a3d9622379445c15730fa2ee16eee60fe28a0ac5786a54c8be11544186f6a59fa41292672097aba660ef88b10666d1fdaa0c4db5eb019371c2b5c616d03feed121a80226d3c6704c3bab98cce5fa293f665d8dedf0222ab9295cfbb5a28263af1d4c3fe72d6c0060244e83e65332fc4afbb24dabe3b4ec12c1ef7c3e87bdda9fb37f2fd6015b92818f18e652d6a19ccd6bd0207e69fbcdd673736449a317170c4a14118722ab872bd929148bad02ec6b644d47425520f81a3222e22d736bb65f897dff449974fbc840e767fa86eca2fe1ea8957166e6682b046223fe17f8726e9843e3c2f78f192dc22399947081a0d9a5cc04b34ba0d4e1fed70033678567a2bdde4f04cc1e15ae8b32e3e838a39baac77563847064dc3b06f11d56b0966d374368f7672477513f191f457eea79782325e7a982a0c6e68f576cecfaeb2aaf96594f4726d1a837b84bfa375ae38ff62aab276469739d976960d158aaa10aaeca670124a3bd56e6a2fccd47a9c81fcd7ee3fb32ee4900d5cb001237f5a9716f7042f5d91721294cf61ab446ac763f7094657a689e72272a5f427d69a73ddccf3960f26b8729f3f0792fff61260a21c0745716fbb48735ece5e99a980343f0bd2ef6785ba721b36a5211a0ee06f3c004f9b5087ddb0dfde62011072464ee6b27e84d962715ae16b09ea7e425d41341104395d5106e4abe98700dbe30f1a5cb6babadb7f2572dc13dd632968fab9c02285ac78679075a50f6bfd1d6b8e14b17ae8e0a3b02b6039fa8c5d1c7b1afc96f2e608932a7b83c1172761626ac054d7bcea37fff8f266b320f281d72738a1facfc0138be94f74734178da66be9dc1ae5dcd6b4b2a4272ea95647bc35d3cfe1c53a0c7993d1b26f7ebdc1e6365f34870f211b272dfc85f06a9dbc872516b953ea518e8cb532b63e5e700b122bd52eddc4e011aa552c372956e11313a39c4e1ffa8ff120aa5d816f60eafd0fc00313b0ca1f7dd3684ca72409d5e34956931f151d40bba0a0faab75ec5c49269cd016fe2048afefe591e726029db7f91b6953847456fcb3fbeea3e55832def789ef4a8b9a876b4e2f66e72508a11accee900374c72999b927777db69f5d536d5eeca897597b5b7267a38724061dce8c60aef257d259ef061fc3a8d2fdf6e70c00ba56a08018176bac00e72edf0ca7569443d2e31bb403f7f9fe964bcf7b6b962d07d6b8589c72e66e577723dee5b5934aaae47f9d8ef5c8183f4a9822f2336e42b5d8116877d73797c3e72eb2ffd7053bd816648e54e983361c0e09ef76f3268db9b9fb105d4210bb42b720d7302937259171c819ed6553b222f236d9b10be1f72602cb649e5c15b620565d80294ccaa48e974ea78b4d7140d4f5cedd91b2b214159df4cf65c865dbf7372a97aea1bafa93c1718416db62a1e6dd522c8461d551ad768a61fe16cc344bf72cbc9e036f10c37441571223dbb2bc0cf0efbf7042b7f598edbf65ba319480e5c6701b55df883d63916c59d08e1b858dd1c7c22a49a1a738da435873bbba001720b1db632b6e2eae94b66c7c5ac00f47076a507e58f793c197246e8994b2a6718eb27a2c9151e76fa8089794ebaab00ad72c78033c6bed92e8172bfa735dc926fb5e2f74656657cf254b34e5b36ea685ff189d4e439977077ce067e5101c5825333ff831d686dd12994236b3272c1cb17d10511169eb52fd8aed5bb1ddf2704253f06e59501c928f4ff220434e89baceef7a2c74405f6706c7326e89f4aad0f14b947188c9a5fc529d2ebfa18bd201b6eba87031f744383c0a9c7d7efc5c27d72512dd72ec5149b0c8ea19bd01d928c7324d69d4ff4b08525ee14fbb4fbcf8472e3442e2bd523cf264b2818fa0b85467261b74ed9a38c81d8f1b1e29c9ab99f0c28dc8751d24ceca0fc87ed1fb6f9eeec323c9232f42b096bb834fb5cabe0a308ed7c928fb26b85441dead08c8fe2384fc1daf21ede370ceb70ccf98973c22c72fd604ed90291f90d67abbd67b04ce5bbd85672cc9701e95ecd8c416370ace3722f7c75e0674a7b53671751f32242f9ba46d5b9d1913610f0aa92de5f1ee277728a42d02054a176cc1780119650f83da12af0eba75bdacc3c7b83b4aad29fa272a75a7762c776bdc88f14fc3889e88878d82aec5c7d034694df28381a8d1212513b518f28aed8e5b741536d8f08f8c148ef69f8679de3c7e5bddf45197ebb41367d38b084d936aeae61efe72d7e1b73212c22278010835be2cb92a68a039ce07279385b4fef93d21064e1847a885d51212a5cd8002f80cbf07bbab22abd80c8722bbdd0760ffe6be6520800d16363ee0ebc5e1718166d082ceb174fde5c134a72f682fc88f53a8b0a1aca55d2115f3138bb70796579c82b46175015e835c00672ef7bc05f6962e8c9d9929aed5b6da82504ce0638629877a236ce232c6fe2380d2a6bd3aa926a3c523e3295c674aa6df45d906da70603b443f724bc446b0c3572460bf332588d62f458fa9ba1956b446655d11f5f68346a793203e6f763dfc00701ac4bdaf7cf832d3d2ea819575d276eca7a79846f4430ee036b5c941729a6724d7f5d62a02f1c28e23c336ef21ee0c1669fce4b2d9fdaffe7d18002c625083a5b6d85008a57ccab46b2795e2f2100bebb6832ee39cd8d20233f1979f3264b72bc5795b25eb7e92ed29a85f0708f95ccf262ce1e66af9393cf364c494deaa77274b96b0f80c03f8fda4eb5cf43110134d4c0ee69361d9558e79080428e605172f73ec1831c2873b5c3f22143f3ec7d0adfc843cc8dc8bf527a71f4c3a6c1ad721c2e80adade3b9d6745a602b423c33cc5799a9d193f37328bc07f47a73e6611dbf7aa4ae42916f5791b3ba3c38ba312812f43722c601624050eab0a640767a4cc22e51133921f545a9631e8e98e4bf148eb79122de2ee312e2b056b5c5dbef537a777c1de894a6e0ae7f471e9043a273ff5d2f9bd7b49e4f58c2d86850694f72912b857d725ba79c3a388445c7052c016ae4397ccbdd420e09753df713eb217285cebdc970b506eef99acbf58bac5cf6aab17ee601d85b16942869111ab92d72987db488452a5920c4066791be1b875e5522823686854b68022e5c4c006f5372fff7c13e049b1acf670d2a34f7dd8f1695672687ff0b6d7405a3d3ea9bb4f672c6236fb122b7fe3cd46a7dd09375cd86895a830588991dde5c719e02a66271726fb2cd62a59cdbc02d9362ac67b1abe4f6a3ed87fa2cfad26805ff1e7cc1376fce0513b5ad3478ee605e6cd278370c56c60784b818880fd7403e158aa4d211723e43b5b87c61b7b5f2fe76757eb9a73d994cf923913977b11e9fdb6c28c9f372e80971b38c9fac50dd9ae10b89fe99ebe2cc7c59b05a4e3900f09fbf0912bd6b5c3c5c0c3b0f86ecfc7c34aa197ce7d6b4ff22b5e21445884b13766c647ef97276ad6795f78014443e0fb7e7d4d17ebde1001aa82da9a763a9f48728b47bba72ff1871110e19a54850ad3a0003d8d7357e056103bf69de6f2c49b23a10f18a7032557c7e1d59d6810e0b06d5d9fa609862fe1959a8f16a0e89b63fcf8dac942c1a76d6de1a7565d6fc231ed5fc346e3f2e2339c651cc8a59dc4c04299a92006344a68242c5e5ee1842c38cab789b0b4a127bf3da1a3c30250e0ee47d4f92ec63312a73cfa7b3e4779930e343a8b25c74f287810721eb8697558210231bfa1412e3d6cd124cfb417e7a95ada4af9a3e910dfb2471ecaf0d6b4a7edc34b3c8b35818cfdc3090db10a18fac9af75ad01a6b14eadddd5e913dd739c2f3819377831d848d3cd7e9556ad39aab1b3b6b698538902201b93767af3511c18dc751a3552844af2c69f5e8a345207326ae093186763fcc52ce311c835079c8f91f3b7a1272d8c8a49e861a589d1744d36cc08d8e5b5c3c1daa76a60ad6e54a9feed808e57228600a5a96bf3f7b295e895ca01ec8f8fb65e7bc37e0f91882e39155e02f5772c5a73adb485bed78a8f547213d0a17c9321a400657307bf87bb6c59f5df23c72efd8e24104c173f402d11cd953de8024221feb942152abf37f68bf4797954a03d8c6527fa44e0e2b11e937ec4e4959eff164ef7e1951b081942885285b335701dd73e863dd76e365f8064071e65b713932c7d0f95664cbec9a77062c83ea5572ece88aae30158a4864627dcc69c851815d513506baafacc0cdc10a510f135227eecb8a48b875b84fc1e30b70851508e23d78a7f2d63f1971d8a26577f8ff081dc1a829872179886f11902cb903c483963c0001e09adf0302e9d6a9aa0140dc6e9ae263700195586b9a39d09fec2b05d232335c43a4b41d24f9d85904866db8721438326a0dce781c75acc42eb2715dbeb8d855b0d1c735e875c04c694045e972661e08ff23d9df2e8c6d88a7b96c970f09c700d28637cd6e71bbd0915af44e72fbbb606ffb17ab7956d310d79adc1b0056286d76a48a8e3c9f783f2e3e80305a912b047de884570c502e9092d3cc4ee0e642f3fd962136fab0cc516bbd03a856f9980e044487651ecfe7a2826891d55fd4355f70f80d37185293a0a9d91b3c18c768181056028f44895a70c0ec4f39e54a3018cf2839884cfe6167ca0b62ee72fd88ecb260550a093c8d2555ad6e2aa263c550daaa1d5f67755729ea5767d10e3cdd020a44ca826b5174f14ef609ea9fe1d67c8bfcb1f60c7bd94ab98d746f72265ee3b8ddda120ee9238432b73dd785ff8f1f4b503132628d70156dcbeb9b728249818caf6313cf1bf208aa09f61973cc9cd05acc24d1669237d20b3dc686728741b07c801ede2055e4802344dcca491a2c8aed90a89486a06e621c4446aa65ca762c954a7f3e4b2c5a99df73bdd63b04dd409abb68879ea44c435ceb5a7572590eee14a08672878dc30ae04950d442487983e21a41a034019d088ae45b700a0cf8a72ef5650fa69fe86bce47701b51c8552cc8c484fc15894aa9b23e5c8e7207b9380757c61a07e4ce0658905c5d63e1b8d9c91e7fcb25dcfe8bdeaae2ce5b6025d0dd8fe64fc85eb1ba32f2b696fba3affb8bb95b549701818dc6f9e36e40de48a737396857d7d4c774a66dfb3afdb061caebb09905c4866f296f95df8656b6604373da7aa6b2a88f573887e22fbd7b93242b1c84fc0c7c06799e0ab209034dfdfa4edd4d8337252f9e7c1d6420b4fdb197dab4b4baa1c90578985461962a51929a87c7a64ab51d70d0c58ad7a87f171fe93f4ca8c2163c45f373070fb872e9799493c0b599c566101f9c28a3efa70f354c2dfe8eaef7380723ff5238a672a3c75bd97d43573646a9e67126450e301e60162ab86c7d85ebdaf9d3e4cd1848af23531b93952c5ba8672669a7266db59194c58e4e4803252d653b163e9b240e60f20dc78639493a31a07db94f7d9894fe638f94c7e5882420f323b575a9075528e409895867bb3bca46787172abef81947677ba0556db1652268539bb7ed75c2e94142f79b434d17e4370eed5261870a7525a38d664ac3bec98ef95a2d66e6cbed83efb6181fe1bc67907f70cc243e3ed0f56e79bd05124ff1bf712ff31ef7201fa6043ae25d8054873982e3aa18dedd4d371b1211ffd84d1d364c6fd01f772cf15725e071363b369572570f973314e9bb4edb6933196ff27acd57debd58e726805f8bfec5eb604510a0f01276f505b2cbbba2896a8558b4a2bb2e6a1405f7254a97c6aac35396c63ecb5f89d81c4dfe54e6cbc2535f4f0dbec45cbd6e1d90934bbed56bee3030a14e295f05dc36e696649d190de4d35c833d6c7c015124b72dd419685a1244ccb8ab3659302fdb67bc3723af35129c83fb2e0fb15517da5319a654966bdc4151df39aabc74bb068bc812b2660a8689fbd30c084e0352f165ac4067c185450e46e3a5af21fea870f0ba17814c97e167b503f094c8cb628693dfdcbb690359d22e16b9e6f37936694fe1f7af751ca3d1a43f7efc71955cdbe72d07a9b25d5db8b14a2aa621230f2a4a55750a1e814bcbe666ecc74b4e85ccd729479c1893f9b7a8911a691a7c8d450ca761c96966bfa1caa549c4df2bd2ddc651f71ea33b6382f300da7b9222184475f0fa4340dae681a928a4f5c25bbf5d2434fc55de02e5cfdb4eb06929d5668d4b9df074ee289d31a0ded190fc24e943c6821296f941fc1271bddae1d451077c545675588dff4ca18c29d2b5fd9ee3ba972bdaf606d27cbbf6a370386220bbf1ad3f80e76915cf4b833f9bd8a421ff31572f7e7f2da6ed52665e0ca49acbbb4092e358734f8aee7562c9bebc31f4c160372c946bd65e3f6c373def15786c9b8f35094f97c29ce0c70dafe52fdf382416e03b8e8967eae0ad8484bd276d1afc86cac19e86d6b526be04764c7ae62897b99331ce770871bb83ed520a68aafd66039e551a8e0766afb9458fe106b5454955f728a2f410146d31dabbc99db795f933510c7c42e81877761b4a49b28b1ff82b76dfeaed54f43f5b2534693518fe01dc3de70ccc3fdf71127a878e755da0d48dd7286c3a5fa6a13771d15d10f1536f6cc798d2d1f4036176eb25ff5fbd4b8abc7722b5f7186ac18079a5b4f2c593ac65a5b334d629a00395a49279a9dac4bdb49727660eca23f8fcbb3a04b55593288522ffe51b5fd4f9554b57ebc5af00bd1b9724d53618eaf75bdcba14d81bea82b6f8b82450ab6f3a4d77c15788738626c8572df0e420b2bac02d988d4cc046544e8357f6de9e8d9298acbf6d03b3010d0af72170d6b1b4611940fccd3aee30bce1569d37967353c17161adf24073768c3b16e0b9237e69a43ecef6ed4eca673874751ec6d3dc07911b5d66aed04a4ca33c63bcbedac12e1d56d7e193818f09b65524b74ebd1e606a7b5c702c6a3b4ecb59c72e554cc4cf445f547e841a2cdf7df9c43191e9a5c16a65f2e345b107fd2d56a72b9f958c1a7691d5567fd902c1a6d9f2a15d8c1f9cf54f6d4f7fb7c68e22f3a7202319be3cddbe91adf63b412e4632356dfb6c0a1ad068bf9f520d668fc159272872c73b17bd27aafed2fde0f1ccda94499f2c6f7ff58a04b18c5d51ec2473c7241b9adeebec2f9e38baf933093db9a4eee3e1b2e586adbd359d0d69c92606e72d614782ec5228e0eb81fc24ea67f61a09b9f2e8ecec74ec63b80349afa38a0720a0d9befe109252a35227c15dc3db2fa369274368d3f20d91f4b71ae96f15972878c2f6eb8b7073561eb56a54aaf7d3e4db5635c24dc740ca0bb09a5260d5972d9eb7acdb3eea7fd8e153ff47b483512b367abcc9fcdc2f49e60be2c84dd4a72f4b802edf702c626999279d6ec347ad95411b743471dbde076741527d673194c35e20bb1657b5bc97a374ebba687b6acbbd337a171220825096f4d791e2e7f725830e20febeac54fa38b5dc2aca25749731ea21e71ef2945ee0a054cebaeca724222dfbf734eac6fd218136db835fb5be9edf0318bb981e4146afa099637bc727dbd876545f899c379d0829c56d4d6b58a96ef6155c01105c823233d54b69272b65fef069b27d71c6827eaec97f95e111760a2d613e465c070ea5da9b0c93a288394c5ac1c24c80fac5998cc18b42644076a01c51919f58e1e1a395f43be4f72bd23a934ca531c384e51e8de5c7e58129544d9ac370760c4bc9d58148a5e0c4af5dc54682fa105f4fee06ba4826b37f3b9ab729069e7060d04fbf6c854307372cf1f60f8f2b0631cf324543039a0171f50257c33ef44309d26d46a55a3c3032d4cfe8f6a444917e18eb26dae8761bc8ab65a3233395522dddc766e1f6e0113722b3c53d3e9b591df716c3038bd8ff128b6e992b3907b14e55a6c461727b12459fe07579e7c464caaaf7e826dee1d6283e1bb50fa38ded0cee3f4bfee73ade8729e2fc6af4f44ed26fdbf6750bc2fdf72b9342872f6fe9531c186a303de273b1730257f44363dcc4407de5a5ec68f8de5cd200879ae38cf280c27e0835fbb8f7205a257818ce441fdc82f5ae3c445fe85c8fdef148a270b64ba7c0d84013b9e31e6a0d0b296db90a72d1c7587c1282bbf3e05502321128e4da4059ec7cc6d1e72dc0d496d2ca82d11d2a8e9a4eecc165665f46dc84b35023201ac109ff6598319881051684ea09276830288050e1c2ebd1191777659bf0afcc9edc932c99bdf72a857b1210c1744e2c1e599ae93f8840ba72c50c5ab9901cc272f9d0f844de072f78cc50ec8c4b9842d67765b3e2be23f48ace770ebd3c5c928a3e73b7096b6720d69d7871313ab1e6b7741b1dc0026d1d8502fbe911a50c0cf6b86115da8cf72b9906f9b83f4d831c648f22e32161c808aaf4d5cbf2f964fe7d11c851feaa77261c1eb82ffdbd6184ff052788d77b1ce0c1f35d3711dae915bca46079956ff5063db7e5bd12f3382b30f9bf5942acfc71d30bf62bb0f3f9b69346f8b67648e2049db3e127688314cf9eec55d569860b5c6eb2c090175faf3955c53b665c66a08366870ce31d7719c65bdd6e64739f92a5d59578e192695a36251d61830d3d735f580b6efbade05b549b7aa27e25603adcc56d48aebecfeead1d2a5937880857218012b7713545248076f496616a0099ead0b62e39aee38bff7d5250016b21f725f26e40603c0318c2680c6d1b33adf92dd42d0e99ec5aa2686730dbb9b257372922d419af8ce3185ebdd72ce266a4a6c2d6e916b05ea4e00bc40f8b60c41f87293bebaaccd089332f5c701de79b0994b3601244097ee060060a670f9eae55c72ad5b19002bf7703fbc9f4cee5b53e21408fff76225043c39874bf403bd8a4e72c6460a0af7b832e79424411d22e78511fb9b42de43f0c249b1e86f544176cc72ad65f6c5782a6e410d249a9df41904626af7c0e1e62cecf936ff5ddde5ce9072114af528d17e319b83555c490349c2dce51925aad4b5bc8180856395ec157033161d00b6717109e794b0d7e5bd39bb886724a27400074142b0f6b31ab7000972005cb01f19e13f26b303671d5bc996dbd6ccc094050a2b60503473ce54cb0412bfe0c154815145e5255591346b927098707cf069efadedea4e1161e059e36172dbb91c18a32a19f0f2d219cc933471bdb04d67d8f60053cbcbe2a7928c6676722cec29750c7b5ec88de2b4db0e8a48d41a8847b104930faca8123f9794802a72a2a73e3bf34542984e479ff06e3fb29fa7a28953085aa739d3be60c3720feb5c5e1324721eb30a23a5ee0cb196d98ba7f6456ed8d71f5f8021c70908f42f010fc0aca7c412eeb9fe543ac59582f7ddc645ab261b85aadee86206a6a53cccfa6ac39465bbd83df729249e1f6c35afea4521c245d7d6b6448dab6e4ae5087337725e8ebb24d10b9b5f742f220fe0ca6aa817d1bbf39b5b6eceffaf8f0c3afd4b0ebd0e93fbbae0f2c49c3eda1660bd5e154b14fdf5e067873c78bbd479ad51d872579287f67d7fd4c54cdc4e6cefaaf1bbe0dbf9b9407fbd9e65a2fc7787410972dd580cf422068b0b414af25c431989ee469312977deaeadf8078718d63b5be550a32aa25e430b8b0aff8c7a88cc39380319cbec0efffa3bf2ba227fbc839977281c11377e37366b008a755e1718815dd445946172538f98f1bdb49348a2de322199c942c5f4d314491e402d63f4c9fd4ccc2e7fddee95d1161aba5746c04d040c88657e8639e67e7ef28a5f723c755e46d0a61ef6549b538353d04c67f5385627619df802b8201f1ca80bbeeaac009f7e13a59a4f5355075b99fa07a7b832b5d430a0de02579945095cd235c964a5fb17fdd28cc8e5874bf64ff819cd8c78c72cd3083b11f9eccda5d531ea10c15b19f54a22f11a2d318eacb0d511068957c02945e799b3a7a65ef95eb4e771217f4468e9987f0a65b2f621ae5e1756b58ea2d789dc66d0b7761663af2f7dee1158c6555f70acbf3426401b14e5059a486a36cc937cf2fa3326d0a75bad27f8cc77cb7e4d1448d47dd420f5998b45c09972f1a1009066023409b3b221fce69c98283045771d0fb19ca171584a461766c23647243cdace371db589185f10e63cf5bcf7899065a0936fdd66acfad24b3e08005729b1198d34c73629e4bf4f17987377574bd6e1e9006f8e02912104aeb524e2450d2b0fb393169f8019d4d7eb9270ffa3287595e20374e5bad704fbddd29ff8d72c3d3568120f8a232b1351d04727ab92012bf7b0ea5cef55701262f1a0292f90f8c4327b0bce401b7c53d41806d3ce3f1a905e6b8370de133cff82f2493f21572ca824cac3705b00e2cbc7913c2f6d91feeb8349ebcaa06b83b47b239b4b73a5b9f7a3f3918da5df373fa26809ba8aecfd587e10951caaedad98f5e5c446bc31a4a0b69aef932af56f3da95e7afaed22c100197ff960d02a3941044290acc530fc6c0e193b1aacb255d84bb9ece5452afd21bcd5d2e703b38c54cf4e6006754721a42aa9a40598796f53f1cc2250e37c3ab90ae606d08b9ce3dcd43485ca7cf725271039cde880cd41d574524c65317105a9e11b152798007862f39d58dff2272e4c8453e2fdc146c697d2eff090f5c1725b0537abe8c511d90c165f2b6bf9d7219a0c371f627e639e73e74608c919a7039ede1fbf28a61a904e1f864ba3e5672886060ab118baa3c3abe9c8c258c0f4fa50c9f8d86b793bcf39c0fc4e3913248bbfa48d7b4cc9a28cfd9e7dd291e04053e7a4254b9dc3fa3d6d604b28501aa0b6fe66f62fba2d0a4e8a49477e7d10837bc8ed9f2a706016e51d7e20057b52b72d9efff67946b1d13a2006f0a9b899a7e3683bf9add43e1fde327fd68b2f9a8696780c3ad99879d05afb35ddfafe325a79558eeb8a59b0925d5776cb3d316b055afc8962490b6808ca9120b366ff5712d68310347c4b07c4f043b2f63214f262dd0f93697901a8a58f424ce34b7d089ce02556435f2a263622e64ac5c7ad16d721133b1d6c960b08230a4821b96a79c5b1438f61e3121a13fcda05f8fd4c15f7269327427ff2a9166a992af5355680f484c6627dfd6c8bff8ea99b58fb513306077db0070ae0eaa75e1f4f87f1a764a3760168a264715dc611c2ff70380818428ceec2fe72fb5715151ea6b898807a426f26e79ae116100c88193d974c32d4c723cbb883df224b70b2be0fb23227b77800aca7ce87d510ad1900aed286dd3e7729e6fceda02bec8d2307107c3a4d230bb33d969dcaa2f3b646f929735011a9d72a198a20e2ab7d09db164431a8d76b6ea79e3fd31f94a6289b4ed1c0c631e3a064b0091b0efd9e2f115acecf043674893f0319032a7973582dc2340aedab921725c24809cae1ef52cb45a90302152e1563516a431dd16e9ab27308ae6b3066067a48228044dc07958471690ffa0a651b69c8ff8936a30429a636627f43e99e072c3d0acc875e7b908da27026e24353fbb0607f0374ed46e66169fbc0753947111b920c5809f7b12cf264f0e2695c20f5531e4150fb65898961e90e648b30b7972966a692b5442e7ebf41ab4bf8a611bc6ae6d3a264ce4176d15802cfa5c1f89562562c72e808732465900b74d8c02808ca5da38167743465bb19597b4bb86543de8e76555d2501874f0f72a32e3be0f1d6e73bab6101ff9b73a83fb922a1c1072df95c6a2eb4d4788f718ee6ab28fdae49c6d8a54ed663a50d9d0e0c2faf075720a2b6479c6170e3752225934e2794d4d77b619266add1e2928512d19751c6872e4caeec681c621ac62eeb7e9071524964df41d905cd9e9a689abff1c75c89372b45844d739e7694ed06892907cc5814f4f40f80bf3e73f5770b26e4229b991053b66e8e7141d37bb323fe7efec9083c6c623cbee5ff0f19a2d56b5ddcfdbe072e8f0d9375afdf7f2d4dc543657ecdcfb37d9fbd32299e9578a70260661f2501594903b6f186995c7899610374d3612e2f37b813b2713be409ee7d43ebce4a572a4302a043ac293382f4d76d368dc3341dcfdc522068a93d3531106ac1a3ef630ff3de34611c019dc2882a860700b0e4f7897e62cecac7540572a0916cfc46a55be97881f2d80659cdef3055891b119803a6dad4c5820557ebdd49dd7759e1b4703c63a32da2a0919f66031cefcd6614dd29bc9de5f7ea5228f74b58e4b218172a5a16e818fc1718d0d4b1e0ffe7bab2ab4a006160fb1f6a687e67186907fbf724e2ca47b382e28f0488aa009506ec01770e22a8a1e898d5f36dd2bf2a4425772ff417d6241356454fd26e8c1864b24e41493cf69d49df6cc9804616b14b2f072dd4e4462f0dc5780e80d1cfae290803ce64ace83721001855092cb73f30c8f72d45ebdbda7900cd728ac38ee72d1e996efd457e79b464de7554c8ff4efeb13229232e281f7cdcb2965ac5da7ed82e72d9fb7129d640df6ba978c95515823837284b568a73dbe0d701fb0b2b292ffb1ad9727f3b5c7bf81bccf6f77e2682bf572855b7d46d797963383f6d4e1a94e32dc449baee99277ab047803df2660ed9955c71d70404f3522bfac0763ba05315ed286bf2180398e5cf325beecc7c8b6481d24bb4e03ab3b89cda256c485774a2e2acbe9e6c35577761e4fc95a47f7641b72c531784afd5192bb55a8dd1c139d58df313c601590a9860fb150d396561bbf72b965606d4c748fbe3a76647b230893ada861200cc80b373ca5fddf3d46eeea5ea40e0c9278731bf5ec01aa3da3b8a3ad21f67d6729ff3923e463d1c52b08aa19cd76d343802b764eb08a1af3f452d7590d88c92f76a7a8c416ad9638cd99257248f71a4e2ff20067c123861ec7506232fb24b136f0131aa519a4c73b472c8272c74bb109591b678b6279d9dea28d412bee79530ca8aa1b821a5856b0b019d61f73a02226f88ae1dc3f1972403128708c59083220b925db634834913007b0ad08cdb62c6a94eace9e4dfc4ea44dc2f5eb4f10f73853f32fe43b31c94a943a1e726c7d7e4203ee48b0e8dfdca13a899cc28dc69b0f4d37fbb51f4e2aaf10564507e96557f06fa5a20eced591a30c1d935bbaa681b3fa39683fb4dd9428ccf8145cb1624ed3d681824653bd7e81bbd93ededfeff0897e98787f81d3bc07bc29b5721a27002066a9cbc638b1088bff0343a89d2d2d9340183f1ef8ff13b26ff02d728fb83aa3439c6bddf89c6868e03d4c55633f065aa590d7d51bd9806101680572ab51ae0ea3b68eb6bbb4fd9f0310ceb1c7738b3917c2963b5e0f5afcf2d31f727e47027848e48861ff58c8fb4b194b57064c98d05b645c53fdfb1d2fd478c94e6a3ad7a77f5c82325ea4c71ed4da6be4cbcb51a9c9d153da53cbd922aba247723eef0b8ffd53fdc74511863803d0f230e24ac33b87dde4e180372f29cbecd2727e5f93fb3faac1b839b3a582d9e4564938f0ae0c5cb7cffd2d831dcba50e8521d62e9be417ed758bdd4c1e413158b62161a19fa7b5ba452142d98b104eab69236e9df8bb838b5a747d6f846d8bc09c24ed35c79f0d9a1556515c995d22d1f15af706c8abf69b004ef20fe37ed6b14a219978f4823b60f7c06e3f9f8d3c3b2f2edd84d9b612591af28c715316d24b4cbfaa0649efed51cab6dfd853418880dc72ba2b9d8fbe2b52f6179a5ec3c182dc3ee4eef1e97af99999bea65c226abe6a6af49c66996d4cb284da2daf0a7c01ca2b6145366639a7698e11c6f0d4cd769f72a1d9be2321a244372f11cb303365644692fce32cd1a8c6b438e90512f6fc7772dbbe66dde0bf304ba4d0f60731afdbfce47f1d9b6c7eb753aa29d01c83790b724aa4f31b99e0420541504a0771a6dd8a32cabd5b35a4e92b6da5b11d184f1c722526763240f6bed544587ee8ea0bb0b0f420229d9e79aa926f85fb8a0cb7d0720430729f360d17d2f14fd512484df73def713808d5bae996f5b7046b034532725f42d7747cb8706c64db92c5a74328d985a4f83dc05586397f68f2478534775418701a9bc244fbe6181c34ebbb4c847c8bf9dd8681fb138710a885ca1450cd724093e5876d9060957b83d1162f016b2fd9a7f9720af2b622a090620cf5bb5472f93fadda6fd1460281d5c7906950b9c820904e76a80888f0e69dcd4f5c349972c92af4d0d22623128497f058a0cbcdf62ff03ce9019c4416d79718c100de6b36bd62c165df6807692d320dc35e45cec21f00e7af0be5e4e930f963f2d06adb721b35e8f3177812747ba740c7c85c3ebd5a636457c27d703e18bd816c10b27b418798471c2282eb778659742ef79ae008c716f27b12460a901fdc908b3d6d0e50498d82a9e79995d48b85757de980a9eb086ea7361a9b42f3284d6c3a6d841a726827c8a53cf79d1b188a990e129427901e7f30bd23f0947203ca6e8ffac30772b8aa5516ad890417cdd23263160b3d270305ea269f4ead106fbb080fba50ef2241540f4bc7a8c4015a17808ed0ec6390d7bc73832ff57174887283db6a0d6448e9eacadaac709e1ee40bbc91b1fe542dd1ed7dfaba9e90f557652754d5d49b729bd62aabb4ada07ffc0977be0ffabf52e35b4f937007e909e59c60d871cb99722af793396c03555d11e5c209ec46068a1e06c17c3f8cd0f63afc58119f533172ed1680c3d9a7e730ffb204af5b1848c9f46018c00b40016075d27e6861b5307290fca2dfd4574d776106d7ab2f908752f358c0b7af4a896ca11bceb8ee29417291ffc42e633b322a1403cba10b3a21dec9dc601c52968ba849edeea3a0e5f020e7a855fc93603c71052bbec3122c7d59fc3ef10bb9b30995ae06a1e0fe9a0b4d1c99da3cc28d05e8575cc3dbae7f6462df7d6423971673c50fbc96599bb2c45d270afe403ec9ed6c8b6129ad7294bf77322f65fb244ad3d6894a2bbb4415f86a2dbe7f3670377ab25aab69e15acbf31fffc167e2ed6559216adbf62914818472521e1777c64bf11ef24751462458dbad90c5b2e63711bd1483ebe66577905b395e202390e1c780f9112b1a22d55dc96c6bb7acdd1b74e062c1cae64b1860d166932273e09016f79b0ef791f22368fcb2a3ed9c545d95feec334cc3bb93b7e2723dcf8b339e3499aa627c0602b65022e572a061e3caff22ff66fe8ef89edc7772188f2be6b8a67e39857aebbc7468858c47917a94bceff17f967a42e30bec10158feffcd8e46a9f0b19c7ed7379cb4c03e46a8e53fdc6d941c31cbe68c363897204d2e2868d9a7d503b9a2a26bb2e27c0d9519f62ccea7eb9b448c9a81559b95e3fcaeef02d65c2cf099d84e52ac67ccb588cd7a15aefa966301650f0d4c08e1b29e52b543f1524fdbecf57195426683c36e3ed61eb2932012d4669c16354ab72615cc2222f75016ef0794f63905ba3d334c53051647026cbca71b9215cf258517f3cdff8e1d12044bc6378d4b2ddbb23cbabb0591be2adc7c7c473e41b899072ee32127eab7d6f1a2eefdfbb2d092a6a2b81d3aae3e3c3d2260e2683315ea07293e1ce8ab4bb360374f01f392538affe9ca832e7e4067ee4424ad1d5053fdb729cd36c0ba20adc2f68cd4522a1cdb601cef5e19226c7248b5d3d0641624a8f72311ca2855e8a59c50fa937ac00135c4196b2c2ef59278f5fefcad5d51d734172c7b604e42d29963bebeb2ca5bdfb50a33e285a5ae0f8b774ee244d050fde47720f144443f7d45e437f0d4212eb7b1394958751b58ef685ec8e2dff1fa012cf722fa34bcc25d5499ae7258f5c7ec7c0dff1b1a50e8e82febdec28e48ea7f8c27204c3bf3b415f0bf8cb341face21b5bc3d54d5e86591fa11ed723a53d9e724d677f3a6cfe94c3aaf034994eea7e853debf6b4561a7006fa9022a2382596c92a728e9de5198d93aafc45c56e8b9c5cbd6a11ad820fd39e12db76e1e24091fab1726bff96da79547f0141000ef2dd4c32831f78b513b51e1e850fc5e73ab9bf2372527c945aee40bb4f8d0aeb9c639fc12b2e3e96cb46b9b223ca0f64be6b200072e9cda8fa76dfe24fcfc26a5903c9800d34b073cb6211dff8a009cd72987f30728d1f065b1bf912d76196236c47f85d5e1ab5c09770dfe51b0c5cc8b9f49579476ec974c19f581404ffe13a94e37a4e9364f54deab5651174d96a58f747a84972ca921ee2b03dee647da2fc307f037716c85cd002b11e183742e2593a83880e65ecbb272a0771171d03cff9cea189db40ddfed630d30cc211823a9b09340cdf2b6bb0b6dc0814088a34d82d1f50738d6d82677ff08c6eca791d9cb06b51b8a4728a1173748eeeb695e9f9e3f215f4e14b4ee7e8b94016a0d2e5dcacf4610c141eb307875e124379bd64d5733827f614d96bac69c011bc5faf76a54ae46297536295d731970628409025d783125644a3067de9cec30568386a1b4cdd18ae2ba972fe831a0896f798af235d1199f9f8173210b91535cd897a783e08285dd664587286d36f2e76fae2ed9f505459a0058ffa521d3cd6c50f402eb06f38ca1933a8723848911ef38136bcba302eebbd6030784ec1e90cf4f3ebbb2429e48193ee457265899a30cfd28255dfae83a451db3ab3e18ce7746fd672ea420d470eb4756e676a1b6f9795c715204684677a6c3df332e7b8ad75ef86dfc0eecf5e44ffd8da1a1d3a4c5424ba197109f3996d7d3cd856d452bca702649a43f8a64063a65b68267cf9bc6804ca99cf1fbd28c5e7d60e737d2f9d22fee688b50211e6300bdf1a72126068cc0be0ee5868b3a043c07b7c4e519a7615d9f499bb631b2b3f5758637284fb6d5589b33d529388e17afa4ea999b831295c7c1c76b35d8966b746720272615bbf2bc41bd3f35ff47698e28f992db1e5b247f0b9101a5ecea9c8635eed729dcc10431afdf51feece5a74de804f8604e8d468d9904059e450590b1f4d3068219fdbb004bb485c7f4eb606a4249adec1c2ffc5694ed1218903c782d5044925420dc4451f1b8991263c236751a4adb46de6faa8b3a30862a4a281bb44c6c172ef9c44655040ba61e4d3860766868ec2a9e0e385025d06d2bcf9c554745a54472acfd0350f2562f4df660ff3b19c6122191b8f1439d10b363d980d8e279ed80f9b0cdcbdd387544dbf499bbf4390b44ea7ae57c69b466a0ced17647318c8f772ba552d55ef08e553f40d9d9c079d700de06f2bc5ffec321485e77a7ba8232e4248e248482a67fb12a324114a2ad11874f50c275b37c16206b6f5ee01f142ff72e780b3e5576759c1d3f87ed05bd14314cc478b0eb5e309252a396f3f37f07772f09e889228273a0a84f6b1e4ac9f975f5ef37f2e5657370d1c4a7732a242c64aedc8547df3a9f2c3dfefa7e8a42ee9f2f96911f9a7609fe19638dde538affd7282dca678f6a8898490b474759f6ffbdc4cd047da423ce3bd0006d1b220b4384ba4402762fa019c8987edee313f11fa2194f1abdc6aa1bfe2de18d0328c3abe41a03b3d03f821094664fae13866d8302313471fb4ed791dade5ad38e00cfa1948a853e8ddda641dd61ce1139e9296999e0103b26cbd659563df2011bda71e8733ec10241bdb6f63be9c4f6f7ae6a354f56a04d2396ecd2154f1ca66192d7a9e05d9c9c142694da4b45aa7e89d6b21026dd4ea323b6d9f2d30c21d4625cb192c496daf7e338a2e363ff8c1d1cb4ac108bdf9d2ed9d752c54cd281794cc88446957efe17ae39709156872824a2da9cf1419bcae29b3ac931d1de4f07229e0105c72f064c37395ac240308356811c5952234233654bfbbdc796bc099e0c53d790072c76b1d3f596b0b280e9a95191fce54b0ec55e05df8120fd6c459eade68221472f32d0e365169b432fd6caa87f9858ec2a2bf49607cdacd2258c6a7e00e4bf372925aedd51488a09a558d184b2a1230b9af6f85b70dc1d33af22191b884c5c413765ce5227d9a089df91a4b8e1e35f38224b6cb324e2193f1f474a8f023343d72328cb2810f21cb8da0433e35a6568c781593e94a2351ae7fa5e9f4c407105672e1dc21b6182297759c61d93ae92057b79a76b239004f71d23ff4b9eced5a2f720f4309dc64ca93b672a172036c0760ec2cb47184c466f7f2c1470784c74fa572c995b0d8859ce83b8fdc87b2756595321d3744e04e9da421026421bbdfd0ef7252cacc4a54bd7fadadb986df729911d7f9a37884aa24636d8c6c7c14faf8297227330d980f2e1d10dd3d2acc7bf999e8348f4d605f84fc2a1311a236c92d1e5645ba10cfd74374bd772fee30206d096de2e46782d918299ef8f9ce5d7784107295d74b5e7d5e1fc44ec23d1bec61100df6dd09942b1eebc10f597dc3026cf37293c898909c13a19e4384cd30cad810303e122e42313318df947c31b2c490b26b6ea1dd19a079fb49e0c592e0fc12f4ff6252d54e3877d1c9d7f32d7af85849726e06312c35b5113e1e192aa4a49435c64b5b063587949df05e2ecd2711c86e7282c4a34a573f98f8d98a42afe5b65b99551fcbc74adfd5c159eb0c427e2fc47271b410f7004639cdd84537403b8f13ab77ceb6408cf4559ac53e6479ba88f272a859eb3ed8956b3d9014ed2bf5b64f166691904c0daabab784d97e96e7446e35cf7b9a5d724b529cecbf83d10007e7afc80b411ada48095b495e0bb83e76497259aa67890522d0c93870841f2d8368b1073d01f424f48ea137bad9810d1d5272054e0a56029e1581ff8d6087c26a678837e19055eb770bc9f1b90567f024bc727caef707c9e432c73b12ba51fbbb6e3cf079fefc28fce27ffb35f2f35ffc7638da9cf041222050b002107e3603ec82904a0e8fbbbb03e4f60b827ddf570ee0228a7fdfbd4a650c951ea758c230a210b6aa2d93284450c91605c0ea180a244172c5dddd544d096478e07f331b7f67ce107ebf45223701d31634a5e7e973b9695ed35988efc6af2aefe7f0f71ba82667d64a2a406e157291552ce695f64d61b272f24256031168e68744972b09edd2b4582f9a8fe34d1f418dfa0781e9a6bac515c210c6d4b218cb187e2a3ceec1f2b29ac2a84232ee3b8732d6c416b246de787204a686ced76e83c4f242f92d97451642562ba2cd6a93e47391a11209e338b71a7a1cd86336ea42c4fef5ad63c7323ca4ec74166180c3076dd8cb2559a750e5724c09d3fac61562d55fbd00ffa9fd24d18ceb76837733b55fddd41c3e4fa41972087b38bff31f11cbb715c1f13f50c35c01abbf7ec135c97c08e971fe33e84272b56b20ea6a40a1b02306f8f084f78047fe5b07d9e1744f5aa90f77421965a57233ce8884246f0a42ae1ec253d170da8bd8f1eb592c1b4453240eaf099f064c23361f47263ddc8f0d819f45635015238d652a0704752c29b3bedb2cbba5a0d0720162d39b1e095fa6a60ad57f93864ca50723a24abf30dccc0942152cedd0b41b32bfa4cdbe2953984e6dc30a8d364fb6a65e1ae6c952237e5e676de6201a8472a7a52f9b99324d5fbacddf58fd2cebc0ebeb2f9dab86f892b88c791dcac6fd0b56034940f8ff9f643f8bea780d8258ccf741d7973198038ad5a7e422559fea7205cabbc89068e3ac0bc4cc7cf9807a4d80b78eb028bf25eb6c1dd07f6b349772312ba8540f89aba6f251f1bde673d25600b14da8be6f4362b0f788037137cd72b710629ce3386b4436dac6df16be1d6d5029935b6d5133b79f45a9478676c972a834c091849fc54036f6ab315e6c9f00289666a61024ba9f81e01defe1cb6472041718f9cc13a3625a4b554a9f4466f248a56b4fbf54f88b3e0f23f4e21b9672990b7035e3bc21116bdddc324cf629e01546e02c7b039bf3ea6b9ac723adcf2646ae589d8ff963af8455e8d8185fe277678a14152d971f043f720d0504fca5725c268e615a37d0cee900e25e9a70d02da2a87b8fe88cf45d1c0df5494dcd7a722e8786504f7a4281ff6701d4e3f808abfd0d5a4915dabb3eaf00d1742d78256da0b13fecdc547c92b937d45099e1f5f63c200fd82941c8d1b8ebb29754ddab0440a17d3730e2e4259e6036cb1462e74491ff93fb23b91eebdf7fad3102516b722bf178079d1c07128fbcc6f8d0e1b1c0f664ccc652ca594aee359835ecfa3372a4fbbcde9ff7275aa55159887de5bc6e9a42830b10cf548f2d628ad1d7958647802919402e3bbed3f66ffb100363c0d2be4c7f18932188134a7b5709d65f6572e2927c18317608bd3e99d888c84e85d1113fe68b52b9b7664330a61ce4ef2b726b22d58e5bcb40ef6fdb8e083054a2c58a7e6ddac0be3917ddc21ec25a6d9a7240b6787255e1776e33c36f13e3e317e99d9f56b18731384c46a52117c5dd767209f911ed86a8114757958257cab7115e433d3d554983482805cde0c7348cd2724aa08d6feae5752e66d127077406a90704538b84a08c2baae814540880cefd3385fb90de01eceeee26c0e08605d6a5a5ed4da29e965e2975736e36e47897b472369024230f7a451151b75d18a24527fd4f3ebdf548da5b786b1ffcde1c6e574275285514ada71e001145dc37e927281b493079a4fb2d2c1783e1bd5274b1c772ffec223670fc2bbd574e08ec5bcad680125bf7ee6f0bba4c06518076cc96bd713fd9706c8f2d3c025a4d70f3c063e5f81dcfbb686dc10068303d84817ae0cd72c159ee0b559f6f1ace6e6300b52872395e08756629fb8bb5bcb2961cc4b63015d26d3da4235b3abf00245e250155597e1c43e4c23b544b17b28b7cf4dbe12972ded07c132a29218b3448aa4a5e582cbc72553cbdaa41bf57bc4e8782be60515cd2e0376f4a27196a0e85e048971fa437a61df96166b811c54de7f5b08132a572cde5b3dca9db0591f64689eb087280f0a204843c56ed011c9fd1821e85fb62725854c757a291aa2706e33ac587e6e5648b8d71bf08d53ab9b18e9d4cf1b4a6726498d43fe9b1f75b9359a6e07db5e498f7a05a79a86efc024434652f4e2c8472070188e73258d0df7c90af752836a5820d32e6bfed91ee15ed7846f9ff2f2172c1f78fe8e0b55c2e31ed310c95f30b2a606d150783895d5566a394afd2819272aa6091ed0899b65999a0592eadad5160e80da6bfbee643d13f31e7808e496522c7891db88bf6e4a8d5c4fd09cde676fbe17e18dba6cb433aa4989b2da6d61326459c85aecef2ca14659fd037bc95ae0ecc42b01fe0bb583aee4e93eb580d307287320f9d5b40f651885818527e2ffdfd5ee8cad647d897ca9a26d412783bd910f87494493530b621873a3d05e7f672af3d94ed93a3f0c410abf0045d5ffac712280bf1e5ddb9a1c97da46ca543a311cb28bcedc12424157b23ab3caf8d1750727b233b2d556beb5885f463e1af04cac29887f405a718732af4c8d0fd69d6cc14362e70aacfd3f567ecc51fa482d1042abda2ef2bae04cf81c3c4b196552d4072c5c7e238e2ad66a2509bb72b16ce124f991eecfca719f44042f65859c861bd7297b5cbae5cb4357d7e8aa0b607204cb8ef4f32ef439b7992a7c21286be72f2543e818425a2a411dd716452135945086eff106bb2bf0617490cdffa5c5eeed3720da199270ebfd9f3890ee2a82d6f952060b9471d93a82ab64f97ba961c95d36a9ed60430147142266d6ffb4c539bae032d73fa7b910cfc2b5bd4a807863a2272959d2059de45bb2df93039edcbdefe320b7e658029814f2e240bf160e8ce491506fca54aed06a1c14680844166a9f1684190b52f5ada6baffdef15894be60f148872e18102aedb58431bc4e71f12326204a66ad82f70efdf2ca6f39106d16772a0dc0f6c52e8c6b2fc62cfa1e234c59f3da3153cc58af93356ec9c67cc279c5aaccf3c9ae14003bb207d50a5db756ce9389a2ed37b593cc2ff6ade3454eddd72e8a033660b7758a7e7bd93f472a1b3955db1d1b86eeeaaa7719bb1b92e7d5272f097460ca800e12cdd07fad7ec41669334ee1a13ce48613542f715c9426882729e9dc3672eb7eae48f57719a4de30e34adf54607dd6d1028460ebe87e98a4572851ffceebaccb0866f76fcc6f59836dfe2de862c0d278ee71dd9c60e9ac5c5721600eadf59fa58ba890d6d6751c0097443fb18bb931f0c8939b8653252714272f5a76ab2416a30142589ba3072d332caa46541c5d296799db5575398f6a0cc725d084ec32cd4bd6ecedc2e0f3adf0b3e4e64001ef0ede62aa3b2c151f4b6583677b3bbad2807d6e09ef3274a149676da2bc0b87b4662f67e83c7e774ed251e185362a19e924b8aa924dbe10798e869a6ea6c3b38e0787011885ad27c58f52172affad3eee635fa1042cc450ca2b04d0193566009ab21c6fbf0e38d9203275a156446eba1182e6c150873fa3d653781f35ad44b6a62554239852e59e8bcb50d72d6333d04664a411a6cfff320076ef331dec94b1e32bac228864e75ff2024fc72481f8cfe3c2972d0989dc33e4e8fb3d17fe2d031d68911473e0dadcae323ab72ef93bcf8b8ce8824ff5829fb37c2039ff446edc66326aef78c1ef085249d836ef82654cc01934ac06b7d1b81ac9841f0e99783448afdb6ff6378b2c048e1ae08072b14daad712f5e2455e253d2a1f7280b35ade7be91c845a94ec36b4bd6dd7262d821cd7e1f5c939d6a2c0143e436efde9447bbef614c91f1c8c64294eb3f58d65d7d6e2719417a3fed5576750c8737b21742b078976de46235755505ffc372be9e18224805ab90f9b50ed5dfe8e8f14af91ecc4a8661f337cbb6d7845599543614e3d23bfd8fd16f355dd65cdec4920d5c7e850e3503ad739d7409ee6f1772955dc200fea6feb060fc048f2a0081b1e95b317b15917a5685c9701674630e725de27686c6a9d59290e7b58adca28bfa8ee42989f4a5e167a0140373f66caf1e6da42e4c9ce55cc81ff164f4c629543d03553188ebc64c2bd6e7300806ae3e7284bbf85f53b01b076a44f978f5df5ffe693cefb213c642d50037eca04f515c395400d1e7caf7c99fba5b6645fc87235e14757fb3f9c82405945788ea7022b0402ee5ab77b3655f54c6994ec72d401ecb349bb880f3f097cc8ad02d358359ad72b5bbdc0539832ac291cb82605efc6317dd87fa2b911ad00ce32128d665c53172ce2135e085fc2afda2da902095006d61febfe9ef52c821e392bc984a4c5c36720bed8a5f87717a8fa9119022865bfe133516130a51a51f3239c45d7db791d318030699306c31778a6848ef9b019002a24a29f230548ecb53d581df668f5f1172dbe140b7e47b0c80b7a03d46ff6a0492287c53ceec9b79ecb485289f88e0e814935e99e7ebf76be3f6e01e9aee5a1c4b4baaf740ebec13212236e358c919d112e14601fc736429e4b198b8e9567dbd64061460c5234b88feeb94c7e4c09a712c4975ecc6ff79ae9c1d278186ff297a1adf33b652c20c568c069824a0e01e8272aa0f9c87f0385485c374df929b940771474369fe9ed268a5a127131e2985581eece84ce94645b708030a859f52526b73248099cd65bbcd257dae92d5e4a72037d107978d0e26b7ec34d9a374bf92ec9d9efabb97d0b0bc46443da18d37b937723709ebc5439f3e88f94019cd25d440e9447f363cf38012b9df1582b198a72b549e1fa6dd99a5ec053c35e6d889e39b6eccb9d6955885f02614aca004c1d89f72dfe3b44d252c5c4b7efe19f97d1f77d45c845a19b79a53818ffa8082d0ae0a720a32f8b7a003115fb9fe738ea66bfb87d14606feb22e77ae03f7e2ddef922d2dd9b176125515cf1c50cdfe7920aac0d8910a22c02af08065b53351028369cc32986ce77887d79792e0823b5bf8c21380d2f6005e7f99c3291d0b22d4d2623c4be7ae42a19f8f111782184776c1f3d9ccca01f12b6638616acf79ec2d6883a04087445a77d1afd48e97cb6782b48818eb6b93ec5f3835cdd7643e6f0c8f67494849d3fb1783fc90c767d75a2e0fecb24aa48d0cd4c94d5543f79b11af85f0e316b829fb35e935dfebfbb9da72efcb8b90fb284d1e656e367ab58b1d0a316650721af3699a90596236b0b75af56e2a511bf0368cf25e799d7ed4bdb06fb6dc0704004357246b3d54b1448fc643b25859232621fb6c2ae32a3edccba57dc1bb673c394dcfe5f2f0a6f720583d433bc437ff096b527cb5a1aa7cb1bac3e70151b672195c73f17b5cf1306e100a66e85121b91851f547354bd420d3bdf511133a3472c80448599252633ac811b69536782c09c46138eb7f72d8dc20ac6d61d6fb0c722387605c3aa9cb71cc3cb8c2b97f3bbb35421fd880365a59ad11e3816cdda21efaeee1877ac544ad23ddb431c5d93833476e245d0658baff4d9c4d38656dd12d9f750996b1c2f4cbf9fecb914bf27a45c97b5786f9431005cf80be10fb4db101d4605057101c1d4268be25fcb4f495de85f89a81d8fa972d2bd4b497c5a69572639a03813c6e3594fd18840740b72b043569f4d6182a8023d7699f9744a76572d3c1a5e8c417b42177056e4556cdad7e6ad62af72c1caf0bf3208ac4f4af35723ed136807e307e05a6409a31beac75bd7857cecb0d3f19b99e24c13e246dd672242ea2129bc0edd343d1343a1c8cff4088be861db0a89e0ff1a558e66d63427262953f636b013675d5915943c559a442374f57943694157368a3dc85ba9ffc383e04ee895357fad4539a4b744ff0ad54fa19b1f6d0d1aa8502c8e0c37936c82ee6ceb8e68686b37c01e9d4ecebcb6f0fefcc39227a733d4a49ae2b79fd275072ebba1532f3fb2b37809746a66b24fb3f8bb16ebef2fd4e596fa130571e8a1072eef487efcd6a61cd50412de83d29713d366aea18004b809bb06b63888ac5561ce75bf14f3eaecb1e5870eb4a2379c1e54aa09538c08e2ee66feaffee80b3df72a2130355ed09210ce15ec24ce1504fda8fcb7bba11107a9edefd25ba6b7fab6af711eef564ed48213e6f702d4594862b90d8b06edeaee2356323caca27781f720082efa2537fae2feaa741535953e482a3134ca6d930039382369cb225ef3c72348028724de66dfb008c7d14a33dc71bca88a71720a022c41ec3a25b5c1ca872fb195928cc61f579f3b91a134da446c9207363d245f3ceb4910bdb115bf012722a15aebc96ca4a301c4afbb613a97781f8624997bf7610d09ae89bc55fc915728fe9460403b0c284b348875607209e1a908888cefecf63be30be270317561812a1ad443d174c80679438fd401d394fc963a8236422b76db66739da6bf87521359e9753553d104888a56365d9b50dbd1408ce654a3b120e4d04ffd091728980726a9c954b0408493f7078e2b12006cf956445991d5bf409ed745027747eefad72eb74d7ebbf26f717e80c28007532c88a64de288044d5ec4c5356334b8281ba72b6911b3a4b98d4583ad9f2fd03d4b9abb2bfaeccc4c07c6bfb1994462312193cbc604abf92a6f256a2ff42861aa9da063b02954be70e2ac9f42196d354a43b727d49f0fedd82efe7cab71ef70ea2649f1ea8d97a216ae7a4d3f87d35e88eda72c3e079525263a9b9d9e53e113cf8fe63ce95a2052c55a355d67210801c50fb72a3fede85f3f78f742726470880df481527da39ae003f249b719236caa065900dd85370e9fe3f48e3f7ccd4d0def6a8b78e0b4adebf497f1d90989068f9d0ed72388cefb3caced6940cf7f08f1d064e271e7a3b449f868c1b32be06db3396ce729cae3da84546530b5f64c39dba6d035bbe2ee69aea7895a358714c22c526e372238f7cd978653daef0c72a8b8e59e2dcb89f90c8b775cd41db3a4a786680c8727bac0e3382061011514c9ed511baea5a58713737b12cdc5b177da384a7449c72e2718771d4e574e3fe332a0a8a30ae20e9db9ef1fc76fc24808675f1d4874a4eaaa8459622d1fdef0ccdfbaf39b5fbaad2c11b677547b144f3555d0c35d772534790180522ea75858aa61599d208fe26a38f0fccde4bdca00210fd2befbafa72ff6f7aed4426325ede6cd00cdbca770c9797cc1f72a14a7106e4fd1193d7b172d9f68ea65dc89da4446a7a4b38c6d79466b4b07a6cf0054da690fa45028cc4729e9c96bafb092f628fb6d1aaaebd1f32040106aded48824d6323999b57edbb727309b437e33994ca2c95ce771daa1d649be7b34dd185235e3a48f9163983197235936645e8277fbf99faf4d87e515183ba8f4ed3fd87493cd5e6340b86c1da72badc5923b5de18c8f327d5c29eec53db75e4a00ed0c074df0505b5d89246a872bb94f349e6010c2a57e6b1ca201c2594bc0a9f1b0a40b51c217bfa5f12c33872a2efbfb5d95b8da3aaf47871725b2bfc25b528ebd6dfd42eafac1fe60af96a72c2266c9ebe1a7458f2f2fef97c2f19fe3df9a3e36c0279e4c1ce3561bbeec56ece252a3ba0a3559e541b0ed6ec350307f2672329e82d34bf5996952f8bf0355032ff56f7ddee45e30370093da75c8dae46f6ebf3a5ff6cbddebe29e1a5040a2c14aef95c92a42ea6a925f6f322a41d566c28df7eb52d50275b94e4fc7698a32a73a33ea5c1631c18d13e72ba81bd8bb08f8459ec3edb70dc8cf9d7230849a24387cb9a591503b4ac3e7503acf45a83bfe0795640080590fb07e640130a20293526074677269439c48cbc78afb1c7f85a78a1c8ce87da0a021d713d85c7c42b728b5e32c737910adb6bef20e7840289add168712807afdb4a0f603d2bdd545e62d1a2d9c25ec12e7a0d44f24923996d8772ac3c1edf278b84a8aa8ba55737897206327b155f8b97f0c37391b73f3bf507be703f47eb0918f89e4b722a048afe720b4f2b353a7003a2950f8ad66df4e29e51258283227e6d58f98bd221624635080a6ef47caf326ddb069d9bab0f0789880e42c79fcf07ab182bda0336934b8949b09fde14b6936868b89d16a82bea1d073cadbcb1dfcf461162873da12a1137722f63ac6acd8c04e6ed39eb7907ecba120bd3514c7a378b08d47dd0eb2d3ec972d92946fb7977e74e65e92272d48ad1591a0aae5d0a168ae5cafd133f603fe625676b06d43e75fe72eefa5f99c18612aad529a066fbdb7d2d4ba150d962b56572d42da559b605aaa5beb5a7968fac0fa524f5afbb6da4e2d08196b8016d1a9e724b63664e07fd62dcd93f0158cfe2b666a8bca145d9b9a4a6600ba2e7c3202f72b85470a2fedc19952778a4c704ca2012996786a89458b2e155d4c778c6f1ed7279a24fa7f9e1a4f51347c05f0a6ea28dc97dea63b25708b9ab42442326a3943e4575a84f29440cc81a25d05ca3852c14bf1f208a69eb02cc6b2b539e26ca81720a96e7b39d85a940f417a37f5487f9bc079eece6d967965312e19edb95058d7246d1422cff78025205658e6ef53acda0e53df8e3092932809e7c6d33bd479f72ddd7a720ef979ff24495930a53b599683d535481ab32ca29527ccc7ffa6cbc4bcf03ffe89e9486ada282c09d20c1d837ae416aa1de2152598c3a9e73c972f47276d9e6758d409ce16d2a6dad60c6653428c0eee2f19cda7b9fb16f66bff2004c20c2cabfe327214a76c10c279da7b6732377da61e1064abeb4cb038b7cf122721a6e2b0d83d09b69fa08f35b45e7bf1cc778fe306421e9c2010f4351b1dba417bcc3e11eac32b9c7e73c94adab029186387f93c6532262100038e71e501f1872896d26ef30615dace672bc0a9500a50644730e36b7fa503354930130caebdc72822f51e3ad1b7ddb9e318aa3552aa229e29b98cc42ddde8d95999257ab17bf64ba5a90cbb755a996381707db97e4ba54d960b2d0c74d774e289e4b1b220c207220a447724e6133d6e0f805713827e60f0c3341c7d623c13b8f6e84c0f0076672d640b723d942164381d74af90dd73a3c0b6c66f2688130b83710d0bbe92f101d38ed081df444157a122397ca53c8c94c01595af6f605d1c6102aa56056cecb05a1484a3747af127f8d389b5fb14fab3d489658415dd49ed517ab9fa854ad774a87cc92edeadd7af61c5e5afdfba54aec6d46f48286a8aae9bfe4a68c44e9e7023d70f0808e8045bf8c835a4efdf1874a26cdd0fb0ecaec8ff0887df7fa476b30d526be20d64e630bb41781aff48ef7771af178f82ec40e5d797f0975d760683d8e2a9dd27fdc2bb23f8a40600cf7ce9b908d94e81580bf17654be2021735357243d2b1246763a0da0d8d33304a4f3458532399ee7ef8ba7f0692f7cbf4f4fa0df2f7abd5921efd08d71296c9c00e181b12707063e339392ec541b1d358fd314b07acb29a00e5cf8ee8db6a9fb981dd27b1cc835ec60d90f7d65595237d3f3f723150b39b6b74cfebb052acbb83951fa6ca1f1bd36a9a07b89c4c9afa143b874650e82d29dc76f9dabc7ca42a28a4c024c7df864ae6170ecb020b646b02160272afc15dee799835c954295b8e0e9e2167c40782573151d1189be2796b9337676646bc03a3c98c6803f317b6bbc21630582aa3cc78ecfcdbee133359bb04f35372d6fa2400a9d3d2eac00036bf320503a1f73180b8c4a629ab2643280b072bca7263ae404181f4fd59c8155233bb4f0f0da321d39ea9359702a5031084a80efe0967997c031b2043551b7c6ca3e11060bf55071b4599125831b8abcd60897d0a728751aa342c5162a6d7be96dd46254878533999ea55b56a55079f84fef333d472667dc1e8435e0fe6eead6e83d663c38d2f9e391fc820f9a59a78bf0cca82c0725466486d93c908a22daed582593b0eff1854016cb1aa9f307087b57ef3c93c72714e7525403d1c405c34bd3747eeef936df0f975905cf3d9c25b44e752355772ff0371b3de272f105f192638543ded844393da4f4dc71b105707590c8579676386f858ad815db80b945b0a5d3d9b5ba2b09c344326b6523fc5fd6408d09a44448d2b035fad1367fd9e5b44c9b5e1348c3fb429b25e4fb1304f0d50ea30e8db0f46ccb4c71599b99ec375598281ed9ab6335ba36addca021c9186be6e74731c72d470b06b6aa983a28073c4b62fb46ca8d83e859177351823e73be2b15d536f36ec55fa54877bd622e1d55d5580f919ba81f008760dd0ca1c39f19d670097d372020363f95b5f2c469c999769f42d06596456994bbe920e77e4f0394736825a17487d27d2a18fb6b11075bb78653d07c7f034b95724a842bb0f4d433628b2fe72781e569c411efb8b2f181cff8ed70f1315bd05f7ef298301c06f8b6860fe4c72cf9acb626088ee47f08ac0a38040b2937374c22a30b02e251abc665b9f52b97295867931b05eb45858e297d41b229775b7ea199ca757b58436bc57111a94700d4e7e608acd89986df33d30cc36851b53ded90d6ee91fb12c1710b1c171c94a5f7f985ebd3f52caa1098cb36fb3bb681fef433f5f4042ceb5f8b7e75ba44ace1857dff159cb622f52485fe68a39592ea7fe936ebec5ac48ca5d1d3ae839396b72bfa76a9a22ce4a1849d34f6576ff81460fdd31a299541de05ca2a5f7476ef72d3edb4f30a3f77c643cc5ad5d5c95e3025eb0829be5b7859056dba4b51a509a6d77746596562dbd5ff997550ccd90e9b8ddd8957e9b118d563b4d6ce64ad3c555f9de5f9319b50752c66d535d68278fcd1ddeb212ebd6665396372157393c0624c92ad2005a2c3a19b9e4922967c3050b64fdd813f5b9cad4ab13a75da9637472db86ac7bd1b964a24a53f12fda88e6d0da6a47363ca1ad1bd0c56aa559ca8d0499d1732561c24dfb619d036af2a182a7a967bb2f7982d7e89c0adb59dc7152729afef2cc39b2b5e034fa36c584092b67590d189b2abdb66da18b140353e37e550b54c454006b9d79675cc0084765466e2ef0cb2bdfb8ac08150ba85e2ed7114f913878bf3d92c39c39ae477d8a095346636e7f098f42eedb5f4018d7886d5872ac2d95d85840aad65501cac92939e3ae2a47ffed4ff4f2b16e401514ddde92720da6b7d948016e2be37d1d351aab7c2878ed0f1e9a35965250780180182887717c2a1a8f68688d32c3561d279b23d2872c1418d17223d3ca7be55592c32d9426149f7e5cf135ee570db235c3a7268b400cb55861333df671b35c36a70e08e818b81de8b919790b974c30922f8af2fe08155b69b2fe5cc359003928d7043da40bc4154256d18b883d1b45aa1cb92be1e4a0039f387851f47c028e2c3faa2af25bfcdecc850499a8096fea1b5372c424087ff9c29b4ef04c9c8fd0da4caa72e472b607ea12c275f17e5cc2e4687b2d8175b7c681a006dc142f627dabe307d8046f58b26d80e3a8731db00bf8b50241eae4e00779b099313464cb9de2e2af7fc9720577208bf77978df1f59d22baaf11b177ffb09907a6741957128c10a6fff7f2ba8247da2fe1a4fca7b797c78e2cd0c1bc268efdcf0921a80d219c8573f38b2727cc5cc6e0cb066f323a469d4526ea77c0fe85258cd99c01faebe698c944c8b7213d47d128af72b3e66a5bb9b25db39aaf70c1006d52d504363555b43afea0a5cb4caa58443bc0335ca442bc18b40983a4cacf61acd825a0a3aa2c35bb12d374f699828e5fa418b082aa526ed2eac0b077d8037b8be3d85d461213af9810af6724c5f6eddacb15c85b782c0841eee84ca8c430a849dad173b6a36bfd115bef57219266e7c99f3f864b1b67edf955f68c709f9cb23dded2499c7ec6e35cc6a1728cb7270b430b4277945c695d09742ae2c3f84993738ee324696cd3d7758e868729ef9e67910bac3977ea5642d43c4659b3216e5b80bdfa8bddb41e13f2e4feb0c10b3e1c81c7a3b6f28c9bf277f5444abdcf1b3ba67f8ff9a9773b09d93c0957222016f16ad6c6b198435b54d8706b048d9c7ce7de243ed127e243edcc0ad681623ba6a3f3214601aeab7e1575e5a0bfa7ddb00783ae18ee971f1729e937579720dec80c1024a23fc0aa9ae061bb27b140396402ca9bdcb5abb5aae8bcdf57b72ea0eeae370d335a884a99592befd429c226223793818629116d71fad49981a678457013eed21b43be7cf5c4d8b11c4c0879fd8d52b939d4c34277285d2998c724fc560151d11783602facca2a54ab663011fde7407c0aa963faf8e17b45dee7214b90488161bfaa2082302e43b95db72d28dded3aeb7b1175ceb986c739fa47279e530e415bec555ad6ce4a981180cc4adbbb91ed87ab21f64a981e90eb38c7223a11e783b8afc9a2cfa7e39eb405a35db44465cdac0cfb2958fcf6d49e01772a93cb9776950d4ceb337f3f8202f8d51e8f00062446f726d88f35261523e75727945edf9e155ec4411b4987052d72b571edb908a725162d77a7278754339516367f6ccd1f2b7985000f66eac39ee922b7c396fe4de46203ebbff2b807d044c570e3e966ce5bddddacc5cb6df5e75689232559f691986144a659b6af6eed7bd7211d6081e8ce009377286b2b6f21ef49d0110a067b33fdfecb6afa2567ab8191c6aef955c7511b80a8c75c378ebd5686a784b2c373844b037cf6eadada3a7ed728fd8aba28f9742e136fae5e0a525b9358894d20b62c3283740354ae4edc132724f721fc3a0bbf93be8d06e6d620fb0adfd4968d7badcd6768f6b745a122a1d49368bd5b7babd1501f6f9267f47a85a0d2dcff98b852f8c83ded798babd63a072a83ec78935b4f7ef8dcab1280b11493d0f000600283ceacf5d003de2d95772729be703202a937c08c6fc534a7b4b5b54127d5df481843d7fd4663aa379201d723fce16ca66def607deed82effe9c8214e47abb09e7faeee85e795ee0636434724051dc1a56a50763d90dfd4a3fe5d11ace4eb1d803b34222733d36b01e524e726ba8074f35d06459430b453e1d30a1d8e496a1b95de845555f74b742584a2d140e8eb4090d9343df87e08d636229b892a4127ba19f6e578efc92449e1349f872f38a6fc2b780da5030b625760d8f0a9629531ceddfad532325eeeb6049a01c72cda55d633e41e6cfacf971496113bd4129727ed26f93d711862ed4ec7b5d6363f3c3b1ca1c0fdc72f18ac6103458e1ec9b71c856630e0d05456a4114e2cd4d29ee2ddf01be48d6da63deb5b842de704d3fb2ad6c2d9530395d94083ad32d8218f6afecccfa628925d2fa4e34ac8c46caa72024d720d929afef0f7166ecafa17205adccec2c9cf18a7bb9bb96b889ac1e6e714142b163507788668a354a2bb872052c886e19a12c0b3cce8c3ad91f17db6ac834b496befdd7a283d605e45fd21bc0a2be1874ef181931e45c1f069d1e2f7342aa8d7ee7b48a6ce22f3790257e21448915d2b3e9242872c061b533b74645ce02eca7e433751344416d2990af6672506df4f743f5e47eafbef0fa33c05b7f4c7182ebb39e4b2a987b39bbb2a5d9255218d53b12ec2f01a710b7b4636183eabb6ad799ea61d0f17fd156816dff1672478ded50a2b534636d92a4432e383635f0576e9a638a510e28d1518844b655725aaccbf7ea47ca3e37c252acad844657fb04f5f65ecc014f348841cebc91a93df52287171df9f924d6f828ed7a66d887e36f02c53971ca77a038cbb58c0d57724f3f7bc73de033ac2b087b655e5b8d440f88bf472954a101d61bce44bc1cc25417edaf9ebee8a56d494ab51ac82cc1ed43f40c979df6ee14a60b4e59feef054c4fd04ebb879c161ae0d5132b8c4301e323d012804570be9305d2dd242cb8c5724a546b5c3bcac43adc511b219e8929f3d641d10715c12e48b50ff3301bba2e726a254cedb824d99227aebec4dc2631764c1a9bbccfb799bd47116ce166f4a757bbfd98be8a57216c64d042de00665e187178b9e08bfb7ab2ffa3bd8c40231a1eb3c47f321e0e7e64f14eaffdd850ea84e57a24ade56b9a513f8ca3fcfa8f8b721583c7a67f3ff4c716f478a5dd54c181a08cf1d329b2e35a53673894a762804a9417e179e6fdf617f0abc24437036f62c235144acd051bf7dde1907a97d95f72d93286818f864ac089cba4c4e1abd95ca763d5ff23991fc1a09dfca65a864064d24c69efe83e09804718dc1dd787b3ec2b269ec2b26321b50af7415aa2a62e72e981bf531c24f11a856f70f35028988aeaf66f7afa3acf099401b0d5f9de291db93a9a804e15b4f0d4fd426c77b03e13a1a976b9112f9c62809cdcbf3890df4f39979403ab78338b220762c253fcf95d795a402f8e7437c64f9d2dc433efb2546fea22cb2f7240ad40a7f2220d6aec3c1e1e2695d83b41699af4ba06a6106e722e43d8e8ab7aeba55aecc341afc534c52fe59f127529b712b576bf15d1252472b05f03372ab03afb6ae17f201e43f09ff1b9fd0b57940f0cdf98007173a00f0c0af0c6396ce6982ed45aaa5885772f51b3e54a06ed69399ba0a2df376b82667238fe3a404b95991e150dcdae7daac397b02355ccf085a311f6f0f048cea9f86f3a817fdd5c53aca5b89b386a142aaec7656c52a40b792a9d43bb2b21a58bbc64ea4e092b9b9321d89b78d015e259db2d9666f8d9c3fe2fe4235d821df7acc67299a6451f4b2c708ad3ae233322c8c43656389a6c51935aee48c2186f1c5a1772860a99dcc46f2fa0bb63193a8bc921a235a719492df6eea3a39bb1f739ed931e57d6792c9c69b0e6b3532db6972bf6245d4f8aef992804c78baed35d94793172a274c938c9c2b477eece1cea4ed0accadd8f079cc1abdf8cc7744a1ab4042a72ed64fd46d467401c7a283d59931cc5f73c89e91c704581e4e7f294217a48c9729394a485ae7b64f62db3c4125eb2160226233cd495893bf8206e5e81367ee372e7a6636bb9006059f9bf3d268336293ffd9b221da7ca56971ca63692a65e8072c8ff3adb2438a0c4d2bd2c55b6f58ebfa8b79cc17f78bf599dff14ea4bbfe43a5b391815cc70440d35399cd657b2863489f534e9f20c1a1122ded7bdb2b48d212af6d789aaf67546af1989093272b4dd839c12a28423ae84c64582c70fab8d72d077dd63ee1ed8aaf6017e53974f9d9a2995953790ae444beef5cc46cdc79e72b9eb4e8fa25489251d7ede8c710e7f52de5ce304bae14c48de5f90cc38a7081d9a795adfa2e66b7a25e381cf895062012b93d7d034464ed9057fc93b41ca5c72bc3fefc11749190db023fd31d1252e760bbf8451f727039bb8a22635471c3372fc80f2a7b97f498f14bf4ffe9e6bf5d5061a44660120ca1dd30c8457c9568d1f7fefbca1a9b0a1f0851250500c460b8c4eb49735402efd712c46f650b3659337a8954bea57efd7bf1548c8aea5026e58892ab9a04e4aeada84eaacbce257ce041ed72d7a7268ce8fa166db268f4747f16032cf73daf5d5b325a2eae5e15f337269846d8c8db3089697d5dc391eb8028b2ad04f054af978a79a079743c501c2726876574dfa713ecd7ba975bcafc1fb7bcd318b4642b4d8470b1105a36378483562befdc7d11291686f2c3a9618048c487b0940e2d8fba18573d4a66a3fa08f2eda118d506c09baa2b44799935968dd4fb37ce756a70fd942261fdca2e56dc95310a1130c5543b43cc1dc7decc6c18595881c524dacd8d14d48d29a652fa5d526f55b8923df4aa315af62315c351e849bd4342e0292cc3dd3b42c30131d211768fe732b10271ae38f3ba7f4160401570c923d711f47bc6192ddd6dcd85dc70e399f414ffe287dd771bdc62dee719e6b764377fd936757884ba8a45f8e631a3772b006183d76276e12e491ff1f5c13039aa932b33e99b7f5630f218b1e41d09072d568dbf1f3a76101514db1418b01e96cd3958b34e4c048ea1b6db84b39bccb72cc69e64383bd10b80ad4b1dce56059215b0bfb038e7a08c22f2ddcac993906727e1e141558ceb991bdc0b5c308cf433752603f538446d90a17cf7f6b83549a728c13eb45dc16af2df3878d12c6737334799637fa4aab51ac03d44aa998588e72f2513f235224ab159c0befa7a6c741f2f0fdee6f9c59ca83ac1e20b2a0b0fa727c712a241ebbfad20d43ab92e873369249d930abcc001d7e11ef7d8535ec66727c30218b3c90c3b4fc4269671a24a749786d088d51a1d4f0d82726eaeffcf0727ab70285a02fecf250ce4e4194667f81838e86316a5ddf01abf21a780384d972d2c45b12854e39034f5e99331750f8269472530d5cb2bbf6639c9d55baa48672bb1509e9bea7cdfe42e166773d041f25da68eed28e0509f9df821fa812e40472d12b74418b91ebf4d9ead3dde9347e766af9a3b9a744a48cc41068b31ba4a0268f09427abdac080bca0ce2dc59193f0a6cc23bef590e3b8aa63d25bf961a930447c419f85bb25046337c4b28e942680b9f71f4b59f0175bbfaa1559c063b3972f6dc9de717b0ba34d05a49561424cfb5c8bbb412b54e36b68bdc7875113e8f72fd48bba44c54f2928b173c38afe32e5ca2c3111b9dfc6d618a6158b1fb61997200d24c1ff3728266f618c79afaad0c593346a1f7ebbc2c0091c229068c46f83c82fdf22b0626a7110b900308d49bc8cbc8e889ba8cc04628b4803b424e80251a74f8f1b936056a0848c8f7b6cfe4e06433940154d8a7ae9501d1c6420415d03b5109557ae1e92fd2b6055857340fa9bb3046a616ebc9bd11609c194b8643610f95e87466054be05cb11eb4a26ac81a060552cb2d1077b525dc85e5046a807132b00c963747fec8a13799c38e62dc257b7477f03d3222e08ec061017d1e0c57076d1f9d638512f454c558c6d8c409751f974473d6fdc1fde19bf4a530c2b80c64a11bab8d9277f320499eda22f55f7a141bc79bbfc8095a0a69b101344c6e250026e83b5ce0552cebfc61b2871728f71f1560a30076342f4e1302cd59c12949720992786d4922903a7acc571fdb26f43726e91d5657246a4deaca8d0a7e88f472f8f5ee8d75dc0eb40f24d39a67da73a0f221d136b6069e2a449aa0432d8c4372b41b1377d01c66b7324a432d38d1f8f90505ed4129549feffb3cb460ce61327223aa644d2178d1b8ab2f83face46dc14659452b73ccd5660596b55e863234e723625ad82c251f27203e5dbcbd143dc6493e86857c567fade5fd14e076bec5372b61018678103659dadf3b17fbecf11d20923c4535ded1f42810c4531676d78488befbc2fa82211b319621e5c8c42af349b129d33b9bbb925fe7a85abf9b3b53414d0a26491b6cbf91ca80ef13ed33351fb170a142375332198e923984e2f6972c8afd72f2bd5b5a9cc129bde501bf5733d6620fd3637731f5e6159635f7b7472263913d4d4fa0dd6ce6fb234133b7c6a45c71d2bd865603d410e53d50332a00cb6a000bd5136608eb2ed8d5663c1a7c3ac3a3b57b05728b3d938f0c72e22b64b738781b92180991ae8530c1e43c77775f29a4b90a94ae58469c5768845b86c23e10b5c70a42c04934f0072099b8f81d2984e9a3e2f41e7bd59cefccda81db743cfb326b68321eebbe0343ede7ebc8ef4c63232b076cb59a6ed87a134c5128772e7e7e1a21b74831854f35a77477387559124f24b2e73f449d691049974d1f6725e2de8fcad1cf216c7836b5ec03419af33bb9f168e7b502aa70954e585c8ea62255795b8bc7c804e61201c7f3ac2cf7db68ed823de1b5b02468329eeba76753f131563dae60456d2caf8b8207a59f70c479b505d1b5f56aedae36040430edd727c2567baf67808ceaad5850859e1a18dfe0a81014093d4122fa12fb7f4d42f2d83642e4e86db36b1acc8be966e7a15c6cd7694307d6b8d9b215cfef14c313b720554d16cec54bea97c7226b44dbb04ccab18b72bc650b3df73208e3556ba023c693904fecbd9fd770b9592e1fae77ce2448cde31ff104ef382282ac2e9359a3c5ffe2d99ac332eb2a575bf935b2d36f0f8a51f83b80caeb42092337596b115720bc3f068f260ba04bd04dc5287c43d93a8fe7b1d2aa3a492779a1bc85cbb9645fec2d950f6e363759fccf848c9c145042c9e84de87af8208767c41e795868c725d642dab8d87b5170bdece342f4d9dc86028dc8a0f57ba5196313e87432ad23eab4f9267ac7910e426a247ffec788c1137f2507918da63530f4c64247161f2728cdec8b9b926012b5defcfad5037bab3a643cacd89fc68c9323b2a0acdee1c22db52d4505cf01614dc4692195d00c0c819cf58f7e954fb1724ebb693c16d1472eaac4c6ebc5625b0e8bf51a07203737df301f678d0431f3510aa07cbb1c9ca3d92cdf0d6b080268939ad57972b13a11d2514a8436e48139b48bb64e89637e172cf35182e7cd1a2c9710749b13a932444321ec9c18fbfd095f92fce88ffe57f721792db610b7837858cbab49f407edc71f5e4bd8bdc8b8083444b8fdf5070556e6bd01c35bcada039de410e4270e7e3f2e52a945a9e664b0a9d305753c8eab7727388105fea1784e9b4ac78a64d7d5de2cd2c00a0728cb74750d816f57c58bd6f343b8e25a61aca014d0cfba6676a2b36a3058407c71cde23240bc96e6a3a0d290f602948f5de54c2afc3d7b419b38473cb9a8784b572e49c6b6474d5b1950472c0464eb653829250c8517f474d1dba060ab88f623bf232ebc6c4ef0c3288d30453cc30a10e52e59937703b39606ff53b1d8f87ae257c79a7abcd0bebf3a1a909069eefbf7de2e30a53276a3f7e48bf01a7c4624d8e00a5623eafb8c9c88a8d72db2c7071f5458bfe5ece2660d89ad0d541b9836fe54403664cd15b5d534c1d72d910bd6b45c98a57cd50f7db49b36375511526fcc523d7b10d94c43139e52f720fcf62c12115c89491a2878389a89c3f9060298daebe761bb1109eb144cc7272ce002b264c62782629201594ac36b2d55443a545f2b99fb1595b2334bb11f572528c44c6b37b63df766ffe934e93d918e96f970f13fffb12ad84bcd4e867f3023ded679e00517850b5ed931362b4e34a574e5a283f1e728318fee71bc0adb750c5dce9a25e325eb693d6faa632b9161343232d5a8cb2deb573b1da1c949d26722a52cedf051ff1712b9852bb15f7a7b2207ec1cc746aeecee028156cd3f9c62ff89bb56b8d11b8922c8ed15d1a23eb58fe11312984395b3464eb829a32c8757298db050f51e958a3eb3b0da79bfaf8dc543c843086dce8ae7f83ecb881dee0723bf16297606f0e2e1d6ba99177cd9409b05598134a119e9a28260ca248e6001aed4e77497eff29b51769b34f70a88516e89670f74b08eacef7e3f9a0c51dc67241950ff1df8b0b234022678ad22fcbfc78914984071f67ca8e608d4e98dc516e84696b4615d37fbd480f6f807abc836f5be1e8470e2adb13031add627ff29672b5b006be315db78b8315d3f05909753c2cfe059cd3829fe24c88cdef8f55a0721a0cf3801ace2881b7cdf986837e009382c0b29eff2b33fea685aea9b111627251fa74cd69931eae2519755f0caa31bb459acbcce5aedc757937309bc007dc72be2f07c780b6a940627e17345b3071e20682670bb5a391d5e37d1115c41a8272b7a145efc7340cd3d26bfa039a009db1c992165edeb3da06168d720e386cba1641b64154dee8490a51feda7f876e666f76a9e75a894fdeea79e63137610a2243dd7b6c6711e937b2f97fc70940f77ea081cf63e1a9b4f6e8fad78187c77d2c31d5bcdde6740c6cf593e4bd76a97b3686804b8bf057bc35f7f4426eae003ea423be1815db531e328c3c0e086f93c33d57ec1ed2275b8a42b88c10596d9fdbe972ed127675aea89143cb6447cc00edc2404cdd23f2d91f7a0c1ccb7a7fb649b27210843a9a031e4ec8198ca50206df55f1d6679f7908c55f9e28d7c175d1006461286404d37385353f3e7bbf64d3fc4f78a3e844b02d04251ae73bc516067f4745307a6f62ea0aacd2c80de3fd23857e4168eb4532a42f93160ebf20e643afec7272f8dac7e46ef8f59108cf9a6e99a6a3ce5cf03f4bb1c423c6cc5ba4d83e243e84ce45e0f35a352eb9f58eac8b927197cb2d0325ef9e5979e51eb42439e3381904ef63e6713d7bcd5735a33ebd2cc35a627b7987c8e598ebbd116e621b7b51727ca779fc32b1b7ac4ba573afdf42576bc56869ff2add5fd421c2fb388dd7e17238e64ea3aa5b43e018b41e27e08a310c9f29a6a099fc67f84fe441413e9a0b375cb010aa036647e292d71caf8f461d35be6397d9f1e431e5d87ab925ee3b174a61043e8e3eeaf634e282864a2e135854018eb902d4e303e6773583b2f33bb825bfd5a14515967514f3ba8d5d3ed3630dd0befffc5a6b0eaee81d5686a6129772f6629d749da395cf866c9b4916840395312df7d8e63edb54743f90fb3bbbd27233c99f601a829fb5f680622fa4bc815a5c27e00ca6bacd0d682cbfa440856d09ad891b62e330f967984110660494f90a2bd08a3bd63a9d350787599aa4bee2726eeaf508de6ab47b62463166f8035caac0ee3586ac59c79bb5c47a56be42a972d31e95acd88cfe35f7135ee9dfc59a20a537dc3eaee4b359d1f8285a2cf4c00d257da00eebbfa023b34ac94888a3a0885124870fba6070e8a9b0465fb680d465e9fca8dc170ac3e47a07dee59c8b4e5def6beb53c9805bd2745f9f4b1af1186e83bbf00e23a33dcd247ba79182d4ecf4b6ad8327d1c51f86b439542a6813157226fa5a0ad9bfd7b37a151bd1927cc442ee4f6d0539164975077444873abe010eab7441779fd6bd2edf1925cc09db88b62ab26562d68a6e1840b384c3bb90356fa2993b451001bd0d2515978c76265bc520ca2e21e55a8b14f3c6d4cc0ec3241ac9ece6633bf20273008868d74d61af1ca1984e01dbd0bcfbdaed9cc00cdd6a72fcda19b54b833ccdd61c16e8d6b519fb52dc6d5b96c308e0908255b4f0242b6afa5d3491442291e4b6b333711db9b2d32ca9779fd4ff5eb504a38379f9accc539f9a888c98fe9662446571553245c5f7a5375d280e595c45949ddd62352e0a72ac8f015ee275abff38291d73c17b79a168dc3fc288bd71217672d8f9cbcff46c9128c8f8351b721351e654758c5937388ed2e8c86e5a59cd0c105bb9bdcd3f11d077c43394c52ac3c3d796a7cf8ca593554b4749acc921477100cc427b0fc56dda5d04f4f7c2006aaea440fc840d99ae8a7cc22df97880e1ac3858939fe67a65d7afa64f022e841102754eb33cae50bc02a432f0fb06622b3fa67e52a85dec5035770a9df64db8a04b35dab896c5a8d9c613470ca4744b0610819b1de4cff823d1ddd6599002bc31d750baef5dc874f9570861507789745e9f6f0d910a4f3772850cc65d4c0358b6277ea9a6b33d2a7fdb6d7883e59297d4df9e19c74af7f872ff5c563b02b6b5d5879ba4d1d2e8ae0604945699ed9457149818f308b8cf1872781ed21536dd9de8b3d39539bc2b41542aa01ccf6a16fe21804a026503fa1441264ff827c6057f756798f69148954a8ab307944c93819a7806cbd834e3895250b3eb892fc38d8b5865e6376500f47418241c3749bd8ff8990cb26c0fc30bf16d5a6727d0a0ee1c8702fb8edc0bb78ec3ee0fd3c003882b245a6e108f66ba371d16bcff3ea7b2450ada96c3a7b25ed2ade980a35807a3477f517767cccdf66f4101efcc6f092d7f0f56f549897c6da8172ee75916fb5944fa49250c6e1dd59d304f8eee659fa17d565a1672eaa19eb8d8cdca3fbd1deb6909c269afc5d0973f7046f781d26bbeb8cc71cd3ca7115143650b3778680e741e72f2f65c505e2cf0722ab4baae70e94539974128bd33604d03514e1ebabe02fcb0e515aba2e98be92b21bb05554fc357ed7613167e9e46f0f43c7bbc668ddff58ac5a7adf2ef355f72f7d37c706db90ff8b6ce18cd768aa6bdc4335382f2fa699f83af64323e4cf572ec665174e501187b8a253edf4ac799d1246f96bb08f20c84b80d9eb93fa610721f509c1db15415846a60cf6124630833f5270b1fbdde2e40739c938aaf11cf7267bc5aa1db56e52784d7c2606a8cd4234d8815718646dd910bf05e454956c61261492c6181efb1d3f856a1d23de675904227373011a463cf25fdfe22b2bf8772e2dd85587c456035e5ac724ba206e27e21af01debb75f753cff96aacd042dd72cc4abf7ddaf2ec2454892991b9e24db8d327fb5eba1a97a3abe8055c7863aa6cf923f6c654552e57b8701519e0e841a21c9cd0bd55d54b9c2cd6b0c7c17ba9729f7b0c02347821867a247401cf1ea203eb5af9446a6c75d1dcdfaa57096401163fbadf76d4712c7adebc8bf328b32b23bf48fe1ecb11134ff8cd055171796d2b03d6ae3a8f3e43727d6a16f27b4dab7bfed71b23b01e0e280c46eabfcb68577283c38e8c19675112dcadfebc5bfcc232ff3d3840cc3bf20531be1fd6e4ef6072be0f28242ac1223f805d1b344b47a7aec0d8bfa0129f7c7847f85c8fcda21b6e75e308ea29f42eabff186026538b25bbd27895792bc3928b68c74d2366e940156bc91312dfbd2b4965409e26d5c678a55bfedbdb247c37f5f7497e6a385d0d726f3486704a3b178f40c44533f4b0454ca3024586af9b671e3279e20cc456f1729823a1284042c6b2be02027a3294975b8f3420cce35488f98e597e4543a69872697a38e524a88fe58568f68a82cc2d6fd9b8ae86d1f3c5444eb4faecbab73b729b1f91358db22642c4299f978bdcc3ff088a7b95acc5dff715df9e17014c867230575cfd9c27b64b4a3843a85296c8eb318510abc5be240551e1b317d75155720ffcd0321a180c061feb90e811d3ce28cf13431d71bf0a9ff90a712ba696a672665b8662c3f47cc9d2a0480051ae27481bab771e5495c2cf5bfdc1fc5ab14072fdaa5a7ef9b2993028d9ffb0edfd05ab6ce1f3faa622d6041e79e1c1ee1e0e72688e419dcc84206d1376440d62272b10d37bc85684da247a005ff62040f15f00fd1f32ea5c0dc927231f6a87f4eb516e798c94b0052a8246586731efa41213720768c05c04d45f74bce1e60e8df4105626acb2f747f480972c8fbed22750230902af5ce310133bb28d369a3539d005e9e04530070527b5a1b640c403e7c03372c64b9d29bf47eb7c5a90647f7ffcab989ac766f072b8ed00779f015e83a8a37261d0e7155dd30cdbe3fd9eea79c8331e1241d8e20d9af57c372683dedfe64a1af89b144bb11636fe3c491d1898efdb06fa9aa28f583ce4539a2f4dc33723df7231d61f79bb6da6881979b25d9f55ad4cf6eb111840461aaf2a943af412ed7e7200edc2e56db63297a7eda607158337d1b5576d165ee868449130d3d14f53537276efea23274adf6b95d19e0120cc7ffddeb9fcbe1c2a1c4b5660cd7513f440584da32a14d76977a1a2b21b88749ae69f9822bc281f0d0857c78d5c213922bc72596ea5ea132af723e956b7bfc9ba588ac02ce455c0c5c653b78904fa6236d372b26427da9d7ba4a92dcf02431ee2acab04f6309378b201bf95babefa985a917219ea9b6786c4960c1f6abc7892be66615bbf1a77d2c58f0620641372ccbc6f72099d3646a6d2a93a1429c64a01eb59e838015044abc8c26baacffae2a465b272d01351508b9ca07d390160ef9eec4a84ae8a8f6c79d18e640c78435be811997279d192acd5e23b6009c5c122e2a76c08ea209ddbbc37fb4371f4811fb5147672c4786e071ccc8fdcfab59b242a4c01282d07a279c113b112a8efbe10ef88687276c622251fefc426a333b86a19c33633f8c5e9598d2e9b559b1706ee9a644872e2363f5f5af1ecee4faccab53a55e0deef706c692666a75c2833cc3de70a5e440c054e0879d434217a0663618c2e18a723fcfd2c833b9a43f3fbc01c25b498727168b159602f8a8afae4df3b642c957ca50c2bd475a26d1562e16a60b6cc8172ae1e10b3dfe7b90d2a9a0e85b2888387706fd757d3898c0829c9ee50049750724076afcf80e5887e7f64c9718454b994d8e3931d9c3fdf627701e4219a34a27221d9142dfc4b7ed47b4e238f26fd58868f439e487176c68ede33a13d5f0bf8086beca5894d7d0237abf6ad60c9912f31e9d6918a3ae5485b22a9b3ad048aff7270965cb63dfcc8b7ec7373f861b9117aefc95f5198bcca29ddc27149a859d7726be8fb6ddb6c31efb917618a2a05e460d86ac5bba9bf6e1212ccfb8121b9b70d2e94b8794727ce733af98b77f641890c786f3150900f71d4f073047ecba81144f5b84d9a6b2d333d1dc2851588697ab6f4db51a6986dc79932e59e1cc45dad6d988594da10cf7d4e96329b537fa6f5708e5ff08c46c3fb6e856568f8de05957235cda9a6cb9a62f5df94926285943e59fe484e8905db3d9d5cf8820f3694577285358d2d541b9e72d7e307ea93925721ef4c538c1e4539fbb1b9ce41cb4d3f6755122190b40cedd737ccdc2e6f6f3384f61d145fa0a6d88bf39fa6015133cc09476d7b6134c47e07277a7d20a0e856d5ec46ad5b5e1585c86abc231c85b5f65b627a78f70c65a5500b3a6ab9e7fd096898d26ad45da4fe6cc083c9fba404557219d3fa622f93e99c22c906514951def72cbe0c8602d0defca5849105b6853d0b7ce8fb14d4a5dfc41f92a258137e53dda346cfae08edee2fce0d7eb6e78fd42523f93959e7e2ee5d008ff8bace6229b5318fa427fa1343a4d7f2cedefb6a0d72e97c76e63e6d58299ab6e15ab86aa20cbce76718042d584318d68a194bcff3729b73a8cefdf0f49d3709eceeae9584093cd25c44658067483c41224a881bd3723ba4f782a60448c05a7a984672ca905faec528e8cb1a79aefce8d37cbda17e72a35a420682fa958e1c8fd6140558c99ad831061343d860390f1a85e46ac6652d9ebce5bb70b17dcbf18bbeb2f89357a613643a37822739861a625fbaed20d53373dcc0cb5825c49a317390290135401f250f3bb458319b7913ffbdb541d574726be18d60679b1b86742ad3850dbc7f09c7cebb267ebb957f83d6f24203d47b728a82d5c60f4a4cf8efa705579c6ed940207ebb7205464675197a8e7612e5255c926ce0ddac2b18156c6524adeeec47bebcc92f9bafbb31c205f5f4a9dd2f5d0f0203bfca6f66906e5bb57a6aac893a20e71769b6d285b17f74e85de3f5f2b97285b6ac18b75e0465a4cf95bd633372a4795f05b222725c3f73acd6f756ef9a247cbd699f0625af94d808d912c3e1694e2989c487502837fbbe95c4f159fbb87292987a52bd74f8f30570fc4e78581f7bfb5a6271d66f804cecf2e72f694edf3a70c188207e7239421b99aa75bea08e9423c1550a223a44e1512828f28146c47259bebcd5f7db3cfb43a4133bf0f3fb58f87c035af512f5320b3b65597b04bf616ba327bbf828c12028a99f7deda6d997072882de94b4e4fb532cd16825ea7a72b320814382e0256a718b883be6684f762e786c33f5ae992549928b01c75f967224fb0540fe0d4e405bb7f8bda4326f2eb3caf45caceb30ea9329920abffe3872f369ef331da34b0d444de1668c24f51ccfe7ab0f82898cca2cdd959a13d1d547e190a43c3e7df331e44b32b4ea7ce19193ccb86baa6e75ee748582205b923972e77574f2fd95ca0a6d2e74548a4dd080d9b4605f32f731e8857c0012c2aba8722de293cb881b055f958c45d416a7c19efc9dbe0453ffa32e7877c716890e2c723627f247339e6630de4f3c2b17ffd49d0159dd9eaae666ab0c8564f02d0b9072b8789146f6d2aed0314554d5f3a01092cb1426044289406719bf91214856b547d8cdbe270399b628ce4ea52aac3d84236ce5627a1014f48fef06a33e6d000e720eb364a820d71f74ea3bbfe4f5d30cf1bf96ee7067709e44329094ea409112080df5b07dd921f267b40a81408abf50a20f80284a22e58034b43c05731a8ae472592f4ccd9f4a6d41014237ce8a1d6bb46257b298c12365fafacea5a652e3a572c067a6178959703383f69452d449a6626014d8e7c21c7755d4635649a456415311e92f27192cdc453e05e0de1f45acf083b10323924ac487d3b5c47763706d28763fef6dc2942b4def3c109d14f35bbe07d96b6b03d78ed661b605fe4d7997053bca4362db1c183287e32fdbdfe52f09c99869b6b36ada5c355b8c39a3925c72a2ed8446b6e6de24766298d6b66c64027d3992b7ccb44c9827a34df6f1c99472a708bec20e7e89186e56b6504220e9742fd6d62da620e07d3beb8e7b9d1fc072cfa9f1119b51528c45032bdb533a3538dcbb2949b963fd74c2d80ec998da716072c4fc8aae9ebbe34b553bf1ffe61255be9ebbea9435c0b42573afcc88bbb5721da36fc143fcb161120e2b28ef5e007b85eb2c23703367fdfef932cd32033a722163afb457c583dd49c35bc3d5f654d93066026b87eb8aad6f85355773e64f72f3d5d778396b1e7e4fc7f32e92386f26b707be4df0c312e89df74695f5e24053c8f1b18d13d138c2f458300b1a72f2d7deae1964b5846f10c5cdd879e84f7d723a9fa4ee715c52371739c01f010e878417cb339b0796330b2514aacc0a1f3f72dec4344e02eae16d8cc64b0eaf7724a396510b0c76fc90b6ffd694abc9f3006039e32a96a0712531324c12e8ea8e68e408eae60b62bba54a36c696d8bca9ce72f55df128c0324bea813128d556607e42c4923a5468df0ff1d19ffa3eb081b16a40d96aa24ed34d9438ffacefce7213dcaeef28498f6aeef7e9edbc90a025cf72b8a8e5c60786d838e49a6d97791cab317b4180ddb92b39e3a76da137f5a10201311a232f44abffec357854df79f694691f05cc3e3df8ab786adb5dd0c199ed72cedc0122eb5a56a98d20b43254edf36e3f01fa14428d9563f10b7ec374728b72d0397be417fd3237735cdd540ef8865cec7a5d79256597933a3d2d72fb110764cf2c76f247c8b9fe1edc88eef1a8e7a7b9b90bcfdc43c5819c0e9b8ab1da5a72e3f353f24a6b8d1ca9e48341b00f52ffba959be664c2be30ae224bc84937e172bdcf87639f92338ece5e7c7a43abc2a9df0ca00b44ccbd8811901d441b041172400178da251bbc284917e51136bce96712e5068a0cb73b34726941de78fa2c7230e71e27253a004c3739a112f527fd9d33685e5e51555fa480baab8a992b8d720c4e5c03d3cb9e6e9d4350be3b6855e9371263a7c60436b295b5af86cca4e96aaa0985a48155e5b762eb52a29988fcd7175a3118367b5deb5112d94dd017fa729928c0f1e95a4425a13abb78a9180d4509844e9ce3fe920941665934fc441d725f4f060fec1868a1a02c838ef129e7381af37e95c9abdfac60bafc05787bbe0f1d51057b95d963073c7c39558903273c80bba3d8ab1021a1a0f9132411645c72f9d2e0e34fa1490f25cb9eb7c06af7c8d86ba1bf0ca85ebb44a459df7ca6d0724e7597bf7058bbb979dff4ba502283a1d55537415cbe193a1bba156fd0c1e6729fb0a5064dc16f8cff47d7d961474d31217e6623c9575c3c7bf69053a658fa72a31c21f9c6a0b619f0e353eb00ed7d96fc1aa2ca7086aff62240284fb3bfd4722619ed0c4b441b6d8083a906f144cf492c464e08dd34d83b7583e085d2fb0c6f87152c795438b09277fff1d923d03c95a349cab27dcf55b1e4915a4bf26827721671225d3cbce54480099e4f5e99ca8de217bbaeb5c1570eff4d9b6dcf3c3872a44edc6aefe73a02b43cf495d46d7ac3585e449dabdd137accbf56f8277ec67211667167b668662fa15452fdb4d0fd1450614e29996a85405808a8ca8c4e3a725b377d6cc5f866136fe0508b615bd22651aafc12a6bfb8fade8e63a7b6a2f021d80b298f0d75d4a4472026d662c9dbde09ef82d6bc38984a3ec1b4c6d62e975e74701e5377a5e407aa1b9a4793a20b31319b5086e7e3fd30e6876068bf23b26826cf464f62a72f34aa9c3d1c7e708ce6807518dcd69e5c5427c133a51693630e00370e8ecb1b5e6461b84f3b532393b680958de02b91336853e1f1bf990ea11a4ebe8270a18cea6c177ebf0bf0acb149cf46aaa1180201cc5a4fcf9862205e65b786f222c425f0f2f5f13f2e7b72d122695f7dd4fd49e3fea065b5367b27ad05bb47cde8b5238d3954c64358a59c7ecb2ff669ab2c88e3836e00c19a8fcd9e7292abe15c8afcd7bb93fa02bfb528342c98c9e99226ccd9cf6fd5c338f3d84f728182335f12c81e399b574011203029223f766576a12f945e2acd15c4d398a6723864212c177423690399c9848b9b5a35874026b759217d7e6c2272e3c32eac4f7d0d530e6313d44796fa1610f30c2a740aa4709e29ef6219d121dffba3969a720f9880b02c5e8ed52c5291140ea59b94798040c75be1dacc98b2f644de24e0723baadc97b6fc84e383ca1297f5b8d8cef4cb983349989dfc97c66ffd200e0372fc9492f5a5f60b6ea3812dd0f433e23926f2f71cb74e3024fdf9f4af0283987245b210790d2d601ce6266d39c1012179cebc88a125dcb2d2ff9dcbb54ce903729e158a22e44e232f3e41505133b5a5a7d8199947c27070fdc69342dfc2b0b908262662dc312076823a75e8d2c608153ac2a53774325799d30eaea58f5b8ee8726f64a34c34051dcc80c839964bce9185dcf1d2055e7f19931748aec3fa422f725d622f5025a870f38c199517dbd641fbbcdbb50532a83734993231dbaed7b91dfabc60510af6b1bf4d95929135aa8d87c3f64d3a302260b131c49dae42b309095d495be80a39faa66fd850dc669ebedc793b7102337e65dddac2f9e931c76c721f1633b6d00050ada3df4fc638d4b9639291537517705fc8bb2a226277ba2272eaf05844a58652126c2733e58ca70af21e201f27ae922e207519aee26ba53758dbf04dcf352f407120e7720517cd0f9a745107c766a85364444bb2a4e26f96726cbc416eb37f0b4d44b5c4850a8a700ef243f7ff684a96ca3514c2555bf389724a0f5faa91492a5e335905a202837d048a98abd7aa6aab74d375657168ca0472aa95cd5e7636ba4a5ab21eb77fd667f86762839e44f6f1fd7adfe942fbb6e01d1b35af8819b3f038f066854e989d147a525dba1617664b061678976c4960c17251b45ef7ad0af0475f0e8d4fc8f1ff3c657d93f52a805ff0a1dcf2828c6a6372e02b48cec62842dea896e90c51a87d05fc9032f71c8ca0253cea1a0341b52c578c89c2b360ef5efc75bfa17299b5062500857be93ddfe1852bd6e5cf032174724533c8d8bd4152f0145fabfbfb64b7942af6426fd74143f66150018b39582572f2023d00975e700042d857831851fccd465ac499d317ff07f4eafd2e24f4f03e07540e5aba20257aa563854a3a64b3d364b7f5046c5c9f1fe3d6c3c57551195999b8b4e52b4f55ea21d46ddbdf2db8b5ffa97395965b280b4cdd081012683272de7595a1e0d59b80893b1840980fc9f620cb041ef6d2d7c1709b23f37dd3d8723a02ae519a6313c388a90caa79cd2b73fa9869c06f41a70149b940d7eb108f7299f563f09391d593d716e16ce32d495b58ff3bf09b5ecb54632356b4aa9c29729db2877ba798af207166fd3697b11505287fd7f895f5b392dd3906add85e9c72980f79068745267c523a3e6acbe8db14eb0fab806be597960cc9377aecfa5f7211dcbb469f1dfa40cc593e0babf1a4f386c4f0adc1baeb13aae0f270ae18725f75cc2a48e5a8b944bdb62c15ef06fe55d995d50296998b3daa6ef47064b1a272d25acab46c1d02467dbfb4003e76b83068f8965a48439813a378f7230582c7729e0812c339adf988f8218a1ff7257f7d0bd33fb32652deb2be4a7f89dc66c972bd4b9c97e6e7bf822bfd78cc3b1efb83c9dd770849a91c3b62396486e45dd972082f29d81f66d86e49adadb32e4a9a8a99ee3ddfc1ac311b7def385ce8328a72b713333366689d794880763ee10190943b26a3bb5ffff938896f30069afb98728010f9764b4c9dd67a093969bfec29cae50c5eb622bb1d925795969f76ef552478849b1084f8539d18b711234e62d4075a696940dbb287edc184300fad619205cda42024fe98e2f3c0e9c65115ab044bb60db98a551a4b1e643357dccf0b9372e151f7fc777babfe905337015251a8492ceb1694fc93a4d391684fb0c5a5fd72b320014455f72b3fa248c7001b2d1dd40df52af92a560a8ff8d766bdb8605372c1e378cd6784e87b6e600172e4e89926d8b182778ae428d42ab23ecd50f6d230576bb28758235a770402cf164a7b25be59ac82a5db9c3b45959133354a37ae72148d36eb8f070c11e24c56c6b1d9ba06e6c861dc4339b78d5dd6b41035e7312044fd1769fc9f6f203c2e76c670f9a1a92850bdfb3d7a982c9623d6012321327255e1433a2b1510e2ac3a0733dfa83354eed4b7c8ffdd2f332eb41877d336b7644be117ede1e34140e33ff2e13074b1eaf92d4a05155da32b80f9a1f44ff0a4726f6d80702af143c58c73961bfae556c8750b0d8e3069651c95afae5071c41947a91d08783c565a72fcc77c78d87a00371977ee5c03c56ff71c2835b3bec28a030a9753232ac19cd8d61aa6a487b08e45b15857ca9691f7a99737dc0903aafc250059152b5a9134222b3df1124fcaf7e708ac7d9519f826a373b328c293cb13726c12370234ed3b52acb96638faa7c47ca0e1b2cc40cad1e5f00caa957407e872ae2ecee46a4466ed664f7501a0da497893f65f5d296066bd711874e4d1fa882be0cb5498711989bf7e2ff57baa1fd9fa096e7bfe889e6192e541d4c0584505133c7b5cfc27ec6b2f361ae9fdc919bef780d9613b02276b6bc57d8d15726c39728ae1b18d57fd5dccba3021d1b368b60b4595e965f8887f6fe2cf5778e484697292cd96b3aa5e8f6fef3072b4b41f9222df36a564567dfbc615f19576cf6f16723d6ce4f2bb91f7e1b3cc193e075f1bdc1a462f07105881531e4798ca0c2072726e0a601e730e796160481f9ae1bdfd76bf54a5ffdaadaaac39e68966065fc7728503ca35e57f1da7b3eaccb5c1e6b299764c3e6994e05ecb955d4dfc2d36027210cd01652a8b6d4bfa2ea2fdf42a28301ec8fec25a6ad134740ee6c260cf4072b06a3ee9e49008d811e873a151da32e6c089cd9d0256132acd0a64989f6ec1722dff977d171af87d3259bd071e0f4ad451e274ee3e183fc1d1ad9383296a4000b37c5df421ac78cff1d6c6d672b237d63309a771af7b47c422fdf3fdba97961de3a6ffdc3779082dbd61a8c9dfdd85dde0815f3534a53ad2101f89a73f3af1726786e1b5ecc3f964ea49cecf6901cd799c4ced121db24fe39059d6b02ce90b72216a37b15d7a36ea366b05b0fb860ec70ec41b5b9a9e25584aa2ac7a2dc8cd021b3031ac6b5959dc589ff0f39d0050d186c4f60450596f1311dd2ffd1a8ba015a57ab6b23cc728d17414db3c92d5f0fa221365f405a74e95614affc7576d86037c7d99bc789f73dd1eab160e117bf11ab05a5ef41532eeeebadc0385f73fe85fcc552aafa6125a428db02498919d537b2f057cc403e5dccf071130b51ea6814466581b6aaee50aa31619dae489e18921a14bda2ac2811c66844a0e11f1b9447218ba3485aa0cf77f982db31298785f558f058796b0d99f4cc5e2c9c5ed71d97269642bfb0120b0dd75af73f162775b7a06cbd2d1748e81d73c06d8a1031e9f233de9cc032bb5238b4fccfd6b0f8aa1205cb54e3c2c6feffe9f155fb318239872be7db1f7b8326eaff6ae1442e9e6c720d5067c2468001bc922bec6ce8100b50966cf7967dfebb4e6d5a42a954f866090efb7cf05bb9505fd12ae267379753572c89da36595f2a8fb998cc3c9200e14d6c436782f31e2252e67410d06ea7957182ed0bb3af11161393a1e23755478628daa8ce61185d1990d31014150eb5938434ffba3be411ed64bea46a6be7e5af7418410044c0cb05b80eebc2c9c1c4a780a08348c48de3025f1e760cc2275edbe53541e2ae9f97421be4245238beb6eb84dde08b57fbbd4c64fc840db885fc7c8980d63bf9f36f84ab0ac5e090aaa6a6c726b21c695df629da83519285e028927b21855fc74f7555363fe04c5e50991e159faf48cfd0593e2e2653250865baa86453462e906928e69452b2d5e6db13d561fa6130018e727fb1c134de16eb61c6fd64fe9a57b4bda9156e024aa3e2af9305697b31a41f0622eb33a72df2730670b0d3bf31d82d796bf8819c8a81e579947685c1f50d93b993917b4fcda938d445f5355454a9d9a496d63448fb87712043a72e76f940dde5f8d1637a4f317f209753df27a4e41aeb2a409619e437f3b11ab72643951826f313017249c6db1f31f9069c61ed755951379e3da0cf8573b670072edbb8347389d905b1212d09fad14ed12d0b44d7d02d978098e54860bcedbb06cb7aed718a03eb83e6732883478ad5a9cda936b2ada2184fa8c08ed7be774fd724be74533f6cba8f440f3107f868309c6576bc313d0e0a2db81893633d17a6f7296e6b89ec1a143b9e08f11eb8d6922c6ff503d9d50a07a3f588e62f215a45443de582d599427f0f63300296d1c7cc079e6f8d27abe119e317702f3e121b7ea72b91957d41db94d09dbbc36750e7c82c3f0539750e35776313c0e30b646ef72654588b1a63d2351de5e7c1f8c1bdb179f45e0bc8355e2aff65e4ad612c0c38e49fe66a6f596b41c1fc6e9d34d03419011d2289564ff8f9a063bd226580e76a272681dcf941a30d731353998cc3a971d644375fccfafe0a09742d8953513fb044006ce7d1206cb3abc6cad79bb8be310f99ba523abd8e812fb2f6281b8f071b565a50eade7e2de4ab8128f6933b5c8fdf0596a2a1fe9db3020cc82173b651cd07287caf3060b7db4c9c72af93e9940a73c7493ae6d2415f896658a4c9a3d62bb72ef5a8c036c4245ac030696d3ad03664873a76bec122b30d4f289a2a3eef57b07f3f15983dc0746eff1f47290d75a8256f9b14c460527fa3a9ca54850d80417729364035b02be30c7e4b40ebd20f63257f5266eb141bc46f3f638890091841172f28c3fcbf956e7ff725468f62acf1555d6c2226eac844496a6f1d542f1b6da727f2b2ef89b0e668e0f140d4828cb077b585f06fa555a3ee1663005503296bd6c91a97bb15f7d1c55098f6e993799d7dc080091d2db78a0583bc75860c1e4c25b3e2619d2a2f6f77ab0541914f8ed606c1888efd47caac1afad346e8f4ba6312676b2fd91c94d60ff4fa54406a587ce2b8f87730f89742c125e6c245afcbe7f724bea714e73cfa04c078659c97b0e3b62bb89a18e9a31d9ddf04870720757c469997e739a70fe8aa2e9d4a3f984400b7773ef8eee2e555d6e22105f4452dd73674c4b2c902f9889ba6583469e9fe8f0212bf07141818e79338916a72c9942c0235f7e0f46ec4f6b3fc00be74c423fdcafd7fdd07e7fa0c8de8cc1e2c43b5886729597a2def3d4cd173f5a9212f4d3c8525105cb4e86414b1ab5c52b1cdd607072b7be1887eab2166e73c7d613597dc24c02f88215e37dd8131bb4702f50b22841c5df741db09188a52429433febfad4f0b34da0d73f10212cfac3a97a3b77aa7289ecaea5ac3e5f76f2912cc1aa9462fd9d3cfd111af271586df93bb8ec6ad32eaf94793394d1e5c85da783b89a1c5cfd582b3beefe95454ed6e1bc8060d2cc5badead3fa20c6e80f2180af17aba8e29b752ff7b3bc723a79554cc638233ff872c031cab82f4dfe309dff51182836fdf1ffa1208fce2548488de895238787b55910b111234af6e62a62501a321ac35c9580504e1637ed46618c8d2e28e4d6f7726b0c6d91282a1a8d84bc459f25b3140022688543956c6768ad7ac1d799649705349f295c22c2753c6a63fc92d7a3ee0d9b6bc5f9a2edf8c40128ab2517ca46722632672d118aa05daf6f243deac865c457614e78dfaa1759ef18f741c62e86725971341c5bd481183d34306f0d88f641510da9f52cc18aa6fb5ed6d84aba44004cefd5a9dc116a379b861b46ed769e18cdd448a3e161a89235cf85b3f16747727027fb1b5ada4934667f231dc56a24e80c05b7db2d3d4fd00caf487db7e03b72cb02950ba9734aad70fd4f4296d420ea5647c2bc854441e1008a0d3a14e9267269e7dca34dd0df1ceb76cb972598544a91303f9cb80329da317df0e516615f2c93dc31915d458165565ad1b25351cbdc4f61990197812c7f203e7ec15b245a5fed661a2195aa59a15dd87816937eb1cc8600ab0b927ddcb9d477e7123fc40672cd4283444c23c300eda1b013cf61d80977340915d0e3002960a46fda695299021eed063827380efc928a849269d2a3292fe6d17fa50e71636ddf98d393c2a172ac33f59cb5ef1ce5f205c4e1fb1cfc15f9d07be29f3b8a40b113837af249ba56d51a7507aefd89aa7c0e51c519175bdf3121060998225e96ca8b8decb9cc925e6992df164fff299074324df757bf8211e537064b044455991fd790db9c034172f312bfbd5c91922ba63ec1499581a2d3d001538212aa1a8760e4a87789b75f72c0f23e5e152f3c43ede47bd314a5ca1d163ebaccee1164c9a4173d40db942029fa81dad3d4b7a0af9d34b6ebafbe7f9108068d252c0b1bd86d045969d38e7a35133db3f16a08b6c5c05e4c8d7a626b15d11a2ae9d718fd8397676d7258da6d721427ff6d562b628759d3d3059b7c03e09d0b3669a3f317a07fb38062f3b694724db4df3559c3e855a099abd6d8cbc3190120337dc20721526e4d0134a4c57d729885739f16f12f08e6b65f7b9d6da271da8d6cba1a92676b4efd6b958eca541165111fb296a898af41f1a151dad1c861b90bd6c697ecbf637296d27d2fa1566d9360bc3b72b7482feca251e7f940552701994e489eb553bebd2a19e9d022343a722f472f0f5ef6e9684d2168776d4d5d96bff60eda6ab77954271ee739ab7c1a1e0b8254380fb67641ff41867c080abe6dd44e02aee35232fa26c757fdd05f32e28ec57affc637fed223a449b42319a9c2e9c579a1beb85d60205511d83dcb725c7a2d8e85bdef9a1cbc99bc114a586a31b40fa774ec34749f4550d1afedd21d3968a4b9142fa3d26501d06927f7d09ca643754d03fe7549451b77b1237338729b12797ddff8a696570bec912b6aa4ef5385c2eba1ee450258172e54405aa972dffcf93f410bdcda807e7acf41923c162e397384b15b2106a9f37e610fac9b726e3d22a6948378c4de3f1ef77949bd35e5f578665143b2f37f6edcab0c8db67246cd3e0253108f2624ec9f374d87ade35466563ef721f43f7ad75a25c527d1728873ab115185f95855cb0e73a6e447d4829a7d4f43cd62aee51281d3657560725454922a6c8342b7a2e5408cf0f26f62439aae25c08ad5053726747a9dffe03a8cecba4adc6436d85dc60b41f4170bffd31e18bc998eb6a0baa681b75afe6472fbf1d050c651b47f85439ce431a1165e8647f6ea4e8524325e4ed95aac5f127220ba01c056400f452f1de872c2e83e11543bda1b8a148ceed52939a2862b3a72d4e3dfc00a70fd4a9bf9e631dfca30784ecfc2a11a91ed884f576cc57fcfa260e26ee9053c034c1ebb21fb96c7a504945605a46f169da423d8add3dcc85a827208ec73030bd9a035b606003464ea82557626cc97c2deee6cf6bb8fa63c60e31c05b36dc8232ff918f5a0d95d466fa04069dbb0fdf1eb08b639a6de0effc1f3722550626aa8a2936b71a8e48e5195432eba371ec1dc4d42efcdc17b3ea8b15172da23daeb42d44fd77e564dba42629c7f924bdb3e67dd4e61ce70c6cc92f52e4df7ac6308cfa9f5934e64120d79773e2ce9f5e5e3a8eebf763f18a3910c3e8d0742b8c544a7f39229122b1eea9795f85bf6e7241be7ef9c2c95c062e00b08ec72530d33c59e7f3d1cee90918de7d7c0c74c21b285a9ac4d9347ce12ce589ed872ad85db479c42eb2eae403e5dde0722b79a0a78236b3e662d02ea6e2bb04c555c933fff7cbab750ddcac490f3da14a42cb79b979ac454d59ffd698f87d3133f308a9809ff695566375e3f441f20a27a3f7af1b7186ca128fd649d7f32683c35729bd3984adcaa24be9b72f98dc2cd0c9c97230d8e034a0adcb9118a4ad9158b72bc902f113e868d9279ab18761dec59119c659573c5837fbf326fcc9531149f7262609acce8a1da1edce73646afac810ed7564e45f5093368f0e511e26201cc1b66fde8e82d2a725178e1669ffd6120abdd8956b41683a3d723feef0230b60d723832759d71459bb6335cf37779d145d88797017f6f5cd5036a0dbb21aafa107224830f2709df710a0629136422013973ed7ade23afdb0e867ac04254400aaf72c925a2c423c4043a41f4fcfc644b4099f1f0226927e9d8715b369b30f16d2572a40550f34fadc40a269a503effe26d57bdcfd9c1dc04e6ff301f66f188eafb72ad00f0d2337e1a4ca8be8c666d32a888d2e9570c5585ad943bcd4f9c2e42b3725b018f27f1cc46df8866d0d3c1b45f25a527be394f00959e07d51f14b01f3c72a0f53c5f430aa5af36adfb931a4b1d4c7c1d83c5596eb20b1a0b6d6ff89ab472ce098d3f3ed09abdb610ed6ab8ae84609d242811bc16a0f7bfea9e41e73e8b0488a84feb81f99fa121d63d5a87ae8c7f8b8a63a22c1a2a76d97a58cdc06c8301f29696905c8c8d56231b6e07b9d3fea3c2d275c955f013a2d4f6e228b1963e579003c6a8d227b7e55f506bfe60ee63e9ce834b06aa6f9889f15e18d21c0abf6a6160e227e6cdcd39606c2fd1035fa7f0eb88e73f651d2841c2adf0c4e2dc03721170a46040f90ebc21fb689386e9ef739b235ed3287b1030de7af9862960a323fbba8156efa459684253fb3c1ccf37e6e69f1635907ec91df0f19ab63c51247241939a5df3f29591474e90b8b0e7d482935e8a174664e807387c3d141fd2bf7241746881db460e3d64b23cbe766e62a9bf484d957631504fd927964033da4872ea94e89e5f52a4a36eaf7aa617bd113392909dada1333ed9aa3647b8073e982890cbb91623892f2d9b6ce2f9540003b2e3b401cabfd93108e3185a31648d4072f391b9aac03e10eefaf584accf5c9a3138ae8d74da69d6eecb5bbb24f480dd722525b584d91351f6c8a4aade20da39d1e3c01ec6254ea705d2165e879c027a727de945b5c4b530b4c8e837ffe5dd5db995f613f22d96b680818f122ef2b4c9728544a2ceea78592c25827e6dee81caf28faf26755524531200a3fb01e765f17106fadd3403dac8a6e9662921c80941492d75c1d3dc71dd6001624795409ce14f0e27925a29e2a9efc033dd878cf0014869da7171b3619831b13cfbfc4c8f843bb59b4d982ebd8d2de28d4d7764264560d028ce082ef8e7a49f58b9ddcc3c27727674b59cbdc7b4e4795291b70a8785764b972d50076af9a614f581185c63ac07d015ede777509b5dfc01c94defd8f0f214cd3af193713b4aeaf83a67be36d9154a90d521a092339c87e189e2c6d1d934a6ac9826d3875da5bf72b4da15df652bd540e63d91311d18f717a02dc8e3eacb1a6b06076e89bc120f0298852f29c87259f6f930ae1441f9468d7738bb2af4f15916afff09ac489959cf60cc7a48097277ff5251b9547d85104efafe33e584edc4f5b0b7067dda89a456a7a4d455f4726240bf4517df0a3720a784f886bfc5c01eba2c5623d90d90626cdba3838c921945c9ae409f80dce2e4499b16dbe5d36d2576be53225bbead2a6cd11577070b2ea4674b7b737174c6df907674e4b634ed6da425baa17b1908d0bd8ddd53cbf27209de4ffe2cbb9d21eb817ed56f94ea75dd6fdbb7b0768195774e10c965edc5164ab2578679eb647eaaf9d6bf38506c7643a449296b5ed695b46524d1ec243947f7bcbbddc36a5e69f4e011018415f4d18ff128074e8d3761ad4634a0ff532172ef4df7fdc385021f922514a3c5fa0f73a9721630355ad0544fb02daa0628df72a7cd35d21ba6320dfb9d9bac8736128c40eea90dcca32af314065867625d30720109776d6b34c4595966513545f6ae39687252dd065cae0961abcda39b272b4f9bff85b0f980423cb183c87de45a7f24491996c0e446d8095afe035e3a66497235eadc903e6dedbf1a6a28fc74781b7002bed3b156a53cc797aee2e12d925e4caf0a14f95d8d0479a7902d128481842519552859ce98f74a48ded4abb8aea26e55871ea42f3bf50a427eece5507defdf8df76a1b207a1395957daf070f80f4706a791c5b363b7bac1bc520d16e03dade6f8c937c6a4d63e607b225552fcfdf0c2178cf71aa6ec98123ad6bf4d1250a2a2e0a927ea6075d024f49633b90dca954442a05fd53dc3db5daadb60dfd1e614e45e1bde1ea79e1db3d178da5b8e9557246c3dac9a8dfee0e2ebdbdc72c8a28c12ef4cc442390230a94ad11ba7e3fbe7238b6487015c68a84c996c9910d9a7583cc16da8c1eb5e5d2d6572f7db6bb5f72d6d2d85d4113f9ce8ec8850c43d9b28ec3cc6399cd943cee19d1f88904cc620471063ec6b1da020a5a6acc1603ddf50c306f162d2489e78f892eedd2e8fd5d721048f4c384443e3232a70bd24f64888e2a19cb0b614cb14b958a13dedfe2e65bde3760ee7a897ce4a9419dae75e917dacbe362d62f5d6ab897c7adb89d8ad172042ca37ef45ff36f3e7a4c141c83282910bc81122df0ae2f82417097eee2b472bf64f3cf054092e2bd5a71dea0c4c0ed471b3c8fa83f337ba6dd3b4bb3879434e2ed1b62adeb3291a53108d24e4d2fb22d8cb5e37549473b3ece41f4d7ceb5727855cb053384cafa2a7ef65de7ca7316fa0c0a8f8b57cd0ff758b8002df43157cca2d53220312a8ded5fa8465e5fea33a9813a36bd36ad93936f5fd293ac0c27ec069f99934af5bac6281a96ebaf72901eed8b6a08df972e75390eb399223a7280d3ac6c6b73a78f14c424c1fab87a06fcdc662a203f5238cb7fcb83a6c9416b15d3502aa17ea4bd4055cfcc9c91d11402419ef024061ed0f152932270f67543b642487e5d30cda97cbd0f4ede816704e734cebf45c95046adc57cb60163857263ab1bfc8fb65e9084e023eb23d98b8d318838d6e7b70f4b27c35e7aa114d872c29ff7ff5a5a6e5bdcea00ca997e311ba726ecf4a7ec6c0462a9366adfa836727dc53ea00ce994c034df1b1e51009e6f3da1adbff98354ed3f2d0500f1a89972ae8c536c19c5e22bc9af3f96e7662ed26be3792a03920bacc75590bbefbb62727332a92883c24927e2426ebf13bec1ca2ac5c081bd4958e13ec2ffb708b6627210fc158eff2c696c1474b25a289b3bcab744ce5b85d6f51c4dfc826aedb585729572737d0a75629e3168b4e7ddb3c3ed3faa6c515abe2ea0caee0e35a5438472578499bb7354cc2ebb42cedd6b0385f1787d93fbe1ef50e317c515b8ce792072ada31925cc8ac69f133d86c1141df88446af7975afdf5b96f9ca335c5a422272fb0ba216cfceacd367c26c6e5b4795627dddefec6c20a2dd4d126804f7a7b962bdb4eefe9b9c389e6efd76bd6b44505d69d286513a1434e7d945b4407cf20f24ddb387bf302eb95deeab6b0aa32c115757bcd7679ff9c5a326b9da687fa19d72540846e060419e36bc38341d6cd0968678a17c5314b380759b41180792835062e1ed0c01df2f5fee5649820ff9577069fab60da7f63f8745e7c88364c8e7697287f9536d9a14f620af3409cd7068336a7e86a143a0547b499e1aa0d59d2b8d72b34654a71c15e6a280a13804e8629ffa4b2ae6d5db9afcea7cf0c1386891ee72f9695ad2a110d4b9ad96214285a3372ba8b94eb8f2444f2873b9616003eb132bb0dc9696a90494449f365fd40c3763f39b94a9081d1e1f0a665465aa537fbd7206f237917eaf39dec809e9e868b5e0639d975acdd497d9feec76b478b08a7c726a705b9ed0f88cd558c665ea0661d9a55215c765cd81a16b88e490a4d6526d7211d0b5af6307935edb77ad64bf38bd23ce2a62923fd8ed08626d7efcc5c5b84511ef8b5608b25691d922e424e1eed5b4149c693b8c6838c7a74c51feb8a1e372fbfbb5fcebbc0715ac72e67cefa4659a43031ab7dabf9b2d2483b3d8aed1026fc2912d193f7d57eed7dc372aced37dfafbd19702f8261f8681f88e74f0c55a2863ee4980dbf941b11a8d1e942ecfee81a86a5a1d8f577e32020549b59f02803949030a27cb6888c6a96c728df09e461427d9b4e47b5695c84da532851df2dd273d24bc2b3875b37d1831a3cedc4b2053e2b4320520bb34a1749f68b82716f472e5800eb34c88ba7b43d82db3180943097c30091283d4f780235e14a5afc4a372ddb31958c50c3164e19b7af53dffa818b874c242e996a2ed77e3b4dc4444b972f37e51c956374282e27abe95d1ca3d8e023874ff6913a0e368ceb53bc90ac9729534fdeefbf82d2e829064463b6af23ae11fa627df91abf6014f012a78fd39729bfa499b4f9c3ed816611295c739079cf259b0089bf8110a2bb08de22df917721a56e07a492fa788cc231eb17968821e824c8a8f1763a2c1462c093bb436f03299ffb16ee1786650d61ecf2b2bf0907329d13fec283cb9c0be0e6aa39f8bb772b9b58a1caa6cbab40e32ad2fb7368e50e1e5b686a25cac0482e2e30c479e8f7296b1eb0d6407125726e2d7b3d819031b19f0f9b9f2d17a159aa5d1dbb1dc8472efeccd15c1d9b9f2dee63d4f8928644634aae035aa54547c03781f7325a97a49e01707e3b3bf325522f082b6dc51bbb0175d97635cd692322e2007489d8a9472e7eb429ce0e1c6b6e269fc91771901ea4cee438344434b0a545614d54ebdde7267594728f9dede89477ed8956eb51ceb733ef6dd8594cc23aa222dd6920b2d72d0fe5f3d46771eb1c0eea0bb805912da019cac579f8486c4a4c3b18d8a8e5d34db958b027c417da1416a43d2feb59cfa76aabab6a3ff0957bebfd4747ceca572fe52227a2fa4e19c1e292bf92ca42f6da5d17727727ef7ce4e2e7e5d99b57672f32e9981f1a841577ff0770ac80e249070418cf44b1209b0899689a451e6b6369be0a5d842d7191f7df41795eabdc17e0484c2a8feef982bbecac144e2dd0b72bb27b1606a1be741a769305b0b87a072a0ea6a2b63d579ab1dc32a6179d24272d1d56c212a7f707abb65cc5655236a24a6e35e254e70e835d64806e4ba0fac7213446274a5765399b0829e10acad71db516967cf8c476fd7ee380eb8c42c26728ded1dd71d9c974bfa8797a55e6a04a115d85a27e533344448c33042c3af3c727b0f550876a16f59cac0ea6931f0b2439a936f34e68fca0cf005164c6cc57872f6651d00bfc23b94e9e2b4698384350804ff49b8559a931378f7b0a75a89760d08ac57393e2925509a72d8fbb8591fdd3da4dfd82f74261f9521df1b502c934c45ac67458fc0760903617877b70c42f3ad504bcfe20ea5d6de237d646231f4721b2954655f216f7d0b159a2dc2e8d1e09c329c20cc8ca8ce385d6359db1e3028d78be45ae7988cd261a94bbbdd69903f961b73742be2b7e7050b6a3c802e941e404bd2ab5cd4380900a14370ca4af4538953e8116fa0f27f7b6c9c28845fb6727f71f6234f153ef85a96faf58d3967256786262f79d5161009b4e4a8f701fb2859ae660d5f9e00e18f4facce6278d5f5e8e585944cf5a7cff58e9f001048b767eab73d44cc9765bb9db993ad8f48a87fa0c79a66c8055ff1d16d0b11dc57e2722f1338efb24e492e82851105b5d92631473f42872409febc9f4838debd8c2e72a6217a356ed95eb285cd9cf878c3ac4c314ad42bf6542c85c4c0f21d538c9972d95c60227bdc623b7ac7f8e96813565f7419d6a1ed2837316e158c06fee9417270ffe4edb2ba45af8681e9d464e77ee14e1c95c9fc8f3b579d67330145ca9d72769673bbf18586c23f3757e970086a3f6cf8eb506c8a722094246665d4dd1e0b67d9be854e1496056fd7bb8a646a255025d201ffb984340643f9ea3cce24d503f793a0bb30efac2d368318495d6099c6883a9a2426091f51f276590be72da572e8666024a855107ab26771717692e065d5654012192da7fff115efaf629b9939a4faaf3d157963757a564f2d5125d7531963f654ca22ba811d9e9c5c446f1172f057275b8f2e7d0587f48a764dee32db65030e8e50ee24d3f0d33244a92edf72581d33fcaa611fb800e74682d3cb233ea6ce150a5848bbe24887bf83a8c19772571cce3228162f19d9d30ea5da60f58222522a197d4253a4690afdd7adde275b6be4fcca556f956c6db2a2bba7de9113fd388394b19c93fe270f924dba118772d4ab38510290d36406295b0950151b6c7c5f7cd6804352a81a9237f61ce2b672703fb8d979e9cd5ed9a65492fe1aa3a9b98aee9896202da8c6259a00b7d3787220823f8b772d510e3b0169ab62107b89dec2e82474cde171cae09dcbd6864e72fa5c3b3b786f04f6a706b96c68c54f09ce6a49e494b17b5197da75d2f686da72c7f4b89290f7e58e72a938c398e598c26bb27569d38c45ecfade7e3d93d0f8723c5bc47b6bf5dd47b399b63e0cd570f1feaacb38793b53a426e6ddd9cb0b8472e1f2ee8d459146acff736c0f6fbe1afdd17df1a3220ec5cf160a15c7e5ae502ee6ad7dc4f528b1468ee1e1f209371d2d24f6932a67a3c360dd27743f333e2a4b97602de75fe39cbdeb12c5e0b9ef71abb236a5c51c702ae35c86a63ec9437072a6c52a25141033837df945449778a5632610b3f1b40fe2ed2f9ea26cc9ee9372b36fabf0937b073888fd4ad39e5e4d99ff680a4f5b76a30d1ba049e2dfc95472cae7121d09caa353af71a8256c73805ec2f27373095b71c79222986962b81972d5070deab87add317490feae3d52de79e36237e13c29df9db2ca5cdbdd83ed72eb35401885ce294e02f6b994342d37b468a080680e202fb9f01af72137e1ba727e2e0ffb8331081edb29bbeaea790e43c3e957334515d80c80960d0e1f7b677219d92a30b34d7d129fa06de3a0aa9f9d3f1044e6787864ef96da95a80816827290858f2b6561f6e4d0c26d2b28772709a42c892d78725d49718d715141654b058ab64c5af8b64bdbb957e2e1207497fb685f854896d00c43f28e36a895483372767f97617b48d059ab0e1f60b90f3196dfc3c5bc8a20ee3cea7c22269d67d47284d7f463e153e2ca57b1068de74772d879f9e402690374bcb602a105e9cb9f2ef06ac7f6e3bf59cd77a3468577c39a70718f9392925435acc02eefc81b8b01104ce166b1e82bc76447d02cd1c9061b5dc21eb23e0bfdc91dd97a79bce429d1722c970b5f6b9492dfc72c7d99edbd5a67bf171eee00257b79a3eab638716b3a72dc814e3f521241e18bdf8b53ebb2ce1452679a4acfbdecea15c482a3af8a8d64c92dbdae3a7e8b5b39283fbdfffb1968528b3217d9ac92b4103fe4b97d1c757270cdbea0fbc9dfadbedce48b69240a5d49d4e6ac9f1fbbd8fb883cb3dd150e667d47e2414ef98e1cde141922af39cfb954175bfac7531a8d2572cc65a423b25b69d1e1ed5adf633c6c1a9caf9cfd091dad651e7d8d35fc0f44c1a3df3e96dd7260e454fb1bf70e00512920b75158767c5ca04ef7c8d6b258eb185fc3679948723718c6138a4e6703d2334a2ce03f205d6460670fdda5c05bada44e0998b4c4729ae333afa35159c6d8628af09fcb6629a1ca804f5e810a67ae147132b2ba9772396b0d45f2cb1c4b7c157bb5cf31f814e17793415e1e00576d72d1fa416cd17247cf48d01de2fa9aeed18404ccb24844de5641bce86205cd2dfa431563141d729f9a23eb8eeb321693d54ff0e39df6693da57e98ce3931ae571ba354bd038d5d899c537da2b63faf19acc0e6643e3f7b89e3f56e6848f1d5c95c8beda33fa3641f462ebda10e641a505db57aa6c5c3ba5b9ea724ee925c8d99ca93cb467c12729017857b8b9dc80d9ba34a9d5c9ac97498bf7b7f1bdf790698537a70734fac72666b32ab60f71aed6f0808cd5c560c35ccddfb26bb84c6c54efe1749e222cd726a6a408cc32df55f2a04c8dc505672f40fbf2223faeb782ea7fede2bbd3b59362a758df71afe4213f6890092bcab28b50cc9f82732a184bdd85584905f34a5723610d80d1461786b558b10ccb5e4498d36c91a888e4ab4b4d2c007f6e8900772b5d9eb63019f2b343927731f3d32a1091447d06f6a9bff786f1cf61447ae703a49d43541269383707aa04b1b2bafc9389bbcbe409c0dbe970b1710e2389f9e725ca5e25542feee4708f206853713a2aab68f6eb24790f1ca8f2eae60cc9fce6c324a888d3df96f55de983a677c7283f476f1645c6810897149a38d231eee3e08d49633f205ef0f267de8fa581b18e129f8f56df4463b5736f92ac00e39a59b5cedfd67663daf053b5afbc279d61fbf32968ee378116fab897559dada80456272efc64e1533f29ec3d8142df19e41b6eb607682c367a9e38d0c48fce03de77f720c88ad72b1f34fb6608c09bab9ff513185e1898a221efb17ed1003adf61c4a726ff907191155b6aa6837da50f09772777ae3d060eeeafc50888f28d833857f22056b601265f040be0a56bc92ea303b6a7ea392c426480dfeb9112ce0b29b495b623dcd5b5c38887c94a3ba6140e7f8099108d07681173b3a88a89a7595da1c5ca18f5cffd75cbdc1ed5448189632c9447c6cfd953f5ff75c078e18cbf36733019a4d89321c7788ff83668fdc09bdc3b467eb6b5efccd1be22a552d1bb0945f72c6818c11bae128cf5cb0326fffb607f8e6a8b78bd0522cf6dfc8b7db34090372573eda7ab4520636a6656d682dae86159794a6cd224d3acb181ef292d4248a5115a8332fab44ed873af4641a932d3bdfd542d13c1a4d22c9c2e69d3a223d34283985922373f64d42680ffe19e2b238fb9c2dff61c735b4972c754c0ff455d55ddd650bc8bfeafdf1ebbc1e7b45bfb68da9ace4e553f6afdabdeaf4d581e2722f3436b5ab70f177d7ed06cb9d8a88d05f038a5191f8e95d1f0be4269b4d95e472d32483d06dfc3ea27adea72b40e5de597f32d2dd6efb714f1016101f2547ee729511903c64fb349888aa7a0ee59380ae816350462372d874196a446404ab6672cb8b0c3047163f071901ed0469d6851f1905c74b53d3800d43bd8fddf9f67665e7d83b3dc38f2ba32186920d24f15f34ac2b0e758c24232c5b150f6e5a7cbc72599c3c429abe361c2a9a2e2836f2e799cd53f204faf722239067dd96918a676a2a07bd2520410ef318ce7cc311b03046b749c969599e0e27648bd7ef163f5472d4eeff85086339fb5ddf841a27e5d25fc2cfe5f276a74b7a19a885f068ab32006f7f4f594ea2b89a811a790c21d2b9474e169f0b072a59158f1f7acd1cb4097269ce962be839a1a863e241503397fac1d566f767cd63abf9247e0d79dd9981655e7617f6493bb3b4331ac092653989a04f22956eb806213c3d94896be081f472dea2979f6c4d0daa0f0c402e61b31de9ff57b592940ea0a49bbfd33394acee725363ccadd5c7b288a1d9bdbcb3f1dfe3b85eae43d254f45200dff6726e554d63dbf0eddca86f8c4d16180d74ad85b2c4e53aa5d244733cb9e5862d8ea64bf47286430bf4f369f2028d89ab8770dd47d709d2d2c4e6ee27ef1b4e299aa786e472d3aa85fadb54c056545183b2a5f35be1bd9b82dc8985e8fa0d8065037d302e7240c52407179044e6412e3cbeefbc7e9dcb0842bedc3603fca2995fb4b4e457726da8fc58489e06674ace75c19616bb3035d445a3e402275e57479f2eb07779724eeb49e2258b1e1575cfe2ed7f1b8be5a11cd7d7f496bd6f994ad2fe9dd6a67266eb2f41033eec868ded1272b9bb161a8bbd5a4e3042b9455e87e05baa063e721ff2a233aca3034b9eb4cb6874942826dcff31b937c0b92d455ec8bc221577721496355e7bca762abb4b34572f86a09b740b061ce54265244ecee09b3f2ab87223489b52992278a94c6dfa56ccce229e882e8db56eef3e686218984eeee9217262bc214e08278f8ab9885122ea94b1e8a161b3ef2f8727a6b88ca406646bcc29f923ab5880da2f07cf1562cac78df31f9ee91a867a57726cbc83c726ca1bc8350173c0b3408e026757d705e3904593bad68a687913d438f04129eab593d3b372f6869d50eee41cfdeb0ff3fb21e519ca2a947bcb37d479120d4f7d83621e1b698c2e1b0b4ab2c70ce9d4f453b9cd64365b377d78f1005a079c42d4ef9a7ed372011524a469745008b11404149a993e25865fabdf8ba0ce98a8ac21eb69a97c425160030b2d0c614eb1adb079a5d2d1efa27bb614bbd10a7927ec900f5a775e72baf5be2545db7c71bbb6892e8378f798205dd58d5f361aaa14efbb451b2cea65ae025b9ceb768783f972edad50fea4e177079645a15cafb44cc6fa9f39adba67fb0a8735ece40a9602bbc5b6afa5ea8146024f257eafaa67aae3e524b61a751eded25839f19b3b49659862ca494eb63aa930bcf5fedef68bac2c42fa47274b4fdef4151cffaccb92ba444bfbe036a1cee4994232deb2bab51fb58003985d3b3ec16385d438d01f543cc805934cf3441eeee8f20852dc31f8e25b059437c40743910e6377c156b2821b07db076507b92e5ca3b96d8a6bceb58e6d7d673c9375723156f89ba50f4990f1b50b05a372128d0b5fd79f07f0ccfb85e314f0bec6bc7234859f2d361685457092bc56cdce0633b3a1dc8ad5ecf2812cf3bf4340422e7240d63d8292e50aac8d10ba756371698936026ad7773369119f84ba47c69b5272c8b5cf111014fa9889246b18e5557573b001bffe80cff92534a1a01d18f2e172f8cde91f5b7676e0e11b1ab4114ea385e4eab5c2d53c67713d51b6625a53195f1488dd4617c069de214d7823c14eaf3a2f6b6450a32dd2ac0d196a8c3f6cfd3362b0cd1d33b825be9208410838cc38c84ff1771b7a890df28a69d7e72e771f7295f60d89550c57bb680d767d0682d56513466d39269915aaea60917054f0c67267ce8efa94338d07baedbda1b442429da674ea1af904bf9847e4e5827ab4350e998405f42aeca463f394a39b22a70c4fd99444f81567897b3d911f2daf33ce72787959c2d6b7edcd41bf765a61b4d4752d2e89546bc5fdd0305c8373eb00667240d06506fcd6c9e70cdedcf66f4260605c0db6cf31ddb29a076bf5953f74de7279517153737ac0995f61dce220d20eb6e316daedbb3a97639a6ff62d1076fa72453335c4371b40ef417101c296059168ec847bc20376a362dfb4fa075d71216cfadd0551bf5bc9a1b6f1e16ca57477300704317ccecb60a35d9e9551cbd46b6efda76c5dae4694162b8e145392da70ab6a2885396a9c2a810ca712bfbedf55726b8244fccefd96cc3680feb5e6bc8a895e0503434b0aabde4c66ff881467647209920a1c4ee0b1238525157547918eac7d83fc08c50d27f9b6bfd07b8ebf397258b797eef3222dbf45a1fdbe1e7d78ba9d588e63eea3c465d259f39b9a0ad572effc3dd56c2dd8ed1f00bc27d3c76ce0b67a8c574fa1358badd8e0b8a9e78c35b9e69b3163d1360c01ab96cb78ca59fd59f7bc1b6aa61a0d821906aeaad4834b14d0f609e24efd900294d6a7bd07f21f0cca65f0ffedd9ddd79c65502e2086182e574cb967fe87c668f0ba74c204051b5cbd1fa111da3cc16203e6822e708f3cc5200ec1139ed86b5d8a90383895981cc16c28790b90edd3c2e7f7268748f960c6e40ba993ee28e04aa4e2f5d5f1090f220b35cef4151a9ec30fe478fe64fd50c96e875c280d9a189ad601fbf6ecfc6eb5cacec87eb77366766d12997dda1a2bb758b03d7674439b18022693fa8bee62624a1f002956648fba1cc10e2bbdf272039649ddf30ae41071573b9d4ab3832742cbea178b2ad4123534a7d7fdeff472409d72777edb5d643bab5ac170cc8ff07302811b3983f766d96c2e8da15aad199a5efd5f77e12ee1b40437d7513605fb5803338c96fde3eb287e4dfdd6f8e5335f60e74c57a72599b8a2643e919179abdd0d0b247e24172d2d07e6edf32b5e71d253b99d4879e5a8015076c1f6fb33686dbc56ca373b45426ea1dcce770ecc1a8bd55d8e5401879e28f50ee1dd7d85f1e5992465fca68e5bc8aa96dbc404f94c699dee88d01586652369fc94a3957d6dc274ec6b5594aabb44ba3be52873f030fd134acab3e3f8ccec0b509375c1901ed11b13896b80ac41bff3bd076dcf75726b18c42880d38c02a2d6e0e4ceaf4d66dc51944d051b4cc6a3897a1afe3f10726a0c9f0b2785a14e1b6c7575e8163f0e3098544f4c59dea28bef517db6ec9d422856cf8db615ef403a03c64e30339683f3f1e6bf61349de71b605c6cc05b25716bb0c63ea3f8f5d9e184f73b69fc3286f41755fe3f7f0c09baf95741dfaeb172787810b9b81870e25eee49ae9d245d72051c8367ae4bc0c56129617006d3131bdbc780ed4e139e8da689dde4dcf00100eb601ef023733ad98937eb822341c5723bbe9ce1ac82a6a8e05c485e16ab1d77c1384768ccf82ff55dbaa335dec26a37125d8f9854ddda96fecbf0dd324180ea7eec30458f33e848df7d03987a30ae39682467ef06cc7850b3c287395e2a443c0f5b22d83f03a53f96cc65c64d0dd072147c9bcb7c399f1dc2ce5232fe44f96eb76d4683e9d7fdfb5325d70dc729a672b8be67af54a7928e4857ecccfdbcc608648e983428f990e3d587ff8574afc272798b04b0fc6baf43e52cd7a472202307ec5dd1354979825d5b07864022672f21677358d03522983432d75687d84610e73c9e43b5d973b2fdad45b73e6bc2b672d0da2013eaa8f3e8ea8d0be73d653130a36dba59dfb6f998109dbbcf3a7c500db956847623fce269b2fc0c985ac42835cdd3a5147e127e1bb462cdf8d2116772892abf4eaca28d59d80ec649f8643f62ba38a7a88c51eeed2234cda82b225c140d9395d4fbf88b1dc9275a810da08ce1935b614cdfced0cd246f5775ce46fb7283180163373563e1b69caa894d08e0104c8e8691b5177df464befe4585d8901c6104bdc61f8331552dd2775f6f01eae3d4777decb683a9c8b388c87292fa73400420309dc8e45eee87386bcdf367a41f30d807780a31f4d6112ac2df8426e072816b0b6feba30cf0ee024a791a7266826c434bde6fb63462e22fead7de29df5b15e232ca8d70ac4c978ac2816cc40a5b4528b881a9598e1313a9453bb8cf3c29d209e8fd0c75c105d7f5b6d7ab8a18cf28216e06d7b4949f111584cc004a9572bfe12acd70aa1121c45cc785209f5aabe97314f37d2ba5bb0967c75509e7d572ce4b3377a7911a5b0f4c2b0dbd21fe88ab3352cb7e86910c445f3238fb1eb67284e708d15355e4cfb72a28445737c1ad0d530aa6a5e12e3b295a1d55e704a272b09632be9e1d48ded899e5e5bb8c2f1d0168954e801493d40c97a6128905d672c3003cb773516e17da84aea564d2020699bd49f5b2291736a704685d81ed3b723dc56262ad52b2f9d1d9edca2d912a6fd3d3a758500a6f53cf286d7dfb8cc813a942c9a49eff8492b0a1b9da6a711b498b3ec2899e8505873f5727f9f726a9729d18905d66be897b5f35e77bca96262cdab36ef0aa73ea3b9012cc7e313070720996f8251c7ebb52786c0b194e487c4a1711a44b118e4ffbf84abef4c75ddb6542c04247bc1c541bb3514362b8a4e189bbc51e154c4f23bf1e3e9be0b988e2724ef77604e0688c45fd8c7bb76389ee7c8527fd3a900d154b91757d1dbf4b5c72968de9f75f0fe6496159f46b6b5ebf76492b9c726b0ea58de78e9814f935f10c2041d2ab44c99a51b6c59aefd793ac04d62594c9a2327f5af4dc582c3e89ca728443ec119f8ad78e2c0087808cbf26fe237f0e4782f1caa0199c03694e3106534ac530e912fef4949fe4af091d20bbaa92057c49bddbd0b73a0c5ade2dc194531a0da8da3b56058dc0564d02da9f8a03704977abb639519b661050b77e223a72a90100155f4ee5af050bff6e053637d8fed1e78a2b62714365fc397c05256572957261203eebe015b317c183056d510a2d277090c3b388be9aa88da721a13172a72efac8d49bfe0efc730d0a3d85fd1c11ed28fa3a27860d3bee4a4a23c9be72a82721baacb60beefbd5a325e856a6be17cc47e78bdd4a75e9cdd02cea429b72aa5194ef45771848473a85d82c7536b914db5e0efdc6a9e0db164799840b8472e1ac6f95851b634c8e2729f5e2382d85e9a0f23e0040b95c1d0b8e64f4e8a967fb63699caa016852ea6c4c71afa7c0ea925196e4c8e0b34848e4ff53d7a7217248febfecf5670ea098d1c599cb101752742d0aa63c91dc5521e6aa1a9ff5a472341bca06a0b79a235fc199af5a538ae73b0a19268d12be40153a45a36f1038725f3dcd882fc77b1f112f101530b2f3f6ca60a5489c248ed333a88c57bd3f6b7208d3144b6763706a7b3f4cda8cedcbc0e044f66e444d57b6f23d146c8e0a2572cb92e7e558d13d5a196a68ec4b606ee48dfffa579c3954382d9be213c13ac1728789058ccac8ac6c51ecc5c64ecfe704597f66ee96e9793f5c0512b7eae12a72c82a4c257d056b613697f989a28768459c2e60d595be64ca07a134c9caad1a72887af88bc270161ab17119fe321893ffa7b5c93cfbb776c83be23f05d7e4937233bdef73d049f7c748b732062e58fbf865ec1908e15a9a12010bea1725a3d672ce8442092008eb6c16d6ad261d47f925adebd60b7cc9463acb8df91781c94f720fdae086370d1a1ccb6eef79eea30254806461c0f9fc520ed0d96dbca29784320fa57cbfba8b27f5a269192aadfd1d110c08ec481976b44065eefdddd92bcf722bad53ce3fee0f3aa28a59c925ea2c8701ff8981eefa8ce19afd57d99ae92372465c7c3a8726371b90322d0e9fbac4060d7ce7504ba53498aec8600b715054727f2204bacb056717b5ad3c6b386cd99af0ffec0aefe567b8325160e911dc1c729152129a60ed4bdc7dfbf4dcdd6708a603335f3c8bb7bc32056532007308aa726d7001d7ab581473fedcc1151e162045ba95c7bcc7b23d3df260a8741cde5b26f141d5f6953d08166f5e5d585831ec6f9e83f413570186f3965c0c4d1a837072efab4e2afa59d2e72b6f95f6e232c423cf1e3cd2dbb7818cef3a2e660f5af17257afba641f4c977602af69ac68cce02db2ebd53d8123fbf02ea069910d97bb6975e0ad9f70137869b5ff0060edd40f67f55a4d33e90ac96137c688dd286c2a72327bde1981d0255d9e3d6ef07e550b67f198044d42a2a69f8dbaad60d9ef261fb18854641ad500c5179d8dab10a532e305903911d4d367782d5eed198e8fb072ce73e380ee2b811d025149f9c9c2964563fc0ed95a72fb050ca557b6498b0e28d60237785c509218008340f1c46ff50359e7617aca9def4b891a2153e87793721d6d44f8bc4059bfec21610d51e889f14a30a221e1b39ac7be4859174b0adf589dd0bb8a2e34f7139b152ce0742d147b9607583497bfe57e8cbd30f72132b67234f93ed0f3b545a4e1715e90be23b0814949d6426ee9c2d63bf1248484936472e93bca90ea6a42942f5683a3bd112bbb249a4f537f660afd0ad2d29106f5c87241a5a3f8aee46b67394d81d3d1e6c65ac322440b2e90de19ad1a8ada2d2d5f57872b2a9b54ed29cf3befeb3010b793c4bf70fd8604b4cafeab4f1d9c747452489bd3f66cf3b4508ff77249920220e2ffe660e1b5c0fd345a27444fe4261724727f72e2d7b708c4e1e4bfcb51a4761b42706761f497ed1b6bcce988a4312a7972179da0ed2e58d9fcdf43b7e55b637300856919ee368ac988ab7e8ca4bad0eb725d5e5173279b79aa8827794dfc51979bab16adbed58808a36d53955b2bb34e72b93e096958ec46e6a19014d8ec1f1637e78637a921d4d6ea6fda99866cec4c72778cb3882236f9814978970115ef828d57306de1ceb5efbd54cd9d239a5bed7297702860598c8e8a9b6ce2d5219feb4a072c044403a690e80803e59055a2b07220b77b4eead44d6b42d74aa5361cf3beda36c5229c4b26f2cf0cec781821fb6271bbf8648ea870c0c4f80a09a7082322ba5825b735110c296d55ed0f631d8d72ae61616a1dc05d5150528000c15da9702309bc43412a2fc5ef710bec5ceacb1fcb8897b5b3cf3e3085a3f18878e24d79e93d4e653f74e4c1b5cae70170cf5a7260bcdd2fd203abab783b87a36613e751e7ab30629903f7cfa006a43a0a9104283913b04397f048afba97862442c05aa546c63d82368efa2572f87673d79f6e72fe6a8c20b820b2eb1c5057c481cdd1a9fabb53f6cd3f0b1a31745dd6ceb640454bf7587e1ae3cd36c3a11dcaca823485383ccbae6f6a50556f1e11230cbe64722af88c3c59dcf3cb8d247dfc3d6620ac2cf9de7a95d34c371c6baa51dae8521bb1139aca2a83bbe1c06d1749b2caf5d559add408f2690309cab194ef5758733e6e99befb06178a68c8fea5e30a81312795c770384b68c9f0f703d4e64e4736729f013d7ecc0fac82720e5ae5fc71e12c3df22d22e943dc64e2e6080c7f711e7239389e22e6649971c9bf2f0726ba3c6635c4ca511ee1e1e6e0ea83bba9c9d272afea690fd1b604d1824ee04646d5c4db46332881cd08d6a2c3bb863a090dff72a24164c2e45575395f7b7e757e91eaea41a47ab03f9d0f8d601afac977f19029a5dc3cf0abb8d3aabc0a407fdfd9bac6778f4eed469d5930a5a47bbcae443772e34b7916202df9cd237a67b8a09c33cf7bdb4956e7bdb9db4999493f2d206c081ce2a5948273c1b11de2dcd9143c4832813ee67e04f2c7c84a75a563c7e1ab729a8b9a5c383d9e1dc6855e2bcb5318ef0c3c0fa535f8f6f61564aad79f436172784f2a5e00c98b46cfbf25d6eaf3e69434a25f2004d0b3afe4e19b6807d3c1527c377bee790e837b40e20d7bbc0379298febb2572f26f8de4a1ab6febc4f4269d69d907ec6ef539c67288976e82a36862dd9b844d2ed1cacfce0ba1c0f73a27244e05510f73ed6ff91dec8b0dd7d93026757c72d4515f30491b2cf64538da002fb038ce6c1c4ab7eeb3ae94170fa95d72c2eb6d62823230a62ad536e13e2f672bf421e70f3a27e0de3a0286cc69c822d452d11aa7b714e7370c647de59bca0728387d50f319b2a661efc830e38243c1e3429c24b4dda118f4af7a2ae54323272df95d50e43edfd05ef03627cee6c7f10c608278b1eeb6d2f8f1ce8bc6f758f0517fefc776c510a234b1bce91cddd605ee90bc1a9d8d6f816e2e4fc8fbfd25e720098f3c2e55a4257ac635489eace0bdca0b89e307606eeab72790272eef46316e648724ec340e44dca387697219d12570a7e6f2267d65b4c2691631d47a5380e999120fc3723db02b7cd71246be2a28188b0a8da8226f3496abd7b4e09639b7221bfcce28bcb27161262577c4842957bf5d077a74a89c87022a85cca01fc667244f138d2e399a5224e0853ed7cd30bf58aab82c2b5da85d4a6a75f21170c8872e4b07018ef1c2c3f2acfb4547a4bcedc6b0c0aa31b712ab7720c6f1c76327e72f75fc22b94526756b92811449aa062a90931beb434e39abdddd10d0f5db38d3553004722bdabaf98a8516c754669060471481bec16f6490e44e7523f138a8372f49d67f89eabce09ea5dbe6830ba43b7ee4a3cdd40d2340ca451d058d6ac8f72455e9cbe01ea766f5d9c8634007d3baf409c5f90f5f282944253b95a6a376972c78ad74b7f6292d8410ff400fb01b47f3b9576826636d919271a8bc46235a7724596093ba42297b8d992f13abf04a24605a330225eb4f4bb436cbfd7e21bde722931292100b589e63e8c2e4b6f6ce89f5821d6965679a0dfdfe07df293ce27720d2f17b33c8571ca8f0714425f2e46bf9388930af8fbae380f258fada320a230123c5397d3066c3a4ec3630e39e9dac26ace75a542adbb7a5ce023b8eca5a73a679d361bb267ea178da0d3cdd4867b0295695cbe1ca0abfbd6b8b638f653d030b2e45a992b7c221b03f9c3d8db3198f4ca4943e2883a0cbd1c9e4c06d48d995af09e3b23396dfcffa08b69e373930c5e5d65f76e012e9454d8e05faaa9303871ca631fab43a73250fc25bf5e18fdaf0d2c4224e69fde3315a27cc44c28fc6a72c1dc33c7a2912ac3c93ddf2d0374640a43aa0e9fbcc8e63ec9096a2c240c67048ff6d32c17f96e4d93697b5cc24bd3f448bb67dfa067ec6b6c0d8a4dd8496446f944074c2025b17e92892043f7504639da9500f4379dd4c175caced738d7d272e99eba0069a8c901822c9757b851de02c35f1993cd216169279adddc64020f19a7d04a65c662291afb6a18d0e129fdb6967e8430189fa4533308fc08771f28720d9feb55e46def750d924b8e4ae6d115c15e4aff22f3fa7cac1e2349f8f6f8078ccb0a45b793b4797eeda66f419aa806c7ce602df09f6a94617744bf24d8b27220d79eb991f4cd5c1c147e53b5e1e590ea43aaa1cb09b6493a62ee35df7c8b72646bd53083384aca5f3ca69b12673115ab232456a2d9e1f7e055418f696e6e7264a01903dcf2dfd6f2f6a8231d4d0cb737c8bc26b43caddc0e471d48a2f5be72b20833a9bf68a9693b5838a6d5d1e604520951394dea3b44b1efc8b5c3f2bb72cd9301f73599cb8e7e116d3ce3489b32c1f52f539403f0a752f16c5778057e727952e0555cd66e2fe05953fddf6a52ff4259abecd2ccad9066985c964c49f361eeb8e3887677ea2772f6fcd338cdf5db037e0a6289206e2b38505103c9e5de18360763da6f3d40a8e5e0e0fa224e9279dd102096d6155be6c7d3f5130feced7264435d56016205e1d7cdf52602580ade4803868f892ff4b52f4115038b29707286cd21e5bd85151c922c0a7174e0f94a4806a67174b87125dbcc8bb3d804d951e821a3533717aff925d4cc39e0ffee912ef35cc487b0565ce63abfbed1c27e2172e4e718724aede32fdfad568d30e2b7c538324a9c46da7c37b1e15a54a1ee72ddb13924dbdfe87e26c2d3c9c38c7d986d7c7d30ddb3b52f87c18051741c787212edc528e897afa21224c0f87b82628cddf87d89f5d0ed3fd4d8a6c97f292600126539336c65290903f31d1a23191210bdcf835e8b5d5cf740e8968e0dfdde72849f4ebd10e45be6ee282f1dabd0ba19c68ffaed7440d19651ef8e0f971b8a72f6072c9ac1a32710ad370dac0ad8397efbedc3a9bfe2939789e158c0567c115ef6b7ff05a7177836be564b89f1ad7645413410e1a53ab858f197a8beac579872d2d31932c2a37ac9e8a670facf9bf58c39784e596fe769727de7a2b80fc5f73c20b43f27fbd91589715210b58c9338cb343f2cc5089526b4ac8645c3df365572500efd160cb4055e5dfbbc048e7a84c5be8c1be94a8987ce61b62c93a0243e72bcc0cb5ac018a303bb75ea42e26b1c714cf94661af572361aee36d6696983872eef57fe3bde10376ac2a2cf55eafdff12dece2c03e2be2e126b0cd322b75ea72c3128a87f1358cf27811916e00615766149e0a817ae863fe3359cb6df8b43f727a4e69a1127fb5bd2f7d441a42b0cf864051cf26fd4496de952a9683ce70c572f9306cb572cfbb7f4d1d22e468187f1b61554f5068921996dbbc2f1e73a76d3400505eace87224fe106debe955ab29ce7032a32ada55386d42690cf56f87307269f60d897378f08fa3a037b6930e4e524f0b67b0c4dff3e521ef82bc17c25c622d64f9bcf847865a66e0c3255813e47d91b98885bf965ecf86d6ea8e42ba9b7279ca9af8d6300f60e8120cc24040fdfe26d6d469103b24078d9eb656b7ac98585d86eccc3e0aae53298ad32691c7167372c1bcaf5909177a77ad9e56bdf3bb7224fcadb7d45714826f5f56485dd77f0fbfc35133926787c2bd273ff76a63451a6834150d3a5846fdb88a33b4a40920df7f13b5dfa7af1b6eca2b69f964ccef724a5b1145cde6b5da81852b88fdc5fcf9b2480311a297ebb9ae72a7927496227246a9574dbeb002e5fcd25554785a0f85f7c252c37b38a1f4235bf78ec9b729725ecb2d2c6e5cf74d6154ff60aff9bc24d9c5de8a48c629ca84b51483d666586bf4fcd123f14d6e8a76ff824b4a74b2502a55edcaa47971ae46154b77d39022727eee6f21b513e43688f4d032436413ba84294893fb88cf72aa1770a61f943372ad208c6579e2ba36d6cfb0c669988c111805d2fe38a5e3c59d91ca6b9d28d172947a265eac5f85d4be8e54a166e5a0c2008d1ec50fe2eda7d341247cbed9772ae5dc516da6088212b51d541c08b4a644a8ba062c44d1ea7e650eab8952b0b37246f13fe1783338ee5b591b44ee39fa72410357d60d698eb00c0b27f00d948772c7afe0efbe91c7c694d7697247e060893b05a6db66a4403bb48acb7e97962072a211b8ceee0ec5f2c19600bfe1517f0412d4f9f63f8cab9e0b73d9843add6c6e450ad92c23708d5ba292079a1e0b0c97753e77ac64606c27aa0441ecdc833b331459359220c1bb466acc028b754189ee8c5762b879f6ff2c1aae21fe478df5720262b52bcbfde5072822955ef6017bea1cb95615b1b9d3aa8ebafe548ccdaf3515b8d4c5d4bbc396c90680d7a5f27efb3ac0dec2c92bb58c3acf23d46fd7f850bbb3f9f7c7f89c2f313a4d4fc19b9e8fbb5c6f36f6583585c1324f0e07d3011893c82b4eeb519edffb771d18b55f240564dcceefb7ba6e04d5500589b7320e4fce3b01a28488a8676b8e690de4b009c1de8252539e2624d4944391d32750a6005dbb8ca4a878e784061da3e565e9ae7a3d9cf279679f0573a6f0ed4161d820722267f5d1424a062affadcc90130d3aa7ea4ab0bf83662709ee669bab6766fc72004e2ead620c3cc004c29ab19d870eafa9d7e6ba6138636e15677edf37cf7972ef7ffede2e3418acc80f9c7556df30d5631304f0a67723452007e4f37e2d681572f44116c2963f520a6db2e6ef2dbead700f1330d9b73c6f000fda9168b12d62c0ba85f399919ec1d660a5167d688b6373c3abac061b711f76fb874aa72bf332b2dd02a5b2525da6f1d65af1c72128972e9028573e939cd6834ff79e3f3f1972eddd84575cda877521fbf03d7d4d991a3c8cc508f71d0261edb26f2b52ea027216ee8178dd318412e08095a13c80b37fcce7059a9481cd39234e7d2f987e537218cd30fca8a81b13ed8b1372478e2b0c073940a8f15d44172b95edfa50cc923f8442da527bc31f2a1839daefc87c0a5c20a00cde294dc766262b799ddd37f672d7101aae8f99e1d3bd05bde3eaab01edd5eae7196851eff159df447d0142e81120c0cc132cefa35239027e0f3ddeef2c260ec8db3494bcfa2502d28885648765f539ebdf89bc4393ecfcf1a98972bb72858e4674f271370915cf0dbaaf0cf1728b8598be660b1f3d9d4689a93c855c4e75ede8fd0167128843286424cc5e3a723a3e4da1b9162559ba3609ee5334e3287631cd89b108fb481abeac6b687dd672557abdc263a36f87ea8cfa79ce8296363a3a5b56cc9dffeffc297fdb2ddd23453b3cd895b5d107d9539fa3b32e0710939762dd568bd9ea5bfc19ed6847e3477236866e31a3c162782a16e52fa191e920fe24f49a348cb11e40f1831245fb5f721871ac4ddd6e43b923965ff1ee18461f4ce5e90559bbd7c257c468c0e5c90e3e2a53232da7c04138bee366c4d7b765b5f9a91e8cc014ca548508fdabdb4bee0e97e1868059f051d580c9f3d85e5fd1728e1436e2c500de13e9c580757a1846692dfe063ee8def4f743baeaeee04a43c126a7f272e93bb8ffabe6eb8214da7c72c69ca0e9f0a1ee7e1c9b9105ac9e039ab0c0eaff5bf971fb40fa5f58519a83155a986e20c47499a3b8a1fe17a6ff252dd6a5a8cb8ea4d4ac20ab3072bd83795511d62b3f67ea16e11658a880e770f673ff023cae4e4ef3f219db162e3b12b6480875ca20c6a27310d07c119226f9edd36db4fb4a86d0ea0db06ff0d6b5f052724c783290a542a63ebe38b263271fb315a925e6af91dc7f7a86f6f43b3917a829e21faef5daedaacc9059793adaea33c5afc08e491697143063e75921344a34678ad67e7f7543b00a241ed691e874b358adcd157936ea1bfc55ced4e9aafd197279d03bedea96943533b975b2b071bc8e72b306a26d01e49825b97728ffe8e172276f7ba829a151c269938d99c42d73a3145c3db9a31747ff194000ea24b5bf635de20bb29076f02c805c5ae867788be67d4c6764959047d1d32ff4580cc2c372f8fa65b5e1392414089963417289323d00bac1b1eea1771da524423f8392a45cda2d4d478c566495fd1dd1e662b9a6bd6ff3547a667674ebb28b3ef432b6c67249071e8248fe44d002bd8405b48be83f0021203b52c36f0e79ed3e990f3c2272739fb2b38c4827dee2d70895c4982af5d1b114482ec701ba98a96e49cca6e072f2c1aa1dc9acf4dddf87090f2aa66ee7f7bfc6d9213f392b89da90127bf09d65a99b20edecbb24303c71704054513eb78a05a54cf77b4c42ab56076f802b8372c12c70ae5ca305b34b1601edb538c69fb4e193e994cf1183e9f5bfd914b9b772b2f60dce7ebff29647d10d974589e43da6c5d9789f086067cb06eb0004d0ec04771e5db5a14103c37e318a9f3188b5c7a15a47dfbe5671a4eaf1e3ec2f287402310e1692cb4b26f3740004682cab9e6f2c4c60692ea799d4a6da5989739d77720ef4b879d7aa76078649b0093b74083f495b4fc76016b1ebc5fe133c104faa4c46c6cc385faa2e83b73c24ea3f97ff5807fe808ae2bd3bfffa34814e4f2f3460c6f664767e8cc1c257dc18a1c1e000985d614f50ad3ce72ec4311c20fca15e128cf9e8ab9a8cd0721380b3565a94136fc69b9ca2eb091b2a3aa10c20c711aa1ac3f687abf958033e338dc24c67fb7839d999ae83f3d41812461a2cb711573672740ee719557789394725784e5100012aa54e4f18e596480d8287c72dcd481672b275eb200c460e15c20be536a95daf4205bc8c20df2f02f0c0915e196501c400f999bbfff862282f42af0cc8baacf8015314e11f5a463c39ad15828d14079b72480d66f6f4f5a4bc64e3ccb9ba9a80ebb25d804bfd1ef9a2d29d5cf58a0f2c5a34cfe0f610f93e529b1cea1ff9dd6e33ac4c9fad0ff28e88b5849c4541f23a4f17100e5d10d7c4cd1dc6857d833a1c77dfa3cc2140c725f4cfb0bbf07041cc5770bfcebab8b53afd3c72214a13719389188692f6c44ec5cbcf72c5f5e1c1a2729384e5289289d975f0d818c68441ad1646b7ff9a8f39f7d8005ff822ddc75572184ed106e3c35d6883e01e87c151a22a84c000958e94250606dadda2a0f71f2a2293aa20c0a160b5e5a921421dddd9fc1b15638ea1fdea9ed24fabcfe703de72d8134dbbad311c96e67066218399e4025bdd6fc90c4ba3c352533500bc4e430efa8b30efa432db4b4bece4f731ed87362d9036975b8ccce7b845b18273bac972c592237ae57f4d57de45fc6e5a0485aecfdf1c9bcae59b6dfa6aabcae21fa256da575a202b2f09c2ece7dfc9a89b79d0176297306a825524a6331c87ce4cee7290bac9fdc3d96c9d461eb45eb179adbae9f0ff74b34ae61e504e739361d999729c0d46a251c6c30c294d58115715e0cb31c2212084fcfbf87a18e0b3c82ccf22c5327cd2df7db7b462a2d0e8f6f95dfcef0e195f07b21975da0288dfd4546e72406eb5a5124fa8126f0a0a3f3bffd1287318d04a208a7d8aa3b0b5a33f05e972b82c3e993cee97d8f9cf96a021efda049acde75039dc7c271bdc888c8fdfc91530dc7bd41bec32e2de13701f364b80f3852dd95a5a49d90c1db3408adeb3a672195c85458d8450f63f0eda3c123e538b757c9d78d1255f123433dced3a08e90c24e738d246365482aa58cf7b4c25fccb6c43b74a353e1559a76f998ff1cdfa72f347001b472a1d5bc1f33da6cabff6aa2c6d59f75d99e759da37c5166411613f23a772266153cb6ba3a93f67f7e8f54293e1c86be35e90c35f48745d69b8f472a3527745fd098a82238f756e908faa2c680ad226a16048c95c96e49be645ab72dd4c5e3fb79d631ffde01240eb7386142b3843eb5f50391c18534debc814582b674d818afface845299711cc894f68812ef24c3d16a327ce580f11798a3d761e8ff2443444c176cb5d988b3b38258e421e88be6cda1ca2dd92c803de75a0c31d06bc49b670c7fd100c8330b9255d710895529b503d7e431aff3da1705baa4a72b251896b1d911e69cddde613636486845ab8e00252f62a749a73510386b89f720142d03fa7d5dd99f5d5399dd89153325457402b575943fc10744e2511078e2eb3d7559bef1211391e1ca9ea13a2e9918ebd6b3c0e7da8118b6301e18ead3672a21c9515143d33b9e78ce6e51aafe06ac22c7ed158823037e05c3911e96e4b72d048b54c5e7dcf3919befd0f1be6caa937c915c5b414700633ea93899c587972029526262d0015d9be47e1308ecd06283b061f15d05a89445f9b0d55e801a92c74b7641c0fca11c174330e97b1c46d43bf8c54859833f467824df5617be40e726c7e25dc9be51b12c70bc31a1f581aa82c05b83b218961d86def13294ad33f728c81865ab0c4ccf683e33c96a37319fdcd45525b3c152a1beb40898850526820f5d986a61200fb7627248dc06eab87637de79291861876d0de364fb3e45280722ee97677727861ff19aee4b30cc89c336a56fffa09ad121ee2d4780d29767a076d9b431935bfae839e17c2ecf531aa297e1e7c9afe29837c03f3443e8bae73724fb89c5894cc4abfb60b06a9add4ff0cd588b9a46246e0016ead036a7204ab72e9be0d5146c2f404f66a1bdd3d7b35584094f434d46d63ebb30679584770ee72d69cdfc6de88d28aafbc426ff304d804920ab590bf7b0c312df554196effc4722a5d71c5fa00b09fc34b56da7350753d6353836ec120eb3706271c1fe8477272a57a191d1c19eac1b791aa9c3aa2a1560f128f0b3e763c33f38daed99924187232d01ac49fdb32e307a4640d2e8517c5c8d3df43b4c314e97a3c0afeab9d65722fb0e0e9826fb527916795df96a3c293c7b949a5c3bb7d6e551f96d27e96cf725643de529fdc30ece9c2f0a43ee8782189790c30110a283f9ef165a6eb537b1669391ec59871f30a3f1e1df4f4ccc19b85962d0b139a892d805d70dab796e972458b028feade8688bc45931fd36b2214c2b116beb9c8267eed16fe3dfd139772582e419e62b2a5d7d36da89fea9a14dad8cb362632368d3f1408f42805787626082dab05344998e236cff970a73d7beeceb2b7d0d46aa6143854a65b0256007295744395cf905c2602efce4bddabb71afde4edf4e07166073c49f87631a120453a4589cf0965519b9388e822c5028e94e9385eff568309fd78ba61dd02302d29e8d48ef6f26d6782de0464491449460ffc2ee628790c80eae37b36d15f16aa727399cc31ce22e98bacb0d741d0371e0e981a2aea484ebe8e19baab3ba7301926a57a919b1619f2d086340ee1f8eff6298f1631131a074b199fceeef8808ff2723ecb3953ad6f71897c91f1527aabb121a54879434ab3394b366aa94b024ae7683aa052a25a89a4f2bfc9dba024c8fb123996cc5c0ad13eba7d82a3b2691587728fbf36e81c74b8940cb2be525a5b3ce230e2f80e9b692aea5c60df3e3954ec4f471ccc898d24893c87bb5e3bf4e59a0f89e29146bb84953e41ede57df2032f2ba8148bb7fe84de78aeaf0176aa22de45e5d5fa7a465614d05bfd905d8cf2284065d77b0ed98d257b1941694e51d1767e41f3930122b542ee34d5b488f9b3d172afb0788e12116afe0cf4df13d980aa902396e8c8ba198bd653b1428ff50e497233a5f87bba8fb76ba15b7aa09530893ff5d84f1e7458864c4cafde3399509672902331e57e2da38678fb9698f43da4da765456dc7549d2e8a5bb1150d5808d72d63bdffe67de8175039bbf269062f4fcc85559e90730acb2c6accfaa546e4872287eaeb81f118f2004a6939df74b4101b95ab93da2c5e24284da4b04a8de47262f8f998bfba9eede9fc24b8036e3c6ee169f52534d75f52dfe64c5140ea07772a844e3bc936269c7bc762b7b11802786fd473458d421189fe060ce2eee4307452cf736bb84f8d232e82f238a7fc7a8effcbc0ed339a506cb2b8a239fe428bc234803e838d65605f802f82abf88dcd57a94b82fad446bbf66f4927e084a97a072bcbeb4f683be663e5a13d7dfa5c9ad55584baad49f22a74884f5ad7d1940310941d3aced13da889590cac94e0a70e01f11124b4e973b377330119d686bf4553c589f7e9f8b3df2d95b7024594edc76d08567f7df6e44bdc9513ed3cad19b1b1afd8bf34079bdfa018d42cfea0a578c9418297a47c154dea9e3f1f901bd62a07222470f00d661163760465d03bd40891e310108c500466eb19b29fcbdb08ae91ec588609a979974fc5e48f50d14d4fe0de00390f5994defd8d0a28c5a375eb872f028048b0e164e8b303d62ffe40ba940584587862cafdb9bb234ea1b11dea1720b50e67b0b725c315017f673d947fec27ea6c5e8128b1d6d807b7fbcc6d0dd07d19a7b5aefca5e77cf3d6ad3da1e303baf1c0d8d6e0a918672fc2290cdc61472b922f2e00b9ec4e0da7bef71b8091208eb74b2ef268c273a9e1d906b54515472f3273bb503ea2b00622abc42d61d6fd717b0889f69be5b2f37727e3382bb3e6f4fc8f3461b2bc45b89cdaa0aed5406f662488c5fa8012ab4597a9fcb87a8401ec162fd41c9632c3e78e2a5b6285b9a925aa9aa380a3a58c9f2d56d46052af87210e4021df3d27d5d48acbb24b923a52022f85edf7bfd1386a2b423bfdc4b5572a2979a9dccd22336a4c29d324fbbaa49506aa3e3570509db173d46955e624c638a80e6adf7e38bfbb385995bb5fa52d4999693864db5d7b0f6955e006a0d2b133cad0ffc5e49b85f2c3a5212132cb2452aea0cb33132c911c904a5d33a40932a6a61afdb71edbf77679baba6d9483606117a90c9c5fa0b4dad4459df54803172fcdbd90a817d043bcf09a50e8eda0fe56bb39832d53ddcc17d9d9ca7a8f35c7212f9d011044f370cc0f557c31279d0d8a0cccb279175f8dc0c2cb1b43105857269b230a4b5866bbf851e5d51468da864d17fb76ee1a194d74ae30101a0f4b164741b6e9194a98315419195d2a55b0f682b2dedebadddc8697029fb33cb0c6c722074a1e6058dde9d5e33796649d4388bf81aac88efa99ae1da41eb46ccbe887280190e43334acbdea081e289e3e1aaf41ca80bda5a017b9e506c2da54ecfd472aef18984191fe9b4fcaa266634741f246d7cf690709c47c8425c4b507c3212143bf64c3c761d933aaf2dbe17e11c7e57a54cfd0883756dcbf7afacf73eff3f5085ab65c9648e9331671632a0d228581262ecdf93237d2a11584f7c6e9fb26272951a02fa5014ccd2e9eccd887899c2beb86a7f99d82a36d3fb78ebc04a31f272f7704b0e8d25bef1b218886ac387c9fe7155408923d1124c96196f296060c71dac87d444f3eb68799d20fe46daeffa65d761566faee538907c2beee37e8bc072df3ba15499f2dd7e65071631c3e08109f00e25afbd30704aabfbbd1fa56c1e72526ecd4f61b5c9c95c566f8453c1f5d0f165bc9888f76065cfcc3f8673538821fd07afba2c3c0342a735933ede0263d48ec415cc812f1a50e4517a665ba6df2ec50a1556af43bd5f749b9d612ee81962e4e0e54506cf8baeda5acd562c917b721526f5e29245a7ae1d177e5c59053838d7257e66fe8692f26c733cdf2d170b30e9e3d4b8bc71afcd31f36d7d41012dcc03230222c1e37a37eb6ea62ca525f87283b7cfba580f89c422e031903bca101fd191f1d01421c8cfc0a98f756e83d172a40767fcc64c1b5477648a3cbbb3392adb157717ef06eabded0916ac8d1585163e349f8f3ebb289ffe2f9b13edfe17d9c6e0b82366c2207541794b3ad148ac7214a58e72264a3e4892512b4d09d11912b738cb33f00efe30e638fe19680e7372234d7a4202814ccf44fbe7614826489d5a2af2984b31e39b2cd5db6e254c4d727597cce643e0d6c408764220587b754d3b4115b721039ad1cae67d094c805a72086ef4b750cbc289fe118bfe2d94331f6341229298f371701f370fb03a33277225e89266555c566d5a2aee4675fd67d852c4d73d799c831dfab6507c7a682d608d6c65483d54e06bd8e22b7a79fc371703a5ba354c48ff0d98a761d538808172a16064f05f8fc890802a2ed394261869455bfc3c0aa521cbd8c59d9c55dd51555ecbb32be662f924d419f422597f68efc7f1676aa7ea2020ae4d8acdac17b972449126b90e47f8af3e5ac5a0f0e01cdc3bbe80ff9526064abf139e0e89aa0e380eb47b79c3017388c534cd0798da21cf97440afa4608cbe0adbb26b214a64672394fcb2248087b02699faeed4652077f8d20b215789c9a0dfe8d609e024a7c72bf4b34fc7308588234a24935a125ef7a32bb7cdd97c70393372764c874d66f104470dd7f01c7518ae831fb25c1d43c923adf6331d3324300a3949f077a3045723ee46353384d4d8803791353c7433ed2c8a74daad6c79f6acc738c0fb3f22c725999804b3256ce237b7d322133bb093ac007b00867d194e4d7beead11fad740fdb111666860b53375cc80c7a91cf9de0144739f48bf6bbca31026a17683ae43dfcefe3c94b9bff71ac3f47e0a1735b6b368fc74ad019254779c8887d7c7cfc5a4e472da7743e32a6b8a1b3ce119ad26d92a1699f0b3da034e2e4c0c6ece09b726edc81c168299ebcb6ed63ddefd19dee3d3be5c39d9e98fbd8056ae932a6e272b7647e55185df19a29b5733f0694e471a5d32c9275d01c8b99946af5c163681a0a1b0a85c65e5c4a52ef9b7e7c6e056cb7a2c4913b950d74f976668d497e4f006697105f2eb288d01149fcaec70413444ff8f95b037c2d6a4ff86a9e5ef0c372a4fd7b1d2a191e5c051c80d0822cf412a57ce74d5c73d4297ae4ca626d7b26720a357b549a30b61f73536a21532d8380d991b643f385b68d4f011160a8b0dd201951028d88ae5da8de72fca09caed2b5b1ac4a53a793f746a280846ce195d0666764c9d488ffb8681254308da903af247be8ee3736f8f181c71458605af19672feaca18f4aaaeb1b2f78bb7bfe0c9987c946e8f56de27490b074a21092fd1672a0a26a20a398097009ae3a24bb8126425f687c34f6957a3e07be14d05dfb82302e74fc9d25e7f6eb921010c99e828e10ae17fea4216fe3c99f9ba8b4e6f8d80ba82754b53c55bdf584c1c7068cbdf754471f7742d444c5362cf38d1ed18ae35a5771b076fcd20e47be3bc261bb37f66f05f55d5ed6f1df665770e50c293aea7280ebe61fd91e4204b028b6f78c3e06e3dc59e47bfe3ecbba012fd7e4e82f88513a739836da2536ffd6ab9e71ef4a894e95bcebbc9ffe862590b7258f44a05672691f13e5fbb1325c8a4c560872569f5f314ef7365542c36af41a4dd2ff100572729af967e09a037454a403c8c3fe9d1297a2483ca39ea9292a5bfffe17c46a6f272b2359a3b6e0fd7723012443b24f9d38405c4e2554806b4682a521c43d7372618fcb78e4c7037d07686a94ec2ab7909a92533f99bc80d60182c92072e37f0822d278009a04cac49f5e4de7cd492d5374b964a5370699f03e1a43f7b6fb2f725f85a07f1c7f4ede3cf273665d04f271ca4fd98a78ad94f5528a872bbfcaf672446fc60c8d10bc9466bc47f70e914ea822515a69366bcef1a82922a248ab9272eff685fe39f0a254fb1ff66b8cf1ac3f233d6b80b2d3c5e2df313d2bf9990c0a2ab6c85abc950964579ec0190b0cbb0ed24ba3e6e10b6d70ff5a074b1db745723609dc8860cefbe7ba91a710c25a7002fbad32fd15d69e9107423908dd88be726002ca5c864fb1338359d07614f638093765589aebed6958fa16e58241ff217275e910e0887ea36c3374e4798763f17eaabf812e3fc974c1551982d7693c8664ff7f8353c5f9481f1683ea35d89257ba032691fb0e68305ad7830f0fe5d8d13423e151a61af60f27969cfaa350e4128c4bad8b12a0c4098cafca9e86f1ebd2724e2f1e2baf8df861ed000e43966c5d52e23669eee5d8e338272ded5af8924a7269f5f6f5b84a50fba1fa74135acf3bc06f360bf34f2ce25923149aad0e1969360075d8abdc8af365a0dc1438237267a58db2869c7c5c3bbb5ce1d142858ed42f36c5fc5841aea61ef9d2eba229341c25da7167f0a8ecda451a7a3606b2bfdd72e43af9596380d964a84abfbcb376c8cf4b94ff2a4f417a48459b7e58980a3e58f494ce16277a5799a79c6c10db7bebc811bd6e616f926f62862de333c5223f684c5969a29ad6a1d8d5651fd78f4b15c553923ebfee9b3dc97d5ded64e1fb2e4cf072db78a13497f26a00346622be78c1c519c82d94fa10511d43d8e138a4c131faf84e8bdb7f3d15e77913170a4db8b0027f6bae0734349809dc59ea1483c83ed4164e8ecbf922cfe05ae7742e98eb95f6514046dc2e518cabd92eb4322a9e721b3d3fd3d8e640129362de44ce7815dd19dba7f9df16d22db174d3b26d9fcf72634f95bc39f932467c5052beca266c9175857e45363bc3649a068b8fab390772d3b90dc794c222d5eb97d4767f14c63bab6289021b82dc5dcbfba3929fbc7f000ba3e597eea8cf37e79a359c4373a633a98b7657622be983bfd2221c76772872f9e0e24e95a007e3d51de12e566083d104970d59ed9fcbfd73e16ea945d1ff6cb3e6854bd8ca2b7642559af9114125d3196fed7593a3d7b50d9252437b9b42726d8f73a3e699d91344d11f05b27d7338c9ac19c5dd739ed961d3ea5e51667b514e4c4da70dce745ea7fa3bf7ee439266c266ae24f68ee9115f417df65799ec7251632a9d01f57bccaf7331414f084c6cc9411d232cb82cfd2c67176084347472903c2fb54a4f3d038d85dfdc223dc51755d0f6024b1745d5079c3f63b9154f72721b6cfe17ccee8ba6705025e4d6df99352a68e2e1bbd3e70ceb700dc91dc27273d4e0cc5c5eb2e9b2d091c85370128e3b42d5cd41f4ae0dcf84b862f2176172913412262df8e1eb21cc1015406aae5a6dc35e8e13011d9c89365a7ecb0c3e3df1be0bebdf9cda7eeeae6911e6a79de1860ba0dbdd1af93698f2c783d5593472da148c1505d793458b306c16b22a47ea3e4636d088fdc8139558b321e360a5728469c3c4924b5c256c4fce84c25f04465a2c73fe3bb080cfa5c57afbd215d143c9802314941c74c633638ab033af99cd3eb77aa0781d393fc2492789f80c6d724ae4a066adfbf08fc7fdd52a8b6eca79dd615028f83b61e9744a4946d03245725f7d9b57643912b68ff5430101ffdfbf2c5332775c0bf293835d4df6f0419f2303b0f6b639273c6c9c1e103e8e8ea652134dc3ddc588374710067c5c81c8151a5d87d422beef9476440aafe0b386e746997e85c210dcd5fb7df5c171e88eee7249fae714b03f56ce2953ca807392e306c9fbb7d1ffd6569b72870e1df4428e724ca92e6a87222ade129e68fefb3bcd1000b741683072756755d884a9e8032d5b6a4629597607aaabc232192008b331a1455b5b9ecf543c4d01b72292f85a624bbbfdb3347063e60f188daf9929fb8c76de84a505263e28137d1695fb20e232726e1beb6d7a08fdfe814db8f9d066b1e8b7c8102bb841db39bf354430cefc5e53a99974f774bb3fbcca445571ba94d89f8ede44523cbd6e284f9231543dec7b720a9011efd51525d108ab475b41e07b58f56727f78f7edaf5a4a9a5911adafe6d6717c0c278c99f722ab750ecac1946b699bbe0c533b593249e52e7c794bc08729fbf45c1763e5a2daf43758b7014ab32c8972d12ffa308a533c04f24f8561c72611005793b5a8e0e02d3fcaa2a452cdba3e753978ec4dcc4e1cc14785e02fb37b2c78e24955dd7a38e88fbb857ff07fc8beb39c9ea0542f364406c9db085ac72f23c48d31fcf8dd7732a2fb9fc26e18f261f0cd4d766cc1eb2d1fd5ee865de595355b9bc336eeeb79bf38c29fa2d641d0d47bd8357b2e48137180e1fd89c3d26e231483d63dc09dcdc74e5786c6154f791c5e6c1e62f2ed7d02f7267f74d4e3eca48b27ab84a81e2c8e24bfa54b24d8e3ad436c0f05fdcdabb9b15f81d2c7e725590ad8397bc942ce688a40ba63589528122519c35ea47ce3fefdd9153fde672a6866a6096fa28ac7c737189c6792e8c675d41151c9774254e130ee9edd4c472f9075a6521fcf65e96690091095954449ba7324ec6bf36559af88f430c5cb77292cd19175f6df14769f5d44d88bdc46de9263665e889406837523c84c1a95b72ecaef88297e14e979a52a45686607385958dd4c46eb62c44cfa81efefe31637271d07ddf3d221d4a727f19e7d6bf196346957f7cd7effa9aae49091a319ec40f376e7c54cdd509bc3ac4d84ee024e77602ea0ddb7b6a015b2aa08313d928eb7238b08f9a29f49cb2cfd771f9658994dff58fc19a66ecaab5b19debab476a8b4a40902e0f750744b4d28f390f32d91f5f4c27e6d6fc99136bc5dd9b872bd76972159b8fd06eafff59a3fb80bcd8f5e4f90c4af8e4f4fbb945422dd8a31553a6724288e6d7e1ec46424f7cc82f5ccf09d491c4332d9d8ccf5b843360cd909358727c6813aa98d980f5da73f2757c729eaa542462870bce5069d4f04323d1d8b0724bd48b86365cd019a4371fc623f4aaacbf8949d04a466561cf6e180a259ade72b4bba7b7a3c66ec6d4f3ce64f29e75bb2d21ac467a46494a4b8d8f1779f34772c0eb17a39db41dc6e48d5a9d29b9885208450b0d67a0c73e8e7c509f8fdf2f7297197f5ea9818c97d055b6084cab96c5fb979069268e1d933deda428bc8e9772d0960e2e23a72329c28e846df2586cc0c5cb39ee71ce412ad32ed748860e1c3fbd4ff9c9a4c182d14d78e8ac3519ea265a8b75231271c3f82e55129fd84b02723fe0206d282f96b432231ab4ca23d2d57b2efd0c2df67de17173ec613a58261e00fd7240906dc6c4b5374e8f2ed4a654412327daf89f87cd88ea63d469798972909cf6681c16c544329a1b8b17c667713510cef80bccec157247eacbe47f707223a1722edc4bf63e536fc4d4ebcb2491a9180cef896e0a47b2ec5b92ca30fd0a56cecdfd0a1fff9515308d8fd2000764f976499db6f08282b5698f722dfdaf728a2ae17a8c6b3219836985328fe7006d2a0af08565b0593f1ca43c67fd2ea87230f509aef09521999e7fc6110d0b05ba84280f2e482fbcbb45d818aded52bf28e3a64f58fb4fd03a86d50576b9680863998ba89b6e5dd1e3dbe4727fd3f0ff728a84a2fbca3eb111d293a1a3e45bc6725b7b16c3e53de009af6c463d0d179a101ad80deb1d0bd25f0b0af51419fe5b99e9f0aac9bd6e713f11b5d146768aa726b346f36aadf7d35e9e6a4edc600d5601d78ee873f8b04b69733d63b4abce4d725ebe13f17e089d409f7247c76b09e57bdf82c413f2fb102346efb0875629a372a1274ca24eb1e978dbfc9b4771e045686a3a3b6f977cfaa956aa3cbc7759a272aa460c2552ffc84fe2cf5843663563d1fb74d14492285d7be781db44c0b7f2729da1f95f5091ab7bf927b8c46a11cc4945ab9857e912c4991f71e0151b1f1370d59337da9b7ddef3dec6a518ec745e27c8e9a37f8736ddbfb5671966ef31ca214dd848c96c00385405808b04a47255ea0c9185767d40e30f798d3194034cf17267a540878a733eb495bc6c88f7244a8d42dd0781e5c9e092f6cf104d44b9f6219f3a8594a0e2f3367d8f012aa8e7a0aea0cdd35f3877f1f90059ab2ee3980e72ef33479796c09607e44b6013496612bfd9ab9f6df5b79e6592e39c64ed9c76724e744ce347607b23cb0db480c380cab43776eb08e6fe717abd4c206869881772fc83c25bcd9a1c3e79bdbf09bbbd32edbe7c4552e8723d06df36b436ee07cb146fa597fc1e9a040fa95deb29b57035fbeb4b266a9ab384aac8cdf073718c0e7248adbd4f6fe518cdae6dc5101531d1e7b63d1376d4f6270f61cbb4e3564cef04b32ee4cebc87440191ac13e4a5a56921383d8ed22de3884be6abdfb642cdc472ca72bf29f75846d0c3ddee7ef6f39e595f7059aa85c0231d693b843cbcefe17241978ed0941b6c82a75f730b2dbda9a500ddea07c66fdd80f9d6fc3fa7e6b248139f55c89456c0075c656dabdec41aaaee4ff6797b38dec2b1f164759b4fc13cef369486701e84840a2fbd6264b92a43de67f43140f47b6b97756a0e6855d0721bd0711323ce14580726062ba052a455aa7feccb9f0b9af7523fc20b39b19e45a7923a1a42d91405055b44772dd7597c1d8bf1ad25fc52009893cf50e210d972960119d4954a52a22f6f198360db9a42176ce295d47acef00019209fadd74d72d87a09ef2c2b4b735fede7ac8aedeb35343d71bd17baa7f008f531526de2843a661394b9e01b0d0f6d26c9c602bbcc02aafd83e0c337424e5617fea1a7802f72de36b564db9afbd71a0031fa7b5a3194dc284934b2abb31e7e7ae2410ac8ce683946163e9558895ce4653d7a794b32e467b25761ad9a9ef57ef2452cdcf9671d3a3ff01960b414c43ada5db2c8a4218e007469fa271db73737bdbe97614e4a725ac2af057eaf3ee70f15b8788da3c97eb16cf07a5e78c762c15bcd7d1bd886723e0b8bce3a2e757ad8ef4cd4b2c128e746df87755884d189fd4cf45fdc052471d09d30e208bc1ded1331a2243b1137f73a7e006f9bc89d4a11463d0babfc51723d805cc4c2120f652ff02fdd6cb247c7e65fb6c4384230b2a2544df0c80a37723172296148a677fe307f1325d203b87ed5bdabf55aa7c1b67458c013d9be1f726913027cfd3220508deb1584128bf7bf6d8ccdbd1722959670bc5a3cb6db4072f55e87f764a5b6882084573ee4e2f72fe6fdda53c167ee2ddf4a9fcd0438c5484503100deae9324d3e776f4632f6b814ff52d5aa743e20ac2e8f2d952a2da672455943ed2be962c3d168ecf483934b375abf6230c69f072c16f3a26ed97b7f729c568b5b0cadddabfa5e6935ec9f68f57412e6af5d76879db3bb65855faf49628feeee5cff3bc6edf59ad5e546e7f639e21232883c47de3b0045f5f5e3eae415ccbe028ba061fe5768c3208ab3d3f62ea54e9efe70c5432113fb402ce0cac5641fac26d1e11afe5605becf65401828daae3a58dfe4f1d179e3464452804d644f0457f7bdf159327ea635eeeef722d3a0a828ddad307a26dd7c134983e0cf18726d8ba2bed4503cf26d9ecb8dfd6693f32f8e002ded2a5346b0a3cfee3d470f72c690d605ed96ab9e7187b818a62c56e5fde7668efe541e8669317a502fb4fc72e7a8c9d1796c437a68d9e219a04b1f75262bf5f7cd1cbeb251a8739688c64d5bbd6fc422250a422ae790a0d1288b90c82464a4f0b85477f31ef1efc3b895107298be0ed8300facb7b2944aa1fd64d3d7691f324a6ab402504531f538b4773b7213fd34974bb10a42e8f1b78199ffda153d15f782753dea5dafb3c69990dfb25b7defae7744cf319446a0c59c307fe2c079f39a44aee9f7567f6532dfc75810720585fc635e4432744799bd48ec56b8c26485d57ba2604f7dda9093d21f43be727ed08d5a2a45c261c966d41cbe183d85cd258c64248010cd6c873a114603ba58df7ab8dd63a4aa3ddc9b192fce55b0419ae6de2be810ee6fbe42c6285187d972566a6baa470eb10ba1a5a91fd40043ec9b5f312fe84ea055ddc98e92ffcb77214a47232e5d2e2eaf5e325bfbe2aaad61bc45c9ce7df2b47078160aedab8eb172312a7c29c1a7db4a25fdbfc24b2e6f1591933e4276b430ff8c32c2f607c17472fd939d50c766781fc6ac7dea4e27c8a595d55d7ea03e3b75cb981d6fec92ae72efbe94ee95c8c40f60f2c44cc700fba2310303ae37765951abd716783b8a4328ad682e7d517dd8faacf8946f4e7a1498fa0be708494034c73bb740a5b6cb6172cb03dfc36150a3186718090c1a5d5a2babab7983c4efa624b3115b4170f78c1f9bb2ba4681dd26e96516b1dd2821798c992c3274deccf3fffec011b78796fa395dabc1ee8b224f6eb680c04a5cb5b8edb0ac1f3bb690e71eadec08906efe0472ed245236cd5002ec48ff324ced3e7587a81d1e8fcead4791e7279a9a4817a00407eed0cb8774462115ee1eeac78f068c91dfeb709f84637d1aa6937931dd6372ad7afc524853bcf31b7515c757380d8cc5cc2307d7385aab77f05877216c546c393531f45bb0b42995a6c464344147f2c55dd21bc077a8851f6b97e992b88a72bf3f560b85ba49511aef15a75a6e56c3edefa4efb987ec6679dd7852f75e7e7262aaa3ef056097fbdee4d798809c5f5e19c0db75ebee707cc2dcd79c695bfe727cc0a0472a88039fa36776cd8affe3906bf906d730da732050f5e46a6e9509052a6f166dc2d0bbdb6a26792db3500e07efe45fafecd2a58629f7634600734772e712931ac0bae0b9f47eaaf84ed063bbf2ff5965da30308bdcd0b63a6e33f17292c2db20b7bfca2d7b23dca0e5ddf7da177413f612b34d25afea47c48c2f9c225a878ffedf9bb896861c588fd84e8765ee3b5aa5352001302573c5d9ac936272f61691b51e43c36d3fec8a67765d154a40465ca0fdcb604f0210ad41d68a390acd490a01339e1635114f9a66348624c8f11e871a9f044e7af22c97edaa85a5720f1867dbc613314517d163f168328f750d0401f65c2e577b78b72ffb59d54a726da4e3b1b8329c625bb91d964e407e77593f6456e7f91dff6608428349358c72706d7df18f3949ca09846942812ce96af1ef36e4c8a1d1f9b56f549855554e72de9c536508365469e1f6f6720994f4e54a9f756e66ba460ce988e10a9c450672a728a8df1ab112f3ea0c306fc30637054731aedb0524f2336728d17925b57e72219b1203b7ad70542db4c9eec5b20d29ad1c9b7c7f10693a85e051eb32b29e72d41720a443ae76bd4c47e506704890c40e547471c7600952593081874b68457253da8cca6c6782c1bcd28f3a8ac3548036fb43ac10e27dadce45af473411d2160f6fe33307ebf577ddbc0f55303466808fa872e2d3a0117d6c6b9dafe67eef72569e9857267c1bcac9b2f61530fb7676192568c0c8cb1dfcd5e5fee9a2b19472d86f10e7880efb1798ef7ae3b5b3b9da34e9b3f654f5b3627d5646a5bc21067215b5a79df8c46eb76452ea354088ba70bd85aa4243b70fc273dfd112f5c4790a280ba83404a941deeeaaaba05cdf8c0ab76e85433050821d0716501d12fb3f72a353fdf77184d26ccff8686b9bb689d23b81bccf7dc92e84044914c2bca2dc07e6ddfe9a83796c713ae98695620393b2ae916dc846f128069bf0029797b286609443a2c63a82316f5a48c6ac3cd5b6b849d0bf9892603d7d93f153cec77c6e16d6ad350bcf7c39d64517965bdbc92ae8a616971638d780232cdf6ef1701be27253dce3afb94fdf0c66826c8100809841c588d80f2cbfd76911c10b04557b9e53d6d3dee22e947251d61c0c299790150d0d41ca4d4ef0f456d7e11f06cd4a166d04a2caab3c6795065e76a114799b8cc501739c1bc85b91d5e36b2ee957179c3606b4220f474664de883009efc0b24ce8bc657b9afef7c23b6ca210832ca2a27265193903e87f5b22d9e89a46d831366898f60c28edc64bff95d58696fc39fc72d98580998bd852a563c43b4a40327153eeb1101d048362a8f933284fca9d6e72007197042dbaf4090e50e800cd2ce51ee008c478d84e7436ceaf023e703e983f07137bd77710d358dbe71b19ae8557b596e851890eb9e7e91d473d2ba9431472964874d44fe5b921b8cc95c373f5bd062bf65f403107c06a5967b896a9e0b07272d50c69e3e9e56d57642914d7b39cb37499eafc01797df70d464d0500ff527273443c799f83c1785cbdefcfa27e1293fdbf9b56a5a2ae54d9a7b14af3287c236af258b86f45e8bfbb18dc6c98e138fc65692de6104c7f4e863c0ba4dffcb77266123dc6f8a774668efaa000f3ae1cfd4d421a7a04532f4ecba900648d0b05701af2e4e65a923ed0f260e7d024c649ef349f43b821aa7e2eb9e20f6b50a3383beec5d0c3502d02e226790e770c3218ed24d87afa77fda17c8252b7ea50c64b25ab313b1fd299b7fadbf5b8c2cef69fb11ff9ab150bf170f1ff36f7a954dc0b7042da8606ddfa6a75002040e4b0a960a8888faadf77d5d01dac18bd7ee6e3a77211dab418102a6fafa13a96b85985134b48345fdf5b8430754049c9d34bc50462316b05cc0337a29b5793d54a1bc92110558d31657f08dfcff843353c8f9cb624de3cab889a7f556dc24412c17c871da8a07a5cbfa970bff9daaf3466046853722bf4c6cd94019a6ba794e3f25a37246c8232a533aa34aa84ecd880d89b50de727bf0f21dd56c60abaf30e8bcb1426b5b7382a882615a93db0ca253b7bc94861f15ee606bf1ca298089f9a35ab408387ccfeca20386bd0f04cbda8131c39d0d729bf5f32d6b02c784da61d1173b05d5a813c38ae152033eb3af7917244578e92e4a67f3a80dfbc18d9c42eaac4fc8b7b5403ef8fb38a952d707b4c6d967fac27258022b9e7c84910ec6d39c6ca8c2095c4e9863c9c54883ad8b8697caac392572c7323d890e349d19939821ddc7ed30ce0ea25d81d7d1ecc6ac0c32c88f6e51723f10fc2bd709d5081b4d2cec934583b5a5ac6322c4343b9d26bd02622d5c0f7211d8b4d071b85b1e91ddc38b171ae020960f53cbb7a5bf8a42745d8e3a04267250dfedf7eb0b883f2b27f695e96f2f44cc92e0421ca5cda4c5d6b34728e5c607ad2f8e152a3e73c7b3133086e05f145cf2328456dffdd2ad0b87236d4ef20b727f7a411be2959fed49660971af0a0a7d9658b5af7c886645b563496a357d5e72e669b9340731d17e14fba1ae7ffb157e509326c8c2525cf71bc43cb111f2ea72f0a102d2de91a47087156b82d0a5219223a5673c7179c00dce347ec964d73b720db0edb66fc4f87f73ed97fbc145d48bf5bfda1fd3034dedbd68387fd2ba167255d8c6dec7ac1fbf761d4e7defbff4f26dd24e395345e6a8664704a1b2e5a47235307aa3bb43c969e13e9fe7d384aaec9b360f5a6406987a30823164a7903c72b9ce5e52eabd3130f69b59eb5259ba401998def7b3ae30992a4e0c7e6edbf843ca74a542c38877815f8f5fbf0abedb0830ae8028fe4f039e3c3f7d5dab51b5224b72527d35d8aa709915a72085148f51ac5a22bb901a1cdde0e00b9dcf6c6c3b28810f55e13d163d92b0c83dce54ff51d45d8e06108147088090fd65859c8a721b4e352b453027163fc6ea2cf9bf31607228d21e6efb779c3fa2b711baf424395f44aca2655366332753bf0afb142bb281328a28a85100b72b7c46271918567298f660b34414adc7ee4df3bf4634a63dc62aec725ab7ad04de2490dadccebe721f3b6378a316d94816210f8ad1db1da499546a37a76f2e734b3e00f091ee84725f7d5117d23f0b5210cb72c35918fa25682d690687d224fcdaca926490bf2a7287acabc12dc9854107e3e7e947b66048a85b29808015d2b9cf578b79a44de372273319b9b7497b0fc36b76e61072b8e05972d1225223618a3dbcc8a78d9db0726b347a2f313f362ed897db9bc892aa3df3597604700069824fe51a4c44ba297245bf42bf874921e2c10cc5e0da296c5196fa454a9fff3e2ddfed5b45b27e3a725a8f1f5acf184a833a1212429e4d10cb00634245b72f4642cafb3a2cc4b95c09d086754506546ab4ebe5b9c4222a643e1b5a4b26d9dd60d2d206c93ac4e1fc720506097303c4769e4d00a5e52eccc9609e25d688ef7b0709085f539cbfd951500281ada60833564f617d0d213b7d566408eb2bd7d8124611fb6215d7392fe87278c5969a0d8eb16fb13b9bb10d3b280f6369f8b5403efca0f9a436b6c2cce372c19be624b4a7edd353e34d79cf58057f4b4572438f63d1166915e90b684fd532745c8067dac7972a4499d47521dbcf6c1fb8cb3d08fc00e3f2dc7a899d940072c31da639ffe0e0ee68d07414f0803e229b7cdc181b347bfa0f5464dd8fbf3a42b3f880184471a25ffb8dc26f1f84c94eadd377d00bc0999fd4b770e31b54977246b89a7c40a0267712a35ebefd9416436a3208b3d07daab082f895572c08c572e2006d1406b604a8b972137f2e07bd87b68e2e4685c17425859c323b13621272d6c1d1426f46ccbb65cc192e7c09c66f09e7c86cb467fb77b747373c9a226b27393c2b080b6cbb5611521867f66f65ef8beb609c5ee969b90bab1484bdba557259df6b685b6cd475c027949249fc0bc986f563f5c16fe482388c1a486589b6720161c55ff2fc84cd96cde7a3988819fdf1460b67fc1b198a9bb4c163f0b763303d3ba645f705ebfbec94db2d74ab4ecac4c028d4882e15504c2d4d9d069bfd72d8509adc3f18b20d3da2ff256be7b667e42e106da22f99de502122fd985322539236e8873c85b2759fa3df1fb513b7378dbc92b57ec995460234dda908ca557270f4ae6865384a2537c81fb20d0a6c5ea3b17dbfd5207f77ec13059637ad8213ec34feb6b4616900e3a2a76d81cc983b5c6eec16b9dba9be494152db3d121323bc746c224a6902ae12e9c6f110f449e1cfad12d8fa8cfa55ab4d3a7a0fc0916f6d5b620dfee105ac80c06b797bb6ec1ecc55d89fe97223cf109a0aba8bc0f218809ba8830486fae8ce6bfdc1d9a82baf12d3c1e7e54206329140800dfd657f5db341c943bc60cdeea657412820e75744d1f14173a0719a28694d11ba99ba46724e1ae3f6d266405548c430559d73dacab2a22e4e12ad7aa3295f5fda8aeb5072a6ba47743b17762f12a422674d9f1de1d4095550e9af79cef7087e00defc4772cc321847ab7fb2f44f0973c42a5d9d8b59031cd234b2b3d6f88e9dcdc1df6e6949a3c2b3c54d4314a7c8694f20237417da2c8e0914a4e44b8a1b842610d5d572f307098604c0520be9c2884c909ed92648bf39a17685834cf5985206904e585cbcf0436b328dc586485c2be0c8866ce4c3dc5d838cb43c25492444cd733715721022d052452cf125c7bdcea48a88d6172a81525d277ce0f67933893f91979372fc379518666cfa1b54f2b0804397c5fbee05a99b4f6dc42db9eb7cad0bff1072ea8cebfc0a260a24928e7d6dbb3bde35b4d6bb1baa4b980a548a10e8b217fb500c6d4fe1fd33b21b90bdf1c64481c9a88394ef45517cbdf43e9012f07bed2f489c2f0c7c640e4cca9d9af441d8578d6d7fe7131b9e6ac3d8052c13d848984c72c258854c6ffc8d0e7886756bdbe6d760942ea4e2bf9a0c0c27c2e07f03d79172224106142461423e08575c37de0755aae6bdcee1c1c39ec61aba838eb5846d72c87d4bb617d01394d10679d4ed9b6239410c6f7fa556b104f8d65803bed78c725350e5c9551b9586f8fbf6026bcdb1aef40cceadd60bd11c94d742762d475830d68e04234d1658b05b1d619efc254d3e029d429a52eb0c4dd9c68094de9f8f72d4dcf6e1e96c60bbd8ab2f906c797e0f6b423c332c4f47062a30bba809fa1a72bdccf4f70b544539487bb54fe77e0643b4bc51082af4f06890fd455cac19817203238b8cb5d73273dd13dc1a6593bcd482ba73fa27fff053366df9f13822df15cc72ee260929ca34dada2e4d7e0c802d35c23b91d1d88d8df7238ef3cb8a2055311a629ed75b17d4d16d83897edfe1f1ab12d7890df62fd5aaef2453af8d0a06305b1b6a951301085ae07f362d4fe49dc416ae80b42b26c662658e6e4bbc99461fece6efb00d806dce5da575e2f697d8cd0252de93ba737466d8a77ea671da720cdb83c90bd0c24b5a1c229ae85760918d5623dbe22cf5cf258296a0e03f7e720dff2c069c7f7ffb2dcdba689e5be495121eb7186752e22752733327de586d2d74c49d3d5928e2a45af9d21545874f6d7dc2c8df15675406646cea5b2d3a4272177a9de49a5edb2268f0728777e0c2cffc43123df4b783e385927e59f4adaf57bb0908fe274334345659ed94b7ae2af37436e535f8412abd904900e430a4a13fc7f815c26bef85249e6a665e095b1656db5542fd188ddaa1a8a18214714de715edb2a322039a493c945943278874080aba06bfadcd5b3aaa7e9b33d71ee7f654069c0e1ba4c6c65390ec1ae7e9c1ab49df251d91031cf1626877a1f5da8ce072bb0be50db8aae4e80643693b0e5a4418689699e78230112d8551cfee7bdeaf0e7369cd6a4a5c858a702739014275da88a907e4aab2ce65718723806eae8dec7253240f86d989c77f12cccbc607b8b6f4d8c717cc771a97c3c84dcfb981cd78722d06bd56e8c804d40e492cb3d23bdf3c79ff7f3962797a5d4a074244665ac74202d99152ee1e09ad765ae395c05223fe92955d9baa5e566f3d08a5d7e043f472af073680629bb2d7e62a24e74a682d5bdd5a5e4e3f38786a829150cf83094072b741f03a6f0be6fa3b8d1dc29c36715998d22291915fe02bc7f50cdbb2041503e901df050f0bc7861c19c89f867e13733eb7766d3d9f6668f0b8cb5bcb97db0fde47b4d30ced3247893af9614bdbed2dec27e4086af1e5b25b9b49f820023b3d6116923978f3bd4c702dff6d5ab2404d81ba91189f8e1f7fd75a3766d677c87299eacd59cde5bbe3679d2aeae133c3ea78cefc698aa93086c19866f8f91e7cba02000072b1ff1a6cb1b23738647eb1ea40d8f14b037285e457214aab335874feb6e79e7265d726aafaca1376d02db1f02d5662ddde956b98c950ab9caa28fd8f7c4ef072a85df293078a2e19ed43b5dc2db402e9214129819cec6b3231740f22d112c02db6107320614bef434aa04032bca53142da7dc025065bf7b432bd4304056b3864f03c8c201dbd808be8acba7a769fed933e8571a4480ecf4e1e9fa6acbadda0728d88889e02caad14b1711768a1cd634faba1a2f1bd3ba7bcd5815ce808466a721b5f157e43f2099d3407ef28d0e7d756562157a62bbce519ba1d6424baf2276f58aa10ff293d674802082a06b733af548ea289744ecfbdc948c1e098407e5f3f04f014b764a9cc235a3297316f2302b0dcd16d625c5664f0d2cf6802f88b135ebf81f539b94c527322c9e008449d09ffedacfeb4b63720d26f9647ce2ca3872b260924885fdc2f600e7614c008bdefd90bcc9f2bc890f7f27ba608abcd722f4d6cbaf32eb7fa251bb9bc06be32495d4e9b5667cf78a3e6de64a0fc684784df72fedff016dd3346300ba47d415686e1d3c877d7f1f5526d750609e616f89eab1d2b34b80b68f2e5f9ba6a112d2e7803ea03c9e227871530424e8828d492ad6872777fcd95146adc65a2626eacdc909741bffbfcda51355ba17cbf0133edab7f3bce831e71090a27bc8fc5b37b20ebee95ff7c47725050eab1acad2d5b607c4772699619b1fc63c002af9aed49cfcb68e07cdac5d078d4dfcdfdeb0d9d057f1f6b3ffca32ade59abb8b0fbb612bc654c01d57dd08db05b182fdfb79fe453c8315c1ac5a7f7825c70c22be373442d9ca8be439295a77c48ccc23a4d71f02c833c72a04c10a2518ed562cfefedb145faf7aed69b5b8c3a01c8e2357324892a139619831e7d7ad3ae1ca3720bb0f9b292d3d6c9d22e78e2f81288faf7662063e2bb724623bc47a2e199eadbcaefe75d92717f6d3c788de64c9004c6d1792c81894c6d7605ea6aececf1e36abea5fb066c342b114489b130b6a3a29147e9837e49e472c50f1136cc86c4c9c0a991bd8c4f107b0e45a37968f853881cd703acfdc2a872cfc7d853f122c9aac6ac58cd4a177314b1494087326a506d6dd7b548d4c8637283045d3f21aa6d7c7db5c7fb1cedba9c99b0af9b732f45fd8f05d81343d67c3e9666e04e98e327dc4dc2ca265ca9fff8647e1d565398f535358127bacd0ae2725ad7bd66ee536effa345b3b841ab74581660e88c2a57d2d45e5cedc3df429644e10408c3bec50bfa364a291ce9b9887697a696e0f55a9c70c957b2a27720707288ade3f04e7559faf437dcd87e18e8fd976ba3fcbee75a173cd78e06e71d6872125bb934db3b0d2d6e80cf90fc7949ffefd5406369d21aa7c82d1b8ab16d8701c3da0f88e972f14c41b1943e327dbb18700fdb6308c263df6712a8fd9a4593723ae5f422976b0ef3cacdfc720ffe00a05e6d779483ec36578c3d73b75e13ca728309baa7be348bd0e2fce55fe1272d46d1dfd006b290bd56675a05126dd50418e1e7fbba4a1fcb2b8a47c711f3f24633342caab10a9c17bc9393e2657891de09e32fe03f70c639deb54c576983a1b64379606f35a6ad81922d9ebb68e7f2056924845d13735eb9cde88ab198371d004de8195c36f0b021b4cb67defb0dcb13729f8767ee72131eb91236f19d77f5f7bc313edfb6d0fca06e92f52af6925cf3386f792e8bd8ed527995379d9ddeacf9cc75737cbb3206a12f8283529adf9220721363e4a3f0caf5affed60050e2756bad2769f56fe7f559ec215a83404c24404b4d2369b7b7d3f3a6e0efc237b03a669e2d050da5870485d1b7bbd80c7dfc8a72d1151515d56af8bfda9b2593211b69c10e9e52f31f131c21eb531f2b90bee9720393ab4338560e7ce8f723dd145eaee872e5f1b94ade50e8cd9eb116e656726b416b2ba62646dfe0a6c6176511451d4a48dbb8cb5cbd45ea34ad75ff80646c72c80e4a0c5ee23e5ba0a022ae6e01ed71d576de437732b1bfe11d31ece41f82725ec61623ce67a21349e03dde755bf12f337ad419523e77016539f9929df8927222273088be9bf557b12f8e8b7e17abd5ffaa11bb2e2f7e669f2b962090b12772fb27d3ccfa66b24c23075414bd01fbdaaf9b676663852eae80c28de5b4917a72492817545c26ce12903b23256f69a1d547331e352c68ff153e3de6ea47e5f7720ae88a9cd9cecda83defd68750154740cf5ba7cc0f172e3a97f554909bea575a8aa88d5d17f56e12f1a37d505b67a9c20b9465bcb39be6b6841d2d14921c837290c83573fb06daeacccceab4bdb6b90e4dbe4396f4f6afef6b258010e3b3ba0d46be5de185759c38b68054ccc992190fc693cf3e0527fd67d3aa4668a5e16372ab1905b0eefa6e286d25894efcf2f1145666f22fd47e146aaba2a2185b696172a776999c4f7bf26c1f115a735c0a84b92cef910f9b6424925c267c0c0c2f7672fb7a82bf609b5b26342ce84ace3694c4b4f23ae1e258fb4eee6448cc2e39687226665037b7f9dffca931ef3c7d397d8ee84dde4de36753afa797fc069c3d77729049c0448f39a9bf56e841028d3945162c7e3406df73fe7cff028c9558f5e972d98e6b4c0c22080c2679555121e114401249892fe208e1a64b6abf523587fa72db42fb3a1ab788ca0022123ddf0d0886ebc25aac786bb8d71e48dd7f545a897211b6354e22cb46a49e9a282e912f3ff9b4c3ab9caf6a1997a86f40699c69d86be30f398cdb4aaf3e68329c2db3f72fac5073b918646b184cca80fa1d60c01d724075e3ac82560647213012e2f288d9e2a3d3d0000527f0893d8b44a6619b26729246607d37c2e4824ee78d211ab92dd2195169de4d313906a0f0bb0684905c72b0f5441836394b01b5bd3e7519af742682fcfe0c8470f47cbcb227747d8d695c5fab7db51c48ed2cc7a823063431fadacbffc61b3824385a2097e511868fe07273bafe9263ec406e9774dc73e7967b09d0276d08b54fc0998b64e6880acac072d2e3a62fbfbd74936c2dacec3e3c2da7ce8acaa615007c822e0d90f3fa0f1c72934f780e507732ea2a6027183809e3ab1bc69630a2dee39eec41197372e58125404a92d59c100b1509e123735f9bd87a92301d55cb31d076bad24e4dea2b6f72b4cc7dcbb887e88935790f244552c8f53a2f806300512c7a833432554bad7739e515ca91adb73baedfc07156f2bb5a562ad870d907665619e2ff1052038c5472a02eb79b6b848b9b0a90a41d04d359ff0582c4deda17b04ef8b8ab1b6278d872b4e925f415de1fdac8b316ecc44462b37f67a23f5e707f9dad69bdb09c64227202cf5a21aeceb30447695907219917d9d17bb4b73f323c696043b5e0eed27672a2b6bba933aab4246f07c5338382bec71d6f43bf59915270f535632afe2070115bc52cfaaaf019138ae6cf1b095463f6d7c569059ec3cc75ee4aba352fc4be5f3790808a20e83b6710b712915627a54ea13961d0517b936add0d895a5958a2721d108dbd539856caabee41879f8d78b630f70759c1d39fbed6e8e5e9fcf33a72d137aff8bdd47fb6aaa862732396c10c1fe38509b14b1894dbd621d383ddf9729491ab133f7948d93e61dd7bcad0b10b34db36b3d6631b663e43efd57e9b8459de80a4013671777b35a92cacd4b120c56599e374f0960fecff24418cd99cd8724ab419afc23b39ec8e5e41c4858af2058fd980f3e78bd286da5875084716dc72431e95615dd63e9e6f63d7adc6532a8b7dbc93f34d5403c82238639c3e92870bac0de004392a70f9a323ef7385ac72a63c63c90d362332bcc4c850fc579c2a7275308faf69d09fe85d8f086c7b21d1aff066272e772a5c828be17ab4ea3c2072f7cf75a955012c6e375cddce608a9dc2dd2ad22593ee061c189e710ae4c04d728b06f47b1371970d4e3a6f0da30e1e6524de0c62a0e1f84598eaf7d14d02987275ed4ec0642b3176ad33cf7c27033d279b4722b74536abebf527038dff6b6c7223023bfc2754f9c1272a5a07a0b3e0b3d44ce4c1d4e8001450af961fdce15372ff6812bc59e1af9ab85166f0f168dce0a800eabc4b271f2450f7574d98020f725f536f62b7a38ee113df60674227073fd9512edb64ae011eccf64427ad5be73471d764f12a0cec34a704d2ada77e8443fe6a7ad47432e42932b1e649d4a23c720f984446036f9d8eaa26e7ca1d99b10c07aa12789dddcdf08dc1ee07deb058729bebd8739761a2d6e10c2e3f97e9a3e0dc934dac524e00b94318abddca8be30d090e539ad2a87720adecd6771acc85ba93c44c5242648b3449931950f9a36e723d2725de7b8a444890c3ff1b698098032141e7cf95a712d942b89e1c60ede30efda880cb5f40c5721cc98c932b0b993b34ab2a935f3e54c58bf47347ee4f057290ad0628be48e3c0b915952cd0034851edc5e38176df622a2a07f95247c15f72830fc7519ce5b0653789e15603cf4329d4083c9b85edddcc9feb4cb8233d3c72b0081328645a584e540c245fcab4b5909b9039d9f2bb11fd35f91be82a980372f8c4c46a44697125f63326c7e05770f1c7e96d8b7bc59e0e4a79e0d601775c72a5ed076dc7910c440e43753c7a1c57db735cfe6ee4d91bd0dd4df381696a8047a843c885c71fff6d0d9bb565deb14fa5d3e5c19ce3463809c084d9b026f5407215f5042ebd70ad47842541b66629fdf11cc8b795e7a240d09071a0926784ab72de000481c0f26dd909f2c7252116a6b4d98125a0d3c6f0228e0049634d56bd72ef96de5842e1a4bae42e8e8785fd464f56ae966ba820b7a9ab6ac6073f58207220f308569aaee2f19b640ada8929c5dbca610ab62fd8cab615f71535e582c1721e388a9dd315ec70db040e8d65499e61a0904e36c9a5b036feca45e848c69272b81da93993181bae6c89a46299f09d410a604380e0596d7a63a1ea591ba56a1d880d1d7c3cb47f50ebfa005b2c4f0584e9bd33475a43c38b452b9592dcc1ec3ed3a843c97280baf65de8869750750ea0c60ae6ff28c50606a0ca65da38a5a0726cb41bd3ff1fb12fa31d97e8f6a9ef82682ca0a5ae6c7949ffa19cc52ea17c0355e53be5287793e5d77cf22517fb76bfca34ff7463772ebbe62dee55fbcf065d85a9c3334db78b70ffda738fcbf556a0d3f050a4e595be143f455599feb31872c22c51c752d0da5ebead79a1dc81bc9c306b887caa0b049c5281f99f512d956773a32aec45d0371e5162b89b8b553854a369443f87442a3bf8b200f0802fc6259b9dbd6017c63a67d5008857cdf1b0dd648cfe6b9927ed5cc40929b80c84ec6e45d00b2ba99c5b25043389d69342c6d55c922ce664b833120a390016f21e350a446173cf1791fe3e812c04a54713c93d879acbc543291827d265379e48ceae7239a5fc9c1e4eff4afcc4758414aca4ce84337b4d7a49b2b2c2d8a61179c6b072b4c3095a9c1cc9fcbae14914a5986f1982b97619df7fe07a992b3370082fd850c6f6fe8d7b54e73e37b19baf4a59bc365987830fe7297eaf841a99660d255a012edfe99db8d86dd738fb4b54c0130dcc710154204b2535eb1f5ce1e891a3c32b08534f0c332858e3d6569631d71666bbcc91fe0a808cb5df37a9b02c0b52a37295dc1ca338ee605b5606b5e0dff68f8a7acee5a52aba9a1148c3dca30217a072613e9cb8e7e8782ad128f82058dc5e8f32566d0c238b18bc0ccfc39c6c235d4b08b0d46ecbce8046c48d2a3831e72b628f97531e46966b3c590c6acd84f9f24dc2e367c3705581771fc4b291e2dfde462dd3bdc3d8aaf9e5d28036ba1fdb7c70c25ca8c68abbdbacc88ae27f617acf7722aa1a8a62e8e9566ab2ee5f7f1080720950aba311728803bf82a8456948ded754cfd8ffa809e0501b79726bebb91c5d15ab5312b5f6e3eeeabbaa34178d3457b9340e0bf65f1bd4ef1d8930c7895e72a8cf9b539eef3865ee117b79a53166087d62f547832cf201c57af1c0bbdb0d19c735495f137f246c89cd2c2af39cccde7a14ee56c4c98b4f7ddbc74d8b3645726fd799a5f524b3ee3b8e886c5f4ab453b7b177d5a223fd43ca5fb1bd023aef2e8648a324c56d3bd618c878220e226b824f4593c08b71c3429a3a7326c9a225722d73ac665a99239bf722392bdc2cec79eec6b795a69ba4b60b9ad2ed29da8b10020461ef1f6f893a0f6733b37c038d8c3ddcbbac516755731081b9f29ac417721e682e71dfd3c005642bd221374b274b0bd2b4326bfc4f0f28f8bee4c31bce722cba57bdb66afead8164e85ad76dad3599c6896f2a0a22321e522ff30039b07209429f5ffd2d559d4e85a4a8ce18cfc8ae5aa8a30912127dd996c526503da672d24808830c3f0f7168be3a4c4750f73cd02a06ec86b82207803e55e45723415f2d7e1d1a3bed5ebf36edf18c7f25dc4515e399b462082c6f8292614ec1fdf25bc0969835791462e5b36f7117e6a91a666e77d3d39876805955e2480400293e40c4bf45b2f4e2b95437d02a1179150cf15fc3387ac1253c1a635ed441d5cfc6449268aca6569acd7a873fcdbd714320882603382a0cef9b544db8bbe039ed8c72e5c1c2a544ada07ad88d9f3abe1a5691fe344848b0f8d74ca5c122f5c4189572fa8e00c876a5016f1add425e84b72f22e091eb67bf03ac0da441e02d45fa0661d30a2ae10293b7121b7d7d69783b9192a4cb99d51f3777ab2c328ac6b47d4f72d646c6339f3b37de319af06ec733cf61b83f274ec28254aeaf7af67174f7cb0e926e911d518d9c4f934a26665ded0ba9c9593c1ccd215feafa36af1ab350f5601877e1febf0a45b8a499c17f6ff9d42a328ed29c4a5fc9a8700c937d5a9a9822476f1dfbe61aa2782691d704bca51955352a7e8bd8f336e09e0abda23cade219d7947a533c117b9029f5f8da2f67c3a25aea1ccb938a5436d7a91a5aad0904727bd87a7993ba8018c630d1eae787ec1d15750b2c6de7f353b026a99473d0b32f3152c1116de975ed3190044fd41cd528f395d4ca960e8e3a105ed1c52ff0eb4d5bfdff1737910ff94f9c7c95faddbca0a1f614269733b93f63f0a3dceb25674cb2042db1904aa481669575b1395cc531c30acb2b1a6184ee9d0ec355cb4fe072efbab154d94b7d14c85a6cd9e205755b7889fdd4a1a3b349804e145625aa1e72e524b2dd182df97a421e5e12529d7c9de5d0274799bd0c91b6418b34a2c87672906daefb16918c009e65d5ccebec23045f9079f3d046ac672354c7fd68d15a33b99196faf8d5f14fb33afb415dc3d5878af94e8f23b03d81902733864146ba1817f39113be73260ca8e79b5e619fd178ad29e32e6d9760c7311a081d951cf47211f5bcb714a7044e41fac3a46fca095d6a39408860497dc5e4b883a29ae8837265fd49c3aa1413a439e4e67c125345cd8ba60784eb9268cb68dd786bcec4512e4d20300a6873c02230a89db7e0585a5f1c886823cad8c5e831d4db63b38e76726a84605f4ad88ede5737fa10ea13a77dc8465878e85d58717ca4079aac0f6b58111a43b9066cf3ea5344936d835f0ddb72b9f3ab8f2d085b7abceb9b1e365f4e6e1763284c475e3d80a74add584d61f99f27734b7fc3283fd51459e66d54f772df72968cb94d18c82d4d8928bb7e2c80840486bdf77f33aa64fa5c6f3660273b97db9058ce93a88a814bac1c756783eab5d8464af5906e6cdb9211e975ed1672e3ae8dd2f102dd0e4bc6361a043267a2a80b765de7f9e2bfbdaa9079233fd1722c2959cae98a79aa847ec33f81c26f0b7b28d3ed7656b78e5af719d329fc620a2bf2388b28ee3b06a416f844fdafa4bc6c7e0cbce9ce2832b14c3a6412a9ef727a83372f24586ff7c65277b434a8cd3d9f6b5d4264867aadacb0997f6ab2df72dc4fcf1a781cbceea05cd593aadf71a1f7d45dc5b154c78b971b6683cd6a5926421d334268c92e87b1bf805d4ec13868a7602db4880c8d979c8acad566afe853169c47642e1db6e795fc770bc030f3514db0d98ca8f2a83f0f757970b63981724ab6064ab5299c94a8f82582d79c8901a22a5924f2119decab7a803660ccfe1503ef6e78b1e5879307aa8eb6a2a219efad8bef651f8055eac8cc69f792284640903362dfef4f3fe880fe22e897f7b6e185464583ba93c3894ddb7f18ef14de6cca49b829b0ae4b9f2acff411159d033d259b099ae76550f515e0dc8f46c8bc1aeca2dd751c04fb8ffbbdcbe45dbf0a45ddef80a0e8e16bb6c13a472842a36c72024d5ce66e7e3fa2d8ebdfcd0e65e70064ff0102e448e298a6d1d13c22af8872ba7c701cef0899eb367ad73ea402ddbe2f4f1773d4afaca17ad6bfff3ba9507213eec51d15da000246dca95953d9c4267f37d2fa555f56a4eb097311ba79c2725012a606f93714b6d17818261efe36558ad96aa15ed03f3d82586e6918d47c33fe9ac49b996b5817a6303b79769bd57d38b4e6de522af4b46456679c56b18572fc29ea667b84b9c5186c7a80ed6379941a57de0f030ac92bbe325ef91efcaf44f4e7291e27a16ee67f549dba6a6a0c3148716f8d6cbdbe8bf00fd92a6bc09f722bb4e97f397ad3e7f3373335c0326342eb43f2d5467bdf115525d1df3d4b244a2f498914dd0ae15673da2331bfd737ee4a57debedf785623700aee9f84d737432493fea4c31a0f56f492ba10f854bbd1049c6115c4603a8817e8fb745e5fe17267721715289a8f4c9b78876aa479067723b85636ed340769da826e704fe6e17236975c82d8a0454def685529e262c90a3cab548dd7aa7531a05e5d51e0a63472cf83f62cd7a61375d9594177dcd8deb13beaf55acdd0b9cc6cf0244286ce5d72530dba887694006955872734e5d33ab18b1453e18f7548638c16ea4e55612d09c672c4a2749ec7d401753f7f88e43ed9461ea8726890525ab5ed470095b161725f941262ab116009897f5270501f1bea2d3121ce62978838f11170722262b304965226706ceabd6c0dd3f6535a1c7c6c538769aab2d2c357ca89f549057a2315d5ee03b052d958ce0a432525af5f6b1957e7a29d8f12d14fcc71ef23bdf40872dd5e58ce349b6e2d599df75f3ec3994865cdf84de6ecbe453663b41df6767872672b53ebf0e8da1588d0cb3cb5e49341870cabc7136166e7dec63dbf9cf73c6581e4a8818f3a53a113cb9d04eed70daa435498f4e976b668db33f6ecbc92914023e725da424b6e4d8ca737a8e5448e0cb1b3e5465bc3f174a409e83c15b661726cae47424d0a7436ddf1c941ebcd498df6c9e197ef779eccbfb5ca9a927b0272fe1b3846a7a8c8c804e8e8262eee820423cb43c4dd89e7493e33e173e8fe8059c0934524607bc4f796580a24c77ef4cfa820a82d08f88e66cba463fed6493772913d3d0c36f035ac2bf2b2416ea18bf498c4fd5abc9079f68d0b19c98a0e0372aa7760bbd1b7a3d09c980832abc8b7cf826d2711855490cec8cb5fa40dc16d1a5c1e31c028fc2c07bf8d16aaa557d55a8bb908c0a0787927673f2f02b9b4d108dba411dbdf4ae016b80232774edbeb7ab459c5292b07e1ef0f6a61a0bace1a72853487a1d37c3e63bfdb08aee6de951cc560c3dd70352f7832d4b1c4d0fc58684c5ab82f649ac000aee6fee2f0547bebbe920161c8d32906dce37d54eb8081321b1440d5eb15b9a9d836a64aebcdc2114a47d23c4038810f3e77593b3012d172e0abd8c7d3bd7e18f00d714fd1bb4d39224961119276e42fcd2c6575cd44de4960cb71e1b32c5d83e0934f052d61344968fc027525322ae170641519c3490f30c10e234d0c7fad2a586be1550ee42e61bc9f1567e8527adf98b871e1971e584aad1b0527aa3dbed1e9bdd128028d76cfb7ad87c70486d95eee69cba91e30f30f709ea70e31582b0f1ad0900e5ea5682ba3d6ff57df20b6b920a4c3965eb5cd02b844f880b32d664c40dfcb08945a1b1386aabe517979882abfddae76e74181728fe3e4cbbcd1834a2a2fb6ab4a3ae127c9f9f8b24c6eb5ac08653c8c0338435e3c2e3292a7e45de7916781538fbe007fac7ad1cefccea7ef29e7c73d4a8943722203a20621a591e9e9e3f8bc9cb7227fa90a05bcf7d360a173a4e32165aa4d0c34e578925b4431d3fe2115f1dfa6a8f8e9614a73b621de27e075efe037e0df18c892c370c794ac16f0c623c53acd16cf30a7de761ed639ab566e876de6e623722a50852a52d58f39329f988ff371f51b506ffbd5d00527370dcffde3afba4972033b9f0e290de33e1bc948c4de33b9627a4e35b54f3438f8f0ca4216decd336eeeee7685ccfaf8ea6dbf16a0816aa3220164932a1f3dc42a41d93f8c86e53d72ffdb2e737b5e6174a5119503ae788c40d171aefcff00ca8b1a45ab83eaa1a2721b0effc37ab44432d7ecc299572e2451e099ec0ba24fe2833dfb691037b6c8726a3ebe3e78156fc8691daf6533c822ee65ced625183536ec22a7b65edf86164043b093c11a2fc76f223fe30da9f42883dcaa7f33900688e9572bea23bde8e172cba9554102eede675c95750f75016ad70f53202884265b43ccbcee3419c194379a16f90ec00cdb465f682b5b3eb0001edd94fafe745233b66c90c7a233fafa7273e17e34c7d81febae02a84454fc111694830b4c967827717f5974a84bc3f249480077dd14b90bb961ab4f4a495649ceb00840672627146c13f446c4e5c02e4ec86d44badb0d36e01e15a4dbdd35d856af95092fca5028a9ad13954f62a4876b24b654c00d14e5b01894eb1bdc869c6dd26da68a64580cb2750cce1e8392bd3e857ac10d056215a172120c48168a7d5af0708188847e90c7f9580403178af2685bd7ec7309c707d50867791aefe59b3c34d20ee8b573719bc6c263737e5208606f3843ecb68ebe82a5e598bd171a2c8d715b288f63b98604cc3e6eda2dae00613e9d6368fdc4231922b46609da1ece31a6f01a2be8c55f3dc7ce7f9719cf387243a179d31c89aff6a9fc36c2fac6c8d14556a8d9b771a661a15025680f0fff7238ac6211c44a2db9a44bb63ef408d0d770993619606c743421f62511883d4772874c3a2fb0c3c642852ad43d9ea5ac80e0d6d23fc151ac367261aa1a5589cc723a2ea0b753d3465a658b0816269b0ade04c685a440c36c5897dfcb7558259f72dc6b22b652841440dd974e5b30be1682f2b4f3e470a1650be466429e5490ec3720442b8d5ee32e44d80e28c1da528061b54c43bc90295a4c409d0eb4c7ce25725c8227c050028a223555be4fe3996b55e78e465a330b3e41308a18694cfb5372ccb1c7d8f6b078360cadf7cbcb397ae75f4bb9f41bb57886b246de16fb1e2872f2a3d69ad347ac9c7cac9f9c0a8123fc3bda437498872fa318b4649dcd4a5b3e0f985ad276cd7e796b807ce095f087d38afba1c6be896b1597167609ac711c5907451fc1b43c1b422333c701b695caf1124786f096717dd88aa3f2d82ad41b72464cd8f9136b9899414498eaa658d6dae3e8fca834ee9a3ee5692b716be8033cd0955d0231376fb06c5775ccb0b2fde2df17050b97fa56d0746bd5b55a285772d54f0da28ce514de12261329e79d7e0864b6e7fd8a13d1980322bbb6e88d597273484e7119ddb29e904d81d30036535cee3fab16f48c16835fa2ed11a0e316723f651c20535263fed7c76dd93df43ae398321a1c0a3d9b8437a2ffbc69a987720c445b34087eb0d9f566acf72bca6a5f4643efd5ca47ccfd8169851e1823ff726ecb49b38c64b63b69582397275b0bc126d3f305025250032f994d2f218cbf2cf732515e79a4b45ebde897e528e88bb573b24da623eb91f1483157edb725e06348374b3ff9b3cd7040dc8f9cb234f2c41a2a6106764a6eb4203781aff8110e2040c0cbf1cfe8a2f8b745c57e356cc6df811c7da556297f7c497ad0cdfe8d36725d14791c5968ef142dd439f00df9f7946a7bacf57afdb975eb8f55930a35ad43eda8fbc9f3877468e2d16389a640893bb9026bf0e6517846ab1733d5ce4e4b275a8ea88d97861ca0bd88a8602ce8c6d348897c00aaa2f2e226c9a63ae9ffba447304f15460a9d2069fa5a0da32de1f66c81dd1b99bb503c4567acace36949b2223270a1bb926a24e4df582283274cf472a693b77e3f4a9c99f88493db3316b3d87abb5b0ba545da7e1fd0b5ff6ed944258ee382b7ee0a4cc326ed2dbe40b8b0f5e08f6e973b6e9c9e091a83be633597e27a01889818ba5780a185e29a5207725b036507aa4a8a786ceb4a09f572cc48df67d7d866bb85560412201b2f8c09b72dd525db976d1e8622a0886c72eebbb52a9f0ef0671f219f42184ac0097fc6d727c9852273063b25880efca473e4d72cba039b2c27361ebda2cf95f37d6c36712f39585d030f585f5c50d3b19c1cb3d8632a3d1012639f3f44c783b720b704b72e1b74fe769873ecd8d0c68810f2265ace7ae7c9644af7a79ffcfe4bf919884729a3713962611cb263ae4d024bad674d3ab8d831131bb8496839c10a4e29e2a3bd6030dd804ae2002b5bde5a4bb0808fbfea98c48f2a231705815908dfb2770720a6e8f58caf717fb5334315bf21dc1433b0531bb4e5010642e0cd1c3099d3a6984efeeb718499dd5a06a303bd7519e5f6cbfc20114e06d9631a2a9de436e314c14ca886a028ea56a1d4f65c00d2c14a2e2037b8e0b4a0bd5d53ae5925b302f7255dbdf08dc0b7230777d7455bc7d28041bb65e84aae51973d50ed9a9f15b496524f5adcd565399feccb6be8f1e24039705138a2412d2afe9f8feffcee0c4b61ba0673c86bdd34bb324da0bb1e769ff206867b9374bb71dc2aa2c2b73a09d03728af7a0d9146032c110b479839c91d00618c582c3d538fbad79dc725cfcdb53458b56474330a1730637f6487452b003b48e3d2ddd60eb7824b520c5ee6f7917726a4f352647a42901d38c9c23fc4dcd4c3b9efb548bb4863a8e96605a0b91ae72956a7c4d561ac3bad77afbaf6f2be8d086ea8aeaa23e5c6ee9d2678c4881130537c612a7848cd7387e012a7debdbeef81e367a89f1c5d78f8c22be1f0c56446f91707cfca9161d79110b0517b846167587480b462806752abdfbc6db197810425684bdc247fbbe10a486fa9c638730a8b6f6f6458d62a8148c3c11d48dde85018aaa1c6e104472f1e5b9983b73f28aa846540423c2595a743f45a70730c5586cf58f1a7234b36214beebe21c858361e29e1216f9d04b21163ff770e91474fe72ccebf19d5aa0737d6de5ca6629f1886b5d411290baf1828a8dac632cd51bd2368756cf2cfcc21a8e059831d56026e0e5902a34f2dca75fcd1fa9c65a9033f04e326343711186ea430751b9bb8abf23b5667cb3b85e39b270d8a4794ed5035b72900d59c48889a8c699f8924ec1d10b34dd9081b624e54da3de7e3bc9998f44723a435561a79f63167dbf54a352491a390ba5e8ea369daf774b1904c46197b772f455ed52224c03d75721c0587705a93a5652e079503a2007b897f57838f7e872a9f92fa880c96d61570f303125b3e69d44d615316c861bcc775cd4ccda658e2b6d47beaf08599dd7135de935daed7093f1739cb5a429a11efc21c23cd0973972dace8d319452eb45ef608f290f410796e6b902baee625f9c3701503d83f08d2e4610cca8c31589eee0b0f810afcdac7e302edd64fd128dd9ed24f82bcf96802b537d2d6e4fda3d871123ea5125e5da924dc178e10bc9b779e8515c164793ac725d204b0c04dd07572149d2655cbd7837cf3b0920ce18d9d1925b332b02a3383b05e2c46584ecc9121d613f9a1d847835b70011685c232e6bdb623eea5059a472dbdb92187759567723fbcaf198ced43fcd9689c2b1cb4fabfc4170fe2be24672f5cf4b70473189b6eaafe3480ad68fb299bc3b5c6fd8f02cdc8e5c8654933d4ee613b948c352528bd8de0726e30fce89eb4328413236df536e95167094344720dbd7918837c6f7a451d8e2b6dd3d280e1112f22aeb439d8f332f7587c71e2264ca7444a40baef360a429e4218bec674804db19fd2ac280952c38ac6c47be4c72393dc4d1f528a753db72920a12d30b4e67081374fb07cf3c54127d9e780df5729277370ed908852ab1fb495d0346d7acb123b0a38c672e2da9ab07a9692026726d3dfb37a2fc82f0af4a34569013b06d9f1e348fdeb414b2783da896661e7b720fd73c9610fa3fce5a0cb2a7ffca7a997441e0fa65dd66d6a9c05fd68c6a3f72a4f84489e9ee3d91a455d1f626dea1ba6e48aa88c55415a53d261debfb45bc728e0d2cc23bfdf467b99245283df9789808f208437352501b6f4b57fe643cbe72851e2391c22191e4353141dc583a43a2c5dda6d1038d20681eb8062dbbedb57291743da5c59b74ce307ee73468535ca5c345dcf08b7128e965bda49b47dee911fe5e8747168d9d3101220765014e8c7cdc3295c466ef8276807d4bd151b17b7222bb7783c70dcfe3550f23ec5e5358f308b1d406bc281b0f967120121bbed972a1cb87b482936a51c71118ca83af3bc3e2d3fcb817dbf34222affa4c4850dc72ddb99f87210ed46647a6ce5189b52ca35a0fd247c92808316aebc168e9fcca72a40b5344436c9a820145052496f54ee426af62362eace0e6e1059544a35aa1726215dbb2a9e93d12c25ea2c4ff4a3df82982139118fa5197250d13302497f216ab51ee14ae24d76b301225a29cf5beb9099faa3b60864711d9fbb57263a0f03238ab3097d0387f24cdce20f3998b6501a4c4773345e2146c9f2b60f30a5d0372b84ad06608e344d5a27d32d41416ab33b1cc50ca25dd711466c590c998d93807ed27d660653763e4f9e029a8473caab3481bb35d546be205c6e46a06d75f200e65634658335f62d3d78946ef14a1b7d9a0f03ac2d0e407d633f17b338f470d72e01a99dcbf35dfd560c2d39b09ba0f4376186f560f746892148d57a5f81c6d0d2e3581e12b4f500fc40b72657df50ee401d30deb532d8208bf2ea87394d11d725d14fffd49d27142d88ba51d05207655704b507b5379d64612f3f1f7825fc062f95346bbb26f4588aa43712e73020790e9893ac927169c00a0d6fbea0418287227e4335e41885e4edcc1858df39b7bb5d1554cdfb96f67cee50870b42dd29672436a8c22b6757ceef440dba3662f94bd70589138e5875af550ee6a7625697a720e71c1443bb1b3fe16665c58eafe6de65a10fc003b6cda396eff91e6198d2b729d93178535c674910cd58176dc6cfae635490b3e139bd30c83653a38c5d46f1f3ede5c596acfd4d4c8e2e51cfe9930792595eb612b6d84b87b2f506e7751f55d91f51f3499366e1bc267e364ea4ec18448d3a88de2118e63233f7e23a2f62206b37dd19d2fd97f12631b492493afd627eab8a658bc0371f1107c7d0a7bca770bc91cff939247d3773a2723258437d2ee1ac89e2ed4e95bf0a08e9c04b65a7f72c3dde3b1328a53703ca6b71fec5131f2622927a767bb915b1087a87274171d72042d4696b90664dbc809b5c6bbf42aeecafd51f3ccb57ac4fcaa7608873d86720eac981bb72cde0d879874c1c1e2ad4bfc5e179aa15a5627e0eaffdd63635931109ed7127a1144e4b39dcccc922d551bfb30dd2711c5ffc7bc7d2e7ddedbb209b3433540a486845ef2792e7bf72fc506fb01f3b89ef0f7a6c929c93ffa5a550e0a70b5e3c803cc9cab11b31efc7d4e5843f0f4b69c2a549cf20676da65ff0237c77288f3c9c57143be682162afa6e9d5d2e51b75a61df5ac07da95cdc0b1ff72eef9987903226cc8bea2e989cbefa3ec65fe6a01e0e063c057bfa29896c7c97298ae0e1fd41d855eeed8ebf7ca45b077c222922a462eb7560564f141731fd57259c2e8d85a4501542a90387e534e2e0e8932d321423b74c9bd150f8ce947721d66fb94d1aa5e3732f3a49822a556e721b7a2ca457e76a0cff55bbfdcb44607689d4dc980c778ab3c8fc3657047bcf21df5fc75d77a27c69204937112b7198672662b8519cd39bbcf59a20007711749b275eba8a21cc38d49a5f610d1382d942a46fbccfef97498249b3e370f5f15c7f352142e22a15445e5c66718ac43048168a0b97c5612bb3a6f74b62b75165b9bb9967bde82d3f2a9cca3ea4e16f6344b0dd152c3cdc0cd7fc9b62fd58d413dcf9877cf14d85ca820f60e89d532a1e26f72342d9f57e70c225ed88fb8cd3b7b188a5952d82167405ca857f058556df2c57272bce335986a19734e84d3ee7ddec9e17234702d434dd9e2f6a58b2d5b3ff5720de8bcad8ae6d5d2978a15f26d920bc6412cbc758bfc31d3f5fe0271505f6672d643ba2ad637d8a1bc6049427d299c6fed051434ade66b24da7824a61bbd634aae2f942544d746bc889fb7d95eeebbab5df5b99ff94375e51a3a78061f1d0e3b0d4d7d530392cff81405bb8744d87588f7729cbe285516977cc88406bc7953720b907e7b5f3dca8a0a0c2db2b86106c798efab83e4e01764b56d546850bca97250081d18840811382f8f073d0bcfd115f091bc28fbfd60ed6a46a6d624dd5b056329b84354e578676ee124b48a1b73f483bf64ce65a8dfef0f21fa8387033b729e73c82ef900afa9073c0af5a28fea2520f5477f5793d208e32719ea55dda372adcf6e72648ebccf11fc156bd57290d75a6ad2b8f142dda78085783b94851e159dddd9b5a354b1b3421cc3c4267d9ac1d7403b9bd723ef7871b33adbeba6117269a5fad3a094650f70ae32449225c69f8d99c787b4467f766267e2bf673916724614b5ee36a7899d49702b90270e1588dd71497a12882f2199cc0b8e82f29e72bdbbb5dec72683260caa797e3ad64cad26465358611aecbd8d8018f64167356712fb80444846d11a0a51de1449c58dee5f449daf9f524112118b5c3b8b15d43669997f6a6ef3336c0a0a8313e8e81e9d6d7b8b4757f524bfc3dabf153fd0c53b366d66a43b745d34893a24f60b8698a13b10ac0f578046994b6a70856807d2433ce528ed69480c66b8a170f8d620f99021f87e7ee8fac7ee16a102bf076e4c1b60d12821af9e84a0537cd924b7013475e87e1a373a937c4a037b4b7a1261fe721dc4a2a4d50372049da42de5006ed9bdd4bb54ead8cdf7c7d95f754e8610d372ab92873349f10e37b643dbb24825b0bbb5a8d80e49abfe98c047115b6b439f72927502f71b762021abccb7bfd3a2374a45cf2a7e9300479c6b1150f204f04f722eadad7df2a2e3d831c3bd5a14f0d216048774a2b00499f541138f15455c4f72b8dab4073a893863a69e469290af9e5ea01f8694fe733e8cbb231b23e67d4601c13069a27051449f18e1237a88f6c1851b1050e69722db4aab6c91842a8e7152c6f4e740cba6875314dc4f4f50b4570122a65ddf69c3c5efcd94cc0273e886729f350d8500ec78d3e2b77b582ce302add1b4d9185aae806a524e6146eeec1772d661928c02e9704aab9742f5f850201f892829344f6fe0a4224f6764046ebc6f2ba714d2ebddae03b6ab725b4369e1d5ade598d91cc22dc8a321c0a41e11aa72a609688fcaf472c6b4bf71e04e5e5e11a04d0d2e48e7ba3ec5dbcef464719c72b1d26aa34926884bfbc94ffb246b6adbe5768345363eab6f4516981421cc9e4e1f90ee85c0eb2488a34b3c03e10e0fc13fa81a6f51063e9f42a255409b181472c592d14d171a2444363e9d9011dd3278758b88a28e2e411232076d25934b9a72fd3d2000ade8b5b2b5f7aaa2160df525507b5ea8106b3203569759e6d76c853d962f5d894048c2fc0e3162dc524991880f3d494fec582d79a60d1088e37ecb0531a9646bf7d9f6dfaec0219c755bf1bb3c4f57ca6053917de30b982b4dc98872848f82d632deaf4d9be6b3465988a070187fee15ff1268aff045e6d33ca1577296dae1c23c44a83dfe8a905c9b18b98f5a5031db77212fd4c4eccbbffaaaa95a5f92423e12940d0f09898c7171f76374eb2db76b4b7e81bd66d5ab2fddccaf3426c0939226f57e4b2743a02dc08c01ea8e3baf5e5244b9b87d410788477ec22942e1e5e0fb7159f171ad60a38c97845c85bdf3a9cc17a0a32e66f867d8b38f3e1096e8608ad6bcf31063d7248dd9e41f2dc0afdab2aab1bebaf58d0a94f8805593e3bf94ef67c6ca1228b9bc444a337be843c705a7c1e16926af4225d289c6722b4765e28880c97b88859835bc221ac02dcf20acafd7bea724ffdd549b403e726baf4974fcd5f8943fd7ba6cd708ed95ee4dfb7d8663474618ff0b937b3848496b09b954da93ac27adfa2d49fc86cface0c48f1bf5c3dd9de41dd310621d7772102d850e0aaf5b763504558bf58b651a28932ff31d09f08b26cee298d75351726a5df86cf9017de5a6853eb1b842c25314a1bf7de8d287734564e9ec269793076b43c17d7b7f05adc808e93a94b4a1ff9036445b481018f510156b40366e3872d877ab46288b25e9f35eac78e3652988c8a69de4cebc4890cac4b7848bcaa60732d88bfbac5eaeb92303b0405b3412b6fc0db2b6d55e5bc3f78652d8175f7b727078cc61460d3ec512c1ee3598be224b9a6cd3d60e91b41719a8bb75bdbbaf72da86a42876d170c3d701a74b445be5966a4e8cd7c98cf611906141f975c1756ba274b32edc4ea68019c4b930538c359e219f3f16709243efd4cbc4e6f9dfde729f2c2799def3b10f373194013d076bfa3340cc1b8e96cc1f01f0be4865286e72637199c5311055de7e3d449b08e675ba4ce709f99d4ad007f292fb6949ceb6721a25cb1a9776bbb5e00cd1057430048a88fbd33d09dcc6eb36e036a6df75e56637905cd873f5ad8669a1f3d13eefad5d28bbc3a1f9b720d9118320e02f4a2f72507455fe81def176842b874b398da462b4e5c3d7f38d63e262848743e9448f72c4cb01700be55be2aa0530f4b8f7b4080c78e257c192a7e5d6bb537e3b53fb14d7d16e573580021d9b9301bf387a129c8851547586a4e60a653f3d7d866acd23a480e30d2282a58caad031a91d5f5951f0e6e823b659d8458013c3df872e87654a2be5e55a854ec3c16c0b6713372f66eb4a6479d08ae4563d47525a3ad224162168e9aefdfbe7ae2cae6934067f6f9a2e84031991fd3194051c97a4404bdc38958616412ffa407ff0a9b10d2f836461c401dc6746d4e7a9bcaf9c46b51335729741366b489ded68c63e6524b5f0c9d62a08e5a64543a023a98670c78fd3b10a9b02a0f3167f9b935a91977cdee417d0678b00b5c92d388bbb99b6489853d772ca3e70fb4c1928604bf718ad272d347ef57a42c4810e3e3388f534585c106472c11d9ed44a1a227a232fb159466bc356021d48bbe7437bd6e8c057b149bfad72141dd33064fc02c04491fdf677f10ca3f98d5367f99f590214c812c86c90a1724c06ebe64422d6e2679b6b338946456bd1fdcdfefe3d633b5271da4f1e90e572b16a6de962838bef507cd40f371b9eca2131943da0a0262fbde9bc9331093e726778245206aa29da7c976ca267825291fec60b487c07684146de0fb2b9b1de55a629ba7c74a0bfa3b47d29ecb1baabb5011d6b8b8f15201c71680d26537ce2721e2dccdffc581e492f1476469f186226f86014d2248ac54006ace45c9b85e670461454db04f8ac7c1473b709b94f8557163e1485f91785e550ebc278b8a9657236d241d26767974a4654f614e69c5128096cba71e747f12f39107d055dbacb726619bdc04ee0763be406e79a45e5dd7c37c68836132b1b5a85958a1fb0ed557262fc93bc8575682e6f13db4346e7d518313cce5eba4d507d5ab783fe50f5c9722c69557e4b40f8407245584f63fc2c1d0ea3d0f8e1eb2c366cca75adcda1726c826fbcb35a2cf7cddc961f81e6c115a2a43c098728d0d9e22f7f133c605465727fb4b04726c2d4bd5111a002cca98eddd3fb82525feb4a3ef1239a1c6fbb6c7209d22d7a65073ebb5571987969b7ef3fe84e15d4016685bf9a931a39c03f4b7210a2ff755f23cfe0660d4543e1cd00c10d3ad039ae51beedde922029f3a84456fd4ead84bc32132f1de6621e919c3c460d752375a41dfa89f8d7fa6fa1ee0372d44c0e64527f457afdb2954d4fd0bb3092306974473ae790fbbc34d8f690ab45c4df29e847ceb11e796afc74f415db60ee639840e67da6b2588784b48f1938081295dcc03119bcefc6e8fa105670a55a6c134f4e6136fcded2f615662941fc72420b6d9cb524e4849a07dac5ee05534c2d2bd0685f7e154ba3d6c5b89dd9b6728618818ec9724e5c468d08f8d7b06504a7d3db706141aff9d8097f380d3cf80f704b488b40c2782105ffe0e39aa165b829bf3260b48ffa73b6ac4e769ff2021632107cf283d9b25de7e48944d50e76c423b4cbb1c906bd812a5175459ff49e3191a6bfa5ada1127056e5a08e6c894bdb6eef9ebd0d2dc71447fc52b61be7746aaab8d00c7ac77fd65ce1a748dac5de8f7b0d0ca2d6a2747b72f9fafe83654056df55046bb0058813c683b9e6039a65034d785fbabe4b56bb7d9017e0920a1f357d792a9035e94e8decc8878ddd09ee19bb562a5be0e7867543ca0a3daa83cd0c87f00d3ad002d636535f6cd0ea650a451418bc29daee6a692f075fb6a4baac5e94f6978e8a232a867d188f48ebaf29c3279f16d661a2e3ff9d1efc384d771d1e4fd8982ebb20a4327cda487b86af9599c865f8134ee779147f3a00806f1305533d6572cd5227e8c9356402c4189b1dbf721ad5436f7f7a4913784b58b50ad4721afe113f0b0a25d5578aad44f499ab10fcf785a29f167492a1c00da8cfc24972043951467eba357b8c6000aad1ab301300c36e6266628afc4574c7026a1bce7228ebc40d5fc53127d7f8fd67c6e1533d13ac00759ef4ec0a868345fc54b76272e8223068a0c9b655639b672a0c5dd40e9bb68b961c42341915792d6d372b5d72820342237ff9bae4fa73ab345687b09b4626ca5452df2402a7ff9d6d269e577261dce16e250a843975c2419267cb6a66aa57806cf936c475a95010989adaa31028ceca105b0d244decc21c224ade2d36d3d6a5bb07a98a6064c481854f7a920ab6256bc29373ee57c2c326c68f21188902bea6c5202a247797c6d98dd2f0eb722beab219354e7567bdea50ee457739d3262432899ee360240168a957907da65a64d388bc74efbbeb9488edf8f814c4105f535466135588e0f4e4984d8f12c66fe4b6364b6ea9865b5adfeee95beb0de62fabe3b5e56d57d8d063fa4ea45ff032f2731d4e38824cea2e31137b71a5474ef9f2b0bdfbb2ad24e9aeccd3de2d9d72b6577916da7f2810324635783c9fb7df7d89f91145649aec2f2e1e2d3438f4724b0e3a84c54f7b262cfebc8832bd509f051d117ad0f7f215f0378dac2b5a276ffea03fdcf7bbb308fc3e48d7ce41c4e7891d9b2f9b8f4abf8dc344edf3e18440a281b0e1346a48f701315d2d6f785eecbdb71400203155e630f7f4d40d860072e9646f8334c5ace23fb8dd4fbd496cb814d0684684f4dfc349cb635d3bfae3729a332e888450cc982c6edb1b5cb8d65b8289ea17f68233ab697df117f676ff72bf1339666973008ff0892b90f03d9967560ebd1f7abe45cde65e4a12df6d991e16b22dc50a402d718975a964cd956c42c1dc69b2f03756b056bbdba049a18c720d98cbb4d62ea0b406e96db7477f79c6fcdf12cecae2a2caef70c8121615c77211d06d1a4f80f10cbf15183a3fb37e233819226dc7be7a7cdcd52fb1a58c1e72e33e1475c825ab6713fe55835cd9fa7770182e00427410f583e5e042d2844c4a74ef9efce24c6ff0fb6278db3823642ba943da6665365f062defce9a896f656a3203b5c748ab49b3612112505ded7bd55027cd470faf0866285bbde79bdebe58c321e27f13b72f89e5652dee1b2bd197184f10a26394c2347127553119dae801068f97200c1944b1bc731e382283da4eef0159db7a4d07eda3b3a9e22205fa722c2dfe601861ec32789f3bc4d9d9e4de2c5d72873f37f31c5b0bf27275c7bd0e93705a99f1593511ce561539a2c2cff3fc1f1ae0ee27d6d1e7e918416dac41726fc9646b5d2b7d2a44dd800a929b193424a12091327347461f377116a88f7d724c9517c9fe99ddaed19e38e088640b72785bcc7955cc36b42a94cf04e2ef467253362b5641b0f21349a23e74ecf7baeb017892e80d84b403495d5cc43788e67289da240e7f9d9a71b2d73389a4af18d9d67bf6d6f6f538a07df393754671e1728306b5664bc33296595a2af6d627fe126938e363716f5b4487498f7f18b02017f7eaec8ed879d5bf3a2a2824dd54af842d26dd6569092870ffc316cd2aca0f72c05d55dc33f72a72ecc2f301bdbfa81aa79cbdc5b457ac2da2d0714d040d3a720f46fe1ac1d99752d5ca0178a91f93246344e6ca88ca519233162adccc5880729dd498d4a99e9e46f2950d9924d3c351ee91fb7643547fa0b1bbbcc4b1070772b170904d33a079382c62ab62dbfd24e766feec7b57c29f3b55acc111ad9db54790faf20fc2cafe594105f4f8769804698dcf5b964e8b61d1e90d0897e7c06072da43e01490a9bb5166e51a1b7fe07947eeb64b57f706fc18ccbf95bcb93a71587a58df4e53d1918e41d4b5c7e33a395c2fa82a8e350b52153f77654a28469e7238fee4f25f865282c805d32f0d08ffb40fe8cd3ecb1ab34c0889c893346f26727f696e9d477e304f54bd8bc95999086ee7260d1156fd5b9fa547ddc5598a0c6688cc98c5ea791eea5283a7e6f71d38e351ef04b18a670340cc2787c706a42b6356318fdc17b8a1920b198d2433011ac614c71bc97a69b710dab8c0ccd4749972b623dacc7d9a9a36d1c95f94a864c24ee2ceac3119d2d3a0faf49b927e87194506bcb08e296ec2055e37d253525ba7425a66206dc1bd6eca81e30ed21d6de7721f355fc7cbfec84ed65d97555d95f4144f3c774431d39cfd2ca34016535be704f08ca83caf8abec220867c9815a85a41a6699380600c9ae59b802fbf50725472179ac2ba8de1421ea9dd3f4e287753b0b69a71a7244ab99d84f0f145f6619c726ec805c62d27ee2ae82388bde0f074641e15a3dc36bb4155ce4ed4601366b272d3a97b8f663563e5392aab91575772c08550002f8fd5068199d2912d68b5d472c851a6d8d18d9f6a0aa20d86d26499d1cb3581d9c3434fb4c45c83774fcda1722a8c693d43cb5d17d070965483d6b0c06b1dec8ebf6402f900415b078fe1a46416a35e32f3f9bf0a9bb9628a75fc291892b94097b629926cc6196923f6c2e13437c87bce6e79e7093b22f9550ad14f9f4c08cded4186e0bf227f6d1cf797fa7254f07c2360d76e35b650fec51af624136d7e25214478304d3a7288cb3ed7c1174158c6b1aeb0e1c7e78d01cb03a2f710ebff40edde0e6b8923cae927b9408072b87d29c567e8f9f2f09d50250184a3dd8163b566861c4a847572cf5dee5d5972d33eb27656c906d86d2a8a0f699a2b568026d322862536a0665fe4a48fcab17246926a524123f8e814b78f29798453eb7ff0fb901379fe1df833fc4ec2103e6d58ba26de572e9adfbf7ec2af64c7d58fe247078856b7973194598e36cf72fa169b416dd3f0f7c48425bd4713e18da3a25d29b8da44689257aed69cf2611aa6729074a6d422c8ae19bca0cb64664b9fa4bc34f828dbef78afb5411b43fe70384b17f181815a9eeb71fd728f022531532b216b8a3e22f479ae2b58d6e627049120e364fc2e5eda8ff4ffc90fcceb42b9e6a67bfd9efb24b53a50db2de519292a04d106d52e45955a03439f9addcb64ca1fdbeeada07a0e72e44ba4ba89ddd089676ecb83794a76ff5bd95ede0b353e3a35bc9c457db44cb0edec16aa216b2e8e6851e38e7f189683d34e478956ceae0282ed8b61ead903fd3fa64a54e403737d5c97616f3cc3c9205de627d91ebbcb44bb410a7c0e93575332d060471282406272dafe45912b5a22d35394c6b4baf35246f1731b68353aa2a04ed66389a0b1097249c8b4bab0deefb5d1cb1f4fc79db30aef755453747acf0b981ec4b84b34087254766fe32ba7428436e93d067b404cbdba2f7152877e4dc5be55765c89575a721eec79c91906fb19ed4a90c9a94f3cdaf96cc440d20ef5aa7cefb2908b84ec2c7857965c83db3aa5b34d33d24f6b1dd056846f8466041b79dc63da810b3d7830400f25cd8def1a32cf407827dacc6ab7e42ba3160ef54b73134260dc11856d5f005403b737ecc963629fa5d6c142d861c05547d1d4a47c29f6f4d1cca7951872168c60e008fd679bb099a352e77b9c7d870e2bd79b4e76d9b6452037cff4d372b243dc94fe6dfdeecd0d5bc49ff1b8c000d85d5efedffe42b75a74d13564b6721a34257a6954c005f18c380c8081891a59b4d6ca8c7506bc25aae30b60cc4e6e20f019969b2b9251a06f4786d214596752f1a211b74cd4fcf66e7371b395a3133b9afd5c51bd262dbc915389920b1ce7a9d95a69cb8eec3bdac489aade723b2e5bbcdb28606f72b0456f51b346974f20e60e0556a15affc99cabb96947b0da72f81663162db1652d6f55b54f7736e31ccbd78ecf506dbaf854ccd0f3898cab26511cba5cc5633d4dea68ecff73f5a43680ec4e0db7c52b5781018458159a5b5f8d03409206653767ffab8d756fe9c5411478b3511d9524be6bf3c0c1f2265248589dfcb45c3aeb7e68c19a436a3a3b3d7bb91fd6b0e63ea2d959e4aed67b0d723e61e50a64de3f83172667a3d49a0434fd5e1f9279f65fb924a34346a87d2e1663dd76cccd75caa9847fc963df16bc0f20073adc3916621930a02e0e944b2072ace4fa1fa2e01cb8340f96412986878d642565a9f2a0f38273dbfd9fbbc40b3362e14ae006806f62c18cad88b4e11403747b54a6f4bd3f751e4c0fc634baaa05284dfeaf6d9814accf808c8fa398143f9742e654bc82072ea1db576958515a6e70262ad2e713936f138392c291d1d4506bf3bb5f9fdfd6bb310b85b9eed0e0726e0e26863c964b2cc138886a2e2038f44772735e52d9e1c8e853c5230392784f5dfb0b053507064d989e1b7fc966c7143dea74a056ff8d77bbf70b392e761f66c4eeb1bae6aba1d6510ae4664e5ff04f408c14c0ca899a22f7faa1c8eca84172881b8568e5d5c7106296046374a7351c7d90662b03eb184ff43c18a0f3fd1c6732ae392932ec0afb5748ba984b3a9dfb70a38bedbf98e2de95db20772c02a25ecf45fdc314a277c9880fca9a78466c0b76a05b0179344d7ead2f6656381c1f723e6dd1a86ef56d63aaff5b10ca019f8af2655b3475c48b8cb3247988ffa3e0002ef4457340a49a25cdf6c0367ccc3779224ab1a3f0cfa047c348daa4c6fd21726cd097cf4e95c4d2aeb9222edc660849443934e0260b5ddc720d87de038e6a72e3f400a45d79136ee1f5c5b1258013f80fcfd0b029b6c87289115bb8b9050b72137f08c355f8251543956c383a24e1087a15a4e4934144604d4a4eb239370e724f58c5d1bac7f261caf3884cec9732bbec6751d4a95321b031b95e1444765772e9acf095c30e4df789c1c938e6123e8b7b69c7189ff7e06ba89b3315dacf0d729e232b63e57be1827f512c7dd2fedd8054b49f6713da550d5368680cc8e0f732a28b57c0fa69c193a9572b73b2a3d2623b3c432a37bad781c941d18d56fb9737f492de9a098565e43f243bf795ced738792b3611bbb1a549e824e04cf5001b6da6bc6404a3f31b8dd9382e89ff27a498758b478015e8deef1449a41e76549272c00fe57fba3bd7d92a8770726fdb3ffe141497070db16f6c2861eee97f73162cba3d9b992bf6900e8c4f02695b6c370314f59a4af4f4affda498093c5e48e2722c81f64325b13551234eb81d486daadb1914f419594def193d4b18ac8fbf9d72b1ed280ad15620a1a7ded385ce3721136787e8493442c7d4871c7537fe12f80a2bf9ae135d67fa4684515c6a276e35291b3491357ad9e312bb67d27b44131372fc6e47041c6c5d39077fab150d6cc706a02633b8b78468134b4b3357751eec59e5cb79f1a5c7baa58c6a193b576420c3faeb748cdbd770a4b83454b0240e6955fd2d615354d4a6c6d9a44a9a9e17f4b426c71ab362f8cbfe45d9ac5b2bee465048efceee41c5baa467e70187e6728aeb80f5f72f3e139f9d299fe6123acb16728bb5c8328e5a3e4e3ed017a72f9a6bee374d43964e656546022741c42394407267dd4637565eeef24dd5a2cc170ba4d2a6ef3787a77d8e1a02e9b4f0e78aea572a626cd1fff8276c49760396ce593e85e2b3d059c6fd2611afda59fbf1cba6656725464432fd119775a1517201a866e65eb3a6a592d57366a6a51e0a9cbdfc7234a4b17420ef294cda9123f888e22429a796bf82e34371a0ea317408fa3363723650cf432e235cd14f7b070fbe1568d33c3347693cc6126f282278d2cd88f6725ef47e26e42e863dad3cd5ca94e0ba3c5da63e3e8d7c15a2228d43506274a1724b65a68835bbadd572aaaa44468a4cc3360266683dfc2d39878baec677671672fe1c92a80befa6468030e2f0469c6c4de93d5bd4df8b9a39d1a33dc3ee51b905dec396cf8fdadf0ee8d3f2527d992e47df0f9fe619df7b8f08101d074acac972a24bd7f0002df9bafbd4e57626f2204817da8304abb76dce462e93de652100721c92237df3a14b4d1984a5fa8715e9c20a8b6eaddcee7f12204e927eacc49f7289bd9ba0f49f578dfc0cf17e7f4e3543d19fc20c5671c17bd2cdba420c3fbb728c018f88d8573a52406b99040ca52c85a8856ad7ace56749504baf64b755fc61af6b7b211b8c6cff34289845c9456db5a98723af7911c2d91e629b861c4aa2551b36f1227d1ab55c7dc418d9afe3086dfcdb106d24ca6553f8b3da9a5a410272eeb9792fe24a44bc96d3ff7cebf1f5fdcec86d26dfa3933bca127934a6cb9c11ce7dbb8f658f3988f1756fe0af7f77ae4da6f09f19c73adcfc40651f1ca0547275c65167f43376000a097a9f2d6fc8fb23f48238c6f6a7d157227db79bc7407260c34d6b0eed3debf299618f7cebfbac72de7a7bd17ce36b76fdd9354be591728754624aafd9e0f7f83570a859e18ce2716df17d64af32b8ac8ce8e806883e72a6997044049f1db8994500761f4ecec2007d967ae7bcbca9f1b1c8ffa02c492e3080e2a492c263548938f395a678df8a8d4c42a73b8163f456ed9cc354d06372548a8b1e62fff8ee71c39aebed8e8571936ba399f5bc56bda163a03f56d728726b7c814ac1a4d9ad1fc5bb15e81ff5c1386ccc9033f22f80a86af9b594f2d5727b0f22ede2c5a6deede115feaed65f642fb102cd5be7fd8a90c3051396b3e47234f8cf8709cb0171f7443cba6b4264356def9d9dcdcc3ed40811231bce21ae722781ea95c7f7652f54ae3ad675fd58dd9db20f6d27ec05bdc6f9fb631110ab72b20a07ab8154f597f4671399efca26cd95613e8cc77ee56a94de541c325d362370a67cb529a3b524b7a4fe1d1c9e686cb27e5a5e75d593e43bfe138085eec97255d15fd0fceb3ae51d12facda30e206656e7d3051856606f452f073414c09b643f4a05ec1583c9b85da2d1e914e2232a55609839d0c584dc9434eea1a6f8e172072480bf4b4ffdaadf52098b84752444a94e8f1601fc05ce7ba0400c794b4b6f17994aaa2e59e1f73525ece72e2b7d0c2d4e16a00aaaf7bfd9cbd7590723413285c61c059b67c4ab2c70c7e80455e57c0622cc04ef80088d6f3130073800b272aa846aab51a9c52a64fe796fe1d27887bf9c05dcae27d523a7a5255ed12d2972f71bc04891135656b904155fbfd0ea79c32765e9ad75f87e4a2b87c589ebf47218cd7fab8694faaeb353518ee4646d47768b1ea332209aae01e176c9f84f642ef244fd610b802483d3bfc79e505f653010268490a04d1828d8ea0058a5895f34bfc310926b1407b805683c2b05e8b358a31ea958030588e059436afdbc7d7272532e7e3d8cf90402ad8739babc2bd3a005f6859c35018ff49329a5b18b08a872e38cfe2bf7c3d3e5970e3026e0fa6a9578e6f13f8ca01b8145597f6984e97672b9ae229f324748e0bdc3e891af567e0e107772f02a64275acb0d2010f1a89c3b9b748c077309be59d6081e0daba170b345b9794f87045f60bed2f3dd649812726784819ffa49238fa5aff30c2b995788715db1a60ec66ba89c7865bbe9d9cf4f28821e5542f547bb59d25815d313fd4ab35b3d10437b6cefbef3ccd8996a024236c77058a72485a6e3a452045c92b0fc7314e0889aecd4f150a408e3051eb272640edb9d178074e5381818794ef5c1554ef7abbfbd12d83f13cd552bfb6ce1343a0ad7bf396cdbd174bddea0f4016f4d65b0db44fdf3395f6d3f1b1ae02f7f729782f77a50e8b9a3599e54f91da0512af986715beb6c8573a57368106bdec705444cd369ee5fee40fcafc61d4099c6ec346e78b2a40833085b423ba3e71e407264c6bc963ea8cbbdecc4894288bdce273d2623165c6869a3d19e90db81b33c72f56c24a7a123ab028fbcc1f0535cd1fc9363ce8ed4cab7ececb2bcbd4b0d93726e3bd932a9b28f4d65b6fbfe2ab8d6782de44816bfda0c0c37a03120275765727cee855bd9c0ad1918c7ffceca1181932e51adfa9f9a4e1d4cf9fec2559d0704c13f39e83ef86308035f0fa78bfb883c78c108b54efa8b0c56a3f821a258bf72275622ab3f5902417b8e4c3548f7b47d65e8d08d9a6efdcaac59c59cefd4db724c0e83e30e8c6b1b907ea0481b2856529474f49b894c414bff1e96b9eb770325f0def7d572f6711c4cba854abafe2714060193ea78a5dc414955a9c0ea0f8e721cbeffea9fe86287113a486661945a13bc8d250ca0f9b7619a6f4de07f8c26728ace53a16cfe6dc1bc007ced8e125b17ac870ecc304503d9c45aa6e18ef1373858c531bc9a07c90af4d0e42af935ae1bbf0d2555064fe94cdecddfad08aae272670f905feb2e5a536f987f1698a5d7ce08216e780095a2efff4ad67927c23f729f2f0a2aacb71b28a5caf23c48c21c33aa54f9dd5bad22923b7b7ad864b1e30a3f30623ef24de9c07bb5626f4976a2455afdec95e38f5168ba139ef49d79b00eaf6eda67aba4e658e8f805673d66edae2444055d15a02e83dab2deddc48e86723d3839ca093fc55bc889deaa639f6a0b46b2feb604c57ad3a4e9bc2bbde6f67276bf3de252c896e7ef1ccf509b1fb75e83a85fa773fd4847193f96154ad7d41bfac2b8ee32e3f0dbe3b3f9cb5de4604f3494fb9a6a40a3301449f71fc7a48b72bac25d6227ddf1fd3fb35b2106bdd78bee4e67281a29dac2280bbe8d3f3ee572e45882502566fbf99b6545b8a2c5278086e2e7499384ef1147b55248eb5fbf72fa7580e1c2944c8daac6972691fb00f6d9c0ed81ec59811cec3287d5eb2adb72006ca05cb835ce2651e68127f7994e4db31596eae87c742ee63a46d097bdbb72901b7863636d9c40a777560d0a9dc17672e351a87424158526fdf9d5ec3a6072698c7b8547ce073c7e059733af29881d605acd18c40541f3df140060abfc2072c071a4c2e75b9788bb74727892346d9008d18d1ab08b647a139caead10919c72d2b6af0e264ad9602985d7b82b4e468e58b925be6e5e75c44b155edba9793772ca8d01d4f6e9c680a307dd2d60c907028d01c71faf069e2e8a478023c2bb244d463a73460c5b210faa16d1586d899f159beaec1903eeace64b673c8fc8385f32e58aa386101b681ff5fc3ea6cfb1497f88550fadb5a403ccb9dcf147f04b216fb3343b437b3334b61ae6a99aa02e3769dd7e3ac5438c8c5891c5790af4ca2872cd75a5eb9c354be09f2428397615ecd9373cc2db04c91954060ec3be27513271480994c8895b9ef7bf974a2ef1d5fb804dcf8f266be448df34c7b61edfd83872ea6052cf2dfc65e8c46da639f01b6a80d51fcc5285ce9fcf87fc6f62c6ca7072011556a730bea94694b9619c3e55996c0c1083b0cc5370e6a584c102b2a8a472f70d6fd56bab35d429f87408e022174b768fb155e9cd8ffda22cd8a91f8f5f720362420488a9153e862f6119c3ac881376cf3477952ef49ed3c7b8f017e33413a1813e654f5939b5781b4644a87f360a910346418a99b2740163c5651384332142ac1eef18a19933fe8ae824fce19d01fe27bbf5a5c007493de605b63c0ed905a1a6cb7554bb5f328855f2fbcef3c1f894ff71c79ddecf269abd0636a7f578025053ed532f2c1c86f45b93b56904361ef75cad7fdedc286374117f9f0b8717720c93d5e5b0e9dcfddfe4adbc9084fd28026ab16b394969ae86856d6d03d25672d226bd931d9e1f21086379ed4b8dde8571dbce50dcd29ac6d03be747b9cae07250c896a0e9751c9ca9522e06a9dca7ed0b899126d1887c6e09b67c6e4989911cbb7e0e16495949dc53761982a2060660cef245fe0d63953b40e44be61d88ac72005b9b8cb23f957f49f2c9db5932e59d5408d9fd18a552b36afd6103c0f78b7281021085e9a550179dddd710df2236b7903c188ccd6b34175216593c8954bf3cc6f992d7b1ed1e5c58840074564bebf2709a1213883f0ae84fd20ffe5a45fd3fdc0c81e67e95c9835b3ecb7286ef5c04f0cdddbd3f33df7cb7981645463a794882b76f5ac32f0645155b4ca594d2c547f8b1ba457270be344c1bfeb5e5a83e723a7568668f101f6d201db951f7dc15c570f3547a2148f8af0fab9da8fc07027246b1c9b4565008188aa68943a080e63a1f689256b929e85b87add322b4cf6172aa70730f9b890b7e0a9949dbe338a21ffdfb34cb1bb067dc06e96ccb340935618390bc52adb6452deba5ad038d4caa1694d73434ef7276f2ec73728820d2785436f3df9486e5f20f1d691cee0023a3e4f6a848b9e16159ce56d776a8c6a70615368941d7dd9b3ec0975a0f3dd393a4ec3e5f7f35845497299e3e87aecacbb372244888f91099290a1f1f36f9a16ae9cb8e4fd8370b7a5b9dcceea697e052587253700b2ab396896a8dbfbb75b10d044da2e32c67944fe1c9df80c364ebf6d572b12a93d4b00befbd777286aff0b3bbcdf1c37583d1ec0af7ec71631b70ce313f9d3d6311893647d1b9019e3a644156fde6034da1a1d7fbe9953bfc7834c0ca548ee92ca401508e29942700753115926e2af3090d86b6e3df1bbfa7a1c5ce5b60006a21c875d5d2813b970472ff5e54fee71b4339e240614dad8eed8317906010865f23244f871284ac1967e4c68155285f7d1a03cf93cd70365ce57f90cb187249de1f7ca8fec3ed17e691cc78920fbad682459c534250375fd787b7bed7d04ddb56129836f53d1cfc7564a23a60ff14492c2945f7ce13a98f45009a180c9e42df5ea54dadca9a9d8df72735e03e99aefe06588b67d5cc9be2ff7148917ab454d243a2c56c3cc0bc352a97e4290a0f9f3f2127c32b3b7b92e46f9bf790b65072530fdb09df2f8c0d933828cabfe059d62f2b6f622571302052fd52ee5b01600af9c7a0b18f01808848dba6a9052075c1f85c03196cd8321a44d1d7fc74cb2a3f9dce7b81a9e51003958f2b4ba479f7caba645c036fccf2eac943a87615062d729fc69751b3d5ee86586d93aac0f69108ce936168fe3154e964b75a31a6387472fad071adf2bd3e5ed7dca87d200ecb0c12ad3d83fde97197508fb1b4140b4972b1c4ea2432018b9e116c1ec8d82b131db6802ca954b39a21f91a4935651321720e79e4605df48002ec961e2e0ee639c87d95ca3c39eba878bd5284e59db25971f1250b3e2f9e0e2feaad9706e7b84d9379fe5923a7a10de458c05a8740ba7331b2ae50a6cf0034643b8d9b380672122cc570defd3e1f2056ca28bd2b7cb8537231869bfb067db2d9924452feffc8a845bc8440e271b2a4902e051dba84696c72fd4a81e989c1ef4cd7910c4a106a5033cee707cf2a3cfeb77c16528375496c72302dcb3f9a4b39638d78b91f206f0e36f6f3c3ae822bc797ee49f3c0e2d5da44c3813a824458eaf2156b243b42e590a7b8a8709becebe96c83d45ffbfb90e601a0816773eccf79f2c55d25d3bd3d2e29c3b2acab9713b87d5aca496d013c513f1db1723844399775ca1205057e70682b99360f8c67b1e60e7caaa88cd5287972bdd33095e32458f3a70696b38c9e5c530d852473aee9dbc52032a5e9d2f36272b9e6a0b693442ec3c177c61580a525ff45530f788deffe1c9afe9c4e8c4b40723fd78c974960e5cf1c2e9193b793dfa6626b9fb13822ff8c841fff8d6d276a72a1f16c8276e8f511aa32a8c88c4000f02821b9dc935f7595d76444be3f7e0772cd3d213ba87fa28477a664125cf400bc2a82e7ef9b9f08d0b9a6edaafb9ed272b17bdd59e6e979c50b67d3d908750cf971390ade34b6189b387d5a8d684f05727e752aa84189e4118d32e393f8fce1b015e6882fcee5807f28db701a0dcac90787a15f60e4983e361318768f584ddc089793c53cb53972dcff1fb2401ffcdb72bfb490b552acc271bb95a8d340b60000936fea4c276eff7a8b750a5f62422f721f0d59b394eccf4856bce333dfc47c08fa8af3b513aaccf49ea598d030c09072c7259e1632b81aa07014afc73ca67e49ba5c04fdf8812bed5943bee443b2c204a7cd15503bc3224afc2c4ecbc3891357ab96521a3c1697452c8c0e2752ec0b026311361f69e2db2d8f6c5c32aa5b4d78554d71cc0768a21ee0aff9121c40eb72f3885d800e9170f0889977ed71cc0570df5e397e3a34ecb668197702484a9d4ec7cd64db02b2130d7a35c3219acf34cf350af7235abacafcac2451691f027d72a1fcb8bac9625388030e0e1807cd9ee2d67113ef38767e304338f22a7e862e26eed8a437e9bf762b56a13cbbc20d944a99a2cd6ac547a979b215a3d96af9320817219ca1c258d97c3be7803f931e5e2b03dd3440166f9e648feb7781f7e9fc3bb3efd2d09e53ee1a6e64efbf9e5351f2e2778dc579ff1badcb9ebdb46c04f32d4b4d2841a7067937bf9160451b06b28ba65cec291e11d2d04809085a22b5c710353c117bc56fe5c119d637de5d26ef954d31fa3db31a949ad4eda0f1541e5b724c3c15ba80eb99a3e01929db3204fbac6136fa7c9931f068ce34642f9478fa7290f4e3ba4e8347b1e5847374f177f34fb41d82529589aea62731901ffbac6720cffc5659e5c5f428c15539c8914a7ae7ca6ab03aa0bd4b50d34a6f0a1f0bb529d45674734639ffeadad86fae8c89affb96aa4ffd7e8705d46ebf53485e857272b7015720c4bf43f1d65eaa605b41dd4cdce09a54766c4e701abcaea8bd5dee723705d3b35f27c33ce8edb8419250d48af8f60c0ac6ab3fbe00ada18d9accf972e37637ba562d03347f4c06e781f0e845501ea69e11e3442ab0ece56f96e1a41c682a3b06c0c43110c91af682d88ca026f488758f7b81f30789ecb6e4590943721bcd497180f87413fa63bacb517f57dae7565c1eb2b282a7f1d31f80c4d3e67232d43ccdcfdff789e33660a1d3bb3ed7e2bf80efd3db6e3017705ec0f4821b72ba82d2fc214ac65083f9b5eab387e976f4e140fe9c5598a1b2b3c3a30bb7992c4b8722ac16d04287fa6d2b98b4eb3f22c82f6092c3e2c1a92b2f20fd3f339c72ae6b559005965345ed9ba64bcecf4a0829f503cdb129328fe924bda6beebcc1f8032f82d8b0cf100bd4d7ab07a8ee4811f698324e160f982adfa27878dabc87204e90854f987264460387eeeeb88b912d9488ad87bedb46cc0e249de8777cc657ec9061c0cd2dcd5f067e761e7155af8c2d5c9996eae57df5c627caf847f3472b1f47a2c568b5e811a61e7ad8fea7521fa3c9019c2345c44a7361c7f076e7e7280b0f09b14ed9763b5f95b1d32fa33327a772faeb68f826ec7ab6251c006f4721838ffc584f403bec911e647b378c9ef02032dccaeb2696a40e33d712dbfc11a08389a58041f4121b887d768c711c20289b5e71bb1ad54b569d89fdaeace1a72c15b8b8f64822126789486f0eb87f2b93c0d877780db8781b2d5caf812f15772e91023efd64b3840b044b9a0fff633b092713f078f16900294008d25eecf41723d3665c3af529b31eb11425994c05e81786209396c9d8527e32b1985f67cb0413375108fb83a3fbc656735ec3888d500686a23fdb1568eddcacfc36eaa984348480c465f24d3fe3e61bbb1cf3bf8c9e46557464912ee931a4a837fc130980d4b6b70221dda163069abf3ae3872d520e99776701989edee69d1e7a8957ae25d72069f412157e05c81dfb15063374c6381276dbed162a487444c48ab0ff84d21728717fa25ef4a266fab3f8572c98bfbc787ae1fddf96f3a95ac0ddaac31a17d72265a345685caa9e57c1cab9a18799b91432a240af6f07ca40498eb81918786624f7aea56534000491e209212588579daa849a2dc43cd2a77051c3802d99dea4087a52ca40d372a5cc8b3f4346c81120a2f0a54280454b95bb75d769d25fe8e72d8ccf9509d1d3c3fc0552bb5a6b8c618a3b52e8f40f95ac0cedf6c97714c5f72923acff28649fdbfb05a6b04c92f25ab698c0b7139f2e6d4aa01224d74cb1e72f3db3458443c0d8b56469f85952e5968fc80f221ae9c2c76b48321fab71c21331d01477ad1abaa247d1fa871da372638e6d4a6a051b2d262f2efe077ac04843a93e08cf67d641e8edd235083d9a3f3ef34ee3c7d9a05f945a993d9957461b65c4eb39731e47064fff1d3a2b2cf1844f133f8c930f801fd2929d1a274403ef7726a6cba7ca892d840d3066562404a5d82c145f6f69f5fe20c69134ea0988de572e780d9b86a34f78dd3c990eec79367d4cce94b97321342db2554e2294f54a83be624a3cbaa91c6289427a17a9c4f100ece4a563221b68a11b5bcfe56b3059772cb8f75464899fe4b8d9057168cc2b33855fd995b0e899c82007e9674c704ee72b1326d86d09882ec5aa46671f18c50a0e3910deb88f1c89767dbf8cb837dd472af86099cf4701d3a89c1bf313faffaa2bc342c3b8050e931a50fd2ce3066215d8fc2a6c5e849a8cdc46b8bb227dd40f5481b5e7769a6888e86db254fcaf96e72864090eab1d5862481e7bbd09d895a6c56845a63ebdce9e4261653a9d3015f5085bfe8d4bf817a987f81903777b80c714e5810cb987cec11a45cee30d3fbe972d42d86307d7dfb44ae92b920397b48da849014970ab435d729eb8ef1ea97e372bced079ae26aa2a5d68043f96016198d2ed18c6cbb0f5196a529c4689caa4957c095876d5e5470d196897f870e1d623d69f596bebc5026fb87e4dde17730d3726bc825f7af49b14b4702f5946a6a7cc955c7c38dca7f00e3854f23c0bb15b50b80c00ec0c68d6c4ff77c5043908f1f475cddc098b8b22add83021255741bb56c05b2167b958d16f956ec1dca168e17d8f2cabddf84d6984ced5298e12da07c000572a7aeeb261b8e1f1f01b3251ebbd42e3e47e5496d90a324d80832b4a97372d1c33901cf3d052ae1b9785d121748590e78d7a6d948791b09894d13e2285172de010139f6b1ff800b5cb42edee6eab91963f9aee9c28bb2856700b361987372bdc86f44bcb236cf62721ca377e285d3a45de162f3c91b54e3d5082f85eca672c9091f12f29790ae8ef1871fc750429ea966d42571a7520645b6f45779ae6755b0c4b91b8da7157c3d8181670d66277357a04abe87aeecf679388d913df71a723545aaf3690b89e18322569e0dfc7e395e7a636b18d6ec9785f7c8cc1dda1672e7c4531d9a480825e703a10c9bc3d4ffeddb1c398d84289ce78ff1030164e972e68cb2c6216b8d62b4403b085542a4adcd452d2727aea838001d6e55c148e7661bf38fc89bb064fd39aa981edc191c9359b29a898e56e5ab85b057b59816716b3e14a327540bd4b233028b8c34f921b727f22bc6fd1d0da80007299c416cbf72eb41447f3e4e65a581e49a26c995f4d02402e3d29759fff77369ff423c33d05b06eb9f13bca0180f7b62f13efb3dcf9c7f29f4266794d1180c08e82ef24df172c35112cf6422ea34b1227bf7d925c88b3c238ebc1ebf95b25e2897c94410991e3db78b34f8f28a13eb2e5bba8525df86cea66c30fafd389d5f1854bbb4947b720c6c4d61c380289af91ec28cc2c333cdce2e51befa289c87454fc01f8869bd63c4e478893b31d5a656f97261c4ae0bc7dec9805179ffa54f3d8febe9681e527295fccc8263ae14da25a30da2cf5e6346caa105e26934e1255b3d44fef42730723d7cb15ad71bb78aaec079354fcd7940947286b4270497f5607898d349493272098a868823f45fca6dce8d89419f7a16cc5d3f494ae02dfc7ee7ba2db19bec169990d648aeae33351edc76cd89ee4fdf02ecd25b4ddea8184639ef3dfe935d723c4b50cfcb90af99c01f3679a201651a6ef3c41b46c8dc3d0010a463d7eb3f72858300cc4f128f1af3d95e7e94f6a4b2d4559f0986d4c3e15c7fcb49a61d07726fc1cddae8e4b48da7ba1ce6aa52d589685e0e3de0737fe324b3f86533201270bf3d055115af99616f902fa03202af1137b01621abf063e9e374640653f9524b48610aaa0282de379805f50cd534cf53b3e60e69b86e611b1d812b5ac5bc3144d88c9568100fc3fd5dc6e6b1f8c3cb6e8f49fb7bd44b5c3d85e75dea1990547274063377e235687fb9700d8efa086222cc89c7a839c2a7a77c15142c4090ea5d6747bd5440b099ee9bb79edf6d6457ea64bdba71b1df8b75f3e8782260439c47dee62a0f47732847ff5ad656f45796a1f51bb704f8ebb4c0379c03eb667b2c6cfdb2e9636dedaa0e3a6c555735ff6b66d2314c5713432cb5e4e74ef5df7da1193c96f4fdbaa7d58150e5c38b578b898bc179329dd822750b05ef8f211c80ae72ca1bf889beee89b7f6de3c551163f064d830cedd17b1abb792fd37f35c68ff14a17ffdf71893c3f29bb528f426ea6376230372c668a6515f5cbbb9b8b8606572514c28bed6b54beab67f9a501c41276dc1154038a2755a859eb782910ab43f72e281f6af0951815a9e59a245fbee304ed6020b91f2f38c285ddfdec55b50f91fefc99b42dbf1f4d5c06dfa089945058f646f4d238fb90217e06aae4d0e9d6372afad0938a2e0198c095d666f4107328132a7bba6aaf6ec3817e643e24abce00321a1ee9c794b8dfee4996b954ade8d0b5697bbfda7463069e34eeb77aa3fed72bb1951a00357001672672b185e48faaf80421d58895af71b820eff94b78ff8725e284acddc37f053dda847b3ea84a35d1192feafc5447a420385d35a017334473b43494ca68092ae12f919c7657a04115cd36aebb0d46a8b5bed8f450c5fa8727dbfb4474fc6e4043969fa56219adbcdb76ffd3dbfa82400d2190b8046f59d2ade94a66d257b6a1eca512eb73994842f612b0b5cb05fe6d83b789663baa10e72170b84ec2e1fdc1dee42be5518ec28219be9b333a058f939bdc963f47cdac27262e35c8ee88f6ca6c46588390508b1c57dc67ec024b6fb9e3e1fea6bfc97e6137afcaaa59b38f2d3a1cca7d3f4b80cd0132731561018ed67ae514ccf1566ec7274afb09647444571626e95d589d5c62677c0f6cb19a48e9e9a9c087b95a9a8725643042c207ca2588808838197b5882caf247f4f6484a9685662034cc3098772afe80846cd8fd6f4985dbc5a259eb50676f0343acbda88c443324a034297be7257f28beba22f6ec03a5482358052d8fefba2b5102cc4fab427beeefcb65e6440a0416265f5a9880414d78fd0b69a265afb7a6c184d1713baa6bd223c0f9180720a028a379f89e05c4128d0bd9c60e09a9d6bf3825df0ae24812b05e59290da6b96dad1b095085f7b17307c4fcf644a1a563d3ace8f93ecc24ff996aa0ecf4a72e421a9d16d427785f629e3c5fa172dd007902e9983f9db7a0b50452be34bd572ec0f4316c8774286ead0024c8607f9797c8c79fa14c72f507f2929a73282c46099555ff828c4f5cede80fe105d777dc7fc94f624207d8487ff593bacc8158428fa6c6992fbcbac51cd5f5233210cdfd45c75603b4f6f629b9b99d0581ce2de0ed9ff0bfd635642185ac842028224b9b0dd2de25ae90592ac13924cabc8315915215396a9f9288a5f3c26ff0dec83481f42965cd6540cbc6b1697120ad0b4cd60df7398e97966264cff82b935d802495725bf6714ae995ce67e097d1f6c482a724cae56544c607d6314999750dacb4f5147274915790cb962761a6b1911231a7269fb813393fb28501b25bb9a951cadeb1911a18176e30fcb2878c3806f263a72951f7e5c0384a050c8b8f74f554af80ab56dc299b77f34238d9b829bed883a5f97438f5e88319abf065023d9416c170b9184bce42b505028f9c18b8c56f7da72c2f2cfaa966aa395f926c11a5650dc91f06abf913251ccbe98942e90b84f6f7273f41b5f58b24960cc7224d4104414c6e9241c8d10859747346d95a469ce12729532fe9a124d4429b5da32e248b1780beec53a310b5ded60ca423337d0c36772c8ffbb55beb3c196b59b3ab1df00fd4c5cb63c5a5597abbe117c5c046e02fa72b1e224fc8f2e77e3420d18c1733ddfa108c62b64e852510ec61fb7a95fe5b972a0711194e733b2b32345cd682f5e08a4b57510306d3e3988b9e90653f4ddc67289940ffba8d4ae4a5a8e75f7bac4446a94587ddc79e182539d874fa75bf4c07283cffb9430836c595dd33021be04e413f533f97c5d3d10cada87ffd60c2fdc72388bd0f31a67c0e9af016ff98e64cd3081d160c79c34d145a80ddae6cd10ff72c40602583d371d406a09591c2f791538bef0b850b6c47374769412fb2804d6720ec137bdfa3bcebda6c2d1cd6f8a5d9ec703abdd60a18a1d3ee3b1b7c1e00e7235e6f99727b33a95987d24d1347a5aaecbd660e41664e47f995b926ba5599672bb3d50a4bc88ead76ea69e03d2de6da8748255754e323f888cbf859d81e9a172fc014cdcb1618a4e209ca4925e724021928b937a4b85e799e44e644aef2ab1723ece5974e73bab9945348ed4815cea300e1b3d8204ca19194d00e94ea16d1d01e6a8915fac55295f90d00c09fb1cdd2b914ffa5e89f22ef3a3202a148d7437585c8365b989b5566c1cc4ae2e4964627bc0036d9badc8b3554f68a876c6746f60d919e8acfab5c4a188f6ce47917352e37d6d934596303a388b37f2f1e58cdb727d9d4e184f811b431aab44f9cb0d015b49cbce1ffc5420ba032117dd72c0de02475e9d885df5436b286a222559e0d74bf6c17948d76f6510b6f140fe06a2fb6ab1ed376d26e93a1be7a740bdb36010ccaa145af63fc3c8d2ff70d4838ad15b72790b7e5e1ef164da0444c1d2f92f716b6071fa91af2538db509d2b085bff67726875ce20fe75450e992ad7a0d1d515b7786ad0b107e5077f6eb7328b06e79333722e7a9f5016171980a8608ee39e7b02047f2b33583a6c502fb3d1418a58c50d94d17a06eb85c7ed8b567fe23432f5381342a80ad0291c07f40bea5170c87206772e877e680868b56300fe56b16bf40b0858410343fe97d4116c093de2606172879a2c8978638bc2f181a71eff6b250a85e96e9190f963946b9547d0cd3d6772ddf6fd69a011ef9e5d70e3d9789f40ea952c1172a109876aa32e9eefae95f772d2478e0a549864a0ef880103b4a76c48568d4a716546c393ee874794a5cd887227525e9db615ff1d8e0d71ac7b9d466327a6b2f7bccb5e697842ada68a47317299e60a749f1e020ac3d6d102d822a3784a8571f4b351d0062c8a89378e3a3a541e1f0967b6c8894125cc31e8aa5adea09ef104172ef728436ed9bc97df5a3b720dd6c1cfcfc7f1f345d7430302996a14746cf060f1f388b7542949b07a8da47209657fd72c65166500f6908b8d00ae252b13def577f06968c13c334b2323b472cc13ced8c68423cb6554e2fe8c71daa6b5d48b77208e661c02022652a4faed729acc57c048fa58ea7b33e4476abe6fff2be882bb405bc5b0125abfabb542cb725f70e01977566c1351573a5d9cc67d7e83487cd3c8e522ac6e41b08e7744ac7210b4fe1b1da18d139cc7b70a2afaf21278f174555aa0f1ff02bc3c2b15cd7c63158ae731763f40beaf8e4e4691a8202aa5b04b10a5205163441570cdb35f3a720b1052c6b19ee7193956dc6f89ad55e4049bf2b2ac4369d72f18a39bef3f0019fd0ca113941df064922b0667578ca1556a3d2646d81983d5d0171983b6524b725d2b920322e1d3b78a37740518fdad60681269adee7d39a5b93b32589f329c728114eaa11bc8684f9726eae8d24a7364883a4d47a9ab987bbfb87ab9e82b4372a2184e67b9b3b640556256c4beb5e1a418b3674f07ac9f67b65b5404246cac41b80ea09179bc633843a6075a87629c27dac31d3b7422c61742f710af029ae844b465d5f2063239d281b9296f5bd00df0d3d56cda0027a15ada67ade06670a913174d51d5bff3ae61086e1a7b057cea3de4cdf55b12ef6505b8406deff0b8357214603c24a236a78cd7c88f9f0a59f66a1ab83d3fa25afa82fc1185a61ddeb56655c5c4c589874b3dd110ca45eba72daff7819a9de104ca3ae2bb68bb20eb89729bbabb40b9be2ddb25c0acfd1a91d3daa1efba0049880297d8c7378b96e34e72d2458599b8ff5d72ae4a5da08ba30e4dd8d951d1421078f5977febccdc297c724d0081b6552f1ed99b5275e1b9781c807f6844203c0220df40aef239f0d911728774af667e586b7055a5afdd7af4f06caee986bade6bd7e7927ae574798a7f656ab41353206fa5a7481b0a02563b9c0f191a7deb7e27446954448097e05ce14b14d5405012a561d8c50b829fd148437f590850997afffd420e86c86b30393572ba79ff12aec0da4e4f3356e34e6067b503e34b3f4eb40e02d8c93b85954a337246542e758d2e7e9c9d96ccf28599369fdd1b3222fe076b6414c5447adbc67b1ec394f4dd2aa17f2720fbca2d172c175418de4c1f6de24c40826542cc4449a904ab04971de41ff2f7a50718ecc718d6c2226b93efc1d4e9e29762fe109b10c61eea6dd6fdd9acac7ca59d21cff090282be199af4239daee0327b19cf246b3a00228c482fd050ccf892629de2327c98c18fce6704cbb7967811295a8f4ca560072591c362f4c1c79b443e3b082f7b40f95b3e3b01bada6520d1e1de53675718a72b6920c6f6194af8d33d113203f00a2e7a13818e18cb5c170e92b433e1d3bf372c627c8899303a893c42b10c3002175e0128f953990fa024676677d84c94088256effcdc8c9d51c1a2bf6f3f36f0e413bdd67452851f9e0e05ed1924b5e856772e391add09f7e44c5eb2e4d9637214eaf08cb81895e37d6f3fce7ca4bb595e915e3537d5b4b762803525764f36b6b3bfe9c56c2c5699f4cdc9da43778898f562f4f94418a1fe6308ea23b33f93004c8d991a92a932d92c9437f4be0553330647271ca67d65a2e72383814ab69376bdc7c073d71e2f13f04888c4aa2b006b73d724aa0beb24b25bd6820f5e1fb5f2efa594687d07bc20f7a3e753aa2930b0c3409c12640d73ec86a50565c455a7585f4aa28c5bb38bd7043a2eca05d72b63f45725daaf578f56cd79ef182fa97d04e023d75b200a3418576921e5aa46c2d118d72febdf8ae27690abeaffca829b203507dd006ed18363a748f06ee7cddc1130a3724bace5f1ba2005ce97cfa417e2b90f1954413f610dc7241b9a5fae12b95032af9e3976ada64ea8aaed87111b51611b9cf866b1dcf7ffff3d010d8b6777088724ffc9daabd4c1917668285179ca1db014ec3fdd0cc4d57c787e4ed0c71b6c472d2cc2342bbc33aef5928c5187252393028d0cfba555eed7b07a51c6b5bfc9172c6067938c334c65e6d0d10b8d0ea71ad366f35be57054468d347a1922be4802c482bafea8d319b0f446e8d8d1fc9171cef03bd842ddcdc58104bfb1e4f191072350dca58169f5a95c98e213d8d1e49de2fdd25aea6fb4d5b7fbed77545157a10e6e6851e24eec4aec8f194dbabdb03606a1d2f6bab4a41131dfebf2fd8fe6f72841dfc778ec40faf4344b0df29be31f68577ac59a42e217d96b7899d2b07a211db48878b3ff2b6e91f74654b84c8bf1fbab4357029e5538dc515f0beeba43c0f7fdb2c5f5df505a6d5cde67e3a48dafdd3a335f8f61d3d0ebc37997185881c44cc7e7d1a114ed083e0269eac39b33544389bc36ef3ce0131a32bf514d21ebd1b5dffdcd71c7c68697693a06f77c7699a5120e172d09ae6a0a0f80850fd1a6a66f7ff84d3158a9ff28eced8395b71ce524c68192a4ace1952f63588290e004a72551a6d300476c83c7a7b44a2c797090151099fbcb9a122b16fd6c0329c99a872dafc6e322909a148815fac4085ff29545f673f7ef223b50bdfd17545f1d321720e92fcc643c7895a41bc62faa62e9daa6b8a4614dede7bc61c5b111904b82f7218baea6041f469f61ff5becb3a75154b327238239467df4ed9f026f9cb86a57265c922ba1caec50bf0a278d8eaf95ed8a79984e6707ea73026177416cb1a2500d2062153249c5982fd56da8d03f15c842aa7fd5ff239af4038e58499997cab20037d172e2382b30d5e84d8d07b8c5e829d654b2d0cb674a71315f1f80bab5d72a0aeb06b065fdaa9e141f67c771e9a8db38fffd9b10d63acfffabb3886fd2e1e4ff0fa7995a7d44924fed93c199d6fb479fa2f320a8e6197c3fc817421d42a3c16795a58b4f89d8b747dedf7bc4864474b9b8a240e7da0c0fc8b8d60b79817723855d153a96e05ac1da957593e4b1464dcda42a3520f1556340bacc4d5f3fa725e5bde58996552299ad50b4920452e168555f7f5dfe401df57ec669726425e1f4617ba24c350d701e64f5f00fbc85104e886d636af1ce106790457b68bfb475ad9f9d6beb9e58fbc90cc558993ff8f963b9a867d7f92196d952e252ff8b5ee1a46a29c919c431d879dfdbc50c662486f4a8bdcd3c56ff4e6b9e3cdde4803005c25b007c81f174cff2eabdd410635e5a44f1435d501046f2926df9702a7442b6097d4947498d15a0e9d95b9c26f3071ed7c2564ece01aab35a444d1f380dc4672faec0568822cad4e2a1c8e7332cf1f21e54ffc159ddd420c228a2a35a202357240053f4ea2ec95772f3134007a05a04f079da84823586bd5e62b880dd86ff0507f29db38d99cf60f090f47a9f52ac2b54225c34008e773b6797265bb1da6b4727f150f81417ded7601fe0b57b534b89f0f1bbfe3b188399d64ad0cf0229565073892df0cef366e8c0177ac3c34bd1e7d303c502914bf0500b9beb5ccfebad87243899a0b1a09bb34286ea783d0b7a2d37af32b2a797d24cd182fe39c9e4f5372f62d408d7055ca1dc7661edfb480fea28e545bdc645aae79fe9c51e639b0d372d5a47cc079f0cc3c10fa0bc6b30b25db0980f3588d2ffe708012e5d3c48afb1c0da9b4f2fdb53356bb0d011a3ff6cd9d55d801b139112bbf8ac059cdfffd2572ee8f798c11b54d0150c60190470ffb3840161f91f57def2c0ddefb4fc337473f5dc823079d33f536ba0a39ec3e5be086e69b1d242f15c73a1025270cdaf205721f853ceb7996e9e3b2d6dae184d5dbd3009a0b6dc2c370dcb9d13151aa11b372c6570e2d6b1bb469bdcca3140c007df0f523b08b9b53a3015357575e7e864e405739accf4a1cdc188007b6b2e699c45dbdb8268051cb11635929baa744fa737257cb9b2841a8367219755d73cf257ca1d03264a880eef2eb3bacc02b3825657221c0de9ed960b3542284682abd15669fd606e73db9df683eee69a27e16da9d72000fd815d1faaaf72d9c0c8f4b25652ebd2c493eb462fea76440297ef2404670e6c43fd700c8c7aee581d926b1609824154ab2f5c40f08b35c87d5b386b43072a7e2ffdccf8b0780534c04f4a1b7b5c5f2ce8e85da7fc8cbbc9d38f72fd54472953cd71f2a4e153bb12d614e80e1fca9a1928cf1c459fc1f2b49b23c01fc2f723b1ed0e9927c4e28039dfc88f0d59f233ef26746186e90a83f08d8e78615f7661107ff9a167a1fd2b8cfa21d563476fbcd344a2dca3571c3e77bc3d8ba7d19721bbd52e41c175198f0563dfc56932ae8ef0ba441fc94b607ce5679f9ce9a987201355734ab413b32235e668027ee2a2e3847a2248d863cdcb902edf9c576cf38f701a4849686037faab1ff01e027f05e0d0aeda8166fb09826eb05a1f9bb147287ed0a0d613bee409031376587534963ef132e1670b46263a54fe73dae54693c7b9c2808671f2adbb44e73bb301af75a81a766e741a1769852bc47150a51e1723f35dfff024197f7f30850db8182a3d8b1faff964bcd8d47a5806c471dcaeb723e44c866dcf5a609e4b72416fcb59eee5ed0b4ee5a74e869043b69f020a8e25020145584f5c490c93eb9232930afa7c03ae36b74412889e4bee4aadb308f221424323f48ac32e0ff692fca3aeb7a99ed8465083b92a059810ca1e8a06df4d6726a1b286d39ddcd32e641dd13d96806c4e828720ad3241bf08a9c8386567c0172b9a4df99cdfbef794a80e08add484e118132bd8f81c756719e6b7c8a8998dc31b079e349aae7735f0c51cbaff614a2d482c5a991bdb41c1b40f12b4b5278c167681b436883197af352646ee9d2baf0ad36af2741557207c6ee37fe8c4fd8cb66265b9595186a4adf8cb5703fcf7fe09b9c0cabb8150c33f2072140481ba0f3654311f31caa8862f91f0d6223557dcfaef06d1369bd3c11cfe20f3f93441cca1a622e0557f438e75593a7972fcd9f175cfdd6e67849c5e508197f88f57569e272a8a42f3a16c3b18af6c89621ef9d571eeb2fba5e15510d800f58dbb94896c55f310de66c8892e8c1b31f53bf93be4fe5751f9cada6ca37a22b448dad728e6e724234e7962f9b1a39c83d72abf94c5915300b2939d1a2ed79f615f42565ebbb724b2f0e2fbae32a3b3885697482eb5b2503518659d1020dc488243f4d65e49e5211cc0c71a47d43e9e40721c3b81135ac750888f1038e34f9e7faffb92c912f728331835f876312c0e3377d3cbe0a39b1dc8d7240ced8458c936e01e31f3e317229ef82ba5fd17b844805e04a89a3af1490aba64c8fd835879992cb6da9f23f7226ef183f6e69a70342593d43dfaf3fd05490c8ef292d7e289a7adce89fc8ef47f8a1d020b271e3eb8024ab20c6d5e00dd95fdce5856f4dd141f7cfedf02c994147ba8b371d4967159a986d3fe626fc61df059c95d9839529658f28b35bcac91653180add08866a8c888df579ff8cabb56bef8c5cb85d527cbc51ebc83db9e62d469c0e2494e6472ca551e34cd1709b4efa418cb9befdde12ace1b0db2506164c84a95fbf9a6d27442101218408c1ce812c47d606bb9e817127202ffded280172d4699527c70068249de2721abefa4da6de4fe6788f73a2807af6bed4344db44eed46e1ce3fb82351a59be2daf5319a2bfe88e08ddd170ac946d67081005c7872902acf5ea695e7c3a3a863d64b934430047914c9bc9c8bd4e6ce845ac99e100d8ca54c6e53f7643a2a13bb260b466623a581807c9823cc017fb6497497afae72c6b2cf5577f233811b50e15b184af5af24cc50095d320047ed97b1c23f199872cd7ed2ed98bc01fb9344fa4a978307282ed65029e4ee2dce2ab8351c8bd2f042bd09ff61674df6cf578ae4604741c14621d58ef278ba056dd45e41e3e8b60f72f7ff930de4c11b124d1fe04f3c18f26d1cbd788ab54b2c69ea0bd79c0a2afb72f45d0af36cc8f0671a5cdbb57784c88cbd5797e60665432e09ddf42e9ad0cf725f41494f614586405ad25cf5d6fb3197b33fe58ee1849287506fc5a8f854e22ad140ed73e4ea17cd55cac1243ec7605653705dde7e62f4b7d1f9553e782c1b430469d3cd9bd973792934df41d5e979b054f98c26bebe7493372becd3a4814272044651d30ffe98f025e686004d1e6d719d36c2997f1005a0be2476d90b34776b60823bbbebda024bd74984ee2e09e2e8be733d9715b22862f6b27f1c4b2b8f728e8278d4da8020a35b3655fa89f395adf11256256945277d488008b4e7bf9a72c339c158226d457702dbfd33946aa1dc37279d3f1393c53ef1f0d82fc21241132edd3aaa2dbc13c505b215a7c33c5f1930ecad8a8f487a71ddbf7f59cac5e038533480d775091f3cbd99272e2fce5388e38a84c633799747bdc0cd1f93bc23723458e49a5ecbfeaa8246f80f47c149b34df99f2956cc420c61b7590fa67b2d324bf6b9613bf5b5f2861275a476310802fe2bf8cfc5a1cedb7728a20b0491ed724f77826dc03dbc9b63130fca1a20697790653afd11baa7a235938a06f4dd3e72a5fd334694083150715335a4a702bd30e21ea5c696733a7563f9f757830203283c2853b7a3cb2c63452a2d31243d4ea0c7f4d4696d38d917fb17360b4a410a725fb3a539c1cebee1cfd278ae17af99e3a0e54a2aeb9cc08521caa8cc9b34ba00dd66cf7cc60b01ff101315c0338513cca1c4774f73af75583e077d71fc77f972f9a6155d3c5458c32c095ac8dee743ef05903484e2c461ef50c7155be6c9651c1fb168c0c79eb15ce36f49858cab10ba4bf7fe63c19170a08541eca9fa3bd872ce2ece0ce85fd3f11ecf975455dff3eebc881e4529676046c45c12eecde79b72b6dd33f792a01c5bb8d81c123447b00923822f609f56528dd9780b1736d8ca3ea8e920dce05881e37cde2938d5862f5e872ad395f9312942920594fa5aca6c72086c561c638524d0bc8d06c46bf3af4ba7f9a89265f520135174bb84561c2f65afe4c08ee61973e906dadb04cb8089756e14f156119bbb27750ccdc3aa5c2272369d3a804e911d9f2813338251d38e72047d53943b6b6e8c58dd712375667b25e0c52c1ec1d003ce092456d9a17102cf0503211b93e7efdbf5ef694721aa7f09008928c2f6f52c69b101321e6ba71ff0c9dd31d4fe945cbac99318ac9f9920728a9665d4a93008edb835e168c3de01b13b536a5c8807c2cb0b6c17ea10b25672dc50b0c882d5f4bbf26c3ed9db31d886fe29b6533ad37e4b24de747abc309414e002ccad47df9deeac262700e73d4b0cb1c3df7f81560283ca4e72afbeabf6724bfe5414a4e5cd6bda8a5ec9336776ac83d422e4e6b91bf9bdfe747b1b438772e82b6413665a02ece978b638cb58ce5fcd2ffab040e67329206f5c21eb9fb07277de47f427e7dfe7f5a805a4663b89386af6cd03ca491de4e878cd922170a80b2a4cf46568c6403d93b416bfe3cd3dabe4b6dfe9fb710cbbb4f4149f451eab58d53d007b3886da6b1f2e761c34e809814f09cabfdc4c1bbce32a5856e9d0e272f6dade1c308047041b421f0de58c99313758a3eb10d4076e74d6d5c68f93cf72da890e9dc7c5b2d9caf631cfbc6e6114cfa25818afd465a32092afdd623c5434c426b5d0cf549d30a703bdaa562972a915ec4222555f99f2be92a4a99bfe285cf9ccf9989f83bdead36442574ae727156b6f6635b3be70d5b08acec240452e729a439bb821331f4cfda41903f4a195931553e8c24c96e53a31efec2b67210972dd95b2f95f5c61f539666005f23acc2d745911aeb07cdad59bdedee9f55eeb6affaa9252b43d4597d8178939e139b1e325f018e399c4804d45eba42d70478172c314e05fd082b566a9b69d1834f15186e4de8bcf1b138e9d5f8474860f7d784445da2f47aa6d67c1886f7ba16cfc3e6b6512d5dc2600ec88e0e649654b0334523fab8c26b5e81adb72d053257292ba02f5a3ca78e1eeda48b7eaf44bd96f0a396bc414ee24010cd3535fe455e00ba7d088473c78d6198fffca91e66d022f88729939670f16280409e2aa92b7c762e049580119c9e751e50b64d7bedabb7b8d7221c381d7b252362350e72a261a0abe22604b4f261535a27019fc0d1130c493721496e3761454fab874f1f68f54164e699e323271c23af6c7402ae4a11eccf56b53b6c12542451b165b10e5d271fc18f360bf3fdfb5919826d34e42981fc6f566f1087f88dfbecfd6685561ff7c122f5d7fd18e78203d73e63bbf6abbb7009c7285f950818f80e348b45f4f751ff7db251ba5b1435199dff07e35c652e1ae6f5b19b5ed749a942e37265e546b12cc5f63441c04d1757d69403a80dd8e49af4d72e95c660c5999bec20c3753c009ae4b35d68ac83861021e09530a4502e8ed5c72ff664ce0499b85d05aa967a2e077848ddbc25e1555b54235c28db4c7fd401b5445e50f80cd0abd4fb8feadf834a76e50dbdc345d2d24417592dab93f08d581727bf1d5b62b0485dfdeb11c9c289ce9d5bc134d801fd295399ed8b71313db9472d964417f0b8f3379cb7cc491c4a0c80232e565e7547a3eb7c8f2466eff592b729b6545e9c3099b61c6df5cba3ad37e0232b8222cd14a2a5e551406968fd6e5725554b4e14733ba6d52005504c58594717521d0b547729d5ac2a1ef77ffa8227276c32a353144cad50787156c1e61d172db9c5170a2f818d128941da6b975414dbbedbf1de4afed166140866656d5700df9955fbf2046f657936cc65a840fc972596f1cde04019b248629729161e90252042a0dbef376d573409b391694059d09b1da8e320b1dbcd9cb550ba0200dbdecfc4fc848eab3a3f0c05298f3bd9baf72309fbe5e0e186a073f2833671c6947c141d79a049f55c1209f06351be4d246728e563634dd42f15dc6c4f826069a322a53164ff941baafdb264852b2572fb5491ecf077c2a4495029710df7c56ea0ec99882638f0a10d69ebd9b26ef67a85a6ad5cdfeea445d3ad0d31cf1b32eddc7dc107ee4ee6c9d58bf676a8e0d1456b672a9d49de81755a55ced67b52482a7a1e1f9253a64e2c76c2819b087f2292d4947027f9f1793f7b09230695f4ddbdb18f7bb4221dc782cb3a6115f5ac55e37b6016fb7788f6d12ebc9d1bc3ba45d1330c8e5b73550c290d550e2299fe4180c8a5c20d016a7b600d79afdfad95b21a4e21e430aff30661562f4b84daa54657d3b725a78d7a7bbdae5ffe37ed8524f198c660a38b276e5d7d958593df63866b2e15d2a5ca741caa1abaf0b60400c23a38cf2dd30933016cf9e45e5526a91685f0044e192e67a1bf692bbd511eb07759712cbaaf26b6f6d7fbbf040f1136e9237543dabfff9aa72b161046dc1204bd1c4aeee616be98f4e879dbef11da49d9b204b3266bb551f3a5d334a0cd151dd1242389b9384f59906f2cbf16df27b9585f7d9725c1d4f582c92491b1dace1238033ba95d2232ff4b07077795033990577ec9a722eb3e54ee50aba1187ad73aac7c56ab4c1503baa2a1d81a91c9c2310e9bde666a9c85bf3a3ec8964016cd949ade712cd25649bb28df2c85a39baaa1140cd9172cd308f647751f01dd3e8b7f97f99fb00482ab99b0808ccb655f0d0fad1ef78726bd4c69e798ce52be7476368cca1d5a6a4f85c238bae690385cb93dca0278c7229c9b7550f8d350b7857b62b92bd58800419ae2964a83de7e9ce83b5b5f37b72d0d5f1c422311d4663982cd31f6fdbd6d0f88e89cbcdc943e06e7bf6e2b7aa72eafb900ade29ab6db3ed01e7191d10c3525bbf65d35c572fb2edc849d6c3e372ea685886e65861b81a5223dc160622a2ac6f299091205bf8d150522dbdc90f3ce33b16bec0e11a60f3288c617b5a148c6de6a58f4d8291de6bfe7c29c7a7ec275905883b2d09d6cec11c3b7d17571bf70a1b5c5ccefd734ed976da499c2f6b720fc1555af3a258d3820ecd06c7b943d510d5dc826cc52982e50c332a7c791e72f6dad80869f6d0b375dafc1a92a3a007b25232f597d640ae688b1a1bacd4d64e333510637bca1b558b62c3008b01e99acd32a0bb8b13dd244feb2603b2ebae721e975a92167da0baa64b11ba8ad96f1c7da02d0761a40d677e2e44be01ac9a727b83c39c6ec23e39a6c242c4a4fb17fefa136c1e56c628810c2146a8c7f8b072e0a61c02b008f5e3c5c69dfe05bd843f7973b6f7a66a03b85f9161fb2b32e7727811ca1fafbc72d509e1571bf899c0f4a2fdd8a12238da6458c32feda71d4b3bee3acd7272df1d6f23952a65027e957e7daa3935b2a1b0de8d7fe73ac348b551f06e5ea63b47409f57f2cb4a937a7164140c050dac71ea50ce132da02a49746ebc4c1f0ce18cbbd7ccee38812b98100bc591425a321717797ab3bd1a32970472a00ffa4c45eb1737bcb8124c054334096602f62a26bad80f81465ad7fcc4b124ebed12a51ad6db5a3536720166c41eb29731e256dac22ad451f831c9903aa872a59ed3ec2fdc1c6400998e72f3bcc2410b7fa03c9adc2a5f12433791294e25727288f0af39488aa8b65d060099c0034fb659551f4736e5c85b1eb6ffb8ffa572b2a26c52191f81841f32d76fd329744bfa5134d57ae8e11d3fce4f4678584472672d8a490e36a834dc4e878fa9048b8ba5b98befb016bda382f9e8d1b338a54f1c94acd44c256fb87952a6c4c238fdfade9e71d97b1ca3b219ff5432a3907a723ec592c14c6357845ca8f6c9999a8cb59327b465ff7f5d1275ff9db42fcd147254373a95a4c80ec3c9e2a85dad313d607141cfe1b4cca07a8a0d1156041d8e082864680314986b5f62c0c707280024e10041b4b1302142876d632b12e248a54b80599298100dc38296a189579918a6bf574e016bb921a4b50150842c40322e2f4bb19ef93027aebe178abe97f9fc940094ed83472da615183c819709be75b8607b17e601d93e31855c6f40c10de63c4b40a119970ca8c0f33214e8dc0e3879721fe3139549f4f436b2f7900a5f90f4510def4574c7e49f8d146920ded1d06f262d3159bdb4dabf86c504958d20e5705376ad5490422d5daf55dcd1a405c14e4f90c26faeb7fbf3da7c55f1fdc2231da6631186ae0bc902c9e81a44597e0d1872be8963581d64e97032720666726820df78484df57131e0a5a72f3fba75c8477254e8b4f6a3e212ccc0219cd47367271706f661444d768b9dc6252f0a5e331872e4b8c5e03bdc58d7ef40cc09b85ab8c20f3e7954be9c7dc61306b7d6a33fde692693fc0498fc228bb9e81942ba577cd99caa90abd21ee3ac59ae1b156f3b2d723f76be91336e8115398b3bf22c9f7596680b96f65757d56a2b8c1b365ad9d1720f28b0a706c4f0526ab8db64424e8f8ecc69f1c344d36e0a923f5875a1c0ca585408d28dc1146540541d55c13662da2cab3fd8dcae0c83e2b236ac746b09ba724558a34fd85b4d10ad325e145d0c9e78fa43dfb9499b3d666d454c94f8c6625587de596752ada2fe1b126be467b6b635c2a3245d64b0189fe1d320fe3a897572117228a18af54aa833a29ffd6405d46d9b01ac1fa0b71efb447055455151f437ae1a5a8b4a2f987fc553c27f25ee2208dbe76f80c5d41cc5e87a8a6ef7354b72572ee8ce88a4af98f9c58bbe44e394515d49e744a521078e19ff7604f68c925bcf469485d28d5f46fe6e48e9723c754f34076de52b78fbf54bdb79280ac27f72a5bc866aece8a264cdf3160eccf6e9ae519535342c7cd4237979b7e70f10312a0761ccaa901df87d66fda6e8f3946ad829cc3cd01f6c10bbe44afd9d691a377245935d1ad81e8320c13c0f90177cef629e1dae1222145d0c09d4fa200d930a702aee9b40188658f84e981a2db02d23ca4171939bc5f4bfa030943e2770ac23721655e89355f9728d6b5e0cb09bcc9506de28b2d919cdbdc2a24d7dc1c8969d71b515452e9f29b1d8b3840aaa4bdfb5e24a72f30405b265ed6c8dd3acb9b00072a894ce4d91c37d32404921954ed3ae3f22e56306d116398ee2aa77ea2e3b3c607b12414b9e6e08a98f7cec8e098c12cce48a91132c648abeb011be506d068372be2cbb9b0b30aef714c255dc956b23df76b1f74577ed280f2c0b2b4563fc6f6bca048b1c7d1a8618be8eaea833723a1348f3d9ef735e070d249e229ee32f58338044469a900c10fb9779b30919cbfc8e63c292afcc417cc7e5f8c9a42528ad7223741a12e6406996b49b75e42e8521a704f3103e34ca6e44a4eeb1cece048972d49ad77455b56f0d0de7453575bc1c56df79b2252ee8387159363b23e976087204572a7e3b9d22a755c34478f575cf120c7ffa880bf8537ef67a8e64158cb472399c20b0c2b5e45ca391dc1c2a391f9f39c9e0d3c7d8030d0d744fcd3436dd72f484769c3229d09434df855828178a205f49141748dbf82d13bd454135eb170a62624a4581d81a363ec352e63769fe47a3dc0d0089ef3ac60961d468b3e33e0bc7d6ca522982fd498a4cf96f5bbc032c775543716ece0ecc142c29a2a1261c72f38768c2261f73e1be520ccb3ff0682ad48d08337da25a4839828d64777c146b415778f53193bc2c3e7e94961c2217d4048238c63eddadcf48354b19b9202f72cede190580e79e01704136438008c9ffd3a705df15fd733ca3afd6e959368572fe39d1d85c9e6a253a4eef12cc6fda55e188ab6e5177c6602c958dd4cb777f67f6c67ddc7e6fbc0fc3e5a0593e859b3f3bf37f5f6c2eecf91f8b448acb9f6a344e19852eddad0e2645800475006690ba92b31f84aecdbcb1b697bc505a21436b2bd34bc98515b2469fc198c169290b6dd321171e34f26863950412372468e60f0ae011b1af83a1e7659024fdfe55f3bd374adbbc392783aeb99742f20d516d6f044d4bec0964adfeb7d223453901d3f3adf2df67f6a2d7e271ec764db2a59905672a498d28679a01728982ab0ecbead5c80b0a5744a8e9e181849e42facbbb282b8d5876aadc87dd393482c2769db3f78a555a17201aab504852d0e4b7a82661c354dc03075f58b28e16cd38850afba7590e1b024e853ce14f067c14dc766d324be030191ab92975ebd19deed114859b9a5d597666c4fc591bf945dae277ac64d8df24f901e34bf116832aafbb3395c8672878019e095d2c2b4f97e1e4755d727fb1ea538296d9820c1dc6b422abc6fb7bafeb80b6292ae58f765e385f6e8c4e95a20b11e523c30d1b07a7ad387262365d6eab3189d499e630a426d768f45c4306ea5ff005946391bff0e176158f9e74d2359a7bec6cf8048d627ad5f5977972a4ac53238db679199e86d180913d36b780a12ecbcb2cefc593d18b7b77210d724d8b124185f9f9c2fae2f513c23d42c22227b49cf3397c4ec99dd059c9d6be72ed1ef45dbb1610930b37e5f39eaed4154a90388b98c97879692797009eb05c4a26bd1b0348fb6d247810ba97aef67d7df223d548306e7f24270019ef4fdd8f727f7a00dab7e5a570618f4d7e56e1e8c02ef3beb7a972e90f92c5e13d0f07314cd381326d68750e5a48f695507a9cccf4f3dca6dd28cb0850efb578028d875e5b22b54a5e448f93ab65c3cfbb6e8b9a5c84d4edd8725b3c2dfb1c095dbc54db722c5185c322dc405d22ac650789a88b05bd79e14f8517e345ce63b5413c40c3727c8b50686e55e83b0206bd7b7fe930bc130de465038684798240f20c068048656e31673e897aea63c82229df1940247ecf68a7843ceb08c2bedcbc4fca854072c17fb516660153bdf026736a337d8d5ed6754c5f660f3c2bb456d4589bc2a17246b170e9465f4a5e192d759bb20b11a0f44757a296f647de84cf190208fd72560611e6c52e1dc5175589817760949213fcf129f4747eb29e30c5da0f42186e72bc9640c797317d4325f27bc1a06f84487331a46f5f04af709dac4c8746147672de1cd14516915f610e2d3dd9d7a94a1f2a39ed79203be7ce2e4b4cf5220e3d0e92d541c3ebe800b3d8ed7954e8cba496524441f5d46091acf692b3bcea0645255d232f688e19215a90d9c26b7aaa0d512c305c519ee8c57bb655338eaa5c337290d15308787858fca5e23643577236131e896657a0ef2ee80d4cf68001af2872a2e53dce55f586e2493a2530f287d0a243602c9823ce6c6aa966506ff1558438fec4313f5a312a0bef74daed559a37c5a263274783bfb9f9d6936a0bf54c2b72d85e084b531c3c02e32daf30c7875d69574846e1200b904fc2c3ee947f86a601752087387f7d5901d384afaa17705e3292af06ec4506d0250c3ab1dd5f793c58815bae66201fdb932cbce2182b35c08269741627a459393b81466e62de731b090cdac6a6d4b5062ad2ce81e6c73c495e1f56eb659dff5feb43523ff8d7b5d47244c969dd26f3d185c248f1dd09546d0ec640ca61dfe554e06bbe87ebfcaacd721caef45c7f6adb7a049386ace229fe1ab02c6993b689a6f843d35a9afc124105dd3ee0c5a52e5044bea370d723704ccdbc820ad2c45dd43e1ff8c2ad21895722270ed173af2a2bb49ba68276d2f17ecb0d58aaf0b69aa7bc7c51c008b4fe0c72dd7d4a90059ab6fbe54bcb0f59f3ce3d694ce93618bd2f298d09113f6f0250726e93e919b5008137f3fdb43c0285d0c6162fa09ffc0ec1cfdd261f2f8f19d130c5c00a6bb4941106f1b10298b7a50fc820fc23b91d36b434a7bc1ff15c02b46eb71d5888cc7995479f6aca376f423e4c59233755016c10adb7e7e19115ee3f659f679b8781847b11e4d7ba807bb8b78fa4992afed0478b37b814bec5638cbc728b47c6f193cb70aa28a7cfd49ca34fe632460968a18f182ecbf98c22abdda172148f4e05b30cdf0bc6fd80604d113aec1f5c260bb605cb2c8b428c966bcd2f72a95d4ad38c8695b4583c1083d8c1ae76b890ec9f22091ecc71daa4ff8b20b37239de3d9eca1fe15dd14076f981c42a9809a2cee4656ca0d1ec7e25082a199572c8502047bb807b388a107b9bc97350d470cc66103d89f62236b7f130096f4056bcd59385373bd4e5789d11b24c22aaa88b82f7f342a7d0d744264a9f47f1f372aad3c211cc43c9c8cdd642a6b56f6f91f03cbf584f1e6abe94aab88ffd375872e24ae74b30b6b67781f45ec611e8db291af9c3cd3267826bcd1860878c31627210f03b8b130de58c25275cc4ad93c3e0a4924700b7217fc9f6138741d205962dd63b4e622b89d2ea95292b453e4e839115d67d9f88798491344e78ce9600622851f9d7b9bfe16789468de68e3c2a7966ced8b6095dd866d5716573e50504c172b029e359ca674a38993a8bccd03f94ce11cb0ee0befbcf8667b184d53380d4564adb57104155bcede4f36e999e1add5e83a5c1c18507ba38ca86e14d4dbd79727f41dc68a3fc068801bdd37eeed96bc23a4e987a335a2e1f7936ae7598c15d72a5975b4a7e50c95b6eb8e4c0de8aa2dd756360a4ad292ce9e11ba947fc89d750e84e57614ef2989c3b236e22057ecda1d32aaf454ac59ea778ff715000980672d6d7c9ff0526ea46f02790ff1e166cac22ae0ade79c71e23fef94dc8035d52726499f924a304f7376ac6b6b66a9557cacffd96d679e0d6ee769f81b38893e61e666c04e97b06aadfbf8ef4f4d65565c1ba5debf4599786f06d6d876de75e6872a9cd03163f68a7763058ead6cf9d2f51c141849f00e7aa6671b461b5afaf515a88d52d790f42c07705641fb11e3e84376579a8384da12efede57b33bb461e04adcf8fba8a89032d396477d61c3119bf18cd7aeab1fa2992cf6524c09198d8663243d1b034532921deba3089a5add62a3fdd70d7e77642b4da25c379c6a0186338f1f9530828e3f071f669b0566362f897f47ee0de77cf6e3478f28caeefa3837d7be3489fe1e4dcdcd41a70fa475372cf7937bbf0cd4a4dc6d80ba5a1835a4726ad6c1da9572519e2dc6656c71aa88e99c5161de6efd153f5a48236c01a7c20eee70e1c6a13fc955ff73fadba5c52ce4734c1ea8e79a9f91240247ff4f5aef72f59a3d1f7d4dc716dfcac65fb2519a08382301e72e0fb763b442b08e51ce1a15c6570419ed3cd5419abfafdf955d3e3eb718398f3b45931bc0807144d7297272c9a19151ddb9a073f7be56265a060786225f0010e7a79ee8a8ed649a5a13b272aaaf86fcae82f2cb88da847d7f3c5532b6588ec483b4aef197b356a47617d77291d540ae8257e2903925ac6cf63fb9b232c1941f6354b80934f7abb045764f726d51f976a3dab5f3d7285d4a536e5abf2f35f4ced472c588fad71bc00ba0ac64ea9046b063472710aec4875dc05fe13ad354d99aadd571710069ef55ded26472a8d6da63909676844f8831b1f99b74168cb50f055c5b715a9f942e0ad5cdf906905ea29a7c0651cab26af8431287c19ccaf0c05ffe99a230c69dd337a838e0383a67dd0cb63b5b400ff6ce1b1d9c74c54734279802027e79c5753e88fab39872f59fe7f6940b3bc4ce4056bdf0c4a8849fb22fe57899904ffdbc62f56b05d172a524e82520e08e58b82280419f716c5020f689677bd4ae0ca6dfbd37f81f652495c4ebef9e77094c87c2251a93ec268ca790346a3c8e4d14d1490a5953eb1a7207403400abffa9956e0393cb67e67e54092807294f4cb142097383e37f41f230a4c5d7bcc8a08579118224c0c47f8fcfbed6df1f723bb905c922372171ab963b7c7caf6406557dddcfd70e9359f54b5fe1c6bb83b96499ebb8e19e7abe1ea07255fd5ee4161f73f65495ffd0970350b01249a19dc3d19ab684c0affdbd787c10613602b95c4ef4f0d4687a31bc94dc61f131e800898c980dc2074ca33c744a7292b9ba245c4204586f2c82e4f7f43c67d2c8cafbacfc17184b728a5a2b0f89721c0599da97bc5557fa31cb9c47905b1b82e29bcde096587dae8e24725c52f9727227bd84356ba3ba9ddd9b77ddb4e6cded1744e73e57b1942e3cc2aa37f3bd72307177db33bb57496d31970e986c0ff299106615b0a6c60c85abaf52182bb67270fa324c49ef89ea0468348700e39f28ac4c47ee2a5ef5ba17529e9d9a58db72669e16cc36a0782d1d9dcc969e100460ec747fcaceb6f78431c05d42003c83727d3849bf2be427b9008aecdae7d1ab528010b53d19678d0962b6dd49124fb83a99fe747557a796df9cd3ea00e91eca2d51f37da6b6d7a73be1aa9e370b38c77296f8128ed3aa6ca06feb2b50dc0470d96fdb4c924a6e5653ab858b464286ff3fadcc29deb017ec0a3a26897c7185c49c05055173a5c2114541594ef6762c695fba4a41db035b01ce1c8246fc6dbeaf1e9b14662d2da6c5cc2d1889806d69904b2923680c996332e361f2814cba9cdeb31d4cc33244851671d72fdc4d6d9a7329ad9436da64328131970d535c6f0f9de74083807d9f606427b7a21c2a67b338727b8c9cff34f9e433efb23ac87a80ea4d6fb0d88f97ec4a6ce8d454738aaede38bf91249e3bbd340bf373f47bc2317d9ee625b9bd1c15533f003e3a4057360f6c00ccef6c365b2cb235a50069dbdbf357dd68825cb7b46b6b4a478fba63540d11cefef0415304055b0e9952235846ecf51e480aa3bd0b714e6d851997e8099c72fd9d8505129c19978b6b709339189e7f6bfe2fa22727a068faa7e894b096047249ed72fd7dfb530471db102f7d1afee041526f99779e8f16bbf67d69f4fe026b3b8ca14599dfb27327e4a7b91482ef9757a6f1643f083a3988d4b976f2ea81093fd53bf2728c0530f56f076ccd404abf22aaa1ed416240d8782a260b4b3b63268ba25f42dbe5b05274b43470f83c915aa3bf79350388b45a9dd393655913c93c70d9937f73d32ffa14eab23a67c854b8ef385b96e80fa43addfdb574c91d1372a1e7f8193fcc811182c26a20ae7b540d405571968d9a6a19dec43dd6a3a918193f3726d54dc887dca0668028c7c73acf7f3864033c9837120573eff46b0d5e72990850ca735955102c9ab39b6dfb7c4dd8213b3c9cd4e03dc5debdaff8f9fd33a7ccb9b3621a1faf770069c57725a128dfab1d54bd31ec03b5d7d73d95c7e1504d5fa673a3423cc189e83925e498c72e5acc40fb54f92fc51b855b6e4096eb672c94f8e2062553dfebd187b4a10bce0d709c8d619ac5b06605940724d7221d7227f20cd606b4f56212bf3fb4b62e8837101757d497b4a201e48a221cb19022723a8ac6e68b0157df982d61a2753b1a7648d7b4eb9eca9097adcf54748496b21c4c7e2f7630fa3813b5985d6545918b4f642ce4b43fc7a83c0c1227bcb882f46bdf5545cd25b4ab72e8ab1f668881d9b7e527114b4c7de36b89f0c2f966d58e675dd25030546c90118be1e0f033871eec6d6130e4719f9ddb3ac45cb16ed59e7228c895c0a56f6e3c0a87a57c35bd77ac42530973cb6a7c67116b475fc20b96729955c92cd41126cd8a3ac5bf846b4fd6d324f10f178e1369288d7e078ccdd572cbb9d2de629af987d28528c88b5eab5ced501a418b9f543aa5e9b85335c20f72ae8d727c2fab5c2641eab8a54524c804db021bb32495e4367aefb480da9bdb0ab9cdfdd3a5fc2e63f7d4a5fa9b4e08758ee025bdda5dae6516489f46f9c4b972af900072dc298efce8bc45a86d1ba11c4021eb46d2f413700d3adcbffb59ad1cd25b21a5b60670eb522f834619e8650ef6838f9022849f553cde20b91fc50950e7d8c4652e27df7e5369f043848845a90d17ef0a5ad6cc89dec676b8a89ffe728de0cd3cdf2722b527d2f09987a186b647c1690cebe2984fcb04c9fc74947c2ffb0d3b7e24cf73082427e24fee8d7b79a95b2746f5795b036fab6cb2590b4d72ad654c28990cb92e295747468101d6137d91a961627d8f8654caecc4bbdcdc685471bada7e1b4d6d2804a01137571ca36e6744861f47b5a2e3987a904677334d838e50c1cb8200bdac0b56ceff97ad39d3101dc396eb340dc1f6304ee28fe772a5508bb1d2c18bd7b2c8bf69e411514e2efaf5bafaca136800578fbde914ed3b581f760497870d9b82050b663eded192aacf10de18bfa986a11308d424e85a7206d096502ce9c7798dd02340b6247be68634f1d5f36567040e2126d6dbff597145f3457aaaa8bd2095a2175b12c4a916d3d2ee93ffb5a3b29a894dabbcfdca48ff60b13c8476b9f3a5402b61db7e4e8f49ce1052d43ff99b543b8dc470524972cafc187dcd63d856edcfce85bf3ca64491566d6cda9a3f284bdd0215bb66d05e5b76176a26dd8fc53512c851ceb0b8237a7f23d4e9533cb22ad136bc5863335157a0573946842f419ddb6165d9e1a33523c5233378c2ad323db311c88c54b77274068485ef5ac41b52976867b34d3ced079e05afe12d5d5a9fa608d239400872855be40d1f734449f61b02036b5b55bfcd6b2274b93af762a1a424d18066ee1891f3e908ae35662c7a58156bc4e2209d754a39ab580ca4dbb086ad8bc0e87123205edec4821b3a8049e1af52713ee29cc7de23fccee1cefdb1acd545de7d8e2e0525f3934a0a980db18e70b6af7c301dcfee615a758ec9218a8778fc6e425d7267de0aff532755c0b376de2bded82951a4aed27a5f2b615848a752778957153c35c59064753b629852e10367f1eda22c376c92a1e2dc7aecdfad838172c954728e1d8b94e2335bd9f86a72a64b8a3b89df3b40b158cc66f0cc656940231d78720f3b0dfcbb00133905b4dab2c9cab45516f28597f8d15c9c2af3598ee6b646720c2178658ee155e13c7f0a79967465d8d4990d8680f9d61efb8cd98306d99772a3ddf6daab12871ccec86d596ad6170ed535e36607b50887d60057ec720a443c247e593c02007d10854b9d81d5d302db7576266810c35648f5f77683a500766bf9f1f6eee4ea1610a19cbf65e57d992055bf1d0b682ed0c0c6008d24b983df72aab17c33dd5399956c30c48e2e9f926a65cd7c10c98788e773f149d01ac76372a128933b317c0beae61f625d191eef0a037f055c7211243a803d1e10589ca254acb25fa4956d5d6896f3ac01e4a333bd13bf9c5259643b58337e41e9489337722f72542aef39ab6b333c0d0f35b79bbd451d1fcf1f7279bc24885bfe4e0d7e723dc331e5a897a331842bac8dd406d8effb0f54b53f2ab9f67ccc8e2d0ce3a572e246ee939c48d3f9c4470bf9963bd9cf31c25d9dc7dae6f1e29bbdf16bf8217273fb995b419a442602f842b0f8511c86dd31347df7ca6153ba88d5232a4e0234c9f765db8bf7bd2fddd4e759a0f29209bf6088c414b44beb544b65cbcb2c11721442706b05d94845ba936f97fb75a5d092aff0756d6849c17406c20a750f947285ad4cf1d9a44c0116912ae9d0aebb942ac64e8fda44f85467cad5d54c4a95726059f0465c35911e95c31c760b18fa2bc3c8326e43f00c0322a68a567717cf72e0fa753ccde99240259632d3db2be2320858c09bc1e4ef4b2bf533af75eb2b728857ab1cc950b1fef2369c42580632c66ddca4c4068ca901ded3403ca5d5f05e978b84796ad6bba1a1b333c33432d96c3385d80bf20a6c2ea28bf4e70cad1f72b831bdc2d7f3fcb0c4781afc324a0fd9621e6d69e83b0d21e542b229e8feb7726b470f1230df9ed155253fb6a42bc17468a59f86929d535d1941440d9e27bb7240ea8f3d15fb589c74401484144f26b59c07fca9bf3cd4c392cb1104053be85dfa3465a686554f9db46a9d76188844b9fff594a88847e18e2ab29a84a9de5359506a5f3cb634cb2f1ffb97dca000390d6dd91618d7714964a5ca5587f0f17a72803d6d6ae9bf71f90fdafba9d5fd60b9fe65704269a2e396577595242c9b91726ee6546a060af11dfb0e65b11bce11dc428a0e76bc030a1feb1723215b17b96573497a9044be4dd896931dd8032d75da34e6aa62c8495a44a769361e38046d723cda5a9e44bb494057ae83d3d0c21693f79f047211142909b0da4b80d11416725c700543c27c2ae4ecd8bb5aede381a57050707df5567584d7a54e2a0633cc7226314bd62602cfd4d5433342d6c554ee076f6e82dedc53ec175d5125895ecb72326ba34a50c5d09bc0ba1077af1089aa4aebd1070bcc75f1193117a10ba7a472e5eb7ea8d96bff16276a746b7cde7b84bdec6fce0d6bdfdc4c1f48f2627b6272eecb49e87f076b012a022edf9125e97189d308f7679e8fced238ec5d09822772c8dcfc76ced385cccdcefeb55a37e24dbee503b876627327bae344dc6e572d72500f17de1cd68d1b2c6f9d2a146ed1a2d9a2e3cd404955453478141a964acc3550a106b534000d3034b950ca4c9789db96e94feb747a0e90ea0a33a0092df172e76fa9703065d2453d47e90ca8d39f791247fc9165fbb5e323bf6c673b58de3529ac1c250d8bd00df4ea2cb124232eee095336ceebd4315ccab26fcb7f39ea729612c673a448932d97646fd7c95c8f7982ba5696f630a7992a1d4f0c8d60a372dd9b4a318d3c9568896404cae8142208736c63015444c0631485edee1bb7e72b52243be734ae1fcb78c8ca9db85b3ac61713acbd01febbd3392279a8d1ebe00180f5c1d031c1b6f0fb212bf3dfd9d5e9493a538e395dd074b7d3603263b56311fe7463c5d0f9218cf5c0019567ede27b9e51844fe3b6e8b60e326cdf751a3372f1b0d6167e422a426d07166a732a8abc77a297a6f8f32a7deefa9491a06f5b2c3ce13b1ee3e9d58cc018071bf8bc3451187ef19080eeb92f1a1d69a63aed7972f5aea21248ec02cd911376378c7102604de11b3983b197ea066951cf167d5b1d8be34497cf49506ecaf9a3a077ae6e8ab63a901f483985cc76c79dfe9d8e7c72602d6568a339bd16b506a4f573d8b29241966148179ab59fc37648a1f9b42e72a5d00e0f900a8084c17bc5f7210c035bfb35df2582f09c449bfd8af197b23d5e7827d5e3bdfc6f66ab4a59248c0a4353af289f239ccae482611ddaa657bf9f72c4df3e4351ab29f5e57ba6fca06470eda41984c463574c196876fa96b4066c726125a94ad7158695bf85b1816d2f6df244eaf5f71bed26c7fe28c30afe4e00723d6a688ca5eaa669603e5c9866eea89a0e581a841ee3c9f6dd4177fa8b1ba672f33ec3d18e99db912f47b161aea0de4e3c3358e2d6ba49e728d4fc3d3fc1b672d94a22bec37485999fa4cff9eff6111bb9c036501e2cce9e503579ece48f21727868e43493cc95dfe8ee4645c9b6b2ea0680ebd8e0dd82adc93caa541eb2b035a4232f2c7e69dee6541e1d8892f5afcafb3edf260b9e765ff197cb9f7512a21fa9792135ea2f4471f018c8cd120ee3ca10e575de4fe017cde3a44333d87f8f7249c51f8b2565692d172faad415c49e2dfb12388ad669b4349304974accd604720616ac827f7dec990a3adec8f50a8761f247eab489206022132f59ed26838d65f0683f1fd0ed7bb0cfc64e3b5d04437fe87990e40dc5b328896bf08047b6a2370195bf0d403ccb9d0ff5c9e28a52dba9200f523c69f4f0eb19d6f9d35d1b126cfc2ce647c82f443a83942d96a85fdbfe71e9ba9161e540899de792739e2ada19ad1e988bbc3dc6f568c9301548e3e06efbcd37d7d587b77952201229c20e6372027d9aea7f69f2bc2cfb65b70127634eb38e7c3f61a83e44e4bf12838f58276e193cfc49576f1e54c7d8aea02d3e146dae66ed3937c4f17b4fb4e75fc935b30ca082de0a79443d7434521ddf1f5840f27a71dca083834300b0211aa84e686c5b3b677c86260ed6f57ff7e64e9a498b1d21946b149695679db496676006c20272ec8ac5dbd307e841d468aa8e89e20d0b3e935fe90b8aa7a88309fc5a92133d32bce2ef5a8376e987547e1f898584adb1846d8144ac4e8c9edaff59dd0ece2a3c9837a0ef188106b8e6dad3bde42eb324c80b6388e44a84bcba02452acd3843534950896af364ca74927fe4dc314f968aaa5d9d64baaabfc8671c409138a02f7299618fa370940c998dec9d3c52e5e36aa702f8a68f4285cd704c20afd9f9c540af9619e2b9a1ee9bf93c04aa8ef56d8a04f28fe9a1888bdb2b37f071a9fa3e2627c03e6a436926b0a5dc2f6842deb1fe738920a4fd3d95d22b767f51d456654a2654df15a6945bf515912189dc88ba8c22607904719540704d824ebe19391f7249e9d9acb172d88b55b4b836cf922a057c7286fbc96be42d7c533389eff8de72594ae7f1f305db10cf1e7e42166df0f71db8ce977c3354952c9ca3229ff263728ccb0bd8549e0670fc7fd49931a282f6d45f0d8fe97711b89254bac3a078072db24b5ff61ffb5b961db361ed361a1b3bfccaaf30e255e76960e4a4eecd128072526d774ad7ef9a30ab9b2a4f926a45adec84e97ad5a35aea19eb960487859229af04324e2c1c11d7aa833c3bc4d4f508c1c389f18e68cb49c475df61ae59e34a75d6b68bc9b4557309c1766727d8f217eff514f53ae7c2f2b4bcab2d89009707bb17a9117253f429071afca4f35767be1cd51d04d3eb7ba49f5c234068752272bb77772572664c0ed39d9d6a5c652aa2457a0e9081926d4b9668d7e0a34063720b3411afe9087ffc7c0b2f79e57785ee57f44f9fdaa9b7b8abf2cdc629b6c4721aa54a62cf434c2cccc10343527d485f16733ffe6841a9ed00ee923e14dcc2724993fa6783b345bf6d83a8307fa8ee948f8b09cb3c46baa7f48b79b0517211333fa9020a50ad47cb6c22d9640d74935052e8f5118030007039cc50ac6bfd175c414a3daa9f98917a3c232beef47a4c10ec47bb616e58f225ec6364accccd824d62625c29fa1b9310da6e72fedbc2d96ebcf4fb33234f1b8a95aece3e977ecb609825105a8e83f1dec065ac8e28d81044feb6154fe82c776b77434bb39af9587279ebd686baeb098b9b2814c204cc6b27db31b2720df9d48e4146e7fc5a976c72b914df276f0f05aebe116ff9147b8fc7cd9c3268afdc004156a5f17b7b09247269cfa2a5aae5541560c812ced4de707982d42fee45cbe4f7be9f42442d6a8a72fd913263ba8d9cef8ac6258161aa6559eb70c98790a6710f6866ec7ed4bb821d3d93303cf746010fb97a0efa08e1984f00638db01ed941267306e115bfbfcc599cced5cc9a42d9e01367e72bd02ee6f2121eeab41f88f9157991137833cf7b18ccde6bcbc77aacec3fc814e51c8806d4448f3dd4b2e0a77ff05f733da13e825f9b44dde64828f84bb1f69b987cb94fb8177b71ff786ec3c8682a19808114a672d036737204dd197d9edd5e240aa91e5005025f2c6066298ee3b5c0e5e6c78e7210dfe28559ea030f7db10b60a79c521ea5c7b32b3f70effa49e0a6a36c168272b9d21e287276ed53d51ec0675fe55a6f02d5352ff5732f8047baa991f0d7d74501b5cb639fe8c865f096ea031daa969634ca7e9dac3206fa31984e68fea63b25400541b62bf5b8b1e50e4765996b738957a8f8006bc56b9ba7df30be65366e720654d5ed65633bbccdb9b7d227855f748183319200a6b91da20ca053d7d08d723bf16b299347e573e32545db07fe894ac5c61c627423c269c3a93543558c9a727fac26692a425dbb0caf9bd02ba143b7d40a0b7d7d9a4e08b1851016ad3fa4727a7995294080f19a540803501a7742ef6b8b9406a5edb35ae0f24bead9761c1768a6db795541f9dad13d628b49700a25188b8fda14c03d88d9a55c88e742407216eff7e905f12da49e2c951e35b0dd3d79ccf64ba26a8f2a97bb44be7acc8c72b15c577fde9e19c7e8921f22338c075e7537187e698f327b7b550e2d2e56997205847a37ce7c98cce77727be8f3cdd96a742b472f027bd0df358e1b3918b663c86473d5aac4fa10dd43ee28c9d989145ff4a8d8b3ceed1ea7398865a75ba1239a4547158c81ac901c229dcd4c0f428ea69e3242aeb9f68ccab723cde822dfe724b340591137a72a1f616f106c5b229556260a6685b73d3477c5c6a08a7a22f72ec4ba591605508d18ad5ba6ca889b050e7b084fe70a3ffe2a6189d2fe314cc72ec9bfd464eab934e7d4d6014a3ecc288814cdb4836109a5d75beadbab02370725bcb5c6fb29d26dcb4f6f86bfe9435569e9b03af89b42263eef55fca2489f05fd8855dd9825d14a1a5c536248092c3f1ed90dc3915e7ad26bbb8105f95f40324e66a2191dd626cadc21dc049430b54898ba725193746f516a45bc90a73239b72fc23c7219a251114ee06225018d52d6eafc87b7f254532bc8b940fc012604e4686aa1941a16e40be0f5cb77e881df1e40697a034947898cad957444b687d213adb0e5bd89af9507f69096f8d62eb0784be16ee54a91b87bbde18991d6f582e72b25a3e060fb2316b1bca7d12aa0e1d29527611a3725de3677b86fc6d95f67a6bdbaa6ad287e8408c70a601e10c6b5c4ff610b98e14b965bef20c538df1755b72669dfe6db97d6aedb1db29316c30e8692c9e3e5304b5ebc18251a97ba8946272162c3ba24c5fb7138362722ff857d51120c4e1b014340534d3734f0d99143f7245ee22b8892cf460b293e5c1e62601094d568d6eb78742a018f013baf3128c725232b894f33c42652afb4e720e15903094d393ce791eed8febca7f74b381de016beb06dbd09c69d794b747ad38d20971dfc141373eff46d97ad3faee4945d3726007c7654db13ec306d67900785b14efbcf08ea566188e2421cc4221e5464e17fc14925310b7e665ae3b47f4959b1a9e02ecee6699e938887258e118321efc7245b3167c73b7a392dd5c62edc76c3c29b83876426a6dbd37d1383b07c5dcf4722dd1e0cdc754382247a22b86185db293ab6c7c810ad39db528df1349dbd35d5399a98313dba6d4a23744d2a80954d8ef5302d248c62e84461b30c80ec2c13872efe4475293fd7d4c4ed4e24e256e6a68f102a5ea65e1bd8c72026576ea56df729778f0d252ee1c4e1d0b3e46eca3fbfd7e65eb8540f95185da5617a5c64d0e3dae27db3ddabd08246999593125d324d54fb5cc955b07370e1b7d361487fef97118234f552882126f5f7ef874edc58669d2b930b87c17997cbd88792e453a2e72000b5e2e84b0e683d35c512aba809f1aa6bd54f3a00e0f6fed432b5711534c4e52ae9a7c97e2e4639424090f0f0489cb165393427321e8df9ad80ed1bd72d120c0beeaca50c4c9df9a18d4ef5ef1d10466b0b0e5b5a530ba77585f2a469722052279d0889444f043e69d21b4647cd3f7af74b91805a2fab06f6a957a37a3060cea09306c766e0319747d06a371717113fafb80754a07b7c5800bf0a9cb868a7242b1484436dc6437f70981150885388205c922d46b1ed3fb2df2ff563f9b537230b154311a05c383af6e35550d90c3aae597e9aa6379b26956a8c0087292dd720d01d14bbe6c2b7ceb231509460f5e56e2e3f99f84fdcfef534c9fac53533872b6f9d26cbbd0f46ad7dd521a11a6a7ed272865d8e48520fe17c2be72dbbf7c4b70027218fa9a31fbcbbfdd2893cfe4e136ec610c79f314b97d9082ef24f65c72ef3266a70f95fc2090735d5b4759c01eba2ea7fdec7b2bc20e07fd085d6d60148e2c95d1c0a2ea224d113516b16bf459742913fb93509213002b216264ce7372cb74f70d06ba6f88aed4164e1d9c37eff88213f8852c3600030013b8ebea255b03d50a9e4f47a88c79c919cf268a47a3cc05c5fa2ea5f8fac2a20161fe1455722da9b1041269b4b420bf0e0fe25e963755a32f5a4eb4909a9c5015c4ca128f7233843ce6be2cefe1dc7c3f98876537a83944357115d6bc6cc2e411fa2d30ef284a67c647df4016b667576766a0e458f92c5e8140ab2bb06b69bf571b01e06854282dcdff5d0fc3d3f14c71c302d25bf1bd5c034920d1de52dedff96e57ff1e2ea712bd56fd6c673d821fb980b11c34fe0c6458cd0c9d4dd302067d91be062f06d082b76addfb6545ceede64567a0e08627c8ec11617fc157d013947fb2270c344fd3dd5b6d7136005cc0567380635ce43185d48fd09c1e2876198dc6a7bda613761f3a253ceb165dd321fb09ada96f6a0eab8b099de178b7b94091bd92094d728b31f33027fd3033d887b01eb10c93be325502b94e02fccbcc05229d216613723138d40aed90f12d044642f5ad0276321194bfaf77d9c02707136fedde3887720b8b739d851939ea612e0f748a500955ad4f3d4ddfb3b0ec1a413c562141740be58ea21110023baed250cfabda3d70fe6c9c03e7a61d990334db4b8809327a10212b1fc5e8af2aeaa800aab03b10cf7f3c8b0bb4eff8bea6748e3fdc94f5ef72a951a8b283fd64e6f8bba6c7cacf3d21c01c6fe0cea367eb51829e331c52a749a0c1f671498cb5fcbed3fa185a9d2b09e47861ece731ab91864cb7cb5b323c72fb8036eab1c2870bbf4cc514a987b5028deaa4bd916c823478ca844d3db60572876c587bd68aad6148cd4a5ffe7db4cc2009a02dae430ed13018bd8a31a96c729effab607aaa93307461939fe02d71e813f0d09ffecc2066e2a129a2bab61172aaf280bb436882e52a8bc38b442da781114627e57c7cfce7ec8a870a43284672d828bd65ce78df2312e6390c3113d283120b9d1c484f58c80b01e4adaab5ba025531479f7eb6811be2f9f66a2971fd295e4aeda4bf207ac55d3598d6f8b42a40f0dc132aaed81bf6b6da9859769b887c4f8968a4f43aad86ca439cdbc08e6f16928d6609b00a5aef613adf7d3ab922de35914371ac1d11a164648a29a9565772d96406647efafc33c16901fd98adada67e0320dc9a7c2d77dacd0fab124c327221b3a1716c08a993ceeb2698167e380cd6cd2d27e2b581ab94de3534b255e572ac4dbc00198f4fa128476488051f7ad43c39bba1c87dd2bc9eb815677ff639544922e2595c9df7d63f7181a4f938dfda280948e85ab34d0916e0252222c4c672db257e2c6a72c01db0295148b46a910afd9b9f5527b82c870f6659695870573a427be997e954b422739a1b8a656927f44dcce2f5f1a9886249c23cddda317057594c540cc19d1f01134d2a31395c4a19a5aa0241d95180cad9370dd64063ea082c1133b5b9b9a724e122727eb4916e78cf9972b45075c2478c3dde7d0ac3010545089dd7ec6ade647561b3ada576a5b648e22ba1b0d4538dd64ce163449f5904092be040340ddb8f8a7c43561cdb7008704d914fc66be0aafa9c33289d50aa05f8329b98e6750d5a1b487eaf68476da92b626e7eb6f18d960536cb6cd313a57240c57eff19434049d354756d5830c2340f0463bfd26a3b7eb5713dcee4277572f47cf43f86baa41110d96649772fc4205234c36e94c9e4a3bc0e0be8d5c256285910fe05defb1097a6bf18e9d3727a9d1ebf6af2252bc4c9971ad01261825472677c0d2ef33653313d07d3a836288862d65631f23eaf16d6ee58bf2cbc6e2a046afd8eeb7575c87c893a5fb66263269952def036a456c08bbe4d763c4a700d72d062234b593ac8e61612edae86e083d19858fa2951f3ef21b529f77b4aa74a7200516a36ef8ab4aaf15c18eef24e9a3f23a3aa9e7956569a90f6a27366075c4599f7942a0722521b1ce1334f9859230e18c12c3b013546e13496e205bef191255665c1322f66e6ea497185b7511f3d4d0463692d12f0b456315525d615f9aa7231b66fdcdd0be1e0a5db06714c5ed0206031811ce5f1893efd3d37e40b60e22abbfbee110505794cca3952d09dafa876b6e92c0461cc9efef73a4d2726673b7230411302481a19270ee707a7d730050fcd4aa0d701511e789041a339d53e7e7220542cf9f8fe4fa3075ca92ba1779090c7853b21e686d058dae4755e3d4a531cce910e0dafeed4f2f0802bef6c3e2e1a539ae4523c8547a53ffcf66241ac8b72f3b1f8606404a2c2858a2a86259f5ad6b7afde870caebae61964e82047523337c1a3fb26fc9f87f60652a60efe1907acbb6b337869bc11817e2f0c492137101e6bbd367a6c729f1fc6f5867e473f2aef44e850e2677327c9cd4601164ed8663a026226bcb440d21e267c3c7572baa9c7f95d2b212a41c45d50d141d4c2fc4a2cd994a9aad34cb2e0d5653f6acfba72ab07ac38c8c3e3d71c1a2423be75b1311fac40fc6872268ab37996799d7681150edafdb64e79bd33316def2d26eae81a7256566c07dff5915f683f2a281f6694e9445c77defdadeee62d5ac200e5e47967d2a40f4d76213503dc981569057405300c9a5cfcc0a8cda460b3947c5896f072df74b58fb3ff9dfa00d2f015b8ec28dc06f45ef37e4d226f2caa86c3fad936726cfde87504ce411b403dec27c4c4520c00601385d744d98f9f49d872bd01c572e016217003308373d842ff6ae125dd0429eac9b1ab3113a539df805ba71a1c7216b5d8b07f4bba729192d3be452c04841a1c8d45577e88902e1c63a083a3c7703a5fbfec118a3b97a2eb3c52c3e64db99f420dbba671e82a3dd0733bf42baa72d94ac0ee65ad485c08bfd057ba3d181756e2b93933c5d24e2ece3f288e0ae172be39a847f9f152478cd8de872b1f1247dc20713c8e9f1c222c9d98634007fb4f9546ec4760291ae1a3909b405167af29e8718d004d0997f4eccb54e09cd64b7283ade28d099c44d10f5f0fb3eeb29bacd2b1b6857223ee7bd1703d238c5daf7237a1b814c5f2eb3c617e24b15291220dcf2ccc03e4e762bba591a64b614c72723d6e0e2a73f6254817295a6e459e8182abfa2dd5e5cd7ec354914579fb0d32722ad0507a742cf2761d7f27002e7b05b7a5fea819ccfd5bbee7fe58f568e4c73cdf73ec5617fc9d9d3de82eb49f9fcd313caf2e86bd955c7ef6a08ab4e1698f25d5fb85846188142c23e0e9fdbad7823dacdfc81755104dff017e45e610f7a072dcfbef436887472f9bed3d78c42c9e9c4e44d6a8f75b899c842f8a765f8f9157bad81f93a79a38a7184bbc3b23e91fad7f0db9b21cf9ca3fc621ca8e1a4f8d2b448e34ce6bac8b9f2ec185eceeebe4426bc3dd427512695d9b11f1e26f287572fbcfedd449332ed1087e863626dd0cbaed2a7abb3422fc5eef326944e7fdb872dd7e037010480ea35da53b13235872d309b131a9e444c5e63e84616470480901cbeb7c9293839caa77ee1c74020558d00c0e420f7ff0a811a0bc966bce529f7242880f814bc9823c47c1ba1b43b4ca09172287f528d8385393c426114b45d372779c341cc7a493ec0b7ba79480f0cc925ad4cb0f645fc39e94c2d0a549e14d162a1bade1bc4afbd71ff0d40420731b7fdba39c7caf069963382acf58c6539c167a0f8e8b562bb6ba1b473051858ae2ffa89de3340b8894e3afb3b92922f73d48e3f3fa6fbfe9f82bb70246700da6b4f9ef72dd8a5031e011ae9e506420f32e540ba9e32b82c30820e878cef0c682c94b4af2ff3a872e415ea70daf250159737238b0b9038c0a4fa22595d7f30d0370c94babe4265f7f943cd48316228c02020b4054f15d21f45636cd854c0319b3787423b2906852139ba98191a3014155d8720ec2949355f120debb642df94826c90470bdf553a5eba81fceb0a626a8d9d972349d77b35fecd9c095fd7077d191cb2f2bd2315f1eb42412f771fc0e6170ce72ce268a1138741f1864aa5d27579a670d432ebdb9170290c0914d4735809c7143b42ea929554d8d5a9b8814da26d354b8b806d6ebc10f133bd06e3de2ac41e80a89b5def1236440f7cce4d680e5e638cd879717b3e26e406fe7a1e322d7994072104226fad6f71135471d2e1b0c2783d9f8bd0b24b0173e1bd6ec4d1bd726a672a088a35007a9c44b2e43c7530569bacf269d39bc6c79604f25507e3faa877a5c94ef9a70e602d20797542ad8b987e55f405acf96af125e4c1ce783d2ecd57a7297d3c8081984e1df3fa4807ec07bf15e49b7fc6050b21c7f7d9c136c2e1b0872d9081d47e1fd43bb1ffd1fb03c105090edd205c1d8ce9ad1fde424412691d5725901a686f0951d757554cded2071c90dfb99312450291d96760be3c2ccfc6f7255802484564321975edb2382528d888770f6e624c211cf2649a31c0fb674c472d8478df34bada37caa01594a1223095099b3d631511b815f79d2aa11b92343727854efa01e7086b626e440b9ca998f7bbf7810cf2873170bd489b8d70118577286401a3fa051759f9f6b19813137e2f1474643a4d5ddd2867c8790ded8297d722e3dfe43b2857d37dcf43a592a91cafbe2a25e7822131c16d9e85142f85293723d7ee1fe83f06138ef3310e08bb49a7b08655eabc8d7b74934fdc9bd58e1157201249ae582f007924a150f93edcf8fdf132c1ac6557a9067c3e4c301b9442972bf5d84f45316ecfe63b276bdb35a0260c197891c57738004e5c82532a85ef872fca9b457f19cee3e152450919760485a011ca65be4eeab86c765cd78dfd94172250ff16653b6fec258a90d1ada4174f7f1d20d7a3b887d7cd9a0e8bdc9377872c3cc42cbe683b1214352d68fb2e77c75827f4ff1b6e98e8bdf3cb1c136884065fc8bd0d062215cedf456f2badd8c5ce4818541123c6cb5bf89517b243955f472e7821fafb35b93bbe4d647f719bdf2ba432c3e51523c8f20ec4fe4c2dad48672a2d21265e109438b58d3a79e89bd1d9a8d30ebd88ce9cf7fad319944d5b1c93d38a79d757d0539fb9b7b9677686ee231607e83e26c2011a3118f79aabad8ec72798e6fda374c0e736c231a37ea41665289dd29d54b7d5e276a5db8fb350ea05b58d5594d1626266359b5d74e7d51655052628ba53c8876dc38234bd42e6acc723ce9e7f68b147012a2186b8c6636bc0777e2c49e891cce0fd3e0ecad143c5b72d02ac0918e4942ddaefff7292560887a831762ad199988bd109ea95af4c5d55ce5ce12d34904624e8d4751b172eaf70a75b9f6e92b168496366fe4332cfeca72706d2e57ca51c2c9084360cd1335db343db007c4106bebe1b62cc2ce07b7fe47725e77773cbacfa396dffa5c83d0de21ab699f0e206f547b7b33d7904e6f71129306fb4e44fb013a0e3b0dadab9bb6b0ce39f2a719445c034303b497a2053a724a2a4c7a5ced12621ef07f8de56250c28422b772e5eb0b2a934c1a0bfe70526424518e8fd278b9606acf5b8e8ef34a4d4d7f8bfaf37ebdd71a1c3142bdb32c727461c53ef35ec1e3ccbfdd1b59321ae9c08cdef6fda2dd4e519a7491160aa972601f7d72e4974452a760796e83a7225301d983a411ee1b23621a5230ef0136725ea4c4b0e87ad242060c4fb466ea78df6d9eecd52c725aaea61706591562d0723d39ca990cfae8103088ce14891126724a7423f125ac1b884a00cc971667477299435f3fb3b606dc5a755782261b77ae39e329a298f5d222d53cfb83398c8157c39ecfed3ed7e42eecef42a3492ea28d4e105cd2da7076c7ae6ed8563e574c55cb60b80189568fe894d5891810e5bb04b195718efc74c50775f1598c324d1172d64314eca4c76646cdc86db439e587a32e08632ce42aef64463e88a3cad02f17f6b832cc6f8838c482d9c80f7078c6ea74895f21aad4e7864b1fe375b896ee338b4bdba47a0ecd7f3bc2159683bf46f24a38f7de5f631b680534871e1e42d472e7fe55f9c196fc6fb4582a440c330094b119b5e9dfd5d571f4660af1ed8d8a3787963d911fb83f688bbe3112ff8f4a1609c0afc21d9366a74cce3ace9aa77f41dc7594732d752bbd441300e78059f25b137612d53998e5bfdb9155367aa4943bb57bd0200a1b1f6587db4aa2e0ba1e87ba6506e9a2e927e3688a4cbc2f347443f766d849e860316ed5aa1347b39c3e116079dbe40e3cacb52838b2b282a2053679744d89653f4387cda6ad67d608624e8e49907fe7ad46a3f0521f32dc774e34675c49e8792f53897e979598e44f5b647ff4dc1bfb93dc2f8bc4edf44075827288b726d9b896ef3b7f035a19250e19d5bb684f2db9fdb412728ead9d6f9667721f25fa66cc8e3d1778690a6f36e2dbd4a8c18f751597df91ebc478ac20b5cd72679cd77da4228fdc2e00abdd7a9ea2b29816375f1a1b43a8ef8d2193eff74424759091a9e723717b5157ad29a0d4e43b9279b1b810aa69270e84e7fbdddcd23d0ea0aed3549ea22401eae15834e8d3178dc2b9fac30d41601eba2892cc012372a43e3fad51ebdb41d51cb6240b05300c30a204495febd58c0af5b94facb40d72707377f9c3110685671de23f209c5ee61ed4f68e1df6d2ac9458b831d5f73672737fd3e6ef9c1ec3edeec3d76791b61aca15ff1d2203888afb3713ae6955b872eac18b7bf05659199bb6b7bc0f6bc948dbd63c50d0337ad8d4237258a1bede578dabca900eb36dc40808497d4c9ab99957353ced282555ffd7ef0be8005acb567e7667d6265337043f691469c4790a2620ce19a1bb7b00e913132c0762f5b772d90c81deb9c5d57ca8c26dcc55c14fdbef69e08d082d6960f229b7d8c3658a72bca640df9d2c5b593e455756959c8f32c66fd9a1d02ddbe33c7d073e50a1ca3c81bbbdce2706bd14dfde545a1c8d63d3f4657e45e958b77275ed4113137ed06bf9ac40a1ea65f4a90936d54e48296522ed253dae4243b2be410b2b78589cca10c5818a108aff6a5fe7f351d43ed508090c56e3ff8eceadefa8bdcb833a987207e352ae917419a1b08841c45ff4b8eae5e6614c3046e2a08b19331a8c89f9211815c0aa66d35a23665ff28f62f7759df74428ce20f009c8ebdc3219a8d5062172b1c729c7533f780bf571bad3b8b747639f9cfad16696c21fc327bc4a810ab73327aebc94e8a760602bee8a4c89225026e7558cc91b86d11e2f3f835a37dce10c3d66ab902c6998d50525120cf5113d73342090fd134efd3fee25b1cac437df72c70c42ddbb663390112e21948744506ac60dc1cd312c6b87828bff4a9bd7533d6a4f37643a2692f6d504db86d60d9577c028bbc3754004a6d82f056bacc940727f26b6f3deb284dc88a1169867a1f86066bd38c9a4bf1c4518f1aa685f4275589324b3d00e399f3607f3415d148d7d19eadc6459ed98a071224eac2d2741d731353859a082c0691d0b6e21a57cc0108aad1590ec95b42703b12888220b1fcb729e25763dd451e3c78d87c99617b575bb972249fce1e489ed6e36cb100fb71b207eb841ff2054087f1326181fd4e191aa7564f6784b43794877b3580051c85f6ab08695c3a58073c74af5d952dc4fa207e898b3b50c7f589336c4cc5820561c0a40451dcf6eac0979a724c4630829e0d8c3b20ec258d6285b30e1c8b158bdcd722f3337d9b3710cb7ceedbca67588b5cc9b6de5688150553ee37f7a9fc05b29723b592a02cfda32e8ec88525bb09fbd8bdc705bf07b8f6d6537a66e3805238e01125463252d404b3a41b38548afa79c001b453530e21fd1a62b356383aa2771090197d4648ad215846506b69b35d7a20ada554d75dde1468c7f660add48896972865defc46bf462cac0d8220fb324c8d0997e87c244af3e6052601883eeb77072f57158c1f587f1689056d9c451b0003cd96e676e188848cc09c8c8f85cd45a726edde67cc6a2cb3d1250dc936a7bdeffccaad458ffc79c29803bb0381c1ac87279ecedf885c918bd28a74fadead8b08e3598c583cbbebdbb3e2c815f5f415d72e324cf9fb8210e70df73ef43bc5a38fae63d568c8c73e2bf0412ab17a6191b72a8c119d510438bcad0e2e82fedcb7e5e51a42a094884826b2347f0d96adfbc30ddc67eba8739c20023d46c82e2e6159d2e85dd60a59aabe43eb8ffadfc2a38721a3cf800d5e12c5c10deb3237cfff433ceb44415a6ecd1f2b5afcba3ffc5027295858532d82d0c9d203840281eedd4bf6b66498273f2a4c53ff176e9c4aa631ad1ff228def1a995f5f1110e10a9aff17bb375d0b87b7bcf07e097163aaf32d05b04b5042c3e92cec9e9362b09b6844d9a41293ff12734884decff998883a0d295bdf7e22962cd05c2430aa5e772dca58d682a199f0b770dd77ccb7590a38d5401c72f98f589c6a914345c223676d90dabb97f7a248aa94b7b18f4d8ff2849364577d959b330092c7bee0cd56392c85c86a7767184fbbcd4c3e9705fa58cb0a72482af9d4f494a2ecb8371df11c827d40f43bf09b52371553b6620a53b16450726cd583462e24d2b64c7f8fdf6afa15b37419a8568fa8724ecab44995b7ed7b72e8687bef5ec96fa2cd521b06ac34146b2fde8df06405cd2ca94c63aee2d8ad12bc6278325191dba6688ae7d3fb618db301dbbc095d73b7e6dde6d2f462506839ec866efadd24bb89366d00371dcc577186be9135ba9a8b675e35492ab5d231726b98b38ee692574b365b0840d8aeb4e9e4a17bf3e31098673c0d425c35d9c025c6b47819d91e46495d2418f606574ddae42a400264f667127a8af2ff181f3327d250988b6141d35655a82b3e0a27b529a2401df5bba9bb8f949fb0c546675d72148ac0d93f0e735767a8a69baa5e5d002fd63e2491a7e9ffb1d92f69b60f7c3133293b2b341be0b6b55f800ad2a6b86604abebbb8958d5b5ed2eab145eb9b072ea14a5208919646c0fa44930b00eb987ec62b14945e352259e2ee0d66c4ae031ae78f4ef2c76190128a721a7f4c8a8f4849d9319bac7cf6e4b8991249a41111253ce2388a027ab5b559d5546f8e42dd8be783fc2ff7c13c22c531e698b751c35b2a71a5c98aea7f042606f4df5b65b08f685873bf44057e853d4c28258386e720f52dd20d7c9ce9ffd4b64d9a37ed7402af1dedc0386aad1ea1ab39107ccac72e61affc75538d5173b8f4d5f351603d6e6d93975e225538f8f993a3b1c546972e5085e8a679c0a3bbd60cbd081baba5af5eebaa3ad8020618d0e084fb8efd163a3ace99718b9d35ae9751d96ad5741dca517db18ace890a54519c672fddb7b176ad274d5ffd2802237aecfa25b3520f77eef63b268a9b5ade947fae503e82a373a07c6a88839314c69e5a69851f5f87bf28253a8cdb52ebfd8b3ddea27dc657215221d8fda9b40f15d6305ee4effb07570b7791be41921e8496495834681da21602d8067720c5bb5838fb4f5e78569afced76aa52b131f26bf01c9aafd107872541469c241eb239a031728a1813ce75eeb63e5c91a4ff3dc58cba6850774a45e0a363724a5bb1e7987502509c28305aa9a7bb6d9a168f8d80fbb795dadc0c627b28adad0f9659e49174ebfefc1ed90d36da639162c31fbf8100d516e877611712061eafac95e01966c359e0afd7a78232ede51d72b4ff53ef184f04df9344a1117ca3cbcdbce5524349d5ce0b1a8569ba27d9e25e5498bc462f934b9859bea72f82d93b4f4d7bdb021d3934ea7b8ddae0be55b8ef3de4c8d1859ba68aa070d043e578e41568f632cf211cf0f05f7e937fd7dd98006ae2d3abe35afbe4cef0c3cb87a7fa39d630ab4f71b4cc204eb409313c49f38805e34e7570d4cd7f5658b7258bc66f6ac868e0072a77c609ff3af0d08e642b728ebd44c29fdbf497b914c05f6061badf68475b6df5c028e428946ac293c773448e7d58b500c06170c79a533f2ea0d1af930f5227d658db99b1f427642eb01a6354655f70c02b821d20a9d72608a91635e9af3f52c18348b3eef280ecbb2f33499ddabffaba36099dc8ef708cce9d0313d8b7bf09120b739bf474afb0f9f44a739ed3e36cd22f70e9ec70c4ff9417015100eeb71417226bed451b3000008afc22c032013b540a463fc8f7a4f7e624f4f5b13c398e50a9f4623a6933db8166ed61924bd5d55474921aecf37722d65b15a964884a05e167c86ba56d932b137f1374821378f9b77442ce55f3862d253b9652e60c39596431a3c73844021e1d75dfe76e2017867b07be6331fb2729f92c277b5bb0b0422ebd075e64bb242ec09b1b50f69ed9eba2c7b6b53406032b502a80b898a78d731e8df12171cdbc179d2ecd558a2eab7d584373de8712d184d6ad2438f7cddcb1580a41271fb9030af3a9d5abc1f4cb570722a0e461498720312c3d6a9d362246735b84365c900f34310293493ad54be449baa95ac237072d323913e42ace4601d79e5ae307efc97bd4d669ea8657ac344e7a7aa5fbcda3dcdc47fe9d6a620f5e67ed11dbd6061fae42dbf5992755ae12da5b336a8bacf7275bd125845631105436c1d0d16fd2729300cf9d9367659d2b3fe5c786f436072db6b9349694c8558f8cbba521c8dfd504136249d05fd14ebb9dbd6ff51995d7277bc9606fd39ef3ce699d9e6f34cbc1be0c64551dfcc5d476eeb1433df88fd7298a92d2423fd38d17eeffd66674776a99ec7159f05c61a51e5fa6fa92abac072c5c36c0030c0f9ad49b3f243ae1b757eb922a43567bce1df82a729ca54976728216bc5c48e228ee0dbdffdd8832a183e800e0504c285b390527519ce1272c2729effde87f22e902ef60da0f52c98fa360efa26e34de75106be84d8d55e56f77268e774bd2957fa02525a3a341eaeb6d07be926c2f226ec538ff25302dcfcf2727be00a2218389c2e163b571fa22d7378b47c2a6723a1dd067540b96814eeed72968019fb7253c3c8b6bcdb19a0b77a3563b5b9b1eba8ecc7f235c28b36731472036d76f5cf04ee3cf63a1a9af1b2755f842139ff0e5cc437910c9d702f2374723f6399a3786679f7835adbe0aa9f0ac42e603375f87b9703027c8c1c8a1af17223080333d8adace71306a6bf7924429a0b9fc3faa28ace0af851cca7659d787275ab88778b44f91516e4999ccaa231884295eebcfb4176edbd7d54532a8057729311aa1d15d8f21fc49a7b9d6af8c5920bf9a8bce6f65a8ffbc531d4073ef45b9b7fc053580981e8eae60af439269374ad1d8d7d127321b5ca546d3f6cab92101d898d975c11c27803b3c767f02cf9dccbef6658f0a9784b982e915b12123472ae7e1891fbe23891831b89d7282e890f731cfe65d7a07148838f706c18db6272d179336c1c25e02dd7e6942d7a94e7e0c54311bb44b5ca0a4363659e68ef7b72e9b68f4446f48ef9485dacf8bbb16df7d99ad755a6f0206de21cd7c59b95841d6db72ab8edf61b3663e622cb503d9310ebf451419128f588d2d8dce9269c0a661ba44bd713140c0f1442196d4859bd4c571d37e1312d4ca53122c68f5fa0174323f1df876fbad6ca4a6d8fd7c62216cdf4934e0d24b410feddd12540ab781572c0637dd3a0fe4ff1906bf9c29667e4218b598e9982cec4e4f627d1986301c372f48f5383d92df68337c4204a9aff78b6de0b21cbcb4487105c9e064e0d322f72f955c784f93a6a686fb9ce1b696f02cc5772008fc20b31cae3c58bba844d4c6100cb3af8d03a0dab6599eb120664319dbd887dd3c52063c8dd9b6ba44bfcd772025cc7b2c75a77f2bb7469d069829600470ee1f43e9f2fb4b147501782d18a72da090bcc73fda9f885ecc800dab7e9f31240c29ee3eae24bd2638c17ce26384873d2ac31c971d718a363dd946cd57279a089c672151d64f4053c45566e099472a717fc07631be3d90e8e57d1530a550c716bdbafc9a729fd57692d9336b79d36dc2225a06df7cdba44b20d20f0fc88e96d78fb7494d6d5376544b2db52675c489ea336ac197025694d9545bf43acffadbfa681c2a68c99dc95d9b5e35e61730e276f83f44b3c1d382fd1fcea38937adfaa10d6b740848392c62fb03e74063c7281282758f230051496b4169ca05201f52db36e1347403199566ba28d797cda72952843fefffc9b977db3657914d1e005f7b89010a00d44c2cf0c1d79d3c9621f40c9ab69d3b5ac125854c889995a7553a7383859a54b59f69a6ddd8e88eaf17204e57065c660161ef714c4ba750b3cefbf340bb0217db0f080b709d6e35c2f7276fbdc260f9ba4e93ce058d4efd23050fb15ea7159520ac8524ac7c10605972e55e91cdb9ef0523a137f558594e806fc6fb45363fad105996a5634e2339e9772d03530ae77b0370ec4759b594ea9481978a45b0a32c45c5eb29ccd16d89bab237a58161875b3c55d72ccc68e8c4b387b63d6abfca8b47b7552a9e9635d33d472e20b7fc12c0c55c00ea34a3a6f1d0273f9a2a6ee7ce519a2d43d967d6946757264858b81bcc4f114158734f7aea11a9f9d14ae606efd09b1e6013db54a28a372c93c63a39b8076909ddf020e98e6c8587ee40d69320128adcd64194be8004572d37faf6993b6241afa8fee1486abf11c99da8c2f348c467c075a9671d0e02e72a1a9464a5ac724127fe55a9e46a90c5009bf85ec4415c8540518a86f135d897220966b10a0343e85d9eb0dc4d4a623c24225fe0931a3dd655d5a524bd3e32125fe41bf20303b9a8cf95042a038f0b44802a98ba8dfa2b415bdda4e59e3e22c72bdca989f5ae381e31e3ab16ebcf32a60aa1a579b0f25c381a054d35a65882168bd5f9ac747e4ed97021a5deea4d8ab0cc4927e8df5a29a04ba9b5bfbef2638722f464fbb11351e56a20e4197677cef58530581585900661a67eb7c8a54726f2401259a5d92aec38603758a0250275c3744d0c0ea8a071ca5a7d9e3df3d9bb072f80ef32797fa950c243142a00b3e8986dd82ffb887f5144f26eb0a6e565e9e72f7c4f9ebb2b135dd11a658f343d89d3f900539fda528c576bd06555a3b795b7211b10da89d3b0b22f5c19ad4897487a3a1f7785440474aff57c90a311ed96b50d43f0fec0fc1355ee42a5c60a286aac536ad2a6300416083be6f161f53da872d4ea1542ed7e4ba6baf1649db8ac6fceefc68fef2c0037bc661e2762576f84272b97a4dfe49234be43b67a6eeebcec40e2cf11356362d5d88de34ff343f444f66d906a9b801ace35ba09baca2e7c05665d0ef3f5cf1db41c830ae1f5919623c22d2a06e0fb462f3b3b0429c5063868ce2830b39a496273c26c0fefde53728fb23a07103e293651e927b124f431cad01ecb91fdac1bb2655d9d1faaf7b20aabf728d7499a1776ed50e1b1b8a761ce290cfc5322905fbf14f87875c8c58d0165e028c2e3d20dfbebce7c918d692cc557809ee7bee3066431078885cab5c2c248b2adc146936a6fa947522eed0ee49093cd0762d408f457582b6ed39cc90db649172bf7a57cfb11eee8afc17f6ec9a7de8ecc21258824cf385ff74cac73653ba4714d2eb2f367a354ce46bcc2574746d8da1135edeb3873124e5937ef1a9cbc03972441968dbaf862c1f8150c211e389548382581121aa03a485adae4b54da8b647212f5739fe4a9011f93a727e709be83dbcdc1e478114badaa2070207735eb4c724a6bddee758f18471976fa1c2f18b9b91a5d3bb9d921a3a1038e7192f4378b7264f313d959c9e60ac54c63bdaa309012c8c7296d046c75738bec7cb4a860d77211bf79e1f378db516955475e9510fd88d7a21e0d58523bb2538783bc75e47a723955f0c9d72cd6927a43a3cc438a7dc78ba61553a32680841a715bea8e9ace4f8722458a5beeb2d06ea20c211fd1bd1eee26e4a00f12e7ac90904242ba903472450648fef6db51e0f1079266b48e9d2accba513065e2e367d513bffe5a45d172d8bc19e0e39501dd8b8f7a21be48d091816466d6ed61e872ca8a999f119945726dc1bf3ae62f8d468ccdf8648fb54b40088cb7b077697c8ac315ace79ca4c73d2dbd6fa191a1c76a32e3d1e7ff0a3adce71f23313171733a30267608d653294a4a106977da251f1e390d8127cf878f3e15675e0fb0b95aa46aab112488188f723278206a64ec5e066b5b024282108ccd3b03d99fe6fca8fdff982215ffd69e378508fb3f02ea1bd4f451c69a4bda005ea4d1cb5275fe146cefc3a86df6db9635247c474de6823446ff0d70e92e8b213793703e5988c67c0f1aadc5d264b21a1d02fb618bf836720bba76e0f3562382404a40111b563dd74a114312b121283372c721037f6c0f80dac9293a92d0627c28f7a8e50d1ef242bcb78f1a1b79adf272539f4f6acb76e9254213da373c2ea21b4f384073208c0f63c59c9188b0c139523e83e02336df1b89cc14b6a76b6293fcb805be219f00e262c1e9c0e1564e371be40cca6d35fff72273847dcda3fa247c38c83ee7753bd18f6c7578ce93c1b2724705b8caee24d1080dc5c43dcb67fbb3e6634a72183b0b60179e82546fa94e30ce7e84fd4374f4ebea94a69fa2996418cd79de9fe3881a3a62e098518dc0de7217a2e69baa20defecfdd9ba3dc0b0166a3fd3c38696eaf0a63753fd4b9287961a859ecd79779b3ac115069c92cad82246b9b5c6e9efa0041fde1c67edd6f66452aa2b149873a1f2f930c410e825dbe3f37d9f38da55298e833e2447222d500726dfdc61d275f8f5f9476965805be51eb42344b6d30852aeeeab45ebb4d54301ea2d4f4e3f5e98441ba1d8f82a2643993420deff93f43bd3f876d30db87aae272e43c20f556b58d708fe12ab7fbe967fa154fbb4110aaeaf9082f47dbfe1194726e4b8ab0d6c4a9ce0d964f4c7c1af32c5735b0cb73ea5093396e274978c64c38dbbbe52ce665236ce2145ad0982812edd979adf57425894da5ae66a5d58599723ce07a1be48acf6b0d3a20d6c90d0e94e41ff22813e2fd8b25362903660b3a332829285a69af2d560329c769dadc289d20f6cc02bfefe1a416c37f2cc9ced072b75868c4d29fa7334b521a2b689d51dc172419f042f1168d3eca5bedf299f4726a0adf500b8a18001120b3a78f708473be8ca3dabb0ea4eb06fe0056adf8ea72be71db992049d652a31db3e37d237154f50f2dd066365e5c86c2703e6eb34a519cbdb53bfc8b7878da3a32d518dd8395602d6e1e9d2111e673b36f679736300c42437cc4a9025fa1ee0c8f508cd30a5a2aa03d4ef4a3f8a2ce4a2f677ae4d072883e2990731097e67009ec770c91fdeab98c0391577c2190703e161a8456d172a9c8ac5d6c0c2edb0894c2cd1dc7aa2a2fddb39c6d9f052e3b0064f9ff477472a5b97df843a4ba90f18aa12989f11f8246a0225e8c95667f3e3e44e7ce1f4403e291ff68dbd48fcd0416e99c80582e08208d27061c92303a6270a1c730e1b55bc9a2d21b8487e9e13563800cc3208f372c789f832d7b0f7e71a6e8df358561274d1ca91bcdc4160d99d8963293e4eefb84bd081c50e1969bcd41d80fc393e5549f7d00fc1953deee8ca7be14f7fa7dc50a7dcfcaa1abdabcc4c1826b3cb9cd724d82a1ed9135af29b1399085a1629368539919ce8b14d8e65575f917d56dbd218499836893a8b9acff286818a8df56a8274b5357436ee56aca3c91864666a117388f8096857ed38c886425fdb0f48105940b0f20630f9d3e9d4348d08fdeaf720f3eb1dafe28086140bea2cd772c73a78e3e51de5e2d22d501278a1694b8ae72b11c5dac787b07f7d23281210d2d3aa72b7868363361247dfd10b3a338bd22729f7e02be8c77b53d99768e4994c69b3ea2e177ae66d3852f800fdb19703e60174b016142a73022e16e4ef7e7f9c6223e3f2c92ae665040b024afae3872822426eaa2b72981ec0f894206d52d1a07762beb0b361f1a8481c9d4faf6fcc7f70f724ae489b80b46b7b9db4cd839e17faa3a8901c2b9dc817cc314078ef86a65c40c6b464a707134dfcb3dce91aafddc462617e4645c3246a7d48ce41d291fb19972509ac45065bebe0eb4e8659061b9e533e06a80e9f4d8fa265cb76cd451dcb372122f98650bdf2971911bffa70c78c74d0d8062d766169d68b9de46dcd5aee45f31e30a87b3fc9ded5d6f51f6b3df1cb709dca3f200229ae8260f2bf798b5667275f6f0cb9fe1a39409fd4835caed605b6419bd899578d2e4b334820fd22d3a7261020cd03bbc223104a2c5db81852771e3923151b094f25bffa279d9932a3a72a414f03aeaa6feb93f5f7ee2fe8aed1aee314d7ca6db35daef8d206a13675a72a65582b5853f90ec3f5b0bc302defd811cf02b16817541ae99896edb1b7fa432ee57d4a9abd866a174c206d3d69ab9f2ed01f8a4ad4df276ef5a1a7ed930043d1abe8bd20f99d7959798924f0b3d57defd75fd342e2b8edbeb24fdaa4d46687255a17f443fdfd60a7e123b8499a3eec0ef6b55c5d127ef267bb658c5a09279639b31c8c5fb65322c52dfa141fd40d29a19daa9c6866edfc8070ea87e59a4bb4aa4f44914cc9edac595d76e64fc835fae5bfbac2eda57c202b21f6924ca75f0672c970a9309202a4ad32a636f24e9a82c9d222f04d9cfa86e7df077d4799f515571b9a7aeef0672e5cb73264b4456a8ee3600baf92b015af169b74adfc9aba3729d0cf4c7ecfa9a51a5380ae3772298e165eaffd6609c7d50634a3eb8f3a57f605e11bed0b414f06b7bfe67106a20049adc3f1fb99440593a34e32e884fab4d72517e808fa6772a9625e5792f2a642905a91547ccc04bb5fbdd8b548dfe640a72f4c49ddde441f38ccfc17aa22260230e27faddfe7014dfbd6ba0256b6240463aaa045fa081f22c0d0a8ab4dec4cc57424feb5726505bef949337b8e69ba7fc55bdf6bc63878c3a0e820f4933be42fa38c13763790f87698a79bf2eeaba81257295fdb3a5a0015496121ab49cc8721507f73caf059019724b9155ca87909676728c7e95eed8788cb81f605b21f9921d6d715bd0706fe81cdb9563cd63c7c37c724941e035de5fded390003f9436d726ae3cd7d8dde353e607c58b6ccc4e070672c872edc557555dee5bbf4f3c7f47be403948d08a7f247980bd3055b9fc071972745ce4c3d5a2defeae9d43e60ee9b9ff84cf94c2efc5c93369889b635963423c99f869ad5db18125e1b9f7d01e3a8c2e3600294606c028a1b9576bd37769a172fa7bafdd4041a6e6d4075ef1b8194c06bd3ac864b93ef00ae810976048636c72c34f85f3135f676184bde184f8c60ba62d85770b35dad631e12e4feb21e34872cbbb8e7dc87ed27c4d9ba7e00f12dfb27da35728505d70566af5b35fb2ec7272d96dcdc26883460a76355a2efcf18b83fa45a60163cfe25a411e649364e33972514f027be81750d71c70f3abc9a644adb0d8244dd346edfc31833580614d4d72efe6387d96878a4b5a992f95c647932a0c81c8dcfd3eacf31bfad565eb00b172bdc8b2a3c8c5d3c377b62654e3ce08e718419b9b5ad29a7805f046171dddd6725d99a048c506f8a9f9703d1cdae3c4dae414981677385ab7591433427929ea7208c7d7fdc705cc12d6da8891a7f95d9767133e36bfe44f575f7b1930b60bf9174251ea71ec06515079f63438c88ec48de800d1ae13f14408647b553d29b973721575f17c16ec55f8b545d3bbf129f63845f816a67b0b31ec3d135dc6e121c92d9a672c18932554a6f09a902b23f92ac8a66beedb8ff9bb6e5de51d84826d1e72f20db3828d2c1670fb1f46327d7de8057b98a9f244294227ecaa31fbfaf62e72ca3f36f0caafc4e417d6b7a3820a6337c48c36f801ba2c4e45d325aeb8af1e72b9322fce494fe8d09252567ca9655f8581a670d3b48e1e7aae95c0cf25ead372d8fcdcbdc47b8b51233058d3059a591f0d984e0928bb2fe27806de211b3c6d5d906fcd6040d27739de1049b57ad6aed9ccd42b772c185628c7d61c48335f8872531b30fbd124483ffa52caff77cf814106406ccc624d17b487c07bd590303a261e2e1594569d1128778aac77926d167189245730bf752865e5263ae23b2726725672d4039b9818eda9a2ef305b2f3c1ce0db848147c2e3f9872f6c612d762f7246f3045503e8207f0daa1b194e80f64858b736d9d17f2bee240880e82d242172b0d444b32187659ecf674a50d6794f9973e357576c6a3e0c6e67354db37b6872271a5273e8438a8fb0e290bddffde93b3eb013a549522efde12bfa941359a67242680a05d9b8ee3e1e9494f984124cf5d2c57d8875fffa0e766933a28fceac47682dddf2a9802ae30f3238599c8786760d358c2d8a7dfcc40061e45fd2f5750a25d8e52c6200880df2a4fcd919de9e6bd2fc1f1e98f8dee6a7d98147bce8157277a210ac7de46190d73860a71d5326338710b4a02d38ae823c7951071504c472724b6e8b32a7065bb1dcc1afddcdab49e6a53015a067b2df912001fa2df63443f34b15b5b2702e25b5678f12075563f26dd431688287b0aa5806548d2eb0f370f4fc64e18102d260faf704b6c1a6cf030b27a6396e224172711cdcd1426f8f72c10e2a5928081200acd7a0ecbd2482f2df88a1d73e12beeedfcfd05bd10afa72241fab3286dd3cbc17a70001c6209b215413926539168265b60763d343f1895edd56c048c272e66980a87ddbf7a270fc4bc3294d2ae717637204e8b33d481b620d6daea7b6220d5b50fc8600f921a62652a9d8eaf35cd018408424b4ed51447257194710ab9228820f767b31c9445961e21a05c75603c30f68a7d775220ffb72572d22f02de24a88e13f029e4479aedf94ccea92e5b27118c973cb780d449f5b1c9bd1c389c4960275e831c3a4afd9b27a10e6631e045740cf9aafc1c437d8728a4eaf74181d4816f968f4455c4849d75745fc5615e8ab6bc68875164e44497296c21ee9a4b72974c66de5c1974b2366c0beb102e3056388b3d78430c900dd7216717161ee0a1c605d34bc0108d9623fccb56d884c9ce22c20b8e83e7252f108e54d689db91b1529f69939db6bae989dcf25b7cbcb186ee7ce6f05a3854ae47237dfb7e953f572ae2847b4970eb72b1db90356d3a018b8dc70c1ed42eb820801f9a8978d8d8d88a0d234603e9437fa86a4750ace41220c403935f2499c083d7275652a6e12a8833086f7768d8d92b33d725f5e53ddb956b2578cc4e702159e037350ea191c8a9d46b98011c3a2f6e3cf40c10b944f4ec60b6c82339c2a6e740382dbd305573f66de9ba5a17c4cc189f28e7cc50f4b88e6cec7689292ec36063f818f720e0899ca345455fb7e39e1e95ae68e3b8f01e6065b7de7b413422a5a72dced81d3515d462c544571482dc4b771a5c34c204b449d48023dd7fb7d615f72daeb5a0deeb61f798505e526f2d18a183c344459c3d0a37c2a4b50f95914422db28241e19c1cfd7c494e479fe4525d27ea231f0ec5225b4dd1a662c24ec48f725ae9e5530291e4a6cc0747db3ca2ff626676f73257a6371ba6df3dccae0b59554b09135ba06d5720f8cc87eada91f58d865f8a652e8e1484296e49a8573c6f550b2bde81189105927f9e1cb5043e79ef03e94e4078f33dcd8c58413c604bbd56992c21ed61d2d14f5067b3d025927997049bb1b076125d6707df578297af5272dc3db5c10f4face520a20858665347bf9ed3b1d8fa0bd4f5a020dd2e81b0b272e0ac652b87345f0346e28c1a0cbcc8f95146c70b15563fa45c8e3da38ae90d3bd539e6f709773e759e717a41cf0e52e8297ef3a9a4ddc322e2889a1a4b9e617282a1168c060f448ae31eb326957d2d030d87d8dfc3792cc5e20d4a5c0f9c9e724c695a65ae0c8e07e6ce58dea7e008bc051fdb333b60259eb17d7a59cf80e31a30a92aa2741ac6f83e6c2c10c4ab9d6c413125458f05b70aa4282b848b916b7277825f4d2f9d941a27358736c42e22fa7eccc4e04a2e5bf8629e17e15bc96a6b368ddb94f8847c7ffd1f1d03108d0aa78ae12cbe6d74e6d178ff30f58772381f1db47dc39a58e84efebbc7bbdb92d45d82d1c2f7edaa34be0eedbf8ca5a38d13c0ec93eb9f5e6ad5b105d46929cb25a42d9220ce1d105b8f6a86169f0afa2c715a8f82976f7b9172e3c41df19f4c5fb40052a21b7cdfea964f9b0e04cb7c863831ea1bcd3c04aa346f7097fa152458a5ccb6d7ef4fdbfed57ec893c32b075065706e035f9155ff1725074ef0940047485b33c28378bb978a2280ef5c623cb372de80a1870e42af87c1265ab23c8956be26649a6d9c02ae7438c3b6204a6436720a64d6dc730525901de93fb3884b8947fd865ed0be2a4ce0fe5f8f5bb173647296092cdbf6b5d9a0b14c31aa0060f51682254ac84dcd5fcb98af02459ad69968b701f83d7f34383d5b3dae187f8f1f980668bb85e1ddfbb159a42fa13525947212ac823c11f73c2c820b87aca4dff1cfdc1774d26d5ccd7608e33506ac70555521e781321003f3be7db582a4de304d829f665bc7bc4008f09319d36b2f022a72cde143a413761702f69bd9297834b5e9070b10a61e39718f6a6ff093e9e97b2d0753ae82b21f2791421afaaa51f395fc2d01f899c3b17c729e7fb58921cdfa728d470a6d97ceaad8968e7661a5b32660215858dbfcdf229a6d413214a4ea7e727828fdd52f99d81dbabeb91c15c37611afe667c455ec832800a98f922e5fa872e586d239afec7970c44260e9f9003b0f8fa6a4366a8c6ebf24fa9da9b19677720abace6863284392aa41baab41ece805dc959abf0237325f76625721e0e31703b4f27332401503f6684328663f0fdc70d687f7f93e701eaf5ae59f66cf0ab464fae31b9d7e44a3605ffe2f44d1fe1928189b57a54b5ccfa9770130b4cc78a066241abae63e4cf0a8ace4bc1fd8a8305ce61e8a5060cb8acfe0853df0d54cfb729515aa7a60c52ff8923a847e5efb59336c82210373b3be074f91fcd6266a4172bf22c9af3c5ced6fa9f265e034b47a7b7adc72f6f988a349ac36964f84f0a601d1fdeaa94c9b799c791af841c18218a9f5cd8f904b27060849f8768ff6cb7e72a0546317f03abd53c3933c3be07bef94e690e55459245be053f5a1ab9ba68938847da14ca17300164da7e620b47b8c3ea1484738b209329836c9434f8bd114727d143ab6a829ff8e843fcd031e2201c79a93ed95c15630e0e8729258861e85008cc90c405aa65bcaf2fcb6aa67fb6ced44bab8b513989b2c8424e4b4fa0382727f025d555129c61f2bceffcdeece26e35ac2a8c3b534aedb676f2d22ef5ba272e7bbcf9d7d976820afb18da82638e5df3895a12385634390adb051048c2ef7728b61b700d9df879a94e295eca2c9ff5ccead2e54120eb9a83aeb6fd4ee9b1672ccd9ae94e368984f39a1017cb5fb2ededeb60815fbe9aba10b76732290a63472bcb6b61f7741c6136afe7f372de6447d025b2587128ad9f348caf2d468944c3b8b88751d08ae61768bf3bf0efa26a4bebb8751158c64c5a31bcc7678fb9aa07207100aa1916337932c78157495b314482a77c253fc9652496fb9ecd7b9ccfd2d548037ec6db364e827f33e20fd0dcb2f8ecefc7ec69cae901203f33b9b89ab4b052eae8e96ce5cf82fb0e36ad165b241f4d5fc3635b3925665d7ac0bb247a972f743a7fefeaa08a0c7b50bcb8df62917bdbcae56d5d98d2e1b6045222928a572c11d1d9473fc2023e072fcefe2e729620e90b807ffdef9e323889d9e30782372d09f91142ada864b005388ec42874a0e02a118ff9503f316bd24e9732838a972a7effa6bc812c5221855f907f432c1217e9916a3b4c91ca0bea9b435bfb8c67207cfb905ed851d85343018c5ebcf724d6a445db2adbf4893182eef8560a7362566d4994fbb5cac336e8fe0b450c3405e8bff95a1f47f65a78c91a91cdfc8c0728bc86788826f765347cedc9e9926f994c96befda953ab0a7fb9ca0653da5431baf3b07bcc4ee5d3a2b757f8e39b2eddbb8d0545dd006e9b7634559a6b70c4172370ea840100707a12e5b99690f895e2b380a56abcc0d03c36b492bc41d965b72ec9f6249ea2d0e8b8239876d2bdaf860fd934beb521993cbdb292b0ccca0b2725cab36b3ea6a3467044147617ffe7b3e567a416eac5ba38444f339baad93b836a69e613d970d5870b2d208dcd59f7d2cf3a40901acde507e53137f1c7676427296e4ca4b48a29c2cc2308d9f7c6b0914cbf3f460fcfef94e3312dd280e0a5d6bc42d3e9c33da95a969abf0da2e3266858569151638e83b2535fc907061cabd725b281e6badd9b34ec88e7859729dcc8416d26aba4a4ee86633818e2bc5e04272f9f91ebf83f39abf3efe5d5660dc76f1a7de4fa231a9bd84e66b4fb35bf2ab727225194c586b0914aa9324db8e80df08da0c5fe90e8c405e4221c2b1abff6f7220ab680c23c4c2ccce7530c6a091e4bc5dd7accfb0f34964a5881ccab3b23929013edb8e963120623cf5b351f1cb5abb82302e17340fa467193a6dd7f6ace47217fb023c69222233a83139f5626833536d30a808c5c2a8745de23a235765a37285c15fedd371bf54a38b4f8cb0bba670af6b6c2587d5ec2e09c0b576a01e6172d5f6fd0f0c7109a58a8e46d542e29d0f367faa395dc4f03129c843a9106e51638f0b78cb430179528e7275bb05f784ae202088da5ecd34b8700118e3b8e32072d7566e82f24fb1d87bac5221ef794c61cb81928ce7788f5ca535b5c1a51ce572a80c7316302d9e2066132436ee4c6fd557feaa52ed8a9fe7c4d03666c1072e72e42bb2dc5af8eeebd8d663307db019480c207bca5cd6be64f014b19353769772dc4443484dfda98f32d9d4d49d7ccaa0fb00712e98013a7791e661607952957256f19c09962d7ed4d8cbecab04c3f734d845aca40b60bfd68c46f592b97e7f1a7ac7e3e0e17e936e9d865d1030bcf8835b05682390000d3aca2561b0efa152459191675b62120b009074551e9c2de78de37ded323b8f6f23ddcd967e8995d572bf50ef79b998bb3e60bb42171815e468bf76186a4e4762f708083fc2dad5a572b8f69693db6fbba2dc57b51ccb81f0cb73f3a285c911d3f92bfbea01a41ffe015ebd3dcb6da91e2b542632ce88e6cc012998e52945346833590b7a4df067397269086f77517c66d51d210a5854173eff1f4c08d4f5c912eba8fb31f097eb1672ac0ddcaf078d080e209abf2eddd32c1eb62201ff2804165ddba60027a819403bdd227d5e2ab20146b62cb392abecc49e5adde6aa8d66b4cdf35bd6553c3f0b5873d529e4a3a4bffcedddf013723a90e82e1a1cb53c76c3c5cd6457113c54c2535ef822cb5ec31e5ed6651e107822799011c15e051614f238a4bcad9fdc06141e90f33ff3c17ea0f7778fa95e68afe141754dc4919efa6b52f48e6326ec375a1f354662416320709d7ae3d81179636d0a534998df83b040b987dc32a2e9a96b3e967ea6a81b4fb2e277adba92a7b1e507d66d11dda993cfcb8d99b7d56170b072ab90dc012c34251083160fca87a9b3fd164acd52b5f58c9ca58c18ad17a85572cfa9410a56854911fb07d1d0f5ab231fca04bcc8d0945467137c2d99b9da0d727fdb4bf6f3067c2c69aacdf79349b5ebe99b0983fdb7976d41b31fcf80e0ff25274e41776da5810b1356cf11bc097110bb247e5178f725b6cc0e9a6aeef8fc427b7cff7c885d106d1fa7d198fc974b0e194c895192a803573a640c762f4f5839266c5877adc1aae189a6acbc25c9160564d94c0713498a9128e947c073c30572350a8215d5a68355a55ea353f2b8e5cebbbee25dfb4ea5b69292af283d553e2d65a3bda30df393a06e85a830a3c94454a1500e668b889b4857572a80ab6d7172efece239cb193b3a0a48ea45990cd29d2d1fe03366ff8357291379455f4cad72e2431cd6d38ca819d19aa068fddd0c572e3b535f5b872f579425ae0589696e72ee09c675b0dee09dbd709602e4854b9fa0ca75647cbb729b6b7bb841dab72c725a9fce69df452ee90f594d9fdbd4f46dbd937628c9205855c6e1be92604e4d5d93e8d6169af27d3f7f130a179b176b4e9a1e71dc85ef467c96f389a27b4bbd7203011b00e47e8b27e5ebb9a7fdcbe43a90d931429871aff908dab78411d73e724c3de73f3cda43a624ad924a899b9181646523af07620f333a6b93594d69de729d348a5e071ffb07f55d61208b63d2449665e444a6766b2dc13d7ff854f8c672c10d0dc44108e3c0b3be708d5f68bbd290b94c8b744a3a16ed80d5435fb0f71a93ba5fb0b4fe938708c5034c03d97dcb11e4ae7638228afc737aba1aaf5d2f727933eb55e2c360da9d766e3205f5a32024149cc38279e98b3a162943d93929723f9b530d914d0158c4c3146bf43099faf08ac044f559d65df4d371170b5b2a1bf77820c3477f4234d0fc00e6b20625bfe668c54db46ec1672e688fcd62ba3f72a602ebe0844b21d441bcfe235af2d1dbe01966c309c33af1fbc065fbd1107a720d3bde2c3a249b5d703affd7aed9b05288b6bee1a6841a6b392bf71670eb6272d330053bcd1811c0e6242fa525a1808a9faac09b149fbb3a1b976d5bdbaa582c292440576b94ac75365447236963e3ded1a25e7b1dc7cd050ee715b62334a5723099bf3d11758f1014e3df952e5053de0e51c47e7f127c6f10280d7ccdc1d0244dc51d171caad434f1400215d27c6af9215fa5400fb268b1192238732eb9eb3093abd95df97464156f969b2025a9bc931bba40d06ecb5eb143dd4c6f2d5e684196747ef9e443a537ec6f3fc1cc143063708cd5eb0d36b414dff247c7d4f9d47223de92ea00210e327cb9e31ea252d66109ae013ccb27b25d75bfc82a045c467226de7af3e38b8995a65bd6efb54aec9e7dde3918347e88fde13a5998133442065b55cdcb5f656b1d9983a0a30eb74e355271a604adb3ee847b44549c4dc56372e832195ad836f44f083880d9ca39b8be93c5d856c1c5406c0fcfc90ed289a905dde725f392a3885ccb58effb407811f8aba8f51d2b6669564d6392ce8176844f295403f9db33b5f58dd148557a66652ed78566964914d51002cfca8f3a08ac0adaa3e3461283d965cdc47df996085605405dae12eb5fd847182c34523e4b3a720899a7815bc23f77e7d14ccde2f0211eb0c6ca99f676b3c79ee37aa6974f28721492528d4fd756634982747f08d5ba676c6ff8761d0fb68c295e8f2abf8c294e29e2925a34164b90709b5b4427cdcd30e9b4b88ed0132dfb8e13d6c8fc9e0b592bafeba552c481b882be152bab6ce822afe33549f494f71798685c9509fad9310a075e78498ecf1e42284e0b7e9bb044c8c5b4f2057001c72ac1dc59c92f017291d685b22af783053cce5ec1f60ad0c57af0f131aca9307cad49bbb719a4827298d6dae719c07c6bf885d7427e0b3afeead0475bec0b0417b2ce966801276a72ad279b3249dd6bccb5d577e1fe9299ab10009129d826ed3ebd6dbd434755c572064f9330c08b801664d57f78bec9a604d6fbe2dee0c11f6d321be084a91c7712336a68736eb21d2c19bafa54c99c0903d22141247b547c1998bdc2473d19db3f38568ff8aeabbd73e0621db60f095726841c298160234f5a3db5777cce4a30475bd28d59858fe1818bc54e910ec00daa70258e62331c3d08681c6f0d16e2254857cb93ea82c29c683ac64bcc50627a514cbb16b5f827dd9fcd26cd07cb24257234cf6b2ef826b18efa8bf8faa7618820d9efb079570e84639d1e37babe0d263dd0df09353018f471d8a3525fb1ef289db5184c1d62095a2e798e023abb16cc569dd60004f0ae0b5369c0b577400a3e14f61e60308742a33ee6eccf30eefcf07213e0d4c990138e6868172d8ac01c03871ee94521a389d9b98b7e0c31cd1c00724cd269d9c4ffecaebb798716d6440ac0b0704640809ceff71b023fe97d2c1e728a4dae939d4cbde6b5faa35d0c0e4d6030bda021f3e835ce084b1f1271d9c27238d7e886fc72a5b0a5df604e8c671de976091d64527bf8f73b8659f38f653e3e07954fb177f4a237ea790240503c9f4ef6a0699f8c57d1ddd9c3d1800939077212815527b3095ab68878d9ed6bb383177dc6b8b7d5bef1ac82612532f17783723a6d9871eb398995b13850eb39c02e9b6c3a82f683be687aa621083c4ccb6e724f163a699d00b061a221589ac58e3136215491e187786eed76e2d851f4bab672e05f47c4ad95470101a86b3c0c505f9037ef44880115b9858c4849730bb211729c68b737972b6025ebd918a9aa7bef8936763ec495e4d41f64df20dacbb29745a7eea9d7f4122295be694b5e1196439c358c98796977919390047d709a278f72c8d4f662a103789ec73ee0b3d17b5be859ab689ace66108df286a7f8eacb9072d9834972c2521675e3fdad64c9b6ae018b9b4edf9c515e4948555ccd82ed597204932ce5eeb2ccaa1691039494958d39cbf40b220c235a7ece4237751ce4f272933768b768510cfc95876be01f71aa36be0a536fe7f93ac14ef8b42b7a0a9c72ba83fcfeae5205a155ed9da552f01f6092efc4847fde9dc305996fb3e815364637c743ad03d2423d890167d471acd9a9079715f36b4dd2feccd8a01929e32327008f18ba612d83c621ed9dc5c2515982c174213f9fa56fa9c765a3ed47a39872ade5b583df32ae525dc8f38903e5687d123827aa170dce27f96e7281bd165d72ee71eff894839085a8a17874e49b404050f4b0555f27ee6ba7af7cafa872be48d8f63012ec50a0a402c3f0a1bea7fa5997b300b7f6340bcb2cdf60d522a18d1811e33ff8f42598a692093c6d26957768bd07cc575ec31bbcce4ab5f4412f637214e8618a1b8ac7e206f33455935c09b87d6db12cfd38d08d3dc5f80f5f05cd726d84352a187f4bd512f6d22a310091f9074ab0b990ee883fee5e345f49c8f4004f065a799b454769f56c37f6aad53d2064fde3ee201706321ce9f72e5ffdc967517a0c5b4f008396cce976cd0e302fec03e35c8f5dcdd4180b322292a9febb34e1a03a37c9db8274a8d08684c8f3a970d4396f1b8557b72b48cb78a269df29723e4ec31ac0e879dd8b10ea37d78cb26e73c4f5e420454469571de96be1897e727a378e80658db545994bd36634fae94a402efef7ea019dfff1b29a1c18ecaf722976d3ef1ddfce60cd6b84a1e3383fa65d8b615e1d607b14f988765c45115321e975125ecc85397d7a6672720c4b5800eda8209e61245b4ced3056325543ae728f3323e0309fe43830af50fb2fea2fa2a416a6428fec4d424f5c04bb3f709b725b7809bcfdda64f77bbed3128f4f1773b7db4d0b5375cee8234e79f4dd2f8b151b54b79cc566c9327a747a8631a9227687d8150db13880d84ad9c5ee6533314dd6a70b45f7d6bef62f61fe4fe462397aa475aadbbab1fdfeab78ddaab1909a7207e62977d6246fa65058bf09dc34614bde7a0fbc71cbc26b3393c44c6a55f5729388d96aef985bd647a1d1933278bdb320b8e5d6b43d334872113c81ed44c8723c0312ba16e7d1df55d592edbd01085133815fa226ef600a6c2aa603c1ed1a44f1de2e11926e8a3c5e2ffc5d5b6c10d68b215246b70afa1f983a954b4b6d2b72769cca15beadef3d5e40b0cdf8dedabfd19b96fc2f4b559ed69bdfff72a2457251354aa9eacf0cc8eaa3d2e3a4eb0aa6d56dcb234d8c665991b5dfb022585572b4f0b9165305a5a15a6f41ab0a35582f0d76a3f685a26fdf0aae5ced24cf7472eea7b536daac5abca3902150ed8867cd153b27f52afccc3d9996cfe27ee32d72b14668cbc9a3f95217bb8e1a5de0ff2c928d0f8ea3cb6d4e44105ccc88add7341f3a201934dfafea10972bfb74b8b74e6e360a54f0afa414c118e849722e2b72cf1fbb47cc38d9e856a169b968a184f05a4d1582cb96acfe49ff648f465fc472220188e24397c3fbfec837c29ee1622ea39981c88d90102c89fe4e165fde847278605419eef9dcb6c3b35eea1cdf06228a5d6a2121b1793b846f755143465472bb9625240f9ea68f6f8b38b25d6fc4ba30b4dd481e6408ca0b58838ab205084101560baca592a9fe948bc068a1391d06f8bc1d17dc9c358933e61c9065454872a6302fb5f440399ba339bd446f0627a8e36b27845b7e6aea82f0f8132dd4f17214650db1001d4833adb18d5a43c46669e3eda87dadefe9679b93e4cddfc60b72f2641f9e40e13a2a23a61f2360cbd2b61f2fa067815cde84efc4d83a8b77233c9ee7a39e9fd816ebd88995a6dcb78350e2c20a19065045c2d478945e38da81722b4ac63480468235be66302c50c777da54360de2d533797f3ec3c61a4c634061b9f5b5785f75b649155af7b6123c3dec2785aee1f2efc38817b1fdbe76e49d72078261fa6d8edbf81d3a9f214f3b2aeaa34b10f68110abdb1a69068bbf9a9e725cc61b8098d633fbf148229954935ad36923cb2326c828ad14d8195227384972a2fbaff81d296236e2b7948a53f5f37e7d5caf1ecc463081c26358c235f81f720257a8a2c70f97938029b20ab91a710da835b1944ddb5136890ac314ae505e1dc563c71549ec3d997f20dfa95d4445f1198ffe3908036eb7cf51a53f07618b3a92350c0faae8e94c566cce942b3e3c2ab012e5ed2e130dfda3c54dba3135e472245b7d21b819483d97996d21af5a2feb5bebdfb334757ac7e9b4a12197790672df05dd9e1a35f93637acd67986874fff62ada15514ba176b136c2149983e0172b6853705f2edd380ca8bba0484cca0467bdd386a81b71fb5d1bfbb372e368409d52b023c9b035928bb82c8387a9ff7e6f75c87a6fa94c6461651edd0267ff1727868c378348b29d9495a628f408bf4a8efa84081174a590f29f033a929bc6e425e09ce560a5e69d10f23b3a5bfae05ba7618e9c5bff0e5f08ea58eac6cb1bf4e7ebf1481580033920f78debb329d4add103c18590892d9aa2510d3c1ba41955306411798de91ac93e037e07373be0e8ec407de3aa44f823be2acdbdb3862ab72a536d1e87f87418aecfe558e06665c71fd7a3522ef26a47eb2d1d9c648e534723b2286b08aa81b749fb055c9e54ba1d197c5dc2e9899e6ca90f72257f11a0d72430a8aa83bd261bbe740a15fa20afd3f91d7801848dd85211653eca018ba5f58c8a48398ca72b6884b17af47efd23c04b7e1cb42d370c9767c3186fa7af4e967d72eb999f4285faadb96eb21acc9be5f54c592efcf3d0ae85e132f7d9c85a91f6b5446c27d87ee3f0dc2cf76c2da2367d7f60e6075687dafbc50229ca364254c130680461da356e4c39ffbd94e059d31af16c91c227e37d50e8d7a8752c47749c675ecea3b757a2b9cf70e946fb6a1508424d674a9bd7d9e22f9ee2c8c345572b82537c9b98b05bf99f707ea9ce832b5f7fe8a1eedeb8d8c143b279902c0fd72e7b97e459d4c4130230782fa0d3b1d9a1def9297f5457997839fdedc73d50f72d6bc6cb10d80689567397d33e4db80d26d141eea8e0662af992dcc3cd9ddf0720b9509f147b7efe39fcb86fa3d2a54188fcb006c4421b680a92bd123ab3c40726d64d1fc35f6bfeec5ea4d33dd4ad55f2f90da54eaae84c6a4b4e3966a52b572e5d698ec17e39226c6da74865c9f1bedeed856576dcf96d4e88902246c8f6e72f668c490dbf04115a7962711d64ed150bd28222723ef94a6696149682e1f3d286f843da6933ffc5cbc24d4d8d28c37b6ea483ef4d51912e6486996d03bb9ba1b60ac4d45a834b5efe6e79383a8a6add6e729b8374fd6cbdd9feb79406bef460599e08146355135a074c91e91d26215b564af5579d1dfb271d45f648bd6bace46f24792b178b3acb3fe8af32e530df528136c2a32ce20723370c0b0375c0a62722b92704c55d298adde272868967e017c7c846f1be09e08881d2e107dd7f0a26a0d3b14ba19d47b4aa6bccb737bc48f8a49a407a891acfe032d47c353fef3bf729a2e7e800950ca3ccd01724df8abf0b70269ae32c41f3958d94d67662465eb5556db49bf600b6d3224204d2a2b289cbef4c61bbbdb103c62fc901680352896729a464b34b3d20a76f15b0b7ed698ddab90bf71e968b1252c0cab82654bb188726f2234d9814d8ed8c06302520441ea3da03baec70fe642e431463e35d41136723d29a6ba623c9362e1289f27d668beee37fddc0be314b630e9e5f6d819123c72e2d6c06bc74a1a92279a475d271ca232a33a186803a875ca587bd53804ba1c0907751912c34ecaf94c7da26617bf5fa0fab6c82adc618776a815806633123944aa75daf2166c70b821017e840bb2d9eaa3bd4c0d08823e5828c36c2fb23bc109d2c2be0a118bb513ed39097412833fdfbc8bf37b90930e26182152e7d544e872e40e0a979fb8c780fe081983dd965c6b8fb66fa29109bfcec91f426c6af1dd72ccb7d78cb9d023533389c4450398ee81436a13ce0749ea1f374bd5af8dc8db1d5d62d1de9a2b23e1574f301aaaf133222e75e09fba765b1e3443ea00f36d3f72a841005f655aa5e837f93676ed4991d7620b8ea3c57a2c7e156f9b24c416ff6c2f9620afff22d0f80a897876ebe5657eabbc4856c7001852a44c73e2add96172e4761e031838e678ed60e8b28ac884fc09cae954e23e285c2a39ed8d1aed4e584df8bde2adb773595b4b1c7c0500af8df3040f88b87f342274d4b527cc0e6e728398c896a2f549bea5f785ff199ca888619620de9f280834c78f7ff335511b72897e8b37414dbcf34d8b7fa279d68543800542c8815489bd5128ccdb4733057204aeacaac85e7f3436059c8ac9f63c8b0899b80a058c3c57029e69a42f23254a74ece0da27172b17e435217c4c2cd2510c3e5469cfa2ff584093974004ba0b72184db51c240440111f077e42a8b20c9822b07183ed741b697210400871954b72b1aeef48390d4eb7703982833a69f5ee80e607b36f62004f456a65bc70ab8f72379a392209c1c5e10f8dcf02586b315abd860c20d1d1a21cbd7a640297fe5272225c39597a2f7425f74c8f5e719f7ec96f5c2dc9d4bfd9e1030865bd9294a77207b5016305a38a7ddb8725aab8f954b1a7fb06443f9f7c7cc0d1cd0b60bfb672ead674f7b8a3a8e6ccf463c95d1f942ebe1b91c17c3ba8682fbcdd7b5ec76f720e263be3f9d2b1b1176bf16fc5ea3f5bec9f739c172e9893ffd942258e9ad335dcd2cbb09cd31b9dad8dd716914d8c05b83e9fce325d2a581fa4ab10e8e47a726f049e857dd028763d319da65c3c88aa26298c485545aca17c03f2345de925473c829bf0bb0852d55435379c2faa4a02b559227623075bf81c2546f483d12d7274338b9715c2a678133373dfcc1f7d4bddbb86c488c97ef9bcf7573127124d0fc0ed0747a9f348e3a80452a8abd90b346c9636f243f26e46ab943f42d3e3cc72c64bc46f7b0e17c6f1374af13c17a22c21677cbd86f04275e953ef661f6478099badfdd69f955ff4231e01c58ffd67a961491288bc0b8cfc1337fc91fee3b16ad7a966b6feb1f1ba1ab20f885acc6dd31f7b43a21944dcce903c9e8de7d84472b6f9acb8c3140bc329549e6b02cc7d097c9450c12ee06871774f1efd844ec772604f4432a209282c1254033f024cc9448c3a223227004eaa375b22d5b8848009286366d77435624fcbadf40e1adca06851cee25624ea30119ad3845533b99565acf5f0106a8adf6af7b1315f54c56b6d9c82360b864d70c80250a5d57d930c722aa1f9f6e7b1199d8ab6fc8391dc1188164aef33cebde9296d38faa266aa15015846b070d073c646bab2bef8c1677aea7dd28b40f6df0c4ca0bed8f242af9e7249d388b84a7a6d263f6cf717c1d4e0678ea108488e66bd203ebd8466d1d86e72f5ed1baa465d3b11069dfbcb342d9bb726a7d09903cdb48bbb4ce5785d2f5072833d16b6225e3fb153d855418b8d3e29c316ed83c9bd563ed78b6afcb3d6a903af1423d8d086f88ff3aeaa84272cb079fc0038ddb4d2b32d23b6af421fadbb72067766afde4b4cbea28e8e17a89a17a7c18daca2cf27715672eb506a2a8cf40a548d5a75dd777f972dd45f29391463662d0256c841381e402bb36f5bc123937271c2030b495f4e8b1fd568dbe37cb8e8fec966d05d8bf3708fb4882f1009e41d2e3a472ccd1d7a3a1d26a373d4c336835b106ee4777f68ed03f26d0c082dad69c0ac08010061198b51bc7985b66023ce9fd108b8ca42248dfdfe0da3421ac3232f7142b53b4fe7327e30cec58aafdc8891eca71f7bf5e6466e4058e2eae7fd72dc05038df65f8821a65a7eb668d39e49b5f2c5479139c27b0092dc77937b0765afbbe6d05b5d5599619cbbc11225c24d0fc4f7e3a99cb7e3164124182309c46ecb4b9be58eeabffd429e4ad2367d2f0e78c43a496ae57b6404947b0a02f8c6521f6da2dde3e7a0e1db9bc28952b4b5d75a301c8ebd65bce0bf0782f8e65e4a72c22ffd1eb07d0e3639e09f188020216dfaeef2dd7a8b147390c5ccb79a754b2eb6e5ac65f54b2595780e618e4490d67c130e93d7a8c2a91248e19760ed13fc721fdb6599ad81fe239cbafefe10f8c48fbaa8d2efc692bbefd1d1b8965e3d9b4e4361f40e2b3f0c82d46c0f785e08a7730d36df2ccb9b3f1a88272099ed582a7207bdf738ca5afe93724694427567b32be31bf0ffe4f003f6f1202f3c919a884c5b6724ce61c8f2b02e6afaf3f89d68ef38dd26df12ed9f1ed6f25f183264d372328e75eaf5d524fc2d09c4808e654ec746db385046ed80370adef66187fde07209e8a295acb75c1e3ebfc7d3f5037c0ac291ab3ded06b268f087dd82fa3f8372da350c714920edd4c34e59fc1dbf001f15bbd9c7f1a3f4d8b21e7bb13dc4cb0ba4486c8d2ebabd5d4928512006a193f321a80bd431429b6473801a9dd0093a7212eb6d40d9dcc98fc73dd77f45c2dacdc0ce74e064a156223ac1ad261dcd0872101feb76973a12c85083f29ff3f3b1bcefdd9d140ca21c9bed9209d1b89dc71009ff80d9d6daf731664bec38b314cc424ed84a0c1369bf1325f1e81f22cde3720f18ecf7f9163c223ac43767437eb9e232083f8654c127d4f553ad9f9416ce7257e85a40b9dbcf8e2425f28addc199de982075a7d47debf490c2bd3fc3a3d069a4031cc64cb3796b355eae48cd0611e19dbc5083433cb9899b3c426dc47fdd72bc4c05a963e4391c52e553c498be36a5deedaf65ec40b10e585bca24e2aed572c70365b5bdf80991cee3d4e9f0136a7e099df55f7d4536de17756a3bf1e80c7295335f4e71f5e099ae8cd6f53a7e626bf65a4bdf4a85a8af61a0c1aa6debdc2d55ca0b62ed73299e8bf98b118b6fade6e82175661e829fd8e8bbf7f0de918f256a655a905581b8a71650af62d51ce198fa9a67e982ba04a36eada285627a0b72592408e0425da3dd17366a6b386e7ea11d08900ceb9372c13d800cfa3376ae72baa659ead99ee053d357f0c5fae1bf6c0f9921d8c73892b0f1aed2fafb919d72afdaa4262ac23fa8fa024460792001874d1277e8591aa65ea21d5af8def1f372c74ca0773ed10aec8a6e5a5ae8098ceee6bafcbbc048f931e88e042157fa477280338e8c5683f986f6f9b26430c1f469fc1a9bf21f4d7136b19144656b4a27582faadf6bfe63b128b8c3af8cdeefd4d6eddf8c58e0e1be8d36ca75b48e742472ba6072cca62d5655d210a28eae3baf3e2e1337adb9a002e5f19b84a85d14a039b936009d376d4294c948507da960483990134c5f31e2de0a8259fa8130df6e72bf22c7541bc2068ea2749f5b6f15f94bc5414c8a6e9c6ff6f85a17d5f99ad07252f92a3fd40b8ee06915a54aa253280391cc39eb0448825df0b2d368f1657472037398619743377ae440c62e37a4b59c12f01aa9b40abf798c98cc0b0b402b720daccb376bc1e79388ed0f4fb411a5d8d6525c64c42bda58252e762c7b64ab72368eb5c00c997f57a0f8c1f1fb6409d6547f63387f072ec1bd5b5980b2d9ac27fa5852b86542a84120893b05ee6fe26833fbceafe8ad1902d163dbfe763548726eb24642a9c39aa8f13886846322eaaae65b914851a41c9a926ed607b8d5c672b8946d829f65b67dfd50036cd4a140ff2a5422bb27efac210cb1d53b67d0e3729d5c8e00149c1a536f99553a81f15008d9b53f237e1b04de9bd40e2457f19f2a4052487376a9e66264ea02e6f3280133564abb7cb2846d598fc3efa6c61a0f0e6243e7e51e0a4722690fe0599d20bef30716eca4e365d36d90b08337f158a2496f8ec21a2cd8ebd8a3274b13a6b21e07ce35b8d60ceb206c8ee81664a5909c72ff00fe8e5dac646727cbd3079a3b1576055eeaadc0b7b8c525b64356fadc7f7127d7fcdcd714e00ca3e6b1ace28dd18da21d6e767a7312f53040638fd5a6457258dca568d5be4359c6c19e586555f6fdeb5aadcd0fa94c9741824641ad690501ab133d3c39491d3bc8f3ccd830e49be1e052e7556c5415d336682c8d85b13472da060666d8efdbea8079960fbb5cafb78a720f066430e80677ba5d2237e40472136ba7ea25fba5d1c4b81f1e5e51b1764a12392457d6c200205ab87143de5132aebf760a102e1f8bf37b1fb7d0bc8815305db33e654dd7a86d4483f97426697264b6c9b5e3ee070e24eb940930b3d52c19009b632581ffe0342d08633427937236a33eba2b68eefda1ebb756f38e960c7228631b3e2e650a2b2e930519890847d7ce47ea3e4daca97a87a9e6a3ce3fbbebcbf3d939b98b94624a02cdd27d20722fa892ae677d253814e0278f52c0fa280ce5ea96390d183c36c792673ac5d247c4af8bb0401f4cfbc8360c1175f550e65d085c7fc1450bd4655e36a8bb3d416c0f9942b62171aec0c9c842d50cc68d142857d601586e0a5faa7e7c987a339c262462b2fd73f8e0b5c185e089cb3ce4e49c824c4e48c3566db336c02d54c9ff726fc0b2fc2678ebc3b2f6d96e809e703f77a2027afe5cad19d6261e9b2ec345728f6f43f3742d284f5de41fd5f2548919ff9491623c62476faf5427e7abc9514dd2fc6d75f841fc7893c81caaf369649c7a67093f5dc9643f4a2262c8a565f3725c059acbca60aa09acb01eb330f2c4d7231f29f73bc0ac29ad2ace7647ccf9720144c68c79145e6066b268af3e0f5a709641ba5f6e3a8c0e430a20b8032be90bf6b4b8b810588d8fcf1cba3954632dc297054c89eb9174f9b9e52ef2986a4672e5270773a242931f26cd72b079dc6712ecf130e88f2a08bf87894e9f5398e75d30f810fdcc59a37d6fe1f34c15c8643c485efc0ceb5d419ccef0d696e32aaa725a9ed460e637e794448bef92b0198bbe4506bcbdbecf46111514e3b0a6ac3e72233a56ebf135b8903a95af1c7099ef8c972b5f0b94117bfd065b2ba7b6b9e346e247fd132dc760a2528fff36f8185845b7d028df94a5d7ae272bd56ad5afac000fec908e0852c687673e0df139157e4b7a80046968a18bd26455255036292772f2f2ce74739ef315ff7f820be05250d1ca548ac354b6ab49f54a1f719cc3fd7260eba2318d166aad12e4697e5225dd2b20bafdd03eba7cb3e6044de0f7bb8d0864ad533da6e38d75a057f29add57a4e1dd4662941e5152919843edd46004472f6980d2d920a087c8c61616f1460f026b723af92905f691a1ee9d9a7e322abc06d14da29a9a34c818acc72d96ee3dadf677d93b4f411e98c39d27b15eeb7b781616c616fc19303c230d8c30c95d571c89a82c256c2dd730d4b8f45d0de9b55c728fdde4a358b65b8af0d77611a9dbe07f4bbc29f83a5d88a42754999f73927b72034742a333ff7cf149d33595dbe8aadd4c6e09ad5d5b1d0d0a5af96395f1327294d56569f41266de082faed4d781fa0bd50c8bd193133101ff86efcd288689727043f71555776f9c78d5d905810622c306edd856367824847d035b98f7631213fcd6241a7c116decfc9e9698b652cc4993bf648d7fbbb0006a809fdada88327253eda3d605c348b86c20c92e1492ee3dbe6152ba5488002e403f8d9857c3bd725026092a10dc32248d6f337d7006c84723e0478e00e6d09178573476a5537172a46b3f93ae09e26e3b2aeeb0d1d161ca1d04674271022c7cf4729061a098a87226205ef67e5cdf16751c2f70bf45baf034459e660ee40103595fdc32339ef627d41f2bb6b45edddaa4f68e988af6f2abdc40932b9e2d3b9006ff649030bb4e566520c95dab8ce0c0ab18d9e5c9e82e9cbe4d2455d1450c1a89fcaffc18129f7271608c5f0eea971fedfe9158c8c36b74f227a98a19b5b58b01b8d3ab3aea226b7d38152926ac066154c31803734dce908154cbf08231f26337da258b1053fd72f7928bd1a3dc09a782cac7e779ae8aa5ea3fffdce2f73985581fed97893af02d85c403d382988c603213d05c39d904774a1a6288268dd162f5c33a86737e7f72ad7523e6df87df2b4b07d22ef6dedee9d9675abf58db2178db7d9b9353828d6726b781eca9ff935df6a500e4ae7a962bc3fe0f75199385372318504c39d55d72bdcf117a8826c40fbaeecee5838a5a36f878a3f7b8991adffb6e015e34e7a4727574ca6e1b3c84a9f84bb16d0943b6daac242c33f93e47d678af1e21514c9e724e98d8e2c6ccff00faba002cbed1e13677f4b6833b6c443dd2874c2839bc361eeab9c1e2dbc13f8f0d86e59474efae1fce47bbd08b1430dc0da94391e590d27270e2c0a6ef3268096d31bbbcb68b94b9f49ca79f19ad1d29744d741543022d72135be18336929b7103609c675917629d0f1fd3b3f560a78d5836022cbde14a7285e42dfa88d1a948a6292ea8a61bfc535c2bf240fdebedf324aab56048b0091a4a3fec301904d6d45b2e2db82326ea176896be8bebe09a81999a959300ea1c3f42a82c36dafd62cc8245143de43d3a9f38763bd6084dda4825c11a7447728d6c3dc7a65d7f87f8ca4d9907e5c7c32f412bf95dad0b139d023baca17a2801b844884a95d75b0b8eadb9a523253549801ede09bf2ba44e02bec9014d5ef0b65972980c0a90277d7a97fb460a9f9f4a03be14656700934843f6f729bd24014e7272326cfc9a340072207858f235364f78b782c99d0b2617ab2d145fa7816dd9f3723ee1e3f1f8ece3ba776be0231c369a449651f3e7f75e8ca78dbab0638f035a72c9703e62543fb1de81d690ef749b78d3a243a54c58191469c2abe05571eef77206ae58d011ea0f1f534dd722f1a7ad654d8a9c2a9a5a76bdd23bb2d4cd218c572722600e81a9239da86fbd915060d677a53338853fc68056ef7e56f8b736a272db9b8c87fa9ecd1b90420225c5d6afb47fae3cdb15c3cae008431ef56d87d67230cc96b14aa381902a124d8d85b3576a611d5f3f28b70bb87894337a08177f39e452006c45047b9e7eb94b82641de94234e210893d17f9298147b38964cb9827a857e91361d9ce58cddebdff8b1cdc4c990b936bca70ac825b55d698d05fa5302023394bc40d437c3450cd3fd5422ed04273ef22c190cedaa825af3d3a0f29223385212d02c29f2b6b49df78c9263e24cd76a225a992ef7ed37106a6464c7b72e90ca02bf8ef6c2b34b9707082f963721e2262fa57f306b4ea29ebe3651d6572a86065c750c87f4e12506c4c38e07a9387641352adfec53f29671add05309a72931de40855d353a5b283f7983215f5308b421af0bec8d30d8c20cb5cad23ce72a884d4138182aaeb9335c3293aa3bab923c8862617f94516019407b1d1ab1a72495bb97df2b26d379756981f833da48f194e2e77c8fed883eb9ef2fc563f6829bfd248c30d4765bff506d55cf603c4c80bd815dc5cd8a081988c095a645fa35ccac2a879da89d05657e5943eaf125788693aa21da1e37dd576a38056a62ef4118bafb4ff78fe432f19f91a43d62319b913956a6cbaa9cf92cc86f83e974c9e2102a20978675b472922f32a33c53a4fe56b3f7ba2a6150e3142cb280dc6bb017248589afd6653548cda9c962ac5b73931976a618af5595c9562c021832d773e7223e7d9cc1ec4f411cfdfde3f49d31737c1fdd3adbcbcd9a94ef171855294575d803eac712e1203a9964df012cf9923732f88ab8c27602a67f8292d3584bb6b7205bc957dba43ab50dc9a360170961b1f364c5efc0152b2bd7837ec4eb1ad127233da57db75f912cd03a1f8909d0d9d030f175579ca961192bc69b3cd8c7b6748402948508eb13858349f5a83b694840208a330a5ef3bec192ea5a4959bcf7272380b32a8ede1e44c43aaab77e0e606ad3ba2aed531064a824a9476fb0b9d7507a6b433fee542611dd5562c24359a0728a6bdd1bedaee7ca564a28696e187be15eb4218874504a4c6421396d5471f19f2b628bfba4e50546117599544c538c072bcb2521b544ce9131621fc292cda4be796b09e47c49f747bdec5c5fd2be0c772a5636eb5c8a29c873a30fcc7abfc92c726288fa9e86f878dc058bb11f1c04c72fb47686d605de460609b70b5f65acaabfa5303fb2181fe3372f36d5c3176c872d6c9ffbc41dccc82cb38c631b6571570126c7972ee63c7e6637adbcee42b4272a151526a8813afd490c87f151e188cc264cd186ae9b1a6582b77887bb0d71c164e93efa898a93ca27ca67aedce1c0b0bcd9a1b694bf0eb6df75f199776af88729634eac98a032976afebbdae27c9d295da05abfaf97821d0071e550638a19b72d7177485731b71bbf0168b89375912c7f0229f36724adab3512316bca5e15d7205756ea0cb0bdd47ccd9c9a71b4870dea12f2a60f92f335dfe9de793b23a8164c9613563378737cae2a0b5bbcbade660082973e6d6654832cd7a115657ff655718d7064ea45fcc58ffb1bc00a19e1f9372ae11c937a7c10076616eff4e5dc07221925f1d0462809770f02ff76783f812d61ed829d4cb4cc9b5c86f534deead72e8ab262a867a467b848093df9e00b18be5fd811a57a926f679f465ccb6e77c178340d8de5cd0ee30c4068fbb80d86b6448e9054e8c66d88111022aa96acbfe0111c967051c70f7b15c16f9dd2cd7136536813acba868b4b24dfeb7ab5f10b46c3762513d3a41050e1ed03d2ebd30d72b7599cd13f5187224d74fc585df225672ca68b543af05c1c43ec69d9a9bf58aada3d1303e5937d8725bd6308d3b1a517299510f3419c87cfd86f3f77c2bd71693185c9433071bd3e38921c19aeed8e972e3d7a838f89b5ebeeff9e6c10870cf46c1f99d4c41c2cdab70e91ec40eed6865d051a6844b9b736b488cb87e58409a03dea1bb8fb265a5fd8f8c3803c9d72b4c71217833ca76a48e4313f6e3fe55fe1bd1aac94781fc7f7970621e3771a3482f11b25032472ede8f6ae763f616310ad573fe1eb382b3e9e0053514d0fc8d1372ef38bb86ee9435f6c21c30ade2d359e1aab1b4377a316ee2e86880e577321f72a8961bca27447c0851252023b036e26677546fe843ae75ea02b86498f95e03729398484d96b886f18812ada802aa5da3cfb30d9f2c23e1a1be183e15742b5f727321b5ed75e4dba52a5b819bd1bbc6a8afb0ea17f44aebeb42a03b5e7f2da35385a6a08ee4addefadf6afef34c456bb91424a02ec91e216e32f8c5e75c35ad4f2de83e97fab1030a0cd884bf0b2998da8842381200e3e96ea4b4c1e59595cf7000d31b7c7701c21bbe399e6f60ec3ec000473bd22d7590cc4f6091b65d57d846f6d0f735c8936936548c7f01c150c6c15f9cc82364408c785b8aeb1c710aa072b373230f31ec0704ee945bd2c8164c4e355eddb8499afeb74c68b791b333c66c3230b37a0b85a49d91cd6a033082e4cb9bae7dcac2fd1df3af277d52aef10b723d778199841ac10f328e04cb1eb694b3735894733fe09ff18663ca6e02386d72bc21997aef3ef7326eb61941dd191f48bd7113468cd31d9b4cce7ecfb5e56472568a597296a60e863543f9b1c894ba6c4359a122554dea98b32c279808155072337fb3359998c227a2658cde5ab97a2049e10110f29f0d2c3b05f08d88cf7d721f4ea9ccfd77e1c8874f04e8011e1b9b6de2b7bed4dc963548cbcdfbd0b70e7280cb6c6c7f5f2dc0494d3f652eb298dc8ce3108b6b062fb0ed9e6907dbe4ac2ec34d56f4fbc0035f510964c881bae79a3a3da4827b75277b1856d23b19dcfa7274eca952701773bd9c0a1716aa0f11604256850fa135658def2df07b814ad7720754a070332013c5354953ab9de09ec237b5dfea771555114c76ea4f4a82e572003cfc5c19568e459d78249a3934a1657c78d71c2b8b51b1659401e18d9897449cb0b83288093db2a909b6e575f2049fe9d4fdeffd908dd22414dc0032172e72d3a5cfb93081f39fd3998c657e70c71b9f82efa372223b903846ed3e14387b72a5a5cae684b05a7c7657425315e94730526a73190824dcec6210b16d30e695725ed8cfcfcd1eade90924292fb477eb337cb91fb9cd8bdd3eae73bad0434f6c72b5611f41b75b2e844df017e078c59895e3b97317fb516f187563f4b5a6ca057230d80c8b7e8b9cc5c30df65ad887c1e9c366643094c15aeff910bdf55946895a2e2c025bbda1647f35ab602b1c0594dd992a666fdc09a356a9cb8654b7c7ce72e4d987db253e9ba48dfdb8fd06c3122618746a2b6fc028ae9229245733aad7729bd3f6777b586144e1a71522ee94f377abd19378ae392000e0545edc0a0a2c722a9ddd658e24cf11403600f717fb0ad3404c99a1fde9af36df9c90f8b6ed7f72200dfbaf68b1e6f9c4a69d6b813842037b87c907f152b309f039d5c0fa080c1d413ce89b69b1f484b4dc88aefff5fba450642bf6f13fd9320ed9156b0dc46372b7a1abb473eea9b35f88c148fba0cbb7a7ca658476248a9f6da8fa50586a1247fcad365f227e1715095b8f04a2d9512bd8bc1c4ab3c2a7fe65b0478503298d0fcf9d045c87c95c43ccb6be0ead3e8b9dde2c405e692c08417615714ed2478a09ed4e8488758c4d2b39009c9b588afd546c407ec4537ff129ba0954d92673f361a68e1335f956c689edbdb994180e67d6caef292da61eacd80f5c6caf6b7ade721268093d4f921d81a5422ddb72f0223439701543faa9e95700ae8b6c54331b396d56d80941effa236bc3c51c65d5356d27e7c2df2f01355e36993c1d0c0a867274466ec8a5b8403c818c6226cb09e49805b591194efdff878e3ce42282891b720ec853993e0268651de95af197e6e94cf74e61032c631e06e487051a86ce4a72cf04aa785a848c6253a7d78b73ca58cdb3fa66b91ad7c0bf4b382823c2981272674758b4ab31172b2848c5126e85aa0bb66d8249464848e6198ad7a99fbe022280d327bfa12172cd473f9430a71c9f3520f64689cb93e1f5e57bf422b2e03c4915769360ef1ee48289e74fe3ebccce88deb0c1ffde2559a2fd9373e2083f667230064bd5aaf5140adbfc531599ac4a64d65bc28548d58d7c2e0016493a914a4d538ae4a15368ef83c76563df38c88fdf4d3884ed2a749cadcb052853e7d09a727483665119aab334581274931adac9901d942195d1302b506cd5f95e96ee1a72d52ddecb5485f3493f8780712354402e544e1d059dae83097425e29fc9fb9472698e11b4be2618ce36eab6ab419e51df825c88490bb9187d6c13c8af0baa7472ab760fd4837997b80de0f9e0432c5f8e7e328715805a4154a38645103884de72d74d9c1a454c34d98c2b977ae0734cdfbb2c7f3fdb2bc0996a3820b0d0d5107220e13542bb58e9a4ad3d94bb2f88cb2105c33c63649a683d2fa6dd4f76bc34720ddea5a1e47fcedc2ec22df11eeeb58e27fb27745287dd48353a527a8cf5ce230266f2888d57bb2e9f2963d3cc8fd5ac6a409239f7be86d4c564a69641416a72302a6acf7cab7fdf879f0da69b45b7fa471b84b5ade9a2fd8d92aa3d9d38e772dbcc55de2fae110a4fa340864c47942ec136dfefea8fd8da0d88838d78cf4e724b234f3e49230437a8b1073c17acdc9169783158bc0230a4b5a64863258d23726aff0bcd6af2042ce10630b36daa48c1c51fab2e56fbb4ee4de5ea0c56ff393f48d423343f4336f95e42c2ff5867be1e6d0a9bd0dbefd14843a7f865802018721085a8d6032bcf3d4cf3766ccbbec8bf3568d5eb73a9a6c2b031f892d5b79f7236f3a589b11d5b072610d53a7120ad8a1cb8cf6fc262580319797dd0b15fe67285e9057e514672a7309961566b7fa76fe74d54a0ae3458d83af808ee6bda497291da0d3ebde2da1bb3ed3dea0091f2440aa94dabeab7fedc222e72700ec79b0a0d79b80e96753c4a51d5239483e0ad7c28f966d46b87c2507d9a1454bce180723552a8fbd27c7754c4cad4240806ca5c0051852e4513c2f587f6691e895573726f19dedbecbdf02d0d729e56a99112bb1c0a9deb95cfd613781f1118452ce16e1487d1cc27668af5c1754395792e117839201763380895cdb95c4c3b10d1c572390ab7c7ebbd8b9d5b72f3b5c5d0d7f51669261fa2df111060d5dc97e048a9633e2342d1d1c82b9f3d7537eb5ec9f918497227ee575d357db0bfbb7bb4e3d772172c6e801ab134199b5f8277ce3c9810898fe207823eee0af77df89e5956e1720f2cea8c0cf29e87f89d02a96e3a4f415970914922139d7544a09d2edf13ee723be5ddf5052e984e813a6532184e659b5ef5f626fa339558b97d7cd2a2f1f472d768e16dad1bcb4c3a59940cf6438284a4066ef97e26161232dc1164ffbc101d1fc2a475ac1fe65ca8d8b87c9e56d9362e12eac0d6dae7caf191ff7a4a867d071dfa7335cf73fcff8ee965c3f30983184dfec73b64694d48ea70e20579b4e930ed6c501b7d40c224259a92f021440b9c691d8c2b4859909515ac2aac6c7a4e72c93eb3e66cea9cc2aebdbde4d6fd072bc53bada0163d8e3a9ca3adb5c7c814722c1774bb3d81ad5688eebbdca23f055244676bd6470193f668a396d5f02aeb63edebd57bfaad42db18719b6028b0906c9dda6d858912f3c90aafc2249e19c53220415b1ba59326d227edb8ca69bc6163febd40631c1bd58312312c0ac7f0ca72d96278662a22935f13febf591dd7a22904f1828265a03d266bbb03fa2c57592287e28ea37f33e3ff2c3add44b93c64dcd11b959d277328882a2cebfab7f3660800a9989663881bdf2ff77f3753590014cf815adec101835740224c4879c1a321051dd21c917361b11c5d34c0fcb8674aece7b0c89e778a31cec00d65d971a631058fde2a480057a50114f9f053f9e73d638f5020eee235c6dc9ea97f8e127c7291e4fe071541657f940cb7ba74346f6f144936022b377dc845f2089026e6e8400e2eac756f7370ebefe886eadcf593bc5286e576b0a3984ce88957dbf814a172503fcedc083b38df83b58ae4f4988d4c043e036b519ebaa7b6ec932927989a72f81d066158e8e1fc608a4c8fd8c2f8afe2296bfdd4a29ce8cd9e70cfef35964c22122fe2a7cd8f4e97c5dc7658c5ab82a7ac79d3a78205b197d9ce1f709ef5723bf77779789c8336a7b80ca624052191478ea4623c33fb26d193458aa5141c2ad8f54749f9afc1e0905d1d20c074781a9f0804b4c825703991fc7bfe9ba49955dc3ec71ffefa0b0fdd3043b5d27ccc9d137166f4527c615954a5144aaf1ede72b574cc452ed77c589f37cd6ab0d954baa96a682647444c92e27ce9e1f3650b726e5e332a572c4a6a14f29081a687b4a0de8a09974227527cad051a71999c5e725d7a3ccf33d38d4728128873ea8cab9281711f37c5c75d2e09091cf87e03f607153eb335b7c0a5a4e67ca1524e20a22fc76f1bc714e8a0244ea5478d83da546a3fb67037a6be25d4a5dd7c8005005f32e17d5aacb6b959f2a7369246683d996256fae934b53d0cd66bb7e43abd385001735f745d0de834a7633462af4237033bc20834492d9dd9964ca12a89f51ebd5541f5a2373d81691fb70fa01ed846ef7272d7825b4e2114c61168f5c93835bd6741859ae01fb228410837be0185a25272bbdb48d6a3c95a02231da9dd794b0f0410da7620bba1aa7a76ebc5208867e872011497522ade838cc63519c1688371f2d3604e59cf16bb402e039995a920895c5791dc8079d5d78af6f62eac02428a459c7deb04a24b962b24ec1e3d2579a726924059ee6c23e9d33b223f5ad84bb48ab747d12d4c854ebd253a67d734ad00726c19653452e6e776d2248719fc239469c4c37f0bde0a41e6f1877a581a825b7209290e9c541da6bf71d6e748e0076a4e93afb774ad76ad876f763ccb79fa0472e96a271618af8282c1d5d5008d065058a5a3941dcd07c6e2cb0e14394020245227931a4a417d40c6fcdd66d8e28de07b80b95852688aade005b74a6cb9d52060e6286aa98033780f390d8d16e3ca659098a61801dc9ca712978f390aeb85d9349cbd2fd0445e8e4c580c4fcc37fbb08c39b3bffc1f8512dd08c2efc241420a724559bfd27f5120fe83024b38e473104a9e40333e7b64135cff50dca791c2ad726e572e877c920adae37cd48694c304af592ffa5596b760b8df38fa6708318272ddc5c45329f10f2f8bed6f2e60583336b9338b8e41232b757ffd8326ab30ff723b7721b18b5600b84f89352646996fdd0ecdaa9daab63e17b9b6b9ecfb936072a2585badb6769f87c51d4f862f38edd50dcf5173b9aac8c1ab4f902f91c2a77230992580412496af124e2febe4a56e96d7141b7e62f5feedbf08b03986b9ff725d81a46b0378cd7820c9e41f11002bac41f04e558d0acad85ec026a282c5862504a94781155012ab6951152544b0d03f5a6930b7c25750fdfa1afcfbc67f0759b556a080da11b032b0e243d91a7d57565bd0f32f1b57dc46ce6ba4cda87be2677a270015e117abcc9a302be2e463061e3578e2e90ff9caeb6af8145d4c86055a1f258b13d11ccbf5a2ee0c2f9c9ecd5e7c0815a47475829ec6a97329ce09c8726e10c496d96d9d132491a1611bd6b5e0160ed77c8f12632803e435c718c383721c959282d9424797aa310ea41c78151a7783c0904612cf619759c2cd6df32772acd4a9c90c370f770ec8a2171fa70d2a99994f089ac914ce83fc7438bf5e85397d9e551f78211646028063f080c1e316012fe6a2b404e883f307a220f799b04c5a2b944d605664486a3b98dd1eebe4b020d43ab5b7a25851a3f4c2d66d8ee772785f10f26731104ce8e164bee9395727339c725721a1df9d435b5e75d5f140721dc9f935824ccf3de8fe081b3635d8bf01d2580d54010e4af7932788ec7a384e22ba19b2b0478c61a854408acee7bf8275331fc22587013a588aa626ffef98418f6b649e951772ce42b78b58042cecb5f82976c7b2fa91852e1df4ddff80fc7260fca3902527a71bb6ddbdde6ad5ef3df4f78a0b29e020c8a9f3babf10332572ed7ff2513ecd8cfd010db2e30d5df41474177bd92006e4501ad440c542ca0872a7fad74fac239de98c2c843a5be5d6d02efb7188d035cf64443ef76cd7853b2f08e67142023545b5ca1f21f8a7e3ae65d6bc569da8466024443ec0afa762cb7260ac2f9da2520280fb2b19a0f881a85f8ea01f5916361154772a640dd6f06e721402ac3651b23e78cf473c917509b481d16b740ac5b17c91f2357ae40c6bc514aa0204a7d6e07e1401d880789324e245edd6e76000ae9c9adc8430aaeb4526727714b0e4716127910676fe553f45fa198e08d6125d001e4674de89cc0332014a873369342844774b13736454921ae2affe826124b9176bb782829e85b39d6c72495e37708633722436a649ed209e056176195be05f6520639e80fa781b582340634d830e965fa55e029a41309e0b9a73a04d5117beb2189ffb5282a59c3fc272f4535be904cf91643e08662066a097b4e3224070bee3dedf354a264cf1f07e31d6ca75cc092262ddf792340cdc4fa80e16da469b3ef71675641bd47df2152839dba6168c8139c8120066997d371d4982729df97886e9f198951d4b9ca0444314d8ea6bd38bf82c730539f60ee05153522eb88671e3c7c91f92668b771cce903a5f6572c356afa23679cdddf376cad1c80fa332bb47461d880ac0174df2df5d72424ea60a08032ce1f70694ee8c1ed00db9364b287cff39765cc4df01a122847216c8928e8b19ae3c3f405861194a609db461a310a238cbd6f724c98e6c775608d670083866869dbc81ad1274e12730ba1691bed518d661a370b5e118ca3b1c72eae0a73ea9ab1cd6c7e8caa6d80159c8f815f64cb291e5cb1c6f2a067f0a4b72c7d9107400509e114c7fe50486191ea81094ce803acfce35a2f7a81d6d7af427f94b9f3a29a7ba5a8872b35a11374e0af4fc52a766a9bb90090d6b16470fd25561dce8c0544f74fae2b43be999084c80d89e1d2b4b17ff737292f14304e444727d6daecca77b1d9e33b9c28dd2297fc4cb5c8bbb8adb3c2971ec9994223af769c1b6cb560d3c2fbc95b7aea62af52b3b93fcf23005d24ff96be2a46ed7bf0072120ddbd2209f8126a633c59c866293e5b74f4431b2b8ec450582039eda2980184003415ffd83bfac95cb9099de8db0b03b29adad5d0184eadcc08bf8ef609e727a0e102089737be1f3127d35c9936e0cd88ced5d9eba6d23c68fddcbd06e233f922d757ca0b4d68f9bde84963c10e7b01fa65dd24bbd882a842f985c5f80397235d6467d3d7c0bd5188c57deb5f3dea12134b01043851e2079c569f14f66e972eabf32623aac7fe47aae102525e0989e9893adc5e009b6abfd4e1e86af49f672c4d072a1b92b14ac27769fb96c3a484f8a830146cb189a10d52d688a523a74720cd02a80b9a3418ff3b94e602335a35527d2e7600230607358bbdd2a870a3472f96d26f455f81d0cad3a5e2beae065de56631e0802763b13883ee29aa6bc3f7231b612c7390f94cd7d2b087bbcfa832995122eb82c6f956588131fd9966c8072aeed71e848474e3e49cd25f85df5076b9192539650d2d517764e7c4024da323fd82aae6a832bc811c509d006995a04c260d56d0da820f5274fb49793bc815a11f71addd0fb5328631d031a79df8dec4b637602b708edb42f5d19fbeaaa68ed72969f28911d2d0bcc203638e0be0d41fe0d60c6a18d9d5c4b9c5402bcd503c26b17997fac953e89729c081ebac1cf0fbfe26daae87e0d8a25e290dacbabb10872f803f1ba7c720cbcad7e491bb45218eb4b2bf8f93575dc8a4f848a9b6e4bac69f9d09e3b667183504610378c1a4cd9ee5511ca4d3d407edd6bcf05999bddfc725bd2030989352212ae7377cade418dae77817716bd98f8be14cc1c00e3be3e721306263b93d25cea1b18dd63cb45ee151dbf5828e63c1c2d37f6f09e1f054c72b33af18c4d82db554fe2e1835ae1ffc7ba0543952d77aef1c13406a771721672cd421cc9648c0ea9bbbff371550bd24555d89121d38b243b0597812ef99c8e2d15f5672c6067b24e75ac4c21966fff5cb53a712d1d547d0c860ee902800cdd708860d3751e630a1c53fb43b0b701fbf1d3ddebe3960ff8fe37d7935a137a957233f83ed1f8fb0e7038ddb38e44598fce1f3c7b87aee8f6f53b3951f1350f442710b775c53ecb4f9e09e5e14e3d15250f5ead5ae0cf50c7c360177c1bc614af3a6a64bc617f23680ea5cd0247bf2ea078597cd1e5cfdb405adfdef3f704aa07113b4b660a6ece3e56dc9c5881f6bf3bf0a7d0cd79dd9ae88295ca4aa2b0ca915282077d79591de8bd906fc26a6e11ad74055267871f8eb9398909fe08513717729708379203dc8682e00358c5debf99cfa19e84d0e8694cfbbe1e6a3bda8fc051b596a2bacb656524d03f44a6e956d3ab172e868f74568853407a0d61aec6783f841d44b91bcd8a4ab710f36b38aebd8cc07ade828e8718bcff327afa807d74722bc536014cc46e2efb372ddb2c6abc89b376bff9209be374a24779522dc58f72ab3cb7ba14b6f16de4315144e31cd716b8f10cbab262264d094702fd4ea68572f71c851142736a3011ca26fd211d6eb7722be9c0f2c2c87d0bd8e0fb05dccc1465efedc761f91bdb965418ad8705127f927a8b2d39e5be1f864830cf6a0e41554e28c671cc9c301961140a02344f7d73ff67e855b1d9eb8ba30371879636b4722aea654ec8ae14be0afe01765d0973f70bdc6824f97710f9ccb06d666c442f0d7793051628ed1ce3e72e0ff410c67b1853bbba59527666cb9417243dff35537239bd44dd2ef616b0af772564761c4463016c585c9bfd289fb86249eb1a9d7e576303d0429089e53434ea06948ec21e2ff59c66af10f94b05a73c1fc03ad4c272b10d66c441ad24969974fb6d5770aac2f20fced14b855621c8ccda463956104eef43cd7bf8efd9cacec091bf213b3661a33905d1b1a9432fe9e25f11da3bd272e933da149f4b2907e43c245a8d2e3df5e8695818ce3a880ab77bab2c17b7e34d46731a239a3bdc9bbd42ae75bfff798f9e061128c6dfd2429200b28b492770721dbe02beb64c9b7daa8d0ecf73c54bb4048908c32f582d1fb6b93465a144723e354e5cc5537f6afbf9fa21197774ad39faa451166a5c8d7dc8d5f919de36ff72382356e893c8a410187066e844435e0abf2cc5df61b92dc6cd9996db5b05602fe8c3c6072d3f9079a4dd1e5aeded686d14a301764ecca8fad9ace66737f3fa40f656a3adf54e47a8c7f52b119a56589c362ec505608d27bf3ab333e219252b72b39313e1709dbce66fe35315b8db70cda099a9f48caeb58467d181bf026ba8729fa15036cbb4ae6a605b31e53d35def3d557b2ee06d5f1b2cb56665c925c7b3bb2f978cfed3a1e188c20762562afa63c50fca494e97f596e84ea679a2801f6727292f7596268e445e6a75f616f55a159ed563d648cc04bd0d7ea983b70e6ac62309b197d7dcd262dd35cd63a57dcc5dea76537d0bf85c86732fd3c3c0bbe4e5445c6706b003763f72b593157d82571eccab1e4a6f4d34b02d105852fa6c2fc5e1c508d16ad52179c9ec4e9acf74344658f9f92d8eaa3df4854ced8e09de5fc067b83302012ee13ebd6feac9bc5501dbbaece40c34ae37a086e9d828f925cca72f5df939a2e15a937e369a4c74d1c5dbd14602c0444969085154aa5a66af116155714f8a7a3e624104868a5c16adeb43b704ab6148bdfe2256494c4e66b5377724631046f659806d6b21fce70cfeb9ae5681ae0b39e524a5585dba755ea8f114803e3a3473f4b1dfac5cd01ac7a954a0ba1e8309e2ec22d93c1b5c25c036eab21d41615e50fb4bf06373f733c748fd944e25f5299d4a793b2a67572965b10f6729b5870ed8e29066ddee1cef8254f76becd38880b6fa712976674f6ad866dfc724ac7c3fa2644281d83248f4d4a8240bcf211f26a734b0bb75102829ec31da53005d6f4e5d5a8fb7fab6430caf0a6321a025b0e21e19b4d6f154aa2dfb95f2160c36f7c68285a4581adb06ae3701c7bd02ebbc2f14122abbcb952c6861730cf721ec78723286067ee47feeb038b5b4cc0b74352ea1ad24d5ae4b54c8c2520b872f289d1f3183286305ace9a361ba12e102ff34c59d1be6a13e9e59f869bd90d72b58d3324c08eaea6187f37f21ee4f0aeab63e6b5ac46012707d4b795c5360c1f80082943fb6e7dcace5fe2dd0ba48db2db05710a2b56093adac8fa64fc943e72f9a1ec98e643abf2d45b22fd0f56d59237efd55714155414df370ec66174e772df32b0eea9e3fb5b3e90ba18a0233a654958392d842d73066597111ffc630772848fc30c974f96ec56a53203ff7c5c04e941de62ae827023c4c9a5923d76d30d75eaea17bfd8d1e3a7bf586afcaf82b6afde5ae14e9a9b271ee655d5a342b372e5882615f29fe776420b6a44ec83b5071bd3c1a3387c9af3084727004f08a6128f132da6d0f74ba627df40a71fb7fcca63215ac5247ce58a89de817d55be083f1c1d0caa1b4c7b829e8608519271397ae21076cb7956d5bb5c0ce31d0be1e372bd526a061e8aaf1de08d866f85b5b8ab16c1dc7e7ea4165eac8dc24bd3204772f6ce01a9ea6a9e947b51ef6bf755d3b8a5928bad0886a616dfba894a00054072757981f9af989f35ba5c50f81c4baeb1701d2046bba4b925210861fb6880d7727c3a92e467b05751eb2ed3f4a9182490b83fb629565ae708f79ad92ed2474972c9f61db79b44c949b26e9a4595b2927c92cc19355853e9ff26d716ad02e06a116505f58017bd0e01e2dd932ad39236d84633d15461fd7430a3f603318c415144215916c571b4ae020b11ba23dc31cb661f61d204aa1d68f6b6ee2a5604c2b94433995d878bbd69192158d3ed9fd8d79779773029348bafdd88903d2cf483553bada6095304010a719a2ff7db0d1676d62d44693379ee5cf5e307c9c3bc6196729283bf436d988a36399a69afcf3a0f3eece25df508fe631de26663cad9f62872a0b4f52b53217df98ad7cba34e4a81884256932455587e3536ea4b11c4ce514f1533e1eb2f57b0f2f8428dd61f7eab35b2d759e8f4ece80bf4e17c6c5d9444722a8dfae02cba07c0099179ad44a9bc5f2ffd706d15eb47b97c94f5be3ad6651e6c2aeb3e393f47c026c36a96409cbdf4a5d934246dacd5b2d0afaa51dc866918f911207552d625659b5276e289fe675d1bbf858f4f9ccb8940efc30a7c4fec729f0d40ecadd6908b667517df045e2dfab53999c7dc315bfef7e7fb8ee881f133886206ac93c6549eb3590e8c9e170c7ee6ad33023e6a0f39d3b6b8a00c979e470be90022f8b53bfdbf1f576767960ad6b4556eba741361877319cb780d4f844b267f71f631696832b287dd52bd32aee82f94ed70b4f43f9b7fe56779fb501a72a9bff74af662ff85f873b8eebd6f17ca8c669310b4798317e466a897bb7b8d72d76e2eda6e411a467fcdb0adfbdbe6d0a2df11a636e0f4e25dd272505d117a7236b3f14a9e14c1d0dbfee3589aef093c932bda9b8f6ee6785314cf261ba9e27269d1851c0fc9bc168a55aced157ad8e08b6118d6d21f3469237b009e8074350f4c9aabe3aa473e51005b24340b7b2bb65b2beeee73f929edbdcfc344c181d9543a457b60046904144e4b3aee503e85dd9a2b0b778103377dcd77dd753c50b972ba08ac60f060135ea494970725499a6061a4ad01cca988707d17a23050560752b3a356a46635ea0f29209441f88ccb2140b36f81c91f6f6dd34edc11c2989072f643fd58af07fabe91af9f71d3fc2530a8727ccbd105ead85dc34dabaf3ca8728cc1b393bd08039102a4c150d273c940e6b9136200613735ff7b458c07f55e7267dfecdf05f876742770765e6c1662b31be7f82c3c55c74f23f081a4832b1f729945720fd053c85a6a210b40ad4632957aa4a16d4900dd723f65eee7a94cfb0795ad6d0577c490b10518cd3888d1ad8ee7dfaa92e994c2384d8fd44c186c1c08d6918992f6eb17d6ecbc30d624137897dff76b3631b4f7fbddc546eeb865976c9fe520fd8710d2464d73f7c3485c4f6aafe029b1d93c72c5069881b0cf51c77288ad213ea287b8c21695827edc21034b5204c51ea28696c70286192996ce0f64497a1a4f32b7d59c8560fad172cfbd20c2d16ce94632c74d215e577e27b396727eea598d4385bc3b1455567b47cdd4a80a6be11c3917f7802047707747919b727b958504c92361e0a7cc78ce68279fa278105238d230df17b34a2220c2a4a31cb17014c6074b446c263d4cf425b7669b3cb50cd8ea66b14fba56188ba136846c0b69bfbb5a9f6c46318d218715c1fa01184333a4de36a031d555c9235f6f2314a3a43ee18622aa3385094519366829a784b2708d70b99e006f518ee87ddb303ff4034b2cd267a2e7cb18a20e4f572b23ae4cf5b6ae238a479efe6f5e269ad344743afebe8c3f69bba2336118cc503507d070a50908911b9c6dfaa3519528df1913577e0d5f4b6124b3d6a7fe8c6d37ba3265d09dfa52c31e6cc5143004e42023ef35008033ddef5e124944b7678cc5e9046fe2fb13b8eb889eb3e56404c3c65545767ca9add35984aa38ddb5d348b80c480e80adba767461ed5a1fdd0b4ef14d91b94654be811f849ece1ac23965e62ab846255a2c8b3d54d2420c5eca05ac7212e0b0b3e4b5b04c452b543e1960ea7fd3eaf498ace6895757a85a7e98644705e90f357b79315918157cd37393efb14f580c89b7ad3b23a761c118f0e1d9de7233170e19602cf3989e300c330afe1221719d3b4416481e24e8a6abea162e1272291d2946257d11100d8ea622546bd6d68c3f0a66c3221299af3a76b066c89e4996c628c7c9ae6ff011269a28be7e1e681159b4b5e835c95a6315adda06143b7249fc72d89a444c925e1a5f7557d3a7f16d004a25ee5540df4130408eefa9974c5c4659ccceed3703b9e817d9ed5d8cae792880fc27b0116a012a8989ca4cb172ecf6f6b01a39c15f73eb16354cf88983bce233e97eb799f740e0a12d6d7162688ad185f92f7650256375d764ebde8e9c9ad9b4b729d96e638292a9f7a022f972598203983fdeb11874ba911765568ba0fe6455eec2501fb3df19199f9dbdef4b0f1f887a6a3e5f472fc9b5bbb632edadd3931e16be9c258e1a22895e5d45b572f11b712cd759a4a92d9c3e8a21de736f9170bdc53e345f7706b60fc5c63dca7255e236de6cc4b9fa4aead493836615c37146a9311d845e234a918c13486878727bd63d65c7e6ac4d741e122612ae3fd9b99cd7364f31543c6bdef41209f7c172109866c3faf257b46021aba2bb9596ba8f1731c6cf75b7f6f3b1ee16a18eda477f203217cf6df58bfef94d24e1ab7f19a176ec8e8193871aec726a331d04e672ce7c13ff9a739b1b270a15654c6eae2d5c4f9b7370d9e3eeaad5fc387719da4b923e0978196f7d1aa7a6b6635522583126c271d7bdf80cd67bb5a9a82dcac65f71f826ce6fb6f47076e13ba40311fd423724816f20cfb1459c8377bf06bf247299a02805393f1d9175b57c2f5ba37e1cd2c616e4fd2bab17f05a6fdfcd2d0e725c800f00d490febddcd6f3d63f10352f66667b53f70ffa472a28f6f383035558c97bca43c023d30e0a77ae5ed4ced7873a919af4c58d9beed1ad04d9d69d8b7293b56fe5dac9f3a5b0f49a31ec1a9c8d91be0f8e45454f0f5a6ffbf88382e972c5c64aa85e46babee1619184a4bb1fa6b7d2d78dbd0f69fc1ba4e7bc43c023727cae356c7213ff57035bd8631170915aea0a1f2ec88ab7456f6a95727e157e4dbd92b6ae1e253448544827bf4aaac4e77cb65e8d3c4219189465b30f92db1f0dab77ec0cb0e8c5d5d5f422dc80c2bcf98d4d6ecce59f10acd44f8e7e41c64d4fc079e01bcc4393fd5ab44d12218866cc24713186686a12e37568f5c8c2a2d2725cbd3c8861f5841c8d21a7c7f35a37efb548b95e63e923026165d7fea545fb714c5608428ef5b3b2f95c464bbce252076cf0b03b707be04309d6f42f58777e367b97fd00cfdd6c3823b54716dec7c3b859eec32eaafd101a4270bf7d613ab235dfb1c1cd82fe2cb3c6f513de607b8f2f37d851b653b937358814a061bb58ab6a4650dc7fc40c6a5a66dbcdff719b946b223708230258f0cf22c19191b1195c7271cfdfda5c6be2694e282e69fb0634942b533295cc6eacd7b04bdd07d21a4b7217ac42f7e6c10e97e35ceb5c4934b519692a4530bc944042bb4dd53fbf59a672526b49afc9794d9d2ca499da1bca5f96347838ed4ea65696778a06afd354e06ba430aea640206fcb706f7af46185e51fe1b61987022803b598bc8a5cb3780272e49ad38f9e3e88f44050899162e9da9af5814e2d857474eab62e454c28944b395f1f31009dfc31c3e0398f6aedba459882ffef17c13f5be072ef84d26262d2720fd1efcf413a9aecfcbae963ece0c0b7cc93bcc7b62fd14c3f64ace8e6cefa72d06bc5b21621035bb4784d2e3a346a49e299a5a29ba3367c22379fa6021e0372d650f28e29f51e40160a15583c17c96e611453bd0b369b4106dff09338e59467eb97ec3f85b428e38c60d976fd73e72d5a6761fa51f6e1caa8d58b9ae2dd3c0b313647829e1f324c5ae55a11a8ea3938b284fc101064860f671c135b7c8191099f5a0373e4cc908a3d8ea7bdb3af6b75dc942b657f86175c55eb46ef5f3cd272bd5e6189467e6b8b20babc6f2b09f7a4e909ca01ac258f5eb6c243b7b4c286729ccc2b80385c26f9823b64cbad51dd0ff5b9daa8df6ff2e34ff95a0c2f46ca1c0cb97cc289708e311af22d9c7a043e786bd0d6add607546970d046f8e5c5242524364452ea3a322a958b07ad7076dcdb3802766729b10e9b39f23640d8d7d472f4fc1479d19a524f8cc5fd39ce06105566209ec19777b1c662c731d3637dc64be563e33d1ce9a9973ba702f0326e3cae122e3f97245fe9ab12e2b5aebbd0b07266f374db10465d206a4fe6522947484414aed45a1dbbb1b4610b940f6efdf5689321e31ab90edbd819c9652e3a8a9689cb5668e56560a2ce9d58aab6426f1925f45d987a26106e9450d2b2f3b3fffdeb8bad5bdef1775f666f16c18db9442950d62a2341c00a03eec41074671894a6177e86370650f20fed568789767deb2424f2cb6897bd938ddddc857cbda527fb0a7bde187ee5749eca55f8a386c3adee294772bab7312b717de95761f7ac5e7bee20f52df7d593e5859037961b520a04722cc7b9b56ec1f16ce77eb447735b92a542dead4b29f900186077da143c47c572a8df355332f14741a8a1031249477051256a52de4ea679c9397def96f587b9724b81129a74ad78b4b0707066d2b553247635385a7fe64faf92bfb04910612b0f242c57e02c584095f3996e7a8c2711ccd7e9d73df9186848815fd2f1f0b3b437f828b85a9abdf35b6eae107d44de7836ff0d76e79c994cc4bc882244efd32c1312830bf3e51200a531a0275ddc007b15ae3ec0ef73140babf501c430d6cac0720450cfc2a80a186d49b121c691d6f51f1661a7b86d7c7cdf8f31d178c27eab7201494a0d4ec0f5fc1ac21b1d8bf280097f30eb9dd2b07e2c838b1042dc9b0872e7801fb1450c2ded312a6260221591c3abd9f4db75b06745a213a624800824712cb81cd811361006054522df7a36ea95e9d4ef703eccb6494ce34a845d99cd72b3e42248f26e6b15fdff7e4c30d719d67e2c13e966e565020e09a60fb63dc37270900a30dcabc987aa35dd3b3370172920bbdc0df6811ddf78b1ae324e7b314efef92c116c7e28f20d0f32dbe79f1f27804ad4beb0147b890203acfea1b63a72318650b81c0869a1e6b1d7e6c86ff323bb62f9acd936e801e5bf3470a4d99e721c43afdcd5d667b783be8e3aba29e6f9c6fc2d9924ff8cc9cef69090d7834e6fcb8259d03a6b1427b5f9cb8ef1c285c6abc5ef9db52894df3e2799aa79747472e2e756116174761eeca3ed3cc3b4d864a709491f15d6f5d441e9777fcecdf3729e2773437a5354f1ca4eb43b915f4f8a214c2c492a09146e2923cb13583352721009c56a2b460e68aee346efbfecbdbeb18218aba6a8c9f7cc21530158c2ee203cd758509053ad8acf246e3c26615d1ac2eb26a9cec78348af44e968f5248672bebbeed523f13461fe18d1234a6108444097b31050fef6ea442643b466b82472869c9e95005991db865965734ebeabad43890b08ef5f08304b28f683198d67726180dc46f9d1a1f8826145669080508ea413fe063e0dcc67131bf92b946e682a24ac2123db2455bce2e1a151f106ccd41dc294c9513c1e64b118cf790d308241af57dbab8122d249f953d6c50adb6b567b76ea232b2f1e89cdbf4f37c9177c006e181509225c9cbe08c3d89a0793d72fe85e1e4eef5e12c6b6126e3351ee1b633a8339cfd6c7908d70321a5aeed46c0dff0fca33023d8b81be2d162178b891728391234c766ee7fe097bebb079a997629fb7750d691ded545368e070646dae22864bcf636aec689ffcc0cb1f773af1dc6936bd171bccfb9321e255f2672ed843f666715bcc3b869037a214b7ee37bf7ec73a0119f44358a3f2463eec5afbbb72e2e06bf54ccbb475671105562faa9d8c9a1f1b95180a499ef1c3b80833b740724a23fccb121578f91c62de22beeb0e1a9f39bfc437d370feefacf0d681d01c723176cb81841821c16f746cc271f4b63102b3a0a8e46d863ee0d2e956ab6dd83bb7ffe16eebd7b880fc76bc6dda8a8c92837cd70dcbf998aefccee2a54329a33f02673b9a46a99fb7d3741805af9472f5e04bb5c4d65ced54b77c72a06c232a72636deebb76f3cdeb50455ae9ccfd94e66c990a19780fb01558f2a2ea380125720175f08f81c0f9b5403cbfcb53abca3665588f09ecfe71ae06d120c278db41727d66005d735c4738cfbeb9ff1d99e71b758310909c632adeb573345846bff772f6b2eac94050978f9040f30c664a06f7f72b57d256df63d2a359249b9dec2c7245d6bbe83ca67def958fff44f0233ceaf3b6c0cf7257084ed050ca1fe8d4867228f89b37cbcc89db8fac14371e6e3d2993261efde5e1d5d0f6d7c6224b8c166fe6cb2ab2dc8143f5d51a48bdd0e0ddd8278707fe9c78369c3c9c52ab6d32bb725d6b58e2fff1df56fa0c620a4f4e9b5463070e752ff272f89cdb0f7c574fdd05b1716a18642d2e5fad06377ba983de0a1a789783f86b7a1640dfc8079e618172aed03228e06d460b7c084fe92a25a8cae54117b90dcaa52921e0b9b03a23b753345e7bae1cf6f1e7341832df56e6e4c40729455cf6e975b7ef9dfc510240b4517f09128c4e4b713732dd8908c9ff5d753c1636be02baf3331caea57833a1e336a388117d7ae129336c5ab3668c22f8a7ba006110cc4f05a586687fbd27e3312cb655c702d9ba2a8359b8a1dde25c008fe2c782aaf8b0bfce5de5a5dd345630363c9108a3872504b0eb9f5665c2978d3ce49686c99f52d82b5e7fac7c2f9782723203c7c0b1187cca9f9432084e1efdf279252a689275f9b33527317332f4eb729af1e7563b6866a6da63fac8804c5537c85a8a35fe5bd31ef474c6cad669a504ee40419b0bf4cb290b5c5993fcc05c3eb88443496b7da88c71ecc3b58623b23a4545144ab119b9cf84998731e431ad8aa4257fcf6eda9cb525eb6b008f201272605c546497cb22b275a372b9d7b51f8b23cff4f0ee20c538b2f6e22b8a5b5c728bf63e68ea4fdcdea7132805df5610b3dba60eda59f70fb8335f880b58da1d72b86350a8a6a87ee2da4259cf3c427192e7623f9cbb53593299fe2dfc16244572d9498d7007042a7006d299f1c2c63ad8443d9e8e1c8d7e56a2410e5abbdada72ca60d4ff0a948571e116df9408e955d85af6f97d2637205aec9f83bebda8b172ae15a213a5bb0bda33ff33e5650041a6389e875da2d765f936ea874ee516da42403cf1661666d07c7fde60bef71f09d1768e7db4724fd548532cf7c71afa3b4234e2e340241fc0e1102b07d71d7524e39349dfa359d774bac135d1a14b731310bccefc5f30f51abdf6b2d781302e77ad4c27184ffc4198e22d5e4dac49739206c51e31fe54caa01a88b7e13b8c8b741ad4bd7ff64a2a55d42d839714fde3ec72b971c98de301485a8adf86146cc8c74f4013f5b1276e5b66b45863324db899723235a5a6dc589f9ca09f2ce372c4a7ed04d43a8f272641351c6bd387bd33414c658cf856cf95b1ec63220a813a56314b68f59fe13fec651c902651f76420d172402d80f66844588e365743afe12fb16b4da54d3b05df93bf7cb81bb8d93ee472f56cb934fe4236d393bc07dc123bf4b99e0f6bf8a780a0c2f486cac2ae6f0f728e340cda739dea9d3aa13e3119cffedcf7333d30e18cde81c27cedec94f73f727e1291b6acf7c3c570f4d17e421b98ae2cce7ae6bf2494e5498e202ba0139a4d9b395a6193caab59f6f2c51b959b0cbb90d09015aaf26a141f38a633d9a90372f61221c1e16436f6eb33590012c5b9237f0a26306d69cb7c834c7bd00c0b9812e5b214746382254cad77c496c8e9dcc04a8d55a330477d9c5e3d9bdc65416d72668ad20980381b984daa35aafb6da723983d0b513bbfed01b15b45bb0cc55c72f8f5ee178e7ae3d9043d6ca1d42746368b75a580d84da8d1bab3582188c7460c4fa896c0d02b49e4b58f0a6ce40c624326236e4cdc33107abc1aaa253eab5e72f8123a9c8d11093ed17092976a036af557cde7a985fcdcf1726f227959998472fd7ad503f054b7e0f148c8f44932450db5a3c4aadd7d095f2d08ed1b80b945723230161e1a00554c57d3060716278f8c2e56f06e6fad50b8d5d13b9daf58d26cb559057a9a2a7a04e3533bb6dd85950447fdb0e552b68a75edfaee889a90b8685249feee1d23ddc5ed596c72e067c6ee57e128d8517985998dd567ef87873072043336118484d35cecbcdf51d983704b99b28586e46169a5b9130f4b93108438485e4007e6054ff09be9d5d790d275ac2077edb29858a528d227f2f4299ee603cd2065d2a01b8baa630066779bb1ef95489a7499f36abc0f009255fdece1914711374deb9bffa799521b809df1c8c61ffba791b8f51c12bd6d15f7ff89ab83720c023d1acc39bd121decf9d96e3fbe57de154b13a2b851b5de69d7e96ff650722b650aa90ee029808744aca6a37ea915734714fb64a4aa458970b83a6a62bc729b6cf43c6558d145d76a183e1a4cbbb57704d77ac7836aac02147f23efeb1772406d76da520a7f6f844e4588f27ab06d9fd0a88d650759419df173eb5319c169f4c8f183e0381bf7d6289b436b337fb4f8b14c757b78662407f90ac12b4fff725bf39fe10eb03e988f0ecad1a8268b4c97b1f52ce39dae204f243348e288fd720da75a43773a03788fbeb369f3e03d265f4a1f5147a81fc4e15baf737034741b36b989813039928596d80bc894c096d31c7a5e4b1f453ddec39c2e6afd660b6f23d5a5c376fec8d264524a6c23da662b1b276fc34d7d5cc899634d41ad2ae94313fa4705e69ead5ac73b5edf587806ea3db719c6465e363ec3e7ebb5175ed8729cc868a1c85f5070cd21c37e60ef5edaacadb30e2e5bd9eeca978041316336723054b163305588ce9baabe1aa2463c09d26d41cce67a4190c9aa0eebf7acf828f15ecaae7783a2d9f8c29c3dba361e2ed17852b71697346a7a2cbd1675893c506c306c06a90f6f33d8074336f76badf8ff9baf1e10f0936ffc88e91a4655867284eac1dc465ce5a79d5e1ed4a59be7f3424f05663818002f2256acb77da68110ef1e399517872a029363976ba5ef16c00dbe72c29f0edd57f7353b86e189c772da7fe6e17565823986147026a28ac0c38821a37400ed2638f015ffaa2d0a6f72ba6218a2911069774b2211d481b9e81d0534162ef63037fb2a01b6148fcc17226fd8a3c03462edd49023aba4fb45d84f05342fcd851a2d2025b1f5a13acd087237338f45d9dd2a49ed3f6f3fb2dcd0f5bce374815d64c1c5bc654bacab278423e6adf1d2877fc706d9a284f021f61fba19f6c9d4d77c3f812e5a5ad8e04e2719ca59fd00028a4ce911e368742bafe3e4e7f010bc3248f512e89f8bd074c9b572943e26c0c0ac2a434167a3154e536f62568699dbeb06bd2b9a674487e3b1e04f27459141a69dd0c8b421da2b10b7eed385550e9d741d1f0a204d77924f5170722aa059af7845829dab614b0229ce6f6673820f6e4e719772ba4cc43ca4d87e46d655c9394833d9a0e16aa58632bf54e57ff58e3f1bb49cd99925318eedb15072aa40098e7687091b2857d3b3414d696911563407b5db5cfed245fe6015d1261d9b52477d61578dceb1ece46193d85d431e45e07431b8685ca02fc73bfad43419c6414f8f78cd0c8a80a5e3a277c691f3334e25eb8606619fbccb9fdc9223ea5ab04af69624383437fc7cd1e83128250b3fb9e66343bf5b0c17f824ead5d55672078b90aeb6bd843da5e60ddfc316bf2838dda9e76f6808ad43ecd670d5054172018c32bed967f6b5a85159e5202f3fe6eb616e1702c47ada9300f2821095546aeb9385614d1df32a16bedcb532ff9e653637acf508055d3d30cc2794f6b28b421f48e36d85ecb3e05dd50852cfaa463528390a0a3e3e5b3fc7249ffb9eb13472c2a3a673d789a68fa7270e22f59e3fff86ab84a483a5b46aad0414d24871f272f90de28042748f4d755630ff5340bae85cff232c4e1195b96da5529f8576c87284417fbf5f063084ef00e546c0da86894494257e208372b55b41e76ebcf5b60183c06202926d5ec67d27ab3c2bebc32f0c3774796ee050ad93489a206338337216ae4c9762dcea1d69d8fb1e85964726a2548a08575ada22976342e7986ec172d58eebf521377fe2a9e6fdb1865c5255db7b27635f926eb876740e84e6b8d872b473f3cff15834421e90060e819a84b848da89e9a9a5d29ccbd3a0646f5f6d729332b5bff147c36c6c013e3efafb3a350cc78baeefdf8a495b53fc24c1abd6727444199b385513514d7d008aecf0444f5834b42aa35b7510c626028e78c8ed72298155fa48932ecc2fa5f374a5cb9c3791f0c4776886ae5cec4aed1bcdc6247222619cca9efe644c49c35c695644dc17feb380f58235f29c15bc4614eb79ac72927c2a8101cc4df835511ae90969b95bcfedfeca34577222dcce84e768af8c1347fd419aa9c9cc6d5738a0d703c6e4bfd4e898a45e4aaa2d1a3d5c77ff432e72b6e155d7ab6614bded019e4ae20dbdf53a96929ee6a1a8a8ae4fa94f810389723979faac305f10cf08d49e738703f45cf4c1d72f545b5e1deb984561fbfa59726988f4d7786b681a500ad1ef0cb284e587d023237a098d841100bc8c4e91aa72368b81e69abf4da41588726b844d82eceee18c76fd6deb5d55197f93eff169727027e59ddc59cd009aa1535046462382332c7325d479c1985f43134ac7424672cade9978d01799069a5c9c1e2e7f996afc949229e7ec7c84602f0a07c1169572b35bb2ee8fed84e4a4771a5efc18b71274de4fe3b9cf0cb234bec33850f31272e91b0acd65e5824efccd7dc990d7499279ef727eef32ef9a34323ad71bc3f0723061a2f85af3fa189791f2d49eaf672420f741b8434634bac99c5f5e26e4b772aa47e555b5a4e93825c1ab7427c437f340721e5d5742b2aea7e6be3cbb2e4c72705ce39c941f7c548441a9e5ef40ef6dbbf1dd60c402ab25206593704119977232ee3b29de2da35f9de09d6bde0561da1121ad351bd60cd27e5a641c7737e070234dc98494098359d5dced9ce37b8d2b3006f775868d2dab9f9b43f9d9a0eb0008708be4b7161e1de3be9f301942f0c2ddd0e17fcaa474a3ead581a61114821de6e8551a8b54a0e349ff757bd5d6ff6827c724608dae7268a5c3f03569a25f03745b6f6402794dfd86f959785647e59d1f00404f48240e64fdcd585cab49bb26f7b0b3735b32b853fa690768f46dae3ac535422d4ac2100f1883b9360458150a44a51957d63c9126c412e231f346fc5cee353814ff1be7026bea760f415b225b0d110ec19639a61df571d1fbc3dfb4f7ef4d38848993e5a0d4c9d1996b6724722f4712ea10c1e2c18fd488751ab4847dbddfafcac007c2e57308694c1a568372bc497739f391efc0752a93d26b4efbc83ef7db45926bab253289adfdbf02b472b97fdb5a07559ea5ad4ee61f8c6cd9ec8b436e93bdf9b6e017cd2557168d0b02af7ecb44145068fafb8ab0619236491e78a7408b1c8f18d7bc7cb89e0b461937a0af30ab4b9da7fee2f0b6e4340593fdbc919d494c4a0621c79fab6acfddc7721cb3ebf980cdfa34ec562f147c25ad7906e0582a9a39020c51499c35ecd8d67258314c11887663965fac25b5399aab8f46e2dbcddc5326e6640bcc80bc4f4f72031e19bea7d8eb7a2b22e5668c05752125234a46cf0b437bdc3e6fab425fdf6c5bd00453f273db1fafff1b3b29e0df6beb6b78d468dff4848dabedca0c9f0d311022a9b87e4c826a251ac1d397ef627e3e75393395814b9abd90f7643ba62872cd199ae4963816541b5817cf43a9bb58d107af5141ecfdfb02f6f3a4b8d7542f3698f33230449ae6d365f2132dae2e80ec27fb248f236a7725735f7b89d87f72efa4a5d9e6fc4768c46092eae65149b03162a2c711d88f57d69d66100b257951b582001e68574962636d346004b3f00db1157f9795046091c273561df8dd14050f3d988dcb401dd9266336504f5fe96411b3e1a6b723bee3a5a7d1a1d755c872b2d8a155e96c004e6cd12264d8e6800811da817b4a8d1de8f020bfb6333bf4133ace3b94d438378afb14a1c2f81fa7f1c52d5141094f1db043f118445e2cd24b8dff12ed90659e4b4a1a157c9efad0e9dfb76a8d3fd307eab6a7ca03507a4072228cca7029afbdc26283a5f21b0e84db440c299d5554517009b49bf4facf4f72a9338f19f24b94cf47f5c200257e8976a08ff09b4cfa26923367f329f06ab37272ae39414995f91aaaf22e6ece9c18f08d074b6f2bd4ea489da115937cdd401b5636496d0e9d33b35d24bd4967f74d892690ffbf1609cb44214589ed847d0f278e206cd1b32e4bdb47737bcd22c84c9200d3c431939da6a958732b88550db472a523d566a05de9393ff5ae2e9af326ad5843922413157f3a09650bb800fa7633f42cbc919bfaf0b9206b43a16fe1979c2c29a751edaef0b80e132a276917c772248ea39a53ff85fc80d5f6d6ffd18ba989f4b04d560383c48d1ac9aee5229071e97997ed9d3a32325c38c84266844a83bde0a51fdd1a33c8581dd8d16307a95255edf91f93c5b531b5f684daf49687956e98d9bf99d9db3d8df61375f72fbc7213834fa012ee6f8fbf083a80b82b3827ea2887107b270fa928d96be7bf25cc7244b63bccba494191f8fb3753be61834514fef8be998489e0cfcd56bd109be80925e614bba1b8a466eedd50bb87424c242d63c88f846941448eb3136e0547e03f7f8d4b3657b626bd658eb19c6e67e0c2fcef226b408ecb4255914887f1e05872f0c28a0d7b3d86c6101e6c1000574e76f1bf2418bd3e5f0a6cd6f7ed221af172ae36eb35959bc4773675b1adea5753584aa3f8f41e237971aed7c4c81c28032d1b35dfad277333fa27ead32075ec621e81ad1a70b6bab486675aa8e47bc6f372bed583f77946860fea71bd96b2be93a45ec0b79c9f44769c583f4777343ff442f2ea0fddfaa5920ed78338faaba5a5c566903268ca9c0770dc1cee52f4076c7267913f4248e6469f1f75cca6587b9173a3bcfe2f6ca9174f303edf3ccd9b55720c3d14a185dbf92dec61957146d034b4799f85f146a65ce4e0fd2ddc5065da727f1e127bdd1d355e4115bc30142f669fa57a902ce014399bb378fe3d95afb472a374bbec0eff6f6913cba2c652b1459a32bf8a1b830a9cb45aabc291b6364c72e13e6e3d66467777a37bb6bc49d8104585207ccc775c4dfdeae1416176a7fb72f5fc9fd14132e734c65ac3f005353a227cb8196b6261287bb399647384e3a67202fc262bf3f6425ebd1d68bb4f14ae281f36d2094f1b42c1340cfa7853ec4172f790259084479746275ceb69c5ec0326a4c885695d4e71d8f66634e0a963f070eb6c7455d0dab2fd2ea3c99e6cb962559ce4d14eaea87775eec3c782fab80372f89d058adbf36a511e28d7f4ae5ede6b939a3d8bf32e45c7862cdb04b1be365d8ece1d87eb48e3816bad503e94824da5e3ce898cebca4f90da9a7477d30570727f0d06f9d9aceacd910e2035c45fea0fc0fa9c67ab936bd65f03e3fead0c4572bfb5de5065e691ba3801b4d1d6bade615705a1f4b812582166a57ab214f81a724ce91c415b1353068777765b337cc2069692fdcf6dfc42f0cef05430bf5daa070a6d88bd6ed1c1c9b8161849ab9d27b8c5fd08b88760a6619c88e7061b9de772325564b02dbfd0242a95823b6f873a71390dad3d8bcb34c6985ecc69dfb2cc72a9a7f21863910326e594e0ceffae3c0d7f450a7ba5ae5c777dcc23f058a15172101fd504dc5f211eec02c9075113c99a47f5334384f6c401e8dd0c9327841772c37f7b0b8f0d2d07dd5587a061db8ffc58e2709a26b78b9e3ddd3bfcac1c08720c125df722704745634c9ba136e47451267c89f0092bdd470434a6fbd295d12cedd6e5c6358e9c533e954dd5385116f9bff4aa2a88c01c5acb622d4b613390017dc4c38b379c255a831d0d7e284cbe70733382d1598eebcfb7a11c0e5d1af953f0f70be2516588e2e23219e6ae3ec2808592287bf6b59c77e860a9ba7cc9d6728f97d9768f28d1401626977dc0f3cf3b8dcf2f2fd62b3e30f362c10d46396403ac5683ee46ae9dc4a6b874476a79922e0d983fe02a3ea567ba0c68bcf5b42224cdc1a1d727c145130af22e6bbc34a1739a755facb0c3f9815b415d1a8d6c0e68b76f12695786eea6dcaf1ef7f349d5b7fe5be580111e575761998e33107d5072c1f25a17149c140599158f125bf550ce0c7ede42f384cd6809c9209f6d6f9872c95421eb82c08c223fe543d94bc432dbb2116d7688543ad83b53b288c5528862fcf974faedb3e3feeb76b24a7a1cc5322c33265d3a34d50d26561bc15c6c98720afa3ddf9408127e0cbd0e25da486a8327cccde72518a5d4b52801893459b272f2eeeb726e0c5689ad067c832e11d61d683ec5ecd97870b9e2b4ed95b3d4a93e14591d35f14cd43be1acb02ba016b0d87e8f7640249bc745b8b87e2fd8016426060d8384ef6b7769a7ae39b0cbf5f8f94153c8ed8263165f5b1b681abc077672c20bfde486676f0c27f23cab43531543a3f402b230579b476dfce885d0076a72829b914ef67c35ea5eab4c792adff07c6040062d65608981b054cfab167183720465b45c24ce17625dda666f2c46815d37622a44ecc079cb510951370f75ad729673ae45ca4ebe16af83ddcdab5bc37af3b47044a29f60d1be8289435d93fa10b437df2b6fab4730d0b03cd42cc9bf977dc8f8e083941202bc679f599ef38772b886448a4e0e4364c358c74b0a4312ab74430f3344424b3d518adb7b4ee1bf3c8a840d66d4fb2f72c31227d69a1e3d36dd5e616ffad2bb7d45cafc8d46340a6cb0ccc97b435429376dd8078fd575406a2978e042b672c0e1cea60893de0b3472f02d1cb6bc18b50a7b9b9e14608691dc95c5c7c85f97e419b85c00137a34cd24967ff6852e67f219fad7f36940463df92bd7966a5d4c37a120b2239d644ff7320fe2cffa144f4d1a42969857917c0eac1899175c321f297855ed0f6959d69d72424674ab0f636971f3290ed7ce500ca05368b3a0085990e81ab6aff22fd2907281ca58cba18cd647c0a2c63738d2cada19d88d4ea570a9732a8254e208ce1a723d85c5a0629bcfccc86ffcbab47aacdca5a62036de7ea07c561dea6f0c21df728934ad24b2f0f6cd3092dc401c6f3a60eb381422862114c92d65c84862c00b17f15eb5a7561dd7129096f986ab0075504547d3a2fd1fefed8797a9235110b45ca0e1a6f5b0e7356110f313b867f2c8e060a03f6cf7b2d7ff5ca2b566863dbd052cebd1d8a2824c501083db1d6224505ba9c1fb1105fdba68bea56dd6f4c72b7288cfe1b3f4590cbf1ed0164cadb41167df964a0e0e68e1d7073c0049e9508a72ead40b76e99ebba9b75a9de12bd00c9828e4f6241f30a5fe0dc8733b3120212c15f70218d6d9a4bf3acf8e21ae7a94d4684027369c1067efeeea9cd88c95f355b439eee9572078f490ed1c9cb13650f79bcd561666f0c06ede3ed40a69cb68726a28cde0b2f77451777885775f1af727f27bdcaf3f792f5c489724690294f947590e76359c5649437e4e5d70766e148ec0aced2f1e2a99f6f02da93a83caad729e09eeeaf1f0168eedb0405183a8d83324b291e6b1420425e5588e81c38018379bcd18e9912280bf5946d3e02e9e37feec6cc2f30a5b763959a32c3466e73472027122cc2e8315d5f1ee0407765f3c570c9c70d2f0cf7274a44cee9122e8ec7224e169e853f7a3b86df011876f55192a22f103bc34ae1aff82f6cb0f85d0eb4ff868dd0468402c860d0bb0513a7abedcfa70a3a97a2c636a65878aa26cf6bb7272addd13d90e41498be96b008941cb047cbc2f6d98e896bff3a601e10e05505d8bab2bd9731be7d99fe20a676955ebcad41c6d0fbc491cf8f191bcca6d646e72b8c126aad330790605e307a3987da916377337b169b2994e22157b51f4a7e972eaefc673300afd130ba862a0f6e635f7b95de5941bcff865aa8d7458d4b4e82f89d753c3e0f8e6c4a556cf26cc2c9efc0b200524672b1cd2f20e0bd35e116c72a57246941ec322e275eb4e9c59896aa015dec33262d9b9fa0a8f22344bc98b26edf255dc10600477130455b027777e1d4ce3902f1a1763195a27e770214c961e47b6940f380e997599663c8aa71044ee90d15a1d5dab84a05ad63dde6924c772c3294d41111751ed765db6cee9cff74584a9ff2ebd09c2a1addde9dd36d8847263caaed15b7ef3974125cb0b0a3982c60d75173756663a84ace4dbc2afbffb722f3763ee957fd974ec3d4d152272a606c105fd0af2264b5e369e6f1d8102ed1c0d32da96cff03f8afb8cda617f881b16af3f04c420a3f399dced95f955017972753b3143477e16571d38cb43b95a22635b5d7ec6a8c3fb32b806dddd2ff11c71e9a6b9303461da3e7b0fd61772afe0352b0fe241322817670e274d467ee12b72bd0332be66c7783b13af3c0af6d123112759a8519834aaad1084b80888478d1dd0dcffb1e478481d7716f4f35c3ed27684114784aee259cd8558cbddf3c0c102dfa6823e2f465200bddabe3725beb6110410adebab2a2cb0afb21746968b372ceab5dd99e9454578ec75d87b9a929887ddc9c6089653e444ea4b6e74550e161d14dc7f133855b286f5dcea2ad7041e3783bf0a8f9dfd3ec08b32ba156fe4010102d7926a02c59b727f1f0a3f38237f4df6bf32d89997a6a3c4d70d78d9b2607248e92579dfdc664cb0ccee0e62b3afe33ca61c2179a0f17ffa4fbb71646b6f72eeb6b863045ca8a16864b1fbd2055261d0d4c1246b3d5b2bd1fe76a75ab02b0a9d81adb2d6a426a84b9e1b33a9f05e540ac0743d1daab7c4bde0867ab20fc672ea332c3414c4bd04717c84ce7179530298c5573b97453e53225731b90aa1af1eec14fdb7052e1a6edfd60143d4ab0c1e068ac615659754e975f8af784dbd5972dd70d4c29ce8829fc0c01b77b57a85c14f973ed4f39e7f133d37b3b21d3a3a2ce558efae988a148a4bd0a5c5ff9c2656092907ffe37c9141f6b2ac2cff82cf4d141ee5b8297f63aa92e74775382aea9951f274766066c3f058dbb7ba07e9d84dc4db8df11b21fe34beafc4c9b6895506d6fdc758c0c3787316af87d00097d004d3ad181621ac6cbc795714c3000fcabf8edad18f7cf42a4fa01926ea82e1961a6b89d63aea9082c51052c798eeeba67ff1d2c144caa09d9ebb25385002058b722563f2882b0c79830d019ffff61b846b00b4852ed523948d40a37337023fc21895e4720f734a74c77f9c4bc1d8602541be72b03d60e089d32df713d04638cb4a21e6e293b1b8586bec0acc32821202b71830b2c1450089c4bc4756bf62bba172566c27ec1f29f0be2fa1609a53d81f0c1ad2590eb1a80aeff6b29240e458c360bb8534320d500d2d40056af3120c95f2b611cccda746111a5ba29be9a9bae24115b9a122ee35818418cae5d919e59bee3b62a26a7af7a478aefec9e85759507219a89c4ec4e273cfa9012b00e47b260745d3139e473e9b99d5c24703625c804cc5899e3d9ac332c09ece93a9af12cca618e09900e5915d4cdfed2d8aee49877293db358fbe9eb03058818da56fe0dff6582f6e2d13e48073184e0328f43ce072941a36afb212a3210632bf81b18eef12b78db00c2655cd0b140348c95fb1cb726976d40f0b06f5a37e46cd0ab9c9eef62bc85c88ef6bd35bb835f66835907d7278033ddaf6307e697b9d79a1ee379b249cc04dfcd9f5072b0f0df073c880042bcc123d3b9e6f7a1e98ffd3202d0993f9cbaadf5744af8002add14489ffe0f67295abcb2f73617c794644923022ff02b2e7d40ca7807d2815234472aaac65aa723c0033d6bc4035c32c2f0417f7fe94ce73dab6c03e2b829be6f317895e763f72591981782fe58d7a840d5cd9fd07bd5a043df3e89a1c51897725d1751044bf72da5e786aa12fa4a64e4ff68155d2de89bd103a28aa204140d70500721d78a872d423817a9850475e139a847644d527a52ed85bab38618b817882f9e0bc3ac272a378fdb28150de423c277b29a464c9da99957929fc6ef0742e5d7f47002e85577eac91e9a5411b9e737a332ca02542168bcae036fc8f7091a5d34f10eb42af72834e53687cce87855322f76c58073c054f67ff78b0d8d68d51dbddd0fda08672e147e1082c9ff413ddc98300acfebd64cce2651b1690b357ec5a64b76d42500d9cb2bb51809fb99ef5f2c70ae91bb3b69dac2cf6cff6a9c49cc53b09f838e872bf4f0897b6d512109d90989b76ba1b29ed5b6b107d9af4cfc440f7294be5dc72423c2ae5614e6554c9307e37e397a43e13b2e4dc6bd42f6b5f9daae1df2599722c91e84ce31764d1a7c04474e3d10a2782bbac14855c9ce54176b25e6dfea2247f2d81227b5fac31f4258b2bc29314fa5c5bff2b5aed10b076b464ac7dee2004a7408b021aed8da70ad73c22ed20b5e4a7f99e7cd964b1af6faf127dc0473f72fa3a6a046398d450008e7a9e9731c7dd215460d0d1bee8f90a70e60deea267567bec02fb1f8d4f776fd2a295209d1f933f6caf83c5c2f6a0974d01beb40a0739c79885949726f1dc23b1f9e0ef43df286029be58d6526d1c6961fec4a108165e0ac2659f94ae635f58a3757d6018ba0a2058f3e7916127ca5d2c43eb483d0a72f48f6b96c3fa6cc2f9dac7159c54166de4639541046aa5ea89f3965ff86cc172aabbf661718bee4aa219814c5386a03ab11ea9acd2d85c294c590a83717bec72910c21880bf21ea401e45d39497b8ff12aa9d030449981d5e481848868cd9e436b7abd5176f94393b8546b69debfb98b43c520e2458ffb52724d51c649e4c30c18b649691aaf525a6eca9b48820ff3967ac41703a4afa10caea575189dbac24b237ae0a1a40234ee8bc83b16c1faf20198b1ca67fda107e15b9874c648e4784958d8bc8b4e06c723e70b6b87453334245e3d8a892aa32f5b95d0b869246e15440ca24427a7c09c3eb0c4cc69814a140924a1e6cf991cda4c54c3759a2671d732412acadb80367fa61593866652ace516aa92eec0843cd14f78b9f76c9e4a48352dbfeb0348dac1ccc73b5a850da1f150d1d448ccf136cff17a66360a4c0e9e702c31893169c3c1b06ade7ac778e50598a6d093aa4f22a73cc9b5ebb209b7bc72082ccd757fe8bb703ab72aceaea1a5dccd673add1fe8abd78c9e7ce48e552e72e81bdf2a1f4a790caedb9e762e6998804e9404e8feb30e488d1b7e625bde18725c906484658ccc08855b112f8e57b5ad8acdcd14ba89d392276923cf90d7d0056a33fa5cb1936536a173579a01d1b967a8cf90efbda5244691cd07c0255cc872e1c66a90400bc38661f9786af312f4f5649f6e510aaaf4092f489e06a02d3672688dbb0a9d7416efff9d15439a94c1d3f4d494a3a1f060fa49b4bab5d47c9a3a0db5d10fcc08940382598555a0b908bcd4bb07c48aa17c7629f0e05bd4a79c1564143f6626a012fba5fa62903f9cc9e40d8e2187bbf50ab0b5dd92ccd3a10f6b1a8ada8079de76facd1feefa402571b3e2ccf1ea22a1e5ffe4d45c336bcb5872b02774ec570748b37638a1da3c5664ae740621eaf87c4371547bd692d971fa70fc4e01eb60fc962c32283fb41012080b3d54d2a26e0d0d4a206bb2020ad43f0c62c4e5efc847fdfc9c3eb8f9a963f7373519508dff10243d02ad06a870b846644c95f660e354101933efeb2b2c85e233682e6a0d0c8ee6798b26b5a39b040172692c175608a0cb594973ecd93651605c47eb9d8d0f2050464ddfc825c77df0724fcf12225bd09a80afe31dc55f69f7a9e0cc568db71096f0aae1b2d9f838f61077d9843cfeae84450a2d26caf8cd08a7d23afac88e33acbb3a68357c7de2d5030aa0ef509643fe7d957199ef5159e81aea68a53864d5676748ceb8df9f4ab208f4b9075fd161978362c6bab3af91cea67282062e1b75bd1497c664b111d4ad726768045275816b4a4d5c286988c6c8fb31e167b894eade932231f9b0e9321272c0105b4126432410735d936d9247beeb540881e2f93c8971b51302399bc35472b73bbf430e4ebf641158cde7b074475e62e7dd9c927f04ee9da017d7327247630c41bd0775d6a4dd6f23c0897a7f89221a79be9abeba175057d5b8938e395a7287b627237d9ca01a3e0a1c07cdd219174189f0d36e1b0f7d4e7b5376be8162704db06fcb586b77c8a425587c7d4a25b661a15a05af683bbce2c65b646f700e535b1692faeeef6acaef5adeed0a06a451de7fe9b54aba6331e0a36223ed4e2d55be62e7c1b924581daaa8f550bafb73eb161e828aad2276e0d40acb4d69840d7250ab2968312d651df912570fdc27d51b1ca28829c3c337124c7018ded935a045c6d81dc6168b8252f815dd343eda70f07381faec90e8e9d4420a24a48c4e6a72f354023041e7ecedece96e83c7e6a46070e0d9b59bbfcb07604fb66f3263eb17f6c4a2da7e1fa219bdb35415036b2d42f01ef514e8af1a8a3dfecf934462eb725302644c95dced2bb0b272bc30ba40c1eebedf433235ebffa9052395c77a716dc325226c285427d978a703b7a8102d80612d2f7a3050c48fa208c76c6db1ea1a1a1bc9f844c05aa3192d496d908d62b817cf035c4c7040d9c6ba775fe2f87672067a521126206e4d0bcfcbb42c237ac732ce4e1786b2a98a95ad17aedac56c72c03ef06cb8f9c2b02b10563860ce0dc3f4447e81b5e523f7b566147cf9733a49dbc5ea65bb12567e2bf9cb8c9752b2424ae37d35cbe300789279c12d9180ce72c932c6c9836c13d541f0e934f976e17f912288253d2a819d4690b526cdc85e6518ea4331efb44f7915c187b8cbcbd1fcc9505c9413ee8aa1767e886cdd0053722c589fd5c945a16b93839e76981989f67ee61dda348f9c3c8eaeba1139cf857257114025fde00963df57e1cc032e653f89d70abd0eb3581ef8c0c8287f784772be3b01321089f787a738d3ec302a026b98b14b9098531bf8b1fd4933ad08bb727e7290729ad35473d3227383b755ce7f97c206661b31fdab2a1a59317326a772e0a8ab0127817f454d2a45461d0bca64d8e8e01275e97a3faf3f52ea01cf467290f27ef6a3f5060afc60b27a97ca32d608037b02235ce3278f677b5238087d630d6bcf7c7dfe3d3a6a73e7d0f85773ada201fb096cf00315e66a3d2191725d5114680cc97de1cdea89ff8d01df066d44c88d6766be31f3904eac08fe79de40541a4e4a418626d51a9bb1fea91dec9b59aac9ffe93e1e6885f2e568f4f7e4072d074b6b0bc1fe491350cf8aefa79a505eaaf580a4410a24de3d1975fd0f7b882708e5b41916a55f96c5c9a178ce1478f90e28287d123a8c5d8ee7562317018e0e8e6dfe69b68705a9d6d10f9004c1b9a589f39dd4a78c033f7f03db8b99ffca72731722bff6603f720107de4a42c5bc1e9f0ca8f0e3261e3e5772a23ca927d572eee172f6e6e4a3e3c18fe346ed1a881a72a60e89a4561b29757700f02c8279633870e3038bbfb055d33f3523e71182a332c7060e6b28f46fd9b51e6516a12d729c77502c81eaa490f6d2117b3380662f24d3e45e0466c876e379917953e43972e0344a7dcf7cb10fe32d3e9e6ef921e4ee77e64dadb44e4de9289cf1fba87a72bfbb9d043fe4b249fd309b3b9deb8ac665d083f70c29b40dee3a52e2fdd5e772b49cdbb18c1f99d6086ce2fff6f2345505292ab96fa433adaa9a09a89717b34452d7d362fedfb9ec800f2048bd157164edb2ea8c575b4d88085156b9954578163307e76c502cbe64b3334be058e456613d7c0625c42b29631f21ae5cf24dcc72ce3f1800771d2f3eb9b0785c2f795a7df11f4e91ea9156fbc6776d00248878726ae93dfae83a40cfc9841c16c11f9f7377a79673a7fb13f6eea2740c559e01721615d3305b824f537125b60d6dceaab5f0a085b4d660e462b8eaeb914191e172cd6145efc390e239adf80ec31fd54b33e052cdb98a3f8df6d71c20b342ff4c72d3e687be3e7ec0a14fd2a4ab1fe14884d60375e685ae1107507ca9f51dba7c72446a260304f841c3d286c0370d6f94210c3be89280d7d8e320dad0ae58176d72cb7348b65d474ac18512df5d3b5e14c1b0ec34ec574436c1a0f15649c5e62a7210fbc1c66400779d58708b6ae82ee9c53f85ece612fd6e85bb374d9193b3f0727210501d57a173d6b5edea1fd3bf138454889de181289c6832591e77558b8d0c7ca45769323ba1c78788bd81857805d4ad3f3d41a3b10fb57193a814c4a52e30016ef29e40042f27480fd60042178b86219c327199c81151ef8da78aa7cd5372620ea8b8526d0359b01a56d3b3495b1bb2fdab0934d4468eb7ab987e9e211b72f5d13bbee6a451ff24a522511babdf93fe63aee38d8f7d2a0f981b716bc7dd6c30f532f63f921dcd76b5889a18ccd6e63c8491cf2e87dff903a0d94177bb5a6aa2bdcdb4aa9e14c521bf49933cf4a1a6978400b081e84466a68852567065ae3aa10fd76507389c64b9c946679e63967575b7741dc512f675345cb1533bcea00639a0bed63c6df171d956b5ea4bdc55e1f82c18ad09e376e82950ee7972823354f2a09cd83657f643dbaa6bad3f224a2efd67bf1e9ebaeab04498ddf2384135722fb6b21f0ea3c8fe59f75f69afc74be552853fab5d009bb82da1d9e8b7952d726fd87b2d4414c3eda1297a84d90235d73f63ddd150331f1a689561d0be0e431239d026320c2655a95e74b2f546c0822afd005a2a6d93d91594d4229d638d4d72b8c5ea3c4ff527c1b8a6508147cf3e2ccee072126276e87c5a333f88fe80c11d5988093f0153899ebe8d953f04e39ff42d72ece7dcf7e69f684b591da0d8a64a3bd2de9a828057f59b61464a985b86d20d5ea16da6b83b705459252a94ae072669aadfb7e27b0ba951e1054afd07599e193b95ba8fe64fa31ad32e35157e9f67009f695973259bec7e80be3ac119df366cb17e15e168faf9057842fea1b1a87226726db27a38e52779f027df9c2e6cfbb8c6eb50e55e8401a0faaf15209ad162ff3eae8e43f5b117fafaecc9b1e9db14214e3e8aa7bd9decc3c58bdb312a11170ec34503685907e4d64696ededf3ee96a2193b92fccf882ca3c5db6dc5c2ac0eb8796be688bd91349f5abe23ec01dc2c4d82b96c22e2584375501dc5210de2720a5c2e2b08889aa8ce928262f48114bdc32d66cb3c37b47dedd0bd601f877572706dfb38fdc8100c84db1037ed5fd008fbffe06231b7301c98d0a8a3055b727265db9db20fecbda0c424042423414669671099f01af76a5b674c3b540a8be8720aef7d349c3f7ac9420b6361beb0863115d31903e1de5e41ca42d2ddc7d16372afd6a88d1c6a000a4a3f4c4013c3884ba4b65775cb26228369476bc837fcd53a7ba0c9fdef51fc3a0100a749fd076445ace13a8b593c9b1e58e3206c801ff50ceb40f9f3e260a0e750434eaed44ff5b63f8b39d2ee55834f61459204f2ff3f060280a3df3c3dbcd2765e80080f4f4c42787830a4b7f0b7c61cc3b2f6f7808772c55468f210b2bbb958833eb00d191582ad5e27b3ea7e9e3415c1dcea473cf07218261141eb6450ab36a05f1c3dabb30be3b1060499b3d6b5347e87bd3fb9c372fd1df0c39554629baf526882e481f24c5d99792fe64131e3842456a8e2df2f72a1e7f26723903ba950c70b01bd2831d7642b17c07c8d23daa045c086d2d00e7245fd75bd8dea6a0e28c3393bd7dda9243a47e30378f90272407665860d53f572268647b75a6c699851caf7c3717757d8d481b7c0cece032d8550066cc108fe22abe9a0433b0fce9355c21f13b912765296cc1092c5afeab171659e699b932a30d8d57aebde541a1908557c38bd4dc0ff8da69d684caee16a31e4080cd400130cd40a4941940464a092864575b6fc9fd0f6e8feb98a19a01b9971892c1aa04837008828771b4d5f09501398af82ae7d803b1316c54046df28b51b9973762d0d725fd3f52d0a2cbf76ab43f6d4aaf9af230abd45d87fca9fbfcefb6aa7e01e15507dd4fd371bd52028bfe6cb1880feac5cdb94492556aa95e89240fe4a7d4cad7282753596a8fb6a9e15856fbd82c66060b802431cb90d063aa410745634a595727c725888536c85e64ebcab4c19eafdab268d8c4cca37a89afc8f972e35eb6e67bd9b0b986def5b4b05a25bd6653e4bae438c83c280715b26e638e64475573e7272541e13befbe8e92d064b1ece634baf85d3f9c865416794f7388f71e0e9267235003bf17d07831556f905d7804fb0aa196a7f077c52624e464470a2636a117282d90bd3a77b19723335da5cc9c23fe574e13f16e1ad851490436710a98f9c724882f327e96df17aaa3bc8317c88838d664fc2cf0a0f9897b46e82923b29f972d48ffd91c5a0b787744b50f67d6b4b0c8cd9e0be888de54241e2d4a7d21066094649643fbf848571b4e810c7cbc5d245b1479a1818d5ae093fba2484b4d869729fbd6844369b3a06daf46a3c300fbe2d60e39cbef33d8eb229265b5ef5e89f22d37a9defead8bac70265f7e3c65c741905b0294f23cd52e96394389e4cb663536ce4c470b3f5db284c98e21838557d55c9ebf88c14a35e6ed670a4f388065a3a819d3b4598f399ee067aec48095285eda496aaa4070f4ae6aaa65fffcd4d1772c8d2adcf879b2f059f52ab1e09254da3c0bc17af4fe52e5b69c80fa8aea5c249afe632db6273751aade367101bf8e16acd5924748a987f07805fe783d3e5c672a4afa425beb5dbe60c79b2a87f4abf69c81a219b93d0328619d1ba422afd8f0a277bae39a45436ea13c9112ceaf5b48dab7b4b88d6227dedc26f4715508b195e3aec632de27f49b2aafe15c37fbac1c171c329f38bce0edc275b0776dd9a3a380a4c301d72875f75b76f9903d0e982a9d8cc00467b5bb8cdbcf1bdb3136a7241e354ebc35e832ac9b7603ff58a6c231c5565e7cc1daf45b4982d80f9e8f72d723a8b03b7b2af3f9d37c4372c7fb3253b2fe9690e0d8e7136b244daf366e03b725084824b05d7c05b7710d725b298ce4e994435bde3a433267daf359e9cad0f06422e575f8db2c38f937c59a305a62353783967bf9e39cf8ed85a7ba5abefb3724d09e9f47c3990e09b782f9a5759e031e6753cbb1fcefc30a13893f3e966b06ed7c08952ef71c493cb89d0620aeff6ea51edf897a6744908380334ff98f474724b8ab20e142ad939b780e440fd78ebb2ee5335a89359d29aa7fd9b0c4ae24a72670f122e366abb7eb0d34245f618b95ab3806936600c8e7d2cfb537c9b87e2725522d826f10a9d611ae9386c6fa368843a075d4c8ab75f510a406a4e65037702705a27c31abe9d08baf0a27b19632b05f663c32c7c85cb856f2da086bfaf7c48cc031854e4e9a4cfe87badae80c03c2e8be3e9e3617a8f746b962ba7f4572872904422680b4e0311603335001fc951a59829e90ea83b973a77fca050f324b2727139bfbbc7ae59a4c0305dc5fb1aef02e6351afb10290c6099184cd34cc7607289d75a9bad5bd7df5049f47c7906367d52ccae2258b1cdf9031b9757e0512c72f3f8e15f046fcb03877d5622745b156c1bd0433366b46e5824588ef823013e72df5df52d28211cb29dd7aca6e7ababedd2f0d4253662e8095683354f20445772d34448af5d6056c6eb8a17291d363453d3779633bf7acd5ef7772caa24a2e30b349d02465bbae1d4c68425d22599fb50fb5017f85caef3ec68f8ae40dd9c7372dc2878040bfca58b1977fbf86258fa5172ee0b39375c44defbf56984ae3bef2ce8e9d2eec850225993ce050708fdcb78786b3611d4422a15afdf5fc19c543924684c21b95f2a27c2aa6eeca00742d330e5f256bdcc3325edf78d63a64e65097249f9cc6bf169b41d581e3ab37f5d2f617eac16a14bba980e33a4823c188726726979167edd8a41ddafcb2b72ac984b2f24b923b2352f06423acfc938f12c2630b12edb210c83797455cab2a6230aafa8a5f255aaddd3751d730bb3ef7a95547231411943e6725b666799450017ca93f472b8f6edbd8d89207f061f8734304872c6d91bb5806c0094a193dc6e0666a15cedf3a80c40c98885d907a46370beb172fd6e06868e6d9e2b631c32325881f80ef2d891dd2c2a2161f14325cf9480e5475655e63aa8f0d8624e9826adf690f2c1fe877007cda39c468d6b57f13357dc72124e74399271f7c64b94aa175f2f6ef18579af2ea0f3be67c381135cc78e502b49ada8f06201cdb0dd70657be9eef40abc2a4243f9b12ea24e07db8dfba41972d4862afef64650f40614a084c51d904e0b4a084209af60aa9d2668f44c3766723341a87b694317b0a8dd03c24fc169c53aa4e21d5a6f68a6e31f444dc6a1037259678b057864c5331ae69bb91f79021b98458334cc89f72d0c83d3b12a67e61b50da2748da6e62e24f5d896b69193353235be86c2f20f736d1b305b0c4c06072eea388e003919c3044031348f7878af15c3edbef8b0899ae27784550354ca60bc8b6897da25cdaeafc0e402982b3376ea72338269c10c4c2a1a7e14a13ecff064470a64f6a93c7bcd69514fc767e2e9987c0203dfae162f5e6d3ebcdf83ca072bf836039951a102b2db942b2a2bdde433dd9c17e875fc99d767e28e5eea19b4820743d1703becd43f6d319bd2605271e5642b8462ee202c2f732fe192e25b872a262dc0b9d27213f5a54841dd15e31517760d3b149084a1f4c0bf9dc74598c72339e0a4420d319a06b753e1d78e99647ab17f9c3f782430e5626fdb3f745ee22c3a20c339d87f22eb70074902a3e273e9da501ae1fe79a5f743f51e6f273e1726ce0349ae9c5e13838cab7b1713470e402a365c9cb646343197997b74b43577269d954833898b1c8e1f644894376e41ed2a525f57dbd624e1054af18b929130e147c8ca35e646f08a8e9c7347d6a910b4eac37c5dfe3c42452a7ade2258d8c38adf8c97a0322336946dfc246d19cd80141bf2fe77356d384b67cd28e8996f2003bc4a56ac83045ab24c62fb16f6d78ad490fa4ee0d715418a427779c5b203b7251ce312a1184cd90d42713773efd883cd82609f369d5c77fa6b4a9953acebb2360ac13296f9eb3c6e42edbe0cbffc7d56b6d25506d457012b1751c0f7899d44d2511893ec8397d153cdff2f300b49e64699600fd63017af9daf7bd409eb5e3726f14a83e0c1772a581659df2cb7e2ecbe2f693ebf63dc19de881532513d02d209bf6633566efbf985f25c8a9757023674d3f9849e84dab43535480a7d767c572cf2a066e3a95faf4adf4e1bb94a7bd2b3ba242eb55db2341a5deb9e838a6a7725cf0b27652a27d87a69fd1995e2e0d2a3bec2363a690974e8f55fd1ec3190772468f34a32639074acf0c81d72abdd8568a802bac6dd680dbfe5e68ab79f40111fcfc6fa11820183384c303d0731096dc6e9f909790b91c5a4b14d1166041e972e1bce14afbecd4ac09e7558b0fd6fb9f56f883583a02fa34ca645ac1ea445c721ae2edd58457d15031f39946b25b1682b4c3236d9b8a885f6639c2d0a17722722d7ba80a48cca2eb9d9e0b944c3fc7b6319f3a307c7a6023b9375fbec4819a72cf2ad1f3af16043d23073bc554e5bef4b28aa2973a2d26413a865797f5214d729f1fd1706a9cf309b581f8206001a2e6aa1b79ca26a7f02872ed3aa2bb42cb1a1fa8d0f6704d63a586207081915aeb5ba33efa0bbbd033048cc7129bac9731189e05d81dcd846681c53d5add1e9d369c24551c82d6f0d387314b67402b2cf63e9ed99b9f47d270322b794f6dffb9b85817f49d063a24e263d43d5ba1a2af2472566b36668bf7e7cd503daf9dd83f77fef7078411cf75bb83a6be5c729b84727224903b01fd66d8f1f77b58ea6ad0ba6d7656e388b5e2ad8155487e62d98d7b4dfa8e4f99c12353ca5167d53e2ed3683abcc9eb5bd8bcbc0c2f409ea5c038c972fe7e8106da29a5141a178c5db4925835a030959a90e557194ff57f3252062772b9db067e161304d314dc5ce10a702269b7b3aa792557e11bf5fc73bdb081b972fea88b2ac823b77366b205fa11ec24b17dc75fab207cc26c8256b6fd6ba88f16b8fdf1fa4579d524fbb72e4028f0f12fd2f871841b4b84664d0cb3357c0f4c3dbf6a93d8e842f5dd994d89113345f344e3aa4b484b8bd1771b0b57d79eb1c036ec87f2cd14587556a626dc958ccedd95d723a30f7c61888b59eb4bb97079d467b968e357639ed9b1ea097cf21ea4db63ea77df8a4e824045d71460e8a5e11272c5cafa7c0ef55bd6898e82685f1a9ffe7958ab95e2f5577094375d86d83ec36c77a235ff77818d5babc3b3ff799593f7c9760431829bf697522177cfbea50358d7fa879409f48a8f89eb8adf46ea1710c98484b56c010785249f7bf3ea21f472542c2baaf5fa56b2051e0da2a72609d5dc34b675481495c96f3d4dd4067afc72463e53a0c76e3e913d4844c1c84234f2633bb90ebb3d34ceec0b22e8b1076072340b910975cd2924a53291dcc77159186db85cec57702c46a8baa59e31404972771314330eac67e2bf701836b32591a0a941f5825186396e348de722ed28c501867968530ba34db4240d8a512bd3f9b3453838eaabfb92e67ad07e4627ff18729a06883517edcbdd5f94e42496d3351322bd66669e07ca0918a8067d5801a970949b0e1d6f8ebfe0e6d20ac6f11803a0c60aa612fe5455dac3cdfd3430f6a42768802b1a54dda2a8b7047ebcb98ebc606d3514778bf2048d60e8c2667cbb310f252bf4679703ae9d17a961d8e5588bd3b3c25a64c8de70b0884d0c23386d64721a290d8a40322d78fa0539e576e460e724963230bc010fb087786eb6533d7972445ec6b7edd4ebad1e0b0cfcddd28587aa0f393f71f8c9c3e5d0d181a86b82428f62fdc9e3865f3ecca932b7d09efa7bea0d67480774aa1c1658d2f812fa604515988821b7a1b58fbf4c57604bdb38c09bc2adf6a62b57cac597afdfb2132e217934dbce562b75ddcd5943e6e9dc4b985705bffcef247fc9c20347e464683472d2c6517b133deb0f758e53bc74f1329fce6ae294a99cd5d0ea600708ad60ba4f9b99ae15efb6a3b8e03308544a67e662f5815fa7984dce3a86e04581ee431d727987beedb2a4ab1c3c019882a2b5f1affe491b966459b6e9b7fdf2b62278e72882f4d3dac6654505dbc24a05a9e8f66ecd31cb09bfd3b2f26429047c191e9837d990b082d34dc8452b1c14baf73c8cb82c0887f061c9a071985484f2f8fb9833bd632fad18ce5ef621bf55b878a5eda8174b4e988b3a015c488d15dd0d7a067266d95716ce6a80332097ce2f0f7d4cebcae442fc07d0d2ba3b8cdc216be3a7722db1f530f4fa9e7630815fb31677611f09848ab7ba7f32ed6487d07d9cbe8672ec10f20afce61b206c8f0006e72be901f6464d097dcadb7f0b5f61c53d650b728b11d3a6ea92162a775439d3c98efde8c99c597595affea32e516d3137075f729831828756ff6dcc055a45a4be423719ffde678490c72e47ca1f437a9c0b4a2cb5e786b549d16f10f2a3916fd93b55a7a08b6f922ba7e340409841b9642e0f723c692f5d3931c3e2c826c4b3251bf8d92d4e08d97eeb103f4398ed57894a181da0584d087d732baa6e9167ee53ad00a986bded8007f2267d7dc24486f4c2ce72a4329c850c06e61dd627aee1e5f1949578fc52b058552778766670b6692f7372fdb0138da4ec1c8518bdea98b9a6ffb8014e797fb928460eb4fe1bf4789eb72860d9a611bb63ad3daa7c9ebdbdce07554b952523a0ed7575c63478af828fd172f37377648d0dacbcb3299e71320c0bd9f8df7341f5d92f9102492ba01e4782722117870fa9ad5340ed1ecc530b6296700b41f002789e2402848443ad818ec8723124f2d1a15d2b8144781c2e5a973043cc469a8e98bf5d49a285b869b96f0340c6ebb85bf8001a7dfa827e2989fe4183d66dcbd5e2668b895dd34b068ab59272fc718ea663e1281bdea70288f8a6285afc0f40af0f00b347f276c74d6d7ab955200a23a113b8e824ff6405cf07a8e5065320d363f29b381832f3def320f6f82c01422d860d6702e7639f70fbbf72f61b54dabfca3f8cbe54598d6b896887bd65130717d61648696e201b50d8735ebac023dc696cba98da3a8b5d7a677dcaab51cffee8d0ea3ace023410364ddb07088623334352d96df4342cb310e61232776729fee449f0d731f038149d2c97191e096266a2f0cb6225c747c9d45b252b270d188ad48c6eac38fd951e9b7f503b47ac1c9acc4eb92b3b132c869250cb7c1e722af2d35363a61aa3e9b7553009520c95b98dadac9ad6b7be5d8e156744dc5572f1cd9b75d8f307cf2c857d2742ccb734625ea1cd14b996fd624136df9894b3728c915492107b52cf733662f44121868eea130570c2e9b7b6b5b721ebb2a3d57248f9274fdb035a6900fb08f4da0b02b4bd56381046f9b449b144662dcc7e70111b37475fd9f6e20404e1870537dc1097833bce0d5bbf8ba1842ee04bd251a96b5a969e1e00a31751dd5e9be4ae60292b2bc28259f28836ebbc1af3d0ed2ce672d4e952be4ad43c385d915b0abd9eaca1ea1b346cc44830b8aa355fd4a0dbbc03d0c35f46a58b61bbe8a4e331976834e67626d4f3428adccb7b3e05b4b47ce072b0f9ebb81d9f402c38b3f484e8150d62cc3831a56e5b2a6c449249a967c7fc5cc3b624147fdb3ed35faa8c0e6e10dd0b2e55c46cbf9a6d80e3ec5ee51085b372580d2356f7cc7e03d6efb7b6024155155fe36edf1d0240c0de2c1242129980321a9862aa84fe14ae195d400f056d1c34d2bc0e51d7e41c133c3e89515f53d2720050ac02b73c17135340c23074f35f66285e9d47c9802d49303e0a4ad8aa9e723eccd5eefba7c7279bb860c9ea64e748b1fd817e8ffee2a6974c732a7d71a172fd6fde1b02e528efded6f6f3830b24d075088ca2706b37342484ca9c32643039475b789f854ea6bd7ddddd35c5fcf7d770e8e3a0035dd6e864d778a4bb360f6b45e020d272b1372e6c4747a841d5641e7d564d444c5ff61c8c7e53f8d5ca7731db331e6987e752923127eb3800c407c7a84e136795682323f566ce916492a56dff2f76b0eaa25d5cf4b9012022ae838a0a357c8c7f16d917c4d0defbac73e90a62d57fef57a52b22c03d3e46f34e46d987d89888e001759a3321e1a8c11aee72ace57e4858d13029dccef141e0472534b37edca5c67705f25b3e109a895d8172f235e61820e3b8ef405c17ff96a8e1ea9cda1132fe3711a70039c8197a570f7227959515081aa9e29251463823d870881c4cd9ca2b9f6f76acad0472c0353e72432690346ee4b44462bc8cc99049967b7f0a80beb824295979a400fc60c3355556ff0ac6fae3fe88a56fa6f746f43d7f08b2ad95af017436606a29833adbbd7202e92070076844c5dedb3d32e357ba74d7bd8e1367cb268ece997e4712ad851abeea7970f195a0e3371b364267c78ac78bcb19d3b5acd6fb87844663ba11cc7250bf06ec0f068b077a41dcb2110591d4216044a0b3620d68c9b3a4ddd25d8c404ab47573be39a84a672ce45e0d2ef4b3d0899fd5df8c3bdc4a2841a116021072f0e4a34e6dad0d812440ebaa957fdad934b23e4faf08b127da6a5cb3df6b6d72c98b5539799ce8e5409841c052cde741d117a373571d6179ca9ea7acb16bd93022fc8eccc2a2790e8edfe5f0d722523efe2b40a6e9377b254c60e13924d02047332968b301a02e9909afb3ac68e2b60e9b0563aca91c97ca4e9621914a639645db2642bfa3ea110a75a7de53a52a672ca1afb128bde7e278bf84a74bbca8bc494a0ae4963ef57c5045a1b3eebcd72b6d36aadf75a11a922b8c9c12cc0030e7721b99b819d96b90208b8c25367aafea1c409be6d85e533508399265ea4820945de6991a4c2aa9ea49f7156ab84179673213a72633d01cbf338a53a5dbade99a57ba8a2259a223bb3db198dfd0bef7c4fef13ad40aa0d224f47444df3d5682e550d07af26537f50eb68d9af6d3ebe1e0b8869086728f9a6b9e5ebdcf649c4d4d725c9710b6879bb6e21f8fb1b1fef8276b4ab8fdfe0b587bbffa4b23194a58d2504253d7d232df8dbb91a1b2e0e84c262b2c4a68adaadeb5f10d1dba480d93bd727b8f772b1cbbed0ddaed19258d9a578ef34e85e0788fe1e70305e97a8480bf72d6ab69020cf7b6d9d35f9961d173ae736e670789c89a83b8328d1901bb5e053cdfec6a557f2c2538f887123da95a4d7a45524ade48bbc7d012d12d48b1fae07287c30e266d121a8f61368e8954e09c4201d5edc685340d8495de34a40376a0726779fc56eba80fb99ba17a983c4efbd0690b964bd87b863c3117e0953989f0724da5f143937f28534ce97740dc7876245f5b826f92f48df84206610a4d561b72c81f6d717596358b829f310da10c5c021e5b1cc39daba1f91ea6445ba6be690a5d9d36448d96b973d04068763beec5545b2638cf866b754dbc11180e212fe072049c95d19d2f8c00456f622f88739815730ebfe1034f01b3bbfcca70c58d8572768e205e445d77b400ad58b11d97a492b0e2570f9631e1cfcf2bfb69ce80a944d0e0428e195c572ce023259fe12656f74c77df458f27110c8c4047433954d072a279d18ded23d2f178ebc1b23c4c80ba9447343ffb57d323827766ce999a7a3b072fac3aae8ab38bbbfe750aad14923925119895c93bda6662991affcbce1148f560ee5316a6dd1a28043ed3bc532e10ed9febaa619525b89658973d944f0d5ff9fcd66553efecddfe8f76d21e15451ecbf5af69f5c19b00d640dd73adbc3d72f230a61caeeca43ab987bd31863d95ba9bcddeff5290ce8c95cac27dc46743364945c833193842dd9e2b7bfa311fdef31fa9bdd06426541cb7d2616d336c2f72cff2c461032ad090ed8606f92587ac68f5513377b7b193a78ee0d77c20817072ae8d70af7d60d8d8d3d350a1ea37a39d19936a4c7dced5c023b132064c96c072a924692cced0521dde86621d9ffd3fb77c6b8704e0bbb756ecedeb191e85560eaf118e7264f55fdd2e428a22a8583e51e47cb901e8bbc983e27009abfe337861ae8d4fdd91c7f09025025a68ed38d4986b5902fcf672eb192f90f40b4ba58d723ceafc07c40e1da0af4cf434cc3856473f5e11c2c381ae8ad34f21430c13a572b0d5d3273f03babb1510c1632bc60139296e23f4482bdc7d34ac60a2635b787275ca5a8540d791af3b38fe3e7a815665f4d8e36f47d6ed845e2fd8c795834104bffa74b594b8d8c6f62b7934f1e63c8b7518dc9c35e7213c8cc087288dc60b6529bc2d84de6be11da1353b8ac04adceddea8100f8e3582aaebfd7284ab3d6a35d0518c3c6732ca87ccf3e105f6196304814daa3d88994278e29271a5655e49215db2cb204cbf2d5507a24439d275ddad1df307ab9e1472361d9be080a715fe72b47a0f6aa4a36875dcc84b4a438f54f86b87d8b06194a7d42330b3c928b52272bd87bd5d8c6853f983a2fc30b29f69a7f0bd61eecc01378703fc977d9c15927231722ca1d669a8cec7a5c3442c5fcf6624a6cd523812c7e8005a11ec7248114d95b4b8f44a24b85fe3fc89bdd6ec3c1848ff137f228586461fdcff137ebf774a901651ff17c6ab052358e5c138679df89c96ddba710c91eb0f0a765c2cbf18729973e9d6e97a064343ef382f46cf5154550e90f53edd39d6d39f6599f9ffc57211f6c741553beab64c8e56ec7907b70131cfdfb339e4378a50088a074d25c818b75396acb008e90dbb662b63d2df918a316aa0bacfb54fc9f564d664884c6e7253d5b42df293e4f78ac0932bc2281d221fc2442d8612015c928f560686ad3f3dc76d92959c137bdf40268b7a8c0954d36367c3a71cf038dfd63098d8a821d272e4fb4458e66886806d198260ce86b5dd12f2a6c8155e65902e65b0a70aa22072c6bba27942a39d94df6464fb0bc8394db2a3480cc1d371ab40c306233dbf1a723121175051fcc963157150c90bb20274ff1d1f2463706b1eec2b3b7a929353725d3144c95eefd4de9e0fcc7bad953652143518ffbaf90486e292ad85406ea6723610b9f0c5803ae88572a7d052f3d781053cfe1a5bafb6910166439b67fef72c0114c3bf60051bbe15aca3c1e7843538d1a9c2bd2d413571735553121f1987727f76586745fe6e43f7c3535b987135e6e8646ef74f3550eae21e54d6dfc1a7726052644304016efbf49d206e68e31fdd8087bb3f3d6e09ed6cbd095423b9e7720c6a26b834be5fddee20168001f7ff1f7404e011b251d2b23d20cc06b6abbf723ee7ac867715c8d36be82bfe8c05946aba9ff056578bfb5dafa64988cf949c7296d154f5c3f5f3d9adce606f4e563797e84e84ef11c780906069b43645e5ae728ac20aa608862183a1169c23abee525b14f445a831cf285759be82c5743abb20f618faf2af41d0dc45c040eb8b689b25a0f23718b93fbe112549a4ed1b9b401c66df102cf6604232ccc822a50e6755b68fcd69d41d26579c0dcc881ae2d8b13c44a5dd9444111c866b5688cb219931fe0ba9d16cfefe4ae41e8d443285ea9a729f429c9d24331afb058b8e2726aa0d4c79bad7aa1fa2aef9a9e60fea4c34464ee4b172bacbc3e1777f0cfe13f921ef0eff347879a19d8538c20f9e35e23e644b6379f5fb5282e1a8cbccd920756df2fcfa4e38058b102e81455614ae89eace220b720f890c2640845b362e012846023eb72ab9eb05e036e90be38367681fa7723ba3bb49df41f327fa4a2db3e7e1ab1e47f18dd04b93141c3e3f3390f0063f729cf05f630915ff4f784d0fb2e29a9c2f68896c32239eccca7dcd7fefd3113672bf830f6b7f3dc720cabf27db98939233342c93c763a88205a0bd71feaefa50613464643b994b30c8f7e8f80a8b4aef40da4a247dcb0ff9101dbeb01711507b72379c17abe03010c97f3690cb0b2a319dbba4bcad1b0e32fa67e3de05ebb9a172701b3b5003215821061d18498966f7a420d752527d0b4407d1642d02e396a7722a003ebd2a235311166241fb96ad1d3d1ee6c0ab4411df7d0f606834ef9eb97271c5f7dcbb3b8dfdeea0a8760af0b8ac876e95e7c99ae18e9c05b36214d3be6321eb38509335bb8811950c35242448cda97d50e96cfa8131ed4d4078aa688513ec7bf2d250139ca6faf609ef9830c7139115f1e732e2f6e0f4bed6957b60d20d46fbb943f77e0b0f924fc436ac3753fef64de31ea57ff2b5339563521e6a1c72e5148c06a8588425850224aee72539fe39cb9be4cfe494f1213bac1fd125d756389713a0342cdc7873415c5cceccf0d10f20585836ac03ffce0c561e1480cd2eb81394aa302995f80b94d736befb8734b61c3af945c9daf8ca10a0150990b11809b4808236f6a082a4e8aaab477115ce7def9af42258a89bbcc0906202e191721de724725efa98dfbe8c907ecf4cb79f6e4c9ed5804e33aea614bc6cc336ab7211e34eb1fe7dc518ec280ba87e9f53ca7612e66876b37391ebb41cdc16790d2e23f3c429f1650278be6ecf5fdd7e6e31999d29a720700828c8221152e61c0e72e73bcf097cadf46a00bce0d2e11d821a36edf087f57b90270f4d3ddd4857f667d20363db6c9de44371cadbc5d8e9d5dc9fd967f580a5735526f328517e9e7e722de503646f587c1fe5ef4418170befe40ae85257b5244b56711a7ab231acd012d567c9d733678be7d1eb10a483f742282cf4ea362a0374c8977af6571aa3cb7212ddfa63fb1e682c76ba9a9d588cc50348a37d60cbfe871ffcaec085bc7ea87290dc267551d9700d9b351cd00143852701f734c553f72769ea05071d57e83e72cab27414410b38f2382df915ad1c11ad5fbd9bd83559e970f6be2b4182644e72fa24eb3076f3702b5c5b0e097043cad62dff554aa0fd1518610d48982271c327dead2632c1b161d1d2c85813955eaf8d19d37a3f3b27c18ef0b27c0ba7cc9372548fa98441a4e33b15da6582074f06ec63c77b845fba8729e46febd6359ee772cde68f61d7cc44c249c776be0ef4cdb56c331e83fa0dfe4ae8275b5e9cfa7c723b263e78140b4507832f8f5af42f1e6c0b65e62d8bea280d5256abba55f9f94f8fe0c12206c63d1677c231ae6254ba4df72989fe645dc8a6dfb8866839d91c722396f7af41f00636d0c5241a17bdf997ea3ad7b1a42f70e621c436693829c500aaaccf301983d96caf77a0a57ee05701d538db0bb148f7ab12ab632a07583272f88a52a57e0fb73a82c41e9856b5870b77584a4047dc454be9826bf2303975723afcc724a6d65b67a2058f0a2e3e5bec683aaeccf8615a4759bd7ed190bd5d725a62edce7ef67c3a56e0bb954a9778fc6e26366a9938b5f1f7d22142408915429fbab5ab8f8c8b80ce06ef5d3b35e91a7a1a713ef14b4eafac4ec9e040f159053e8d8d57e9648bcd35001eedb608c293ef4d031cac60c7365d0a66bb3b4872721119edb5ff64e7f9aecc1814d76473da82cb1ace5b5909ab0cddb7dd46a1cd1c6d2afcb36f2d3ac83db23ffc13f3513588438f8d9c544ed8c83c5b0730871e723a215f75c2edb05170c51a5e1e52ecba424f2fc69bf7c3e4ed3ebd436cfcd772d6bebca0e4df605773aab07fbf9c0c579e840746f9febd505ca4e0c56b48a672105b292a8f0a7bc9512edfdb987a9988df5b9d3990472b9c3dca3df78e81651ed2c2a905707023ec79869b8a5691a3d67e3b8e50eefe5e63a7f2c34e4edd300eba9f603faa62da84fd3434604fc28853dfddeafef22fee91f922fcb91f1f8572ae6b9e736c97b617caa27487f301a2574d726ba94c17a4e7da58a0735f047272e50bb01ddff66d71d3507fc408e5a57d8cd38fefd6df1a932423f950d8c31f7291dcff4aaba0ab0d97b10e02e53e0c2fe77e1e69456672e32f9d618d8baf896692eb78bdc2b1cd31f434a2cd7c91cff66a81c044fbe6f55004255457fccfe10dbc330c8a8ce182c5fd5723d21e5a6a9708e2c9e6eae65321b5943878c2ad294433fd138abb6de9d7d086aa89a17fdbd07d563c2ec4aedc53314f1e64f9f4c16af64e1b463cec03110efa21cec3cedb9a7999fc4a66a1dba2e20ef00526698472d9792ce8f1eb1c42681a1eca1745052d8c575211916e4586fbbe8ba0b662037237d5f804317047790f65f4bf4ec78065ffabf5ed391bf3575eca49eb7b2ac045a031e99c7063d99caa3ee07b76daaddd584964d503d4fb5bf1c74efcb83df761188cb81fb42641be879c02a54a3f13a9cbb76a2a75bba253c170ef86e80c6561fc5ce5874957733b0500ec3d98463389b5089235045bfa5d80343d6a10e2b37278689ed4f7a9d3b87cbe08eeaec9cdb3170ee1a5998f3de4b731f84763ffaf72b2ee67cb3f144c55a4e0956730f3ad32302f59175dacd9418afdec453d93f97277822b93d30ad15a7d0e071766ecfa55cd7a27525bae3dde5fa560042dcf3957cf4bea22114581e35555f623c3112342e740d86831522bf1de178c05e5d39e5f62a76d329bbbaa383ea4c770108b3cbdf16798010841f7365aa74d7c881ffc72690964414043c911e7cbde80d8006e706962d97274be1021a08c78dcfca76072bceb384d18532235c43a91eb4ed79ffb2671f75ff629c2def8d78c31d2648972ea1045f2acf1c39358392ea0f631f33bd9eafae16d97157c9fa9bb3de1495772b5d3616c6a75d95b7e9c301bbf0670d1a8705ad82c1647668783855fe2591d05d64c4c166ad5716ab2890aa1d925e46c61c86b0f32775d616b8286479fc3b972946754cfd2dc574d0c9677753f57c8df1b4e820a98e0e4af47876e1f61300e721c49c02acc0c52ce76adecf00b322882a71a3251b8096c575c55b876bed85b47209536d9e704df9fc7cfb6f5cfe1ac72a3300cfdd7f30bea2d931ae0d1bd58720b9802a6cff8a8025e83494b69077926a03d11af041dc7eedd3da8be05082f726472d61429a37e251ec28f37a7e14dc73d5496dc717a70f0fa15ee7211623272de63fcca40ddbb87b036c7dc9e6aca7eb0003f9239d256cb9c79e3068b02d0253dec6b75a55acd5a2a5f3ae95cdd7d8d13f38ebb3f80c0fb8885a094c53de3724f03f9552aab025634509dfe4dbed8afaa3baf2b1185a4cbcca5dcddb14d0872e0f510db2af3f0f09aa24f68a828d566a8ad93f419e7ca030f67a950d12d2a7275d259927fdd07869bb2ed5240417de9913733c37f8e62b599fed2ca524c5472125d1c30031af403c3f12e7e31d1873e4d360def41731358f6324aec45019e72e913b2eef2a9f1684f0c3cda4ab68c7ebe9ab7558a9dbbf63c90c9f4c963b809e199452f57325904f32b26abf215f864b26c755ff86e23451b3f68dd918fbc7209dbc1e925ce7e8007b76f5c50f5a89cec4f11d537dbcb630a1a762b01245c147a6c38c29414a8ebcfb33e5ee429fc05037e24e8a6baafea65a0780a3bf80972dc943f514d95e374547ff0fb13cbd25904812b9ec7cfd493e1d7e88165611205c3c86c4debcd17a1c705a0621582e91e4ab639dd45ef1698a5de0ec961071d2bf54ca5fcdcc91d879c9d190cbc3e6d9eb5680385a61947afc0340a9534fe9072151a57b674084bc98c017e637bcbf43a54a75d29354d01e19fc4612aed954572504b6c48f1b270534274fa509b638264af32bfb541d2478815dbbb0c7f058672a8fa00271c218d132937a1a21e7f2239a647e87baf7ff180f701e7f74fa5b472835adda0510bffc430107d0a763d1a30fa55c940a57e3cf44ad5541b2ce9517287fcfbf08cafde30b15f026d9c7c95a67da4ce774c265e8738f25c7f344cfc7243735959bfd85784bf55f5481e47a9d1b0ea1caf4095cad7b6fc63b08abd594ca039e659475f71e50592640f0083f3f3e730f9db2e6f0664e2433fccd727dd4ff7b2b2700b4746f75723503e5bbf43452d5563eff190056cc2bac5813c7eba4f02e488613b0a121970d669fbe30044c360ae8ed8383a39329083443400f0cc725429b518f962b1256b4b342f87f39fe0ce17efcc169a053c4ac877fb6a699a1836cf8ea4ec3447be3f302fbd696095cda2dbe81b646fce0424c8cc4662f30e729ff88046f9189b81c20dba7395e4e0f973167f8fb9c722c48b6dcc68bbea96724d5c39a1c78a6f0b3fbb26a2a9fb8751a09c7271843226119ce86200212fd77253e8f80667bcd35fae3fbc48f08544b7cf00db0d118b26cf410c4aaabf69e332ede85b27937947a5e7f7f80dbecec35e52c74abeab9698628e3837d45903137207107482a085e47b1e9ba58f2fc879542ab69ee957f755e3c9d2936bd21eda7249f12b8921b5f0afa319f31ffad90a4a4a32be6ee5267fb98ce180159da17b39eae7f0c3271e836cb09b4e9a19f4aa486b985d7a440c0185e46d414faeec9b45d5381cb0bbc8ce134bbaed7b77eb1f7263fc5c21f0553b67b39e79a4b38445722f4d3d9c86dfc7015de4bb9baa707b75b828351b44d084fd8d75309934af3a72ac85d714f2d457a690fccde87bfee40b6ccc078086fa6448849c9e4223300552ebcaf13a675b2e5d090ecf8bf292f6a1b4ae52c0ff5f6dab6bde799cb4131e4ed7c5c19ff59ae37f84ad9d69a4db22762e3496cbb02ab8cc439fb7a8ab2e3d1daf1c9213344fb2a64b79adabfec47645d3ce650ea8a29ce44a63c4e1f350087260cfdd4e23b1686643cdf96c3bafb9984d933e2450143f41102ffc1440e8c3723a39bbd7925405dc267b595a259c1f5228753d6968d8ea59ea1307f5a75ce472274640a5253199cf9edc724e5d7bf9931c084a8c0f921043e8d6f9935b392b36eb0da53b789f3e62d841c2100420d2082a3f09cd66b3e70eea0b66ce74b68372ee292378d590639a74738b9aa1732124960f5d82c34a26c88cbaacd8d1c21572d8014920b2d805114ffd66428e60d75afb8fbc0fa6afa158c02938ce765093374c5fa5042b9dc19409a636fb14d662dfafb989b937210e30bff319654dd16f1dab001542b93935ec62282c272e9b62cc57648d8410acc3c44078eddc1fdb3372a2cd812aba20aa3b84e4e411a977a4ca48559f3f92544ead9f3b394978e79e7242e8da9ff999c06e425e68aeb4c767013fd0d002b97e28ea098a6c3084dfee72844a8f6648084c5d5e49908d2b5b71303f603609203c00a47c7dc9aeb2939672405228060e211b86bea4de731d9cf71258a93a1679073d85db413414a889d472920852bca0ad5e1dc9bebe55c32faf7f0391a0b38e0d108bf7de8914e259f27278a73c1d4dc89707db0cbcb4dcd09cb66a0c9c2325814cc83ebbbb902284192d0ff9d0255ca1d0082cefa1c7d587670f19d9e64fe1b9ecba43249d5b00bea80d2522bac38c890a1316bab3384e2fd616199989583ed49e942f4ee3ce3c4400721dee00caf888eed720b92ab943b15778fd37580310f6f85d2b3f331ae31f177210600f2b36745094e44c70653be5897602fdcf146bb90f21117a92df9e2e5872ddb27970242cd459df3f41da81ea1c03d53a87d805ade8eab90cecfd70ad6372df23686e9fb77b33396aeab3ffa842faef7db6ecc3f104e73e058be86ac7697247116e2e26b571ae4b5b0c873d6018614f46823c1ef5b0019453b540f5222a7262a8a006dea4dc150fc76ef434fd9c36aee7c7e89063618b6e2f5adb7bf3b61736848d33543d06e1b4640287af6191d3eb113ada5c85cc29714be83b2a026727679eaddcc714bc2587185c33b336a14781417034d150dd98d694c7ccbb59841cd0b2ddecb552882b7ed32fc62b334f06ffa623c4ea0a2821ab6df722d91a8972b78ce600fee3cfb1feadd939b3c4e70d7313b3017136a0d695d5689040eb9c72eb265b6b2de1fc7c809c95612cacf5acb273e4964c39b2e2e32147c35aa42136ebea4c4589669701c9f36dce5eacea67f5ba961dc29d30d7f6418a298139745edbcd253a047991299e620d9b640e800d03172492a5743cbe2826639e90552528465ac8777560df5a4dcd1eaa07719b80e4f4451c24772ddfb6f0df5f062d7372f25c8ccf53910e0ca34a904b4e3e34e46fd0fa51680f7badee8ec141d9d0cd723553e252c41a5b70a01987af7de67e44854c1be5a5c90644ed1a78480d02cc729626e693a4574c4d533af4a05adc316f25ebff02cc78b51ad20bb092e673c772480dc8dfaf2faf3a21e082f01f1d4db0cac5acd98610b1774f88d42380e8437295e3b10bf1a42c84aa2d769ea51b5df48774eed0ed5b1e99229edb2c9df9d0486b5ec37df929f13fc6ee5fd2acec661753b051d70bdfd407994697fc8826ec723a1638163e2b913057bc50dcfc5a18ea27f9902a51454fd7751a7807534f4f692d194e62255d923feed09f52a8754f50419ebf56404d6d3359609750c8a2c5722c99cb31c169bd1255a17de11a60564ead99539577595dcc9838e2d362f4a472f50828cad33052d517ab53acde089135e182633609a4f0888acbbcd811b9c05097377a54180f7d4075995ae6c635bcb8c391bfc6d3f96eec414fe2e109778272283dee3350513871f1b957333476a7300dd5d07eb3e4c602aaa2b6f16009137270f18d5684ac2ddbb0d959f8ddd411f65b43e479ea9a9ea87904acdfba712a72f6372231e0c63c118f431d93a32ac644f5e3a00d95cf2700a2620e2b204f3a72bf28397f7d51e9f5b2d4c529e3734f1acdb8555f297ccd621eae667935ec2355cef34692c4d979f172017cbd1e2da8d11b6d9f73b8fc12153406481b01616e7234d0e0946939e0ca74ea32b091f2ac67c27ca2a9e273f1717a8727d331bd437276414256501defd822bda0e5a5b415cd6156ad21460314db40256d7dda242a6bdcc3f140f7ce10d28bb2142df0c5284786188b7d63d1acc0351862a79280d272669364c0e80a91466c469590121e06582d977d863fa0cf535861dc260e938352efdf870a3408e27adb144d46603aee6946143afc5d5241357de8967edffbb37263ebc92df10976a6f3cd8784b93e37c338bd139258ff1ac7e8ab624d4f7ef3721730752a2808bdc8c98c377a2252548776497926174c10b4078ecab058eaa50305b7a14f022241e689a973e29b278fee3f22f2f484921e6a1bb2964369e28572a1ad2923805edae38d89181abbbcd7560bb3e115c04cd5fae347645051c5cb7216754799565e2f17c01b4f3cce7578ad91e41ce6218f035e1d5f5dd35eedf472c9f7472f18b3118cd7366db0805ab43814873a8938de2cde6ba132867941ca72fa0916f18bf4909d2aec801a43a279ebb81270dba63188c344ed5e612deb77723874d60683d7097336204944f5a3f13b7d37b1b5c2bb223f2baf5167084d071855f4a1cb7f633d3bfd786288a5a177e1233f1d327ae85560b4d2379679dd0570a628270fda68409ce15232587d9fdedd8668b3866475ca3db82c52a6f9cdcb3e13739acda1b49ca5cb7c46f22d3adfd8c60daf42953cc5d307fa55308273827213185be13d516692f5a71e3a6dfe808247fc55c83e21157a11118b2e104e9b7230eb4808bb51cb97670cabc13af3ba286f8a0b218cde457cddb0b70f9eb531720849e3f72063bdb16f414c203fd84caea9112c691602c8a60b4d7498ccc2db71f021620e4e70087a81d36937afc1bb216f8baa3a81e34a9533b0cc037341841b7060aa19cde55e836f100800e31e5eef47a13cb2b936b5068cde291ce30b74729bdaec518326f6cf76b95a34c6710f0e3746329f3c9725b6ab637f570ac41d7226db44af938df48e3a8a25029d696d17cef8c09ba123115501512753665e7802787b46b8c73cd5fdebc6d9b86ac6db757a52003f7ea1789ccc40641e95b7024ead95053aaacb1689ed51f5b676521e346f6b155a63723f205eddc15608be255623c1f0965f26c3a2e195462e687fe693ad211fc90210fc67cf63df9eae2b9e7253b83a586bf4474cedd8ba7bda5af547084f56a47fcf1181a4a8e438727c3d3ca71862d1681cb090c886b6720e292ab4107a0b5f04e3233a9b08a1d7583fd104c4c0c785f86527e04541cc1da8247005d780bdfeee2d9d20cbadc7b100ba856edf17c2812dff20bf6dc1ee22269ec3e1605dedb2df6aa5dabb99c3e60a16b27257b5cdc78f6c03bc74e20e76a83406e77f74005846ee680b2dd5d7c4d6ff3b0939ec388ecc03d65663d344a3aea56bf0405b6e027c536e4bd075b9a520700b72f88cdad4d8101a705e9e759f1444b51e4d5f3f97fae3c0f5d972c0ca7e0a7e7274e100b943b62623154d9d0abaf3434cfaa671d36b9e0295019c67ef12835a72693fddc3e95e1235600ae34832dd1d62fde857bb19b7d041a36f8ae9f4901f06c861a6f772902114ec66f1b3d9d328925b392c89a1689e84936a77edfe11273d138f66798543b3a276295c362c56727d97c24f58e3bc216068192281089c95720931ba12214832433ae999925cbbeea559143005f7cbcc1c9e788033053e765d358f31dbdf3709235fe7d355e945eaccd5f05b4d468b3edc2ef7f9223f9ed9720a585b18566d5d671d051b27a72e5627ac99b6d2edeb47621e18af8f2a32cf72a8a26cd5a2597ef9d0d3d11eef4fd3f08a2f469427fb6a66ce28a3b8055e3708553920fe411f8325f97a4140560fab4b954ccd26f83980089bbb0c0ae08cfa1312123af519fdeebc5a0a33fb277cfc6732205bc365c57683eca3b28712b7d009464abe7ba782dbe1b40fa4c443574bae591c964f542e6fa3205a3160564a2631946efe4a5cf4c085c27dcdd862172bfa8ccb2a1b67a5bdf5c993e69f414e17727e52f948c662bde47b61c77d8b84113b40f21bda2a2c98d32066b8c877a4a672e32c1dda49f486dff2639b4360517a92e16c458ea37897e83440d541bc10b572d0445e168c953fb667768199dd69b15554e9609ca2c3e00e279ab3a7eddd2d72e34c6401b7e0d5d6c149171efd83a7af148775101c8136ddc32742700f2b0072db399b30596c10e7401b3bae4d6e36d4f1162ede074d3bad5710e6733ce6f226045068a404994d51928256aea71500798f9d701d896f0b2eb23b4aa2fb009972887cc3ae254050fd692bb060cbb4a3558f28378875b5f0f1af5f2d182f02d27238aa67a82aae51a6e4f5c430f9dc7ed25afe0ac8b9d8d0fd9850b924004f563c22260b7dcb72b586a06d43e02006dbb7f4fda0c76e4256fd23542d723e4992726b6824010d724de477b57b585b709126fdfdbea8c9495922dc17d84735e7f7525fc2593d329435ae985897f130f84362cb0579ef6b800a410ee7d56c7b6725000770167133ffcd2b7c200056d1022ad1dfb032389392a41d1c0459625cb7697298160e0076551d12ebc97f808a7dde2dc02722caf69180459a9b8e1db7d02464b02ca70ef6b7f29695298fc79b0ec53e7b49e3887bf84b62a744079de7a63a7286a4212fed347e2138e7c636b836fe2f55179dc9dbe8fced30246d5dc673533daa441a97c8381a8868cd1114e2e7c5e58b68311952507e13b31e78ef1dcd5e729327d01beffe17de50521125e869dd24519472b6d14fc1c29dfd5527868df472b7e2b4b822ca1a6d536915de570d7e2461585721cd85ad3e6cb27a0ec26caf72e5373ef91820775c0a0bd279a01377847d0f0f68d073ffc86d4190e91ea5b3725ab5aa1092a0db6c5bbbbbebd429932be8eeaf937fe3dda4b85ae5491425f04ee9fd7d7eaaa274dc3d9e294eaba2b8d781dbe38d7a2d6421f720c0d37ca88c722eea5f8bfccdddeacb62e70c4ca42219665e34341df882785e5923ce2aa672285b0bd70a9088e0a2e21173115cd5375dfe4c1f3b872ee16ac0498583ca40ce5edd7e41d1333cc5fd488cb5eb88b22d442395f079a7284ca1f75a5a1de53ae172572f1d4d2ce41fbdc4143f26319f57679789a491e8937097c6bd2bf9de0b5c14046833ebd54b20d492aa2c58979a184a4fb5e07db9c50145158ea91c081e547267641dad108f6a1d0a77084aaada72f9a831ca105262e365f7d0a981b4d2be537a4c205fac1d2ee14807582051b1e61a39db96f034b5718fb3d4d0ae6ccd0d72a06120f5c3f0321c59af66d4b914a8442d9a186b31f9cb94c912d01588edf24abef2cc4cfda358bd5eb1c6f6ebde51847e56a0b0fea5a30e1d2df20fb81e243ab8c0ca7041ffb326a7031874f1950f71e68978de3cbf7032733e68e54d36da72e5b728090097de45bc6d7e2b2e75fe40e4cf6fc64681652518fe93f745f76e4528b07afa3f5771f430de3d30de4217f3278e08375597c8b4aebb7c1e14e6a772904723f481dd9c40211ec4b0f60e2d4e405f69495fc3a7aa9b8d2b71aac7e24e5d5e3702c9abf3efb051c933b37dfb0aa78b0de8f1b13d83e6d1d801e4879272a95132d34b4540af834a40c4a30a50e3e4fd16d4c485d3232c134d2346d938245ccbc325252a7bbfacba6a840a65fb3d5eb232a5d1f18d95c749c3410490b05a72bf35a9cc75298f04c8639d2ef3c151b83e4ba3fb616bfcfd215f25a1e2f2724e9eb1b7ec6a42f52cbd97dccaa2ff6d55dd924b495b5c8751e81d4dbea46f672b10c8857f280eb79bec9634985ebbb7eff22b9acc1d8e5a54601dde91f72e4292ce030a1353dfbbf1114b8e9a10778a93516db9789e5e527c836291d66f9a7297faf530eabef1ef7f83b3b42573c4c51dd35694acb7a8bec1e65243beefd76546e1f47d861cc771b00d4989fcc1fb5d26580950ac82a90270c5ca933378ec18252646f97307fe0df132f79116c18991d43d8910f78f8ddcc7b11d72dcce0e72c0a0fe28da282610d2fd01ef67b394f7498065550f494b1c5dd85ff9c1fa237238c02731f692787ec14d65725139dd8af70e12a48b5becc85ee2f5bad8ec8f72a734b0bd105c161ca5aec8c60264c8b39be8210d299c2d19e8cc2aa468039d72c0e8ca69ff9c39e8933b2f4a409ce55f1afdca9da2d7d289e1d2eb26798ab97246bdfe4715967eb3f1529297c5e469b52b0c382e4070eefc90fef95d5b5c9e72946a8b1f1c52d81ba15dd6ee756ddf09aad46997db026b03365ade2f56fd8172aff73a92339ad99b43dd6e305585b259a9ac7707962e014bbfae330c8eeb6072c452254fa43394e59cd61cdc5f00d3ffabe8fc321846e6b2ddf9ede4e342260effad52f428dcceb680ca9e39567d65817ffd7a2a6f4b90e8b3f0121a66095a6bed7fb26dfd545f9db28783dd542ccaa90a60f4143e9b8e78d5b526568c4d780602330e7df69d223cdfa389cb236f1615f0a8db9a9e52b164d28438b3c0be6f0c3eb9399e21a3b2efcb3a3e44bf6eb423f3645b8c9f4c997d8c422de04ba31a72c05050abb89b7410c896e9e8164af5b31d7fa9141cd0fb635da7b2ac6bcec6729440e071324659c41e2ffa21143a9ffaadcdae78795979893789f37f899d87720e0bad9384f45364e769239260069c84e322d13e525843e0838e93f4d8739166e7bf94d9f99aeaaa7cfda3845f02ce7a6176734045f9eef4da8be2c3ccbac9080fad4d99d0552a39be267df59d7654d64a0a8e13af24d8c8a7a6aa9b89c77e72ad282b821924573b41415b818c97cd9d21786c87713767e6f4d49c5a5f08db7267211d9168de9e6e3c30b285aa6ea229b6d94e64b848875cc13e36a3027f7372661dbef1bc67676135ea6f59f2424c986b7fd03a7712ed6b389521d0e0083c722981576741d332a6f4a66d84b35aa0cad9c1cfa33267d393ebe101f70dfd6d728a2fd074daeacf98125fa2bb5dd84049bd872f45a7d6f5d3e085bd3c769d9e72470f0aeb8ae344a11b7f7992c1b1596de588da791120d976455efbc4aea8da4c6da88bc9b0cb633cafa3c2ac94023834d35303c3d191a58112550b9d65ee30042dab300a3788f961a524939bd06b3f42f6994633079463f86623f7b31ce3817244d0824d70b8393e9f98cbe850399dc7bb12b68c2eaef96d02917563d5db040f07e4b938b4e0ab07f1f99f43e28fa554587c8d7113b4b5e00191fb81bebd937228bf41495b061d4977262623b5a7ebe2c9f0c7022e5f2db3952aac9bda97c76bb8eaa9c44132a1d2428f3ab6c92d2bd6e7fa578a72d31d26530bcd37956add0e9a37ea1c43a412371b6ad96c73b18cc200d9662f34ddebb922d8124a49e77272bf5244704c3093351258e2d593a19cbcfd61681a80d5dcd1d65af0ffe369a4728fd846e2f94cb7949836c57a82e39c5db60e7c32a4d1555c3f3e26313dc65f723807157ebbf519345b6f1ff909c620065128f445ea9b87c2e9873f049ed0a26a66aa82f6fbc149ad2cb11e2d8a968321a8010af2334f1f62b30f5ed5eb54127207d09c51cf36c4811e4c87d1bd9c10edbb1364e0006d05559e9fe32891f2dc35189b110b6674f3548dc29935b4d0a3736f556d2289f32097176bb44322da5e37c96385464eb04de89ad7b30d1f8d2f10be2833a4405bbc638427dc5acb72ec565034f6609cb138d83dd0b5b96049b457b98e90d69a3bb9a6613c8da77deed352311b7168e030a25c3b4bad6ac98d3215f2e6f6d5bd9a749f57ce6ea6ae0eab72a546f6476b37d3bc44880a1eb5221f742a77baeeae5b995efc92af01803a5435d91953d7cd0582cb6735843c186ca5eb6376a09a2d54f444b5201c4027219c72c77fb44eff44cf929b2e1faf70d1fbce6b94015d1ebb7c44b71895781df53772a4d62dcb4764f7f72a39260e62be5c0b23c3ea7d72286a358c10259ab4349b33a4342735b1fae4269d64aa9a067ff31bf4969e886a5dc11cadd53665c6e5ee3c1cd24610d45d3436562cfba11ef464f1e21c6245cc7f0f4280143f083feb7972f706989ea008ab8b96681cd004db1c548e7de18fb5268ceba9df21fbb78261724f1138cef57e5d6dd62582db398f0ad4469426c80ed14a99b973d788a84f57266a5d389833a070cdde0ed828f9c35bdc1064a813895ea9fb1f588187ebe2cf72286ad7da6aaca6d39be9579706fedcf89949ca119aaf2528611c98d5811bb0726241c9501e2491ba010883f396b5fc934675ccc6ef98fa8cb6bd07cd4615f61bbacad3d7fafdc389555a7fd790ddbeebcb80003f29e914b82d8d1e85a9964c72ff47a189e88f6aa31f9474042166bb33eb840a7ef4b2e8c0583a8cc92417c626fe6f6c2241dc8a4baf52d9d1ef1d09c7a740241bb750d28172ff8412f0bf0172f9801aa650b27bc7162609fc2fb09bc272df02d3a7008e2b26cd7d0c7bbae27269fd64aba4f70ee884045b44888e698a5f2eb47ededc42a98e21fce9beec974f1866e8fcf1b89424b404d1910e2efe5dafc865c9ba313bb29f84a0dc0afac75ce3e3b0bb4ad8dc58236bb7e0ab83373b499a25c967b35eb92b702988c2a5564803327bae634357ca9f77a59a81f1c77fcd782107efabd748d17621c0b3064072eae6d19a7706608dad5ce9815e40aed351f48d838aa9541df58243594182a07259fb47764dc39764e40ae1829e5a9255ab6a89f0b0d8eddc328f5485f7abdb72e6e1b163738b819a173280a806602af34f7e4ca33901f553c284d045114b2472b7d6b10f087a991ec1e3722506911d709f5a2e2eba4a70bf6e76d8c44138434dd9535f0316d90984f66e2e389897c09a1a0737cadb58bbf173d4121fe07a7972a49a13297866842a531013d19231f4e1a3cd7f3b05dab9593fa5e2554a3c6562a5e4bd437f23813c140d755f992bd08c614c131a2404b2be75f08eca3ab0c572ee04efad333fc16df73932b4992fd4588bedc9acf9212c5a0c727fec8a0f83051cb6ce7d1c0523c1fdcdca8f9566e00b913e5578b2558c9cb058a28e5401aa728af8721f38d2ec44e895ce797f7b1131caae99fa1d66d340b7ad8989a2053d0af00ce4204128d3a24d0993971f1242bd655579ec4863e6c8b3d2668ec1b0006bd08eac04b230b485cc4f73c9467eee3c5e061d78347ea8dfba4e740f64897c72cc93c55279713e5f929b6d2ead29fdcc25ea5a2a8a6f73469c93c7c0bbb18b4718540841025bd441a615a2d2a6f678a947b120c53e6f2a45546d9facc8eb6472b2dc3309adeb88dcf18e8b43992b9b98b2dc31a3f4d0dd1fee5b31c6ac2b0872c954834f2fddaaa1aedfb3eb47138f3e64bd1ffeee29101ca95893ea6ab45628fa67197a303cf04519c95cab7deed7231c7a2dbbd5c2a6e336e7d3a52fc1c072ad6a50dc70f622c9e718952ac799d7c6097ada00e2ba0f8dde7516b22a2bcb4830f9422a8dd334f91569dcec47284aa7ff22437ab8e72eea45d28387612e8072a572c1572981b387a5cb63a1af96046e08955dbaeda69ebbb247889eb499b372389ed767e64a10bdb3b85eaf3c0340387629e5b3d6fb87107fc6da25cc367872ba3619d9bb051b8c912671686f8f2ea30f164eb12be3e576bb6051d6d3611f12e3daac837885639d6641299c32774dab21c6f5efb37ea222660a75ac01828072151f05f452111065cd162b70be3f5ae4518245f097992482f574aaa409becf46f3901ca29ee553166fc2af87af5ee066e5f618f989574f9c2da67fdc9763550fb446acf3e7293b8b878c4c6ef43c19349172f266b37bf53790349b0ba959c26150b100488ca9f47d7b7d271db9c64fa78a7d8db757ed809f1e5f14d8fb237a2becd7e4269540c8cd182de8cd53615f19371c53a10fb73ad72079a912215d63723cc3115aaa32f9a9fc5718ef52e2bb635df9d2ce4882b8cad1d1d4f285756872d3a011afc45e9b7e5ff55901f7f61af5d7bb515bc9ac00d4c291de040c58627237ce3065c3b4ab226a532d6e179b14247ffdb9d82daeed514112770ad00f9d7287d47df1f1b92c530e8ffaa62035086d09b183d6d67bdf2e2c6bd3d0141b8b7247eecc8abbc85787484d5f9bc8a84224a743451ee6436943811dcf055a888772e76f515dd3d06617a178c6fefe40b55ed86bcd105684154644bf369829a6dd6edd53b406f65734b54bc3744de90a827bd24308634e03d0af4e55c1999e202031fb94b188bfc663643eca83a795df109329009785b8fa2850f8637e7e3b9458303746525a89e950bf3d9459296b9206819f93dab17c077a427491f22b7f052f7262ccd0e5d10f3b26e2bdbe9c79dea3374226e7b19b0d20471012a4ea155d180b833c1b27c7c4e687109cc5a979d564c79011a126d98154820356a414a5b4a94f6e58586ec54dc1416b3eee0286688b70407ba196523b69d943027c49aa6dfe72e7d4c3b1950f282218d5461f3ef910eda0a65dc5ed0be3d5a43610289cfebf1967f17d4a94e6f6c098db8ed1a74a129cd9188bfacfb9ab2cbd1ce191fd79816e642802bbbbf1c4a7debdb6e33cbbffc574e2493e7e84d6e41007d9585bd906388ef59aa71d13d8327b7ff0712a51dce7cf93c3966b55a8d9f5d8f93978e354721d7389aba4f7dd14300622d95b8bcb78377c2f225a2cfbbc3a414166a052165a3e9a8a5aac041ffe67a8a1f068b9047ef34ac022ef1d1fe490741c6b0faaec7269901b0d4e3403c5408cf9e0029536e53e8ea7b79772e320fb324c7cfa8b11723bc84cefe2df98c92bda34b50680cf9b736e81b470df58691fcf91b7952bf837d47a6df1ded215047bda06b641eded17f3211b199792b55ef4b2e7ff51f700100f6b0c49f4ae6aa641f0d721cec3b038253f555a64ed37ec51940681339abf7261a769d2859baa409170ee2eaaa87e82a6b00435c27cf77aa9a5c26b491da0400105be4d5ab82932c4cefe6f1ba601e789173d40cc3ab5cd6d05bd5fb0223872fa416b88b8ffc9809967c4293bc55464b19be781a5ff0c50765e505e3150b40c9a7c822559a7598064aed0d5c761d2769783b2c1b6ad68bd2b945d538f964056b9614253b22810d4e7ef4f3f00f737effd30dddcf49527d45af510590676ae7269fad3e459f67f796f984a79d0867cba61f0d89477995686f011b5fa7946957240c9c8f3d00116380ca83a9ee20bf703a1bd55cdbbe110851b85bbdc35c94c7208d91701dbe3c8a8de61371611b4bd0c7618e161ab070b33df0bb76fdfffda729a61b190d94a5bcb251dcb32ebab5b7ec1ac008689398b02bf7385c3bec51e723a672f761f2d3d6de4730b6c008ec46c6de1b81f264562b1f6ac31ba06cac920a9d010aababb6154cbf369618e402bfb17ac30763cc2651bef30c9432df49972e5401f9e581eb2d84d17f71cf40a1289c79176dce18de5f59604b0186192fd645fda32638a3609fce469b71c62367eeb791105c1f52b8b374103bf31f95a953f555a8329d386ce75474393057e7320dc3d6c21553277eb96ab2513ebf3d10104013a1ed1a8cf940eaf49a131f17e0ae6d3bd1910232305f3ca7ba36f004e4f721818305b6ad81ab36e11b692884f15d8722561e450a0ceb73aa0014c674a7e72f227dd26f0721076303908431592b13ead208256499d801fbd8f8874210ca572def3b4fd1551417f248d820fb0a9e1fa76c5b7167184aef110dea99481d27872a29cea62fcd6410ae76340cbc221c1ca122a1dc33f63b53bb7b19c0cfaba6e299b33aeaa49feb6f778114680033268c5fa305ec7122e9b8cda009314409a4e72cb1cd3c4d05e697b848c72fe1aaf1edd7b6b12fce75514a4966417b960f8a272452d6f88a4030d7af057767eeed962629427e65517ffd19020f83946a59ad8722e36e69abb6d8f2edeccbe997cf3d91ab76d35c783ece9e27476f33145d62e2e1a2db21c0449b1018765f2f6a3bdf037fc752e1eb72187a448e370f65bee03075474bf03a1866606a9d7bdef76bdb2742fb06382d43d89f2d287a2cd7b183d72a0432f4a6a34a5f817e24e1da871929be53f7da0c3fc874a3208cff872bf0a065c240f58c4c7c42fba8e1405eaacb7f5bba847af328490a76da1b92d9a1c077272ddb2fcb5a4107c8ce3df651f7f0e205542d3a4dfbb8e0a3c36921c9aad6e72eae273ba8c88ea049965eb30111215d93c9f4fb1e59f898e45a4067b09b52b721bb2d76e1209ac81fe5149963d07a38cd5e93036c6bcf865e4ab6ab30d3a0b1f38fc2ca33d0a533a8fd12f8fec38d99c81cab7af3e64c0fae765ddde5836124879803a217c0291cfc263f90f9228d6e5c3f59273280e1803a31185dfec4472198fef2b852fa0ea99b2a754e97d77ec465ab22d040e2af2fe737aa8547f08e8486e17d681d9e0fcba5716acf742239fd353483d2446f2695e9b292ddbf1c13e727f04da76618a6b8d1d47b85c48b311e3bda4fe02948f6166ab5ce259b9ed8b72fe02133c2f0b37adb4c8f5f1930cbb6c521c074f4e9248f6a265d1594a1ace1df58038a530ca4e4f1c7debc2fdbab87c10e5bcec3bd7361216d6449a9b3b2372756077f14224a224dc59222bd7b11acb851d44a52cc5a4630b626c462900cc65902bfdb4be31b2cdf234b26b0e04c1040ccb7e1bab336dfc40c586c948cbc8726ed18dbf5b1ee994683a6fb013ae29d135889efb0da1cb25be0538514c495d619fd5ca6f1be7c75351c8bb3a65a3791db3f21b61c2e9676fe1031af78847a4295c6562794d167a31f3d28e5f9f0671aa7c4dc99951acbd823a46eef2bf33a91f3db27505cd7f58fdccaa4d9832ad9762f2f1a5d127c3da118cc8832c1c103172a0223bf1bda0caf0f0111e47fd8a35f15173118fc9179c6657095dfd2885420e3faf9099a1f6192cf0fcfbdf7b75d667973a43e8dba80e093759091c33888172289aec698d9cad0b3e785f66d50d0a54568921249f58e985177f57056f58403f238ac4630b55c2dbf68b052d1cfe112632b04b24b750db77977197f3adf88a10f89b6c4a2771e2353eb73e56139b9a0cacaf192610f2abfc4b8f1e3b40e77372cbbdfb27524191adf3ea7789bf671c762ae71fe445017759052295300cac8f056d64fdc23b62e6517e7a1ca7a5a8567202cb1497eb65e5bb0e31e0b5d723101f0e9cb627eff4abca52cf40ef9bb903f44f4ad61e24728d8f196c9bea527765724b3295e5b89c797563c8d804db942c856d22e6c4e6d447494b5312849d3638722edf61e6cbb3d900fb9792619f7c6042cd2e4f1a99b20bd3b56a490542ff7772611dc27603f7f5f82a122bda1576c5b233cd5b81df0ae4c468001b70d7a16472b63ea02b2b7a5b97fbe6c2ff07aeb32d7c695b19f656a77cdf80869eaf2db472fe6242beb9c5e68a4055b462f8aaed16d6d548c6e9049f92afe713b615bdac1314bfbc47724edbb1b69154d266dd6900f324e4a3172b780f71fbfc0270cb0e72bdc66f27c21fca3fc58037a2e114e8d2ba8897709fc51b76b4e1d3c5ce1b327216780e955c32467563f5c638d1e0e90d859a5c2fb214eed3169fd400eb4e49721ba0eb37cad3e8befdfd4e18b8d5306c3925fe9c9498fce35b02b65a2e392308339d419ee8fa6337a932b710ae4d5358539bb083b32deaaf9d509b22540bab0f1df5dd5c08d0c4c7a7ec907b20e7fae8a4d66e7097053708308b88823858905d7b969a88da3baa59fcac19d54303dd6dbaded3a01170647e188a3518409c06589273f3d896f0971cd001a6b05ff4e4c0a19d806f21e1004d07fabc0f885ab353ae9f147f949035c8fed288107a1f4c3544d0d5ef662778f055bd6402aaa962728165939f6396cadccb31b9a407071234b1e2501b28318bb7b5861eadf7325e3cc6373a4be9ae6c4522cea39a280e71d3b8e682d455a130c2770ec01ccadc115986bf308e5f3006baec44e74d4569d07777967a54dcbcaf9bf587147fdc372b67fbaf7dfe6bfb002a63b5dfdf3f7f3e4c582454682941666c0c60561aa23b247248a2e1c6ee09d49432ff226d08027a3114f40cdc10efa1c5370638641a52cc725c0354a38dbbd15889dec6d0f80da5fe61c7cb8a8e2d31d1ab8b5f1ffad272727f1468b48bdcab5e2d2141ce995546926d41e98fb4ac575eb48230fa7f7f2324248b5056845076d5250e63bbdaf544ec28dd268b3ad3d96db4f93f3bbb0645723dec9400ccf74425f6438029b0ed93df5dbba74d9269e6bab6d1c34bf894ce72ed5c047e19fd96f20c1f4efe6ff801e3cdfb38a889b6cbba5092b3c44689ac5b607ccc472c7fb417595b9535670a596d13efd887887bcbd2db8c17bfa750ff6499fbb6891d8b12127fbc7e17342afa2eea286dbc7eabbfa32dfa0d0a068b9a167908264683a8b088f8947e7fddf0fa7d80e3d359bcea53178ebcd5f9be133405e7ae1617ac715f83494964e2139917cda1dc5867aaace7cd9302b0f8be51913752ffc162b7fc7fae262997f9c238089af68ee9f3c4f009f2a6b14fc926f4c107a4a6003f249184f9f6605359e27becca267009d0d363e5ab156835b961b62d720d2350808aded8b19497ac0d48a086266a5ea22b5f344fb9086f84b21a13e07282bd41dda4d872de306fcbf08e3de235bcc3a43ad0d168a1f0fa7c7e07f04810bf7220a3f67c003997c9c71fcee9ec1e77f01902321962ae4ddde1096b0b2f72f23c7180ff7f2607764b330f9174ea983d807fefefd88177660629eb0ce5620b7df606b07cd9c87564a4447886863e6ba23f86c3db5ca8d9a6d6f9e7dd03c7723400248f987700e7666e1534e6d5bed16b396eed079fdb911fdae8d014e6cc221b385c7305312575c6bf022cc0babfb7ec95a04a1b803b9f86e6a3acf643fb72b89be2f9109d309b9c4556d09d47eb02f3cfb95b359f1989dfd0c613a563ad72a81e0c9ff0858137d5e5c0e1c6a7fc2656d101c4c4aa4e77ec5a91127b574d7230695e47f918441eda71f0ef9c57c74ec7dc86874227e6fa4930ecb098b8a812283e83c8b796aaa2cb6e99a1176f41b179066ccac665b5d6a4fa45208dadad022a196eda2a596457f8aeab49e15d15915d3536bb65cf192fbcd657a88c1f7011736f09ac61120a14dbd5d64a55c5356b74c1119b0788e75d4e274a4418fb535dda2ff8e6095689086086ef1d8104a3c25e716c54be929aea039ccd9a4f533d72dd89d16808d83087263e6f240654f66f20085cf393d6a952656ab999040842721c2d0f486653916b6110711b0b20a2e989b66bb71934281933c54d5fe4f98d1cefd749dd3e772b5cc3be0d2625da089a343f9046b5ecee7d9a9c21d7c770b3459b3795db075b5af7924d6769febece534ca39b9f586c908ec4c2543bf4df36680f7e275cc435bb0abdb72ce61f3fa6f9e9efa26c49a2434cff6352570ecec0723388d56ed81c797a28287322bd3f69613de4a622f8dc87ed45aaf07d7188963a603a316168a5c0e6e3168a4f20a8ebc3dff54c71a9e20d78587514bcaaa05372dd3c299f348828a19d6c358889ea66fcb116aca8a5793b68bab7860d7fac8772a607870c27ff1f6e2304148c4424bfffba9e2caf682c8202df25530fddd6bc729fe7214b2f0722199d8f987ee76bf526012de966819ea34929181b1a97600e24ca9c1711b40d32e3112b814956e0d68b45d39243563e6c143b89c74e50724f720cc567d4f8c9bf398d0ef4b115aa000b06246884d15815966ab1de0f806f9f72f3b50aebd69e1568ffa11bdf4263eeac30666786611808e7cb6ce0982d6ac2262be423e39bcc01f12693d16e2ffa171ce04447a96948f6bb0721d11919fd1372af30f3d5b0443acaa51de6c874c66ed83f795864cc6bd733a78aa67dbb7747728eb7a4ec4125205f7b0b0802b286ff37bbe300b51a449309d6b4367c35e35f726f11a5f79ffcabf875e1854632e01f6e897ced5113937a1b231bcd1ed8ca74729676b4c54c58674c8698c6761ebb849cd3ac212c367fb5bca0a8b7ee1c218a7274b8de7f3eee64c34c2ec4e700d596f4d6844d17c45b2c37eda0c97eb62db472c22e90bc9f48a1ab635e8b574b9169cbcff319843f0953b164a9c7acb919f55b57495a9acb4e01468dceb50957e7be3a41697bea305080d98c13c82d89942172d941f9cda7e3fa997bec3fb05764e05e9ad1a6431c8a8081fd2f367b4b0217577f38d8ff74119ceec649a6701a565bf3a6b7848735f8ac292ee3864bcbdb0522a03d5a22af59b26d38296dfb25888d173912910b2362f35ee1a40305049d667257b936a3a36fa705d5f244c6f76a8009e491433c684133c623451ee1d43517728c7e2ac8c3da9778728919733521fe2eb1720ad738f8726b38965b26d45e4159745a520f0610440de69df4a7d8b2ccde24f8d01c77e546b2eeaf61f31da15029e0cc399bad77d4ddf92a53c512670cd88f8062e82d5a1fc0e00e5fd9e4a5c67246306ca0632fe2bed90fdcb6dfa27f5ee497323537264cff9a0e8aac75963c25a5f74e99715349977e85943ddd14a9b0996ab4feb7a53eb222758b6c15ffd072ec926119a3885ae13c3b6dea167b27c31c756598468d4e05038356f05f870772a574954c993cbf187dd3b2460388d79de32c0d9173114941d70399131eedeb723414d44a67b32f334864e56cf8901ad9fa13449ec799b806086a21f46826b472f0479b381675ccc00a78c59c6abc5905e1e33d19f785f09564dc971ebea0a372115bbba2d45bad21af87fdbac61a8b9f145b311866fe8024800b3fd36fc5ea32762f85efd4c985d6d187a0ae909f66c70b0932f0e7a0c0f19af7061f0350c572668ab5c8ffe26f0daf83fc53e644aaa06699b7b86c0537d6616b5cd430301772ff4337801f3af15ac5ed8ee58d730090c493523495911c81de82fb84d3f2b806b0225966428f2f4639ffad3992aa1ef750a2b9501b80002b204b0629d76c3672650700dcb37d4cff7b45e2a29bc03c0c8a4288e5908e245c95f4cd1c0cae0c72a7a93f4bfa7abe01cd1afbb2094eccd80b2e928940f147cb92930e65b4c09372fb89a93c2d71f4a34cd96270751508e392c48a1ba285611254924f74187b9b59c3d2e745b86ccfa5038628ce5505f0882b7394a0cfa6eee91c0c57cef7007932b90d8e73757c0b43010742dad86dd7f57c6c5f8e2d14f8ee82b3144f602945724a4e3790bb82661927a75e5719208bb91c244f82e7ed8daf84f2ca2d08120e72beffac8a772fc2c03355465a8ac463b20395f70d31feb8ec9c91ee1d2b1b28722f5acaa1e1572e17bf2c07dfaa259ea1880eadf2454802a483c52e3a92983672eb6073a5cfec3f97f5e67f9287bf63b61b17e47084de4ff4b4fa4d45f7798f69de41b22cc886b1294b4c04b78b56f0c32439a0a0472a22f08f5c35a2103b123d184d82e548cda51607c9ae900e11beea72da17a199a78bee110ba8d5212b8b7275985072a9acd1e871609b1a3eef7f36e4ffd3255dee7d3128fbcf55260e9d7256d324adb76667a06d33645a220743d1469b6364d5cde37f6c8256f1581b63729bbaf70d93d22896cad7c50fae104c2a664c96a031d7f12297bbcaddf494ce72f6a868efde54cb75dd3a3d90c73d7dc8d2161c7d4f17bb4f221110ae2dd70a5026a135be428a71a65fb01134b0ad4cd41cbd6a7c9fb76f51e78a55efcd310272c80a5b0d2c8afb2dc2c610c3344cc340ec752d6a87b31451270527b5c853167235c5754237a53bf97d86ef42d151f61f4a15b9f352d87062816bc7ac6c17c042c5a520c77c36c38607a8ec20555175cdb005e448ca693b251c1cd32d2ae86472684a98840adbaecc68c0ee1e84e6bf10c380d39969d97659325aa5b685dc6a224ad3be5f0cafb00bda2dc6f3fe2fe349119dbcf1dca4359fa9f0e129f50adb69e7a0136f38aabee18f43d4ddfd8fa0d178ec9b447d750e13da291e578228d8185f7ae3868b3206a308248f8cd7e8f87a1bb240139cf54efdc7fa11976a59bb721e5ea4ac566ca8144d719dfd678ab8cb39b9a3110d4c0096f554409a69e73a723017b293e5bfafee53824f2f553ebdc314dca6b33c43ad5ffa83e26da8f995723f86258c7c76d7f944f86e40f837a7c4f42a32296ee1c6e8cde052e113f1677296a9e387481741061ff3227e519d995dc02a65ac3b7c0c56ad021bbaf5b24e25b2bf7ba62580f21233f609eb1be7efec09d1ad816cd0a02533b5687c95af88725afc86b6f146b01bf07d9c93fca9e89d1cf344810f1679878205612c947e232b273a98020f41db7b008dd6393921ddea69dd55ca90d47984e20fdb0b97b80a728ef80d33be678ea205b0600fc132e597a7378f310bb5f42ab6054cfc11e6307156edb6755ae66dd672d1b5eec733430a518b751bbb89bc3a5c71ad3253e744727827d5c040857c7cdda84c84deb2ffd9182e44416ce41ade3197467a15f85817e292e74d4ef7a53cd48c309257675877c7303b1516e87907bef1b8656ab8b46ea1d19656be048126b6a30eeb7bfe527438f69e35b1f89569972f79962b18f13c1d45a110d86208315b09e20fe67bcd36b2292e96d39ed7ebc23963a4f0dea772f1a62c045956383e3dd8d74f85b6b49e3b587851f260b224c8d6baf10260146f1f3be1e6c8cd931167d87afa6735759c5ab717d86a215066384ae245d930c52392e71f9a806be0349f187cc6ac6915cd3203873dea215abb26dcea14682d883c3747f55dd3354c066dea32ed8a2e83ef46a84af616095011a0288ea8481e815c5c66359ca9e880c334cfe2febc86a55a0641f26018fb3b7a56acdebae30a9e720665017de22d50c61c67b3d5692122738fc0795a40e4e427000ed87142c9c64fcde626f003b22e9c07e50edb33b7a51a2c05cdc79ab33b614369f12c715f8b72c7d48724434f639fc89b4eed2e4dde99a2c5976049950fbb2d7d0e91da664e1058d77f308e978313f5231a27780d815c78fe58ff4e74320b99c6bdd7f68a9272fc8c81101f7d6a2a4000cbf2f4c0cc2e2fa44e08f9c8e063a0b7dd1b41972b72f3e09139291856eea79f4b09ce63116b97e4a094b695a76d80259d5adc89ed6b4b28057fe5a33fc806ce3cb619991c3ff6d1734480e3d2375731b97dd1ff2a72d14539a9cfc59019d02238dacc6c9109ae710f0ad261c39ca72f57ee1c726b0414a6b07efa32d7eb6a19b1b9b5c0718ccf032e592eee1e9a658fc9141d39107241620926bd6ae1bb4846f1669cdbbe12e37e898e118625a4b71d559f14e04072ee3f9fcd9de018aa7d44b13300ae89f971e1c6a86a07f2bb94c2719d7e7500722edaf744fdcc3110050577d3267cc28da4afdb5e877f25b6f13368bb595126720f862f88659e0698626a7002a47f2e43aba7a1637a736802afa37f3f2e5ea772b783b9826ebd73ba9e378237e16110e178854023a19fa9f37ea3493a63622b725be7be0a60ac27bc2d175fa24c981af4dc731cf71899d1ab40d4425dfbfbf072e59fd60fe05da60eb8b23a1fce72db8d6ef6680dc9c7101eea3f0241bf3d9b6ec940668bc78e1f68804d2cbddc9f812d5880155c6deeb0964d9ab1fff11d5c68b975eadf6ed93553d6980eda5d815bd4e1dcebb70856bc4ebcc3b8d04c5f6572ba18bef6a0e55a2ccde5f05fd63bbc393add18b04d6a1b1a69a74201c2f32b7282316c7f364b30ff1168ccfa765d06888591c6c1e5bdf7ff356e400b078613721e68e2c8bfd21321cee22163b9a88e7caf5f5f7079be105864f6a2d8713c590592e257bfd60c9f0302fb2f32137174a5b369d013b427af2f1733477d47ae2d3e13f60ba348da53e7f19b1b556ffc65e034ba1a09bcb1445db5f895d74b6a4f35d63fecfb7bbfa8f82295ed56fffe7d7cd3c9cf980e24b9572e4d07756e19417201c115043d4b4084669211535a00e0fcfe93f11b4098ef537198f28c9a84d172ff6e2c8101049f759963dcedb7363bdd45748a079cb4a7b6dd89cc4d6418f25a5af23d3dbe1b71c1884066b610a39e4c57082756f89c371b7ee2893ea9f530624ce2755da6846c903420c7cce4874d51fd48f8ae3084035f6bdd61b5ff8ba44b7e278ee40f3a5954f84ae0a423569c05e93441c30716da09401bc0adcf97853e32852e63eafe6b5ede25936b10990b7e5c2caa707cce7bb3a85f502f887fb772095c023b6a533fa23a51cecd349852fae93e75fea40f720f2a934bf5c316f572292f149df40898757e215379c2151b500db8af040f16c18d7e19eacd4b0456725aad7368519b9db5ac63028eb299f62b66817a8436e90eca3d363a48c2ef854c8c8162d31b87acd56fe5fd788ca434f5d96d3f51b10828c1f1a8139c08f2227204e39068ef28f47c49d56c9df24b5704c8a3f35c1783d5599bed954205f72372a161ed7a75c97ddd9e48719569c0314a1f5da512b2ff58ba6568549cba65930d728c8c1a41ada4772f64d56846c42cba25706a7205b315f614675be4e0dafe72c708cf384ba25138484b1dd3f73bf23499e302f36e1ee9903689d9148a31ca2efe71e61c9160e4e44d8b28ef587a16bfbc3f830d3dda2083617f26783d36e62c84a1dcd23cf0ef1f9634097c3912a5779f9914c2df27296b9e310487fca0a2722b6c7cc532b02d1c11daf39dadd3ece97be691c605f7de8f4b33ff17b395c3723579ddd719bc6b543d566e09125949fe7943376eb176d02f8bd3e56a20005672b37dac3269f9e6bd523cd6326591893c17a971d8a0075045f326cee598b21672d1fe3c8bc11aac3b3c0650a3fef32811e388b553b9674b81dda72f34103b4072ef9174c37676c93212f557de09f7029471539f6aaf036808732d77e36a40ab72552bd4b1f031efe61d82f8a745b7c8313216aa5a0862291f004ace9231c20d72d48e412396613f77a9c19b1e44b503bb9a86df9871ebe1afd9d8094fcffaff724e10a4234a16e7e3c59c445712f260e12734b36a546521b48d9529d35c52cf6d058af8015791d7904a36b96a6750cbd17f400e99af03f066832447df430ab872c9125648284d15cdc7cf3fbc6e07145f4d7883d294c929eac8b0dc1b762ffb1a878e6c97afafbb485df1f0e29f626eadc1eeed1cd375d496083c80480a77de721b348a74df9f06665fb0420cc115c9c13908f825101476611652a109110cca6c88ba83bbcdecc344f373a4fe210d8764ce29f6d8d952faf47cad41ace14f3572e641f20a88ba4e78ef4f5c392f2ccfccfa0296035d89521b27fb201cd57ee85cf42d405af8da30f99564b8321944e03d8aac3e17c17aa14346e7f69c95a0cc72d93b5fce55b542049b3cf411b672e16ea0d064f093aa4c868141dd4ca5eea572c5d21f04e66167e531082d1a8ca46c06177b4243d0ae8d490986ee5307d5ed72e5cffadf6ca456b643e1106da0ac3cca412cca98ac9a5be6953855466e56c87276dbb2af6aa507115a93e1bc7d6a9cd8b6fac451fc881faf9090e6b5da2c636442ce45953426002a5839c5c9505e08aee8d5e39a5f79d65c6575f538c3786d729e7b5a263c1e28101f8889030e946f19860c3d0ca0e21800967afa972967ab72158384374afaa7c43c452009d0ec4cd6b76fdb284c8264c031a5e520c48515726065132fae0cb92089f7ce0b90429676a363544458e4109d8e266358e830df08872152e7c428908166f40fe0eecdb9cac742a71403d1729ec3eea81c8f51b87233e9c3a0460a094857b3df1bdbfdaa36bc563f4daacf11f9bad184cda991f0728d4d492e3c00233bb1a15fe29e77c2364cdbb52ec2a96ad14211094579a02a38ea27e4950b7f0b7a641b166733256d9f44bd40ea3cb3d9500af021cde29917728523e65a49857e84025aa891ba3b0c10c2a55f33066d7f0663937b69aaa1403101441d5693b57032af2c66943d107d37f8b780824306676b271be23e0cdccd06a59f4b68458f58f6897f50a2b16f59a82fb62c4057f3e27d1371e1859d77b37078d03cd94e83f47247fb777bf6c09bf6c10a587e47b05571d59377a7df701b7229ab49fa44e0acc86258a76f53739f9bfa2257e8eb26b015a0434e99e09d81242c089f244bb037ad36121f594020cc42ddb3df1917b1d548e3195a5ce43c6f0f7f18ff87d266b324b16f090bd206e79378daf07fa1d13c9dc7a6496a00efb9301e1de981e8a02915d6b2c0fdbb3a73eaf74fa753ed32b0f17b5745668545a0725932aea9cba3a423d702e6700dc057f04773cb318901d2259047ebd3c5fe58722c8baaebdbc89348d49c84494f3f67aeb9432682bef1f1af969032c703a34c2adf49990255c6de73d2faa0c165ffa2ed4399ceaa58127621b7d532d713eb417229c29a406ad2b1d5876ba6970790acfa199a0c29bb51f2a04d54cc8d20882972caafa06a6d26adf367bd862e41cc6d189088fb9054a176d080c052742296f1728d59979377c49486f8e53eaa32b7d7e09aa6a6089577889c234d2723060b6316ae31af4971786cbac2345992943010379a8c9d04106283ec1ffb20bda6f04072bf8b0981b25d30c7e496eaaa225dce03759a649bedcf346964904a3f56454c725f76bded4ac64aa5c632054f1c643415a4b961e465ac371b35327129e1981b19cae94e721f27a5b7a139d2d1922c7319f92672ea19c782995074f55c3a8d5272b5feb97fc5ca993d80507254e9a8c5098007d037595116200d72390a349dde72ebba66997a574cff2777db64d3bac927c073b8c6adbc677d1601415cc112db728634dcb507abb96e36ad83939ba3eb4e40ebf259ff3c56330e77efa4917c0272b5fd3ac61b1dcb77d250aaa735292cbc44fc72804989e20a6e88e833b52f3e72005e4a4d4e80b387f05f10f24d817be68d45be68cd13da25bb1e6578645e5372b8686c8952584d0c22fe0389518f297269ee0bd15fe048eaf29f8831f1187b112b81f36c9ec9cb4cf5f915bfa58e942426351ce3d7267a10d7771b1c047d8e72bc7fcfcca09bbf150aeca39fc3aff350fbede850265aef950ec60966b7db0024d1fa94e765e02fcaf5384d2d4744a66910ef84fc19442192101f6ca92a5d3a706cf6a65eb98e5e3d877a55e021a7dd5b9aadb65c98f1266986cfda27e70cd858ae1ec36a2e9cb1aa31a7fa475fd71e4cfce79bd71f762ea3d7c4c3c1972d7372e2ad1ee7908aacc3d348fc822168bf095f17caf6e7155f2f563bf328000dc66e19d838fb1107e4ac8d915dfc83b545b564eb692d4fce9abf27d0af6c2e22de724dd5491700f462567c9c768d6a78c9e5fa3cdd015fa5dad271baede3fc40e15ea2b2fa6d2ce5132362b0914697496591895d85f4d356b04809437156b1f35a72b87f1bf8c90202f24c63bd7671ef5fe4f0edecc1b256dcf15c15b7dcd5d6cf7257e0e6f815f98366f7144c9ed0b1546d9d4125b1f5f4feb1dd0f778e0fa28a72c1f30f2c3c5bbcdc7bcca2eeaf0e275775e96dea71d9e936b3cd142ec8cdd572130aaf213979bbdfeff636866ae7f68969a77d4f5e870717278df091041ed444954507e3dc80f98c027fd50c7ff941285436522379d2435c4680f87ab5e35018fe2804f2ee4380e39b095b7fd12fea68799d9d788bae0c4fc86e43c6dc2b8a72d054dcf8ea473623884c8a1da482af1310a5667442e2ac6dc39c1df0aef2e06cf5ce680d9276b77c8945b3ecbf51005fb32ee443d756d49ec532fa75cd8f1a723653f7127cc797d536feff54a123a78d6f58505e7a6bd33e29095f70202c393e9be122748d7a32a47e1560d971db0c999ac705f108bdc021b6f2f18b21ebad33fca596031e85156c94c2350cb449ad33aa9c128f2b06ef6e0b3564a81b10577205e7a57b7ac2fec50b338a7bb637cb8dd40f152537f308b002a7311754e3ca728f3cb12e2cbb364278c780ab09475df7a73afc660aedb9b6450c7c46abb96d729fd282d0dd2112ac8e910c670796aeba004c2b0e911114e7991b42c589604772a1b3e1306968becfb4dcbaff45f37f34529c0aedc22bb2d1d6b6514ed589a264bf2f90bb40247648389c79be5aab805b9fc0860511da430c5db8a215520dfa729e6d7827fdaa8ca1a1d9aeb10390df306917e3a886b5c01c012108d44c713872ff0b6db4b4db5311653fa661d863f0094c964c61d28fb87505e0e568b792d7568370a7d5d8308d48ee8734081109274a1d55bf055c22f9d8adf3da7c49809072ac8f02185c09fd566668f9e3ccac8994b631dd555e626fbc209cae0af622817206dd7e30ace2c8ab752612733bd5f2832b9639d9ed27dc799f59436f87d617240536ddac5f6d307bccd728b2c0f3b26e23498ae082370c99a57a3d8c61e30e72d9b20b1fe7f3d1876a6b50d012c5783e00ee596f4c957d9f42884a04ed2fe0048fc1a59439f0601ed64281f57a3f9f8869fa241d2f53fff246a6ce5acec62672d84bf8925eafb7036a33d5ce449323d9e08481b27c00ec0feca03c5d8254df7276216aac522ff8f92496532deb6224484756c87b30269af95fd1522507bb7d7229ecde52ac3ba3b276567ab827972f32cb6b803a52f0e1e6204971bba68b0b0817736d3ec75628250b489e4a965e115394b4d6ab07424625a1a85ea7fd13f52f90526d72d83b4339e1aec34141e2a509b21fd747e1112eb5120433ba5d2c557215024ab7ad6cb0fe46e4866648d9e5a59c507bd4c16e99747e3e50c980b32930862ad4385d29d8282cdf62a15261ebdefab486c2cdf5f432f4ff2c0b3af56f1af01fbb98348410506059bc9d7b10ce2e8d80cf93b93feb94acb1926051d65f7226229fd3d22ee1678b0edf36ec5020f66df97932f5b9c3b9060602eb743049491e50fb0f239233a6bbc3931a5c8495769ccf5b03d057e406ff5060a2bddc2c72d1033bbb9fac88be95f472f2f1aa513798a08aeebbd7532fb9ce6ef4c90b2e4abc323c2ca11dd6ef606d49dd8e4e4b81a704981281a81bee0cb09db06a919972c0107df29ca5120d9781a5179786f682c05494c306ae977e7e81bc6f4d78e772b0c743e7f323df7c74b651fb52925f7078a723dd21f27fc7b52f59783cba5e67ca7cd886db62b6cf3154d0047f920ec5cfbb69f37c4cfdd18fa81dd637f2562e53a7d8a28cb81cf7515f17d774d6908916bcfcc40524be7b67a962e36c01d8725181c1623c226b3e24bb4a782d957b80180063aeb39bd4baefc00c75ac1168722413b9761a626e5b3329aeb74739f52380aaf0fe815635689a4f1145de894420c6dfc002499e57bc7045f26443678feec05fa4d48413993686bb19b5821ebd68abaaa1294397ae4104462e85b446df55d7ddfc6d84d8c12b63e4df5a88a70472cbd9ef22140d158525590ff9414994c90081b1a8b16889ac778d2f8be013d119f5e60abedfd5d116a0472b27087d1f32b4e6c2fbeee82acf947f2b4317b15c7242e268d2da5d4fe7dc0285b4aeedb5d545895d2cdee5c9690fb37f712c12314fc994a8b53241d2795f6590ffae3f9f6725ce04e76358ccd3e7a66ca8fe9702687eae86a588675de0941a7a8746d97d5e0aa6f967f6ae83f38db22a3640d654692ed7cb97db881ed7f9593ab8a5d60f785e5f1b0f1250104021bed0e8c84290726979e32e4fc42b72971665d7993956557fb17633d4b05d74dfd5f80c31ef7472db1a64b1399e86111a4e0877711d782b12aa523e2beca20a7ca5f624333f5953f97d1f0cf822f8a296ed97a2bc411f1acf5d0c936813ed2283404b3eb766a972cc4069b8e74f46693794b49da37ea2f5c934f24b46f6eb0c8b6de04af1d364727aff6d6d162d86093b194e88b09164fbf393a4f92d1bab9b267547cab52e080df160230f8e46351961472b66d68d6a19940ecc06e9c73b6a2593d099560a1622fa2f67038caf87a30e7c50596a6b1bfc777b678673f0aa5876d3b70b4529374bee952393fc97a8b90d2dad0f00dd7cbd000c62bc02031df68401988c1a822c72f8ac7ff12d89d6822def778ed37223a85bd8d5bc0821a62d38edfee10a2b3272ff7b0b2e5adb48c79ffe6b3551beac7e6fb3a564c6629a8262e25e2ffbc72a72ef3be099c0b0c2e18573273b6da400788ee9bb1e6c3e33be2b6c0f7f3b22d230b0f2b7f1a4ab0874a8dd92976d2c1d07084ae4450eaff525528d5272ece51872d28eb11e4f40a7c14af6bfb4c1bfa1c185a39a7f7181c02b062bfd331891843f4a8a2c3303fbce3864ed652214e3b6754b779c63065672b40e041d79f85c7b72f6ee423a132994c586109599ebab8e074d5c34c8bd3cb97467241f6758caab72d5d314060c719acc100948f3e1f99dc0b7aff18e7b4c88e4268bc9083d00ed2fe652d68d9a4de39f2fe13064f028bdaa92bd08b1e04fa6f5de532fb3389ff4728a90eb457f06735d94b93b810c1292e9051a3bbcb2469c889a8e0e058da027289656ab145afef3fec6e5c1deb14f38d9244fb55b8db1b2c19d6be88598e813723a31773909193d5fae561d4e03030104584e23c20ae6dfc65ab6bbb4c3e6da71b8d5c1c2718e5aa35e3e5a8650664001dcee5a1b54b170ef26d7594548678672a1b3b88d1d063e4baf2ebe8463c9a4767e8d5bba91e7d10b036a917d475e5f722784337d30c2d2ffdf3d4342a0629b411cbd906735382b140f85824742e2283c583d3b2940898f3b31f7b2150961b3a1b42e5ed0ccdb49ae26f63c9c684bff0cc28b356419b0a97d6f65a64b9f902b401aff237f28bac5e96b595d4a85d51f5546783082895617b81c365ae3ff7085f9df2c1e08df0391ae6214c7d8d59588651daf24f624d58fad75fd98144507be516d055f88609d4343587b710eda0ed627b2eeb59ccce8e0e845210c8846af4e32e8c93f15bc2b6e9defadfb453eb98e42182efebe484ac37d7084cafa4b0c51b4577a5e751f7c19ea1f0af172a5bf9632bb65c5eac55fc01c6fbd73f8ef9101fc9078216745b544e7e3c158c3af8156721c2bcea8794a2e0b9ac3e35e09faf40577fbf606c99540316c471a3f7e40bc722ff33fb15d66ba1162002c00a3480bc45b067a2b08cca0f2887538c22738373961479bc48607401e5e46d289cd8439cbf82ac61bb5e039f3a41c3cf846b6a029310baad627594c10ae56752a9f8e9fd72127b0b3e8ba098f2a8dd9430c31a6723bb5efa7075779f98b06d8e33859be1d1c4638054152dba7d49beb4ac1321b72a554c7069c03331c1d360a42e538374a283a8d70a74c39635f2254098d9a4c4c1ba37b4ff816145318393c02a5fe982fe76545ef29d0a64db1094b0556509972a112e9a1fdac951fb5625102dd59bca860706c453971d13c18995be318199a72db8efaf6a86eea87556e3ad93afc4146ecc93ebd636fdd7bc5209ed1dbf0014fc64f4b9d5a01ce90f55fd6235f91c0802ec6b4867cb79979c17e6d644aa4880c0d4f5bd57f3c3cb1ba7d76b4555458da4e8288812fa7e7747e2e9ae769b16b7266b13512d3835cf5fef48f9f6f7cf85784ef53ccff55fbacf14763e238d09172e228cf99cfae161573aeeb9b68325056d62ecf8b1156c5d96ea730598845ea425e3f0b7e67790ad2a7ce8c80ceb19ac7e02851d7969e93683d7722ed9b80253b43c5ff45532cae37a59897e147386aa222e552111485c67d464daf71b4480b72d2b9d5032da176dfcde2237c8ab4a7717dfc345e6470ce56a59032626675b01c225295dcdcd0950dab55ff3583c634d9be91bb816001cffa9d116278377a4072118fafbfdfcb3ec5b783f7da4205f995f45cce00279e6be70c1998056abc492d6db3102b38de41e9d7249f9f280302283c341929b724da73a6d774119d2ae97220554808d6586bfc04e33efc02325a13336d151ac41c1bff1d15e92ee5ac8b11bb5a2234260ddf48f6553597da6adf33b46ef49e2d0db6e87da87837c23a537294034e9d458e1190664cd36b2568f6b89641511888cec91ff45cd29ce26fd9726ab7fc7a2b2b404006762b88dfd34575221aa832d7b639ea50857cdac0730972ba1d203003652a1637f3a5dd12eea3204d9dcfc4833bd4c12bcfb5fd7e6e6249177c9c45d1c1b5f142699c660c82393b07c1e50f3e7b4c3defb98ee9a0d756491d0fc8c7fb95e4a480858025b496f1287852a1cf879cefb05e7d939464f5ef72af5bb0c050c66d84ac479ec2affc80a12933a32316108fedf4109cd71acfed72c7f0e661d0651fcfbd6105c921fb06222dfd5303e3bc0204bd8c1b22ffb47f72292335eae3b35172c460264966264d341611274db8edca296667d14cf5565072940bbd1bb2eac0b9c01a98f1d1ec070f768974ae5ea1796bd881824138f09772e6ee5066607e0fb14af29423cf3e3a8033e6b54290ca294396f6255880df0772bda81d8fbb8f2b2ec38085d7effd8b759150d066c02943c775610323e394ac726843bcb4a963d8aceb3ffe99a126a55244c0c2bec86b59a1c7fc66bdc48c401b84e419d02ef455c77ec999787c2859dd1e86f192c2d7c97f994239fe27b7df3eb5edc2aa31fc58147c24c33b9b5bbb2f53aee12a4ef8a0d7066ddcda979e807259886c585267575fb3d9fab7918479c2ef05aaa4f6219b2be00d3647efb81572e0245c952ee1d527ccf0512045f15522f47576b97afb132da622193756dd89727d52f661f38b9ccdfd82ab85091692e1620c70eaf47cfd0eea180111d31a3e490b324ae24779ad5521d274361940114ab09c34cde2832933ca458e6c1de2c6123270a8eee699ab4c423118398c9d305fef238e5fb0f170d515ee33b360824b20ce72ed8290829f81fa214857c4d6ed50464688d2dd62cf2727f245b3b75db372b060ab333dace997aca55a4b9491a5507376ce2f69956a49c2c62a0d2e6a19088fabb580b03b90f78f0badac26c92253ddc0b17a55c2dc4817d1a40706c78ff90126b0aebd8cba8381cd182dba9c76ea98d6f1eaff65c8544f328ea4098acd486fde47aaf3131277149162e260d00c83591108b08d49cc9252a7262aa9cf21f4d432a83bf3171036bac2fcda2a54bb444824348826f9f52a4e6e09e2bff56484fd5f5891b0a1e1be5395b1cec763208be13ce46e3bf208570b8f8511671928b2f0d78cd30cc176b514a8318aa73ac13bdceaa059d9b0ac44684979f60b9af0c760917fa0aff5cd468046519edb1404c4ab150455122d0867c8268b4f5bbf7f10693bb1359f2ab08224305ca781780717df4a96c3916e8e4836248e68a27a4b1d58ed4598cc5a826e74ad908e3ab12890dad8da2fba72fbb0ad682545dbb2be7c678824610a3100f94c1b8abbf6bf6c0bd3569b2390af96fe2d2e10cbb6671da7e398f114206c1845f90126b0869faf0b0efe0fb1c02d33a9bf3b073d9616b340514350c97a21e3f0f8f262345e907249262e0474d995584aef1412d9b09589b94af6844b6ba2a20417a748c3018829fc43f08c9a66f3edfc9d68fdd791f05ff52b46517d3ef78c99b8ce7d8ee5b0b82e69ca2c4428f63cc3ff7b477287721850e1c34d4234897da9a42a882f246d53faf3a6550bdc250b0d26e78b069752b08732703d9ac844b5fba917cf9d2e428183123d6b3ca37d6167894e226803afe60e361614b8801ec29a6f49a82ec5963cb081d3766a9cd9124bb683b27cfd9948b814729faf7b4e47014f7c9b1b1060f669d02e7af44ab8e9b1c648708f682eaaf0b0b62ddff7ba83cda99d4f2e7cd27c71d52a1e52b46fbf6e22d9a62799314353b5a48e4f7f0e03ad6ed1aea942d2a09b4a"]} -<< (9ee7c86c) {"jsonrpc":"2.0","id":29,"result":"0x6e02c51300e038ac45d44ed794c4c5d07a20f9e6f2eb3ce8135aad736e5bb1a8"} ->> (9ee7c86c) {"jsonrpc":"2.0","id":30,"method":"eth_getTransactionByHash","params":["0x6e02c51300e038ac45d44ed794c4c5d07a20f9e6f2eb3ce8135aad736e5bb1a8"]} -<< (9ee7c86c) {"jsonrpc":"2.0","id":30,"result":{"blockHash":null,"blockNumber":null,"from":"0xcf49fda3be353c69b41ed96333cd24302da4556f","gas":"0x186a0","gasPrice":"0x6fc23ac00","maxPriorityFeePerGas":"0x3b9aca00","maxFeePerGas":"0x6fc23ac00","maxFeePerBlobGas":"0x1","hash":"0x6e02c51300e038ac45d44ed794c4c5d07a20f9e6f2eb3ce8135aad736e5bb1a8","input":"0x","nonce":"0x4","to":"0x0000000000000000000000000000000000000100","transactionIndex":null,"value":"0x0","v":"0x0","r":"0x369cd08bf56b9421dba408042ef493567a8a403c115b1a5d62f5f60143d8e3ff","s":"0x5ffa00eda1cb30fab5795b2395a4846144372853155990cf45f9fe78f5d120b7","blobVersionedHashes":["0x010cb2e32661ba3842016565ffa890e793906f85cc6ad70294dcaa5fd6e2f9b9","0x011f7388728abafddc7e1e4b724674d0ba7ad24ad8dd9b2398ded47b97ad6252","0x011e5f0b6eb068cdbdcd1df7ae20fce09a3b7ad23c53f6e0c2a36614e42aa515","0x01fe604cdf7bb13bd6207a3b73d10acd94813c7d0c9fe475006f9bdf2593d30f","0x0113e7b8417c5c57e782c7b643ec9483cd6d8d852739cd4843e690ef097ed984","0x01a592db705852ea7c9a6ffc914c4ed81b5ef755378a21c76f5123ee0c8a6e59"],"accessList":[],"chainId":"0x7","publicKey":"0x95a6357daf5d9f91c85bd4e1f8b6226cb18396d772c35620d071660400a543d8b51f13cf95d7191e958a12c6109357a4e1e50eecd92db513dab323a5c1fe7ff6","raw":"0x03f901350704843b9aca008506fc23ac00830186a09400000000000000000000000000000000000001008080c001f8c6a0010cb2e32661ba3842016565ffa890e793906f85cc6ad70294dcaa5fd6e2f9b9a0011f7388728abafddc7e1e4b724674d0ba7ad24ad8dd9b2398ded47b97ad6252a0011e5f0b6eb068cdbdcd1df7ae20fce09a3b7ad23c53f6e0c2a36614e42aa515a001fe604cdf7bb13bd6207a3b73d10acd94813c7d0c9fe475006f9bdf2593d30fa00113e7b8417c5c57e782c7b643ec9483cd6d8d852739cd4843e690ef097ed984a001a592db705852ea7c9a6ffc914c4ed81b5ef755378a21c76f5123ee0c8a6e5980a0369cd08bf56b9421dba408042ef493567a8a403c115b1a5d62f5f60143d8e3ffa05ffa00eda1cb30fab5795b2395a4846144372853155990cf45f9fe78f5d120b7","type":"0x3"}} -INFO: Sent blob transaction: 0x6e02c51300e038ac45d44ed794c4c5d07a20f9e6f2eb3ce8135aad736e5bb1a8 ->> (9ee7c86c) {"jsonrpc":"2.0","id":31,"method":"eth_getBlockByNumber","params":["latest",false]} -<< (9ee7c86c) {"jsonrpc":"2.0","id":31,"result":{"number":"0x2","hash":"0x90c8b9e1e8e51584453f5516036bfe0031ebb595e47c5fd664bd56471ce618d6","mixHash":"0xf8caa5bee858bdf1581f3920c0a700cd25923f194fdafa96e47fb2198779c608","parentHash":"0xf9e9afb4b42515283ae292d4f9982cb0c87d2338c717af437fe39175fd5e5fae","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0xd51ab9181e964dcec962295995b1fff437d9504a88ab944324bc1227c0c94bc2","stateRoot":"0x724b6cd36d03f71b4c088b69f1db0a61b3d9789546c24ef32d59f1dc2007041f","receiptsRoot":"0xd50521034c860197d235df5876ea04b9bce05f69b7e89b96e597d9f6d35b1492","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","baseFeePerGas":"0x2da282a8","size":"0x408","gasLimit":"0x2ff7d8","gasUsed":"0x17a25","timestamp":"0x1236","uncles":[],"transactions":["0xd886baa4d7824402a508487d94b8efed257832170ecb5f5a85b8d2e15317728c","0x3cc701f8f4e4c7d32e1a55dacbf4175dd4a61b4b8f26be373b8f7b0bb4c430e5","0x6208c8da6ad2d0b72a65110f972a38861ab4d12a45397dc6026d07e5623f748b"],"withdrawalsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","withdrawals":[],"blobGasUsed":"0x60000","excessBlobGas":"0x0","parentBeaconBlockRoot":"0x062367f0b23e2d49ad5e770d9ad17b83c0c1c625c3f9a290cd9572b3fc6cfc9e"}} ->> (9ee7c86c) {"jsonrpc":"2.0","id":32,"method":"eth_sendRawTransaction","params":["0x03fa0c03a6f901350705843b9aca008506fc23ac00830186a09400000000000000000000000000000000000001008080c001f8c6a0018c91526ec9789a35b497c06e3317c1a921b86082300d651ad001a09d1579a8a00117e079f225ef61b292ffb0036ef8ea4efaae2c19ada01af7a58e52a0a22574a0018d313f88d431d908dd44ff99ef33d3a1e86e55f4202643e155caea72c9fc9ca00113ad8a86ce2aeab9516290f52635be56d2a3dc23dce048ccae683bdfec0b9fa001c150e6fb99dfb0b614a16e2a087a72d47d1dcfe3a3fa070604bf4bee711229a0015a7c4688fc9d07fc303b466f606f214c147050acfa852ee6d292f33085d51e80a0c5197c7a0591012f96eaf419db70d3a9fbcb7b461e8b3e844ab7b1accfa7142fa0241efb3a03f433cbcbed1cc111dfce53e88a48563adf2281d4d9789f088ad73afa0c0018ba020000726c57014a6156061ae669809ec5d735e484e8fcfd540e110c9b04f84c0b450472552e1bf83f647f1a1212fee8821e4bbd0088d1891a673ff9056bd06d59db277231f54e6b448098b3d4cf7b6d49d3dec758a0d5126d6d6ed99331fd6118d4b71a2ca06c8cb6c18f1e9e6308cd4a1ddb820066dbb76910527428d96ef0e3b83972e8ef7b932f4d0050168e62617c9fdc1ee56981f78c2159ff0163d0eefcb14472b42ba677351a945a0dbe5661c7ddcc06e08f2f79832b116a6163098a437f55722e0912679521713879d30685adeed62c9fdcbd85dbb80e31d93649b83fe4d2723c710bfe509756ad8006396f03e09d1cb33f94ab02d1ee77f79204b103d97b6414d0415a0678f7c7ad9516ba535265d16f728e786c771ca0fd33016ead204e65b951fee5d43eb696bbda821cd50e9c160746b21c1dff69fed6441b50b6f08372d4373a93a1097ae632e8bb8173c3a5b5cdc2eb8f904d4169a2c442be02026e72d252f2d0b4f20efa3bb9e06ed0f967cf6c3124fc8978777e41e9760b5258ef72483b41983677519cc23769a4b142d8579c6c20c3ba1bb9f055ed22b5bb94264b6e9d793d0db6d5a1774bb427c21223bcfac447fbbfb2cef4344771e69f679d7255b9aa6d39c3d779607fb8d85b7f536a30bfffce78781dff6562838a3e01a772fd1c8b7bafdf0ccb99e530401f957796453d0b256aa807c1901105c0fc4ca072e59512f81378d145f85ad783a5d3ddf7e8ac869b2893ca940ee4126524ac7f720e60e55630ca467232826d0aaeb8f9c6248808a920ce0c7d57714057f6e99f72a7652afcca1e86a0055751bfe727b52378ea365a4a00f277ba9fd5442262af72093f45dc8cf7fc05faf3a3a8dbdce957cdf1b17d04ff28d869b70a1cc52fbf726a586103413c97000c4f019c2bcef5c50c1af60e4c142d23b45fb1e41384b2720173fc34a5a1629c02f8d2519e9d896ee9af9256232ffff9e641eb45983894726481c6eb661ddb29cfd02fc13c317087c7165578fd0a766a643f1a343ca2166b838ae73b5215b5818e830c2d310b03c58c4e1a2835245e9d6f0b425aa3af3a3c0f847a8229cda58ca8454c01bd2ae9cbc8174fc897caf1cd4bfd20ef9015ee7219313bd48daecdd96d27043968708690ace0ed8c02750c620bc6b1222601502e561464d6521e72587a30b85663da248c0c92f2e2d552dfc00035dc0bc2e60b7200b495a75e29c92ace184f625a733072a366582532ce708bbd5790825dabd9726964e016b5693c15a56520754f1fdc56af5957bfaa8c20198cde6f5c7701b3722a49eafa61fe9f73ecf99b9c138f0dc739596b0cbb3980b3bc2aac77b0ee3672367cabadaefe4dd07f2533ebb8bec465f3c08c7d9a87b1f80d2cb3e4569f65723cafb0c57a4d9bc1977a21fd186f6d3368b93d13816501a156a3dc3ae87ebf26a4f7c2e0bfa6cfa451d35b95bf719d97ed4be5e0a0b3b26940093117098bc77207f2eb7286d7f5529ff30f1054f3cf2faf6891ce4c35eb9885b1f4a67cdb053cbfa5bdeafe6327910e6a42f75197f3e8d865a2b25bd4d8849fe9c3b094e1c1728ec3a28c4ba7ef5a890eefc1a1131f5c94fccdd3c430d0dcb9fa9f90e3be3e1ae19ab727f9a16363e99267494a8ae115bd3f67cd73f866538360481b567b2272bb7a92aa7da78678515cdec2224a2d8a5ac648720e88b6a4d5c94413d7e189727a4c8b38303a1bb185613f18aa5669dca75106bec47529a9e97d88330c9a07728b4209b1ba2ed7fc0b6ed7b301b020466b00bc35c8cd4797565e7dbfd43b91723729de87dbcfd0ef494358d5da173f870f7251c6347150a76570a411e824a8726cd61e76740641d904892de44dab9989e0217e587931ff25b35339c3da61fa32895c7f5214ca3682817a62aa4bec1018dd66ffa78e49ea4212b6e5a401fbec0ffb26b6ac626f2ec11e595cbb2e10bb2f7d39932bce52e3c7be570e501efc6a72aede89caadb2b01f761c340b177e23b93129d34ccdf50582aa4ffe8b6f049f018beb98b1d88d57ed38754a388c7efcb7d7aee083dd005dd5328edd1361d00f2efff0720691667c879c29065eaac3f545575e3e32d14a93bf49a5c4b30a5bcc722f65aea3a7e150c3927b6e559992d5464ffd4faca5f53ef5ee26fd5e8776d3729f58561dfa9d1b27cc681daa67a24a0f3885c405cbaa5582f618e221889b3d7292090684a2f08e222cf761ed49ab5e3946fb2344cc1754497fbefe65f98a71720cd6dd3760ea27aec1136f88add4634e4663a10f3873bb685e20966430c556348de85e068deea192473b7694015d7886baa458a2ff471760652ff971d4e93605b66ff7db94629181008269994d782d336d8084da76f415ed44c1ea42f4ffb872a67b1f122ffb0fb6f1843374d31617ba82ab8e62e8d60b7efd461e3df98cfd72f8e0e5b16908210abbf4adb91735d07eb26d0791e25a3e44038e5dda7e3d3d7228d1129da4656f2a3f2e453367f599a91336a4c7fcaec9a78451e7c610e5cf168c5d1fcf4d7490f79ce016b7309e20c2f3de5051d94e9c069dc5ecc2b78f8f725f59c29c496a00ca436c8e67c63672d02a5379e600b78345172b517a5d74097262d39aedf5398f8830c7e65768bf1c9b521b86ce44720a6f3ec712c428a99c11e0cf071fb7e1cae95680f07f0856043770875712f28c18455c8fc802f2dcae72763cb66479a54e1d7d2547b3923c0bd190a81a07fb2358a881d9b671725e47726da3e92efc2262bb191b33ede94070846c1f2e576c046ec86834522c186daf139dccba9eee4caa7e3d7543de9c8f686fdcf0270041de86cd5532c6fd3fefe472609b9e1042fe777512349d91c06bbccb3aac77d451ad9f933fb65bfc65c8f630840deac2f036a17e1d5542047b6537eb78e5c2537646ea54d01fd24d263c4272f4de8205d0ff5f6431e068170a84ef2f7bef5aa076364ae0e8af3e71e67ab072048deb7796740975eaff5de4c2bd1b51cd4d3d13745b18d1081fc82a17013872a75a21b8136c7829ec5f5b661905352d65572d87ce196c087048fe70ea20b372339b3b368e9351fe9d54b66bddaf1295080e1552a276aa1c079a1eb04ebf2c16479de53ef7ad1bee6c12597f016ac7daf8476f2d9b3b273841d1775dd11daa72a5bbabbafb21e31ccdd786e5745b1784b56dbffc4e9a3453cbd4203d52bc8f721237e21fec7fe382b429aaef17351d0a7e50f0eadeb19b2a81060f004e789672113baf2eaf9ad9202dfcf0ad0a8e75d69d6471817a57eb4a1f1a88d2aba74572a0d197aad007b6d42c3b528b640c0dfe678187bbeb0a61fee98aab94eb20ee72805af81e50ac4b2d6377e12bf32e65b1b858d020c8edb20e4b80c9ea5747407223923859d3c6fa449b80db87698fb59576ad81ba6306c753f5e81ca7b6575d72383680d7eaa8b7c85c50669c75526cfa8101439a8ce30ce7769356f12aec232b599c15d33f9fc823a08472a4185f56fbf9ece8d326c49214af117c94d56e3d72dd22b51bc72ae80c82e0c6e5a763d08c696ac47c02aabf9f157c7ec8b9ff4572ef0afe85ea2bf55d5c38ada973e3707cf4346606803eddd76b14c0c49c893a72b78fa57b9ec6ead99fddc3ef629ddb14f7acb8c9d4a41845e4ae2212a067c27217e368672a11efd1200087392dd862ea8c67b79658c64c8b3a9233f7b8008472026de921e8bf6f8f9ecdb5b2303787aa01c6742eb98c518a2b3a528336aa571526af4e502feae4a69258445650fe3381eeb373f1fb4fdae09fcdeaa970409072c8b0eaa6be62288090d96e171c39026d56bf493891bdacfac6cc04203d8953432a4c1fbe966f5c43c5c043c358714cfa0283be41d10446220c957588503e6c721d42d0ef04ef016f85188ff70c5f8a69864002e90d98344ab42dc3d444454c72c47e3cba43ef32675cffdd883494c6fc4671a3b99d22525617ebc5ccb76ac21ec3c8db836d37890a549f490a0081f9f77d2ddfae2c2df379a1ca751837d601502917747f9a63051b92b661b67fe4a01e3bc80c1a1c323e5eebf436bd15d12d724bc3206dcf16ece8b26266fad255f2516e480e1fcaa0bc7ffb659107e388e17298dd6aa3fb717af99c4b6cf19f68def7cb84087820443bc239f4fa3c61d9b2729548f2272180093b724c8d9621cd254a5a7812f97056b04cf58ef8a6185b8e72341379b1e9aca803e31c06441df43ef719dfd1125086127431e1b61487fad572602514af1feb6cdce6c39c9ca899c43eaf8e1b8481dbee10181730f5d42b1f72e53c82d6cc56572c13a676a1798d1362a2952c0bf216f54f4c1f6cf1fc814e720e2d1a4b9f7884449b405044d90f3988aebd62604b116fa39efccc90e8d77d72c3a1c08414812f7907c6a1ef48b8bc0b4a44fedcb212c7fd39166f80d34efa72bda6430e055e91e25c32e2a7e11b54b5d1d9a8f85281e986072ad7c1ee2d7072026852a49590a82582fa86dc6897ed2140a974d5ece9525e78e02c455aa03172c6d24f21d49b2c8d411335d61c26246ae9897266797f01638cee3eb10e0ae772396c6f1badfc7dfc1ea42046a38832c77054c10668a117bb623cff203ef40741ffe9f4fd37e92d96396ed7342da00d605e2f5ec7906927916e4815fb8f7a8b551378a132071910d79a12887c23bd57671e311dc52b5a2ebf5d229172d8a133492e75f15a0f20e6dea2bbd318f2cb9edaa14e8b5d3f397d8a7e416ba61a232072d0fe3914b9a433527b5dcdb758971b7a9ec0af97c6c19df410f5e92e0155aa729a7ca5144fc979062cca33f73c8f624e74c1338f13282ff9d11f528739630572e6b416dfd038e568fc76e309675d273ba98650a5b57fe55ae41dc74cd16b8f1394cc6e443079ce219c9800ccc3f9c6e5b3015733bc7da851e2d482202164d47281ca0c00dc4fa687f62cacff0a836d3440f0ca2aea2b21d68ae3743e54a45448bbc53a13b8bb0e0e181ce564349b2da175da7dda974641eb91aa8f31b695d2724dba39d5e73d0f020b2dc05e2cef6cfe5951ec38c80d5d71bb07da6849277901cbc6b22f1ee990669b3a1639ab632c184ada16be0e17297aaf898b3e0167a41cb015c987062f7945e9808bf7f0245782f6c589545b0a6555537c322984ea8d60b3779e3b25a6ecb66cb7a73ac535ed5965dfb98bb701eb4321319cfc5dfda60f6ef12d8054cab73d691f8a81b02eac02a705518163ed6e124b34e1b0d00bc172dbe2c756cba6c861e6e136e4c4483b1ac460045b9733cb89fc98c4bc0d75c06cb60fa28f5c539635134e3ed9816d3f4d1e320cdb75fc93aa8b50469c980841038e456825ccb37daac63983dd463008375c18d46bc0dcd118e142d8fb860e91723a29c9689b1eee9b718409c01c7ff122fb54f9620be569c6879e281e04d883724bec368e70c53b760a766fb079a55df0f96189a939864006347ff5a8f06a86685c3299eac0c77121ff2eccf128e399bf7630da9c38ab179b546602b48678d872a627ee74e5c921b955f6ee42e1ae0608a17a9d7cb87fed18080ae12e2d419e6376b502ee042abb5f49de931210ede4dbd888efe7fba2ad63356167e27a030b72bde7a01744e5c50bbd68c3f97a49d815bc865e286baa171916a49622eb48de72e146bb531fac8409f6c1b569345b7598c88a58504b607181272e88d0228def72900ea69830e6d3115c1bf1deb9782a15dc7d71147aff99044b40bb9bafed2d612d9fcc142dcc6064b7a10ab20809bebaff19a40b7f8492e8b19a8fdf4b702a725d62dc7a25c23e3197c811ea12b3ab33d5b6cc1ea43dc30da369f6225ad31b6c3c3ad99b828dba3febd7b4433ef7a66a3a6bbcd5eb8dff74cacf5e8e1e41b172683d2d4eea1240cef33fd4b7f68d9eac3d053e970826386e86ca316763a41b720aa4fcc3e6b54c6ebacbafe9ce333e7afcd46cdc91623dd2b08148293c65f24366abdd2b8f9f873a2929f931cc72a7a6d9f0431fdff7cd76580f7272c523dd72558fc5ca28f81a6c45c10f4845a7894fae4c8400b0ce6d5e6d1576a1558a295ffeb355dea3a2b286290b9f4d9519d28c3b4f8838fb31cdc8e19b0e32c8aa0c2e4f0c08f6c306033175734f2a1d96708f241f95e9287287ffc02e9dc3c6f87072a6c75a6a609893a0a1e97aadcba148723220f496eead960ccf8414437a360172151954e3811775bc8a8a2a2925dd9cf10faf1a9af3160f834425c94093122669257c60f42ed78891eca0289d33d42bf31266836b90185365cd99a25a5d2a2c041eb3cfb205c3107b9031cf70474727f661f24f7740187f29fe69ff6a0f505353a1cdae340a82891c1959fe9bed55be9a59bf8c0f35065d45142dbd60c492b572acca90876da3e63cea5a3545c6d6047ffbc651093bc6cf17f3ad3b7e38731d72beb5ff053e8631cb16432f63e9b0e4634b4d899ce6093155de16e9289f3c93725ccd0ff229915748ae56dd92c745031f6a8a62f6fc247cb90e7284bcc4b18e728441b77dee969cef82723118640e0a81ae214903294cd3d002b694c782389c5487a0db6439784526588b795f7f9a9c5c3240b61519d76bf7b4a6152c87bc6214baf7fdf7c7a98424228fc8a498353e9fe763afa3f1f858253b5f337215c64f5a8a7cb551bb1135d0f70d15a9601b3898e65058dbde4493132c107a6fbb200a33949c8bd0fd48e0cae286fe9f449a2a4dcb4af9c6b52a099a96564fd286ec434e0dbf73f6768e784e101958694ed0741655f6a21301a241af46bf0bcc928d82723f2f1e28b059773a2012b454b0067b17e017ca5bd9023039b44232c769d6905a48be4369f35a4b361c592dea1675004be2c57b1d3c8a6f831261cddf9107073d314463aa884003dc066c70451b28c0c08c6bcee47a780c985f1ba717c907b96d429ba6fc4a363aa060f263701a961389037452cdecabc316c779046e5618472f248662bc391c5211f20db7eb58f3cbb887262d7363201027835f85843f4bd27255ca16c183051e2c26d2a6246593bf8ff827602b11bea9959ec0ce0a14e78c720b0de95105732da6bdc3e869c507fb4bdb8225460987cb4ed18e26322440fa72ab45d5ef8dba756761b1f311458b18df8e8f748b6dcbc9e6a72547f579b67072c31abbf8f36643e9aef301af0d27aaaa6ec471efadf5c09c42eea97c0449ed726ea6acb5be662b86462bb4ec86dc00f71f5dfe84f46a569cad2daa03295563722f4c65cb9bda28ebeb947d592a1bd881d7566c2aa148173b0bca126c489afc52ce990e4651b8f3779b95514b90bb54e952ed11704558530c90d4e6d62e17484af85cd779abc550a5dce48c26750e7cd80d1c2c406316aac958f9d59c93a654727eab750206f34a4c2f81abe21da7b7b920493edaf073bca25768edff44f699721a21ef575a255f6cf18ae2423d9b40ec42f328028cd8b050fd237cb5f6c33e291a3cccc56475d4575823b0493672fe6264ad4383f66d7431ca2f349b89be8772a84e9bd731f01e33ba104caea5c001a5ac355080116e3ea547ae6b789e9afe7267709db354ffcc33a5ddbe55c39035e7d974d708fc2dcf3f3e202428dc97742f147ebc59dca306d71adc943bff04de56f335061f4860519bfa90470bae124e174f2fbab72dae1d98d8baf0d95f9102089321088601931f1177a2996d7ec5af70035c38ad3365be28ef13e4b15768c9818f82ce8229838847b61d3c9d2b2df7722c77c861a9a13043ac5c948492b0edbecfe1c6ca1e2600ae3390295d80038a72ae75b656d5d1b2b6a0c1fc9700302269690f5faec2dd3ecc83fc8ae4b9bd8672c1ff1811c699418fddad5f986942ecf8c0e6726f1ffa5330d35804a7c8e3871bdd2f5a9cb1f24705f2ec8b4a0edf95d7f450afca55066866083ac8d41f46cb6b8d70c8c1b0fbc00e1286023462a50b878e495026d5a45e5c8768b9235703fe723b80ea8d5b6802b2b02db3612e7f5d649f10fc834e0d2cdad24273cf2b03a51249383404b7529eb406ddc77fc98485444d3550e72947667e49ec18fef8f30c27f7c1bfe09364f47f626e30092968dbd4c9b187f7b8d447595dcea6815e06c672ea2bf217d000a4c7c25d13a7b78f9827e054840dc94923762757443f062d7849c15e34af07742644eee517c8d46ed6a9884b9f66a4282ffb64c31a4f8bb4307284fe6402ba0429023658de32f88bbd558075154a20b2fac1fc179d91188b6c25d10adb851adbd3e89a471c901e1395ff46d27a0925068354c3418e7a3ae4644574f75c74ace7444fb1b502cc1fd8f47de5f1c3c4f6064294410036765cc2915432ed7d78310bc6ed5de219a683e910c667b51903b7cc911605d6e9d2f680c860e02b64b0a2fc1ca6090ca3b27e48ffb0ce5f50ff3cc066789bb9965b821179725fd5938166fdd67a57383012016357af2a8791d22cd059c3ff4ece29ac4d3872e1fee9e4b06b3790fb405f083a2b7a9d5f906db75c61bf30a4c94fc8cd23427217982abe6b643973e585570e00db1f8cbc4f10bcf54fb27428dff40abc4dad72b4427e30ac1d777d5e2b7af6638ecf9bae64a15addfa162d725e075656c51342219cf08ac576c747cb3bd158b84e1769eccd729c77fa052b028d29fa279b7e7264e4ddc82b3f19c25ead76480d1cc838eaaccdc5f449a1d84a4a24c4e27305611b7ba49367f88efbc0726e0c3e4bf67af7720f16a64aef7f63cffbee962741099081f3d0ecf5694d09d7f3cd459752753eed10b9b77c3bbdf22cc1b1375d4a49acd752a634c0580ccff937528c642fb4f8345212c892928e65c2d12fc3347b20be8baf7c8dab7a86e4dad11383514302089d3895b50c58bd6d7d4f498c43fb3c046722056f56fbe26a20b6996ce82199795c17c34c72c7edf4734dff2aade4505557d374b1442666e1b36024b59a29c4cd427313dfe525b63b8f12f4bf37c31445a984453ce79dd5e27ad6798bbc60a73d66ef7e726396f18d87a19887444972fdb58388d94ac6ff861ba4b1ff08c1a8442bfbedf76122932602a7e8deea4d21854e5525a212ae11eef8fe5204e60afd66e558e4a01e58c3fb6201bb37ecda724eda348add55f841f87ba4dd85399d9ef675681586e55fa64b155c5ab4fb0d7225b9fe4dc80418618ee9def94e077ba5835b78b3106302838a6289c27617dc72bef9487525205d54ee181981059f2c6a3934cc15dd377b671897de8d0196364887341bf3401f0dec1b134038ca30a460e884d5ecc42eb3e3de7a3b022fd9012e6f531bf588fdf8f2f6ace2c711b7a3c93ca40aa4a2e52e1ceced57dad592d8565617ed0dd488594c828d12da570cd32389070518f73318c43b66ecfded46bf3d503586636186e009a85405685bcc65e642ddd03b985f37d6f8c12cb921f296728d8fb73a03eeb84c8d81623043ef15025346a7a1e0b3bf66f86a70f6ce3c674f87b48afadda43fcd96fd3672efa7f3ab1ba1ca5b02884ce12c7bc9bf4f0e9a7233d4b75ec2b39e382305898a2ad9e4129cd93d14ad6a9a8bd15712d195d4b172ef8ce2e77713fe424a948b7c98e01cd900ccb3c9de21abb252813d521738d87203f5391e07076d71aca5cc795a5c2cf2fb10fbd01cf565c3be02c12e73c63167645ea85edb225d6f2412d649ff4e31cc589d107c212f53b873190a8cc05a6b720c042d696a508905ce94bc29f3632c716535f851590e17ac7c8a2ae42778bf72645207e5987096f5966ddaa65993518e79d968641e61c10a8ce7ceb6d9c3b5642a5876ad9e79d98fd896bb2efd479f3eebdb6b0982c54d5427a6bf4162227e72d8f4f5b56fce4309354029dfbff1bc1747c680030e4ca60bef5710ed485f7534dcd6f023db5e37a83954051bd5d862465236f710a9b99e26427f26c7d85541726c990db2fdc98b465a96c65b59ff18a7bfd1407af8810413d10ea36a95bdab1d338359779c7d2b986705e16740f417bf60b68a9ed5789968fc50a0aa9e3a0548cc2c4e7867f63d0df5265255cc8be63a2af5d1f1a1a9dc6f35f6a5bd68214b72ef9ad2a07f286c8c77917f3c6347014e518831cc8351d486149fa96ad8863d5fc559793409bb0ee00962514842d8e7012e93ec0ca53ac013d15e07a8012a387274e3d89f4f2584dd4f9b25df5aaa145c203e0b5158e825cd24b44804ccc19e5729356025eb02a474b7c146a26285fb1828d7b4493fd32ccff6862f05955c927213c816998e9a999d6cd66d98eb7464c9f47e012ad0fb378ef6ade46d678b2472a5407d365cac0da6a0a361469decbf1689f3c67485543bbb26f94016a920ab203a9f51a981d7e5be2ebd051e2744b261aaa7dc28808e4606d38046b1085da172791f05ca34056139946411012e4cef94f8292b5581f5ead6710105ca031d0e1041b516da849d192f3cd6e6df3503b264e8a3ff153eaf6837b573d89a44fde472003647405719b2a1f797a649be58a31d868d5d121bb790b058c14aa337e07a39bd9f65dc6b2d92e47c5433e214b9315441c80907114f9e7b52ad37d52ac11a1580199c70586191981a70c0796a5f25f943b15a4dfdf337dfcdb1b45975ea58729cfe327bf92a2e41ada5676c225595161f6887eb2103d6f59af8fb5f3d5eaf72197d17f934f813f8d02133262d6d4dcd4c5bee101d6c0675914c53fae911580390edd0fb14032e1f8dcbf7e0b95c900553790a48956caf5714709eceaf7d0472dbd6a234ffb2d690f24bab395a40d4a76da67ccee4d78c1f8f79b21e43814e34659bcfe1362cfc83798c2212dd134074f234963da9d24cdccd22fe141cbe291b2ced7ebd122c1e7eae3bb7d48682120c852248260c9ee08df889ce417f79fe72cbde762c6a4013eb4fdb11d2fa819262febead08e38986bc193b455b364a04721edb58ccfd2df6b6ddb8606336f67c64f8f79e6f69b03792e9392ad3389ba308bb89803e5ee34d1c18c42c7e20aa299ebd42195d3a26c144998f4d3835d0d76dfb6aab49af1fa23de0fe95f3d1fd455174ed4cd8770525f858cd57f73f728f72d6f29f683bf0d1df16793390de6ca134f8cdb6d2a6baf08d24705aeb223e917228b8b669d6d460cf430433e23617e75df8f0fea0314ec02bee6b8de205a9d472f96faea28567ab0b901597fbe1dd51090effb9ad64bdb3f6d9949a8b824e03587f4f732c75391ead552741bbc416840d1bd118e3e85fc5805f571a8b8e26c144f27ba116660ec5dc5e68d67c485bc20e7b5e48babfe5c3636ab5b898c9abc3722e37e5e066d8a5cd7a9d5622fe8833dfe2947aa950d4579878a466782ba9fc72a25d85cd58ec1a9b35d87a7968c8ef97e89538f0aca90b8cde680801f93a733ffb71f9420b2c6cdf2507514728fd1d62451b1a5ab50da2e96d87c1f8d38dd045856dfce598a66bf64907af24e1c5388f0c435a0396e6d331d969fc00a351d42d668d700e53510496ce5167553ce6e3e4e77c56e9ce938f900aed3db190008f6fa5637535c34818fb0f7530ca06a995b04dfc235294544c8c010274dea4c9970a98a2cbb5cd9e5bb5aa6196ac5b63d059bd95e4103a2d42fa94c6a2d647dbc87207cb7c669e07cdaa56c5594e02ef132c05780972169c5d51c791dd3208b544637cb6e50414af82cc1e1ec9b9a143ba17d57a965a606ed729c86210c3135a223d90d93aaf1c30818611d7d58ef678eb4379a3e1d1637cb01bc22f55198d73ac723a3128fec005bd06e84666d5b9b61b62d1cc9422fc1d16e1b71c386aaa70fe5bbe3730e181debb5fd2af6862cc19d761bad93767b4b359ccfeeda443cd034672638710b9ab2c2c596fd1e264606fd783720e1bac3f231b8aa4a38b5cfabb1f72a9fad1b199e9c4541f45f6c3bc7858a936dba8818a7547b2fe0845b252fa6572deff7c576e4258bbf327bc334a2eed29bee5abbb91625602f81c11b5fce079723b3a1f9c5baf0fde6302adc8cdb48f299e2e5e64167c1ef8e586d6023c7c8d6ca53eeaa8aeaaf8918a90ea31c11c468c22e1698df37e83f91f20c2327e45f0720dc68b8d9f75502291f90d4b32ae85f3f2c14544d8e1fca24e7f045b5c0d7800e76f1a93340ba54565144f161239b54a08735587ad3585b9fd61d15706c68e72065785ebc25b70135509fd07fb39e6e7ce66da6c3de3487167f0a26aa16a8072310d1f1a18dbaec007ce8e7e024e633d32310b6d3e11d4be23dbd84618bb6b43cdb2dc71033130f26b9bd863b933fab502d2c24b217fb5e0b5f9b4c7b932c872ab49b6f50db4550a93dd8878a7d3dbc0a08353bda8fc652acfe19f443ca01e72b7b74eb4ff3b31811514efdad1d3624b5021bd2ebc9e96a492096200c54cdd7212b20103a669f065758e6426253871f136c1c768b2699334300e5315db11a6724004bae92bceed43569df9fdac8ec8e25ceef592cdd8a474662bbfd58f95750d73e603b6525b8544fac55f1745ebb94c2d3440fe3fc8d68845b49fc31a91ea72b8bd48a4a9161f40b9151bbab1461c1354abd56dbe28206ff340e587e392bc723ed817db680cc91fb1d3d9e702e8acb92be3f03e388065383ea4025647d71372d0e74c8363b4fbdc3a8724cf722e008a46c05c1d342aeb4e5d66d9ceea75ed4eae1f7223264e637e571c8d99cbfdcb368ec3a359307e4bd837ec85653f262572dbe30f68be9de859ed5e0f1fd22060b4a1fe9300f00bbbdba9435c37b1ba3d5e355bbc41311ade2d36979266ca87bb606a2705dae1b1283e547ba66a8592303048abd57981f8ff066304c381c5c93fad9181535f27787f62fd30242e4263b44ebcd690e44b052768496a86db09c919a4c6b4ff30de4d8801276d65c0acaa5b5de532a30158cb830f6bcfb33f40c3d400c8671eb28d907ef15bea1ea8a6339b3fb8bc140f1fbd6d9ad338aeb6728f9531576451bf057539247951817449203d72810c3568245d0d01565b37760efcae5c546dc6ccb21227cae19efe7711f02972ffd4a151971f84cadb0904cd2b4015fbea82db3c4f81d5e9faf4ec86f3065404ea3619e5433e40a44679263800ed8c930ee69bbb58e1c777cbf6c9f9215b8072818d7edd8df8567cd26a1a42e474cef6af72de029f756e1a0332fc89255fe9727424c9348151aa3537f87a74aa51546d3da033184c2dc92a91c7e7b3065b4172ad7a3dbb79d92260721d3fcc2e5ffd8c7af2e0905756f9aa0a78ab01c0dbd972845ed725942b042fa37c5fb1b83dad1af6f2e4d48b88904cc6f60c1f48a3490c3e0c2a27dc97500cf85117cee17092e2c44af50f59815925395859c46b883b72da3249c42329006cf00248a6e74a31ba00e98e143ea9a0a6139870fd308c5a7233afd5ca3d0c851b7cb3559387c9b96b60dde1e56b09728a65f1822e65e31038fb7fdac7b69efdb3a2633c9ce7f78b6b043bd6cff5c42843a69dd7fe9e718472fbfb6f04fd1cc6f6d35aa0d336ddbbbd9dbd83f6c57fef558060c30c606ba07260ebc878aa5a41aac81d291eb2a3cef17e4c70a0ff0f04f946d41bc52ebee566ac5fc41b23caded5befd88b88d6b8d33d08f698c15e1839cffa56be88429367296b0e90426075b9b829ca3abe29533cb40239559af88a1aa8422d4c9c67a7472e1442ba50380101cc800a8a91b8366ffea54d8bbd458b7f137fc712787d20f720cfb5966b0b433168d6550b2a302041e577f532b6cb5fb8fdae18d3a6c2372436b6eaebb35a3d9300db0874cf3a1ee0b5020c3627d14ea5ec68603e95743e472f7ae5476cac8d1ada5d2fa6c4b1f87926eb754863f7eeb56562d3d20290ae84b28292388cf769b24d75ce1cb17d59fc21ef9b05dac95012e5093c7b3ae6173720eb3016cf6e182e780546566eb29a68eb9a9143dfdf6d701a73711eaba3a497262e5b16efe8b5e2bd77113ab2de6da7ca265367a683d4943039aa765041de872d48a775d29889bab32ade565b7b111082ada7e94947a348c883b726cd6a9a272db161e10a17e306bfc4c4b98b9e26c71c55050c83f79c1f29879d7d683fd4d3230277b5f1596434ccb4e7cda4e8d040665f16195f1c6612f9c4e6115721b1f725869074acd99aca8b9687694465c3fe54b180cc5cee9c9b7278c081be0ee7c0238482f644c5c38ee25f0122a2dd4bb49987748326d55184cdaec59ce371cd072702a86b2a5ebd51b728742ee6123111e5f68a74922d2753e209da3027be1dd017321a05d42934e782c3bcde25447ee8bad1322ac8d89453964ed5eaf559b8b089143199f8d1a5f362ab1638fba385ca4cef41f7fac7595e0a85f2af74524b472930d1f095f0dccdb231d1031893069a00cffb669ea114b9d01890fb92bc40472d8e65e652b1fca663696932a1a91626a7cf27428b9cb2b74f17524e27bb2f512df60992d680387e1d0d9a5abf83a22a331089a7ccb15ac32a82c8dd32ecb547201d9b92c236d811901c63a1cd4bb8a7caa4a76a43951e9adc2cd39d305cd2948b2c39e63849705d16d6978f48b1132f65876663800c4735021f3077ce51d9f72fff217218cc467906a27b8cf1e8b9e7afa01157d641e3ff12774c060a024a52be72f5f8daf5990f5f451aee7add019c5375f7e2c83d6630751cabdcaa084657264589bee89517a871ac1e7ddb5cc1660b2fe872044dac61ed98a361d7ca8d8721c84b7e63021cb8f36a602c3639465f2ac9367d89e170d4729d62fc96f3daa728c6a662a22ca6d0afbdd062f7dfc53305718a20791c5a04f4c1aee5203b45c72003ab7082822430caef7c427d2f2c01e0dba2261b5c5869f7ae88e53a97c980ffb00e5b361950587ae5c5c7fbd85fb55f55c0900b3da59c3a1f2032d14cf08727b539fb812620668b49db58528647b61150c1586acf2e8e07c32e90b97bc9956c92f2783c84625622f751af2756520165c806e12b320873ea37b98a347171472f5d4d26caa8f1640485f70058ba46a170b606683bc17152b167a09656754f572c276a775ffcd70560787e6d93ddfe1d5e0d37764b0c5173aa61a118bdbb20a724dc69651288099b534afa09ab7092766b52b6efe534e4f4d57b697d751388e4f8d716459fa235f16f6cf4ea897da227ae8305dc5f45bd2fec921031b09d4d5726acec5bb5c498aedd56f6e371ce5581375ba2d95febbac04c14ee4364611de2aa34de8b540dea0126dc3fc0e86575da259c0ca1445104178944045bd264bc259aa4173c29b07c0b15609a547aa192511e0a240f1c04fa990dc3190eb7b172d6075477bf51aa1b0c3d09d8c3fc96afa2a0569d59865dc816d01a9bd409d9d7c6a122e8f11db1725b05b8880f15cc8ed156aad41e98e2f467901b027140ec768722f51a71c9852a1a33a667f350bd5016eeb4280cb16981fea400b1d7da78c4b727963d5b2583a3318bf3223cc88397fca56067274e04fe120647d7789bec4d956dec39d0c466c62405ec88a57e3ebd8d8e9c1dc6587218847e9e71d8848a8351af6313d2b7ce8c13e9785a962d2eb290f46e9f8615db8d6e6ab9c7e0dc6c39b72bb4eacbac9d0420e9e6a7962801a02d4ce761085c54a6dc6111eeecd58cfde263ec208138b436811213d5128089b8bdeba94ab21b1b761c6b0c410c1a087fa72f8f96af19a88aceffb2bcb7a24d5fc79c8e9918d95fc1d71cae699980be21a48dee325570dbcc1b0ff08f9157b542a96acae67c4838024e58bdc48f37b6829728ac941b94c13376e14bae30e5f9fd3707817c6e049e8aabbddd6f9cba8045b720002ef9b19d92d712f44e781e064796bb9597a69ee80afb78a2f85d569402e720eaef48ab0ab26d5428048e2989c9a487db8b0fd86a3763bc112d7b231be2a723b2a44cebca88833c637a8c5206cb2b97a5d049e40d3c30a00d96fa791e7a9722e30f89954ce1920d7cf6132af283e5b4925c3d518fa60e12a5896d12e2243726d14379e40381d51668e0be6fbf4c91acf3284df767bd3b3d05e4e38428c9472297cca6eede7f15221ddff00f0d699f29b8910e4dc6ed5caee2ed54b653f6472e615a8a1573538b42488a02708420f0799d219aaf733f4090f95def133ae8a7283ad4c4b1cbcbdadefe8e84c932eaac4ac20e3cb4ea004ba9ee5f0a1bcf5c46085c218d966094ae63352e6b1ba0f8bc5432a35960244b348b3f1ffc3fc1f55722764443f270ae7ee64c939ef184c3da784ae37aa70ae15dcbf82a92cd94b264aac9763a4883f195e8d0f83bb05e3dc0f000dad14dbabe80faeece1cdecb31d5c1c790e5c35169d9f32ef2900757f2f489a4da5fa1ba269f83c89b7d77fba263bb1d203dc6dc1f4bd7a60d2e49d6a1900b4e98d3aeb9f2ea9de53136d20f06e7297731eade37d091126307af37aa68574bde609032528bf051bfaa03bdc5f9a41bc8c6a0fc9231b18d2c2bcea7144a265df957470d4b12c11d30b6017f9a9f5721300334757a204cd3820978bd173f53f73ec07276fbdcfc01f7d8c3a60b4906a6332fe404b2d321607650e8700dc95e211f288fd4fe46fc95d17b846ed38e3723e2df9609f9eb7575e85fcdee4ce0c8d60213a997bf5b6be34a4c3e5d57e695992542e954422bed7c7f3afb4d025c73fa8890536fb33c162d01e5083eeaef3723c388cb0b72785786bfbca843431561e196591b53d31d36b69219501354fe87253e42f00c6d260ebc44f03744109bc57eded4598603a26bb9fb05aa797c0ae1b1802ca6a54d3389cfe50f4a49c9aa0bc578caebce58e797ba6a2ad89a19f2c72d8d0c411a0549886ae091f30e94ac1fae83a44378f61f8f673ed6dc618c45472f41620a6fb81345b516bf8c9b95a5f92178649a967f4fbf2e1a02ef22b7a9f72c66d04d3903b02297b0fd09489c8928abd90953292626369551d895d0ae2ec631efb946786f56e3d6bef94d5e564ea5c943c7d69d7419c8bdd6f8f2d1d0614344dbdd5c9cb8b15ef70b7922fad38c2b6ab73fd38a97d868affcd4996f9f81d7265ce60099920dcb4280ed59eb0b1eb7bd43edefe3f7d60000f91f365d9bbbb72e74f5114f9e71bbec3a1dd96d3e53f1bfb06273c811af7aab68f7907eb1b287229acae64c27afa74ed2d8c54446584822a78a62c6fefb05fce9f1ca77f6352589d3435654d17caea8094d0da47d4defce9a9da12d241162ec223ee8a7d19fe7292421a07d9975bf8605eb1bdd5f52fd70b74296ed867e7a0b1fc27a1f5dc8672598cfe4dd3bf50f7cd0f2e8fbcf1cbf63ac74ae19baa56ae28f55f6cca3ae636954e20e385fb4e38404a14e08c9950cab687f89108b71e95fe83113df47a9a722c5d7d3c24a83974d03f05ba0f096a5bf074c100a6485df5b91f7ef0d0187f72d3cf14660e8c9cfe4f63b850c3dab74e656ecb5a7e0408e5f36d7d44f6eef7367571212e92efd0191f839d9e075901cf47fb81bb89bc7609d30c900b2b22ac72d182089e13b167b06026a90ddd184be8d1b0360e0520f1f1d7df2680f4d0b87201e4599aac21b801271ee68b1e49a9f6d7a34e5af18c9b0a4b500651bcaddb724e6cc3713d3dc16171d88be2cee8939f9daed3fd6fa33a2372a61845077ba6728495d1616d4551143ce10c65eaa173725730c6de92a366450c0e7ca393cb61724d962bc0564c58b05d563ec4615434ab2622f813729a7bd4f7ed0c4e1c489c2661d5de905588a3c48f019ecc3984f4277acdafc93576b4f355ff1b23f80ab151fa6744e93928fce2a88cf011167e4e69719a5a731c85a10dfb17c7b16784d7728255e29af18e7b22101a0fedba95151ea211f7727824e14d31d2b915c0778f72b5da98aeb89230296591cde866000a04b93584ee560d60c324747a300cf15a669cf5191d18e88cb72c0fc7289771eea9b269e8f79056e66420fda2f6605326133595fbd9db87655b60b31a1e59cdb38699ce8f872fb15f2933b968eb2c2af2720f3ef5ff0f42977a6b94c4f3aa664d2e516750383056cc1924e4657b7b7bae72cd6f9aad8b27122cd90ac4b33f17a2e161dfa2f9f6611d8ba520dc84c5f62d2e9b9f8723280b65dea84efbb02727caf04d0caa1d9b1bf563b270099e0c455a0575aa8efa025d9e61522e178f64d099b1a53729fb6060f78108d222d68d61c1721d7722088c33f35948552cf705c2be85817672bcae4b52597b4c9fd074a34472b9ea69e2edb110bdab7f0b4b7f6a2933fa6e44f5474d9d5cb8b019938c2f17729f39cdd4ce76ebe8ba4a3e183340097fac941f7cdeb34769811a0114133754729a254503181e390c6b936df180c98de28b3c3e58b0fd657e44129384a89bf20fb33c4445c7a69cf30c1c7ba28923bc027e28a167c6ff85c1b1107720945ca872d6fb365400b034ae4928b6d23e25a5e7333fbf18ba735e8b6412e1696003e272f9b4447c8342a9d033ff1d9b7c0f15f52a281336ced93ba050553794f34f1b728c8051da2ff26fa901ab5c86de1c0bd5d4b58074d3aca7173022aeef0de55b4aa81ca109e58f4f4a1caa9f534c96fd89a3f58227301b3603f649463362f22372460a31c495738a8630e1d713b324d8e98707a7a78548bafd1cf4108b452abc7250dd666f1ed789ce19e7a424defe069ef110b1e3973fdec2ebbdf2f59b7fd01a262698fcdefae837c345d557fd5be3f1dae72c9d5809323a6931606ad02f58026da0a3abc4779c7740bfeb06df67245072db5d9ffb7df49aa78c55b7b5745a028eb29a465a984c3003a7a74cdf786377e125f543a401e3898fb722b3d56c89726ef4e3f96cfcdcb51cdfeb46aa4ab8ab8fea46e36b3fefde60a67c3f09cc2060fdc74779f0e61eb6f0c40c8569bf421062f9c97fd1e8df2c2d4f9940880a777229e1f9d409921c3cc5e25de3d206f8d5041c947d8e2e17be1c4839af5934ff72dedad37135c7eeabc43e700956152b74cc978af0693d076a65c9a0f7e0dc8e72aae99be8184db4c655e1ba12edf14c412f3b16502c5950eadb7c7735270e7b72e5f4a6ca96f37152df91fcdc5e9dd6d51f68347f14d7798d677c790b40945c6ea3bab78faf4dcccdd50c914acd09c98fe06e59598e5a58438814539da4d1023a9c3f8b64fc16b02200e3e22dc6661e9c4f05c2b1111d04af3ccb03ff15a2cf5318ad0e8bcb2776610f7d212c3f48527e841d78854efcd4d0840e53a2c59430727111f4c29e44fb69dcedc7cc1f6531dcdf0ba6ca53678f66fd81380acdc8e072fd3b44bb5a539eae7d59ca21739fa7d5d30bfed435a54497ab60f3ae80be4372d10bc740cb489c0d97a566c50c11c2a769645f0430b14c122ea0ebaae8fa0172753c634db1c8bf5efb6b01d130594c35c278ef1890dafdf8708f8fbb0588a8669bb2982d628b1008fc6db6772829126ad6948b89c33a627cf360f3402b464a72deb64a2b5439184cc26e3443110dae6a7a376c2f02e2f00118ccd6428ca43472e1b5e891f5cadb0afc505651f5f7ffed54a538e64f3abac7ed290c852e31f953d139efc2dfae71c1908e5683bad8a1c894476d00c361576c1c630da1e781eb3a4d08e9f9fb9a81effa23b6c44185bab45342ac3dc548f4e312a6fc336151ce46302edfb50f816a44ed42de32d609a43ad0bd40e6f2b3fbb5da49278865fd7e30e1b893fda1c3b36f859171d320feec197641732f864bac1cf3b67a4ee654bd7298c5d55de8820fbe9e42c36f1d82b01b0f2ed926df708a3d695b27e8672c4a313880eab1a895322938eeda022d7b366666cd4f73dd195073666925a7fc157c7217edf421421dfef90b01c5ab571c8bd28f4712930604169e305957135ca3f972327d743b9dd9df66504b8fb931907425335204f6e0ea1efa9b97c112c8b05872869f79621375e0cbe0184b42cea8b37d5c5a1848eec3f217757ce51fd7feb37270231b0584458ac3a6af5b768750a86968c3372bbde0bc94101cdac89100b0139f84951626a3da42e4c14e4aed4e3cc112d647f1aba8ac09c040d6cd118dbd729999e657744847fc8ee942a4f9012dc216f6a50453785adcda406d0d4af0e24a516fc2087cecab49fbe8fae5329d08b63d69e88993a37e76380b75ff0ceb31726db6456225bcf652fc10633bb4dd727d811fd7f1c59f5732a7deca08216feb46747c1d2b16a962ff8fa6e2c9d77f9e7e8fb95ff6143838a80a9973c27dddb472a257ba1af9e7765c1a67878aece7309af839b653bcf0f7a3490fbb34516b4372110706423d72b6e5b341b027b29a51cb1c0b615027a1e4d4f65aec10386431501bfb9d00add06d9a0e69f2336d363dc9587085d8513356bddf25f879de54a67282454b7ba81c68f91a8453c73d09bf6818d6c08f1a9d348283dfbd9e692961720ba38dc1eb5be1903c59a7a0980634023ab027f4cca4e3acb6e02a93b999241b02c7d8ee2f6753e6afd8bfaed035ad45081a01ecf2ac131705d3f28bb900df1f4b02e4a4fd70158fd4e96d820e6d4e84d06fb58fdca29b5187494defcb1e0272ff9d4e8f6e627892431490d50cf2512e3b47a1f18bf46813cc0e22b505b25f726bd50eee3a1b7768957b6ca488616eb517a069efbdebff07fafa08d2eb4e3b69ff87c28f73df775ff34a050c3d692e16beca77b59bd314872a23c50ba3f952722f0dee79139cc1d25f41d7d5c099264af6b4c0d0c45973a4901567a1db4aea4ca81e3550b0150c9b608cc9e4ec760196e25ab375307e6bf5bddc881dd26a7870e2b9d31b894c9e28238e8db703e1e7413ae2310d94193b722881f7ed83c74001c0847ef7fdc17ae746b6c38b34f4e859414a9bc35d4b0f207f96174799cc2313bd498d24da4efc5931a1810dc2131cb7dc27dd29912e31255888f771b381c0726a1cf315e88b69020762d7d28e0ab87d7e6959f9aedbd9d046adc472cfff527286a336ef2aad62719b19a992232793fd970f2d41d1a83718fbd0b25cabb1c27289444755046f320eebf8d33a9b231eebbf11ae6cf48a8c39b989cce25d8646550a122e119ed23c21f4cf372ba083f88a17ee781fcd98f3b972bd4b86f7678746cb94be335d33c1317ae150f8628584dc5802d96779cdb46ba0fa819002ab5458fe76b3f3b11e2fceabcd25574b66b22844889c2cc19de620b2f76524ad72c672a67dca4d1fb274b29d72c8bcb4e0531226b5218fbf160890d78befe8db2e29726e4211641b20d9a0b8f6fd7f8e8defd303af3b183cbdfc2bebbc4dd8a228a87213127133699af424e131e5303d37112667f48e3da630c99e4c30cacbed5b5872614a92ecc4d316594729982a89b093c827cd7d26b042fe70cdf809cb5b5eff1fb507e1e7c8ae47ef1dcca60d1a45895b8f54c50e1f593e315142ee61ced2fe72bd27fc9e89d24b96b8ef6ebd865ce6109a9e295a82faacb0c8d41823bbfb663a0c6d102e52123abd7033455f6b54f5730802f4b1880f754a9f83464f9dcc077228c4d31025510d28d574d8f1f24dcc71f1eaf4260b8113a9d4379149b6c0201ca325a43ce48db2862ceb7efecbad1ba40daab1b5f948b516f277fb4d8f259c505cce8c9fd9a7c36bb54240b355362f2db22f850847eeee255b57dbaaa9ec9d7272ac98b2704d9c4ab5ad7ddb1582c70c1cb5e84da66ec933d93a6dd4efe6225b60f46aa2e6b9202e7410d6194465e9a105f8ce28e403f0519629a1d61a59896c1d3530caf5e93215ebc8194a4575d68eddbf1ddb030c29e06ed42a1dd3ff1f720c735bcbfaf8e5e5fec052fe6f41f8b8a98b0d5ec817a2673a6e71dba9902a72efe118b99f67592c03d38997a24a9feb46a7f298038b95ccbb97492a49f520729fb0e539bd17e3cd2684aaa81a3798f75d7acdbf257142d574144e2870e44a729869121619a1d46b4541c9a5d59d625979c3a77e62f8e3eb1bc8ce59f679be4e8b3324d55f52656e35f3fa21082cdee8698034d2bfc472bf0d964ce157ead84b3596cf5daa625ae59d05816f08f6c7d62f668eddbc4c2b2bd06e4a00ae02497259c96e870fcda7b8abf354778082f3b183bde2654fff28ff29e35c65ad9106721b02e15b28ac1560b1ce13dcb5bea852b9464f17ee1fca5a6ca868aeb7c8854a5ae8a5746cd113b8344cc03a2a0dc14b02e82b16b581565432dc24bbe070a81a80bd2dddeb157ce26c4830fc5ec35139843f28a29d66ee0f15fd9b9526f8b1726e7d17d60a5b2d3f42ec7207acc17bcd292fb1d1ac063045a29eefbe6fc2b4720fc5afbd662ff8da928fb2cee3aacf4dcc98662414009dee2eaf6740d837f8726c42b0bd552645dfaea055ca098729cbf83956acbbd7b5a35a288b665430c029e7286686e17a0526f3357247c63d8fd6fae794074fa5959045994640c854143653caf78c2bdde04ef8d531769ba23f0db55daaa7512529ed69f91c449aca9905c9b6b0b8fa02f3a35b17f13c980fd6303c30eeaf94471aca0db5768037b5e455b13a2578eefc75d6376355b4518fbb7795f90adfb57033f91493f7d36a5c06727f10ad9747350ff1349e5c14847e80b24ae09c73b117d1b7451f8a7150c0c4311d6ef88e0c1f25a508e78acf3a380651c20cf40457e1f1f2c95f6428c2e7bd72ba21fb1cd26daed40526cf9e491938619166b4ab7ebcda4660efedc6ba7b2272385668a7b9971b299dc17a5ea710ce91ec7c378e50b088c1c4db880d46e00b72b275e67c73139b830c98f76779af3ba3432190b15169ff9217c62debb43d13727bdb6a74b5331d004fae92e91cfb473faafdaa65a5188cdec67f0e3efc0b7f5c482e5b235d4dc958180afc6e85b95178fd2d2a0104151e599fa1572acea20754694f870ae1c676a19a59392f08f68e75d815d65955e5ea8fcc66b9655ba18655043bffba32eb715d902873f4c2f39feb213d0f4af09779229121198a3425c1440608c586f606e82c164e35f7d40ccc576f5d08f22027b1cdbf9e2a6cc9bd8f728c1b360765e09ace20438f7f0c61447564ee397a65d731d6f2ab7d1257cb4c4da188f182339b1f44f52e23360852787bc63f3ccfe62b331e5236cfd26cc4d07126efe0723f9fafc81fceb0dda54009283649acb4ac59ec8af3c30df3e4820551b1c83a0d54eb3243df3e1f03d470edaa13704e43f4587c513705379b093681724b1457e2b158f894d7e741fc82da4bdfcb6132fee1f2a8cfaf31178ba19a11725aeb49fc91c21f340f445055e97aae01d2e6f5e061ba5bbed0b642ee5d4c4e7249dd25fd1ac8e5b422f85a5f3271196d6a70b1d8b677682b84b54997d0e6304ce86e6df8be2842a4df5ceaab3a3ab75cb0bafc5758ef024d1f4f47821a4b1a721219712ce6d6129c3130cd3e9a4ca70c51ec8cc947dcb5eb22fe52e18abe4072e375f883385418035eb0e7128c69ac3f0c499367650a46eb525bed79c84491556c022316cc3bc4a46200641519437b452e3a35dbe559f1c6f16236d3635cdd724fa4ef71b407dff570b7a52299ffc1ea0b41603ced5bcd78484fd015118f05727abdfee474cccf8ca5f3f9f44e77112007ff2f75e0447f9ec2da1cf0dcc90b14efacd5fbfb4c6ae37ae723d4483f564b2125a455ba1198cc3cc205689663c1728f9246c75ba66c97b806a0212dd5b0e5ab38c71e9e654e50717d506f1b885c2c369c9036fc2d73d52b8e1d986ab192f61b1bb43fbcb2dd7869c3008a44793827dee57196954ceac36868f6a84835330f5439db775128397515da332bee287372a419c9a011ef6cc4059c498ff38cc29fe27e728062b7828b338c85d546328572662be1740d8f189402a34709e7416ec3bba595071a177f08ca6f681e63243c72aedebac65521dc2652ca2f7072d8a3474a1ec9b7387b16591c8696142e28a12eab00b05417e1145e0470bb4dd5eb1820c225b0402eddc76f1ab72d3703f77b0f16c433b8437f3c91c72b6f00c8020fe0528948cb4bcf3ee1f41d92b86a325e727695eddf0f68b018818031adc9649318fa6e8043deb8b9c54af5ad234c33957234cb56c004787af8e58e1d67461360021ec56acbfa959707c53e5e8d7bdb49723cd3f3a24580dacf2afb0cb236343508611f7bdc0e7371828ad1deea1ecf8d27dff8f96d0a08f2853af98f4b0ae233947da8ad16190da6bd4f9c7b5456e4127278ca8046b07fb0dd167f0e0e06bf19ab12b6eee8262d70ce20d8e858501d1e23dce7e0a57fc6297b16e5e3ac975ff5f63d2001483d418fef1f44236dc3565f7274821fb10aa5da4dfdd5efd00e33c5988bcf55640b2587fa2638ee712ed78a7212dfde1c30e9418595391a30423f9cea069d29e466ae9d3727059ef406398e725ede0fd5a36088d18b0de426eea61db28b0cf4a3d148fda2cfbf4dccd0cb637200bf0c4acd6d3ad9942d6a0bf24740fc418b93893860b1ad57ba4e126ce93f06f25c46fe7a3d84c2d6dc377d13771dc9e35e57d091c059df11f575586c1eeb72f89ce30ca450209708ecf2e9ec47fa516df66af69b35635f15da12f40037a872cd0c88db8f5c4919d68fae22fb165354c09a2c0fb05c4361d1770b96844dab725a8af8f2c3e162993d261381be7984d38b8bdf5f7472f12feb3fe3d8d6d9c21b022dcb989e89cd67018407c7e93d02bc7061b546b2414a73b7dfc4075ee6a65339eda13e395c71178e1fd82da4d6eba5ee782d820231cf4c633b0cda6c06923c6f6e86d46d6c419a86219df6be792d20cd88930f1cd68a5490aec324858cd25dde76b00293efb92c81614c4023ab735e57fc9ad9a6884c48a4cc1ea2b3e756244ab09c47b77746cd636dc8a6fb70128177a831a0376110e7c8385c16977f6918a9a48750333aac5d07267ff0f592cd8528dd7283de37506cee029a5d9c84b67211bb3028a92d0d0aa7545f06e5d6c7c3570c45afa3c2cac6d574361e26cdec6c6017b1fffb3b28f64092e9d09269b1dfc104b06ae2e07f0b4b243d187f7c43720396e8d30738061892addb8f0be79f223989549a0363531494b8053a2cab00729ca9aea41f0b6729ea4d2287aafca26ff14ecb1da13427e51c63f2a139929372c1c1f47714440950f88d306dccc7ba20e70366a2c614338eff77979024edf019edb7e40f35a21c82da918be855322d81908d30609cbf34721862b76078b14f72f046f76948d41bd219a91be646bc8f91f5bf87a0dde815f343a2ce78cc46c7728cd5819d563ee1c47db1f0a4913b1fa00d3a319e716c1a529b7f1eebd2f68a7272078cc34edffd2f77228f25e462c02021de60a4f58eade3f8665efbfe6ba5027df7d15fda70fa08a04163ca7c067521261d60d69d92a53f8b06dea5010c6b72c5f9493c302cacaa7bf0ed0d16a196643064fb61857a819ddd78bd89f4db5572b81f70c268de3b7753ceec37af8d1720e4af5830f3d8336e1523f2be60a07772738fa86a0a1bdc9783e2ee7cc24dc4a04af82db9c6422b0314a010dd94354c72d9252167d66143b3b54353e463cac261d93dd33677304c392850a9e98c4e7a725c03ec1c40f7c3703add722297b926cdc5ccd34a0dadcacac5f60e543766e4724b6da12969aca797722263557d64375d0c9d7af1e0351fb8bd0fac27fc03bf72e8543cb48143792bd093dc43da9f70d984d4f4c2177f33213e9ece5d699dd872e368020c9e9d09bcfc6e8b216481855685e8485ac61c3220909175b62a92c8721d907a7c72688ce746b7665d0ef3ce52b82ad53c08b5e04027a1193c64def06f51dd8ac2298989860c6889f2e72a568e239a2f960ce5ec4bbb6e594fab3487720dc67a5120545517b81a178a37ebbaaee7791fa7c4da5bd0b2954ee0249be6017329cae0b651cc56566991964f1b4bef0d2ffe2812d17a23d6a071e1dbb43d72cdd124a642c83a0e3f08d4d8780389a4d88bbb85ee165144d9231a49bb65bc7237e1a29cc2bbb79268df863ccd061ee5e8bf80fe609b392a5078d9379f744472f26446c2358a5610f6952b38be0a46e6e1277ca54153eb4a7701296c46afa96bca8f2c17632b6351a2434cd462338a28f27d05fc7a6dd576f03e5e673eb37e72a850664402e56107e98824e955da8c88bfe4a01dffa2ff8f6f4f33c568f8e172d5e58bcf036a2b562162dbdf381cc942f199345566ad8860a1b13f153b9d737245507ff00f27958537a3166afd7e6a0ab19307e9b1c401cd92baa4b80d7ca7724bbdbbafdb76bbab127d7b89e1f9ef5131a046444283623be1e0e0ddf7afc3530f424b73320d8e23cb6e07d6087749c67b1992a73d40068e30d21bae5426d74dd394d98bd6e2b150c5a7c02c4af64c8dc4fab7e53b58a4c0b86e53ea31037172c68029b05094cc292e8867731494e5603fd1a388afcbe707f24029c306f53f726a055c7b9e7f99f16f580e87be91662ede8ffdd5ae8d3798751082b62456e67229769aa415f90858a065156e295cff42618f6c13ae954a6bb5781c33dc403f727e202a9b41dcd95b75414d80f88f8af42cc4fad611b655a75ab8f749f2776d724ef51ed679dc17d874ce40fdcb91ddccd5b6676ebbaee032cd4bb4280a8b4d20fc85f262a41a85a92f1ba408cad2ee9dc6ee0934e383a27b19c8655898711c598a5d3637b7e48e4eaacc8f6dca1620c1ea81af1236b9828a0aec75ebe37bbf72ea4a062e811df0224bc810dc460e46e67f7ae59b6c3ccc7e1a155b18512a5c50793ff160bae8c76ec830e0a4d02d9e91cc9ab57b6e5eb97a8707af69607bdb63554135f2886e1a0d83ab75f4dfc02bdd205f2ae5c78d26676fe3b270fef15a471bd4756ac2feb1200021e508bb6b8b3dbbc42d568931f6cf531c16f16c8ebc724c758436614ae1ac3c0bbb2ebb4226fb25f672d44fb3184b2e0b4eede5ce5a1d7708e72c204d35f8248cbe7db12002714a9ebe369f34de29148303abc0f1ff722619f38d6c58c49ed4316ac6594efb108350c0ce294d7dbf795fa231e70e7c5e4530e519160a946799a5288589ddeeef171ef474221d98b6841f81b1864ab90c79dbb9bd703bcf171ba579a77b72324b4e48189e7ae8cf6870414da82dce8a036e8b901e934925addebb09a2636727c6207d3ccf5f428e0b68432cca848bd2727d86d64ee0ede60aa1e2bae15cd6c4b30845a794d9b056ab491817857e7fc87291e0e84226b131237474140cd1599e9645fc800b00e5861433727df8df8cf3728f335d2c13ba77fefad292600c0612dce03f1b8e6ebfb4b2ee016b5444e74f32d578870841a44e0db6f6ac1ce5cc86bc19a95ea1e27e38f518e36ad7a1a85d72279c513942b35ada5123ebfc85245138af23fcaa04201696a79f23216303bf72f6641249a6113864e4b6bb1a29447c6c41367b1ecb8a93a7871547d3c4f4510de52c1ce9e93340e1e64056e67a8c9601f8023a8cf9d14d266d88d2072f7ea672e8d3237083bc6c14cb8333c2c6f616bcd439821260f3f2768e365108cf821e42e225a77c6e99c6ff87622b6209d5fe7dad7b63697adfde65ca148606cca8a17218582d2e40f7023694b507bdef13fc74ab25ed4de4042dd21e6b996f13eb45725cff053dcb4b2d629359d1248cc9a8cd06a9d11aa3b6ca2ace2ca293d4af13720cfbe662b0005ec215da9c73cedd182c24a5b3c10bfdb1aa66520bd41cb5fe72ed6518f63d6ea336ca69e7a61a6d537ed978bdb32b929463275daf018836b472db29dcdceb2f4adfcfb935ae17b6c74af5ef89f679d7d538e427d0bb579bc20813e185f69a9c4fe5894eeb3384f31b916e90bd14c78d6fa5de38338e1d1a177205aaffc3ad828f0d81bcecd9de181ecc9ea0c2976de285404f22ec3550ff7972ca9126deb3d770fd309ea9d37ccdf1cc9781350e88053d2b363a81648271237213792f2860be26bfe1de1b0b8e6f53e1cf72b9468d13bf8b9c32b89c0bc66d72ce4d648824341e6a2e94f62859e92e8ac5c2a5a03187406240381477881bc72d014ece5f63c6b7b140da780c052946a84e4a34e8bff21b8c7da0a411b8659140ad506428542e44d231822c9ef27c13cda1a5625c42be6b63ad9ac2ae2cfadb39ad8aa85a83e4e1e1625aac118afb79f69be904cacbd56be09bf9eb10caad63728db2f58dd4387f19d6986e0b307e3a66360c2e0835a54bd188eeb07b0b87ef5b3ce30b0a0de082342a1029982843e32c667d303a553c19dacbc86f8372b6fc7272828fdc8d2b303b956897a9498d37997f644344eb8778dd221a94f171c0407287aa9ba7ef3d7e75a9ccfd168bff268451fc018dff8a3b0ff81b7e99582be3728eb127d31da9a5e11338e3c67915384d92feaff68b6c63118f2032c011691203a1ef8d47c55af16bc69d03d2d760139d96d00a363a3794b5af705396f3a05345bc24ba955eebbd8ff5d43c30f82ce9358c2bebfe43b9a8e72b998c3dae462c174eed63c5d278b1d667bc4330a281ec254118c5e1ffc17db47fa348dc566eda721a765f046ca1630d278b63bcb8875c60d3c6c25daf98f97daf64b4afd3129725eef6d12f224b17ea128c38c2b38da281341e032c8e922c19c6e29a40d31d4158e66a12d03fef24c909093a605dfe7cf2983dd8bb688babc4e84180d9e5ff577293fa0ab6744a55092613d0943b2651032a244db7d967bb8663e7430c711510725659d79215daf745b57c405516403e554ce4910ee33f7dcba35233952fdd17720858712bf8c146c2895ad1fb838acaf18d9c42891781d6620e8a973eb8ba957210cf9c81a6dd2a2da1f0e2d123d027f27af7c872465250a5b82486e7ade98103d2e5e29be017ea4f724fd6154bb08495f1829e8d0da0b73b4aa6417d198bdd72b9b019327ef21dd57a3924db8d51624b97995e7232c20cd83c726ad38b41f80df07f54292d71f9b7d8d4bdfd9fa088931beaccb081b59f00562e5cb49d082272aeaa1f4f2d702ff744a31deb56beab35ad278bf082f12106ef814df2d630d1723633315a9ae37733400bff17655458ebdf5c3cab9b2b8265bd0acd1f4800c203bad0ea55954b3cbf47bc626a6f656c0292fe82adf363f7285e091a978ff1f663f81420a78e6bb7018d5741f19244df0d6ed7f6d80c255a6e479f3bb6b78d2072ccf5ae4406e84e10369561cbd134d578b9fb0e1f5acd24a95ada6ad53ea8876a15f61c451938ee9606721c5748a6967b426c761b652787de9eb90d5a1a3a31725d9a795cf3a56beeb7e56c024894276850957543572352473cbb775d32d14e727bdc0040eaff9942005585f301c2f042c0b7f97aa4fb361d76fc748eefa23420e9e8a2b54c83ed3bc52beb99c59053c34809ff3c4ece1fcb9d7821a5b3694772a83e5453c62a81e9a3ca89e9f007b1f8d3c12669ab5ffd6e2bab14f4d13251123856be6a91cd194161e8c87498856ddf7d8bb2dbcbadc80a6623dfeae771347296fa08d6651363c71688e81bf0ba2494decede9e14bc190df66200ba4fa23c72c50d3d18f84c2b5c1694e55e35b927d65cfb100a09f9dd64605c0f122645c3069d3cd9c2ab6f4c8ed1d0ccc6ac8dd15003bd439475844f43693c2a2232b98e720bf73d8fe6858d1d6164fb9ea3c5507a85c18ae8c6f8d69b5aba8dbf67ac0072341d3149721ac48e1924a26af722b973cc848355d1f698ddd9358db457ac5372dfd8db0e52096d0e58805cbda71cbf53e28181dbf38cad6f7f8f9641a3e7b724cf42eab48d9492d575e437549d03d5e2b2c21635b6b12a768eafb0297b3c857232d6c669e51138d5ef178beb256c91984583d496fbc7a0d4a3c0d462467b58723cbf57116e1c721f4388320ba53ba75f8bbdff7a8c02af75d841d8132a23e172e5a1a1bb09f305f8e02ad35af7884eaf24dda79ef1592cb2fd42713086b4385e9b964762b68d0616787bb53f223296a98a9c32171c0491be9315e591bc74ec72ecd297aca9a80afef7b54c400d769a1c17d53696a285a0f259747e3f105d4872dfd266ecfcc7a95a86ec4017d432bd571c45656a7432deb83a4720f8f70789722d96f9a0234733b29937b744ba5625647ef6aa3239cf668668447e44689a4603e63c85e5401a8f55e93f00f1e7889e0fcd28015f837ea2be1e40a5a57db22c720fdabe7100282c77fa5459670868ebd4f85ce200c9a537b4c8805de4a39712729a5580214f7cf9ef5b75ef8d7eaaa2a1245c4f3164ee40b618c8529108067b72196702dd73f092894ba826ade6342a19f1fa7fd1f6ca18ee954ed9a476744272d0c5b8c7ec1480ec009655146c1f0ef8ffe8dbdadb93a0b9de9c841a6f9c9e1f364255a86d4bf4e45624658e94f6df4f1705cbd6c19290ff6daf7959ae20d83e083b1c07bcf32551c7d7742bdeb40477df4f784ac60423120453c12c6196c5179585fe6490a9e1fb1ec7460fec04d056e6a8b617d5d66d33cba85def9d000170f20b57b413b937e94ceb04f5b8096ad7d685f1e2572c7d4659695d6498a21766580b61ebaca8142532fdb7f30b9aea895e232ad695fef2539ee48f523e24dd72a577dc3acd9e85519581ca353a729c3b53e890a49caa685464ab833e9065d772fa73009e5d3d5309fa3812fd40042a0cb37a57d93ca4ffccae026bb9239d6d721e21b6fdea90f3afafbe17f2713f738a7decc53befc4366b0997a825aac0e50b897c455ee342665244e01567f85e7c292a0529205ee515bde564524809fecb72f2fc4455bbe7f9abf9d194a9a16daa1f5feaecd2fb839b12e8897acfe31b0c729e51d979e5e7c76abcc87312f2bdfaff39b9200e952c74ded3b7fa0a7b79c82cf9f695298fb08df3a06a7f04e76a1e6a83b498c73fdc25ad3c26354aeaf2956f341c2d762a8ecc8ae9d29f9a49153815eef3731336c928fd602ac06b398b02720f84a214ba3400491a37044421983febbf87708ae1cc64c1b81b125f3da3f272d2391c6821c6fe08bd4ba3bc114ec9cfadb8eec25c4048f4056a6eaeb119e625a3d3429eeb18cb99dce9122c46dad418d06b58783f2c01facf2b1c00915ca37261440068737da4fbef9b1dbc2660fe90d22d3770bdcde705de3e15c37ef933723450592e2172d5dafdb3f9eda8ee3c1b5ace1d3817a1527370d262cd9359b52ce30f3680bbdb612d92970e50cacd52daf7d1ff8dcb986a089271a17d375d7f6c03ab7f394dfbcff92e137f5bc893b31cdc52f600347d25ea21744443bc987b72bc378d9ddc2fe7b93b92c809d6278dfb0ad0b0ceca093fee729882b8a25b99724ff1646e5e54b9251b71d8c85de911ed0d85155916ee2bc72802ebb72a1880648aab975aa82e5b0b9fc9b330658f97bc0fe14f6e35bc93cf1647711e15e507291b7ef9a8e9179ef37fc92f4332db80889e30367279c41db944da6c47e14ea77205cd0fd84fe4f2ee6c2ff07a8d8e4eedab8dae3fa902e818340534a64e0e967291a27712aab74f762c10b671ec4190e962920becb8674446802eccddae93be725e51ff3f9442cc63d65ab747a266c5b3cf2f2a760b4719356b09bbc7c9e61372e3298c8143cdc74b1110287da8b87b315ea8a7cacb740d09c9696f1933071b38d4bbd8b3928478da0520d39160e732f33a72ffb8f1f1965aa41b9b1c8e81de6042d33c140e5651bd870fa7ce7b59ef00ae060106ca42b3704971473421cb6472143dcbe34dd49ca2e8b5b0120e395a40742d0725eb38392f71193b130b924f06c48f7569cce8e90a943501253f77eb0f1d5c15d4d9016b6a58f030f34b2c481d7cce95940b62ffe367135c2513211786a58d7247cd722a0f585db70ad1b76f727a02daaf30f273aac6356ed16e5c7e4432d054ee1e5b96fc2a969d2dcbf67851a29715b3a87a55bd6b33626a1918d5cc39782c7b828997203341d15e2ac675427e2f59536da74a5e12f6b366ad1ac4289cfb7a07bcbf0a945afb077a1669a866815e349458f30355c0a527d8b9bdb1f7677510c23351365509834db223b183728a006f62d36fb7a5e646c6dceab3ea10260e770f56094942080d87a36d4da91418cc11eda1daeb54bf4a2f840cf487584371e34f716f40b5da50e6c114401772f5d6098b7d3ad81508c3539764bab24ade53e3ebf247c7902dd0ab36931ad1728a92aa7a0f274b9de8a3f1474a47dac347d9b8f714fce9f9e7eefe3fccbdfb0dd2b00f33c3ee0b8010f9078116139a936ae99d4fc5b75dc58a7d6929e2dba672f203315b6341e44500129d7a10a7403bd841e7526d7bd6826c9ecd6ef443d972222d2184e5d0da9e29d83c5868a46b7e4d1ac56b4d18d3c3c6f702699c204131a08796f25ed54c66922d54b2103f4b23d43d06f8ec2f5a2ff37a6da5d6eb587205ce0c86883136003f6e67657e0297b410181c62f1c5c815e1a7f611cb0dbe13b6e86208dea41a175b38ec10122bc3d8c390ef24fafa47a3d11ef9d26a48da1cae6eb71d63f9844551f74bfd9ca4a09940050c31f17d9e76f825ccda675ab2729bf67cad6ff731422e19248c709de4f354b3f2b2274f40afa2db9601b6fee77256c5d21e5cc9493203da67daf711f64bdbe8072a126b2f44a4361393034f6a72387dc043ab8007b552a83ef56ccf694424f1f7a0865059de99fb3479a095c972d613c33f89a6f3763ef87cc18985a8bec051130096339d7959868474aa3ff472a91c4974896c239df3dad908929fa24823cf8097b8b7b0cd216c074197780c727a287a4a846fcbf18891315fb01c6bede33af49d1e7117b5f22439aef0a4f54a6add24885b1506b9de3418b5a2526b24f3bf0012ae9aa32888817cdb93e91072922c5354005d3dba04e7fec3742191610af48cd82b905f799a5e60e29ab79b7244e80f2013781bf3eac7ec648259e10430b147db22bd69c0e4a0cfcf49111a4e90425206a84bb7cac813338920aeb5bd13222fe97bb409284f7d279e199a30721693327c5bf88b411790f414c82b2891310aa57996a9e7b62938457f55a10c723c458105233e2be4d096f8271933273fdf80aa815e67667101ece243c6442372fc592d4c17ee154e4f5861426141d1d9716695e1a6d5cc753146a8dac3ce9c72ce9031513fe5d2e0865a6b3733f165c8f1b13887f3058e3dd85fc021a91a862bb7d4740b039c9acc30bcb1d5cd99441e2abb7e4217e7be10ff0d8ce022b4a9083ddf4cdcb0f639646a1ead092982d68d343ce4c60db51ba98f9ca631dc83a4724a6b9378a767ae9847ec7e98f3581c33fb836fcc50fea4f125e3ffd519bd4b72b1faec50c88b5a2b78e98d860840b4055a39baf069613785354b83f6deb40472a4a82b0540b7970188fa6598e164fab361abaf39bfa359292a0b9e49d5311272abf1a4a5ae27de3a83d550dfba4bf7f54eb2d51c23073b8041e13f04fd02fc72da0512178c3ccf28f340640cc5a8dd9671549475566132d5c5d4cb48c172237268d6b9dd842530e7ea1234b015405b5125a5f2496bb351dff17657b0a4d03e4c1337ac70090f887ee3f31074a1b7938936d5069559db6709c45d14442182cb728a4140c04c85af9703da27b54d623513e23837bac95865e1a52b09faf298517282bc15b016f8f2d2c636deef505a3bb52af7da4acdad7accc79af257402db4726fc2ca146f4ba65207489f80b9884330b62e7554e2c5c62c883b0c7678c3c472d14c15de55c4067c521286877fdb77b5e8bedbcd6f0c0f1e8e75fc77210b9338add33ec3f2d5f5a024b88aaa482b2f792730828935231be512a7612031b80372cedf2417280f86aca11ce483147c3c4f368857ed74f6b2a5a4ab79f2593f6f218265fc00b6b9344083f010297b507e0c0d3203662ebec7a750866d777c4f0e1687797849ee4bad4f716d7d980dbbbb57ba25a089511f0d0ab216a56dec3e8772ee825e2523761f4849a2b864e24c8137bf8248b8d658195d3120b58de42b64086a1eff0b0914bcc137fd3862e8932a7cbb106d99e1bd639a513227fb5ba672317d4a679af5f5dc731e26b5fba1acb94cd12e1e07eaab916ff1d0cdbdb2bad0075d328eff409258b3ed7a90c6a17b3951118bfe5e9db8d4297b3ff88c38938a42a7f210d5dff0170e723bc4450dbc7bccb7db3d1381f7297f4b81f84bde8352727fd03ded66135960b138e494119f22370b818e4ded1a23de7c518123416fce2a21191a4b9b44f113104bd859faa04d9f43f4efd88111f44a1c07c54c1239354a3a30cb951f1aa3f458f30e44329a8f6d5ae642555976a57a6bc6ebc02e364b72b42c7467eaeb0039d5f94f80af5dbaef57c37ec1151bac400888af51e12731154dead694c1c86980157dbf16635480c5213a2318e631b9cb0faf2c9301870d7296385162e489268a2892cc9941b289a3b8c292233c6d066854b4423a474c04722269b52372458bcb51d0f617011c36696e7e84b64dae22cac69a02e1920af37210defd170ed2859d0bf0631fdf438671c5e6f5385555e20194531b787429cd72aed2835cdb7f06e97020c48f2dfd9798d6f3a07512aeacb69d9f1c7ae373de4aa38f8610db5bca350370122d8efaa1e3ebe4a13499b60a176abaf7f29f604c7203c19a73857299fd7c44077dfe2d250431a622059c1904278b2bcf7d47c6960877327a29a4615af32e1b3b4d855f5c3208bd62ba508a0e178bf76c9e37413224d21cafaabf7c9dc2b294ca8f3418b3f83ce41571bf6b9cb8133e33fd042807727b48cae689a7e74f9f31bf162cc232f255a7e331236c5dbd13660236b360097243dfc9b9715ac93a6deab72aec8991d8c30947e1c63533c8880ad63006e4523503e73044f092d3a9a325908a95dbfdc96dbe421c65b38b1e17338f254cf96b72f595f55e71f2b4d928c30c3ce1c4ef7071d8ac7f5a225e1826c49a317c608f58a9fac31040b305d06212881eb67997aa874548be4b781613b8c7b82057e2e072e208c35fa711be35a52c2eb13b1cf3e47eca75e0e00c1afd9b96e4870f8c177216a4d346bde6994eb5490282d145298852fdb568b757e7d96fcd05a1ec0d9f72781e0bdccd4b739a0b559abf6953b282495be98de8618e8b15c7caa6e635387206a0d463430c2b7ecd66e4bd7b23ff613f3029880b6c35629fc3b34180664e43447b9191cb38fcdc18796965be6d91d313b4577b8293354757ec72bea70ec172b027b93620c25c73b6023ba9c99e617aa8fce58c5bde5f981fb5f816efcb16720d3a04877610b27fe21a2c62792a6ebd36b1c52959ce0692595dfa2f7d9f4d7247bcfe7a3159362750630aa38b64b0f5e3adbf9f7745df48ad8496a2dd20bf72bdb3327c2680962299b734050d2f98f6f1fedbe583e290bff6c75a3ed8dfb821f226ebf9052a0c99f2fcfd392cc92980bddf4ec6632c9d57c3b308ecd18bc972a3576bf8397a6e8fd2e3ba1e9897119d46926726f5d69e6eeab0bd107d5fe03ecd935f984aa0e4b9549ae66cbc2578c85d04dd0be0faa9fa48a66407bd3edf72fdb47dc1650ba8bce9d02d057498bd21fa1f51b61c17e5cd9ec2e1c002937472f56378d88df0c5553f0e7c910d529a84fe25e673c5b2198c34cc905f43c1c372a63163fe2dc641ea83ff3b529c283c64f38702932138f202fee07d13a6f81c70674ae9f758cb85cc5df20096caa29dff3af5609a3cdc75708e0bfedcf6e7cd6f21a3ea2717157f0041b7d94093ad137df230d58eadd7fdc9b785ff7d0bdb4532c4ce625d7c1adf41655ccdfc307cfc1340b14e456458b59c9b7b9aa997ad4c2a8fff84b42890c427d33f26a8596c8b3655dec0e0a8baccf87559096c8cda6e2861dfa69bb690e70d158cea2411a9ad6209c05b7a70b5e662c5767a3003a97c72c1ddf804af09d6563a1c453aece951d64e6e6a88125e0bfe78f638e2d5a44702c9ca227f24ad7e9688be5d7624f8c191b6f9dfeda9cf5a9f79e489fd2329e172951705bd7747d72cb3db4449afc6bc56e1fb28e324a62ad7c31f06dbb0a1a765040cf8cc51be9fca826d94e6e18c5234536876c8691b2d966c6f0c4e3e8d361c99e242b69e806e6cbd9de5cbfccd1c786b7f9dfd01b146fd486dfb18f6c881721f4bad3bd00eaf23f97ad35b5de0177f4cd314b438749aec15690dd25d4889722eded1363611a5ee3d653feba704b8be8b7b536e30df84fcd3ba0a180a4a2b72cc581f1ff81ffde40e1c92ea0584f0b0694925b3a2b5fc7731d8d28507fcf658bf4730657d1c1004baf59907fd4e75c75b29bec39268fb592b3867d93f25dd0cb214a4d5757444b51856b97ced9554eeb4b087bb760b6b465211132cf663ee5468d912eb8de89810a3ee8632b983d64fc9ea0e07ac462618dab1595a3e7b9a72998229e9f63cfa5067a71703673d84cfd3021fb23a8d3d8db1261456cf921372470c7894d63ca9d5d5d202fb6ee51936a95ee744195ba9fae0d7069928126f72fc8e084e935b4e0d4d8451fa8a6e80d0d1bedca03af0401865750bca6dc28b72a0b6dacf9cc47f30c35eeee854fe91a3178fa5999efb467669b1d751eec4f47210d5e42c41f67ca7f0ff5014e7149f928b7639f54c99d922b8a425b93886ab6b5bb6437ebca1529d9c93cf26a9c8e607fe749942029eca495700121d47fccd6679dff18d37b5aaf20872974368d744a57fe4a11571853e560b1707d64beb70721051859483b6682195ffe201e8e69365298bd2c6f9d3e227259c2dcc88ee9072b4c38cd971c29acf53f7682b1880874ddfe0ae08331903cfd3a64feff689c072b2c2fd559177f208effdf78516884eb171605e3a3b8fb8b5c6d920845190ae729740e17c49c1c6a587cd81321028775bd41a853b7864957bab0324a1223e517247b9b889c37dbb8c18423e7771c4b306971ad6a192dbf7120f7c55565253a04c135fd181a148c466d8261a78c3834e34826fba54ca405389f6cdc7e59721a81d72c6e0d6ebfa3a6d5c6df70717a16f55b8b8ecc655c3bbfd411876611e5ee82e9ccc78b2c5e59bab240111f86e6a7745932390cf2df22191a252ef6546132972e18229051572bffed287bde0f5b91c81949670cf68ef222a50016cf5471b58728ca899359285a141bd3cb357f56f4cff0b397c828444a03883de4155fa608072ffd3ad8a955060ce849e024400ab4254edfaf2cfd5cbb4adb42329809bb43d726e9db726d38779edc8e2a56f7b2e0490e6222d5ea35ac110f2baf1d6486a4a7285ca73086aa09167ea0d215e50f523e375274811235ea227ed544a3fee35d703e45ce3ef1804f6e2f643afa2383431ae78a8e63147b9d950094f4636c6daf872c731bdba0025af0095f64e1314772ff69b8ad85a8dd61ba5479fb88edb19fa24d611bff7b696f3d80b3f3f033a940f8af4d2792119cd95c9431c8c9ee999f611ea4d371573322670935b5d5645c7b2c6669547f31e9caed34f04146dfacee82f41dad27abab871262209d714a1d4962706e8f832ea582ee8dc194aa81d1ede7250197ff7673c1adf494b95465da4e19d8e3c0ef1d2dd8a26d5f5a7e18fae0172f023f6da3fc8a45e9b3dea5f2fd95c038696cf3741e8ad0932061917624c09722a3f4ded5255682ae33bc06a61bde7eb0ecec90a3e2946720de3d94e21f3c17219b3c1831cf58148fa459ddb893106d204c03f6ab88df71bb155580572429672264a50976873fe62a6c1451f227b028cd388ac4c4da81373f883310ddda4f572357bc6fddc93708171030070349cea9a82cd7e86eb9619775264b6593d4f1c1b44a5d18d6b8c0d24ce1444dcc4e485b3c3fac002d2886992227250e4f7a787726e5656cafd46d846c85776e13bda07d24197784a19bf2442c19a6d0d887ee97253e7cc357c413dc4be4aa17a5e713b5b52778ec38b14ef64495db9a8c55dc92ced532abb44be4c3a99548415bb2a823981c974e16802e3883120e2e7d0b226723585ee93c5b04207b1cdbb55bd4998ab530b1013459c2c4320e90cbad4bb1d72412b5cbea6088d61113629b1fb2c756a89f597f5b0b2360be55768aff3609d72928d7be6fb52ca414a3ec29ec02e81b48fe392062f16f8e0ea4491a9d67b0372b404194240d64c76905ca48856e486e11daaaa36798f34ab5eb8e40e98e1801f040dc987fcbf83cb0f5c878fe0fdac8a860b6542190a95c289e57670f37aea727c6fbc9829b4d9eef8e622b6d12cf7a067ba12e0bf84c94e85af69b4546246594035176ee21a57a643877b8df78a3bcc39f4ee61f19e5ea0dac7848ded799b72996ce70bba7555f8dc5d47ee78406f3779b9cd395b5d850f1bf84a41cf1872057b8a3e0b2519a60d890ba777e3b3d0e4b65804ea1d69ecbc71f934bfb0030572c30d42646f5ea81d76333cacb9be9b86a538d9ce349e6d876e33f7dcdf88c8724405db415eec80707b5cd1e8869e7795ea71dcdf4b5f85b935091e2aec8c94721e74349b137f3a98b63c0e62ce70a1fed3ed06bb662dbb044f7f1669574e8815581d422899c105b48e93d5fad7677ebef836433985083eae6200a513fc61e237b2c99e9a01a4111a23b855af2eed74f5d17eac840f4e5321d0d2b5b57e6839013ad178638f473bcce29b7e3294087ee61e7dfda0d05900407bcea18821b914724e19d5786c462eb7f84d09477b6aa2dae6b9a105bae78ffead00cc867f68b6729d1e6834588b46cce7f56d01859c738e63db91906df14b36cde5600224fb8c72a6331823c39c62b607c7fca87d0ab5499adad737575cd26942bcfd5cf2bdd76a3c215d1f5ceacf7c7bf73e8c19490a1dc566e1a8ece6cce59c78ff84e41148729142abb34a25223652baa3baed4c25b714bffca384c931deca5fe7c3c3b239728e46defdc6658420fd3277b5641751249a2d521ee89ae9e59d07a3023f2bf1726055741d299a1dc674ffb15653a65649fca6a408e7c2d05d0ace1903f64d3000bfc23af740158741cf8e8f62bdbd215cc38da43c12d6076f2eb446e055e0b25586be1eeafee1705a5a222bc50cada308c2efcac1b159bd7667ceb4d8a301e672a5b062ed46ff53f6b550523a8e6e19105fbb4729b57fce0582923d8a71b71b01a0f2d6cbbb42ed89e39be40057a35ac00458a02db018269d627904172ff417725bbd8818ed8844346ccde2f06eade4623040fbe920e0f207fae99fca0aedfa49f31fefa6fb6c501c3445a4957c245548dbaafa2d8e1dcbc1b95e7876554bb772965760b0dfa9df895a8cdec29dbe67b126c8b027ef7b44b9d36af8f4005f5d7267cd4e953139145147f4a8bc2d2c7003e1ce19db25f17915f31d85d5d12dd97207bcaad9185555a9b617e340e84cd64e32885a2785695f8112b07bb467ae8c72f3abba924323c1d910224923f188bd40a8326d6cc3b4ae68d41f3f6d240bd072cfc4e83c76fe0599ee5b7bba016ebce17e428215dc3fb4ddb62e5a295c0e22232bf54a2da35f551842d46c2905cb42b05f652f42ccea1c21c3a1973ee1c16218cffea76ab00d0ae5b69c118eedea81038e451f4ddc8e17b292f81d638709d17217b415745441d38808b05980195922b598a96341c13023ead38544cdd82aca0a78d96cbed6d199481b7835cc0b5220e2be928d8967fa415d6c699466f61e38726ebf3efbc12b82f0eead12fc654bbbb4b5eeb31f4c5683453d89883b31849672cfb08d258a320b6ffd6798baf59a0060338f4415b9cdcf1f1926bab1dab07e72870bc49cd19e6a365032c458e81b68163a6158e33b5b68144c52213eaf9e1d7282ca81e93a74c3028abf1ab24b4e37bd576876efcb2d081d5731965afcd8567246acb7497ea1f6a6b13f279eafc82ded7c54e80f5c0df5056f46268f34337a72f7a7a324aa2ecee7d47ea93e2478c7a28558381ee69027ec1299efc513dcd36b5b4f39b7448446c570fe009c7063139028a84d7c1b2794733849264e688a9f72aa147d7395cb37d2235cf4e95a6e2a1c629a423fda52e1f3cd8b990bad10e2721d88b332fb5dcba2a48f0a9019aee209810d032bf44ece885aba93e63b3b4772a8e9966a3195672657bc1dfa04f308ed28fccc453b5acc4c72b041f93e92cd72aa35dfaae371cbfe4fdc83614156b5c3dfd1d12d7703d373cfd9ae8230fcf872af5efd2ee98a8339b6ae4f46233c647a8ea9ee3396096ba6447261fececa2972022b3d47bedc60a23257dd1d3218e91189db3946b3841138c908d37eb48f6172dc195a8041f124b4c2549d87fc14c1981d49d45de239de64d191026d49b4206882797b605f489ad38d7fcef743e42085ffc15a951017ce07503c62425d8ed372907d24be1265fe710f7267eecad05ceb755c77d6970eaeb10716f312e17bf67240a7341453145b710c90555f1d9c3f60993153f51b8eecc3bec8497a01fc4672f3516963e68e720d85e1185a46659d02d35633f39f10482179f7061159353f72d599b01f4cd276a180ec3697ccda4c0defad93283e59172f077339bed639e272619c664f545f09e2f60fbffce5745adcab3c229fe922210011b5fae5b0824972af983ff45687aa4228dfc037fedcf84999ddb1b43cf46398f70a5e95fba81272425ecdb6a794497ef09f50c1fd7ddd0cd400d76a6274eea48f5f392f47bc06726ec171b1515d41aa45292b746b3d2eb6ea3844e13fc76385b5c8b9dd8550ee721c9a33e97eb3cf50047b2ffe85f7a5f93ad3c2d3635033fbd748d90ccd6e6e72a97933f4b61eeb5c8c4856dd25a177e90d217170f5e1af6c0fddb4b04ce33372bb714bb70fb722d02f0098676973162f5a1866be8bb85cec9415ee6ef57f4228b0376e65331e48e012e3ba231ff0aeec04fe88bcb74380ab892dbd8cbc349119b05b4a6b424cdd3cd1cc31dfa98c67e17f58f27a696921872e6f3613b1dbe372e255d6a7f556795462e1c5632ec8734db773ad7c6b247efec65096bab25bad2ea6cb74db1426b9ec13e8a5ff6458ca365f820a27de88b4000e92872d2bd45372b5b039cef3fe9c4d9db5ae91e9a71ebc93fe3ee5ee205b7cb2dff68e2711bd72bf1059718da7e197dfbf0013bd6d0080cf8723c4dc39b2c4673eede581e2a372ef3d13f41197b5ab50adfc55ba278a31df15c6d3d9e903ea3dbd8a8be7c14772c9d6a3d15681cd5224fc415b5bc250b0d616574f8842a482520bebfdc1fc7d724e68e780bec9f715da934b28f20036f857cd60184e2a81dbb5a772fa089b36025c491fb6aa7d83736cb8b63deaa92c5d4d216afc0599dc489903f5d381f2bb725777d9857248a863e39c5d72cc4af142168a550b5f770e74f95df8ae4f587b2ccc35896b3742271a7636b6c03e9db37b99937694fa34a474afc22724734248053815b10f0669866e9a030a55c122dd626962efd904b5d6d4bb9786e9f25a7372f6e12fef555455d831d64e0dd18584244b7e1d4b4b9747bd6cb22ea0319d2c69a1cd63e380b783fd98bef0413ef2237a0e42ed437238bad3894f8f73e87adf72ddc46b32a63544a34c1292e50d306d5206469f59283ca440bcbd1c52f63e5d72505d2b6881e6ee67db78c9d4ff0a67c19048b49aa9583d4d216d6fef75dc2d0a23fd08de6ac70aaa30a4cb3ff329bbc64ad2a0fb0ad916dfc616e9c167362b5b5516f884622616b2bb7ecbc5f65baf570b97cb15195497ce1a84b8f3d3b690351440e207fafe037ed5599057e278e79b64c7bb8b830e75e55811851fa1a3100382def312fd6de13d188c901f276a2ced868c4c23bd2fb61901b5391bef453372a0710ce982a1f89bc95015107803777e647510e6c8a744aa2e3f3621fed945728b40e5767ff0ce7e69afe4e1ecfeda8287121db93a28f2d565e436897c476d0c6565894765561887b59016290b022492fa777476851a2a88f2e4e99c4c6e2472a0c69bb1cf60dcf0c2897d9b06e83f469266a88db67337b190771c7681475d72ac71b18b60f0091663271aad69134ab42ecc1e6a962792b0f135e6ccbb7598726d79b8a37da9e6076bac8ee46c1ca87e5f01a3b13ec30fedaa6231c0b4755259831cd59a021de2b0ddb890679d5adf45516305a8b6bd132ea1d450534619c572d61cc648724714d6f8ec2ea389930b2ff485dcb4b52d7c9b6ee2508727bf5a72c5e79bed643dcfba85b8d995e88e21cae56809e56d6e3c5dcfe40566e80b700f7aaee1f7554bde7740d803e9ce0beca1d74b177ba48879f3022a895c4eb0be2e4cbda02195d0847883cce2e4105ec1f90d7c54bc1d8cd6aaa2862dcc68591f7281eddf13626646bcb66a0d3db727ccaeaea0b59560bc532927a159d8cc8766184c41e5ddfee032ea886d98275b738f4000c45d3c9e4c8d2c181a000aee79c41bdd38b5f014975f73e67106a48cd3eed31ed20bddfdb411c747f9d690d5e091724b76b3833cec28f18fb980f857f8d851cd4f7358146822d75aa088bd5df0854cc0c7be0fa65878b678f9ee091d0013b733e06ef465a066752aa50c9ddbad0d723f480d6baacd72b23f6bc93ba735c963f1dba17dd2f95538b4e64dcd3fe47002d4143e4696da8e80782a51b4ec86cc02fdfa247454beb510146514c0fba118176aff750c25e9eb57da15f19085b45f2341aca2aeb7d53a38ab81493dd368f413a9117625613499a6b3ecb663b2ba681034c81e8874cd2fd65b0106b1b74edc72d8192f1deb048bbc6df92485299e8e80f9606115ef5120006bd48d526fd04617903d5976b83389a3f24e9df5044c9f40760ebf8df78835b5f7f08d4eff8b347254ffd5f1b983269cd51202a8ba1af4a49dbd34b40b239a715b21aacd344653227844d54a9a2d1c863ab23b67e42c4b6b7f135f21f8dc4821211e448aa2064472b9e948c9478ff578201e199475b492a0a38b29c12ffb0ca6ed61f7b486fb3d28ed6730075a3823d789c87085bbff2bc884fccc32aff545cd95baf39f19c84772606ab80263b6e77f908bc74bbfe8fc199114d637827cd580db4dc4906b8b0472c5bf819b08d2e2f4108b704c3fc269e30c8c6c02a1e8f6b3fcf3fdef9e1ebf721a8f2e9d18aa0e1add0451b2e3f0020bd11de19c3a99ded783d8c4f7e8494a70822e1a40903859df4f5d46d39fa4a8594885bb09660214cdf833e0bab0e9db72978f2b984f442ee28e1bdf45f4ec4894f56d179c8faa8d17be8869c27890ba72fa8ead453ee705da9a9a903dbcce9802d3e6b123d1f752189a364218065e5872c16bda383f7f0c7471c40d562be6cf32ae3f2ebd5f90456cfd1b957795c56772b6a4920ac39caaeca0f58ccbdba92174e803e9bb04fd1f24bbaaf18b6fa81472317ff62f95f2a640af644643b7cebdac3fc8c5c70e37c84f0e3d77a862023e72372d402ffa825053d52a0039aadaab7ddcf841341588520a112f18bb684a687244cb52d48e06521b97b7773a49a6f7b6de597342fa540d81b66f14b0415f9572e4def5990a4fe85170900a604abaa0c701bc457b359a36311aceda4050f30e445737c261f6f6850819f2f6ecae502ec8e9bcc4ab7713fc58da28492c6c023b003091a1f9cd47d27282055725f1ec1c63637ea9a6dd1cc166089d93ec5ce5494285820956abe1a444a538736b4acd2f8c51b2ac66e74bf85b435a8f58f4dfe0404c11915ccb78813ce96568d9eade4a67a38c2f86c6c1b36faf55d77646ee2a72f2b4dfbb7cc3ed72e83b239e525d4e18fecfcb031dee9a96069055c3c45cae4d0d858c71e6fd5bb33dcdf9716f159aaa1a93889fcbfaaf56c94385953215c136ae8df90760de0f223933d235636ba146d06c16a17997e615251bf2d14f16ab1da483a8686907c49bc2b58d50b0e2ae6c6dd7db51d409425915cb70a88946397285a4c96843b18b172a5b9cb4e6dda5d5e220b72175a46701748d3c58be686e7276e71690237d83ffc77fa55034a36f054d9e21e5613d3da3e0c6afdc7765857263de2dba78c298edaae111a78c2385cc782d96b7bb398798cb75eb61e40027420b1d5c405c499b953592cca5d4e75b81d6731ddb9e3706ea77f95125d394ae729fd126fd7830e2ea5795b69f72cc06e7083b5bff1abcda02a65d14f3eccb3e1f6c958522a7e3d6e7e9bb1b6be8044ed52015cda648f05c78383a50942faf74720de809ddabaf81255a566ec65cf225f772411f3f9b40eac32674869921cbfc726cc6c658b83813108aaef96466050535801f17ea90bbb3a5eef3c02b3d125c314a01ea1da1b730bdbc6c906edf7132904c6e74f13709254a7d236a988316c1720e77279cd6c2d082b73ecce8737ff227c53db6e4f712a7d2ed6fb1753d8bc45d4440b8981be30f29da939020967d25bf44bdf2d3650b930a2fd0e0bf6f277a72aa8c3364f539a70ac755d690689a10b962de2decfdd0c5ec1a52c1c6535a505bd58f5000fc342f5a55a0e9257785e29e3e1a3c84dc25c89f29e2cd64f2ec04725b01ab1c5908c2bb70df86ddae58055bfc4f422eb3d29e06cbbc61b8f0963a7213bbb6cefd4004b5d6a4ad72e0a240019ff6590984f9f666a5c5ccaf03db14075d2f1e997a6f48014c13693263fa8ac11faf73cdbb8c87903b0f63feba7fd4721f3e39c94c286da7e37ab01aa4c17362384247c9a5afefadcda25711f65e3f727c70e1fee4a8a8ff7d709fbfa896e164c60b6e79505db79e8aa335c3050d3c7211bb6522d1973a22aef3ca411e08c62f55b824a5f9153c19b1085b55cdabbf666c6a31ca6dbcbd0ca14ddabfc1de7484451bdbca8292410c880c656e82a56871f664b139763f0d69cb1f4f0c357a0e0f8b4fac888e8c4d800839a3a2b91dca16cc92285bf90cb952eaf7b66b55736f735693c7f49012f9fd58a920d471d96a2bb21c78ee230fb6e11518af4d0cba89982bb7191484450a9108d3b97969c03d50df4fcc1c85dbcd308373546df31708ef7b9755e6cd1ac1209cdb7b0826285e200571ae0028bdc88a29fb765be7e4be6155ddc0e956f766343091bc10cf7b7c727fe4095e5d71e93ddc24842e7efc9d7e43b3b62ac605da8ec95642ba2fa5f27221f175f985937b2f1345936a968dfc65f8663e3846494a8622cbc28337eddd721494a485a7e493217e0951ae345f95693efe5a56f513f646fc5e2afd1d2e0872ae28750945c55cb963c3b4316016eec3a0686ad00cfe4fdc635bf603b8dc287219a4b0b80732de09cde1b98d5d64d7a68d40175322de2fdab257d2227302f272cb67b06ee106878d24adab94529451d21d60d8b968404ad07e8845ee2651776af51038e24e4b249d862e961ca34615066ad9d464bbddfb888beb46a8a0ef83729a1fa108b256774f6dac48e1a5cc6f898c274178ba9a3c3e1d79ec5ae7a4d472b82756d8337d7bf23a6efa2908b7a3bee434e0adbfb1564d29a35d5d4d5c234420adf7c122502b2f66099d8c2999ffeb23145545587a0ae57a2ad4952b2eb83acbd7702c7e2c0caf85437c10bc69dc314b81c27dc141fe8c2e289a376afbe16174eff0bfdbcebdb6d8a870b865f894df02dc2d414f44aac68e4f0cbbb30dd47241da772205068f07d3bfb6356b708df0497ca6180626ad9912df093c18c6060ad847cb7f03978d4b88b07ba95590ebae3f803008e629aa9689fb908d96e08f7253e189dcd6219ba0fae64c10683508920059616507b17bd2bf1d373a5ea7e14386e278c2d43643195ee3aa3c15bad95bddb406faf2654930e373fa70e8392a72a395df02601ac22a8252962bc675dc8ab5e180fb8107afa6c5352461a4cb72345beb60734a10ec84c9261fe6d6daa250959ddf99ca54c4a1ee287afeb5129502826c7f78fa166c28d91cb124713a0fcc1cfecfb7cc82ddacc8935d465dfb5320805800174605d8470a5c38631304d4de34864f7d6c73f0e45f741e17b1b860722d19ba75dae0e1f6bc065596d8ba15ba3ec96e4d6ae109ef54713186d99d9b726898844d621c06f14d19df74c4d3f74c9c0b5c4c9921e79d624ed7e16e877622266e70908519e912cda0c67fe84addd738e409cdb906801f5c142392746cdb7215f2b1286551f83daf38758dd48bd9bd4561613eb898bc7df8cb030894e34372ea08cec168f81992c76a04d0977ebfdb82318d7552bad79197dbac29d9fb3049f65ce2ce306579f46423edfd1d87d8e7f51f3f76ca69cc684e72f10f25eeb33b2825568c715bb52f729a13831a98c4e8f6b3fb2966dd2199ea79b74685af1572ae3e8c88b35eb49b826c39090c3a617ef313fcde6467cf408cf33dea1bcd2d72d1163d8501b3489d7fb02aa78d4dc767394fb95584fe7eff064eba4a34bbae3498f35e90362a8b9f283e6b7f7a6d6ba7aff9e181a3477460159e0b20e3fed92d309e9f37fe9f67374b794fde8f044836d54b3bfa2185a3edc82fb6cf2083847236750d339f5fc0065e48a1bdf86a8f85c97c1319bfa39ef2564dfd77ac0b7c72bfe8a07d19f70cfd4a9e026ab35e2eed92d22299ce83c0751f7e64ad760cb2722d0aabc81c44d30291a8a9e75c1d159d83874f11e781b44543bb01b5c62fa272daa87e897d234034294752c2de05b7d7096094d706cc18e109f288f7f4f34c22fc59139fbf14633e875f9719ba21f1f63873a5f1029d40774c0b631e95dfd54cf83f708e8184e1bf520e4b0e23c9eec51bee3e8b0912bde75b19385d118fb54841bde87d0ffc17b06776fc587670226a46688fafa52ffd09980289c101e450727f6ef0407d7c0c41592f377e45ec798d9bb4f7d9d5359f2f4d16919f4ed0d51ba2da5a26cb33cc3b2764de1e2b77b33429abe3ab70be618a1388b35898bb93635fbd8d8eaaf593db20db450926598c591e80099793743a4c1f0b686956028560bb9eafa93678672ef38f33ac29bbf17d226c95395c384092ee671616aff496722cca71264c60a49690f5cf732f81d5755bbb5605d221452ca870c0331c50b072bfc73d568ce5a34528e0071c54ad217d3cc1f036b9ebe85a484282abfe356172fb41139a1e3299f94e379f1e9961a7c07a83f1108ce0d7e283ac4b629e6878198a93452a0f2d98f4308170db36184b0fdabbf052429d143d6a18cc4050dae850cb71ae1da1d458970b0a8fc0610b4cd6d532545eb88eac9ef1cb7b04f01b43722530d6f26abbe8014eea5f41b3047f276cb447c0f2621cb7d07d6564a16f2572a1ab42424b8fd9462ec187e0c14db16212dcd37900568fc8b212c6352f29d9728bdcc3cf1d1567c5a1d3ef42966e7d9678d00bbf10377e848eba247e778a7272803dfd042b2384c2a38d3c672484390b3b06c623010497bd7505bbff8337634879cbb961ba13c347dbb75eb3d3d5035cf15682558a583efd56f3b1d9423f76720feb653de26761d220d625c04c081e899f767775709f8f853152aff1e649a872096a61cf220cb15723adc1f88fae559bd3391ab31f3b483d8efd29f0f239e538f6d1870ee5c80bd9a7bce3982e755682019d087b882d92c84e5ae92e18aac672a6c1baa09d627a808c42e5d377c9128a246a68ba3889be6f5d5a8483ba81db72c128265a2ca84f465a3b1b05ecbf096b26c7c0ea2cf633ad52b850ed0e437772583e38ca5293294963cf91468e21525558f96ce13368158dd6116b4b85d82e72fa3d6e5aead090d5d086677996c6d4e4a13f21275baab632da9839dea6fb0a03343c8707df4b41c9011d2cf43257524db3ddf031b63083610efe5ee046973672cdc66ac891d570cc23768aaedaed51fc2c6909e590b15aa0c510b54a65d6d77238ec2589669f5767fc9f8b6bae1c591c8fbe4eda01908b7f390a02b90c413772ac9ba1ab7cd52450e355226d992768808bb9f10417ce2e93dce6b6cd784c2072f333bf4cab52b7d6105263d7ef1cab9f0a7866c46ccbd2141fbcade1c658993d125045a057933ef0bbebb292fd2aae92edb45873e795fb286f6c2724fb181b6a5de1cdf79fa2dbd4417d0a821b534b6c6a2d95cbf9d3017b9953df505ca448664277ef0cd232455646a1363db549c868d1aa24b3d01f6fc7ef5dc938054aa57205b00cde3b256f6a8ac66460463972d1670272973f337d353a9d164a90780017c7e00279873e35a11823509da192d1561c917a704d00dc75b5a46d849e571a52b64bd06fa25804e2596d559a688bd6c924715456ba00ff7e266fd9c6bd89d9721a7674de03b92c0d956d006427f0ff2f395aac644c14ee4a0b111ab32cf0ec05738d613d903265886633cca9408d386ea4750bc8d87179b6f1915d925acd1f72b2c61b3373dda016df22113ab999ec335960f0e7fed2b9f8b4223845ec2d5f3293f9ef5c9916c952a021c2c45f61a8b0ea9de71d921a8ad6eb079dc32427e91202c12033db8fdec6635b800b3150890816ab4595cd221079d10634d6981fd153737e549098332b957736ee4833098a2c791f074989536d99447f14dc612675557b41c13cb6ca64f31fd998e00efe791116b92beead9fea6ff423e5a086010572870004d0f7d2dadb792d719be2193565e4cea5ef2af724706d2a218d8323297202fa4ccd72e01d0110fdce7e4982cd6d1e04b2a2dac722bc1776f287c4652c72c0a1e3ef7411c055ba74683701e1ece4b83d733d9ab4e486ac4547497641c3592633795c0c23f16655a9f544ca65b6ea9920f2e093d606d9465697306fd71d6856b7b5d7fc498a7b338dd362b06dee7ad604d8c34b623010f96ff6cd306e8b4ea152cafca7ff2896752c97d93a45f34274642dd4696477b30234febad1f1a026043585b8ed7da095b555af2e9f1e0aa02f46aa33dfcb5c58f6dbba2dcfc0610b20f56088d791bd6e6ef503b363099325f3e3128fbb1231a0e63aa06ea553cf01684168f3b134a0af092717fcb70d6e4f2a5d9bcb86c2b2b5ca6c1997b93ae45cf33fb3e51e0b8806065177269d3501fa99bc20a0d1fc3ee1cb02360207c918729990b400638ccfa4aabfc423aa49a90f45900b9d03b2eb9bd8a0fd636ff67462d0a484d4e93fb46ea80ae868f4ab16761667951cdd8eedf6bbcd11d33052e372e7bed2af4017024d9e45502768957540caa139b462bdf6af628c01d10f4d0e443ded76ca62b971b6e2d54033e605a61240dc1896c24da9b102565f1dc28f4e72a7944e5eba30e9322a74804ba2f8fad60cbe2f54fe86be866c6d0bae0cb5a50b94aa5a0276765088e6d3c8db40173550ae0cc883d1accb91ccd5868ca6301029d67318985225b632b946417c2293583635a65c3d24cf90f1e530401329c2aa723ed0133f72928abf3993604d33b4bb92ac17bc3d1dca332c145212bf82f718723fb762a81d37750655b5169232c2e06ec215811147675c85926c442fcebb5f72fb6cc04e20adbf6777404b56589928c4eab3e2e4f7930576705e10dde5e02f721eebc582ccca9fa0d71394a10ecb11107cfcabc26d3a628c6cf6aa02d4f88b72cf0656ce725851007d69cb3ca82a2f5fcd709e0015feefcf984cbf4ec8527945f621c249e0314008cb612087dc9136716e4aeaf3204cf6e5e0a1335e2575211d605baf9c99e83fcaf61cda72b076f674717860e1cd39e73b6502de78b42657605f414e9d68a183417f10f2b98cdb677aed2146cfb57466ea0228d76e8fa8026e10af52d954f032bacafe630595e59e38e1b52d7bf14001064e0df106d639c972888482c5fac39e7cf9d7ef6a2d92f89a45b954348cdd577c987b15ec92b0827218228aae735a38f4f18acea36f9f5b87b34fb2f5c9f5949ce12dd90e95b81b720847d3b63f1bdaa47db276994330ff398adb5d77b0ba50f7a6eded4ac5d8287228fcf12b8e453183ebf254bea6446724a82205bf01fb2c376d851ce98bd9a417adfdf8085b39dc4559727d41846ada2ec0c05ffb4eb887b923c71f91d2bcaa0b93382dc08c4d956519d88543836b8273552907bc749c7d0f531581be2acb993fbe2b0a089f4fd1768addc67043bac681490f522ed71094a09160f749d6d15672769d60d5064184b5e45f6f7a7eb8563a63959eeaf51f594c54130b4b20bd7172f90fc04e2d9640b2cb2d07e61098c9ec0adc8955ad00c011645d98f0a8ab8f243a9f8c8b71a9e631ee02a763ad2d8b82b4007d24e38d52b730fd55b5e0cdc572fb54d4fa447c6458d2fe59ea73bdf9edf6e82f5902601296bbd22c053222356fb31c126725263d023a3477b474bda2f5b9c01030fccdc2c16afd061bfd66d22e7d5d2826f01dfb838e11356a20cc24a94f8e2b30724f1ebebf0f24d96a34ee72e74a81345c7e23f6d712482eb4f59fd3e33825d84e182b2b0c0b7ee77c6eea1635a973885b9b21453561e2f0b0aa509e128c74d9ab60c4793f2efdc9f3657372317be20031612cf4fbc8312d23ebcd0a2c8676f849108d703ff3519ebd52801cebdc9f30de5d89c62cc74515891a25819914a1a779d22e048ba40b127c98c33cda84e02e36d414eb80353166169d0ae043333e147c96836a66fe86c6c3cfb27217c805babeb620b45382e8d86869edfba574fbc7f97e3c0ebe4701715ff9f47248090b0e42c595d91c2beed53254d9321dc46f399e02591ca8187dda3c92cd645ed2b1bc6b59c35b880e99559fb02857bf705562475e3e97d2008099f2684b7255b242f34a852de7a0bc16f81f65f2030c372ba2331e608a3c72a5f7de3cb7726386a9dfa577b541002c0ba014ab47f31ea124be44f142c5fb121eb23552e6726c21f61be239a4f02e707306db7edf71260b47099b4262ed7ef2ac5e8c2597725b28aea538ee85570daa4e00426bfb01a872ea7638ed23c383da9dea003f62524fdbe6b8597f300335605d4e00d5edca4705c7e28ceffc752adcaa3fa8230772b94c76f218ebbb1be545b36e2b23093417e94b17bfd0cd1d14a3a6fad5720a72db5ac514f1a568fac2ae921b3a81d5f7304231abda07fa59a52c63773116e7277b3ad4962452470774ce4fe9aec65c2cdb8f33bb678bd184ec8f0f699f2f5126fd2ca9f76fd6bf22a7ad0e1dca87d4ed8e22f6b505af51aec334f41a6344941d7c26176c7f628f09422bf30d2dda41f6a9b2030118b0f74913f4c92ba7b24e4f0901ebdf46a63b5ba1fba99c3681451f58871423e1b8c6338d6550bbca8cac72d0ff0ea3342838a6a9ce15b5be693a09f7d2e274ee67ea3726a1ebe7ca79380183b4209a1f247266dae16522a132f9d841c4c25a2ad7ce4186b0618450d29c72b091bed946d7916e721cfdd4dc9a0aa2e5f48a10a27e423a211bafe974d2c8720ef401473f3873410c2b864ef4b4be064c5e15700bebb38e8ef0d42a63f6ea61e141e2bd710e8190afc04243e8ff88ffec181cc750b402126a2c140461ab53721734a19cc4a4076f3af535ca181ceb9662dbd700c1ebeb0cd2b792fdfeb922729bf768b137e85e11606188709f2223cf75adda901d4bde0567ca693928c5cf72540eaa20821a5616c383c0d6ed8f2165703f53280bec138dfdbe23a4482d68721254cc9c43cac76c7aaea2effbbd632f5bc7519d9c500ab93bbeb9451ff3b5593553e2fa10abdc171798d953e705dc7f236e60e344ada611e19207bfe71f1e3fae9d5ff959c123f34ec1d59537fdbb226345a2583b2dadabf9860d029bae9772feecf89674117798eecf0fe9583f0936a5c4ca6828c4b6725d649cf1051e8472f962c07942b0067c32182efaa604589f959aefbfc2c05f446b4c8c124b567c72741a8aaeb1a8ed2e6cde31370e8b150e687ce6083e4d2efd97215ce6161a4747809a24982fd9b5c8369f86d4e2258ac221803256b7892a42d1da86fca6fc2833b933c2cd4151d3c30f528e88ec52c1670b45823647e0ebe9a9b1c22c941a290c68a3ce90fc853c55309ed747fbb8eaad03b96e0eb7253bc099d022020b1708727eb9607aacd5acc17f23aef505b422f2744441dbf786f148036e64ee52cb3a51f1e7ff977acea72b3c1765be5384cee859d4444210949096d6804ff8ad832c2a31d7da5e7d0a49d29a296e7da1a80940119c5e1229cb96acd18f5ea6f146b44aafc12ac9d98d937fbbce246ea55b3f9ce674b23b9c5f1ffd2d4b679650a958303cbe6a30ca7ec0914c8d0a92cbd30495e98a3ff7663dd7744cbc3160f2e6d072655e8c8c3be79da082fd2300ff2086999efffdc9d2dca712801ea40f85e3a372ee3cce6962d6d2006403b7b954185ef66edc5a9d103a5c52e6beedc4efa00b7287f46a0488c24121818bb77bc086026533df4f444c2372e0427f60c68c0ea672137910f304257202a407f06a651f0ed9e35de145fe148339f227820c3f0a3572763a4e2897cf052cf86805573fffd35c05aef096fe9351e4af608317a4fcd45a577abc0480648ecb2dd37b3173b8adea2e1ae29f4321466b5cfd6684a9a5da72c2763d5f22fdcdc7e24269d7a03c709f775db11205c457433cb219f676fc6b72ce63cc10e7db7b3935287519456f9cc0f3a65fc50363e4408b43c9789174c3191d82b2802dbc86072cf6f3a5be4a37ec37039e405e09b958c8f0b374752bee72a891d8e539e6155c1da92d025dfdcaaf875b74fabaf2daeddea9d8fed51f275541bb08ca963562b38a6b6d9014dd5ed889707e1395f76ab8774ce9d1198c9d03440aed1abc2472cb8e9c048de1afb6ee997e660e8e77bc97fd2e385feb1a33723018f4af169f771599590a8b914b2c15741a9b712b76ec565d4da06d96605e4517ffea96ce2c9fafe3f150f91cc798c2f24ef54006b636716192e0dd7497fc723739accc26da810571ebd34faf174467263e13b461a16a9fe550fc44ef80a072d829016a016ec6b6e9c66553bf16c94539f6fc7787e65f55e5a9a6d0a80b4672abc191160efe204c6dd28451ed2bfef3f625fc0ef3ee5cdb2bb167d596f34d7275566a7631bcadde7de07fab28b57ad3fb39d401c6231d27c9716846a439cb4f406c061aa7f6cd10a86d07245c5916b2e22f008d3fd66b32d23df96388db3a723d57b50ba92048bd131b48dc8708b6858a9289028d0998925a60b7ebaeef4972d5e70e37a07f3475a455e5cd242a32a299ed7b637e6d73dac00265d2f942cf3129b25aa86d08014a438e31c8bfd707084bd1741fa1af9327da20ba2c4c1c55726759dac70181a38cd544d82e44f827b81a0d20976b7927d173a49a718dd63d7234a2a11de16b588d19f9d8eaa8ac47368b6676d88d6b5956aba97e2a3b2d91722244d352f52c35f7d64529225329f0f9f06a2669540ab2ca0017dd92967d6772c2038300869f06818911e30820bbb8502e870c1faa8658e53810f722788e6022867d9498fc77234c9694fcc26e6b32293af4e8dc3fb00b6b5c55ef711f365d721ae0fd2451197b5d3e556ef34f3a40cf525930336bc9ad3dc891c4174db733726f4a994e3b6752e224a4204f385e21abe3dc8f70a256c80f9a3cb677b2c01c7287b6480df2e16659b34f6da5aad5f5447f9112804835a2511c003efda10d87725751d12592435ed242ba783bfffaf5366919eb4c02c2a4fda58513677e71ad72cb94591155b54a93ada39a63e9e3193ca3827873e890d2a66edaecd61063e13fa57769776861b48bb874e3d729e4ce6f23490cf2900a224e401c774207cf9d350d2d3acfb072990d35b02693fa7c3d86cc025d9a2fe0aba49f2ea2ba8c26771bc702c07b2a8d2bb982675dba3a884e91bbe5150d6993b52981f9d62152fd2672df8ca2c295aecfa06a4e0f03563f561058fcc2dafbeeedcb07190fff3e80ec6c0ac5c66f88b73edbea81d5b4311acb6812c17df72cd1fce415bd3796c6b180721d6505449dbb8f223c7705f54365a8e02d593285e72c8a7e1bf66b658c43ae678fd0723a1007facf7e4faf1c412b924cea7f70bac7db608feb920421ced13c6a3847ef67af7ec86b470c364dbc769e70a911f991f05f370cda94aa48a529db725f6efde85859630067a2ef448d4822a81c6b9733bd85a465646c88020269170bc26bf13767266931165ef7c0e50a0bd188d6038eea0466115d7cf64fd0625234637ab9c4a7f20029ff2a8fea43f40f375df7c59249f7991d721baddcc5ff6b721308df782ac12ed6af4d33ec668136a9520f8dd989ff0c1072659f71a2a00311fca9f02c8647b4f2aa056cd316ce73e41f9487f46b4db3656e23871e5380dc0b0c1ad1f19b002b3c81807f0bb7275745a4104ec5a714c4d18a450640c5180672de5ed025972ca4a07dc8621bbdb2d8a4a5adea948fd48e98c653e4b2aa2fce726fe963582ce144a8e5058ac4ad4e18591d302df7785e946d597cb7d5d1f7da7294498bbb2fd86953d05b8c4ee956586e7382ca9922ca9b20cf73dce89742a120e5c95844f8ae84770cc339ecc2324b9146fa9ca4d72a92b4298b92e1a87ecc4cc9d428ccce84d48500db1f9d28ff72c031013e32e3887985c51387f63bf6c8696b1c88a5ad332a53d9ff261c80e58d0c8ab3c08feb27beec548ad6f5bcd065724cf31a5db220ecf7d792f4b831d88226996609f9d0de3dff5a0b6218486b333ec9f0b534af13015d9d33f4f3a88cf1f21d95569f5e990b55bf245138edc9aa545e450a17c6b4a441958dad5e046f488079842423ce48681c62b802cedc9d2472593e506e78fb7fd024283df28b919f601c25ed99ba89b10a5d690accb966f821b7b25e28980595ed90fcc7da70d2d3170d4dd14a7891d00e17b7ea46b1efcf7293812f4d76becde111a86cda19f0a784e180f5eb552e51f358492e5d54a01972710660aeba0c40e363adf0d6689a7f6c48ca5417d726c531c28ae618b7d08172748552400acbde88aa288f89a4223a17ac0e26af9eb11dbd0d540f5b467e034be68f5ae9f8ec7dfb48c7bdf5643b5d8d31fa19524d73fa4aa85e712d0b72067290a295347a54f4b86c99f7aa88067509a7342f691d19f8f53b307203e058531bdf449a8c33eb372cfd893cb17db68433f7ceacc418a0d84c0f753288cfd8544c33a92230b6b8d5effe12def3cc35f0e8e2b56ded26ad405d152c90fa7363ec728bbca74243c29ee05d9d497bc748f6db86c2183482c93079d9ca65aa6760d372dbdfcef853f4f8d2c8c50c0ee8e51afffa21657e0cce714399aa6c695fc9435d1395a174de7087daa836a2a3ef13b327f5c88dc7227aa838df66efb6d986e432d0953c033ad634e99ac30f817361beefe1a5986878f37396c7a2c7fdc405a972304b8f6e921a688f393e8a74768e3405b9fa82897e39aabe8b731c2e4bb1867207d5fe5d8d4748e9e0c075213fb3e36230bce210fb72996c4081cc32d6df000f84dcb633a04371ccc1970ef29752cb15a1849e0c72e6c4f6f76c61c657366f724cdb9f817c5a489e41b8808edc48557feba7afedae787bc1fb6729e52f0ec655da1ef87f553b01e14587c0423b5ca72fb25f77ae07b89fceef7591212ad7082cd21c7b94109a7cc4acc868a2f8dde2582c4d931b75bebb3c8f8624cd04851e724e5ab1acf7c91fcce30643f4ace6b442e010352690fe9caa11c72a0b89439a722462102ec91b156002b4808749cc9657124cfc1e5f39a7763b46f6c2f386432fe52f30f2ddc8b2893f8b8fbd71460602c789ce0a5eb2b070a8531120ad29790f794594385110e9dbd157a23020d025ac4eccfce575000bc0b5fd5ea2b5f89040eafa70084ea5536d8b2e0840b87e79dc619d0c806e3b12899c26e93472035e30869394884c68a9c423c23e80133939a680f6971a2685afe501cefca06a4bdd7219454f31572960539f5137b3ffbe897e8386484929822f194fd1a9eb5ced355d933f7ed0b5fd10944ac5d3fabd2bf7c4bbcf65140e4ce2e3e5c32a07ecc1a33cb8d8e689ea94fb6affbf396a9c686b641cb192d6e6aa9be5924e3c12dca0861e45885833d17c7a444e1c73924496725a99129a57c5deecb6133d6077a14e2c72b91c2e90581edb286e5b50584a23adeb06ab7ab671e1743857ad2789dbffba72c36e4850020f36631a5b90bf7ce5662cc98e2eb7504822828d0fb791e4202372e89c0e53b464e739c8b2e891c6b98fd183e9ee5678eb0a4d921adc088484db72db5780dd3edbc359f208b610ae17af7b2e186a03483d2cd29211c6b781a417725ef464c9000999de6ed329630ecbc309ccb8811dc777ff84934b98ed2d4dd008e263992f8742cf2f117f50b98d0bf1f21b0278b73ba743770e555edaa0bda07245d01a53c9ad3cbe2d24dc68a1234af9670a1b86190b73b1680355ac3b007072b78873c3a55305067ae81ffdc0d26da492042c4577b05e4408ae0a02a0cfff72c21542cf41de7b1fa2de6839feb6329a49292395f365aa18462fef51e7abb372783aefb5b9c0d7728a96d06d5d7acff476a83c5ff2983e0360219ccce77c5560bebc03f6b3a0ccc0d4119c337bed83753af2e76f51ba932d202ac4f9de6dde08b192352500f169df3af2895a717120420d04d40b61c089a69e67ee381ed72a1f5514ad246d9ec6e18ae7cf1916851d2e8e5dbab928d23cec443ec1ec48ec33725d978d6c736a9a5dee6dbfbf67bc5a27314bafc880a00be0165054af5b4332723c0ba9bf5ab872fcde3e056a8860e7006c162a0f1d78841564fff7c021dd4e02edc942a59994e554e9b8cbf46f388c1604827ec2bdc369f7d14425f90f5cde728f50d0e6928f4d56880d7c885a43ff97896c6146964901c2c2632b1fe86d0d72882b2684de94a9ab9bf34d367a6e13dcee1b87b76185d3f23707c5430b7f3464e762c62411061b061d8ea0e88ef4ad6af7da0eae713ab32ba3bcc1c89179b842dc6732ad06bcb5bff3091fa13a433f6e966797545ca917f7adb0f1ae2dda0d00510e0238d8d0a8f19d696678a714c283e28b7add92e0caa6bec5b94c0a3eb272de394d8bbdb9d8039a54c61b864731c608c76584cf1316a1a7553df081c01872effdb134d1380d72167989c0774b3bbeb528f485e37fbd53d0da0a5b85dd6c72afb389d9aaaab37c670a78db8648b3a36e95f07a8b831f7d6ab8349f1c3f8929caa11b15437505f1130274a02f6e7595ddb6447dafb4b20e51a5e400c5c23b72fca2a18e4360b8ab33ae3ef213f3492abdab10d099b91a63628097dd24c1f5725155fb198b5ce058d4dbf0e58bbf0c9f79b712fd748a7b87df14c1bf7a6e4972c812fecb9eb5ea36ac2dbfd9ebd97c33aa8a706860c10199ac9ae22a72ecb07206cf6420631d59d1c6ca4b114705855ef73c9c0a7d353aac4097c5e788bc060bb0e4dc20227152a05354fe00ec4d7c4a7766816a407089a6c027b3842f7b9d0d8d4a318be81845b870e69674df5e7b4ea1bcc26aae66cfa0929e3f8241f17672cf203b286c2a1cf3f9dfac901ef2792e06d970946b3981e25b3845f66f6c01583b8a668024e94f056c2e4cec41ae533f1210de4c19f6ab9e3e5aa03c8a1f1d5a4f081c01fd7598012c0646510d0bae2e184fccbf97021cb6be86b12359f0283b39813dc6dcdebbfd4acdd8c37c973de29ba19e32a894288086131142dd19e01836a9aad4bba27fdf4c906638019a462a8d990789f083a65663ba16ffe84eca72697ed231946f22ff75293513b816a180591a6bf92542018403293c451b3b4416dfcea9711489d58cbb43039561fc7a3a9aea3712e064f78aaa23eba44e7e0115eed0acbeab836f77a907faeb90257391ba01f5b74e25e870a7fdf6f2edb5734134336a68473150ee9269b619f3213bab782a25ad541ac5ebc72ce01ccebb39708287a1134bf2106839287eac553939bdae34e7e97de357103c45a6e85355904e830eecb36d533ff39ae797151df0fa6db6104fef7320ed88dd546d3b69d2b572c510426bcd93c95c68d9b67d0017e0762b52c97217cbd8d4db4ba6ac1cf45772ef20ba5432e4ad5aa8c94a77773abc9da703aa177be4fbdf2f14041cd012763489095f46bb50ecb513888e6e97af6062afb5a874ddf3e78ef8ea720da79dc472102472762f56ee2411038ca5082f1617564ebbb64f230fb8a8d0295f9dd65072bc2c264334955b31e870f4aee5935e204026dba344511bcc2fdca28d40825368fc1f8602519fb2096c5c15d1dce644c0cbdd53610b694e75594a9331f80faf2f5d117bdd74e3813d7259430c15fba9864b4f7a060f8581c094ef5e337bf242705a10e98ff695dfe84f254436705e4a0e167a5bd9abad975469232cc57bb1b7724ca6282de4c81b8c76272648ba42e89a569d4769fa3cda7e2819a466d85459727647cd6504781d429380de47be906dbfa6141f27a9ecafd15293430ddc984d2ddffdc5775b51f033f162aae6d0666cd6ddb32254cd21946ba958559e393d23725aab84d9c844904f7efa480def1abe297ccf6ceae3fe1d4ba2c92e54d38eff72b04fb246fdbb8d2b1f9421bdb8ee4156cfbc45740ea8cb02c4adccd1de7fcc5f9fd2eb1865b1f58b1d10d597a3528968f3444507610c028adf5302027a38dc27a41679ea284fc2225614e1c9668e5813c83992b6971ee970ffdf9fbd8d2a4d6364f8e812cf80696993203a2d2ae0cd4de8d9024e0bec8c78631c299aa6f402722ac95e8992585993523b5cb9c8e9914d8580f0e5e69f56013cb88be27cadb3729878ca169965f82cae08ec6ca3a5fafa93f5d07a0afc76a0afd8fcf06a27d072e0b9a7aebf581fd706212a31666e15a759d1fceac990fbcc928b82200eb9f867c19595e25985b91ebf595fdad46c6306c5ffef7eb2a6c730c3b3f96ad25cfb5491d2f4c7a1adb0170aca0c78c40b2f6452097050fbfc6632f26911e8fced7a1d6ba49745c79445187ae5aba37e311d98756adde53863688bc58801589728f172cca835d6074224bbd2f6eae620442277459b3f762bb4633d340dda3661b6bc5723df72d8b373d1d91abbce72d8a248bf33066810c1ef307c68cad6d6ea0a007295c159b8563a40b7a9854167e6929d07c3895564c1c49583f2e34aeef5701072527d8ecf5cf5f2f0e5182fbec404f21d2c27e113001a49edd80d90d500e04872007541e218ac6146502d93f9511296f7429843a2d5c9e0ed13fcb17fc2ba656d5c63896dec518a6f0545f1c56edb241256cb208a972c6cc1330f82c19c49a37236ec331ab88d1f5041140fcece5ee9f96a6560e6ca8108f378fd0d9003a9fb518659c7eb916b45f620511d2cc56ccbcbec7a329f1ae8311db703fb39395c4e729b588d38282e75c6820d8f35cb11839cb2fa61c2cd358647400caa3b04643872b148bfb52fd2e8f196c1cee8f3160d5b33b11a3fa2669e9bad52348af2c17672e2da887bde0fa0500b07a9c6707bfd3266060ed1862c543b06305ad32fb01b659c15e38fe58115aecf48b281686635a2e39749618b9d7e1763cf8227e125385b15716650c5f391e42297031ccd46d89e37d0dcf65c8e34cafaff0b9067e3ef72c3be25cdf58fc6b3e9d69591086513ba59933bea4e4e0b463733e5e970ab66723dd8daa428d32b03b1316433f52b4f0c20576d7e017173a318666fa86ba87224ca83627da64e0855fc53805492a6e4f0c8066f508b7acd6c59462655d82f0b729ed544f0abd91cca1e7720d787b3670757cf1273936ba3eb391a79930308ad725f250dfb95036a48d7d577ea6ec797c98bfdc69d9011ca6248510c596b111e72355c32f19c436a3227732a568a72727a99a90e11478ba8b65fdbcebc8483d32ceaa769b930cf3db5f8c4aa639831595b0e4149d70486c8b4bd994a1bc96c722f63cfb603d302c1242508e20166cee003b71d8d4db04e27a19d397a4a3bce1d7215a4b0770b90c75e6a2b4e5c78e2eb41dc3cd486eb5d4029df2683ec6fd50d27c8ba185fdcfd25debf782ee5d8c1a258b5d687710c944dbe7f2c520d5329b872f0d715e447425a20be2b904cff539fb36d96593964ec012e0f852103a3d5af726d0a29f896e8a54a48b370e3f8070fc6c6c916bb6a74538c23cce576bbb084726e617394a7c493caa4be9d4e18612a08049ab91910e36a680ed1bfbeda13b763d9f7a0f42489891c9cbb78e0c9a542b0a2088032c17404ece15d014371309672d6d1727ffbe2d52ed8f0ebdf4aebda1a807ed7a99de5100c9c0a2b6ae199ea725eaf507614f4dcf454b5a93ab23a41c3879dd7ea04287af030b755245513c4700d0887bf66e4622e54c88a054b32113fc019e94a7391b285534dc420648c30513b53e9602d97f94e922f8a9425d71b9ff7b0532122de063c13eba909dd7b7218c17179657366bcd8ede09f442c4896be3d8f6cb83d983c1407d2ed3bd5ec0a72acdaa83dc427d9cde9075815ddc2247dbdb44520de68ad629d321315efef0c6367254de0c96c02cdce8d387f54073cf8589a4f33be7ee86e485fe43efd7f9972ce71c52d623b836aa0767f792aabee0c6158895a59d0c7d8d4bd62d3271ed972f4440e22722e2a30cf4c6b027a8929184b9c2204f7a0214147ef6096da076a1fa27c8a437bcdc46385fb79a6d66faeb15640112247aca6580729f7e973c8474806fe4233f1c8ba90ba69e07629c7dd76e5d035a67991c0f1b2f2a02a567d6572e7bd8787cac33b81b61cf78a9c0507a928ee121bf35e33f586917faff9ea024d741b1e4ca89145d69846ea6f2e6cab09d4eba596c49a4c0f6c7ffc6e6ba4aa72b29f746e3cd5c3aaeb5de194347c889c53544812b3067e543419d847cadd7636778883569cf4c1b17d489f571f59c5a6c25a168febee097bf0af3d33f860a67238581311dbd83f2423b1cc06224d1830dade5dd4ecab6d98399f6c066ef83d1c720c281d06ef310f20be4b72c22b46b60d28810855a7167d8bdd00d172d3d0729c3aef1c21ae2b3b225611094e5e620ef15ddd91809aae6823982a86b91c2b723e500ca44199df7c5733caf1edd3ca6235ed6c20b0a0353bd39be39e454e357221dddb7c74aacd50b6ac5cea756c54a6b5f08fc4edffa4b44fce8fe61c08e272e5a9fe1a24ed9ab25328fdc4f2b09405aa8a9bd00b70e4aa0bf07a1110f71072f70d58c8601d5edf9dba80fc545af26c03674796f92e75e776147138d8997a49722061dee1fe189ab6684ab410260f6b060c2da15597fffdb06c84e5c81f035cf2ffa4cebddc952c84794320d7691fe2ec1e33d75597c82bd0fddd315eceb472c63c5528b8396b6dee94e4449a03e1cf6f68ed496b2552a7be045b9b9fb4f62d1a8ec1b8707d67ef6852a90bc6991fa7a18aa9f60ef0e352ea2b96e8efe6eb729c05073918ce4db1972798fe14e866b4fe6b5bcfd77a63ad95f3944e0ed35b7242db049b04c1886f0d94a42de60943a1ec0b17e6e77dfeb14fcb91bd900b7d7298f064b24fe8289da2469d61fb349d049f4db627eaa08458e0b454b590324b720b91aaeca7cd4387e2e8454f2e9ab0e337674d41275680f72f88d56dca5a3d1744b3a02d21944fa6a6fc11095b9d389f587991754990f8cb0b0482df353f0e72005150a794615ebc705d859f0b5daed732b4e1322fbbcf6a06f3be73ea7e0219af1025467d9c593632783665765c7e5d1bd245a5523eada00f80850e8b4b74665f9da9319e228f6037b708615613aadc902e01752c7fd9d3119665c7d06eb65a86000592950bc8f056f0551f2adf0c6315cec628629ba5f35f34d551043c92724b64c9dcb8e429f3e9950b3416dcf13d324af3e31aca1c2aabbce7cdfe75af548b93d10644c9c5d308b1d1afa7123e61462e0b365f5c4a6acba2f205939bb8503a09f3679c1c6df7bd9c7ca5548bf4289faf02feb6c2e918ad1e6845a512c6599207802a8c0b720621a1e63cd3edcb0eebac2b4492cae06fe69af1794b3efb72d9baffa48fa4f3dc01ddbf661f690618c1bc92506641ac17154a0f7bf0f4a7725452c184c7f8fedda54acedc16f59544c08ce45522f8877cea0df5205817a1726b493cb2ce1e7efaa943406afadfa1c165d5bf31a6a81d5aac3939a029175572f010484cd392e99cd2787db8bf75c562771c562f8ca5a87efed1fee9e299a2725ba79e9564a088dfee76682f1464fbeda414b5355be500176c2461bf53b07d724bc5ed508fb1b8983d6784787ea6d91d0841bf9778bbed29eb828a7419c43f72fcc35683100ff72cb417b83aa3316307da7af51d92dd8b0998f5e17e0766f26fbd2237646b71345fa8feae96d5e1d3d32dfc911ee522afea98f1c12be7d5b372987e5adc45d64775f0bad4f03027deaf05472dec777ec49b66e16d299f4ba97219365fcf8d90fac558b88324cc11c5ee0b153c1c0321488acac9e0743c9afc105bf1c43ce72a30d4ae373eb9cdf514b547c616831a3f04cc0f614072a47dcd6e1427699fc67f04b1162aadd35b7a029daba1898b13c72568139613274c4d4572f685e872cea111aba7f4fac9c9b1040abfc15a869b413ef7a08bcf84569f8872aed79e83d0992efd13da57bd4bccf0562aaa5008c658a2dd60f79520c4e45f724621ba501ab63d271cb7dd61880f11abe2611535fef721819d6d97abc4674c25a0c154e97ca2571c7f6ddbfe26f0e900277396f61cdae899b2e5acb4db66f07277f088dffdf0c4e10a07b4bb5adaad438745d903db09bfd83c1695aa9e0b23113c0750470c5820ee8519106a9b461cebf0288259a75f272c0bbd31f61133ae56d2f7170079dae3520f62d6e686eb46953e73a5b57ef9464102950795e3abcc4935776a1f2db9555aeea39915d03e873c208371b4a277405bb3695a41c895cc7227ce6e22b9aea9122b460b9db9b5e2debb976f481c749c6a9284a2f8a3a8e122dc01146a377481804db600be75e4f6ad722924e96078e81662eb0a54647cde3a60054177a38196aefadf43cf2483e3b4188cd429865bdbc06afae293e8b35b720be4cf9eac3d6d54d11d2e8ea803a357f66aa7cdc76877cecf823456cc1d327268c3b1085e3f113bce9b164e052f81f2d791e6b3c5803b5e181821b3dfd3d472b2fff93dc3e6d5a1be57ddf2ae6350d8886657fe6af77b60e589f71fa513975cb1b43c6c4768699a7cffba989bbf5d377f82daeee1486c611cfa697f7091e232077c7f9a9a39c26899b0162a8a02a714137adb4c93828fb903e15ecefab4b9360255adda84bda1fbc5871e3d61285e380ef1b85e0dfcb1fddaa30ba696385724e82078a210f94cca1f8b8d911fb562d0cef2c8a328ded417a17b188b9cdd527258e5552cdc5ea031e553a67633ee5f9495eed392ba59960ba088155b6aaab0182abb69c8362bf32423a66068fa4bfd9be683215cf88ead155d80b8126b0d155bfe1318ac8040885cc680ecb214a18781fac66c52f5380750e7e8e7b8118c76309a4dcc067a518751c322753d4831a6433790a83edf362fc33a194dbc6afb907246cdf44f2cc4cdba5335fb7912bb756eb15c9bb352c3cd321237e12e4fc9637233733fbf9e7f553838b4162888d13fb9eb84ac410bae4b65319e96ac188d4472811b50859438ea3d0795448216cf6ca84f6ec94f73160eace71de44de11f0372b2493bc015eae0671ddc63b9f2975c6141dc6f81bbf59afb357c976745d2f413cc99e491692caaaf714255eae3b2357119ee745d348316c36fe1b80af4742e488abc961786e604e038602b3feddbbcefa8b7fb7d6c3638bb4f4f2380508d3a7224cbe68d237ae051323abe84f3089440be7aa4397f1c604f183363b969bc7d72c867f3880090ee783f10239305dd7c557b9ee6b25b936720ba88781df8f59872317d57c00902795597034390d56662228dc4a64ce080cdf52679805c88779e539efb3303faccf8e61e819a291b9aa73932c3e6efd7ea45420e13beade3537e1a2b1f5b245613b1e491fc2f212c6b6d2a6fc192c222a01379237c5b162014382cc6a24752e3d3e441942694ff001a4a01d09689686b00a5301dece1c1f6eaf57274ca1f3eabd78bb36c982aaed250be84d58dbaa04c4672424210b857fbe56a1a289938804f73e02099c3c2b9bb32814039acfcfa143166110d7d14d74bb96d29ef41dc83394caa15b9cfb1091cfbe3edd7a71728d3e00331c2457065ef23a143c1e7bec2282a9f529fc0dc2e0c6379ae56edad4c2acb257bbf726ae8e0bfd86b3e26fb206276250ed1729cd8ee266c0eda18e411621f8a77e26b07b565011f6ab7f0ee4c80c1a6e79f3ab86b13ac144ff5b9260aa7348fca57652ab53171f1129ceded014a0af8a0c113d8326e8be560ba0660bb0dfd3cc7334bc982ab5b0c72f2e570249d956bbd1391b5714d28e7a1a07255dfa3ceea26be4eb53d6678e172d8731ba84a54cf9469e36a2838425adb6dcc55a277b8c9b5fd5ccc9d598a8861dd42c05dcf37e045b8ae667a2b1f1e332f1bc9edfa7ea4a9b7455961d8243b725eed7272a444bb64ca083c8d15e4a553c45118d16aa7d4228b201b585c808972f120aacc40038a7ee41405612854361942101a237338196e0a73bbe056c2e1729df253dae58463e636d79ecd2d5ec4c29b8ad34d6076e5f2711a02beaef6da64ea34152b29d3fcde117d97a362befcf230023938b80ada38aaf114d3d0fd5c523ee2b74d0e1a766604d3cf614029447179e336d2591ec7203546b924f1ecda7269205bcc8beecdfe6c026bf264daf33aa9a0a14323a5c0c2549389e67a4d25721ed538d5901797fc90daef1d0671da9e43affda1d5324f396385f6d11d356e72eca6be110ac201d2fa1c36ab0f225b76d855d808448c034378c5586bf2b57958ce08f8ba2909dbb117992edefd89b03b2084c2137d86889d169e9e6a921778375fb636da523ff43e8e53326f53f4cd54f33a8f3f5d533e79c6d9b53f6bb54715381fbf6045586817f8c71144880948592e569660aab785a64af009f2f91e3572c27d6db990d4c965565d58369a01ddb968d62dbd7538000899e30f0099a36e727e9eb6cb06d5e02d2cf48cb2c9df66834f9132d6902fe4428aea00483d698e72a05c04f20bb32d0074c675cd553e8a0c6f9ca907529c4c0adc556c1302dd900e0d93bf594be15ea92aa7d682f7b2778caabd9d92188bc6508fdd759207421c720d29d521cf0e730e136df3ea3301ac3123770631c5360de9fff4a53c7859cd169fce9730b97301e1c7f788b355af98e34a431f1efe564d275817789ffcc21b72124efcca54b3896ccb5466e4bf683d61c4d25733dc307ea3a94a46dc00341d228181b2ce3c0d18eab52aff93299aa1c64b50c24cf8367f21dcc539e9ed7a98724521cf641aca8b2c95690edc9774ee20d5fb745b7b1626999bb7d313ec75d47257f9c9779ab36c6bb914011609cba92efdef06ff9cf2caacc97e83c02bec814d2a6e37d2b8d5efd26a285a399c038192f7daf96486acd1adf460112a8089b21f5c6a7d1c06d45ac54f08f00128b133094e505b37478d30ad91f43b6b94970372824b313ac76f472fdfadc1694b3ead2ab7679d2fb8f64c97cd1533c2b13ab0727c9692d5a06cd1f245f177b38362071ed5ff0833d7de5e870e77278ae854ea528231a64ef5dbffe86bf3585d64357b7dbdb221c7b199ceff7e4fd44b5c89aa72ac05beb495cfe867f5ffad9a8e117871cf46daadc612fa6cdc0df9877a202b51c262bcc58e8bfda23c307690bae97c88a8473200ba3c403af44da09d4ca3b05aa4db56783922056ef26c4603ba3a2027b47e62fea30b586c6f17a459ec2f3808a9c29cafb3fd08a8cb1a39860ec610294d954748b528e9db248d39b6858fbc72bea372df17ab9b4286c22fdd774b18ce4cb2c02aa05e7b9112c117e5ceab1a72b4c139bc2bb3dde9b7ab7b29b35cd5e9bf9842b9a1adcd41e02cd19a7d48b46fde14766f1b0795db999fe10302c98f03b459bec248a2d95eec28aded9a2c0c1a772e43064f82cf2ba4351a568a6fbe1386bebd09971f21581422eac8dd5fb272c6289f84ca11ef8f37d46a255e1ebf517fcf22f48c118846146a8689741a832c9c20e0676829368d69c1e8fb8fc34f726e1a36c807219a9da5ca082cd9377b727e7aabc24c765dd45b44b467ad7312f54a99a6c54bac5f52749bb230bacc7d72b78213c0025b24818f60cd05ff9e318793a81f786711a64f6ebab99d2b11c8729a45517c59c19888116bb30bd374ad9ab7fdce17e1cd0652314981a66af61f72d3fa3c6892bce9b15bc273013a80c0bae9c66dd4d682ff9833f0959fb58493727c9db7dbb8259c093602018eaaa30b055ee5b2ef4a73772fa34ef48b3436fa72d0befb74069ce38244ea3058841dce209d7e1932c7eda45ce65d772979fc1e728bf50c2e8d2e902a3fcdf41e1afd462cc42807f24ad0addb2c075a2e57f1c1722be29415b6ac09db16d14843d1cac0a6e6c9526709f52e7f826ec84ff24e0c0c23e1bbfcc94a77a4266685154ecb2684580d3111be07aa59b88773ec8b525d72aa26deba1959cfa5db7c0c8fc6f0f6213a653a11e3e37aef9bb70a1419f39572ab4dec91fc57c4bb85188468e68905be480781b989be745fbc1d7c1bddfe4c72fb5d3be1fe00ee4238732cbd0797e28adeaf2c2d3de4fdeba3f1a49eef4da0203b4f690978ce2fc3a69fa72389618951436f7980eeb464294befb4c1fb2b02725671a50d79c7131d9271fbd4b45a0724baf4c4c88123b962e33a1bbea50c52270ed1bf042d251dbc783b4495d3cca11a029b80a06a17c0a69bb4a859014ccc72852f44af523b131b7974800208b5dca198fc7988ffb1b09a9997b2bde65bca1a9aa644ae8114b32684bd824f1870489935d91653ab098e29b37c272484f6482335f705e84444debf0ccb9a39b4b54130a36dd80214afb579d4a407fda446bf114a92f4e57ddb6bbf792c0d3a4fd22b4f6f97a6fdf3858424bbc943a2ae1733725649820ecff83d08d7608f70f9e89eb3ed310db179a77a5841d1365e50741072aedfb4129a2bff0e596f317186fd76e9e3ba0f6e6f06a23a1c720f57c8eb1972fe2d78ccc5d949b1106b2fcd1aa94eb8c13fde0200689fc3a6aa00eab6a5d0036a87dbcf30b78090996e7b072e2da752067a3fd8bc1ed2cc0a83887db928cf5cc7e29f300787330becb75631af63d310ad54fd3d2800dd365279cee0d43a464b9db4b145abc56c3f8774b30259e70a6a47bc56b1e467d7690e79dc81283ac026640356ab5309efc6d782325532363f5f288d00bf8fc1d8b8cdf2766c1b8de672a5f0aaa01a10406a8acdc499d1190e86eceb9e1fc3b3ee91935c926d2aa3b072aae68f00bd81b21466cd2d043819b4d3bb52996f1ba0f1349d14b51110559d72b7b46bcaee6aa9789d33fe25b996dbc6f6b42e1e69aebbf37d5ee230f0bd5c64bd50fc00badc1ff1ff00f5ac8625468ef2dbdb62b07960bdd3711c1414679472d9b785e050f736e9fd132a3c6e4caa8ab0fe9a76afd1d29a8a167a8cf3141772757146d744b67190b42a77d8c79d39106bdd3ad617b58b7b67d8b63c7c7b57035be323f651ecad532fa15bbaadcb05a3f0538a1666440322d09e878ea3cf346f562750d38ae342d1b2fd6e324a1a461a5c1e3220191de72b0712097563b223724fe1b5172d8c7977c594ca9cbcf1cf20adf62ace5e5a6291a551d8253e3b4f0b113da9c3d0c25416c4f79e551ef3c580876803ec67df63eb0a8ff05aa87a79726d56f9fff24cadc3b642fc062ed6931720ec88b9fe76a4a028646b66b37b04711f903d44d34eae56ab6ea5dd4aae3f56545a3dfffae996573180b827bce46352c42773df4b1473aa8e92d0b34a87a69109d7308b72e8a6d2d3e6c57535d66072e0b68f2fd87e1709fb71d5a11acc557dfdbd7c8b40a245faba7246221ea4a672888f82a538231cb41a435607f4affc778fb68dd63f53fa824413e55a7beb0f1b32e4c08e699ed17787755584258880740f676e8511fe0454de4258ef5aefde725fdfb98965f79947ea677718892f198757c3b80e96019ed32749ed8fcdbd512fd0d20f3674ce49be36adcd6f3a689dc393a5960367e97b46544c9418c53717641e8e410e6afa928e0b60f199d3f509fe76b0ceee5f2d031f264fd923737a7b4cb2c863f631817c8094e6c6fe5147ffa370d71dc332f6762db3c372d9347ee21076e7f27561ea20ac10affd97a9c6d4959aeb616decc49bfd1aa14df85af88972acdee2f6991e76a154fbc766e7658bf5037f11f48870f94b7e0430a877f37a613bd9c21da8ea6ef9a710ccda758c7a33bda9fb1715c137e107708a98f6b38f721627f821549f4a002cb32c5a38fc2f554b1300d7028adb3144ff18330fb563505ed87d5cbfdf9d2fa61e3bb7b65d1cda63a6d7d414922a530ce54418147bb14f9eafcfef7928a5a7dd2c929eb9f51f58a1eff77e9cb314b83e52a95de6275854178f6680b6aa19f9bcf43dfd5e595c2683cd4470af24ee9416890fbf0745af72ba44f9c0962d26e9f636dc99b70b008fc9b6a88e83f76512757f6d0be0b3050c8413a93754351b592c77b3181cd8a35636de75e76536b110b63c54c5285f9c68badce0dfc6a65fcbb64ca9ca4a7c250d0d1c1622fa30c25a75ffab48cc640b72619e009a43608c7c018c349778e317e45681586c1f5332a7b786597f47b46900b1b52f784cb108036240f74f2f09efa61b01f70bf882eaa2bef2b27c4f58e401ed9f30010ac67e25df2ae5cb400d75ebefbc45a15f55950195fd7310ed6eb219705d7742e587acc3d48abd05fc3c0e538f93a24aea1583ae48b57ebfeb9dbe446e30630dc11767966ec172b6653fbac552b274c375bce741432b6510031280725a6e72c461db606e070e57a10293e9779b687dd129a9f4ce05bc553d376c25211070ff73bd58be2840a439ca23c1961e121f3203c65b1fa4877eb6401f8116379ec7cbdacb26f25e46cb0489353f1212d0f106b86fbb4454135c67bfa581c97279ab07bd77498155b83aca10105c8d4e5101167fe74922b4f534bf4e022850722add2be6013fa7624f82707d6876598bfbaa2196e44c3082c542ed2a1c92f2476c4292bd1b9827c9cddb0d6c80d2bf83860a9d5e4f6bd62e2d0043203004261fe11ced6b8de4551371c17c5fb60a66cc69fb62057a9bbf18ebec33efebb716721742455a247860efcc2f00c7b7ea6900170e71786bd8d26a99009a8efcd1844c6d31bce4e59e60d867721923223c2fddce40fb2648eb026be30cb2fa4fc41e34d458d7f82f16051e15938776fa535c1bbdda8e7a315e4066020f4cd6be6cea3c6607b970c0b8e8d34715c9f1ee79454c303ed82bcefef856f8ccc2996d812f726a64b39c31aac92708d0c73237d3cd8aa8c2e516466bcd6a7780064358b8d372fc5f9c53927bc9a6442d46001c99830153f17fad36b0275c73843f307a68b86a4745f3f1217d5b37712c372672108ea21f7138cd3aeda630c3fcced9178d5a664502010f0d9aa33ba4230df071e73c12fd542214ec4cc34fffffefdd1a5b987200eb7992bb2157852ea79ff66fe79509a497258e6f7b6ccf7ac673f8fafc5321a1ddb4ed112f9fdf3497d90cc8e598b1c46f103d82e2877527145c5f2917de727ea315d623a08bfbc19b9a0fc8cabb05fb4d5fb99b9bc3c5c0eb7a2b233f027289b36af76530daa25b35370dddb2cc792a8025d58f13a1d8adc220fcb781e558093c409dcfd1c36dfd8ca3e0b326f3eef7c15670eba59a9a3818ee0a2528760a8553b84fd8888450522102bc1cac245bfb6273a1808a627bb5972a04a4d38672be2259aaee51d0b5075559f77b02604cee7fd68806fa5ee018a8b9e77dedec0ffb9ae70740b772f152146b76178173a441d943a4f61380840f79b3f6ae48201990c2e0df3d33677fd6c91b22ae778d7844c0fe46ce8c4a5a153c3bcbbc3ec3724651d68b5bd44264f6cf684ba75f0befdbb3d848bf509e514e5bc1d772504c149e019af514fbf90a4252a8f60df4661368d2c01d244673d6fd81b055fe8b8c72e48885d38d7d856fd6b44e6ea9e32f5965469e9cc1b08760aaa717378ce16b720d4e539325e9dcb320cfe469e52cea36226c524bc12647c94db38add3e6f3e5bf395e9ce12329e296dc0c07d78270553c074f3718d1ba863fd29d601a64dae0e5983f7166e9c61ad020a48b85516ed8c97d685c9cbf5fc4c1d801688418c6507c6c9d3083fac8ad7a976ac79b1463fdfac0e9ebf0dc4bde210b7c1129b6f6c7268408a4d9d82cbc592b93bcd0f72999e406ce6aa7cfa05a6b56ac3588391c9722a8b19cf90242be0d6435a4d59089ce2067453e10b02e8eb7a4f7db3868a2372289e36d10e467e9d44b3b287d4f9a36f379034bb92a82e54430061d39fda9d00d7c282f17237d06bee9e6b84c6f4df0c92bb4751694954cd02e85eb1b64988055dfcb305603b16da23e2e1b41e0f4a07ec3a4a3c6ee519234683a2508bf26872c294dab9c9565ebd6ea72da2bfc80c28325a2cc4591fca9bd0ad35316b1445065eb5d65baeb3ece547a73f476423b02b426a455095158f814428ff3106b5215e8134c7045e0a81072b60063304f64acca6913fa846eea2231d007507b45323416c2a5ec0a03f9bd4ac97b9ae3473646afe4aadd9e1a8eeebfd0182809e7252325d90778dce824f9caf28973d0347e0e8e1bf2cf1f9e19932214ead28dd281242e670bb87427f0aec1f4d9f9ae432c97fa1d0a38201e15c381f3d2c1e30c4a57266e37f039e14db92d37b0a105e2576f655f0f9766053570cd7e642f3cef56d72dd1ed3a5d56262a21a11ef0628e101087a24e73286b4d3971a6727b4538b487221f026765be0d085c45457a7ca5765bef6209071bd7a198a0d904d271a8aff72ca6323feed0967b0eccf5df11c07437554b999cb2dfaf3c540a57199f2676572d40899f772b8ae374404da96f9da2ad8a55875877f11d912b56be792056afc72b8264ff277978284cb14dc6ba5fb9e5aed0f514f9ae220b21b62f8d99e622c72e3539714c8d4a022a1e2cea1f6e2f00e86259857108bd66c7819d0a4268bd772a4b4e6f5e24db5642890d028b2e2b3047b199cb578c57bb20fd9bc42d713c94d7faa19a9c9844e13b173b9588ee2745e8836c52b31cdee4210822bf299e68c29fc06e5b3f10920a55dce11dc0ba1c945a9cda7b92b7166677b8ecd3ccd19f52a491eb56ea7f5ab6ca294b38e1c76cd8e93bed755c144dfe0b9ce37595c2983052590d846a24a8d2278411976d92e1925f13a6c96fb06e6fac812f44d6ecfba7286e1d3ae24a52e7e5207a9d86732031664cd39c52ada14271c0386942a4e685d2969b74d3518b23211d7feb3e4ced944690e08fc154692abbaa49e7771a82972982980f3a8ce758fc3f0ea24008489a709bf5e8a80389bf174405fe91f80d3729f20bcb6c6e088794ff483108d2070625c8f0674880e3674e9ef4673684e3a5eed74a1aa1e1659e7bac3b245d2aeac15a09dd0eecd553f8899f48538bf472e387b978c776a2fe5394415af63e44584fdb2ed899ed0310723e551792c5b889472d698b720daaef054d81075c64119aebbe44d221719cb2d22acee512c88764e72ecb636d8e537325e14d6526aae6fa77fcd168b814095a5ff561f168f5c24e66aefc593960f7b04fd28841f1c6e18a0141e7a2a69f174499919afd50a26019e72f66df4d209c9b024ff86b6599563a9498bc6daa179ff146706569af81222a27247a4f9f0ddfd799493de9142409ad843ce1b3885d2406280a1c5f0423d64ef727287e3358b33ecfe05eb669c82abcb56b2e13b2be103aba98c951f2f34434b721e9b97720cf4fb62d863d1658441d35aacf6ebcaa9d7eacdcdf5169284d7a834028cf6ebb7f64e986154dccbcca91c6f6ce5eebaed0fbac15c33347452cc1446af2946c682e123d4df75d63eeea5429ebcd15837bbbaf00a5d41069bf790fa6feb015736afb89654d72bb7c1404417a58df0f0fa9ce7b716808a1303bb87280bfe6e367e13bab1cdcc81e29aaf1f2d1a93d68c4a4d8d0ea32f3ae1fb7ad4da0585689f16305cc99f8dc040ae57f923287c326512973500c7e29efe3b8259ee7212a2374524a67fcaa95214b772719049ccb2d44b01ef76bc8f52bb870afba0549972c3e13b3865e79e0e516e4c14d8f6f6299fa3e4b8d9327ba9005a127c875cc7266fb57b771a94f2086d1a4e9e4d99a70a29aae67b7de47e16327f7fe7af721728b6f3aba8d28a6d9701fc06bacbd6c420dec7e44e4b035fb80c4633e53a1d24f6eb3dfec6f36db2ace9c3f48a765caa7222752fc39595daf7333d36746839ffce315ad222ee581502e3855752f87455b6b9164da4f25a62c7d1d6df200b0a7e9c1fb5eab476371fc387e2ec6cfe8e7d5efd7434d62fe1e3082463487c4420ee32c20365c887edfaefd695d02288988bc0eb11f7f6d2e5e901f53db9c2987295d2edd2ec57265df53cba39f3f069d1bed55550e27e511ee583c6a8a62e3672b3933572bbd14939434e3488d89c167b77c93b0801f380d32cd5dc53865e6672c1a6299854d4882fe6d342a18cdc918737b836ecc7d204f4d86fe1b85d7e090b3990b0b9a60d5d75b3d928a584de4dc3ab6b3b9158e716aeeb08685e044cef09a2397b7632933514f365c69f0a9832d3b9d315b0b1eb5a38fd5d39ac2a9f9072dbf101dcd1ac5f83c7e32a8a1dfa32261d89414c42bfcd1a5381dfc19504637265d34f63d16b8b2d17e178fb24d2729b80892c992fdc2bb34b23ca66fc5550728eccb512597fd5f5824092935197d224692b439fdd1712d37bf679eaa2036b729b99ec72f0e42c22191fc61a9a210d5c1678cea0f6367d0e89e3b7338e27f1082ba86813fda8aeac5433b8e93c903c3532f06b0331601a6baeee7c4be6024c323f5126c48b0233032283f38a16883537f3a4bdb5a4e5c856933217d107a63372ee4f2913580d0b8b8c79e9a47296088922bf0b44ccfacbc665dd1aab4fdf0d72ab0654ac64dab6cfc10c8baf8849f5a3f4300563b899a857a9f4c9e92324c042d86deaee7f630281ccb9e25bebd2050631ac4c9a887d2c35473a7e5e20cf693639a2398a90b736110681f5ea6c25662b02d47c2a3c3156860c1da1601a25670c64e22d71f1340936fbff09ba9369004fdb0879ff73baff5e8a17aaa84567317259df0838a9d9a4a59d8d0636ca8353d2c143a44690347dd6f46401997a223a4b19c6a701a060e6d86c2c3ff28665e04403bc79f69366f5e6b6c7a0e87c93b440fdf0a053a0d559bb731bd4dbe2fcb2992e449e5e0696b5ca91f8d8aad06ecf72797d0bb090b5b95a2477fc6740b1eb362b0273216b2cb906e54a1de60178c126195b5749e5cf46d70486534a769f164ae1ecafdb8bda988e2337b61f451189724ba8a1df625ac6619acef894b6192e637339cbe5cf0020cffa0abf1791a6374d0ad986875dc37e1b789444055b7a94d12faa307428b0b53e70a1b0bf7c282272d08f281d79a03b0c65e05b15c08d56e292efd1f947ba8d4e8053e844f30f2b72135e7e74c7b9194ef792b8a114a0808cb305ed636719110bce40dd3d81c37f6d03f48763f6693ec6fe9a6c18543b32be34a211e461b3b56d18776c663471e172ea99c08c8156c4064ff6501a323ce59d2695b6fcb51d24e644dc524852a78d174922e87939a6623574d20d37c95e3bcb697063153826d46872a3e5eb95748d40ecf256a73a19faa757d611bd946f595727c274b178ef9988c5b72f380cb110676ea0621fb6d473672e8d00204291fa866fee58af704639c79899b8b32f7fb73c6c5e4963f32c730b776d88f4b13c1651c9b849b04e7514353cc2a6a218a39d72741bb3aab61c722c7d7cbf28894312fb991ca231faa05560555d5b47c8c867530ee218714fb28101318971b66b43cf4c68b4f3a8dea6b7c53b22cd2a397fb021b989e77e0847224ef04b713ddba6713fc146993ad1f30385449d36603a971b7271424c0400bd0ae86224bf06962de48c21154cc35fa3e8ee2dcf7175b8325572cc3c28237d8fb692879725e72f49a02d64234a10464ce572d13930499e7d4f4cbfe9643f020b5996750bb49ae13f7969c101f9b339ce78ca6ded101516081d72e2c25fa9368cc8c4a458ee5f8ea75e818282da8b0e24a10d9d0c83b50f577c72a9051d1112e65b55c8431cfd3960cc52525cfc814d748fa409d49228b6cfb3087dbf8841be3a2b33a8feee4223e05a2b0201ee28a476d9952bb5b8ab2c27f9723845b2edf426ebd6902585d569445ae2ec79cc5dde0abf336a9ac1af86b0ae72683fc67c5d93cc3af3d311128d4dd9c28324951b58b91724d9955d59ad7e3b32b9047812ec6892f324102da2be782113ff7a6b349324a4644decb46b1b69c4728416e44c6d9cc433c167a7effcaf97fe899a72bb2d7053772421df23412cb42a0da2b30afcbb1052b365c6b3001d7d48ce784f686c9d70a129e23ea949a2792e39c80e07ebccf5ce718b2d39fd1ea6db6698e14d6465cbad31e37f980dcb9a4eb50307e82beb766921bdfe068f60da72fa8fa6cbe511034461d98def14a6e605f2c3fc92df7aec7ce13682158458775c3b0102aaf29c94e0f14be74593e84d729281fb93e8e484edc2aa215900aa4f658c2ec781c010b9fa54f1081804b3a3727c8b546da556a378319bb7c4156128e68507462c5c189e7108d241fd39458e4d29d4911c5ba6b7bf4611c3397ac418629312848353e6ff8b258c50872b677072957fd378e8106f64c9fc76c2d5e66af59223c9ef40dd54d40933ba617284787243f6442e39133104780406da76cf19d4873a49bca288d360f9b523a13377b96502ad09d2f922d7e3744720a385198f843ad5da593e53e9b485caca73b4c3176a6ba3b1d903a29798e359db87ac3cb1fbfec1788859681bf80f1f48d5be10fc400f768161c405e1237898cf982acdd51b55d4f21a0a43acc7d90e4ac6d68e7571adbed73d740f0ca76887fde90600d2280fe454a77b0e9895de01daa636fa9d711b823e0a33d63d2bd4def9c6c7e6f10d54a2794f83fae436dbede9a1f04e267268799b61f52f51791ad2b468d15e476aea546cdb09954044b10ddb80711a7a72298371e48385824407ff6fc58ff645a65fe64d1667cf370bad31580e6c55c9513f5b1e261b391a7f2cb6405cee2373bc5fe98d3b0e93293b4b3f5be5ef55a06e7419dd0a1e3de1ba0f394cef7c987a71cd5df4be395e8c8cec1059a5ef7d424f0d08e5ee78087ea91a28e48e6d9b2164cd3ae17c692df20ebdca647012eded726594032bd333dab9c1ed1f29fe7944728b31acacdb07ede430c774469529f25a39afc4e69776233414b9893f901ed563bf9d119403fcb71bfe39caa8d8bc5b721083545b42e20b809d8487ae47057e592aa404d2358c0add6c1c3bbe69bf9172ee26fbab51e4c608e19d966c0816418ee24f98738aa378d609fddd497584f2721c6321ab1fe4745eba09c2fce52d8171aa699b9655c996981a482acb11ecaa7233cd75c1c3fb75d6de1a69e8109beb6ea84f6facf16a918d61a64b6910d1e472c309e154db61af95d20cd00f3b9377c0b42fdee1d87b49d252f75fffe5168149334f5cdfa7cb497879e4e59bd35abc5b5722e948339710174865274abe355c0cdbeedeb6bd7b17855c38825cd45e6eb009661da36a8182ddb31cacafcb8f2f43404f5bb077c48364395d2884ed3c835eced390e340891d06b38ffcbd1cc4d07288bcc78e67c959bf9b71b0196572180cc21a98355c148074588f9f0aebf5b272a5fca69c69c71b1fce880b1e8ef0133693fbde8525e2e9b0990e8c0ae7fbe6034ff8b5d4cd5b2459e557633bd32371268a0f171d649c659ca9b78350bfdf7e72510b1d1c53da0b980a8556e9094bc8c091b7dc2f34d9ff6185a0eeb754fb083b1a3702c916404d69f9dcd0fe3eb9f67c9485dbd02fb38bf40abf51e684f3e77291c4e4a6f70ca359d9ae6e8aee663ffee6bdc71bbdc546de1317679d1102d0427b1db2f45dd2a8c2c51d7dacd589ce52d7257f21c6a1bf10b59e9f3bf56f9c00c772db41b265e72db81b58cbc0b9a5a30adeafb9bba839f816dd0a321774ec5c3d5b6502815e6c9e50abcf595a984e9ba3c39a3d40699fd359039ae89aa1760232add7b49424c5ff18076e644963f2db4d78c2b48f54759923667ce9c2d9ec72af3bd6420e280abbfec25e7fadf43196e93e93c697f6afa5d7533581fc3dfd2cb29e2231406723babc42445e975e8815c18658f07f169622afa83858bc3dfc722172adbe68940dfb07493a1aeb621877e842dafcc2f96543db574bd3ca7ba672f7a78ee5f86f09059279d5a343cba815c1088e4a347f808220e8b26220da5412835c0d9657986b344dfd3a352ef7172554ed383588462eab5a24113072a6e7726b3d954edeab3ae5b713ddab0bd0fbed5cde8976e5e34033ec3a2903f14c62723418a85db25967b43f00a5da51a005555cfb5c8fd991cba9bcfef9a77275d372c1f443697c0f3de6fe5064ec6e9860e3743f19e7c7ec34ee21821930649b8672821e75d9f231b321c336549edb1d30dec63df37cdd753806b5c8775d86641c1044aadb52395bd2eead4195b63c297e9ee7772d67ef748fa562b6de01dad18551210eeb36e4c8f1b8168773fb6c2ad6a9434c6b1a2d5e6d5f49af2f95809c7d724dfc777a1180c36fa13553f73648686ba9c628e11843f10a84c30c76c768bf7220da3c61009f939914fea6903c0ab9ce76d9d58387cbd2a675f6b84afd11ee5da8d9c43b7b60694e82415bd099aa5ce99e2fe2910d955999b79f8e013df38372c9df34d2e0ff9d2b29e61b4721eadc29f2dc059a0dbd41060b074c341e9bfa7243418523c306d55293e5ac0103d4b077ea03fbed68ff33374d4632e4512d8a728d8a1c7b3cde92854c2e9bb7ec286a8e796196667e9271995be049890a0ace7221bbfe7b3cd5a5b3cc9560300586f7a8c2e4289ec729f6822eb8605b48af4d728cddc7166591b312765ebb4cad6b357a40c47776157fa6a05803eec13321500f5fcc6c8f76aee3c742080baefeb4f291c0a2eeeee9a5500be521b6f0ddcc6b72245c744527afb8e5d5d661651f81fb9d6891645aafa4229cd31fab6e4b2adc2466132561e79b079627ed93eee2840d2b06d8612b34fe25e0132e855ba597dc3c3a3477fec33130868ca4ac758a3c98164e4b3628f6d9eed1a21bdc144514bf18d54a53a594d52eb9673d57c823b9f455d5150247a4c8e07b6655a31ea3b9bc72b32e787475842959e056cf392f2f6f003a86b065789eab9828223bc14039ce7209f0161fac67bec9bdcb232df22e16b037e47778fbbf5f659d8e3c09c2c77372f0a4da0ec1c3559048a5f2add882d5d3278db58d3b7d82cca08f0ec7423d947255c41b8d5d7076aec5f0ee15562419e2002e9c0d324e4b61fa7e387cd7d0a91537e4c6e2394c0ccab22f7d603460ee7e1250542931a9f09097421e5203577172307ae0034840af6afc288bccc0dd9e0258bd671c6c563f676049a088df452b72a096e23db60b0ee2898807e4f3cec495fc1e30d6e837b74a1a7f993aad2610722844559c3dd0acb08267a8e158b30a1b1e5cd77209df6cee6090b49d043f9347380ec30ff167b02fb72cedc984c5d1bd489e7b0c1c2c162cd48975efc49744708ff17580771d60dda771efce657d800f4d0ef460c23d54644024f3956c138e25b42513e271700a17143a153e3fe56f82a98863a7473c5d57e21787bfc298897296f5bad8165bd38fa584b3b2991186d457725f3fc6e476d38f73917b25d725724dee5eeef70aed288075f3f16e7f05eb43c3edd9bb9bd0754297e70a8196b95fe32409f5b390f87cc0303f5aef3dcc0be8e516e6109aca394964840ab1c2765972949812673f8fec9b90fde93abfd99173e24c83a12e85d294cf9ce3834b5b726c24e674e2c761198331e90ca0374785a175eb5e15baefda3fc3988e093251729bc6de62adeec2fcac66092c680a864ed76f4df41bb99825210e980485d43e72062e4cea5047328de5db383d496e3aa43f25e32004dcb54cb6b42c5b90e4d272787b6b54e722035ace2fcf4b4cf77f3c913a91247c1105fdbf62412a9793d74afeb7435371e91dde0cf09efe4ad54976562fec040d85cfa93cfe217ef66bfa3b5a6ec10bb860836f19147bf097bd9f4340fe2720640bffda0c397e790c61500c28bb9a19fda5f9eadd80745e1a73697ac44bdfd457830c538e6236b8d22b7d7290d14c7e8be6e3cc0c439afad8a021f640faaceec813ae8ebdff333e417c0556959d00c7c413a4e2c56ec76a5e036f50bc22a0453de9b9abc3ab3988095536002968e743392664ba6118355e43937b40de919bc8a212f58718332302f1015972e845457b3787d3c3dc32753f0587faa84a3138dce375dce455af6709af8c6862d10583a40d2ba3c0ef8b26c02e43d00cbaf0534c9240c23ea18ddd6bc4f8f172172834ea38c7fe0073f213e9c687c50b1c6d36b3a85aa696f47213d40cb22655f200874ff3160fb57b64e5efdc1a9290b4a1e1263b36d7ddfae29ddf28ea5a0b5a432e6d106a98ba332aefc4f1f2b66bd05dce56f080121cb63cbd0f757d347220b2764ed96580b2cd922c39543be8966d55abb1a205a8215c6aad4f5c41eb6b235e54d428049f2a02b867343f5a6f77fd00700012cb01b35082b6cfebf659019353199e54ef59d773c994bf51c64577f99994cc6ca94c17d2f75c8bc773417211dea3f91d06bb786526d8dea1bdf5ff93e0de123e7c58ab7ddc11e73e543172d78e51adf4f98798ffa7d689e3995bafbd52650f8bb151bac278a8b6050f4455a709426e0016b0bbfe9f15ee4ace5a53065f46246771b3f62bb7db5121964b72789d3187fb5c0a09806e4f65a2888bf066afcf3f96b9aa183f15536cc28fff72d2a7fdb429ab736a4cdba91c2f6ef7e86eea1fbb57c3293f07d2b3b27cb88d72359b8b19d1216072ce8adf08a88462d3ab96896605883a38cc9362a7f2365c72c460fe73f463ebe189da58af01f89e6837a3e4a767b0eb3b62d719474520a87255abe64627e71e7031f20a8859c343dcf0c52ee06b854d77f9f67a2f76033172cef7a33bfcdf15b24772ea0364afba6ffffd5e4da96806c1eed59942ccda4f723c4f335aa1cae711a4783e21618f9216f30a58abab766a201f8b7de0610eb700a308f0ab753db5b2f593022589d8d04386e0ab8c3ae9e4ceb353d545a0422429598157a6df3be03464fa39284804e808304890de9d3379401af024fbe49bed728fdcea73039b1e3dff284ef0a5545b59c901d75e09588e60f6e44562cb6b862fe1b1ea211516baea61e85328c60d7a950ee4da4341b56a652b2b6e55987ded01916ef0b781e9fe2ba09471adaee5f2a393750ddd448cd89fd9994f27dd14af723148b5d2b1ba06324da6cadd294efee0ab597f3e900e74f3e0b462da04760072561a2925850a2ba36542c0f808f9e5584540c945bd650a9d768bdcd8b39c9f7255b0f8e0b160e6647062b0fab927676edf547526fbd22eb32937cb031a6fd3722d2adfc540f03abb5c5141adbfa970d45124d1beb2faf7f81190912b8ae5eb720302dd4f9d938f8a29181354d46fc90838ddaa16cdaf2eef7888dc8ba5d70e33894f48682aa1c377011637250b880332185ec4a26f464ce7d32f3a15d1396d726251b0a8b037eeb5154f46f934e41e757442c8f94318c45eff13c7bf8a1eea55b3bdc8155cb342b082adfcf06786c5643ceee46332d5f426b808e0e3aee7e9724e9c5fba13fdf4896d35072189125f914fce3c63d963f17edaeddcebcfe3ba3f665b2ff261df39d580d7b1c91329f6f4c1294c2efef307f3c8d328f9d6e2a77262e5e041ad6ace3296176021303aab9a78e5ee0fd9096ddfeadd895f75dac372f0c625bfc826723c7f1878e4f8b6be4a399a47eaea9b828cf4ff65a0bdf4d2474b5ef86fc782eb78983f2e4f8e4242d369ea405cef256b2a4ddc839ceb32b756ac16ff13ad229d7997960f349576065b2e27aa7ae52a107351607a58bafea2579d7a9f23f107edb4212deef75563db04454e84ace0084ab564a0538c29cd17727303a2e385d47042a5e7b2e2fb05509574b83f87a3184a98ee891adf6ce97372d4311430effe4b9d84ba505f78675fe3a7c3aba090ae98436d4aeb77bea4e97254e7d8de9e20a2d658d169b779ef65e9b2bf1e69f4172579150a0caa439e3b0f66d989e58772f9d2043c9f74c1694dc400a0465aaacec59e21f2e7d3ea4ab9721597d6b917d9a9abf5e140555e614792df41d8f0a4e4cc1205240db96ded917288492d6740a9f8d07ea4c0bbc63aa00fb3c2bcf344e4d999cb5dcfbb52a62d72062b3f0266ea716c6cce340020abb4ce1b1e8753c96ec4374625abf8f1de9a7277fa193fa88a4ec29b74caae1fc2446ca9ce7521d71cd97651205899d6201724e65f5125c02bc53f6f448d7738445a15c7b399c41ffe704caac2545526994e0d2742d6eb1e0f981c655705326128b1136a5b9cbc9598e831207b30f4e836a57217aed9e62befa41b2286cb6070fc2b2d80d12312eed80f3902dc779af561415fb61e9ff9cc96b33feef79dd0378d340d8afb3ad3e2e9bce7025da6d1e8f0fc7289707bcdbd2dc6b66a21c5b935a170143e5461317292884e362753af5dbf781ea79887a6865cff43703ba7c754511f74d1fb970991d558d517ff0f7de9f1561f0a46d5cc804357d5a0d2c0349ce99e33f7d9347c0cda5137e319dd4eb2b95f7224afd66c4e9735653654e6e0a7693b7b6045cf06dd7a522540db5e256df91a7212599a0055c6c6761c23abbaf580ea4305a34a12384de782bd9fb6da7dbfc4420a39086b83c21f77599efd07f1e85b29b6b6b1781fb68e85b6de224f9c74b072e68c5db1d8d893cb9acefe46a75936e885572c1e64139ca4ae5e7f6c5b4614234637a870e302bee6ddb52d0386242fbda1105d88f08b517fabacd8781137d20f50480bff65bb56677ff7e93212ec9e7484a96ae59830e0410c8da8c431668172f0e146bca916be23f46006f5f1435a6401b465244ac7cfac33e5943385fe8b728dc960cf6dd6b9e897726071de01a6108fd7617f4341621c2f397f49b4206672bd7bdf4b358f7d355657b20f380443d1460402db00dfa3d0e4251222974ddb72bbe480af7a4a8a58c895b5b20c187a6538a2f3513819aafa7cc6b7474a1eea6b2ad672234d663be3f6f246230d931f8ad5df8d614709fb703ab970c23ed82b34e25598f3148a67eca68e85f0d4664054fcf02ffde37bc232083f857373a3b812c438df72129aa36f9480018dfc0d2eb3dd44f1c9032762b49fdbc65c84304672e1fe6c1896db7df6ee6dd0c9416c09a1230488329d9ba1d4921695ce508f6a72cf4dc752bda97cb291df6d8e00cd5c4a47abc02b86081d8686337db79111c94fa3ae12929844cbf00ceb9699ae95e8ddd110483bc39cba8405dfd1ed29bbfc72ed22faffa28b002c0f525195a9c404f613035128567834a6e9d565b95d75b67254ca85d5494d3dd839bb641edcfeab3699347d27baec67cbfec26119f8582b6549669acae5348902984825db903d5fdc58546692248c89f4675049a4d8d96a4b03be03fac7ef7366c5406694b7a4dac99d4d0648a9e5d4f53a72c6d3fe626243ec645311d0376e4fb3dc806696f507889c6589da616f7dd34f27eac6378e397220c8fb628619d41c638b17521ed7dc539b5af9febcfe24a7a62a80caa772fd4d337135ce9ac20aa6facd4aad94c8e54cba7d28f6a10775c59e56dbe51d66f372875324a4f86b4e8f654d17952d4ae549e767ffa775337893ea6f0eb113dc2b4b9675759d623381d32a6e3aaa3087409e7aefcaa747a8ebdd0dbe4dda04804872b08042e90702a83f15588edbe6ef24cea719f393622e1e5b7114e3d501c71b726a4f8dd8e47aa3c74fa2d28cba3671ef9d53d004fc195841853be1e179eeb006ba79f8ac838ad5c91f6452f7b83cf1622dcf08d15f750a9d040ed7a92f2ac672187fe60864747f16af7274c3f041579e7856cebfb69e3854c24502473bd7f84946d5f2ff25634c6560c72e2b104702fe5d83ed84c0616e5fd5cbc7f9b20181722a1f59bd05e8735a01c19245fd52edcec681b41ad31feee1cf7e5a57eb58af42347a10994a4322d7799f69826ccc7b279cc6cb958dbd2a9213ea8b5610fa4646456daaea1445368ba750e541d63212b7613d673fb9d9da6aba4e8867cc1078086a797e8377df1620c29f073991110d0a61e1ebaed6c2e27e7f525cf5d3cef665c663853598613b8c22c9f0161e5d6fb4b0429cb40975ee77f7a725d10bfd1f024c7735b6f1267c570d27d9d28d7f8bea0a1eeeb610e3ac1a68d7f085fa20921dc570d7ed77eaf6ad922fae8b79b506a98e1129fc4d392942616e323fdcab89727c62482cb4be9066b5c598f3ea25b22dcf884b5653d71caab3592eff05538a726d1ecac0e1c17c176cb709f694c53fcb8fd2c3c389aff14a0821d636d1d7c472c6660cd33b318488e20c21f8481a6e64c1b75284b6a735aad0d02d3b09b26c729e93bcdaa7afb24a072da5c2088908cb983b29d51cf9227b8790843e3cbb0262c750ae879ada0d9c1da103deec39b18ee524c0d9d3812cd9633ca5d41e785d2f3c240c9254f6f7fc480dd84a429b74d91623c06670ad3b505be434d45e2b92725741566cbcbc0d6eb134499db809c59d2832fe94be5e9ceeee362e5441c611725eb36dd4c5136c5f0be58cab63b5dc0efb71cea3d75f480bd16f8de9119fac5cd8f1f3841eb4d18adf44ce8ec949af7374d67ff74ad43fa0a6f15819ced0f07287a42c21b71f7a414058e005ed3b1195aa035a7b0586d328901d5d05e80a826e68d417a19b36dec6da97fd65f43df19b26e6eb8f5ba30f16dd2d4e8108e45a292c2c385c4862450c0a1f51717e8bec67e949c6df1c0171f5bfbdfa94aeabb426bbc57768dbdac9b75f6f732c0422d9849683329710f4ffd54dd3d5da88c6a744410c1cf168d007ebf3369028b08b2c10344d97f2e325634b219f63f17d6a0f7212bd444cee465f2edbfceb983ed64a453f2dcffe03340ceaa8d5e73b870b952cf7da152a944fad2d62da851ed70f5ab633bc8aa3a98e797e8ec616dec0ba3e728993557c133819d6a36e11ac2a807c1921ac6172081678f46bbb11cd2c184c722e1d171fa3a35ae9b98934177845755009bba4a92cc9dcf2e8c13d50449d763ef47f0656917d1ce63d21a30e8287c4d3021151f8b9d2e7cae5713a141a26e91a30064f8d6a098b16bf46dc17d232aefdf211db2fd9cfb05e1876036b90621942d5002ce3fd994f538bee2d225d33409e1e3416bdc7517888216b3af3a01bb37280e9cfd307e1f3097f00fd626f875c6ba91806a62b63ee10b9ad8cc6d597c6726cdea142416f9d3f3d4e7102ed69fd19ed090310cfe7281e26c92246b222d5722fbcf6ba206d8eabb5462910e2d61a86bf932c49474ca9c91804f14310400e72f891e073762a1f712fd09146ecca4a85e069e5f9e3182817e5692d2427b03172fa152006ba21167158701e6f3026d39833eee5917d1f2c1c5910e3d608ed8d722f5954233308101fc46dcdc0d0bdefb27eb6de773ada3f69cc4b12f99189a272cd2eb5ec554222090b3d480e7110c7444096a7beeafaf6703f1799ac91239763a2455bb15436905048e3e35117a4b4491c04e4c3e7c22acd2bb99ad57cd97f6161095bbd15a655925f224753f0c84298ac61859221e53fd6dd282b62fd29c84ff073b50229a1e56b20791b86a5a9c3f689dde2cf37c746655ac2087d5ae07c72dd17814b63970d3d713dfc0c0d77f197aaadac59f65e96cbfe8d922ff9452972ecff8bb609913578935ec8ba373580eb7de2682ffe194560ae2bbf5ca8befd72b59a188a975b012dea23f9cf1cf8b1d781425925c0e7034b763e90ea0d48a62b8b082cb2fd1b6f52e0d997bc9fe0874dfe1a9a1a3c813cdce2214be6b5062a722e816de626f6b79f44662f7c86ca6d4b4a1b7886800b04e615121b45a1c36948ba744db59faa475e53c592b6fe759680fccc6d976c48a898bdba0f1360ddba72a12c36c5831f510418a5b4af9d2e19c80b2da85f80b13b36761f59325f0c5c2fd23a790d028ea353b420f5764868d524432595c07b6ac3b10b3e6a003a27b872a9d441435fbd729b134c2944e0da271964b1f751238479652c795fbbc38ccc21da48b1a9f35bf95b78155a9c806ead3d424494a3a22c1164d97cdf951c3b1136f3eb5f507fbedcdbe22f1e075447a67850f655921e08c484cdc2b9a012be5572b1f9d7dd364ceb1022433bdf6cc6e55ff551960c9cb5b5792a028f60c5489772fc3af5e3a5031053d343ed7cd4cb6c7e3296ae7da55ced00fa74e42db5dbcc72e799ceade79f60d9264ab75fb78ab403f06964d248551c93af2b0a20f9df416e8b08d3f45fa1cffc91fb92fca9104194c52350c3f354aa9126013ade6e8ed4724210fd8d993f0b12f38dad74ccde798b5b2fbc2e587ecf3adbd874b1128e434bcd43b1ca4cb1003e639c68dc2b5adf01d9ac49c54be8df5369782de551602a72964f13fb2189bdcfee9cd4a8cf6d64325585428abf04d44389a30b77ebb1ad1887fcd9452516996b4750b952aa49a314ef6ab4ac002af357d34f3029a8f86972bd2f5a58ac1732580a9b8e33e65880d7d6ae8ff30d65e239b58aca6e35d7ba72a2faaf07a30eb84d635116b0f2306ed12de3c73ab282ae2a4d074c67d9dde1325102156e6c76035c2a84317e79cc823f2276e4534e47f3b52f986096c2c4974a268c2e8b8fc71441606157d359e275a71c1b584835394349180376523ea88a5bafef4bf79f88912b68b5b4e67e1270e7a2f8bf73d354b986b17df1c9a0b9a90ec7efc16487ad3d803bb4dd6d8934362a2fe81dac1a243613aeb20f53f36d1d42ecc411c838296509f5de134c01b199ebbd51dc7b70c8dceeab00bdb02b34fe72ab8a29551eeee3f8597cc0983e7c3ceeec5c735fda3a8cdc09ce0b7a997b89475774aa67a0c2e27118b2f47051623f31f73fd8be5698fea794a9a05643f71a721d1a54733ce45289dac97b27b8cfce02aac068753b637ef456de8e156e946b72c2db7cea44cd0b44fa81e9e400b358975e8d72911121e698491879ef415a9e72fd6e447554333fb29420ce57195e4cf9ebcffe4ff3aa1e237d1b954e716116729286ed061ce4ebaf6fcb8675f7b9596e56db2fdc6e5b282c8c9670d2c138da72bbf601289c48a350c23916ba5e128dae842a39b529efc52c3ee4ee096bbfa072cb5507c1472c19289890a0e0367a19a5b5d063f04a73523bdaa25b6f08bf87668d7140b7a66079e25ffed392415875680372cdd3a4df58f857f863666736fe72c4a1599b3350f8474ce9056c6e4216a12350d4acc71b4e240e4465d67b959c05d905381cf01971b85568ba70b1b7a4a1d93439b09afdc26bc161dbcd4b22724a60d00529145ecfca7d896303474b1fe57b46d0f74d9943b10fe94622e94a4a72dc4dcefadd64a7e26fad6dcbb8d835c379556ae424edc16977341049e73334037e37e0587dd9fef794bfedc032040af386e030661a3da877cd94f240b656fe604403c211d035b172b1ef78fd5d0dd3a29e28af1badffc8a65808810ec6fd7f72e7daddd04eaf77ffa07266578b16b19dbf66bf3c25f09b5e7e1f708b054cc25d251fd66c00bbff91add14a524e9cb2c175fc4a1950fbf380004b506b079700723eeaa83b9e8154da9e02f95b19bb5665540e3a63d9d5a4c9748368439d6f09158e162988a5569008b42ca79eec2b44d64cf4f39ca0887737c0a4cbfc90be4d72c20855f411f7ff470e41066c66fdff90842c44a391141559a7f980d17a86cd72c4314674c92e3bc893f6867355738f0ec53a7193faed0f5fbc89e047182fda480294fc5088fb25145badc3b3506cc7be6913b71c07e6f31f6dab7ee8f1e924726d10de46c2fc29f44f393106fea1c8b910bdc64b6a6d0b369612c43f40b583391207593bcf0ed2af70914052e72f33f7f2994f3c75fd92b09395e05bc6390072edad214f891312024f9208306e7c8430a28f1bdbba06e4855295a62b23c14f5105ab6f6d40f4c28b85c8bbe34ea186c6b6be622a42c87bd3b34c8c0a9a04a86d457812433fbdfaee430e76a51ba7fdbb6e17bf7ab51df5832933e2a0e872117266dc6ef7d9fb6e933128b6a5bcbef99f80cacdea92cdaa6de818e0837b200272b25ad2ddc5b6b1bc7068dcbe834f4516afa7ac615862c304149f9ed1a7751172c6df8eb49d8ac19f98d5803bf2e1b8402e23854848626c75d37c3e996831bb4fcbbac5dd5bf2cfd2077493b1efb6a8f00242cb997e37c6dc5f8165441cdc317294d74805d5f14ccb34b39f5f1d03ae44184f3a1fb78a80dabbb16b789ce0b77222445011f917843c5c0219ceccd27da41aade4fd07c4e0d261570bf9277fa572513e785d63b9540730818233cb16350fd5ff0f802751b4e5a6d86b739180bf7233b9d0995b7edb1c24781dabd7a90064d64c1d3445b61792b1f98abff7bfa86f18eeabed38f5aeb8bff3f1ef6f560d048e8ba95c12808734d8e34a29503e9172104d8624659fb56731194d61dff04a1354a16d8eabdd85550049709cc5101a155b736bcd6b385818aed19cc3641559d003acfd93a09de00009511c7645dafc72319f6415f7bcdc8173fe6669e07b119af0b4f9c385832b3b680bdd2a53e5f672cbee8e63a8a0d5db87d133c9f997163c679d47fc9d024cb2d6c5dce8e3f41d723bacef2746dd75b84d305c8369ef32e2fecb62b863aecb7f239d4ac1dcf66a3eb19d21256d29f41891f00ce7457574e8fb107141b6569137b7f4644594832c7263304d4b71c557a6a17e30492564fd89c7407ad3b2b56aa39f416214e1146772216f3cc415254f90637c36518f63ae28c524a6837169aee584939f63613213727440e09faafef7d97cfa3ac7020658a23012b05e976d5cdbc420ec26e2f44531b1ab401c6b6b9f7e1daf851632280531b13302bc0bfaff5b0d24870c6ce6a272d6e4f596c0e149ad2cad4d02a625be38e3c85e9af010a02661dc85d4013dee2140a3b26c165087c7f55015a6a811cf3b908334c8d4a688868e53650b2a5be51a344928ef8593c7a34ee63daabaed7ae25cf37b885af1f2062fd19ca3f749ff728673f31d527a5fb6a1d74eedb90c5cdb8d24780f70dc1317112a807d15f5a26dee73648776ea4828390ddddf2002dfd0776c53220f1dcc85e6cc38865b724d7208976a0af77e56667763092da3fd9acc4b777cd74fa16ae52783c5021b35f7132a46cbdfc3eb553e31e60572f0d0a11e2f99e75ab2f43cae5a1c680a58feec05b3e6441dabc4343aef8a7e68ca15a2bf251b2ea0c5fbb5d70c581eae6845b3724f12b87d36108e3c19efc4c8eeee46f3b0a10781de71eb627941aa78a49a5f68e27ff3660b098c1a88c5cb3876caa7ab39e538eacf972ed2e5a4835962b0e672e929b9d072a86d4443c9dbc9a52647b8004421029d6db6d28470680bacda7f1b06fe51268aa0c2417d245c697c119fc5b4cd9fb5fad974d3c5cae8c8726d102ec70f8f9edc64d42995f2f3f62a2aa7855a0f099ffa55bcf2fd1d63d3036249722adcdffe5ea5614783df3cbf9cde2a4b83cef3d225238df3d90690fcf5849e72b5709efbb13609293c02efb9dc885a82fc76d1416032a02a66efdaf1b57345722a87d4756bb05586e91bdf8d61393cc1d482b942729105409225c46f97221472b97129e6abe947ee847148e67632ca0de3853aa734dffcefe93252876192a50aff41e2605f1bd390c7317c7b07de91cdae899136942545c1bdb2adc5b7881a4e315ddd610456c8d9678f1a3cdce24d2b25f265a1eb139144bdb0d73c8d6c287225bef748850929196b038ab2d0afcea5c8ce392128ecd4b744e631a86f6ca972a241b49ca0e3c33d81321bdf4d78540f93f9208b1232316d374d1c5d6755ac72b458d2cc52a49d93a43e7ccddc4b7b475bf00f65e3f76e32e17dc01c80461c724d121003cefcecfa8f2c9af8bd16dfdba0ddf782fa98a7d0187e21f97281907264058effd81d7525e76be39c8c0c2445e8b922b2706572d299627072f2876c3f331d8d7dcf3f902f2c588bae63324d27768e55d4f14042926eb58b3dc96c9023a0b573578a1148ebbf1d95d25d074fa1e1e8cc22545941ed38816669c09a8b6594f6dab9b77b795eb1bbed8cd822a2d1d4f316fa9c11b21295a2f18f503dc8418ed1ec05759c288ecd415b078b026d32ea14e7acfd3f2f67b40d634e3e5d6f39ccf0e3c3db7e8d55096b71d42eb42241e03ecd65ff2023e6075152c20d19d76a58c27e5c011092b31dfc9a1c3f42ada49f67f186745d2f0f7a9d00a6dfd8a272baecf5c35122668f8d05f911c0092b79203b58fcd3a8d5e121e83efd6c38d97283c9f77ae8f532589eafca97be0fa5309fb9083f2540218f82ce2c7505fd526e625c78b990a9dd695362def6a7b5044d94dc5bb3e36cfdc98bab26ecb2da4d7257311559c4cdbfee1e3ffbdb2e6dea6376b7e99ab9a0b6c698ab3c83f0e1bb1851c476e1e3521b970dba73715866b983d186e88ab42f7bb63b1171b73c1d6d7266ed92917661d809d618f064101fa5a7f6b64db0956070403d8f13757bd13772ed2e828261ca5349a901f976e819d3072176b7b99f1f69d41a3aa96e33ec027221c1c3412a5f7f4a67cebef0a56f3da2c50ce6204b809ef11377f341062f0a514138db9eb3659de4b47a3b0f127ce2591da0e68d07c02f1ca8182883169bd3721701487593c8415b1243bcc6bded60d55c29a9717e3fee81dd6acbd6a67855728a71c6227d60c9d06b2018bc92493e8222103b529c8f79a82986c362cf95d3304f8aeea26d76fab9f932d8c80eca380210b09fd36537cf4ce027593f2a81826fd1d7e65ad13422b2c138f6f87e62563d22ddaa4e0c704e4ba9d990916c74b0720bea779642bd6f12eb7fd86feeda5207aa6fc0ac4e57a84feac51060cf0f397269af37eb5ccbc231243ad5f740452b21ff83e7263af3f0d7bacfdb68b696901b7b7aa2f107bf74ca8c4127bfee5bf872951f8a06d375354d169bf430c7330316cbcd831098d1645b29af18edba55e5df31ab5cd2ccf17f7c24a7c86f672c28726e99f0e203db0ac89789125f92951ed0eccefe3625b82e2ccd536bbe6e365e164a311199122d567374da9a72025f745f1f9112460eb95926983ae8b0aab53b72b1bf0b08fe2718584927d8dd333ff7c285e9742c97429227ec6cd0bf992f5072d52ccfb74f69cc3795630f2516468615ca125e377f208ee0ab224327f349f4026240768a33b142ed4b5d5aa2018858b15412cdffa0461d706a390dc206f0da72dfca3f79e98c42e0070f088718ea457396df8f66eda25662a366272516298872577f938decd689d2d373bb236e7ddd73ec1db0b822508be05e400808aaf0fb6347ba06f6dc62ee683d892225b1f6ab1e2ac7b627f95cf9ca1ffa55807eaeb872e60a7c05637c235cd802a73b44608882eeb0421df264adaac0ef220502162f726a8db9576e3e68dc86243f0b429610ffb0e094322eb5edb20088439eb593644655c36a5bb95c6f8e84ac4dcc09107ada4fee15cf6dff6061ea8578237dd93f2b74fc39c52d499d0e693e85c0dde4d28fe98500632b7ad3d5c8f81a1b07f1dc72f75719473f897eb14c2ff67442853e605b65a8c9a4d90f97e61ee350bd808472b457f0c786a0f06c39c146971b7571d731b4e5b5ccf4af0c67cb012d529b6d09c8e3e95aa25be0298957b2129395f8c6351ffc1cb4bbc7313bae3730d93df57246ebeebab4d59bbb017505ca784c6a8d919a1de69393a0cc04893bcf6518fd72c7c3ef4ba9210e9739489cf49ea5650e72484007b4532082fd953de22c1a41723a53f1f3be767dea46ee8e9f07e58aa4c298e66df2969b608c5d3954e2524d7208c4bd4bc7624aa23e6ee1cf43d73a424a8ab5e08eda55e0136f4cbd2898b4316e30e309c659c8ef08ab078819ee46025d2ec802f8039166530eca9dbc0e306a93ebb5ee56bc4e77794d048b055387e61fca0d1c642effa726760f4f39c88072aa780e3ccdfab2fa064648428d18ab4088227c433f03205fff13af547a87ee0cf300c69a8348bdd32ef637ceccedeb33bab3399f5d7c46cf495763cb50a5d67251da2f986e24c0863ef850be63107dcfc2ef1051cfaa79f8477d50c10b8d6c239d3557f7e99bff476badbe22068e798deb03c85744d5945d9549ec701d2c4172037fecfee1fabc78eb974a6e3bf20f11f2f3a90b8c1028a7c88f1ebad71c6d729ea4159ce6ad5dfea062d26318566fc7a3e563ef64857ba06b6b1508b7e16a72f06a27f564effa7cda3886a58be3acfa60d961b1451f102fca0a93e977f00672e2a942c8ee8e77b674e4d798cd1777ed657d8d3c75dd2e14843ccfd7f02991726679fac1405cc4583917cede4764b3ea5c3aaa6330fe5da669b0cf794d44fa72ae9f84b075a7ddea36464537103c0e8a86e9d3e738bebacf8f00b3cb47de6f33145a56a515b3c20985a27bc1e77e5bcdfa40cd554f723b1edba2f43ed68e14725b82916dd11a5c29bebeb69cc30e2a890d39fddeea5172bed674ca38c5e7bf428960f869de6f39c04251af6626d7becdcd84e60627b4d51b254f5dc139de6427368c712809558959cfeb6732a28758b445ff7d69827974e93bef4fc9ab0d0d3d8563f7a04890ec2135444b6246e552c9ec886f70bdb3e528e2ca3f72fbba9f1411e14f09113310736f4566ad428236612cbad26e924e4e7e833dbfcf19ab7e7218143bd6eb5327234773c7a50fea82bae4ba53ad77459b114f561f9e67685a18836b65079d69887d855d3f2d6da2b5f6702bb17b4c653b56d4be87f964761172e4056589ab09de4467a2d0ca25a24d8d51d4f27ae14a89e021fd28c9f46c06727c9be8d8a407b410abc50216cc5ceb344883e5c134610909dadc21cc26955a4c2e69ca1fea283b410e6da35ff451282322758ef5e049be9935c67ab57d3e9a7274d6163cd0f41a75fd36761c42fef3ea76fba112c4f439eb8088c836cc307972d0912aa8862ed93f67e469efc5cca291928dbd6b8198a086cbad16e58fc52872d162e20df65610a5c9796c55d73b50607b498c6068e3753006b6854682fd3e136bf3bfaa96c4f83540c290a6ae8a5f77b6b6ea2ebc2bc9c5e9db3d651f1255043c6c1e8b8ecff279c8fefa0afa21c7773c649fbb718d9e765f1b510d76a2160a878312d88889e2ea869926a87cce173da00a8cb496285f13a2b1ea0199832c2cc1e0b2e97209cbc3cb3522924a2f933b18552f932245d26839cfd0c9ecad9e72003ab07a2955eff8be2a14dd642dc87f392e4e3ffc820dfa47ccda8059c93672e170e1150b1a875245205c3d822e7c221db2658965aae8e81b6a31ac499ea21302b1e0a7cdbe76f92560d6350bf0cbf6da60cf5a06302100a5135f9c8b90b072d5955cb0c2869a62d29479c1767b2f9c2c37d24670c5743b1ec6c63d4d968b72aa5896a45e60dfdf59d20d0bef4c8c4fc5b1e4d6a3312748582b065a62db6a162d5ed034d5fb80f4972beed95f75860bbfdd99579d4c7d28a4b2fe8d7ce9dc72430d03781afd1545116e1c913a2634878b410da983e371142c08021a55ee0a1892b4d866c3bf670c2ebd858d6b51b3e23bccae6f9b10ac9e7a5211642f6b5a72a16daea608cf3331ed34fb525b7124f494781185f191826fdc1812e4a94b17415c576b8c59893840fed9ce490241c9cce8bdc50227a1d3af6fc18380648310724f8df74cfc51f83f9a0b08cafe4bf3cdc74928a84a600169cd101cbb5dd9530cdaae6a1ff5244207b12330f90462e4e68cfb046dd2ba0052022d00fcbab3840b5c7e47ed7eb9cdcbf575f93d0f38077625b207515ba5b21a0a22e08d27d23615b18dedbde92a82227b6361eaa2a28e38cd5e08bd9619ae9658f7af4b963883725872400d6897f9401952eb0a86abb6d831ae7e9c22d5b5e698ea8759cc469746ecae68fc1cd48ce5cfc88f33b4ed16cbcdaf52d1388018378d9041655c55091c7ba59d2b9196a4cb70e3a582ac87d30d31c6be49c0361b3b9000bd1d5f096d7298694cd5b96ee76a352354dcf85f0b001b162fcc69637abb4dc6d0110834e7729448b66b150b55a8403a9095f1da3782aba4e437360990d977bd7c8231d67f6207dedd30e0fb2c2a03c683e15a14c2b5728595267ccbd5a038cfc86faba162278b548497c559d978ec1ae5a88d80e5a5e1c533a7275f9350f077b87709e9dd1657a8012a8cbe4a519fe4108dd3efa37617d072384639f1c93dd9ad87e42b92724de84e54dcb2e499199b2c01d324e9972187559ef8c08e494943f458af9062580f7c64d31edf1c172846ee8f0c8e9b5ae959ab945ce9ccb6fcaea6d59b2634723bd1c599b1ea85ca77bc854627aaf2cf1672831f6bcc82f1e5ee79371bdff372906bc21ec2aba1536dd75872b5d0691815171a4de0b4c5ec43571cc40ba03621c75c65916452467613b25f7390c0e08d4b0857b1e18f6b277dcc7636ed7f8a11434c5188f2fe9be2918d2123f7b06825bc054f53a38069140d221de24a408b7235e2b900e5ddc8637c36a4f351c00f004589568acb3384381988e169041532729c74cc06fd9dbe27d0e91ddbe1577609baca91c57879e8b1b5a1716ec920097214205081a158f5f75ca0cfd69c22ed1cbf0de7eb8b12ce3102f651dab8ecaf72d8fd220b2538fc2fcb0b73a56e5101486901ff2849e291aa857e875d17897232b233559b5c7b1aa03f7c330925ef4ea778af67e277ad164f91c5f97ed600c1722c877dd36ef00babeaf04cada7d6aaf92c29639c4aa81bf33068577ec4d24a4fa0a344043b41cd4025c8111107d55a857c7dbd7bf84a9b109c229f57a67f0e044b2ade2cac54363c90f6a9504beda96427c6274a109385d5e2f0ba4f6ff63f72f18e8388f0cf9a694750d2af549a0e3821f406e2fea5ef919b3e0422f06ca1729f2fed904b56e693c523171f1e82ddfd0ee24d26944672c1b3c1f10e4c06bc72b41bbcdb93ebe8be6df7605f010b400f245fc7cf197d0f0cfb6c52968a96936941ffbee88518264c3906ffcb9455007a3680d6c665153e03e910da128e4d9960f401a74b41970c1caa9e260d42371022354049647d9d24c2aafaf2af1415c05113e9717576d17955af67a7d9243b0ac522af95e0de2e617ab465606f062b0348e704268971f7f47d1c695f7c80a0ae157f935986555e39c4ad3c119296bee722515ac623b3b7fcde1166ecc770ccb7b190335cf3d708d1a944c67ec049a0b9726647c2ef440fb2f43e04a60ee3fd3a9d74a09ad1ef0fed0196543e515585b7725d7953d53a12b22088b7a62e9053b85a8b127b3eb7e1e047030d5fa4798a463c559bc3653dad85c44d20241b2c41c88dd7380051c2c4fad1364045661704a32bf82f4a3d24102261434d745a5df0df2055d2beb450dfc7e60193f8d229f09a72eaf347c7e8b6b08d06c59a938a67641dbca28b6dc8424c223abad65eb5824072cd9383cbc1f06b4ba96324260764fd04e394d87182e92bf28a72934c80aca472b6b484cedef0111c31d8f5c48d81d3c975e1757aaa2ecbf594ef53608f860a725d2c1fcedc3f42d5aa26bf2e4060772b54f4fd2ff5c811764681271b131c43621313d7bceff56a5fc92f3e81c794c0ef8e0187e50106366034e8a07ef8f10c2f8864081462b699633f71ff5ca256ea435b7822819b82f1db2b7a74ef9475c572b6a0c4a409dc5382eca2499e12ba03932b0b30e1c68eac12a53f589dda4ea67248e4f6861effa6114e3c44b5ae39ec06baea116e26daa78b45f91f77e19baf7212a2a7e328d796c53ad7bc351ea4edbeac039f50cc7e73ced455eccc619db4538e971b88381ee5dc25e0fc2e043e01054b20acf8328021d3be8da56de99b2a723c811c9cc80cf48b2ae71101a4a837e91c710ce0b8971859a0dc0c70ff7a4172102630fd535d4ae58df7d687dbd8eeb173f4e2dab89d2ddb571f639aae900872e1495228b46f49d133b7d86ea1bb0d9fd6b1d9246b9d9a8ab4bff2948579d2726afb5ee49cfab9a4a0a7604c0e1961d53009c1ac19c3b57242eb8e825ddc6e72ec6af61547ff2a697c0f48b2d169630b1873820278e5d77080f5aff1fa77e61534ce5632b5e529e419abd1407fa16d9aa283ecdb8d846620b1830ca57bbf74720e7567e562beff0650702f477ed834937fc87c06b04ae6b51c72eabad4b07c726f4bfe70f7c13458d1c8463c8b26515199c183231d7b51e66ff6aa09f1259d72fa49c9864e02a7da65285b0fae01ac95ec197902ed690e8e7a482e95a7303c7292b172105082358ffd0d817f70bdc176ac684a43749d02b5fec7e018637a0d724cf89e6025a2e47ce729b94a5750a02d81f12a6a20582a7fa4d22d031baae87256d409896c238ffb722722d2e68c10df2f7778e0fb7152dc9acf613d95910c72a699b4811e3f8401965c784dd484af6e2d06b0251643283174a9b3f417ae7414a201fde299e39bce68ec1361d7aeddef71ec0d2c8c29fd841177681ffda914726b933477015085051a26f98a6600653ef4f08f3d2a112db6022aa255c5af540e9fbff17e1d8a1a96311fdbdc02b9093f5dcba2f26628d943f962ab569ae28c72d711d53570f68fa9c937d9033f68132548fa99f3af2d84280636b63c0572fd02ddf77d86e2600669e8cc2e002b951e131bf2a8771ace43d0aacafae9f4c352728a8779cbc075e94d7f91fd3f3977d1ce6f9f923cfa815081f6df7a0bda832072bc252e547860ac6ceb54d94e536717588c299f45966fca733bc7240352c5720656a619875853033903778560c5dc7271e2ac5ee1808ea3752d712077823e54727e350ee224504e93330bd69f1a7d7b807ffb04e4837f469bb64e48130abf857260b6087b9f1ad3eda32ddb4d362b3af4d991a43f1c6fa3d588383a443083f355b85cdbadb0479e8cc573206f88f99d011ef27e151154da7499a52c2428e7c05197d40e677b9b0ad0d5cf5f70d23849550f51c09efb83cb345ce78cb4afd3cd540055f0d69ecd61688729b14be424f4bfa120599fdc54078c7711ccd0ef9fbd729de6cd6869558ab687307e2072eb13fd1a7023f786efa4750af6759eb5007c72e5c3b96b48e1c5cabedf6d953ae9d1fabbdc1cb62ca7ee07f9fddd04af1f47481200e4b5f16abb6ad508f57b65d31fd8961c0a33bccc5614b851b10d314dee6d997a0135d6040ef2f576db2d372ccdf78211c521684f1e1b052658c3ede1185451802dd909266392409044ec43880b1961eb26320adac8ea65b624927bad3872b20818423b25d85d6b2dcace29f7b4ca9a8384eb2fa373bc683e0d6531335672f020daa1c8e2b483adfa405c277042bab4f8a255e6d62da86e9265625e9f4372e720a9dffe1a18ddf7e903eddf9283960c19a1ba57fda0845f24e485c047925cf9e28a2b21069cfc4b271b4093c14942026f5b5a1e26adad25d8a26743833d672b8346dd4b302cc223a8edd94979e160db30c72c8c9e9499c30ade2b47b93872725cd1981c978e621eca69c9265cb0ac09d9fb3d2714c714da6071868e845572d8f2fb2fc656f92462698f5553ea09f558ff77163058cab8db42e1fd0e0645721b9f1b6e702a1c684f680b2c7c8ba872cbfa4064d65c9db4904a4e1970e6131642397ca5cd105ffb861cd326612e484f73a83646703e9bba3bc3e55f75e27d47785e4b924bd6d1c6545e97be7429a4b29ee6bef1da02aadf0625cab602a1b615c34dad80c4b88d238ca600a10c623c4121a51db34c52ead866e1e50a6e0fbe65db22e4180a9c02b4b9d8cd76d81576ce02f5a3827c64ac80697584b7824c3430460c2c1f6d49695125ac2269e3996a70529451679521648d205c4cb82f4d1e721970bf697e38494fb4cef5f8011d951d318c31419c4b584a3ba2e0e468159c72cfc7373072a46e39256416ec08917fb67d615b1d73fcc87bdd3998cfbdffaa1f0a7811f579f22ae4a08c64784f2c3dea15e5c9ad57c75690593293af7d9e817253a756f20e1d801ae3e4f7b9723f7ac615c7cc0a27da2eacf2f2bfb497a00d250f6583baa7ac6044ff501124fa280ccd1e98e6e34c707410e37224270464832dc3c6ccec71246a303eeb9f48f3026c4751be9e67d74ed39820f66d414943896e5a7e2493e03cd26ba2b3c51e9642bd823b0b85ed7b0295796a369c7bf2453a72c8cf1e417fe2f43802b3faced8c3a8ecef380285e09221e08397835f5270b37258800eb6f90d6c069842a1b4bde71a2339a13d029168451675d2aa28d3084972fff7127fad6c9c882c3374a1270c2e6ef5376f1026bd444c40872ba83945866d275725973310b4a47e8d4b3d4d4f5ed592cd1839025f6cba78389aaed516d172ab05108551b7855066a09e0158cfbdd95bd5a6051ab8cfdb53be46152a49ce72bb2915e7f8c027f35c6c5ef8487bcf7a263ef7288c028c1e44138ac85af91f087912780b0ea27e5d4bdb43d656672f9a4dab042263e62c43784c5900116c1c72fc1ff0a285f4a5c8843da87b51e31bd2115e2e6a36b990ce66801cd468a6037209ee330605729c8853cd74eaefc77e5439d017b9946c8d7ebafb39f99099e65d5604b1d3dd7900217e1cc565ce4ac0adc18f302b588bce52959df5e35dc66172ad274d61088fc38340ea3a1481304505d89801325b70e0c8d37ed36e49a79c04e121c6341b2560647e0896d45526bd4788a9dceb538d2f515e60f125626fd53d6a55cc189c9dd349259a6fa0803a320483b3c19cc83c78a27a88e54ae9dd3f7235712bee7982be7a921e9055c8ad2633bc0cc74c6967e28e10d458bf964527722d2901599ee1a815f3e4f3b1bb26f9c770e97c1af3a00201e3894c841d5616727a84e1053de0b3db0efc01373488f69b87933fea193afb65619f6c8c78495325ea58827497aa08790a114ccac6f5a951bbfda1a5c503a15b1e55395d7d0ed972fba6cc201c6f6d619d98f597a8530dff7b664b7f74b0068eb0494ca177aed272283e4140fd007d6a06ca859809c637703daedf7ae5d47ac8b2b73dbd735fd3725da55ce496d73db4cf3673a0e04cfb4d8d86c0baf7d162633e19646b55b7397274c3438638991572926554636f086c9119e91213999f5af0339d09581f457334facebcf1c99e18281acb6724d211ed64c844b54b543fcf40ccde2e510b4bff7217bc72a83977060c1749cfb9db8caf5588e5c7108996ac091d6177bff5d88d72036b0b69db7c89c0f3d7c97dcd4e1d8edc3e22ee9da86a0c091dd9f8e81a7f72aa9bd7fe7c8e2bdc4151ea2dcfbec2cd32b203aca4f5ff9b92d809d17fb86172309126419afe552ac3cb63c4c1cd43c695f1c1ede9d5ed269352400f38c1ab669972b0fc41eef0183d66d10069a56b17eff0e32b823b09597daf38898b476072aba3371e04269d6bac730a771a448cac21faa25270ab1b4b24b657f8e44d29720b40267e5a0d0f71c45d6ee8b7376bf86579eb6eb4ea224aa4f24d0eea15ea6c8f9272a79bcc81efabbf4a18f8fb39e81b8deb555558399c5c933f5e9f3dac72f19ceefe6ecbecbafdfa1a291e13acd58c5de654e2e8fc0e11c04dbe6716bb72b30ff42bc26a5ceab197308e6ec5b412462d89f819328ffec5f5f6d6d9e244234c694ac33c8d3fc8736288936fb232ec9611cbe997f759d1de70f3c5f7cf760131b1f914ca483142265405e33d990f2513a2d4cd535bd05c09744cd3fc518b728dfb7cb5189fb619a247b18b2d37768ef6cfbf6ea219352b351a13b768cb6072583837732f6b28eb98145f55d229ab59ddf852c2774233bea00cc504b897bc221e50164b50f55af83221ba44e2fdad1f0ac6d5b03da99eedd402fe4507f5574e1309600fe277a0e3c0d4c39f7acd3f06db5e6e67dc3221f3f378567ea7fe9a72a20eecac320c6de2cf747a293c99cb6bfb5822fd1c4f998ad1a57c76ce31fa0bc1f033b6e055b29234e16e29aa3d1dd251383c894dd28cba81c6dcedc4400e72a6d285ed20bcb206edad60883751333da0bfc00733b9dbbf3f43d2c576dffc7219436c3cd523ed2c42957a6412d692b4bf7003f6e3522493aff2664116735972ff8623dd24d41e1883d7de10d7ae423849ca638fd9a6c9cfb4c2c2e6a57ed213d4f902365fb82ef40c6f9ad2022cf8079a7a17ef8493239d9c9984aa414aba72d064288257ac0b66537e8c368a5d9b168848325b4e914d243848afec4e74b22be0cfa692694fe7c962dbaf6a4c8f08f570fec2ae95cf232eafd5e8a21990e57289d0f5650674c925cf784be0376d238b10e2cb5240c1a4edae76a4635561654b24184a93fdac071a800d4ef2e126d1ee607dc30bfece1ab83149611f33912372b62dbf1d560fc2485be3f9a539b08639345fa6ff53e013768a8ce345d3624b104344616ee3d64a6f83ded622f3018432fb1673613f25f2ffcc59ac391fe327148cde291fbe30efa1367cc9643775d6fb588ef50f8e3af464849b6250bae09d721b2409b2edadffc24d29a801587dbef05b4896c0c138ce43b002fccaf164fd72af5b649f041ae0121dab8a39ed44a2500c211582b6c883c7eeb222022e4a503890c012d94942d76c87be05c51bc694462298f4e5d773cb001baa55bead71391a06c633be879a41bc0eefef07d7885cdcaab43a2fdbdfc53c45831c3757d2327290e334140e3262d9f7a845dfab49b5f7343783ff830ef99750379de0b4d7e372ac245e96c4453fc43dee88a0b96df5ba6ab0f81d7a48a9c022f158b2cbbe277267eb844196b4e7757a04a0466ee757dc07ecd8561e5133ec178f721a6ce26d039efa77338bc833dfeeefc1ad713ded0de63be3d7c359ae30acfa3c55c60764728cd0e7d1ca1a7315f88913fb744f3ed062be963b18c04a173f098b2ed7c2cc7215e560783bbec6fa83817666ef42bb8e92ba560b00f279078ba25131477a7f61af5e901ce7ad348d2d3f2e0b2ffc56f8251a4a662102a5960053f8dedf659427c1ba8fe329de5cc86411e5ad26d670e5ba9b504fd49428fb4b39b52bf5b6b456f13e36c7b0549611faeedabf19a4f75ced2d269701ccbee0c37663f932397872ec7af1222512b8d3734fc2123c65222fb2327efb2d8aaf8c1dffb128522de008370f6fa8a9ea2a4d3f6de1fce9aef1d4e64ba361f01e6f446c5b9bf69b9a757285b3a11cf643c5d68b232dfbfc93c968ad598c8993e1493da6542aba7ed3f37261d2b5885f4b9f2a6bcaac761cfaa6d6efa9020ea0e1cf309c1c4d521f2456720d37ad519777cbdf9606c31965ded71af4dc4973c60cff33f73a2d436eefff6b43460caabf144716b5093636cbb744e2f73b1b51c535ec9177aef55e0df0fa7267e44599920bcab233ac3ff62f0ca39e503fe20c94ced68e5987a0a0fb5f3272b968d01fa66314a0663fe21b5526044d2150307d4fcf8e5b661630d5ae45db720ffb3cd2046ca9f62e8076724d5230d1a3a504d240b58d94bb1c0777dd8fae726301d3b44a25e74f6f6209fe4c56f1f935e3c947763404e074a5fb5a3ccf1055c5f711be28363bfc83b02be729300ddd207b51e81d96235398cd3a17bc742872279d75ee9cd85db17667b9dd6b0ddd5ba65a92a5abfe4aa309157f1f0877ec7283e1fc13b3c01e3e0a6e1d1976783bcdd4c0c224a5959683f520f8c977a2db72350fb72399ac3d20c495ca9f14d4704dbd7ac4111196aa005c4429cce12506725ce16084948657070f0a4e300f152fbaa2c95fcf2bff545057713d2064bdbd72e8bf310f74835d319ca2dff59d01a8ee097706dae0be59d88d4044da31829d347e76598b5623521de66f77f295fd654f6c5c42a45bd05939d312a4998509ee72ad3597aeb59eff3e88578667b5c609d5b6d532b58ea0c813e68801668808f172666b570669c750f4e76795856fbcf154d830aa7a7663ae581a49781d0ccdba72beb7f9f25fe7b6303423c7772c94a7a100f08b110208d59c5596962417cd3a72ce6967e1595ecf0086c18288ba5690d6c6832179629ce35a26a5af97d011862a26286c1c87e8618e52fe20c7b0af6ea6abefcca48dd64057e9a0cc94cc20d07290e0ac1f2490c224b80461e99dc90827867cc2b1b208c77fcd39c9335c3706604e79883265324c6a8da9e7a653fe37094b04e67f650431afd3f4bab8e5ed4172646ee3c31ad729bb21c292673e65b9075decb49eabcd76203fa3b046cfe65d3c1c57c9cd96efc9a055ea0abdb5f4856765f112fb10acddd915184663814c207255b4a314616ed80d80d0f0ea329dfaf4172b9c6137c5982a7c177fce45989410cb0a53b23c4075aa03934e8051e5c8e783d5b48617aea9fee100299eb30eb25537b3a1791e8d90d256468452a64ce29a829761a413219ee9d3a57bbbf02d0305d43ce4c870fbfdd8fff52433c0d06421d16f7ff83ee2e95e72fab7511d2f6572a9504ef9fe54275657f9a366f41d91b4ea39791df9023fa37a2c3a7446c70a7209a803a1e7baf23c6b25b75330084369003315a4566110c5aba666c32513953e326bfb0aa85d7e9d795cddc7be7d6a3489ae22c597f3faeb8c8329bfe22437729cebcd223452bd2c8d4c96d2bdfbb930b21e227d85d7dbfcb5d7745a907de16433883400620f2338db113f0c9880f75facb45abb7df610940a36a11e87733d7227a090f94e46a9cfe3e46a4e28820ef73676684237fa8e205a0db9aba8fd13682ae68278461cb09982eabbdcb3640ac02f03a30d37f29d7a8ce6a8ffe9963072f06d936a597f2f63230bfbcacb02667c653fdd59162fbf1782e46ff4c168506fd33f51c3b090dd17e1d6f8f3e349b49222f35da65dc035e99b00b60d637da07266a106e2bfee689905cdc9b1233211459c3b2277e3bbfc08c56d828aaea3fe72e0efbe773eba3197934812eb0f6980f44f40069cfc6e42196919482d3d0c3c72088118d421a2bdac262601da89c10c7a3a83df1bfd87bf288ffd3bc8afe783729f27eac689784b0c05741b670ea5d489c550d8a997fd35ccfc14f7854f4b4672d5686a8d2d97f1ecdce8f4f14fd8e8842072d036062641add527e158401e0372adf06c1a34ddf14ad502152cfb3fc2b86468ab4cbbaf0603d79c3f2dd8b3e672902da0ebeb5fbf59bc75a817a038d53d98c2714233a8e6b59f6af0c462f7b90d4ee8f6e93047d4e33c80d659123742960efdbb8cc0c7040815a7ec466a26827282c0a8bed48c7bcdcf167b1c55a2d95eac43314888d40fcc2b0ee28430dd214369082ee7aef6447465fd454c33e1ecfe6055f63726db8f2bb189d60e95990872fc61189d8492735f8e89e3cf63f0ad735518df809d79ee829e2b9f0ba8916a722abe057fb8a93daf711d21093d96012b6fd95413256e1b69c4e75c9e62bda77275dc5afc9133d7f3fd461c1bbf2dd5df969c5bc97549f9066a2588531d4a98723ae3f951376047f119df5d13688b5812faddd507da80d0f1f912aa0ae370d472a0bd32b0bfe062e2866d18ad4ba7870d1ec8eb937b8bdea9f57c1f42671224721479d7bb97b640ac15f0b6093e8ec736e734d6af68deb290de75ec7ff4bb9372d5e468d52e681e5c1a97ce0bd61a345162f50ddd4ecffefd6a7b53ee2e2d713b50ebabc96e3fc5e256f81bcf3be0caa18e8fb9d55d25b1501e898250d1f14a72f2f0ff810ebbacec7fc98642fb022434fca2d52a2518ef33d23d8ab7a4daa47289e879bcfbb7389464521937e45a372c4bd11758d0cc28c54a51c4293416ea723dbe8aaca48284036f5adbafdc3da112058f63eb69ca6a430e2cf8ef56bf0c72a41737153293a6b743fc1b00fe1f7f5385622acc86d6a2549b4c95fdff758b38adfb91bf471df7322d398b217d863e7cd204a1a4719453b7e01d1ecc8e10604439ee15b77fecb153e2adb65ab549d175a83aeddb7f3a2c5129f679d566db5a2b28f7c4c177e488819ad80c00a1975702499fb73c23a6771cad60fd61972a85500a64307bb3fb2f34042ebc8de2687a071a94b4d58019faeba9e0e40df350ee4d04ab20b12fcacd6aeade7b8c62a537082055e2e46d3606ff62cd3c82b5347006f1b1f3bceee0109c9fa29838da1864481c0f748b8cdb65d2967cc21057a78172109ecaadd7c7b2d6e477523a2b5e826637079f7f6b88b16dafb1c13f1d95812fb612211fcbd7a485b1b3f4c64cbeeadb87259a3cf975a9fc3d82d7c01dc55f7292aafad2d12d62704d5a307b9dc7f171b44214c1e9837bf5a6a6f14c5d3f3e6cbd76d0e3dfb18affc8b3fc7a4562f5afd8318ee62ff732ae634ae12b0b8b88728e715b60099128783bdd39159686742b689d518a11f796122f36358a1d24627244394c8c1425a770d8c98a82ef52ebbbf2d0c4759c7a539bbe970813339aab6ed740bacff5df56c224b6fd5ffe4c6d7ed08bf7bc0e93d4415115f59786436372cf18d157ef7ef44e621182feb1c29565a6351236b3d29301cb4e23cf89c31372780d4889149df711ca9a85dbb9d721196d028894ceb743c8b121917488dc092a0091696ad17a7665f5bd5e0a2e70dfaaef766b17475f53185a0524eb1a19137251ddb24a0f7d826bdef76688a002149930b5b4dc9efc35583a040d704f09b5011351dcff1c2d2a6c19bb16e4cdd4c30b71133058b972fe18fe83adbfdda33a70508d34e35bf7ffda36e788d6932ccc9495c1b128497d17f40b020f651ff03172cf59ce6a65ab6912d9377c95c52064886a03fd2e6dd6c9a0ac4bce966c12947213c94e08820d3e7158fb710814ea5fcb3812998b2cc09f1d1d6618b8edfcc272fe15bc51d1b58d24e667c262c61301199836227fca7154c3bbd7a9ac2f27ec72a860e4868624dbdc077213772d4f462638d4b9b40b1e23139afd4fa907155465a8311ea48dfad0c2d341d1945bcbf7ccd37c83e653bdc6a2db413c33fb78fd375d753cff87ab5c6581d00a97b07c3479deda336c00ec8ab1d8d01baa49c33011e852ad6a7e56d7bda8f3fedd5f882972405aa37cf4089a01a4d8313a2677927200087305fc8ea8d734427af593e330b62dfc21632f18da0b576f4faa0bfc957290bbf0b381114ebbcf6bc1280df35b8deb32a0459c79f3d4399c976d8802c32af3e28980108c242b8729e600333c1bf011b712c072bc23497ceb59848716c8723514efa6911fc6239e74607bcab93f578cfadc27b29dc4ed03c6e49e988e2f03d220cd08045afcbb3c30803476f618d280c8a10d9b30aaa900ce531cadeb6372e10bf727c55effc6ad81a1f8a8f239fe26df6d8c0d5ed9581666819b3aafa84921f1c9c6ea294136e806e5cfe13e87f5d33b2f2e49ce68e24bc2d0103177024d8016cb98162aaac86972992cb93d149cb5f949dc4251bb329886083423452431f4147dcfcdc76a4d89c06e3e28d357a7a7fbde246763a307c5de549a37422672da11ca2fb4c36d10e1c608497377be40c28f8f8400ca1cfd2904c21a50010d4eea2104ede0d12734ef7461b56124846651bd1a0d7ceab214139fe81d4aa47e31f2385d25f049036f268385c12f982642b95ff8f743f2304b4a73a281da40a411092aaa1989b0eaeb0e3369c7d58fc11dc28c8d2f9712b60e6f0d4ae4d765d772548ecdbb4def0fdecf5e80417e517ed1a85acea9a4cc5acb10176452beccb072c43e0873cdb46dc46c466f93daa2981ce0a90d4a21383a7907c8a2ab14846d72cfdd14f79b562a3691c96f80cb14589a7dc592b8748f35f06067301e6341ed72d6bba54a94408d4f440d0cdc8b4b2a05c966b55f9812e1c919eec9df37e8ea72c0140aef0bdc162bdecd5d84895da29d7444b06b9e9d68b9c79a010dae6c6825e588c1d6ff093820d56b33f6f5ce388648eee1a215e211ea547829e8e47a3e02750b5051c2d8df461a36600c1aa5e8a084c94e7da5d9d36364b40baa55008a35808117234891da55cdaf79b5f09c985cc58c2ad81ae30637b6b20594f2e72f42e626811bab63c76d12776b430d23a87458da63cedfde6c46d9016ae0724d4c72c8119a73b4556e5d230a67bb3abdd189d45b42c13ffed5405268e5486234442abf45d7141ab91447783b8127e186a4ef38bb6ac220d1c3a8a223546a4130b272a21f3e922da1dc2748794f00c143be110eaf607ac82f538c4ce0bf3e1d50194dd697affd281ad7ffe1dd70bdd074f7bd9386263e7f03900a783bae7acd6c6b520bebcce6e364ae6bc2976363d4cf49674427f450ed9465ee291c8c8ac7a95e5a582bb5be5522087705d7fa61d95f874c6c6f6ced1aaf2e190f3badb803eb186eac36e5e31852f5319323bb75e6dddf7e89f589b6e0e45a8332cc23cb46809e729bceb50b8e3581fd5cadd5efdc7bef101a895c749413522fdc1743880b0d1972df379eae622684e41b96296350ab1848782852dcfa6f71991f511a1a42318072ee9e8c69d2342a2d8a643b5d1d25e2ea632eb63366e272f67f8cf2107f677d725f1b47e62f2c6ba4e38100d7b019c0094dc100c211ef30f362f6d2a15c4e854432ca516dc82657f01f8a5aa48ee8d736d282faf5632f6f6544dc18e1cba36e474e44efd1dca1abd8c498c331e946ad7883d2e6f2e51d829d4711daa258d5837288777ea1900c19f4bdef1067e6367f6f8e134737be0ca0b7231e139363cb0620606c7c55d66df7e0c0a3a469d5cf6bca38b8b74c6eff51f36b1783107cb05b72a20d10dbc3e7f01cd4408438c9ba11debe47cba0b89931abb7185d1d404fe13fd0ea4a9fe515bbae258a9d8744a38e65fa49721aa4827571570a61235e8a6872a2483e49b32e6a2c99968809a1814db15f51655ad5d6281bb671980689c9697264ee0e755bda8849e9a669570876246bc7b9b4f262413b0d2dcc91ddd245c02ad362b78bc5dd6c7c7d35d6ceb3bd4e9436e3d2087afcc9cae0098d29c1a13672143b15be5abdf6ee5eb870ceaf98e9ae50bbc203d097710393219a3c241f5572f4933ab4587ff36a847638cda588c360e1871b9679167ebf71cb413d8debdc7203113b300d06ae191c9ef63a1eddf473c0561e095858a1ef31c2c186e73b21721b359bc08aca5a50def7af559398983d270c3cc824b78a740dc99022e9c39d679ee152f739cd8534b5e0babdf29ba02225673ab479e45f9470ddbbd79bf4223add18da9726a40a6e8537eea82deb2efa849d8ba6980ff24c5dc34c1ba3360a729667ce6116ef3d0292496ce1ae53ee16651d9519084c04911dfd4b27b112865edef52f15010b5d9c3641e66e50ad76eaed0fcc4ab3a92a88e54f7671f977cb727b4a085f0079dd3e1a3d2ea28a7a6aebc3e46054c2ed592321c61ac18c5be30208456df0fdef311900d0b93c2ad1672a4f12a3c8fcf48097c9df3f52cd444b72ef444ad0fe1fab22cedc0e032a8dd77f453a02e3ba325a209ccc8bad8446fe72ef2c4f8bd127f550c8c8f185555d5e33a4c3b5cc19a7ed33550c63f4a4dfd336bcf29214219d4102bcbee2bec24c9986c2db0c130d7f46928e3bda5c1b4cb55a4c39768b66d1c744dd6f6491f65e98ed120056fa7fa84261268d448f7d1132554502aff01d070602bf623c6cc8e0c1e7ac609d99878fa25ca461d73dd68615724acdb0741e01cc5d8f0b928cea14a1470e143da2167cd081154e0cc668dc2d724c54767a2ebb6aae7899a6d4b966b3989c6d955123e97452f96e13ecfa39070e22f41ce268c24eee204fe4b55f4293ce3457841bf53ee00957cb876db719f5726b7937e1b3f58ec52c0757027cca74ea05443aa8cf011bdd80bad272cd2b95729f5e511964c769a2b281e2fa26c23a11e445e097a6f9a187ed65bb44f5a03319815b9c7b0626e5eeca0e80753456d57199467a75d8fd4179912afb56c13d0f22c5cf48e2850aebbc3d635de334e3c03d1677d07b20ac532641d91307665d4f722998a8fa99f454574b82c8b37de15771a3ff0b777e8df7db41cb9189d9017a7252eedd74577cfcecc75f477d53a680e054b3e4ddb183147d81673e3f1809be72d301ef9cda5090bcd39ca248963f8fb98144fa52c9267d4eb0fc836a450be3101d80ae11ab097e2dd4c4046689f5652cb1838284c7bb38635bcc85ecc5e4eb7210118722f7da234b4c08e4749bc8b380f635dd7a5d59623d481209ec891fee720d65afc89cbd79ab869609d99afa8e5da6513cba50ab119c31a28e95b7e7ec72c43f61450367d817c605e41e5c5ffd4632da1e09444c915dd468b33f0adad372584039c4c9f47fe3f3d901577c44897cdd60905b94dbdabafcacc47dd8fc357253c7f82b0f0d48261853618783a27a0847425b15232d6bc85a58a6f4780e1244daa10efc533fae8986ed25b46c1e3e04b6488abf5c5e325e33a63b41dd87877284a798937c87d810302029c148c4483e1d6534ef1c8cb946526f16335f43a85e830911d05fc56837f349bd4ac5736fd9964471a2f0ddb19655fa5d5d88c39e724253de24f711497b213d8d592c5e1edd4d69f23c901f9fa4f2ddb5a9bac29832c74475ccadc9e7a52491adc7e21bf93ba7afde242b2390312b1789fdbc37e61bcacc3e5f7d615946b1fb0b4e1bb041437370c52ac71dda7944c9f4a70261fa721db1c5f4115f4538caeaee9a75faacbbaf4cd2a3026fcc35a760908b82c834655f81d6c4b83b741a5acd94a7b122d3c1f1d7618ff103b859e076d8c2281bab6ae24ccd13d696eadf4b68f8e70d8550b72e78d3ea8fa40b054e0c79da23d4ef7258d59e18beb858bd0c4c810c682443980d0278ff0fdde92e95b0ad6966ee0a728c4f9b8206b3c6680fe500f99562ad71cc1cad542880602a5c22e5d10b1439727e2a0b1d293afc6d918665702f1b060799092bb3ab5cd43efdf4bd3522487c5124105809abf9d722e6fec35bcf82ade2708ccaafbc3f7e9b4de21405613298724878cb7d0a6bf7d1796b881c602ac29a1a0347b8f9882e70997aad14cb3a553b7981bbcb76e1501690b6e2ccfe994e465a8cfabfa9c9ddeb6c0e5a092069334f5f3f0bc91e663238a5a64611f0eb943f0b39d8c255063e806704295d1d67db726a7e3435682eafbe652539b8ee164f04b0e65f97292e410cba5027e0bb714405b1e1bd79e0c081fa17976c83160778dc9110fbbc0a716a663b163b7246c6557252493802577662ecdaad3b39fb4ca3b3fc862036c2da09a109b0d77f457fb7721fa0f0ac207a81105bede8f780699ef4d80fa7e4cc2d62efe4615d0e854b5113ad9d71991aff4be949e21847e9f769fa12bb26c1ce2d7cd9c2dd6f99fb7eb43ed69e6b8c5f794dd9e35303b6b5220b48e403fbfdde46fb67536c2aaa6ae7be72272e38b7221256d1ecd217a16482a3dcd90b659c6792a26b498f96b78894296aa9cbf61702ae5e5362432889250bbfd3212f3316e4ef7ac48d2d25730e32260768071b755436cc04e03dd9111f739913d4de3072ba79c31b127aa63698515d6f3cf84f5abc5060e9c664411f8f4cede68d8742decb77a5debbc37e041cfacf022c68dd694a527c71d39bc51308fca9a3dc84f19137e49ae2289621d4b83fdc722d807bd6e5aa22ea16dd112dcc309df71ac0b2723b459b2f33231a60d5c49e72052676b46bf29ccb5a202f859725b6004d6b7a1f4dd381171ea3d79f1e3b1672c53a4da8be0b2e8a83c959c0d8887da879d117f45d5922c63d382b8dd396d5725c4b1defe35c3e5803d7c6faa1341cd828d948fda09fd96333932144970d9e724c492d996c139c2a3b48ff8af9ffa160f2575ac05f4cef4b1449150e52a0f03b904c8fd6497e4316a285efa692bb3ccc7a5414851ee7f9535702fbe13433e4081e86528717a2caad394592bbceb784515fa33588fbf5addcb7bef3f3e7f50f724079bbdfe123445d8154b6165531b119ada890a93b4619e47e0bf42cafbc533b7605fc1fec2f9b3af3238153d4229d9d1eb4a8e29add02b5f397327bf74ab572a4c23510276e9020259d135a3a2c3e77df3a7f4a73f24ba3cf9b7f3beb24a872ae9a61cd6df9bd9366cf49eed6c805a2a7e6807c5b3a9aec0df8b7c6e6e33b4e43aa01f4d6500334ffed2f1c5c40b48514cfb4de517531f87895a151e349884eb4f56f802c6e76dbbe70dfebce91ef5acd1338ea639ceff208d47b3da7950c1835b5f7e413d5e0cc8fb522fbd4035ee7bd2b8ed73cbf95541d74bd165276384ae7adb5bce9ae6928e6b1c274df1bfca31b9ebf28af5511f3603cf4f08ebbc47295398abbbdce632b11088e856a55719f3abdc26adb50c7153bbd8ce289411872c253b7bda1ff394471839702b120f985b9d4bf9f288af77e3f9a5466b37633681ad6e24ff195997d3185ccdac8b7412118d2ab6f19d1d0e2c55f436099824a72ea7ccc1ca4ecaf01a39a5d42b5db75a1e140255b30c31e390e6e98f864e53f56c06327c148ff3225f9f46c9a52d8715e275fdcce00d22c8839160d2c47069c03b095ba59288eacc3e7e171153fb1a80be4e2f34eb5906965bb86ec2ddfc179686d04e148b4f8f91738857b2f85c78e1d396b8dcd7698005dfd4e1312647d942dbb09181de9c7fdfa394f980e17872889ab4fd79dd880c42cb567d56fbf137e7281a2f1be8cb3145a24e2fb6aeaa52b8c4388ca0e354e248d7cc3ec0ed91d5072020eb7e87650fcf79ab9a3723c896ed81de9615ef295e677513e4165249a7a6d6008243b14e79b82c9d5f2c51dad1ed96926fb4e6478a636f55051aadb5c5672c7f9d96c1e573ba11b9db43f0345e1f0acfd4a92ef7f91c0285bbcd20f57370aaf28e50b2c0f9873255c31a7aa642fe1d6ba9fe5edfbf4ce45863c5e2906b6727b0344f9a98d7edcdb33f95963e23c444a30ec0381e42e77fb5c8dfd0672021c8f3a9c5461c67b95cd5ebab584540b10840c73db41688c0d0ab973a546fc1012dd1806af4eeb567480674658e3684068669440ec6509916b4166079396155f5619e7f3769f49df09fae01f383f697b3c5daf685ef6a70b83eccc92a34d84042430c19c8dd9c010357bb1e71c10a98340f23ae820c621e3767b964b15a8b683728258727275798dbce4e0b06418b7496fa98ba032def32b58b5d90c258a02fb72bc09e3148353c989beda35a8a5a995288586c8c3310f9ce6d5629975e5aaa518c1481dd20510668a853749d02e67f0cea4b4bcac24d705fde35ed694a720c4568e572a3c33834dfcc2226cb8df6d1d031a47c191c84d89aae07a6758150fe87252b9e2ecd0158c635aa6485616818de79175e965de334a2880916a3bed7f1a614fb9de15ef954f986eb8a0224374a7a876de351d0511611f337220d3c38518721f4b687012fb9a80d8811ea16e2974065cc83bb2ecefb815496a8c9bb8088e721fe6e0ffe76062ad2ca508129907a9fbe6eb883f6857361c3dd99e74b710817242028b091be2f98dd670e9478b54322cf8df7c7467bac33e0cfd1e13d74d1572f0e22e6ca374adec5b495125845a4ac73c6cb7415ddffb207359a58b62976c726c45c9bf9614a926ceb31b83825b866ac0527816457467416fd8f302831c6e726a00d3ca04053e15ef663a0d7253360073dd8f221c64e9114b3491ed0c473c5a6e7592af3492367213199821ec0308b84b3de4f347b9c1902dea1be16f4840724143784321b6095d7178a42cba3076a6456f425388928cab03c2886d082e2e29e36e19494c6ff5730cae7b48d6f8a4811c82a14629147521a8c656aa11caa372cdb44830ed91da5c2fe93e04ba749ec44015f0144020bd4944d0b18e89430872ee483b92342a35ae12a89c6ea3f7a4cee7eed273697d08a360faf2fb57a810326a7eee41bf5ce75db9969470f653d66bc3e1bb00cd7885e9ee7ef80ce23e9c721af2cdac239ff98de32345901df0025f1758ddf97e5b9daaf855d5d37abfe672a709c25309fc13d584908e540bfb0f3c41d713710a8d93574b23334ec966e65bd32ae1d75e95369f9d19a4f640b4878148d029d6b9c0fb9904153f04239c5472561c512724ad141621103e4ae637bd4ab28823612d5c1d653869da8777b2114ac7d27e1f4836d5834453b8db0aeb3345e7d925b01b4d6e629e4c971a19c409003418fa9293321fbcbc862cd1d89ae8e10cf27061d2666c3ef3d3da62c5eadd24cfd7f15f24f27345dd541ce39cccf2b986eb6f2a7568058f01d7ecbec7099e722038916c20fd20a7966cad2984fd0f8321b481bef937cf25519f4187b87c0872cf4a7a660673933ab94a7a9c21cf0daae2a6d5a27ab6a44c2277b1f633683f1dd7983207c9456c6bb3895688f096bd0150637519666f2c683b7007d1d8f96651c0c31655f22b620cd1c96085980f3a9f66ec47730e7330417380473fff377026fad385564bfec86258389ee67c2487ebc76b4bd2905c2892721a8953ead9ef4da81f6dc058390d51cd16062fc8716b8991186db4717b193cd778ff7e4e116f72cffada20ae9706176f01cb655307b36f04c2e71956967bb69bb6c933589d6b729520b8d75fa3ee15837f2c8476a3671d6326b4e54392b38222c1e75b6b9bde21c36cbe1da28f47c1f465ba8a79da1d3cba6d8742db1c0e592e698f105a0ddb723047d2cddae02b86c9ec22c9ac2017d91b5d0527ba2cf2bf732432228ff3d36729ab05329a3d2b9a2f23e145bd871ad82e1099733b5c247a276a67050d79f87261cde3ec98d76e780f487f778e0fcfb78f1047fc19761d2d11f15afe58e2aa72ee28bebeed8cd9f83d08268c94ed615c17756405596a4993da1ce7a2d9270172968de27e4499e9ca8bb5040838e80177f04c50d6065af50d721692ca931e3072cc9297829b3376895bfce8a72448e349b71beb78855ae44ae5fb8c4ee62f530ad9a7961a77289a790394474b4e5d30669b40cb98e38f9a7e78e4b139eb39fd727ecad1b2eb46b9046187f0a0840e3d51d012a936db21c72441756faed6209772934da3ec3b78ec560275d40ff23c0a9a7d7ac2e37308585720281af983b9073fd262a255b2dcdc6c9a06b9b4ee46c63cd7b187f446438dc2658682f4cd9cb214125f9c3a407dfa8a55d364624215a360a508aad9beae32f563cac814eb125b72d944378f448ca956fd0409702c7bc964c1d674a8ec716fbeec938583915bda624df8e3fd912105fd6970afe74900c14fdc32c129a7fded6d80b22910c392d84eedece4ac7d17168a0eb49c3146b492db10f09c70c5c086df34a50b74ac57137247057f0c6cc44f2119493358a38bf2bb5afcdb1a2bd9e5499de73e11b248b272e652edec45046b6d2b91861289f326a9dd689c4da9088b1daef35fce3c138772778d85e0d837d00ed2626e77765fc7aedba09812f36c209410a4b77e6f45fb420a5c34bdef92fe2f9b90f8e8e913354ce539e21b0fe2f168595f4c8996c9aa7200de479a208ee26bcd6a7d743dd9a7e589fb93d47a8498cba0a2f1f32380c72ccbbb1846a4759c8bfa065c23c97b5df51acbf08849a5bd5ef7a965805cc58a726274fab1f9ce7baa0570b2783739bff80a7f2e17423079adf5effa86361012616dc489ef9e6af29750ea52e7a20152d81a8c5ad977be31ad2e4bf41d6a6671728d9a52d702071a8687455213449e07c7acc8ad1951b7933aad5d02c276addf2a819db5d45b47933a42c4d90fe22513b6d6b59667e1bcd9e78c8b6b1896b25f6c51eb42dae9ce198ecb695acc1a760a3c7f7ca2e3b89306da58cf5bf7cbbdf634303698229f7105d15aaca416ff466862b96e8a04f9a9187d7d9c2ea869eb2f72e7d712b76b8144ccb8a66dcc54bcd75e8e3a24ba1fed6e10286cf4a948e24d30ad5317ed26d2b20f9ae0875b6fc8a9bd1fe997977a745210dba2e42cc764e3729f62d27e3804d7098a7e2491de44f6d194837208f4284619e2f888b4eae49a72466d543f6547d0d68073d98e18c462b9fe07a29bb9abeaa228fed4caba44932286e9dba73ad9ed97ffb1550ef29fc4b1a01d049bafdb9bdbd87543ee42eaac5af5615388cd134a0017e3e64ef4295a9ea20e29b15418fc569618d1e1481533194037a17055d6df6bce923d4d10d48de5fdf1a38cd136afb4f454788317732a2bb5454d4b090c90da09d0e43cd5dbd5a2584ac4c9fa15926410da3306a0a7e172e000642e8d3e129e6e62e8cc82be3e601dcf19509961bbff4bef33941fc57672f40cba259d5540d52eddfcba394f15d52ae5083f73b0871d8be59f7c2b4cfb15dab32d44f4d1ae52535e59598db90c94db05151c6371bb525bbde8ac1aac2e725ed2403c9a15c3e75e87275942911e460829cf3b9292fb43779afd6e6ba1382a6f87e9994d5021a17457363779e6080ee4ebf0b926ee846e38b57f2ac63a3072ddc2d36e3cee147ac93e1e02c59f5f03a44770b191c109164ca572dda69a7a21afc7b40b097834abd1a6bc8e2376000f983c912a78bf004a6b131237f59a1c20812be19cb7dd1ada351c5eed5936f60adff304de63f4dd99c39f701356f8c2722a0c0cbd302959b501c0d11a5cb3711a2156986a4e8d9fa3ad2ece2ffc18574ea45fe9d8751792f984a056f6e9102cf0fb4882eefa19ecdbd7e153fb31a09f723724e870ea1163e27cd6126bfd2280bf2d3680768c37fade533b4adc57538a725fce0d10cf86660a3b5304e05abfc0360ab350a882aeb7a1c9f1dac9c91f983c221faf09b794466c2ba50d9fde0a9d00acf1ec0a9c396dc644448ce15310846357c8c2e8a1e4fcc344903a41971ab2aedaff094804137dd76eb054130cf8544994601edf05fb8bab204fab8f68dd8ab9b27c854cfb461a6a39ad8b927c2bee37b08ab7297657485db55790067a872ab77eaeb818f126e13e72aacf0594b2c6722f89080b02b6e270b320d62b7bbd054b2941da57fac003f95a64c525c62a5758534852fd270f702f9591d3fcb9ec58b799292d4ad5524380b0b30c388c0c6972be634483f38fdfc48eed4b7da2aed61c69791df577ded05397a0b4f7c9e7c472050060ff432e1a79ca3566a40036fe89a9c685b1e54b5fea01accfa79a2eda72894f8c5445b1f8aa587e7bbc53de8d3fa97d7fc99e3a65df2fba73174815b472c0c94404be1f563904ada1a7720d882fb2bf7af52b7449a4cb84c0153b739463ce023ef02fe99bea747bfffc8d6f79013ecd3d19eef5d8ec64bed2670e8ebd72d90aa3048c1fe93abc065750805f4ee4372ef216848c824327aab59ea392997289c6f7f6744c067e01d8e04286ef5adb1e09f4b7e9b301dbd21b69b53d08d37242f5192b0157c6afdb97657a1b17a4783d0ba4cb5aef144eba7ed777aa2a710d469db0802fd4901ead9603847e61be907340eb34547de0e5754f37fe38e3b172157ef55b2b026a4384dd282ac7ae347e71b1f6b8ccac7e9427aea3b3c597ec72c7cd546fa6693cbaa25aec64b87e5a76446589e27080b0925b4d1c1eb7039b72d63a18259dde44e095960a72c5a95d33448f1212bb3e4507e3d313bb9dc67c72625bb90f436e3096dddb9442713fc302e2677549acbdf53f1867a8a92aeb2739464083dc157b25aa8c5fe0e44ce1c7ac0ac1d61c93d9c8b19bd2245c88f03027bbe5f7480372f58fa85ed50e34af39a182f8ccc606b82696726495a521baa072201d7359242e3b2cda6a26e38d55af3ff49a1015eddb029b28f557ca154d6572fa5bba649778075d172363944ecdf5506bfdce7add2889016bd794e950994e72e69b899567b27c878511103acc845abb52fa65de73ef537f00afb6e00dc84972af36c6f39db4ee26b4f4b1c2eeed64928a15deb9f70c526f4269cfbb0e28fc72cae1786a086e8ccf02efc4c136ffbc0817c79b64a5f65bbf3021e564c90acd0d4c91abc007acc8fbc3ed99b90cba5395935ba065394c54d0dea0ce3331f4e2726cff3eb9195e265fafc7a7edae4c698c3c073ad37c96aefabd997d7194d7871197674aa728312e65694316b1448b7881e34eca1c2ad38bf08b9f92f32abcc0725ebc7933b7b1a10f1040ade245a0a2fef9cb66d8954007020a84984643e97c726d34eaf0ff89969998609264e5f36c116593d70a5b5346634e0746d9f3633f3edfb609918f86f64eea907d97a7e9455bdcf7bcf8235e5fa41ce6facf2bd8107294b1758c709de81526cd75ee15e1f42820b826c05ae4ec475051fe1ad5d42f70cb1b45f445c0a341211e98a0a711585dc7c17d41a90997eb18746bd42ac85c1228784e01b1819d1bfede857f5839c4035b80e6e24c69086c5874258972f6555923832c585d6e235774e2081c92fc83be5c6050ec3f0b105a59be35907e65cc723704d89a2e489aeed0554f953ac203840b469a8c60cd604b4a8ab09ca8a1a97278b8e75a72bd072926f8237c649ae804429c139f1761d2aa9cfa96c639c093720fb7348222c250fd1a0b9f8c2495b8cbacd3cdb63e4c7836c29cfa27039c960146c56cf8594df0166161c87084f00fb60036ca6424170c86a1c118f5c69b99721c92b36b47d61e3bc24bd3cd55b3b92758ee2d293166cb893f9339a3936fb2143e471cb9defd058efa1f8984efbbaa2f2a37352dd80b23ced9e4eec10a8f2a72a39094073c0704fc248e731acee8afb061ece40070d6129ccdc2920e183e1d7225961364f42f92b674f97f5c2a34fd01de1c9bd61ec9571157e557a7183f4a7277c89938464685762655ceae45ea810509d6cbc34fbcdb62076665ddacf9c47244d2d36568058031f7a6b37276cfce680857da4202c114df3c303f94bb9a83724d9c82fbf3d2113fa6dd0cd9609aa27a249636b2435a1969df699d46872f1172dfb9874a378061140e13850bb5e55e4ac73024facb41e5533358c8df2bccd9723831a8268b21b38eada7572f4f1db8738c57dff92681aba552b17db183516b72bd92cc1a843f7fddcf7c6a4405471502d966f605c321003bef8b763c9b86337291722f971f397968d817ef41b9bd9c0cce8e62d7181ff65bdd43f61972b491722b976ae2d5dc6459208cd45fe60c5bb25b328e088bb8581427b237148f95987228cb5781d35c5c3acbc48f8a5b2851b8e1a38e996a383481cca4726145189f72d9a43d1b5b7c30c3a62b51af0f156d86982611f267c797d9719c2f89990ef5727dd202f51cd501480b74611c59103be6e4ca5ed329d2e3d12fee6e11a61995347425384d056c7861409d6d8d1be5ccf92157c78d03c646e1951fde9b8cb68c1e33c6092c1ff8e31de6169fa6a4a698511c70d4e5fbd2b5661dee09e23e54e0729c42c9de680001799c75e19d3944a372c86101887959d008c9af51f5d4bfb65db2ee2cdfd453051b87ac2fec9afc9a57ff4d4139bfdd1f1ae195e9b2f2f88d1b561c90bdf3be753dd9d333ce830e071c9fa39c329ee529410d7929e224a97c724df2c8faac3b03fa0b7f47fd2ab398547e7fd18ca43c55279a6b618c0b12c4729f980410933e2ec9b93dd32462c669d90576976e3070dcd3de933b7a0ff5cc54ee47951ac13350bee4c62f32be6186450480bd806bfff2df297726caeb4d97728a9fe832aee14a982a9bcce08864280a25f58658f3250147e7d5221c331c7c72e9cdf607115db2d3c02d145245a253b1ac4e018e79b8cf9db84fc6d5510ad4720020c095700026d73f443b03a88d6fb083170d6e0178fabd2639bf68c4a8bb71f182f1d25955d63a5069e36373153068397ebb01df61ab282849c23cef159172de3532a835ba058df84e697377b91978c9835ab9cee891293f5c668bd587c972521d1a14edb1c13ea4571fe8bc3c35a714750d62e16a3bf32cc5b58ac41aba720fac91d1ea6c32b672dfaa53bf94df5fc31072c6c4452a60dbe05b144adfc3321a8dc75f55d2b4b03dba04edb6f821eafa2e5cfa548687fa63dbf30d3bc90d7271fe7e5638ef7db36b8ce7438cb1160ff3196bf4f0968579522c80955bd5ad7234bbe7a90032db481f1b52e722eaa43cb405a8458a5dee9642df4db1dd25b9140f5f897ab7b51564aa262fde221a22a71fd4229550c74d1e31228973313d1572ec8f69683dd1dbd16e320d5420917ac58b682bc5f69e83d97d3884b1576de2650f5efbf72726a5265d2a75a7d5272d6fe7aac51297bb656d2ca6ff20725b6671d6d4b2702455f44445fd25c2df38c0ec3a577d2366ce631096a4937a56bd835f0a79820892dbfd974805209326b26594414a9c058d44d6b29ead6934724c8f315b386ece5f838fc7cb179fbb7e69f005bb4335dc0dd6249f84e5fc9ff04f93722f2b91ec18400e3cef6d281e4d67f7e069ba76d021b1e9bcddf1d3268344ca17629e18bb3db57ea6e463b07a1c59488959068235a5d34d22cf14752c4d443e7217845d0407ba286117e045eec73c76ca29d39b78e127c426f8b278a63b79387223bdbf39175f3253a9cca017476e5d0374ab7131b4f6f6e62b41a1fc22c33772ae978996e63b37aa7a9aa66f86cba4e135949d12e3d0b98a9fa60171bba2176cb74ca20e0ed226ade3131fd93b4413c117b938fa4114c8765c36743b8ae59772a278d893a2698146017036145bc15f17677ad76a01ba85bdc8ee8b00e1b60672034e403760061e3880c4fe115c61094b3f78b23c66c958f9f7c2bdd4b58a1d4d806e30625b68914030dcde9d309ac85ec8607f2b8e1409ce83d25a219145f87232d348a9c542f1c5cb1933fdbb0c06a94242db77c81702149697dc539b02460fd02f5fd928e85c1e7bed601615b7144ab1f85e5c40a896b5538814a0b8c87320427ea63c7ee5172cdd0fbbe3d5ece2b1a154feff31424a584c769154b544120be50055d67eb0d7ca53dcca16d814e5a8e1f1459d6598f88e895c3087010a09724eaa766fd04de8252409270ebc66ab924d83f21b1e2d96d8fe6529d5f1163a72d2813ceea6e6b63defd6eb4b4c0804e93ae44271d170bcfc91f8ad5a9fc5d960e3ad8ea7ae0e552eb675ab80a1ff2e6e55eb452e02dce8ad8673f03d8d98e84b9f8f5fb7dbb386864d525973595b25b93d17194d7c67ba3d022440fa10ba6569a26fa34cd96af0eaabce015cf75a22a2b4964b1ed7bcef5a58735cded60c3872069ad125a18eb1d577e87b8a427e72391b95f67272697c7277fe17f9f481054c430ce76459bde6b27f3f6d14cbba5fb31ff2d2ac7851582b9fdf606d92c00272b6d0c1b85145b69012782a691606055e718ffaea82963e3bbc4da948032d6b0b3ab61aaf4def9bbe367c642da461176f73a17b83153abe5170d186fe5c2c6560f1e6c0147bd6c1e72fe68ee3118a94604fe3dfba0deb57fdfdd9f3f150839572a3db23cc6631287c1df49979d90ba44e6f7badac1d091d41a3fa157027626c72a0d2ecbbc3128bd059d0dc6c2d8f8949524c7883508ffabcaa294eb48439d57265f36f0d2816167d494987673aeefa6e16a548c92b7de764a265648bd85d6b42f9f7dd220d4db726cc32292c738d3cdc14e82dd74259487cf631f5dba673016fdd6349315f996e9d476e89bf66274a9b6b0965c9d47f85b2f735197353b14472e10b2778376798691ed804ad1fd50c28803a41546718dde6d7ea8c35f717e946d55e8c41407e499e2ceb00368d22910f9ce8c3afabc8e1527a8c69f950479240d6726850655c2c59d49c4ec94af9c0a0958cc9683d06d9c56f892155a4fb087201aa14fc802cb564eb4941f7f8b82e63cf6199959dfe5458f20ea926377d082405609733aebb347d7cc3ac1dc18afd65aec55f388381136b83f11f0335c120721622e25c746bd93e2a0b03c14f76137e347701bbf64427c51fb6d86d9224a672c0c6944d4ab4e0196828d45a4b2840665874df23ea17bdab71bae104d763f52ad8293e331621f12cdbbaddbd1358f2753bcbc7ba09e4fde1ddd0bf6f20cb21243fa809fa5d439b80ca9c62309f31b91de96734034b1607e835c42e7bd7b71e15e2e88475b94629518edb22d31ffba001c26f5c3462f9e7ee63b33458be7aff72f08148195f4c2fb03cb3f846e5d78d950b28eb950b2031062c739f087c35a12a9e72dd93d54396f88c785dc7c43afa7bffccc31d64311c4a04a566bc378af369a772da8c5be9bf4aae7856bd83e36b6218654c51b671449521a8c3006be51b728671833a7db6f9a0392f085e0616b57f4b0157b166df55ac2f88f28db9109872749b3889a85beb07177a1b4f93f4be615936544a0b8f819300a173959d0d7d72985b42ecedc6c88dc48852891d7aed274a1a39338f3c58c81f5830fa725fa1394ea4e65d47ec3c98053a1112ad050ed50523a58999c9215980b764172c30ce7268a82b14a3966d9ae0eeeb4a98201aba64553a85ad9980598675d8f6295c347267016c3f4958798949741f7505d87d344500b3cf83a7a735fac245068a0d270d9b55dc7524cf545ef5e232dc18c25dd71f2962355e22ba8da1fee827319b04727810f48856bce74a04ac62eaa6ef25a66f84d55825828424af291ca64fd6c072698b2e305fdd4dc834a157529848bcb42cbd0c97117874bb93415c57d631c772ad7cdbcb856094d970eeb03fb7de80437699b370895c8194fdc9921c2309e5724dfc361cc51b8e06858ea55b05166305800abf801f0a68bfe6a4439d40560b7293646c3e5aba81df541f2ad66db1e2553785c990fd0539ad8c0ab6388ef5c8728d6c39c680737b6e32282088db012002e3b3b3cafe8ea4d9fddce7ca955d1972e06bfd1826b16afbbe0804bde2d8c030e61f9936b6c803f4b64d5931772fea72c6c306b1de18c86a2e8337ec9e2e9a1fb7fbe3cc95881c3eff940192dce08f72fce24fe36ee8351cc45e97f173fa298d2731c56a995e27917cc28845ed345e6ddf40fe243940be95ce52e2fefe43c78dd68f503b090fcb72d3947641fbbac6725d78aac68bb95219ad269478861b278f80521b82a80828ff1397ed96f40b3e36b0fcb7e80b2d658e7dba3e01f1d1938b64bbfa08ae52358345574b4446559f72fed5e553c69b6e1dd9a8599cd5bf0eccd795a30215c683b19ee1bcde5ec61c18098d2040a7fbed0b07164d22cd075b4cfefd900b3eb3c20099c1b8270943a2721599b108c22d9376127346ea3e186be1ecae2cbf444f08f75b1982bf9088dc199ff0e62795c5e02bbce6ef5b65878d6d848eaf2849f8774483e71cb1bc797672de9641a41914c1fc7d7397f6bd61fe998b2b63df1f73c07a3907e26ade7867331c6f2e56140e792a09295ffc136f049fd0dd956cf3f64b180cfed0a2923cb93ea77f630cd82e85f429973783c1214e2864e084e8772acf48e8bacd8473f4887237bf09897acf2c19d39e60cc529319d8c7ff89a8cad3522fe0925091299f02724bc8fb0b9b81f500897825752426613bce1155d5f9868c7fe17d6896fe634272556c0a20b7ca04cf3be7de4b7f3573fdf143dd07605bbd7683802ebc5620047247f393558a596afdc83980e6fe0a953e3ceb4fe3eb335d24e5c9cef7d8ec0f1048d688028db0f259358120da6627b36cc6bb81d7703f3f75d340073066184a72c809c084afe80b582580cd40d21d5daef965880752a93f5ee2f024857ab68b7237667c07d142d0dd1be8155db45f63f7fad89ff1868017383dd2e11deced9072b24d861eb36b06d81ac18127ffc3bd64d8f07521a8df46e33d4559c63b080657d76ddd57bb1d6ff7d9e1d39c95b1af1e86f499048fcfd22425942df4456ed0725768ae89db0821af0622b2989fefbfb28f5dc494ee42f9c641acf4bbf0e8265b39f8f604bd265438a3b13cc5d67c48679fe25123d71208e17df9c01326c3a16bb292f7aa722ef17d993b4d7a4a723160dd3b39d52d247e250aeda5fac7c1d2722030cd53bade367ba43fbc94b895ef0e9ee36d14fa678ad3f9590e9b5b9a973016bded4e68ed39ebf7a442574ac0b156f2eabf19cddf48feae89b27f75107950efed29d81d8546d5f12ef5ecb7c54fb685126e7f8c8cb2fffce7be8f67d04972356c34d46dc1b06b9a487252577e6a2f13e906a8b89a6a040765533661e7f172787b07f51318c3de8b4ef4db794eb3bca2045feb5a3338990d83c44ab9824c72264669854896a08d05b5e8d68a647887d0abd95cd50c9bcad2668b9b83d7f372807827442677821fc332eda03a9171b75047e1448513e96e907a6e185a64be42909624a5e4ce469c4a1aed08928a6eb803c7565ef24c849e4879d630a4c22672f0dce1a4fe0686fb0e58230188899ec171b211280716cb15c760aa26047a8d72ba8b3578493cc26b4cdeec5f9cd91db9f0732737b147a97df76dff5505efce4fe2f586d7a7cd4f50b28b2a3d1b6c53ef87780175d7ebe3904680db583dd48a72b226990d94f918b2c2ed1921f789ecfb5145b4a6c415e1fdac0b0252f0c66f72cf9e7a6b0a036afdaa33f624a71af6f179b4bb282bdcebbda2c93a517a8d1d72b44eea15b771db35aac632c2f42512551368e95e0098d6e752b08eae586a6972eb9762f44a4daddb492ad8367309e72dd95274f4f3e4b3efb08fbf086d9f3912408d3fd434685a498f8f483195a7a4860719f1540d50fced9f6470b5cf78646429f39d33de3b8c97523b0ce6ff93dd69063275d1640cc9341a6cdcbd47620c7185e97e1ddc2e42d13e43f4e831920a4c594125f4e9910fac44ec82aa63310a4c51afeaedd48c663f415b9cd57af5cc304536e999c13b959b096a2ed9ff20b045e9f9f31a4ea20d3ae4560483c40d67e5d1b0076df806d623758f48e57af4044640a3fcc010b681317b14515816eb2d56878a271aded18bd1cd68e97fad1b1e72f75f54dfccdbab4de7d497d49e6d77a4b9b54026de3c3e0faead5ae2e7743b01dd54aa2abc94199d520ccd9954817e7e42484ba626522f56d9ff583e26df0e3620b8a2e85167a518edbb78f040ceacf1ccdd2142b0123aeedfbdf7d3c2a36372f54b2b5e2ca4d5f12359429bc25c65276d21a9ccf152c244a470460796f87351e342697e337aa05bc3ffdb7ac818e94f4c97684b8323b6bf9469ec61b9c6a672742bbcc0c7f6e8bbbcae0395714d6a0fbb6ed9b15a729a2a7954cb3634b13352d1796cf6ed981a053dde7ae26e5fdfc0adb3d00b4b1f0b9e367cc237a9b38b0a42085923121386152831c89e21956b88ff21aff1e818a598e8a0111977be73726e25c73bde269585af71d41114dc27dda633c2cdb48d2387e377154e95712c157c6ddd53ed24cd662c35d6402161c242b5019f7de07d49bfe2ba113a2f0883339375fd57a20f8542c82ca989623cadb977fb8c6f1c61e3634de52f74861db3424dc73de5fdf837257ad7107860c625600f39895ae10cb68af5980ea9943f4872e49bf3a1e4ff78434cab4c2259e0a2cb6f55c1272d141760eb6ea751b767600b477101c2f110d85e1012b496291b2a16567c6e2806f331697af8f7695d68e2726a2f4f00e6eb696e3b28e3bb04b31b9ec36fdca2855bc0febb0e22d005bc7d59a1b046c02f53806a42f319d3082ec0c7282daaff3ed05105c4b12bd0d23bbd7230ab57f8fc5f5927a0bc120d8ffdff7967a40f35435629c7cca896ae5ddc6b72aca2272ecd13a79471470618a9c0dd9b43e0c4fd8be34f8eeb5a007cd7bf6d723acab791d2e60add67aa638c85f5aa217c3f82f8d942027a154dae6170044e42c0ac1f4d54cf48828ef2c390112bd2ce4598b7e611852ca15d2d17c883c76e05e776ef61e42218395865a5b7a7259a3a7b271cbe594aaca239650292ce629072d45fb76b3e5870f5258099dcc2e8bd1a4ec16ae6d59c2b515350ce4cd0c69c720dff15c53a5568cd46afa107c13c7ed5239ff005cd978f0b02ab57152512fd725f47c2d54bd0ad8ac72753c08e794f695c7a40545a90b1e6c6cb32fd85d6d572bf483c678d0271cce0e00a58cb27c5c451830fe95e43185fffa03fddeb68ab724b88887ce731d024c7725e856ce5fc3e937d063824eb283b0760a26584dccb72f9a14dc685db46f14e163a7bc19cc5b61e6edd3b093f3dfe3c79c14b1a5e9272d9da7ecfc72c449a8300682aedfdcc089efaa884760aaac7a95351a860d7374cea2de1e8ab994d414e51a525e5c905d4ea87574697d2809b19bafad357597272fff662423731cc00c3474959202875a97a837d5926f0ee7f262b4bf941a65c300068d2d1d8350d0a496c2922a4a868227d58ed8fb0f7a77feddbfff162fce8722abaed0aa4e28234094417a5da16b9af4eb10f0a85ef6de1e0a52d9761c8fb27f6ec5cebf8811567d161a160e6ed919d94bad0b0372550ffa6f2c33d52c605679142135e4f8c1c617bca5c826f1c0a6a72ef940c660aec6ad95f81747ad729722eae456e44b1c2e791e821bb143ebc052b103b08e236daea58ed2a0754e6c63414bd83f6d457fbc505192874fbe2e9dd5a98e9a2694ca371afdee483d3f32c72e1c551050b853397aa21f753b7b8b6935618c5a2f6acd9e11f1e3a576f9f14722be4c1b7a258d4abe1c24b3af40985c00d48e4fcf84aadee2f01148fdc6052092e4203320e9c81db5be1a41ff57c38f49e516e726bd4e6b2d2a1bbd78ac5b472fb027adb4522074060a411087942cbb4f1f40f0705ce929e58a00a951c813a729e6d47db5a65bf262abe4d8c6383a0c81020b3182b697255cd6e210ce1767772a0e467193d5fbb0ef83af8eb014b983c12db1632d18c86a8709e1b9a59bb47727f26d207e82da2a5f7549f506f6e83c24fd2e6dd0e9dc2b82a334f56f871ad7234faa98db747b94c8ce9c1843cdb5aabbfc57d0e0839bf4858d4c0a90ff1857239ffa159466608aa8fca873e2b5e1fd5beac02d895058ddd1684915d41e57a4a7f58eb44edfc0336d41a7d8ab2009cbf0b7f2c8dc4e192cd0d4f98408436c07257c87d91f5aa4ca5a07dd53d0609d5a4f8aec5f752cfb4e6827b4c09b6083072db902d47e5bd9cadb72edbdc6773f47788b41441f22263c3be4bb11f648f5d7211e2471c1554ee9b9cc914f32fd4056d4050fcd541dde97d8669bd50c5471d723d8a7411ed80e31c6eec00c3d60bf44e1cb4ec6841cc44189206a6856ad4f2727545a0d6d06693bda2cb539a874cce406c40f8f81e9ae929573a06ab4b3682190a0dca35f70045efd1ba634a22351f9ddf7044c1047341ee9b0a57b3dc5121037bf8aaca8470d94fcd41384f885e88d372d20d10d0448baaf383c38d758d5972953084e6171a5787ebc6270b9c96d03f680faa9279b7fd67c443ce962339e672ad0db7f4f96e9679f085a72429ce7c804439f6371fda359459f9cb6e7698bd1800bc8d8ca01b567e1e8c264c8cec1d919baa5b2ce7f4483200ac77b5e9c76a3dd59bc262d5521e01771c95a866bc6ef8dc2c5cb88ed5af5f2a0a8a2918499929ff83900d40a39023636a6e6c7386ab830ecfb62a63c8c540b2d71a5f31e1237297bad8430f5c33d0be1b942ee6759e47e11f8e381e16e9c6de62efdac77197721e4ec114f46c5a67c0b2ab6cec5a22c552e5ddf08c209fb008d55e98fade575df6e0d5c8a7743baeaeb5a000818d68fedcf5de44ae3c9ff6747a5a59f3b576722937d996a90be59cdd9a1afff78a7fb144a84c1162b1a3c8167d69d37c13be7258390b826ba9eeb5d09601456bfa6e268ae19df876f7b2d25a603ed1f0f6ad72483b3225b0c803431071dd3d116d5c19dde18b947e5b2d8b95903886e1a0f872b964fde7068cc6ccd8869d464853a39241dc7a514526684e200dbfd98fe72c7263634bcbd21d65d8a64c37d925302c31d46213e78b516700f3cf242402db0649de5cb1a695559e3672697b17fa7a2d07c2797bbac10c9e36241ab46a34494272e673eadbe4150b013892ea518c9fc0fbb622508a46138092bcf0c9d8075669720339beee80fbf2d8138a5906b542968a4b1ad3469148c7d994d13d0bc02a75720adecaa2c4b100118655eac7bffbec5d0d7416e51f239d686cf35d73678dfb537bc9921296c6b16a806fc47c7a5a03c2e9b29cf37b15394710f205ab08d4e172943f4c28766aee78b77adedb2f9a3bc28fa5c8f789e9b8e1a75120c58652a6721b926ee4444112fe0cdf03f244be017ab493083cf04dd38abbde2c233a6b08724cebe4763fe6c7b380a4b8eb9c205cc7ea9156f4a3de74bc8adc576f2fd98a236257132006975668dca0fa9b80f1b3aaa9d963d22e677c26b46c8091b1a8d872c64541dba4af2a90baaed3531812c9d88327cebb8969bf12a33fbbfb993bb042be30c0566a8255a75a2c18cd1bfaf0593c0844a5a32ec6e7ec3ccee39deac42df1bc5e5f99b7c58e38d55f755795f85be77bbd15868e104fdaf6bcb4db1dd872bee2e38529ff8c859c551e2a14fae40835307d6cae7dc0c28e2f45ae2e6ec472727fba7760f5a0ac1b52e9b80c84d176ff3400aeec1fadee25ff54801430df729cb70d84d4a033bebeb8f380c7424d8bc9ba8cc2b90c9ba336dd5fa66112af3a3fcfd4bf22eb68b489870c52fad52da89a907b6563cf72b4e63b7887ad030772c6e0d214b96242e9f755cabcfb6334ef90651b25f041f433d7a89d4c06d97d260df6a0d32e2c1ea5ec302dde0e7f2456c60e706e3dde8ce1a94600002f09d372d6bff5f9fcc328d68d4a17f2473fe870634096b2e06ea74f6c452f703c4af2720e4adec83a36bb8882d874bf9826d7e796c5ae2710d34ef44327f510315360449cef03a5fa0d51d6fabe0babd7ba48a9c561f83ec934ad84df5c391700c46718d96114ff27bc4fdd8a0536880620857aca4e1f355e06072d797d7bb6e1ee9e7265e17d71a959120d9367b2b02c9168010ca98c1f848d9b25d88018cb35da2772a67d95128393acf2378caf793fb81bf1d458ba0c9063b51b75dd2c68cddb1972d01c124f6cdce26290953dcaf0ae599484e464fce6d0d905c107a61d6860ae72ef44c061ae5f527561087d447897217342af29dd5835c14ca91fb4cd6fa59a72b12f6ec3e1d0b264eadeca01feb10b17aecb13607e781e2186d327d71091016d194db4b3ba252392b43116c88d681934c85b68011d7cf30bd290744ccf294521436332ac9a61c70b9a7731c39dd8c52a714f2af5a0cd3932788834dd349716720d44d535e498b1138d38ff6cd21a2b0baf75148fdeb497e50586bbb723624c47550d526ae8a3ea59c67615d8fcfbea2f924ca424e87b76509324de72092c9872a9cad03aae0f16d13f754548d5fde1b7832304948d52f595518ff6272273ab72b5ddf852c39b8e9b221723c94ddbcb2a7df400af61827b6cfc593a90c950d572dfc61b28071b082c0251e2d92801488be35d8a8e367fb07e969cb6a3499d9272c209b2ba78797a49e8997d7a6d426b4b133b43f2d26706c759ad9dbefd81ed2f3a6a7f55a076018294a18b60e70f3980e22191e66549d2751fcd93a612aac1726017be1b4629c91c8992cc71277291b01bbe9fbf461b330e4fa511028584167277fd19161f92d0e7b48194bc081a82e5208a3aff28ed131d435189653aa83c72667488dad654cd2dfab3ee6c2adbd83a4b025c503eab026140b241dd2524b40b0aab9aef50ea810db2c40807f1b85bb75317e88e9ed982b3d95c6fdfd0d38d5f8360226bbb7c87f9fbfd5b517ae3ada40190add3cf35cbcdefe62406b8d0c6726d1e3e8e1385712823b2781d462cfc15c15eea962527af9c52a6397d01401137f206da8446e67d605eed2aadd93a64dec4efcab20aed6bb4b8f38d55dcf4cb725f10fa4b9a92efcd7f19f08c44149156b1147394666042e924ae1c2634006641e4af6c1c62ff4f0a0ccbf8fe6786f3c1035e06c46fe51ccfd653b029d74e8772b53d780a33fb56190af2c19f512ab31504b2016b42e87b2264e2b9411c3cf1590ffca170dba2824581f8b922351cd7f92cbde6da8777d471f748f8d19b8785230f2d30ef938f925bb68d5cea99c04fe2ce67143aaa4cb3417f8e1b07388a2072b6c747ba4bb9e8d0e14755f42b2783256547f1f27978b2043bbccb2096203b06e18fa110b8af0502077a95ecc028994839631e98cc6fe8218978565910eba9037bce454f2d042dd03aec67aab0a25c5b1fb5e5c9642f1350375aa026ee0b8035646920bd598996fc8c0846a96db42350382784a34309955ab8ff78813d4a572c50c71410c7ccf4d4dde91bc2eeb754bf50c563fa5efca93d1f83ee72b2fcee72c4933dfd65ab2535e89fa4926302b6b0d3ce13fbe7cdebe8335c14927319c17258a2fe98a1dfeeb7da18c8257fd58db0697c68af7c9ba3085dcb11b10589240f25998826b1782f1d77ac1d03bb814a65955efeb108cf908d50a0c8dbdfeb3b152f87bccc42cb4448712d45c03af9c2abc37095c64a0a98b4acf6697209ae1f06b1b3dcbe32ceb92500e53214f4278104a9b9cb6fe870cddff5523e05718f6203ce9e5755ef34fe2b989f90a5af42e61978b504b2bd0dfa343247e9cb65d4b87209ef3a345a413c52730e67c436539b95ad985a41ceaf9ea3b8d4c2cba76de50767d6b03aa9396e55cb1e40b27684a890fb643730e2422f9fc59a1c0d7b08ae386b5b016509a586520e703d0b27e4ce149d7c7572aed6b962375ef90ae592ab18bad188f7a132bf005b8c312658d09958f1962d558b708fc2664a015221101b1fd9f29e3d58f5827530d9495e3b5f2cb0143a70cf3ae9e5fde68c722221813d72c4b04decf33bdb4e908fd0486cf57ab496379543ae1a7c07b2e4babe34fc0a72e820715f8c44c70e0cd5d21d9857679edc5f361600f4d9e72fd3a5f69a51ad729aa34fc69fd2ee989a6e15544e66ddcd751123b1023670743bb6fb9f8a81da72452692ab79600634d2b2640fad5a7c351e655cae9cfcc804039e86d0c15bb172beed6bbb27fdab19b2a47a6fe99c895dab319d81a3ce098429c7a91ed713a16333bba72f875882ee0a9f1a067ff623ca721c95ea4c9a60d336010d65e5fe3772fff9f34e81c3e5bdf711c11ef6ea3abb20b20cda06ee685dcd91100bfb91f753b4f4aaea72200a43e2d04a504fdb720130e6af539feb73a8a4f19bdd1c6aab724c2a942be65339a8c4bbf5f958a1ad1638fee3731c3076269d135b23e38f4f09c288690576f27c5592456afec03497057a0ff6842667261284a14dcacba7a6544524242f85bcb87e45e291b6e28bd7c1a50c4c137d8156552937d3000946a77264f7a9465b0a42ba38c24f4d41c8cf9232e50b58c9bf1af4451570c53036557277962730b9599cf909e5a5006e1d4d280cd31f9cf1b9d112d1e8be17d62b5072242d05e118c43b10b5410ce4a75fd2acf794a3dbdcf492032fea54bf14e30d72f7122d4e9f1dc7661dd5551dc15b253d4ba8683526c09c7c2b92b1da72524d7270bf6516cd1086e7912439ccac398995ace7a854f3e8d9a4ce2144b06953aa723d33cd9b15d4d4fb4cd9b7c1953d26e0fbe3ca523efe5f668db22d49232f021f9a70b2dfa5f68e0048266d92e2696230a9f2d898faf68d1b24734c7ae878bb72fb49e275baeba0d1c02bb77b7d57cb070ad54de39d17fc4416a3e668a942f325cefd914a4f79ce0e3ab36c91f0208294bf41ca9407e63093ceed7a89e143191503441a1d8386e170b4a7edcc7267819548acc4a213b4b77bf3db5ee3ffe42c58175fc63d5612d5f30d3a102bb91f0da4e960e1ad33fceb86f65ba82284497d72934ee377486f13f776df2932513e031daee6a9e062484874ed63ec8d7208f804cc796cc8d631a6412ef6d3bc8883bd19ce4be8f8f8da04a85d46812579480e5e679adc98426b8ade0a9bba09685f31b89ebe17d84e0f458b7677e01066dd624857de7d654d72a7278feeeb5b895e9516e66fe50155ffc2890c6a34f2a873da490fa336f9da32f4734382a2a775572c99d95046ec6aae0942e639262ae4fc9b728432a664065bbb08553ed2876eb6d3ccb8bfa05b5db2d8cba6cb39d31c1f2606752ce030281c39c8a4c3894b123e5d74df693b208a10ef26dd8911b2b3cf3d0f506d0ce16df768a7520cc9a4c1a65c340414694c0783a616bbdf8f4c414e5b7243df6bd5459004660d1be4661bca79f5073bc9a762bda1152fbf451c69e6bc728dea32dbf67785db1ac6a71682af124adaba122a171ff7bdd44f9c0b96d93f72d536cdaae8bb7736b2cd0270837a0331b0ce5f303cc8573b85e8a4ccc2788a4d34602f2b1476b29ebbe4f2effd1084a40cbef0700b5f1f316da93a8a3f4a2072ab702ce50858b4439210d993d7b0c41b8ccf4d6c50cbff7f4a5a64a76b03a67248b3d284865005025518162ab10ef6c4771888458857ae20cdad0a66d4302472a4ecb158c6bf011ec5d4140c21e5d04bf1f90fab8a7bf3916c97bf33e1a3b065d8b88b6614953e3c11ddf03573fd03fccf43d15aff893860471088cbfe789a728d1f1edd47b7842816ab2a7cd2d88fb9953f1e8dfd3b07eee0ccbb45e741f472e9e89569a2caffc765608f829e77deb404a991e4a00d634266adc977dd3991729db4202250955bb7866945e2eeeab306add2ce47753b1643203d274e058b87728a9f2721aecb46804a6b6fa378934bad8ac3499a2d23e141fdb7f535350b1e723f249988d3ca87271b9b52ed79753c260d6527ef04cadab55e2c7da7ecdfff72a473f718049125b953884d231debfbd0110611899b5b283303b04f97cf3fc072fba8708d5d8c8fe0a432dbb0594e1ff8435ec73a415262058454e59221f270399a40191f81397aa884fabb7b4dfd216c23b2843ec56b5ce4233748a71ba6ef724358df43bf7ec534fc7a4b67ddbd3558148776932564584dd89a4b153bbf1e562d0ac5dc163f56731378332bb4a89eade1dd5fbee3cbe57c80dfeaca8fa6a37261e01f40d2f8395ac3600ad9a632ee03bc22ab8a5b9231b24f8729539f0d880b48c5a603a5c08deffaf428d11b13d7aabecd85a2bab781561de5a12a7ff6bf373c68b2121164bf18e0267ff9d871ce43f3d4cbeb1bd805abfc5e6145e3a87a084fc4039bcdcf9212ff9d8f9c5c91285910ae80751923bcdfa602f8f936f5cd104d30552c6bd5991dc461c72c4b9302d75ab27606481ea55f8f54fea72f7a680622ae68d5b8df0cb08db3dcd9a28f15a7602de0a3d14d0e5abb9afec9d4fa6b3d521a0205cf5dcb34c790e8d91993d2379f33aaf9f108be66739b612666e0e372088bb47631771192d943d392527bc472423b5b8b09a3013e5247f707b560c272fd173afc8386cf1ccea8b106e0c538869f2c49884a5700c52a9e3d51bae69d7231ff74638bdbcb144a462ff6ecdd1aa404536eb83e9db85b0bca4a526e34b27244cc59c8a9cb8a0a4ac5bca933b3e97477e0141e47ab439ef85d187be9bdc672ca689b1fef75e4cd0e56da569dca15f37afbce188f80caf71fd4ebabc870b427acfd853586ce123d4d87640df3ce2d8da6e68126acd25f0348cb1844ffcb8e04a7caa6ab827c5812fbaf3a1fd65e4580ddd15f8a9603569a6a34821f64da0f3f21f38858f5cba8507122a7fe3a4e894e469731fc8f391c7e510489e66d837e729c54734397ef6a065c49ad8f2fd01b85ed31f11746fc8176a4f81c25fe784872f280071327bbe7f1ed133e8b49932f416e1f7cd03e965921ebe6defd7668a811179ee6a3d918f813cb8f7e8e330fcdaeca24d74f61fe9d16e30cad42527fd072bffb90d928fdb7be92a65766d1cd4d1e3c3a9db750de62c3feaa84082cfada72cfba636fcecd69a58fa432f5cb51d8f28454682d67ea00c9611adb02f1e51345d0a6dba10ea47c62050e1c0801f384b944dd544f7e78808279aceeebbdcbb3136c94d734ed06f5798c3fe4a8965d0437c780d6b0dda02d506f4c2bc68e24a072359b6aadfee0b476d94a73548f01682d218f3e13dcdb72727c1ace708166ad19fc914b74e3372adecacb29bedc4776ade67fb31fdd0d3cbc51ac199adbaa3072963d45921f1bc0be3962842b43dd026698d6fe7ad569f84353394a0ef862b072e913add14d20457e6df308854f4c226eb72999d920aa3de7b7df767b9b6eec63c0b1ff7a3daf4e2db8114b9b7047099fc95b211d45a3e047abc400636de63b727700616471697392fad6d45f7650aafe62159856874dd51e852b7e7964ec900119c9d7a6ed74687653af1c0bc323597fe8448f8e54d2205a29a1ac6946ab8472230fda19c18bce9659ac7ac8aede22ba8baf1a6f7d5d27112f6b813226f1b416363994e2f106afd524860b18b969e325cd53b036eb840a62dfc7e6a8d653fd7243d217dc08ecfeb3deb3927f61190280d89ccb73c7e7538d44563a42c755b8720a1b73bfd234fd19dfa0c1a5823541a5a964ae22779c707e217c2bc7aa752e13806e4bb6da67a869231a276ac730b7e4bddcd6936a7c68fdc90297f05c5e8e3e893676f3c398a270f0a64ae45837a8e7d3f52bc9d8fbfc60be0e44096fab616cecb3b8079eacd3d3a18f9e935a089101c2ae19eee6baa86536c63f7acffb0e01db40f34b0b737b1424a41c42f7b7329d213ade5189b30280bb066bf74f542172d3eceef13e8ce31a5a0117fc5608bf2a9234f4ccf190969daed19a7558599972598127ce57800fd8223f7544503b596d17d35de20359974917b2d91f4e2a3e621c630f11d3d587a58ec55b2c8392b1a9baf22afcbcf4592bee056128629b747276d016f197279265e1b2aab3f3238660106c23a97c2bc893fc89b52052d6d972ff0ad6b2d7ae0879b106b81b892d35648db995520ee42b9b83abeeae23af87667ace1ba1afbf4398781a192212736ec7b24e57b7a6647b60f4553152725ce072cfc9c6af40ab7d63b16125a1966e04a1761799906cd28a948b1438fc66185a526490b9a5bac52801988ecacea9d7a098ecc25c8309abe687a16c4311ba09803c8314c47712f5a6ba16ef88728146ecd9c37c77374618c38cb325ed57e1617b72574dceb33347f6abfdf4a76bdf1c9b1b150f7bff388a02c4eb9aaea2a65c1772e54255846a0747574c93e298628febe964e4328d3eb8ac4d207a678e690a860f48322609898f344f2e73002e25ea645f01be7e625a6f23a82824b7023a322572b7161fe82447e4108294929758f6cf5874027e22af0bd006eb1842912f25c672f9310c7bf6115f5b8246f54c296a83169825cb540c1936d3c242be9c366e2a7242400f0070d43ffe72926450e1366516b84f8fc692c6844deca498ae5f18946f500edcb2214bdf3e073560c7dc81287f78cd048d043fb9713a1e37f42cee6261dd7948727cd7895db7cd309646d336a1d45e45bbc6eb6af7d22beed009c7e00e10b4fb05487b5146b97c150c8f0b2b0d12eed194910c8a278ca2547ab7c26668b40f4b3c1973048064dfb148b95dc1f0a71cfdc9bf741674f1ac1ec180e959721ede11797af43f86de827dc03576ec3e6b23b0408ab0233511f629761110c9168cb717d8ac96298d06bae20d839ad9c3d788bf0348fedbb390a7689c2b4245002eff1fea5db3022ee1fbfa5e2cccc1bcd14085cafef78e34c44463b89fb5f8723b7150c8fff3c1e30375a80696c802a86acd4dfdc00430fc0507194d1668a933ce1700465b384a595673e6c09b038357ccb846b583a7ea3554a011d2c71f363d9375385d27d45164ec63059bbc78bfefa5f40a815479435aa00f8c72ce718b063f27b19846382ef0ccd02da44dbfad7de7e56c3d2e756ffd694383a9b6322a723f4996ebc2de5f549c932c4c2273bab5f85cddc3b1237e740a9149c13c4e73521ae43efa0bf723323156deca0687b36143ddd4b95e769882216c2d1b1cc4e072a6cf87e3221ec6241ad42a68d023d9718c6f69f4999d39fe38b4a305ccfe85649b0913b6ad8f5f54dc1707606390bdfdb4fa4bb1288a0f0c8b910dc296bfc11a3502107ce11844cfca02f2de778574465845499dc8ce948711a9547e1498054f5a2fd4792c90873eeab34e89ee04de7b818d6175d70636d7ed93dcc22727be7246f07776126c558fc4eb068911103289a851b6a35e40f7a83df349d6a74be272451a853bbd72bdb1c8898dc2d66c63aa297460011d855c0b9f4ac080f00ffc416ae3330c5b44504ec4701f7161052fb658c8a8782664dfffcc8aee0b568a8f39e4787655347fce4690d56ed547c8a9d842c2ae1e0fb6c1d900d2e5f24fd73f226234aa2c430ae6b6de028bfe98276d56014863cc45ee59fc454ae934f3839d724e82c2e8d0c7bbde28ee8bfc81dceb05a4689804740d3380d90625522e3b4d1d2af8324ff5f191239a426c38b347df557f4e0b9b91a8585d373e991615e6cb7279fff16f8396b7b6869d945fa67affeb342c2b4ef64f1761cdf91cdc45812d724c294c6ff6f8f2b4da344a0963a5f2326a54759e7b0e2af40aa3ff3a6dda9f23183b2b42a1cf1664ceddc83827dd09d5e18ffbe25361176f84a220b9640c170159a0ecea8995d0d2cf7ac0278156e56d923fe82fd581d6e9f00c85e6231753722b5b0a6b3d59c0c83a384713effc514e2ec6042a095b5671f396c955f1bace72466c24e3b3b0febd5a40bb1d3467784cff4ea6527d69922d3d9da1e89bb7f8720552a44b1094ae6ff97700a3ddc0a1ad34d574a2bfe166640e3ee7be8b325333a940bec1615f71cfe42192583167c19b6782032d5d1c79e4ab63d58affd4f1722df2b0e2497f13809abc8e392932918060f89e63508411e6c4df96ef7d378d724893b136f679a5b554dfdc43ff34699f518ff19ccebde2c3d2647985bacee7272fd6ee187631186fcb1c6ab5cd7ff3a42011efaf2ba5c8caf26f14a6a2098572096eb8eaacf20f44b16124f49cc6e03d90d9b2b928477fd55ca6246fb016c77282a6b039f497af336e65b7a045c9e55f3b6088196b61d47d92e7e0da214eab72df4f932c3847142b8353d895d656d44617caf96c334b87da0d715a01f0ed0f3f108124b7c10408f03f5e4d597d820457eb8b9332df44f5ad68e40154c1cc867235483896816b5f439d5bfe95346eeef4baec9bd8be4e951eada14a5ca8db4972a27c3b1b96bd3494f8689b9a318cbbda0f8c7bc622671c54ce155e8a8b3cfb7242128c3729548e15d37cbb09994460ecf3087b2afa789edd3e0d58aadb21f772b58b0ecc9f95b23d8da342d2bb089273f9d9404c6c079f4852ca5488a85ec872bade0bcffef6cd42faadac1b3c0bfbb286477f8a686f219e1421cc0dcbbe962d4b9e15f1a0ec42ef6e5fa5b71ce2a05fa515e488ea80297faed1750331b15f1f4223f65e5652271decdc88514f605abe3ab62d6bdd93605f8d8cf98b86c5bc01956d885ec8ca2faa3cb7d1356c90d7a0d7eca2cd188e8e3e9154bf6a43a74572a6fda819c590aa77ef60d2dabb801804adbadd9517cc4263f62bb40379c63c724b1663a1cef0cdb52060b78d3c623fc32a44c60c1b95d94b4696d4b27eccbb72b9adf379b74cb59e237b979b5b6c5c690d36ef3dbe745fd3dff97556208b43127c5487ea2b585f6a688cd258fa0907cbdd36a27e3fb3464ac3a136467ca8724bd71d89e17e6efbe3e545deb6f13a63f8c005e923b1bc03cfeca2bd9e7fbb1872bff9230dc089018ffb4b0427fab276327ebcc739546dafe446af1f82ae73c37237176792f72a1e00b2438a0c5c164ed842499dac2e3fd6d4d37a602ec37394728ba3cd9e3b353f5565b6b332f058014ba11ef2a33b3ec3bd1ec660fadf81b146b5a2363953d8c00e93f4f1a876b3275f4a34c68b29a973ad08a87f911ab1dc721ca91a3a7d0d3b80cdf3c6245e284204a0622d0852a1514a84d9b5673f9c9208636ba8e7b4b8f05f44687b9a2c27b92df9cb7633ebd06bf559c1cf14617d2172e9b04b2d6758c6d39cc4af1ebe880cd529b5dada40a17f9d11805bd7eefc1d72e94374f54052724a4d3e21ef065c64039de24c7642550c560875de7cd9ce8862d2b008aac55a0a8494621035118ba9fdb907d760e6daf841f91ab2b05c99327224fe974e6a2518b5899966bebf591161b3443de3cd77391f4285d30627728f7235be8b02871e58c5638ee03587ddc1b2c917453ab5065c741bc2b6da37da9b166453ba0b48ba9edddd40c5db7c8da78dd84c63ce0f1362f76d7a276e983c9c7178f4f08df302c551957bf042eace3c67c1e796fbf9857cd11d1689d6c91f197255206241eb5fa361b751aab19fc35b25c6a0cbfdeba08f08801c3da410fb347238045c1e061911ff0a22f0681c4f50a1a0748affbd0beab6740f323daef30f34254031092a6d99805ad1830a239c8dccaa0d5406d1a6e6f90aaae5e14e8bd4725ab52039f150a7b09ab951df544993b891df6152ef1e6a0e819c49acceada872819f39b02c5426bede8b2156b5cd30215ddae2f04e5474a8067f7919245220079ecb277f0bda62f6e4f724188d8c632fd3950c75b83af0891786e2a66465f272a67da9b19d3fd7ebd2fbb1c6eee4d86ced1f146d251a2400da668936d0087002bf4f745d8fb412eb35b6190133f030f45f0e26b11aeb08cb1d4da57e4dd95334871f6be81cc2356ab421ba5fe8fb1faf5250178c98c2075731efd0477a44d6726ec16ef0f015de5fc711737c45f9a2c661e5f72c70f3ded486a86c3c26233b728c3d867a3cccc7747156e3a6b697f7392c3e2fd347cc81420d42b298b9474972f433114388adf9385a3fcc466597849c13b54c98c6ecf647e36d2c205b574b115bae69e2fdb775f4d20e457a567ff4a41e8ea2475b945de046930ed311911472a718702f54697fa0d7e8583569204b778d161b358822ef9e1e33ea30e84e5f3052c3ed51d9632a465943f691d5ff3d31e19dc208e3428668fd575cb7cf1791724fc16536152bd85157c963d355e208fa04c96c41b74e8c6633c1cc86e479b1151bbed269fc602f4f98c3a760e53da4b637039596d07db18700f2e1b2ffa56b390e64610a19dcc7f914173aad9c6515846ff58532cb0aef9c8472ef0721fa28720340aa53b996eded35e29e00d7e8ddc7db67f99fe1f93564c684fc56d0fe81222080bcd8b7a52a6fa2c8ef13d5a15569eb7a0c4eca4cdeea9950a2c423d17e7285ed42293768fa73b3ccf5d308dddf06dab2922eeae151b1fd9fb76c1533d25eac0ad2c5357dc2104fd70e6a874fc585d043c3e663de32c9a37fc1fc5e858872427cbe81b0c9b187c7b04d68a7cff622f6cad2518d030cac0158eca9ffbcb8722f63f23aa4cb43139d5ed415ded72a48fab8334ad840ab8a6641935022539572f4aae701fad70cde9ecec59b2c1577f60472e81f1c9a8f40b63243b47bfcae70906d58a276d829bc1136527953ca1c6d8b3446b2a8e4c0c8b5046488c1885124ef3317e52d5dc0f97a62c1f58f2a48cb7377c98d216af94be629e301f5689e1e48b19b8d414ca008134b0ddc505381d9e387acef79706b98be34b91773ad6c55bd18b1b33c7cb0986b0f6e3b61b3f78664369be26a93fbf90f980a9db2537b354c45b4a85fbd4cf454ebd275d87f9da2f4abfec90406eaffbfa23b62064865724891edbccb692ce6440d5433626767dfdfdb63d1160a096dab2b69569099945af1699185b8f9b27a3483834fa4de829acd543ea0a4a8815fbc6377ba5d206d72658778a937516ba82886649cde2974ac70eb179a733ee165817ec4503f25967298ee8c01830d1554e2d34a1b616f8f2741faf0a3e496b0c3d6e07fe82104006d2522b6976e40df23a225e54f1572869d932df5d869c0fe55fbe57af68c7f1672212a7e2a7876066629725d0ac97e777a6c5fa57bb01826c427b93e0e855d67107f2f9e155ea3a5256edd3ea1da30431734380c884e3c5265837a9993b595f1721dd814c3825f076cd9d96615d393b47aeb9dae5960280781ae77d3fa9c9ae972aa7b54f4201f964e5ef6041fa41894abc84435c3b2c3a1277e8f91fa9745d462be1eb95056bb4278cec4fd7bfa6b86eb50e0e3daa54d003a29505bc2c6cbcc4528952584b5d7a5c2fac37d23338c900ba73b0792641878ef5d4a5d3b3a23e11778103b6d2a5ea09ec96872e8eeb7d5dc499202f391c8ee42a0463c033d02f8597d71ae98e55f9e23bdbf47a53243a4c12e244e22a448c140557eec11c1f2e3722279ca6490546c1d5043015dbac0a4deef9f58b73d29437ebf2414b2f6c2b0677afcf1411b0d39a51874c29d743efd1f90d07af65257a4d642179f7210bc5e725d4e7d3f0e9213374af50851f2dc1e75de069b8d19f68f4702605a3a803739729d31741de6724c303d404f21fed180ce61277a85308035185dbf242b0dd11872ba4aca7d4fff84811ae0ff718deb979a207f1ae7529a8088d813bd768b1bd07228837437af94e0ad3bed1518c10137afd5445be30ce98c800b71bb36040a5a7224ac2d817d03d865676598280f8967b45a7251fc46254ed35d0a20c5a4e8b3723713077a1fc52b296fc7a6756f594de329f80e1f22a05fe0f239212d238bce52da53c90b8c01558844d18c93a1d9a49798a755fe15f7443ae08592328c9de772c560a3360e8cca66650c9701a7c0f98cdeb1729cdce85de903b2dc897bde3672d557fc4a0bfea7a36bf59ec6480ac773d6cf9bcdaa0be2b18892a75250c4bf722f477f2ae6254f12c66f1a0cd3c4050f680890b5524f97e4e048f6ce91481f17ce18517a524b72629a33b5d71a65ad0d1a35ea6964beaee446e3c5461c5ad166e8ee022d23214743a85f1b97faf83bf330ff022f0f0b1c83c676ac3cd63e82720fe2deabf6a1dffb64fb5f3179585cc378086d4606ab11bf4e98fd15bd735372fc954a5e11b967326833bb5115a56ee2c0405240483c3824325b791e596c04721f1ad46a4836dc8340b134dd357533e368d6571d2bbb5f6d42dee2ef2cecdd728b300739779a32a783ad79a615cd4c43df29aefd32e9e672b70b1e6c0b43bb722a889fb32c7f593fa6c1a3c6bd3986f730880ffa61c859bac034aef15a0cc772b013945167606fc757ffc9c17a27e2ef3418eecce45358c323906fc79b80c572bba9ea34d3ee2f9344cff3652685877f78681344ffabb88040eab89f15c95672453ee0a90e6ea3617b978b1a7b314d883e5663517fac5ded2ab3a19b52ab1314a31a59bf24263786f49ce72ed769bd3d68b3535c2460be2c98e7a03da84c0672b814fb094818228c199049195ffec89824d0a3fb152a6ab8bcf40ccc17eeae72d9adedcd0d5d65e9b50a5097f54fe0c1cd34de7e695da4bd384b2e64fed60b326dff4d84781bd74b73b926fbf31afa65b58998092254418487c3524f520c6372fef5d0047f56244d2ae0405194c1acc423610714f8d80219834b73a52104e6724886fd0e707b2a66faa50aa807950a3719fb5985615d8a12510bc2ebc4058472ccd19a03ede931e4bd2a217553f8477cd0759dd205750f273685df71aab9a1283bea4c7cabca7eac242c159ab08fc66eceb0ab9cfee08fd60d3d771bdddd053008770354122f180e638c6f821b9f1ab061fb84dc94c70f0a08a192c9df533640f58df8d8608b83ab6a68bd5e7e2815ff58e57fde62acb4fb008aef3afc3d7072910a5c4f33612dc35180db217f3ad0c8f28c666b8c87a31577fc77bcd9dfdf4df1246eada3ce5bd8b9c6d5b685368573d659c7ae5da0d518627af97dee916052fd6ebd2c0992517eaad0fe732a4cb086c3d664fe6d740810077584a045ccc94e9198178b261e5d1a23f42d278799ab9b9865201039b19edf905ecfab1636a272dd80fbf9706044b26fdca4ae13392a5bb9ff3f7044e8000a64c3f1bcaa0b977247440ac0c79d035923efc708a3d1d67fe3216c18a853efeb59d848407953b557ffb1a5f744bc67c573164c4bdc952798762126ee48bfcfb10e3383cdc5c7bc2b7f2d9a2f395c89c821d59776abec9158a7fc0e4417f77dc8556ebdfa361ac5723ce0815dfe6ca8ac2066ceee28cca32d66517a92e0ab079c92993dd0f724cb729c4e8a295c28e867b1c5859fb51299d43cdcae95760b8c84d00aa1ae26e6331ed01425051b89a6aac815938a191cc662fcadcaa13f18688d916436d4968ddf721b1935f73b20a9d42458e43e16e617517b1f2f9d2185dc23f1a705998856167270e69a8cdc456896d87bfdaa852bf952f407d645aed4ff2595ab5bc77bcd7272506c5ad3dc732f016d2b3f84d21c891e60b4bb19a3c2d7e50871bc14700da113885c2db1f8bf34a889278663b6c5e775146c75bd9bba9ea074a26293975c2a72d63957402f0e08aefa73a225b2050d4408c1107700fd98e271d0ef8ec5fa1f7239a7dacaf584dfcfb2eaf01de82f6a8b33300a08242f7b00a715631a283a7a72f66e0a261a43cfdbe8dee51114c865af37a70bcd1cb279235023d96108e12f2e3a7507323270c8b9a43dad4c61fb8d1a1158fea529adc2765821b341699a641cbc10f77bf7992d76f6da2ec9d105f1d2c3c5a58c2af465027d73aaf62e5499726bbd020085d59d2806dd38622ba5b9fcbbd6500fc763d8036e57b2cffa4ca92ad263c2322fbc8ef61671c8b0d09bec144944767c065d4abfd14c174169f17d72f9242ac9a1b38a35c18ca713f26a82ed14a21465871d1025e84aa123c09c6f72dcdedafc249fbffd690f83da721b6a7466af1d0bc0d4e6025cded8437173477202ab078d6bd38d2576b497f25bdfe0085d6e09d602b853fcd51e72bf304e28424f417df913891b54cf6e1034fcc52b3c4f68231a5f11072c0950b4cf82a5e94bc361d01426a44ddc2100e4ec8b4fb2de5d9e8073b0e67ef50cd895ce2c075a60757615d6bb908686c2ac9bf688f9aebe2bb8832888faca97403803dde477d272c14f0cce729a56cc4be689ac1d6ab5bdaa36a833bd4de9974a4e07ad5fad6d43fb9200ca16b2476131a17f32810206367ba0280b6ae0b096476f450ccdc8ed72b720e3b9d412a90b759bba440a9ae406b423eadde7cdc3ceea5c758898e8b0724fbc9c613db796506d8cf4e30cf254bfa17260d6fac86f984161834925568b0217255e7136020b4edad2029fd205e7689cc4a4fd101caa8d2c8f74b3f6870e49fa088d0c68c827333396289d7c0cc9c200ceb4d1cc3feee3e8fb429dfbcc29039672dae8fe737b9841910b7449c9635683c503158eb84d273a035e477edf5372778752ca555a71252c56c7b64eed1a9896db306cc4951c07751b442ca7111a51fac5baa6ef4d952650b658779491b3e6536dc42e9e8395d690e1354bb2f9c639c0d33860e03f44139d835c4031072ba6c8a482d420874f7454763c2c95f5ee7249f6f414efb56696c4ab876a27cc5cd7e7c83eb0150a84f7cddc8debcaa3246587ae0ba882705bf59eb14ef23944faa7e2d259e1ab5e548df229aff0ec132c458a9e61776a6a77636fd75452f5f10a5b68cd69c424eb8b9c016970d2d1c9d17228f4781c4245bd4e3469b7d53e078478452c774cd646060c177c7ad1c095003c695dde1c18f844096dec8ffa2d952e3ce4992092e0ccbb0c434b2ddc53f77e6a421616626f3f49bfc026e03f82703e0564a899d5f196b3db85f623b18f4a4e72a3296174dc43835666407fdd880d0429f5fa68c05657295d031b59b2e58660729240e427466d121b35aeb461df3661bf505a8ef2d6245d832f40b2fac9b7896b173e408bdc888d94b3323342ff6a80879e9a9d4c02035b44047b925983ba606ba35f3104d21655b20128f5ea57869aea059c5df7a1c3ef0c816d18f1cd3568723a34327ba94f3a6bcd9f366774a330b31e68f9c5cb13a1e28d23a9c39a45ad3e2614867a83a21791bdc40762a9bc84829cb00b0e809fc9d1648454cb4a71dc727f48993e4d7260211b0795c4fe760959230af4e7929ebf0f5cb5e6508681b872bc765ca7db0786bb892350ea24a477982342ca06cdb28fd06f53dbd9e9a23972dc3d0bf5e8b69dcfc60cbd5780ff0b576636a99209b2c530f725a34d0db30672de2e3e420dbfb169a236bcab20ced625987e8ae430b3520059aebc1d052e13720933de4a56f5d4811045106e1dcd21b77a5e1f00d9f64e5d9a8cf592eca40253e79a24715ed899651d38761551a17555805146cfa515c235843887a5036f5f72e49085235e1ad50fef662ef2fcce38db8e0c99f6099c0e6f15746a4e1ec3691443f8d3bc0587f36b88d0196e8756e22dd65d6173ff5c3941a85c9da86c87821630da4385962e371e7a197cd44d1031378f2eaf2bf3a1d1f7f122837d3d10e13747c4c5a8be66251c0e98f3b05cd56791b71516b7b7158235996de348a9bec172523cb5ea04aecb83c00ef70b395d8ae2cf55356f86614ad719ef1776dba533724f758e42343cfd20199d5a2615f41be40bc96c9b4ae7e5a121307b95a38e6436b2c3c80171405a55d99897c90ebc4197d5ebf2b613f66836aa47fa3870968372ff6019b7c7b4ca776a81cda307367d4f37fbbf8c8fa5609f6c3edc70f7e8eb439df49dd4bc931b1c312dec393efa5aabd4e9989ae0b186a7e65bc73bce5b544a52ffb029875bbbfcb6063d5adea646133a2438c34af25903d2d1e8351dd1ae72d90844b137116a628f455a3962e6379ac5665e289f90623c84624b7c333abb52d4b4c6003929ecf772e7386c844b3439afce210fb06681d692a51a1ddc0be672d0d74f915bed9d7ff49b0d8aa5701bfac0c6488ebcbfc58c43869419d5da9072be053b8e639b35a0c774ade9599edd8cee40c569b5e8d6b0e647fe91218342405e44e49cd2007a920a530817ced03238abea14015c829a7e3d60c5ba5feef0544605f545105da58e3b189cbd0d982c2ac952039a2b1cd8fdcf3a88fc2853f972e226032f13fa633565de14364760c3f183c1ae6df3377992cbee4dd5bf96656f5feddd117f3fca9e5218dad5877bfd6c9494d20303cda1c00dea4bf255d1d91317cbf31dc59ef0597af9e21bad972a15a7c1ebab9d51264b82c5c255f4bfe0727955d651cca4936948b68a0db1a6c4d5a84c2f76a789a2c8196ceda21a8bb072fc642d23aceff02dbc2af2a38d756708d435b750415156c8d5eb7e52e74c6d1f530f71902731bd0c3f5029a3b8d54136307a76255f335f457dd636e83bbc2242ca551cf21306f706c97bbaa5884e3a39bf49b0a2eceb55c1ee17eba9a52ca172d3bc7add165b43916de49124597c651b911af19fde792027ae6774f82151aa721803f452e7aba5be1b5ab07459d2f8cdb16b281b2466935786b6a1ffdf7aa5723fe7d4be6982917ed414e48e656248571f9cf19825e58f3e1f3f29c2c81c1a72cc0707580762364958fa1e713963c1d317824bccee615d54686654e029625b2b123bb36d88607675a622a4f04ab167f6f42cc58a5e21a886df7141517db07972a63f6024616f3143b1c3d33960b076f84c852c52e8a31542cf059154f34961723b1cae67f003475364581489ec5ab58b5c9f494b856c110a8fa4185fda8cfa72f09026e0e8d0c2859a004a7663c1c7160d09b70dd1e3f0e5eb59ed527c7d5072257831533a6f6750610bbbbec10b44943fa218df38259c9c278e9588e9369e72508bda95c98857499f2fd60428fa58d90f78d76e459dd0d1b8061f2079f1d41babe0c13d4fa0428efc61c179aa185c488cabc469329fd89c28eadd971b8ac71b7dc6f3ec32acea90c698c7e4aa61d152d90015eab3d6baf51e2b89d65d2bed72dccfb25881e44127e39241c6a9a92a6e1adb39115e9b09364d2ffca808f2ff72f2c7c277e6cc007f803d058b4e5bffb6987c23adcf4a54b3d110221e0bbc5b724053d0b0c569713350fab40a0722844e49ec97b079fa2244c1093c9b3e89357230e60563f737ec781f4b6aeea007b74283d01cd1bafb9eb3371c4849d3d0b61d17f570092392676dcaa0a427d0bf678141da9f6f6ef0d80d66ed1e669cc1194a2f1f8d91c936e43e3366a083983651e9eeb8092c3c4c12fae80ec451e7dd5e72f4ae414e4219b6dc761e4b3a85769eac9c9674e0f0350472a6bf3c8c916a5272a5d2511553d1924217275f944513351f541be5ce3f5416120d3817f5410853721619b13d61d594b57914ff44651337b3c580f3fdf67fcc3ffb51a41a76d31272829d7ecbbaeb2589ea8cc299e602f301b6e067f64464b9c91461aa2d8184c76a50be908ab751a8bfdcf784b19039c809b58cb64d2197e75975695c72e84b517244047bb68253493f68f3cb8dc68771cbe9e0d7501205d309303fdbff43fd1a72aa15bb9f4636fb5894798a96aa935b893b8ffcc59d6db979671f31cb720c93367ef6e29208a9ea368d816c3d828e0448adcf398ee2482038df137dcd9e3d93661e0480871b5b31ea06577b9509c0c3ddbcf862d94b9231470d70cea9616e4e72ba6ea4565c1e8342be340e910fa2f8f67022ad6c691dff0e1537225b55f7c67289451895e667038debc042e6b653ec638b81484536740ad0f96dbd4fc73d05001a771bf0463677fae56eb6e6ccc7dcee44c98bc13d1684069df4228f08642e5354a5f0e7e79b5313d2232bec9957e8439e8dd5fc51c98ba9c9c1c04aea55c072b9a7cb8b9bbc327c4c5acbe0dcdf25425bd2494b0faa3b0d82b06ceb200736419bc79e94e91a3f239e5b27200d0068464e461a59f5947df52ccd29a1fa1f38721acd9e55d1eea07e25fb55b47b69b87af5718b02800e399796192560efb24045cfe58d8b47ad11942483047f04aa0496a81e44fa4b8c0199794a2d03b711237250355e14e827b3786c08cdb52633a80c61693ade1e7a852986e8b33c2107e32a77601a1c6409e10ef781e85499122a5a158356855262553743570ed91c5e02581bbfb814523e762452d6f83383f14e99fb1283432f178d8c4df2e750fc59264fb75112d6ee1f59a25d4fa5da719db32e5a9ddeb3f70576bb4632d05218acd772d74d10490ae03455e66fe2dac953e08a9e925b78abb96d16edd5e46a430a2172964d67aa538448a2cf34845e5194c82321896508a38051a31c5d75b99cd0b10606417584ad594ef09fdafa38d3df7013cf8816a2d318e080d0caa50a4d20347220d9c4b0e04da5459f659be8cfffb83abd4ef4845b093be54ff326a368997e1842368e69f425e48df804e13c597a4fa06434659e3b92cd2b8c4bf754720eea5460f2e10e066c8ed9fd001f508ce86037c4e5b06f96b52876d64d82f7413b2772a9c2982c28847f257984c6b9e21534e7693aa8140d32361f5484220d28a7205bcd089692a9b1ddb9c40299443314e952bf3e2fe89b563a0c62c8ac57d81ca272463815e891cbb187db22a026905ef0d04c8757687aab861d2ae3bbf113eae9722df1461981c6260669c8b48e034c55bdc08c5cf7609e97718defe88417bd6c6c71163fba397518107c211d0278963e61a02c450b60e72b870ec83b13e1d9af32b88d7e6963b568028c52db945dfb6a8ce6b2f4940c232a35a517c6e81260d36c771bebe8441dc7357365ebe30bc57e866b75b1afec3bef7570f8e8bd1aab116909e84d8bc3b5097a0a6e03967edbd445e172d7442038a01b1327c8c8bdd6047249fd7ec9398dfaeba41744efc970ba2f5ff1d79a0e5bef86819fbbb73ef7072d51a6159b72758634a9c45edd3413712d6c47861551dd2333e676a4e37442c447ac619aa0bbc12cac800407e621e34c484dad895534e64d074c53b8d186dece725991f1844283509b3adc57981d50a3efe9a8d51476a4edffeb28138c5c4e3c10318e39adb089abb12d30597cdc58243e6d7c4ef9761a1c19c359631a0fdf547273ec3266eac3520e5e8b2642276825fa3d27f55861301fb03d00a12e9d95e872121bdd85b68d4c39ebaf50dd2dd777c0241397f4eff5ef199133c56e888b20518d210a7fd7d160fd21721f341836cf0bf3f9f2373db755ef69cad0922b9c1772c0040f7578e27227af146aa8769ad11a0cc9a8608483a6608b16050d2a7da172048a67238756e10f88829db3a789053f7ea15283814c5d2bf33d8b8a81cacc72d5c731a603dedc9ee660d1b0b3bafe74e8d00e503a92d8d648a7b8d3582a7a72bba266293b5207fc55799afcbb8a0df10bd90f6395d130844058c54732aee4725f646aa2d8c64de2238764e269c0b5772df0d50dde5010b8178ee0eeabe66f72ab46f7286c32339bbb55b80ebba20dcaa2152b296e19af49acd37cbc4f9b8c72e8e4bf6d3735cd270d05cb0d06d902c1ed7825462a85d2fe5b4ec7d056acde728f2d8f51d117e4beaa471a9b5d0a499a7c05f4f39a9d453dd69a9f263cb94f722c36d333d8d81b786855045cadc3ffbcd81252ffa718119de3d204ac6b22a267873f967721dd5a684524dbdc704196bed3d9b3f55119b3e639c2a997e963ab72cf2d9299d12729fd510af5390010c527343e10ac03d16dd2c5498093a271f846c8894dcd8675e59761210de02206b955bb363a9412c6a7121a0aa0fe18fcf77252808608a314f0eb15f72f9c932846f4284cd83ea89258a220a168cdcbdaca0e3a4941a8e3f8de50c5c5219b8b6722e67d6e368ba1417ad9de13924ac9ae457299d34b56f2b442f76082d3cfa2bb01d6723fba5317ca015e6776c78538c74a72559e51ddda2915bc26fa87090262912fe4373001f9157939da6b567102af1272a47090fbbd3f6aaa928612d5c48d912d78bb17393adbd7d981e5a38da901ea1f365d32d1638a1730ec689f10cf7e62027735b64ec0f7a7d2878e79a5d19607490f1c01c2747bf3bd295000081ca8fadd56714da4dcbbf3e15e10a9e677921072d42714c55fc989a0ae8c6f37a55cec936e5a258c90243e9887ca1222a50edd4fef051d8dcd339b6f8f573624da9dede42134d16c7dd1cc0f4cb3b86c26308172a13ecd82ed28895134161a6036a4120386a6a0e840edbca949034d9699f0ea72ed956c904f3f61ed637f6dd3587d23ea8c067513263ea9d1791211771c765572a3bf3f74146b081356453ff85e9b7faa3d55c51fd59dadb386a33493b205ec70ed17e9934699998911324672f15d57b97b5b2b117d5f79a46a954b7ab3477a271ffdfda723a9350d3975743f9bb3acf6252b7538a18744f4deb867efd30a494a33cc3eb4d268bcda18df1bb622c394efe3a6dfd57df0c9204e0296542e86c2724a5c33ee94bde968c3756fb203a0a9abb3257b7de43c56517b0a73d36eafef5aaba1ee1ff334cfe079330f1b5cb94f2b2d488d3f17916e89dd1949edb6632072f7c78cec1d2171d916b75fb43bb4976a815a79b45fe4108abcfde0942f0fd5723b4a087476061f877cb63f3a50200de07ff625a9eea73fef353ea1d66ee6ca4cd6a13d4ed1b5015d3965bf45626c7edd0f4d5c2a52fc8f507bd3c63470611304c3db70aec891bb0a66ae14aeb4fbc363014a5591f5466db0e6629d28704aae72cbd32837acf015ac43f4b115318cebd19dae2f839e788eb10fac557d3fe37f7206e1c4529629d93a3f9fbc7caf10908c30cddb3e74893bc19f5bad967000e3724c34dcd0ed7dde2724010447f46bbbe280471e7ba95801caac303d558efe6e4dbdb8ba88794a9f3039c50732ada015bc83694f20a53c2d7972ccc3fc5392d072faaa418543dcf9eddca5eea6199154b4c80212b214b0802332c1bac00caf5360f09c53673339d35059e8f2a3d2751f015cdd71d77e214bd75ce818ad89d2782ff1ae2ff00512a3e62abdb5160baecccece407aeba8db90e892a7e7b53818fa72afc48bfd9fe9abc0af32b8c80c345e57dcae9031292323113873209e9c6f7b7295df369e499de978e171e23424c0e216f741c9c3a2daf21e4363483a7f4977724a4db186ff22666ed417a18e9e9760645f3450c8fffa3cd8f6a206f1126c4b72ed090bf7833ab8b06b60c163d8795bde1332e16eb666cbdc62b1e7a8e7aff4039744fd260c1ff3b6fac3b07dae2614f68c38c4095985d69e55b605f3dfd2e17273e61aca2f176f1924d8012d5a868d277d67e362ca3bba418f39ca34003c53729b10979ec8d11a30bbb3e100a0d2dad8ca93c8198ef19121ef0beb5bbccb4b7004697019df644ee502100b92e688238ddfa2686b78cec0f2a47baba64bd6cd720f5d0d8271f0731ce6bd060c21e96875ae7bb6feb844f94bab69ccb21595117298ddf7381b05ced7cfb76b2df22b6aa113a3d63fbad64734caf3ff0992bb2672642b479a09c68f5bb16df83d549378aa5377c86ce9a34da6e4f344dc9defda72bdd42bf2b7476dc8a86fbaa11767f2ffcbd487e90b5cdeb462b62412dc130c3ad15a25096174ae9d344221ed1c339b70728fe94b27b409db72ac7dd2861de67001b219ae60901d6a9943d7f0a8226a93f3a84939d876148a8ffb61d54836842e9a4a13f1153f208ecefc568c5b4ebaed2f3ca8bf89a2f980b5f8514617d2fd68b77632c7bf8d5c2ce80e8b62b063e73556c879a89884df267f7c2fc33eecfb72e598ef9c23121ef64e3714cba7d10f280188aecd37284a5d36c119e7d296c872bd2d46240a0dba8e5deae1b7347ff2786aa6c6dc1cdac73c1b193aed159ac116bc4cccc52e24808f44763dc1a68c518d48732e76ff0e6a6ac9f31fbeceaf7a724048da8c464321b59e45bc3eee0d3bd8349278058aa095e6735d40325c4fb872e51b9f456a0aa23e58f121ca88cde8aaf0769004a5bbe1b560b069d6386874727a9246ff72de10e4ece3cdb06bad7f6b34985062a4c5bd8b1b4cd303f3ad4872e6f55b8c75f9217ecc6064d057ee83ee902765b4747eec0802b2555c3ac4c072f7777b3c22277f8d07005a055f5bec57161849101bd190e86b2b265042458c2155e2765ecc1bb03072a48fefef69f0e42c8a92e61f959baa89727d8076a1257236effac5008cc77b3fe6f00c3ec2348053613ad4a21b37fe62c7db0f8cabc961d1ee4a4b456a80fe645ffb39a3d532008f85d7f6a41848f37c0cd9dd5a24bd16d1f27eb5290163969b4459a511ab18abbc809c699e3994e32473ac24e42ffa6b008bca0c6afbcc602637998d550c86935a6574bac7b54abc42e0152376243723b9409912cccf2b1469280ca0c921f1c29bd5b72ac536e37a3398ae840d69d172fea17820ee68f50d2773300942258f4b825066f5ea13854ec43e981f70e8e52f20662332af22807a02a036cbd02d1ffa6942b41e4d1f4fa17122429b1f8b7772d7ae857eb7f72c43d6c20e33cc018bbbfab7c2ad1e6a6bd32e721bed47ee080c38c46a4359adec049f83b2508f4e3c5309dda7588bcef632458378e9c0f086726d21aaffd745040329fd2857012305958a206c93f2d7ff2a2de1cc8f2d252b5531a6b05b5c78b6327772f1bc5cd875cec853267ac67652db7f106ae0764c1f729672aba756b5a596f5a5f447f9bbbcfa5199e8450722a5c8eb59e9502bbab9725780ce2352dbdf1c1e6613bbdabeaa91338d31eff27e2b11bda2a0e528782172fab5839047816be050c62318aec6c63c4720b3f7e23382f2beb052800c32467225676ef3f462b4f116c2aa5e37df8aaa4a713104cbe5ecc1cef3ae5b7bace772f5f2744e1b95ef0fd885dd25dca104315e2c588fa5821b81a69f9aedaf609972ec497e660c8a878b40b47d97abecad0b711de3c53da02f372ff54753cf84b672cdce09facd27da64f5f32fcc90ae2a03769ba278ca805e6cfbe0ba75ce6c3d5e3a6262fed50c3b031c266a94f7fd3606f46c4b1ee50a8c7f319b9452b6e43c0ab2fc3c63929e215c1219ac5bbc40d09c3f37d862c20a5ba8326a5936eeca17724ad22680d2ecadf020438373dc002b9717c9c96cc916464a4c07dcf3d762287254d6a17399ab4a7eb0ea73affa1e5352e9fa81886ea7e74299f3ec2602873272fba5a9866f0b5e827c18fed922faace476049b43805705972b79765f53d73e72422f45f5ef77819a8c4537832c1130b597fb72281af6b905de97a2b31753cf6f43835c1710d4575f6efc5b63c438d258954a5a59d9979d3216c393ee3857c272ccdc26109eabf7aa9aa43f60c4680ee44d556f7d93a227cdb5eefdc224455a72a86df9cef37728bf765f16a1b7f0c747aeed710f2cb660dfcd41a5afc80d1a6f97b0de752c325a65f71c2d3a55c7370dccc9b4d7ce306951d0699c605a64456c5224a23641717630d323824e7cfde4e5190c85b5e95750d86afc21f9d97ae872df154a08ecb461c4e673c22496504c86201ac6f072e968a54af2a2e37de8f02960ac54605f9279594b722de90c5f440417cc147b557abdd3944a284d831366722cf056bd9176f953dc3c9342f417e150d5e47c64edcb7198156b5fa64245593e84d38952947ee41bc0a487be4e523a26e0f535b1801405aeec7ff9f2c80b3a3caaf15170dbf7ac441fc7ba0915be2fa264178683304bf389bbfbc597a48d50725e4c58d3c49fc5916df4a6bcd808cc107f63bce4eaf10fda96d10cb00f3d9872682ae667e9d5d46af1896463fe308e567b9301940e7d625e0432f93b926897727eda9387e49c8d8e257598d5dfad0432ef00b0a53414e74a6ce3f05057bd8072feca1cd8e5479efb72429ba6cd43699541bdcae95ee2e7981735ac568a308b5f81b8f550351bbe601b97fbfb5ed6fb8ae339bcda805128c73a9a61578af0c472f076cef0acaa0c3604705e6b519b578f76879ca292a2c3805314ccb5432f457273242d7516fb9ae18849fd4e11c5fd241792d991c43e1a9b9e0a481e2ac8d40e25f539611506baeb7537c7b770212deb882702d8764f674476255f22519988719e163e826d02bcc84f1633a36d28c9b885ab0d1709b6621cce8a3421dcf0257278ab7f24ca2a138df30853b7f94c5aede450c588b282f6cf3b4910e650541e3fc9e48d25e3969a06b4f943fbd1c0ce00fe57998abdfb480744f99b5cb146b3419c3042b60b5f03f897d7d7ec0299d7988f331f085b9fbaa0a1f44e6935967c7277c91715a47c82dce07218e2d8279fd6a25321eff36e44cfdb5addffa1c7ab6737e7056e439961928f8f7677450c52a3ef95c624ef311ffeb09c447fd9afdd10fb17e7c05fd68ced2bda15a47c3db7029b530d62e7a09673c5ff29789127f76c8c635de253a4a09a4371df474d9b7f24f3db108800d699daca9a0b0e1f54417275877900b2f360551d3a75917740a4379b2de214bd849c0361c4b2a55b39d472bbac5636b6e29579dea0fab29262e8f2624c3b1528a41f9c115e8b5afe241e4cfc36b742679006473fd89bd03b37af0136cc296ca7f2f114729513e0c6770b06d9f789e0857f16aa245bbd12435afc3d6816450ba68ed16237a2ff22e886e0429a288a61a4cfe12ee8e768ab2e4dcdb1bcb4801fa8845e0b06ddc9548bc63b72a0eb424db27196f329876b24b2aa5625d0205b6eadabd835cfe2514137ec6803344fa57f0a83525471a822ba5ece25f27a8eeedf652e4ca4b7381e570d50e272a77fb2cb933b64c1483306dc87c1b932c6b89478d2156cc6f083d45ead0d1725377c9a04e8012e19cc6ddb31370facec592de0a3f9ea230fb3b26ab9ac6ad57244e19ae8ec2e8d3cd489700d5a2f0e519df8a99e638122b9acb7537b9a16b565f1a42958ffd4cc413cfa7dcc0f43f00f0a24070ddc5ac1bd5870077819227e5bbf33026defd43601695a7089bf2d470e067d199bca5adff1612ff48b79e80572354b92647ae826cc7b3913dc6b01adfe55e68e7ae558017bf73d8a613b9762724075b14670befa5c5e7833e75e689e99553a9891c79c1aca32753b9c8cd23c2688b0afdb8f8044fde29a18471f075c4936905a58a886ad9e78e362faffd9651c50a9225e2f4fb826e4108962d003311c444d2368fbbd3b8bc6d3eee832850a72a26d96487961f46942ad6aabc554b2d10a31f08f01a97502da7ef7cfeb628b11155077094d4725fc9fde7e8ddb989d74c5ea3602daf71045c4dd066973f999723b814cacd704309e3e5b88e0ca448e97d2c8637e80b1226d7e50c68a0528ec72e161e55edd025b2227a544e6d568741f8ae0fce2652ca4207e0e533f3a760642813ea66804a6cff484ee364ca2d345eb315d519a51b92f5acf452e54a0f49972734a6d1db844473591635746503a6778537641cc2082ed82cb5516ff62de432a330e276a364cafd531d0847ef5cb99366261b034d5df422fd4e939e05194ae72735af1d95e116d9c13fd18c543dca27284f2f1eb2361139897b48a71ea770172206ef3580f7b3d16b87fe773fdb4b8a83a4f418a1196e53d36afc8ec7f55ad72494dc5458a0fd57353c7d06ae2b9e0c987d2e0cb15f336bd9683c031fca33b7206cbaf709c30618d4d58f39d6a4de4481ad0f49d046b15aa35638418d0e7e305407977cca8220c862530d4630dbee9d2a0e5ea12066f43b427a27ffb06212a721f8b5b68db9be5b59ed8d9b918770898bd8d32c051d9cc3cd2a4d2777f30833e5a6b2c7e67a133629d9020cfdd0b0b6345f81cff9cc781291c1069e017787c7279fe2d6438e0e8a4a1646680b9cb8625e83adb5f31510e41fe54287615320a1b6c5cf5420c0d639dff131ba678b23e2a49418e63788305874bbb4280ecdbc1729136c3f043f3c037907b8e789e90c9eafade0ebf3213769b65d0b8b30a549e72059744895dfc7ab6b48df344673ee53d1a61c2efddae53cb5a1db8e58f6779724e3ae877c89c188bfc71f7cdc66ce2224e77387abdd98ea54285cde3979a4a725737d99688fe775c4a0cedee5a106df42ccb37f201acbf343fa870d862730e724a88d99de0871412385778e2d9eb7851923a4116d4a0e5ecdda25c35dcb919297ee7a83473ce349d343d4d1d8c7b38022fbe6f2d30e79f16045c7f7dbee2b572a4fc4a60f2b993bc4a84553ee879ea154e7e1970c9f6388d478e0dc1ee1c98416080eecb85d13333ee18e75fde1821b1e2f36007ca1f454d108b8c1953efb072379cdba297bd70943068954d0372833608361c5f4cdf9516744a4269a7b428726fb971c6d6a2d17883914ebfaddd8ad7ec619580856d390efe31df1a5d8b3072efaf7f40120aa8e698805b4023317cf11dbe249fdfd6b2359a103064f22107720f66b4af2121bc9a233787f6a4583d1f919c35b96d62a6bdedd7faf01133cf726568608c54892048b8602647a949c88ae137058aa7dbacd76afb498506969172906caf95f339e397eb24ee22f36667d315b68af22cf4566aa2b3e337b7262d72c0b2c405c213a7776c25f2d02d074cec49c30614f5f398aa14e647613ea39472d4fa2f8ce6fdacb88019dc0e0d80446ae8d621ce7ddbea495d10f9d5aa6ec6728d9ed40c043447ce3698402b5158440c1a418ac415b581b75a7da407d308792c3a22719c684f4b5025a85c14f24773e4cfcbf084423654797ead33d489dd0372b8b89421365912cf863309649472d7c8ca10d3b181fb94dd8d1d0a55ae567b7236a1fb21a9f1c1600112461142158d965bdf7988a86c34fb0a561d55a6e9db72030cd1aca1496b3ec54fa33fb3aeb4025b6c411d94f94ddafa0f8eda105d3f72a4776a4f7ffe0e76f9b56f0fa005d83dda734aa2d30981aac2295cca4ef06672a4fc1d065c5dc6a0f2748e192af296bb76a220a3757914c1b03b41a746ca7172f38b351b7f0f81db59f3a39a04716c4e4bea9c7c385e2bc998ed94c0bf63980c6ce0bdf0527e270af8dacd274f1b03fd51d6865e0d03fd9ccdbf0de0752c6272d40ece9cc2d131cfbcc85ae4f9a5c3bad44454ec5caf42c0fcded5a2e6815c059379ba432a059300bf8e82607b1d9c38766e6a39fccb3467ac7bdd5974121b2319aa20617ff55f38342ab4c93b2fa10f4ecd2f85a780edd5ab8594fca24b2e72dc0ef254c9fc75a972e0d7978813083c2c88e0ec44183bc1512211a824c2a6723996f29872bd7832b70fd582d4f20392891b35e4d3b4816bb6c2f045db2d0c1f1fdb58073989a56fc07a28a7e616d8c1c316cf49ba7f6d6f6de6aa9cd8d2101e8b2a72b8405895fb87a94faef2d32e5f1e1d0507f9ae680653bd035dda8a042a53c4232176ddb09a1cb916f807d0e512a6aac2164d21dcce1ebba7dcd5445172886e525789a03b03bb718dbab36fc7fb939fdec86ece127f49f93700256bc072e37d53a8fd953418f1690be63a12ce21fbfe0fb610485cd0c42273c75f7a83468860219033e0f6c9f89ff6431df40d92b9fd30cc89cd1df7df2d764099518572f3e4bee2a5a734969028a97e497163799e0261fa123185ce81f74548cb3ed35b58e705b28ea2b6c4757b617ebab5f74f6e4fc3577d48f2ca6eb2f34cfbee10721a661b856d05bd9c551caee65be635085050c2307a9cf47e2195af82304be1726888995e9fb3061935df7de43f122e68f1e7bbf137da123dee2006fc8b884d72173ef2b39849d880c605346f8d966797743607884a0aa6d81a317c8633182a72efdd2affa53d69a2c90754b046c38e917349b6e594f39f3dac32eb767d571f7274aa0ea56ee81e08e9b555f3696626821f1ccd806a224de98a4d7ef29525b872a3b64d671de5a09451866b9ac71d998c043140bb63eaf002cc47df7594fd90721b65b570704818b90c871e3eb60bcb41c5b328feb7a175c938226efb59ef4b722b104d5f301702df56c2da2346d3802dab8b8e020976ea3d2e65b89a11c70b722346f284adde851ca6d5420f349e4f1a4ae8d61aa3bfd122c4b86d15b4d382583f1a2fc81ace029503ae9f816e7bb7a523bcae636d96a972f3a4fb783c73a0694c18edc9cad37229f58fb7dc8ffe613ab44850028b7d3f7ec5c47b148da4692b62e22f5467f81e5d4de80b64d7c98c8abca8c6ab2b46241504d59895014c52729ae0c172fcda3f778ddca2bb8123111627800e145430a6911d1bfb4e08a98172802bf3a3c11eabd57f0e485554d1d6b9d2ea8b851e2d96c8cead185a1ecc495cb6bca693af834e1714c6a65c4ae46dfce3ebe94e598be59edae1bae70e1e1d7228557e131bfd0b85e6755218483a6610906de06ef83fa75456697388edf0876182526fa0ade51fa0ad25ac22b30095c04e1727c78db062eb45e8819761e8ad7228494974e4eb38355556856cbadaff14c7f919e0245053495037c07ee864cb72568b2e5e922c8228dc98ce8c1f7532fcb448cdf94303b252885b855d79474357800bccc1b0abb1a03480d0a850c6416886e5320970e8b57e218c752677dcc5720f43096905a4a9a6668cd7c67ae3c809cfcf6d5cb57575e198cd46d6b4e41072fbb347648768c2a458a560d0820d33d4a6c05ffcb750ad3f9fcfd7844829e97220fc1c68654d529e924ba3ae93b8cf22b3e614ac790d769212902dcd9b40120f06bc8e7955f8990665e48cf964b1994cc717550f0975d395204d968382c13472d118ef3e75cd2d2884474f469e7cb3a1a916b979ce7c7588db1147a6535a6f02074c2f3ae8479ebbbc701d1c39bd5cd7ee71ef38c45528dfe2796454ca82ba72222d8ac6d0634c39582a418fa6770e0cb956d2d165f97c6a550b34f7b3ac1572d3f8e14f90411c91cb469ef2667e4bd6a9fa371763e416b3ff70bf43ae83c004ab23ba565f98e7e6a8f8ecb11a82807a8faa3ba987b2071ef41b20fec664ce72b0567c2d10ad247426d2ee6370f5c616a18cc3a46e92fc58256a7cc5527d9025b88c72a5fd094461f78d4ec963ed0b780b9e4ae80caead0bf063ecd77a85f558c3781681e3cdcaa07b69bcca04d7609589a87fb6b8553541ab8513cce0757223039cd9cf1aac704668b5237a617dc54925bf450b245e12f03b68c664283e092cd4d23d7d82491527aef4604765525980ef6f658c1247518aeb8986a22580154a4206bf73bb46c4088e7d7e879e32850a23077fbe9082624d7b7fd5c5027f8f72d04158621a6f74ef59e378ab94b8f0e3e91a30ace4f3afc3e65f9944d400d772fce276df3866aedf28fc578e2eac9988c9efc643a0803efe115c33793616424b435ab49d492c0abcd87808a4a69675aabc426374beec871b2dba6724bebb8b72c39455e5cfe222a28c40ed1bfe27dc539c063183d0eefa45bcc5ea49b95f4c0b2f85da77cb6b58d4aed88e9d66c198a13bdf5dc5d98b5566e87c0003822bdc72a9a7ba0ef9e85ce182c466dc4280d7005957de0ac47eb85117c143480bf605726727656530dd60d4a26907fceb606b7c97af3178a9471f2ab866ddce13ecf272923cb34669fa362c1e2d11fbca6fe438d040e7db2f81ab26cb02f69ca23e97728fa414166b11b8c379657f1bd504801f464b82518b63e709c156463166b84348055719e2581d69a3c264aeea58d3db5d7051e59d99da772e6ab5c416477e3b72ff28cb03293df8a821f3c34b1a419ec440425f35d80fc140760389f4dd3bce0a1a2d150dbdcc2440fc12b327efb490c727b5fbc8024c0c4f9c63c555308cd972dcee213e968240f5952a8ef02c55e6e6c9e3923746afd5509be66f32f7f21c72174d98413f38ec69171b217147dee2a6bf8ee54b5c98b3aa00b119ece65d64361a6e168612f935cdc2bc4b78f7c9c3d222b1921c06ad6b411120f15d3d6ed56fcbe12b8cfd0b50fbb6cb60de66c800b70cbc3d8c89f52f8570519ce605a6364ac974449d8b60bcbff0d8b4565a29a39f2cf3e79ec50389f3db155c0028813c08b467107411d8fa45750f07e98f8aabef4e76b638752ec8a01d53e06d83829272278eb8f19b42cc35d9b91746146296066eaaa8342b3463e555d12e83bde0b772546ddfb1f89b4e9dd9c0a2f9ae7c4c9d6c3788773c9a7795a87250673842ff72999e7b0f5f480caef114675398ebb82e26422b8e1658ec01630a5705caad784e12a4a4f087100b622f39285475351924fe2ef4f0131ca73a3733d13279c36a021674841473d8fd67c82636b5bb511970febebf30eaaeab194fc16c3a542485378277a67f63dc79614a0769d6b09a4fb972c6a1ee6fd031c41be12ea7f1a01d2ab1b018fd0fd66fd6f08627505ba545cb51d432da58bc4f8fbdd91a93273a34728ea4e3b23d0ec0e17cb8cffadbe3328ee2c504d50898c2a1d05095b82f414b7254087f262591c6921d83d758a012424470949a1f5770b5ac6bd22238eb44c42a4f252b75d5620821c53783311c8e5b1e8e2771f6ded1af7f26ef7962429f4a72397eb318085f9951333c7147d45bb7debb5194f5f2ec22c912a7124bfc0b4755c7a0c3b2c0a691520d71a5c5dfde69dcac94ebb8b992ecc4a2e59a6d8d8629727551c8eca2ecae3eb2f71f8212a54e9cfbe4b6eff09ad13523d8e047fc0937109ee3b83ba22b586ea9bb218e832711cf83c6c1fca17287ac8b740978a8c1c4728761b2918062784457368664f96e7b5471d61c154b9d2c11f2e57607404d837291ad3a8c50c9b3f02e416e9167ad014e2ef8466310e4baa9e81f9c17ab5aa07225f66237cc7e790081461e1e6205610915d8d36f9f7a91afff4de05e500efd726363e899021da5c7fd1563ca989c73b472affcc8ce40677e3f19f046e476a372e9453148e8ceae96a60ca02211b12565dafbb8f530f524a2ee4a669934ed50141302669dfabd72437b9692ec4fd846c7fec5bdb3befca8dd7adec346f33c487273828e0ae20869c2e6c22bd3430eb472c16780aea649c512fa83bc4488ab232673ab6f28135c56c98be72f97f2dd787c9e07d9f2d8fc91329836f8da4d4fcf3c9618ead632f0d8f6b9a5dc97b350e77a7457b2bfa864635a58650f44ee2173720df8ec7a3d040e0c00700f333c72fcf639d8230330c1c4ba46765e4ce8700472dd2a7bbdc7b4b7ae7c6e179b2f03745e57a9a20e0252e32b13943df95fe8f572c985bcfc6a4ec999050df3961f978e445869e64a544b04b006d33514a8d7207262ba923f0339e0f1810c267145741471af89447db75a1bd1f72c073ee1985836246d6136f87d2c20d906ff8d9f88b90b286d81a8e291e30567a715ac277be472a4a72416da0565e43c1296d7a1aecd4f4c61fd2d69a02669788f93ab885fbf61522744212ec1d69b4494f1071a1cdb9f5a757c0779d61cb47ed099d4cc8e0938766a44bb4aee389b33f7afc8be329028ca5a101acc9d387aed8d908a3f957372b9092f3a6af296915af5f53f63ff7e3fbcb38aba6a9af3b90f89d8ae6ad65d72c828eb4b58440d8fb83c172f1a5ca8519bd4f4f11615f9a52bdfcc58d504b572f733a59d52bf5f2a52cf4d886abc34773f9d45a30c9120d64ccf3ea96b13b57277fbea6806e113844a99daf995132b2f1f7890301a94323ec144897383dd8755dc5552f28944f42e72912466e2b316f477596672954d6593f950c870177efa29b7989cea216aea059bdd3b32a13f25ec5ef60c58e8200eeb34930d4f13b1a072e3db65b803cc7f86b3251211e899892fd98f3303da6e6e7db9f3b9bed5f6e1266d27e58b4a4642c5fcd41c54ee04b2e8bcd95c692e3010bbf1efae810d6ec072b10a13df48b1196e744e7073485d415c37067c9bee0f3c11cfa81af48a3c5d72fbcab9b1e5f822bca2ea8d61999e686e80abcb30055afb7ed8610c44dd4d0c7229542cb8745e4d0c56db257d4d62d3f025f806af7655c095647bcb05330b0e72dcebf3a546b6fc95e59e79eb28088f0227c897ef244dd6d651abb0043d032572877820a683355510d5f695b3833a2a97a467395d5f6b07e3d338fd6069c0f27218e6b12130ba0cb844a5225d5c539e03aaf100cc56547895be9ace811dcbe259e5b80aa13fdf82982f5246b0f7a4213c80ed77e3a171b82f39144d820fb1dd725b1afda82176333ba0ff100de4bfde6d657585a06956bc82c3e8d2522f73c872f970c29647d12e606b30670e6a27826716caa0c4a5e4a0813294b60e1d2c6b7253744b2c78c4e609ec485677c17fbf3a55687f31e5fbe4e7bec9bb2a784f7f51545064d5626813057a9b67fa320603618829191cb0985116bc0fd2d1f9ce3a72d4a338f1a86cda37f66215f4111c3d7ff7957917ec3e7279df1b41a3411955547cc63c81b4975b18c7122eb6c7bc1f9c92bd52ae0ead5e15a2a7378b431033726ea2b1529c0fd4e2678a6dd7d921594f50f265f9c56b67f55890a495ccbba12985db5b996ce62784cbfb0609f5dc91fc03880956abb620639b01256916e4db5919f6355723a91e745f13dd9964620271b3020ae9243aca0e2e5c111cc2468f0aca0f26c0073acde497c4655a79602eea6b4afefbdee483c859bf8709b28b8e72cbf43c661b49a0d9029907ddd0cc19a2cfb25b90057db50e5349aa16f7541672683bf45218a205271b8488915c638c63537b8d892417336e707eb83bb213ef7288563b2fc53347aefdbc063d6df5b8a7fe64d4a05dae72001968a657c8862b1c3f68a5f15e52bd61b9a3ad93d32fb062196d5c0b3524f06cb831c48ecbc26831a107a7a2695dc5dec0dc16557fc930817a98e8c00a4c927d91c0f4a414a9584e95399756f1d590e56c161480ef5d4ff5679d5d09d6d7a08793f509f82e8fbd63f420a50432414e2962bb4708723a05471f928e129eae13cb81f05c836ac513723665ff5959e44136f7d0472913a183db85cbaabbc5f24b133f29e84bd7f5996b070d96ebff51a225885a2aaa0ab3292f0a3fc0b32b116b53ce704a970f268236a608e03a35833870e79bb968dfde844c0e55019a1829c7d6076519715c1ac21b66223943ae2ae73d7faa2fbc9cb1aacd139051ea3c31c385fc0b3da90d077c0681c46f4b64ab0ba901f9c855406e2c8a9303ccc5008c82a5a123d489e61ba7625fc4dbb336f0e7f5a4b566f956e72d715ff78fe07ab6604426a46bd83de1b3725d8be9f914fb235a3b1f0057c309cd116899445fd2bdc5ec5b5861d9fa86a60310a211a5b5baa3a66ce3d57a03a4cef598948b9c3ab532fc22f367338b638672399dd39078ec7df6901610b10420762264bb185af3f20f149545ed21d7950972243f36ec7de0dc2b1a7f2fcccd40d542f6e2f473485aa2db4c2cdd9d28d5e472ee1d5db81790c8d4bf7b0125f9dae5da3c7ce012fdadb810d7c373e776990f38ed4e4f317da0047e91cb63008d77c62421147a25b0e96f829e784cc72fcaa072d943b6f2a7c40c068419d8e18bb7dea7e5e2088c5bd5a7ba1dee5ae1301e1f729d949ac40f84c417b98ed04714da970edb5e42e3f72da0a091f6f9dd0048b8722ed8a71fe9d25b4eb9fdb698ab380c087873db73d164e0351ebcd4cffd319e727c419b57a3e18f3d9365d82d3edbcb3708b334f969fb17885d95cb80ae918772782d5560530980c9e5889b72e1e38e824d5ec095f708af268cd7d20dc28c26725c8c6caf23d0163f32acb30cbebd1007551b5268c23c8894190c3d9e49d5e972b1bffabbd1d9101691fcccfd595797436ef06e0ddc083210042226389feb0472b5615b1e9aac9577c6c70aeab0326a6db433a3b5de5a5d0b87c216b3a0e132725402dda0477ae41293699633ee0a6a64aaaf1e3299b6866cee32fb9d3a8949724fcc5acaff137b3fec0e650c3119cf61b0043ed5207dc294c8197b47f3e76d3aa91148b2fd0c77238d60ea3b21cda31e124ec17f7f8ff492d6ab139b9b60be2a683f607184625d8d3efcd71133514a01015fb40b517ec0c0ddeda30e7ee7650fd7d9e863f954d560530ee389169c13d6a0274ca31efca4e8b686d7bc8d14531a38d2e94ceaa28080f92cc3231a72b18d4474ffa26fbaaed7c3cc198ba40f9317a0513a1fef05d4744dc7683b12da5d01bf38ceacb8fee5991094599b2407275aed44cc5649aed1788cf19ccae1dbea0998201876ce3bb958f0236f5820f1471f0bd237853aa39188994f08c0b5132d0c110dac6925f0d7a3fe85f2f6b98fef72aecf4b143f96643d7aeb657119ea22a5154e0a78c862ae2cbb8118895de470727eb4774f08080b2150c407c5594b2bccfe614f790e2e54935855cfe259730b3dc61a7ff3c439e88174dd4fdd9acef27f72f0224b343d9d4f490bd3e27a77a1723ef6628c4c52e6b076a7ef3f814cd514d016ee12f94446bd98a3d344c7a3e572692e76f9d6e29558671d82f202c7544fa3d354dd4454e3f8cffa8e14e0414072a83dbaa7cbccc14ca11169da86c726f2e1cf57ad300dfbd68bba98d25cac1404c008b3d94c8f26d8c22d56672745e3a5de45b346535b92e10177cb03a1229c41bc36736ceb3d59a1e4e548664a45e942814cb8ed8adc0e42c5a2530f2113166627d7360fa33e5cc407ad594ee546bf574c48d098ed695adefc081a9ea95bc06a08c5c8dbba4fd9ad5fa9e08e7891dc748aad1bd81dadbe8d27a5118c5f434a72eee1b4f2466563acb37d922f654591404f41a4017908c14896c587256d350172081f8e8075a91d55da48e85e2e81af28f42ba571db274c5ae4f15d24b521e6147013f150ca39f89955c900824ad0aeed3bad7b461915bc8278e4f3049b16e872e4f49b1f0fd95aede6fedbe4b138c2010a760c3ccf8f67dfbd1c866b173e97725639b9107900ffe8fe0fead1a6b3dfd3d10eecab0604d8198bb94ecb750a7a727d869586b727b34d495d0ccbb0fdccd909dec5bf66aadfede80bcfb4f349657299b28d80f3931417764cc25d96f47a8dfdb84738838abe97191c60531938ab1aa922a01c0bfc813cc0ce2c96f3fe4ba6e17c5659d1678596facf706edf312372a012f98c433228212495839d686d2690970a597291b594fc59cf36baf8d6b272ca38b6195fed98c46d86f774308edaa68abadb080b5d88e32d01112ba96a4c2d5067d48b7be9725b4726c0d04b37cc72c5efeb1c3b44773d63a69c8243fd30728fc8434afcba7f596bae08e71e7d2bb09574dbff35837fcbf117110ad47f971c11ad9e9db2add3e93a2f20f6ad60000bd0b7d0fc78f394e370611b7768fb877240ea7472129001b3ff536fdbf0a5924dabe4113a71f3cc810f7a4d85377c174eee406d73b83aec1a58877c8dfa0563461b1db7b657b6f60152f66f8d5f185a7209d08371875b691f6e326dac8d5eca735ca222ddf1ac8288c87b10ac75aeac726942dcdaadb0ddeb69504261d12cffb4687d445a90b6610a79e7ea5e4d8d800e249023c8f62b3f04845d6623232de7302bb3c50e66fbb9db2b3374599dfcba625d0feb255d2c9426e4237888ca37a40a6570485e412adbeb6b05876513ee1e72bd9a011ba9bcbd9e3c11629eeced1f9a405e273a9135e978e16651f22c99d2175ead947535d82c7b4a4590ddfd13367b04eedb83a18b6e377342a62647896472aac5aec329d2f987b591a354b8f263cf8603205082c3183ea4edb162ca1a20729c3ae2367f015a18057acbf66bdf29419e50e41da3efe22b1e8f731318bf523ed8ec13314add23ef8527c404e68561ef54e97756abaab82073c4abd736074c72bd522f6dfb0a194c186b56bd3fb54f6e91fca342057c5820621e99ed3b929672f4edb10aaa4e5f5624caf97c3899393697b9af87b0e1e00f04a7bfc501059e3e2e837ccd98466cbbb7e63378f34fa48fae10d586faa75dcacd31734e84ca5a72b99368f6d8b6603ba692f65485038969a7b041acdd7a499c14ff28e3772e41305ea08fdbb4e7e134448b26d95e7c8683f7aaa73db151ea0dea67aa7f7c918b7295c3b2bf66f08050e039f7cb37b40badd68b945d0f47ea1a9641be09ff483c41c7874d395a8053e0fdd72c55ee080f0468a97bf472e4d1b563038fe691d140548d866d6c161b0f8448697e49f580a353e33d3adc8c66db026f1574293b3996718f3073c80d0fb76cc203bf7a1ac06c6d2e454dca69c11a6ebe8b88a609ab5534e4fe6712e20bcec27a1236e8117e3036007d26236b72e2559c151b90e56a0d679805328e889a9b98bb4c2a10dc6c174341f74bbef85061926ae5eb28f664531a56b2150cff569c09a8c9b4d3c9c4b790b364df5466050a64b6ed07fefdfb8d726cb0be4db921e3e0121abc914ec4ca15d2df3f21efd40d954ed623eba5021436708bcdeb36d91d6296f4ae63ccb52d96d9d06822ef2f5f15bcfa382cffb9e2729414bfa46c72f6f4a57e00b44abb0634349a72dc3802c09d626d04b26bf61272de8c127f3d8adb8d80305783dcdea52735a2354269504a133fc0f7485a40f829648f9fd8b46ef427f5b34194f6ec83cd2023935df0d012a5d1010f2e0383fb72a4198971a10d5aa593ef88990fe2f2f8cefdf850bb71e2d5a8398aa9ac2181488807769ff153f3fd3bf10b94d1f2203909ffba3b39b4219e115259814da68a1fe07e9b0ac2456b32aaa6042db9105bc10cc1c1053208b29494d2e1a75113447201bca0fcd7a4c732a7189b868b4700ace8413b4d1bece40b6dc4d276305bef7218c1fab9d38c14dc6268c4bf19be40a5cf1db3202df0abf7afbf896f61270772f9ea396de39ab08cbf94969d6c31aed22e02e6c12a9c08c57e9fc78251976a563c6f4459e6e305c2d52d943a9cacf2d4329bc9cc88a41d7de68541656336e172d2e8263a53ad62b69070984d8e0119b0380695676519dc14d7c5f7c2ba31a472dd42830873a126acf52d63c0453d0c12f25f274d47aa0c50cf3b47128643b122165944f4587bc85648f93420e0ef1b639a6de941e6f794e8d5cffb6596eae415dcda072c246ba688b244a2c72c708bfad60f5f7ead99d361b310974db285b93c9e6b2f5283550ce278067a9e1eef727c1e3384fae7688cf61b6ac13e23b39f228dcecdc3534ccfb6cc9131509d688bc3b16abc220444302535dfa4fdbb99004261a07ec41e98c08408233f79297eecc0aaafe1a05f235abff6967c33dfad2a7203f5640fd681e562661edb963e25102f09c8c1a9457c5496965a569dbde2da453feb563e664f5e354565f2e74d49d84b78116c5225c5cd54132ea647f6b83725666b398678ca5388069dfb2d5b431613406c7f184d936c5be70b6a29e7996d5c3e136170f82615a21cfd230e8775de3ccb3ba23e83dde58754bf6105d0993a499f8acb9098b6ec877ee1fe107a9885f2c2746418f4902b8e0eb966f69264c34631894f675ba3eaec374ccf2cf90e43098143aa6aa016ce8e2374bd64eeac6d4db7f88842c62e8b71b0d9174ea955bdc77b672a5c5f78ab560c214dfe41ce307268a9d1c16e44eb9f622d61b6c573cffb070a99ea93aa846d7d36537dd746c159d3dd7e8e47754f0d82a4435ad556452ee64d3246dfc4451ef5b005d4e7abcd7283174f47ca7d7acb4589adafbc208dd8f137bbdd495c346cad12adc8d2dc1604af190186c8b2f581a9cf8e1b4668297bec99bdcd2f2c9b7fe452e2d37a7b4e7234c8e5b4b01028be30589b80aa1db4dfab2d74c6135020633d0ddeed6dae17728cdf87063b0ef5386c419fdd72487ee20fa35d33046e9b0adf8f4806707c0b729302b21db9bc44af696e84ba407ba04feb9daf3c15f72040f496dcc6080b74721e703f54d5c97465549f7167debd33efeea4b43b483fc46d6ed686f50487ff72f4623ffd603b2e7bbdcaa5ec085815822b19d5433b9c4e56dd16cb764e9eb7468a0661155fc19720ffeaff9e94430dc2b0a1045f8482448a64edfa30b5c42572f4bc4b9b9b0434bdc1eb7afcc97b5f619f57c7201561f3b1f894e5bd1c01e5720296dd77d1701940e779ad7b80079cf2c31227d87a8e20f760176c100997ff72f5ff16579cea0031996876ac167bbf27d9935e16dbdd73c24b1d7499994c79729ac5bb5118fe05f8544bdaf716d8527b74dfe502c6395a68c1a811f3fef07561e0d0ca8b706c300eb937afced27246af82ee891cdcbc981f3ba56a6410208472c2cdc57e2b10348c5c14614884ec596eb2acfc61f9579f4c7822d7e23913e1722de950e9dd987113b9f7ec4b73d2d92c10b117d11824df509ff2365a586088726e97a5d57e3d70203f5238700d4d0a955f9164e90dad0df03b1594618f4e3a72796d17912c541cabee3a9d84587e9f7fb57027ff92cc2ee84aec0d58810b4a2898ecb15d2c2bb39cb13983ee2eb6271085a0de3f1cc19169a49e4afecc215853f2c3dd5a812190881f7034d0b92e2466d362824a4e9aecba345c38823f6eed4009be9c628bc3b3338dff0b4dc2073cb30725dd7d38c83a70a716b3db35f43a6c64793947e21dbaad9596d79134a41e19bae8f7729223156e947cb76312a80543acfc97e34301b13f782742a6e601466f49bf4300a2412d579c5bca178dfbef3d290eed6958cbef90e56e3958432e9082664899cf0bf940f358d0e27f715e71723d69a18593767961d0bcb3ceea0f9bd88d7c2f696d6db9be26bc51f1707a3572ad282e6f6abf938248ea0cb1f57a12a1f9e2de949ce2f4b2e4bdb8ab6ada2b723c83421ac382735e1833c1064b1fcc71ae885f096d1f5f3460745726344bb319ed587093b88ec17c1cda104cd398533623452af25749c96e41febc13007b1f18a3f31e73f040861e195c037abf354f2be503be5b80dc6722dec4dc5faa691210aea3bd070e06ff8b87eb38860072f34a380da5332085ea2ca39f1b31a735a006c9deef8c4475cb160cec613a3a3bac9cf56adadaa1b03496388f684e40734c63e80e29ebf94d085825332a17310785ac11ca06865835720cd81be6f3846e8b724b5a71df23f3db7d61ac83e0edc9379590138dc9cf4eb492bd5ea3d3a986fc53c715bc84a4dd703d5cdccb2c884d0d81ce12a72abfc39ff09daa121216be6f721fdf36428b585201031d93874b03f0fb6e33318daebe9c28e4eb10c5c0c2393646b6f9769f202b392e14901f35ee68a764571d60a7b654825f48887e26f2b24e0c3930b611ff005aed53f4439ced75ca6922640094928eb0f578dd6e888b6372e82150311339f26a3bda1ac0b5937bb9e8a1513220fb458e456aed109f993b104052c8d8022f516f7468cb9dfca5fa0cc88cd04abfa732fc4bfc09b367d1e07244f17b0c06e22e61b0d09f2fbf52324781f6196ccab9831cede0abafb6ee503cad8683f3c3b2104f6251ffecaa38604eb2ebcaa36e504e1aac04e76b0ea30872aae073e6201e4036e7ddcc375b107e2bfdf10b1e3fc2e1698d9444434e918d7093dc0a8546ecec26323fcd99c93110cbfa04682d2025028fc4aed898ad763c72159fe715f9eb8d1519890a7f16be0984dbcbf46384d7a98ff48543f1ee92444ad26d6c0a4a5a5910ef3d145a1220732aae00069b2ec9884a8c1d516b22f2ec53ecd34fe011e88051494eb3639d53aee332217f23e621a530ea6ce9005b56524cbeccda17232d47cccc8997754ff194bc11073489e5300f641177c3caf4ff34726c81440c5746813eb772688649f716ea2e8edc11acc7e25c6a97fdd0d3a42572b2db72d6f37f9a4326bb20e2685398ce9ab37a1a706263e918f460e6a54af772f875af84e89c851486c22833317112c0f45f2679311ce744dfb0edeeb018da30d4fd88626c28409f6bd9ea737a3bbc84181a7ab207965f7f522c7b739b210972c759b34d445ba9c5e038794df69c23d9b14b74d26c8b699100019455dbd5900cff3935cb238e89cd068c9382c3f8d542c04c3098a108b0f9536f37daeca3536746998db8fcd52f144d8a54bb5f54063ee2ca14204e40c32c0d8b574f4cc16b3192ba8b4ccf799d5221023efc09e5223e4bc7092245d45595b04767cb508082656344d99eef5b7b0dbae5299cd3cd440ee9f4bcb534d422ef5a52a7908059db08471ed8098c6a5c84510bb493719f4bdfec9cc5a417ae6be75f1b0a7798b876728ded48f40abe02d038af8594cab1ac3e8e6fbd5dea81dcea82dfe26ea80f27722f0a344d1683e643dca3805aa2171a10ebb27bca0bbae0bb450475f26ce9c272a77bc6efa1f2d8bbc2c917352acb273d367e9d79ca78b90e5a91fc037a0871721b460a0789676836ab5e2823ad7dc5e659fa9aa7e4a5c496157e437c47fa9d72c37e6ad4255e95c10f9e689a9721169c8a27556e0beeffabbd0b6b23dc19be0b73d371d65461371401934b038b5593c33a5ad927d65e65611418dec31d65f85ed0b96c0c10ad8f4e950a51dcc302cda6459932314597dad0bc73c591f0968172f1b2aeede2f100f8bfa7649cc3fae870dc49bb71e755062a0b75376b1cd27556e187216f83a65679fb95b5ea8547fdff7df87702bc666a2f4b1b3037c4b44f72c4b85d8e9e84c5affa0b9169ee6fe096e853a47c60a0c81c011398b0317a00725637ff843faee77b11ab8fe8f8a4930bb0ac27c29da894ef1fadc16c463a0c728627fc9d8e1f5afc3096701d693d2f2da9e1ef9eac7344ca39af5843d5601e720d68942a5463f9c7c2ab848689f6bf1d6e45dae7a422f0af232a0679cc731572ef91f68898afb526aa3d11e4f82d90b864cef3d2141d0b9a591820999332ee7273b167615dd984f630868d6c942a22de2f7c0e6419c0b53fa4c2169ffe6cd372bd38a20c6976ac000e67280650dea119d32e0a36a375615d68303fae32339b726673c55107d13285582d91580c6e759d3819d24cac37bade604f647ca742d60cd2dea70812b101eab44bd3d6c8019dd7257286190e47b9d9b42cc667f3de445b1a47cf018a1dc6f8255edb5be52158d1e472d627a9348eac93f78be6e5d4d26d8e9250a8751bccbe9fdc7cc97ee27c0db96df299fbd13a9abcc6641150bb1972336fdc5fa7ec103e29a8eea80c2d614359ceab3442caf78ad1c144ad4645bd123f9bf5283585831bf170306f59eb372679d73a398127d25d7c44cbe4175134137c55270829a2caeb55c0fd529f9a5aa25c30ebdfb29c561f971d534e1f81da72479c2b84798b04341d9727c1264ddab2a6659b8a6ab7b8802089d5b323ad7a6b10625507bef8eb3cc8aabf442c2bdb44e4ab8b2377a8be8b6db11dbc19b4c2338d11ec4bc1d9496e672b3c185d564d4c029dcffcad3ba4e7fb586ba46960f156976a543d9f9890ee177d56dcce30fa14e584e457a1e043cc2b4f8b8bf64208726f3f39f5ea9dea993eb0a270f686ba138a1e2399a367ef19b87d39f8e4e11872edaab73612bf0cc94fd455632a3bf1e31fd0f6d5c55c75be0b11078adc07e072e0996951bf0f35098df3852089d5be0ec3b56a970c7ebf223f6a6df6a332e8727ccaea988b94d5f06b27bb3a3f9a746685d615cb00d1e1780fec8ca6be21ec5904e3093f682ed4541b964e2a8e5996c80b70723dcb3551b4d86782ca73ee11536fcc2c57b0860cd41c611218defc5bbac3658867222589f8497df050b66a346afcb1016a2bfdc19b4c722012b6652669ab4e2f30d4349abdb197782112372172a6dd53431f73c2234d0314af89714896b0e472bde15c2d445d2162ce6b5c7246dd9be22a1e94dd20cf3407d1ce4e496f163f49fd791e6c0c8c1ddc05dcf3d772bb910a16e2822fd062e234fa8e550be330f454a1e59de986d287cd1e057a485ed6292302bce6b6811a97582894fb46b2580a053cfae851291c95bc59e47b4d343fb541ef2ed5c08d4f0703fa8f8bf83877fb4b95f75219dbe9e3d2f0dcc1124e7ba14c75c68720130e6af040a93a90221521c35921096748f64109a3be5caf727af7a0c5521c0b2132cf7eda272adca84da52d9bd4b582bfa6d4f0e1e88e067265787f7840304725f24dd546500459e41b2798f046f36a0d97fa750ff1ca7e178b9774ffc224ea3dd7987650e0ba5ec0bde51704f4bb5af4f6dfdc7f581f5c72fbdb3fbb9e0eaa87e611c608337b0bafa398cc8ab56ab67ab883a9aee26b5872250fe4a885157ed0466790db1816fe3b724206025bb40148e966c13d954fa5728a935c96f20bc2ab8ac07c5427fafcf77a479af09db17532d7f99c88e0df233d99c97a86ba0d47c44ecacf084f626be5407ada61cfd318156292399bfeb6907261e8e68d2c621eb4f4b9a4cd4a7ef4a0fde1c60065c7e16a0374a3fb38ce044a5bc6a459a3a312e4f0ed30900264b221ffe2520b823660eb59cfad084b82cd002cd53cdf69254ee07cb0558b8e8fd6b6244686eec4308d1b3b22208bd56f5772031d1105042375ffdb9e2fa4bc57747526733d0e85a06c59d09aa44a87087c726896070acf673eaa4075b970e22f8b550244111dbcb0aa967882a0e4b9f4ef720d6dfe1ac85821be5494e568e340e82e5b3a7240619d7a123abe300f146014216e08e45bb6f432a3cfaddd51d8f569202ec228c335eed0d3d2377703a46609106d0eb3a878a3e3ea5da4cdeede7b40f3804942cb437021f61d5c6c3d95dac80bf3346876ac749131af2b2f7e2a9582c33cfb55f2f5e41d48f9f76de4c3529c72c59f0a199abf8f29e6c6c8d0e0a1aea206d0e308c721cf0d580a74bad7e5b9725df7e3eb09423547b335850448ad21a47b50f9523a12456d04f23b2ef3d3eb64a1d9fc59a87c70f48f831b52af1792bf5968408fa6b238c7bcb24217b344d2723708cf709bb6d4435c1f6ea129ee8e4f5240b6722cecf0bf17d9811f804e36236fec358ae0bad63fc35ea383fd80ad5418ad991f4901c265e9b4f7d2dfeb482d754e83085b5007721e71606b4333e6e96c539d033cef2d6f46bc3d5493b4751749ec0504a5f2cab3b3d857732c80159485016b12d9dc589d73464bfe202b6d1e65d7b2c0d27e2dcdb40abf0f7f212395f068b1900c6bda392148cea4c7f59e72cc7a97c4d10b1af264c7f6bf2199f26fad2919e6e735730fad8325887a6fc46775b0b48a3fa1b6feb8de4d82c358451ef552f546b806c080653fd427c2ac3f72e80ef407a43e8a90ed3bc8cbbdf8f048dd712a341853108211bda765d17ad7694a378ca82a83a75b7226fd0b32f8b8034e816fc278d402aa98434909898c6572ae41bc6b2ec9fe701bbed47f7967fca5e76a86c80b6632ff1727edae84adf07251c42e1bbf3588e1ef09373fc14c536e9e30dd8259ab546f93b2c97dfe12f4004c88b7b9697cc9de92555e7f0fff628d26d0f3914d71b5ff2e1875dab7052872bbaa3ab34ecf9476376187d127f5256afdc545b8cfd1862eec920d30fa7bad485d263a521ed37f46a6c4be891ce403e6b0498d1a5b4392e672cd8424d33bad72155494a6b30fe5352f80c9f0277fba5472747b1dcf55a646049e97367f1258723067290ea18b1c6a7cd6f3c849f97f7273fd08b20756c3388574118805728572a834563e6eeae513756ce7765d1cb7fc0c23d6b8a059abb910363a81f7bf0b724d41e59eb287bdc8976eb4039e7aeed5130f7a4f153c55dfbe4f30a3a2808b725896c8de4f963e2844a61c1b95779518b81a8e364ba9e9cff255f74a1e474572b2bcbf16ebfb06404d6c2def1185033e62c17f1b2a686be758569b7c86bf26720bab645b688f84212f9bd95bff855b50c996454390b1b4d91a0bbba77f2aeb601c3b5367a974662bc2f8bcab97b6ec648ab6bc6b2986093a6e056c700601267223cdff87bdcdb04c8641b23166d5b93683632b2de1ba3c5dd1f3fa8b19419172dc9b6abc986ff03fc3e7e6e6eefbdec4cb2087bddfcbeeac43b7c3b6fdbea24e2023ea3cf8d52cdf6be71c9f40db6dd2b9108ec472eff168ff4357e7bdc4de395f99f4acd8b20b39c68e7db80b7543b1d60ac8c1545270b8264fd2a06d06b8727b494019607de57ebf9b135a64ba231218609fa4dc7ab09c28a67e17296f3539ba9f2f77dc5d49869606ec40ad9d760c58d201174a7580e1b9413439293cbc25d802f88ae460e87a16432f5565a79d48cfd92ba79ecb5aac023fa42c45955306ee6e61d31e493122d2b60b1656b3a57f27d9a253761cbf759cd2a538942dbb72864c4ed5a014bca44acbcb43f00549ff0f24714a95c25b6f8525a0dbd1bf78726a4aa0ae9538177d3def2a0473e5d29fd28a6fd1e61d17e620f90cc24dfafd726a71b26d21e64595ba64239c8ed6e9d3519beabe3e1dbf4fc22ed777e2391141f683b10183cec56bdeb80097db204020fe99f4291b8354020e960ae6c8d1b372869dcefe3061386d9b5932bf2ba6429ef93efaf836e8f24c6becfafb8acd965757fb692c018a244516af5e11421fd15ca7df123ef6c502de615c031b52cd60279fa1a1bea6c2f4923473f1396b07250c11b13998b43da941d4ea512ba7a15072e9ca89321c1975716432afdfe9c2b3f499cf6eeb42102919d2f7c0bcd22652720ac57540ee0b7f07c206beb243258a76ff0cd40956add2cffc296ced52f2567292a621282b1d77a594303da1543e95438d8c09befe8c4acacd1b8c20662a56729c44ff782c42d848f795ce145d86d05cd60ac756de335a19e38c7813c1f48817421b4238d90b30188087a267dee302b794ddf254cc3d88a7afaa57407c9260721ce6c606b81d5dea7d97d7821a8d5a0b1e4fc9eaf454ee918df95748ce3f0572f9402d271d93a5f2445bf2170c78638e7ba5cf6ef4aeeba1f16820fc9741b8728fcdbc0d7f30e9d6eea33f46367cc1b5250d989b9e43cfafac46abb9453a7430c64f8d378a69666579656762af54afa7a6bb848bb195139a4d9a482feb8dce7274bdf5f9235612fcfa66152582113f635da88b7290e359a8b35b3802fd0a26726654eb8c422e4797eb422e9ac243a60fa7bd77a254d8722efbe0c0550190df72094cce37892b41067febdd8e2a24ac75b9ed1b0eafbee483f9fbeaa0526e1872b36ac4131209a25589ed15a329b16c8960c0d3e2ad3797d51ec94dc73c8f0a509729a2da1c0eba30672e22aaedf54d84ddfc72b3dc8fa2d432b22d904a7a2f7259f42469f178c0ab28723ba63232b10a99265e577bcc1b0abbdc8e75e26a5d72e65bf64c704eefbd4d910d5947d581fa2aa7e84cde576da42e2e9843e0a68872e2c17a51732877fc10981c5bb5e93b632d6f63f7dcd44251da532f6132f10f72281cbf32b1668a74dbf9140ddab8a5c96b89f30591aa0f085589edc7720b3672883275dded1fa72737ff1cd01aa5e5030b3017fa7d0f36e5313afafecde81c0b99d5a2d90d00a8286b5fe025a28c0a73e0c0d3904dbdf5101259399e60ca31729761666473222d01cb0315d9fa885f99e129f063cb75d64ce8f4172aab53cf409e99413a365adb65b66380363c0f3349438f728408b3eb32dbce8fb9afbd4372f3d00d77c75d986e7e37d922c6e53b1052e722ed7c7e42f645ce5010fb14a05e87cebbf5f068f56ad4b6fb3ed3bd02e54b8f6bd3418083f2c63ccbc8f9cb0164f96c9f19e3a389064ba039d3263d7cc0c6b342e947e68e7403a96ae2638c697264e122cf15dafe59cbdec93229d787d126b35bc67a00657518d7710efba7064609a0701b8efc042086f5d8abcaa95d1f3cad88368024c25a18ec36352c7f38728018294aa3f003b7cde72f063d05b2ebff5a483d734d8d3fa2ea94dd00f82672b7bdea6fb6510c6c11a7ae40037dfecef6a44805475f11507f866cb2e5636e3f9fd3a8f417f8ce051a14370e6bde24a006b0a2cd1b6d052a0867c60c57d2e17233ad8b7102ed6848161c4b92139c54cab8b35545ea3a3f543b476f47ea0c2b2d8b66f5cd9d71f5d491b5a0dfc9ca685e6ff091b640e9b29294427ac0ccb29372181cf7a6600fe072a8ed4c894046a18a01fbeb3c942653b32aafdd0ce0c76672a728e76f057339f506c6ee4853151ed17d97b1eed00042f76f89e70a2c0d5f06f857b632610887571f494f7701e4cdc04f08e746df5e7ece4c91ef1db46e59563a123102007bedd39fba38f61b7ef0925cf41c82685cec75393f74e9f35b1972de3112ad42bafc9338a3306b26d0f03c04b7177b6cc862071c2661403a67675195caee416e4e6a71cc2fcafbae810a10d6fc29825f6ba4411e5fb678d0d6dc0c186b08707d74c4c4fb888877b39804056b028435d328e7882073642a1ab06a3b1b905f6220801b084c8aa089c9328c6496feac4f80836113cb3c7785b93baf7236813ab638174647d34d77816cc2a154693c4b21165bf380ac7768487f4a452199954cafcc71b61bea2c62783bb5d3078612049593cfeb737470b12177697972537799dac6c07176b1cbcefbee98b9b1607e24a48a1813fae2534b0e0069be72708acbdb0cc1954ee20077471e416382250b5457506de39107b7864984e223724b3fd317ca39303059c6f06efba97bc7df2f3ece7917e6b615d08eec55581272ea6d51be37d7019aacc87e7ef717ec51c47ed17719afb0e22d158970eb89bf729d86a601b3999b953d8f092b4cbe8747705e60527b6f0ffab88d60de079b167266d67e721cd19c3cec329e32d8276cf8ae17bb81e991e6a2b147ab1fdc604b72432707a08987882e204fdedcc04d9fb04c1a0ec5ce5c1a8623fe1778a124ab050a791dcf8bf84bc419d264ac477969f73804e683c079ae7e55f244f82126d172152bf03c493a01d2e2a1cb1167f03e95b2158233a20abcfb349ba23e79b42072902b15ea2b2c36a8ce8711f51cf5ec977130333c32d8487e34295afb64953653e4a0475f9f9a652bb1e62840ff97f44a8114f8adb3a472545b63aab6727264246117b30800d9e0e7c2ea592afbd4564ae77832c8e3fddca444ddbdfc124ab8723bff3a40784018dded112862bf52483715116292ca2662b3384aae4f4913cb72637d2ebbb110cfd9c73ad233dccfa288c1bee78f0e16e8f3cf8eaf8a7b2f4f7297f88f24debb8e9ea41072ec4bc8749080e4d568dc6520e1df8ba882c003c766273641c84791cb971b37818ffa145bd1e25f95fc5625f8369207652e63312508930ec0590df1080f8b48cda1fb480d4cdb51ddffdaaa91dbc2bd2e3612c41844ef52c5714bb6778c46b159a840d5da0650996b03a4b864ef9c6e7478840fc068a6e0d066d439557975b805ca2c40a9186c8712af0d851fb3135db86a683fa172baa6f105184268b2b2c7ca3589ff85b6c70f46b9e68d10f243f98e0d24730e727e34843db72e706ceb6ab62fd0567701608ecf81b5712acac08430138c399972be09eff94de27825720e3194fe172edddec7d83cf957f8e552cec59a82d9d97230adf33b389910a68738dde736194f0657c738bf15e1fdf47c8607795efa2b72ed79406e67728b81274b55e258f5f8df16a7abab902095ab7e439b8065c8d3724da6fb19e51e9906a8ff5f914f2a07216c4a0946a790104eb1f331e0a8f7a4724e36e6939f8eec1e2bd07b23e747ba1b8d4b8b78ce0b307b1ea33164db67ae7208021440169a9c0fdb4694d979896d4c113de6a7f87e508ddb2428921d1c9a72e0aaac8ac55f07ff3614d857f636e81a8f858253f757400624a59d6d020b650c276f94f778553ba77054e15000aa2da8bdaac2c1758c878619501bf476ca4772bba1158f860c601731e6857c9571ea063f618ff8e11efa8c351e66ec93d61c1e3e889abee7a124e26abe128e0100460b87562d3af1736d379e913f1a37c2f672b7b6ffbbaa2d1dac51f569acf688c3478f079e83357a55d2d6b9d996394588721a0e56d0aa392d2ab5e7701b6abd33209f4a5dd8311dc6e904d6c84ff0f65672b5acd856743cb8857d77e6844345fc657c0bbe4d789d7b6d9dab12d44655f20d8e146e6321d4beb6300cd9e6c4ff081978db9d5ad2402b63165690b4ad18117209dc93cde96fb93c0afd297d7817aa8d1b05d595799029d5d5adfc0f677b2372e2342d43f8816d6986b1ea6df139fc28bf350ac285882b3f2c2d2daf9dcce42fc24665b9f09eee2d08ff4d1984cfbcde803ed381eca37371507bc21c0e8a597257db7344854f78d8a4211137a6e0a342ae2904970d1c476301e0712fe330b0726e59a222acc2d26cdc085ad6b3f502ca27f12d5d26cb1b30b20e506ad14b78630b79a4c26c87629c783d356284cddfbe9e56fe50b3cf80e1b5781574ed73c45f3bfde96fec31c62c12b216501828b1bfabd585ed43374b8a019cfa3ac811a172f5c8dd0a2295c934cc30e3b1fec6cbaa939dd54e91f66d0863bdb27d8049ea4bb7cc1bd3316347836d09afc2262f830950f64146337be489730052c4a22c271a7adc8f77dcec98cb7f14f1be1155d07fcd7187a581d50ffb93923ac9078e2c72d504b78fc7ccd1da7ef9dd36901b32bf7f44069ff640905412d94c1f41862472c66f79c2cbc2ab0530cc8f07886176ddb90d26c145e2276aefb9ff10b544cb62ede5d029cb05ed23608d6bc2315766608cd236062d3aef09c25830e71f51b272010b09d09a61198b4eeab36971c1dc75cec14cca3dadb68b310e277900d9ca53bb728ecb38dd32790732aa7db37655407ce392a41dadc5c26d09cce45900c4262a3ea31fd10ea046fe9f1b2da17bc98377dfc3c10a1b35f02527a4342374d73223179eba2b505ad8850b0b73f00634158fb4325f7a1802770897d6bb0d4ece3ced573aba9afdd2c453676c789393f87b3e873e94d5ecac796380bccc1b28a8026ef5bc8c379c5d21b80a7441433f68aa33be8893c4ca45fe9dcf833adcdb1f72f967baa3dea91c284ebc668b45cd2d1f163e3f57ba21d172d72bbf05ebdf3d72cce6605ba10d43d04aa27cb84ca969631a62e8017cf6c7595dbbfc28662c1572f81a02cf306e219c874c6d84c5a8e2aa74e17598aeb4d3298c1062d126b64b72a842fa81006c238e83f27ff6cb5499e1fc1cd210c20c99527b41984c7696ae1fa6aa7fe15093c426b8fbcf50135d332e941d5438a169c603f4f682953d844a65a5a18adcb1306b5446657105ebfa558c3d15adb5ff7956de5ebc765d72c45e717f92979053c57ee9696cc7129963ba697ad77e8ae4fc27f27b78e0ec38be9403e066465b4e71af3330661c76fa7319c29937557f6c83f6ff9e3c7f4931732972b4e826c882b44eb01e2dd8e0e20b2b80b64025b014853ccaae90cf5acfc37e6a99d64c7590abea7c3392b938667a5549f8175763a2001c76746d7cce0a187772a313cd4ead6529df0bafb55239e65bea07857f1095c209b3f3f16edc6a783212df3d6ac4ca3b16452eb3ee9721f6d3c963348a6bbc4f0196bbe2ca3e77bc8c627ef4ad3dddfd614fa4e483044ab642f82a326c62c988d9a7cea755498315537262c6c2154bfa6593c2bca4fad08b1517cff6bdcab8f9eae312d38519ab73287276496cbdf811974bd61e999fa53e26c5bc5930d39e6482c03c9aae2e52d2cd2d4d4aa3712767efb331c9d26e50e9d414a92897d3f2e4f159c44eb8540483b019dd95d74d7b3d400f3b95928c6aaab892aa95c24a3fb2352cac7c8ee44facde7271d9852b343e163f1415ccc69283ed7a543552c90dba61026f6c8e323d79ba72ca9f15810ef14e331bd9c7a0b100c0844dd015d392a253aa0df572264bfcb431954b59e1f13bf4a2260aba49d2cdb8ca16a54c9b232c438662db208b7209ed619189ddb1d551959d86e7164faed2ecf11746ed5111542f20e5632540127c72722a4efcfcd2b33d4f9a3be805f35614594196bdd174ccd0e05bcec0d02eb23426f568d9d1b7690c8dc0ef17e7a924d0bf0d83c6ab03f3568476a8e1eb2de598724a915ce865f6da51a9348a0b0b094e977dff893c22b643f286fc6a37d952b672fa4f91bf8c15a5fb8c1a031a48ca2ca163a7b478227ed53ecff8a4fdd392cc12ad46897bdb73cafd56b4551d8db73c44a0047b62fb9c20be8cf6e0ea1ccf11724f9e433d1a18fcf361f1d8e01a9a7ef11f3495dc14474a6a0102b18bd0ddac0f58639c3cff7d71009e5a8be81cbf0aafc9de23d6176f7d5de4da8b3d7b92197288763bf0547e6d586c2dd464cfeea2609f7521ef62668b50b4d662098c84fa729be7ff5973aad0e459513f02382aabe47c42a7e9773e3108053cdd2ba7cf9211ef7393bba999ac6f1a46acb8656ff145d4b987b418117ec4a6ecc9285abe04727ac27ff1514cb4a56e0c73c87fdf3fead712a5400523be31c3be47a63e73267245a6633b7bb78505d55c6f3827cf255c683f0ae008ad4efbd3a43639bdca985615ab4f7cf7056a60c8a0ab99a71950cd91b99849cb6bb7b7844b362aafe14b2471e820dd55d3b067811e7d74ee55da1c9508dee657a462d38fe8502bdb4fb672eaba6f49f68402bdf3971f6b062d64b69cecb5d3671c612f1f1380fb6f11054f5aa8c2069d15b8a590941258b5f50a868c434eaf168b4da0454c8e12ecfcf7727a8f3d1c4ee70ca6ec8bc2219fdca2b3c4b3fb8ac7b2a91ac9b3597e3464f872763ef92f5ff9e8643d09da3ceec87c295eac2a92ef4d49d51cd7f9dbed0cb07228c8157a075af7b292ba92023436e9f120257e7075fef76d50ced7613c57e966f40caf1bed7b08099fc3f7d56b1eca564db2c19248c0c88626cff0a0192535728c34982c957885dee081283eeddfe87ca3e498b47975e9f449696c8a09e43153a21ca25aae1f780479f8d3e189c9fa4e04799bbdc69eb0af26d2d8c75682810576f0fe00242e44732b720f9835a23eeb1e53d54084b27e2d23390d39e71c80042e6b801cf4d1a715af6d966a6f0c5ebdc0b1827152ddcc9fc3827048ebfae569d19ef0e19f308a04ea51b895f41424526a858e264006e56594d6ba72ca5cab72e2db44a1ad62445b7fec981e9b8f2315d528f1d3d23dd870897391da5b8373200c067016d86781dc165e9be1bdfc3dffbfa57bc1c579053eb889bba307b07a3ba1c5fe2791fc61fc99dcadf4303d3d22f1e6e3ccba5582b2bc862bd5f4e86172861f18448c53bff112d7d6ebfba50db2b297feea22bce97fc9c619e6512fe70e36ac7d6f63218b993ff6973cb3b2958802480cdf0341bab778f7854441acdf7220cf15639880dd2da34763abe42a701d9b91c30eee7324636a06fb7404caf57233237a1275382488af401ad6ca3a04e430e1ec39ad74c70ba3b518b06f62aa7289a5a4b94747b9b7907a8e5bef5ce4e7e67355fe294c9b693679088bd447c572d2505471632226a2bf3f2be18d58ef56e188a9e2ac506e772babe6a3d5c1ae7258dcb414b5f0fd03e2ec98acd4f04494f756791972305cf4defe1de2998fa572f3b2804a8b5761abb4c01eae0e461b9e9210cd7c6459de81b18853e3dc197964909ca63c599af285d97f64b90e623e7cb60effd41696d9aab98f20880b419019a37132c99d30748fb1d931e664a0f76898afc3f32745d2ad504ed49ec33e84034aebdb69fe65a0f3a5874cbda476aaedba722374e09b873658fa4a0c0d1d97728bd929058b7f886d0217957a0f894265470280629d98f06c5bc525d73e777a72b34c00fe408ec32bfe7d9b57d0b165778f0118451fdc0957bb42829acbb69d7200582b08da4734aa183592e6ed90855495b35f31a5482f0520395080913e845f4e512a1a95c96afeeea8296407e879c18c17d1f8ad40c279ee31b02c37448672861347768831eaa58cf764cdb6c0290e309c298e2b6f7b5b9471e80e3845fc729eff228f1081496e037755710fe168a052b3e668a04bc4e9e9c9aad022e55551350b41782b10108ca726e0be1dc319c43d2285ee1ce8dbffcb26ec9de962b92b90e9f754078eff14282e9d03dcc7063e3ee8bcc8f70a90b4f09e6bd038c6f072f5b7659cf6d2d776921b5de386001dee756434c62fa1c8af4f3c1982516fd5519cecc8c6f6bd91f8e2b8e7c57e66290161b9bdb5db3416c1002ddfb425402364e0e64633c0ab3ccb0b8ea2d9c4a0e1032eba31698f75c250c06bed7de783c9724db271558ebdc16142f6bf5c4f5b08c0f64205a507a590a75fda4305be6d2172e68a8273907c9161b47bd66c6d0b531c3b0ebed38351479fc71a42c624db7e72b2c335b44d9ae149142c34cd142655f454916b8e62b4b75350ee28d266628e72516b77226cada08d6a72e56f1fcf95655efe1a65b9312727112ddcff80dd2f13e02d2cee81b1af0317459a8b1ed38e56248e49e6d98ca480d73381059769356b702434d0d166cafde7d807b842d6c4fcde135e4a37b3ae9b83bbc7cf20ada47227e8941736001d839f1e91a26e7747fe96f5876cf9ba96a914da371b637cc850d60609eac2d49d361e8421255b7d263d651ee3f03df90e17feb95a1b5ad7ca7235cfd3201971c7819943e0591232bd57e1d14b65b84d20c1a78276bece61c572b7d96777a9de6b32d16172fbf289880059bdf6e82e7065a733fdce2900664c11e50802b08a62b78864a1e1c5a13ffa356e797e0a34afcca9a04fcc294b3d462d83543993a44f2c3529bbb0b43c347a32e84f9d1b7915c2397063de1c77a5da721554f99ae804a84b4239cbc727283e44b3bbc84250de72d784d7af43aa79ac722a9a0d1dea03dbabc82fcffe54779053c2c5d97f2e3bacb30d2afb24a6976d290f0ecc6c2f51023ad8a64a2f6327be98d79bb3ff4d03df579327f6edbb5f0a37d630213cc0981ad47156449980547bc56746af0ee6a209dbb0407a16d3347f3aa4c27026e8e6c57841d1d5a3a9b5e90d2a1e043159be898c617be4fad6cea17270cda42e0a751cc7c58c6c150142f479e70b645154677d3e7f498c5373ecae723e3f9276f89fb6c0a859a949eeb38be7a194e7b0a8e5cd9ab38f87c9367d43720333fd22a729fdd49ef8a292441fbe0ad4c738fb29e941453b1e1db836a6a909cbb0bb7929f18d51b029f733ca3b376c357c954a3940fe52671217c7f7037672a336a451b3fbae16693cf0e5205aae7300b6140cd30bad73310b5b487463c54926809d6b282e1ef0c222b764dd54005e5ff5bbae412c1da4d57c409e020e3872a2f09e4018a3d1ac2ada53c9b0ffe809cc8b9094e12cad34fd55e219d35a7728fa6fe2a726a71dd0db3b5c74fcdd048734d58865dd57e03a647c86f771cce772402e7202e8eb60427130555c17f1ba44540bfd0e18990fb22174acf19ccc0d7296ee7018f6fe59a778e2e1a5dd684e5509a22b89cfe65332997ca5a4489e7e58be67860d93de04b9415a81b6173d66643a3c5ac06f0935a92e33eeb398373c72122d71b14a8f5be6784c9385ef042bda8286cc174b5ab080237dde2b46ff076674a2460a4ba8e926fce32f40c5b48b8ad029a82bd3cbb96ac43e8aac102ef17289ff62c66480b3b697e9527828a106763619289045eee8e8205d49d68d015a721132d9e91cc92354d76903b6dfd209ebe6aefd9c4753ca1652573e89fdd37c64a50366e07991c4de9eb143feb7f090673b631b3bbcc318634ab097ba02a9302e6e73c3852531fc1f312b5cae861a5b2289cec3c1f9bd1a2635b919a89ee32372376f1f5ecf5b2cb0c0c3b6ae0f21aacc1c70e893ab64a10af0fbf37b1c5ef772acc8d9ae34ae69624f4e162f7ba7db7f25186dc030b07dbc86bb88b9de19bb25c9f656dfc0c2e6f127fa1fac2b0b976011a68bd30a0f9caec7ea5c9e66570f68804214c541dade1f96e80c5d2398dedb2374272a618f94f172972119d2f424729d2d06855847287e016799646f3af03e811feb9438450f58c28b8c331857673cfa091b5d85b3ab7703499bcf9c0e5adb680800ac821136c775c51007067cab10f3a3c6d6c177ee52c5a0012c495663415ddeda6acf3178305649aa02589a9f72504c1e52667abae93e5df628a2099dc7dca4952d25c19f647b5f8765983943389964e262163b0b4f6473ae49642935bb98186c62de4332790e0671b3de965b72383fcb262364f454791208cc46223abaf20f0c17992444b4405246a2339bb03931f48aec7122eb7dcc3d67795da28d43c1eb448e6b4e59edfdf581fdcbad691ef52d22b0bdf6cce5ffa2aaaaba354b7309232422a47fea62cfb99730c358d42c7a19f8cc4d5de5e8374ad8bd7b7449b745c44c3d32bc3d31b8756a5320dd685fea231fc9c241bc6ec526bff5b07abec5ab7eb5a221acfbbbb3e3700da49bed72857f8148f75a63b862cb975878ec2b3a7a7033fc47008c3dabfc8e1b0c2ceb544ce78ff576007469092c059e0c63346ea7cab2eb9adac5462d24b9e36f4f55727174507e6e7c25251446dab97dc94e37271d9b060276145dea0b8b9e6b84b972a384115be0dbec55613dd597bc3fe0f989d40889234254a2879dd777eeb8015c36b326912170de50d0e954e07437b12fc2510bf4a70f704d5a755d6dd1a1e866f5135c5129a3156d4c6a5e8e7d61ddc50e2fea0d4ed5b286d43bb24f184cc772f7a6f5db0cb559fdda37cc0f2965fb0a297fe92ad99ddb52d6f9b079bc63dc42aee7f5de802af4b11579535e3ec93144c429c489986397fdf82b614b55170972b281c9ac425de41cfedd19992048227ad6b78fe6344e93a483388a4c253d327214f8e181612c58c6f053ad035aaade2d72f4b7e793c88aef215b63f2132ae14088aac258f3574ebe598cdd1ec3b6088d5fb4b9709927c652edfc8cedb5acbb726b2d1bb559be764504e325e7972628071b8d9ffdaf44b273e37948897e229b724126af295ddb96655a04fe22ab4b670b6df77344c3a6f71f9326ff64c24a8b726e1d76b475d7974fb0fd47997f45af21b40ee4995c8b9273a445d258a9f988563dc4952a3a6b952981d20479d1be74d7219582bc5ec401e1a7e74e2254e4297224ccf78391173d6e38a91f4f241b6974ae6d84dd619e78d5fa0b945a5121bb06bfd214455b39b84b819aa0a69f7eb6fe18d4177be3f0ac3fa0dbbb05244c750d7b9dc358153d44f5e7ce69ac994ca13be2c783e577f5d80f291a361474a95a72ff4e0684bb7591a9028c48c5ca36a394c658071d592ecb9e5d634dc4ca499872bed43af9a404f4f25b9dd4d33705eecabfdad58f67d2865c466072145467a77287405138a72db07e88ed5c06ff40c953d0629c83ec448e54edc5cbccaa85a17273e48fde8bcfffb5cb8d22de1eddd2daa8d5a89287632775d76796bbeec4c2726622e42385dbe124e0355f779fb2e694524f7370d1d390a7206e814b98b493721fe0326e35a7533e6999f2460d5d9384d42028ca8bb9839bfd9e01e0b711fd72ac224861aed681c374e9d78c2709b589f28354b7c3c0e87729a8976b1e882065cb73f0a18965499d0eece735c67b72c1c0f69ea306da1bd1b9102f8d2e08e57231d2e4e506e7e9c39f0190447cb53b2ed973d5015d6f1e4d2185e8d3cd79d072295bfce7c78b07e77b2634fc3ce3222ddca333445d567e5f1dceec9003c1c72c86b98c448473f58f841cb3ffd3e66b55be93d6cd6b856953d38dc1efb72ce072b449c7b44f050caca599220b8e65d7abadc79c1532111afb7c46f7ded43492726753fbabb4f0d7e3dbc45dbdadeade2c3319146324423e25fa50ee584644911ba6feb5bb9a59b39dda3a9e9ff37b04d3f5de3b640540a0c9e706d1681d8d1922b1a4416b218de371401c98a217d6c9e08952c657159bead21544da7f4df44c7215b8303b0d78088d3cd095b10df7d45bbebe0cd8e4cb34a8da262a4767e3e7720bf22049be3bb04eda8282336293b2b64e6ec1ccda6368c79a765229accbfa7203b66930d8c57698f5290c334e2b1c087dee748ff0fbf9f4b2fd1d42411aa872d62e3204260e79681891cf49fe71fbdbea232ef414df7bd924c8c9295d517672c6c845176a12186fc4aed8ffd8d5d649afa0e869c78bc485d20f6ed2efba517275298cdc40a7a3f8d96e97227e774b45e35ca430be5326143f724d4028c7d9726444a21a89036e33951fda70187e107dc12d9ba3f86590abac5c429ca44ab9729b06d25587a76cf5cddac4b714568aec8d8f0ca17cd9f200577b50ca968e42720f0a38f7e6753127e56fb16bc196c64ea93dea309e33c92f24c25619ec2d84729e660dd86fb1ea95edff9f15fa51d56a2a8610d04893abdc130efcf4142abc34986455748e98c38ad926f0f1a96185d33fa5c23c657c2e01a794086e2adcec02b1ecd123951a26d038c757d2e174a707617e6efe6bd1885594b812b834b11372851488704d006c624ede0f620df6cf94aee52fc9654be6e4df6cfcb0bb71ec18e28b725c767c8e9cc04084878051a265087457921152210a89e7a321d3851d23bdc3855ed44a94e0aea31d113f720565c02158bc8cdb567e25bc4de6795879721ce9224e5912503208a72ccd40275f4640dc1a3b1b16ab4522e8e9010daedf7295fdb7724b79c197f513eba4fe21adc8e122fa7e30261bb7be3c7418f900cf041fadbe20ad5a816435d5a1606d9ef1234b212200b68572618bbe9d197b470504c186e084e0df8f4154267f4841d48af7b5038388737f88b86d4fed7ebccfc33af5d4dae56e56bd6828f1315c651b1ca5e11636589ecbe4eed8dfaedc1686da72940a68381389ca386a6402eb49d338db2c43ea4f96a76da2da5bf80ea2b54b465a764747e1405def0e23a8308dc0c5696b337fa54e93ea8a9bc4f9d324a1fe729d98ec71665b04590579102e311af3c3b2128b4c399105befe8f0e0f28b2057274e111460bac3e03478ea6fde885c3a5d927d613bab47fe231b7b20fc4067472aa2e128f8d4c4a5275deddcea72af3746628829e37dea2ccb92aa061540c78722968f34ffe496acae3b477abd3ab1b2311a049adcb1c2b2a5fe08dc1d75c1f06c1d584222728d7aa40d068eddb4b8402ba5262f27aa5b406b7fd4156bc664158efce4c8f550707e9f1cb9014037c384b721fb105ffeb8884a76c7129491196729316895bde11f44c7e2effc8304e0c3fb6ade51e4f7fc1c4c945a3bad4da0a723b9d81af0dad7f1d60e1d3925ba26449b77acd514f4201378ff7489a95ef22727d491cefa8b056be2454b250cfd63570542b914bc486595a9bf371e4ece6a946479db5689d4a975a6eaa5243e8af9e810e9a0aedf55c4e834e2d3ad315f2a54bb2a53ce6e56d72fcbe7afeda79b44a1e239cef9c17d2b3e08ec22c1a43ca6272aaa5ef336093a77c2d89461966c81bdfaf2a761f17470887522dea70551b9b191006b7176a5d26dffb4ea4125b5dd0863777f5dfcc1004a9b390851edd809447db2d935c4afa43db0c979a1e20ce6b6bff81b78ba6f3f34616736b005f866d626fd642642b97cccd6da9734bbe65df1e77ed44f9ea1105156703cebf9f4260721d33b4072fa6a73f40695300f82ed1044a026a428ab2124c7aa6a4c2216d7472cd6870fba7e61637c8d689623c6430148a05c937c2b9546c59aae86454d96872bbc30c2d43e2ff715dfa7f006750898293e7f2f4dddfdac2ffd03013459892556c2aa29f19e2fbdd679f0b99e13e23619164f667a677f34444d3207568fbb36d31c107876064869b1c61efc9573f9f148ab38bc6248767cb117e2455065a57724f25741141160147f8567682fc55a5a9df752caf32032f6b73a987b30e28095fc1348850cd3cb82b1d39c232637362ee0cefde0802e6485f104d8f7693415a7205f0b79df0814ae466c49814f3aef5dd84056a38fa66373a62092f6c9903ae2106d790d2ea26492523643d2f28744937ee1f511177c5b065cca636ef425ff43159d2ed2b41c9a24933619b5fd36d94389d9b0f4d4480c58ecee8b25f5cfeaf72c34b435e70674ace1915221beecc87fe4c37fc15efe79f4f0f2897a5c64e4c72c68937d28bd159c0c8280caf343c1cb07032b9cde4e4f4b47db227590f0ac872f3f156c385815edffd874db9baecaad356ebf21522b7f82b2927f35f455728507d6ffc5d4268e01a8c8fd1b8fce134b4b30247f087f241c3ea92e9df449c69728df6955df96434710495fe1b814613709d80f97ad254190f6d417e956cf86772a706824ff3ad4ab20b86090fe526b6cdbbd547d16e11b89f58111f91010df7725f3ed990ee075e282da68ec9e364bd630cd9006a25e43a23ec21068bd5e2ed72ccb3ce729a1e4056d2a797bbeaf0a23765d2375c6da1c47e7be2be9cd8c3243d2b0969eca2a19bc682af52023834071fed31fc5615f22b65c8f99bcc0b8105721928a9fb0336eb26f7e280ac2a787c2efbe0e9a87d71aa3691640a7e5b07bf72562391e54b0e773c3c2b327d4da57cd05ad9ce39a4714bef0972031d21b253722677ab17be4d5a594e0fa176b58750d5c80b1dc8e931c69823d65b0047e519722bec8b273511918369e5ffe49889361cd658351e682380594de31bb728a70672f48ecab547ee89d8fe6db9bc27b2bdee310081a70eed6a29c53318502e342e72a021079d3e5bab2296cea7edecd290897ccffef73c6f588f13d9796062bca92c904f83b57d794d51b9b734d088d100e531787cb63f1903c4f9d02cff7842ee65e2f38a0df7064e48ad8d0defb4b15af2b8322f82b08bcfd7b421212126f256431e1aa97e51caa1f25782e0caaebde85afb5cb6cb0a62b2197eff31ca7b93e7727a418a5e2e0d08bf712dcb1bb5789ddf874e2b1fabb1efab646564c349cab55d2300bd7084b71903890c1104809b06adc18faf2e20bcfd675aed2d0df231e01daf08c4592194fa925dfc9b99ef99ce82898d0412c71bd566a0ed6f338a49b172657cd630c80d02acbcfdcfaf71b146d7abb53b9872a196d008cc05b848aa9838c418d3d7e674a5928da6a9a90a97e80fe0ecd88c24eb60fa067846b9fe7de17257a50ad610aef5107fd61fdcbb7aee2216e78dee1326465caf4a67d2e29ee67239c043c0fc628f8e16c1e3b1686d02baefcd23ebfc2344f79d72554b2b978972d83acd460d17d3b43470076ef6c7d17dde70839cf7ff860d2d420dd53f8bf6724efd3dc44c4435a5eba3af067a11d91f6a87238a7a7792fc7645d38f2e6707724fd68b74f7b447de6ca6e1618936dbc90f33090a4961491943ca629637d4a5727995ba606cbf46109d30da683b26bc2530889a52f0c47789835c0c55fb37ff72e532b2f19665d8081d9db88ccd6f697156c5301a34b88f2e106be1a8c2952a72fef64a1c4d24787908196ce6324b16c0c22f5d3522f9a2999d0c295cfc68ed07b06488589851b8670419ad658e398324dc369c65d0209c113bd91bf6adc989727b8a66e00ebf554506a9abdb23b57265ed989373e0cdd8ea5325a247eae9d97269c555bb4ac4f2b4db7751d5f10d2a64577f8ce133b0a815673a9b206f24f972b42f2e2e1758ea8f940e9c4ce399fa416f60712e416b027ed8f4d47f9956fe54c3607a0dec2ccb696bb0a287be2c9ec36b45fd1c32394278e4ffc333ff203615e25b15bceaf48cba92d956abd00cdc53950a51fdc01cc802acc508ae06d6a5723a3b2967634be3621123bfd5b62d2d3c331afa4bcf58e0e7e5ae7667cfccf272c8ee1e048493cdb0a7ddfb002e66cedf812ce3bcdb348da1e394779fd0067c725fe05f4b240ef00653b7dbbb0382494482d5be44b45eac093656b7cb68be3027039bd5a2b20ca3124c1c99dab2e69178f2d617aaff6527a4363a70c79202a32412ce6d80a8402a388052bcb31a1032b73a400defc77f80fe922cfd7a43edb0434feadd010c645307c15b05297901288f67901387ffe2a93579beb0b670e27a7258d943d9d1e45f9a355897ebe66069aff35f53b35475a06c0f01d1edb7d9032f9d74aee08e389dcdc6d3352b884ae0d48d230a11d8307ea275f1830a87be5030b20c113d1ec5bcdde0025d0d25b2b650f92c6856137ff4c06f9aa74b065daf7267facb1d5b1e222934753d71e3586af3dbcddcff54db33a080922d52a708b7727f65d177d192d041000e1dd8a3e8dffc4bc763a9f79d1fec02666b2fefba9c1cf61e0e2d48489c93f9bd05b94f0178c03e4f2a9053fac1b7f2c5d4e31979446af9e3204dd24c7ca11624329e8012091b0e4c787b603b27753dbcd679e5b9dc4c5eef0429a811d1f5b0319fe90dbacd1ad1f333f8fac0c6744a500e4087daab7238a0f210e7e0f281496b75d9f876db263184061e5274163b7a47b9a0c0e93d72f0a35f1a81bfcabbb262a2e50eef7db42de2a59fca54032dc78f9e8813ea4055b2e52e0adf714a608eff254dfb3d0d633080050415b92481f6f35264718871721c4169ffbd3f10483d080bb5195a5609978810fc9f5cd60d3abec42f7efdd7727c6ce53ade443968577e69426d60e7e61e620e4ce7b75e89e71d3932c327422be4df1d6328bcd36dabdf03d743d8643845baff355baa16e37df405e06ae035474f1a582629f68a6328794f8ebfda16fb1f940a0d15d4e1416aaace161aa79d72f3a0bf4d958aff20e87f642106df8d23d9238b12a1ffaa7fb0b9bb532879824d7d96eb36e1b0e1960c722b7d1bcfaf0755ab3d99cb61abf4d7e594b5b9496a02dcf8e1ddb1313b3fc03236fdabefb306286840cca300a4772ae14bca1e31de72de4d2dd50c99b3c8dcefae42eebd5d285af6e1406da2e98870a5c9d880ef9872454724c3fc604325284d48bf84f64bc0d5bb70af449321280f9be062e2b02372eb352f978381c03fc7f1231f857fa58d366fba4d07e78e4dc15f1aca6db5ba57b91335fe1ba5e39ee0741a0b44817263cd8f23f44eceeafb45569bd3339d1d7276d9afb13332ea5da656c70b872c355f058ac47e422a9e720a7d69def6cc1d72b921393e38088c2e57dab6a735ea03cbc2ba5cd9f12132088113a635170b8972565ba33c6b5f1e90769e317bd87f77e1342f9b80ea6f4c4a8eef5b3acfaf87ba020000728e907bfbb34f71c66b6dc6c40fe98ca6d2d5a29755bc5a04824c36082a61d172cd95ae485bf53f855084705fd5235fdb293857f57331ec95e56d1b3e1d9c45000be10e485bb70d7dc12eceb5dff65cb2f410113b5bc9d1d91f4292236f376b72eb30e3edbd3015b8868e2de91c970242c1d7c7600b36f7e14f419bb60e86cf489e6527384bd20f80fba315041d43a4ac2870c48ec59d2981f1439c40fa3b8f723da0f6e1c4ac93ebee771edab2168577e6d16e17dd0114fe675bce74d720d072ada08e0087ca24c44220157e93fef2dda77e6370f83a754e6948c1ea86825a2402af2e54cf030c6fa7b6e8d96c2fe1afa97c63fa35b3ba28129ade7c58b92a1bcea9008c72829f6b0025dac0bee5b0c1819d5bf174386f1492dde8e0758aa472a797273c6efca6557bed63253630059c13dfd6255f45179f26d41412fdb238721c6289a686aba22e6e90c7319157e1b8b02b2262327fe8e28ed6635633207472f725014ec26c89c420fb0ea689f4a7ba39bd91d62020b2adbd6bbee3e7e84c30dca3b70e9435a53a12b1a3415fba16fc7023471fe023ad57d4417bae07df0f723c0a3719a6992f179cbd6372cb7640f3d45286689955ca29cc993a92505ac272bc350f38c969ce0956288fb749593bce6f9184557e7e68f07f5049bfc26e455d800c563b44e7d839e38a24ec33e0d8cb32d58b71390a12653d80f0c990028b1587544eb0c0f4f1910e87592c5ea3c06ed772cd9060391dcf63a41a52bb21bc72ef452b5af69928117b8f0ecd58933ad887851b7847edcdbfec8a232f3da2dd3a25ff295e55a689a60a083c15cbed7fd5d91e81dd79f871e5ab1e36f0961fce4d5a93be0645772acd1b43883889cff117e4a425c9b7886ebd0add8b3cf073a972daa6401249364eb796a6df83916e5818c43c32989d9f9bd6d265999b22bb3972e6e5b49bbc6b3d1f6dc86e1eca782509a430b631444e1134843a38ee321e8c03ea94d06f840c001ff6daf49ac9fdc43e9bd99e31c31d3f5ac4f6896b85bcfe681fdefbc54197d47c8201e65f65c7af329d0eb2fa1ecfa9ca92da9103cc8ff172ead3015838cbcb251b15b5315b7e660c5cb83bfe63b7fe51968a125c40273a723f4b1dc70b9df034b30ac19e7295b55dbab5310b041ae192b11f1873b4a3fd5c03d4f49a4f4a1c74da7473756ae1a1be337a89ae510b9532d0551a118bd96d724253c9bceea15365429062ceafd8914dca41052c97b4a9dfb4484065c9f03433dbea1990092b28b9699cb8b935b653e490d0da2ce202293bf8773a55d996140a4388578a250389c3a009eadb34ca25a160e118b17396787c47111bb53b8be706ee8ade836e9ec6f87c09b94ae204110834aeeb2f9245d03089364982333ebf72e75e2153973516d7f7fa02ae6bf3193bacc5750a10e7afd713f1c5c9bcc888723c7948f504bcaadbe24e5c8d08ded4f708fc0f95551f7c8d690ea0dd78004972a5b645ca2803344eb55bc17d61e2fd73bc61d0cef152961ee620a2d5e57b8a7264c8da293904ef411de361463de69ca1eb768dc940a61f729cd09ef6c110ef0d3800915d7a8fcb97d8bf58a2e8d3a1a0e0f92a9e2dd8dabe087cca0b3e97241e941cd623f614ae998d69b83e84476a3ec7bed5c5466f63ea1ca656d503e89d359c100974db82030e3dd0dc94b445066d32d1765a17224c625b23781ae38f2a52d24a5c937d343c06af6deb71029069b71138ffe77c541f401f6836cb76aab3155aca7ce0d01485ad5dd6546e0632ca3e653bb3cc6d5aa7c8f35494dfa9ee522a8cbee7318fd5ed0fccc40920fc4bad2acebda8e1fca1204686b73fb9f127a77250947e67045c739eb0185eee04e0b7d8bdf7789a8864417f4718bb57039a7859b2794e22589ad02faa822e4340624b1b82af1156cfa79549d2869494a1ee1000e5bac6bf8d07b3c4c80050df06bec3b0535b318444eaed4b5b8b732ca212b972a839af26532a6040ec117125fbd843ad4a0fbad905f123f545bdb237277e2059fa10615dfb27d31fc607a1d4d411c63f5fd037576e9d1fc546e078cb51cffa72a3598d2bd412bcedc81346bfa8798586c3832251352e7b92f52204e914fbbd2e181c8373ee42c9dd918c75a448bf8f6485615672324c04c7bc19ea8756415172baa861b62c6c67c17e362d2549a89d3dcc09ab78bdc9b537b732f17f4a574f72b899a5cdee58357eac188ee6d30ba7bf5758c438b38a679591618dbe94910501ac8c02fec52d7e8d6e6c32ff18606040c323b554f40a44ec78c621499990cc6693954e09544dc0e787579a5ddb488c0b0419efe1d6b2ff4cc4bf6e679ca251558819695ea89b90127533f9d81959331a5ef1c29aac97747fba617b49f6e194725117450fa3144bc6c7d12b0bac763ed355575e6f47acc506221f1a583dfde372bc407b7105fd8b7b0ed4f3b34ae9d7a28a8cddd1d16aa715a4ff085374ae4e63d40174e62f5c29966d0601e1e87fe4582336699d30ddd2615c264c7d3a2aa372f29d078b3944b6e4e6f118f69faec139872f33a6c68d97acd45e2fdb891ee465447f48b2639e296229b3916e69f86338c5f3fee54e08be54e39963330697592cc3e8e63e537884438c6af823ced0ceb289822a5f7bdce7769e84059df4279c0e48da50b602ad13fa5ef0b663ee5c94e565f71e45571251742dd1812d57e7c072ddcb0887bea4c757d333881511cfac48ac1b70f4df02b2cb2bee80560625615a5cc83fa8e97ddf22b8d00ff2e81238763ca1d4b015e3bb2aebb58193c0e415728a4d850fe8af5bd9724101ff2dccf89496dcb926ac16932e2d3df5a3459689722cec8909ff22a6d67161131ea940785694b8807b4e07e87a4f75a5aaa07a11728c1091c14ee23f9b0c7e75cd92c954a05415e2ce669b52a974996373c1768d72d63ed249a1ea851da541399d85473b872049b2c50702c136533acb9ec40dff3c971c42e1639ad4701dffa86c7031f7977a32b0b3a1e8804deac161d8f08022211377bddd0b7cbecff1b4d6ce7e43da09505ef14aa1bcf53d9c91df3b5cb8a550e6c17c027e3280a73f78361e022c49bff798aec669eac93a5f740a7c656a01698a241d8ab7f8a24466a228677fdedaed6f8e5d50a135aa10aa44d26e5025907265e6576ed80918964f7bfc44dff41020d7d3b04162651bde97a8bf998616b36f163aa27cd5904731a52556a227c3f77084bab45b3343ceafcc0dd8a2834fb767b089b6b6063c331f52692480135a87d6b20afd02ad9040de2edb80b762d160290f498d9f79ca01c5cc3379faf2a0ae7093bd930a83c08b68006dbaea9b0ed172de2e43da697a6632a195e60a22c40e9a4c8b6d8f318292fd2fd805dfb8943f72fd7bfda941a4166c3e0edf5f74c160d9c8ba1d61580f113f858fff20707c887297b2ec89c87f305f494b4da425411e74e45cf613b376d99d28886ec5c1cdfd723321e951dce6587a461061ef084f1fc859ab202182ede563bca4f373dd57d772984363097021e69765863d1f9df324d10ff9a3b7f8e806338e36bb0c25a3a63dc39d13f760c5c72cfdf5ac6ee7d1450333df4d8594176b7eccfa55da1a7814729c8a7fbc966f098212191b290f2ee01664dfaf6aedd149789796da583c9746469285c9bc3524adc1894459330bcdce9babee0525b7eb95978c748f8ff9daa3723e769f6312ac0a75db57028f69458b07eb0f6ac54ef9f5560534c246fc7947729f85970cc0b7397216e3f5bc0bbf4167bac98ea666888d537853898be687b7214d42d0bb65f1de7b41e728bceb82ae0e593efab6ddaddac923c7373ed670b772eaff6558592294b925be3f4909efc09ac5742fa110656ad2e01c7fca74a4a472507403c9f8a06ecdf09e391534a6806266ae30d1f5ab0e1732c6bd5193d658724ed2fcb844611f946b703635e61b2a472b0092eec142a0329044cacfa08bc372590fba6ee31c7aff7432807320db0af06730bf8294651745e361d5f85df09e723b87668be5451033491f1306ad147b9ec6f6350b9ef9d06789f09e770b4bf372c87508ce55ad57388193fd474ebc23f1e1e12b60f8038cdaaca268c480703072f2d4f8438b80416089b39fd7c1aa32c3a254880f8d932a3c156503d851c3417269aa3f2e701c82240ff8402aebc8c9515364bdbc0ec25a7ef71076112ccc80459085443fab5af94a9681bead89f71cdf0360a916f2596285cb04205312f463323cc4d4c53600cb93556b98e3b767fe97ad43a4f7acdc041f6021e809f79c97726f7103e51e9fabb6e3c20fbbca10e8dabd8da26b723ddf31f36a264e652dc76978a3a5e68017d8e2097a024cb60d1f3946b46e147c273d08d381be26ef6cda72261cf160023236b4f4c85a30939118bc9ef2c39b59ec4f497322387f696a055ab04fff73ea42ff77b075538cf28c653bc5600045cc2173632fe143f15a842472fbbf5111a7cc7c01eb82f6b1360289c20b017bc42fae68f570677b964862eb3e5d886a84168e3f9c6d84d86bf88a980515cbfd4c39024aa0830fcefabed6e93d7bd752e2ce41255030201def705f874e92976ef786d75f0e69338b1e4eca1372add926df2a4cc3c4ff8cae7f0337ab1c4a5b0ec8f1fe89fce25fd26da81f4c7224db9b31271ace5a5efc41bd96aa5dbf284e02c938abd77a9d5a450524003d5191c0d4b5c5f5c533b1f042053b89e9a50dac5aa44ec15c1ba1b787e13c3a7b699dd14ff6398af7b2fd8ff22b814a6776c1f8e3ef4e5d76db85a90054dc74423a87a54096e7475fe6dd63d4f112737e6369901f20d16d09c32b6828a786b16944feefbb268c2acc6f1639b2b838137819d03eeca02e76b4ee810dc06ab882c872a8598a18e4e506dbbb124425566349281f52481bd507a72df711501129ef437284325bc4b01df2eb08451d29d676a2f23043daf0ca880ad48a16d3cee158e13e65889c0105ccfe8845e102d6fce42a7ca66ebd72728c3e0557786a5bc09de2527d1c8deb0760f1c96195e5783616654855faeb0a068310f847c9fa2b27b8007201d5dea22e661e148928ab05e1ae14b969e30fb6894f8f4b8dfc5352ed70fe3182a87da23afb3282fb0360d34916c64bdd6565708346a6b95a96e309d3be111d87e2fa10caa20bdf410b3eea204bc991214c74af0ff017fdbbb2b9dbaaef9c6e29566cb7d41552459fc48288c47da59d8ba8d3546ec3884ba48d7ccdc637e372b2f3d6f22f35f6396281c81a288bd569faee1d85bc4b90c110d0645135084e2a31019e12bf992ced31f9015cf2896df02188798274d9169ac934836cfd8f9f2d8154a5bd7a754bfcf432deffe04c2129e77414b66a6a0de8322db814e4208c72cdd5356a408cd9f76bd6eab838b242444f432ba9247034dc5db03d4c45b6a8721c6d301de15b917e309753a959597f93443d08baedeacd00b104011ea8519372d4627e43341caa5f77c837b9b7cd75921a58a94abd6c360163e353a2eec94572ee1703bddf8ad891bc98525c8337c344f7f44d961dec1cc29b4ded9a62efc8720a808f8ce7c637a1d044de5d9812be84fb8a8acf6bd7e74b1032fce4748c37725d3ef3ed3fcfed0e187b7e6026df91c0596ddb890b384ba9ac5a6a1b06bc9153273e58d06dcccf4f386104fac26a3fa65d853c60421cffc809d4c1301cc53456e748ffb721199f2c5102352cff97da14c89e981cb7ea5c3b32b71461b9b24d12d35ce367dc084dbf117d83fb00dc53dfb1909861713c98cef8d5dae26961d672c708c33e5ea5ea19ef082263bd539549265e61be72066f467ee15e172251cd59eccd670bbd9511ecc3afbbd181ee266f24b7589fd13cfce4dbf7be8716aba372821875014cc03783338d679fb361409f288e9d169613b482a08f9d59424e407254c80d931ba26b18f69771c010173475780e8c1df8aeb428d2b8ecd6640b827247b567f47a47179838a3b897da3c211d0345b86558c1796711a77b7c2c87451aa6d39db2547c443f190a57fff335dcccee5a648a518f340053cbf14ebeb90972e1bdd76d6ac4d7847b40346b79085b8e20eff159df3ddc0a0d259087693b0a721d012c825c0a0fcbf5d2d218b6882d8fcd87a3f479c9599518000d19c6fc8572a19bfa05dd0bc85a4470017d9114392391869903c78f85cc4be5898b6455121b082aecdb42c1067abf9fe9b1238b4985cacc1aa22f371254dfbc1f1bb6ad7428a2618ee00a8a0c8ab0857cc1cf9d025a4908c8b875c38d4d4dd20b0acab2b47228c1926d8886a58e94bc4bf5ef9997add69aa55bcadf04c16be67d4942b965722b0c7f3e9bf18885e25ae045bda88a772adc786f03e8fa5e7aaaa4df31027a72db503e1d251f75fa2cc87b886cb146a665e27425d090ea70530569b00951c472aa939262af4e42e050a9c63520f1712b333d67125fe347d844dd33c4cfc58072326147fd77f1f17a5a2aeb811eaf85ed16273e8561a4e6432beb3281cb71b267b260047ada67b9a68479aaff4277dc95c2bcdeb6a693af847bd92de224cc8f225220849f833a10507dae9f99ffbab04242127702370ccbad094d16c3e408cb7261a550a7ee28ebfdade2a8048c25bafa4f35c0287db27ddf1ae84d13d3efff7202564514fe48fab9ad25faa85a74fa258ec8ec986fc4c969c22f81c296672c002005b2192ba40c3aed34a1a429061e46085c455dc9a16497ce0b39f45b01c16f1c4509e9fdbbd40c021c7a779114443965f84ce93ac94fea3c3967c76021fd72ce586ce419a77c5310c25d09a6904e73b09df529e266fbc273cbc2356dab5872c733f669ad75fd6ebb7ccc2ac983ce2decec9d91b108983a795b82be1a6ba166eff2e4bcfb3fde32944c8bcaac0a31afeac5223dd5c79215738484f91704ba4b2facc0de0c574e174a0c40264ac32de8ee178c5ea77d4e02a1e3042e58cc31216ec20f15852c4037da616f0f693c57baef442b7fb47ec977f799e6136f2fcf72fb392f2b87e981f12ede16ae36bf8712286ac571b59d0e43f1b5e88c948b561f38a3c5effb7abf83e532245aafa414b37a95c8a06ce1c6fd1a46805ea637ec720f5bda49f785e90a5d5064ca766376e5fcf3cc5bdb81c154e5ebfbbe401134501f45685e002031bd67da1a2213a3bf81efec684ac7549232c6049d9c1b3dfb72d0133f16707d3a2a756e37f9d2b7a336b8813c81e9f976ba8c53d89986747672b8bcef67375c59e603192ae07b403592219c6be5f18318539b11739ad9ca6b720ac94dc4024e6045a012aca06c245ce5d3a2919bf6c03de6af9733fe7a03df725ae574699247d1b49c9231cac1ba4b22b97aceccd0ee81de0e0cb9de4b655c0e49385d521e6ebb7c6a71ab09d4b2d9307ed4c67a9f7cfd1515097a22c54ce82097a6710743f57fd0357bbe4f32088865d1f71bf26e3d19bf0ddb6bb14c5d9f08370da74e9800433161d40c08e6ba7fadf9cbb5bbabb153bddca5bab702aca67202f924b0cbe81bf5a90fca1396e7320d59a3023808911f3f7b8efa30ba30347235f1feaca39bf6e5ffcc3ad8b36e13531c082c09a28476f4b6b62be9b5a2a3721a8ccf0467e18557d41ec9f274ea365cd4584a9b67192bdd113740d84218f0723c9cf7c6ee778997a5e2163924009639e517c5e00d04d715f8b25605a659b42e93ee3bbf8a0163393c8910113b1e6fbfb2526801079b0ab59883b7c15dcc5a72727ff0be143a7d3efc1f879aa4aafc213ee54f4e990571b59ebcf5db755efc7298f9f2e511120d634a7ccb3e7942761cd7459b9df5c05aa725a08b1fe0cd1f252a473d35944eee72c14532684f9431200d76554f64e6468297ee784515120d729637e2d7c680eaa4d292c94a9f3b4e36261e766e3c63684d196421738e1ea572a1847ec1150d39f207e18803e8f0bf5ad98fc1ca8c53ad92fda7fd61704fc32d15bf3c1f8a468bd99b3de99ae754b05a18d1011a64676c76393331006da4582e45b445fa0648d5b3353e8c8c53af73ba0e85c2b072419231b1cf3e5f8f00e31a76ac04dd6d2bf106fac1dcbc92d2e04ea5b48490e98ed19b5a3929e2adf4da13da09a539d1c01b66a746bc93a92b672d047a7a139b43b3efbf9bf1704cbea0488b08e1a1a96f986067092afbc9e01a6a10f0ee84a9feaa8cc14a5661c42fbe726e851a10f75f1e62c6ed608dba8b6c37a354377dfa4626a34481c32c2c8b3972c2dfa0e30657afe652fa0a43914d440c2d3c228f419d453432dd07bd547365723266699c6451c8ce53862ff22b99eb1f57871663df27239c411ffa1611131851d94912a0f6bb209cddadd77e3e6d54bef753afa84e396f64184a2ff08f04de56f6a25f0e5748df4f3a0e4c3558c8a8a55421e8b19c2452ccd2974d3e1f681963c96693277f8b9a5b28318b53d574beb6233364e1ed87638cd1e34f4c376a2558a08c4b7f752f4316a0e1278e46b70811022bc6f029b5d0df1f71d73321e72a473854fcabb54ff1afbed0675bca294e2287b2c3d1fb577f18d639e950a8b9604953c2914bdb8ca09817d43484011d8eee34f9062e6fb8263990c4d5caa93836723f1a4132629540e319d0a3a64a12351bdb71a502ab69d2c8313fedd06341e0727c531ff8d7bf6ed6a16c5639dd2127facc22db33d38c825de15e3406d2b4af4c4f6c83d413044948a5f0e14cbca7dc5f1a10f489e67c1bfba24378800ef1b54bce0f0875f3fb04f6f95677310eaa7f77da2c11beeaaf156ea92224449b57b80162d2d83e8462822db828543346cf444248bab39c8a2b08939e684b53d1a2d11508f9a0f80dcef9a159d190df9271e829ddd51fd713fa04e92cea969be7ba0672dfee815c67f5ad2bb179053db4f1cbd14a7494c5114f7c729e6ea389b3b37b2f8156807382bc8b13e84215129628798164b397d5b4a4da7b099bde90711ffd0d53fbfae42189b8b9501cb8792607ed1af68ed799281626fe5333b6ac64fbd170b460fa3c0fc3e0578778cd1be334194fea341fedc13b2ad31ae5a511e4573772e9f847a49ee0e6028a5a9162668c926e0137fdd2d716280cd9f6b29bd36bef512baef7bd8a6376bba5eee142572a08054803d2e19f841d1e6dd1b6c9c94c153b755687c9d8446c34eecb032f78d7195179c543458271d94f1b73e99eab8cc61ee3c5e7653b841aedd0e03521bec70d9c95ba4f89c57a0c99d9ca17e11899990f1b557d2301d3f79b4bc4b7f5f1853978cf6e0724c9946b5fd1d40f5adb427772ba22e0f944f74554ac243cbe65ce73a6a82ffdf4eddba7f29d43ecd799b8746f39dfe920c923168e5975f651ef8bbe0b0fe94e52fbc5ef502b791b3da2f6e07226f68e384a412a0f8ab22d51c7ca64519787235c708b12a73e0e0bc7154f0b302b20164abb505f77170aadca602da7bcc774f3a3e6cdae1e737bbf806b7c0e7236ab5c25b17fd37736abfce879496b4b1ff3c633924f3cfd911a70618da559727b339f64234e577eaa8f82c5a364dac84678824edc22dfb80dfcb78ac674da2bfe407cee80986b07b24947c9f04c0a6f8e1d545a54cd1ec1626ec070e4b83572ed15e2b1f3e4304fc7c6dd84ae1c5b7aa3d3ac2fe843403245a427e3bf063b0619f26d253e7085d0127c7a2834ef9ca9d121d8b68d77a876a99cd087ac46d93d3c27c26fb3bb9705e18aebb5627590d3adaa4e2ee354083857d77fdd9b912f72c222fcfd6063dc2a6850ce5ca0797a0ed89544d39b53a6771da9c406b7e34e7295ae542cce87fd4135cf82e48ae0dde0e6dcfeed9009b8e2c46c4e3f4804907215f1ee786d18e6517cd0815169485a1b5a0c49a1c6dfb10c3054eebe112a4c72a133bce85506bbee6b2ee28d543fa520ec42079ac3c49f432eb3c304ae4e2072736af04f199bf38bfe695f28d53dc2fff9e142c7546d8f19abc658ad2bbac1721e12ee603f2c671059a27b503b723e69f8b99610431f41e02b164e6cda55a41ccc80f15d65e4a24ada2e326e1bad26d26aa7e896378e2d6e1a1d9e8bfa69782dfa812e847b1f1aae413f9bf6afb755d2545c469b820bce6b3f7548424d0f5b1d51e65f6f52a89f91f31354e5f3445d3a53e981bb4e55df50ee32160c642ef772bc4857eefcd96102aebd4ee6b82b67e4a6d24bcc002c1d9a55b7319190297469708f1f22e41c4e09a636d795b5abd5e99f2dca175ca1d257694fc55c1ced4f5d02e8984110b4acd3e4f11dc6137d4a516f42d7b6f4b2e7d69f4d1174bf339d20662b657e0212378ee8906ac1bd785061cdf99800ef70c1c65e6c7d29c0f84572a2be50ee91dd61250c7cbd82ce5ac9778e884432b2297079258552b0cb7ad42d56dcc9de7e8943e53d0c2f37205803985b4a15bdb7924ca886585ad463fbe3727ba9d60d08553764fd87eae32d7e451a6d2b2633e86f01223c1a5538dc5e067204462427c2fd78c4c9396a07fdd1f77cc4140a338c0f4ad0bf5d2e7bedb09a72e6c9487f14f6bcc94672cbc793ec567f98b29b9e5412351feece511002cbdd6a09853723996916e4e0a5635f4df111c2970212129dc556e1291d027038964f020c15af86d703aa1726991eb1835ca66d66de3030117ae319e58349548a61bd72183b52715c59493f92de4cc248d55fa93dac3f7b073df856acf893796faa6a600ab9c087e36821316064d32ff32086b753d1710fe48a6d91807f84725b2b2b726091eb790bc6f7fb022c7d05934871b0f8c889c2c3d0325f7ba096d102982d2b5645db809fdbf837f993c30af46e70aec12cb1dadf1475763e487e0a6d2e720645f12329c1f16ba070a4c924e1c40bf736ec580ee8119ab2cbceef0cef02c10cf6f004d64d07854be7c1867edcd8e7a4ad2e393e9802055931947d00feb18d725b5f63bf0c30f4cdeb3d76d34870212054a919e19df24d29acb2decce613883c17c41ad929a19d6bcd108c0261fd1a38acfbf960e349cbcba181d765e7f686725d82ee6bf80f1cf79cb857ebb9365928b148495c26ff6c2ec0b6e01d5a712230f4229bc49fb0a52465d47e15364c74ddc80a85ddcfd08b815f5a842868363c726cdeb0395beea175ee897838d48037e0ae11a8aa39069c1a3153584d279dd472b0c1ecd3427c4f6af720f70184a19cb9383ac82193919f392d5c053e70fa7272e98e14f1335c0117985a99dc6e9e25066389b3cdfb1d16b47bcab525075eff72efeb298b565fcc6c31b6838107876fc7f2fd38d0417a0032e6598dd90d3ed37234bbb759c5bf8a7358eb0cc2e4acb9af049f3d8fea315edfa02b85bcedf23543292cecb3fa2b6abc2c7f88d8f7176546ad6a84c3cf97fce525005185a4ecc2455fe183b523bfe042ab603b7be1e46cc4495acda594891a47d7f7c06a781bd84facf90af5e256ea21024a37d60b3266e66e6b419a8974362d11007604a42060729d6b1bab3c63b9d0fed2d758bd2f6887815a7f065063cc9b3c58c04b7f477f720e8144c43122681d27d5f56705e854af572f8c2cab0b7411606d59c356bb1e72ad8bffaa1c87f018ecf0a352708af060ea9aaf417402c80b5202ff963eccad72795591f6c5d702f76cae8bb8637607da176df9dc6404b0822607e9b8d0c7a34248b353b001869be01e89e662a301ae5e26741c962914e37e0567382a7adc0c724221e7632b0abadc790c509218b092a706af6b4e6a65f6e169c37b2ffd636b2262021d21b5aa8b3504ecc4a4f1268e7e47f12bf00365a1e7106b2451122cd9720996fcecca10bac0c88d745fd917f423ac607d408231aafd6c201e1fa884bb724d7089f4ef601e39d2b121b2fdb582230aefea5409c14e5157cce8dfd36f32528cf44192e0881764f2ff516ecb0f7f23d6dde6e4d9ef2a9611446ddc04ac3472eae829a8f73456429455d787f31877051357f829b0c8ade10f76bdc2ad32266ef3d1fb64fd70c4df1d4589ad02b17998c8f1585267448b828d43662f7e14f16dd850638e45cba80cd763ae73037f6cd4d2a44f3cdc450141ec85a42a303e2872b0f4ed8600cf7508cce1bf7dd5012f157dfb9173d62e9765d0976053d1b6c41c9ae5cc6585c4148cf6090d0ce726f5aef5c419e9f22f3eb509932e0e8b429320ce4b2d8ed13ab1fb881cc6246c9bed96e858850b34818f5a03fd2e9ceebb7e7278423cbede62df648e437a22f60896f8770adcd025e50407fca25f8a6b98f71854057c0705bfb369d650450fae5b5217a23d1f4028a78ff6c3132a84ae3fbc72c00459a3f4342cc8f130bccba1f901b53ed143cae90bed256beaffcc2060927239c3aad2dea2d2c7e10b678ff5571a094ac331658ff190caf7257fe554b9ba3c9fc4593dc5ced63f1837798cea7b1be0878dad600f3db6433b31eff52966c872b17996a510cd814f3b48a01ea2bc7808c71c8136021ce1fe1171069ec5bf3f72b127da364bd5f150cd4d6552d749a832784c9c06299652fc054a6c5ead68af00199036387a852f63a591efb19cb945007b52d1e13ed794b20c06c92d5bbb61725700809a820058189f7c1b5cccfa5f73516d8c1cb75fff478f8ed890002e3941da2f9d82d5adf5b1558d7d816d91092b78cc5c825e531d92dc8bcfb1783f2572e681198f412a3cf2c6038bcbe24bfb3a24c0c34ec21bc7f955ba49c95de3b272e84b2debe8188d55f8f2baef4d9810c04da2778f9533ed2d2025551ae59401725c149334911dbe57b0a469ae244468e73ceb77d57b4bf7562b41b13c75bd4c722615c47de5ac858bd6548e379b5cf751b256ea5a8870d487a8aeff7d5bf77172411e00bb5b99fcf49c3db25568e1c5a575e070729d47d06bb2dcd65ed7d2d35afd2daef71116170379153b6495f49980da379950c4d2b34322b3646841aff53421c76a2a97ad559c51248130e557ee1533bc82f7a4f52ea17f6bc6ff2822994b523e176edaa09576111a442573dcdfa6d1d53d02d25ee96e8a370aac20a5af6e611dd66e6c984aab0929c4f7f35b463c88a8f41621bc65301f7a8ab7d7aaa77263285cb73f7e7c81f28901a6abed736e0f3e17e6e79310a558b176c79c79a57254baed7aeae9810bb72d44aebecf6140be2bc7844c45be4aebef72052374117292e61422532b4a533e47244c0c8fb2fc23769b3b72e511c03f8868b4d3bb7c72bb55edde331fc1b99e48fefa04c001dbd294e2476b064565f5841bde1b3d7652f861585e239bc28eb8bf91d5143316c750cbfd207aeee66df9862ad773e6cf08f520346545783b9237a99bd80792ed0642b0f6e103a4d22cfb7d19ba7d522d72a618b85e3c79f578086c70ed2f6eb0043bd871561207a855a24a48ec77511d693c769fbf696e0b97eba869a621a6c808f4e89bde07de901057f124b4089cf172bbeb664e49e59f93564fc9b40fbafb59bdd103e6916346508edb429444aa492437d40deec7c2f56e1b488161279236e14c03c9031456f38ee95d13d637c3a07212f3d8c089878d1bf96577b0749ef20fda521f731184138937925af56be15643754aa2955ac88f72548a9fbc90804050763661ccab7a9e6145615a830a780a72f0ec0bcefc24d3b50e5ce15eb995e3cddbf5a07952c3727e4eafe61b4ccebb720484210a859e284d534db19ec69d565b0f3c71bfecf893f9ec4b9d8c42457172638879e267a06d340f650b37f5316f8fa6407f2964874232ae3c1af2e8779572c474040678a34bdffe1d9ab5d601cb9ea4857241e9157f5b56c0487489b2d97295a02fb6cfad8ee17d9db5e70f23e9637703989b534a509c0cbf025a6e6ce6004017c2839175ccf517cdac3f9f604ebddc2c491dc1ca165950a6b6684e9b2f7265f05668d4fd378f0e39db0dafdded3779ace5068168003312428de370ee7672bbd9cdb475456a93a45a981facee9f860e4831a6f257eda6663500cf725f095d00a5ed176133b5303f3af7a59e9fe5037f9133a7467fe970888f947c7f2b440ff46647c96037115d6fc332a0d8f01460d835f5081418aad9f2b84727f9709b7219748067aab4e4140e6efbaea361b49a8b37959e8f9ba4cb53c6691d1de411094ec60f9d0bc39bbc5a6f6d277e1ece28d28e295da3e4987c4d15f20681944d721c741b4860e0f99623137231d45eed66e7ba81c0b7ad364313a02fa8055c1b1cbf1943f39c1eca7de070d9d07fcb79eda33ed716d49afd4fb763f48cf6900472a2e950b445e7679f687dfe70fd9589c7b09778e4c8c7361898c84ce89db76072eb7f3f2583beee33dd774a20a68b16d113f86b9b83471dca23c4ffdb2024ca59bcbaaf3a535b977c1944d3b14880182cfec8b94c1472feef6695e8b290be9515d5a4d0886321ded7e96c7c70a8b534a7a2bba79ead7d793557b367afc9bf5372c04181c8adf43873c54cc6fe180a2d1065f18fbadfcfd1f53ba72ca1e33f97725d4ef49dd96467cfee06a7d3eeab7022afb1d79c556a7ae8f68be5ce20f9627203b8104315ba0147c545c8ea78c3f91cab2052becdb19f3effa0cfdb7c6fa9727812de18db89c8832a3bf4ccbc777b64bf0fd7ef862853520ceb5a0c665f5c729aecdee4412bb56ce076febaeb2ce2148479d45e8bf0b850230fda37f78f79634422b3ccbb040b7a33e17bbe8fa0c2696ff96b56557bdce1843de989cbe9bd35da32e1d1bf23b5db868161d195dcde9f0f8f420e224371d7b95b36dadbfdba722c8d3ec0caa9ca3e2cadb217f703d975ed73ef069c604612f1cfeb536aa5d172222b4ff52bf8cb7baaeb55fff2b98c7676bf5baaaa8230c1d101d711d90cb524915b3b96f4859651bb67155c1819ecf2835feb776197b928ecd0ac9958123d1eefd77595b18ffd1ddaa6f989ac0176d91ef4d77aa6ef16b3814ca2514bf9f672214c1bc9357084f8aee530f37a34b17d5cdb2fcfa6b09404e95a27a6f022f136ae2a6af8345ff5c9334c36b5a29fbd33efdf2c30a38aaa385ce37d4906c1537217c01034c5157ed96e0a369ec8151964eab9be38d18db1eb43356ef9af6e3172572c7f8dd76c6afebcd67708835c79e5eaff060d7df9cff534c47c779b360a72c648ec9c4b7e4270dfa879422c3caa99924892d46fded1320f2515e720802b315cfb166a145b74bf274423dc57575cd008df8ca921b0ced0c9a9c47f671c3050365010a2523bead388e5a17c78170cd737afcc08cac45192810dfc6c8062237267bd73d6e5d2edd4d79ab8ca6cf7e9d182bba26fe5c5a9a05fc547c925d17e727a65c1ef6000a4bfbd013eddca77d3451a65e8f7c0d08765f9aac0320dd5f572b83e96fac5a7da5489754f4f3571dae0944dde65d44a705bc1de5aae28017724006f1c9235948a59ef0be572c871c4cd205591ec441522dfe0a9b442ce161a43a4f6f57725f1fb6d124c2fc93954c9fa85335187bf0838d0989e736ee28a6c7263414b6b7484d42248af81d0d2f08fe3f02fd3a9066deda8468eeecdc168383c73169faba9b4fdf4bfad31a1bb2f9875087157a1138345e12e6a2ee3d0ff6a726ce5a37b305843032a184cb005bf82db30543174a5d745b273664c095deede7246ce5e4d186917d88d111d6f5ec7f6d849c2a80360268221447e05918901be70178060562006cb7cf7fa3b2583e7cc663e5bf0b6760daeb6fd3664d97c28b172ac293b888ea3acd7bcd815c4fc4ef51aa5fc1ccb337f7fc73fe9ca22eb511b723e400d2c2775ca5df5d87bb2d5639378a6c7753749aca5618751d25d8174875d8db7ff1b976be7e9bfeaded834419b1a5a03c7b33f01a05dad0b8ca083039572e49b6d2a0b3d8b0a95072d0cdb30f56be9d8df6ab747d61e13583848165be1728f3400a51a6f88d713b9fbd6a3d0661649efc8fad7f6690dfec35537c7fd477210aef0a85997c109b678c65850a7e9fb61fbf0c0081e417df847521e0ea89b72f9e7ecc604cc2bfaf786d994c0ea14dbddee75cbbf74cb1e6d93ae0e8d2e19460e66d7e91f8026c416fe5ce8adc15358da8be01b84f68aae1cf21e87349be57019cf3c8b82acd3382bb8ef86d23941922aedf87c689b23096ce4a28adefc6c6bf54dffd44503f04408e3962ac55c7c730c5a9dc5f477317d50c71e7a5a08731b239c7c55813e3b1749670e12a1d755694fe0601530bd7d224eb0bce0fade0372598084fddae4c379517ae3f3a509889a0db545dd7100bb306cb410d0c8a96f72b96f593ef0447c0687769d6d43ed212360120fb2176cfdedf26dcbdccccfe02262a106fc82fbb612a3b237eaba498716ad7d3c8a98b6db2cad9df7761c57624a58cd6082d56f91fcee3b8185215f19528f5a66291491c9e1b7d5e6a9e519e107c2aee666281182eb3c662278e52912f0b60e572f51a7a500ab1cb58fc7ebb02c054040aac69bb55fc13684d039439c1d61a3987d665a99468c52385c11058259849630bfa45b36cebbdcaa97147a6b8f2f976c46bbec983a252d7d218ac6e00aee45c1876068bc2e4bc7fbb196981f9190e1f348589d970911f4ca962de55828241a7cb0c8538bae262bc52ffe94ebd992d73217517427a616736b917cc87c72cca3e2e8411ade01a2810dc02ce5299c871b57c5390378e489cc9bea80b42172619ee93b08d75a29b732f4ed7d0fdf8970cae84ce226db4a572b6fade5d8916e9cd9a41c780627b844603ec0856b28b23e5e7b7ce148b3f90d21be89a888d26fbe5b77208a02359bff4cb17210946470e4645120bc20534909cfe7aff7da4c72feb3bbd465db9980a89a800a11e2e0bb62c2975eccc7c6c5f002207fa6e87a7259a2681e17440fb2ddc4b5ddcc66cce483d34eac3cc35f4a0472420c5b7776721901fc5431a77071adee580dd7989667e3bd4b4ca5f58e4bccb6842462ef7c72943ec211fb2ed44ff22fedf0b3a75abb1e45a378aac09870dfb7fac0f78cce33419d4edf1921bc1dad95b606911affe06bb405d594057b29a8434a27d771b472cf04927659a125c7d8e3b5f684e51ca6dd4d56afcf4ca6e5a525ab81ba8eb651db73fc0a953bbcf60b03fa17d0fa4b4c57dbfd6542492bd816f54c6eb81bd7727b2a84ab882e1ff8fce355fc3ad1cfcdaaab6ac2f622e2c455334592720e033c65504dcf78ae0c248545c6b74da81a76fd7b21202b97dacd23224a3980d78c727270c80c6bf0c9646489614f643b9c7674feb6008ee4ff7281e77a76d8e0f97270ff5538adbdbe8502afef2e3bac080685af7af565c8410c06d08c90841d326f0b2d6087e19709f8b34827e9d235f0523b622c8b1e27e6198f9199609dbafc72c643a49ce52f1807e68cf0f3a02e83ec191e254a23a7569928baaec2e0120f64165e26de0c8794d25a73cb61fff5151558204901a9a3af31177b9e906a440e585d140c228c37da2f7073af410f1fbb549bfc62056fde0f32c6f4a7feca53057284d8ba8ea74f2ab52682422c4dd3b2fcbb0a44ed56992542407270a249ee9f12aa9be01d6bf97a7bcb462a896a34a82bdd5e153fef2bba7b1cebc3f0f7e06d1998b897322abd1c253798cfd8b4c71e649134b6738ecd9098650be51fd30cd7727fdb13cd15291419082e87a15b2cd543d09bb5f77b8ae2d6ca2f88043271132e59ce1ad958cb7c5affbdcaa2d595da769b3046b69c6fc6c606bfab4e7b9c907278bd476f58da1bee7d6cd5caa1a3795b9cb0c6b69d2fd9b1ec75ddde180cb5728aed41902564e28beb549b37feccaff4c6a072a4e3a2e4fd68c0a6176c0a927233dc03698d3d035849c19326b7271cf42509c4698de5636d6f01dddcbeb3600a24eb6f9e30149cd7ce73a2d031012419da644f2e13682b4b571386426e2aee68c795e4b6dee1aba6ffadfaf1ae7db7a7bda942c2f850f9a4b6bac0adac687a72342abacf032d32c7fd8aeaf4dca81ef36f25e78c948f5322cf7bd69722868a679c1cc6f5a5d766b1c9a5fbe98006b1476e505d3ec57fe3326705b41122dda272c3ca67b8b0c55154ebb5cd63ea813130c6f690bb52a4a7cbf547d1183992f0727897e7fbd57aeb2da1c388a7c799212fb5c0e3a8424a41fc282549233d3e0e1e7c9778382a70bea0b4451561868036417468bf4ec6577ec82605d5e5ecf4d972c47a71ec0c1ac88e62e2270877f48b7eeaade26325ca9854f32e68fb5712657238a412e5bf0feb751a0cee497160d527978dfd56ffd8531a29a24e4bc266c0497bf27ef1972cb10157c94c01a67a7679fc46cd12d81a01e9e1b202a8df958272bf9b638e2ef1208ea9831fbb45c6592627ec2e0b2f348c8daf9622fb9dc28f7280c344a7cca46d2a61210fa4f87e238ce45d630f0ee98caadf0b7a91ab91e072e04016bcd4223cb78d1a208cb8108fe9e6032e5627bc5323224016cf941e28239fd2b420067a39e250c36ae612ca3e800fa38142aa58206f3e557d9fac114901b249bbe1bda5e92a89f1ffed138cc52ad35b083c5c57fadf8f9285a0e9d82b3ddfb197d6b855b018d86c1b31309399820d1a73f9f01c21dc59f6257f86a5024f5b1826482ddfa9bc4582ce07c78e34aff6a447136569871f40a02efaa9d0f97173e3855aa17317207a6e6d9b246524c08223c952e196dbc88dcb85b3a05ae029b4839a6b5865171500f7018a9b0b3be3277db31aa0f14a77f128ec26a1492272b6933078027032e63c1e658d90af11b2b1eb20eb964c78f0ae0dcd9022ba14720dbec0241ed55ac14df7efe34f668a65b27529ed1138be075a7e0e8847379f23befc36af6e1631a7e44b6341decb5d0b73d0c62845397176bcf068732ae0c144e2f24ddad7d873f3db6039aa2566dd3b104544fe81f36aaeff039a094f1a5372da5217b52410c8c45ae2fcf1eb9d6cd71177ce46d31bea88628495d10aed4272c7f04c8369f691748218df2039bfc661c214f7f110af1e8440c9186f59e2b404cb228cf48b3980d03bbe8f7ad5941df4e28a7f8c9db3b03a300209b7fc1ae70c34a2d829ad427618456a9e7201866bc49b7603f7c4e7b4d84881d024e1d93472918bfd7835524030cfe061f0dc1cd7b44b309b92aa9c64c60bcf9068a1cb0a41c939b03d44df90824143479a4c6718c657a9eadb8de83261dc4e2f5cbab18d7280975f32dcddfd1a47ba0d46e537d419521937d9693469079d29358a4d220b4258a4a5f0c6ccbd6d9835928ffc8cf57431330a2e7b9e50b03a24d22290d65d04fc78b79cb0e7ef25899592200ae45a9e5c2a72e8467397f1e693ff74b783f707b8ddf3cabddb3c7f1fa12c7b4b917381c8329dcef3a50e2e363a436c39a114727e29d4e761e4f3efb39a0d8631dda0906ae40eb77bd0d20002e8a08fc480f6726e6454e0d7e77c38c506daa226ec4460377816562beb5b91080c786bc4df2b7228aa9bc857e31ae49ed1a4a4d9f9dc48ba97c14a98eb7887524c6f9d38af817241f49c5ca1a1ed1cec3e35a3834b3189354ae8d8a501aae7d499d93532c1c51df079fd3231ff8e1a863c86759d7d322919cf9ffee6910b7fef27ef5e075e6f72bc8b6e2e3ddf160e1ab9ddd12b5f6184f318e616b514f0ad0e69c6d3c115f866b4b73d4295eabd7a35a69983b1a73c5fffdb625979bae3c75f3720d89b03f472b5d30cc5f75ee9496d3027f53ffdf7c9b082c8cf5d12a8191e70269bd7dd5272e7ff4d9a2b6374dad775ead61d1f9cc3e8a80f8e8dbd4d147de650c7e25a3e2f856c83b8adbfea41adbeb3ef5f01de924949aa2fdf5e0984d54ae3a5213c3561c6e7bc82ae3aa8d2880c2a8abd6e748d138e1158d28954032c09ba72a12778726a493f1bd01a4cb711079f4486b96d0edd5e1bdd43949ff7ceb912f6bf3a6270ebe944e66fd6ddb38f8c581b4297ccc99e52ed93dfce6ddc4ba61a9e996e1472e38382242729dc585047db9179a7c0e477c43d0131664be6919cb218b1098c096fa31563fe8f38f8f7a4636fc98b8a3eb3cabf950e54870cce4d175d5b89171186b69761df1aa39253b7a893dfd816cd05d4e723ddea540071c773ac18298472c83bee97f2e994db9548206993907bebb7a28d6448ab8a785b7c92852a355119df13a665ecb299349d68f7b59bee22b8039b6a65c330437bb861c4e9500c7372d67f31e4508f27a2ab91a608a717293bf417e9944b463f41d2a996116969597282963a28d8bfcd23ab8e7b9adcec2ce2907955a90d4b2972ac895cc7e5b9fc72cd22e81e6c5bf2394d6237e0dc461b0e02018565cf22f2c319e04d2d4e8871720a89ae9cc06e74d212a396079c31801dc9c1dbc7aaa9c8b922f473781de4b272b6b33c75c5a544d023e4c1656b103e30fc9fac4da1b6c2b59cd77273880bfa328cb5eff4947428f6d10c40ea67ecc44c1647bc0a6328a45741b747d92ae20672e131de6c0718068bca72c2342756bd794eb36aa85068382e19c73eada0e76c723ef62442d2479274f5934bdda12ea0c6b02b865554f627eb8c21ae76fca0ed727234c000c9b284f3f2df2b4b89911f71396351a91508a91d8ef355297b84e02b869e67110a124419ec485b2c1598f9955492410dd7678007868902256c5f4f3c05e6e4723e604b96a869e7ce3f69bf856b9357cc31d30e38d962dd385f28562019017ef98ec47823027f57662ecca7022c57b6544e7285846c3d377874cbc672accce70294719b4c12ddbe7ac348260203ccb25b01e52d6919695ceac238b95565836450168876885becc60c8eaff8b393bee2cc3b25bf04239c1ee232ecd0520247d2b2f982c44caa0c80264fcf72e7bdca9b78d6edb4e6d2b0d0ce4522cb72b021ec1354e7cb02375efca530bcc75d846135bbdd36cf18f063a4c85d7d07084a158f07166d4bd9f7442b8b79584e27dc986bcaf7c42c275a635ea88731187231137f98cf6ece762a95e45453870a27e123dfd72ea757b68c73ed58a1484129c08225cf74d2f964df48841c57289043140bb93372c4076000df92a14be718725a1ca8d28fd8a3bbfb87beffe1f35bf57cd75345e979e66af0d19745eef9a444101c257903172222bd6a12e634ce5f95c75d40e901397f98ef075352bf88f71d008301a542d6c6940e0041a2f4a95d120e67af45ee9293ac7d55409c0f0a91420eb3d60845c1083b18fe4732e6c874466baff6bc839f033b3c164f27b4515d1684de7c6aff9c0e960bdf3ac6f47bbc18ec4a7f859b99ebf309af20ff60144946dfca0feae861929322a2533d3b563a71180e5a09d6d3725f54181f42ebcf562ea0343d3aa75e853e3c64c3d130f2de43e5cb995d06809290c71f4358b5931e3336e8ea2b8e88ac53f6a73a23bf8af3c72baac612dcabc23920e27ba6d3d6675e3ca170c98473ade0ee1a91e756fbb5c6ab95888f50aa95ba379e5915702c887224d4d491d204cb8ae6fe8aa05ec8cf6f4adfc5d6ed02c3d38f7fa38e926e64728323856192b8f3ff5d950e76de35ff37f55456267cc50104924e24621f9a2b505055bbcab5602b750f2a6081b0a5c4d92b69406755491aaf3ebec150a3338772ff04b9e5d6573788dbd879b357c7cb0355c2a8a65cce77d9fbf92b061fc2e209e034e417069a273403ef76be39651b09d78a4ea51a385fa81a921fa0eb1d8b720e8236b09987d1765f131e72d9f319701d230cabf45ea362ce2ca3f7c73b131e1b16971ec6d17a7f04daea8126a60e0230fd0fe26f2d71d65f67cb6fc83ea70498fd58995e4a47a64fff4ad7b7a3d3b1d422e6a85ea7210594b877f76b705c72ba7db96310e8125e21f80a07b4c5ef3f14865f9bfd1e96cdd2f2920febcedf0d9b9250e5bab240ae64fbe1ce60e31da962b27ae73d0ef9065e71193135fe40725a502df70dde0faffdab1cb1918f11604b94e1fbd5ee172ae65f18e4faf51572d26994e97c25ef6fc612b24f8b9631de71a682a33acfe0da9f44c597aeafe7729eab2b9875542326bf5cd0317f28816917ba51e9c17ff92e7e4cc058e213a472eaecbeb4ce3163322818a7e0dc4ddad5ec08650d54b0441b8f2ffa4c10e3775d27bf3c0d879f05632346a7f3684e27408a27b09555b063147e4cea7fd2e5ba0861a3c5213df0412535448c6abf311f4c4da528057a146260a5f0ab7c7b63a7336868f8b25c781a5743f7fb35c943f673814ea47583812d9d043f4485ac925c3a3f23d8192853d13b98430438c641aca302ebd460b7f05a1fba7442b41c1fe472bd63b45ced091df22e5569e359ab293f015208876aada67ab196d9fb13c31d72475d0d04b64bfd10064f99bb8f62905d30943a0078de67126acfbb9cbca0eb0ff8bce07cf0e8cd0892a8f1e1597eb48ecbc2c9953dc6938336a1de7fc589ac72f22dc4e0d4b1b584b34679b106bcc5938581ff9e7d2d64265097fe3054e9f9725cee9b4b4a5ce8b2cc6d3717f4ff6cefae7ebf36875e3e2b95bc285b8f9790727f515fc5385600aef367941b250fb3c82fb2418af6258b74b5291c7b946fc272cd7db498ace6abad9fd2d45dcc3c9cb420ca2828ad9ba15a9a1d06682559f01b785c35eb8dd48b034434ba96edaaa05bd565f60c3a4b5547fc4491cf17aaa37229f66322342db45223366dfc0fc8565fcfc6f0cd837cc3c5443c27bb49d97e5f360ce20e84a16ecbcbc056d8350d6dc0bd0886a2ea46bf203a15a2992ab46d193afe827613be4225fbedb35de109117b219c67c190b1081dea2edb0ab6c08872983b3aac1a78cb4450c34468f031ca0f8fedc94853b199fb5977d7d232f43972d057e719f986252c10e898db8a678f873c19fbe82188b948f6c64ceeb72fca721c820988029e9eab58a47abda923cf10b91252e05e0a4905c20355c25be30172771a0a9fd50807150f91ec116576627e200985e667312dfb87f2bf44cd6abf7293eacbcdd930b276a90d44a993060ebec23e0143ffb473489dbcc2891b56c0223fb71b2f75e174a55e1e9887f91383d3584ff5aebff413214ae97216ada1c372b0173cb6e5ddb61e622b79b9f65e43122f41fe40b95d666b537c152075fceb18558d7d76ac0fa3c3745e3b787e1d20eab7c5ebcbe4c0869579b29b58921e6a7259e838777b81d2b54a16b7adb2f98371186f903ad6495884ade05fa3fe429872b43f9fe8f4177b884c7018d9bec68ab39074952ed7795641f9256543a37da572a031c2641042ca181128fe18446e064d2f2a7cc37a5ec183fde53498dea4922616cd3d1a0612cdf9e7de503ea0bcecd97c5c76faf650586bfe3202d06a2ec8725fd98520a8669fbb1f3647d13d1f0433e10ff2144ea8bc24da1337a5dcaf1927cf5c52716166abbbfe792a92dbf89558cb9ff3a5a71860e99386cb92b88d0a48e6d1651ba47038f2f7ca061d8eac5eafde57c909681fc6ff03c35646d45b2f090bc7508c7ff2415082ba9d8b0d5d7328d314eba13032e8b9a1db7f6c9261745d6727286c11271117e75221887d527ae1f2d61e79c77a9441f51250a8153f2072c43ec4ad23937e9f0a25a47d35ca6918f84278d975e9e86b9da0f0cc303a75728bd1561dfa56e0a8f5c4b8cd832d56a844a2a715178ac6837afd7f732a7f9b1d5d3b9a5af2704ad9f490611d9851d4fff5ea0f5161acbcd64560193fcfdc6c72f812b967a700c45c11b00c7fb004c3d43cba3530e07e03c60817813b3c43497296d0a5ab5538616635f44ea62544f91cad209440b251b9812490f7d6f86bbc720171e35273e4669b24bcb5fdf27e1bf731f91976141e40aad6538611d1e3ca728213fe22eb4020b69811e857d7bda1fc24ee69da035099c75502405976b1e172c5cfbf0557b1e7a82e962ae8748ef058c449384dc1325b994d0f300e997a5e41e0233fb06e2ef2a53777a72eea8a54ecc7fe825ff64ac935f3f2964643c20b0deb16835b7830b2aa000f3e33b31e7982a5e11444cd7f697c165465b175b0054ea472cd699ed8878efe5599aea46edb8bb5be94da9a39de02dde814ebb379e272485f9e1ab7889626e820e00d71f9ad2b1575087bcbc0c49884af21422686f418d7243e5ecb3de8cb7ef7618058f0810dc9c622e73c67bc7e2a3c6edcfa76b369cfd92487eb5f1d533629cc24a682a1c22510c7001f8fec369f4187d20fa71a3e0c3d95f3e2e369527e9f200e82891b30374e16f643a80ed3993a2f0a12acbf723d87481d349b05ed0ad1f835bd00c1483e9d6410e36b1d753fd3e0c1f68d80729774c1f51f8b73c8f5ecf192d182c553abc2695d7a2b307df7dc7dc5ca53167243fdb92c307ea54c77dd46a25ddb62d0cf2f5b3d6df2366778c45ac968080272a9e496e9af72c6375af038292106d0be451e15936b9230e5782bf0405f7b9c72e6c54c2e3dd0400669f94571557c7aaa2d40c922f40edaf36cb8ccfae656793da311bf674da74f67a57aaab4371d26b5f97b87db93cc35546a34f9be6cff3c72d61984bcc983bfc4583958a639ad792bfe195a73e0cd978a4ee5600b7cbf3972fd05e58237b602d1e5a0313d13927c4de3ac8484910a0ae2a10ba8e1944549727f30153088f73de29487cfe8e1604ec25feb714829fd6bb4e0ab94e2d2e1d172c61518a9c3115682a1b0ffb95eefefa8e0a7d5dfe38020f827f509c81d85ef336c7ec980b899b74702560ccdb9f9f539117b08661f2b08723c034139b637f67242a008d43133cbe6e7d539d6622b46edf78e8c6ad639cec088e41558583e4523cb5373d82e96f786e39ceb311368116f3e23c243c8519432c4ff5d2bfe44fb72754d313b8a5f87052a4e5f638e1f431643d0bbe2e9974a61fadc25a6617ba072786c2be268b2c0b7423f1ddd1687e7535513f8a57b8fa09aafb178d53fdb2220384c6a07ff434f5eddb80535f31a3958d5be0a9a4ac12fe0009858ad780b1272674edbe63f969eaefc2928b066cd6750946d59c5a7798ad40b9b356ab6d09f720f69559de7a8937e336a17c4ca8bcf2b5cc5c70ca2c32b635c2dc81c15177e5ae1e80bb2e87920ef5b2c04024438cc72222d856c07562fec66660d24865ead729534d6a9944ac1060812835e775626938d126817bde636158472bee444fb9472e9440efaa96dd9d666e27f64bf84ea9cebc34f72b5d6d7947a902f71c72ef472d6acafe8c810cd974146fe8ec1f9423914e2d56969cc58b10f857d6a2cbb484645bfbdb2921cc3c921f077a12209212d3f019d22ac8b1703058b230db14143727ae1220b9c7668da8307f480e6a7db79e5331e168e184844b0627363d1ea1472d381c41359a6d14382253321be5bcaf233314481ff10f456f26ff388725997568102b937c8e7d0b7c198078198ed420be9c3f3879d604e75a702626f00e0617214c84399c9b44aae54c4921a10edb05a32e048fbb44ce809104c9ee3edfbf90b15b752675150c59aeea614cc6635fbe15479682917bb9c15504972769cfe347280349f2149e6a41e55d988b7983a7431e8a841952d3fe15eaace41a554af43722052cf3e6f07edfe6b356210835319bf927808150ed72a1d74668beb9eaca6728fdf36cc70525c6636670894e4c48e3f55b90242b39b32d9b365576e16fc9f7238421cba6a155386076f5de33c4428bb88f2d00c7b44a1fa44df00de3c802b2c1c0d3f83352c9f7ce8e5f37726e7a2cd2d7d67e3ab3143fa17e6e4303be8c52557edc34ff5b7435cb834aa747526805d43c66770f60c617d2628c5ea1579d23270b6ba0d08df922d63bae0d99a706c412561218ea01d127e5e626ccef3035e401e0c0badc5c660a54a048686130e7dcd581aa428dd2909d6a58d28d13ecebb722891eccd697a49b2e035a71ad6f396eb63f8660ce02ba87d79334003b28df913dc20d9d35e73cad11c617fb3c92b4e0263928b6c13dc47c9c6f22fc55a739050f9cbb556a2a4bdb9651991cc2bd323c0392abbbc8eb97c9faf45d2afd9caff7208c5ec5df76352a35814cce00db343f7f371a63e22708f39df5bedeeb400ab728b953d3af7d20520f9232004717cedfab78a8fac47659d961c652c819af49e10a8085559d61a4d073e863bbb0493fab58c1c93a370f132b0b2f70e29eccc6e7204c25460df0a85d497e39be7319ad87c39ed799e854bb24025af1704d13f615e359ea721a8389b337f64cb3ac348230c3ec7178409f7a379d7f8549e8a0aa70e5cea4fe0ee8a73d4a1c163dc874a75330612cc738cff70395762d0bd61d9fa728d5dadd417cdb85e7bca15244f4e610bad58a473b67a16adadc0d8325b08cb21d814bc00e2df6673634e47816fbc05ed2588da666c98085f67cebdc8e682ad5d3a4431fb0c51bf0c63698a9ff92a7c5dc384e369e23039aa86975a7e1b716d72944e621ee16256ca7af6482f00c1ed76ea737921d8faa3701324d804c9812572f19eb6dbaa6a434e70c2b832089661b7323b24daabc27f80317dbc1673072872822c20a9bf86bcf9730be57a324366c47ab5cd1f31721d938d568a4a59a3964a83fecf642e3bbccc7289687194d0e001f5b12f2479cd70fa5a6a006e155bec72368053d65beb99996616c52a92ee0d1a82a2b6d45204d5910a875d8d90d283725fab15291392f545fa768816c0f9e8741de1e867200544028a844a3e0e744a72ef2fd94af92620fff738d7b23e86fbb39a4b0d19d9dced96bb593da42d9282722984e85f133b6b887210936c7f5c1f8eaa4b08507dc4ce25ac39d4e21bf0086b6c9944cdf3fc0abfa7284f4e5d3027f83dd87b6d14bd05e1e5fe6469cf6e3b72e45cd147b4e4441eef74a51ae8f44442669d0d560a1ab75796fc7cf2092f0d72d5684b62c52cd1060172a4f243a4400634544697a3ee7d98db94b812fd63cd1c33a8f4d0149e437f3752462b3f52885cfa464ab2d2edda573e7d1f4f4e88a572282933881c55ef91c2038f3f5b85b40c95d6a4595a347bf68c3cdca16bcfab723cf9a8c6bf4c6b85943b32a40ec55fb202682aa411e8d0c75450a8f59fc72b390becc316bf551aff7c66e69c722413890c337ed9202544ede935c97cbd67bc728a05a7429de49bf07f210b53243672cf4b155dee6ea4b583c6695a186d82a372fd007faebd4c5b1b3c98de1f33b4370de6508d9ef21729817f1c3cc44c7c7512c2bdea88ae483512e0549a3b2c78b6c9a76f49cf7c4857c301bde445617a0372eb12b2e6ec1558e14a593e16e441cbdae0490196eaa50ad3cfcffeb95a45be72d9965b07fae2dffb2a60263f9196a66364f8c20865a717555cef3aea0219f472d501cbd228b34c0b4204f6154cf557bbc903b5db532b73609311929f83b9d07239051fd541903b4174b10d5c13c12fa4ff78c7a36d60a2b5cb366e2a303cc85281a068db490bcf291a804f7a40b8d85707a98834c3fba14524c60cf33b8d2c3552a637728781893edafded61e0a64cb7da746f693d04b862c55de19d01bbc46b4ffdb49ada1c8a66bbfff889abdcb90189824f653321364e9a367aa551dff072d4cb4cc979f49565cf624b2a9a001187a2218974c0b598fdf067d0cf7cc1ce271d3d98d89600404090144afe9542048810b8cc61a5a5bda6f23ada58fae944447b0fa9297f5360fdfca045c1538bbe86668cb8c7dd8035ae0874b0f0bc555d645f624bfb628f564e20ab639221bb76876660a3c2be1cf5806f90361978234b72b77e399992ba14d8f3ea77014c700fd5b0491df205cd385d44d230f9d64a25467eda4cff54e0f2f2ede2fc99e9a41546eb0618743614dcd94458d091262b2972ad6427ec25b8ee74f9a87322a6e64f83f87fd15e221265e66ce1313e8d0a1834bd48c0f627cd1093cb2174089b3733ec4b2dae0d7cc8c86b95f36ad057f2ba72aab68ed4cec706b40896a53168cb37435f26ac88a0857743b8eb57e8e95720723d4386c8c61ec47442b5ac6fb8ca2053e7f1d441a4c4b7aa7a60fa92c52735724aa09646246b304e134ed641acb9ddccba88c857dcce9c71a34bb59ce949e83bcfc9f000880cebe886e8c9541127ed148f2f6a92e8f952b71bb7c44e7e24b40177c6b9ab624c4ca52778ebe9bb1c8f6f696f580331e1edf7c297f7faf2b9cc725d31403c74d58849b66271be67ff870c49b69c088757d85b246d2ed2f9656c15f3d35ec17e8dfe906ed842de789a1c75c781647db8c0b7d2af177abba2d73035094c93e361c6a65d431e24d856870311a3b15e2075ef3da2e738fd30e974d272eb65d9c40c9cae08c1e321f877fc1ea639b097695de70b7b04500940656dff6da13c8a16bea24df7e4b8ffd0127cda3ff848ebd24b0dd83dd3fafda42162bc2100fd70717cd79ab49c93a72f62dde663d8582e7ec6e83074c04c56243fe9a072af4a0c4aeb3f150b3583399633daa0e2bc52a6ea100f7ca0cfc924b6c05ee97230553bbcccd8170a12cfd6de7b2600d67e9f4a22fbe2e850fb766dd2bc543072893b7477cecdb21280bf7c9421bd7d937d1cd13f499d3fb182e225cd206e2e7228017d1c7ed47b487639fa3436128e9bdaac515fd1f529b2d99efb4e5d9c6672ebdb3a5073c9470d53d4ab49eb36ff8989633c2b117943ec198ec14c6613ae72f5564ba9efa2322f5fa327ff0c531d434652cd7cd4ca7533f98ad6f1a85dce7208416826629b03b95050db8cfe9395b13424f0c9dcee9b9824466a9f323bfe722885541117de7c5d303809f96120c4c421e02ec0b557bd7b320100b3c4101b72e1e471e02ae7d67f03efa06b4ca19c2ba48e2d869fd29d6c0274bfccd51f8b724609354cf7e8921705893c1f4208e0dab14d8da2e56b788b34be219fe6576b43aa6a454d74562e75364a6217b1877abbb93f52195375232d6ee180d38b636c4ead61fe17ede5dbdbb258dea1258694e03ea95daffe555eb4566fe02101b6b872ecf8d09808991013c645e2d286e7d5c3378d8c9eb13fba5bb938a9d81644655178625d4008150a6511886725b5159116609ae53eaaa265da5c7f1dc866c9307201d41df9b6345df9f8443fbdc086915464d713dbab159268a42cff53ae72db254715ea0ec8bcb0f9061dbf0f3a9f61ff60d549537f2d7c89ac50b3f54ec60a7237859b813e9ef34b6f287df066ebe0e8b67209b280413fdf954e93d80aa09872fa4ec2307a38315524ffde66816ba2bd497ecba3306048ad993702261f2efc4074fc0875c3b3d9f1149b0560894a4ec31aa4c3063b9fdf96fd6bca05496c7716e8529c5bb26a5e24db630d049551fdf52c5f23f6776d746fab5d8d784f104b728c746ae11d011724b4d42665430c3dcafdfb8b042a524964b3ed9800bfcdc1727b5fce5d98b58aae2f80a85aedcaa5445cbb48fb5d38e7d6c055a396d7a6e0725dad635883653c1b70ae0378624c0fc3487df5a391c7cfd64db87242fbf52c0bce932af3d07776aa39b92c4d0bca11b7d5de6544df2f8113fa7122edf63787725fa173c64bb8488ec8b2c8345565cd48a5c641ed9e0dad46b007cfa348ea7c1dca9fc975a3d37a139390e3c3856509219f74d2b409c3ebd985ab94a950c21e727df7eb6a26927d7b2857de3271cc909f9e9b2574e9b93ea8150c16967aba65214d1148cd1ec062867c276568f17f930f1a20e5c9df1cdcf4961f035df18a6d20a549dcca5c37e43723aff61a4d5020a1b97515ed92a7b689cb585607bf063a250a1ad74b796d34b8485d751923302af6e0cb557eb6cc0fac854b2f9a3d7d156fd082b74805dad76895db26456a68ff56d1f55760ed867ea47dbef436e6b4742724069fa51a5efa8123ae0ee23b3538123470d7f933def1d6f59d133504882472b24dda5be2483882317f8bd9950634f745850e351d34208722d1fd9a901408662a2c95d789c069413d559b70f9765361080cb5052af5d443c4070e5fa9b66a720e79e81ccbfb915fcd2c7402651fce59f6bcc97b4bac6bcfe7f6fba3908d29729f25a240f51333a90f4e363c011a082437f49d8e106ee1110d17c6d36b8ece72b4b8b85f5d4fdf4ac68352947d5a218b0c766e67afa4766f904a09d9e53f8d72c3acb29b82192e6cee2c6bbf8c37f14f8c036baf67558454424d0cba46868c724a785772ffd5fda2cca79838e990ab25d0d57097148983ef13ad1a78dc1ca04b8ed729bf40c41ed48746c19f1c329b2bd80dae03273fd33c428296967bd04b721cf27f1d6e99e0dc41608f018fe66179fb66603eeb1d89c342368db7763bd203734acea00835c66edbc7ed95f2f7137d6e7a3fe83f790fcc3b79b3ed0bc6d872dcc1687028a2f3a506a04356ddb1e9b86044fc9b41213aad6e13c0ca09110c72f397c11cc45b1f7405fcc793d966c0d445718949dfa12fb0a2bd4ab6801a492e04f3c1e1b41672798d31eeadcda4e77dabf6de9441f1b30891f460d5abbcc372467a0b4f11086f6fd0015df3bffc80be81c4060cd96738fe599ea74207556e722d508a47ce567ece2d89166155b3970d6508a32bf8fc434cdbd8500db5739f7212779365930e40eba55bebbfdf9c6df657a91f99b85eabafc2952bb9a93a2e0f6e2657cfc577c0b01854cbb17e258a7cb58e2e3cd553b9daa7b474678a8b6f48022588ee1050346819f3124de64ecaefa235c3ec740ecd61efaabd2fe933f52e5597cb5c8edc4bd041d0a3ca868a80793c2892b7400f2f720b8b595c7c1c7f7243f9ed2544d1c0c87c9f94342a00c6ad980dedfee934585c6814abdcc0dcb6472f1b31f9dd466d23aee8cedad6518461269b137703eeeb1b359caac00a77847290dcd717d5b8ac25c20478cd4f23cee7adff0bea3f906527821756cb937a7272aa12fbcad752aac6754261f47abb9b5d5184c5c8fc5d3a814db58ec6bab5d40fcb6943cffebf0785b2b4e54c909b0f1a7f59e75927c18244c6f8604653f89a6c7d6b7ef61a11e6a41b40f8aff26a19c45cb70ac7f012bd8297d568f38205df729a50a9f19e47c38382a9eb999724c347dcaae28af178b12f8491b0beb796eb5852b3026d763c385846ba4ed280676c3472063c2a6a3120fc91bfab891707f23f1e1c866f8482d23a32030cf95f4fa8b9609973dd3590d53c5cfdd94e74db4f0b41b78a2a75adc7634edc2924dbaa6ca9ad7bbfb009288a02472c05b24d5c2b7235b27a1c1499268b197e16c7742b782dcb4d58629974a9f549577c433bc107725cd1b89c03fdea4d2cdb3dccfc1d9ec694688578a0ee5d6516246a47608a7472e392d0a1a2cc5f222d3c5fc6cd2e9219fb4219ac4c96e9b20809a1de94be32725fa914e04fbe810be0763c9b3d917ee17764a57eb8ab8e95533483459098ab50c20cf8639bca613e451a9d1d82b4f3cf436bb74aae6c35d1f7286c3fb06545385e051947a48622147f3c9bd50384e16ce56c41a0a8bfed63d0f12d27e01e3b7255a4de0e0e4360f3a795a319890ae3ef77e558cd0a6df332d5372e54c74b4a0753c142b5a173dea03c95d38fd878d5d23900253671500d0dfcdf513d60329272ec4bff32614f4145d7dacd8c465342666990bd319309458ae240efb783c2eb7294c662e0c5fd7e5456136ad32448d2acd09d13fb18de2e90b376fc8c466dba72f834e7c8db5feacf5b08e67c4bc73697aba5923eee15fddc8cbbfb4a25eb8c72d9f9c20fcb7d8e3d776b91b639121ca90401c31c3b77fb53d1dfae38a98630261da6dae79a48165f2fba9a0296fe5a6d86315128748e67ceb19d1ae429098d726357a87461f58237ad86d92b6d51c8058f9721a3f2843e43a000f058c7d410723e77af12b24c9c946462f72d245bd67e7ad03e156dba62ff6a67db0b65c17b72288720401cf72346a54be8e8e0235bd5a5013b9b629b8457d0e27a5cebc5f872550067f1dec903e11d914ce9332b7e9895328ce060b0b887c7e36d4752e2b7501e8963801735c813106adc0f267ca255d35fc6b45199cd1ad1e7c78560eefb72e1091fba74f45cf45cd0206730bf7475d6f954ce1a560993556308543d155d6d19da43c53ffe2aa091dca0ddc25496e96ca061467f6cd967dbc6afc539f6ce3849161fb20851e0f44bf6a0f48dc8a281a637fc62f4c2121b2fa4b42e756ce74c49f4564e061fd464da89f5c16ed9a6db6a1b4511cfb2b41dcf1cf44b4d22b764a07b870a91dc711bcc9ab49aa803690d3204cdf87f3cb97d955a066f080dd6727d6db178a3eee6a4941bc51542e80eed154a0ed23920a290c1757f8dd56cc25bdfa097ac7a5e1eed97427cf3b91656adfbccf32b3abe5a919ddeb4c07124fb729f41d36e3677a1352536fdc3beab37aaf9b3b61f4a1a4443a66cd571e0fb00720b18d1579e741d1a738bfe6bf8c2538f2e32fbc04b309324f5dd1b495471c97250c3fc0bb62927183aefbb1a4395726832f41865270eec03c2204aef9ac215273bded26bb247abbbbd5fb5d9e8cea11215ba16766e3eafeaedaa715960c9642b90b7967053cc37c33712b2091ed976f5a8af21e4d5e18cad1be2aaa5368e4d72f837d7a1e3dc95963daaf79aa4618702d368a4b9b10bd3e0b8e1d1fc65aab0728d857f1108d75399241e12eb2a0052f3282f03de9c2184009865186aa0eea60a255334b339385480c5ee6e8e5e629c7c25873b1341682173d5cb399b764141723b21a9fc6d746fee8633a43f85fd3053bc27ae5cef67548aec3075b343f14a72db715238ab38a2ce90eb54cda40e12a2420be285c588ebf7c446ff7917d8ed72dea22f49fe8a19c8f14cd123f23063a2d4ccafae77e279a03d66ac07bfc6227275825a8b47f0b2f7d7b5a55fd8ce09a82170408203ff9261f1af2c07d29dbc1de53a1527b82dc03cc1edc983f6271821a34db9a6ef15ba87b955b521fa9ddd727e770132ba5d778bc74ac1ec5f64c5c3e2ca4e0d7c280b1ca3f99b9a10604c299aa50286f2dcecb7e8955bdb46fd7c2dd8ab4670b82f6675301aabd1e0997b5402146acc76c0f0967f1e018e0280bbfde8f2e265126ed818d29fbc5e473b265301a4d8d280e4f3ccffc765585e668c246c9881c607207aae0cba7ed023e62314c5d75231dfce3a238fee92d1d930cd5b2e57b63a6c67507e3a89bef06976397266de1fdfb86afb39ba6944970e4482ae448fdda5ecb1654c06c129b59fa7e520a33422570e52c02788992c8ddb6edc777c31a817f48893c55b9df198a642bb2e92685779956629464b2204528dbfb993e27efbdb98faf71add655f0f76251433f7a83097fe0b046a7070e88ccf635cfa79baa5aaa9b368b17d0471dbe8ca430ef350588246e7b18643748273b3e8b6dbb76351dd988a6e8acca86d621edfb52956c777c6e1b5f6704d14dc7ce3ae5d80b032d0e0f4d42b97f2faa776e7fe53337a2427bfb09dd508218bee7986f95d54ac6cbbd798a8fb3aad68e3136cb4c0723c5bcab2377876cb29acb5c0c005e558c4d243ce5b7e5d1a046b6c36b2f5b1729639dea307ea18dfd5fc570de5373be035de4b0d9b4bee58278860d0ec1b6c72081ec7f9afb30e17b6b092a96c82e3782fed9ff821268fefdacb1c6466bdc972a5c7efedc284e71814f5caa4c41c59050267b139dac2317559e3fb112b26e46672dc27ddad2af46dceb5185fd24998c7d2902da05427dc90d115e41c17b3477299a9a04492d64a9c34a7c1d840e4204927303b5fa5b7d8885de37f7f211c02531287d5a983e32f5f634415ff6af0642140f45b557c39ceace3b7e2ae6718c0041238dfb1f13da5e7169af9316eb3aab3f374ec697ddd1f82f2b75c8ae4717a729da9a65f75b0880a9f6a67c8f36b8c79b06236bd964f399ecec5acd95cfbd76e54b446ba404291253ab7c8ca2e8d16acb74b96ab95b75c7392ed5b3f0ab3af723db3771543f99d5b27d39b7224243e3a977c6bd273bb33f9e3f55f0f5e0d5e048c318995a3da58333371be944d3ac1e4141a5dc9bfc55eaa00de469b20c8857297a8ebe775691d23f757f6f217f5478732b7f04082940115062d6c09ac6a394a31d5c3a6f4ed0937ff5860123d8f837dd28029fbb7b666da5055fceec1765306e785a963edd0403ceb3cc1317d755f9cf184871660153caad5d0e3b2388d3e729364a25aad49fa74e67d8b61c881d0301045894878a099213c0bca6e4291687288729855757e5fb18cac671fe28baa479180d51681033aa6427fe964935d4c2f8a0c1420175b7ed487fa5df48bddd4f941c5c991d9dcf60ecc6d024511e05972e8ce36c01543dc35953d423bcf3ff4550f99a4d1b1702fa7e54e15cc85816a51e2a53b4e98d811df9c54016f046ffe591b4a36ddad8e26fd421e998329718472b60d49955639002cd2804f9dd53c6a374a66232761bd202854a1ac0be1cbd74f4ed262153c603a4bc141e93eccaa90c41276b1f1c672fad7065c03f70b592e727e4a2b1a40d7d8b5994287ccb2237e1778e2a9441c75e24dfd01c4f6cee81314e66d70279a51619c71411c5500b22fb863a46e954024070b1bf9e1a042afee72b8a6692fc22b0831117922331cfb277465dbdc0f32e69e149cf07baf0d22f7079a5f1f49ab805531eea8ca533a1a2163ad2583cd696a6d913f526a9be7ed2872b8fe162bc162cd39d4f9d446c66914aaf447740dc6192827df694ff4b1781c72c92c59dacd68d1485bf0e0c52142dc80eb6405c82ad6a7cd8f5f9ecfa94904729adef6ef6d196c956e6ea136539a10136c863ab3775bb9a67e67724639dc553900604096296e1dcec844240a60f8246100350ab2cf1cf14a724766a9a85d984f96f97408eb37397ad0fd152db6a0dc50e557746a309fc86af8cfc6f2942408635e45b664148bf4759cd4ceb6aa2afbed57c18ffc80f0a84f521952a267b679724f637c78643e94d514952031e3fa2c81d403372c7357b390133e3dc860b5200b55bc05bba389ed316696080e490dc1cdf69a2471b73f9cd80f9efd96bee6dc72ff66a67a119d95d8723a0adb6520da3d87047feb3a6db15e93ce889801ba9e722dad54b3c8387a1b87191b5bcd5bd410c36cf8faa08cb394fd3b4f41935417206456578f871ba13f1ecc1c2d97e80a8438bf6c6e27ff7396b5f395b22db2e772044dc3e365565a551ae85cf523bcf6f3f5acd8c66c0b7d7efa0b683e0cfaea72437b57a4480b1e659969c9ef3cf88dd9ab7c1c81fce42391888da89ed4f3f63b05c2c98b8eabf28347c077abad66ca70d8765518247519503dfff6dabe42672fe3efd0d406ab5955f19b93663c40d19cfc754948db98b33554a79c32c89d7f1bd8eed3e786f263bc93cd99ac1de45d2bfcf3c4aef814302133ccf77a9c6acc7242e0fa830b85dfb8828fb492c4c4af142449a5f94cbcd5c703ff9146c71b3c726c6fe54e76ca4a884bb61a08feb38303f2ae3ff13d8e9079aacceac7987214724ec0a07e293a23f08e2618cb643638a789d1408ad9fc8332fc8a79d1a1262172645de7d51fdb227e8065cd1f08f0b0cb363185ea96649a8f9b5fa2e1f8562e721417fb99f89fac2871b6e342ada035d3f3ed1dc39200c7956c23dcea6a720f72ab9177d651dea1c7878626f8dca7cde5f7e22f0d270e5c3dbf8a74ca19a3467298367a4dab6b144e765c62cea2a840711f42f39dc332693582d555097b363c72b22c6ce3ad1ad9274dc5df2febb3bc0e6c14ebf1238560f99ac6f6a462ed2672aa2b1702aa5f3574a9dd2b1b4db8a99df3bc8e5defe8ba5fbcfba94b28710a006d07808fd3ca6d834791519cf74dfdea9f350f2a1e7a587859d259187c3ee86e0405774221fdee8405be977fed452f3e16814fce196e9053cab30e655d4c9453f649e9fe99ebf53594558b81d944f4c043f08964ba2b7d5fe8c4ea2fe91c0067a4b633463d9e33a67fb2993273471dfe25f0f4167d1dfc69c1d2604117c76c3640d28d8cca412077273271493516c528112456a9c2058a127bb70da1a60b89568735f09feecfa56767a841f062e4c080a0c0dd9cdfc62ce6fd50c60c6e27255ea2ea98e2f5507b0645caf2c8cf8166bb341723ee4984a7db1134d26a787bdc72d0158782049bab8543b519de59d601e3e8355be5a9e5c848290930d0b9e23e72d4ac8d7bb18f9c2be9186e95fd91806bd57f5f88719abcfb8c8168f1285fdd72a286936d09189a0bc82abc2cd916103e7ecf5a8563b2c5d04fde651f13948b61d0272cf05f82f2c287411f192986da6a9f55a810702e11ddd115af5ceec2bd2016d5330222ce55b243fd2451ce6d058a3c926630e3cb110e858f9236dc5aec729c485c6bed0af78a84dac61d19ac215113cbb3db86f00d665d93c03b0367637268c778e4c20aa876e71e1c5951378eed61e0e0dc6cd5c576fe34813c59292d700daa70159c432d0e77aa2fbbd8bbc957d708d55725366432e0fa912eb02a8e0bfc7cf1f72c5452128c3d1a0abfbca732b868005b7ca07879134e4f9adbbcfc2994a94a12f284f48d56cea51d5cd6666abd9e2544ef33bfbe02f0a307afd11a725c7af6f8af5b4532b7ac11a7591592631906e91462466f381a0c83dd0918275457010a44bb610d0c7e0b2d7fad7114caf76cc421435727d3cd2ddae1e74ce872f85642e5b410becd00922ab64d3f574671a94e23887164a9f67d410a44ee6a727b59a274de28be0785901dda9b819e9d6974a4f2758933b3b4f369482d03ef720d557eaa64de555016728317a0a3d7c719365316b86753062d0759fed168bc1a76d43399ab8ab16e5e38fcb0e2acb34a951b7936c589c7b8b8f039db3c16c30ea2f431c56ae74746366556e18d90c2d3b7ec084c84a2688fc6d531f2a91743728e1c281e356c79b14a5f08e616f10b88aa5136153e799bbcde8e2c8a7446327231f9e1c924bcefd701cd2e02299c3038136d2829847891600fe81088a5a474722fc7be4f9be57d07dc20547afe95be6edf8f61567560e8347c8b25febcac580559da281d9284e072d4e20dd35ec29544d2972557df7cf52841c7b8469f447c723334df1a583288860e4af286ec2565bde99c2fd7064fdc1565b51c51e4ed8b3a3acd3d1eb19c606620bb58b7fea6b458c516ffa481f660444711ead32613276f63c60b685fb25b86d0a5304df438e8d5340c801431c1a2e91d921f1220512772f81370be48b67eb63430fc0a0a75d4d33e4191944e71307672778bc6fd75bd58d14876c64df2e6c328f4e068951b8d36db964e564514110a454c2eb7f13b9772450d1637528a46034411a08b432a9b1da8bfdb55610ef5fdede7c34164f5d85974f8aa0c012e4e89008b89df6303fe0df66a14de2de1571ef7063416edea294367ab85a6d3b2acda66a42df1ae7116315f7e99e60251b5aa8abedd290dcfa0407518fc184a443099f504baf318e64c21dac226e038e082ab7a1041897fd8a23cf94c87ade82786a23af1cf3d167cd429ce4c714df8c7541aa0e5d6ca74ca9e70624432a21bbf355a71911051c569567edfea5178f85578b3d7bac29dcb7ca972c2d822d880ce860bc32f3fd41885232f5d0215068fba0e655061d6f17cd1b1725b5c524d323300ca4186d80771fb466b26233700cc88a8b1592146f25b4ea972802d7cbdd6f6f8efbb347c1d6068f410571af155f9dedad49e175aa4ce8cc572bd0fac90eb12b439e0f2b7d77e595bfb5569c4b414287a8a0d81aa6c791a5e7211e5d8c7872d902c678eb610f66d827c710155b256e9bed998a083f681fbdc5572bae5213a9335ac37cc52bedd2cd96cd632b6814963348ac678a251382ab072bf1c336180304758a6f51c7c78701abe009045fcba0aaf45042e9890fda4c654c80d83258f288b8aba2211fb7089cbc674776335114719935bbaee793e430b63c0cd59c804f979af3f20a1926f3c0a4e0761ff6f62bf3909880792c93035ba72ad81c76e2acf3856a7f55c7ca990742b58b0845a85f0b02bc62d8ab284b7cf7249e577678037f9675de728d6b0fc1ac708e34fb5ccda781c0f5d4a1c3a19d572466f5f3d554b292bb0b8b0c4bbf1a382aab7f41d6ec9ef1b74cb0532b0470250e7c3cffe1b4cdaa8ae5c22991389531395ff039beed4f8be3209585ee17ec47293079185fa667359e597dfd91d97680de22ab9a056872b1c2adb19d94646d372987bd38a6097b66f416672a01d005c92b37ab368eebeb1aeda5680d008cc3571fea2500299a11abb99e414b60d57f6c1de7123bb6aa9244c15f0d9d9b9f7fb72018c1727e698a218b77d2dfc97b16c926443dec62e23e616a2a7c85bd1223851dccfdd91653965f561a8a16a2842c5199f1d0cddfe749a0fc2f5a84f13cbc51c7e0a66d7cb8b0ac0d257eb1425f2b16e531191c2c3d5c9c6da28685074fd4c4d0d43c6995413357bff4f2a233f4ab7c390b5b01359c42c88e540a73bff322172fd91933f5ecab13c4ef10f798b490ad8ed9e34a78d68043b29103bbefd49511a2be4bd7bec2873c6fcc88fa83848d0a982151ee78940ab105f29cf18242b782874e735d49de5d5e668242a33c873afdc20dade42a9addc87ab456084176a3372a105e43e76bccfa91ee8fb32fde987cb20297ebf21d89cf71fe251124a1e711053e2d826cf0248fd5a9a73b3679b60fa8823569b5e48e6d39831772e300405728a2b7fafb92082a7447101c474bb431c718a698445f87f732d0f97e2b022874c9d3860987334e0ffc7b1e0766e70a6cb4f5213a45b1b38b4b397808e1b339972ac0a249fffde847555661636823f7b385f5e5ff887d4c2e9ba9b4907e65bc1594d6102aad4d8d06c739658d0180f4ad4990ca8c8fdd5fb9636abd52223172972c58e0e842a9326d17c55ea17673e56ab18d9a73fd3e158d574ea0f12386b6d72c50ced461afc708cff55f6422d0f59e9c567db8b2d5acb0895c5ada520065b7260d653327610087ab76aea87aa8693ac8a1190fd049b3c296090e102e6017072d6274b9302f2e7b1eeb056d26226b16171d774c0bcbaaa4b229fb2ea0fd420727b340fb1ab67fda4114a248f759871013841140c14d28a7673b9304c4155b972309e65227f5bba9d95967ecc0f5dc2ef472463086c478f2e471ae1ee755de3657b1b5454ffaa9896140482e50467fb7e86e4bb699ca09e3fbef9ca63c1a6c8721f8bf0c6d8c9e6b9d6993f6dd03a8bf59f1ad2c557096b93491a54953d915e72e2560406bc01875ce1c51ec0685987c151a0a9683b94c2f20859ecb17eb21372f933c9e83c75a72247ed7184af6526d8330a60ddeecf77236f9ee41fb5e3687298bbc6ef1109f11ac8bba20bf47604a7cd6ef335959a5a4b334407a1736e1d720533ebb022cbf6721cae299855e043441fcaf1eea8662aa3222505a288091525daedbe8dc7a8dcc2d15d083182f6f7a360811935bbc5cbca45e94aabf0abb672e29f0202ffccce4a161da4379d91143da7bdd9f7712f992ad2597511f5a9b462661fe820c0bfb824206cb15959c027470a14dc1bb5f6f69a112d71e24bed6972edb33ebf2c63d4334b1fb50661829104e67519714d1ad0ccb1269e39800280289116c9d5b6998f458fae2bda15d82a17abf955a0f316cad8bc5922bef0214e38125df62e3ad7f55a9b2948e58b1c9c02e8e845de52035ba538d582dccbdca965cc878028fcac6b5cc2d01b2e355a569f8e2a9b81651a823f39b5798404fd69594fb1459e8b034032fe19175dfd80c1da7a5260650e1d3c9e39514fa9a178c372c926e18a6ccbae2baa23ac65bae943d7226d11c2dc78b9f784ee2d95a4eab37253b376a947b09d039e1008724747c5dfc05583749052281d1b281acfc1e80872254c22d2a16c611f042790c44956a1cf750af2baeef48020481ad363407e8e72eb95beb3599830749cae74ec3bc20c1a4f15bf377e86a8f91fca79f7406816722f68568709564d1e277f7bdfbfa26f7e86cfffc8aaa8fb8459abc5f7d390377202c47b147013be95712b74eb653eb83973f1cbc8de33a2375caa901b11122672c228957b5b10b8cbfcb8cf717a920355444a57ec8cf7cfe58eaf279a9f3b9b7247a2c0e104f9e7460872181fcad2f7763a6e20318ccb4802fbcb9afecb86a90591c122c017019aea5252c7a65ee7be59161d07c8cae3d71f305df979f42301726f6b06851afcf276a3010810d74d3f12590254302896890765bc9414cdcdad710ed35300a30275be5b710c9b08668ba78311f11b464bf7bbcfd121ec79344b49f2472729de3fb5d51f62c360fa86dbc49e71bfb5ac2d5cd4172aa86b482bf0720aa7942b24953d3d740259183c71b98d8be066aa8e080440ed0526042da14149eccfb0213236b9b9569ec6514cdc053f2b2713a0886dda8ecdeb8ca413245b5b6aa2e78beaed809f8709b3d5c47035d1a47b41da6ea058adc34b1d1d98dd9f721b378247812e1b474c2138b26e6e8b82cd9d4a44fe1a02d8c6d6fd490326f0720743c8562f5b30ffec5d75e9bfca3658a14ea869e1c941297b3d706200fc3f72e305f8430e1e6baaf059b0b5c2c4a9440c637291140b0d9fe47db44a286dd44104d0aba9d3f2bbd111506ef4c055811c9182257ee950fcd6ecbe124f62f95a448b5897e5ed32aa592f719f08dcec4eb5a27e339e6a351d8edd66cc011e820d720844cd7d8b152e6b7527ab1bb6c6f3784bdd2ed6a7190e31cb3fc5f12fdd8132b996641c0fa8c4cf6b380d3af35b58f2adb009c1c220bb4cbb4668c44f37470518e12e03f46f592c3b2c56234a97598c579108709665b13ee7ef2c3d773a0b729986214cb9d0e5ba6903d4656bce9280e5288ccec591fe4a47cc3d02a2589d137e8e79a85c3f6810f0236b0b23a941ee41d088310838aed0fc5eaaf49dd71915d27742c9455522aec1014c2601b79bc5ed9da55e2ec05a4a1286dc93c8a72f7244273401c73198618f67d0c2dc0c677edf12d6e8ac28521ffac2eddaf9e3c324f09f198c200ac27d32a7647ac96237334e56a262f6c17b5b6ac54b1282d73c525887ca8bee7d091d008858bd9eea2dcb72463b7b0b7bdc94b79ce9b79c57467207f95875e99ffd30b57d0834a0da0103bedb440c2a407ed54e59f189001320332e05aa37438405f7753f9208e3040aaeab5a93c22b1fca1dba7003ba6485875a0ef6d6a921763a991776ffd0440eaaeaf241fac664a6026193c5f741de554a48b00dc1f84ac90bf46503834f95f3072602717b05003537dfa76db6da2ac46b7241e605e99ff430db890ca0a231ef78f2a5501a695372571fd6646a59fca1651bd14b8266e0fa18bf4d1fbc81a17f8f92c8dce4e4d6d6c571db75f121fcd3997252aa123e373eb54f547545cfcea75aaebdda298988a988894c1b893045b8f91adf0868f9ce86a9287851348a86f763fe91e8648f9e0f625213e0413546ed56560903a20535fd784de35648790dfe9eae1cfc32e5b83322b8ef749289eb53bd728dfeba17fcdfc96e164a81fb14bd778c08b2bdab14d9f4a86fff164a4067ce72f5f03f05519af2a0c5d2b1f50082be2ab8ac7cb3e7d19560bf6df2eae294686154a0e0305cd07d4113363d7cb82da782aae5f3a346fbbaaada8f1ed35fc20f72b50c2fe707493a5768ec9c02f75607277ebcfff7e87f5f5c1afc1db36889f572521cd116f849c3128fea86d523d9c06b8c2e8772b617f2e9c0614bd03d9389288442c0c1620ea3481c68c6f35cc3eede93e1dc6ea2303d4e070a21d808225b728b19cd6db0170f9177d7ac59aba631c30cc611a5c9921907577bcae48b4fa95be5c14f29723ba7af26b6b50a43d7b51067582d53ebc8152c6c1181876072d0724ab0b0327a2a28c3e313c3edb9d14b4b3cdf45af0f798a94e5cb122da8a2d872465b1e49ad9040081f9bf9db2544ac32914a016296cdf00ad90ca02d961d5972be90c409c37812b0a50718fe07f988ef3d755077f5f42aac30a01b39f4bd597278b74fe711164c6627e05efaf0e6b92531a095e9fd53c1b374f4a6c19ea9c672c69086616c793a85f1f707d849339a907fdf0d5e16270d13a00bea4959264e720dcfbd09d98a8a7587a5343bbf5b49c81dd31624a5cb7fb3d2802948c6762251bb54277d220714b13aea5d1c737d4fda2496800d064916e86826b62b1530990b08aac3e68884969243bbb296b4bac61c7beb7aff886bca47ee2530b2b6ba3c3d336b0067c88d6bd130062a70c1c187a08f73e86d8f8ba76bd387da5c973ee072e31a0254ec5c2f1c3d8773cdcdedc30606ad1a8bbf1c3f51f8d3da0c78139b0c7f581b873dddad95aed3920b3207f026371ca153678bd6a4833fd237b5827b727921b9adf21f9acb1c16a5b8ee19f29deff5e3b34551aeb9ba310679c81d5472b04f0f57b0a37dcee58a06a731f12bcfe23cd43d78c58303809188667f3b235818aa1eb58ed7179907e77d907b50b85d0455592db801ffedac60a437a79be87264dde1100f6733101dd3018849673d0ac23faa924a4735b0a8d2b16f75d0fc433681ab6b442c17a99f25b2501055337911bf0d949a3a719ba3c998e9c8e2f72364e72f47d80b5c4c5c1538a40fa8d31cb085f2cee611591b71deb1815e63be72f8525f5cd89b76ab35028c738287a88e2d61143d9a8af0aa9ded250a43f8a02044097c42cc8eafa94bfaa157078d2fc7b0db566c659c6df64c596e8a3750c11ffd865065f91cedaf1950def36bd033bbfb065e1e6d8bdf97c41d7d16e617d572d208a8d6fac167d45337adbc63f681a0ce8ea5e22a6a4649db1d6abfa7333a727272e5b64c7253689cf4aa999297feb57d24082549a8e875015c5cbe2de5fa499421d9383329b097edd204723cc7c87306f6ff7946c052000fb17afa26742772900e1ce6e1b38d1939cd08107706659eb8a5a1839497f5075c22d20b2065fa123e8293a156b7f4abb370372d18b2217c7d6d587e0973fc8bcc0095e136012d72c285b0f49573076c765c2aac9c24f6dc69203c73fa385945fdb6e121888e43312f93afd3e0d3173a0e5b69076bd416775715fe4bb7fb1e7133dabf6dadb463725c93f4342832a48df0248d04cb2efcf46e0a3501ce073a5e717076185d558661c79bd2e4477c6227571a321debaf34f54907be07ec26887dde8922450bf6136d7b77944c0d79a4a458e31f8b53008b014d2f1843490cae0c76b3b8e495f74772eabfdf7dc942e22a3d94b7b343617e37fc98cb66550453225a0e300f3dfc3101c6454b208798da3eb90fb6985faaf6f4982afbb47b8e33bb57a2778ee646d74013ecf3408648dcc77441ce7af10aeab18473e9dfa16fbe645a02178758b3c3724ce12ee631cd0f36e8f32961346506496f84d9872da0143df53168de14618e5aa3f453e04ba97aba1bd2e9e8cb375439291cd08595c05a360997573f05b7be1a5ed4994241654d11b9b679ae58c228ec4f4619a96fb6b41645d002ecf313757278f2f8d517bb0e52bfa4bd1b1740cefff339a08884bddb204c5888babb47ee72bcbf1d044005899591583abf7fe7763ddd1162bbe82fe25e8aaf67800924df20031f93bf0b6233cfb7f2848d54b100614ea91f5a733740bd55106ea36d7048050e7504af83a6f27cc4b9ff8eaaf2893e8341bbe479c6eb46b10909568ab21972089aa9116c73d5e28ea445f391ca657a61b64b68d7a96159c8d5c6edad2efb72ae87a9c0cae1a8f1d049fb925a4482a6b26e80fef0830024d533cf5cde282772408faa85d364c2a2d549b73e78627b364fe94c1361ee8c5a81ffb93c1169011216b1a3362dc0cf263cca864a75bd6732f64302292b3a884bc941947b60a17000de5de1535544cbcea044cc55ab7a0bc2b792d0c6c2bb7508e91321607732f872a7bcd487968215538281fd1a57afa58db84021fb86e87cb154155f33def85472f42809fb02c06d770d7b67119ea9519230a3294313f12b5f42b76005848a1f728d0c2bfc0aa42af8a712d5ee7f630733b9c26c2ee78db85323e31d91e2669560d4abdc43bc21275dd5e62a55571334fef15a3488caa95eec2e82da7c48bf533a1a7c99d953933b5e5c564da6662ff7d257f4fcaed91f8a1224afe65eea44311511d25705a0350d7c8d527879068b1ba1a01476b1a84e3797db84ccd7f205b4722a9ad8ae0d5d9ded91467ebee37ac1e6b04de05da607823cc62af2488ad21f1568e324f9272d62bc8788c720ddb215b79e223b9024106c4d4f4696bcbe2dbb72fbf1c80b33c1e5765d1e8c351fac49763d526518e8f6159a0c1ee0c619954772b53a41f0ce0857a0fb8e54b1abbe74df3b914bfd0bb1daa318453024698a2272ae8a8f9e8490856de3234e57dda7727fbe766e790e48f06f9d0227d5172caa142ca284a48f737737e7707b09c21ae0806f1c499f695f09b84ec2ec39ea690e72cfaaba8fcf47654d0bc60e51f5419ce69ef0ecc4edfe31a79ce723cfd1f81809e173a17dad25ee0cf9d58e77f7db2dd84091ca761de7ff6cf0f0555a3cb6cd72a087c594e432166f2500d8e83b20a155bdbb688d0773774563bd03856eadb1722df3aca10e16a429808baf9ceb31f0f868ad445b15253e1cfb025f5c04f4b8599a2288d22bc0d459c2d40a084358238b910dd9fdc145e0eee3f617e0944917723ae78f91146ffb0878c82c9cfaaa816329974971ac5e621fdc9080b0359619087d5d818af76347e794a91c5805da7e76ef66e5da87502ffa5ba626879533da4da96751d5ae26a48dc8a5e7d45625458d62ee0f8a2d3a356e3abe190d6719fc72ea6a30c7a683847635fa6f8dfbb51b38b07bf49045517b65f310686db46f0972562b55da11ffa3acd9244554a5c3ba05138d623d46967c326e354d018cdc9452464d1ee18e512ddbe53052f45c7eaf5825c6ea4bcf098ecf616c82194ea9420bcfd1956987f4485a0c9ddbb578aa9e5aaf65553f75ccbe6d09b822b7aa40b6197bad302c07151b043442ed1d24e8868370411fd843db53167c8c22e18251c272691c10f0e6a22a7a4e7d3bb0cda066293bccaa529c9264fd436568b140298f723e76f5ea312aac0cafa3f4fd010057d8de7e8d180f6ed4c29e397482de222d3f1b2ca591887c896443491260771799f191744e8d32537a412eab5bbc046060728dbe0cdecb9a252838b42a53b1cc17ec4afffcdd5669436dfaa29267103af872eb4c71bd7bc62735e9070dc1fda32339617cc0d405f8cf03611203c4dbbb7072deb443d6d85249f4539351c775224ad2060af942d29642df85730a8587343772de417517f7af2052023a5d2c700bc8e9832cc2af54f87cd7827ade248bf81d6bce5cd2dd07b144274cd22678e43ead5acfdbf287bdb7be7868f63e2fa6c8214cf2331bb6680d43416cef83f6732b3bd74bd9c4d4c6522fe9684bed1dee5ee57276f7476a5cfc928f8a674c90891ee09ce0bad804a88337d5b1a6a0b550690572e758aeabc68c592a67e12bcd1c11cf596acae4b8ad0046c8a6668463058caf3e3edd405828d685f2eb6be3a231042d271a5da0d9c30119094f8b08d1371a14726c8a3ed3b103664cf0638ba0722fbdacd026c8a88b5ff1f8ec4116f83444281294d39bad7efd9505abb696f590c1a668d4d988913f59cdb4b54917295a53cd57ba2b1293fd1b868a6995223667c3ffb1816093d32a8ba5e0ac7b7b0d97094b7256318ab74ad90abddad2d2fa68dd71ecbc50488a810e91cc02cda0b2499a486a33188b95848a0e1d7e687a864b545e9fc7ad19ee04169ab22b110dec44990c72a87253eb1f3d6a613e25378e82327264f2e0ac5bf404e5367102fadc67982c7262495a52a548cfb1e1dd9e9e25ed9c7b358abb419e39374f059ab5a617cdb67216e97bc68d676dc5d1fa68dd6e15dfee4132708083d294691849e44c0ad5ce72ecd4d98e0088ea7351738a27c1e782a0007f832561c00f9736cd2e079226ee7290241ede6d95860ca38c496624a043ad32b73d7a46573bf1cb8087ebcbccee72b97cc94ce62a4c5c3f9150aec1cf9afa8b69aa39d60c00dcdeea62c59658eb726fbfe9e14f87c8e295d87e986bb18d38cd13a39efeb0b24dc86967914951c9724d9d12cb9d7e3d913d70348c6944592ea16d1f39297c073dc9a46a1afbc096365a0ac18b85382ceca5c149ec2fa62d744a63824cadc037b06e74f8f77ac6f572d13bb6f3eb89a96a2d4cf7399f88d51dab36e2f95cf421e01fd2c9c1cccd8e723ee1c8fc48095feb9115e29839f91a2f345753c92ddc12ef9006f7009adfdd728c69af2d3fe2509d3c1ce152bcfe06c8a7265d3f3ee9479241ff63a9adefd5726e2b0c8189c3f6dee4f7520c43dd90e9115496147c1078c22c04b9eb1eec4772d7680117ab14b6c8ca8da5ececbcf1905c3cb41150e1e38fa661ed92435b8d72c5f080fa884eccd0975a265867b768cd2d58955ddbe9fe24f4c49f6bf710c672aa7bf4dc4ca49c24319a4662af37c3310e00832c0a559baa6fdc975e7d74c7500f38c7080cd3e4cfc8661ab25223f69c6f433383992c18bd20fa3f122e26cf7272aac41a83e79a4eb8975138c1dba5fb93bde040910506d03f827936c9544830d11936f3505bfd3506def4ddba2cb74a7b151844ab5e5f12b21f19b2f6b74f63a4ae496f8c5a27adb88feec2c610372ad21c2c9deb4dd1bc2ec615da16c7187228732e5701771086b5fd067e625339db8ae15c74e17e1f24ec0d7a7801d75c1b878ee2c2dc11b9b99743937d5adbb9cd771b506e3388ecc75fba0f546671627228da0ec19170f7c6af4883b67e5c96badcc03992735f4242989c26d6d606d1721d444f73bdd2581ed0ca911d6d7f029e0b50431bdeb1f9b87af6218be3526d637aa25ad9dca2b250b136592ad01afd203c3bd416c9ec98c595221bbfe85b87728a465faf593fccc0148d093e1b0a6d751c66c3d4e926bf26fc2d8e006a2df97225d6d4eeaca480a2c9cb7cd73f201842b3f75cec33f0adb80399f5f0e780093da0e56be76aea0f4083635cbc7373401154568c11c8cbbedcd123bac6e9dd8372e3146133ae0abb03ef9f4c839dc659efb42321b88810f6208de14b0b64dc6471ccb77c006fdc4271f03b496408f29853c2b151cb8d14cfdc25c50ccc65f0a572c4c4f2c0caf5065cdce4b3ecda8830cccdbbb09d8066536ecbe4bfa50edd470950dba0327b4a313e87260429016bc6a44e2f081b0ee2f3fe03c09e59d8120a723fdf7f163fd6fc06040709bd4211df7c2aaa96d38936020e9e4034bea4c4b40eb88ecee53847389caa67444ec5ccea1a27840daffe80a8459dd749f3754b337233ae5015f3f068fb16fe75fb078c147a6ea6ef9b25b1d6d7c7ebab13d257e22f3895261f2dd19568ce904778a941cf6c40baf1fb9687a671217629bbabff1e49e0330e885677c4410faf0848ea95c23efe45e0f96a1156de8fb9ec372bbb37015300c511b4d022d0c84703e735d0731d2b0e6398c646a7a6b73792635c52e772b1416a219345a3791eb578a0eed86d392f2049daf396decc4f782b4bcc66c4720ffa11b051804043f845376869136bf682da15230de61558b1fa46b082fdde0c77beb474583bdb3aaad66234f45192258f59a7adca33f9af32edefc490732572dbebfb435647ef6f06155789bd3509af76685d59ba3fabc2b99288235895516bf095e5eecc41d6c3e45e193ac0953530cd8635de565867d5553d6e37b95c79649a37ceacb83eada972b1b27b5607230002769c8c770fc5455f00721ceba6de72e09132d7d4c449886e35ca9565b930ea8e52a481110f3c5b35dcb25bfa2798308ccd7ba13002e2ad2f86320076ccdf90546c90ade68b7965d43debda58014c55bccfcbdae7273e94b6f9c441be62b392fa3b0256ace5177849b4772c5f4c8d72d7e617a01857fa0aa2bcc17bb02f360fc21969d899211087b7485e73abb3323a5317a324b10603d8ff153225a59681ee3b0a9da61c6735e0ce6e9b5e4b29e607ba104d0da3362791d72d96be71718b2e593f4e83bacd224801c1ffae2febd372b3f58f4eab284e2825b3a3010366f9f9f6d808feb77d48a9250e3c4a1ab98372788ec409baea9866a4c44f0f2970def2544e82ef7cad187d6a049b58649a967291df52b9c3c65aee700da1d4c5da80b1acee8075856e6196cdc256a7fcb4e06461ebcecb61432c9397e9097c08c64f470d3b1c711f7c26a25dbe81867de5b864c4517772c1a4b97faa851bfd102c81f6abcec83d159334e81a2851755e020249c8e6b77aea97913a3f3a1de700fc4aed3aa40b31518f0360078abf90bce70511da8e5b1c1b45fab4aab951f6413d2b7a9e815c8cb2bff9c48af55ecab8a6d2414539d691e753108f959183916c8c72e98eb59b8fd2d99b74dd0cef672620687245ee27ee0478fcf9d87ef577d3e3faad463d132d166e70dd1934a7ab9e3ca772f59f95adf9be4e670e9a54e0e09dad96f7685317e2fa58aed0dab3713527c472073774949d2c79531a8ceca195b62eb487afb66704ddcef397d5e4eb0fbb7172aaf3f1c3e6942facaca910f6341eb024469891f0394c96301cd743c4346243727303f344729609392612a1b30469c28b6b8c44a721293ca3aa2ace6a92a7ef72885e79c8e55f11a1e0eec01645a9eaf3bd8c6bfeb86cb99fa752d98161064f39e74bc3e809500e2a03f2d24de24ff20ca5cf8db811686d07f896c9a22a90cd72f5ffe4010a6c2609fe9a3c36e15cdba5f2912052ecd55fcc382f72881a435d720c7b7ba0bc947edadd389e9b5ee60870ee47eac98d1ffd0bef28bdd57a0b6a17200772bd156f70c8359b1569196b05efdc4f9e2e8bb38dc9953a475476bed85c66b477faff5ea14f9662b4c8a395d7260a7fae2e462ad0275e1a6cb205f53372eb617f2ea4ef4528aaa7fd536db985d1e48d78006ae8d7874bfbe47070d65819514d613466cd21f5f27ab0fa073bf54cfef8bb3935e1a0f91f53974582de97725312553d00eb1f1a11f11b84870ff912bff4d077267f8badef891ccc71341652b556756398e515a9de939be3fdaa2108a127aa23464cb95318aac75d1602615644d9a36746262f2b5e6fdf40bc1f185b8e4be72e055e22406190e3f57a702702d1e271e35d0fa8e2f996a67f7674ffd2a24189878229e5defad96ae704c55835491faaaff0195f06b795bf4753245060aa089045a5050b39fdd4c94ab4abb40d48130bf6f55b6564b86bd57582bdec9529421d5a31ed531f5066c191c4a7c255c7d3db47d31df35a7741a9735012d2e1609a63db34690463f510bc18ae670d720ec0b140613c3eabca4cc87c7b19d6f2ca554f3920e0f264e8d9d71392947a72c096979fc357c5900013528e92a9b27a3fd4d5d6c54c3f7027cfce781904fb02c43cb9f717d7227a14900186dd62afae75f5a7a5e04f97b9461516898d0eb02c19d78b868c9e22bef95c0c9e328e8e8507031acb8e4dd5238288c5f0937f7d16eb8987886135ac9b0c9e69224ef0bb10ad575ba13d7e5cc22f21943638b38272c1625a298a127504f930ee005ea1b9e92ddac30d6f7ce5c74165b679d411e172542f4e53bb0a47c47e385e69119cb11db964676a6ad57bb96313115ba9999d728768827ef517eebb6d5b69e510dba3b3ebf91a20e36db320f9dcc585ac2366726c4d272602f6653ab5aae06549d8db7668ac39e6d0349823c038389e823e9e72094b6afe70a50363da1e9a57e7bd2243fc3c6e6a6fb6c3517a22ef5338bbce72f2c90515548621e0619a70b00675fd7b56588e323e5d86bc9951129d0c12332f85cc4dfabe34bc2eeac758046dd1a6fb9812f3ebdd9dd1b6c783b49603644472ea998e6a1b680697b4a587663c6c77521026216366493ea52a096aa6b124134358316c4c2b9259eb6b01d417b6fc7604023035cc4391d41a190e5c8818a80a722c872603c21061ccb27241c2a4bbf030a90a488e8516b91a5aacf4b26ffc1d72983fc2175da1563be96a09f187a158da260d1a205774f28686897da554eff863ae938442992d1fbe01075dbdfcf888983e7103e223d40133240f06ce00c6912c2a9698184a8117872c362e768792597517c29088aee16068e760173c0a6e9172fd6d15bca4110b87af6a1c34bf9848055578655d3383947eb2a8a3ff60bf0672219be249bc2ec0706945a0de40760e5738df8315e78b8fe28318838bc47a2372893421be037acaf6e3c301089322635f0cf6c4a5146560978b3b4e5fd50b425d78f5977aee753c3776b12452d963f50e95b05e157eda8733a75eba3fe8b41e51010ff5359fc1a037141c41b1464255965b44bcf4ec885ebf871d8f480dd2b003376bb09242ea4c06186c33909e1c0087104f38bbffd9921b7c0fddfd799c7d38cae621c2a59f8ddca5225f06d8c4c2b34ae58033b97a816982573e5dfce5ea723733d5b054b888bfbb71d3d5c4752234955cf55232387f0d8247c5a994e2e9151cbacd03aa8daeddea76c6a8a23fb7e3c6d2d7a5097404ccfe355647b2f9c272d68b29866639acd8f48a8f6c7f0ab3cd8581bdf3401742cafda3f8c60586a51ccd8d536ae2574451c63192c70e1e387b597fea43c002bff221806637c7bcab71ed783d0be5c85c4849a43b51b343871c4afac9da317842ecb80e5826177f7072a30b79a6540d997f8355f21959664a356d31538189122a9f44bad87510b2d87278c98c5fa2256d1b469707b30a691cdfe1ef5c64fb7ba8023d9b6a84299a9d579c327d0035f8a40a811f7688d4f506dc9664ab8569e67175a4567916b9835d7265268e5d783fbcf332af509666df3df46462ae24859ba6fcedd5504dad988572fb87a839ed7925547ea038a052eb73a41d52cedd6426859ec1db136e0f98881b26ecdbbf225e21c6fd9256b996a609cdafecba12722ced954fb517e4408ce472783bf4846a39c34e1fb949c180c3814b3c706a86821d0396a3f639fdeb7c7f60ee8bf79add689cc757bce2e6fa6d896ef6cdc60e0f21f0547e876b3791872a15742283d3ceb9aca8a2fc34910338c669a5a4fa3d49d022a7df1a2bc172c36c1451b5cfb14b97f85051da6ce39575dc2b72c9c631b65b76675efabd0f24413842d12825482a2220bb08ad4b962a552a5f867bf285b94f0857ace511f90beb55726fea5df3d0dd8a5aa98bbd7908e7993cc1f8b44950a1bf34ffd7da02e7f5ce2dcb15eba546c0f8c5b1a7dc51f97364a19c72a4070198a3c14ffac069e3bba472d4c3c8d155c2ba92972e2b0429fcef56163d3712b1e5155bc900564f7558d14c0588e76db72acf43dd4658bf07fa9439d8fa0fbc9c1caaf4f4d8977561e4d572d2ab43070c7f80d7423479241f254a396c7a01d1484c51fda90f572895142472cd4623f6c7fe43bc24010db5691a69be83f9ffc2a142e327449f248611c8b14059d95d016e958b6f45aad9140fca6fe7885d8cf970dba02e84ebb3b2c2ffd27203cd46a655a4909bddabe8dd06e9bd3ec6ea0b7e70ad58ad56f6799b7205ca72eb0184457a1f0ca686a767e3a4b4af8d35eee2a9ee338223f5ac033c180f587268c6250a914e57473b5cc2cf2f60d3f444978c05c699af04cd991ddfa7b50672aa7c1d1a438bd0504166be528d970a47c000f282b64d4565e0455177cbadc3389bb010d6b7ce4e2ce00b105db07494adb2a0cb9847025a3c2ab04b33edf28f72189a81b1beca5281af081db49f3468239e8e3e3707e316fb342ea3ccc389a07249f63f8dc3035e031f3f756c0a3052c1a31d8c7ef58d88f1873673b41ff6a972774a8b698b3653805b89278906f8145c1813bbf37317b2b1b8f53073f362ce7284a1ef7d53c9fc6547788e336dbad1291cdca93f72e153ab8b297ba83cbbdb72cab38297e0070ead39235a3b570768c830af4bf4a5427d1effcfe5862e5f3472e9286a0a296457f117dee1073d18914fa1d6c1cb30f28d53b965d8e92451b90eb25949106b65acde17be50d56a38d466335e8a0de62f38a5d1816f2eceae7659baa5546969a9c0574c157b727e78ab7029e2b2482f1a12a5aa9a6500b0fdea349ebe8c91e3c9ac329a978b67cc419360dd72b17276d1ed1f824c16bd7f9af072daaac6b3b631b9af681a0592d78d7783b482c91c87b1455838349a300b6466196efba948a6b06ec24a4f4d41886c2f10b712bdb6fc675992c0dba004fa099372c7b990d3ebae7dfc2439a3e74af10085934c4a47e2a5d5584fdf7349cbd35d0d65741be8ddf315ad71fd504429b8299802a0fd354cd68c21a8af55c884ecb0550c8d606efc9cbc25869f3b9e0d3c0524cb7694b73540a36a95697a24428f44562f5000e8776d8cf6886146b3b3b45b6fcab61e6ed93e5dba2d33dcf61cb9d072f6b79899934883b60221c9d3d0416fb0558e1c34fc03a1a07d97ffa189bd6a7249de617b7c29330d2ee5c9a48a8c7141e0440599c1793b4b744e278975612772574a85e053f5f1dddf80a1026090388c714d7ed319a09eba9ebbde11687c3172a900aa49f48af80f4eb0547cb334261901cb4d312d8ebcfa4a752922aa52f21ef4f991ff1930e7c69b6c8aff476c776f60e145e54d4350bb09e2d360be9b327214559063ee5fb0bf513856b57843765ed9798da2ca440c69cb27971968c11272afccfb1a5fa6bbbf4409f9d0c55e1ac898be3e1f82e5bf3da2f1a166890e1a2d0607cd2bfeb104e535630602954eceea06080f802759ba81e6dcdb41b73ab27211d7d4941333a29b9a6a9027d1845f6fa5cd65a759c19ccde66c66af3572ab65ce1be0725b16f4626005b546a22fa904d09eb4f203ab2534e8f76b8a9cc77772ffb9f7a9569b1e6ffefbc46b956a1ac2bde975a2d64aea3ab44c836f62702472fed42a982af4bbb7214dddac71c154e3a62befb26e83bcf8f937baff1a3e514eca95d56cd90898eab990996036f27edd7aa11aa5c7636647cdb29813c8ae2372525963147beff32156bbe762da1fb7f409fa89986e1a1aaafd5f08cbef134b72364890d9aa97602f01b39701828f0f6dab372ba2fcec37584501f045007a800072c9a07152b2552f44cdd487791560d1575133467c42cdec5d03b602237e075b13d9ab1fdb4a7f19bceda4eedef5be1d91b60830a2fc21fa0e3ab8345d150a724eca066820f95dd45b85da12032c1380559a79cd9457e75fb113e1d7a73c0472c9e9f3a712af7cff50bf1d45418b3178fa7408e3d7bcde72610881d4f4e4b36f63958d4fe2025ee5ca9bb92f2c1353ec095c31f7332d7c40a9c814c9cb175e418e76007bb4ccd1b4b8b487b00c44fef7fb6c398d3ae24bb6ee7d76e94528af72a9e3edba6a69e26a1ff83b15a8a4e9ebff0b98d9987c30c19fa79d15d2ff5119afffc13eeab9c5fea4800679bf05d5164464ba3474470d23491e735b1e0cb772fbc2fc9a003024848fe3fa89cba1e20ca2473c8b49234fec0b8b5f5a2e338e72cbf5c14edc0d2dadb5ea6e1d4df311d395b5d570ca697419b4a835c99c5d353ce060acc81db2126d9ddbed9f5f662306195c14efcd67357019af0d7f5e3bc9721728b3a874b7175a6c35c43a2c1ce086f8fbe9d4c8bfcc83baca56954582e938cc21332b22b5e959a33f8075420f4010245843f779eaddc8e0dec1356e5f4c521e29c752e3a5970ff5725f45bb211b44a2f765c76007392429bbe5d2bf18a7728cfba1ab7ceeafaed0597f11edccc9b34eea973a1a6013f7d1ee698c235ddb72cf2846056cf8c9ea2e86e9cad78aede5d84cfc276eee8eb217e9ab8c6410437259eb6a9bc2bb89d7721c9837336b18bfa5b9f357c24dd45854239ea61b7e6207e40389d8bc79a8f90d61dc874bce9e5b3033c2fc2181e94dfce1ab9d8ad0c072f2840e8379f4f350ecf27c192ebeccf4a4efa558de6a43d1027467a6731e99723152a80e2a85f5cc716e4901e13835d180261a5cc59df65c10ef449a1755e2720eead69d7701e8c71ad6b6fba15d84ebd35f1a75190f3c29b627cc2d767b8c724483e98e32802f7e679e1b8b868f74ae6ff7bd4234d4e7e04dc46c0a1a15196885ed9d86f69d30be05f3d70fc639674d1761c86c652a492b36e7ecb32516c97222325aee64f9cb7a170ba92f8af55774c90d811917892731ff378ed31fb1df72db5abf90144fa4f28eac69eb76d0331aa4f098cbd44d5a99aa64b9d66e0191729fede166f1d31a44820a11a02689cc10b22ac7fa598efd79638bff2db056354ab2c9a4767e9cfb1c186903e3c0f1f1bc12ecbcb4c6c425b672d19917fc2e9c347c1bf8fab970bcd149d5b1a772772b9810fe211268c947edb8b4414d2a87ce725c6afa065309422d3bd9723509aa2433b8afd05b43226517e03d1d918e797a7222c37c6daf48b3070f83331796b45afd306bb7100b372047bf719af7d75ae75d02b9acd5cb7758eb7ec5468d5845a6802cf1ffeab86c7f0ce2890d57daa5112d7184ae6f5e1a41c4406569856ecb65c28e117eb442260913c0c60f354261b872b1fb624ffd189d8a2cc31023b878e6be6d6b0bb7047792f03beabf647e692b72bd89e02ef0a1a4ccbcb7dfa569ed141e08c116d1a70dac462eb70318d0e5db721c1f5005b167540fb796dfab9824084919502bf6af12b368de62e14ad66cb563672082bfeadcb2a1091e66f85484297cc9e4a56311ecd018a7bafe49c8fae572613b56a8a8af5ac93427491788c1d896b2c71706957e9801c4db2a745e4d4b2f64414946d664fd8200bb8ce790a9729e2a60a81b92fbbae671366cf651953a720deec5e5d9cf07f5e6621cf59ddc5264502e25ca0842dd38a0af9281729f2b43b64d0f6e858273594195754694f43199f1d3592a147fdb2aaf76a06ead0fc2729549f8714016a6d65aefbaf771c7a53520c7b28b6a6c8ec1a989ec31b1b3c5729151f5a5778286a65cf129ec8d12f82e18556573969e3eb2665cc14a2a2d2c07fe2c69773f47b7d1c9ba2097a8646ed97f3ed4a8791155c3ad42f7c12d25f372fdbd959e9d8d28e82173fb228470c4bc60f4e277422af4f084c4c1b0a4a7ac325de503d1d2b9340f031c2d0f8aee28175d374cf2f4494d0ee3525b6df51c0a726890adcc0d33923dc8cf9c3ac4db1c60d4ce65e2131e85ae54f23352a026520bc8116e73d9352bd7b142f6043dd9b8eee5d18dd6a94f0f4279da508dfda2491f6c5f0b42e7ef4eca905274e9d165676d0be179cfd6c2d04bd051e631327e08681a14511ed331b5a00df0c97d49de1f8a428f277581821300575cc36f53d5ed728c94282474ffa420560742e096eb40a8a0e9a2a1372b6716e08fad676f19251324abac51d9999c3cf046d5d080cc20493fbb491f4dd9c38be0b23e8a1ee29f72b069f1673f98394807587352a8ab1276fb90be14087625e1c93a06a9cd10c9438819a62957a96459d061bb82bdca44fcdeea9d2fac46eb2d4d64fbf2753ecc724b78ca55b8563aaa3b8cc1b064f1718e7b1bc9d90598668de9f67c85aadd9c3cfeb27653839c63bdfcb6cb0b0e04263151cba48ed0faaf68266c4c69dde6be72f402d114845408802ea7981e665c3fc2c86792f7b32add5df737ccfdec62ec72939d1af024aba04693888605ce8cfc6c5bba2feae19166086427613699e55a7280efcbc194a2f290a41f3fcf65506c7195f78669fa28faac48cf34ec40acea64d0facf9f1d3bdb3aba3927816d7e09bc4688296e21e980ffb654b9593df7d962c500dd64db9a6d3ea8d178d6db6ce3c757a8cc38d2f6af85a2214ce55483b87236c38c2a87aebb2c825952401728903442f4e760668b8edcc0bcdbc5e2537f4fdd455c3fd39b2edc07b58d811ebeb56ac93ee821bf5e09644711239bdba5364ce66d224aed941a060317b21d40e0519fc4efa7e669b0f2b5300a66bc9cc2e572b70f4a58825f8dfc9f9d6333dcf8b16ca5b54aa55edf6879612ad9e3c39ef9722e46a175abe61bfc1b0778353f31e931550c0107bcf2418135882e79008ef072ddc4ea285e6f5696bf15869e2e498110fe6777064bdabba4b84fe8adda6a6c727d2d40b6fbfadd856910baa7f0d141a604f041735a5f3a4ed27d0534c1c8c91ea9159ff754061959775d41d17ee73779343270d82b88031b028023143cd33572263a3805c5e128ee09b4b923d17c4a7305c8621f3e919c1fc63a3017f287797258ba7df01ee557dd6de73e3d6cbd58c668d2109f62d8acc797f4c1db66cbc562bb7a3315254f8341adbe2d06c8efb737cbf6d43f0b0952b3a5764a7021feb83da7d6598ad94f9a875cd1588230602d28ab77ab7bb392ee3ca2f2d62585ffab5f5ea00dd4a6b777905ac7e81b3e049f35daf3edb7b1247587e2a46c69b7058c72f23f7ee7709588c0055f66c160e5a3e8df3249bd2402767c3159f21600a1d172f3d4a977c2c203c9cfd7d3426223e4beef17e61a78eb6ecfd0a76be24b757916611148e5c018de2081f6c3d85eb48689b0e5f42945d3c41b75b48ed48e79407210391077dc8107645bb0ebed222964b3251e1ed19b4df8f53a8ae8d8d4250172e16fd278fd0664521632c73520e15ee715acbb55b9737256588b4c8e0797d5720cd46626f66ef5797d838cced1f9ded65d54dadb1aa647f1626efc37c48a5c726d0efa399e0fa0ae9b880e1373f7208593e095dfba39cc3b8b72b20ffe044a72f2b4c5f9e3c29c23b061e03cf0c9f48d21911a5c9bbea5f370fb8528a14fb91aaeafd89dbf800e2f51ce0b08b5324a64ecc33818fd436ae6ef30b5296b451d72f90064acb86ddf1c9b62b6b8ed0de3eb542cb70cdc78693f8b34e3fe9c6fd172a05c2de7bfbcf8b60f4015cc1c92b3091daec9d69d99e353013c5f90284bec724f94005432c89e31a3058005b9eac8032dd7eade1f24bcddda6fd5d89322ee5f77541df61be09759835f9cac6c502322582affafe8b3f38b3c29280b541395039fc7e4b265ea249b2ae33c4e8f8b1e80704ac3e2c84b32a32b78f8e0e656c172f93d1d2d3f7ab2d92c442276bfe0c592bc0a88d8b7f0a7f355c984cbad9b2f729988bb43cb3ebbec1561a28250fafdc1f607e1ace37465e591248536e19665698e273bdd0ff8cdff1c4dad0521ec062bd9ca2882920b64a0bd6b44ca06f4fc202e16f0b889c248e9940f6c848cba125519b8501c6459b3d6bab94a8293791b72b79d146be04d55ba52632859374555da33aea2e3fb003abc93098220eaac100fb6ec82f9a5aefc33c8d2add4682769118bbb24cbbe847bcea92501662296cb385caa1aec37a41fab22128acf14b01b424f924e3df2114d7e2b16dd543dbaca725f57d756f303b61ad53719e4d593b01a242555558de1ae905ed0eda842ffc5727276a216f0aa3be0e8baa0495a3cd7d853fa961a3e1f3c22e1dd7ec1a783494111870a3e8aa33578761abbd9c6d2f7c97cd2cc1a1f5a5a42828125c67177bf72f1a18a0018fdd6cf52b74e77cc5dd4d1f7506f74d4b7b1a728c907a877ffc072a3a2c8728e053bbc7bca58023ef95667a8b111e4a90955b073e61837cb740056346d87b3379a9bb39c5d7f66f7ed20eac81eb1ea64c80f1dfda75b6610b5c4725b8ee0b187eda88e710200a2215e62c697222a0d8e8035008035feb98dadb172332f055a6ba1bccb54e7375f5ee15b88fea56a7e20fca952da09967ed3ff742bed7868696475924d282378c34e861f21ed58f5ea4df8771e7a8d2a0b7cc4e013e40b2642b455fae3c0e7d70693445f9c67a907b20af3e039d0199a935c1d992e9aa1be8f2b91c4de13051949eed8743b46ddc589bf0cdb77aaccf5c07338a7720886d3829ec5fa600a3fbf07a5b18dcc0da3e749d6ffc973b68ad786682f9b7266805d94a63168e31e60be10ccfa2c518380edc013be37fa80031a860463c0722d0bc887b78ea3fae63701327119ba0a5b5ebdbbf0584678258ac0fd6a061b721ba7dc454b0abd6cc77d30a2532520c9bae9bff684ba1ae26fe67b2c4a77347238e6f5cc44244a74c1d8ec037e25ce5e5c6cdfb48474fb0f4c3b28fe23a7667270eecf2e1a65c1b2767a3593db8243e358cabaa4fe57f12b67b3f1416d30d472bd875162b1c7872361fba2ccb6a52e7516a17a04b126f2e0cb8377faa1d9920e87ad82be177a60b878ede2162b81a777cdeb1afd94063cd22e10ab9dce92c572f746f9e51974a15cf8764affc8c91fe5ffaa517abe59fec3b447b9be3fd40a72bd22043c1afbf8cac2d98eec94e49b666d0154b55386a67a52d2f0beb59c7e3fef2193c96d59fb4d79cfa7754a8b956951e8d0e23bee34d84f9ce7bc52dc1b72e31fc767dfadf97ec9354cca2a9467871d5c1ed9c67c5a0ba7699bb75c21382abbf45bc9c2ae8f58bf78a9dc3691ce1d9be2b056b46e0dc85fa259e938044d724d76dd9211d5c0222deb3f60e697ebf5c65ea525e8b486e937889e61560891726fcde4ad07d6c53f92b3fe8b6598ed24a28be65aae0c9c0a16b6dd2175f0dd72312e161fccea3cb8832ca5949eebfade8d8f5b94fcf5f08d359dad3381d09e4fbd536f5db695634340f99b05db4d8195c0565db4fb67c42385709ef3eaa2e70c0321df73e34b3cb899fdc6b78ac7bf1bc4d8f1951d26c9b5a396b6f114c84e68e5ccb4b0d9f5829aeb49112a8abfb00ea736e872cbed131f32b579b08e90d972084fbba999262312620c4195a0607b269ae9905f2e697fb40ec41155d30d2c72cc5b8b892ab81ca35c216f9c78db16bef965d380914675cac4f39092c3793127f5652dc34e69605a49c9a57f1d15b01b117e7684f6eeff0a505f7face9daf370c6eb59667b552d258e09286917a0c9c2795fcf3eed4d17a1173ce2267a749a7244ca465e9bdf416747e653db0b3b5294a90f6e03ded8b72db0695e770027b60051daabb5d7132d4f655ff86c6485265551d68bb182581e4c71a7c5dff3bb5172d390e5af5ebbbaec77f82b645410c3e6816199a874054543b70a13754ffed1723b023dd052e371b39082e06eaa8a836476ed52ba974f2d67e441edbacac34f27893052724f64946fecf2718901e803d0c1ecf7e171bfeab5339165e240c3377221fe7766c7d32dac17b2a02b10b065ff2436b0de81ce46eed91e4885905f1660042babcaf0f35fada21669b551a877cbf697801a1917d13a4cdaf3e516d9e0720e843b75d4a3a2507ae0696884f9c0ba987749343ff08401f3919906d951147271da6239052e72e487c41e696bcf777727d33ad31a742c1617d386813fb3b072971e09063a0eecf5066ba630e43e50b54527b011ee3cb8dc8958d9bfc81e6a31f85fbd1e690286a3c70bbf31ee7479ecca5c9277bf191d75ba24f10568aefd72a4ebdad18cf41cb099001a7cf4517013a945ebf6a436c03089e15f2b5bdc2053b2c4ed1dd15f1d777c677ac6404c43e297322d0c7e364ddc7a24da0ca7deab7241522ab78cf0f04180bac87e5615d2bf9974b82dd74c69fe309b08fbff71067214397b33452de0e00517d63eda2bf75f5a851fe2f23e4ed17f95d0b6cca42372589c43e35cc9d96d6d124b62cc00e69c5e809781a39c128a9d62ac1768af9272ea3ac52daf65e1ca0a1c036026bd130876282a7f8bdcbd1d7c72cf46a1932172a22d19ffe80ce8c2d32cfc43eecefa6fcd98fd91b552dd7353585ba413e4f346a16824e71bf069b8e536442510c9e2736687e15b767b3ee1b1d895754358817206105e7fbcbda712b4446a9a73aa33bd465f1d9cbce37838a9b624a7e5948c72f547526330cf5f12d7e2821f7d03e9cd37c179e7975ec8fdb0d58a1222f28972e883c87d7ebbb0836e3424b80f3fb1288e72149d26ab644d5265d99be885ab6a0182e279564fafa5796cc9c87ba35a7fc578b3018c4c637f93ddbe162f88f472f26a7e62c9a7ddd3b2e62d2e1dd2a1f8dfa913cc7fe045a29a4fb44778a51d7233d6f1734c091c9bc0049decca279db1fca603cdc4a36090e6bd78059cd7b03eab3e95aaa07c38e74d837e8d828ea4e86986f477f1abe5bb8918a3731acb34720c2878b99fe4200ad1a4817084ef5bf6e9546e705b7cdbf00cd98b3fce8c42381267419a055f93ede30bcefbedb47a1f1583b14db61f5a9a3abb1887dffdc472296926695252eb890d7ccc3279067858abc0b3c2f1525005e7ffba224dd96a722ae26efda9a72d425326121ee89d56e9a209b6b0c2312bb64408247bca693361e9aafd6b62892174eda3030e11d83cc93946c39e125a024dc196b43f4a70190370899c3e960736baffd60e99d98a50a327c67fceb33b779e9101b8e138c4b07267b042bb5fa50de27e7b0cc796812aa0257924e6bd4e6dee4fa8ca929439511289553961fb7cf35c4b9124fca70c036281f91741c2d6921eba55799fd7104c72c3647129bb7fc9caf4d0b4cc0e203c9b354be7a546b859d9b515229117fd9825ac7222ca9042d32d98a91c183b9f36374c689f37b9e00346920f42768b917c1246bd7fc72eca40f95efc0df162a21ba6768c9b044f501c5927621cc878da9e722284e861d7fe71fb7a0192dac8994e0dce00c8a643f6c99209b6d0850d2f3f72cd504e2397b2f265dfe543848e31a15c15b12e17d63bbdce9499a35d9e0b2c254b0beef3c8a7d113e128ed9b9892193c5563b8e990f3da0bb5a1ee3ad48cfd729d73cfd0fac32eeb55f14ef2b723c5dc0403cbe4f2863abcc4d1e993bb177130736fe5ba3616d9c97d1c32c1d2d6cffe0cc5b953b0d9dfb00336442c3f2bc072676253069b7a392cbd8947753a568c8b17b00ef497c591f050e0648a3f2c3c722d58393a3eeca5793e2aeccc055d3ae3912db9e3dfcfef25dcedc92794d9520aa820b1a505a607efad99bf656c307bec1eba70eed0b9b85c8d9afc7fcd22b26a23adb6ea349ef1a5818eab8dc96a16f90c8f26022a11bacdc19d0b2450de4e72679abfda4f4e34bec5a3f20504716a0420757f7546c9d74041ee71f86454f8729e98b2ba50cc902b94a36d0d870a539debbe9385ae2ad920a6fbd572ba2b5a725684f295adf8815c94f04fb4895a74b55a16ab74b51362f03c52b05606c44f72ebdb37540274c73ff62bd1ba05e7dcd52012a27cc1bbd563f12798bddefca9725adcd59bbbd010a6e7a17c34e9079c32abc6ab6d4412e83777fd7be7b735397238bde11c59366b07440a4e26aac55b393b5982cdd89f39faf403ddae76c4b672fab622887cf974d3ce8f83718e4be3e1c3bfbd96c06001ca3c2146844c7fa5646777a8f7633528e9d69d4106aa8280108efff87fc56b4bc904584954c292f672501bf1667fe2ea8ad45a74c5f5067e0a2e6a03f9cc48e13facf6ebe4b54b9733c9e6e4c4c883f5d351fb8babe0e97a29c0dc62263f5bd66afa5b76b20216de099ac17d1c0688f6422b2334edba4fe5086f234bb8e60a14e093916480184f1f106fea954e1a945fffa2952ac764e8262d245ad347c5666d6359c7012c54e08761cbe2e289b2a1c7c4192c15156485af20d0e77a6e27b4b4006da416c80fb84272efd5e5b0022360f9ffcf14199ab72cebe6ebce03c1623205afafd8af6325b17276982923345a50e0c76c08015541695e859fe1bc51c6392b72741623ea26d500106d96f3cee96f602ebbd8dc1ad3fb453cb542489cc3a78bec5f1ebf69e06172b0f1b6112e01a0f3b58279a2eefcb642b304ad3830a3e8a67f0145f06f4f36359effd6e05974eb12db9106b90c15fdf6933c1e9aab787c991d41320d5f45b672ba75fe8ee1c1c7bf4109c9fc65bb3c6b276ede2295c9023b2d243c86d1fbc2237eb8fdddbb7a25814b4a852ddb5f78c680721b5b518d5350f4f50173afa15d6daf976f5718f192f166c646c89a39d8510da8a7bb4d8eac35ba33a4a4ab6c784183c38f83622b555b274c8a220400f88b96a9dda1fb900e5d975a13cb443b434d5cf42be7740c5554f42441e9e9196b4fc5928b1bb74d6dacb73e4e394f22f6727ba0e5dc5007c44ae741eb6af7307048aff03bf22eeb8106fe7b97b1e171d772f606a0a1d1acb65a9566a634de114939e12a9c24a4f15aef4d54036c11eeaf725aaf24e45b808909be28a19145dd4c68907caec61d44ae8102b108efc8707672386da2063282d05cacc50e6ee6139fe7e46625e440822e1140cc309c26d60e0093ccbe4f140604a3b1d08d43aad6827dc45da0e53aa73045926d74a11b54652ed3796959334228b49c47fe3798f3560fda91f465bc01e175bef16e6dd572797202983113be0cf9673a1d56cfd294076489f26239d2eec868d0b9c1bc2be9f272e6f0cb318b43ee47615f60c7b0614577bff372d8f1a2ab5b9af874b9244cfc72e0f2c65ff5220e66190bc17aa769afa9c8f96035c15a69af60e50b80cf7dc372f6c487d6b285cbad77421c1f08b632c682fb760f06ec1818c2f29a7a5c1f9a56d877f5fb8dd4391407cdcb095628d969e76199c13ac33d06d940df2a7733064d8d06ed119b20a1c57e63a87527a6949052168822aba8a45c92a91c57a5991a72b2c23deea39492639befb2cdfe943a27f111bb607d31920f98f617e7fcddfb118b667c024744805d805e8a8b5675d9be40d0137cc9bf6c19c2868c90282ee672c4cc879896ba62134790b452881f46f4fd16e2be9a944af36f135d4233a32a72d80810934181bd5f1004468880638d905173514eb46f91dc61dd0fdb60a8ee12aeac7cadbf25c9fc866d916749ba181013b193ccfce2768926b515a6add01c72f7fca52c3e741492a05f621ae6ab3b8ae8141f0dd17b16340be6fd1ffce51c72b22964fa761f9197a9cd2cc371fb373849c9164d031f942c3bdc9d2c1817ae4d283e2b14702b12b2f134cc26e5ea48157203f7d472adc3864d759c9008f0c935b764095c0875e5189440850ed57334e33871ee07cbbe8fccd675af40438d657267ca79ede3bf8f1566a36dda39376f87b3cb05116a89465ad2a77cf8e3924d725c69679e549603a247acccd4e14ab66ac319179290d1401dd563287b976077143d6202fc64a397cf3e67d89d8cdbd42f0fcb39fa923b7d70397ad71bcc58b7726f1ee92618a499434f6e5343908a764c0f826e0ab4e87c592a6ae6d747f25f72f08d8b0fa3fae535905514d7183e87d410b16f88e79bbdf5e3e0e70f29e2af72828afab19d75c8b5af11163bcd2b0b1430105a7fa80b0474844f2768c9804c72f68c19d5245399dd11df5e9679c57878684d5184e7021b64b0d7f0c6895bd07290bf400b044fe77341d2d49c4a110f489a4381fc8f5288838f7caf0670a335704fedc9ef1279210786cc1846583b3739d5db8d04acac7d75c2f79cc3b22d0b7227111fa43bd6d26724d98fa3b0eb9c6d54e450aa2f051edb587130279899f72fbebe4e1f987ba72a9ab62581780604641887a7867a0b52a28ae58911dceb1029cd012c8e984d856393fde421a4937801573fddf9f316857c5d9703ab05c483728eda2aee9dc8f813367a69225323b62d70533cc450cddeb658750c70ace45420d925f3680a454492f1769dc54014946c90d11f9f8e881ca27a7d53066e54d344c4ad42b85e785c9d02b4932771b5b5da767800cd0c8fba486c0f5f1ceaaf7e4407d57c37039c0fdfb07ee613b7c36c66a65c7bcc1861d3ac477ef34d8568f67209702731addec181bf5dfea711a046f27ff22b7cd27014013e1599bffc395249a65c8d9569ec1a81d10c5cc4cdb86826cfac01cf7e38574b28be0f526bbfc072aaaf37b49eb13c16a8fb3f206c457015fb9b73c6c7e220dbc7e72a860ff15572c079c90767eac4a482167b0efd738532adcc0c1431654d96dffe315c40316107933e66f58d4c6d73db83470ae856fee8eba666dd690b3e25f930641ea17e503eda352a07c78cff04555eb0db7b07d37cbc7e2594a04929f6b60b99ba9a88bf72b1ce732c9fc485efc43fee1f7e357deeca6fd98865b72563a6f1857ed7f8ad30ddfba5a180b815737c3a9fc7f5ab284649e7a688bfa50b18fbd0f6e5fbf0487272e83bd9dc17e55244544e86ccef5f00670c0939374533c64fa40b0bf43c231a07a1b1d869399b10e6863302f345243740fd9d21870ec95044b808fd45a5a405d1e4a5b2743f7db17af1cbdc71caab6ed8ce6ea149bc6ac4bfba64fa5ea2907239fa3b36c1ee73d200eecd4a6758e3c8edf037a80fd624a9fcf2363804a09872ec987fe1288dabd814fc2523627a4904c0ff3f3fe34733189964ead5beaa84727a8982a846781b743a79f8f94e0dcd39973423fba1726b9c9a535d80ac70f2723cf1bb6a81664d21b3f75e68d32b4cffebd9c4708d7984390b6e32230d48837291927ddea060a64588d6571daf4b5bbe83cfad9678bcecd2c9bb91d161547472c2e4c11abf5823f7c5218856c5d97fd3a5a0d9fbc3a92f32ef9d3868fe3c63724290f7d192133bd8fe4d6570a6f54d8571b1641ebd215c444f763d1ca5ae4425ce4f3b10a6cb2c6c67f0abe0f69686728b4ac92c67c040493facfe1b7242ec72d5e7b959d2c494c9826bca7668085c0ed8faf1816511349a616dd45e1b352872522ef265ebc6e3fb4d63f6224549c64c6c86c65b1c4d687401154880ad98bc7259b53b2dc67e2366557212dcbbfcbe3b5cfeedd1721558ea24a0a5e537f94e725b8f7d23dba27bcbcfb94d7e4e5418824aba3b7fd07f9ddc4c404014e4902e7265e93e64d7c1be713661d0850f32078fc7c7aa8fd9d6b38dd6568ff35d2afa5690749b3644d682232b0e736bcdaebb40a67206fd3201734dee64ce484cd640725af968dd4f766bd617b8884b7864a56673a6d058bf8eeec96ae11b288c5ece728ec5ffb8fdc351a5a7b342373a35e623ecce510df59952ac8c421b75f30e222f4359e388b2e9b9d07a057347b450067338bb1423e6c9277eb8001ca08ad8ab720921adcb85329ec79b6f686b1f3f39e2f65245885cc93fe201840a52ec1579704d6ce7fe32c4754dc0165ff58e0ed645fad1dcd13ac80339b3de1c0478ff347239bf5d482df83bfe22346f3db194a22c5e11a06e93a10a13f133567b8ee7eb2b1d085e6d278d90fcd6abfb12f181060bcab400b74514ff6cd29b0946b0371c61bdeedc640835055a5bef610dbe5844017c4f3eccb803a6ca1e7ea817fd6929150ea815b2f604b25dea7686ddaf38c253a485745a6eb9701ceb447d79d6569871d7cac4c9118b6d4aeca25dec69123b6754af118a7f390dbca73015382c0c8672a7452796ca695009516bb4a19977566ebe1f7943c99dc90f267d78db7e9fb10ab8ea61408048fb942b05cafd11f68b1a5c3e10c310d008ee85f6856a59751a72aff26a3774d04766855e526c2457c4c17a811cd24d5db4e4494c9b974f8f5a5694ec85db147f9d27be3f7ec2126fab158c569bfd6858d5595bc21be204f5ce725385f1f7a48459b84e191fbd088dd8ecf99877ae1d41fd020614a51199e275214fc6945d6a61c4b4cce0feaffd78782f3b43b5a541c1f023bd151cb82ab8d02b6fae7110a2424fe3662885a392be5a4bc164a6e05d231859e60c004714d9fe61228153eec5d7040de4ff7f50e70307097097237779fffe9b0b782cc61c060026fb1be5c861ffa5f95419e7d59e4f5df0d4b646b0d539d8ddd2db6efc80469e67c1789b53e658a0ac7c01542eef333ca95c4e1a6b63cce96cc845cce28faaa772f82da21b20c159e2b21112afad387bc2879e8afd7b925ee05029ef417bb7b26d71f6f45d81bc8d93ee6d5139be619bfa9ff6c17ac39e4046b5c437d6bf394872c43966cefad85434e79994b0f061c119108ac488beb7b5965b45d353cb685172304b956dc3e10fc9c8bbe32f8e9a06266d0e1b166e9b6a8d3d8640ec2dfd07727e495b451119819d4c3b84e500b990e95dbf592009f8e31d97b704d707299972f3dd79191417b40c9add6eb7583f65bf5def73852ffedad8c1e7809aee2a494cc189620579857602c8526a3e4f5463f0eaa112d733bc45da44d1a328721f8207dc528eeb67f6ae4de2841e286c1640e25bd7ba71e4365e87a951b398376ff472cebf991d8003de45edc319cc755b226726f0225ddacb0e193d31a57ed6e1ea723af08567ed77b0dc1ccc234039e5de7f134339a2327be328412f3e2b2c489772b95b8eea5f39a5f4b5e921dfb494c8fa44a3c698566ebae462c287d571b17f62eb179c78e2a71b68528aca2164355beb084d165f426fd30eff75e1474d66aa636905e4d9715d8343f49ae839463023e0699aad01c0148bbfe4c9ea4713de640af18e6d9447d1b8904e8fea13fe47c99e512e48b1bbb625a0c6fd25890d4b767267f8376ddf9bfadc0d31afb258f9bd200a29b275064c7c1c03c3064e71d312729c4eae4e6be0139e1262bf5f8d6329bd3e2638bf0d0ffc04e62e82be05fff048b1dcdbabcbdcc2bf5118c42c709f0161a85027b7b235c09576e8587e9744f372708508f26a20f5e551933d37e923cc8c940f763bbabc2334ce6f4c37e0b4f472b192bd4c7c53d8f76743e1c447c14e15842bcc0e0c90137013a5edb701727272712a065def9c3ec3fe83eeb05dd9b78bd1b6044f1582e10a8d27261c782b1b727080296b8210219b2a578896987e7c11d3451876cbe313d290b06fd49a105172d82bbfb0674df37edd5bd72a6af74202cce3bf2c7491274a09dce913b8b4c9678dea8027bf5f13ab3cdc44a45bd9fb80b80455704f47173e05366ec7fa01817270f5fb783705e4963a39203c0702beb09b74c73fbc3eb1dfa91685ed1047b2723730adc2611afecad4f5c2c5debe93112a30428ed7c1c45fda01d24fb3963e5cc46aff22eb8fb836661dddad667b475831a848216517fe5115bca2b148d8e2727755d6dfadd4ee275aa433c689ab9d171da593856e0dd0990630f0c825cf58722fa6b5987b0be586981c203f186afc66789cb45e8fd96ff37b7ea2a382241233cd5867b20cf4a9f8f5f7127721ffac2e379c63729351144932732ecf1476703429ded4eb9bbb303830f7fff564483a59e15fa75ea04a3bda3212f76ce6034b54a1bcd6a7da656897281704a21882d9079f493690e86c0e12ecbea1139bf26c47e1681f539e16edab92a9920f75c06a102c63621bc99916d0a9738e52c128d872d5632445ed24f2df6b922bacd970b366e398c5357872d2518c1151def4022172ab293c6659b888ff4df760bc8bd5afae028c42ce8dcc45f65e91e4254ab75c48948cd6a6a013199dab8d53b20a40028be999ea8ceb347f26d1913697cdf7f336638829232a34a989bd6c95e0467d44c94b7e25dc9d68a21d80f0a42c4b62b72d3cef012f87bd041ce284e6ef4cf265831ad7c31114a52d4886b7623e87f0a916bc4a5168a24c2f3a31ba0b1ab653b5bf4ffe2aa1deaceaedc3130caa7ce2b7727d349b84451ed276a24a88f711f7564cd407ec882750d49e95a7cfa524eb5b14c1f1c908f0dce856b0c2e8da9a6b655fb6aae3590dbfb6aba9f5fda41f85b5721c757b438b30e97a88e0b1d3bd3201359276eb822ac42d598b8eef0da44714724509042ab9d2caa8d9dd437434cf7be493c46830ba24d5c30c11fa7e166a2321d5e687093278d28c0fbf6ef27dcc1920b0547bc7ee8ce007c33873984d32b9148c5ae037561ebdf6771906a6b35e23927a9af35d878ba3387b5803a39017ed72355da278a010f9e429a0c63db5b791f0ad638b10fb29fbf858c732d83ab96460703b8f7e5fe33f668d5c17ec98cd8e3be98c38e92c8d692326c7d999bc21d7729c2ccf04dd7304f9737469d9ea44470c61e3c37d888be1f769780c662bcb473dbc3d38d92809f378113b898602b8596c30fd2349d7a641c968b67ad437af2f729efd331c0fd06bf752d73c177f128ed90491140f9ff97db595edbdcab2c9ab02ffb42680f224e5dcf70e9963040dc8c23d1ccd8e167d3cb408671c7fc625f741f0cf2823be04fc4d2ef8c38e6c75d4f5b9c558f8426dbb4106bffc0757984703115e3225d443346218a1a970f09e2179026a150284b0a9fb3b8293eeec2ca80f2abeac8845076ab4aec2a90ffce2f722d86e89b3085c4b7708fb25a38d66ad146da271836fcb01bc30d87adf69f95862fa1925c3e3e97ce8e908ae1d26aeeb02bf17d1fe23180ff95102c55ba407ce4d238c6bd06dba2707a474f3fc5d234d656b11500e181ec2c67f4f7eb15bf5054cc3cca879766ef0db335d2cde6a3bf7727a1d1c3142b94b495728f742f8c3a01c406fcf1a5115bb54d8aa48a1ddfb53588a5adb82b31c1d5a8da965f64eb8b3618ae4ed03f63e7a9b7985eb199ec5ae3744288d79d2ef33aec3d28ec377d384ceebdd9b3e2d7a54e406868c92ba56597240fd9deb466cefef4b8efbd129c4798780eb24d8e93e2a6e77c022789f8f9f7287df7f92038fd9bf00efc9e9217cce0bdaf28878ab7e8b76c59c2021baeff520ea0cc1b905bcfd098509c9f92f3ff5df0fc59c711c473e3a39ded8958810ff72ac1d7538753388aed9a7500b29d74b8667df43b64922bd1a1e6925b7143751725e2ddffdb0ec96ca7d442f9a563dcc95dfeb81744268e52cfb66bd71387ce77296a4bdae5538ad3b0e996d594e136ceb04b1864007ba87c4152fed1bbfc5ab109d774754b2e8a1628b94ccb93117a72ee2b26a2869a17bef012eba0710866e72d8b1e3d84ae1f9326df7c755576a4e13e6c9d066a15d09ebc1cd7231db53c272a0858a3a16150baedcd257636ed2afc5a7b96f7967dcc5688460d45bbc77c20b3addffa5a5f762ccb78e05c32cf122829c4f35f211dfc04b510218cdd3b9fd5ab1c3465b68a336a6687df969c8e9b063a482e34418b6e770d3b90323da06e750d806971c189737c48e72468278558e2e439b131cbd4f88d1dc010883a9bd0c72b84bd14e4a6e0b4278d5d382496f35d7b162eaa1182335985606551ee1d0d35cb31b2cd84b658996dc3973b4d4d8feedb5f51ef36c6a68ed049bf9747bbbdf4bfdaa29f1ec8cb9c53dd5ec4edf75c57fac36bc94c045970c230f3869c6e795667a9a1740c54a65f9ffb383e27bab395406ac55aaada378242380028296c6b0722a68808802a082f6113153ff9e326ff189044246a5ccf556da153760cd739b72826e5ce823ea3f52f70de44d7016f6c6f7c35d407d57c7e2421badbfba9deb72907cafc34b530102c3df195cea52c843eb506f30529d4beaf3bf1bc46c600672173643296052a7a51240dc1c606684b917f86e7528fed76a42bfdc500725e6024b4226070fec09fe1638cf57969f9e5b425a9176e004a92d0fd0d61983e6934831d5a8d24912a6fb8c6e7777a75cee4287be784204524e5073585b1020cd1323aadbbf69a1b5fb59ff92c3bd8a7ab4ab4cdad001fab8e484acb64a503ceb7e3401ab822de6ddae526b41c97d6ef3ad55715a3acc2ba5723d9359fede3a3ef37298ed967903731076e45504744e25cb061b689f18252834ba4edb87f18fba767206b70952920fa8606f6c0ce2bc11599c787fbf96d1f35f2c569b876373b986726055847a4518212da69aed52d85510c7dc90f6720c0152acd99e295047fcdb7201d60e661cc0adee325df16bbbbfe658a7648be2e11a7e951c9674c3c6a83f72e155d3977fcc974bbe1b0e2e5fa23e4585ba191ca8b0be5d0aa8cf518340b172b7b187f53e06d403d4af95c546810fc1af3f469d6c73c780ca0d69d6e2d88f72cb8cd9f9271843207bdbd7d010a181dd364b428b7db3c85c93cf8e369d8532724546326890ff3983f4c574e791468d70e5b5594ccad8b5e9399e6f6a68050472fedddde6e9d3b789d82b9d72064e616b991365f1d469481250388cb8c03c9b4210dc6f9735a09b3020b110eafb466231f0e41d966b46c4ecefdba2e055718b721b0ad4bf9b20fa523eeaf6975727859154fe057c23486942576715cd91accf728d675bb70b3f1b380bb3ed9120b1a6f23dd3d9bc1dc04d8de0982a5cfdeb1b726a51f4a4eac6a0dc2bfa068a93bd6f6902c526f50f54d09503eac8502930b972351a20cbefa24c47f1d4aaeee5acac9c5415d44e8b981ef245327ecef0ae3a72a7efe47f41d8a4d6d5cb8caba03a38a348a4e72a2686ea7f5450dad595373e72dab537c46cd0da1cafce02c16845831722a5317407017351d18133b80e74531b34c37e956cd27c7a1940f29474c95c79b7f4c4bf686ac97b252c7e0f3f6ba672a62f202ac5e0d27aebad895f19169a59763d6ce4cc28a8345f59c3c9d9ecad224d8fd5dbf2561e8fd9a35e876eb213ea11c86df611cafea093b360786af43e72025bcf9ce6971846fe08cc98eb0e56c8489431fc7ee59c72feb64d3a0cfd4b72a971af3f9b8150293f622fa6e9520c8511c46e7264415b506fc325cb4a8be772407f4003952813a180eb7afe32550c15dab4aedc72df254ca81699a5e2256807b98d8d032a2c4930e9ececfaa18a8ec0fa8118153104b3cf74fdfefbcb33fd455c38c8e337ff233b2f0678e11053bf19156b9c1c3c75e48e668be0342c5f85721892f757aa6d32484c34338b9e09cd1f844405484fbc063fb7a454d2b1c6fe72b86a5d61177f67ab0d0f8894d44c9f49e22e53f267d0fe25f8fcbae8ffb81272809bb4178ef29abe0ea5c9127ca1a81a07f4fd7f0c62a5a8e8e8a469ced33872593457455f669f9684d4233e5eeec44cb5312e8301fbbfa4c5d453c05051476838de1e8889e797eca11c867f5f55a2fcd9bf87d8ea6b412cbcace22bfc440a7236b06c91806d7649c334b3684e6b4d86077b72d4eaf9ab1d484db594952faf722c4cf457366133d65964d959f34240536a0218f42ca32ab7b626bd3e871e40720d7440cb7d1ab87362764412c26f115d48620005dd5fe2fda81b470ca873bc72690c585bcf0f75d8f04dc94dc4c42919f199dcb1dc4ee452d2e140a4ec68392c1056d6fe7c16544086151c663cabbb1fd6b87a1bddccbef849a69eaf661311729ff74a8a95d10543a52a696568b7af9320f0325f857443571f4d97204ca78372fac07799852febdb0f759de4c2f45ad9b96d50fbfc64bf475dfc7b3540b2d5727e91659b22061e0e2c6db49595544f3838a987fa4daa52656c75fef68dddd40f1136c354c47d6afea7583e06aca47ea3ade61c2062f5370c9b11d1aeb845b35492bbf351788988967a3293cacc89885e653d18972ad7081cf0cd480e0a6dd072114d9516eae2a182192958fa21d3153cf9110caf40a6beb5a300db7f7cbb4e6bc323e0370c26c8747f588f2b35201af231f2889a983a6e953261bf69b114e372d4fe9ee0d815bc08d63c52c0d75ead6c4674c569891cd3cd709d9d03bd7ffc72f17338b116f8dbc51a96b30b58d505229b40fb12e0a97cd3a5ec37138baf670e9973d5ecbbc333f6db9c9c4a73f4fa111bb854b3373617bf5a26496150c6f972797066fbba0cd6ca1c741f1c9d39880f9f6003019df1761f812f6fd9b8ae29726e44f76328ecfea8731988eccf643dfbff4bcc20c52229fbf9a798f6133a1411e00255c9670ad94d307bc11bf5c751cafda4153409b53600333adfea24dc0a4b5d0c6cc85030212479a73077d138a3636b0364710e681848ed160ef26bf9cf13c929331a5f69033c7162211935bfa98d587de6ca35c77332aef4256c4c855072150239326c148a7a4a9d2cfa0b01da6ee765448cf19a9394a450c3cf1cabb34027e0f79e23da40e04a2ab27763d7f967839bf264a2abd11e5af92d467b475172919db0ed6da5f5b83662a6f9d1aa5e0cdc934e5ba19375e44f6a49534913515303ca9da78bf286cf6e542e006cbe5fb5defa804e426745e2b46b547b6a9c9872f7b897fb3ae60830fe57f9673da5d666704c1330cc6c3d118c84d61ac74b492d593c4e769c2848c8b6439ca7e627297c22001390dd88e7e00e1987c4a92115729a327a85fb23862a00d550c0285299d9dc62582e08c0eff093f36ca5967d3a61ba918fa8708e4ac2451560f3f0284c3b7bddd04f2d273676234f577653fcd3728ac87bf4904c76c17ec4aa081a9775d518ce6f5865909217cff47078c253a87212436cc29fc94ccf0939f49c0513580fb39f897690b4456fdd3a4fec48d789177b05d6b118565a0c5af2ae4091755f0b977d7afc7330c3734201c31540632772299920e7198c9a3144e3af76fc47dbdb6f51307be0313c83ddcafa10f5d6127238373f97e09e7a3f055099eceb2f403d706101e3e0c9f167799f1af698cd3823aa3b46e5404039f39f4b9f8870ba44bb1b7a6507cd4a69675b20ca81436e800c5a31500540f396e8baa45bbe8577d1ef7ca3090a04589919968773d5f250f1278461d768a4e275acd488ac015801f817be4955b339528345e9f704773d848b72890b8aaea26e5abefb8cd6414f0946c254746a928c49024146d48ebbcb42023167a435e44149d2071c3718997bceb73b535bfaca05681dac2cc9c89f4b3d6d01e0175a00ff640b06f5b502a49244558af684a0ff43ae19b9df8a2faafa94257225b7bac7c46d40fedaaa163acb6da8fb72edc7f070c1bf31413611675af9b06a7a8c00bebfa165f4e1b6aab1ba0e4bbe977d59b6d0daeef38cc614a9f7f5cd638b05066376e77ffe07c8be78e25d24a7fbefddbc2c84585133f2d53d08ceb872988197056c11e833561d0af36799c7e10acd7a6b4b7754231bbce5ed40231c72fa8bbc8a996a63d17b83b7307d02e4912b6d35b019c9648a6db051609911b14fc7b58473407fb39fb02e1a8cc8d4dabfb3bfbee0264fad544473761e37e2b2720488c3876f9387874834b3f76af0958f891cf2a1c4a25f7d868bd862606b2b53a10e671a7967ca80d36a634294bef5a240ffd3c3a7c96f019149fcb227994572b3624aee60816fed71457e77dff76c6cb2b5ad4dae0f467fe5e85e0f1e139b72bdd669cd4df6b807a5ea58f2a501f6de9fb495466776910a4f053f866ae3dc7210554711284bc543049e0f7f2075c3a9b6c30844f4c8ce4d6787a5cb466a7b45930d39ba347c004949daaf5c84ca9efdf0931bd86ddf58b7e58c29d630519d21e80c9fa5ec7d9f2e023dcdd22007c866bc6701eefaaf8dfef486cb1206cb11721ec9b633b50047d7afd1c5a8c41a37dc7f9d47e372fbed28c0d10876c4bfc372f61eae2c8bd9c9bd1535aac6baed65d2f0df9390f34492e4a38c1a1cc3da4904cc217ce29049f1e4454302ea2d483ddad0c88621d7a54b28a43019917637f172b2d9fc00b29e111f45fe1500fdfbeb5cf86b459c2443285cee85b7c7bed0727293e0f27cb0035e54fc047695d4f5af9ba88a98fbe46b20b0c56a83684f32694c6863658072f75c079a4a2532736744eda8418cf31ab2ade1bb646292d825ab09411d196db32eedb0f014014c0e6fc68dfdca6f834cbc46e8a2972a97999f3772f911942ac6ed982c4598a7eede8096a738389bb22f8333b0d454665a01d56e723eac616c68a594f8a5b4b7a722d1f0d245bd6c8851ab1369a909e40bb4899a72813026d2e6819e5473cd8ff5389ff42e9cd0b2a489b5633eebf6cf2ecd86323154eb3e6dc80f2b3c3f93a3a12f033a96a3383daf4e276e0528f4205f9db74c7282002a27ac92ee7082562827ae39a5a90447b989980f4662d41dced73f89f07268f1144b5b8ada9c964e10ddba8e9a1f2ca7ae0f34e8e3643b04e8d3d3f6d01e48c9a2704ec21014df0ecfe7f02d44c0f1b2b11cdc80fedec4d61374b338231d72fe5e0da20a85f4aa341171a7e876076523f690b4811becb7c23a68b4ca711c984b4cc44c9aa03b76312c231d1768b756dc21ede1cb4fa446786cb9de44b4290d05a1c562378968765f423d97b1fbdcae91114b81e3b5c66499e1b94f7f6a41346eb9e717121b3b3459f2a10f829f1c3e2f5516fd2d1615fcbf460dc2e31f72ae097276c1c31e0d9aadd6c3f3fcf567a4ba189fbfd79b6b204ded6edfb80972bf56c5695cea67d3eb740521630df7b575861fdf897a0253df851783e11771725bb8fecd1cf5d1f3bf7ea08ad91b96850e768f02fc04f3951af05f935908ec726807412483e8fe0c4014f8f40e659d040e4156ed0fae41bf81061450e27494357cfbe4ca4693e7009c8285e8ed9e3dde384f02aa8a4d63b83d14b22d59a0be726a65113909e325f59805fe5d985ca1d90d812ee3291e2f3e5201f9de96f00672a28756ba631dd6040f9bc2bed2f0fee42b584a7bcf78b65235835a8673dd6e72cf1f5da7aaee400d753be6f11779eba9352d3536a693b0be4bea149a4449002f442c1f3fa0fbd411270fa54ada4b4fa3c44065110be780424640be858a9ee872fd1538f4e4ebe0e24da9a469de004acd058716c14afd915b8b118152c79ed472685dbc0bfd67207d74a7d811463efa654416849df540e0e1080d50a2c8ed5c724608bc8e8f91c852a8a94984da6a097542f59a624b9b5e0711e3b775838f7a72db83940ef0889e90fbada82836dfa3121617480cf4ef367712233ebbd7763172cf91514029db7ce55bce0154a308f975769391e3c5715ade174224d37bcf57176c602498d1ea736b85284daff85dcee8cf3b50417d081ef9ded83431c2527041c9f950ae2f0ef7ad097fcdfaf74513342e208f1edc94b082a61fe44eb3e99e72f763c7fa9fde20a12db1b3f2ddb5c64b851b5cea1595a18eb74193e277755f7292e93b827504b060e8c15748c1800fbee92d255fe5480c488e9908afb92dca727b97edba861914b9b11220c7cfe76fb7e96cc75f58cbc0b2d6210cc8808a1e10ff482457ed2f46bb4f8d7b4347acf3eeaece8a3967864124c0e164ec678823721078e88ad9fca33393094d0415eed1716c7eac641b7361670b0bb9d1e84d4c2e972b896f81f6d768fa963f936934cf8c5d504fae641a10643715b5ee620b6e5d6533becac1c8f6f97536a4ff2c4d46bd9bb911668fc1e95d5dfac2476937257282550134bfafb6c957462f16a48c82f7234605a4d4101c550cbc2f466852d972d2720b2b183a63d9f563353090e311f2bef485aeb9f726cb68392f8ad996ca729ff18c0004ec0331cc6d21db7522e0bce9c2417381070e9ef74d616984950972855f80bca1ad150133c1c24924501485572fccccb812cc63b05751bf62828772001fa4fa671d9878999f9f246cd4494e968dd432f4bf13d1cbbf19dfc3c24849ed7da67b793d53115d05d90933b56b4ff72d65779e2076409c7a09b85b76c472cb2df4e56d51a32a9c7a8905d83cd64e6062490a99c90bba353b12d36c1f9c277b41bc1f89b1f1b27155f945e3a44fdb83a426400350263c82893b13f287680988d06ab39cb57a8db34c49f70984be9bcfa4145a9a1309f22b8ab8a1c86b74722083e09397aed63e7e77d2abbd0244508090c443fa2f1ea0626e6bc6378cac3d0ec428f14b624b1f7dd4755ecca48fc3fe3e73afe1f54a25488b0b305342877221b9bb44f02f32001c94dd14820855a88205ecfc2db7aee7ab489834621ff572c958fd3de0d7733be8b2f44ac6d0384545670b6fcf0dffc1e40c83c0bc9b147208c53b51f7ffd96f231c314b4ec52d18a439416752b0b6f234188b0dd57adc727843de4eff18b8390af8c9e9a1367bc5c94f69f2111fb9556eab82bcf0796672b50a6da1347690e995ed4553957b5df77316537edff68e09356fd3dd8799ab72544b26181a4a0824844d0a11d855396cf75f6eb47258fe245125639a7d3ab972696f75fb6cdc023c1a78ae2b93969ab5b24bf6f988f975b05c9928de44729e7251aa6074a9ef5071fd7630099b3494233213c774e681175097d65e7c39090d7283196a31e2d9af76003b5faaf1bb1a3b44e748ed3fc955304773e11031d91324e56d2fc8b2508eb78a8b2a01bf7475bcf1751963ec27df9e00fd2e1de02adc3b53bcbec805b8f538f902909c6a7d4f7d9a6cf53fe455118b12d167038baf1a72d68f6c58c527c6988ce6b8b9e95bd9fbdc39799bc8fdc5e85c6f3ef8fc778d721a4b0f2e62b1be21757931d5c7a9a2f694e59755c06bdc3bfb28bb8d4aa086483c2fadc9896e216af4451b29d12dc3ed482a8d94ba7e5276247fe0d6b421ec6a9e4b6c9c3590b298d9397b6d57a2df1f1ece3cd05293ce2a703b683173a52e1a758a6d33909fb59b0e380b3a6e1c1fc296e8e74e41cc79daaf6e295c6d4f977238616637f9480242fbe48cf2ccf8b943f32ec582e95f4c54a6171c058d839e720087ca501970cd45486fbe35e7fc48567c4e4ec154f03ec9eab503e0ae210f72dc86ddc82ed7643229247612a6ffa24dd31cb6dfa9f55d55223372a13e7926723a113db99ceaff79ba8ba8b086d5b632c1a9976f77c1a107793b0842c442787225b2679127bfd93fba477267aed31387c8d76d050f3e87196b9528f4a25d885a4d23671986eac64feb2a38a912814621451b82b9683d7a46cf43e46b8178cb7241b912bdfc0020e4c41840daf25dae21a26f5d0a49784ff354b4f7e86cd269400d2a1810ecd714974dfc49c1dc7ab40752909d8cc69cb0d171efeb914095f70936f0a54af133d093f3f7bc596768011b0cbcaec3234bc10c662398fce98f2372409dc306a30cb43eeac389c6fc228ded25d1ae77a90dda2c2a22193de627e17276be30b70a421eed537a56f4fc37b37b1d2e34ae2c279f017fd4821bf835777235876937c2d3ac21541f9f169a02396de3dc7ffd47fdfea10e99d023a74e5872916a100500cbee20cdba0fbe456c0fc6f4a7fec600718c90a1f4f9cdef26fb724554dd0ef42aa7650918739411fef50b95fee511bf13e353f27886116495ab7297611c1425ccf54bb1df7d4ef13315a5aa3916379ae8d906cb821e5e72c95c2ca363585626856b829c0ddc8c14563d257b6f9487a4ce4adc85f7d888e9322d44b01ff8299226b6bba239d1c51a9b526bd6fb0c14aef62a30eae823d1a7291572bb45aefc05fc55b5431ddfaabf407fb4192aabb3055c71d9f56f71f9e31ef269715af325402c45214277f28142a1315e0d7d05fac508dc0a1f5fe73277ff1e724992941aa439adb66b7d4e9f94cfacab1e2ab9552264230a68a918b19f0d87722edf7c833e5bee922240c7df203febcfffe3796cb7a9421cb2eb94f692466a72bedb56dc44d604af70fb155f2f499b2858b482701f2aeffecd2834a88770d4722e2520a9348a8152fa0cc0ca5057a3827d9b55185dbd7ff9a7492bbb92cbd13cc03bec1fa4ed7bf4e0bc23392897e2e042b4526b755abbbc22123107b8b7d172b4262779fdc18937ec0bdf4fe284ceb48ec8676a3f5aa1b4b35099cbefb5ea72c30f3721fbc3eec45b3b7afd54c0e264518da1888ffb02ced52d0eb8e43e1d62ee923dbed8ce6a1eb4542d3f2a631dac390a24f827e9c869d8670f6c03a37272b827b3bee053285c54bb93539d82cf875a5be980566f38c6b04f488dd7e4ae57ecd40a5a403c2f7f4b765462154c249536d438a9e788c01f61b29e7e9f0ff9727e5a72e5e6a7d19813b48d18c984e433e5335cc4b5bee0e3a501f93273469547fc47f246fc3fc7d2e409a16b6bffc7d9636f3a9a1493357029923cd9a6930372f6ebbe96d682df07b47a1a258ea41548af45692d74b30d3b4b5a1b9813045272c95eb10d697e2a171d8a346c046c7d290977047f997ae522d8a1bba9e90c8772d1cb78192618240f0ed7cffea67e77afac460ac5faef60ba87b289c253fa092d783b0b55184bb91be434e4e85e745477b6e290cf19a0618f0e2a8f546e06d972a6440ec7a34e6d9041dff9e8767158be959e4c585b4ff3ee0d86c57a4868443ce9476b599f45202e217199d50ed50366ad40c5bf95c689237fd0b90fa866b00e446d3cea5f7b4f5867fe54c2a8e07ce2b58569b355e3aff29016dc1880646f72cefe9fa16b9bc2d899fa3d92db991601f71c75d6e7ae0a7d3589df6820756a72be226af9172704c7a54ec3785a2690260056136757426badcae1e3362a23b072467669122ed82ccab96342e38639a2001dc357e3a7545f1c630699bee7755636c2a0d0218daf3f3c92bf5378316ae8eefeb2ca7baa6798748d38d9167724c3725be3db619a81827b073244164a811c5a41ad22c7728813b52b14a35ccb4ae672a8fbc9b585c1a5311e8f2fb1b0a12c0c8c0c0af1b8fb62cb805e281260240c72e6d713d02fe32648cccabaa07f55f0a41a993fd18830b83e05d00c4ca18cff72f35700a90a6d90a3c5865b21afa13acc7d2f0902a34a670576626cb57d2afe7277f0d6a59793d2a15e165bc5abe2c096c7bee77d64c058afcdc96ce4c01c01729bac2e9575cfd9082d56da3de09dca2869522bb1de44b30f393a2d1ecea0b6728a1b218c8d683e9a71eda333a42403aa31c20e1a279f725533a865a6497303274b0979e09a78acc21a0872953d81082d21657d50e6cbe1c1337eca07197f8d0a817d666333ac51e9777d330f3a2d255b70624e25ca7238fa5f96b7673f466a7276abae1cc483493dafecda42eea250ed8928ff75a385f7163fc066adf3f6fa5554dc6bf13dc377e9639d72e33f55068ebe9ef55038d6de7713854200dc98e04063518bb49e4d21123d212010f837a8f2909468ae2c60f3dec2827e80ce6fe27278df02f200b7cc625ca8d7e12af4a0df2bc2412cca5bc3361532d02f19b79a722114a688643047e2c6d9ff75d43c65b8c596421756f24003a125d8bcff0ec068ead9d7262cabc6d789963d7b8358de309724ef4b6efce61fac3f99b8a927fc72e81c843790c15b8191d1679555c0246a77787f011ef2dcede5bd02aa2a353472f66e54c68eb8692955608d459ef9ff1844228f0be2aedb977924596438bc75724574263b260666aa547fb86c663bed847f07428642000f1e4f8bbac079d38d4bfa38d52bcad3762bcf8074607c1af2bc0664077592082601a74d4faa8a299572e36a36099e98e24ddebad8d773468c1ac6495d7cb0fcabe1403b21246617a672695a88a8d548af3e71c883dcd5ac8349a8d76d647844a7a270ab2bf664d4a332561b7b155620d0e800f3564fedead46ee00cc30158d16f70ba3c2c904a3c593c58a0f44c13799983a07ca229cc1a251900e4dae77b3da5c5b8e7666d79a9c06a2ba4f6a4a721ac75131152cabf3148f01589f869471553c3c90f0a53a56d710c9fa584055c186c0eb2fbc75fd31cc07bae3076676ab0fcf551d07307f08b2f722dfdb2309af06463728f2555dfcc4f991310990833fada7532daca22eac22c0748e75b518c5d031305737cc3ae17b12c50fea4cefcb1baa81bcb2fb0336c2c01f36e9d3765e881ea12e61583280539ec701b227111f0ae50a33b5eecf9df3c59a691c82c60b835405c1ca2196e971a4076efec674a242b4235b8162c1ad0b072d4347201fb8e881b2f52b04df10018e934c0c13d4d60e9b4af3bf6a9c3666c45c262854cd7acc4d5c7cc09c93d3f5bf4828c71725b8443c533334b28a0cbe337cfee0022c63ef01148f86cd728b8524d72bfda11882af51f8648f16c0d9af3729b05f577cacaa2de72f3003619277846e2368bffb1aeabfb50a052bee16cd47242a58d0f7d8bb4b4a70baf97b04c3d060335523742e9e988b45f45bfa5578872b4e122adcd47488b7efac7607e88af8aefc600e47d7d7fb150b0969d12ef9f72396f6eef6aa52fd6e2cbac9bd783c50831b91cd54e4e9b64e58058750f5863728fe25b6ff744fbb7d727e92e8912cdc2a63a1920b4ff68811fb29f45488558728d392731921e81af0590f70505470aa78788ece7d3d3e3544d0b86a12afd72728e663c38a7708dc3851c2d4b15647cc1763a1c86a016bc5bc61631a8373c347222e931b7cbb5748a32bf66d010ba887be560882adb784fca527156e329eb4772466738d87ce3640d5002c30c730a4839ef11a131885b685e85cfacf4bdfe7472963228721db73eb3143d0e6afc3b39e5ae52372230eafe8cf39981cc232a6c72d43d40ddfa3b52a46a76798bf6a988a0dcd7d0c4db44d80eaa67ced5515a312b44ed39110928068c70d677e4d77c776177af7625880aeebae659d52aa8cb8f04c6a02dd3e762677aac1d67709e1db5b2295b61fd9c4498949633d35ba7a573723185b5e0509c8c7b4bcd790a1d1b0103d5b4a33ab200da1f4d06f6ade211c4726a358c5ee9cca0fd06ff60827a779c06b3d6fd2c818e50d626a22bcbf3bfa572281cd4896ee8c35620aac99dac1e47e74c35ef768b6d504dbdb07914f55f29728bf94f6c55a31e98ca44a1f9e8a075eee3dd6b7103a00ab58b5359176e62d372500b4560a84770d016adf9d52bdc97bd467a9c003226e4f8eea380d76137bd7282d2037bc67680026667cdd0865e1962b3fd5765a6c6174d5dab0d998dd4b472c20531358a22b4337e3b623e780fc35c38611f097872870f3df488c8227aff02e95dccf7fafa6f5149e272f98955ca9801aec7a4568f068ca1da68c7207b1d6423447830128a4f48100afb46f692cd1ab5b1cb2729b78817e99f19e13502a76d9478430f2d90e3cd16924cedc393b71ac035589a1475b5d05b7f0dd3e770f06f942aa702cd57907bd0ca0049036e25335b98212b0bf5355fcfe757061b0bd41aa0db2a24f7c18cbbfad6834091e518c3a2107479a77dc3d46c7d1f6159c7250ef07fa703c0113c141a26664ad99ffed12bacc98ef78422b0895c22e4ca01033c8e0ea407b3ef6bc701d56c40768083b2cf3760f421efdadfff952a8b2b239272a9c697fceddfaa3a98e06724727343ccf69120e3de79c78c9f76b88e69d0c672e3a4112a0c7afb1c6350c8f97aba3b62a1ec817e95535e62c3a7db983bafe71c500b50c1d52cd42c7ee78fa313a82e79f8ad0cb655673e45edea19164cccf972d290d1aa66a99752b200cc3111ea02bcf36d330a57ad6e123f64d397112d415b25d927bc67b1a4cbe3ee4608822aeb12ff29256c3eadcee81c34cefd6dc26e29468de847345b7cbb5c34aea9fb1476ea64da6b6e4fe07156fe982ae9137e88725a4d50e3c17b46b5a035d2014e01eecbd7d1e55e1a8e017cf6b3db7203702223484d12845ba5d3564369936ded7e0e7effc1cc3c2efeac2bfe074e9d8653df723b04b6b5eb38990b8ba7d71b392a2bc9814d6a26ac74bca579cec00c3fd02f56794d448b97c95737d02fdc2d2c501c1cf0131a362e9d5aab82614d5de656fc7276806f6d0f78345089fcc7483b4690e681d17466e61b220c600c493f8817ad72b6c981ebdc780d3accb9736ed6fb0d377bbdee9dee0d4592d84fc1bfab0a7924bd094271adeefb7f5364befed1f9d830a9ab8ad23bbff3f93fae5c73193e90721913fab888a72d7ce52f47e04f7ac224d36ee84bafddb105e4c16d49b50098686fd4007ffe4fa1cfa70c0864c94c7259f96580e90444ea95f1edef63c63c6b72db2482096cb638ec08a130f41a854f6c5b62b4421c69ef85d9df58b5c7607c72613465e78038fedb962ec192d64461fa85ea25af3ccd1adc03b6f4da38d58c1f735669e1ced042124e2958db8a48714cb56082400edbd696579708e6a9ed0c4164acc270c37f037508a3ed0ab907363f202fd37cf8ebc9edcf10dbae8c2b0272e62443d351443a8c681978cbbc6f044c880afeb0faa053dd4264fad72eaeb27207a3ecedba558f77e291a40e4da3bd5394d3c7e1fabb2bd04f4c2abe5197cf6d16689738c4bb04dd88cec0d4ded044247094bbdf16b9ad2d03aa6e83fd0da572ee2cd909ccdf0080961f42162cd237566c9e81f8bbab139875ad010f7fe51172f05e8cd824dca4e038ce9427b0f5bac31d1e3544490276eabcc1504e5ad68e72882bd6588be7ac8f1cd9d4afafba71e5888f70cbefc2c7fbc6a99841c8528172a4015881d39b4b47871bfe2a61e29c2aa24f99d7a97436fc8b0774641d69427034010ef06eada3e4795252561433f1a5cabba7e5e79fb52c8394e4f2a5482c7291d895f0634f93bb5ed066121e96c774d721519c93f4aa36f32ebee561bca77201c46ca3a0332f06b025e5f7f4c3fc09e38bbac68abeac6a6a649a465745c131d3e864c609e5a8ccf9e87ff88cd74969aca4ffa1b1cef7ff1aa162fcefce047229fd875819f1db7ac76182a64c98308b3d5ddd1fee5d855331565653234b4d7262ce318b392dea84448b4706da6b87849af7c0b64191947ff04387021a502b72f5c7a3297eddba69ede417c3c02a68051c6f26ab63fea6deab1aa08bca3b9372f18b0f0f4a4273729735dd76d71b6503d9fefd50b7eb2ef94167642c1eb65e72f41c0387aeb1cc7fcb813e6c8711454d77bc3f4823c6e111f72df7d9c77d92341b3d8f4300a228517a161ec0ad290cc7d48a1755208fec4c51be6f1dc366223034f0f6ec729e6154b51cb08fcdd333a6bf2f5415a1c70bef0ba736369a37c672556978d7535ff43bff9f5aaf7293c4210901252aa6870dec3a9a1382a68ff4237c88ca8497a5873ce775bfea1471f6969ccb71ae170712523cd2e5af0c2b7172c38db1357618dd9de6c6f1a0a2e589a2daa0b6a2b97544b9dc0ede58ffcfa452ecb28af50a46a549f6534296e8955cfd2cf295fa56f854aac70620f4643e523b0cbbb12e2ea2001e661bba1c6a5e5a17f4c1962183d9d799b5ef01620ef2d70cbd05cdbe642fcc44a524af0a9c8c052df41f62ff5b09d6f261c89234fdb69e19303bdf9a49dd571e8f4296a94415c446d190ff8e68cf1d9a0aa6fb4844a1a072bcb10ee3ed0bea6586f9207f58d7cb48e7fb56be068ea3816e5fba134fa6847284e2c23f8b1ce544295d522f3e44d2a37939310f6f0f5f890827af5b184d7e72ffa339f988c0c74701bd1828d3ef815c390d025ba5c646e6078943ba04c7a072b4a54d14569bba0960c9dc2c6655c710d598365591a07e5c96b08b760bb4df72987e204f953ea6ea7e84d01fbea14520df20d3a32f5d3c5ac59ec450f36c232097ab464c652183de8388a71e550587ccccbe877916d392bf12fdd584c7327a154b0b7528d6bfc4c4f45ef98c3998d85d01c002fd3036741c41cebe151a9191723a31f793bfdac0bdb3a09b1b532994a8907f0fd540ea99684157451f3f6e9a726efd8bf397bc202d83b9837f4f90e94e159497b6522c369b67d00c4a14b8f0721c276af1c2a0b767043017de8aa275228b0d489365b2b5f08ab04d1c134cf372bdd0d9b7d5d657844f459035722889bfde4a11195fb0a6db4678142d9c4e8172c6f3d000d12a1b620843ccb4e950a9672c0e0e0330a081001dbb6a169e8de472463b4c7fe09187982943ad65eab8b0c32e4ea29f23ccaf373a237547dc936b64a2ab5284f21d46a5eb7f88ba76d38af448eb94c499a1cbdeb0bf24440ca5e572f0679e758d40a3790ce9e4370e420a7d283e23447393069a4a6169498ee97527759fc6a7d85f96b71a24cd9ee72214deab3f7e7ee8290d4fb5ec32ae7ddff672c9726f09b07edc6cd23d3999c84468f07f443f0135297d49b8ab18164c4ad72bf611d747152f043f814f9b41b03036148c9e3d396c5e5a1fb5df9cdd4eaa2b6ded17fe621fc6691d15e5c77c9ea195d37f0558b63589a53bce0cc69dcd5fa32b506929664d6ece8e888fa3a28470dc4f971904c5869787296155851a5a3478129de366fa7df5d5231d14b9e0fd9f8c1aa0349f3e253ac6ec1feb5a48f72e7c3fd3c73e0fbbfaf6cef360baf9f08e0dd8b276a82d3c88ddddc59c732b948c49729654e6828b33f2532f66dca7636fe50f4df40c0936860d1571069cfc559201380f09d2ce26a2f46db09ce975ce10ccc86120eefde0d42a426db7997f7b2a2972f15fe2d9512659d74bd4dee886fb584a48a57c2277b6df34567bc2b420272f7237fc3f43c03a5bb9acd36c971d28d5dda307c16a824da4b6e1ad358b5ccf7272d0a609fe496ba113b64460123139bab2b47078f04f05e4d694b95d90d1c00472a58860f8d0013ed3f43f6d62b99e78560202b5dcb5a555f4e162ba726c0afd72d609cc3137bf79ccc694c4bd7787f1e31feed76968a0d7899583f2886189762306146b5c395852073154e6af84e1d73d275dc8f6bcdfcb954111d2b68de3a262c948d41ff3e4bfa9b6a4a0a6637098a52ca51527b369421ac19358cb33842e7222d609a154c0712cea1ad21afc52d4fd9bd8849aaa477b86c2db7b8402c53c592a32d15741fd1ed7678cb91359a446d0c2c8dc37392b38ba279af847107cb572ce5203b16284fdbd3af565e61f7497f6a0da78af5e7bede81bfbd5ed65fe6617bd5912b590b15c0a5e7c3b3b8e76b64e18341e8da41fbe7c86ebd75c54e3d66d6027194740ec2f22b2a6c475f7a02dc1010714b932734437ad26f86a8e3b9a7257bcd0b8c8507b9b40c6cab5efdd56f3a3da7f4109d0adbc408e8697e33a112ce3b3c1716af7bf91b45b87e545269a02e082b05e6febe52a14599f8f97a5330fdf7375d597b8e58cee2b3d42f34e2d7bfab216b05d3b49d1916233ac5c94bd3dc9d64c925c3084640a6be57a55aee277d5baeb4bf61271857c052a0d134ac972c3f2381de201f1b993907b3e603fc0f8ebdcd4c1a5aa4dc06dc740b407e06c72092dd91939b62b4797ac69d1a9af88f56a15fe0a4ac47b0e36c8e166109df1720881d0af250a9c678e401d29ba7fb816794357ea673fd4eaafd05d1a24c1c372c36d9ccfa9fb3d08e43a8d68ce86e796b90611f6d62be96705d87d6a9aaa0972c9cebbacfa92baadcc206c84f5c38b9ecc342c3b0396faea40c3e77e6720aa41d544bb18d4fba017f158e6c6625d7d3a2efb473240b165785ed1d672c7f45072b1af59ef67dfc440ec7f334d7cf01f6a0f7fef2564dc789d2747cd8ac279a372081d2ee7ca49a958ee9e7be279d21e3d2dcba4bcb24ed05cb8bbd19d4eaec54636ff18163285735c40cc26bde02a3a04506099c46465cd5c380473975909806689d42dd7573f738458447b2f91275b5aa43608ea5cf0846f06b7534a0127c233c4075dbea82529b27330089333580443c87490f67948a9e74042641abe91d37292c9565975ca87686e002cd972b705f9a09228fcd3a193355402308902a105126d16b3898e3876e09aac00fe76a7219affeee9b54537f33eec92b4995ce4cd477231547a2111ee14d99befec8ef53a73524f3f1f693fc00dc56f9838a372cf31505240fd91a75d92095c4cbfe830d94214b2073d8dad3b7984ca3829c61717544916d61b632d48c40b3136fb9e866072244cf4829f3fe97e5b1a14b1e69cd70c5629ceceecebdc438a2fa7be13219dba1f2e2ac2347e2645f84a53a30eea143a5e132add8891e15d96fa0b4ddd3712421182aeded8146c3c648a183ebae45772a4ac9a257b71f42038d7fe49d331bd3b347ab1738dc830840744faad9d62545f978c2d817957cbde14694aeb716533da0d37b456d3327f7b57866f6bb3afce72b733c12d77efbb3985ca660b59d5c1cf278df7580618d301f686d2de2bce0e07fde5d0986cc403085472a19512fcc1672e2221052063e0b6fe2e805218d99514674d07ac65ced2787bddc5a214eefea2e992dd14869431aae5e104367231576c1768200c29c3a458b8cd1b4a82ba66d8950cea6ef4299dbdc1c1ae32c73ebe72cd1f3c469b6758e2ced229fcade550dad3b4756342f313ff6e7e6cab92f69672cb2f0f1206e3282533d25e2ae4bc9dd3dce44fcff7fedf5a368f081532ec083a446f6c89112788b742ba9c19b1d659aeca4b664060353501b4ed7139dd9b597285616ecf69f10d5d6594570cb26720c47a38abbb13ea1ae52a064fa1a037f2461a37bcd081ec5d3dbf067f2d4c6963df939fca4d32996ad20e8993ad3ad501722c4a3d0e0a5d459f57eeec6d79037f91126deeaeb2ab97c1762c6d4b6071fa434e3ad61de2d39a6c0bb8678609aca398f51fdb441b35a4d3962b0d1f48f1331d083b16f06a6b94863f6155a0a87f82b6e4c0d16842895bdab61607c26508803bf1ed85b317ee99ddc92a76a59aa917c4c56aaf6a35da663dfce1bd0d6fd7344cb31e52bca0cd2681ff8d31b253fb4289cf1e4de6d95ebaeb701093381f730c72b7f69baf0e1757871bd40ba965fa9c84f3cc575c8a166c8c4bfa65126d151e72c76eea4f10b19b9b16adc7f0c12e3807cc4c3a79c7b0a10d07166a431f270a72db2deca3d613606fb371ca96492881f4ef96fc7a2d26aa14c8deb2af32fca87273290c13d6ef420d8107ec4827e63d6b6fe52a41f3045bb123506433dde0f34b59dc5bb7d6863134fdf6c7dfd49110507014b512e3b594858215131f24df2b5ad394f29c8e2d10d8a7c03106384b01e6b118116c7b32b5c446817ec59d32b659305d309e0fe6c0e6eacd58f304825e5fe17d67e80fd4e9dfbbfe5e5e1ddf2b723768a70c8374a961f43bf6a2994849663d55d940facaa22908f99ffd50fd531ad3cb0dfd65c41f4cac9e73aed39d9c1ebbb088770bb5225748d6347767887e12232ec9b14d95de034e1b64efce17b4f77839d3374e3b1e034accefc7f32a7572f77d0d2523ca9590fcfee7ce6f9262177cb7d6e166a1ed952c2c897fb62c0172ef3a68b15f5918681b9c90007cbc476188e5d1884acd313d25808f4224af237224bf390158815ad15495009cef61d83bcafc19f3f8974066659fc7b812321472080b5a8f7f9b1d5a1b78326c8fd4872a80e57b4f4fdeaee0685b452bf88f754351d830df15758dbae4ffe6f381f639f26ed103a96286b23cc9bf0c18cb728972bd4441c4d67e00be2da0f544199f53b89670d8236a294f5d35d34ede70876672d2a90407689b3d69d9d96f67110e4bf5714afdb110190068c85b53ebcd7c547212c607560f9bdbbaaa077c63ba8cec8525f286dd21879623606511b873687f72a3d067805f1ddd456de5086db20ef452b8a015cade57866b4cd760d5954734725ff43739ceb81baf3ee056d6941b4983d65a03174af7e6b86605eaee88a349724b896eddaa81bf2903b839e5c9c710e1826e94ee6abf7093d0eb724ce7318672f435b2e3bbf87c505d6021ff13fbb46c73fdc355a0ec774bee8774fff87f0a72da352820b62af1c97782b64bc5682dd675bf8df3651e78695a103206683f3c5083e6bfc8bf4053b86c56080b6405644545983c5cb993b3da2d66e2a58802a072ccd631bbb76d6d7c27d31ef7a4c3b43809c83eda37d4198409e41d706396e872a25fd0d618804e81462c2c7c299b2c8076f93f92639071d1d33b62cb0c815b729641d8e87b5ddb783cb966abcd4dcd20c4a8a174eca2444808ee24ad19e5e072f281b925d35ab1149ec767ef0040c9b016b6fde8771bfa146f7662bb358cf972b5f9b6a52d1fa9d00c4e240dbf7b4d95667c5582a258f74bb8515e4819065956a0c84ae9ead4ad517d3efa0d754811b14e77722606eb79573c30ce0e6ed2a40f110b0a4e1cc624e60ff57edd1c0dea3f06b1101608d536d4ebb148f605e19f724fe7d5219c519fda2103d647b20c7632a88bf66116326c895292abd4ed7eba4bcff0791dd25550e27fd6f20ade047950d748552e6da92ce2b16ab95860f68c700afd65c1c0909818b09ed8355d466dfd175e37377d4d03490960d3a50d4f94728bfa19292576785ef5285eec31e6548c5243cfaa6fe471c3dee1289be498d54e17933a9990801fa3a480c9597bc5edb5fa8005407555591f50f2d516fc59e018d924d18aa79f185e71eab1de18bec983f3a9d046b3cef31f4c9b06553d4a5e72c38f49c27c331a2d281561df251682cd80bc7d5da0a83856011a15b2baa471723aed0fcff95250f864aea23eed66460dd382cdeb9536176cfd6a8ab32da44e1cf2cb3073f4c7b76aa88067fba616b4afda3cd963f8e8c29377724fb708ee6903757f341a1f59574cfffa644ed4746ca330f31b4c248f4446329f9240326cde6d1028dad23b559f8bb61f689c01326637d61cdddd4147b2eb0509b6027985c272a698907b6f3d8a84d76fb37f8352b65adce599b7c62a2b3a9064f306e89dc1722074b51c8e487b99bd40b0ec0cabcf0ca2b5653f653d3de328a2c67ce9bdd919195bb8c306e74c78900257ba4f23cc27d09ce40a941b2a43388ed2bcf7ec51729fbbbdab6e010ab67b870a60d7f8763487832908d6a83c0ec13b6816f8737172d113ff9dd23b518fa80eb54a01ceb0cd5232dbcfa96a269141cf0e3c5a640345797380c146246277ae4bcab240498c164ee39310a733aa4e3f2bb96a2513986ce6f7373fac9f018de6726264c45924216731f23506e14d194ff1f34eed97715433635ca124cbbb844907673c23d8c93b3838a1c3cff316c786d0282d4552a47296e458a82299313c57acb73151c69c4a87af0f02d56d8b61039470fcff7077131e70b04b0deb48cfb98cb23a793f798e40eccb9232c8c7399d6d39a444bd7472d31a645e1e67637694d5d49854f22f313e787e11fd3f79af59b442249e13ae729e4f3a14d756952b33ae9b8e23b3f45262327f97c502927f0cf7fa0bd5170872966cba1ed04977c95709e72bde18cd98d3f771dd0c497b8d91a851266ccdfb608847e9bf319a73500bc4d00298fdb9c583aa8585a2fb2ba7213e3ac71cb7386f5f67b56aee1e5623c3f54b6d303ccef51f3076982deddb8c10ef4fcb2d11a0554bf260b8539d662746279271b533815653b6c0bc1c951fc58de8225e0102247299836eddb5fbc24fef2f3b32d95a6d68cc52a1648b74583bd337f8ef9e8c635ef9d1cd2a6a34b01dad9bd25a5ff120bfbdf2534ffcc3f8ea60908d3f1a0633724e7fe62586c217660588c101860c35ee7f929b8647c9181d78d1a10cc4bd7172f35e6f0eb372e93c4b4bff7f84e2351bdbbd8142e083284bd08be1f4982bdc7222b51d925087d0f844b98c0469d6a27844488afb4cc84cac67f243ac33ea1b722376afc37054e25fe4c86d84772d91ed3c2fb8bc839aca89dc17137f7921876485e802652c2ea3a235ba82046d60912884cf0679533b752d75e8b4d67088977245a39ed51b24c425e23a80b1f82eb312b922c763ba0e548dda08e2f890b71d72b9987d85b1822ba466233f6d624336d06d70ce763642690dcc52d41b08483e1ccaabbb505ecccc5bdc03990b0744a05da2230caface49b988c48a9baf8b84972da46fd1e29da1595b419c03387f6b8090797405d0e128f15a4250e23dd4cbe1d8c7c8498508dc8953f85657c2e639fc6a9450f7b1312ec2ec4d73ba9964298724bd453cc91f53d788efbd6d616dc3783c4fe677df8f4d22de5226c5165a0ca7247e0234b833c67f92adac75bcb3553222faffd91803d45e949175c7175c2c64c907d819d03e5871d125dacfa39386c8dacdb287b3e464ac68a7cd90897bce972095615b9a51e19b298a9d46df003c40a1f3c37ef01f073b41c486b43b9cdb6213092f1d5363617891c019fcaae3a9e7de03dff516446645b9f4a3e233a1da46f5aa89c536aeb0256188e10e756ce6e4804ed0b0565643c5049485413bdb42c16efed704340e3f6f7d50958bf2aaad173130dd8bddc7832d1b98859191f041f35bcedb716b471a2a0f25a149f6813b20f6782b5cbf768886604c15bed289d7018464f38284e877fe6fceb957ec1d92f34e4c943fc9b3a4706c12346e79b76ea726cd01231f5b39d3f4f1f216a9e5db8432b4aa8f67c8ca78915332c2b0c229572666ac8967a327dda9df83da6c00ea4a495f4cc85b94d4a3fb32c99162fe0fa72fd82c6d0e06e57bc4e967c55a634f84acd41e1f457b2696ae49606fb1d9776726ab23f39b9e2991f607dba38c6d90782464dbc61eb36542c04b07e2079d33b4fa85b7f9942c84e7fa6a3fc6b787db120eac2f229d5c42c5cab9fbf93ed223b72d24c4711abb62eff301693c973eb81cf7f24dc0a09a4d1999b88c000de56d71fbc33df846db6c504153be5febc8f76190468f22a853f135f9f92ed0ef44a44721df080958afffb3fd555f14470df5c1bb374ba47b79dc46d419954d431c879722a88d573e51ef27270e72222e1a01a9166a7f2c20891083954bc55cb15bbbf723788ba5d2e192189897db8834b80cfa26cc2008dd7ee7d731fc1d3b52f38f0721cce5a57c6bd0b84392b724d003f6cc363882fe904373e88fb4bca06d071327243bb3543100cb47ec1281af5cf7dde96eccf44350052b2e0761feeab39d40217e0148026c17c02e702de8030d8a5dd4c204a5b3f5b823e8ad4c79d8b3fe0b272d423fa7f0f9ebf4a9734ab29d6c07dfa592d05a138e03c063303c047b0470126f5d7b92912fbccf2c4e9b67b02dbedcf16cf7b26a69a99fdf0714abfe3678a201bc497a7bfbbb671b4b20bbab113a3ec6532317559ff176a57493e286fe94032982202ee5cc9fb242dd54b35a9ff0b3f325e57ee507fadf77f9a716f21360d210349f02fd19af8e20223b8a8315e654502a409e7b57d604189d8eb77a0d53b64aae8b491689ab935ae3a33df64263700737287ab10afb87179a50ba71190b17262fbf9ad4b5c6703f80b2817b6008bdd091f3f8eefa05b6cfa3e80b5d258b97202ed3d4b130bf134476aabfe7424bd89844e54154ac86c74f55fcd351a8dbc0b7bae88132696e2bc150731812dc3c4e0a0812ecdd679d0b96391b8c0c74f5510d81d1f63e51a5e05f62a6bb5227635f275a38c80b4e239afad2698ad31075a72797588bf9b59578299838f9e754b7de2b9fd28f0813808f20eaea1a4f5037872d309ee00cdfdbe4870ebca2bea05ce61cc0d7eba28ee08dad1bd8caaaa9b0d102a201f06485b7dc1f16a08dfa6f715038babae19c09c9c480b4e370b78caf2664de2cb21fbdddc4c175f092576ecf5a2ad0d1d0aa901ca48918cf95171c4377228e3bb5fc3b1e0308c68096e30514ef1e474100e2511dfd95255951891587811bafa111a3910a50600c35eb708c9b880e16e0db2890f0d04f5252d3ca15164725350e06c0fa65dcd0eb8375c383d0ac276ad57f8a9abc26eb33d20e0cd656e7249061698ce5855375c0389f5b3477233a68771980f955d3e552de0db0196004c0dc95dfc80fb931b0baae330fba334c32fb321ffb2b674389cd3c82e368854720c422bac6849c61ee6413c2b9d5d01f3e76cba281258abba8556a4a662ee1072b710473a2dd0cf1514b367b24475861dbea05b158935029bcdf9ef17d19112721bce1802bfb9f4cde6a17af130d19b8ed403a1929b5980e88a4b85d87076b772fa8cb9ecccda14c613e05b31523233aedb2d033ff002948a4818bcddc93c33727924b295a31d46128eb5e37efed82f569c906dc3e194c940385303cfe8e4c572994f00832f6f32d05521fe9d00e52f410fc0cde8e2cb692f36e6aacd9259161d7d1512cae45777850e0f32a59bcc08e94bdd92771e93535eb6ef62de5acf745e10e7f24fbf871fc8af1286edad71848a50c42a8c3d72fa30bc84d00a70282972affd7fc26a7fa482d8a39b8fc1b80fedb8e04790dfbcf8ead2195eea76d15072a0d19ea1ed41458aaf0bdaa5da665ce45e29ce6bb6a7f71e2ef4c1eb36927f183de360a6894451fe4e891f076b0b3ae75bebb1bfe557f4288b6973d90b8fff7280c457e2df39dc7ad13b3fbc1d9bbcd2804cab59ce6389870be4908ea03157722ecfc8473f0c84fea171aac246c6454cb51e6d12961dcfcd355e785dbf15ce72d531906d6c78c312fe2c891c909f9832251aa8976f2c653c8c9236e35a5d3d727bf1c89340e6bd6876ae7239ab0daefa9d8a5f7083e2bd1cb0b41a86e4433572474c83aa1d6a819e7f0e2fd8ca28947fb5859715a78dfdbf6184f23269d6c2720097358ac326a73f0cad6617aecbfb0374fdbb38c76aa6d860382fbd0226cf0b64b8901b57171aa351c9e06d485c675dae328649bf81acc9201aa5df187dbb1e1c9ad0f6fc81d3e66c2b95aaf5444f71530159d43b81deac3796c2a5d800a34290c343648c2d0328528a5e2ab985f8a904560ceed92024bf884bb892e9df9f72b398c083b306f210d88b45928af2f65536abda420ba6a2c2d152f9ab643ddc727b545b31fea2fa721e265abb2ce78ffb808944dc5951c88c7590291a3315507291c9de8686ae0bd7ab3201249ea6df772b1fe638de0dc34388dc91123a6af272d0999ee922f1bbb49ba19b8301cec37c04f3257a4e157dd27194d6a9bdc4f17269dce61d4a40f1387f036e7a71d6f8f1f6fe676c9475ecec03a79ec8b2d347567265bccd431b51d690eac2342c56692962fd00d8adebd55001ca5a259431c97207a9275fe6971a09d4dcb5c605df77d6bf2d8898bb05b9af01294b3113468b72004677432eea4a892023894b8ec7e7187d9d8d2d64a43cf6e46ec10a4d8ef61c8614da219ec08018698fe4f90e77595ad43afee53b9afe7cab80d59bacbc5a72089eb386906e40a225d40fb2f78a1370a7f39270ce8a7bdb26103a5cf256e143cabf90562c5287f6f2df6b174986f4056e54547bb0b31f69291ba6f66c75ff727bb727dbe5e72cbe3ffbeb24418e6e16bc42ac3a31544d0b4e582e135e781228abf5502b0116cd721c1f0f5a485d8b444e0ea0410f36fa4375e790c63a43a015e339cfa26ef62b6045f7a768ecad402557dd5dc04488e4fdd1bf41d1169ee5304e586959c2717c99c2432789fb70256712907b77e8739a85ba675bb635d6fd72736074d1355839346ee95f72aede25686140f40910bbd38c52aac1b4920d0b72141456a5ec553ed54ccc1ace3d1f424179c48f686e75e4cd0f64f6125e29c272acb5b0f594e3a088d3518c469baeba0c0367886e1ffaee439fb1446edbd66e57ae8f76f142cecf41c0fff901f0063b789bdc1a8d327c8f118892f387ab0429323d1a3d4e5fee7f6e374ff89a3d92d1f67920b239f73ed802f75c6209648b2d7203b8d477047a4b5cdb03c77f9dc689b09f4e3eb6c729adbb62888875afee4772b908fc2df1790f68ce6a05dd3cf43f78a99949b70670a8ffb128ac5b9c1b1e72ece6bf2910aa7266caa862beb27771b30b901a5d4602d36d480d89bc7d732572edb3021d18b5f2ff59cb09ad2eece1da3fcfc77dd8ef5595394b77270ce10972b049c3b30008975327c7802d20a5db2bbb58a12245437b669959ea6752b2d26f2d8472707880994ca8b1dae367890c48b0ae0d6440da47e0982682d16c435938b2eadce15e0765ee7847358f4c21a4edbde9b534347d397b8204fd4a54ea727208ce2873eb75f769a5298c6ca567d90324bafb94152a32b99100c0aeba454172c88a68a5cb730932f93b5f45d931db8ee8a7c22caf75d8fa1096e9f47c82337241c5c973f0c7faf651bffe83807554ce33c8e4ccf8ca9966d0e117156b1488725d6ecada663c6999ce71f01fa123a2e2648063a3f1d190c56ed3dba9a9a497728c4f3e1655531e47f9b860973c63a21cb2a7d01a15ae78cc6370bff485d2d572ad62509a56e2bc9b3c9e2c0dcd7ce510c8723cc2b2eb62299621a45ad281ea72584239dbba64c3315f6c7161985048b9ffcb7b4735c8fe4f0bcdddac02e85f1724deda26615cd3686377c4f204727eb07619d527f66014adc469ed6cfa32c44b79a9773754d483c834be6635256f561ad575a8fc04231ad8c7e91ed84785ac72ef3784b128486c24c74c0e3363375f25c3f2f072eb63fbc2634849306dcac37263aafc12e63972ef2c60989f609f3c9fd2cb22206220744b4761dc3f02b077720c710ea368877c43c6b10cca54885931d3f7005e5f9478a2ca4b0f50308eae7238b63badb6d8aed6a64938b2922279a13958510bdf609c54bc696f145dbc212a28335f2a8c3969c46925dd50945533e8fe1aa63c79f895b18d76f6bc0ec2646650914654b827f3a6c5e41aa450a239bfd940e1fc94ddc406038c50877a465072e1a1707fe5bd9e20f708b5000d87d963856c9bd9853cf51b46c0e8a005ad304344e26a4cda28e880bdc2566d6efb7adaa0b90affed0fef25e381f9eff5c22811ea786b1eac3d8eaa568a2a6d0b40adad091263c633167ba1a40f9f277705f432d1ebff6a6597e1223879b427fac424b9ef861f2a5654f2b3aafecf056a7a4a7245474bb4e8f477d1e2618aef35e481785367716069f6f18a3f7a2fc453803849b4d6425128f0e2169fc7853716fa60b11116c3ea1ae241cc3fc74ead87eb1472556657912cef86d1d41f6914cb7607fb95ac1ae3cf6e7009155625b043c21f72006ace102c3177f56c2feb16e7a600d3eafd3090890670e1e349496c13fe8f72901c5a86a7226d8664ba5684bacc023de06b7d414a8e4f3b33ac4bb61af25772e4f2d79bfac4150eabf1beca83cc386bdb23a6ebbe88c25d825e7c78f211c372d6b9f43f2c74d8bcfba02578eea5a093ea7a89d02acc47d8cfd63de4024bbc724b25b257a4746047d89ac9fb84900c3c41eba4b65b4fb650316d559867a53572d94d1c27f72be8cf300a69bf286509c41d2e41446b2c47d0a1355e757ef32a72bd77129ce90563ed9154aab9870cc24217f57f35717a103efda26ac575af1d7211dde4da9a519cc2522f0a619fb0237d50d69a8c88c74feaeb73810b8829a6722d73f7446f45a10d532cf13e172d6a54dfe8dd16f24f08d64c46451b82c866726b55d9f4eff01d90bb9b88d321a711ee11eb45906796ae4646cb48b4b4a7113e29365f5e6e0c6268adcff1f978269899de2e13995b70b9d82ce1b07c64396564db29090119b0ccc88e85c1a87ad32fdb5fa025f86346270ed074832b143d6d723040136419fa526f0404fe293b4ee5ba1a9397cfa02d05c979c27eed38f4dd72b17fc3382c06e27d63a25214d0f7ac803a520288db8a2c7be1f5a94b7b5a12168197402cce54c34998f24b2c17f1026b656bd1997dbba8ee8ab5a1521bcefb2cac11c4244b71455915bca4d66e9e11901f1b4cdb3b206c4045f4515c55141d6aeaee67f627d58e3503cd7f8ece4297c475d58d5b7a7927bee5551e200f7454727fd4dc300e7d2fafe9f3679d670147916591a26709902ad66369dcf4f0e9b86531f0dd1ac26617d2120fd6562a60f8d32d3268f7fb736e904ffafd94afc0d572d2a5f105c1cbd26359395e56a6da1cd8549a51beb6a1738d948b5b41c9c0e572ef926dad8a18479bc0e03a95ffc46951705e199a3e3894404cc68e8bff5b9972caf4a1559351c35efce86af14f74070d32aecde967283e768ecdf421ac30a8657b0fe7c5e38daedaff840efaee516273c0b1e74c456cfdc21b26a6a0b8d03e72b4176d4979430f59b07f7475c1e0387ce246668ddc7f3245eb24c96c89b2267168d55c23c0fe941ee83c5971c121ce8f276a1c9b484ca9acd68ec8178953500638aee73cb6ca3a3d96e42de581e75a3f51656b40bd867118f31c800c8839ae22ec64568615cd26dad4a28e2d93c2d7d6febad90f2702e22cfd18c89f5405345a5feb2131b4e37449ee2f06844d6c510477d79d8ac088e3ab70428fc9f7f2c972642c88a1dbd39f610f4fce7dd3404ce78fadc6f622d4b57a86a55df662d27a727520e900a0679374ea0b856549fc53251b0f2d69777c46a9ce44e3466247d872e8be51e68a4cc999c27b4dd050f969ef48fffa106e2d617a4f77bc41675a29724b6418b7c79a7bd14d122c1d0b3e5f399de9675442595785543c3edc85589c72b7e849ca7b244c84a50e4ece7f46590d7708df40ad2c273e31d9f5f94244646020c637b367b188c61b75cf86225da9363ad61d1fe5341fbf7c758685ac0b2a7217e7ab34e221200d222b59ff196a3af921d2c9c1f82651c2b25b56b6f1a51d38c0dd124d53566f455dc84746d26f975335cfe806ff935e912f0f21c922e9d142220fdfa7957a21ddb07bf19ee4451c53b57cddca0676acfa8e453d3288838c72a97b84f12d70fcc5ba89db59c369ab3bf76024178a0d96935ebe93f00a1c8741db836815eeb075cd1553d2c3110476be5aeaefd7692c2a1f829f89dc17bb5472a8595ad052f42c1cc3c689ca52c6dfe7bb077f1d1c5ba93c0170ceea5d927272714b292c1023afaab316a9e6eba6704b7d17490a327ef60724c2113a2b88ec3b5c4d9a1b0eb876226196a4ed3c23e4117f77649c909dbeb6cd8229dab3131772643651f8dd7c8c0062a1de21762be2301b287d918f81d96af20fcd245cfb2d0f64ea9f9dd8f1dc403afc8e3ba3c5ab861ad4dbbf3cab0206770294c0f6b87e7228cbde678245e235621bea216bc2c8f974ea484d159ee49ed7b4de72b403864db8046811f0fc797b75231e44f20f96658a36f58053b85520db7dcc231bc9ab59b339d08942fb5df7030c0e1c90fd19ed0c752aaf0de36049d446922a4264a1135a12fe2f621157f31e52c9fa5a1f2af48f4d9eeb2d290014517f5fd18ae7987293c012769cc6a8cceb4bfe58b05b0846072ade71d224c7fc5ecd981f78811372e6a6b2638105b38333822cdb6829491470f8d3bdc1588bdd6b0c2084ca6516004f780fe2b5d813cb5de9639168de41464de0bdd712b839ab354daa435c708972ef5c3ab3831033d2bbf4514c8bd6c2d48bef3ecb55f86098cb09e457de72d31b7310b1322c89c43ee524b02c8aea9ec317f708fc9fa79b93963f66d563359c4f8a97bba90ee09cc2b406f2ad58673af889c4b194bab64d64dbe64d8872750d72e7838be01ed76be57ae7df7bcdf4ad47e56f7232d49f8612ff78dde84e43da1e823d4a21b2a3a92f7a39a63dc1596e48ee578f318e4c3fd3b5dee17ae5d8af72e4a4fc85ef97cca3a47c8de1b919791348e7c2a5c008d2c06e87252f0173917222e45a5109f9049496981a32d0281369e5a6c35afb5bec806a7565cdf33c191e3e48e8a8d8b43ec7e1f3e52aaabde6e42c902bc414c964a28b9c0218bedbbb7248ef89c517dcd048f4c0d15c7ba44cc1adcef0bbb0494cc15c410d2153dbc2727018932e11b730ddb74309f3c71de6523e2669a133f5970d3310920bb8028672db9fd738dce69a97a66d68fd54b8ec3c60877d4a7986cee9c84e5a2d54fc7946586c1b8278452fb55c93e309579c1a1f802b7d921794d2264ab7213060fef6720b25222716e2cd9c321f61bf628da54e4f97b24a000e16c3f873e85484f34e46bda3c1226b5c0f7778a7ba1146f21f13fc59d1c66bb3eeda50694791bf65a3152960f1a172b672d9b14f31514613bca2dfcd9196bc46b45b1e6714feb0b2761bd754db05b9526237ed2dae718e9e4ac194de9548507c0af77aa017f5b7f01d6f0273bc76ab748687c513248565728e38d59c7a310dbc2dc8e75bebcfd650ee72cc9f3ae5fcee1a47affd9ffcfa4179c119c4042d7a5ceb2ae6ea86ff1b713b7260962a85569f54cd0941c5ef39821f165162c56bc449bcb5f0efc765a8d9c672d2b1017edbbcbe958ae5f978d99aa1e02dae4e118f38f1c74ef429499164b3056576e9455f07235c38c2c69bbb34b50f4654d895ae64f482ca3b19d1da5a6d72618081e49c71803c07db585742f9d826baf848ccf897dfd6a8844fac756ea50443c5300a62cbe72c729f711c66c1c2dec74ae535eb711e464d93512196042072b6113de63f9500148a1e91f51b6761ecd8a86127845d2d0fbd64203b08c2be723e13172882ef18f584564c449a19ab0cc7a5047e79651c0f8a405738d18a507252b4cec3d62ef45eeaf06089d5076ca7197d874b452f611b94eec52d8862355acd780f03a3af9c9330d17b6f9e4d7e60ce519e1da9464068f0fb8ce4cef230135f6cc529321c937b7816069e515db3be3974d98c4d4545d89a0b31592f7d837211f3e76f2f1ee8fe964bc3ff57e7d421d483401695133c57f58f6e881e7769728f89712c41fb9ed1c5086c51238b05eb080f124bf5828a69457de4602c9805720a0f0edc9322e90f73f56ab8ffa9a42b34f2057a17a270f29adf4063c5042d54c8cf520ca5ed2b478a611c78a6f267e8a962ae04261fbeda1fd9150270742072dddc92f1a5824e1ed2038480ecbf4d2d346d5ea6f3d6c5e49135ec2893a6fc2bdf303dbe145f470c4da33abc91e8be1c3544b83ed0b726be597abe4a57bff872c204eb579f44be2b683fca89d2c135129f36f715d098d0caafc2161d844e721d8b79bdb035e2f946a701d3e6213e19c7ae8a303b53349dc008504beec3f13f06132f2513e56c0556edf35f8489852a8cb6332c676125c2fa259465984aab8634c751518f722f86f891a9f5dbeaab0a847be4f532f9542a6355c70032097ab072d9b4358a3bf28ea82dd7290523fe13e47b4f0d131f154daa3e9f7f3777734072e1039d3a792009c391b59dae7c96b5a764ecfbc7922f56829b7d1be25b6049729ea9f061aaebf3f72c32af7b977ba87b641a6e73063eb2988565123dc30ade727711672f30d2c1dce19f949c51c5dbde92a1f287349736d6402f90eaca837958c97fc19066a6f854a16c8504ab24519e2d91e27aee9a8c31417aa05dd723233178c9cd1743b39b66c91fb742c76045b88f34fd387df421fcebd93fe241a91f72f2da90381c8c8f2822c40203185806955f9abe931ffbaa4655b1c0c664dffb72fb1d377191661e04a795b06365b2caa4a853ef7e8e1367c8e48cb7772237b0723d3576d64c998b71874127a18c8516279d1f2f3b8aba8d27f04383629adec972301842c32fee422e5a29eb76699bd859a2b5c3ac92a55416e0ac2a6ce4e86047f56a2082dbf377b9c215ead1a5ad1bff67762cb70df5ede300a4b3c3b4a4a5075da8a360fbd3811a5666b7d1b4c628ec86abedf4fba51524eb092fafd36a394c2fd52edebd455e3964ee3c9fd02c11d6db23f90d7ce984338e9ae2e6458afd569aa937f0b5981b74d6ef64684f97c6092e0ea15cca4a3ae34b8d6a1f7595fd03bed515c6ae4d870431f955a2f5c4c329e375bf3d5e9781ac9a94b4cfeb5970209996d9eefe568e8f1580fee19d8cc7583d5009ccfb365f77df286cf863bbaa43d2171a4c299b4fe98145360d4bce06fcfd3f4e39d321ce9292e46cb21e12a5722a764a45f0b03b372caca712709b752fcab3610d9ec76f21c1a8533072710172541b570bdce67dbd552d265250a6db815cc3bda81eb1ad7d20adc75c48fbba72fc64b7f177931ddcb3762b4439eb164f1cbf5d36bbd5c0b57ffb002b05fb50325e91b64c4ad1afe1b23317252dfbc6e7b3d231ce4cd475c7ed817ac4b838ff72b78137da8f84ba9271f84b4fd3a8fe48ad43851eb7b67652ce845497488c9d725f21b28647a91f88b01ddd316f3ea20d0e0df57e708a1b0a688b3f842aadc37250b6659858f2507510bfa4a18b288e1fbe6fd1b81f51744ffa0c2749f8680e0ce8f15f02f719b2b67e89f8532240d1b16ae23eac7be38e3832c7a30bbf3857728814d723d3f58a62b851b04c4971adaaafcc84d9071c01fe725f25e2b5237f723829c0cf0df787f8d4b1379d2dbe0cfc36ddfd92ff72c3a457bfd00b8bdb5e09a1e9b954292cb970920d392a7833f5d651ecf29a53d6bdfaae11b8392e9e132059e791032c6f7845c4f19a4d418fb36150e2ddf7f3cd71155601d9e55911d14bc13724ce7b38d2ca87d95da90f259a2578401060e2f4701f7ae9530aaf1e9a67a7a409304a2d79559ddc3945d4e80c7b419ca417a27fde5f3c6c3d2c63b5f27258f36ac643093630e801211bc6078b28b835ea7aa29d133d6686df42fd12ca72d8d632ae4d2ee3facc832f615da7e8e2562bae03d9b533a4fcc806b8b5074072e8604b9678672d75bc72ee9d47f64eecbd8ad689b989e98553f584daf182746455776c510b4791aff66bef6faf700207862226daa20f60cf59cddaa3cb3c0d72cf189fe7797a22bbfb8287bedcbfad1aa7ab90af0f7ee4997d299a4106b1aa72ad35fe69ef4e34eb41edee09810ec4ce7bfdda1a549306c80ac40abc5dfb7b724855031e7aa1221cc8b584d1dd67f8ea9e12a61002e4c2fac02e491667e86c439500080501790bacbdcd142057c3fd9734d70137e19620ba834478ef73b6b772716727b10085d66562b96e22e9352b0adf0efdf5453c2dcc42af5361ea24d0726c4c4985a98a4a802c0f82bd9944803e54bcc9f31c554731b9a3ceb9739f695be7a5ec19f81a28099985ba788e1492ee81aae451d2dda0a87fccc8b28cb01d17708e1997e31c8cfed2a7ab24658b65ccaf12e863e949b48d0d5915e9a752bc51a3e08b6b6788446108a994fc2278460f034bd8f00f09d2d766d4dddaf0f37672a1c941104b57d878c22a9ede02dacc16145551574fab82017cae0c4d38c1c90bf611e32d5ac086013a68feda4d830b66708ca05a076ebe33aa97cba1d3ac1172383877e48791b24e93ec86844d3dc8402da7683f743ac5f1f2226ddee6bb437289f4ad9f4efb94af03746da7e3c767bb550f638dff67bfe6f5dea7d2c802a44f983de5ba57460fdcfd50407b9705b2ad31ad2d323bed4abf7bdf294281ea11724269df31ebbbf09c71099e5a6f98dd36abb22d80608b4d7ecc199d5a87408b609a4858641b0e85f11f07fd08b1c6e24d48ae2d982a4ca974202c873eded796434874d4427056a43e1b07ca00a76e1b0bd246e761296f237d0f01a169293be47261d5eb6b808750c7a00e46702a1363ca32c7dd955ecb74c6b72724a533dddf7209edeb6a288d7840d5b125abfb789873778684189b6fe5b214402e3a8ad06136c9d2dd894b02e882f2f738781ec79b0c8b38b9cbac12ef82b292ae12b72b2b72606f6a8abf18b7f0a1e9072df568bf9ae62636d5e36a84e2009223e96d7ff87240269c1c593aeef7e796997bc11cd5fc0cc42208b32509daca919768c2bd4a1b7168959bc92a65d5c6d125361f1a597c14a59847edb98bafbfa3cd069c5be972b3fe893d8b5a515821bd24133ccaa68cdd0d7f881becc1fbbfba21d0fabbad72418bb42956269158b195921ca633c03221c26e355d0fea0530f960c3863f3a72b84d01b8be22e6ac2c648ae773f81aded629153e8ef28c13f80c28c307abab071f7d911417aa4ee28ec7a7bbf6e1ef50cd30e6c9ef5a4bc3d51cc5fc46a5db725d7106d323963236880460ade30499ff7b23e87346b10511a5be927f624761726add04f1781b7f8ac2e1083c77cc28e8e0234e3cb21001102a433b161e4fba7215cbb911994f80bf1790425a73a61817143ddbfd8227740734ac9b12b7f7e457ee3aefe0e34ff91d52ac52b330059395b9bfd0b2653627b35d2a51814b2602728f1338c68f9fc8f101e96a0f2d7869bbdbd799f9dac23e574f9c671efc913310dc70f96c307b03325d63f7ed015931830d18c0a5bb949922d8fa492452421272aca8311dd6ea2211265f35967272e0aa8bf275062d4d2d6074cb0f18570ad250458f0bcd6b30be7566d06806e9c319a35ca4edbf30b3d0759de16df4a800123ea79269a6dd14f669ffc6f361e9ccc4deb7d43475b83eb2b6ed19a7f8705a3015cabe581938582afb76f538d9c18b764c525f50f98d70a63061a38c56de73ba7225b08cbde6e91181c45c1c76922eba33cbe4250d10d9e65be164239f5d449572a5e3b3a9e44496409982a58a83bab741c3b1fd9bc7e0c4dd435d42b81abadc29416b9502cbed6a82f62bd13f95d7a86e7f8d9965bb2455ec7054f17d93814872054f427311edcccb6487d65c89e54cabd08ed19f9da2b7025bb2adde121c9d7238080e853bb0805d10f77c9d8ba2cf843345a1273018c291ce00f5da8ff6d572cd8e36b62b6d5b1871cee2d6779066ddf8096215376f0a5253b0d732771a3f70a5bb5fdf8cd0587dbec91c245aee72b4c5c9fe2c6270764bedd6e218db20ca2a2dc7c01496da8bbacaf28a87c58666122d549a35ae16905814ef49a7828ff50b9abc48133ee3ceae56b0369e354892ed5a2fa9c634e1c9d6d59b14af79325b56ea2ac59665f988ca96da87b543c6b55f9fb631fb5aa6501c4cdc892a03e489726b898eff60a4956e64fee5244cd1d51cedcad223176106f70755de01ab5e22725be6216d8e5c20f6ad5108eeaef16c7353f8c0aa90ac10e61966d7d8f5b47372cb8e40969572153149d0c4a17c920764df8611a896a904cfb1504b4de7aebf721f67d03e7052968f560709df5ca586c0a380d2ee79cda63f6e73c65f549fed6424c0d0b56dd659d58b2083b39e80a42a81aa0bceca1887349c3908546af527728a70c60ab99ad6bf9e85e4cc5f9b00665f7ae47150216ba397ce944c395b53161844744607bde2fa70573e794721b96ba650f11b2660d66abfddbbe56379304f31ebbf1fa427ff11fb14018157fc1c621a1b2f3a61ec3cfbc9517228bbf316726878f4a010776e2bb86c085b5adf85ab63aa45b8da4bceba50b8e32403f8d73e30f5bb3f253a662fb027e9a45aa6e533281f7d7c493c5afa7af4ba48a9798d59f9b4ea42fced053c354e15073e46d5d7cfde67f696eb932f6cdf8d66d087967289e568eb8092681e881ff5b832b990ec5fe964e78800a23cf0e85196de65d3724dbde966525f6122f178ff46f5b6b8e03896697591c00f027fc37702803f3b210fb909dd18df524bd0b124177e5d6882c6e4804a89b344fae8792d5598c7ba0af07fbfe91e8e35cd9e0eeb0155c7e6d8bb4ad528b49c08148255495cdc995d6dccd2cd2a337341e658211c6739609c75fa51dd3f3366d6355d1cdc5c5621596a35e4e0caec59951b251b58a665cc6ff004fcd1f49793c32dc23ca30a87c5d5724fa7f3bb04b42abffe75617cd7986c886de1b850cd679c3a7ff2f2f200b5b3728ab3321ebfbcdf48a3690904528471d690d965a8b885b5008e6e1418c4fea672aea2e0652d648856a7587305f04a245776f04ae11a1238600c6e68a5a66c931daae417bc2645ad47e690cb3f20ca871eb8df39b5434c429954b9d7539989e472f7689d9ac38e01250a65ebfe03123e2aad929493edd6b880dc0b8a77188b6e03b0d9e5efd02b0225051d9946974a537e5998a1013d28c84f8132348bea7d332eeeda92128455152d8c6c8f213b884389020ff2e7d94c9d8099badcf9c4856272c6cf7c1dac2306824f59f087e80c1b869e6cef1e0fffa537b18031ed1f9b0572e8b3737ce061271feeb2deda54a781e8d90198d794902c0d286708050211d0720c850274e196105a18c567cecb3f4e1f50281e0f4788c825a09080b51f27e714f8fd7c3b3edd156ee573b556a9874cd6fa5639585b92d578917e3604e6cef6722c7b6ef74f9460358703de3e2e3e2afcd2bc8bb63a26ab997558f6aab6f33072a02f25a9695f6f9ac44050f15fe97b65130c5d24beb663720f7a9a689c935f4822f34b43ff0bfd9b7ff8cc6e0959d55b84890c633965bc54befd4976c02b7a3dd07a498d456df559e29b09b67ef01d886a28786926b5833e8da5b66bf6f06b377fc57765c7ba1d131c6a5481c797a9505d60a50ce933e5347664957eb94bfc72fa96d2c8d46956aaed8ed13b774e3d6a7c01d4fd4b5838096f595c2517b38920693ad223b077779a4cac55df5da7de9993ecd69355ea18286c7ec6547e95f11815d98313821ffd9f4912e04268593812c10b795a7da06d3b722e231757c43636ce38d19ce39d26f6e836a820abf632aed36f8f9457def4f5c643493ec4e6e4727e680058ac8744980da54d2183126c94e4abbaa36f7eca925ca2c9100ba4e272952d769903434ecb96dda42a8e3c281aaced44d5dcb09e7aadd8d3bfa3773772e551839cea9a36ab075224b4b1ba1775130d17e0310edc056e7631f5fe84e02a6572fd361cc2fc66b4624972ab9e1ea120be23d4ce370c40918c9957816cba3e4018c13d162e6b4066e9a2a0c1d5d5e25cda580ed2646257254760851c0db410f1a80da23009985d86597fdb43ce53f0aeb7372da8c7fbd1cf31840cee4bea72abd1d1c39b4e20e4e3ce17bcaf1b0abec7da0dee5a40406ba787d2df742ce0726a13c8702fd0b2fc2d05137228f197f990c8ab8d3ea0d020a796513f03cfc16671444d7422e636069719e9ae853d30c3d32ca6c77d3ea7c338715a15fc298272962d831b76f930682c5d7710a61b3166d7112e7bee13b696ba756384dc52cc19e663ddb721f16845815e75fa98879a00322e6e78d59b5818ca595441f9012072faa0dddb68eb5b291743855f836ee624c93c21cc915085e5c47affe099aa0d72b3a7fa35332c07624ad79aabc4c03dc52dca872f547a388aed70fadbc15e69467669a4424fbfcf79ab7b85ce69343dd61739455c80072c74fce4c34fd2ef8c7248f6dae46972b9aac9135d6a9523ec4bce79e172af403c28182c3f023320e272e938cd74b502384acb753a2b2d21f5c7d27e0d22995fe9ee401ae47294fff972245a14feed8b9506aa671a4f87181b67b327b7898a18997ee7daf502e6162a725bf7b8b202563e1663b15e30637050f3bba95ace326e7af07ab3ccf6d7e97772f70ba0827f41341a16cf4908f9fb9578ec4f828df1a6da5dab27771f5c726b250a4a581d8c7df76154c03b11f3894b9e676af8b0800f72bb3a180bfe72119072316a2d7670478484dc450fa4aa06c67737c5679aa94efb065ed5f4870f78b1727ebc990492198f020148e0f0416491e0834cc4f0ca3a0a16ba1831deafaeab1879b663c94c48504695a5f3fdd1c8822bd64164caf19bdf33c327004fa7082f3c991d5ade928c8f9d0344455413cb90eb243ea046f288f9850bc7a953d81078727656abd1e566995f2f6b7edacf08c4363221be42a59160a25303d45210655972305091d89c5e8799b3c1960dfd00ca0cf1da36807c895c8fedc63040211424724a4b5329a78dfedf1c2b016373bdcf16677946bca80cc182fb2916bb63c32c0d86b5e46d805a640a638676bf1102acfee80dd91998233a4f86a83a843819d17286ef196de7e878139809bd404edd0a8349c5001ab24719d1d9f0ee3ef2cbce72163ddf6fa2fa69b905ccbdd256589d69d8b34e9370ca114480334c8cc7e45e5c7284d90f038be44f642d2f6d2b901acfbe1acbfb169708e454cfb14d6ae95143f1b0fc8bae66719d72c4cb503445d2b3c7a088abe7e619cdb1a8cbd900fedd7282cd5f49b2c286db9a237af5486feaa50ded983db9dde139e7e59fad7ef8bf72ced2ef6a23831765e6a81d7534adbb06baf88458993fb5a8eaa799c153d0e00106923d932c7ef43bda29caffba05d4ac18198f6d0286a837460c2a9441021172b20cf221088835ca6900abea8a6831854978b80cc5ad6663d42546b535dfb472cab107373237dfff35b3d4882df21fa365669517f045d946ef43a88c41f49272eeefd88e4af3ee7fba7d0ea726666926a85da570183bbc638385fce40821787242313c9d03442bd2f945d3fd4312aadc017691ae376195dd304a845bbe27db72990208f5391a87eb603db655e321f6dedce58e4e962ab61516db43408fb1f86d76df4e3e349b444be48c492f9a3fd440fc571d4bf7c5dff09e3f04a241d11300f19fada2b668cea22a880a6697e09d60f18c253a7d4ba0df9409c4ef5621e672c038a69653461f6fee09b1125f7041a93519717bca521a0f5e45f1b098dc2272b7a7f7e453358ce959f239e8f158788a7abbd75106703de7d5c14b38cb6ce9065a6d1a3d3e58b440b0122a5dd23f5c499700cf85ec765821a9c7307a66d78172991fb140998782e212387ef48850b6c98289753e74ef453a52801eb255125119846bda07a6064bb017f8c9f2ca67da15d32fc8f2fdebaacea4e3cd35c58164148f8b1ccca5be85632f013ec4928f87e2680e7b2f54fac6ee72e9dd5de9172a08ab35b5562339c779cad5a636b90391ae3ce0b0ef48f1f7b8467119a23cf70f0d66dd545eada4d600edf55bae7d8a33d2b3d307501c317c59c2b8f45fd8d5d41849b7fc890f380cb03433fac5fb424d8dd020df5b27ff51f8cb9ceda2ece99f726587b227e00e34ab6cc45bc9a478d7ed058a8c95c5ae4fbe0ea24dc306bbee726c0f9f047d00138e82b4ecb5b8ac8885bb2f9b90746baa5c346d3f0173e4bb72adf6c2db135ea03111e64b6adc3371f3415656c06a38819cddb564b7bdaf3f5e9e1ec8e2b5a580593e4e7edd99296463f3c7c5e232ad17911ada1794a090d96e7f5d88ea584d66b7f3e186652e57424029d18c756c2bea700c2ed5a41e268972dd846762d5ddc9cebe1a920ca320153fc1975edcf104870e0c43e5beffee643df8d2b6cee2d37b19f033d6b89115d67ac1740526eaa3d87477c8919a3efd717284f164860cd1376da6e7fc6e91e37d79c0cb218188e50ac6ba05856bfe382072a6f52cbaa89513a0153676b808c95270be18fdbf8e19d8756fcb6604469b68721bdf675699f5b00b8fb9d811b8cd4a67e0eff1d12133caf604d7c27c82b3827243bceb9c3781e181d0c73dd0dc81a00b9fd73f542ac16ab88c57df5eea52a47268050af5690bfdc9827a6290520ca4ac5c562bf06ff35820a5b2425fbc88c472da6760b674e54fa3cdde37b94bfc5d1bfc96c1fc88300882fe39e155191c935ec53b3f67e076daccd13c5c871f37c43caab073ead23b817148c5b1c06a322e5c95edf842a66a0365adada851777eaeb56dc021f665d09eb244228d28ee06a51b11110da2506c642aca406841a48d3ce069afd2a1ea8d2a71431afdb9d0495e72369595b7456bc232d1834a3f03d9cfddb8404f4bddead6a668b4b9f9819c354e2c30bb629297ef6ef115a65bf82922fc04aac8e793de34a06a43cce60816c772815ef840bb8cbc9cd9ae004250b6de6d51d469db549bded2276b7b4cea88e63099aa1cba195df56a3e2ab6ef8c2fa99ef2f9fd51feec60891cc3070a7af19c60f0112064ae6fe338236b1d6ec29446b020192c577e9463ccd30907ae5fec6d727612bd47d6b286f1cee180ca46d79c9a2ab664be42a3aa6873cbcb6ed6df7c72f682b94b074fe9231160850781e83e98318c9f602d6b1913f89b6fe074cd3c1ade12329e749e0450718d8828f15142d4a3fb2780b96385e6b37e5b2a9c898672052eb5d7b81b48565f86a76ae5fbd6bb3115418b2455fce7bb5a8f1f28dec21a3a0b97a7c0a36d1ea0a2a706f79247067f4df6634f83b1ea261cc1cd9d5dec6c7d1c974790e79998788ad98d60e98712bdbfaf605771038f2715109d6f40ee72bd94a8a4cda2d1a9ffd130cfbdac532421c4e082da27a4c5c3bc8b390a5f135266dbe0f12e2307b00d46ed4c9dfbe5d1b55ed2dd6dacf32e9eeb80d6ba9a886f21b0352fca3885374b0006ec46b35f8413377f9091371501134bc1083c81b6701f8c900b45c0d047f5dbebf4d96554d5625d5f68f72baeee3c6af1b62d67d0729813c73f580eb2ce3baf713241e4079dd4cdcb9d2b52061afecd13b713ce5064904ab4ab8a8808cdc8041fb9d11a8a62c6e8290ae87c8ffd08c4830ec42a7c72e71d394d787dcc630044553e03939777ad4f30a32dd6901252692d62a5040e3ca117e29671c008c5124b6e35b3ad63d2b6c90ab0d78251fa2ee4dba68cb8b37266a782542abc26734400b45ee7862352e84f556e0024df8160aa99805b9dc10f3127bf1f0f92d29b669283e9f40be2cbcbf47819310beb090d6eef1402932335a10e88ebe6affa7fc45ae9d9069fce844fe4a9fba04a0075b78406116842d53a905451f3453f2ace0cf5e1cb8b4db58d5425dd78c3493cfb8334b8e34bf18344b155e6faa2e48784e3ba85894f81c0b4a1cc6d1d9ba9b83062f449af17f2157252a7386a902f236bfa4d719a1e39b39714bcb0ddcda4033633984ec1d40397622c5791c111f454b703d630218750ea1270817701e31111cc132b065e40b0c3728b63be9c50962e280ab727bbc1486093f3165779fd7c6512699ceef0df47f5723009b4e5c7109b1f8f966ed5e5130cfcfab86893205609181dc2444fb7ab487242abb6c1e1326fe74bd0df4448f3af6f0c5e0882b3ae796743c9abb758fcdd72cc3260ec5ebd86193db980e2409fe346e01e094996c04cb2652bb406e42fc33e6c79c570bc5e3419ad77f501c0bbf008b9ef4f6861f9b61d743080c74104ba7269fa46df246ea0df1e7b8bf6211bda3c8a682e7a1fcdcd9c318a05a1612b5c13b98f80d56a29b8f0004968e490dd91df58e0f8226412860ddaa2788954e8e9720b415bec70656df10251c1d07bfad6e4ff9fc682ae8fd7b08534f4c4d5be8f72547dba6ceeb26656dfb67ab86ec6c25f9b4d90cd5283d5c35e01a97ab20b6a72fcee79304c2639855615a3154cc6a32b642d3f73dd1e167c59227dd272e5f17214fc96cfbc48d2aba007a7c739c1f4648333769a0e1c6f06b4a3525d274fd001024c50b1e1d24c91a882456dea8887a6a45cee4ffda4a79ddb7db15e0be45672e5bfe621fd6f20dc7997e17bb5b0d189bd11ba0ac4358c56debb23b949a96072a3e88a38211d98e0c5025ba3d638e7ae1d6f43ac2d73cb4bc68517df005c8572cb3bd042df28d62c442fdc50fbce5e7ede13a7c888029b61ebb72640c313ca09a4f0f448f21917a88ed4ba8bdb7c16956d87cee5051888618b25793a2118857281ac31d439055e164d23013cbf811a681f3c07cb096f3daa8c0aeec02cae0572b103ebc5e9b74c3cc553c71882eec744be18000f04b01ecf23855c94ec3c3372ce065923cd0bbddbf8fe7e81122154b80698adc53e93e9eb0b4f67a96b7b6f3f81e4ef252b8d398efc119df4a7b437dabf03c55686a24af64130a587e4e51072221caea42a204ae06b68dee4962d5ff5e8eb3128f4fc7357c7029b39392e0172b6508d9ef442acc9588fc32d30a9718a9e605f1ffbc50122f888c565d196f07266a3a43a917d8d580349646b3d5511754d578403153104d4571da9d08fd52d724639d9ec12e95df4d5a86a37d717f55b596016ccaa464891cde738393b256d5d135b777ba18e97c2482a7528866192338d0112283e768cd145072dd38c2b5e72b550a9577dd98cb7b60f7b055d8f4c60473c626d6bc8c3ce24cf260cfb4a45023454660505a61046c52fec6b39d1dc3e3150b9ed6bb27b52653cf102800a7c7258b7adb3e5681fbde7ad39ef52903523b8cfadf3e8b942d91bfb9d28aac79272db0e995b2dd8462111345ca68ec58afe4ae4bc73d2a259566d070be5eaadbd18752f95f977db73e6e215264e39750d2cfa33b22ade47d8264aad337fd1290d72f657403e9923a3405a29b70e794f0c3f6e0f240a9c391d11683338537511bb01f523b3cb23ddc2bd71c9b9b5ac71d1e26cdb957c89140a3a7412f021df83811591d72d5a0165a27d857eeac291cd4c7c3d7bb8b9afbcdc568c5de954763f4472a7afd01135de16b61bd59130f10f5453ca77bb9679a35a9c081f2e0934ffd872fbaea64471cb7edb3af0c4c96801bd51768f538c3f946dd219872071b191ff723feb5524fecc5d2ff40787cc4fc75355e2711e6a8ed090c47e6cc56b862661729330b3b6d4acbb61643de3dca96fd6197fd92adc300c93aede44ad72011691720eb2f32ec7dab18dd278ef0e7c0d651f2ac4d6ca8fe9768299726e229cb85d50a5cb1f8140448c5a40cb3f88c0fff202e376148a0d9f8278f6f2f2acc642057241a8887de3c262b958926af5b65d56edc2110702f48ac4013fb3843eeb13f9142a6753bcda7a310388abbcc04775befc81b5cf676e5184f868c1cbbfaac1ce72098a97376dbc35ece31822f6dfdf093751da72a342966549447d061b9fb6cd720dc502a8aeddfd78caec0d9b05f3cc32d418ac483b6ffe2d5e4244ff514a68723c97976f450767fe6a6896ecd2cffa3920d897b7731886b6adf07da3973ae75cc53f8ada43e22d976021abe68122f4f483d51dc7d8c8c9dd14ed6e82c5d33b5c46b3b74bc9bd673b527664606e5880fe7a90df016d90e7b685becde811ca790de0e03a370cba664d87bd9a6c5a9eb16639ddb963d2c94ef3984fe1492b84ba725e57e74e8f9bdaff5bb63f26daccfdfb6f32feab48f397128aa488784f005348a85f3a023b164207354e61bd4da8f7fa77732cce9d75b3f124969a4f5dc3b272040d3fbd10fa55d6bc302fc8da936e86fa683c18b244938fb901f3f6d587db724cf08f40818d31af8caca4282c300f8233e908016bfdbd124189ad680e4727725f116c2cab0c282e8bf32b551ddba39c13eda10a5359f2f14844ee440d4de072d9b5287721977cea2d6244bc16f94276e1eef6ae60d0e67bf50a00733cbd535efae115d9dd60023098bb8d53064785ff21fccb87403d9fe9280f5b3f5a7dc41c2abb2299b711d6d826bc46ff674f0eb7f61c4ff6e7d88e4a5542119765584272fe66f2879afdcd960c5e6e002411a1d5a403d76c9e4666f6364a11685a04a672d9a3ee605cb55894d8ac637488f0ea3a6acb52c9a6374607e525c7be24c810726050476f02bd8f543cb07e591cb9becd43c415cb3b480daa8cd91264d2f37972a58f61bf0c632ebf7474d9a97e56b729b66671b395e4a296932fbf7c018c0e18d531289d57b176393c8448a64ae4820e2ddf294295a0e8eb34a273506a33e97269be10dcde1eb053509d83e97e480dc0aea7cd7844f6acd3f58aa0306539ba061e54c4bac3e0a7cc2cd68d442c8a5a5695485f74dc7426af654f9aa10891f221b6b8644aaccc11d2a409ccbb86da54da26652ac7f19d140886e4c78afc97ed720b74030787a43436d6259806a70831cde0dcb850e962b8f43c1ed85bfdb5df3d83e3d634486d619491e9f87662c179f2d8aaa71f2dc05085281fbdcc4accb00373ff8d62396bab90a7c49f11e8274256735ce9eb8858a5ae45094019db5407722a664bf71f6a7d3997462872536214fb36f794207e7f561380e6361f8efe1168a5ebd4295129c9a001b81d6941fd089644446c7755016fa58edce8acd365be72cdaac45ea0316c205bab374a899b4f1a8a2b2f7fecb3e8feccec89ee4fb8e272d813f65999a92de29174c7c8986374b1c01f1c5c137d93590fd29ef7f15bb072580e77d43d1b68be543e7a32a71526627e1372a25a2d0c4c672d4b48eec6e772578701e58a23e1a7b50f5d8bd1db49a9b61b1cf8c472855dc03012542c3f93724cf1f71f0d6ee307cc75c6e3db8dc005e16956c273e2c432af2de443022e647266e7ed8fc903f63b2766f919dc3951c10668609d9c5a7c8fa500e7de8c9b571ceb4e78dd3eb26b45248d7f93c6b636315fab63cb2bdba9cfa340c7bc33bb0672668a1b27026b0d37e4d4cad9337b403eb18ce95af636aabc06de285e78e69a72723f2d211868e107432e40368919c18ead27a3fef0a016810630398cf2cfb672febb8f9b263150cbe78716839b8aeb87b36f071a50fe34489a5899b48930c47217e7bc90c321f6baff4f533cd139cfe32c56e9cb348800d202b5b16e2cd0e453cbcd05b3b9eb02148b5012da2b602cb3288757c647b9b8605cfb6509552cdd7219fd4d43d80ec8e295411105bfc55c88931b4797712bd88a5dd22995d4dc1672bd562042ac72cc96ea377c57f172edd99a4e3523a75e6f141327a5aa79f1db3d85af6e8d4565c5ce205c8fe580f2ab499ec220583180e335f25d926ab9789f571aab43e3f6cb8854e698f22b72d35504babae1b00dba1fba49b84bde07208272c993fbd6bcadd2eff2b342f3f229b865b42d93d7488b7798ac7aa88ab613c41f3c85f533c3c224056b5c4a26f4e582cfb3e8d3d7a863f5224d853106023d475c8964198c5ccbb61468a5ade7b1160ec642879a0f17a8b09ca879d0c5b109444cb3c64111170a69f138bd26bef1323dd99c6ecb260a8dcb26d25255e39dc2c601bfae333a0f80a5089c4ee21926cd509836cd7852dafc3aa582d7f554447aa572773d332eecf4c24ce12e647b8ec7ace922c919664b29a48a3fa3ae1db6db294451c6601dc029c60834c47d2c8cb931f1c180a8277247bc3cb4f03e5a9552ff0c5de8b6764dd23fd07cb209dc9f1534163a4936efe0e5ba028bb31aaa2b68ef72b12d849d666201fda4655bdf0199c322cfbb93d0283ce9b56ae743f21a80693eb0340a301ed4bd993527ee062566d994931a169a06524ee9d47bbd56ebc2c5727d7ebbbe477a2b4f80bdabc6e0fe592ac61cfd1f8c7672151f91e2f30e6e20727b8081203e273bb215f5cdb55689dfca6f0067be312bd0aac860155c409cfd721c56f363ab409e8925759823d6c72cf2b2a7355d67274af0f44ea0da4239985fa06f749da5126e0fcd8273ddeeebd41f5c4767a186d3a1ba08c69415d209c4722b512cec55e61ec8d4c7c63430f38ab039a496d64a49636d9c7b7c13f96715722fd5f39aef2dd8dcae039f8aa27bdfa8f6d9088ab1b787ecb1c3a171de9b04723fbc4c2c6a9cfc67e51666845f75cb4e67675be37f8c70e06cebee048891d635285f21dd20e340963118d44c6e997f08c76ce3bb53563b469d4e9d8257b6d54fb5816fbf9fd08c991fd19b0870423e21d82a04ee505eceb776fb9b785b259672d8888bea4c29510b42efac6adb3e75d64c8153ba5a070803b085d9cd3c273472d9d51a3557df938cf0e565d684af4cf9949027de83851c151974eb7ae9b2f9720d6a9b18ce2d575bf17df421a7dfdd68c0d424cae66e54e5831634ab7f0bf548da1ebc3693328f11a06fd2ac4135508ec575fedddf7cc57ad959f04fabf5b0583b9acf42577c2b272f4688b896e5cc0752c92a9b8499da1ce21f93bdf28a3c5fd130e8778dd4ffeb64ac9123f2e528617f7c7fe9b35b6659d1bd019c47c1664df2e4299014d4c1ffc5e44e16917f63f2e125d95472637f8efbaf444517dc76369204de17f27632ea5e7c007506383a9cae3d5460f210b603e8b237e89ebfc972e39e6210ec1f658da0506ddf9dca11dc7314dd45336bb48d10cfddbb7ed5be24fa2bb8421754046cf616845bbea285b790635758dd0a24a102ad1951d5f470158f19fd043191b8963d968f388bbfe193cc21c831d311cc6fe6e0394beac75d72aa45a17c1d0b0348e7bf7c72f4a4d97add536f53d83a54cf2bdc9e591d4bca72623e8fee55de67e3488f1aa62e546e2879499bfdcce5aea39c37adb3ef032c7211cfaa75be620a2e91a06bd6392b7b4e931dfd91aca531617f61801594d66d46ad77f6ea6e0cf30b06730fadcedfb82a4efde327837cc077af1b1955eb7ace5780ed22bfd05941f39b5e19df0c84ecaa610b30fdbb9a6fd87858d54f39626472547141b9c9ebeed5efbf5e44c90304aa76017e13d69d12fb9e05ff08f2003414d1fa1c3e2c4784d3b8e9c36d5dcb2b7b06465494b0a5ba0ee706d180e82788725fdf9e68f56c6f1888ccc7487e69b4497c5496d3fe7cbb01386caf5260a1954f755e4b4bed161f5fb3824aa30402864e1615cfb1a5a3f9d26e544ede78f0f13a13471a9615e9aab9c272daea67fc565aa58a7d2f0b15043795b0475b034b5e4b68b008fc72a7ff85648df5fc482117005773327dad3d8f8143a430205c169725cc12e0d7544c34b10324a2286216b321ff2e7cc6715992ef6d89fab28645a872f79a3b090fc23a0cd1a30b4a9599dbd1be553943a45de22f3d5bb921f7b7a94860a2553d0dfb6830e8b2723906b70379c727bb38bebf3fdd8a6434edb215ea5e424c3a745b80cb41a69b0d3b835035414ed54be9d3f5513a821f66e942f69872e142d1955c97961053d25b9b684c6ded83fa9771f36bfd12b9f9cfbf67b73e72967b76204ef1f2692f94c12e758f5329e95deecddebcb7d532364b6cbdc45b6be29b237fe18d0cda33a188de3889088f43df79f4f4f76f4676b0cef9fbf0c172dd29273db9d5e69ec9d48487868febd8278455c62d341755f54eb32edff0227277d3841eec55d220c6e3b5a269cb0835dca453f49bb6e898ceaccd05706a48726accffa0be874c2b46f08660e48f655f2695d8b08753346c3d1a218ff1c5eb7289f5ee3a6eb99f3571c4e52d4162de9d91a2cad5bb3b4fde844708fdd417d249ac8ed4d718e331a6e9049402c5186e5221d8c3f1721974f1c668c89dd8bdff28db56d4095db8b097f248ac0f3f1f0a9c8ccd26a41419e238dae43d4b0fd82548bab17080806042ec6b6c02f042f6d16d7edd0e3a032a86653d096ebb680c1520126ba729fbe8a359ec20abe6ef8cc7cee270e546e41465ffea5e5990445c9772cd7b1c3b102446d07d7ccd524265ca3abdb4d6e429459738c3fc8435a8c40f04901dc96d4dfa4cf0c220cc8d17c4fb4d913a491ea88ddc382520cdaee7c07100ccb62e87161ef54afd0d72f01076782005fbdf6c82a8b23315e338a4dc888172e159dacb4f83d56a208658e6d50a2b69939bc971cbbaa79122a7aeab5e120c7203c348249f570f818d04785de2cbb64793c9ad3d806e9518083354639de99b7251274ab82e424b7842d06f5472ec954c85ca3d65af06b0a40c45b62f23fea8720ba1cd11bcf51ae18a6c71291cbe034eb06867a6945cc7c36f2dfe1479b49072708461436997621ed0691e9ae350ff5634d6c5376471a4340c36e813e8d53c72c845048cf6efc4b3163b3f3d26f6fdc5114f1e7480df9b87c9ff1bf1e20a6b72170e3395818b3436d4981f2772bac122c58a4cd9182a32e1be1fd7ecd620a86a1018d38b1eed581b8fe0cf3571ffddb742752e84f1aa3e67584a14c1d49c940af8b477dba50ac009b463d1fb342ffddf8b81d3886172173494c543bb731fb361adb6d965645381707b8a47192c4590c653edb283b84425c2be414ec93447f4341ba727042145903d89bb30c5f43062a84cbf90cd9c743cebd77267fc7922fc31547545478df5d7e0afb610ddad04270362292c120f24876acad2f5989e855072fa3e795de66f87ad2f9b61176c31d0785af55ab212f3a7997f60fded66cb0a3791252b1ffa0222599e3742bc8570577ffd053dbd216400b029fd6ffc568362725a12843ba5e32b8687adadb36c243d68b35adc7f28b724373c0d36c6ddf8b22d2c854e14950f07eefdf38cb9a87b9d46c1ec3dbdf990fbad9913f6a025c52f682f18a40368719ceeea556d0e2f10b40f88b37695391072da78ce3967dd1620720a8128d1299d5728b24834f32180bc62927765de671bb70be24c4e0774b1a77285e5795b1a440752e53934bca2289a99e9b46a63f4b77923cd3f40c0c2a1a772aff8239af366f1e5cc2ed81ccfa914abeebd2f438aebaeca77e9d48b9e7e9436320f32073b978e610f52eeee5953f4f7676f59d9c1396eaa5a84f72640023f7267c074354b8b2183df3b7e81fe61414c041a8d82244ee8d5ff699324fe469a2044ac46f7fa994ded4c12c215f3e9a30bfc7b7d084aca99b11c0c408bef010f1caa836d8811b704d8da89816dfec6eacc730c46062eda0d05260d37d453b57e720716623cb48981d41b86fb84791909eb670e36090281ec11e6825eb8c4cad772a5ebe83e6d9feec906928f68165281446d579023c458a47122f6960318be2e72471c37b021f0998c6c90b3a52dd9d10d6da0381784d7e3907f2b8971d6b78f2162277bffe0fb70387a4264f56929754ed3f1343a51a6b0fc04d7358b5dead272f76ed2c6c9a4e9c44a29380997d6e117bd637471cf7056c458b6e1caa25dd17269f166e85fb7b31c5bc59bd9d78f6652a1221444c84dd9138c1df5de96329e046771ccd3fc9fc6c0437a230a3c4396343b3cb3c1c233dd5e0f886678c798977235f703aeee613849f6faf938f3c0e42bff2b725795fa1b27d4ad2c2a96d0cb5f4a22e9c2a0ce5a0e4ee2e97e99909c1d71134f1708fed76801a65cd5f858b272a73fa88928963055d12dd7811891bd51bf2f4db7fa0d5905c4a67fadb72fd7722418b42a9ebdeea2e12c320a7cba938800c4e27e4555902d886584753556a272b388d1e9b8b189a745c7a73841eddf8c2362a64d699e5ddf3d57865ee8af273f0f34e72f742247cbced56f38e636b2427dfb0aed65318765bf3a517573be9472efabfa014eda7b07a6225b495f92c784af380fe001f6c66c2ae5f8fa1ca90472dc58227b116cca9b483d66c4a2db73654599677f16e7622cab427b9fc6fbd27246637756d6673f4f2ce0aa26c08413fee9c73da9ff7831f343c00f8a508017461c8e7a7d3da3e70784f42ca64bf4a029fc253fae50d983ffc28f501af08cbf725e48ef179c0e1e2d51000472b7319360142662b9375e838bcfa5bcf49b9062720fcb8e773d4855e925071872a5457460bf55b7d5d52d71d82aa34e77896c7d5ebf2274c018a055a2ba03ef18bfc7a79273ef8336dcd3e69a2265bdf8fc69f572e9751bad0ab19fb742baf69a1bf30f020ea8fe31f3bdf2cb1778ca47cefe5412644fc92d90b90995b1d26d9447a1a352d071b8d2cf304cef65b664b0f27139281801674b3642f6431de8911ff89cef1ae448b8aac626a8e0eb85c6c68362557278ef89642d8cecac4008542776af9cfd40b86111d52aa9003037ad80ea59917226671f91df309084a8d782b5ff6feb714a93232fe8ddcff51d491f7f6de8f5721759b333cf7175e40f86b1ef26091c3a194e2b121c5add2de2f11642f0b9d772e2d31163a1e8db4cef0f3639696ea4634e2989ba839be9582086aca1809add72cf3caab8474a09df14e342a4779097ddf4c5ebf2368c22857d4d97184190c27288b09a2d07061aa8a552c8791c550dec9a1f4b7a5951a77616fecdf97aa27c72f8e8f3c615f2a8dc3f21a3f56818d1bb42b30cb53d83ff20c0d10ecae51d04725d74a14d27b77badd1e7320485835aee4aeb968ea69540e91ed7788bcfe4a01865b62f26a4d33a938fd622f0490a2900987af774619d8e0b23dac894e94c1172550e40ef4f622164a0cde44430e0bdb60735d064f1243ad4598b6e8aaf43133ec30566c6a0a7a98b4e62a6e0c32682bce49df22f889de5b9c195e97190ddb802ac2ad47bcb48e7da3e771bfeb453d81d56d1ae1662115e049100e1a5b9bd9772261598823d85d1ba24bfb0cfd6a049193170f47bbf3a0a840fd39443378bb3329f1014db5269f35c47732165c4fdee63fa69e535e1b8eb6f265a57879f7b2e36f0209ad2433f5d201d9a58c560c7280e125993cdf64113185ee4f1302ded9e7247435d55277abb9b12ef796d3866826a3573b02d07d5ae70963f637059c8df727903bb95dbccc9c90e7b653f1fa188298193a1582ce04a6bccdeb24a602e724ac3060ab8a3aff1476433e92448401238b7e513bae8f21ee5ed2b0557450fe7722a5b2155fae33f6e381e09e9dda1aa20f03daaa4aac880ff158ab0f851c5107279c37124d3eed1d946aa4d97176dc4432efdf8689c6cffeccff675683375d972bf095fc7b7a2ae77283137d1f079661985f6a92118f1fc5e1dc4ab3327115e4c1fae72a8983fb1a429925a47b4c1b6fa0023618d9ac751ba1b0e7331c57129726238ece64f29da2df1a778da1cc929e8d55f5bff4e21aac5a644a5defd3cb52b3fca073a19aaade36d66e2ea1e4dab0338c9e7ffa8789ec2872d8dee0d68c472668b63057fccdb9813477991c1e1043dd4e8e93cbce52c37d1b0c0b342bdba64885a6a6a8a9799d27598a1b3c7c7ac9b55bc42d41bb7bbb35eab32c98295087275f807293ccff69748973500f543f26ae8b5d90e1b0554779732d4f763659b72681985f0c1b10126a8263d514b293528193621469f2591fb6e8a50a7a6107b72df789849c55b7e60eaf4cd0393ae60abb2976db34026e0b036beb8be14231732221a852cc5c8c06b30a8a9f193e2e561f6c893b5cf4a8da822605b223a6c7a7283ce3e80fe081fcf5ec4f62d5d5df7e7bd498c16aeacdda8fa146baa379da7727635449c01ee027203be68403f386c913fdbdd2e7253dcf86a22a9982108396d0c5f5dc895fda00f8376f5a2cee5a2edd019b19225dbad0beb068718a2e004726acfe0def1dc0aba4db95fb195c59730c8f5d1964f3db2a17c01be5d61305749c0c5b22dfb72e22d87eb35a65dedf04e1fc1853866441db70d0d31ea2c19e572961400acb1185e0f59569fad62e92a9ec1cd82a8181e1066b266e0119d7979245eeb660a1931677b07c074080bbeb4ce83b434da7ef8644da6dcbe3caf5291123e697d52f5f718c1b97d2690cdb73e0cafb6220d8bec33854fdd5dcfb6071a1403609a7f7ac5d984e5dcd136c0e94375c5fef3a11c084ae59f993b9310975a158209d3897e5ec5a38ed4b84f3d87f44731ff3ff4ffb9767d8ebe7d3b0215c53957f5b602745a2ba0bcc0f05e463efa833b1755735d8994c570dc098bba40437237366dbb1091b1021d960aed3e92a8467ae856d3b1e9a0eb9816761c4464de4464402498d302f3a6085dd0f00159d646d2dfb789625b07a105cf1165a8238f729375673df73adf4ac89666f87ae212710334da1f76c53adeeafa489002dee7725fb82aff025ded89175fb1b3fb1d633450d09372cba25bf70fe6618d54c0af720e4bdd92e46492b2766eeb0dae51ec4474f8a9d61602b554196c21095f247a2b04aefc7cd983641e7076e30ccf73bc29f2ced94dbd30945026b768e4aa11be727e0ec4d44a94da6ad50dbd5ce90d80ef1432de00a7924a78d2ca5edcbe015e726d245bd0912b3e839706e868605d17b9f759952287ac7871030ab003e85e0872ce2ed503e8b104c0bbf19b91e22fc692ad9d3f7ebc0e611e7d7b1c2855ab3472f28d7ba58805abbe8004ab95b3c9901785940a7d30c380214ad837b7bff94b2ba5e070ae7329d5dbae3737751fc6e486f06f1a1058a34b470c3a499a2380a472c74fad194b6c3f321c06ce72450f93fbe156a9732e22169a2af2ec6592075540aeebef36728c2527e668e391d03404ea5dd8f3898346d02de787c280f8bb5e72afcc2bd92c070787773a68f9bdefa800086cacaa1d8d0b695f3ac3dfb396273b002067f6a379088c3c1ae144e99bdc346271718e08c905235605f9da5b677336ff15ffaf9036211666e7d6a33c07e465493231b388d94b9bc3731d5819425572511be71268952e312f43faa913a5c6268aec8bb95918066fe2cab0671636b3720a4c7bbe45492832511fec896a3ed0ae3cebf0fa5de2dde9b534515e7e39313b7535d4bc10fa990d13d4a598c30702efb69c2a1581480205136f0840e0eff758523a23d57950586b994a9d24a7a701a14810c126279e19117dae1257866fd772ae15790184439894cd17c10dbd0cd5c0a5b06514fecc9b149ec79baae92e796a06f22d50432d8c894aa46f151cd1ca2a0048f03e230737293b799b0235b625722124eabc929aad4f1780d7ad9c8ab3321429e50e72bee06b774d7c4d6ce80a4b537396ce6aad4f96b985fab9979df60c04cde202bed90293cca813c69bc0c972e0ea128033e6b86bfd25db30dbe0c881dc4d4a16176783f73c4225b6e8914672a8b90601b227d2d1395342f696387355be07babbbd072a583ba82aae3927b5727981f1afcd60f340965dbb305047f8112436142ab706379f641de6d0630e1a72c38c90d2447553de3e7203a1dec03af505885c74ec42b2f61751e5f201840d720450472447d019825b9a1b2f996277bddf0f9604b0181925d2c835d35e76ee22ddfc81815253c43b7fab4fde1b549d446c9f9744651888fac1c2c124c9cdf0721956bb8273d76557f6af4d615761dfb40c6f6e60809b0ae3ccc02b5d8f72b11dedb1d94df31889176abcf8429fee9cdb61bf8d06277fdf9de6849d66eb718c7277090f4edb915ec03e02c1a7654fb3c8d564b297225194fdaf53c1a028961472f76ccaced04286f6e91eff3855b1e3199182f2991e0ea3cef0d81d9d5b9d980b021e5d07cc2ced0b006ba8cc02082b40dcbab2a7c50e35db9f863c480842e47285c87abc7ed15aa08b32595111857f025608b4124f267fd18cfb3d50a753c772cbbb52fa697b6024b08083141b83fcee291803230c5b4d58aae53418c74d6a57144dc29ec6e6c9bebeed5781778d3ade1d64a98da52c3c11575e506e133529014721b0fd69a40042cdcaa9dac67e86a884a60b34b5a2e9e3e39849559955ee2dc5ef7337342de2f056c0d57445bd3bb510feb4d4d8a228dc310425dd874ad17253a1a65b10fd5d4a395cfd16acc2e5b2310a12e5f2b16b06902263d04262ca724d7cda3e87433d2d1fad7f5c114a21a761cff8123571034faa4da37c78861772fd415f9fc96435bc21266839e70e54b7e098a95db914ea309688372a480ab16b9230fe4f84ffa57030a19d950ae692b962e81c1d9fec72c27495b7a1ef036172ec8253aa6b252a99a2a56d76da6e1f32173dca6c665691dab3343e17e6638e1a92e2a4479a23689f0aa6cb03e50da25f6ffc2dae7284c6f7e030dc168e82fa304987a4acdc730305e577a8e1d67be7ad649b45bb3b0f2d852a12efeda64bbb720e65eb665bb32acb1d5dad704ad8cfd1270aefbaddba4ac86a7f6c87fe1bd90283c1d21efa2fb057aa56f8198b0ba9c55e16849ebbbb9b95de0087de40058f7267bf4c39e46c5eb4998f1732ab5edbfe5aa7be083267787aaaf6bf82023a527223b0b561065d2b64a4165c94703a3cbf8f118d10b00d61197a95bf3149212f5b123edd4a5a4e8c772a0ce42840d7018bda17050d0c71af7b47a543e3b6e0e072e224d9f695f5ac267cffba8d5557457897e0aa127041b72233916b8274e95972642a89a4929cba027a112cb30bfc9bca5808bb6bcd2a0b63fd2ab0552c04262b4d5ca0b07b09a2e394d162e7ca83c867071b050222f2643aaef7777b9866374baa31272989d8b3767d2336a72b0b918ab694420a41208319b6ce1daa4bf13e72bd642ba6a6472adad6c1340c08774ad277c882916aaab115dbe902fa11900f7219baf2ce1e00a5564413375b848374744c88d7696b61944d174cd023f41b3a6bb8939c44b19f7612aa667a883c8b1a44a364551166c6b4c3e623eadc9a2b647211a51b120d069cc09801b94c8084c1ed924ba0d522c44f1f07b9e44f8a2282722aba592f429abe92c5565836b3d1bc89a5c74cd81d6f2943b612deade345bb72a0e0ff9eed78729e8ce746f8d503049119f0bdec9e957cfb851ba6b56dc654728e7e430e35c5644c170038f21ca1aadd41e035db89427dc7fc8d7335a3ab047244ca66cc50e17665a2adf45fb959fe707ac4c6ce6042c8ed2d3e9f3ab54d4d72cee465262f0e7e2830a193193cb159168acc8ff852d3cd8cfe153435c08b027226f33a94139e3d7dee6100dd6f41e0e56697f53e62252ca69643c01155be4c0f16257d3cb873ec4d23ef6654595e355aa83608a948e0bff7e79f3d057c4d9b2f09131464885e30d9d04c0458a96d8443334938580bc31b0ebc8a7d9cb1754072c5ba84eef6b1c35b26ad6c026dd55235c3b2f71258f26baaf85dcfa8eb35062afde54accd894ebbe2fd04daab0ceabfb0493110dc0195bb0b3f0b30a2713927222c45ea9cb200d55599b51269f3e638265a23308b570624fb55e12c562fb225593dbc89c1298c730df59a8b1b0ba85d29b6f051c6f2e66ba384d25035cf37572956dbd2b01b08e6a732e066aa33e39daf9491fb0c32e53225af3be234701890cf01984b063f4f465099e98361a522f5642b34489368554f65387f5a28cd6f172a029873b82a0071d1e72d11422da5157842125999e7cd73cc12e73d6c1a089727136c097958958ea53cd770dd376be8a68c53df0c3e1b46bcebf801d2693aa352bf72f77ec3eee0c61fe21aae16c0cdac0352a1d59cf00e7a9accc2e06ce9b28e24e729d4a0ba8d950ceeba6b3d8aab8a45aceaf722cf9b598cd0f24ade53b5dbfadf46640797b59ffe86766c7e22aed062db6ddf6534d80d611f66eb38bcf72f04cc1ed9bae2674a9985a76ad03932399d8a2ca759d0813f6e6c3acf7f8d30b111ae5b1d12cf85c60cc77965fcabbc2a7de298eb18ab741b88bec597fcbdb727543052db9a44537aa936b4c0a3a087367d6b977aaf6c3fa64bc8ca0bd4e6204917aaad52dbdd9db3268645a4a7144382800de88cb453c2e40aceaba691b9672e5c249d0a84d292a12a666a2b258a6e91a7b103078721f28d586f044536e8f72da29a3981a8b2cfc9ff02e60bbaa478fb323399d9e83c0e74b7ae1c629901a72f711fe35fdb3fbd130f528400d20d0226db5a8662061703ed27f77a3e88e617227b1b4d2dbb46d5b8f65062c1bed644add010acaf6f0d6e8f52a95c1ba2ab3726a1fb49fad016e973950582afe4248a13bf210f7b489c6b818162abcd36b9972abb3cef13a9564f82ddb84e92c5d72f23c8a48d87187562b57cd0192b53e4903ca1b5c9359d6c91d9cc4090e40c31a138d2d46b0a5ec4201bd7f5d07ad6d1a170aa128ac19de5d5eac40893f679fd58906990b3c9d3adcd590806f5c77fc3c3bc6d31d3ac33114038872cfff201c0856f28bb9d97cffcef976dfb5b1036ce472f5e5dc4a58fd610204da91cca95c890af46e7aa3cc19fc983957f903c5b0354115c1b283a98c71d3922b574547a1ca33609b1e4b619daf6738d427c8c39e00728e982e575c33e128486bd1670c52ff4d5466f43ab13cdfabf7c0c6b80f01e3729de48333e76cfe67fafc6c719190dc25f2ecd77816be0fe8bf44bb5821303b60e497b9cfc676cbb4c502570bb9d18777e7e4053c8e345ea95890644690d7105e2066ef9ee1233b9dd0c73ecf5babcb9b8241bb2875d8edfbab98e862d5a417139c1680d099effe47dd799abbdf1f166ca99099e160c73c8fd7a36da8a7ac822b7be4a279d97e60b3a71ee26f652e6b9acbf37f62e7bd14dac4c7a504cb812d7209366cb47c48e3f42fee3c5831a73b8abd959a823ca3dca450c9d025e501e7727e4f554e326185c109bfb3f97e5ff3536714680e89a35782df6b4308c26fc7721134013c0f270a2c72f8e83b68b57ae7f856f3f8d6d740bba84aa0793a9b2270344f7d119a3fe2e741ed37e6a7c3804f3adb634fe090917c55e88c59ba271272187548a9ab5bc990b3c6864ae6286eaabe4be30f96cc5406dab790a39cf7ec3b1b9c1e1564cfe6f6fb7828ec0cf693c2beebd6fe9c30340d7721f9daed964b72b7f543beee959d981e2a50da14df92d21705248d43d053c6bdb1197f7f6bf95c767b262e54353aeda67f699d93cb5e9a4810fdd1e99e4b7a690cfe0a9aa7c1623adc2999febaed3b4f065d2507d405eaf76081a651639d2f50873296d6e9e353fcce49f85cf382e85ae7bc17478fab60f13d836219c2c3236b6b4134bcec66728596890499c1bd35fb1f1310944eeb71c2141f3e88caa6f57588683a72ba9372494e8501367a2888877e2d4380a6ce9029c3007cd0905f5dc18b80aa7f0a071930c327467d2288998161cb362deed15ef5c40d005a78d2e8b0aa523a15bb67721f9258dbd763cee878ce198fcfa12ef074cb2a439d533f4541e30e4148ce412c7702763a98492b80328b19ae89b33686fce30a59ce4e194c084e4c27df99cd727e9fad783b77fb45c8cbabe4a83c3bb8c00427c52182e8ec5706ea7aab109866c7c6ce11df09a1f59567e0cdf39488f9a3e09acf1c612114e521d2b7d55e22722502a524ba12e189f12beda2759d8047ee96e314f686e10b2f7026b5fcf6a40bc1b4233ef26dea3efeeb123f6e3af88348ff9b9e46eb988f5f51a039acdb026ba63168fba54eed219554ad17f7531f20ef5a6fa19a44a34057b1f3faa7c49572638ddca8183800bf4f9fadf6f1d22e271627c20f3137b2b46c9c7dbc1bc16b72fac9b85c2b400b40b256e9a04f81cb785b6e50f3700ac252abc2d4b26d85f77258b8cb99afa1765d8166c1592d36ebaa040257ce7398d8ab35475c76a296a45793402c58e4d865864db4fb3b6df353a598575d43bdac2b493a47a74f0b64645f9f77334979426896f295e1addbefde546647e1877d5d221c94c4b1fb3fdbe62ac6393fc1dd93c993cf1cbfdb7945d2301f7abbc96e9553fde46aef5038a9e072826dba69a6714d8e918d22c9998511a750fdc0659ccfb2ce56cfa6f18a9bd47266be4807130cddb90b5c6ff5604f70877e45d9ccb1871934dec45cc1118fec46123898cfbcbb06ad5d9f1f5f3c16c08fccd5067afaae7964f83816e99240e672e0964a23162581841687523c7ec9608f0414ab24d487b230288322f3be11577220442f3e8f7abc27120e7ca6be8dab0437edc064e948918fb39f3710ea685872e0aff015649b42151244731e9af7bc92ce6cdbc96d0eb6b585213135a584077214a8bf09e756581cd387e20fb28c9a977b822c48c1f5a63b9cc0bab8a5822e7235fbd51c9581aca02a93b682511c1139e8ba8ae87c05f6a4d136c2c1faf2d529c733cf9b1fb7caa153883af16287777551beb35941acaee978a859927186f627ef4a2cd82ce03aa56b1f444e3c9346ecb79c1b00185cf0b8b043c121d7ad3372ca8501a671090219eccaad9b5a820b7ce4db70f4f568e94b6723e039c64754721911648a14e64772d66331bfd91271e51c21631793ce8e557fed81efcb87e672914e2d1c82c00326a63ffba865d0ab9337921afd611a0a628dde3dac79eb1c72ba792fdfbc6405988f67689b700cde3fc053bdacb6d17eccb56e0e766ab23f18774e1e6e9245615685e2ca59670bb30a38468da25933d3c3c0786b046c5b986ed8402d38e9d0b9ab9dc42bcbc6d2638df95ff8bad10cf3b6a1610484f7e2dd728ebaaed5ec4df30e35837e7fb36c4125caa43d76145f2ea9b65f9e8d8fa4921bd2a968f08875b236fa337d4b9f84aec9550a9deae168e5e6a4178ed6fec00c31484fc531373aab34a78a416c969967a9a80352efe17e66e7fc672e5ca7d61c720eebfe0217d7e6b620a1734dbd9c20b2db7f42bee6d1106adb730d67b9400d13e4a7157d231de6091ee4fda722dc15f4dde60454128a9ab81597bcbd5c40e272dd661e768bbdb456c69ef9f0d8e7ed36a721083aed85e4fc74c8d00d4f3101724d038795db8f41af604965fb4e4da66f3d73503b297f955afee68008cce31d53c6e7ca6e75d24c5e42b6dfede69bdb9f7a1a09828fc3d6bd320140b9b421ca722553ecedb59650bc533355796af71a7b3094ea929c0307ae68df1b94641e6572b66b79f620289c7b8d2ba4193d886cabb5e76d28e4227a970d2ed502f9a89835aa21b22d42bef718fd5aabaf724aa3acc1e5f2323e06aaba38ab901362b513723457e6c3820bc2c8ecad9551909dac061b5f8b54195a31fa9d8ba43dc82a24392c4c44879afb41878b54f3dd72f2b7fc7601259eac25143e1a9719ac43cdf0721024e2f5b671a3ed88a112ef9b1c542f6f28f15a5ef4750a0ec8550e11990c6971567bd080d0ecafe3a7adfcd5f40b2635ae9999405b923bd2d271d7b96d037287911585d1f19810c57e408549167694c4075713d606f80f9fd21263f6931672a01081ae7d2e040d6643f4343dcfc2950fb1227a632aae4779c99614a95c5a72c3fcc151ef60739fb51ae0e1b40712375c5db2c470f33597647557efe48e636345ccfd13b625a7a9fc1f84e0e17ea51875de345dd51248f6e63d257f4052d0335299d86efb05401ede87003a00ae0aa0d9920c03069d7b5cb04dcd474d70184d32e200c93cd5c02e4ed8660fa9d86edc9eab1a6024335e81b8b918cf568d2917d098f751870830e815583c102d29075acef96bac4b38e5fdd8557ac78df60372b2ab2288935a51c523250d1e66ad3e3406cc4cdc10f6c84a7e6c30e1a96e592a3a9946268c9ba33b848431ddd3819f79571b7309d05268dd81df6b58c1d1d82c78a408d763f51cb99a36a898bd510253105dcf5364fc2e54bea5c720905a317223b59dc2a5011dbe0f30bd3d26c6cadae749cba9b4f0f212b414dfeccc67281cf6fd6ca80dba94a1eb6f592f9f3cc717b5688ab2bbab19e8703e470a614ffc2802148750196972ae8ae9dded162378f0e76ed1f4d4d76a2caa4e721c8bf56672107d34b6d34ea3300d38e49338326dea4897bdf5ed1fc63c7426ced41e5e4c269f4a2b7791d8df341df0b07dc71a810c928ece2994609b6aba73355ef6fa7e2892265f213bd8993cce5550d5e9cb809e7212442cbec04fa31da99a34929e417239fae0c30f70752d0d72c1802e5849626256f630d8794c9ccfd10844651f5d07dab82b75bcfcb9b49029c6034f9a31fdec51b8b80cdf08f74b26c4bb5f33ab7275d57b41b29b08b64fa4247b70afb7a804cf027c98bab9229613be78d18f0b72d90bc3875dc3abeb5ad824228a38c52a57d62cea10b10aa169e27176faf8b35c21cf070f3f324f7caf591d4c073ce672b97b7ab7c51eddfa45ccfed276b1c072dfd2fa73e15ae64855fb6900c286e9d30cc9d5e4c17afec49ffe8b3bd3e3c55386880b7529a7ef60ad38d02ede93b1074740a80a7aad5a193658917330e25572f3bee1759c20995d97b51a9a39b1b235c154a2ba538c540953598d9ba5531972f1bea1436ef0baae83b7d1e5ff9ec350e8e03a650f3d990e7f264d13362a1638c43ef0b15faa5d0106df4ef233e69d0a755f29f4ea973511b2d181e3ccf6f04ac815d2bf49c0517dd9f42a8b7ce93b2e3101201d089cc6972ac11f01edcfaa0b8da6c5afeb37d1d7bcecf78986947fa81947b566a47ea02c00737b067aefa1330b0da6adcc44f1533a537283e6a42fc9e60bbe9e9e62effd3765b6bd7b3376725207a0ad18a9fb965a3e576a783e26527c5683e5c69e76a692afaeaeb7020e72c33967bbbda92379197165b96b6e6d5968675db40280cbb588e822a82067197281aa0f43df4ca30aba4bc17f8329b2f625f661a838f528557a7d4b5fbaa25172d9ef9c7ec4945816f9bcd09e1e0bfa1a0bc0bc14e0d3d8fa27fc7f45c8e25172e5bf42072f865e097afd54b6666365aa840342ade7709903da6cf68334b14072a65cfe1dd04908066dcebfb4e992b30a99ddc5630c1c5d34fc58fa4e71d3a472ee1b352c6f4a0fb4ddc9e909ebdad03242baf348bb14f06d97cb5829c9ec6b36a155298fe51312d345ebb88c752770f0f2f422b0fd1cb18beb4798715486023368b663fb882dc22816671cff4ebe64d251d69d73d56b8ee7b9125be72e02dd72502eef95052ba5173b6ef8f9494d34467b98092ebec24e1a86b6bebca2a68772cd74a7f1cbabb627c1ac080b028adaff38a9f2b6df9b65f2ab408c73dd5c647268989f8f3bf8516bea9fc2159b8c61f64fa1b6269b5b5beb198856f9a56fa1723f28c33e30df34c021ce8c2793de2085952f560ee51c87953a8d0cebd7c0477233bc42372f3e21224b83a49d13f8513be84c0042d3ba595901241e19c73fd8261ab08818102446527fe227a2716afe7347af742dc8a6231956a141a34a648472640189a926f4ec7bea72a4cc48b02ed9058c8818bd13bc08c8fe469e242c8472575af29d32b6fecd914ba31ee5852abd50a4c4d3f4708da8cb7635ef16850072d19cf08d9b046cabcc31859958ae77b639a2fd29bfc3827ca8c4b3111a6de74e3e1aec4e0c2f7e9a1c8eb2b1f839cbaad4ea2fd668beaf28fa93d42cc1b9000c1defcf15dcac1fc8b261f1683dff2d2f6d67315de46a7e8654432aa3b043d3284098afe86bc931c4fc3440362e672537403d9e4074eb1fe48a2fb89b525964726aa41f7f7acabe210acc84ef5525772d5d9fb633117f495ebf69bae9b0030472b9f758208fb7e7c2fa86c57c7cd1bd38838c2e07f667ee88b8750d4b68fc597212d282cafd23c91953af81e4b45d18101b45ffc180fc74d515de767a2ab6a1723ebda48987e56bd1be91e8d0ff6ef7bda80dd157cedcf640fc5db767c21a9372cd5d3a83ed1427c9f315dc8868257635a0bef860ccfc596978881c08d9a2392ed609c64fd261fb56e437bd4c344c3a4f707c82fc61be6fc734bf43568eec7b723069e56338ca77e64da56eca93da69c8ecd6a9c4b0acfa758cfff8dea5b1a872b4f54e4f5480f666932cbb0f2bbf4a8989c71cead4eaf6afbc5e4a1d8f6ee672eec2b8ba135b637e8711862d17baa73d52b431358d67aadc8ebfa71222dc827287facca11445b7e05375298299b97cbf16d5ed99a0462e2ad56a519e1136b92f1ac672643da357807457f772cc9579017ec68cdae8b3d088f2536df5b753e60b9add9b8d32755b35a10a03e020c886f05dc81e5254454cb8d50d610da765da1d78b78ffc935de9294b11da8667399442cbc3155eb64fdd29c2bf553f2015807263676fc54a400abd52e509cfbb63e2ff95b855217ef380e119b9f68cdd6d0272b1b11453b562f76b5fc707ddf2576e99ae965d4b3cffb19d84fb92ad8601d571784ac69b5d21c8bb141efa564a07e74e1a2384375b0ab48f8dbe53e4f8d8d81fcdec2e08bce8871b61339bae7d9e7d1955f253521e821aab3d09f64c15502a45f2004504fe98acb27d8dee0c63a57668e70921e6da21135a4ff958c0cbdc170d2015176af05eb0ae6cab405cfad3b422e921824aa29e24bc21df005a735891720ac01526872de906fc72bf7c57c9a341468e1e09d4eedfd095887ac3fe52361fb241bd26c63317a3caf7ca7d507790dfdabeb6165e4207476f5ce6f11bc08e72be42879407f671e8145364e3817bf9e8d12d8112803294a729918aeb5ea5fe44d4641ee50b2481ed188a29fd7c5f8c00551aca1c765df56a29465bf2a231f272908c6cf3db666a65b75bfccb8af5031795f165d3898a4abd9530f61c10b73a1488dcbbff0342381431b6bded68e5a84c2a0b535fb439951c953d80e8f6555a72046557d5fdd52e38c5eb0b751b97972726c12f091be8957c9e4843d5e1fa414664ffc62e3117ad315506b5b356759ca9cff1b0d2b4407907842ec503dbba9452f86b23e93365e911c8d965befec8b08348b30d862802dc9b3d7c3cca054e79723dbf74a51ad01c81deed4cea7eb12803a891b4faf2b3192b5f10a382dea40f262ea4b89e9cf387419bbe67024f91481e3479aa85ea3fbd35d4a1c0c3e41ea2610e042268e82f673cc3e43452794d87a8dc281e187ec1786e1e17cc3746d6e072ed1d9cf2cd7d213079ddddff57c77e32da91356af5417fda8b2a42fdd48bb73b5893e6b7de46ea379e9a464e236beeb1dc0eda0989c38185f0f322aca289a33917849d955eafbf658879a7c0f1084111f731065e7e3bec13ee10cf092615aa0aa5a8954eb24f65a4cb3d9f71b19cce0febc6d33973f5694e3d8762aa297e5a6b07755455f57bd07397f6cbbb8f3a786670c3714e1e8891583567135667dde272c1e03a8985a4f16588e8cfbf1f8b1d3407a081b8693c36ec27fde3da0993fd7223bf24defd8229e4d43df99dc4a627cceacf2184e4a952a48c50a4392ea93c72a87ba4e7465306632d640e5bc0ffcdd09d146149f90007af8a96040b38071272b94ecdd3983f18cc92aa11c9b723b791a3e5dee505113f8f82367e4874668d72e8a0e482da26dd64972a230e93c3a33ffe3182b1d539caee3fb89888cd326f726a5130f8a1e2d763e30dc01e152fab47cb63db36bfe4927905eaa8d30c5da772b609467dd2a76c14c9431623a9ec78e4adb35fbe246c14e2d0fdcbcf03326c72d7812bf0706ac21470f58e95cfe7ffb17f76ead9e6fb692009423f8936356931de2ecca10750e3596e446aaea8d05b419e9c55b98b44c99ac413b22e577c1d2c4e5ef7818d4c793e2a51ea9fd3391ece2d1de333106836f723244a41dfa4ca606629876de33430996d6430134d43382bfc3b30ca3df896eaee77bad739991732792117e98d4b0f2cabee77e26ee4c918d37fd7647fbd52d18ddf5aafd150db727c62371faaa2d844ab91954535a74cd10b4ef5621ec4da5dd0991c230e9ee8724c0a1d24d395a1097d66a98f4d42a3aa0fc661d34795e01e99043633289d991f5140e61d17cab80aeaa6be4c3bd0f72b78e1649bd88bc6a1f921797b97d5c820d0058d5b2ad2c55da5c4e298d7d48d2fb0b391eaa36f01b2356406ddbed9b25a17598be4542fd008cd3edbab63b9190bfd16034bda6e1d50d82c3dc6f25e56724fa3e765bc0a02ea177af84282ff1921f9e004224f9041fb540117168e8d9f7230ef18b5f923f23942d76363a13fc0c6fb2982a0e018a4735e01917d5241881343a70ec6d299668c5b20c1d0dcdcf3d1fbdf88bd3d55f6e910767ef45bb7f772a3e50bece8d5b0bd902b637399f52b11f10e593bde4d7447451466613685d8725b3800f5a0596c6335e642b85da1733c26ad5a95645003e07913ca2335f90b0b214bb17d20660e355b692db4b3fea23e04bdeb85af83e5c526e78e83f57e14723d0e97bbcf7eca1eec5d80b314c46e01ae79d5da235414491732626a8de73f3e089cd715d78a72db8f4a79375860ad809b1db037ceb4bdb6b31cb7bb4215dd723746cd9aac67f8ef2dedfc1b7b52cd15727a017471025ca0bbb60614d4b8da1413badeef5778e303e136d6980a6d41224ef46d7a57b9de64f939b5749d03f172668b8da83e867a2722a0bb78154d12b43190a325419f3d4480574e8904a2fb337da00cf43f7ee56422a410ebc06808678f5367c1985991384e6ed4a13ce7a9727532d54e8eeb0cfc3267855ed8b8aa85e05911cda1fca25a854857bfb5f91b5dbd1613f785b093286236e09796678250bc29b755f946a586aecd17426648a764f979d269024843cd186ea348497bc4c24d3fb1edc189d2613c4e400111506a726fd44769906a0408fee7d7da9dede8426dd3848b971ffeba468188663b641411eaffee0cb7f9c4b5d56417728e1b61a1a88bdd9c9e7441193f9512c1058c7572dbee6b0867bb9e9fe5ffa5609d579afea7458fbe7bf52d910beee38820f1616208e4c1ca30a8954dfcc2f0a65a59d15bf5d7a8ae4f0534a4729e76cae29dfd72fb793fb7fa3ca462c7737e97bec8a64492b9a80fe548fddacf9643af12143372638bfa939fb31d937495b5c3fe878968c90e6db464ef21a44258a5d2df7afd727c22f97d1b0d844b3de6ca05561ee1613dc5dad5d8b21107ec522b2f3e0bac7292b42cb331a3661331637cbcdcf35c6a42faeee6a7817de6de72ec05c04fac722822686c41def9211905df8fc4937d10fdc4b0270d8c638132d915897d86cc72bca41a2468ac66bce108221ddaed0ebb95817556533c13af49e4da64526ce540a392db4130f648d4e865f4e0390da73409a7063ddb0ea0b9d4aea82a082cbe26b9b8a33dfd97f7af571ec1f320bb0531f9ae7eace34694e452f7baa7f8536b722a1bdacd439861b7b5e03db455208a608f2c1062a34b2e87e7c92005d3b91372730f1846e832a91c3c20780144ef76c2985767205056fa3df9b6a403b23d353d6d53b594f6d6381c0fc2e1c442c0aa485dbae8885f9d251e0dee575c27cd5f72453b49ea2f1d09d3595a69859d74f9dc8b5c4af7cf9a4892e4af43446177b53e11001d1f7eab551ae568e42c2297bc0d53d3b9ae3d2885ad4657e6d0b73bce7207dab8dad679c1b867d4af8939f83748ea8d7a7e0a0a0c9ee211a9055034ab3333cb0408fde45bb2b34e33e870b1b7c75887d6450cb1b76182ace2d955c90072bf21a5434f611bb189807b045119d2fef41a7596da9aa432c35f625bd50f7f72aa5e3dd9c4e7b85b647dd71d306f6a9452ed4f2c7555e5f9034ad9ec2c2dd272f6b7d25dae9210ec4b31a828bbdc4632d0730bcf46fa258dab9646a1dd7525009131cea9c685e5aa9067e4e803c4a7d67838534cf4cf433293b7ce63b36c9572525a4daad1e3c2aefa0df5ab530f6945826108d93ae3c052e5e6192ac4d8b272ae2061b02d6eb7e3c67c9254fb27c95ed73a217b696bce99cafd77bba5d7af72df7d1e19b9c8e47341edc2cc2d314d4d399b2a2b252d237893c03dfae58ad472fe1e7f516a3b37401491177c7595436f06ddbfb69709868701037639a74e434ef5b9d97f632efe550d585e9a8791109e6cfe5b360380987df1e2e0187691d67284e2361e59240f842363d89a6cba076ddc2049fc7f430648327a30cbbb929272ca06c75a1f7dee1df664bfea5d8f6f939b6ac89d4450ca69f344eab47c31a372f07e2a0ce9e910a2964668d153b1ac9228a0674876e5535c1af021280057cc419c595cb999caed92d22778733159b1bd970bc934433661c1689c452cd3454371779c1b349372979dd69bf06c321c49d72133541e58849e34dde820ca4541cd5efec547d7d614cb3fb9b4a5716317e2fce02689931a0e0ddcd89381193c9cdb6844bb7da2be3b78f5e50b6d80ea564e5d53a6f83978504c96b4a10434d45d1e2f09ff1505168675017decf3098a0c13f5f894804610259b4eaf6f371c84284c72d55d7ec52d0877fea8dbaa0e18f658fce2bf824054b8c1f8253dabf444129f72d01b63275f9c99788321b4133fa566af9784d17de71f081fa7b3c66264a22c721cb70b2a3a16f4491da2b0f1000bea064e58fad860c6131e6d21dbeef894f272d48c9a6382affb6434905c1e0f0794ceb3e09cd984c7117f23a03994ba2a241680c6444f6242c6b5f05d5a368da1c3d1c3fbf78add449ef3914f023e4c0a43722901d601f025e48d0310eb6a429ac9ec1d7a676c62365f51082b27c409e4c55a53d46c61b7b49148c054271afd22ad466209f2b3f4bb277b85f43d20cd0974631167fe7453108a12a2acbf224a0dca1780722a00a68119946f0b4b87fff5c1727bbfea12d5ca677b451c107a88f46893b8740bf0acc27cafd01c7b00f1526c72f0f7fae823eae5748f6688d1d7e00066591baa394b8e49ad6e942286eefe34595acae961c32104a157551f7d30b3f4f48a4ac80c5657bd6644546ea98e26ea7202d426adcbd6b8eb9ab882d7b9332512abef5438756554a5c35c47a36dc548729fb7b98d6b72c4784273c99359388c201df3021675c48830b0b8e8ae21d81172b193f6d0b94e0aad67baa39f891ee6abda0529572da67396e896ec15fc8510726800e707fb74d0eb7ce31cef7e2282502aa8544afcf4a6f2dc329988580f437211f55e475f764c07b32ce4ad8b675980716c1f91009f519e37cc386f97af4701cf7fe07c49527f934d4daf85dcdbfb9ce180390dd05c6b7dc1177fc86391fc72de3027b88d3d03f5458ed3a045b5aa402be07164b76ba2171a84614f658ea66b17f53bb81e8f33b7f29b45f30c6f2fe98fe55a68964820c9b7168b3fbc9d3e723cd28b1426fda04644c72b309a69001c9ed7eb2909bcdcbde659dec0827e8c5d65f7984658aa7fa86b39946c54c1f2cebaa1ddbb7cc4e40a3f57b263ed12a772042e52966befc155e43c848c8ee971e8109142c366d9c862d62cdca605f98772d31df68f2d53fd0d14f57386303be85297baffc56a3c9db49bc5ca170b70bb722b1ddda60d7bb5e4242ee72ce2421a65809d5851f4d07e8c6abc5405a656db7252d6a5d6dc6c9505e6f720fa1ae77c2076437ad50118bc50357182603b28776cbeb5cfdf9da1d4e43e610eaebfa14509d32588118149ba3e211b8b1208bd4e729aa4f9ebfb25d2682595b1f0d04bba25277f4ef61aeda82c20fd9fcf93e15772b67cf566a96e1f2cd239e849f8f5241ec0b8ff43bab1513d37daf1f593f7644b270ea617bf9565cd90ddd8a40078e2765286efe75407d11ffb6fc77f971d7749c2a7b71a2c51b4d4e82ff504c8adc5d9b47f3f479a17533a9b6f1c939508d572809353f5dd36dff65cc662dc2a9b5a2f70a3b1551dbcc319dab62a3e6d05580d24058f26eb730eeaca96d4a59ca883fab1187a67b2ff5ee5082f955a278fb744cb4748f44bc3f269b5d718ecb49214807109fb9534388caa05568fcf1f3f01499eef589445aec476e58df325ef04c492fa502c0ef15670e9bbff5c50c6c1f0523153a26feabd54d84d67064eef34858098b22a1ef9bfed4f2b894fa3887da9729d4a35ae86bf48a5e9f2ecba0e3fa00ae4eebdbd5e76e98d731e7bdb4b347103660d7792512c0683b7539bb2bce93ad396cf7f43bf33b7ccfeb3816aacd53409c2dfac04ea0d88f7b2551e8fd69e3e4cec3846e8e769665b9134f17a8e375f21274cccd793a6491c88a58c7bbc92b628fbc5f31cdb9d3fd9cbbb721bc31f0516b79491f3a94f7d6905c7b38dbff5fa26680fc9b947e5bf0f5a2349c724f84572bbfd50d6e520ad466978a64009b1e7b96407448629722f2c768e216692778c47d7782cbe51092a15bbf0730a193cd228af832f14e46e6a39916984dedb0cd472491346260dae6204a31c63068de9249140a793ef912f2f0ec2e8d92965eae672130b8d14da01773231bc3c505a773779d798bafa24163780b97ab5ba85b35872d156256db722e68221bc3721e114edbf65d8f4359074cea93430a1eaefa78619025cba20d992ae47fc5504969c77ef9e90066af76096f021300f9d225bebb40870c2298e9b2ed0c5ebb2aab7960e8a72830fe54fb6c4177ad5c6654a3a184d723f5f09bb8c70b7edfe80276976447d0ca2d8943596711540ddd3568d9a912b72e1e148ede9fcd573db9ee22531b952df32e57e4bb0c490edae2c12116f5afa727dd71b689c1311b3788635823fc2e3acda400d2bf1ef31b752595cf24dd575474f9a2d890dbdd32a1df0730415885a1468d0c78402a900f828855124a5249772d5547ed96e88abf8f53bc65b26e0cad29d0b4292b983ae40546ba66455bb8a720acb3595fc147b366f804717811ae99a05e3df301fb183f391c7dadc4b0fe672b33d523d45483dd1c3de1f87c1d653d982ca39181adcbaadeaccaf98ce7aa35d9b76e5883d7a2cd7538c929570bbce7e0d48de5fb41f8166903222b1e2bc68729fd8560deb6623d62377664ac851670e6b70c8e5ca152934ad0911e9f99689725bae21e56029b7ababcd175cd7f55bed2bc42c231172aede254679fbf9a37c7213a9c4a3a4ef82338f63896ab30f531c17d3f3a2c0905a7ceaad5089980b35720d48a98c930ea78343f55615a0aa4ad0bac4ceba47a92c6a586bc2473b401e7292f794263378a9412cacd2c33a775f3c62809b5004d243b22ee4bcf3bf4f9172063333e72369cafd923452fa9eb820468315e41cd6f7caebaac59d380857e21114df25131d5382c9a47f5a709cd617767147bc236185167369b0c6d01af105720c4702ff525ea1a4e1caf10a694ca4273e0397b09a77912e5b18ff91d1f2e872c08dd6359bdd48429c6b8f158b7773516c777df6a1ce8fe464c46839b076a77219f604e80b42b95cb2dc17208d65362af0ca15d5d2522ed0a8152ed6863fd5724a7d055ce2fd2adf10183185e7dd64610cc898b94270b95562d3740a64b7c17284c2903a109a4bbef52ddec7b64884b44d977d9a76baa45a071a64dae6236472d4b1d767115ad46ef40f809e2afc65516e341613c1cbc4a06936cf24fe71a3603042e4c5fd2e1246d33b7cff72383c85943c4f9e82e3ef18a7e5c89445801f72320e6f77a52eb90a1cc81b56193c677d64de507968fcb77f5e2b5e421f309172d25e1756fed5e5cac9c18b4b48d6a6c8ed67aa6fea3d94b7bc7e95d82aeedf56c0121713b54b13681d0cf5f07e52a06d57517ddc88aae0920bf14548de5f0672f87f0d2c86881c8bed70955157ce70b18ac8c68f657f5d355b74be106054aa72f0901e444eefa539050763b490fc73bd4fd69e82633dd8b358365540752681720c81043ef3607df2f0dc3e3ed7995de0bec44aa2c8f13f5191b7d6257322a8721251db6e5a452564ef9b684367ce350c0f88262fff12a4ea9795e2888a118f72be4bb47f69c13be3a4ef2178aabc0e5e9c6a70b768d6be62d26110c1cafa2072d3d6e1410309d5cad3a62216ee87fe009cdc3b5da060b0ce8e5ca9ccd171d934c09375315f2ac51b778b9067568c5840b1fc8739fba7cab5fa9c1be451d4e9725d5af66d3a2626bd949dd1b8cf22f5bd33c51a298103242e0cfb6c866eb65472b3f84203291249eb8cd641681864199d1305e312161de733cc6bd4fdd27d9772c7518ebe0d075db9f125abdc7fbdfc4fbe2df49a698c04562b1bc5c659dc5b72b74610cee60b51812226a72cb2c2df539bf0424fe8ddcd6741ed71584fd3ba728c4544d084cdd70c3a3b002d17d34f4c0578452da31828b9994696755d66d564d83149d8576448d4fb02d953902bb302f4e0edc89d4985e875213620715e257249a34373d633b7a22792698352ea42cd7ef072f1c1d7ff9a9b458a3c5d9fb5728141ac90d447b9350e7a25463a0e173785865567df8bec100612d60e19903a34eced7d3326e2c304f329305f01f8ce58f8f59e24d9a8276840409d4b34520839fc26e25013557003d1dce4bff84cf2c52652ccca91186b1de65539e9187f537281366b10aec867373480878c14ce44f2314de0ee7c64ca416cb0a801e7172572867d0072024c83dfebd01ef3f0684ab0afdb028badcbfa7492fc1cb126af7d3c02987464a9ebb6bbfa8ef6a5ae665d0239502cdf2271df8deb1c860eab711d16b9d70206931df34213e5b4bab0bbd71cac4131a68edceccbf95144139045767295887f92a291a6dd7a7f0885db5b9ae13a877ab479bf09206cda6dacdeadf4721663141ba8163eaa8d633d7348030c32acb285df701a13e2735e908cdc72f40d3c5b53b04ac0c02550aadba9e52d9e3558a75bbd9d9f69e64ad1ab74be240d10b416e57076f2fd3e6c97fa888c3afa008bc52501e27c649b5bafc169a900f06ca0074de21474215b90a6d6b623d6f39955bcfb5d4a55c51cc5d99b171e25f1721c1e77b545b05b361a360a7bc262467c609c8c2fbfb70dbfed0a66a2e9e271725280e4bb4324d7655bcf0cc651def80e0f4e8ecdb0358bdbd59a4d2c3f41d937de91bbfe02c998a5b0312e101d8bba04eb98253d3fd21b4b795b47ee04fb1c72fe4f8cb06f4473803c5ac8b93f8470369e015ace430a9b00d25897e1d7a474724740c3d4b801df99e30464a41fb9684808bed3ed154363d29e3930d355a113722aad3be8b2c09860b049cb00cfec7542f3a606d021e40f18a80527b1a81e5b72e75127963575ffe8657b068e711b8e03edd53d262fb5c837744c41568f03072e1fc2fe6866f08c182a3aa99d442dcb4b9aebecc0947fa4049bf2e4fe26e4f02d45cdc0557dd12a47a3a06603cb736e0e24e70ff56ffc93e4df7d1cf4d0c6357205cd48aa67b2573dc99bde44e6e558efd07a4eb926a106938cfb4353c54f9f024b975c19a206106188fb6fc1aa5584de57d47381c9d1f26241f33b2d3ecc224e3e614f9462e097ad43af1134d226242315ec155478b7499714448124a67d792a20a476d157e976c3fd13ccd45a2a66585d7a5be913013b934a7c48775f383363823f55ca6bdc227685ea3c1eda62cbdbcdd460386c2c6f5da883bb19e79d82000024d42e1dd05f3ff6f30618e181936e43d8b2c285a1e582455071c9349e00179f9199c14a4333243c84383b55f4e96cd59a13a07b8e341d3ccd6eb2fe42237222d0da98703c719b064f773e56301af3bac85b17ba137180e5cbfbd0a3677272182420aa25139b95bbdea92c27963151643a6e7add7a51d1070abc6325724f727a134c455308f25fe487d3234a7bfdcb4344b32b7813117a99c7b37f664379722873aa3fc247e7e31c592c7910be00c1480f4b40263ed8e93dbf5bda678ce4728fec89e9057d1668024c75ab431cf302a68f42695d84448203c45bb6d3f76472a984eb67ccb12616ac379d07f3c1f091487aaf6d70383025f2b133be5327bd6577c6d5711f0dedc7c5cf774105bf13e1fdccfe16597acf775dc300d7129a9a1e88920a498f2c3782acac88011e123516438ae7a0a148f99a96a864d3f508b472fd0db2f17d173d8158101c72416c181866a63a4cca6e0534a67f21933177a0724f74a4629731544a843e699feaee05571c9bf448045c914145afcc8dc8100172de2923a7a13d64ee1376ac64b76d25ea066c46369ff163fe8efa883443af3572689e28dbc78cd084832cc8d00bc73beac598c8f086b4e1f9a600ed6a9e5ed7724028728e03318526295b32d22b894fb481491439b9e43ca2ce3cfdf37a65b4130c69439f33608b1c96608c6ba11b5704efce4a919e141806ab542fe29975757224291ad8105c4935ad2036dcbc4ea3b16786f6f18dbe19b7c61e797c4985787231eed0cebc19f0ea05d8151a73b05e138e23a4c28d442d43d543fe3210497872900aedc1ab0c5d5edfae811a3e072ec6dc484f50e826853708c9d4138592163981063422ec8ce44a09ef2851c44a65b495c88ae48a7d4bad19464c06de7cb672ee8e8794c75e224b576a91d7eba7ceec4607dc42cf6ffd283ce8e5eb98cf611a906d63785b6c6b09e71e5e1875123210d2816b8b5c07b7c612ef9bb68bb44b6608a0cccb703917463c564962a3c50de4d05b3d9f1999a737eb1e522d1b08266c6f8f13277c8a13d7d5481252a54048d76787413d739a65c5fe831fb618352a30c325335816e268d81a71a380fabe717fde263a5a7cc121135c99ec19d252cf728c471d931b56d4f2733df98976f5e1af7dd2e6ecc1661552224fd55993fe4e14e64e0c7bfad973abb4cd597fb87677f745f86cf17a3302c85bba146ec288a35de41edbc502148f521b4fe27deca939d4959e1db7d9a482ea11593b88cee5e223e489127165bcb1f2441dcd30c8178bad2bce99ac1cd53a8288adb7630b6bd472550e85b24d6d59ef5dcad31f1edd315184e8487e8af88b7401c79a79e1a76872eb1b8388631cf7bda07fbab262ba8283956bad52c27bdb2143dde43995e335578185169e9b75e4bc82efbaf526bc6bf2c78cfb9c9cfd821ba7ea863df3b8cb72423341f0ebe36d669480a41b42b4bc6efd46e83b2d0278cbefdeecb37e3dfc72397c85f99acf7dee8d6df5b54db6f0e61fb1366aa6e965b0ff40166253f920600bb062d0ad5c9b5f97cfa0dfc5845bd30664e78f1a6c1c4f8a9a823882caf772ea093bb0e676902674fb28c9f464d7ce772e7b5b04c8f3e3f3c1b93c34054c72dc099a0d6c8f4c88d8173995fb74bb0a3f31ca2580ccd4fa27b807783e998145507ccf448c194338635b14cde359242cc5e87f29fbb0259f69261c512687154a6fe54bcb950ef79ed20cb55dd61ba718d47ec108ecc6fe82fd5c740a4b4cc052935c9cd319dd109509597602f21c0dafcd2f9dd8df37d8502106b56a82283d198467d557eaeaf5f42d40bc01ad53fe334ed169cb133292a00d17a243a8b23c72b7779297d7533fd79cf6c40d0fc14ec3c05759a3476a0ae84e3d97c86fc5b7729444e2ae84d6e8028531891e8026c01e3e8f03224c73b680bc8f315c805ff6601f61f4acff631b444e973699dc87a9819c30e1d18c7d7fd75c4667687f0770121f9e53b2b55324028ac3c45942dca9f966552156509965c692d0782f5264b45c7d5ba79795242041ce5bd70b74de77b6a9ca969379bc3d9af6f5229c92cd4f721d441a226bc825bdb98575ed57bf826997515e56490ebe59ed8456f54910a872525208b19978c755c19af3cec8d642eb7948ff154e82b166028ea72c4ccc12724c7fab8324faf363ffc1fce7f5e049bd11555d9efb3747b7efb240bdea59710a431d50d6644984c96c98065882289c2ab78fe43bd64875687f0c3ade085474726e70218a9a589a7315e13a4972ffda2ff923575d847314310dd7225349ee6830c10c2bc5b647ce6e21e2017a209a46ce7b4bbfe9c483bcbd84be74ec3db67c729796a2176934fd855e4f77a457b9e0d6fce8f16c0ddae5fe4efb48c0633ca172517c82ebbc4096f131bc801f252cc152302bf4e807650dfac799f2f819c505728717183e093f1d31b14266ceb9059afa96dd2791b1fef93e9e31794e2da91d341c82902d48b901b00e58a8e262677003b4d9913048aa1ab3422f619a240d181e4d316c06d8474fd676b4dfade344b065f190252ad7d20c64da5c6261b3cdfc7276f8e33e4ddf8ec987e0f92b912b18791f6be897d6d73bb03a4def091d4e9b671f1c33cb39989d1978fdda6f9099f686f72c27d8c6a404e921b5f8a87cd97f725ba5d05d15a77d070096389c9d2e6b9b492ffac63578edab65411809c0326e6f4df2fcf2497626355c5bb764e36ae6ba7b9e6ea7402985683195cf3583e36e417a5f18127418ecc6d5d64af30a7e8c20abc4ba43512721ff41c083ac92fdac726781e2d5d28d483df473f65e1ebd608caf77c7a3664f2ccd1cf4b10555e49c7250d81a1b18726af81e003a89e223b12d495e32e164274d7dc9679ddc1268d872f7b2560c8875a063cc3b55d30700664b55751b60d38ab4ad7081973adda3eb724e535ba24ecc29618524b71f50288f11ef00cabffa65b43f95c5f8c9e58c87724c6163350cca7c0ab46e54a9d850cc74253662f7ecc02a596b084f3401452e72d39678aa50284d3f67e36f9eeb340427285ee95fc79f174824344b2683967a72b842ddd483fd1ac5af90fa490486b3f99134f48bf70cb95ea407dd56ea945e69ea2d01b7adf1e377504f35fe822e9f2cd450cf35d5f055f679c0a0de7ccb03722dbd3a9cb51cde2631c025fc044ce1182f01e8aebef4d6ce41f1f7945331d20405674a65de6f0e20c6fa4667f5453d30650a2e5f94112c1a6944c5abaf06ef720f2dbb79cb0c90d02e8e6b73fec71bd31e61aaff1db7556475fb61de445d9b72ccf3a64f8639c410c630a4f5268356241dd2aee3c1640072fc3e50c88b1ee772e38d812af26560cf35d57d634ae5bbf8414cf0a81f12fa23f243f8cb0c2f567281ae2a9cd082d1ededef6352ca39ba5b50fa98034a8832efeb47b83134bd24721018f1d2042d709f9e56875f044dc1f61d8f97f6acf83d42d0361d0793f24b72bf3fc75f55a521b1497c81758e2ab0bea4580c0c1ba4b6a5fe1a5a7847a98a726973a935fd9bfb232d60840249b0e06fc6b4229bff5f37fffdc874ba96bf87720391d66b93d15ea6f604c11a5d608bc22e3952245519a765bf4021bf6ab4f65c0ebc911fbe549793ce64568ce9c503f09635814021df87bb30bba578eba28a223ac92b8f1880bdc2e4642916080fb1dcda77e66189e2b2d0b3aaaacb8bbd0772bad4bd43e8b54d60b54f59121084689957243cbbad739bb2b13be905a20fa17247764c99dcb649d28e8c321a9cee2cd06e202fbb6d7e6acfc5edca669cc19a32f888216ca76df983be0090f105262ae6ed4786e0365440770f0ee536a2ad2572d369ff2d7b973f13ae958d0f2da4a79607b48de76f0d0acee5c97abb1e49b072d3d42aff964f684fd583a53093a8f47b4037e7d9f8de4ce92239f2f28126bb7239a3e1460fde279b2a4bd84afb2d825607bdf1b3ff66c16d0cfff5a29865444599c75e772fa97924dd345ec76e3429977c5794b8deec31986aeaeed83c001772826378df746b82c6377909913ad825639317cf2526111de9bdecc1ee1ad31f72d886d38b727e614ff777e09145b7aefb78383b311610d43183a2fb4d698b1d7227d7331d5415bc20b0d0f0f08d710863d0b8e022ac7adebb9a03024922e14872431f704bb63d0dbb743697491ee99a803dadde830dc2c8a4b99060101b2c6a72d3577f2b9e1c78f6c14e76fa01a64fc1287d1f5bcaa064ac9267c18c800242721f5b4f40b289fbac6c24ee09b471276fbb5e0159aa0bcd06dc867e0d8d672f7263b8e834eb83af6930edd26f10c87f98bdfc515ded387a80945f3a6dd3a84d7240a9b539bc913df11860e8a5b7264c56413faf115e86a37e3831bf380eb63a4eb09c15a389cee58ee2ff75e831b83b8a0121c48a2acbb57426424fdf5f059e39ded1326b07f939a9e71592aa9ac5e90e4a71739321702168b4f648055603c16b65d0b6a29873523b4b0d5ad5c357dd457ae2f387e188d90c82a1f0f6c9096a72b8f2ccba0b9afa68cf4c41d9be9c84ad4a81aaaba9b26f4b480f6b3c1180fb72104595f50aab5f051c69b765015cc117100f1d98556b9ba4d7d2351b396e917279ae71c6713211e3859a656692ddad418cfb9d1ff98ad98af1e38ffadf059d729b1caae6dc19a8c9bee10137d0c3c286db42881ba7b094286e942031a72fc93801d970f7e3d75148842580640131fa6aa5f1216fba078868ca2e327b7d5c5369f5dda454e89aae5fc40f244501ce4a2a41dea4336dab5ffbb7a361c0f928937230e4dec1437a47d1d4113f8cbdc15b6b914961ebd071f4f5842211c9b2e232727d7a4841ca81eb444e4ba72fa82bc0d1ae8e53e265f6ca51f53dd70ce215ef505c64af3ff2f5ab625a550401bb26f0933b5df24ecf8e1924fd02a38fb42c1271a4d1d94365449813a6e3e10d43415b730277f5afac52410edde1a48f78e07a3896a31bea27a714b0128ff52ff7d3dec7929ade9426b1716502e18b64e0826472873d31eb30b66b8b376421869d3c89fd0d116865dabd0881e80dfb8b9cee97723a9466160b7919b4f9c87a8ba3b3f57cb741a1a525577a091fefbec4fe831f3a4b7598326a1bd675b9f911908ab0e7068a7eda48d109733f5b3b6e4e4035855a2f424fc6bea087b49ccd10d5093b3dc8e92c0f4e6ba337537a2ae74daa4e6a22b94a72ffd219decc529b75d65c13c6e0bb129720fcf307e56ec65751dc6a505d6a34fd59d4cd4dd53d4a5e4b133f56e0d877bc34cfe6381d54c17f4f2586f22e5c96807207ad988bc5821376e45db1bf36d9098762f0613efe2ce1fac7e7a57211fcc804c2ad5d7a395923d2a0ae1816fec412b5bdc3344b27100a31be1c8c0c07bd244ef76a64529d32b7716057dce4fae0fb55859cb3463714be8fe6862b72e48bf2edd485f211363ebbb6a68610a08679061ff3f15bdcab4498b5ce5aa6722d68f3d9034678b48e052e798ae3cbb3fdc3ab04b4692c1fe08eb19a2b648a4d61ddb0c018c7a826b867af3997ad3d2e30273de4190f0e4e3ba6d255ca47ea72817132433aa3bd25e1e370a0a33cd1f7b6d350a262a8807f301a7aa720bfea720e1a8c25337a1e2cac4c7040729c5cbc8bd8a8248d131244cb9cdb81bc752e72c8c37028fbcd723690774f295d299b67217b83e5fb7903a2e039dc6289d5c37226b12540d2744675c8cc90f141804f129c2bf2ac9febf804038f1c420298de721a40f1114d9c148b5710d834341016c9af333ef0ebb015f72fcd680e7b936572305ae10bf79db7c634e8d8b4bf73b5e43eee8973d0ecba3487c0a1340b0a0b4d03d4b01b742ca1eec03bfa1d89b2d3d2df5b0c2719bdcc957cd76e582f9369720618d43bf7c8230185c22c3d74495612ee31adfa867ce7c3bcdc847d1b490b72ad914e87f8e6dd2ceaa201e2dcfc3885e86b1a66eb6863db847b4564ce818c722ba25c7f3cfec213bbe0433588da406c09cfe7ba53da20d2be3d8f245c6be07298358f385fcff1650a686dbfb2d79a30458c281a0373bcd1642ac3541c83594ca9882ee8244ddc450144cd01fd73ead20dc08ccf72c9f3d749933a35a7eba3721ff01189c9cda7ea6deaa4c471c847c7e77bd4d1c0aac3840ea3c9a2af4ffa724c2cd8bc2b910c16415c34395119d222b000bb9f4f05d415f17b7df37c8032725cfe73c934011e6cc1612ac7eb2c029dae7b1e91c59f52e020794095e21c4c373df73a97cc649d540b127c223a2c594ffd9f61ac35e27e7ececd3ddf5d39072308186a9c1ad5e8eea03b250a2c23cb2ee5c6a19560d9b0fac58d900e3b6a5e204e372bc30e55467228f4f7e7a313cdcd092a81572917939366b5ad54c946d0332d6d24262c0d2ce39b183859f02eddc3b8b77119555fa65746a60551b2d40571158a4e42717e756ec54c7d9bc7d9a2ebb3dcaf2d9f653c692f99a9d0398c5a3a209b5dcbb66f5020e958a254a0301bc3a90a58e78ef69790483c7ba0e04ba672228ef08eab893b5ba56b450fdc92c36ebd2414821766f5577f5760589c4e271a44cf0f6c0a10fa5423feca280800889921c50553f54cf6ce0839f14e75a4017264b7b55010445ef8840d6cd9cbfd05b64746d954281c507a30687ad1fa0f9d41fd3e762fb66ad6b29978986118af2d7662fff051f545fe200271739c4fd43a199f407e2bb37a29868137069e5d2a54b8222dd476086059cfe5096a8742e5c738fc43b12fdf7446f93e96b6abe8235b76be29c171d3ae1f6a1e507837ed0bdb4227878f0fbdeea16ff15939ea3c7e40cf5c481a40785f781ec65873553826bc2c17d76e750a550f8f52d1e1add5ad81d70ea47f28c46bf98556ca2c1ce3710959baa2cefc1c90f47f30693feb6e3b8f7ea720f82c26e6ee8069336a6480db7e6c2eed3626135f213d31b2562bd389f62ab921ebc4309bda0b73ada6f57bea407267283aa89cba1fdb2f371ced57b46c73a3507ea3b800732e7d7e40bac4b0750bdd924075d2a5f08591f288114e300a23723143fe4921800bc5cdccee0bdfc4013fa078a4e04e62f1fbc34dbf2db0810f3b7889ddb996c12b4cda19103510dd72fd10a58f8c89f314265153ebf2a9049989b881cec147c0ad1e74acb84b46e00542986e041dc015783602198deab88cd0840f400d4ca043a0a0fa2ccad7c9987296f1e62e3ad357ef14ebbfe1cc66a17998bfd61595227d2b81905f4e68dd4720a2a8c8fa7bcb16200d1881ec4936408bcdbda8e027d87ff944317e3a7be01272a4c290b39904a4027ade7bc8c7a10e32b6e5b49c5f7ec6ec87ff668ef25b8d725aa9cf667ceffaf9e33909e8167dee7857b5348bcf437c8df8465f1025f91127be4a11a13bbb27982cc9e9a66fc7c2b34efbc0d2e1c36e783361fc7a74caa851f77cf3a258b635722c2e3fc3d171a27373ea51505891326f6df6add5853d7772377f415a5f538c3df13c3a43267a015de5964a493e888409fb128336508504166d309271b9e70a9d91d64d7174b31296a362ce8eaf07eba4e53cf0caee5b3072613073181dd833d8209b4fc7172503ef2c94778bc058f032d6c9e4d609241072a9d99bad818f1bffbfcc99b773180c0b009a8a87aab379309ee028f8176b4072ebd5c7d2f515da2df0d62c4d7f857c84650d86a756880b9bc7d664c684f0de1df171f1b2b63ac80c1d02816f2360d7f9d6f0c6e5cd75ce673ffc73a26d96e430ad5a8280aa47b44e8e4d1b38d45831f5b058543319b7383f79490ec3176ad3131903d61b33d266400853f4039e77f4f738a535e4fb6988265b6d373db7308803243207a7d20eb5ece37bbdb8f54e0a3e453e94760d17a08e2ce75783fa4b63594556b8adbcef6b700bad6825cf6bc9a154ce62a0fc9890c47ad1ff0401b1b07284fcde2188cfa790032e7c260b76335dfa8a3fa0ad93b9888b56acc600274d282779edb66f03abb593eb7dd0714647414a179bafb4a01472616b4f1b257e385966c905755f6be308aa428a2f49d9da15f2187751381a981681034e0deaf6b6726d49ab0398f6590b1b3a1c774f70fa8b4a15c0dc1dc7d28d1ecb96ce73e453728f0fa70fd9c84d1c426d585327a5936e331c5ca44d5aacecb0c02f5e9622647250be6d09d8f62340d92cc05c436357b73fa087ee3b03597d2d2149757e487f721ac63714b2942034b85a67b5958e286283cc607bdb01fa137f7106c9ddd8b8723a23be600ae9dc4099d785b4cfc0514f303061cbbf1e29af6be1f89f419b27728e3ef878dfbf1c5683b107c1f7b7b4ed09f672adb722751db69ea223be363d72e87616ce52813255ab5eb103a7e94af72c354394951afff366c5e2ab279fb2487617024e6f3a366bc517b853fa4eb19ac55e6cfc0e4e233c7d64af31e1689872cec42ac48c9db53fe589f7d5f4b00a97ea10f9034a4de53b76de1591f16b901e9da6432728838ba153b32273d0f243cf56f87d247b92fa3cad051e5b627b5c3bb9d7d2835cab838c5290f49bd328c23bf5983dfffee7bbe030ca60bb634f0572e61673859807725bd0870a840c15b6f9c2a9223190d3ffe8f4738b635008bf72ecbd11bd81fb03788013e03460f50da594db418ab4517ac23b4538819dbaef72cf8a35daf5637a329a089a8ae3c9ccede804d5a21ad9bd1adf6d7369175b5e5642bf7d06770652431f7ed1af80922be296909200bc9dd42e4e0aac956770957292740f548a1618ce1b2f91a624df786c4a407aef59af771e95dea9470a896d722e0574a98a25a7f3da7aa5f64faa6525617097fcf385c5d1ca45a54899cbb772ca4a3cfd692e4be5922823b4e7ca538f9a8437c355fe42e89105c9e8017d8a5ad0865e2f68ecdf36fdb79bd6961def7c35241783e895753e4f7b6a6f09a66169f7fc7b49cf9df4c5668858fa20b1f8ba58a9062a02f28edb26c8ee671f162f72da4ba5b3361a35844634b5092035206e36563a9cca5da66221511b162e8be400bf66f9b6760c208029f57ca5a094c4c9b5af7308c0fdb877afd01f3569e27972d89e842b57ef4cd45772baf61bdfa082d64d0d8986db7647fe76432a3fc90b3a8c3ae45e338b4958629c74b41e54e476a4d0265ba32de0f329b5b1065f10590fcbb8528868c1a96fa705287ef1597f07fd9a41a96c8b6029cd5562cc70ba162479fc3399edf46f08112336268831ab45e6d72759dbeba0833b11d5d4a6aba426205339cecd66747e06914592ec9508ab45b7d06553a9ec4725a9c37ae3765f722e8f932923c4f53aef8a813cb3c5364899b95bc4189d5e884f3df21cdce30c727118413963d255fe53946d5c8d0aae543ba93b4998ac59afc769724ecc355f72cb608d2213cf231ad1d1e3155583497abc8b912fd5fd1a24c726ee343307427249d26b07d01217bcde3a5c0e83cc4731ec081c1d956fbb6d2e130bc01ce8056f599faac8db633275abada379c0a1a43b601ee0bed9fa701baf7e8dd40092d27259a0126c5d03eb2362ab8f2781fcab2602f3eaaf4b2a5fc61112b0cf56bbc06aca33f9b035ed937f9ef5d4c2591b364b4f99af1529b760e3b521161e6269fe35dcabc55f60d1395ad5f620ed2735102758f4a7ab2ff339d0d2db6118f2a324726907fe8e4bd37efec3b9c248967ac922de02dd261dd1d798245f6aeb89d1b272d6b4beebfbd7cf9626c857768fd1d248e7aa02d851bbb472cc19a2ba4a874071328ae56632a844e6ce6dcb64c2e5dacaa52ad672f156a9a89d0c1b8f55d2d7724e927a1fe278dd94ddd73f7286118b685cdf3b18a5ac2f3d2f9e1d1904fe74728ec885a6343cc56ab4c04dcfdd5a725cf0d1705b9d6cd6fba9dedd9ca90fe63125a5aeb90140ef69e91cdd0de288ccef09c887bb99e02936b471a60339b7e55f3de64ea43a1eaf916f8ae729d019f4b0c9b2aa60a9352ad5a1537de99e47ad7281b244033768cf164d5f7a62262e973460bae140bb7a8561f3309a81ad2cba72881ddee99f94a0ff4612ce82dff04c258eebeee94696abfd804dd016f4f36a698ded0dc898745a9497a33564801dcc92f2c282e041827f3820199a01acce7f0312984fc1f8e9975c81f5e36023028459ccd11da5265774d394896616e28ec972fa4dc259e611349a286bdb5e2cc7652dc77a925778f2b0530ecac180ac962e7274fa062bb2bdd07a2b37f1894fd14f09b8ed8d874ecc2fbff9c5f437fc09a07230b64e51475cf3e8657768992e4e8e01cf52ccd8983fb4d7106bdfeb2f588e724328dbcfe4bfde40440588e6865674b14a7681d558dcb7df937a8635369efb2d8d62d9e165fbcaaa133880955fb7bed4214d5478ff4c1830cafce7ac6a96af72961a5e662894c9007f67be07b302d5861422fe33df98821789f76129a4f03670d83115fa0c98fd2c64eef32c6aee032b8919268fef2e10dbe2bbfc27d8fa11609cf1e2be514445a22a8f22bcb3d7b3516b7d739939873ae348f8300a25131272a058cd33880dd943cba8c235f2003e05519bd97999ad6535169178bc508aa9721619b1d9dfbe19031ff61c7ea5f14554e70266b10191a8b96f98410516cff472ec53112182edd64be3dbc876f28737ca2054503ab57c6c1642c869fee22ea472f502b09442c135df4348398d3793f2815e5464fc8c6ed1fd954c1d53fd1c887291007c8ba8c0e0eef45b73a7a8a1098809e3a71acbcf9fb2ab37bcdbe2b0c21803dca57bd222725c119e18c6743d7f527e57f52c9103925423c7a566da0dfb3c513dd6d31a0b0248f4720dd7327b65cc45335bb68778b3795a830fa66aea272ba253ee20f5eac03b12f8021bcf1914fab7c2017441ede756f1fe044602be13726cac94995db6b8ce913da3de47d2a164e547feca70ddf31fb246403ef751b0729eb23fd1bd5e32ae6ac8cb0e1a8d44547f0c41851a2f2ad17d083cd5e3305a727f60a41e1922a19ad27ef85be470152005702a7c2d461129aa1958dae2f5f572702b4fb27a7fa69e0987e8334f97cdb3cfd1e5a2c81c50746adcd88e1e673172d6ff6aa362a3d8f358e08573bcd178b2f3c63fec0553f3e14d2b48efe676a32c34d9b7517b77f82e439aa6a1edc3e3e79aca0db8fa3525d6ef1ac415ac8d4572439cdd36bf6157c99b75726398f2c49b044d8cb91d38ad4bb54ff2e038c04a08869b4e88e96b04ab7b96c0cf28313c1027f1b685eb144e723cfe877b5cfd02727dd28f0a1c041e571e5e34c91e3c3961e9ee74983d22ad6046b7a18143706072799edeb2b8374668943bdabc188bc0d37eeaa94672be159310afabd55463c072bc492e417e2601df6bb3ec82e5a0e60fc6956bef5b215349c3fdbbc8fa141413b79c05c6ffc0a80729177efd20832b0c169df2292431f282318690aa0a1e4772060edc44b2b8e87e600c4ee6934c21bd4e4e77a00f53a5fbfb960a7567134072525969d9e8419b6002de2ecfb318082e798d7268022fbb039b9ff0dc142a693082a612816057b9327167a9a2d793277b2fcb6c21decd591433df4a494e1826553f62adf22a9c9487cdfad6de7683c98cd4d08f70ad16b95efebdec0cd85fa07296774d31be2590465aeb7942ecdf08347c01cc93ff9c9d20082a0c109330876fd7e78f814ca61e14ec8618640c80c6985eda8e024562c0178b6f0b2037eed972aeca9bf5b2b4d881d235f1713bddd0ed087dca9f716a0934800d068a97f03372a8fb77b5e67948c7d05f844f31d77fbc67be690d84e9d343e2d252de6e285b725a6d293317c16751eb7e3419570cac821be2a941fbc41c9b0ea7414cf86d8e1e67cd1add4cea527c6df5b2d1eeafc2f1a4b4cec71f87165decd7ad7f706cea72f31cda1832e14d0c1c0d804a30cc7b550ce96dbb2d5b15c77b0d32ffdd051272172b900768930e912a57dd148c0623e05b8d70a5489e366e90bb2be885ec6969bfb696aab7a172e687415cb1d9f49f48f5fa8dd30a1fb7312527130e389323508873be6ea72e3b6afcfcdf2d40d1879f348fa8e9ef0bda28f169b5add0f2df72c3c0446117ab037a787b370b60d531c04599339f5dbcddd11f0bacd3d6c4ad12d15278232e74e4ce440f832e8ae70d12055c20352c72b01a97803331f99479725a57874507a569580655f648eed60a884b55763ad5a4452167b017f8aede943c21c28a31377a614de11e2be12937fe1995d07f5adbc2772ab4e52d4dd547c572cf89b5778d80d0b87e2a11baadbfa6a9e2ddfa8fb260e68f9822ccbcd71d2c721c08bac33380c4a57cf9931b3b3c423b32e50eb9a31c698544ce1c23707887728bf05ae5337fa7346b8c995f45f84c041b03843a0bc092c439997768aa514172bca64dd3e3bd5300ca24fbc23bad45432a430a983a27978b74e6d8741dd0c172466a11e7f0bb3920059f46e847a51aac0f281c8e393c1e1539129d68124460493c240c4486cc34966a50df15d3803cf245fa817f7645b4dce42daf4130e2ea72157653f0882d9d2bf69b7f072a0a74812e5a39e7f75614e6c53761b9db521a5044cae2de7579a58640fc776da0538e667a69e640458f7e08549c5f6eba1fa45cae86acc3420f16e490020b714700c83fde13e4ebe9b2f1879b0e6450ab461e72a709f00e0c16ea5685ff23dd42f993e4bde8fc5ced8fa78440ba07c493caa672f9b6341f4511421ba813a79f16996ae25def1b840886ac9f94d856a9b4c73372910b507f57ab1d8b43d4a56a703b1a4b7550288b248aa38bc488fe58f7a8a372b89c0676e124f28be8f62866f3c51534a4cc72be819cf42ad835321830b53a354105e1e66463f4c46359b77c633620041b859e227a52ab2364f2e9046c0eb67254f2ad8dafafc7cfc17969410402b9b6d6e50589d6f9132fc7e7d70c0b7c5912d74703a52ede5d3b113cf42cae0afbe89401981b56781cf4106e4397aff6ca21cc77e99f35191acd31c585c8310b5c1c93a684d22faf5ce85070699963e87272767aee20409c1effc8197dbfdaa53a3869df38671588d28e8327763f22faf57221198040797e8a0ab359515f2b53be516dab08955fab7221f2a338a38199aa72fab1598b700cdc1f6c27f6d42ac84dca37e5062e2cc91a9af089e2f93a40cc72a8985246698596be9d1922f92c3e615c1719164ab4157e8920014dd66a973737f025b72265df110fdebf10143792da579e6e347cf1f486778a9c2502d8db0b7291798227013453e4c6c26dce2a0ed068df06e9e78c5a1bfda19b37c97bdb4e092ad4b513ce195e46397c62058b9b60376a50427430a1e4c366421c460cfd7d04fd2ec79bf7dc00ae4ff20b01c2bdd379dbfd5a74bc0574b826c73ecb5799eb72346641dc210cebc622c54b6da00e7aecbd1c85397a0043744a28497dd2e25b722a94d6371ddd19f7be3aa22ac1eaf17aab525247ef946695a36a43ce57ebf7727af3d3eb6d0e0d460186417e392fe6c86ca05e6d0ea5dca49efb15f6181454228ddfa557e049b488945d039fc014991757f10c7c10fa1168b07b5f570375f52b2740a96143bcd9cb854750b0a2a6ecd5e7277d04cebc7412a851349787e7da72f056b94657742103c46a9c43dca8f0dd4e4abb69c1cbbf1755dbc9a1e612d34b8bbbcbb5ea8f59e32dda9a99af45a9cb5de7ac376942923babb7fa15901e5a1d1c9b5091098d6c5fac7ac24dcf85f6211e1b9672838268c9c966a259708534727941d90a199a25d6b7e1a5833274c68a34dc5aea1ee3870dbe7f8cc0d562f4721a372c42b3ee062e066a61033eaa7e62c6ce8b2fe78e5210aa9b93256fe68d7297165e7d34c5a3489cc532946ff3d55131bf68c83294e9e25ecaa754ff9f2c72fc94aa85859bd11d6da7d2f947a33d73074a7c65ee18fd6eb7337ee1ff44446026e2b779b326da2a653c7130dcafe80e1e60718fee7d4179ed80d55e628776727508650109474355b613210285484bce6d1028aa7271d427bfb00827b58fc23177be50530eed25155f78b0f1c6b5e42b08c9f6366fefc7dbac69edb7fe1be328e1c6920f59c6b859d01921412518381d62c06fd4fb9e8244b210105939075b72dd7e3830c46db3bb24a7f162f5a0e38f132e251184a795de4b717e560087e949cddb761141374315be73a4334a7ed66eca760637d555da80b86b0543c69f3a10abae6f44b7dc61867e65769f46f1586d1350948b0bb19f52a519f17363fae6308e943ef07dd2f5b6d98cbb574e28a4b4e6694f1210cc1c45fe87cdda0662cc72c39f81f7b6aff216b1152f7652211081bc62e40c4c0c5f20c5222a198b9d091bd6d34162dd371f65e4be9668fd9fa1bfcb39085f0dbe581542ed9670c127a41013622cd46f46af5bfbb4d4be9a046888650fa320496564b71d4b94e40a6ef40183e8ee2b9487d5bddc634d42c63ac5d490c58e46de23ffd7dd163d37474cf172b032d616e6c821fe87f273fb5ed032df713576b42efa9a6b188d8783096d15101b5c26b9030c04c57f22978c8f5692c120ce33aec462ce19205e90ef9e5557728015ed1885091ad5528279cdec3f89e0214cf06092816a7762ff3c73a3475572003cfb734fb5cfaf1ad655121b51cb6953d649c13ade8f186c8b58eda5c4f772c17330e0c85c715feff7b47fdee77be7242e96375f9280bbe35e20f66b50941259d470563ca41493d2f7c5999b53eb623d4f024f8dd82a93288133d94cd486720f5b41c0654bdae1b4c39ccf0e1585c8cd1d33e12baa8b434f61a8889cdc0b36cc36df8c5222597999d10a15cbcea8902ed75a893ac0609163c18cc9e77575351baa9e806bc77a6b1674f56cff473861ea0518b0e4e7b4d4c953bcfcbf1ea9720c9d146614c1cba709061d3dbf0c5dce80d595dd4df52dbe098fecd6d0b27266fced2294a5b9cc8dad82f8bda13ba6ead55027acd527edc6eceb1ea8ca9cd4720ac32c3cb9715a870dc80ebfbc62afae5ece73467011bc82d79e93a2c6b52972adea69adb41df1c5e87af76a89d074f10b92e975768c5cd88e4d0ddb527609722a14fb029a3c550d7c291ff7ce1b9b27f859666fb9582529191bd3b3c3078072ce955170524706491798344cef838216f355a7262585c16e21671e2891aa1e72a9d9b0700163b52df30b5be4f50b0d8d88e470364d24a0a0f3f6b7f9cf157f729077a76c6a3022b2710b242165971dff740498e29f79b2b52bee1753ee2deb1f6a0c181c307145b38dced4c3ce3e3f53856b1e1e27f992b8d2024255cb694772e8957e82c36029e019fa75dee89567c3e046d50185be4388938a4fdb6ac256720bb1b4b27776861fa4af94b8c43fa4aa352d4c35b3252a88b39a166050ba57724b211682e5d52288192aae77bef9b92e81fc15ee81442586c32cf3d8fa532772d1f61ce6b95791e664b84ac38145e82fc6aa715fd08e24182798e79b49bfb91db7e7ef69beca1f5d4559a05cc910e53e928c7e6d9adfbfb425b9011bdd5efc72ab5ac7343491e59ae20ef816198ecfdad5f64be95b4ccfd701deecc75e6b9334d6fabda9444ce6efe95f1ed24efadc5a6d3feae4e715385c49c52bdb2189f4720c39b63a6a455fe3b7cfd7eb907c2679bcfc4a8225aa2599fdc9c6e7e0f96872cccc0f7a95bf43b7badc209e61404209571b3ccb2b6025799a7d6f528e23bb27677be0ccf3672e3138395b2662facde49eb854d36806fc1f01b5491ddfa6c252f4ed5f4659cf0456e2bbc727a1261bd7e03462b2b65931d5d9d7bb5d4ce5f07234ad7712754fcd7ec322d1564b108b00663b10601b7bdd775594370160351e72324de39bdb66f06f07b3250430e764d0663bcab00a5c64b73a71910f2b3e5357a0e87eb932c8f0effb82fb742bbafabc58915999f848753a3b8ef8d6e611b672d3295e65000dc54823dacd391b6e774bf976d88d97960f4e9f314f8f63274e72db70f2dd01ecd06349f74efe2ff45f1eb5b17fceba14f8a1fe544d9dd64c4a0b303c82c0775366db03c49d9257b68879f3894a7c431d437da9b13f046a8832477d00cf4dcfaf9119c3f20304d0b807ce90a388c1e704c05dd26a0655078cf272a609da1e0e0a77a42e2e80abe791299507a1fa663c4d5509abccb96e2f13b07253ded5a1c1e32f3bfa98a7844e97911ab8104cc430209bfd0a71c5e63348ff2fc1903e72a782de45b7ec74f6ca938d8ac7be4ddbaa8e3baf842c08f09c0c72113deaaacd7fa80a15145412fbcce816af5aee2f16c3b5e8b74a250a3a48d72b727112b959ce9a193ab7a23b168df138bf555e69b1b88d574e4f7ec7776dfe2672d852f81dc7097d429757702023e407576c42356727fb3a1281efa5f59673f87243d84385edf4022453ae3d452e7534798973085f28c8f5f82655eeb867ded4723544a9dce4d5868d0398ee776258b6ca4e0e61683d47beb53a923a9e75bc40723ae515b786661a199d97634c44857d21a997e6d65d9c600a0c374d30dbff82720d5bb33d2b011ef438644ffcc0dd04a6e8df5df451d0e3f4192addfb2767c57252a9bc7bbd6cdc24caa0fe10181bd5752aa4bf87f433c28f5a38a3a856d7ae72be72cc568463f379edc8ba1780ffdd167191234a9875072d4c65a10e7e474f72bb078a9bcd0207217f3678b7c4a6756bed63a6d9c2a9405cd73159b99093f70f915fc7fd6a9935af0947ea94c9f72a7777f042a0f85f0492da13912c31619022066fc3d8e0343fe5c18b1be357bd7348dfd3c17c18301caad93b46ba50d79e72799631f7c6b7cfddf38f4e1ad502bdc3b62ffeda8e99e5c79d9328ef9670a437efba06c12c83d29ce39bbe6841272a9621915971d6b90472fddbc4c5f93b5e727f4cba72383af49e202bdae8d08b1c9320b146fbd4675059a050358299508d55a19e98ecbeb7a75e387d70c0a157655b555d0fbb6f26329a8162adbce0c5b83115b5b174ad63e9ad68a7469baf076ad4ebab0f33ef31a67e1981fae77059c92c8b16dfd0024bf389112cfab68c618846d1f274b76a690424389e382c54051742a5b989baae7d8e3713db159caa3ecf99f36413d97db57033847401d6221ec5723d2477cc84e61904a727dac24b39caaa876e4bb83468a27815c85dfe8742357213cdc1a92a2729e10a697bc6e287f6ed0d2fa772d0561262262c2bbc1529ae4cf4b3e92692a80904283409cb267c6d0f73e231c88cdebce577979d208c85b47255b6ae4e26808958838736a8fec4d7c5ede76536f3c8b26c98409b715b85653a3cb844b492e781703f5b2dc48edb1836b7858ab9a60a8ae35139bc6de45963326c1273d91043cf3fbdd105f336ebfc3689a23acb36d05d2d631b156071e05172fca4109fec8b6b02458245445601c20f01d2cc6f553ef65ba9428335eef03615e9510bb0369eb84365f718c5667b80fa924131c6d53d2ff09f10a07be52076722407d681a78fb00a029bed455fe5d4704b98405d8b05f44ca6fc9d8fb26a020abf19cc5173f4578071814a5eb29521f5f3411350848574751bc57e37e75b871556c2f9d4a54e58409f15fc64e8ea35e9ae4cf3e83e100b3384c429477b63f14bcd80164f5b5099b4acb79859cff721073d05f4194f1f101686eae5a5fcf5de72a1786b85e2811cd7e6c91bd2ec895a544b4d58dc951875448e696d486f7af759431eb51156a50ff769d7a84ace6f3668076e8d532e4ee801bc6dafbd6ff615725b64595b05496d111e2ea78ffbceb48bdcd3251ccbd600ac72a29069831c5b726d2469b7f95e4942722a99d3c9d51bc58b151a1ca58fc67230d1cbca21014853f63a50afe440e4682f4792655b22bd189347e19f700fe1154cbfd07f6553f57288bb04d902be6d02ad86cdc120426be1eca424e046332b7128eda669b9ce3061bf2f2397f3da03a3b2bb8ad063a88a72ce531237436992a0682f13cc3e77c872e8f5eccf30438214eef2711849d759ef0c90c51757dac4eea3a5d739d282ba72f1986038246aa2828ce29d4c36742e12805508f30de6ccdfcef3fad40c16f5725747b254b3306589a88e2dc70deb80797415ff81efdf2be27be7094a7fd644721b3a491ed3398f78ed4efaefbbecbc4af0484a70a53f956d65d2bd06ee78a82e9ee8e121fd1986d828387ff6c0d9c60605d017407d9b8d50c033c75cd3ae2a72175a1a88bd9ac917f0dcfe06f39051386312eb32b4784110f0dec429c0973b6e29d0d4895d648aee63bd95716c6fc78950ec1ca30305f6f6265f6daf08a2d472cf16b1923f8de0f1dc53b73bbc688c4d5cfd4ae4064bcdd26be5cb0b375fc0723250d73fcfce34bb58cb2245d069649bd2ca40fa114e4cd7fe45f84c329c022e6adefdc008e5ad275817f5f2d6880bf5642d828056778f8510cf94f558db563c81974e53e83dd5be6107773aa606021c18da3bc41c66c8ca85ee1533ca8dba3fd8588fed299ebb234fe28053201a3e00b65aef9b4edc81b6897108fa0d77c440fa8b3a2eff6d3e4f932c7de2be860824fe80106cf711e842af5e2a822d4a6472e2d86c3f45c56afd928e88affc1a1526c64dd904d3ac7b3aa7b7197e816a8b72eadc8ababdb676b15adf48009add070f6d840ae909822af1d62d2a96ce91cb5bf158f0a759ce57e92820a7079352ec1e62288a62f9e267e538f7066102185e720d9cafd38e9e33fce34a015edf6105a79895f3d772045c48bc20b636e2698872f789f180742feb659bf6174d417f435d00b6927e2329e8c4731b5cf439d3d044a009db4d33847d57022d332b00206a2d19d77e4f98626b0a0be3e8df44a9ca727edcf5c800c5fdfd0e96226ff052b2656e8e8a4efe0b549da6133d945063fa48192f2ce86a3c5b197395f13a8376e5343b5f1f752af14056a0ab864d32b09c7250bd047b267a2c0b399bf0843d9149464b5f7b37be185885c990b82ddfe5537245bc3de6d71e42fd8ca9c4ef98fb53109a2845081ba3f8a9b7d4af002352e00e577b755e1ffed0f4268a59d57169a3d2ef79bac0db923076310ccaa4f25d9e72e051dd06ebb0a83fc69636330e2e549bb128d94fd4c7c141fcffb4862b6ef072bb639cdfec1e000c3ef610e7af1ac3cd699cb5545344c84d27de9b786b83ac7260df1449268dfc8b18c50bd76008828c1600fd5dad992b6f0b48586cfc55695674b5bc166f061220560ba42d1b703aed72435616d6b0f02a702cb38b12db58722fc8b2866c1c41cbf37076db9005ff20d9b65fc8578394c2145881050a616d72635dc80a2a54e25664095511d980128adfe572f1a42af671240774e59c9f16724585aebcac38870b39952e6f820cb998dc0cd38c9536018ab049047dde152772579fc104ac76594596de83174c478145dc897a7723930f349892254ff18d7772333d1c0c61b929b02bf3df0f5d7bf36d500341b45d6ea62353560c9933449172c3022f8145464433d3a9e0d2f548063ef55fa30b8ab40b89c0704f1a599bc20861f43107936048f0ac8cedc31e6e83562ecdf2a49f5d1798df0015300161583b3a7287cbcad29ab3cd0375d197158fa2d8de82f0edba227a9020e396f7d12c2df13139d1cf71c699477ae2b1977c5a5bfdd8cee067ede688b393acf2cdb93e72b7d4b7ac836017c7cf2b7b8457c6d51c2c443f0fa55c358146ad548609c7af724fff1741f1af16a5e502709da29bfc54212105201ed58d5c51bb2a3ca65eea48986fe7884c0115a229e6ac97ca9480958aa685d4d0a5bb9c85132fbf39629272c09e80941bc65ba171d6c1044f38c99b9d00e84039f1234854ceeec9c6eaa4723b70e188292f825e707211fb04240de9e9fc8234978f5a0dc96bd66f77bf7172dfedae5fc79d4fe28108c7451969c28b1b4f35897faa34ee5dbd2e21df505572c04215e58a1bf08da33297fb67ed97a06213ca8ee542f87a1564aa73343c990fd91381142367680a02bc389fbc98ff3ad747238d8f15619b28feae73bcadd465954a18fda788792659c4aa81602398b0bd824f0716cb99bc56e49662a19fc572298b3eb103152a95a451f6e29fc829259d3a094a011e9db0c9ce9e9a37c54272ae90e29ca6825f8a2c53aebc990575ca758ae154cd046673d07086c2842eec6f1fc6245a8013595be296e8afccc7a6a44e90b172904e990f47583c7d5a15f47273eb2278560c325c09a98032ea824dfa4f0e4a94340c85ad44813e583fe2f4721869b0657d5faf990308e9d397cfb2c1a3013bc7de006283f3401d43187f0c59293410b5e1dcdf52e0b4247a93551cf0863b9306e748f0d8ffcd1dc4440a4172f6f65df0ad1ea05b1753aa37d5d4583e6b98d64977eabea014a1b2c5b9005a22f5a4814936efbb4cc873cd15254ab6fc70d26e148ccc522de7d3d4dcd3869e33c70c03dd79e10d7fb2b7424abab0f76627c7706baa6c789f40d45d118f520756ee5f90b53c17b59f5a96243527074d7d0803d23876ddb62ddf47dd7efa067572cfeb7bdcf40ee9e96220ca89d1a54be89f7f173130fa13f43a9c0baddda6df724d257ecb262e29cb818b6b283aa8c1470ebcec5b8c435a9d80ecddb2c5454672171ea6193755d2d0980a9a50139d3acbbb32bac85721f71b69aa04d97a4c4519ab4cd4f3453788e0876291bc54b921be02b179ebb968516c46dca77f01760e575e841e2542bf63ab63b64fdcdc57f02f452198b54b995e4596f2cfe1f6ef977269786fc4c4d63a639bde4c8930d4b8e9b00c938da4c4a1d5bfa9a790ca15da7227e12f5f34ee1e7b75ee7390756e00d4592ab1367e0465b1197dc6bae1f6ce7229d303c7dda3732e9aad24cbf61d3f0b86dcaa69c3e5c737316566e2261df0722162df823e41bc2e2e47ce8dd3658273968dbc2261df5c6fe2431599235c4d0aa208b94d6f4f6b1871e93f0f949382b880ca138d9621fef33f0edb84757d78067505375e859c917ded85512eddb42341a9ef3bee33b6708de64c5f6351ac9144e234a33a260056452624b3992e6562a16c1d447c95299df40ca95f1610f9e972b871e24a282d9602e99addaa720675cbd8371ea2c2da86d896d88b6ec747b672fab3c9bc670febe0c38fc8614f48b413155d5258f29cf962f240ced47fa184649c18ec5af7a221b18cb348a5a8384b841dbb5e894792f36c8c31dc0927354872a0c5cbb1872292844b0534aaee4cafac9466aec86c76203bb472ba995236223c1229eee75addcba981341018360e98e6dd5a3ea80e4443f08742ae82cc42ff72f823b5cadc3b1446926a72439ce3ecdf8386ca08bdf4885c2d746f99e5ddc672f3b679f9752c04498fcdf56966377c11cbf41c4de7ba2680455878fa336fde12dceca577503098f2e41b56932d3e03c4d6d6de1393426b529c9b0585fe3ce672bd6beed0244ea831862fe06150be131e8388e6e5a5240b8269131498f0025172984fea7075c3dc4c81337d758e268ef796b8809dc80d4fed6bc5158b39585242f38b3eaf5fd79797789628001ebe714d6c926195bbc1a96d239bd1b210df0572e80ff0907541341b226614455ae8b4cbcafafae222f763e855bab46b93634f72d0e5e791cd4fa258df0b3e762f9e43074377dbbed4893fb772dac2eccfe34e7219e999171978a36d0f3748194d19399c6724bf2b590569e4fc424d0b739c487287a3294ed2dc404e922ee939868efcddbce22f3aa8f0c1d60e79055ce1d1f272cc0ced49efb5c04ffc1ced7f9507995187487e1ebd2ba22ad9cad0a835725e3b0de44c973eec6578a05bf7544855ebf446fb32fe27d70ec665a6384bf86fd172a05b4b548d96fe7db183221b472f1e558e1a6fecf04a02a02e8998399db03762b9b41b4bdddae4bfa22acf8714b9cb6b9477761e03039ed04c8a55b9ff591533251c7ea8b2041c4d5890122587d4974daef0cfc693742fa7c0e55e4da0fee472299f8d4fb6679fcb3c9372fd24d25e71b8f00a147a330970bfc84a57759492724965d25d68a9277da59f0a7c45c139a9ec844cbc83e1ffba99de7957c468d072f76e70c38a6ac97ba60a05c3130eb82d653617480071f54feaa54ca2cb50b90490cc64b15244c7d593920c88eb8765ae38b5794adf7c2c081a93e03a012afb729e51057888c61ed6afa843f0035bd50490e784e11890ae8bb4aa09b1a890767258ff822bfe6d76d589c157c34b32597940db8a64e1b37722c3813320684dd24b102c442a3b9fbc7adcd4ea582ff21dfb61ae8e57a847fd1ab5ea71e57f291672ece31fd3b844cc4a06f172ccdf7736440c78764fc954357e4c98af34be4e2a3905ede5eff49468080df7423eaa931d969a64a072aca321e8c49da0b2f1af417208260180b1bc93bbae6197894823736af63f67cc51d1d4ea6afad69d0dcb587266bbe7c791aa6e894c93a4b77356cca44dc33b139b9deb55ed460ff135e17a3d07a4c154b962e0906adfcf4ea5870bd650457a90b07e0f227de5c887e9164072a627774678de1c14d156ae79252dc188d4aa98ed9be86b33e36e5697ab5abc18c13b3d3ca4aa232935f7769ab1f08db1ed319223da8d930d59415c02a7356f08993cd9d38da1943b91a790df7c67da4f8c5ac7d2e9f51d623aa802128a73537291d23b5183bc05a43af22ed4d0ad1ac7d51c58040f227019b1e821c4135e2972c6fb2c156265cbb75162ac237af4dbcd75a0f7526e0f7a440ec44bebba55907232dcf1432d49af3ad1c24f7516bdc3c92b9d4ffb4cd2558722c63cf18c08e7726ba7d121b3f4bbe484c933d4f66726d56005ee3ed9702b7bb37217635e2982724dfb2a7a4848de3a496b4924326f35032a40e4b536a1191021e6b044ced85c1923a3da1772493afa31021e50c7b8b04da7b9f694e979222d8aca76b916e81a48babbbe285ad778b555db60fb8c07edbdf48e8592defbe4a8250494a7f67bc172710e88cf7d5399af976bc2cc64825028dffe1f5890e22158cbc65e91d472977280ab60bbaa0248209518f1442acb63f8954d0decc0033b93e8a90954ef7cd62e8c502a8b675e54902c4ccb8a6e5c481c43a7c07e34128b13169cf60674f067723fb345bc44cc6bca4889def8d38f53d832a7a3abc48198cbfff4a8b2e42a8872d08dee5955e384439b22476083cade926039ede27f8e5155e4cb78557df79d639047f7d91e31add0050bd3b5ac01b9d3dc59c1867ee205f9b6fbe4dd748caa33be15d27e4fd683fefb07933a0da9cf5a69aeb605d545dfe141f1fdb85f354e72cef3964ba63e18a701cbbb6f25f480adc3290535487f3616a3cef9095626292399654e49b136af49f70d96bd4bb3b9335d1cb1d0a0a1eda036a34254f703ee33d7babe08e1bdda8aeaf024a1262a3b0b661d72c1f50b0e708e87c89eebcf3f72a46ce7b433c864bbf4a58f191fa3679353c0ea22c5d317e2d57dbc17a15bfa72c64263288a380ac95b49ca938ce198a6efff984cd65ade50f24c2f3d97e0a672cafacc35d84cfc78eb56c2309e7396bb50abd497877f81928be0771961a5b70308f3b6906185d4ac6f58981c25e1b74073810b3fb4ffc0549d157df37470c22541953a55476ac199bf698aea097e86f688fb8efc2f3f7294354c185ff83559726ba7d5e2f36c6db671055e66d97b4ecc8a8184ec63f3781de830d0889ab5eb72515a124eeddbf943a9ba823ab863f915e6567534b903bbdad7e4111193eb8972e3d7852956a7d7c7c2708ab6eb859fad4b535ecb53a47d88a769090cda0b235431369b2adf6aacf96e8f25df78c661035b37494737a8c1e0b53be75645a88172e66e278b79544204b55fac95270432f91d1ba53e27a98e6dd0b88acc8fcfba3e4458694e558b260f6623b924d762be2accc0c2630683e2272ed494a910f5c172f786966767e7cec1eef73f2eb0a0a641b24093eec9e1d9ac76d49ab55e61533c0a5d3f4986fc0f8200a3498738d7044ba3ff69f56076cbfa9fbe40fec95cd972fc9d910670b28ac984a000e579eb2c099b87b15cb60d986a5def3cf3442e8332793fae88e761f541d066443f758eb51af696ca4b950a854a12df391453a83723d498fa04fdebbdfa609b1b87ad5d49cfa246efecf74ffc05a32c54c0e323b763f914bbdaada91d1dc359041bece1022bf71ab33a56142bec0bc45ff21587140f7535aab2642089c7501adf92dec4c7651b646e855093a68d8c46ad019e23ea7206a145bc9fdb6dd709f8e89eba17527db822c5660ce4cb86a7ecfbcc0fec5672a389768d76471f5715293b8b261ed6ca07f047b45ee811a0175461c7b3962172886ad85f9c7ae7b5f0b620bdc4695999e986a436ec54d8faf2ac83994f8210688a544cf92e22583f67d7d919a842023249ec46ffcc0d730be64a7bc0dd60186cb28d51076b61f039aaa7f23c798e6b33fa4f0fdba39f1d695b4881ecdb9f415a76662b7225123faa4dd0a1b53083880e86539b984f1bfda0c8b5c56931b7c672186cc714fcaf6ca70f4e056db6f0eaa020fdc446c2f5a41cc5ef5223791f466714bc27f4431f5c038dd2f619ccf40b85e0d0b5b25c20d2bf19fa91b2962eb8726f07be047e8ef7b959f007d7dd1328e59bf987dd7d40b344b029a7f2dbf81930150df0f7e6a225ca25d7668d930f975824794623dd0148dc0936815f0cab5f2f8708bbcbb0e70511ab91d6e885466f398922b7004fa3caf7afd1db0dd1999a3588ffb2c10af1dff0a7440176cebc153307344e6d20fdde07e85a38c8c9e5f96045472ddcefa713d7f8998324188756c5797fdd0dcf325b9b0aa0caaf96633e1866c8983149fd0092b3c579d35177519e38fa21bebc04768cce2b4118d4b03c7274252edd82c44fac72e7eb8c891b676b2c6a151feed546f710df2b7364a99303e3341421a3e1a4efa8ecefb55078b2fc45b3e5f7af2629efdb9bc4160a1a8c462fd67870353f59fa9e243efaf19fbf291f8e7049635f7e602d619f8bb358e4508f57bbdcd2d9e0b1197dbaccb4035b4999be67340bf919504d9bbdd22f0fcc0d8382c88982726f710940ffd7c6a6ab9815c89b11ea6149ddc3fed3caf6b0467241ff11ce5dc3e966d9bf8c8589686a8434dd1dd4042de4c626dc5041ff372a72d2acea326a7c0cabc1254e990008854292a28ca00416bd8456bc416e5940db044c8b247b149f3287b09f4eb149bc6063ddc9220ff64ec35fd94b72864383a47263b51dc6c768565f1c24103982b873fc2c0ec25870225dd7168ed34bc3e32e72189aa4b72570609b3aeaf4182e1643a0ec7f93eb5e6c84c8cde6d445c070291dd7539125af1565330ba43c471ae04f2daedc659d75075a4fbda3cd3394779b72e970773a61c0098a10a8e0c7111c2ab839a7491b9c4730d7217cb2aeb37bf22951e5954d7d766df8280c7579bfa5dead9fce99d0678e315b91e8eb7b7ce8d9720f34b4589352e37f5b422c1d6468b61201c2155e1f7a772ab13da11e65518158a0bb1a21b207672b70c3df87ad581c1899d57c71bdf45318a83a7ba3f35a547219b069ef77419e0d40cce65d94f843210407d5cb6b703ad1d98de6f64765e0726f7e8c0a21a2c92f258b75e9ed562696f20e0c96604a22b23e3e528795da81728f5d01818d5fe7c8dd573c8988b2287307a0c9fc01a57a451f7f388653bbb504988385d37fbc2d7bb970b61402064de19145a1dc256aa18406aa9865e514c77242fc9892a03e38ef89c3c3d1929533cd2cd9fadf3d8d5067a2e498c86103263cca54915778258380468e07efc546072747ce400163c50029197fa04b6ffef818129cbda1dbc05a1754683dfba0ffef3b13a95c849523b10a267c768eff353d65393ad7c92a41340357143329c67a6b5e33babbfe569c73ee110051fa1c063772f72d6b652079048fc494be4266c5cc0dfa47db3113718eecc2b3b80851ec0d0bcd0a630944ba47c199035148364b3d27ed304999f430e111e5521f06e022917224e88311e8759cae2626af7785d1c803fe7254d0ca33496b9d74a0ebf856a272524b51d50eb986da8f1e50cd42f09d83cfa7f1f6c6df50b9f089ae7f669ed17205cac6b40bcb165947b7d040ed21cf3c673a0e28cc82b8b57b35053c79f58660d19b271ba45ce0ee4e473355bd3b289829796c3803de72220e26daf7b9a0a9729dbb15bebb10d9354c0af3b0f27cd8c0587459f312b190b39583b2ff75444a26097e4add79e66ed21a698fea8ab359207a002a8b4276a74e55be802ead02c172a61f8b216de0c64243742478d900d57bf680773b26599322fc53e13f3bd14f72f54d1c088c8b7e440fe5b4452ac7e0e8211d0165f9a4747338b0abebc411db72ceac4435b3fa64ed3777acbd1a1c5eb98c908083a86014faf2d0d012214beb725fa25bfd5994da4233d50bf1e8488199edcb6f4e21cc510e4a4793ef5072a77283e1f97dbf50c9e19edde323266153d33bf771cf3c8ddc8bea39ef648884d8728caf46a687441b1e93d77f2827f4f4443b6a6d7226a7d53a2939f465588dc17239a002d60fec8874c7d2fcd69b62bb95aae4610aa9895422d8d6a2c1ad1d060a6f59d48c75c13f6bb045a897102652444f7a4db9b1830dfedc8e5fcdf655852263435898e705b2446c831aed1fc5cbba10a089d0a5d39817e12d0c3041298872a712f3cb08c3edf85731409c5559ed99272bdfcff6abff17ed8e0729c39e4472ef5fbfa3171ccb425ccf24a6cd729a71be093bc5d3b23e9a443c6b734b9e9a72e5b46467f5bbc49302fd277c337d2a65c169477205444c8260d03386ba7c487253a95d75b3cd41d0b1d34ca4644148240dcd973983597d3060e4c587b7f83d1dfc020704348460c3902757252556f397036d36396c4c60a001b27134fa3555724150b7c3086c7f15a0558e635a3b4069cbbab8c19894a4f6547bde6270555434a2433cc2ffacb3a6ddef26d5a1e08ca2d10d21ca97391b7ebaff1e9144b4bb72ca3194b4e36024746b689a78cf974ab9f9bd76b36ab81be40c72b8310274ed72c8b9ae880d0190e1f2b06b2b7917ec3956badb155f51701a4db39a25f2cdfd22cda35c94b8535ad3fb63677a73063df87a2b6a9174586beb8e826f1a8a0a37724cd1a8e9a250e279160464f61bae6ba07541f84636ba136e4c87d67d4b88be1ded38b639725ac069bc4984ce1ba0bdabcef0fb773e8d8fbd47081f791470c9726ad151ecbf8f121a6fee7421b8b9b4f0b24eda1ad204b2b3556f46aeded72072da6b2d5a06778fce0782bfd16e5ffb723df61f8e2decbddaae4b5055109ef0344e9d3d9e42f0c1869e73bca4010ba4e80fa3a3290a2bf355c3a56283e2825372b9cd34777fda27cda43c07fab23965af91adb0d89ea450b38119f0e6a2affb72b3ff37ff1bebf6c64add9a32f3f5da0b4e483f15133822afe46b995d1912d072acebe10bfb25ea527f78c4c0e5110a1d01b2941b6dab595c3a5445054cd9200c6f2f22722bfeb613a23ab0db3d3d5be2caab453695eaaa48b06f5cc23d85dd722d44d350cc91a44aa947de25ab921cc69ba05b60d8ba15dcd829df08c02f1d72d3d797d1e6b7ddea63e889db4464744ec703fe988d6e207494f3b1b4c9a63d2acc1b1e039cdab3aac9613378bf057f74a9e7cff6015aedc5201a9f2818e87072fdf15b588dfb910e847b18068e2650b53fb05ea5850b826f1ab4e0959d9d3a7276de1b04613499dd6318b06d5b202fbe5ee13578fdd1afeb03dafa759f950d72b2037846aec57860851143f3f3cfb13ab8ba42ea2d8870a18b09734a8a80554f8a359ad87d9bd322ffd481243e2c3c36f08a33c1eb54b295699cd0fa8f59cd2d741d2c53a7c56482758cfceefbc04fa08b6c3ed139be2d5b8752a3a464a2250aae5cd542feb18933ef1b9c31f75c2dea05089bbbfcff55f0d2fb21f57cf5e272bbda7cd16646bcd94f3970a0599fbb0827b030b92159dee4c16ab10d448ed572edf700802986ff1ca53bf2697d0b574a588cb1467bffc338754896f545b8d95be72f48de962c08e79c05e820315df6e3de3a0bb91fce2f343bddcfcb9e408112a3b2e9e94c9f49acb789f8e2fd720811f3a6a0a53b8a70f0a7efb799b95fc07240dbfde7fe26f6daf50f1027271e196a6caf8339f992a30de477c4d6605dd0729eb2cde7866eaa088fc61691dd11f240dbd1d45746c391f2d2b59ba916a94972da26d3020decfe20b68bb45e3d191900fbcfbbed09974bdbae5754fa77537b72e2daa1b035189262ea11a0e77198c86b89e43bfafea625c686994ff311ff89729584f57645012b73c9bdb531c7166b8ca287ff591545a4f4868bffe5b227727208ee8b7c56b52d30dc5a78d677eed760110c4c2653a8ecee2e3a2e822998577277d7ab3bfb3a43ac8eace7f2a5dc2ec73902cc3c58b4de20b2e364cbe335bc6b4e76518b53c0786b6613c04467c680d50cf5a7973fb0f09d9dce3abb26ce647292b86e7c14606d4fbc81b9c4e9bc54d55d169dc5786ee6e746670add6e7840723125e1ce8826589768051acaac0f57ab14e5bbc4dd2f53379267572030b59272045861c9ab8dc4dec020c74e972811df18076e7890ad5db2acfc8867fb5c911c3a1ae17d35165098f81dc42381f17d1c3bc7d2fe39676135a0b286d1a61d91727056021db97cc468714b3edea9c850b1c2ed8a57a4c1193207bbe05c42fbc672f09741a4e4f8fe3948cd96e7d2b80a4161448cc6e5f3a61e851378b187566c0028b659861006b91793b9fefd0f62d658936b7eeb3d91eb72a3a039f526e2a672a3e2df9a3811ed8457d8a00cdbf928d385687a9c839c71bb55e16c83c3e24772e9c00f243955ef1161522a12a0809d9ce325bb07ba4bdda3f9b88e45214ce45ab8a765e5e5c5c03871340e3706914cda5643d91760d0807bd10e9283981322728304bb94725e272d728618d54f5d589f0b0a403cf6fd1e5bb1d67b0ac33ed2720ad2a30a0a45070ab2fc76aa26ee3c8e9d595ed506407458d135e57f4dcd1040eb92917f49260dcdac896131aee5d3f6fcdb4f42d47b56c47317157325586372b4a7acc98068dd1444e384ba3190974be8735ed68114eb49ac6c6c80381669102c47b464d99e6130d9a5c22716423159fa827d42879a3397a6b636be359ccf3fa767216e264b7a9eddeae93d2b021c6b395f7e708feb12cf5f45c226555cb672d0ecec200d0100830842b1e9874df622d85ba9893a2f84d63faa2e770b8c7872d90f03dfabadd00582c70b5862510187372ff7a5a9055e8ddcaa300b81c6c172c0dd55fbde27c00423282d31cebb6347b7291c8c909b770901a2c9442dbd8372ecf16f03305b66d53f76fe0e90cb63edde396aa8a9e0581b5d9b8ba34303c872de935f4a57b3612a9b24bd1e302f495449d0e4b3ce79a85db8b473e17355ca72b2d3f75e91f4f7d5178e66109cee44988bcdbccb122b5e58ea2e064327ec4b3c199369ba16de83979f5f8167319f0f0a322f59811e7d9cb8429ed2cb931a9f722e514f4c1b88ee738873d636510bcbfdc56b852aba094640e498e7cf6e4d554d597bc0674a58378ba5873b7c733677ddd940e6b97e4e03aea5ed5d3acf01ab723c2c44a7a38f6d361d32b2d0e358b94d2bcc93632211ebce131737b71f461c72b87570732daca69d4216334a2744848880eaa9ecc4af2de3639bf8f6707fc17277cb44b36a1f407d2753a919a8aaf35b8efa59480ecb494d492798af76801b72ae8127bec5851977a5caf275e4474be90b1c994c4fff4fbf402db0afab3599729c47da698306d94c3d39a89f9beb8eaa78482d036e1f453e5bd5066038507270c09e55de65408d549c58f7ec5ac4a0d89b3c74715e86a2d67ce16f929b89cf72540d590ca9eefe9dcafc2c10a61a0f0043109163c1f640dec58d538b611cda728d94c9abbbdc626a4ace797679fb77fb7bdd249a7b4ad441aa23df299d36b611d05a0e3e0367698d3dadc3d9e84c9adc30fcb7105012226d71a206fa486a137216b6c6f47377ecd783d98adf5a303b3d9f6446ed5557013e9099033d734e83721a2830f937ab0d8026137b754df2d06caa6bde58af6f8868471715a594f23256265d4305eaa98800e016c52548a7f1db27fed58e0ad37c943e1a1a136360e77263595c00296120e58986a3905ee6069d575db50756308f1f7f7e37bc420c0072deb5d08dfd8935f56af25faf9f25ae1132af0831c4c1aeb3de9efa7d91fa2a726835c8b6ff39b39b6580412fb573e0cabdffba77c571f44fc6965be518a4026a5a7cb2ea41bc333b656760c7e39cffea9bdf436bb6f6fef22eec251e2e12d70225f2cd05e085833abb57913ef2cc6550786bc6cc7098794d6ec1e855f95c62729235047402a9054f4986224d792e52525ad54095422bf181c83264c5198f734eb9b5c4a58e57c236312198379f9467c99e8c4c34e86509410c0753f433b581724558391de293d9f56dd880f4cd2375fbef269e0b03061a16e995a08d327fb272462130cda77d20b28a42518fbc04ee21f821a5efefa0164fc2ee5fd02b242336b00d312c4c888c5c7462ec6c5bf40ac156ea59de4a1d3c03ed6b3890bbf128722f7f5caa3e6578ef356af189759a1009852b7cb503f8455b8e25f5895baf4d72302a02c01995b48089887fd7ea7fbd371529788c6a9c416d1cef54e9b954264ff55bf07f918784422c8f8ef4ba586d6d0ec2fbb60a00d1757f505d39ed210f72f54fadfe7c1163031a75fbfd948ec9d8f70ead3105bcfc46f7f172425e459d5d9cd7fd99da06e9c91f8309f851caaab0a77cb8b73859fe834e8fea4f10c737723195b0dbc64a7c3a646b5b874a863666b9b8eb6aafa470e5b04590dc76ef6867daba89991f36924f0c31f5e304be08a0acb20d194b36fff673242fd082c6027225890705b8baf9b2b6e0d245d4886b6558d4c3a25f1ef62e845da0c20334147223b78f4b4a97a0a2e5e4d63514ec178212dcf9f1fc102eb03450432b1b5e8f7288e688aaae3f260099a6c40ad6a7e5bb283d7322b1d42371f549e582aac4da18a6bf70d406a1a899eb269dbc3e1276ad3b911b5e80ac988946cdef406fe07f70c957e97abaf46ea05b2b8d45d8cfc16cd725e181dd42683b699f33a484448572a37b46adf222e0bd983e597221709995ecfaaaeb7f1692e76a3c11752bce62722b71f94d9831aa15fbd71ba28bdfdad40947fec782186dc74dedae75a005b8729be43fe33e162c1254082a1716efb0ec958b6ccc579c36cede5f1f3fb9b3082303a34bb5c8bfdaa75ee7839beaa198b6dce3e11ca9be610757dd328fd1ac65301b97b49c4b24a04f31c8b122eca4e483b1fddb12f4b0b0878fa5cecf8d590f470c12aecd3de801fc39cd99189af3400ba17a1710a6044ae85ee6fa7341823072c0bbe304a67304509778ef5d245066d973107510c3070e2c43546955ee52f80112d54f4ff199839feef049c375204fca51f2af7b8735f440e03d5ab660ec06569f586dff25219b1e96ad15b657c69ce453cd8cb273ee3d7c547edc14124aa02343bc7d6148a8dbafd0e42be85a9d75700a0e40e81f43a38cbb4f50d2e46962141f5ba66ccbaf4ba416a0edd4720eaf6aec6762b9b344d887782cf6bcb6fddd264dcb425891225dc92bc55fc769b167ec609aeea35f08c56a4fc07b9afb3e5e1d5a3df90f576cefe1c281ea27c6b7bf04bb5544b488824da3c813a086d7031d72bb4bd6de982b017d2f4d7137fdb5e76e19dfb715b2856fa01fc97d96b5fafa72e4a53840312c86984267b7308b1a77cf4bb0150cd7ad68b0d63c72c308ec2772913e4698d190e482819167ed973c50b1a7ac5636a08bd1cda57191131e17c7724793203235d94ebbd4388d07c06944eee7a22d8d5ece6acd345b1492ebd25b4f01cfe425d228c0d100d3fddd8c0b78d3932e7855a302d474c910660bf43c2b53950a4f044da4618d4dbc19033b79d828e02ded1553abcac02893cb436820e30f54f93c699cce3d6c4a6a42bd47525dff0cbe6505702623df494587ae6f0c123fc1dbdb31701903844559e4ae5b5d45d3b2d436dbef9c8c412db08bac5e29fe7247eff83afb20ce75e571862c9debfe1895f5d66db7749b095dfd38b2db776f0de88335829d0905f3bf57a60e46f5ed2e60b0dcf02ea36cab7b491712fbf5b53e36a1ebffcb06e9d67c23fafc3e236679e803e3bda4a3ecb81bc2cd0da63bb87238c2a6ec71938781a2f2df582308ebeb44e9a63969fe47a30652f9e8d622db721c3f1c5e3049e057ad809fad37b54d1606fc56888799362bd95e23c48d884072da0c47e53f53d60189d135921fb2ee432e659bc3e5a495c76bf7739eb5d2374797db1db93b9baad2a058cf70cfbb5c84860b2cd817070c99e867025e7616f5300e7f0eb048a8041988b4cb2b06a4c429dec973b29b083aa2b9bcfac691452072c9610409508916d75cb735a1012bcfd1a8af3e58aad53387b5deaeee212b2272503731f659cbe4a39282765d53fa3a3629ee4d03be3d8ccdc7e54c0710a45505a9332ad12ebd7b28bdff4f7283b57433531436d35791bc53ac48f3ecb31224726d302fb005e7b6bb24dfba55750bb2eef4a44d71e538e955264180b05c46ac21383515aa524e6b05073ac10b8708a7600fc019274370854d25ab5c2ab8a23372ce885fb8010c9885a304570d812ac184e4364ad3389109a6c5627632b622a45ee67527f340c856c778da845d7d81219358c4f048832b5039af45fa25e495d372f4356af76c5052439cdb2b5fa17e02a9920c71ba8cc5872c30fe89fbb3334e7264038f4389da8cd5f96c1e7c8c6b6bacb62a8d4195620ee96730430ee7da3c728d789852aee801d0291c091cccbf315d8d7ee083ecbe481ca124643d1439b172558b127c55cb41197a0a424623ff5074c2b5579015b68a7d770be6cdaff998117ae8654638d156a94ea7e5bed665caa456ec30c331cb376d95cb0fb61fe3ff72f9de4d4702017c003af8c85619698c718f3a0ad75b8ed56711c0e9d7ebdf220cced984d2959efd5cb8b0e12d69ec6fb10f43514c82ee45f21804084cdc41fe72eddaf71a340742444608bc7949eee6caae95d80eb24560c716e9af9702676b7204f4946c832a212021bf5118434771f7e8dfac53ec6e9b9972fb722add0a5272433f7612677b1833c8e071ff1b379a7ffe2807e1c19456d8fc811f9acbb8677265d9c268b9b8e374168df0c1294e016a9975a68bae49b36e90b285b83d68b57272d99bbc95f0fe14f8776fe570bdf26e2b1c74300110ee462cd62d0cb8fa5041099dc449c5a21c8cb362f10296949320dcb8736d30ad67517417cba9bf8e797251727a5a444f16dc1f0b31c4b8018ad8c4c6160bedaaa0abef41e680a02934725fbdc50f20daafb2078b0291639263843a68e9fe4908522a2d17c9b2d3e36f720f2edb139c417efbca41f9b61a5d830b4f5fb53a403eba5d5d1d4ac26e661f72dcb522b221066f2580a744acc93d0e0a0eb807e5ad28639e399e2ac239e8092ca3cddffcb3bf3ad9b734c48aa6d66484ba39028632bf103cff61f4b9646bc57280a752dbab15e205484c3259617a16ea69c586d7f8043314804b9a13262cc4729376f6bcea2febf8706fac4759cb7a580988fd0bda7b603c45777da5e2cb667201b456826fa19c8c660e690e74981d8613612785f2be182d971e97123f471f46de1da3863e50793a8b8977532a1289c7f5509233e90baeb56d3008c56fe03e52faea38b2c6f154c2dd2374be686e6db30efc6231aeaaed7eab75b2eb694bd77262ed4c807c6fd43fe64abacd38709e245545008f4884f5902d64ea1870306372f0144e60b4f51d9a9fbce925979189688973ae68a081233b87d80d9a418f956f15be5d68d44ddfe6573dc2cf37b7b2d80ac4517c67880feb49a840e62e4b6a3f1a197ecc247b76b19d99095483b8128b9efb0461ac074bfef280e414f2da9572bd1507c66ac0854b6679395e1af58993559eed92353426cd4905598e5255350866212b7b9c209a36dc389e1c0c7401e3bc708d3b2adc59db9b841e5851ff0d7285e9c966bd3d959e7a1c771e866ced4c9a863a77fe4a65bd3c9861ae3c335730e785a03a491de8f6da7663a377044515d6c20de4a2d5426e023c7a8bbae809721078f9a0010384edc981158ba64a4c2a350aaf677d7bfdc14f039078d0d12e723c37909c8a3ae6a813e91c15bd15cb89da05a384331ec78f47d4c0e7205a881185776633456249567c79b3549a90e18ea3227b370f17580a9152f8ab07bbb172b70f7bece9d62612b6d117911813d41685dabee82f890248566bf61b88f05772dfdae1bee0214645ed0c7b2bd9710edccba70a49042754ddbd39f0a3f069a672a56dd7f4caf6c41c23044655aa3c18000a8579ce36532506c47e9c9ff0fb7072a0672cc5000e0635b78f5ac4d1d94c52b2a837de1aa20c96adfbb6060478cc72c99002e874bdfd84169936176c4311f059f8f0822ca918a7170105b2ec547b727640138f1e07ea94f0f826b74ee68ebca8fa9d110bc1b3fb146131ed2202e172e267b9ec2615b580b8ee7aa493785043947cb3bd65fcad79903138288abe996269a3cd561fced4fc3b7ac57c0042d5f9c516cdb5f909107cb1e3652699dcf44207cdef56f901d9db1d9e980048533c5085bf8bd6086b0df60af70d9616b0966f16f1e6d189aa92b54a412035ada842650f193d24ebe1c84280f5e28aa1e72f47046f78bca461ee072232e74ced52ee02b21b897a504ea054bc9fb1f0ece786720c60c15e5b2ef2ffeedeaa80cd597724bfcc520080fef4c8515932492a08fb72b5db346fdaaeea1e737615a51bd4b3a6581c1d82182ad146c8d57a666d6950044b7530ca4fd8022d02ab39b523042a582586b127d4cc0919c7bec9f07db7a1727c0146200924142603811efa6f2eae7e3b5ac3e31e7b988034e90a183fa30718c065a5ee62b475170fc9f4fe6aeafb8206e2f5e2300d8608fa43a06ce1d47772a7bd49e4da446a740f2c5b772465cc0c8fb9db96e9c282ef8d5ba00258bd6172777792d64ec8a6c576697e3f90e8ef4a3b51507b8bf99ba8412ee6b3dfd71720b0bd0420843bb0104ecb695985cef65142f6d89cbb3d6138a8003b24d0c1df72a1efef449e4ad938971a8c30e37d459780d397638ab7e95bf58880f5ef12da2cf27a194262ad7bfa16cef1aa444685be7913620279336b30cea095580b97167296c810ed978a37e241a8b0832fe7d3044795d0980ac5ab599fb278de9282971c71714764545a7c1beb3491190439eaca9976fbc0fbb90f979e298f01ddb894721ec66aac295ec882fc791e9778aef99e3da725b41e27f58c0387d1d9c263f672d417e2cf6a05e55911b3805d54c0a39d5a5903c7e2d3826cd0631f4384fe4272ae5a25e8962f6b5f80ea6ed0a513ecd416b4b8a3115858214b71c49c43fb0472e16a38103024b9456889a0959bfac5ccb4ec979f48986f508e45b70b528c28435bc2358fa008f7bc3a0fe5f8e07225f7809f3e245f39e23a94e636d5e19c8c725524eeec26fb3f542aa8fe551537e4b4536be6caee65cffea413ece35c4fef72c205a3c47b979023c24c7e46967ca4ad6dde93f2ed97ce8da51e17af86ae2172e497558ee754eb8040a1147f63ce530bde6727cfa90fdac738248127427337238722fd6538447a4d45d3f5c1909d1caabef73b680ec6e1525f3dff1d381d70721561b75bce4fef5aaecfac7aa4e71dcec67f3262c39433239cf9690bd022d072f85e6da7ac0efd71fbc91b432b61a2e16030afafdb493b937e6d187b5c7651722d9e94ed6f3e76d1c9328f41fe5037d87a638f3982688cc97fc5428187f04c6b41fa227c707c2c345c2c59d2920d1eba977681f2afee3ed3a3eb0a7cfce6e236bdcc046e9b7115985ea865a5a4e6f5cb22a691992c5014bba1e780834db097255e972c8bac5665b01a32dc7b184c674e658b2e86aa6f7f52252d904d1af56a725900de66d961d78f2f37f72239a81d0965eff1c959600036cc420d4303940272d04495121d1e575c140b2ceef88976ff77a62c89721a706211f4db89af557d10a9b2c1a6b844d2a160ba4e0ed656993fdcc758e33063d9497c3373dc09c73e724bc467b76f42171ad086777e8f9d748500f334d328bcd083c16696b4c58f871f119a9b2888c67eaf02c47547ed1baa2440ab99980457247f8f2b50adf96f072438d7135f55bff051bda76ce780eb8fe882c59cf49cc4309aa4c123ca99ac3f722ac3106a38ad908742ec5a52d6f43bb460fd3435c42fa1371b359e6ac9de0328229f3bc08e54442a77419e84aa8d336157bf0bcabb601354dc1c3b11eefccc0ae25e017c360b4900fc7512749aacaedf1014efc4947d984a524c4dedb607e839180faffc93ad3ed647577a8946b96e57856a29192f017b489c81139fdd350e727d9807d52759378241aa42e8ff64861707904fd50960516d8fdca8dee088520f683b80272238b2cd9cdbb34f4d278e1484aad78d6427831a555672a52b6b1f72fd48eae840dbdc62cbc1a519efaa5c333c53131a8bcdc2077334fec9a20a252fc1a859ddd233ee7c773308104c2179a8c8f91c7cb1635947678be0a52a01d90b5f79e38e5321c49138a46e1aada4cd675f2c2c92d4567c42c1df5cd542d3b36d6c713a4f5c93b7db4a8b74a0538028c82d824781b589da022e5e8c9d19e96372388574925a378f1abd246e61af2178efc8d571ae27d0965e051d772e6ab9c44604ce12531d6562e209c68d5edec169d7e2d7655de02cfaffd48f4dfe2f2e5b72765162b12c85fcc6e55de274a755837fd483aaf2f9a7d76930a40ca35c251972f6a825015fb5f44c92ee698f3f63342ed0b84e11097e615dc3dfe0c0d3c93272d37e39c81221157dc73b249139f5a8941e6dfaa75bb0bdb3e4ba7ba8a8a0064b9cfd1c72fc0786be4f89a6cda8a6d21f3148f8e8657eb3e1a081d1d221adb606c00b6f1ec0c534e78490939928335a961f347f4805fe03379b5cd122ff7b0c72472f8240c41a1ca5de5f7538aac169f35f0d2fb1f5de9884f3207bcecc84a61f85fee9c84d1c2d55e892aff98edeb492de656e078dac09da0ac4cbcc32c56072bf46b03ee2a169b8d77a8fc122308e660c5829deb64687a96d91c34a0b65c572caa97ab86d64007d5dedb0b5422e66d54b94ece82d20f34b26c035c02f77c247e2930c5f0e1931f4c96e9ef3ef76b642f3a37fa5791023dc340206855c26b572b1019842cae58fda2f1b481cd95eb111b6a6468f0eba0e7d65be9395cd2cf645878b41895ffa91e7c8d8c86a9cb3ca1791e7e5ffd62772b5c01080e6191a83721dec417998b9e609372ad1b5535604451895719bb025254a244c4f119680eb723b81b9653b825e88825bfbff26f7e3c9c045ed5d1ac4c040e7a0d7b37847b358ddc0e4609f485a6c15717b324eee7ad38e89e5570b2423d91a2dafbd37a9a272434a7fcc7caa2eefbe3d9780d3276146011d4def9f024b07258394fbad8c4072abb8c6dbc4097adfc3793406cb4e00f04ba9322ebd1bf7a766196024bd1b0c72d952a0c62ae276716679d2fb0793fe6f58b8c50bc86795c8eed1088ea098a37283b3f758e080159b3ed234f746cbcd06ced578cc3ef420552fcc76737caf367293f2a5351d2947ef5df641233abe4b818325804e31a3b022adb862bab822cf38e8bed25a0b6b819dc7bcc3325a5297c87e6d86be018c47150220f977d1979d6e4128c74c29cb21c0c56ad69751f7796911df52620e2f9cc049380f4b442bcc154671f76e42c275e8d7661129a7c14504efb9c09cc4b473957f9b69f1b4d13b631aca806b51e7d20e62292b42a9cd7b595e8f8f359d1fa7cb634738b2c3746c729aaaaf1cea64ad1e5b2dc3baff54142d8a9f173355f88a2f48ae17b0c9dd300d0282ff32721aeac76cde68f0e1fadd22e9c043377543ba72942b96ab223fe972dbf1d49995de6ab74d11b29efa1b4b4c85538569e41e73a3e74873f1e8dec67256af1600761f0af5393c819b1a95f26a5bb192a8b5369350eb3d74d99cb92309a36d3c8b7d94ba503af60541a5a72127f6b783db682bb698f88648fbd235877225fbd9fe1f223002f6609b8dced7661b08cb054d098adbc0fff06acc71f7da6c685e6ffd40392a788199f6b6ee2f589aeead111c96d5df1607b5452e200ccd35b775863b9440c5dd29498ebfdc517bff3ca867c191c3673eb8538783bc18723eadd48ac90d1d1bc3cce3f380d8a0f359a51a2780d81276d37608ea2ca2fe9572edd9de172745a8f37f78c74c90e2f313c9b28787ee501c29be77b7c368acb072aeb38474350c012725baa7e1e9118536116e7960221aa005a84bdd1b97ad1c7229ffe3b3b26d786b1ed6e1a4f000c3a10c2a778b8e8698f55ef24db343f0c5728d45486131b1d7d1cec30cde0a0958e122ac8460a00bde59d7f447e10f45492c8be479057f04555bb4f1005a126b2deb3cfd31cf60307893f657437ccdf6867230dd217454d7a0d9912aaac1244530e3484099bd726f0b68368f46467d9db3728e393093dd1df3b9d81bfa8dc309ee0445654d5d3b324a2cac3eb415c00d6a72229810937f460bc2125575430e2db73c6d303871445c734fb03d85e6ed12ef252d8479432959907dc58406e71c7e1e267600ad9b6039d6803cc5d5703f9fc2727370d12c0afee9b96d6afbbf84a0b3a38c09c884ebbdfff6508f06bac7fc037295a1227e603d7f3252908bf4383f57bf3e4cfcce91473e7988e21d416fa4f87296f7287ce7028ea1e9fa445c88d8daae51d950883dfb82c6ba2412b54563b1724606766a76b5e1aa3b6377657567ab2b59eb4648a9fdd53ce5b184d7cf44ad7265e146572f467220aa51bbe965075d0a165ea0d6378ccf763206eca9709d8972a34423a741b6dd23a51ac413e21892dceb861024e8ce93e62b58d30019394f726eebda203d34a6ee18d54fe073cf542fcc97b63a1aaac9d3b5e9d5c532feaf720f697392ef0482f28b04e74b7ac9df52c324d5eccc579265228bb2933935c472e83fd033f38348be6f6ab1e8243ea99c8d8236a9cdcee4a0918f107bb6487d62179da0f69908a1f5e4393274b8d9490cda2f72deec94c6a3aaaa2eeabef75f2968a3fb4313f44e5ba6755395b6565e6cbca88d6200eb42db38203798ed79d562f0ab3525f0fbdfd0f4af6cf5668e91637d81bb8939fed21f24153961b172ff36b414b9871f3476d5d99e2116152d98a7cdcd675db3ceb0ff93e67d5094ab3a72f722937e00f11f2fcc16058e9e4ad99fc2df16ed7d093dc66d066b75968af00b6e57f8b87c1a507e8c10bb59b64e9e2e050fa0ce508875cdea8e7753faeb3d72f98a66693c04fbf7732c9e53bf18a484d9e74d4503dcc8aaa16585e7be89d972bd6159831bb10e816a5993f96beb2ef811554b51d966ab6fdc0d9104b9b5f472a736c4406d3ac485ece57db08f8a31a074b8817cd69a24848234bf7b05951846043f346507d8ba427eec9a980b897c473ef0c3b2e94788c12576278b2865b172d184b025e409d302adf59679189e5ee2e531028b661b69e8adf4e9e47d37ef72d6aed2209a9c9f841f189d69c8d666f05a5064a27aa543c8ad556ad4e1cb20720815e1d4fa349dc0a23ee37a45d25bd4ea89efefd6d84c6010715ed31d0b654f37b8cd562ffc0bc52fe53c9fbdf580319e174b70b0a6ee4431c5d6587c553e4b3dd63886412529d59df245651bf15171b6627f00be778207150f7a9f452f1172260f2759418be0821b82ba4543399c858f2a7c94c6fd241418c7d8e35706a85a9aa9d1b7e3d6bc7bcdb74fd1127b10c65b659b8e7bca0f576848f42a32ec3918164a4bcec048e5281a186f54ab35bc1e85954346a5b1cfdff13467d2c0006472a786ebd1540839e76805992a0cc4401f067bff12492b6e40e5e49afc98dd9c72e97866df4b7c1c7611310ad133334b6330e7b529c1f5fa3965294c126d2c2e24ba69095221fbb2d1bff2611057e27a4f8ee58f01d3ed7846a71aaf687b1af91a88bdf059475a0896a34fe1a13e598ebd7e34fd707af384fc9b4a918431e8573e9bc220c2aeb35bc89c9c655825061c2538dd79685ae65f4125dd0450cd1c1b726499cadf9afc5f7065ab440bb0dccb66a7abcd21546a1bf38017d5488ed2b81a0237e3766b61e972472d4e8514cf2b135866cf556e034ea9df795fce6d4a1f72bd6f38d6822a89f746b87d85c55ce7646689e79b8fad183a5bee9fa41ae8467204993d5d74397bc57d07396b5b672574ef9679e24b98b6fec4a9e734c517a372ad9f4afe59b51ce4d257d628cbdd52818e484b4bb89966fc3e2a571f82880f72238bca189de01b08907b741ec8ac9e47ffbf78c35b996bb1c2c640e046e2197243a61e4e86421c71926e4783618aef175faf7e79f9d5561a20c46ef3ec69f57297f6eab0e8b53804c70eb0a39672a228f01b8ef03a3c3d58dbfc840ec1a92f4dfb3ce76ddbc5498193d3c0f27b8e8fa6de19197e7ba85b7348b79b46aff9115f042e6abd1b9cfaf03ea76268bad6c645007ca02a8e3167f5cc012a29da1fa472aba32cbac1fbfe27aa5a7b50dbc1c4c699927f8455a1f8f21f5ffbe4813ccd6492825f659a20e27e549748ff3b049f3b58eca8f4d0e8560478d3e4545eab3859619b484cb4e3f35dd2ccfcad07d5020982ac399098d028ad37e9a18c8d545c3376a0cfd81a13f56d3e060d1942047d195d1bcdf760f0e121aa68c0ab081eeb724132b3c01182a066f8950c35afbd8b0c8e67b0bd5ca2f639dba8b2f23b8e3e726a4475499235e1ce3b5cea96efb21ae96beeefe51da51040c2eea338d55ec9721d502d53a5bec518779516fdd6d1114bbc552961bb1ea5e03d5ee73b03ff6c4624a96b06bc3b7a2617a3a0afc265a1173c9f06e017623f6945e83ae8287e8847fb18b4e72d11a284f7b5adaed942e9c9350758ed0d148dcc07fe7ecf8ea38e061ad9b93441a1ef852e8c3a4c7efbdb02f37ca8cb6eba0e9756544a6faba5c872f4ff18a6e185e84632016f13af8c9b33e51c5682455839b2e67e693193c3323a7f1a9c2576b76bca8bfc7d2ba810e85bffac7c85dd213ec42990fe27c2c6453cc0cae6a2966f11ff7db776ec283bc5be4d728ad106e60b33ea49f6c65cc1b00d950ce7f617a7382ac4b572f460a4253b3755ba167285186d3351c599db02c772d31df73a5f99ebb9a2326581d98f08c406d6adbfabedbff69b7535fc52b71e0d6539bb2b868900674ce90bf9327c675fc4710f1d01ef1dbc0f9dcf038840583aa7930077bd4cff1055abfa7b664860cc70fd64133b2e3b2cc681d12e0788fb7229ee4176c324b8ce2380dee803db87abb97c6859986e545145a8784c7f77bb43e39522d1b5fd988c664d26433645d236ef845adbef26efc449b03ccef53c3f72ed3e8360031e012001bc18c57e52dddcf298d55beb32dabead9770683facb5725f4bceaf70950a27a9e95bc01f223038d62f8266e30f836642d67cc27ee5c125c6234d0c56d49726326b22203268f16bb19c3fa3cb38deb97f9f2b69bb95b272dfcf00818422fbd91bccf6e90eaf53e9351bec6fe2ed8351095c19d8a3a1530050a1aa32dd65b585e99ea4f0b146d7e4180aa464ca9a5600ff8ae8b51615c072f4c997230488ede24a63b774796663c3a9b82527ab710d3135b15f6ae90f853ce9b23e8b5548ae097cf405acf2391f82bc65fdd45d0ba0c8392085f3de8c5672d3501a65af591d8dc3e618985aec76024ce6d2db9eb1fd9b019541b407261872788c43a1f4844e167c1a9bef41e57fd92b72e11ea670cc3674fecff0bc40a319ee23e02f9b091b72d9bf649e0dd4299ba766fb1927482f03e3f81a5a29424c72bb0d24cbbac8e6d45648977ba14e318d076d696f32ec68380ef33118fe9fdc5a92dd0679deed33ba45ce8a04530a0cb68084336f26907cdcf5eeca38d09f2f305e1b27feac1cfccd9044f00aa35de2d089b80436be40f49249cf53877f64cc72c7e09f187b0296e642954c9fff09575b95801794ebf6deb2e935fd83208c3a32ba5a91db383f6658efcbe423627f3f99e17657a9ebc8281e88ca8bd708f4eb722bcea47ea9fdc0ec1f66e85b8b8fdd3d5b50826423c239c7c2af41f077de1b72cbb73f26cbcc2078e614565f90ee70fa35081d504abdc39bd81af37ef9f4b403683e4a3241a4f6c8d0ffb68a004f66aba815278fa11a983c86a8e66064a932723ed121736445b012cf6eb1464ec2dfa7ed6d9554f3cbefb6ccb529ef1a0e717278f2b5fe954a8b7fc8f25fc725584f44aafddc981f607e8be6a0b39c3e83bc4224777ed0db3309efab45857488c6516a733c481d7379315333e62efc5715e772187c7a4f972db8afec182287e0dfcafe144b2b87b781d3eb24f5de4dec4f6c720383b2de90ea6c02c4198c1eb86dd19d9b5deac37acd24cf3d3ffb6862b37072bdeaa1b80116a8a13bc4c5eb87952f34e1101e9e451c87d1065a59d8f4e683720d499f275fc61b2ba5c0b63dc46dae227ae06faf809f469c3cd698b236dc3f1d5441356f1ef54a6ac14cba04c5ffecec21b7b9b52324253f0af6c985aa2a757273b497775b8fc6468012aaf6dc5b189c5252d27d30613808976dcb38de8896724f70e75427fb0aa71db5d20beebcf5f6d826a1555e988c6d21d802eb79a1a2727e87acb228772bcd673fbf030413ada5d7dd89f8f6b75c7c8b9bbe37eef36a726c3e29a87038cf5bea6c537c6dffeee53b6cdeb501dd9aa1eed1ee3215eb73723c95d94ce94b45473eb201ad9ba1ebdd5606884822f737a33a1562274424ec72602988bed3d7bfc48c2a979ae787a732883866d68a976b669534b5a369691772ac3e33568846a5d4de765c337129393aaec730570c79c243f696563e2965c2056ac4ee715431cefd9be65997c496d22d473d94ed67408ae0942075254bbb3c72e4f3858944564ce35a15c2373d7335b6c7f1942750439ae41a8d5da79ae645729c79ab17549d716d7a2beeda2783761161eaf27061fdaed6142155c04db308723f05dd1623699d15ce92a134b1540f21f028b3825b21806bf83debf2c216527216c887b505b6ceb33b220640a0d7902494f245e6da5b7609a74215b1cda32e7224b1972da7752d54f9474db09139bf8f18784000c1af1c3a3a006dd94252df273b7fc11afa36b091845db0595f3df174f776dff44043a1fdc827b63271f42f7234e9fe9b8cc0cb9b7d13b15b3d9c8701b784a8130ccc2783b5444856825926725c788471345484bfdd1974fd7c98f52aa60c0ad1b7bff5273a733b62756db5722b89402e3ff9c6bf192c0558ac2f5342b040b2b608811cd47370207238ab301726203ce656718dad8b1f5f750026934cc460f220423190407a9b7de435f4d33399e44b34c830de5f9f1ed47ac2724f5f5b289a7cae284fb5c05d4c49e1e08572affc134ee9d8995f8c259d116508f2ce7ab2e3e0317e71dfd5d9915f6cc6a522a3f8f88dffcdd03f3eaef3ce90b1d714f626a39f711cf720e1c353d7bcef5272e0d2b903778640314a288f3334d2d80723bff40ed59a5f0c3e8d9644cc6377722bcc5eb448c61a482e3e091563e304e1930197b4e65a5038087ad7d3be70d63126aa04a7e7f29f5008c1e866ebb9389d9e912a3e8d4c33977fec5e074d6b8572e862ba19fa1fc72d25557d70c0b04596e675bfff311dde77dd0a13ab56b614721b8536b49053039e132425cb0b457490c8f518d575a4ebd9e3277cf351965a72152306ae37b28c5b58650f8b9a21be4c4fb412c37c967b34f420894c9773ba20f1701ad13f5b290fb899df27370540ecf415344234186c7e84d9dbfa176fea04f16a0a618ac272c56a28e23c8a5a5e009b0a0cd9abece209728fb5dac0afd972f7a3beb0662d53c5b37b8c2d94fd79f3d108e0948ad663054544f6c633360672732aca4f3f996974ff5756d37a502de4695693ac4648077e1b426ef1c301bb6ff18bf6b8eb1f72519b765bd70e1e13520fad15258c15d028dcf2472d77ab2372dcb183d9483e5ce5ff6432bb64a1ea926b4d8c9b807a3c4d263323d780d28a103bc3eeeda5518a870c42747632f157d1d541b0d4aa8553e403614a9928c1cd72fed2372711f745ece2163ef8156d05f67ca2c52b67533e46bd62359a59b02e7216e9c936d874569ee791516dba8cd541772fded071e066556db23546a9e1950ffdeea040692b9676ade71be655112aecffe83af3625da99e6948f3994d3a3b726b305463759773009bd110f028f88254af2adacd444a31950bebc7c81719d572d790a25e4347162f40f677138aaa7401ba12390ab075e1a675c1837920d94b3800ee01bbda3e1f84b0c2b28eb60c74266eebc5dc7d1ef242cd462bbcd63ce4728a20e41c7d901b3fa52c37b69957bf531ddf1c06579b2978d59ab40a6521124151a6d9b506cacee632f8a53946a08f9cf0313a85a3031dea2d8d4024ce915672d27bc885ee89f173765718071ad2e8a9d83bd7ccc9dc0339ee1d4a09298df2726dd9d94f877e4f5bf0c1d6f1bd8b8c030f234ae40eecfa972c2ef027ce507d7282c6cf4b16f4df84d41252f799db84cd62409fda3ae0cd2b922458ef679412724d39824e27845af360a5f8d66563855a2d94d1981d8f8c6ce2e03bdbb828077287298e448ee1a036d22e99a33df3a3fa983535dfd366263b2f9dad027215d47221a6c4455ef927452abd8104546bbc3f84089a42a2b2e3271e40b163868cc41a42d50d925deb0b461819d053ed3ada438266b2210b33fdafb663d0e194fe5e7252f5f762ea0601c265afdeb45c05cd36d3da92e21dcf3131bd629ee3f8fd7f7251c4091ea9bb0334211fe661793d0f7616d42110cd735e346212887953a65e726410a4b5246ad44a0c7f0747e253354cc253a4bde8d712ab19537b30c4658372f0685d47bf29c5e28697852a7befacfad6d269bc596c1811a0a3ad629e645a7285dbe6d251b8b6a25e9fb5f8dfca2df81b9f23d330c3ace4995dd2bfa2262072f56a391e54b4ae586c4f9271a2df11eecdb55304752d963d5cb0ab9bc0ca1401c6a479f555f9e929a9106a4be1582defb2fa41227491243974fe413c6643f55e010a1fe139b23cb19c874c2c287093369f8476610d0a3813a25bc9522fbe177295797fc0468cfc8ddcacdee10dbb37d21299370fec6991ca7c6a3d4ddfcdb472211363153f83986ee43e33bac79df735b650b914c624405e6b0fe64955105c72c7f38bb8e6179b383325249e2f51ab7d4697d2c1a247a663a0100351663b51721df0c3be2afd36ead992c65a1cc4ef0896d02eaed19a09a73f31caaba2937c72410517e1131e1f5fc399dcc9cd0861d740e5d6094c52c4d9d50f3e2a6b3d707249df99a2b984daf59dd7af09d6c32e699b45a212574a431e35e0ca9f03cea772fc2ff2918d1888a3ccd6a04bf91dee297dfee10bf1b7ec31d1f4ecdc1548a7170a1d094f98af2c47a9064c046867dc4aa484f580f5dba7388a32566757ed467208f1ba42b9d05a40109730f00f30ff950dab3ae70fdf2057ed3176574a62ad549208e9cf021e33aa2aaaaa1ffcfd0a80c75e141653f8154d4eabf87cb84fcb72d1af909a1bf515754ddff171792fe40fa00de3020fe39e09be89e26c296ed2456f05e76c96a0dc228206544de49150ce907542b06d2cec342ce2b5477b043a72fdc70d232007c4a2e60683bfa23aa6bf1e84736873d3a5ca8822435292bdba7231c2ff4b6ad498abde19520773283cb5a9df959d4ef58452962ba25849bc3d23fd830d236e9d70547f314a702dbcb0dab181cf83641d319d40d3642f34a3763ebf78314cd7104249677b706144e711af9ea6017e2fe069e158531fdcae9a9772ac72b5dbc2e34cdb734d4411c0bfa8ffda745f14c436615ddc5c47092d12a5722ed8e6dc684dba58c73317c48da72fb1600b90a7eff58aa6d1d4b8aa7db0b371b74d06487f625633611451e724847e386068b569eb3dca2ca9e2d349385251722f658ff19b368c6f1c50664070c2846bacd7810aee0fd8e07d64769c3c37d7729ca23090d2a50f7978d96ce151de1fd178f9252e6fde90b662ca61fc30e98a72cd10489205e780acd571f430416bc7a705a6cfbf873d1f321531ea5a605cef7215020413e1d74982198cf08af00fbf5ca396982e1ab6304ad9e1a0b68c31a172bedaec3368c6a7f32391f9f788aae05ed57fe143d95a4d3651580bfe6ed85272acfa9a61bfec12697739a735c30e85bbe0e2bc028548cbb30810b5f607ba25725b860bcbc96a2466e8f5a48014014c8f383814912f9a1e38cce0c96f2ebb64722ee3e07f1d28acaf96c33d2eeed409b95326111f228aba477661e74258532d651722c6c0ee52dd06ddabecef630a5db418d664fa1e03b79ce2da069348f73f60f99ddd87b068b0ca041b4c80887c0d98cad1a5fda7095d14ebd7459ccaa63072a0151c00d9ed57e704e55af85c6aa28036ed66756d032cc14937db2d4c5125277566b6e9909f083dd556c49c0a6c856956cbd722d262c60d91a1f55868768636a81c8c2529404db237d0448119b1129ce1c705045374d6105aceabf13247ca35c763dd88450fb8f072979757d0f5f2bd80d7467222acbf1f170a9f4f076bca19b9c3b3804062ece2871b234e793d3996e098e18dadaff7711a8cc9ec8931a90e2e735aea54c763e391e5e1bc109b0deabb015c81919a7534c28c518c10b79539e8ced555fb945e18b8d913f0b42d48e9fd9df8c075ac245ccf3d4595211327724da7386347ea9bbfbaca98bba5a2c03c05a5011959c2d8edcbd327e2c3abdb417cbc16d7ec8320911ef61503add24d402e454c1ec3554a5aa181013a6cbe996c07f0e0536e4b0445ce47d2359217a492429e0936ce2fd79d25897a092c6b2b3347198efc90bb95d4484adb5916c200927254ea9b2ea505b80ff5fea508cc1472bc8df7217f780adb840854b7f50670271885b753c849c002ea4ab6e88c1f7e727665bd328f70143d2dac8e9bf4ac4c3b5cba7e34d47658818933aba6fa7d0254b7d6c5d370148e5e3d32be0520812bf6de64532182b5766901781a82cf956e727d8aef27f5ab6b370da97737168a30be99ce891a545c662c667571e46f57ab72f8454893bc5a2a7721a227af8b97cd572c6d3c55056e979fbc4348a3ae8e325be29353edfee83a20db0b2a72873b13547ec6b98d3b580ef04fa825bf07e62546e6ae64899c8f68580e3646cc1ae2d1e522e91ab352e2c4ff5664ffad7efede0843c860cbb7c5fb89ac8254e015af565d2eafe6637741dc26a862a377560fd000e2dc11736f5315f2487a9c7fba12f28e5d545f0c861d50db4397e079bced376c7be1d831013472d25ea06b9e0d15b49411143a4d08ad5567aaabd653670a8a09d02782a333158ac8d132e5e64462fa41c978b10a057aeb9af3f0626a45e376083d744e6c73a9b30b25f5fe3610a0898c12e29609d39cab0b4918a18bc08680721c9150c81c1d876c2add015fea6caf892c3ac5f1210ca0894305683edb79b172973897bf54ae4511a9bbf98c3969e63cb3de5333f38d7d8cee67b621a817130d4b05c21f4d3d037b65ac502476cd700e433b079b98b6f5222b268e7ed7e75372bc777cc59c852559ee12dec94e723c81c598dcc5f46c1f63d2ff30a7927702722305573475bdf7c6b0bf98218a4da38cf8a0bc99483b3b76c6033fe4581ee73788e8a7e2d8423fb4ccfd965b410ee3f92d990951136ecdea8b21d39f3bc77025b810445c8e4ca122c845489869462ea334ec3143bf0d07a100f0f4a2c8fe06482f4933228c314833069a46f0f16b2ed6537f0d3c24c94ce41d5b89d5dfe1e1721a8dd4ea9c87c33e4b3decde027e1e7abcf8571e7ee5fc1627272b8df7ffe37288338c87dfa7fa84a9b584e062aaf0cd87810d554404506931bae184790a6e72afddeb8fc8cb52c2f12bff433ccbf903796ecde531f20f5958f876f548f14dba0200007248621a527709a7d3a71eec18de7bb281a01af97f06fe463c87d0de5c437f753d8f98caf21bdb0ba02064569b74140eea76f2be79f2d571ee64e6a41d0aa24d72785a3b00686e0abb4778bcf94f8494acfbb49f8c777788bffafd58935b54b172e8e7979f9df220c37a36c3e3fa4dad44242695bda1f1925cced90baffd98c359f62f88eb028152eb9f7f3d6096519061beb2d9bd841a7ac0780e64406e9bac721489e86c92e80be32a3eb785e1e156040ca8ad46b5f3382db0ca9a9c3a3cd3303c0b5759422441b1dfadd8824b249b8f5d8f4ba0703db376720989c93d50df727ce084e0a7ec9414aeba6a5083c11e6d4f11f22ca36d54e215ab23d468e6ac334634d0d2a1e5121107c0b6cec18ed686e526faf6652bfbf99e71b13eb2308c7293010d2c918b014f1cd5d2db67990031cc930f994b8f7f7429f92e39f730be72a1d67f7370be89fb944a42b411064d878b9439fb065b81f8b35e82fd4df3d9726daa55a94b22b0ed2b260e0727f8d54a4eef63a0aa110db7cfe6b3a759c9c0724ade7e17473a02ea6e43487fab5153005aa8e468fb1ebf2cd44bfaff497f7272799013d6ca7c43d33b44ffb0d097759211a4ba924425c205e9dff54c6daaea729c50c4c247f1dbdb18d3918e563bdf9e3346384cc8f0a0f714de4688fc61fe4f2fe20814ef16adc8000e9268007dae7886236ce1ea1818ec4ac47b85c2195c72751cf2c4a01c62a03c13d68c0d90d547191824b8a8b57d473981810126fa5726086faa115fc044e8a2ed3cec7a6a79cba835ac184680a1c658131d7e83b0f7720f2a200338ea84b229b8d9aa892f07aa4e70c44523633882f20854e1078fe872c6be3a21e604049effa06779e18358975f3ffcc4e0ce61d0efd9204c38428901967daaf764640cafb1d49c6c94403aa32a3e5e5ecdb10412c41f09883a7a61095537934eecd7ba427ed1bd6e2a6e8d76eed3bcd4258b65242a6687972c09d3039815e0f75221d8bf50dee32a738f67ca72a16f641729eccfeb01e8de15007b726c62fe25f47280c8c5f743603cfb56dae7595e5fbcbb4f09cc476d74d5a7c2512224fc5600111ef6a6269127751315667f3ff97aef2201a95e16b194d6cb594d57b2b4168c05dad20660bc5000536d0364fa1c5f7f00c3a00a512746a6b26f05b557ec63d78123bd0ea08c120b2a202aba157d4c824ffe50bf58dc6f98e6b772dace13a22916f50dc3b335ac76e2e860061adeafdb363f8f6947928aa4f228724ea6ec1776f61473fabd93d24041db8637de9324156789f4d644e8a5cbe78420f7b0a9a6b7b223b02a395011217e966afc3c3e1e6bb15766d158da1eba6537726764babca277b829b334ec702ba1acc7937b5225bc3da3f8f03fb5cbbf2370729151b4c770105db80f40a517537caa9a620abafe1030b9662b7e43acd94e817294be5ab53b99364f5346b45948fca90a19c477c0802d45e1071143d7d348e620a1024e74b89c225dbc9037f29ea46b650161ee26381011bc474ab6248985022ab40991d5afc54441ab55399b3b8a8a6305f37a7e40b0c2684e44bbf4dfce751504ef6702e9777beb9380d64693d94e3c8c3f092d65d52295b31f1aa27d46f772fdbc5fe0056116d2856b757850000f57ccb084c235cc7bf23ff1490666fe3511a459da3552a3f11c527a07e978e30b9ba0ac6ff1a03defcf30c0f9c73a937572c20a9734a29dfaaba1760fcfe75a641d6917e27ea4e537e1b09c22bedeee5a7283d0355ec8f11e9487a5eacfbb8ce8cd218f6f268d034c8e67738b7a5b6b8d2069b83b7aff520eeca75bfb1b3ad9d4df84dd51b9e454ce538e7fd7652678f37202dfa1101d5375dc79ffa7ce0909fd24844b0ae87d576e65e17a580252ec63726455023a5043ede9582b583657a9c5a5703bc15d6890ca8f32cc5a2e19cf53725c9f949ef8a8ca7d3ed65102d97ed3cb85358e9124ed496d83e817dff8bdfc05721addcc936e1af3005eb672ceeead1890a02190345e04e2cb2f6af5a5bc0452729f2c78cf5954da1bada0f1336e23b5f6e920e3e095893c0fcb128ea081712ef7b6efd6318757b0bc92e34cfe8c7173c2a0cfa966fb087cc59218f646aa9072c3b2d530513396529460f83558fc31601cdb6f93f2fc84718a946aa61bc1dc7292bcefa77e26490457a270378e13432ee18a0bd7c186e749b48acf9c739a4f722c8d71c2dd9cd5bf4da5aa4f744a42440c69a63a333f9d9519b444e3e7d07824ed22049d4d9264c5e486c6c34f7cf56754917b9949081a085ef8bea1ca611643200dc18c805fac663b39c2128eefb58f3ee29204af6ce06ca48f63aeb7d7dc50437596e88654e8cce92ff38c4ced47cc5a17ca6100c88fcfcb08d874c1834b0fdc8ceef959dfcd58d2189f1266e26c22a8969b950a2a1829bf5bc20cac0548724fffadb72809c0a45bba70856b9754ed4086db061c3bb2607a156bb4dcfa0a59c0bac896de69379d02a1e7113ac69690892742122270fe209f0a23e9fdd956163e501c4e006ec00e277a26b1c543b311308d9c013d1ca5855c8957db7691ab72b11fc623d6a05e9f67999a5dde1c66f6f5ab1eb48f3107aa2b8377c5e13dee725ad02ac0cdf175e64253187dd4d4e116d18a502faa87856b34e34b4391686e54641a8367b57e9e2e051ca258c3c08ec5c9921fdcf56e5f4d94d6b10b3ef16d72d28fdd37f5e0876442b7b9c2992b53795a09d2b8a574f6497fe8dbf4a6aea57258a98c18af33f1ff4282fc01482342a8c990abf5ed4db4216c2de9c9226bbe6161a6161fda10879bb2dc58b0c2c2d52b519ec7ce4cd0963bc5529290369df272d23710255975a3fd7f7519981fbc68c8b6cfd81cabe6b30b6866a1613abd9672ad423c0168b497a3d067dc23bfd429a0c10b8cca03f4d34608cf463e2dec7e72dca10aa74e44991ffdeabbd08a865104bec25af9bd33bdebf116590d11444a7252c2f3280f27a45125bdbcb0daf09b728444b2198ad0781a0a4c54f2d7ff6b4959bb842fddde0a596549acaef945cbdb46c922790606bb98567c9c624ce6270dd63fe9aef58147e03c85ce1f465de97abbfb706186523eb3475cdc878feea372168079e02bdcba58064d2c27772543e2102f665bc48878ad42d4d036d6b2fd72435c0d30d5e332d6853a0ea1096ad29d3490a4161675bba9185e18370b3f3d6b557a120119b47c3686fe3fb36fe454a91d881d0721a0e867726c7d3ffd368d72f545f26411aa14478760d3d35a014203b096ba8d9cfdd136cd89cd3012d6f6725aca5f81535e27471fe93081f258e3d3997a4f669c0d3d4287c3437acb76e1126fb135a88fbb09d9aeb3d556ad97e60a01ad4f09adbc7c9cc1cc5f3e350571725cb9074f508598333f33383a4ae7a403c0ddff51bf1b700745d86676c6d9d45e5033df670c3d8b589ff72c72e70b69925d6efb7c70fcb31d28813ed51000e072ce1a83769a17bd06864152475effa4d54bc93f87598805c9f81d1b9ee59af77234e6fda53dc6b16a801940ff80354522f2905652575506e8d5a3507d0316ec0af8e55e668e6727753a76d2ad535edc45b7ec38924297367e5b6f67cbe4e3a37258e36e77aee2636e4d230c1e1b0d95e892e539e3d8e4fc231e546495f381d772052171b4df64b6f5e978c755b9d10e86485b929f9ec85e9c341a20a6d2a6d972377531e133c2454bc83e912ec733aac8995dd374aa0a9518bad26353b16d3d159d499b722ad6801a02c59910f9bb5ddf2efcc1da951466cda01b8b1b6a78de263162dea516bddce21ce08bc34c60e628c6b048dd09b98712228d4622f469b9056617ef4b6c586a3bab8d483ef2d3b28000a2a3a99bc2f8bcdc079f5d71a08e2fcd2ce3c916d5388afc398523a3bfafaadc7745f4535950dddf614f834e87af214e07d97a9315df2ac8286b381e07fa46058dfe59f8cf9b0c8042c884f8fe37722af855a72e779569d7dae4798ea2a1e96b4904d36637d947178e65f3f7e83572e9f17a51ab113d0b37283771c4503b25b9aebe23440bf128718d2e35ed415048a32196a6259306abc90b37606bd39fe155a04bb70f6910c29bf895e1044772249f925d23aa5ac4a0e183a8d0118b566e55de21180018521e07816231d9ae385f7c167b9b232b43a218ccca20db4a31de0fbc6349a6ca1dc9564618fa3b90ff6ddec2b36ac95389bf9cab909b2f2c1376dca3890d62c9e5a1a2fe6a2556c76872a3c3511f13b61e45338ec648d909709f3a2bd7bc063cbbde8cb3818777adcd70f02c739dd50c9f85949ee6681a49b681f12c14f40d857e9373af54b7bd46fc6fa8ccc6b2e28b8dc082806b748e7416cfbc7c902e44659a5a3ffc5a203b0b0c72f6e61e6155a88255e7ddc4d8bf5062fab1ca6cb2d4af3990c8f09fa8052e87726803217a6d7a96893c93d190923e4822ee1e89545224e506a9a42c85253be667983f1d0f66291c800f3706e6c693e4249b1c8526a61ebb0df493a3785ffa7372f0719da651fb771b555a3235e007e43852921fce21034462328d5cf9f7c98072d18b2e2ad42096d79660b40d9699731da4b7431f062163aedaef578106bb014179a218eaeed60086a47e156c8fc09dfeafbbec00eba3cfe2b297d7d42793924b275fe10e8c0d395b320fe2c43f834b5e8b71e5810d880589067c7b0060943b0005272777ed8ba2568803db67f0da9037064fd41e37486f48a5d43fbb91657c72ba278d8d70291d13451cc2d6f17b896a8b80bf3deff0c27c9f46ff9d7c568137974fd6403c9c3ff6b2244a58d15d70daee1e30c2539fdc992510a3b644072b72fc391104a0b10c549fa5f6d3f5553faf424943a8181df0758d3907a1d1c8525cf6eb445acf9d5ff6836567981ac5e9698f265e28dad34b10a5eb08037dabd872115ddae7827289ba322b4fb5c22b2a530de2043dc23880bb6ab78e6e6ad1a67141892a8b07565855387913249a438db56dd6e4029ee64de75f08160f8a726c720310f82aad1d19bbe45cc18624e460aad20d0839775c293933e3af1f67fa9b3e3b677b34465056083b9cf6b6eda16c43646e6e509f771fe2bb6ad68235b9db727b7c75eba57084e9c5dc81937eb659cb8588ad7718fdce056f1a228f72fecb0505bac37f79dc29346cb410fa5c33dcbaef80ed09b42b7b109ce12333fe82cd51df871891ad8de0d40cfef04832bf995da2d3e4e60331559738a1cdea5790e624b8ecd64435b27d14e414ab804f087c68d5d6bb1487be4b2159dacd47bd9ab4727c398040353554af3e119c182af49beecc5d54aec638e4bc962bf229be1967727d65f58bc05766e3a687cbc64392049de443048793e5242818fb4eaf78c087724da71144b4ea3cfb17f2d0070b0357f5dc6c6b29a1b6be8f8ee7a2d08c53ac13c1342af6b200c94b05580c7a35fa40b34b4d60b89e512e29b672117f1f318d38823dc789f1e9c64b3d4fa969f1ffddd1133538262e460d9aa02277081edb2f02f947f75ec4cd2edd0a9dd4d573b0330840ab4ad9fc9d1ea8a8f6aac3f9d21d72c18982f9b0c2e4558b0aac0911a4b9c4203827912e387c9c54078f94140d0172f7635494b79d8270992390c7b656aa40516ce2a9f32794a81380e02bbeb4c27269eab1f39bfc3f63101299af020055cf957b2bddfab0c14428ba1d174d07d972612391775cfb9a12f671def3725c4d5e1baa9cb35ae032ca8f347351cacbe0655cfec289283b5c478e0fe2f6ca19752512fe3d97fc8efb62e93597e41513b17288955e28fa03d00b2a26cd681e3767959067c1b6bcd4d340e48d2601e8cd745a58eb536a80c706a93294b771e84601cc27b69000a6b9b6e9c4c030d1593c516b0a8dbede660a2b09cc67b1d15c9c3466348dd172d4eb03e6e2151ec6d476dc729497899e164fe3534479592fb45cdecee986f6358a80be01af2284656e866403dd57410536c48de7b7244afaef696322de28028f4430b6e80b371dc2fee68615e4d29fbcaab2be733d28d69d4031d1737f1efe10c7e60ed3cbdeb808a74d0f59bc6b7d2ae39d2710e84a9b127b3d236524e2e0402ebc510c74afa94354ae5856373819dce1cfa033186169c40238955043fbb5030a7d2c4e9b80d04308af0272432dc00aacb149df1f41ec32f830b31070bdb538cc9a3a50e7ad9956b538147202fb402a748a222c92daf6d222c82c9703c0df17535e654c2e5c4d460d64b57224530ef40b1fcc0947ddf60d107797e0488a8135f5f3b041fdd0b8ac608d1572c348424170e17d9a2ee6cd791206c0f24cb3dae416df2bf2e79c1abac78c033a9b7b49649e0958ef115d9b53ecf8a63a864db94fef51757530ff46eec7b956543c30a2d6f4ea08f7b8b496f5b730a220e5de9e0af240ca4e92ed338193d6de72cec79b08fc97e2902616357433250aa94e20f173b961d27ac8d07e4668d01872e791fe667a536cefca96cb4c12a2db89a20afabdff28ec7b296957e844d5bd6936ebfbfcba91b208f1a7f55918f23ae1685f710dc8317f2705a5465b5bd42072368513b3623f6399aac3bc8769c339bd06a621ade9d5e3044a27ca36e88c7206b4a9c4ddabbadb6eaf3ab949b63a7cd2a63e32a66002b92c2fb186e9773bd06fb821c504b4cbb7484072b5b74e569cf662b489344eadfd819e28acbf42aa826ef7abd0ba01922848b772948ddab83fc348f687c7f2582d75b2f6ebd2f998760f27d23481878378a416e42f3d9cf33d9f7e0af76f8b51756cfcc7618ea0edfd4c024126f62c693c031e413e77a742f54c860abc087e861cc58fba27eb9b465f0afff206470dec13598ae64005a81a2545d30f1750bfd032812bd66f4456f16d4b98211bc0df690a1582a4ff68721c469cf9888c86e170f1e1f2904dde8c3924693bde08731a0f1faed8485377a93499903ab547944b1ceef5561778c967d63a3b86a412d8455977ff7fcb5b5123e227d533ce91a3dfdb1a625e91fd2309a2b872283ecbd1cfc4c5907bdf6c265f4ced320addda88f5914eedb4382c24ceaf5b09973fce3853de6b508db0228bef00f2d605b982a4121e3c87a151d3f61e62533c311ae68cd574ff4473b86697fbf1f78239c1f45fb71bfcc70cdb7465501a182e398a68fe84213f722625ebd20fcb783f49b209e6f0a286b3b4b822e512717c6da88c52e0184d153e573a147cc3c732c59930b99902bd3cad0a71dec269063b72e543d0156effa31afc3948a1d825656eaaaae093cde9ceb10f9048805f02c172d1745d0e639bab96a7498475fac31f479240a509a7a7f83934953e37ce6fb90419c99763b80eb8749749da6a04b494398d9bd5f96d9d2dada89430e9020bad7245ed34fb45d10781ab490aa7bb3c1b3ed2470694fc243d996e20fd106a60ea3297230faf0054fdd6181c5c9cc5cd2db808185e5af0c2e8aeb5d17e75bcff47232449eabd126ebfa704e1ba62f0d06fa309a0ffe76bdb42ebbe2e8ec1bbf92e72fad9c08254816ab5dcd15bc9a7548f509e760f81af111ef29e1b5753ff6b107284f094030c02623b5e53ea68a20a78ef4ca42ad12347f7630a4e06b8a86f1272746bc3e5def82a7347863509fd87392ff17d80f1d85d44febfe18f87d7983a36dfd4f03c7a8114b5e1c6f5e5960fedfbe2cd67c75db945aac4c5dac77863150bac6be6d06174d1874f2e4b4349cafeab14c6edb98cb40d8666848cc963c979720c5f439df383de05f25d6df6b922210d0c087e020bc3dee5de704f03eff7f60c999f186fc5a573e894367571c0380391633fac88a96b053a9da130c69ba4ce72d69f48c86b62eb388fda5330fa4de544e008df158de770bc174553a54b8eb572b5018402cd0bf5b026d0d3d911aae7ef5b687dd8d50cf3fab77e92f50909347292cff3a777ecd86bdb116fbf01c5f6f534049c5a38072e11ecf11c677fe05b30408274f4afef0a43c95a1cb05fb7028817acc9620fadd0ba014571f7bcb5907203dafdcea0072e2c5cd390182bf6a459a98d91ad2d3fd924e080f51320cb4372d530b2415288fa4ad6cbd98a15c5059039a25a1aaed01dc7244358556c96bc7212b867105fff2f0dcac41cd2cdbfa2494a79685f59897fd0cc04bd2ca7c7c05dcacd54120afdeb00d17b41b741cdb00a9f2be6a1c0422598b5bb778d9ff346643228651112e3c6d2260e85016f2a25fc7a8418f6a4ecd2df58b91507839eca67f13677caab3e119569535c4f79ef5e7c2db2b0caa8703dbe891c1e2b88314472172844d2482ec4550c631d41be5c4f282b44c1b544a416ee5c5978bfd642297219d07a3d30c3d19b12711b5dc061557a8c756c304ffdf65c72fcdfe202eeda60504eb15c0a7b6403956eddc9feb8ce4a6983f8d984fdce1dd17e29e4c8971672d51bec6f76c48d82b955e1eceabc5a265b984fb30eebb52526545bbc3ed0ce42f23e6fee871a999f3178e225d6fc1cef1a7d850693682bf2cc22be99a1eef8729299af1a2a1c0049f345541272359dddd0b571429118742b3a2806c041075072dc0430e366d30bdfa89679c701409a6525b326a571121c2dcbea4e4804114b0ee2723d54915db3fb3e9693f11066cb40c8bccd8a76e0d3f4ebc47cf0cf0ae8727e7ca638a5dc23a933cabc5435356c9a64884c4288f354ae91236f0cd94b7057d0633a8444122c072f87e64ec718922d771e8a27562ae986fa3bd53670736f721f3871bbaec508258118001b72247da0b652ea77e8a0b3a874e09fe3948b6f58d12412a7f381b17655a233ff331bfb9c0d41704d33e084b6b121ab8f8a9c1772295503ff6ee1528879552b01c0d40c630a103b8ff819188586645c8481fb83728059430562507083eb9421dd5f861a16a7ea93520a0ccfb5ab8d17e56098f11226990d21b1e7ff1ae64b14828cf598a741f55b906ec0fcf2511fa79435683d72480d833eb3cc0cc7792bf551a83a88cc40d6947da59fbd1f7e7973d38f80a1728facf355b0b286d1e515276bd85831110c8133051012bf40b5062472ce1e5b72a637077b1347725055c1589437a54a45ddc86b9bcdf66860648df284e96a0e726943b7addfd136af8192da314cd1f9f943a911cc85f97c8b492370e2ef0b5272a135c8204d19548a26b7205584ea7df5a0a8d0782afcf61fa19b2a6ab980a012725555829ce2e55f7aa87a89b7c47a6bada49394e68fe8a3205be8c415b390047f480e6eb7bb7f9f03dae7faca9d08b9b65afa64e015c75f6c1b658cb36f5a72d16b298dbf9f2dfa76a3f8936da2157fa0e4ee583450091a754031657df63c72f6e6c8c71ecc61679e040cc272e7b6468169e0749f701e2a1025e9bb95714a64a81f5383aae073c4f0fdbeaef7a01e12b199da75fd56cad87c632929a271b97252ca51f4a5fe6980b53d5da18a39e8b4c055e873151eb5470016406d41a89d72be8c62b6b22863a72a9638b1a7e91467d24182fc0ba95caab81ac6a447ef075a50dbeb8cad43d247000239b21f465a41cd6484064be1325722825be789a4037298a517fc183eae10203b4cc66b84c798e7d0ff3048bcd5bd0c084a2c486b1b43ac2233c1d57bfc09f8a11486d6190d14d6d9265e0de9dd4c467d7738fb591a7267656dbfec31c966961c07048e63d9d040315c8f1f2a6981b3f1d1b22ec29a72c7dab8f67ac72bb6c5344816cb15de9087a6280c6d8a087ceb60b98a3e39fe72f554d90b00813df97f551417ed582860c0c1e9dcbf7b7fe14dcf07e790beaf68472ebfcaad403edcc0b946303ec2e017fa65fa91b07a8eaea7a1c5fcb263f37204af4269d8fc77f806cf27bc5d889057352484de1ad2f4027a1bbda0548fa172b64ecf988bfc48d1c57aeff0a9687607926ef620632b1ba0bda457aa3e0f2d46600fac1948dc361c634d7d3b34bc5d6dc0bcc572c9e6943c69b5c6a2df63ac0d84467bd0f360d1c0c077178c30c63d239aaf925e452c413761dffd12f1fc53720629ecb230007bbc400207eff33c2872ddfb45f266b978ea8394cc951b393472a2b4e0696891031f2eb0ed2939e55ad53cb23104481e946c8f0cc7b69213296700b95fd2d3d80109de8fceca7d7dac6aa039ae7d5bad42402df7c7f4e94d67723109d16fab7b201def309a405ca66a01daa34c26e409a3b0def02e0ad209ce72dba478eba266fa9e50c20a68ff20f5586050ff8ea213d609405de51ebaa1f769109b59a4253355bae1d86cdb5a3e94bc1e769b37cea7465a34f55c9d20ab6540c957953d28c02ada3541c00b1a13360375c75a5534b0727dd1891f32051c1b4af3975ee0829b5a0c4c2ed0caddabfd18bf7fe47be4e340d84653d49a02a2067256f702a503f1e7fd190dfdd07215a5b9f99c873087864825ab2329fe7517c24f06a421ae78707cfdc827613d18dfc4a61f9f1627f4c3ff1316737a48d479c672421c1211d0fb977aba45b7c711988822a83d5bcd41dc40c2c0f6bd8645db6f72265c2497bedea87a41654ec6290cda58e70a0cb56a90f7203e836422d31a7306ace7f3d95785cdc3cd503edf0434f42807ed48ca01cc879aa44e7d0c360f64112c19dbc3f11c9177b8fda67c2435884ac05ff7d7e1942da976d263fdf56f5372f26925a59e5fe17fe7222b507d48b71451e31669b31c1f3ac93200fe061ae772879bad40b0d9bd32e99c97ef45e8490246d561437cc380d14153457851da8572b7587932b772bc1d280078e7fd0faf76d6ad7ed6bc7e825d7ace3fbd451e094bbbeadef1e3f4bbaac0555e2c85166d42c5461cc63109ee1e8293d47c86740d72a1a51e4e58f4a240e53ebcbe3bc55aa0d5380e35f61abef95ce01d9eb8aba372d41b82db122fd7a31a9a8f17909bdff8f2aa31ed82bd7f84f3a264e39f11c72df7d2c75ec689c71a209ab41e2a2ab6a7006a99b3c4505252e6e7b4e149b30c47739db0d657c153dcfd6cdde9b89d8613133740eafeaa5a21e930bd421ca242721b78df028b5989e8a0be08fb5bf2da7d16f5742eabfc4e7c4f1a970e3622a748ba633187530c256bc5c39a18915c3ce2493fc0fd6d3c1125b8fbe77545809b09d9d3f1aa527d0969de686a66cf0efc056fe6c83f201f0ab58f61bf69338d58725e838ce08abb77b649c236e471b5a12525119ca74119d73811cc19797a6680720755e3d0f4994d0d7f68631daa80c4f6382727f5628ff842ba005323ae829b72bfb669e7fdef0586e983d5f51067c2326fc8935eea3940ce7015042615eea772f40787808227e750b1ddb4c749b6648816830a799734c402f976b05794892a2a63f2d230bc8f6f110eda3fc158a6086b49bcd6fad6bb6bcff41809456502c45bf2b617647454c4d7f2e71bb63a75f905bd34ba2c3926f3132ba225e6c0afd1440b982e592d98504fb7a0977ebe3f89abc393823f7e7043e5e00ad85d5caf96317319910897c14ad4013f835c030c7d703e7960ca4a1f183c54e4e3c12215f972453dff9d0d3707d99d8f88e1238b8f1463f11aca640c2bbeaa268c24a5307c02e134c3f96d759a877b575aa13056271a2d28d6fcea5bb5f61ce9919d32c61253beb32e6651b2620ce806f8344fb929d4152a7e354aff4c6885b4eb11291ea572a5009e0fc50552ec38e88a50e1b0cef89dd77195cd90348daf4ead77dfd69372268e2ebcbcc63de95de06300fb093939d5cbd970f002d8cff4848e086c838b568229ee274d99368fba64874ec436617170e2e134054107e78f9a1c2be878d704739f9b14f5b0c56bdcced732cb9f9ace411bca881c74b50df8aecc1181750b72499eb0ebc966ea0e041638f97d172048b8a19025b654a25c591ed5dd580e59724701eb46551e16337d3d5db1cadff497d058c2fcc6bcc59c62da6b093df65a72e0ce850cc0c59a4772ad73cde00f930dfc72022a9c848e0e468bd22cb44cdb6850a171b50eb9bd696c92aef56edbc833d53243278115a4902c3e8819f6efcc72ab2b06c90b193b91c262fb56dab2a849ca68bed5a967f9ef81b5a13cf5171b728129f81c80360fbdd8d0bdf8e834479a3f3000a477209189021c4dee30f02272d0d062a897455cd2c78ae79532fd40ef8125e2e077b7702707c803951824951f1072909c48aa30d33dac53673dcabfdfe00a900d38e83c9cf24820126f75ab52f2f7400d46a9145894e61999a0b61786e97b7b4764e23067a9ad8789f487e77220dac4c502946af6727989ed05c47676d00c91ef7a56fec96bdff8545857940a88281805cf57beccccc07b4c463fe97e701dd4f3e4fcb41035363d49f62aeb7224b88056b631b963a8c95a8b8b346671888cb7d94fa1ddcccbec076194a8c7642350b17462840973b8cd7457b583c3b711b000999fd36de972a1c7abed9e52723742ebfc9b1264276275616812afc6af2ae482c9a0908729313221bff118f424630a32de61718cf42e04d56e1e36c21e3df5399989985b671e83ccb8e5ee21721c0b1fff2d9e15c7ea071139f4f0cc60f2fca7744dd4568a3ef26e5ba8597372bba65d7f33e3fc2099030c49392752dfe39a7364680fff2b74159bdd2321f672aabce26acaf0496f5c0859f36e572de1b48bc9131f35b4bdce4b22216a812a72b2e148a654eb4ddbb9455b5f146578f321a975140d00a2c3c3adb949fd8f38358f8e6a44ad87005ecf36b2984cfed53211a2061adb0e652503d88cbba3907739f5d9b2fe3d1be7d5a0a54a1f30373d3ea2682680dad61e7eba21af58a9df8472de57c0f0fd46c062beb5830119594e505036e04b291ef63e5df92af9e89d8972594d5bc6c858517e58b132c4b9744dff84022135eaffedc7df51ac55c5986772095a2a660017d368e6969907826ad15702c573fdc1d7099760f22cd53464577219847e5d0df27a0b18d97b66fbea525ab9315b22deda34046c8a344ff341aa72c2bc2fb7ee61d9f41b3ddbf1ffd91fcab1fd0c134bef26eb6be934815cfcb972943ece993b62145e20277cbcb18a3350db53b759099ab826b308873776d0f56fb2618423ddc1113260afdb9e556a6ed3df1d5bb569364307a8b1f7908d1c467243e043670efd8cb9627f589da07a0ae6517013ff6a07ca8f8bf37be77baaf9728b96faac787e33c54ec5f8128fc02829a7a654ed4f21bcd2a27f7e490cdd45726e6e392e17ec5ff0e98449af4966291a92095d30dc684a3b9fff9ae90fc15915f492fabf952a59aa99c79537282d9f03bda72c23d1ed7dca816a86bde4be894c2e00cc45322256154835afe05c11f0ac6f2adcf8ced4b2458d267dd94f488b72dfba91b40771d20d3b4e8a335088181e7ffd2b0ace79c87057c1632121937872fbd3c4c474bd863dbf09f26e8e11d8ef0f602277d8672d063445f944bf42932bf941b720c1fd5931d6177af9a5f7c18fb9dc3993030e116896278c1d05d73c56ee1837a1a15a33fbb57d16e152ba38f09701b83e9df2deb3bd138a82b3d1e93ab26f401e2901cb99a74654908c0244e29af318b1e7438507c045af30dd0ef472e996344455aa386ec691df89cb20c6fbd25632e30d5c207284924afc0ad27872a5ec2488f0c3b24c5025f250f6afa209a0b473258d071842997515b0286858724991116acfa416095db7d7ad01e493d5047c731c7d2ea62f9cd3f433b8fbf072b4186ea22a1e492918ab8ecdb4ac04bcf43ae6a2c406bcfd82f21ec48cfe3426856ca320061148f9c0e1c1d56bc6739a8363fd9501b91e964397c57dc06832493c0e96f075d11cded77bc4dba6462de250a35f87efc283f71a9104521096c3724056101a917fdd5d094caaa8bfc5aaa23b98c68e253d00b7e24f2f442112d972e31f7ec8cdfa748f2f34f76e941969e42721dab4b1e73d62b3dcabd9a79d040de1fecb0d82790b198be3cb7a069314423248e623ca3b081273b562070c9e9c723eff0628e7464bd1a3a3ba1f2df83e843ecb1681a8320b4d8c0762cb21f18413226da0d7ac692e392b6ab0516d635c4a7ac6bbeaff7ea33bb1d400f232bb08213e689f7444ae0ca3d9560274c625c4c582cabe6bf74b7abe979702cfb3bf5f4b793a18f3571c833c4f4a1a9903377e263887c6d6aa987b38698c9b34cbcd7f2720e666283cd1dd34e428e4cb3b6cdac049bbc5d0f9ba755a7f2f62ae616e9172947688d7c737d2048a5cad37aabf8f4efc9287bcc6e83a43c92710e1b6d5850f3fd3350157b08f51e1ecbb0c3296059f22ee089e942cf201cde4be9fcbe04e722f5d2b6d9c77889ad9b76bc2abd35f45e8b02ce97c78dd7cf8568db022cafa721016bac0984708da148148c9f1e801c89d8920c9b717a6024b42543e96505706fa93c36a267a7236bd3024cf1b17c10d6e944c435f7282db555618be38cb9662baa3468ea4af8b7ceb62a4cef3536ba911797e657d39d8681a34c25aa625e372bb5d3130402d9489d0ccbd616b57a4e2c1a5e4f80df6760223d8328b523f387263c1c348980a34b89bdeb5851742487975b9c7f682fd4870b25a7e5f3af9ae3c4f2695db2337ef80966a6731d827c9bf0d4fc926afd70f93207bcbea2e5db772aa44553635a27b53e3c1d48d32868bab30cadf9ae01784a0392602b11658da725a74d917b8534ae9662b2ca1c3fced7737227f1e8ea8eb62624074e6a64f952fd61567a61c1fc976cea7def9df739099c505112c583795b9e6edc81f19d7ff6ac8a615f95bd2e4a7229194dbdc8f17aee72ae4a30544d990999c7a2ad42bba721d6d5ea1eee245d3962e5d7375e48e3b0f88d31bb3285d398fc1111be44523351d9c60e0077e921d3f8b4011edab28e1d678734dd0d3254d33086f6aee208945cb7105e951c13640bb77e2ad3d14b6f3c6c6fc161b7dd14258193faf021a6b720db973756cbf74ae27977ddb9711c195c987cde9aa64a93e861687be88669c2bb8cc2f0d2d13477e5cf64742a47136e6b83fd475cfa1c4b3823d57f00efaec72484747c2961d06aa9f99ae76c073df7aa21d16e1efcadaaaa717726408d1b50ae3a4f9ea06ab640b7f1971ae089055613946ea63dff693ebcde5f8e8c4522b548fb6426bd244c4ffd7042a2b2fb6d307c936f2a0dd8dd813f5f71204ffaf3272098ffe4182f4ff86a06d5a1b8871ed16911e52062463470caeba33c518407137ccbc1360179de4f80f8afc8c2817bf02d6382b603b9f2aa3c27d0b1084c09a723a0525d59f314016e3e74521ddbd60facae0613fe0614bf292c968ef00fc61728991b60451837431e568f8c6ed4308ffa5028877651ddc9a2d229923968be572e90da01c56fefeacacd14d126aeb60f31c8cc04a4fe1d68e1ba4bb963d3214722dc749b3aa932c43efcbc0073100ae55ab2c265da19a8cc962db933e74ff7e72634e0b5b9f46e4ee3944096d05fd84ee0b147884c78eaa766ddaac7a0ea5ad726c0761c49637b5d35281e340fa97f7c1426550af3a44165046b4c82e834f0e727c0dcc31fd9029f9e855bf96f4117a4f4551a89caa0a4ca4a391875f3c1fc372d90703c4b4f9d1d7f35bc1589962a579698afeb4e25a13d9a154f535471eee2d170fa31a11083a6f1370d8d0792acf655357ccb760f4e2dbddad5f3263ea9172de36d2248dc1de366c559394cea8b3143c0c52cf1baca6caebd487e8f14c98177eb1b0d3fd874d17f8ed711b4fa40580a0fc793f86d9980ffb4d28062baae1723ed6d162cd08dcfc43cf941792736d3ad3f2901388c237769ad8d50b85719c7288561f9f55219eb5f549b051ab86d322d8aeac921d8d7ff66f485614563d601f4db06740a49a2caeffc6a3c35f8987d6c74a2f2d6be9db4ecf855571639cb723ba85b0f92f6a0f8873548adb1c8c01d0a1983550183d66633f83cc27e3ec3e4d646417fdcf175fb6c743070e1987a9149d975adbbffa62195d17555f67674a72774e2be722db6023aadfd3afa90d3d7d15d6171675cd31b08f2ab313521c257216183e28587862063986ceb6a077cd3854c383989487bb5856ac5926d2e5a372eb59b8c42da9a9787a3bee2d3d3bf224d4ddfe34e4461e2290ae4b862499e4720243a9e0a0f742b21ff1b528b2ed715796e48fb1f98c26660ba16d1baee1c9728d932b10c3a4c82362289211d5fd1640d7d4ca67dc9a32c262abbdeee9755c16cb6c1b1de34183f0fa8173e869e70f10eda6cdd5b1fd78b33ea64decd85fb372967d981492af8ed35495892756bb8d7d82b6681172188067df3f7ea2eaec417211973cca209a1932c903e5729d8d4fe6f3d2cc64a4d6e529cda43b24385a1072c233107a6b5fe17041812dcf059611d2137064245d5e3a09b0b8c430fe5a8372da4ce215c9bc878aae72f330c758d85e9c292856855dd1551c7e3112f5ed6a5a8ff2c21449c18208f9d49029eb74c5086de12e839d5bd8bdf5ad6ec0efc38f5849a3c64883bf6faa3509664f4c31cc7ac09b42300cf6c2584b464167682cca1e2069b69b7f8d41b441305381ae195d5606df7a2c7b0a34e07b0e6773c62b4572cea6cf5347cbebceea699de5f9815ca36c08c60828370a9c87c1045dedd36004cdb816f2191d5e281aefcf8d3bf309d245106468471cda40ec100266ce1fe672149519db0d225446eb74890d55c032c058d79ed29562fa2efa10d93991754a0c789c91452e5aa0a22a0e82e5c1306ad7090ee0d549c15f86d25848995ef6117257277bf42a83c47cf502d4a79adba434d929a77d0c29f757edb42209385b85728aefc2def5ea2fc860e73ce74366de175d411a49eba2d6d43edf76c0fbc049546c5c675ca2439f07c5395ff9e685cc2b500d4ccc4c4f236dcf3f97de527e435728d9130c37000c9fe69d59dda6deea5f332d9b415a92a12671c9af7e3bd50272d5fc69648f7ca647cadc4c066a4de725861076dd501ee23e5724a5b45a973d688fafe34a14046761cdae4e197a8007bd7f38ef81e237f70775a32af35bd28640619cd38b498c3e687361a5e354cead36380cf6ddc3cfd9bd7f9355dda426d87251c33abe9540caa8da968631735766c9c4e81b59aabace531f7f04f97353106defb5232a2777ce48f2f9ba360481335c54fd26a5c334daf47815617a8a40022b578eaf40e9478ec85315be6f63b01f74003bf7346915e383227f0b88299c4d72273f46a5a21238043a5c1f54747838bf9475b331b18353368fc0f0af3bd4ce4e484061cd27d6972eb458c7bac1a7b93912f62c2ba864be03797cac0439e526726b24c6c6c63741d2642b12dd969fbb9091389334e2afe8d801aebd19729bbd72fbe50d40d2b26b4718090b9de69c23a05adfd4a83c9c952b2cf93910980e7d5971ac101a6a387e9bb6c341dddfe947eae633073dc64e8eacfbd5ce7cda8ca972fe9eb60182bda80c26ebd3a1f33c97be5bc569eb2bb25ee282bc1da3886c37276561168b76a30fdf22f53621f6aa6b318307e062c38477152fa4fc7cd15ef913ef529fa9f78ebc87d0b82e5966354dbda737b8403bf949571eee121ada08634c2055e32819ffee9a33b157eafb138434469c7c0e2156cf8d650a4a0b7611a0092531c00edeba5713cf1910cf231a77d7101136b4633c27c9d82298a905f8f53ac8b8ff57434fec79d3533aca8ce8650f2566305e5c6968928b863b6be2cafc72be9377d84c2e0828dad3c66039c217fc9d730dabd91e63700981eea68cdd6700780f3925a7d960eaa29b4b3156f17445bd755305969f4aa3d6822db77c6cd0418c9dab0321b77351d0e111faf7d96313f57bdb515a6d19a4e31f57be0b348972b965756b65d4305ec965a93615f3966e96a57ec00d49dce23a27c1c545cb4d02fc0d8df8fb94b3c1216fbc9f444f230c4c259b6dc294a1bdc2933a888e194f721e821fda903378c7fb7c6ac0f5b54ff5ec0350a2d643199e7a5461ff2356d71c3b786c35b94993cf4305e7fde439546c0532f83fb395814ad42ec827f947af028a43faa410583701b6f89173a925270f2a3e71408408775185fe173a2a6c4272b2f4a33ac580fc4ccc2f2f6d6afea37fa2cbae6a79bfdd63adf89ffeb92b5672b92ad597ffa31dce5a1dd08577de592b646ef93d58972f77d2e2ed580a7e951513c3500f24ae3e40ee551ba5525aec73172409e444e3acb2b8861f5d8f3a9b72b474e1ec4044916ee52dceb31f058191aaeb8c0041f6a2b6acaff3c59bcb117200640c904216e269c9bcbca17b934f441ecfa36eb06181793b28ef1df22a4d72b215fa8f89e3bcc40df97898c87488e2b4fdc6f6d88aec9f64187bdf974c4072075790834d7cff89125b7d5c88a234f2dbbeed735385430ac5f22126d5c6571540ed25bb4cdba85b95e99a30ee6314fc54c7d283b6db034f834d6d2030bdaf323be310fef13c587fb569032937bc8907bb4d0b462dd36e21a5ac77e734d38372ad58b7141c0f9eecc54220498905b5c54842909637fdab385a6df90d099c3a72d6861164e9e44608c56dbbc536091be436dff4494bc4e33ef85fddea69882772b6d9b4ff8e1ebd29b6e24259d9b6e9c405abc887f61ec9cfbeeecf26afb42b454a01546f0f981b65768bc67acd9f7de24ffcf32d003d4fcee464cd96fd4f5b6f57a9ea468db52528bf392ce9555c8900cff2e2a2a00d8430dc41062d4f66a92be749fb190680f36f7207a6dfecd9eea803bff4401e22da086c7294440a46287244a401550a23f5ac285a07815f38f7a3e56a43d39b9e00b9cf4beba82703b072fc5d3d26b1449d67fae41f2ac3f6213aede3d86b225b2588bb554523eced50721eb715f9a29f9fdf24cf6cb8d0d86574412016804793d21a029d4f12674091728311c2b725eeeda6700f8180d05702cb673bf555025348e16844866ca4280a721307375e514f88b2cf8cba29fbce9436011165c2070062723309235c930b0672333f2fe4b6d2c251d8f2e5dec1b35b3b9c0e3fc41c400f540f14c3bfab2bf55c57fbd00c65ac5425e1254ee7ab92644b2d152d385ef172466a65e3d39811c464827272b416de6e87af768301325128387ce58cafb573287abbd45bb7e405cb645dc5cecfba2f658975c58aa493514547fc65499c905fe0fa20f5287188fb2c49dbde56937eb5e95a74702e8d2ce98b108b18150b4b3dd3d63081a64d63a538724f1e76724b5b3c3cdb7e7942677919e06ce28c4f62c145a0e7345b069d7a9972195cbdd0f4f6bf7a5dd9eafdf73c3bc67608ef68331f885de8b2c6dc9dd09f72a111bc5d41b8631472ad1df73c3b7e53867b6e5e3bc57b00db1f8e87e5b909726a84c8d584946d599cd5a996d97799a575b8fe164d61642765dcf0b421ec8815eb493d81d1b71a0b7fefd6f00a5dbc06438290dd2500639d03dca6f59e5d9e644229eb69c6a5d2e6ab04cf1fa526a8225aa4ddba8d698d005b107b28eab3c045492af50410d049fa69d7055df47636d364797d691b84b30a2c3ff2b1d50f1072843e37c4900e9054951b554ef54367ecf3ca3f6440d25cd61c6bdf6202cedc72bec2d9def3c1afe4862b7f4728377bce06304c035fd6daacf10fcf5245177a3f2882558293876e2214e12eb66a962ff131f0308c11c132cf87f3c53318394f72075a6e32935084749d7e5295c38996bface8bace50e1aa753741a1349467072a29cfb07075b60789310a6da1d4037567b5e41930ca77a1a182003551a31651724b9f53c807ecaf10f4063f53e72c663f3ea038659ca4533d1882be2f2a2d9d419ffe1214ed368765b821b1739c5a114ff6d69b695c479e240b2c83705c4d117213a6963b14bd1aa062c3c66972f50cb4183b769b5a252778a38b1b2ba4ae057282683e49ecc6bd7612c002cef32d2fbd2e545cbb5c022f68a686c37e32942a72b81addb8fd1a72c53256cb1d81f006413f13626ce30ffa373e8e32776100c172d1a3cd661261d4b5c03cbe5f1b5676ac9240b4cf2fd8bff6640c80f065f8227260e1477394593a9b20745d1d0a2b62eb2b2848b3e51b322ac950b79901108f654f7fa37c8020a88c36b71518716604a5603606aab2a3beff1c54dd3319ef6c7229539e3906162c2e8e739ed9361f73d7c3f07bea06fa40d88f312e140e520d4c4dbbcd291d478b9e36831c7fd862333b988c058da1fd19b9e6f989b373c94f5fb14d7537a01d19aed2983c70b06df66f8c5b555c0e8f492f1e8822c8fef3ea72554067ea5e53be9e77e0497a1f49cfeccfa70b3df5b3dff98c4e2d4136268f72edc1f16f1fe7a4ad36dea208c0b51ad069468bf58bb290c256026794305647245f2882e23f5cfe3160a27758e8dbdf61cfa8b307e318d3dd4745faa0093f7e31d975821cfbbf13c44d74b77e346db0e940200375145325b3cb0255d103f6de72e4832fecdb2262072d0b31f2204b13c21b47df32e8f918ccecac0018b9bbb34ef257add51a7bf60eda2b58dc0ed4dbd9a6059d0104bef649912ebb8dde83af67f08c7746ef6c5cc46eada7920eb1142a105060f299ce83d25588283eec1b3232cbf47c7205073ad37739a86ce0aa58c068ae0540d41b0bac99d3c83564072a5bb6711ec0a91436da352fa7faee4df3745d6a4aef1a52525a5f36f3f2f74e535df331cc9cc005e9b001520989d9d25376652b3eeee3ae9b98e656cdb8eec02b723e5672ae3caccf2a2d810d0801a318decb221138039b82942037f6db89e6b972679962bb04015e0cda92f486751d25b32a51141811e47bc52bcbc51e4784cf6ff290f10b5f16b18962d945143c0f05d08dfd17d74bbe9c017199f76eea9cd92784d715234e13c4bd21e17c1eec751bf243ddcb7a80902d361d995559d0103c72257906b55bc44c196e25430410e0ac87495bbb297ab05c07a5a0595fc3ca110a46243b34f6b65b8a0fbba543736a745fe34078ee50808c1da7793990f629c0721bab263c216e84fb2ced0908d823db1feafb994c698c1ce945d291723d9b9f72713e621ac6fc24e702844534500b622ee2c9292efa373ecd9fd4e7b508985d2822c4f4b1b4cd6b10889f7eb1cad497fb05b85effa053921c02b843e9f6a9e572f7a9984b360c26423bca32dd630f63b7b1ef21b39c3c37c364b3358fb59d7672acbae4f326f0ee2837d7f7b90c81eef84d444e6c327cc5527cb6974aa9ba36441c25488ee393b9484c5ac24952bd959d341b1ad1f1f4e0baad73d6afb155a87295a728008a0bd366a893997a2d58fb7645e6f8076aa42404ff120409182d13727ed8e28672598e5c46861630e1dc982d740f093b7f8ff0d33637be2d2b2cc6724b1180aa33dd9b52f492062983db8634ca56ff24a22d06f137c1bef03f724f723bd3272c5a3696ce9c2990d7fbbd6155b0e81dd7b3dc874215573ed317cf6a213c0723e4fa6960f92af423a7b2f793813a1a3625cd62aaa146d0966fb3d91f3c84ce26e7ff061cd2675fc6b3deb5308109b539027aa04ecac88b9cbe6875567071236b11844f9587d903b5888250fccde286e7a578741f25ca4fe70f06469172c36300706b3650d6491d7a494175ec092a921aedacd00620197d321aa9dfad726440514283f694a285cd962f36466792300bef925e76b2bb0983424ad4fcdb4242836c7067401b5a6e5fcd955b3490f5bbcc725ee87836c5e27c82d0a7129d725282230bfbd496c94b847f25ebf41c0ff5efa9350457e9016e892b085e0e1172ae31e487b601b866ead6f34ceefe612fe7b999e73add6999a37115c3467dc472a750aabf7ad0d4cceaf8b37301173ed81208bc50cc130ee91462cd06f180017299ac861f8cce48cc947d941b5265d328ef13e7c87b3247c9c500d540d3d7eb0d2e3894856b40c672ca9de5f3e1ca54117b153146c4b72503682f401569d705644ae3d61c7961df7facb03c0b3f62121f9e51ac7339fe015ebc79fe6c4a3f2d0ba8d50790f826749336b6a52263e53fd84295ad8088a71062a5de4c06bcb8c57230d4f46000b5c4465cbb2b192e7b97d1cf8cf5028f4d4150cb4153c1cff12672c8edbd60f444a273eb8549d044a39a4d37bc99a36632056c90e1bb04f68325605ed78f2b8e8849e1e322cb513304878593bebb338e1fabe31fe49b700e3fe23612bf24282271e30f05d82ddf619c9d526923cb44d5336e6d63d8c9eb0106b872afafbb81963eabf79cbb0e3d2fd65631053023ebbec46abc041f56606675ea726e3583eeca9707e7a15c048bf2032aae66d7315f32e056b3d73297de8d2faa7225f8d6049974693f7fe006330486ee9e9ed2fee01836f6cbdb4e702b6c89721dce897a04fd564e25c2d7a42c760cfa5738cc2fd7fea433f59b5117e604d696727a802d97eb537e5e4a7afc05b3bef1c8ec93cf64e53e2b89e6b069a8920a00595e0c740754d8e640db143c27360839054f072eb2d2c609ae5b5c3339bd83ec7205f398135d77dddf681ee2cfdc0eece21a8fb6a238602b663b73449b484c3872252cd94b2435f900fefd345eb0882993724443b8bdad8a6ac0e81c18f8a675727130d683929ae04e9b9fec1821852c1a6cc36bfc17a426fc5f7d72471a9d7e5f0e8cd15213d0aa644d093020563823dac5da6dc0e90c5270a1272f691a8fc3631cbd8d76411be589206ac14a2b6db50ed47014a994ae60dd2025443065242b652174f3219368a61f6ffd4633c18467a0c4b16042bcb03d0f527a9224963d0572c5d6ab0cd1772fcb997a20ca2822148e951a83b40cc75d30d7618138cab05572be5a674c370584d51af499ec070d70bb3f6d4e2f166e1dc9c8d9b78d649b536a4c452507df605395b3122e240f32e9da746bf2ff5e282e105dce507e6cb83b0edc014820e8ce4963a2788a83954cf7511091d316977bd841d4170d38bf188866c8d76fbb7637f4ee789407a2563fb59cc87beb75f646309f31a5f825fc885172ae9703a422c9466d4b85bbc1c9670868e113d98cf27135129ba0e4e68daf9e722652b2aa1083a3ff2bdd39bfda43109b799621d07ea8cc5e6e22af7861bbec720d340c87dd7390f473913dc62c15f4e096eed39457a1869bc919be325ba56b06497a993d9ae0ba824a7b12f8d7693bf61fe766a957ae4b2a4850ccab5bbb787214ed3d71a8f8e774a163e114e7f74b0d5af8a599e8bc884139ddb992348e9c6a8ba127f6968e622556c8c0704ab860fe0de7e188ad1170e638b1e0c049ba5d722a79b1754abbca5d3280c01c91360c09ff0e55312bfeea16c9d32c74cf852222b09d6e98b0aec04b647f35fea339fd91c891590c48148acae5269860ae452a72fca6fb51b727dec3865f025fca9bebf0306c6358051ba302f723fc4c1a2e6f18e43a111ce7724984a7562b207c79678179d7da6b589b9d6a734020d0ed8f647292fa9eb0ec5ef4bbc10b1877000a14b296ef4f86eba9f0f0088f5e277fdb267206086eb357ee1f24112b98728830df33eebbf0a0827a9e6a740990d4feb16d673f3696b8eaa3d31df5874b20f15031627ebdc66b5271b6b3a0825caaec293372a6013d0fcf12a846c1b1337c33dd27a8768756300ab410e2f5b07968852dee128e18edd96b2d5751defcf0b9549ee37b141ff11a1dee93762c40691d19fec948eaa77f5cbff4e939120c740cdd07d2b43aa90f09ab661508320ad35b77a6d8722e88f6128adf259730d0346f6826e1e558eddc8543ab69a4b4aa6f6040e83072b3a2641eaadb7ea6ccf8782b90b7e47e7743cc3f5314a7971a3ff983a9737a72d60159e0bdcae83b4817b59d0b4c5a432d6d151cc6cc2fdf8275797cf77d407273adaefcf91cbab44da36eb8727822b1c56566231cebbb0b9ae58f0908f7df7292e37f00cf677b3487ac9681499c29fcdeff691b0f0e51da1fd449894dc5f572d1fd76a103b41f36302cab0a7c9920f707be9e6a9e5d613dd767e2211b28a645e7c4ef245127ffd947de68eb7bda4fc6ec0e6116e0196b63fd62d438d12c9e607c43721489b26e2c10817e65924e257edf74d2ffd83c6b36bd722c37a8e46c72084949d780d89ddf61a10ac2bd0acf5a109b51b3f178de5d491b40d0f998a4721c959f0713301edb1c18b551ed0b93bfa46c860e015ee766408746a17f058303099a633c4bea385ba63e10612533e5657f0ea058abaae431acdcd41d1375eb439cd00e01f5bef5ade8754a133ccaeff4af84120c9e1f64069a21d1fda87e615a57da2dd19ec8944a78ee04f893d35b6b4279c99caaef79d06ec4203551be9772d371ac4827b45b2a322469d99f7b0a4345e15bf01e01a5e696100c8be8d74d720143258821e63dae4ed78669fd9915328067fd9dd19ada88720f71811c02ba72781d52daf9a80bee83843de875f0409f8622a5e0655c65d750c8fb808b96f14320c549565f822a06c4c5ceccb5b61fa7bfdc26a460b75898baff6ba2069f7c7270e1c46d37a840efcf5d6fc463c6575639acbe9537a084ad19e02a0cfe1f9272a56336dff9b8bc1eca7eb0c3a6366ef7fb6fdaf38b59a46d9174738978f7a5402a3bce43e04d489bde12f5767c314dfe4f7d001d95ac78aaa7a897fa8b590d7289b72d34a4cc509d32d8e3f8a4a8024f28680ba28cf38b51d1ed9dfd49892472c90a7713f5ca054fc92327a11fe6bacda26f9fa7ee3bb21f666b26b6cd17eb7245ba2ed7705c39322cd9a20037c1e007310bd685be86faef2bd493b579222f7254f1e7642417012002e30f82b1828fb1311fea2fbf97984d813f09596f8935729dec97f44bb4b74b7ee3708541b132fadd3bd340ea8dd831411498f832745b72cf0fbb4e0488405e4d15ace3b778d0d3526bf54ea13cf94eb68aa74263828672fbd2177a64f4c8b21900d93126cc5ad66d6b60868177dfec8bfe1f9cc0b2677263ac721b1e3753d8277d22c0890ed56dec36921ee1c688ed76c3e4ac6c7a50356500847f9d2438db56874519e38a46603234f6f63040701cc99a17b5e56dcf21d62c63704dc63016622c3351e3a37d43a4e2e327d9017f0a6b1232eddca4ef72adbb842125e1a7759dc5459677dc378004a5df38f27ee3f80f8087d9f7550a72ce23063a8666f8e069d396e37e7048deead854333401b9cfe449f3bb7917b200468269461dcf87abe644dc5e192c4843c62015a17894370becc83c5fd19c6b72cd9897125d9f7f2f6f8826ae2712975f1ee88b24c3678e36f85c10fc7358c27247ad1c9ad3a702b9e3c2ab7bbcd73eadddcaff176d4130396c3da41dcfa91172ce11c935cca97b703454421305e5d5819795d67233f8fb80766859ccd0d3576c1e736d496852cfafd248361a9fe0fed6dd14cc11cbcc9433335e102aa0681872829109845ba847511b290c672de087cecabc3f56de78a028be0c9db06040e4035d3f9d4b6c4443c2f4476378b887199e9322688121e7043d82f6189a06a7977259ad6118619a8168b37e176bcb62b63c5c03bdccb90e8dda35f8fa9305d07072ad74cac3f9ec61768dcfe66ee60eda45e6508f61ec4337778bd15406fe187d6537fe1ef681e9ba3f3b8fded2be27cbf1dc0627d1eb438d3fea9326f00c5d3f72c17249535c0f6405da009d6e0e0ccc884417e386874b7011f461b2ee12033b723edf7bffc11c7f9ca1695ea0d784c0cec081e7aeaf618934f2f214ba37740072cf323c40a96df7cf0a181ed479d1265ffd10774ec26bc9fa6e6a48354c346e72c52367cef14e2b7d6b8c8ead50bea09b3c344e5893ee5dec83438fe21bf48a7296d609b11d1ec88cf71b047c52bc4696e893e41911ae58e6ac9ef5092447f95472ca617962e72a9d98d28d0fdd35427c67d52f414287f474b8325df67ed72072417f576d17442e5b8a30330a2804d6a083459b4eb08e90ebe1e6dd178efc611e14f0a41842561e7cd08a5dad69968a3e5c79a47cc1c81f31f6c7d4a1d2ad9372ba57debd6b28bd56c609ba612e702d60a616798abbfda5b8678ee3c243c7b8720694ad2f53a36af09d12303258de52a1b92b1c412f5b95e9fcccd169524eb0722232a0fcd8cf8aeb7eeafa4a61cc28105ea90e347638f759e47ceb9a308643728afde8530bb05f94527f7e2eb23fe1cd29cb334e810cc42f9dc95bfe4dad5e7273457a8d674dc3c5a9010e5beb844074ce1b28fd7f5312f72134ac83dbc6fc724692b1efde3aac35b68f34b29cea662a182c98f8da0b15084fa38a0fdafc9372c6ab3d5de0570ab4cd2218084287db863883a7c2d5f4d8e7add8d64c81fb97727756776e8016c88d4318777a45da64908057bb760296189ae3cc1f4f1d7db6153da6b74e55a741b2d1529338c2ccfed218be3d140cc0485efd11eee1b50d0a726fb95a6565df1c9478191bdbd600ca1f256bb7530166f834cdd273b103004b72ef64f86dee284cf977d1c27aed329292b65ed9a093ddc79e79cfc176dfa135309846c3243bebe966a377cc82f595b303ac3b942cca632bc7419a051229faa972452893bba6b47e0141eb4062b22b3245ee14d405f9e8acb918b51e92e1fc5306ca73d52696508d00c51e8fb3543baa3288eac8adc785b87b78c6c65c6d270d71024cda41f687c794365e9e516a025cf4abbca9f605702b937d9fc695a5fcfc72723c923fbbb2056492b2eb4761a67fabe4bde7af9c6bc7f08dafd46f8501c03e538736ad7b0365594ddb71cfcd98a09489e0a311d74781e5326210427b0b1d26b0a9d39cdd4be072fca5cb0eb2533784cbf78dad7fca54e95394a8de6d80ab0fe03f921da12cdf4d200abad18ad462ada97588a2eb6fc66ebe0876bd8e087c239971a6a1c6b0d6d57dc2e3a3093961431b9825c7a8ff87b7f2506240d3e5411782b541729b5684b52ce8c1a77ab12e67e293b18a2c41fcea024588c354b87372192eeaf8f5abb212bcf43e822525036fc1c1c902094ce258e0f18e8c18f48631949fb0e5dea81737aab73b80b5304f240c6da02ae8195da512bc877dc18d5a14f1eaaa5361c66bb85764326e4ea0e2324733df302695fca6e959a4dc144da33aac526c574d80efaf96c51ca8e9f6f3951fed872cf78e98155e952895e30e1b04e8caedbb3c84faf35bf5833296494746daf8c603131775b2e75ba0707f24d072254856b3d94421af10b92352de496f9794d123a0da2027cade917862a0f4f47261b0e96b44420e399fcd16a365c79ca5c45a7db177ee1fa09bf8262496dfab72b5b22d579a5a4155d1ef93f8ce7c2036f4b611b7408ad134de782086dddec948319cc3ae954303c3a06cbfdfbea6092cb6a7fae4c65b8769fbede3b1af5d4972e48b6072da254352cf00f613c903700c0b93d8dc19d8b9c0da7cc42df16bd413d7f1a665b0d6a31aa739bf0fdedc7cecbc63335c67de240d80efe83a6078ca72359993abb4f1efea921f8fbd201be6b841a8c1140526bdfe10c6abf137a10072af77963e46770ddc989f1a8cf3ea7da276f0e4f38580e534f35d42b4c2095d7226670f9810656217b334fe881965380de60048a31326ff2c6a68a3e6541f7632a6e845966bfc102439500258f81c077782450d6fa2e61ea07e6902b28b6c7f72cc93e21d8eb062a2b927a54c7b418d4c3c01dc5a1512e69a2d295f14dd13ce5829f3a930ab56e61847ec57de39292cf30f3bac10546dc5144a541f8a7face072079c1821a4cbb17b0d52a705ee69b3bfc470f5809f08e40f3184206fabaaef323eb44ff43b8e44ce80ac19c394f59881222785aff8613f7c219dbbc7e67a052ae24078191367a26943a18695881ed4ad146af7d4932df2055e56936c5a017b72a9314ba8c419cee866bd5a30f906ee2926ca8ff99dd061cfd0f0f3c880ce1572fc7cbf08ba657fe4bd1f343dc61d8628d4d3faef573677385c35000fd50b9e4d2cbb79927360d8ed28d152c65361a4df398bca76b37b1ab62b999a8265d37f7216b7ce2869816bcebda26bc59ba85a13e62eba12836244309b5ac9d496ed3672088ee61026095dbce33c1e22423e6417b7f90119cc8fed82f72d21e9ac14da72b50708d7a9925edff23ab509c6448874c2653b2749928aec72f324ff3c37f772c346c21854f87f974ff7624063a3b791ef1e4cf53db1470c6db59ac893f45042338156709de6c09aa4e7a053d36b44341e3a837aadaa91370e5fa6f875aba8373bf3a1de69eff02bd4b519b945b0f2d5f1f58b4cd6a62685efa0c9d52cae8372ca2a147050350b253a3e2b4bc62c7ce0919097b138c563749e046b3fe1c384724ce81ce6c3ba8d40012379e9abef4e1d57afdc62e8a5e44bc229eb21c302f4726ad6b5eeeaadcb6f0d49c2ac31ba3a1e94c5cf7647359ff0dbde5fb766c75c4bb5bd703f9e2b72638e09a4d63c2b163b199b062bc6d17fb3b46d309f089663720ae31cc116d25e3f71fb9bacb41611228e18fcd1d14663ee2a4688453f18e472731e744c2fd7c3dc91665a663260893033f01cff98e11b7cd9385b93d85cad72ee2c36a903b4d93dedec2da60301eaf2ad47236b9dbaee92c3a29d8bef117e6aaf9a7c61b52fb878510c4318c2fcbf4f64c62ea357cd80626fc8ee5fd708f620a5a39e14d7a1e14d1a813d5fceaec1cbd3bc03d7b0254fa53a4b97eba1a87f72450fa90a9e5424c3383f2a2f26ccb3a16b778b4938c9763b58a31fa2e1b0a872daa8d69726941a9d84befc382d413435a8a31918d806d1c5fa32443196fb666a50352ea62dcd166e2240ae53e915af607f646832b8a3ef92f69ad71e59144d72be74adeaff3d8b84e0ca6efd06a01030bb861c4b6c09750f680dd2ef30f59730611dd5864b0fb73a9bb5ec75018c46c82f66d6537c45120e48b2f3f31b50dc726e61ddb773ddeb1c7e119779f11978b0e18d4826437a4cecc1c1afadd3768272ddb70c1b9eebe93a009329c9546b960d01c1cd00397b35431d03675eb2f87c72a99c7e8f4a122d8339001b1b463b11009ab4e60e90fb04c054f0295dfde7cf694aac5402c7ad8df2ac3c4d606c1638f3c6aafdff72ffc27c44f8b56715f961722b96fc7117b63c69a5c4c731fff253845b18ffdfe679a3e679a085056fc6e82bf80b3707bdb992d553dadc844d137584e0a312ea2395e8dbd4b8c778ef05f47296391bec1d89cf28b75c67e1b7dc688cb706d9fd4ae18684c18c0024f56720017a7e87db59d5159e116245114be9a8b8f8d83e9a2f9ea10f6cb3f789eca4137245d06a8b3a6b62df0474de9776b82d7a370a6aa32e410523ed5020646adcfa7230e231afbf44cd9d3d92cbac2ccd15f1eaf12c001ad994edf83d65ca95fde37272cb9d282825fc40ebd714c91710cf37920614897d6c510b8032340709eba649f4c879961133986fca950dbf17cad4a1af6503b4da4d11ab5bd79321c82a3569e279dec28dc3dabad32ce861df2a63f9eba33d0689266a6326a1a8ea8e6db548b618572efc3c425a86aacf1d508f6365330fe27bd09b3c0463afad291dde4d57eb9164d4535b0ad37440ede7e62090ffc3789031065f420f8b3d1698e4dee607f101cc296969b7a7d020ff13340a92b2c27edd5431c95bf01fab3e8fcc009972bf4ce62b5f94e92eca276074886b519f472842e5d42283b4d49de3ab146ccd727fbaf1bab241f745d0aa208513c16b298bb864645c3e242555b4436d58d3ae1604dec64122c09c32edcbfa936ed10da67722404da6f5e9e509fa973b1e6f27722797cb3bb107866596c1678f0824f3be4452e08bdfb83dd288902b99eca0e072241c52d0e3bb1edcc066d6f25cbffb64427a9e37b447b7628cb34fd5476a7106aa5e8142a54235959ec2ec45a704bf37f1facec36ef5d5e1d57b872fb15a1c3f1485277117dd8def0fc2c3d055abf6a0bd3bd9562a17a00381b2a7f176add272fa33d44ad4e763715b05d9d2363dec76020c225eeb98b92ec5234c0e8336197208afd716f96963a9255aa094fc431f5543e98d347446d0ef0e20a1e2db1e4a26114db3e602f89c407462eacc192ea234d82d28b266efbebd225f3e82c0b33b7222d25783dd8a0e7e7fa84a2f17a28ff9746ae72acbcc04395389b5173e3e46724e0497e5de2edf783b30fca3d920101acb9a69118fec8741f12b334cb1794f362c38d17648f9236efcab667712fa2ad0cf477ebafc2d9865ea8c5cd24e08ce4e4143d7d3e2904be2d223cb2ce919a16661926af15753600eb12e4621d4c2ce05e081f3074566b1acb2b6a03316c2e01f117e414e78d60cd654d60a9c3faf6c72a60349dd392c469ae6e4a8f1e04f9329635145fc189785f151447992bef00572f02e638272f87c0cc7a1c8a957d8e29e44ef94180f1b6be28aa1e5af9fdf95722c24c87d3a5b321350cfc380028fc21d78380f19c9d0db2aa41fabd736538d50c2e1e05d7f30bb3dd783c309355c74cb313678d74ca2f3e24afb2269b28f1a01fb816a16a5835f5bfee8c847945cfb25064e756357b2a7866452d9b5b4133e415f71864d3d324e795ba3e48341817e42bad3062f1521bc149fcea88a2203ba72019166f2faf3506f8bdec18991533d4e17e761276fd64f9e0295f3ab7a2957557e7180af573637df95fc5076dccf9a0ec80aec1db94a047ec07b1e26bb627172b4cee80987f391671cf16ed18918537ef3b374eb91f9f98713aa6488659e7a5d19c7c7aee3030c808ce43e6ec2ca750aff44e38d616999eded75c544d0b0ca08ee4a9f45bc75e45c57c2c616f8754958a67e327c99b74a9eb3c3d28dc3297d72162ebca452935928bfd74f4b1188e21d218787885289744ce37441bc4abd271df4d64c9e283e34dad4dfacfd17e1dd5b426e92a62a7331e8ec586488102a1c72232f7775c1c5cb7b2d2fca967942be1d5ec5ecbd80c712ca76b0dd09b798ad7221f100b828ac3352481d7ad2be811018d14f420bc545f1b73b79fd8ada266272131cad58128f167049c0abe303873e425f5979d77a8587573e46cafecc721572a7c0905cfada3f3414e0566549a8d5b21f74fd62480dc96c6a77f8e4161b791d76d33048c089a9c4ed529cfba0b9c14618af3d1196447b780f7b7eb32bcad86bbd2ee29a250e2adbed11763b11f31d12d381779cd70a07febe579a8850e5fd5dc6a2f6e97cdb447a50fbefe3c489472bd62fdea6eb7ff29eb3f4bba12e1145721f6fa756606c1506de53c262cd9c4c9be7c5497b30092c98bc07a96fee285c724a6d47dadd01e8618e22b430dbe1b612c63b10ec3dbd2d38a24618f6a2813f72ed07ea55c6d7b637bd3efa25084d52a7d6edaa3bf54c74d9c2f6318e7f6fb149d0d809edc36d747f87e37b8a4409c785ebd98f83a3fe5c33de752fcb65cf386dfbb72f1a4094f1dad48d6bd7b570c20654af1bd87e1ca958055ee816967fa90e8d4c54b03cfef90d12780d6c4a97ae13fb7c0f25bbc25cb281f8365ce00dd0010be08e5bcf67575e71007f646af17c41121eb9a2f4c3ac60a68af044f563215acb58cf87b4ce8c34b5a1f9d5622e996d12f2fc9c544d8c91083838a7f367eb72bafb08ded13f4f3460f1300257f8fd5916ff2cca272c12fab1116614fa631172b0a95913db2e38d717ae962903d371aac4cb3e2e1c6dfdd4b0e7596c9908920266576d8e5d115541d8c555e5cfa46200200638cc316e48a1b559ef00cc605e72370e77c1de54bb31d24b84f3319a5cbf1aa42b36190c4da55ce704145c6566723647fced31124a7972763a4ebad987d5962698b88ab612f02967f5651526f57268e4f7851fa291ed29a3b7a8cac3e1d188587fb90c29b5814a3f378a171a2172b76cc3cbdb35e7ae4058f1bc272edd021cc0463ee768a9f667dea766a59a057217d65e86333360acbfe4eb2ddd67e112492c7eb8cd1d20013fdbc80875d8a71354a2d2e40fed4de947c6e1bf4db2ca47009f54c669faf588a41a54d3ac643c72f3eb7277f23a48cc809f1430f32f22dccdd330e9a07acac75cb3672480012e72543d70a4790f52dbd16d0ef253a436f3b68dd5f5c7f32271340d9a10b6ac3d72423644e5182e4a7e967519ad9a156f80a2ac713fb82346a218c5736a0474a5726394a8eee2a6922d98021d647f230403976c0edb839f52a4a5b204e8e6f7f2725a9e102d20bfd441807cfc6c7e15140c23ec1d9dd957450345eaf4796a663e0c36f2a7efdf15b7bc3efedbf049cd381a9a72425d565fd0b23c3f92d96f515f72254a0fae22d05405965c26ddcf7f4342e4a68e129ceafa2c094ef78f38c3af72d96050a1fe499c592d89c1c875bac5fa4dba6951bcb3ea321ab81fa7b70c966088320f9df558be2261ecc93e126b48028a5502ad0030c2319bff4f02de20bc72d616cb66d954bf5827c5ae8377647593d7b1456627731b28f1ac9203e2489c325a42bc422901385bc61628e33d6b1772f1ae65cb31238120d74dd9d4da0941729abe50d18632cdeb44784f5306c9e350839a7f1a7498c8b6226becd839c00b7246d20c82be7a7f61e8fca6e8948f7380288eee81ae0d10fe5530dc9d31efab5f1e9f9d73936da67def7969861e83c31145e5265f70afa891b69f240c6e2ef072d6bf61fc1c595b11452ed869eabdbb1235f9d0a41598d8fb0e400f7432ba261ddb3e0e26b998d82a2465d8e63dad6fc1f9906e1f69e69d733344f79a751f7872c4ceeb77e63a77f20036352cfe10a21595d98d0958410af8323f7ad4a314f70d9bc1965f5f11be3f6445ab54d5467f0d079ec9d454a1e2a11b5df304af9fb172e51efb84277d42e9634c0c003edb46f795b2419d7a223fd7d9cbeeec9ac9ae72d0060f9307551d7fac5ca14c7904cd7c698c6348bf9eed585aeeb5be8be80e7284692e25b340c2ec7bf8b88bd312a2335ef9fe54f7bab4e00d6e740483cf6f2d4b93f8c5185a98dd420a6aee9324c1c9330de257818c5cbe6e274fd3f0cebd684867bf7eec9428d439d2d9e6c35d71fc20f099cbc20568e4db04479b5e58e05c7b755db60578b42590cd998d1ecd4123fe596dbaf04f5799687b7256314df739c0b5ebb5a3a3006b0da4df9195f170dd5a7440332c42fbcd1efaa626e37c6771e1f6471d6055aa2acb1586a84690d40054467ab0d416833ac4e3028d52ec2a7252d6815d419755f9f289a65dc04c2c6e895979bee2ff2f21d5cc1f6225668572fa8a57e4494a74a8c79de89430314499103526d51a2c4c173f0bc70ee9912c72169db1f4359da87c2bb26550511a12cbd54ee68aecb10a2a18604f6855ada6122e37a34d891d5d5a75ec8877fc1ef1085d8869b99ad1dd939e55fdd8f18e0f7281e46ecc42d90d01a5fd832dcb9e849e39cb3c0db1da05d380ce118d7c52172ad3c8f7ab9a44ddcb1fcb51d78650aff487a734fdcc58c18ef1e9880b6127d1181ce82d1d043233ec0935cad1bdbba6203690edb83257b161885ec283e62c003dd94a99c50a62863bece16b4f6619228b7d3d28729a95e604844e04604023855c31f98ec2154e610a57a51637da8e1f2fc485c4d6e2d6740ae63e624a4eec6058cf93e67094d34835a1cd465f8c8b5c394846db6106a85c48e41cdf9262434172e1f4531cab2b54f319aa7091ed91071bb0c5e20692e5f6ce9f54036aaee0ef16c42cfe2aed252938340cc40876cc5b9bdc1d82ca9db62251c2e8fbee73d2f472ffd9994ffdb6a23fe0b8fceb9f5de962c367af820f000bed9a4f90bc1e83d37207b35dda4d0f4db5314fe6d57152addba66770fa69706514bc47e33c933992724911ca4ab075968bc5765508fd3ae926ef06e480e05133dca5d81a4058e7267230ea3f1ab472d339980b7fa6a748c0f6b2b9f669dc6f1a0540a1834b9dd6f9720931b7f074608f5895ee74c70fbf3d8a5d204cdae9a5856b8a25fef3d6d8490e34dc9ab66d65a67a679cd7941581e14d597f1cdb70b637a576b6e749c9944b723651c72480ef8317ea0eec5d8b665a2a03d1075a31b753a35833bbda795cee7201683bc0a5661c55e3927712b6b8d2692a6f64ca3cbc3a316c108f28d4640b728b9e6add6de12fb534eaf51329d2523b891349400f2bc4b30713697047da7e404b709f5d4fe1ca22cd2e13259dc368d2bbfd3d1b060683f1cffb491cf5b0680b92b7adc82bcd9b38f801a592857cf91b66ab5f9a80d170f569f7f22c94af4372eb086e9cacc188cad4caac190536e2da08142333e6ac20b5e5f47afd266eab4c8589d0607acf5fe790d23e80f0d10580816d42e6fd55ae4bcc1b58d0ab44b072929a8a44a945a4c2b2f096ba4de07f2fa18780fa416503d41048d1adeeab3472d038dcfb6177bf7875b166c96876a005abb3433e58d2e30a47bc59bccc30d272ac6572ce2654f728dd817397f77abbaaf58dd69f25aeecec0a54ab9262a63d631583131539fb8e488b9ba52ec56890312cc49899b6228c442c9d375b44049e7224c3330de22be7349d2aab4f5d36cfc84e708ebad1a5516cb9950eb410d4927248c3a8edd28b2f6e71ce5a316f7ed8abbbc182713cd7cfbe370b2a5d47dbaf720a79c1d4e3629abc981b32898719818206e0bb1d60a42f23204368c0a8fb4c720b58f8dcd3f1094f24930c664ae36a6cb8f7a25deae1153dbfe96349cd85a90b9159a555dc1fc4d6f259b8697a1c03fc810226bb1b7fcbe7596901cd9c11586b780543c7bf016357cb0992eb0efe50a4f285247c095f78a1e933dcef1f2d793799489ca50773b2c89ee3928e356ea3429df5b5ab840ca462ac59d53155868472ff37584ff2318d0e3a671af26568e8c0ad468d8f90f69c8173c4395cad74a243dd8b84034108593b8d84dde6fc9b4e6a32f05b3039b6508e9d562de5678ac4294a7f6d6afc203f35198f8ceb1935ef3e8429cfc14505393bc2868fa7d2bb747214a46bd684fbf5259cf43e579679e6ceda7235f0b60ed324770b5b3ac6692b4e3b1c6257e438eeabded7fbe53e10208116fd66aa5dd30a5199e036f2ffe3b964cf6475c6c7c53a710d45da31348c52f78f94bdc8e0c718bcb20d92e06126285c75a7971b602925c33646909304a4a5830dcc9d94eb52d780c5382cdb578ac772201837063a0088bae4183430c701909afb76bcf28cb352f2f87f2eb26f92687201ffc46ffa8c5895bad195c0d30e337fb98a133b0919d944a52f04478d2a33722716c2e1bba1d0e0a1423b35b586bc182410fc8695f57dde6a3653d345f12e727e290cd69c00ab0d8f34321119d00b9eebe7cb43f77383c88c46431fe17c7872a2cae986e0879c92b04e2b7f95a052c466d6d04aa7e0c489c6ca4730ebebd872c3257fe8241ed84cefca1c5ee1f66bb8464b4286c561e4baa5a4ff182a8fb418dc6cdaf8ea5ee0c5ebc13521b7e7eebc148036d321828af38fd8aae19ea0795e1e24ccd8aff6ff13cc559fb0006310117d7ea3761c92a560ba0f92f1cbb8a653a36b7685c9d556e21c0e36e6db896582c374847ba6dfe92136aa9d9388a43b72272950ee1863a505b1a81a3756956b2c365e55454bde5babcaafebeda3b4d472166767ad6f0cf84b7490a05aa29195761e32fea41ca783a2819d57430c1e6d4628416e56d28b5db1eae850762074f5108f7b5936b47f1c48adc62f44c9bc1f5728e6fd9a6249378b77cd24dc4dd1e3692668e162eaca63fc9821d251d416db52dc7413edadad3bc143f8a7ee8e8f637416c29bdbacd102d4c1268563b5dcf65cda379cb04cb042ee1b13e9475a455520cb35a03291e53180f3f81aa1686fb94431008a4c8bbd33241908a847fa9d1b33a8c2d78f6b6811bff1e28f7b9f852f72977db6f5405fd9296d0e23a0d99011fd8a67f7755a485cc64bfc3436e9701c0d689c2420b3f9edb5173075f9f40ff8ef6123f3b1ead9d5af45e49c832a98c77294e874f3c91a9e1d0d2b55894f861eb1ededf32ae7fea02bf32ba7ff490e1b724ecda83b92a115d9407a813d258e5a463b0f0427e70f8e43d2afbad0bb57f772803b0556f92306a0e82894580459acbcf3cf7cdd418b4fae01702756623ff32b6ca1f8ab6b799280635ffd1653afaff51b77b7ff18afc0cd173fced9794cd85df197107154245eca72d3200741c17d7dac0d1eb2411aac066704c35a9a3bbf72e524790d813abf860c103e4c954102086e104c27d8835354ee4f6d4a1b4b5d72a2ee5b3ffeb39aadd678457e7472f0e709e27c4eee3c8863ccda786be970247287a399751fc766203ae2e685e2c25e5dbee8f24f511e3ec9117934a110b17c0f2f105d979a81f65f278fd77412a177df1edbd37e146c5231df54d4e6ccd4bb72db5b65f5b1d10a18caaee4818b718f6cc1ce7ce9965ee73701c65e6ea5f3b372f96d750d371b7c22a821d96b2b82f47d3ff3cb544616c4cbc890135e000e8c15a748ce8fe73c757e4e2ac6961ea369ad5db83c3249f07141d5bc411df1f47364368d3cb4935f7bb114b8cff987ee88a72db278a59da752333dc65474f2781a626cc6b35d5aa9ab00de7d549174fce1928f385fd30587ecd88c400f5bea275272a1b5387d858bab9c9c14002abb87af16837634668078ac30004a7da7c7ca26000be9a2ab954fde6e73353837942d2b1040ba532d379881b639dab6c15aea2b721b6072eb0d7337505b7420d1e7f7eb207d621145731273af62c7001f38198772900535018a6ea2ca030f9f7978dbb4973f551a0ebd18cf070a85fb703577361b1acd7a96e5ab6025b66f0f7211b51e7fb545bad076aaf3d0271414ac1184767210270c0fa7523bc91bfde740c61699e0f91375d5d4f1af970b709442cd0c257253ee1b3d9bac58bb4ac7accb115cee775726ae719b3b92c887aed8071871677253300c956bdc5c99997ab660941307a3a3e3c41acf720fa670565a5b1c7dbb72d36da6f754215c458a63b2b27c07b4ca17384207f751d991d1a37e05c86d0d72a6e90cdb7dea4acbd3f9082bd8b2a1372bfd424ec2f7748bb105586373d52072ec11f25806a93a050791d8ab220df05b5e65ae32841575cdc88bb068b408607233ec8341b088ac2fcd30d49ef6e9f1784913ade552aebe8d2bb47a1dc5e74e3bf7170ee61f15cd599161058ebb9e548492abea899f2c86067b70a0dc27631648dbef2850e9b1450a38f88da37fc60a0ff9c71e91c477ad3702515cdee96dc719cfb73dc8d9830e7f1f39ffeada0bd40bc98faea6d1440f79cdae8839eeeb795e3826f1f900054c7a3860b91f63ce2eaef1ea1e8980e432b2f9bbc4dd2cdee943726b18500a87cceb87d9b06a2f5ae1ad32f1522f46b75c903d6979396feb35729070046109d59cbee3e6edcdf8613556a16ce97e6680c34e7bf4ae6f6fe096726722137bd91d6a27efed4117a1b419c076386b358b510f280f0f342281703a7296777b67182f0ce66f7671ccc862376e23ffde3a80b6190d1753dda2c051364f3411ce80e7ffabaaf9885e0dcbf07654a7d24fe281b8908b57277b03a3bcfb5ac1ccc0522fee36a5602c5259c3d2596351b3cad399d3df4a36355aaee55d7272159c015b807b015fef70114dae2d5e754145ba64cf5cff8d34512da86318077233e14969e6ffb0616ff7578e359043da95b3d26f2df77d58d3c50d3f39730657580abab8bfa4527b705a5841a7eab2e5229125abc5afce1b584d2fec87d69572752911413b33e5169cbf07a331a6d2a8f539dc5210a5d5f347f0df9897a7947253f93959bb8a7b154d7454a6b9a60732f672abf3c895767d450e48a19719501af8238540779fafc4327986d740f6b8c9fc007ae0142b12670a8a1dbf5e846b720f2926154db9917b44687b2f74361ed3006eae99e86897899f1d9d5676d9192cb94513448e66b72e35453c63a1cd7f257d1f9c4c22d4327a3a5f518a34d5a624846b12e82f9ad547fdf57a9b56b77f4f1a2832936e368614096b3894af36be729656bb602860714f424f891c1e68da7cfbcf01f2f8d4536c77053241ca7725723e1052dfe85604a518668ea53c78dd00cdc3ead33096073a77362449fa60bf2c00dd7c11660e2219ad9a9c1e9ee3158f2bc15990a2fcbd0538bc5f7d80d96c729a2ddaaae905a02b602362f08bebabc8ef51baae4808e4321a13394dbb73a1728155fe3f1d424c7cba88f6b57cd1c9494a50a64fb879c0cb4cf37d4f71e298723ef1e777f5503585c12d2780ad91815102676938c42b6949f3eb89feb4f20a72bd202b300c6bbb86391108990fd7e96cdcec66bf7413c808c8d3c6e7edee3272e3ee3b17992ff6e300e9c41955bc1934df2f3036d6c0feece237afe7067ef865ae4fea09e004832e79c97ae9a55909c9cf13719aee210be151b840b721a9df725341cb4189386cc8509897765e7979f650fbfdc6f26aa08bea4ca667039fd11bf96e7fad1b30bb728ad35d081350bbdb2a24c56439eff1be298ec64de3d8071b9d36643a877a094b3030d6a7e09d95d83d662dbad38391a29a35e56c25434f726219fdf385ab5bdaed5ee3cab46aeacaa0223a0077b5bd94a7c4520bca8603721a0db6b38ccb1debe52eb3dfc72ead181cfef310508608b319120a9e0c3d85720aabe34a01bb28b2e70de35d5e76ec3c6902b6fa8531c729f5441dc67f23a64c1eb865c1e64cbdba2784f556543652267e7b9e3601dd0f2ccc98e9266dd16b724b426e3452cd145d2ee9e936ead64bba4f0b74513e9b846caf6a10aa098310726dc8f4949dd778fc4e1c4d18e596d1043b2f56b201e97a69298a3ef304519572b073fd111014aa3c210b8ee65d5e0cb099463edc9a82dc37cd04376d1b14394f68ca7145ba687900e383b8bd8d2959a173613f5d2ecdc7ddc324613cf265e95d1be5f1262a7c3d33fbd526fbcf2d05c08edb05abc05585314655a77fe6ce6e57db6c2a7efc9e7e15aebe4ec196d6bba0fcf1299f5a005b1c10ba4d7d813e047289f67045db3fd30676c5f10c86051abc7f1819997f2cb9fd8853d94f8830bf07b9f7aad5f568ea2674fa8c5654a747a6012d36036a8eeace44133ca74fe74f7238fcedc9ae26ffe3d72493414f003164c33641e80c6c822da2dac018c40b4657f91fcc5a6b8520e6a982b1dd7aab49201c16aae7781672e797615b1bf68d717261053a69fe96c29d570dca4241ca63b1320ec042e06289ae53a87c67431810136fc18772148f6110e1088a84e8a76c6b4a5080129798b0cce2b9d38fa5e4aa1bc9305e797e4816b4770f09a6baf68a915236a01c80672382b7eddc87f4b7b156810ba00ab274d7bbfaaf10bf3985ecc2f89e674f877e253b1bea280c7660f8524f23c04fd1ea401e35f7bb37c93fa18209fd47711b49fc7a103daf5629960c72960fed5fd251449ceb95b868f55ec5d47a212ffce8c4d311509acd9b57799a0a44cc0f9d11d4877332b4233b419d9f32242df39f104ec67d3df717f25e62350c24450810f2d71954b8cf5ed53f90c183348326a76a010966233244a590425b72b1ffb507116abb5eedf5ec8515e56189fd596a3b68560b3a33293d9445f733725e37eb2e22fdc235852e4088fe5c36a83c5b8a75238e4787cf63376baaa75d7239f79e60184addb6936b94b1b53200541ee245555732f950fc59a698c63f6b723a733a94668e5fe58eac7469efa9a69d97dbaeefb781a2fe4ddfc4ba4a5ff57233836e0d1896c767a14eaa320bd6b6b7349bba8584e570e034c3611ef9fe6272bc409c485edf1434e66c7758023413d19918eaa9035751a73fcc7ba3e46ed955c605dc9a35f87424c62a0a53af0fc599a7e76350b305f54ddb055c52b70a29722cae21125869a488cc22a22863f80046a91fd9f8efda46990e443b6b51d3486656e5adc64fae42527c17c426c1a8367eab1e7b20f30d409d0e54ff0956318b72cf2bb572748d037b588025dfc7d8972d28a97616662ca6381804a07489770d0f9342e55ebb51cc7d9392ca4ec58272f240d441275d38d52fa2eeb3e5601d9772605afd129627f9824b1ea22c293883cba7b467d264fed159ea96dfc86ffe036224019f630214ca2c6a805998666484081b894e9f7a3dc5d2abefafe4b5ec615423c16f2966b9766a73d40018537c9ca3f334b80143ad2e0ba7d918ef43429d092c21fda4bee3785d4e993ffddae6f19a12c6925e3a04a3e0710f9049dec55155f40e316b298cc1499d9ece26068f1d2d14305427bdcb380a926e0c5bc6432872862ac3efa8e5915dbd15de67cc3a97f7b8cc1f4d70159cf67892a576a5c5207253efc44a24e13ee0da5927c5df611f031c108cd305119c2a2ca5dbb937a4ca72c9cdec4b157b87761c398200c5c8917498bee62dead7cf55a91788c1c80de85c6cba880c9817b1471f13b07f8251cecbc5e7c856a3e95e8e5eac35a343a86072f41e99415df6cb2117cb84a96ccb1f8f36b7f52a889d234f7c4f7ee786747272e0dafe927a242f696c44a91ee276ba8096cb87a83735c7be45649790fa466548382c88f00439153ecca1f0a3e894ed7d11f46af8b8c187222321aa5c10bf7172933f8c56e23e651d28bc0203e7ced493a85637026793b04a224ec85abc2f9672f14d0aa51a6332119c58163861e7f48d650b6b7e489660b9f0a89070aa352772a029cc97eabf5a3f5056ca7673d3b71979cc16403c932617ece8839ad228c12a7fbf48de355f4709f31508e7b15d5e2746fba350dd9e4d3b3be8ac8aa5a80372dc1c12e13b5b636feb4413e4b8872ce057fa4e819f24adc9a014ff5f4d955915a41c4aad971dde9fe89b8c72cc1efb6541facf867be7ce9217f6864d633a763c1a786b478f9c6b47d578180c8b60c4c145449977105a2757643f21b072a04c20f7787d5a14005b621335f5853181e85171cc3e4b8c74883e101aa7824c15d72309b5015a0ead3506ff06d30f741b36a2ca98ae34b8a19b3345bd91e2826dfa660ee9738c7d8e0c3efed7d115f1fe0602d4fcdecbf1225a1fcad7d801592c3e72563199cff9284531f8f47ea212b5375446f3ddafe8de43c844e082477bf7c72bd2f85d36d3466174342186516fee04c72ed453e74b6b5c4255bd2db899bdb472b488c3fea012a5493ae64bdb9a5fd51af5aab3ed08cb1222220850a2c7a9e507e860364246ddc530efd162cfebaa62a44fb999f6c99f6ea58d12b6b6d80dab72e62df22d16881ef133d5a4d46e18c63ecebd238c8756f5e62fabaca376d28b3d7f49a43487ff1a8d424bbb0354803652928f1c6c10b0e967c8794bb6677fa17219586127147df5f60baf11299f44cb77d9817d2a8ea7ab5bb334796376cced7240daf0032fd74be4afd953c49cb37e5af50716521ede29c618db4910f2509372e8411ac31ae4b77f0aad51c82d362be4761a853c9f4766d3e9a1c777a6a03b523c9a16bdb52db91cd4d85178c08f3bb5bb7b496fd72a4de2681894685f4341312e81f9867a233e57e5b0e9855b0dbe36f888a19af23ef240a3753cdad2da91727975789d3e2ad8330576b952da4b42f9d06b6949b13f54c26ee981809e24c33f1e27fe2b1e308aa4acbb9eab73d1c0104f7d71e1ff6dbd30f3f514e187a50c45970c48c8fd445e692664c643d77d4025def9ce359c4a12f091eb199be00227728ddd7c29e5538e2fd12b4a359b8a231dd7db62840cece1acd30629c9c0f5162ad543ba155928526bbbdc4287c80636c25d5320b3a97b250dabe52b6cb7bd81727617d8c68a90881e43c9f8cebd8f1a61e68830870687b4c805d4386ac2c6c872da9cc46409ce13ccc07be32b03e893de9b07db58b809d3d9cbecc1822791a413f9b892f15aeedf8622a8673668ae88aee32a60c4f060805001ecdc5a08fb257283d85e91f24c310f105661858b577cf4cdc58b9d3dc229b94c7e64e29c714d614c3ce9b0f334b5f8a9fe7e79e6eebe1c26bbb747786ee932112c60519cdd07158a332d426b4bda29b57230747359280f448d27ed287b0824caf91fe9a492e8723934cc18bd3559b2acf59b823a5b9b129c150145ce14bf6e86dd924a6becf572c56b7c539ecc47a5df9e5b53189164bd0224c7f92ac3fc8bf934177b168d6c54ee82b73a66d46c32448c434f82e286a6a1da4cecd3e67f74e1f1761cfdf93972b203d8280817ce857cdb915b2e4c23b7e306e450e6b2aa311e96c91b0dddc27228034a111d1d2557ad4bc41ca4534b05d9a9f859c8a9209ccebf13c5c5e71872b7d5c745acac8698419417a92ff7253c607d82d2c4e7cb93caeeb4adad770772e9950dae577a824eeafc54e561284716b72d51ff79525e90d1d989803fdae87275e8152735a493d53ac00e89e6436f27f341178e864f71db6274b9fb67880671a88cdaddff2da7c0bf7f8645ef5b3318b6f7f2f304c6fa69abf05ae5896010724708caa9d5a143001b477184659ba4454a6d84958ffdadd2320568defa3d93189aa7ed4eadc1b91ea5126a4ce6588943016aaff43cf608aa9814168619b19772ac7685cd7f3a6c9d4acff6edf2e9e224f2d12402876165c94f69eb141dc49e728eaa99b59b2c3a156f90e489fdfc2e48c806904f6bfff79aacde592534e8d15c96a4ab187e95bbfa484f55c9a67336623194761bd9c661162f6cf166b57f4872350ea4ba2647a8751fbeec7f81b87c3af5c4fd499ccc8ae7aa4afd9c3acf55695aeb7cbd9977edc5a5a112b13c498c714bb9f4828fe8b0d9da571f864b6a1f72d57d54680849d40e88995df4c4faf15b42eb44778f8b063d441e18e63ac77d525ca5c2f18b19a6954298b208fcdd007d05aaa055f7f899b5f1ac08a4dc24fe2a3a76aa4bd3dde7d65117b84c35269f0b11ab51f716c0e2f41891610f14ecbf72e48570a5acc88528e80715747d8ae9a4b11e52b82580e904ee5150ba7362c972dcfcc415bce13102b2a1ece77edd67615e89ca09738d0200153e09ae2c60d27218ce2cdaf7f410c76d4011563e3a82b9fa32747a89eded628ec99af42ed6c951346001ebf007cfa66a6c74e078570b446c31c2d59227a99fb7ae9080758e6c12b660e5eadc8f3d17ce37e8503457a9d066e09fc8d6f85289a190a607e2aeca41fa617c3287ed239696e324d152e5c136e59e82eb1b7228b98b20601e34929955e49146a6fdcd73af66de5773a91ef8214da02d686f451cc889c438bdef791f72cca29dcd76c2a069e1fb6d7d3b7428c87a2836d8a3048d28a469ea85593d6072826141b85efe7cc80e3fa9f5b6c971d9ecfacae00b17a90c4cf652f7f8cf39724236cbf48994e3d74c9093a41fd6566afbfd15730baa64ad60d3fc591687d4465486173dc76ce65d4273da5848ce66365141df1be338ef5eef40aee38afd1e727c9a7c6fc5eb97fd2841a589c4e9065bafc47e14f19263c8e4ec740d84fcdb6a119bf2bccba47f9cf2ba7ae16368aba9a54b5dc0a17cfa371ae3a2f888f6d81e41ad620b4f546426e6e27e5a8629721908348f10c5e99d18dd8be59b4ecfa820a21b6cc0915c4b1afb560c5d71a5f180b6cd889b0ef8f891887389c3eadd9c729997bf556d547e0f12e7a335f91fc9a27e0a6ae8e21fca0fa07611df37f7fb722c4c342ccc9cf170e410eed64f0d09bf896dd8fccf3eb48abd57a54270e60d4dc42adc351ca9d47e79bffb1e903bdadca54df885658b8b8bb0fbae66a3eb717240b001ecb70089ec98787287d6d964b90bafabee7cf85776b0a9354fc8a92c7227ca185a390c89753e411b313dbb9608e0fb3a61eebac73d8ead4ab696c37e7239a429a26f091441d6981af2da7942d586c7e12eb88309d5718aa87d5e65d905dba6cfc4503864805899796afe20cfeed99aa385faf159618d1e06bed9c51d6a47821b0f7ddc022008d45218c2e05259c4475bf34813cbfac11e582d36525751ae9e6af683563597e39cf1ff933b9eb228fb7355ea50c6a4c304892bfb663072586e057a1c16821660336944eaaa805a8e99a2da37bdc13c756f80c36167a37259d55ec898d3cf63ff637812881ae6c6b1009fcb1e30ce9fb51242f24bdb8b72136b4e08f9684bad591cc3ae6cc5ae78a393db38ba58db10bed1e9f05818cf3bd866da57344d097b90c12abed700ec5c0c19e06e313319af4801d38bd91ef22cbba1728507357f9b4f046048d12eb914a78c98d0086071f02722210905963672e0f67b731824e82c7ca4f257ed7ee731a0743e07dc83a05696d90d2e0a19ee5fad601a2a1796f0289f113913bb3db2e10884d0d791a809e28cdd20b0dfda5c7246e3ae830080ede6cb1b980bc7b9bcefa28693d0f83078959b8dbf0cb5b7992384225c2f9de9b616ad843014aab3029334e4d279aa7512a89ed55a3504f83f723b6b89b6bece7a2032b98393711bade055ad9cee5d393a3bfcb4e535b071a672071fb839e4c2e40f2c9a79be84595f6f8b3d66819d415ebaa994c0d00fa213405011b2d69ff8afd5995a0057ec051f38cab3c3977c17909aeebb9e1839dd036d7798afd3ca286dafd78d60f02170ec33c46591184310a4ef39ce5c12e290763e7120a2eb13b835cdadb001301a72249fd273a70bb10a516e2f2932a7bdc3115d7bab0b82b0cf619f64b44ad102bfe9547c8f6876ecbe5037e92e441441bbcd7241e1b7dcfe4d79f39a4622403f9991b7a5b6d73a0358e1a4481f1ff3cdb9ab6c851f92f0428aa0d8f680b303fe6707b120ff592fb292dab7c06b7bfd0fba30649b1450991714a3dd61d74e43fee1ed3e18d97ba838c3ac44a20fca055bbe08724369948be0c3cf190e7f756171f1ad8a08ca03ff352e18911be468e4a2d158321b5c77040abbcc69f6acef6137e7798156bab49e467203ddff18043d6afd4a72c0dcd90580c71d1bdc25573bf35c7b33d92babc93bdffaf5628e308c60aa047266d18d662beb242afdd8c600fe4c23b2e213e7bc176645dda7914cb07ded567242b708a14a9d340345f7c661c620ec87311806d71e0cfe8c0ddad2f2e9f2920cd86a56735ad2e16e9d136e1a1ac702bb5e25cc29723055ffa8f0a594ebfd7626e52427356f271a2f4f0ed151b8dafd81542a0d87ea8916421ff266c157696d7242571f518cb176d554505d635196f0c7089ef14b9ab503169999658bd92b923379bae0f73d801ce0fd4158b40bb2c3c9f4bd672b964b07c04e0ff2aaee9d214a3b4f601406aa610caa205b45b0e100eb869b50ed03bd1a2bc1192497fa473d61b5ea7764b2e382344a5abc786494922175765110d63278c79e2897a9d826e5721da291b47d1599cafe8b9322b9841ab51c676dd058f03e62dcfc1a8986aa84724bb44ec5d5d804b2654752d46d671f3fbbed8edff17a040800919ff0f57a4672eb656d28d6ae3386b4f1c3a71bd5f0f2c419e7e0c239ba00d2bf32a52bda6c72e8fdddb3308d364edb92302fa6981bffcc7b81ac497e64be421ad9d91328e97299d75cd1f8bf333cd2743ca4b1f8e191823278c6cabfe050657e330863132c72369887220a51fe0184166ff9981d5c7595ac57c00d33c9f05ebfbc1d6acda772ed9a78c53927313ca491603c0661316bab10dd551068b9af80996ee155860a3e210481cd913bc30aeb67ab4ea2e4ac161341d1bfdf4d54e02a745cef8cebd047f405cd3422812b129fce4a91de0725055ab224716400c362e42ceb52912c9172dc68b8bd5824133c06a81dca99b60d48e31381da56f3019b452f272d6fc817729eef8cf071b1746ba62f2d909f2c0b18128ee9184724ce2b32fb395eff4bc47270107ae2e4471ef842ff4bd9d0939256e59bc7d73f1e56d1f991f4423e21f2384aa481f5725c4ebc6bb0b96da0c5db8e9582e52108f2d1f69a9d4ec11e7ad71ccb2722d37a9159ee6d094c1b649474bc8dbbeabd35a6cdf307221e97d3e4f572d16606d51f7fa9a21ecab6c898c53ea44a05d0378707cf1683ddf54916c0f9729887fa5d2f49bc1230da3518a22c5f20b96c7d2b725816a3c032758c1d418a72ee759626d39f57033ba9b56e951d2923ca875bf42495a1baca68d2d80c92a7722150cafc752f7e72dea61babe7e6e65fc70b4587fec0448111968d59b2b86704509d2a9824895c5faf930c2905400d44d407bef4e0bcd1b2d491dfceeb74792356b0305b38d13a2770ce172fb4acb85a3e65698df57645c7ae73f28d8522bd2d94c770fc605819ba440dbe43acfdada555a5998ceaf1a216f61d2e0f44baa55dede3e39ad8431fb290224c1953058209fb14c7f565c86b977833a930a402041220afba88fdc95e866d62b32139531370ca4da07ae4fca1d4afb6afa6c06790721386a51a78e6f2524444871592fd35ff5bf78bebebd8d6cfdef1b1de0a682672a608faabf6de4bbd9e8eafe6463d38fad65d6830279c9a191e1c2c769b89051de97b3329d34ff4fb7e1f463f70b60abf7dfd98a7c2b1196f26484af5538171722091d81b8b2f0687cf79e16687633b46ac3010631c139a33279f5f023924b130a4c1b513361fcfc2a094f04622e45e02c3fae707874b1716016175c653530b72346e76d45bd6a3409742eb22bc9db8a116cf70260447a4471520afd811089249981f294d427a0861983b36cc89eb17a943f6d7b7f4efe960e5e0e5b1536190723c9c544d92ae05440001c1ff3a150a12d702dc809e0f444342d41075a5a7dd3583be1400577e3671a22bd74729bf8dafd6ffe6214df9d8cf356e2cb57568f7725241a5a5a7f30f4f6d2f61e93fb10798fba326ee6d1037632f64862f6b187172d65b5af2f7b9973e8ea38eb180b8031c57ef6e278cf00d1d4b456a491b90f70d5e03c4f3403c0cd3d1b0901bb032f900f8b5d458f5d13237d1fbee99f54be4721e94853a3152ce7dd709764faa80f63f2d6ddb316f50b5e0176bf107facd1e72fee54356e96ea5220a3c85efc571a5cb5277ded066a861762b382a207763fe729ede3a9d84b0080d392cd1da2a33a2150c433aefbce24ba7c4ca7c1dfc500f72af44a19595f3b7c0cddae54fa75322d54f72702ef47e288a5dbaf54a56d4b5728834cadb3a2bee31c0f582fc6bb526f5c9ae91874ddf06be9d4464d2704bd67282d36e21bec46c9f1784af44ad1257fc2b4df7bab933521e9783de1ebf14b165c80da60f2962e146788a24ecd43d7312f7104052b9e58221f7fdf6de8324f7722d55f1a53a4d93f10882580761659fcb3229098f757bf1443401160d5128a5725d20f86e970e487f7d571a5989aaf3cb26d08db4153d4f5f35b72094ddba33724848b0eaf5d846b0b8b4e69028a7a45ce66fa0f2176d79c0d5da65ec1f7ca020154131d77cfd830c83d7427b1048e229e3b594d78b6d38488a40db2964104c72a6f09594530a9b17d825a7b65f5585d3d14684a68f71516e61c8d33c2f72f2723a93304b558461daf2230867cfbee409f932cc37e6a1d3575021cfcb1adeb82544276ead9bf7f902c4964f26cb83c5ab7a197b5eabace9285e531e7c824742002e7ac0990a7a9acfc61352013a7d9ec40ce30b01a84d1d570554298a1b6ccf34df0a390a919668e628fdc0a5a5536033597d34f70467cf4eb97ceac8d764be72a5d69052eadd31899bba52771b4592b5754375151f7185bb487ed7894e6a2e34610b7e6bf35f716f8c94cda83c36181d241f83a1b4f9d78a1100f5b698801372c3ff3e0058aa5bad6674a1a03e9197aaf8bc89311258b0997c4972be20903e7242008836b9ecca5fcf9764d440aa9e6255c6968cb2441f6b69e49d587dc90f37e38368361aaf8d06587a256e9cb1918f62165fda5508dfff048f4d01aa51ee67b91fd99e05520fcecde34e376ff77f5e74c40a74f80bf41dc0da318c93233172151f0f1d8d7a6350b40f83c1a26cefabac73016fb6711d830c6e49927ca418422f3ed56235656c0385181d01a36bc92b4a60a53ea3530299966ddaa750ce737212410f6dee828ea583f3b02ee25a2ccc317c8b2be9c260885b301b5be365231690adb6452d4017081894bd481adbfad75065ca5452a5d7f2fb2ab93fc110fb6c236bd2d16575b8f831fd25db4e856d20a8b416bcdab7da1beda4e23f3281a672ebe09503387801ba08068b64c0af4498e6b5e8390abbbd1558082dade5584c04cb1de3ae917590c42800e72bf8f46e9c181ce1093af11b63382562c0203f1f3ed58431a81945bdc8620e117b2965f3611361e1d8d9b5ad54e0e38ac9af51e47231afc5ed2c4ce8c877b08eac8940b8c391ee3d43457d1144b111e4e19833d372805a2d97c1629bbd0965642d20949df606b7463825cedba76eca056147c71b04ae4f9890feef854566f7ab8df46f502e3e9776f0b4ae7032c088c39c7296597268c5afae911359277d89ff987700732f38908fbde6dad7b4a3a392ce6a389e72144d0cda733d721fa0ab9fce76ce876bae26576f81397514b04b6a9b8abd0272b45b3bf27ee9d7bdb47cbb63c495d12b5c9191f88f15666ffecea252195162464d1dd28c779008c03fbe6570531df1a71e2f80a616ec0b5a856415d5ef6e0e4b49fe24fa2fe3c4e03a292dbb1212528e5ee9a8706cd19747b0f2cc8e9dc653329029d5795d1200efe2b0d39314c229ccfe95f21dd6249ac18d11f1a199b91e72f4a40eb5dd2a2789fdd001a9da854a439ffb3b21478d4393ce4ece818be5a172f819e08ba7bd5e0fc43f8597fc224b8b3b34c98868d31f2b5a369a7c4dd5c972a8a73ac8ac51ad5541b9936c3a90d4af7c5f50a5f234743a540a02efb169c3723333390f973600f134c3c697ef3db1c49cf773460782a7f0912a323256d7d31b25f48848f736b3b1fcc914e862af98de1a87736330fc56a2e93771b8ff785072d8dba827b10d5ec16c819d04dd709e4268e42d6b5eeaf7b0183035db084cc17257febd3d3d235b7ed5bc1fb06b973c504ddc1f3f18eaf879205e8f5904697472506166e4407bf7e6cfe59ae12b37673a374f7e106931c396c35acb63776c8b08f8ce7fd213ba019d8aff4c7a51227576e08b859b852a6d4096efc5e37fb41d7253fd9efd03132a2f4c0783fdf55f790d1973283cfb63551a6fce64a5fa08177252c32b56ec31d44b9ad4c2813fe78df8f4b62d654b1876d4c6a1d1fd14df6b51541d0e26bdbfbc070f5bcea4dc2d926de8ec5b52a6ededb4bc42e33a22dc72722627f471e732fecd9dc938114c5ae0d45f27cfbf9cf4dc0f6424c9bbf2dc787201a9eb62944eb02b4a7786e4a93102b27aef93560a2717b9b1c82245ce3de21af5f5a41cff45cc2d66d7f1849fcfb06b04f4d0ef9bd5327173e705f8f4e2547239df3356d3501220f57abd6e248ba451c21838bf2cab997f853e853a667fa55fb6c10778d6d2cdef724e92a24ce90d1b1158dc01be7bb0a22482cc11e7065172a76918dbe1c20ecef604d7fb80caf495cbe4b854339a1651cee6ac149c1a6c4e15f7abe919df0c59660a80f2d62816abde3b14c72cadfdca36003ffbbd58e572cd82e8c18d5cce659d96d76e199fdb8f3b6391d5bac0939d4af58c0cd333c27206d92ecfaa3231ff8fa7d5ca57975df6737e3aa7d4d8135cf50b1d4fce2581389f0c4af87f6126c448d32238ee0603c473beaad7c5efe3b3db90802265c5c314547031e65b6002157168a3b79a3b3a4fc712e29a3e6e5df2dd4059cce4cc6172f39602cebd59ae51ffd47b0144bb5066cfc0646523be9168d961e77db29ae8722425230f7355a0324a474ff9a83fcf3067d1d55884909576e982e15b54a2425bdb30bf2791b8a32a9d8688a95698829978e8119efa77e91b2e196ca29d0a0914c34f7cb7738f1bc4dede945f6cf9f2ff0072628ae6021a5e75afc8e0809748722a11fcefe1a359a87084ab9c596c67b35d0e6939e09c9cd71cb9f80cafc1a3721b3b731a1051fdc90aff43c761cfed7f5605b86f7bfcab691898b1917c04dd72dcb6122c2c77734e7e613260a5c1d4467969dd518451a4a6043408495cc10a049fe3f0b21685f27d18cf51b42115cac4541ad93b8d7ae75f883b83672180f97264472bc973efc7b50192a211fb2fc52f02926c0cc1941b061f2a8dea72969b72c9428695253f8462351b6cf23fba012d7459fba112b783cc903c550feafe7b6da21b73e0b0738fff5b98f5592813fed99440e629914e32de0c9f0b7b1c683a72fef8a6d9f88471154587b40cba2585c7b066717e57be7b6553baad7b90b12d1bec4a6b24edf870608b810e5f8aee8414b691b3615cb15013c7dac3c2a06ba96a0ecf6742deac1a223e13a11d1fa09568bfe369ad08dbe0139c51578b002342726cb41fa16ffe1e7f130c3c85db14c843ad75354abfc57d6a0d7a51614148e94dfe71b66712d8824561ea64e345d3988449c1b279f38dc796ea352620e47dd672c61417c123b395a961d0f3ba7c9a82b0d43b45b65e1cc6f8877ca57e946b97131e0b6d6d5c0bc1cafdc64693e503ab16b91c362f1a4dfcd53e423d1071e979000b94e182b81f2d4f1813bfe32a65200743a840f25e76040fe77906e22fb927724b40581f47c12888471ab4abd11e430a1ab59789223af09e653d9639865fce4a9304065741a9d74960887f221bc245c5382e4133f29f4e35a82f1ca87d3af422bb764fce8be38d81778d8f52c682c4a21c0707e7005917abec37c389019fe9720079dc1d3a802201a13438b908df5720515385caab9001510cc21a76d3019d01a484568bae75a6b18b47cb99597fd87a00bb149394a03012bc41db8c18602f181cc14d95038b4b2347d193bdf6fdac84509ea01b906fb5c1c86af6e0b019d4729ffc7ae7419b8b347250d42fa44ecd360435ec203aa9406bb2a6fe4574a0be7262a5dc7168ee7958fcb510e009ad1f71463c801c485bfe860aa788eb02143640dbfc63edf3b40de4e14161f7fc7ed2655981d7958eede519f67a6d1a8a08fd187e8e1a9e9c2806af06f46f1fbc4d6c3165b2e71ff169f18e9069cbf0e2891472a5913925ed199f995c1dd0a210eb23b8780d26f7604d8052cc69d2cd2131e272c0a3ec7c7491327e6c358d4110cfb346af3579233e2f48e453285d9e0dbcef72cfc1b6fac6c52cdafb9f8aee6b74bb9c3d9e2b079cf4d95d2dd1724c60695d722ecfdc64a4f316346c51388c93a61d009a661c94aee96d16ca137f04ca920c72204f005360308ae0227316776b91def5f78428deeff3dea5ce12d511b4c60f72a876d6a12e78f8ef81eabb1c00852ba4be1cbfec2c769fb37a0a175f598de31958167f041f8cec283d21c9fb3ee224fc9c2af7d2a8d9f009090775fe3e56f872b489496d0f6880078dd3aac03efde114745ed1e0d671a81f34ecd8a99b19e3729fc6fa54c1b03ecf5c7404c7600bffd76fe4e6c788fa5b733c42a82e7df8ab7268814415a6a26a2c4eeb34192ee0abd54391c6ad943c3e2ca6f55043feedad539fbf175dfdbb42febdf01364916a19a0479eb984fb4eee6243c575a2a4113d0836ffe5ba59cdaf1d2ff13bf579c020f2123d9d55dedcec2bfb40cfddd1b1093fc7fda4cd134ca0813294d7edb507a29f43b3c5f2e6403e344c15b52b0690a90bd45f31fe2b7019439692733b2faeb9c057aa2e9e2bc6f59ecf791d2c000650098e6f10e8a24cd407c006e3e3de181872ee8999c7947b434f9121ddb739094f0a86ccaad03934c3ac08299b8266c91d06b4cb6f491766a58d584a2231a1a05940419271b8372bcfb19db6f116afa27ec862a6850e7c9db1ed0723c251da5621728ebcf3ce7c1b2cba9f4502dca9c69882044d10f06ec484b6dee331887f62f402f92cab632aa1f486890ed86b297d02807d6298ef2f32085de7a1cd6b79bbe172f64f52fb4b060c37fedcb1d5ce60b799213cc93a209deea2e72eac426e647907916e5ef193a6037ff886e3207d78971a90d6b5da36ddc43dc43ef4e6720d4572245aad655f853efff69dce5cbc828db6cc23e5e7b527fda664554ccc51ad6d5654e0a843c4b17526a3020f692783be81c436cf7b4c355323251dbc5b58c772726e35599bc2f5a1663832c6b44cc51d5632c29492c4c0ec505aa2c3a1ed22ef455c805b8254ef90a60e6e7e9ec1d717738e22a83813e451ed3b0a9f37d082e3726e306c3f4810885c16b2c1c1012ca9cace0bf8738dc8b4e4e58df34715c2321baf9b91fb0b396aef26ba2dc469699a0c76737052ad94e83b1b21fb99b3ffb813960e1361e71b4b125fb0ec6504e22a789c80a724df3952202e1e23ed5ee41005e6bec62516dafba29df6c9f3689692377f9708e3ca0aabc4662d017216cb0572c07db3a75808f9879ee8e21df00f494608ca335ba6386b812cb347a0e686507275d49d12af13b0d6b31ac541ecc3184104f5a7bc4b2690b77f4a227d231f323d4e980ae6b3b738c11ae6233e72a157e5c579a3a5b5fd30b2f5bbc42c03a17572a0903d1e0f7acfefaa40fb5ebf6a9bb67a1d5d1785f566d5418fa489e6394b68cfce843d448b98c57a91f12be51c0892e07d00ecf9bf9af816de639880656372866362a0b760c7e5cee4ee3dafa213bc5e986324ea21b59bba3c7fb5822e49539f4001f3c251877aa4177bad52f485e81dec043dbe6018af1f85f6612f209d725d3d1441c8a4bb50727f0a22c9a4b19a382d657b7b0dc7a9af3091a90d791172f17d2a43ad8b31b982b2761d6205944b0e6d81a388a5db18a1fab10ecd230b6a54d34bf2d1bf8c96fb49d48440d1aa0c5e5c48d2e127e42e6f1cca5fa7578c43aa0378f66e55ccbb910d61efcd4197ea170b402eb60cfb39505a4ba626f3442ce4552dd1df60f9a7204ee9755c98cedf231db033093b72941f366f4b1ff5e80aaae897067267b774f1129c449a803ce28e3d7c4f46ba231e67ef352decba23723a13820c672cc1308bdc4b383cb09beed8a1e25fe31be88e3b09e1b20ddf2f65459faf8c100bd312593411a06354dd81f1a435fab93fd7f56a537736501a3072d70f56ffd257e4c6103df56da15b4784be0e79923c52d0df1ebd8a14f761185e5c38d036354955e81953d1011ae90519c60b3a2b4284729d0f4d1ff6f225ba2a9b9799303042298a61659255289b14ff6ab4d228121ea3f9db69de319135d8720f9faafcdfb359990b9abc1dbc5ceea522278c96b4d37ec344e7da1ef5dd2034abcf7c48836770c595e63f17f02b8f4bdcfff72f70a20c02d375e57fa1473472c6ad172e0a08f83da3b7073b6496bf330cdbe751652d06fdf6d7d4c38e61de534c68ce611b7bf8ff1fe6be385a456105d3647d6d8fcd1f9fb3fb11dc3730200b8735fafd022db548b56b9f94a4e6c086846212da7dc356efb8018e4bead65a72150a65a43844b8bc4597db08301887e04c9033554113122d93400e5824f60010f4db733c8f60a7627bff92767c0076dd1c14040c6dc43caa93e951d70f965b724a03af562f99422049a3831ce821bc23ff6a441d1e2860eec6c783ff5de7f572fa758f8f52333fe1dd94ca8eb8fb440fc7fd6d314f5102de1d2e98df2310ad720f9f2fe48610c65251b1f6dad5e6162f098ba28fdc876f4b1a24bfeb69d46e3974edcc20e046edfb837a2be495f367a663e7107d34565cd9db3a039b18c20241308f4ec2ae431571909d6bf9c4ada3e3dd02d18ece95b78d2a7d23ca03445e72ebad2453f7dcdd737b0060ff3210b2816a427ebc268546906148963cbccbe9246d410a533eb795a110718ed211b9f171bb610768f69c203db63a49784a624b4cbb04bf6af399e2d802848f75161b9a1405d6aaadd4c64b2bd67f41812539b772cec2242f3a4f04a572e4e5fa0f5673f2b3f076a693fcc63fca6517bd624b9e72345e43aec4d4161953503c3a14571494565360db63691846aa9c81b8818cac72ed69b7270bf5a02c0e41afbe8675da3b945371cbc9243a9e8652a47319df045e15a2bc4b50b43317eb7b74fd2d9e81f302258f44c9c2070042592f6d923be9720887f558cae9809789c3ee1e341d727aa617bae4cb6b4eea9fe44929875aeb619f07937d8dff2e408afe85a2172388bd57f84f86e826361a9c150a6bae36ad559a1fd64d7564f9886eebb083641a0f15a8634cc3afd07d137e2f1294a7643c72b9630403939aad77770aaae65bbca9b6b02b62f8ae4123a8307e1d79d51b89720aa20edf66017526a54ddac0d9064886ea0c3353f49cdc9592eccd5c9fd21b729855f8519c4a177db16c35199c807552fd11d6383f7bcc09eb36f4001af211148601781d245d7463b556e73c9c2bb34f529604f75dfbd717648a6cdb1397892cad1f9bfc48159aac80d7bb9812e957a19f31d78c48a37915e77907dcd53499722562b1046154b63d2ecddefc501271ac80353824c0f3e6bd7672370554c3a872d7c18aea1164454608f4db26d1b057d0e9ef7e8474c2202b61c491c3ecdc9a72aa725096b75fd37b6466701b23524460a116162e6d0f124819e678070769a072cde740b1f9acde93cf88f51841cf7157a891a05fcf174301a9ad5ddf33c22c14a6dfd155c266a7b4239d1adc67f01e3cae6e15c933d04469f904aed4e15aa372969106887053028c83e4fbe252bfc7671bab2792776491d362cc6404b6c42a09dfd9bb0f343c14e0a763b540aa0417a5f9836c53c7e0c96ba0d35a11f7137d39e0e135f286788c14b021c999bdc8abbb734775c7a82919132d10292464dea933b390ddbaad8b69d65a9b092efd439b3b8d75329b779ddf222390e0e454a2fd37cbe0c590c25e2569763a2bc8b33c8ed97993b227c48fefdc9b643d821356de72e8481d762806013d73ae8d7ceaab782938d328015c2fbdc5f3cbeae0e739612a70855406536f44e71b694aa0c61d000d1ac12bfeb3d756dc3bd22511358a4a15d39de2f4c2d9e6bd63de360d254fc2de2f31936018376bf44cdc691d4573a172d1db61af7bd0d68f736264c66e6a69f37cfd3adb092842ba12241c5ee02987575a0e862a4a9a1399bdad3da60e3e4c4457b6537cc6be48010c8fdc9175c9ba727cf679716ae75abebb95f9cee842933d8b27af96ea5a0b8100988f5e1649483ea868d13a53e362e70a6bf6111e8f7254f9a2e528090fc7c36bc1ab711adf6f05c0774d8305b8ebfe6b91832feccff2ab6394b8adfe9f1d2affc67457eb0198208cf75abfa5c8e38aa80e1dd9a176eb0cb1661b5e44ca0d9cd9e91cf56dc9c56f74432edbae30919ece8007a09ebfda6c44f25a97b08ccd188dd87f787069473a2c7a1c19704b6ab9192affe77a02a044a7a85942791db14e51dc9b0b46f05011e873193dbe1a620e24843ff3ec5bec262a58417c71a2371ead0293a819dd197283a2d461ddba690655e3fba5436ee4219774d14deb55e6434d545d6ad9566037fa55239d7df88f588d4704fdab4ce4a3f704522f512f345be1ff0616bc4d3c5149ee6233d50b87001048edf219c74f9e521e036a62801d033928167da5aeef72256694352764570e94b0e5525edc76113fa2416127b02b174d3c941c1dab025b65aaa434c4ce5870e5768bcb183ff0d0a754907ca898874ddf4142c8014cb31e93d169c4e67dc7d53ceb298e23d31cebe290c2a69ca506347b3ce9ec6e0f2f1d95c1b2cf4fa0bdb3cc8156e1e838fd6f793e6822a887ffde6fc5c945938d9972d27c7f64dea8ad9f0cbe8a617135e1336f3582af1e261e2672fa8703d3d5f272567c989d9632bb7b3d63d4c98b2594a893a5075998d72bf0c7a505704f93ac3218063082899dce98f084e9fc6b7969fe24294d01872d67e03d105ffd4049ab6abe895e00472f0a9b5436f3d935ba08ea413996e9814b39535df3416feaa99372bce3064aac0c8297aad7bea9d690b209a1fd3ac7e060b94cff9c21aa9d53a25e7bf9c4e7f8952a67fcc0b2579b9ad0e6d90ef1782b73be8fb411b4d18b96b30bf535e82684b3bdfafaadd3e1ec0e2faf8437cbe960aa37510d922ca326e0115b1db31ead01949874559bbe15b62a45ed825ddc0bfde87586c407cf69db249744a29554f9852e3a1bd856ae9fdba492309d6309f3b70e96f7fe174d6bbb2ac2723a51fea701c4379c2430c122ba030c0a196f6bf99ec516b44af635882eec0c72e4f5b1c88612f9ea698a6283906ccfa0d6afb1179ac3edeb4a3d5519ab0b2d64b12802acaed39a3953e849f89ff7cffb70823ab7b67a60f7c1c16c5188d1060ea4d72220006ad0d6ee37ec4f8f3f7ab737b0469400e308a70d06f186d9bcc47272d6b5c9d5a629359324858df6788592ff4c3994b3a73c4b0d252f22403222729f8966e7209a5265ea412f0a5a513213068644999bac8822406d7d7643d96d72a5286c62526eeff7177b70c1c8ffc81bc20839fcf13f353cf41a1b7102ebae72ffd66adfe3def0434198b539ce96908c21f02e124fc1f471377a457cc274557217c01cc009fbbab70f704ef5fab9a52e880245fbbe843b4d803c83ff41904c724ba7ca491d8b5e23834d2bd238e68e866a98b444e1a54e67540cf7847828880d1d5c997250af45b259ac1edb6d016d5a795ae8344dff5b3e9516dc3b576400729cfce8fa9bc4ba6691f3493a4d959f2a20a0a88a960ae210504e6ff4d06e31430636519a35e4e8cf9eea894ef5de5910d4877d72e0412caf838b5b259d884172a44898567201bb385c75acd1d3fb94296a8c6c0d34c5e914334b3b9fa245017222d9eeae59fdcf065bbe89a1d0f00e92bd7efaa1febcf3f827928b441da8ce3ae38845d4f66fcbfe37f8368a15ff20cbbf73df0b369018b15395637a4e464e0df9bf1782e86260f804200fc28efe05370b84665d3cfb4dd8ff0389e82a62ad7275308e05a276a68c860f321b0c50cbbff119cfb83e3402332fec05980285df723f83b9c3648b48de9531f1117749f7ba8d3141fb00649cb4edfe89a9b1018d72f980dbcd74213b37ebc702774e528840536406c9b87e409807773941ca5461725c86805a2db05fa5725b6c754fb0ddc83252267f25d4844e9d50a038863b643338521d2f4870ed6678737f836a29a4efa5650596d58c097e58b7da5e051f25264dff5276a4f03ccfa373227aed7e496f83676b623326746282950426b7c23c199c087c2d1f71cfe02c1977044b180970677812804e19a363910b6106d92c091b108f3e5ed367c46c19d1ac882b0b7bc51398463ba87a7ac2521dcae2fd54477260e7e26a436aedad211e937e3af382110e9c50099f980d6e1ea68951bec2b0722af9695fe772a030835a13c314852d61f2255bdc1727747fad857c43d812c70a980b822c4648d995141c188b02d29484fe2628fad64ed4edc19359fab455511d190261a9777323552c4a8a17b40e953a078d6bff31329db69994827cd0cdf2728c57f001b538cbb14661b246bc0beb13bacefd3b0af60255afa7456e951e88585ee1270077031721ea0a9ba0cb12f946955f55992d4ce7a1860b57af8cb4fd04616b6ededc38cde32752e34f57bd6542bbc7a07ff6f5a183a09ec0639d4fa15a7bf5336f1fc6fff415d1ab108702500574dd376bfaa81e687fc7eabc9910347255fa9127b64e1c349377a187fbbfc5de2431934c6831346c6cde488e68937772390c803baca8f626332be928ff566dfb31ad6b55b0d283953bf894090e89c10eb8ebd9b52f7f5da335571819ff2711c8b22d752c6cf7258909380a7e32d1cc7255764d029389b4e2b18720e52ff90c7ccf7e61d7f11e3265fcbbe59d14fefd723dd1a4765abf9e3007304ef17283f47ee1adafd22f1b7deb2df8068db651e6727361639278a468bb147fa654176bdaf0258d519624304b90828714769870e8724206613ff1c9ccceebafa4c1cb6f69c511d51f302c69015dffc2f3dba29cc622af7d1ca018aebc4bf68bc2f7d9640ad3d81fc44fe6a8db94e143f9fa29c98672b4e2bd170733a2cc6e4c93f31cf148465cffc9504ae482c837638cf05a5443726134a6454dce6f725517eb6341d830a3f0b8b11b16acd373c6c16258229e1810f8fc5004ca34fe19dbe26d90f4fb3e84e9796221c8a5607195ac1d9d1584e372b62b17394acc66698c360ae3c0c0828974b502e723291380a4a55ad66a8e736262bc0ad93ae0d17a076697484e0b70258fbbfddc55c330366da09e350dbfa772c88257ee6b6e5835913fdad1d1798741c75aaccbbfac30878fcb849ed7bbc50f5aa61dab937eef80266eec41f4c2f47ad94b775b4b167a40a18d8eac98c0937252769bb1e24e03e763ec95abf8e9cd7c44b2a6cf5ea7f01ecde5ef2199c35b726ed01d70a8fe163a6ad601dd48b0d62c29b7094037715de76fdb6307584c3072ebde7de610e8f0a548238ccc286231f89b76d76864e21490b08fae604e640323058f3aca5f86016bd389051fa111ff15875f7ba14e8554b1e3f2e6ede2acec5b505500dc8842d33ff2a931c1e653f9fcaee289dba8c56d6cb903a8164fd1f2137454af428077debf37e1e775f3f1d13bb0cdd7ef1dddc7b62d40ab8858a7c5720749c2d9179078ae0eda30573ccd6e2986f433c2dc8727c4b870d032dfe5f63b02c771837c5aff3be2b14d6e59255faf738197704dd4fc0357d0266b7bf94537deb441205440907d1580bfd3adb77bd48f516a4a749fa671dfdf9eadad3de172d894d460ef77278f866fb6a4b4a08424ead2d476bc3c660d09508ea4f7a9e172c5ff8854ca72b23942766a7dec000b2564c50db798d88fa84249890545efba40effe8200de754b84bb316126673a6fa27b182c3477448d284c9df3862c98482174152fb36b95a402578b9f3be9a16f0deaf557f31ffc2e46eb7c2a50a5917205363d7fa7aad6673a56da6e7fca879393b50874ec6d761305e4146857b2afd072401427f23b969ebb1da372bee490539f06fd82d43abeb2066df1cce48dc5446f84a3b8f4f57be5c4d197fe4fb35189a08afbf64a667267912e7444a2441f2445d5bb5bcabc879858e7aa315b186f24fe8944fd69a9850626b67fe9a5b60920182550333faa1ac4463a17992a3e90b0d580a6ea3aeea9f05e156670550e3f147236e97cac30364ac696e5c3d563cc7f51cbe9663587bfd368550bba0a9a685d55fac9b844aeba7a6d0160945894199bb005b8f1877b39b4542e08a603ff24217233488b90067379e269404bf303e97241280ead52fb434afb729f44551b5ffb72f98aa6e66390320f58f7ba76fb2e0a31a870441714f08515d4e11049a66f4c25d18415e1d6372f54b03f0680c81d18d5c65d5a657bc140a2b9866d3a9cc67b724a3089d6de71aade003c320699f2b8860bd1269e9a35d94a973e02bd60a754578d3de8367a779dc0e1b558950f7ee738b28af52a21691e6a503ef51a8a2df972df7f76472dbb6fd6089ff167d1911d525efbca3bf237de7ea82c5d48ff36df7212dfb6f07e2c79ef28596335ae4feec2b556cbb903b40a3eb70fcc96d515c572e22f9341b0ae6b620284fbc21d795924169907e7c6989047c8f4e4173121027207f74d7f8c900dd1613b150205123f8b78eacdd01b6dd3c57c292af5bfa78372c85e891981c4a90e91b2ec1619ceacaed4cdca017ac454bb37f09ddeba5e11723e0c5ee1b067a890bb6a7740915a6e250f32c192a04049d7eefad57ee5fde33852617328bbb25172eb6160bd188000911e5bf62d14998e61a2c44f959b4101403f981543097d5bf0f41210d272e601d531ebb81aa56e9f60ada394727d17f151d4e09e969ca9fe0974920ddde3d3400e9351cb53f9e35ddf0005231b44036e72952333f38bbfc2207f2021f994fccc6b7f92675949090c762109069a76479e72d17f879153ea2cad92d5425e15fdc9d997a9899cf598c275215afd481ea8bb34e82f32da4a8d090b14b2e8030f67aff75a5e0f386f7a3306269fe2dc1d2c013eff44ef340d1b2045c32dc19f96d6f5091a8951216de5f562da58a7e53a457f2a0c422e8a6f5a0147adf809ff25e3ac899bcebe925dd069e896dc220228669325609ebf34f998a7a6ba79b43adee4e42be54927d06280e0e5f18c48681422ef283144be5b6314152ffa725268e0bef1f93dd352db37ad9dc490dd5234e86bf40941a752e030c3279361ba8449fa9cf8d66bc86fb30052867304a3f8d2a5eeaf506342614e3cfb744cb373ecb2e9dc3069959f4af874b56acd3fad7a2154e4bf721983da12dd2a90c3b5f9365e1e0650e3164dbc8bce17dd8e2eb29a2621946472f7d5b9d0692fc45ccea8878e36caad92bc8a9c3fb6ee807315a36ada43770e72c9b3e16491b93e0feec05ba9c01e48ba93829425343e67b2bdc66b6c4234657269e83d7294dd692a3a2c1eece334942d0bf7e1f1abeb12db87e3ff024915147207415c93115e9d658e4910ce1d9362f71daf9578abaed68c35a311763b16bc7273e696cb96dafe7bda81a711a98ad6ee2ed3c8979d6214ae0601a23f5a79875d3528ebc1b1834fa2d2a1416c3d23939e8264f3cdb74f50e5fa13f2d2e3ac9016aa5eb6f377c2030a6a348895faf253afec786b62e343f5033f73e1f0abe81b240f968e8dfc88a147b146326893b8d8b59e3322a65f67ff063296cfcad97e8e4d4bdc3797a71e242a33bb81d627d28404e7074f21a39a3e7dd541fb9b24c5dd2fd1d8782950fead49f324bf0c1aeed5e088b8a37f76f754542a0d0f1571fe7c57bb65c529fd237c701492d75b67b4d98b760cac28a4727a82ad87ec93cafb8a011358b6cda3ff9eb1ac2e332de30b5b908194734c01ffb7b041f66bbf4c71f1727e01205e12fa3d7e29b226566c2edc58c83295361cbbdd8df3cd0aabd4a608729e5667cef4fef4dae41cb163648eb19f8df500c41c42ae5ed0fc63f9f121fd7260655e23ef777771c2f661f461d598e93a289e24e8adf6c7ffa7fd0fd1142f5219105d909e2dff44fbd500b8eeb8ea4b13667afd326f5547ed084ccf71a30f725bde3320914f16d6dfbc155681a1b56af6a09b758176c581521a00130ed9d21caf5ac2aab84f8f5470a8626b9ea1cb442a4669b299742eadeb783f1746a65172f5c901857307d18a2f1cf891a39ee99c79b98746f2953f5e9a900e0532240972231f5916074e912bf5e660cdf8ac68a9ec1d9c749f5b3cc3d5f7b88a4ea38628fde41808211cb2835a56a90e2177e865edae28419b496d142d98e6a630e95672937a94a2d94b1042eda9ce5a34ecc85cb3d78df1c4e3baade363bba7708399729799898381ae7d0730283edf07daeae1e6227cb5449c0379a84f5d0476bda005800d190f84f751fd99377a095567824553f78091dac1cd63bd04042f5540932398ffb8e89efec5442022329e8ce0df40b5bacc415b49d94d9d78b445b3a98d54c27ee75f5065a8e3c9756ebf5b6a4a56d6b9acb4502100a947508fddeeb94a51def4058c8b2815a425cc627712995673e9d1b71662e0ec8b319fc7b7e44356720f088cb9b9ae300d4580adffeeba224ccc3152f04ef914213daab08e4045ed6e6b87595025e99d233fcb863b4ca935c34572a0d6c645cb1925a353f81510a10ec8d0b9fb9737a62b3ec03afb1895b8ef24f8ee8d9fbbee968c327949cc00f572063eff8f8a20bca1d9b1f688a2f5c861a3c884e340ab0000342f889e67644672fb5f0f759d2f88d18b6369e690ac8959b01c43dcd59ebcd22bd87b4cceb3b67204a56aa0602be7c0116a50e9f3fa24300f6ba3cd3fb05f781c9349b3f9009772126151500479bdc6feb03e4ae8dd31dfbeba1af792c8a00286066542167885729b9cbf5de2666badca64da3170b2510830727b377e77de1f9986bc0ff571cd3a2fa6d5a08598bd94d5560c3ea4d0251d82cfd09f9135cff1f18ce79b06ca6172af1d4e308c39d1a1220a488378ead09f8ab905fa275cbebaae86fac6638d566f7de00f4b22e6523df6f45610518562a66d5b21bb5c1c84b82f3932609b8cbe684da22d2b3842ba3064654a761114e7ca98ce5f7e7a75267fe30face595f8f5725daf540e53c8914d320628d102dcb86c4074e8d4b12a316592c9b2db3277da72d425d9eb4873b52601d89b48b0afbf523e110029912a3a59f1378533ba88ed289e7802840275a22e08efc4bca89c38d6931591dfdadc1411bfadc75b73d2657270529940663c8fe442f9e038fb8b9c3e4d28bb1b6a636f1c3a0989ffae274c72393b7a823112319580afb98dd4ec1b8e560dc49bb09279192ec0933e2d9da67282aee9759796de45eef05e1c75366013f03198152c4d33fa737f89d4fe5eea57dc7d56e79cfd0a9ada15d97b168b71e5c8038e79254ea79d371b3348bc33a67220605b181177941a79a6725964e366736d45686521ae52348782baf86d5062325e1dcdc53bc14c3b9275d20460c2392c588f46366d07c8f79cf96127224021721c82c370463b1b4bcf82d3540312de33c66063b0ba1ca4c43ef691a302cbbc0acf8d107b8df50d7d15bfb2c93a4d16504d94b37da5688c7dfd4685b7b8832e7291cc8b781f5dc712f8d2b7b6c331349c35b793f5ccfb9388bb9b5ff95aa526530c9be3697cb0d76792065a5b5121596bcb207c3f8151a85915639a8fc2470072797716e4d997f7d56300d2cf89adbe51b0a30f784bff1ead03e63a9a6fc302729d0ce1ae262c43797d0b30bcf40fc21e939646427557daa6480e6e9e5d06ea29d76884a96d7eebea1e7478b0223bbc958ae49526d396e40df76927b5c9474b405ef2692c932a365a07161b7b2ab06f16a579f5cd30ec66dbba4a73a2469e2d72276b5fd8f2dd307f5e12b33635dde4df0b53bb5d1bdf827f65a858e5c1c04d72b1837a414fca8a0984e8b918c3cea74d91038772b95d37c1cec195e428b1a67216f1ff248160d16e4d16fdabdcbac48d28d4e850fc92ba03620769bd815ad6721a76c1eade299ccbf26dc77a03cedcd09f75c9343d71c55e0b13f29b59217c7280d6b4fd65067320bea13613451b51680dc3c381b76fee769224343ef55628720fa3f6eb68c966f8b92ab013f1913edc71384a0007ceb07a8f5bb8c49503017236b4f11e2d97d4637e815bc2b62c2ccadd57e6608bbad544c9fa312a57422d7266fca640cc57890b63916c1d9a29e19b84439d7d5e43f67a721f3904675af41479cf27b7cce5e1d24bdf06c8f02a6e96bf7bade26a61732a5ecbcc8bc4c33755359b37ff23d160c54863bb5c8517a94a59f80f984ebcd0e7690abd19ea266b23efaae7a14515d46e3b1dee3e901a7d719a00b96538e4fe332c5f48357274a50452e0aa9143a2b3b401c2ee1fe443f9a1fc26e2a4a3380e2859f75223ad2e6872108b762dafc1b6348b120ffb3d99f824ee0427b7bd1062fc053a3de61f75e90c05488abf4237533da2eb40db09e51e1fe0b249ddf9364cadf6af20146678181614bca7aec43c0af053f2b2379d367f60873cae39f865befd40ac81a895617d280429cdfbefd9eb2e565b1b2db035eea8e9b8570edcc79c79d6ab330524ffc21477eed62399cf0f742a14ff80cfc9d0acfee65765f9fc2d91b35100397eff9f725bce47ba5f01b8af6d077eec87832ae1986c206d8f015cdf8019c44c71771a3c9246ef522bf7037aac84211e920f632a70f5fc47d2257149003dea83c2fd3b7203a8c0193534218a7feac117a03a4e6cd122c562c548ef9db4f5fb21c1e7150272ec9a767b761b8120227d1462f46230a1aa43235e867991f643ddbbeb4f972a5234f4d5d1b9cb75d09db243a7defc9201d1f8e33bc8f902c4cf5b1c4b76e772f6e4997ba0d138369ed51d0d0e7f47d2e40897c09ab03111e07da06705de52412e1cf2ee6d96ab703d50084ccab8b41cab87231c88b2432079e4be8ac6583010e63f825d9d2593e5bc2a7f4cda371658be5623e4b1314bcfc00dc0f20f60d6729e667f8f9819e63562331d313202840ef8f640e6fd961f851c5ff6bf4ce74572f252a12f4113649de68acd46d483fc7338d1bfddc0f3dc13b2b232f32ffb875beea530e9155e79916f1a39f4d642f49d8a797df4d51b08d54afd2da2accf2e7270dc5c0f0975e030e4789fe6c2e88c06a2efcfdf25216c66e9ed8ff3c03f9372ebdea6c2691f2b8b0366ac9e31150f31903e103e13c0889cc3af699e5fdb84665096f26adfcbc943bda8793ad1ed8fcfb76d422897e36746bdf33d8d3e32cc3eb4670e8f069b57b6c53d230a57a58bcdd74be53cfcd69ded3baadfdce14c9872227aa59ccd0b9f0f850ef3c8a841cd01e0e11edbbb523efaed28589cab7e817239779c3a0eac076b4fec6805769df84106a54dd94f21dc109c5ad37fb7522454337765c8910a078510627e376c2cf3b57ea921f51ae42f59c84036f358ae5e72ea60e6d3a68875d982e1273ee37febddfc6553de00d258f7b5b78df9504a5f72c43ded4c583bd683fb25aaa1b0d2fc95657df812a8fa307252fb0e7924e430535fdd5b8d7b0fbb1aea335ee02c5696d9d97e26e4a0a401298ea3410efaa2ae72c3a8fd00adb7203300cc046440eaae3d1ba8e191ecfbebc67cb8a75d273fee72a03442b05cf94c0b8fa1800ffda1a3ebed6d1b641841d0e687c22d180abdf06bb48264feb06d231d955f7900f94dedb4baa4f5ce0b3897da54bea1cd4f2881726ed913cc2aa123a1c86fdaa2b52b4aaa9ccbfcacd610173b68f378c78b9f3072446e2da83d653932c8f07baba871dfd520fe48fef15bfba7d5d30e8574fb1f72371ca25658f38a3717f754d3d6dd932b8ec359d949c28c8a20a76bbca2582472fe0208b583236ce90d6846ab505d3ba297fdf00f6bdc6db6d855be2583f473728f0da65c7e3ee4bd98bb777e914ffe5b978da794f13305498285fb3544e3f233b3c0d72365bcab67cb551f16c74e2afa78c8885f356c0c5d982d6211ce75f23875073d8834c7f404c9cec10789119a62dad25689a39f9318d80a9cf7c1a4002ad51aba0b6b5bfb56bf1afa94f80b61153da5d8d96358a36ba38155f49638a2722071be99078bbd5516d1cf019f96181c14fe03bfd3904fb7130f6f7aafd2e215c5d9fb7d060de9cf66935bca36d6d5cc6b47ba398479594ff4f74e1d3bc4bb72d06dce69a0db3eb77373bba49f9919ed7b1bb60bfac35b021685d0009c9e9372babee6add2d704794057af807395b185c964b2e562105efbc45289edd0ebaf1643955818f7e375484408bbecdf8fe4d0acccdd4c5bed554096fac86447e22972814ace3d49b3ce454ba9c55caf2678c408fb8265ada7279bf241bb57d043ca72e5ef65d43155c7ab86729333233512320777aaf8d72fe8815b76b8a07dddbd6a36b8fbdaffa855a69f83704e47413a8c3de99e571e516a486f578566e8867b722b9cc187f137593173f5b42bb72ee4e6c95f516969b17bceca50b3282cda2c722dd513ee308895cb3d4cd5ff2fe4244414c64235de0af17a27bb45f9bd8325726b04fee5488e4f37d5c14223ec4fac7f0b68af3ac85bfa8d5b62f89743c9eb01e719bc57cba47ab91532f90ed40336cf764c42f0442202617fdb58a29b95ee725161693d57797be3e08cfb12dbfcf0f74788cb794a00e7e29b02d6d81f4e6140654debc4cec703ee4445b258c22ce658c908f238c5bbadf0ae3374368e089742bdd4f35c053bbdb74a1fd2197d25d9a8dd8c54f5458c282569f5bf9060d3d412f05cd6cf2b03e85029b30bf18d61842009e3b543c2aef57565f8758153483f7272919cc8b054a638036bda02ed7bf19388ab96e6d21a92c34f825c8967b5404331e6bcf46ea38ef601c7252bde58a4e7188acdc2effc91febc6e0c0f8d9d0f68ef4faddd6e01cb8e6cb547f81764b2f01d3c4f0f9d6069455b1a82cee361027295f1bfee8906ce1613c91bfdf0000902248c97c5a548306045405f1a50ba827289a6e476cf411970ef89d4a2700dbe80b820bf4083e8c449dc7292110066d31166e283522071bb56b4f1d8ed7c0d8da2e2eaaf22b3ac359436813578222b4d4a83c5bc79511dabe3c60b79da598ac0029e8e5433a3cf5b5bba65d994bd3fdb67b6365103f6e3199243ebdb3784e9d52cac7db216897a4c33f64aaf4fe9d84d186049f620c4b70c184d0d22b598688b663d157f98f16128bc439f95474532b97225a208a7779bb4734f8013f87ea427e542b179a1fff72b12eb197c21fb278d7243c9250ad15edd3274b457640acf095fe68d670ad5148a7b76e07b9a73889e724e8957bed2cb4b8bee3d07e6b5ac49ccd1332e9b53135bdae7a12d40d2c9f97246beec2067356b47c5ac889af215034b32d262ce7c96d36c50c3e12051720872965fd5d441ab1af729592b9488fc65bc4da1fe5414a6b45ee1aaf69f4b5ac972f60baffb422909a5fb45e7d00f5a8eae00600bb5fbbf348d79ec9bf317c83972689939746ef8ba5db8f4934a989e15ac502d17c9159b0dbfd915e4227d8b4b72563a3333c558b978c6a529a46204d89872c071c923bf6536342f30c1ebbfa37215534b2092abc932ac4bd4349d1089338c5cc4ff8f07bcc15f5dca094bb0d572fd1e648877b714f6103678d1c38552a29212f68a7d161b0e83c4b128fe333972e8d11ca700836d774b03bb467d0877e1404c25216a35c66ae63d6637384d75729c720fab2420c7a4befbc785da7e539beb5114f532f4a73480e7ec49b5e0a27220588161923385a9a8f710e61c5810e87d9b365c2ebcbad0a60f555fbf742572d7d9b103a995dba20add94724c7d63e953e2ea8ff09c939b9aa1a78bb647d43ea130f3db40c64dac1e2fee2081138fa1c5ffc98ae60cf24d7589b338daa43d6c61254deb462f5463f09c2a7f65d88cc903d3062857c098a8cd8ada78e0225533cef9563378c1bfa9ed9319769d7bf7ecdb2e9e814789bcdce3de7d4779f7a272c2dd33696e4ce6565471c04a82a4df4e3b58552984aba39825a79f8abf709472fe100e856d76887985d901ab79c0aa591392463f713bb3c684bac8a1aa9daa1aef92a4a371f2aa15fb80ee23fd47eb7d19f7dcc9b7cf997ab742cbd8d3f49272783618c1418ac87dcdf00bf257b3ceca05c4faf44f6f425c3decb1a5cf7bbc7203a657320e3094ded2fc8bd70be7deea660505e588e8077e959101d64acde01385dacce31b8e1dbc6a34eff41e94dd0ffd51d321183df1abaa2d3e1bfcd0fa470bf3f0fb43c3b0f0982c989de7d51a2b1c6f428fed45f74450963b76de6df772c7bada344a3eafa845636381a16b5c5e40cbe8f36eb885aaf0e522d4ff56456cb72d68aae9da5cd57567a39d39373be72433657a1ae5aa52feb583daa429637288beae55958728a98824476393317b6f207ffc9ca627f9d219e95cd43cce4f7259ba2f276ebda42d2035108522ce2e6a60c4afc96e6ba0a01a75bfb9c33f2e72e3720c4c444efd1bfdb5c4da758efd347a4e8c43a9835b4d0d6a142b6404db6e16324f8b0c0230cf8728270099a8dc4131f21461e8fa81cc78c78b32c899d77241de11a3404552f21dca420516c9da134a447d642e02e61d1fd457ffb43a302dfb15e6422f16725cabda0e154bfa37f9bc6ef13816eb1654915db633e1fbd22a35c4413290570ff1298e1560a4a47359ce01a4d6ee4668ec2b47ccd7854b7e7208a9ff6ec2794ce8f0dcbcca53a0223a8b40aa3470a7ba76ef43dc93f9f7037264654ab1b64d08d703e5b927feb260d6f569725d9463ba68dd018241adc1b4729147156dd99ed79457ef18d4c50186a804e96d8d48bc344fdc6e2992a69dcc724d317f1aa4a4d963c240641ceb85b510f0391fd8800bcc721bbd4ccea739b1727f63171e9bcfbd5614d2e4ece9da079907562015ec4c75af0ab2041848a6d27263e52de20b303ee78f7249c2f03dffcc633c4ba0d384ee1a87e81dabd902a072c510cb842e10d0d7b82eae10bbbc8468fa7776faf8c4adb260884083f4e7c962994f66413fd8d1fd1c470e76193cf369e2e4f6767a832e065faac3fd91f0060bcb406babdc640bbf4c50a88f068aedefd5e6e6856b5f39c3a0dfc8cf5c3cc67274a4782d2e48171990ab5e223db301ef18e5ecad1ac6d1921f9e4203fe278b3bd924af18e2ee9ff29e7097e6e4edd3418b1212a69022f1f71bdb9349da026c7231e54d43ae24a9c5fea3ad64be8f49e166a107fd87ccf1509bf565a731019f5f78b8430ef98023d60bbee2c8f7e54c0d713c2583f6cef326dd3e65474bf9f072e122917d5600f054aeeb08a26183a6098d1eb933ce6235f851a82957d585a3729f8b1f7681a8996363888e577ab4a49febd18fc885634ef79d8c72a880ae1d4e0aa8c1cbe46d2bd3874d31a186e3d8e34c204dc5f1198a954f8f500cf2b872678aac4644859b9b52f9cb983484fdcc2af33f9293d13104c5c7cf8c4c63207d2a1b8ce2435b02aac65505c126194847a61003ecfbd37f518ec1379a8e6d91040beae06ee430d28c03c772987bebafa1b5fa28d40459203647da3f2bed8367ab7214f5816c2eb943ce587d42526a49393a3661810b75afc50011a0bac9ae4a3672a23ac1b089443ae56a74729715c60576659881f64ae8d18312ef47d2185d1c72a8175c9dfb04275bbd1178d4c19b66d830fe6cc225148ae7d22f5cf99facf601019e3b9025384e69f66f747bdeb2add6bde0f21cdeccf23e1c796102f812fa72da9f631cf19dcf78dddcca56ed9e9773a22b8acd4a68f385010774c368dcc5726a7a73e30a3d0f6c0a4b7d4eeafd0adc0e8610757c2d666d5be0094a5d6bcd722f6ec3e97eed2a40803b0b200d1a2e8d3a8af8b90a587ef824a551cb1ab0cd71dcb48499e9cce7423514a6f8b23a1db6c6cbce577bcce24a61666d50e70216728cce366ee12a70c705a558493d247800ee81e4b65abfecc559d9fd3ff736c4697b3daeb558406d83ed31052b072db58b06e51d2faf98a09473361e253d5748725d03976349b6fa4b91910b4a25fd8f49f4d0637cf8a46f384527a66a4ece0e72519eef1a5ba2399aa73ea6737c65d57e30095dfe8a25e00d9f7d587c04e2df7229f6a9388bb9bed826d6abd175b9d5ef9b4a8e8a3249683035dfd03689446172b95898b0bed8467bb16fdd4a9fbf925d4300c61a6ac191968fab81cbfc2e8a6b5964c6d712411a7a605c2dd8a62382321a1307a45bd9a17333f839bc32bb4c058f1d833baf1bf49309d465cbbe193434cc132fbfe0d420e49fdbb257d0a4ce728d5300d686ca9fd11cc2dce37ffed52486451d48923a8779113050d18415dc5b0c53e50692779a49e572d07f6e3c472fbfc203ad98f544015711ba2880468a70b93bb8e7aed1140a3776811285ade9dc02f61d8623cd5862196d09a7b8da6f21a1d1d1ea1c5899547eaf4d321c3f87084fbd7408683fdbd137e437d64bee9f696a1219948020baba9d2bdc1b35819b88fa2079ad464e57bf1e39087cf5bd5b121a7d126b897c084997da341d5c3c9a827dc9326f0b02fa0d0adc264f5b0b3c7200e82fb43c9e82934fd350c06988c11fc6c2e17f265f386bfcc8362035dd847278e732b5f0acc376489dd9775f87f873d9ae78b2ceade7d7ce4d6a1b4c90a4722821d3f57651bb159f4701b06b4e500fd63abb26f7c363b10d1d088ba8e94d72effb5d30dc80b6051bfaf197ccee8ddec81356f6ebf896050351af501e7ffa7234cc346bb6ef240e9975fec28f085f8687cf69404cce995a1d02d2c726f3e772ab5e72597b387687b9ba33e0d46839636f462ae57f74555128c0037982b9f660bd777d31740475d27c0df04131e8bdaacd0163c35cc95607b7caa54c5861346b3f0e66ab2e98284d81642e743f035e1f00c0e4a53296ce3d4b5412fb21f75572e31456c7fd68642e3ab9b546f0fe05dcc596fab45ce4719596cacfff4b0c50319620f85ba57d71783d6a733ffa3debc97c1a15d6b354bd6c61b42c9ab1840c328e4a41c28a3a71ba25e494746e43384cb128eeb1a638fb456ed8564922f808677d781aaebec06af88796ccc7f754472bb93b632e6449618b9cf3658bb9e49105fab4cad4f5cd1861f620b0d9d8b03e976172c2c8aed590750f6b0e1400e6c02a5e896a9296096a9c53f0ce13ffc125404100eb5d52943155f1ee0a89981f7f3b491ce82aa28d761a4095427e9d522d8a97dd1b68ce7647057542e66e181e6f72a3ff28fd326660fc21057cf753092d3eb3a178bd02629711fbf72f8137fffe0f30717f561a434f76e8b33dd0bd04ec6103c956c8afa3e38b15db51da18558872ad5946dc3b8d6e74ca76f84e93621aa3269226ee6633d2fd7326ecdbeb43627212852df8eadbdbd3fd31afba4054937dff6e80fd409adc9e45434243d9506672030b28d21458ca51d0762a5e38499f93b281d867ada102c67b2931d776992f72215fa41d0e36b150313215d50f729b9e06589d804be2850ff92d7365282d1372e621084746a1c02397ca3d06147c4bb58a7196be522526fea21d66daca83fc4595582eedebe541db8128d1ab4ab0780ec9e2ae45b822deb045c571b5a4f82b72d82ef47d3aafb5b1e1169a6d437dea8ccaf44c8ae7781e00d110ed2e959e3972f5925fb33ca66ae9b563de1ad12ef462386c58990c4952aa246d001b382ce7148d094d807b475b2c717917bbf31d40b5cebe6cfcea05b22dc7642110c8e01072666e83f44ef46846c171470b476a8677a4cc060a267bf2f1281f314447445c72aa91d4d4f54a8a6eab1fd1c6029947f17d6a18f43cfea8e0df7d9e2a9a874a72b9a708da49da88fd1478053e9733344a4209f5f8482959c911d97264bce0fa7235d9b076872a1af5e7220d8d2c21d48eacb421421339be1fefc19d8a49451b72422cd6baeba1ed8f02941be181e447770c0b6434ca7ddf772833c38521078f56294af423df99b955554ac95e1c2f3a2ffe042ad9dbaca89c4649ebbb8be81e722923949047af4899209a74d0daff71942c4a1df0427f53edb858092ecf011d67278b9edd57c8fea00640f541cde9d9186690a7f904c5be0343a7fead563f0772ae311fb9ea829f47927ea770fba42c8a6b839c93684c982ef027cef6ed9e72725498f41329e6366f297674eb9114d28179272cd1a47b4b035a7e61281bdc92727962e5a700bf0c0a74da467e387d45597c5aee014af3c035eb6d71f0400dde26ad4b440192e707789de8f956301fc36b4a220e33558d5d329708e6360360b372773b6350f23db96709d86ca2e58313362fcca156deb4d8e7a92647d30b848d72394f86d8bb6527048469382fcffa75b36c5f74402e11f9092512446a3e557650f2388a0e51d6d3344dcd87a90821bf2e94275ba29b2c219b968b3b386131cb7284a3a2835b8971f26794b1f3b43fbdaea019bc672bc77f34bfbd59512df9f472a098afdb50d52a3ed70e539f966f27a5bd611b076e6f34636712e0b9e0213e570956e87136b530c5ffe3fe074c75830d71fdd6627addcee1e6eec7a713d9bc31f3b0b37c9f6cd62bd9f7f79ec0ff815d03d031e24687174add1a4ab82225ab7263dcd6a6b379e4aa3ae685bc71338b9bc908401be72a4d9aaccffbfdfb8428729d408f594947b49a48a8b327c30eb075fc09a6cb56d624015736b4043e6ff8722b37f8ac72507ebe99fc71262ae31897eb8ebcf99a8980dd98c3f463f96ccd7226e3b68eae832540203d1343a07e099bb78a864c60115030ad494f7ab2e41838867060db43cb79167a4bf8a00d940e961ed9b849b11ededc170a8b71ea4e0959480e629fc0e3e7faa984bd907141807a1951644f63179694386f91544cc5b5720a6432e8f5068ab101f78ab66db886f093bb1496ca4f0f2fef7a0256415b2f7267b6725a17fa3630e164e84ba5d243f40982a7328d6d5743d038f2d5d251457229c2b093b306ceb1363722189796407a8a4b3d059b7fd87063eb1bed044e28067d1e321a75ac0ce11afe952ebfb2ffb2c2c1aeb687e0cc6009eec8ccd085d672c3cec9a20ded89e12b068be3fa6f569f1bcd234e577808defb62ca6fb7bb091037b76d9c1e224492966c7c0b0a7d5c65163350217bd74d41db72a430b97f3272e927df6b6b62376171b5a1388b7dfe82029727cdcd9a37c167f37a0528051b4f63d92e966162a9ba61e8dc36608536401adbb3d227f4d03c0540688d909e2c72154078123f46617edd91c38100132e73c0c782fb4af498ce86afe8bd68c8267279544bb36486c79dbe94a93ac41ed61ce4f9525e5118c35a7aa59752c9a95a72cb320ae2023dd4f07adfc2fbd49de4afd00fcebc156128b82a734588021fec7210678da1eb116fde1a03b8bfa31e89d8e3fd9a0a8a3508701101f4b348186747679c64743d3202746338df27df61e61eee8a2e4132497418c6242cc247b43872e317cac3fe42d76e5089fe9d8402d56b8d4b81d3e995607cc3fc11a6b89b0402a21c8f3c1890a52f65cfa14bb39d214a6c0f17831287b1386595075e2fe5892e97d2c88240863c50d3928588c4607b8700e5ce75f0b8fcd1f72e0dd93b91813160cb5a3b524d78d86bae752575e2dbc3a5c452c4d65e08d61a632c0ff1c54e72ce1f1491455cf2083f9ad39d73b74fbf56fda424bfd65c25a571937910f248710e95b79e451aec4b7fe628bb7b91d5c6c1bb92e7a6f9f82153f0037b14700442f03fc5c7208f6da972b0086cfbe79bed3bb855c96f8b0540707b7d7ea6123420f645efea8d6df05f0857f0386e573af67a420ebc569c665a88ddb55aa3b9f0720a637c540810d870496193399375374219ef160cd88e989be568bc18ca6b595b509d7fbdec0bc36d00fa0ad43f65d9e05686a1460af2e2cf662cd255677bd8720d9a4e35e543422027a0c3043f4f4b7d0f258561054f48b616ff4f1496e83f019b25850fef0bcab199565c6c94db2bc6c31c52b4e280df84ff5ca8a80c2edf10b8a80c392461ba14dc4309a73c8caf5c90736c8fec572a87bbf3aaf46b53ed4de657aab94439d85b3e93395dd27bf7fef5bd3c14b4bc9fce0292f9554d62d9725484a05f836b77d09b26bf7a0241cb7d10776274916f6f4d0ac8d45eb9a8a11b3d3e784bb5263a4cc44534f7cd08831ff42427fe618a96a92fe546b2085ecb08774dbc447a4a1660ed1abfac8808bc0281b910c93322c4afa68750b39b2fdf72346bc0dbaacfbacb1234d2f0a9824111aa0cf07998cff6d9d25b5ba855ea2b002157a6bc72b1e635ba6ebe89b08e88f74a9f19492950b31abd129415fc6239064af816c853c700ff0b0617bfa9db3c270eeba7af05decdcff66204f6d76f913e201ceb62541615e1f9c3cba6f93968a078b08c038c6cf5d10f04dc8677cb82723572eec046a4b92495055c900a50b09d19141e2785ef67686ab2ff9d26534e6b33bd1ce7ad71185017db7d92cf625db3923699de8b0f5f2b3b8655fbfe8e56727420203c0f37e0cef9cd1a2ca87e113beffce54b58bd5cb7f76b5d9cfe91977225e8023f4abe25564f425eec2edd573c2a311033fb25a2c8156dbe93474f163f0a21ca2a65146ba557ffbb7a89b04c6062c1964a9472291cda53b50c6d8282728d365fa1438234c0e27d0efc854e7ccff8a09649ed4cacdfc035a8defdd7455a3cd24305925db4ce60699b476705c387a5d68e2c037ff0b905c3ec8fa8e8ea72f57789af69b77d387075d959ca60f94e365d6e24bd07ceaba1a35eeb8fd03f0b65e954b76e749386d374fbd34a242872370904d08a3f7a150cb9d5297acb4472e3d5b73fcffb1455c668189b8c9652e05f68c0e764b9dc4e4cd1717b238aec5e67715aed15f470c6f98aacc9cd861d7e196e8afea0f1d2bb319020d59fcd68722a072badf1972d448aeee45fda1b224ca8a83aa663a67dd5b7177a11dcbe36723357dda742013d5813db8746721c93e3fc6866aff98273ddbac12a4754b73a7208a5055d9e72824c837768d278c7944f52cf98282a5c330043e124fc4c5f5b4305fc249b037c8b0c0b48245181fbb9d39dad6bfbf4b6b93ba23a8f5256750672396c06f5d70cc82f08587c09172a97015f897a9043457474925da288e8ee12722db01ab7f22ca5e752120d245a26b362169651460c702ee64838e8d7008b230ade3ee0067c533f5291afd9628fb231231168e2f97e0f32321837b649222e177278d9bd409f5ce3479410498f48ad0af6593d3425dc0aaa4c601a1c984371097264f77f8d9793e983c5a015bfd5e1f386efe75c91e5fc201455fa292cd37a177282c21714beb734d1721b842262434eee8a27dbcd61b0b63d86a0c8810fa55f72140e884901c281c56b3dd78a8e888ef4768871c396c81c609635860465a69a72b1589694d2a7ef646771b328696170ec41abd6b973231c072ab23220d9f82f5e6793fcecdb63f931cde0bb03808e396f581f7526ea7684e6aca17bda68fab820441a11580be91923124741bfe75844b559c2eeadbf2c0733c4b86421c5f2ab72e87a124c3bc21641bacbadd6cce69f22a07fc52ad030a7d1cb7b2388f7534f72cb31f6eaba55cf9d57db4695643c50ebbf43b084c0f079e6ab307edbf0b00b722a7fc4eeea15f18ffdfffbaf05126eed3fa5729baa25ae9727c1922bc79b4872baaafc7b6146862be9388f3fa39670aa76ddfbd855bff62d28a22b24cbe3be0712f120547c48a7a291fce071997a4f49e43a77bbec341d29a4a48489399a1f61726d196e4b34d1d60d4e0495a57b78b6c032607a4037423c101bdf57a69a321a715421b493f376a74d4ca56e6a33f52357639d101e1bfc07610b38a22ad28033e4a53dab00e9ba1fdf8d47891e3cc1f8e250b5c5091e3fd46e0d475747874e7201db07b699dc973277d214168b7828726316048906863532c290ad8d1a0d46178b8f2029df2df150d2dcaa6208ab64ae6939cd74703f900b971d399adf756f6a782d9ab5fae0c3234142c3f31e8f784b42bc2e3ed452edce0273e8331b8a82726bcc725e33bcd76316ede55bdd20771bb1822516efaf3431ddbd36bcb7b0433e34a95f8b8031f5b877aacec78125f72f7e1e1856759e5122f5fb0460bf9f39726d6f7fd614716cd29148a58bb874f0f06c0e8a60d5594ee9bbea65723b383b608ace7f544b86d1e922bc1827520b9e7643aa1f5fb4e2a2ddf3cf1db2326604720a2afdf28c5414950162a67c64054c46bf2fb4d47ed31879cc000c1feae21a72974a8123bbc97a5da88ed372001b7148dced820a062f1877278f0d34d02fe27248632effabb033bddef727e3049741752eeeab4104ecad2579ebcace9238341d7638da3eb866ae0ffc020667f4873005f59ad654ae711d2423de2367b88696729f68cf89026c85251ca06f2e07fd27856c8181e803b277e8e0a9e823187c8a72761a0ac9dc14d7ec282e8a2c404741b453d784700670b1e0921d11c676857072eb615b875e886729219576f9861fe5b77eb12dc16f427c903613ead58c0de872078e25e0111a9f0dd0f9d0479ceff9058aaddc05d6afe70fa996423b968f4655f7da53aa1853a0694081a27921792ee5dbdb934b0405e8918711d94d4cd7b11402d0caa91305f4e0366ffdaa97c3a7296e541236b396855ba7a0f01e67dc7d72997e010f13a601b4a580e4f23bb3b6aa8263a569b322af32e985af4ae0279972ad1c13b08bea856ce6505f47c5db5773ee101fe8eb1b357b7cc41d2f877d8a72f940cf74b3b3c0e5a5635b452ac05e79bdcd597984633ad03f2b64428c0cee72209401d3445b698b3f770ee98653ed73298fa88d171cdf2f570b228f5a395a5dda5a9431402cd259ef22e86a17072ac9f2f68a4214fff859d2b4f5b66287c772ad6f75c3fb7690dc4060624d4a1f818ede5c4ceab70a05ee18c77e8ae7469a203789dd3495504a43ac3cd797f78643b63482ca280329b0b8ac51168d6b02c372442a06527cc780278caf7db7401c3df9f28109c99540d4160d0d32cc296c4c12473fc941642ba2af54eaf57a11e66768ef95caccc501c99fcceee5746479722cadb8ba6d6cd820c1628439d27b871906ffc70a4569ff339655fbff7ace67992df3801f0c80436c989d1399e2b269e5936d891d727b283c3c5ad222f348f22372eb921721494b801c67efd128b8167f5b4a95b0e9786cd6a1d1e72a481cd70572fcbabfee5e975518ce0a84d2047579c985453480fa6a3d2c8d582b3dba4ea3728fbe9e21ce90658988e13bff24827f5204adae32dc6045a9301374658cbd9e725af22b18f0149e063af7270700631e1c75cb935e941d042c1eb336145fd86172f8b5d662cc65de6e4c87ab98b79ef6f62ce745e333e309767fc1d5bcdf88b5726a30257835e66f68cc1b9890caf14358ff6fd76964b571ac494de8151aef9c7277282c1d40a085b9cfe6efa9e41629a048d7339c32e1eb51ab31a3eb2638fb72470c569ec9db6143483ed0af047cb5fa0491272deaa19295830d958fa1719972a927e4ebbcca5f1fd57bac08f6b22c6087153e217aa68e095ab566c0f3662572abdadc35375b0dcd2779766c8d7261b7fe90d722eb5a003c7408e32475fc130acfaa0066695b92cfcc50c97666575f84b3c01bdf7522c188e0b56d177944f2720b94f02b7daa36070359b3c6ea07779a1dd051ff74616cb32950b56d3dada90e22c76de4e482deadc77ab9169f7c0dd547a1c84234b9bc52f95e78fc265e9772be1d09a788b180f3087d6a2619b9538d9bfed6eeed40e9e0bf2c330ad1b2ab3e4cce7acad1e2ed448a1751815462b36abbb216410a9bbafdb996bc37de35bf72a5fd6cbfb14b77e35c0f566084c5a397c03b9f906351f01ebed2dc1df4b362722b1b4e9c99ae6e9cd9da8bfdfcbd554916318b8456111f29a975beb586824f3746c05304202c83bf93288982b20be2bb47ef4f5851c5b89ff73187e684989b7226a6689e0929b545ec71e738fbb9a009f053ee64a991559b14a6b104b017ed510ddd278b3de36c1e36a207c8fde1bf07cdde4f0ea6945a3b0f7935bf7b446f6694b64c3d0c4fe6f75e36b40a8310982b91113401d488f279d9defdb12697e672a96d4b46db6ee81e81640c3b23345adda9453f700d365d97858f8914f55abb7249c2fe23ece8aa3fef601465fc0ee776e33e24371e16f4cf2cabdf724710da727f440cbefc5b0ff4937b370c8789173584fcc881543c747df7d27d0262baca0b2beadca62733b5c00fb87ed73891c3d99e1f19d3f5544be7f26f34f6b526d072a9fd3a8f43e0ca0314c310ff7527f776a301eee8433258bae82c268672b6322153c97ff15469a636ecba9c5dfd2169b30b380095a5e9bb05b1981653bf6c3e401a90dacae032a36abddfa584c03e21d465490ae006cc2a2acc5cdae72d540b721d2356409fc666940b53c676787f7329425233d795def0a2d186579876215f72caf0a07f9ae2aa3c100f49925a546663103479809c7d6f7ac14659e75ae40472a75479e58b3d45dc8868d0e6a50ebc72369b1bbe603b804e5cae7b781384f472b334b40bbe39e7aa228e893c3f7d979d8c7ec2c491aa2cea0e7e434ede54bf7291c58b855ee6e7c372f77d9cbf0da21b38ba6acf7eb8a775cc12a9d9e0c55d72b6bd2c2a17213c9197f8f0893bafe1b206a2b8abecb3d0308f31176366dc5d1da77ef8c09dcf7c41904667ed41c40b2176bda5b3a169132a8d9c63d600e86a576b48e140f57d61980a10d6b156c235b78f88ceb31a99af43f288123dcb09643e51a25c5918e5a3098a237099ab24a506fbdabd0c5a9b834085b0037f77044a086d9ce0c0c7d674f1aabe14fe2c239e6065956016144dfb664a7b8924996e5603282454aada6bcb7caaac60abd44f54d634147ab40ced1f72628e198246b7dd7227256aebc718f8e6343e1778be23cbb60def4bb79de713a1efa436875a49006385c2762b022d688d8322b0ed34a6a8e8d0d73eef64680155cfb11e6a248f8772cc33630381a332a0842d5e39f22c8d19f08c0f8b39cf9d46cffa37f5b6c9d272e6c85c1bf7ede8d0b42c24b851fa2e798bdb00140247148b0bdbf302522f63416aef66a3149d84e6697da759c2350b121cdd32938f4446dbaaf0373f039894124320ccd1cd1efe3a059ab09062ad9443faea20a161863f4c44ac16684008a972bb44231f99b54d21f5a5d258e6dbbb0096d8f84fe9cb8f5a7cc137aeb82a4d72b363ecc0ff27be169675d9168dac1213d5cd8d3da11a6d6c3eb61e47216412027bd5c508a800abbb5c0c2bcd32c74affbf28d1961ee67fa58d68b303c92a8d7264068a148f1aaf1d7f1d7018b2077d4faa0f306d660fccc3445993effd38326a7137af5176b5d949bf5c157af29f37d6e5069b7c51655d094c07b16aced48f72a3967a2a08e8f27060c7d7e0b49adba157dd79185b9db42fde687c5a0c72c310487eb06a51bf642262ee039938e190d077fa869e6bfedda683631bd39ad1b9720ee980e1a3ef92e856f9f6a01eeba8b6df0ffd24fcde455d7e73624a719e9b726e97b5abe8f3c7829328ce414896b295add62a5f0847a6ad3f8231de71b89572c3887c753e4a8c664deca5e10bf0c1327e3390d4956249edafd91d19fa49b6578db27ffea6a888873fe05e80f42704fc7fdccadd8557ab9166a16c612aed5c72287f0c40e129fe6cb6123135bfaeba63aa4309598868bdfa9a9487fed827b572655b1eb0e6bbe995b5ec67555b60c7afedbdf21fed2209de1e9c1e7cad10e37264b1fca1c2c643443ca6969130710ab2afa49969f45dfa124329411bdda6427299bbd3f87e03bb85759016929a87db37b4268a39ef7d5066f3492c46ac3c3672d7add99d9aab91a9818913a34374160c44251105d037b2b8e9ff8ee4b2662372516bd42a1a54e69c8e42d6f3cf896e99656d8da048327488f4c79f1454504172f29ddeb8f3837e27e00a16adc2b7066df9153378a9c2f7008a35245092ba9b729b2a356f31a2775c14638ae5f3b96fcac85e0a573dc38d6853d275dd7174a5729d0cb2f8aecfba27922b4f2b1def7f52d59730c8f91a406ce56a5cdec39645729fc4c08fdd3a7ff3f3a746624d99a3193d3fe4caa13b118930c8e9d550f54272817f472773020dc9d141099c6446ddf6fa8ce0b84b4adead2a67bc9d1214fd72ff8254c38231dd015f70a0ad19751f3b732c6f9fad37f4f88222dfd36d7453725030dfaff348b752b195fc1ac205481f6518a4366268fdd09680b1c2b59d1b72cb697a62de0d956695d1ff89f39a70b653ee6f334e355741d2bc58f0f592340562898abdba4d81fa0864d48efca84a57952304ccc9de0f284605100de8876772bcac28610d15f8eea0c22ff479810a52b8332cf20750c0ee21ad0b8e420f5b7265a7a591777eb8dfedef5c5a25e429bd3acd6fa83844ffe3d274ea2337244b33ce02ff9106217632c1cf95191b16417b450f311767c576abb481b4bf15b2ae1fccaa7a973d2dae440d2cd37b13734c6ee383f960197325e6d9286dba1737227248e963858a2e7f25a898cc14abb7fe086cf2a69b4808e23fc63a5d4dae2df372da27b42427f79abff89141c47e71855334cabd2e3ab4f8788bba390ce28974723d298fa6723ecbaae1bbe59d95a2a3719d193683980062847004284dae2e8f723c9b9d24c9cb341179104a47212684fef9ef69e73443bdcd939602968a72c37288166ef841ee64afa8bca564e07f34dc59eae5bf7e69a4d1ce4d87cbd8f547721515b67f89b3abf57f41fffcfe2f058c5671aead187d5504b0472501787bb31d469b81e1a02a625cd765da5d659b54759ded81f4d5211dde01dc9675ada80a72c4e59beec7aafebd442d51dd63b9bf3a5edd493a52f98a505bc108782da21109a4af062de0fcd6cec4610a16433bcc57e985f6d671d7d907dcd145d250d72c729d50d185fc614fb8f0cc3db8c878c56ed0ed9bd654286f3c0755bee3d40f2672746f811b0b0cc024f851cdbe80d43dc0087d1da728ca72b482d3016070e4144fd74e237b53d3b5aad381aaeb65523d234e7f49da2fcf1971dd1cfb24847e59724522afd65122b455af5340ed2f957a918b3cd1d00cc21ded4c2ee4a81feadc0c21b5d76b5c304a304ecb5953b03b184a6a1a6fdc04b35b1ffc4158e85f552e72df724bb4b88e09a1d386bc72df8c9a1aaf2d6af77b9f0014c341e331e4390c72be9bd3dd7fb6b68c8410a1d596b247730267663485efd53c6c5ebf5b20a3cf3a294affc2d2c1b999135adeeeef9a48e7a28cc68d1a5284e1887892cb1e175114826e05079c4938316ef919d170dddf14bbace359ef397f983fe0a59cc43f177227706d91c08840505698bf2aeae38c0645266ab8a006fc4f65c59696c83fc8720f59372d176ce5112488fc818680e49f5a59ed23d244b142f17981c94b3d667200fafe67290475bd18e3a947ba319244ee7447bf7e31208623f40146cd8c8f6bcd27001d6c32a42d78327d63edc703a255716a8cc10459177bb378575d379930fcfcb72527179d62ee62090d25ec384cf8e1baf18a3f82453f37d91815e5a0729232124b798195e0808d952bb439934909a58ebc1b221ffdf5c8d0e71a5f33727c70e232c8c85ef4f364f9a9435ceb4019f30e3fac0a1e77f4db4f7c9d8f1a72e3ba56abc2ce4a88daa3488e47917c1c4c8d106a40bd4e4198b12079d0c5d87222acca958d1b65e0644ffba86a74453dcb68a2c95169c29717a669d350335272f0a8c45c8a649a2040d4f6783d753b82dfac3d981c88b3fcaa82c45298e2f43f51473fdb3b2c4574745ea24ac8facdb02940912322d33e0272a5e3d5e4bbe54671097d60815c45fe557d158b990294305ccf7ac9f8510738ab6b78245c8edd72f2def3487644258fe5d7c5687b390731e33a530ba9abdda93a277772d508f972182e9c98a692af96b6449cad7ead2f3a5936cd3e1a91fcc2c5f05df8628f0b243a5f86d0125b8c29f16c11581ef3b0b89fdaa5f01da3aa51e8266c0f1a7c6e35c76e61a3b1ae00e4adefe1b5120ca0046eb99634dc336ec9c34d5ca66c931c72e1c8fb3f7e190eed324db128ba0cc4b1719ef4dc12a77f1142b9244eac9c3d72b1af27807090726eee62bc901a02a2924cbbd5eb75052d205f80538493525e7237563620050d3ef954031031a316ec37a492d20f7b6541980454c4c01f16c0524aa5d38baa630ea60e343dfa7cc19f3964cebdba781507a097325293d61b63620a2578bb5cc6732353b572a4ad02503d42d0f09c03ba6a4311e4521a3ad6077299833776b4a5d0e68ae27c51b4e6b79aba2a93f17569363464f1c45c96c15f201a56f710159c0e7989857f493e82755d1b4856bf4ad23e627b95a884e27cf372aa2db2daf830e0c02b55386cf84b2da02585268e6875178888110a00feb8ce26b8155bdf3d1e105f21c5ac2468888f3001de4207d80118c33553ef8938cdbb720a4e177ddc325e1e38dd1f056ae30f59c2b1dba220266e200ea8f9ae9f1ff642b4a3280e8b78c5fcbf9617e0265aa4eada8583308ea89cb8347a64d02c58337209aaee4d05d3f91dd4e52c9e4a970b62e5515cc2e8a8aa30aaab55b755fd880283ba0ed2ec7b26af218b221da972cfbc0807a8461f7ebc6bd6ab5ae99b358d0c7d0785c4baee80a9d2c6d475ba15e4554fef4ec015db2e27d15a3d85e070357253cf721a48c166e2ab1bc49e6292ff4d7e487389349e78ea18d2e7827f8c9641e8d3eb770a518ce9055444ad475f471a2c11fefc23bba7ab8fe06db53d464c7213e843c6bf197a1b1a68df0441ba633b56ea3308a4d8870b01da80daaf8485723c2a53bb64b57aa2698a6bd38cca565cc5dcf578061b1e25d9bb133c80c73a72dcd287c93fbbef58f747d987f0e20065b59ff77434f954ef4cb5b708ea5b3772021a410f6b715ed7558f88837641849e2d0388359b4410f529e51a7d47d9374111868d781b988b79b9ddf6d30ce9d82f9d57cb04fcfb75aa0dcaaff414ba9a72929cc76abe22e76c2a05532e49f61d0549956e4921bbce79789a6fc9b1897972a4d63232d773de0d35cbe8f8902ed4b76375c55ca9af83fd10fc12854125f872b8ad8a4f94388b23d3d726ed50bf0af2a3424d9fd892d4507b8cdf4aa9bf78726e238226f5d7725c19364e37bac870fedf720862ef4d243bfd222726ac9fa42fb21c1dbfe37933b1f253ae023406822831e8e58520debb593cd9011676c95a61e95349f59d386e74993b3a71bf7530502bad336d53e88dd5c59c2dfb711c62724020056b5473907d3bcc18ec0c1f208726403d006eace530bba44460cbfe8b722510fc383ffd3a5a7e323f0fca1cd2f02d0e04edb32f9c82adef887f9647b62463da6211f6fe8e2c6af9055b1f4d8c48773138581b395b231070e6d843a04372e81959e124868e92edd44739e6d587275119f746e8c9ac2c695a386b8a374a2590598d4767097122c491ae4e824511ec2cf8f6b11bcf2977b73b15d63744024e870d07c1f570ef33c05bad4fad1a559767907f222d41e397fda8173c4f03f21f0782474a5058a635cbbb40306482a16824b2f65bb70524b886a6b32d50b1c67155c7b33ab922faa8ca6d6fd1b4fb387c261ce70136e70111d66142346a771172f57a65e8efc736768549b95fd17c60590cd4d362e0f61a5d6decba8dd9d249700f26b8264fe8243b73d5f1276d39c6501011a1032e4b274a255222b6c435b30e013af2893621c9497092ceb568a17f8369af16cab4a866d999c7f388d3429572cb409de45aa4dcfceac3762632793deacc8dc616d1938d3f429c5923e2718045bc705f663366e368d85033f0c3f81cb7825c412b3e3c543979f52bd9b24460213cda8c2d04db8a64fb4a47540fbbf69e1dc8d8a1a6221c9ff37f39b64032c66c205d34333d6b335a8f933151ae0a87e3127049efcf9e27696bf4f6026737e0405a085946f19b54977d5e8b3f36d93870390c657f70c117b956ed42f94b78cd721e213802ac1c41c514f9712dace3767b21696ebdd3aff5ec7d4dd38cbff5ee7227ae60cbdfea155fe79d94db4343d74b7fd4dbd14f4a0e1db693e03fa2caa872221713ff30b9628102a12a440d37e0375fb8588d91f81b0c63dfc8f215f0e21f8a6fb965a25786372450c889d8bbbac25d496b6603c0d88e5b2bc15690c07272ba0098a577d3f7150d30c9a6e484a37dcdf6f7a147d41a7e24c39223f7fab65bb47392f83402f42b41891dca55b351c7c1124504b989673b10a26bc5df4cf23531663680eefb203fa3b99a9e8bcba669fbb7f59d83068337b50e805102b388729ad7c2fe5cd9d504c8c7434a80f403294c87b39fd24c813a1f7dc8aafc12aa2fe98d4b41cd02477689e32d1e7e81910f4fc4b2166ff9e8dfdad646722c73f072d80270ac6cef49c7257d60db7e95d91b9ddaee9bc54c326874862f0e368462725e9de1d11191e6a0cdccb4ac4f86b9ea04ab6a053de2c224179ca1e33842a672563814651f42fca0c22eb6140d26deb2b201ff6ab4bdae3f87d99e60c7848352a72131be3b7448b2e99411a1288bc421051a41bafc24c8bdcbfcdbd902eb3347d6c4dd76024ee8e9384ee78e96b66776a7e3e7fac7881ad64f337ca40f318b722119d33491b3b07ffa2cd2567792bde77addf547a2e480833f9691ab3a9810721925db4878574ce07faeeb66924730b00f9fb0354d4ffcda5b4d5c94341f5413ab4bbb73dfc6261f0214064b9bbe182b4d67fc6ac523357401fd1bc661393f72e41c1ea2afe39112b674186fd20401c93adf44c045830da76b1a99fb11aca86d168e5e205d929d4d8f18f5521185dfae6dc2422d74d5d1fe74d5deb30febb272f18bb1e5ee77c24292fcbe8a1a0a35746dbc3201bc5bf2a0dd79e19fe9f06c1729ff06b3baf83074d67d182b0fd05aa80abbf0ebdd1e130dbbac7cd717cc6572727d9f14a107f1ede4376a8e05d459be1a145579837c6b42944cfa6f67069a72a9659cf8c15dd66ad785575e9bf7af4962389a631c204000df6eb21a9c1f95729440dae29c8faf8bdeb623c623f99601d821e29ca92bfcee18609f1126e3da2c8a3a15517c984b73db7ff43c719e13b0419fcee07bc9d7555225df68ecbe0a72ded3d42fd854aa18afe3a51754d550ae220c4b02784df03a5e03a53d914da172499918a97b3451ec2f510ec416a45dde402358da2f0fc7901a079eacb82b357218b720925cb831c6715b59bf45f634bfca4685b18c7db0c6b943d481060f420b16cde873cf59b7b3d4749e1b451827264d3879f335717b5d488e4ee84135be729d65097b980a8e01e624576aa314fb4ce7d9c5f110c76e1b8b2bd9054751f27256d91d44681ca19e034a7588599e15f6aedd344eb292ca3524f565f0c2952f72b26993c26f9bd42516f356a5a3666babe1b36d9ed87a4a6b68a95784d4050c7294197a33ce4d05708061dd94757b439a527234b735a3e7498f03131141dd3d72798aed5ffab4f97f6dcd55e5f6e2e143d280426713ab65228f91e0ef7b4c14720abba0470af0c01be80119c72f42976aeb8306c08cf2ce4a44424bb7910f6872d58877a3192d033619a20329b44c83e4465ae2cf40206938f3ca0821b86ea772b7370c21a91e1f412bdd25f9cd5e38300495d0115e7f76009e0fe428b53a217238ee5d11e57ba789d215c1cdadd09df9ad2b25a8c7a5c3b28f7905f6b77a1d6e2711637fa2df373140d20442bb9f08a752c5514fda25d05c494f2f89a291017253f0780c1877a46e2cc0ce2e718913081f3f4274b4f694a7e42fdc5a2ced6d68465ebd8a2290bcf7067e689831010527f8ccf91a206bd081a8106d85e192352131a0985f62274ffa341fdbd782a3ef6760fa30daf2a263da45f289dc5753aa7246b487c0a856c49e349451d3ca73b80e05fbacc9353b88894222816610e1e972eb3e67cc436bcab8753a354e0fac2c87f6a5a242c84db9d828656acd519f3b721bcc1eead12642de5aed825f2b6f3287d126616a6131080bec12d5927eced31e5985e9d2b329a55a5c20a2bd63e99c6b7a2108552820f2b194a8db74de4cbf7293b030f23978e884d712f8d4a4a465e999cab54517092ba5d1faefe6cb7bb6725e2130b629465dce0c0291fc2f2a5dc614409bc16b7bb21073297e5c88734572870a401405a7eae26595b47af415341ec1aa2f0881d1b8dcd6f44d37ec2bc429c046756341f355d5c91071cb4215e72a89f3dc179e3dadfeb1098a63022a9372e2b7563662a2fa420c63407744325afe2059368ab965432c19803331547e8772b8ba5c61b5523b29b6235b8d13e72cf27c4c65e3eca9352cf7acaf93c33166727e31d4d0b54719b2631297aa40d7ae63901a2acd7e87e990cd205c6019c6de728d065374c0f7ef3afee5407cff2ab3dead75890c1fd37e762207027d928ab772bbae5a104847778f5901feb151e99b0c7b578695c20c371809dbace696a5a472afdf19d626c02ed85b41ef720ff6d237bf23c0ccff1b757e40affe653741e3722fbc6cd35e4df61b6b31073b70e7acc1977c0956fc3f19226ed674ef7181f13f1f506b1e4f320682ad77382f8105ca3f6ad539f21d4bbe86698ba2ee13a85671021909df2e9f73499eb0fe4f128c3cf231247371afc4c3c59599d8b6aed0c472858739da2604749c9bdd79c6ee89c33553a67802ed6c163a184b3223f0b49f5338e4bbaadd01afde4a710cf6dea7ca6a1c1e0a6a3699929c932a9a8bf5326b68396b30ccc119d51db3091a8b20dda28c68f6577495403fcc067dcf349d40fe2cad30c05755f06e63d9f895b4913d70e22027f37a7989990718824e79e2dd3272753f737fa5b99a4de3d1bf0fc55b08f40bc285b2bdbd4cfd5cde36ee5e55b8405f05bcb56256a61d34e3b303468f159bd20b38c9d399c428bed57f1eaa1cf87214094ce574ae73101f78f797fad53cc10635aed278575a6a5e8315ab8b487c29592882f13d47edf1d57ef1ffa4e948a5bcf8e72f59ed148f2e574159bd7f5267f5bd745b88d5a123c9ee272bab808c504ea6326ff2ae3fb575133db54629563d09f1741c314eed6586891d030c5ff24004a82efa51c450de67a3f82cb1289772ccb8826edf8d004ff3a6ff1ff4a2f41a1dc6972e3496d950dc722e4f854831220e53b1c9f6c1cb5b0b522d2ed1c685a0357a716bc8d750c87f0f688138c81a721bd0efe083bbab6e55d847f531f5c7df01f538ab3f370644a99815ef120f8d6e0c216a62fa4b897108f56925aab2fec6a149239654a2f32b678e1c2e51d4331d9fcb2686010f6b9f09c2e64477ce9778dea386f5d8ae6a59dd37deee99cc13010ffee6ce66e17a016008d34933b085697e4ccd32aa891dfc83df453a389dc255dce80868e71b923cb4122ebeaa849ce8436140d180b53b35571b7d7b351623720fc7f2386b29f4d8b931e73003fd4db52991365c0a42fbbe9ff164eeabebad7155fd3def761d6d01ffac6ed25a990feafb39502fe90dba27d2fea0c814c29872175055122f877e8ff072ce5fb87ae0e9bc93494e940bea9c21c9a55c87554161c8fecbe672f2f3a45fed7203ec520e62a3a1905d8bca38ed6a14082630edd97248f4f73097b62780430183f81514db05c4c8c4ee234a517f75da50793476201ed2f181ef1f8d14e0aaf33f287bfcbf07ac40584de151802b3e6918db17708d4ff5553b58d7743a1e3dfb4c707a968787fc79c03c568599d260f1b416df35c94acc28b966298d62bad3c3a65c2757cf34220134fe780adefca7d309baf76554726fd666f95343aa49ade126680f951a34268614ed852350735fdefcd1c6da23726ee6fa3a03508786a2aeb7181a0634f5fcc324446f7c5ec4e13fb5b03346c87218a0804f49067f10a5b2bf1693461c93d61133874398067cd45f1ae93ec97a724b5ec1a63e04bb1e8482870398a3ea61778bd0c79df4b7bff3519ba79bca8a7264ae8c3de829d08b44a108cba93abdb7e957172e7d41b1a31ceba2275d991872aba8a6030c30a64a0643f5c221219d0db212c8588f66e3b00d148ba657db92724e8a0785bd87798ea7355ff2c0459ef9801e01039c2bd1caf77670bb0bc873722b3b06ee8544c37a01652bb127745e7ae96b1e8beb4a4798cb1528904f0b5d72759a9c0351ff13f600d5e29c309cae41be65a938d26c69715fdff920b72dd372aa1667d16f7afbadcef70e12f94aeea12415e14303ae150eb21844b29bbc144aa1b5b732f185b9c37af2e39750f322dd75f1568d570552c465a4d6feb9bfcb728236439ab5ca636e0b8ada175e67e23680b94965abbfa81d62c18b8f53af803ba2af68659ce1a7285bc4e841f972fc36e18fe09e157f8a20ab9465cc0f89b41814e6ce2b7cf0aec4f08ce50ff03a9483636916e2c734799cf86c5b9e00b8da20b808fb55de3b060950866023a4bec74ef06a03f61c03cd5c55dd826b106bb6722933331cbdd3bc7448742a83d0c3964b6389c73bd8544e880b987051f81afb721de0930d41dfd11d36ccb6efdf6b483b0d5ff7ffade69ec132c136548d912b0b9519f2b43f3e8e42e2b8e5bb2d5ae00782339a833b7af349aea9841328704e014951b03c528ed0f09580fd07ce4d6c6370a74559ba2156b4a2bee387d0eb1072f23a06c332d2db94fb159b3897e2065d94aa3608456c43fc10a038ebe7042972020b4de8877d5fbfa25a8eddb420a7c901ea12bdd24889ea2e991d8ee3215f727fa91116e08423b329dcabe90fef7664578fe92e4eed3329f22eaa3491846306c01df6accd8ce9ec3deb9ff6e4e34e5f4ec7d68a35211ddf9183f775f92978722b7930f6ef9a2d4afb09a4bea3b6ff5f45467fb127f23d9d74da1ff3782b37722be44c8f5da7c054e3fadba1a6f7ff05ccc35f86ecc5c8478bc63556a63bc45357a89a8958bb5f785ed561f366f57544003db3deea41cc5c1d67ef7bdf77f172e9487d2a051eee9c17708651e91b8e0db60c1a0dda2238f721e000a9003674722c3fd17d442a9cc656f0cc1a47b7cd5cdf729f8aa52da9e562413bc5fa58de729a529d634bd54fe9ada600c5f48b5eef713353915fbf6590c3fd6742f3118931f4484c89f6d9d99d65fc9faf6f4711b1251ba67f9c9421b40fc4cbd042441e42c392e31329e57779224d8ca89ec5f324d2e5e7a5c188ef90224763159c5a04724c2386398e251667f8fba1d2b9d9937d63d1b9828801c2dd570a02c95d601c7267a216d3ef96b6856791b08d4160cdd13f8563458db32fcb8de1368c57ec497207cfc71573102d9da23dd8033270fbb09ee223674f16b52dd521807a2de7567243826b3d581291789e8adf6cf0058bec2168fbbf98dc2b9c0fcb775a8a7f507275a738c84e04f692acac78889e629c5b00d2f3c15be8a599c7fa13f2160e4b5e5b65d5854668482926e5fd4d573bb85dbbc463ccde569161d35859bb5584ce0ac87eaeb977e6265c0cb2ff7a287e8890e389a3c00039740efa4f1aa1a6ab047230ab3a008ade98f6d8097ece31f2cc2c7f6676849ff6697d85510cd0c4a88c1256f78da751b9b9d95b5418bb45a039123a17c2e8aba1f4dbc6da4e77e32275512db340299c5885b34895aba05570dcd9a0ca8819013c932c6068b156bcc8e072ae89a917b6ceabe6125e96b469378a2d4a7ca17b6535f3533d14615215076456feed018ec75e30e0d09382d07638c4f728ae76bed15ea27172ef605d240a4462c1e93c33b9f8e58a1498ad122af441f7a8153a577057b47efb4e4c44f63e5459465c2d6aa7b231dee2bfcdc2eecba4e4911e86d391ec733910ace22df7c68a727e32a96a1bb64b87f4e2f86fe35da6bf07e90cb8cc80c6a85ef56866647c2a728d88c7728f31c9055c74da175748e9902bcdce4fe549d04f7a21100d1b9d3872ca95002382d95184b4885c5e08fc28d052e74f3118530705e0e1770174286972de547138df7aa6bc11d38118eaa62166ddf8068f3b9eeae5e3cce7779bbf670820fc6a3d16a425070a7c2cb7aef3e46283bf359537f1685dafe59eac7bcf85720671700eac31f64d4adbd95ab99ddadd4d86b9cdff25038fb3e469e0a4902772282a586a7c99f260715c34a668efeb590b70f8b2d0ea0a8607e2229fe1ca821fec1282c69317f82d93065f2a9e573b755c333f8d7080f96e35350ce588ae2072f58b76b590b48b14533e5c834039334fac0d9aec48648ed9427e063abd018b5fa5cf6b8757077079ef177fae96bececfc4a6bf2f589d7e1a7c6b6bfd0abfba724c4475a7fe9ca594aae4d433a307f937336fd08d60228541d517138972a6f272d13b170f002b1078b461ee5beb9ea4f38e9fa2e05f3d22892156b4d9b0a77c22492d52c9bf4a3de7ab18d40b4611bd11f5aa6308dbeb154b8c4b68c55153d57299ac76f131e1170167b4a6b2b7f498dd2e84c9b727c719854bffa83f1bee543971069363fb9d6494fc174c34eee7e5d145c113eea2cd72792e7a779a6bccfe1eec0261ab8bf6e1c2149878dda6c781dec41edc556b31d8d3b80a1737296243728a7d5bf2b41cc4391d7ddde9a4cd8703f06512ff3d3287ac10a038a502d9ec24f93bea825962d1872103967e61f8fbcbf0ae31c00c26c1bee6b0fc324440cd72d5cb79d6b7b3a57720098fa4bac766e8aef099dbe0ac4f3993ab367ce14c7131eb13b42b4fa0532f3d075a97547923922c905c895af8f930902ed5d6f1bdb27257b0bc54e3bbfd8811efd60d279b65476cf34c3618eff06cf375781c5a226172562a7844ff8b85cf47f055906c5b5c90cfc50df470c03bbffec96d84133a7123310953150466529ec44c1105f2bcfc09cae54c47d87c9fddffc4ea0eb75f65726acafea113762a3daf0a47d974bd719742dcff64ea45677bf26c7e7ec459495e5b77445b193d64c0ddb10b7b48dca8d0f4dac5547b757bd483bb4882a83bea7218e65befe5a3bbfb68bfe8bf7ba63e3e7d17170257cb72ed2b76ddeb47e0ec72056fe3ab51171ce28c54335696a492151dd88617a4a2fa0233e0cfa7f56dec4b3ba60fab07b5b35bcb0d5804a0f9ec29e8d58154510064b28234769c41b9a77241d0299a90b92fb326209cd9a448d12203c392674aeb1ace9eae1cb89c39a772033bfd4d2f5a542c89907fc9ced395bed20ea7b5384a38eefaba330450bd92032214117a60fef5102bcf85d6c3e7bac772de29310f3e5b67f3212aee8ac6de61fe48a58dd34c11b5147cf9944cc0b478bccdc0e03deaf59b4ee19c0e2f3a9c72e78209d31de80cc0000368bbd215d1aef554aea4c72d95032b095fc02c96c072ec34856ea8b2ff466201586296df1f51fdefe4d8bba27e939ae91c4e6fa90772bd18b92a20d450e11a15846be7addd7db776c71d50226bc4654b5fc5afcbe2648c1070cd31c890eafb3be45d44eb3afc7823777c3fb460b38fa960feace9f472569dd734410724a8fb95a541fecf8069122b94a1c7cd28afcfd4e7a2cc781a234ecb665b0b03dc055656a27f479cb1ab984a18c21663bb2455ce8b7559598b720bd09b6f18f351ce943d7214d2891d6810cff0f0037d99ce55785e76e6cbdc72b7af98de89e665e60b784e6fad99ebf3cbf6e6d44dbd018ab19096ce1ffbbe69303da2b2b3457f306a5223fef526d58f06cd599b502499e02cdd1cabd4334d7247938e5e95ff5b08580e161f422b88bc1ee67ca5730fa48858dc2eb774f89d565da62f0481f5995c2e625f63a2303d81b88115307b22851ce39d47cb328980720e10867cffdede88fc14ac55fa0e8455044f8e00beb4ae305f266ddb54374872d83ed2d9944bbb2e4f1b8dac35e52371d2d5d10abba6839091c86d36db8ef3494384f553e20f6f6d6023aed259d1759c84f7e78ab8201d95762f670a4ebaaa72749b55a745a3f348b933e3155a37c5376d5bc89f2c5492219f0e6e43bc54c8161f76d37ff728bd461f8e410de1fc367f07ec871bda0d853f535b5c052ed81372462883db3217e1f6289bb6b8647d44201b14c24aa19390c4363bd0734d9a5b7206ed6e1e0170760299a8263f9eca9cd8df35c7a0ec378fb00d32df7d12748072f6698a40fe62e5c419283c4414e0a40752aad0fc977d7b9be5b6a62acf5b5a53fd639969915b51a7236b690545c492a375c54ac6df5795f92f03e3857dab2923ab6b6b0ce1f4b96ecb6dfc13c9ff4ac5ab321585ae5db175fa1b3bac15034e079ac5ccec8fe82a342c269d758ae716f56b419d4f31a0584f19789c6b7b3edd72154fcc5d87047b56bfcd3c1c62f77f290d834dfcbcc54a6d258f40e21492c8729d1a940b0b7521899a3f5a6569cb69ce5ad19b52a9bd3d80e1a0835b2652d33f336f2b1b59ff033c3c3a96d66d6215eb2e627de667d2284b0177d2d477c1b272ea96cf1e90988eaaa1e92690f59e0aa9b14ca88dd1932a0421ef3cce63d2d6725b90d7262067be9ea7375205e06a3768fee9c6dc69c3eec847b6b5d725c86372d8552352cfbbacf63c6d8bb5c2630bdb269edc17ca4ef86175bdb8d512ebdb726705e0c0f5becd66462ec7b847f404bd522124ce9775b5986afaae48553bd272a3bf3f0a727d7379713cb8c8e4f5c6db6d7ea43c3acda403579e12260b776472da303e676cb5100cc09e1af78497d0243e2913a75ba00bc8a6727bc0a5a9ca4104fac15a879db7e3733c903c44bb326f1175fcc0a469e6736c547b5c384f9e7225c045199624fff86b706cc9bfd92099b0464d86fb9c93f540d534cdab6d0424cf8be7bc537bfd7c6454fa41d92a422aab5e20ea228e2f30aef6dfc46252c02bac35fd04d923762e9f7a712f68c4b68124e93ddf1a834d3d5735944c69c501725f5a777a38f6a4c683ab0426daa77b075f2b5f26ff420afb0c332ca666a32a72abed9f75be3d34eb24b9631e3d12e194d15a8cdd791f3566667b495876425c72bab2bbda8d15ba2ada0d2f95774695fb1bbb9e9b8b6d2372bf2f09d959e076723e42e9b0ca9b904a0860de0bbc3616702b5511ea1788b3c5fc4ce40f91bd91727f7b4bad73fdf0f3e5d3f381fc241b31d6dce6b24871f64ce2eec69da407fc72dfac4956e8e09a2b6d51aa9d5ab43493cab3ee0d9b2fdacbd2fd80cc0b2c597266909069e114d9589f5d7a12abe858be5fc5718bef6196638b14cc67ed56b0728b78e6fe74a38bccfc4c5052e379e8614ed211b9ba0521072e058f5a4ae00d722c70d60f26cbb3711a290f8d5197ee8d239ae6c2acdaf1fac975c9666df16c72dd0e6f16c3ad9c0594addccb9f6bfc28da8131942a8a227dfc74a0880a15981bc18e4e159d04621a1e17f3cd179fac27833c8d37ab873c84b61fc9b3d93af6593f05acaa70dff4123196ddd6493f9c35bc70267a9b003b4cfe21d9828ad2cf6152d723aaf92c22107dd26cd1720dca6e569b6f6615fe22a3bb5fb68204532d726104a9ff3dfb88d657c0bd248b38342e9c899346d3b713266a9ef1458865b67288011379bf5184b32a088ed0ed61fa2310a55a70111c4ad699b693c5e2ee3e72fda034d354bf8eb52918570a715b5cd8a5190628f83a1416dd7369c2157ca1357e773bc0da9af32b0eaa834e7f08557ee070321bb991dbd5c08618363545b9727b08ca831c3a548f508c65cf0232f01227ca7c4c8fa1ac74613f954fc1aaa54869395273777a8225c75f38a3fbda23a773157851c570338ff3a03731679b2a7219658b3954ef02faf05419861b99e61dd50f0f687c6919f8fff0c42493ecc968cea35e0fae353ba367722bfe87e9b48ea97dc678c00e24a94a94cae0f43bf67210f125e16739f224871f3f71271afa6f87e19951e8337b0ac07dc312181f6b3cc38b64f381e7b99088d2a48871aec43f4442d92805f7cd4d45f87dcde8b5a97200cbc2cfd287a29661142a1e8ba86788de6d44ac62539485636f94b1dacaef729d5aff55bf074cbd0bb9b066b219dc5b427560ff6b6d24ad17af2906a0e112724a9efc3739fbb929ea79ddbf738b24a4b0c52d9f8c4ccaf163a521c8598990729f5d392fc6a3640c15dc378faf1fa4fa97818a4ac21b6a9a3ace01844c49bc72ebd6e2ca67f0f036ea18b1e9faf0bc53204794c7bf8d6e610860bb7d4016120cdb218ff9c16f8b1a7546318d45686ffa8de7b5bba5504c2abf4f20ae8a5259720f725d9a75752a1c47fa0527f2172da7fa2aba647d5e3e95f512b57b368b7c724e718d4413e5022973bdbfae8dd0b0b37851ebd4516eb91aa04ec61acac34a725280f8c6b14de6343f77292916547e58de01146665921d0d69d7d26f4197967215297a1d25ee55dbee02b851010d9b7bec17a1ddca2db1ff29aa33d1b260554b1deafd71dfd14cfc1194592c7fe320b561e2a2be2fdbcdf0dd1aef2097ed263c8dbf512d170f8c2c05acf81a74e6c73ef8d86db3fcb1d68c46705ead9bdee8723c07b6362cb21dc83a8bfa5f9ba8ad3404d97d283eb37a5ab6028f8498ae772af6d7c7bed46428c4c91aec2769b66231ccc7856e3944e2f22f3bafad6c8bd3330fe95e4173f08bb432b507acbd8d9e12b141be331c388bfd97008bb7aff52472d183dcc02f129ca65b798aaefaaff3daac2407e0f1b97066256902df73d42872fb0ca552a04bceb795c8846c4ebb549e4a5b1402754268904107a30c2280d97212f32013ea6ce4a301fcb4c15f554478d11e0b941665f1ae1fc1c38d415297337f06f9f0455ac372b64767c082098da9b6574c27a12cb26655e5d477676e794f29a6976e3ce5e0ea3be48f3867d73d050236f06fd059abdfad4ac0f46d58c3723de642746412369ce8cc2f71cdac6b2906a79fa09490eeafe8a28a0b1829164f058d9cab225d5b938aa923b70d0ff27c34d092f4d50b2adadf5339a4874bc272feb1287bfc917af057ca18469baf76d90b28fa798028b7b255aec63a1c049d4a912d4b13005d042ad9b21f873b23dc8b3a90bde7b230bcbbb4b2343d32bbc37231e939fa19cd7095eb767d59001d90338c06a3bf5644bc066991c948f97ae219f97ff60259c74d79fc49ae36057e7fe91c33f53899ce6d7f63935993ffbc176526c61b4972c300c77f7dad6e7bf6e17c0440193b69e98469584043ded82444688abfae6f987900451dad9e5ebafdd0e29c2eda5fc8c989f211549004f632de72ef05bff9b4c957af9c29bbb9a0aaad0bd6473dd0bd30c70bcf2042710ce2c4553ebb16d4a8fb2871646a23b543273c76f1068cc421e0674d4bda5d48a9d43c597c399bf9cd85b327e35dfcdb6146a4503f5996571a02c268cb79e387828aae720789ea8862aedc5e680ded8f48fe32bc21a9b0244280aeaf9c170523ec5f6d72d989dd44b61d64e8e7abfedffdacaf01e47e10bd13d43e2d4d36bc65b5939072b0dcb6528a2f30b2d670a534641749f08ee2ff6f0492a3c85879a22737b7307239bc94909d4e519ff1f7b4d67c72d009197a454317f3d5b707748b3b11a53309c3d1263eee00e1240ce7f27aa063a2de13b83c1fd0fa382379f523975c21fe70623ede6e85f1a7655fc558317438e6aa42cf663aa904d1c3955388ca5391567293b40ea31288a5f2ddc419cb789ff8ab839285f1290183efdd3aa17f0a269b5c1e76e998722e4735cb2a29c72c38edd230fcb019a9e89a5565c4a46d28e22f3b0e059fa5e1dd0bd3b72df67a40c0698e61ac4bcb40f0afb79914066eb4d75a72975ae93e723ced5a1320343de7d07526887bb92471476486d6dc7ffe3f40f872f117f3b5084af2797d40c64cf0932fef5de96f8b8deadf0e31a7a6de5ec9c872050afa27924ec55c9b319540b4571cff0076f11da866b99ab9ac49ce3d5750725e7ceca44abc663f42a9705b43e83b03fbd0d9c9c611cbc907f51b01356e4572e188a4e7ea808b45d0385426b0cc201464cf96f9eaeed2e4ed7ac010feb1fa35ed8e53132796c77ea10e8594058bfc874884cd71e40426d0e545c3e729ae9c72afd98b68f821abb11f520aac2105ac19e780da37fccd11797bfe5c0b52b0353ae74ce563242145afe2a5cd1be8148c9e0a4697dcceb7fa15df802899f01d3e240a4ab997888b0e7d97284b8fa2cad6f6a73adadf57d4a525a049bddd305ca272c974d7a0f7d37f8fb9ae0769dff11852dd443b274390f9b65e06626234c530722a7c16f111e7f7c870f97b7b78c933dace0ad9cdbccd9a248c0f037de8e674720751b7d3a88be15485dfaead8aa54e458e8bc0cbc5b7ad5e0e001a316d693972a3318b320abb0f85d5b89382394e69630fea5e4f30cb68ca2830fdb0d90de512018c90f8a260cd784efc78920ebdbfcd1b917c11d83d2e405af9434758bf0504b4ed821c4a1b436c5e57536c271a056526ac487e370bece3cd7f9c7c34e9f308d3bcba128cabadbff71a2a26925f88079da787b279139fd6235f155fd523b56ce55c6855b5b3655eecbfd5e3bb39ddb8eda4b15ed57ac47d720bed5f9917ab72093c77d0c171b201396d7f5095422972ef85407463f81c4eb47170af76d0fa1cfdbbb8336e001c396eb0e89db7337e5d03c47b2deeca284514c190c4090068219fe5fc1c943e2a1b43f87e4930e94698812a2517198a452039932bd3d7f8b6727bb7e0d75ddc72617744fdc7f95847e636958b7075617fe9d1fdba55e95659727d0b22c20d606445e80500df2397552b5bdd22213a779bfcbac8f0d9654399724abf6b6f7b2e2fca13779045f1937f1875cf2fab8401afb2c1adb2f8270bc972c1efe5ce145551a0c4bd637ea8edda7ce5c8f0c822719c67dc6b2e6f85620272a371094d8ef5c4ede1fd324694de564fcf740d1625b738556f320d15fe1b0416ed9852b2ec9f1852685c26952b7b48d53c17507074057d738105d57d4bed8d727ce35e6b634ea0053c8c6e05ac0082561663b252dc21caadcf28bd9c86209172a95d0e0f7841b401a056c0218a9cfc7d981b74bf65e3f8d5d9666ee09031547201b96cd12f20b1b22074d6a494c0d07ab37900665947be3d9d50e9b9438ebc72ba286c16995fac9f89bf18a4524807f3088b31581e88f91b010a7e8fd8678d721e99b2091d03f183e57881ee9f69a5d06bd4b519f7a8daa1ec5e8518e367ac72a4f8eb90dc5f50a03a0199c25d9fcd4032f1f5c468e7e6bad06be7343b9974724cb17c8f54fda89ec80b90b81e50f949b37e1f93d1be7c1119a92aa1b9119631fd6399f514fa7a440da811f1baa5e36b77cfbeed51971464309bf70593e72a723e190716981241da7275defb5547b79526be9eb3e40b2794cb6b6ad51943dc0f1d82c5023baf5454e10d12ba36edd47adb3e233112013387398904113bb0006681b2c601eee93c43680581a2b050868820a0c313ae828c46384b7a91d0040729be5be79f7f3bec2749e04d77a36ed5ce4e69841da3fc8a7ecfe1de0477804f22b2955b5eae766375911b0e66e9b1d44acdeecae853fdd83bf9dea06f754a325f6374a7cb4386dd8ac2fd2928484ed47e22739a8e28ce2913d81b3d989e8ce5720ac2c0d480bc4b9492c5eedab32700edd4b0ef3cccca33896105f6e8511dc072b62b83bbc05677bc20ce194b82dd96985990477704d97e837f7cf9e69b1f207271861aa8476fb9e74162970db53b54ac6d5a08664f31b450e349bd98e4144d30f9f204b99e44323e0c326a1beeae4dcfa7e9ea1f6618518d4ef62d0d8d5c8d728f9021f226342d123f845f11d165283d985694d39f1c3077444dc1854ff55b0339bb98856908c80938e76f04e8cfdc70e133a3702dd1fd203201a03e411a1f117da015cec1c4f1be2b51d42554a61de2f63572cfc7fcb9940fa424e32a37fa50dae2ab265715573f4cb38e0e037aeb3124ba46086456f88c9442b9fb6bb9f8724d3a6cd60deceeff8f82edf16c264d7a1bbbf3c876525ebf9807b2285ff48f72859cbe82d1f435af812f15fc0fed4e160b05ca432f016398b271acd8bf9dfa7243d0061d00c6cb585bd7787a49993e807b55d6c1ff274b4e1733c0ad49ab2a729b312e3abdf86bfb8db8bacfb20a5dff3f9173291ae804a322b79d602580ad727aacf7f932bd39f6bfac2d5088ab5a855f97b52a2e768e9b00261c9721e4ec7219dbe12f1c3b86f5e617c16abc9d3a9139295e291a424b775601ecdc7ae22472653e357ac5f1486e3d6d46a5cf8eddc9a9dd9ea1fb658cbe1f68265f9036f445468464feb420b4e080fa4006c2d6e28721c1de0d978053f2df3441e357320244e010f081a3ec9136de7857894ea9b3685bed331b5fc69f54f85860645b54da72cbe0fa97cff08c2d02dca4c8b66dcc6a8499b8a0ee048491e6323075277d90721472ad4a00c8c40c435646b8aedc7c945a17c78f0979e3916ede7d47cda6f12436cd1ee448fa05bc1a3e8dafb5e592b4daef886f95a9cfa9a8cf0a89f9570421bc980503900672539ecb20bfca68250a9a619a5e360a27deff6d3cdc44da8b26616b867f32cfe92201a4779f9c0fd388d396a54f0ffdb0c6c30cc9975d04647217b64e2a9d13fd8068e554097d36c2bdfc767c0b95c5820047323d80f90b770ec336c6fa1436fd7257eaf8058480bf8b03c5457903c9fc9c855127967addc572c41d0456819b562f58e7f2b8cfd19cf801f0f1be32fc3a10efdc547a5ee7e749980db10d214a8e28e5bd7fb83abbdfc273072b7af55e109123667cc34c471b4408865b8479c8ccbacbda9b58cffdd3cdcf7fe27c89b823df442fa5e5cff8ad72979a6e705f31cb8fa52bcc5b952287a9f7bcc307cdcaa549b276c27802b11e4206715436222c53e1f6082b23670691ed239ae7fe65c62790618712ed9fc683723b318e014290b31a96aa8c1f0b2ce16698010c48d20b50e323dde352a032956d6c9a00991b5fa4deb032a86c5ed8cf0420c9749fadc40ce712da2b949cbd8472360b386230af19e6d393047b85d8f3cb3c1436e2f87fc0af452a9668e3ffc072aab2d898fc470f6fbc1062a29c393218dced910c9f0904548002cb82dee7752a275979c56b7f6a810c7397e555d604846e8948411a4beba0560012e272644200f260899c7af3bb96bfcc21935c8aa447d8fc30f82ca3f9e48be587645c045b72fe6380563b1fb44eb16f98a2b4bc6a1ac4becb66ba496c6b90b0546c43c3e122ba1da12e5d1aca06fb36099090176a6e06d16142bb79b1d06a77341af9134333f6856b7bea9633f1022a277bb47bb6bec6ad63a82bbfbc8c2399fafee811c46bb4cb84a35c9f9c57cf1fb9ddf3942bc10a293621a77dcd8ecfa8791f3b3320725d7e42740715bc1f4eeffc750c4f642a334a6710b4b0a96812921ac8a7f1415b16b395a27a50e7657eb86c36952e7d81327e12bacaa19e0f97324808b7600572c80ab6bfa3d8496b634d24fac45b1228e284935bfccf1068e3ee3327b2aa3408e7dc924d94fcd29475424e9addf08a8370a271fc1b92fccabe75005c59668e5ad2f29367b30350be5b2b32dfe939bf35e5c8db2f76ff600c65fff5c9c827a1659df72783d81481786db92e8bced60c3a441395fe47d717e53ca684acd3dbbb16cffc4b536167b99b2b012b3e0208ff21f2f80ab37f7b3f8116b71f43413b22724b01bef6858ad2f82447f5e7783a781205962eea850355ed418759b20ee6d4511c93ecdc51f3df89b2855695e90c7d4b5274a20bccc1ce0a62dcb8b37360f5188c9c4f9644f8f342aff78e055eae6104222bfa95aaee94b543debef128c8bf37dfe445190fb44d05d1e5a6a8518002d413dd8b5c93d413c9c9ed0a0f343bef72998f7a2e06ad1afe3b291d3059fbc3695ac07e793d2c73663c3e6311aec83644d32d0030aa88a2f329e20dc68a2177b18cbb7ad3a951e4dbcf48255e20004c72c1c8170cf46dc1244bb6698f19d5cebc274481fad7b2b13e6508943ae01d6a3869cb0c7fa8ebea1e187780c74e653d9a59534863d528c943aee9511de93fe80540aeeb68a3d73c13ee6350f81279c3163bf6d9d3597918447cf13de229dabd0089fbbe83b3cf1fdecc37a2309dc09e6671494351cb8af719cadfd8fde3723272d65f8d10358aa8709dfec9e0fa2762d1d1e3b74fb0d21ad0aea05cd49b5aa972ac3affed1511be778b75026f302cb09b7e602f2d3199038c876f4d98767be828bb0164822cb3879f147c86fa5c57299386dda9ae9ae50209a55afc3d8b1948728114ab7e7b0db3f2a82bad2d2c0e51190775aff08dbde48baf74e6e0530a4d729adba7605fda47d7ff9439b84836dfe9a96d51b98396658481ed7408f17d8457b243dceb5f7419db0f27ea9897d92f66aed4942f34267bf235eb038a35ff01649df37d7148c365ef046cd3cfb6feb529866f1c1977ac5e753119c8ea78f8472ef067091788417540c2da433dfe11186827b86b500246a8a7f42058cd82380d72f7e12b48f17927733d20e9b70a8d014d32241d25188ff79b543dcc336a253c59003905e89219fc40f45ab1670ddb0834c3e1bff76c9d8285ebc9e16d4c1b977222e6fc999e9484e5a36eeb60c32f9149289d0e46bc9f7f5f2f5fec62192daf72b7ac1b9bcf59d0031be3f31d28206976b471c8374e5fa8dd9f4a7d87db2a0372f5a4eb79b912e40e4e73480805439c55db0e38c88a06a23b4a2d67900da192608942007fe5505eab1db4ae5745dda1df10dd76b65bf4414bfb0dfe51f2372772fb0709387ce3e8b1181c3ba94a1321a58b51561e31d6b54f4e700f23658fa33f36ecc2e9850c686ecf79e549d9a837f541afeebe2ce7e27ecac74f367d650161de3cb9d9fa69f99c44274e06ed85a790b8b06581002080309ad88ec66fcdcc458c842c70efa11d9b063c6b4b6bb742e3de5d4cbe5983aee5bc9383008a90f2726120031543e5d008bb0a3f6466007578ca2e58d1e2b541466ed8d9a94f4bd5725cc1a9623e642b49ec203dadc0a50b327a7b4f95d47f29ef4ea429432abe156a2b590b239c9c220dd724924aab68fb82249e5bec52b5b36758143609cf0a4035268f3ef53df5c9651d65911686ed2292a7fffc431c5d4772342c712147e98472124b790730b27539e9e35eb0169cecf102b5754b77e87cb1f4d9de97d58eab1389920c59ef26046bfa5c3ec9ea779e1cfa286a680bf231fd51f02187fe3864294e81c10f3c47c1d944ecd448cf808ab01cf267c8012d31d2e639041b22bfc40cd9b7a05233f761d3d1a30771ded2c0c8eb4ca614f6c88c40e14fab0ef93ab27277983e7d5834a860fd16626158335c323b5d4f72ce740497555b7570e9a0167219423de74f428b28b64f4f8cbfe2628f3ffe2c4cc8b9be7d5a054221e1190072dd5a3902882baa2f29b386da1704c3881b93b51718f8cb40fe0788842366a9729a83e6148adfb26feb9f539ff76955176822e9d006ad1476c69dfd0b8612887068c72300879ef28a265a22d253266bddc86c871bef8165fbf4c8b4a4fc735a0464b6f490186628fad00d00e0da294bb6455eda2cea8f90a966253a69351e5c72aa633fe5cd29cf53da21d93c4a30eb7217a70d4cd5bc47c52fc2b870f9d3cc72df68b3cbfb97e7fd63b41de27a1bf806f49843bb31eabaa8d9abe499c104e3722887326c0194d84f3b8b82b1b65707afd2bb4c5ab04ed5bde95c945f38852062f78798c6afdaa06dfaa03c05586fd8069e3864839ec8c0c6cef303f59899a3725ecf5dc37345765f80446b905d3fc65405f856aa778fc91c5696c037a4637772c77273d7f0ab79b61925a91a4478facb3a6a067bb220c0a83c0cb0f29014d57288da9ef3747f0d067b3a5eff45b9c5c9f7c251aadf9524ca48ab2e49a7727d3a918d22b2006ffa88647c1135a80dac7fc6972fe73b7d6a799f32ecb4dd322572c0ab4544132670de58a453f4ce58a02a5901830b33d92a3eac0cf5f4d31d5d7290473b87e877645ee829f98c9c4a9a5a5b43786327fda7a6e9c344fd3cbe15074adc12b6c329910880ca4a378b3b1aee9911f2a7bcd209292c146393b6e93772083da334907d35ddfe65e6a98d7d0f45036d18c86c2d21f5539b1e8479b177729e3605db183bb0f8f7967cde4f3c798ef78566a5caa0942accee20c1649b7016e66fec045575245a525beb943937566f9ff945b9b260346469eb207c79ddf4724c03dd967b3fd529ce97aaba9819fcbaa2615ac1a87d4ab7d51ad3336a1cac6760bfbb0e8c6e1bad4d5d5510208d1894e23a51aad5f83ef8472f74b6de92ed729ad030f09e8c72f893b0033d00a55919a4c78e0a6e47a4fa307d3f53d6e70c72c97184c74970c3b39d110cb47e33a800c33f4c792c5cfc6e4473e2996672ca728645b4278e6a345a4d4621281df491396a7abae8df9f9fea9a6d5e9a4b4a8e7242ae43a26b73e9b6bebe5830f104eee2e5d0dda4993ba3edb382ddb66ef43972a523af9059c34f1f58f2e55d672d0e326c790e3aedb6efddf95ee1fb71833a72842f6226a980f1e9bef1ff7aad214a13ef45cca3e13d310b3f72f1781b6d957240593866ff408d8b3466ff75a5ba3b3b1d38548859e7996e6ad7ae0a5cce8672d145b3468057eaa519af54c48b8d6216e6475e950cba71410dcb4e96c315480a5a9aabe99f297e4aa31c1a260085ac226017b371617db382f1946790b818380cef5bb4c5003329b77215e63c4aad20f8f741ddc8627083c0d645ec65fb7592720fcd01877a7a6c979bd47a4e6a8cc74c2626e16fcec3e0af01d55a291d5cc50b2bc2dc892a80ea9cb7e7ea5057c9ffeeebde1867622d4491c9df0d7b91eefe3894365a208804ef64f5c6cec8493cc69f01dc26efc4ede1718013ba9e33363f72c2d22fcf42f8f61981373648e545ed168f717e5b342e507fc8413e4952623772a51c53b85454af5db25f8b472247e8109ef271e59f95ff4f06ffb1b13fbd5872392a468568eb16fbe3bdb08b5651b18c3e7450cdad1b392dd2843fcedf833e7274022042161b82764fd8d9aa2d950bfcfb12ec193b212fa19b534111d5399172fa3c608064950ae90df17c39dc76a8cd4fc01592289c7ab2a5e2940e97a27072454c59de48a09ae4869e89b0f1f8c89ef3089356ae919fd9b3b79cc2ab9c0d723174fb823a261f71725aca49f52f71fbb5b847d20f4083c19d55569d95c348665937df3a9ad47f65ea2817fb849943015b869e5be0df331d5163384867f28467c9aa49dcc65ff3720d88e9216fa6c82caf062b61c56da23d1575bd9f5e8d1b72405a6bd396c007772ef12561af70bcacd570365ac95e6b9c7923dbdb6ae0eb72d2253ed9687b5bbe74632c52847831446379d2004b657fd08f7672d1664c78729a36a7847c5fc9594b9227c965e7466791e7cf24bd733f6b6045f2d3d2fdfb68a9a18a8df89e316d310bcc40a804c4b3964c3a58db01128dbcb49ac4c5e10d72fb8c5cf297f549570d31707bbbcc73c33b735f6cf8c65e239c027208e3038c727f596ac38600f84d99c2cf8e33db917f78ab9faa8ce21e7ceab55649eb3b270e5cd5b72d77b1e6ee5ea23e792381967307721db0797074f87de6a231a0728a5b6ae6c9ef4e0c00a87d689c642bff3795da376a4f68da2a821ae37f61418699721519b66b904b9c2eb05a8d7d34c573345f0eccbff703fac7c669520b0d7c761cde20406621f9a55438a4212a2f2a5dfc6eb19e9c42f26a18065a05be9da9e941c280e287c5d4981b0ece6dfd8c6cc1a40bc689551d838b9bc635047a8c0fdd08b4d81f1da9c61fc6a73d4692d83ab8f6ba5307cd4aacb072356dec559d9277727837811625166abdca1d06cd0ce1288f415240874ee2fda53457bffe9d4acd72aaa2ddeac74aa595785a83b395d84faeb6deb86123da3b1e5693aac0309bc70882755d418f95090e4d6783bed86ec85b43a87327bf2fb9916d42aca273b98472758cbd256899a27e2c8ce18e9fd5121994760f3afd7a30e0209d28b585e927724d8078b6bfb5fafd94bfeeaa0f07c2e173845d233855a2c9c6d19e2331fa9172f6f067b0136d955d1830f6337684f7c4bfb518fb08646341fd1c22b360c493727cfa0665f32bad5f8ffb1e37d1971f907403d8333d82f60fe04548889dd75c72d6e52a00b4eba4d11568b54800dfad98ecfce8932b3fb0caf0af6c4b6e834272fcca48aef2ae3e7f1d18bff921f06c3593018c5f090bf25349d8bdbd7f597333d5804dc5905310374fba3f5d9b993ee95c159da1f7467e74cf305936246cc13574208b64a54e3f4584f4d71751435845388e7a13295b342dda8f4f93a9a13772eebddc560b21a0d331e5220df6500bc086909303140d94f50707e9b32290e27209d2dd5febaa1114470872f193caef1228ef08606045b03ca4b60503c8fb4c72791b0cce4f58df21e3069def7a0ff3614b7a45507bd79d97d8200a1e15180d721ed4c0ee08b87e0c4884729adf8111545259d70a68504fc0d43d3bf396336c72be1b4160a66354e60822060b07d6e016c434d43390e1943c8b899e7fb9021872990d3017904db27b79cf8a5118d92dc933b8ce40c150e9a4b83086de77e52772045fdb74d89c1e88dc4ed8048637c5dcae8fb81ef8c21faccf5d9db27b0a53720157866dbc361f0b3ae672bfcdb3c8a9da42f8a2913912ddc184575a0417a91797307c17cf553938ccfe83c7e76597d404433d349e8b7a8564bb484083977b07d52ed41789cc56d01116a31ecbb3ae2dcb3317d276411e93cb676771aeece534f4573a248757d69f93c2266247187b9cbc4b1241fe0072ddd0818ae42eb58616eb20e0ef6f4a460e394579384b763fd127823dedf6329af89ab53868bf428172f4f0c72ae401f81469713d9cbac93ae0b2005bac752620c5fa0e1773d987147220843a4b7d359924033bc2b12f221f4c7965557de3089629bc349cc5127e9d159cd1cfe43b1d09267e08aea8e37de84733eda889d3c912e8a8ecbf56d986203bd879a43b2a2b189661a0f1e6e0a1aab5b896a2233dde5882b632b57d8d34ad30dc540fa5c5eb61fe7db5b632cf1ce3e0a15e32426bddf1189585702606082b72cb86aaac307c2fcb418a25edc6094332cc1c0bdb6839c24296c33e310817af72ea5572559b122a0a3d0efdb8676a1dbd39bb4dbc9a965a96f01c437a71e31d2e00a3386ee1a12b1925fd03cf660eb0f12a71d0ec10de698fab7a27dc1d6064726beb2240fb6db5c2a3906a120125b883aceda54f26d268c8d3952a9d422b3045f23e8767df28dde6678d3bdb26fc847d9b8b026db821c3634da7729b797b2655947c3c246b563a30a1b13bfcc24612f1c0005629cac5c28ff81504f6667e7572b60d58de1f5af2a8e3cd5a900041f2b2b7de351ca039e4061d05bf79d24d1243b0f6b2d2bf62d4bfb45d87afa3ea3a5a3ce86c93933114a03e3ec6610798b8724c82b2f1e31d10000221669a41d1b0124f562713f3ddb8d24de07e2294e08b151c9274771204c0f5d9b1ac7fac34b496d8ff57427797b818d35ea03bc31868505b51f81d232a5d9e7526d79470cd06fdacfa3bf6a5c7321362ab0c8462e44072357b45805f6eadfc46e9839045d08a588ba9bb8cceb7d1b6f7521841e7840072977e677632590e65f737dff63ac63791c5056f136798cb603835e0e5dd05e0109d4f96959cf8eb7163ee59cea99785d124b054e7945f40cb9c54cd9ba9f2257295b54f91cdecf29c03d08c701f56fcdf597744f0f46c310c242bc2f3995f09721c9bf2339acfe8443b3b211bb9ede4ee03b7d9165e054d6a8498b6cd54100a72dcc5e14f17027786b91ed024c82d409d897fd21b1116d7890337578630e97f3a1bdb126af5cc09795eda7f5ccdfd62739c6441f0b2252ce1b60651058781064799d08549c5a806d55b3c90937cc413a1de1d45caae51e4f7a2e6fac086e6e852b3050c5c313b56862e93798cf29d074b6e5097d49aa02f0a9818d92e6746997212535898e680b24cfbd517f5a923c4fa45b819a569a6c6c776f3868fec1b6772c122a7bcd334049b365d05e863a665de19df0b7570feeac81905f73f8a150972cb21a68b43d19d90ee4b67f8aa928d0cc2aff0d0c12d2fb6995e474e3013b87234f1e8e365eb0b6b3cf0c1162abc06e66946f771480021ff2fd6f2d64a9f9c723d35114ce401854833a6c46a19fb635bbd1dddea79fb9d36d46d5f57fa706972f1339ef4fc41d8678b1bfc9de66d9f26e6d7765c262d05e398879b1bd372ab723e96c90af7bdc5f3b3b961f2e782d4895d5f090880a94c20750b39fabb42927239c96d79ceb7bca90fbc83ba881c3f0b71be15fe1b443815c39cafef11f064723ba9cb9b53a7bca6c44b0be9dffb433db48611b07bc0b9f7a4caaea5d336a87287a110fe27a66ccbe66f7b5e4728a2999ff0f6faed01a10b79b2906ae0da017260ed8a6ee2a7773330e8bc308dd44a6f3f86f8610cf438d677ba6807aab7a57242bdc935c720fd5ec77fdaaa3b21879cb4d136d615270caccf4e3bda0e98120c96a8178f801b96de0c9e54e93cccbc6022e91dc140b2b398baba3f4bde2a0f7232316a7dc649b656fc778d65adc884b2bbecc8abb85b53aef65ee2fbafe66872c3be9c827150495915691a481d385eb6a4f351b7b4c72e44ce0aab7f78828c4071ba0cd652c6c68ebff9134548dfc35e11bdd30fbf29a0461aa9c5571834782c25bdb8e2e50c3ad14022c52e4fa7067182bac1f7605673591edce71491c8db7287b214129e16b1b1f4ff4f11bf3ca19c7437bad5782391187d65239c275b8b72fc35d40fd67b75891be5c44a22342e61b7ffe039765f3ce08f8049d0bbc8cd20a50c4a3a9e88070142574b7e8a444768b75c7e8e298b1a429bcdde1f990fc572178e441797ce9ce726d0e77046bd869ef97a792abcafa2e0abdd14fb59f23e1da78997893846151bcc041fd10c1c3350ac5ae82145460f0d1d91ce5dd7b1320a3e29b64b1e8d76549f4dcbc9be9e5c83a114b8dfa40d75d614236470d8d0e627584d80eeec9b0c87946b09dfcce74532f423fe233061a3fdc7a46154987bb7724560d55428a1c083ed0a305caee1f783a39fda42d1d45ef77287b43a4cb5a8725637c73ef341a6ee53c033004fdef08925b68e8127c18da4fbc54934f61f8f72522fcc9badf275f63453ed8a283357c0de7e53c09fad959bdc149ba7ebcf694afb2731c47495d7b1b75197d7304de0098b8889efd9e8201bf6129c47340cf061b5bf4293567246935e558bdfadf2923a5d0ec23b8a38525c3b61af10e506e44b15b53593607023e5e09e6cc1fd06d2a87a31beebcb48c2ce92a95ce5ef611d4a3278a64fe74b063da462ead36fc233eae99a7dce827a5000ff576bf1acf93b725c09cb7dfe38b7b1bfa65a4fa0bb83242a069df697828142fb42bb27e1b20a1074fd2baad0ad5baf42b1fe11c20c63c9640343dee9e20f56508fd8f09fea727227331006824992cad078db9f7b0f06129a3e6b294bd7fbf5795429d4afbf5e729ccfc8f855a7015dd2766775eadd3ba148d6ef3cb52f7de0358b531f8180d50fe1fee2d544c3d8872bae78530299d2bf65d691576d34d50f083d7e188880902fc69121b688aabf288a4e8dc3a69491f20e1fa648037bb92d28343d2b153b33722a341ec7ebd2404f381191d2440d3f30d72c5a82fb19de06021fadde9b948172109f835cbb2a20518c076e0b6406aa5069b405d276bf9d91078461648e86cf72b72feff5832b5dfbe323f48297416f31b0da28bf7e5cbef6d0bf522f5ffca1106e342a992c0ac734b69a1222c15160f1f4dbb11091c2e4c2183ec9837c14d872a946679608b12ee92707fabc058158c0aceb2bea3017e9812c236be6084b0672bfa5992e65c7dba2f9904c3e5e2b3b658dda25e8fbdb575879c809005509ef727a3a5ec9b3ae765287ae005836f4232fdd05685869ad68abcba9a49e442b7f72cedde165322c13a5e7e86f8cc4f2316f07292fa9399a3264dd4ff64e86e56e60afdf4487b56db926783de370f7fcbd37100b60fa375a8f5fa15c6ad3988b2f721d7fbda8c873e21f368732421bf0f7199227db052741bc8ae1d90d46faa0217275d69e3c33799aa1d5d1350db07f419cda8e82bec7f7434cf1fdfc395e142a72183626417f74e9ebf0f6104cbed8a6c6afd9cfab81ae03045ffd0dcbb46ea872e60a8167ad9de69b2164c9450315f82ccabff430ef516f1f6000658a3d65ed1c8fbaef1291da4bcc1ff21ff12771da9b3dcd7093d9197bd07e931b5aad8d2c4c6dccd63492aa2205debfdba40621432d43ab15dfdf7805c7e88371b09d6bfa7229bec146e99c850ea55636b2e7abf9d7c63074f1542e470a47c3679c9c2f441f49c747fb20e5fef5c7868367e22617b7d50f6d57228e366354d75f5053579e72ec1d23e09b2cdf88a745d8d6b0fef2fa313452fe9fa772b1510787cd1dfac13465053534d1b02a78d19fb2ef7d851944105d56cef54e1ac34a0e4324745c6e4819892cc091da1be31b280eabd43589a2d9f1d359ec614137a80759fd9a59a7014857ba44b460680a7b7a87526ef6c90686adbee60726cb2509aca9e96bbb9f2c40489d582429a26b2fbe1ad9ba89bdd8067260aea2f44feb23f4dd05b49c860d18708e3e47db168eab35b0f1ba02d685ed2d65c8f173d925f9c6dca230957372076bbc2aee4a318089d78ffb959883c931f22c8f3cdb02de6e41dff893bbb272c516dae26a03156519484a1d0c1be4a7656f9da7f5ee700934c6de3a364d37724aafbfd36a82480b1f62b26af5fb95e67789cd0c76650b89a73d89f9dd2d4b72a0a0fb23e0758c71de66c036d510af5b6264050f3f73fac05e38a46f3bd5ef72e02f36098eb64d92af84769d4f1225a67a3870d4570b90615bdb942151290e724de3538d0d95b6f27e2f14519fdac02f243993ef2cd14f24826417c4c7be777297f11410f8060bb4c74303b0ab690185a9854c6c97badcc906ace98d6185b645f97ff5440fa7de600a5f362b6b36d609abba7c6bd6f8afe799e252d75de2125f62aee6ade078d6ad48f0c6bd0920c075ecdeceb875e290aefacb6bd3c33cbf0013b252cac98f6979301d84feeb38cb296648e05c77fd40b6d238cd5d4db2a5724eafa6b136819278575e2b6f21995c8f4777e50c16aa7ea6d25f57fccce0c972df1e8cf45496233ab4e850f72d68592188ea01133e56fb109357b6b547bb5c45cc5ae0d1f2180c086ed9791b26715de7a038ccb2f78b2186a706e7207a99587203a2fa7b67a126479d192615334dc96ee693755795cfb6a84aff2e7e260edb25ace9554469d6b15b5fc38ee031e8f446bd9f2301de3ca71183d47affefe119725878e823efe99c6882681fccb7b82392821425600c6fcf2ce7b96469da9c2c6970e7cce6c0efa1cbd8309c3103dd56490be5cdac623762f07d1c8cc0c537d772e84b29b8b380c6587949247d56eabb52b685ce8bcf8e47c85ec4a28f0600a2528f8c8a8c19f20c0b05c230a9ffbb8c36d472028c6beb215d0d6cf62691c1d072f37752af7f88e1bffb53c141c7d7b956ffb16ceef4e4784816f21754414f6c72f8e3e78ca138cfe6459dba6f9e9ad6da0844388f89dce3605e470a8cbfbfb519ad6dd4793c4236331c88df07e6a4b591ff878994bc0478fdaa2c8d1f37f41a72888c09614dcacd67393c7ab69569285d9e6eeabcf8c5abe01417a7eb55ae4f7271f621e6a168c4132f6e34db1e41360b334892da58c4b0f6e9dd8ce75269d172db8ed9f30f9c88e518554f52d4fbdc5ae67bd080b4022f0ccf6ffe0a08e53172f21272599a8ad39ddb1d40a7f8b0de07a1ff097d1bfab202e3d738637b91a7723b72d70a26f349513d34371b244d9df35fa385eff82fa7cac629fdbcfcc28066abea1c18951c72b7a51cb39b4957ae43b8e511c512c2915ed15fa181fd9c8b72b3161734eb22cf49a45618783a0369e4fada158485340db082147d12754acf7256c8fa6f5292400c8d9a057c774fb0acab38719989287314c2bc4be5f3f1cc01bd6404960901f746bb4911db2b7e247d0ac86dc8489ac8ab49a82211634ccd4b91022e1a1fe629ed72512d4704b830390c37f05e7be0a90bde7ba589fb7a7272e1501adbf6ab34acb50472c5739fb9beb06302dcfce6d601e4bba1c8536a4372a3b86c4fb4cd8054f00e0924d38ddcfa33f47a7a454436ff97a0e66240311d723989f6060580e0485a4084cea610c2b984fc713a663f83da217070d8aeffc272533ec34b7fc0954b296316220391ad44fa99a19fa54a661f3f6870d1f6570b041a037f4ff700068c92650067c6e98efcaf104ae8837db671dec761eb15cd7672b54a5e3a78b029ba0952180311cec4f05c598ceac5c01222a08f2c98366cfd72740b09a5a2277986ca427d9465a62b8587635e9dd46723a0a1951d658ed85b72dd3adda53fa96a0804859d75e0188eec0030913eb2c22565f53c98fbefedd67280b17aeccbf12fb3f378f8946fbe938f22707b005526108dc97a00051434a0095b957112a83375a4bc2440998620f06d55a048cd73b568709fcd3d706e1dc97293b8d5fb372fe51e8c7f0ba632c21bf8c5d7279aa7e12443d76c90e98f6d0e724e2c0ea43b51afc7dbf51b43f53cf41db3902fa10ea3a9f583073cfbefedd3725886ea17b495629e14e01f185229259aed039cfa33f17f4a9bcc9d35ccd072728462aaeb863bcbb61e6409768424f27ca32d77dbd138f0f0ba37b5ae00cb151e6ab00df1edcea56232c5a971e6d59577f30e61b55e4765e8bcc4e0f51c59af70a9d66e6e055482b1e747a741cd3acc235faa7d392623449438bcd4294d51d872f60d7f0bb3f2579638ac615ecb4eec0569fd7cdc1373ed51ea2e19a4db351372d6668576a6a7fb0ef2d51b8f30b35564a20388d2b24a38616dd2346cd9a5f5055b160d3e75d4d5e3be14465b440c275a26fd48a4072de932fd81acc73521cc58c65361af5ccd4ffec02dee4d224d0947c0c66d30f610545337c6b5c47449c2353394fd9766b6a99dac1fdc399076664439d0e520261663fabb75716757635d72aac566449f802f1dbe7369c763aea914200d5601adb5902ab0117599b8ff73727d428bb94e96785d84fcac07fa4fd93938b32425bcaaba60fcb238cbac5b431d73c4457b6852f3c3ff5fcb21406eb6da4a24bdbf02c6e56a3dfe508faafa14723d590ef93fd5b2ff6d7dd4833e61ad314f9264edb0507cf991ee872bc07d66727798a29abd5343bd3782fe4fab098b24f290ae9a9df6ca7d2f89114c6738b21444b38a1efc190cd312f2886ca4e086992b17cbfacbaf26cbfcee4c2a7d7c7372deea2cea636fae3b1f14e866fec97f4a6ac9d2a6bfda4cfb86a329f29f857b3c0713820e5611bf3aac3e098c9d49d638cba6d3898887e396fb206d61f81ca62e989cc86842baa86b2315324e201e8f968a5df904a48b63190ec131ea9a903d387e9543820704426e042bf3b447f64fe8c7d34bbe38bb4e4baf1f3fecf094c454bbfffdd1589367f2f722dba999d29f57e43fbc98cea3803fb569ea56e54cc1307f37607f49aad3bafca99552bd9d32477f2ad37e60124a5dd6b8d44332659e7218604833824c7af4a22392703a80a45147c48ca1edede36f40188349c4a67c72544daedc0ab352e69af63364f887a53444f4cd29217d71cf150c1e1c8d63f572eeeff8ec588ef497c94309af85f528c9857880706d3e98f997245097d737ec37411bf1a863e6d8364c3357ea70e7ee3755ff759fee5282c96423d78063e93d25d27a7f73b335135aed95e670e9b6f3d550c6e891adbcd2e130b8ea8284bb2272374b80769e3b9d8007e7c743451603458f3afbea18017fc4f9418b28dd34f1642883b9cd3e1dabb2ad6c097efb17336f5cedca8176ac1bc7bfbe0c87441d3c72d817ed94b546d782bba1ec6d77ffd3d9b8c83f23819e56598b53df05dcf2f672226268bce34ee0a59f876f947dd2e9d6c89dc22cda95c6048e8f0388ad75ef72204762ce7e76759d9869efeba85fcf2834b83798cdd33a335d26d0bad1299372a981dab2f7aa3ed7937a7443de77244b5a74886f2c8709ec928c7451e995260ed42b3a5268d9586a79832348fd3b981bf30bae211151b4c8f3128698edd02f05d83632310291b67ce1290d902179f32e9a92a362421afd614dcf00e9f375d3511c0e87b648dd9f2f70363d357a6db01b26748b28382e78295dd240df11ece572997c1fe54b065c605510010f2912a16eebd371cae40a31b129e39c0cb1214f729b8df0ef4a9bd57a14e9e4aca4e22c67ce403af585b383bb59e1a24c6d52fa72834b0d1ce52f553a47762c6e55721ebf082c6d65dbd4932d53bea0179277ed72ea77230fa3c16f85d25a636388ecb88126cba1d6de399a5e7a52d9ffdb13781631a8f9dbebedcf702a1e0c2a09dfa4fa60ea1a716f05e5042cd80e61e299ca726a6d2ec3895c247a410a0c0ccdc4b0b63637cb623effb886f4dd44dd2b22d5720c49df807d0d2eb309c50a0b88ac887a19260e22e7ef66a8ae7e8be3293fcf726013f0630938a47af1cb0c10f5454c27ed2ad4b9f084ff2d621c459d81ba77173c64f16ff7dacbd38f04cf64d15c8aab9551eadac61e8970e3af3e1529ce0d6935cb58f88b8e5076fc6c923da57ba657b057485edcfbba45a327cf30e054d972cedd2a4d3fb0a7ca571342ab26d11761abb621aeddcd7252b9cc0a73c405dd5075e342cc3905e478c8a214cc332a7e5c435384532c269f519f2ad8fb47c83c69de015d8302b634bd4e53a75754f91ef4aad876a1bb377ad9e855093e8ce3d3727b797d2f84a381499cfc7e4460640bcb7019e4f941df843402447697f11f8e72d98687d06ed814e53016da0f7bb143bbe624f56cff2f9b87a4276020cb87cd72805de0c9dd4ecfb166540a5cbd5430ebe8815d21b481ff264df5592498e1a672bc89284f4de40ca2d053c6b93826409ada5ba7e43970881bbe4f4da1951d4e72c0483b189f184b7f138ea1737bce64b8de634e2e9ffec38c6dd3ec696671d47226c5b1b29b7b88febfc33ba72266c61b2ffaa469ee9b777b3514899d7c6ba14d9a0346a244a791b4839e4203144ea58f1cf78079e8d0f3b76e6467a818a7bc72708c1553a3c5f6d60167d0048a0b846ed61e949ee93c57e7c2c4445d4da2927214954fd5cba34152997ca1535b853973a26fdd369c899807c14a99f56b765b72f51f5ce9ca719ff76cc516d81382aed9a0061c13a2dade53cf5089b4a5af0a522053568af11ad333863646b58487d5b6cc40d18d3c65a0fa7940b2742f50fb319a6e3b92e2298dd9d874690bcd30d270ed58114c7696f218adb41c350f1f816761974da3248a78397545b780bc9606c7568c610b11326accbc87ec2b72598e72da6857f10281ea016b552972cb9d5df0cd5b9830626c4be74838842a10c952723eac8f216348321f76ac2578ee5bc768abb8bdd5c9334ad142064f8ffffdaf720c9ca3de57b3f98e580307ce3a6d04db300c33af98df21a32d93bac6d645811946a9d006af372e5a576f8a992303a7606d343e68bf28ffa82e59d9d3ab075b12215504b893489a6ce8e198640c5e3e2da054cd6b0385f9c7de0e1929d37be216be4720d1d7bc1693dd372e018a815875b7c8d17a768a9524e988645918eb8f726f642a47d67fb0d80a5f492405925d6b8ae859d8240102053bff055eb2267b5bd48b5f02b18b1fde2862298765ea59f5532d60af07c0c364cd7ae5b6d5537f09e03b7edaccc0f21669af288bb55fe3229d59ab79da675361588c8e65d435a930de278afed1a0f7caa3ac8127221ce218f1f86b44e59ead10f86dae61ccd11266b91020db6dff641c1358cec1d85a9fdd252aa644ddba45bd7849524b0ff1757278617e83b0f6a7fbeb657cb7212b5851f0625e47d7dc038934eb6bcfda3dbc2fd5580fecebeb2253b7d72728102f66b9c9934da6db429ebaf9d4e4060cf0f1503aae199b20099d94dd5c87ec2f63f75133c987dbd83735354e26b223f9983f721f688dc74c51ae216c75cc81b1139dfef232796c5688a27c5d3e5332db314672f01c9597b475a1c11d845e7d037990e7ccbd0327dc47850bd7f0ba416ae46b72733092bee69f8426561745eac65c77789c1a2b428a69873e4767474fa43d74026aff2d6fd954949115ca31c11bb2c150e535793dbe7a4d1a3412fb86afd99129b182a03343be10f545a6a1ed0775f3193e6821b99fe0f4b13207e955492b620499feb12183c7d69ed76c173b4069e2fff4575977898993f410364d27582722424d3227aca954ccec3382c56e91f03d0063fb1553c31b39b3d7a9b2684f54ab72475d0a6c248a2fe9182dcff78a5a99e945f7edcd51660e12eefe8d764178162d69d9b335c528071375fdb1d76ee8d32b537a43f838d51850928e9a6b79cd5a3addf75736f4ecbb2eedf5c68b9045e435e0625cfc6718e7b4553eca93bcaa2f2322e07d3d50c6fcf37780efead8f8ec163123523429b724cd7211c983a0c16d724cb0be963b94d9fad1c1b226c9d8897c10e28f7f7df91aba9ecb052006862d5c50654952460e771c02fd397da49dcba3550bf1cdc4bd595d1e8f99805036e87228fef6e707839851e9d6259b233ca6d85bb5bfb00d735c41cbcfaf60d482fd38e368fc977f452edc95f2c966c8990cea7b8484824aad8de8b6fe4a1d7c57397227899b597dab3186c4884df32ee0f204180375f090635d86adba2cbcdd6c7e14701dd3acd77b77919c414cc5351443dd39d33ed268a567f150473330ad638272725ae860bd72c6071ec7ee9adacd748e62bca2dc5381e29d0c4900715694673f3c4a199139d228d916fc5bae2304b3c2a00848af74d76467c177e52204cd4d729f30f8b4c8f6ccb8e0ba10f71d3a805a7dee852857e50eaabceb18faffe43b024613578da48b41e08997944e4406a461f8990dac6f1aea45e8764d106a8df6180dfad7dd7baa2703c43c0c6f636e48bed479eb92f7ede3bcf83467d47ee40d72ce0d581a9f531cfa202aa03ab15e3b0fa50cd5bf4599f53d789a7499df0a8e104886e8a5f23922eddea5d31b0255bff77b41f168f677ca91803c2fb27502fc721e187ae884ec2dfcf376bb1f8f7b7c3a7b6e6d3d215a5c2cf661c9c229987c728433fd5ce115a2b221b978f18ff3ac20e5b7ff324d2907e8707196b444fd45319622c5a19908a694888e669e508bbec8fcfb5ea5dc4d538b01c86c267f611b72e809b3de899b59728336311c7b8213ee5ff9ac6dfa3477d1518335db14a30c2402e13a88b70f47b60ddcbb0155df1d1cefec4db8077db97578a31f868866b35d3110d5476f4900f68daaf3ed75e44d6e2fef95b24b1638759083ae0d62dba472fa6d0510002895b3cc23461c8fec705032bda854ba7a2a09e5085b81d635ca394e0808ae9a1c1b718f0ef64cb418c2366d17e58c4fa2c69640156e1094faaa72434a04b4a57d986a09672bf847b89e359cfab68a0eb2e3ad73a3852ce900c0726e9051e1d3c6308ef86404c83c2ae35d4d8c7dc1d0754f81283d0dcc8c36515ff865885f5b954a4ed0f1efbfabc557b533363b9c0ce9b60eeb690092b7a65c7257683d2393fb8b5961f88d318045d9c2fe4f19a1309273b2b7a2bbbcd8383a69a3990468fd11d104064168670bc9246227ec2fbe0b0e2f4067b1b8d26ea0c01390c504737ff8735c173a6ce90607c8d4e6c159a023a4837c1bca5e896d417172cfcd8b92cff528578ecfe3b3ff878d1714036fe1393f4bcf59bbb3133f4ff572ba1e46c92d5579937764b161a501a0db93d18908d5183c5fbb4d832d27c26672f47a78aba57c6c3d191039e867acd8a0ecdc06b03add61f3cd293969eabfa172792f2f1b600381e01bda3e125bde234dc788d4920e61b083fd3da6b4cd4ed57210f7b9456bf628bf56812ec303defe81e7401ae22141d68b1b72251f04b06c7210e2d6ce1b3bbb2d37aff3690d6d5634f304f76e0dfc2f1dfbdb0fae5916ab09c7ece75deecb02b4fac3dd72dd88fb0e9a679ed279a7704ccb2d0804cb07452a7ebf396a8a217716fc82d1e2ca8b7bbb1e372e671096996bcd60ec3698820058457957d6eac6a38653063b2bfc57b6e6bc267bccdc5b41aa69a1ae8dd5a6186323a07b8eb6fc805d8231f1917665104fab6e8c225da609618a3d5b48e0af6c72c87f629e6137a0971a7b441dd4265abd461aaa8aa787b072fd1413889638d365f5fd84cf6b5ef8b883f28d2ac76aa3eb85fb6f7ad470374bc039270532119e72f181b7de970b873075e3370381577a3f78b82fb614d1bf8071bdb41f5bb53d72c9f93988198ae47af831d9def4b7bcd310022b204b2bedc5d7a5e484f4dc31729883d08fd3c2e96d92d7569f59be5f2b06df86954e56e7b84c944c5e28c96e7289e1f436d98883d5ea8387d8a0ea97b27c6fd901df14f7e8f958321b09be5b3eb55a114c06798e260261e3d0f445fc6683542adafac801a3c95a4a69853ff9725422f75f8f5365a77faf9d923f3b5aab6312cf91a080beea7f5b4c21c1ce697277a9a1f36a29bbaac16c9dd68ce7baf70cd48d93f1bb096dabcc36cd13c23d72334e1e7412dc7327054b6d644330d7563ffce2b89555c8a7321854240628b56ee83dc53547d76304c4dda60b35347bb1675c0d0c1a51c743d79f576a996524729fca1f667ffb67df0d9e03c503ce3d3c1d84df40eca817f3b95de5d31f17cc72c1ca083571075f4e113f2a27ffe43b7e2447b664f13e9c6339debb4540c0f652c947d45b3b16f9e6edeb3aa114b8a853348cbe67fadfb51805492fdc288391724024d093fc2763e0c58574b85ee061405c5b653005aa5a20dd40dc394768491bdfbdb7cc1beacd4e8680abaf648e1cefa59ea895c53ba991d1fbd52f5438226fb65fec6b3047f6a6580fa89aa0ac60e98053f71851b927b78cd13473478f7e720b5d48db323d1ee1e78d8815bff251bcfca628e6729faadca60e05407abbef17b457949fa99b33101ea2929f72dfe55adebc811ba95e595214e7e9e8f082fd724330180a2091d2d1210ea55c02394f4e18830dd227fe0cba2291a2db4f1612332beedcd2e39a2d3e55a5b1952218e5342d1b8a96297ae71e87256140859c0872f0b031e7059872715b6971001cee7b3d99c212f4c4de09bc3a150850f69ea220fcf6b514fb6a51daef0ccc807569da13a6a4b0a81f451b36dec841d03c4ba3571a0f4b50a89153366c118b1a29328670f91d31254e3915a301d5aee7c51b487213271e250964d1610119d8c48b4ce5a700dd69ddd1192319d308e41081835d31813eda23c7c0383fbff29f83e874b0ada625c609b82d95122758ef34c5c85d529302d7d70efe3b4279613c54c7e1da030a3fe05eae6f283a961dd51acab3df7293671e8ab4f4cda4bf8a5e0bc7d04a3eebe68feacc4376a19fdfd89510a47f5a48d8bb0af36783903b86de9fc883647528963567b1b9e588c53ce97543ef1372a235bc822f2c2ee95a27d973ec69ed7133fe9013525e3a4cfabab20feb752572a5a547efbd1a190d69742881aecdf18833a567aff125c412987055b672887b7253f259074e7f1e234a35992c0cc25b411e2e67ea4f59e103eba2a2c8f3506272a0e8218b7c83f77673baa9890905d3ee49415d0dd220a1b82750c942f5b7c372ae572c47dac9798b9a9309a254e6ecaada4f70e3769295d329106774a46d970649820f416917456c5fbf518ff810cd7ddbb8df8e0dddfe8cff2f6ba87162104c37e8bba23cc859b9eaf1c8fb1c528ed0eada6b756983ef6706ca6440f365a5728ffb0d26da947342cd0468dd9d73723cc1cc1367d8705f92bcecf041d372b02fe8e3fc1acf8b98df26244ca209bcdc2c01f77033290715ac2572190a1985dd72c5fdabd5f400b207ddbe5f7abb0f268567cfcca5c77a59dbf9e53bf59dfb6f68dbca7ddae2a22cdd9983aa659bfdeb82664117fc32ba178eeb97aad4cd7b0372567e376257d43e1414af499a3d67d6328074bd144ea842a04e4504922031d23b31ffd72aaeacffdb79c3cc4282c0b1a091149e652d18e9313ed7408f9b00b672fb6a64a26f8d5d792325480c3937e73b052d326fa65a3431a95876e21971b037f3bd3cd700d188cf8fd9958d230535f9fb15007158376c8b21a06afe8f00bc72bed2ffa9b958b76efce9d560b4d67c5c3031b843890f75055a78239959f81218b4b74e3cdb76849c65015126398f9da85f165546904e6c0f128c738fe0ad06720c794c477cd88b17da51779445e52b43d97db8ced9ad06d8e4843c7bf2b1db729ad964c98441f1e694e7c6ab6c5534f4970514153bb7553386c00d74a51b14010fd785fbc71811c99e9101dcef66d7685e09bc68a19813e8c9112d7a018bc741a5acebe6090eff66ba98b7870f7d675d5c62ba95b61753109a5a4de479224a2aa23727ffa9e95c7f4ea468ca6a4dae49535fa46dc01886c289aace0beae59d43e8d01c552f8e5624d9fa9cf5ff8a47e70d637740bc46d06fb1724d005f63f16ae9306e9c6803a21cd6895408f922410cc6d2e893625c2ca7f6f76ad1ab4ef872bd20b3cf9325e3bbf6a276810cc62a92492d5686edf1416a5186c72ed6e00f6f3c5bb98ed4a4994fa415c38fead7b0cf4a6e97ed9670cce37ed876cb8b98dd72e85120b8a354d8377384b46ea770fdf48acadae373d7197e8a809d12d3bf2572cd8750d3098e80a62f0856f3dd68e4b9c09ed3e4d3516b948d772a9de84b8a72ae2482929bf028dd402c250e84752c3c9d616427efee1a590d32a4d0fd293f72f54eabf4246c26caf39c7b48e655643df28c572097f69857ea989b87f5359c72a4fc90e544d5d6de56c0fcc5b5c4094d9083b818488db951df94b3ffde4e3372b1dcd256abb526c18e1641889d6f03c3a88b9cf52d8030da301793a4f18c80353627bef55ab25a70f206889deece66230ca49cfb3512703dc373d120add0ec3531a11b0ea751ecd41327a8ab9798f2cb07ab3d1542d47606f2db5418a32912726dae05dde81780fd5185547a024a73613827b4270110dff8b5c3dbf04f14b7725a7606605ea9dcf043570262452454f9f1fa3839874542ba6d49a145e965f772e4e6beb1a49157738b49a60f5fdb5484597744f6dd6d2ce8784bad5fe610ce2aa262eb4417d71b5ab39534e22852532a3ff1c040cb1f1287ed9bde5e3d0ea8726d7b0882368c52b41ec7f3307cfec6235cb9129067396ebb5bcb576b24c84a26441038b39b15ec6600e6ab0c71d24fb17de04984cf7ff4015344bcbbcff1c072caad5375c6afdb554e0e0b720489ed2dda226897e02c743d504e20ae371fcb38fed0aeff3dee2f0ca70c86ee84165255b1750db89004b19baa1fd1007c41637211ec3a4bc56f03d604c0852c1d07991268fcd522aefc2748cfee2957cf50a2722ba6f3a86e6493849db943fe0ea90af6a6c4c01d165b754d308ba2a6b1dd5c72fa6879bad559888c2b72db96c8b70dbc12c21f51156a6564857609b22f09d046089f61398e61f2f95f2832b37d5a50bcc728207c6d3de29b482d6cd46ae6af40186ccb7eecbfc779bbd54dd78cc362b983ddb8a797bf379cc72ab7837b011f2ce145773ca63359660038ff95412f19ce63aba3bc11226a1fbeb96a721773314496c71840fbc9c47ce4a75cc669fc2b93f6a35236ae3117ba18e1196993b4b072afce3e928bb1e1fa2521f54e69241dd7d21b191e189311cd19adde4985f82621ae1867db49f1a334ff168bf69ebaa5a697abad59f919142c1944477f4b3a3d37699173589d38f2b9487be0253815729ad168215781734d742f460e780656e654c79e1d2681525ea0067bbb7a6713fd5f67bd885c8e22b83e153167f82e01703d2896127333d933c81209ac614fc7361dc699e50c1e68db66b79ec1eab77406603831934a05f559e0e1548ca4504dab5bfb446c3c7f7d3b16ff5e7b746dc25d721feb88f0f670f71a9543080afcdaef3faf488faa87cc8c23f98fc4293d66f07227d0bcbc1daea183d9339df1d330348d0079adc963fdb6185b7ed71892c47f57c6085be97560e6b19a66e17e584e443d8c57818382b114dd02d5502087cd88728d313725723369e99946267ab5a92a946f5c03bd500969addf624948a26820722641d3dfd0429334aa5cc70a71fe0436a9e43870f9723d09c8bcc0370f262272e34ab15eede9e4e888db4d081d13c24d7f626c6eb301e49ae0bcb735b8384172d95ef36712705d04bf5f6cd78acd79e0cc73c75acfd0889bb823f22ebe2c5572bab2e5b93bb91e29993d256764c5e27815134313a42a0caf0960ca23066ed6725fe75001ce22f4b687c9939ad11649e6036043348bbc11549cdd21b620db2739e88240bd5a76c86d25a7102b10b0184d70418fe2e8f38d3e50c3fc74f7355d7275b81579c0eadbd249e5fa62807a25d797581f26969603648c968dfb56f78572a01a5396966e498d874ccd36193b7b531a849877687cd60d3d938b3a2f227c3b378c7cecc38fb2ae1d1dc79ab8cfce90e99814d9d8465e295ce11254d8f2b572fed99f003bc36937c90427c9d4978d177c01f1ab5dc5fce3f77249d700df8a7230436f98ff305df22b45100d9955265bc839c1357520dd474a4b4428f935e8726276d807ad07eacd306b0faecaa2cbb67e60212cc5400ad381b5fe9de9ec163a2233bf0cdcfd6689e452f2816b77a3cc3590fa3d9dbffa28ec696fe45eb5045e54641cd01dfeca1c9bf657095691d1c6f50b69991a5b2c190cd53b7e17608f7225683b7a645d4f1613fb41a594808d57664679c7acff420dc884e49702b42b725fda159427bddfbde48fb9fd8bc6e2c59c41ce633cfc93ae0f27ef3298de4b48799310cdf1560a07cc70eabfe55b49829a8b47c55d903eb683811a1ca83cf46216775a2831bf06c1c4f67440a143e0524d1bc42705f4236e6973c1e6797e12729476944418fe21a755ef498ca9d81d1d58f32829f9385378deaca99dec2d24729c131fccf7d9e177305fd4fff8253279e7e2ec9a4a4b19dd7bbc683a2653f07216e69e04aa6cc836922625bbade7b594a4832f5014e1a06df263cd3c1fa56972bdbca414a417af573176472b93ed06317b0d9a96792107de3fd85f6326139a725a610b9f19e90742af65e2e43085287f8adcb3dbfd63942bca176a53ff062672427d08a6c9af83d46fd52b2aaf3c8614a8de72d0c27fb46d968958488f5c8b56eb0cca7a637f7f605795c7877be5dd1dfa9e4e0d04679e528af5de32d0d71f6d271ffb5731aa32078e11f165b17658b58e15a55fb3782ddb2a6e969d02610b728fee0f27e526d4d293db4a6481777fec4f5c9ef6e91dc96e6901e7fb2e07897286f9f498386b7295004fe482cee2d6d472a1ba9782690f44593ae632c2ccf50276ce5d996a2c0733444a636d47970813d4567730f35f09218fbc29ca7964b272198c599e4e76f13c4b8e28964f14fc7dd3df52444c26047d423451f8cad64113702ba11d9b27181db48dbe781f8f8162d60d84a61693284566ba2e60970db84b8569a9fdcf2d4d8573ded92e8a20bacfafc38a290cc20ef045ecadce31af980bc8305d438a77ab337eca3a4fa2edb282ec2b0259381db8dd3482c1b03ee1eb727d06de3ebbd80ed321ab166bfa11b361cd6124da3efd29e123257c9af2213f0a3244aa03935c0e6f2eed084289b40ca17e6f52360f04ab661a9ec31e79d25072efb62196f3db8bf390f22b5223734112c3edbf1b1c99245e1a71ac04755fc7720b94cd341b59c488d33a20a5b22dc8320ea0d07b06033427d305f79d68491d170c399f34486332b60b1949b23671538075c69d7ad6a25a3bb3822380e09c7072a9b8dd524cd7fec7cec57c810d1250675d3fb91af4a014bd4951ab6910c88161d71218350dd1df3f7e21a7123d604aafd70e682b364bd697ea5863f55d8ea2722a0c9e6c936d6e0cc31c1ee25ed022fe5e50371afea362b2040cce620f0e9c07eba13dadfba354cbd46e425d3d5773579c16e63c06f4a833c158706b68bd7e3be801b9632cc1bb4eaa999cc731d5856b82ec8a020a273168c78cd20e07fd3072713f48ae46e9bb39adfd47f736260eb39c8be56276b283676f1ffaffcc707272031a206eebd373012227dffae5e52fe55bf731c4d6935bf885d32cb484155f0387791389c1c467f02d1c3bd7d322506098c5c6b95718a3aa149ff2e971dbec04f0cb87463cf9d8f7444a5ee13d5a3106afab7d15725de8b88f485fb25352bc72313d9d5afab6189c676b652401b7e94ce349f38f063a74bd85bc22e3826a93721783b146eeea3baeb180d5227e9a72eba9c46b2a540d6a0b6d43740505527a2c379dce7914da6e5f3b202907f5776dcc146748cd9d36e176869a57e1e2ba297203efc529f2aae97e24e51845cb550ab179b72c8dd2bdc17e8f5b56d5c430f072a53860338bf68b9d81dd386187ee9af269fff94a7fd556127fc27cf5bd234e72886658073268e0af23cacb297652f7e57572c3f9dd8907b3e7b0ef1250a2e772b6e57d3053b5671ba247c93521363aaad6c6a1b80fa6006541bc382247952e2b3ac1d47703741001312295c91a339ecfb4463b1ee3f559b4a3b7243e5af0c3058090f4d1833282a4b7c3b8cae67e64afec79f4971383f9afb4ab9cd7a3dc4172f37b32f380a28a1264a6176050029ea2292402501b82deac16a1b1e1dc906b0362ca2004e0f682f6802dacb59475a8e853ebc85de681faae35907215edddb97268a0c1cf52370a7fcc4cc2e816f2baff2e63cd1751cc570a57068e01d88f48460b2a925e3e65104363c6b4da6b4207d3cfb2250b87793254c95ab32164708958f3d0db0503329dffa9efad21e384e704442d45bece38391d80dc72b5dc10cf724b1b1fb31201917b3c7f3e88814109c328f026e0a62dfc31bfc4b4b8516ce5720d0f939c1b93d4ea23c27daa17efbba1ef82c2c80afba0a5dc1ef871e0dd22729a918b79937ac995d1a9a511ad957ef8847bf8f3734cce51b44e057550bb6f72de77f63592eb913c5d9a15bf3f8349d82a487b1d6a12ae13b7388652d1d41e72b134d8b792767c838e29dce4160d0f6b59ddb11125d4031081cd15c5c422f572ca828e2f0d9f5034a18101b9fe49dc41bd42398218c3d7df129be35e5cbef900ad877310c34653168a27e4ef3114896bb57d6f0d9b37ebb248b61d150cd5227200665bd749720f4834db71d8f2aacfb96d208821910d873ddf3bd7a903c14272a11c643da9420c7c97fb591e672345bf0faf9c16d359c6f64060d43ebd17bb720740eff6db944b2fd997b47bc185043c26a00d5140a9a88162cfdc75ca8ab7727a57778a44e5756526965409ea37930640dd5b26b300c0589bedb05f7d6ee624fe24ccc2048dd85c374dbb63a2435d4ce1116ac4a0bf9718331596f9e03a977258f2311d6edc5409689a846844cb1511fed93ce264d1916c94a0864d5de02f72760ba25f4d561d99c61775de7f53ccbe421e4005453c6995167def7dfd559672802812f969a97422790c695df2798b81685f4ba24dd9bee76318efc22b225a72787bc39cd4b57bf93fa4037837ee7090486518880e738602d5cf4d16ad21c91b9beb11c2b51aa67b1a2aeb0c1e6ecf279235092c0edf34c422d0ca8982478453fff95653a8c9096f5a3b2975d7fa78a918fdf76aa6b5555c0cafbea7f07a9072651bee16d44dacd593bf40f5a591b950011d694d704d59a8e77e111a9564eb25c0c063d14ca1832939d4e82cb4c9e1100b397df262c1c1d049595978fcb6bc7286423ee65866820b4854caf8160e2a02f55d07cf9166ad7e566d7b23afa6d572fdc15c53b8e7f599392cd20fbf910b4823356f8ce2af49cd3fc117247f81a621363f019349f9329baa9f354869e3667b80df991714300a9c3961c46f75a469728813937f918f436a6981b0e855c0b7c218dc30ea511ded2162a1af3bdae94a54ea816544977d075eea7dd41713147d4924f5e9dba86f66ecebda12b27e7b76374edbfc6115004e974793a649bdd8598ec3b7b5550e296014f144fa28066efe2cc66adebcc26d02b4096d408aaec6f73872c5632e9266cf807ea174b159a0bd72777c1093f762a0b5c8ed413359fcfaf07001f655cc8713ece1b885403e69e616c6e3ec7486c7dd55e9bf939d6bbfcb77df1055fcec8e4762510165d616486f72d6886b046bf1360fbad1a9bd457be7523a163cd83b066f37cb07cbcdf0b9cb72e5f2a5656408bb504a808d305d595c441232a70b7b9a3250be0e182114562701f3aac6a654f625c428543287aa6664bea05ca5969dd5893ecbfd2ffbd0bd9e72126fb7e83941c6f6ae815e25a92680e1178574840bbd6024c77d30cb716d5572ddb3525dd456d8d84c3875bc04094df443e0de989a70f7a7e990eacf21fda8334c25217dcef920721e4690229607a8de3b12d3babc306592de5f97e806f28218ca50798d5e8a4287266b9aedaa15eb5f3941f9c014683b6b990f828058e7677257c6bc125dcb089a70593720719efe68dadfc5f6ef3ab0329fd7937cfac36c723a222ab259384e0272c42c94aa4250af51068a20ed62b0368b0216e84ed526487069cfcfea7a4e5f32a8624f43a1460e783e5fdcc3d05ffb25cd535abd3143728ad725e4fb27bdc494f1e7b81baa541720c27b72746b688033e742f0661982723af12c0bbb5ed00fe32c4c8c32be5f86058f65d015f811133115d96f6afa67729b6fd3dd02243c766b31bb325b58f8f5cd454414f2ef6b5eedb180ef9da5cb7261df161acd2bb2cc1a83f6ecff48a9906a53a15389f84e5383c7a01bacd2dd0c27eafcc9c6023dca10e1880fccfce8d0a3621624cb7ddf61123d1927d7b32636914512d11c4503e13c4dc34fb85a89abb9a4a757f597506c18b8d82abc4d340e4357a246b341fd59421897784811bc6d30db75bd824794eff0179ef98db69b722ea824729767cf981a7e77be741c29739e5d2c4f8d6ba09ebd4460d1563a9c724995bca8df7a50823054f57ab69ba3f117c8234708a3c2cd2631fcea2ed3dc72f27c9b6cdc574b8647545035df8d7dc0161461cbbdc192ea9eda6001ebcc4b7210deaaf3dafeb3982628022c53078501002c2b7fa305e35f91f4672829cb4e7244fcf836038228e126bc84daaf9a8a5bfb3eaad6410539947a2ed766b646d4725103f97235eced9db98b6d1145d0a1017762583c24a811307569f098b3262b5016cb51cfdfcc5e1d0bb1830e5f4f167425f3f049d3f0af9c7d0a85a625c77e72284afa022cb9a791335a471b420cd4d141923de3feac5bfe62211560fc9bf572af4c61e3dddab47eed24e15b7fdf709a048798094448e5bbed7fa2c3d6174c26ced527b99d4e16b5c39493c3c732f6fe9a866b2ca8b80cdaf0d9895b82e2f6051709a0bc7d17dacc054e7c71def216fd7b50f19c088c27f590f15a48b6bb8724550c6f45b08a2e083f197dccd042c1006ab646383f8daca87bb2d8bd9f48b97299e6373ce8f47e19e34850e2e3af2f446ce893e7e1153da32ea73bc3f3cd7b726a59a24a5a9ab21b2f2380893e65e11429be85977cb700db4b6df873f2fb517295843c5ca47369e957b25bad2a66780081c16f630bde780f78bef848171d287222a962d0254e8f970a560586dd9852e31e0b52e4000295d1cabca6fe5a394534e5411e23343a311a739e44b5afe09d63f66f560bbe1e19f42db7e4970967f91c23c62d817937fe1501726859d28b61c2232d68c4fc2fe6d28cef0def1e971c728d64a81f15e9fad384d6db2e4f035dc45b28e8afa9cdef779df1f975e78b8472f7ce11fbdb053ec7fe9904df51d2734843134d06f922901b36c293c81d5968727d444ec3b56a23df6a2fdc6148003b63b7945c24e270b5978b4cac315244d92577deca8365e7a50f7a3cb370e9e35f12d2f488b85210cfd45616bbe165f32872f87178a573202f3fbca8308722e32a9b2a12e946fb998e5e5623592c5622597222265d59a4d14bf3ad9fb30ce0c2c066a06d0fbd5b0816d91413c31e44d7f666591a4a1f05f7dd007cfe7dc98df8c3fb0860a31e8d78fc77df021a0bd9d8120b11bdfa9d2292f023413598d12f8d630bed3a437d628d40dc4cfcd64e6b8b0872f63e0034dfae887a3d1a8637e756002cd01dbf27838be5c18db75857fa173a72914be3e919f79155323761ebe95f9d13ece33b56f9efca53d1ffff86f9a4d0720814106cb3a95903fde4f376874e13f356591994511dbf1c923b5a236e77894883ce9c34810d9c6a4298282bdfc6e091aeb3b7fb64fa62e9a765437c3c1bc272bf088f043e25bd2d60515fee882d7c0a1a11fcfd8fb77ae7e4753ed0e336bc463c01cabe0fd04d5ae4ad5306a526008df58ce604accc051b127c4326ecd6b5729ab3148a4fb9cc82492841a5e5b534b46a5c130892f3d34049b6d3ab63a3ca2e08c52e43348dde91789d8aacfb93843188338b2022f03e64038f1517d49d097273217d5585a86c7520712f7238717501409527b54f7293bd23c77e2b12c66b7263e1318ef742b9a3eeed5ca65eca9c07a7b60fb72eb3f06a7501d2469b71f772ee5fd1e90fc5e67f9eaba5c61db30e6a901baa120f385ac8152328296fc0e372b922087f252c091e614ecb1b2050c2c115d79c3e6da424bbaba836b29336f872f88e60df3380b7d1b35c41b1d4a394ac0fc2b3a4397c89f0170d0e27c1815e265491dd6e776cafad6b4c537f43f0ceeaba501f68be775899a080d993a346f57203a2c0f03c265820b0fa7d862fc9a510c7293dec5bb5b6f72908862deeadda72b0f226c9f8f779ccaea685c9cbc940cb03965e0ff216a36643dcdb6898ea1a725e30c2471c62d7c9b6dca5adc4e43cdb76d5555cc276cd5d0b00b2138ebfb61c1afabf18d0b719814b0ba005a2e5c829097edea72e046ec4a0ebeee33ed38772c60431946759b0942536e1a77614a2c916b07cd2e61eb5075bf116a179d5ff7284a49c8655abdc35e035224690ab56daf01b7cb4cadb1277f0354d0d09adcc253c360d2c5d91acf774369b62120edcb364294fd40e1aad6c49389319e434b97223661cb05fcf2c4f4f17bc535dc53cfdd7ac534f47d64ce997d1fda5c2406e728543dba40f2960079a7b6aaa0710bea2f863d355a0f3ec5f08951e778fc6ca728e6544e3c2937264123ae481297f622c94129c7c1f1c190b66afcd0792634672eaea57ef14608239bdbd8263ec8220939088f9438f14d14884cc8aa0a3764172297a4e750cbb589d1ce1e3995e0faf546c457c3e214a7ce173b770d8d5260a1f8280757cc99a91e3f0bec8e519a463360c3347c1cd5468ebb4b209fd7ece24722a7f77c1cd3fc4fbe3bef5a6456eef8ce3e3dda00e93264bb2db90302985414336cb92b9c09b18eafd29fe71ae4e6c19967511348e47c2a5d635c6c6e77c6172cd6f0c650185ba11ca82046a3c4e617f004597db91f89c671dfb8b9203bf0d72d18318c18992a1294903b5822a049fca929737cc70c803c91db3997519235b49e4cb9b48d05f2c0893d6cf1c6a3873898baa85c0793b10012da4ed2ef2add23701cf77c6226182c19802e408933d5f4a778f0e6062676bedc54ddf045d44737263417a57ee73de0cfac1f1757bc0e19e3b11854c2936ec235e4a3ccff9d62a4ff0d48dd5cba8640a0f074c035b7657cc571ca9981b5c50def1e047f5d872657276872a3e9aadc35dfc8ce796989a9e1355d5acc107d3bb09bb40f6d909afb2723f5fa072f43e6f1d8847cb5a93fbc351aca86ba5a55df536506bc419c5b0201900f3b9ea9bd48e8ed430b7fbf62e3dad752c9193f1b2ac52c5b73c72a037d95cf087296303de6c22e41c650d232e279964d0cd60c09f3e50011e4c76f9bfdd1bf2245f408f1006947fa889dd65342c27700b7956552c1c668bbf3a91401930723fc0ff2c32e1b2647853723a77489ec84f0bf9796b67de9d53edc14b03e2f0298bd11325e7ecd1796bb4de4b8d63cc59175ea5275d6b4b0ab876abdafa4fbc5d390e8838e731722a6d8a44d63dcbec2e3fa6257af3bd309e1e5ed34993de0206ef325c70a2e1631adb587bef084307196132d1b11d5f54649517edc716ba037210b21e88d5c7dceeabee7051e9767f7f15c03ddb5c48c3e534f9aacfcefb8911b46882b2654b05ade94416d934938f450ac7e367bc4bc129bad783f701ce637256fd2573cdc90df2cb02468fbed77eb0695a0baf23d28e763670097260d5ba646d270154ba3257be39168ed40ee7c89f94f50b746bbaef04629868951b37c518904c46d6e1304f2e2425995011d781423223056236b38a16a084a7287bd2ce00557dfda632edbd4dabe6c2112609c420c51ed8ca6565933b0cc7e81a3b01f854d5a4c5c6b05c36b1d0dbaf4ee374a6a92e34d7245d3c456a03e5d8d4c416520f8655c6f5bc650814bb367eb19513e62e94ae928b018abe698e3355849c4e8a72aca95055f00256af8e714e54db3155d9db4d1d7b7562307e75fce60f059141729b7091bf05b1bb09e2953cdf6d4d9863533ad9f833bd11bb072da05654049e7252de80b93da70c29a693420c9cd9257078b606c99628239ad9562acf048c3711d0e13de7ccae24f7d09ae1f8b447adb2694f8e7fb1bf4d9964d302d0413de072f94dd550e91d6d5a23fcfaf6c80471ce688c023b4d015426dd2c3d2281750a25745358d65766976f772d5c27710afce5540d788e5fd8a2c5d2fa79b82d950072c4f28d9a26f9ce5eb10b08ce3fb73167e5d968ab71d68763b588d7b18db3627293549b819c2aab5cd92a2c83b7cce2293b9386e8dec31b2951468256ca1de972f84373e562a09508872e5a6c76d6a579b1546e2adf3cf2054e69125f9b5fb54977c7c260b8b3a58ba9ee54ad0006e8e041652f75560142d03a142edf9b2322523a98edeac9bf8f149bd95d92905b3f599eb799ccfcef6d505dcb694cd8bbee726322e7c97405174dae1555bbcb5de0406c168f7f8f8c7261819b8e4abdb9af72ba0cb1e267eb5868c752e4ad30b4aaefc0456e5ee0095aeec2f570c8cb2b7b71df5c9d9011861159046d2d1d472cce99e5374de97181b493bdcdd30b56719d7234ad8f1bc0b7023460cf95b1d683b06c46048e71c5d4a54f2f2f70e057f01872af1638fe91e3e5551f8834d5fecb77ec2153380c64611ae55497592e30ce0f72188ac060c7de0adf687e32abafd6c85b0e1884cda2a1cb2ad5d8214a7e19de728c3ae9f68953503e9d09d882f972c3bc698508495cff888771b0bbb6bcf5e04115efd1914ae7a5bd0d08db94245a7248ee7737a37ca3678a9ed05d9073569d2ac1d4053f7ac4531c365c68f38f10728c2bcc9e55419da230fc87db6e6ee9b4589c7559541d78610c67d08c90e5944cfc0492827f4e86127fe81d7cbc47cb8d010b7e7db3d573d857dc63bae6c45204bd8c5098885684f884e1d3f2695e09277257eed1af514943cb0a5381c83fbf7ae25b269347346f8d6095e2a864159fd0723c861f123af2b5b5cb9dceb19cbb167426ad571b0fe57cbf9014a7325ac4b4727dcd8c8dbf4b6011b12ae719a0531c9842888ae876d983e0cadc533c8e6f0172f7c39bd22939ae859d5c84ffc8c5a1b8d8b9aa07005f98d4a675d9087a4627726deffb8a4e6feab54f3cb21ad8af6d6665418306f1daf518cc6c93874c630c727c1242d0e49d30e6e2d0b59d88a714e9f61557a70b623179e0b712c5777b33723fe3b2d30b3063deae613b6c2b7c970d61ab9912ed65ab150f9465230b4d7a1b63b70b8957b2883539ad6c4b1efda2074c2c3d8dc1a726022046d15cd85d5a52e76ca4accbd25941936fc23bc886de0b22b2e4dbae0d5de71485e1ef0d1c27722a26ea74c344ea65b860ba45e2930327684da2ced7a1b52222794f53a6d27572ab3d90038a5c9fb91998d8b04d12ef392447e724c9e852c09404ce17c2561b72c72c74b458394fda21943100e14cadeed58266cc6c92c28cc4ef40a269c0530be1ad26404d842bc07e95b38cc30e46b5e9edce6ffa22f8f60a07958a9e5dbd48f495850f52b8a4b3a05dab9723c3febaa4e61387b370ae167a54d8ecdb704d3e810e4ebd0a500bf4cb78a03bfeb4a859b86de246edc1d263176b4e306038b40b78a35438878f486f55f17eea4e83568cc6fdc6f35445ae6efe6448d3902eda720972c5f816b59fa7c7693ce36f83e16be47bb759ae33d9fdaa75dc3fb3a9e96eadc779ae1474a77b8103562070eac91a3bc7e703e49d41326e42121e9b725a72f226eb21ee2a6b45318866bb14e3cd84d1589ba755cab74e2abab2ca84650272718f56ca9f6b1222d0b5cfb62fdf23673fd01c29fe13b9d9f2433834c9ed4472ba9f9e5037036c28bdc4c85adc2cf59093f3370d8e102dc3d7712e343914731184b5ddbdb499400638eedac1dd2caafeddad8915681cef6c3ea9409a175dcb729154d3fd2d419a8478b59dc52c2efc92841749abfc630cf67c7e712d8e3ce80cb232a877989aacdfa211a00d0fc2509633c7cf17af08b167286365fd1296976689f65c285aadb451392eb9a8bdee7a0663727ceead5fde3f16ce7dabb8c6dd6ef6b6006268f735a90f2969697f86a1abf9020b0815f57293b6b8e60cdfeecd30ea0b3a7bc1268f6264a7927e60ed248d6e31a56c6320833a64006ef056b3eb721104efe9e2624f8aed1b1b6a011cd8862ee65a2e6bc1b459b99c34471ef2880bb34b89e114e21232c18576b5ee5a73d7e64c60eb99728f06b46169167f43d47237338d376492d386ac89bbf87245c8bfc03980136353f3ab3036dd72a598c13868d916840130848e731c976bc968507690eb2a8c27cc1afdce60c62122c6974645249b21b6e3f38e16da70b4931ff64af17629745790ebf276be42ac47c70a6f6abbd8ed8bff04c2ac8bda3174408cd9980e89fd1a6c34d9c7f543fdc2c6fc4514592e9afca6fe420a2969d19ba3d28a11aaa1b03cd3d45ec2335b3272055e72126d557fef0d86bf4a87232a9a5beefbf78b843b900f8b73ba03cac8065cf60dfc8c1afa3000e5c6717d2a9d35a6533e866e2f6a41cd205e80db8d39babb9572a4c4bcc62532e5fd3dc0140e7502951c3b194783e65310c69fd94e6da4a3165e0b77627e0d95456bf944aebfade3aec493425646205504fb3a429f8415e77a1653f2de55d6a1c0d375939ae967873689fa1daa36f3f81d15124cded561ae1872940748c27116a0a60243274c30f0a22e74d2acbfd5623e51d9d6ab2dc30f4f45a8b60d8bf30b535650399b3990472fc1fef4153c1082a5cc87bfebfa99f056722b8a8dd06ad220fca5eda29c98fdc1674e9d5bbd3551f8a0806014a5b23dfa4d753def774c1bcaefddfde58d86e05866976566c190e2508a290f7e079591df09346f31bb6e86f8fed54c15499449fb7061aa1704ea66d32e6dac6fba7d34d67014a888a78c4cbb9ea750670ddc581153889603ca27d9875757ab77571b640f7268b98fe124a19fd429c3b2daa30b681d004abc70437883a521047a2c58d98d72dd0f15d4eb3faaf199416aec675a9c516c63db6ed605a8a90e84d40c56437f72fcbe739a38d2b6b86eb32979cc6d70e4efa723b58fe69ccbd0e865fe4893bb2027a057debcff639b5e1eb55c82cf5564a31dc95f298c0b2995b26b580347637210e03a4deb72ef5616172ffb1cd7b6f5acd9d81c121c2476f8c3aadca79420723429417d1051aa6c241ea4041143e743c35cdf3abd2a170a21b2e8a50dd7eb72f1b52923ded5a7ec883691ae8f83644e9b41063acb3ea762d51d9d540f9c414662cb4be39527f86747be800f42fa5eed496fb4573b58138d08d1afd2d0d0f0727bf872529a083f2f6c68dbd5c90d55920a8f95c7df12b6e1a9f0ab2485bdc61efd532daf0c1c3aea2f5c69da66ac31b2b3f6ae6b2e22e179f0918bd39b36e127ae6d0b5c1d42877bfc6db195a5f0604161c4510d3d119891f4a92c2b780ed4366c3c6824a091728bb6dfe493b66455604c5edbe86aee393368c5778354f8650301db8e7970e625a09d6de5caaf2193c9d547bf5a1d5e2f1336830c769c3c6e2f1242e468f245defe681c74defe8cfa3c75c612fa5fa0d263a53ed55654ed897250b9e1303dc410f82c491592553e87a65b71442ffffc6be51c87d711fcfaa33f92c933e0174248bb8acd935858a546793ece3e48055d3b3bb1b484008b1f1443f68000614e3312cfa2bca918feae2cf91ef5aeda90fb70e2d76951f03d995e6c91c52d571039a8c5d6b887ea76eccb717c3854ca2a450da60f3e2b0097cca5727d2ae5b4c31b7d979332c24874dcff81f20aad04cf7d95d9fdcb3f249055de7278b429f802aeab01877a4dd4af376d4780963f18df08487ecbdfe7b97ed5381ed097f65b2b0c8baa4c451f7f98f9e21cb6d4470f4d393bfe2ba02258dd7b821119c32def390e200e36b08cebac073e981c68511728b7e217cd33f3fc411e4872589d8c27204bf71317b9db8c3a50c223e0211b916fc4112e3082a37189b0f3724b8490687482a99a64f8f78b7d0730b3e2953225e6bd305237e161854e86277242fcb0b18454237947b5cc1c72735a5bae7cc191d4cde36aa0e600cdbf0597723d938815dc3b620592f1efa660de359ecc70b0f4fe8b775c64d7ea69d1c3b301aca608c87de98fb05285d9b26433a83f103af6cfbb3352cb2d04e52f0efb205d1b1ea6c357c81ebf842cd2712de91b63f107f3cc70d02485e25e55e3046c40198eab324dc844264957f9e83bf3472fa0ab92aeaa8b6977f1f46233efe69959723eebe6fcc00e6a4b5f087453e202e29ba3c67d0d4e3ad7675cad9bf6f244b17201afcd1eb32f1f87f9598d4facbcd908fe3e79c2db7f7b33927e0280e9b7b372e88dec7a9c14382ef2fe12809a9ec559978b4cd3b01f6f5fcb00bc19bff8817114ca9b932393d17133901747baf14c1c936f21ffabc0ef642a19f2fbaf8e33120189d053cb29aacc4d97b2f3bd0362f362b0730a916fed8bc54ad5c64d3b08724a5aa664e2e32b9d1ea68c6bdb7ff2aa34e3d8397702c78c0f1fc6cf08e9fa7250f58e89f0d51bd6b17045b9e35e41fc0c80b6ad1324ce40c92b20d7112e71722ca0301aeabbec1e2c91ed884952970a36bec78241646b5b4f46d106c2668e72cbcc70191cebcaaff1348a94a3270ae0cc0530847182c9d797134406e002bc723628c857dd7709402340b031c0b0018834f3d3c69622e55acf7a75c80ab8ed72212eeba699a6dcbd42141495616b91f338d270ff8b5a614b28850485b684227233833c6d982452ecfa279237a18dd17aed08fa3f07de6ab47d3b387520a766124d9a7ab7d68bcdf5d35d612f29358977a1d808f5dc8ff3b52ff3a766c51c0d72239253ac51f37e67ab92ac2bc26b172618c55c2fdc3fd59c449ca0483af642720bf67ff9e006d5465ef9d71df25cea80397bc2225bed09fe555397b53268ac5bebb53717d4ca32b555f27801165202de7843e78753906e6357d3da30afdbe4727959c5baa55cb8d27f64bd6f87bf130d14440e1120ef7863ecd27dd85447014874ddd262a61acc98e795e3e4fc98be8f4c0cc3dedafd95d1537d104046d1652db625a6632dd75eb4140285e7435b7e330d21221bc23923ff495475fe163ed72e53d24554e2d759ad957b2d7b296c0fd3700393506763955d2c1cdc15805d60722bb2e681a262bbd2ddd36e96ff7ac99c52641afa8789c4f0239e5e6e6c662a72800220c407209b2577d727b39d1f36ce76880cd2c59b7c8f6932335e36008111c38578c12c3b8b1d2520dde7fa850288b3edb52a6252e22647b6e77cf86a47725ea3441dcd5ccd0f279f54705027e7f984a946c1aec5f464d9c68dd11011f672107d10e2224b223062aa3beab8f32979d18f0cfb0f2730e5e1f7697d9c48292f7216530abc3469aa1de37805842e81efdc3d7f8879e9dec6f73c52ef0ae87c72bd0c5c676fe6296a85b5e44537f385d8ab67086cafbb6f41f5546bc9f495be396a6291918eff7096b594871c4d0f22e1198fbc3adbbc39ddec41ff5f339597720c66321b822b5bf7d6596fe43b1caee7f9154919dcfa14661244647280361f7285b59985b473ff0db234687fbc139f38501c23efc94c50402ef945a48a387d72fa80e45b9919c9f74b0902de2b49110421a703a276ff6cf21e41982103180f724a94f9de446c8d1c95dda6805ea19fd97ca8a8b4c749cf734838795e28885372953d37c4dafff9ca8f8200168e8829b403278b500eb43a973986fcc0f02d1b62f6cad9a960ad9a51665036faf5a355cadfcda5fa7a3d36912a9728f084559172226e8bc82d65a94d0b57c2bacd1eff4f9e0eff4ae73e49151ed317906d8cb30adef53dd411450a1968ca285a2f2d87e5961ff69fa233715359cf2ce5bc61443d4c79d27bc34b0fa0a82f3243183dc751c7bcefff920230442fe35b3286bc2b540a7bdd02e113a590204214555d2cdcbb1681fd2dfdf39d47e89f80b57d7e9572bad76a9ea4cf21c51ec0f2bced01949cd32c9837aad4e3f3080ade30df2bd7380babafa7d542bbdd04874f58e1745ceed50d72a44735a29b473372ef1e61541ca3a57a0661e8bfc3adaca2232b90e85841ee12a3eff538ad638578ad4b500572f491e19cc697617979addfcefcaeca27de8bbf2e8d43b04461b5fc2fee3aed72f1ceb22bae625ffd2606c31bb990a0272dac89b1702f6e600cb82acd109f8f7290beb728de6aa910c7e07bd4f0425c1c202526ce23deb96b26d40aa9d639266c32dcfa7625e78e22e8574c2689b984d11c0bf6ef9d58713466ccf26ef1858072deb1684482608ed20142cef5eec1129fca93803d1c496851bcdb151c61bd1972c237e7a9d82f0c093325789d82862992a2f482e79a8c9068ede4f40bb7c3f67241369ee635bab723fdc3d736bfd7d4d5ad2a4ed097418a3940ea61a3e1877c1435310a8fc2c7899c6758d689d52a795fc2a3fd24c5bf64e4fdc332547a130f72af9e6c94bfeb8c5de6d3af369602d704ac5172fc87c88d767a17d192505fa272ec552a4a949eb1bf02119bccf5a19b3c9301b34a6045748a23339c995b63aa728d044b496cb3a48cec8b5d9cb6aedcc262d34989b8194cb26ac20028eb83ad615ba4a6b93e7657e7b88868ea40dcd32417d5267b1e1ef4b50fa6dda9b3fd7b72b757df3d41a029110852cd492bc1e95913feba585331137fae558492c43ee76971a01a01fb19c690f6c505a3e8ba0c00cbe9a73893719426bfb79a1a795dc072a3e693698f382ae70c47a1ada1e89a60699145709ac2e05271aa47c45770636db762a8eb8e55c9ddbb9c45e9abb6d66aeba281e40beaf0b61e87db275870f9167fb87e26941f89430a253b0e9bf3d2798c0f5b47786f2a77c76afb20ed9f39633ae9b1315e9b453d1b0558248610e20397202a9abfceaf5d24991d6712f3db72f80696bae9e7b4080ecea4a716e8b2839cede69482a3693ca21f9dd92c4884721961c09776e2aa4f4fb3a4c1a26f3fc0fe0f9e4941b8af6a2c97ad427f917b723cc7b264dd3a0989a854ee8dca8ce5631ad40cbda501e0a10c78100948e6645d20eb0368669c7db61fdf096744a1b9ef6707e743ea16c72ad8020b0a1dcbde7291e45cae733db1a853a9a7b286e5bf1235364e44a4962ed9cac89ab367579b18dc4bf5aa7cb004f44ef1c6daf868ff69addfcedb46e1fe8a56ac8d76761f382e0d3b9eea3f66227dbb80bbe30f25d55d197b73250fc5e93be667d1403995f072a558334978164b854ad31fa58efc8830afdc6d89ddf428c1504cb25ece487b7209567efd6eafdc31a46465cba9f6d7051193b54813c89a6a914828bfd4591f727d788ad66cc195d9e70f9cc7e189fa9e55666a8f049ea1a80ef96e66b6cfe572d88502942da2b641bdd3f11159ad78bfeeb11b6cc6ab137ba47ba8b2c0326d2be9b7ec2696ea4db6960519aba2850aa7ce27a1f2ae2bf343641060a813b59d1ec7c63894e9691db27c7b6111c4d704ec9784b84055366816dd28023d45fbdc4ff77f48d5a2ce6497ea8fbbfb69d84a823e0f0c44e2c39b7a0da9e6616d933f533d1d622470b371415ff2a93e4f43fd6790bdafd07200806157df92cdc1ec4801a254fd84d5055e447fa7ea59c0ef7224c168d47b6c4c263bb77fbf21c5d23c723b4902a97b55f4c5d8942db3e41a352986e2a4879a73d7950323d295229d2c72850bbc4e297ea787738bdd37efa29f7ddda069eda9e8bbc2b4d78b2cd3447646c65f941870f5854f86eb660ed37c0da572dbc7611e9dcb95a9b5aaf5b4856172341ce9d2a1ba2082462c655231040cca56ac07b17a39fb0d02ec3fd112410e671236ae2eadf596d61258414668a30650ba4fa8088e2450179990ac71134193729db539398753ec75c4b7e4bfdcc5cdff7b7cddf52f30834358e1a55580f1e43f97b7839e1665dff5e419737ab95c7a69918cc25e5a481bb11fb4eb4bc326df0ca0dd75cf124134929ba6f02fa666199f1ddd01d301db9c48e840a60f00a15372369b5f7a9b648664a7f769faef28ce36ad38c813815c458a05167112f2e19f596d7af6318abd176c3ee540b8be32c0832fd2ba0fc183a3f2b6e26edf410227339160e4cb9468c7500254be7c0b622b7e7c3231e1e11281fa05502c344b4e7c728a1b3f08ce757c8a6dd9eee44be16e190b335278b5440f3596e4b3737e93d571ac38249196a79971e76e2b148ab438f063aedd455d50a89e2a1995944963bf720488783563fcd44187552582a99c6e9b6007b6265672f20f905ebd2bc288fa7232422fef7c8e4a46e2b73fc46170724f608aa0fbf9e5f4d793380c49e36ad8036ea9042ea3210a0cbb1a5c37f3354504b95efa303dc692a5cb5e3520702d202e5372e451aa39fdedc2e13147ae720657003da7290a53e23c3b72f617b433957218c0aa28733e9fd876cb1e8f73f6ae588d8ea03bef2fd801a933977bb540e5726dfae278c961259fb36bb8c613124f1f47d551be8984da15acba8a61f53fb5727a257b4e381bd37f723365d0fbae9f4e2747e98858034a914e8dbe0dc7100e1e05eaf663442dbbbe0034b00712d6314434075bd3d2018f19ba7e30be657e6572261ad46876a7650d5fb8ea4d48b78400833c1950fa67b1663f85843f9a1a2672402149ed271670775688a44d63dccf76babf73d98a0c119c66c809d06260d37225ff8ceb41b387ec03e10de44f0828621c51d7114e58a9c6ce9fcc7f557bce08897f2f7e4567864319bca1399f181044b8b5bbafab8b43313e041023262a6b7299da4d0be75ed918013c5d545238334a2f65626015e4d9b856ee3738263bf67286a79adc6fbd973d689d12b13cc65153300f5334d2157b8d5faf411a54fb0c58c3952c7715de88f7630059ee092dba43eb2fb21fcd015be4a412d1b857280063e97656ab7a81cfbbf3c65dd772f0880871d55806e26f212f143f6adbc2878a7281a112775333bf1bcdd35071da7ca5583476ade02e249e3cdab8a86259b1f072e67b86a8881143dfd37328d9d65bba6e358ab3aa3647e46e04bcb498480ff872a0b3d59d4812dc621e470cf1438a9379b39d73cd7873a99a2152afaa1d3bf9728b0e2dd4e6d57b8e9d05b653a025b52c437b0f459d3c6954c66bd3644da82c5e565545ece9f57779ba4b2cfb67e871422f0afe0a5d5bdc627aab8c3c8ecda4724d73496234230a4662ea1131701080555cd693b8eb8719f42cb74790ba13f33adc2bc9cd848e083a9464c5ce2569094384cf40617e97272c7f7fe993c6ecd734b334ae7a4d165539879da9f4c3b9414aa181cd223d627379c0e2bf8a1b3c7272b6e8a2d67427c288f4c87e6e22ade93268ee042a2a89ca7ac732863def9ffe72aff4af3e5a80d1f7c6d4065665feba58996aa2ccc4c66622bb45709056899813c7d11679a74cad19e18228f6aa2422a8aa6b3c7144d87a07dbb7a1229f9fd472fcd65b8afde449104b1a0acd2143140263940020681ffa040df85baa41919747393d590eafb33cf049f29f4159425dc3bb963b6db0ce4a72effe4fbe2d7d0b486236d0627307a715bb4cbf2e8d9b16c252a24242f34b855a97d9b0546596a7031e1075a97303de7c7c2ebb7ef38b487939d9b1dc9e7f73d7d8428369e7488472d4e5ac8a9c9aa8765e84bd375fa12eb0449219d0a5db0b6b950c85047d29523be5a9fd4961b7a9474385a17efa1fbe3ddce89a210ddaaa8fb01c90ff6982d85224e11400f7662b027fa1b257cfe2ed90bb07f0a76128d33ac1dd16aaca22597231ec1fd80e8826ad6f987e493acaec3f42f6cf95562ae8d556ada515e30f0c7213145825081ebb5b3a90022913a0e7a5cd4f29f5e8fe7932ff6fb0a87943f76f33e12bd9cb83532e6c82cae2370d6f74df1e35e1a944022160751557fd0616422e68908f5990673ab6bf389e51d7c75b492d52559cac3600127c70d15d7c7c729ae0d40d8757fe26e388a03f097ca8a01de464c0805c6e8830b35bab356d890c5a55c5763dddf0d25ebdb8b690620b5c49dfc22be4f3407f58f94c2e9a62697293b20791e78082c31ae2023483890dc8d41f274057da1d8eea328fa82b7a6172af453e096879feb0516161ee8a53cd43149f15db5073107a0ad145a7fa8e0840022c648cf9f048183531201ebfd3aeed7902676f28fba72145e74d1e7cd055728114352d6844a456c16764701432e72fc2baa3a4b01ca81a7906af12fb7a24725648902031222592da812589378874ed21b7807073708d22a07a0a3664bcd272c137e7b3fbc75eadb1b040f7bcf7708452a9747c14f64ce8a35843a43bafee04590764522b363bc19f7664e208363ad3f0a1dd4b9b0636ad0e7277b929ba6b721aa60b4e34ebb27ca73cc0176ae84b5b8d2478cb297908b5cbbd45838e5e177249f90c660b52c74375445d6bc8ffd8cf297edc93b14a7cfeb973609410d0f40b19975c1509d6203b9150dfeed8a70678059e3ee814b3ee9d1e3635d17bffc07293b6f841f2a636492192f39c43a499009b413be882907418f9c257623481cb726b5a46234512968cbd32b3b0559d86e3b7b8e62eec1946c36d162d33f333e737f7006b82b240276dac48c6443ca79848bad0b3acad40913e211d320c0c66c65bd2dc96daec7eee7660456c942f0f6e1002aa7005036a69906becbeafff61ef49ab2db07ce964465f828a507021e53562830fcaeaf0b3cedb4d9f4822114d41729f4069750b74392e4a4a67f96a6606dfd8037aa5b2181c5af2e2c5ea02bb3b72ae14f65bd8e01dc4bfd136b0b056608a6986575fb65afc54443794e08266de721ddba8e683a4bb105c0097b98e93db9507e1f7681193ac294971830424d151364b0a0c92cb435be741a32bb33ec686af1439e7033c4a802e24ad7dd1aea640726bce7bee6cb0e2b7977569e6c78b9aa073c0a92cd98a9b9a1c2e1adefb84f872af8b9600abeceafb4bd89e5ca4e60ec39ea517ce438c31c3df46cd0803b46049653e5a4ffd3ffe4ac574b466c089163444c39c2a6fd81ed04452e76fbb23da51b417d5cc01885156750c5b311f2e063f77f8b5e5cda4e6c46f4484607befb719f273fb08ad8eefb4b862c006fb30311a3aa576375bdd8fe69765fb097523976d3b2171b588fbbd5f210713e9268dc9ddcca1836cb7596ec4b9789702ceec4a724740fb256fb135b23ebbee200a83c3fcf43d1a4341e2ddfa0df3087f0c385f72beb368a45db1299aaa28146560e4810ea21f2a9c14f2001e59b56fab2fbfe07297a8d1e3aa53cfa60fff6ec03cf704b284f97eaa63385078d715d15715c96400fb753a074fcebb5bc819cd048636d36d0a7e9ab2547f0b26373c07bf92bb165ef50438d851271f5482ed0e8538347ac20b060ae88f3ef9fe4dd1e53fe3ac1a14950e7dae7d446db3c6715ec7f6d912f2b0167c7b6905d4478ef0a8ea29e831721416edca15b34e7a9f282db9f4bf59816f3fb3a63adb47999472f6e99a37f01536e6c8723619ada27118d781e02f9a859618b2a7e217b262914c8d1912b0d03f6cbd3990e0326dddab16ed7f391ff7a7e6afee2d98eda2cbdfe794ac6c85653f975f212b351e5d1edcf72bf6c9b94432e2325cd00f7746075868a3191f2ecb721d9ad8a19f3f6d3319e8f4431597c99d6df0ff1513152b20f6f2a4bc70f50d72f4d54af18aebace80621eabc4b452e8f44a9bf713ec8c906c9badc11668415729d6c6fb0e90de72e2c95a6453544af4fe8aa2e60187253dd92fa6bc9dd24f972111600bab2c20cf0195fe3c1023d03b800e65f2aa4cacbac71345b74692b01728415c35bb4418a869deed5032373400bcc9bff52df00b2770ee8b19a1433df47cb974a02d47195aa0fdd76db1b29cdfc77b07f67a45c6594731d07b3db42df727c5b8a9f58edf69d497fedc1257fc18a542688e9e2724a23fd76bda2768fd472d478b9f6174c29050e7daf8608f7fb39ff50a323263d2ebdca220e65381c0c4949747b8e918ff7dc322e12003e7d58bde43b31198ffb85cb366570c7fbdfea725dc3f7c16d8c316f0bb410768126694235695f2eef3c6d7bc7efb5a6091f46450f2b9eb849f9c1fadad0d335532c7a76f991664364a381c9524fa2d352bd7c721ff569feed511a8287a86c3810197e5b01d096e2d1b423cc6596304689861f22ffdb4f7fbba869f9d849d3aa7af6fd99f334ecdeb5c092ca8c731a1fe0b780729bbd13149076352b56353ec24013c6cc50967c14af33842dd252cb4098db9672a5e591b8053074fa08300542e35de594140feab308bcf2a4ad89e1c7531e2072cf907573c3dad5820fc1e40766fdc00eed68ad31b9d380cf8781c7eceba93a72393e97cd51b1f4a32d763f5a4bf920e971d5d7f22afb920a265e769eceae08720863f0577b821ce9517e6d552ff8a83af96bd69545c618bd1549740a256b4d720a27ebca0cd4616dd25e1530a1c866e3985e1192cb60ac5967c720ec1122df3518233b579db85370b8dea876eea68242edf6516025827f9f3f3770ac651aae445b0f0a2e6b62d1b86d3dcfa12add9737c52c633db4238e5886cd0e28bc5bff7252163ff29986e805df498d28ecd0f47fb43ab1aa4433e34ad927a541633b3a38601c5cd8ef3679375918e127a141499b045278e5348e8fa429e2f2a8daa11872278f945db459b2e366ae287a4af9fdaf4f096c107001f98bbc0249bacf5d6006fae982f9dbce6f051064d22278fb82fc377d19411dd8b23267db2368bfb49c72ed43bc915a7715b40175ada1bd2bc945d756d23a6e44e5bd2c7153b47eac41435cbcf01c261765a3feb3101db7d0a8df881da767a4ab9caa264b0f9e0ecf20725a639bf1a4926a189eeb5d9e4d550c8f54d8bea200266b430a1a8cb2620ddb72eb47fb7a82dc5d6e6e97407fd7de1e45960ddc8490ab2df12f978678a2577b07803664171e48b3661e3d8835348204fd2ff7d1d4409e91132ba8e2a5c63ca00b813ff1903863e01eaac81e0d24b2cdaa673a921bad0fac9fa509f93d4fb20458f4c4531a14a122ec4c9ef9fca52531610c98cf40edfa8d9461332f6003f0d472f56480d661e421dfde0dc54a0b1c7e8502f01f40901799f5defdd3f0f5234872ee2b34ecc18a9826d5068bbbb8ce755176e64ad45e0d70b7207b25da5e72ba721d0b22da3c328f28e3732d0236e8ebc8d1e7ccd8b0dc48d97d9f06c911fc767243cfad85644adbdb25c17a6808041b2cf7171c4d254a765c5bf6412d9d144b729eb144ebdf367073c7b364a1db8c4a5611cb9518e13429306cffe8fe1f7546139787773a9a7bdeaf34d64e42e069dac9d259302e3c0fe122461fe1b5a8404372a35102e26c06f4714d06c0d6cfe12d47b1404ab4ad8097b9a4253378e2b9bc72ffc23f7ab880b732d8eb8f5d9ebb2968f735ce17ba98f354f4ab4fd7e88fce72ec840c92e58d912b8c41e5245aae0a893e4fa904c345ab23e64ebff82237fc3ef3550bc7b8e14567a83eeaafada50048489e512001d977ce17968466a80c7154e4c6a6c40add3611363c04d74333f38b6bd9074e7ebe7838da66f77a6452fc721499ea5b28869921dfdce6ee2f986f91960dcdfd07bafa5a575c05c161ed2e38d73d4d2fe014238db38dc401d2fde346989a440fe01d481fd18a60818516d044b7524fa506dbd4b92efec0189ae28efd6c7372646125c1646e18cbe8dd468a3d9dc631c75d14c4976ead8ae41336649964ef845629a5a92b9e92286690889672df3b69377b6be5586ec6d38453a9b82c1692e77bfe9a1b92b8b9cb9af27495175d1d52cf33ad2a5cdb6354e0366294dde83cc66c04e64675825cb2e2f2dca47250c675d402a28db7d4292f991dba3ae386d249733dd8941671e8337c96dad06452401769563be20acc73f587c23691f8405219e8eecc89f2d8893f0b9b199c72aef758adc4b329d0c28a35a4b089f15b159ae9b29a89db4f936ed25011f8fb7260bbd898f27100d9ba14c29ecfeb699fdfcd2bbf861a4c5f197e0b3f50b96d4c1fd0e782d095dfe0b68217f8400159add6b66ef83760238e55c8df390543dc25f9a54b2856cd4398a985b404d93e09f6d3a19ec9890493471867fdac3bfef51342008ed1d42eb6d8647b9d34e56b9d2b445b23a7cedba9490ed41d28a5d1fd1f85b775803f1673c91a19953882a1eecc6ad9832e4c72bbc16b598ba197809b72e56dc1a40c3e895bf68a7b10a5b847d702b95be53182112ed791a1e4c5a40b6316667c04a91bee6dbdf3046e665145c53d046a6c3ec5d317c63e69204ce54372dee77bd4bf60d76f620bafd5d675366868bf8f9349a19117694c922190e0e858c2685311e229cba73a0e0024d65dafa047b42b7f3604752d1820df32c3061672eee315accddf5fdab8fc838c86212c00c14e73727f8e3b4b198c68b4ca847472663557ba1954f9524e35cec4e111525622e7ee486a3c1af1aa3101a37b97b47215fd2b080be17c67cb763641b1dc3704978eb21a9aad0a3a55288571732f8f72c2074086a34d6f83be883aa530f39898271153f8b95dd19b2a81f2bdd29e14725b429d264ec01ce1ab05da74a56b6c51454ebde250bfb330c9a73ff6ee154f22cfdc910c97930f19b1eaa8e5268fbcb43f9886c15b8b747e5ebc3f345fa36972ffcf87c5a6a7c418d56628f511e8f2ed948e1bc3bac05dee42172de002ac5672ccfd1bdcbac20e176752b855143500746f72c1cf06c1d9f32460cba67398ae72866830796b5868e83e38b0e1860e30ccd9df1c1905f822047b0d595a1777177206a2d6e7dba841d6a18c2d6bc5b9d38a8aa4531beafd47d3f15c6890d1949172a97284a9619b21b29094b6087cc7425db705b97228e21beeeaec7a8dac8ac372afdc1d1ee17d2262bdb51177f20a621c8b4d76e36515069f5cab14516295c41dc851fca4f8f40fd3d2480d9deb0f243feef01aa2214a42c5d154d0e91cba585865157ab2dbcbde0cb212dca71d9ea666b2f11bd4a22ab38ac3474d31c6099104eda85ce4c09766ccc8d1974d6cd4adace616ad760c49300a7b8788c020a4ce483d0de408f6e44b4af951dad783c5b32d495ce0441bbc31f927e2cbdaf840dc1091e3eb8bd8fc0628aae05d283d4c711ddcdca889d6955899cae15977c7226f721e3a3310d4dbe4330ba71b5fc3af7bba9fed61cd32aa2982bc3589e646415e34ba92c26dad29b1a587795cc5502384187f3acf301f3a195365c7c85998e24272d9ea607223df3561a7e3d2cc94fb0e4896e1187b732321f122089c70eb6d970a93cdecad15cf86dd3bd8d3b37944dc55b93d456f8db1e1462d1aa02774a73f6a27b42bc973f3b9a81433b31b824b705df9d43b1dfc44318ed750677bad8e2f72bf57849c1e9e73d1ff4aa7dc0a97fad6a871e59f6d5e41ea4b4736703356d772c66fe974c26c8e233e3bacad0718f080fde9f6bd21f20e07b74bbea3f06fb0469fb1053cbb97912228e88624713828f7f15a4967c376c6810a506ec047c00167a5230adafe365183c2d2834ca4b0eec557534f1da03b6484fcfe34e20a080172fb251c2f0c93f649f8524a997268abdca93077beff12f7626199f68cd55c20109ee847139dcac2b99fa6e7d1ba0a6537fdea39043957531c9a95253cfa640e151e8306aba12ab3afc18c083e2d4381b257819ff7c0decb459947550e6fa94717cb8f3d6628b2560d9f8b6ceac166cf8b98c4d652060659bf469ce602d3d051722495a27fc0c2ba9521406b920042ff6a7a23b784a8b43d51038f99cf1db04003054a6011c8eb91dc4a0cd2cb1ea342935781539f5c5d559b8f821c02f6640e37eb06099798a25e47fd63c3c40d18b5db7a96c41559e947581cb1cef88d2c8172b76abb99eb5fcbe182dce869fd97816430268fafc801cfa9e55675ac788e224de7e33059ba0ef326e6f176628a54c88eda4a7e0b69927a5dd42fbf9ca68cb272ccbfb9311ded1f1fd7205b944dbec52a92ce691684d4779b6497894e13b0ed2bf6ae181b59760288ab8709bfecd78c027daf136cb22c11890501120123ab1445e4dc54bdbff3fe1bc513779d96425bf83ef23116cef5095a6acae18552c2fc727a7963846f2c8958a18998c7323f70cf866dc33740670a6a98d8ec9f22e9e71eaa0e82c758ce8d5ba67877f25f2d45b2f8449ea80aace19dac1bba29e64ffd34ae6c9a4bc3c8a63a7bd0e965e47cdbd5265b268a2b201b446a3e3c7bbc64e30bea00d783ab9a36ba5529278be879ad235f718f208f89dd48b482b7339c3e6a230b755a26f7e4d8a69006dc75c4558dca69ee8001a7e5814c1a2f977205fa6472d97d4bf27a47549d8c6df7bdf0417ab941df247ee7422a41c589522cb25bfc1b9a1d977fcc91881e5f9858886be5c4f68c9ecc23474e6e9397dac8291314d94dd8ae09b03689337eb851276a1ae9765e253a1ea8465f666813c78718f07451229cd52bcfe25ec23c7df82bf973d3b8227d4d3cc83cc6eecdc3c5bf48bfb914170d2b3c16a3e7f452a31bcad10430ad9f7dbc2e971a2b266d68438455d661776c42ae24c3d664992fe37f30fbc22c1a63bbc997036293fa8fe68b497f7f52a6726b689b9b391d39554ff05ea7d796650d41acb55fee25050443cc63ae18537d6458852bb7759786237f8433ffc998a3536ce2ed14f7068b3a43adb69fd5838d2178d13f0a07c84cfceb4fcbd934ae7ec1f7d9bb947f680492423fd048cf016663849d32a07ce0e28a41686815fbcfe062f0a85ab568a8862a22cb49bd11714e7206118a73362b33c41731635b4a0006cd8c7f38660178eafe8dfabf0e528089723eee369fc118fcc3a705ba11f21a7155dc6c2b3d26d562bdc94df212e942a27210b512579bf6abd1a6a3c0831ff34e514aa3cdfc373711003ef6d6575c789072d35b314a948f4c32da60c6a532f56c9996666412286dda86481d1377ca510f7267d04142a4c01b7245100d99fdaab270388a22165a4bf18e1c6dae464fe62628cf128fbb3334a25c0c46572f45d755157dcad1a1baa4190473ed460a0113c872ad08c20c8b0d6299bcb65e3bc21cbd0d2ffd35c01f598ee2c6c77c61a185043368729ded80d363736ed69ac8ae1bc5cd1f007fc0c5b28e8ba9bf67a8eb1b3172a67e4800d96e8297f1ab36e037ce8294789212110d9832c06ff8d1968346600eb93592471ebd9eb90d01d5478f5f6844520d1789c4d963d4003d2957e88f4d72c60a71a131febc45d4fc4b524211c3d1c5788e51686a22778e0a8028ef4144544f74b78fcd6aa9d53dbeb91b074be31816f610ad9f60efc2993a3ef361ff822f9ea522a46353dcfffa5fe0ad0dc20b0f243ee23ac5ca7d7faae4397a7e24d072267c7f666c810537da68cd705975cb49b7dc5a1d5e295a1e189f099d02cf8172b28152e6f1530bb1d985733baddcde48076759076148290e0d29793a9cd8a9726622db3cc92574ae1e1b7d61df9f6e4691a3dc5217648505fca7f838b69bea720d8085530ffc38d7753783391c11ab5559cd94a127d8ff1e3b87e0773bb84872a4061c4595b4dbad82b3cebbf09ab3d58b6cfbf272f249976e830fc1e1009772f6b76669ea43b370c62e7e173fe1794f1dddee18fdc610da6b55e7edbb821e2f788d89807ef9d593f0639ed081eb7a5306503d73a80f3ac3f87dd28849997d72e9cfaf8ef8bc39dee33a84cbdf67d1b5d1b77ed9ca45c86549d1b0988930672848050ac9f32f3a99f5376b3fd2e1138fe97965ad997a3a770a08103de2f8ff72f40e422bfbe411d1c77fbe81a367292dd2257f2d604c0dcdec872a2bc44b0c721c9767adc3554ac34f0891d343ea328613458a326d30b4d6be88bf33fbe23172fe5dafc8267a421d871b2aa4f6e12c53b376ac1341cad482b75811385ecde416c5aa42d25ff1ebde6a1652a81a240eeba01c565e4ccbb25b2e8c83df9a6ac27261325258aa70dcd3a1c60afdc365c5a8921a89521dc0fa5116e2dd6fae64cb41daf25f032b18e75fdc90e7ef2687e4eb0501772fedf0cb112ba969ac12108d4f45145347bf265381eb29801b334a1c0cf37d88665690b11c67ac4e45d718c1721bc9ec1d279e5894f7b709ebc3f7c7c9907422e5f4ad4db96559726510f6a8720a9272a2bff1bfcd79c735d11c527fe34fafe0d2aa901f39d87e88d8aeaddd279cf6a3caece6de694957a037f2192e9c937188401eb18702eb28ef1ab4f4f672fe85654054e3cbc4f5820b12c28d1c8dfc257f0991303c07f1d24ae512b6f672d0e016cf07d59308902486604dd535d6a0468847040666059a4080431e11317254b542c9ff0bbf10260a5ed470b4c1451d2d184a17c17ad41ffcc99b29540f7240d2cd35fc7c6b69320f0c84679bdff0d163b0de3c78a5f1448e6483d4559671067736065691db2b58014e8f390b489d43aee130c7a4abee0cf853f1730db10ca2f11c52f28ac1863bd01377b88e4faafe441955a742a82d481fe8076376e172974787f99f57ab371f55de6030194cf11a2f65e6bb73efeda5b5beefe7bdc772beb0a6363c40dd132ff37ea8fbb16a11e55646c524aa04132440d096b2ca0872495fe6318852b1a0e88af948e54612849373cc77a565827ae1036e7078ff957227e04b541deccae9137ab87df23f407d8394ae95737320791021e3e4574bc8548448f69a2d9d81633980c3d450dd39d827294143a8d48d146ad27436d6273b113e13afd28e3ec74d904e952247dbc312ccb6e580ac53328a668d02908cbc51721754635174717b0a4c6ba627f56b370d21e69ffd06fe333ea2d0bca2166409728ca5904ddfc47b1de7dbd586ecb6e7f684314a8763078d4ba4b5417fd4994c5944beb47ad240c99e0f9645ebabe4e01aa95b93b3afb99a53fd31cb97a4ac8f6404beaf2dd09bd0cd893030823a313f158b72c4a863bb97be46b131ca13225c2a93eadf7888315dea23f25e347d5ed16f1c549d56a3fcb71ea0be80293df4e67216ca3ae80ea804cecb4de9fa6b5a78dadf3412917a203efc322e20b1fa6402725ae5bf377fd653c4ac60ff14895154b419525d5d3d99dd8b2f9aede035f604723dc54ae2d9aad15710202bca47e206362b9c27025358b7bcff1908db967a4a2f93d88e2bc89390f458da7856b8e14301993d0031e33eb0f4146dedf8aa967072b171697cefd1c8af4f77f0b35319fb644119cdd3335b3f97482f226a322c0456219a7b01f1110c97a4c120cf170638a022c8d7abb394a22a1d16227ac7d74372da7084a0bdf06b1b370471557ade32465a492819eb27692b6b75f7197799d17243ce76f2e3538bc527c9045ec01b2d222d1b6b001786489a02f1be2b278849725d62bbbda2d78b9e166a105b4a4c4b95fac7774256b5deacbafdbdd29d06795403e14a6689f92649f308f945e5037a59f25106b8b681ff0deafc1804b9ebcb3a7bf765cda27ed371d81ad0715cf4390bf139b50d52a9315c223dbf537b95677284526ad687a41b8add92c3a1f731c2e90c54289030091711ade782198e6b0172e74732a18fc09e4c923a1f99c11b1a5a67a7c99d19556addb8e79b779dff587287193092b3c09e3e380a69ed795410c5f858485ed27dbf5cf193e332c64275721467a4eaa95e19189b6272ba1501b512bb56748bc0cc282c1469824fb2f937379ed44ca2050741c2ec2229a711189cfbbbd48bfefb72c834647a8b2422e7367282816fc0a260497d26986668f1919a66fab2a420557d773cddf0316e07e12b7254f5c1f1a8dbac4ea9f9d934603e32b438b74f48302535cd61be53acc303087278da55dbebe5c349f73d0b41e5e5fccca41df32702abc593f151cb4fd3230c7299d73e12805b7bb25a3cba54dfe186988d096640a53cde7ce071a903d5eb385addb8bac5cc666780c448224b093da024f43f6c8fc1f516c216492c854e06bc72f94b24cf1de8279ea5d28a286b7fbaa4be2ca2555a8eb1d001ec6a43273945727e9df93d686e3c17ef4f136604975e385238b6a5d210d63ad117ff7bbbb58e222574272cf650261231e6264d40b745945fa81a43879773b5277f4e248c95335f9f93283b0fa58965d13abb5146a2a15803f8b6266cae1d6cfb2555970bd0ee1185e5ac6c5a449e296c7bf4345b161c4c6f52e96a58af003f3453bd8512c89d50e5e924c4416cd56ee2c6d4d893a16dc14b4aa18d9e1a0c796f69775233c123345f82fe7746fda0eab32c5d89e13e4fe7926dd71984442d85b9ee56892a38705d58982a26f2cf4c5a767bdc2c8ae4ace68ac99c32944c624560a1bd256d85e1726446a4be7c9ac6c84d5e881e8863f904bb1dccf0d6e6aae3fb8af3cfb833327226c56e1e97ac0a00787ef4482d21f8d1e2acf2d6da8429db6a8e174f169fc072c4370c1eb78a7a9958489fdd2dd70e10e0f7fefb1e65fa6e037a16579f10d139c4c573151d5d69cd3b9c0ccb08cdc7c2ed435ae682cb2a2a47da5b8d954d8810f0c23c00b26606428b93d16b5e9d870981f787d0a0578652b79c645e106e9172a790c3272ebfd4d07c97b9e2860798f6b99ed4f6dc02f224ceb51e2d48e5d24fa748243e97ee1a53ce07defc5fdcc30504e37be128abf13403668cc8e13ab06aafb59993f471b06f92d177576041e9fe719b0670410b65f4c50ae8d2934d0c720c1681d22560830f05e1e18c7d2166e732d5b4947d1d8284b64132d02c8b0f1a1f528411f675d324b8ceb54fd6dc8f8a35130f2266a295d465e076c5f145df12afeb6bc647f1f295f806d8a6c03cf2e97c5caed26b6b791954ffe065960f1e7222e79a558394cc7ef400ef699d3f7272f03f812a49bfba93bdecd75880775e72baf5a6eaa830ed8c7880c0a1c30dba290f4b9d1a17c87f1497bd223889de9858ec764ef8a382c2d3e46f5cafa20654569c74b2b18cee4b3ebda8384917265172628d056e262fe1862b4a9c9461970b5c4568a34cb431548bcde7e1262249db7290a177ebd045477cdd1b2bb3f2a3d267bee671b2d358520f96f715628c31dd4e6340618845ed251937f08c81bb72e791c0db40eafb92641029f142160472d17257326f3a364da897e169930d028eea139afaa47575f38040c651833c4a2bb5720a76abb16ac773b8e5b30674091a980d2bb0fff96600f3756d896555ba8c4f4548924db48ddbe246cd75a97f838e48618354391be61850d834f27a443e877a726d0b79f0a9ccbc75071803f499ce0f86f530f1d581e3114c85fef737a10ce61512101ae99ef561a42cbe2b752d2675131b4bffd9841d8932b143868b109a1d7295871925120163c69d40796dfa984b1d95655d28c0f51c8e3db032247432c07275e5729b834b6c6b2e9de19532247775de287b68ed35916807f101bf5df4c17269b7ad61aa4ad47c91fdd0bab20c701a891ee00d02f60fb9d4c868b314107372cd761b90aa84dd77773373c58219cd68eb2b398e34b3305e2abd0c410fbd2d01623a1f14bfcf2f2fbbf4089afbcede372a7e25fa1cf4a934edc162440d309972537d594746a56e13460f85526de3e8c7751c43adfd2cc077a0b952cf2b24bc7256efe83708cac751026be7483393f4590c8bddff0f33d173b6472f75a9329c72af08c952aebcdce39688eecbc701e391dbbbf844c3fb9d0b572b947b36669932e2e2512b1224e1799c13983ea23256e270a319236b9ad9713aa2860db9d3a9722990b5f36eb5a70ec6c16876b96ea65b099a44db6d9244af8c882dbfad8c0a3fbacd998c5550e363e7b260830a02c5c67ac50ceee85c09b7e059babc7ec91272839ce3753ea93447dedab7ea8d968498e179a8e5a12d99876495dd5490a0122232f009effcccc8e3b2b390d3ae971b821220320f4a3b37daff3bd1036fbf7c5233f81306619a1ca95ec9d9563f829f6d4500a35bafd0324fd2c341433929667274c31ebf1b7b14b2406c88cd9f3d29e6ad2f5e5c305019ef77b6c1fefa850972027437c54d1e5beac8c50ec4e4147086b9f8e56bbb0d79a751ad87f03229db4bc7346bf4b3d2fe95684daafc33c01fa0a172d798a8797aefbd0d7fd49c28e272af7dcb7459cdd0ea5fb33025e3f60ef297a67bc5c28312a9e3c9066370708572ef5eb811653d46f0bbc925b3c4da27437f09044d2fa8eb3f2daeb37efdc9fb40b90a1c333ecd0e095130eff0492599fd4d3b4d02066f1669c7bd7cd7e0b01672213876e43ed7990314c3fceab6dba7a1aad9507a025d39cd6b598657ab4d9772b5626c18dba57023d44f319b31d66a9b2c7b1fc0d42afa2b7e793165336d7f7261dff208c5fe6746a2d659712117101fc6e57edd02f1995d8a575c5f7f1b4a5db3bfbb9482e0b579821ea06eb32d0506a5bb651e825e39cb138bc9ad8eabcf608ab1ee0f8a6c13a81f1610448a298251b4c293f58a07b76a41b3970cd20d4672863e6689070a91c81641517e2994055d52df341a541b2e4f3f42c5d164bef721ed6a6a2a3765b067b561b74a10049c6754ca7f0499e64573b472112aa27ea2720e2f95a8a6f6f807a164bc0e47a04b474414ec4a91a41febd32ca5c93ce74672d0425a2ed2f26757928b7f087059827859b4b4590270894f5cabf4e47873723c293662ecd2913b5aed87f417faf9fb41a2ece8e4c6690c4a97e9ba81ee9fb1726e17b3f91a413d5b1aa9bcc7ba37843bc79eb67336b00d90fb3f15e15ffe6560e22adec0e1bdfe7d662d7379f2937b249aa24287c2c9ce77400a0f958a24c872213d8057d0d6ff6993226c404bdfb564ebe8386c2df432e9825d3d6da3408d7222ab6eaaff2e54ad22df83e1fcdb300bac14b8caea60e3edef7b8d756de1ec0d3c5dc5a1f44ebccbce6644ca06196e8c74fb83a76cedd8cdfbf2012adfa5c9422885eafbc095c52504812850b41157441c1a83d2924259b276a848b6a193c30679e626125943e762c37f71cba93fd729603ae6b7cdea228355e1e8a2716b37723097df438afccd806df1b2baa84d00d01a8233a679630f5a35bec6131cb49d27abdbfe433f5da03ac37f9b9fe25de6429c5d416f512c2a641c3d9e272b0f172bc86619d23bea18cff96dd673a67393b14147eb1403115d52c0f87be2b4275f720ed329125be0f7d2a2bccb71127366e27aeaafb92846734510bf28181fb5d91d15491b7d894d98cf723d8812bd2550fca6d07f8bb8b748239c224548d1caff726e77fb573c2aac0106ed74e991b0aa93b720db21d032ac2220a6dc9276c6e772533fd763e1834f6cfddd9867d9c0c80ac6b59f4fa6de3481e1f1542dc7e3f16e1d4a05dffa624da05083fb9e7576e7bb6256d7df24267626686c1480c25073726fa5688c3d37513edd7cbebb223489952673d70212b2f7d1dd022e9fb11f8b07159c558eb268812768bcf3c94cd0420d1c8fb5d9eadc83649ef1a981475c9872ada0e9b4cb525e48a1bd67eb0060e05bda3434bb8bcca0ac7676dbcbf7654772cd96bd19bbe8391fd519d474319ae072ceda423bc3979294b39d97fd799c194324edab4c1273419fd7c959bd3b83cf9b2718693bcd5c56b99c084c46ff9117650e5f848cd5ca132630239a8365400f4de700cde59aa492eb47cac7ebb6af13702187f9b1d3b608d4ff4d43e7a9fe23b38550914b8839b52fc52c05f8b93bc23d270317e3af3476d964345acc536cea636512897ac3265d117a990e4fa2548c72a73923f3d689b060abe03a383a99411f371488750728abe214af55723b8fd30c1a22bda95e79107dfe7f33c6e661a76e6f8d463f79afff909fb57fdf088c2572856c042bc33babab2a2e473602c39305a9b6987aaf8c624524f26150009fd56fe795de5209a91f486c12fca2b7497b1759cb3f134a4b50f2b4a7828e5225117258ea14ef65d598aa207983cb157d2daf85ca253fbabc1283b15dade3ea1dda4e96fa486b00dcf87f0f8151a9f53757e504129ebb5c74ebd5d88285a0d5c26f728d77592723f820bee86eb909f8ea743e541f11329a76d81d2e48fcd3025aa172cc71b3bd3bc36201ff71dec38b2c3646de940b3727673872719730bd06ba0372046868d9f5d48a577566d124d1f47f4a15f63120f31adcf30c8f0a6398ef75720c8d200d5b5f81ee0ae3820de4b5621b0568aaaf3004544dedb1b47a5ddf01720b03b35a78ca5975287f65581bb9c290f100a73817da266ec87d1f7b01f24a72d25652b8f9bba714f68d4f9317521993a600fdd91b3df60b5707bf17bb245272b51b9aff781cb8dd168e33a703dd4a90f7b9dc9499c580f24d02128cd1812572739f69e1c9449f7f594f02d16f1654a19b4d2ce92700218608c3f7691fd9072d83d239ea4803da83fb01048f617b62fbfd5905ed7ce3744c7911952586a8d072f90ac2c6e9cd9f5f4adaf77f7c668336994ed5a13125c4745acc08de1a69a672aeb2fd617b75db5588f9e0ddba9bed121678feede92311829bf6eff50ba65b7227bcb262ce04bf49dd4bc08433bb0bd943eb40be0ad12afa95f744a39b208b706b74a9716e3b56cb01a1f246e706a98b4e5937fc5b8dea4693327e088305a24bb69a1222943288f38cf364227a7848ca627c0a99fe1ca1b303e133a6547d6f725847409bc748551665d1b1b376ca5e776e1c6755676bee3c583b0aed78ed4872928ed7956424a655356b7f11c855794a12183a00c835d2b1aec4b1e4973c15723ce5bb14b64c696ff90f70e13eb49063f780c7bfc7e5d16325dff14241b2046fbfe21a61f68d9ff9bd0d8832d8a585e23c2a5b6f6682efb6be9dabb795d2fa2e1abac6e8dc44fd1aa19579770881153eb5977470197388cf03f5828c24c6272a4e698277d5de03c5409f2c51bb108f2f5acb8bc61e83173a67a198f30e9d897233d5bfd39fb5a90e5191d847c6916c49a6627a2cd8c7c93d24db333bf1129852941ac0de3245b06b739142ba3539242d6d68924bb7495a82c4536a4b34a167726cb29821274e8894b7284a11aad273dde1905da06a5584736718058a2f8c6e728bf994ec0bad8d89d0b815dac80cb9a3ad00629a9c12428c7c6376002e72c272db144d080b65d592365ed2618f18a94e3d40b2f1e0a072467a9b82c185b39a60804dd4d0af0edc8ceba058991d0acb40722966e821a75aa05516760eab19f75839c89be218fa463c21c7c6f417730e248848d441a2803c3e046920a57aad247205cc54eae493881cda71471ec9856bd46854b69326756b6e58590add7992d772b555ac0822c833083eb139645caa15304259f99701153f83b1fb618f02921a7226ca06fa8686facd9a4da6155c9e0facc9039f64af358c5c5911414b30153b728dafe96501870561e7daad5cfe6fc0b117fa3553bbc2e30f4752d32c32143c05f2816d019e5cf1a9a4b256599883c0a9ef23f21157ad3cf5b551abf1399e0272e2264caecabdcc9800a0b6734361b9a9dd530f67d8f90a6ec23d089186abd46b3ba5f6fa49eac0f4685de63d0a601e73d3761499e44f172025eda44f8535816207aab22276c0eef31c53c981f1827d5f778a60df8a20a787586c2fbe02815172442c47602c9b581d30cc631558e83f5aa92a7335de8a6797d2e56572f9153472ee483093a8a391982be17f0c8dcd4dd2471cffc7bfa4852c01c4d1303016c7723aff661e4538aa3fc6f5fa7bea8a81228bac2415002ecd4edde8d3aca924fd42c19dc21e92937c0cb46a6dd327140e937c33aceab9c1c26b8e68b766c89beb72f290d91a3a14ca2559e3d3f39ef4f2afc42c985e4ef8967779933f5016b2c16104bdf33e071e9e50fca18fcdb14b204ec156a3cb03da28d8ce62b2678bfa9772ec5c51582b99c3eed4a6a5394dc0fb056a7c6d9f42fe14b4fce528fc75f3814726e473cb70e4e6a6ab205e7dd1df0d6a6fe765c1663183408a94c62af1b90972285e82f65fcab81e23f5e9bdb578bbc97758e7ee2a30a8f344ffd102b65c20728e1fc2a35215619e61a4cd7ae745a8f46c05b8201d348a78e463dd0c50eeed508ad3ee85241d455575446b7e95a9781a5a13f9e25d744a1c9343093a2e5eab707fd1fb5ddfee3e29e8ce4f2ef81f28d78fdc0292ed439be9a81032abf0df3e58cbc3c9bdfba21490d580864e4b4c0b4513ca9dd4d1ca085e8bdf55e278d95c728868725c1d0a476099914bc244ba91caec6abfd11aa6a056eb4e555e09db2f72b0ed12b3939fbfde6b7fc327d3684ba56c08e79d936bf2ed3a8b7f1d379b6f7268902a7d7f07c099865935626ee438c8cb1fd4b0b4195faa74ab9b5e60260b7280149d1506902090a784dd324256da21761dfef27dc6f8bd79638870e55cd7014ab190a1dc77de9da14621738cf71fa732dc0e5020acb824a1dd422f4ca95772a69e42e27787b0afde9e3c248224c8b3cd098c53e48982c240c8c448c1c9ee7299a175902ea8fdd9678b75178a52bcb16027b6eedd5201d3d2cd0503b8b4cb345cef63da7134ce7b4ba3cd66cb58d4ddc845b5ef0f880c5cf47cd764c6e821722e78d6cdb8ffade38f9a0fa6296009aa2bd9615efd3190d4febf516843248872a92bc07a166e59195bbee56f9d51eacfa254542f1b2c31bba8734e0b0c0124243b4957f681d5d646aa232d22a1934275c67ecd8f03c9443dfe3589ea0cf831563085ada6b83ea8bf7e2deccac4496420990bd8dc14a476fe4c0da602e4b29e726a59f08f0ad2a7fcc57dfe3b3063cf5733d07b7ec5dc71a09dc9352791e14d726c4ee606c2181930ac043712ec8eedbbd78952a2bba2c371465119a9c1332c728d79512d0c6e20d9a4b5744c7a0542001ecae2df7ed348b47deda9949e45066b14527be7d848fa223c237fbc8d3f3e5e75232fe3809b501c9b024b9fe5f53f0c4927266371d16cfcc6e9d2c1c142e126599888efd99f6e15505a132c5d02d27212468e342a51e60a61fe9b89e24b03f7169abe4a36a96c34b9b47eced0adb33bd0b6bf60e3f57a481fb64cc45f9fa9931f7d17eedc2ff904d77473ac0d59b61b74606cbfbaa17fd29244a89a9be470fd4a2c90ca663a4e5548a31554c720fe5187cb18722c193bee1e6043ecab099986bf93aec330573450362d2053fb29297218ba2c9d6be017aeb4eaa606aba97d13feb4ba35d0b7c7ff2f7c7af5f40b607289060eaec84c9173b6779cf074c14c9a7007a33c1847889714f51a3e22897272d1f6570ce8701a6786b1fb8579c9b16703bfb59015094361f8bae313c7b70f72956e90f97a60015755973470380f6c97af0f231bb2413fdc14f267ddc7d2397236247c3d60a81d96c52239122c3a55aac4f6ce84396ab24dbe3dc54a897fef7280f526069be72e2e0154ff0224f3b1c21a9a995696faa9abd204c5a9be7a0172cf00762934e415526730431499af50271a1d65204e2422e3d0ed3459247cf97224736909059016a5a39d857ba443556a1d4315c40093a75618c58ecf5fbdc572b2510ecca87f2761ea53460926d1b36d1167d00f97289f384d247b263f653572dfd9b89be400dfadf5272ce29168d8097f431c6570c23051583f849c4c35ec7256f9741b80c5d0093551c69a757c7e5c6e9e7d2f99b08610f0ded99696b08b2a1e65fce713c3effe8559b85d3c7ae293dd96878907a8f648af38edd4935a087214e7928c7565556e0896cb06289bb53955543812939d4333e7205b04951121589da32625d5edf569ee4aa153b7960938354c81b86db935642f8a4a75fe50124ad4b1ea6836341e9f1c5a355f2068f82e84dd2c4f0f9d46f677a57507c5194c38643547522eb539af751e66ffb18b3873ef3deca9347baac5d12d4bb04a9b6d7265ae16da5ff3df9fad3876551fd262830031fdd756b1592ace3f30f0f8135a72f0073a9e95216aea097058b1caa0d6e590389be69b2178926a83e356049ed272a5be114818f8203c3bfa0fbecfc1bb39a637972d347bb48e3ab5838b111834696345c743037d85c3d5622f9a323827a995ba356b08b478939caa3d667c443670330d5181924a8c39b080fbeb3da45776f212f2c21164a263991259349bed1c72aff9230d31321ab816f7bc78f9088ae4e4cf3dfa4fa0c21b4679e66bf88eba7212ebb9672b099b7d218fc40bb92f10431076ae960acd368ae88e10ea46202b72a0bd1a766fe50ad26638c7b9b158a4b85743412c29af9e8f79940f2a82fd6e3d9510ef62ad406fa33b1e73e0762eeced6d2d0977d37697d68d51a5b80f8a1a724b181d2cfa127607ddfc76c69a195fbf7335d0152770cf1e777d353cf056431d5abd5a77573786d66ed56f418ee9c9e337a78095da899a562468d13ae70ca2729b6664b842afc76e771d721d13baecaa1da4a618ff87ef2933b56d5e4f6ba572225c2b7f8662be667bf777276d608af385df3c05b4204aa96e978cbda58d657201544c6520d68705a1004af00fe3d7999a8b3909d3adac848b12ec7b067ef610238090e5bc67f3f4e09a096ef3598c833066b9335e00dc850fa1848d694121109f269bd5bfc455ef8f8b9c358427038e5b3e44c037fd8375e6493ba5861f8672d5ba6a7c4374e4696e9ff366bacaa1e61680668e5d75bfd350dd6d1dad62e272fe1ee5ba1401ee581b4dc7bdb3a31ec825c0a6635d86bbe40806db3c53169f72e9d44e89f34ebbfdfecc14be432ea5daf6a0835eb74dbe1d0d0d0c23f5ef2d66d1c6fa27f6b978d0c5123b17e1b5e5f74e823d62ce12d2d0b2838be26585e1729be3a63d38444ec2a90b8ec86a743888100f79ffd744e6ddaa85c510da09a372a1298de88b5d9b9f150d94f2d771ba2a088a715b56cd09959f0e2127ee49c37236640c908a5dfe6737b1ff1850b153a2f39b96b862dfd01aabfa412acd977872a8d97ba7105d256586bec66d6149bf4c693cc6adec3366b74f524bf14ff9b85ca704bd6048d8c7c438771881aceda4e56390241baad301b97aed62adac89bc0821c7a2b98f5a5d6b17cb3284b495679865854be07cdc9367ab2a78a62aa13472cc91eb1fa18686608dfc5181260e4c238fc110836e68b2d477c90926bee43f7266ecaaf4956c1ee67142957a4ef6c5eca5f8b796fb489785160798d3e9c4d572b5544c40f90306c6ea056cef4f9ac2e0c9e4ec3e5e69f9be01110ac614a6bf72f0f67ccf235091be08fc122f26b3360f5749abbe771d0b17100212dc826a4c72917f009fd9481d78b69681d2916e42d9f708ae21c3f0d8b5297250c09644b972bd2d0c14d4f0ec3b3c0f548be1982d68096162b254af8476b6bfee384c8f2c72596914c39db87b11cbf62dc642b5dc410a773848d3c2e7c6169d239135349172d6bd04b3a81797e384edc819889f8ffc611ab67d3b88f8700f3351325acb33159bf947c144db6f3758eabe58a60ff2abe217202d1cf07a0c6ad87fee41b9370505d37d949b0efa8b3d938d7865349c0d8ddba883231192f66fe89545f204a5720c10f3e513d6b7f751b26b9f231bd110f10d02cc31836029da8a1e29994314724bdd23dbfb23715137aaec72f0240e26ed233500fc5fae318737805d22920d72fbbdb3213e43bbeac8c0622991755ae73588b4e49478dcffd36e7fc13eb3a172b275d8ed3e3aa6efae3e38b4a397d4aa5f8af8212f3a010aa3d2ac25923896724c1aa4b4951e0079539330322f0d583ceba42595a1b432fa8b3c4b39cb7bcb1572e7d6e63eac4cf367abb1530a122905d1c7ec99e4a53be9b6536de00437f339cf34d8ca0cd37a2087d83fb298f9e7ec4527135e8b25cb10d2dc7c982b1c57720096e704846c319033de941a0d49d24138c82db4b7b3fba6c1fce238f9c67d72c0315906485254d00f2492895a5a12eff687806de9b781a556746a346b269e196290d5e3897cb7a0468427614490f00e88284442987703af6a335462000a146fa373c7c0125284cb492424ebf87f2f1ba1d7180ecca698b314751ef960c4bc729dda14f0b9b7acfa6e3f54c1582edb9eaa2b88ada715ad920cf0222dd4b601674ed4a42029171ac38b3fbbda3bd6018040cedd37e7ee86230ed57ea32b77f632d06210b978f52aad9905e3eb744d7a86f85ddf994df26b09bc60712e5e0dd4722e39e4b8701771c05ba8f11f49fb76066cd44477e87312f815686c1628a28072190c4b2d3a4d47e996dd538846e2e3fd4ca692a2a346528e7a58d4e1f23f7a5821833deadecb153f97438ed300be012d8c9921bc99f9dedbd4469fd4c3796c7255afe6f15929cd52e5011e4e39aa50a0b9f3882c00052b9f64383673c334d807f4f1c0c2bada4b117797021c82c8d7d8e29052937d0b02d19beafc213331fe72e9d755f1fabfedbcc5f9970d226dd5c279a70028316b1ca32a73c2d85fdf5a18cb28458cd91af3cbd6becb70582d8e9c7d7b2eb910c7a79d4f2b6f4f8ff73a725e723a4984538f11f04ef8bf61a5780b4464d27f5d0a3d8e90bc78ab5da1a072471c7e7016e2dfd37d79faa1e57f7f3b676b616ef2d2230c5e175d0f4483de5a552f29928713d7caa6df6f203cc61adb0c7faa777b91916914f96e20985a45728eb6b21a6d0951d41ae2cf0980a34ef639b16814535ae95cd7a8d377426f0e72755342a0354eb36b6fa3d6e085d34fc51a1b33efb5346dd2b696bf35d3e7d1015c4982c3fe659e571a0b71cce26dd68bb9d0a05d1e23cb2a0de10e6f92fbd71a97bd4ee0e67373a2bd292024cbb581b0269eddac73003e13c216d70f531e5172cd11b0f65b0690603813210a43342ad0bedd7ad579db8a47aa8aaf03cb6f9872be57bbb5b2da8424ca06e35fab5d15d07f79d0c0b249ed60061bb28fe6bcc460371817bc61ba22c4de2baed3b2a6c71fd1a69c8dd51ff96458c04559cbcda27247865d915335250f829e35bdc20c25d62afbf1b7098224a6da1c75b0acf70b724678b4c4e9724fbe3ff45dabe5fda1432bb19a78aaa25fd673d6db47b024ef5ca943457b02179edea1fa86e025f4e1c88aa5ef0d6bd9c21043b8b2927d33887277d52b28e56fe81e8d8c29ad53e1c782971b74e6b936495db0b528aeb0480f728cf68a0da02c2267d598ba7b3b4e069d0c0cb601d31cd9218b5c8278806f1d72d2a537afc6e5aa693d021ec8d2daafe4714bf0ce3d22d7960566c999d6e0996349acac735d087cf967e0dfd77d2df0a5fb97801d41abeef734afe3f03ac31e72c2e37c2af374530d16870b97a8aee94b23f70f76f60c6eb7080924415733b15d02c81a66bf49ec8958790fc947cff1f8a5f63783e270bdf61e108a293720a372cf40b4f6830b2823c61117de256f1fd47617a07b22ccc4ead6097a8ea5e63672b87541b16987f8ab615f1bc1399273663eb662a1790297d076b4cae34be65c2807d08e3ebed4718a8e9f3af14e19d0d5592a2011ef7a4deef9839e6a703b6a7206bcb2204f8ed9f76d994af44e0d4861c2b6e9a2c9398e8b460694f354b1f872502574a3c2af030075074530e2ce9f461632a952c3f2ef6822d2acf5b4ce8f4daf3f6ad37e9793e9553591680712fb8ab2782df4a23d742a166b2f593bce2772165002492e2b8cb2a40e3d02f8662e05224ed70f3af755d8990a758715304872bef5916293a2b11ced66cba9b6fac4b80548d1a318e8a44ffe1f5061ee0268727c7c8387f8593132443850ded90352cb5d81e12cdafb3fea81cfec9bfb718a72abc4acb188befce71a74752ceb8ae2c914202272ce3ad421099bf34e82b7de1b878b2dd5352d2b7acc3821751cc153e8a80d56503e2b6063e9d5f77c0dc79044c9259131c124b30f550839d27ecdbdcda794f99332251197ee3d122ad07613720c31207719f0b895774187a62bd6724bada3fc6bd0dc483f29e8d2f4b6b2fe72892db6f71ee183df1cd3deb31fe35c8dad0b8f1bfe1c6932296ffee959139a62b0a2ab7f9afcf2ac1fa75417fb913a8b9ba7b15a25bde420deca8abdc136a52cd821395928666d8c20e601d9837723e4284c5d84392a0ad5a3baad170c9b8c72205e85fb175a1dbf3770995341e9d232a12ec9a1cbe78a730939840509f060723f2a5c7c33e228d9b9aab97282f24fb9ae7a717512afa98c894a9e130299c4723565e456c2162cb4a79bd9132d714bc6e8b1928b39ee1bb803764f7c0bd2ae72f8af2ae2ea6267cc3f8cd7159545c3d710158587998dba2b933328d1a7dab1726c6a19326935f7fdba08d5a93310489611a74f9e1afc0dd3c1ac45720ab1a34521b68d05eca0c5913594ab59b7310d8425c63a4bdda58f4cacbebc03a3c4575f680a064f9333dda286981b9142eb14b62f3380e00a4460d7be904a6b3a729672acd702d4cdcca8d000c3b4c671be39873f62fd2dd26c29bcafb482f711df4c5773653f60a625034bea57a1a308c231fb2a2d29dd6ee1666dff9697b75d217b72e91d71422e2c791850bfea769c4750b24e163b4b2d4a79427022cd6e1da91603565a9b428c2905fe83cc02bfe7c1568e908c588b5bd47db9176b4573bcb7146fefdb5c07a14bfc30a99f2e64d49a439cd4383f8b6771305a065c30ef7163fd3aba7b3c7ff545f18f7ca88f0fe59cd367586793cdf8f41280db2aba11b3ee8f41e3ef4e0ed2cf881221a5e25d5072c9a7a2445130760bb60a2d2d259befd90d20485c51f2432553f8a35a76ed3e6b1498b416571fcd7c1575dd65c85c1ace66723e670e1b8d73ee57039214bbbd8b8d5b5a48207654c5a7f6297d7aab2c3fa44af34675e15654c654bc77bd66939b28d50c46e92c7a7f45046c70fbe9a56ed430a61c5beccf93baf880f065fe2dc8b3415057d6552a938dbdcc5841b2e9e0a51f49ccef27a852d255e1ad26d15dd8f192205cb56b58bf1058df6d4434d1f1a3121d8887b621e12b3f8a78171f07527be687d4c68e19c8c26170e124900e31d903547e8c61cf58fba023cdc1dcb9e0dc05431d85803a93f240cca12f876de76072a8f313903d1914d9676e570c2a160d3cb9c93e74b1c3c943c2b99f543e9386729d8842788eb4333c3478c8edd8f65cbf17c35b64e23d5a7ab5101d04e6a6d435d758270ce3303d15a38ddad2667fc506e9e36741a4f3b31f367fb80e6b87f0722e06c148ce13d5b45c6c8643ad8ccebf8a56a696d459dfebbb99bf71c9d7a5521180ceabb6b01443612023e8fd0e38cf5b7bb5a4567007be69cd802db9a3f140132b8cb0789bb839dd8e4d6f0e36938e123dc83501dabdbcd062b739f71189728372c699a8899085faeac790e870e0df57de3919c425651195ccf87a481fa8721605f1f7cfe0c000b0d31c6d763ca1a65828185ee636a256982b2b5a82c5f672f967dbdee5f6abbd014dca18dc0ca4b41cf678b5c078179b83e8e314b89bea72c3a0bbe574f71f7768e7a6e5aa06bceef1c5ad57ef389b1ea81b83e51019df729cc1f6881f2d2402040e6bb2c2f1739d1327d088f30b331fbe3877d8648a3152c58de1d6fd440b5258de49ddfcf3b7de8964da9dd64fd2845ea3d62c87e03c05ed49ae681c5eb365a7d479c2d99d8e03546997ae7be4d423832fd7b3e8ccc07223a75baa3936df6bc1ee6414214b07d21f344094cbd97f64d3fb8d74edfad57276d1d2dd8fd062e342e432b4e61202dfd3f9332ca3bb4e9a4e5efb1667051c7253f5e330c4314b8d66850d1376936de8977695c972efdd1df49d75d85f36de314112e2464fad7a610d6eccae2e43b559061fce363059c60314352c323ea6c52534ff2917b9984805a509855211b2c2e529bbe573eb7cee91eca954d3ec0d3472e33cf78f559cf5acc0b44720e9a68e16a1586727eaf750558d34df833c6f243bc9c5bb98490f0fd06e639908760f65d4b4fb3df9fc925a4e92ee1c5a00ee0f72839501d14e456767a371ceb42cb51e00f95bd01a7dd3fa49819e86e471121624417601bea834c4222fe6fbe5350707fba95a0422743031c4234e1af76fb3e206268bd1ed9254a6a6404b95736f7a2281711477394ed8e9dd8b0fed94c9c9ef5381a1edfdf323acceb07ec08b27e7a2c80f3904c1ae44c8a12d07309270a90239e0222a2d39a7693e2af2d16c5d26e10b96905e39075fa97b543a6fd00997643f9031d2b53d232299d5ae445b2b4addca14c1ad96e18decf9cbb8da6c5e26e072b5857a332b308a2d069adc0975cc07651ed947237e212f0d59660293418de07276de60655c0f196e3ce7ecc6ebe76c4a42a1715799002a88743a120af5be2c72e15ef73348594f63dbb2965ff574e923461de9d48a7c8838ea6264cd5bd9be351e707a5ce02c2bdc94fc095ecd798c75fc453c779863b1b0cf8c879a3e5af872e34dcaa85263bd0f6f8520098210619a898074c1d7711328b62445fd736cd90afd5304a5e7d458d9e0a2943f9ae0211c849e5abc329551a4907c1af9eb7cc772be4974ea27acd0c4612e4c6ca7d05c79dfc7ba4dd4040f0a3ba8a923e0798311605a50552100853e8e3f3e2ddabb54c917f1b337f62e1e769fe11e03500e82727bb0c0634a89212f3f36132b8bf7ed2d887657a6575db968b9cc6f7e2aca3972973f6590d39156ab55422d04576429819acd1758cea4ab8b3d51ca3a373cbb1382ecae86e81d2451af49a25b921da1b446549015e690a67346dfe8e4173b344d9e3a6533a3250241f86aa2433f3a39b8cc724fbb76097acf2a4c1c120fc3c608f6be5842c9b21df5e539a1857694f274a992591767df5b21f8b957d695c3d8724810e2dd241fc0cce2145db65a732791f69873a49e7babc8000ec71a527aa42eea7725666c147a00b1dffdbbd0da7850ab71a796113b06843305787fff2174635ceefa01d46a8427beb9199c1b93c1285a4664df93b57654f6cc47eb19b4cc7231d15649940f1fc02bc70eb6a6c9d3b3085f9219760d33eaf1bd671b34b8e57252d54ad77fa621c670fa2ec9489f4020f78c0908af74b9edf54c8f0a4010735fe25a2b079b3bcb55833471440b3c633eda36d1967d53455a39d5d3b5e9b7501de444f807e709c85e7a969f96a1fc14ec9eafdf934bf5bef64d96d4d737a658729ff33aba271fb1b1bd5da2b5a004fa2aae5e8bb2e4dd3e41d22e2c074b1e441835f6225dcda86f9e39ff75bc47d217e7b51adec4b9035d1da9841f3b15c1c572ac61b38a6c2ff668e483c7b0cbd27b81e0dfc3d2139279da4b74014e68e57c66f18bd0a53535185c4c49dc56c052ce84b02a156fc03d239f0021d98a9c6e0a11705fd19f8668fef1b74dcc9bbe403f9c6c9dfd1d7351eb7011c9047a4a78765fc8603824ffd19698893651cd7102402407e2d678cbf15b5d97fe0ed3ff1175726e4a3336d881d238fa7adc853bba20772a3e7dad87afb43f7e0e9c1a54a89324d789069a8f057348242591f258695704351feea0f5c4ec24497481f32c2b92523660554b6d50a4f7aab451d13498b86f2056132a163e58a5ecf009ad88e314726303ce0c8dc7acf50c54fa9a547311713ff21d943ae08495cdd6b6cbc550dc72af19c1944955e526cbff78aaf870b75d9f5d0d1ecbcdfa9798178fa6769adb725dcb4acf0f9008eeb7703ffdaf821b5eba7c33642b8fc61b34b3b835aa731e42375bb10a2de3f03a96d7090cc9722ca0838be33511aaa8100f10e08b60290272036b6053e18279c58ff1155eaf8553cf07f87921f767f33cfe5ca499638f6e7276cdf6580b72ac7a704ae576b6d88263084257e33fab673068b47e49a32dfc725a46238268d615c95594528284cbab6543a6cb0211faf93f62117a1d2143034b610716fea5d91eeea2aec29c45f2eec7104ccbd5780cd8f1a896011e0f4489395b3efd3aac54af7a5c387d1d3cab7c38e2d00f8dfe4d4154fbaa63749f1a5a30bf1eea6bbea83a766eeba229764c34e376194c6cfec244e3d4d99d474ffaa472a3ccc16902d370f1fb1d197653f9a2109bcaae845c62db729b30051726c03929a7dc4e3ae2098576056e33b13924b34ae8eb90c190c7969451b8eefad78660722287863781d2212595f186278fe7f8fabd88f4b7f3307fa2c895fe9224d7216914ec070dac3812445be60ebf418cd5d37e4f0a155ebd1875b5061a4cf11bce5513dbe69a0f21aa601b52cd9a523328b26c4202074587d5530be328f87a8a3772664dc1390508f4f36351e05c1363f782850a3724952ee31cb93a55b17cb37672a3ebfcf553325166aa2f06054065140f3472e9be0896423acaa218f5aead8972860eb2d75d2a8f73abce14e263ef3da2eadab9d5f43c2817084bcaf286133b72597c976019cbc8b04b22bb358f9c886f0e19543261ec1fe188a17648cedc9a723ca6713296db52bb4e8b674c7bb15d1a93bdf2e9118dfda995fd06772ad3b7728d9897f77b6d006313ec4c2765c19ffc00189b9b057ac183bd9dc7c9df8a584513f8d0eb6d3af11b41809b37d2887563711f32df35582cf7484058c4b8e3b26adcb73adf45e6c09e0350bcca5aab3ac3783012d6fc4de4f9e7671b08c48d557121848aad9d171fe16a673fd5a74a269893e528822e9e8a69abb9a1ce256b9572992a61fa19772f189eb47638fe44ad9a212074cbc8271c869c0afc1afce94f04d61b827bd42bb85b2922cc2c41556549a201f19bfe4e7ea3b0b361912a10a237546d043afe17195234cdaadb99ea8563e0d65b4962137ddf1c674c9cc8d6ed2243642cbbc09ce64ed281bc0b773be51f5fb329aaa7ad5d64d7016d0bb937a118bdf004f0bfe0f84031cccdb6677444ada393e058fd9ef9b96fd431ecdb14970a6c0bedc202bd648db652ee8526ab2683cd2bf055542dfc6c3ebd7c7c3d4aa85a2cc42c5d12cea522812b4cb214f9c360ac8bcc01bcb29476a214a76c4ffd8572be1d3591287e884e93b58e5ccea4f7a8eeb71325fd58229bc22f4a48f5bd6c72703189fce7b373ab21819d7b8015b6bbb9cbd11361d54c3d1a139ba7a31186720e2555e56fd92fdd05ff5ffd23ba8766c06aa2293dc8e1d29c0f25cc8c19845b66a7e2e2fe6c4bbe818bfcbccd54366f94a6d590e7ea25461640e4ab75726372863776f51aebbe5fbc59238d7242b139dd4d0a61f70b5b3b1b48e5d3c17a82722329317cef7e390ecdb98348787009b2a8e503604d88719473c12f56d0395d72d4b8d3923724a30f33847681c990f37fc57cd0697ca507b87ab7e92ebb2e56725e95b9585b4c3be613365d696db208328de0f059b800d1dc80f630bec635d572f063af39d9b12579bd99330d13574630136d04bca9eb232cb1c0812fe14af47217727ce89ee862ba1e96bf4a7b7340ba3bd29cd1b0dffce41de424113b30c142d9f4847e753cb2177a9c1c60bb5e0940efa155645c0ad4b8ad4f00ed475119721f69c74689eba797f0372bf0bba2ab49d49ee551544a5590aa7be0de1e4473722e180219c32fe6ec368706511a368412e0efda05509b1f011d0a5d79b9b3261432527a717881f188a40e0e8ab3925bf20536965caacfbe5b4b588979deb7e265a6ff4dee0338bd675c0e06fe90b3b9ea02ff75db235bd708271b4ade08863e188d1e0a845c45b422d03edb84c63fe2e9464dbc2d28c6b42e9dcccaaf9b27f61321757986afc866e401b625807b1d259bdf40ab04375da61280ff281eb1bad638eb91876d6de5a13384c05a6fed9aa10836b6aa9a6407ef95d3155fe455b6ae0881b8bac15127662d98000f1df55151de1da94ea4650ade1b6c7705f10c5baa72d6b0993cb7117de8426fe75c7a911ef4d261ba281604d87d52a859e1f97dfb206f7b436e53c1c9150e35acfbdb3c3d214572dcf2e71d8b6cb54ca35c05e8be18e408cb4895c6f334372b58a974fa14d543ed45fe8e49101c47ce2db287e5026355adc20f63d99b79e969ccc972f00ec6a5def32fb8cefb4fcf224d484b9808729c450501f28d89f9b1ae000f8fee126941a8cc2b52135fe23d1d1cb804f878728a304357cac22372d5e907bbfd2d1d903fc9dd42e8ff6768f81193fae1e35c729ec5393eba45ed8aa9293764e84682d94ccc440cf0f0e381d1f601a41ad4792295d1709f2eafad9091966133cdcdb3675e52984e9d6d93ec81c809769adfb16a6fc363016e059e7ec3c5f3df7bf1a2d6ba8d8554fa5c6ddcad43dbf007648d728b5b4877b622b1949ac583ccfdbc0367a72b8d2d2db4fa54e582b3824ca69d72b6ded338bea6a46a5e81db8f174c6eab0532a4437db188b68d82c3fa7f2deb72efbff614bf95fb2f97daba830c4f9d25d7fd9e7db0e5cd2392ad281c7d717d6b91ed5cfd9c227ed5b1c11f9347f7a59759f00f5707765a611775249840c7f7726616f7a46b38dd8fa9a4d9dfc769537a9f142f8bce676ef2e40fe36fb2d86a079b4d9d62e895e16883c5c11c333c3a56d77ec8c8be6921890a37cbfdd60df072547c12147bd56f5d09fe08ad26c0c222c5e8b8f1a8133a5725c7637dcf7d18095804ac607fccfe7a1e5a0d411f0a8b4b4c425c7fb01ab141cedc0bd4ca87ad7231a2eab0eef961f91051f736341d3d32c52d046e5276652a8492451dbc988772c88d2b8aac19bd6d8dd1eee889c10326f6fe5404f35febe484f0aeaecc3b120b96391e230f05f0cb3d106ec4eae939ff787fc1644a84419d61c509a0c2eee1727abb77b72adc565aae598b4ad9f07aa9004e6a6515ab493653637679a4584f0c0df2d6661677863246abad7a98a2fecc917b79e7a91a867799f64a4f766378419c7968809b5b86fa462227320bc9daa6346fced54af738fa5433f54f42e7c232d79e9cf2a20aafdc1d692fb9ddb669deb3096c8da111e7f04e09beb0f5481c72d8c4f13f0cb24fd7365a4215b18c1d47e37e7f37c24ef483531b66fd5f1f1272fec023245563ecec64a2df6c50f3609043c1bb154fea05aeb9de41c61a759334e3e78030599c0bb4a1691bfcce36530aa9613a5dd964ee98e1eb026fd721eb728e9bf78c49fd7c6a037767d614166f845845ddd9d2b3d59aac1ce97c35bd8772beb5263c371530d2b60f0c3f809d5c011cb6a31a9d8432bf527b09c72fbadd724b1d215bbec356ef465230f676ba656116759994ab75ca9f9f6d626b942a5672edb6c5d1f5310ca770bb72d01768a1d3240b727ad686491795a345db0a1c0372fb1c32e3c5eb5f4e46f44a429b288343845688e642e0e46c2148ddd9edcbbe72c2daea6eecdda8a2934ddec9e85b52c469f425b5edea34f53d137ed98d84d27228eac4621e28326234cb3e58ae97ed817f5ba6aad7c006bdc8162a6986d3af72d9c9b798b15ae46d4aff3491b1eba9034d3a64ce22c4db4e27d2cf6589422a7200c7cafda1cf9417a53a98b18d8a4f5b6f1eb8b71197869f46dc45fa6dfb5b722507cbc4ff16da75846693f9f797c26929a1f178d44c40c5bd3845ae5e4f4c72c64d0c0baf8335af2bdb390497d6063d013f24fe3f6ab5e9d4ff97d18587eb3e72072313153a6490baacf7474b3cc7c833f735c1739e3d69ea4181a484ff71267bc897bc4c638a867ef230176c9d0b760e4f6f99b6dc9dfe972744aabd0484016ce916212881fe010417f5c21cd121e3eccc0f3d0c008718a7538de6b3c1f672dd43375bfb818f0672a60dfb370b4585bc8d2d2db59e807169c158f4bf7ca3728ffa68426206c7ad6bec9c9ee9ed2f065cfd36d5482a92a45346f231e0a0e172d4ea379c3c8fdd9d66a751e1a873bfd6dca37ee29a920f4403b0a41e7b719f722d813b4cbf7676bd60b44bce5edc2d7d1c0bb54f27a0b98319a4b56003895472ed4bf79c55e76a20b6a2052e36de689bb2a2ebc1f6e0b8b5c84dbe052d276272823d08671c22cf5246761236cab09111c993e9d222fe42eaa047ffd51d48d87280192695add5ae6921d914e8e11277ee88b1afff3f524b6c78ccfe9720676860dab1a3d6b1eb240112fc7ca047146bdee23c8d062f8f87aea0be44575edd7f72dc2b37c1530fec52657076d7d26ac0ad362bbc13d45e7b3b501760a420524d72b990f6d4103aebfd88add930e3265eaa959998c56ccaa50d5da63e0481149e72bbc9ea1c075659f0861489770d60ba176a9cccb8e7ba79e35a75ed1dd231c1727e121bd3884a24cf7f0edc207592e038f6ec4101e2c33463b013592ed5269559dbd5b650b4517a74092aa787ccdadaeed712ffce58785bdb4dafc25d33c4c1468ab819d08209986be4e5b76be44e6caf6bea2018d740a81df338914e52cc9572130107d43702f4558a66bafe53b8071fbbde4fc80671b5c0c1c247757db9477220f9601c1126a7c063ff14ff964c21564aa5ace5aec8f847a9d008b65a2e7c3a3d7e2dd22f766619ba1088e0e87188c637bc1b1dc6670f8f7d67efc003d77e726723f6209da559f2e30a24869a015188d2c4b3c8de392239145f3b98c4d201725f7fe876d7f055198f7292bda75a4d7d4d7b4eb0a0ffec7f307532f2b3b99d4a94949f5435b97c01aa4805774581fa8d1ceb6c2cfb7337f00aa2304dc28e4b729aaf6ce0c9c59a76ea59c72898c4cf37c9f19697e3b0919f010a0ef45639e87242ac4035b9991ad280b1d48c7e82945de8edd593ef627c45d509e8fe7898ba72a0489e1c3f96210a86939c0b5bd38cfe0dd3f7bad30981b2d9fc747e9b8e02727998bc07df260b6b3387c06427a8849409f5f6c5d97eadcb0923d0a8c97d037285be66fea51e5bf95c6113fc9a8084538151e8f8cf037c33d0d8f1155e9a0e72f13420da8ad168ea3b21c617c43860a72ea596fb1070aafd74bc88be062550725fd3794a773bc23d049080c7b33c65b4f0ab68444f768fda569f8c3a49a1d955e1292ccc8608093cc1b11cb55fe4f2d6c6b4d967b78847c573ade3a286379672fd72e144712b2653fe91d740b2c99e9cddf35c68dcf2ffdd5950fe17540ce97217c7a633375ae560c42427cf835a474524a68f573713c02a2068fdd20d4c45724d4d1d85ebb71e41b1bfaf9e975f86b8c0ef0052b7e517a3cbf03e6f295ee409719b762aeaafcb9c756f8e926634242ca711824b704515cff5e2ca8a1eb4431cb3d9fd0667d632b169504b9cd5d1f6401a79597891b604b0d8c14615790b9172ae850d4ec6a39cbff02a01ec9945bbb6d252a4a62e6e2729fb1b4971e511fc72510607300ebead400751722b57ee722c7ffc3f8b1d9952126b079325871f1152f443b36d78093a4616beae2fd8a2a3e206ce5f5f5e9cbaad53f01e3b677bd90bbf8d1ca8baabaca1c5f1d91f4ff74d0df3242f2c90699b8ad54809860816a472c999145a928dba1d39c1c3d6380cc850d5414faec312280bc9a7475105366a00e1db6ecef4a94436d51caca15af0e128c6d6674f6f11af75bbbecc1cf2096a0ab9ca89569ed5f7075b80221f1ef14631a90ab2e3a909789906baa8b7e1a89116f16f2f6e9c34462aadcb19e7fe9cf8d0520e5d0bfab18c9200375ac9acfec97243b8e4fc5ea350f6a53b9abcbaebcd47e6af5c71cedd62a7c364e1fd2f298b721fae381d912355a9561a6e0a231885a47549b428eff5496ac44347ae27395914327e8f010698ec1330458f8a99fa9d0379539c4e686370b5afb62179ea4ea872d4fc25651f9b6330f11d999a2e66b0cca628390b890d946616a6723d2c865e724665d6eaf8da0c6537a8f8d0b43837e4ff6521c07e8a8566dde3dd85de392a7271cd8b4d5d97667a0b7d8708827931f891f6d06c91d0a011efe35ee91a55e81441cfca444820eae0120c325d830c3f825074c5e9fc1d67cf36b084c6b3c0f372d16172e0b274f4ba64879263ace0e29dd0a81af17ffa59387b663fab075ff337373a5c9b8f49ce67bcad17dd82c549f2818c36ca9167069d135aac1dc4684d6e2a06a8960ada0174efd4ce87fa8daaad0e29a8c8b391cd8a7b18bb4c1fa17272d9a3ea668c56f13816eaece3edbb1d5d11d41700bdefde443ac79fab9775d072d14db3b3a059ad235d4b5064a2154ad8eed7aff1cae5874677495a4381064d729737c2bb521758bb7df392abee045abda4f1635ba5a41545a3a4d11d28a753002af074f78e0dbb6587e268ea5b79099a9c141704aa6d0640b8ad00f8af3f7a726420021230d483694a83339a30e697582f77154d42074c2877c85dfe2086ea72753d183c18e620f3919ac3291d024f449c10ef8645a2a6cc0adff56783d861723f833749c3dd46588b2c9d9557f56ab0271d122df7858c2246a7d9f849f08d72585eb30627a19d548bdbf68a39edf0f959ff3ca01398c0debc7138902b74074163263e5cbe285557927c2f0c9e8529f9d35670f49158cfe6527d7fb2cc143a724ddd7dfcb83aa2c132150c5e4d5893f3c21da6d12cec14aa00f8dca3d197d872361ce66bb0f8004388d1a00670dabc85bebfcb95afc50dbdeeb60ec295cff209b92052cd1315b1addf742b4e87fd8a1c9ef6b673a6c560bbef0dbf304517712ebb8a12f222e7050c3495eef03cc7f338383bef998c0f8b841675763d33904872036d6a9b557ce5f0670cb66aa67c542ac4c0648a5a255e9aa346203e75e8fa72d83054b4a4b0d618f2ab804bcbd9dddc265081e40319cd0c0760f6399390357260789f0e496be21000f2c9de1bcab9ee1118b4132c9b1ffe7ea84f40ac4e9f72d34e5c5cc709507929f46f73aebf3468a75460f9047562e8c8c55259b0f9cb14dddc492d76e931a8fe4bcade27e77ec4bb00bbd31f617d5820351ab4b7c8cb7263c7a583692cdc166213961d370618183bb6095bcb15930dcbd5e8d8ab86f9721cb2f2f474611e7dbc432cbfc5d07f75d2187ce08511a94a12c981ce17b3de4115cd6b359207d55368c599f4f38ed20add6abf8e4c6de110d77c799a773bb972cf8ed3c033a616083cd5390101ba801c77d5c09cba5822f34f2b8dcde58f683ccf3572e2abf47fe254d0ba2daf0716a280705855219d940f33f71ed953052618534cab6166ac1d152a92f73fa1649675ac7ab26beba94bf590ae3db75d54d1109c552fa9422abb4b12e40ff1bdddbc36f34b6e857057d8a2378339b0824efa604821e91b24a6b772d61521ae65ac3b11f6def48b4173f7c9d034e6d1b339463b4eeab512bc6cd277b3760b9d3b4b04d90c2f6ca02e9a7c4f4bef7b746cfd9b3570218803ddaf49ab7a042c84b22376d0de96b4bc0772f08a95ec8469641d9545011b3031ffaa80454580afd657f2393b5607f2e91c750977307d15778ca7f072701196e07ad638e63fec43f2fcc8fd6efedf04cc2987497f075fcc6980852872bb2848475caddfe86ae4f69c6d993ac7c0f0cbb6f2626570ff8f2aea6582fd72d59eb5de7ae8a5cb25867a2bcefa70fbaaf7ac72f13a8e7fb6e70027cd854c3defbee21ea12fac7d31d0d7c83a99567a4f7e720045460ddc8d1b041a7aacdc72446acac54aab246df85c49b31337c1c58f223fdd8c6447fd2050c3770b015172ea10bb9c7e1d8cd106768567255ebbfd2df6589415dc5ae9a75464b39e36be6404792673eec8d1776b69ba25c211ef3b18512ee7b3b3b40ba830312137663b565672c348d9039532ad87ebfa5b7c7829c38e0bb41edf8de048594d07b386337258f97ff50444ee69d15acb01ba9aff4d865eaff7b196674ed24e766e64efa87201112ec6aea4c9490fdec15c4c0035582f15694e182a050bddc402089f08ad4480bef917dbbd3b72206cd0dd650f03a2f9b872668877d17c8963e29235ed53725f2f6769222786dde93731ff0c08c3dc681f673f982d320da0e4aeba3619847296c1c7837827908f5bb30773778699b21f6bc19a1f6ecbdf93073d0546298872db5812341757573d97743f42bc28fa47eb5147838293db7a0241f487f81fe572fd732b3b53a2727bb751cf8402a7e210b08a59118067d688004d83f94d578b7221a0e0c7ede0f58981abbac7006847686048611fe6aba3d103eb8a5acba50711d538dec54bb989fd7879c3b007dae823288e4ea16dad24840496a1db8d618072312d210d789c79fb9d2c8412d85af2549508c9e72bcf5b98e7998dbfb9ae11041ccd63aaeaa5e0ea93099ebdebaab22f820a56f44668151258332cb6588115723709ce12090de4a9890aebee8f2f40cb9e9bc72eb7a2124dc1abcde312ad0c1071fe9a43b0a93aa6ae3de04f4d9ec8ac3a3ceeae5a44f3d085ba51c5bdd19c6361a88783454c9d91284760315012089448c06d8655996aed6f130f5b0ff541727fe591c4f3c01f9cfe7146ce3550359d45dcbbac5f3902862e35577127dedf720750207fe21d87428c88f248fe54ce2269b424c2375fe3b2b8cef256459a3572d165f360f1c961f78dffaedb84214d1a4db579b8fc2360d06343a91a3568ea6c037699916c55038bfc8b7b81c66ef204ffefb3f06327239aa118090fc3a46a00475ad1b592c4a13b5ac6dc7ca0926401f1eda76157396ceece99ac2cbafb4a44ee753d87d528b1f9f44c4f7822874c0b9315daf147f659fa72219e3a21626272b82aad908a4c5a9071fcb1548831e2bbc7f79cdc174939cd9132e8c3df02c2727ad30d574e590b6be6a277e80f106b5546285e56609a70b4b165db8bfb9e8e6aff85dd097fc23c0a230f2985299bbdb455d363fc23248c00e264f177331c04725f4babaf99a80153c3b204455b9e50d005605206f163f984ebda027b4bec8c0bdb61628baf59eb201ab8c3b86f697444b4d5018d212d53a27e074ee9e68e9c42bca9edd6dd02bed624e8b8ec8b5686c8dabf3545ef36fd0ee09d9c8b44be16729a067bb1591f7bc9435277006e1dc6a97ceb668c257655483cc02e04cb0fa67224be7177a5b2b001bf69bb90c9b1f95630101ebffdd23eaca7be2be4da906672956f9a45f40b4680d43f1d152e9139066be928c85b75b9a0a695ac3e8f162872b630d30df1e6230bede6be7ab98a597d552f9134a83c723dfc36e6d0822f1228519471f66716dd8426c7251514e199914a3d36942af2e92a20111cb6d9ad333ceb967c27b52f6b7b915dfed9734f472c460a57cfc8489196d9a43384f99f4272f416d430f55965c9b447c859f077c02694132fd23f306a70751f2f2d0a1f13728b482eee44818c58be5ae95bba2595e37e5554bba8f47fc2e851ff33acbfcb723a91d386448a3ba9dd0346942b5258a46cbec177c6c807069901208f0c720972592df380556ae6c5735cc9202ba03f83d0305df5ddc4ca562dc082324abcd84ee0e908f27d18ea984cdf1f30a9d9ad606e213f37eea86f0f0c8109ff2be60e720d74bcfa1ea1dd43cbb139f7393db651a0c3e9e369ae4580ec89148cda33c842017f2e3376c2efe4d7f8e980970cccf2900c98112fd3faed5349277b394e76729e2439b9353156ad95b5cb4f5e73a1b6a91b0a896e7fa838538200a62ace0872eebeda7791b47a4f6cd59d2e36c58543094494f5a3a03a9c7587edb4a47c7a1c1bbbda3493b8e35016af7aaf5c9ba39fe5f194c66dad50aeb7902a3594d02472385fce7dfc717383f7b468160fd755706300a559d64e7500858e4ac6718bba4258f4c7ed64d8abecb4e9d71746c3be1e3df5df2793c93f496c7602b8fdb13617c390708309acc2c1fedeeaef31e644761f4f4683cff81e53bef549ee7a270272c5f592f162ff0f475dcf10ad0401b3ec8bd8a950883063f4b738bd40cc4f8b1784c3eccc61d15174fdc0da56232a64097d1657a5f3304acb1cfd23642f106b72222431a852bee7ee68d2758400389583a90946176d416a1e3ec8252426e45872411b50283b35dc67727c40da412d56e9a4a5f0d45ee65e7f77fb6b10583f665d59dae6c0b831cb76353068324158bf7a1ac9469e6702f8999da987775dbbe57202e58e36a6315ac61b922686c5cdc69c056bd2412726ea46e2cce04ee55a5a72ca6ac7aa543dde78e11009567cb3cac9e828794bde5cb4740b798b7ce60e4572aa392d5a538be51011b6ff655144f422a429833989b6a91eeed482b8ac0968533bf3549f52e2e8e6e61b873eaf8163e79bde3d3f8f969fc1896f29a0ca73fe1cb81ebe78b7f2379138c5b4819cd0097c76f6dee9511cc99c9633d007574c5972d2359f3b3577d0cd65faaf2a11f50079314b07a8af9da66f259a085d06eefd1bc7705063dd9e7ffcc2f2175a7407c32768d79999a774f3bf6402493e1def8b67685385d3a5424ce97ec9712a61600003b08947d091f2cb0ec2f9e76eb0f20e441f1a1558ff6b459e1bbdfa090b3747ea74b8344726984401899ec8aec2baf446f853468eaf7bfc4a15cfe9bc93e72c0e2c45df88cf0d63a974d95cb020c55f3c5a966f06608de049deada592252fe6086160f4764a27a850c2555e1f5c07a47259ed1621f328aea205275becadaef96ed52ca8746ff7ec0ffd3dde77266f1a20cb134752abd09256ee58d13577f9c1d9ce364b3d47b643997adeee07ca7262723d35c8356f75b49c66406bd833e7bb0174af94658ba0cc3b3894974838e1b972bd395be01872b4734d99e762680122a6fda8ed85c568de7db508cd7662667c72b3ee44c7f5503b350c67b615e22f0818d7323c15052871d6003f0bc65994023400a5be8c5f57d692da3a9cc6fc9ad26a78ff334bb7fa92f36395cba1fdd04c2ca02bf12e568b65dda5ee6f08f547b82205b51f22b15ddd964790dc207fe2fe7235f2d52f85fd91c18ac1612c3905a553b3ca58f216df2f8bf67092bc930df005bac5d22ea1f10be924ee682808a133ef0e7269538c5e0e1dd3dc11d5172cee723da9172eca17bd27ee8e4b05848e3385f804a751d4c4e8fbea8e0ed8c87f31420bece25818ab086854fe950c80e2f50d8db06b54cb769070696055df10946772b838ffe3217fe1da6067641c215884ca9847037fb1082c2266be46e922bf470d9a6ee3fa443a65b991daf4093aef871baa74b44fc4d674b36029bcbb76f9de7270748e44eb4afcfc0ba0f1d30e8e521810527b7bd61d0c55d2ab4ea6862df372114a5cf3a4d420b8218a6c6d6c95164a5286dbec17cd423c744c2bd5272a66296c1f796612a127250e81a3c549b6135548b0687ae7b9a8f1a1a472ff4e809451db895ca1db0b423b8304af80617c05ae0ac6cd13804c99b4bf9c674b98b8ec3e3ff15bfe7dbcebcf7c045aa81058cca0c6ecca00f4bf2798e574cfced9ad2912bf383a628eb5ee96b85c05303cb2782983c5911bfaf27e2db116a74f5413a53e9737daf610d64c85e9a39b2289125084bd70d70becdf09c0e13f68d2a0a5b5721aeeadb80dd00824cfd9113332a25b5a3c023477b7e1a607cf0c25800ffd0972088a3125803413175002b04e8d52e858ef4cc1db3f7e2a05cc93acf15af46b554eb945aa9d7ccb232f6502e6d10ec832f4c0fc6bd8c12b2fca91ae81b8493572a6c5235354ab45fe07cf794173c701feaad47f18a4e1b8527023a9877040f0729299abe91abaac06f289b35e406286f5e1638f3e550edc768d6e3558feca114e20ee4317c2a2ad0a4b600c06d65c19904fd505dbe649470a7ed5832d58a2df7214f76058373c9db19169f0f1041d0c3b1152adeae094038ae96dbe8de330030906156be08847d507b031949ee7895e334f8f9ff17e2561c5c49a998a1b77aa5df467092ce70e57916ec52da6ede95bbd99c17395ef402d9b6693c603c2166327254f047277b517f33ecbb8c5daccd4bf977e094616b8f6afea104832b22046518cf5d0b47f201240ce42ec92abf294eb7390418aeaa838ed858e7821c1029272121eca72f3b1bc70359082ad51e9d10ce9b0fb11f893b36416bcc977057fa530d1ae54b827e799f3e7dcac97c753b54c3a7b60327600d12fb29ad645fa80b2720a01db3fe951200d747877b8d9466025a7993cd03a82d8595ab7ee8c679f457231c415c3aed0b848e48704a811f7f7877d06968ce5136daedcc4dc0cd841ea25fc7170af4aa00aba7b7a0bca86940228ae9c66cca0ff74d69816dfa5e1d7b6726fb3c90488a1687af9b811c23aec4c7c83b156928bac098ac1b9994d1ee85a728934a40b1dbdec61422e5e13662f5b4c554163ae0a9b39d03a50892abaa92d72119ddb111ff2033f6ac04f8bf251116cdbe101e0798c6f4e1d3a074f3de33b72af0c47bd51d12df890c745a25bbe76723e5b0a950bacd1a8cfcae78d48626672717bf0f712fdcb0ebb922a79a643739780f038feccbaf43455fb7602b297ce5a7589bff112c35b4e4b8d15d9b4566d1ec1fbf2f178678a43f7b05f0e5bb26872ab3a7a3d9eedfba8d3ca00a198a3556f0b2febe192b33d3bfa338b21456ca77257dc55fa596dfaad0d27310f39de6a93950ec9b14b003324955556e280123572996ebc47de3b87f43c3e74fc14fe34697b523337a4ed686679f1bc7925f3537267f5a75b9abe1ad8f3836b2be21938d8e35a9f53a873a6cbe0ab50a6ea65a1725e5f44f6d63cff2b18db719a006582704f63556e077357b566a533f1d30b1d72bed07821ff4c2ef74d9fe460e0885fbeaba5e96e4fe26b9565cd58bb60712e6b8e95bc03492154ca5234d6214b3551f7302806822579c5f46032637a7a2707720ed67a38b172a9d98477d57ff636d936668ed542a75919f496611c647b929b7275795f8590573bad4325317c86769cc57a8cfdb9a2de31bde735b3aab0557d72db6a17438fc428aff1308312e4cc5361f8c50c200ff84ac0bc8c4bf1e3808b72e0a140324bb4c2284419370f6159a9e899855efde6eb02210a9e8fd998f1f5368e9a57b99343f1eac79af7e0fc1bfa1cad663093eea76c917181ec17c6a6412df46bfe01d60fb2b737be9ffd7f8f5ce4ce6703283233970855b22cab2bbade72decb3558a605c574927f2f766fb8e58e0a1e661c4e0146a0a395498884367d72009cfbfeb426fbbfb0ff8ba7bd3747bcdb6008745092256ccb689674e48a15580217a9e1496bbce6bb6049b94c04fb34919a24bfbcdd338738e9684497cd2672d71e0ebf387e3443fd45271d7844402a24f83ed7982b95aa761e536dcaff56726df89be05c1f776073fdb73a969259d4f87171eba77a4852e13ed32da8bdcb4f1dfdefb5d68d42e570e6c947ba26df688827bd3489b499b8147296fa3a66660016bf765f24d6f0eb41d9bb911e80426626c6820d7b77366bc8e9c112369372722712d8b695d0a7348c8584ba05304739c88defc54f7149a6693dcb3c1f988d727f7f9d5dec4af239fdb51744390e59e010a2ad434006017e15f2096edd7d93406d72da91c1aa6d134acde577f31e9a6da4111bf6f0aeddb0ec82c378c0193313aaca726a2e7b0fa4766280805cff2120eca318116018d7d3173fb93f9730d072d1c41cd44ef845e6581dde8358469da4ae7e20c07c282861f11db2b61947c809fd3fb346190fe6d1ea2e6538b4eea03c612476a87f2ff9ee93e91c1ca3a0107210f6bd755fe352de0354144fe9c93094720957c2f1682760d93e45f11556442707478feea6c6a8be3d3411b0cd9402c6764695ac11a1ff1d3e10ce5518e4c02e5258a88046b709009c066aeedc611ab43afc5608921a823deafb1a5bba5ac3081c0ae6f8f8e15e31852ac6ea72986e7993accf4478f6909197c8017b2a12db7270f37244f5f083e33aa1da3c51789c1ccba895940b9a50cc602c14cb027d9a29a7d498c2f3dfaf605a994b540070b015fa201142f44e311aeb6ca3c2d8537a51bb4934e2541296d908d23191e2281f1a3c80cdd1339d24d3faaf65003594af72f651c25e9067b0e729996cc36f9018d05c3be25c70853c70acb83501b008397268302f85aec92a5d76ce4e30042e9dce874c9a7a6e76084aa2487b4f8bd28572018c0cc3fada08b538c548eb2107021ba3fbec5dbad645a661f20f3b490b924e79195e951111379c797d7d8f4865bf0a92f23859ea17aee207990f221a8cf61eb191091a09282b10bc8d69de1cb02c462690e1ea43254ba5aef3208ae91f8d72d71de8d1013fc9c9422f64159bf773959b160e74ab655ec01a0d9737be6cfe5d5496907cb3e30d5b1cfd8d002ceea7866353cca862385ed5fbb24488212cf7727b70ffff4c74b10afed852e9e063d73f2295859acce5ed69cffdd4e9ddbe0372639b75e65cbb19a58284d8f56ae0d40716dfded7331737a5af4d49812aa454722c6a9cba7bac595c57228855e548b41fc2bd12902d5eea828402bd39f4279524fd3c57317b4e7fb4bbd983c259e6266745064a0bfa4a0853a70646bc37f1f8720b3f1a8a590c75d163ee91c43359f4f206bb279d5b63f3cc793cd742b83bbc23afd83cd3e66d1dfe05e22bd038a5a8ddc0a45dd868ab39f4a479736d36bc0772b2e6bc0a1d388279eb440f01e991b3a30e6db19302aefb989c32b4c62beebc721962d4c6c2263351c9985b37689497cf916c46866640123f1cc08c35604f70724ae9ab0e958783e34ff0b59b3d5f9400997b2c5048d6e4dfff0d918f37d4dc4bfb2f4dc5de8a0b5975f66cff1ed26b8b34f336aaab2df2f5bfd6dfe0cf8e427252032f7e99ea26a40058cf5347593c3daafea30e031436dbb120d5bf280f91722e13bd99cdf723b4ecb92c7de6d73255c2ef5a6fdbfa7524a90416dcbe59dc6b4c86a4a5b08b6dc5172eb48fcf51e7cbb6f03a977917e8ae67b021045da8565e36b5e2ef1e76312f072ae7d6c05e894188e2778cc6b51f38df669d9fc0feb26285b38b1857a7264889ca27207d8c427e5ac47e14d62528e96c820902b09751723f0664d9e523d8dce1fbc7bb1f025495bbf61fb295769a16fb83b10ef511555bf9bd6d80a5a43381412a2b0f41b2924b5f2d6adad0d04026c48b37016a5a93720893c7846274acc7e05f2764b6b2f66b926ba094299de928b67fb7d14b22b872f81b24ecdd4f6f61d69c0c07c4e73ee72e922a182c8af72707cb2d8175d60272738bbc48624becc47e8ec48e1c4a43dbbc00b7ab4192bd792292156be6d86372269704833a20b4874b8213a4fd92db41435369e59d38c92f26262d388d90f672ac2da325e88fb4cd2a4664556017bf97d20c58a5066fd8ab82ce57bed8441e72ba6a72bf3a0ec5fd68c5afea59fb7cfcbd62faa316b89e9f7dbc8064a3270e574f9f0163b7fb9ee5032fc7c10c31cb4c649cd634841cb179d491ca5d49ba260d094f50a2e0c8f0901905213a7e4f0cca49fea1e39688fe43c56fa9339b9f1472b4b08453f15b225842d22eb5e98c2f86c25801ef73026e4a5eb23edfda9046727137dc6c146a1db16714ff86e1db8a3023ff10c8a408efd1c1b59d5b31b5542a9809c68f069ff37ff6d153c14a61532eadb140614584e58359915771b7351620251b7e8bb25f569c424b6390f9577d06bf896b400158af0a05a611040622ed72f18acf1642988dfafb657c1808c7c3a32f617349f002c653538c47f1ed250c2c63d854ef2d31d8212bfde2a72bc2aa9b3a89be80403674ba79e8eecf0158ce2618aac95b28f8616bd0c70c6d9660ae832e2f2d73314b7bd115c07a5f40987967421af2387a96ccecadcc234b5b855d7743d778b1eb3ceda353cf9f3b8854d472dcce417341a08b96d129512adb77b42cd977a9dabca2f0707577fc69db72017225987a53c98c62950946efefe4de5127c8919df26d9a9339fa633398aa2960721eb6be1a399858dd5af36f3dc1d7f0a4789d9664b703135c6af2937e64ec21727d06395f579402256d4d577f8a426363984c3f6d720836a091d276d0e1b86f720cb4865fd072cc2bd5e1947d9068bf83cec0f3497a293a33dffc6197e85d8972f9e07f74fd6ed4d2dcd201a39772d2af52a64f3b30ce77aee64e5b3e4cd49572f07d3dd44c6f6a1fa88fa65867e3399cb56a7b743b6d2249e6d1a08a97279e6463782f3d0ad43e970564a5b4545f98874f8678b84dc6850d6009aca40825fe4a56f1e9a451cd6b9602b150df25c40771b7424e311d0a112c9fffb8ffdcbe022884a973568d50ef4aaaef4a9244dba6a4f6d4ccf08e1b850f0014d322938e1f726e868a398b10fc3812c6e38ccebd954af7bc6a173680be05d5df05b2adefd472e04d3c9ff765a7bc7e5ac78eb350b60988b6e5c96910b2f69c5c7f39d1c0d42d20b5eb3feaea2bb9e508f6bd29457f27fecf6f63e5cbcad9e143b2d70059750b11d73c810e4d395007fe8974fe1b658dac47be3e9bad80183929dd734b0d1333be2bd40ca7ccc69443cd0a173b59ec47d5938682e199d41e2991ee5f5626f572ad1c03c5193f25b616cedbe825011f9a58c9e5ff3f29f64de10475347f7441648439883995398ebb0c9087661bfe7c160bfd126797a5700264fe5fe8d259fe72c8b81c58277d77e02af39d69a48dcd1349bb8cfb905f79de59d48fc7f21db0727f60b92e2bde9afc1a8ae740d4174c176d2a21cc8f27446994a8de5a47b8b44c483d4ccd9f27d4e342d4b19044edc2b6bb0a4f24b1db111412bbaa1180eaf6722aef873125155b618f17f809c7af889797850d4546995356ab483b1dbc73e137a9e3a46239ca44f264cde3b8ec5fa6cddcbc0021900697b1643ea4b7d8ff69725f84a29ee8852e94da753ad081a1c2131477addabdcde5131e070dc27ec2f92a9bcf772c6cb6823d55ea9a791f1fb9b86667031fc876c278c1a9863144ab493178a540d7559bdd36ac16619e871380857e14f9ee4e68e113e10c7786fa3cbd72b0f097326413c4a12905ffae7ea5a787ea997fda5c54bceba965e7bba5bbaf5136531f66026214d8d5083b8df41c7ace14ad4df2fc2b96b8426825f4cc099d5adf6057e90c1d8468baa27540fb2575e2d1d79596bc51be86ccdbe15ccd9eb82d3d5d665617d00909389a6af781168cb46b1c67e093078a64b486a0186667684f1c6f209a4768dfbbf586c1b93b06e51362abba729f7311b3e5f38aac27b35f0d0aec5b38b32a306085f047d97ee7749ffde9a85388b229fc394fe3f38817e1574a9db3fa73e5ba94defd9483d239dc8ec4d8ded7784669afe52c87b6fb5a8f72a59084fe011d030553d0a0e848e60855c3a96afb910d0ad80eb8354b421ae821dad93babc77feda936c87d4522b721370b487ae91304de70815081221141de72c1b11f7849d70ff04703ae69b8cfbd47ec5abf0e7205bf33d6eadb9065aeac6904eac3a299154ed917f078c49692222188fdcf3b8c98c142d2216b0a3fd4b46b7bc046f838ad745d083f52eca42431e78b38510aa437d1e9ed49ad2c2cae0921e5346ec0c1ed5cf8dbc3b60acd34811b4b8911b2c6a11d92e6957dfcfa8ce8018c6c255fa3efa44f8d0bc9cf1117696b8b730208b7dc24d4e8a2d4b9a56733729c07708c1d0248ad53d4e5b46c0ea6649245fc2a3102dfbccfb9aa2b2bc079728272d6108604df8842367c8df87c787b80f3ea33803203bb89337e48ef6b6c72dc681944d91500ad6065b55a402e41ecb981166619cbe3160179fa2d88fda072f014cce7efb4d7fa8332f58c5540a203559c4311a492a116644bf1d071afe96cd5937d49063c9f41897b5a31a7e320e2e1deab2c9df2bf254004add19ddf6d36612596d5e943a11cdd843762dc8844006f2ce72637fbc0cb652776befe52027276b7df30e0067820c6032aa556a47c1d823739030c42108738a9807cfa31bd4a335a9961cff96c10cc9840d6d14ebeaf7b4988639d20b03d0118684a2fde5c4dfaf782d0f55aa629e1ec9dc534c462ce1e008d1aa112a35fd02c5b79c7632a6ff224f81468c4bf69140e9f6dcb96323fe5418dbd04f436d218857c16d8a3823797e2657d0a926c73feed489d49f46e33c4e09728d1c7dcbb7e0a9e10f913b96115285aa4b500d2ba3d31fcbe7f8c808eb97b68e878868d0edfe4aa5ca9fb0e72ac330256a75ba30021ad3ce2e4a39146e83e5f85cc84673d00406c17d865a5612ec262cb80d2e78e2563faf9202dcbac40a6087a4f07af5e690b86dbe3d52837d467324bb67780c7ce1e82b6e7d049724c171ff7f88903ba8df327135482310abba085532664e602ae746f12d1bc77c03f4cde74e0ff09e887bb28138c2cd3166ee5fba7f0ffba33f149dfd8ae031ea322b8eccf8ab56a62c577c112f10881724833a9d09d4c274011ae99895511dd39122ca3aa1d0d3fed9b433f3aa92282722e135f67cbbe134d51a0d5457c252d40ae503295d55f6b6bcc4d08163470c672dd7f9649c7b26c4b4e007bd641ed3262382f193b1390765f847145c222d92072023b5a04fa842d35bc04bf5c5c7dc705027af83b1eedcd5a827b6dc9fdd44872a065bb0eab9a07317da0cfbf47ea7c7c1f9d6bf5049673f9f90aee75936c86728e09582a8e4fb39d9f894998e78987c2a585707724d0ec0cb39bb02ff2fa9d72d93e6bd90d7996d9e9bc6632608e9a492a5b18504702bddffe12d688f869cd658e0edb4da6b39d72eef973d2aeefe7b5fa97163c05c1337c199d9a26c194b13d66878dcbf1efd978c83a6c09295d4ca006bbff815342becc640f32f51e77622228ebe5a65d05910039c446c64523f696c5f3f016707de6ca4ac230ca2b9b6b51f3a47d76afac844d8354918ea8d31fe08afc7bf8df3e31c5c51c38885ae716088f7fb14489b65edf8be648c6eb07d6ac75d00339fd50d013fefb0ab5b345e70d0d08172f10ef74d5a44507d775e998d5162c37adcbe333cc71f9d73966ff8472135b98b40e6da0c5d6d9fdd22f91e91041218aa6572150dbce2fffcc63ebb35e677030ea550358a126b4df450f119447ea81441e4101c29f48bbfaf6e1f8382a94714ef7eb023f73bab61e68f528b0812e9f8566606ef7c7e0dfe314eae49272cb1466a047626a7bfd3ba7a9d5c78f44aa84e3385fb6777256b8619d2fea0604878026f4fa515f8d59f89cb3181f423bafff769c5a89fc07f43468aeff5ea1729e507186c4182b8d8e1b08ea01233088afacb1203ccb813ee27df457c95f8772d16977080b43e48f7c35fbb307ac674d2f8d5a20baeee214d2e6e6a8df43bd72cc456cef98dc22ebf7ca71a4a92ff4f20c4f74a65d258fb6a2645d7a1d7f7472db28ab1b810df1d00e8114a41e59f180c7ad50941e4d66cb3560ecb3825baf700fbe236e698354cc2e0f6a9075a4d486e7b9db4285aae22453292d508b1f0b72b6af3e78793faee5fef1d24f4a2215cce7081b0f8634a83eb38874e7c4891c3053da0e23aabe4809dd82cd318afb7b5d26eeb5a266cddddd31a5b51b9e61707207fc0f57653b4888ee3e80142e3bc52be9653d8f757d2ed18a2fad2a2a659b727a06854b25970633ab6193c38062d2885c2f4158a5a0f2e831c1c793d1baa63ee15032e5f8ec6598003cc8867ee49c39d308e0013072294e0df5fb7ffaa3c2720d8140c61b7ff993e262f666b60e9881c2c33fd25601ffb543c563f87a789647338b76f7b84a4fff0f711ad51010a079647f9a4a090cc08ea00510619b172f72397c22b062b97a56aa6a9cd90bad4cfcf14504d8ccf101d4e7f96c310bdba97253b77bca8a7454e1b546b0617859b479b99088c1b1ed4c94e377c27827d287729da7a0273ba0a0c01dedf8904c7deb3b9c00fe73512018a3b505f8fd4e6a71159e6fba66da99976de27059fdce8cfe07ea353e3f2cffd9a090ca0bca32d2e90d642221e347daec6552bbe900be8d5fc465ed4a84ad9b7125231e476186760b722fa873a72df661ccb58b193dcf5ba9590bab46fef74ef246f4f06d6d57c776721bba73a2126ee35e2602165ec27f734afb2235f44987ec92b6212ffd5434392e428ff457c2dab85d34e2c50bcbe8c79602c607f56dfea853fd67da701990172343a88ddee5f78f1aa7506be031cc14bd45065f5b3f08de7a76e6d8f7da0ae7727131e993a48493e0d586248d5a9fdf51b5fa38ecca19390d605ece962508f87274df91d4b3dd00b75f5a6bf5258d62d6be72c88efcba7839eeb4f31ba2bb766afe5e2cbfcc2939dd922cd0ed1764f6b0f6568083755dfde17aae03a0ae21567284601b63be12415840ff3260ccfd6609f7562b31d4edc892306be42208a146713035cae07b95904646b71e2970384c685c529025fe6dcc37bac0e1c9fc9b8d3627043802245440a0c7d95edcfd56c4e12bc6a165ffce88cdd383f1a14e2127720d07a81e68c3cf2bc86f301990cb45667fd343934c1b327baa298f477efd58149a7e1e9b77ef5141570bb1d76c91119ab8769572c29f360e4e68c79e11a79843aa3f495150c661b4c2e0a8f2319d97b688dc5bafd2e2730dcf9aacced5cf6d43973647ae82397a6f093bd8551a7713bed0cc45ecfacd38ab95987521733fe37239f99916fd3722f6f25801df99118550cc83d33ad742246a9b9c5bb79b471572a62c1702a97ad504ae6232d97a5f06b9924f14dfa306117a57fde35069d64e724777f99105a993cb4fd83a0b724e0a67618108d9d70e119e96e3d1b159c77e62846f16f802fa5b839d97b81f6689c6da388b7fdcf796c8fd3ae510e2cf65567273ffd2c520797c3bf5496902585a86cb316b839537ccb9ed594f769cb067a472dd35385eee2753cc73e1d0299d230054f66b26f3217f09473ff67a4e2819d14075befa9f48971fa7b9b412f42bec255be7803c43c94aa9b5b0e4e5b1c62578402d12b9008dacdbe5eb04b1f58b37383cf9523212da674980ef1be5b3281b8e727bed8787fa7c92afbc4434e34e9c631262d5464f427786d8a9127e4c13c944414c6b6dd7968ecce5bf252f78a493a05a9caacf162dd781300674a7094419e42470a0e95871ee867d0f7423bd6aa6da5149324c45da6825b5036ca0e327c36e3720b42ee21c094d739566c8085f96d5869e34fda6cb493d142ea2259f25b9a672a4405298358e9d12b2aded9a85392162e7ecf9470359c965c673c9ac2e68c872e4e12117d7239d9a7f0788aff91f3c99b2893dce1df996f11ddf6983b7c474724d41b72ecdfe679d9b0326384bb8aa6ed96b78a15d0901c30ae5e35a50cbd1726a7162e9a9632cf1bd74cd13459d16027e1a497ce8baf2309c26f6da570cf372c10a702beeeac753c9632f0b510f8f012afed48f86d67a31a123485c2252cd727fdb2a6b25dc0ccba9ed7c05b820455254c5f21a6f7a05cffd0cbd640174223d893efd8f169d4f11d541b60c0cab537091294cb3b436479e9d6dba7ba3cd782c9f1aa237d0626d49a017a09dc631b26540f43e7de6f6e415bad24764fdab7a3949356d2e9261b6c20a5cac02272a0ca7b57dfb5451deb08b125389de8381334f9689e64f28c9a6c09d30c817782aadb83039549dcd8c05d3767ff6f6b3683a699ba5c4b4e55432e7481efa3649f37ed110f40c9f8f3497a6b8b25bdcbdda252564adbc3d26159453ff7de600786c02418688820b253b01422d33c3d4e74456687fd8c4b41ff88ab4e4fb617e6a0b3d3c5ea852d30abf9821b9a11b2d1672404dd93a113c7fb60f2075603aac5cc50fe92a3fc276af8bbd873ba52273936bf350fc6f962dba15cd4dc8607a6b292be17f94efe09af5ddcc02477856cea28c087259e5e353a0c7fe9147eaa43e24e344f3d3233e5ff6ca8c66f3a8d5adb5edcc72ebc6a857781281f80ae391ac25f44e5a1c2649ec931a689b398d2c98a67db97282596f1f653bfad62140e868b0d1de24ddba93922156ce356ede79f573103b724c2868939a1bd7936ebf4edb8957870e05aa7db4a635e61bf609b7a76901e3726b8e716290874e4c0779b9626f506dcb526d9a3b5852d322c215f04dcc68b76e7928e015d800f42ebbd417e087558e73323e1ced6895ff9cdd2cc2063c3d4772944e69cbc5cfa9fd87827d72efb20f045257d36a7ab8a15025c08a8b1e29c5721cea6af02eacfd565814699f259cd93adac97d27e076b5fd819c4cff243f8d72fc0c94db30f38b98c234f967d0bdb3d96f979ef8d257e322f3b3fe7e5c23547235f63074b938c4a76aca5fcae8397cad1dc5d57e79b7e3b8da4735c71e05a3ba0200005bc67471c189d78c76461dcab6141a733bdab3799d1d69e0c419119c92e82b3d64d62a960fc7c9b461ac9a6ecd756762ab69f456a0e93346a14353dd640f1a82722a84c29c02ad112232c6b2637f4944ee928797ef062289786fe3cac841405a38bc59c7cdf3eff6d165191108da2b166dc1df72280021a9e535298d01fb4de972e782f08149be4315399372535fbd88a11ca85a472552373764e3e7f1a33c097263e4d88e069a3022878873945ae3cdd0edb546f5a543fe09f0dbb9c57bf20b6ca5358d824bfd91ccd312157a4b45366e6a1a5fcb9f5790493e61d1eea165437213652c8015cb8c3befef422fc62d7bc1208dd5d4f6ad80ab989fc695d8c92772289fb8fcb8a91fac615a97aaa0456b4d0d634eb464f9965bdbbd6e75e16ed172cf4edbfcf96828cb6c2773efa292c0b3824b0f4f1bad68f955d7d698e0371c1461cc0c2c0bf62a4d40662e9369c675ca8758ce4056d531ad3a19883633907d40ea6be6ff67c340b9cded3b90b3e000f74a7781fd8977e535d5ac03220e2726720e21b587689de6d2d1652c92b4ae9a350bcda1190107549b7a76c2dcf3aed1460781eca6d8d46336fd954eb9947f033fc98e1c36f52a318854b0a0d2c9d8ab7277ce88b3035f0f489541b288abad878a286806c206b483a86844d2a6eccc6172d41760876e6444032d271a85978c5b543354c0db55307522d1a51fb16e2fbe72a3f397b6f5850fe314c103861632bb552500963cf57b19863e0096f769144a3f0f45834037ee8e7a002ca121ba330b959929925557ed69b7bb5d9942257d5a725db1c61ebda009b724e24aa47d103e662d985ca23af6100ca2e9713850ba33074c881d2e746203f9285dde424b49a3e59fbd85b6960d4c064c82f2f0dd55d572424dc708b6c81c63c08b93354a03d9b2335eb13831e0d5d7316e74dfb23233729353d4e6e34ad438dca1b30aea6980e9ed62a7a1c26b3fa0ca9e3d6ee79d227251219b87f5de06cf7662927da31e3b4173e0e677bcfbcf6a23d6799c97001b723435436eebaee068b33fbeff12c9c0184700641a9e7adf498d7a2802f67369725f49cd56e36f7607347615771f285a63c348117979bd914be79a3283799d24352aaf84e3c1f36e7f5c4afde81a2d624b7a2fe6901ee7a3ac8fac9e128c36a472a3d6158a3280e5083f43ec8ee4ca3911bcd5c7b7506f92322420970dd274ef72e224b84353b681e7bba0b312988a14a0c978f3a69c9db0dad784bc59619cf47225a8bd8b6a8a5d2b372fef5e0b6810b33734addb0969dae7c633d9eae66cdf72d959c9eeca1daa76788cac643df1da6a41056d0c27de1d37d0686356a8894e7253bf8c572023cc5d91a00aa69dc628f8ac327dfd66c563e12dbb39ef848d2672668c9f1cd6a3835fa3887a1e089ef37f587a70c0f9a25b228febaad70483e168d7a1a52fbf292a9668ae7fa6bada1715a0267f37310c04a4750daff0e18ada3aa891832ead6735fa542d34bfd48c9a91658b56d98a451d8028f75a125d98900153987c01589c420be2bf03326baa42d681ebe9dd3c7c56137da231998d97c56c57372991240b4b250bc587e18b4854e3d9b9c65d5670cb18304cad5dee3454594a5e78cb58a9966a4dd54ad6539373c9671398d0b3dc3fb01505c0d97f8fc268d5f00f1eedff35acffc29183c98c7117a0fa03b042c94fd989f4017439341072237976d427f8c6be4e39b7cfd16eaf9258ccfc73475fa84f18d0a4f29dfea92f3f45abe9ac29bfb6817b85d0f1a04eee323dd74d3105995f18a596d5da293d26c23dc46d1f743df38195412776c81dfde14f285a47e11d97242ba74d6447633e3d4cc38773e0154b26e40fc4eb099efb4c51faf6f37841bc47f496b0f7e68c724bb724052f3f60fa8a058c1d774fe648d1a5ab3c48b562abf71f0adaa81e5f7276411d0649cf11c749246f300f5bacd682e03b7176617fd5cc6633432c3bc84e9f5168293590854cfc3fab7c15b7c80823eb35e8bd26a5555b82fece5778dc1efe6c70939b2764d98dd619d9eb7539b9d09dbdbd723807ff8a7d623f57fbd95dc5bb55effe40f25418b62041087370b704a28bc5925cafec66a192f479175b69f05c4749e34ae11b19f87e31ee11cc9988985ccc354aad779010d457436914002e039873cef4a51c51b6610475cad07df2b60325fe8330910e10a9ad6b1b1c726e83aa620e7f35aa88bb4827f969747ba70c8f6e2f5d4f0577e33660af02e9724adee50d8c9fac818a2e3f9113f84b9046c50f132e0eac0dc843a746f6bb717206ccb04a84c54acd5c374b7d46a24a5ba74aaf5f1fca6ddc798a77af6534fd72ac8d85b0463f1a74d2bbb56ebd5db87ee0b254f0149420aa6641c9f4337cb072d7d196204d85c3d646994f5d4614d82a41f38643df539a9fef2c7cb9f822be4a3e592ba97f4671b23b7ff61254aca94ac4b33987b7cad20d615ca2c00a3e8f72be3066e33d04c21465bf3710c05ecc8053fe3396f0c285ed790c4bc5bb5c1511808553833611fc132966690e531e0596634ae8e1168e36cf67e6694088cb9350276e9c99fb521567e4c770bc51a453cdb15b3f4503869f2fa50572e9054c13728da83c9080d9db5f07f10440b9a1c5fd770884b87f0648f64581118220e9e772abcd9409c2e75c0c28ac043815b1a815fcbe8370b36fbb025bc0931ba0244d7252c75ecfa2d9af8108cf0d22c73bc6af53f7a108da592f930f8625f5b39f2c724a80967d434374a9ee3e9333a602fde7fd0bd1b6348fdb2f57afb2b26ff01764aab921c73c42f42035b3fe951e7259f7245754d2b0c36fe9c3a8210676da3172b61c3972aed5e5998d5e607857f84fa06ff4ea87b34f64c968e92547e2faec7217637bda28604cf3cb629b22701fd2a8007bee585ee8bb1068831fb22d4a9372a26bad1a93f0acb1a84ee989db0971f77dec11352276f58d202d33e91c7083721273332acb0e9732b520986caa7feac7a324d67e6df9c3b42a241912662b9e190d04289088b47bfe98f2b2d13c8c0fac63ae58d25848a008a5dd046992bf923bd4bbedd729da66323a6c6a9d778a01149bf604852064b7c0642ed965c6488e5543c245accf0c7ea17d0ef0b76db6b4a6514196da28a323d0c36a3d8badf7dd592d3ba6da57482a855e8aacc5ef79b4b30b3b2361f7de3f64e95fd73117851f325b2264821f36c7ab637ea715c70d19185d93a5f6923287337dcd76c45143c73d6141e28e7742066631e1f9be42681358adf378a7aa3290d3ade470b2999d996ebef57bb1ae1d738dddd5e3383227c03b3ab8e11ea3b1337a39fa6ffb1e53010cd297fa8bf983c889e67a4668fb297b2d6dbfcb5a603f70e406ebc31c92c7d05605e789ed01264afe84044e7bc0fcc758cce12f4afcac467200ec53112d308272b1d212f20bdafd9af016a0d0070e426977e90be1bda13f8c365fdf875aa6ce3711985ffb4365add45c10ea15a927dcfecf41fb3c2a5f9388f588217c0520c033e4b1ece2a5f2fce1e6f63511ba47500141185eaa59aaeced276582372203625d41bd8f11c56c7813e88012ecbf7f7299b79f2be3532afa4c3b4981a7e499d32dbc4998ac817b2bce0937f7f112daeb36b5221d22bcc4f3914c05dba846a5aa7204573a5bc7ba9091eacb77b088fd814fe6bb95be37bfa2cfeb576cb0cb87596011622037d29924992392571e50af5622fa423937316c2adbdb16c231848cd772d3a42b946fd7ae873c19730c387c96857e7da24fb2be55d4d334227a91ccc16d03a981206a896b0de5ef7d36678ea49ec98487520223b7264f1f25edaceb8f7252757e3cc56fd0b8a72a430bd1fa7597115f2d59a0d2c5399e072ab3b43a7a21c9cb0b232de0bcf51eb0dd80909666d9fdac771c1f2befee20c775bffafed372838f5d57798a249170382b995160b162303f8b74c29580c8da56f898b6825e72909dffc9312b2d36bfce8b883f2812ea8f799e45f9fd5c9eb208f04913c0747212f5e53cf3fea9d9426a10112311c44371ed24c69481584b06a719d69ec1f8720c91d24df93e00a6b078e71c69673a227cbe2974809876a0383b36f866a6ab658117dd32e0d6633847229567a509f4ce454dcb872b50dee9569bf17029de81721b7c539db6d61495ad0f6b20a6769e97c410b3bcf10752a15a357ece77a7b8724bbf937c31f5dfc0edc5f8d5f5a380f12925a1850ba1249ba294fc245a9bf4728ef00f4109ba361c6fbe240c79a39ddea647d9d26b47ec58dcc1ee04a8364a724b8046151a0034d240bc4135c2a1b4559d6326f5a1511c8c6d383e7b8ca444727711725cdf14c0e676caadbee4ef71415162fe5a1d3728c0976ae510085fe32d0c526b1a0a57b1d213e5dca528b3a11819badc877347a3b3fa078fe1956b9a721e13cee41be344a7b8d809de7a9515a0057fa25b4dc1c6b421b5fdd8b384ef72509d1311891006a040f3c4ea227d2eeccb3e801d81474e8ded959defc2a5ba3572a78ab57dd8af3889e20d8a5bc76396d87ecee9c6697b032c9816bf34ab097272de4e210eef8318a8f37b555ed447bae0a8b67bfd9cdde5acef9edc686ebb72ab81caf0f654669929fb9ad7ab9609eae1b8a245defe9c50a8b418b195b0e072dd87cc901d3dfc8b92f494e47bab144b06c2edeb2b9b55507c0bb2c8159c4b1d2b20369654ae40b9c9fd0dc6b2fc6e6fce74f69e2aa708807604bba8fd60ea72770ec260e9aa42636792028f466bee44b7fb367a53e2bc875fa621a34d63ca723a31d7f7d5ea4d5ffe8f1b61507bcf8820007093b14dfd3236d749ccde2a367027b055573f975a47f14940daccc1bbcac1a42484c291ce3466cdeaf0178db4722c55344ae4bc898098db5001461bd0514e8d3768dc95ec0d611477b950180f36963fb0b9986ac8372150c9ac7d942fc74c1059db513529b54fb91efcd5d5280289bd80aba26d99d641717c05f0da7755b48f876235259393e7218304955a0d7268861e1947c11b7e346c1c114df0bcad108bbdd3f31a2d2d473e7547a9fc71726fc325341823d3aedb497d3df013df0cf52fc872292a281b543534153e11085769787a91c008d3994abbec9a3288d3b90256e6697ee78ad16e3c632762c7c066364ce03013d35ada30355aae9624dbe36fc11fdb2dcf5bcbb838b6169ab82572e292931e0ef209df15bd8834e6b7a985faeefb2e258d4b1eba23b4a6370f5d09dd7960ce0586bb4bf117eaf0a34c7dddd3e0c120ce333d9e91ceaead07418672eaf77baee6870ba185f0250414676c17882cc1832a4a1e6e72249d82f05a0672819ca81e801d43f6786f4b00496da3ce64f0282177b592309575cb6f7e57893a547baaabee8c922cb912b3a8ad808a7fa9ec46c9968b36b7810f0f10d8a79c72456e6d3343f0bcbac16b7ae841fcf445e1efd6f4f9356d8c654c80f712bdd3641876f8d85022f0b708d8325a530c0aca4bbe2f2e683f8b0428a0092a33ced572df7700a9d458160f99ec22df4861c7432358b3b074449d5e8046c23967e44032a99b9b8724fd86f468eb4cc335d9288e172ea3fbcf16b5a96328ed1c226de072a6e0b18367ce3479b4a0ddd31b56a6c646b4a20b9d4245b1f72ead74cc4bb472b9ea474899ffcdf946f394d2b1cd5595a3534749f5e9459b0badbd0d12a0527221d426b77b603f1c9143e7291a90ec8e0387061834b8714f911d5abbbf66903fbc713593cc2f6e643e1757a20e15627cef79f7a32a8866f233804813df8fb672a18b40639e4d1630d674ce3fd7ab8045182f399065ec6ecb4a45e330bb90f372c1f3287247c810701b3fc3ce6558b703ce02f0843b9cc0f463c74156a6e3a0722088cdf1cfb79fb3c1c460ef1633cdd086cf517627bf4142b90486d9901fbd258dc25ec3b68cd971ead287c2e8dde560f168f99e951d5e86b3228b92320531728100d227704104ff4940b8f11bf7971a9b16be2894979eb7ff96076188f467722655ba50b1194fd5536d66d039582f1b504fbd0cc82c27fee9d3e089c65cf872c4de2d0507b75ccf4e6446a1081343c8404e743d30eeeed65f3972d741ba60723b631f3f0c4f528ea764fe334aeb93cea407fe502003b8c7d30d83d3dba7483cdf368c98ec193d44cd73eb7a85712b8f1ba212c5e9988caaa23bd4234ac3b8722880d2d2ea35f1916786d8cfd30d37f15cbc3b802cd8053e3cb2f7efaf51c172427000792e2138e2d2db86e9f1937f85f99cb4a8d3a1b80b934c54e796e7d4721256299993d423256dd1da030e8fd848b2c9443daf17ef94e0eeca209a5449463866426543f0e0ce91deea9b4f0eb1c96a0b082f5674084b4b64271329f02f138e6895a591abb17bf0fb919786c282c5ed1509f8cbd4874a37bf75ac4ad1732021871935d81a7fc143e3c442c7709d36d9cc98a404a0ced45e2e9ad09b921f722009ab5975de1007ec0b845b4de9d444741a967b4d88afe3eb5db12e7701634a4a50b8dd07ac60885326382d9a712b57ae256a39fc2dca8a6e1bbf21261f017249ac61ffa8044b5d95d382a37b70ac1fac7f11d68ce8c23e12b995570bf2812248add75598ae1d82ba7347c6bd2b61b989e7267c8718072b12cfebb8e0221f72700cc49c3e7d43a998b5e553d005d53e45aba5f2980fd975b173b8fda5012872fd939b8af4e2c7e8852ed33c4dbf8a219433ac9a65a513b6b3ed7635f316d71428eb0456a3b6d39f7dc740723062bd128e5a8871e4006b73123f314513ce0e72baf31857b7bf87a0b9f156ee35ef85a71a0c1ede57928cf36035df40eeb01c0751cfe29d1e20ea29653030b0c574a1a2f5e99524642d60f1ebf1839e073be272cf09591a7f02d3d2c727f0139a197b3c4d926b29e2c9f2e3cf81b48573501972806db210bec4ec55fe4c58d9a8582eab5bde6de3f03ac9b4fa6780e149218172e0f899f89f46b8ef10350a8bd1d86877f55ba25e6fd0b744209c9fe1c73c9272af44c6b7720403ab9707e83e273e215a33260b95d75644c17cd6f1ed74e51872989da8be1979ef7d161ce4a641e2e643fba18ce8baee46d633e5e6ee1ede3c72a3ed91f54ccd5919fe1ef5a2d37303247fdd0fb58c11f63a0339fce55119c07294d7c9ebb53f05f5ccd3e6690dca868f317e96056316f23846808e54c7eb3c086c6a503be7f024b4c9c736a2aed2f4b2c20177e432b93dafd4e13e687eff593f39ddadfbc1dc6afad68ee074a83c16f28120267ebcd98b0b4338c3e1cd82de72dbeb59e7311b18a711850a1e7c797e9ae526580e11c99523a3e67dfe55087a7227afe0342c32ee045b03d2611d6b2e2ac8dddd13b0b42daba45d04011e358e3685e59eb5daacf3a5f12d9f3dbdf5813ab7deee0aca9b141a42b27249925c2b5c920068432b08799e46c87245a865a369529ac13f7e56c8e5ea7c60d238f2d448f77c79aa5ba2c3034bfa857da150b93aa4ccaf8354c883bad2db4a9520b89947a7843d8b521f9498ca5410fe00d1bd23a57743addceda6dbb27f1d0edb63f072a8b2b8832459c3d5c3409d12375e6f91fc66aa64a54d56f5f3a2db37402b42727c4c77e4fb99d9c645d434ebf97e33e79e2ad564061449e9cff8df31dd762f72b73828d192d7e67bb40768f0bf37db151b762fd7b54620022c7b6fb472eaac72853a20ce92b452e290c2deb4f55091eaf1e9f37732bc7d939f73effdd32bf16a6db171558c0c96fa79e9d99a8a8ef56bad4af37341fa6153a6740b8dc292e272c2d4df36e93d7574313a5f21c087377e8d6410d8a12b0e468b2ea7936fa1ff15c109ec05785338c0bd98206db9018d26d3c30649778fdb656c63884680bc481876cb156c27ff3b7de3f4783bb2b19bf271da778706060731f5382815dca187728255b25b52e8b063570244aa240e66bd70e18f5235cb8b216a19f34f89755d72552c4183d30547d2ccd96829c6149b49d057914130582b1bb83e78c8cfbbbe2d2838965b39ac5fa10820eb6a9ad00df6b4fbc9d88d169c6bb928bacd23d6aa725f79028c3dff6dadb8b294f2d5a0b22fdeea834e5bcb79737c417201db9310483c911405c7c679a8bff495579b4968e0e87a0e7dce8c849ecb24c584bcb80e72cf4b4d2b1020464dbe6aec7dc02fa5b281c86a7b3c146c3e9c7f51728677184c068bb01c95816b5ca4409aed3aeb0f9d309850dc2f7a7c5d44eef7ba1d44110ebbfa0e5796bcc49fd36a8be19fd90511494fd0b170cee2be5137b88431ab37206dc3d42da0eb753a31dc916e9323787b6dbdbe5027ac5e64a4ee8a6508840f29ead11a3d2dac20c2e5de6fa1063935cd048e5009a4834013fa337fcf5e9f3772d06d3c46c60ec2edb2501da8e41a3c6f157756975e7c5c9ca3d66afeda099922355539c9ea04a25ef06c91376534e065ddb7d1662b8cc79d96294e7e9f947e7213b5fe8ff91aaa69a0cf19b54dc127bc65a007588d6b6eb33ddc7c7cc4ef3472a4da3613d1cd31f691be2cf9629c4b9c1f0d93a80c165557f71dd668f3a2850b356d6c075a52a908df148f8f77bbf843a3fa750a47ce2244e85b72396851b85571e38b2d124da6bafceb27bf4785363d55fac0f8fce7a9286948371ce2996f721e8f6cff46d2fabeeca6e32552e9165955bc194df2549dfa897a9dac11a45072367dabddf0a7244ba26449de754169e908672f8d847197a11761c726c9a448272066a748f9287400d81dba4f0a2dd39dd88809c2ce36c76b5aaf7c9f8f3eb654aebf98af05da62fb68afb1323dcc73183b9bb3e54a2037ccf0b3058afba2a97200a9083f7be1ff3577b5a245fe907c5512e4e82e455eb386e8c38f0ef7445f57ee0bf81691643995fbae32a0a15c3594a283d371dd7d623769277c97b6a8b33570fa7043b6a81699d977421f47527d1a4abfd87d8ca98a24b37e4552526d2a721196ba4bcabaa774f822388d70e5870c754d570ac5d84cf344761b600c6c1c1dd7d251b2c26f9c3416e65253a972f02c13e3d18f77312ea4bd24eaa53ca09d72099c45873f337d34f6613d8768ad9e88ed2506f8e73d3801d54bfcc4d6bc7e723e62cf9e9c79f4746495cb32d0d43b1a3ba3257ff0de7ea4030dbcd8feee8772208485de4eb143cadad731d28e6838e48fa1448bfe818c3cd507b773279e6b247972795ef168bbedd14aa7de9a29d9825a8b92a17669a28ea9e454cb12a1e05fece459b97b763d472b770e9be5e8079c3369b47371da0268e135ad13f03cd572f9d070e7e6a6c74671effa201e38166b5db4cdd9d62d0cb9bb2a440c82bf0a63df380a0520327aa2ac053132817ef22c2d71894b84b964d3c95a3e7c2ed56c280043411837b89a50f746e622f7c3c1036ce0d51f06ad622446d4b00e74db99720ac15defedeb3587bea9625c435bd8492d47b85f0f2b984f2e4626ad7dc8c5723fab83210011614747b79499ba01fc1e31713730affc5ab9ffb625efba308c72d7033b301357c3755c40d55d0b420575649cfdead1ab6a6d026bf8320bddb072bfcc5f534cce710e3163531cc1d37073e2d9fc737b8b78af8d51ac97fefb9f65dd58b2e8a5be2c446bdf7d9e7b897624e33f3f9e44550f6025023958ed01e463d1c64dc885ff6415a4a18f07b1ebcb209004dfbbd13aac61111f11de258bd20b4ece0cd94e5669af961630131ce28b068b8c863a072c96d8dcd1e02076243172e67df2f7ce8b71e176e97cb00098adf59f34d11d28ffe5651a28149a94e34023914b8118d4e1be36acdc729bf51ce0f2d1a06c01e3fc9e96d216cc7cd2a484728a9961d2434d9e0cc563a0839a341ae8a2f20669b87d3d74b23b8cf33539747216d52eba83416e916d95325c62665f8285e699937fac6a759ffefa6b5b656d0cdb59e79d60447f2cd79926b38e6cca5117593bdd475995d8e5b9e127ebe81a724802e9c35b6ba2c21f8e9ea9a32374fc4360d6b422b3f8545501690d6a1b0e30ceecbabfe36e86934af6dce2b215fc3aa1d926bec02fb60efc6bf0ea972af54b4ba8ae80e36f09dc21ce963e06349e0431b911b4ffa65e1fa5278a42b9c1dc72611a74888ccbdba4056e032ed7ba0901e79bef1d52c15c7121242a0e7225f27243d0826eab2a7db7d3fcbff62ac970f2c4943f9d3ffc15d0649b68811732a340104f1a181545b7faab15099f75dd4d058ba316e06dc5b8c8aa58dfd078c9fc7223dabbbab828ead92df85fc91f50fbce842f357d87b4ce0b7ac0661942d41872596dd68b5247075585a602207083d0130732dca2139d4892d744b97826584c72c0579b73b289f44b1bdde0c232cc396abeb4e1be1af7f41cde49d9b43f0ac31ec4a5c4ff609db7b4ec9f19a2c33ee164ed5a0c7e6574ee89a1f4a15429ef496e951788c3a7649bde1c0c8648d5acd9dd42c919dd8952a929dd130288728c9d7254834f750f09c5d668209f25845071fa40f9fe8aee1425b2e11048916ffb8672a4725af78d722c3428a57f5cd9ece5d1b6a02bc8e5c7e659e6956736e4815e728089c101bb251891a0e19d34fdc59068ba876f48aa06221b404b1cf470e9417247b2d60d5e0807ac22873f2cf127afc260af31c0a868648f3a8a57bb8bfee4722cd437c7c5c4540f78de0610564b8538bd599dbd07ca291eb25f9a5340a6a016a8cd1b2c0f64eedfc25ebdc1f3765dc2845ea24a997e323e9c39e27a0b42b0722dcacee8e191e9d0a87222920348763861632a7273e8bc58248228211c3ec95db04d08ce28bdebacd674f4d6316c41c669e3bbb8e56e86c086e77cec3c315a6c4a65869e8faf659a55a59015b8a9dce250ba7cef569f718de0b236ccc4351f2cf737838091398080df8f5e8ccdf023e1d26de6bc1fc580cd45120d949abec94ef699cd14cbf54937795a37c8a4ea913ee1edcd7b7e35d9e5c16288dda475161e4d74adb98b2a5243031d6143383bd9f46336c3f1be251abc5d6eccd04adc6e0cba2dccac854e76fe668b8f072a8650f6f08f7f57d1b94cde27a580e54454760416eb0154975fc620302dfd466cc907c74287fb239c33b20d00d4c81bf6cc9e411cfd3922835611c364c7f007db206ebc411f763009e5c74e73d80c488fe0fa72a7890ddefd090a86e10290679d31694391f8182f2e2d5ca935777bf786f51e20bb97fce5213125b036760b2fd72e5b0f10593f1ffb76492c8bc61dac3081a5183c91550072918ebcb4c998dbecb8dd5c080dad11dd09e0d0df0c64961782f94b9d523e0707a0ab6ae3f86ba6d1979f45948849952360d788e60c738f32e6f372f7a6c78af66684a65757bd96f533b4d9bc2892774b21e82eea0779447be46870287d6374ac43351cf7cff83149d196906baef702dfe2c58d39881f183839387278d8fb63c2ab41bda1b7fca584c3bb9734fdad483596417112ae11d46869fb6071b1fb8f76756dfc8f5eeb0e26a6d7a624585981f0e4c0bd6ec7b4e413c63915ebe8e37fe0effc49be6f56ca8ea4a1cd91653e10233fb7920144e61a66b2ab72fbc63ae9789db7df0d2aceb18ae38dde8f21f43215618b9d9b976c9b9a4125724cdbdac5beda85f42e7436728c384e9192d40ef089dfe6e5181e9cedc344a6649f15c43a4ebff0d225346bb0faca4bfd01ed53b524e975ecc14cc95602f92c72e9b445cdec1c0d895c8b9621db282381d65f7b122f3a241224d104c0fe524a650fff47225efcbd7f9c28470cfce0a9b95ba99b2c34c2cd9c72d3753e1f4c1872152932f4ae1996b6e83bb0eaf5a7aa21ac9dc611e18d24c04a2412b944ae21728f35364217bf6c91e85b8f0ffc07e973b6bab8e547a4520d936ebd1f76e2c02aa09a78c2031ba9ca82943a31b68b2fa95138e5d777faeb9ec3c29a262985e93214611c6dd75b7ff9ca920aea052eaa6802bf2febb5333aaef5a85255d2cdcc5ecfdfce5569eabcb5e2d204ec173c09fed13f15d4dd484fd2d308c2c7cfa4e912d7f28a605f18de6dd72f7b0bb378e218c31486b6e100a4156e778645afe0b366d8a2f06d20b9a9f197fc1812fe211334fd0952f6d0740602a18d42e067f01d11532a1fe9512e6228ddbf261dba99276ec6996d01ba33be7626079d491379fd4858bf7987662d384e367e7dac270db8d04b718ea7d68480f5ecea812325dfea72adf89bbb6cf6d8e4a02417428a1a5e32256d0f8aaa4ef28b95ae7826acba0d4a48ddf6528f97da9456db9203948081d352729a65a8dc4d597750058fc9f2957262d8433342303622942b5fbb98ccf775f3fcf7c62a2a9f87c11f04ae6cd09b317abac63b0649887a211ba38aa7b3aa935b74d45a2a003148882dda9b45f653722fc563bcdb4c21c227d1b150b5901907a3d1cb290ecb8c64097e5c75d59ff07232ee502c81e71b51963ab49086c7ea971471ef8a54df4b761b9d7b09b5a6526adf9246e30c8fd0e2335d0ca8439308d00491987cc11731befd3d0ed79fd0b327a7335f61a72a39efe166b38aa605939f77ec4d15c28135a363256b172831cf728e6ab94ed5df6b5dd464261f83b1a8cd2df3f47a121693fc0407a1e72510c572f2c8f317fc68fa1e8d02ac435e3fad3c23dca62d9568e6d55169b042d2e5ab2ab0b652be9e241fcfb7f0bdf99e726a2c8b92104e38b4ccec619f187995f17d4cf9a8e2cdd544290cef1e52f77218943ffeaaec87b50b693ca5c9e450c22ce80fb479a35b308b19c35efe8a844d58c238551223a7897b7f6a5bcc720ac505194aed2bc26fb4c6449efd1503d60dad95d803b97063efe1093185cbf9c73748db72689688af3a24bb78a2c5c9739fe8aa1b9d44c70b0996ae693d9ced0213f4af3e0da01dfe10ea75a52cbce63cf666d511d1d20c4b2f50f4a9c14a17d302c3e67238efac6f6d4c33827da90295143fe31087ca491c7d8e7ce9c1cc4ba3852d557201746052ede889123a5583ab4bd416742214fdf94f13175db1c95435b5bbb3725f3a4e9a2d4d0d81aa770dce66397a6c897f69bb0d5fa57a9d417e3bf894a90e031f707158ad0e5b25808cbaca830d0ffdd36e5f55a31073b66d473211c3b572bd4dd113df648d47a9216c82cf5ee0d4b3f2fa977b46605449fd64edd3f05b0759694f78575f5bc848a8022edf4f48d319df16e13f7641fc88ff7155a624a172ed68275dc2d99e4243aef94081c210e44e40946d1ff569f18299c4b3263316589b9cee1631b8f4c267341b15764bc908459117ba081ee842491d5012858c3e7218dc2e855078e9185da5a7bebac5f7f2319b7ce07763029ef09e7312b15ddc72193ebd1957ed1f05a8c0e9e31bca3ef5df00c680c887d77df6915f9668d0b372481d6d0510b6ab273a03131b87ea53d1f8c282b98cc958b0f1ab1dbc4ed7f7726f7c80d71f61b013b226a8966023e3b25852a7d7b3477a8b7f3336ec993a8412753be4b8dd977243adf7d9c64dff7d115b0c4de23d22a392d34f365de56f31260b2d9c0226a690b9dbec5b6a3566db08a3d44e147850fe9e7794e2f71c76e472db4cc315db13e356482888a7d4a75e2ee47d373b52534f5b04b7cf541b3c5745c06ce57c74d666d424e83b66f432dd879f401052ff3f1de490df2b494ec2b87267fc7ab4af699d800080bc958894c4be69f0124292b43e2b77716e0125aacb7263e47dd58ba1e1148745e1bdca37e23df2745042aff13f0ae4547015edf9fa1c5491998e7db82d7ccc10d503a81e2e8c92bfd82f46ba0dbe9a3ba140606e32728ae39066d9073f5eeaef30938b10baa82f05c560c26faa8faae0f5a2b9181572a5fdbac9176cb2103065ba33c5021bed0df360a6944b622a5050c272574a0e728c60d123aae12a9d8989629c054a153cae82d66d192d8ab9dcb6e456561f3937a62a58fe6a0ec228543f98a6c0aed4f2c6cdbea5d889f5831cf87a0bbbe90f22d147237e4d5d427f5248e5f55b44ab4294ed5bbee5adaa9beaf0e15082d2d272d90f27574bc156f76435d078e46a047d7ab7f90996e9cf1728c0fc5338ecfc72815620aeabd24018dafa892098415ffbfec1fa499645c903291c6e662c08417230454be90da93365ec2d821d9bacd1af285fd3f97a5e19c5d32533d0c80341720868b07884893a0274298929e87291792893a7591b70de0f74e4db8e00a369075871a3525b685acc010e121a836d20f46b36622d37fe3d0b6b21b8db30a6de725df2851412ddd678440a97631ede81d056928277bd50eca7520bffcfb8301900043e7e8c87570fa473175bd7cbe64e8be1cfc37ec2de04983faac9ba67067a729d76005ec592d979d6ae01ea4f579772465a8574f57ed8f5179a04240308780cce2c63ca916b2ca01c8eb5d47b00f4f535e9edacf50d08d355236f9885c9a22f10b96da78b90136198bc0c099e0852f423ec70c5c2040617070ece7d8224f672c575faf1308e12426ed62dcb34d8447e774862cf1ea0c5bab620011b2790123df9c20d3f9a34cc98b0d0ae7ee6ff28d938f6bade38973f80ecb139c5c8e94e03ee736e9feaa1cc539b83ee426160a04dca41ead79e6317f200cbb873b1bb8d4e64518f28c280ca5cfca1787527cbe90578b0faf977f94249c9a528ff30b33c72316f2fccefd8d98f84c9123becd5e06f1d0f6f832e3e789f4925ccde0eac914b2084d89b9311e82661616ca63f7fb1363fefa1cf90fcbeabde18225d21b77a72c39c52f69c20077ddd6b5eaee5a0e5143c46c2ea2d48aaab76732632dd646f729c586a692c43f97223af80a21ba55f0b5ece60af824a6a42c1f23b51c1c8fd72f3ee11bfe41aa07bb6838380b1fd6ae100738e0bc404c548c983d3a09241917232f31e07d5bd227de9bccfec82966ed0f5c80c8ca348d3a164bac7fd967ab8724a6cbcf4070ea469eb50913286d8ecab45ea47545a2360303baf0a50f6fd6b72c238be18129868add8549d80de4c5b4af6ee2931e2c6fc5f7182a778b3fd0c091ee0df80ba5b4655f8864cd6838f2413762dbc015416cb627e4ca42b88b57f722dd10a4f04377faf74eeed94a816a5083dae2a771ed444d86c96a2a3905a2560d12f3b341c11753e1a345f0d47403785b6b8df5a3a65b6cf661528e13a20d472751e70b21309c9eeffccb7fbd7c4d743c0483ed84031eb6c02cc9cbe85d717447ff02355e5de05af8b2cf0fe7769c2e7bc556456ec611c900a8ce9ee42f8c450c40adabb94612a733a7e3b1db04b76ba88b969f4cf8f9cba8faae8cb7098fb4bb01f4741af73a5d51ff55e1aef355ca4e117fb37675a6388fbee873d05bbe302213ed40d9a481dc98d911cb05ebe45d25656f6a082cc281577b4511899d37c288d47f2b3b57145adfbe2002db5d7e5d55634ceb6936f8a8eda94549a37ca3e72afb8bb48880cab5e5a5de24a1a3b2ded80595250c19c1d5da19e9a719647b6720ed4d18e4a2094b8bb2d9cca07c80e1bb85a2ebba65d079229c46621ea19276bb3603ce2ce1772ea1ad8340c6a6f4f111d03ec25e1d6e8e907f88afcce913272debdfe344a6df13856fb2f3327db44a93d07cea3ca659508c9de4fedabf0c0723402ce32dec62986718f3f3c4055ed75256903393e392aa27bec5e7a212d322d1e71ae94ab620976b47dca652636be7863697a5c6869440069590f8efccaef6ded7e7dfa5db293935eb1891d6819866aff5efba4e105db27cf3c26c01d0c813f8eb1c89b6ec0da815ebda3d7db6ad04dc383f69915f446ea72912b8bb90e4b613a55d05ad9f637d4df3bc16a80b75f92c5cdb413e31912d2408e0c2ff9ca86017643805d1ce6d60355b0261c74c2983d6b68243b02a1031d713589bdb57c3972d774cbb3dc77dd88ce58093432437286333efd1b3e0007bf11b1ceb02bd05938678cd829002d51e1e73647af27aeaa53e7b404eb69d5bbbd647ef48a97c39b72a2adbb9691a59bba16221c05039d4a4fffb6102870ca1eaf5492ff2ba7736f2dec172f83fb52ad6e04270b8b7bafea796e82109a2489ad246ed16c360b1ad572465c4540f7388cb2075461be97f050d7e47ede032dc52e4e9dcaf43056db2a72f58f5ad64aef45d7c5483a4ebec56b5ddcd4d53097bb1bb6bbf46fd3519a0f6f868789efc5039c09ede626c26ad130cab9fe3c13bf60a0142fc3c87638390f62e34d23c5978b10289395de8ad08a1088f33667358de2ae5941c73a7552d7140aa1c19e0656f85ed45d2f3129563d3e956de01049c91921603e92fb5609439e6a0f3b908a151ddb9ebd4fb57f792e62be1cd31389f1f9bc42153aca111c3b4a72b7741bc9cafcc309d7dab8e2218d767fd48eca3cfb7fbc3039f71416c3837a129c3661adaa21c91707c961778b90200b00e9bd9325a4b51e6a30d9aa2e397c72ffa884d9b209d3489e90164239c014f5eff75766920d1fafd0e161639a36c2723be5a1563484cf733f0bf34f14c7bf5a5a425df6c16a1680067a014a7ed23b29a4b92bf4c1b0fe28fb14036d4640e652751c57013f4ed7f955229e3f8ee9f159b829865493d23946198859aebba9c7ffc56959771f0eb7ae45bc559ade0c5a72a1ed7c05db3a74957ca5e90398f37bde15f7764e75978b4bca5dbf2bd7c00472a36e915199d1369458e72bf92dbce77a5e1c2535082a9d57efd4e9351aa8bc72b8401120efaf520395a0ab0c3a20a2ce71d64fe02ffec849f00bb15f69f9a77286385e65dc50735e1894fddcf61b05346ab84398e205c2996462cfd0975f5b0c8df6b69fe8109138d65570345838ffb215a7bd52498197940aed2cfe62017b7227204836c1ef1830ec1c014c9d8016d75dd72b1d29d169aa3c429043c94c8a728dcf6511d591481b11a0401de68924480cd63a0f30f10b37ee35d6043af81769065ea555aff1f698cb6280afb6d418a8096abd232090b074d2e4f86e70f7c0193e66463f7fe2289860acc9baa6d3fb8fd79b0b37a0bf9c028c2a86e0f1e10b72cff228ab338fdfd76c41d1345397816bd2adfa43331414642d40a951acf476721eb20d53f67f07c2b670135f5dd39a40340e851c5a7f2fa8392b14ad77f27e05ef05a691b4fb7df5e18432a72ad2d6aef34ceffc63908ee2e429678388402572a557401aa4a072a087e90e20165f7fc3216da1d986a440c51bf30aabe87b3862183213ce287428c4d4e69d301535154e1f1dec5bde43e9497a930bcd788d2972ab2ef19de82b8ef587a2b98f4eaf9ecab7d8d4bfd56304ce1e736cc6ee19e20ad11aa06dca6ae0050de89ec2d32e5f4f2c7b762f5279adf759c95ac265ebb86d9dbea57c8808be4ddefbb086a09a80817beab841070d2a275dd0d5780fe2f4724ecd47763dd003ab6a0856002b02bc3c4f391a2ab5be99a4a65ee903169c3103b3769690b0b31b66e1c8a0c97de16a23745334496f6e10d18284c8b1be46ab7200246185d2e4201672f633039d84e942cb9d78a9d839b1a1cd3b675555472e726ffecdeaaec06acab6af3ea853a9a60147bd88d50cd920e92571fc3821d31b323125c036787b249d2f2ef03401dabb08e0144562bef5db50e1707bba11168072955bc2f9ac56c33ced9115951aa93c8eb66d44885efdd6331e38e9d4239be0725d360752d4de56c73a86bfcc649b3c700d516875d5dd06866d6c4100232dbc1c688418562ad7e4fa1978c2bda69d998b3679e19b508898276f6c2e98d8f97b42cf1181f2c31987e4e54839abb7746cf166a27dcf093d412e6eea3facedab1672ae83c870de0296a1a3e0a0c618af742813a3d914361e2fc77249f18b792fea72f95147c847064ea7ad1923b410904bcabaccab51e5c4f7378279903ca7ce2671fb26467d910bfb001ca88512f6ca9ff0ac4c96a83dc59d341a0080f04fa2df729d7091f721f30c463f1b795c55e0ad2f5cdfddebcecd59da975dc12bb792c772594f7915f0c1bee4ce3fe60c347fc2eeacd1c1bcb180de24808371e97a99ca1dd6812b6c334fcfdc530e8deb0f60bdf12a29ecb1e2d93c9313f5df1ae9a5d072fdbc3b81b15c4caec1d94d42fa381d02a27d4743d139cd221c4a34b969827655df10093d957d2292d1cafe72e80f874e50660a6b3aad8d632c7b54f7eb2492579d9a27e2d13ddfca0d0a834eafbe6bb401f130e6026c13fba21f21f1802389720a06e9cc638b158de06355347095806b14894d5432aa983edc20721320dbc367a700bb581ebb52e47a4d36f5165a225c3b532474b7ced915b996fcd0fdd09b0176c74cf740c755880d828cb4ef3224f99c38f02cadcb915e022f688347dff17297e1c93e0eb67930465958fceb892b1e711d74c9b178c08ba0b8da923b81a8720c4690b2357078105fb049bb77eca6bb231b9ca398a2a9bd619e2e8dfc4c30445945ee1a3d39e83dfa17bdf286ded8ac509d357dd646e1aa3b78a945083b6d72f2c478ac4e4ca6fdcf5e11a5d54769e9b20e3652e7b4dbf4b284f1a4c6a6404bc3f5248da336fe6d28413644fe01deec7e7df2ea8e61603cc64a963d0ecc157208a0bf1558806f71edc682f7483f29540a8dd343b886dda7239611acdda9be726cfe66a0b72acc87a068891efcfa7da87c61d6dd3190dfb31e268b51c3742e7214de69f757c1ccd6e037f096ab4751fc2391f6e309629c68901262414be16f72987755490b9d7386372b708b7a429db2124b72a0af3338d95c2e8de47effcf727dcd020746cba6e8084b24e22958912fdcee71c9de3717afe7b3da16e1bdbe72d4966bb0a12e5b362bfb9a1d22d94b4466bfa8b9c08e269ac3e4ee7fc0438a720e234a27f4bdf8c530bc566132c680e4d2d863a4bf0e97512425d710a5745e72c2c145d2dd2f6c96d82029dd482990ee82e72290909896f7e16cead747591064b360ecd6874aef82cd864e6a95970674f458f4d7532b7fa52db14bc1fab84770ac0f75a033184cde5c0bcb7d03161e669342b3e22747b4110242bac4220d02420a51b559aafabee54321e3f860a6479fb42169ab7e52099377c132224f35c60f755e0a518fc6417671d5bf403a0e7ced2c315a78bfe6344f1f6fea661568ec6ad2fa0542218e344042b5c75e8ea7c1ce6c33c6c35d6995a79d2917d6ee8e2972ab2d960b9b2ddd5531b99e05a8bfe03b5c12ad478f1d0eab9a12ab7f386db17228a97061383018c7a93cd425a8b49660f4b2b1d0b7f9dce66b7af7d4ce06ae0920bee736efa7867c3b14d4bbc857f083d738f86e627f90acc2090bb74bbd57353cd31850a27b41b2b39e4e5be95e2f00f26f7de8ac450446a7eb4512d22699063b8fa1e5d1728d06bf3eb71b09395589ee75403417ec89f78b247a6f9a1b96463c0168f5ed46f39759e3c22b1bd1613077991409278b740d020a33e09175c844cd41db50816fef4a8672d966002d50fb7479495db01e49d27fad131f69ac5072e3547c6988f8cd6c9e956cf2cf92015e891a64a3a8cc1592981ce209a7564972bd39923f7642c31168aed799ae7e9083ecae0bbbcbcc7da53cabd819e573597257dbc022a1c559ed62ac771bb49812861461db60a5bfc660b940b745d7d7d472cb6e64d64cd283ff20c5d7b3bf347d7a3749280038f5a6dd9ca74b7d624d2e4d36624b402351b70162251e738cdfcd6ea3693ead9be284e5403a1609b5fde74cb765aec3c8f9514eb53db509e4fc18e11a6dcfaf09170579c3b5268d99329d709c0b8aab276f8a355659900d1e002bb1b2e82d272c02d84e33e955a6c12746397be1b1c9b2f23bac877ca0651ba92eb0162c127f8653986af8efaf83f06fc724b52d136546269d55fed8912adb933f0f4375e621b42160165f929688fc310d6081b84577c8b2cfbc4ed2cd2337324b33df485a923cb09f706ca1bc1212761172a52c15e8a9ec8486558b4af8b23136b04dd6c00fe697dfd59a0b156b48b47f2b7db11728f01925e820ceba666cfb8f097dc994c851cb2f8c3e29769893138f5ce0555b4f7351a62957c4c627aabe986e52aa8079552739fbf96b7225375b6e64bc7d7d7b878caa9c777ad3414089d81369c91593923976f66aabcbdf2a11a072036ee0b726e0ef03fd14f1e3f260317142ca60cffce66964aa78434894f1ce7252afea105f0c3788d7c5711d2f75422717124f2913bf70c7ae9014b9397322170a5278ba3967c837f0897415dee63429660fcfbc1319b38813b19afd8abb6912f7083a732a7ab6744b78ce9ebaa0cd0e87edca511dbeb450568656f65a6a7f2dee80d454a2f301bca980d0ee968a835571668e9c5b5f1b02425a0fe182680e72689c6f917bfda17266c1178267df4e169c5c88e1fbc765578a7ba2b7a5e5c072c7e9c4c4c0636b9f6b381bbc74dacd68c62ce0a5c17f18fdf470962fdd537b7205a2633e33568a4084153cee14e5f938f17baeb18b790f05b36224eedc3fcf426339e1e44b79bde5d9a16c73b6d374542e760ac71cb57a5b959ac11bfb91ab02111e7eaa99c7b9d094d3475f9281844fbaa9580c2ead45108c63289c42745d5a65643a1ec695d4cdd27b2448872901a76e06af1a85f3b81a343b2e8309016372bb22d1c404b266529d2d7cb81c46a0fe8211cdf326111eeb870420e02409ef5fb68da17256506ef0072e1a3cfe1587b2dfbbd97289ce06800d20cf4ef1fc9f7232823a54076bc5fbca810745cdaf50993fc2d6a10d6fff947df7ccd9c3079228e5bea026a81d7a08ba5b0ab75034af9bcdd8fb6a1aae1c9ac561a8cf6f32787268c95c1a580e175b66bd2ebb6b84e9242a7da75a02a389c7116d5c2275d60114890c0392dd3c12a465df1618d783802a4b63a1ccb3ec33404318558545b4a366302ab5441f1444bb32d71895a08d1e5f39fb470dd95935a73a17395d97ce0e729d44694abdbd35a5f133ac5120dca9f5e39d8cbda1adabb1cbedac2222db5572d055beb8ce4a1f08d5f2346406f4d38c29bf7f75634ba5d414d7ebb913dd87724622269256b899c29330fe45ed3776fb06372ff979ced368f6b066a83cbb8225373b2f2b94c50ddac13ca01da9f933611baf3ed920402d2f812b6e91dc42b61288421c6da64049b78eb85587a741d1ca0de553c40e49fd5f5691533e1e12987232a5cbe42e4aaa37a5eb5c435ba2c9427823978755c86addd0663b127b7bf96816d18e85486b0463c7b3b43e965b9fb3521815d714d7eb434f8a7be39835a3722f72327698eb7957179591e6f676de27f831d214de9deba0cb83df2aeefbf472f54be029a2d5e38b1e7d8484d113773dca05c548b665bc4b2854dea59b558f72c94c4f3bd829360b52d270cc3821d981977bdb4d032335637a83842d5a664b51be9544ad1c5b3456fd88a4920de40fc84354c5b8ad96dd32d0285b5277dd09726057784ac241a2ef246a24662d2e5dd90d45e39bca856ca38fcb8075064a776905f044f1dc42c2c6dbfd3c0cfab0d1bf3a8ad3d7ac856c0aefed49e9bbf8c021b8ed5c147fae857f741ece99b690839b2fa418f1385421f456052a6007d91f7282f2b1ca69f0538f56c18f904c12c36d1d49fd07a3d3bc1183144c5c272ef86356e5d3bb5fec03692ea67c46ed800a58e67d535580a5214bfd124f509c12fb363c4181b2399e3db4971960ce321d6a0c0f7301639a4825e14afc628166ce37729e3a330d8aa14c1119d6eba786b2ebecfe592b3d3279e93e15a7788657fbca722680fba15e442033bab31f9ce40f9077dbfb9d7899f6c83be8861a187b71ad720e58a7d5c26e3272571f681d4ea5a360cba45b588162605abc68cf095663fb401e46142f395fd90d0514c673dfe28c30e77fb48388ea9732dc38896f4ffa7656409a86c7b95ccd62fe66d5e7b906a4ff2ebe8a82dc63df923e4f3bbe67004e728111c573558c64db785a1a2ffb6282cd9e0cbb3644f0a507101319e0457635723ed7fcebfdd3cfa4904ad66c3a8c3738efb686a8cf75f9ae8d1385f4480df2724220b99aa41435c13367127038b38ace5cc80dc468f499af3c6ae621bda0403c8832e446a28a98d48639d990a893382bdda24fcc80dda0e3e6deae3dd17f184ebd2f788048ceda733c8a848cb6926cbe11eeb5209a90bd7c49ed2c9e6006e872fedbfa7bd9d7bf4950f200db73ebfdf4235eb684eb6d649e32dd6bd06c73d1323a44965d856e45080fb3560b7f09425a334e20005336e6e76473526c0d28244ad915d789a6144079103fc34fc2d53afd16fb9df014bb71e1e8276943f9b1b37297680792072475a80c01bcd33830acc250153c4e67e7e7f5b95c23fb0293fd0067fdc813347c7c7cef48391d8fe8c448be74acc88cbf10c24bf0d5839b577605c5349bc6fb9e6c0f1388bf7e5ae316881bb3eca529875fb24082a16dea1300474ab8702f825bda99978900c4b1c4bb3af879d89178f6c809944fbf97f1b43b4fc0c518960bee1d8d320186cdb18622eac11626f4b24ef0d2d8f2d82398f4f1721fb19047cd1d23767efb926393972edfbf62be674bdcbc3213fe278c34769e3af740fbd5705460c49a14f4f3e4525277ee415bb59df645b49d5f7116d8637723c15648a802568562fb69c45c6ab59fa8c5eddfd19906bd64dd066193067e89199bfb7c8a57aa5a0705c657bd88d0caa0a89bd3e17812ea44c940a8ff6b954f727f16d5341c4616b443b38ebc1b71843289ce2f5b697360e54cf7e86b3c21bf04c90310baea2384abf6961e2981ed14a015ef896f1cf51383bddad0be2d2e3f29d197a14f60c26a05e021f2797ec2396b1f8b450db2642d4035626a66649fa05859fa87d74ffecbd30ca18c10133c63df7bbb1d8f228d71b1b1d5a2b65cce27256d36b436d7828da5c0099e7f6bbd62fdea3509b76c1c0ecf20a4ed41e74cbc721dfd9b9b61e7ce86819768f28064212f38b51a3bdfb240c6608afbef287fa872895052f65fe48ac7123b1f8516ad8ee89ac5d59f3ce653091d09cfad96b82b4cb99b814ee1a0161278220f835a67d9fe7ad1c77a485b0c0f07c31ada9eb702724474a8da2cd97a8726c092b248044993255abeccce0cd7b54c605d0d2ad555729e05921540177c7dfb602fc5f3df5bcfa43e98baddb281f6c28d6279fee94372248ad92069897ead0622c1fff04b1fa2e38435aa210d49c8197a2ef94019ae729db131626242a1324c52a04f7b3b456702ece07fb81a80e038cd3b6dec952e21fa53bfe7fd7cfda5105b22c25056a7bd078e2d8f31a162c2c4dafa1c7da7c872a81c7789e3751122613628a9a897ba18a77c553f62e0145d1ccf0a89a038d172218fc4096180c7aa5c9d6d3eb32640441880baf219b3eb89dc46107377c52f7252737bedb3e9d6ba9e7561ed2ccf81c0d39c44b5fb7bc31478195ee17ff8714f45ee146d5559accaf1ed32712d2de0d7dc4bf2271f4db16988dfba26a518f1728723c8380d20c326d2a8184a4a1ae503136f155af52d399d30e490a16199976998cc7600f931e995a872ddf72d6b4cdeadd6b9340f5c4e51cfaf3f9b89e7b472d2e607a74749b2db05befa33da3235d080bb015e479a1dea7627a50a03e803725a9fd6bc3bc41f814dd5630c9d24609c881f3ce5df5a0aaa321fd4e2dd8dc43197f829ddf8a23ee4bd96fdfe6cd36ccfb733f0a874a2a721013c26fcafde4969275087a797037c89e281a59db7a923b2cda8e3e5852b4a444d8c006dd67e265cff834bdb8f890b64448364821f6b5fbc08d660eadaa4af0da02b5ac82e948772c69eaba60b88908698981d7c5554c4688164caa4c58c5197574ac181f9ac60724a30369292d9b7b599b09269d17b76649f161df271895d26f0f0afa4585dae72c3d5be93ab7bf8c3d79bc2b43e3941d571207a3d9b7ffc3f4de4429836f08c72359eed4911375e15bcd881b903355bdc2abae591872f39827bdec958e0ffa0619a76b319f26a605aa7b23c1922c79258ef6b9a6eee65adcff4a7afa38ddacb72bfff24ed0d677d321c3964830a489142c9ae870c78142a56c239fe59d8232972525c6c15769f3ec047568e44ed54c84475c55470ff129a1c720fb2d1e124ef013181ffc1a524bbe3ba14929cf9c02d197681b13dffdf0cbbb96c39d95e160172059f5c3de995f2815996063e79ab642b1f559b5134d6a357e3c36974fff491494622a78d5d274da0b29d144580c75e395cd895f95d69110ad34000f5206bd93e8b115de5358ecd96099c30d72423c9387fefc8551f78300d9518138005cddb721b422eeedbde44a58b58dc871d8361c11c882902c08bf45a2208e11c00f7f93a21ff40a94f5df8739daadd2c86c8303c8376457417f9b5890e105363548231729a4d93201b06f3c51828393232dfd9dc16b8a7b70db7a4c36a3a4863fd999d72e8fd379771c3ba434f78b79c449095cd3c9cd39b46665b6c505df7d10262c072597f11f45ffc34cb537a27c649940116117239516ee7e77186afa6acd7742272b1d937eb66fb07381d1e2c7c1500b940f33c7856f86b1f834ca5e8c02d4ed772fc771c1c2dc1d7e1646249c35e9f2df79300321333b725aaa11439c92b0e557280eadea7246ff488875270a824466fd1104128ceaf8397dd679e24a1e391177264b5371ca93cb5b71da28ae9d088fd53b31ff1523f3e8e5c33a493f35f09ce47779698a72f82d402d516af2e7e29014653d3232613911da59665c6d248c9d27205b49db6235a25752b299b882f5a9de7d7c71e739f8c06e325a4207a575651723f78d809c42441348f8d1c2b56449522553709a66d7f482a2de2695f379dee725e17e05f19304c64df271aabd58de55d2be6b3086e9ce6fffd66bd3ce72a3572f3686f13ca7731ca9ebbca70cba578ea8b089d641fd9ae00e3814faea3d5491bd0a8194c39a023e66e27f05a97a20b4c392ab5079c16544be1c5a9d466255760a45305206ad7e2b8eafdcc0c92d412a9d630250edbe559b1bc85258531ca9572730e5e3981f12e5a9318010878ef7032f123b8d5e07ec567349d688060556372b7d47b9490e2262f6e64d7dc6d9e70d817ec11f6c9869aa5ed14bf3d17880c720a4ffd353773002a0d7b65b92fc349eda459c3c125dc98b245fef234b36cca3f34c2e69c50e88967fa06fb41b15ad6edbb0a102b13d748da2d037e7ee6b2b7127f9d7ea85196493d87af64e4116cd256324dc3a8eb6f98bd016cc6582438fa728d9cc3123b95a24ea639598613fc684c2ccb2f4ca0ba26a92e6922d9271baa727104980e715d3529e818c7bd7ff6e41a3dee660b01e93bcba88fb85e130b723e829a9ddf70798c089b70766ee07708fe9756791d6e7edf61dc3ffcb0fa6927722ebfd420b50d22fdfdb1797a0283ef85684a1c52c97ba47196056dc4ac65d4656fc5c8df95d6a6c512eff4e76ad8d85d60213d48723fac7cde223a5f33b2ba1c1fe4568abb4ded01637f5338a550d1c100472502e7bccf734b4ac5786c0bb537b1793b1b29ab7f32bbbe38e0fd68541bf1c0c06bd85c8af7b0fe5aac7d6f1f72f8dbd794614998d78356a87e61695d365c1a49c7e6b3cf85c5a93b002c54ba39d2ed53f8bf8c6d1ca5dc88b596e1a3c74708893118537615d7ccfaba6e7bbb723af5a9b8092c7e41ed29af379262a2e3199f92f147947f4b854b332d50f055050c9d9055dbee6b6ca40f25ea8af186303fc8b371c5c1ee4695b3f26602472836d61c6961b2c09300ecf741bdf720942fdbfda3802d86f342ef29c05fc514ee721650531452b537c88ef53a3491e2701185f47774dafad68a6d5a1cb4dbd9c5721f85fef854823a6358ef2ba238e70d56d013bbcef4274682ed627085a7b7ba40780ec8fd09832119512d974bd9bd04788edc89772857001e060bd45349b1d872632dcf91bb980655bd709d3f2bd55874da5bbd6982236539e1678e767d80c76dcabc22109f4bfc8e86c829f25b5b7db2da9a1fa3d58f475bf11fddbc529ff47295a935c5da92e4e5f77093e6a9134a1cf89293fe2acea386a3282298db799e3cc6b7d0a4ced87736671c4011f6e6189f41e39fc25d8e5401eb1e33402394e572d86cceb5f619e478ad22ed23aaee5f571a6beed2884e64a1dd61202b93507c72e8969f2f2652b4d74faaaea776fc8a10051c8792045ffef26660a47e493e4372f1c04f8fb394c89172a722d88f24a79195c39ad7e91b008954bb69c7d366966a69e56f5c80effee1bc58067abbbe3e63d825e7c7902021cfb82d0be2563c5407f1e072b83ff12a1a5bdb3362d55a9ae1122f5c32107b275bb097a6e64dddc33a375d2efe17674fe650b123b3e35e702f60d85f45aac1cddb8be612535eb39a70b42c7aeafa30c81c734cfea7451cd9b9b9a558aa5517b72a5c50aefde0378f72ab545626d18c3d715c06e875e05a7692d1f536b3f3e095eb5a213d008b6900729ec905c968a6417700eaf859f43fb131379584e2ed3a7acff494aa8c72c3a80b87a7c192bd9537126c1e7b00a8418601af2e4732b8e36576bdbdc2be416e226f71223a4eabd78d1b0b243992a4214440f8186c2f4c1d31755136bffa686bf11759480fa3879eace7518b87fc09b21cca46930740c94c721e8805dfdaf33ea27258b8280427de6c025b3deeaf23e0fb503289709387f2e7422f32bdbb6b51fb33e18f2d081ba4109c0cc7f952d075c7ed28b29ca4df04bc780ee090b5174b382071ed61847a8ee355b6f3afca78e65bf5768519e224fe07254886134d041d1b05f1ce2ceff46eaa7711bbb6a48c5cfc1e389f087cb1f4982229f6b80b84c27872213ca2871031ed5ab955e65475a0535debf3c7cc656f37b9ad1fa712c959d272097b9aca06fea9ae6beee9343083897cb5e8f60195a2b5249cc1a0b1d78ac57278ed4670216f1e9cb90526f6497ca907ae1d9923352ee3acab8ee0f562309572ba81a56bef14a9fffa228a433f05b6272eff8b55d837831a95ba2ae6aa026b725417988af36ae5dfedb262796393d0455183b66d47c832fdba0c97f71800a472cec674461099af6b41c87ea0b5822cdf22f92f3768b6337f43373066b96fcb56e78063ee8fb5997e904abb4fb8051713fe30c46f4c0eb3a5dd0158ebf1a52f720c253cb2c6c3a1e9026dead11f674b66e419907959e3f111593fae8eb0dc3e72a924fdd16a6c5ea71f80542b9dfc699b4b299022ef5caf02736ed178cb419772e6dd293ef4fb7652aa05c03fe17a2e8b44158f07a4c76904f0d096a91d81831569fa49b68d9e3afb0c4d7bd08efe66d796d630b2cfbb0a5dddc3cab6a4a9bb72ff194d6b8be7371f886fa990f5f21fb0e2da675ff1690d2f3c6c958c2fc5aa32bd91589399dc51b5aec5d97f97ec104e4d472788413d75dbfcfbb0ce9505ba72fca2be8d0862ad8043f7b6bfbf64fbaa88856d9233198e5aa020609188c9c87209bd6aea848c6d1aa92a74595216f57667306aec395eea0e74f33e3e6ee1ac72d9c09f952cdc0fff38b2ebbc6bbb116abda14b06a38b05454d364a432ef41c123bbc86481eff042dd3813e5dfb5d01062c8ca945e0224c93f9fc6117b9bb7c6acceadf5befd7d1d72e611715a5921356d398b17ca5c6155adabc5daba08801729aa6f9275c27dabecab059687ea5e5f71f9efdaf736cdea2ae2e820d1c992072477b7d5c1c4755a3812c1bdafc7150610dd57f8bd1fbc5d0f7f62d5fa1384719e285d0a14bb064e55183bc173e0934c5cce567dffb0640180af23bc25c17e73894c5f0cdb9fc73aca37667fdb38154ddba7b5f259fc2c3008be44a1eb1fe4472968784a83346966ad3bdf412be58a99467892a8bb0470ee51fbba5a911dd1f7239cc24dda4e9763712091a93da7fbed03c8fa0bb0d3e508d6293e2472937fd5231d65c6f69e2c26b63a7b98c94eb6897ba9d862e7bae6fb9e6097f6ed9f1a920209a2f0a63763ab54f08a13c9d356e85a494591aa9cdbc5bc1eb30c7e7ecb6724dde86884493064baaa88a34c1a692266134075e930301d3592b57ea49c7a755f25538635b4003092c688f3de67c2f59854b83b4332f28130f779fb3f889de4ac6efa86141a07b04255a9e62db3c372a3cd19d09171bd093ecfcf15de64d23726caabdedcf4bc0806219546ad8c4161d69a9760dcdab41b57236a30645c62964c1ddb1e6675cc0fa9fd241ba766aa9bf624e0a42d015d22b85538e22f481ec72f08a465027d0a3306700cdcd7b34ce98bcd95a94002281a252b5228369bcb47221bfb555605a118565c3c83414c13ef8fbb44e67f8293962a4940b45ba70157205436098c04a3349ad05e91e9e92ae2a90909ebc28de3f7d58f99de6cf4a2321d89b18adb51c5469d6a60f1789406a3e1968dc16ddedbf165c5fbfe7f487a772d07c0f8a1e24cc007bca0270d30961aadaca37278d7872e7bb18110f96eb0a34edb640f9224f6a280e080e42c8963d4ca8510e093767429e9f24ed436fa0d472462692146100460dc0f270a10daaae9875869b85610847cbeb9796c5fbd5743ff37a95cb9cf762bcdb6e1fd9941a6171f3d43f7aad436e9ac7b263e10a8b0f726fe8e8587ed5f3d042f664d76da70a2b010bed60c471bc08a4e1e683b9e26d63079c5b1a85cb99be9e23bbc440834179b44d32d8dd66321c94677c064585bd72b9c7a9440d37aaccc99f538b2cf04c3aeef035a5a3f1ffb093a4e75b9b36a2725c1387e7779b8a5bf6fda2dc8fb22842523ac2c3715172d1ac7d41f7282e0637f90f9e3d093b6e022bff7f9ae7e7e2b81ef6e4898662d449d300e3c76a298672025adefc7f914bc08e4eb8a668d1ce3934356e2ad9acf5896e667cc203c70d61efc8e7646b7a7b72ada0159c6a9476a17a2911512a1ea8611f0ff863cf824b04fd6ad6e9dfa39d391678c7b21b514aa979f02c317859a45ac43957400e9c4f2544d4af5051ef2adfeb97eaff7f551bb16779afc8978f3461e90defab9748cc0155308f91b8679f74634f9a2f4ec9aa7d36891e18e130f5a8f27647ff56e6bd6638e55537fe0732086bc727c5e51d9374c6a65a03833b9096edea0336e9091f2ea930578d3a1c6e6e5cfa2c7ec16dd5857f65f1cdd32ec4668bad6557d833d117f7e5eea594db60182439d87bfc05832ced48dd2480bcb4315184d6ff74f038727a6990d860652e1abce2dac2529062ac163ff25ae86cd3212248e7eee12f7f725d3033df4875f9cd9aa03f2b6faf2746ff2abb0c2cfc3c2e7d75512bedfcd8725a783cd755fea1f873496c8770447791e7d8869ac9329647cbacc281d204717260f9990311a19dc2228d6ca27d15df23d8eea440bbc96ad488cda55bb9830f722addcc7ec638a6a77fbd81f2aaf9520c5fe8e36498686579405b2e0797070072ef4bd8395cc169724ee118322d17fc768eae99155abadf9bfe9cf0b52ffb80526ea12cce114039de1c2c349746788ae7079485108dbd97a3fc7919de47ba00014f815ba6927b5f7b784b5ec8b4f8302303143ba6e0348970d130676d94d8d672f78e3663b22ec36cbb003f13e17e821bcf53e909e50993b29a183be257934f5a07f0085d645c43044d18ba0bef557e323e8cfad8c8521991e43e5af4604a267269c2609d608af4b3523b8db2c2f2e762f6494a0cda523f3aabbf8986154cb8724324ed6f56e5a2eea67c5c805b08dd1ac0cabecece6f783ca65a705f7ea401052dbceb5145c40d911e290ad1646967c63a7991a01452b16b218776ad89ffa81cefb8f4b27acfcf3f7737693110e45606963b7b853b8e1dbb6fda9b5de1871958daf0c6640570775d1878a9960e0606d19595ff2a37ca76119e041a4903841c72911e35e6842163cb5a37994ada70c21bf384967609414a7e7980dac346106372def4fca6c5626a0e928bf852b047a4967d612d2c62162af53abfedf94b0775724f89e570dbb9d9e93b84e1e3a41733c55dd02daca74add39c2ca4f7ea04a3672ad163e9bffc844691b932eed67e704319541cf48a191ef69d77be0b0c1f7d10d3cf9669864309095c1731b993cc675268f8d3e9e706864180ceaee4e6fa0a55fdf15462af67cc97686b92417a83417cdd72b48547708f3bb4f3115409bd28d2a0134592d62e36f084ecc34249c01378afc05f8dabd78836a82638acb41ed79720fde322fd38f051a74612ae60293e2bede631fc744ada7446ed71c4332b51b729f34a4ef98574e91524a2336cf6217fb587435df8b7f330136d7021dc45d1e5c2bd74487a96e709b1a9a5b94c7f0ba2a69a790e07d8d90596133b56106eb8272f5723da90528ea3ff31c30b2422438150e6f6511401a77b121544c573d128756538853816b47bddf685de7d191f153228970d12db98d1ff406e3ab6919bed20be0761535f4e299943a37fe6770219a28031818054ee97a13979162dd3c46e3725e7ad3f98b2ccae0b7a15a422d64bdf37e68f67d865faedac52b38481112970a390ae7788f28ac4f3b036a78cd100bdb7fc6003729fa972b7456d334e3429b2236b9e602cda494bd4dd7b7b915b787e340a776ae6cb59c218233ccaac9674b72412105e1b7c151d11e7d07500b8679a947eda18716ba36253e0dfe44e9ee0a7203ef1daad85f1a3f82003d8f6153ff395822c000e34444835d1fbb8dbf228c018fbc77f61a5ae745c5173c40dfe98abdd76de200e2cceea5c5903791888d04609b88e682faa354aef72f92a1693beb3c034a51463894283c5c9538337339a85f9c2dac052d6dbc5455f87e4224ff0a94dbf822969d005043f199b6e404845572e6597685948c1d2893737e0d6016d48f04000f28cc8c7107646d0e5ba20e2e42512fce4b1887878f1a8800bed38348b58aaa3cf5c146c45f16f714a1dfd09372f56958ca996fd320c83cc66f03f3aa8bc636541b1e2acb9d97448fbebc4c5972b8564bba3e2c3601991a0d20215d0e79ba6b63eb194e58b8245c7aae573620723066b4e880ccdec573ff2bed9282929bd4696118e61890c8041b002fa97d8833a083cde78554799672f7d8714b4ab50c08a3b0f9e02363843e488cdd7869c572d9a057d68c69dcc47304653d30757b4b52de3df41acdfb0757291b96d2216c6908716ef0344e0887157b3d4e8904f7c234be196d88ed381810904d47bd3e621901a4a9d1155a0c095925ffb50f9251b971905b0a446383abd28415285232e0724e46aed2c9c5d82be6a35fe00aa8663f10b6a904766f32be6039b066ffa5116c78ed1f306946735b052190028d22bc96952c51918c96ea6f4290545f90a23371f3c0234d87b36d51752a6470fb57938d01f5f34f91609de31d441572ca13ab7224da5d33d641a3e7a79d87706dac4d6240e987d6f6cf32fb93f47d3e14e247722cfbd9110cf76a59d5a39e2a43d3a3f21825d6042437ae6dedde2fb6ea7dc3729b810f8fa4af49ba11fe643dcb3a45a933f79c4ae5c03db93074b2a972f7a772950efb8b5124c01ecc4c055280b05fe5a86424205cedf6c4b25a67b446ac790656fd70ec1f13a12873182966d6989c9954e7912c94ab39da04d1ed58abf27b5d48205675a5128c9b3b2393cc971054525581d026ba01e701d2f6f3477df45571b20b9bbf4151e35d962a6421f1ee3ebffed4bc70d8ac1c1d41099a251c320537bb0555bd469cb413fcbcc993a93e752db490c696c5727918964f7cfb332fe772b75f76eadf35416743e921f6f7f303180d023dcab09d05869f13f4b5b55af47257c7686c9396094e5ebbef607aa5540d91706c606eba084258aee5bcafcb3e72a95d59cb866175002badf7c723520048210ea4ec0da948dd3c0ee6085d2e00721a0e7d219acb16f51eb7dbee57826eac8766ce582b65504d1c201510e12475728a8d9b3bef2ae6041c83b1ca9e552d10cf113190a964818895464074f1312a444900fde49c693b4d999de7159bf0bed2f38e6420f896a436a970a7c5353616718fc69c86f67316942f695d75c80ab99ae70e88cbc5b9470fdeea5081eef977722767394252658efdb57b1d0abea6d57aed69782a807ff590d20b8814cead8f6f9f82240e849ddcd3df0c3949cc1576c5a737395f81dea642d481ba14d3bcaa359415722b14e298cae34928b75bdf00eb1e026988bc9a31eb453936adf7c5d172325a54ac4dbc08ce2af66494034d5f22a35da6071d57b37b1cadba7278c14f159c9f68070dd5015f972dd2c8756616f3af397397f73c84c8f02358bd8c0423516df2df3eeb6d9a0382e065bf726db771517b55d1bf96ab4a8740154bf33a82721c1bae055c1cc41767ffeafe8a89bf4339bba72cb46ceda494a401d908bf2272cbc3ab3cc2d115cee474f3573af342beea6353e2713d253c30b5a1bf5a8f0f3852b064dbc7d98dd34e3f81efb9114368cdd15f6d97055a73f4c3a74f780a7f7270966409a5808ee719d898b7ac767976ec9156386b69d3e788cc40b82e699c7293575ffa79ab36df95c6f960610e2b115d301031e864593cd1ffc1e43e93782e9fadcf0b3dffb60c0d7ff4d9b385aed33e3ce72efd909462adee72057fd0107211d80f1e52ceb439595c72ffb63be75dae6de1f6de0d3d1982f8d10c4f7a0737c872c1d55e91faea8ebee7a6f44a7925fc2656e1f3565fdcbae4c654910410721e5ddbf0c225654f2b331cd459347e6c861ed0d407b68576eebf1deb28f18d26d724fca9c46ad06127e67202ccea26067e3a8f447efae8042dcf24c205a57772982f0b53065d19ab911c0e4956414c484f8c7cd7beb30105f6971909d10ca04eb73323bed785350286d46f0c97bb3e38b2c1db9683d49d49efd1e6e3e71cf572b5cd4556ee84fd91199528affdb330063c7a9dc354fd90e1ff26e230893b8a72f5ee2aaf0a83cb92a28c1228cfd8fb740e9fbd4daf7cbd0137b0b7041cbaf27229cd0f983c51270d13994ef4f620657d9336c2f8c2f9eb4427be381b930e967223e6bb4adc2d8c469936d22cadb347bd8320a4793b8bf7756ac8f657bcb95d06ec9a373f775709dd26427b6264df6885e426bf0018a3232588e903cc086e4040f7ab20c2bec18eb0248c8b0d7c63bc75a9e194bdd8763aa2265b6552a483532beb92ee310300516f7eebafb42c50d91eaa0d88cfeed6d6f32ec799244a1a690818359a27debd142de962586130be75d592fd90ce61ccc13048fc9d8b6cfac772eceb4c4897d3ec133ce4d9bacad9fcea1c27e3e9710b1a97eb23bab338154257b6040111fac78e8b08a2913b411605d32c4ba453cfb2d2361943f714a47c20725df6dd2d038856235a624764cfdc3adcafb1494891d90933b7383cded62d7d0532353b766a45626f4ee9339ea97721e5fc2e187daea7ba91c0aa03d8b6b7bd5bc887a93d95e4c17a69b229e25c0c417d8de9d37f14c7396f7476c53b15d28272cde544001a5e6f1ca4b70ba787873e32e51fdc2e3ed2eeb61432e60d29fb277294d3a738b0a21d15393eeb1692394926a2da1877b6a8385e388d5ed32e00476265bf89a8df50016e4920cd5f844b51aee79b1934c40bdb4846201ad5c7990d72e45b1d9d038eedcec971234fcb0ef5247d46ed90053057d9322df8771315af72224373670c749e3f0e1f33638b7925d63a7d39a9ff66cc01d9f8059a8d66d872175ac54b3cf604dd0ad63f35421e99cc0fefcb4bc087bb015f5d7425e31e155b3a26a38250b2f6613ec86bfcd106a690346004a8c612bec3be827dc6049ad7722ad5d550dfcb1139ee7b7d1986ddfe43d49d0b72cd2edf61c5d5f4095b849331bc1403d4f3f26b6ec0392b95348d6d1743a695ef525b0d39421125006d97ae7244f9ee51114f7276d6d0c66fd4c98cca4ef0de30e48563cc569e05d94fbcc072e1235b81e07a0b648c243a995d5119e5f764ba50652c63d5fa59c887e5c4db48bed794ab742d64d4d4ea551cc6d3e023f46b746f736f360daac8d45b818d2b43cbb8a77aebc6496244e5a694300d600bb3a8f998c2d3fcf524604daa3578e97217ba14387bd03824f18dcead38caa3d0f984df5b5e28897c30ccd1b861a2590575dfde5d39c5febeb78340e21447e38da40b15b021bee5a547b92ab8b0a314726b3c4d4df735475a9fa924a7b56e59684deb2fcb47427b65d7b340d87bdbc620136bc12eb9abd0dd0d032158983973936e1594f884be8bdee97b690650843f720241945d717f00c774aef4bc35928367d0722145e95cc5a522a98e8f50ffdc1973dba5da12a142b9c8d399c3a2760517da8af1b5c64e83559acfffe0ccafaa728aae3b22bcd7eb84e907cc946de40543de85616337765b3ea5ae27de94cb0372d6f82a513429bb659d7a4adafb245f859ccdd3be3bad5f4789c5cc9c5cd11c729967bf7489b8b8fdf620c8293cd66763e39a8f846b66d69ac5da172907d6137247551688b8e410fbb386cb3af0ef4ee08e71fce56ed663431393ba33122d20729a2f80a4f1896965b44be8ac7d6430b85894594e85ccfae537ecd907b307603a674d43856b4ce23963aaddf1a590eb9eeb84e97bf8eadb554707d3d36fa13f6d5cdc822173f4570328dee51582d82f91d372c9c11af486c15af69a2fd3534b72992a6aa3f514ee96071c9a05723e867c17e321f7fe162ceb3ca8c6381c473e4978b31f81eb707bd9abfcabcc5e68e680e3a012ca81bb59fdc4e15cbb5f53ee5db3b7f519b0b6ec489d116ca36f9206c37b92fe49bd0204fa245d4a22c34c4c727e6b6f17ba620ef192f4b62911d1d053ce4992b9087980c69a2a2a20aa65a3721a0503b7dc7781628525a0fbcba9ea969205d476b904f9c4a7b2dfa699bff916709e154cc3f69b7db81f3f8df9c59ac169747be219aa3dd5634a0b1249a70072755e3d35691c7ebf8a7374ae69c3653750b7a83429c81450c97c5adb32b16a7244bf940841f0cd6ff023f446dc2dc642c77ef6ad14d908a218039f43c89d31724e459ec45f4e3bc07a43b8950d31ddc3a7b3bb18028a199ba9fbd79f1167d6726b42c3abc05a87e033b016d7bb17863a879f8f97dde6d768a29c94b67c76d672209ecf0808375ac5c8fff9aceedebda0724908d69a93b9aa90bb4e5e8b4b2e7222bd1322b66b510c099dc1335217c6f01718e225725efb7606607c66c1b0854f50f18b77c78b5ebf184f13fcabd03378f235f41ad8c858e972f469411f95cb5cf0a97f3e6f67e971338bbf95f6053f8fad965e10efa45c5e49a2969d5fcf4b6e766bd3f8e2c7b7c0c870de54b0d866d98a51c0c2bbfe66b36fc59251662337723e71dc87f2316e281c3b5fe19627f8f80f2af6c95410281c98488a38af12df725257b39ce059e2b932673775c3e763a40146cd17075e29b2bbaf1dd84705ab719da7419df8361ba38e94300ed4591340c5d2809aa5d9b0bb17021c09241049727b3fc7eb4af4afd4102bc8abaea58ae6e8a56e268ca54277be7dd8eba143ea72a476f34f83a790ee79c9849089c72784932b3cc369b911900571bb472e3a84721b383fc388e88e953e7d0581d0e05f2b8ed536220794064aba728ab681583d7220dddda9f2b88c77a02915a8a3e6ac88001f83f69330d782c75dc127d5b3fe28cd9f47fe4a2b1ee05fc3912e567f7d7a6a5e2de5c5517503a8b5854c9e54d43ff7638631d2c2d825d7bed9142baa2fd8c20bc8983459fad46cc3143d5a696b72769804ee2f625eb7d23932ae406a1f947330340a9badcea6aa0dca31e601916f37c41a2b5656e6245b776a2857db03c9444e4ee42aaca9666b6a309833a45272a6f8a1824c8afdfedc8a8ddc1bc9221f82978869bfde18203d80369819e6cb721b69a694b3e51d8404f3b3c35aabc47ec143e47f0b79970cb5d0692e3dd910724fd53afcb89047c7ec841d43d2ce5dfa1bf0fc6248f374c68e2220952475d036614ba3428c6272d37d22c9b0bfb634a696a1cb18dfc89a871c997530a260874f52f0c98904659108fd8f295c52722dd6df47df8232844d3829b14e5924907272b04d07fe60b73469baf30014562e696ed7dd173061fc5064442c40609d2e0a72f16a488659ee1617cdcac8c9286cf593668f94587900baa721ef930849e2b9728bfcd57a97c97e290f6cfb53d0f8184236129af42bd5ad6e8b98b53bc684ef42e50b6dec8fc4c42adf13c22ff4eac40fd92b80f3270255b2fad3e6c4d98e8d426ae6ca43a545bb5cc14493fe1d42c0a7df137573364889e2c9ebac3102fe600dd71dd34229a16b153e4f836647d678a32557378e10e084cce872b436ae64f726298710e4142051889e83a73a06dbf928f3e358f9a9d2bed52fdf7ad0a84e695e7f0c795d6a037f29ddaed37bad280533f7f2e1d80cc18f648fd644298489fa7251376ee2a7a2d4f27663539c0a6f9011a45c4040da5f98ddad5a1b43837b1a12b1b936ae8c380f26664848812f38fac0a4a0116329950c9600c0b519f89a4972f216a4ff9a9e68f0ae57b68102c316de325762d701d20a7fd1e29c64ea7a710a4d08a06c604eb079522f073f608cf323138fda6eaf376e0fab332ec894f87a63f7acb32170fd3bd6de52be4965be9c6c0fee7361c3750442533d2e754d50205f48716285a6d3443aa733e34577da36b22888734cc2b5a87e06b1542284556d726148760becec73bf04cdae7141e66f6df64853201a0ae2904d948946808b5972caebff8adaecdafefcf7046f32c89821214ee15038fc3ab59fc93eccbc05ec12523666aeb1097d42c79cb0fc59a2a0150ff14934ec282de72e80038d47a25272f51bd3ca2f1e76989160ac37e50f3fcf569859fafe3a31ad2530638cc177767247d751a562def982e7c748aece196df498b007f4ae93f22ad77007e7069bae4ba1addc8e0bb9a8403b3d674119aae8a8040efd44a0a421e6f6e6404b4825577223e6b32eb1c219f41110b13dde76ce9a402d7dbb80f66f3672ad6003e0317c72b29ae4b028fc02b14ceb0035365f5d07ae502c49f4c316a0b12da60b33dc034ac821ff004dd1ad6f027d8c0283e2c1249ffa0a165e5bdb232d192d52e11755729657d002fc2948d2766d0165ce0dc91b6136bc3b69d36095de2e690450813b5ed64222f1a74b1da79b769d29ccfdbaeb3cc5c44c4a8950a22f9daf10e5b63172cf9ea9e959bef599d4f45b1b32e064437dec3c5b844a45011001255a7d154f721e55a8eeb45b41d4b2ab935c0e73377ff8a87f2091d8e1b5d438045a3165cb535640662fd31119bdf2bb197566e8d4f733e1f6c84c927bca355b5bcc319c0b140a47e44661544766057ab7179eece639182a5ee5cd7559591a9fb58de9af7551301c4b75894e133a95f688c3d941cea245f5941fc185cd46ec6ed0022e11de5cfaf5187ec5876fff11c415a2243de630dfc4e5e58564723f1e062224d5bdcd727f89c41f12267e56ba3721ee660966c937a88d4f9553a4f61269f65a5228f658fa024f49012aa146504809b59a790097a4a4e3320b312d06803a3bc350894b72f73998b664a00942ef8b3b49fd143f211b1cf6753c2ebbb5dcb5671fcb73d972935de7531c690ddb2f9c89ff0284e303e0430c7b399af2d96bc6ce357699fd72e25cbce50160f2f615b6b1b958d144fe6b346ad4a76fffa20a85895d80595072f51fb9c3fcc980bbc2f5d879b1ad3bebcdf1ed3c1ec59ee74d5f867e9c9f3805876b09e285e5c04d6b4310ccad899b42cb92a8ee44640cf1577944389a10b572cff1121f17c06b0ac0557cfb0052fd421285183ccebdfd5353a0b1cbb82c3272539fb4f826dace4e39b7977541fabe08caf92e2c010ca72795a97b1265fbd672885b9d599f18717c45b1194662a23a72a5070946159a577caa6b12cccaa867560d72a71a36e8fae4d5c1be0550f99b87bb02bb6a4fb68ead7a56aff6bdee5a72df3cfd98f55a12f9ef00421fe95a0b3fa0f8d430d5d2ea58623c994ce2d3be727361a65ec4433bcc0f47ebc17e4e77227c297ac8a009fcba405d119b7c8e16179587acb6bfd111dc86e85eca639ac10952e4013e89303fcace3800f884c8f372b86065b281aaa0226595d8a109ea3c6cec2dd0de60302f84810ba74be847854a053ad2e71e01d9eae7b0560829a5de432e41d21a31910ce59ba83fa9471c5c72c2671dfbe99ee3318fa312ae94a496a1630830437d306f1fa59e964bb29b56720d18414f21999e248de8fa8f2d1ec4f391a2ffe66a4c6cb23ca08d6567835572579584fb3dc81c81038f98a70ce65ad97ce03fba1b3f95a6572a7c4c77f415720abaad945cba88317472823138d50839bd57facd5455db82a8f62d02256be972219225c9207810e95bd5869255b78c8be22d52aedaecafc8def10164412d2a205f10a0cdb5f1aa86d8ba1a1e085112bdc45eef37370a9fc2e3b7988d2a0c5472ed1ad08138fab4d33ec8057933baa4c34c948c336c24295ef626c6d21e967a1345e8d05c921d2638eb37f5db68fe7d58d599abf1a3cd5c121701a3a62c0ebd7287c497ed96c9986f44a6804054eb6a2bc6925b2abbca83d0435852f472997901f2e10c67d219d1db61ecc05421feea03d04a7c0d9483dacdd06013d394156b1f39511c8b30e806fdbc6b0e2388c371502ccb9992b17f8698c01a3ff87008cf720a35f1c080c5ca0da8bd9205706d671a74bf3f5173263b7b827f7a5176df3e72f926d88ed21398b79a124de3e5e13ee9f2fa737f7c90d20ab612d56055a6c80fb39e6bd885f94ce4fc0f6f91c38bc9ac2e3a203e986326d6df0610b92709b113c239565e6248b3065a7d6b95320f8cd31fbcb9f304c2e23d6c813b1acf01b4058d3716e1cdcd0219b96b7b60aa564bd04f70e3ab435dbbfea7fd495b4b4ed67286e8dabdb93871509b5bbc0b8e8d269c235ebdbbc82a6a327412eeb80fcac0111944f748779808f24b59c8b30c5dcec864b6921f8a43835e23623f12ec06744f084ce6868b3e06dafa01b26ebdac65e455dd196c4eb33a4810df46213e802072ff4a475033104fab003e76add97d560c00ec5df1cf9886b4614d0ecb46b5fe3f5a36ad78c3bcdc156be25a7b70853d57ec27ec6d9138a8d20f6b47eeb39f04729f64e8ce3850ccb1d22dd18c42fc3d43ec59e0e54029c04350e273ed60860a7255ee0073d355c22b0835592571b1b1c8ec1572a6f72e953fbde3e3964db48972865e2524191f659481e93ea83d5ac3ef9d972288f43b22ca9fa2d95694726c723f3208b0dd1f70d404c2971efdbb2afab7a6de471771f1cf78cdafd93622367259b67aa3fa03a7f2e9342cbd6e359cfd927af345588e49e14db7444516469b72c15f23b522c47d880c10c6a6851692d3611f7f570b91d84a552d587c4b48937271cf01f9c8b2eddd5cc94b2ff4eb56aea6f1a781876e3cb42f8359b2f9b7040f613475a8b99c997fa2442983ab5ce3a8ca5dd2fdc6c560d428d0e34f28a78972a33fd7a472f0151c9ccb3682bbcb143bc31ad5d5d0f1cc90b9c5a048a32d7b3d2932afe97a38291c80443cc531810cf4f0fc3d89a73514bc3248f71008756809a3e0042c7e6bdce8abc8b059e1ba900d18398741968d1ea8ed2e4fcda63d93579e4454323e1853a61e05171e106b08289e2d17010e86acacdea7c36d7722174729b3f56a955c8188fe7a40014570da15b734fbc6efd2996802c32648a651e07285c872f1a9b179509077b256e7cc1a0ecded29c5ea28956a71c964d014620b3894af939714c03fa2ad49e4a87b1958b553d38ff64f4132bd1734e322df4d3e64052aa8c9adef3c2ac25ede809ac26c61f091eef4b86bd77154a92b715d6e6b3abf69d36bd6b596f61d36f07d64b790060756137a95627b16b8c576ef75486772fd3bdd9ac7533b4c18ebe2295f48e4a22f1c0ea10b10e2feeaf07d0473eb527253c49cd8411d38560395beec1a9e74834acb9f9be3b14e7dd937ba6207375a125459ec66486ed6671254fad1140b9b57cb81848ee89ad4bca465ff2a3601c409b4168023dc7ecb4861dd121645826104634edaecf0bbd9b696cff3072c25f8625983ed288edc72211f4826ea3aabcab8febb8181718c04e32d5dfc5642ef006d5c1b7e235f27f0ffcd77e790c02fab53f99520075b5e889c0215f9116fdcbc546d20d94c7cb4d9616c35c5d6c786311f5b3a2c9bef90965e52aa823a60fd105d62538b459e1a7e128114bc973bdc13a2adb463cde8d8ee80f122e06063cda272fbedf383af69a7145938c1d361254a318b8b9308bb0f7f3c379c7dc8a0f3c5729961265c58b533cb9988adcb13fa53321fe243a4316464255ed80308ce0f02669504fed09a86ca65f68ee69f9322d037517ebfd57eaaa04039bbfad38c881072c13814662bd23cbae3c103bb889e2c1c891c65b85b896c2342f03602f70951728ccdf08820c24dc304dd3065d0bc7d89f5b854bfdc63e3f054482c813e302372872285b76da06283e549ccb613c21dc6800cd5a2b954523451766cdd8d96861c2a508e86758eb09a53aedb003e2c4bc53bd000d2ae90c5c9c474ae59f44a70021b60936a413bf5f68edc3ddf8512c2d208c9356abbd734d070eb703e76a20642ccf2c4b53aaad87d0648e204ccda40a7dd82a182d34f8c91c372b4ea71eb61728b376cb717fdf16d95bdc2ab22c3e08f9eb23050d31e591717c073367569c072325062b78d7dd717f131f48e3c568fec95596780dc76e8afa88dadfeb6480b7292589247651fc48ceea73a788169e9462a457caff035e4c153c5769040424072d376ba5f33e686d00f02bed88b961208cb058612e8999a2dcd06aadff830431eae8e6be58190ba9a00babf24b7a49ccad92be737bc52f7be9262b4e05f5d8172e8efed116066dc40849c99818d33a70def76bfbe242c7443f4e91af00c03007259cea2f3e2dd2858b68f9068bc065cc5843027cca8521953529c322627417921b71200f572093dad04831ba604b181ad7d0e56ad074119f28a1ac2c10b06fe195442efebfca301e29dcaffce30f1dec9a184eeaee7f4ca26dc05a5013fed76497511bbd2cf6ac5fb601298aa88be78b1616bd754abd0f5c28c0ece9889ef927260a88623b2d0f322f6bb5ef1b8227ebea5bfe3e7628b7e107b09d0c3a70f335ab36ba114800c403e0005603ef7b8c9fa2e89d1eadd0a285721c0961fb71c25503af0b3e4f2fe41ba1afa273cc5c965c7e6285b9b42c0f5987e1533b43a43357217af22ac7a0b8d426bf675c80686882ee0178f792abc835a9c2368d89e77aa2e3cd290308d35061710fad805d288abcc86964b0535e3e9f73b500e88597cba6e85b0aeeaf80ebd72aae95235118988e1e00a4f2a85f310ef9a4870710cdf953a08d1fe56560e76c330c35e13829377c98c577f7f2d086d1075297bda2e1ed75ff596270ea8219c47980ddfbf64aeeb290b83789c311095e4b37c3c125fe7bb726b39796a536e27da67cdbe1952e64e9b7cf94adc6285e38b880d261a687f2772093c2068849322202fb495d714ce47ae38a5e53a5c0cc44985965e03e48a234ae2f64cc3c36fe1f411147a1cc53b4053bf45860f10bf497bdde4068ef098ea72eb8d0aef4edf75e755e60d5770b6f1d9a82dffba04daf77a915bacf69d5e0671cfd75f7e02e36556b2da8b70f8e7aa8611cdc8d73a7a8718fa6fb85de5faf939693ee1a6eec95542b1a5d24c855aa90f450191ec99cb04357880d686f6c3ea72433f521a311ac7406e21f732caa05c0cf134b3403302154bab5d5d1fc9f1d1725b23510f461a22a83c5c0b9ff3bfc35d304ce517b80b09ade2ffd2a7101c8d4cbaa47401c7ebae788184c5ca6afc89013c64626b993dd8ce6dbfa845caa87d725b91739e1125e29dd2d43461b3f5d9dab321c962fe5b9961aea68b1929d7ba727edcbef392d00d550e4d35bed4c4af7b23ebd789838f1cc76b89c783b9490b639dc0780b00502aa077160e72f5ca372fb19fe4513b4183e0ed99ad4812dde072f7ff58e6a709eba646a9de26f0c24e20e82873976509e6017cc705f6b6039e5446245a131aa7da43996ef16e14c32789fb7626ac388f5f0226061500920b367207a4c26d27de0a9d429211b42dc27554ed18bf3ff1bf311e93739bb921379c52f69e21f6f92283278ec7a2b28a307049a4dbc76d323469c2e559b6d811e45d72b5e8e49158d80f24891280fca1e56ca0a151cb004d1cc369b6a8b995ec33500e220ece5209cdb71d87179555bdccfb6f410b2dfd18a833a5a236d38b20d61d726faeb81551dea51bf507c4946d2a79ab14db452ad64c8882cc2e0dafe01ecd4d6e99bb461215561b808e1b949723d374c8562cfe1e38307a93353a8f9455617203abbbee64ae24f0314a23f728cd22ea698398697bcc1ae272467ba00855a9724a18dc83b3fd9a174f4695a9d41ade39e27fad11dcdd62e24054f4d1f7dea321bbcda0bd6e9e0133dedde324bbeb6bfcfa1eb2e8d64abc61672805ccd6b0187284620b5de26935df0b715a141eed523896a511b2e5f9767101f1b50783706172e9f9b27a75f83d0bf2a87b1ad24a8b29a07f047d834f66e9556ed5a42085ea72a88c44cbf618bd0d1a62a1ed097a6f9e7c4dd5a893d5883c9b055ac88f5ec77221a4c8c8d1e09cb2593f41c5bfb1964580b9628a74ccc2683574f36e1df4ac0be33fbfb6edf2e0a1a31489bd4d49e49a603d5c24fde491fa085d0da3ed0e54720e8bb5d2c38ebf63002aeb93c06a90e78a669e26fa8ac90ab46041153345f442dccf180c35ff80e7180abc50f87402184ed11fd69b3ea3cd0572de63c5e6ec2f30224377bdd4649960808cf12763924240a66d9bf8d8da3339ef52b40aad3c72ef2b167fa68a5074da0e6f79813f986cfa6673405aceb8283506507ed5d6ab72178803f3f4092aa2fd9d00577c81cb04062abc7aba3bacd47c3328f338a33472cd7d23ff6aeaddfdfd48b05d0f2e0e9a62a888414fa67ba7d3afca0ec66fc672d6ead0a58c029415f5b92264bc818eb96535cfd3921d36e5d99b1d139eea7b72dddab99282f67cb410f0e31d2aea7d24207bd17a31b14294b0779d4c958373661c581aef72ba0b286ba2f491a611c38f878401fd02a59884fcb916e0a6e8d37266270dc944e89497463f63e5fd2eb6971e1f1832de0e7b19811c150596019572590327e6ed8006e836dd11c0525010327d5778daa0c38df2065128226d4daf726f355656ec36151a1d5bd4b099e4175a2a69de05e5f3f8658e5adaab7e2f86724c6db4290a762eb87babd79c221b58b75cc195a30d3b77a38b2cfe67ab6b5f72511a1441a478893731349ff41405dbdd1ce92b923e30170cfb25ba769e89bc72b1b3d26ce5fa6396c0a662e2c92c639bb05b61c48bcae52417f29a4562b4b203f272969b497359a4bd2fdc20ef049e771fa312bb27a23199d32793c501101572f8add002fcefc9263f75b611f4ba2cee45fb00d06771b3fe8a4711918eda107270ff7d892dea0b7ba77d672a6b2f886dbd2b2afaf2098db55d15003b41eae94295254faebe91e84ece0d87a29253480bb194d75bdec21e60403bcd38472a4d72f46382f14362451f32ff7543cc2b2222342699c32a2577e79e41c704ba26e86ecdc1ebc0260f1188c5b4d87ff1740c09e5e58cd1e7dbebb501f1e6f1fcc8b347581518c39448eb87a83da83dd2c108f61e1bcb6088b1aee58369abca5c4b09393e3b6227577eca8d7bdc8196179daa4a3dd2357e6413c91a4fe8038adf60807246b775a6d06da16e0229950be86d8a85c5a70b0c85e104aa815d3beeb7809d724db5bf6dbecad9716eef3691e407481e80de646ffcc4312abb0d77cb5e1634063350ed22bd5148f2aca3412f7e658a27b85d755705fc685e177a82df374825726f4b2b99db9e5dbaa40f12d69b28cfbb5faa4065a697c5b75ab0c06d016a2f7270121dd05fcb7cf13690af65651594c621adeda33432f117a9bf95055e6260722f261a62092019cac0575c6c240e7b8ec168e7483209b090d14c11fb9251d74362bd4478a42d8920c2043e258d82862f84dea2edb2058e59de0548ed726c3a2712a3cb83c755f956c40ad198ba75b97f6ae3bac4d30efb5327ac8b2432e5564795aeb23e7ee9bca11cd1e5c402720f9f00770ad6cb1b754e17ec4d2a844cd84071a005f591653cd791bbfcafb7c3d1418be0b695e5f8bbeb3e6875859b1ed7724f52b74a27bdffe7198a884c4df7ba8d720661ec431a43fd084f67cc8a9d42725e8987754822df82669f84c9edda0e15f28e0ca699cd58f4a533218d50223b72e202c1c6953b497ae5a60944788b777142b06f5a45278643a056aa025a0dee108dc505aaf50dbaa979328b2dda060bc8bac6ed5ed105d975be96abd0fa847e1e2ad463ab84c5d5a6b21f301bf53867bd52ade475d9cd1b32fdddad6513689239129eb489316d01bc546c72aed4e8e8d1fce9b81db42ddcee0887c19a930b5f08e68296a458e6708ef92940c8bcef2bf027844057d3898a2691992142fa8f12066320686897eaa948376c38b274d3b53cef3b8d40581399e246ba94a842123355a3c546a508d628aaf2c15193574a1eaac9ee2ccacc349b40ea43d38871867015789b794af14e8b39e3a9f33d02116e8be0fba6f9b023b1a9502e5351b41c5b72da4b3a0f22c2713a5b02e1accafeafcf46ce9d5c6d91f58d35e0da21941b522af3d03abde8b8380bedc325d3f02cd0b02067f94579c07577eb54dd4a3f2696067f1998e5a52a0a21e7b8d3160f94060025cdff17e555ccf78a6f85d7821c700d6387c3f6c8dedbd83e660572f70813a33cf2fa4749532a7d69045bda51e933723babf3becf2e76a368092947ce33bf657d2bc350319a2424ac841f2842d36d72b32ebc0568731e9804a9e7d8ef9b99552367be09473e2f5eee96ed756eb0062e17251b1560960576edecc4ec97885917c8188d6a3bb781dccb93391541d9aa611edae6d1964b008fabf9941854cdc5233490024bdf653195dda2bf40438a62728db58cecd90992dd82b9f8565a0628b504d703c71186d3657f4bb261a4ddc272d3ecaa87d86bc3311822949385420abb5a7e03260f399261d82132844422293c20cc55015fba6f8de14a838481b53435dd33b6605d0eb622a2dc2da8de5156725f7cbd78065f3e7a5ab1af0ecef73c8a1ed777dc1814b29074613a0ae7b30172a36d9e2e1322636e9e800820a4365f2dd02b3bcf8797b16cafb1c157d64ee42186c015404dcb88687313f10fa23d53efcbca6d8ed4e51628e67afda262eaad5e0bafc5a8f32ed10872e8ed2cc912b7f4e65264f03522546e6260d543c107521db650fc02e558dd721a20f9e4c0966de8f1035f51715401b2f0be270ca8e65e1ba0e17d4149c265701c16b35fabdad4aba5e91616d54cc8aabad77fbd2fd0956fa801d72614afad641c47ffc3efd9cee6782203ca5f547c762a9d0b69f0047d726815a0c1b4616776e577f217cb0f794b959a61427edde6cfafbbe121cffb04725f23d91cd4a40f08e1caf373ea7295042bc3b7a5eb2cb758ac258ce25572da7232e4cb030585297a8f256a8eb814a7715dca3fc38124311ff89f221f31c1593fef3861db1a99c0c92842583d394f09bef8d165ddc06686697bc66e4f6fa55172fce84f83356a82bd5fd0061dcd95c81c2649d2a88cbdba737ee93071d9bb55729ba583c4e50aa08befcf4690ce0ffe00bfef8419b066d6ccd3c8b0e917e0ea15524e77369967b23574c359c54bd85aa97e1becf333cef71bbe47ed08d6398172cd86f2f5ae1d3d84423e45015c84166c6df5e9281b6db2d53f73ab934bd8f3726cf8ab7a70ac7eb4211319d0cb6f9c86e7eabb93771f1776b8ab5601d54c25724a3df3c1b008e4711f9790e8d6bf23bc5f9fa7a6ff9b354dd2aa57f423006572eeba3a663d5ce46d22b87bb6f3f163a0ac06ab2a21cb4034b5d3f72bbf43c6724f98c1f6d8b6395aaaf7c3595e45500af8ba79de96730d0fd8d4e7a3b8ad1e727bd2c5b113c282d22ffc9efe4d87246a74ff8d90d5345bb5cb31da88f520297258c0bc30673143ffdb8d2edbb7e834d4ffd5ae46edfff2cfab2fcc1b238c68729f65d3acdb8f435b07a28336ab0d4977f091d4defb2b882c05757ebe5db78e72d1080132e2e89eaa565dbed3ec2a33b18bd879fd16275597dac4c717c38d2272c6f2a0b819d91d1db24dda4911a7a999cbca52666bb15fedc169a5acedd079506fc7875846104bb541c588689f6c9c9760cf327d1b42d14d26ff05b05c3fd3053d1692798d18662245d473b12253ac0bc99d4ce3a38e2289f5a7d75d94e76b03e3ce359d456884e49c42d76720778202de558d77a9d54deef4d59755d8e9ad07d050997d467833af26c0561e354a19facd7aeea7623551971ed590631a9c32720928ca00286008bd50ddb6b92498fd5445ce866d31a500a354121d5cfe9fcb72ad04c4126bccd3352a593c36114c8f44f76fd5a1a2774db25dd31d85e2508672b340dbc31b649a435f728b283be60c7f50a7089bef7eb7e52d20f878d63de072e4969d78ec4af17b17cf6a2be765925c55ccc749b92b85f7cfcead5b5ea73d10a806bf4bcc6e527766592f5206e6d303679fd0fa88286959ca961ee3610edc7238265cd9f3c80f3aae59f3a9e3f440713b6349ea9f8e30bf3b3f07cef08251436938701caa75de8a4a19d84b44e02bb0ff576bef03b8568d71325155beb2e97229bd67539f1d3e255d8519b67f4b5fd32bc81f55cd64c77c84f20e3792e8177211b3427d3d912b171288973d2a3d9e6aca1160ba3e999572a7d48355b7126572c0c7794520870ef15898666b9638ec2745c3ef6af20ce7f8e48087d6686e2c725f72f325accde6b3fe1d0a87c23c1677c6291cb8a5e51f131c181f71f8e4466695c43dd3d0ed8f5692e773fce242e9729dea88be5ff186b29ed5726e89c97972d5d087e21d1967d2ecedf29913fd9fa8c63d22425e927eae3273c77915febc5f98b3b095299ce05c949413c345ed3b32af09e6460d92d17c390dcd6f4a6764276b50899f3f067ac525a314d0bc0a97a6eee33fedb91699396dcf7dba3379c1721b2b179b0ac45574bc86a731684a24366bd6a3911f623c9bbb631db9ec38dc72886a597ef4c34f0dff717eeb361635872ea8b7a49f16dd204fc5b75ef1d04f578ada53767350827cafbe548ad05a724cf1cc5fe7463c486991ce54e4d643957262bf42034e3a2d268a3d9703640bd25aba095b45bc92d74be1b708572d00e172ea81b3dc16b9935e0f8a677a1dc0b26194a0f5f430c422408e070806494ff672bcbbf430f9981ed5d3894cc80ff41a10a67cf61949c556f7d75ee2ba4d84ff72c2943b5994eda591a3d1a7858599cae73d0d25cc697492e2b8113e8e1ee05337a068ff6f361c6494b7b2219a154fc25cdf507d6dc0bd0b1db6ae27ea62ea0472907573532cc55797b7597eee7568c07f3d02bd48c9e7c70266d98ddd9420eb257340e7fe8c71614a3c1b7e3e0c9fdb0c04274cb31e8db8be515669f890a963726675d8e103ec5cb68b4a590a0af808a80f9ae89d1f44ce9959f76dfcde18e022e2f549d128e4b6800ae5dfa35e89cc099b207df8ec58b1185f5c008b16e9d15a667c51f6129df077426ea24887c2346613ece0d177a792d8c0eb1b566cd641729831e2ab05f7dc10699636107fe3967542bd8786956aa7961dca85c7fd691a4f5c9620b50d6a21881f75ceae065555634f58d18d3e8f9e3391165a1e7b81cd72f0fa0e2e1526b988dde15ee3f35485aca89316c09356c67d4b769638a6f63e728275d33b373abf83b98dbb519707647a712166a716d9fc4211cc435365d0192d7155d5ce6087058f73f199ec394b8e9c77e8b883866066ae5fae1d19985e8469edd2f4e8716b464197b9187ec3971647d7f826f39358860f85bb67d408bcaa6050f100ea2283149b4ad226093e228c7765c18424e5fb74eec473070d84213772aa1c4630a121572b45b1dcde07926c847d7eb1875a0695900a11813457f900720eb6bbd6b52a6c75c233a61ef446e4ef1f40e2f3f3eb23ed920914f538d2fb722f79e599853ea4dc998b0680e2e3cc5e518cc4ac74ca65588aadc70f63ac6c01c9ff460feeb3459424915fc93bf4f72aad3527d39635faba289c82aeb44ba63ed0d0e119676e6bc974c8f944ee905c3ca885de78fe7ab5e92ec8af85b889030017bb49118f406700fbe7316b255e8d805f1b5be7aaeb172bd742801bbd25ab72fc78c774b5335c08cb752b0fd0db6c39833eca9f957c01d2f5f68004ef4b7972477b28558348dc072b59d446b1a266284d85af4307c603153b517cee7123475a171eb09bb116c8bd3470a6c96234f0236e8859dbeb94751cc9d81082d07b7372c0f33a3bc23ab9ee24a2252dc3908ee54405476ab653e5a944b9d532a8b8b243177718591973d56f2c2836d937c76e0f0d6a2dc65654d4619a09f3d9b0371b726c1687c134df1b62559255b8131028c006320ef746b7c666118ad8d8147cfc727da89269ce4f6aad16b001b59ae11b946e8a7c4e11106cc6acaaad33a9700d0fc9ae3e9618ce6c056a9f5b3e29924829916958b21c425a9f4b30e3ed560fa907b7ebbc5477d5525a5385361499d737c0b9c10099afd92b50ced745bb37d75c72e6505ded95a93baddf424b81a77762fae868d1512677edae4ebe49f1508aa2720dc1006efd0853045b37fcfcb9656f4eda1bb3902332769bd30923e707d9886233cabe65d379b7c806aaf70f14b9e7a773e81c44f11e3e9744c28a6b433fd07263a152242a3918343f890afeb1a23d68a4779e362c214439465d4932cbde5f5b03e1144fbe977d0aaedcc7b17eef721a01794bdd29d09f60cca32760062ba472ad609c2c91e0a31cdcd97fbf47de55236b1b2294bcf9e5ecc5466a9cd1ba6a728371cf556dd2eb229b18241a9aa88fbd1db46d73f7c6c1796a6b697756fc0135c92a3329e3955f192501ffcecb417d959898770b9c5e504dff248e5550dc164c0b7acf1eed5e630cd4bda2dd603a10bb1316bbce9e71004c31c31111773f88722dd70955632cef0df0223186a59b79c899fa9af55135639c8f6dfa3fc2b0b04d1a7d097a3c268d515d9f67215639415e9c9ceb026e82a0fe5e41b8e655122672e7c596da2e8de9c5c698849597aec13bf61479cec1b048adcb0c638e9f479572893c6dc12d4074ab76f358410f5b29fe1680d9dd41bb56cae11183414b66a672153b399eacbf9bf648189d53d153ccca56e15febb697fad0530fb1137e31af7241416ff02af167eb7e86019092080753bf7233cd373e13feb74ffdcce3f5e22f3aed626dd80e80c3f25763c699fee477bb1670ea46921f9453f562450a1a6456af22b5eddcfc255f6ff6cce6143acf97538751577229fb3ac81467d751959955f9e30830a248a28ddd46f368e5c7e810b51cb936a89f6894a69cbec22a9d1f06b85a3dbdd901f6ee01a44a8b01b505f3b1f2600d02a4d78190ceb19e1529fe0254c7e48ef742e7a806d9330e9893017203dea289898d96d14d5693f7071c737249d49c3548b50e7aba6ec2c91920413c3c9d3c544edea698ad067bc385269a3c95e79085e429afa41d5eab1ea860f0929c54c97426575b7a576a4d298e8213721305d2d46f8727b243e6d07c454af34c709448ebec2f519e00489b4c9872a12db576b0df50139489e062bace4854fc9f2a14b617696a990aeb22d7c53abe150fab2d82d6ab1c83509785d0f852ea033d0c79d534b70adc909186dbece1e8b87253c0624916aa8e34c2930e245c0dd9c5cd4c02ca3d85ac8dfb2d54214eaadb7288b74d1a0ba0ea02a7e13b2d31933e8b6b18d24ed8e1970a61fadff52757f1722686a277c83a9e4ef34592c3765de0d8c4800a464534ac135bc6e49633dd446233da4adde452e395a2ee76df361433bf4536305151719a1225d2a1d80c8c3972620aeca697d9b45546fd7477a0f53a65318345a5f5fa3d18ad4441669330ad11cb262b3c67976d81b32ae0156579c88ad44ac4a706549c5177192b0e6e9f8a72ac0826467b672178de12515f6a544d8bb851acde94189243a12e897766255c728a3f3b344ae9752dd8c90065ed57f0e00ac5b30ce23b882386dd152596136c66d7f3160098d73912f8978c0392c836ca7790ad236215f178b20ce5d7ab5dd6701b0b4155bfabeaa94a732224d4ebb453707fea69f7f22b671a6b73f729ea3b725112dd849943682b87190f54a6b89373815025a6c05a17b0b71489933e4a017215564afbc25f76b019e50a90e1d1412d7e9699bd513188d9cb4a8560406eb072224f481340e490daf340d2c7db3fc5f7439ed1799b7436d52481d9030c8e99606fec29b1e54bdeeeb76389a1ae4c120b56a836d9afcca8bd692781cf3e55be720d528b95227b26dbeeec71b71d79424dd1e25c50b8a76f878c4dc5b930f9b96a531c47ba54ad9860e737ba0127b3e51eb100682819a7c43dc73e344f024e94728f666f0829050fbc4c12c44727b18e8af1680f87b9217e2980859d8965e978724f0a7f289097216b837aa3980841ff2910979761ffe4aa425933662634904f72449e159dc81ef2dcb516101d522dc1641266fd60d10891f87589913da78b3c7271c047e3fc0c9880d937a4a751d500ce2489fa92d59657474f8279b1b43e2d72e11b4328429bc71db33a96e659d697454e92149b129f880511196636f64e7b72dc1769ce4aede806ed0153d26fbd14ab9a0b797c6d7e5724a6a33d662f85c943953c0334f093d8811e471be807c13af4bbb2f7b877a369a8ad25f842759a066c02eda841fd93f23aee38d1d13ae59c07d3c5241d927a8388e94bcb1f730b2c653bc71fa259b2c6fe731aaa3e6853cb8293f9561531ca94ad32541ca7626ac9217218a0240d18fc604d76815affaf949ac5edf6213ac42ae575fe89115a43bb725789ec9a3318cfe8f118b10275f58081190048f07f60e77676335f95766702237bbd6ed830d86c9f8b85713eec8724ab57dd113d66b4f571c0e26a9687408f72bc7465c3d3f948b020cddd0694f563d1f98176b7e19c75c85d038fe9095ecb563bb435f4a47c65a946291685d7cc6f9652771f3bf8eadc164614b5c3fce1a5727d2a0beefd7f2e1bc9a5b888f460a089f97158008828d152a410236e9b6aa41e4a1162d4334433bfa8ec7d052b5c4cf88a87081b92573e72b99cf21d0522325a599ae2083292304cf6005545cac77859c1d1afaf7f8d1f6412889e6fac456772de9bfb5e6f2f4f18ba507fc3eabae9aff9a0dffcb70dab93de59baf8b49ed111dda9f3853c47f935820ed1973c03fef60386933e240ac0f8cb95cb74242c3172e7d868eb5656f25c241b99790ba8860badaf8e39f7c85f4b40d86dc4bc6f95721f21628a45ee4c4145a5399e64350fe78c3e012e8589f5ab0b4c456645c16c1e90a885fd95fdac04ca313d3821015ab790d3762f009db715482a35786e752572a56447f5afe7ccf0925dd2f7a0d345998c0902c2c4b91a8c8e02422c0d31010a6fde2b851fca95414ed81a3e3d30e362d14df45062d96dfdd1079faeaa65ae03790e38560c122fb656a28fe5617387c39fcb3d58cb9237d95c72936c64da4072397aeaca500883f6fc203ceb84286f9aa49e0a724f5c4906122676907c090c7232147bcef12d6083a3d9e22532e60827ef1abc9d29bcf072431229a05fa0c672cc82d767724d51fabba0622747bade81596ff381a73322e2944f9ee134c931721c6b23fc6457d744dc53a4d0838a7eaace1179a94c002823ea6f0c7d1f76d972e3edcdd519193d8894732f1d20102f96180fc5b6e4779e506c655e3c1d9d12217ad40bb5695a6e99d946b996ea1e64f304cbaff11c21369de0fb8f279b73786c8624e91f90320f1cb2314f72ac9731bf6d1a987efc6b9c283c5d6575feb410722ec59372c57a8de5479cf3f3cca3666a6e966745a4a5282cbf4802820c506672ac4e52ab93731e07d44d1ecfd1e6e2628be4b85b1292bafe30a4cf1a0f616e1eb2ccfeddd418281cfe028a6696ed0fdfa8434129bfa3586d69e83a859a0c580241090d84e14ca87edbcd1bba207759bbd68cd43a7c794c5955e12c0fe35ee6729016012c7c116288ad04c1f51715ac01734fdb9feb248af2efd6fe7c04d66630d7041f0b16dd1ab7ffec9aaa275b15ba768974b9081913c8d32e2fc99bf76d5ed8173e6cb0a8673a3cb53635dd52d06ccb684959f3e37f22b44e51b26e466749a8f9aace38bd425842e5d88316bee8eb4fb868d1928394a5d7951988c77a0e72ea3ae6ab02468c847de72189382b579b57014b914d7854854e1e79c058debb4923fdabd0d8ee33804555336b2ab18d42035665ab6b04ff3fab70c8b6259a8d28b96507b8f4b68e18756902a84d9a1882c50bd54302137a0210a3bd238bdf0672c035c0f6b652af81f00ce64b3ecbe64b1d68ac90691e89df82b49fdef467a23f54b7d9d6e150a04289991187893ac1122cc9d0db99a00fe81b2b5fc02fbc94055a588d599d708eca851f9ad491bfa9d08f5d66bae345bd8ecc303d38fe0bbd435bb768a14b19856e4579651529b935b647718027e69efbecc14aa5194d9d9472770a359b82f8bb9ff13d0f61b5c05eb491f28c81bbc2f749ab51d32fd71bbc726b5d19c2f2febd2e0e94eb4d66a7c8778879318d3b360a0d49fdaae126197d4d08ec0237186d0ff5decabfcff8016ad8471dead1cabed6a7508dc835c362a42e532614a235ad1100400152cc76107d336aa784e16aa9f428a07bcd2fcaf2556dec93cf1803d2c860001f3d52382a530a969e70205f647eb76676cd9323c57172247276ea0806352fe07cdcf7c63536e24bc711d4b4832e290f73dfa32f0eaa720cecfbb690bd127f566416795bebb791eebf4d11aefe2d99a604ea42893f742009383ac84bf837309a3c80646a57bf8c68cf80866b3f7f45d2c468db98000b7204fef7fe21c0ac4bd6550653088034eed610e46d02a356b6d942a77fbd94510213641faa7784725072edb36885d166d9a9d1e5dbc0bf3d91782b274cedb1695748ebce99fed09de59bbed68759e62d4fe1ab7990719fd5ea5c5ccba645239d15bd331ce947814e5967a908479b8f6fc89fc5b22d2df09acab4a78ff3cd6048720cc062837a8124e1b79a6f2f91f6ab8f766c2ff80c8a1b2a4e9658151a71c272d980ab7c5fa7cac8cc3aaa343f3d315eb0580323a41d1d4db9c0abb935c08a723e72aa5b38b0818ae93f6921a0a81d2b4d0d6c5a43d6b81c52f4a2e6ffe497725808368582b9cb17b3c2d05e9c5c9df0423de1ebf64c1d72e788b7c71ef72b721c324173fd8f7b1472247612812da113e4cc0015f84d5e77ac3320c505916372a6dd9d0c2228f5df6ebc1c907574aca1332788004035683fccc8fc768709f45ae7e9a63b31d7410f0eb9b48ca37accf0356858731decd41c8111b420e92609207b18c9496ae358f1e6887cdaaaaab9d618a68173e6cc23596c69ef2620001b72b29424f18b4ea8098dee5a24992fb10b5f15ee9c936e5ed9ed749aafa871e267e1caa663289278de7f2290a99bb3dbb716f08a41222b9b05fc69aa794f5b1a72c9e27019610162289345c7e7cab31035b4c911c5b9fa59648b9ecf956cec0d5ea3f84b57ca8cf12cae632f525740765219227c902e691d0b73431217be200872d2063d0eb776e53740883f753c26c772da3737b6ed0bf72bcfc3ed8c31dd2e72c984704fa3800af10f2b8830e167b600066fd2931d0901f5f02c159cfa539872364c49b9b78909d8fe15d5995bc687172fcff86a65bae57989620867f2008a720b981ad66cd46d21b5d6962f6437ad42f12865db76788cbcdbcdbb7271368c72fd1bbc65769f79ddb7629a5d5d280a10ffa061a6f2d03edf28fd5a548e812f72872f34d3024a9fa1cfcf11af264ab56f422d48a42f303513f9330827ffed9d140b86aa45f97ca32c3f2a519940550165c9ec83556b4c1026bdcd3965b036bb58bfde9a19bce82f0739df8010070b8c73a80b33cc304fbf17b70eccd2f950ec725837272b491a8cc079044e51df5e09a7d14f5890f82160098d7162552c062c72591dd40c1139fc4985a0fbf8c7751fd387b192699870da6ad423f659d26410729cdf4af5862eed545ec9d66f2a895539ad6435ad3f75438cfe7dd72b59eacb721c2855b4a823d491e72f519fe9f3c94cff4f0b5ad1ca2ea46c4c4b3092c613729981c5f118fd4cf80f785457500fe866ed76764e36942ced61708d5730ca52722353435380853d87ae70b8a077bb74a53f8a272615c97f87da0807f74790ba72749559fad13a2ba511414d172088fed253b1edaf88e718d1f52d1ab2a243b235d5fb55f6b6de2b501110d9b4744335812790a3f523dc0dceca7c35e4913bc362fc071b44c157952266d941e42ea8dc44248a3637bcd4fbced4f1e931b7754b67896e9d93b4025db83eef6f5b2f31627136bd9fe5de914a7f8ca49fe631357e475fcf97fccb29ab56150bfd6f69de0e85c7ea89fad921c764a5c2882c94a2bc7252ffe9be51db0ce17310d800e27273165628ede329c8421219e59a6039632c722ddaaba75e3840731b2201327cbf473709e92c15d02f3494e50b87fcaf27a0723d9775d130f860c1ea580c0fe2a5420e2e964d2b2398bfca1f921fce57d77c7203498f2849d22b1161c6de921846b90aec9b7f82d3878602b6a86460397bd23e4505077ffa851aa1811adbe0beb17684bf10367d2fbda12ded232d84161de8382a9101050a54c1a358617a84ba048cf2cae2b2db2d33ddc34b3c92ae67ad3072807018e63f0863b99b4c58106e7ec19594f68d86313d9e9cb4419516336bc850f717e789f43b3e59a0e234f0503c8b0d59321c19b322996fe4d28bc4d71cd53276bb9dbe44b688a07bdd7127e75b71a2d1bfdb827fb45341e1d2ea8e4f50ef720b10ea9fdb1dfb235727748085ee3ae823858cfbf1d3e18ab2975e0b38b6c4725f8cae3085533845ef434b264f1bacfc4146c0e08b5be7269d96909fd78460722a62105f9441eb168788d82f63a1689a90e1d27fa80d5714ab396399d677222899fd44a75f197bd06ffbe447f9f818e749eb3bbcffe05cdb855c432f6945811180be3a6735bd43f51e1e5c1b2061f1426c25092fed872354f158c23b6c73337239c0bf83a08319b678cdbb6c0f2fcd54cc866f2ba4cda8a39c286f18ca131861e4ead87dadd3bcb8335f38bcb76ab1bcae2373c86f8a46afafe48648df9fcd50d7e935516d9e2bdb9400167c7ed787826cf27d93f65fda975f2f9f8d634e7572fb5ca6193405c1ee83a863141ce0962bdf409f8e6dc943e0123b001469478172bc383bdd819371ae52e308335fb75be2b00667ee910c852fdf0052c4e1101510d2ad5eb66e38bf583bee5b9e2ee7d886500ee0c79e9a57a39c02768c20466972c72c707c1a475a5cdb05b98c227cd42d5400dc5b2c20f15133a6f325d439a172bc09c99df0a34bad0f11fcb2f040d540ff45ba14b36606158591dda4402e58723b3258652a750ed08d772b98ed52bcbf4e12f020e0b4c30c3c948637312f433011535ab1f027ac24ce148636770927d65d77f006ec3c52182b8f4109b47c9e729455366f3f6fc958892887eab280659d654ac0cdd1b19856f6cd483ec598f86f271e783f691c03bc713efd435897b6b9e69abfeeff001cd5e64600cb86646972702e47074a65c4b3acaec0e7f1b10f6e1f2fc4bdebeb3b57e8d1057ba864a0034a60b6d57d58c49e431cb66ee0ffe101be618d19d793fed44154c1fe981e2f7249153497e52dc74555af308c83b20a9d6d215cc0efcf4357d8e78b7de4490f248d67a6be49d3e7f0b8b2e50e348d6dfd0ebe77ae8a3955262aaad36e2571bf1b5ea4ab73848cc7f472af0205fcccc5d1da3e54831f326dcea2e8a0f7def8c13c3804132d895c153c05169bbbb1c398863d0482ef3a27418135cd651d80457672ec4c5391f8ccb5d414b3345ad7445250bfbc7cdf328b114273a8f920a2702172eb6c7ff6b7a90a4678e1045539c8de3cf4a3bcbd5ff3892df96bb26751b92c3bf26e92c7ff348e38ce3aa58234af07ff82bf1e66edc1b30298f15be056fc100273667c1ac0fe90d03ccf14f2485f47b7f3b97375063149f51a768c1a53b4e672d4d177e9be3d5ce86aea7acc24dc1072734ed18e670afd5703903a0c562344728ac2c0379d235bff93dd1336fa6e8f70bfc58c5fa5f2b8e8340d3549cad86f729879860ef5f1bbde25631bded8c408517757ad2d9e23e774ec7576ca7a4a364f9f4e6db17405435c66b2cab9796c144055f28db6f49bc7d0928af84c9db487478e9310100fa2a93dd16230b468d889478d8e4d8550a54da776b3dd391ebabf6446c735d5728a755e95bc803f7a1c83839c20aaf1fa33673d081cc05e7a7dfc72af6b54dcf285fe710159215bc5ee40ec9a1eaca53ff6019c948ceada4ab08572438bee1df71767f31f42024318383c6979e5776ae844d8eed488556a470f96728e4039b63ee45325caca3bd942505438a3a54731ce5cd79a7303baf8c5007172d35f7cd6b652453186fcf820754c7ec8911409354ade579f1c6e547935f86435d1ffe1190388243b73fc1efb0865745b31ab12eab6921f94cea907b661f3a4466e525704dfc75434eb67952e9d91c0776d827c0c32df94ee36361ea3c2b98c309dd92db51f1cef82923b6a073f79608949cacd1de88e7c6efa6503b82ee99d41d2d925b81704d959a7714e02375caf6069321600781af8a96f4f2aebdad0bb54e7b040f9550189da08b26a83a30379dbec386fad5b8863f80bd8f64d3d03fb72d1bb0e550c22acc3e5ce16ef028f9c3c7f06357665b4ef1dbcc760aec90b107290814b88a4b2d11a00f34248b47d92b906b2bfa63405731609bbc83ba992775493f0acada2dd00cde1fc56d812ba6968b663d027610b31be14a502839055190a327191cd859be2301c2ffa93243163aede1b379da2c81cc7f186def49e59127224a835e911917fc142621c5a8a68cab522dbebc22343bed35c5c4dae4a37c97274e90d904d083d912d3db3bff204aed1f7cd7ae48e25d1db96229dfc4d9e9b72ffce9a51ebda44e0dbedbd27b2b70a7d5fd7fcd2fe0294cd0dfb6a07e4430f2688e3086c1b30689e79c2d7dc2b96ccaf160a455bf91e429f7cb370a5887d3d6e822e5ce19252ad89deb887878a57ca9b20db0e749f152d8df480dcaffa45db44534724d4992f58d93ff9fba61ec108b2075e132bceaac466e7f5b3402a3bb65074cb3b98e7b50c2a3f5067f90bc8a09009f7cd10a669184e26227e4a5ed40f7254e3e9b256bd48b369a4fec10c54aebe3dcef185c5ab16b13bceb714008a8d726a495da0c5ab4491cb959e1550647f3ddc53d7a225554844f30d7224fe261b72ecc8ef9e0937f67edd55ae5c71c8a9733c575d67a0093a53727ee9fefd3a0472354a794788389ddb531c2de8639fd7f76280511d37b7ae6a28dc72f349fede72c3cbe8c370cd2453c9c3bd77448e2efd5f5a23b7ca854219c402299bf6a0b75dd50612b3ff0954b801fd8c02a2c373767bf6884bb62e686b46e73683aafaa4726afae65d049c10fff19719ad4b40a5b36434fd888f7e82fe3a1cf838c9210c4bc3c0c7f7ecdf281c69bb3cf58502aba001bf85d4df5aba369fac266d46f0c472bc5bea638b6e5b40cddac5eb7c27e7b378f368849c1fb25a8f42e0dd80acaf5d8ce7a4ccb881f57caa7c46c93f5c1fdfa4baa4b8ab7797e9c3bd260c66c248727e9216955d524bc7571d8f1509bec070162c5b0be1822ab103f9a84d44541672c6cdf054b7620a2efbcb699e106fa21740f79777bb992906a409afe01d01e4310d9deb9ee85b024fe3f4593fcd298472f227f2b30c81dacd585e924d8772822d06e8ca67a3d31154df85e1f1f2b7c1150f96323b1761fe7ef815d362c0622072d8cb405ebe9b761e1b05ee1bb5f2b07e4c727c1971512eea2f56d7c7c5d919725033ce111581c6a928078483cc3c67efe006f0a44acd073f5623a0714f53bc0b4ba7ada833cba6abcac6743a9b333e93635f4c7f740d4a1ba49d11021e6287724b8bc043a1f4c3c131149324539fd5a7eef05b31e264fa11e5a6065d029a8472305fe6be1a470a0c61a6430ec69632330619ae42e26c98cabb0ad613ceecdb72bb0ad311452b3707e4a5285db5cf6071f6812ed48a06793cef414768d3bc204a187bdad28b3574a105f5e3ce84dfea521241a0f4f9808bc63f29b136b30c991609e7252e168807383fdc62719bbfe61b6a7a1e25f6275bad8677b104a917e572a75cf85817680c5d08a2b8787d750c2766414ed140a5586c5d3ba0a40993b072cc173738d628c0da3076284f33e235c79a04b78bfcf88fa44aa391da07fc9e6afb096c614d32216beeed051052f2781e0652cb64fce4dd27ed8d05e70507b672a9bce602c271ee7c7111939d97478a5110ffedef94e6495832bb954005ef44725b2fb757ca1bf1245566dda751147bc026ef6cec72d9a6feec5f7cddd67a1a72dec88ba0e07133507b7f64b750035488c629086056571b6f1db3a8754e7e993baefb15b5fd1f6ade679fe0cc1437b88bcef814ec85448f7478be541a3bd236725396de06e661ae0be714f6c78bd72a03c63d294280db3d8bb341e17785ccee6dcc91975326c2bd80fcf0344e3ac0e8a67c2fbbbe3874216b6d927ae6003d7d72a19dc1d2783f80c791ae69fac309ed5d7b468b82b8800f80966414f524f3a111019e6b76f7929c320e6737b7264a0fc52014050ba4d4e08fd08c59405fce2072fb89d33fb007603608d981c47d50148ead688d6184346921b096f2a317dffb7294e8e6a593aacd514db11e00dba17c19347b97b0ff4b6ae0f51cf67915205809f44d080ff35746d0b673d62457586e0a5effa3428a6ff8cfc335a46cdf28ee138b13d97e4b35df4594a4c901bd5ac376f4805d70ee61953986066f69c2d4a6720a08b67f283556d49517c339c4ce50e7021d1ad3a45ea439c92de71e27726172b85113431b91e11e1ae00fcb5d60998e38c3f682d65de088bf02cd2314b4e372d8b18b43288a341274cdbee6cec41c23e614ecae586efd57779537be93f03272a40931dbff6371b05e044face01b9161d91d69b30402c84a6b8c99db68e1fe7271307bd46400c0a3f0a64636ffc4ca585daeb225a19fb90098e81ea4bfb86a7273b96b2b61d7290ca4667a42c4988dbc513753d0c8152d71c5a41583cafcee72dce36aa7f9c37993d11417b8312f757afabc4a915fd13eb529863b46ddd88d728799e33a942f2acbb071edaea07b4de2b322ca01f6d50e8fa73bd92c685e3d723dcfb1baccc1613fa59df6dd2e819b408ba2ece8f2781e195196836aa13f2537a602b0b10daa812e620265afc0d0bec220a5f7d03a07f4e37e26246ec4ec87722fc5528e9867d682ba74eebcf9de4b990a9021813f3a5ef84ca97fe1a59fe438425fc51b1090523b69d67df7c6dcb7fc43c4c90173eeaeb0150450cc1f49c27264c9df6f740797d518f51b8d43692aa23f43caf00c75435f57ce1341743c667233c44e724a7b78d4340c4fe71c0f1d7c8c1e4a5027387ad0b892d18290ed8c72b2ac563d5fd4923e96cde58b64a4fd91be7cb04935292ae1a4bbc794870ede72168cad144f8f5b7e54389f44459c576b9daadb36cd0169b2f0460f792c934c72aca0d00fb0999fe6879ff58885965f3bbab759c2636680a8b95f5bd8e8faf972a07c1d4aa9f5d01a940b24a36cd6602ea633235930b0c37799715b83b4901272c93ce12baf01bbd090c984b2c54cd90b0d1f32a1f37f18a7d26ba61f84ea7c0fca8d4df2ab2c6cfe72004f5ed76d4d687060ffe82d3c6d7d80be18db95cfbd7262206a688607ef2999ea4e54af3703afdecf81604913ac1d5681fd46b77b7872738699ddccdf1c8042981db4d45122d6dba8350aa786fb4b4d160d74a6ee7c72412e697dc18c277f04622f37bc9a6ea4c3e831ec1b809214b0d3c0d6a0fd1972edbec81f2139c932ac0816b9ec63ee58aa34a2c36bc74398471dcf50172c065f9d1b917185f08e79f43f88f2fc449f5587cc67d194c71e8010bff8e0fcdbdd2e922201437c095765945a060b2f84d861b45ecfd2c41dab5aa5bd8e6dd4776a72cd968b4cfedeca46a14f92dde3675d5ccfe3b277881dbbbf8e510dcae8b74e723f13f19af47c020213a3c15a8186afa20c49afd7741bc64e16b2ece7644efd723f679f04a8327d6142131f7d737bea711d2a8970a7d55bc9fdf251878752d5689aa04d48ec255083f4e61a864543fee08a8d076ca42248222fd6a51c96b6521265fa3874bba9d72ae54fc5d451040d8f559868df4642a17396b3e2da3813813395579b3e0237a8678da139f4f671c5c6ca3763eb41d0ec8ba992c394cfc4bf72e6fb69717fb5dfb88155107835b3c031e81c43ea0b48e7cd85955e2efb51487239e73b186c0940f7d851d2c334cf555fdaa48af9737032bb1af8bf5f43e49172a6d349ead957fee82d2a75601631bb6b680fc6e477d3c6327985082e6058c8728ad6e03505fda51c4eadf28ac1c941a5f06fbf6bb82cce2b8551b2d97d0c14725c1f157059a3bde502af618482da1fdd5193e800ba22269f13321579d1377d3e4e4bf1c961ce014b6f8bcf0691fadcf5a724cb9a337beaa8be045dfaa139ad39b6bb2464e6b8b6c1a23ff31a71191a163f192e30dcc6bf8ed4494e470b084a729fd9c47f7d3425d4085d21ef149c411a9aa1a96583cc0bbdde4e31d9ad61700783bfadea9cde14deaecf3d47538493b7f100216e1f8ccda30fdc4123be6bc832542e3139f0843fa75a348e52e93acaa2c7fdd98f9cccadd29ff4a777d575b47227091b2a83a9faafef28fc66227210660917a633f24ef0bdb2793a53af517272a13cf9c1cb70a747b19dcdd19d18f312fc1f887c2a7812f3e3b24662ec971d4b2aace306085bef2882cfca05d2fe1fb2ece6cd384c1c42c0130d966df03fc27270710e1a649b7d9a01e6f0c4cc544dcb90d0c9cbcf8ab31270a74a30de999f72f8ee2bb0b4732629e18de6dd9a0c4532ff282c18dd368764a5cd6ada89769026eadaa9bd3b0b3cf4edaf341fdb944b518a21824015a877a3465a76d0eb3d3772d5ded1e9efa73f6d0d9df963e696c3b105900d82ba77a2585f7b2ff52c11ba358cce21dffb322fccbae43a8e118b2d05942b418bfa1ea62891c83c21df957372314c65388bd3f3bc1514b43033ea9a0b0a086f953fc80d552864064e06a1c5010fab1c93fc963fe3a93920c7915277f20dd271a8e43d6d58ab6e90c07510f4723638ca7dd9af570fafbad46c8b1fd5e0655e31fa1412cb3326fe94ca3ac5d17209b95321ce1b3bb42ccbb273d54cc931e96780e7bbe92e32f25bdb54cb18595a5f25c39c8903e73f2499be222019aca81c0635fc6fae96e4b6181f1c8f169602441314b919c04a55ec919c2dd451faff4d43ffa33e504026a4ef70432880eb5da3d7508cdc2a4b8d726ceb9e8d4e4a750b18d14bf16eb045aae07ba4a602cc72146aa89e70bc2577a66697175b83ef3b9b0a9500b4457a260dce1b2155f2f1720ee0ffd487f7318310e660fddf3d578717a2a93dbc9bbcb922322d5a1634a6724ef4f9d223035a2df8ed56dd37564e454d3f047ad6ff0c38181fe2d0b5749e720b7816f20004b8b3062cdf4369927401e68178062e1bdf3870e4177a3f397a30930991ae1e2c575c3f210bcff5b3bc8f68a66ed7992632fbfdfd97f793980d15cf26e711d230dc9fa5cb15becbfa2fa00bb4cce1799e0c4d79c2a8c6e5050b72c8c4017503198005bc0b9ec191e8ed2074c7c335d8f1557ed11d6ef8a98dff7270235110b0ade12fcebc88008c22fc45ec941c505f9125e63017f88e5bd3be7231537bbc3e5d797f72442a72f2e4e12abac761c4b68a73f812365a381855e072a4a6e2f99c41edb462b89dcd8790cdf849bd5039014938e62a1229b793f78772262ec9cb9874946a1006ea20747a3723cf496da22da6fbaaca455ad48daf0f72bd73a1c96611a693024cffe5add4dcf928d167298dd763a056e0c2f5c41290028ae729d3ad9b8b859d09bf016fc08d6151ab48956f5b4b4a063a3877c1de5472f5e9605f1f66e9ff98010627d94fdb0c85c19c0ad4f05b7655bc0ca05b17a34832b55c74460a17905a50c3dcbff0630f1299a65a49e8c6ab208f05f9738f1e21a40068c1754178c32fd30a3a4bc2885d689530d03f7f91714156997018ff2a06ccb0190edbb9c31ef60398953a59d9e720c1ad4642b5c47ab5c4a3f423919f72c18764c764d7a34749593fb1d7e820fddd5e81ea211a4601b3afa4945f134472180fc6f2e2d7fb2d6d49e9483b7f9bbf1f2bf0a86829a2836297d6b22cfe724a7788ce3cdc7d687d0c7f1aabcd10453f2f9489372b1cb32bc0a2065c8a5105368cb555dfd12ff444aff1ee957be535de7b99d55526fa11602452918f18029b5931551db504cfb5966458c06a3e4de58ad681da1c7c9b7fd5fb7885edb29fe81b4eabfeabb017790718311541e9ea04fef149bead1e66ce97d89ac8b0483ff872dfca6c60658755471627484631f4c94f3797b14b4a7706703f65b649420e420ecefa18d75bd32b25bf5edf5079c06f89fba734d17f52181b396d4aa9d2345d72b87bc328beb197be60d151cb25dc4a4a5b5cb0776a934ffa1ec52189a749984f5cfdd1fc3fdf3953d19d810f4094d5f2b12e04f8af231eaeab11fca9d075d823ea95e51e32fb50e2b3aec62cbc4894857a6a251a88c9f5f6d4bbb42497e61c72e7f31330b15817c87fadd81aad51ae9b4451255131a85df3a817fbba6cae4f4e0efe62b6740f2baa071819c6c4ad2e4a481a2a6f3cf46807bfefe71a2a2611726d1632bea56534b89909882a01796448146e171b6d77beb7bc1488f5cf39911964e4a5e90191a92a2b3e4a459eff0da36e57538160b1f1c38a45182483e3e172348e08d0fdea70f69be9e4140f96639912784489d8724d13738830451d63e972f024da8d5208b2e14ed9fc6a7b820ce1784a10b1520fc6b8cd784a6bfaded67267b9df29dff610ff35332120b311dc65beeeac518a8d07a46a2d51e84e9c5f72355b7f52b2ea700a9516dea06c83d44c362959cf1cd60c9e80fb4805556f3748127fab215316ab08cb52f0a8e3f37ee7dbd506f0314970cdbc628099f91e4235459a81d72f0ad44eebced1e578876d3501eecd917df5f85aa8ac937342ff6a14d48a4c993ec0c53039b66f6d924a20e5768e01f2e862f9dd7faa8e7f3b90f5723f516f6df88829988352b310437a6b7a7eb9eb8b8cc4efe506ab4b49e56ec072f42f2de331b697fc2a694a8974a5a27796ef1339f907c438b93abd710bb73c72d847c22f2f07315d3487ba81e1101f389e798ec9e80a1659451d70e319421f72df5c2e93a50b50831824be8ff90cffc227b0e244eaac45796995c6204e29617240b0d0ca94164e0fbc496972834d644504194b54c41e977e7216fa52514a16664628faac8687af9a41b48b2e945ce884595fb33b43969eaa1fd0622f615c4672c281f1896f9df7ccf458ed13ef70f59bfa60895ff0c138d71ef05dc1145bef72542135b63026079909122275e9aa6956a35b93c02512cd772a54958f17479972bedef15d696337754006d88e565aa7bb5eb2eee5332a3bf17752233c9d1ad86564248e714c42b9440e4320e22cb73b854efcb5e910828a1f0b29c31f3bb4a0721c177c77fb5cf4980e6ed93d230e4f5a62a226329c30b7c43e7ad3ab4bf51372e0977486b9d1a0759803408a5653658b958d7a812b45efc90d99dfd7d145962f190dbf11d2c07087494ca8a95dc01d317be83d1667c45296ed1e91d0727bfa724dd7583a7d2447635b6c03bc12f3cef22a6f3fd59a41f08da1c6e7e0eb15275334b5fa6ab9912afe172075a6e8cd9ac3982e81f5e326a056cf0cf589a54d477290b519a99884fe5bc37adad4ddf494e7f728255fe6ab542d0521a1d57f4d527288e39c9e49dd69d348d5eff463acb3d4bb40715ff54357d898cc24a233cde27272947d448f1de39909722d24464f981df2639c72f1603da0e9b65d5beb1f521f2e8d6a3acb2a634d51bf200df99d5f22c0e640ffa58d80861ea6c45b6ec4d972e9accee84879d79f28ace6187405cdcf00e6b11ff3d7573a5bdaf166e533b9722e6c98c6a654a37a98b08f07dc142dc092250cf0330f7af4b5ed89e886f53f368250db86aca195224c0e75d07e5d3d4918b080abbe37809e9425f342fc8711721e3661845d1e63d94b67e45816359c491421b62e5ab437b855de342a380013722a43e332f3060b52256130304e36d634429e564870499565f99a08b8b3747b7203d79329ea664aa086ace97a8622dc3d22c18a73f742cd81c0a22df157380b72cd4bedd0566227bd9cea25298332c1f15bf13db317f2fcb4d898b47457810b4b445ef20ea46c182b1ad9a0d32f47f10e33cb0f5e42c3b0682551c0a504b87872a331ee41195734724d80b9eeb44bdc18656308299a255f5033103c02c6d6a1576277f5852340da8648f7397231be28f0ac77851452fede73a62b9444ecc8f07232f630c17931b7cc1543100dfe341279129350440696652b8e11882691073f37224c2485902de03c6a21b2f356e6eccfe9537971b3faafb996f45ab04f3c93443e9a16f5f3ef3fb3309a9d9e63c71fb2f05fd2314b1fc2e327e6a94aed5bfe20dbcb96b6c822c2410216c0389530eabb21e32a7a18fbb6eeb893356793cbef720ee6c39d7eac970993cc191d44e4cee7e306ae7b0e31221a12cbe1ab0a8d316d9a6309c8ac97e93dd53d654cd0e0a89107823ba59e2c961528e49a94f0b4ff723b8441dadd28981d374df0010e615522d586ca0d6ac9f95a08f8f9eb7b07a872d6c659e0eb5d49f6124879b44a7f142aaf315455ab33217577fea43db917e74524cc25b319d1b9f1369a05b98b6cd9bf6aa2b30a223cb0664891c78dc5d8b62f1569cd173e5bdcd79b76796c7b2df0e4cf2189aac844597ca67873399c9e2e72071fe655c244619bfe59d389339037c854441ed611927bcfe79e17b991ab3e65dcbd67b81893db5646ab6e59baa17c06c473fa23775482c71f630b65ec4123724f0f619b38de59917c29bbf0c0033ff3d58198095064bde01192aaf720d3cc7207288e859e6dfb7546151a30cb5dfab67128858230ecfa5fcce5766c05c54f4da3d0b9b28712891eeb5935091bc0a47b3c67b6c81a09463c84a6bbec50407656955fd6ff818dc99946cb682c56640909f544878907b03f791fcabfe83b8c15720d6535236e409e9264177c44907aca98d7b247cd621bf78f71956b9416d7523734c8ef9a44ff960f03c3a7efc544729284e4d98367d49414a8e6ca4c51845072990f971314f894ab29befbc2725ecfffb244f4e9f33a1f411fa59568f00c6037c7f9a8db76ddb1f741812a218e98aa9f02be7132d2dcdf38e025b7a9215b457282d9e3e5b141e28d8e35311709f56f999f0ef00039891e1817bce189c28d363ad2bb6265b3d3ebbcb8c80e80a4f5661a886a444992fd34307395256d16a6e372b05d5f437fb4302affc10a2802a9f49fdff15b991aa794278ac66b4201818a4e0abc5c354f2728268cb32a25f6d81910abbd5c3bddfb1ed6d35d8bfbbc255023278b7cd0e2acbdda48b3b56efaf22722aa6e54f96a071f2c31de07b0cad1c1725438f30e03d70d361c8fc1263f7b5a1f88e54ec742b2c407e7da69ec7583bc7270ef1923ddabdf5c51dd7ffe3b4dc0a322136c68a7a2fde1a75e7c36039336475208a3a5aaf2fb0524beef3eecc778d8546778e764945c45fb00de9939b8bc728146a3a034006277b273bd7be1eab3ba2f12ce8fc55b909d8e59c173f3a0e9728e3b11c7640d3e0d0ca29b02217b1d8a49ab0a6b670c150fc9bfba04ff425d3ed4f44eebc40f67509ff77e75f1e4239f261521ed81359d30b2f0bf1c2d583a7225e44a8d091161cfb7edc24b7b2707d59a1f44a23ab1846a687e0cfb6a25746daf523f9842cf1cac02f649737b01d9c9fe2cf0406463a4e8b0629c695cf7f272f2bff2a145f0147ba973d26e1bc962f12a34fdd8d9548f3ba70e67370314a0723b31aacfbbf6cf0a2c650eef99f862026ab031d29eeca3966c9790731f567672cbdb0325125a9ed4008c7b34544a4ec879b732ba373b2ad9b967ed1e49ffd67212dbebce2213e92de68d0582cc62cf0dfa7e707b910a7a90feb75d82520bd4728daae5eccaa16587eb5fc2a63705eb33bddeec77ab31f89184e6c2f8cb07757235f6a07e33ccd27e7210ff2082be408dcebe940e34d37300a2c95074c45bb84c0aa50de71b2f2910493eeb5fb1b588a9ad49137239d6f8e859e3782842b27658f2b4ba05ded803cf52f42b061e41e87ce09fbb4d8d151f0475f17bb763629b72fccd0dcfc85682ca4845a331ddbe835e26aff58f00aaad9c2dd87600b0a19b3b708663a2285809177ba4160b834ffccc7ae5b14630970f65c33901d684321572a574329066aa6756e1fe00eb51b3b3218fc1b7e6ce460b21cca09d99edcd3146cf51b533b7c3f17a53f1aae50bcc033d6fc09a61f375cb84cdff985718c0576932b4613ce7defe64c7c35f093e8c95ec42ebdf0f483ffaebe40a017db2257444f595bb86d03744c3898190af21092b226a2908d07814612e79717ba4d85bee72f7888b81b13763e6f137311422706616c1345841f635b30a08cdbd13b90ce97252466fdd777e7d59bf03cb4665c78ba2734f91cc9ddafcff31e745bb77f7f84e88791dd1b36efd76c0d1cc7ac479f60d949897a6294c5467f638a7d928410f2f217b67e8fd2cc1a7a4b05a36287ab7151f1c6823dcc04b8671701387f89f6d722994748fc2633faea00931f8b162eeacb935f23ebf046474bb365998196145727f92fe4b771ac664687981d4be3f603df814b8d9f8b36f4b3ace9f8b70210f306743bac5ea8f53056dca68097b43bdc76ce28bfd2c4f507220be682771c1a533d84c6183172287d2ca8acebcfeee2f2268729677f60da4773324ad41d8b88372a6b889474d4266e64921411a9dad3e8c5a54ef554f34fbd65e3b7a3095a8cc728552759882bfe7045c3a5d77888faa49459d5e6bc15cea9a0e22bb477b714472c23aae372a77694b256b72a131bba2eb9dfa7a737368b0a52e99cdd4b78b62284d58f737f874b1d38011cfd38a12da0e6ef232fc1ed2c3814be6246b0bb1cf6cad282d677cf8fd865052514b5381281c17d408c1a4f465a7c2d3b86d456c2d722a0efe60b6a3d8acf820728b07a74ff59ddddf9c5e65f7f28ca1e762d2e83272f86c60264d7f450316230b820e5d5970319511e6310679a7b4196d0f4ebe29728bfa68b4c50ac73ab07bb20891fd3e5ef15a8c31ef4e044e2358c1d666edf97254177c8c7d0d8f5e7069ec1870fc660bc9523b1e3c81a37bd1ec802ca310b72f7a9b6e485844f8b6269506196198a9a5c8850192e99d0ab215cd5cdc5b98d7229509fec994d370c6d3fa1bea26ffedad9ad0257e4a0ab7bd00ab3c67d61d5610ed2124cb36fa2b7b2c9ea5d91fbf50609f199e2e4e2ae0b5ecd56d2d7314f62f61b430a42d0b10481cf5732e8b42f413f3894db256f99074c9cd16baeb86581f8999cf48d229c359e8297137fe15aa783073c967472c550e70cfe3db70b821729e2fbfb83cdd0bc00c23d0260d3754a7458eb59e98683c37427193e8c6703366e64958aa2fdf368f34a6d5b512cf97f28335911fe017b890fabe4881f7073d33d70559449063ae49a1d6a4a991aad4fcf819f29714c10075a6b5d0f9ced3be42cd948c6501748bd74270590c174167d919fdfecff31cce702d012eae5539bc723863bc232b1189f25d40213f0fc81287b83352c9da0917377ac89292ecbc5c72e0b0af2456243d1aaa0e635f76f1042cc736c34722a4960329eed3b0d05d951d14c0a325f7048d81938cb61d6611b1595789192f31b93202925eb94f1c2edb7108e70bf7190ad7e0fc9e0275a03790dd09c823c4716c531c59e263152f83ab05af754e2c911840935f231526fe3f976856b9c3a284e50339eb6879291f7af9492d998d48913c47fccf969183c384df0c687f9e08a320a67c3b1ec1a7afee2272a51b107a5ddfff748c3314ff9f03c76b117f84af8aef8f8da3934a4ea1195b02e18fb4b2f9e0c5f7fc70bc8b9c4313753257336d7c75345b9811b4b99f00b712ade1750e3d2e610caef74bbd6272b0a8409c17264eb59a93ee50d455e700b0729e39a39923ba8ba29b97b7e65123697454ba2a620ee5744582cd8d523d74536f069c36d20e106e2884594f839071acd7d83212cd43f07094e9cbedf1976d0b72c3a84f25efa8bc51c938b8e1f42e92c63562b56de559a3dce373f0e331a5f9723552933c16be7926a2065d5970e30a45fa092ef49d6ee6ec0a7c664e54b40f72667d914f904b3485e7f57e13976e41513bd8b50bbf6f5d9cb48284f65214b77292157e675d2a6ec312eafc923e71540ac933b25cf487ca4ffc0febb2328a283cbf65ae664a43397676d6a928620a96eb60c3bbf2f94e889aefad68561c33d026773c52addfeedc58f9154ccc37704c8e0e39fdada1495e44b869770b07f41372ce5505eca7bd27e86e37e667dded83f837dcffec816b0e482065b1a7e6fb207269056c17a401c78949ce3744defb2760fcb3bb0bc201d0902a2e64c56c46b7728f86e798133589ed6c0f710b5cabb56f26b884491f38953d458d56793e7f7b72629f5a0b8d86ad26342ad80ad0f564614c1541a7421edb3aa13276bc0831676b69d483f1fd3b6fa027a630f9bb115fe2176f6e8ec775955fcfc7b1e7eb272c72087dcce874a8d62106dd3b66bf74e09ceda9ac201c0f3b77c4b5dade20302514a28b06b34a2f85cb73bee76a5232cdd62b50386a531be7dc2a75115762a97572097ae443a23c15778e5a067758699baf041937e34a8fecfe272dc728da21d16882b3fbd5010a58716832b525947cfe922e32c841427ce5ebe58bd2ff3ce03272b98ae829680d2ff1b1fd885bfb6a7d9f367b1c634a21b8a7e0bc47eea4609e725996e7d39208a81df6ad571368ffe7b7470194cb61943dce44ae89970c57161f581424248ee86def29c01d9b3cdb5043ad4cf65ca7d1eee9185e27dd4ff53072c01c37d235005c14eb20ea25a17179a41cba8884c03e74c05ea585b19a39527229dfaaabb6167acd649db8eef140af8c4f6403ed570ae11e75552f2267e49f7211bbc7d541ce29f230c7e4c7f509beca469ff6d5e0929dd15db19396e6154972c7fc701342938904db54735a3240ba89248bce733cad0ffd642854cb9e2a6b72eb666b0f4d4ee471bd2c0dd7a3fea6092aa84aa49fc84c38535d1b98729bbb72f8e028a0d521ea90d6b2dd3662336fda492e8d915f237905e414d562ce487272039adba33c46e5685004dd630a3322f75b0275305b07bd0c07d43b5c718e4572ad1808c1afb58e8b747f8d205d6cb101f02dcc72177b0e2cd04095387b261a720868685a1a59defb71198b8b5d9523caf3a5b92d7ac1ac88ae766421b225432069e7425079e6c483b166f465196229c4a63be090bb4adb73fa93b5bcc589c37245c68858fd98b4ebebc6a0e0119f11e2448b1b985916d5d168dd72c7d12ca64129f8d40236142c221a36d7168ef0a35bb498dfb6e190f424c59f51ff009ef645146a3334d66d77b0f9a14c1cc7db5e34f170a47fee1e60c1c8338f1f9b7e4f722bcda561970d7dc94dc2efa9f097d7ac19c7287cf2bb7e18076b9b300f645c724292cca4b210866765ca610996a9351bf53b2d7c85feb9940f5d43a243856672f89bae7f4cdc33e6631952c20ca4aa4fd54c041a05148a6a43ef2a943bf10c56a53fa71717d4f55bfc17b6e37c039ee00166c20b6a9b7892ba972a2b38c7d14d275305b534e0cc81465f099b948b9a5262bfc6f17a64a8e864d677a9b4160070eebec41bc6f3323012474e5af0ae9b85d8e55c6072964952616c6b94fafb8a72f5708887dd431329a28212dfb5d23374c999f257bb8710ac05f48af935b2d51f1e6884f298597a6f0ec0866aad057e799a6e47991f2275144265cd19413949723f2f844c4fa7465bddc92aff83611a21d9b0e451a1d6b371191889551a1f01546eef9c5ec9ce52b644ca428e3f73aa2f85cdf977f6933b47755525edd9ff0872dff43c12c6cb9a00b738ee809847c6d43d6f893f81deff9e9c13290f892bc814e619707c48ae23a61f0abf42e1ed38baf461b996a17551fdd5b04b12b99edf72019e800cc946cc84356648440e5120bbbd853125ce24b762b6869788d73ee57288d26faf072c2efac41a9d2de1aa98e90eae857bb249c61c5b9decd36afd0e729241e016e21c07b555744e3c320c0c8d7dad087f2d638c0e8135c8b8c69bc1318e7051f22a3d9565f698bce4e77cc1c7f2d62bf620dd581bb431de059c7bb7491829e2a3f2d1260d875645876169dcb1b5a6a32430994e818799c49c24f6337200099db1529565587fd40fb1bda713683bc76b9f2117398874ade4d7631eee396b308f60a02402b9dac38b9d7d16ab984cbd0c3f0cdf8512b6df8ad9d33b467218abdd9016af4ebe106c27a2e6c942875018ec465d37be99e19e98bb3221e9723637d998228b23a70f1e73ab7ffa2b3fb3a6785552bcb78e00e112c9fd963172f2f3d9b7483256ab594872fa82e7d97f407b298f16ee43c4e2a7de2fe0b52a2219fd870f6b2b47098425a7def8c70c3dd09c93a4868e9b6da18472a2f9ff7511ff41e27e050611334f6b99432e91d9b36ac5f40a26d1b21ed78d8edb0bfc77724b384a8e5e9586fc241a34591a97b9f9da1fa4fd0231405be17d06e22401405d2a8d058ef04cdc8c1dc71e28dc71cf10516c6500df915750c01c416b31613a7284dbd4ca3be82f5617918988472f73818e59a9beeb0bc030e3edaf00fcf92c6b31e2f338e7b3c975237d9946b2d63e91e88fe22a83ade1e73247dbcb70957b72912c1e7fc3462f17d358c452b368f29daefe39bf895488d3586adb909e6fd1172efb5a2e1f05d556a58c6fcad7c10f9d6e86f195a31206ae2aec0564daab8a39ede75e2128f0c1c37c457f364b56c854b10399b6a011c148824265d2715a152bf03af3057f07f95b35c05899c75e1db9ab45a12e64e3ecc285c7ae50c534a772901ab3adf79d46e694ed6cc154720e337deb306ece2ad2c6a1a3ecd8e5c5ee72c7552678623a0630fa5fc01c01d94cd51eb78a6061b6d61c125d022833250c72d53483b1f77d87a98840ddfd18bc134ef214e98be13b33660cce6e37f8b15f072743ffc7c94523e7eecce60f298a252c68163dcffc14f96271f9cce13181e46d92c5e06fe957935b8762c81ec6b2f19be4f1e9882985edd9e323efc6a90b97724a0290663f3d36b704e931ab8484c2c046ee9a49dbd62c6c9bdf05b4708a0072ac6682d7ef0479461debc3e159c7bd9469623342839cb8721e07a1d66d9f674d1bfb1c63df3a9ff14b4cdd5398d896d5e4486f3c2754b5337de92c4734e2082eec2a940de3605db495653986e96a21ffab90c2d81649e846a25c6583f03110721625f4761758d7192a5cedc69d7cb8f82353f49fcc62d7a08e2d6dd13e304e7252f494b5148c4268dd16d972f6d2578db757d0a96393f1256a778846b5ba2b72ef6f0d722dfc0bb7f3520ed9d3370ec07ccb0ce3dbc04281060f9cedf2423272ac9bf925740849705e894bcfce5f25500c15b68fc1f39af2a83fd78ad9f3e61b42b58c507f0d75a3f2dc1d94bc1df74cfe759776a23b13720ef921b17d055b72f1c49c58ded5a4c904f3d59fb262f943ca7652dfb41c1f84ff394d3a53d0b428c8fa3428c61d556038e80b10cdef191bf6356100558eee191a0fcab502486e722bff7c9ac62019b90d51ca1a83aecc30b0780cece1f96ec7496ffd88a5fc297271b4f05a52cafc68dd12974b4bcc62510520203614644e5f1ac929e98e79a10546807d33b55098a1e1797c0cb38d0058ed693faf739665a1995378e23a337006ebd68fd9ad60a4df4a71be79fbf0f665186cb1cba34aaf4959e68815b635bf724154d1c47cdc43c138558054ec57d17d9a22eba259709627ae9b833ae4e73b6d30b93743016650cdc0b6fb39d3844885910aa50a8f24a70149e2cbcb4cc78d313275e2ebee5969e659a181ca8547ef1494ccea27c9aa5580e4732589d6fc0864d852f15ff5ec76455b4d3e8829aa7063f2f74206aa4315119ebc90d51b105a72f7ecf8ec11db720d3eafdaf913b5fd9df68c403db3eb24049dabe02cbfab9836f75f825f723c8ba85f434105089bdfbeb744b5168da605d8d05a7e3b7710db725dba5a339310d766ddf73200d058bd4dd779b1f318890bcfaf8dd2828135cc729e27c5b99d27e2f5834228343e56826dd14ca109b632a9b3466dc30c952076013c4d766afe89fd23e44dd43a0bb9c4d20422ef0e2e0ffed02416c044c752bd72d5d4ef08f26e41cb7778cc5878662463ea01e18bcfb377a045a2cc76b1831072cada05dfd70babae4444ad2aec412076f9997284ae412398d0dc46daac503f72c13d47639b8ff70ec7211df7aa89d1ed36a75e43235ba4f932e63e81053e1c7283d107c59a1df826ee227308e9fd0a9035d522b54262d1d30f191af87dbde172d9c6a7381219215fa219aaf16a5f2c4a912af262e4948e6afee41b74023b087230ec356f91b9c388ae47ee76f154bfddeba4f19a27b96e619749321dd52939723bf3b24bfe8bf6d5c0bc2990914592bd2397739bfe20c2584821dc19527866456d0b22ddf1641a2e6368254d6774581fbb9843fa2fd0fbdb543e0ce4137c573933e09f2eedfdacec8d1a3c8f6135f8b0726c9e2e873151600792a2b1ef0eb16287d1556b1d419d3b68553c8ad9bee6b4b6462e82149c080aa2d89512cd1fde6267c793e48e022c6aee8f64033e7fd60387e1934e88b5fa7316d8d67e221ab6724e1eb6ca120b1197bab6eeb92141bff56ca6bd171740f4ecbbe456578d65f83e8e26ff29fc94252c616c25cf6ff4857728a4e888c6bd155826cc97897aac4872dfd3003d68a28e61c3f092c5d5e302a2faafca76df1ed8905d5f0d8f6756267274599c871f35c8b3bded5f0d900978c6e09d08a8270d05d1c4f1b9670328a16aa2cadf3b84bb50fecc87f4aed631bfb960658ad239d0beaa293ed9dde162e869baff27de2dcb751b6a455d747f8bf738e708c8e2c38876a9412ef66538069458875ed7ceeb655b80d301fa7d26700d37341454d5538ddeb450576e51f702e8244ef71ff6304f56222235115c6852087f3e472cd486593c70abf995f47704b13d781d414b8ed3828f56689912026e42844d82968a128894016246598ec018a12664c0d9b0cf2aab1bda0356d46c2387cbea66b94a49d83c3cabd4aa46b865a316b76cdb2bbf20a374b4422a8a5b0590bf2193924a3e31245f72ff718716c03072b54a56c93a6b0ff56dc2462558f1fa9fa0776ac6d8fd0a3d0a791f2cbac5f36b7c53d14460f72d7213bb05e39a49c9bc6114e692b5aa69432f5b3fa88ec945723f78ef5ef543a43a22afb803a56da917d8490431d812046244012c85a2740a2280a259ec8092cc0cf8018cad9590219f45531c78cb539b273f8192bce2ecd7644772853a708e81f17d9a9b81e54eaae60df82e6fd033116485e2bb72856c38723384dd8228ae3765038d70760d81b5b2125e81927aab9f0738327834f088c5722b55add75ec6e670182174a325f2ef272fe60306855119c6a395a7b023c38a3d769e14eb9672dde32e6135dc9d19d8b520a470174c971be9657a5638b5c56172678e6ac0333f44f47d1180135e86bd85a348c8d057e9932dffbaca9f26efc272462a73b9073f4c7d431d15616a16931e270a689fd1fc5d97902fa28eb9e0f572ea63d0c143bae1f50676a7fef95fd924c468107197d6d37c0cc6adea08f54972d9f68c16a3c4bc4e9fa16c17e5d30ea9fb47d2399c4d1d72825c422bd8daa972c4addcde3234c443be790dbd8704950efeb0cde76a55d5bcc9136d6a7137a72f7642c022eee3182823b3066f98b612bae4912784172d81b978982f6b649e0f72b2a4ea988cbaa8b5e0c00fef13937c554b41d2428eeaf90c41c51d41f8d7127285970dd116566380cb59be782072eb2d903f7394b6489562f86bd963338a2e3f5611cbf5c9893de1d7724d86c043581f7fbcd177565245caef569b0a3f1da97229e9faa0c318d360a281495a70babe01b06a2c9f0a0b61d69565ba6cc997446271815bbe7d68ceb2687c0be6fc76ef2aa6f81775d7630bf8afadd777ef823705b4fbf20cea804a381655703fbb7890f859496486189d6e5b9b89703ca03442721743c20634f57a2994379afd1630832f8234842a89431272fb1264edfe4c9e721bb6d0c441e12083a829ad326a4f5d40eed9850198bed06505667e34f303a172e720d2b9c714e385d398fa449051f57bfe344e1133786ae40d5a9c8459b8b8725f3682678e4d2f4e0b55d3178334e9b2c70a90954237b4217f903e0e627c332375604993dd52f7da05254545ca8019fff32cd73b9a4228b9c73e37c24e36db3c679485d61ee5a34ba81dd89a6665b907b55b67a11be4645c7b7b9ea5a5f90d72831e6a1e4d08fb53223aeff32a542329372ec188d363262351e9a82d163e044ca0bd1be453b4518be1947cd02becc31bbef540bef9af7f50cd4dfa0e9d62f504dcb178dc97226b0eb1be04389f0bc02b124d1e031b0d1a10c82e48dbad402f01a1159fcd2420acf492b1c44cd91762dbc7fbcbe2e975f98cd0a7d51b3f46cd72516dfe041c0504da3eae64473fae4fe9f271b7aab0b0d391f3fd63d48adb9e424f8b07dc3084e665926e5de7eb8f5131640eea269532a3b5c0d5982205ca807227b97d8dd5b268aefa79656257e9fd09f6d224f8ca7bfd65d246a30036f61872db1f52e44580cec9bfea5b17058527d9c50c57e970af63ff244c200b3f6cde2eb9663e66f9546ba4e52eb62ea80caaab4df328afad0f5c7fe6e9fff4a7e6b92aa9d40653ba40506528a13f4c1ff626a9573558975167260a405feb5eeb770c7291ec0504d3683c0f25094b7e3c1788178e0826c9e0360489bfc6d11da87522173747668b6b7b01862cb0607e054f8ca8fb4b7642abe692edf53c51f73354d072513f28f06a9fcda859b050e40982b0ea2293ab9fbcffc92a56705ebd6d1fdd2b9997b6f40d91b8c3c8f5aae9cf9e793dc39c9652d4fd74deaea13d852b615e723a978c12b93c92fea52374d8d89de60bf00b8f8f6f432c21c45c4a1d177f3672401fd2c0caefa56c44037421b6c49c121877e19baf474d51ad30749c8f981c72de557e55e1c193c5bb47ee382073ea7d1d7abad1740abc77c9f12e9ed5663663176b763ded1cba18644d586fff34b3f5ea890dd3533fa29c76cc064613da8372c565d150a444d9dd050ef9d164cdd5bac8a81e238b8e8582c3dc20dc376db7729b3bcd1a6cb1c1c0fb20506aaab7e1f953125a3a9bd6aad4c6c9d5ad6c7cd73118691e495e36ac034bbcdcb012b4b5a8902b658f6301c71f115c3ee38c7be44136180f40fd670ceea179c7974b4ecf1bf3dd95a74560fb05225feffa9723c95aa65c1b3924efcf5a84fbb852a9cfd977712c62b50c58a17df59c74430d164644caf2545a86a34ada987bc9d7a1a43f8699a997a32314e1f847dd8aceef20252c13b0ef981dc9ddd27ce326928c6ff5f666d57d3c2acb7c18cc3fa8176669861fc04615e9845f13ff4adbcb1a1baf1b41a3813444e785d55fb796630b29b8512869fb868b6cd44d7966ad219f11721476b1140386ac58f4dc7bc62a08206be17232166a3cbebb2cbce275556d9d6573e03f1ed98d48c553fecc5c1cd5cc2a4572ccccd62a94ecc567f4677586a1a0676d42381aaba9fe6826596ff53d5d10537221f20852f5e127380a2564cc10d9ca9bc7e53d4ae93da462277524d93c897128a834fe4681529ecac373b8587d1eba15bb639618aefa87ef17a80eff7ee26325c4e9324c77601624ae0bc764baf505609ebb58b585d5ef229848aa867d96f472ee9f035d8f67ad3281ff7794deb8084f4cfeb9ad9ba183256a2ddda78e550272195a7b1c062892b15f9a82aab428213b937a4ed354d5f991ceb7b6c5cdcd5351e47a8e38eb785d9c79dba5e0a826369736d5b11186e6527bdb23ffbc15adb12f2b2044c39f90d8f9749e69d837b57726685448666eb40782ca26f6b7727b8d72a733852df5883235ba254ec5312bb0862a0f9eb41eae6457f69e4cc00b46141852e17d37cd77c75c4e6a8df8e02181285f93754bc98f7b59d9b9bfa2ca993a4a1897b3fc95fdc2141d0d7e2150672fe6f1323f8103a864ea9b50475d51b6227225b9330ec5a1206bb683e5f72d81df16b4ab7e004724838390f4c1dfcf95eb0b72400dfc6c5f4b0975545a6474283b768fd94ec8c3dbfb0e7033c202afe16f0039df92ce6df8ae2e52fcd43154406ba5074002ab3ffbb28ac9ef054362b5a872d1e9764e215357ae62deb802bbcd7f42c49353c7ce37ce529b94136fdd44e872874a4d059812c1d746fea85e822443086c2db7112609cdf19cdd6d268a10f972f7eb2e22b4d0047dd52439fbbb95f7fa8e6f09ce4d935d899298b5429a9d1d72c4d5935e06f098112d673ca67b0f14ad5ff1d834bd829864ea2b4d0b687c0d723429be505ecee462ae77485b9d42a14c8ca58dfdec549c78fb30c1bcbd6e4c72a591c257eb4bae16858044c23885cd559bd8c5970f0ae50136e5f826eb94a806f9f0bdccb12003986c38dabc22aa88bb29452d4a1990fdcdaf7abfd3014dc0726c090fecf2d13d022db3a6873ac6e98e70cada046ae9284c0059f9fd50ed037201755dafd5ac59be8b5aed9ac0c2d757a6b97ad5908618325d54f6fb21bf306e3991f102a74fd8592a7b0688f4b956cfd6a43a3ab6adc13fb34ccdc29870694b5373a150cd9432249f82c2ae587cbfb974613f51368230bc46ff43d0e8585160d6bd19a939648fe3e0be410f9251f9d6ec4bcd9c2f3587bf140de0e6a732e972f26f268c06c3a78648492b21395b2c93a178f9e0daf46dba76676c61144755727d817ad6a30f68c135e482e85a8cd86aae5b3919866af53f5257a0bdb61377720d02f88a77e5f6ecddb05c8b3e16960d9b6c016b5269b676416cb25c53f12e720f3e0937907c946052218443e4cfba8b85df914e694638cbaff09109a5f38f12762416a2e5468bbd8ae1527caede239d9eb2040d35fc3fb5257f3dde296cdc72f658186e2d0df85d6ecddaf5339c04cb895481d0b08969410ec73014a7d39972db8b4f2a08161fb5e5c5ab2878518ee64377a0272dfee6cef09369379f51e272fd5a73c6ed1c8847ff19faca2ad71c4c669cea9f1a454e3c07b3b8442230ff7230232ceddf30f06352a006e9adac59aa0f4423bda4dda891aa39b8e87bcc0d7250272a870dade8042c1ee02e369b7979adf02d38d44cfa6ecbdf3a8c72695436974f1415e366e8fd24cd484c26b974333e1fe73d1ae8e617f11d4dc5691ff172d9f23835d179f417f6a1fa164de1752fd6b3463d6b7e9c15e6de94993915a62e6e9d023e49b79fb34d7a366713f2fbded1176bb035d008d2b60c0d49e2b53572cc53d9d6039dfdc4a0f6b36fd5849490154f5b3e8b862fb77b50a42a8480e772efa5c6ab3792ccd372c8bb1e7832c9f5e421fb3e838ad7d853a3e0a2d69afc72cd2929e0686809a22520a90d1b42394dec85244f8f7123818edfd931ef4ea37287de9bc8ed909cd74eb871bff7d74767eaa6e077b58669972a166ae8a5d57d49c64e8d219284c8b1349a43cf71a50cbbadf227c419c0a552caf10d0a4975505d457f91a0bac89627125a090450b3de963e0a4ef37477d5c9a99b1c7c032da52b21e225a58c49cd2a7f9d05d46fb6b13ffd2187f7f38ccb78ff8068811b432e72579cbd1b896548275757feb06f71467b8b625fbb2307632cc6fffa8d5a7ff54153b46b9f8ed67fbcee451fca36ae1b581ec27cf1a20490f14658d7847ebb7b7268b64fd703373d456744317cf82ba2282a1d089da74038e32bca4151c88b06729b81fdc3bb891addfdb783f4e1da56155d6374d84b356417ff3dcd8b8d1eb4724bf77ee0b276cfc1d1cea5d1fa5eb595519701899e4f2e23272e5488a8d43a727683e1f414cafcaa5fa977f0a621d4f98443205249c5b765f1f482028c3e4d726a54ff0fc16223a593b0e03f845ff35e9284e08d3edafa5b21146a02893fd0151388a679ed067b693c9f3907da7a3e51ace019d64d4dc630c217eace7561007206bd8f158f2838321eb0ec4f2ea5877fa1f1f6922c6964cccc85b40b6ed8bc7221edee269af581f0cff9a1468dff9679da1752f7229e92d964cde22ce1f646268e9ec111eeaa4ca96d7ea9e454cbc031c7881b5da0a71529d76bf1c6a36d764743b8dd936d916d179664cd7a1904f6c137f55af655eb58dcbd2e9b558ce34972645661cc3bb30b65e211c10e13a06410634158fd3db5fa7a5532243da999367233d3a525becf9154de713b711508480e6fc3ff1a21cbb47e46ffd71ab725f93721d11e97d480782516bc32233b3f9348c6bf5f57072572f30b678f90c61b957244a3d08890a6734cfcca6b5f982503e1b27b4595505e95c2a6c6b6bd2d0df35a9157e68c5d0c2f8d4b7a4f675859e9d14fa55f7c5da66695ca7d5d967e683472b3ab3e465248bec6b6f40726665a4370e9662b921dda4deb1dcba6024095f732c31e00e7710a283803a1e590b21d254cf495d7ba819393001aae7e7249357472f47958753689ef0b7022a65b53a4497e51cf78a6bef70cf32f91dffb7b418d7249db5290d0db9d5db7238934639d9b13e48d7f94e371eafb87bda086df1ad3721191cb4bfdf693c3b64533bcc1732555bced4d4f6d8e3f6ed07d70fd95c9714e10dd320f2e2e445368f593f13eafe3f9c2f31f9f9369047c6e211ebeebe0dc6690485bc5f9f2f892bd2e093f83039857163479fd40ebf18ccbc76b9a8c40eb72de4a823431b32a07c3aea07ddc3afb87d6ef21d40dfd06c71b34361f3bcc314e116a8c3b0fb6e20350d41cf3c5037de0833c845c25114d94c246c341bbb2fe6fc126487a180b03b5789d4bdafd6f3543d109684d0018fea9261c387f54334772e17b5143d4ba8c49b2184570a9d9046f32a1e62f9d643b8411c704f83f80b872bb2b45ff2ca235000146c644a5f81cfe6ccc9f5d39bc71d4c235294bd656be533c9c3e7570dba8413d412f417b086080300a887e1d04641056772149d629c472ddae6143dc738da630b3e286aec45d735c2fdca390059d014557b25bbe24c52403a02c7de106644cc468171ec86207cbcf8acbdd6619a9f6bbf3755b29353d1cc925e7b0ea2186fcc5993ad53f9e3af5d884553d436c7e8a4aef58996e5dea1f91fd656d8f43e53ed177818d231b0a157fd999891df2e41f13b2ff7278321f204a46ab40405e6590f76401e079af531e45d0439df19ae503a3beb6f29e29217204d1df9091873c78204c45d5544c4d19aa21f77f7d758682021531577db06f72c7a3f3899397bb44fc8436132a0fb55c299f6210676bcf492a70eda09d804572dd88e4bf371627fc3dacd6c056099d1f69866b2ef22f2fd1153bc0b523db39727eae0cb5933697961b25a0d2f747286e83147aea1ff2ea330997e0dd3bc83124a157f92050e4d0cd8d0b160425751067f890dc079ef14ce66168588af1bbc17209599e08a83961de806e89741c7b3f2a196abdd5e99104cb0e091ce46486d8727b8d338fcf6814d23a7f6b513d4c6ddbe88c55e1df62b30f8551fb6bf3a7377286e76e645bb37a8860f744b207acee22f3046ff7eafe28542862fea25640442726c0a974d6ae5636199ccbb13de6f663f0dc407b6f008899ce6cc5142ed1fc727f314bd9f5bdd492abb4b16e7a529859158fb2198c95d4f7fc9b2743946d420e055d9ba89cf865682d3e29d39a24ae401ebfe6893a74b1691040b6fa53e6be728209943b23f150893139b8b1d4c512610773f3607e01ea0cf22ec894bdcc17727063c637dd299bcf45b9c0c18e66411a2451094d70f2c7d452203615b4287b726551c28a068e8290d499a51f933632d709be763dc69f85ac0bb3b062a14a147243a0a321e7c0db0f943a8cd14a9c8fb3d7ac65cc0eb4a62b4b778c688164255dcfdd85e4bed69937b33e1947d831392f8641b1afb57eb127ecd5ebd903cb26729e38f20a1ed20dc92fb72d51a66d19565f3a2678672615001dd6e180c612f84b81c1930791831269308f83dac0b2a4f1e14f7f78b968c391d6653e56c25b9004af20b072b0a44371238297e12730a37f0f917193a50a4742c5a45fc39f534f1a8304140f273a742406544e03766fb71e0bae9ddd1bd46a90e9faf324f27202688a6e88072d1c83c971956234482a958e96989a961ec3bbbaad70f00fef86c71c00c9c8eb84b1c4fa3af09f840e5845e60ed98252f3841cb1f358ac6b85009872c757df9cfe354a63531aec8de48c94825128f5d9b1e7f8b8e84dc02042e8707257858977395251a08536890a52089ce906b1c1debf2eb1a450f8ce103b1c0d72cd345da78276e5d69d6e0de186823463ee3b51ac956d5e7662e4003ebd33de72493ed74efab61cfd39b2b401539e28569c7ce634fd75b66bb6634c412efd5324e2bf9a4730b52a694179e97be2541f78c6863935fab695442c4dd08e3a28c072bfdbe1e5667aedc294a0273171a0464a40e4ced67b36ac0053c628945dc7e235ae60d7f798600c6d799758f5b07314b1457de41109cfbf60c02044750dad320b9b1b8ee182c0906485a5719bad79b169769ce1e5b05cf1cea87251eea916532759d0cc23450780e40b6a94acd5fa22c61ac44d4c7576411281479d44cdac9d0df1d7dab7c950d8e60b90c3e92819f009c8355b08972a7bdea0c0cef2edcb267230e0f97b1eccc440ea10a5f72a2c4025677892b4621aa7f35090bc74a35ea55d249f4c153f13dab09a1f99ff135b1d5236071551608bb14b406c88201bcfc172d8e8068b455ed8a6ddaf1c932d7cb75f0f6007c2afdd9776e9ffd6e072c19617df60a672ed7934c77d13f488b0a8dc499145d4af4d8997f554a6861ee61f6372d6567e6dfbf5b72502d0067649bdbce5b596bed0d5ef87c7ad864fddeebcff722e0ac877d762eab4d77052074f0c8dbc35025bc037cc21e045ef8f471d2922720dae2ca7d1c1999f46937a7f45cc2fdea8116c5d72c0336ecd2c1ef6c4eb5672abb34e15decd94fe1140703d0d9f2935fb1fb5b87189f1e26323967e3e0fec726befbeebd30a7dc07fb950b77864e14950a1ca0cb634228191d0d4a62829de06e4f507d143fdb7ccd4ac88d493f6f7be6c039b51b133a9fe19c35a52126d0e721865cdcb756742e41b55f4ed6222566feb17e1f8aa2624e705fc10be73487a7287b919714258ec15d50568f5664e2d497d1d84ae5b2256426c07e7b0ce9ca75fb0d552a7f2000d4cd916b016acd74566eb03cccd7fb90499b48a5dd6f1071918842a6bddf3d279c2f29301601ea11c2e61e88398b6f42a67bfdac316d1b59c3be12d6ae24040a40a2a0d0087f7288af8718f3ddce05c6fafbb37b55d3ef2c2690200c557bda4e8b276c385a256b8197582927ff2695691f2baebecba4914d172d7f3b6dc616528e835660c6580fd383abc50870fc536f942e85ba5642a30c772a97cdccc356283a83761179bdeee4c82d516483abc0e2e7c33d69bb9ed2f12725d27b2bc1dc7d948eb9a63c0c687856e76cd6f3828e89a662d345f32452c5a72c75a56ff6822a1d696c77d4611edf3b796a77cd0c1665290a6c044dc3d779772035763cf8012fcac24fc5e35e6b17ee154fde3858fbc38d2775cd6df2d4df672629b4481e01d28f5d7702a4bfd80017c393448be4df7dc19222950ce0007ab72087b54bb7259dce99ba9172bc0d6abfb8e5e2bac42773f630b88c06b9c1fcf72713260f29df4889ca1b6685c2321b654bec329fd6bc1a921f3d7194e53ee1d47d69e1b1e09cde37d7939be7ae9c09de3a2542f3fc81ba8f6be0a1ee5c42b8d40419aa7a21a5e1947896ed5f3fa32a7ca8b72adc5a6abdaf0307dda96c87c0d720594a3f04bfe11c7ca110c830e091d7ed367295783795ba1558cf0d2fa84ff1fee36717081f4fc56c22654bbbf32f12fd68e3f6f8cc9040a106f581c9e74f472ef892a4095cf408efc34e752aeca25396b028019785cf4b4ae4d40b4713282722ccd8ceede61a592a0d37e728dd5295611da3ba8edea5baa79b4dd6cf3f577726e6f84e05b1ba25126b66d846256bcec5b31a15c7cc55c153c22f6a79060042b3246664f8b5694e39dbf2760ea4a816846cbdca737f23a5039f89c1e4d730042f532802b3dfeea9ceba5d54776f12184eb8cc89a3aaf078e4b908344e403327287b30c9c7b177a070c2a0d040ad0d8aad447adf6c7f85227642a0412d761a73fbd0ccaecffdbf111893c207749a62fe7c2f4ba11e789f78b1db2a87642f9b4729bb6f1150780329daef9a9f84b561b5c8c93ba3daa63ecf4e33284690d2cad722ccb7b3d12511f370332b2eb95a6321a1ace6bef52ec36cdda594fa3ad88da69d7cc4b84f74a8651924167a9718ce35168b6115962a2f5843ba844a735dd674c5a6e095815a6bd7967dc8d6bb596a5e9478e2a18ce054d22de1b7e61ad55bc7289a035a9df16ec502c27c909f7ec053377aa88a35b624fe8490dbe7fdbb9667244aa987c0ea6288dccea26242b94cb8a1a39e35f491a34edfb64734a21a2f772b9956bd6d6f64aea99cd987b47d9bef3ebf3a5e28c0f86e6641f1212745c396868de2438b8d24f08f46d9581f6af5c157227f27b5617bf28c5f494973250e472b545d8aafffdceeaabbf530cd56756305dd91422b55fccbad27d50b2219da144aa15b7cf2ab5a0514d4f37667d3ea1d6eb4bf15bc1e1b8e49a735ad002b0bb72c642be7a682f453a8c9a406b77678b15b05170f321de169cb9798b23a14455398451a8cde2df67fe793aec5a8df3a81d57c933028d5bded854a15cba60a14b72241241d9096f47399d18cbf251bfab66f61def4a72ae42b27c6114c396279429d918bca2ce8e1e0d097ecdf019f142b494776855b4df573742e0a86b6762b37227d861462bd818f8b8c28566e4aaeb54a93d4d67edc83f88f76170ca11b434722f25833e5b2373b1adc572ceab14a34e9e0a55ca0062e74df2695429ac1b9b720ad48c4bcad966bfed5a36a443d7d932c5827bed2cdb7a9580c99cea4b9c5172cd77d8ea7ae4cebd3ecb43af2e07852dd3fabda9bcff53c078d731077050f8728816fb91f1f0eec1b5845e3969b2bec0f9e8b085a53d53689c8835044c4a605a0ef1216669264a06d1b75c7325cce3085c24b3ee2a14da5b0cb764e8e801616a8178021785da87717de4caf628b48f29084ddb1312c471c95fb83d093661cb26e67d6851d8484185bbe35b16fea7204f262eac16c9e0b3e0b92847e71592c672511df6f42279dd39280614d01110e8de323f4fea8f31b219d853ffbf1d1a25723d75e46f08970bbfe2444821d5d34f79d980ea1d8f0935793024f415d28cb51e151676b3e33f5e1f7e0912cc0804c8624a1414f321465ac65c38d7c741ace06e7ba4ba7b4fcfae8d495fd0668709d02e77005120207d48e1d67b25146e196872314ba03bfe0427f65ac4ff1b763a034a4439899710f238db6901edf1f2bd9a274bd0c51eee55310fae745d05f50eaa7b170c1ebd1e124a0c281f4e056e3b4872dc263cec08b804c23a833a4716c0b529b5cb3083acf1a2a70370fbdc538f7b725df23178e7043246f14eda6562afa7941fb4ce9b8944c509649ca5fafce85372c80288451a28d60b903eeb2ba7757597d0a937e567780496368e276a0c7b9e728fe942e0c35618ba4d95b5e41a6b93fb78869e9867647bd8895ac21d37f81572430b73dd795b3b3245cc98c1d0193b52a12d3778a87e0dbdb1b461d59250f3727488c1426f4905136d0e042124532103a827aa8de714fa5a7277dc93a14ffa1f37b2152be87e8509326759ff04a9fafca5a89af122878f6ae05f78cfd6359c47c645b10979e2be03d0dbbb95afd41cf96ad3ffba7903e021c11a5bdf93083218595d87d138adb4d7477890775919c6d1c07c309586f8f17bb355ec561ac3083272dad3dbfd91f98acc0b3521ed51446d6fa274414fcc155a0d44c6d6bb022272b651c4454bb5cdfd161b3b5b38eff45636f744fea4e032da742ef1263db8ac72e2d992d20a9737f1391223d712c56951485501be1ffc3704eaffd4e6399c2f72a08ed4f6f3d6c9871d9db7fc883b79cbf1a11111c713e0cf2746056d72185e720deb8ef8f6abc3d0e98da6ad7391ff8ffadade68a3cba6425fa33afef45fd372f5a87bd7ebe8e11eac549aef06c442691fb17c88ec20390c0abbb9c0591dd72d83d7b5b1b821dcf9ffae2e94bb6ebdbe852d9015717d9b56b6d610ba55894f7291c6059e48c6cc30689377d96cfb83f47f26ba79a34c0393334a5f917c1fec72b3ad0ef2dd5509306dc580cef2db73bd6e22d2784a60f61c1672501b2ece4615953ae0b1b4951ec9bc9c8b4d9abd061f0d4afe21e1fe78d82c92bc9bb2b47818b035f08b8799ebab6b6f36b335f25debb8e27cee44ae8d26b27088d4001ca972806aa0e90f8ae98d187ca7f642405babfdc3913d1f41291e82c5f5b808f23972541aa6def9c69839cdaf00da8f2f91f2e76c8e5b907c4e3ac7cda9a19ab1eb4d112b87eba91442b68de98fe83524a46dfd29af60b9819f4bb2e78c899abde972cf6500145a950348b57a96a6e5362d5e94953c810984eec0540881fef690dc2856ce2f2e2a1ad5505b68cc0c8b9b7b050265e61dbec9afda0856a905a82e1b72b0ba9bfadcd597f14035ff85fd15d188d055943b8065f93a25ec0904e9e2dd4bbb2000f2d949e7d712bce89772c619eb716eb17cbcbf76ddfdb34e686def9818cdba03cdd80c4e58926caf6d401fee0ed1c464f70d1eeaf8998929a3a18fdb09d1a356e263f6e3ff32698dcc4c837b99db9b95c57ad5a771636c8d3706b60f72019a7758bd63b2fd7926478403ecc0dbb32437fbbd03097645c5242cb1fe244e0801162fe1294cc23f9773c4dc4617c377bf2b91030bb698facb033fbef2360d6821fcd470be1a992b55b1f4a4b8d51859a79ff487bc85d9066876478140ca72c849f00ca24ef77f9f436e82ff575466ba72126bc0a5c45074eba16fdff7097270bfa96f25903177375e7366094a4755a6710e82e90de3d5e3ad7d742638c2725ef9f20d8256d077e2816368b5ec3df91dc5706e151ca67f9e3d50446709bb07e370ab6ad6850c218d0ccd3ceca30645d8246bb51d79c9ebeb7e395e9f7a0244b61fbe22758409417768d56c4fb54efdaa89b6c4a21bcb0070ae98137001de3e1fe7f6e8393f56880b3d426d969dcdda9ff51d3e17a690659395987b3cbab872b72c86fd738074fa335d0e229cb26352d2a167d9eec53d881e736149a7a836176ec12d99fc132efcc56f9acae2b836bae4700711db34d36ebcdc8ad318d15b72d0882a78534cf5b01894f5aa188cf0e337892d0adaaa8f3444d8d7e2f51a1f52a961c48dd80b33d1b59df901add9b83018f73cb87de62a22417b7eefee89e072f8334a4ee5dd07b2e269d0f485f6bcecd16910b0592838fecf750101bfb6ff72d13528ba30d137fbfee5e37e474e49efd07e9818d76a892ccfa09e80f26a4218741a4e6cd2e5456534aa8cf553f61dca8e73f2af6731c77422e0f946038da60feaa85ec7c0516ecafdec04fd1ee91c30b181a745967ef6eca0d0d759af0ffd72b42f3e77612adfd3f5c014faf53e66a62decf054bf226fbe53a76c87afa15b727cae218d9a69ef6bda5fe2dd927fe1c82e17f276d114e7bfd995556e5ea855720714eba113206a044881336a452e6159a29c4c45ceb555450e037ba80fd00a3c63c75747ff6b114952a9d7cf2c5b7d8dffe6593316306535563ba7cbe691d2473e9c28cab88d68e48a6b0db37107198ebe364e5cdbd58f296f797578a2fc2272a949b95e83876647bc5009b3795fa532c9252efe3b00fc2a42b65a1152366b7255a365631f9bb696a396cb3c01e10bd75e7adfa47c95c555366e10225e7b8f2a1f2d2de5c89aec05ee879bc51030350c69dc06620e9235a745df81fc09d430729af48ecc212815d72a5bf838b333c740da3d2ec5f65ca37aad1e69d6f87bf67264dc7f4ea32d7c3a0914822d59bac40de62781ac719c8337e169b512281550724b51d09e70db729225ddc0a3d24cf8165a764abf856989e0de785d86266f7e44e65d2c2243c7fef22207b1a15a02f6c27dbdad85139f484b22ed7b06c61595728946e3b6efa3507e4650d0ce9b934c48508c602134f6d415acd9cf0ab65b9523d8300ccf1d48e489d35f941b8d4c854f7b2568b88013fcf66fe4ca215cfc3f1af854bd8e712f844de674a3a547620a515274244880bbecbc03a0f7b6c81e5a4c542ddcf8a1c3d14cf357c8b2802a1afc62d7692acfd8e3639489f9509f6a970c7e30b7936bb9972272a24a6ccfc101eca95791fed83b4a44058289ce6e356572ed2e5166c7976fbeac4d60113afbb340ca163cd4c157b273bc2186d4e8e0b03c65d820e2bd1151f43dbff176d810d553d25cd33677f9395d9bef0f02a80a67723dcec7e0b3622426779aa17d029b1b580a2d5c0da3f1c3e3a626e5c7ed03c90a5a3154ed79d85e53b8732aac84ff87968a06b9651b86b428b33811d29615957252b7a65496bdfffcce42d2f8fda19ffff3b407bae8d069664f0388d4f553bc36d72a83992e6fe346304a41b35a38bf84cc6003394b1a70ce657b5bc4c1203b3d49085e389ec7d60d8da2ee9833c026209e80cfa9d762d3da31f86de34066821d0da18ef52937f2a5c3564bb1a3f82c29b742876c836000f1b256f036197ba43239489e65e15f89d21f0a10c234ea7f3d55ef028fd9ebbae87eca03d4222eb76e4cded7f9448d8826ef27b53468e9a2c2ef57f86b5549fa48a8a607e0c0cd590faf9cbcf05fc2d915e62abfea4fb8d78a05ed40a4a1e9e1dbbcf2b287b5322b7242b1c2a3f566e8efcb946460724476168188cda63673505c2889d6dfed482672c9e3b31b7453dc95b6fcbfcb79011c287cac0125a54a81e88652e15c1f02193cf36dcdf538cc2abcb43b9cead19fb0f67d5d13dfda360d4c3416deb89c5ae9722f66cc063c063fc2bfc369cc69253117b1231eadb0cc68c69b11a9ed78414c3999fb7b03c82eb6f121b0ee95afaeae38b353ee9538de7415396e1542c1128620487427ff01e404c8a7ea2baf1afa5bfc40944903c819495aa71172af947ead722e1f01f466d5d09c230e6ad0253fdcb07df296300589ad3bfc5c65e997a6b9127e38422d7da43ab5aea87f08002143cb7e87291394eec7498302c730303fb272335393ddfa9c61192930903a099111f8dc949bac4a082be9635c3c6cee5c7b2f0a16690819f8c9678a4fc12d3f517ee1c49473fe831abf84879186a6730aa27212cb0f3a55ee4170f4ffff6e1b9403df42da679a66360cab1de45ab28d44477208836a9a9ef4df2aad5b3da00306f8188dd009d76184ac931024e54c47831f72da88ac705c9cc1040bb2cf751474e16f0c9d73bca6533297c44d0d90d7db107241207bed90928e7976beffff7571ac7cbc758cfa04625a9f3a4e343627026137e54de7a9d56d366c604c733ee5e367bb050367e8d355c4c4bd109cc6c641d953044cb1bde36bcb140be9290ffa57b3da97aaf1f0bfb41d582eccdefe981bee020367826de1328bab844ccbd5c17feaafacefec383a32e01947359b9d49b729729077c8599b980d88ac3013b70a0483e17edf867664d111e4a5cee6a31a8c0e7277b7b4efb49ae4e3d2bbf16e0d31f35e78e48be50b95fbaa02071f9f51d21772c8017983f9d84973b098543641e703c5aaac7a1e71d2f3fe6ecde782d9dbd772ee9b585bd7ee3cce9f12166639c57399fcaa38c4e207d74f51cc249f82c4354bcab0d697b02b5967b8235f3674a895e2dc6508864a1fd800a09807862e18c23fb11864c33cafe15931380ee02b7dbe82990e10ebbb234e30b20ab126a48aa1432b2c58099598f70504821eb73f5030feae27265fa0cb369eb8c4c54641a157728dd1556aaf2f482449eebbe9171e4f7be27ee38b3cfe5a8e737fd22049af76634b03b2b48363c7cba6fd95614bd7b0fccc19bb00abcade073488d24a3f68d072aa72f36c06a6c5aa5224a30a2df8994e67850d15efaabb11b674793a49b18672aa3033ba4893ff676963adae0b2d318bbc6e85af875dbc5b06d51cf760bb2c726429df648168c345be0747c35cc1f94987ee0ae5787ae272d27e7b17763bdf3db465cb4715e03d33535af99b8365f5b8a60ea0fc351f8c4c0ea372a4508bfd1fb938f5c5f4a0ed13c1957d8aa8f2c4327fbc201eff6203198e41da853940a37235f21df8c77f9dae26d8629c4774fbbdb4404ceedcecdd3517d97c96f6afcf72b72b73942db5b950b6c23f067175b498abe099a608a23211dcae095624f4cb36b467e54907e4138c14b73461d14684b14579b9a6ca63cc6ecc25ed1c61e4c92eb1dac1157f25efb7b45d26235b6cf2e8ba80567ccfdf9884c6931657a7dd4572831484c6191f384f5b8004c01a26b59cbf909843bfd99172d3a2efe8cab23f72603e24852e8e77fec8027a0a5ef323112f2e1af942b091ff65eeaebfc63a7b72483b63da090eaee3ed480b95f12d352af354bd228ce4b0461b152b33d5509130b3890a8710c861e5b56644a776864115a0c03b3b747a41f28be79236d6b7176498ab1e952598e1f7e341e24db5848f41c3bde40287db0bac083d5101aea0e725eafd744416df117212780b35cceac171b445e758801ad36afa29e3583b316e7205f6a5be934a68f2f512070d7b34b7aecf7b262a4bc30fc5ec35b02a22837f721ca885e19715cc3369c36afc03ceeb7b3de54b94498b0603434f36c95f6f5572c5880e73e237ae194518d2832acaf0f0ea508d18ec7e928d79aabfc9d16df845bc12c7efaf804835130eb5da707bdff22f3befe2685f73093054ba0780a63b72a84c349ac5126f29e507a0a4af38d149f0f4abfa7337a464b0d56aee8207d6522fa5dbfde7bc6d39131cd029baa98b619a7103b2217b0f0cca9c3cdf4397f772c687caa6d7a60a657391c82e0cea1bbc953127e83d4c3b7ba75f1b61f31b25720e177e4b3f6c8315abbed887d9144256dbbc62ae5af0c4b95488ac730068ef72d0bae7654c783e4fb242d092ee581a9cf9ded3a52e1e9d610b3f4bbbfa7b95721851ab875aa66478439852512cec9c841e1cc1ad60b12962e8c4beedb8d66172ca3a303186ae02a4d3dc553f2d12c6fb3cc2c8ab11ca9b5fef8ccb9894b5e0725290cfcc6e6c2f8c3794842b37be19daeb7803e4a3583f314ed5910b2afe037279d96cdcc50e76f9b8278a73c44881e7cf2cb74dbd8669b841f7858636c6574f82392b43613fc709ec293fc8a594c136bdd912aa5295ed9b1262ecfae57dc468d051d17a7016b92d879d0afc5d35c5827df6e407f5d9c0bb6a05cc71871fe072fdb7de4ebca5b7f6afa38eb23e212bbdbd3bb1c4633bc8649cfd56eac7705872ae1c32e4bd2068adc3795e6adc4b65dc7a0c9f7755685ed2e23f06f7c61dce1520454bd01987e9e7f63200876133088779e78bbac75aeb8bdbf6efd388c19815fde4c47c479c8f65ff9ec03903c013fefa51f69e3f2473715c36b05ce7703372938bde417dd0fe642379aeb1970231b40ca27384798fbb2c73ffb7422af2bf728101c9ccd88e1b85f23c6fb27e1f140ab5a3bf5842949ad2ea451d6432da63725dc79487398b9850148aeb273194c7173df07d462c8c0d0136d0eaa86b10730bf531fd5f93141eb1eaf9e688f4498d3a5f9b4a4a84ba6fe68a91ad1b5e43903399315dc7fab250bace97019ce330771815a3222a3a5f6160526e1c372b4be257ac49939eee1c611bc752709b205952a89196c489f09b3626323a4b03463c44272239452620a5a984848589bea3c45825ca5a90a94e3f739c2a0902f30d7120334e8acbeedf3e9ccc31cc93ece23fd55023c0b06eaa3f70f9959db5994d863d72349714e3c019a1725b555ce4ff82639025a03ea3709134accae61173f6cd997218700c9a3405796d7827c031ee0be8e6e9a48d6a3c4aa77efc949353e0bca37262dd413f8144607b294834c93317bb244854dd8b63dc4e6be61ca1aafb0fd46e4fb6c721edd347fdc8d395558b16eb18c28e83aa91cc5305aa0def160383e3539459f353918935f0cc9b9a03fc09f5a843b8e0e488bcede22677d24ea56b2a72e0b49fc2819e52ff709c4e6589bee77ee984c585ca7899be59e2feaab4881072be9e5b43f430c5ffcec0fcc8e2c249a4ddf46affcce5c5921345827f9a755b6132c8e64867501a9cba991255c29e085320fa06614170e397ac4f71b041d8a11d9d222c38961f76bc84f902db1f1e6145c77273390eb434f0ea134b37c124427267c1fa7c31a18e3521d02617007ad167261319f12fe730a59e314f1cbbc08572b635d1c434225d23b60170d778a112b9a72b0a71b2891114e479f76e606a0870c39f3930fea2c29047ce4d8fdddaad997a073f8b6642d55eae862456c0a0fe39af15f77e3d98ef761582ff5297412bffb160149a5e1b7bfdb574b5192804d67244e9f32345de08d410416d191676217653715d581b4a72e6f169d046ad552d134f9a293f8270a368d79d9691ff88e8b619f46472816efdaad28855dd7f10ed72c3203fee0384ef168a7197f084f23c9a11ab39b25eb102606c017973e123776cded744902323f9532da9403b0bcfe0d1990919b8f9ac3bd4b2b487f93249e672d19b93853b8cd16254643af8a42856516be1391c7e4f3f93e0ef9dd1f281d767a08c96854b04c6c9ad96a21be3be25c2c8de2e47e92d777cdae81be571311d72846a5ac6a221e84c4734b59b056aab7c23dbb3a6d8f835f1c89b0f20aba29b72de2ae823e7c61b13d018a83fffa7dc01fdcaa414bdaf18ebbb7af92320a8f15b06cd8adfc66eeced9863719b36cb91eb8ff7801e5c4f48e501f5e018153f7772a0a672216529872a3571885b57c7b30316ea2cb9f63bb8aa271dacb31c9b64725f3cc6cb725338567a52e938dc5de7e511058fa98e124e36577bdddf676bfb7201b720bcd3b4ee8f261d800758d1bf50509b842f0e2a87153e3c976cbd64ff07905c3970ffe1d0cabf21506bc77c452453e480037d8fd06bc356b702b5e51a7241644a8c067f495305ccd76ebfa999ee5102f4b45279aa9abaa55b44c3c49a59f08599016671c62624acc2b680b6f33df791548995ed50ee1888a89e95021211569e554c1e06dbc04627a56108e087f3b47f85f7497124278725a2dda235f87244ce28bd5729f87af0013cf0fc66e9506a0f8c35c260ac54dc47480b199d907297b07f34af781771c552feca84c1b8f5b2ec3872636b24cf2b32a1bdad056f720dbfdb00aac8f2e261d1c90411778a48823ec51fe4fe58de9118ba8b2c51240ac6551acdde20b5e28ce065a46d4a0a4875c157db0746aa9fa771ff75a057836f16e244fb0b5c5045a8128d2768b57b1dd45e61aff39e27106d903923851c2701cf1666cbd4f46b83aca92168a82b302d8c0df1937907e7afe6231ffcbc1dbb72f7e63dcad0e647d87ea74d71f3035bc090b7394187b81385bdf2cea1ec1d9102202ea21050a2f1466a4c448a418c1bbd7308c0584b5ced085493ba3344eb281b7d1ffdd13acb96f527a2c404bf1172efcaf6d8527bd311620c62a3faaa8ffb72a15324d809ebed6ad45573eb89bdd6f347d427683a78bbd579fbcdc90f4574727f3de5433ef24fdba9a04f2e00f32ebdd42511e199db92c7d8b59ff468d7271f0e669893e62bc4df9e56357c82d526e0233db430d8e48fdee221934d4daa417237f826d7d37523637e6735e40f32046028c470f67b8100e48486a775d56d1a72435cec5988e7054f84d66c34dd9624afdbe7489e260603162b1c27f9e3e5ce15bc0e6044e8725de20efe3931287c9e5d68c31a43f118eb671d144143c55fa80287ecb440db3d67a4dbe1bde274f295af16937b8302735168246777cdd1486e21e86d5b0f1f2d879935c13de271cdf244bc36cacdacaff9e83ed23cbbd3432872b0b9a1e86d2576d5a5b38e14762b45a9d8afcecf177c41273a198446af7021721f7374e60eea1e185ded4409f71e8d6b72a2b8c5a61f5dbab986b42e1061fc105baff29f09024209c436d8d94efd28ffb1e773b6ccef967fdb5a90d323117272e15b0c1114baef0996e8d158999fe4f52d2f871b15f4fbcc93c890c6ab6605078be5269fdeae5fb7fad15b3c63c4ef23807086a32e58f56b7fc311a4dfb001726d2900de1070697fe5f56ad7a6ee5de51b220ee1cf981287e4a0180685cebd3dd2893a3cb7da8345b598e41d1b546239fd5ec1c3df0936cb273b7513c205a272c190d395dc30046576b0f6c4071c5eb9ea686174e58bcff1cbbccc039281423594bc6c0ea5b11489a024413fb4983a9e37b358ded019a4b79636e5dc63121672cfd912c7416dc288cecdb7cf3b97b6ffaa76b0012fd56e156561b4747927fe728f56ffe7f84ebd1b2230ef6e9014fd0ce70b2a425760ddf41c2f411b5a9cd572c532008d9a37361caf4524abde0e01f09da2d17b15dedf3d884ed64d26c48e72fb234a7985242d83791c9368616db8a1f5c17f4fc4057bd4e1187f681ca36314523c28b93f278d0fe426eb80ef9031bd511ac29b656fc86b0b33d5710b2f0e17ff3dffb8e68ea51037f74a0dab3afb9587f2052d23e6913486a1be4e7fecd972c98875b99219810ebb80be477445e1598b4983b18ab2c4e4156e6239d9956f725674303484269e509b9dec79e3baaa6188f84e3faff0448a5b5fa319ee6b28472e7dbdd8118a0a91a1649e887a889aa24bfd8fd25553c55dd1a472a35c3f3c1b9ac0eefa16e19341d6dee86b635d32581803de14ccd010f8830804e09da82c1b682af9ee94383f68251cba7f0f7f48cc8f63557bd5971e5cd323bf98a38ced15c982253674837fa8a03be561bccb9f74887f7cdae8b31173872bb4d6a5a89947507cd058dedb026e86689ea57ec7851384a9c77e22bbd04d165a6631018e474119a019942901db3880003b091c51336bb5361daedcc085a0a9dc6bf51c0e6c4124174ff25b75b4d08058ba942cb85cb64317eaeb7d24f4affda90bb955e1e1720ff6f0e0bdd77d005e411710725a152bc8121bc2a53e6ec9e3d12bb59892ee3299da27d3a9917ca4ec2c6ee7872f92730ff8ab7eb6c4202e604e998e70fea972a5238f53ee8a71e9faf42ed6d7b93edb967d05150b2e81b800c5bffbfb510d3879bb01aa685abcd61c225d55776b1ccb84ab4aa28f91233ca62dfd88577494722b9d3f7eb547ac19636e74e89260758df08ac6b1fd3400a234f94123be4cf9721086007132fa697cb7f691e5dc520d45b0da701ad20cd8ff00476a862427267243b9ddd0f03751a2349c7ac886a967f82abd140ac7f8014d317c43342a22df7237a1fd78868d4979f9f52fe5fb4c83da66254f748b54a3e43ed29cedbc4c035ad50d5b3173e9c04e39971f190d6d6a8e6dffbf2d8afcb85d9f649a6fcfa1837230852bf3f96bdf779e990b9bbb27418dfbd0fac57ad9fad09a07937e56d03d56004d8ec73f8b7be82cc2212017722cd0e80308d683432600d8c51d354fc5887252d5410d7cff4763bd9e1bcf0c1a81b9161a3776b55136f226cecd00e332c97209ca56308eac6d21739a8864e18b01e5d2399c8aa905be36ceb1d898406939729922a731fdc4e9ad2626d792c5586cf5bb27d9934cb5825e37bee24679043d72640f37d52dab1e3cfd5fd56c56a09e8a6440c34c46abb9682b412413bf9b7b7292bcb596695037c51b4e12e72c09ac9faaf96f0691243618469a91551963592a23d80644bd9c597d493c2e4a37e263949113f923f7113a8881b5f6b229b2ad72652cdc80dbfbef0276e2955eb9703e9f2b08574972e5d01a49bfa4e5b2dda972019589ff3dbd3f3723c1915922041a63962de4f4a955864f33194138f23a3e72f6141755ab772465fc58c7316ce95dc8983988fc8655436b8a5a1bf856abae72690ac147b1ee0d2c6cead8e90e3c6a81c9af1dcbc6f4d983c451166364e7d372db763cb2309eeffa3e87fb9f4d9444764f068b78f0e60b7c4a7e80016643ba4b6e0cc6faf18cd24f2b5b9402000cf19662ffe521e5f0a0b51fddd5683ce13d078192ea2040b86fe06d0a67cf9e2566771ea68e5e8ac372cc0c9479bb50298a72de04a7d484866f3aa8367ad2da89b7cf565adc19f1d9c7c66536e22f8af8d772c0d9c4b328462fac889509e3855f96ca20b3405d621461da1c7ba20d1807264809309edc096928fc9042bf025c3ed7092a0d6e79dd4a0ad21fa32ac0820a4a211222708d2a7bf90da6f39cbc1118a25c0f9e75dce20db85bd6620febfef48d72358eab2766db35b6510746453a2aabc5f5ab532e403e6704df42de7670ab5e725039a9e8fdb09f2eb2a0f7bf3a3214676ed58b39567e9bf2e7fd05b0049b1672e87a00800e93812b70797be487d816bc4dca8d0a2480fcd139e6c921f2385d624def1d0588c9a22d060697cde7c5f99afcb53bbf89da160f86c7bf572c51a369caf1da76c6d44ce737d2e77267a4828e5a57b60da88456aa957dc5e6b69f1e0a4203053968ff532b1d13d45327f67ad5e2e595c88c0fcb46ffe5e82e6276725e1929e5f953b27e354d4a964eed36a70f84d9cb83461fe8afa66d5f99b9836c727e195c24ba63a63b44f78ca10ed0d83b72368c893a97c3b1ec25cf95d96e6e72961c6f09a5c057aa3a427ec7d9f2e5013cc99b3646921afe28882de5b30d6272c06ba8d5562ae8a1fedaf30d555ebe5f7efb343e3ff191e04fdf3e6299134b72bfb48c88d506421b82fd33634e5cc89b8824e5be6844bd83c8abbef8267e51728f8fb2b3f3f08c15bc32e9d2ea72d0d9508c6208dbf4b852c6ef5095a1f84f7236703bef091817fe376af61c4851058b8e39387178709d94ff72de2815f1ef72799fe29572cd80297ba94b5a5a9a42eae4c6b158bf5366330afc32eb32996a726d31510d49cd48fb757a58c30af3142cc02928b383c9f8181079aaefe760bd7227bef2fb44482f57253aa4f8b49bc47cea6543ff802c133a5ede7719c0916472adeb3a50a889f21fa6d003e37b1768b24779315acc107ee919f41cffeea90872e721bdc8494269504203fbbb0cd813807ec5e3d17fc91d2386c8ed9990bc4645e5a7a9bdd7b71b03f0b22fe17a7c1b54b51ced62d42ba3ed381e2ea5712c76727e6f6f148d2b25c11b74fd861fd4dc8df9048d00549335f71959f0c583f134153fb106062e909f642b0c92636df9a9747685d2db866391f283cb2fac1750a872bc8779c853e2cbca4bc5d73e1a53b425557b41d1e6946fb160328a47e42ae701dde476b17dc0a66862ed39cae3dbdce59687831dd35ca697c68ef51565bf7e31e9d27a56f9986ca55d9dfb963096af4b6daf78fd2ada7522c0e0313df67c3e6ab14a09134716951a77eac147683e517fc5bb87359b81b896528ae6c54dbfa535dac481733185ae7018d5f0c55ff3fe74356dafdb40c522f507c5557856ab0672680dab02fef5aa443235f0b9cbe9b5d6fce2406a5f5fbc16fe3f733963c8237214c0fb8540838ebf6e264072238b68907bd7b266e09fd477c8778e22f35cf223aad0864dab2d9a0b8e90a328ac630bbec8d6b21a2ec4d0adcccb492b03a7602a78c644b07282f90e20d4a2c340bccdf039438faab4da0dce15578ede7d13e32fb033addb73e489819ad6d20ae052bdaa97da9c8da0c795d47729195eb7e72872711fedb5d0c5e1b15a48c6e5ae398b8112f96b364e33f8a86ba3478f07e29472b53e7295a8a77dd1b19bf0960e9846a211d16188f15caea2f6cd18f8ee13db72e838053733302901ee427cdcf38855bf6576163dd1315995ad819a64bb9b217285e1021a7a374d966667a01fa38fc3fcc0f128c5c4b75ce6fdc5960e0a9ba3113160b699c609cb555b4b83f7586af7d7f9681de2b91d52183691fac30e6c7166ba5e7ce08259b0d973b6487d981f752f00767911e71cfa13051447946acad32afb70485197a1443616463a593b4a3075ca93b3fa68c094e7f48d670a5989646b9fa3a5c38118562d863b73f5ec21ed9dc8da6fed55a388173b6b5d5012c0cf723f1991c21b2607d3a2cc25e9b8a7186d625354cff43f8c8c9f4b80626c6ac272761988a5fe404b36b6d736efca31b402e7d61c34367b08419a0487a13f817072d711ddca1fcb1b9d989f62604f68868c8607cad815ed8cca5b628b6443360472c0f20f598c14f349e285f69a483e3da8f072e884d5df75d5f9fc099e681e6f7265b6c9181b7bba51d2382542f04c4b59838443bed9a4161a6b1c05181bcfcb727711f635ed20467c984edeb0ed6392834a421eafd9fcd72f150b6e1b7f706e72b817fd39587eb0d464c195b0dde934177b2e79b69ca3437a7b6843cd043a5572f68168e7c6d979f5b00079b48a2d38bc21dc3893d4ac4ba4207e0535c3cb657228723bb4626844e508b92cd6d9f989aefcf3e069746f1145846a9c8b8df81a72c08c68fca54ff61e4fa14752b6aff63cd221232115c8f55b53f37b2abafe7d726d2a9d22253a0d78ee56733f1fc31890dd9fb71ebac33824db04eab8a0417c1e31833704428ea1bd370c4bf754a8135b2587b6de0c4e05d61904ac0bda63985fef0a8d153576b562fd1a81a6b78dab65beb4967aad4779b9c41a6a2e2b8060726bc57a4748f55ba48bf5186cf5b4922e640173abed5a651167d3984d5b1fed72de228e1826c8ccf6e2efbb76d4fe39476e06fcd6073bbd403e657ebc583c4e6e79f2dbef85b6a64f130631233f9fcd1a9883d968c76498134d8b0e855be99a3ee4e0bb18f3f1fb6c3999d064a75bbe98e49525e37d52274d7488656605c6a772dcdd940b4e978b9f937dc08a2f89a04e4ae1c25ec6cb27d5630f8ab066343329de5c184eeeb01e7d916ffbaf8a3fd5a3479b461ddeb5bc5c42d866cf04c84e68e610ecad68e786d29194c0e3f98beb3556d8b7b35cdf1b757fa205e2f2bc505c14b244d2350353aa1f59e69b480e3bf7e837eb1b5c4e65dc83039b7126bd663f065b0698a7697d291eb7ef494cd27cec5c1fc27242cb75486de0c3bbf9c9c8225af67b1ba51f3ee5ac340dbad110dab4c420e2d7be383c5d7e0852e3e8d8787204e612a287ebb336ff3a2db4b9a3a6c66b24ef88d7ab945dcade5c6ec49b4c65f1077eb3c359a6107fb3fd1244741e61f9d684c7864b51d11847fb45ea429d7291dc064e0811219c77057a2911afcc6ed315a8ea42a55b9c436db684bd29b172e93c86da047c008e51cecd21ba531b59baca748e57ebb0fb10cc35026a367863ed64074daad0bbf108e57acff1fc3e2edf06514d01c4c7e3bf90dbbb93af1372c845089c44ad3f198fc176bee48c8c16387db53aaef7e079701320e173942372909adf23f766418fe7d4b05abd18c972058636292c62d2b38239e65cd3e5c672acc69cf1261434dc785e8d9dbbf910091e46facc7e6fcfa68496eeb16fb6cd10c53b58952a7f1e7b163c652c8a463f587e76c8fd88eaa2600a3343a9e70c8172db9ae99ea759d882ae45c6950c1dc4b5b55c0bb7b6fca548672799412203781ee3296bcbaf7a3f22a8056af3e5cb7c88ff912777ebfd96b91552839647412b62644337448c9cf57a08cca44e5b16f17cf38e5fd14a52bd4eeb50b3a985728372655fb2ee61e77aaaba0a5dcd610456a7be361707cacf1510f608e6496402960fc12c5dee5ed50c290977b4c6b111044ae447b477d22fc5c192219060175e887287ecc6694799a6732318a286e3d1fe188283ce0b05781cf7cf40a603140fcf5d329ff4b5bd9608244a2eef763093a031c4010be374a52bb1093e99dcb4ae38728dc60ce41dbd54289b5f060cc7da0b5a7e81ad7ddbe0db30a1cfcb7ed367dc317bef5a35ce24be2c6f37de28ff15730496df4acbc942862382c582c250f653722ce21f9a9767fe07efd36eb55111596d2dd72c9baee2a2253d7b411f83e08b7212f52bd39f5d8bcd08926fec409b5bfcbc4ebe6dae02118af0e18d9b13178e72065d541973b1f25b53198e062bdda128db396d31f17f819630dc4fab3d531a72f1945fbe03234e024a96f04b4346a2583ee954606e2fd908044cd2b14d8fec50ef481fa6f6fdd897272440a59aeb3214619f37b43f6821c69c47d9e959143972f380818a06aa44b7c3759117ce63f94d0fde5001f4ceb0ef07f96486969fc272facaf204514435abea3cc087d82b5a3ae030e86e84b8bae3e25831ad5d10b872883be94d5f5d5f64b0f40a265da82b9867573d436808992e22e27ac16882527242439679cd63ac32769b92c4213e7859ebc85b114c3cbbb6f1cff4165201a17227918e160ce791bd389450be13de28d5089c02d54e0c22a37bfd934e29615766807fd6bfd059bd6caf24d1f1af7676c45efc367122e961934b64129dc73e6a4f5ab9a1d47e3eeaeb2f080b4cb30bbc42af0c23f84b074f8f84b89a07339c0272f11004ee306fe44f3533a84e1f5456f863307382df53cd23e75e76341a92584f23c28408353f3a83701924769bbb2df945b37729468a2426c1a43b4860479b72882d8937648b590b05412956d849d676efc047dff58a9d2ad1cf9866f030860a017240f4bdb1d8d381811dc3cb1a121f65e482c02db57009849ca026a738e972ba20e8e6d73efae7658f290f5ada89df89d3908a505be8230596a1f77a6a437248fb00a9d51c3d36aaf3f3a67aaa799d116fb9da6249d8a3d4e534ab844f2f0f7b47d1503b4de43525261b5722fcc677d76c1fc3fb915a9b2ad7227506866d72ecc1c53ec12980573adf6510446517c2d4901fb3688d3499f55cdee1ce9a05720be1dea4cb69321f0c9c69afdc458728d5863987db41aa70390749efbf08bc632e063193172bcedbdf9109e9c04022b4e9b703eed948c379ea1ca148bff9c30bca3bc9d1cc311435198b22bf31c254fef94c2efbcccae55a669bbb426f75186dcfb89112a3bf53f0ced6bf7f355accae6603a0de7a5120b71a8d17dc94e1070fdfd85495563917cca7b55003a27a06c7e243b6cea5f0c978f46880657f8a133fcb90b0d936f6b78ea85997d34be4038e1fb7772af00677343343d5a28968902507694d5446544b71e0f4391de649b1e455e6862bf410a2c9548aa802982c89723b22bd224580dade842c369216c7d6c6f04bf464e7dc44af4b321a363308e203ef8c7214c5a2a6b614c781544cb368baa00251848abd4baa9ad2b265e49794541e07c0bac45905422c25ac983c544cbea7e8fd2c9898262bef5be7674b461872ab09e6c0a99fb61c6a9f2cfe37fb6f4c0911451d46709d3c967b1228e0cc2e33e35a74b58fb627c639cb548d273f1d36e51f7635bc79da39203cec25fa648418731dbbf166dc78c34d0f4bb45952ba543e83c62cac6d5c2f3cb3213ade7ca572c2ea799881b8c8763ce556761c79ded8eee109f1ec6bd736da5625582922817294a872252635e368d4adef9aa342a2e0430001e9fda321ff5535a2fa63da517259f5c139f47dd50d369b703b3a1cdd27fde4d304721a2b0196608fd700f6c47222328e6790e591c9f0c724babcb7d43052adf3f0863cc4dbde1e30540ae02772ebdb28090b766ec602218000be5fb4834370ef4f21cd2f6462261a77e5e0e830e90ad16ab24d14961e07601b2c014b702b3ead095962c7567dc2e24b42ca2a3008c6436e2a852e44b4a93b4dfa0677f8fdfc8290ce999829f3d39f0b4fbaf272c0cb33c4528bd3055cbdfe3e1540e41dd2745d5d362f227e8e4490505f90f7058d02aecdcd6ee29f8c64ab0492a716413248a8bb9ae89bb8116f776e2b246172cf5499b6596261ae2c6440915a121c41e51d3ec92cf342811f64a17f4113a0431e3228f477045c0739dfbfaaa24c55ea9fb3182a741b59284f6212040a29862497b20a6fb8bf6c2c7ee6a2346d381c3b9f77c81e3fad44645bf2e3137ae79a72f159e95097238aca9fc943c06c9023d661c74dbf19b8bf1c69c9952a10b46372b2586d0363a435dc7f69f5c55fc9bfd2aebe06796e174ba392013242fdabdb722868a9b08880a6cfaecd1e377e74a71e5a4dfe404ca8568efb05a600995edd1f31534dbcb0fdd2bc4cbed8c4c371adaaeb26cfd635b7bcf35fccc7382703b9720acd0dec939e57a2284fc9979ea66f9c783b63c25a802714d5fd5da39440b072df74e13199df5120dd3ca3e9590aa8aa80f3ac3ffd774705a0e240187241c07286f27b80bc4bba62c49b8726a22a0f4aa861d180c1c83a5b571b87ffc7c9ea7271da86b86ef23434cf329443f9a1712f7721196ee28435940e3b37e9ebebf150637b629f87e6a6c81261d3f133838016613dba94ab7cea5fa1073a7ac2f874319ed53d8c565546ee6889e3fc5888a63891e81b01e64a2742278b50a471d21f727dbd1a898271cad591d9ae21f77f7ffc4b006abf715ecc8c5edc5e65a5b5367201b1e040e6b7187042150b3c3790f4f2bd81788771ca9c5bc6c7280e491d00630f6c36877d216a97b43424ca23da0062747418137ee4ce5b80e22d4521143861843f80bb4475b17a2d37d737af7a84f24e8aba3a63ead61eb8d5979729c8ba7244cf19717e938796f9a36324bdfce515c1ba1940e8e28babe288f5089445fb72e04411412eccf1f5a21c6d5bdf49cb240ba68ed6e40e58ff9f1e08a8f94b5e6d0c5076e19e0d5d5d3ecef3119a2cd6685b54a28e1f050f8244d11e260a58bb6a3832552611eaafe993754c40bba141acbc76acf239195d7591d64258d3a8d07235999576f021a424d6483e95e4bf3832fa0010a23b3d3a7123751a51601f744ad44105e37db71b04ead4d2e3eb0abcd108634ffc0aaca53035da8f5cace54d72481faeb382da9f05e9540094e9e47194d8f0ff30c5da8e707929eb68ba6a4d4e36a818513714f7b102d176e2edd0f5b35882a3d272ce2e68e3523be3d97c8772d4c18d20528b90f79a3ae8958267e8954a0293a6ab05484a1ad7720c5aa6603e3ebb5e5b6c604b4f43aa5ebf1d2620c4ec6e7bee55c5d80c79972dfa08cf3072fb23363300f48b852d750a67c2fceed7be3a506976559050bbebb51c5b73885677369b409c7bd14c49ac111bacde3c7d5712f77930570bd504c63ca3fda2df7299cb8893b950b3f8107d5fe7e752db95385a1f887714d4a15f968a3acfe3397203594c9ce4259b5aa1e00bff84041a9f1c8f5d8f2115ea616466a373f428630cd68bb8b2793c93b1d4471354c963b6abdaaad8fffcc0cab69adf8792a86f632bb748fb5ee754facf1af21131f222020f66bba5e507bf5c4a87354665cde23445794bb12e08c973555f29ccb77b4ff696e406169633c8d894ed88945355e39c6e4e8e43fc18bf39ff828ec212d308c996f8a3d00d27b9bfcb50732b27b810c6331dcf2114bb7ffe01833839fd6a342c30f3bd4ce17e28cc7bc396ab6107afbc72d73e3fda20754141c3b73bd90546c32d31990bb42e1c7a1f595ec9421f84f654374bea004688bb499d5cbd6ba325999af165bba4dd810f2cbe4e69d789b7aa2a300b2d8b7c8a82820147ff206ed6f61b11b1f73bec81ccae73beb3b428aa5c7217eaf73a7211b9fdfd08c79f703e16146291d084d219d54287c51716b7678b1508c294cb200f4d26b1b3c7a3e241c9432246caada1a8f45c4ff1862cfbe2ea722672f6aa6bd88630181e5f9769a13c07c557c8eac7888bf78395cf430faeed72ea83f58ff55dcb564c4656817591fa5504f15ba4d50f848dce030070215ec772abf5d5c8d1dab6087b96daa618c21435034bb61c65aa6f23d44c2e3ad7eb036bb9a495d8bdaec540ab265fed10d2abcce67caaa648583538f8c42638d5f586503cfc51aeb7e1faa0e6fe7811d6e76ad19891ff9bbbd4cbad8fdb6e9497132872946a4f24055bc3bb244bf4c3c4d9bf4a190b8c12a34ed6929ba84e70590d437283852caf3996e8d5c661132f52614d22dec210d5d3597c07eaed9991ea3721728864b9276dcd6460ea8ead058168d189902b839de8eef190a7adb708185e414d292bccc8f1984cce016d576384684b057501e1a9410d3ac9b4bc954378333452b974d873fa038ecb1a2eaae30a5e7492f2a80e38a0d59cdd1855a320c0c6b472f72111b5d34ff4f374f22990433eaefd79ea7e4d9b61486bbf407bc5c216347245b5784f4b94abde5b891a793be01aa23a72d5b8ec2220be766fb824c2765172c3d2cece52eeabb266a92de111d2129763271637e1dad070037d10e5546463723443f718778e796df8db1fc866d15341b39da510cfb926662e303896c64a5e0b66ad2ec82257a68ff7d02e86651a938df31c77d6d8dbcc621ade65923132fa720bd4bf329c29d1e9e8114e82b40b8d5dfc33771f0dae09426680252050015b724ffbb4fce368943fafc3024961350b6bbbaadaf6faf32c1638c2002d8cf2b415e215f8ebc2b72b5b5d099b9aeec7737797513ddec76880f1e8561b6fb43ec47226d527dd548ecefd8d58a4f6336b0a0c1212ec8f0fdb5f18703da0b2329683367c6c52e02931bb27b2ea5e2d0b0214a21f30179d34ba599899b4f89a63169567b55e049362d15fc332aabe2ec89b10b9334afad3b1695a215c4487891edf9f670207b99c1db121de6914f6983413a7a5169d455ece9e520e3b277f24c572d47280168bba27aea25c2b08d2fc7f4b91370f3c0be2ed9ac98b8637ba4c9e09664ecbcab4f712c26e131e24ec1f485cab6ee7084f4086fa323cf0653cdfaf8eb87179a4eca84b4534a4ab82d909e90b7baec6743c5eff1c33b6397a024e2d78887253b4c617c22ee8f80087c477b4714db67078de2b64e957f08733c717da68c272d5ea28627d6717c6f980d1e00f9bde29a51d6b25532d0f93b6ed8e7a09a190209e0ba1fc17bb24ca4b7724942efbac896c3b63be6f4e4c9ce1e90b788152b372b85c61de71aa6304d681a87315f42c44f852d5fcd52a6f40f6f9bbb12a5da719d98005b2daa8ad5e132c56b914af10ec8469e625291668c1c13d138bcd71f1721cbffe61a86ea5ab9a96fbadcc0157075f354fe0c7f411a3f2bc2f80b187fe72a3a866ccc9aae43abd11d83ee3e0aa3dbea0046531e750f73f5b6b1193d69d722fe266495c5be95ca115263a3529421bab96c0d6feeb0681546933c875f8dd7236154051723d56e03e1e01a2ac7c355982eeeab0cdb55c7f4b5ed3cd651b5c72538bb4f34f0c3d5747538d3180552ab3b7d273a2c11221749269b0f83e267d7280be2f703f2a66873a931cc947001d4838a34e3066b9333b2ae4bdda882e2c725710d5b9dbc6df9ced8eef66e573c9088d883132be8dd360795e183830f5244245a4b6e1951a292334addeea593be9ec733035e7b7fc37419efa599ea2b6d7726ca220dc70ca78d3a73a3c7dabfcc1939514a1ebb3e83e5db4a546d94ab91272656e7919051a2fb2ba4c85d7447de585ebf515c6080bbae672defc64a4f25543e60fd6dc1186a5eb50802711bfcc942c00e7ef8af669c7b471bd211eeb3b73726f03794e1a1a5cf237253e52120c0e1ba953bcc3e2d862997ef32e15df857b234d7b7965b2880821e126f8d91b1374de766e4bfbef247c0a822a8117c65df21c7d88a59088e9a1aa9eb67e3e348e41dac33b255045c1d7eee8d71a5cc8ad810c6d5d5bdc59f7e3f73efe25e52c168adcca13bfd90a4c303a7accfab871ce7839d3374c676db3b8887d9b2c454e0024cb4d4e5ccbdf1ff57b1b7081b5e1d6c372779b0558d48340bb19753efc639a854718562968304e2f822ef930f2e1daf5450812e282fefa934077ca720242f3da07280b27eb262195b792a7d2191e3a8f72ecc908fb7665b7a1d6c3d13ed2e393b6d55d380f88427be76187438e62ddf1728cdcd856ee2b6e53e26fe3c8e6e7ad2f7bbbb98d0f273e7524bd6ae3141a9f72f34672b3e34ec61b3ef09dbdc272390946dfdbd41a8aed45d4af6171c22a83729122a76fee4b7db00fd6e386c28ad0afa340747b3849c1ed3007546bc50fde3c028e862a20e7052201c9b0fdcd21a298a1d095ecda6b9d69bf8b5f857079f84bbe4e78c0245e7639546108f68e7f5b53f9212b11a75694e40a770e9837660c721ca5e8ef5ebad38008bc000ebb665bfad340df58b751a0c3fd110d03b5f1aa723f6f3ca9aafca184549b2e8a31a6d6d56d8a94e0731e271487f1cbae6484a572942028796e6fc75b5bd53cb1fd8a0b2493b087453accd9e702da5459e346060811def4e322ec1f52986125642934ef78952e4fd3232a41035774f021e75a5a394439db057f2babe760f3d0bccd7020bc94024107bab64d76a0cef388da1ba57232c99c7fb6b373edb42eefcb55f423dcf3df9d32cdfc937d7c8d5637fc5afc3d9709892fab30e030f247d364a86bc87bcfc28f9c620962076fc58767144a1f0ed963be3fda3d63e8786971899119a03302a27a622313768e42782535d4ad6815917c5fa141399be6065132e6fa963886c3ab6c8e06d6f382bdcdb4181bb3c87283c8f11ca9949995726d24fc09fe067df5db26c31a0d7d6e9fac84e10be05f725f53f3456a5d4dce47baa145ca9b5b84bccae9aee826d4ab2538b6a11c9f8a7216d6fb1cf131c935d2dbdd370aaa67e1613b85d059395fcbde61c16425e39572461ef49c3e148f90ce920dd3545bf47d365e565ee80ec03fc4beecdd4ca72a5b9a49f7619fec229e51c4d86433aced5aa94d10b234c50312e10eb04c3d130c72d5806851e0ad56c855a327564835aacacae10ce5bce827b2c82c932106e44b72ee90129ac196ca3e1240a6717414d76824cfa5fe8e2eb8017f995d51a280df720351c957cf3593de6c656b6646143b2f1694a0fa01933f52423e8bf7d0e24f081201a24f86a4348ae7332e0ec3d5e9d2bbae62e2081e550ba4743fab09b7a714af465add7ecdab2b562cdebb1836fc42e66468d3027332c56b4f45916d79da72c9c3d990b573fdb9393791d2784132a14030ea3093a5dd48baab3824be89e707b3e6a3674afc28bccd7c03224997f43a8bf5d9c49a220bcbfd960edc7db99d72715c057d8556eb281e5a226286dff50d84f6a64d0308c587afd90acf3b41ec722b56f3d4b66b47d951b41e40b21b34e172a870ed4f6f383b52fd58402512b35dc81fa11e2ae6f3c3bf69150a577a526d178ef30f9ae0d1b62cebda245a9d53729c5585367ed8c9a5f1ef821de444ad53411c5b8e8288400a1039863f0c61e972dc7911c0c8706ad0e1494a614f034ad4fd13965e79a91632c625a2478bed3572e48e61e6ca439169eaca993afb6cbaebc0a43a89a163bea978a01ecd22bff972ab02b9c3d602961b19160be459e108fd5f36877a988de497c76aacbd187f3772cb19da904d2bd9be75eb8ef1e3bca11b9c4bfe1ecfb4fb283c67471be6c97049f38b841d64f70f24b3143b32926c845dbba1c4d79fa075dd5ad3c046c2a1063ba1f0c742c22a3123c866737df9ad228666a9564fa2e4e6641691247ab5374664a24585e9c72bc359b87621aee1fe18369926752643829e8f6eeb2883a8aaca53cbd6407d92b51b153ce9e428483795744a2a15df7a0f59e28fcd7608044b0e2acdd5e13d9856661d25280d1393c9a19f58b554aa1b09f3987150a50228cc504dc6a3e7817a335c383356d017d542abf5c4f36131c883596e3231733052d17672985603c601d3623842884a4aa21696febb0d976d21ea60661e8f8844255acf7222254866bc0638827938504875b98baf9e7e06c1c48f9a21fbae8e05e46c0414b63cd10a5fe45c267361651dbe16b56a02878259d766fbbc274a2e2399c1c917ce33754c4d07c238e85f5caadb379aa3264817954ed05385645cbf513d05ca4564f9490c4cdd65441ab839db7b86643ce7e1c3f21a1528dff74d62bb00e2fb723e319a539b97d02c97daaef76ea3dede0535cc07307983b72ccd525422138b724d1515f9413a6bf232580f24f73606948e4061d06584aba600a460073d68221467edb9775e00c703b6781cfe68d171a7989fe246d8ba99c6a94ab2be12006e728d1d001de3d920b84b79e3f982d148973f5d1f10d65010a69f63d0bd8ad92e722cbe168c1379712bb31ac71b9be6e4dce8a87ffec7b3d68284ea926af618ac058e3034aca32ef5108e9c0a89aa772f6bee853fa78b9f866505cdd1146ccd297226b1a27ce1e9ae8a697f3c6076a042f37dfd0afa6a844bae1bf53e8a9db0730546b5270cc9ad8273513d994a1a1662e2ac88258cd70bb2668ba1c9d84850ce59fc768ac6192b67089369febb1e5bbcd3b666e2b148c0bed0abd9dfad37ad1d72b3e997d61fe6633ba04352c906c554e3e59fd4419dada39872896520ea4e0672b1cfd055c40901e311e90e29fa1d42f75cddfa4ba8b9ad07be95a2c7a61cd17232b615726c999686f7fe8832a74d44557b10414439115e272c7447dab4ea0872e4dc67d8064c63f983271e804ed34f070d7f8e5e9476ca710146c9cc68c2812a431fdf0817ac872d619cc0e842555939bda8abe96e414b498c252270c715926edeea1fd27a0e02be1e522493e1e9045975da468c29f7c46f663238be29efe522ce198f5423aed1e0e4e57ffc7f2f1b37f789b21322e497180e760856a76e806a86a2a8e24fc7970a81260c2d6d043b46f73b9e1c44cad2d649dd607292077672fe00cb88ef1a381900a6a47984048882fbd888f52f5fc10e2b104588309fa227adadeba9d131cb5fb7f882246dc94b73d4fd88a7438396f29e7fdb87a66d4c72caca3979b36bcec0bdc03d5b1175bfe216387c2f9d5043bdc292904d0f06e572e0c5329455a8f65d245024ebd391b0f79323f13787801e334aa0ddba6f1a527270f1bc9a8c7de42562c7608ca010b508228f773a608a458e6695937182b1fd5e6172b73e6941f836d483e2e9b0a7134fcf589ca72568adae65984a29925ef15be390f2c0a6fee2b78419ba5971ba8853c263f3f980f24e627c288aee9df86452400c0b268593411699f4fab3f91b9e4dbd96da65c6000eab18295bc9e8ae347222a75de6f8066941942a25c98ac9fbdc4074ed24924a43f0bec1809bdeeea25b839f0dfa5cc1d70bd3ba67f5e1170805297f91046ffee1b7fd8ffceda3a8c0726371fa206769519f68e89cbaf4969ef9d684caf39ed74a1155df4d94ed3def72dd4a30b9b6bd7c8cec4a34217369261ec2f8ed167a882322f2a6e0cefe3ff172898701907afac64a2ed444f4d7e958503cfdbf02f7f1cd7bc8f69f50b73c997256cf8edf73d84dfd9ae106b0bafc87b99dde5eca9fda105c32b5918f8a51e07298e1ad7a85c99708e9188f45651063d6f511d476f423023e3a305e1392ab2424e302258a694b4a0ce83f4f7be0e72f39ffe00490c8f28c94ef27af6d94329d428a51f29d08dd5d574b4a75dff0942fca213fa1b6f103ac8428cefbecc17d5172999e22bb62f0d2b02682beb9f347c23e3fd87da3e053b9b6d543301fb2fe59720d30be8bea38dfbb76d7dea54ddd51e3aef5ad6398a42649a9b2bc5299fdc672b46f4f4dc9281b8437f3318d2003533f0c4cb4ac344de6f063b23bbea78bd27291f92c80edf64b54aaefece9180f2124c0d022e796ce96a52ea58baca2c8d90fcb6960a6b906212ea41a900d1d86baf2b48fd3ad1761bf3dc5afc5af94c72a4522cfc7be96be8515140c0ebae1f6b2a73dd3a803ebfde2564d00f46b7747c672170c7eda28f50baf049a7c40398bd7e80170d7bc7cbfa8d08d05c55394b8fd6c7f019615c94785cca6500bb9f47600b29aabce57009399a546aa31b839e50a721985cfecc4f61a19a7614f8543afb3c65f1b0de7c0210d0ca103a1515e5c6a72dcf5b0e941d5de5f2ea47ab29f85462282b3fb30a6d72ddbb462bac638ce3372307265290ac5e0965c9b2586e7a6c7e6e4fbd8f5bfe102b55ee8da137a64cf7221f6e61388520de348bae543c4d2eacdb3142c4f4dcadae57a8a685fec02477224760de1f0166125e1f39e58120fa4a75847ca74aea048348ed4a0d2f43ed96781e930c225b1d085a0f5bcf99e437bda780d20e62b3abb14d598706d76fb8e72edcbe6ebdbb5a704e486c011390975c9618d22be7992cef849cc428a73c35372feb2199b7e55086d760057f1ad6c4baa52c9814ca62b5778de54df5ac42eb47212d1bd1eed110746bf320dc84557a4ec7a91b7a679fab612b8dd734cc675167207ae31d2de80232448afac0a24b836586e4bcd729d2315af02a730e31ac17c2d8343abcaec442002f171a957a41b19fd6e3878de524e876ebfbc3bc745348d72ca80fc45be465a0deed50b2fa10287e75ac990e05000546b0d39ea0c1cbbd4726311800bc9ac5950ab02c669df332948166a099ce83a915567fa23423a45ae57fb8a3d6a9e9b2715e30234b9ea88ed3c079e980f97dbd76e9d7e5cfbfd287b72c1980fd91732152cb3f25ae17a011a361f36d31cf8ffb9612802033ffdaef43fc450ebbf9e929d8c45d63e8a1e89afa9a3ecf760cf14699757e68edec9fcee72d59da46c0f25f6de0d85335d7a0934411c3ab1314d68cbed0a00559ea1adab726ae90b9db8e9ef9a43a3661d705f942607a09c9a0cae2984418c395cd2a27b723d7ee24717b725829d1edde0d8655725cb48b3a0257aff29735bb7a3a29af1722670f9133cf80367ce1e0285c30f7585852fa8885b8b2517346a2becd8aecf6cc32bcf70e84f99f631e956616aa9b19c536157611f6568003487540ff2ebfc60a127fbe9a38389ce44cabc085b21cb9aee4fe339a29eb2eb234919c33d29b6723d3f02d722381d1f20333532c848f0b3032345b897a00ff3dde4d0c280736d66797abfbcd9509e56c672b4f40d24e9079f32a466b246247db591d20824eef35b2a7af937dc3e6980f919c35a8e6d6f65bc81d58ea2567faebed0bd815778ae72938c40a18a79f899f9008cb10bf3d052194f755ce0a838cfa881a3046c1ffe72dda7588075618dc9c093410c8638733675c4ea6618de0a058d300fdfba94ab7218556b15f2bb832f114cba32c54cf8669b098f009e322a8514778371e139ef72e3da550908b28af165f1ceecad04f9992a7eb406f7e18eb47840d07f3e06086b08622db412c4a9a2aa5af714664131ca5d7af2231cb62474828738e3eedd0b72dd53b5bde69a930dbff2bdbfe41c8030cc35f10e8363ad0fc11728d10dcf05728f4f5fd4f68a282eb65f77e3c7878fcc34dae8e4096009fc2877ac10cc570404364340f0cb280d806d028932c4fdd84e10e00e78d7099bce17ccce5ebd018672c54a7fad3846a1a31723796801e37b603840695b338938f3cde7a7790bf7c31a9cec733615ae6c61937c8c2351560efc3b757cdcef179e4e668a059d147f6a55760e7af501940c839ebc9162ce93894fb73fbd73cdea72740fb9117853f7e77215ba55e7d0a1dd58e14a348081ec41ad030adc02d3ebf641946a6c816e21da54ac501e63965c017b5798090599432d70699dba59546accb332c7754a99a88672d94ce40e48b404e71782938bbc1cafad2cfc5ae3a9c01293676f4924264dbe723234de7811b40c46358c04ec9c559a5634fa03c2c852e27e044e630a4fece81c533ba8fccf23a19a4ac9d927010fa97fa2f350100d83175f0b9a414bfc5abd56a032eb1b9c61cb3ea2583bfe24bc22831ca21576303a2c9639fb223de4397f426c5bf7ae3d7589d5fd8cb2ef5c352024b1876bb2de3a007cf7c511086ec30e46e41d8f320ffcb5fe9c6c9279c79d76f19602aa1d5438772c0fa01376164050720bf1a68851c68d1c012c750614cff8498103c6d6f4d5f749235bd0e800db4772f7edede2a304266bc05143c992c697e39394921ae42c0433de177927a6df551c9083f685e151c99526e922887dbb1a8d4640c9214c247677cfbcbc65460d3872a4e52e7651df65b9c948e3e43f4fb88c540e363c751bf51d9da003241423647252a9b4de04fb8bb3e37dbb80fff0ba6f364881a70bce0c3b17cdba88d5b40372efad8db8199e63c0e40577bce0a49364c53f34bef1a8a70dab5b7fb51cc47c7215850e73540e828cf370b5a7c3659621c1221b6ee8997a9a2eff93d7f0b7687255175be3ea9410bf20de5bce3640e107bc6d82c88eca9532b72934f12654b6576e87ef9b0044485fe8331f45c4a57a30b994634146bb95ba53b49bd069fbd772f803ad74c1be93e638b4d7a4542280531c595cee8aeb766046e38915f2e97e694db4b9884c0eb7da636e4537eb9460562a474c658a8bef1bf280b38132bfcc502eef3d70c833bddcc9eb682ca3a1462998bed730232dd6911968515b6b60ca725c88af15bf3d357830bf861019b79275bb388a6d58cc205e6021154bb92a8f675239f21d54dc80be7d143e931d19fc668ec131ac1b8fa4657c68d5a2a36f7972dd7dd391628862c7b7e261ebc2802fd035ee25a10cf0d77aa0118a9d8763ef721b6a726a6c650c58a5ad508c5fa9ccde7e510695e837ed2f0df1dbd146e44160da0b8b117a9e68da3eb38dff902341954da795bd3d093bffccbd712c46f8041824aa2227bb7334efce4773df91f40d4faaa1e6aefa00907d7eafb8266dfb127293b1a6ddf9824180310ac42952eded6b7fe5aba3fd1ec3595353d4e2948502588caddee777b1236c5ae6dff62c3b47a2d7418b5a0b3f7b76276b91153acb1e72d2b5a3b7fc18e4e58cfb1cc012f94f337e870642bbcc4b0b091ae46b84f37172a1fd35e1d3e63274df54f0b471836778fba4557465259b31e6e05d63d1faa3188c556e79289a9f2f5ad53c673722067a47f9e16e0ba0ec15b05c52447436f6720d9c2d91fa123bf6dbfe57ea54b6c8f2f81d924878d0712b04fb0ed34caebb726a8bb2c56ccb0d39f3e7238cd650b75bf9381c311f09bd127c4952fad972300315a36da1a58857709c36ffc8e0b9d25b056032ca69d3b7ddca72a22a24a3c87285f224a7da59c15937e4cf4922dd2ee874baaf148319d84063e5528939069272b7725d7c0563891c2f63e7e4b38b17ba15321ebf599cbfbf59d2d112cf1e2d729dccccffe577ba6f87e27732ba75a3c387ded27ba71fc689478502d4137bd5724279fcbc9b1d58924a97378de0894bb1b6abd378cfbd7d3c64ee35bc236b7a720403c1338afe505f06ad3a83c19098a0504757357c4d660d9506e518757b9c2b453f0ca616140743988497537c2953648cf812695cec84ca29b7b5dc07e50672fe299ca7c443ff335642c32d005bc07c7045687f91d0e0475bc4c9e7e618a57242b4610d99e24419660c8e35038490d4827bd96ff724f5326db4c23d65abfe7299b39402cfb0655a385b5bc1e07fbf45a2b25358f899fc1ccb9c5c316bb243727a0433b64fa17491839d071b5b21b3494bb3893562a20b5d8a9e14a184d6b372103b5b949ad270378c6042b206dee2cf8c4509e5306cbe60c65233450b3b47721d4460870e9a3a9df1e2fbab06cd3b536e85173db378187738522bd259560044f1252542dfa81bf89e2e2dcb92e33515558b67bef79d6c641bfcd290950ad6721ff1722c72464151fbc5bed27c3933dd8dbde53c0acb0a6c107e0a1919fcd772f7f75e1e4ba9be875f453e415e35eb927a7d3a70e612e2f87172c94034d97e7267ab08ba07334dfad705485702f0447fe2bda6393a85e8accfc0f4a2885da8727101f64fbeecf458fbfe59249723ffd72de74e2e7e4a20ea2e94c81a16f646725a09c93ebc0a6656f099382df19b7a74f23368b9a3b0ded90f112f8c4ffdd2637b5d77617df408c1d217b8fffc48b01c69693aae2af5f0a5b7c99e8203fca40a482a9ce79df9e524ad379c006d88933cdbcce4865b200250ddd2ccfcaa66f3724dc97c66e8b0fa36e99363ac011c501c01911b7dd974d17ac28e02bdd2d85918ce27c3b5c5fa6ee0a7d2940c8b63a92680c2a4f5f4cd08727ba5d33402864d72243dafb060e7fc7ed772f774618808468bf696faf6d3a362d4a82eda89188172379b2d5703d4e14d117a88697e63e9e403fde7be2f3f43965114386378600b72fdcdadcc7023e363ac8527064ff30fd884b88aef9df62633b7ebac5395f77f72ec06cac4f678ef32667169971231105133d1641e5846dc592f722a3afec04f72952f1ebf07d65df6fb78a905988d7a2fd831cc2bae9a1e98abf38b984239354c7096f34dec4ec6fc02ad708fda5e704a85209e6330e33c635a432ece1a669f72706962dcfc0e3a5dfa5144c4f220f16c05ac9d5a28e47c62ae7446c1ea60ce72d5601c112b9c96e22a0b8d18981b108e9279de9d2a796829d23374726511ef722e2f40816f4e34a34fc33b6e3657d64439259ad9a085575b345e2322be0e3172a18478f1cff36b66e56728b4795f7cfa623cac6d63b7c542d2cd84afa98089350ecc2c530d4e3543818e7b753c8091d266952389ed256ab409530b600f508c639b44c8dd4cfda869e33c1f42d479a1cce8f71534713e32aee28638fca1c1f61158d0503976ece1396adda422436fe3a01e87ac60406e4b6e99b1325cd1e51c724a7fa05730f0c7f9bf58d1f3d96132ded6b006dbe7f3bd04096c7c58d804244a94a21a5db8e8bcf3a2b1d0a51d6c180b6ff687cf7f367803cbaeb62936477a3ced444c46745670a27e1fc9c8317daf2d049129dd7cc7389f20b554e0efd2f9177ab868bdd5517dcf0b1a0015c1da8082bde91109efd48fa4e0949a4f15ae1e72501a4d41a4e3e3fc9bd39b9b10f09f4bee7139ae7874de3023ebf3f6353e89726e59b47389500c3a9aedd4b144779a4acb99e0dab253a16f9b673e6de592194e02cd112b59350595c78af30524f91255382a7abc54f67a14c5aa3dd03515f04f290e0e84fa349a260b468b594fa7b8e603c05d42a0de5ade3b7298afd1aaee6b9167deb87b266c06cb5702dd787ee017699466d91fd0b7cdfde62f1db0a82f1a8978b3492e0c5decc135a112c079389a0894fb00796944895f5ae5d0dec45e50dc34ec04df6934800a2a728ed7666fa2f597ac442849d4595edd9371a6b27b67ceda4f7a92249639b0e5b4e7894c10efb39725de99fefc2f80cf71a70d3b6f61185450ce021d0bd4ccf9e794bfb89024c50b0cf022fb93eff7355fe59aa1a8720ba0949e20919e8bc780d280dd56e8f6d84dd8456dd792e55b517125678e7557cc5d22046d892ef6ee560bb0df4a3ed6f70d8ea6d422f6fe957458880e3bee137d27eeeca6f8631104ca48a43a97eb5c8ab0f3ac7c4c78d7395e7fed146e4372ac68850b6f1a2dcdec1fc2e2851eba8de1a74ffc28c37caf89145161abb1d328f14312be6913f8219ed169edda79e0020c713d02e40611a7ebe1eaca44344963a6631a7bc999ded20979aa5f51ca88d49b4e9e4965fec02c896731aa3f982732b2aa24ddc8d1f46da15fb709604fc169ce5303827d9d77e72ac65b95f247483fb54aac7757ce676244021203208291920f41ccf9ca49f73c89e649c4bac4a87287bce46342c558ff8ebd4a30ae830f09ac3931be5cd7874730c802a9d2d6ff1a0e6bdcb2bcd66d0acc36041e15d7265775b316bec7cf6acba9449c6340020504c3c60d6724d75ba59e78c5a39f61d597bf5b91c107ac5631e0005269dbdbe27292984da547889df0eb8b394e30a98a38dd01b972953e0248c14f7d2759eb5f72e408155e6c329c007e982c06cfec65187c59e687b9c7d7c18f390169619c2e7232509444ea87ab2480c016067acd1cea381cad1602efda75e911380a0842d67210e88339bb4b444e0fae4b76ddf75880430648a76fa839211af034ac3c93a572acf5ddca172660a54624b866201e5ed147f24831a5296c64a858adef8a95a0720945c4beb9b2dcbc3557638ad996b414614f2368e05598a3f94a665188b88172260a62f756ed88b9ce29e6ec118173d95894dbf8e0190bcece2b4b2e5545e072b7fb69b96d91f4374bdf0340bc16af1f952078fced36c25c893e74c082ffdd723b603b5363dbbaeba0c97265e9bb55e3312c7fc5dfaa92e6a58859d3c9ac4750082467a43c99fed9aeee5b59d67120797d5b1abdfa3d1712665871f0a5449b0e3060939e76a5b4db126797beb91c52ca07945742c3f7219760665424cf8f8a7248e54c223c4fd453a50a2e61385475006e2772851764dbebba4931e3b291ed1a140abca067c82dcd60fc241c210163473bcbcd213d06527b1b719b2d01b36272d1be688f3d7ad361022b803766902fc3d338255c068b68e900159ec94fdfc02cf449913d12285964e1a3d58d5c6a1f4492a6e13e7a4ba99e77f9e8736ce0b972c7fec21d6f59d97c700eff0635cadb4e1b9e5dee9301965b2856088d55286e00a0a93475e8d63e076f7ed141614f5e194ddc4ce443db5c7a43e69e60cb741672953c7d057bc6b755835127d6af0413e01d8b23158a28f4bfa290727d930c0b2bf910e0cfbbc418de9b078a5f6308d20dc43bcadba4ae7f172a78b160a00ed22839dce48c20c154a1fa0c54a8a38e5930549d5da8da584484a772e5e608aeca0d87ef01b69b6a6b71057731251f006f54b569b9111326976a2e7a4141ba6b995658e0381d30b26d086f5049c744cd05f427b9dd8d42171238c1c60ad5232d15195f6eb8eae8d164ee3b15d943acc98c3665c41f8c09ecee8758b40204103c2e6bb279d37cd166af209d07bd7dd4c7222f5a0d99019687bb28a1d9c4c61dbb4d7212ddd11d515ac788ecbb1a9b12d1978c02f53357c1cd08c28ac9d4cc3c7df3727c797713ae5d92fff9671e6952e0fe88d29436d0319e36a53be7d227b99fd7721061b734ee0cc334047c5793a47eb170f857a2d647e856ba2b6194a117f7f072ec3493808e964718e3098982027443cbfc86d24bf8d8ee342be7164abd682c2edd281d9b38ab1125acd11198437030118b4c691eb4b8acab0d5c81fd08101c72f7980e03e4ef01a6c7e637e31f7bdc6cacc58c37879b98c76b31c675ab9cc908779a79d3ddd9b28bb32d79300d0e8d3ea3a9cc03e1b876d420ef9dc7cc81f072068f5beccf4fb34f14a3b9e9bce9ea74be7832fa979d190574bf83dbe926e172299f1f7ffdbf2c337faabe80e8bcf091ee9689c99d181414eac2d94fd068ef6107d64e76636b3777ae312cc87602f367db0aa8a2df8d49d70216df0e3f526d7222e5380b30b9122599fcae57425b1c6de0a9a36e50c369dfb797a459f49a6b480e7686c678a83d544283acb24a5b9e0b9b3306eab974e977c0127a4590b9575b164fe0d462e8d2cfc85a8e3c8ad8f1876758f7a47c619efa1146ee24e04535723f0020ee4bb523fbf363ab0b872c6918989f9596bfaf71e97fb5ff2d75556472fb7761812e550f15cee0b4f714c3e3484191057b4669bd8779d619b0dfdced72dc4b18124e098d55cd4dce494679a8f8bfc225b999dc8781784ca16e9478f840e01996fee6b32be7108b1551b585141091bf669e4b8e0eb9061d31d9175d1b7247bca812fca90be684d472270798073a17ebf0f85354c2170b7a4d02c52fab65e392a556a6f62a1f526435c49f3871c775bd37a8f4751332ed22d5ee6957147281417615c794293b89dd0b797999d2823ff8be14270934694890bcfafcfded69e45eb4c6dfff3f484f4cf383effe682359e47d09c056b3091391860e17b0ba1419f12964ee987bfefc6359121f8bff55de8a9500f097af337c0782fc901ebc72b820a083cd049053ddcbc8b3057152ac66711f88ea045dd6bee45a9e09e8c045c6c4fbcb9eca9fc052927265e3b7756fbd93aa9cbe39db95aa48869c1f3eb4724446b7210d5b06f55b1e30428f8b0fe372d734e5180e51b13826ece8bb5933718e75c4bdd904b9bcfe2ba62cfe637581f31468f544d39f84d2983e89678c6d72da7d08d7127b55509bf70a6109d135bb837ef09593fd27ca6fd0b99799175272a8bd21d3ef47767fec16420d56228127490d37ad0f5bcfc9d40790d8d755670e32100c2d469198b5912c684df5b640cf65e45bebec685f8640a1d408180063722c5d6ab43321048d057d55c600f9c3ba5ab11f30a56e822c601eebc2a4c1532e112c4b72e1bea3f9cc71042e727cafc5560f1367935d31e029a27b9fadcd80086a708b2ca17dd58059714a4f98513a2abad9c620b9af86aa594caeb71d358372de588c4d471f169a1f4d7f9ddfd868f0ad9e172242b2385266c69b2f2472b3724f8deffc1d8f2121b52f656e1a3d3c667117b4e0e2379a56b1ffbfb58de7d3728de56d9b5e48561d649e7eae1d8cc3606f69d20f1d27f17bb13c8da01c3584724b29ab0a0e910d1c7ea4d00e532e85345a956a065174fa139fd962c17e1a3a180531efd0cf6d2794e532a2be808c2f455daf7d97ade2268f4998f884733b2b721f31ae94b75a9cca8eb5ee400b14f026b4f6d72f98cc64459845f1b6f9bed072da91eacff982416af3393e1c8ef6c8c5e428f9e0a97a23f2492fb069cd216f10e4c3f65bcc98d677796f5e01d79a55a9d841ad2ca89fc97276547ff7ada8f772979b0e44ccc31913a825cb2c8a03237d5efd3048b2badc493ad9711a39ae6272c1724dc7aa9d2840d7ff1ec82615b8a76125c7bab16677fed7e8ed8df2d5bc5fb1b99487637d581fa042eaf7f4b98329d3c43df3e4dcc8d9340023a1adfc7a50ab546367468cc87be812a606740593fa8f3540359800db719b2d903d3429c84c487981fc246c74dbe4d0a39f4c1e019ea673c010e3bd09976967e5ec1a7ac2100fb64bf0207faeca647f7961a098bd30cdf20678503bfcf0216e11bc2611d872e84b7f94df80f86501a43becc58c1c4f3b0c09f266cebaf0e9f1f5840710c535685d38cbbbb90fe1b5af46050c1e6ef22f7c896a9ce10ce156f28dfc1a21a5504b94f0c97f6d3e71acdb4b54bb900de040d4fa76cc6e9aff5a669be91d3465413ae935c1d2a778bdbf041b239f1c292478b87c70360529019537014644a5cd518d9682e7283fbf66ac5b00d610d21b8c0f0a563b63ef1530ccd11ea6a9c5fa72e4c3a1d5d3d1d298e7b5d4412e7e174fa394b936a6383674cc41d520fd70e772510a05931e4dcacea49f51b6be093c0729483f8538ac3243794176e4f6223772f1d3fe97cb9cfef367e6a4431682fc3f906a9c55621cc53dabeb766fbb4a4524dac1e3cbaeb163d183aaad361b1e66942d2a2e93418e8ec4ad6521a039dff144a18c8b42def1aa195649b10af180ab21c8ce8e2acf5a5ff744dc60b63b196e72a4bd19ef8558a72636dc50c580799b01efda16c72c6bf08d2efa665fdf22b072bc792b59bd8eca0ccdeb34fd91b2db7afa4ee2798e5de38e8cfb52549dd4e872f9c7a4d85ce7693b0b1d3a353c9e99e554384087a24b882af251f73c18e7374dc3b72bd10ff94ce7b6e1dc77bd4f2efcfbeb79bec507f8a56e851354449e7272d8c8714393abd8eb0bf8ba90b91e297a1b5d87f46dd8b21cb27814fc51c8217202b191ca39cfda3e83dabe516969792fe5657340bc6d97101977d4db29ec2372552b2794d0934ee27e9d4ff9d6959d12598a9ca0492ad0e3ea91aedd3540657277955e30841ead0df4ec3b6d94e9cddad3a8153d9602ec9dcd55dba59cec5c72706fe33aa8291c16316fdd40e5f0bbbf3e35c1cf774fee39e58de7a9d939596407d37c48c01ab930a0c449509e12498113354a455fafde4b5be27c4d3b870f2e379e1209d58f2931350a62b05bc14b4afcd224bc11cee165b0967bdf17e8af720b6b0b22c971334865725af8019a00b0a1945aafb7d74b2ac08fd7a7b0efcc72417a74fd622f57cac50e1c4fc4db2e4a38ba531f54aebede4b7cc6733ade0314c34a141b744614854251d96b9991c07f9e69b4afede82bcdfb87a92214dd5260fcb33faef319c6a181d09b8e8f08713a4b0db6000419a70a32307e9ba68d5372f8d03f512b60117ef128daecc551b13f97e213007469dac8cdf390c40001610d9197691acca655dfa3edc6dc8b49c301ae1317f42eec9eb439f39d83a7c91e16957cbe6091ff2a791aaeda3cf5f9414eec21a431e6f5e8332a6556ad968c6b197d64f831114a964cb2711c507f312f1a82735bb36681c3dfba1e2ebf3b1c3672cea5982d50058545dac1fb78e425f5156e2a9e26a10ed8229d389271f8e7a76a3727247983618e943106b7598a36c240fb05c514aa403e7e0b67b815f8776872f7b84181098dac7b1bb00a18fc85a41bb1d95ca90f4c651eb9c7a7a2bc83837294b8771fdb9074a054337bfafb9d8f521f73741dbb129450047f6f99afb9fa08c5b372fbd160c1e8361f5c3794414375baf312a34360ce0a4f0e43a39632ca02d39cbd0fe36631b8e6bf3efe29ee7b9d84dfeee486960e43e0ac8282445ce8728b7037d289394e8b99a442657f56ef96426d2daabd1ec18e7e15a412e84ff972f9f13dd1ae37561af6b2cdcd6a9e5b3b9f7b7ff5ec28ea89c4255bb71eee2d72204fd6f011caec7c20a0abd0b99d0369496d6330859bde6b9be73629808d2b72188f076de0b47544a4c04cab78cf932ee0278d0e9c2a1b16e8e0709d753bd372fd1afeb09e1c0e338d36dbf8801741c0250f975eeda792c6446d62ba265e0f720589aa6a3db78379e0224ed14f52fa37038b736835308bc64ecc4af09b487400bd4c3679de3b08270869c02bf51fd4726ba6b05ec3c88b81c90383433b2aef1bd85e225b1b8a15db1b8f4d9e55224662c08dec6a1505192f2beaf8d7a2bfb772613803a23f54c789b7cda810cf23cd94e42885ecf6d66ac35370a118bb2a14154c906df81763a86ef5cbe0715487289f0f4cb6c3793bccd4b5fd4f3bcf59f750a6294114c1434f8e5a0efae2b1768b5336f46e0e2b02c06835679e9561da0e5c8bd5830fedaf3190ed12eb42474275c73786c750b07207e1b15bcde14a694072bcd227558b088c0eeba622576443b757f916234318c60d27eb332c2daca423729e7a76171ae69f460ae6f3fda73cf235ab6bc3f1cec744ed7b097e6da6ed5c6c8d0b7c61f1d7bc14a15620b533adc97e1956fe9f2d7bc3c00ca8cc23b3722035511cd48265cbed2e4236ef0f512b11d38ec49c4ec1cb81251a054168c7b9a2727b86ff2d847d6e5faeb922b59e2c20f0b3ae43160900da7a35f2df47bceb0e55cd8c44ea78d3837bdfb0816a4d0def6e8d2dca0eebec765918b474ecdd5a83720c89f63d51274dc36f8f35c1e8ec9e9ccbc29f1e62998489eb71aab8f4e70b384bac14a8279537a376e2d617977ef0f2140967e76015039b48a97b0f624a7922a8050a812f955da6b119ca750071c10f041fc157b27058b912ef72c7f006db722b72483084782b7f418671c622c1d77196e74ca792914932940358555c002d5280179a375a4b14959209a28ab07d18cc93b5a13e5e2cf87f1551c4f5d989e772ae541503a1a1745700e80ea24a45bc04b469805692ec0d53002a78559717dc2ceb73aaa5ba18c42c5e027747b73fab20d712de387dd27180ad172bd17a12b6724f26da3aa1e52cabd4cda6a5ea3981b589bf5efa30b216360fcf6d55c1e8837258242940d607d1b1ae8c2770023ad7329fd0921076b3f8298f1a78a1d5ab2c726b92b8e10eccaece5dbe8029df91633ae2c317cc906e28c7bd8a81e846503f72c8d70f5231944cab43823c32e4cc81f85f3285ec541c6b225ac69609c21f8d72fd78ba880582958a69de61f8dbdb80c8a7fe4526c7657d38db1da6e09e3d0d3b88ff34fc325c3fe540bb7bc34119db9c0af9f42139de4c2c9fe1bda4ab8f09721e1bdc00b05de7f336183ef8a6c17ff1f66ca5de4e746495ad6f0a9a8c157764e7f62507536b7321712a47c2b84687de9cf432891665eaa3537035388ba114728aa9989994c4fc19af5c837c70174339aab838d1e683f0499e9b6653fc143f0346f4488065c15a9cd067c9b8eddeca069c86bbe859753c6782b9589b5bbc220a39db46261944ce1283f015f160c86a427f951de6dd3ef06bb325cddc0bb6e7721c0732128c66ab4f388edd4c975c7520cbabfa1ab82c05f49e4880cb9e7d9f176faec5ce22d1754c76e8c86d34b67b372ad643a3decedd8593875c668aa14c723409faf13b55fd41e53df4373ddc436b74f2189342c75aa28ec186d38b257c724b7060dcfa10bee3e5df47fba78bc50ceca5be19fe6a6762e72aded7ea46b072fb4c7bd5029382fce6961860be4898f295bfbfea10ef87dbe26d87af39acc472dc25713ebf4822719074fb97462289e49e6d4ac26580654dbb238fd602e44072b93ace75e9b4a2d62e1d57fdba7ac04255bb574a30a682eb43a2f53b3decee710abbc443c74a3f4367dd31961320dd7cb840a4b7806976f47dc0629484de802bf93e5727e7377917cbb0b6293c337ecffe04ab4f5bc81ef5a615502985241f72aebf2f09ad7260946ce752ad49a07cbbbc1644800123261e1cd826cd979e32727929383e298c1a07e4263193f0880ad1652d4b4a41c15b30e7b0b55a604a471e28f76534ca94473f2fba9df0632cae32a6c5462913e363a5b7484016e365d472d594f42b584730fdf37b8e88c9a77563ac272c11089a19e6e8c43a497189585a205fc0ffa2324107e23df45d220b797475124161a24819b0a633608bb5181a72e1e099398d88269f37c5e39a7f4eca120cc47cbac0d568fdac3281661f2ab05d3463ca7b1c9e2dffbc53e76b031cbcef7cfb72c38e1b61f13b8fa05e129d3d72800561fc50c5dfa68849f9331ab17ed5d70e41aaec839198437a3271766faf72ff73140df4f1a5b4741d1d0077e53c1897e9d8820318e588508626f47f2d325a7be0629ed589d675ea554713bd0f0fa8e505b9982cb27947235553a4eb877472208de9d65ac2ef285dd4047df08ff8e106a984d8ca8dbe32d3be6d4490a856728a917455427f532062194b3d621d1c1ff40b6ea2a32eed24f814f837d63aea72ab0f13217b96dbc0a0fb64434110960c0cc6204319ded67afb2df31979d1eb020c23d3956ff67db71e14d035e602990507858dda5a921b4ea758e8fbc697210f224fd285e6ba7e47102e99e147da79450905921b6880efde56adcbda12818502f98bcdcdb4f812b96f6551b7c89adb6c2ffe8e1bc4a6ede58b4290677f97847260ce23ea89920b0d9649fbb7493814beb1bc718a2151040211657af9ca8ddd72c5be5ebc71548ecf5bfdb2c00972e30cf5c3bbae346ae6f92663f4593ced45722666c38182a2431d83e5052ce97886964398bc814c6a87494eb564157806e542aa92819828cad1b61e5c05087bb9aba432d00633cbd71f98763519cac60ec52fb3444d451b37f9e1dcc9f156126f1769e196ccadfb84b9ea1773b2be32378259b67ade5f652bb1cb20d0bd8d25c048384a0623c032f4a443b734c122f65e6343ed11e838ea1c5228c411b2ed6c88703cb0e4cc38e7c48218969c072b448c137225e5dd5e705e176882517c933f23c7d5167fe52852c8e09bdb9f39d83de8ad527feb6b56bf0b37d3ed0d54fe4d6cc28d4c4ea4d4fa9def848efcf498a1b8876bb534fcc686d4af617386bbab356cd9cdbe64747cb75647dfb8cd10e51bff46603ce507b65008934dde829ce72e3ec392337a17bcbb920a7436f411122915a67249fd0e95485754de0f9496503b432fd5f8664ae6ad032ef9079e775b87c4ef1e1b189bfa72a4eab58d216bd8c62bcb55f36a3b3580afb1546ae0ea97f2727c72060860dae00e0627d1755588b402cdd73ad95773335bc9cc3af889a1085ab572d0206b927a8c57574aab0d8cfdfbef7a585f257f909764bfd76c428c90c6a61db6890d576929abd7630bcf6a7e0284ae157736de809fb829260bf4fd30994b6f9952dd3dd0021a42b9b697777fb1c5a94b41af8f73e8ebbcd86790f7b6f28d5d0659d72a238eb36f89d5c0a3d511f600fcfe6d26ca5039508c1977fbd75c9c723dfd59448db9821fb507579ba0ef016935810e76670f2b66ef7231972894870b8536a282aaeccab763dc587f06e29548be40fa528e7ba0e537052659c5f8b209a887972809b6e8088d117a833c1cc71bdea55d711b846866fa47f916a7dc9d66bea2146a83e0484fe0ab62b481acf7eece7018840d054c955bebaa2fc09539727a22db2bafe9b216c20988f4ea8bea5362a9100afc842cfc1ba1c15cb616346f0bd0da9f3ec216853c98943e8874e269ae312ff019d499551e2b65d6451d5f4c78359759644f9355f35cbfe759282a234eb3c090221073c29c94fdd6b8a3fc7227d4f26584168b2f46e63b0eaf31287239c50387754ad507f8c2600e718d684b3fb36d480652d50a6e06d73b14dd67a3274699cf186042a4e9a6c8fcbea367728c0b2e4e94478f43d1ad09f38fbbab07e644d278afc7261c39f7387c4d5daa6fb3fb42140efba0f855f02b45a3c5481154fad21bb01c57bad414749920323972049bf03d207f86313512091e6bcb5adfd45a82b5cc012a6ca9d9c1bd10815b7271d95a9c14074a78d1dfddf496eb4845d76f936901244c61dbf524a305c6a94eb650033c16f35ff4d82bcdd629efdfe6cf177a39384c66117f3a4333eba8f92c18bba6a733d6f04629e21d4d25c7f67cf1930687bba26a36260ed9cc39528c3b486faedb5e4824213165bd05240b5878afb08d055aed364b8774fe5aab44a3725129aa1114680202620261a62d67c3861c3665356701a05424977017cf56c9722bbfd3de3b0e9512f04eb1f7bd490e5f49d568d4dca0246aaf8b7bc5a1cade72fa7e39d9a28956b203a3a2a777b2c839c7a92cce8e8d164d65f04033e593105cb9bbda57a0b66e56b74f5490d2dbeb2e01b1139ff1798bf9ad6674634dafcc726a70e262fc3d231ce1950a5cbff4a0cd99b0b4ae082144a4f21694ce6c5d4c0ad776ad10482ec32f4588a6a0ff6f5f2ad6635b3d171a97f9ea257a860d5d26728dcf1f5dd79947eae33a86d91dbece7aa8a597c196a4f84b59beadd219017c62ed314814fe4ed739b16c8da88e499a0728d8bfc93086b0dac6dba812f8332572b4d724216e5c5fd887beb9916c1fcb0b3ab90ce71bf5a86ff02f0601ab84897266c0f121693dc0d227392beca8da6701b166a4d7f88fc4c196ad429278c6ea720d41a73bc0a25a03bc2999c3ece2f00c12133aa114385134e7dbf2d0f626466861d8e511335529665f557dc463a1671a9a70074a3b21d61e7ac19ca7d839cc726499654b5abd3b8b71a3ef7c2b4ff41626deb95329f71080a91eff13df7655153ad490fef52c0477ee93157b090f153b815b46bb3391543c76bf07b66f6d62256762e5dddced8236237020615cf93901a4ffe6ddfbb467b18db5875191ba0572813455ea30ed27c63b70ddd050ad99c12d97a62e484068739b7ec2e3e517fd15db998092c534bef98a664f7e0b31162a8ef7b3bee79c99f23ceb5c0ccc9c603b27132628cfbd4f72ab5bc431d63645f82edebeb26f6ae007225ae37642dd5d41ca0667acad1a6d5337a9c6c9be1fc60d690595bf41ad191067e2ef91e31c19720fc8a62f7b0751a140da92832ec5905ecf40937e371ffbb615bb63caa810a472cae448b582c151a6f64bb1ae4f65c285b985ee154e3373c85076ce19171a082f431b59848545a7ab59ab88cbdb8a454154c45830da4f8b42d286a6ce06143e2b50535bc9585f0fe88e3758abbb4fc71d68be3d2ca167562d584ddedf4fd1e82bdf61a25b1ccd17eb1fef57f0edd3662382d034614c614f4a20a44bd4acc4e272a3d63257ed4826a75cd1b25359a348911aa48ac653ac37e7ce0d3803b95d98725bdf028d64b07255e25b2f135b36ad8fdac58fc663416621ba7448381dcb502b87c033c4964f9f7c9f07aa85dbdd73c841b629a2df611745a09c29e01cd0fe6dbe1faddb6cde7edce880d145acae7aacfa62cafe430e9268c623876d97d6e372c67fe3a35313ad95a947fa5c7d332bec9d0670d2e49fd40276891aeaa2ec6c63be48e9f5a3ad55bbeee39319aa39b166408e33a31dfaf9bcf5f65764464e3f43acbca91d25ca5716e244e3b8d719568f42e109e0d22518ce56664bc76a784b72aa592c05e35de1519703e205c0d708ee127b8d6670dd535dfa6f536ae5d3a953d47f2876033d11821126cc93952d61a981becd6307f352783c51d236c51617464d817c02db39181997587270a6246e97bf45087eaaa4f9326ce1b6bef0f10072ef4f1a22adb51ff61ab08434fde1dda2f48487c6101a9950afa593c0c9b1b272c0f371dd7a1d96f9821fb29fbc1bfbc9c63c5f423363fcf00cbd42f131069b72e3bd543a42f93e87238e6633a54dde5a5e797875afc80e43d003c391beec9b72b88e9b5d053aaa6a0b458e8e07aa510198ec32afae89f0408418ec84c7d0283175826ddea2160ea6dc2db33c46ee8bcbb8a48a4fde5e2407ab1ff3d42414db16e1dbbe7d1698975fe3543c20218dadf4ab5d57b14714ffea62de14aadc8a8b304ff4ebb74b9b4eae493e77ce4be000ffd379eed3058504767a6d7e87b9769b067cbf6f66813dcae7a3be4a53756100c360d36b620046fd5c551801514818d472c99660206abe761792c9c3a8799d9c1155cda3dd4ae304cf5fe47fa908ff8372c4c0f56706b33b2d862f79538abaa99113a9f19a0919fabe3f546f04f8d21c72e5a9d773c5f3e12fe0124d3488c4f61354fba8c39a0a6196141bc6c16ed74a72f1632eb5a3cde8e3f73c9ab43a5cdd493856a37c245d44ed58de00c9d23ed2726d9dc87b6f643fc38afb76b49dfe6c207269ab085ba86149567789435ba3a204ec9e1118e2e581622b7110c50fb59ec428e255602e7cd92981e03e6a0b9c01415be357c8a96d1a26816436c462e96ba093e6ac118ecf00c4ca1bec2482813d42ae5b973bf476a949026cafdd20e9fe088c9fd802715c66005631e7ee184ae80c2bc99a05bb78833a226264a77059c460616c4c13c36956cd2e1f226b4f44e65ed058ed4e9bc70f05c683caf9cf0ba0812f573b20a5cdf1ac4672ade97f96a96339316e2b3cbbb9d7ce8c465a040fb514586e5358d95738d02f3bbd78d6ff5a72ca213c0c5dfb84911f45507c3e325d06d4640e3a6460455dc5a0d1e4ad905b097f1196195f3ddc57e7f9a61278eb8a69bc262c147ba855ee6b84723ba96114722befe597c80c2c0be7fa5d686827e63decdec4a0504034d1859589dbbc946e72b5842dc8732094dd4a18b74d536c4db481b07afea8306eb7f573e413e1238872a2d481c022c531c420411cb28e57c3447b4952fd288885eca7fdebb6f61fdc72f7976bc1042bf12d3c9d5ff974194d3aa35ffbe49038d0eaee0a475cb8f8570297327f5e2dcbb16cd10a78b979d6355bdbf7ee49e845194dee7323f6f33360729e1c056e5feb1d3cbede04beb8f8d925296b4889ab8d9a400019a92834235e72e7b238aa7227fc666157ff8b9450e4b244d1ad06e3dda401869a4460a7dd3c72725e168cb6a50d0fa68ee2cacf721194cc309aa288726e9d1a7b41cd40aafc728bdc69e9bbb2231e0e80f39a7093d3459f139a996f83e5bf2dc71ecf31f37957e09ef76a9b266c0ee61fc0f196f551867ea5e85a82b41e5e80bf1560023d1d720384035c448d5757b286d8a134c5cc2e1f0d2ed1218739aa5e56ce739ab1be72aa900e2cbfb6c7917f4d210a3d41000f70599b4079905701f61c91af293e631a2d83779369dbd4808fa1db281e7c38af195ccdbd2935b39efe34e8e8ebaa510e95aae586341fb649c36d05bfd9d98abeefb522cc8c557e432c7a7f3175e94d72345e484c640d5354496c636d4619516976be38dca98ab674c5f6b32f2f21c172e287738b148f5814012c6c5964b30ea4d7e48c02a05e6b3d59de2b234e32cf72e98a80855a4f5149a8cf797dabbefb09821366845a72901d2059b7654c696472c441f1d355480fbeb8a83d0f4df090c9fdec49c41aa0ac3af214ba12e0277a6c90534c844043ed6f264fd77c79b556b9acac94a13cb0cb120a59a638f020c9720de92ba331347e18257d4ec091d5a92de9c47260467ee87e09184ed66c99664f0c35b733656952047b98bf9847d82ef91c54f0a0349e57d58fe1e65b3c8985421cffb055d2b1d9c2dcce76c2622ba87dd2551116e6a6e7e757ac25ccebe6807272f9342b62222ae383007c4892dc4da4ecea3e2e42e6d00800846526721bc6724789f3e50522a3a91b94302c17cb331d36e4eb37c9579880e9e569fac5f88d721b0146161bfba009675a4f4bcd499426a54f42003f243613cf7cdd70e6c3b872e617da162c5b4ffe8dc16c4fa193d0d35c75e1bde5988c45c06c39915f3fa872aa9232ad19cb12e38f565ff844ec6cf58abd73919166d55639a88c7732ed11572fa575410abd270eb2dff25e9606926abb1a4edc79dc60e6c258b88d899641015c39bfb21f63e1ea3253c1268aa27182657023cac52142b85bd84e8a3d4239729b8c17d4028a7a69e6b234fb58d886e19c205bba132944676102a6120e03e0566a5e8210ce5024431fffcda92af6cb44f7761d0e937d76c9f7b4b73b55183272e4ce867489b7fe7a89f49984e6eca530842084ed5fb55988b9f12ea0b04c92722d90f39f4f650fe7159d65c9d0165539c081b0d375dd6d91df09c7cfee83b172d15538a0357e2c1d4a54a27812c727dc6093c686dfb6df921fcb04894c22cb72c16d6cc73a98cf6e85d283fad1f8f9add78a7fe0e6a815810c59dbd2fe249572a48bf6ed2f8418bbfcb13fbdfd7eb089e5a93f6a531bce9ec9db57c4842fcf254e2aa15485a901ffeb37417f3a746cc4093837b4dbf959917982036585060972d09697bb229e9c7975b6c46bd166ad4fd7cf74cd8a8d1a7f853097aef3103072e7384783ec64c85d61ae88ae8c1c8ca1bb9977f362f156f96cdd4b811a8f29057472a67e59a65c71b6ac321dd3a4f03b49cb1055d5a72c9c9d78485b6b1a87723915a3e9a852634a69a64ea294c06698ccbd6a24cead1231dded1c83b50907399894f2486364a8c18f7026a8ef83efe1dedaf916df633fa89a0a9b2e5093ee0ee8a7b328bc54fb4ba1add565f92401681e3b5723437550bb5f2264c29381c67245ab6d7b29a7442a304b7ee07c9027d0e7ea5af4d8b6bd3935d67166475c6f0a709de7047fc30e8a6b3266ffd4642f1e9fee355e562584085b347f74ae9da46306a65930198b590010855bce487eeecaafc076e24fbc91b1bf723992cde75572717350f5287437c7b89c04c5127d52e1d296ef6b0c77d8d9168f2c674036f6723934ec753de74d15f7a2c263f04778c27c4b42e799dcbf503126a01dfdbd63724aae9f0d4968ea9fff835ca6b1cc90210e4a6c9a8531c1c15158bcb44a0b817264ce867c2657dbaf02bad0c0ddd2df7c4a3ac7134d5d3d33a240ed4c7faa933b4dff1ffe1c128f4d91d7851d5e0a23715d3f6d218c48b6d9d320d6666810aa72bf4a1261d97262d03fb8cb7a17171a1323324416410dad54558a9bfc7844a7625109d93c36d3f0fbcc823cb7bd2375446aed863bbd5bba00b4079a952c23517271bc82b5c0a16db70744fc5aac61dccc18903bf91c422234d2683d7ff62558724a7eb36764e2c309d5bf79054a983700fb46ddc362b9ba7d4be11e4cb8e2e8720aac530695f1066ac81580008ece79f198d8e6487f4c7ad8835168a56e48be721f84095985974ad6b54fc286420b7fc5b0cc32a8297cbdf0454e323f9355861cf7aef3d2527c6b5b81adea2e1ba18079e9d3984bed165f86a45da1f67d6ac204041f7758c0e3f995ec62f79bc3210e97672bc6d92a741c8067196f281136aa690f593e768adacb52da4591b3e3fe7468d140afb17e5b96c93471e81e679469727a6ad5e11361ff21d58c88c42006f8929970b1a1c35be3bfbe98d308cd0571720ee246cf6cec91c4a0b041c2484c92b83d2ca9bec23e4eccc841bcfa22d5b1722e4bcb5514d855b18f01af89e50efc6c5a365b81c6c40cdf3e6e4cf66c5bd9726381f358ab7414bc94b2475a8da4416a738dc957e5fcd301a524ffd7937fa12b81d1dfe4eda605eb9c78ab83eb28b00619e1a941c79c61399429b05d5cef590189684430b1d63ab215385534064e5ca6bba3dda79805e20aff80af301a1e095be914859b41cccfe2dfcba01b87111499efba0f21e8516d81535abd9d9bede872438ed51b9e469e53419dc20cd27b156d7904957fe4aa9c50c989918c1828e972359ab59e5ee48ecd8a918afdbf50bc08257fe03895174c7d3df85fb99c0a523780c3d395176f9f3ad168c8678485dda9d2b2c7cb66388ee59da4a7d9add3ae727705b4079520ccd26cebc23bbfdb282571b634b53ec9f142aff52d480c2b0964204ecaa1a59650b3771ef81462f9b7ac4efe1badebe89a39367dc35098bfe972804ff4abdbcac954763a42a1d8d7114c2a5290931158c074acb14eb3158dfd02186129279d2190e282923910e5e24a9a5e06f47db5b1b7b6df40ebb146d8061fb8c3c2085f608ee39dd2bd43aedaa16fcc6b7a49f936eb25a221c5d9bd04fd72c19f56be02c8f3ee988167be0fdec53f2b284b675fafdf7956852dca6dc3ee72d0b97c31c38e83c1327d03d961cad2fccee0edaa63d137c598dcd6b0a55f56109711cf767ae753b0b2fa74162041affc4513b334dcf459a83cf97136f4241a470d0691700af534df8d78d04352130eb5dc5cde91b8bed1c6ff076beda1bad47213ee29145d15fe6ecbed227bafed54f51d7f5375b1729587c855f4669e24374a993781689ddc55cc2724aeb0155ccbcbd74294ce71f08226134f878fe9629d72f408652dfdeb10d76997545793ee608891f436f91f698a1f044de05b049e3c341d71d9511c797d0ecb86baefde8f46a191de626570bf9ca6ff6e824f8ebb695fd241a667509364286e3e477d27e2bf98dc77bfc893bd94c712f224ca56615872926005aead1e86e88a027c448e429f36f6688e0172bcb6329c2d68fe98adad72f82c01dfd260b87bdd35c30272276bcf96f550cef3a1c7a5f15b2a5534ac012507ec1f33c457d84c1785f5469ebb9b177d455b0f232aa774073e90176a125472ee41c7fdab277c86508f947ab42aa1bb46b8e2aa7d66ef7c0bd83c179e218572a2b6f3c8fe48444554c1161e1e7139962e0dc1e58154832aa1a3d169c280b8490b4af0f817e17c970c390fe1df805300ccecdea48571fac346bcad2e0001d834b05c7c5c345895a54d359af50449b13569ef917db6fbfcef394e193bdd4daa4e00bce92bc0701c58c36406d9e88a16925be2b7f679e219e5f016a7fad30f8d72d573ce5084b6ced73bf5f0aa9eeb013ea8cfea0cd8d6ad54289dfa641dacc772485995d5c7265260b448754e029bfcea703d18663d4d9228129f96e381a1b47219524401ba12f20a2040406859e47d236468dc773d838e443a60e8fabe40a672cc1dd01f1b35e201af97afa22444586e64a0f55a80e761cbec49f401abc6bf7229feefe1db7de400eee8c108b268bbafd25d30f1a76d21efab1d241e5c492072e1e10925031c2a8e3ed16a5c0a22ae51cb223ba4f8df032fd062aff82a22551ccd6108615c482e1a56a3fcc5076307f42b07f4ad4b1c447ff1d10cf4de85c10290cc0f8eaa0b76523e6c77159fe938af3da908b4cfad3aef35bca33d5ad3892f9223af6ba0e9826be18df00b0b2a7688c5104ac21a5e2ac98d884d5ee1e35c455e4ca16505278c88fa74c30b4c24c3e4f7670be27aa8106eda1b3278a6ddde725d63a581560d5374b90e991fa98ab1d59b1659490fa92aa7ddb1bec9e171562b77c28438287298edaa509cf790b0c44fba38292a6504ad79b4e18ee56c92f67297ad06475139df3db292b5a09f4aa05176d09bcea8fd992df2e6ba1c29c7e972b415c7eb775760cafba7126d6e455c6b1bce37dd1509bc7e53c02e15883c002e19c5274672b89ecb91f4b62aec5d4272828ac2ecd8baefbab1507452d382093e66aaef3fa58176ef7598fb02bd8ce481a0ac74106212292e3e5e7d18fffe2267ea26e5b82f9bb9cb8ec90c19869ecdbff214d292dfd8c92860adf2980b8cd272c3ba415802cf242eb6ad0884a1b91fb9e36ef0d49b38eff6e0a5bdc33fca7772340673d303ddcacc9a51c3b726b8eb926eef9ebbc559f1f670bf1f2e4388e83cf224329dee275e7a8dd4700f6c0604c9713ef6bf08757024cfac834e670f857203a8da3da9a587df75d2a14de090fe5331ea893944f77e7f49d3aa654ecfa405d6c6e5e7c4ef2100ab314fc179d67cb5aa2f8d6e1a39312e3a63c0bcf95fa572af46b23e63446eb3f292f31441c8e9b569833bd5c78fb7e91b9afd00fc7f4b72405ba49e4ba531fcc670c6049a0439d1eddc5210c9b95ad17ca098af79a8bb5fa120e69700ab0ce76600ae55bcff30c194f82c4b8320e2df77bb82dbd705be72a26d165fc35105085c7df7186c18ac5b0efe4b102ffea10ba78cff0f8b178520a3952c528d55e431c7b1b9a38e3de4dfeabfc7125d37e8e271d4f1d2f84b1b3d32f0ea29bea13b44064c128d0b8923625a7fe294817efc5ffcf0b8ab728c3b0cf5cd4af24e72f2e90e26cdbec0d1a13f516027ac675276068e1e7ebcd04efe63607ac2e200f588313235d950f543e20c53f0b79c1676467896030ea2f2a0946e691643dde9ad1138e8444434eef332aa32816d33a32d62896b91084d141d101798c45d39cba6be91d6f28ebeb1688b4ecf4ae78d2a558437ac1963c524704a290a4377837ef635fd0463730f1a484469e62eaff6f6d40c9657ff6c3685d325568212a7bca881ef9fb177df59fcb16edab7181fb4137d365502a53715908adf7202167a932c27471514b8386cd5040c827d8023d90335535a29bec2bf0546630cbcc47aad98794765a1bd9d93841c5b39c1573600e2662110dde69e14400ece6aabc93dcb76452b141d0f0a6c9edbc0b0f8743312721167a76806d93d735c6a727c5859ae2d9cdfd9356d8fecbc30825403566b8e420ef61afd8db891e5612b3c3caa6cb4d6599bcc87b95ea60f2f784147d5a0e8d685630240064a365c87ec729c8719bbf0fe66905ed647ce3d846bbbbaee454425c0abedfddb369bcfeee272cbd9e78dc26a834b53ba85b3722a8066db9c1830fad7bc4c99b7e8186d3ef172eefa1f947dc402c1159412d7de94526b8321924bb43c7b983403d39851d8d5326fa9af052f8a6aed39c948e39315d083a558e9429214dbe38b69cc18ff88fd72e70f35e19c200308642c5f447eb985c45d387fb7e6150e9b6acbfb82850228110669d92122567ffa206f05cedc4d5a587f24f21b2b246be53b40c9afb84f8372253be4d6f4a411ebed4eb0314b1d1dd2f0da0e41d0d844813e334dd07cf66f72ebaeaa3f9576eeda6a02f8f16e5abc43a64dffc1c2bb86419e96ca609f939472f01ce50cb9b863c17f18177f202dba7d69073add08626e5753a81dc1666a8d26556dfd1cd966c31ee5fa340d10152e6304714064eaef505abe02a337bd48bb65a6c352abfb1fb99a1feff0d0de582dbc20d80b591e186a8f4750f302178cf6723532c54ee9a723b303b27c6b73971dbd7679d8b2e48f913f972000e9300e87179a454d4ec86ea036a1bae832d74481ba72faf37703b0a651351bacbebdffda292b0d3f752fc2618dce864e2724055892e967538451e6efe3eb74b1ba23eec5161372ada73e9bfe8a37e472c2421a1da9cd6aa9ac8e8874e7d10e5a7800719e7268122ab3d960f6a349c07a9b1a60a27a2dc94e4b88f8a3411a4bc561c0adf54d648f65e4dcaa52bf8b8a68b7ee1aedb1d3582120a01b4daaf9035a18628d21725d24ca229604255b1f47af5d3ad068c13f2b5cf8b32cdf06aec5bf044955b272f4f81f173cfc230633eeee7214307a428aa47ca7a9b7815ad96451215303a57228e26e6d70d001a849c28a526e38a862e4da0350f9c56ee6ae763258a3eeb4671d1231ab5fd3b1a8a367f52a2f787850f782732b3e18c1accfbf51b33fdbdb72a441f7e355605976610b99b36a2630c5dc78a616319712e6fe8d47f2ec8b1b1b19e773c52538c09558cd556f9d7763e896dda7490b62009808cdcf79b99941721c1c8679f156f0f76caff496a5f520377fca4e14da9aa4ca7c10105780548c721e98ac8119bd14be4d2cc150958b642296ebd93382000bf46bded1d38ddb8547db553edff984de4aa76e256c8eca6294161f787f23a0ba387c5fe4ee2010040333713a554d87a533c0082c5f0eecef61237a09a5865229ea204e31bb7ef1b76f60f4cc379856fe6c4f4d61a6d0c2a9faf7d6e7dfde4eec63036a9301c1da0572496ad80d2803650a47dad208ec61fd17c42880fe00e45483fe2e7d0563252772abb9fea950d6e69faa02a61e3d4947c86f6289179ad5ca730f706298dd13087255df0ee9c556ceab783214aabc5707c511fdcafc2b0ec8a6a3d9ff04097891721294bd5e1b628211ce954c6cbc42504b5b3f8094721b066fc82a13754b3177726bdbe77e5801cb03f9ab7455a1d7b6fd8516362d3a31bb8179fe8aed49b4f567dd0890ef7568e1e33168fd02d531b177a87eb36e66827a4051c4021fb963bc72d145a8bf4cb5ed030ba5172dabb6dabc621d471bd5d165b35feec4463c2d3c729e33b263a65b9b1b432ba6f66fbc4314c3bb5ecb638fa0a973837d2e8e107672a3979d90d5300719557987785a111d0682d1a60b97d2d3fed01306c8bb0e7a2251a6d504b92268908dbed72d3234a30ce5a46a18b7c581757fed431887815072ebda85d1b36dc8c51d7a9b652efd48e4344c83100b787cd1bc058da2b101957258d239071e6d038521a4bc9c7d929c7a589f7186d71bdf680ab0f2c4b94798725b4341dd21ebb9e33d81cb000dbc370d48cc3f17ab26e9a11ff3638e27a3a07287f80d8ec6d66123edd887e18257cc124972c676c9e7afa5371578affeb7b2656c6ff92cfce4a45f32353a71c6759d2806e2feba295e284d0ac7e67b6edf3b72a3335e871fe6f7646372865226556bf33d9775ec7cef56a314d3bae6f638c821add3093f07d53d352060a66507b921abd4ed07af0d3438ae4d31834b9eabf572871645bb2038178b0d2807e1c921f9aed80c5434bb282eef31e70e74061cb8391484c4b1d55c696f88548c36d8c08c9db1c5f36bec82488079d5a4e01440a20b40be2063f9b8176989cd854b667ebbf8aa2ec3ef308350d55e10dfe33a4d0e7210042cf2098f142f02cd7fe14ad15cc58540980e7de809aad0f3e4458c75d422afdfa5193791982800978e1f5127430a6e0eb969cbc15f78dd8b7d25dde5c166919dd32a42c60ec638ea4dc13678c88e6647cfca8c32b29934200e3f3a7d78720289d97ee5f36aea3d85c4b5637a031c231c2b53e83604965dad6879d8d57b72414ae37e8301f65efc9b53993bd2609825e51eeab060aff3424c0d4ae682197218aa2ae1086ab4c0f601df76f56362a1055a79aba12b29bf29de43bfbc7150722cfe03b0efacbcb91f8ee22a1e129a69c287ded49a53fa0c8dd2772d2e5e477290d97e2f9e24cab9b669e6b593b158caeb1d0388a19ac239dcca7b44015507720236f6b87b843dcdc25e026eb30959b27c92d484cb9635402cef153f53c72b728643a7dec7ff9420a957ad6d5026404ec9aa0dc69f50372c27e29f2cd8360e7249eb960767119af8edf4d5d294651607aabe90a8f17b30ea53e206d2f706b7721b3710fc5a5f4e9094b281e29d75c124bfd77c7408da5af34447d6d589e4e445314e7ea80690c795c353647317e66eda595cec9e7e7d12a14390170e23819d72cd8024e9396920ac9736ea917c9bb0063a4ef6f14f1292b5877a791295c35c72a68430b17045fb23026102a9d10d34ea0f13b424945526be6eb89929edc8522cb58782532b80dad1c4f27e0962bc881778f20ac0a6191786f97197cebd4cac026910eddb1cfc7f34ec37470ae09f5732a1a0f49e2134c39b6aedd9ce485c4f728ad538947ab562ce7cd411459393d1b1f71a0a5093b8c042f833ecae0b763b130b9bea94b6902084f0b769ec97285be800d6177b5f93c2ade88a87a0f44bef62d1f8594dff895439d2188e6b71d8016e1e9c0d47ddaff9e5e981686cf23c6b722cbaefa14669c2c2029dfc95d0da91940e081e0fe6fce0818aee754b8eb20a42810fc700703e8d0772caf63f42b29c482b274191afa971675eb578bb3b8377726da10085cb53bc3df4ab967ecdfb8ac6e15afd6f6cea19776e7d22dff7555f241c7e2f8d3e6973e42d05e3309cbda163367d4f5488e7a1368297b4c94e6b4d720eb56c42ed7d73f1d44834fae6f1f1675c3a8cabd915d8a07a49e5b055d9ff07aa0a57a04fb3617040b0c0894c37a6659bb78cc060e431877f945a743f05a372f553d303124af6db86a8bda35eefe7826065104a4d6faef0e8fb54d428adf1300e2617bbbefbce2af7bd6402aed83cf504c8ea2b686413df8a71f12e67f86b5e0d04061cbba3b99fdff3b859c7d44a6e61c1d5bcdba5fc3f76c364025b46ab44d9e7c897ddb759574dd0b2edbe2a3c5bf7238deb5c3f3ca869aa4a42928a937231c1bea0c96fcf3e27f20737e059dc9bc819ac130de8b9021ede52f3d9013d7239cc6e43534aaed3048953e6b782f063328f37e6c343ee4e3295893c4cf4397270ae35afb5549f57774d7fa2e9ac13221eab2d9c5a6ebe7a01f270d470f32d72b5ed777f6c66aaf7df233a91ebd9461aeee48b1a9707820bbded63225df2a813ee14671803ec110ce8676926710886e3205394b0487ab8f844989605ebcb1172ef3ac6387ab3564e2a1b1b156542e7789ec66117c9c83e68dbef804f17eef272e1363ba866de4fe7b27c91dd4275c961d37ccfd9adf05343aea37716beba0772ee7bec99919c74b8d7be57c3a06048a89cad7d19762800bce43c8bda00e66c5eab5c4d784f6839efe9e0bcd724a23790dd5381e316901f2294177137d45d8a6db858657337a3eda874fe1b2bd4cd0ab223375e46d5d51791a7dc15e81078355b37a02e2013557abd07d654303f2df25e786df0b905926e576d21d4984483497219b28a81c6a1343984ace4e98496f553cea325f170db12902ac61eb1a252e32eed0ec8b6611d039472efd541a8b2c5c00437f84c43dda3d1849ccd9ca2ce1172304c552bb58a40cd48aaa24b50b486ad39236d756c72e26652ebffdac5f3d272a0007a2a260f20d77ee8476ceeb35e1938a2815c4f2f1e1d2b6352904e01a572fa3b3f4975c1ed02c3e6f3dbb1550c6804226f8d46aa310fd0348106b52be83b630514c2fbebfb0648dd9149627ec1b3f1d982156d1910f61749629bed9c3072cc1d9a0f6667bd9369db235c6d994215fb255b9a7f79488bb1007079da6f853c473f81fd069c9deac2f67c4f596650fecbf9cbf4a705376c96783b68ec608172441e518cfc77d98749b3dcd107d740ee8286cdd83656f0e6602e4a0af2745572d5ee4d346f3fc66e40b0bf6a28cada3cd9947649209bea3ff3114101a76e08722836fa11a5ae00c15990c40df6420bf8a3eebf321b9c3ce534304c2d1a1f5872fec42f44b86cb83bcd8621a24404bf5fe73fafa71a9c5177d2c12c5716528372050d8817e20c0d378123c2cca9240bf3f73d4e403f1348b08294b10bdd228072f630e6450b8f1b3ac034f59fd3f3b9b310a329e349f616f8c3cba102787f5039c9a7a2e6a44c23206ed6f4b6fab6825a1758b74317285b11151fa999b5536172c9ab2d28e52654b70b26c869cb3a9afc413572d0d76ffede96dd476e5d1ab6726d86a7a6abff463c68ea783509c6ef8229d217f9580a02b6b51ae845c16814432ca9977a3c3360b655e37bc2ad41575b9cce43e7d54f48ba14638a15535d3a1bab86b00443a02ef1a9b4ed4b76c5215855d049be7eb7853066e1de2b0eb0bc1a59e40cd343ba4e6c5d6b9f2e7e1f736ea26e1417ebd075602c3cbefebe2a0b41449304bf7cd44a311f92cedb9fbff7d23d690d92ccec71117bf17878347ca9727b691b6ccbbccd11c768ef9cf521328c09d444d9e76359c9f988dec3ab519544aa16d7b096d841288d2ba9710746a88810a8fc64b107d1392a307751ef59eb7294594f7ae63c59dc22d6ab50a439aa6575f2c99d5e84c65b081338525a95e6725daee83b8fee66bf1f21bc940f847b5ead8c2d4afe3af053b885243965227c2cda08db60494e67def586c99d38d73f62fa155e8deee7802780f13bf6f223107279624a343e3f6d3549e537623696006c3f94a83bebd5f9c8527c075a2cec32728beacf7f0e34397f8d4570a508802f32f64019c3cc9e15f01b5d174bcdab4a150719f2fc02452c31b63e8af0f617d29e16af90314d827a352bb7a4ebf7f2854f4a003f45d03d39fd2cd31a1bbd2a325517da92019d5f8f753dc16f8f7ebf42437264b21bba2d16653f7f0c6cdd943d8fa3d576455f413dfe7dee2022c713787204879dd572f972ad76135d469c208f669617f31f23d623738dd20ce3b9fa7c722e0ca1c7645844ac23c9739b025d5343a4b626ba43be1b3454f9185d4eb78a7283a184f0a15e09cfd024b501e7c21ea903c2af6011fd19186c3264474c17c07243a2c3ece55599b7597caa7b19bdb992866871d3a985cb737bf269393571443892f27352c301d8acb971448c8d119dd5fea0c57d9dec154ab34c498dadc3a728b3e53eef6cde0b096d5d69643bedcab4e119a33e5c6db6f3af82233e25d80d72ae5fee85420d8b19da5db2bca44d6c348163034ddf2ca1079211eb5f37729272249935160d2c75d6a4412cc5dcc5ca4d92d85d1c13dc15ad33795b99a8c081726a724e94968c5be5b71c3950ee0833c2cdd5cca35829a9145ae55659c128c272db39520968523b880a6c84ed7f3d6043fa20d521bfc97e136d8b25de6df54b72e0dfe385c3a01854afd1a1a9924e119d3649077371305e62700a1b0469ce22721ba54c4e8361eb5b8706712523e4b4871803333fecf8bc1506ff7d0eec376602daa68f0f62236ce9d40a96871b3d82fbe6d5864e443a4724f1b49d24c3f3331b9e6414ea8336434e23142b63ad73204f37ed8ab96827aeee21150bc1a45ea072e0adcb3d23e088bb173bbc8ef94be40a8a87eaf8292e4210df02259a4238ca46359c7289eb72978266854eaa0fa57ad95211955a967eac76110338a63b919a49930c4fa468aca18c3afe4d2105ea9678e2a151ae0345b67ece0f13099a8fd75aefef3cb8012888468472f5fb809aeefa6eaa448486913f9f36df30d39e91ba722d7665c809752a373a83dae56f2a6a7ae910134914e076f60f8159f2bee95f72f77c1951ea93a5535fd901518062eb0d6a59d1994b33d772e12155e39ad06a2613b0b51f3896384385cd03906a51ecb2a4df278500d5b9134ade3d1613b1b500076189edc0cbaf25764f041f4a41d8ca60549a51c82eff53ff7c83373a541d724d25339ab2f67372cdd76d7c6e0cb0a0299470d109a30c09fd2bd9ab8b4e2772a5671b9a4c450f0a720f7e0829e5a5035331f711dcddf3dc01a060d1ae10317226d5603c703ff663584d5f216ede85f13f3362715c9e01504381652731290472d9c545e018b06790849e6ab497fcdd46bfda3e6608bc8d537ec41635d7ae99725a908627de6e754ed550cc6060097489d8e738c45f43f392d9b12b161cbb30548cb902f999b68f186c594628cdebac3d5b653948bf065b46d72e735e0c97586f8d93a8645f7155e3686a310d3250d91735ffa61410638bd147f724c3305e067254edc21c85f688819e7fa34703c35a0af013f3805784c9241fa6e9623ab45b72c1de0bebb1be37715e8c2f5b737cb07104245bae43149266f420ac645d29da72aa789f0d02cc31e6d7544ec77e28b64256fd9fbedb1befcc8f65d222b1f10372d02a1ad72ed80c1a8ce09e3cb8b37e03bdffb095f843507c3d118dd07cd4841f4d4744f58881eea4fffab0329ae1560b892e8503506054000de69f73cd505a64d58669cfdffa0c8b1f848d0f313b79756e5851cca48d3292d19bc15aac945a1d7bb525e2ca957ff29ae48d404576c83b276b9f69fd782a9cf566f74df7ae547235941099329b24f88a2cf8e7e3b8f83731a242ca455d6acb2c2af3c92ba72c72935181a64d5008d121d264c517278c7f676ff64b64f0820251cee32d015bef690333be29d96cfd4a89e584e11986c6ca4bd3fb255c477d862112077e7046957288ed079864f9e3f58adbece2ed5f4aed8013cf49bb6cc1d7d1f2a9b11c3dc61f7a9aa605b99c836f6de7543df42f6d0d32dce455f7bd2ccf28291333c2c0ed72d7a080d6dc2028ba7bdb1d78e617ab05c4be86a769c4c12124f7709e01c4cf72d9c2e09e519bf17875d79306655f8460c7d19116318065175f10bae20e91c2723c0a595df3a0f24ea9896899c3f390139de633e520c96378cfe9df830a7b4568a70ecfe663912659ff17b7d98f2f4d49dc7760a3f819dd324187ab2e724c7172475442dc05a3fa090e8f2e2b3267781bccdfe4de826bf51c9ac404918d7ec0546e2949cd0b226767bd3bbd2d0ba60b525864a591ae3a42257f4cb50d59f16b2a71497f0cbc0e4321604a2701b6e891df093f3fbf9b9a703aff2a1fbb909ff86ca30e2bbe2d7ce45aea598b819ebc19746ced341fd456918084d2e7ed88f021720742b49cd8fcb036538359a7e8c37ee56a664ad5135499438d336aad5bb43b72d63370ce908e160a21837b30b470a0e214ee81e71d106cce63740e1afd10c372d61b3512215843fdd68ea0ccc6ffd35b78bfc7d5690f09c115aa1356938f0319ceb6e5859157d2225f56826b48c188034d630f4a7327935ff86ee5b385b564373098672f361d2c837819cdb002df800b6ec32bfe65c062a04486de397a18047204fa3b802837a28b2e6224d04178535eb39d839b4451a441584e2b843f2eee7291a1158cfa89e6508fd1c754e586e27629871527d3b35c5a35277e82524624726e0933d3f4f502fa3f310a07f4cf03926bc54a622947cd66d78f2f1041768f6940170cd4691df7e2e5b05256f11f26494f1382ba10df806b44dc177a60cdda51628d5002f62830232322bdef37173115a00694ba99b73adcfe2de3da26a94572df839e84e4a545078ee2a1e4e74b78c859797f533635f8dd6cbc5a6240689d3385fc902e4e3fd20405368e7bde415b1d000a630feca47f7bc80c62ee46bc126f7dbcab197bc95ecb2f0c72073be3d119bca0633f5b7e7cfb0cbea7549a8cf772d81fd04d8daabb1165ebc2bbed7d6c020892752e210c36fae1e60cc78deee502550643e1c70b343cfb5db67cdad242bf9355b15d0de9903092a4771a6b40f82e22aff0c2dcc99002313c59227a52d092cd05a628c24fb8099bbfb86519495a72ee1ef0ab9401140be887bc733fde6d19c3b5164e2e8040f79cf3af7e3903de7255dbe546a82d09a476fcb25754d4c7a28016c3814ebb647a9f37143a30729f20286bafbd55b3081e1d1be70886f0e1947a3f3ba304c2ac0ec46d0537daba1572119d21433a5bbd6a6ef1a200be875321736acb8bf893e0dc7ae5658ade908e6b620e21e066a461c3847e4e9b52929a7ab6a1f658d217dd428a8f7cc64b85d37200037b70f20f8dbf23c0bbd7a82b9667ce1f2e2648b9433b612c78b6f5b7ca066cff88bf3228e0620c9937394e182155dbcff1bb1cab9d983d983c0c1bec467292043e9351dc14abc1e027d29ab56136689446c5b2bca24e61a34bd4e6ba9b37475e33b6e1733236f93c951ee29731d6a06e57ca4ad23a05f19ba4cbd8f0121f4e6bf99d9f1795a6152a2529a69ee7a838199d877a105798cc3116e2b32562729f05655bc1af9889f393e1ecd342fa30fd10fdc351023171fe23e7087364717291c674fcc2d9b2747555c91c16e39ad77f5381445d51ea152663447d7468ba18f3d589164548fa16d02f25d267eed74b4996af368b5909d03ef7b66b233f3272a4b42b344e7b42cdcfa74d3f4faeb0c292ade2ab5e2dd4c2b6ce55b1bd62fd389f6da7d26b6a451a2e7cdc3669a268d286be307dae9ccfc6d5eb148d0f7ea872f7eb0df1ef46b110e744b769bb0308ab417ad473b0ad40d816391a2cee04c5723d03aa2a354e1a8f39edee94cd0d7cd3e4ba5b435c5f197c6e3d3447f410b27225d88519d0b287c1edb1083201a7f8395b48ff9defcbc1de3fbaf8f87f935472256aecd2e164804e32ac4ffc4ceba8576fd75713cd00659dab80384e0ed36d3b6e98dd4cdecd840a92176b8e2db6da9e34a8680055c2252bd9795d0eca8f8a48cf1b86cfa12b65f306ba518292109a67ba40904787438b94525b06f9c203ce57f153f8d6f99f8e5bb09ae67377eb7e926f12899f43654a5b943b8da1708c1b72205159b416377ee5eca552bde15dd4a6b9a8dafb92db95b1a81873b0af101a3342d5e93bb588aaffec9c76eb731aff4401e2aad61322f9c6efffc99ea57271720b599ffcd3605ed28ce92844bcc9bb37e47e599c95e4c64b3fb33bf5718fe77227bd5d229fc7391e081a5413708707d78fe6211e3bb12d14ab4b2f91cf3de3336928287eb48ce98fa4b7a8cf7b4ef4c0a469270753ee05c4eaebe20d4a093110c4c5acc0b915cbc2a467ec2e5af5a673b5205fd418c713bf061b147de8cac74a382dfe96b928f7d30beb4be6b09060b5fd7b982e217ae44bc54b9cbbe21eeb729cf7b6d4cfa26d2d59ae176493b62c7018d417374160b1712982637a96e87e72c0bf6652f106f43ae4e8ac11e379d559f912aae96f6a03bc3d2ffdc4a83a7072fbe78510dfd0f59e4971504e6bfe4d47b4a4505eda296a37bfcfe77a6756f772976ac8a8e455616fde9b6a15eb5e71b833e9d321cb267cdf6b35e7dfc6932672d155c8d96864c4ad2226fe7d0677eb3bcf50fe862e634e59ed08d1f3ad700572718a6cc68b0a26ffbb9965ee0e741df81cd2b1194f37b77657ffebb9238c7227e2ca5bf73edb1865085e6408e5732ee3a37c9d31d05f087fdbeed7c5c4a0a94d82a92b099976eb1bb04adf2adea57e00b6a7c0798b0f7119dd4df04fc605d972b01e9aba38ab6b89f5c89194e0041df7a21d583259c7aa89395b8fc07fa28b72a132ae3dc0421083b6656df55e3d29ebc80fe1c267bf84b9e74447979ad9367208974bdbe7aaaa7c87cc3a2850457f442ac80ff3b9b1f81a43d614dcae5934725034eef2dfa7b0fb7c7f2c37fd2d1e20847d6afd4c3cd876faed0002c3302472396648544b004923c0cc3082495eaaa6ba59b5b5153d20763eee1262f1d022729cc82970c747a0542a90af44fd345356c1979a81626e5dc70bd0e50a1ebb6a0cbb3f03ff69d2ac69e8e1edfcbe729655a4b113bf5bb79646e98ad855fba81a723ab501707c490d4b8c50cbdb13a12341f5f21df572d1c104d8757d9f77c694722d474713e7995031cde7f8031a3682170cb15c41c27b20df513d651eefc88610eedd686718b989947a0497abe9b4ca6f0abb67226e7d5af0b6735718ba3c3b727541f886cc73c14b7291bdc7d7be9fba3c8459f78e511290b97ad890936956722545ee67cd7f38c597ed0e993e65a8e13d64e8cd7b24c76a3c20e155ae780d3f46c644223066a4864fa630ed333b8a60a174d0546e4919c47ceb68715d275642f23fe66dc8156c2207462532641493a9a0c44e0d8b8f6d8d132640225d7a867235e1e4296fe855a80d114d02018c528f98c14f7818318fe9594cac254390777203b1eee70de96aa86effd0e6bc4f6cc1a3155ff56824602202f2a276a8627f008693dd904f56487c3f5ffd88a578ff197109de58f04c474afc73b806859ceb7246c169e0862d62ab7531b277a1c5d7068cf13652283888f61273fc60cd808030013372254eecb212b3a8e1cedaa94bb8a4bf8ccedbb8b1d5dee4d0ea380394725fe2e6450accb87e615455d4c3b987c36046b2e29ad5c05e4b8b7ccde0176072fda8640aff941702fe9edf6f3a4cefe977b8957158bc06eec291bcd24e87f026ecdcbb5cc85aec20baba6e5fe624b397485e302dfa0a54b287cabedd0d7c3d720727914b97d58af6f926d3768abacf4d1d7837d98457f9bdeca26084f74f4f4a8ae81477497f1f603c4478281f57c5a354cf899dfd567c1d08ecf42c86f7155eb612ca6a3aecf1fb01b78b69295f2097765397f5931ab535da7b41fef0592a3eb9cad115ca3d0c0e7435d2e47f7937ba863bf017926f5c4392c20c073518ba5c3af700d4b8d2687b73d27b391cf049bf81b7f6917b6abc3d93af8cdcf3fe33653e126fe7c575e51b1e26e28001a9ecf6ddd95314417d701d52a54d9534dbd1006eff571a587b51adf45b37bb2eafe3bb098afb4aa2e8c2698134a0df31952d104e05de8bc814bbd430d3d23551b5963e5a68cba02c5983f242dff446b357df72e8f9313e016d58f70ddc167d0149d3f66e5e274672962d14f7d8d878d808927234d2d75537d8a265aaf89c5c4b634ee7fb3272cb49c26635184a90c82a209c6b455259508190a2c21bbdb4dadf3ea645bb53e67a953342b602b37c66994f25729d6a5cfb4e145b58463066669a828432d1f03149931aea369db9ef82124c021144d6bf74d2a20bcd5edc2ec103ab6b3d8ee862266ab170f0122e9890ca6d7d72af2d41f29933c87ec14a4c4cc712baa1c0d0ed30b4d3efbbda0a3f0cd7cfe972976b4512f9d2ca05ee5b783cb5e67563db56cf0385706be748d1f4fc8beae37284f96527530341e53e049718683577f86e7e052924ab72b578b59e88f8d44a59c2c51ea48667ef1855cdf1f962eed5cc72a1aa22dd426102536fa84d4c135a7220bb237051bce0f8e6829e10b5100f6721d1af5d681ff7a4af4aadd48c144a443b1a19f6c316347505aaa2edceb7e8526ccc674d8fc138f0b1aa9e13b1b31f6b72cb9713755d4c064d7fce5a0f3282686683be442f87045a0bccb42625a14e721de5c75786f4df47dba4b27af74873b8398c6abdfcff227bbb5f23635ab2833224e98ab62a7c851a6e275a8ac7ad930bd40ca70eac2fafe6e4294b1e921f4e14a63a42f7b984a37d53d2d321a65763d849832b4b36cfc0a410a5a84216117259a91cfe5fb53a56b9888270ee16034c3a5f22ac35606f6db0c2f80ffe0698e272d5aae423524cf9eb838c8b81960cd907148da52e92909d1b54e72077265ef82a7d205c78323ea4dda6d4d06ed53d80d902201b8f2f05faf7ff7081151304e47291312506d7ed72e2a610ede6108fecd04d9fa5cdbcfa61e859e9279ce57b7f727f357788882c4270274cf1ff0dd3363a1ccdbddf5551cd6e8e39bee5046608615361ea3b82f68157067e55aabfb78666ec9f6241cd4d651bc4732344457a487299af3c4f5aac6b9a9befb7bef6bfae39ce2beee6af4408cb694b8266fcac634889b000ca8ede79eb8f4449ccb47d809889d9b42f964c24c09417f32448aa425fdf652910390c5d3bf0a914d8d1ba2dcc47a320d59b49d927153c68cce1b8c6726ca0721ffa22bc10e3dd49cd52ab2b171d9fad269dbd7fddafe51382ccc6391701d94ce9d400b137fcbd4fbf186c41642c3aceb4191daee8a5fc32ff029d2b65e7f7b0d2e7f0f123c1c5b84ddce1753d0c46568545523fcc5297a6cd2f7bb972a2bb23f35ed13c21e2a8813fc1f5bdac9f028c504b0139ad8b298431f64f3a720d0435880667a722d0ddb459cc5eb10540b7022c438bdbb64e91a3443b5325727ece42f107d7ad6a8d5202a43d4e36791b71b3c7794aa3dd4afe8605b5ea9b04df35d089e0ddad7d98e964d0146c09f971bff10fc8436a6a49d6f79d0f5bac665ea0cd4215f4fd955bc033716dba80cf2418682c7af2856e3856a602f6a2cb0bb8829f72cfb9c28ad04d19318685eb1c528f2d329c642d8390ebbb531b8eeb72251fbc6d3ccfed4ee36f4773e0a55524351402cab8e258aef2b5faa2330b7572ebc781c17251f5acc9417455060bb55e7286ca099e9f67168a9b08da7c26a17245d5b54137b81d34ba504fce23db2dc0ea5313f54490321620563ab16983dd0549bc21563b506685bf72b655c24370fa8a162d62ca01147e93155dfa3a18ef3b2e34384aa646971cbc5254e81255d5f67328748947e9e7e00a3bc1fd80a8fc720b113b83aa6989c61830b312ca1394b423d3d10f134a8bc61d365e2a3ae3c172e770af632bed9b10c14185334819d1c9ecf999cff1da96f7a8191e40b3e2fc72526259ae6605794c0314c7d643ab49e279e9fb83e313830c03c685656281b50d6f2ae714e7e7d9199eeb476c6765a6a7170fd9cf7dd5a9cb27ca58f9d235c27254bf6dc919c6c1d7a7604dd917da860356fcacab93fed3093da3ba3ee5a5ae72abd4e80d92c01673eec4839b4dcb2caf35ba30fecf3f78c60190b6d0cf4a867282c3d9deb9c62c5d4646a864d6e7177ce98284750196e65cb8d10cc5c868684ac8a6ad65c61e0508c3c0aa9d44cec4b79d30559aea70ab4ee0d4513043f0ff7279ad6b4c264f8ec38f81c7a5083b48f63ef331e3a41ab37e6a6075f36b39827256eb2b9aa5c3e9f9b4aa427d2cc34da4985fabea76e6e7eef57f7042599b9f3dd1dbaf8e581271757367ffd74bb6d312916148450ba796dfe49c89ddd6285932fd8b5e2f3db9b6211d3604456e4a094b1ee9ae8e398c2a93f1b70d06049dea722b42693c9f70637b6b9b586b0ccf9171bc5e96c7667adb8dd37c5cae3cdf9f72b9a56b9ccda2569b5a60feae5d6f35c93d6a2c5e609030060ce885b6296b8954fb0af705f03be7a94af11f1446af32a712068094f41706b87365fcc4ef9c41728266ad935666c6161c4af5a2e64bd0ae78488470ed35fa9e036f6d36b8d2ff72c4892391fa50e43bbcbcd54aafc2ea3155535516474eb0794661810b889b827209a4d6c25ab803fed2558f35e0732268dc13fd121c3f26fff7ecff9fc323db72be345a2db8afe44b3fcc3eebabf51534989953944bdf4ae94f14e35fe7853472de326d6f21ff2bcd60241611c4d25342b82d6ebf7e757c5a870e60f67df14c60dab1aad9492c281e07ff70b73d1e2dbba01ef75edae67d3f442452807cd54e32868556026567df00523e859353bede712d441a89df5453e9a218a445c0743172af3c25679820125603a9e6cfe5a9475310f7bd5b47a4f1c7e5142cef6cf20e4dbdb3f31aab39039d00ccb07f68b657aca5ce62f193f8da6d22845892089b93094d45139b3b524dcd0b42e1d124a7828cbf98aaa75aa8eeaedb0dce57c7968022629bb1548f235e6c6489792b318e1f3b8f919bc25dae986a7980a9ab648cba722c7bc909bffed202592568b266b7277506700ab88c98c014b35c0f2431efc2729eee6e9535ee5a9880d6cff227fd4bea36dd0df14e3dce78953f2fe09dcb6b72125384015de85f616f240803739a6ca40566189d31e76a541097535beac47066ad72ea884cedb2d3e20d6086af858ef46415f7ad2f53a6c7dfd13dc7ea8c68070cd1ce2927c995d7be3075891f5eff296b0af74976b2fecd57e7590bdbcbf172a2b334ad37cffbd12968aa079a6d2960e16866f71f974c0ac0c1b7a98cd52732115b148e7b8ad503498999279c083a6a8db1d7adb3975ce4d58a5c0c272d5806d0107beae43ce10997eb49bf15324f7d896e40a3390adce806e162adbfe82c7261bfd877dc696c2ff4f01723c0fcc4a2bf0119b4b8d150bca3c54928339bcd72583450e1bc4916e228547c20208a9c0fff304d9690a8d86aac7501028adba8726df0a85249976f8c637b91fd87bae41f5effb3a44c8b1d1e02d02bdfe62f030df81a15226600bc7c8b4ca38e4459a1fa563433217855911ca532521203e72772f16161080d9e660532fd3db91a44a6e1c6e959760482dcff53eb454d3168c004d0245069bf47a17a85491c9574c600a9294377d54c70941d3b03633d7fb8ff2273adf81410d0c2cfd41f281c7dae73cf962cdf6dd8a6139efe0664cb510de6725ab1dd3e0565aebcb8cbc5d325fb9979597b7129eabe5219cdc6b105135aef729c0d1e0ade482f0e6262210ef470f969a42a865a3b3604cce263b2635aadac729937ebdad348112504098a5d38b0d18615197b5d55b5bc213aa8f72bda835672e74006aea985a3c192d518b86f4896567199043501c720d4a540a343064b266bfc768f29b517e7437abf7f70c450ffde4659570031471b3bceff27a6a9fd457259b87c80fdb5d8d9f504438d4d296456df3c62cb5810d70aad02c7e6423f8809cc52ba6bc6c04030d94b17238daa3ab5107be3ae9c37b46881b90b06b4d6535b8dc6b12f7ff850e16fc6835ee810dc809910344203dc7428c23218c649534f72833476378960051fbc7aeda03cdedc5eab5ca7b0740d36b5d9aa7a70c25c7572df7e17a71b79cb930651100bc78eb21d272c8cce46f2fe585f83d99879517d72c58b55416e56c1fd166e4e5efd48a22e86f729e1725690a42862ef3d36008f56ccd41d5098f5384898316b5653aa68b9f3f709c9e1b782c4ef01121ae668b630af2e7abdc1145dc4eaf64b35e1f6315805a34efacfa32c18d6d3f75d872e9a36d6b804981b8d4d3b69776f7a5a817f2ca9a1a1134301004ff8708735d4994e7211f48c637965f17991138d584e7b8a3fe7afa5bccf32d1833d608611b2278f481e1e6a3cdb2a5fb0d4f58baa84d1c086029b622a5e28e8a86c4f39ed424e5f72d4efd6fe4612d70ce8570cae841c1817c5721a601398f14dfa84a341dc052e02dbf750a86140365040e704b3fb3b0bab7d1f1673e7757615c17e16bc7a51b47201df9519bc71a802ee750efa4942c0276990b0f72edc0518fef165c9a8453d7206455be056250bffe0b8f63213a76a6f5f5b51a9a6d67b106e3e0cab3ef094728f0c64de4ed16083bed0774e2787a3cf1dc0039b93e591d8f460a92cef23455174d5a45b64ea0a5444d9e3094615dd032c1fe3324e0477d012a0227fee483672bf3cac4d812d2b856ed89db6207c0cf8feec69db76c63dcb5b874f3221049172d64928a9f7ead900b4954c9dd762716c83faba10846a0d4408092de4ac8b18720e817bf7570fb6e7089366149b309c3a17a4257f5f993c5f23e776fcce6cc2549aa34d0c07ff675a1c891c9571933f8c01a84939f074206120807980e9a51e3470f3c21ee813976a97d9ba2b41a54ded9747381500e9e0f7e15156e40cba5d22d0c482d68bac04b1f5e69f2bd9a232f9daebd2a6071174148267f6bd8a0300548dd29ceff6d3ec2311fc8eed8c985f293adedbbd6c14e7754f02ad789693730570784a8bacd27d77db5fa8dcd12e91d1d47ae5952d44b4b8096abbb98e737c085f4e911789835e862a028d6032446157d778d1ad2c6ec438777860ae15a2ea721dd340a9e82684aa1fafa1dd1a161ce2ca8ee39e7b776b3542c3062ef025ce1d6bf2acf851d6929136d78496b2c8f2272a3513370107b447a21db6e2cfbd474de32a4c02a43222005b27253c802bf1327964f3343207fd30084e9f9fac51c76fbfa7bc442656b3b32dd645a717c61db727f8a4b62cdde68894e5d1e48a473c72f55872f8920def465ae72e5e63f65eabc382b81c3f9436d6fbcce16528fec7372eceb7c10474c1f0bf439a456c6c2d4f7dc4b503a28ec5f0c997816c46b1ad72b8b5271fc1905d015f646262fddcb260986e78ae13feb5f842c2ebbd6457363b5b3e331376c3d2e866193d5b2504210caf49aebfc9069dffc42c246b47e1816d9f14993d8ada91a55bd8d03cdd1ba6f19331268a4649e1a348e2f9db3cbd267285ee6401ca531d70855530996a4596b6e932eb6b51e56fffb199a1adc3c80834a17d530fa878a06758ee75d1a2ecc7d4c9d9f397b26a2bbcf662a8e82e9fd37259066eca5713a8df0a91a0ebef0ab626f4a904f45679f5ccce81672a91a558723f50079bfafc5c9037f5f02fde1be1d8e066f6c2d22e66b539362dbbb89c42721f48ee70f14f66afee392ca797083bb9e897199ff1c04eafe80a2c7497a9300f60a1d2a6cba08ba54d652ba5ef1bd8e40ea4a3a4134c05d87ad535c389f62972beaac2d0d3d86c7dae401be3ea74f9c5b0b2e3422c0055572c630aa485e3bd0073150d49f911304582538e9157a2ea64e2504046edb90220d3eb02fba5b72a41ca6a4e235a7afecf99f645c29adda768315ea3c5055b22d47c0c88590e3d8872fb53ff8a1ef3ae604f0519e5ede57df63ecd2c2ed3ffb1e5db5e3dbdb5cc55728f8e6755bd33d726d7958a2da92ca774a49dc98f6adebe97f8ba15f84f36d4225d453237ff1ca1e4aebc11316999af93d4193b6217210a8f43f467d9745e2039c4d77b188d4a11894bd93f9e7765ccbc54f8f207effd1be5b38a7326012b8f72af0bbe6ed6dd7e98e7bb4b7f01beca850b0ac69f7085c48a6bd4ba03da9d1f1a359616b5c13e7e9d972aa506b3788fd5ade0b05f3ba77299f1eb7515ed3ba372daa8770ccc248a7a98f7a0d59724dfaf69e42ab1c331594b7da7c6ac66399a09a4bb78fd193f2d9de1a6413f5bdb09ac2206fd85b5fb05ad524cbe5f05511572a4eddb0ad54fb1b61b521c6993c785e0b6b8e51e27ce00dcbdb2c8512b66a158daec4be716893667c40b61a4098c3b0f535487f371afe8729cdc33272472ed0ece7082278446839b4cc763c33d516592373d45500e638e890ba3f34e7b91a01a0de7489b15997b131f48077f4008c31fb5fc0ba481b39579d1c99e871f617772d76807ae70d052ef4f3ba498f123a1c9c41ca6fbc760aa8c4e66b97751f98072e8b35fe06b9edd04904c2b19d38c234f1960bdd966430fb51b26d20f2f82e972b80341342391ba9ac52b9ff7242831f79a01d39c06ea0cdeb6c81480b8c0cd09a26b68f55fc9d68b5de5317a3a197136741993a30e6402b5a38fa84e435b9b72406937df2147fc39e7b91284f2d9494252ef53cce1020cfa996c6f0475f20172c03447530d8f7c24befadc597fe4e540f75deddef93dcc98dd83b7c677cf2472e14481c8849218fc48acdadd367a0c7366c90abe874f62827971edd60cd8945e181318a333e6c196eb7f143e862632b3f8566828a6180a4a47aceddf95f8585144986be15716447f62d9d422a97b990ee905168f3b7f565db35bd2704b9ac7725f9c23ade8e11df13c602678c845ae4d63835d19575ceb83d3cd5b35181f2972f64fe28b7be51814c5f8225ea68f24e0eb457675fc9f94f7f5cba529a804f1728a0ff43030da6085cf1d5ee727709c60c0de64643519a3aa658b0132ea8735416a07873959e95990e27e14dc76b24f47cffb2c70f98dc5c47627ed4cd071b07266d146678b5496aadadcc5bd90890e23f6af9e51d74cd53b84c1e753ba3ddd34fe77f2eca4fdfe4a4d053492a1d269d6e0010a7539a722e5699ba13a3f936e724c712947e6b59b31aeffdbfb385ad67fff519de67e8b67b1ed96d7c1b8267672f21d0be5864d058226229a31975bc4c9cf1d8219761046fd52c0e8027ccf497279e4a0101a519db49c06e42c2d7500be1ed3a0259a5834b9ac2682ea0c042d729548a5fbe7718364d094e8f9416a2c14caeed8efdf7558cb3e7e80fb5d2aa6725c0d76d66321bd9464ac3f3a9de442750d7bc69820aa953812fa14ff485fb4723b0705e6ded9077ad41654ea6e0257b3418c0e8f05139584ebf325d7aa86e77268858a3ab6a9d39cbf8d87515ad0d3e2a2c5836691cb34d288ea96db9f65f51aa406773f414e6155b3902f8d9f4f57f1da5507d8748a4a50b8d15f3bb34b4372de2e9d0e8f5f6bcef3aae6cff9d483029040f268b9ba89da5dea86be54385e438dcac742ace03c4cee78c65a56172a8b2d9f81abe8a982242647dd7de5ba376afcc24de9e59ed6e9cf463b22f9455ca23939f217f42aa5a1b872733f89261572b343ab0515da22b16e64a97cfc5553498235c1fe4c92ef1aaba18068f09411724d49494088c84e25a4dee2bfae92bdccc0fa18efece457f307f13f8b30e4337261ece70ce3621f954428d3affaf07170bc361b11a7c65b342d8cbea9e6e81d72b78ed56708bfca97f21b9ad78fc3e6a915e9b0a84836f5d53d95f0a734b131725fa1f24138f98a93ac89dd6a33115d3923b297fd49004877973ba73c3229127280a7acbf18db684e07176bc3d72102cd161c878078162b19179748229f124f72e479b1e91f4b0a0d1afbd8122cfae89256bae8cbcf99a914eb8452bd643e2e21cf3924286fd7ade53cf9b1926f6ad32ab7c3c5a6a15431fe6393339bffb8933a0ba6adda6afa649ca990e5691cc54756a9a4ae4a36d6f82d9cd1213f7ec739722253c808f00973a022781924efa05a8e38ef5ca09ec5ef88386422a2a19c117283c95750a9d1cde7c5563f47f5bf17f9a6c726d96d52775ca1a79853658c461423fe403517e4b02357cbdd0d719c63d5787ddf023c8361dd4a2566a12aac0372894ac63fe74d20e6f2c4223d5f6c63563d3101de4db60c4a02d1017f3547eb6e759ee47902f883e9d0a64134110747cf6c4f8dd15dfc3b00585c2e8a3499a9621231ab4faab4c7d2b9f52c70292e0b47db31e5e040224cbf311a2e9294861430722e54e239d30cad4a2390436b329cd4f250ec8b6b9c47a26feecf8af41811148c48a90c4f4533c704b1dc15030ca7d7bd7fdd47c38646637e74d9f89d1cae7261685755ddb94d478d0478dff56fa30a27d14ab7a7ec5e1e28dc3364dd3b410d429f084b2a3bd94e46f8b42bc413e0d09d934dee08e974aeae84e0ae8e5fac7266a24fb0758352fd6daaf9e6c987a09588c64e206413f3a3bb87f441dd3f5a720aef9e3bc1770d7dd2c67bb70dabee99e58d716eed6bcbd3df9f67a69dc87734aaa265e1e84194b686d89788ec6e9c7d762bdfbc82078f3828b68a9dec7505728bcd5cf43e134b46634bc25763be516be2427042572ee5108fc600a345295f72f928d9b5168c71da474557dcfbbf9599dd46e69ae835c44c53529549afa28172cee7025877eafbedc3213f0ff60653743ce23cdce6c4b4eeff1d433e7b583f4f132a3299c174a055f3724df2042dc2d4700558b5ca32fb16af14609881b6794c1734de6463c57cca792949ac4e23934600988ad5fc5fa39b8095c81126ef1e4cdd4bd7df77c584c213a0b75147eab07d9d283c25b4ddd42de72a4b92a78214720f2ea8b3a390e238aad8eb3c7a7ed560bcaa2758eef3024b356cdbd6d9e12872660db67130d00187a70638fcf934b4b0c22795ef4ec354eb80afbd31ae4add728d24406ac842b97808c37f6b4db764e0421f6ec65ae5c2d3c0e0ea8f2b609e26179bacd5cf6f7beabf83e6a56574d412129af30206ad0cdeacfdde1148316a7220523dd8cde7d012d721afa2706c060ad2ee4e27da182e061620d39c513da36ad0e2563e51554dd58b782000e354a1c5e1439e7cef8bb7e64a7ed5e4e4954e7207dec8eecd90c89f7feaa5a42fb31368fda330956e35c604351a15569d6c57704a00b3758efeee8f574d177d921c0055d809668eac915b6be35f54940f722372d39c486a24496f43242049911b8d0561028367d79fc89af97e00e4aa1e1ad20128469555b3566c95b2081e0ba8483fc1d873ecb0274cdcfbd93559efce08ca4de520a456cf9ad6489dd3c1e40a76eca4ec802fa828a4c4faf13152bd4da64072a183ac89dba647d154e4a873eb71489f0ec675cc746ec207519897644944043a8e15bd762b02a02773d8fcab895e4ee883e2f7bd77992b17e07c97ba26332f63c3a752099115ea316a213410f8bc64bfa15625bbdd066bc32bb7f8b43ffb9a47aeca0c767db6b5c0ca5d9d817b4c5f0246a266e0a336ca38646eec9e55598c722e18e6a2c1627bb7f297f209a1e718a06f193c1195a37161b971aad665d77972123e999be9a6566db37ac31120e3628f278fd10f9dc22825af1890e2b8bb77728e3ce2d97285d3bcc95ea728bbe77c58a2d5a5bc68fa2aa28e932c19d75e537223bb93045e892fc2d0ea966324479e1b0bc4d87eb473d470039e5fc753b31a725b4066103d59b80ce775de5e6bc4468f6f92ab3db12627aa6ca74e637ee5cc72d79ed38b94cb83135e3202069923a88e70ba2290672a28ab836a11c5ad4af2727fe89221a5156e86b80f45de46af185d05f72d3adebac782c0de87789fe09272fcd7dfaac71fe39a6c83ad56627638faabdcf804fc4d5df4566193783ed67a31386391cb4400af1482000c9051facc2832f7920bef7cfebc996be6af311c22723885c33fb0c611bb686b709588f230af7fa749ed5ae65a90292db2a738800c5fc99828359377449f03c1e44a3eb37aff27c36cc8d97c23688ca70d9076433c7247e42fcbb665eb2e6b28c9da099973cb0a20d573dca9c260dcfebf74f46ac814996c4274cd02b13bcc5cfdb92dcb1d0cf4d0082f86a006750d8c8ec4c993af72d8a6406483c8440d077696437a196476b57f26445d2afc8ab8a3a41718ad4729e86fa9e2be24b0ff922ebd6fbeef06d8b7be3b74645154b72621cbddbcc635632a6313739d2f5ab099667e4b3320cdf76e3b4d50aec1b43b87a3f124193885726e4c0416eae1b57ab4ff70a1425b00ff852be809203b96b078fb22e4387cfa72e12e3a258a482a09e1f9eca5c7eb3230d5f45aac776f7a9a81ee22ba76f8e872e1c8f603f2eafdd35b95b115c4486ee2d7dde30b4bd1b1784b45fdd5c7803d3379a3a9fe289b09bc4a60e462ca62d1c495e982fbdc6ab9174aaf180a44793c72cc4a6eae6a34dc33db0b78f2147d8fd203f30bb2fae6509b1563c5523bf5a07229cc19d20bf2409ca107c4a6c5efb780dbc85820d1bc8baebd31f079b0b18015914e903b5967ec89785d5977d1de47f6f52935b22aeb0ddbb671551f6260785727e449c975b41c7268d71398bdf104cc578fb50d0210eebac0ec1b07a11a6629fd9ae7e9aa077ee9b30872314355d055613e817d750424d5e0cf9f48ad79a47282190c74249b83f63d65d769f7e049529e639affe14e8e35e74cab066d5e627249926e10da856c3026ed0104baf1ef8e457d8b3db63f9de9681e5d4c72c5e82fd83d32f464e09b7b607e0ce7bf7ed031ba554d1c6fbee7ba0947cb96210787561aeab73ea682a19f5e08116eb388fe8d2a69c4e2d4423f246cf1095efd4c1439ec9a4cce12d49c1470339e48d01e7aeb0199ad7c6eafe076b7bfeea7ec6ebb7234d2ad7a19dc644868e81ee4e4c8542c8b9b597cd090672976153fd0161df70a6a41faa9e77acbf37e8dec32954919f4f24e62861122bfa381cd87d63700bc72e960ab7e9794ffca3e7f7963c6638c9c10036db1bde2fe94d334c9a276ebb22e0fb6776bf001f09d80d77937bd896843956440572af132df6b5a696b00cb5b55edc5bbf173c22503380fbaa04b625f8413d8add6b2ff390e6e971318b15cad2b74a3e3b64a3b518e947ec21aa6c94915ea718a5c31ae851e522b75165dc4ba72c49f344a49b1edb67f6cc93dd0562ab52addfdb66b2b7f109133e2f5c3af177270f1f461986448a546fa114ca5cde1b681b3678c990c89a1784818392189a3317b063452f09ae5b980dedbf0f2b56ba0f78840eea58ef40ff47fb814369e8472aedbb88b6c5913b81f81db6c6dc7bfa3dea1422e3f15275917ba5b991ee72b72acf4974107e815101b6fd7744e304ed4e27a83806f88e8fe68f88cf428b98f72edfa31110a2b65c89caeee79c49e209bcc2d63d8a79a945c384e790a968a20728023dc2671e7394ad85071cde84af067d16f911a646a239d27c171dd7940a57206afe15099de0da560b421bf9bff35003ba6e0062105dc9e1b3a6f40179be772d86fdda9244afaba661d42e93c5a329e64095fa65e37db6c32f99917304e7d72e4c2836872bf90c0e2c552cc3c487140bf198aff074e745c2e8d6088ed1d2872eba44208e8985eaa20f2cad1cba7ceaf6c577647fe34269474f4aa967097fd3bf24f8a5b8fe64d65fa17ba5f3e4764ffe9f967cbd5833c382f1c74f462fdeb145594395b721f4ad75822d745f0fb808952e0c9e42f1b779e6e9ff298a644f972c725a98c11756d32e7f5ebb07a08c5c8f24182634ea72867c3bb5e28c526ea651c5ac148b23f5a874189db295a43659680b32589e26de954b3c6f1f5d0acc172219211d4f36530eef2cfcd2e84379714daedd44eada7556ec1910287e5cdb147790df96c4607e77774ff3f7c0a43deccd86b248ec2849a4af3ecb3379c29eb725e8835d1527c9e8cec222ed01633173df3e5bf838c7799b752338c32501ccf726565700c3d57c4ef56d4129520cb46a2601c9e8a37a5500f46dc84725668826dc24e494b013461c3ca2e71e8658a6a0c37899d64501a17cc68db3e428c5fb972644c7a524f9939fb2d823bee4b0f59bbd56900caa167cddfe10d0a36ee24b972c9c1ba5e1b2c493b18facf9a52a4ec839b5c568be025cdc9cf65b2d945e84b72df1f954cd12e5b2c5152a7b6f275deaed965e626ce5e4517b7ff3e46b1e2e6720364b260804e0f52c068013cd4b807b03327af80b602d7db9f6520366e5a257213c31fbe2060fc1dc8c21dfe9604776964f1c2192c932e2855bb444a84f5b95fba571fb11ea1cda7ab3093983b36c79ed7103b5aeac61401030711dcc206264465d0c1b56b8128933e26e04388fc068b02e64f692a890161506155e0fde53b431ce543f3a7467c093d9c5959f07736411f6eb1285ade1e42eb1c6f6179cdf06dcb4300d34c60ffc6aa3bcd110777d0cbb747894ba7765b4f9862953c0c91cd7227ef98c491fd882b8fa380d6a61d40be0f2bddd0a0ac9955ea7fe8573c0abe68871e1ce8749587e0864594aba2d407dd19519c00b87f6938b109b32c0209f772864c00e4dd81616e345f54eae20e6f7a3847be9b6ec64b52c2329933398f4a722323bff2b669ffb9c2baddc808215255b0c083a3a5c58f2c25541b0b11028e004049915c671f2b9e4f4dd51c4e2b1277d387e8b94f50fa036ceb1d2361c0fc522faec7538322f8083b02d279d57014785cef89ae9e83c9d072b00fecba9266720b795be1bb72bce2dbb44f94f6db28fb095818ed328b903357021035ca8b914a6fc01ece958d416b801dd29016ecbac2cfd9f10fbec1ecad9ff9fd47b4452072cdf1b0295e3a88ac79e2e2fcfaf8e33d12f261f7c22d7fe966693bdfc2236361e95b1f455d52b935c17b27be3c2fdce8ba20fabbf787ea7999c1f217ac996f72fcd62424079594c70ca5673bc0d2ee916dc5d4e51734b3071fa8b462e707f972954e5d934797afe9150ec1d3b0857a6247d51fabab466f50a32438eedcf7f77218fff5713d0502157256b8e9fc1ba485e83f507fd8374dacb24894b2353fb345a364c9bfe801b8a6b8f8e8cea71650930c11255be7be35e0d9353db7ead2fa72723c008a969af0cbce102d568f971d01f1f193ca1481f72eb0daf8f7bb52ad4abe4d30c611c3d646c27e1df27f4d113a4bcda9614aed72ac6ae213c5b42f6672e0893ba6995bb64ea7f9b3e8f4d5ebff4df1b658d73d596134b402912034f57272cd5352647fbf7517568938516c3e66c96a92e40148b1ec53b51de109ed2872b34bd08f1b7380af087ae20208b4c1a10fe27cce3efa613fd2501c2607c26d72ce7e0b2253a9ba1fdb2ff8700a3c75368d1c1cd08374c80989612e0d2d1697011f95a19dbfb367ded4a05cd645e179f83fae88f6cd4376eb87844e019d78844506f52fb962eb265f209ad6c68b9672c1dc1e6a8a3c11b188dbac14c3052b245fd9019e2febebf8cc115e77cf1cd56b78e95cbc4f1a7cc5b0f6b04c4bcdcfce72cbe72735ac3aa5c8cba4e4aaed7d837992421bc63e3f4b101557cd73621e67720b1d9ac96c48e3b04cfbe2168e196bde4bda664d1817f7a1ac8bba08cb25a35db192139ef7da607d2cbb10d31b0c4a57f21a290d98feac6193984f46b10e1772e8e57dde62f19289176bb3305f964b2fff233aa725d2836c6b3ab5d63bdf00079a30a988973037c297efcb827c1b6024b1eb64c5d91b4a0b4bdade5d759d29729f11644e261f8aa2bbfc823c977f5660051049480359e5e8ace9d8f9816c057279e8a17c01a8c064a06f4b381e2f81efe11d9924026bb3ba6ee2d56cb7aa9d72d97b6e87a047f486e37e24162871d20b499c17d631540bb59ec8dde8d13dc27207b8f667ab88e03f514b82183bcce3309f87021dadf59db9ed99f6f1977a5724471aece21b3643549fa23bd94c11e0aacca1bd47d036eb05964efc74fa924d7222bb0cb9cf35687e5f1615cd550ae1f68e6b7299d9f05fe767381adeaa118272e6832a03b299e1d737f7b15fa2e29ad8e23ad58c8ce7e59bb816d59b43d66672f0809df22c4fb4487c363385313220969328d3a371076fea65b6c2476158f75e8d6f669c231c58b3fe6907bd4fff1c1a264847d912c459efa4c09ffa2af84154d645ad4d54c0641abc86bfe3f9bcec57438985ab94874a651135ff49ac246572b74738e9931cae27d18f4d4bc7bbb4e58882921c3c8bf6f806aeb7d64c9c086b0982c2e399fcb3ffbaaee1098b4ac4dc2cddfde2fdee3fa73fe1978030607c4ed21952d9c49aea21852e0992470a0ef9e7f59064545db7160de217d38a085a72b39b0b04f4bad21ab805027294e246398dc5c01067cb209e3f4977de21f6f572b9ec7134fd6ad4eb4731a58cfd4e48c5b937d3c3e65b3a1ae49ff3fe07f1dd6a5d93d0ac5993fa13c0d1ae5ca585bf4cbddebe610670315bb5b649c59f177b16d0af490d890596ca88ae59ed49b9f18ada52b63994e56ebbfe1215ecda085d6880fa5609fbb12196ffed8094f9193188df4619b9c27a76e274ae3b3e4fb85c1651045ee97a1acfb8353890992b7913a8e7c3366bf00775352b9efe519fde097277cbfa627e85f4af0a15e8322fc2d6a7a3aeb21bf72176137960e17e4a2ee865a4840d8fa893dd78d4ed778916085f783f9c4e19d8dc0e478108028cfb129a729b73268d02c8b93ec9b421f556dda430e84db274923e001f3b65f337f7116372bbba9f6bb8f602565b1f39aa609caffb6e02f1eb9de3480d9be8c1e2137b0a727edd061297592aadf5cd5aa629dfce98c0b535b75a27e1c73de008941d7417727387687fdec78c9e81a071dadc5b8befc8da24ab718c2b9a9764e47b599c420424bc05360696ddd00c563395869248cbb36a435f727d9aed4a59adcc4f8670728f565657c819bbb69bdc37be235515842d1d704e3a7377bd5d26e71b8d760908e4ed4f3aae0b7c1e33dedcab7996f8b8bb5ccccbd28aec6a3f6d67454e2a6c721248e429bd50758e75bb5a6c7b7b526335ee27086d9c9ec98a8336097c59732f454a752f85262ec1f5e75a81dd8816f273f8ea26ecc868e2935c955f52093971cfc07acf0f4ca7a1c819c8597da313f4d0e12f6e93dfd5cf7436992bd68c945f4e5ba61a16a8b2125da1ca9608d8d7175d70bbd6880c8098983a9ae9c938ef7143b00b9a349c861179b9c3dc6d3d944bf8c010a7e0e69a91b2164d7446dfc1723c762fbfe382560862c22ae109d00104369503e31e6b0906812fa920945a7772a7462ef2c45ea8e8e073fd0d01c05acc743f5e6dc102447f4fd7eedb7187414e8189430b55d3d4fbcba0bf9a5826a4936f6406b17d20b27d4c118a6bd7e50f72ec78618168126a365cc9bf588edc576bdb4e4144d831061c98f3f7f40e4b44722adaae4ca05a7cac7a01b4520b8841d43d5565836207090348f894b6cb0127725b85876b6f398aca403b8b1fb3433e2a1c129c9be1274ef8f858689d25ec6c721d7a1122fe10b1b63076f4dd638ed030c4198fb6a6783d04d7b7a85ea86b7d72277b716eb59d93dc390a25be57b50254adedb5e2fa26e74203012df570706972a292fcf4f684409d603424afa08230b4226990630752d18190efa06b2311be1a0884d0ffdeb52d9ee6ab199b043d59c92bf45d3e48ea37f4b84cc1acb23ba27263e2c6b57ade74584e513f1be2263fba665ef9f7c4f12095d707f79eb6849c2aaa1a046c87219dd70b6774b7852b24a92b890a6758f89ed9e7d25d059b8d3472ef7d42fb6b762c10e06874fdb24af5a9b8d7b4f43ac67a3a4d0b767bcd91ec72975bdd335a1952e68dcdab1bca9bd857cf1d451552897b03c5406b829ec16c72260cf789780b65e7d8d535384d13c0780cfdf5388903334e8dd9664b3d683e7298111b8d31da295e262fdf97f665c405d3d7829a52d67837a9f1e12aabceb8729fdb5c08d6e20b5a1ef3677721b0147e902ed410960c2d5af6cb34a37d00bb14d3dd58a3c961d9d71b6915b5ce1f288c9a95aa14174448ed37ac61a090285772e45e9926b293b04a187d4714f91a77147fa9765b1f0f7e4b8f01de09a224c172116be42abb562783727038092f3ff56814336d254fbd0c196a876231d3cfaa724c2b064a7af4d81e851741d7d3ead7ab9eb17512e3ea3d6e23f05614b3606d720560a84f3a07d8f481c5d7e75b30a7d13bfbee5b8a401e698be16b33c2c72c7275506a6e3ec895c18a9561e3dd453e638ac93544c5cc116890e9852a8755e7721c0853d3d136c8109d118d53fdc9eee7b04b3d0bf177f6b6ab4c02116a98c7563191f19e53c5641e87833bb5ece282649b677032b7c32b4b2cc64e0163ed21722a5d177c9b977a8376e70abb0031c6336c914e9f9435ea1c6e5a43e7d5813f72bc8ec59f34782487883d422a8ded86c7894c0a058d548dcb826950e1429919726fb6a402379e9a109813bb283649ec6b9711906462c2b1167984355478b78972d4a436332bb73691a6cc681a226daf77b446bc8b6ce102db0cf21ad31cff2172e97d0809181294ec921fa6892a9ce96e66696652c6e418ef86e2122bc32f6935aec6341ce72f7266f966bcde091ca582459b9c50e6f31f4de7b38a06dfda6700bf3c474fcc34b6e8cd813a0846a640f9ce0ffff04c564405a24746f77efe752bd770f6ed81f62a528acf1dc737ed92a671101914719adadfa6b9457c31d76e26a3296e274f3f8236f5ad075239ee30d1ca0c2e25298a9e63817a65a3ce74357224cf821441acb569d48abdf757e1e6abdb9af003cc98d80efb1c9e4bd5360d6feadf974ed7d81ca95e40abca1683cb14c1f23d006e1704d93cfbcc1c02f85172695b64cd6504f6ae47ef12af033d12cbcff1681ef6e169cc7ad4bfa65fa42716bcd9a8dfa67d0a2aad2eb8b36ea09cb399a493d3b5f00dfe5d7e58e1b0da80397b7658391ba1c46e7c4cd2a400b845abc0ce43e1bfbf408dea26fcab470d4372c04a731ee24f8c8c672a3c2cc287cb970d8f422112e463c9bb0202e7afcf4e72e672136b4c0dad095646f19907990d332eec72d4cd2245ff75e752f7cf242323f69f251447f62ae3b61efbeb0cef71508c8955d57eeec6ab8948a8e24f787260c0c03633eb13060cabd6f5821d43cf75e8c0f14dfc9ab191942b795b7e27f47215e7ada5417e13e3d24a29b974b936695514413e22baab39488d010158aaa020e68ff626af9ad63ab9ed9a31e1d044de88f3399848a73d7aab46c2c2b5dba07236b5b905af8a6c236772a7c41e350d0846f4e186387c1889083a771b9283a825d76ecd7bc5b875409838a2b7dd0869da3cb1ef5965b2bf419fbdba81beeb273dc9fb79caeb85650de83a18faa34d040f45b1d3bcb36effd1d056e254db876f723729f50e5c9a782f8472ccf0e8b796a7c55163581579c946883e4dd9c8c15372b68900e1ae3c84a08163b02999f84a18fd8cad5a339c2e62e7b3a50360aa1e16a85dda536821a66c254a4ddaaa63175c4d12d17a94bc9089915e7e7e5631136285e9b9026368d9c8d35b7f0d86495e97f87ed13aea37db429ce202e10a048d72fb8420cdd6a9879a9bdd9219a5d1995917d9bc240db63f3339f286c6ba31b172f59f1efe411d184c0cf6537467729910d1d86a2b96d87b85b29de3306fc0743ad9d99896201e6df278d318c9b36026b6baa06e70b0b96a1a7541c10be242cb39a48fd38f60feb1dcd6063cef6040b49f4b007b0d5645850f06a5007e8ff7f36e1afc8aead4961661ace848a1afdbf53387dab501fd86c4829ad15597e716df0756d693cb92b0350de459235fafd5fc2d6e8be89c1d3fe568826a401dec05b372bfbc5e98e0f141b5bc2253519d2daff02a6242e701430a9f43c5edcf29a27d0a808dca18ef066907dc49a39d6d83686c6a04587c401ba6d8bebb582985d6eb72dd0585f7ef400dbaf4097ebc9dfa35822b99122b0348164133b02af45f57d44b63cd2273b32dd5d6e648d445dcb9716f9200fa75801586fb4a0292f3b874bf72bf53cbaa4dadc583041c7c1b9c3edf8e809af53a6237f2f870046cff3b9d23489c462eafaa5e533083d25fced99097dc19cc48dc5ed5e00c4620a54157c85c72eaad2bc9d907ba4fa14506276f5444ae474be44744183f5b88ce270b39e81e729a5ea774af107ba4d23d36f1ea23c9d15c53c9bc312c29a02a9ee5cfdc962e728d64699adb5828562506e4258b4054597b20647d59307745ce9327461fa5235025bdc1ef4c14d2fb8eb68d367248358ec235768c88531f4637a2c8e2167f6572a86a847d23f956bae91c93653d0a68a5269325599f33f50087427872bd27045c31503302f46e22ecca51abe33925ca3c341851fa2fc554598155403f5c459272c4d31aa996373b8f2cd5f01226fbec463ef95fb3e50c5ff152edfd478d22441eb72dd91364b7b8a71e65e4d473734a917e30031ee15449d157f0726afbd48f723e30fd1658a1d6c4d910eb984e659393698861d4d1400f1311bfe68d7903967288d04e17fa876bb225b73964b08baee9f1b9a7168a0b32f090ffb3f4d148201bb325773d07eb49e1f6be576df626d2d124186ae72bd523a923173bd869d9e372e674b6a14b1629f1df1bb9c87934b2e48ce4c8045cf33462ea34047d1d8dfe466218bef5ed1e5602040d3fdb69a3409c8a8daef4e1d35de6016c5a9be1ead269d03ceb8d2a07cdef04fe214313fae75511e7ff90771d9ce3807c5e003b74f5508a7bcebac21544d4267eeffde71a9244e7016cba5efd13bff9b1f5f01584fa2cc2fc919e5475c45ee5901c01f29cf5d7d5582e585083709f258cab5319502f7299a2ac8ecefe2ae84b96fe99d0c8c7fd1b99723bdaae93e8e20f49dfa6799303e2bed2dbc486b10ac2bfef91b9ff4336b44d0b2653ca09648b5112d793567a7243c62cccc19709cf4becc353d44e8c510600231d25494d54644b0cb77f2527725ee88ac377291b5b4e50f927d1f99776927d53a3149a6cf151ef2f79857737721420c21658310cced898abb325297e4a13f5e7369e252135267ecbc16e96c27243655b430eb9d6429b20f396a1b7e28c431a277c1dd3911697053b8c28814a6d9392c73755e3b8c6930490cf9181fa8805396382ab813003f612be16687577726fc5c134d932bbe1965609eb85e5fe34e6c69bf02e02e0a98bb672b4adaf187211f33e40c4529772e352df2490a830ee6546a1a592e96ed61c8a701edc4d3966840e7bb2c6957d47883660296751fefed15214b8ed86fd898162040902de410b2cb65f07986f2d8ac9c24989229b19fe803aa8f5e6210262f4ee210d924f3d631845d0857e1f8066cc92eed46b7c24181ae256c7b38f391dddfd762f11e9c650e7ba37f8d78441d344fd52908d12699c18f58783d4e3983e84f8c177c71050728ea25ef89c44834d89ab8376e609b6d2c92bd3855e6f67c3756e936807f4a772727f4b05a1a1f63033cb0ed76733b1e1a1cdfaa82006a4894245e1cc37d99072417da4b41328aa65eec095c2bb0dcb4fc171e29b08c376e3dc986327bcffe1722f7685e0a7e1881cd863b068de81a4e80f08463ff2fddfdbd28a167b79ca9b72cce4ee692307b68c4ebfd8a0e50e68463b3b757f3ef128610ac6f6516fca5672ecc79a522270b024415436e8959efce09f0dbd605011e7291f7efcefb2194572d543d232423acdd6b5edc21d3797da72e84f619b88ee84c0bcf7519864afb472fe66039ccd1fd9a41b0215a9f791bc5b8b8ac4258adbcf5b311e55725b840272226619c01ea53a6a5d29f3bcd41b19c70656476d48dd2095c5702f2f9d7a1572a987dcfda2159e8bd7498d0a88c0d116dc4f702c258cf85356081916ea4731164a72e58593c3371f92b615217ff62f2c8a0e835daa429cdf10c6a45f1a51e769fa4adfb31be714c87a6022c8e5c3e2d8f328d95949142b45d92ade5cd8f702253ced9d4cc513403be8d7127115a6f3c6e7482c6a706b213ebc12639571f87d725a6462ce3bac11448c66bd3e1c1c483db3025ee4db1c797548251457dd270762c475b3ff5b3dbd984342abec19c45f551e0b5ca709fa323a3e57b4b8b0ff2c72f466c20c8578d9ee4d6ead0831a7e57075c4cf95ef03ac2dbfcf476a51076a27ecfacfb152602062ab72902dbdf6962bb5ee33610761c373e4ab03f4af80a972e65080323ba57490be87c6b54ecc35aa58aed92e0a1184bca4a8321b3c4fa644e3b4862e04f4f58a3f17ea1ed61c34e88b18ba0ea5495aaaa1e373fb22550b722d790c1e5684316118dfe9abe26c958f98b701517a28d1a1eed2d7683229111d3414f4ea746f5a9ac55d97f13ed6e708743b0c34fb82431f7d00691c303bd60d6056c1f1b2483ca60270261f58d975c31a61406eeccdee604ed83ba0798a8772f501df36652216cf9972735e360cd73edbcbc72e317c43a7c5f808deb5542372ead4f9efbcdb9c1d365b3494fcfc4a18f7c7c214b9db5f86ed7cc60bdad7b003525526ecb9c38bd1359ab51aa8dceb84ad53e3a21e727fb4f3415016714a1d7201314d814c8e059b91dad3a12e0405ffa11623a797dc3df02734449a7ca8fb01d87d08d25e89828fe3cbdde066a6a0201bc29367b8a98f8dcc4bd4f846b9ed09ac2e40848665e8043be174dc90504264c3e1e1ed132cee59c43d2231b0f1a3724da05a190439f60c799c26b070a035a7de73c48766e3300a9aafd23249332f3475325e141d34939247aae5aaf4aaa6ac0f65111febf0688348f5358b7e6203729975541bf358922426a0bb3890b0e83e4d48d24166016163e20475301915eb72adfc9c0aad62138a30aeed44476441515d98d10a1d278bff71da94157c6a694662b809692baca1947934f943d271ab8a5e3fbf748ea5debd2de74023d68ec5494ba67576a25b6b8113202842b3df1af6acd31a5b74fdcb2dd30e5832467441724538fb7017fbd0d4d04c4d642199c6af46288dc69b2f05249a36141f27b89f23d84af43ee26b4cf265848fc008f3e1152621e9a01df35f88a12a92dd5002be0220e90558076d3b7dd71fbb3dd60933917b696c35cb24b2c3dadbe2bf03d9f4062b3c9d16f565c6e51c6b768e738acfc0646731158ab3cd6a801ee385b68e4772e7af933b6640c17fd8c8764e09623096350f483107882dfa93d52f51c2ed7272416453e8f0a770c9202857b6842b71b97d7a36b2f25cd8572fd2c69416097572ee63c8cd7b520c3feefc307e73f593ee32843d0d911aa503d71ab5a4ec8a8d5c0b9f64c490c451168236438e45fe10e9a13d3b61371e9660352123b9e9711c04dae3ffec2b13bafef841fe0207e313f956bee74ad87b9054758a71e831185d723d84d9ba3de76048a21d7dd7d5dec73c717e6344aeab95c65bacbeb63a5e0360e587001d8ed02a2816de805986151a5c10cb7c5c88ddb3bccc59f1acd716d472bb21ee30b61c18c27793e5fc74170068e4cc2c7b8966693febc3515572eb040aba5e0d1905d0b81f3439038cd04f29ce2fc248f4671a2a37abc78b973f86f4616ed882ec6cf1356a7cd11e60cf55228b904eb9ad57e05cffde8bea249ab09a6f921c5c607571de2d706059bffda88d8300b016eb3236583fc28cf1746772e66491af9a276714c65c5b00797377cc88b2af7514179e944221e256b7c0dcd33b47089051bc0a82405f21fe49d456601a2cb8108aaa16df0fec9e24d8df8685a4728942160617945e6482497b942ad24437dcac6ab217638538dffc3a78b866d4726e3e724bc281f13106da34c6492c0a0ffc2cbc1492ccb86722a0428fe6baea7276f3951111e5feca4bebd59cc51f6c2fc875ab2fc7530823039618538603e072d3c9bb44c92683790276ba1acb65dcff67a8fb3fcd04bb5307c44b3c96cfc47235e41fb94f9c2c1f6e72f924586bf238f803337330e0adb81abf8574104b817230af05efea6db17ee3df989d77f6b638d41f47f6681b5996bbec05b6958dc572754a250d8421cc02e2ea74d418c989fff799159f0205fb84f8d0d4635116ae72df80b446a2b37e9faf77b9933ae2e8f2aaee87192ce27cdb6ef943f7dc999672bdaaa9d012b8e643e14165f5762552f4d27fd11a725e529b420e6791562f1e65b245e2515c0b603d7aca67cad886e1e75218805601865d78cd3738b67b1784356979bb7e4dd2d4734040a11eb3828708a537cc8d9da73d80230a26a7454f102fb5efa0253fd71c8ef88638d5010727ae1aab7ee893d3d27bc527bccc03b02672bd2ed83bd9bec3d1052ab58dfe4f5f156b42d5b7562192998820b09768aae0722ec73d71a234d143814d9940a25ffc4208f51b4c51bf08540df4f16d80fc42725c3277d162d2226c05ffb700e8c9b4bbb414cfa9a2abd08c2ae69193fd7cc83408171800f58547c6edcd711b8f35833e7c2729a4ec34ba9e06bec1320ea7cc064e21dae207174a2da3846e80584ae545a9bcd2d037e5cba412945e091b0afb72f0ccd214eaab4038f4a297f291093a518941b483a52fa637aa7cc55ed038b972a9e9d40e9475fa945e0adfb798eece279d97a0063bf85df4bbc13ed242179b4d72c44ef010118df8c94e35fc6ba44ab3b71e9ea6567500e0bfb7ee0a9a7613724a50a71c68e9637f56fdc6f72eaa7e079f79a23c53714635df951a615a5404729918e0a6aef929869bc0209f2737fba764083054d186b9627bb8140109d97c0f20b554258660d01fac97b953f7add154a76c9d9d1dbd084ea16c43664e02ec722015cab004bebd9bfbad43c1a6139d29a298911d075614e74e5254e6bafabc7270af353fd6bf5c17c521e46690b8ec820636e6368c7d7b3c9ef208378c500a61abb01d865175448201dcff704c39734b2485e85b3350a2ca352ee59e748dbb725a47e808e9a94846dd6c11b1cbe6d683422a5e77d37b24834264977e422de57273fb193eb87b6416256f5a60ec9bdecd44e5fff9fad40069d425f7bebfc0fb7229bf5011bf9fadff11adbbc1788f35a66ae1bf2fc546a123bc2d6763bf919a72bfdeb6db8740cb68a55957c3cae7d4fed206a9bcc7f372d705fc0675e9ea57728c69d36586d0b56818061bc261be82d5f6e7228b91a224246da783c7608ea972eb348e84a04433bcf4da1c87bb94cd08ba47ad53f750b09363fffafc3f3f4253cb186431219350a5b288d436ff2cfbd7a6cc1025ad3adcf199518d19a6cd83727ca6b2816e5a204a784c3a644e6898c8387cc921a0eeeb2c24c38b093853e672a255def16ebd71e18987ff0024997852efe2506ed23ff343035a7c146153452b3c56c03a048fd76b29b33759cd0af0d5328e5501d86a589fafa6d3c7fa00246de14ad2c33e4f150b0e54a09308ce0079860de4292152b8e225afab51829aa97246e56d59a45d978d89fe56ea6df8f3c5d63fd2d489f65c42c976966ee1f4fa5970a34ecfe9d5c7410cd7d5fc0d37ec0b601b5c9d888b2848f0d8b02007713b1a38903841cc3c8e0e43a130b8bdfb6f2e3cca060e1e914a7d232f28f3587e86728dbe8138850fb669c2780b5aaed4aee1a9810d3c321992456a44e59bbb91a7727ee592f9dd1c62103a5606f94e75db6ecf5657181fce1617a97078726129c472e913ef528de33d80f6dbbd7cd01bc8e82cd2401495de4aef4358bfa9b254a30226234d7f8003e5e752b2149478353898d920f79eb630e457fb4fbfbdfc2dd972cc51fd9ccd9bbb4bad4828d426ec985afdd02e94c336247c3b795fa02f0de372ce53da71138fad29a0d1aa2ec7ba8f93166ee33d14f7f5191e719d346881e02e951a44a2a4408ebc6c479d478a2af97eb60e2c6416133273e8d3b2a4d58da04834590004c4bb35e2c47fa3e3d67df21a01bf1025c458257f45a8a830db99b4202e11a96336a9120290504d61e89c517177e2adedb7b2d829b0b9fd3c98b2694e00da637d0ed84048fc16a14dea6bc8d4df40a46e3eb5fe94c56f4d9dd28c4972b0258756767617bfea8de067bcfa5dee0334f24b79ead320ba08c2abac775972703c71ddefe2d7a9ee95bce269262825ae368fb1a8b42de8c9fe65e479ecbd72287c0592253adb3dde2796e614eea58adbd826c19e9e0e6b231467e6f8e93c72c34884ca639b45b4df6b3019b7a921061103c7b7b53fb0d33fea4eb623f1cb58b37a51c27bdbf0010643ca2d4113a7d2ea1632238f6c8622ff665bd8736659725dff047f375e408c65e0c796c716256554cda5b1c5ba57c9e1980fc400130c7295f8546eb8048f58f404366fca64b5386c41a70bfbf44ac4ee3d09139c01e47260846b34a3c29f3e8e6ef1701e161dd90e0a1273928b8707da50f40953eb4410cfedb7cc39b4024f25795dd42923b265450712ba0800340850b3d69f1a36b1720c7cc28249debe6037bc50f922cdca89d9f89c56525acbd661d54e9b780df20a836f7ce679bd9d0fdaee2a45a768e6df56db8ae6dbd2cc2cb09c11568a5c46729c52fb471699660ed5c506a4269346c7ec13fc224a0200c6463e6325f240530a5924970369378731b43b381e44b9a97563fb20a8c1f242738feb3b608fcb4a7213dc455e7fe0f5b3e384ff58fd80176c4ddae80a55729f8bd8a0928490442f4fa2895e6f5ae06b12f1fa914746f77adef7ad0bb8ef33efe1aaa764235ad15b5fe620101e8b4aa6abd3be8999088e1a5c4a6335451c0c75d41b8393b686e5e1725c0c15b3bb70a0c13b5c7b381c4ba5f1aff8a12434662acbb986aaba4595102b1fbd175a299b48b5fec69b0ce21f99564599f320c1d798c9c365f1be4213d939c10e5acc4e18a7acb37f83163bd15ea1b0c196111daa382f323cdc904c1e387230ef5b9476fc225fac8dc83a42898ce6dd6f7f3d5d7c3d246c151ace39052049483818de4c02fa51cc0d69e4ade5707764f351682c845dd6a9bfead8f71423721cefa538989d081ea673f2b19770dcc3f3b3645595d230c11a3f0a00db123d72b8cb737632339ec6d6b7d3ee1c5988066e03a6397522ab6dfa96738484360b0ef5fa1012a76b50a2f22893fcaa6330f5e1816e75737c42bbb6ad1e4b1e2cbe2d3f36027d5dc3d275be3799abdd6982c7b26600826812baa45dd1486616c7aa3f232e8f7e4aa66b430328e0cb57365b559a3acf7707ed0b43a18c057e564876127d394198ad126370dd9815df7e97fd3e82b5514856054f0d23fed4a8a1c1fe723563094765a1089cb00bf90c4d849064fff641eea477be9ef5a4261f3265c472f494c5e0a86d44f1abf3f2c4f5c09edf7119336c2c16e1eb0518d571933a3f1b98e6f9bd390b6498de1b898316afd3d955356e923212ea9ba98d2dd8d86de741cb4e56719fe294ffc2f35c4e71f823b41d20cdcb7f5b21e706a5b7b77d35a2721854758702af7636cd4b04bb44715fea10a4c6aeb0e59b1f5432cb1655a8b0069987d322ddaf3dbc95c5fd727b73c38d50d141a12b272caa67013fc5c6f2657208523172d575c3dac27862700cc680bca64cc10f199d02e911230926494e31720f7bd19f8afcfea4a40ff476f9e0c4db2a48f3f9f172709dc117953454c9b172026f4d13f5194c08a8a13d790e803f4189ba52d989cf873794debc103b324f448c4b154526e4c45a828efd5913036e6a59115967def0e553cca66b69942f91728a47a07018d3f85570aa956c286538ade6bb846c199711fd5288019d1895de72e8e294f9daa63207668139b57d5727504d28d02a563d205f8345375ce6b47923ac21666d86a11e7f1e19dd63407fa28152e97b910d2f84903dbbe0fc91369212ca97c4c48c71368071c25a0b1f9c1384f6ac7d22fe5f8bee16dcc4f1faef6526f2b4ea3fb5b94412bc8b023669a3e17964b424bd29ee1139c9280561fe1712229d31a2488ff9112701c3f7533445ccb8de5025077e1a97eda0181085709a502408b898c488fdb5cf6f03b417a4248517cb20299e6807e17d4f96e18ac5cd4e723e98951dd335b49928a50d6a9618cb6e0f6bed8d96fb82b881529f6a520e5a72bc9fd5758b00a6f3f173db29e11e670255d09c0552e9e5d5d8f475f1490d6a525faf87f379b92443d06f42ad2523942d07b1f0aa47abb7d7b37e72d7ece40372880526fb1788493bdb63b96a33525d599d2fa6ef0eced94eba394a2e5f0f4457f8b91fa9654dffe7a2fa481312bf520ddb6cb02245bc39e6987865144c0e547232b5be14fb72aacb54866c81da9dfc4cbb0553b98866d43fe57c0fd2dadbf872c20a90cbf28b427b95799bf1f0b6d5fcf438809f3ff425be019e9276b68d517226240dd61eafc100b488f4a7e6a07607283e3f46d408b47440c234d9803614727e73179c23b31bac8e24efd89e6c5b1b1c4dec224c297458a68dea9555961d48f3123c057ad5c642dadf0c1f932d9b44ca506bda8e7b5a47f67feeb92903c8524c09ab10e84c249b0e8b9403670050dc31e2b514226263214130ed78b0406172aabc63e7bf5ed28ebd9f62d1d5b86a5a0c2ef165a5caa1df0aca658c29b4bc543c63e1d3b3d4e0a420d31f9ec71885ccbb4043a7ea4cc527dd9e457355208d077a4aea6818c0bb979fa8dc83da5424d8438d70555208b19383a396116327526b7364aae8575e700cf029431dae38b61256c258972a89ade8dee65957b430c972a387a3ea2308c203759ca7b7fae5ead456d4593f1d5f4a98a51ad4a27ac35b51967bf15429151211b7c9dccaa357daad81659f6cfcb2a414d3c8014329b5697206b5d7d1d98ed8115b68c39133efa0f7283d9c56ce3b4a07a88b725cb71b8f1084ed0043a696fdb81f86038398cab2647fc2ba0d27f3555150cf4598b67d701865f2c6364deb149182325efa494b231e9f90a544b005c1c9527c75ed0b94c272bfd1ad51c4abc696c5947d82cd5dfde2c979ef3fbdd42cef7f03a7275c5e9b26db0a6cd6f9b29597bf28526b907266b68018c553662001c9edcbc7b8f67dae72705ce7e03346c9c01d59613db26b65140b48802678d4f7fd51d7f705314d19069877a176552e5eaf04738122f797b09cc6cb2af02e21fb346660c9e43b53bb29b6c2fbcae172365a3595d4afcce1341b971c65c424c1e3d3a87a72f7e2eb0c725ff159a82cd2df037444014dac6ff157cba74ca136f73f779e596b5af5395457a206668eb84cb33db35946c85627ffcf21f97d5573d8daade3b65d5e04e2a357311367a1bfb2030d8950fbd6c6c38b772dfd67c32143fd1c0fd6c06c714c3d5c9341d00dc995e5955c0d9e3dc7456be5f52dc54afe033cfffc73f605f4a6f54a9f9b559038ac2ac0602729fbcf5d060ab3312acf8922799236335fe5c831b772e07f496330e3dc954a3c5b523d4f222ccec50c76cccf6ec14f3fef607f982e7073b2c695a6c5b8da36578963d6a1a2bcad0f1c903897aab2cf7ade09bb0e3c72550f99c7dfbaa6fe2c5dfb873f9f5bd6da82a1bb51e1621a91b1f0cc5a3ae94eddd626431eeefc357949d138f48cfc7f9819dc39421f8579b6f3821c4497f6727bcb2b900a2aad7cdee6cff0014aa885b61a5d4f21dd9994567cf1cc718f7e7244b6e3f5598f9d77f5a3f4e430f4a9b0374157c58a9a512ccb76ba2758a0fa722f8815d98bd764ba90460ae577256c42721afbe67a2a5ce7a1f385950e8fdd72b18547cd4926fd21f356fa4ddb8e0aa286c4f1ff4d982fd77f60abd97bfed80788dfa4567e7d59a6e1880040e3f73dbb358abc4790ea34a9c19f8481e8be7872c0b8c4c8bdacf6054f48506720e282c6e83726dd2234688e695d1993381a2c72e974b0063d992a0f04d2f72b25a3ebbe55ee9ef69c6efa2b4285b83037c77672ab28cff14af7aca94c27e2c5413b32ae7f11ca955b500ff23e815c3e5af72372041e7d42eab3c769979c0e90ce7c831e5f3565357d7729dfaa20231bc4ba9f72f0aaefb3c4e6838a8f12b06e12bf4b83aadbb25edcd4fccd91636b76e8692b7219cd7d79006bc6ac80a6c1b1b31305eeefa58d315d2f08c9c30659c0dbc60872858a3be829841573414fce777e7c9b7d5f9f63d7b11aea9e9bb5d94fa68adb7276c8b61c2b02cee87a241bcee5bb66711ee9ad2f099948efc9d31da62f8a1b72ec66e4f22af3b7aeebd17fad937a88b746b98c3185d9472c77c7dcf6951b421e5897aa109cba523cebb004f3f073fd1eb3630c25a4e630e74654e5ed3418ce017c7b427a32effcd6f8f6f975595ba99c8b9d5951f40dd3b39f26bafbfc5ec872eaece327cf573005c47f4cf31ce49ab6a595d49b9b9e76277f399953ef15177286e44801b9893626f65a41d72d58ee1d55d5a672946e820ebe43d6223a781f72ddac029851e243d0e8199280b3adb9e0886a3155bfec1e95c429dbba7bdcfd727286530b3f78ba610f456366976e760704f6ebda3dc1ddf90d6ddabfcabff972753d40f85e440ee5dd45ce038fb6f58ae8b9fb17a737e0a8248e22e5c9b3b554433643a8a0bdf2cb067f4e36212c4caf7bce5d58a74957d3bfe360c73acef5728f988cbd72b16964d47ccb49c2a4ef0a7b760ac63e34ffd38338649b5611d572f5e9d889e37a40c6449c5c8d0c81b8660aed6d5891e81d0a2d86708969aa907261a6d41a2a1dff5955f01bd0c2e46deec23b13e94c5fd2521e8d9ff3b33de272c4970020ae19d21e006f71907940cc6bf1edd879d2f93bd71f738b06c27a665216d688ade8e39c1314e84dcdee0315081528623bad7d28200ab29dd043776d0c165d21ee21f1124cfb32c5e36773edc6653c8f6f64c0ac3a2bbfda2e3c60dc4ca6fdd790e1b866cef1db1e89d4ab1e5e07259a6e29a03877609cdbd50772aa72878725aa25efa733cb3881375e2e57d1914f3fd0bbc4a28122205dd7781e5e728ad783a1f44752d83d300ae9608d1be91400b210c54ca8276671b5f6d11ce372a7341a9221306c3a61d39430e4b8304e047fe26114f0b71116f8a07697e64072937e6402f6674e25a48f6f895cc800e218b950ba70f59fd65481709af7198a72e7b8e0afdd688d060c88c8f36a3964ab4351ff6a2e4faa7b0fb8c1acd7f41534afecb6087b3ba0d6096888e7a1b777f8f209a5121fd60936d3db76273d1df612e5d60aca1d74f8bb7a1122f1d10e21fe61bf70b51cc022bd392f4bbd5181d1725126b19f373dc5beaa92e8868c9090752d948b83865c58e904d8608928dc8572a73250336fc5977590622e1e4e31002866b777e338ddd1c9012ab5aba644527226688cdb622d36b621c8d2862a5d210d7ae51ea8a70476e1a9b36ce87a506f668adaf1ffca47db3be134d6b88a99d77b101a465e9b15fca59151ba520a4014370034133fd91ae2b170d6ad66c9ecaf500b5e7a160046ae5452246ef4725af77242aee990f9c5b2707112c9c9b697890079994ab014c18355a3193b0c041d9950f570627e70ad37caa6d99858ed76a7f392cfef757bbc1985409c0499d7a8ab6fe3a75198b376b982271b1063a89e26888e31fbc4d0abac8457fa0d94dab616005131eb124aae339218aad0b50ed5c5128866a737ff4210d7676f8068b4216c7217ac00ddb28b5d9d5784b073f7aae9ddcd950e1698a0937315b6a166b6789e31122e0322b6662947ccf875726e7d7a0419dd7e56cd81e5b20632cd7f76ed4f2f1e6d020711268a286110aa1b58a1212eb6d41de66fb2c72d99f43cc19528c17239d28106419d1abe371d956798ad3a6231489cb530c8221643ac5ca7d334877241374391fa1484ce609c75ea8cf6478e83f43865e8c31f89789a7a37e1e0757270a8e48c5c97e4e014a6779754b9f8ae2d60d3961df21fc5e8d3eca3559a086c8e397701c08a1fba5efffb30b0521fb3821405de31009d169b396fd32a51637260a53b36f1dda445ed6725bb8bdc538b84cc7779510e71f451518da0d571e12a2e3bc0b44e917b9d75fda7fe053ccfd6a3e3ca4fc9ce49c8452f230790c32272a02e68d45de7981e4b324043dea198b0ac0abedc713978e6c0088fcd6c920e5510bc86ed94c121cedfc3d934253a28c0c8f1870b67a043e9af1f283c087d9b72499d5237872d2505a67c0b43b0935fe00e84b3f8f00d895e71ae8cc40e09a7727ba7ca76d34b51421278576376ea5def432e802fe7798950b56ca972a1dd7e4619e000e336b408f0020859a771793d5494e9ee528b03dc0d380d4a2e71ed87722feedc59052c281ed9696ade82fdb8ab8bd2ec3dd65d502aee016faf1261d57200e61b8f24fec5b8f49fb19027064d799fcc84360a7a956e66c5c3adf62517726baa50b302fa673a9ff2f0f98171a92e273342b5d33ee924f1df0a9f8ccb80729a12628e24150cc09aca8ed40d27dc3c7f282e54fd9eca547052b7782c1a1c72660a5f94e6adf448bbfd5ca9c70717e00a06e539f661413281fb3c7073a3b26abbee4a3f9d406410e60e8a266986a62bc60ab603dcb75243d5017c3a926ea272093dbdd1081fd30438b337c7b3402ca5b2d9a6d11f294245a7ef0fa25f60dd165639519b1b44bd0c2527e125df7ba0f83599aeadde9f4d67ee8e157138bedb72133cb42cb89e1a276d1db8f16ab695df43aa9dac39809ba25a2ffe00beca1945ce21780742cba5977fcae04b8ea84ac4ec6fe9030e99d3d1460728d5f2525a5d6266688b5436b4925cee8acfc2118ce44071068b446bdbb589fb1995e0c87f53e0af77cce2af757085ccb9dff8341707ecc4f912aeda8e80a34e4786716c5e72f283c03c4a4bd1d14fea4216f427b574b75d3b46be08cff8d1566124dd7e4b081d82d540335793087635b37fd0dd88352566c7429ab1299ce9deabaa83473b72a7e4dd44b58bcf745f795a193651b88ae4970a8a375de444678b3b4deacff73c9088f37c91c71787bdb6e48ff88e14d40bfef26648f6bfda2cb46a609bf351728de7324e921d5d7fc583518c067eeeed09b1d17d2ead6fe2baa23753e6bcec08f0f5b23afb5932f773752876ac4fe3e7d2a3b6f4a5ec5a3de3a7ffa3df4eaf724f30a90a44fd57460e743dece332c53bb80497e1c37063a1007ac11f783d08431385e67ff54effe4e948655a76106482ff3399d2f2c8bd82005dc412a7432872e706b895c14b53e626c2cd612e60739f620b74325e0b38c554bb46671c41db68b76c3f1571261ecfbdaa5bc7e9d9b83fdcd22232ca1cf31350a9d3d85a5b8348cf7be57fd5e177d0bc83ad89245f2e9028860cf3e8f2852803d0b9153a3620720032e29079c524fdc1222a78a2179b91b1844a1eb953dd102f4e6f0f7296b32aaf2608858509fffae56ecd8448ebd7b7f371360c264deb32828cef4d670b7e729e48642cb94de2620db663bbb864ebd25377941d3af877af0752b337e89ce4727d01ac8aedbfd7e59bc9d8d32a03eb7dd923708dadfafae5ea67e32ea8890306661c17f40db4b16ea95c008ecec95bd729724c0e2b4378736f57c75e374ea872129aa53aa2acfcd50898bdb180096b4eac8736cdc32027c9582cf4d80b5f6a05f7db596d2e98a379c16f61f23e0abc7d40c813d7305978760aa17c5ae08523727c38798722c58b20ab8887c15d15bab94888c3f9dc3c65de96c2bd8b90f2bb7293f52658a5d382f1a0ebcb92fff19b062144417b6eb0e7f527a9a0e366a20d52e7f08f57d8e9b3787d5a040846b7c1cf5cbcfd2afcb6dab7838dda03e138927207ad758b4c5fb885ed0356752312c631d0918632e68babbd5dc7c571c8fb3f72c1c5caee9821f26191cf9f6cd3a3d8b0f08ab2586697fd1e0a6b18583fa2c67289ce64bca4e323a298548968263a2ddc8c3de1bf1f55f9c92fdafac73236c7722d651a54c941a613a2ed18473a4ea85ecda4ffc35894e17201f0c9ff5054da7256fe352f564c5edaf3b23a1e622d5cbf6549d226045b039096a3ee27969d6f7277e830f8af98406bbad965a6476540881f10e7866aa5c560f416d6baab76ff72bc2b4283c90b391cc5b6dd4e805a395f93f1ae391ef42691b323e69471495902053b58e78504074298485f8a1a80ba5bc1f2557b052c6386bce15e33d46f38431087b89cbf53ef6dbed1d3a59f705f9bef2cb110b63702211768806e7d6e1711981289d0f5e312eaaa6d913437c955fbd8a3cd0446954b1dc8909d3dc5d1b9251ba72c5011082e1be90ae44f9af4c8c1658431d1489054597ac0de72ab154d72eb3f3a4375d0cf708c38a4933cc4aba1b68c5f419b98915ca19bc63fc3bfc9155155d67c8392a9029d33ada1db9edfaf89f5e946a50ef326f45cfe735a08115a770375c58dd279bf29d448b4829eed2daa0c0b1020885f547997ae66bea64b728fb33da5ddb2cceec84ba5a9144d691e8af30f8bd78105b228a9657ee6bb790f669232f98a7ddc5b8f12ce1efb71df489d526b191e581e98dc48479b205cb43d971bc7840b88ea34c271f8e5997445fbe6459374ad2099485eb4a1390c6f7d668a8620717002db2d12a779cc847d5afbc22a5c40776995684e78037c46368a50f8c90082a17b181c23bfd7301f0173f9ed4facab603cbe0fc8617368b309c41428cd3209299e37be20bdcbbe1de4c81d754bbf882116de8fb2b50a04b295c154c917fb240214684455aeb73bd8c8fc269e944367b426692f23aa7c5de1002862f499e23878352291bb3cc2492742c009bcfb7f9df03268fb16840eb520eb0b7264a3771bfdcb0f525b58eb21c04dbc9bc21357d5471aa5abd6a52fe7b731580ee007f10ba0f3c3b6fb28f3759dc2ae45de22c9762359c17ebad5157a5c03337236a9375cea4402e5ed1542c8d51a3b58ca25ba9706f507f1a7d7f8fc17759517d308327802dd6a315f63cffa1ead325f041cd17cdca47c1ac5ced8242336691aabd602a59f086258dc4d364555fd8e16d4372c9b81784c82e3aa7432cf239253cbb250ea1af8c30186a996b10e7aae2435fa5c3876ec9628c68a0071129e161f8721078fe88a4951d106d41d1ee5d9dd4d84936f3efa45e68cc1f0bf8e07f831122f7a3ac73f50c171f53aceafa8e779858c78d28a24e358988b1939f302c972e539376badc75a9e715799cbe5b5909ccfcc98017989eacd56e0763373e3c511a460e022dc8c5accc8aad9ade9ffc6c6b9a6d69559ec609d3795c9b5151ab3721671775444072f3632ecaf65d004a88a693002dbac065e770a2476fa9ce67a1418e2ce7de2e2082475fa6e022599ffda7fc7a1b5ac549dcf9bb38dc27adfb0444c1f86eae1a4ea02f7c31cd937d3f6b5ef21d12f7b1206de13d7035bd4e4eb72e8858d08bc885e0d3606f7f94c7cc5ac24ea9cd8b02939fec14d743007c76f7283d44b5a96dd1e2ac34bc0bf837160691306e7913f71e232affd69c809c00d7229b6b322e02926819f9bed0a6f77e1eca510a48329e0ac55a3e2b74a7bb7b510a4c358e1c4ccfc4ef44aaa8d55a9c695844320af624fed19f830cb1b24683b3476e9fafd77a29c781bb9b296dfad54b4e7d17ac0bee6a04c7df8f2507a0c2f39aba7d68f016c49fd9837be447ce163ad38815dfdf8938a14cbc751b1c2c46172b646f9fe2d9afff1af916450bbcb615979f7c9684c4c911d3471a5d114b60c72d142ba41230227029a14b2a429c032e6f0f3a12f6c0407fc6a61ce1cb397ae3a8f4c55cadc0cae233493aab73c95e55c9c7cf9f8a343690a1c24284548e39766a387bd346412023b7520ec12aa2bf0ed99b2b9d25ce0a708f4214080b4f41972406ad320803096035754fe7aa47bae3dc1fc532e7180fa4618ff8fe1b16bf73c5a7c98ad044f5bc6f65f59857ca25291d4c8f4bec7c9f5c2686dd573be19bc195696f3a183b6d6a119f1c2a8e7bcdf00facfca72bd16891f2f41c13222e2ae72516abf46f3e1f07de57106e7ad791e4f71ab1db6d30db0bb21cf5b44b4e51f72e83f8259a6fd04d265d875fde693f7a41964897aaa6a55769f77300ad1c76a7205e79794e248d7979c9ed9691cb814c5d4c2be6664f1630dea9474efd618e37203384f101361e6dee40660f4beff4f3a812bad0d540a51f201cfc055a0a9d15fd3019d87b78fa8393cf82cf02fe77c28d2a227174d2a06fb3e7cebf56d7a693efd8d22f5bbd7bf18d8eac0b742ad66092291291a49c2139963a2bdfb2a16ef72f3d456847f2f61b2fa957d86e65abeb6b0b4d8cf57a9ef0d7b5b9bd1f001023a91c9a9bed71ee50397c953de57d7310ea26ab59e9028bd18de15fc13564fdc72988b2dc6a1471534d81e83b1b276e6de3c52cb7fb107516486bef5a6854a55729d9df3a9a648a819fc4ac972542e2c383e27bfcd6184cdaf4050ceea1c12bb727eebbf7bf0722ccd302de30a5523614e1bf7f741d564befcdd07c05a92ae41607e9a5142e8d33e74724397006aea400c5a874b528a953a3af67fb117e91b567230131f04bfcb5487650195650c5ff546c383e5b5322460785cc77bcc13a7d61863962047fcad4dad861adc3d1633669b5e07ac0829cb6cf566a934f575edd427167de5a9ad7018e78f902f3906b50b4d5d698e12045d07d420db875bb63e5672fb55bfeb00a306a2e2ed9368631c6e7fc6fa065fb73127e63659257674cdd001fa8fc5a31c8efbc9b4fdb7cf285dca839415032599a513c80debf5cb4dbafb376aef332bf92545427243af9cf437b0eeb1c1e02b52f70017c010b455040ece5d07b67d4786671f22b7b44e276ccc33f735b639ba453c2c016eb5064ddff9557284b16eae4296bd6ff411cbfca25996577fd6cd06d59bc5782013af11e5529841f210b1932fc5b70769e9508e440a9179e3f1c6d64b28fcc0b9958d008cacbf1ee8a0f6dbe48188d42fcd3f3a229b4b08eb8850354d99f8a99ef9c8be93630c3660409f688e7e03b840d1c85e240ed1d1170aa2caa3fe5214d4dd1f74b2a702580f69c9d45edb226323379f822f9a2c281504870f890d1690a527ecb8b13115729e09c213b537cea7eb63f28b50875454cee2bca1a1fb3a28b5f5cc1b6f95f97299b62ab118095f2121e1d5a900d58f7b27a02737fb157c2af5d05ad83a9ac372a8d50ff625052d9d606e52e74fb292cd16b0fcd0726b6334c4e8baea818f9f72315203aa9b977d71ff35620e22f436bfcc81ad9470a1b51f857647792c3c82722ac7c406f86d8b24bef186860e4a55ae1021a51ff6aece5c0a0bfdf1a2ad764473e8f489af695023bd6ec61224ccee1e36b65ee3d4dc251ed6dddbe7facb187292e401ddfb18339c762207f7546c268fb85de2491f5a89da15249deda7ddda48f0f0b3013cd8c1b03711df06262e3a94862a27f433f78f0bcb2e35f982b4f072fa72cdb8e31a78ff41ca2d10eace20cf9bb4ba7e9004439911c94aa2df06171321917a2cd1ba2c0bdf0c277bb78789d76e5bba1f796322af46825af7bd23e972cb1f63115f81a8ae36271385b25afb9b7689ab37c6351899ca8ae3577814123c544e7c4eadfeff61f5ca61927789acddfbd143f7387f503e042b8e9759f56d6505690f9a6804faa9c371cb233972c6c9a19fa4202e54072b52fc95ef46657f725383da668d3d334823a7f327b6a56edb61c4ebb99abce66434dc4eb4965c91727ea482026cff12930a828387bf95a8defd46bf1a1f41035dae4222278a591b4e30f4c8317b2dcf7054d8296fe86c9cceb771e8bd30faf9b651b8277844a2f519dff60e57a20853c52892f0d04555878dc309cf8e4655afec8a9227a35cf24172ef677f5276fed914714d186c06d8a2837beb58b688a8fbbbbd75f23173ec75024fa32d7ab9eac83c7d03383e96df7b6bd2871359ee70f6b1356f5155cab998726ab8d335af0e4c837da346a6d0972bdcc1d119226c90fde773df8949d1866006f00db2ef1ac8665d0c73fb0e468ab5abdf8c2de1e3a110f82ad4d5efc434556d538d6d01930ec66a0dde122b294d66c9624f5877e137cca33fef5ac8e5817e4c95c9031fec65ac2a15cdb00e3505d9902aa1fdf33f80968c8dc6342e66da8272810128332aad8cddb89fe31b6dd13ef031a5c90472589cd558d5a701c3df4d5beb4ce0b35a8680dcf41169ed2733fde25a057549d10ef0a35d433d021e40bd7291c9cbd1f27d0a5b3ada0e2196cf68b6fbb2f63f967e938911ba6aa3b3a47a7248f8d2cb9f68d098a73fc79ac5731d7a9f8eb8824baadda3b5e556ed1a69ec7249805f3ee07f5e0ce76bd356453f825bf560d235a26e397d274b3b60f86fa25feeae82f72a3ddd1a68133a064fbdd9532a2f72d44bd971e8012271a9b1b995527545431b1ab08feda6b0d8662671cd3a2043c0fdfadd9fc879532cc26a0aca72bfe27d8f9266b02ace783baf88f1536c57aeddc6847f9d527916204b1346854295d3da5ecd41f1d8ff7457c81dedcbb1fc20f920c273e6d0902505c50d63d6321d17dd9b5f0bf3faab6902f96635e24b9121eb981d81fc7abbdde0e442eed80dcb4e96d28601a67befe2a1e1eea22c20e8b2e696d9fdcd544a9858adc004632ecc1580552be7534ad36a108d2accbdc653dd2c63b3bfbe2dc7413abd4b64e72ac5a692cc3160f5cef79867e2f6a9deb539cb49b6a8b729308529fbb52ed0955887e277c9e885aaf2beb42e725ffe8aa3ac25862aef5c0f4a0afd0be57601515ab48bdc49c10e5b8abe95c085b289dd3880d3284e0132d018199bee4e9871d2721e41380445b711ca112f2cc7c3e22aed898703212d1699a733ed4450c1a5d9721aba901b1cf5a396b5db8683f281bdba1969c13be80a29d6d8368cd8f85cf72eefd7a893928bb2576bb40721e62c476ee3068a291b4387a8c617ce9bc92875726073a1afd1d1189b4d42199920cd58dce719721f4eb75fafe4cd6bebb482e67255378aaaf831d81e5acc8c81a9ac7292ba73bd584718177b3bbbb22d6ce79a33fdcc93d0b025408d55fd95c460cf5c7ca1bc5393b5076952f4fb0d51b05bb21d956acb29350faf0179c242f1eab11ae26ee48092d76ac5d3d27f55e1646224721be1e470e948b301e22c8d286f2e85a6ab9ec7633f161888d7484fda22d5db72e10c258a1612c2d2c82fe2dd8a52a02632776985f68a1a6682aecfe9c21744592c63162f821cdd5ff63cac21dd0caf7c38af1ad38bf497d7338a1023f1d4d90a9d1f3a50c3b2e6764b6eccf7ca7e663c2630f2de71cbb468da82039c459935728338400b0de8d60492232048a56804af46e75d3c021f6066e3e9cfef15d6941f85543124511c5fcdab333d039ae4d811ef4e2a299c324eff1b4942cee6750072bfeb84fd675508202295524b1815fe8dea9a8f4091bb3fe126685910784f22223e1a4d3b4a7c73114a8607ecd51cb69988fbe1d8012e936e71467f4bf9afd072e81ec057ae55a77f3698c71067d30b11abd4ffb4b5e810725ee3bb2b23c3147226f3abe74fcc77fc11239c6fa151b7697c10ab0c5ad9e01f29e13417930b993f9aa79f1febfe9219deded035070dfb872fee526c2cd84175a5913efb28e88623bbc4838428c7686fc980860d5ea37fe732674a0df6b8aa95aeb8da4f12fa1d12982d577915a2d60c11870549b1efd3c4c7364d388a6f138a7bd626211997e872efa51a90cb96f97c823512409104dba022fcbc6be7a284b75e9a1bca81bb7172d7de89c62a04d5b8b2d7f55354e2aa24744c0332fe3238a7376f05473d5a4510fbcda5ecbd59f90ff8c138999992c1e9bf3bf6ceae5613e46c073ba84f208f697ca0bb3782c7bcd52cbe3fab96e7bdb4ae7b70d82e5075ef10a57afbf1ce0f723c04febdd3c6970ec1f8a081c22bb76c0414a3c1c85839da3cd780f9ed3d4e72529a2f0ba3c2f0e4a9af09a5058b61aa269c285ddf7a638d668ea02eb1f55b72c40874224fa0769abc79fc7ced46ed043b146381500331046773596cd5c81c2c8904c6446d2b85f48f64f59a771a96a790435a4fddee842d1dd1ec41d4d1762b518f63d7ae790f7801661edb7afba64ee6f8fb77f4767fc6c2496e07daee9772b6d12255dd956f9214b3421c56281138b087ec2c5e4e5d8492d290fefb78912ef2a11345ffe33996e305624de1d1e623820594a6531936f54c7555f2f02bf972e1271fc881655bffc22c569fc6eed2c474a97701727ac7bf644aeb9afbeb4556365948a001dbcd8115d4d40d0d05fcb8e9e5e86b3e6130ffc2e63971ed8c3e38df7b33bc5c25cb7efde50af7957c0bf54b18cab9de4d2a54dc662afbf35f536231d698096e04c9bc3b0c6cb7c1ed5935017b3f913d2904629e7253f61c5a000e637f62878793cfa3c95f788cc8dbaedaf5301696df305026ea5e9a166af45e11aa2ade22f70b3798f2ac13e7c50a1e8a6fc210d19a37e09f412a620fc386b27262ba3130610aac35a5eff2333dae8f9d14f6f22474c70b11b615a97bea5786726864f84daf8dca4804ca81cd9a9947de76fa2dd4fde3b47e55a948e8db45cc12675b7f65bbc1f7291189ff588a078c7257c0d35caa404b6088e0fd0bebbf2b538a901b04a05f59544338e2b5ba307e10b6faf244d311b9b5e955c67813e1e1090dea22dd88a0f3a9629bffcc65a0295a11f71b483529ce87a4bde045dceb196192484d2d5cce99d435404d1eeae1d72cd4b764862267d528f406a413cb0c6254c6772dc63a4e12079f699cd401a7803b11bf67b5acfab2ba271b30ad7007977265e1f0987d3f62b02110778825d4c039ced95cdd703f8ebfd1d050fa767a6d48a89a98a03a9e8c76c3cc925b72bf7ccef5c737c821e624340f42490939085b46eb55d4ad20a2c6a659349ba716104c1498cae486662de13e75ff96eb089d8d6b5c9fac03c6a19ebef28e41b4ff798f87bae14c9926a1a3ac5f1b1d2a2ee9f97210348b53521ee99bc836279b9b3caa26f403ecf81ec955b8b5d4c6daac92c05a0174e7d97bb70193e95e609726026017af9d36150881049068307afb7094c372706f126538f996a39c3a44516acd4916e7b20ab1f1de9dcd1a17893d72e510723aac3af8d0f2afd10d383e82acc762301c863678f8060a687b785355beb5620f8d6a32d6c6604b91d3dc06009f66f6a569743fe7ef41a5c9fce560cd33dfc6721433fcc9f005c233258f8fdf2d89f49c0665d5dcdf70b0f19e1ce2ad56f7a0722c392c3b6dd9861fb6f18d9b808a3128e6ae51823429f941854ab760b5758d726fc94d6ad78a4285b8074d1330e2473a6086e63feddc31562ac42d921cd815722d97505b7eaf869255f6e86aff768331747e125c3969812188b1d237e280597299b0b35934b62e0b0a9d92d5ef5ddd7ccbf8328c30f4ba8fbc28304abf5aa27228d80e05d9091bb143fd4205d5814125e61b487fbf95b9550dd6ff3de734711f5c30f5f139ca155143ed7e4479ac8490651b4a3c7b7f04f9774e0c067f384b3af6696388586a4994428eb9840de1d7171e2aeddbea94a7ca8c081caf6fd27750fbf6ec0391ac009e7f5064391c80fc91eb9a47c828b93f26e645d7ccaa2b844d4af9a643d2b782ec3ceeb1bc65a372de3781430986350ab70871ad39dcf95772f9701e7f912fb97af8b8f664c8f0421b7b09935cfc16adec91b8856c8d012172540e2d1e2d6325a7f0946698842a6260d7583bb6647005426790923de4764b1c937bb604e3096eb99e200c2b22e6a4da90ec0830d93ac54915fc45562603a472ea102c873d39dfe5b1222086d30121257554f80597926eba82cd8bc2cb36e17258d5cdfc874bc943f593157e1dc139a2623f1be2759310d6e65d72f1435c4772ad9e0fd4c4cb6adeeb10b54d78dc64d4afdf15079c7f746378f41d3f2b5f6c5ff84a93ab73477fbdd71d8b3196c4f402524def8852da72053e3c544a1761b8106495e2f176cebb00ad34dbc7cdfdfb4e10bc14e2c36ac9c505137523233dd10a513e1dab816df26787f66807ed432b17b3bc85b86830ecae41f2a4ddfa1d3e7264bb8494c44ce0e0488992289136b4da3a03642a9a2e0083e88faf63ff63dd723f88acbcdf1b7dde101b54978365e102bfa3cffa4ae24bc95900e1ad1bd593538dbe184bfbd3fc26a0528791a9dbb82017ab471376d9ba840f912eaac11b0672dd6826ce11d516786e822d8f8f242785cc644ef7a69f7f53660a9919cefa8572f95b0ccc0c3a6bab30d3200a993bb71e77a9b20eced4f89cd7a9749402140672ff8d0f3efb3dc524944a32fe7d1c9cefc0aab51cebf0a07bb74010cb21f18648546b28f89728508d13f4ff72fab586b11147e56c2b82970d4a685220de93dc53c69f95572b1d74f2a7358f18dfae3ba7458f5ba02d1350426a58198672aafe6d185b0587884cd827cac6a33bfb6042ea255a149ee98f86df9b0ae22120aca972ad45b6bcdf724ed54859887631477613bc79831b5598a74ea7099ca6d2f77672461f7a6de60112493e40c610a79b0104ffa903062935e153f29ec630c396f0726f14f72b9eff39ff63836f54aced58f1829d5f817efb9176ac1324bce43789720fa4b6114a04f6c8424c5984ba1b69b90c594486f4f9a900caa91b846d50e35e26c41381e210a77d274e68d1df26bf732522a10ce58a66baa4f72a389e542e72cdce9d160a2cf4f4ad12e6d6ee76a21c6fa356ba2e0d86eff4cf743268f29e7265b802b687061d338780dab1ace5e67f399292603d8affd43200b326f72aa67214d84daa46d5a828b1dd7d5824c6104dbbe67109dfa58389bebc6501e960d4728cfe3a16dc23459a8a8486955d61aabeb8083649923480db64646c41f235f3723d264981a36c7bfc4497f346ae025d3aa755197f4175f827ea31666912b1cb7236a440b8397f04a26898b0478417b77bb6a8513f98fd14188e36af0a503228655986e1bdca24307aee0a1f02c726f25d890ebffd26ea0a07e51035399720732e7b1b0f59b8b353d7e0789a0aa0f1888ebacec90bc360a7906d1f99e0286d2a02c4a6c483ec6a85a9203e78474f3cc6ba2c2c5f56fdcf1b561202b47c1ed4ac7210b8d8bde0a19e38d17dfbfab68ba4dfd39c819956658ee6cc17c1103e6db4074662f74af5fab58ecb20cad6f2962ae17621f89505d8afe5227f6a364898b37217cb2b3d2b18b876c3be7793b6f696733bc77008b1b6170c287fb12eb4a43372258095cc969113b37db42599dd09a650e9a9958ef259031a0062448d132e8572846e1952483abb81bcba456c00c771aceff6b36b45e2c2e718a2b1a02600c8729b4119324fd600e00b2e5f92522bf98447fe1c2d67326bd71ca21a14e6809a6aa4757df8eaed9a3b3553e3196c5e77083e6364e6677efd7d77a46208f894bf3dd53cb41fea962748ff6398ba518f561e97ac5aab20d57492c2d5f20417ec497256606d4a6c71b98a225098d948547022fb6a2e4af3cd9615fe552ffb33a44e72ceed7c121e886f5f74b041204a5572db87972610169196ff16612fe739497d72002c1abe36b4eae1447a34f3124b2177c1540576eeafb6c8dc9ff6090803634db46d62045086ad63f29528b0fb479676f54fb01928e04a52ec762f915be5c4250e214d9534d34da17302dbacf8756d348b40f0ad2152881bdf1bc2e7035d07722a7d5a571c6d437634b32fa9f5c615a72230f01d6850706e524e4a91eb54e7727db440d636b425d9f5fcdb4e9a1a8cc87d44370775bed80b3863bac3d8538c72561ec700c4a1af5c97f2497ac61ee3920d43c7d317a2cc1a1675ff6bd38ebc6d80deacd3d9c16f53a5826733009be946d7305881321404385c3d6593cd081e72fc65750124d5fad0553f85f395a1f9bdc92f60d186c47451b3f6c6ed721b1b29fa74653bb30bd8411a80438598a8fdef3ceae0725f89667de361323339600972fdad69072fda3b1bd87d5f29009fdaaca19289bf7c71ed6514c9cd032a7ebc3fe2b4caa370d1768991049037edce1a2a41e035d924c5d935f1abe6d87cc1ce3172550f9e0bea641cda89fab1747ea3c71dc3d0d6562abeb3ac6980b157347f1b452188da7b9892aa2fe6c22ad5c534af7f14758c9b3828f0888f215ed9529a3413f0e4f317d0226fd2972f50c0c795f71991b10aca648ccb697c403ccdd3ee0497770d02e51ca4d74bdaf3c81f13a0726eadf4ea1d88cb7b99bea3ff56c4dd7297c8ed74041fb45546d3870b195352e41c7d35e143985074bf7e7e09e5d54172abfb17b7bf58f0ba92c0d574bc995585fd2f4c9ca81ac9b58a5b22e500ace35e6e5eb2d71c554cd964015a7c6c3662a0046eb8bcf489a371778e00a44db86272e8bd31d832e2fab04d96ed0437e6de724a0b466337c9a757e71910b8828fefba0200001b8d0103e3a8d9ce8bda3bff71225be4b5bb18830466ae94f517321b7ecc6f9405c1e78693275c3a8c2d9f2346e8a469beb609bc409faa33dc74d4382d32b855721d44d8c293b7b333bdf92f542618bf7bf75861ca35ad38698c57bce0a5948145c2d4806708e7064469d26f713fc670b19a87caff4eff3205b915caa4e5bb1272d717e956cc8d3d175b8bbf46ad1bb982d1d0c85e43981025de673fc6b418492c63b2cf778c00fb649a8e21d83d3a9021e1c9549e4795a1f5d491389e4809f272fb5fb36060aca13ba15af5b98bd332cf23555799e6df55ca64444fd7ab41683531f224272eaa92a97c5c80e18b309d4823c56ff2ca9d91aea9e5f3ccded8ab72e0aaac7f1702ed2ec81b2c2af898e9b0bb8b135db821bc78112098c035bd40721d98eeac460e93cacc873a38cb4be9f81fd2af539e164630fca3df474476c06a2ed5d3257a062e16eda32f4929543dba1232621ef9ea120ff1852f560bd9797244ed1552946fac1a58841f7c07246046b27c5c8611b6191e1c81b30fc4846c72c3963e6577ff273356fa4a4c644781c725bdbd5d182e22f3b7cfa4bd62fb732398c3b999256cccfe80cc868bdbb6e9255eda3e5308e8e9f87429b7476858512076848661b9558449e639c507cb6478500daf6bdeb07540ecbfbf5d75f24929723c2d2dce36ddab240b02c31deef89ecab24792042a3fded30bcf5f9aa3975b72690733a3d96ba44dc04fcacad3590d2daf49f8b682479158153e2c5595496967e7876570e3253e9ce0b916bc59f06cc070f0a2bc1d0fda5834470eda25bb4a728c7b3f8b241efe684c7ce62dd1152f804c83bc1fc0a76893429f134aa38f3820aa88b3aec1c64f0ffccd3b9c239a8bc8c8075e5910a8036fc2e3a2d6b246b36ec4ce3759aef2c3824c744ed0740bde65f228497776ae9521318a983ff20c0d72e619d5b9a28505b7605f0563e0ab825c16163986411fd1a67196e9da922ed3598a4ecf328f589b7988c19f500ffce85a09b527e117b282ee9d834b76ed7a704bcdb55c52e54d6bcafe73c53cf6d7cc8a38b555e747f211b9ea0c85f0305d99722b9233b25f65809dbbdebc9198d3e885d9550126272f3c647dafe12a6e216607bbde5f2d09d10beaf2b10ae04ce3675f45c61a005849920e5d2e7ffe7cb9bd726ba15431619a8a9714cda9c84cc88b0b637dca278dc3ed4ca14b3f64a50ed172b2f4472f179440f0b361062b37a1c0dca32c1c57194ae1ada79900d2ed2bf50ac136a76c78fbbc9fdc0b902bc1c314fdefd7418c207602c36c17e3d4ace9247221180b4976139c8a8db360dd4d3b1e5b93e35d4fd803d5e815f6b54f4491cc72d5405f8212c5a4f99fb934fdf41c50992d2e18de8e3d591887cf798cf3579351c7fe261a21a42ab973256e9d6e592223c6b6b4516924dd4245a6881fbcbb9d31f10381c446f511f79ac1ed7b960273e62fa68157573c85fb0c4881649ecef0724392d09e18724d25c9c0a3c2ed41c7cb3dd6b72a8e6248fb8d9af9e3e39ac5700624697f22c5a8440ad86b20e18936eed3a8621e99aa79532bdd8602ef757272bd19e0eefc6b787c4f144079569a0f998b46943de10b228d61a621b66c768c6fe96550d7290f5bd43d8884b50c7a07569441bbfd492c576b4e146b0e22193272d7619006808d1b96e5224528e4761909c59075f92f532123894fd9cff6df3a727e7f09a6d355d78823d8385784948ec744390c7695e22877df6189d23425bd08590f2ac7b3808957a9b4d2b9af459fbc9fa8ea7fd7e6eff83711e1e4764d6772f5356d92f2b50c220cae46b46ada124587bbb312137c922e736762eecbe4dd72f1ec1721ae70ffe783e56c77146865af1a9462194104556f3f9de17fbf86674937d3cbc7c66a34b799cd01a4f69f6d42508e65550862de0c2824982b56d55d725ba39ed1c6bc1df92b86c71afa745ed22514dc243b415ec8c28c37ed7ebfb17213f14a7b16392ca1d92f04d58cc8e82f3857f7b7d23b1beadc634bcf52b7707263e374e4a7acbeebd60b29811b149fcf927c5c6dbbbe20378335e416381b67721f56f2b6015a2cb36eadc67855489414e6365bb2ab6af768453534f323d8937286f792653e2fbaaa570fb52aa689534ad0b7ebac990ebf6a8e1f80981790bc72f7b64c8177ca2decd983f91facd46714969dc3792dbb547aed008ef4fec7ca72c87d7a41b829f5fdf987891ab4b6030e83ba10d91e39100f47769fe89e6e2403a36a1850168450cb45edc43991a006544303e5499dcf09150d369654d9741e389641e2fec669efa002a08fde139bea92baa6e742ae78e9bef610e4cb65402e724929b2d2414ae58b621baf867b785f34bc092aca6ba2add71d90b08ece2cb10d7fe44dabdad91b8ae0e95b44039a3c5afa344bdcea143a16fe3986f8f01155724d7bec60e86f3b8e2799b14444be547e5ad67f701f3b151818d9fe6c6b8ff972607755a3288a0ed374a79ca30215064c0f7a1e587aa58243486b70e11caa6402df9fb819f68c177c1b84c1da28b1660a78f87221caaf6b5c912fdb6fdc87cd7245d0975088101810a44b782a355c586f79bf017c51836f426ed779b6895fb66e43834fe16c0c6c9e2ae86b8f9b1940fad165f54b24fa9987d4e44741b965100ffd3f48dfac6fcb5663b1f0ecf9a90d4ee5299ef9179c3ab523bfa663185f69729d9770c89a3dfa2ba30396a312fdcd5ac7439e1c3ac0b817b66895a7784e4c2bcff4ff0a241b9dd2b4cc12a5ad38e34a3d9dfc440c9a49eb0e83b63bccf7337249a5c6c29e992e8818dd2ce0042a40505bb8caf68b91ddfd26bd83cc59eab7729da84e371018d69c0c063dce885df028c8f81bce39277ea3b716de08185b672eebf57663291b1841073a7d2b8154f7bad8be04048407ad85f8aca58a4f9c717291b85ebe507bf242ae05c8dfe73ef6453d8406fa1d982f5ad35a20bdd50a914003ad920bd5ec38fec1c1f373ac0a55cd278a897335ac1e328c8ef7c9017e7c7264381fbc22eb4ce570078069fda329b82f3b67f96e0173ed8faa9de5b5da945d3dc6a9c2fea9889d73f9f98d5bdf48eb45b49351e313ad782ca3c863039e050f795f9488ca5f245a0f68d76886fe3b8c0b97978d0ba397ba3b19aae1cf984b1b7ba078f149597d54451dad3c154dac974d42cf8bac46c019ff4154b1e421ed72ab5001c759313edcc911b2bd064425552a30798ceb94c7c0953d78b063935e7235ecd2e2a3b7a45deb69b2404b3b9d54a8e26b197ec4ded023f7afa0936cac72aea2dd8e467505fe2dfc4761f18c8a6a1af31cf618a85e96f3a0b5af0dc511725675cbdfc83884a1f3cecbf2b8a78570ae7dd36bae00777681d7d2f8bb252d7292f03ff719cc2217a0aa41a28fe374263c161960423fabc113c588bd5bea552be8bcadd4868c9861f9b1f4428bfffd20294eeb7dbaace7723ff2850c2ef5a872f5631e8b5692988dcb8ecb48f2c73fe85da65c1e3b64c29e296354c0ce102c726d8c46edfaccc336a8a521040c75a9ecf6d6565893549adc9ef35008146e3872779e7dacd6f69d876d047791b89e2cc49a111e49308e0e92bfccd2100726d0710f8f46d72df12a80c307fe6075da358eb307ce6b6427bce125173bbd6237f4727e0e83c9ac59ec09e302c4d6c25631c4435fd09f06b74dfd96956e3d09c159728538fdc9a69026bcf138be2a8d6898ce79159a95421ca59ee12eac1b62a17a723236dbcdda8ba0b9baf073dcaa3701d7fdb340f5ed3da98051f48f1bfc3e625dbb0f212cb1f0711e855a404fd0e4f5b0c586008e300f84651f0665c063373b1c5bc46bddd1fbe1ee9907dae429049e20b25e9a8d8b8c1c25490f72a81b4be072e515de1eb5a7dedbf69673cd9af3d1781fadf2d7cc59d51d54ebfd5d91c23272e10217efb579fdae65378c32c6cba66355d3e6028072ac4d192f1227d394e87231deb4bf24ec7aac3e7760f3d4ea69a73e2b33e8bac6dd9c09c32293dee12012e75afee3cbb99d397119ad5c2f205e9c2efceca1fca1eb36143693aace2c4872c15626257a50a793a043a061e23eddb0f86f691be765649e2e80e743b6dc4a214a98e4c090f6b4a87b412fc5f0a4df58b9f2a4a9306c6278dd042fb7d2baaf713a679817442ad547cb26df515df947cde97b4a11c248ce4643b2079f9b44446c25425b22dde583b097c41b0b0527b393787d71e6633410c92169a232b0ec44726a57e58911af111f0d29a45d95469968bf0122311b4a9c89c9a61acfd257c3727509cdcc74425897a05859480b2179a97557922f36772e743324f0e0cd0af653f0b444e30ed4f943986f16629213b4895fa19135412280ab91299ab636d6fc61999a9f85652d1cac1f67897c650f7d3398dc26a8a0efd6cce1828def28096f7299820de246370e0b606bb0be712759e4eff8e47d191cac24d1d41069fa98c8728c6e936af7adff3731c415dbf7e805f1f830b027ee4f05a6d40e5a69fc6d1972e90d9a760310c2e0aae83c4475bb9caf9ca2f3d703eebc7a1cbca4d91fc2ef728fef5f99785b07a0176890d461bda7db78dfada2c91229f7028a23532236e04c274a45c14253535be802bac83de83327ad3f4be2c75923f544b6e9b1302cc0259761b3138ceec0b597884b73c08ab5b4f3d6c00d9dd850e06b8fc0227794dd72028cf91cf55a9df4daafc85565ccd82d631c79d0dd0a1b61149da876c4dc7672bd8e134002272bd6a8be6bc3c91ba4e6593bd2f4be8d19cf9994159aa2ab84198cd7d1439440a7b6309a4006a111934c8a1793b0ddbc2ce70192f4d4582644505aadf7d5694087427b5f0242378cbbdedd201455a333c04a4bfb1d2364eb2472ff7e53b6cf6d456a7ddeb0c8267a742290219ca85058fb90cb0a8735d97f9759d63e96cc643691c40c4995ac84cb95496bdf820139cc4a9a11d8b1df44422d1d52a3a9e4eea9fb22315fb8ff8d7f38946148d28466511d7771b433b76284353d82c8cd8f33d8cbeaacf038c46a102892b1b4aec39eb50c5b2a106bae92acdd7200ebd0a00f10d94d885576feb52bf518c5e2f56076b01c3614829e1d62614172549634abf1ef6005b9d149aeb9e5b7b2b68e423f668f26628b65be7fa75b4672f8b788ab78550a110d52f26439408a155668f404ced68c66ad7c092281e78f2a60fd9c7813a9550da74713ed04403d33d13d0822904ce62295d90b946af5a8724f78814299154f2416c2dfe9c4299b1d1c5b4935b88d4090c7396d5ea1bbb8726a6a9f2cf0d0a9e9dbe5338e0b2c0e9cdc5727288d961262710aaec647f7954d359cc860901858c6137e683aa0ad3591d7d2fefea8555c688bcc341fb0998364fe4ddc49a3fe709de08e922956e95c2c5fbc744a3ebf917409a06643421c065521e1bbe83c35618c5b93c4f99fcae294d454d9f910f73afbbeb7cfb618992e1e9c481eadfc80c6f51daf3549272b4427ee2172b28a87a63c468aee4334c3f27278e9748b03cf8c2d6feddaf2867d86f85a1520bc5198a938e620679eda82cb72cdce83ac65555c5d3777ea90bc9d3838ea52df83253ce8691d5b07bf640a69012e987d88326a9334fb7a014a68caccb6e0e74563a7d5067f81a6b026ef89a85685b3f05331c2e9adcbf1bb5193c69e42e38251c5d2ac31e6ae652897f09c5f726f60d6b2286cf75860bd82404ae4ba042e2253812df95401f3c2a82e0033cb7257ecd4819d1c2ad3f6f577a244427e7600ea987287c32456f99799f720db3b7285f36e264f3cb27cd237f0918ca4aac89ed5a183e3a1b59bf084d28dc3ca87516fe79fb52bd22597da54673634dd54555af8a17a78640b68a45491b22cc92072e9633450302e449082134464ee2f6b4fe49c6940c28e6fe0f4e642def5b6b972664086723ebbe011fd06337fcae1b29b8c0a0aa0236f0015e9c0dac5f6ff295ea85bf2563eeb37ea0058248a4346bc75822e04a7fe772762df90215917ad194504ca257fce9fb59bff4faf118217b92e520584da2e0752e67afba3499ede6d72309c6a2e235038706ed828a9c1e10c56321a43ff78a1356210a0fdf2d1611972e18d0d3f8846d7c85e4d7f2f881a1138d257369b850237627baf862d2696d3722c1eea1af160af19f55cfff03f283e9ae942173426591e51686ca70c77af276ed8ce5e931c45347f36de1a4e2b013ef670040152e0edb6900addff266ca5e55095319826775ab3050b1a7b6a683b36dd7bda141dd750675e2b9e8c5ac505596210de154e51b7974d73e0d8fd0fc59e41051986bff873c4efad2b9e606f5cdf727d285a17c96f7dd03cfb38dfa3808d700c8bc064ec459cf2f57cdb6aa0351511a07add7ac5c03f9f6c51be3868f7418b1146c20075303e5b098d4c1cce45657262ccb084d13a6b4e0bea52a83d27076a868fb27f46c8376fb53cf3a79d058172dc7766a5f09cdee3e69d212bb2caa262de8685a6db2a83b7cceb0618fe7215143ab50fe54a4e7fd845544ff0a292aaa1b6eab2521bf22c937f0e649ce1574f6e9bfa3dbdd1c318ce9da56f2535b08376dcea58966d10c665ac47d67d69668f72063758709b979d9da13d0a546c9dc4c60d2213e24ae8825d464c42601163fd72df8a265f7bcf8d35478c769a6c4f7f65316401faa24836d827229fe1f9cfd37281aadaf834da098d00095f202ae5d3df857ccf61363ec8d52cbcf331086db672205fea98d7518021cf5930f61c5e9dcb422f64072aad481b297c14551a8cd972245032c7fb11277fbeeffed4f3aee36fed44f803da8503ed1d95fa4cfb280b7225039b14a95d988647f1974bfd5b6374f016ed963f09df01b216a70f28c19f7264034b3e2b0d298ca7d8f07ebc959b69374ece714074c0103bc84c0f2b6ff018329731490ff6308b8be757c79864766ec52ee6b01a75981abb08b507ae7408729478b6a434f529c2f86461e4e841c4d08e595aae8592e47f3ebde19c2037f51622732320501695dc749a58f3db60d195977d0edef25fe013d7cecdbed2619a59a08ffa37f909c9584b6f75935a0616c5824b17b4ec3a1d3d36c4311062c6a00acd42fff15173440d0782aa3c21655f828e80eaae57bcc7a7e978e26bfb3acf72ea633357cd95535b2c42d4ca9f9e44e25c0c485ba8a41fce2e33276b4a850772c52cd99ee3e37aafdd85344fa24216158f3f22e183a994ae9a9370d301a23472e7dd32cbe62ece6a7a121a88d72378877e22c03a6c416bbc8b5da7f63ab98d72809c024f5464f0c30f770719dc8dc762ea6da77f0dd9e5e833af344a889eb31a02e1a8c2c6e0309fefa8ab5a754c3ae1569ecb8dab1437456b63db35c9fc0c71237eafd29496fa8f508bb40ba289eb8db8a4ae8bb5a3425a915c8c9c2342cc1df32cceec29cfb00651229b241f18b37e047228b198fcde8773fd4a77d989482f0e827b591fa2f64a50324a229f1eceeca1263ad8c858359a3a0f47ab2fca762819617a53ba0564ef1ffe4e66b63d98014c2cd80d0d56dcc6f8b7805bf0f5451bdbb4c463ea3c950bb11895790269cfe8238a9063c7c99e8ef8171e81421b69727ce0167c483d86c11471e70be3a78599407333160fdf77e1eab3d5cbf6899e72421f61a0c51cf20d3df5d8c0a2962538250cf047306b580f35b5fb76fe472c6ee781f91304db8c15452c0de2750fecfd5fb0c6d1e367cf4639e51eb885006d7208bb4aac5f08ffe35078744f2fe2cb55970ba177e68daf1f92f6ae7104eeb372bd883e35d33afc0f6cd47b942281f18e9c817298853cafa7855ba5cdadc8cd727a52e08f25bad6093b535ef2ddf8e87995720995fd6e484423fe9aa5711e0f1bbcc632bcde9c5e6f94ab5dfeebaa1035f6f019de21a81266138b545d3dee151348e2524ecec6a65e50104f7e5bb89c1bf6887170d05142067c5795c0ec87cf0b8aad7b217477d6813fd37564ab2c533d66f3bc84be245aa85a095739f4157372e65f7ae16c8b8c5f786aa408bea860cd32a0b3d6caf7e7c9078ad1ed380a1372686c993ea6d7c4e258b80c2180c9dcb0546c79ca5616b186aad0eec34b69ef1f43490a1cde9d3adcdf920b1738abc6b7aeed51c272184e137dff2f79096f9f72140106e8888e644d6d824194158082a250cffa2904152a01973e19c5a30ff2397abe4c41de65966b771cc2a4f998145d6c8479a56741023fa9844750f4d8e83a22d2072eeaf2597c0669964c41c1d81a87eccac61c19efb133af24fa0fc654729b90e8d310b083f0b01c676608e077759ae3db1c5bba1c730ecd7d2a0373d048ac5dcfc4babaff17f590d61d27514f09a19eb59cb90e3c94b51d56d15085840809ecb0140b56cd210ec734dc0e3e3395e81fd954d011a3d97b6d12182c312b61cd9b7a8b332bd9e468050c0d2c0b213e2e7ad352aa6698c97132d62c0e99e07297ee301c7d3f486675289979cbac80be75cd1273561fb58877d30708fcc5d372104ffa7c59fbde243bbd0c747a20a5e1900eab88245e6286273f2e1391682c24de44d5f9c73fa2a3b0d75fa50ffe3a9b00ee2a55faad5e1ae88c29e05bd4f672f343a51e1fc6b399574fbac8207f2ddcb512b4ad92111b6f727a20575253974e247c917c8f826681c501709cda4c3bd752eac87a1caf001e48713eaae20f632617274296bbe2c9bf162cd4e08e8856b73fe5cd94c4ce6bc7bb113728175ca90e582fcc2ac71864a9219ce870b967411f38b4a720e9e4e8bb56242b980f310a03d72b026f83e433bdfc6dacf07d48849b8f10598f08ed62fa78bf6b3aa71d86729ef91bebd0f84b46e534f95d9173791c2fb30f5fcd7b1db65e9b62c78f5c0072a490f2d1300d41dc897fadf23b04d8fc138db05ce76901f464e5f4f06b9468729ab6f2b859f5607a982e65d4e4e73baff57f3c19bd3711f64b80547ac9c308727c01ba0c8792a1a49156a7821106ef8976dc9a9ec1b09de71958f7973057c37285ac90385bb4471acca3a50d7ed9213ca840c3da029ad453738881f9fd7d2472b8ef7837356e2ac5a6292124a7cc55c34414e689afb2106ed25647f35c17e1698cdc3e22311ff142ed32fc471f2c9606ac0b6b0632184ac20cdde4ba09c823553964d8d57f0c5ee733036b44e8290af828445dfe0c5e21f1792c7e73e200ca30ae3c27104a296b0867ebca280cb230e36e8dbcfa23ba5165e914e08b1c365572534be25e383aaf5f4d64973cd4f613b94d0fdc30260477cb2b183cd35ac5e30073ef46a40379a6ce0c42906fb7dc8dd9c08652eaa5ae2db39582531f071a570b5a3e9f3192e3decfb70a270ae1c7c9a4f16741273a59176f3e1a6b9f7130017279902db5f9aefebbeb5a4edd828a8754b07d4e18e9e23ca91fb4aa1f4429447218b1a96a51c0c8adb5a225c958e3ecad295ae34d0dc6da9b71610bcdd3962572d1d69a72a8c6f556f33bcf9c1b77c611467cb1ff006644b5579fa957bf1e3b72e852d7ede0bbdb1bfc85d66d58904e46494c4693928ca57a1e2a163ef2a9a31c08452248191b235134f8f7a1337ce8bd4e41da0beb876db323297478bb56475762c00f7ded4ec999da506e8daf6bb56b20d9ef1de7d3b42194519afcad397e723948dfdb31852c24befac180dc6420b3959a051801767cb44a46c5a6e783937274e1c3a82034767f4b3446c519329056aa87ca9ceee47b583bf7ed7e6829187233ec1a1200164f6a6c3df7b3d07c2079489a0df5c449d45661d8c32017e24972b1da602cf9106976343e539ea18ec25b137de536e1e5bc3c42ae5dcd7a6b1e728a6b3c29b90c16b1322e3101138d56f4bc851b588e30a9178804e03f79ca5b726851688347afc5750d539cd79ca062236c09a32daf475d2ee6b2c02a53453872f1e1ea629938a335ba6d1abdfb7fe544f3c9f1d79afd669da31fa8a015f82c722a932dbd1c651d0edc1f961b0ed6d565d74cf48cb9936ac527981c9fd8af431dbc2699105502eaf351bddbac9a38c83a9685f1b4b4f89302ed09e8dce6e090023b9a46930483e40296360b7186195032bb2d89314838c2c4ca9af102310c220e64de18b495e7bb7f32438003eea240a93f89bb34f09e1f49a3e0a61b65b79a6bc5c1b82deba68ca0404429ed5b4beb375c0bab6f49a109f8bf43d5306d36e6729d0adbbfa2d199cc9309ee013d520660cc3820aecd7fe6587c1f52235d24a97272efe02d3d7f3350470a8b7b09557723c649e2c3a71cc36233b305f6cb779f2b3e1fc00978ab2a137b5a5bf37c5da4e9bae7de9beeaeba69b0ab1aa6f922e7211fdeafad7a6bb0f808bbb005e8cbc16cce7b7b726b2463a8a980b63d360d46723e8a1552a82a8d86b461e5181be22771198e478ca80f1c4411ed54bb344909724a341e75095d8b01bb777019d26eca17644f61c70b13830e2b91f8a5c4356a5fa5bbe4bb095e57da1aab5d2285b2412dff77eb28bdb6e82cca90b824c18b03687583eba531de21d6afc2a388b70fa7f460f6f8c3a71143dfec95dc2de5684e7209d3cface9776b7ace273319b968cde0a21f7aafd9d67f284ea064a0e41a1172447b27e80dfe251fe3b802af393bf9e48de60ae548752dfa60269789bb3b6b1800d039cab6fd266e914da1301abf3d851a6a04d62608ea37b4f538d5ddcc9a72d3f03a08d5fc309295b1a7ce168133209940133ae79cf21551f5d4789ee456726c024a5db33ed89f1fafb74225bc57cd6e7eaea8cc006515b4c1e4bd8e02d81a54627d2e05742401e7543e36e77b8369deaa6911a2e642d3d3731130603e4072e802db133bab5a0ab38fee0cfd601573df4e20d6e92051b5f34c7904b4bc1115753e96a0a014fd1eb87aa6a3b16a4c4c1db5310a45b9d0315670013a3580183f2cc859e8c5d2e73d0f6ec6762692048b404c8c88103c68737be4e9d9bbc17323434e8e2dbaa46d01b4715ae874ced0747476e78bed30e4c159af0db0a01e4f5c376116c3ce013e8e7cb12e7951b12cd556d7381d582e332b5831c55346ba7a72c9e46c8d3d0486315c875c1af2304200c0e48c30a4bd744fd1f2c6b575978572572687089e8678b865e45ecd58621933d4f6ff1f6ce8135f745b10de9a37ba722a60654fae916ed44e5637adb1db1e396fd6cd61b9b341ee59d6c85e57b14234092c4e001b21ff273edce64975368d2532a2593f7bbc3b541a9ce192df6e417267cad783b224f29ed907b87f3467ae84a578ff337d76240a99a29fe126eb7d72ae2a057d62360cb7b3088d756c56fe063228930f956c7c1bcb2d05162a6ff17263ac4ba563cf50dbd8c92026a51d234a96b077f1e7d02782faed197b14f0750bdf578b2d92577a44d5bb2846d7809d31e7357338943e5271f92faba093b1c02b7aba70f862cf69c1b10387b1ade0b9be90f60b1d3645cca694766968f0ac601f25360f425c71f5d970e4a3b8a76434c81ac4ac081d89ae2ab63fc033e999e37282b9a87f5873646ae34a4ad0c16954d2262b2f586eb8c7e61af8f54e9dab3272bc10ca05fbf6dd3707ff6b6e05e3c4f58b4f2eec5603a8826a12509a7553001614e7251ab0f9c00dff1f3f55cb7468c155ed466852b3ced3996cdd79dbbde67223197d98b8a2bb0db2153565ae58cbeebd719366177a323039f8e02e537cbd3950c1d2576284d9ee6b576389cfe3020f2f3ab47da9797473a537599d3368133c59d3317a17b9287a48d66bccfdab8b1c9cfcca5d435626522586ea9e979991729732878752d8dad7a4d34d0bceff1023e036944be6d99e45d6d3d61089bce77266c277d70b632e30dbf7f8be82b19260e0e05d526dd27c7becc911fc43db1c24458f57768824544028701e78e2a5ffdc327c23d2ccf382bc295c90670491d67223b8e7c1e1d09e0d7cfa67541de9d7468ec2024a0b7053b3cdc78b1e045eac72083967966f523c6655316fc99e4d63d8dcd18620cc11ce6843c9b8718fca6f2fd059d39e32e6ee4c27b2a308e925cff93de09d169f8375977a07ab78831f697246116c0e873cfc77209c95c08f645d68cd315d0899633d7cef1f3cd134be407292f75de008bbd2eace00fa20167447990f162c1fd2ad823231af18d63625f67263ec0afee282bbff2994e22687fd6512aef9746e5308f2a575451d7b5f98fd72cdde0430610f7a7cedba517d543594107ad377191e72f82a192cf7b01a64e45a54064c07a3dd84ede35bab78a170abf3f7e58df8f75d230d29b5d6725ff9ae726eead81347a40f275bb8e9edd058148e870b1f206a5a33d6f48b5cd23aeb076c7c6256e9b543f3ba73671b0d29e6d346536fd5de198ea2a69da1dac72c08dc7207372ee270d7f7a62eafe933e135c89ff671ab7212267cd19677e636af4317309950621b108847c423ad47aa47e1790e12fb95662cfa0dc0d13a8a121dfe49729d35040ff1797bfd4db550a00eb449bd87067cb5d5fc2615e531e25b01846e722f8bd60dd595a3966facb5e4b48fdc7c520cccf229461617c1fdba2230c74632f1856d4334a513cd31c66981f93d4f24799e57e45778effab10ef43643bdd053d6e4eb036444e83eb8058cd2aa946a92f16c90292a3d4581706187d9c4479a26684e50c34447844f5008febaa763f96aeb087c7a940b9be3abd74eaacacd241b26f16aadce582b9c47af7a3e06d45ede6c0c4c638bcc40ff35be6087767ed9720ac98d65eb162ce5260a5c5fa5e2a55d7c4c44de46f55aee9a5c48ca2ffd315539b6d63c1fc0948caacf973dbcfc5f6b0066cb1171b6041ead078191f3c2a272da154c8f65c7ab47b3a0ece718123cdb3cfd4ba409ad248cea192cc8794cd90dacaf3fb4205a9b5b0ffe40c2b42372012b03ba02f7dc110d91cd1c3012e5a072ee39b5051648502a2d45e03b9c5da193ca760dc2186b978ee25b5c411ea4e57229da793069d14052c356b9d9106142e2ef1f63f501588ef65bda4d6ed219da16740cb7c34b3d1897872b668f1764eda611dd89ca9907ec9b62aead172d92ca72328263034febbaf4380a36baccdb0e41f9d32c4ce5b72854cd762d55cd681b725522db3175d8d8c01e1a9a313f3c32f6f02af34903dd71aac6768d7f20e93d724502b7190858b0af89303fdb02c5ede1f07b9cb1d93c406af9fb6a8aa84d297215d752b207484716407b002b661efd040ab8dfa7611f86b5bc8f43adbd0a5f20b9e27a515456d7f41a42354315b86151ae92c1a9dc0805af141353e2c91a6672cd228d804c1f1e6561cba8c68155801d36a4b6dfe1106c7f7423bf456e677972ebb3d4931af15fec5513f3048b2afa4078e02d37b105014c82c4940fb9e4ad72589417d3f210107e3c79041fba276fc2b629941ab702b63aa1217e5aaeb73f5d7555e756cfc6a1be1b8f71cb39ea5f23317b81e215a948f4f396086575b2d672db81e45542fde03bb455867ba84db8338479b4aa34fd74f245b03afeb77a2f31c85c82b54ba63a30af37493bbe426c7e5809068108573e6f454e92ccf3f7db727b5a0c89216eddad9d53f8c8eaf97ca0997e18d2c0324a07a2307eeccc480149b3df744163563980b939f1b31ba0a2f538cc4430d85bbf060535f6f440efdc2a71cf79b8bd9aa1cb38d03ad24d27c496cb84052a2c1bbaf2eebd991be001193ed7cba60f4712c818261fa57086e42a5074ad0280903473675d920c704f61664065125a5110db0852e1e817f8d8be0746d37e9bf11eb8772f4122d0ec7b2ad104e3094647864b347f3d6e88e939928555ce7e06f2d1580e2eb55f8a039865a74cee3b12f617769d3ae254bfc9ae8168760fe6577bb83b5257d006a8f2b9e1b172af99b9969ef0e40f44b6790f0831ca74177fcc6e06399c60f0c9492cd2557662fafbc9675610f3f7a616ffb73aec3d132281bc5723ba612c6731d0815fe86e723184f1255a150cf60010a20781d798a4a92af32ebf3577b033d1cebb06367372935a6f16da134919a965ffc29bf6875076a0abcd12c5a055422e0859d6a952659dd545842dd67af0257678e2bc237f19fa1078b28567c25c8ced892f42f28537a6bfbc322bf4b3c3587f40e0131cc574e5558f9bdeef7adb147c514202d4e872e18dad08ad5b156537aff70ef2b0e2e3583040b8719e0ace2cd39d6befd3c472464db29150143ae52d3564dfa91f44650d37ed36e88dca4080369abf44b0de0649f261dd32a8d6c2d3b4d8ae9888e630cd2bf994fafdb848cd61f959346a783c5d08dcdabb2648cb8f0f75e19e7d262f9f13897012505b3fc6213c0f3431f668fac5ca1773f3977cc518ec6fc59501e9b7e4c90ff07c24d5f63734a34e68257264ab4ac172346d6f9d335f7ace118053134b3d7179bd71a0fef5e040523f787261f649dd5b850fb1df13be5938e63a885bd38fff11ccf09bbe44d29d38f8e300b515e2d920a7e33fa3d91016de1bea125c681dca2035d31ba5c7a44f0093d672c678a1b2be30adad0ee888a9aa43769c2966f8bb30c6d516bbda80b2f2507238a5921fb0adc118c6855f8fa227e23241a850d7a689ffbda5d2d108859b5bbe727268f125c7e3283f9a683c069667053c245de34a0e34333be2d6d34fb0b85361f305e5c20bc7d651b278831c43f8a4442c9d798c5c5886dd7c053eb1ebe70f2d2ddff20ed874e4e1e4fac204a14c05bd017ce7ff47938dce706a1f7209dfae72d7a797a45f1075c85bbe94a2d471befd03fd6eb9c150db16fb8ee1d8aa51777295456286fd23f122b4c696a6da01be14b5242c98628dfecddfcd5e022547fb7289f001aee85ab17312707a427f79c47818daa2f48964c95c482d494ceb102d3efded264bd9e6a54bec884cf1bff125fa5e321a5d440c7d3628ee55ef333e4842d47415c34a29df9d3b310fc42028794a90b3dcff9da3bed63860050cc1cc8472abd214c08acc9c6388cc460fef0b161011082065bf30cfa1b3985a5233cee7614177fe0731d7158b7d7f37d368c378945f3c44795fe138467d6e2523cb638d72a517b01c7b83b76480207d063ea5cc64c5a43a5f5088bdde15087a998a1909722c30382fd093914ae2f9617e38c59d169673d8c6f988b880e62a5153083b7300bb5d82d10d311529fa29c05baa360ae57be8f8f3273cd6e8eb838d417f87c572f6f68a6c4add5a3798d47204bba7be3f760d8173440e86634d604b78cb0d4b72c91ad3150530eed04fb33ca8377f43c0e3111ca5db1936c9c70b2692a044501553b7c69accadbf651bccb4d1ad5fafb739a224a8d46cbc533521c3164a38c772b3a52938aa03bf0e92a75cbf685f01a55bfd62dd11350f0d94ef1a102af6b23d6963e7663f15f38237bab2bd682d64fe64011ccdc1dbf6c5e2c00948d5060e72ed2975863ecced5d6d93624a31984e402880ad12f815666778779daffdd84972782d9ee5d37a9dd65c8317f7d8e5fce0f11a8e1ca6341ea578a5e22a4af13d21feea4f51f2c817738bf1b759612ac50a8f165821fc4289f0b86461d3f908f20ee935e0ff3c379ad26a4ddf9066f2e7aaae1536be01a9d6dd43ae2fc143967372ccc4c137769e01855ec279c368419d3c7946f3d4894b631e83baa70dceef5e72b60b458cb7646e1c010666ce2264bc1e747b8a208b23c32eb9000ec15bf8697275b42f13331d8edae64c58c346a0156a7ba7581b19d5efd6c38a3b8e476f3372cfff8971840cd45af13c672dee781c6de6fe1b0dc1acbe2f593968f98fb3a8720a8e0543a70df7c385a1d5c379ed397a8d3399c2827f78c3d7bafef886558872bb4c7c115a60b43dd82a00458b02153464f78ed2e0075dea4b7c2481ffe626726b99fd5eadcafbc72ddb778d38d036eeb8bb73b3054a33e99f629c29bdf97d720079b818d0eef0dbe9096e232f5b8fa2c31e32e46ea97645eef00e84bc5fe453dd27174f541422b8812c84634c2036f763c549d2a9d995af80d1ed89e3a9cb382f434c7902860fa76e05dad271f6b37706a4203e605f143a7fbb1270c80c3872aec2edf4a9c8cd6a135d988dda57b4ea25b7812b8782eaed01702724eb046503a6896627ea1189fb0c7b5b387007c71f015d9856dcb787ab106324cca119b916310dbeb83606d714cad0a4a8e9117732e0502da0048cc07e822fc2e2e8c11e7234791a673f953e8373563c59935daa5b037161e0cf77b8f1afedbefa12091a725322e9d80c843685de37b0a7a34fe8edf90aca931c54723b66f1fc83a849a272f3d98f7852a1f02ed92c1aef9c1a5a844917b603ea263fdb63ec0e8e7dfa347293356c6626a2c03f98580fe3a6d9443a0a0540a252f24fbda7cea44210a352722d89ba84a47377e656e426ba512093321db795aeb255740033b08e5f9e227f3fa9710ec9140a0b9664e601501722f776be74b27a9b5978a2badec856a794dd5220fb3b2af02b33fe20a26b32683b7a9f5c091c5cf43220c6aed93e5f68f1471b50198755cee91865359d85245c9609247fe07b66c45bfac2ad2c157511e7ff06b19f8b8ca960a470ba60188bd84906ff8a4d7996a0b3e27fa2d177b7cfea0c72b1c115315e7e282f255c6f9afdb10330bef182a1ca5777d835ca5fcf62cd9b1c06d096a342097105a972c637f97fb3ea6025944ba7e69f40db814b848a43bc2377f4a1d95135c8aa04b64cf5fb5400f04676b6ebde53c0aef3247806868a6f72d9f5a1e9c29e24e25d8a592ddfa0d98f9f3f8bfeef93893e0f2ffa45db88801c8d8148b52a5aa9558ef825c187d3ecf6146c281fd7e14a87bdb0ed1702a359044757e0d9cf96193a32fea74243715a34fb948cda7ec0173c85f44ace235d1566589b631dd80c1dcea17c290e3d578e9eb0fc13aeb9a6b83afcc1f27027d1b972abce87ecc5cd8142d27fb5090d1f885431516ed99ad90d6d12f6cfc23bad3072c3e395e54fdf148a1fa651a4fc521a7d06fab475f2f53e060ed74a33b803855ac2db0ba14f4f829e9b5372ef49410232b40c4e51ad75b5d95fee927ef0505e72927cbd433c8465ca3bcd0905e958f1350add504daf2e92af647be559293369722916c20c8bb4fb51e390f692eda189303a460c752a0654aaf271081a9155ae6d386731e037cb677331ed7b5893379fa0da2d8d8f433e7a6e12010f2ebba84472c9424d7df6cced460fc672ff926f7b501c807fa94fc10b0556d57e6987c5e572f8dc3c817ca3cb1c72cf458df18ab1994c69fa8d780a776a9d4fb7efba22520112b02b8933de1a87b8ddb1e167d4b19ec77cc827841ef297f6cf5d5b10942a4d487e8d01f8557853b30b7be97e9313a7e3134261309a3382ec0a871f8386bf7240f78dc82ff1a938d8c0dfa27e1f772914a40caad082aec2dda818e7f58cd672f335da207aba950056828d9aef6162766a35cff8a1964b16ffe301d79b15f272ea5f8b62d1d0fa57f564c56abd8ad401402785f9afe6370b0d5ef5b40c9a62076a80ab1fd528165066352e44cebdaf7f654fb8491497ea469aac07b73f985a7272cc4a39f5d44c85795b3ad92db0862a0e56e78d8b589476db66673aa18c62516d3117e1b3ed45fa365bc9e97acb0a8d94ee156d54f5d75604b258db6ee2fa724c7d25c48559d5900d5f447568dbffd6faaca0e773a2f701853a85199c77ed00eb35ef3b52c2c1ecaee474dd1a7cbf0081db05b1e6e4b2216e2d6d86d0f490237575b64280cee8c3e4cbbb8942d7383ae756147cb011a09bec74c1a7f1f38f729ea5c29e6e6d0edf3624bc54212d0ac3f04196128d4c633474be893b2f28c67208adf22ef938c7d6ec9d181dcb61ab22efc88af795d26e6cd00301a1b9b788317f9ad11647629979be5f3414d7c2dfae4deeb006beaa284a751951e044c39c7250ad6bfa22520cb92cfb081ae2d6ef37ecb2b6a378a38d790145fd5615eb453e1e7ca6a9c46c77c497145d5729dd8e811a3f6de421aa24bb26ed85df1d7afd2d3a42c10c4e8b0e83a50e72a8aee773fb4df9ccace88535d8da44bc698c1ec9720ea0b9ea29ecdeea862c04475fc48255ec55f512056dc0a1ec471bebf532b3722d38e3f5bba4a5e4e28fb7429d6b819d353ecb16da57ba615f575e8c5b77f92a47ba17c8203ee9117554538c5a8274e10c4f4bf149dd3b61c14379fd9a0be872dd43ae542f922f1dc66007fb5d861e88b3d94183fa6335056428de06bed41b0c53727eea1e89c9bec0ede44ff261ee1f82315993e35913e90e9fbb0d2259a1303c4e1828e3cc60571ce841609d508bb0414f066536c9c5d059e3a0a18c41f3727cb55e2dd1ada8689ae0a9f67569aa783212b943ff41072d52b637e37bdbbe3e2cae6e70fd82944ed15587913541f0bc3a62d55bc4f87f45dfa721c9882ed5728cf8f26c63d92fb9841f32365276a14ae026b0d51c94643852635884bf02fe728e4fbc6b74d692c9db845e81b3841901fa3cc3f4566b29e84dbab56c9f1b7c201d78f956ff826adc522f716959854904f5ec413af98fec17c02da8aae506be0c50919b6ac1368f92bc8bb019a66dd7d7041d84b81872aadb65f4d5fabffd7d635898febb5ebcf09066de73a65864361373eaf7bfb1c10c37322d3f337939dd72142f668dafd377e898bdd728a41bd28b781d0b5a4a8b6a39626f7dff922c27723ed891079c51f98ff12e3bc50f5d765ba8a362560b996d074c2c250177c25c724b5e173c89d4f6103cf60286f6d6713459e3e7b8906ae25e660273791b9937729aa6ba585cfe64e12bd310ab7e2930929633bd5c81f5a1327eba4ebd4807b072177665a9a96780299274d09e65336dc3a66f3f0c6005c4fa8013f4c4e8031b1a87d9cad4599d4da8cd6f3c58f4bda976f68ad77feac2cd0d3be6db0044452c72f2f7126d159001d18331172d5b49443d7c11ef5071c600ccc91db21abe33c024d7e382f9373a90106e13d9dd46fa4d8d5d56a4dccf6bc0076f931cf0feb4c37241084036b75e64ccda8cf29f4c82e5d52b3ae969fdd01871e4a5bde5e4ca1e7209afab030c5e1fac24b362f0c595149425a7027960b5f06623628aa7f3953972ef5cbfe8ee034a161a9a3a7cdcde6b1008630bda04079cf55439b4385b11d772579dea76e95f1fc0d56b515f70d40721595d0fdb55f4b2df0f23ff2c27a1ec72ae90d750fc7fdae8870fd1c6bd91f0c35b69bd033c45465dca5bc7b944e49872569515316b6b3c66a980ab18805f8d6b36411aa4dafe34131ecaf1fb0780687203b370dc3a8aadd0241927be075fd3804fe62bba9b578ebd07b770d7b0f31c72d79b3dfcc7ab66d069117b02a6eb9f62170086d279cf977e0ae17f346a92e372ab9a0e55d88486d63e2c1487dfb4268014127645fd709d85dd82dc4fc76f15027ca9b305f73fce7e4912ca4a0b41cd443679da1f1eff8083940ae9111afa3672bce8146d3eadad56677c84c4d25808a2f21ffb9148c895ccac663779a978571a905aa9f19302bd1067b0ab73b3a27f458e825fe01b9beadf6fa7aa366d089d05acdd78f024c5332724df8c9c0593eca429411c8e7668435aeaef789ef3a38b72d0c819e3269f016b5533861b50b058d7ca20f4ab5fb0a9fde299e871c998e73b0c79996588282f49a7131092d348758d75984345c0923fc4ed2faece1b8263496adf2cac1d3b946b885eb2c6b5681da4da6cbc4324d21b50d413a1a9703cd0725245ce3148d868ed41dc0c55944d29915eb8b607bea49b7af19938df60baa70fd0646a21ab299f52d68f3f5015aa7f3967c7e04f9a70682331b014a57995541a7517cea0988f8aa9fe7186dd1664d79717345c82e356dc23f2c24ea61a91207222406540bc873644d654472e111fafe32e698802a954e1903f9701ee31f94d4c063e0bde0e69365b361250c08e6ee9a8bc5fb46fd4b1c0c58fa344d4f9452b2ac48dfe05a875868de9384c264a60143ffe798c67380a011ef8d6bea4bab81d72739e0d837d0df34e0c51ae3acd5b7b3ddadfb3e17856f6937b7d4fd749b2dc72f21048020b659befd96f3210004b1b2efb0aa7faf7a663845edd12eb8cb92f56b53b8243da24de5ed311bb4f9660726c7fbc8b322d8cff36f5b05d9ffe3bd172c514221f7f852b137e6a41e8d1bebbd896389c650aafa5af4a5393711235b57298d448de63948f89cbb3d41cbb8d301d7bb714ac119cfafef05febe33a8af844c2c0844fecdf34fb8a91e94fe7cb23f034eb380e5497ad7bc7edea06d2d097114524c18f7341c3c0eaa93a36286af794f6be938946724ff5ecb7b9f19b7a3072c6c476663f7e0f9fa44b9e2b351b28105983ce3fc15da8c93a2efd0122562772b0e5e1dd5575a82688d8251598e080c89159debc9fd63acd506725d7a72b5b728beec6ca4ae3b53e82d54cbdacaf9d413b023960d26aeb4f76cd19223c1944723e0bee5aa91e8be6d3ff78982fcbed72132d232f695bfab4b49b232a91d2f572f0d3f616bc27abcfd9accc69f22d591e7f41c466e2eb9e2f17cf28b0ec35593d79e93936dc0b3a264616e3ec07befd64cdcb358464ee7da2c42c84c5abef8e72a0dec810faaf884789da468a814487b3584eccc650cf838707b11b917f4f3e72813a715812e148917115f3b13b644586d4f68c8c9d2ebe2f9fd3d0989c2d4d51cb7393bb111f11e398393c8f37bfcf9360d829437761873fbde0d5d5fcc7f46284368edcf302c5d2f9bc0e9d8ac8266d4549dc5996cb66c1e86c11fb6f4bfc6b3002ede1e994180ceff5334f40c30a5f33fff22991afb35bc637908b4d2ae752c2113b11df4a4f9cbaf669cdcd04716f2d878c3a28dc8f33a1d41d5770966072bfe32b14fa33a1b2447c3441c1626bb43f20d0d95bcc2c0b2888a0ebcc4e45369ae4eedf9a52fe7c576f08f3044f8292ca429658ec63bebbeb965798a4f6d0359439b8fe3f3491e54434e5ca9f672d9a72d321e3ebd0e3597ac33101c8cf85445d5712ff4b5abac55a56c8527e62162ef70e5d6280d9f53af024f0759e185601a24e159d2285405a11a3ed9944adfcf08a205e8be1deca25578fafdd24a44252f3f23270eecbf9163bab557dbcad1fedae0a49e20c4fe975498e109f44b28271ddf6daf40fcf9ccfeb9f2e73b5cab46fdc8189eb4f275383b2130dc3173c4c273dfef1ecf66aef0812a80ef7b9c0bdc151ac04a989cdbcec145ce9a0f9f66714a363cadb14d2ba94e5a8bf9114f7547814efdf603dbe86dea48f47538ea39e7296e5a3cb498bdef21f44e562ee4241e1c3c8ad4a1cce891304b0b22dc9659c72f9dbc8b52af20968868e06b590a78841d5692de5471f3e26da888ec510fd0c72e0a077c1c273ee2c4a8d6c87c16ebda911b3e1d52c4efc4da7198c3c5edf452f1893c03cb2ec2e66e2483155eda177fd54ce8268c3a13421d9858eccef5b3900c79069f4a0421aa5b1fd0d086090dc12f7d6af4c91adc6bb6ed2f72c25bd847219d41bda065b3ef4bbc1258bd654a333073cb2a908e5a7f3be902b7f69b3f6727cde770d50a8e195cfef4623e7d1d880d1cc1f3471ab0908da50d3fbe1e1e560dd426ce53b4d9ddebae3da99fb46dee19520000e05991429874e7b237c79c7727e0f8973ded42e4b096cdfdb2a1714b9da2892254af3b3ba51d67323e982d110422d2a81ab293582a23b9ad57525da0f74391a108f4625da1c124976076ff47206fc24d36016a583b5040ad392b90146615159ed3b6ce06e8e09dfbff4df8472301bdd7022ccddf1508f9a41684912c9b2337fbd3c45f658af5e22a657e98b7299a483a191afd3f359c1b441a8a66c797f332a767cdd1a683fa5ede9997d82392416c13a97b4dc2fc42a602775aa0f934d7961425e765a1a6f49ee39f8a8df4e46518adfbb91b7b17436114cf172036d707fb82d21fefb12c224879caca5772caf051c6ce27d88785f0248eb5c9ccc33bb87d2e9d1e6ba9748696b72fa9b8a3473b0277ca9c393b2e30e6609c2ecb4fb36fcee8d31873ca7f4a08aff341e6a729bc901a65c99327fa3bf5c178989ad5e90194b68f897c8f484ab922a93b1a8722fa1ef372ba66e53aabf3ba57769da09351bf41e461c10c9efbab9fe2b27b472b96217784e25f6cef40cf5444813b76624299aa659263d1339c2080cc3e6485e9163e8b9b0bd1d9932729e7c3822fc7dc7cab360f963463b5fee9dfa1e086b5b074d420fb9bacc6a40ec2f40724130666dbad29b5a508d276506730a112785675d7a904f77223076593019be1fc823100ab2d3f1fe4cb94413914933b6532d256afb058e99062e4dd3083f5556580dd58cc19656ffd00a5020c00cd12ad2e7721325a77341bb78b4677d3e03bd74d5e71e3c7dbcf44da70695304ce2baff5a35aef31abe0687d28cb2209d1c473ab51b96d6e54e0d24379836144e6bbc9cef72d60dd5cdf8400ea0a90ba9521645b276157637965ff68b4e6bd4ca3673d81f72a9e5e3e4c56bc494052789b5541eb11276fc65239785af9a8ea9b41177633472d8456c46e93733fef9b10f443c6dac25d9ffcf470d014b5794c38e0474b80772ca743cf96ba45e87e7c9af75ba42056bc046cc9d8d9130aa54acb2dd03ee4431f3ee54a911294b9b0a037c6bbc865fc85c2d910b706acbf5c76650792dd5647257bad8c018e9dfee2ba2a53132aaf797cb2e1b3d9799c9bfcab3aa26ccc6300658c1a6dfa8e00825cde7b28f944b4d1ab48c269360dc8da8971c634e1c04e4728c0f1cca83851a86a5155354df1cff2c388c610c127057b9b92155cf2655aa379f862095054b273650fe959b4fc96c0d654f4457dc979577a1014d1a41181309c81e0b9c85988e9322b0fa353b849362254749fbfd2daa57784ef746ae9aba72406aacf1fe03f91ccd61ca5ffd00303d2c375766701baaf3136f396f94fe783f22863f3c83568d1ef09042f37d9a055eceb715b176230c461f5a664617da3a729e77407f47a62d0029b2936c17e73f137e6a002433c216731e58d27d4ca900726b772abc12b127fbd8f2327f4836bb348467ae2f4b9f4980937908485dbb924086926b59c9152b6ffd4b2b2bf79a2ec511e2ee040e7bfbed0da3595017481c728c2a2743dc30a8a75f3319da7a2a6b724078e6bfdf612f7ac8c36c6dade0a172f910606f64e4895e43bc2d10b8ebf3199648f68163c475ea109b31cdc216e172747bd2be7842e981aa8cd403ab5e0f5f7d24a4a9e803e7dcc048f41ff34df66088428e49da3f3818fc2690f0e382ed37e53b6c06803c419ea3525cb173ec43476a9b9836c4903c07bc5bd3f74c3662b3829d6551cc656a0597c06fd197c5ba6ca25bec1e5942b3ea4a9dd1137a7c9107463072f2138438c38f30ab4f9f2dc320bc3bc7cdcb20cd01e1d46a1bf91fa14dbf7d76a07ce36e63cf927ab8f123d933aa4e187b042cc142433ba1661efd6e9e63404a91851be4705d345753afdbe8724881733174f875e432d8fa292d033b14981d20dece67cb395c6c1996748e8e3a75c06aaa615969a35b1f3ff02af1befc436cdfbe697414b1c776f7fb999380725240e434723b1e0f76350b99f0dde820423c851bba71c98f2da2f670c58b36171a7b37882df9b74aa97e064e193570d34508fc8b650e70fda87594dfc23085723d81cdb600ee5f2bc2ca95cffa0eed2183444424f99f66e417f97ddb6f5d4966da2b2d2bde8def78e1ff033a396032aea2d8613ce3647a7574731a49a7781c721767cc13142504175b854c129ece3a13c75a56fb734f247225a0f929fad90a59f27f21d2d8a28c1d6dbaeeafc1547853f508319e57ac116118ef3a4db678177240a1caa36536a0e822a4fc387610ecf461044f40a88090cd6ae1eef4383e007240bf64adf021fad442786d8ce0385cbc9eb8e3f31fac1fa30dfedea596741b609d95e7c5e46636c3634823afb4c71002ac342c8a4f6c4da4bd4562d39651fa4a93753f470db7f82aa5deefce98c3d6d2a079739a9d1944992bd72afbe5578f23a445c8640be3ab1f5a6f7a0c68cf3e1b822db8876a8a9424c91dc6e3f80b133c3a41c268ce845ff0e859ee345a788f80ee1edf1faaada7425833d4791196f93b374bbca35fd4aa7ae684d403dcff178f3da81437ab3ec772b33428fc226005727877d8bd2e8b1bf4eab8e2413ffc537b62ec04deca324531901445ef0787376f4b61b48a5eedd8269606af8134aacd1a635c07bad61e643c5366396d95018e3cc090a70c127ac73d993c7984f87552f75a558da539b643d5d187ca4a36cf5f72957da517766e1d3b9128b2d4aa48fcd5eee3c66f37c6caea6baed3cbdf230772883d86136f4c689dfeaddc1dfd0c4abd15a917ff8c957c9f6c624cf515d05369e18b38299bd3bd68245fb8484cc01fb655c4ff583579dfca3e84a5bb22e8d172d2290615fd8119a2179f2417f83915fa9211782abdd60e7a2c401e68bd134072feca687ea233960aa19c4c85ef67816303d34c75ff5af6a987c4614d7d43f30d6c94fed1b99fa2486bee3f3262861f0602d05d7e502d8d47ae81b2cdac372872276bee255ae5f5672fe8133dff2386d1478d5d27d70243b2fadc4370cef4544978676bc2e5b3076f1fb621ce0c867b7d5e99f7a1d275ee4a55f27b4885799172214b4e05753d4085fbcf383da03c9bdb9c6c956b04d180261622159621c08519da4f5536e46236629b6c64fe2474af68da4182ae278a5723f0e0b16a12b42872472613b227450e78a847f6468b2543c726398e70b820cbac58331619ac505d3c628dcc472f50b6931dfd700a6529cd6ceb4aa97bc54577b9098cbce6f4dfc172cba98eeaf22ade007732d5f1381986ee16ee8c559211f340ed46814723528772d5a2a90ce6d7698ffb12298da2abd2419be024e899a0b71b3b89379c53e7347222adcbc2fb1cdb35783766d61e1f0eb43db836ac8c39b1a815e573d443e853126606f961a3a836b2e702a7090ece9b5338bf93e3d35d6d2ac6722d078d01513d573aca124673fd0a78ee5feacdb1c695448517d45088d90d52d68d9b41c7085a2a46dd8e8d6d7029a3843f5a5f1031259e550eb3497bf4eaef0c58cef23be47226964b36e08d720e0d8d018acb83574ad41938b7ffa9e4138812a021a570ad7243cb210c7ff21d10af2cad8267a74847700dcf5a3a56c39bcf151d52ecc04c727fb43b0baacdd0277f978608a768e418dff368609b4a4a25400b3db19765fb1757be1d780a8c1dc9475b5a5c0b4304d9950cbb083edcaec8b87734d6aa06b12d969b23f934c2922814db20fc7de0d1e5bf7cb931bdfb5bd996f159c5da6b2d72e9fe5fd024dfe9f0dd98afdfcc2d0d6e1b90b818d99a6d1ac4510db41c4713721edb96063ee1d606a9134830eb06a08f913f9215504351e9f11ebdb15dac2872c099083025e43dec7458cf941beb60e37a3c4ad0d56c7a3f38896d7f2d3563724d4bf7d0a79123938af071f4693edb94c1344f0fe3bf8340d0fd342421998b724b7f5a04f237d57204f77e4b2749a05a7b41b2e1bcf1e07eac5706f1d8082172275bb046934d46bdfd497e8e2d8e86dbdf066f063366d7909a3e11cd0cb56272c1d6907ce6df938f316636be71456e2a3fa94c141256be06567255779b7a13722769a1b7ae9be9030524c01b4dadec6180d24608afd83faff3896f257420ce72af8fe0ec3fc0e97e215c94227ef46390dd09109256ea211da4eab9a3e444c61a6f5f5b1a78ec7f0d40748a8125974021c090abf6b9cfb24529d9df8b96afac6bc71532e719c8eca5b8772f405823488a99dea61ef71b36363944c9d39d17ad726817eeadb15b95f90fc7d937ec0d04930793177e6884490b49fe2aeabc3752723891c38d888240ff4ecc02875bf14ecd4009fe1664027755bc83fb57632eed5b4630259348049b2d664175d9dcaf4517156a9a9884eb350ba1727ff60fcd8372b1dce6cd3371f8f029c0999ebdffc8d0446473a68cd83044dabc0e2dd4d9e17292db74b84d552fb3d25031b6074b5ab6bba4c944ab2c9d8d5f31f45165cef772920f32d6ade7a58752ab894eedad62bac8bb8e921ab20d63f18571774987c5123d9a16c8cd353d0cc62e40db79f7a323d157ce9922c4d6319f319d0756a0b107f2474dc25c543b128c05c3648b9c5e6d01c0b0d67c487beb63df563889ced536fcdd5ae966bfb0dfe9e08cf4529d199925810c4c3f0a369f5bde9eb25ab32d70188d19c6bbd818dfe2b81e1fe92edc3e5dd53993c996ce2f421e2f8fa7ead57205793758d63d858c1ae5bcde4c786d77e5a83fafc71d381472370298f86fe572164f7e2ef8f2d37e536a26873f15053a1c4a5306181790e45d81f71e9165946374b59e0829717e841aa65b5bd6cc1cd4b74c87d28c47a99c56b94cbea84d2321044db498bfe39dd1a7f47c599414848e3a2b5cde141f90b9497f14a2bb10c624656d13c80e4a3c9cc00c3d1088bef1e8d6c01a5981c149bf68ffc3f9b7b68d24504be64b11266ec10fb4c3708583f2704ab73ba5195a500d9d63004e460aa7727c365a076ad4ec4193d26fc9573d4fed3da3085cb8b75a2e060e5188056fc572ec508bd8f49abd6ea77fcd624e85c885ad9cd3a7ea1add824064ce98ac1d2d54e701f85b76d982345c8a703d12b5ed5279cfb37dae045530c06e78deac247f72daab077809cbc1d3e2fe4cb034336833be656c5ff0dc2e1842c825678087b1724b52f3e5f6c6f3aca4232d0f681960c0b30363f2608dd48742539e7e04714d4ac3b65e8f507a18d686d6a74c47e4710f5ea945ce4c677680f9d0d347e44826721ad59c8ec3e782f7e3827419271cbeac2870ed74e209126c334bf87dbd9f8309dbbbbc8cf27e7ac66d10129da4f1756f88b2ab9bd7112bf4ec77c5a1abd9a47227dfad9cea963602ed3abf7bc6c54b57c8e2471c10dad016406fc628ae901e725c097ee56513a93f854c669dbfe94f2947ee7c45220f2246bfbd64ddcb90947276d507c0b3bcbbabf23e7b8a62f494179d9b95b60eef7b6ba05bc0e527642446a89189490a6b85c7a7a39d4ea59bb9ba29a51517f79192f6ecfa6626c95af901ab4a5d55ac5c08411ae1eca9f3eb31855e93bfd69fa30d58b62ab0883b3bb172a8e1c5b2acb0097609cfbf72bf5b328e15db6a57a8b46477e4407adf3d229346a28b93cb30db402e968819f6b71b9421ddc9548b2ac8c6e705afc282a35344721ff9dcd22b5e5eb02fb3a5e83d796407a23013cea7e55f001227918367447c7262bf709b14872a9447ce4c3dfea2fdcd0166e215b98c29ffb1808822f3780772f69196f506481fd88145b344d5c704c53a3a0e3d657541d65e93ec50a2db7572b60ecd4ad3928d03980374563cc03232c5beae8e349f6ca477edf1367af12b7247daaad94769d27f7fac48cf59a2ac5669e5f2d1f43ff0d1de396faa06ca7d72308eefa2d2af92ee26faca838365f2b0bacda28a231394fc86a563e92fbbe3520b3de3b431dd14ed72daca6ecaae2c1c881e43b6acddfd4ad62ba55895cc177282820c47a26ab31d411852aaa150c26387cc4b837628d2a49a60b82ffe7290727560200f6fa57c9c0922fb38a1364dac2d2f15187c164aded0ceb3fba009db72390a61b4ac65398d06edfd1b924ac7369ecc3eb86ec3c07d55e3392afa2b6d72c1b073f14e12a2c169d7268d0558d8147b348a6228073356378c74dab68f5c728f73c0dd6441dc28938c3d027dbc7f2c79744adb3a887e895219d4ddaee3d32a6e19869967ca7f02f55432b1700e72af3366f5bf564d9aeeecd7330b8bdd3b72cd9fb2be80a356d1d2da667a79cbb1e11daa6f7b982ad9e2b01c0f2cde2bec72a2f1d502a4a458392649e0d6cb65c1e8abb3ed78c0d9267206de147aa8f21f4923a42152e3de4e0d91cec2f9df45c70065e1476811ebb5ba93108079aa79f3325b0c1501d98706e33fa9cb6eca00ffd3bc34123601e5487bc930f371fbf84a7248c6ca0bb6359916a44efcf4b6ed8e78eb255a4a2eedbd2e65acd2200ee60672abc6e0943e3de48edac203f69343c8186f0149279908b2568d3db518f768ae4284e7777c9692e059be4f98345baebb166f0433e388229f4f362aec7d9c78ab723dedd991149b3af4208de74d2287faf78a046da0f50cb8a1528b5aacb5970249a57b0e33852e5bd37ab771bebc42719c9e388e0fd4001a54dc42d0979b0c45723728dc6fb0685f92e86aae97822d269f389794ae44c0b321ed6231a135611d134a84716092d0cee767d63936ed32785ea72b78ad97b2bdaeaa10f07f3fb72e0dd59bd151f5515885f5f2f37f28b7b3448011bbd16b32ee08ba4713d73a6e75349bd908c13c6e2c02c495a9fe4f90e517c881dffb85c21309a64df9956d15582274e9ac7638547d7d4b4aecfa5809ee455016b06d7469d0d32d46efd9256c1972da5e8e3274665184e17a58e64d27b9d8fc3e2ae34c05cae6e0155d65db5b505d07bc14cc606a3bf262587cd6f23003b2f8fb331c9e4b51f1b8ad30af82c751724e173e46586071db8e639ce835e2d731c5fed10e899c36fa443b0e8801c52f35a013006dfcac658c4028490baff333f2af69428094229aa3d55bf2d8cf74ba72841f06c200a6865a2c72e3acead109aab94f3a184a01d5f2aa7bd65e7e948b34b48de4daf5cbcbdb3bc5d896b3e3b127b8d8e436863ffa592d16a11f968c09721139c54fa82604f620c60c5768351098a33296ba4be7374c663c96ea0cff96043249351b2de0b5383220e90b45e8779c32ded5397da76b717adc96e6ca994772f03f98c90f7906a075d7287f7197536c29250cc271a5b8f9a46c7d3a6d2ee208162d8b91f20b6f665e95a208066ee29dcaa6a320ab5f932162db75e58b43bf725582e03add89dfac0ebfb58b4ad3ab857f53e60c7a31df61cb329b8bfc70602077be7cc88b098b97e6890f64dfcaa2368310e1566dda9083f592f1e1b99e690c4f761850bd8f1e92dad4323d0af301969262663c558118e776fc2feb8cdcbf72c0ba262a026b5b8da6247db3589841a20d059ec7829a7af31ddf81d79171d1728c478c88ad73be8193c1e35e3bf2e5e0d650c62997b39410f1cd88884de299721eae570ba577543fc59c4a14fe1a40d2aa9f3ba9325e7106c47d3ca0097a0c63a1dadf67ee004574c246d21f674592d8932fda20c50d3892c3bc57a844ce28099fcc686717954eac8dee7fd922ed0d3cd848853b8b2962a13877e9e93e0f7751891ccc0000f30d1a7c28d42f428cc63cf814ece74049556132fa19a65aff9272b9718db18b4572d2e5a48f8180eedd953f806518c221bd886f6b41188168c472e0f2cf0ef9599a399f9f00258ab622fe911498ff0d0f97754f015e3df3e7bd40500ed7242880d2da8f51f815ba3204586258fdd4a8045b799a49b51b008f016b05df6b48bf6f34c5613d8cbff9313941d55f9eaf496c32707e8fb134d4bbb572da30336b9eab86ab0e38f960d84615606bca0a340809130cc8eda9d50d3b60720146da29e0c1d55791ef5fb9fcfa60515d6566e17a13a76cab0ef43d80c107726928488234701a7dcae0fd4be5a6f01464af2bad8c1da4a6067493246c9de572589b4fb468bd3d46828230070120bb06f5737192faf698bd0c81ecc269be8a13efd88ad8bb4173b98375d12e38f56c94ebe6f611c65d6b0522c8a4ac18d2685cbd6703c39f4768da045fa9fc92e5503191fef8b2dfecf6696fac7185b14fe9417ebe5c89bf3252eb1e2893a9518859b4415d59d5e9b5c1cfd4273c05d0cf2772abe60d090586cfdd294b91062c5f6cff544bec8f465e3c74accef37af7dc731326f276f0d659d0c67cb1b1ed1335a34572e2f8c453f9d9c6f727d4737cf7a872bbd492b734b9db9270b0b4718c62d4b6c8e1ad30956b0b8a1fbcd98526e68a0f1a823d8296c5c3c7796f5ab142bd0812192ddec726df7e3a243dace1d36b3872d4891e45b11003936ee52de7082345d74968efaf863f968b57bf6e247a8b3372ccc678b7545d6e96af94e674cc1ef489d574e89aa92ca0be38e6f6a89011ca1e9822669d752d602912463ffd130a01cc1284ffc1d636224080a1b1af02f3ce53c9a6a9d9a8819421dffb9ee03b83e6744c3485108c914768331ec6b8ce3107724711b94909797959224c8fe5d23be2916d2533101145cb60b85c079756639e72c35e91ecefea7248dfb4f0aa77100e740c59a10c8769fffd62af9898353680723e6b07e335d0ce7aaf3300c1d8c0d52f867ce16bf05580fc9bd86feaff6745724232de7adc53bb6104f27bf1aeb8ea069fdf8efc3fff8e4c24bf401235724a246cb83d366e0c13ee0409dd485d607285b3badda1ab0a037dd01b6c262da30472df462b0da65fd2f5a6aa491a2a171b516332acd8fafa46d9a25275186dc60c2b4a684a91320649255b6f0241a91b63f4841eca557f2656b95c112fb563e87b728058fed889dbeb99612570cbeb012d4ba59d0d0c1dbc61876d9c2094b97138721d5eb456aa24241b37580c8c09a29d7317cc9ad360ee55dab4ab0d293cf4cd72d7c6dad179ab635fa46aff395cbfe06ebc8817b10ac7fa12bc28a5f0b5778c729de18ac4e374e6076dffb42286d5caed9b5fa2751d02644e7c45b55615c1e972c1042637bc44c9ca8a99ecb1e79dddafaaf87bd7c8997e636288fbf7d70e25729464b69cd01c6bfc69bf07689988b7b397a8d3f9eed2c9c4d4a65d7c3def693845f02951422a6d9477b00ca4b76ac3e76101a3099cc2f0b869d842f157088572838b7cecd3d61890302a170972df321623a01e0831502e62aac842eb369b2c66bc96d07d7162ab24382aa846b6f35925b7ee36451d233938adeea2005deaa66733649174271d3851ffe28981ce234f0c634af6342aaf1c075a46dac15fe16d686abc7aa547125e3961992838ab3508c873f022157765e4ae104f9643c3a05a721b501f6b189473d50351869178f91babb0d8e258005c090360b43996d186927261f7a0db568a6d3cd44a20251ed4b69eed416e6337ef52ed7c314659a15ef07261667577e6338f64e20f540041fb9d0afc6bbc2d480ad15a1b039010a96cad7249722253bbdff7fe449007e837fa2d4ce8d58b4eb36fa75236d11d3ef23b5e67afa8d67accf51a77b7c72d17c00d4ffa617c79ade7a50ad883acd19d4ff97f301e6e51e8836a57fbc74ad60994b32be7741835a0579d9fe2b4c9852f0928367202d4247b18066a19d5f026a01e4bfbdb05e69cdbfa855ee92d3ee274f60733180f0d6716739a5a6da8a25a0dc31ed49b9e2c3d705543250e756e2b073188de5a81240d398888ff90a8da38859f2a90ff3439428ef57ad57749f29b4cc0182272392ae46583d9aec1ad2abcf2edb20bb72452cbf0f3c49a1ff3e4121e83b3c172662a4189b34ed58962e97a686b9a2d78ae16cb9124f752e682eff61f5f09d95b992109a8c172fabcc6acd3d68987dbc6c1f70b27718ffff0f968bd28a81d3c72487b6b3724bbe44179270901e57c76c8827a96dde834d3d2a02fad88fcb08172debfe1bc78f66bfb757ac88e27b3024db6de5e94e708018fd856c44c03e7d53bafae192565fab0d3de33df5ccee1e722881305796732823331c5ccf51f005d72bd64f95631dd8c830e24242fd8dc100c94f3f1af013990670017bb76a089e0723f641668919f634237ab148ce14db07cd397561d2aeabee05fd3eee226894c72831662183f8d594586c185d1d39a4fadb0f21148427cfe8a5db8cbb834266f3503865b48c99166992b20494fa40b66317850649384b96c10f4668e222d21ea72ed490e97693bcf014ad84549dea08e2877e7d4e6053d1b92713fd08afbffbc72a5b9f9f32f31b3865d4d01e4a35a651f6cd36a419979f73e58b83cf3435074159e1f73d150f9f7c8be4b6f2fc23560f8026607065f300a8798c5f2fb8b421372007643c13d84091c47dbaf5c191b367b729a6dc4f28ef7d3dfc5e9761e7d881ae5e4baab223a04aafb7609257daf4422b572efea26ebd1707703111015357f0d24534872f7fe36131d83cb4577dadad6863f9c45d9a396f4a3dfb33f0f5d8810b512cc8caa606c1bf7e2422b39c7e3406324e98835cfe7f0c4f9d2399d154c724bd0248acb1658b277a5f829f51df7c7aebe66b2daa2ca842184d2814076697287c78d5d262be03e81cf9e8d6d748718f726f546e0f62999ad0cd3c9d3d8bc729d5a45cdca7b4d19690e1234005cd603cc5d24245385fcceac834901932a957262e7b164f7fb5ac6bb6d88fba26c222a4fb575595597a4534403bac634c6f1595e77086483523dc31cc424d3cbbaa5f0497e3102c511f0f0c271d2d8c5813a533ec307cf05d417e874aaa16b704322004aaebf572a506a658474a2273860f2724e216b1bedc67ba4ea586fc06f41e694fc2cb2be55f3f46ba62975dd069ae772b414d3762b2ebd8d88531a74939485a0e2e2724982c965bfee7ef830f80b3b0c0dcbc14ff873459330450d9901bff51ae122d33c4c57fa84e0bc577c93046d72dfcfa762c8831b5b1c251860c7be412c987a6a1e069bc2d8b59c413852b1dc725a3ff8d86b54631fb641f96ece0abdbeef31820f0ddcbd5ab14067941920a872b4e0a385ba66edd38d57d25bb6ed5846fe30604037b4623fd1ac91bd795e1c046277d6afd3a21b4209fc0f019056e8661197cbd6d447d817b5f56692952e4172d2e2f69ff5cf9d2414b547467da6f1a007b51ad92b5889da33528ba68e27047281f2682955708689a0d42b6101f27a8b3d8625fe2ac3e532f2f159d1e885ef721373ed69149793d39ea27871c998834f9b12dd94ac756d54e115c0164e0d407251e734aa013d5dfcf5c377f19c99b0509801e71415bf7967f91255828542937247159e8eedd4b30c740496e657b7bf87bc11e5639a0a73c41cfe1d2787e71a729452fa8e6a5f64f86b233da8dbf1711cfd799aa095faa79c33646f37bb39d472386c04e8978870412dd6f5c8fa2391c05a6b4e7116269163b1bd8abbf4529c721a6bc32270568f387213f0c3bfa8123d8d517cd904dd592d9b76bd7374ee3172747f79662a8b4ec7760ddf68a287a87c9f61ec045a00d4f14851aa7289dc3772649ac983ca1d7740356e3dedf09ef8d15f3de19bc166b908529d9c92fe9471512aa6ac702387309c19a5fc6615b8d3ab9c8b7b319c4c91cb625fc00876ed735b2f57ccf63f3bcb430872004e90e915225176d9e3c782c17b5dd2bc3be7953d230a7b7a7a12739edd7cefb7d7d6e86c4ceb7a341af7242c6b608147a75bb9c07230c1da66a757470a680bf4ebfb99e6bd7306a18037bfda89a12e2b129b373201b2f65f74c2d756b3603a50bd43efea19110d57f3032b9f081535fc15bacda372cecc0de980a09ad21dc3d1a30d7498ac66d94e0e199724480520ee25db89f87294b395a8691d2347edebe5c66142250140f30d867926ec01b1bbf8f6d1e73c72e951c51193dff004c83d170e643df89685187022519b940c6ae49cab211eeb72ebfd4385ff20082c17ee3dfc206d1c10a00771abc11d19acad8ceda3e4db7f2c7f45fde1682cb5d8ed8158a7e39938759c42beca4328aa735f7f002984c59d7235075bd9b387613fa4a732dc8549c7cee781bb9d149e6f1427558c867d672770410661ccd4c4f8d75aea366044c89db8f26b13a9d3818f2a1785ba3518cffb722d2116266aa442ed08273a3dbfc1adfc65721d646c63b5d3822c5b1fd524b97299afdd5cbf7b7a2d4bca7c8c43cbde38aee272ac8769953e06d7dc112448ef428d0903775e4dedd17bbe2ee66c0c595a865e605af25eb878fff5711f0767ba4437bc7637a05a78f87231c1cf2a489188e010ffb5da5eae7e8ef9836a2cd40e7297accedd493f148334f21cb5d8fc9eac0330d2aff9f4e66068123be3c067717222500e7c3099bd2c703aec24703742c88cd1c444d0892e8fe85e8c36891632728cef093423a9d61019b0094b6d7c00d79b4fdecd3867abf1312f58d9b293ea72e7425dbf61739c0e3d0d6f81f854f5657d0e78fad107c9b8510383422f83a313d44521eb584d62d9c0c663a86793c5ba9049576bc2fb434186b41695248e5e72896182922ea8f24e9c563147f7d5c75fc9f3aff568f2878278b56915d950827212cea3904986fa4dce972c84aa0e09a04e53ec5ecb0c750c8dad8dc9d5c3e572058a457c99ae06468e72390e0bae3a8d00408f44881499b74b9b454095aa6f725578851bbf92d52a4ce19227661d98b5e32ab2a6d703f8965a85271276ffe472a6e7b09092ec57b7bdc24fe4cb36e8c7591324332d6f9633bde518e10e6321475cd8bc818b189d2e353be379e891f9a9cde449710b11946dea539fe6bf345a4723d65a3bd517f97b167a088a6ca1c4d313aad0af6c72fd185cb07c0554a59372df6c810dff83c73ad00daa28b6726fcbf871a5b4b25b5ff09742afbf71429d728fced69bd97c2c6cdc7167f7429af625b880e070f77bf250e4799db3f1009c72416ea44fb295515a4f147189c5c7c03b075d5a1976f373bb43318f5e36d46859b50b10eb28f1c5457018408bcf896bd5055e3a8e0ec0dcb3b3dac14a0a90c73149d3bc9bc84c10fab5488ced7e258ce6ca5d3bad9aa0e796975e095d0afeec5aea35de1f78f1212ecfbb2e502ef8f9981eeb2e21f96ac9be3fc20a5cb63db611e0aa6cd1e8b1eead1f92a92fc8e2c687e9fb8c04d6a8f0baa7ff4ca2b9a7e672355a400bdb717d98c3bb85f509cf61543b22b1aab1053801ffb7ff55585a7720f1d03dbee1336e21dd90fbc8a8eafd7404a574050fbb83dfa3673cbf4858f24321d4a6e68e30d75e3e18ebb38e60b64915e326411935fed3dc8c9a6fd1c09d72b3d7d9ec6f710992527e669696ae87a16d611154e488ff9fcffe9b675da52f604006a8b1a1dd82cfe2a75b9bff54ca52200cf73368d136391def170b61304d5c5fc2f263360457ac0315551256580041189d32c95782a85a7176463ee296a572ef3ce447fcf37ac4231d23519733df1dd45f55d28d9f1e8f23cf7c9d7f363d72a270dc2393a9b9957c80292adbe9b863a107ce2494f834f8d8ca352a2ddcdc1bc361fe9a8d48a784e238d2cb4d345251103ebd713b5bfda8f382fd8b73160d3dfe7e2f24dc889292db7aa2ef120607a2e50da19d8dd5d023eeca838364362f7294cb550661db12a7c8dd50eab7e623708471c0cdae3463f87ea66ab24cdeae7252942678a441d2f029d281c582843e0e3184921e3eb6a8a940432147b8bed972e9046edb10b40b9fd3947376153a27ed496c03d7c3c5aa58e1b527f7245d50723a213fe0d43b7ee4f0a0e9574800d3b44b989d748828c883996716f4e4d65c51b0c0c239261d0082d4ace71f1e12ea2d80493e68b0714a2748a0d7a98687ab1d10b33b17b55adeb197e6d7ee9e3d1db670cd429579afd465620cb3fa9ff4b035015e07194ac0b7fd0c1150ae26d6f60ea455a034be65625152ff74b767a8c61e215089bad5d235cfae1f3879792f571b4bc724bc44d4827714c6b456ab83b742e7e72d233d05c28a55ed900a995ecf954a935c914342a688a69f8decc431ad72300a3805f770f0d2b7055d7b38747acfb3d1567cfb200b2b75c22bd28771ed67627b81ef477165a74ee7e90714649b65fb24387fcb9d1a960e7b7afcd97f075b55f9a64c60c9cc31781db78bdd4eeba041dae71a15bd168ac94a927083a4fb72a47525ff5ea8b167199213b458a7d1e100103952bace7d1fc6398fe872c845255f974bfa21fb9a12bf1f444267955e7481cba20a17ac5bffe0234452b5c38364b198a148a68dd7883054e092165515a5a6580e112d1d128125e56b86d29c2a678d40f49be86bb90375bff57f535ee353b5e782e135c63c3c381d32da6b981f72f4baa4e955477bcb48ead8cc605aca338c9d285ad76a05f8e82b153671a4762a9c3550faeacbf0305e651bdeabec8e64b21d3902efa825e59c591d80279a947263cf9b7a8742c950cc27c7511fe47a08d52ec05cf57b5833659e73cea5f713729894c918e7f24fd617a5291ef652d814bb9dc4f4873304fb87eb7597b663a61abacea0268db29edbd3a03dc32292c97901460d2e081d535c0ee7430472be7772cd3f4f500d0a65f1bcf24cee46f40a4095a82202f0a7b0d1274c6b868316167269c4ef529c7da9f856daf2d7cb55135f793976f601577ad70afef185f0cfee72c5a7ff2b501b0ee61fd7fae8d625fa86cd8640c0f0451a109de0eea763590272bc53ace748e4b1b81e097dd0c7cf9f0a5cbaa28e2199aba41dcb6281e13006727d507feaaaf1dff0d94a4aab82d53e35b298649f9e9038ecefc234665ec05f4948658a4f8da6c4157a1d1594c3f73e1b967a45f99ad74bddb3284e736457994c978fa982ffaaf060f5122c24ba83a6f309cc4157ae4936f4c02a26a86e951c72a56055f051acebae4c08230c1baa0504dea1fd10942ef46c291f4820b8a61323975e36aad08fc7845df25122d829079b576fe463509774a7e3f973b4ac8c5272b70955e6a3a61ecd626897fe15c049481e812b0d61068e1e31219547ad58bf63a011039cb825cb8c1d1c40a03ddbd1b365c057a58c8985d0ee5d47dc1181330c237ac55e8c973fe880217896bf10f4ee5e781e1bda0e9aa7e31bbcd5313bf2679058b319edb626b709c7122e91c78681f498bfc78de942a2d5c611cbe21ab272219a635707b8e7eb5dbac2d52955f6bcad050cbf75df9f16d6c48308182de32e8491f690c2671844973cf3c40bc712bbc4a0633262f9268e6608c10f7b4df65a6b508deec860737481f286fe59b6953be9b7b1cb518c532e27e1ec10659b1665242225400b42066435a8de18f8338759c52c1bbb9e9e333760f275f5bcc475193d5de330c3a8771a90331cdf0e08ca1ac7835cd3dc9c8477626f6e0e05bff11a9106d1a995d4074487591ace8a21047bcb18e65525d46fa97143435495c0f17286945713a48f68832c20c42d2952a4e8653b9e3c25e494b828688e386634467255abcf985fda942782d9fd19311f904f14d1d742d06edec199e45d32aaedb23ae29294ae2c198e6af69a652adf066f181afc75bc7a405adc5e6f5a23f320db0afc4d355a6bd2d62d794815c71014ac8905d687837cba9f2a24cc751c44a8e4726377de1907f1bd9214948bc073414a8365a0884664afbc78a787b54eac8fb61ac11fe718bda85633661dd3a85f267136564a761ccca8a744cd1f91513b6552722da83d2737bb646f77470a58d46bd0baf2f2d64383ca7d9e8cae465f5956b872b54389d678e5c2b97c7d52bbe8bad4813850132516e45a94af588d472281ec729b36becce3f062dd91afc153eab9b7204ad59a4d2772df2161d957719f79704d575465532f240e238b78ae52d3df365ad48c2f838c286525c11f2e0ad328e672cf2c99fd99b5b0da2c7aa7b19933ac926e37cc58367fdae632644d67ebbc3b514c421ad84330eab090f35f4a549368883c731fbcc8a7b22812296a57a77cff45d79f36924bcbb99045bb2c30d8bd8b7986a981c113012a18eb9f441996c9d7721587373e8f0578f45952b0e96b2b2f2ea5fba5bd0b8158b5e809c9d29a9b3f32fde6fd2e2558b079b94e754fbaf6ea38af58ed842fc36fd7a6b59e7b1f67907268c5057a7db729039e3dada8e97d3057070ee0aa5a095d4c70f92ab6f4760d72566e99b822fe0b2610336623a36d798dde5c60997f964b5614ef4b4d21c4ca72563349b5d8e87bbe1952cf5989fd049e2f46b6daaecd6f04805db863ed44680f34d91505912d00a8437df69e57a27cd8903c382b768ad4d2b9f66df36a3a57718c8a14a54381a84f160e3024eb99f6f4a22fe08e67d5d69579ec22e773285872b8e50ce59930cbd4a3f80063fc781c3dbb761ebb80a9f94dff5a1279b786f3720aa8a100fc790f1468a6a9787dde73214f96abe27096108d900ee6980303a5724074a3ea8d1e640b38c126efffe8eed3576919fb8436132bf3d601ef6c4b0172954d0d7f39db337371f3df62364e9b6ef599f88ed7080ab49d41cb023725877224f217af293ff5b25c72ca5caee89f80323e0d41bf54e7ab6cc7c890bcb58572edd4321a84ad2d7a096f7d927e2ec754ca1230930572708105dfd6ffa3d0ac581c748e798a32580050fe3d6148cf1adbcb9c8a44d524fa4b750b854a4a06e64aa18436173323b333470881841523ef1d6fca146539c09e42ef01908c2d4ca972ed681b39b56c595a31b1b1e729f94d3d7afca890da75c7deb02a5f6276eca67263187cd944cd350518de001ca7e8974a5d6f77e4c5e710048e68b4857f06d872788d7b7dcad6f88f17b950945562e7395fab17df4643989020061221acc6d56d01858056189d3031fb7b9af90668f51ba072c5d325cc5092bfb4a6a7e6924272b135955e194e74ed206db2abecccc25d8931533fa673d7fd011ff40863be70725aaf0f0de94e77a8f1b5dd0f95eff60eec2db3fa6b9497d4e9c423c84b54dc72676a706c50b4cc933e135d0d423d126854ebfdc8f9755917b86ae8dfcaad8f72b48c039b621db42edcde31368e87a0ee0c1fe65b62f4ea0cc92196a584f00572dc9f9852d867e01048a8867e5f208b9425a8c77ae146ff76b27a29124186b972eb537686f9e452485ee2412e89b40bd2b7b5ef00d9c359c0c72581336e14fb721578228b743b5999b116bbda9c43f7902339ff0d978c66793ec35e11567ffa6adb2cd2d6ab9f6a248deb2192b1baf4a835738c8d97079f3cef517b45d50da5723a01c4c33a0c7af9abe29e902d7a669c9354159d263a1ee60bcada77f934b47213fe20e855d710db0ef013d45604cb3853f1bc1b11e4da02f1f992678482a43195a2fdde4022eceb77184edffd8dc19108b4ef13b9c4652b1661160ad7765b204313c2d48ba532e6672ccce663539f3a5214ad97f322e252b87e9e8f8d5fd906d4d35e127d9b1f23197f172f456efc8073878fa6705ab78e1cfb74f18ddfa02251931140e3c275f762c55d877b1dd7c6a4d2d97feec53029323ee11591d251689dac6a989dd00a2569fd9e9cb4e008430fcd6a7d5d7faab5547067e073f12872da91f460ff15fdf2ad2527fdad4f3e559f2ce8739a36ee6e067da7607081f03a4d8a60ac695a76ecb013de051cdbcd844969ac613fc3741e366609fd006dbe667318f2d4ecf080cd04ab23f61551b1f13d2f80a83bfc243336576100ab507f720b54724182e1804ba243adb9bad927b4eaba92d226d159e8b3d15a9674a050103edefab5cfb88611621a0eaf8c5ee497b50adcb6518eaa6e5c2d925e4bb8c46cecb32553a2a3fd48948af7a40de11782c4252aa687464182922057ddca693372291e27f932ee4655c64f4512a697c1795da606683f67e3b049a95ef8a2eeaf112aaaf37d8555e40fa5376b41c1cd90bb4e449f1bd9ffa35112d67342328fc94e5c78b9e3ad2856a1d35a96dbd3fc1764cc70a81cc8ac393cf4404796eee410728cd172bef5c129687222c07ae4e2f5388cc33f84c575fdf43be797d857211972d385a0b05bd495ca4daa349cd94e056f77e044900481eca3e05c12959391ad20b69514978ae25aecfc1f84e2bba3f0f383cc8d47dd5744a50ca513a9149a8862d0eb9a21cd5916d9911993e788ea979b128ebeea787cf1a22bb38d60fa763c72726fea7468550b9fa3f61ce2efa6810a517d37cd7a5a05ba8f4d54c817eacc723c801f480d102e17e36ea26340794f488387a686d751ae8b3baac4a9ca46417228fa245a4242d1157422afb1579c22b0c9c7a5ea194f46fb19a4e618855c4a72c0ae069d392b8da8b842a37cb8f7ebeb8bec3f27118db2929e1080dc98777d72b83a167b7ffe71fe9e67ae52173580ad5970978eac3338efd25108493fec66484125ec14149cbfde61d0b20aed6fef6115c9de3a4e216c135202cf368f3695721900e1a9b256d55b17c73721d0103dca53efbfcb47435b11894de2fe5fed5a496036abcd6ba40ca1c49598cfff0397a5beefbc60c492c8de5cb879b559ebdc721360d6b77df94df843b8719a8e422ffc3f36c964281185d34a0b405111c861723fd3dca1df64322eeb6c8efc1c9ab3bd34fb15abbf3e0d4b738848c309774372010b2ac83aa8787bcd22f64dd53410dcf69408329cb6448c35a435b83a654472a2818f88c38f37e344908d702bdd25b1609bc6de469629c58d3c2aecda5c8472585dff4ea38c64cf7e2efe7b0c597cdf7e248ff00f38ea93a44e0ac042492f72598042f84a4487e3ad5f6bf3d9098943e132d2f58a2101775772541e628fbe721c25016d3473ef83b1db083351d13b5cf604e5075b2ad6570bc1935b4be4297275babeebeb10e92ba951fd463e797440675dd419873da0403c4e86fa94c8a172aec238f2e39295185e437efaf8baef91304b9c368a8698c03e4854010cec4935550eab986488834f7cc12eb1c7023116af6fae6538bf5a053ef39e78c687d872a88eb0732177dbbe14c451f01404514a10f71829ec10f6ed9b391253a146136c4e6be7495c11ed61c3f6d863f148ddf1987d7ef4378ac835dba0900a69746e45e980ebf867de74dc38c83d1f232dc406500145474e4e0b8499f7052d0ccfa320fb229f5319c06a244b90f55e35ddf872ebc985d01d8b7db52283dcd36fe69c72b189bf4465ba31275bbd71459e34d8f0a83b91c84e80f02970ab9bad357b8c721830fe6e76e226f091a1e4738f9ff6c854d491e9dcffc90944ccb50fa8ce5a5d13d55babda8574a398fae18b4afdecc22e6ac26ca61b5c125defd6c53bfeb072b5265f725bfc07448822364199ab5c053be532dd23db049479df6ec393eef572b6ed8b3740db0ecb0b861c32b3d799e0fb7197216fb8b2a506c8d855323f7872d1183c4e6e0272d4c22dc2126d6246fd7406d6eeafcbd715e28bf5416c1825721df0511446a3483cd997a62ce91c81d1128bbdfda51d384e98c4a41b6b75811725bcb24b6471724466ca29893fe8fbf0b9b0ea4b93fd90af5b40b9157e30ed7210c77270c00dc83370b52bec80ca2a0a22c1feb1efd7510010fd449f486869508286668d190fab8cfeb9c3800f78ecf1232960da4fb34aa9298f3605f54cb47285fc0afdf625c12cf2ef666cd11478cced49bd2cd01466c4d4b613bc89879762f6a043ca3642a8f8f22df57a08014bd6723122761a182c7d5a03b1d136eacb6e90ff7b1c0d4f01509a7d06647188c0277b3ddee6f7f25d593ff423d0d4bbe672ce6d3163c7c0cc45959fe2373d87117d29f0e0a95f8f01d7a2b979138900600ff94e5c4d0482adc479a3005e8ab2605d540fc7b46a5ffca780d81bb8346ead3a6d77208d8fc2d1eed77d1b9b03e0d3b21e64cda0705380608afd2905cd325c3ab75d96d08b8bcedc1c22e11129f9fae30935967c6d036b166775d401272cc111b46270350e3e8c27647f7a635abf131342b3379d9f9ca24b07d34c1d32af5a0befd2f79efe6d96e887cb5deef0599aef8bbadeca5e7eb934bd8b91d1bc93004aca026bcd0db4d4c6b33e3b1078d6f4995ebf9275ad9b26d0c47e0fd7242ac94871ea7b33010d3c8953a3b5cde875a8bcfdbc1844d937d610bcd1ed6a50c78672a94109b93a3b90e160aece989db1e0d0a688868cb3fa6127d9a0c2de48fa4072df92624aea782ac98ca214cb4424ff69ef12a1976012cc228f6fbeeaac254172978c237d33f57353b5ecd5d261c1fa2f370aa504ebe303c5abc490eb5167d8724d42c6306722e68169f5614f84d0e4944ab1e45d3f0b88fcc7d0cbc995c08c22a7cf3aaf20f614d9b89ee8e7621d0f0b919cbe053b39872e48ccf8a5798cbd01c1e112871e7fa60134b4e7c358f2c2eeb4e607836ba977b93110210c74f69a728a9100dce59930c130217f89412b493619a7b4dc133fdb7fc2e9d5eacae5df0abab027371095a1ed2131da856d98721e23ffb493614d7c318c77a2837f8fd154d6fcad6d872e52820d1e17fdc030d09ae9c748926d8baea29f0bd4df09a1546f4c528c9190c858ede107c290889dfb46338bbb5cd490bd4ca82c33d63ba9e33bac8dc2bc070ed20721c381f8ca35e53f7bd236e1a2c92dc6db9ee6228581a97267dc64284f6c01c76dc79daae998d47887ca22bce19af56c3ee52665e03b68490edcececf0bd19e6838afdf7829f31d591944199a0f65fd20d0f9ada7319b672eb190920041e4b29fa3724a947e41eaa86384f5c174ba9b91ef13a6753c74c6778e4e2cbe7d92f47358a537537fcece793233e9151b82dac2f57ae441471dc6445c08f01ead70da8786dfa3922f42a86cbf3f520ad9c8a9b817cbe57583b6a1447cc6778a9faa15606a8140224b3b3214411ec432ea0d346c87bee12012b4172d0730dc33fde2f84d69693650e7140e0757badea26629fd23b21a57576663f72fcd60c23f05f3fdaed0c660f785191bae28772c71532999a9d181c67e548e80c1671561da8c3d68f721285893cb32fc8223b17d9729cf057aca093c49ccf1272ac60c7d71f4dbb63c81e5730e74276efa02c2f98162917e7d93a5702717f6625dba4836a8f3bc0ea63c26bf130a4d6968d92dde2007f3730f87ed6a0b0e5f5722b2f32275965e54a54838bf3dab88370910aa07a4a69d8b12d3f9c504dcd9f70ca24f4f501840c441e1df0a07214574305b9d28f52af239400d036787c6ed9722288cc20a98c641d1d9f33c37b76da2cf9c91d1251a686c79cc87b979b9207080e823d3e8e9932c6b563cf22287e1de6a6346b7ea20fd480f553a07d3bb8560b8bc75046865e761774c697abfa55d4882233045988d3c58ffda13686bf39d172c4e28229555e07e5132b8ab820112b9f41ae4a634836c50a153b39e7433c9f72d100d8b65a7be714e443800fa72c64a8bab9915929baae1d8d0eed047f13ea152937d15f49f261cebd7d148b3fc50b03fe1ff10dfa8ac2ceb19d04000f02377292609c87f953b505866167ed36c193cef02b8f3124dd0c0c3f1c6cddd66581722ddc40e485421610dd4bac73e74c36e761cd80eafc1823f8eebe9999280589723ccd9aa455de86d69a5ad78a73ed5b9f026a46af5db7b72a513b98383e78ca720b8be289f16e9cb32663942f5c14f254f9b940eb87be6028f02569d15e66533a1d27ccb86733f2724428b31096a5f8c6d92d73ca47545fa85f2213e8c8648572950a1a64b5c5209c05e9235d1b9b8ce6999e7a1b838e5d7e5257e9ce7ad14d724e32898e8c32e00a5db2e3b6557cc6364f17a026f9e02c6228fae54cce4753463a22e588480236bcf9505b9199d686c8f333bf9278a1546ace6146188fa75e71e19181240ac2b1e1effb6ebfcb8d08d1d06e78dc7c3c4f447854f241c5acc072e18b1915ea62dc61a2ebb976da5790ca25e77b101d53b6599dcacc7a9a8c63723830ff18ced3fe094373e7833b7f81ed9b5253cdfd2328bfcba6e644b28da6724cbd11eb04a2d987a98fa6b780a4d2266e3f7b0a89562ef0feb29d574bf5064f9763bdec8f6ecfa71098d2b63eaa90700123882e3c413966a2171decd874cf50fc87874490a8e4078d4bb54ccd668d8c66e1cc6211aa218377fe8b301e440b48265db4d03d0c929e45a22aec864b5e0f50c89df5cc480f9ed1e44f5ac2558954151b7e460874548ddd33db667306495a8e11bfdabb651a2f0b7a66bcf6d3da72818ff0ed794c05b716d7a0f7973641a11d6bc314cfdbcfa37405c967c3073a72de497fbf68aad67d3bc4c9334f23f44b95a23682f653e7d3c0720ad9b4628872915e3b0eb75fe670c0914fde4562b99de61e19cff85a6ce0d9bad5305f7a1759a22aa25bcdc04bd640ed0c376b888e3ae25697b93e108223c72451b38cccd572e6f3b0509b1af1185a3d7bf2695eff99ee1690c96a7ad856d283ce8a2bd16172924ea4d7c41c9e615372b628017f55ee06c813a7bfb1887faee0c0b5311c706f9f899629a10d1a271367e95ff90a44fac09d37b0bc506e543e4bdac2eb03b872ec708a1bb9908253e95af2a5d09e1f1dd0f7c89df1f6c415d7df6dfbcf72243e25f0318b003f32afd6294cf3f02ce9d300e77552396265fcd93f0f1d8ba5cb06b188fb6335edb3bab1fa44f2336b79b9961083344c0676f36fa52cde7958973099cc11e7d342ff95f85ae704aed6aba835309c8ccfa563f112958dc79e05bb1558ddf4730ce0ab46e4ea462d98349cfc674de0dd13f8c2a48c5deb2322c191112285495ce2f9727ca64e244cdf48259e0d16915e5dbbcbeb3622f3dd6b73142bfc7df72dd4f4ad9a4f4b8b5fdc8526de7d62526f3e0085f024c43ff8bec1f17219477d068e4f0a1ff6fe23cb7c0c3367d3fbe925b835e4ab82999de735773872a168795188efb9991ac6397e15cc9312990fff9ea003da0e54cc1ba420464414de021bbe196b62f8028957a852565aba26bc16d1f13fc36c51f54830e2dd0b72a4b94244fe04a0cddfd71dfc65238970c62f2596123b5a399ecf9eaef0da975e1cf03346b73066b5c674347a29a27129b0833971f4d9e196b9f7bb86615a39724cc087b6032f564c3267073a6f9b044f3705cb6a90cdcf1f63f5c3b4bda2297209e6ec26ce20a4d2f382ea82d29f3544256dfb2c069eb803abe50f505d15d2371f26fc57a255ad912f2cce5e92a7c58762ac4f79b469fdd8e7732b8024329d725fe5b52c73737413161d03549c1cc51f0bc56f689a120c3eb8a5fadc8e8033527048950f3b23eb87f521ba6009fe33dc608c3eea0b7f379d79ba54ef8aae405a84d49a96f07bca927a7e34d41390f6425d9018cadba37e8704733911695546720b5dd3d3503e0f2c6308738fe688d8b121a36b36d97aa96d0733ad8d34f0fe725875df9641244829f52923a9a700cd9c5d445443d0131fb366dea57ceb835972432e4ea6e0d502c89e615c85e675c9e8062838901c284e81ee047f17ec75925e4bf890b9b2f11404f37b73df1ebeef5a3dc60746696dc8d8b88dd972ca5341724dc70b7079bf905919cea3dbcb9f6f01cfa59e48489f3a3dc21c66ff6a618a721d398b7acbad595ecb1dd0aefac774ec70bc2ca229890e864c6fc815efc9db216dc8701d8479d5c99ce7bec0fc01387b5309ea021665e15452b12f2219767857730a08ab5feab8be184066601c7f95416e0fd56bd40a0999677b37f02379521e80cd0c211faabb66dd7b2d088d0e8d1295d113b0e25d55a0c31a63f99d4ac6723823f94955b979ed92d20b837c1dfd12860ad4103f5c9054a15639632e982b72dab5461e6ef35a26a099478629ad9b0acd64670874cc6ddad79ff66447b40f72f72b2064dc9de48158fd5d05d4f80dfac9b8f92d64ede26a180524352cc4b472783b9b99de91e2ed59871b0b0770f01d3d3e30ef907a998f961830aff2d8535c0f9ad7f32c5516ee223b4520118bfbf0ef49fa8389759dadeb03882b6c5541720faabfa6fc2200257fa7a00fe798c879638f1e4e0da437263fd13f8ce24152727b4c7a1bb1c7678caf6911f321b55f3d58678b47280636229cf884e723098672b9117845407de5e370889174c52d05ad9ec034b20418bb5c77fa5483f5ceae72c2f516bacd813f9f0f026c92590dea2657eb5bec277f4a8806ad589300ad817222ab220364ce6dec97c09ccb2665229bcb6316970b36520ea2898b700d527f1bb92bf4d7d03a5f358c718615bca7cda58f3dc74103e9d3e2c90370445049fe72d1fd19d225290d774f2f8c40311da06dad40cca7fc03a73a94a27cf2aa35a472333483a5e6b222fd74758460937cc62b8729673dc3ff3c3fee2ba1a8bf45272c7edf88b14332b4275a53cc7a6e6c181c8414e1fb3b5bc355aaea1432d7b7ce72d5fbfeaaea77582464b32521297084120b36b3f05fe244385829523057532168a77879b04eef4b664b841748f025b49c0b6eed138784166716c631ee882ac372b17797d2c9bc38221b4617458df4c88523b458ba6a279dad3114032391e910724010bc11c802fa5694971e89d8857aa04104fb1f4f76da8015f2157b682c3b72333f13971e2926673d52465d53d89a0ebc0afebc2fedeff982bd268b4905c43a3ec6ff5ae29dc62f3e67bb62f6a6fe348780091ee3823d7833b8cb0cf9a67b4a0a52533843971c03379676260077a85aa3f4e496d68ed916cd04f3a8572ce072aab6290f74aad5768020945879b415a43e82d101257bd6eab7600bf4a6c08972bac279c8c570b541a52eb809f2a06fa0925ab7f6b1dc28289c93d51674912b7299e05284669ef0702ab0f05b929b280a56b50227237e9ad5446b49fec1c14c3c06d5167a9ed92c505e14af171e513da87e1fe8ab4092418b3d127a576d413566756addf5a1b6475a474d5ae4ce43ee4309947a6a43026b048dcaf9aeb6e84d72f56a30fffc32d2e249927a8b447d8405b8a40e28846c95dc01a5bc619ef0295ae546c756a307ad9133f2528022c4906d884ffa2a5c9fe6854577c4edafa49554b611611c63a5beecf2ea3537e95718e114093fba2aa7bfc4ce4b46abc7c6ab72263433f4830b3928db74551ace3ddfdddafb0d73309b8e91ca5df4924b4e47729ac5ea0eff0e6a30b7da34267d74f65a5ee915067b79d6e3b03adeb5cca4d37253aebf093115fb8fa4f3ee1a665426426c5acf9d7cd6a9a885cc77b644543661dbdf41b21e6d57a8771ae88a0b48d4f367d967a9291198cab57c90266d034055a6c27bdf232ea91105f19f3d74fdf9240c59566ebf435afcb95e0445e8ad8d7209049096d9d3df7d513a6dd842300de063de84768631ff152e04575e3e831972efe338269cbdf96b301af7ac10c66e9597c1beaf4d0133f22589330f96704123997db74a7af57e1359d078860b57779096101768640b0d4b15e897a34b4116659fd09a8df1c34059b4b33b576abe4a7e00948c896bdc2003da807123124a49725d662e60b4305919e70281f3203b75d9530a32dcd441a497863616727efdfe72f0a6932dea163a254ad39bda72c5b3c558543128ba65b8ef6bd9cc7a974329432dace4843377e5ef432a12f4d4d837c737da8c1aa17a877c532449ae2edc8672833478fe866067460cc8de87255f1e51367b8d1f38d0f3e837504eb042b204726bcacd3339e10593d99dac861e529f749fc41a4f824c88c3876a0c86af80aa7259c93d62783fa29eb43fbe565de56f7dd6249ed86ec3bed9b1667e9633af5d7298468a975d3cc4b6dc0670bf3897ba40398d448a071c76ffe9a8df9b8c06e3723eddc31e08821639a022dc965fbd9f9a1d771536dbb9469a4aab7fa566875d5aa558eefc5edbaac9903c0a4526334a30d0b2830d9ef336824e192bd5f649b57218aabd3ec01554c9d08c1a2c856e49d740a13509b38619baaafb53b4fe52c272d326333271063ccc9078b98e344c0f0986f5109ee455c9815cf0f05c2eff2626b8e497fece7389a769841b0f0b21506ad0120fda4a8b00a29585f743e8544672537c195eb3668f30221ab60100d6347d0ef532553247e8ecf079fc36e2609b21a76c5befb581e623e253292a4ac40e9141ed53b9a856244246b5e630ccd3ff72135b970f938af21ffd8e7d21f22a3ad71e76535eff01126c850fa854203a3f3bb186e218c3d9a5a62b01196b4587f6a038d1855e91eb5a2fb59c0172d4edcf724194db12fff3a1284e180de4d8ec203cd623115f96da0ed05ef03d3a92752872a98c6678dccb631281adf13abbf700555e818122433f60d5ca84d203b29da17213ca9612d1441621598e9c0f2ccdb8fb1ade15a1554165a495a7c4373845c6561e85bf13f8413b1357151b725b1f502bb59e040d746fc73f1553d26ac55d4a222a3a699ef2db548a7f93b5d3b43b2565c812a7f8a41e2267fb4766d8441330381a7e1d16886c7ad6a20463741ac33ff80157e43c86f87bfd8fba2f24be818672b0a978667179df64e1933fd76e6675e6a90bcc0968ce657dc9872db1bd491172864d48f58b7baa41547e73d0f4fc809c320bc8b7fff15c60cf476cdbbb87111e45725c8529752d17d343fe077c7c0580c915174858b2f97c980b8e729400b7422e20398c5cddb489f7454dfacadd9806428ce6c98a2c2b136e7bd6a375f4985e185d676344c45cf052f1d17ec300d50becf5f8e8b7f77f75251613f7da813300d2564851a3e24d3d395316869f723ec84a8e7b47cdb587d159cc434a154cde72372ec905c18c30a9b86a5f0866fc492329fa74622965de09b6bca5636a017f639a3b6b5987de134bbca3ba9dce4216fe524136e8a71a47f0100d1f03f0edde72c3094187f5633a0cb53d4f77d5a4aafb54717a2c31bf935da47125bcf763e05f78c64175dc4170cebe456606ae61eb7c095084306b5e8b6a6bd2404ec4d42346c0c9befce79eaca4e7a4b5914238e14838c974be9ea4a43e82f8d4c5f7262272513250d2147fa1f65129ffe678d067bac39e0cb3f30731681bdaca43615e962e5a5233bbf6a0be2cfdb6dd742e2509625e07672b90cbffdb28cdddafc652ee72592102f23a51959db095adc6402f6ea6be87dec3c0e7c4ba9179728759efe272731015ffa77a0ee5b257c008f2b913846a35d067497688334ddaca606b21244d2b2e1d2ab3f670e42f2bef084e72ed499b209d54102ba04f1588a545c3b80a7264a8b35f7b129710342d2e1409c3c82fc2a9eec0ac5db181796a6428e6812361f121eb0e9de2a6bdc31c5dec93de99b399bd5c453ab9282a888b6d1d53465b727490e850d5b1d3136a8a0844acd676495f2013f9c9e7457ffeca3dfd75eb7f11a7d2149a7e39733e0c2e0a957a4ec023e8f282ccc2486a46c5415ead904e787205c33c365d0f67b93a099328629f877edb920be08b774847e3c707f6a253ec72efa9c94de9790b30746e57ae35f0e2d97f9e57613a0f254a640709a581a46f1dc3d66be5e1cd608240878cd7181bbb1db52ac8b344603f9260f8660bd643515810e2199fc70902e89db008a9cfa988ceff0ed0c93b1794811e8e6c1141e70f72d29e86afd0dc9c4adbdb8a78318569a9b65c8411949021e3c522584e056796107dcb8db6d23205c8fee8c657787c387ad81d8a9c6089d9f26aaafe9da6109166cb16e1f06c89f15869638b5c00000fb19511e7e0eb855113644a522629c4a43c9738806e725e6aacf794f71d55afa8366c8803c65d259ac78470d2288bcd0572af255656986d65a444fe9801a53b8976cf243bd60a6c5f26c5f0f2e44e03087266e60b67a1ad115105aa50159180b4d749ecd416d893618f7c35635a0d0a043dd00fb6a9c370b3ab9f78e1cdc62a8f39b59a505d3cfbf8f64141dc7913867819649583f4dcac6510e606ed24b7755b103b9cca76a379a907e6a455925478e772fd2aca6e02ac8e1eeef186fbae28828cd048e03c7262ee538da856b43a995d728c0cf66de2776154c8c27c4cc690117d1f6f170fff6595520b074e2903938a728384156d8431c1159f14f743706d8c026eb60f19cdec4415dea24f23de1e7b09cd70abbef41836b4aaf112d6ec2c6225c0164b6b4026362e70e5f7439e431c728f9e3e3eeb40291f44f3dfe1c099d52c001a5884c5c6fe8fb9dff7cb1c534e554ee2925db0d9728136b50843b8b945f17d23959ae0c7ebeaefc10c23e89d62722472196ac64abcaa89bd251aba535187372a62446cc333d53d134c7ccc97bc05fb55d4336e88fca4442baac12969f8feadfc6108125f3b5ada5b2bb92dc50e7232bdd537ce464432cf6b260ec5c14828b28e1685e897560b1761883e540b3b556c95b8511866933ba966d5db9da65b04fab799b7304b3c09cfc505b27122e6725f2658d7b209f2e02947abaa23dc8307bb1b1b5715e087a17d7dcba144c2e4725d7dc7aa57ccffe202bc6e507094e7d889f8491ca1581c36ab39a4bf21e2b372ff8afb552572dce9281dd261a56dc1adc3845a41515fb19b4e64715c4152e53d639d1cc211bd7c9ea3b35da3e54d55e28cdc3044afd8bbef8226120d612fdb725d3d5a7be61c7e6efdf945bf446d0a42198b0fd0e77d78e1eb06146ec9e58b69ef17a95cb7614518764cf0392830860667ae03e9d79c0cfd2224fe49d471fa72d25e06d4521503560067db955e1cf8afa38cbbb56ea5e24683eb6e9a6c2ab07283648dea22ffa4ce918ca4b339ec5c0348727da98c45bf80d2830f0804d46372873130ca3f189e22f767f01f45d93fd314281fee71d61e224f372bd9fcf73c72946c8b0e0deefe06e008a1cb6fe1faa58355fda38e3625dbc87420194988372b157597f7c092884d84f6cc56e774ccafed8272187d0f6d2883ce4f8e08389e72a74c10f7826fa879f122b097c3618d8a25417bdb457bb36a504748370749436223998bc8f33714a58e4dc12adb697a4d6f6e6bd72d8741b616540bead698cd2e0848ff29437e115da9ff0a5d45a08e09510004e52735c986b31f5e8d9c5bb472c93fddeddfbca5eb621ca35e49235e6ad9d76306f962a5b29c951d01a4501048af4d166d388dc5e50287f8e64805a77958b8b03eacdfc730d85bfb3d1ff1ce724f92abefc86124b7ced9236f8e8458d55834429c53de8b704583c205dce66648afad1957c35cc188d2501c2e4d098c992af2dc953ffb836b280b9a54fdec9d249e38452e2cd5cf365f79ff9226be68d8749aba5bcf4bccd4400969428ee7f13e55b1f729a9836ba6eff1edffc8085f88369a083743b8a48bec4712c2ab56d91622a25e9ac2fb378911692a9719cb2f71491d0baea7d27e6255bb7ffc019cd9724cb2c5200a84e4e6b41dd507b292b426e38edaad698da400edc797ee87355270bf4be641af7dfda1336779926b514956838d01b0c22ee49ce92fa02bcdf6cc7230956f1a8db7d9de5b8329b709092ef702e2475a559a0d16b9ba0de566d1f8725b2d600f8a7acd75ecc41cbcc63013b48e87fd7a74829103d5a557bb4cc7993bc21d10f56e9d8814317040dd99f3daa75d529319a2ee5cd5b4eb64902fb3c172f5b77566cad69e663d48019f1a390ac44acfa1d4da7e4f0900ad588ce7b6f4471aebd0330d294610d694a311f1bcb287da317f2258ac5a15ceee795751048b72ef9a001dda7e2469aff37990f7fd260726313c3ee3205d1cec02c12add740b728898dd2e45ea84c5734f27943f233f09bc07ba649774415af5460f8230347c723c30b18d10fb7e83d2310470ae960f3c24953ef7f3a88dc1191f14d64d538572591f4357475f1794faac386e578fd0f5b659b8dad257a10e8f6dbce9274aeb611c7e0714ffa12b7fbbacaf4b61105e88d166300d06989577d596d6eb5c3b947258250cd8e248a6bac231c0f959c8c8ba4a6d29976e42eadc4bec18ad636f5a72cc6f6deb5abd4fc2003b42cdb5a41d075032ba108d0f6a82f67d6329df499572df8344953680e960c7236be5d9f0b8af0bf3710a97dc9a232f4400490253df510cc720b24ced9de9abf8aaae9a2791c03affc63d8ade20c67d84a874e888a17269a4bc6ab3365fa2bcd6b9d4bee14616e52c78715fe7a85ef50c67381da9f63ced79e96b2bad4487e24da0cc57a34d2262cc0e436c4787688703bd530055fd72eaee7f9b538a19174bdaead7f655c85d7f9f88b7e0da7c10aedc2b906b67aa7203544c65eaa49ec4bf0b53e45aa0ce46318847e0bedda3b05b5749ca1ee1f472468a4905d8a5312013aee81b40e910cc611d1c75d70219fbf078445adfcaec7289f9a1e4b1b4281110f4231d1159abc674f55d23ac805fe5f4aa027eccc68872c4a14d3bbd17371b7b275f9e9dc0b6bd3575d1f8a816d298baf04cbb548505727f88f1dd566eb8862c2214e9d7b75889a42f4cf0696f5118d1046070e31c93247d87277d39761feef73295b18bb6c22033fc0367e19ef77596df0a32e01297720a63b3cbbffbb1fa796f430a1948f5874e5bb5fccc103cd08e0f8d79f99b2c72a49a0c448c9933cefc434c5355b046b74a0794b262f032f635236db48e35a664cea72d9eaa81d0daf6fc8cc8b8421137c099ed8c2f32a374e4df5b94565ea817c95ff5fe42c39521757de2a3d9c4f4dbb418431fb6a8ca0d292f2f195c1f57225876ad382d2b53f60f525c9c08a3ae6b5cd1ac25882930faa685e4b2907c167284896bc9ba55cd1779447c049b38d53bf4c1708e53821749b1e0218dac220f72069bf3b1004f164aa52ecd31605f576c8c6413d9b24039bbb0ba949b538a7172409e91fe1e17b4581d7cd2945ef9d1733649435ed99e496de76ece8a105c2372ee30cb40bdf5fcc1d71688bdd79a8842d186fdae4af19478012fae15d7f9e6729e46cb81dc8a6441ae335bf76b79adbe0a25db6a8ecfb738ec7fd917f3a52472456732c034a8753696632f797c5cd6c37b3405b33b618d56d75e4586efa49372c0d8c1df528b9eb5017370c832f7d0d6023aecbff1643b2dedd4d70275bc5a3bf35267345a07280c0094e9d09b75f95da60a7c6cd524820e79f640f46f754b1900ac44bacc3b70c14be2d2bf05a4bbb6406020ec97074299a7371f8931083d11670660537ee3375027e07b84a077338d030a533b6d3e76f8c39867aa6ed8c9720c9e6e20cc116e9ba2cdbaecb7958756efe170cd68dbddd57fe99876b9194e7105fb1da40ec3652a2dfd4a3928fa4ef587226b04282d0b971ad39bedcf1dd0723037c7d1b1de8778d822eb00c97dca464637871544e09974196f6793ae68e2721cf40eaa50ef8302607b9c7bc6a9cececb6919ed6b7139d9c89db918b937f572f5cec1c0dda8d6aa55ad53d11b7078b9ad73a6e71bfc33a7f5b6e9ee704b4b72cb7dbda6ad33654b04be0d4e33d9ab5fe2e2cfd6f07d0950980e8ad1e7c98a72d5e8b4208fefa283c773b9745e29e39fa051aaddb5914d757174233fe3563e6d6559b3616381e876b96514602b8fba857dac84bb86341edc3717f37036666d72ab1ba6c6945c53d2fb583d9e96ab28c782f89a402703070eabab333e3870442da6716a75202aa49cf4ac4d2ce268ec37f4a9711d5b49a932e87721f8ab8488724a13aceff22e2970ddd9d4be27279d6fe213fd9ea963abaaa8889a3210f1b55165a8d9d83858eb5420573d4fbe4ea8dd33f3749209a155a95511d2aa7da1732e291a461c472b81bc00afa599decdf88c523e3e71371aa88a524fe235f9cd8372835e5096d571a4a294b96991231c400e0d79fef08ea981c1f31a64f25e99366c9c7d53b6c69777ecb39cbfe3c03cbe1c2d2ccdfe8a908748ece397e48b2cd672c67f27d8280a390e3b372222b93083a26db49789ad2d76a6fd91a9c706afdd72fe6dbdcdde2c875f25660553958f3b916a4903c5a1b0e985b668fe56c04a8e7279c0192142f55cc66eaa0ca81722ac4ee2c2fb749f086eb0100dfd1c4b933506e09f3b4d114e5874c7172458999c8b431c671597635d37a539ffb62483e4363387accac6fd6520a2e6d8f292f48854b7a11fd56b81dea6bfbb9dccc2ad65ea369087412b4bc4856c9b4e63944983e629c172047bc0fa3a0bd7c129b20fb52772d7b8853ab0ed2740838aef2c8ab5abdf08f4806d6e4740c0bc83f111e8d05c72b8de76b7e6e21ccbbd4e6990d57c7ecac86e3884c9bdf2b3989fcd65429ce50569d9164bbded940413e9160fbd0a85339bde08db8ad49dbe8e3daa12070e0c72d3996092a9d5556b6e25b1d99112185ec08d4232142f127c8071dcbf9c51b672eb86fd45dca6413f6cf7906bf3a3ab67e962ab88c98d3cb854e268ed6d490c147fbadfed1eb39ee2514df9345a7589094b45912ffd7eb63534bb6ac3916a7172519f75be7d9ebdcfe449cbd1c65103997b5399388bc8f620d23be2b382e0b33d20e4bf9eb995af11c91c916b9e88b5418a5b69433ea185ac8c9553a873838572014fe12a9eec6b9b7771ef96c7b19499eb9149a3285e814b8873c9757850632efb240b4a6520363d0167a2296cbc8a3e0eaa6cd8ee7dffd1025db6ac6a3cab72343093665caa5c90bbe757436d523ab032fa8800c0ace2afb43773e4b5402d72441cdf44c8ad363920f9f398448e3ed759857407d032c7efbf5a2c668b34c0725149f55cd79ee8471e39601e652f7ba0e7d482ddbb68ce456c0a11573d09ea2bba5783fc01d58b13c9f3a7ac128639fffc89d887d23a3f1d2ee7862a0f4ec9729b29dc24628cb9a6911cc95c7725110f5d845f174a7b9e4f7b56f2db2f8a03064cfdd9b5c00c200a72738e728f744af872969c63350df17af1d585ff42b2286c5a57947df756e4c38e616af8dc5dbbbfddacb304a52a29ed0394e8b2c809711a66882c354d033556ed043dfafb2eed34dd1cb6ee80580f53741c6d044d0a5b72d0dc6f990b666442ce4b172a04953a55e0e5885b50c73e9b2e3aa812b45e1872f3d561c590a140574975368d8dd17bafa153d781da730aa532ec94308f464772774d1636691bb098d40d1da28a43a747cb64e4e161baecd0e01b5c858ef7ac72a0774cee5355f9b1f7abf8e0aa2ab6dfcd0582828182f36379ceade68ceff44bf3d85715a4b4c3ecc8d5015bd1c0968308698491cabe95be8e5cae28a6b5e972483c6c081724440763883d6fa62b836452d348bec6f3e28dada641d82f27a462614610bd506f5e100382e4abb8c0bc67c1715e284bf5e39685c25c324384ce727ae55afc54aada1d8c8d24de9d1b370abc93123db792e38e1d1d13172c002b4062a62ce69f213c4a41a2a514289cf910f2e27dd17d53ee7caa20752033dc1e3adfda249d877cadafb7b075e4587c8dacc7d5d2891b1096721032706b3e55696e1fc0ca2034894b08e9819201bf5e9f6031a44a9e810ebee936e1c169d4e83634ecf88fff42f104761b942f42cb8d8383faf4bb414e48fdc3cb247f047c7d7a54b0c6c0c5eed055f5b2e51b02a2ce0c500046c8f61d2710f32d63d4ccd7f6761bf46a36281d6d9167919dd131693da538e080003a70ff8dc0a08f92e2b2ca94723d43e1eaa7305700e6c11bfbb7983beb3715b0526a67b94d636e6f12dfbb0972a3947e79335587fe76da18748564ca53829a604cd94cb496da9242207aaab27283a9512c270c97aa5f11e839b3741e49d9329ca0c86c387a208ed2824381e672547474c4e329b542f6234ab3ed3d18308320f2eba300eb90e2ff2fe678d52021f1439c7cfdfe1ce37aee48dff35878a0823a1297e8e58e523d19b0b1a0c612553d5892b709bcaf1f29569886d818a1945c61002b66af2f04be2a62c68285f672ac415a44c4f9d2f59418e40e5d7d2d83a51b6a6c901b3937321d9b145ab0b36d7b016802087a58dc802217e0f7ccb2069b6982b56f90a393642d0b32f7fd786cca3f767f28292c241544f54832c46a348a6ed37b9123a2890a9804544659fc72483d640e730e0072e9c15ed31d51505cfb3947661ff803aa42e00c4aea435572e1d0d83083e8eba5bc1b48d4869f3ea43b21cd5899e7552f3ca7fa1c698ea20c8efc88937a3600b32f6dd3e3b900c3288e8eb05b303bd6d364dbd92345e0a311de32cc36fa1a28cebf13051d227f18b1f5989f2b15505de1bc5ef62c87b21772d13c3298d92fd19122cbfccc9e4362e4a8d0d336ac5ea15c2f51d14221855909c44d6457ab0457f94d63fb53c416025d7313bb581d3c0d8289b870613a58a4720c3ed83d209e8cf5debd4d7d83597971be774d584a0fd90ffaa0cc4eeacaa0723e628063cdcd07713ba8a51e7c6449133733a1cfad001edece23ccf47aaca96e3603fd82d3d8fda1b96de713b51b6a580e4a45d8d69242b6f69bf98c2b6a88461b8ccc004c8416be8976fc86c97176b91f392449c14129bee1fe00beea51877230e1aeaeafa6340db75114fc6232b0165e2305f969364ffe1ac0c14b96f70428c8e6c9157a8afc3df471aeca9812cc9299d5dd91941357d36a5995631b81d2726b8d45747b20f9cb5f8d8d06df4fcf619d8dc0789f79af7b877fbc9b12867c722216e0d7f95a638939cdd4066bac305405aaedda7885f8706af0293dc48222720939edcda7e0357ffe7a24f899ea8d362af78bfeb1de9dedd66c676ffa527e7278a91b78dc2c6c1638dad6923391850071f2ea7e2f38a76a8b958be3db6e6372713210874e29f9119456b8e2e47107675eb1f6508d95844466d8b35677005d723600d1210d97a7c7640a5608c847a7c1786c40c8b76c684cdce1747ab4af69683554e03f9a40ec7182a29cc95a803fafae2056d5127d846fa46f6f660d8c8272a78dea767a65883913c1a76cca4ce89b0073be8a764c8ad41a1f53e11aec09724490f2588e1b4cbf6bdaad0f59d27a632b173c73d22d99499c09315621df7e57dd204190ecc6f28336bdb2eba63a77b7ee138f8ffd034574c50e6b80c9cf3f3dc24c0aee0f00fbcf0c8163cd6686cee00786ecba042d2d835cffaa9450fb0272ff99478b7957072c0a8cf4fabeb5ca772a7756c183c121746f01f3b8450cd37200ef042f8392e91b616d0eb1e422b83d81627719f75529f3331415bbaa29b572be4dfd24e09137844d10e6194b05c2e411fd4add68e991751458f90b65ba4c72e50c7af1947418b83a02ef62d1723b63ca2144956adfe2c2e77ee02eecd76a72268e46390c1a39373ef23e3d82b0ca3f7c9eb6640ab6d288f5e55b13cc148c7273ce9b8fd3f3c032e8f434b69c3edd5c30b258a921303425020fa7f9d086257262e471b60e4f8bceea90a4890b3081b86c1020dc702c1ced9ffb6b43e8863c72f561c790a56676f0acf6cd825b70441fc319f20689c454cf08272181ccc45872ace30f3202649e85b6c683e23bfd3d880cf64828bff02551c5824d9b4619aa7245c2eb39823ecc138cd5a68b3d99c6faeb130c88979bba0a198b7c25b200237275f2d16f641385129673922cde59ef10b25ef43df7b3a804e871727fe6f6777278f41b1ec40636081016e72f9a501a19500e0ccb615057be824f8e820b2023720818d4a60ca6e3d6dc9ec5611701db5dcaa98ec3a85e7f179d6f4e5a09bfbc3a39f765a525e97067e49b646bfa26f876ab11568c777bf91694dbb5c8d259bb722bfbf1455f3e26088908055e570c8f664a2624ae34087247506e85d591f77a72ceb2249b06f3d51a8b295682b02eb143fe1a3113b6ff107cd58ae1aa4305d3720bb6b3c7110149022496f22b086707ce0ebdfd3bd1d6c4b66741a95617426c0c9c941aedd52955c4c78e7ad1fe1b4f7d8bd581d930ee2a372584505b9017361774ab5e1e925359c165244e3a242c61ca657338f81f824ab8b3650486768c662081401218891586889528221686073d0e4ecba6ccfa3fe921a48be2786989e67260a0ae97534556fc82fc1cfdd60301bba95c3864239ee0b6db2099f2ef85cf608799f6588147b6aa74b63186914c73ec2fa57ddbd9573b19ce75d724e7a1827231eedd16789297f0abafe83e450abe2c983674d51954375024f5f0753098740a61795ca13dd27322888ab19c1d35747bb46c271b714aeaafe613ef59d487f11a26344aedcff5d5f06c0a374c812dec60d7a8d8e6c82688cfb9648b57fe6acd4bdc052fb974c29a648cbb6a09dfac94ec4662ca278269ee8929b31dca0d3d30329fe398569e2416262e27942408550def2b82d53298ee536c6117c6fdc8b9d072671b139f95d574c0df22c4dc11a8f4a6540b1761198885caa01a06e39cfb4c72010ad9290c3983e9c27ba52c159963e407bfb6f01d339b3213f72510ac182772ea58e156d8ac9f538334f789873121346b8d9d85babf0af9c60d2210a6298e72092f0996ef222a4dbb48d5488756beb0a53cfea4074fb2b11f1bd0b7fb0b9a7237de66be7f4ed97faa2ea79b5c89c137c4081f915e00f0f422ab122a21314672903b080ae98d52a76658c4d6465ba026036d3545a5f5d3cc866bdd22c0ca9e161ee2d6f26491e042e810fd964496e7baddb80d1f85a49ca92e1183ea01f197727c0924ca32535cf20ea5564db7c924da0e2999f25af3502196045713cdd8a072e2618273ff31b594c1da5b25fbf26697f1046a16848d1feb2210de5771411968b35d54dd1980bf31507fca11bfa63c15ae750bac1e9fda237124a3a712e75e72695cb8e1cfc286ebb33fe35a8b1b552c683a488ced8ce759f56057d71a05d8723f112d8a15708ea9204cad307143bfba82990c743ea7cf8e7b364be3252839725d2226f86fc0132bd1176026975834cc3106e112fdae8cfe6a396a2341777b22613947d5df27f65554ceca82abc1f0e515a32e854edefd4512c302fa326a6972771e01c414dc18c6920b5c544a19d64b1815e36be3131be05afaca913c13ed3bcf4b31321496b7eda3f8d080c06c6d252c15c65db76fa588f20defb3df11357294f87a1e415c06ff526a99b31d1e74228ad0a251deb6a8cd164a343db0e0900d256ff6d901ad1a550847abbd683c8c6b47567553af3929b7a3329321418bc5725c9a877b8b75e3b7e6e37a40092430b71f65d8235bd1d7e324de224132e63772fbeced0978b3037a99ed7f6a768bd628f364b0cbe62c533dda6e8b2d19186a720f13d898dbfa9fe2533aad8921e79f0c1c3f4682f6dbbad20e97964231774772ddfee1ca3c1509453bd4ea478a50ae3acd90ac97bcb6373d3e502a0edf8e441e44a64de5e2a4eed8f4752cc37c7bd7a0a947eb093a4fe6336903e58a0836fe729d198de95883c7402b7d7589e8f425e528d5794bf7c980b729540b27eee11b72df5b07ce5f5b2cca0f48264efb5bd794c58cbf104dad3582218e28db7f641b72a9da3f8c598ee89e22d0badc4548667bed487cba36db3d57ce7b179de92f73584d0d315de9c7e1555d90e88a2d3c5a8eb6497c4e4a3a2611ba0167e230e06834644da17879e2ebe54d33b20b34551cd21ead835b8f748f739924141687ef7859f2e31275bf49833b71a9de39fc83f238220125949a22f6c48143804fe20a72129763e6da4d7193c4aac24bd481d8f74f72f9b56f72da8004848519103caa15721e0eb9cd2bd7fa23beaac08d17e347b6d1b45475a91750fc286794224e1aa07254afb43ec0a9dfc8c9ea7c6221a044fe1f41111c00b836cae25336a7dde64072f989f415dc87ea2c1094f1b4cb82e37b95b29877c83cfb0c47e672532217333a063bc797da0fc0a7bedeaee5edee4025d30eb352a375134f734f8cfdd3844d724c19e4dbd6b170c38e8bc60062aa11764f10f082eee823f45fc32b92ab3787457fd806aed1d7454e7a3be53f02e15b9d8c96dd0bc7490eee8a0cfc6cf9fa4772e5f112b6ebf23817e25ba6aa49fd2efea4112e7c16acf44e3eb6f6e7f1d1657266f8e3a88304ad95f27ffb8062972fcab9efdedba0b1d042c23e724a49b191727f4c1ab6d6b71290513cc92a9035744e80a53eb8c4d35883bf2b23132b7ed044ace4ba325d17e63a8545c99d26cf91947252799de8e2639ea0773a60eb427872e5ee81c833e802ff2add7b1191ba07d0b11e7d46692152fd795992e7f5e8fd04bbd48efc43bd87ca83ccea517c44db73defb8ba43d2600876d1086344ab8df5bcbf4ca035c45487e9c7314a8f524182481dd2941839528c9841a2b76f60f5817aeb98621b52616d49bcd4b773c920c9a14804ef3b1efb537353928c4527fe622b95a4840ec3c0c536afdfe66c2e59f0f2e9f028d85fbdeefa1abb4185c822072f90d8d15d057f059e18423892416f494a5f8b4b9893386781f4e45237cb1903c8eeed1ba4e59c7224a474a2dd303682b372e7d3b429e29151ad01705ccc4c472f44d54ae948f54bec2078b4e8fd2dff16fd6087c68d29059ac19dea183cea172dfa1bb2778566ad6e641d4003a08dec5ba502b7b6ce0dfb74a92c101983c055b28e18b36eefa97e4704ed680b3485ae570f3d5b2a6b45ae7448558c6a35c7f72a2aef51bca8bc22178539317527fd51cf5c45e0464c09b5e77777a310d681972f5ca225933b69a0bdea99082760eac693c87ca346ce0129e5ae5e497aba6cd729ec5900a88c76a1e2b29a34817d6e7211ef5ce15942427fc8fef36c567c5ee72a0c15cc67ede970fe9e651fdfd122004ee377880ba7b22722cb0c2471f37ce72c05985bf5f13721eaf7c060a714d71bf4c0da203b6e597123143d0fbe4f0665c64e13907a3dbb68323cb60cbfb2260b6b5709830e8de2582bff71f1e05ef2172fa1cda9c6d5dde7a0ff4aae5c26c44c769109a3a129c2514b5cfd4fc4b68d972ba25ca4bf1aa8b77fa8bb08320c0c471e6f998a1055598206476b758bed61072a8e265b700fb471075b61b587365350ec12bfb8362a80103508adb0362aa0b2e834b772dc1fe9fee5823e9ab94a3307d8720bfa98c5cc55425a73b8c0db62f56fe7bcaa9a3e7cfb1b1c6ff3b27574ae11fd814e489683e88d044dae073650d72cce416824372401bfd6728a89535c732530f2b17dca89519e0574c0342b10628cbbeee77c54840efa6fae683afe75f44f55192b2e5e0eead0c53efd084394772cc06f0c33c22dbb648bc00de565a154564e789c68e9eb6d62a9c7834b01cda3720a2b26191aa66fc58e9561549c789fad8501a81522cb8bca8f2a6f1134ed95d1b2d0b5a341c6ca35bf4817121de886ec19a8767b543b642cfa0436ca3e26150904b6028c708fa1f96bdfacb8e812f3383da8a9345b3a08ea8d243116d2d2c72a8c012567c7b4ab505c8222f55c986e2305a75aa5c24959eb7af881257909f249e5a00d1b035923455ee30db892353098b8efb3a43c05fb5a85d87ab55aef821aef3897d82d2746a3dd0500f2a6d8045858b073584d825455106c3dbc378982f78a7867708c70a9753d9a06b31211cb9c6b08e31015cb36b58ef07729fd6ce7205a9507195289bdd7212d1daa65677bc0ded5255770f0006bb0de0f302b30772f7f66604cc3c151bc64be612214e5670376e2a61f021801c14aff4f22dc32372e24f1cbe32f04cb1162bc1303093dc23d9b12f21f59f851912d19de3f4a55d7246af0bf74ff180c61886e9d0d6d433e42ab8c6b74955a74a68cf8a90b904d672b9e55ff1cec66895e963d496ef23c68d81e571cb66a19b5962246a70d86c1e60137e71e42579bc1f57790c730d8134e3193d1116c529fc9ffe66b5e39635477274b0f78270159df9ac28e11e6904ad1b66d54f0be7c955efa4b1881540ea1e56dc7e523e8e1c40fa66636acf1382695a979318fac582a2228c29f207f0acb5519e39bc65b8430281433f95743e63e235ed77fab9f8e7ad4c11f560c331a94172b9d840eccd9e9f73bcafafcfeeff2ac308b99ad035166586b17d125dd8c1187210ca5603a2ff634742fbca8eb5679242c58f822a51ed46454943f7f5cfd4e072b0d258d2de99d779c74c895ab67d03a60d64e6c399992747496d9df8ae99954eb128315d709e278a420d6b745385903f964262321b0088bca7284ffdbe28643e897f33ed9ef8bf699dc4272dddd18594360b8f9703cafa91a7fc5f56ec09431ad7ded3022a57da6a7b2605ab8914cf9f6569b87fd9b491974bd1cb483f0b557256c25ab4f40fb4c559109472880e40070a1035772553576bdd0df9cbf8e51e7231d46090764ee291adeea60eb9158824e9fa8eab61fd7e01ee7269ce17509d72eb5857002f4d74555d844f1dcb188878ea9bdbe373683f696f34b1a1ea7b6d3693bf2d2ca8a12024325890f406b4af865db70d4d8dfd4bce4707c5a951375a72c7b0af9122989a6e13b092b052240cfd5fbb573aca008fb46b528c7f57c7ba72bb9db0a19bf07c03b09fb8ccc510c0906e544cc53a21ce10577361914fa4d31fe4458cc000b84e1eafc54b10bda33c2b7b42d905d66038845a6ae4f3422601726904323d64fd9e9a72d53463638ff60491f9f6e130205b35989a364dbf58245b7e85bd270d2f8e91997acc6ebc92ce5416f73d1fbe9ffa348852a7824407902c915c1cb7425136cab9368be011ab2b1084c7ecc28ddbdb1ae6cbe9155131657209e097de21782738be03ef11ce44cd84df5331958b8111b011921b9971f48372c598783b7f9891548df866bbba1ac879c3343d70708da66412bd67c6c993071ad4dd17dd3647336f791d5dcce22ea487201c65cf8e7abaa2ab77a60ca1f26472fd463f2d5a1fd439cf57f549e23a0fc466ac7c1600725c4a83d1e07896258a728bee71fc592a06f53cc00af96d3104be2d95aaac3574ace71bdf32b804321a073570eb1c9c03fad08f6d650927fd2f55048299cd7c653da1997f7e696696657208c813caf4cf7c753bed4d10212845b018e82e1aeb05b247623fec4f83da157289af9359b68c2f202b2a66028a7e810df91fc6a46d3d2bace312412d7c5bc9726dadb40aeb25c2919243aae88483619dfd53e9971656e0004005120079c4ab727cce26e7216a305e465374e729830bc0f7f0c5ccc5607601d557ec49142cea72481110ec8964c2916c73eaa8b0dbca2cbdaa34c9a2932f353a14ebc0dc29890a19e1a4c313743259a3d4511de5baeea6dc9e65fe6cf7c921f97d56356623b172af172dd0ae4885b0df31f7fb15663e0d16e731a01243e190749ea513444f2e72422aa54ad81d212b6d857c237633ce771720ac6b302a3b8a0df58ac45e336e72053b2f5e84354d7372629c462ce55d2e69a645d6d481e97fa0b4b22c59000e3ee2a892741549598ecc76696c755a1a60a1d72704c932ff0d6df247b2586f80728c29c3b9e9ebb79a278768981ee38c49e35a2c4ae5d4f4c89cdcdac8ceeb3b6e3373c0bab9b3b3ef448396d11d47a606ab84c9f37da06371ca4486e89405a00224868923d96d25ca0792a5577fbeeb40616849c8e49afdd7d1dc7742315582720bbd426ba52f011d9749fac35e156d70730b74d39a065ef27bbb9a958df2602f454cc0d6516e4893ef5cd8fb9f00cfffe55666e09cb179fd3db16f6b07508f72f672e0703c6c1eedf246e71661d62ff27b2971a1d09d739ac7d8a226124fe37276fc56e85ee5e2dba9cb768169e7f5896d30c1e933370e06e345296a3f527a1092ba55d14043e107133bcf6d4028a3258be8f122789c8c64c455b86e4607f872f6e1d88f1f9748a00f7a95bcf7393f629c2a1a4c386c1d3af44ccfe8f1227872d82cf191c4b351c3bde7ffbf73e36e73ee1a8908fffe12b952fee50af3bb9972bdb85960a0e76e99b5ece49eb4109b791b7de696fbe12004d7f9b1373f257b72c4fde6f0bbdda33aba50d2bc78d14b522df727ba349f6eb72839793fe000183530cb0b871934976053c34a34375f4b29bc7851ad1c7110295cf7a165b392847267db0f03d0eb9d49be8a9976f07c376206aac4ed589c04f278f98e4da3252650d2134386ca02fd52d3051bff6a7163a4816bc5b7144c78f5ad1ffbf5eb97602c60bdb4426209509c854f821cb376ebca35649e210562361774b7bc812cf44908f2ce186fa8f265ede3e251fd81b7b43e6850b792f74aea89394a08422431621d1fcf84e4566651c59046a22822ca5c06c66df06a1916c6bbaeb10b342c7144720ec90f61c4ea251746d43fbc4322b79a25da94ee3b7860e7381cc19883a9747296895858c5af095e2d5f50b4c94ba8bf09a0f48bc384a04fe8256b919974f249b0e7bffe993abda6bfb94024501dbfcd4e60509b33c8ffa94c8861e0c8c4c9726276b10012f816e4f4da899b18a0a7284177230c7d852492065967bdd1fe9f729439b25bb846b966c8da5f653d679d10616c79ea6be8e3b482c8346b54745d385d813d8fc418031cface3c367615b241e46f31563367a5ac749b1ae5176e5d72e4aff2870def53349d3af56317a40f0b2efd4da73148c3fbfc9be8cdf7580f7202eb8158542abf2dffb25174172bcbb76c63d32aa2e94f54d39b77dee352762855b194e9c575d9926822cc50f24cc9c591986699ce408cf252b4db74aeed27726f92941138789bbf68e3afac63baafc84cadf21c169a6386bbce36b394efab423134e3a9d878e4936622698b2c6c29c265175bc66297c6aecbe42b0475b5a727e5e6a90af5293278fc249762c1326a430c9f81956aa5a9fcb9f60df898ac98721cac7a13b8ede352b6f1a76d33d3c511fcd54a9cdd41517e88200505c3121c33d2d7c6cec577ab22d3d8cd71109c455d0be156624e8d5058b6556d7283d8407231d1516d3d180ba540739c4e749e124851145d9782f6d3670dc52098035666348c09b72859bcf7e8c4079e417cd39daeb0e3e6335eda499965e203025a032272e54df493b4109e925f3b4501be1f57808a3e657a1b72ebdc6fd24ff04dd3982d25cfb3682213f89e5160df797e709cfce4fe4cd0c4ec25cd71af9e3d8c3d690965d8d5b1532a85765fe90c628165c8e87a06b3c4ec7a59207d232b0c93692472b8116aae1a3063ba7bdf75e6d734b4d0b2974a1234bce71690fa903ba24e87720d6dec083a2d07ab7bc533e8fb795c27ce0e0d6bbfb61368d5bb26645d73206c04391fe5f31455837201ce88bd59a7551a3c9fdd105ca4d206a3fa28e5522872e28eeeb7442d7b9fe011ba4f2e9ef15358b20ddc3d816bf24d48c8090809066c0745726f6be1c5bef38941dacfe20e6e0a9df8f376d38950cab98b2f1267e672531bd957b74535ab5486615e1b5cfcde13a3bf1da967367f5488f7dd7bdbb03f2fe4cd5fad98d47345d28744439cdd9d2a3756e1ad37c66a44250271b335716252c1f9e6eb60a456cf9fb7c4133c2a1f739a6340792fa765fb99fa5fa769875bc3ea73fc4398ece6111398f578ffb33f107b827d7505f292b52ae1f3a50b7471c1e8f58b885925cc044d0ff370ffa091e59de2c219f8d6dc0e7ee43d9588e94bf8c33471c2244625ab5c5b2a1c55b4cb77d2a7453be9f17045b0afd4a18a8c72fc4b06bc46cf129fa0d98d5f389b96a262485f97d50652884b988fb4b36a157257dc1b2561c68586fc368ca8df16892c877b9526ce93de9c9affcd2b80b4e0727c8793eece49180a16d914182064bd789f5884af52cd680a5550ff89e453231fe3129631dead9306353125b7d7179e6f39b290089c5618cc5233a3e68b050441b8cdd633ec84ec9390b75c5b282f2b98675f1975e5dcf23106b505f847d47c6d4f969e3283aeabb54ef656a7b9ca84555e83c6a0a05342b664717cba81d1f272045a0249b17f37131203e076eee4f506cd72758c37f6a7a55406c46aaf533472a39b11e2ab9bd89689468ba43b135d9afd19c752d0587805db5a92560ccf9a660c005a5630d241ad8047b55b35332c73d4bcb5681425c9d2838ed552cbe4520779e4bcb1fffd9dd6afe33256f21d70c6d395ef0284039479f9a8f382f801865f320d97fbde43f5590efce687631139ba105f1460d654d12d1c4123fe8ad06c680020ea8d0d7e53c78185971849be41ac137513ed0c1e0e123967f1beebeb4843acb02457971d10ca647de58d3cbc75a464ec4e5e0372650185edd0dd79292f3d4974b8f886f183a91c55a0f9f4592fe9499633e4c1a45430ac4c46e87ff7327246a7f0fed25387e82d11f51cee3cdbbc4dcebdb7b539c0a789c9344872ae2b11dccfed3d94576323714b02978a3e060ce584cb6e19f1d6762cac3fca7e56ea7298a4b196466edadcc18af41847689e6e3712bdb10047b18dd3350f2afecded72681c83cd4cb6dab1cb52511f92bfc1fa950a43bd3207e58c81d89328ccc99b72a0c8f84a38b692017dd32c6aea46931cb4081e9afef875aa860298f7e69e470eac5f0efbca02d3c28150675365d13b6d2a6aafc1d942122ecd84093d910f0472713aa79ce3827e843d1341344ab79db38d2cc2997b0e1e20c0ddf50d0cd87172b3a027f63806a4e52ee3f8f29e10c8005e0269ac90bc18d4e9f35fb837ed0b726326412f96da2462b338c729c041b729ad261b10ee8a6b11c1fa4557a20f2672556d695f41bc6bf6ed20cbeb74861edab87fa172c1c0a5cf33e19faf8cb358727b3a1a56e6e48ca082509da0e7b3e032f989e7cf6baaf9431a0d9c67fc36e372f5f2a6c96c9a3be7961d176ed785c8ec8003f498545ba89a4ebcbd7cc359b343013d239115c0c724e80b20c0783709a00960699ecf921bfe74ab5d301fb85e67eb3acf99abbdcc7cc667e0da9d16ca5d3c690463f9d9e491ec83572540ffb87238d353b9181ee1daedf4df5bed9b484232ce09007a60731beb20a46a9b64fa48c337aa60a93cc9698d32f69bfd8de3ec487d645f8f99a317aee2602bc97baa09a1a20d89dd2ebe3b17d6cea7aa0376df2fa512208ce6a0aa2c72064d70d73972188dd648407abfe2db9a9fb0aa0523d204737b2dab4558be553c31595dbfc57211e68858a972d0ea73fd08d29742b362785f7df696c2c0fe0d7094767cdcf8643032d875070a921335483ba900d6c4b309746fd92a1b9c170ebb07dda0756730b9d65145fa4949482ea9ad3ecee07839deb779d45aa4c02f42f6422ea6b950727979b876cc047193602fb98f67e42e9ba2da451719c4b22c9426cccd1c74040b86b2cf0248ca781404a8993aa1d98a424b474e13debb2b716f041b932d249072c4cfac0a928f8e0544c1d6d4674eb4255e42c88f089336d7ed03250cb121a0727e47bb89161861a6df2e1f6e1fb10aa4240977364da44ef827ff8a187ab69610d163e6da946bac081f4d8b1134b8a2207e37f33b5b84b86472effa4c2803123e2b5eef45d2a92d9b854526fc440d8e678eacd85a34c68eae92c737882936a472c46925f2135768a85cd2b8915fd2a7dbfe69930ccaeec143337a217fd790207271447476a74b13c99cb1fb1d85bc7f804798740da0321f948b2d0f244c344072cbbfd2fe6c515137688568fd4521102349aa4a24782065492d05592405129072e65e813e8b11168ae36fa431c6b0fe1ee455409c42ae65d4ac9af1829dc4757283cc18ec70e31a011488dbd070edda5b292dad2b4499ee31a5f28c8385e4fa7231f144a8be0dc2a9296798917e84e2dbd8c775d19ea38567796f9a96f130084e0ba9767ded3a91717c04ca8d479c76531720bc6f94c813b5ff35e348e1d0dd72121876b74aa9b6005d3eace28ac32cadb55502e55e097ddb1824fe59f3a68e5ea44b167553d06b6a131eb814b993c234dcd1b6975146156d8e64cc3f674a6e720cfdad6484947b799f5b5ecabc8365b9cee4ea746175dab60246c9f1f289996b89e50f82ae9be4826349dd53561fa5fad7f816ad6648a29a69b4a6440a4f5f612c84cc8381d09875ae86fae89fa1c2f793676d3f37b480b2556a1808ac6be4196f43c902a1b8ba4ddb3562c2fb7235adb5a283e321ca30efa4fded3c8f94497239df59723df00549f7c2e33b6d4feacc9376471aa04d13d3b1cf2ffe8ce92c3f421be97ca2507b089d789ce8e7a4ae07584d35ee32c09b2fe2c177a95a573072b8160f075af96e356cbcbe7def21fc1aa32335ac31f340c4440d947280483972e3ca7df0f7b1a4bafce7963a174fca9e5c110b00a4017f35d97aa0d495933e476fa2ddc43cc750e85e903cf97f6cf6cf78d9d244f51ab3a92982ea643ef51e2d769fa2a00d534f59f671e6f0ecadd877f313f0f75dfa80d5002daf0c477d8372a1e4a3ab1912c3f21f8973e53177e0b886f01e21074b2b72c79f5b232d48032cd1ffe20bb5e2da7ce2042d91afcf20f5e93507c0b1bf2a04cdd9cb467465ce2c89e9d5bcc59991dce8622263df9335d360d07a0bbba04c08f6d7501f3bb5713478b2b94b07db4523ca8e0cf1a2abee13c81d71cd8235c65e54ce2747043cd45b6aac3b98ff31de191cab9bade4cfffc6a4b879f37b08898cf7527fbef08d2c30da9be8fc22e2460fd4731a1162713c49cc9b746c3910fcade38506efe8298872597c57211fcf75a070429c2fb61d8f3db6e1fdbacd2b2ea1dbb4a224026a8f720a0173d4473b15c2c92050648c2828405a9492d5e5629a92c354b93a5a824672169b87ad970e8d90d771cbd5eb7436d4cc6b144caae55179873175f5cd7e56554121555b51fe949840e4f4ac4b0097a157d6d83f2f6993ea06f9fd2472ad244306b2a0bddea9b669cfa9ab3454d16275f57537a26aaeffba7a65812b3f2505725c1ba41f32296e9e4fc7da661a3398cdbbb67af129bb9d596d5d92fa5122a67208fc016fd4829cbc8fb9508e2b6f577568e70bfe7ef1f828bdf8c04b9d2cba6c720c01daa21ea94c5067a1f2291a89a5f4f61ee997477f894d3ef27bc9d39172b2aef0c5345b592230e603708b90979861f816fee08534027b3c162fc956d04d43663e06729f93751912b5ef86eaa2b76f2285dc8164818f96fa17b4fa5ffa492dd509e10fcea77af40b5963bb9d36bc5394260693a921ade04983d5f256a272e71e9d0fe1f3d9d98a3e932ef4de13a87967408f6821280eca4c727fc504ac376cc928ed2463d4105e9ff668ed5d2bf99c7bdc2dd41b35c0165ce4a8ee571709dbb93eee1a42595d1f261a72ac8231cc16a8d410c185a7af6916a9c4fb7ecf728adf88f07596aa8940a30acc5b84023ed87ddac2da3f07fc9c368b43bdf6e93219588c31c02b8edd7d985b738eed688ba7774520a4ec3907f745c2f6017be072afd197a38d3df366892005f0b7966e45ba409470ebe87a86dc92c20229702b2fbf7c20ffa42b031b958eeb23c65968fb81091de88930b341b5f53c6e864ce7726e5d2ffb093613b00a0dc1dd1b2cb331cfb9ba63f3d30a7a956fd83ff2bdbc545fef1cbee5b5a894157ef5ad5f42ee2098e40ac29859d42d72a9eb8fd21c224b33dc987e33c9d697ece1bbdf2e277805e9e9d211cf73a4f16e8527fc8e4548314956febb25e28dda4cc3d8b4027c1162340152ae9716d96da4e46fb1c566e9063a50a99f6dde691e39215f4fa43e8c2c19fe7ecd3d4ad6f5b2e3186f0ca854727580c65544eb4c977ae726c831c38c83d36b23509451bf87eac8c1d82235d0722c31e95554d5c3b546665606e83243cbf65395d4eee121b97dc75a4ed6aea5721c5eba990c7af6b31078d2fffd3dbbd2affdae6e3459629b908240ae1c9aa972629011527bcd8a4634732d90122e188cceb2df4fac1cff54c8aa30b36ef150146b912ba448e6fe1e1d3eebe881dc64edcc17b110da16d06d1957f73932658572f081b0ac2a37984a5e4af28e53439c5ac785f637828b66938471ec66152ed61ef27a4097657b11281971a9c1010f63a92268fbc531888ab96d3197bfbcd82872aa4a2071bf321212a421ed8c1ef30745e38534c3c404b77e3e50f9b7c64dd572119d5ef5a7cb05e00348a0b60c63a6827fc266fa12c205eee9d388998585ba724bd43f1125c8b4d6fdd769e5d2a01c0da454f508b33d275cf110feff40d04272e1229cbf77ae798253d5963fb8273460cdef2c3979860e7ad4a6b09f84ad344a3a47e6197c73862b306a35719da9615b6f96d770660dfee0d708c84f983fcc59fb5f17c687e56019f0b9fa039153325adcaf9459b3c5e073b383bbea7e68c63f83c47aa4351d6be805e1d2029bb2fafcf621eda9efc3e49c82ac6785e7b30b514fb6078b2d79ae8edd9ad4d992c9ec9e84cdfcae3b84cb7144484617a7f78c05de5437380b528b6405c10c7736bbd6c88f46b518149fc2573f87353831156272a025c888c885c4e718bb67c5f341f7687c9c6b35636979d1a471ad218b8f2f72730e45a8733d1a0ffa4b99590caf3209afa1d03437def624722fb76ae4c2447246182bfdaa67c83b9c22c60e97672a7e89aec1a723f491dc58c6d70b894974385e0379da569e60cbfe6484836a5f6e69404f7092dc33c1888fd3af2429a0f6722ba59928f70fc305abe14bd4d8f8ef9fbc80d66c37099b8898ddd426d553f872e3df4070a5db8ec8d611e5dff66b259c5c8dcb69a84b0cfcd0ade3ebc709ba4fc457354e7c4328d09e27a06054d6c0d48f5e5daeab82d862b880855c7a4d4c40af99b65a6b5d6ef13bd35ecc672e469caf9aa8b3c62f9027f9e7d67b5410d7729b0d87d9d7ae6348dbbe323955707d100b698182d0a2566f36ef3dc86b35ad5332fee16f27be40ba105a3e30bebf34a1339b1bf0fec855deec0dbbfcbeb1c97215ac361228129a72b346f01a480afca434149e6fdebc2d335abdb2e72b95ba72f35ed054dc812f5b05b3b5645a1c611dd374d7f2c8eebcd965ea03d69a94dc1f23bddd5eeb3d063f05a19766addcaa04c76799e75513ad0a22a04e345693dd670969e18ce3c7e5a9494585d2371e36f664d9ccf596b3c765ddcca565c0cc5e72297d40d5e2424a005954cb2f6f3324a71796a2208bbaa7ff69700f140226027278c9065c9c5b86cc1eff39b5193937973aab950d774eebfd63d4626bd14a3f4a86f83283303bde342e5408a4510b8ec171bced756f5cf2b37c2065dd67b33472d6c5600ed4f5e0305b9c9cb866460581fb00eece55f87653be482b39a180832d2571aae092676b7c26032bd845b54b6f137bf114537b9b78dd3fe9bf2506b7105a8489dcf821c5580cdded447b297d082f7cc41ec87948d72debf055750fef2967bb2cf5891be824e9b8d0499ed40b8fa3ff46d959c9218352b06f50b5a2cf5cfd4900564ed4775e3aa0f2191804a9190a051b1c8aec81b79bf20883172eb75f2d73ec77b7597bc0082423b59b74229ae17f6adaf6e677fe39616e9c99a0847211ab348d23f9fc874bd9c3810a4b0485f5082f71ec5b198c186c784f0ec91972f8e1721af72a49cd39b82ca5a411a5d608289294974bff2fb73c0be85b49f27239d3ff2b4f35db1d43774b28225ca4741ad196ed160ab3641aa32ef1502f06193f2814de3a13b1ae4bfc41437aae8f6307d9dcd51ac97de96616d8670aef99720826f00e1fa006e5848b761c61d359a4f792efbfaf6bf6e6ddfa38c21ee22e389ea471d53ff19c2bbd75b88f0f64cd2b1b8371296d2e38cc120fcf262a97137260c123377a1d78e4a2cdbb8ed80a906277351b69ce874ef664241243ca6910724b50040e31be512d022f83b6153a5670fac9f29bce6caa709b92f8bed16e25335ca2828171cbbbbf1f767c510711fbf40cd5afe7d49871f21de31a45ade58112aad3ba7d6e828a9c3b4b30628f0846b4810d1cb77da26a2f28b6c2b776949837230175ac0ff858cf57cbda7bb51146f342887782b240391aacb04bee874abc4a2ae347b20ff13dcedeb5f82a7e8bb03598a233317d17b67e9e6155f287df5a22fcc256ad11c326d3c0c5fdac912b34a3e9b2cd93eda34cd3a723909212ded736105ec24144b8e679e124b9b699a308b240408da438f8178c15ff1fb36d2ace728450a37f16cefad0c0907376ebdbfb84eb1ceb820e0c6f91bb5c29bdbd12906d9a0a1c56bcc2fa084db95e976f80dc0be94ae1ac645f875c862bc122f1c1885d0593e9cb8d0a8d8d7700493395b2a902da432ae3aeabca892842dd1e5d22ee72aafb84b089c7f4342e2ac6ae9c56246c06516d543aed79de9e3ae96840f1aa72ed1fce51d4e0c931da9bd1fcf4a09fa4907d2f63dd64db4d11707b3f24b948381436e6ec7a62b65d679490242637007c86248fb818f0ca458914f0deb6d7ac49d08e925a14e4148b76f481ece15647d581db64cfa4111921267e8db1620ac86cc319c069219e34127133ff99d6ede20479ad50d0c45f43854b5660354c272172e59fcf3cd170da5e55d735c28a22673b7f98825e618a80b59b9f8d30a57b3a72dfd0190a4240a996a050b07f6b46c9db9750379f22ca22e029dbd6691d4653722735c8ea02dd3f59a6cf44e8758bd733227b1bdb4a63b8107420bc205325bd4933a655705d506d9095c19413ab95d628398100ffd19fc7c51d1414674cc8b47217769d8d3b732e360b3e46274fca4e0a50f9253c5d2364737034d045a5e5f243ac3afca3966ae29df9ebaa3b048811fb3a74f59799a2694ab3a49e627ef89472fd4ef2203bc9db05215020df7d2a85f55774190d44fd98cba35fe6aae16dc61ba709af893c6b0b8b543ed82467d4613d0d026b5ef08fee0486f7b96b787b8953b17373f21a464970a4974774792b39dca587b2c0bb29a920c50a75f2bf069672925d514b855fd26d841d8ecb4fa7487ee0f1a5bd159282283f706113c914e22148f1396067e00ce6dc726396b778fff55394512f2a8ab88506441ec8e2663b40bf75bce17fec172ff3999da0cc3965e50ae0b5b1b79f25d45f15c31248d1a06794a5c603de78872523f92e2520f396837d40cf649ffb1262c17471dc9f5d6c36f9f8c980fbb1fad5e8012a31f4cd7a15f3046ec336817f028c44ec15fa2a301bb5b51545363994c3e2f23ef12998ab6cd51d0b0ec4e633f63f62cc2e95c0ce6b1c058c43d98334c0ffa6ea4baa97b29e6adc8bd8a14dcb3759ddac181183531b318a467b76838b17f681d774c7f61581ddfcbf3c855d1103b9c9b37064ddd340aea092ec28f9cafabeb6aabbe06aebcbf068f303f1b298fb3b976b1164d16f634caba6a646619cafd9bdbd7dd98e849f601d04bb35e60ffc9cda925214027472e003c456eddcb6cac74638e2479c8a2382610875e55a6380b1a45a09b6199e4ac8083cdd9efb197e9d8e8fc58531c61268463b487d8c80d02df4d6c15d1b0b34cbcb6e3977f722ef707a5c07b71f9dc98ed2dc1abe3d99084163de244bfea53eddae67f27cdbe739040d9b56c15a140794b96d5404f28abd1b9d8bc1570a9d726bab57ddd65da05132fd9b3be1b944e4f93e040b5b16374ffaf111dd58fed865b8242d1d399c63dcad7b3117aa24ebfae5476d295ccdd886e0c0ac7377ae091ed62e2c8e6bdfeea5be484d103353504fe0152e51f0d185300c7596ad4b84b672d6716f8dbc12f92323414918f34c92e682c9c19d5437384af54c6353e1e1ac1bc0192ac3eaf9bc9caced26cdd9f1b6ea9d2a67aea96b91a14e4c2fd597df357282f13a6f4cccff3e73acc109f547562c938c282b276b5c38da34c274659b9172a274fbcc025bb6ed7dd49b8d6470202f937e4a8e72e0abcc9dfe1029859f9972b96df487b7c3ef52b74bc8fa4b41b5292f23ae820e44761538014a526b87001e1aa3d4f4668269f19d302881246cf37a1a1c64122b73ff67b5115d4d4ea5f854595e1dab16017e0e34e89044a1964d17e2f37a6f6c322ae700d4677ea22d4772f0326f397ea4690be4a85045eee101eb57e17dcc1508dd2a6173a41f4213be72e8cd309d03706749f3b0d4187f00dc41b929882ef3ccd24b404eb062793b0372d90d18761fa5b7b5cb5c500811ba8333e6f2dfb383797194d3c75e679ba8e872a78f0c21d31960d1100f258e335922911276f0656f9b9334843071b3bce11926628b192a4b3eb7a33521a701ff627cffc6a3de43b2d089c361acb43030deb3726b7ea2986f031ccb086a32b6ba8b398ba7c9c495df463b3786041a7d34dc287216cf751cff5e56c14778b2ed99fe794a79c4bafdf021a9db5bef71c92a5bf60d63d12ba59aaa83cd37c581ebf192f808cb76ce0ecee9c5938d407ecad40f27085b87db50b9d7f627582a32113cc96b92d37ba46fd06cff92646085854eebfc727dc2786df7b0acd589d259d05436c078bc561fdb711fa9adf180c6b01f62e2074efec71b0fb0b7b2898cd41249db5adaebb88b0b680feb261556c4e1807fe072063da04f4e872a9028531806afb554466de7f8ae9d932275aa02e1b561b0f8725b99e2e4b542f1779ffd502e72c51246da13996020063477a69c0b101ad4d65eb5d37fbc79a023c367629561b4b2666832a67e11767a9af7eb22f5ca00379672b244181ee294a406b15b99620b3e3eac26fded9adeff18ffc09b81d72b35c672f453213a7e3ef6df76646019c541a5f5f0c4f28ade91298c2f7a20fb93de5c72b28aa3b14e30710cab9c11ad961219aa042a2a52ecbbbf603492e9d648ab4872f9254272ff90d03be2420b56bfe9dae07575b6ad2dd788e00071dcce70034125992c9b6c8f57515423d76f6cc5ef31e5c6023599a87a579bac46012eed00e23e631a6a2a59e15c89467c2b1e2fd8c76e9e68fb1b39a0e4584389caf48e6b56729c1991d7345e69e00aeb6a41d0ea4e777aadb379378418c52ccb066b399e317265f97b7e88394f41b1cb97f1e70803ee7328391fa9256f8422324ea9e6f8a872f9309958f4f20a7af1a51f591c17afc26fc07d5fcb54231270b311313877ff08a578b727be41023ef5f79bd7805e24666f8b1bef9284a1cb4e7972949ce2cd3c571f7cdc0ff54393b2877fe10a4bcef127fb035cff3def8c2141bf9cfca2a7723492146c72c82c39d3ceadf15272ffa74cd0693002f5986c598278ae6241997252f5f3ea1e709c863e3ad457bcc2d729090572b787bf84a2c3d59bcdf4d28c725dfab185df5ed27d11e2f8f6520241ff8bddbeacf82488597b7600fe362e2372c21bbadc4996254069251c1f8d5c3ae7db6ad6c15a181aed31a83dc0268ba072bc270f9a8a350275e3009f2459bca3e1f7bdb3f1bfe271ac24049f8e03980c72164d813ca7cda8480a9f9a883e0f203accfbe922db24b1dee9c0345ca9b1be729d5cf6c3bfb8e86074ad435e40c471122f07d3a86ef0bd19c2678ce2f8b9db72d2bbb371d01e8e92bdf039a3a3ab76d6c489362336a460932f609c0f9d49f55244425b83e7b8f947dc365a704b62eba7308d19576265f1235f3af7e3173ad641aa7399b11ea824fddf51e3fab54e0dcefbb42f47f4fc79390177a12cd149287286fdb303991d004ccbceb837ca5d9f8c317135355817a6549ac4a41dd58b1d49ced02a9707de06f8dfdd1f03f14b5d1f4aedda65818497e5d4a0ba938a2a8a72055fba18302f577633ee90aa4bfb62b69c67866bc29b7ec8db377b23e018aa37753178a9cf86d5c035d90c708eac234a61f523ee808a57fafb4a7de77f450c729cb65a0184052ea1e54e76cfb34cb32dba30320bc74372014116367b63943872a81bdfddf670632ac7a7594ae343bcbd1a74653c252c837a8e4b3bdff2613d72bcc38ff59cdd70ec9771cf7ba956752ace614d051a4d1b97674b307bd862cd3092f55f7973efecc927e8a69fc9a76b55b946abbe5ba69356cc13d2cf4c48dc56ff1421898a918a967bccf1c7b33b79b8d2cdabef3ee55d0ea16d46cff84c1c7212280250a412bc42847b9ab879b4678276b1e3e8a4499e4532d0ff485abe8372e72187d399230fedb09ae83d47966cb6db829344dab783e9438f4960ed932172dc3c93609a9c4cecfa516add0ffe32b40c6b3622dfe44c9d980c5ff01fe1d172fac5585267fab2e32a7f9679c7b0cb8e780e577c488e2e0a58a2f074ce0fbb7237bb4aa9fc4448c001aca5bf183f8e7b53fe5f1706ddb1c873da968654f49323ab30baab40ca0845a8ba93351c6a85c823dc1cc062a0f1cd371298bbeb53f42eb1ec20ca72fd31c5ad4ec85a8e60d7e4aaede4f5e186bf63df9d34125d59b30034e59588400d3a3420c56a7b59ee6e91339c1b7c543a3f313ddf06d9827f404dc8e80ceea4f8e178f93ad674f0b2a191adbff9134380cbce54b0d8f206dec772259372bc63c07757365f40d1c323a88e5ba0c546b14dd6305d75013a6d6bb272ff3b9d5bb4b36244a2001d4809a5fd9cfb9ded5aa34ba412cb513909a339317223a98299ada608a120e5da75423a0bf54a3682d5c028d90bc72ac831c72b97627b98f601158bc5fc7e380b81cbfa4bbaf8e7301687ff9cf0709eb82eb684796ff010abb5d91a70ac153f312012deead21754635cc1371319192f4d934f53e972442aefcbfa191b7cefbd60d9ceff74c8f258aad6444ce4a947c6d5cafbf8537287d1eb6d2c6ada29f58d1bfce9ff4bf34d399c9b020cac742d5e3af4b3533f160d2dd0878ca61ddb1612be7a88ba5e5483c79f8cfcc688cbab6305e53a2fb572b8cff5d074940c76f5d3e2a47d645ecf5bc3c1272180c2fda3cda944a84aa948f93c72946cb587866c0d64ff9363a093de984db656ec5d4b95635b3bda2be5181f49ed1b45b581719014de4bb7c709afd3b2a24eb39361013f353776bc773f3ae210c3a2fef7d1ce028111b1db1bc56d4fc01f8cb520c94e4405a8e9c6b1457272ad3e133be35526f4acb3328691c480d93bd5fdb9f97cb7d2f71e4c0261be721bd081335b85bffa5e1402edab8124f7e2a4287ea6e29e33ee2f99bff3053172a5f80bdfac887e3b153088ed2aba12afcd6f16f6fee714c85543753f45cb83725ff0fe1b28da8aa0123c0f9de2cb04f19c60459f8a7cc185091bb7f5ca86e9721d356e634caf81db46e03ac1e4cd1fbcfe091d1275892c7d83a0cc99faaa3a7294057b64b897e827687493ee074086582314273342689927ca190134b6167b72da95d67296a54455dfe3dab23dd6905e882df9f72a564433401611695023e672079031c6f8a8554a67e99338907cc5cdeb8fbd22b026a47d600aa705d92d8572d3d7a62ef48a0b63b0db5551ac8529fe1bf4a6d43f1e1e9347503953510d69723b0fb555ce35e014e0a744b08b6b693b4e93392508e2e05bb739e55317f35653283dfef1543cee777d0a5a2aef7767c32684140ddcc6b1b97cada7df6aa0067240d42cdae8d59d64ddbad7a2ec4006c36f6d962cff99f0c8271926ba308a2272451aad5309907ffab8a66b1f817b4aeb1e503c2e85d50723916dd0eda5d44f72e95b9cfdae2d97db8010ecc267a25179df1aafa19423fbb214a109fd1889592bc64f9d6507f7343b40f55fe67f03765ac00f49893a8432bb2ab1e56a45daa9161e7b58a1eff2b90e9189cf2cedffba9a4743eabd32293c12dc608cbe7ab3df44f3c94de88cd540e96cfd9e390c6192ece1323433d9ae92ca246ec27991d058721a61cf83023fba95f64be5ef72ea5032d45c9cee700089f1e60907162729a73163708f6db06e37c5a85e2aa135cd9ab54080cacb570e59ef78d4169714b08e3ab496902bc5079d00b69f39a7fceffe63a3ddb2dc6723f83d3d9978b17fcb0772e893ae17563c9943895c54bfa344db8ad15b9d83c6d8c3db0c6d2ee203eb6f5a4dc846fad3d7a165f58115cde60285d91530ca5a6aaa36f0379475b10a4f7807b7648f3a64c9d03f6e067b371087425d7369597b6be238dd4e1a7068f919d51e12c7eda8865032cccf5d424afcdcaefbe45bd6e444c5784778feea40f6975106b8743a3195cdeacc0564ead5cabd472073dfb8c58710adf5514ea5926c0b96725f908f57e54251e18b53bf9de6fd911e113a4b996b53bcdc551d97619b28387252b0e9a5a430afd2c0294105ae9101ecbf3ddd8fe4682021dbb935381e7ba007afba30bcd2b8bb61b17e36daa4c643dadb3d4a8d758ab3028eb6de5694f603295312121c173e5fbb517d7756b8ec1080d4e28d32f63fb140fdc536d4bb83d67210f795ccd2d0f22f002d4171dbc5001f927ea0614b5ae5c54bd688877b29a20d79b587d132b3f48582ec29e5b0030177358ede1548d7e8d28ad6a8dfe28d7c721b3b2f93d8f592ecfb805f35cc38bcbd18a1bc50ef39b173b7214dc7dc85f772cda9ab171ae7d152d3e4ffb86af8c04adb7a57bae1e8361b3321fd1ae5951872ce06ed9846ff3b9d3c72421eaaf30ded268cb56a6253556777147c9ae2013c217b34a316fc74d6cc12182bc0a6d5c82522b53ebdff54fa9b15e845c9cc5d0172cdc768e1feffad81e41acb32fe615146a5dc1b92f4a0a40fb14a3e37392051725bff6984a52fd17127c6568fe841d7d0a326081e1df2480abb665b50a719722ab1c05d41fb90a200fd124d72b73dd7e138f6835d65536a36ef7d69079498b0087762caa25007ed6f601bc4b88834578aa5286663cac044cc3a3b97ffc1b6701497875799b0464310a406557bff9a8e2cbcfbc7a06ff0af20630115efe2c5d64057c756715c7ae9bac469beb567241d428adf63510a539a80858516a94189b66e1275f1100251a78c41fbe5cbf03457f68d522d82c2039830c26d510009b45472b60bf3110a91b6c6265637cd885c11ca6d45937fc7201a589f50e92780a5f172a6f1c90329a788ad83175490912e5d5d8bac3895e820f02435837e3320097f7232c3a2abb3a4aece7b9c1c2b15e91116ac47cee65e4e525ef8d22b999e7f2b727ec9dd2a8c77b3417241c42e34aa2e535eef149acfc6e0fff60813d277301372c1c31d0dd01c515db6cce64ef684b93388182ec390d1b4aec92636c8bce3555cb646760b2e82c10d994ce1c75b7ad3fa0d6b2eae882140698c191f1652c22c6c597621bc87431b97e35e01d53a9b59fa969f1e9fcc641aceb8ca9e85d74bf4720e2caf6456ef1a819579462b56b0ac51e085348e81a76e54c5ca6b450e9cd272e14924371519591560e7e843145a77c4e254dc055fe23670bdbe765769e7b4720a9fb4b0f9c7763b4e06cf571fe6cd283df40f413fb4c1ba9c502afc9300fd72b337c8b63064da977e4c0a13aa0f16a467f47376e1d62edede0c2a067612a272d1682e73ecd0633b7a03501f41b3c788b830a44c296e243417ce4ad699ab707224b3e85f05baf939cb9f44e8584ad10e7914b29ba4ccd842caba6b9e4182c5724b3a25337cec125429260344cf82bd93bbfaf952be8da8812e61991d89738e7242a10d3fa8ea63d016bb8dd4d5f8638daac9cb44bca4cebd2f03f5a73c6c5f724ef22bf04aceb27741d240c52d7a83e12d1f882beecc77b84b5344177ccc473416ca47261f1de6fe372fdd96b68ff1d7134518903b90e395fa9cd1991d58e05d7f36c8010a6f4be404ae859f3ceea6a0797541f56bb1fce520471a1b48dd2872500629c07c96b468ac04e936cf2be01eccb154284d33593d0b4b9adaadc98e0e8213979ea91102fd20345908c08b6be26af0a9c8b96f68a7bf92d0cebd5a2f72772bf9dc49a2a842f102390c9caef805283fa260ed88cfb0d0d0b3e7e35414270aea13ebeccde9d244a1d1b0ff3e4b1969523903b761146a9e3574fc380c8b3e28b3967cf03d0f715f2bd30a97b69054dfa8a854be3f26a18b50026540c4a47240a2b02781ea3c9963f1aa7b31cea6835c7bf157dec1472f349b84fd0ef80154900c80a6af31c523f09014e33486c88cd4b7c511b675fdebec55a0462df3a072dbba18693922ff14b2afe49765ea51b4da03a233c45cd7ab47e8dd4904b629724c4646feb15dc3f7be0b896a6c669a1e5085d1798b3dbb84c2601f4dc2f6c82922a96888cc4a7876465d647f19c8c1e0d85dc970c134483068ef93941a6d3535d866a38960d5b2b8b6825ed4949c6388447a0602df82ca1cd3d9cbe15e108d67695a585ba507113a018aa2f9f0a1478802e94eb9153d7baf3b3b177b3e5c7626c4fe18a3b53e91a5712bc36ebf40f419d94af763de637fe1518afded4faf32727d1719425918ef4a1708da1fc123dd9cc6cab45c2ef756398219cfee319fb64a3b83c6530e6e998e7ea0a0a9b67cc67960f6c4d6655caa38953418a3a3e0c6729ba4dedbfb1283b39996eeceef6d05740bc1a6c7cec4a074cc9f53f15f124072445c9394c332554eef196b8b8086bcb062004d0420cf4a383bf412b20c7a821d6de522d05c79fed597313e10db2c0508dbbf81c64a264c219ad6744d0e866f72355ebb33de90c9b862d320c40bf991bad091accad428ec92e6ccaf4ad153536d9da5ba8c4e510dd3fef992c73b71700362a36336302c1168265ec864c506591dd6f6081016dec2e925e6ba6f6924a15cc398214245c322e3f3fdd394a131c73042ec911d49bf3a1f11062b838fd5c48e4a8cb50ff8b7f89ae5d192c857d661721b6298d1b8cd5e416b1b84e4a786efe9d9f98010d5108d1d4743747173cf1816394471042a97e5e30986e27f74eaf943e40b226f5d3975b645da73c411487b0d6cdc806674f43b38dccf0c365188ea46d4049f33920f4bc4973596252f0cc272cdb8b67813284efaf54655b6c5e5d57080fa900037d204ea3bc2035a94731c421b757e7fc0be9806c0f66692859da61c940c0563bb655fdc4bbe7165b3484d1cd8a8cd919d30bb945005011e3b5244e7008b41995c44e2a915281070b39ce54fa7a3f2d94748d06e2e2456ce15b815a1f52b7b36bf3f981de1d5ddaf1c454d6d4b20a7d7cab0494a6e402e9d9dcb668944ab50b243929bd5c5c3c45dd45bd915e3939c03e9554a526a6fa39933ce6643a838346aaddcd314db1c5b073eb58c31e8a238e63b18ca73d97a5b31448ce8f7cae947f080ae0a2477a820a1c45d74555dd33237e79a0d58c0483ce5c17b44c261094ad7fdd6d1c0541686f7b4fba17269f3c3b29e56d1fa00f0f38ee405b015b31df7d92fff93461070c0c95192390a615d529667fbfe74dc5229f79f3abdb6c2213cd835db38dc415fc2b9c02be1502c0e9c89a02e2ecfb9d5f7a7568665a84d5274b7b3fefdbc3d04431b44b60901333ab28282a7565305888301c30aadaea09efa059bbbefb9ccaef238af5cf37202f832fc24a0e455746aae71db3e6543b4c56aed75fdeb71af7c5c0b95e5a8722da016d59ec3752db2c3239961c2c24f5f27554a0c54d3be84dd354664b516493b6ae88232b1140759224bc0677bbf2e13f0cd1be147ae6f67807912eb176b72f96eb0944e09d3efd4fe3f1ec9fbb5fee4f747cdbc9ad02512e9af0cdd7cd372feab9255b16dd16217b7f2afffea80fc44f67479c1b9e954e27c3dc9d9220b726b86049c3e06a56d95c2225ea43b1eee925f1adefaef8e03e142b1bc6c414072fbb4189570f65f604fd8ed243fcdeb59258d8ce75f05f95dcae4048a13e6177230cca2ca478b08f60c5a238b39ceedad2cd3fd2c0dd2158e85ddbe66e73801727923c89c28d6538dc069c292bf8d60610889699bd33889904fc52df85c65980a11c257dad8d6b86fb982f65a2ef8e0156a1eb0dbe1e54dc5ff3ae028eb6542727f88afcb2e433b0aa25e4c2544f3d2a776ffa1616abe5a7657f0e22349fb58675e505bb9c3be42443673b955eb8098ecd4787a5697cfbadf89d87ebe23b6a3387262704cc44b8d82eb27dc0582c7890f2c1547fc94f6f48cbd566d0823c9e172ed895eb5f1ec9c7921122512bd516ba21b00580b3d7311af0c3b4517010c51728bf455a0d4a700949f8f53d127a1ca88349c3a9012562aac0dbda1d35095227241d3d3d102a3fc8cf0fb93c27f3f761761dbe71f0579b8ba82ae17e7bb05de722acafa605de2ff6612081e63b86168e97c4b5003974433347b1abb443ced0c4c9beda631c628c1a1f14f2aed567d1ca42337d3890bc35d9ef19a89ba2d5f72242ebcd587c8afab641400719eb00d844696a7d686b46a2283dc26159a27788972597d2ef1ae293ddf93af445f5765ebd87d1cdcf9a328ae97712739d38ed51972887cee81158afef2c369e8a644ec821f7955f59707199c9926b053e45b1f040168094c60e72a304a06e0a65daaf536d5cfea9954691ef2e710f3f13d52d05372b26068b6cf5e53616d295ff42cb863a0d97d391603b4e48971d1382789b447724714742bf05c123d51f7465e3e98baa87a5465a567df458989289168445ecc3ea90e5faca1d7d54ef8a86c10542f9f3c63b866e9f0dcb28e7ad4e9ea643d4221b2b383aefb382d52e0884d862c9e490478d5168986b735686dd522c30a05d672eddc5311e4314be4e4c95a319d5f733cb66ef13bfa0d114e22e551738df01f6c295712ebdb5eab0b2e7067d766f2b8002abeae93998b411d3c6eb9697019d87203f5cc479c3a1cfd5157babe7fcbf7f361831a93b4f91d8537e39b3ef154246dcd68ef1393bf40e0ae9869e602a93920b419f5972c7b3c399fc3efb87af38372e46b92a69052caed2cd9f0d877d1ee78f92a4b7759fb9c46c0b13ae79ff9f172ec6b72bf2d81a6a37f62ba86118fdf516fe10805cf7c61f826a8b5fc604b3572afa3c7fa9e432c222615f23875371e4e7460800c61694f7025020419900dc54fb83c6dc5601f7904cf51472ac5d2ca99e9197f2a307bd711fd4566dd487aa65921dd316a521975760d8cb70be51df9280baf34e62307783f9c1adc5c59882472fe8aac6212bcda52f78a0f4b6a1bd8f6cca8afd48c49c4ed4fe1812197f52d3ff17141b35107e1dd6e8a34855938a183e3b7e7d1f951b4025f83595ef5bdeb729c9a6a347f7e1d2581c338b104541d92bf7c949fb668fed436bc8f7e5f7e5e7201fb954bc76b460b35e74e2ed364f31221740d87e7023fdba6e00a7af248c372db0a0137d1d380c8dc233fc4d1bcc632912529b6bf6cb3e6c7279aaf347d6826813333262c9a956d1acfb42365e6b5fff85dff3edb81c913a04ff693ff5b5c728ec5d8deb878544d8370d982c2358e02eb7f544eff987a77f655a444608d6e20610b9f94dd5ffc78b09865278473e9a0a467ad860338befaddc04ed3ce8c0a722f7b514574a4ba4f0b04d1233a3dba3f08b70c8b6b451bfc2561d86266bf8272c93e3835e6d517a1e5fe2c9155c94aa5909401777ade147b3b09f5fa3c117172e31bb492a52baadd2dca19554415266c3c14876fa74dbaa6413ba286518fed727be65f37fc4f36d1d3f0c85ac2a7e62328f636e675cc303722fe0b98b79c1f72dfeb5e4511eaa85d21baf0d392841ea42c0182a8f47ca450cd7d5b576ce6c072e07e77720d34cd1e4ac284dce42ed6613278dc079dead6243a85f22c5a25763e9a6d609e7e723567fc8a0734eb0b661a6d64063f67dcddb118f883b66eae2e7240d7449a64d0a744499abea5de24db07d1f9e717d386f24bcac15a1b2efeb672a5b86ea27ee63ebc7d060fd892c2c3f05d876227b04df1cb35215502a92b0f721c3ce76226d3abeaecfccea1c35bc760792e01e7c7e24eb52cf1b559d1be40705f09ebe97d4ef5056dea49d5b193864d4bb31e1d1a3ae0e36e0dc9a8e205c2720d90e74ad36024f4abb1bf2d5f3483aa3dc572535a57020150a7324418269d725be08d1890d79a2f1ca93fd78d7984656d29838606292bdd6fec0668b42c6172fabad1b669851e446e3ed87db2fb1c37bfd5f3a2cceb6ac982b55f3fe781ea72c501939ff74be59368bbe93f01debeec75df1774a625a70d6be74ee8bb66c0721c69c01d1b11d35f9f6939b698b3bd70c54cb9621887ad3a9faceff37549d15a2138a72d8c290f87325b7e1f16cb19fb088f203d4618e9f5e96e78870024c854a4d34066c0f32928c158012e13f75550037243c396a24fb771c0340231898572ac677d81135e9ae03cf5247713ed53d108289bd56cdaf4e3232a49e98caf517252745b71427a6e54d24232934f93986cf88513567bd7885331ef35c334a9ef7214c417f5b1e8b6b9e59e624d90eeacfe4e844dc02847d73b13eabc93c0ccef69abb0f32aa44b51604a22aa69a0a1ebef4a9a300473368c7195f04bbc0c0c8307fac3fd9bf3c6ff5f9e159e0cba6bbc0d0e845349b0168778bdf6a2adc71b397261ac957aa01384f8b60106efceeea5aaa32453a3a38d15f9947f1ce0e859a27283a804e43ff99587111bdc01269bc38b192d88c5e0da3efa87b38c93e3c36d722792c5bc81dc0ccdae1590de41e4262eb4f37516d722c2c4d3b380f616b25b6f4fd4a06aece7e0c54c33deb5fb432b56c41525afe9ccc923c9ba3a811da75466d2b0e5dc98adbcecbd405a889351287d9c4eb58cc81d774eddfb66f936ed4350a2f5faa93b56feef99b92026e0eef05f11f41dfdb734e40907f0ce28edd7122945b0e9f59e6e6cc85d3da9d3f496b4e9fbd7ad089d1c458def7205415596b12d7ab6b73ef831779ede3d34c641db7e3fbdc50ddd0c3daeca32856c5595c8dc7258637aaad33ae95bd98b9089f91e167253b011493c558f2e6b32df6175aa18728ebe3bcb1353f689425037c2e220c8c207ecbd86c4db5fc12a43121d49da3f3ff5b4fe552db25dfb6e5906a4d621da97d5f29a83c38ea9828456aa668e258a11898491f67cbd2b0fece1aaf87a7b1d9106339e2075ee7d5a974f8db1156b3959fe37f2e2ba964fea5dce8eb457dde438d366a2b80ced95da032aeb852ccdf472d7142c162911b33e1ac6c901a0a49626d38fd5fe7471b9a249188c749dd2a93aff40bf5cd393368834d6ec8e8ba2afa4b74dc95804ca2466e864d0c328d0e470a677fe0f268fed74503ddd723685ad0045be9cae008eeae31624603cb9284b1b6793ef9621395f0c5f8507bc6b154255840979776530f737b52ee6a5f2c5e5725af98dd3de6df9cd2dfd7da92a0a037f70ce32e92ea347ae874d5d9cdba8097260b1ad2c8e08798ddfc55ea7504fd0886d2dfbb4c368409f846dbe51874cd6683b762ffd2223b2c9fbba4d3c798bfc5a04683d10e5c6057609b5c6d8c0969b72161bf3682acc3b6dc906ca8cebf94cead15e1b14475103349a4e27d039bc3e44f3a7e18092abae8b1dde1dbd0805697b36b87bfa34fea635e5260e545cdae50ac70872e090473e25e9a80149f43717d7d59b96b1f6340d78397e375ec53ba820afc1af9bf05d1dccbbbc4f42225b5243b9d196779f616de6df6ab51ff16766725debcfe74dbaa527668ce4eed1b810ebe770081cc8581054a121a27f437aad508d549d5e2248fa555bf802c56eb5ee771274400d4fe0a4bbcfbfecb1d6184e2de280615d2ec380c2aca7bee6319d5101664119c1583033f2580af7f340bcfa7204121a1b4c691d705c72333279c5696aab2bcfae8fd0449b7bbca74611124863c9c5873a1715c0271d5beadb5e936a2e59ed2356946126a0587603f73daf2c729a2e0fa7bfc3f41e562a310cca751064743956d32c7e8570bad2b9550d22490ae455bfe27beaab26919de0f903c56aa92c8f60d3d26cf60b21cc7785313611015f0468caba792b1016665f5dec379ce8e27be7004c3877676d76e0cdbb1af472322c9a1b9a1f8ab116555db10b5da5208f01e02dadd95ab61cf9f5187176f76c5884e769160fabc3b0798515ae9bb9737f46f09f5100bd105a055e5214bf653a1f10590cb252def8afba39abc61d2254dfc65c6038f56fe1d96ea85007fdbe3df46250fb0c1c259500849c8330ea9c9d212760d6cbf4d1ebff504f8d084e8e2c12f0e0e2da7ecc9d85dbf3a48e11e535e22c86fe7867946d8cd1148caf2e8a7279d4ecac9142a76008aab1c3cd4b101594e1ed63192bffb1f2b358c1d6984b72c006ecba77bbecbab735e62252ae345a17e10c51935905e8c97f811cca56c9728ece5bb9770fb2f98473e361b3aa0879dc604c2e4525c874c14945c1694693722011d6e76ad461fce2e08fb2c60dcf69568191ec752271855848926f61b4d90c64cbc20ab3b711ec6183bb4d8ce871843a7333a73191421d2ded21581d51d01402adea1c8ec52b52a4086fd1039b405c2bf89c443cbc3bd1c96eb328ee8abc72a9d04ba32ae239aad1da3c0be71041a57265029c23d349261c1ac4d35bd7ad4a1e94177a1ed5042f87c2c9ea9c17e583f49e6b94d49d5a92f88fed9c7e910838cc8e732c4e881e9d1866ab4b76fe0958e88cac6ef417f9d941c03cd30368c4707f4320f684b14db36191897993ce2c57a24b0d4e0b163f37c2a34c4c8ac3f57281d9a4cb870c962c9681a19887ecf2c0025c17d05ad20d97d51ff586482919593f18775287fcc591aec0df7cf1fcc9ed28ca5684e98865bedd6db46172413c72802e3dab1e39443981107ccbe7d5358b66508d67ff9520be9dae655c09e0d272923ca13520b91191daf423dec001ef83cfdebaef72ce32c87788869370fe7a1edc9bdc427734579e742ebe9180cd337638c1258547651fbf29b431bffc009172995a4e5e42411379685086540c32e71f687c42562de26fed18d89ac8b8eb977275bbeb0188dab3616f693f1ae7b152f31ef5598c0527adc5ccad088c1b195472a724effd45a5e105509be929860be9ab8df4f338580229480e25766faf409f0d9ee7f40823b80423be63b73bba13ecb46b2198f6500899a004757608a7179b3825588e2eb4b17c92f318307f54064f72eb15b1a28367be65bbb46516f34eb1723f591f7dfe659771ad07de0aaac16c54d1b0cc05c147acce6cc2c75b86b071722052fb5dd9275b7b7171d5c7773b2e56c6d8fd3df25bcd56436a0b2b9c8cf26384cdfe07320a9875d91e579fa95f0b9bfaa22fc5c8bff5a60eb557c926786550bf62645fbdfd922bdb51543ef53dc30171b3180c79d3a77d66a29386e299427270d067c848b5ea287b43e997a3112524ff09b8d96b8536f3ad0d15262ed48072361d829f930640b0b1034b9bd91953030084794c0ac774d67420ef38be968d7277a400d2512ca939056017dafe7ee25db4d1c806d06eb8a8630136c91c24fb7211b1c75461744bb478bc5ddcf126022be56b96e42e5e4b432f6900c19d28ca0a1f273bfb187e21f9c7b205a868a8ae64dd66a8898b9029527e79c798b7c8507217755014d835836cd3f1f0086cdbc93652a72c02f89ac2648b9768761a150e27703701797545caa1498f6ddaaa1e304a79fa95b6328c9d09ac1ec94346bf4b3e025b32ed2cdce81f0905f7765709a1750e84bc6d5d10d5674ecbbc3398f8847217205cf0bf1b45be160a78e62a111fc3e2c10f0e4dff39ba2bf6d58acf9dcb007fc7f6d5d641b337e955f7ee8ffc90f9503ca96d321e75658a19e2d2d858cd5c0346de141411c690515ea2b02d6af2c3febc5b88ed70fc09f9a07b035b1178723289da7a74a819efd1f924777ca7b430d677a9cbc91791054ec092c78fa70e4469f7cb7b241fa6cde277eb44462f3c89d792c2ddb0fe47046c45b9406e69cb6a13161ac7c1a753efa512b649bc5ad4669c6ec6c07b36da0f26542581a4df0172775c82087006a88905b0598c49fa555f0aa74b404072ddbbcded0785be7c420627bb700975ba6f461bd1253bbc3c6a9e9c3032ec710f3e21b0f6e16c42d33b7235a7a44832794d9e00a980bdbf28ee9c3e5b28dd96ed3a28ac2879926a0f9c7290710144f111239df2b8ac89e1b21d242e4d44088cf9d07e90e2ead4b5465667d13331367c5c6752c302efb19988c5d4ffec495a5be8b13325a4040b045e1d5366324e8e6eb4c2e0c8e51ae44fd045899a406705c517bc3ad4ff49ab51364760304f83b8cb9505e78c744f4d98ee95a1713f7fcd18f682ad7dec246af9f2fb1585a8828bd208b029462bd78b6cc27d48441b0da5cd5ed419a7dbbac75138454f63ab8de57550f298552a6fce89333e8084b24ea5468cf71380ef586dd247fd72061ad76ffec525efad3a0354c5200c8982712b285a4719f7ed4d406a2f73647270f92376c0f8a7a71208fe71e35a04a3d73215126467a77c7bcd6adddafe7272cef32993bd4d0b49a3a7da22d423e29432fb1b2e2a499202a129138e1a201e728edaa7060bfc14f6b6b4bcc0f80fadfded817f5feac6d7a98ee4a3391e6faf05edb169d17b5ca5da89dab56087087be47d1e3e6e38c60189a14a0103b793fe72237250c8ccf81b38cdfd776eb4ef98d3c8dbc9131167c97d3c8285baa7befe72ace5de64592e49fbd655175b552fcd24144f352007d939ef67498a6f78d065726b4fdbb4e2eb6c68d8a156a027c08ee83ae12dd16d6d80690cb25d693aa0e62aeda6f0576864d39fe3732cc7f96a2c7eef2e25640b23db826d5c26adb01ff43432fc48449743ed5d173a10b2bd10218199f76cdfc5e0a0c99d94ff77be083972c50cd0d8f5c44434183b22fd89bff64f53088a565e84ced941d74886291a073ed3484f4755d5a95e9815db581bc43aeb1cfa9b96e33a51791f212d67d6fc4f72602eebd07303f161df5fb8ba0a3863ba771ed7be0d17eebdd153818cab0dfd722336713b2b5eac7002ef3741afd98638e78db0e3e1040ebbad73f7436a99a710c2fb695725b0e10e0bdc771a1ea711d28c9fecaac9cf3a2484758d2ab12cff213de2eae435da7e8b146a6807f54045e94997f640e8484c8a027919d3af946372f91493fa5970f4d74c4cdb53b19930e1fcbccf6583fb2b6ac467d605c8fb9f386cbe4420b8ef1c1f3662aa834ebeda49aa5e93124ab8108d1f61c6afc7695572c4907d5c9aa9311ed62c36d96ae19f2780e2ab2b6393b93f906247b65d5ea0728b88992e30521a22b1a19b4db256c3d8c2e18097f381e11fe217391edbf4b727273c36c38d8d3c2c9bd448d42d47b1ff07ba4c88d8203e753c4946d6eed30972c064083431a82c02fb8cb2fc6e787454ca131981bf29fa4a5ae28b3e9beba672b05ab817adb1fca433fdd9de9bf77ebc3bf301746cfe92635e85c307a9416d3984ff0f8dbeff15606dcd1a90ecd3158a36dd224d643492402f6844ad239bd772546478612c8198a9f0319978105dc674fe93a0ba38465092d5eb8366f0784272cde52ab04eea614dce95fb142752ed8b6a085b8aacd884ecad95c434a486f236338af5cf33378dfa889a7da01a1d5fad6163d6926df1e71a37528474d2b73b3db05ddba79e06f15dc24673d834720ce785b45082bac70b9c3563d0242ba66c72f8994d25f0c0ce84484fc7d00ceb3eb425677a45ebe821c03d1b5760a6effe72c2823d597fb57c9e9edf8397774e630fd6f3a05ae17f5d16cd9cd4b9093a837211152e25e323186756002ff3bf14cc2859ce622bf4672ac6c7433ec82216d57262b63039883e26b6071f3199b19321a1080f2fa3d5a89ca07d6de7e6df849972ee9e4fd50d698f2ab3e7ea0da89708277e0925e8ba876a764a815d5d4ec378646dd8912911a138acd3abb83ebf1f3654f494eabfb9c8192d650332fb7152e7723f17a7d88a041e4d7bfabd70c1480d0f682b2724562d9833513affb28ff1df72f35c19246aa4e3bbf7e478242a75c1eb3221a4b7d868169479041836fef51672fc95ec5bde59edcb74bbdb93b564cf2890bc50ed6f1246c70ed408b3c0548a720bf345fb2f2d3ce5dd90e36ac6b603ecaecb053258a0cb15226eb82ca18f6b4f5f4ecfc04c1d0cc6d7ac1e54778eb518092832bb36aacae984807cfa5a4dab7211a83588334042ea922eb952402dff4137a560b941915131dbdff16a1adeb8724259d84e05933d978f98f9d6ee732b76c3a87d51309c4244eb0e74ad746d9e60a3fba196f6fe63899700559e99db8426743bcab66131f39238a29fa82e2a6f722cefe024fa55472fd4aab4d3b0cd872bd30bcc1e52c1142c68e1af3cc76922720688eaa0f71a94d43cd73bbebc8eed0daa2255fe5211d0bec1521058b0cb6f7202d4b5828010507b30c1dffc9c736878452713666223d452dbc20029aa4f1e7298f4199f26d3649406926816da14f58a7641f0c5658045cecaeee8647e775f274f963f10db22542669bd523112134c3a47aaa0fea866fc9b3260c0f9e3f98c72dc9c8a85282c709bd3dd899f4d8465980983a264766ddef3d3a35a651668e372836f526346d01de3e41d1274ddae45b5fb8971340fb859ec1f89364780e5f4106288e56194b087508580ebb6fa2f74c698ca1abde4cfdfc4c3816a51fb9dff72af93a72b94a0021e740d480bf7ccf530c6af86e8c099f0f55b37b840862ec169e0f63c1cc36c0b91d14ae7d7fd7649cd026dcb53581c3b10f0f9776c69bf0f72ede16384c5e188cf146adaa4f5f0bf79aa6370feb5f24113f5af7520c3b63472e6cc62be7b24112d639cd8a516ad59f9c612dc86f24ae24f166bbbbd61eee172caa5ff207e2a52778f993fad5386acf3998d7471d4385b7a0c2e6c8315edd80f088e3cf7ea0ed7df34c6b94368222280f6fb769bf6d67693e8d324e7e4223872c890037f9a5c79cff1d0a02a958a4c4ff0db45c64cc51561e4b32a0afd5c5d0e6c0f5a0ae995d9d113930f9ac01997a9cbc583e90adee10c20e77dd103583d03f06530fa8d695f1059d509b92850e0c14443c8aa6f18d19add4d472bfee4af7298cc2a937b6955b1516d4c0b80cf24bdcd3cc0ae06476d81a66d5604e77a9958f716a63be972f4dc6846d218f40ba2e89acc72bfdf16e4ecdaf5c5c75879ab615e29c833aa81de32c970c20bfd2cb2cec303de1a9309482f397b91203a567e7281060a82d0c305d0087acbaab12b31abb30890830ca1bd0d92ce9af7109941258202ef3fb04a866f7c9730a5e0fada9365ac80ca14c065e496aaa06d39c02d1559a7cfaee5be038c476511e59caf8a7ed14f9021d35ef001892899d19edd8d7226469828cb336da813a6ca33e9bcc98e3499d01af6c455c2f9f2d7988d4a7972a6c3cda0c3ef8bd8308290585ec2a6663967353f0ee1c8957e666b47b4f8aa1917a26f78975f867af8d393df9df6dc045546f368667c13ab58972c0cc6d6de09283653eb1cd3b398bae2934986e2aca44a9d4dadd676a34f0e68ffc68c3a3172828900a595c84e1beb41d26cd874964febcbf912ff44468749f544dffda1fe723e72fa010882f7c76453e07d4978c8b90e542705d21b4065c2f0341db5f11b4fb721f718611c57bd5f1f698bce1432e1b984022ec17415d4881f5bd9a4b76b729cf2ac0c0431505023f6e3a3570b0468d2b9f400bb82698687dba4a9c39df52206775968a925c3e6d3ff692edc5ed9d3b07ecf060f97ed1d951a069470504c72d339630bdad96257467d27fc292ad6334e93112e387b525bbdda9d036f8a9672fafa876f2761d5d23f697863ec3c54a2adc3f5d9e70d0b71f52acc1d5839814fd15137eec9cfcf2c053b236aacb0aac668ab2dd4df21a3515ab0a4781d6bdf72712bec68566ce6a51aaebf055d53d50134a37b5ac29052105ccc7ea4c8ff87724aca2d960d16819e166699d47b52e71da1a0d6409128b30e8b9bda2d3373fc72722b3e32a90293892325eda5bb2453a688f38dffdb7ac374b7e7c14a9dca2b7296ec1f5be5ffa11f1e558689aab6d841af295f4b4afedb0c62bd2672770391726e0b79e8642a3f3689f5de73c3405fddc94ea5764e34481b66fc180b58dec3483e1eb5d3e45f2ca3544a46612a0762afbda6eacc9277ce8ec86abbb66ff54a721dd2045edab2e12d320b96d72e3cb827bdb83d415c301b3de8c643c35a97c8411c5a2e4d5d3983f198b382c5862efa67ecec91ec9eeb1c2da744f436ddec094b595bb8b74dbbddb9d1224691d83824cf154a22a3077cb9193f75910c0ce87872a16232ac499dd51cdaf9fb5b705609bea238c8e24e451cf212fe84d12370a02e63ec8806acab25272e904f1c14b2b4ed08dfa98801a7826da4b3a983c7054e2985b26740db7c8435bfaa8c196b099ba26c694e0e21b6c0898aa9d5d437f6b37236d84569f183f29dddfa20cea49eb21f6bbdf2c003c320c3e261440c3cba591dd251f3f8ee089626a1d0c61b2eda6431b973259ad500260f8066f052092c5f4b96770f4ebb17eb890992e970fd14f400da4fc0574a2938bb6f9604f18b525c723ab3dc96290bf8bc593682fd6d2c55310eb4253d4b4cd2c8c265510fb0b7fa1768b327de7a0315370f91f728faa740a69ee6f00bffc127e96041709623afb8723bc897772aec35ce037dc147d7e245abb2b278d1c8813d939920be32cab439097d9162f52bca9fae0087ac5585cc06c6eb943527e36c766b3cf728c4c6a4b372aec37e92970738583da91c0d68ddb4f6e11ad7ff30360e0272828493b323bc02b2e51b46dc64e68ac6ad14a4859cd10b585a2333d0f3f69f486288552df73f7231d823d61a49fcf3adb00d9bdaa368b031b37cfe42426fc4fff448370597c372bdc3b3c00c87be99c5d3265531f3314d4ff0a0f60d0da305dff461dcf49bd472e49aece09f536fab2e7ee1767b7f45f00b2a39b0b186b5f07d422b685b9b9e3af5995f20a8063ea252b683f679097720bab8e37eefbd722ec7404be14349192863822942fcb173bb828856779e316167a3bdd167f4c58b04318001102181e572ca0cad7257052868063105e8a8327b37455bbf71b8b5b319a76be2a2eec74d72d1e386b72f8276447792f22ebfcb814461972f63bd95d98b3965ad98d913d7725108e003010b8df7f5893a4b2df1de294890ec8b20cc20744d0991f1f300d5720a09ed7984793a9cc7cdc48680a4d9d40fb8ba827477a84007434ff2252bf0725dfc3e331d49c71676e88dd5a4fb6e072e587dd6cb4a833d78dbadfdeda5d27249fb5d8d3317b2e4de4958409872441a16b5566f3ee7b76f4b6da676e3c477060e6c78a8132aa98f6d8b0bfd1367ecf185ff28abc71fdab7acf00434dbb236702d5149feeed517ce262f66bd431668757e2c181da4d7b5f0838524f265f0b06ca34a228c77ed0b77b620ff223810652588e6eb7c331f79164de126bde4b3c37239c89b4199af77d1c46c6486538152cd2be7ce0acae096848768a51711f4a821f7d71a48b51719ab17835dbc07d6401daec3b65d39d7888a847ed56908086372471ff441238b89a5d7285c91171d3372bef458f44f1462d516cc9c6849285572be5d30e17b6b50875401957a95de6c0ac8e6c0dbc63837c3415ce3a1dae5f472f89a3f08e1059c2dc92283ea93398e54ae180026e412c87a4a37fd4c6de09972035a37455d835fe8880346cbe1de3ef9d627199e4f31b40da7b222345d445f1517e7c02e7eb3a192fe979ae134c7a1adb4c3ba52b18269d8dc412a07948d44728d067d4a101f479543902555cf0ac0c4e5bf80796dcf379118103d9312948a7202bd10f0d305724d8b59336a27938715c00b2d62306f0ad96c52cfd1277d252e868a0265355ba85c54557fc4a5a0beaf0619454dbb84ca9bece548f7dcfa4c72bd9783488316753570a10ee9060ef2e15ea5dd0d7989d0c8234178d8d5660d7266c9ba7755623fa389a3c5908870167b082dd4a4639b3b8defa5ae5fac44ff72b4d6bc9967eda301871ed6016e4443204d46d69d545c5c6506e860ffe69dd572f4cbebe1ebbafd1039ac115c23e5d9fb917ca569d1c6de66357a7abf40961b1bad4729a659da677cffa1f105632f233c0b57276fbdb09072f2836b4c80ab7972ecb3ef16a3cddc6089830e3882fdb1e74143b44e738e89301052d2cdb6fc190a50df5887815ee673e16eda65e6891cce32f6abb9b62100d0e5e3e5181f327e72eba07c4ad8c87c38c0bb9e6041f8180235947bde5ec97b755cdeda432a19f6720987f870cda8cc47a90ab9ba67284873f56d2599ee065a90acfcd777a4af1d516e2ff707d21b810beebc9e2ce9139b4a74de39909e1426d5d37e87139f3b7172c263048fb6428832149a70ac144bcec51cd2ca78c03c313c23b63b3d1ceaaf5a98bf6700972d66e74819485e8bc94d5b47f762f7607d3f88bffbac439d5c9a7210bba2053dd8a345663bfc86ab3872b74e6f9283d38601aba7ffee3673027472ec595dc43275f9d429e070b10cd3802de96c74574c40c98d3e6bce33b500a672a9814675d83042a7291e2c30f96361b52bfa4c3bc8fbd89bd233e9fae810643f7cd462a4e14ef8763f48a10d61a5894b5a1c6c88b65eb55c72912163d630dc2ad2e9388ee51ba3340448170692d4befe08b38a4fe2a1dc9170ccc0e728889634a4ec3f42fe73e19dc06bc34fdead78965fa5c8cb93981f46cbad309633838708854afa142d41ff264d7b74ea6b96edf05529ede38c8b3f33564edcf5d9856c07a5bd8452bb18ed590b6a2a53a396a1363750b909ff236679dfde2c173a1adf7220539641dfbca06e9429e990864eb6350607c1bf706192df9bfd86d405d4fe0d259fde64775d2856b3e1054aad00cca356e9a0264adc91306baf7760842b497284a5ce5550bb9d068b90b8d28ad81bb8af318b1f3067afd08c016792c27e8c70bbd5e8aa6a5a304ca2b9f724e8f0d8b9a4279d0d4971d9a2c8c425339dab6e1af1572829cd411505b725d1c6d173908c6bb09e4ac66da27c7d505328539e9e729497511af573cc12238db9961d027fa9965f304440c7b255443078b2ff9305721032bbe32ca51e92cbcd8733a39718b304d4a1f6ded3c0363ddcc28ceb4cee34cbe7ade456194bcd5b5e570cea03d669074a8fb226ae91076631af1d4acd6c1ca13365225b19c5a72fb4291ee3fc857a65ddf1484dd0f15cc634f42e47927e60134cdeb53e475aa3a479b863c93098914393b78713b88a4c2cd456a08c335572b26583c824dffcdd1146e3c3a4b08edddef539668178563e9347b51efdaffb72692c8ec998d2fe89f85cac3db6c13ad6289d983216ba6493b317ea2ec0a15072f746a8ca7dd4d0a16324e4fe7ce3fa8d96341be2775c19a1d398edf221736d0c88fe3596f8962077a097c16796f8739862bcc10b27f6255eeaa5e7986b1d537205ee53650851c8eae7ddc3f27e0692613637942721f0aca065cf978bd5c6c30efb1aa93a66971f5a05615e4245306278897167e59278eb73a0b3311d7d042838e43a721961660a7f83351c0eb9140503d5ae9cb41abfa1e9eabfebf7c8e1d5720733af16e0743f1d1a2fc6992461074854d6d55d0a419e6930e4f9100132ac2b019b733943b3183bd87d0b087f4822ffbe7142c11b769f1d3aa8084a7e05bc723c84c00256d6f7dd702bd28e5612e494eac3a292143804e32f24c77e1b839e419792a12a22959609304d08e01dc665b5a3491f8fe246ed29b31d19b041fe6d7231092665b4791dc9362f95a3d7779b8439502e458b73af6992648ee9b058e0724568ff43d49370472aeafaf5a129cf980929a5111e6040e167a28fce0718c972be19966317172cd35c5ea652c87d39703ee0e18ee429aac0d6c14bc5cb7a5b6e5b3409d66874e2ad0944a92d65697fece282ba90b1b41bf856f90611e07e093f06a7f690bd3ee08172beaae4e526b1c5f573419b080845814b025483d7875c6ba445a91d60a86039b56b9ff3600ae6d7feb1bf44a2b8ad20b54640562d6563720d4dfd52e23324fe039cdc0fe0e91c33bae1fa63f66e039c262a1f6c853e707295b2a17b6110cb997f2dde96ca62c18946c68c67bac662ef9cf4f8341ea7ce5e7e4b8790bd35d10620fc018b5382e0c5a723026b0c26d44466cb14a917309d1e39da2c98887fd8d2e65018b68137a663f9da115716ef9863ee3cd4d16c38b43737596dd76fb7f085385529620329d7429ca75409d34d3b63fbdcb2933c2bfc72460c892840bf4af9089abb1adce37e9c2757a3ec1985458da02f356778df08726ce5b5283fcc471142a8edf1ce085042c64eb289807333a5bbbbbe889b3555721d55b46f96907e6f4e3aa38c2e62e303a4e6f03a382c921d93ae0d20fb6d020523a8b244943eb43e7aa2a40fb6eec6886a4f4bf0ba86c9c6030b0a49200b0f725c807b17f9fd265e86c3a63ffd1533e9e2934d98e99cffd92b70b60a185fb9623ea20762b4e84c5d77fa12cfbb95e96fef6e2e1027531bafecfcb13caa20ab6bc02346afadcb134707c08fa01cf630512a945fad3f0ccb10c6dd769ccf0d1b3fd719f7a1dfa9f7503c7a85e3c0c7f98fd2d4dd4a1102ba4a78bb4f0ac6c12508aea12033bcd87d76e696459e2b8080df710c65c788da6c8e0242919b838a9372ce213b8f067f42a50a757bc134b4c16f5ff30834feb4344dd3e794c65a8d414d80e3b1dfd9a34430050c11bfe0f9fa71ce5f542bf907f9b9064999048663a9728e0128f4aa4b1dacd66e415ad8dda678077d2590cb187e71399d80c9a2e804725c5d2278e5c7c3b46766cf17f8f1f0f5eb5cec2765c222cd854e8ce3616af672515bcd799a7ae70295e241f6f4195456c4d5c4e4fee8135c9a551b86ddbf2672c1e861d6bc9c977f9689ac1b02f796ff848963e462b4f54d06f96c6daf0ac414ddfdf5517d59b537a3a1074a81440085756a6908936e8b0d42947edf3f688a21a39526fdcb51d54a9e5b8044c6e868ef0d0b9a8f8bd4236700c1e606659dfb723a660cac8737eea765823f69e9d775c8ef50ae5931703b68414bf4926fa4b872d66dcbd47f0f60a69e83f75c0c498a436f9708859004480db5cbbd075f252072a5bec881704d044feda50d48966cdfde827a1f462247839c10cbeb6cb000d4727c2acb2bce2d48c59ffdba055452d759842d568c7e8bc4b3994af9fbcb5c672536788e404760603027570831491fe4976a224670fed4e4b8e96ac1a819942a7284ec0abace96cba05c7490d4bd32f5a7deea5d888b4837dd1c06e828bcd7fa566520594ee15e79414c757f3406c23fe832d46c98920edf387f4459a935ce4d725148a06ae4b0ceb0772be13ba0eaab3f291c59a68e918efa3c729b1226f25057ce954d39c2f344f2c997eb87eb757944c3f0696f011797a681019889eab8ca585eb774f2669cb46c723b39acb4e87b567cd1349b0399864ee21a69febcec5c7270f00f5ac233f05c91dd25252888a1f69de9e9034736442dcd7148760b371468d1d2df867c303252b49fd33088e23d18f4104866e4cb8174e85df63fe3f9550a3fb225654e71f3bfdb2b9e8e24e2ebc6c10405ff87ceac5ee61032d668e57d7270663979920f348a2ea10f5cafbdaccb2f0e88b92d8dd813b9fac0f15b9ac33227ab945c3366457229d6fdfedcf9238382976adaeedff6ee71efcbb38b3e757244ac58f9f91f0eaeba72db0a861731d814a7a7e0e79cf1f29ce9cb6f7c1de872a9790ea14893f7cac28543935c2979457a96fa030a62b5929ea96a9732153172fe9b16bd80d11d97d296e3e432eb1afd150496a3114a61f146d1f33bb0985b58a99acf8923e6385b18d2109a65a9b9356193af98ce5cef83c4088ccbe868a872385f8c1053f517825bb158bb1c9cdddd0efde6cb6ed0398b215eaa35f8101d72bd6d43392b728ec4921112c0e96b23b8a4d8dd07f5c2ae39c0f20531a3750f7215392023d4f6e43eeaae7e40c7bf15733ca67e1c331ea94800e661dddcfc1172b0df7d6115ec95e69b120ddea8be54eb6ac5138fd151df38aa6e50fd3db35312c0e19db519369dbd5c37c53c1a2c76ce3ec818e1910ecc271cb1b6e103600e72eeb85d15687b3754a8b6e5abc2dbf382eb682d1934caf425bb42d4f8fc393e72b106a31d1e97a0477d938053f1f9490b3bd542b25438703271cbbb53dfbbc572bed4fd6c01eef0de240a70b6a8dc4d5ac48cee35ed1dd01e9d7adba72fdeae520abd94a311de2520763beda76e47f5f738ab1666ce732a8586e315d15e40521b01a2c5dfc5aafe9ccc301b83ae6796d99ee5280d7fd05e3f1993e95710fac65f8d4d140c7a3f0daff69cfc07f75227cd0224c7e05b8d7bced7e8b82711acf3558fc2d3c3fe9a7147a7587fb491e4fcc3061a8311d70bbd2abdcffd0771eabd0b0d71d0b4d4fb9f6d3c64ba41ba4c07f3f354b3490e06fa70772fb81cf61c8a4529dce7b074aef0667c24a27a8cfce7b7381f3bb8e383359cc51c1c1e42f2f239c02d12d174efe6605e099c25eb839acb9fea14bd13e6d0d6b6b76ddd68a225723c62ca5033dc095f2b3a00a1a118618ca7ab1f8985b696e8bb71dc4030025f7243d6d61b2a4482a16914ed82de07790d32fa4019adbd31aa784b1d326994f3722f3d5080c35661f1f521950be16a166fda205d904f99a04f0a77e2c759afe372ffad1a317d26b1607bc847ae379ae31771d24630c0713a29cdbf7df85869c672604c2414481102f1a6db423aeeb753773185f462cf681e5829ed29455e2295720655b7ceeeacccf5ac2c1c4b8454a37d7a7daf981f265a5a1b2d1d5991e68e5a8a288cd04edc80f2784bc222deb25511eb86e00e5f90b76f45db60d13e7fbc721979cea2c1e8485d1eb3d4c2476ce612b79b863bf3c202d8741f508b7c2b8372c0a8bd3de0a95146be39c264f09c4adea6dd3090d18ae56a14274ca2bb078972b94d65b9e856fea173580f26e3b9582229004077cb9cead2b2bdf4d88db2ad721f1ca926ca9dd353fea00c59c6cb172418b820be2182a786f38feb8eae47d572625c8ed62d3307348e6250af79dd5f96615ceb8dc48052771a390dae0bf6ab72cc2e1f098a27235d6a876dc076a7b0853ae62ec4e2aed94cf544c329b469ca72f4ec276b2f93950ffa410bce2ade302da41fd1cc6f9f157d2a7ac588cb4cf36f730297cc32e5e49659bdef3b5d7c86842bd3d7ade8dbd4e3fd121bcebba33972ddc98a7d572dd538f4bf9c4a99a8661e63181b18048ae49e62d4e98bcf61032d4315b3ee422bad7293c594a43398782d657d754d450cee33252b02385e45a317fef595fcdaaa3d529e7ace37eabda0072a35bc4029c234bb914a6ebaefc4682e6667e8254993f74a134c9d11943f361dae624df94158756052e82c677d5c727251be5477fac6c7165861dc27a9cd5c54f1d2043a94ac3292d125645ff58cd912508de7729e9f48db0d707e12a18da5d93ef0d0f2267b836659155346a1dd292fa5ed1e744fc60c567a81df126e1f9cb5eded345bd65c2391f236327186766f72baca68be6d6395c76162ffcf1f3be4d48015874c461078826ca5a63281a41072b888149197dbfb5ae58103cb2afa662faf7f7830b1f0b644f1e0502196d8960eb51cb52ea92dc0e0d44b2f5595b4ddc8a4aa3da8509d966af4ae96dd9d18b5725fdc9fcb43f8c22624cdb1bc85198e4290d7dc93682a4ba9347485e85df2c7723423e515123b6d21dde4addc81d95de7048a4ce58964fb9c02f61115ee468f7207b63bdbaa6a2ce0e2727a5f53d27402e5a27b000dc729cba4c54d1a603e29646db53b59479fabe1695a6508d4ba7e644c9d5176d6fbeb0b0809113bba116f18598370381ee0b30b6304f0d7c90ea37a80b0b1ee223b25aebd6242ae695e1b35e143c6ab934dd6ac69a85fe6dc5910b0f2b673e0e858d66be858bbbc0ba2746bcf214e933a9f30433559b25f24a26b3261638ad7505a0f34212970f51ef74f729b336bbd1483990defa02ce6a006a8d1732b2e985dbb1070ebb30d7fb0780456d8176d5c996302151807f9640a9f991f2a838927e29e1e047547c013b69ad948a9a0ca482202121e6035f6a8ac1c1b2935ca14dc1912301203f2d2aabce8b82c396ebc3e59e37bce899efb0542721a49e2a0fcf7e67aa5d868de048ee117117298b370b871bf9f9c85ad3e2cf4f62e708b6718526684c0718a8480c36e1e35185c25d1dbb46e3982757225553a3b6f4e6f0e681d48a08929081667bea9a989722c86eca51ca89ebba5bdd7fa6593ed72fea3bd3ce1b94266be48a3e50144e672e44f1ec2fb18046436b082f4aa2b75fe0a29acdae371a6d54a5acfb74b92d71b112922897305f6a224680fff0889372a83b6ec7f44d6e4a653f31448cc058c7222528497dd9387d5337076e88370a50467b6793997d8fe988aa0a494d1220872cd08988aefbbe7d605ab2cd025b261361bea3e0400da6c2824cd410e1b7c995722d7d5372af5789cc636b61ae6b3aa951d2e7df11bf92c151688181b927f39261d86e1c1c1f12b2a90be6c288d84d315562c5eb99069b6ec47f071566c80220267e1c055c3f6f9d3c4a1bc7a1d5cbc284462d5b1b559086cc47858d64f290472c12d818e26cb55882024bc865f433883db02fd149cc11c45636a44c7db1c9966a97778c9a2f8d797fe455b96f3e6f4cc81c9e529150c499e6a14a0526312847298a429213ab2c7d4e52523ccb2b0d92b48e5d4bdf1077b1ccf20e0004fcfc772c31bc2639a47acba3e5f38d390baafbd1df491110b6da8dfd6e43bb9cacf717235689a238c14ec666e6e01a5a832db90ebeaab0278aad3f6f1f629eb38834672b1e96f9f6f1d4821107f49ec6e70a0f35b72e1c2749985af13c2da6f90f0d3008b391a029f5581903a17a03891f194b3e98aaee0535254d25a55ad3d6702df4b1a402fbdbd6764f7b711da50a4e101d4746408a07e9ee57dcccb55d7387bd420a6e17a287beeb5455b09f234a7ea3bebb7c79348f652df238ce854e9c341eb729be481f27a5bbfd559fbda1341b4f7d731166869503fc0f0cb8d88422ce125725fdd569870661d5617c6ef5f80d6d60dd7ded79c8c6555541d9cc7fbabe742725670d12fe6f015d17b0c4a6d241354608ead78c27414fd5da47baf4b85729c7162c15b6facdef22f8880f5f24936de3593aa16ee0becbc474c8f1de2f9f49a7269f08f7a4efa4c6a2fa547137d9e28b02c13dd1ef40cf6131f6ddc691f773d72adacd53ee3c4270fa6580dd0e849761c71f7885099298fed0d97d0b38477997046e4c557b57b97368694d987a7c6897e35f59a8b9c1a7b27e0b7aa7aca9e7e7226bb1fd33c5f3cf156e93ff7e133e34bb3f10ee2112152a3ec3653a4d4b14d3aa30ad30f7920664b3a4b8d3bf9027e694cf36e18b1e47df02fcc1ec48bfb1a7247fe8adedaa4b3159f3de95d83e32743b9ac7f5ca08c7ed471e6ece754445609303aa75b5c1e54e38c573fbfc3b2e2d6d29f7d99022e1af13af300664b8c68722cadcd93e89aee4ed6ddcf5529dd3de3820c42986cffa99053ecd978970c8b72487105e26e3d96a04a2cbe9f08f77dc6fb5e972b26d9bf421ffcf4b751e2f37244e6121ee7a6881854a2debdd4b1e71c93e8e6f7d983a21aacd698b02f5503726ef5d03884e842c3c67fff4aa8a3e60e483a999baec6b2fb808d481059b56769e42a10c50ba9a444c0077506a0be3e88c5166c2201f367debd489e0a0408e172e649f1687f75f62066c379ada5733c6b90f974a8a39a3fd1c0cc851e60d5d8720fc47d19c4a0a98d35eefac20329484f66ad5c9c0993d29513b24d3f641934728ded9d6ee653f922d4666ad5a2b63036d8645ebcbda6632aa10a08bdd85d2c7247416a40619c53c2219d089dbb8ac33cfba113d5b214864eb8a867acaf5ea12b0395bcab0790452356aa8243c9ffc4356dfaa80089f413776dc15a250d665245894c1be7e835ebd953f63d65c56beda09df6f087fd21a6c10bdb6ddfed832b7290d3fc6c4c4c709c0e7a6cf8c797ccb17166dd1c3964f118a7e9011f2523637231dcdf29f8bd48d0b547560e1ecc6fb179d5d3b0ae6778e69e09f4f99e189a7262adf85df18d8e7872e9730f3116737965b604b48cac526ced3fe1f896655c14f7657d4b4e83e7f5b3bf77e400d1015854f5f1bd6ae11eb8c14cfc01dbbe1772c1697b04fc8d0fbb3d5d8c0a10f002dbf1cfe22aced64f5870e270071285ea6b6012ec693a3fbadc2c5a9f7ea9d67bd38465f8e3fe2bf8137a601dd511e6e172a33745b61769f81b9cd3282e07c3a0d06ed3b18780c428067ddb280576863d722e4a461ddb54b3667fc9117d17e7ba8b2a7243f2e7505ad09dab55112a32e572d320e65de4efb86410e6cd63e3712a44bd25a3232168d0aa4fc2a7cacf6d1c728c561460794e9ed391b57338ea1c8d27f5226b9f433cb66e8f3a8318fbabe67228b644b9b315cab194f9a952d10193b46e40e9ca1295a28a836f6f37bc927b72c2928f084e298f346c53ad5bf82a7213fdce419d1bb86bec1720b2e1bb8f3072fe1787a6581ce6b2f57f3cfc2cf7a2d34fff7b9751770102831c7fed7d133d01b2e481ce151371b9bf4c5e9dbc9ec2e912f26357b00c810d0d34ba483234fb1d2e76bb690cf67ea76328c1110e5de822813aa3c4ec8f6e258e40077832f60f08e5bf8fe9e57cebb83792134014629f82c3d28ef786499dc10464089efa261762f0b190e06ac8b2942142da49602ab05fd9e01ef8c293bb60d82f017b216a03165c957c9983ec20f22108591b5817aff2dc763806b189654120971738ac234a72862abb36c716cc9945b82b64dd17f00070457fbfc75a4badb57a1d30b5a49e72be33a54235278e8eb7ab707b27400f95fbf37ef5126babd7d683871051610443727302c93ee3033565dc34567707b3f0e8e8a224edf7b9e1572b105892a36072172f020818beb5152ef92c78244847daa699623b687199ca92cc92aab5bd773c38431fc697cac1d65c10ac493821059513e5aaed432e754ce64881a66c6e5d720c2a4b12b55a553e09b3e79d69c455bba144f1b4ae6e02502013aaccb4693e72a77354500955de9f22cbaaf29a078e5e6690ec3f8f2d5392684ff84a30821e725e16d67b0069309342145da54fcfb1bfc5e68c04fe18f1ded01bf2f28eeb8e20a7396606aed308ba17650875f2d27956b5cc8e8d239edddb8ada2aeab6dd1c72e340ed521a8609efd221f0dead53cb3f8dacb119b58355d781abc8a96365114907955d0c87e2c952f4b5f0c0da6ae292f1d4255e4ce2c8ceee09ee7e0257147226e387f9f1236e398ce6f4e5a83021eea232fb536b81c162bd1b08f4e7418819a93b4abb00a3e55b741104dc7cd716ff3c9065fa229b8fc54bc721014d2211723115c901663bc19fdccbbeac9a2c34d29bdacbc17f12f4416ddb54ecc6c46772cd7ce988cbcc1f61baf7792b3a7ab3c617b9e1099fd7f03f0593c042ff7e505b6904e7e4681e974b9dd7d17bc25067c9fc6b29e04b42818fb31b4aa2b49b0a6a67c6fd89da0348bc4d9474f027fae76e2578e41c4bffca37521b1d241d42ab72418b7186555beaf28a3d413be276ce2361ec76f802f6f867983c1d211b357f173aa845d058bad135c518abfe64e91b31716e2fab130e51ecc075e3af8c92af422dca5ddbfd25736c9e5ce1d02f7f4cd39ec8215bdd95ece0c7a2359ddee381071b11ed5586365a6b1a957713b9faee3540bf7545ce999088751a389735e6b00fa7974dff9862580617ec7deb1ed92d2ba35a16131df5df21ff264c7b6557d272fe2a99a9c05c56bfe449d8757e7779eb803a5e11844090e0f22774995e7ec9720076500cae0bf5879fcaed57e86da29d381298419d677a0e6b0301a452239772422f18bbc2232ae6978343aeaf8c7f67051abc6c50df56dfcfa24ee32bda75087f3b514b2eba6498eb50b0be60f30cb94b6589fb66a4f2f50ed4d5e193ad9f312c691a67213061c9f5156862dcfe87a20638944b40938cf929a0d928a0ea4272d10e4e7294292eebb3c36395e39f077a9049323449d3e420da270835f90337729369de8839d599d89936faead98397a18e27e9885ab2bd0941c6ca864cafcf026ca70fa626a2671738ccc2a181ed6ac296881ee47cc88275ca4d96aa3307b072801debc5b4a68e2cae7fc7d0178969309074fb09ee5195533be70285b2836d72e57a120f9c9f181a16fa65440012981df8c6a5dfae991f29d05da6654fedfb72a7a6dc65e3f810225d320226d2dd89ef27d2cd1aaeed62a61afd449f24450472d0d753984d0911080557b612f47b1994a8798fb5771403c03c4229f4f56e77722a943aa5f32c235222f3260b948a33b0a90b94d4f7031cd48ce3974c3333e3728b6101094dc11d9071c2f6a4c07daa4ddebba38d3c85a51ed7caeacf475c9036092a1d8f7b4c99e3df289a7e7b091df63e76167a6dcff42bf2b1f6000006fc0eea79b062b7cd3660dad74a37b515c637ffae4783ff7c32380c39d2ec04bf3d3d320f1dad6629dc7bed5d3ebe25f3c88ea6259ec59a7fd60b94d543b497e3f32bbaa42447af90a0261d87a60db859966f031bb8b70a4be991a50e4e3a73b0237245593b6b7493d1dcb67825ee42491f5601b167a773217c99561d04e357bc95366b04b78a68b2063124dbdaae8266e3559913a44554898450d65fbb1a3f940d3697c858426bf94060e6df64c6feb839d6b03ffdd44bedee20d02911c1f17d3e72aa778f6ffd32159fd45a2becb288327e4e23add2243341485bcf29f58675e772f34372a3bc256cfa0ebbccbfc574de7f3e45f76b7269eb29dc8580f84f9e33728bf117ff852f32103df39fc2ece2c41317b5950f76e1d242a1129cee5efdaa72263f9e43206ebee74f9e36fff04c2afda6a5b07285b6570a57fcabdebd24cb62045fe04643316f4af4aeb0b078d1bcb588684697e8ed2239b43e59788b84131e285f7108da92560a5cf10773dc6c19dc8bcaa7ba48ea1bea40e59c45e55fa10b469a000cd9bc2acdc500b055a07998f14b6a6ecc0aeb179ba4942fc9e0d3f1686bd8c496bbbc929a6d089143e08dd0efd29222ee400061b2ca4d60f32fff530f100e8c4b054030b3b326d095e4e520fdd5f8359a72f16ce5404a0a1ebfd6ad0f98743651ab80928f1f7d82df9780107bb175e584db20719d5f21e88e70b38b7266c37e44b61806c51e8aaa2a08281c71ad1cc3bf875e113c423de2a792609c7269b5af2d281f8e312dd0156cabb0ec95c57f9373771bcd4e70cf52e28a2e35312d53225c9a049ac79b22a6ed5f37e8a9dda2292229633bbf4a965545b23768723a7061d36d4a49034635525fff3d9d04866509321870ddc008cc5f32b25f0772b688114016feba16a23cd570cc2cab53ad7260310fe91de43974db9f7b7cd46e6fc95499f139dc7a40bdb7137e2cb9b13f122aaef34b92d81b34e911e5b4234ec9a95aaf36369a87547a24d0745f30f84f6a1bc1266dc111a400e5470263cc3e3ac989b54558799c93c76f90f4a64c25b2cf9a9c9e56e72306c26b722412057294487c5eac20d9ccfe63d07d24ee96e3f424cbda7d6f6d60a1f13e64f3563f725f9ddc105afec735fa97e180170896511d4d957a1f11f1f85f7280ee7cbcb66afbdb229248163545539eb43d006bcaecd6f068c40459fd9b4f969b608a00d37230c539796ac1c89a3f8c0fc56a272a95640ca0d2590746eb0e0f192337bc907206634b7e82c29868053c42fe52e7b880d74cdc3a02a98f126dd4cc90664fe8723a4b34d00677ba6d0c5489647b6a5bbb72bbc77b43bed84de1860e17158cec72df8f48e9a89ff5a2cd3cee62faece3d56ccbdf631be36f5dfa87f1f97980de72c16bc7134e46cc3e51ac8e32fc9730fefcbac478e9d9e9ada4312cbd075bb9723acea410087170fd7efda728a9ddddf60ad6dfebb93497a29f4297e96088627235362d82c414dd3f2283e76abc3fb4f7538106436f014ead21247fae6e6f71720395dda35a22bfd1198c4e0a5f9920c38b228fd98e8faaee3f8a7d5c17175959f736ac0050eda31a83eb837b5d2bdbcf3d62b95b659f56faca2758f1fefdfd72ed930a789bf0234820952f1ffb63f339701dbf0fd9710280273ea0ab46088759eb55872c871ea9c705d841c6af65d54d543da5cd6cd5ea898d5a37f733cc8b4b88f2cf64a826a0630e8924b1277775f275802985993c79fd421332f3a8f99f16822808d5938eb302ce7fcb4a97d1091a686d5290b86a2a195649cf6bd3862660541c4a7132abc2e4476014aeab299f6680b7627593546057b4107dbd60181e72314d14d7ddb46ab72dc018ba022bfc1f9845e45faf5f9d4b3fe762fccc3d45505be526d6146ac050aea5a482982fedaffcda68ed0ee0eb5c808889ec42546f0742900dc5f5ae6ac4b81f0e1cf0d5cd0f011d18707c6457137466187ec69e5d72d5fde563a5f93beafbcb2f91ca5319ee29f02cc3b6b3bf6629d2ae97a143382a50e3c4348df5c476ee62b89a52649dd0a53e8153e308055312ea185f16f82972eba1587c283e9b45d6eddb7fe25c4899ac0cc79676baaefe4ca612f5bd384d7264ec310b04dddf03bda054d21af51f6377beeca2933b3624dc389aadf140587281dca584fdc226f29c64c6c840b40796726b0c3f32daedf109d4d04f8a2bcf726097c7074faf023cf29f56e96ce21b2017a693e70995627a83241715ac5ac12cf0bc3f747197ffa2c5e081c59ebcc7dd96682d6651d09b4cdc2990a9f15e8b72143ac440c62fb6b537b4a040610087ea4faf2d0fba32ab9698d9d9b70e602f72a947312184a4ea5979e7a1290e67f4880847672a3452b114d7b52b32458b6372f892f7c8e4e9d910d32f2800d23f4a8bcbf3c7801d1c5ac0936758239ab28465f564d19636f8ba27ba0adf5accf8591d214f1428571f30410955645ea5b30c72a7d7e004085f1d50a731ea387e11d1c3994068d0c9fc8840a1f4620e9fce6972de78b35ede8eedbc56af2def56b428d4e087a2eebd4c4cadd557f7e4d7d4e272f23046fc079cf5665680ada29905b36d9e899be0b8efdc118af6f1088b9eca726b70ba087da0666298b4f380436b1d399fdc37c904c516c3cd5cdcfebae122726f62b87335c8418625167d2c4d689d0c38bc0572e13312f25709476b47dff5554e6466594dcac469afa2e6d7973f0c1dac6a43c875d7ef11208bfeb1ae5df9311a35b2c955ea6c76eb018b17961f239bf9778ed9895b7cf9e7e913c14f0cba721d4b48bd4202a1315441a9642c3cf2f57bec2d5df66cc1c9ee1e905237830c3a68c61362be866f836501dff181c525b7e76410d0be691eddae3bd36e5efea372635ec84b20edc1599657ab0141fbc5cb8df7605e1034e08cc9f6efc86a4a1172a693c807a701ecafe11be54c7b706efe115b9b8ba22b1744753472b4151758428a4260dce1f915d0e305b2f44c79b0224c44c42ba08515c7f22100cc6d5577722fd862f73e7ae65531488c4fe7ee1ba6ddf91fb9d9860c184d8e943ac2ce9a0297d31de7defe5e36c8a2637677aa99de2a3143e506180021ed6b33b2e243fc45e7515c941e4e9c0d9327f9aad1549e57ae674251014be03c0362b3e5460b4214321d66285218ca19ad3a5ce80757b8596a46331608bc49202b5f02ad98f57a5ba05a1800bd9d9c6e989ebd11986da853370cd5b63c0b557c2e191fdef0459b0ae1ecaa2d7f33b8d4bf95f38827bffb9e71defcfa1e4d2f09b0963a0b848ba324f4f23b92a50cefd9ca1c950b937c57bc51b0499b01bc0808a83d8b157b87be2ff623c8d9cb9dbb25f6f6f03b6a4cf37b5d4f38ce647913f50fc0dddb5731147203861328d6bc2f8c64df3726578eb3183ee19fcf8d7669f078bc6e76e82d12725b2a33bfe1d2e06f42ec5ed445ed94f9f013d830f099f94fa940f6fc9859b562d02bcead8844b61d06212900d3fdf0b8360aa710a8c4414ea85f893d293d4b72214a43ffdc37866d0b8860ec4fbcc12fd6d7d52901d5c7c545d118141b81fd2307ce3f9e3b91c0c58bd1080223d505aa530315b3ee202f8c832a475259aa8c2d1ba55439dac8376225973a377063951bacd0f6317bc4679c78d273b06d1f6e72a5574307526964d4a970733c83ca2749a26d27e419ba43210bc8f57702916672a4ce0bb7661b6e8dd561e2cd7e75ad7c80e14fb075efb26c5f923158df0d6e04e5f25a457ac2c0100c193bd59f25deeacd04bac3f06731983b0886adae793f3db940a5c9de6fd947f82e38d7c49e3e0c56a0a84856a1b7254fbd712669a64a32af16c4f4ced3d7a41ea7b9c301674a6877511bb8822bcd15db99f82378fa8b726a543d446470682c89fcc192d13b2852d667f2334d5eea62259e8a4120381e72917ecc1dc3fb51121eb3ff07229ae70c999290b658da748018d78e6cf37a577248932081016897120495ae250b83ae00e1e3f905fd224d0601a69091b58fe00de58c16843f1465a667afd41046aaf94d23793527d40e86bf95f9a49d29a13d723040e7861c393bb221b249dc6030c6bd994cc80d6822ce6a9c56c26bfba10b727c3f651e2d08ad3099f32968b7d6d28a029d6b44932a22eefe2a1ef78f03a572074fb0002f6ece886b3fe992a85216a1d67f8b6fb9413dd7bcf6b47a72554a7249a900783121496a9b8c03b41e1124ea9b31afd26dbb1a9e2a2c79ad11f57155783167672043d1adc064de3970245f38c169903b1e2b2940f9827b88422f7a724993da9cc1f81885115200c896f77c221ea2b4ffa65a370501c62fc2ee8578724433968262db155c77a7edde908e3d097eacc1547b6f5153f7d9a49b6b4da447201e833c11824fa5f6b8678942301ba6b0d98205e2bf6811fe863d4c9eb537725f72185973990d9dc7b4eb0eb3fd10afad33ed95ecbd3888c3f93e3e601e31720e13a93506087e5a18f4d21158c696df50d7e0bdcbad56568f32004490ae330a6bbbdf9fc35c46539e7122dc4206e60d62fceb8503f4fd250af1dc55c8b9c47214400d231570f10aca255950af941069eadeb284d80e067f88423e5ab26d0c72bc170c8e78e8f93e4203db0936c986663a8a0925e29a7b4385c54f400dea853c52f03bafba08a35b191f2dbff011350f2e5c59d67dc3a8869dbc8cc5fd70ce01d5472c125cf43d506b08dec3cb3e2a017a32ec0850d055354a45e0a58824f172529173ba56e4323ab2d22a876870f886bda00348ac236ac1a13fb9877d9d0e1b6325f06471bf55653882362af10cc5d95b3934a87d95056bcdf5814aae4be972390510a14a881daee14ea227f1979268aa0e7e0805ef9c612d5c508f8a80fa03ef879ea505c89c5398a1cfc781affb93a56749ac9de8ccf02d7450b93791e472f613dbd783c08f6cc6fffd883bb430f4d74df73d05f74d4e6d497c3d90cb1d72778178f7068d1db79c68f9405bc1c16965c697acb4eb83b6d8b7285b8c259a72d0dc4a211fa8da77ea9a16510f037604cf856b90750dab947b8bbf5f1b87e339d0d145cfa17069b46fdc2ea3452ecd0a00cafbc246b136b0df08b917ee72d572789a51d0e7df3cb90b9f643c04d4aacfdd25ea00a1efb93d321bfdabcd81ec72c75e7a036189a8f1cd0e006dc309a8e6a1fdbb3118ca7a84121428abc4af2e72169f72eaf27e6d5b8d30694781d37407495163d684368873541e6f1633a4ff72ed7c98b923e553eec1af0cc33b0e2367e598d4c7fb7a24bef3e8c819f9f36a3d327856ca5a037508311ada0d82fc6b1b17aacc5296911fb120b09b076c56217208865a541a77bbf7ef028fa85ebd6b027e87d684d34f87f48849a2f13824232cb692cd72785b69545f83b36e54ebda00e0209b8c73c4156bf657679373f758722ed83ca64fc377e8cb4059e898e810015e2baf190e2b216d289440a33976d9721cbe5eb07325b0e2448f6507626989075c034242c63b1acb680962ba46af5642d54027d1742ba8becee044ae72d114671fa538d0c38600e1e4f102aa25fc70014fe021374d5224992f1678dba2d7107b6a65362b56beb423d8363ad5355d790189859e434e85ae39c96471714e8aa64c88d40b69ed7db0d0374dd129be2aac3c80da8404659221e498daefc4f4c412871901c0a108c6c6558e968b2de8792014cbe5283270907322154453af69097d3c25c58bdafd095c3317d9504888daec72a81a07ccb2d2a71f29bf12722075e944ce9a157854a8c16e97c79a06b42b427247534d132a4bd602d73dd1cac4abb72116b53960bd5e3c70cd1efee9658d3a727564ce4132f84909956a7aef61ad156d39b2599a861839917e91f4e161e63e7220b3468b45b2af3d2da1f6f43e7c32aac642e47a608e01555f362af73d47d272884a490d2a4a7f0d1dc913d727b1fa13f55afa9df744cd3e29b2a5d929d2a842cc9525ff0517bc93083c60dbfa1c98260335dae9ed872c1b7c20af5d7d09f772717557cf5532a9c29d8095e2d706bc8f4fed9e7161b2d8ddac1082e4b9c510725feb3984c76e777633dce56e36b8831091dc28dd4003b92a6e3a045b1bb61a72534ab4036e6c926342b4237aacc441b3f1f1480ea1befb0d5888d9020e5d677282bac6f721f62daf9ebee8da55f0d18dabd53a83d10c65982c7a5960770a53438893fbbb8715046a28214d3c8c3f1816c5bcb71c53289dbb31b6e7a2762c27724a722c970a9c3c59ab1ea5d3809c597488f936988ea8c7e1b81926665ecb2072a3dc4ae40109c0cd65d891ff224eb2e31e9bb0f0ff5d66d22fa98b70e05cf2350f7d8a54003ffab91a6d85629f305bfc64071528b7beb1ed2f462111ba73d4040f76c0f3441b1988e212db64742a0d9d5a3ce6381ca9a98ab0f0941e30fb7572ee8da7157e11225854dd7f2f4e0a64b8f3b531050836d691ba77d6c715341572ba90cd046ba03602286d3cb512978304bbc5b353e5abce1931d44d2ecfc1a572319986427851beeb6d3cefb733db4f3d06361ff9bd436196c5bfa331d38c092778794ae83a5b92502dff360c7e9e01acff110721a272446b15cd98267e16743a9ffe5508983e7736956a11aaf625594a43f634fdbb888fb4ba52f56dcd4e56689037027c031d37bd1a30a5c7edacd07fa342834cb3c98f523026379375783471e30e88d6603a82083ca9e7101a0edda1ec24ef77b21b59f1c6c60c4d0b2afe407e3cc86798a3c43559740499a5abd59c2661b43ed4741b85b5eed913dea206727d2afe6b97a269fdf7f62d15827cac858f021a1b6e0cb8fcde3fea6eb55063341347ff920a5a7c2bfde11b1fcd8c9941678f31c49da32984be7c80b7a902e1722d46730f8f6a27853dd906c0e662da2ebaf79a57a1b9e3daa853ec396296cd723b2f6f5079780ff4d140b3fcf38a42cacffb5a6ef037a0190deecd49528fbc052c34b1c8321a3be1a43005d4f3c80216ca1dc22637a01838c72beaa6a2481a2d38d8073df0377d495618fdf57ecac63429ff021bf7cf5e8fde4c06eb338bbb72794f5b3472a45a0b7e93bdb80d700a8d0c2d5e001848224a2ad8bfc3b1ebac722857d26f6d154ec8b255395bfb4a6af89e3cba2f9a70493c673530cf85db4050c4878121b1131982099de3f496855909b6c4d6447c1c2c2b52fa4bf705f61a54db5d7a16006accd94a3aba5044259fa78de68d6997ca06afbb96169885cece7262b177810cf262e6fbdc15e731391838efcd1598171b8905b758e3230d30c36705ed16e7a4f3824b712bd46cb2c7cae2fb7902d1775004b75e16afcfa3102c72fbd6bf3ec7a582bb11d9e1194689b78d7575b2bc8bb518ca8c32814f80e52972432ec5cd419b1b2d5c3b89b9ee90f331c04d6e47284ad6194a5f396baac1ff68eafe83e44f6725b646d7512ac72570f2b9a719817eed0168456d85583e116b16f0fc64e9600584507391cd22b5ac8b61a885647b81dfb0ca460ec96928c57a499029bcead1df579eb646ec427cadaa82bca7a01049971bbdd4330fc304e20967b945a52c817ad685ffcf675ec998f67a45f36abc048bbb94599f6896a290d1725f05d293171e83be673876e07f9c117f31e7b39f106ab7d5b1903be6c7b9887263bc41b7e6f4e7af01d55b1354adaa1c4d90e372ab9008d7f22d26f752282b72a01f8fd4a4b0b6fd0e3185afa67ae200f9ed38bc40489d8ebcdf928822520c7211e6dd15f62b5815376edb3caa2a9691f647caf87a129c47e59266c0aec30772ffb33e1f225c37252d608a663086ab639750d8c7a463e6422f3876086357b172418c67fd971b6e52d8963c167091d2e728512594408cb48cbabfa66672da4b0c16699f8423f6f08e84558d2a7ce2e7a353c8585a65a64b23756c347591e7ad724e4e729754d2f5170ec82de0646e36bc32f0a35ec05355868ba75052073ff81d57396266dcf208a9b227c407f8982d724f543ffd7ba313e8bdcef4c3d230cf72e85ce75c1787df72c178ea43f20510541e87b955ce077d2987a62c8490aaff03300a7df98f6f366732283664ea9fc8bb72233f6049d748708b8928f13df41d72544ad7e66a00fdd80221f698f53c22be5ea23072a5054aac691d255b986f7b578d919f859da29b7d7afd9d870b179499a1b2f39d6b0de006849b6c70ae9a40203c025383ee6f840f237f61bfd6d088d3b2de611e2cfcb612fc0544969e9c8d7247c3c786738f43b9b044b08d1396b599711aff3fccb1b3013392a7490e5761725ddfb74a7042a1ab91ca26786c1491e245c811e6dd6afbe50d67478cf05dfd72e30f33e4b2eb24fd400c37d0d9b39d2c9d5173042b288c6c61d8c0ed3c2b435219c61ac2ff3870c785d8ccbe48f990d5af75e8d4d50525c769d5aeef00ca8c725581cacd10fffccf578138057f98450f83b3d4a9b99e15cecd21970ecfcdd672d6840f23bf824097d8fccc4457e74429012dc361ef5011f9095fc76f54325243f96beec99c7e0ba453f760617556671fb2222388dff64a4dd9f7b9d5e427f572d0f287def8e2ec874f0e08b91cd592557cd7eeb2391737f4a80207b2719ff5726895d6ed4f6394b233e451a18eb51a7fb4b798a1e225c98391df03ee886af3725d488bef1ed6006b737f9ed9c9de4c53842b5065e1113a975f87192f9f76b1728df33e24d0513aaea4f401d6088f95f868de113401ea39ef60b6e3a25a0ef172f24034855e107b4f53b524d445450e53b084c10e1997f7445230bf96b0f8c23c3519359b868803824789944e6dc4f2784f58f2a7137f22c489e5480564af1f2b586d7039d07b00996acd41efebd8276880ccbe0f217adf6f72597c7aa5cc5719b4df484a73f198de3c8a17ca2166f09cdf5fc5f48402f7e00738c5dd8161d851d2e84c4f7c0474bcc5d9c8d7c345b8d96b00bc8436ea77c2d05273a3ca030b45783b145e0344d6abfdbf322350d1f1e4faa21ba30be6eead657e9a9e0c88644de9fc02e59c19bf93998bd4a8544a26c8d1e4e12f8068cf2044f38bf57d33a372893c7e54118aa26139c91eff850019cfe31b14f903afc17420f054d7ad92011ff10783c802edb0a546ba43226c2a22917c4f2087246a3084c30c133ce06b2072d63bdd56a34007eed7142804ba29c8d0a1f6172f9bf5d289b9f66cbf95a7be0fc6b0bc98918750c2197481e76588e5a066724e0797245c109e5f3a4de9f611500bfae3d653639f950196cd14b1eb78e297d0466de614b7138c705026067c7f0d858255a942a1ccfbc0e68f8e2aad8fc5358f73ab633b4eee494457f7711da2231f3c96179e85cfe20cbbaa592f70cc0676359046c15c4cbc9a7f1f49ae271a72d1b68ac27360847914546197fa475021f92b8c7fb7e24678b281e49e0e849722457b7b2c8de9a98482711e9a03562f1977ec229636772a78aee8afb422417e72e187835cf66ffba449af295249bad83a23b7f9f9ee23a5acc9472caf7686604a278ab084f3e5147a8694c35fd48809ff4cda92f2ccbbf99b8aa8b7e79f8cbb4b75a8841715df9b403f5bd4a74d27bce2c11be320ba40e6a89683c1b1b9a47a72ec9641cba9d6e2ea6c44207657ba693f97e4b77d129304237c237e8b4ccfd86d11aebde7cb9a9a0dbb3883ec60c38b0ca974448fbd76dac358251d5a8e74e5725767c7ab76e6d56ce32f8d08666bb4bda6a08bde976080d9b83b391cc54be872ff6fe6c16c9dc708d3385c7b78e05131824cf072efd9961e796b1a70950280724258cd1f9748e5e9fb4e2ac98a57a11631a3324f331bf4ab90bedf8a01b83e1c4c3dcd35e0d7031375c9ebf59d56075fd6f65c78b60bb152c8f5338b2f64af72f793f4de7f1927c09e270b7d4099c8adefa1e7c98547935554244e1515c5c944c2dec28866b061ad2877066af1e5c5351d6c06c52fa0308c6ac29b25498d882ffc5d5be47f004baeece057217fedbf91d5ef3de23a1d34c7b521cae2ebfe1e72ea715af27a82f01f030139058770bf97d7178e24a1f67108ad8aab2b673049578867a3a2bef4b97f3678ead662ff901f6b225ce9fa5aa0104afa3f531af3ad2e94b4cd7cde99af9e8137041613ab4b43dd959736617c317b2953ea2e2f51ec5e1778d523c1304b7fcdf2373b5c3554297f6dbd9dc4b13d789871b268d7ab757246e3bd737fd18aa3e6735ca74b0ff3a546085d083b2b289d2e14393ef29563472d8420b80d6e2d4770791cad8c61507070352df1c7a51760a72c34b7489c97728d02f33f0f4bd5d2838a62730c5743ceee3ae454a9c3dc1beda26fc8bede4e4c45421d8238844557b64938e3d427f8281302a2a7344faa87d499e36715d34472fef66340b822b026796f8112f131197da49775927e7e2de1f55ea1841b316a321f87f729d562219befee46e6362ddf5430236dba7678e49cf2ea0e408efd075e56d85fa3e910a5a8914f4622b7d8f4b89cfffeded42d1202005cf23e35e34c68879027535718488e751fbdde1f59c5f9effc5f4bc25b12ad66b9bf4eb4099e1e884c1ceba7a78ea1928cd64524533ca198e421e164b0d7502308a2e5bfa04972ab4b55a10665b7018a05fe3a76b1eacb892df73f66ebd4709d912450270bd97291715e8679ee6416eb321f92f16555b2aea6a81c9602462c8e47e441a8348a72ce22066217a8c9138cec8feb79153776f67749d478cb149e107b71d7ec671b3720b999ad2a937c32aff689a35e5f4b449c5701a241d6cf708e1bbe1159701872d3f25d754a24436a5f4b3b2c7b9bb07a9784862cfe6bc06e8cfebd098f156d32bcf5f5b641305dadc971331791c056f6b21b7d9597b8a44a47d8ffe7853f0e72ebe177a9f5d9e2437dd282f43ae96d956c2c6556747b15a965612275b6eb2372bf579028c4f118fbbc1e99172446504ef956c930faab5b0bdab198bbba4ff0722bca2eca5ca69b06813694627bb809ce6e15252e3eb84bdd278c6ed9ed6334723d11f0c49a624cb267062138c3b19d3018ca161ea301e27007aa7780367f443ec663a7ee968b824f41deb47641b38389780438de8fff742c7a85e5bdb02251394f458cea1ab9e4a1e206fb6b7174388c1d2630b3955edc89f3a06651e79d347284daef7b7487da3152e500ed59316bfdf4250c3d446a3917415f68d19505536c03101cbf6aebb4e52d7c2c40f2d32f74bef361ebd76f09e14a04c2265c45ce72ef8034b6ad8306e7b4329dac1800490ffef687b0c7965674ab66745718866272a2794a3e567b8a34dfc3f027efb3ada365653c4dd7337c9db71e784b2085f41e06358fc7e06e92934026e927397cb3295084f6795d70aa515088e50621e30033292c5fb92018390deaf277676ba3708eba94fe11dcd66350300d09adf9d6f172b92fee5b959b51e73a0692045fe3fd2b769bbc53422ed7bbcad11a24e7613172c0700a64d7012d1ef8f1fda4fbe45351e3701af2d6c0ac6fa4328a1b0adaa1729c428213a91fdec4a12e620b8dc6f86e30571ddcbdd7c45d2556cac2b4fc8172a0f1f04fc1c4259e892dcf35c730bcf0e7e76c6af7554ad693a3a74d73a4c672dd7d31fae675ac4ecad93b84fab6b1ed9e4ce22b8bbe1fdda6baf51b51d82172a53ab1f9bf4fe34acd3bb58a575c120b7e42a62089dd7584f3ad527688e2a225a61b43e0a1df83825c7cf04170d72aa979485ba799b18ef58e9a6420782eea5a21e6d1cbb598f217c5cfa87ac9b8dec348528b4340c7dd01159a14063e93291e8f907b81852b44176a98c1b89cfa95e127f824f4404146c1a7f8c982864b3b72a6bca3ef87d20c4a3b9e577890fff9c3518e48727cbed6d2fac087bda3e67a7240ebbf83e7b12d998d46a9d98bf6f98f222f7bde831aa3f32f7cb89f7a908b1d97ca975a936f26daf4cf6ef3fdcb6afa7a150f1e74d576cf9f791cb264eeef721b3d675bf0c7b0c0dbc799109279dc35a687ab8823ddb3317bcfcc76cebb891eb4a5d9dd7634aa7275d25cec3c77de67e505d9f4cbb10f9b6edf2bc9e74a7d61b2589038c831b3a78b1405af0b1132412f807d58d005e5e398b9967cd513935c96b2b5aa2ae42b9154fb3ce56728cb8403f679728ed7f6f97cb4ae62ccf114728f44390f4788f7820a5419f71c9e0e9e392ebc61d2a892eda0ff19acc8039772b7f8b600c7771bdec23d38349d24103de4eac823b362734b93f186773ccc1e724bc372c797e529fa04c061718eb288df142715e5cd57c33cc79dff6affac5c2f9e19b15c31cad1b480a402bb57501f579649ac11b5b7b976a92dd826ff44725addb2d53e34017d7a19bf3f8283dd4652c0addb934cb683e7f3f72d22f1d1b32b2124f3e8c8f7d94eec1f2e1198d1c403a01e65da2f3ff0a37996c348af724e726ba912dab69504d197ba7801a1a28760de98589f55145b3571e1235afdda27726502b30f0f2fe11231d681b1ef6f2cfae9c756a7ac9b2ca666a02aa3be7cd57271ad3ab5c5a5f4d729c5d63d83c126bd5d2e8c11a14c181b292fdede8cdfde72bf9f17bbff005f0a3963852ba7c62e1a3c51e0e3b078c79e1aee83dca27b3d7201a078531c13304fe1c44738d1ce6e663966da86853e8778ab46ed3f13f4575bec8eec9f0f65e9f13139b58f731474e042e575ba7f095b26c0c6189c69cc5a366a4461ccd98d05e13841700e3605e6ebad2d803a05ef553be55b1008ba600347173bbfbcd57c8e08de49e9fa3c12a64d1870c64ae3d1fc84e44b01e6a25c3b29b181ca0331147419fbcb1fd9433764c59b2161afbd07d72d2bbdd4dec14a1172b069a868cf1e88b0725d7e84a62641fbcb10f80c9fe3284d59196af5f905be729bb834ae121b8330fb628f73b12d373ffee62ff3c6b47616a5accf8ef4fcb0711a8a6c68a58406d9020876146a6fe9fb47d9d75236561fdc4dee16806036c125b09e238ad77d7b9f66ca2ebedc1a782f3fb3580d8d9516914c7e01fead48e9721acaa894a584e228c38bd7522b66d7fdea10b55d16e47ee892a81881aa1b2327017f31a10dedd0fa6ee88b1b187b847dfdab5c3d5c35e0f1d477b8563aa31672d5ac8130e1768364c111e88944ca751566cf5d296f7ecdeaa22bb2a5b764c74b7849d5fdce0aae60ea15109b36dd83c337ae8cedc8deb781867fb449eca3561e9442bc68752cf879b53e1ff83fa777c088e15f61c50fbf7e233c057f9c75bc72ed7a40bf5ce551d3638af44f1463ae76a56048a86e2bb6de99265a424691371905f8d2dc807244ed2e0fb0aab85dcb8ea0bea70e4effe499ad10f4ae9e6db57251ff17f3df38081c1ed789cd9e8c5924f1d3366df1b424cd2b7834522c6bdb72169c7364f91f027b85988d7a8da2d6b0d7855acfecb232cf75651a68021b3b7230d24c835ee6c48585b124b088604bf185971441e944b8869e78bfc033e36c7289e24f9031247dc8e2495da8f941bbd1c280ab5f169961cb63fb91d0046d536e2bfa474297d1d70fd8d92ee61065038034afb700af16db8109ec19b4740a220dec0da3bb2fe06b6ee2d72d197f24d60df658e04d0a523357b2aa000dd635a272f3a1cbd54c41c0a5cdd028f9d480305537d839d14aca9e0cafa93575c6b841720a3d2d4335d4ca14afd3e6498dc5291c49cd314f95f2788376f306af4c4a547284916978f39490428567d8af4891e592bf28ddf6cd8150a379a94bed95da4b539d38e7b2d69f0e8be0e2b96f57d26c76ebb0487fb65c9fad50ca7d04279ce772b3122f68964be5b171ed7008eae3ede0783483254226fe5b267df1807ae261123a9df3e904e60f516fae20ec8db1bc42c81b3696ac1a0596e484b7abd79bbf5d84e764dda4f9965b4c629d62280dd6175c220f99731aec6b6a38b93903b4c972cc55508d72ccb77dfe3274a010172a5aed6e8ebf43a9c869f422ee128ce68321d28cc2b12212528d6fc41ba11e5eaf22046511f1df54e38301fc1a73f01b456805d54fda826d938ccf4ea3f0eb9fac6fb324c91d36b030a266ae4c5841f41c5837a9544305ac2dd1b1c2b15b110c5a0eca5a253f8279799ecbcc57fd222c597225f6a6f4087344a65c3349454054a7845f833c83a53c225334c1f2039e4e2b72bca8d39db0f679365070b888e361671e694036ac422b821a6caf54a2829c1372c9841b7e5d50be18042446ecc1fc0d6c145b97ce5cefcf4b4c281b85a4024e436717eabd17e9e43633ff73b9201a0ae28bde715b808375cadeed0379419a010b864a0fccd637b1600d355b6277a9f76dc17a603ba22be32903087ec1b4d2be72d599759aec354fe10a4665bbbbd6982e48917e5411ab3ce0aeeb7529a48cf07281434f3ee7bc9f31d97841e327e4f9ffa2287b7f925d11a30e780566ce0d3c72ef7d94ac5c45fb5d1e29c4c8cb9a17c1f19cf62688595c24802a9e15b1f74057cef15c78a4c165230e6e8a5f959863772141d107df1a685b7786e273f1d2027221192629cc98182ddbd5489bee844542bf5f7f0c4496c64d6c06ac18af15757233d2938ec402f6c97a43649bb3a6505f7a9be9eece11ad7cf0715bc507ef097268894d96e3bd42fe5921bfa6d341d6ec665cf44f8892ad4ed078a916bf0b9f39e04c1c956f0026eb4da2fda97c5ebd6cc792b7d0e3e59d95d51c93d9b8ec84726bebc78fbc549057d550819d40419def4ce1afddc45c7f05ad12c6df5f3228720a7d1ae0f6998bc13d35d58b05c736b2bbc0acbfb05e212b88d4e127178f74729a73f1b84450ec1d953a0a8e888767c88e1d797f28c18c8738df7d174a496344058e309f0d734fb11f1c13988d622da70e2e26b63e39fa0d707ae6d7db36c2722c46746724fa3a01fa5228bd2c3ec4fc307207d8fd2b291d7e2bd142a0492c24c3920bcdeb3bb0dc7607efe0cab21d15ebf299796e9ac8a7d34bd829197a15721ec57873eb731b9b457ccc3895196dee17e647077466f9246d90b987b239172638787bdad6ff6d8ab411633430b265e9fadfcc9f33761230dbd2a78349953a23dccdcd9538f9b0134df4fc8f70ea299ba386075e725f9a4a067fbbf089ddac72cdb3fed1d65649af4486d55092e76a555b84aca369d8a19c4c53545bed127d40ea4b371fb6add3c086a03d695b56caaa6a17cd66bc62938d1892ed863b5f676832fba6e3617c10bd361c2c0b4893ee1bad538ca80d858fb859efe80e84ac63728807b059252ed452f01dc2e2e970bae77040bd72697b09e9595e3d089b5f0972ca29b0dd5a4d715083869ebe3e5efedc2b00e6e8e1299acfcf97d330836d167208c2338a6b42059fe5e442058a66cd72a5339aeec26454ceb25629cb7fc388722187f6c11a10c84c6c6168175d635a774f030767b5ec2002b36c10f3a8e7aa65317fa0f2ab183a945d294afdcfcb398228d8c3eb40028c7e437675c2cee572723b8c42e928d6731662431ab5c7fc1ef38650e83f896bd12fa9d783415c077f72169c25189034034d78ed6f96ef4047a664e9e564aa0e2731fc4dfb2c1f9e4a72c299d5963587b004bb33573c4eda14a85fe1e281fd2e5d66f26f84699ed913725bb9c5d4828239e2d20f7aa7c45537bd3861f8a3cb814719b41bd731cb72ae72b25b6af4bd5ed7ccb648ad0b160f671c9e2e5ce60dfb75176227b710f3672d7205efbfb23886bcc78021e9cc8b409981d4a751f67bc573c2d3d3f25a6330ec72a9a6eef2880874ca1f3e115746ffd5d20ba66d2be7e16f40ecb035f722b4884a1605481762cb102ebae3a617cf3d0db02cd0dc88262b89a5cf8b91d3a9637e196ca60e2e07dbb2ae3674ffb200ed574feb38f5be0974050480892015247f197245b1bb133877a067083e37a16524d7f8067a8e648e7a27b48a180c5cdea1c072f02c8e0efe1f7ef54386883e059a4743846003233f2b22a350129dec96c59149f18a9d022bd603ad6c87a4acfde82c47424c3ff2f1624221728051b31ca51a0e025a01008c6645d3b678419fabb2c0e689f34c424d95d1bf7d5a419667c2877294a6589298061b8bfc59b69d38fac33c8d58549cd38920b9697c75d1f8f378721e08e67cf8f65de6180a5223e4290428f72ab7f26e6137b3822fe827837261722dc070fef7a163fb8c0f9ae8cef347b772e834c0fda8af7230245baef0ec457205a692e6244536c4984c5e368687760347a1fa9dd308aa5b575a512b3e75d325b8867793b1713eea65a9cc7298c38ff86b4b5d5795665d4f8a99e342a1550772d38c5af9a811ad70c35cd7a6efda857d3b9c6d914d302e9df7bb118ea5198c6b9d1014800048ba4283b118fd923d2231cfa7d6e3bb335c2c21fdb7301af76b003bf087efa1c002d5f5d40600b416ef93591f02801319ad20f486d2d0a9acaa5e7d4803cfd448d89569e432c7cbb8f84f8dbdfef30823bb5860bd3dd2f5d652728645a2f15623be7ec63d986920da58b9c9ec8ffac7ec27016888ae54dbf0977230d62fb5c569831b3c93e6a9c07380bf3cd0ead26b56ff8acb81049f2b75103267c7b183badeff5a6fc1ff21c0a54f9211bfadd4745273761ad964aa4129b6402aea5dddca7fec00c904cbeb5b11f69336aacfc3fd243e591d8bb31ce7089b727ee0cbdb68fb629ac7bacd03048a5ce9531ee221b104c08cd498ec9af7f99d7252819573cf83ac187b3b15431254fe35ef63daa3b7bff33ec9b054a611f90672712a534abd8938c6f2692c18851ccb4165fa9ade513ab1348451bd2c58a35372c78d0843af220235fff19370a24923ae5f8332b05040693b9191c72f9fe989687ca3e8f14402a86ef92ba305ca614215b67924841240992e42f4bddd274ca5725bb8bb4a128f3345ea24a9b665dcbb19ad13ebcfec657b65efba6a5298b87226b079ea2166099cc1c914e6a7b218860d2d83651038d60d0aec2577ec4fb2ca66c052cf9348ff3a2f35e4b44369dd048c760101ca59dd0682aac9bf8341183c72975682da890a97249320943a2385222f32cbff7f9e978208ecd8e684fd485c72bd8d4a2691d84b20049364d5fb63e63fb3ee908ac55bccab5467725942c38b72519c3030bacf10b825167345ce335e598c2dda98c64e91a12c6b6f6167029243b89229fa2238f6ee46994d2e0f050c37a45de61ebe1e033d6f765ec737cd9e51153f563b09a9aa2ab8d3dae2654f4deb2d15ebf75a82f7cc4dd5d891439ac872697305c3e1b0ac023b2fbe2d919cb8298f458ee13f0c119a799b5bd7262ffb72acb9088bf76e1fb00120c7eefc872476bcd86cd4934d162b564477ba494ce250787e6e87fb94228c554ffa5f0f10744c66ebc052bb9cbb3b3e13bb8eeaff2072848b5ffa1811ae58c82451dbc25369e8dc557e9f1cbfbf800d3ff52c70e5ce7229d31ff495607535da812f06b90491623ae042de5c5e99812e054d2c526f120800f883138ce1c1a29364d8d37b149c4c052d7aaa2dabba1ac968e076b9f9a1726735f87118f6a445881b9764d89db83b88b90a8ada94bd537a85624cd2fb4472aa75e8ba3639360fcbde0db7d6e40a4703b79b41c38e34b9daa7f4f5b76bba5597f1ef20c5f5f8e055845cc4fae84f811235eae9365eb01f73b4e48051b5ef72ab50d7d0c8f6f68fd352852cb523043f46f42890d1d6ed94091f98c74cf27c28de7ddba0d152e77a176b1822fe11474f80b52303f7af5e4d51a3155566129172626b5aa8449b5ed02060198ce8e361731d7df9c600c2c782446370a04256e572abed1252d77277b7862b5cf738626a48762cd3568497ee6813056c061b947a72d637be5c094577641f14ac4c1ef0cda2f926a15a57808aed5dbb112d7d17b67272a159a38bad2a38216bd327844a79059c93feb9711af35fc31df175f6835d3420002ca208d7732c2a2a9ec179699fc3e2f9d4c9b5a34a552d5a76b4f340bd64da094ed38a19f423d67f1e823eeaf3eb32a90b02b11da9d799643e83f60a73726156eee8cdcf764c4e56956cbd7225d3cfeadcb4cd4b2099decda18d8a220d72ed0fad5b5676f3dbc29b14fc8cf2ef7553ddead5768f171099d2ae85fcec5d720547761bbb389512ab85f62be639eabfc68410352e16affdcfbcd806746b84659e106beaecd69b401d4018e83643ffc7c6d12c2c3a6d8a9327ea9defc3355b26fd8a50fffeb26fce1571f1a6bdc6f5987e3d34a875742ad60c1211b9cc270f728d2fb2af2855624acaaf649a048eeb1cdf9331eed6fd2a42d32e4b5b98fd192e31403a6685d6e90ffbba43cd1f5cb8147e9069dab5c8d2c464763dc7d43c6119d3f8388decd6e591fe6ebba065da02c52729c197ec1b4a7c269817d98cceac31767619339cdd6e0c103ed5a8051a7985f5334e22c6b1e8100eb8932e80ffad723f417537e0e3daf2ca2c6b4e8aff830cb29721c515d25c9dd501bceb54aaab4c176081c1a814e4db6bcbf26d42b5321f4f5834aecf5869ed5b72515f86c5ee2ac208fe796a4c16e29714425ff9c941f25f97954d32fab7ab8f1312eda40fb538f2001aa561d3271ca589c63db934afda9293e5868115f13cb1a9397e3a108c727431c99475ea7a0bc0097b51079cea7afe6d03df8c8038b449068e2ba20559527b2e773d70c4442866532c976657c90d129ef696fa775a893dd6f1e617422e2e226a4ad0a5e9d9306acb9374490b8457b8185874fcb9414ba758dc17b1062c72a6fc9a73cc10ffe47f2f6045f7a37d597858786b493fb0b13eac84e0cd65807297d64f88a406b5ad175d15462e2baa456c092f2c792f1ab5ca3ad9688bb589282837e8bb7c70c2ab617f741b5e9838acb60e3402ecb5c0d07d41763066d3f272c6b8c7998e0c13bd0a72d8c1d041ecd2bc7943405d8a1a719f67c1b38ba0f3724a2250cf1176540c6f00a426ea85b563282da2f9b1fdd2441ad0f834d2d1e35657eeaa415a56c8eab5a9851a97f6bef508e1e2bb109d28454b43e9b2cc83e8722880be45de0d133f21eb2da5ddcef32d27af32e8c40f628580eee472fbbfb672c04c31a98656b0ba2c7a31df04df5181424cc8235739371da24467c737d307728cf45bdde751bf74bfec812020fc5936831ad66159d80edaacb323ff30266b72de5f4d86ea0bdd564cb23aa02c2cc40812dd42028b3c61b6d113393a8718e86b3e2b65feb622a1c13b13f37d7965906422986fec1b4cede37540edf99de32472a98b10b9e5a30b269a0e7b7c3021bd3fb0905260e7002f7739d459b8ced5f8643e631eb97a84fab47e5259daaf4b0492a8edea633f9d4474a5b223fbf398776074fec443ce3d5a3241abc1e4e663b1c5186bd44d8c72f0f7bca1b57782e53222e02010b0254cef8d4dccd6077bf2560d9f3e03c0680c04c48e09c7dc15a804181871b3b705af0157ae63ea978ea48887c0e1766a4e7ac0e3368a13c2a1a2b5722cbc852fe8fcd7cc82b2e68b5d0c63083fcfa566e14df474762047c9e2ed436d64fd79ba219ad89301744652b3ee8f7ae0178687f82f0655d391f73951e0a84ba970445bdbf90935c9737818a3c1260125f2848737f34017177ee7cd5f5d61326a15620b65c939e1e3891435d229a554ca05e05b041968e6b8a697bc80a6f10d8b51f8b1a2a83b5a9485fc354c44112a231f59c4c89632105a94cd077e7643605aa9117d0d1b10e25c25bd6413fc84e16416dc037df206cbf87ae70ffb213f01d67d93cd156beeaa74d950c595c95ec2b5c1672ff56a2caea5ed50870d413e723c9fcf6cfce5677fec50195bf72847e08ea38786f44fb19fc0c33be859fc525485d764d637d4d9805d7801ad03d1798d6ac45fe196a6be6a97fd74a1e54213725b99d60437fe2939e6b8539b40b8e468ac3607bc680b81630140186aa528c457d1648bc3505394116f8e09c05d2b773a690c5f43dab419570fe56d3839c8334ede4918be596e9881c692bcebd6ed9ff68909706c27dec80bcdcfc0bef0263157ffae474292951191c5e5fe9d904404c13d3f794182b3ad143ec813dfcc618572eaa1e08f0073264144a9ac3a68161bf12345dbae1c659c154c4e4af0676e2c3f13549e80880be30bf617d5fb7caa8ea871df236141af3759b7a2df440dd48e1cfff7a72e411dc2af65e809f9dfcb374440fab64dac5c2969ff376a25e309a372bd1a69062b9dc45a48c69847ea67e49dee23140e630c97b98e254a3a1d119a557252ee112b87aae083dbb147524a21403eb8e51a4fc983e7d41c6e4a284a16720beb3e2646d4c1ad67b6d06f6a7ce2028b70954630c119085ffe1ce8aa7d4a72253a7d22fc9bcb84571c21910dc520b34078f1d74d1bb032b3d80bdf4be2dd37b48389f0d9b05c25bb0710247f90d9f3ab80688b31aabcaa0301431eb2911972114db83d361ca4678701b8ebb28242be4360532ad73b3998caf4a7ff14330e6d7bb72a9128beb75c94aca35e2b208cc25ea2fd1d9cb52516e2a860999c4e6972ed16f77e7c9aeb8941883043fa7e89d2b5d6c4a519976212d961ec944707d6148b3de56de1ca6fc576d7383964660cefdbee96565da6d7307bbb407a726f9a72451fa98c9879f4cc6533e9444647bc9510e64830dd941df1ecbba9eef1493c0f67bd5c33f4f05d4ce9524d5b53a9e949020a64a7d0586b960f63c611211c0b724ab1a89c8a41887f0d5149617a32f89f2d85b82f199b9ec051d97bc8496af7722f0999a7fc9ecdacdbd790d58172139fd16a7c35bdaca00d9676178fefe18758b122fd056422a1355caa37594c0efdd5fd79365b689038a0928a72264b81d072dfeeac11a942812bf8068041fa93eec8e70921d75458d83b6f57cf7c116da81184ae71bc6cb234b31df55b84ad5cf6194de0b71e92d0374dac9447933f8136723d3ac9ff63f2d91b2af5ba0970b063e539b373bdbadac16f0ddb72bef7c24172068307e0a0f744daac747b944ffa026c3c3ad568ba2492cd7b7b9024e57ebd727b18069f7bae88a0f1e1eec23936f79e2fc55f49b09c2d8120f92bd1e851ac72d99f952888aca2a7942dd5de9690ab3fb6cd6605bb011ab822efa7a68f2a2f72c7cc3655e46829e99e20194e94531ba9bf73eda2066d7320bf6fdf513a863472821bc634548e7b74071988586356ac7b0ea49d675be866cf00c12558dca54c728c153e5ab612f0b72b8327410a30b250c2f6e94e5a9952e044f6abae93189727bb3b6b04b094b083c5cc0a98efedcaab6d15a9a4e7c731d0045436e886c43772875d0345548b1410c11ea79c1f52e32b4e196b98a5116e668d2774e38825cd1df54f1bef133a5d8b4315dda00e4c42006d913083f148dbf809fd61c9751487724484405123778c98f91d6e375620d7370cf051ce7bdaf58b44bfa1815333dc72cb8510d8fc5ceca71bc14a403ef3ebb4aae53cc9fe1993125764690645e7ad72d79359ed07c85a8292c556d1ce9f29df47f6eb6a33efe4d28ea0e597bb8177725df56284154a715380df9594c522ee6a7ab1fe10d7beb50fb9a6c45a9ffeda72f92501694717d2b0e9534b2d14978ac577fc7780f808371400cc2f277744004606fc8c0fe8acb4ae139ec7dc32099135d481e82957fcb300fbf8edae66fc34726164f5bad16bf527b4a0976ffe81e4ae847c8e1327de507f8041f29d7cbc425b09901643e147deba074ec0d795896e805f83061afe04f0859606f1da1616fe470d84b90897149b749df0a2aaeecfaf5065a7d21a731f684e11e2ccfd1c50b6724c3322707f7206172b64b6c18efaf1bb5c5d2915411f5d09314d9145a4aeaa423eca5990eb9fb5e1ac697f430d1060d2b2e11ec84aba03a26cf79ea6cdcb3c22fa7119d8cdcf9ac2fc63cd2b0ad3c917aaa0e1c0f8cb1dd9b0ba8f5784a08f5550ae8d7fa72bf077d22048fc97fe81aab7ef5bb3f44689a5a72fafe468d3bb1a527d0f5b192b19c933ecefa578ada6c5c9832dcbba3e4757e668f8957f2fb64ee73c9a4dc822f9610296c810ef5ce897365bec641998b12c80e05171dde6bf0347937972a555e6eedc58f58e3cd8068b3bf48a7e1339c5c9a4372b1108b951720f5ce9682a4c4af0744e75554b6311042e8268b6a76c6f365a27097362a18412d8d68f2faa1c19c7bbe34734f7e7426ddd8fa76b1a2574c731907bb61f06400760f33c84288fbfa1b5951e80da9b33d8694af3b9df7f9a1d0918bec0a5877472aa24699b346d35b1a8b17b916cd0025912752e46ef251a9502ebcb4b7abcb472b8ded6c8c30ecc064bfd941ca98f849661d148e1f96f3a9d37dc3d608764bf72c752f532e11f42dea128783728243023f9c4cf493a137a428214aeb055fed7721dc919c7c6a84a96331fe9f3c97e3a6284c5e568ee7c319e8d39d3191f88e3724ec426b8ae3e4d50040a3686fd85df277bd925983ace0642db257ef5600c901d90d842c353f23de6ac2aa931c6ed8ed239ad8cd02714b6fde65c4d2004fadd329eb3b9aba7786ab813ee9974fac9c6e7ea63c32fb23872dca37a25108f54c77290659ccfd65cdbaf33e44210f9047f2dffc22eee46a613fc0a0c09cc90e8cc0764dcaafe9d95e0f31618a7dd0f806e8585908c11bd3b572ff886b1f1b012a97269bcca334e58b114c254cd411fb92000e94f8d9a35079b75b9cc5a47db16de72d45a2e1bd02c3c01770f75cb5cffb35b6f682d8f9d87c00a3898c549c88ba54ccc246f0f1117a42a219d496b4aea2d5428456c5bfb06fe680c9bdd0f66cefc72b08bc2ee56ab5d5e7127a087161f981ad24187b0f36a9e09d54fcc01c22fe30ea9622d3682edb6b4013be70cee89cd0c86b98e2511114c64d6fa55a19dc2a31b1764f22e7e2b684beccd09a2b74e1f919cd66581c82e08a7623eee5098b2273f0bb57e92258764374f5531a0ca1e34a707dc9e01364b75a2f7fff8687b9a5d6aeade82415093b267cbd39c4181b7e56578ee81450d7edacc9bc3729c4c3041724bc92c9d23b5c49c9589db75db1c913dc2d0efd031cfe176f952bd5b6c54ca199421913efbc5767550211285c45a09e0ad99d121f09014007ecfcc206fa2717211516fb6827f19e1cee8c0333113f6735e384b1dc2839d1d49097bcf427a22723239be160783c4c2934f8c1c81b5cd1699d20412ddd2fb8b8c636b73b3d4f272ee79c88dc79173050c8870ead7b566564c577d494ee231b6e0afaac9e465b734dee6a4f5d7f19bcaba3fb0eacf0850207676b53ca69b28de245bfbd24ffc53724a8f1703937f9f31aeb091b32c1e96a1b08302d9deb5f0965c1c951960554e161c6376b90cd4608cb021853cafa1fad9d4e256bf9531038fb092b516f721cf72a468dd0a65a06de49db483e63d5a6c4259b70dafc0962c074ec68648489fb07222a61e2165b7061b6dfadd733fc79660c0b6134b40a1ab3577412fba3df16d72ee36dc50b393d7639ed05545bdf07e9dc59cd5eb3830d654be363cbc1e561705f27275c7ab1a51a83793e4f20c72b4ea375d4f4f416dcfca34d6de7cc5086c726315bf632e3610dd6fd8b0d2b8c0e3bca13318bcdf224b3cf0a18fe03febe0721b373fdde695ec962210376b4a4082712ab1206fa00ecbc2e02abeaa58c4227217f23d276516f5683ebcf7164b9914cf64bdc2e00dec57d7e91f6a36072e7f72f3a7f07e13e8f194a53ff7ef319269a4ffb59c9b7ca67d95d8ebb8c1202d3c5bfef2ff28072e652dff8ca1da9d19c9247da23a713e995834b6a168ac774df772a90d5b286aeb6e39d5655a89d785e4678c4d1267a60fca11a3f827e7b1a45a72b48909153b988fabf351ad767ac16c198b06f1cf129e746de4e68201bae56e72d239aab85656ad8723c2af1c931bb6a35b857c5fa29d3244651401fa39ef0672c3ff7d1219da170b626f9a7ecff99aa183d6a1233934c22d8c96dd7432e9ca72f2c50b6c0224ec0fc9c84994aa2717ef0ffff65ee1eba48b15de398dfa6cf472245f3f6cb3555d76cd0725d7e4f21f215b6adf0f8d4a9892fb02bdee6534dc728ca9122bec141ab74eaf7c6ce7f00271d980f192ec51f7f93b522e9fed9e2b72f859bb860b9129a693d1c943f8111d292d4a39ac85631ce38663b9e3dcdebe72f114cd07f1b17d184f38d4aba5f5f687cc4e454683375f5863c1a00c746f0272ca01289be43db8f3c54779a50b0c7404b0705d768f72fd5e62f440e98d641872cb6a851c61b4ff1a83960f4af6b2b4a4ac6cd498cfcf9d8d544922df2dba8123b3c1f986a513b22c7fe4a37924e63318b544d0f6efbf7ab503b0b8c10b8e357210f9d74efe2dd0d1654b3bd47b13382b19270f7c639b24583a8d14c912c463321e5ee60ca82f6a9f59973ce4c1158170dcc7242e0740d4e9b7dccd66c6b86c7262fb92147b936cd7e20f1d67569ab4f7ae136e8371ee86c4bba95d0dd9a71b028ddbcea63eb57a0d89543916fb94db92146bba215053d0c8c0e1d9ac5d416f340bd932f454af216d75850597394d203b38772c3df39397e271d0654d15769e723deb7be35fc06cbc6bd8ae0f84fcbda7b34f7de357390c81cfb71dbf24c5b3725f0460f554354b38e463d34c06e12d6d0dd9d7cca787e56db621250b818f3425e7b9ebf7fb2e9677d91d9a791a3316ab3eccf9b4db9d6df450ad40b900704b70b0deafe71b0e3764c07f94acfeb2591d83845620d43524a922f7cc7af74f3c721100c25909c35b736e694cd13d20d67713d37d6706237b74e75046bca9d230729e3e297edcfbc1e423125d1fb22f1fd788058498cf6676f7590795c6ce326206eb9034430ea332fc905035ab55ef56393d04de355945dd343cf91adba3aefe159805f52ca112d108f711123291966182556c055b8715e64578af1e8201db45720ee1cfc35bd981326e3135c654d399db3c275f221388956c79078c57b8e7332be53861651d5aa57abdbe3b1d4fa996f9a7f830e79011541c2432da5200eb7a0c964aca05eb75febf70b54f6c0a39f78b59ee0ed78e3ed131af25a913e0d9ae3eee8afe9cdad31da56461e1c1cf3af64850ad8f564cc250bc3957b6e260d51f5a76fef71d132fc60bed356eae02e05dde4e1917902479ffc5f5f8cb6b4285b972ff94c83f7dcc444cfce600fa8d0771ddf66ac676b539afccfc1eccfc538c7e723c48259d3cda134a011c6b4894076493a26f650379ebc9991d4ad2b7ec83425224a1b2e519b1289f84fce41e27641b836bec9aba2487b1753b61d9599a7c9972c5f4a7b5a6c4ef5849675027880f5f4e276420c86b8f3cd6242741a90b8ddf724cbbd10e9c538f31a9fb66b2ca4b937d30c382e70658cef63c926aea4f6e4e72d78826e850656a231ebd9170847246cfaf4497565da8939571dc9b27aabe7c1e407992b174c331815684cecbe2540684f38416e0e0e2ac37d4d64a8101ee3e722fbea5f67fb81021b78474be48e2fccd3923d94f90c46a7133695129b9904030b3c4c081273e40d983efc19c1d23e96be0afa13814e9922a3a59cda6bba500723e3e8841d42011728f70006e5001d9748a6ed2d5b997fc1ef1da5664cd0bf72999c820d2d4ccdafb7c85ad115e5d7ee76a688d253a9d879860afbe77707bbc729d3f7af1523ecf7161e7c26122c0e45a926eb857db9a332d9479d62ab6f5f1729ef039b9619c7ab99fa3d39c57e8bd5232c50e3a1e8f1dbc0e4db02cdaad8372c41e077bbaf975dcff63c1c3b95459e18300e3c6fd145a04f8bc809448f545725067d2844722ca735d1c548f27716e5d8e3792011f22360fd0a6a62c461d2b72e6efcb98940e4bd2247abf8fc78c277fe611639315258b240442db12a6654018a1368891367fc16b5fd8b05ea0d1d3af3f0d8b5c67096f184b93b4affc4ae15fc8976be38805b01363bbe72d8360a77f46a6131d56f4aca5d3ad98fd373ebc72ac83d6432771922f66fad8b3d5838daa7e0ccae0a225871d00576bd0957d6f50e37e2ede5a76e72a95cb74465e2e0684ca44280d0bba307e94b063395ea28e729ec3c87a3521925e319c983867d3cd13b17f5cc262c3db7c547b2de6590f9172bf1b14e3dfad274c99e6a928ecee1c413286b3a47de7b4b8fcd601482eaf9672d17ae49bc81875c0ccdab43ce6d20f42595fc192993babdb807c4d7d2b235972b1aa21b8de02d8e27fd4e2f34e1565eb4ee5b6bb1b555297d7fcea73f1fbed1364e36223754073c6a7d3178d7e85d6c6ea8c14d685fa0013e64ca6251df4507236391c390d544d2eecbca5e9b422194a3ac4e440a40c962d035e3c4efc06fc44bcc0a7afca52c9409201c24fa8da89f07f1e324463d092f98749f166cf2fd50cb556cd3d5c86816ddb5244e3f52fa4ad5df6c2e73e171afe26ee65b462b7813f3e9ad50b59c33cc07816397fa3ae5b14ac19ab527f16d1568ad15f902a8fb64915c2c297a8acdca606b78aed415b8ca9c0408f61939ab4f85f05ab0a4454ac720d80abb3198920515a197a15a96b9944651c1e895c3d1e70c95c6959af6481720322daee63bf4a53cef07bda23ee6af5134a3c73d296e9c7d24cb171613e456c175ffdc744d78446602b92801209e4d2a0267da23f24f25880ab83e30a177b33d73f5cfe21a9e8a28cff6fc3093f8188e7230c0b1ecbc0e327b3b89f53e931161e7300ca2f78e569836f368c2b52480fa84736fff39b27e41f150a6e860a5572c6fe4c21b8ed3ece95ded5b2bdbc957dea097aa42073fd2a2277c611267d70283cfc276956e6dbaaefa18f41a70b615f6d2221bb7c4666d805b7ba7689c774727c63b8800dfc9d6fda809461483dbb761626daa3c0b8b799edd0a86210e7c665cf276d08cbb460bf209548bdb072dd56e58ee3ce0118b44cc2e8e4e1badf2e725f3e2d0bc20f61ed2890aaf2b4a9c31a4d6b5c1929734be23ef51f9dbdb80172387f46dbb22489c3a9804086dcf936c2d862b096bd41821dbbea36be3c374572e1ba685ce2c3b64949fcc5f4bb39e7588181265711572ed8534146992d676572d81b14a66405063003c6e8b72f52d5a673a095fd2fa7a3b14636e8a936e12b728934bc166d68620cbac396db28a508e19722ce8daff88ce7ae248da2f405c27230dd30986e01cf00412c31353e7d9249d317947d08b93786d8a02362b3cc1672e4aea7c9c9afe69d427a058563ca217214090b795d835109e893362b57fc0819c5c757c8ce242eb27432ea388ac8ffc79ca7f6147df55f94e2eca48d6c30da7219c48495051e6b3f35a3b2b6a26be1c4f2bb4f014b7b71650f58714b693ee372eaec2abbcfa30ab963e474bb2d609d02090d04666c54b25901f2725478163572a25f1b8f65a91b2cd346d7801cb05fb564e1f8790350224812fdc29a56b0d37291499192e01a667bfbb8f525c92c33326f29b2b989fd44ebc5a38b4225df4b2b2b4a9d06330878bce79cd9dcdae8c541d804c73095b6a3631eea89016234ca5c5d79b1a2e3a73b7c051e49890c976a0c9c47362423117cd10416d8d569f3d9135b384cac68b7c64ea7eb49b35c4f66771f0b72c2ffd63663eb234294ac4486724a7a62e8d11ffad06dbfb5f1ab713344a0ff031f5dae191e27bfa77fb83ccc72ceb25d72c5d3a3bdda956d868f27aa8db00f91ad626cc9e34ed6dc0527ba7872fc9857943672af253a93a4a86b83fc2288859d87838a1174ae74cde8ebf1bf720fc538c20c6be48992360ef6a2ed0bf1747c775205bb59d373e6ffa4c8da4c186f94f742b3356e19a55fdbb7ab03353815dea67b3e0d5f79ffa0f4c6274d90182bceacf0add877bb8b9eb862334d4581db68275e3caeea8158761bc38ec28a726e6c48128d46f0d2fd931655b0f3bf0c6c247e34feb3e1e4cd3bde6e2d3ad7726ea5998adb34a3c5665e23027653918aad60ed148fc544fd3581832d04445172984b88ca4a2d240efdf0986379003a5200b635ed55ac80a5dfc49ad0912f2e0f6417904a7f98994035ee1f6599fa419336b8f06930d79041eeaf25b70285d3723232e15789a0e26100fac3f92b2eb9675fa0a1089538f2a4774f827377dc4072689dff497985d863cd3b5cf3f7fa8926fc5eacb6c8ccfba551c951bafd1f0553d08d0a63f716b731c0e55bbe8d2e49dde5f124d13d68c74a5a059c8b1eef3f72e0a10906e687388bfe4945863d55e62c1a0c71e30c61f43d2973a62ef9e3af485c87b412f46f169db52599c0253e317caa55c7c589eb243e879db4c7ad9f8072f6cf7b621602b8cdba1d5b970e0a9d98b577409d6b12b460c4cc53f268b5ea72fe043412ae3de5fd586d8f7de841f5bfdae6de426bc5bcce34d7d7d11658116b47d3498355fc9e09bd893582a82466d1965756a4bb213e024373a56a33a14f7239a37784f14dacc7771a660cf55b27f8607583969be7adceee28073c5bf2f972342be0eeadb32814d72cd53286afbfb0f66ff6bf529142cd0657821e765f9d49a4b3c9168aa844221e31a7aa314f9e9e950ff6e56c7b5cae1e03e4ce68a0c33f3b7eadc687a31a3413d07b8f7e9e95b096825aee3e7c7f08aea917c6cd632d3ff716ca80adbd880f8124b217ca9d65abe9b7964d3566f8ef47e4b809c9f17672d6de46449386a0d0bf8e351406ba5ba8960961769000c50e6c6bff87c2a99e72df49f145854f38be5988c7f592cbdb8b8f1a7e63dc8a1062ffcc842cde73ac7265d28cb932a73dc99634920a47244bfdb5bd8fad0ad68a55b7bf9ce0af0c9872a9530b6e6e0cb8387d0a3c571c4dc8ccc12bf762554131d029b84b7993051109884d9194d8d8c1253d6159cef8c2f9e13168a2e01b3026ba5147ba3b924cdd72b33c61d052588b2874dd709c0d8d48989aabff996882574fc64e9e90abed7272f9463eb6a50934c93c70a8e396c0d489086a3d46ad0eff3772fbd109c5b45d721b97bc1bbed0cd474f8bde39245501223d8baf96e9a9bd244bad07f951318272829c7dbf2eb4b8918f89557c234ff54094ae0ebfb412ba0ea19bb18489524f7287836afb0a9607fd62661c687ee12669657c6453f4752811d088e8e307d31738fe6279b9e4e7d3efe3ea7e5a852ed3df4a9bea1b4e3684c50cd1371df364a367026dd538dda9a97c6f5e3533045e138c35c1e9e6faeb9384a3d53fd18c653172c3bdf46a186b3812719b1173e401daa7d4ec128e4e6c96878d1205620b6220721c0ac604888c76a558016db78c5bc683a0cd946688b74538e5744a5fd5084072a832f62306bcc8bfdc240e7219e5769d7c727eb96df9939038009eb19785ee7279d49a187e20a09305e0fe91cff8726a398bf03df9d53f6cbfc7ed180d95be72f0d6525e81f09660dd2ffd4cdc0398984d7cf0fa61377128ae0aa9d8fc146444f07c7afe0d77e51e80a2392f66f164e408614e8123f86d7affd9fa08d6474448c099d5b8dd74e351cb27cc53ac9b62f0698658354b3f160a373b0c7d814d8338a8bae833a321a4a84e827cca133a468a8a50cd810ce8ec9a4fe95acf40910d3497bc5d8c139cfefe777499d7e5ccaa03ed16d151641e95c2196ff04d8c1f537227f304d137e20ca86724ffcd98fdcd05e9d0ff9378dd24f18cbea41b055f0b09739af78df1bf2f417f334f22637caa75aa4a6df551dd63dafd44331f5f465c7274d009311650681338277063bc40b86339116c1da69312825fdd22e902c2b56eeadc43cb5b2eb07b0251fc3fe040bf2a3acbe314bc20aaabd9c53e6cbdded02871b9e23cb95794dbd6102e67cd964b7711169632ed0bb2de067905b5d98fc472663cf28f51a60b6aa43649809f2d04f3f6be21a654258986f6ebe84368faf972ef5711a5eaea44b5d798b80665cb182c20559448d662e8338da57b06fa8d9d728e7a4719c272a82b986951bf755ca20faeb1c9ab1519d691a7717daa84294d72b94937808aa4e5d412fb5831debd8c60e66025ef1cdec60672ae44e185660872fa416a354d95586c7efe29ec4dea8d5c0aee617ce3a0d0918cc92cb5f785d87228949ba84c682de4d29cd5011ac2d044dde3b20705e93531b31e260f820cbe4c2d274b588d1c6e012f2f0fbfe48d2c524e2e45b9c3dcfe61f877fef526afb772913e1b2f2d68ebaefc0b8892db695d6e6cc2e30885cf0d45a33e636e23686272b98931a475dbef27984fb2001064e9566064f5f98d40e8d8edee9f6642a379727796a9570700eb552daef5b38e4ac8cb48057223db9f09959a9e02ba2dba65729e3d1382caad179066c6662e6d0532822ce3717b263065a2e5ad430429311d726792dfb576f7c647f616e735ed7ec678a731b62ebcb4fe5df6c16066e8547972f2fa668a5e0c44d619a3af10ffe066a28eb926f1eb15dc6f21e9c7396c920172d417411bd1a863b29251d2e1203ba89e0ceea55e458d0de421720567ecd5a626e81c2061ed1342843d8abbfd56b94215584a04130322e2698b246695237d5f72f257b403b513a5a849b3d49787221b24783a57a7bd5549e49386784886eccc72e10f9e2c76b77013217cf1f459e8d43230155dbf7077887814ade7a6806ea272ee72a9877dff497f5fcec82e62f9001f9a37315ea9a4b44740526bba963de372dac0fbf32cc8912c2ab6059f715db462c1138f8d4ad714eb09b6d8ee37653f720a79ebd9873623f433c34cfd8563e06e742c54e5ca3edba7f2288795da052b4185d843825eb619a3ff1fc3a9e89f9191df4de15c77af79ae34f60f0543db064d2d7bd4d30ace37eedd074647d9fa2e35a49224771096f1af5751907f57b886627e38d4d802d0d20b305a163c8b586877468e7c38665e8e09a6fd43c169080c720fbbd62503906ba99c50539f006a44da717467d66f0ece38d9a0a328634480721460e8d238a8181850eb6628eed2e3e1cdb0da137907a405484e9b8783a7ed72cff790cfe57a91f736ded0ce4bfd842f6ff70496c9854ead7b9aeab5bdfbe57265f43059de9e5d4415a7fd96f2466a9bfb63ffa277d472171e12f6181b781b6f986d452b70f11f03d158f3107912a93c653e2d826f5546410988ca706668d1058e735732d2d361d1a0dc18578b5e925e157864216f13d5ab575511655ee3687215a8a21fccf68d6e99bc0c06828c541c50c9d9436dfeccc7fc79ab7ab1269d72fd75c246f140f1b366bb3faab99cdc6cea94002f0e64ee9e07cb85f42bbda25427126563eaf61d092b002a5e315ba53ad7150b2d9d862e17a218e2e9ef7bc67202ddda46bed3b474da20cb48fd660ca5c35934dfd54d96b723a33cc1930d1372265af5ad7852e37d802e7dd71587be1556551e477bbca96ae6b6861b3ef66272817874b68b2b65310f7dab1506522ce270862acda5c55b50fd7e94f51e07a14c1ebde2126a4919b540dbfe42fe9675cdb84d72d42ab03ed4c486bc3785666572c5c55a5a79d09cf4ddcf32f93177259151b5d02fce5241d32528856e60af5272e32ea894429af91c29ab3f3857646ce3e8da67902798bcb6d28595be252d2372d925771be2403e9a7e76e276dfc05d713dfc4e374086236da45f856e674bc9379223b318d49fa3f43a885e28756512ecc2e2d305aa2957af4608ad1e9d2b747255e05f19b14c39e8b932cb1975a922c0e61eac2b0754a4b475041b35068dc45a75c9b4c06ded21ed3363ee331581c2b92edbbdd9d52bf03d92adb4bf8743b80094e6f04fdb574c441d684f529e4187cebd18b5d0f5f7cb8ac29adc79578b7272c9b09483e79429ac66c1adad4a2d0f3009cc3a9701fd47d217bb80885ffbd672551100f994b5b9f054dd1806e321464d30542315cebff5c7708b40bbb30bab723266fdd9dda798f289fcf1a2968de9710784e3d7c36459f5b64fcc044bcdfe724363f19e3898831c39b8916b071d3e5f792c0fa65cb4ba95b00377679d9d2672bf0a3c1cd939b66f676b1e8cc3b43014299ac2c1edd86b42294f683a52bb6872c50b0047a4cc9f44f8122ce0b8044acf085c271384e87cc78e68fe8edf744a722b36575d9606f07d58159038f4e6dc6900c7d2cf505c3769cf74433bab4e7c72af9ae394532d480d8a6fdc0d78748e7961a208f7db93b7f5c7373ef13ec1b853dfb07b8f0d2e23d0536a0fba9423d3e15f1804bc39664b8889edbcce7fad147214d8aa9f7df2c054a6424a87e597bd5e0d7cc5f83a5bb68573f3bc92687e47238a9c497dbd1fbc599abe5c095fd0b413ec058f8b44198524d74f66b272735e72e85908c8b8b0cd0623533598e4dbe14d999af11027f35fe7b00bf363d398bf7277221007b92dcc933e292c9d5115a0d79122de1539a77b47f0c995df9f564d2cadc1321e0e3071e24d29d85d0d675d773be313eaf18f25bc83b2ee85fad0e0729f55fabaa66c73e0b1b35d2008fb89e610f9043e8aae36e96d33157999530d13401927d6582815c2f205c96e94a35a83d17aaf13c98d926b2aadddebc434d972929cc73fb2ce339bb5de107bb2c66f90847189e13faf0c4bb6142a992e08b7724a832a45976420fa623010dc71c1b93dcb7a02ebea8c6c55d4f41ef028eef472eadab0a3487c4cc092ec8fc14368b210cf113a74cc1d1a015d19ab1ea7355f72cfb64c768fac1a2547f32d06d005902a294c8465e04a146e2779239320a4801394955f0e7d242795c0220d14a70f47580e8d8528604663eefd968138bc826f2b8737a61ca29cd6755cfd3222a4f9f77c0fd5088b2cebf234c1940a2bd0951a1b1c8fad0b651f5af424ae2dbf1e9602501bf1838b0f9dae4bbe770f2ae9313372fed17ea0dd3de90d93b6908a5f0aacb338ffd3e7bec282bb9718ba6d42f11a2818e90ef9b6cb59be13c6c2e75eb2089891e83540c6d197f8ffd3b53f588d6657b7df0d51c0cbb2ad3f1ed6c59efe9f0ddb8b6a7347b4654bf2b5ccb39ab3267296a9e8e931d6f9ed8c6f66b6ae4104c820bab115d15bccef2ea24afcd676b0721f04f1c66033add81d822244b82a3a0cc2cebbb8e19ed42d62aafbbe391998720a8889748af58397d63ce9b57ebc7795f6764c1feaadff6043aac010ec8655475312675f8193291dd10516a5088d0bcb3de55d8be6b1e656176f5afa36a7c6728ae73046a4a54da4aebdadacf4dc05147aeec26a171ad0b6dcb5fdc8cd49e1727c5a393edb7280e1b6ab4078ba943f81c3aa6619a3aea36b6ca3a8de04797f39f92ec64e52982ba11101c3cb1635070ee385dc1061f4d71e2950e65741902d63c7a6dcd714b99a0aa4db6385a1a462b2e940f97c905f1e0feba85b54c0846b7242c70ddcc88aa06cc9d37945a33c7d02b5d0ddd8e9ff1a3fd88101eda27bbc72f2f7d0743f22ed043ffe80543872955871b8c2bc4a26d2688572527e7590dd723808bbf67dcdef8fa5f2ab2a72a835d2cf904a8fb520930258edcadf4def4e721d62f475b3318921eba250fb80ce644aba89535a18643b317907346b31097772c294dcf06ded7686577b7c2ebaa5621af6674b79dc28b63acdc903042a89d7726ac982bc1a4e113a14c6c915c01d9d522220eecaf3a40ea9f06c9e606c6a28062950bb1b5bd7fef55f887af34fe866aaa3cf4f720e548c368db6b0673c1d0c6073f3db5f56f213e743ea87c2ce536e893dcd3ac7515b1de0db2c24c3091f885afb1a40b3bce2b90951d265ed5be253e90f4d15a11c8215d05b327487bf9b2472a5dc672cac497011da7829475969a6af253f0a2d747d75e51556d5433f8bee721e70cf95256f82c02ecc9496ab5346c8b63ab43cd32a342cd4c36d5968c56d4c9265c0597fe43a59a160265bba785240e9f4d45943fc22d3c93b3490f7e2b77226185a9ff7a18446c23441da079c77edd026a003f4814ca77f08013824d3dd292b4ab6aec6defc9a59c10216293ef0e42d5e593398d004eabc99227c3805a77247338a8b261c8cffa6ce74282020be3eecb5b4c66df427148eb2c215dba8c0406c9658abc0bee14999a27ec0ad1558177cae96db6f0e2bb1092dc20cae970c159074eafb55e3db5c66040018181b7a971a5ad3662789f4d12c6ffbb931c66772a17d880c3c645076fe0d5b67f926a81d7f08313a5ad73cbe520dc06b70831d67b420bde079fc405f41b573ee599b9e34b93427f2c7d54818d1cede6a8beff16b72b304d214b8379ee16f067e48a0a67247a189a65e38d869aae65e0e46448972b2d76944a254bfb0c80a1134abe9c54cd08cea7fc385a2ef3c7e08e9b35fa8729ae54810f18dbca56bde72c374f76472aa4be9ab9fafce3ddc98f0c6547cfc726ff78c32883a67e04c6fb41f6de2b29876e6b9e2b349608daf06918bc5c469729eeed0f3f5b32f1a5dbfec76347424c381ba48f063db7e52de5e6053324b84722d1f2ae989ef0f831f452575a802a00fe4fb2440c78a271da0feb3a269264809a25c12b902b258b58d9b95810abe44e4f1ccc568183fa634e83168b48795a134ab86f7e02af0be64421b78b1c3e2112e7d816734026ea538e87b2fa02187e7713e30a5463d899af7c78baa3c91b07489dcdd2f805b5a2d351b0ce7a8a49a2a7239259da79e4187bb597c65bd4aca11db3ab4dae33664ac4cd20e2eb183aecb40d633a9417fe6b3f41fd2532732eb3779724df984896055d79895e6f72aea896dbe0d92ebf1f0a5b2a4c41b0e807f64a46131939dad1b76e99240c75690891d72b021cdc5f887d04ccc962545a29b9fa06310169cb0326ba583a710b5c80bc2390b0b7f50e9525212f00172825e34faeb209d5f585be66ba8cf888e72422c15720cb44825f127a254b5f73bd5c336b806902d2d7e07e6a6a471c5e09b16f3ac723ab4cc80bf197416c6ca11928c268c35be6b2b974ed7fc3c528778116e74272371c4996114f8c73c0903384bee77933db8483499cc913c3b1e38b864761224724f18b1c9784bb3368c0fb571e40cc49ae2853f3c8570f49539a8d60539b87a5dc503a868b02b21f5cb07874c614ffd1f34bb3a9e7ece2105415a6bd1b44d2c72f09f611b4bcfa2d68c626278a416826b62dfa78977b81cbda6993f02a96668301944a0d5e71a8e5949fcf74928d79e239acbc93399a6e56beaeea1662c2e123f786c5e1b7bc8ef46a0b22b4ce5d729542877bb7ef1d3d906d7948e2e354dc072d54b82d0b6689ea448f4eaeadf99e6a5334d1b30111509b8300fec9b32ff5672e8ca8275d532489987ca3d2dc669ff3db89e6b5f2ba2e9b5d3649473dac6a764c6639f494158643d0956f05239f6247f9ac935017cf9786a5e14cdb77e85a47297e9c7bb7fa66b781e9ad6c2ad624780f25a36fb3a18f45d5c3064a92353d74c1fe573ec5883ecbe7042819376130e4227e98f70d358e3a973535f4ae97d597257b3885bce66ef067d920ce06f970e33c12dca1610902639b3048ce1796e8c72b20667b297c3ffc4ec3b6e562fc77b6bf2634025230d9d6605318480eaf9d33b8820dc43076fa7708e82b1654bea4c5d1109a0d01894b515de1876384670cb627d359aacad59e303d75472a8c0c6b902cd6451a63feea1351c8117ea0035b872e1cd7ada6f2942e82fd975f6d12b2cfc38e2936735de66f45feb9c857500f372c6b7c604b4b03e0d341b5945ef4d4509365c21d110e8f9cd70a18700dd8cb972b54df866b3feabb97fbc45e0c229b297cd7c17d28a1c4f1ec0b501e1604bbe7226630d3fdd9a0d603558c0a233310cc991b20d922941a80ad3a5bf6331ed5472b357185049bd749e57128f84de418aef5f37f9ea533d9155598ae7000314f95ba6b7f38aadec4397a36ebb53e0516094f317580b858f60c840c0f6323ef1527253965929e9d60b5a762e7ccfdbd669ae91a4b07a6ca64a8a1cd54bbaa12a8a22192f6609978ea613f8a5548221a625d2c8d38c526a96742ab27ab0a39e9e546288724052afa656a6e34c64f8d0858e397259d4dfed2aa38f41f613c88898c334e0ed409c1b93f54c61335983d305c390fc101ef709265a88549fc00e21936072e0c050395c963b4fc9a964174e4bd9c785719a506b03014034bceefbb4f69109717d801d39649afb39342ab866775841c7efaa48dddcdae148e6ff13235f6714e41d1216d70df7000da83fe067929804c8c48e9bfe29b0dd233707db5c7e2f1777687cc8a94009ecfeec8b0bfd6fbd1c3b746cf2949c54fa9f54017fe639391b47140bb428c9d31e4a7d75548f4c95c31343efb3190ecf492867afdd443fab7252805eacd9b02f856cbdbb45d241e91ce1061abeda90aabfde99216c6434694b20deaae2f6f7b09e497d6ca646cf18eb055d0fc1a1d35aeda137c16cb4f040726c78856c231073012af9a9caeccc96fbef5704bdbc3a27080126e687e3d073723e6338af882127c24f1b5d912a54697cb86741020299d702280e0dea008f80112cf2ee956798051f4842f5e6221249aa26449be4f0140b225e90cbfe043c3c726eb8fc5b5050842a85a9f2447a1681201bc3d4d84ef0b6e8880d26ab6c675e728c383a1fe83c8dcc7a7b5bf1e204aaf4dc891e2c0fe91293b36c1d9226f9e765055c1e5c426ede8a7bbbacc88c484dbd409cac0f0846c59892bbf7f0658fa443bff3eacc88518152b04d1c26e00af988225f910514c41c563c77440f401f0672789eb1f89e2d18ac64433efbfc472d9a2351e403e0175c1d23231316772fb946de83f285c9d3f65467b92a6dad54d01dba445a40a14a7c93e582a0b8ebfeb97268c5dd338895edbc65e15f07352d6db9be92a6392dca265d444bf3b924a165726321162f454d6debacc424cf29809f38de382d439854855014cd7fbf34c669253d02a54e5136e544588b154c3caba28bcecd310dec45c41a032fd8e7053290720a26819f02d06c697778e258793f6c452916af07cd13e2ce0b34ae1691096b720627f3d55a1426bf70e5d176ccbf81273a8a846c5c24b83d932d9404e099c84057abf074dbd9b84aaa08c513ac2c2551ef30fb4333314ed0529d3ed4a473c80a283da42fe1881e628fd1cf766a9d7cb79b235912d0b7f2fab8f67410bf4b3072a43ba911389156c236966d357ff1cab3457d6c802a3344233205c8d4d3559c7280a3e018d85cd6f761c50c78dd3d11b4ad0c6fd0ffc0a59a20d048774c5fd61a9d1ff0a3855a8caa375005b438e3cf54e2241075adcc3cbd74ee0933706e1e720b49c5360205b362a75fb3bef218f84a3f280c29e8aca9e4d00f402c0c355b727e20defa9a7720fe4e9eaa953c26d5f51cab593d634371d1d21149de077dc672336cec349264089106fea4fafb03b666ff3334cae5d16f2b7b1245ea6fa390698be3289d16acad43ed20c2b487462eb547c848a69fbfc3610822567b96f81c340fef972858d176159bfe4226230e1b4d701b8ff0f41ea6335dc7bc9cbe12ea7299d87fad0c1f24a8ae9e8663b862fb8c76ee265d67c6a9428395fe655b5cf3684336c7e2b033112ceb45ee6cbbf37cb6c265c08c08267be314b1f601c0070b251e02fce6f33a557a3694b9a01a36c479d36fc77ac7f5cc5e6ecf8e4cb2af2372bf26b89ea1bc07c1cad2d5ba41e109a5ee104103edff8e8a5fc00302c1237a2406b459b4651c2675ddfcbe2df9150c2a6a4833137be4fc671d3cb505ae13e072fa5291458e0c67384cdb26664f5588d26077a1fb8526bb3990dc305d635bd072dbee6793a075da29d2543d4f126a3be9ab54073b6259a28355940fc78ff87e287c4e84465d718ecae7cac03c52e39e41f29618528e4460b345b0d5726ab9e36d2c8eeac51e5da2918df5a462e6fb8d615f62cc895e222a35a1366e5e780b2f0f8e79a9347f40eaef9780ff4b39a49a8d2a113eaae86712d0d476879ddd2a3b72df9d9091e5fac426e49aac330c6d784db8d88455e1adbe9f06787d282e8d931518a70dd97cec3d50b6b4b6535806c4a2f80c39d66d4f4fa28533f66c0ea6a562ea23426fe1ff1df65886b3abfcb86886ca6e965aabc5962b04d3827a83cd75014b8018213688f877008efa5445371faeb4cd49cbfe1926d061f9ff77137fce2ee3b5b436588eb282b825096deebb51e157e7704283ddaedbe6ad631e91767d7263bb7d138937cb3486ec03d4806433963615efa09391d2d81abdda5a26dc053e815340da79a03ffd4f4f4f916e982e7449af5b6a5a011e4197eb0564966f3849b25387aac7cdc485532c6f48581ed40d2dadb4bdf64755630c4c5a5ccde9bc4041588da6c44dad6bf692adc722388b0607a150eb2eeaa3988fb9e0fe8824d772f57fa8468a4f10357eb47026d1475c7505d95de705624220712216fa77e3b0729752519f1f8d78740c5b0f09300ab878a27dac4b85dc64cac07d592b6fe5874034ec770396d5db2daa2895733c276105b815e81bdc71305a2a5a0e24edcbc2725a685fb4624c17ca578b7a026c7cfb78a5272a8e84289375ac10772f43b6160340b0b25709628270cf278b641ef3ad0b2d70c7f177ba5368498e51d515bd367256e749f7f000dad4e2804f16113cdc4764c5d553345394e3396c11f6e5c947226e471106bef56df5a54757a17e340bc8234fa3dba0e735ad061c62146022e172a5b74f2a93f214c084a7c76f5be9a272261fb1c745e1826ff0eb8c157a529c1633bd2f87618286d374224a8d4f6065f1ad166f9ab69e43c4072bc0801e2e6363683ab5cedeb30ce268cf1149cac44ef55118f8d4da6895b2f60517b0a3b18e2fc0d90d957e0f9dd98d531b205472617dd3e6dc3aef3622ab4a58b09013b75f5d99dce2adcbf36ca0008702eee7c76f518649c50245f2b50d8d878d0c30057d2454454871a5f9ca3a3a2acfbf3fa73a3d5dc1004c97d468a8927625b26e65e07231910696d04f499631fbd707d37ea3b24a40b9d9a4794be92ee3a29ce1008a7282a25d29e0ae2f317361f881b1807b6e356ccbe0dceedb34c2c0da499a60617236e0a75175c134edfade03480cb2a620b798c56a6fec5de8e9b7830914447e727346e1de42b299550bd76ac5f131a9a2ed5487c3f3b4bd5a5e76f20ab1d6cd7249a5cef163ed5b48b1687a4c3dd13eb5ec0c10e43a57f7c77efb6ffa94bfdb5dea4ab6b475130624e249ed9973cb31c563613f7d9b462c6f6b5b0a6e525b3d5f109a98c038fa2acb76c6d7ba25fb4151d9f4459d0f0865343eb854a25606446486c4a33fb47adeaeb3a1378223c3f4eef5b20e8b82f094679f43740c4832cf72dd039d50b747fe96a338581cc7928cd54e3592e435cbaa099686e98182195972282f91fd68a7c89dd7572e8b6b4d9710b0fdfb441dc26f2e52ade53aa78a950692ab380ac33bdd3f508384f26d9d3bea6c3e548da417e1be9a4c5a07c3dc69655f9cbfe491f422344a9fa346c7e2dc875bf08faa1ab3cdc730410413e22f47729f00bed94ffea18ebdf8574984bf211785aab1587132f3eb5819a6ebf582f061d8807504d2d7a407f9da8af7456577298be3b645c52a2c0ed64b762eb93d554cf57e5db581ea2285c0c0c40cfb677a433e1128796977c919290ba1fbce7c5e72a9a999d75e0206da85a4f1f0376f33762a2a0110480f89878290d5d017324b52d8f582b935993f3a814841768fc6d9ad2f6ae0c08c081b98db87e80e0d652b00182a2d7becea8a87910d2a9687dcbd30b6bd406384a24a2084ab240271f9791ed3e1409d2398247ed9c3a5658eb23c352b993e106a82d1f0487162a37792577215f6daa1131bc7820ce6137cacb9c79b770d55258918d472ea9d92705bab8572af483dadc4c2284f88f0865422415757ff420b9007d88b9e152ebd1c7e7abd3851a785db56fd5fff1a0519e8b5c980510baeac9abff51969291d2208d22c8172d4f3d78831e04ffb80c62b09a547ec734eb1066b93cd24b2237f6d9c6f400a6cdb29367c9fd765bf512d631e830c2918fca94ef69af043b44c7109da33bdf4723e739b18d2764f359a1814cc5cf4688c2ec2eb7405f0c6df1ba6f41e4a26203611fd5923705d0e59c96c1298d27acebc0977c8b8076e6e35ec1a3571217e303b05b2a0be2871f1c7562403e1ffe2d7b1fea151e721ddd44c95d2a735fb7d02727d4023557c277720119a7dab1f3e7863746191a9697b350a695c4ab9452b235c9a9b2d15905465833b69f8d26f9777b1d675285abf727e5b3e4fb827080575227071f5faf0758c8c503a21056226200b4c6b7ac049096ad0fb1e40a77f250272afd4b21c72fe30a50d268473d8e42aef51119d4d33e95df9665bb643bd7bea5ae7d6b8db73096d1c27821a60211533c51fba350180aa97eda557bdf3acc70d72e1bafd71a3ee8ed85f2901571b9478fcbfcb8c9449a74bea1c99c749d85c1d72912b07ac46624aa57f16134320e27a094f1677867e05b596be59627fd5201472fe72f954cb3c271ffe5c4464b6b83a0ac03ef6f687d5876b522dfc4bad498a46f1f5e5f0634ef725b0c999f02337ff8d7b1cfadc0d0c5867f882f7874aa99972f4ed69f2c32f3d7a3e73b01c686dc823e889be84a005e6cf6416d761a544827277837391cb82798f0b6f471994407a2783adcdddb6a0713682b37fd6a78941432441e06d343d128ea869b0346e7e87023c1cc0600301768ca2b3189d9874df5bf2f70bdb94a92cd92c6503aee404cf0201bbf066c6a24d3b0f3e88a08db2fc729451fc2e5de8ebc95ccefa0bfd76664f8ea95cc4f7d80097bd69906266ad41720913c75a23ade2ff6e211c470e53b9a2706a748065857ed0444378e20450ed726916300677cb2522a2c1899642f1c3ffe52153a59acdd7c232d042caf3bdc272a71b8a5ce4a722b3a61b6733c98afe765a0e35bf4e6d555082698f39e9c867128dcacc3c5dfae2dc3cef51a8ea787dd1431b02a773a0388ee5f6d4757da33471026c50c47b607964fe9b0d72f2c8133d1ef82dd2cb4a0ed49b8e91fc35c1de7108a22e9c41191aff904e61fdf677ba03694db86bd0b7e0cc12ab8ab90b594f7275083011a4c4fbc01338767a3cd138b7c2ec7a3acbbf3d39affd156dcd0aed72d1b1156fb2aabae89676bad913134b24025edee04edf403f1f93a8ba5bff9972f2deeb72c6272bdd1370ed799e93647c50eb60b3007356f6a02bd51cb8803a23cf64e32c757e0018392b84d326642b9bdf23c6f3a36b3970c59d52e3158212721b8560489efafd15994edab220c5d3544a733344240a49d02b58c64eff83f272ff134fcf04f20d67fccabf8d570fd0581a7076efbe40acb8cf0eb1ec24de402277cc37e5feb8cefa4b2e6c8ec11d3bd48999b5e97911ed7f7d58ec096bc8b81bb4a4b239759b7a79277bad009e1888338528586bef06873aac36d5d613ca8c726b51e3862e8101f827e9c13f886c3890c180203b24572e6496f66a4e71298f5fac98bb5ddc2611683cd41f637d881dc5d73b3de5e6f7d7edeef52a06a6f372725c1ea5922291d25870158c7a51494d6e27808cf95a72013fa5c7746bb778d050611d3d9e2ce8e058675fbdfc21483a88dca860f54b104508fe0c5ccdfd54e1083c75392a0badc5b5ec9885a95b90a3dca6e462e851b9e406516f4f9b5043ad72062481cac582cded4405104255c401d7c157ae64c64ed7e48ea003c15733d772595002fe61413f7712f8ee6fc2751c748567dc0c2e7858b63a30963aa9b10d39bee5c51e5568512ddd02baf9551c372e728e69e647cd683e2969d07db84a73320efdc8658c5ba11ce3046681a2b48c8386ae7093d49c64a7c16c948140ed7b7273c1a87703c92c03eafa5e1880c8aad011ac8142dbc305f8b44af3e7ff55330b2d458224bf8924f917943ee7d151fc73b9a85518231c1756bcfeca624959380f5ccb58b1589481f7a068f1a877bd1c152426700624c8fdc4ddbbf1203088287271ab446761eafda5aae7db624c7bc302a8726623f1ccd0ab4ca234cb1b308572feebcd01a6dfa5f283429fcc1c76cd0ee55e23f073744a3e8e70e388979810552c47e1d6c41f9c89cd94143df5c0322372d89ed1c0055cdfe1e8afe7493ee072223f031543d1f2e6967ef6a432a12a21c5479163ccd74dc42c6852787abafa0883584252903b3c841aad4c5f11ea290930f60571bdef6aaaf9c161366f6acf27fed9e6ba1f3cc8c5abf857f9c6cbcaac2530499158147bf57c581dccfe578662f26033b2209b4aa0b51e2f8ff37a4948b3ed8a88b584639c2fa460c8a3a03a72e99debeedcb672de22a6c3c00c208b41c6b3057e0c5838d9ec4add0de0d36d7237ec7838f06f2d6e638cadbcdb34a381d815c90eda8598ad9d0b3a12bf3cba724270c31aad7deccaee4b198beba8c46f86b84f90b203f1fb263aae54f72d8e2913c01cfbfec3b18d90ca0bbe8ee0d1eb11a4d3b621e2d0e59ee540b0e320de72123339fc273b56a81f783e0668617d5f4db1211003000695578b213360b9704c377197789080cbc641fe979fbbd725f81b8f2989d062ae2e199febeacd8d34075775e783b7d99a32bfffe0636159405c9f14b07b4c5da86553c5ab1c18d5e96dbdc22d830582a51c321dc06b3b57537352bba1014bc0c92e79d15770ab65eb72b40e9495ebeda686e446f0da2a3011dfd22455574891c22b71363d3b25bb424f9d9ceb20dcfaa5a350be8abdf6c39721f89f22dc68052c57962a9661b3823e724895679bb836ef74fb5ad44f52d4a95de28700dcda4480ba1c1f549ad9649c1ef3461550944794f386872349c71f992b361fb979259760a214a242811f7b725f57803b4d0cbd8f41cc5bc5298431fcdeb29e07c8efd420af25cc9d42e4a17d7233deb6d468eef5b97b05afb16c13d374120d0241648db10832f7f78f5be7825cf1076933c5af93d6af69aaa4783507b424316b1d60a415aeadc17ddedbed377216b77399581c4d0b10013f26757cff709cf4e6dbe8d0c5fdd421c5d8541927721918dd6575f74e2e1595ec13fa0931f3404597896432adc3501b7e76e89774721975822fecf6ba75768f2513d0757c027d261b7691ba514e9eb1f3452108c672fd55ec0159ca3a0149e0b4e0a5922c6cb6edb813144fedfcffda2980457aad653e67016ca7ddf23a9a4ecc5db2bb7d24e17b11b8b98f33ef42378ff1604b761fe3948b84b9c5df8190e6992edf4163e155d7e1f8406626fec5dc8da7459d3e43d7582e47d708ae15e341a2b834b40bf819b5db36df3c9ab3b719137a642b3b72e40e19a58e2fe491c626ec2e53abb373cd9a7252f20cdb8388a762176a0c0e1216664842d6dce0005705ce4b3a22fdf58064b006f71dcc1e7e3a7567af7191198db2e0220ed18952fea02aee21814f03f10f7adb9090688b38fe333cb1134d72f642a9a144d50812e87906152a451ae63f3b3cae5b78c5b8078b4e5624aa8d72ace032f54e6115905e6b2e240246e5aca1c4a763fbcd3ace19262f1ef5c5ec72caab930b7867129f6521f412e72d276d859da925ba812a61602994cf460b2572d275ddd12d6d9822d806d01208edee11e5ad6da5b33fa084f42edac1bdac075033f271bcf642f9676350d3f982d8cc8b026901df1af741d990b533ccc1f36e720c63a6e91010d4f7d6b9d7a27d27ce32af7a67a1ba7e5b9e5cf68c0db50550720ca7b307bb6797f0fec8ac18796e0cce2ec5e43e50d4b93fd345b2a43d9527209dae966a087074821776dcd8e739bd578d1a06b2a9a7f6a6ba2ab6a2f1fb1772be7f7dde6371df5ba69aed3e399d48a8c367f26bcb54c0e865a9c69d9596cc729a4761a0d32b24c01dbb76f3b336b982531d18e4df4565971ac696952d2cc9728442fd72937a8a3feab72a874b17797ff282ef817ba4c46cc57f4f8101e5e21ef7bbba6cbf083b4db295c03658390ffd91c7239192ed1b4064a5e1e15777c3316279b40e52e17609f2e1a59878d4da9010bb351a348191f0e61b5bcd81860c725f58065d10013e0e4d771c549ea89e843fac3319c9cdb16ac1a39e1e9cc2337203d1803fbcf0083fdc9f8bff24bc74442ed4da6d4a1236897c4466962733b9728bd43c231b8aea292a51a672d369a4b11174650235495957459e292d7285f7722ddccf9468183cde8027410897adcb6b3ab5aded616363121f29d377d220472cc0b6c8d91ffd39c3cb83e76b3b53e9738d60ad998d6ce67462e8020b21d67272a193fee85b5acaeb1987d3f800d5ac8a4370e588dd2de8a95598b80a71da5b0cbf16a93620da78c73640846710e71ab6a5a1590075502d696d3bb09dcbbc2a72be5d245e48c426280dbd601daa8f39b988ffd5df885d7696e5572231fa5fdf25084de82cacc94cffb9b123286c16022eae198e766ba0220b8206613d16a178721db9c05368d5e83347468d6664818709f9414750c986c5e5e1bd9db888e8ce727fbc32681f94b4130c02dfc41eb0c51a35397c3f57bf41c9615dce46dc22ef4ad98622f49213d0ab6010d18aa54550fc262550d5b09041b606617ff156d2a428638f9c3b6000b8a6ad306a30cdaecd252d88e2bc20901920246f9ddf3803c672c52e3d00c06b4df6266ed0a62e56c6db616f2a3a3fb88d3db7f9ab469148f5728853e41d5269e8099976dcc0eee4614c176a2d8459db5cd07f61951e3d7a6272ff91480309af34b9a0bfb24227fc65e941e5af3070ae7b8b542207e51305b072493c8181deba737850666293e2852ff82398924f419db05340a790c55ea8b70ef96fbe330620ec88aceadc1b9f5a50c4d6ea5461794a4d6795e468d36d5971554923657620d40592d6ce198707a3128830e290e8621f1678d9f9b7902f4c3472c40c328522d4673a1b5ed33ee749bc59eaf68267a9cc3d886dfa106144e62872d1432b86394bfb530bb55fb6e99dc8f2f582880f357dbe1f21b80cc6b7cb5372a9ceb403130068b7df94f14f6c6561c9518956e1f1f0a043a813ad66e3f88b19e18784d3028ccc4b37aa158e6a9a3cf4e088be47b5db19981191bf7d8a8d5a05a6b347a6888b5ee7a128feb989f3bf603738530ef92e712aeaba0e351444945af669e056a1d9e6735c024b946d552bb4d208e3619958ffb56d726e922fe9a16f4332a680bfad098c3ff08e069138dacfc6bd112eca94793cf3f87e42fe83a6722410aa522be3d5bc39e4fa12e7c94ec9e15d663b777c0d6f84ccaf49f9eb8a723bec4b24857fed54261591fc63b4373e1840419b2b8b77e2d1a8a836ee5cb4721c4bea2fa4ad0c4e546649f1a1dec0daba0596686bb994db76840aaaba8fb87298cd29ce3ca76561989acf70a34007b52c0710d092d5966bcaaf16abbf59ce729f0f14ec04c9d9f4cee60a9a1b55c749b5a12ef383ba225efcdaa4d3ea9369546279c1dbfac0d73ae93c1a3b895fc42bee824c6beadb01cf6721e65e3230a272a0ade9f13c650811743b20aa1e36ed8919e1094d34ea1f1dff1ea7e1f04f1443d43ca6e38ee176676a0637d231171b592d47a646f71367855632b20a48b4a54ebbf8a31bd8d359c6b30f522598305e1512d1e665aac1e894f4ea319714e8ac26d4a18e14ecefd413356b92a32b944ee317a44a0723c98a84ce6d93cc51f4a028160ae61c66c2d992e19012eaecb8f75fd031b6a26a5f384eb3dd5440a2242b72328cdd42d38d9b873443182a7a096412eb6045b0e47b969051c13f153468cf16123f59054ee78649bd0bc46330d3ce321422e259dfad97aa3262a5f8ab7d48726914572ab8c06e6f3fa0296c28000a1d7fb7369fde56709c73f9fc2b5a41d672fa9853d4a2f5d4125cf8771a5f0e6f524b67839f687a8e5e97970cbff93c8a148788e584b015831553bcd15222970783aa91c87fdc7171746a7a7b9a7c45cf2699cc9e5675ee96ddcc99ef291a78eb3294787359c9e03b053d25d3a89877bd72dd10146ef7f4fe2ef08227253a62f98f39afa0685bcc0ef5000c9de0aa8f0c729bff80d11bac95bef281dfe88e1dbef6b6df68fc9a81fddc72add2f0dcbbda72db37d0d7cf50c2bb744a91e678079cd675a459e244727e19d3faae6231ecac72a4c87b2aec41efad2b29c1dd0053c5b1aa004199a0011d7eb0c145cdfab369538d23fe71fa1522f82bc2c2b2522368eeb4d3a90ca1bf2a30897801fd8166bf323ad74eaac9a4c11a9be9709c3543758af1322f8c101b0efa86633958a8cd5b72eeaa7c9c0316b2de771b839ad43c0017b7045da2d17b54cebcdcb870d51ab24d23b3df11c592af1c259eec88a2770c09bd4ea3bdf6ea8e3e579e1ac4362b055486389c9002ab27563fadec6fbdc7e4341dcd4521ffeb70be9ba2a7b3548c5c72223a411f5d56bdaa447df2d73f8dee64b7f79463a6c3a5082fb44dfd909a93722d391708b8117136169ab3c99ad1a511bcc1d4d5a4960f3a66db9e850c960e721418547a553ca1e8ef546e86a10005cd586b80fda7ba36274f2a517e8e8d49728d72ec9bccca75d87e11e18b5643408007d12928ef6bd7ec6f4b5ed2bbbb1e0156fbb49b054d4035f1fd77c0a3072574f6055511a96ca1fcd4f1365f6eed2d71c634ff61e7a879a6a24899e5d7e4cd5786495e033fbec72b57466c156d906252aa36ce4fdd67d584a22999225b863a7aa8e1690440aed07880d4a61ab72d2072312d77e5b0d785cb358a4c79f10c7e77d8035214193dd4fdf4dc485b57949572658d5c2f2b6fd728bce2960fc7f86853f2e7f1bb87f9eb2668b3971cc13153453ac471b015a8c6142bb85cee92e118366e751a1afc6841fd8fe7cc1a1430614dac2db932da9a12e3089aeffc54b7f55af854ac805b6523ef07bf4e23a028f372872ea3746035475d489b1cdc26161e8afc5602a22918f220543e24c71d4cf211f24071a8b075187a374131c2923f8ff979c3303d38d65b1674cb07bb3de71a07116a50c77bd91db36cebc3b7bcd34423fe092e22705c7d6e02a400c1a4753e72ed8301a404b901f7309088e4730da06728b7de62989ffdee810d334c0136c12d9a036257176e42f2815099f9ae7f5f636823ae6e236710511ef3dc73dcb7742f6977fa40e74d40e93c76b4673e5317d0cb66367266cd65e598c58285e334ab723e774617a66ec587c4432515cbe19f711a5523c978a898544a559e9743c878722f2c63bd42eb471e6014cf29fe3e920867be8c0045f12c6c72550f8dc4ec0672bd7ef066e07f1fbb26b5afdac9ff09d305ab5db974ce60c7603d97443b18520d628ea81c9f54998cbf9c34f0745ee75dbab1e1847f2aac0eda29f5ccf46f6e3299c93ec6ef9417c10ed3aaaf3b42b7705ade427d2c47dda690c1db58726c65048c3a0625d1a3564253e11a2cbf87fc994c8c686b9b036f8c00d6cb96898f9a377f1bc63b7e329173ffd5bb49d37d11a2a5e479529af111b424df79e26a76947298f235f67a474254bb6e5560dfbae80488e6b6928ec9328a0efd20acea7a877285101953556f2d21dad7f60da1c074adaf784fc6ef0b799cf3d542e4db6798729343902bd0dce72ea8a70858e4e1d6630226d5ec6b4cf3b1e7b3889796ed7f72797c6e0ed12d8ce6c0c52b9ce1f2fd4c28bb7bb79e7e513d94179aa8a929575a7d5662260845fa3e3770e97259dd0ed16f5f52e7bd429b865d21b2be3bd2c237f298906b133cb635296497a0fc216e3b1a6f3cd5608cc3d02e6a1e6630a51234ac9bcfd51fbfed71ff5d362986974dc5e11fe55452667d35ef732f7358748d72fa7ed3acf2e332b327843324b0a06a2b1139d809ff732406176ac3efaab5561c4520709172aef23cbb7fb2946b4851b4b070d1bb0e9565ffa771f4dd5f26ce72d7bb7b15d5e354c471730b8c3086c7aadd9ae3586999eb7453b1168b63d5b650f8405be91dc56d045b10c304f472a6a7e0da5544c6f19c237cbc7431937c271d6fd8b4a0b4957ca2feaf11a1a1e0220d7f025db950a7a5009eb1940aeb510b0a57257171db0f900b5e6cc7e3e329020cd7544f983e499c0e45e795b2ace34872d1c2a40852ab93c66e0e0119c729f445e9602dbbcd3b11673c5ff21b64b2cc72cc0b303afda7bc7cf897d2cc744b9606c66607170e13a64da65d5a647318ff729cd58a27bdfa9d18976826b8be4d91ca49777d9dec92849259ffa84b6e396672b58100fb3b4cd6023253a74b056efd7e24ee8acc590384dc9bbcb55024d8e87256bdd21c3bd37fbccc384a540e7c8086150f9075e090be62402d0d3406bfd0725772855f5a2bc0a09ed7ad02c69153ed86935f17e9698727ab068f273260da7207f5f965f5aeed5ef2bbb2d619649c5e6ea461102b83345289ca636a9cef070cabeda3deba5bb16bbb0ab9302426214862d12d7a1ad3e8c5896096b0f1239772c18c3b24a4f9f5af95ce9a3b62c5d4bd0484948a2529a0c728e1cec1d7b74672d30dbf920136d163f47dabc582e173bc733e381b29afe9d96411e66aa8d59d23a6e95d322eeb9a3663586f434e7d37f1decea7abe648c8598f738df3abae5d722f13546e3fb29553b47a6afbd9ce0e8e76801e1c286a32b73317ee8be379291ff284f29707421f0502c51f6dbcf066b7b52a0adf5d98cba2caf37561c121ac7299726622778f43dbd9bec39805bf6d9700bc2380eaa0cfb50db18ca1558b577245c9ae62e6ed404dfb8ee39203548194139b26a0dd7d1fc325069d470107f12be7f5faca1a8607a7d0397f880ce14288d0caca9bf07a478180f31297a1bcbd3821ed2f8fdbde20a4b3a2e958d838c8e1ec2fb7bb5136d7ec8a576afb8c8e171debdacece98a5dc71e04c3d5ae424a654cf30fcb5f0aa424fade6a41d2f6e2f72a98e7d0af8decab83041742803ed3344dadf650d7c9c7dc1f688ee32eedf1572415fe2019daff61d0890fcd201d7a4c1437c2b3707281d228dd59bde180818720f4f610cae3f17128977f8b94c8c7c64702f6d043cd53a78d03594d36dff9e12182ead286b387a111a814a24bc748379688108acef645419c6d434ef0e7eb5445cf3a3f7c7501e352ad15d1b1a4baf7474b17a4ded90c47922392d4ffa1d37347655b9b681e34e25b296f48f7d9f972c2519ddd274d5740a97392a762eb511724232e373c2290a3ea54f46490856b5d507733761095cddbe17a0c4e9bede6d72faae79bccdbc03da03655269dff80a92f273c4205df1b176d9af386db6423d7294997b63da5a91feda168c0c0ea0da472ed5aee0ef18054f911eb0cc8c034016a079c57e22f4eae963974e7c7bbebf0eced153a0341c1925a1b07b5ec1606a63951d2663e3ec0b05a5990cef150e624f5c464783919e402972c7b8fec002d872c9fdf8f6020036aaff3eba80a3f8fd784da61ea96fc8d653e76bd60380c58f72da67199fa95dce1502274d6fb9dd13144087d0fe919c19bd9b6d51f59ef7e34c52b25b05d0c17eb75894063b894bea03e1cd2a3202f32734c346d76df3eaf40ca6037ed91d38334c59c808ff301613c9c5da23810a9cc2a37fdb265ef972a36375a753abb76c7767dbc73daa1456b54977b3218c2cf3906742a1154de95f274335eb6a0d545cbf97add250b8ba961fb48b5c5e3d534cba834ee07b07f7cc7b72bb253c9a63f534ced62867d497a0003f37da5004324b1282e8eac3eeecb5c872c59acbf87424fccc40575c5c2aebdbb4ac3f559cc3e0f5eb3b90d4226dea2b72927f07bc0e9a08e3fe0b208fb91004f05bb22bb3430bb9bd0a90288911ac245addbaeaa7f7da92f2da02e181fd378fc87d844ec86808f31ae23d9b472aebf7721795248ffe3c371df034239306f88b94fb5a4d26f45b5cde1123b0d7cc24e072d9b47d98961ac502ec8b89cbe72eeacb4bd48f56b40c92c57117cb1193883d0d2af66a0083ff82335a48606162c3deac79f34b975081a5ccd2440fd5be72d7583a9b08c3cc48c442e2e10a6723db13ab416d60d0ad702ac006b40e7217c7dd723dc46160876f98d78993ce9e3578c29d0091cbe3a7cbaaf683e19cad5ad6d7720027771c53a0ae6354bfad5e57f2fb561375a4731008564eb7dfe241a17c3a72983dc81509e82bf85f1a125957a8737a235ff99d4ca320723938684d67ad93008922d3d8e376bd392284642910895f3c8bf119ab61bf3aa0b17bb6c51af2116327b4fd96535f9158c55cde58d746d4e911dc25cb1e8f67f3eac3916e83ae907257b11158e38eb011271aa124ad352266617f3d83f985754d71ec526fa5e876456572efbc068899a5b2803f4fc82d2a6c85140fe5285e2c6b1af9e45be79d5a721361ecebeaf2eb9c09939f7191cd1a977d565eba86516c8fa7097b70696607096ff755153ddda38e870b549d0fdbc4fe02cf16ef6f18f24e398311d1804ddc13d663531ab08a0cf64597117b7f6e19d57930713b51dc1418fc0285f4db481472ae13bdd613419aed02cfd905e99a774a003eb94bd0d6facb04018b7a22d55c462ec8351922a54d682e7644e636635a768fd17a48f46c4058f254e3ca0c398546ec580444acfa42d69cb046c1440a86b3b9af9b3e3a6d39f5267bbfddf8d6387290c0d962b4d90eb91ce8a91cfaf35a300c337e813de3d7ac54b355c251cd1e7291a4d63a1ba82b45c4d34b37ee264ad333eff1736c50772e59a7d68881b316154b6a5921412d83ca2b61e57ee6b9228578d6437e8fcaa66a512fd8bd1d35fc72a639bdb20aa8ec5e77a4f6853f160d70c3dd0086a6a12de33c08fa8fc6bcc472681334c3955d4e268973e92401162b0b385442e76d6c5255449a5464416bfa3738e66f200fa97815a204f08902a11d2444966fe22abf5c673e8d138d7c27054a4b4554d74aa4c33682e03ef981ad29023f6f260b859dbdbd91450688095ade7205fd5546b76b9916237b94f597bba77f6ef69d70f9c26656c86a9d733af09372c669cbeb5b7c2ffae035e8e7d0853c97f96512b7ec2d3edf5346a38b00417f7265349085c4b738086172a27fd3a33c18e98653c35a3c291ccfc954a25a929572b1482dcc0aa033e72639ea571f4628f6b654a227fcbdc50543647aedbf5a88023352fc3cdb74632141d77f33373f0cfdaee0a5a85c91cf6cfb3cbd5767823b72f027d3fb3584593649959272d1e45f65da1937a0b0fd268ac114c27bf2a5b172c57ae0a5da329e1361ba0ad369c8371ea3b908ad0129a25265f4abc570690f7288f8dee4617a213d1125cb7d4d9176f17c57c6a98a2d17b117643e70f8e3bb5a1aefc2dac8be0db8367dc3cb6d9a00a3efa6596d925e537d2107536b17124b72345cccac4ac6006dcb322628b11319680117a4fc5048ccd38e3a5145d0a6bf57c6b413b8b1ef89873493a25443c0120f335dee4e2bc4ba04a655e276edfa8b72144c6c576b9b27f67e87e17be1ee902937415b2063faab7ac9f16e5a2ecc8372ca3481d4809711ff6a4bdec051b4d252e8af997bf737d1ff98cabac69d3f2c179f1f7d3ec240bb111bfa730d066ab02d99bb5697b30c943699f511e7bc2bd463f71ecf9764cde987b02aa091aeaf8c56f00473ce18bd2a15120f5506ed5bb672ed0bf47bbfaec158a76dfd8ab8926500ea75b8d18531d9c4db93466b61bd4872d89451491526cae475173162fa07d29adeed8fd24900ae45b1ec79de4886ae3af0f5db844cdd0e0a93e2480a5e9ba4d21dac7df4c44dd762989db42743926e40b8b347e86b5dc30c5cd44c2842854072974a443f727fe00d928b38e3128b3270c2ce8bc60502d63eafa3c2d2d2f78cf7c91ac3353fa49bbc60d62cf2c35a4e3cacabbe0ec8006a1b5d2c865e70fcfdd4bb4db5d41764ca2e4abd3018ff2fd82149bd1ede6f3adfed0a1be2e83ef2b9a080618e9b889a0b332d25b2c83aeab600bef32fc38affdabce93cd572d2da4926f72748d120a9dd96a7050bbcebe71472b070b750b498d5fc9bf6efb97c6fcb4c1dcd8144b15e78e485ba23149e0664533f478a6bc9478c04f5811c5f4940da08f83f3710015aedc117ef37f02dc33e237a9d97d6c4d4a44405c72ee77e169bd12e45b60f039c299950c80fb732bc8024f0417c10ba3f587f30d4748377aaa9bf5f6707e843321835fad2ae2e3ad3e635e764acff46eed53cea1bdfe5fc93c2451d1983fa416736df4ed4db7f1f6e90726b13dc72726fde79a14d37ad3d06751a5899bf308ede2f180e5fd9515819417297a722710e4ed308bc1cf94af18773661d76d537d978c48518ad5b4eea63db7290a715900926dccb1e3b7ab94b12090f839e4c3a9d2f504cf7d96f27123e11705ec3287d95ecc9a8f03271b403dbe05e8b6f953a23e232fe3db610813ed56b72f6ee8a11e5f66d65840775e5e4ce947e6faac69289cae25445c24a17fc14d9713128c61613435750da520812dc762749ecbb6a116bae9eaa9ae5a6ce8b755172cfe5a4178bd39106a4840f398f34edd0dbb1bfe8ddaa05fdcd88cabfd6445f7245f1144598d437715113e8314a0134e1647a99f0d07d818ecda7bcf434bc9c728ccff830c7d8c7e28e5bd821f2d6e663a138def7641fcbdaf255283c66080a08ab3ac966469b9c41dfda351479887ce5acf51b488f369d9cfae937c4682e52724314ef35655c101eca094ae90f0e42c6175eb9340f1c55ca080701355bc1b072ff08ad160036914628ec424fd076accf3f612f07660833f3263c177a7987b2722d6f49b152bfe4dcbab3e27374f0e9438fb8b32421aacabced62326a58fd8d44d29a4640f34454b206f1ca5990df8603f1e7e28c2dc52949bdf264ba281770439de01bed023c14020ccb7aaf41a609f8578d9b2ae867cb7d6f60f2626a2a057238ad33929275b4fd0e365e53af365d8385a0bf52aac4fa11ea0fc1015ea28172281bf0ae175cf36709c9daa7ce4dc3211cec2ecba6594a576771a8817366dc725ce6852ab618ce7c3e821a5f7c04d9562de29f245847c5c2a7d45e05e274aa5f8e20ca1c49d724db2511d9096297d9c450898ba5fd34b0b55dbaa774786533041baf809ccc18c5e307de2f11acfc7c6cfefb5bcfe5d79796f953f024137cfa2db49e33195e92ca1b731cf8b0499763880b69c7166f593af244ad8ba7f704b01c4c6d4c87b72481807cb35403889e0301d8fb04c6bcc482f353683452911979724736547e9b24ccc7eab1de70c482e1dbce356731c29761beea7d80cb8923f8725d8cdf37bde171e68ea8490ad672c25030ae328ac49ab2d22ac48250ed7f5272abf190e6fbf5fd9724d1797213b31e7eb54ce97f5267b8e21e7ec704c135361690fe8bf8ce7fbecea1b7e41b30a750a02ca2c8216ff8ad3862480af39f3c3072a03409ccde636f15e3410b649f3c4efc67f27a7bf6802a412f88700446afaf40f38a0c660aad785431ff09138e2b6ece1d3cd2ff2297a786a9c9e38146645472d141da5e7d52a50d1d32701c4de1f29f6e3bf5b826d91cd7d2efccc333e0606e4b1d4bb738268ce3b0d7e7d2edb9b4c654e9dcf8d33a11aa92fb9b0e2cddf97284d1c49c8f4970ebb0c3b2aeef63fdded947ffe50caceed07636e43a2cc8da720a63285b7cefed7ed9774216d4410a434ea2592a743b8e050c0bf61ae8d366727f8455b761f4c11646941574343250b422aa33025b8c6a7005e260e6b21e5729f21987983259df1a02fa6b6be8ca51ba0febc92c2f1067595691344cb825a972acc6e6eff2606ff90030864b6f857c8fae7ac7455c23ac8d8d569fa082bd8136b96cc9099ec9dcec530e8557adc8a528051fee50cb93733a9eecd67374d9fe28b09db1f10dacb6ff43fbd0c5a76e0aab65223a4916d8ae6417f0407f8cd980209d42851dcd1016852f8ee40007fc3ef25d157de00844192231267fe1f67483726abfdfc2b3c465da518984aba9008a1bb78604702364505036b5efb911b7894aba3f14f9404afcd0340d27522b0578eac5676c98f30873526afb31c002951d1f620b89e7e1db3ed53987c0132010e62137ec7ca2dd6f0aa693c71ff93fd91e12761a520c087969fb41d0f998a06bea0e1b9c6617d6483772c808720fc112e46caa58d3a557e47a1de5cc8d51bea9744d5aea02ed23dbf6e42f70e7e417d1386bad26480c48952bb42cd9e7a99b8c487fdd030fa43327170cadb58b7fcf119547f6637a5e3d663d28fc2ed0037cbaef6b49a856c0c02be6f48aa9750df05d8472c32b83fed62e002a76293544e336be8ac79682993c04bec1f7c6d548c473d7724d50d0392b2ad1e94100bb2bc116d8b1c2f0bc55617e506df1bc2a99d1b88c34f4835e741e9e67749ee47a8a902f79352e471743ece906cf316170e84df0b9725e470ea1e735ef8d6a3d2fd0dbfac54770f201c1c0959d11a1a4a4443a726d12d42a1d2594cd795a557b6d23309af1e15c7415f9fe93dd2de696aab9683f3c72990cd2906d69f1bccfd2135af9e4293610b881812ad594fb946e594402ff237248ca265ba32468acc889800b4734d64216d5e7f419bb03309da228b4a7fd7d470ec3b0df394bc750cf7cfbd9bca26519978c02c17e564e07a90499b87b5787725510b18d93f130ba1d6e9af036063481ed940458b1b5047ce68d29d24547b572e0969215e4c4b04b854c2ab63012383f17e474aa893d7651c9954646366290729b538bfcf75894e757874d42413cec3160b99bcd7968ce33ab8bbaca0f0b7a726891ed6ad3dee4402f44cceea3a9fbbce0a53f29789ad55c87cfa0e69af0993e05d8f2e887de0e108cb5ddade211695c15aebc27c5543b60945da2e2446645388ec067282543e8f79c9f1d56161a1b875eb8ae143ddddc35edf08db20c962d723d982e1f67aba77d3183bc10df100eb3b40354e1c2c874b22be085dcb14ce672aa14f1db700c85493cd57520031649048b9abb8d45eaba6e4e0933f077ecbb72461cc70dce529d47dc28ca6c2d208257e60ee2908bb9bb485393d25953724772811cfeac89992a22466e69cada900541af1684b531691b3e7f820ba56b21e86559e156d60c1b272b755b2f8eb3fec0391d98d2c9401bb1e1b403030aba1d5b236f3ddc1c50e3597658f1c8f004a58f3324c08f061b1d54afa9e1b436fd128c727e2a3bdd2f4862ce7edef3b107af9253fce396d1dcd9d73a9bd0f3250f843372480dda4300c1b8b1d866deecaa81a493d205f734eaf7a8a7eff93438b036f2729880c337b6dc295f399feaad55f5db9fa0a1914a974f30aa574cf44560debf72786dfae1a60c5854e9742715d0156404dc3c3628682bc42d8e42e62bc13cb37291cdcced34bcf7dd6922e75ad1393d6b8f220b21de97926c9fb6fcfb29af31727e679d89202de1a5647838fe919b208e9c11560449ff07f9e42d89da02404272c7b0c496ce5da3f60444382fd9984032e4182983891ead3776392653d8f52472f2dbaa159fec3b2f7af000a8916971733f44f917a46178fee3fe9ed95d08c072545cb332dbf48a8ae0d651ad1dfbc096e8f796484c84ed1d9e310e1e0725f47299cbabf20de4f79188d22769e86a03960b9314fb776a1dd8804deb45030f3972032f28787f3e001a46589ef70f81403899c34a2e222bdb756b7fa7c392a6f1093aae292392c33581b8f0dedd92a8e6a710f02e5e4379172fa786f3fd3b149072e7fa05d2628a8e1b9e10033b35a849092784110722ec4cca9a8e70a0d19e8372e5a839e70651c229d9244554d1fcefa9a2aac6d4320a64f33acbee5f188835722c2f6d6584fb1af5f15b7187492ac8902ee6a9214725bbf5230716488292717210e1025aa47e51823e94ef6a5afda1bf9e86c6e3ae3c66bd69184a3ae6cd55726c8abce30f11a3aaf8de26a0970b6cf2f0a3de7166519c459b52a8bcb72f0a7238e5e18037938a5e1bfa9f1f4a80e1fb7c97532e7b954dad14b066194a03543cbb051c352149e3681c076052a3e12ef1c043f41986d4d0a7b0587531d0ed9072f950023c49f7ed5cfbb5ca8fb891cb2e6bb715f67e76e0c1059d711f55b87136a508a9c18a090f9a0b6c78e76ffe520132c92741f4d778bf8c7873ef8829fb72d38515b1c8d58ac36d04c898f82ca3034c308f64265cb3c77ab99578d9063072806ee339d62564893bb4f21f270a10a1390df4a930100680db2c1ccd706c800afee572d8a58619d625092199417d0d97e718bf2d426193dd1cb89b96f5564972d43438a5d0dcc4f34ec40b439111aaf073f0e627f04568589622a9dbec915d4b2a14c1c8738b08b595f1e4e0096df6d40b9420c24e35b68985270d7645442360d35413a1a5104deae90d6d77e1238887fab1373aa107097300dcb553654f97294574fec98dc72b4f7631a6d85cff50bf7df265eda228b72521c87f4f1de3157170e74eb5222a2c9ec1ec9dc01775131a5d1817ca5cfc9d270d0e1ea6bbef6e72a11128aa07203f4cd04750aafb643e10900cdd0fbb46f4ae5e40e7dc640906726bb1b3af0e8f6d79270bf75ca6a19dcdb4d673c9786bd171424f8e17fbfe2672d99f44500b675972f15add8aa9a5f1912396a395a192ab0ba1463bec4bc199720cbe9379c3bc6bc96504a87efdae30ee52e49ab238e60192ca85fb2c46860472fe425e7c65a8db3013462c5afc934c360c2c41d0cc14a8df8fa5231906277b72efc5a5935dc6129a04d0a99de934f278a6851d8e405165eaa24a67c36f18bf08329f49bf2c517d066864642877b445c363956355a30c5ec6388e7dfa084b787281b5d44f0f985861ebfefe2682ede98a796896068cc4ac42d069de36b508e2728c1d10a3a42f2d82e261d498b9a678d2a56c66aba19ffe1fd2f00e9e582ea172bee19c39a3cdc548e63d01423ec9ccc4ad0085948e090545ed209b1eb4d4c66210b2f542152602da3e7fe27b1855da3cb042b5736be24d0d498f438a6ad8f67285f9882068b0ee059db742956546f3d310f621575c670bd1a2cde20b2ac1ca7239b9a0de62e9574043e63a64a9afe5a39ef4e868406643090fc8c2da8e004d46e5c65812079ad6c93053064cf34225fe2318455bfb661b532fbe814313c2e129fba8e4787b459e7a6e31e65f518beee67a5536e0000033efd9c30b6cfb38285a465a690bf68d9f60a976f54a106d064974a76359b5da621c058e4fa6cc1b647266f2caf61b8250f09af2c0144a7ee05074a4447e7d50cb82f1780e7564af822b27bed637ff5c04600baef0d89e5d815fa5c5872c9c1987ed187c71b9056296388c495af746c1ca5e9b5b15738207afc54abddcf743dd838814f8361677902d53e84094d150aa6393f8b1219152987ec3b022ce49e2affcf9992a8c9c1e62d4721e6b9364c6338dd8284f4f942edb021e0e3190a02a7ad23b7a823a3f10f27c72f3491a21d9a10e6f6a60bc5253d29b040c67b58b8e1c9b95cb67b174cec6b6728f0b0aab028c53db451f7476e971dcf1ca81115a2dfdce67fdff8462c4efcc723b277d4fd0749c7650ea781bf69a58007f28e536b216dd831555c2739f91eb1648362c2e5791e242a3f09d3c0cbab41975503dfc311a5b2888d06186890efd3d4dd0420010780fa34f1dec33f54908c556c2dd696f5d8c855f18dda658d9044a09abb6b771ba94b45d1b187c2fb1c48ff95c154b8eea7ffb9cace4000526dc729a9c36d90d1201224abecc93ec31c49578bc7df06028a5c790e21cfbdc463c5563f2aefa7cb629573f5b56f72ac4b236771f2ccef32c183ccadd9fe2105bca725936027f2d151c2966db8811be58f09ba5426747aa394b061d3ba616d4f210705cfa522afc13ebcb715e88ec219b0edba39f8d3c22864a9dc414c20388e4a0725a5fbd6c1a0e07e94a6a76bfc734863b04a436ba0e146ca31f07bbafc87d8a72b3e1479fde2befb6bfa054bb8320d99aafe98941c010500b4dbfc41689925972fffa980acd5fa1bcb67c6dc0f5abe340a5d7a53ed714ba72619b306897d81c72fb1d4119dc1e375b2b67f1de62f3df280b47ec0f767f4acadd62e1c2bf225672808d6c3395bd70adbccf9fa9697592c657f3abbd0b0623d81dd5da1239995572bd7a9eb831cc2135a127f3813713aa16e8acb705368c44e677b554316ffe9a2f3c5cb5327d79f1dd80784c1ec6bb8d56cd50e57388169a4ff4d784004aa2db443f650be67968fb6c88d5b7f906f5583cda35d01c32556ce3e20f37c676c83213cbdd3e9bd845bc7e03028ee8c9670150bbcf847d5f10edc214eeb6bfef49217294b166fbfd507eb15773babeaeab614e8c597ac448d473bc68615a743e02bb72193715bfbb4b5b7adc8c746d4e33f9316ec66a4bf0a3a9c1f6e6b41a75a728568a593a3872801ee64bbbcc75ac24951dd50db47e486a1ad09a95f5475efa1472233b11712bae8eda06158956b89855412a388387c62f143dcfdbad4ae09dea02485088256764e263dfc087831c7842edb75c96410252676355d73148807a285e6f89565be01f7989465049b934f0cdfa52fa59355015800b87a4e7a63b2d4b7218cc54d756b68329e7aa35d7ec065b0de10cfb7c9fcfcfcb5e31073447145672180445a17afd4d27911c0d8fc6126edd8cc522a2e31ab42a214502b6053d2472ae001d8e1e5aff7c0d0605519a227253d035f2ee62327d1af73013264ef0535807259f9ef8618683ef654e85b142ac7af4591bf60ddb8b691d5d52521862bf0d8b24a9ce50faf63114a8fe029724701b56754e9bcc99058fa55d57951063b449411bdc914e52dac6dfd2cee24313b7c1ac0711134fef37e04a78d99c15837d72fc7820eba4e4a31acfa1802cc8b41e2eae1f16957ff0101d11340997c2b3c272ac86207db107cc8977edbda25d82e817a07a8d2857353a1fa76e5cb8219be572ea97567030bf31fa365bdf17d2b0cc52ab348ff1894a3eb0ea207b20c231d872de42b78ab72479bf23a3351d6065b3614f998832668b04f4fea1ad2d322d0c0a8bebb018d98fa20cc4901332291e20b54714769fb6084e03ec8405d19ba9d57278e929c728415567b156e0936ba2768809d955d8ab040881442cbf2e6ad58b534d408d2fb7ab2f7de3f478d966a245200765336f95ec98dc9f94984d64dcd9686b4bdc30749e9cb064ffd38e2eadda364fd552287bfb2a7d2a98b957525fd672a9c021487f88e49d342fd67b38965df89686f5fa7a9f39ab9fa6e80cf1103a726ca3206dc6f1f4370e27469014fc379820c493ea2f1ee6482d8ac10e21f54e5337bce6f9f15172ce3eed9998ed79a8ff2e7c5679684545a2a50c1a82cc52af2029a40ef4294af3487dee4e462c4502cf1b13f91dea5e873bcf5bf692709af721a9d0d2d5cdab4aba533dff29f799c6d741019b4c912b80e981c2e646aafc204e7fe5fa8a07bbe2e418c6e80b2ff0ccdfff733c0eab4986e6a9e9fea3ab18fa7243f1c08d1499332c9e96872c39b41fc6b6a7cc69706aa2309c593358080f8a72cbfe3d00563a96b50ba19fab973a13c7687d5d32efb0f1fa604118254f2be66c220bcc6bc11bd7285d15679673c8235d74724825297f274010f2460b823c98600e6f2cb432d8854c5a44763c625ea4cd58dd9452307a408390c8c8f750f7c84723ada64f3478b32c11abe8205d0eef38e39d4878601f6f159abcd2861b576205d37ece9b9ac5873669b22d1220d38feb3c7fca7ec173c6625164c63bfffed072724f6c6fe0a5f5218a1e35a27818d5a009b1f94f5bf0e75824b172cc5fa6a472cf8d74193562d2fac72af1e7fd1a5817e754dbe8bf8d182f13c538313c0d7f728ebe79ba48c36c0473d88158fc49d870e6cac831fb1013e4fec268951b37a36bb8e063da86e37c51c7e3a6d54d10da86716bd460cf6cbd8346bec8ed57b80533f444bd9a2b0115bc70b3dfd0e929f6d76fdd70220176086703eb8e6e02d5de1b0ff0738b134f7345756b8aca901e6a5ee97ce1479283a773bc9b83b06e03a47235e0cc50928e6d9958ec00701c92f3db8af703fc09cbbdb4302641c3d75d6105885d7c170670d7a6fcd5724fa07572a4ca21522a99db41aba278c6a15bcf152124423dddf4f780f0ca2b24df975881d22d5a00baef5795ba73c3bb4cfc311b700c78278894bc6681fcf06e53e08837a3899348b9a0d4515e343985c2d968d77208442a83bc09499e55fbbccf29f0ab87ef8b43c3ab41ecec60345b2931f54872e71b8b8d9eff3a058dcb3e2a29616421ef47aa235e34fcd903380d2dfe2840722f8ebd0eb97e0113ea54cf46b2799f97b1b1aaf2bd437d509ad153b9956a4d72eca5b7e5c1c9211dcc2f9953546342e3893afd9baa92ab9603065b8cf85e99720e1e7fff5d9ae0632646c78bc6302c8e75c73c490d2047a3d800180835902c72ef15eae59641f8754db5781b8d6c450d189d3a2992119ad64b25b713b910f37237eea66fe326de0302994c27c954252c5f3e7b22ba31702ec410fdc94186de7294526215ce9721c9c002b649bb68b9d547ee82a45a7c6070bb11b29ed22bc2721cef3a06b0065c8639616390aaf4490419b1b0137362d1ba5d6b308c2c70ae6413ad634427d752f27ec76a109c6aaf57be6b9fbfdae0f1da77ca86aaa8ae70726ffc81555bb14cda303a01c79d60e77cfe4af5ffbc2ffc779925bed3c4d3ca0d3f42d984af6268e9e5c1c7676be15fcdda5e86876224412fd465fd8835c43b4ff1027779548ff8b69aca96b3d72a4c645f4a57076e47fd60d6126cc41535245c4ac5d20aabe5b7b212e988a8caa58bc707b01704155a90f6fcca08f1ea8dee726f97d4b025451d342f14f0fffece05e54251b51c0bbe37692bcdc90a23e204214b71be0c6fc99f110db2f720e2909688bc7b6164117d1be62acbcaa2a0cf2e72f06b06db9bc720497cd02263e121c5acc56fe45d955b0ee0afe42a4b02e8513c035e5cf4b1a110a6ef3b9da7f4e666afa2680611ef4017706439c17b7eb4a7553e51ffd9ef430e7d3908ed1ac413b98b8c95dbe3636b2f6b528028e510f9545babd6f9221b044bf5787c25103cd4866a7cd892302d965b9fbac576c0afc58f721c03c5d066e734ec49d56029921d2d109e6b50aed99e0b2fd032dbb07da2677209570e2234da439f8bc6538b9fff1ad746ae20234925e7a22396db4a05b1e57235c51c5e3749f77c5eb8a3f116f32623fabf3e642c6d189962961949f4481503c44e68c8c0b39990ddbae266c1276cfe8b2ef94bc38c0a7a397c9de5c32f87727310a466f6e9830e651344aa6c74dadb256bffdafaef95850bbe51944e6537728002c01c7a4fa4f69d6b1604a17c130e356fe2083efcfef5b06a1f42715674725ee99c022cd756ed182d10c867b614a7b25bd92e3a093151df2e60ff2bb93f302c7a87b9a04a0d5d1f4f7d22efa6fbd02803af970e4663e702091e4e46f20324fcda6193f48060f9f8f008e0cbb9dc920b264a2d5e5cdde29b4507a71a8e2372b04e131754fc606358701ae5f821f6d3f27efbd6d80fd9b72c18ef17969a1f31d4c0681c5326ff8432d7c2f1db5cd17a126da757bbb2013e2a3fa3cba16198728a33f7f113d67f5507bb93202f9ac23a676fae7d21df59b4b9751b4f07e3b714558a78150287f3626f7289d0a721a121fc83a2589d1d4b1ec14e355d11326d454b83eb20fe5cfc8a85c42283bf16e1e7f7b60d578dc2169fb9899467f4092772f221d33c808f74f3987a9f37b19491e51e2c06856010258e9456b209fb0f5b7277a6444e7441929c2459355d0f20cae54e4c8e2527a4427454ff1f13cbca8870fe76242c1d7110067938f7847eae0f593ba63565231e622c40e1dc416d44c0521b7f64752b6696d3d2c609fcfcd4bd9efbcb84afbb1654ac065d0ac219ffa872e20e7af8871ee9eb427fab96fc7d16c83bead126e9096c9b6193a54f064fd572552e2b4a4cd5b238e092ecdb52876b6dacfccd948a3346a5f6bcdcb8b7e0747233c434cdcb4d165826b6eb3de8219ba54cbd963f72df76b29c3ba646b06f22648b5205cf1eaf2d3b861b3f5a00f7c183bd7d5541e9848c0fb3723cfdc3d4d02d2e427f2b225d8bfdd1d63b385f46a4dfe517fe96be1738ba80cd8efd09ac9458b99f7df2fd926dd2632e0ba63b799d59af026f81bec0242a5e60a904f5e5f6728f3fd4d574bbcae07977cbda0fe7adc8c60f93cd722088e230a1e8c768114e4a46223570f6a83789d3ee9485de491dc8f8e416a8c5b1c8c993e117eb618923729936da2105bbcf325994877d6a6129a4a5537b5af7018a3bfee1f6f569b30972682f9541069f08d9b1cc7a1d20e65965bd2a8687cbde94192c8b6f74dc8c7072b8272e2be85ee18699f35c4d2d1434214dfd25ff32c86deb245fda4eb35df772cf6eb2e2d8d70a6cdb12f6dd4369bfa86630311c67451616091a1e9ef20f986338d290e8f7a3021d197b88b2ebea268889b80cf10063fb973761b786ab057714aa8ac4a576a531b847d82c5a8a966985bee715e65e07c3c7df9ec9abf5e0b10bbbe3af5a5e976743e78cd8c54b4d4de6d7ce229bad6a6236d6f8ad4f3c84703fa806d6fe4ba5a88c14bf5dc8a4071b46a6e935063499551bd8a8aeb05d8e12544f541de8fd89208cea4528addfa71b81f8fd78566ae20f218cb5d82b39f36172bcdb4ff677a8cc3b81982f78588c26752f86f4e5cbf50f9f348cf38fecacb5729c6d5d95d9f7c636c51b41c925a84b39e3496a29342a6a79cce9bf5eb58efd3348fad0b7e5c621bcf70ab93a6783b3c4b1b5cb3beccd01a7daf222f993281472a4076ba777c721d5422283063bf577dce5b92a679d5aea08b60138d8b5c0d434b59897cdfd851d05af5baa34f85a5cbfc65ed65f040e1481b02b16e8698f9e7270f501dd1fe97652c1ae0b6a3b05c5401203107c857569e38fdd7c794af3791b8d48c0bee8ba1841e1db6ea934cb1eaeba53a696061369522a64fcb0a14fdb7272b05edf37e2f5978d804ca7dd7b3e9bbe1724c19ff53773ba0d6fa7a774915306340277df4877793b40d5170c8d0db3c9212a98160c211f97bfa9fec31a2a5dce902df9ef4175b05057d5ef622db89dfa02a2e7ffaa968fc00aab3623c0304939537d170eef03f67a4f17a4545eba2ba6ab10e271a96a2e8acdc7787749f472a795b40085bfbbe9cdc1588cc9668391679b1f92c0ff31b54622b2b32f50946227d7e86cab3dac81e57b2cac07c0170ecdbf88ae4f79d84fad641c165740a7723631f0cb3d056690cbed3cf84047db8c4d18057a0aa7f96ffa11328a28e55c72a80c62032207efc3a9685c65dc9d0045cc2c18bddbe46bc955cc63a3d20fd11e16c92b1291c75ffa459ac1e926e11200dea041a242e722bedc37b7f5b86e7172c1922b8d5932701ce023387349c821f1387ec392c828c52fe8d64294dc332e30d631d36523091047a601aa291ba6c859aa6a34cf463d5d866ae34b34ee39e372a19a784f1978b1d3d0ce0975d8a377ca6f5d6e4f42af706cabcb54f0a1421e72548d290f27293445d89ea3b4853854821702bafc008c3dd37ec787b7d4ada329f30d371bf86735d8a7f199084d58f25a2ebce32ade0e4d9845407432124e8e7295d554149e20946ca080f793fa83a56eb3c301d806469c40f444bbe7456a567243ed4aba4eefacc1bf39d095b75e2a1ed03fb34fc7f8bee3ae15293c0b6ee3068557879716297f68fa4d7dc1292a1457070dd6ab602a57465659676e6f3da76072380c6fdb7f7eb7dba5552cd4a2f1334391983e8dc730b9157f8108abe0d035815a48956f8d2cf8c7097a0e6573cc02701723de3a0fece0f404c1c0fb69c70cde86c5777a7cfb64cf48b3451230e9de4376d64d36cae9dac06e9c72b688a072f5ed09d9b6dc944d7c73b8e7c446ee46082d0e2694cbecb40d6f52b5bd05eb682a840fa9f556d442ddb9736f0e5e7e08be38c0fbd57a7f3b93a295b9a68806728e28ea6c98e596a9d69830db127da70ad167bbb0e3e96cc44e212c60af437c1d4f1739f86b03e61dd68607e87e45d0172fdab05ed33a04514cc8499218962d2d147cfe96a2debf1ca0ce2e4c425412a3aa8aa5d49ccc0926037906cb6c82f272fb96d2fbdfd0fd7ea354019a63dde07f140cca9123d647eabbeec3845386c02d7713a89ce1e84048a509f31e883a3fcda0c6a9d4c5dc588b9cf36630a240e7588482b5b45fae4623cfc511df882da5ecb15ec08ff64f196be7bf269abed924723749315e9bfd5f92e103e2c769b025a2093d871837c5bcdf669fedf6a4176772e1842fa85c67eb9833c552bbdb5db0189806659fed280f3fe50d3661828b5172c235dce6c9f87ffb3b991e0e91d8b475865e4badb0740323791400ef6a47e9720cf12916f2ab1440c94e7b5a38949b20772cce65f88f08ca4c4d7f206b8d5d72232e4bd0a1b8f0685c3b838ad5f26fc3a3c5108df189fd827aafacfa9c87f4722fe678079fc52369337e2d4d42b4778d47a88d63e0a0ab49a61d9908ae317429eddaa38dd58552002c7485e1c1425bcad448f418a801202d082bb800f6c8997232df4782a4023272cac05ae935bf14573d8125f58039211af92605955e513533b7f2898e97e8ee1eaae140f95fecd3a75f640510153130b9cb3eae732498db3141851a74bc8f67a7d3a99f7bec076b7d1fce930ad34b118afa978d2a1b13757209aa3fc05b1f194415ae6690057aeeb24ca4ae3fec81c4cbfc7e274008056a7276616b4cacae482cd68a193c8bc61f84c2f9205b4f9dc8e497008f3d0ea888723b2305bf98640662c8f7eff5bc706974c5115c14b91c8942d83940f63e117429852a36290588f989ed0c238c2fc80aca3cdb6170ff773e72b94c51b1645a0e72964bee8fce54b4b2fa3fd241218368427c2277922e7e7679a56739bbb5007804913e3f9a59fc5541cb34332b0a475ee21369a30d6ed5b7d433ff441308bca36a7cad8d07072d29759ed80ffced055349f4dcd28fed6099b4c85cc392088d445094aba7e7583c6f11b5e7ca181fc91cd337cdcb0c8a1d1d5086022b230a1a10727a8f00b0c25508cbadf27e4f4b1ed69a6c40428b2a58d3caabf6f634454aff55c5a2b6b051c8f9cc6c44bcefe658467efa4e35a2beaa870dcc9ef10760a87a72230dcd9e09132017d765543fbe741be777feaa246cdb375fff7554d98a1ae340a69457bf2e0b5c4b4a41f6d618cb9a9113615a4674bb497481e35ac42bc79e71f4c2a08f5b93deaa6a9218ac8f1399a241bb5808938783a5c11b2d2c3eef924f5d37429456656863907eb4dbdd03a7f61f0d5c66cdf7f10b5a4c4c69668e6846ada5079206f2fab6083652b1c743d5a92db440f6b033d9b8099156174417717212cca14e288335be87d71f40780fd6006e9e1997fa63b708e468af79824c912e915698e711ce5d2a010dc0344a86a0e050440c76b3dd6f184eebaf8e6bc6c93775e087d27a49ab5a37d4e6655f617972c017a0beb7aab5b6796dc52ec3777a7295f5c9747aa9fa3c0c2b84e91bc554abaf67cd5bdd0cd9273af14c8e22a88030274f2641ed3c1b3c61c3e79caac8704b19ea68a27052b5eafa3dffb873492a2a78d40939b56ff8f4243a0695461857120d1d98aa796d33e2333ba988c36379724e1dd8b9f4246cb154b1fd37e6c80008a9ade0d1121d46740475006566d00b729c79ae4d725cb2d6b135df768a67903c277732e08b70a23daf3bfd90f0e59872bb662298ce29bd03b76fd7603ef2c820190c157cd796cb3adbac257c2f16fa7293bbd6d3096ccea163e39158944caf3b767cb9dc0708d6c1c356b2075bb231725d9d7d62ba3e899bf02967568a85d8b095811ff2930d8e1ddeef4cde20e6b67292106a6e18e09dc3d48e3e3742f0c85a91dd202a8dc4de1b7962a2ced474f4725450f5f7bbd1995d9942455377285158eb81c5474e7bc6ac12b9ef8ab343c31fe376945ee850f723de544bd066e6d0454ad9a7b5a2c2622e333fcfecf4053e2c17938c101f0e0e7071d6c71f62c0191f8a27c1553cc51549b7859da78376167215356566900c6f6801336e585189a1c59a419a59248214d8e21201bf54365b72a7e296e8c213411041895fad8784cda5ae7f281ed3e19fe2e4da204847d7827216497fa79b67067e1ecb84587203296848285cc4af331b0a23df6d23d908a538bc37ac86463a81ecb1caf8d24e506edc86682d1799483c749cb5f62f2dbb0172e92903c57c50fec8476ebfd9965aaafa867e259b6d7eb1c9d0a5ecba6ae4c40ed126d08464616a47d8b1794336552b632d5344ea8236a8112fd19b68b197de72c105d160e4b321e693fca55d9119577de9c5bd4d283ed65da5a0374f93444b728434b8a6458fa667ae2982e554c29d0c93c52d6b4738c6be5b057e6ee88f76727aa12c251e394dc7f5071f793b5351a55ddb64622079d736d58474339be5fa72c550bf87fb52cc5a952083a9ebad8a115e04050ae277144db503859cbefff00867275e98ce1645c054d44715addab0763d8afa5398b2280bf0713c53c2db0f720a2d0372c2d4051780d1456e33223aff60bfddece3a6fb74a13c9b0060602772be84da8c2521257d58527cc43336315148ab7327ededfa187bd051e828dfe97290b2ece66aadf34b2dafaeba4d88e56c0270eb59be5f9487c88390395790f572ae612d5d7c70dac95765216f26309a7d38ab9655568a0d37ab73fc502441566605ec4bca50ffb5dd79b877c04d2f2fdad293c9f4db61e3efabd7073c1b2fc01df824cb2202d55f002dfee70c19d795008d37c441f60d9502588363ce718c203b1d07b0be7e91da16d75f42b19f1edd0f4551562dfe9135f669e26b246daf7572c9c9b4620c868a853542e2d7774bbf077c923b82de3e6329e03aa0cfb1798b5cbde4d7f3b43102414ffd5a1d69c637e32645d16b66872b148365bdeb3161f86cb922753cdc793a96892c09dfc907c01cc1238f3d5420310256ebff35ad4c07727a542a1ffdb81bda30778cf6bf3fea812ec4da32593aa4b8d9c1943aa85fff72c6835426e59576c8569258b61b024f04aff1f25665f3b01fc19597e8ae07e872b44a72a5929aca08caacc29af399667e49da6e338b25646a68f4d729daf06f723d5df271b6818e61daaa10b0cc2e0a967bf83d3a5992226cac0b425ab923db720e02169b3b6391a5fb16b2934779c0d93d1feafab79d5c067568970fb5eda672aaf2027a1b0596cc4f0f561af06ba39b485cbcb30dd94db7de846cd669bc19722b7067381d1df373d8ebc48258534bd0fd150aef403ed1d277de5f655d4d9372736a782c91f1296cb7cc886aacca643bce53eb871872aa74c40e90cd33c6c2725c0aadc3b75322843a8b35d1c6b2383c426f153672cba16151e6e0a02cd1df087a1421277dcb442a49dbccd6626ae26fa883540d5f2408065c6c154f9b2bce728f65b41a43e432c4bc547b5c81fe025f6b8d7cecb2ac4b60357fcb2d9db6cb724274dd31d43fcbc60989bc6ac4f4c94c4a615505e11c817a0ed3e9f77fcb3b5a424a9c5bb069b54c4876b8645a92a661dd3b235dff3a73e71922ffb7ca2bd472f2d15d6bb072c01cb5eacee2908bb0f875a14d3a1cfadf25748e8a37a325da5153861997549590fb98fec7c32775faafe003b4553a5dd142a34155e7525671202885b5d2250287cdaf572ee368d46a7f176c544348cea984b253eca6bb3d78189db39b96ce2c1a9db3fc9bf697a9e7b01293ce9b33b9b7f27c916a34e56a9f6bae79d373168f33f07fd96b9632dd444b28a3d2412e70cf68c370c237e3a94272b5f517d56ad82ba2e010ad6d663653b4d84c31dada8226310b948710cc6d3472a198545efd1efb7388bb9ef1dc9a981e9f138c88915aaca2dd16561e9bdfbb72750df309b3d23d4b0ba1b5f2a6a4371ba2d558cf41a1edf64ca748d03559c672de8165ddc925b74b5975a52924ad5c57936352d8135c02a24cb65693cbd49d27070183e33d10fa9ebb5856e00e489b69c7b6c99c1a48253b96235b41bef39872ca0d3312349f812b2d6fe98de5bd4efea23dd3ca4ab7e7eef22cac0bdce28b72335f37c7f8e453d4f4f13b66072e0f5db9cdc3d2ef0945355711e0494055f5725b52cd9d4891093ba75d6d3bc98df696baff9283dd36c34038946363562ade0fdc3b150e5caca7cc7ba30e1aa8f657d1f3c476f14d72aebcff0b79ad45d3d5637f4992181e69b12d3e8e5b9633b67b5647fd44877288902e8eb712c886c4bd40df764cc942a9cc7b161176fd1cc98dd032b3187a891b36f42da197a76ae20672033b96ae48a4d754d233d2b6f4e094d7d35af9a530da8c70f69008726c1b3d2211869c91eba9dc09ddd509a6e65e0c1de3ce565f3b693fd22d4926c7d6d3e372a5a4edc636b954777c53f14e08bf40faa3c8f837ab8c90aa825e3a120ab54759ad832e4bc8d560752ba83ba49d9a26dca569f770024034dcc174fc4a50656c72637aebc243913d57907d625054e4e5e03c608699eb08b7135a847f7779c41b72525a14d914985fecf71ce489ce7c7f2803a8537984af73e87155d343dfa07172fc93e164254fbf7853c84ec99370c21f71da0321fcd9d4a9967a8158f5b6c613202f0f0d33c7a3230cc55ee3f26d701b978cff75ebebaec67f7f75e463e42066c821fc642ab6a0aef202b79f66e4944f15453a4ad23c86d4af77a2881e841b2319d7e7cff1de91bcb15350a53f332a3e74221cf2c695cdd576585126a66789727b788e2c43624b1be59cb58642490a42107f9d51421511dd81eb4df49183d77231de2d1b395c31454dba650344d8229afbdbcd606afe3aacbdd812ace788e5727ef32087d266c0a08c27b845c65244c21a38d89bf34dc04d99b377eccec7f3725f3ff29ab1d673562a5709f77ce695e027758494e8631370be38dc29586ec02fcc4e3cfaa7e8ae8e68aa9d574150a17772b461bcea946849703ed041549f2441c232960d3cedb74db587b6038a5158bfe3564fcbf05d33f94a3b01a567fc06725247a3a81a3c59eab684b5d137cda694044fa7f097147551732ea4310b0ab6728dd4d56afb4e8df7da59f43d7dd187efa90452d39bb5b6440795e6e6fc1a2e72229dc496a70875f84dacdf5f6cbffb0ce8027af4ab631bcdf0b546a6afd9e6723f2f12a6e30de51830d9434a6e3b1ca316e1cfef962a3b3d53c7c2d6af44cb724535fb3202e9e4e8649364beaaf694ca3b0862f806f729081e6e81d7e0a94b72e07d09d461b02ef2597ae3943fcc4c088d7b79f2c2b25f0819fccafb5dc11872408aeca8c801d5e52a102fb34d3f872e48162b5a9f6a4ae639af4b06c675f57262d998e118752de8645e0305d37fc96181c189f3b772492b0305a1ff35090c72574c9baec2898a440b40cd825f80472166fa162d72964758e521dc41ef283d72dac13cccd476d52b9f9367bf5a84363a2e07ff285dcd8421dacf371585a3337217670e6dd21a14b92824c004bd76aa5ff61ef804f46186d7b8ac9b1fed913972fe59e1d2d7c79b3f7fbc570079c231bbb938b5b10e11ff733820632851bf9e72bd713a1c501879ca02c0e13fc1dcdc0c5c6c1313f6a1d3d904ad7809a6833f720fd35e7f9ea48098a3463b366bb551cc03966cb519c01254093d07a2b3254372dfa8a429fb67112eaf3f31bad33706184eb77bf020411969e05a313176cbe1722ef04c918795e32059fcf64f552b6604e1c31758cba8dd8bae429a007f615d721ac882cd855217b2ff04e596514eb974b2e9dbbacb5eb81268123201a62ad04d45e0136447f9bd519837e3609e45e1326a9b5321f68a201054a5e82fec71f55dab186d0f8905e1407bbd5ad80e9d21c62f011c7b80aff448306a564ba6fc4f66f2538c937497d495e0742ab8eff7ad713f22eb56ecd97add4ea69132e7d6c54333b5f3fa0f40085b769f6f5b8fa0dd43be1c31477cb33da0691efe17f534d1685cd920ff2abe88d5712a2b042f83a27a5d5926df987e56aabfd9457d64504772a994e1ad321cdf6d146e578cfb9df0862712b674eff725a2398d16f70a2df87285f03a61230c98efada7d8a422cdcc8d5aab6b00951eff4e8bd66266b54eab724e4fd9cfa68506cd6d3d57f5046590ee5c439d8f75ab88c8b0877a7c3a13e405800b6a3d512e1280dafb27307fbd676941b162c69a841c60b8b4f7b73007177280db6babb87e756e80ec326ff99ef84601505f5d25208b5027960be18733ed1ab30ae269cde7a937881b2bdf5fb42706f5eb0f56b1e9b65d259a5cd237bbe272208bda3f0f5109a95f38ca097a5a3fb82d69418ce8e88c38bf330e76209d4e670d05fc2e011333f3c339e6b12d233825cd9a922f92a1906fadee2fe2309e037061523abec5448563a4b661a6b8a29f13c1a90dc7b562d6c9f653d7524522bf7248242a9ae03dd1f91bcdd8908a737dd68ef04058f5f03c25eea0e91b9ed0c77264586aff5858552615e7d443bd0be9af7ef8fc389364059ab1d94e543bb46f72b891a224b6e58781bc2f564b6ae58c882f8e1f14411f09150c4d77174199ad0b301347b348dab71d47c117dcea590a079799baf6b5553735942f60732633da1a07e0b672787b1849fe44359b904e864c3325fe0b2b2c11789cb57aa3d8947f724c19b279ac2ceeaf299c271c87d6346d9fd1adfa6b7007568ae0c7a59670fa7286c9191c182a9eaabb1315226a26dee9bf4805c7be64a98e6d27932b08c22b720ab68c0f9419c6d1c9d535d444708835fdd00d31e9a6a02a6d666403c974f3720f78003b81124817e30602fa22887731f1b3f22ae9f045bfa2b71887ff608272b9c724d51c4bb1730db13c3fa907664d77890fbfb5c3b69537de7ad56bf98f3a3c518d4fb830291e5c99f8a2980e72f968118570c59bfe9759fb59273ba7eb72f2f357d1fc7aebd5586a33e2876a39c8fd3dc1d363c2a414b2ae22004d73d572a781d409dd40f5a9bb2ac1968c3c5dc357573ee9abaad55dfd03d64355d5b464b3cfa2b6b2ca7e59f6e0defe22eaedaf865c5c1a3835c070f291c5c9777fb64b3988539a9f676bf072bc979443a4deadf1ef7a548958fce71308568abf350b72a8a594f057a5c1d016beb47cf424423081eb8374b290e749340d4268577a6972af0f2f1b33099bb3645c96e1dce95c5432aee72c48528e68c70bf810cbb7a272f121627b3a72f38a498385b8e684c0cd969aecf6616ac012d20375472c35be7277dce93d1aaed14be27e7165f14cea0c87ba54be475a0c94ab4c0de0fedbd7724efeb394e6e083bd04ff4caaa5a9ddf17479007b644f0328f00f93718b6cbe55a1d3f90962e6eec242fc9c1472c260b9551150169814cc703cce2c8510a35127d01bd194e9260267ed0a0b39beade63e101bc8986ab93ce83c1b838d72a2d872b4f10c886266f696e0d3ec740f32b631274b24b24b6ed61abef6fcced4ddb672664307d0971709ccebd428012570686cb270411488b4345655c94ccedfca0d72eb07edf3118bffd1f7cc71dcd903ac3b68539ad5f428727653c08d30ca55c9725e442fa2df5723e2e44b4afd80cea7cb8e20a018c26e296aa18bdad679447655d175fffa9d44aecba9dbf74f2acc8347d7e1d3a545a28b680e9133967f74a1725aa0ed2678b5c6dc5c7c3196439ced60608ecaa406f65bf8e9979a524cfb9f72dacdbbf2dce0f625b7a6bfea0e4365efaf87bb83e2cecb719602a95673fb147277f205b1e9777b675fa6adef03fe9ad8777d04c9aa3aa32169e74d75523c515e2522ce1f4246342584e063b98189cfed5651161baf286ddbc611447a2d90a345f4702373f871d58f0d163e390a684bf0dc228d5249fa546618ab0e608b638121ec7bdaaafba4e94c7a1bc230515768f9a2b0365d43c8e0dc247fc35131d80b4d3968d777100307d1ab47ef27658841e85c50c1de9d17a6ff9e886504aacbfa690104757b6084077d54bed6f559d119ccf8acece2fe27c1a01245032babc8d072cf5f15dd68af6237513e937bb6d1a6bd16f5b0c4929ae26a243471318ad9ae2023d8ca2fb1eb7494fd07ac6123f7ceb53a914015bfb45553ceb5df9a9f952f63afd8d551ebac2f5a96973cdb3c0b350b278ceba8dbceec044c093a33fb4e9d7210bd98338a3bd35b7b612437edf0a5cb5f1576b94caa30cc343adaa70cd9303c3785292c5172d920f2226056c6ff53d86fbc43db9ef673d170b02bd64b275b3529c7c0405a8f095eab694b1b365d658b7531674734f15bf190cbdfd36fba6267f76f74e220dfda46b5ca2309be76e5660fa8275eb709f195ff8207db7b74813a253b1eaad9f3f90ac299b77c57d72649cb20b2bccaf9b200b8856a05f90d910d8f6f60637833720e8ce234c8ca68fb9eb3b7e7901352216391fa093acd8bb37244b54844095d5912086b2228a16cf41038139e6d6e2cbf310c1ffd0c61a4b611825f089fe56c01635da1e4fd92a80f04d37158e8c1270abbcea88e06c368427219e9594e9116ba8f896f64e88778ab81c8b52f176b968a5c02a2530b19207b72c3c5f6c2b9b9a02e5e35a3162673aeaa9097d8b96f14533cdfbab308508e8d47120ebe3a05830973f06451e885a17063e99c68060479947b170de255d92f0529b4d5b2fd7f4edd5dce44c7deff1869d0f74fe3c785d0184c86657a316aa6ac7264907ededabe0f18f41e707e002dba74785e05a38e4b7792ba5abf1c24a6c53d72ade2113ac455cc4e52ada6d9351236c93ae40678ca80383a3550139f1ded47b924e8716b3f4928e448acb8d4acb49010abce24ffe0b3859239237cf2050c725dc3a27bb6b14f3dd997bae4d1440dc46e95ac10a82857ac496234174b78bd72dc24753b64b8ae3a958fe186533c04727f74e6f16cf037818a014fc2b5f5f6723dec549cdb97f9cc8f9f531c25a7c6ec962aac96d3029748669faacde980e772a0c29848a57eb04768b050cc0cf2cfd10fea34b25b933770c860d133b42d75726cdf038ed99559ff23e380bfe0a6078385eed2d10d8e371d6db77268194aac495b8d6a44df4a2dcfa6e4c23e4be6b05347daf60fe198af5e7f0efbb788f1b5728726247175114237dcbc683f3996b88c05303d17d4773b456d8c49c3c32530729f908b9e7a2965a45b8ea54b45d6d450414800b6a0e34d4e3d7585acfff80767025c0626f6cb07cf13a803c4bbc231700e95ac928661ebc4ec0a3fee03379172e5d099e6d0a7c4cfa58cca50c720b7ca78e75f21c616de41f5ee41a59f528672a2e3143340579564c4f8677dbbf2b9396f98567dd946841439bc393369b6f10da7a4e269a947f2bbeca4b51e7edba46e861f580522c4d25d163d82ab2bcc707249a2df472b99f787499cc34961792ee0b9a032f49cb05503e9abbeefa5972772b97b171b853f1a19da014effac398b7be2f165f1fb9f2f49d8be967b43e98534ada7170f9f6a0d3501b3486fb481140b26fc623b7a2e88c03eeab9552aefec721af48b274d6b527eb5b04a281b962636729dd9eca596646e7f4a2c1ffc730d72c15e3e70fd937e1c7adbbe6ed057a3be7d159a1ac1810bb942a4d5e315e859726d77d03a1d92876509a3b44e3f3f3ec47abaa8915c0b169a8c7ad505fcf2ba7242330a1ae07629a97b2f77549f9688fe3d20943b180a79168b472a4d133f7624f0dbe7fdeece2535d419d3ed64e31e89ee431d082a34071989c3fa40200e6a729ea0b2ce22281dbae9a6bbe4533e3bd1946da12e4b547b9fa9389f4042baf34a98a0a68689e8eb156348942028f76b82b8d04d5077cb82c3dad95b1388af0150166c1a3fedff6153d6853b2592c90540f5825366e1e161964ee2ead2556f2e729fe008ecbd9c6a2fee204ae453bbdc4a90dc3c9ff9bf75a876b38f3654e992723dcbb4e69dd3cfa8302f566644bca80ee15a0be088df5fa09d68211df048775c0c6b112909a30652421e9e61b0e235c52a7dec6e37e4f9bb84942e00b7be8219a73e1cbef330c78e29e2b33b8b5ead0f19b956342f113aebfad0ee6393e7e635cc55161b32616a946abdc6cec18c9c5af2816908660a48c2551f5fc13b3de06b85bed4b0e5f208f92bbbd01cb60bc72f0739bb803ba7d5b25c8cda1b18a3e51148588b32edcb54f805968860e5e30cd5991f2333efe1c655a5daac6a8080a267b9a4cf8f1fd6738823ed498ae5b2666c93ecb9ce5ce8c8313c6dd31499cdc75f98f66d30f3b0b76a552f728d1d16a856dfb20189452673e4e06eca04f66af0287f952ce6da7cc045a9b47c99ac950c8516e49874b4b8781cd3f4d190e066ec72128d0446661365bb3e7c545e84ae896cbbf18a2db7be3fe18c8d721ff79a99726067e82737f66f308888d30e6317552991be090d16669b78c2255940042b124e6e4343300f5bcfc67226f5beee5084f469de177575d5c1ecf13e496a4c295e6c904f1d9451d5f3aad9fb5ac5e45993881fe9c05df4fc56efd5eab74bdf1cdf48c411fb254306f393d177340774ed4e67c83322561e493bff3d19ebfca6f3e172b985b8966dccc5e464483cf9a2747ed7c02f487202b0526b7054ef7feb0c0472f4945bef346eade10bb3ef880a6b526ea5cb62d22b2e0653785112b99b845b5e99d5d0312054f96c408dd426de2fac5f2e0be1cafe0c53d8bdb67e88957355729020d2ddfe5207e3c2a89765ad23df6feb49d0d1e1725b3ca6b5e5f2f8e53b2552d59dea23b12a408f5b5233c30c97d284aa3736fc14f6be486a596dd013e07217cb0d3fb1df4c90fc414bdd3ca7bf29f5ecd796a571416997aa0122e8520e72d3623c4af5415a3b89d60dc9c943ac353ed4c6886cd35ef632304ca2b230ee72ecb895149c5d9fd8fc3ec46c469623a0bbfa0a9939c9d722c3e2ea45fcdf38722df908d37c68dedb5880a9ba94896b796245d652fbbeec859a2c50cd918ac92a50ce465064a7cecfdcfcd170c8211e430f6adf72c06f188ce64ef2c98a05233e23753628d4205bce94e382e56f2b4e61c29e485e5bfc0ac5ab0b5380bc0d3d72bd5514f5cf58dc7d147c636ab25a65e1892339bba7a88c64ecd0ef1089fee17298faa004f84c96f2e12a1983dffceb06c2561aaabc937c33d7b898fc0f28c37241438d9defa503262862f6df58a96b208a7577aa372eb6d385c8cf3f773c571f8a2608cfb5987de331fc33a5b4e11204498a842a2afc3abe01e3118993f7f672a7cf8c3bbf00b47ef9ada0373e3f0732108e9500f2ba96c4fca2431a316bc666afb104287d4a3a6057dfd0ec7c8f6f702f4ea164acac1501a5e5d668c8373f7294298e97f5dce4c5cfe37df3f12d1650b3e28dea4652c0019c60c29a2a899372d3b73d283731996056fe7979b9319a2a1b22ded7a2aa541edbe5325da8926272622cd9889e972c82fb60f28e3a58e9923aa7cc4a8a3ac3247b70088a074ddd1ebddb6743a08648c850739e13aa17905ad72f145161d9237e4cb1a3dfb13e4d722480927b255349c8ed98ede18b4f8acc331fa0f707c7edfb66f099000a316072d9426b124d895a6c6628aacbf2210e7b7ec4890b18d9c9fe36b7d28d06974912118646379f1f2c17b206e451232f0ba7fd6467526d9987a2f901434785b8e0081ddd1120f7932f1f87963abf83bfc3b0397f047b64271e457e55da5a2d11ef06c8c76dbb59788b6cb08fe895dc25d5842b4ed1bfdec6f161916b96ce28ff6b1675ab770e46b46da596b7ca33fe516cffe35f760e14d8fb98ac11c50a22ce1a003dbec953cbd4ae316ff81b0f914973933a245f1c447d9b6643ab04528b231972adafd254928db8e65f4879fecb0a424073325bc8481dc043c2288e00df118c72c1261983812f1d4ca0fe52543497225cf294d3538e920bbda68ec88a6338b7721eebbc2809a35ea8de3a31ad8a6521be2b5d02d2155de3bb8c55897716d34872550d78a7e3d6ba195102839257a3e770b9edfb7b1558d027507e9df6f9c34072273eabdae3762692ee56a9ec0aa9f65f6342c115eaa2bca8bf7a0326e6bb1c72eb71483fc960bbc571d874f7eacd803c4eb2cd4fd12105b987c990df36323c72a936f28de5fa4656c1cb0db6ee8ce72e104080a73f2e26cab56583478a6a660119c17a480c16a0c4c61614f57195176fd74b8e2388a1699e5bf28e72ac31b9728c63c937b125e8fbff28e42bd83a85766a22e77b568451692f9b152a6186f426a34b25049e4e26d65f688f0d6ddfdd75ed1aa31559fbc3a7aee6cb816cf91e72163ae53110b5575c28a3d088a5b59dc7b4844cddd4461cd233f358d1747972003e3e5e6fe53f77ccd596d0834851e360a2f7c00745c23d0ad944af52ddf54572418f3f76d5fa660a5b45381d245939b47532e1b112aaaa2edad1f16f06c9d872b94f52b20245facb056b88b60886287b0fa0a29a4c3cf11db503f2d071e34972183b090ed1b2a3604eae01550da368d961ba36e28d4d8d73563b126f2bfb9a72add9d722995c5048714a796c9e40a86a04c629edc81b95b248ac067f072a30726f5a354aca3a4a6208b50dda0a1617ff0289f15bfc5c28d0e70abf0f6e637772440b3f2cb1675b035c1d98db0a6f0ffca26d7501869ad27dca0bba0bfa04dd686c70ed74c1f73e6e40f08f73555dbe840866a22facf792f4d30e24a44e7c2e5b0f8740e93ad6fd7377e844cefd522a0ff3e601f1ffd32e586cf78a40811ff37266f895c65d8dfd6bab430b7520e968dbff20bb5d0e5fd3917e441bd574e8113ce2f8768272f635dcd97813f3e6df271f0659cef732fa177129ecd50e3ddd4772c20cd89767ca5e211563598b16210ede6bda2c9468a4751f917d8015ba2a6272a7d8e9abd933a6f28d74d9fe9f1abe67c0f766e77e6ee8630fc231a0a0a4e472775b89e37bc909c9219fe1abfbbf5a2c9ef320733831600ce04f32a52b10cd721e51cb0b3da90d47f53853135c7d83536fe858173e3a8141ed2320e8884b7e3dbfc98c5c75f3f5f31a088e683e5ba583c9610e76ad0d61056e67dec0d3a1573a4fb3fcb3e217763164616f6189bbb4f7bb6de7e8cb35ad69849089bbbea22c6d13067b8e5fde795b37fa8fcd97955bdb1a09c7954d4ead67fa4e27b566be4872673cccffb182ecb60e24127d9d8581533b04f49740ef6d8b5fae758159990f72433766f4f024b6aa4b892eafad4f980fe8a911487bb79197ac90f1ea31319072dd43c7e35d6dfa2147258cd6907ef34c79e3db214c5dff3ff11bdb2dfe17f03556c9aeae4506338ab157a6763dc039d3f15f194aafaca18893052f1a6e26dd722df17ae1b466c8c3d1214ab3e70df2e9cf5aefe1a7526fbcf5a1a5e79a422172906752a06d36020248b7261342938c467ffbb439c13b862203ade4f0aa825772adaa3109324353983a31b164bb96e77174d6f21818b8ad596acd163542a647066c51908099ae1221540f19293868e7d6c2e6115ca2d4ad875c3f8c9ba5642767ba784bc14a5b30ebb8ba6296c9c88e16074e2b8fd814597eeded84e5902caf11f0d1860a072be7773ac79c7b9dfd491bafac1af18f424200602af9063296fa4cd7ad9e282048c4678f237967b96e6bf9804c371bc7667be3876ad83a83df72659aa61ea932fb0c3f12ffdc821b1a01a91682bbe62237afe70c0061c1aa889b724971ee531d24d4fbde8f4c794dea2dc3e0695d627563a26a1e224d0cd0092572f47c697733b0b10f2328d9acef1ba36bf91f0b7c6295bd1c8a54be0dcad44d72981560b8a8e605510d0ec6e9258b12be65e4b870c1d072fc075ba9fa666dbc72876006c6410ce547a597027968ded45f57e02bae82f012e4cb40e0a0c3b87e6733f68dc23a627147e163a447a2da7d13e7d0cfcf60a57740507938311ea501728cd1c52053de2ef6fa3d8b1287735acb1e653042862f8a17960d24ba101e7b729bf98a87bc67b089106ef34f3fd418a21a39419eaf42c619c920c7995446ff72af031645f707a21ce6c1278ad18ac3c3ea1f1a541c0f3d2aca942050512ab3724bd31b66a866d092ce34683bade3bb4138ef5f8bac293e43d59650da8e43dc56d27322c747ab8bad27e8748512fb4736a6e1664d1d9b9de5aa82465fa740c1431f45cfb3c7e238913065e76048e7cfcd1eb2574822bdb51e7d7f0826c359a37216009c62341b106ce67d0d959395dd7014ddd218c9fad5e0491c2d45aef6981367a8ac076ad30dd38d8f39ea52c1ca4e8bfc161d68fb09d5cf127e5f3f9b5872ba7fa495e76b9c573d5cb86fbc6b60f51368e6d44691072fd4f3900382fb2c7224e8080291f224bd3711988b13350af08c6a6d7b040b312b07563ca99a951e72f136f6ae1abc9e37a148d781edeeb7544586c48d6a76bc1729879dccd2e9610725495cbd65aac2b99f3d01f3e8b81ac3ec295d2f482d4ba73dbd2dfb458096192e84b7262ec6b25e459ea806637ff15fb6c6d022f759c6f3011827b7559907219bdf8e32c26b95bd31082032e43befd7e3642d07f07e3c28eaa4ca621153370fd915696de810f1d840a5e800a9b6cb291b830282608fd1fb25f455f48ed105724988696ca7ea84398eab57b39c7d35de605d584cdf99cd5bc0145d534bc3a452bb2a8038c2e37832903d6f312916ff33168edb75a4e4139f8be0685b3b764b72405faf20682e35c31053cebf9bb0c7c2bda47cfc644eda96745ea2a473378b7234203631f3b6defa6d3b5bbe8a5fe8637ceb1299a3e057ac9c09bf373b6e66729bd13b863219069e06f15e3082823bea89ba328d66b12665598825f28073360497eec5d4c189fde6d38944008c91505de05612769cab12b9ed1ad9986e52264fa9093d766c0257db4f2be37868af87f7526ff1dfc408afc2083542ba43ffa3725530439160bded4c6b48e9ce614aaa31ff0f3dd7f274dbf754e2c2ba3c4ece521c578091ce0c48a0a515b276cb7001481257bed97d989b215985acc718ebc922f8873463575eb42cae46249cd24aff0cca7270a04e6fd72a3cc677daa83f1f72cd8b270f1f5358076e0cdbdfa2a9bf27fd06f167503afd2624280483f7eb7072f999be6188236153d42e144a806d14c6cc9038ae2e6bad7712a28ae7126af6722743d8a1598651684c5be8d4a622230923ede698a13b8a7398085db8881ac0650805631dff8c08fde2b66ab60bf12c82b870a21632c37307e7b77cca7f1d5372a7bbc9a2e29c9244244cfa2b78808f1a3f27140e4a754f8e6f2d02cee07ca33c5a84578892114681542d950fec9a8c1b15b0041e16850fc956123da6a62cad72bf569c11311f4266612884cf750370c7aa696c308e5feb93b30327d5bd58ab72b8f7f326ac58ff746db0893bc5335a6580e65042017a6dbe154e28f23d65e74d2bbec9de0dbef2a01ab47f59fabc14bd649758f86867cd069bf4973f7c9c05228b75bc435d00d1398551778096d9e8fbf606f6b706de85b504c575545f79c7724ba0cb9def5119bc0a693eb9cb9f5eaf3f4e15033bd55acf1bb60532696585725cbfcb4dad9834e3b70d898840bb5910abd09bf65b4a4d5117b4131c9631ff49f8f7476771d782266f91ed9588b6c3f5743dc01943a7d581b01a537c1bdd8601cd96da618bb4a429fe7e47fd9db7b21ca55278e6004ad0b3cd0669abcc025710c8da7521214277a87c136b4b2e0141bb5043d5e47226466fea2b2bbcab6307205f88b6d1ed9bc10dd0b25b7ab6f758e8796d49d65bd81add85f40684e6dc99720fd207a27cad6f2b76c42b27c1ad3d3cb866bed6fb4122c5baefc7e2f928097208547236857ffb6e7f40b3927c56738302d8ce406924f1d6331100a25d20f23a3a3db5d6197558cefe95857b8f380ae500e1c0d74abdfe14f534767635be7d7245c14d700f79f8e974761841e191457bb03c639bae4aa7f6d83f415c3b8b587207ee2f06ce2b91295c6361433eb00f00fbf7b34747d64f8ca8ec18dd4c435a7299ae511451634ef6525565cc9a7bb646fdb1cccaa79062c9cb895154b563f361ed90e52689bbd3e8824f16a5b0e8456d317e306fbbad87f3d3c90436de415f7227a0a62391864150e292b6f80a43531a75009245c6c6510156b838622fe7f3723bb1c17b4f1bbdb97ab0b31dcbb2e88793aba954cf0622cbdc868a37fd88764d721fbfb178d8d6d957d32e2584b0edcb6bd05608fb9d36173977b5192b6b207212820be2b9f1bb9bdab2caa43df3a9a52b51adefd1a11cfb8359f77bb867145b8a2b92a98eaf66fd059353cbf2422c1e361a98caca35f898cf0b98b1cedc3372048aba530bce6c6c22df937b52e2d7cc41bd99644b89671c4fffb7ad52af117259f5df1b7c2ce3feaa175892767cc0c361985c977ec5ff242b7edeca1eb0875246ae0b9b2148f5bc52dab03508e5a46a10d49d6b0f8a673ecb2a18593c359e62cf8e96a51d1b31bc8b6c70678c42b98848bebef4b4638570b867e8f08336030afd5485bd7536b2a0c6471e6bde283e754dd2f8cef95f7e548eb29c7fb2e1ad25037300790cbf0838c9dcb347b2480f52adf49284c39fddb834f7207e0f0f3a72c67ff9891e9980263ae5d8e5c0e107feb79c84edc0cae593311a15bab93da67245dac0fe7c9c7548a0c452a63d21bdb4a7f1dab5598dca45872563079c6d513df86baec573d10a4fb8de9afe1594f947c1c919acdd12051ba22e46937c711a72de4fd9ce4b3f14cae82276b9be3efe818f53aa6c311d0011b4c22bf90cf6367228671cde0267a672a6237a291adba3d97aa5b3b2e0d17036537f47580c86c672c54806613d5c67ef953c4396c8884b4e043041e923af3e468c10deb551d2a272d40049f838b33a41a282cd6cfc8e58dd65dcee2e1a8ac9f8e3ad5ebe2f6a9472be4680b724eea1eaf99400a1ae7d1a719253f512ac227ce207f563e927756705abf39ac88164a702204c205ffd39da853619d95bc96f1d3cf6c73135a81a0e720b7484108228cbb441cb7d453b896ff4cec3c4bbd06fe898132fc2eebfec7b72df4ed10faa37b79c72324b9ee2c98568973360e65cbefa31371d18326b8bd972b033c0231117105e10a227fd1f1c6e2913a6b95a5a62d9cb2517a973d1836772f131035fb2e6a60390460c11c7fcb998af41fc6ec0d5d5dde59701b7d0cb4f1dba8a07767a2109a0d612f1552063988b484cb0ab69036bd8c31da2cfd7de9d72f97cb62e86f207cc08817a8ecbf77f47ef7802e20f9c06c16adbee5b45c8041ddc4f92c4c2501922f1b9746b7ddab1ea973b95820821c0b8fdf90e4edf7023723ff8a82cd32191b2641364a950c36483a68d628d0bbcd8c7930e5c909cb92938f736a803cbbee8d33297e48438f63650f1285651969506ca51f5ab99f1e3f43c6a2f9eda8362f331deca56d331412eedd5e237f670a6e84cfdf8bb2694b3b94b2d02592e9d3ad9ce06a5e882fd7d2c6cf96a99898614e9fb9d1af99fa45ad3274f2a850d02042587dc2957ab727a62455cc01eb36d0d5a819e361154471a5972a242fa33a9b5d5e2ffddc93c68dc411d3727ad7e7705177f483cd7affdb8b516c526ba94ca8f5d3ad9d9ab8856850819e7ac70bd6abaf53154647bd7e65c6072cf71d9081842d303b450c4819de866013de042f1845eac1fc1430670515c181006e70079c6c7a91e336655cbdbfdd8941152be624f28b01fb85bd2feb4545c07a54b77484778a310f0aff83fabe82fa1779270b922f46e7f49b2f1100fe4fa7281126e5ef4b0c8af45a3681f32bd871ecdc79500255690a1c58443ca62a25301056099ffd5f6cefdff3dbcf0fd7d89d0919eec44ce41003c1aa7b5096d01b97226402d166d33791fd71da976c7d36cad735dc90233835d8e6d632214483d7d72fde328248e7a9473659fb5ba65c0bd32a075531d772484752f1851726d20965b55b9151ece6f7a612b578494880dcb5c111e8fbf5df14452b0fa3968b869f172bc160b407fa290a74974a21c15c701714ef35420fe7c9210f01433200be3dcba02000022a264ee63bc826a6df778800a62ca8f7033d50f14c7c738ece23b505f2bf3c47216522431de3aa53b3b4f75facf55b2e38973dea739f3793437d562abfeee8e72d95d412fa040972251c9173b8f9cb989484bfd4539f1136c36fbc69e40e1364a2b3a8770ba7dd170a785fbc7f726881e2b8437f8cb3b7ce2031cbdfea58db551d342e0b23568583579390d951d9fd59b8b915f4286dd48c02e2e01a62d09f9076b4107d6934edae4b3d43d1772750a6cb946de5f2f4cf030dd0b2c63b045650751ef6ba17389d7f4852f1eb73a23ce8577fc9bea3123a96d4adb4d471a363e720a45e655b8b4d2e51396300a3ebd4d2a7caa564bd48ed2d17eeed34c1024d772c7fdf96b6222925ea4e7b7b99caf4234582c4164fedfa4f2582bf272e8233f4a47c0f96f56f69477a17e0c429bcb77cd04b3b0bd113dc38a194af3f613499e01b93940ab9be8d606999476794451e794b8b975290b3bc8cbdeca0cf070aaf2343e72672669ef5dbf9823e96f28fbcb6f58c0882e3766d604fb033a76e6194d0b3154e6923e14fb768002b25d99ae798450a5729a0a8a86c962f2f9fe75539872bf5053ceb173bf73953e0a36af02eeb69dbd64326cbb2e7ca3b1727a24ad1f72efeb3e52a31efd736f6ac40134b14b5703df1c3032be0f99e94c7f5869541672956b5897cb6224bac162b69d9624166f2a591d43f04e29c6fbeb29c7aadf72723aa1d6abbcfa29dcaa93d1942e2cfc09a1899ed36327b46c14110983f40ebf72985b2287ad5e45cb3f6860ed8532ff4dff200d4505bff575cd956bd25459040decac0cbc03fe0bb1c97a77076a347c5c83db7f6dae54e2123a7e508c7797577267d9ffa456f70c9feec70cbbf5892b9ffe58fc75d8728e094bfedc63f982ce4a5ed278f7a94e31f7dea891063d5e4e78f6223a37d82c26d67985cab33c7c5372183a42476d3ee10614ce6b15810237c70776d9e1cc538ad638725fa53916c472a4f6582abf51baa64d5dde322505a25a73db4f8a232118343f09b57aca02b70e2ca736a4015da20bc7c008f94567ffaf28b9bca10ee5283cc70a774e73bc157216680a7310e5492620164132f440b8827f0a45aeeda5b98ae56e6b758b004f30993c3b12c43c54855676b25f12424f04c1bd058f2ea0a53aba3f3a4da63d00724d8b5cc5b7671beb33509bc75dbf7428caaeff9161b8390660d1218e91c4c321832cb9894bfe0c3e453685374ae6d86025088475cb055a71b3a62f02b954967252014f7c7574472f627c133315e86d1510c4d3180c8f14535060a00cba830072eaae567ff51fc3ac2e60903eaef88be1f70d5ddda9028866d888913494b98772067ce7c9c58ad2c1d26e5c7289272eca7f4d707d9cf216b9ad0263cd6a0610481a1e0640ade4cb065556e3e8244a96fb31fa04a9f5300a4a21402150eafea5721ea629b435fd1e51d9ec6041ec4557e7894118e84e92878b696a6891be105672888e980d0f1e7cabfc54f02fc285f9d68b02a4fa6fc79a6b8b5f8c1196049c6c59c84c5f1f5a15d945c9cf1709ef7da1e18f166985b7f3a7b531be2f81fa4e726752c56ff6b6be5b1138cd561050acf28aecb6968fdfc5335eaabd200fae8172032ee141e2cf3913dc6c4f3a320538b02fdb34c54df4b9dff378394ce7d452726b54ef092365e57377a20f400b2188809451cd419a73d82f6d7442fd928358723920558d9a7235fe1d742428ee365706385d525b5636367b249f491bb735ef34a54074b362641d364f0d619874ee2e72e917bb6545398b06c07b18452fb4546240133be4c2261d562a7bf279927498e07cd82d4e9e194ba27b59b4be6e038072f0d63ae2e835d59e5438a9239adf7e70a18667e29b03f023bc7b13ff9ed6353d71842d3d4592dcdf9608dadc664c6563b69c0684944a6106dc83b1c975424d363c1d011d221649cbc13b34ba2198fd656301893e6a52c3882d1e966c0be4f7722286eb98732bf9cbd3d0c161c7ad2c197ef0960b56e6eb3b059552d9a94dc7729fcb265df46353aea57b466af5932b4707ba4eed0276e8665155845bcda39772bbf56c6ff563092edc1b8067aaaad85ae4b62765344d4c0c9f88a8d9a6346f729651849b18a31bf50a233721260d8d65c19590b7c2fca0fc0b0471332c78b7726d066e9ce2d03694baee3b689f6dc4a59e5de1d4d75a1f486bc6a4079bc0ee72d1f7504c305e596e0275b3cae367dcf4414d075b936ce967aca1ed4e7726403d602661f9596baca9526263d4d374659959696893cec80d5e0fe4599a9c1b2372ddc399ca0b3897f5094d5058083768f70fb4db6aed09d27ef4b4503914e02272f2746ed1e5f4ce28b1f84e5e1c55dee6ea63273b40eedf5cc7c5229bfd21ba4b059daa99135fcb5c3f3f70da013d803a8eba94d2091d748777a20771dd5f565322e6bdd96c305cf8a10802821e0ef138ab18d72193c20064908abb990c726b7216402b7c51668544043016ca76173af122cd11a15a7d24e259799ff149230972e241789cb3449e395a2f22b07f05b15058e320d21fd86d10fcfcf7093ab49c4b76d06445fd16d563429aa3da53e79db276fef3bd2ff41425cedd9b1ecb1a1670a96b06edec499be555e8b89262a7128c81ab7fe64e944ac12422301909b8e772f6bef6f381285778af92a54d0ed350b31008a45e23753b25c660cca18a55d6684114c72538b2d133a025b10e4ca4eca4f79db682f3e0456666dd7b16bac630725743016112062c164d1168010ab9f92c58747d958c4afae5c98157a5826d937238e983dc5944f578c86b258c5b758d243f8b668f2dbd0d68bf2006bc5f2ffe0520e74a23d42208d7c7ad535830216f26e85483bf1002c0045185dcac552c0872f0bc46c9351f531b32058139b1b00963209c82be0fcf7c12c683982644f76e7231c57d350def1cac810439611967d39ed9a943859457e41c43d5b9b611995b21a20b394503fa7f283923b1497dd23dd2a7943b4adcfa25942cbc19f994f3667264f04fe14f4d16b24410665e747c14fa5bcf5ffc73b3fb2a3c30f1bd39842761225787fb171ea65de8c8110e1e5a4b6a58073fc7235f3b42c0a0dfaaaf061c72457a02c263622dcd543b948fc92a0c99bc0163a57b757ea82550114ac0e2b25793eb2860d0ca0bfbd823fec687b051a2f08979c8dea076efb6930e0ab01ec372c931ed738a79dbabeea4d09d59357624ce6a4770de7526aa3898181286b96e3d4ae6e3d16176a6a0d0cc7db06ae75f98a471835eec8a521f3a74c9535c0601296e8978e6dad4abf7a1750e7a4c1c49c1979d2fad89cc29cf8a51bbd9ccae7e6003abf67bd340e9e94871c8b11df95b08b43c1b84816b4aa616f4a12a34c53d0118ac51bd8fc059ac6763b171ff3cae5b997264c4f7b0599b17f07671d4928d72dc5cfc9d8abcd479048d1e0ef77dd4b5280f1bac6b06b88461d8fd82930b961163a3eecbcc3b721712ddd4f918c4c38f89945862b7a4fed4111818faa14cae24b82c4f718f757fc9d5c4322de0f4b4449eb903ce16291bd1bb13c727a0712b6d85d4aea172b98f952b8602c43f2a55f214180b7806e002dd141f7a6cf8aa78728c7483ccf76951caeb35211ce751a6500fc945b9532a749f8d0b7c6fa53a2c7245b4fbc478fd225ff5dbe1a539253096892c6fe292d9b947e387c0d55c12de7291077900de7b151d6524bd23d0dd66e372ee809dd305dde900c765c68e7b6553cf5866f9f091d4599c03f4dcb416e4669a4cc2459d143a003826d92015116451b5d9b685f054370f2167d65051f61fd794c6eaf0ffa3c32b19417a256fbb306cbcc317e058ff0abd56b8cba44e13c0004c2b909fe40ddb401b769242842c3e405a0d5420f3c394fd9ba19aeed3d86d62f5bdc2e816dc3e33b749db38c4799a723381625f989531afbad34818e0cd1e6daa6d8644a8c138eb8027bc0b1cf84472b6bbac4113eeefaf2dab927ff972c70472e0a39ff149a8b121c18b446d8e5f397e82c1b79b081ae4e8c09601fb5815888a7f60946afa33e7c34c676a4db41b72ca85ee7abb87b37fda86d6bdddf894e1310a90906223ff5b1d3717822a977972bcd3ae80fcc974833e8ed65003988b75b2ca996702b6f0357099305689af303ca13c80dac9af3a94a5809e2cd4ac63dfce52bf307ad27566d02e191c6d98a0721ceef18f1df435505e6238dc2cb8bc1a0b5d51d2cfd25826d4cf24a620ab70723afbe0fe2d5b5efc54997c6f6692cbe42dcee9f77764e8015b558a77dac4ce4afafcfe9f3ad233659971e1d0f8d2dcc70e7a99e140a04151548ec4d4c56ebe72496fb005981c7f628dd70c57dd2eea88b35d80d8393fe0ea27125fdfcda78c722cbc4130666f1d2091e36536313fd8aca8207056f523a4d8f7a308e82353f772b400c4065d0fb024ee8098b286fc8a17b949435d15028ca987ff7189aa6f9a7224fef27f3a76af4f7ff3ff7a1e2cec2994b863ec4438ecdacb65d775b830e31517a1ef03d931380d4ee3b0048933241a2fdd97b28cd471fd9940e2ce51bca972254e1a8ce5f02bb29cefa87227474e6e20d07afb685318ffae56a87cee72172d71fc4e966a44f215576b7ce3099f4f06057803c2eda04fcb5b3ff7c9966f2a1590dfd5ef9b31f954ac0400d7ebdcf234e84949989edcb65df576b54f98672b06cd8db600d3d28f6459c31fa6fdfecc1cbc49e4dfc828df7092221098be52ac50a42254a021d2f48e3291e6340ef4eea8d0a5be9ba6f0a086e3c2e5c42663c51d0d5214d893b8eb9fbeb12985ec88dc827ff7340416351863747493754663e6509f5eda7c936ef52a8536a4ba60f056fb0ee37666b442a49e026ead76a8810d7234205e952a82958bce30c0835b500d77abf23533e7f8c9fa661ae8caf2b9a2577e17f4b397064cf67145b836ab2a018763b9234261684fdcd3eb9de410d5de56dcdf690cddb2cf74af84e38fde786bec458c20742ef0980bf7e6fafa8f952664141f3d4de987c6f47e4df7f466726e965742e8bbb515b517cb5a66052ca1c372316a4ce544aef4df7c15e3bd2cde024023b95f173042b027269a11df404d8f72edfd898fbc710e24c18df262fdfaa9c05217410aa0e0c619d6ad3b6fa7864072c5c9f75da14bb098afe57ff635180ee448e600958cde4c2311478be030a1e07217f394cd98e0c1e68a706f6ee6eb0dd6d3658a1ec75af405c216d787d62eb321d29c8248cf09476e852e47fd37a76156d31bd3fad315629e4978783ebf30da2e0156fd3de5d0f8bd45032f5fad79efbd873bf0a93f25a373bbd922b87070ed5840e9aaebc4bd7f30847da788bf7d70bfdc980eb5f37a188da2f7322979f0c043314223113052e2091d117043824719ceec0fb66e34c812a5b1de305a8c7a4372d4efef425430bf777ceef76b6791cb1ba6544434f3080e9a0ce94f8c31571a15c588c78071f9b412aadb2013624c609e9ae98598d61169a826de78d92850df7245e7aeaebc6e7222b51f21c9a01c1bfce54db52b5e684002a0f05ac48a567a6a87832cb423b5d4215469a358ab1971ff54d0a32b3abf914fbdb06eef2192095076f541120be9ccfb20d784bf248d52c3c0635f3401bc9014ac18789f609f715a95cd344715feaab4f8642803aa87a07e6e7652905f83f9c70c4a682d0413977239a136afd1675fc81110bce6afb5e5254e374f0ea416e529cd880b9fe33ec9724298552c73d4d44441f17b4cf0ba9d18c19b5fc28687ad477d7155c66cf91c72b4440a426de4779c1db76ff94f0992d097e7114a9af8cfd9eb2ba63f3b7e6f325b56767ef3996d81fb063ed904ac05aace11cfcb722e065ac8c4bd02198d5d72e21703016ed90a088a72510543216b268621236e51b1b3dab5c77f253f4fa8722b753b0fd2393146c4a5a651ab07b0fbce1e70f0609c2501366ceab22733e57288ccd0547be6d21252aaccb781daf8f4b13580f32ae9a88fb152745b481e8972284d154c74a6af0ed6c8deada38c411280e8be85a718664470976b7c813130728121eb768eae890bdabc5cfdd2790183fa46d1907513bf2c5c3a0f63c5ffd24b3507b5c11503d7c7c5b0700a485391712336866f62044a6bd9b467874f19d509288b915c40a2bc46a64bc9a779b05974a6ef8747753f65f313aeeb1013f98c38f7d55220a1efa877022b561ea7b08903cd037703768be0f1bd144bc5c6dafc431ec272ac1758e56891538403505613c1fa64b309394870697df2316ac3263b45ad6a6c9b5df41e9599ed2c999622fb2773ce4057342c938c848542fed5620072adbd584e4f7a49addf81e0606c8452eac1e51014fb7eb68f2faedf8e8ddfdf727e729fcb7c98f71d0c3dff82f61ae741380e7bfc90cefcd2dafd854f590db63669dace8c3c23d70036a52c6d719734dbbbf8831c69aa342d14a738f3d09ceb515d6d776760bbe045a7d85238e466c802740dccf5dadfbcae24f0f3e12215c11f8eb7f8f4ab91916984f72713dc871b8e9cf9502878031299fd2cc4aac29667577cfc1a611c2ae5fe9b4a322a9d45bd9586afe83546a0d03b557ef5c0413a4a460741d07790c8ac5a8900769dd3552a527c830bb8f4c86a34f2802deb7a858072a5faad858c298a50f78e352630c515fb13cefe3a7f5a4b13d07d8e938c74ca72191363ee5af8fc762524817c2906849a37c215f8e315778b50c99ceeda84dc42ca939e68753d09eaecbde28283317a8c1c5354ef5a8658431c221b46725a9672c345abd9e71477420ddba22dc1fd3fa015fb0bb5f2b853ac40ec9b34cd9266727e6260538d037086abe010a59f459760925574dcee6156c868feb3ac4ce3d643c90e289ba58f30ce81a10c6d9fde25115ab217bd681760507d208b67bb52b6068bb5915d58d0d919b8ab025757f61669ca478eaf9538e810072effe0c0542d722c38621fa7c80e5c2190ee1d7082d9fa82d4d513606bad47e8eca3d5672895724ac4f8e6b83424f671775faa2e66b40e87cca53d98b665468717ec22e4a7747255d208a9337aa06cb1370419a39f4c5b84edcdadba6004ad19dc6c17883214720d0ff086e8b08d244ab4120f8459ba1d63ebf59887c66a0ce6fd8e314963b972cc04e4b1356b08c55ce6a0ed051b5d3af243018b16d3e955d3c77bb7dfa58672869fcd46feec12cd4b586cd77ffd799d38531c3389052182e8720fcb5d027e1f062bddb39202886774f36d21cad6e53bc0fe2321e413aab8579e6ec76fb4d772ac6e4c0ee05ba7c15dd8607f9031b555e9fbab540c7f287b579c47e37fcefa544c069cb9d1321dda82383d2664e5ed41c4dfd75b50515dc42f4b0e6dcc70f26863deb524af953c15488a7886ab4e19634f48402c4e466b88d7c483cddc11f133dfd52899ddc128d03c0798ac555cdcfeec7141fe5cfa8da4b12d91779196e872b06dbcf289be3b51de9bd618c0add83b666b9f321d1dd9b768d8c0fa9f935f72b213403993431083dc527e49719fdc44985c3a9372d896cf01bcd721fe7feb72cf662aea66acde12414e39c4192a2c11ce8fb555b6c27054d3f6eb1fa87e86118ef3c84b798128cd74643ae72730ae76d866d2f8b7a1463c5dc6cbeb0742b772ce3bb66f961656f4874f0635fb2260b41f2fff53385135bf8bca667f2bd68772b54d3b0f8487b8507b87514b58510827c2b2de8971aaa83f6fc0f6d5cd0f9f72c16b8d228227e1e697f36b9043c500f939a4e0914162f8b60505ac8bb431f3725dc638afad758dd6e9b728ed5e372aea3309bb0e9f6f4d2e64a68d1e1a84e40c04a487faa7f58e18070240e483ab7c31e4d2cf7ebacafa296ff966c0f46211688fc6ed1b0f755c049da536623c8b925a758b559071263bdbb551ae3a53577725460aff28eb2f465656d889ae06c9953561abf53bea1f38e3dc41d5c3e6055b7025e9d0511d6f7b1d99dd6411ae928f6e346b0374d41655d5012032e4aabb2b72852ff01a92fe71bd8fe91ec8ee105af981cc74b242357ec09f47053044d0c772c6eca91ef094cbb449840de218183fc6a68f00dcdfb3e06d1b7882212ee5e41e5aee8aea023274738c335867ef0f2e49d50d4f98f9215254e44f726f2334b772af36486e809b10a1f71a6fc506b16f9eff4b40372410c1b818b96b658241ca148810c9185027fed601903684d3bcc3055b4629884d71cfbd0a165371bc530846972bb4ebf372fbed359e2ac36d6ed8bbc0d4628d6f8afdcaf38516871f234d72b3a517b9aace5e61ee0f90374a4f65627803d8d63a1293823b4587b5d33c6b720a0e14179a5fb38393d0a9575b82bffcfd2c9156440e28328dd124ad46323f2a832eee5d37a5191000e758d7d9867558d07b68579ad58f5d1b850bbfcf3fdf54331827f3fa5416e314e7ec09f6ff7442e76e54b3e0efd5a281f6fa2a22d86b13e7f9c91cb015ebf02c11cafb78fd6f2614f54d34d3393882a87be7bcc58fde72582eef96dbd629731924d72bdbbc5566dbbdfabb5f32e12a5252eec2ff0a49576e947c34eaf9c9dd316864504943cf52dd1d72f7282130d915427fb6deea2f72ca60b47519bf054ca5c005aa3cb40a204396923eb826aa5200206dcd32b3b67235501602dcea29a0daba5688fc2130884a13c2b54de5cc35b5032301a9d4014ea82157ceebe077631dd970386d6c551264d35fac186e044a17dac0bf2d3b0d72d07a4c8368b1f7f01150362f8ddcf855ea8943d5558c1296e167575916f55572dbc82908bfe8b1d2df0107deec38aae7fa2c07fc7b919f419352335af4e1bb7289e84825c995a353d1d1db01576c792923362fab3482a06d9c663d9cb985d84c1b14224ac70707a4d3e6ce3d9fb5076f7482ad856c79f6f47a2c4aafd84bb722e891debd98885c1a40b698aadc3203b411ed1d2248fc63b6f0afcf6c2ae5fc355908fe44e618208039ad08415dd1a63ce278efa6209c857e5c984d816cc4be729969b262913a397d80c801a96f74313f8492384d1617e153301a6396ad473872c9739f89e1b583705a8f1a58f12b06de849fbbdff172f9e11f2df8b4996c245c175e0a6aad9eeb5daa79d623f342344bca11413867bbcfac4f997c9bef4153724539a080d081e252d47d138b560c8d9d6111c23ce52778aa5a5142570bf3c972d8b471509372638b694ceca18908050d4dd171c65d1187d187452303a49a3170d75623fd96461a94e4119b77184053867dc02db7e10fab02523053dd33fa8572413e42fc0a0280f91f7d9a91e7c777bd1c8739e8221395a2c74372575185e2721716acff90396809501b57f248f19b375a650427993e2c61171b348893b6a3727464c3f114ddad97a73995733b9b7e62093a43579bfc6744e166f8fe6b37495e4ee4a928a7af48bec81c689f3fdbcdf3e57f70de34b09f3e25e17e7c3db8fe0c62d814dadf8ce2af8df7efc74ff1ed8269479420f8a23ffd96d0fe759fba701d57fba1fab6ce1cdc3b0a109088f01e62d2c7834188cc41d0af488d429dc78021691c4ee974f5d32b731d53f701c564e97fe70e7c9ca6b5059f0e16520b0ff12895f436fbf0b4e642ede26469031935dc7231db3e171713049374d86d28363b72dbc6a294eb25b5a2e880a7363f854e96a7d55166a1b48df1a987559975f8a7727b8d9d89fe900a14887e5b0c0ca70cccbb534f357cc216f1c3a45a886bd77b7282d1e2083be55cfc5115553d8f445992429635e322d1047377a1c34a508d903fca0aed15cca28a5918c03dd2f51d8e2fac23a8cf8ca1326aa41f4176a2cdab72771efc8bf22e3605378635ed566b931515c161c484bbcadd78dc4f1f7244da6fd2b541c6aece41c889278af7cd33e08f9f47bfa2b38927a5cd6d198346ff56729f2123faa2955e09eb21f9a506ace862cecbb9af6ec8454ecf1c9523bd94f16fdecba6091a47865569bfb365643f94c66def38d1adbb31adb23fa2995bb6347284f0dafc02d1fe065dd467f9705576a80670b7a5fdbc3256e048a228767b800fb0490f2ab6da072b6a331e4ed8c11a0ca422293573496911f0ec120d94c2b24799859fafa6b4bbb3eda4c88e188438a4ac1da66ed141bc9a6a41353d8eec1d20e18edd5cd4dba9032f4f1e00a70ccd901efe3aae30231af3bf0edc6da1509d68d6923b51fab657738a32ae884ab955ee9a4023917ec3802868e8932bd6f32f721d641a31b30b1c037109b715d9308a316a7fe21af8aed075504d7f4bbc9a54167a6e8ef899efe2b17b87df76374c5a0d698ce64154d7a1065d70711501cc242e4fec57ff1cd23fa89f40c75eafff5d8a296765c3ca85dcbd0e6873ba30f8e9727c2e03a774263dc819569b49078ddc91d092db5c5d86fba3cd00c9deb7a6825253f4a52b3ae207a02d339001b708ac8ae079d2cf83071b6267e3eca498f18c1f61f6e1f5f004ebe6ce0ba604afcdf4820fdbc7d08303bd78d0784727730998443d7cfa463b35bd98d412a16474fe107fec5e9b3cf2e261e7866b83ac2c09f3373c06b6705428db4ada74e731c16ea5688a638ce4e3809cd8e2cdb0498255be724f7947bdfcd25cac6cd384d7e59eedca97a42c906a75b61109576fc7d5a16272c8d8b79beec74cdba79119b9af11a2b7ba1f1627e7e0c4004302173725bc2019a517cbcd073408002091d7bb26db8530a28a067d5adabb9e2decfc28faca33724815ee997e685fe20cca8dea496099999131c2cec1cf025466154b4dc3722104564033aea33a1ecd53837e7e6832cb4b652b8c06ffc87fd9bf77f0b78f2b0b722a61d22c733d300534e9487a0ece904508cef92ad37cc42f7cd5151d31eaae72c871b8e48adc5265913dc80faa5ef279e00715636e112bed7252890d3016d872e9a63ae9232108e2e4aba6c8e69817f77d9e8974b8877f1abb0b08c915e03472c6e98e505fc68bae9a42a6c39084cd27e2e040bb2ff169636252f8a7624ce7723acdcf1ecf17f9c9473f341c1f49c07cc1fefa419ff90974d23492e2c3378068f4f43dad9c9de1821ee51467213d278a437eef967518eb26cd79a5809ce0d472eadb347b60c33e067f86448d1e5b5f3f289b192ca57f827f0114ea70b5e46372150132c26f4605f95dce2cad7eea3e6c56731fbef493d0aff6e5d6fa39bd4972cb5ce81a60931e7d98d585340d9bd64fa02ff168ced65dcfb9554d5daa57a5727f2f275e44f3993a1c80dc5aafec1eb5901c40ad1fb376ef0a0f4c294a265e7228d09066e427c70cc8d07182dd9d65aceb92910e590053a4717f0a6309ff7b72efb5fc5770f96f50b3df0e9601b6d8198af281c556459132aa89924658d19b1711441de05665b73e37b5484e4d6617a22a37c9d07db302be544cfebac51e727296c7c96db76a727263d5f65e89e78f8210ee98cc1fa307933dba7748b27fa1094ba0ec3743e996efdcb284d2dd339fafad9972ca2c8361d5706cebc39ae54245dc67109c94d30e805ed3bb170620452862200b0f5e99090cc323d2ba9df5450a84444560fdfdcb743a3452ca72323d301287e3ba44ba5406b8ae0635f0916472500d27c415768f8ecb04bd6bede194dbfa28750936c83bbc771d8870fea1b472da02fa92f4af33a24f5de56712c0481ef8f7ab42815e7f7e19ac6c8e00efe55bcd527f2bf5dd6dc06cd5a8c4fa0895f90dd89c768920236f4e38d63e7367db72e25dbf69410a622b0289b56c450204e2da60c2ecd76c61ad2df637da3077b7723aa708e1055d1f37c85f3d064617920f382e12e13f9d9b0b70ebb8547d71df72785d668b8ac7abe974ce64bc675676773462173106ca1769de64842d3680702c17125d10d0850f30458de5f1774638382fc6318fb3860d1815d5ea075cc5c972feb4d655e66f66c5a7ae18c3f527a9e25b2ac8d66a169d051c440ffc57cb297246d33adca67f7ce35195edc2143763bff3c9e9bc88a084a0773c28083e0ea8207fb0b8be487af0b775cee41c174d9f8cf0df609554c948eb465fd9395300187274fce98684457150b08e91d116a87ffc1f507fecdd5269136e4e4f3ef2633f7223e5074306e2a9cdd37d4b8b7e5288c6ff672d7f00b70ad519fc4946a23e04729fc32a89824d37d148330c1c09b14438447cd5f1bf586c4e857cfc2251fbb37295159febb2ae229e7d7f835ebec977e08a211968389e2eb5740695e975cf71724dcf58b1847f0d0065a29e07f47568188ef72b2bea3284f08af5c82b3f42ea7230b1667a875bd65e3e06667f42a7e28d76ebe16b1cdeb1a10589e574087ab772ebd21293e2ff25804dccf696e5357f91ecea3f5a7f5b066645b188f88af0531cd38d0501aef2e2c5f1891515ca08f1401f214e89f8487418cd0eecc6db886d72643a8d78dc687e54ae9cce1501499f9ad52727ddbb6b48998a207d55e01bdd34d05aff594b6887a6cfa368e014785b8cfb73f135abc0860a410112c5a657590e99e93762309056f11e0074355999fedf226caedd55707c0998e99fd5903947720340429649621dc1bd8777c0f759e5052d65430cab3a18da19613809b319b572749ae1f0df7b40fef737eaa1854b87fcf15358279d9db47ffaea1fd69b32c3727e6ab482275952ae28993d564e7ca4b157e1b22215e42e5ec0b918947ef3dc72a1302f5c646860c6576a901d07f3e0bdb18fc04e0c0ff16693dadbb1539e193c321e0679c037cce58e837377ad07c1387777a674b5bb29ff3d2d0488c72c966a4beccd8e84fb54255199c2d1e3375c856fd373a189dcee834cdc620230e5eb72695decf00c61ce596a704e93bd649f30f4dfe088ffd948d77b02f9c8c2e4ae04dbd8aa45c85dba38bfb4b1b3bce2d55198e5da3041d373506fd20eb5df92e072aa6ec31ed8d6063485ac481db88c51c43fe0802baa8e142c11e363c97f83da0d18af316a0c8b7f078ced8b7544d4dbffea341822afea32f1f0f758375afa0f729bcd99ead8eaf02b85d39971502b130c7d8c705ee728d860289f66f520859f723abc2b6336d0b6ff6481fe45c0cb8f6d8e4bc7c34e0d5fa9c3408ee0762f446f4915beb5bd41dbd4e0a600c91a5d414a3507bcae5f7632a6f0417124df827b17efbd35d5b54ec25e3672c1e4dfd03444c3d37d113a3c37d5377d827e884ea272e16fd9e37ea41c6b294fb85ff6e83035b9849517b23feb715cba1be6a6d30f725f35e383c527342124ed8587ca69e0d2dbdc69fe8ccb6a0af1b70faf9137bc4f7371830b18cf3f549c4429b093726924f82e31678ccd3717a837e3c0ecf1a3185018527a450d4cec5566ce34fe13e766f124f6878096c3c4ae90d5f3dcfd582d4f614c7a0bbc27e7c03c2d853be12ee33228f0e7fd004ecb3ba85c858277a6165916a8548d237b7a643f9f248739aff5d3309c9f76691627815eefacb8b37a212b40e39591a58d61e71416c39e970ecfbbdda03b96b798c7f569273fbadfcd6eaf8afcaabddb390e6b88e724b63c03e0f96c117ee5ce75e61b54bfeabb70823c4fba00e009795c139949f03e7c9340907d6db6a8945e4c03fb86c67ffc1dfd55c6f172b92ed3b2a49a466e71fbe06a6b76b892e5d8ebb8bebb4ff0ad8f5b255a28952197e3eb2b6b5242ea76c9679b1455e03029848f9ecec769a9806530e8729615a0377bb65b3482a7ce1b4c17c28e549cc62920b7b74fd57e4e6ad5b4de048f8831fa1a7126954122096d8b26d7aea7fa4d9948f59f5ed58330c07ddbc37220a57c4e22919f93f3414d783830fc44552ee9af35ea63edfb0a6ca38fd72c7290872f4b2133ac0799ac90c9fa0561976b425c3563575d4f38765aefd7f59c72ba1bc90037f9d7fa387dea26865829a0d0d8a57792b9857a3fcdcdb21d70cf696fa6696d9ee1d8c4a41ca360dd725792ec504d6f99d14b319f1952a8b7d09072190f6bd078118611178bd2c0f2594339964c104c8b4aaf3feecf5525b02ddc72b43a24a7b2ce8968259076fb00792b0977c40c1c6b5526960bbd6823b396e610ee3ba86a9c3fe6f1247ed93b86547a4e6dd5f5bad42dd4905a92752965b5bf72214b51cccad5cbac4128a8bcabcd2f102caae898719e32ed2107a60ff3c62272282cdc69f12a90a1905050402fb7b95b113151de922aad2db10681ee03c6a8728a04af7254cbe99609a5ec869eaa7b931b1f66d214564f03b18500f03ec29572ab984aa9c1f76b35417ee8d979c53824b343b679d37e06ab568362fc9c4d2762d4128c25ef078b4f92829b1ed02d97a4569b8d857a5b27f013ab9585d894a172dd541ab05d2ac8308cf64551e7605e268fc67ac690d0e0f1c8c4e7e6699afb7263316bc5f30dbf643e5344948dcbfd208019c8064c46338ad53ced5317acf472177031368de35b3444f489a0ab1a4352fa033fd575894c222db21135a1528569f715db93cabe6841451f3c7c2a0930a33e1ad70f0a0187a3126cbd10b1c52f63c92d7b391ea4793421f2d3edd5f801d2ec471e80bb410a329e02cdbaf693322c0d9b323f327aee2a11fe5a9081012ddf8be2adbc7c815f0f05ad23c154f573431160ea8675b6c8f3078e3ae0e9f9a3a972a337ebab329415ef0c4a66f572ea728a822a19449a87d107e93b63f3e559d21b7e206161486236305fcb64d96d2e72da108819a5145848e29ad8c7b74904b8f9c373ceb88980e9e1005111a381c772d9f3d1f5a9397dc0b00a14e7dc5e6f78e4273b54c362dda7aaa1c80b453c424bd48e09cc191981ef8169c83047e276aad6d0cf8f8f4c0eb4892655bd980d4e0d649392d53d46ac2109dbf14b4baaf49612a21a80f8e02c6c05b65b100fe46a72107e956a7bf5cd54ad2ab8d40a465ec1312965ac7d4ef0f259b47e6f5a55ba728bfea8f766dc0d9d8fef13a11b9e2b7e6fb78f1ffb0f653faa8306159f300e72afbcca04c3f592e51ddb08b5519b460cacb3f878ed7040f8eee5ec32bd622a0ab05940b4d08a10ecec6a97ef2017a8c54d2638856103d785b48e180baae07e2106d321cd043921a2c1b3d877e938452e6fb5e29ed6f4418ae5f201a74ccc9831fb537f672654e7f3668f68a6b89d22179872b058aa2fb287c6c4416af86f0e6a561464bdf611877926e7214f70699b7dacf582f5b01e3a8dd2a986d74e80e971aec17ba1c477fc827f98931ad237675da0ebd3ae86c87787ffc7cfec3fc0497252f5979c5bb5bff4b41533de678dfb369b7b6b7dee7e1506085937c9ec2cda72a6fd7089c378945bc49543becd6c246039f9c18fcd0cf053613331083d62f872ef9e93945d2a365aed81df501e541f821a54f666e73ec25836fad6fba7bd7972442247ddc704691bb3427291064dd2bc361aee048f3fc11f612316340295a6726caf454f8c8f40312b9baa471059806eef1f051e800e1f1d2aff5cd369e1b172dba6db7dd3b6274fc7ebf26f758db0ca5cb04905a7b585c1c46bc919a85dba00a573df8e2b6014754d5c8969ce30c32f2b8ecb6fe343bca8a894692c5f3b577239e457755c4853378dc4d2598625cbe2f83b48484ac04b2e8c080cd8fc21c67258b3ed8820247a49473c0a18f0aa553e877fceae2ecc8531221020431c3a1c72d57f1d63d15fd1a1e2c403b1ae65029d68be19045a4e407db73d52261f1c6027743ffabc7829b3204fa64346a619337c90a162bb8088774097ce522d36e894069a7ce4d8e298120d74d2d934937f7f43651284a0168f199c680b60759ed1b5724fb933d8f71f1a83db40014e9c4951eb05622b03f117e5b26ce7e2d0a9e12c14b8df5513fab2854c86fd282aacd91c7c2e67abf2ad18513b72425d3df5dee2728d139210e0627ec94ad91e57411c514715e10dcf7293d5fd2cb7089fe371ec72a8a8c86523df67a63151eae6edfb479e97f6ddb63147d6438efcd1e7348211727f1c5c427dcecf81700e497504c8f86de9d521b73470a10c694105016dcda405e6c7c05dc597d8a3b1ca7ae71f5c33d919ee8a0a1ee61f1f4ed71119bb78bd53234659a89be91c96fd3ebb30381f89fbc6b0d206c1ae75ee3631ebd466637072260ef2ddd3e98a0bae4ec43b853f148bbdc4315071294ee52332aa1ac7dbbd7241f2cbc16f1dbfaad56ed7229e89bca47e83e923a461d7f2ab18c31a64d7a95a9940166d9e7d043e7c730e018d1039ab2c9b50bbd1b7c062e1493924c84fe672184bfcf1da20d3254394c1a982ffd983ffe0ab404f6e410e4389f9ec69bcba52d4ce1788da33b8f7fe2fd1ded19892e16316b9a14c4dcd4f3c95ff60d44a3b726ff59933940b3e5cba4c608344f843bae29e24f34a6dccc1ba3cf09b2912784dac08c69ec1f4a87b489299d4e1e5e1117a9244a974cce77aa68edfa6d7bbb172057b5b8ee0954ed62f71eafb776fe92e9886922746ddc1b72b62a79148525e721553c4d00b09ed2be1a1de29f854c02b4f717b5188f62a042cc27be243aabc7271dc9414b77c2987fbb41eec2789d182744cfe1ccd961a8a24fe41b43d4796727a5510c765957c644dd5bc1a775390fd33743fb09a09e3e3fe4825832395233fe9dfdf8681e9fcd52008179466dae7efb5b4bff30aa9bc943c760e5e6564dc51289084b9d712269a98939e6f8877aeedb2ab282836b16652a95c334f5fb47626d061d9906604394e6563e827c6917af5cf441f6fce991db8aa2649a801c22d723a245a0a6aff60d2822f9661a49c3f877a5e5debdd99d2c7940363f7a5618872e7b9c016be30a8e3a244bebf73d594b3eefb69f0fb4dfc690e2c9159be183c728007f24b299ebf3bb7742ae2ad010cdaf88d33c8e4fb14fc53c91362e5a36172c78c9f7a77a1b89bf82e0151597f0928b64d8b26808b5c47771a25413f23a072646ad94f166b83422b098bd832fe93b01ee38c95caeddfa169cccfd294ca51727f4173068842a6cf5acd79f2f1394841744946b9768b8fd460bb6964a8d7b872f08cef9f5aed808bfdc801a7984b2f0dbe60e55aa3c089df64b893b0ee20f87214bed8bb67ad1187d699f2cf75b0d350e6d586e5b6b251ac47a2a54c61346572f546e3124166d73af0e8a5dede77abf9956f327f8a30ea8ab359e53449e6287231c49e92ab02c9a8dabd1a50e0d121a25ed6c5ce145b3ecd079e7966c0c3fe7213d162adf8a8631579bdc28c732b7911d58cda828e0da8731b7e7130452d3c72da6d7bd1a31971a6d796c7bee473bcfe63f6b4084b36e2527fe33a70048ae1720cb7e7d20e9202623845262dd27d144d9eeee03e4e9d090bbd468577a8723e54267275b8814f8b5b7257fd16c375c9b09d50f84577da1260db7feac6444c55721fa26878b67f5c63187d047a7dce59dfebdbf2feb9398534fb3d294cd3ab22721180fdca4f1c3ac803f38b7b8c589bb2e83e465bcf28ac058ccd9b50c60d2f4a1dfb65b59ad5c553f971835a8632081e67f935ffa5c739f6213c384cc95cd9721e2a6ae6e1984fecea9561de74ee2b66cbaa11aaa17e0c707ee275e3ad4d9d712fb0df391aac69fc5fc387fb288fbe5d6a39e7aedf4068be23e11cbd8d09755ed3aed5c62c447e8853a7c05570a6e30c007de1bf222071789e7e35784255887211bc3ee9bcc25997c3f4e1cc1a44f226bec8b0c42f1f32fce74126ecf116610d263a931b698b01aea281ef83b32f6ea12fe7a34b8c026ff75a746c2eccb49172ba1ca4affbcf8bb3d69bf4b2ae127350550a9d6c9c4a492bd1d0155300e7a2720f840a34ecff2821ab10c4af43dd942de1589d933bd5b0e310cefbf4a7feb347d438d8415a7f1791fc05b9b1e889ad62ef7d08eece5bff0821ff6386ecdea27227073e4f4d36f259449edbaf0d82c8e2024bb503c02fe31020e97adc21417572c0483bc5bf109818541d804ba6a1c2fc6fe64b07fc0e2c14f39bb7a49274b172a5165036ada21bc8728de855c1fc8da042baea9293cb0b079edef25c592fb47261ad1abb7ff5d296f69507c38cedda9b6123b87a0bd73a11abc046b9b98fd8721ee113ad5b1bd3e497610c9a76ec00d37d78f339fa58dde53c5191fa1fd1d81963aef9c3bee6d05630dd3a8c1e150460d2753645b147053d382cb8cdee23e372b49f36e1aa91217e4038fd738669919d8b0c2503d2a83748617f09e8f00a2745c9d7643fcf0391e79989364ed7857873f3356155108508200484e0b2011f917237fa989d3b005d884ccc7ae0bfff304e742baff924e1338b07fe0e1f0e35c447caef5acc45f2e4e80ab0bca8876dd9f8dd62cea634c2a46beda0519f74894f2d1c1dd876e114a4e8ec9682a90d0e3169ece88c79368f917e0d23d81ed3ac0f7234c394bf1541dab5f610fef80225e9f3b6b07859fdc3375f05313502d842a4724e7d9e142294ed8684978cf8f1ed1e32be4a01a0a5a5916e591748056a3aa00faa9c418a34a1081046c65aeac4c1cbb8990388040f0c909ec575a07937997d72fca9579b8c309155a37a9675e12b2b5192383ba22bcc418fd1ba7c5161128f7295cd22e9e8fb7a1b1bba3c5b2bd476977ace407e20884319b2a6cf1e22723b72fb70a5d5f89bce2957dda075cffb4f5ba768a2d82ac33f9ba37d64e8a807f572a4e00fa244dae8bb8ffc281eedd2b41c06ec03c22542b27e49257ab0335cac386a5c7d5282635a8eafc8c074a4bfb51ea930eba3db6dfc4f1178530ecc13d856a7c0271d65d6c19dd9240d77240706f69ad3951a091ee1a5ec45f3ca61b52b72fd823ef828ba63d9f43330105d908c2b85fc019e18b9742143ea4324b1505a403cbedf34c962caa0b27b1f7bf6e6814410e4209d108b3864b31ce59f5dd19f72a1f2582fac0a09eda01882264d6ef6ade36b7b5cdb2d2bcf705b560bbeec4f6e2ec00d7b8e1d2221d7413bcdd0764816f1ee919e8c7178d4cc46d73c7a2ec372ace6d022201469f556592c17ba24d6ead9c37beee971d30a9fb253da847fd31f06eab124af9d025d3560057e57500736bb6a42de35ee556f6cb9fd0cd5531e7276163831fc46b40ec60e665c0b893f07fff9047c07a040dc8b956d33902a4a29fe161ec613f287c9707e56e828464160ee95c7b3646818a7a8128595a2924b095a6d7803c988dc8c4734fd197ff072932948aaee8e51e8524eb6e3ac9dab4753e4db21b7f3a207211d8a987f113d41db9293a7c60b8874a5ef5d851619c3d125697ae5623873def93008a1f7845175b858aec44184393b3f435b89ae5c4e1769bc1385641ce970c3a1516f6d0c23fff2cb779f5d5dd83aff32683ecfc1f2a872f9785e2a4bca93c70b6ff6b30a397c8d56973695a019521fd108686caf1dbc113e883a79a9909aaeb7204d49e4cde03346f6e5a56ee680a592550c603752577286f11832f4d7e38ac2119a9b91f869f2cfc86ed4150b2db889671ca02602491e5d92bb094ed4c44bfb6e4e3ff2209504b697d6bc39c0c6fefcacd9c8766a6f0a680f06397eb173ef4eef3b3e5fc11d3796a07cbd713adb914a235ba822ed3d72b5fd34147f0d2f08b7301701eb89f53e1b25e995c1015726c938a66acc79d072617e82e64012e2715448aa67e2fe317eb05df843dcb11808f8b5be4d88043f7230bf06c939ed8bc34764d3808c4ba91ffa9015310dc8b6a09824cedd9f9ee37242fc5596e71e9280ca24432ec440b13fc8d480674105361ce851d73057c09e5268bdd2173381792bab16c20ffc9ac292ea59f551958af8c24ead296cfecde972303be184bbf99d870fcd0e29dede9c4b2ce15ac8e2082bfc137cec1525e5bb21c093b5ab5750b75574aa0a17b165f8237b750664ce5d0943dd7b708bc32d6c12b12d6902f0d0f36ae44ec83b64eaab0eccaa23e85470bd4e1055d0b4e3cdad720868e70af5b2f8c9361ad6506554d49307535191af8edb2887c18a46f7cb1c4ec19fc46e13c8a09ebfd59452b5dcbc84cca27c207aefcf468f24cb44d34639716a82f4b32dee96accb6f0b38038f03e50c2b6a81f2f14b9e3c36c77f4e997a72c1b1f39a6b65576515c51ebcf8ac19a75411b947fef94cd43d902b299b83601475730ca0c4a0073926c42681c25cb4dd3603c79f4177dbac8fd58f2b21d9d872869e7a3c18fa84d618060e72b2cb0b02888406afc958a17bc1a7424a9c1abd72c578e2a2df98ed524e0b58834b7054ca0c36f83c0b76534085cbbebdcff4da7268d3deaea1e8bed6ba69fe8a153a37f5b713faaa416e5fb2ee9a306c9c3dad72b8453ac800343f8149455d993722a1e6eb985c21c25b167cec11ee4ffc5e486b9484a518ea46a6b83d359e9183841e4c1adafe54bb50601ab8a98e404601fa4644e28a55f6cb8334a1ea40eff17ade21f87bb1a4145cd677f3e5795e4e33475208d76f56906edb94833678107c353ff6aafc8198dee49ce7af8ba2685aaaa6721bf2055d2edea385fd313446c606398c5940d2fb4bd0b1125632d2734436cc43c72bfabb64c04cd0a9d37f65f06cb848920264342f652f3c5e7646ea86b6b7728e3389dc907cbd364d5bcd8d16e2af7433482ee70c1a4d55c3271922e54de443d250a8db80e71a829af288275e6dce0a6ac5b4738f86322a4e5ab78b10744772102f7e3032023cb060505f24ea1031a73f5765958cb02cd88ca4abfc4565e24e963ed76357c8ad479b15eee0710a38ff16743b12b56d4427a2f01bfbf6ff1972a4195ee952a2d09a97093a7e367b23352c88b9a6ea40d651bc03e1eab9024339fd4d5c5c12a3dd275226cc0d30893856351a59da3cc4e2f613f94061a077d072fe40ef3bf73dc1a8fcb9eaee9c0274a938eddc07a8b75d0b610260854c58c2448140789b0e866f8a901f843f819a71db8923c02328881fc8a1203df04a2a8523305da146ea323f597ccf5c81ada64a2924bdf4885ebf5bcd2494eff845712653861ed88f0e66315e05572bff51fe1f75b8d7ce67f818cbb4c1884b4d7a278a722f7280b754197d4366131a6b987ee48e4f455f892646026f0c5f087cfc3ae272122006cf1a94bc90984ba7dd63b606884a97b8b79dd48766f4a86652c2fbc77223344c5330f8956cd097cb5e5332830b03dea37482f4271156fb3e43b488f26b2b22d6ffb219819c990340a99aa7c1b5341f50925fc8234d39b581aaf9596e7267a8dbebd7d0edc541c230d14e9d1d3e8c9dc14a915455a0144c8e60a61962722a9a2f1d1dd3805b940a9c942779f2bf8aba4d7f66a2dd78203647b158455772be961ebd812687c3ed505782513dfcd26f89818fd8fd431bf329dc71b8bca0720c3931f945c972b72b56d2fb9015d8dfec01f83bd163ee3dc18991b274113a47578219baa70daf273315b9b4b0afa5e64bcc807923a6c0fa0164dfa2a6c4ac6686e45c9a805bf24add2f75127d230e248329c43414c7bb3bbe1b9eb1f3f79272df29858e02fa8292d40d71891ad31c1b04b8fa04f877793179ee661257883272c317842ea02a12d0f914d1342962f1b08c6eb10e9fe9fcbd39dfb41ceefd15722f87fd7f08498b281dd795d2519455e417e9cb16884dc5c465a7583a1b8d373e7f463ba2631da09ddbe7d4d4a44372e690bb9af663e0c0d9356f74f8d6c17a2424188f2de6f4782fa17871e33db9b38984f6459fd76778389b64494bc3b23972d9acb088b7f68d225e2c8fb0d55e4a23c2c82ea03c64dddbec90c44b91f54f720e0bd507277d1c1734a3ff18d253cb815e6a33ca8e728f7f990100645b35ff144212a21b179d540a13b7b598e783685f37be00f95d12664bf19dca5d9b99ac4af7ff6c15af67cbaa8d070bb065f42afe82bb6afad874728b313721c447daee72332c2e289eda8b0e952308beea9f0772c43c26f30da65489c6842d63d5c5b76c7727e737524a99f3a0e31c35d28ce6924f160af5b610fec5a09244c67b9bf971538662fb2b00e77c0ce74e26f59cb45095cf6636bca0234ccdc486b121a34c724a7973544a7d9e96b6e3ba7827a2bd2bcb5a7df1cef3478a3b75c5d9395afa7273a1ce4c352fb8fc342c411ee06b6b0b04f500f0b7d1cd63afbdea0197e9d1726cc817e04411ae2e08ab08ba7307dc84f9d6bde7307912b0729e31d35c8a3e7260c4cd5aff616ad619ebce0a08fad1a841cb50e132df41cbd3e1a3736847543e8791fca955390336c38295b870ab2a7b68882af9458d9d8a63f74bbfa15d9a723815a2e91e43e5a8fdbf09176dfd5cc60b3fc28b8965985c95d4493b3cf7b35a37520251b53dff65899f3d3624ffca135627d78fd1d7ed023161f16fe09d9e41704b0135d609666d88f5a04fb7ff807b4bf75a305e86dd63dc224d80f6530e01c9079277f0730b7e63bc20e47baf8f88f53a0235bc92bff4d0e49b5953a1af237f7132932a982e06cac6d0f9b3f7640dcfa540d78d2ce80c84eddb1fb1814c726e8d29983feb66c29a70a9e8eb2a3f3265e1c7be4d7c98b0210bc4dea202b172a0d106ce5061cea8a5482c6444ce781a34a53481137211278b1870e35ce1d542db35c3cdb6c5d7fa8f78bd784e7689ddfa18152466ac85f17381c30f1f616036068b9a94b31f9333b0927224b21b608f1fe7981890ccd7591481a2b7d405d872a9eceabe3e488684f4c09ee6f0a40fd37b19d89704c31f3b8d54966e68b6b27209d22313c2cae6d61987c45af4dbeda19f2eb71d115ad05eba2a67a3373d647254e87d6c59053f90aabd8c37802872c01b0ca8c6c7c456c22e943e9c5e6aa3727b9cc8ef01713ace481b4052d7f141e7ff83b49b8a1dfe8c348d62c7b911c21a8d7c8b0f50dfd7dd261f13fb14daed966c849785600f6b0034fabaaa01042055d005e735c3bf9a5a79595e095f7feeb9bc68cc1abf7a00fab5bb1a8d4197b223adc5c659a53167fe79b331892c1295a9fbd331a21a06e53a9260075b45ab507281cf3c69c2b297b9e042e92a1bf5f67ddb135afbc38e79263f6691e454cc0c72287560c4c9bb529f29c7d246844dc73eb8387b4c24d5687ee82deb150e57b47272e26dcf2cd18b25c25618e96d10df081da770a3238ea4ea0cead8768e17a8725ba6244a04699f5d259efa664ff999410d40f65e76b9c245dd5456eaf51b1f1cfb08c2ec17f903bf6aead28823e20003842d3fe8e6b8f7e68cf62f816a1f447207abe66515f6c05cb8971151dba1bf1da1b9f27487ebce25d645e4648ed64472cb36ea7baf3a4a88e662bd12756d1baa34e620ec822c7e17bb3b9fe7ceacc2312633f243f14d3141f072fe4bddd408a74c210367044bbb62361be309c62a20723ebff97dfb664cb31837398954551cf44709e9216efdc53a96ef80ec5d378a33e4971a146d292bc3336bbce93a56394ed1e11c0be74ba7b91f5cf88e54dd9972a8f975230b48e0b4d68592c908a1ee017f14611a420ce0e6039502e88ab3d850f47e637bc00a292f5d71153863bf8d794e51aa7797987c2a2f93982b561ce7726fc19d6756d0fbda9c2c08efa1e8552d6cde460fd70c2292bbdc832c13492468e8279350a8892822977e41e8584707296b0e23634628bb104b59ebe55292ab0f363b598c45955fa2d484598a9a31a8a86859433b75442278f0b20927c0b6e2725828424e36fe60fb471148d2fa85cf427f8dccb2d32cbf9a7ba55404b113554e506f8a509b004848daae31eb2c87963b79adc711f64c8440e6894294afd55772c25029ab8d8c952d140cb6b600c4334afdb1728de3577724da3df3ee14a844301979a15eda217cafe8eddde399744b876b92422fe65fafd941ccdb5cca72eb72047b4944c943b755c60862ee3f8cb49e322eb84efcf363642299656158a9bf729a2e65a906d21920c48b7291c4b1e7734a93b6e6454c2558233dcc3e0b07de7258dab8cc01cefbda0763b61ddb92f3369922a4c71e6ecdee0f04f38e551e3c727d428c18b53abf9e1b682087f92e4dc57d5c0f420973ccbc3b7a3bd2ffec1a72d51e471d736c5bbdcad075f17d970574cdd728e4d228f839d405fd649bd14f0391f2c8acae5f62d7768ceb4d257e661978885a53e8885ebe7368bd60dff2e87206fccd3def76357459c26027f0b2d7903ba4aa5f2b301d97617d528cb4d749248d603c0941315649c49e2f4cd802ad39a3b5f8f59469ffe21d55ea6f98346e72db4a0bdeccfbea1d82ff923cc920140b305082f67ab541aab5c16c8b981cd52969a3a98a3a954942d77246fd9d7f3126b54ba95e435a1fd4b7344b6d8a1f2c72e076e1494637deb8b91ec0dc8b189ab305e788a3e8ad14c20eea065c873abf726a4c6a4fdc0768e3817feecae3a29c21e9c57cbb8e80923e157f998001cee75fc1d5b89e18bf59898fcb5156a860ea37b34e5241ec8fbe6c114af1772cc70972a9f039f3d059a1c695a20ac66855d0bf3da4d8679f03877b04a885ea61f89d72ece656fc488fcd91a49c528bff5da5ec7ea2c1d99ab265a5dada6ad0ab0e9d72be7f1e224172d991664b2e1b53759d6fda08cbc72cce72d95a8344301280ef7284668215bdf10778a41de33e9bebc61d2f7a6aae2f39949a77cb973d277c9072e3c070dfdf17a4399e9a5db62a01438be8a010cee46a80095a4ff120ed4ac572f815944c778bd65eca8697e99c6ed5f4f5c953f2463e9862012de457cba7d7726ada4f5a6213b54c93ca1730346bdf972fcfe6ad917d7b8fb043bd5b81c6d46e1d65c870c0333fa2fb95ce9dcb2fdba09ae8474872d49a3bfba189e3a876e4728768d5ad0f676d89f9848e632b58933c6536f74aa73f43626fab411adac046722ad3aa239cedc62bb69cac13f081a6fe2049f5484f72a61aa894f886dd9e9e4fe0c1f13167e6f2a9e8dbcc46f9fe9fa11ae7669b294dcf56022f98c0cc1b784669e680734fe901402425290a37cd2448248ff1b20562ecde3bdaff3fbfdbb7497f07c38b750bdb229b3740714d5dff10a2cfef80c751e215ec0194164074b1727ad2d032904fa12a7a3684b158d504a6025c49a407dbf1c0d4139ba62670f472175f7332c925fe797e86fc8f9192f518a22d327ab8a76f09fc8bea3f3652ea0b26ab673c6fa4bd6e27844f2b60eecc067abd00bd4136cf35bc607b3557a45272b7e02fc15fddacb6ea4b8b9eb85b999ddf39a640dd778debf51694a9df309f329c4ff796d2500aa5e09401e3347c9fd7d4ce8cde19b01dea9702b2884350e7601fd7f582c568ca782b36edb89fb12656c3ffec7297c11b6a2b2a02a1b393d30717c830424fcce22458cf562070914e8f91810ce289bf21fcdc2918a15041db7231b4aaf1015a9d4cc7a9260ca4a30dc11a3a3972a21857cee88e68d15f6ae0726bd6fc2dc6efc4071c14fc836a0fc5940faabdc2602fdb29fc3283b6414249725a4c72a6885d9fe0f9d22e5535e4206a7269fb74a2de26bf27ff709514366c726ee4c0aa5b9c4e8e82f31f67b17cbcf8432bb8b5f32ffed841bd31ac7e73c80e80235cf1d625d95821e52ded7e0bb3bd633365d71098144880018a5117a17f5d8efebd182a42b34e992030aeb5f3d2261c25a93a0c2f17fb3b2ee2fc082cf70bd15e186864730fb131f78e8f4f15fb551301b917c16a426aec17cabc358fa8724c8b5bdbb1542852ed5f790b798be25a5201a53822e3c87dca835e5480e64572201139b3af8aab0f05300542c5b163c5ad36c3ddd5fce45cb5174f24ffef5547f9eea8819d0f2c8c27ef65915db35b64c2a267371ab040cbc2083c10f2616572a22de971780d3b517acd5f23ecc4329c10a4654df89a41c04ae5a3b896c88447f2fa24e84f31cc24de5763dc0541ed225e9eb7de3462841f8d7bf7f5da5db1723abeb806ff5dd28884fa088370b377dd9abe3ea9ec842706c1ec584ffc3bee2943f7db2361ebb0095fc45d01a1ae1016c5d410d0f684416975d8bcfa59eaee03fd67f5d529d71d38bbaa9a92092c96c7701a81b6b402cd0f0bb37ec25bf13e09d3a806fcc0cc062ebc362d381834a3cd465e8b06b091421ea164512f5f024e72e109ead824f2094da260db5d62aa192588c619ae4c62d4b043a4db2b67573a72b34c5542bbdf979bb98d8946d0ae0d126975cc79c1203944fadf1cdb01d6c072ea0400a06d4f29e147b815d9a79f561f618d2e56d72e676594d059d45e37fb1f068c8bdcb69e304bd593a2458516a84a72becfdaa05c07812f8127d168bbc22685bdb028b31ac11d2718e5745800137c33209abe2b9025142646dd7b54755372c70ca3a9ec64e46393137b111b3cf60be170c0e47f6572d2460dce33a6172272cfde1c136366a91fa0333d801ab79dff30e7eaa3907a4f540678ccea34ff421fe2aa9dd7c436ecdb466bd00a651c43fc7748409e609cf43e898163ca6f6f123503c238ff6d37ca20020d597ae38cb2125266223eba3bb3d3d9fd2383f194df7258dce94116f2560c3dfad655b5f4d556e69c176b7b0d2ef8348e6ba1e419c37219c60641ea78c4dd24d33af89dead3632d8b74f2992d45434080aaf4a043f97222195a3d49aefbb3dbcb40ba512ddcc34a43ed44fd8f45e6bf8eab12407cdb48d909025b9c677072909b430a569267a2568f8dbfe5035e26221ae3d69dc21b6ec6074d21e6ad1cfb76a229184fd17d1f5e4e0de53e856694a3beea9da66f757215a31fdccfb2803b2b3b13980a02eb450fc547947d6c177943574143a839e842718b096a0adb3de386c5f41e338473908006f946f266f5ed8f2e1cc12c400572b6ffc16bb7f065cb40abfc47a940fc67540b2171d5d9819cbef8ca4ab1b63572f0851e3f3cce16e32b98f89b8d799bbcac3c912097fee73906f2c1453749f0721429b516128526566e273519b1e2267faa6555e64b0cd7f80c9e9db4e150175e2dee03384b18850d10c02128df7b8a79b38318780ba4b698ee67ba77cc2e0a729c011bcdf814724d36d5d699a6577ffedc6d4eeff5bd71c49a9e2f8f3ac875338c460d9f363974e2e1de2b4cb69662aede0f62efd4c393a6bea1b88087359a7220c5e833f646df0999378505d17e6dc93d6ba7229409fbdee79279c0e86eff7218265ae37d676ae260a167e363ffbdd43ce0e63b1fcaa065f726c4efe4ab730f9fcb48b12ff1cf3d265ed9811b3a8558e8eb59b8524c5c7cfca7ee25c344054cdebc2411d279ffb34366582fa47552dd3627b9e7016f3b01a1f9016f6e115772ecceaa7471f83dd769fa7f8e47aab2bbd817ce3801cc07a7d4e312cc01ac2a720cc1e1582447b5cf1c20c2f589daae0b7b37b360f5de61a7b4209653786022696c8da82a56a8fc56a2c2dced2221d7dfdfc00f2c93e806ccf8c5412f640a557213dc342b8af5e09c7fd1e404619d7549d346ea373f9b05c78b7b9a4c027b754c235fbb7309944b4b3f335a8954f6fc95a2f051110a113607a688966e2d657958fedd0769a3500187bd1604683d1d79a3d8a46d18f2486a08eb25ff61087a247272c845f41eecbcfd8598bc11387640aa1af109c9d1955edc213fbc84ec193a2c4de3befb71b783849ad5b53a39caa3f11010ef167617d4606571eef72e72496aa59368fae852dc02208020eea330da3e7cff1374dcd6c54bea93f7b6c2e90272d1d8e2be6fb061560169da73594a654d91bd02da9da32fd88d115331f8d5ab728fa20a28a4c700cc847f1dd2f58852f8b09aad55dab9818e0602efe6e84f097274ecc688da54d34f65533a5bfa6c8de5f2d969bd6045ad37f9184ac6c9b0f61b096bda27c7121dd6794fea625a95d9de9c3a7f6f0725d43610928f8caaf89428c7489e174b389dea557e53ac211c717aee71458d19e43dce185ecd32f3ed4272123b34b57bc4b3e520641f718dc2bad20fdf1aae690680f51fb824de83936e7213ca0448eefe550af87f44639adf9eb81a3e67055e6bf5e0031a2e7b4f4e8672561768dba86b851f7df511629819b0968ea98a3cb9fc0c5e06ed1b6a74483b72e91bbca0a0d8c8b969473a200c302d2fc61ef625d42a70900e79d8c2b35ba150ead391d48ecbe104c48d5b2b7d6e3af9d77c160c6a27ae65f4b01289de7dc972f61fe102ae9c00edaa5650ad095c2f616d694ff759781216da36933abbe60472726955b6719ddef150552b3c530877f13cdb38750efb735945e6e8696b57a96a948c70b08ebdf9a51512809e9b02b7663d04019d4553a66071e0de278f004b72eaa5f66eb9426bcc91168805a3632a268f226a5456728ed1ce31d062da3715726cc51c9f296a3810bd1b6ae22d03f744a25b940b0055a4019253c31d7f4e2b25bc82dbdecf73b2557f66d2082e7abf54381dece74684c62377c0b74c692c8c59df74b8b3d635347b692267ca641a4fc93feed89520b09310ba0b15108b4d8672bb373b4741ec63517fbf9d27303c62e36689834bc804d0b4901edf9449634342fa4256ccfa74a69038cba325113382f74296381512aacb6e6318ad0adb75797261cbea530075453d4a4b99e9833d29287d1918ac52d56af4ca3948b44d04285f2f4792873ad577d5caa4004dec71c7cef8ad58f59d782a01c408009ad50cb372e9ae2f469027229fbcf4ebabb329b44453d337882d5772e1e54189fd1360f0726bf410256007a603b84211acecacd7b060387e12404a01c3e3c8afa06491447243df73a65d4de113299d2a4533b73ea426b1e03d8c1de9b9491c097754cf87147b540e99dfd3c41b38e4f97ba067a399c507318d682398b42334ff88434ade72beb2f5239ae7ca8df3e440bf8cbc64ffad4ca53583f659ca28da32196bc38972dd199a629ab9772bbd049f53f252add6589e140e4f362e70ea3e811f4ecbc5383cc5701846fed3f90b29758df6a71c55551e6d284768c682f0f29ace3d45261291fee6036a724b5df7bba07ff326f626e1e1fee27f9021ac64b88b47de327347213a42a1fec294cece4ec31aa63bfb85b8ab67288779b97b320254baaee676721e50412b88883deadcdb2c490e4c98a921a09fd6155a72f0f2a4ca27b4693d72c34ad43bfc3df8d08f714e5f1f5ca0f67da20a4222bb474e2af1a390eaf4743844baa950fac22d5b51628259d88a4c5f847ef53b82b15ab4deecc5528550fc72f3c91bd85e2c15b8c658b57957a5392ffaa4384d2ccbf3526a58dc752040083c05679831e45fb258c28f0ff471428b167dc428d36d652ce54b5a8ccc64c9f46e425da4974621ba8a56f30aa09b75212e1a33fcf4cb93c3d3f8dc46b16e54893f77234527bd69fe323c349598539704f1ac75aaff7cd2bc717f89fecb76d61772119f1a8e52f810c8c458d3147a7bf3e116958453eff9ca38c763c85899c9b66fd205cd02020ebe1451be9d5fee35fa6319638697c467860050a1ad53bb56e5723c828fabc049fff81f5917a3f8defd82b0a4632a31c867345a4d0ba2a893733637be110310f37acb0074c4a5c760eb17a276b8ea1f8321d1579baa5a0eac58720894beccb3139ae345262b435d6d43486142aa7114c62e393c993b89a4f86a2ac3281fc86acaafee6a985c53af9294b6f4dcf1f4fc6e248fe5b217d4ed6a4872c99aa4b67ff7c03cf3a1eabf5cd3308fe5d36a62ea422cee6ffb57700b13a2722a7f8a1499cbc10c75c6e4e45219c50c1b90caae128e403f9df3aa4d9f688a726a387174d2b1def1fec6f6b9b7f2099ec4993a35ad52521dd9c98c55d2a6fc72904753fe6acf8b7d6ee0fb37542a3a4c81d904a25dfab6b4fefa0edd73d4a072211dc0b021913f41fc61b303685b71197c4e9eff50341476148ee3c097c13472ea1a1294f05fd6858328092176357b691c0e722d3b6ea223854a26364aa7543949af4b48706f16f26f9ad2c66c3f6ea8ecf3f6c65a05f69aef8e79d4437f7072f5d9a0381fd081b13af49adf8c76464fca8d71ece4d69005f6899e07b8c6e401f19e6853658e9de6984911f0e16d60415adac24e07aa120e18949b4d6cae07721119d3ca3b3f1cf24e7a8fa24720c9d19aed28aff22c39536fa813c16739b767eefcf3ff34f9ab46a81c40cdafe5e4bad214b9b04037c43e8e6e1a548c216672b7e7b1461f96c2111446a3ca59f5ecf4fa60b51fd7d796a292316e63e33b6b7234b825c18817e65c4834f833c55293ec2d2a9420dd0bf3cbba162e2e39ae687243582f256944f3b0fc456f14230e7be80fbc3a69dce7f95fa57084a6e4579772fc43cd6e284e3da5fa5f12555a33d238e7f4b923a860b0ea3870217def07360aeabfeaa7d6dddc858d92ead428566d0ad7e747a18bac126446e6fc9ed02292725ab1df71acf3b88d072c27081ea0444ebe5f867be815841d41eb93a55c9eaa72d94cda587173b148c0daeead93ad9c7f4a12eaab4d92ef3ba5c93609dd65bb728711eaef096a687f1e7e294b1e9b74a2ec1637da4c6f73fd14f8cfdceae23d7233c13ff44438fa1e0a1ff9358eb9ef85a4d8a68664293ad9c89d55e87987f74d4f0c64db03da9143809c78be7f4192292755f598ed7ea7fbe21331bd2e352a72aeab7d6551bdb192d6e6f05943ff574e42cddcba88b3c99ff1d1b4ad57f63b722b93ce59efd2841126aa8deb601af5a09f081de283f939d95bb3a15d2fcea07207aa8422fa21fc8a4acb5865b761aa3d264495a06fef2325f731c41fd36fe17248ac0665f975f9c8940d31e58b77e397f0485efb9fddd3d271869a176e74386933eb54aedff46bff453d103a1e707c429c2b8d7335f0635a825104644c35a572dc1fdd163bc3f8c34ba538758efd2a6666351f22ef0a4a6030a4eb617b13e472507fbb679d1d5361630e2ee60450c0425f10d87df424b62c7c6f5baa61011467ad510c5abe38106cb2fc270c54363b8208793280d45ab45e94db378a511ceb72c9f3607ede1fdcba50fe2788de6a3b62633c379c7445ad711c6b14f4c17d7e72ad41edbd3c3e77b6b5f9c16de3bbab1890201fa9c36409e806dcb27f5ce0794734e281139f78fa6bbfd39ebc14961fd2e15e91d3e2749e8f914be70e08eb68514172cf03d6267142852cdc12e341236e9d66961b2acb8722b7191115815b5b72b5d536d19ef03e6e64ef1b6330a58a966c485e7fe7f81ba7205f2ee86b1979727f2df8bec76851bcbcaf63a2ec08c5fe3286ef6614a653299fe72bbdd9900233ec2e1844448fb05a34fde37d929204b55f1012e4fc1545e7f34587b06de36c49b328d4b6b49a3652be139c8d5c0bd88bbde94a4974377afb8cf6b84c020b41720385684ed6865ad2dc097840c62d427676b38423a85d987503177793145c8a44fdb2306aff8477738b2c723fc6b998956bcea53808559fafd06443444a34ef72178cb633c6bf7df6f54eaba7ce839a4f24bf4ce73d8634bf179311947e9b1e72af5f55ae9dae1742b2981dc19641338808820f3dc821636d66fc14bb38a02420da954426953143f7a12dd40def56b2cbf07bf818dcd0daac49e731eb953f6372efe1470e8f9fa9856ea96b92cad4dafcf47b797053ef97a7588ea6c90cf6ea45cd95fad479c7911cd492a3ad4d849c0e8bf56c96a65b33b012dcc58906c02372856efbd576b7e9bdccf3d3a087d8853383b369d50019336ff4ed97f352d4907264d47d3ca40d63543c3cf69a3119ff135ad28dd61159f10af326d150725efe72f39739dc6279dbdd268192c434cde0031876da0d2d657a3221f7da94349fb4723860a7576be16c6a7273795d94c2d418b653a3b4866defb49a27f4ec2ebac47256fc1e15724590a956d567c64d00ec87dd14d05ac83a2a27b422c015cec2436ce775ad411abf36a459826177ca7308ce9769d2a4ea48be9f69fe3d3d8dd6ae65e2d40aad4ccba18eeec4c8ecbbdd05f0ae29b6619d232ba7efd2199b0fd496726fde231002491e96709cb92ceab37c10424bb082aa6671052a6b2e1a0221f772ab865ff5f93709b7f0d67d367a821d7555335434df91b9b0209cd1d66e8e2372c99554faa003d7d96a2165aeebad580e7b5194715ff5686fa68fac271f050a72386a17a0c8cefcda3029003aa21381d42e61a297eb3d502c8799c116ba0c49216385afa685762d6a0ffe197127fe6e7892aae278b71f4464cb2bdf9c8780df725e862162749618b402f2c9d259ae11d2a87aafd17d27aedc2208a00a00694c72d3b604d54a9cd2cdeb80f77403034ec3357cfb3de49ef08eb372a2c82e1c577248f3a7892df890007029b2f712b3ad52d2e63e9095ab8af4bd22192fe049806f6919d865797e46c4eab3507917eba1ac05efc326f5cedb095105a5568b683f721dab6e6ddca3c16f52077c1c998fb2c9e8c78c063fe1bcba88b418aded1b1c6830f17d40c76184f723bdb6f03e25611cc2f5f89a980fa09a596e0f5e9c9aee54efc098ecb00109dc3343dc125126fefd19796ccd2c4a1ea0ef8dbff959eeef724c1e8bcde0ada37c1d443040acaec1cb906bf3d70601f8be1bcbbb86f80fb5720702e877228feaeaee8b2594fcc8a4ec440a40dc2e8bb17c86fe9f0ea0b77672a4f66d5f7cecb9ce92beb240559104fb7582fc6ceb59cf4c386a8dc27c93c108429d4991153db6901f7a3de6c8b562a2d188f5bc2e654b182c72320ed3af2955a0d860fc57f14d3520a7ed0bb89c0973229e3a85ad5e00c978adab19adb820421b5b15e68df14b4201444cac4052cf9e1150865e82bdc2434af826ffccf2de3f3e73fcc49a0c793bd93d21e1ee3180ad366b2cbe6bd82d5615edcccc11f6b15264dbd0ba6eb3da8d3bfa580ce4c20f68ad4cfc6c2ffc8429926f3903f8ee442f43a5c37a273ff12d10d8fc36eecb20152a5ed923dcd9fb826ef82e9cd9b21f4db912dff41630d38ba914b208ed134a7ee05b5367313a74fa9c448c3d3b32130b1f708b4b7efc442836e5f5f3eb14819c99bed9215d4538701316eb4dfa449a72a7978daaeff0aa8e9acae4f0f61288e7a6bda7a4d5e755e7b85da7dfef42bd11b532a52fd2cf5133ee66794b982ee771de49de2031f16bd5064a973fe28bb872fa4fb2e208044c23c6e7147423547f4132145f772f7cff83079fa3d3f35e1872ebaccdfd490bd1dcdf7267e09a6c17af79afe5f040d4b46d03d33f7197b24172642009c22cb6c05da7f6543706f1049f0b7bd5d39d841a85317ae01bab3bf27213e35996a034d977665eda577f3a1df9d17fa2485238acc52ab22fe3548635723b4268013fd83f423b5b89876cbb215441daaf59b40a4f66d7b12f7db28fbe5f2a90842c143157949c5ca953c9978c4ce91cefb33d0fb5028a2b3381cdf4c17231ff0c504c6c0f8ade5ddf743606780834d72273508d8b0c12fdcff66f560672c9b6ef2f228f88b5980af55e0beb41287d5b11e27e291965b0daef88b4ecf072df5b10056e1f93e979f3b646d51310399e68a1574df4a50bb5a674e4be87bd72fa8e90656e3bc14517556f2abcf2d9b012ccff249059fe3ef45f5b640e5d3372835c8147bbbf4f1fb2b179ddaee931bf27335ab1d520efd73dcc168749024f72620adcb9c93ac132991690a2bebc7dcc11d315b235aa8bad7b925cd9d06b0672633d1a5f907b77d00853c66fbc1bcbfd4e1040a27adaa266ef2f3bbbb35c3c72557474b63a49935b77ebdef2f2722d9c7f3a02e0da7839e1b85f6996b6e19e728a226ebba4f7d47a9f34b217245fc574e26558be9c9868938f32ae0e112a5472b84765db3dabb95e9c9995c0c4939f4ac38e53e5c1bb7dbcbc23c7421be3540a2a3d97fba08e30bf6b18f01df9a4c33dcc7720e6a88a9ef816c81934b537431259af6242de996775c5922154e3711cdba1de0e9ee5ddfb4674cfecb4d6aa4a4865e75a12df9b1f6cff033d16084150616e56b8366e0a063e82cd232a7fe9da52839add6f4975a73243cdb45213b6e661ae11beb04f2862bdf22f0ffe623ef072f805ce53e8ee979f042481be64d45b2155e9c513060fa4d130d5854c99e829721d8c2e88ffdf795ed62222b39f8fe65fcf11eef6373f778826e7a99889bc9c72a7df98ff5229d92001d240d3ad356cf23bd6c4eb56fbb57178f92543b0144772f5cdcd66aecb7c88db4a53b162665aba89db81f56666b40eaf437b9417490072fc3161a540878585ed930fd69ad981c0854483f7cde17a0deeb85c4797f89572477024a4fee99910a0d8a875a92c7e479360e54dcb68752bbed9b4adf2bacc2204c3f4643b96f90ed32fb84dd82663437e3888da1530a34fff81c4871585850f7c7bac00bee0e58703362594b542a383a9279daa32515a38408d2a9735c50b4cc4ef6872427dbf35a16caae5b14336e56c054753266bef1ddf00bfc3e95f640761a79cafa8b082612a4e8b64f4a1e7805d8c09df110b1b8c28046fecb617a672e028ef34bd21e0a069dc4d0ed9d1bd7e51d7f9c731a9cfce5d11d4016a02d21bd8533135935641c25be59ac18bf119cdb611cedaefd35eff2643c39356cfbd5f934b6923c8cdd25b9967e6f7b79f46121e9e021c226f41dae622e1aa4d367f725a6c901f89bb6af566797f565a1b74a5e1fbce886c4e927d35e3f98f4f8a99727c463d343dda35a5813b4633f171f1be2dd8ca71297fb764e9da1118c0cfc169ae41d9938028973d7e32258d5e889f605780b7cf9f1b7fb96a68ed7942c6b872dc0360d94e2f7f2b0d722fa32c5ada3f3dac30f49f20b83f5ba4a8b30273c66d7aa81aa64368c1e0f501a60ae1d5e4c7854354cba14e20c85f1e93b5d00eba6116f817278e258f0bfaa51b9a5f198fad9fac364e1bfb5f842516bd1aad80c66e01489413956b780e31340eafad0779068fb73bb248031ff2886547a02ac61e72399a6a54383c85b35430dbbce335140d87c5e7bc29695c1191606022106673724ec7c6a882ec135c11da4058867d5e1bb6b13b032aa5baa8a86ce393018b33180ab77be7736faeee31d444d5c47a543dca4ebb6ecb0174a70eeeb3a6f09895728a0d90b6c057adaf38023bac6d04622d4000d1d465ea69bb34f0565df56d25725cbd3e27b7bff138cc80a7b0918a4a90dd6d9e59cc7c07748e8aebc225ca331ee475f5122058df25410950be68307d685ae74274544a8d37924c011041f8dd725cc37d666d591027f0fba3feb26e2c8fd6273204e24699dbb5ac7187fe102258b61598f721d043ef4132b24083ebd226a040fbc51f9880df1f00bc324e5beb5a861c9e6c2e97cc1e636e6ba08171a957f123fadbb224ab031ea8c155ffa1747282c5904be764d3944550666ea629e410111d65db48353562b263d157f48eb9723b1913acc7db923db09705e59c15a3d71378a4b3649398fe7b4579acf21ed52ad1d1212f0c7c681f58b9396e1453023f0fba70f0d981f240fff445fa8cf591722dd23ca99455fbc14d091ca93605c357e2b041f067644c1289de3122cc6d8a72785dc950ae3e257ecfa3d10291aaa91a8ade95389b4da216046ff58f7b62ed1d60748c7dce55533f38b9fb7a033d6accde16c66624f1c40d4b4f6c47633b6e6490ea018fbb5041ba64c281db0fbb9a5964ca4c1cdf685e09e6a59cc04aa4da4cee601227462936990900e4ebd96bbdfe5f129053b7e8794adc031ae5eab151387766e09f2be92e065cf62ce099754f6fe32f31edd472799c5a4ba4adaf1d0772bb16e7d8a8dda7ece99904a761c18f092cb7ac3a568578657094118d9a384e06ff153be38c32e40cdb4d4052ccd154f0a210f16bf52f1584beb84b5d4f75b072c906349c99043d9ee54b2e31fecb27b83476b889563214f4c469f7554a9309724364034778eec3e90009e5464c51819bceb14b19401c851c9ad3efc82b136e248a693e08d6cc4acd91213de087abbca991d711f81ccbe9b465f6210b9442ed7238e245dae3cf5cda73a3746491a9534d9156f781cbe02bf0febe132770ad2172b193a145c857d4112efb1c21abdf9a3faca9e37c39eea70a529ad7ca83219f72a10341b75482bf193f60f2f278a266410276014c4fcc3b1c658cff4ff1d065257d54b14f5a4fa768516b6e434efe01c2b864e657b35ade5bb22441933f382172d3d0c213a6bb00c613b74cea37dde0e019fa9b4b4213b1fbe6c86eb1e998c7728875fdd4abbe1b0c5a9691af6eb2a3b1c576863e0f6325235d3aedd87ec8fe2c7d3167d4f918807a8d5d8405cefe6a1fb15dc6854b7197b4ec1ae7389095ae72758109cf4625bf10b71b68e0844cbc7df05d4a2f86d35cb7fbb2788e113dd37222d40f2b1a0e30324d965beac131272b19ba907d99c98f670f35085ec75e2872ee158735520be20df9a7911b73c1b3dc817790530f239a87e1ec56c5902900726bed0b710631ec08ffe787ac298b7dbedb8b9ba1eb80f09029f56aa6e53cd44786498c64168a581e7c1f3be8cdb02768e2bb47a8e4c94161151727d234d2b76397a3539d526e83748c379f75d52ff2eca6e5c6cedfced7011a81fcbef12a415a3e95e4f84c029b16d40d0ebba09dceafe0a38d678de179e6032d76494acc94729fc060e4c92b3d357cbf0f578d450925bc5675c1366f38c7bba7edf77aa5b6725688f2d51fc84d5bf21561045c3dce9807cae6024dd245186f5a8cc56916236838f5ce54e6214e155082615eb6608d7437fb823b8e6028c13bad11501e43b772ede072e2719e3871df75fcbab233ce223b95a01ed1703dc6fc08ac71a0fc481a94e9add56e4c91aced5238b13b4964bf771583a650495090503aefb0a282c87212b6cf94b5d25d72a5a4d7c04f3ba25bcc15014929c814ddaa6f96800683d90adfb193eb72dc8b652709f0b205a51a11a862a91e57e56324ebee19f117361b58636e93e0a7ca7df40361109a0c67775deb74ae40e84cf7b0be39bcf73f7e3f729d9bd8b9299c412e2100c5881d0cb882c7990bdc6dfe73868e78e19ff7873d72a75bab413b8d521b781ad33862b94ac469ba219df89c904a543cb0b327d956727eea6b8944b467fe0b9c1663cfb21708f6c034dc50806b4548ac679449dd2572a182b6ad4614811bbad681854cb813d4bba7214d286148cbbbead615fbba967206e170996c992f1a4cf58bc2933a3596971bdf297621bffa8a7ceaa0e7661072d80b95b40319ea7d76322d16967f1adb696e5b2b2f553bf67efa004ce4b02a69e8c140a7e1894a02a4157a8646c3bbd4a3f67c8347b549456ac6bacb5e1ad672a72e6bbf1dced2e385ee30e724f48e3fff625464cebd9aff2fea0d8e85d6d372ada03cdff139b38ade7e7db6122e24fddc945f3da3bde9ed72aab207a070fb0658360668d3d3607dcbc53daa98cb7a81836bf3d0fa82a99940f36ca4169350378690455450f1914b42f62e7a28aa095e008d33386f54b8367592c7dca875387205cbe9191ef3568c1a02827c1d5e8d418063bb432b7f41d37db33fa998537527e1c72b74e2afdeb800acddcd790827184fa61377205b3160ab4d23abf36c7872f30ea3fc0c3365a10126370a4f72e346b674293023cb4298f52b70902aa3e47210da5477a0f2ec9eae5b9db99e1e984bed32d65199c4fdd6ba26e4e474d82b72a9ab4f3aec90d87f544c80acac491e6523b0d0c2a817954748711495a9cab909fb6c690e0a1bbb1e2d4ccca0913b7cde9d1965bfc5ce2eb842ffbb21f24c495c6711b28ba75cba2ec427726ae160cb2c94bbd4daf55fdb92a6e0ea01c8ade572eff3f1d187624e5c0800a6d759005e70b4cb1ca9f5c2e0a4bc47a8878212640a9874e50ceae70834780a83ddf84f3f5f770f6de2b6a0aff2053b4a2cb594d572ad91bd0ebbce4167e0021b9556210e67fcce4e9390a9dbaa65ac4fa4d9083672199738811768f3ab151baad5ed92da7e57e4b4cdecd5ac2a481cd63de71bcf7243238969b586f2e17851de00304d99e86f1ac3c694a9ab2dcca07b174a041a726e721e2b285abd77ec0236ee73fbc43f5ff273622dc0a4d4cb977d6758c1e9546313537f9b744086eec7522dd9e9d3f3c371f10e95b74d1acf670b78d1abc7720e26f83bf2d903912302680ad469cd3b8b5ae1801dc0e9f58b6709cd02f6047282b49461d1afad5db6dbae6da36ce5ddde6260c56043feb1fdb3d379d4e71a523564ad62680cc737b406df0bc116679cb3846154fd26179b2f2d016b44563f5b1dde5b132a44bbb4a5b8f1f130e6436301dbf7d272c3d137c0bef9686476d1721e1f2b52f88dfbcb68bb13ba252ece31dc38de592dacb66caf215e9c08139e0be8cbef53620f642656d6a8da4cef2d1406b03c183996b3760ee375a82f62c90bc8f7984d6cec9e6e5034eceb0eb3ffcd7cc4c839340c1c56b942fb082207c272079ae22444db6397f2f9b5ee352cf382350a73cc1a74a5051fdc1a7efa64ac72b6d1ab0bf33dba5ef1a8506fbb5e3de37c630a6ee59b79efabda4de557cd524842e39d210689889652ace74e7aab92ced8dd22228ddf34c311343d6a365a8b321f2513fad61207ce2effa2ed74ff208ec6b62b5294fef215377ebb1fe0453072fb760340172234c9aa5fcef46e5b428a74ca9e4b8be6aa2ce6d1cf56c029541bcfec7add09601fab4f5052ccf35aa1485d1fbf863555ddddaaf24f22ab55be075f12008b458120ff4f724abcebd3028560d97426bc23aad43f50f3b7d6045d68477b6a8cf3f1229ab311aacfcb92c36a16aec185be3bb1cccf6a17f0178380723c49d9b22db52de13dedf70675fefec1b0640b6e596abac6e8869dc1ddd69e72286c340d3c3721ab2706e41a30cdaa129aa18f61870d78157240d97f969d3b7269567bdc82f96d5f421389eefd7ec5e12bf2fbe7c8fa884ce825e4e518e59316f27069fc2dc33d4093b44e37e587bfbbf8cac8343c56dbd5793d1a1590cbf372a9734a1a59c0ff2d2088b49988fa58e17b8a62132a1d1884ecadc679169044139b45a224b7d96cae8965757be9666138300ec57dca3da45ba0ae31b4a1dc0f72c296932d1cdb2cef4c324fd083b63b751dcb391534333d5e09950655ac561472e6db43d5ec0d67bdd8ddc6017242d5d21beebca8aa6f7e84113a7eaa3358ca72bcd3d667fd72eb36b6d1177203ae159666c486addaf03e4b3111a025ce816c725c58cc5f12e672665190516ba8770f9b5f093fc198199ee3ff07784bf04e8772b973326567c9f0970c525be4929dade58f250d2dd243382384283809472efa0f9c0ea12e77df67ad7dd6307ac18722cf64525489f6bc33393fb1ee351b994d722368155a5c5a28045dc018c926a929348029549eb981da287fe46b372885e272edc582fe5890e6a532c42e8fd76bb94e05c3ca78f719d1335998764b491b642833a64d2842031bc376bc4204a091f8ba8ec03bdec4d7e162eb6d0bfe6330d0724a94928c27b3feb46e59cb1bf69b0893f96d0d268b57133e4b7e0f5a118810720b0f26180165b1ed55f75f3b7749af420205ac11dcd23860f5b5eb47f7bfdc72558b592d5100335a3795af707160e2085cb519361874a40cd8508e10963f7672fef7f681dc982014f35dc408aab17696a8604496b07cc300950e712517f0fc7265929fd64a0cd66e819bf1a03e986e8f901aab8cce5a4aa328bb9a77ec777472006e850bd877c782afa12e4c20963006a41669c3f53016226de50259a4447921a79c4e7cb801936cad7d0715c5b7cb73dc204220a920e69807cf4bae26a65e703f15ca32d0d1921671e6e32ae6e15ed7b5b7b9c14be24f6b0d4eb441542aa772500851b2d81bea27c0d56d03858517c9892569d654a2b651bfdda0883b451f72d16230b5c3cb46488ece29f3c6a3d9db16e601c4f9f68cc7a79941d1cbd60572de66f1a0056f8462a5389c718ea410a924592742b05acb51b057ed6e182b5f723b216ab9dc44426d28c9950d6458736186e90588424af0f6eb4ef0353b5468720290911b81d4663fb566a95e66e93ff485ab18ecc7f0b25b001f30e0e56e9201a7e4f27a1fe7a068301c5cc852d0dd0ec2abfdf737df3e62f75a8ed337e6d1726ec594f7c85f0380513a943a5c06358be3a34b35d8df3a13e51bca8690bf31417cccc14f4eed67b28a4d82d58ea7b6201bf9925648eedb87db62d8ed65ab7e4e38a8b79be577c304e77042526f74dd594e9c7534f9bfad8ba82cc6417002ea4e8dbf0fe46928a3f1118a628619520006447ac064b7853d2896541ea65ad84a72429d00bfaa847028160a2f7abb05e8f2fa8a3a51e45403a77d7b528bf451526bb5c6aec7a4203ab0c4868fc497bd22d038b162d21e10a3647314a85a772e04654f21a245c82ce0b9dde5470196226f26ddaa6c01a2996ef5d4be02cad2442472f5b9cab48f5a8c70012c6b3f438388d54b41e110cba6fc385ea837b676455a71c6eb5004657887a472e702546b9ef5859b3d9cefaa7edd69e03aa6d53699383a4c87bbd1cf8e7ae755e401c83a7a9897e894e9058f3295890e17e7ab97b8030588386eaa076283540b5a0e6ffb18d04cdd276f002d20e46067606a7fd63ee562eb1153bd83dd15f0f8b1b12beaa2adb1750c4a3083ab73d7b4d7d2a1be6309725be50fe180c00d448687238e6572bbedcc9d6d6df671c44c34585742c6f6f1659f0e0953150dbf115336c4e0b2adc1a433a224cc4b45f2417865183796ece57287c0447c120e7faa4d22981dab3e7a497ca0a230e1c08bb3bfe69ad0155ba172a201df5ab0515d89d2022926a1d5ad92b902d865103ae5f396a71ec68cea8072763815b07faee7ee334d9a21961fc4f89d86a19f3dd520cf90bd05f08f979355f6538b1745e38915561b080230a4d681a6484b9c630148315ac0c63189bfcc72cea98d0c3486d890f13e6099a1244214d5d13c14eaefe13246a1d0e6333ee672de363fd4e618b1dd4dd31d24133b0b6bd3f3938b8344835182ee643cf18d7c43c641a3242599587a567ac4e0b940cff0d2c70131877116d6a891ca71493ea672757d5a40fb76ebc0d93b78c6b8e37f81d6e69dcc971895ecd719c7608471cd3d16861a0e2c2d132b506572a133da73a2355ec48ba9c60df93c9552aeb03861722090cd8711664dcbc44188efee096e1949af6589e20053ef8e305aacaa98c272aef5e3f9422535534f4573239c0d3f5b78544b405033b2a675fee5fc2092f87265ee0c6b786d59091414478f2035d49654d1d710a249999c2bde32b7110264726e7003a987835e0780250ce3bc28d42e4268611121b28a88fae1f7930617d372181bcfe5ae7cff20e51b993305927a3958b603cea76cad6b435c947ad518ab72f45a7f3834501cac71c563dacc21580c6ca4e3cc41682a789ae329795c3ecd1df0361a0ab0d8d233b8f2c981c9bd46462575fae52458ee9e33a9cbc3e7b9f14d883c5bacbc1073fc29805222e5775ede6d63f8ce2602a2f9d6c103d576fa6e72991a361e3d7cacea8e3d5466b549c2d833050dad2d6656bd9970935f55024d72b8b553c69a2ced48ada3ec8513841bb43567a6e1c68c7d0eefdcb933e1797872fe327fd7e915a76b0209c97a96e846a360d094654e7683f792b1dd0b2e9a67726774f77e20b8c88f0749de50801c6966ee30f0f4999a9bfa0ec34cf0d9492418b3bb54ca9eff7ab80bc5b2996e1d92d387d0d232a722364b2148c41b528bac721d6eb62860ceb002a38803d2e6037498d93b29e954e3a9e5458bb705740f4f720cf0bb1bef357178dead7b8cb79941169d5a324fca37b248418408509b4875724c7e02975208f98aafe93f8252c0e7ec0c53d651918ab956dbd742cedbc93872cabc02be9e947cecf0ea260901b82e590900534bb69bd36e31d226c6ab1b881ed12ac0c02428898ec4794891881f6e6d54f58fb506177c05e4bfa9a861c5ad72c769782a1560b048700ddc774eba8c21c90496ff85284942e31e604ad79ecb0cdca3c3deda9ac0340fe129d7c8f186964bdf23494e8a8337bcf821cb94e16c1e57ccb34e8fd08dd3f0ee03a1bfe397753c029907598c4eaca515ba12546cb972079bbcabdc6e9c1cb910fa418c7e184e2e495772282e5c0cd9e3fff5490b737256bc197342015f4c4786aca6e00cde1e6fbd423720a28fa73d0a92b204280772783dbe5b5b79ce8485dd88dcc39e3243d78f3cf22f3461bb6d1bb85e3e17c22f73b1eaa4917b882b1aa40249dfc59f358a3280ddbbd8a4728ac0db258f551a72f6d52e7364525704948f821616add2454766823c1250abf312a5f2f8a9c846725312f09ca84a33faeb458eb74883d0ba08ac56ac1b0d8d8b33ffc4db457858720a1b2a532cae1c28197981f499ca802c37ab23bcbddca1df34cf7154b04be272c4f7e658c38360ccec242f4789ed5132be72f6b3640a7b8a126fcd0f6eed8d659a473a029077a055e7caa42fb92250ada36468285e3d01965dba853c43fb7f72fd4544221ce6123385c0925a0d992fab5e3b89d6771c5d57ca76b4b849f25f7207a385fa93289d5d925062db5578b5c8110754b3a20fbf1f4f20d2ce5843b0724d21a8bd2a6aaab38a869d652b641f0d65208824a072ffb6b8bdcd01aaf76e72a84dc99e1d70b5e39d3b3d7b48a6551d67522344f95b7066dc6afd5a5cb9ef4a583084ed0170a2b5d7819028bb4e3df1ee714822fd2349aaf2d62796612b8d728890b88c06dca110beb715abc1499488f6aa2b284cec700daad0dc60140a0a72afee23ea2f81abbf52dcb6574ff070456f02f82cea57583570aa69b8076bc87224da9badfbbb4f91fe57a8303e17da0139ab6a695681b5d0cfa0b10465ae7f19e085681a3ad0e3bc52af5969d271bf0e1f88c0c6abd053cfbb33621c549c6b72df6258090bef29b746fa454b9e3777e4cd35ffb4016a49f1baec6bd05f99cc3a0f5f6b9d8fac43e91383d7e6f7f29968db7ab1b91fe495a20dc7b874ac800f237e7171892169d49bfa61db143b7e4c5bc98be0423b87676eaab94e609bd1677243ad8827dd1e215e15f88484a5f705157de68063020f5d1a9dae77f91e91b472294dd5e4256ec1e2be70dc4e7c059e45887636098339a9ebf1318e2d717d7767ae3f2bb03c6a90e69b4530da728d8765826e3a4bf1c94c5fa6e7444550bb4a3ce04ade9ae78c60088442538f89e31d791966abdd3b59cfd0ca60777451ae041d40a3cc3afe8af87e515f853b00cac5411d19b04326bbd9ec787e46f5cf843b7280c8764e3e64a77a348295cbf4c295c1a339d9ccf653a39a8572901690ec4472f6dd4e08f36942f53c4dc76fc1440c2d9f8515ad4c3bf3c2d63309a5c96e71405a1ec9a5fb809ce343ca1808815c4ac74c6e7a6dacb9e561795913e92f55d872bfc9670470f18920d04495c60526e3691241cb8c1327738c7814f477af1c8135ad4fc38b38f4f2415cad79d82ad05deb06b83cc03641d90003bed9823b10572f7715c422ffbfe362e36ba0809d85b143e7672f58483fdc17ab59e414d796bd3b99f73bb14b1bcf7d6048849ce4f9f13e7a52f1a589fab5782b0fa0ddd46cb519250e60aa50ea2a695cf0c5a07448a246737b01ba33f8bb4fb4de9b78b9c7b05ac26d9c128829f4e48521bcc509ba5ea01929116c2c0c92c36d9a4925e196af06e58bbe33dc9cb1c03cf8a026fe7be51d593390baa98d96aeaec8fc679542db2f506d2933cd396ac21ecb4cc8bc43a1decc14db6340de595a510dc62d83624272c81deaacd07cae3941bcd4344402ec9ca50a725f0a55f199131cb14568eae94da3c9f7e0995143f9afcb11289e3d3607d760707d6208f8474cd0268c0a3b76632fb713f4d1553ae2990cade2da885a3d1c546495c19584224249f8993ee20772149b9852613fb10ad47133d6b668f454ac8de1367c219e77162c85b503a7536eb94d3865f7e0f802a843ea65d50c466cdc8a19552fb9cb13c5257bb68a0e6b56afeb372708c4b3ebe7cd0c308acf452bbce073abd0d34518fac54cc25860eb72c100dc78c2f1d668851d1863159b37dca6856d546534a42b48679b302516d872ce1257da8377e8958480e4183e37243848092079eef352c89bb26253bc0fca72dfba8c26d662b6f00f524d24f56c3164a6a5cbf666dffbe07719a462ef867872bd06435488daff16da17e7e8249be60900a1bc0eec847a1a2f1d6400eeb75c5c75a1365502be6d438fb90cd681c4ddbd5e228b13a92bc3e8cf4ee221908318027e84938443b17adcdc58a02b68123baca6c95b0e6fcba0c774687888f1ac5272f1258e2b980c3844e959288db9bf42ca1645f72f84b1f984f375b637f14d8572fcd3ba9b9fd6a4b844d276146ee83a124255f27b4360a2a662de1f34fc5f6536f3b4517b8f89fa01c9602d2aa05ca0c30498f91e6d4a711f88fc9c7969bd1372c8fb0932da9e395ae1a1acb315c75ec38ab32050603ce827c45c581240bd2f686734e473563bcda413358a6342050f5dd937ad8c9f2bde9b7c8498a552a6200f70c289e52bae51856da0fa612027e769cf6d73afd214f2ce12fb49a80fdbd6727d00130286a17ffbbe399af406420c637271693cdac25b0edc8b18ffb4776a724b185933a1dc16e0c9840d44073a83eee58769aeb92d37fd5e36ae3453e6a572f016543bac8049deecb9d9fe80944ba439e0ff96888a402cc659ddaaf77c9d72fecb6e880c9a972b85ec221802b0d5803f621cc28b7f9a33cb18f1db3531a17201e8e310272d95e31d088380f86345943487f37dfb6440f0a0090c3ad2dc3f7219641e8a34e02fca2e91d04136980dfd64709212b533762e9e369fb589f9ae729d806985997deae25133c1850c4861ab8e89c2af0604667643fbe67e3bf1ad37a159b90269f3723886f5d2c81966924b9033d74839dd7a64d0f8904f36ecc372dce2dbfef7ef67828efe6e0698d13d33e1bd0f88f193d1bdb407133e3bc1cd284119dd63fb2ce2afaf902b0e72d686a85a39094d86547b7245cfad3e9038743a64966279530b6b83fffbe16235a0c370fc7447840bb7a0c0a75079737eebfa525b2dd4d0043f3799692e4169ff063b6317eed447ba4dd241096a5b853d5c8272514d841b4ceade2b5e9e00a8db80a2d559e1192fecceafef4a7a54da732dfc728c8ac40a8463ab72df47ef44cd7a4498a0a642832adce152251215b25502960bbed6321a86452099a05eb4c6d05a4c451c0119f0a95f07badb2abba30dcbb172243a1bc9ffe8fb0d7b0d4eec20182fde1d696090321d31121cdb46be020ad30ba70b0f8cdd0bfda9a86e2f249a489ee14e1797ec36914eb62d4ceb947dd7577266179cc2dca292c8cf4759a5df39dae5270d694951a803ba798a951712aaba6affe303e74d86dcffe80079f32fefd45bf6517cefe25ecc5c9140175c6dd05672b61d3e598d2a9263cee88ea6247f0a3fe4006604c897cd0b70951ff419addc72c9b084136b4b867adb5e16a13262ce13c9861651e195fdc56f0e8939e02e2a4123016e078368252e1e225049988b510286132676f9d370a4b17245f94eda44521cc775c860a61be7a6bcc3079a98f29ea1322af0523e372e524d7a053224067214dee5d05eb9eb07bea9886d456e5f139c97f9b4377685c21bbbfa48a1aac672b8e3a7ba24aaf6f76128c1e73eab7366b08f7036969b580b94b4d4285fbc96480afaa0f4eab29c88c25be0203fded65308793225b5b03560e121e8b048e22239031c964f3020616d7bce871ea51787868755d988cc24996fceb274f391568872c745442948bc58fad7203c67e804cc58302b97c55c8c7b44a8b7798d264acc42b75ec67804e5527cefa352d658d22df874c6088cfe6cadacbcf37e021e09a9729bb30bf1a4d20eb30dff6c76d01a08220e9ff1eb1a66bc615b0b102a21a28172d8c66fefbf7f70484883fa8e1862b326ad171af9693e47cea697190cffe80f72f76626a73e51fac6587d2cfdbf91c3560e71a0bd47036fae035b76d0e096c36d24df3b2deb7a234f5d84b9b93017442ee326258eadfa38ffc3ce35b20cd18f6a3a9b3db12fdbac5675c260b81c51d5fe78f81d36ee8a975f14cb71f4f9227e7284dd4255d5013298efef0368b9fe8437eabc03312a3040660fb64e0cc0caa872eb2d3044761f5460b94eddf774d56e7dc26abc4f671b0a0c0ef03f1be433ce72f1148132388c94d295f69f0e0f74cddd151ba1025f5e59eba70e4a77eecd5b67720436d7c95411f6c37d45f7052a0dc009429a50b9f7c792e06407e29b51ac721dd5409233434a6817d913405db3816bb7c61aac9884a692088322348fa2b47258bed1b8d3d60447e8011130e7ec988bcd60829951cb3b3ebc54cde975bfc3721c63af36c4ef5bccd6457a5d7f91580a33bc7f2b23f1d3c1046767cf98e894721201187d0d4e35c0a2ea8d55003a19f3333e10c41ef8e4cb2d9a69cff48f150c209f53ccfa5e34560d7df209482ff607172ce231c4d138c98a6e40170f068960f168b2db1086892e50c1e279e4b87796928652bfabb9981d3c562fffb1c51c72a0af7f135564cca4e15c0b42d33f6726496481fb265277f4538b05bf987742725711c2419ef0b3d7a5f89065d5757b5741c862fb76136d6a08c2146f55f999606a0e6cd2dbcfb351b16f99f069ec3d61bc5f02f2d589b2b57835810d3ada4b3afd4e1deea0b9e30b799f1d0ec47b41f64da587c8b4a0ca1d9a386974ba2f9448d8eb5ef882a0d19bb33ac0b41363c6891ed12384623e8c6068aff50e4daa9e72df21ed9b1d38d33dac37449219de3d0c9148927fcd6a3be0e1442289f2984320dbe6257c04c342bca386d6f911583499132b71c92260f697e6598e4b03443e725d8be95c5090a9e04a094e7d88d62cae3d583c060970d865f365acee1bee96729928897d924393fbca8a03f07c8bffdd0773c5962b517c0f295d072a1336c87207d83be68c8372f6a14e256ba08fc3e9e45aa2554fa0f98a90d551c514301f72c4ee6ff77b4fdf5884552a48baad3e89d2b3255775f0e513f006dff7db886f0d2b7288417e09151bbef73c79204ed2b1364b16dc56ad98bb466a6bf528a1310fbecc9babd41baa8868d5315dd4c1492b3789db7ef71ed0be650d7f75e06ce0728161c5263fcff751538f8a465ef27541936438908d4cce89165574a77870dc723d93f76d9f5261c17a57fe3f27208e76ae780687b4dadbca0f7d8ab0bca8e272559e798e43308844138dc921987c7d4a7e81cfd9b8c38ca887a1910ff1d8212bdcea510e37107eae464fe66179654813d2cc934dc8ba2d4a933c731aff578e7230aba7f468fa76700e9cbf46fc1d1a741aee950b56f431fa0fd9cac848048f722f676d56223165ed2de485c9458cf5989743dd637111ebd8220378caafbddc7151bedc1bcfd902cdba3801efef8c0d0ab22e92cb798e6a1d2180fc11a02ea072dfecb3cb2868af161b08c669d3658bc91108024999058f5e2a147db02a87e672906992cda6d26ee54ff7b80d4d2b8e83db82bab86b765e0be53d936ddb9c2f720b068e01b894986d541d6556a17cd8f03289f7b668dc993e84d79b08ec86af72cf44a43174462ff2cbe42b03047f3c3e2143b7da7f9d143a32591fb5fef5971559cd6c78c81fcfdf8c418e286add004d3f049e24fbbb8218fcc139fa428e677237a062ce79eff9d548405de55d983680c9b6c1b51764ec4c0e4ea60a840a2972c1228279c06e55b63d6116e47c8520f7a9fe541375e979e1a264b2241904074d3384171a24a8106bbcc80c0a68660ef7f72db932729e7cd2a598e0025665ed7254d4287d6874276f41fd9f0495153a526b640ade40b5bbeaa5c5d055b81c8f72ef51ad09ad04399f1526343918ee29f67ee0ba626be520890c413e4b4a2a54726ffae659639c37b40b6588361b0a103fc43522fb791f905c81d46fbb9d129972898c37007812435703642c34888ea2e755ecae5d27e1954f3d88b974b7c7b83e1bb30a2599e58e3f54a6cfb526c7d2ee8b89fe938637bd16326b5971cd65d77204bb8a63eccf9c1f48f243414de0b14cb360eec893fc3a536c6b253336176b4e33ad17fc0dfa31599e7c951fa3a284143e8b69d405d0ca08db33c57301380c723ebab062923c2dea1842e5c6e9d732934ecb8ed4e53adf6efcbe5705ebd1135515d2a909d5ea9ba729c8ef999cec8cfa125e899b58521d06c69ad3bac0975972d947095620a94043185ef57f53cd97f449cbc91ef0c97558004b3324b495aa72ff4525e4358f3066b965da93cfcddef8fc8bd188b1e74e00b0d1ead9d9194a722bc7fbc3c84df421c1b8a115f27709698873d548d9beca76b7d0027da7784d514eb66afd20bb3567bb8eb4fb978ecf05f6d43a606e8a19771af808d89a948272547ab917968f566258dcfceb304aea06b9059e24a2a4433ae5f26d7b62ad7f72bf5cad300b46730e860a61dba1f38347e9c3821f68ec0321ffc1ca843559877269eb622cbe6f6be19bee5ca71a9fdf0e23ae66aaa2111b3e09d509c780761c7283f61bd23d6a9c7f551490a41a134b462ce7c6fdca5db0b8fafbe5c0d773a772f1229f2efad8dc51abf87dbc02a92c92ed6bae499a3da9ee0d2aa7acc31bba7275758dcec96d7ec91f488ecc7d94c83e6ad937e62b45929d846c157aaec783723024333cab03e58b4331321580f20c4c9b5225cdec7a38af1ee5854afd425c72481f4c9ddc20046609d75cf636586cc87a35f2b43baa7f736cb16c098d63e663c196e71410bcf3f6d6482ace7be7aeba1b71b9457011d3a3ea12f9dded03f12b32d0f10fbdeda3c98eec859e0b8c541b741f66a8cdd7493cd16de224eb28b658e7efdf364b735e42313df7751f5e57739d1e705bb6e7d5630ff5978a5e50df483d22608828601ad4b110e97763c4b91b93080f23375db8b7ca93357f43593222ba238ae20505a469256105723bdaae1fb94aa54ce30349eeccbfb952e1b4e272e006c2b1e23d86367570d76f822609251ff96869a2d99bb6bf575f3793708172c958a2d70e40719c47e61206667dd377630be90bf2b56119d06d9aad591fce479215544c48ce6538751aeb39b289d8e4b8eaf7358237e00ab85c93f8fa0f9f46c44b48ec3337b7229e8b347cf77e27dbe57a9a720dc16271fb06326be85b3d08b1eff2c65cca76cdb955cee44c18aa918c6e57eb132bfddf3886bda8c70bd727b55ba5af87d7cf26c2040a0f85875e2a8a2c6265a66aff1413ee2853f6b61417c6f9d57ea96d76f87638ffce7097d24d95a4120b64fba321accee7a2853d0d72b2b31873505b9bade59e1548d58c9e5b972aa7b408002d3a009da90fefdacd3069f15edbcb51c18667ac61c82ea093f43e1fe21e22bb728a11fd2dc296b3375ee96e10afeb0e160bf84d8eb7da8a1d0b8f26cfd2a187e5d090efa599a338a2162d5f28c9f4a62206a8bbd538a1256346b599a02d07c57b746c1ee7ca47c7b531ef3c71de083daa768ebf2d1728256caa55601cd36249b9de385c118f140d2b43e0690fd634825b36bc3f3f49bda4f463fa114c2846cc114a936540338c5155726475123ad7a183ab09083033af69ba0e015d67039f9b6c14ff895747c77d147265d039ba7f63b2e1e3d56489313441f5ad15945cb4bd4369f39eb72b1928316fe5d6a084b66562722efacc76343e4e16184370b849f730520da417db557e95723a25dfc721bd497bdc5735a34d89c30246f24ba6fa47a24e19b324e09b5c9072c0b03dbf71d1f86798d7573ea5c8ca4fbea90c72b85faf85741bf8c51c97d066b67df7e55d68de6c15a6311bbebdc419ac790926249d22c2c0b04debe0a6a1728e28447e78a095a4c87f95cb32693680b728a2488da2ecd4b331294e39bc3f2fb1bbda902153d5d605928e095e88f91ab6e8a9f40377d161a4c79f1e68972446344060cb3ff0f5ce4d47ac2a297aacd688daed241502d2ddbf718d2175849172a140d05ddeb5dd86a238573e13a6ce31eee32be5247cd07643b80d789bf555721e55b25775b0e1dd88f64ec5febb1743701dbc75f69bdcb369cbc44de6575e1bb5db9ac6a79f809c945d51b672ee3716f664f435b82462e7afef1b773941920bbec2ee96c0da18da5f094f61eda6ed27509fe129e651f4e7ac7883629f0f232b1f40f56ac2c36a9281412a26f42c2f92f9bec1cfb2dc49c562a9492e4586337279b7d252761d1dafedf280165ec888f02b2c0cab1860ece804d47519054554682bda6091eca8d986346afd945fdb4f22608cfd3545881de02b0b81e9fe6d51720d91c2d15e0483852dbed4ef8ff077cab17da0d8184b99ae19809c0ca94aa172603c53ec9e7c15f0cc157eaae6ff9c01299620bd9d3df086045db27e8326e549432787c6e56802444c65c6883dd990e174c4b7c51027d822a837d2b20da337723c74622e79a6db369b4ce5118a920a7dfb39e878de4c12b993d1a7376b137072511c41b6858272a49eb7a8c2faec30bbbf66189f2e49b7041aa38b7f5d2cff430fde5d4405b944184e580ef2aaa8afd67118a7d1a4030dd306739dcb3f404428f5ccc5f55f1260e2b210146b3121c2d22354cf2fbea979e26712f5cc273f0e723f05847d37d3d0ae9965f29a95286b2793a39c062df2be36b7cda406a9b00e72bc83e8ddd87786a875432cd5fbb4097828bac742b221b14f6ef8e8ad552f8a56a7f04587cb0cbbed658bca0193c42dc87b6eec86710569688e8acf52e7e97272615dca6d43e3f2713f8560d63807694c49cb3bcf7ed60a99486d3f3c430e5a720f9baaeb838fc51a5c58a6fddf5b49842e94841d37d5fa7f1f6292bdf5777d488e9ca66e3f2603a53bc672d960b8a27b4af9c60ef1aa9fa8770cbd42c4019d72b4fced87db11c188fdf595419d3c2d78241389fa12d4a436798d6964847e8772d9eec3a7b6e634369445ea953e90aa1ed007ea5ab173a55abeb31db7fc45df0b2f323e9fdf4eb427e05e6c11f2f4de861ef466f1ad2036a501bb9a183d24eb2fb87ac577c37eb59e8b9cd1ed0d28854ae8ed89ab307d8237a3fe4fda06036c72f0ffb7251cfec47342f1044e46f8db4a679fc8afa838fefc6ff2d6fc65e3ac565f8d0540d2d8012ec109b3b34c3906df16dd88f0466d91202e7c3bedac788b118f6a78bcd8a241c522240a24e6cd62d350013f69a59a7023ba62e3ea752df569177b2265fd110feb9ac0ada7ffb2105d2670adfe8e777d291db38ded74f4331813caa82d82adc323f3689c00cbc18997416c6c4d2ac3ffbb9d5b13dce8883f7248bf92553cdb37dba718cb757c276a82526abeaf8b4bfb723482ae6246d36772ad50e41f31b7afaaa8aacfd8d52811a69fbced40f8bc560fb365ed0361ae1a0fc796d461a95da11884124a21b6343c2a83f5629feffc0d60b56d184ac5fdd62a8e27287d44b3ff23cb2473577238944d961e62e8e0e50f9f3ee66c93f750157231dc0d0556104281c1f5063addc77eca08f29d1b481429f1dde231c61f3efb295a59dc3545e9ad23537bddc3dceeacb65527dd6840793dbb542a044a5295151e9c6f596164baf69ee4fc43b9966bcf1e118ffce06f9346a6d072dd6aee5a833572d4941e699f5b4199731dcc1663cd5750c4be5c86ede4122c3c9956b8918569cf4c4fd3f55525ada14a6e0bb1dfcb916f1f49f4dddc65a7256a4f3357b54472c5731e704d35750a082b1464f6fef2625012812b71ec6e9219a02f6d744d034c6dafbb868652e1f96b2a88534cba31aaf44509d77259dce720de9c22e8fc642b6dea2faeb9ee70098747db549655f8c57ef3e1b137e2be64cbfdcf7781aa8372f4e39d99f10d529a6694d508694ee16e26aadcfa531a11c2f4b4a096422fc34cc3eed2658fae1c22c595804f120c43e985b98939d693febdd99f60554f4f16117bb41a246937191c40e067f08ea53c49a6c5935a0ee9ccb00b1e16de1153b3722b53006deb27764677d080abee968d87e776863144aab94b53f0d9d0596d5c6b7260cbc0913ceb46e7f853298eba7ea37e6ba6b16d9e6333265d8c166066e9729ad70ee7c1274bb9f098efe35df562e709d1190a8628721119572906f6208014773338861b62be005ce0d0a2b3bff5ea29361dc9c1b8eb35e467401c3a50a1720c7d3553a3cfb9e77ccb5faa141fc4a0c7c9805299818713800b0b1e35237272e05327f13b8f5b981fd3661d56ee6f2466aa49793a89782dd70c6cd0c4bade0e4241af7edcf18f48415ab73aea3f26fa478bdf9ee914033806fdc17a3c5b5172d8fd01b01fa37b91e9f0f6d5415af6e40b4e09a3405e3420a5b8ea792d5c0d1986b53341b3ef51281aeaba101dd4152ca9dc88e95cd61d16777cdf198c549e21203759ef86a2884e23ed6d1b8ef2871c5b2cbf7d768ec23099f523f7cd54417235c67420e4419481388778dfb60615f90ee22065f1bf66d9fc118836ad260c72923ef6ce9eae77dc76fd0897586bd14dad584ecd6102ec8f006e99717aa4296d43c4375a975fcb7363ce0c5c1a5638548a3b573fb42cabe30aee739b2b69031d0d7244ed391442031fb5cefee7aea7c893a0a37dd809ca08c398a59a76aa8872161b5d8055e1efa46cd30da8c2414170242cf59b6dd613a3909ddb32f674e072d79c16175d42cd5703dfda21353579736bda23ec662230db24a2b73db7743c724a3d06f634f21eb97b0adce759f218c28a677862515ceb7e5a7bd64148705a146ea2b8c5f7e69c1eb3929b1d72a1e18fa2c1a49d2da133e65c1aff6e9dfd30720e0efe060caa5bef01c61e6a03d013fc8e442fc71922ab5f45f5aa4f4d58df3edc98becf229819cc49ee8236e417bd737550191a3397c76b5cbf5982f4dcc14c261605b44b400ebd770eb623ea107911c34c79a736474415ab2a4895da59b272aa2c6343ce54a9a3afc10ac360915b5325d80347b98c2a00997a2eeeac64d0724b23e2bccf9a725a2d50268a5c7800aa732666a9220a8745578796bdb95a3e72bb381a8534ed23f51717be9fbea0b2481a0371d42c4f13766b15c90776bffe72e01dea9d627926aad8659dbea77c7f6985f9fc9f439bf247a43efd441f37b17299a7bc9703e07c173956274a2aae56384a9497e88ff780a0cb2cc5028662c572920cf1914e551d89213093b0a59e1d6d11d1a246c959763988f8f943d2d9d732dccc26611b8a52545b2fbbbadf102c362d285b87f759f5522aa9e3ebef255072fdd47c75fb7a0db81d9e298905a237eeeb8e3f98d83fbb3afa3777addab831569dc0a8b9cdb4e785bd9d4385aec569273789ef2c3d467b0954b16acb2dec2a72dffa0add8ba09af194c8be1ac0c9c7739ab9ee718688e64b79cf22105a914137d057835a041723b1631a803ee677ed5d7639a098eabca1bdb7f2a9e6a072d572652cd52218bb80341a029755934ad82a07564a40a4b43ca2631ccd2f731d1051e3fcf5f3fd960af2b3c051f0ab95a50f7cc032071cdb1ba4d8d52898c606c83189de783222ab83ef9a4a3107f95eaa44680dbb3f745447b377c2f0ef102b165ee6e3a3d5850bded36be03d250e49946254e7d6559135f4d1cfb15804a8a3487278b076c1cc212314b617af7a13f642c292abc40da202105fd885cc5ed3bd457209d1c059863e76083da600fe1633302bf743791e31bb23e8b94c6c5b90489154591df30af8d02ba583be9e9669ebdf94c9fe56de9cbcfabb478e30dab764104f704c146e14aaf448c9ac98488851dccd65d04174adbf5bd6ce6ecbf32ac050025b7fa4e1025252bcad4e0d97148a285610e42aed99fa855229a0a74175d40d72781dd9aae43062c220b7b577eb38ef450ba8728e545742468b214e561a8991630a88e20b2221650a27686b039946cbe35fd9d72b59690eb2f6ddb4bed0e00036f69e751fa55e1ed5446188742b96045a1fbfc1640924d75991c1029e188b1d4b2e0390c9c22ba5378e797fde1983b17b1985ce33deb0e9c22a47dc71a3f68a5ae83cd88b9fdf732ef469b3e21c2c3330b7dfcf63230ff93bf46c954d6e23f4423f5a36e39fccd5ff60e07b07431fabf20f76606c9c877e58967c32c1b1655672eab2fd325a230ecc0e851bebaadaab07f590fa28dad51e99450df31398f53872c02ea2bbaddc8f583e98be509762f28218e33043fddd12bdb7e79fa2b989e061310538fda7dc366012ddec9708b105a2c817f797612a13aea37c8b52b25bdf72f2d73cf5858692827421ee1280bc85b3abe6e19186c270f53732b3e3ca019372c65790200652eb8b237cc759ebf9d40e1f074eaa306cefe1bc32ac06031234729bb2275151848288abdf4e4490400affa0cfff55d8e8ea24c66148f0fe3d274d1e4adc5f1788dfc27a2bb38a3c559c6997355f877f3047bb987b129bbb775a72f6e74ba82cddb3a263beb412b0259c2cee0e04487ddbee49aaadd569fe56680443c652d0f1e72fb8350c0806560e12840a8f5d97a94ebf607e4528dcd4f1677260a965df568fb690b23cdb20b7e77955b50dd9e902f2a1c844cf28fa9817457265ea4788d49be04ae69a597fc56f9d85e6a65b9927367f360a5ed0b240240661e9b1f24a9a960cd51d034d878b92ac6984e2044250fefe1f176f8c04603b1e5489ce248939b244d459e4d7186709e84bacfba25e77f13e1105babd1a7d76ad72e159255de2fd935c36a209b78ef151eee6d59d0ab502eb6dd3d43db07e4d6342529b8c5682b27afa4bfdf384e35d49159601c5476bffb0cd339a4368814e037299fdb962753e8c2e58b31f1445fcdb2acac62febd5cee441729bf7004034f9251817e2743b2afcab1f39a4d33c213d5f6609a4fdb381744910308655a1601d726dd90704c9c1048fa40e85aae5327a6ddac4edeb33958ba1ddb03e02430de572a263a6f8015107bb89968778b0a82c9e822d017a3cc5fc385567d1438fa4175f3e90aa865ea5c9d7731f4c8fe7cf69421689f1b3e424a18f8d58c773c33ca472eb7c6c26d00b360b1faf7366bf9ddd51acd6072ad247bee5aa1caf599d186472a0c619563e05a0cc1d0bd50a0c7158832f6d4e6b314d13c11fb9021251117d721431359a3519679428e4d6873e82bfb73cf698cbc1b5501412584cbbe86b337222cb246b59e778b825d7e8b83aa94dd45230d89a911171959aee5e9fb2e6004aefcf653ef92ff0778e160b1e70260370f5b00cee688e9453e14619868afeaf13482bec75a5fa58ad1998000f0b1f5fce74b863a7f00cf9aaf909c453345b836057f2d2b3abfba5dd8d56a4b416a77967f53dd7d42f366eef4ebfc0d9a7b1325bc6f6698efedc5eb5207219bd44a36c764314df6f0904047b3a70b13c6587cd726557e64e68327fc4d50d686d88f033ef1dee6eb4d8b5fa868bf65a89c8d70f3e2ec76f7a7c5de04518c08985491fa2e304cc5a05ee940df4a39501996cf85329b37b0b1c364c94a955671f5f0849f7a6696e9461ed89b3535037d6301dfe8872d2bdaa04b289833a5346bbb427b4c799f51e93a1fd8ee2184de2a6e53245f515b84ad39e2e4d7c89a777667c551a6037e1472ca8b5fcd51f31b2fa9e2e3a1a0b707395eb7fc979a518c5d91ee7268bb6d54886095818aa321714f88acf27e0174bcff6915381682f533bd9a633d77a08f5a3c3f36e68331fefb8499a3fffa6237cbfa5cb410973c6a74d5923aae4bf0ed2a15818dff30fc43fc26798b9dd33725e861e11ade089ff591f3d105939db1638ab0d4d10ba8eaf7d5654e71e48e409e1e1c9d48a3702d3689afb8781cc290ca9dcbb9b738529c1eb709d72669f53724e0cc747078d177392f745cb6b921dbada00b168e4248e4f08011a4d172806726d1da36a59c5d8a159219cc0bf0390377405546701ace03e61f1f94840a26f720262155ff5ff96f7fd49efc4688275ba4ad39361210bd60caad9739309dad13d42567681f7bbdcf89ad95dc92dabc9b91ca2deaed7eb64536e95b0d3e283e61a7d201c092dff101c2a5b9ded9aeefea7e382ea016f9cd55793ed25691b57eb434a08c856ea3e16c01e6a439e6f910ad1bf9b9b6f958ded8997e3d573b5abed0e80eeccd78391ad65c7f9a92f0ce94833835b0cc7192d0d73d1d82b6005ec6c72dbb686004f77023c88cba5fffce0edb52f8764642ef2da870abf6d8218937b0103ea59b17bb7f16eb27627cd498d7013be2ab79c3de1e973e64911dee4ead318497143b58f4e69b29292247bce9677b6f2272d77ce44815dd6130d4fa22808722013ac87517b33b8e4297ca8b281be4df334b6f46c0eac4e91f3f9a4cc0a7d72997fffc0b62068751197bc3565202b5a93561e67d95fbb54b7f6d5ed3bb77f7260b37f95eaeea88fb590f147417238806bc9acf4200477812ef39f418003eb725125c346f12d0e01932d1caf7f9a8b490698ce187c7ef9ca5f102a3227fa4972d1fcc99cad78f3a668d29a6fe9229aa3a3b57103f61ce00c3cf09e8e566a371c557f281dfb1f1775606def8b4f360b1b147d7e2901333b1bc8e7e823159f0a723d372b060c261aa286d831cff018da7c4dcb45060b40a74ee1a859294c2f6f3a587527eb7b6c671b858178d387777ccc33eff794812d7ccb22efb4deaed9414881e7e1105a3b6d4b132d4b70acd9c25d5051a1aff8a4d600d96ce61d0aa95e4080048f62445ac784e490d1e1749b89465988fc1cd5477448fb8e30f326427872be0f0a69660c661f77c4794d0fd57ece02d56c17e3e082593eb59cf4c4a8ce72203b7ae09d8522c86c55e55f1a5e1827f1964903fbd4a07cd25f12fbfd7cd77288609d414377d950173d2b7e276a6be9ae1246a542fb99361cfe51ffea4552676f9a09e53464b11c7fdf9a5c08a971fdc9572eb1ac1bf2f2b3974a6289d76472913561afec280213aee7d2282ef3172655d32f4050aecfe9e77cfcc39f90267269b47de1a2a415fde14e1adbc450878d2423f5421473b9857af1799354334e72dc347f5af0fb1b78e9b766d5ce1a30623665c5a61bc94959506a7e8c7067e218733e8f05d7957f7dd2ffdf9ffb0949504358c528582b450fa1d2c234ba33ad72a99c4b97924d0a53affb6bf285216c02c0f596457e05004d3d24f47bf6ead51a74e73b9d730483ec2f4eefa7f35305423bb61313a54ec8b8222560cc56c19d721261223c09ff91c6490daee0a714572f726605b661b24734c8049a00963907726d1834637a0e2e9fc62526616c2825b76b96a76afbe1d2aabd1baa875a264c72fcd1375e10b8cc5d46125bd48319453daf7f3471856a9d85eb5b3effdee0973cf26b06d1d6075487c9d5b68c64a96c4337f8781812bcb1495a2ea4b2df247766f8755aa20ae2e0499d616cd5db440581213b0489061dec62b1e960850ec264726954b9ce696af5474c05ddc67f014ba571724672e3a9228e0feb034a5f0fd472bbe1880f8a5e1530ca18f1c6629f1ec22c389bb46a96b47e16243562f8612b33d2e3658ea795b32a250ce98f22feede903486cfa1946c2ccfad0c70ef5dbec72cc2b59ce7c5f1e16377ebc90b5b5d6d22a3623ba67b1be29c5371c021750ac00a75a3cc5a73678c67772c2e3c765a6aadf563a7d21eb1aec47d1819ce5fdd12ba5dd9e5a57b979fac391d58cb4680fa6635319d7e131c93219f5f01d203d3f6b1ed12471921f27645b07370be036107757b49cae59fcfaa621299c4aabe19972ea719e4e5378bf8f76c9d1d60f4cecd67898cc6122f2d13a698b913bdc547a72cfd684e63ee2cfd8a4de2e63e1f242a64e87c8219331a31cd7a03b530428667207c35e697594b72d856dedcad15af6671bc0d40a912967ffc9f85d2eba90746ced38cf398c66dfb652b4593e4ca64c4bc3e50a7200eef0335c6886ef1d079a72d99a5952620959544e9b38e02dd4b0ab7b5ae0c212da33ac3343551d3de01f1e1c9f21b6e6e5c1eb9a5dcedb1ce6a19431289357c3132a0de4e71bd0d07ed4293f392244351e61bdeb96a6d02220aa22a10c940aa490cfcb7fbe190b74ddf1332cab4411f217e8af26afdd7eb5424d6459a35b417b371a5d883d6c6f90c7994c2cd276b554c09c302553fcc9212e1a3b68879ff55e3d4e6ed946209a4e261619e6c69220cb4fc9826c1ff7f4ed7642ba81ca697615360a6e14b73713705db82e9ce188d0aceaaab1734a17c31278615e266e6fd764d0709d085b51151a808372fa40962065cf6a27a05c9ff7e107458f428b7af198909bf62b40184204181c726ff370d48e614bf80bc53fcc4ebf0b10fda749878eaa6d6d81bca8de3780687279f19cef466d569e07bd66633125eeb775cfa124e3af12c049bab341c7086d72569155f7ced63375ae6bf34a87c3b780b5375eec5a6c2e2b0c23fe4cb2f6ec729f776fd1084523b4caab8cca4867c9a45624ecc077dfad17bd2af876f0bfc55d5f0ed3e2e1f1218afda8777b58ced50f33f14f729a40c8de0948c794ca56f5506f4cfec313ae4a43b86eaa755c3a3f1e9c791150c09010b458f09aabe70ec972d6606b757a25beb0ad6e894a47a665cab78e3020aec50f4afdcd83d294d43e09671d390f69c5aee3aee9c3150c982f3dd21fa38f7c91261b7f4b1ba3db80ec2bcb1281ef9d7824ebdc179a9da5d262a8f86aa5f57ef85720354ca26de2bec672c8091820e23d213bee8fb9d0e3fb796abb5722c0cd694a45cdc483cd8c3674615dfba7177a2cd235c756971d6c2778eb5a7a2277efa3f5c98172d25eb1ce8c426af6be217ee12acf69152e0605b6e5cc006cd33175c12ccef6256a7ef250de72fd5e06a9f22ac199ff0174da87c4ecccd49614f5ebe98e744d412fab29dbb0727a0aed5fec1228f3ba918c05b608326ce899db113414447c9b392cf7bb686c6bcb1f23adedcdc12f5630281260305a52797c5243b56734f1cad6e74f1c89c82519afc8be8d82e8e238ad17e85160be3a0e781bea2b49639e4d563a67d0d9a972f2ec3efde983261995af4727ad03a0ac41e08f56d62d4767693ccb4ca76bf951fa4794f06bd335d2bc032b9239c561f0a72ceb4b7cbb7e12f43d1d79c196937269e1b622c587718015516db1cf3ebd15280ef52ed2ed0288eacd3f950ed5e57205f045d3bb44a81c7361a8d77135341db5a425c21e96ea33c30cc6bd43c7d07212dc45f28996e8cd6a43a864005ec7c1429a0d2176340b17cea3030ffe0b540021f38d092dcf41649d3ad85a205f2834fefc74ee77b4fa95ffba9f51c9ad2b7235dbe1763e7bb420afb511273f08ee147fb6d3f33b549d1c889ddf916e4ad272dd15e2a158471b3a3bfe7e9c6fa73ade43d95fb9d346513426ebf33edaf63372c1dd12adab743a8b2a4cd4ffb9655425b7127c1e08979491adc5ac5fdcbfdb621ca5638d297c22f769286e63a04a224736a39479d82af9065ec7abf69faa747281e7cfee1c4e29c44180ba4fe501bf3cba82d3d0cafbfb7e4540cefd0ef9cb19169acb318e53d6cc2b039bd8422eaa3d8dea1b587d53b7489de83084618d5d115c4afda0367241760d09f6238216c8a718dcad1bb789c23a981deae55c17d472f7df5cff36be6c2b0dbe0dbb91e84ef7d917f771fe5816985074169b7dafed230c0542ad2e4132987f026fd40e66fc1a2b6bdcdbaecc57b3b4192477a93cee7255bb88c405af357e755324a088caace55c95e48a579ffdcb3e0cef88b594a172d20e772fcc25a452c80bc0d980101328c6b858349dea24a7b91dac43cc4f19722fef1482bf2fb9eb2dca8b9d5090fa684b95f1a14964e3cef8ec589d6c9da672a66da7726b01decdc6513f24611871d816673693b9cf3f55ba4e07298b02f4724f1dedea8e9aba468874c01dc0e6c7d3f7a4544748e4430592686be3f4e8726bee176b23a41768dec6d1d2ec990948f5df1e07e80753a46ca483d67c2ddada72740e0489b69a910a9c42517624431f3965f5a33931c12c560fede430cc82d472b275878e08329278870fe5a273b6e3d5e2c4709c1eb622332cdfdfc9088761250ac8100ed349cc5e360145551cf2195c38b33d90336a3f5e8f13ddcbb7ee2772bad6929e6e18be28b9d8dbf00985d59279bb67beaf04d37c4194c03614880f72ba6490088a194669e6137e732b051cc8b4e8a4281cb935c550c75db7e1dae233d0e948fc3eb3917eefee7400235d3cb1fb10d697925319e20cecad10e5548244a66a08d06c7ab68c9f45f63c024f047f99a64c3126da1687ace10bc2283d6a0cafc17e2e8898385eec840e1f90af9df7373355d66ec7996cdf530057562256722202c225a6c41b3d5256bf1be80d1809ec371c26bade9df49e4383800a466472a7854780688e8409e32deff7e193010896ef56462043c34ce847abca8053ad24b27a34e73899ebe0591a56d22b30310fc0405b11df5c16ce8a2e1191aa2d8d270e20de1fcf0990fbc0dafa03d1de1fb489841e81b23cc048dbc305e0ce68de726cd460761c169795f9b1cb6f83ecaf06714fab5106282be411b6b560df5ecb726754e9dca4ca7bdbf8b69a64ac4e16f897939d51aee49da23964302a94107f549c6f87914ff9b6928f3cbb1238e72f0f3f59bf79287af4791a8be8b9cb644d038a3c99a4839d2b07fd74a6369c0cb288939cb92d2feea9e9bdf49dcdc42d1e0b73868b277ccddb7c0eda9daacca914ed6f59685ce30603f8e6fe5962443f7e72284942b4dc78f563ed1f1e38271a865af6e5fa1b27c0e2a130829ade2c61b772f8a8b1342baf3423ce390f03bb70011b561719caf3186cfc4d2170e249807f4f8b1974c725b10c3ce1c8e9cc6da1c2b838bb39ab8b6bf24c697d41a87f72b572c92bcee8b6c50d389d18570ffd7042bb675a7d4e069e472c521accb2f7557d2d52c1cd6707899e459a1f9f1aa387105694a933193ad47f4dbcdc74fc076749728ed1f953fddea370ed83fb17279905f3f31a75e5caec5b92aff4410ef85a2372bd93318d5032d4bb12ce4aae086e0e50973d2f9ac54cc1ebfd591c79f03ea272995d3c17088fe87670f1e7f5c3a7f90fa44c2e85e08d1872184c2abc94e9cd72228d9d5a32eb48da9f741b466c5c891fff8b1490694977caa5e9c8f2125eaa72d870cbf48416eee666a226addc011d5272c34ee2b1781102dbc9c6c833b09672cd070a98995d95c7a1b0799a584e97a87e82d4ebab6dc2937d1b68cadbe98c72ec03be4ce25d9e3b04650755f4f96f5919a006b067fbf7cd005eb737b100fa72ade276d732e4a330553918a37917c7848307c637a8eab489567ce21df9697a2f19183059c97768e05807000efe931550eeda1df5780870e9ea2b2ae19349e5087a2ae7baa38ee4544cd47dc785d5e1e563fdc341e3b80855d73fdd44cdaa3d1e35629aa33531ca9cef71c25415be9caf127329dd1d104d614e6bb2ada51ce772d15f22d476ceddb9d2ffeeb0f5a39f7e57f162954281e626b7e4bb8785785000918b247f1192305d8d66970c34c1c24e16cf4de90636af3c327a3b8d2ee7431713e68eb745d16870d7e7b1adcff04cb6a7ce12e8d87b74ee00f861657d1b411fb201c6137404421902556f7c7370692bf5201bb53e27709a9e67ca5f725d4f1c89735070970139802a3f3a29b26fc9fb0a09121e0145690ec58f998ec4efff34fad616cedf90cafe83dc4b55b758ebbc3033ceeb1e6409f003c5954c59b70672678aa4680fedf14b9b98fa6f34e03f829f2723095130b1de30a063b1c15948724ee3bb386b986d24cf1ee09300ce89e02c4d161229bbd08c58612a0e952eb1722caa6073624318411af16fb97009178eeeb7b77402f69ad5edad0cc6b6057072113ada9550a4050dfe0b9afc3ad6629ce7f1037eab004458f13620671dbb33720fca32686eb4a8401c9166cd3113d28943243c3a1fd0cad6fab28c1d4e4f8072dc726292c1bc82a20cb90cb351c71ddb46e53592f8767ad3c15620bdd383ea5c319b2dd5cbd428db78877474c1e73ff6fb68895819e6ae584540c143791ce072dbc7ce41da310ada38ecb2be3551f9d547418fda16c655afc9c1327a759f18349f6cf65781cd49bba1d909d89f3152033618bd282306c5462e31a733413901727825bfcbcc3936f0656bf281fb9521f75a22d2d3b9042a5a31872d5a04ed71509f5f3475c59c06d30245134315fe64e53b4c3017ae2e4c72a1d2aa9dee050a0ae8216ba8ed42063d797d72e4548fbd204fbb81129be2d6fbd99ae9a1a5823565576eee9c7ccf852cc561e9700c66766a994f7a84afb18c34ed9bda919c52b34d29e9c0ae8e6191cd984fe9e2da7ab89dd91e99ce9f651b4175be203aac2859727a20a4434ac1cdb107d7e8d7cc75b1373112168a5ba96e52d0ff44a3e4eb3172e2752c72b8bc1f30422ab2fe07cea83b94ab7eb39697d6a5438eb2f5d05e0572458ee9a63c13ba52514c98767ab67e250e1b72efcebc4df8cf746007cc10445fa32a720bcab4b953d0042d410ca6f5f908004d89108dd05c1ffde115ff492b07667149aaa555cc143834ad32b97078fe26ea862e521a235a893e21a617208a72f66cc5c4d710ce0b61a586eb67026ebb046133380327d1ee34b5f55971aee772cf788e180bb017b7df4c11d31d4c8901cc244bc25cdd2212dd6c51b1050d1f72dd289979fedba5b455c8f5304c7d016d889fc9af7ccb638bbad28532db9f8b72487b6ddd5cdb445db50813b2cb4366c2947bfe1578b6f2a84d2a67c695720772604645eb4aedc9e33e1b95be7ae892a093922e70372be368755da1871c6bd1726024ea34ed0550f6e5618271e1eb653904d2446bbd932708f2f3d7cdbe421b17b7dc123a198f202e044cb0004277aa00179f8cc9bbcbbd002011a9818d285b72e5e7eb71ad454b296e41e4fea8ebc87d5dbba857c351f6d80cbc88bfd2713b72b8b119c22e633de6e1938ff04a88b82da55eb9eabed721c45f8b8692b67bdb7290e831c97094c42fb20393e6cc6abc47d4ece14babe1ea27c0aa20d547bd9728b23580216aab45137395f6e7378cb7790917eba2a369c952f6d8a0df7ab8bf725c68294b6d97edf4f786994342e0b799eb98429b193e20610ad65b867905a7725479da1310a9cce5ae02ad4ab7096e5011302f79e0fdbbe33ad1fe36092f9034a084888891115f428429eec2ddeedf28586fe289effe77ae83f8bdf694672772866ed762fd7950a11dbaf1d9a651bc09fea598152ad764f838809d5bcf9fb632a6ba03dfdf90380be05e74782344675cd5a2db6ca9c4c6bb286bc1f26a448943e05a2a30e4f2896219fae8102e6b5f59a5d0bdfb9d44ed0bab8ee567de6c717268fcd40e5a5c8958f065d73d3a139eb5c3274243b2331abb5fd5e44761d1e672463ec8af2fe23e4d94c44300b84d10c4edb8f69d5a6829847f7e58cc10603072deb5726e051d304099e0aeadb04357b9d492e3715d91ceb999d8ed71501c8e16bbe8d8333320542b25e28adaf65214e3e2d507d9542694db501af51292b08c72416f1831f4c9ace1ea1e41f5fe7673fd4155403edecb387117e4f1a68b418f7294d803a40a5d27a16bb62938e1ccb511d85e16b951809cfb8724e71ab07ad7122b8598eb2bf62020707b87fe88cc783f04a591e5fd9cb52ff3467c30e74ab602ceafebfdf3fb650817efd181404bed8b80d21a4fd63fca778bbc744564809259fb64a9c8b96bb22d63e177415e68a6972f91a88b8d570f18760678bd60acde72cfe6ede3e57276c4ac1bcdfd7a6b1bb4da49cc0eca1f49678488611c54792e7243b57515f7dd8ef5d7578864b04bd84cdbbbc0fbd59b0a952dd0b1172809af72c154dd3ad3bcbee4288806c20cf254f547521c6b65e3f79a51f42d5a219b0a72a6236b2a30de2cbc9ecf9963745e7c0e25e8c609b0229d9a75718fd3941d7131a4a95bcea1ca056c252a304c77af80c2b57f2ef2199d6f3483abc6a17ef67916b8ad36bc8da985355c9230c32cb34418d418eee6bce86d41d92dc458f5ba2272efbac64e6ad4ad4b242407d57fb2955396e34b7bee56e0cd8af70e609c0a17101bcf2951a411bf5e0e0cb1947dfb09e2c4f93cc803482a3536be644d9a1be20675ca211b052684002d6d5a11d7be90b195fc5e68755ba21f3608bcc86b7d23722883b78f177072e7dd6a14ba79b3b86ace0eb16e9ac7ae9f30be3de08cfa5e72b1467b2d07051bd2dfc01e32dfa0509230b26d1b24ed249ba8d79702e58afa725fa3130223cea934598a8cadcafa6fe62f381971010e80c0c933eab5be7051726ecf78b416a9996d6667f16712de8ef8d120fa27521b18729b5562ed201bb3728f2052785d18f5b8c34736604fac02cd2e367234b0c61de34a77eff2b6595742bc36f6c39077ded9e7c69aca24e2c3fef0ea0263da4c2fb70123928c4c87aa1928bb74d1e19a657557a83b48ca7e886214c0f26bc1247a65c951873ef88e5372a45a250562dd047b62d4874802a8d6134e8ca607c78cff27e061648485af622eec9a3d093a90e37d8e18428eaf5868e4bf53f59dbb08bf4c5bc1b354c4603972147fd8477b737ea8388eaeb1e48312cd93b43b39192f16967e8765af40b6da223bfdb13719342bf6f54c8d82c73c9dd108602dc5c71e4d6143d58ef2efab486a20b547377d19a7cf260bfedd689fcb87f755b9a64e9cf5c3bd34c75c387af421e082a30fbc0e243649c105743439c6803994615d4248ba152fa8d75e95f518724768bb233fe64a986cebb9a2331dea7de0bd670f80689a43107854403e1e2a37fa3200b7dcdfd0222b3997104c3e104e29abdad89ae4a8a5dbdd4822bea1c03663d448e9d151e0882e38c1efe2563a199ec7ce92a3e5c8dd29b296c5ac4d1872be55a90d283018cf9d337d2f397670ed483a9bedf7335395007110435b75aa72a0ff5af160dca6e191c4a7e5648b5581bf10d9d1ff4dcee888a5ac94b9c5ee720f4689e82d970b89379327216148bf3c58faed4ef042e36ad06489473f755d259f1249dcd9593c6b6b9d8258e4e87c60570867092b1f803897c8cb2df3aad172ba6276bd5cf217d5d22dfa56bcde9c68d07c8b73af38b865eb279b54706fb3724058e2ae77ae1f5332fbe265ed55f2dd1bfa080dc65d4f61b49eebc6e3249f722bbd96f01c99def9ad5e837214ff42f2f44f4a6123c131494cce8df3df66707298b7f36d18f43063ef2bb48b0efc7796614f4a6812226e0b32ca2e0f5a48057295cd16417625c5cf11e42689ba324b0449a56dc206c47ed0c0709a61ec8ea1720fc7f4ed37431bc73b36316c8fb3e10465814fed9a2342aeb5dcc12834541e720a2e7f88af34655253798033a4527ac060bfde3b2539197f994b19b6b565e2706e40a79832adda6276c27459c2e096198c61ed6865666c00b9850007188b0d47ae67014a81584adee9ced1fb90662a6c9d165dab03d5db4a391198639b1fe1722c70fa55efa7ef2e559d006642c25d2b8bc566e94ec30b80be46f95bdfbbfb3fc66c3904106c9ca40a8143a57a20a53b0dec69464f6304760234d6b45c257e085cd2cef065aeb7fb194aaa930f70676cd13ee43a2497884a9afff8ea0e1d104ad77ef0f9acd3c839524c55ea1c744be6969d767a8351ab0c939eb2c33634f072ea0c1fbc66db57177d34b97feb75ec899bd0777d4b0086116c111ff59ac944629a625a0e67c36cf6a53e32e36ca65f9e61a402167eeb92d66d0be34af5c20c5661d9f9940ebb9a13e6c05274e2134d58251a1dcf1e2226b3841566a3ca0f9a72d5901eaa072632fdcc2289cd8d179fb90eb1bec1356eee2ee7bf9734f08a35723001d521ae0db1e0edf5d3d96ff3a8e29cc8f5f8a3e5ee6b1731e72043ecf755406551e39ee3c99e7a89afeb955ca9243846b36a7c609758d9265cb55681457299e678ff017a1d787a52545187f763c9d9a8dd69721fb601b1697364bdfa7e728d62100009b0f2ccd8cc887e5f73ccf1c88202a38d6c42e15b0b4722c7690b5149bf5e2255bec0853b9d3aa181a3d370a91ff474c5ca95876d281e0b295b1b1e43de3933ff47cc212bec8dd832276a2c550e4a4b33e3ab541675a2c24bc6d50e3c400168699bd6216026e7b989cadc3a57ad3a1566f3e350b135432371c7bb72055826c862052831cc94bcf963486f666c9afdf6dcaa9abcdae5943674dc5372c201b5b4f92ff996843dfcecc1fd1b4398ee4901426a840a8cf48ddb5152ea09ffe8c88370af998986a8189529e449b12cf8acf8b3b7fb774e60e345ce92882ede26011df69db9a7471b9dcdfd16cb9c5e147c8e524c2b8a75c001b27e72e2616d730667f6c4a28ef4cf9cb87d29837920233f64b242938960b0c7d7af7632019e28a75c27892f0d2c62341b153fcd4441541ae0feb10a46c7e025f155360c72a215fa4714f9a38a3cda39592c1a425f06fb6ff37cde7970448623a0d4d0cc36d491ae150546f82bcd7888e9f81e745b93f1a9c69454ff8c99b26b59d9e9f837b8f33ad14009041071ef3401c0961c38d25dee8a9c603331f0b643788797f67219c68489902997fa82c5e17164495ac62cdc27c57d9a2d030bedf83602356f6b1178d7d73c5d950d241d825f1ba51fcc834f3aab9293778c1baeafc26e21b723c53abfd42066e833536c96193efdee4bd26cc71e5fdebf8b20e6ff5feefecf2439effff988fbab306cad8383e0adf96b2c685cc675d55ff121c6d26a87067b4b9c52e72f97d2f05613544850014473d9f6c630b938c0a6eb3f7ce80214e62d395c8bd4c794ae408880547395e311f28fe5216fc8f03011bdee3f56b53e719572a8e0a4815368ebef3f89cad11b1fdc8d2f38d2ec1347f99b40ad4eb193786172fa71acf71e3a48b5d3ddee57b038afa92cf12f9ae70cdd6adfafb1f40f71f472c33a81685b61aad985dd2b50d459304e372d8916ad6bd6845b70be25d06be37216de10708255a45d27440e6c9e070df83a4b0dc7d6345e2385b1d1b51ca5eb293313ca6a6e4aeba30d283ebb9d6324421cfdde0180613252406252d4f7418d72c1e663ad31862461bb30484c6a111ccd9ca5b6f6e3dea533aa04ed6e7db8d572be53156635d3be1202d0ff83e644de35017a4948c4958da2eecf28b275b7356ce2632fa34b8ff2938d3fe35120b132c71dc7d732c84d10d169e9da147c33f80e0142b6e3121429dfa7f894c7ebb7141977b1bb0c688509a6bf246f47b6629372c030b851de2ad910eb7d55e9ceb89b2c844e4287fbc96fd4adb77b45987ef0727e4797a8d66d163a7f152feafb4bc7bf743c70a0cf06856e28092dd74a8bcd72fc0dde4d7d447c901f3333db3875bb53ac66e146a6f425561ff5fb87c9ad16728f1e3fdb502bcd217c5f3b52169882cc631cee52cb944d2fbd88c8ae3b3b3472007f412c608f68ae94d3cb32d9c5ae26b46d15aade49872cceb275dde109de49b6b947cbd5254b9695eb43e1f5aacd1c8a2124b9d64371350166073af6a87e38f6fa4093efe211ed704556f8b267a066532214088f0db9479b5922abe6d17c72bffda7e17e72ac9fea624945e52770643320ac79b6904981c8413a79f0bc9e1f578e1abe6e5aba187bcc029b480c552831fc1b8e366e41f3194d06f1be1fa93f9844dfade12cb69c3f8924a6ae7ec8eb689046de50aa81bfc5fe51180577dc4f74e2ba368fad0bebd3b285b93b6b22e9e179c4a2bce5cce89805ea80f32a080e9e7d11fb68667cb96feb490459104c547451e0f266da772eaad6696292265e722c6d9c066603ed7ba478af70c21dde7b7a46b00b66c61cf319296a795e9fbd72fb787de110cbe84d858380136d4dde18d14a51ed449f7b8466a0a1e2daecaa4a4d5674309b8ad03b1d38e295aa51e767f3465d3e046cc0a6d348725646114b0eb6ccfb37ed9afb301101a39cb5d56d7b831d93610367f2d3d8f0ffed44c31d72d193a6211c8dd99b1bca3e07d1b29213f02bbcce9a727bf966d03157e28e947234d048cecff14fb0539282698286bf14f6ed09e37bbcfe51b7bf25d71b1db272a473966e6c6614ed76efd9c1a8afdc657145c406693a807eb98865519b295a72f29fc854f32306a50c63e206cbc545886031cb9d76e0d301cf552b90862aad72d92cb78efdb35ea5fce3689f10da2a86a4bcd3606a1895a7aaa3ab38e61bdb7288a0912601dc6c44179473cd32ac9fce2bc69c800f43c788dea795a4a1ac327214e1947ba110fd011143e9eb3326561c30ac9b327e0555a3ef51ef3686878d72af4ef2720b137396fe3b40a3cd261d4cb4d0e77e128e46739162c23ee151b4726db2c30f96aba5bc8fcf5c631c43f19a83bd485e54c066b79e052bdd055dd9310cf4fdac7f39e915d2d05bcfda9af31c5c49d09e0ae42cc142b34c15d208777258d75dae7bf1659be1f85b26817d3d069e8eef6f3833d0b5c29142186386bd72554d1bd8fb152b9c79729789ac4044969330335ffb09644ddb233daaa0200572bb0706da231bcd19ed145476f48cb84b94acf8e63f23554f158dc6b20aaa5a30a6eaa4d23741b41f10315cbc839b01cfcf4307b5fa42af8c14577358d089fc729cf6f3916c70e60e9010d50df98bfd4e1b97994484ec2a902b8c1f4c97fdb3725a00fcb3f272393a5094f2dd4d5610831f363c615fe66d4b8d9e185e6e0ac322374fc51645d70fbe5a86945aa47b83a28f3751ec89dcace59e77cc1eecc9ac0d9f61a24a6d5e05d23c520ffd886cb58047bb086d1b3f3391ec38e32eb426524a72d9fec787f68b6173e725a6d437c7fc6db30c10711a666242c5c901315a0f4727ca2344eaad1ada36607d66ee966774fb922751697a8e742dffd80a8b578451e6eb86f7cf6611a3eebeceec70efc5d1da8eb852767eb39c240118714cbab62012c90990a3b017d73711853e58c5e18d9cb0d7f749c158aef42ce1b4ecf4c87238c1762b9bf11235971b4e633498df2117231ec31cebd3b1d48c00c44f64400d2f5d1dfdd86dfca1319596ec66fd600253a9a33126a45f175baa58793fdb5772851541f3ec66b9ffa07d73db726b18776f8519cdd93305cdb319975e7188601c4302f74b41c985d2d780c111528080c965c2affdab0e265b2ad60c33170bd972c27515d37732013ee15c12f06d3972f3b3108451577ee98588bcc75d9359c0727510b807801014f37dd3b884953597edcfdbeee3372e3e1446a05ecb65fd0931eb61bd69763a002c2311841bb53fc4e22bc1fe6d070432aa0cbb47f225488372a2991b33384cfe07e26cf7c11ba4d967598d5a71b866c78c5f8d6004cfbc597248305da8741988c0beda561f54d61464b85e42ab992884d70c6505d0a0d9be72c4b1aa64793c97ed88c5c8fc35e80e9860b1774f6fde5e7a1d60dc395765e972a5842fd78319f9c57d4389f1a5d956af8aebbca04ed3c29ed8012a49fc5b2e727d1b6d1b7fe11030489c1fb7d4f79a00af96092a126af6455b511eca8b86855cb2f5413f7fca5b7f76dcfdcd06246c1e7a4f7854d6f88ec14834c3dba57c5028dc00a9f1ac0bde34f97f1627a8efd54baf32ac698f9d6144823df562959ae54a80ed83a78704795dee3403a826b5c261bae28fd8e55ba98d22a3127ab6e10272ddfcaeef1890d201fd44c5b753d94208db20fd42271536ae00bd1a99eac630728f9f85af800ebeb43c471f98d4ed823cd45f2eb3b6ec9217f24491cd2588140718130a98d5ae5ad1ea4d21c300bca7649515494d7e61a1e55d1b941d54309572f3f8ea8822717a30d4d97d83bd897ee98fb529a4ed2706321ebb7c021624771b69cf01db2eb1c824f6f28c87a52422c59bac0cc4ec9639c41c22489fcbe23472a3b932f6397fc9b44176118c5f64b691a4cb7165c5872af1150b3bdaf0c41a72f9b1dc274eb0656dd64c1d2bd9db8ad39ea68f4d0931fe801ff179b61e44a3723250d7b477a9cf4839bf306846d3b823e4904186a65576737b776135ce2bc40df1a7e6994a427470389ee59a8f6f6abaea6a1af0855f5a223cb9bd24e81b3072c5b043619b27129e2ef2fc7a6a5974d2b04d03560c8b4dd098f683f83b13ce72a419e9786ede12ce6803de5d5a4ca4b8265e4f1478a844ba131730fa0c25e072aa16dfaf675ea22e8a14c7ebd4ebdc0dbdb8217725f3b9b89727343fdfcee21fcbf079874e8248958ba373fb81fe46c0e9743d155927dfc7b7afe1a75070e172ebd6d8e42a109dbd668fe6c71b4477a45bc3f611104be1372f07d0239d12bb46d4b669e6b1a6ea28757708651b8179445be77509c8bb9f4aa1b4d550be8a70720f497ab340ed46ca448dbb6026ad4ecf3ec1d3f4d046b73289a8856d20c2e2487c5e9d31b6f73d0625ede23e06386df0080ea70fa7461fa8540261ca1ea1a972da65117ea995dd65fb23d50d923836614ab32ba6b087998b8e3f18a91436595a5fe69d0132543c5b11aaaa7fe54f5955a5f1814be4e9cbc4c7a32cd95b37137221a62229fc139db29d74f61768c30cf1b81cbebf0d96d76db86c9a91134006720eba9f32be71143fc1e4b57c8cb0f70323a29c132a28d45f0df5fcd9b07086728ac40ab3eb019c772183765c9bec23be9d57573bc26c5e209a110c1cd9ad6b729613fbbee1d51fb35dc1439fc8795387e1322f5ee645a7e80b8eda8f83376572ac6dc14015841a60443cf8afb6f560aeb879d03e76ad926caf5e7ec2781f34722fea0ff08709c42ceb171cff0641a698f0427753b44c90211f5d3988ad442e723828c446e249a1c5f4d7ecf5381364281e51bf8afa755bd53183b321045c5645e50336f8405548e11f8653a883abded8dcc661bd106dbeae0250a069b1636a723ec10d3c67cca31a2e57eebe68a0675adfd7a5e05219e3836beeb21ee469907243efc176f5050c33fa874d33b251de1e44fbae08a33e0e731e6adc4ac3d55d4e447ce3452fd8d1af46d69587fd3bdba4f97d28eea836080d10c324fafa3fd12e1ff4d34d749aa369bf562d805c83b00838ebac960312ac1909b7631ea794a0720b96d43537d2f7f2db18cc5af4195eea323344086cbfb83366ec877f5841db648fd3c85671319c5b3e4621b94bc8a711238c1b1e354ba7e9ced58327a93edd72277ea111a4913e0a7cdbb619704d2cb40e4fadef4241c29f3fc108d3e6cfd0726fee281c6a10d5057cc96c4d8b5c7d0ddc7079754831915ceda29efb86e14c45954a07fee3368595f3910115462760e657c120b932f66b658650706f34330372c14d13acc0a676b6901c083fa8938f6e1a01e89e185a30138436d8fd88caae258b3ad7995cbe509672710fa5325fd03d0e8132071fcf9d9680775f630c2e4872e9ca019da79ecba2090bf8b2508dbe66632e5c38503ca5784f591be86ae7b923b29c50dab21617e424c88577d0a798b9feb1247dd4554b427fc97e24266c3f6dcf9202b7ae85883ab54667daa7d199c36d9b95f014b802f12dcda36d2bc26608951b4822be3a25ab8ca98953f1a9bddfb18d6d2073ce47853710c9bddb1aae5d01aca5faf2f7da552d23303e20431549cef17ffacf3d491ee53444e89d68757232ede24262430af8de2a29f5cac58ff3a7fd91bb1985a5b7e5196dc7926e1a291308924276faff95f44ddd27b23d971ead387094a4d598721ec3a65a52f03b7295144b18a1374aca2121f5f80cda7d48320dd598aa1b55530e2efc47b3baf807eee48964207444d6566698cd8f12712d9c285bd52ab3ecce07950f93f40e9572b9ee9dbb2db98784528c535317502cd3bc4e3399ca03fdd8878c151108c07172acc316a32149641cc2ba163b2567e7c4abbf500532ebf0b4f4750baae005d072d71a4e358e97a5ec1faf3ad717b5b7d05be17a95fa03c0fa4776f5388d144d724771b7dddf29dcafdc770f01fe61bb65cd70d9eb77149a539bcf4cc788e23b17d2c008ebf8cbe23991eebb34d5e36fa9de3cd2403b3aa768e783248a551ecf50d4fdf35acb16a0178650530b0c0a2034051a28549a95b6ce927f71fabfb416349533e088070c8c32c235a9a9b1ccfa9567bef917bf6017c0c95b966db53d8f7243efb4a619c21871d2fcd3e74b3ac4d9bc8e696122c5b3f761c4c1370f3efd72c19541fc9b73424ffb93fd2a57619b7067e12e2f72617da7783296541216eb3b9de08603ec1cf7ce003880f67ba2ca2061cea580abf6a012e248f0b151600e72e3be281f564e7072bbc8d2781717326bbcb8a350e30920e4092ab1f1e0af5f729c7ec752c38629b839dc5dccf5086e95c43a53eab49f04b0442c0497d376c77292b32dcc426127fb6b6260c2478f5cfadc5f2bcaf20d70e9dfc0e2014b4cb9723ccf45ee140db65db4171b0bbe7dd14c3197f02763e07a45119f8a7a097f30726e0428edf53a7793b3c7a8d7c33ab34c99007308697babe5cd00d19a64cf062518c1c74c739d273fdf41e3095314f6c4796ba9ee2f7162349f0c04fcff8745727f7ea0e333558106e3abdd3315a01b03537f4f33cd533a56794b212731ad337259904a9f8f9a942315257dc235e470f1140d72df364b000b00e2dbdc208abf72a4d25027f53877691e6f0a13d6ce0ce1f28b888d7536787281bdc80758b3f572d904485a7ea85030508619c07973ec1f0016d10cdb85e3239c534c95eff04272f403c6e1c26fb7d62c40e8bbd5ef01c29885231d10264a0b1db774fbdfe1687271aa6d023dc89525d581b64fd8b5cfbf46f9a5375db15f5c7f8b6f7bda86f662573524e1e465af70856e0604590fc7689ce7ab38c344ccb0348be88ad51fdc38e4279990623f265eac2fea54ad375743450c3af8956b3b7bdd71b44232423772aaf220302e1d5f21f7745d841a49168d19cd901278ef490966c1a8dca5ac1372d7702ffd0b157c081a54ad9f64fe2883bc9d4efc8739763adeab2c2160b7bc72c67a3b9c9759b0c0120f07079fdadcb28a4719be6093612436cc75d338990e72e7999bb67848adc722f0008a2d8d1a8cd4d9ae2c87f98b1909413a8daf953772da4279d77ed2f51669d78f44c163177e7b9b0c64068029c982930ff32fd4eb721e945a27dea75d1f66c4d5ce6aef580f6da18edc95719e86ccb08c965af4b17233039e7cf3d484e0fe468a07efda8cfef145be16a0783ef8881550f687ece13c1c6e5da088343ca6a27218038dc791be62e81e6530e9f89abd98a6f86d8c7a726fc2fa11473037a77f4932a19e7de49ad7f2ec6bc934a9c78538d2a35ad6cf72f7a46fc309f52497ad3d2f0bea8f7d50e404badb2ee2bf19cd38311a9f85ad5b4eb82164c7db35d66d1c53ef81850fa1f09073b2713569834aa8366112b721728c8b89c549c01cc1f25380fa20f35fce774f5448494829fc343f89e73d641372cbffa335e6ba3d4d3a3629c894efa30e112fb33c687a61fc781b474ce1a9ec727f1ebd5acc3462a7eb3b519317f62291eaa61e148a75632f7f4268b071dd4f7227cfe75868239af5ff38f129c8afd6e8e3dceaf0b4525fdb664f351afa9d8b7291f9afa21dccb6841906d865185a66be5d769decc4003c52865f669b66e5d50de0944234cb9fbef663ef8e07df556ab74db8ca756fdb7661929de10593fb65449e9cec2d777c23848c92603457b8480554d1e88cc04585aaf3ffd900e12b667288eb1543ebfe2792b05abeb09714b7e41dfb6b83414b638d54a3f8e8882d1b72e5df3fe7558297abb03f6e3c78ae75aacc17c384265a5dc4b854ca24070ec00e58cea2b5ffb40f1a3721b76114d1af5a479fae809b161a67b49086b165e60300f1f603f34a0f92e9c50bd85f7f06528035171fc046476181a62130e362994e72ed514b664822f96afb9e68461b9040cf181e74a9fbe6f1a1b10fb1e5a1ca272b58517cac3cf42fca7092f69785d97055ad4fec12c0c39b4bc480a69ef4505b1f6805ba9dd7ab5012c69a363d56af8b52878ab7a45eca0c42a5c8a1cf3418ab72a57664698e31d5d2f890b7da2fb6ac8ec2426e6114174f6b9d19536d645df0165b90925d49f92a5bfe952b5db1bca340c65f71f1efbf6be5a2702a15b0eb0a3acdfd963a0c72028c33f55c342fe712cf28876b76d6353ddb7f2de9eefdfc50725005b1c1623b2ca26f2d68a07eb23b72b7dd698e9ca01577be22705bdde2c272742c9f34c94cae95fc7010f0691785c1e9d4fb13717f6833798fbf3391a558496a914ac7b67e8f6160a0f1a7be3e25d29fea6bf36e98e7ff1c7285ab7ee8870b58d9dcaac99aa46983f3d31b7e1bc2e8e5537a2d6f87723728451ec13b0978727c92968b4417f8169a523c07c8dce3153bf646711dbb976651162bab6830452799cb638bdb7b039fb8ce2b52c7c112615f9191330330a22210c691bc4d66c01fed53101025b3e03631048d0287d9e604828ae80beb4e13521fb252bb64dce972cc675af8080ad37d51e18ca8ec62c68342099296f8bd76fd49ea39e93a05bb7258d4731ce82347fa3cb308a36ce1cecce0c23bd0fc9c0027fffad42d04cd1155ed1b1ead61d9e961f337bdfa6946e817e5192484dd52640b8647dafab9db357220b37341198dd9717f7f2758ffc1ea8174373b68edf485993e8621a5eed03e72ea495dc1c777fa9c1918ca181c10c795f1860eaa4fd5398cc02698cd83c7ef24eb74140fe2d708f024ab5bfea7917407b7c22afc4cd5792c02053176a1d57b72efaebe616c4791ec7f77dbfd5a8534c6c9ed85493862cedbb527d0ef1f16686186298e8a4def076483a30b8128361d12276e009262581d057de9834965229f724f43b2bc1ddfcdfd68dafe139c542ab9fa2f07af664d979fdee829b25a0e903e7d41bd5653359862e26614bf593fbaeb6f8da4bcae552bbd8b237cbf5b79ac72ca49d8cc0839e2a213a76b4b9dc1b082d4c5a2d96ea0c94d2c0f52f34869bf72ada6070ef5c19a0bf3f57fdb0f82c95f63e2499c296cf2993a9721d6783ee4728071519613d55ffba8da793c7b9ea39db4f65043961df5305305f11ea7878072514864b55f227dd30c673b55bd9f219eb6b82989f7849dc4fe472e4c44ebaa44b3e50e09f94817c93b2238647334a99f61adb356e4a58c9d8ff85a7aead28b3954c81013711d09a88d9ec22b119e44cdf14378830835d99d51481ba918c53e308393ed9513fe460f72f26f87a5180b74328c059053a17f35fd8d934c40d9193b59d3db99197ce96008cb90fa54946c5ec3a98b39e389916d889f9a98806cfa17e8c2178031d217b5911487cf76d768847cb31597cbf39b029889b8505d8e8172b6c601bcf9161a3aea5990b6866c5a800f805605743a19ae70508c5f9aefc3729412b5f23c0423db403d3c77d4e33d44113bc792902ee07cb780bfe7fab77f5c2d0c5e04a463ca74d053e28b0076b194f87f4e36ba14496fa67bc346a63a837254235466e706388405acd3b504e24854eee809a92fbb62c19cebdd7553eaae7253bd8f723beab064d29fec101200e962f55b36b020c208acd74b5106bc59580f8974553675878fd53235749551704d7aa99b5b826606faebf56103473194b972be52cfc5cf4a488826c4d0d8ac21499516bcd05cd1e639e5cdaf3f442b29e5283f1b24cd788c5d958d0c4988a83810490213b8ff0d14846c3cf5ecdd783e43721c3ea7ee2f9620f3d30b55409b296df0dd338834cc338d5b198bd530fea3dd3b660308e65dce00f0b1490b25c2d94aa3ba7bd73f975617c0ca34163630878715267c67fae6aeb6b78a99aad9acf1105faf6b3dc21f986a14abb8266571ec69386976565d992abafa89a00b577612a3e24670a1951ec22b440b8c5b12a03b9b728842579616699fb3d813cecc154ba97bbae4a9fe9111d3e4b1ae696d831c4672346022906e374047f748929e1ac2f61c12ade42c852ccf272a238b5eea0a2d38d470cec7fcf14538c71cf09f48e80f78c351a38127ab8d1a7c956c5fc3c8752f0f9c8ecf974c686b7e72ef4005bec8e395f2485f0c66c1456c747336b23010320a50f0675f7334d5cbb645d69157fd7a6a88972ea377b5240b457bba7a34d57266b5a1a73db56fc4233aeaeb4e5632225993a1179dd217cc0b57efdb6bdc4e726f078b6b717d833327f0f7c9a74ce5802a47eddd3f3a6ca17235b4985b8d9c127b99a5e552c820c4a6999541c05ebf87e2293a3e1f07eaf59b570626847b7e0faa2665eeb69137c79b64237ed13133bf1f5557a43d093f0f5e658467a80a7c72b7fcbf4eebbacba8ec80125093dc0542c1609f3e2560ca79ad733dd5fd2fa3092862bd74486191fb0fc485da276af053deca6de22e5521007208d6ff7f4f5e0882bb8e8996f9045bb7d760233692109ab9e316c88a489fbd965640ca92f4576a9b24b537bdec2ff1d1fe27a7349e2c7561cc0a235c41c77f7281f6f50995d05d41b1e3a8bd29c682d58e54e8810cf50232a282586ccbb223c5b085764327ac723e79f3e2474b8fa5dae129a87fa7be760da32c6d2d0efb13aa67dcf3921aec02512b9df13c769457c157543cf14a8fd25e4be5bcfb74f604a6f4ec8b7f19064b5f50e03f6a294a85fca72c5ba4c5a650a2638df5a2d199563f4dadee5c82684fab7581e0eaa46ca5de5836da4c716f38194e95f660573cb0974a871fbda982725fa12a3baee33f7ec94cddb6f838c093cc3d82f4374237658b41b0486b4f06729231753df56136fcff40375c6812f59571c7e36aa98fb619723f3d1e5cd1257226455e5f35a82fcd61b2a8ea2e5e32234c6457315c8d3c4b6a7ac35ece0daa722d4b712a47be702371218ae78b0d3a655cc15575542e1342d6b235f4fe40232817dceecdb19bf86bfd3e6ee1fbcf926655846493a34b61beb0cff3920bbacf12df3d72d2969ee681271310229cb8eecb0c25fdc09ad1722e7ed43c054ea91645b16c893424ca1bd657e6230bddfc6d443f2a57478daff921502e0e68af93ad7204acfe8332e90ba67a2224e038498a9f4a7dbe5ad77bd06d97f308a8872c367289af53361fb74f11dd4347d2b0ab624a347d082433009d44ab4497edf79b5c72f19c0844175cfbc7e3f9c957c0da5cd9250b54cd70667b964ba8bb0b899651721849e4b29d0121283bf0ae7ff06b28340a4916ec532e1ac97cbf083f1700327203ed69891c74399098fff9805a9cfbf87b950d11ff70d04671d399ee8d97f05dc05f904484282c072a1689717e6c8951be2f5381fffedaa05f288dc33d80b3720533b203498b73aa7876df23ff5c5d00c931a50051a3a37da730ea3cc0e1b709e4df36a4bb1822ff5c15b669b726635e0c7252d8c5917b19fdf6c4946774bd7264ca4018f242228e0e60e523d0adcabc1745a62ed18e50abdd87c3fbf7d2d20347e27877d1d60817529ea01f3f51f37ebd79e7cd75fcc421246ea57557350b72106ce885ff4a00acf10bfc3b285760887e185c2031218e3897e88febc7e76d72074222959f08e8add23d32650d940a37dd48b11eb3f112e2fd58b280ca81277253c903f47a8666c2276f5da840a0b8f93302e71f12db411f839a93c6a39b310db82674ce301b68b1cfce24b4f41dfd1e9e7b74e8cfcb13f6f9c0e80ce0f63b726738eb7be765380890fa5ac10b6d8d0353fd00cd40cc67e1d81950e11af78d6347cc0067be31fc7e8d97bfd5d1fcec2f4475ff8a9011938d725200ec50e8801d571f365ed7381ad3079f29990a29b4fd2571e8bf7821ef627046642d529cfb4db7e403741eae97237e1e60416ea6e881029674e5fc07fd708d36aaf3fdbd2f06b704642e430fc0e61a3ffdb853033c47134f600b5857f18105d875f7f8d41f72f26b5bce7f3fb6edfb2562372f090c1c91b2fa26db1f19455b2c533ef419be72f1eb8bb831dd49eda55adc7b6404de66197743d371fda72b96f8598bcbbf5f3324a6d7cbc8da466c5410aeac4cb2ce58e08a50ab35bd2659614e3a1a8f2db54676aea171d33681e9d0f820698ee4897d83d1f3410e168f1182dbb5b614a22372eda87cd207e55f9ca0d2cdefa40d357526230b05da050a4f3615dd4634a30c72b410ca77e402b5af7fc29c41e82a11d837dc0cadd151d816c82fd920e86e57725cf43c4e16294ad45cb730d89d77f7612759ae63f7a434b51a98446c2a4cbb72a0b471f92f981518aa3aece78a44b084f71910a7b531b24c8547f9bdb15d4e704fbb9d99153b59ad4bf178ee488599c6e60d0dea997172fbabfa612e758406239fff9ea2c670d95aa60df5d9864d72e3e7227e0097d0634ea200a2609ef8d44f8adaa5f2705172d4634a0a305648432228d1503db7d75c237435eb25b60bf872e6e9e84143578cb9e9cd8a409400953f7d5d5c6cd163fffe911ba58ad9c80f628a3dc968c55b113d624f65eef4e5aca9f17e05e53b4ce3dc1a63961bd243bf72da12fcfe3390ba28487e3d64a4fa4df66ef19d28e54417fb90a81ff511265672d783f323c6295f23b1b6e52e0de3a9ce9ff0232a1049c8a738ab4d0dd20af37293385b723a976865e512a54c86202dab9b404aea317148e7cf4495df82350944616d7af75b1811ebbba1307d42e0660fe7712c79aaa6681fb7f931bf3e983572b9123fb814a563e95ebd9d69cd2c6a98b88fd2ce293ce74c409096200989eb3dfd0a5e2914ea350c4601634cc15e69bef52ecd75acb12959d38d017ddf651772cc149c7896b0c474956e6706f5e654e6a22228286ffcede3de5400c49b11db067e0be100c5140161e0d70cd82932c31fafab2750c21961b36b79a036b2e8d15e008e5de5d4e042fd9fd2b75effa752bdf8ee09ba86c80fd081d8e660f9bd9b54bcf2d1dfa15fc6173b0e20725659d670b3002666874c51b1139ed81d051a9d72a02a201c98bdf84182da8d4dfe7ed58978b64093466a5f023709f626c7d8ce72a16048ef38b8026ee5c6366b5885173c724ed780b123676acf9cf3aa4a60a47211ad69e9548399d4cb4d6fa96b4632f11886c008302f8586744b35fbb975487211f36be370e2c1b0a5cece6ad3f207be31f5ce2baa063b36c014d5627371a9728d2429ae46e6650dbf51bb60fe5ace515de005e8ddba58b6f6f2c08619fd9264b105b0cf968ae269ec25f6ec9a207cfdbde50ff2b14f1afa69f85a1338cd6172f5221fa0b6b6cb95f100262fb123d9dba40211525f9612a61d04070eff5bdb72bbb5fa1a7b440bc59b70e45e663d1e225f41168a13fd0e685a76ba91604f960f793eeceb3698cafc2cdbb4400a2e26bf26a8104983d3b294a1a07975e2bed5727a4f290cad6166c56bb53d1519d358620d79cb59c525de07cfdb4e3b7ac02872a529018ef4a91bfca4e5de215879ec1e85c1a3f6d254e8e3ef4a4f3c76cc7272fa178a853da90eb82ab4dd2a27c179bc0a98db8543c837339d1d54403b1ea872569744b3f16eee62c9b7950c1198aa8808aaccf780750dfcda9e65c8de9f48728f7036df9c0836f0c4651551f543aee7543c59a8af803e9eb25c22477cda58728c23cc301624c998763b25725d9c5f85f8a872a6bacbbdd3a3b9767ebcfb74108ed945cf7a01645245d59ecad54f8e86f5c5a10640da6cab7143f6f593f3dc720943ff7512cef0a990a2ed78c80d21dbc1c5b93776d1d99c156e53292173107205833d5d6096c233a51313e5b994b1a0dafcff879099173195f7ee5a65fd534da43987c8349f35d165f9aa194e4a525d112b957e675ea32222209468afbbb872bef363917dcf6bb6c87de85224c39995ad940331766c5983905fe8c314f159245805b8947e3aeaa5c65527514d660865939142892f61a6cfe2f71e20a1ea9d0e6f6d52cbe45ba10ddf114f08f97213a9cdd3e1b5e971c31b4c5643cafa8e0a09242b385963b1a70f2d0b54170633618c680c2523417985725e82fd8fa14d09723f48bf77d4a66ccd4e419bf390fd47f0a6c380288368849eb1f1773d98a6f072b4fdf0a2db6826e8cfcf31af76abe89f8477af59a2df252423127d8da5097c3b0585593ac8c52ac9dc914ea00b48882f371b2351ce554c11e9fd43eb6398b952f204d92ec3ad150c70109da6206bb91a5f606c20d5b95e48a1bc83850b8fff607fd83a5c3aeb994cccec52af3d763840e5c2f540e29bb247f3a130b045c0a77287522d4f7d2611d5f9865f7a993a98d30f23360e098c7fa3bf19dfa455b52f721743d30049a60815c85cbbf9313ea600b06dd0fca6fb65df6c0465e40dd1415fecc859e70ef81a0392fca52f04e3322e33400a5af41610f0397e929c79a31f72dd5b01f1fbb23d51cdaff96072487231db8312f77aa7ad6132370006ac5cc272ae900940d69407e5d21e4e92a64e39d70e164f6d34f5e849e376b1dbdb620f721425b391be0eb5a0fa0229720b109c64b0ec35f5880bddec1f58ca076388ac724e0ff48561d99685411dc30c39e81982d7851a8b44a14bcf8922acb892b4ef721d32dfd0e23da81435ef060536d926008dff5108f022265f8c4555add66e5d55eb9b30da330e743f474ca9812ee2b545a3524100c629950e529861dc6cca780b1c82d20da6f516614c50cb4ac597e73a1ed2a1e72152c8e607f952697cc2d1442397c393e50afcf1a29a63c10131b277d7eb78add92b7a0caeaefa1e5ab04572e29c4e51622436ce9b71dc0c7e6db63152b0147b7517311a60677e9e8057cb44d257c06aa32d66b368b1b03c4698ebf4618fba9fa9c1cd3c3257fc418a1dd05bc1733b009ccd1e71865af3ce42f1883d7ba7abc43daec0e7cf957d3af819bb72ddfadd6843999522f0b205c2bc1e1972229b202d68ca48f3c47699182490da52cd34593cba94312176747646fbd608ff5fd28cee5ef119787e29091a90029465c3d432aa3145d5fbe153dab052f17d9f560e87aeffd315a6dca603c2f084887275f61810416aa07e8fa2fc012bcb2fa58db728e2aa64b80046b3a46b6138cf726215a4b8aea34996611db801368811f89a0f0a31021b7405444b4ad7f33b903a6e3697518dd8bfc385b8d22523ebe24ee88dcd73f087904a66d1de1b577bb7278013e752731672c6961b5f3984cd21c21553adc573ef33c097d6df9c8337ec72461d8d372514cf29a7ef82e6cd1d41ba6435aee34b530229416fe98229bc1634deb39abab4c59593b499404b729f9f40922edb0a2e37b0a85e9b26f30326487284067810362d5e2150b6b350afa493d5f497288497c193ded8c1815b34ae6f722d9ec77f021b5fccf62f969aa661a8abd1347be82dacf790c8a62c7a1330b145635313ddeeaf33175b4fbef2094491510102c71093bd18493d2394d958b4ba727765162213391131fd630011ca6e51d78d057432c7ab972e9394ae7ad2b49f5a8f39ba91451f8bdd2e19b0596a995f2eef4a63f8d8398cd04c8b72a4f26eee348996ea2686d020abb9871ab8724b0b8695f1d744fb955b93537c42074609a35e4d4ae3be27bba6760a7f9f9b501f093aca256d7c333991cd020adcbcfd3faf724f97f4bcd112ce30a69053b7764a79a971a15b3671c8d9c2605c5585c912627277bd0264ea3613d20a8cfb1e93bad75a5b8aa84c089a4c0a868d51b2d4b48b5f6dac974cb20f28cdfeb49587679f8510f823755bb0a657efd6826b10c0d29f72d404862995156e6029b9f7bc67499f241b2a7a24bea95a6be5db42f20483903f0e369c8a1250ccf450d820d7d2a5faac71ea9e5af02881a1384a54252c2266689f79db78ba41d5997c84c683d9ad5f6574149b28269d54c748f2c73aafba8d72907a7857f70c0d71c02d90ac15a31f3489e0dc478663a3cb58b8ab198f4ec465634d4221a1792d6796b9b66106337176b76208fe17dd6a2a1a6322df6cf6b863cf3eed24181b7c688c2d680c136c3f8fb1ac1f7cbd799d358d9ec3b7ec261d3a12910d5bfb4c70c5ed4aed594b5d023cb02491f16139cf66eacef757d9989e72df5f31ee5f7ffeff1d67a8de0feaa164aaffc434e84b02eb098369b9b50b9b278da0730ce71b1581fd32e61dcb4e3f4a6d363b9dd643247253c5594be31ca67239e145829079a7c3ab812e70fd96b7b4f7a43eae01c2c1acca9be68b3bc8e76407168c6d1e7c41578beb4d4e96542c36d651be3de93b261e563b8b43cf07f42103a10fe8dd9afd0a67fbf4041462361e89b1e4ce14579315d7ae14395649417264cab8b23d370ddc5c2330240561cd3f9734290827ee5a878e498b850ae2237233baf419c6576c0a76165464ebef22e0a5cbdb7944aa77cd187dd87fb5df1d2c984e18898a307c1f48d0bccbdae1f05d7b20ddf2a6fcc1b1a5ff943912ac5072d42367f50d23f09c2b675f3d9e21a7cb38877638e47ca06c809ce394f75c1b652c0e870d7fa19eb1062d3fb870a06ce2a1cf8dace2973646c75bd9c1dc6b6d05266557d7c73c666e41ed11726d6a90c11ec32f59ec9a89386ace23c9ac49133f87a6d65e0e8b23af76c7e7fcc424493ec3105041ea8ba21df407d30033a89c723a32b9dd56a41b18040c4b9b6052f6da84ec7f372b83ca9df28d997f9435d854fd8168a382b7dac9f56b8b1c23c8f63e5410a834a897560af08a35e5b6f4097226de17ff9b3ccaaf587748a1b0665d934f4ade8d3b62c024f2fbb6b9f3e30372132a76520068c74cefc56804fd9a342b0e26f0d93d1f1ab379f8690a4f6d05728fb948c4239614636e427d290033bd2c7c8a88ead08a0f552d946af17833e272fd8a329360ee08286a426a4d18ca1a291da019ddc0b535c24e70eb72533c917219c46e4174dbea939c32d04ba6fcbb67c572210f225358311c81bb991259993200b5a6ef891bf1d0c05b9cdc58bafbb55256d43d095e7708720ad02a48401e726996d1c9a43d1eea7644fdebe266578e5b503b1354176d2ab53189ee98e04072bec17ac2af6aceca9850ef42a906c24d28506fe14467794b0d4938b73930bd72ca3b318c5e10ec364a4440de431e05c7f1a20dc3542f854d27144c36ecd7f7712bf834b261b187d827dfb3e1a1f24d3c281acda6da88a0026ad99823e9092d72d49718e88bed8dfeae13476af901cff52e590d7dfc809f4f8a3a3216b1d90e42b41e66c9126190d5127749fe4e43357b2961688b301d4dcb0a05bdf6f5f4a843fe89d27afa9640ef87064079f721e27e3128a98e44ec3d50ba9b5206c03d177283f763810b530919030e8d9c1b3e45f4d1408e67f9a82a22e7ca371c1da10c0eaae398344ff8209cb7a877368c2ea865a3c08e58294da89ebd1546d8df02f8725bdd784f1169c7bbf01b921cf86df78cd812d286af7c9aa40648bfa5ea028772e64d1d375bc83560a4f4ed48d4f8790bb9dcfc0b2a4f9278bc5a13d5bc051c7238f3e9ad20c9845ed2856bb66ffca5bbb9f10a374654b5b983ca287a5823b145220aaabdebcc1d406d803d859cdfb7c1e9ba60115b9720e4339a2f56cb33192821ce6930e6d9e834bd009d2b812267315ade0eb0dfb0893f7d467657ad1a217222d37d75540b1f825fb8af9b32c8d66048e024b11e309350e491e350627e6d7244a5a135c9a2abbc484db479aaa6d423c45b1367224c2d0f582873d27cb5357231a2d54c9a08bdfb5010e8eb4febc39bcb45fb69efba48f513fecc8bd60d0872dda85109fdb450c62c691a65fc48e294b9b39c840d15b4ea91d3cf65b8bf7572e9f8fb920783b4e1e077d784b54584fd314045bf6312fc093a8f3d20d873a672dde7227e521a41aa1ad249b7394c5c6819455003819e4d6f9247bb676bb0a072bff2e5a5f0cd3f149758467bccc6f9dce65a453e878927b5f6c709430bf5736292425def4c024d99c4f3e9f7838218fe1e6cc0669a79550d8386672629693713ff6bb75a92d04412049b420ca33fe8e8e546e668d21dce853e7f5b508f5d825f406c099069329464dab855a794b3cd068a310e25f86a9d98900a739ac6a65472f73f3c6d19b2b79989959a5da080d878c53bdbc61e828291dc871265a7d0d63692dcd93d8a8acef7d1e697423d42aeaceb6fe84141facb483c676a504275ba44df236a805c9b5df78b196748f17601df0ca4c1afc5a914dad17b8653798f9f72361c14287774f08133abc227373e3879d5071cdbc071e18e5ec0fafdbbd1ac0bfb7111bd399e9730fdeeb6439c7512ee4892889b6e9eefb96ac5b6d279487f721406c00ec2afc6555d0ecb2bf9282b6acd733ae8a7298d5a0be93bd44ce7ad39300af7efbab796f1609e35c036731ce28c96c01df2f9001d887fa6a69538e972c5673c2adce4de05eff8261f00cd857a71eaf0c1a1a4386ecad9763bbd881c36f6326ef0fbd36ad475ca9f1200e6500a92e0674cfc8c6149b31e96758ae1ab5461ad54cd54a5a3d9f8449b2571a665083f463db90879824c68803a66aa0d01728e24eab7fdf2d7d32363edcf06443ba3f9c98d1ae9b13e9ba501b4b160a8b96ae77871e4700d67cb0d32ec45d692b51fbb5116225d6f9b1f61c7f28757b87729cf02227b0a4055e0f51eebfb0c69a4ca7b2cb3cc962bf43965baa5d6fd114072a4da2fdab206611f68f595f0c4cc3566c1e397ad075012a10391a45c8ab7e33fcd42d9af77dc9c6a4ab938dff1d19f2a31999643e090d912ed316216f3bcb754613041017183c9125bcae3e2657795774e6e7e0666c8dcbe1ecbd1f2d3e0f572b205e7a23ce70ed0e6f2abc866fc377665be28bd03725b758fe24a61397e0047c51bc183c30cb1fdd4ea8fa7e52dded62597a8928afb22cf0dd2812c22f37e48067e3b1d6813fb680a6fadcc22b5bc174419e2233f71ac63f9065f76ae55fe726e775885a05c55af82774e7b5aabfe99616170e60fd2cbea06f8248f76ed5d72e8fd48bbd600fe4f15dba440dc913b8aec03a1ad441e43e75bb910cab4f0c57253ad35d87f713f2fbe9caa76d999e8b30f9af7b963367ede367a413961222e721557f41d7df534f14fbd07ca53aeeaf1973e24be17befa64b43b2e946d14674a50f14225f1fb4a9e35beeee055c4ed80ffc3e7b046b5e68b3c250ab8972deb525d049984d0326f1a96c358ad5bbb49e087d84c2c406286efd5f9d36c54ec862f35e058b1ae0e76996b882b9ff6dfa5d50512ec93335778f15c3c841107ac251507aacadd748cf46b1abfe11281770acee830e6483592659b583dfed2b4cfc3720199a2071d76b5150afcaf50e05ea92ee394704ff1d82d811000fa8674ab3372541b660701ded30ec14862407726e4b5dcb8e23e8c75691f472da2f77b12a672c207353ff3502c2fd4ae2b6163b69131cd736a274f7d824935946c7b3f2cd172dd4563c629d66df3f0e3c5023ea352b7a26ae017ab850f5617279069252c5c0bb2e7700e2eb8877a5e4eecde6a441793fc5cfc92093811c2c7660364e7fa49720b943e20170b6654393e98fcffe9e20c3e464a03acbf41929cbd76898499bb72ebf000e16bcb010e76e20110ef57cbaaa39506c30f7fcc8438f95d90d01a7772b5d0eb3094934de2127c5adfb944ac46ef2872812979e0b7183a1bab190d05723935df2c8551125a18109bdac9b5893f6df2ee00d039e0f53e36fd25f5053354c83a020b7538d97a863e3e5e2d0d00ca7ff969d9091d312d18958c373588cb5f49a007e697134650b8b479fa9eb196a4cb96b0c6e484e8511ff97e0ff7bac74450e910858b0cb02bf29724cd6221a67477ef4935bdeede3a4aa384d44344df727504d53bde719b97cf117617ca871581035239733d7dd8579ee9db55f5e73f726823f8f56267407c4bcfe3bb74c565996da58013b0a784ce8f932b6e0639c235bc35ec2be3a0c1ceb8459e92de9bdc2661cf4a841366201008842cd1d562127262c32afd8bd1bf12224c01c9b515a6a19bbd766256a49fb6118d53c808d30262b4051e90fcddcf6df4b5bbae7ce82c5149af8878ad10b3603d8ef9027134a1004bccd6da7465cb1c519b53c965228a5da7a79d0c8db6af1faf3238fb0a4c96722a97034a52f8cc4fef6fdea744bc2ad30c6d20eeab130a83bbae0b2b25041c722cdcda41fc3babf37e623299a33479f85e6c09f6ffbead3137edbcaa8def7072559ec9844e5fe6d2286cff9a680fbca4dfbeced431379a15a06365ced9fe94210cd92cbc2f94bdda9d25fc3014b4c710a56ab7f79efac1afb950d0fc85d3a73ac2488928a5a5ee89b83b04826677dc30c45bed4dafb71b5e2a61368e5d039b53fdc0747df13b07d709b0232c7e93e23e9b800225445f2dc00f601b842bd10535b96fd79a92181a3325007a7c9c1dc9d6949b8f7475603e8c2589f3debaf22b72130b3412e25b865ad62fd72bafe6e163e830eb8aa76586c7f5b6aa8ff13b141d090dc71b97847cf55080485068cbfc3ce5861071a86bba80167f84011c83c8358dadee952649bb12ab311f81792c3e674f91035d4e4b0dfdd79535bbd7a1ea721de25d6a679c62356dadba298235a47ea655683f2b5559a525e382fbfcb6af72d3a64bca522649e986f6099679b5932a86f8308f3c09ac4abde8d5fb5ee3454b21eb1c9f9aa60e13b1cb499689c7b02da512eb377f4e1135dfef8e448afb754fa7f798427a15be11901f8497ccb927bff0c7ac99947e9983629e42daf9023a721216efba12da1724468f62d096439acf292cb9a6bdd829655671475e57ec6272db5af9871979656df708fd48aa177dc37a50df01cd87697ed1867bfb5120cc722567195d7c1b12bc30c6386adcde5c76b448441b6056d39a5f7fc8c1f1023462da6fa93d5e5b69630fd1acbf46bb94fc1c46c68d119fc19ff852fb7bc4ac827289f1896314bc9abbd99049ee4960c68b8683171e06751f2a3ecbe79b28956e72bf3b4f2236fe8aa6f5bd4199abf325e37c3fba9f29be1e613ddd4ff77725d472e30e1dc7a1abc109f35e763e463310d063ca386d6ce60a5596a0a93c992e82546bdb7ba02c3534d1e8daa10228390ef4450de8b8c9b8393076b6983d2ce3421f7d23d49db7586ccbc07793bfa55524a9f07c4bc5dd9b9120aa84e1324e8bc35f59ff51c9971867cedcb6926bcd68ef79f9dc6629ad1cc8c08052a434d069600a32c2072f2930ead6d2d17bb95562a72b6d0414ac1bcafcb9e74f4480ddb56a418e1a243a33f149c62dad1d776f01db138de4688eba5c83de93e6df70e975777261d80e35f74790270f8f0d7d63703a292718f1791a2548ef2ef01afe9d6435724f34745d3ba04a78ff18e89c716c914118e183708d44539ba8df09a4eb0e227214fb75657122e779696bfce7c1d4debd535da8808e3d59052bbfd3537e9e90729e3659baead90eae0dee75194f1b3974f28cabedb6c04aac8f08ca733dbeda72c13fd499027c72d592942ab55172cdc76f8b83a21a7e105ad6632fee2408407211162cb110d525b219dbd685b665066c4bd9178166e80aa532d15bcf6e17956d9470f9c085390e65d5f903d30704e4a341ab559ea4a4207c3ade1b163fbbe972471658bc18c8b675e0fe18a0533d3d5c2d9e77d3efe56ce7934842714eb48e7263a3e1a2097b15444bfe8dbfc22713a80dd8467c2b88ceef41c597f324dbfa68a639317d079abede30ce36cd82f292d4f2b288a62cbef302b6bdfbe2f27f1e72c4df6e52d7f9672f38b2aad996f08bd4077f946d9bb84f5231453a89d8d3b4725a8d19c965c3b4799c310b7243219b4bd13ac5acc918a4bbe5a27f44de4a1845cf2b1ea112f8ca6e6d63b3d005d9a696914e37f48142fa01c90590c20dd83772baa95c290b38af11dc04a20e45ef7076bce1090199448af14c8f4c5692fc71722640a0cde98dd3b4bf84ba9bc16e4f6e6fc79aaec3345c9078170c7dd5fa2f72bd6375a39c83b14c97eacde49b604d1baa10655cbff8a632f3b790e5f8a6147260ef46d1242f41fef599ec86efe8a539b5514d3bf7958d97e3546fab94377e72022a1517678f443f077536775940997e7c40f759054da54c0311fd26ecfdd272566fc3287b79ba362558fa75d6b6b4ced559d8e99880b450b2439c124752940d6b768b14393b0649242e560ee1a0ec186478480d08f493a6257e233f4552d76c30428092bd29a2b1d83582b15e50f04dbf03035ebce2146e0e298038e34dca4554279bf976e044e94b4ff86cd8ccd45adb6c64d55365a61348577a3f2798124501b2c15288b3b309932f0d09450484b1d0ce5094b2ba6c0df3be38dc35708b72f5a4519a9122a87537140e90c4804eda345ce7206103380d47e80c34ec101a1315961b8e2b11e31e69c2f9cb22db657c3d3b40c6dee8ff1e934611e1c7d128729a215497b88fd6d3c3076e5d600cff9e342a0121c70bfd8c1167fbc9a591d43f5d7eade3cb01053ac6d4cec6e1d3fa5be9be47bbb7242e6f52d8218fed116e6dc26a56bbc7a00038e5848f0702a62877bf35b5c42e3da3ded0d5f4ed6112e93fdd302f89863873aa0fe871dde810eb154e3cfe65f09b264a8a39c3c5eac6f26105e3ce1a87a37268e544dafbd053ae3335808d5ed05873ec2caae21691c0a46aec65a8b13d0854ccd4c9b1636f4fed01e438b1bf325d625ca1e746f140171e723cf67a095646f4c7016c4bf0d23ad890ec582bc9ce9ba25630d92d2a1181b56e9369e0f6e4415b0fb0e7fe4cc7989e686f92f7b5a4dc4849e878622753c78f729d707c4bad4b9973c6d1a6288ae2a90df90abe49e9c1a9262434080a0c046157b42db4ab453b4fce56a0e72a7224425e9ae55181f860597843ef4fd97b27537268b3154246ff7452d3a8ee7d408b58928915c608acaed344f6c52e539e6a720f981efa7865627766d77eaf5c5a2d61fb0aa25419c8847f50e33532c556030f29549df00a11e1f791a665230baf3e7c5b03eade923d2f138c5272f0e1eadcef100cd7d58c24c74dc58bf450af33021ce289a74f481c1f1a16b7437e31c4e5e07236b0d0ce49c1a626bce5208fc2d085741a4cb83f30bfdbc13d87ee7973cac972e9d2d0d6025f97ae8e11d33e1758abda9c621afda6df33745a584920eb19de72d35fd7a7128d1d5814e49320eb9fd59203f34a1cd0c4c48db6e447e7aac10b72598e093a57ddc144b7df99774bc8c916516312d83280da6fc040a012416a042f3893125dd7e63ce23ee5996e4b7fd2454887c633204ca1ecd0154412cd03e6729471503bc9734f6cea5f0e5c671a90acbc0abcc90f5a96711a06ac12030bc46457ddddab1a837797a399259a9e32faac995038d05adda9439ab20d8fca0581724a6eb72e34b728466759e3b3e38f49ac9b598565508d7c08c7a7e3f267b50d72133739caae26ab8739e768a3bd7f2c6d7ff7e72c206e7eefe609df6f3f9f133b3a9cd7b6d1c03638e649184be1a53a54e237a925b518288102e17e8d3cc7f2723ba99fbbd5804d29eb333c546424caa549d030013ca24fdddd88e8d564e06b725ecb14559584802986e460807522c249cf6b0f720108998b4a7e4f5a308b28729c538909f555dcb1e2e9adf57656060ca58cea55968b34fe75b05259522fad72e39906a2686ce945eb3c61926f3f994641e21b7dd47dd0b1bde9fcf7cc64ac7233c76b3b7bff8e1782cc3df08be57d7e54dd0d4afdb97ef2f4ceac5dd003a372fbb8eeaa3c7dc956348f49ce14cd8da66fc1dc1242fddd74bedf3f18a0c7de61b838a299496784a946841584c75a4fde7a1ef5c8ab01f33dcef5da1e736f20039050fcd3fcbd2bbf99c687a7857dec20472b41f65369e1ec2db6f0a4b94db7339d50a42724525a99bbf2b1bc91caa3855bd81e449fe8a80c50afc7eeb7fc6a72cab0047dd836b0dc65fbd0ac55a6b42ebb8c365056391d2e842888c740fede1580838da0bc750df9063acee75649166d796a472d9aec537137f63e7bbfe77f600a031f48e5052d4a6f4a084464b79f1a36438ec9f9652db829483e82014c4f72013dc947a64ea8a45fd306b402b01d6c243ceeb165802bd0e49c40949afe7a570e08f48ae082e62bac88e482acb6fd285012c27dd2bf20c82eecc1d2d402ae6072f77ea883120b6a6556a0194784cf0639dbbfe8e0ddd455ce390de5e871bf725866ebba29a0e1b4fd7648d45605b1db287fbca25f4bcd847eab7e0e5a6ee8727958f740b85519dcbcebf9d57ca1f9320cc2c01101d12f1d50231f3cba0eb3729126c9c1076668aa6474e4b02175a2855748bbf0ca0424bf67f8e41a934d2c7234a74bc5b021798b1bb2f89ae793e740f3d3a6b3257f42e8b2c5212bd3da2639a033db72f2d9b3eab720d3b151817b5015687bc07157c13bf1d9c910295838729051c8bf24ee3aa3903f17d0ca7410bd6ddfaad0a77bab9eb10e52c81cf3ca721a603a9387b39e6392ef746107a85d463c2191190362c8b9024faab7d611b572ee0bdb3e39bbab2d3bc260779beb617e243c2cb5eac52197078a7aeeea28937218822944cb680130406197ccbf4527c18fb54cac18f298e0b71ad552e33c01720e447b4fd0452aada5dd537d0b7e0fb7d4aaba2af51f90c18872afb53e2bac72aa20fe916ac999a3aa04a1bc25d62973637ab146362372c7e17a10da771fef6ae6e3adb11264b9b4fafc08391f012f96333314a5944d600dd3d628b286c0ea724ee74008feed5e19ff1ff55f181d061ced9b5cdca10ac9f6390541a0486db1278bc1d3248f5001d8c4a772c242d8f2d2f9380f07c47b684003fcd3382446e3168548862edb3928b59baf82386a861e4274fccc2b6adf34c2c00d1e8cf2a3763866916a8d2734e9bc0202090337c469fb88f4101a807fd6f67bdf827b5d0336725ddd1560261a440a4ad619a476f09ac2d145eae655990bc9aa8bbcb204f55815564ab584c2079c1cd214155eb9855c4d87ce4c9d6eb1a97264e404d05cbc096e8ecd22166d9dd26a28e9f1725880f4a2d2669ca5abdb061743bda5ff3de6d3658d0ff29e042c9ed042d5b1b8c0f6372e73c861b5a9573b282f05d14f14f97a7217e96085569114d9f771e2417d42742fb229dbaf2378fe4182b307bb3a348942d234c830789da0163ceb13bfa441c4d29f60a2174fcb04c46c6427085ef2775c8f2032dc759fb145f9ba8b2661b9ea6d6b2060989420971b9414739af987bc334eab67ecbe92431b15c01fd2c2256b16d9e2f3018542c9e1689688f21d8b85482506771f1e77577f950f0ecf76d0f8f7f010c7a82e31f55afb1523ba3a03f972b62f94cac36c01672e70dc827af5b0ee768e10a3f2c22d689d64f57e30023b47dfcbee05972eba584dd048274ba87c93918b22416a135babf63a09cfc344f8431034893bb6f9430cf5fa78e9f18e3a9f85a8a144c22b96e8fb4e9526d55df9729c35b3dea4566652dc8b066cb552ae3c93ca2bcb29dc107a7d1e9eca189ad172bb9f0836b22b79561cdbb7250bee21ca251a4879be491633579d4768724d24505de9f44dee2e26409b7c48851501cdf96603cb330326a0d25614a96bff91d3720f725d59c59d376fbf1dbf686c1c0ef8583159564d20b2fda944b124508bfc72af05346a661983d6075e5a9e9d34a73dfc581b9101a6e4c384a957accaaf67594bee6b301b07729849254373e1c41ed84ed0bfc8ddb2cfcec5804a8e5f59a472e7840a298399b35df48fe67aa6d0f465f1672866de3404211a74a864e11e55724153c73b02002d6be6a28f34a40d28bac15f29282d6807886149fc75e85b84040b443165accbdcdb07c96c8141282cb8727ba4321e937b87464fd78063bcb4728b70c0ae63c3c7eac16388704a4092c167023f65af42bed71548b172ee0e18720f1f61209b1104be6ad6554a3dfe4d1c40bfcb0657d129d5483b5971799f3e37292452fe0d644c6595fc202037d2deb5333cbe451da762aeb435e6bd7f98a758c41e01477cff86745f741337c7b4f35747c5cc8df2c7e11be416866a85327872811e12a42314ac58d924fefce6ef10de3546b90b71f60f2cc5addf63e955b0722409d5817f1185598a6c08fce097b7a0e82505a5e4bef281e82b7b5a98175272df2f94008d5a373b6c78f5c016a8df084325229ce0b44d63ecd1a66ffe0974725721dcd3ad86fb2ab43371ba8259ff54fd37b4df7321b3d2ccdc68bba227e54ba9633c3f22ab4bac29ed353572c9a343e44f1432d53f84efbee63ca20abb2a2f31fabeb153daac4dc6cf488aba5fa099dc5f91a21fe8db5f1266e4d337a03d72f7039624bdfaccb4dbd3d763888a0be05fc9934165feabffd794d5221a809a693fbe01dc70559a84f5f42a3d5034a32c62ade0b12f8d580724b6c27a2b50f4728e68afd8488f5ed612de43c422ad81a7a4f84a424c4185e82f22e80e29b32d72df3e12fde701db6969bd9aae1b38ed454f3e92774f4bd8be1c40e5fa1382457235b9ae01bcf43dc1df3859684fe96bde8ad5bf9641646a0f725579626f747372aa790960f147a9b44e4d14745347fd7d3715e7cbb0942127be18a5a4c917501bfaf32bcda60e0274ec140132258d0ccca501271453696ea8972625f03e1ff2723e733432e8cb2211910d52122c0af652c4fe77ddfc95530e5f4382941cb8e172162c2864ab8aada1aa14486dd779cbe86bc3386f326e140187d2b1f6a6ff8152549b5b64c9b2fe7859be494e463e3a9814b211f8d93f751452dcdafab1de2908185eaccacdd7178cfd7e69b6de14aa3b04fd613b1015b0692213a62aa302177241836f586d3bad7413f10dc3635d3473be895c4bf41161d3b373829428127700a044a4f777b947d827d93c22b6bcc1b0de1998789d937fc4bd47b22d8e1c803dd03caa513559339db2c271c307db587396a5a1e39b549cac06edded5fa245c72a320032206ff8b07bcd721f52a1210d5912efd6507f81c179d5d02d442aebc55d5f2091cd07dc46fa2fff2a1b524947249a559e10cb30a05a1a0f2d591122e4f9c707fe76bc14fd01e1608c584e479f5586dd05b5fe94ab618f1478239550c72de8c357e4589bad1c23beecb4b46907146d4e0d411a220db0a07e080c91f4f7224982514025863f9f6728291b44e423868e9f85a1bee4cda138e92e21cf3866b13c87f1e46f32e82b9f37d242d2f66053aa1ef5dd80fa70e32ac55e6b42fe367d40d550b2d5852cbff8166c265ceea496f2380daff4025cac9484d13701c8572833cf9daeaeed5354c053fe7a56a374e9d454154d4ee914b8202fd30ee34487213efcf1b74e3bf83905ab987235095e1ea6e5a7a0512e43d2e623e354d47d240126e8445c5dcad059fa05ce42a73a85368aadb2dcdb0de5646d71205c169ce26e9a60164e018208e25317c45a38cace40ae462cc6773336e76e11d1a4c40fe72c3fd01c7f3906c7a0d323aa8b8fe13b7e9b77c8d0c801abe6e1dab13119216450e66bfb36d05f3ac7d353f4048492c66c00c67dda44aa87675b3e32fe9cb4772d09141311b98731a3024921243330fa05629023c98683136d084198716932872eefc75bd34209fd7e4f45e653c679ea17c4108ae0b5d925ce88a2fe0a724187238f4c31eb5a726fbf9afdb6a760aede41c8e36d2b84d01a43e41e82711ec6c6f66025904e40203fb459f07ae8fcfdb145784d25b2fa348687840512bcc1a0e72113869ed23e9924db782c8db8fdc2abc44ce2d6a6bdfc792f59bcb59f6316236e4290a8dae85cb1651713d03af11b3e49de7f774cbf865b9d2acc6a6c46a1929819a5bcf76162b2a4daf94ce60541a4a2c921f13ebe551286a893410ef0295721f99790b324bf7e3cc06404792cc3d566d6b0641434e9aad5945e9b172007672341b9c8b9be1b83265773fbce95974f38cda1859b33b936c86cd32b7ad73bb7227669a6eb36d9fa7eb1e5f446e381b366e4c04b9d41f5bf09050b37a22072472f279b55987c382743c81bf0bb91b68178c209c4dc9a453f868896992f643a272c5fa797ed7aa495b500a67c82394dd50ee9eb7a6dd185a918d0123e3b39c76728f18add299cf248e032d172a83f4b93110faf30939f48021a34e12965c03cc476bca548d91c1c2ee4f1e899e652494906e683bae53f6966211c36ce425d40772f889b9e0a7c7f38b89f11acedcd8710f02faa2bf59eec9ee2b862b6be719e372cabd9f25520a433b0f50e2c363496c9f42332c2d4d50e10f3466937f1721d1726c3907db51ddd3244e3b7eb1f9e4250a233f15db1ead7b64065ac66a90a9c7727342eac56ced846d7448b46cc92fb976e14678a05ff5432dec7447120e57a772c4974269b55e99e6edb129fe8d2ceaee548bf44e2ebe8f8673e2e6aabe8721727b21377542b64506e6662b694d84d7f5b5c03f5c2f94e640cc8e1910583ffd72d10e73a9800e71ca9ab126cdfd1786f38187c669f047f16af51c68a563b46d55c6992de7407e1439faa7d66497ecce4eac2037506b55c0790e9bb984cdf72e72621e2ff9cb90670ca100f4e92984c4ba18deb0284799585873c13e6bfdd4132ce76cab2b9c708a4ce28c9319d53fe43aa734440f27703f77e175294e369cb07237128ee68a68326acee9f9732553af0406a9bb5827e551c0b4d08b1d965bec66c67f6b79dfd8329872e2763cec01a363cb14533d69df97c912592db5102c9b72b53cfda191c76e59595fc5170bcf29b0b9e99e96506effe524e99c4829c35c72ab3acf35281eba18d4604830cdafeaf63dbddfb4689bdcc5c5df0ba11aa655357f2e6a8b0d8219e0815347f102d03ebabe64f8c18d71cc7f33f6edaf375fb17239eb324571598545a99b02d05f6d4d44c15187781c6258a65fca15c495079072e44ab35ed72fd06628fdbba0a674076425cfeac04f69ca2b3477c2accc25d3720550bc08083c2db4b7e04ae39b6f1e0320e8e4564e21d5e5078234152b55cd722ca544d3d41065da907966a89c97de7c85118553d164e35e4f7c3ad99ac91a69b4daa85a514e0e4b605eb5ae945c56d8db7b34f620e8b2efdb3d219eabebdc056eb8021ccd68d8a92cf6e80de1d5886ff4dd198cf731132b832444809d410e08c763eb07e43a6dce3fdb17459da2b0069d2df878e46cf86226c29a502e445572aa3bf4165ab3d2f2981f23ba651e2c4da2aa77a23cb880a6635fb7ef5280f972e15d2e1bb0a129b47b59b6277b604cf922198c4b6f15c06e5a6fe43e6feaea728a592188b295bf2ffce8b81d4a9e67fe906a53900480160918595810e92f3e72b24a1f2f717182f8f3941af0d9d9feea6157bd3816ff950e42176c695e9776621bf1726d0d83fc08f0c2691c3cb0bc84484a8ab490c127ae21b9140cc7a9c81ed9709b74228f51c8972d2fcff3cabd5561bd3035acb3eb14a9a6abe0a75756723ca6fecf1f8fe4f7b8a6133d5ae6f0a04a0e01b303c59d614954cbf6a8e66a724f295436c2a942bfaf2b9feeaa6043e0453625716b028fba13c09e6c43fc4b3c3cb0692b9e4d7f5b990fa96dbee71cc5cf2a9164ed3ba2685f1226997f0a4653d5e635e7fdf595da3a8aaed50c3372231a8b6705159ceb35e1ac1035a1055a723b24bd9924054c0bbbd0d2c7a90515b14dd48043862d7ed320cc748a1cd355724fcf18df0bc5b6b5c833ff611ba6345da5c00f04b4efba7f97b79e194a2a66054f47e42777e2862c1eeaf6cf4591bd36d0b65ccfe23025ba4ddb9ed583c99672ee894a9c94eec1137d4ae3d3fcbb2aa9a24535a77047a44ff200cf0f3da7a922659de72646fb142dd4f5d9c7c32ef2ebe1eb0ea57f27d666abeec9981a7d9802e4b59ebd66d7fb63e6951a8528a705060fb135a732059260122a6493be16967203601f59a2eea7a7602fd8e88f67f2943bb6057cfe8e5078f105034272a005288920fc882cfff5e9833084c434635733b3c40c7da840efb69af36602e1976b72c82b8b271890dbd9226ec747f57ed4db8dccbd116629760802f2347f21d102724b7968a4bfe921647d7f1ef70aa1c4b0187c209d33cc54f987059f49fefb9172663979602a373322c0da2eb08dbbdba4d44fee8f593a10aa007c07224d9be172c203cf0343dd97c507a6a0a27a0989a93ac80e24a0d7df620dea0e47727fb21e0fbe7d9b8e64a94de5db51bc97f2f8dfffc5c2f17665bd4cf1fe1140d5d18c7250e018257f5ae0ab032d6feda7d74783320adb359a6ff213a69da1dc8a08e27253a47ab41a92a905dfee81ac3834daee064d6a77ab242674e7c4a2f971fb657256157b005062bad2d887d9e7f0b0b4b59fa544f7235f7ff1b3e2de47872281724019ceddf017e54e7e8b7b7976e9d0182571ab13cc1d69714f26a4e3485d4c285fcb72af0fbeb69425d7d5d6bd571ee91ec6e7a94a3f98612f3cc4f2b55daa72b68023c65aa819cb9c8e739887f2bd4dbb8342fdce6b1025142689dc7fda65720dcb82e710a09c121fb01c79abd33c814eba8479fc4c397106fe43502e3e7e72846629826802e67c5fb2189eed59d2a38d7f2b3ac8e98adef3a008f947e5637286c72d96e25895825cc9715e0640c17b0705611e570d638230f246db65eb8d722ccb9f212bbfebd06039b9f6a7c70d78acb218268928b792c584c01ec89a805411255fe0d50a041ecc8f555c137bc83d4306254b714684a38f15351a94bf6d72b27383d03e4a50f8929befef3300694c25c055678a7b82c21ff3ec3c3fab4c72afa39c987d399ccd5735825392d5e77b7b259cfa14a7e51e69ac65baf25f2d72a705b94966ca6dbfab71f77be7638ed51aa8d5c41eb0cca09550878429f5266cacf9002c9952c2492d10127eb7bbb71dbd335fa756fdd208632b48ca4c119b1eff801b224a2bf9740405b45c8ac91b9018b00382d316ecf2736609b2662324726cf21389ce01f75526acf7877e3ee0708ce70cf0ce3b360774e63ad212c6732c9f62eaa1eed7d204fe46d41b8e2528564cc3a34638a1b2435a03c32219464b72cd9a6da7518ecbfe4d76945447de2c045e70cf27e5b2ecbdfbd9f32ff0d1a71dfbf7341ed4cb18fd49091173e399d4aacae79802b640436291a8d07e3dc82272138eaf8bec49915d557c730fbd47e13ef06dba0cdff7d93276b01ac4f3b63a3e1f5d6469ff8bf39f2b905b8aede36e3e23b4c6352b78e5e0bfa77f2208a29a72a5d1fb33ac6ef9cf19199557b54540e1c7c7f5d6356d48c9108dd4878d84096197c1b974c06dd41d70d01421ff8d1d8f94dc9d6bf4239877fefcc0ec94812b2de4f73efa709ef141a893ad58e1328df2da1a1f34944180917292db720982de72fe505fc92f61604f1cc976bd1580394e1110a96642045f03efa8eb601e831772b877fc82584107cb0c7419165e53820a1ae6755f5c31eb0f047de5dcbae61f297f83b12651008e78a4a3b6f0ac4bb1c20734948749befef2b0d9603023ddb672b5ff53035354b1a17fa081de6961d89375e73f5dccf0b09fa19443832bcc423edd97d9e0c71efafa7d80cc7415b79bfcda768c5cf6f869dcc6f519e179fc4665333cfae2225a6eb106b9d6c5f23935c2a2f360901b9035cbc4161f8faf4e4d042ec3b7c773415a84be58546208a8b22b9bab6012d2af8188d43de4b1c6958454ce8bff519cd308b92a00e6ba07bd92f2c06439846a145c8c42c97d89759b274a4dcefe6dc10ec5e5d7c959abda93726a4bea87e4fdf15b40b319c59867984b4d30f74c6aaf18454dbbd03a197d2abc0f02691f653b0a01ca7b8a54c47f2dfd70b6db7484b3f15c71a952539aead075210a16bdddc0baaf3e23bf292c1a2efd7232e51be3c1942701c147bc728047ca107ce0b2fc6ccd117998afecea37d35472135d048779fad4c93c7b228f6a16b41cbe99fbc97bcd07180cfae942ea35165176c0bf9795db57e1c8e9c9954c341e0e05225ba21a9b9bf82e3c9151c8e3b1721b6068144e686fa40ca404c5d42ea49b6321100501370f128ba689efde7822144eccc498eaa22fcc0700dc5753cf915069c0bfd6af45e9206324561a2c298d1f78bed80f89d3e2f92e13c0beb8f18a32b4e9084bb437c5eb08334f48c299ae72533f9acf3c526007e674d76fcf859ab3855009393ceca18971b585a2b771666373d15b5096a1141df5b6d6b9af2e4b6ade001122a730096855b20a8d0645443daef0ec2d8b7f64a7abc6f09addda87f5b537f57641ab7852cd0217c2a2a144118c7a8593e8f76e9f0c3ecb5c33c21958010f185c7cb090935e70a5bb32264172d51f6f8b6c64da6c9447ebce28d2fa7fed0c45b7c640bd53f07c8bb4218a602279f688de733b9b0b046ee0eaf8a8d18a1ae4e6d02886256eaac306b84b0a5172c2a97d1e48ebba0d9a113c6b00d2ec1a1dae962e0a7847225aa7fa7d2196bb3191100ccb04532c65b47274c263a23d25c746ceae09db52202ece364ea3eceb500b455756232c274cbfb6e05dc7a3abd8f19c2e6de179d74292dd554f69868e72870966e4fc33f4f605f25fa16589ac7113bb4aa1c50e3909f2f67972dd33083ae8d3db5a05746e092b87a658aba96d241920f5ad1584bb1a4bf4e6fb014f3c72c333476038925ac6fa82a8451dadc89da8601742d50dc3b3cc9b1f0546922540034794d2725613a61cda2606d4953d30ab5a74c7d4765949bb71bc2dc9fc06302be6e9b8ceb6d267acb0a21edd2b5bfbf62ea4e57713770a6b9e18d561bd61729ef106610931e507b75c12392acbde2924f751d780c3275d75b4e320d74f6572cc7af388bc1419b8db2778539a198acc778dc8321bb1bac8e4b8c506f77d9072ba14160dadea9eead5fd9c1b0e2a4ceeb4d7b1f4a814aee84993247081130f72cfaddae5d06effbd86b798e31fdae1dc4fe771ced93c87fba1e50c4e26471672464e52f6e140f51c467ae87887a0bc39780d89fd067bbb779d1759c41eb1a672ecdfe61f4033ef5f327506f2ee04c5776ec65cd8ab50477f1370c148c692fa72d442b1b5b58c98034b124bddb7c2f0730703d766dae654d8ab778a54eaf758720adcbedbb50d6e9e891b5b0c7355007474bad9134455809e9addeddf4cb44925efa95e3accae957f1b15b7bf5cc2ec0e10150560e20d5fcc4e8c2f11796cd3254cbc5d6d3605ca0f7303cc29c4baf2cfb2c804c7e73fae78d83c313021bddd72f7777ab973f6d1b97eb870f96b97725448144eb3aec54d25cb3859fb2647197241f062d76e30735f728047920f224bb5c0c34f65ea1c2535a73209549e5887727fd7ef05cca9217fa88b965ce632bd1f2c0f03165748bb49e5862ef04bfbe47227485cc3e48e0b1033ef655959046cb3e9f647066f61f74652352b633ba9b25dfc7ee25a01375701f9292827fc07cf8164adbdafdac967d466a3835802e15572fab01fb09870fc4e359c54c331c4e6337be635456c2f3b61c33d91e3ccd2337208109bdd8cc0acf8dd9b2c14e74436ba6f3bdab5fd002bbb14c1688bf96cfb4855d03102cb340edc3fb421ac023eea0c9714e0765d5093e7559c8493a8b7cc726501abb562f19f3fbfd8608b0f395451ea35d1a681e065ba98b8e55edba6184f97d89f90d519162c1e916d046acd6e8156cad7a80001dfb61ca5141bce93437253d7bcf818427268d511209e339c5b0c1393e9ef83c1bb4d83cdf16362ce9a72a774413a606e2bfac5ac392e109936f79dc8191d0cfa6ca3aa9efdf4dd7d9137d03f17120cf2d0c4d6401e4da4540f4270ec84d4e4c82b79ba922567c0267e7286adf7ae15d555d68ccbfa6cb2621a10dff05859aed762598136a0b9afe36b721dbc207d78b22297571a11c024fca5d581b1d6d3a3fa43f1b9be7c65fed71172b8d84fbf8232693c27de3143be36aa2bd8ce1e867d211aa8ff97e92c581b4872e144a6925f574cf5a6bcbe3a309ad33c8e6ea739eba62288eaf5db5c6a4cc70e7e9da3af71a50812f2a13dd17cae6a5b2fb8243f31710653d9a0bc38c1fcd972d553d4a231a33f05b1b9a538915cf4cb50cda06d4111c6a1e441475f4f8a6372fc6ae3304d1556f1ffb437596acb9dc776b8a2956d11ad21c2d5d2716fdcc7728eb4fede2619a6ac3dafbc701e066b9df1e7c09c948385005dbc3d8072ab8c2ada2d995b0300698c898d35f37a00abd627da54ebd287af459f9bc8713189975bb649995fb28f573a2e8f13cc96ba979f1c7a710ca0e72c9645dafdd160e14772e457a477699837e642cf9c227dac5208ffd9aeeeecfa3e1772b6ee6ab12e4e192457da0720a14a3d26ac6f272e3a15205da5a51d2e35b3a59df1032de83db165764da80f745eafab05d303d6d5626805db50405557477bd7db830bb2a731b872153c65b77abcf66616ca6ff8945ed6b9356f34222c0ae59326eda15364e3a262bf8a72112ef48067f3909dab92947380a1dae1f3a45f0d4d3f5cbb472e8ef072c0e7d74a10ad27983a3ca5e84b19ef5a636f8fffa8548308560256c20dace9526dc8696f35a32bcfe64f47b8c6cff029c8d52e2fdb4b3ae7e6a8f7fdd7e16372f4315085eea614b23dc91a467bedc34971524884417afc6982a24fd11e97bd00aba820415ce9291e83fdf0cd791689b2264c3be64c802b374dc3a0992f8a347228285069d1da82dc55d61f566c10c003c9dcb5028453a4a2eb7d3641dcf60c188ebce9ce370e92b92203fa272377b7e8219a3786c9a701d7ead690d2d21277721ee7304d14f20ca99db2306163763b0e8ab8135b474baf33b1dcc2e41bb3cf72db628fa39aaeb5cb0b500a595c9bbf0785e59242496ace02455154aae09dc54f50fe23fde7afa11991d30d7ef19e680fc5bd8c7abe9ad89754a152f6462ab43c0b639f17b03c20fe90831d9286f984b7f447ca3ed26617c3df754848a6be46726628cb93f1c1f92bbb94fa87da6bc97aeeb9d002e2a920764c9f9b053558c2726faaffa647c5a919f96fcf9ecd282a64f071ca91035fbce7d75ecb27d8bb61725942a62515818bf4d37a4caf6f4d4556dfbc9fbb96876204f39a5310deb2671bf897674d5d50cfb9980f3abc68d1f0c42a88b6530f88ba8b44a78ab39c21b7254fdedc1250889eb02e9b1c788776460d3779c9126b419e824fbc360ceee7ef7207498f65cba6e8f5bf26d273bdf1e8f89c1ddb47f5390d94abce34f3a3cfe534b64584b4283e5a69afc99175321fa6f28945c74b6c0cee8e26cf7f95627bb24edaf1d4402d2fd224a1597c64158aecc4d186a31c1cf5bf7f290fd42f6ace9754aad9b08210c6e9048e84a2c660ff587a66052cbe9814652ec7455801e56c93725568cc19cfc411b22f722487b526f1e349790b7c4f01f5b551e87d6bedc3331b23f0fbe7aa458b26a601690c8a7d4cab305d493e47b8c9238b81e8c52a09b66a0dbe3c0bd8d909c7b281ca6e7a92732b38f32db3eef27abc2ca37df056bc9256c6ac0f275f3995bfec1059a8191fb77ab75e277d2487a8599cbf1f086ad10a33aa863e1676e0af7ec9858c0816191686c8e07ce13d192c72daa31a0ff0b69972b40abd0c49e8a2ede13f34a48e24758981e0141ba24460ce2050ace51ca9d87204db2b26d3eb379fd9c70295724da018659e90931e1429c4f618492968b6b972295aca02dba59e6679ec2397954af2ba1be53bcae92c95857189ac753e835d3728ed31241bf3da865d00c1eda2fefb1c8aff55e4f43477a4a4ae07dde3979272344717e5cfe5e9ef2b5af9ba06e0768c1f7b675e6a9d94e5df2de337000b9b72788320125d375a32623180ae527a44c4aef9b1fc1675bb1e0a59e386686ed972315fb307cc9d17309f705eba6c684197dd691cdd472d25ee01b4092ebd61874497e268d214d57212cf915e73b4d5eb5704c0f1ea7b0b7fc19593b006a61acf5d9b894703727a8ebf2deeff33290590305015d6d168d7289ceb18fa63956cc0727bcd84efe42654a4bdb82e36e473e8add147fc591e701696773560a382a0251be424ca26fb58c3ab6621d6d3f2d61997c27ebdd7ccae234e6268b95b69b03b729d8f44ee9ab57250edd1b12b2b65aaa3dd8faeffe80de8999fadc72ae2193b72fe0eeffc8cbfe81745ebcd81a5411ceb0c147d114b8aacc5698cfb86dd475b7267b4f229f125c19f68a2201c3dd4d6d84c320359149c53b7ef1c29d2c57198729001da615e1b612b00959750a3c8ec6830c5b252b89979d8257121ea42a901727b19b3969852c3c41e8deca7758dd05da3d78e40550f24a5068db0544ff2e572b0b788a5b028fe8059e683f0d301df6cd210e1f37a6053c8bdbd2956d809d972078f45d643e936de464af17a887f21f9d4afb327496ec31efc3acc8dcdd9a5720aa953982dea4e31ff11749db9ae5d5632b683b0eeafa3b99f73cee6a40b4f72bbbead9b15990198cc812aa08e742617767152514bda84c635f339238d6e87725f54f2873579e792a77016f6971e4fcb7f84bc718f8c314028a2348b381bf8187e5a7fb8451dd32c1e888c9bb27c485a2355c4d3e730cdec848f638354032920b9c4689d329058b4906e1f9cf63ee3502f3a3cb0a2081a0cd04aeed5c23d0a043f3a399de1551551a9fc24fd7f5f902f560c531cfb433b23cbb15522b59cd27260504cda0667ef0265a37146550f7de6ed329235e7064d1049216f8b6acc4c6a78246fffa05c8cf77b7ca60d566574bef9994972bc84e90e7bbf3602d00c5b0c0166b404d67a1839d5cd275893f5690b05432e7f0725312e26481b112ae55a6a3b48ed06d0bf1daf81e9f97c46068f9e27772bbbc870ecba815550e2b24b90617a196a537adb7fe2dcdb5eb00542fa9501bc15da1b50f3c7e3d6dadb56f47a72a1f2822fec6664596671c215bcf3a49734be1f1b66af2d92aecf2a6aed4acf72b2f9b7a972e9aff7fce5c50dee9c4491211f24081599ddc5f04decf6ceff0616fecbf2bac9ae115b7b8333e315ee3b291971fa79101c750fc5c1fc86c9505272e66aec61d5b2be2758a04e708d81dae39768bec44ad5fe999646cb296edb89725b6998ea696f8046ac5fd8c29f39e632ea00cb656dee2db212e4750ee096aa1cca735f96adfc17ad9530234b217ddac4eb627424c7a0ff445ac7c7f863f5192bb1f18a2ece710b70f690b36a39377c7e3783031dfca755d6b8ab17250c584151041785ef006bc29094a52a5b009de1009c3f6026853b69ba38cf83ef0daff864d5ed55d0bc3f8d7d288dd381eeeed734435ad945dc5612332d4bc99b8cf6d87226e4b9caaece0f041b8878853428ec5b95750632242e172000892bb37a4edb72dca2175686b1f4cccfb713a502579675193e201a4b394d72b898a03e3e486c7265a36e168e64861e85f2e2769c8a16d7c2038de6386b6a236a1c47b1edc27372c7eb32392926f3472162b9451d465aa5fbb6a55cb4d0a5593aebebe1f27b9c4cad1503432b25000d62e092478af3ea9a41fd21a6719bfa67928926802a70964c3f5afb6946a2a42a508ba39094373855e0032d70541cf60beac0bbaf13c8a272942737db659141a7c423c68ffb0c001f1992b16aa176f82eaa7e379090b1777271f0077d2a34163d423259eabc3ada1d27e19d26a50db9dda1b6a3fa0d2e48723d62a53f55119f22321dbf90925bc9b5fc5ee5c9f9ce2d16e328e65c1cc68272b4982e999bab07f776407c059bca8dd2c90d9a718b94c9fbacba6a639513121563d42311251de809908f304d41069d4823fc398f69c418b9d918f9c0a0c89d7212b6084a5c667727c29b974b8c827c5ea56005a3bc7bd414daa20730f93d3c0b67c457369c998c0c016e5e140fcac0621e25eb7bc11bd21215314710d5e16872c474d831fcd920ca352e596340eabe763758062c72e3dd558269ff9f33b1655c5099ac6411c65088b5e4ba3f5bed23daf648c842afce145f4c589de80fc424722be6a4e1e81635c58ccb7228587d2d819480b55c09d28532172ccc308da5964354b1eb2832fde6a7c8ce2ea1a3552e2b89065a546f5ab16e7a0502ed51c6753189437bc4280f6a83aaf3beddfee6bc0110f27a8494c8ab6c0f6026aa43343972299b57954bb221b853c31c60e7ff09aa58e60777ebacd21cdd30401ba6a3dd722e83577bc22592b58e82bb03ce9a0e19a515f474f386747e406200189d97c37215f925edf004729aa0e5607f49b7160f0d31570db11b9920a36c4348e522b272e5e15e87b8f939061f99c7c5c5e4c3d161f00fc3ab85a2d1e378e9249308b472a483d337d2a4caa417abc0e9bbecae011ce9004224e00c1174ced1d42d1c1672a815a3f72a519051f4b63e3b0c178bd49921b196a227fdc0ce99035dcdf27b7267a48e548c30838d01c959bdb7d079f7042517d39d62ca279617ee3397708f1e963870b4a95215f236d8fef10d85d042ba7e8da6a40cbd4eaf2326d83100f555ce6fc74de692525962310afffef10487adda30e7da3252f5d3bde97b712d5e72fd7ef389553708b6bf7cd3593f07c1fccdaba73319acaee5bad453ac8cd32c65917ab8233aa415fac096e871b39686a62124b7b07264d606dc03c7c92de3a07257daaa9660a036e6a4e2cbc425408675a84a5b0ec1c6ac078f7f5dbb5e85df2c767e8f1430e43cf69b3e97560c6b29bfb2a60be2b80a1006a7b9ce402b94c372e35002090f9baed0a646db14bcaeb2045c1db3a481ef82fdd2b072eecf97e372824178939f670ec86c830c705ffd1e8c053dae823aa29c92c3d8ea449cf88b381a8ebda738a0052d92f3c4590e825871ad8b15c56a8331b3c07963f14d812e149b985ee4da9d7fdf85535edcf232c481b3e2a3b69cabbc7e2d1c7fcb71e5b0729817a8d334f16cae461fc650f1b3e19b7efe4a0bac91c59acf4eac5769577c72527f82afc9dcf3ea487c928a68edc2f2ab793d07411f89d7931e4276f38f0b724cfbc7e5285846514e9d8dfe29412caa213e8aba59689ef5091b9989aed7597272de9cdba85a1fe1c2c251f192fd8fe24b899b3490b7be54bf138ae9f3f9bb72a878884c028beefc689e922e500ea2572264ffd179179ffed9ffe1a790366272c29674d9044a15bd7ba0a288148354132605d05a5a1a5cd0abe1a239381e064b165727acb8d1fa431d30a9feff1776c71ac09f6c91c544f29b3b56563cba62727d6b1329c5396e271edad0221c4db3a1ba7c07f38a7ddf383a59c361b3dcaa0dc9d5a4254187af5b8242eba6862f8acaaeced6f0b667c51d5450ed1c7b470c72278eb6cdea26c7f2c1996a08e6e03985de4ff5437a4411a17c2ae2e495433172cda0b8a37bb22404bf370eea442c78a152f5294779bd74509ba0381e8b91b272a8d21b9732c3bd38c541b99feac3b8b8c7950104f127aa62e56b556b1090263915cdd2542df34552a4129c0b942b082548cdc6a1f2bdd02eeba3ecc826b6517285bce11ef73c1361ddb45ba027b37eae4bbf56c60d8841e24c64a28116137808a61c50081a9981b72e9feb2dc5c3ceb63fd5e1363b11640b0b1762016550ae703890b6e7cdc1919cb9f8a45d0f57d1fe99d918f8407736e7f7cae9448ec8587292354106fefd2639bb13274a27ac9a4018ca83f6c5311ffa5a8d427fdb418807af08f862b467ef8d5efa0d05b85dcff37082c1d006f7e71d6571a16b5e180c72ad236a12671a12ed62cfcad33450f3efcc7b1c5c24348594250dbd5c21bd367224cd65679887cf07be257eabaa28615e7a9be6399e0e3a62f1ec481a79692e72a6004757d6fec8fa8b81cb21db04389f80fd0b1580916b191575842580656404491a8c960660cb6ab4d15990b0965e1fb12c5c8d484dba66c0e3ec8ce642a272d06576b67977918fc050155fcb9b8a549993ad4f75c91ad9d8b7f2a0737ecd723c24978ea8de22b0341c9e037728d01ae2d82f032ec545e5f094db2a3b23a172a33e0379b94a95a9602643ee9ebd6017a00a4a7c5c8a6a70459d3fcadac1f90261b2a2447e4fc935188cd42c21e3618e5d28642118af59c2770d67c70c9f0c716ef2446a95a3efd89cbc65e14ef1eddd04bb1b7ffce48c88d3182829eb51c772259e667b18cd77e9de54331b42e3c47e6348986ec19c101e00631b48f0dd9272f21bbf94313a353d30fb3dda62b913d385252f2ca2115ac6ad7718da6e9b5a72d102d379fdbdfc138a1ec5b0e0d9a3f6dfb61e23ccddf3cb1618945d51428e5cb7a8a0762d378f4a1ae962bcb12fc8d74929e8e179db3048bb1821ba5d572a36b2e6008f917039f7ba1fbe3451d77a34262f83194022e856265e359fd4deae2296a313885b7ede9254cf82e224e955fa2f85520e9b078a3f1ac0c7f009cb1a726c89e691a8232880b7504b06f66a73bfd94ffa27bd695eff47ab6636e43a85343d90406e2705f7dd21e4863943143eda36e018c41676c3802c037a29c27685014d250af46eb2305f62fad4f4c8ef96a0b1d3f01091d53d95c187c766de6830726cfe08d5acece8458899500cd0cd585fb1aeb885146dadc92057290299458472b451918706dd2b9b0db199f172a90a218f25dae6306b51e6304057558f5bd072987a34cc3ae67fb868fb8a70aed66fac291a38cc45260fbc3332d36c74630672e3ee28fc135d46786523cf7e66b2288b53e29bc8fd6b8df29f6e02b34fd6ad7243ec7ce39c1716a82b47fc074301e3c59294bdb202a732026170ea9055b118465e553d0a1f1588b225c91c3ee0d05b5372c4394ab57cd4124c307367c514e672125f8dab46ea9280536cbd4a78f171f6555073efdd6252e094f9ce8fa4efd3722e56dde2f48bc12f74312e9a1c63151d1475cffd6bdd432caa3bd32eca1cac4bd26a97a48b4de8ba8880300d7d69c6ea015490b7137bf3b6077c27f5e98644493d84bd72e6c21375d9349d2cff94fae862f741e8cbdb6189126ca7a053e89872234ea503b13aeed4e7fb55312d740c0ea1079ee1a83bd39c23d98bd798da5472ff072b4d169bb5ec3db5e603482ce3cd727d6196101084889946ee63e0a997727a040c7dea4376ef17659dc1b50d6802191f2c5054a87e399ecd101c481ae4722c3632af480fc03214527d0bccd5f156dfa59900230ddf3209b0391258822c72d1f73744c1929a3dd1586ee94eafc08cec4438ef4546b8506d0e3615ac7dd57262bf25674c67380d8ad746bdc160e557bd142e76860a0abdb3d382b49c86dc729b1ad9c0040fdd66004124cbd1ceb2b29107c8725b2386539a152be37134cd72e615122174810f348638fe04f885634bb37cf3d960c2f27607f74eb2e2b5cf69b85dac5e105af7e0952297a4dd47e75948746f45f39f978916566ff9ea99b272fe651d7301b7298b9168b76a74299c10af560fd0ad1a20c11b1f4ec6a5b45b4ec8d8828cf65bacf9e3d0102cb2b50cc69f9e4367512bb1fc5af4d87a8f096104cca319501bbbf9070e316da409bf4a6b803b1cd414acee6015617f63194338722401301b46dd69a5f7d7f224cb0738718a0b96088829e83a256a6aa7aa824d4464e498c0651524f91018b8a3b7886acaedf7923c5351a03511e86638061ce372246ab7af112421920ec94e88e83f95d906c5817f3bc867f79fdf3857f1605b72d390596aa89a4242afbda48fa463e6c57a5a3df215d5e674fedea097f282ce72fd0207d74fb913b408f662ab9690a03b6ea6afe8e8651efb6b839c65d044c928123988879392bcb88d74fdec1035aa3b7c6b6dfd36580a58dc0a9ed036be912122892886a6f7dbbc400eddc1771a35f6eeae00920287095f3451d2efa6d6c21d05f8fb81c83676d470d4c460afa6523454c815d7b1d94c0a05f26ff16ae3ba04e1ed32770b02094d373ad66c19a3fb9556c70a189e8690c243eed2849e69ca728e6ddb84b655a66fbac8c8c6e2582911a57faa2fb9b1755c7b89c249afd2445864c52e5b2a1ec4dcde9c28af5a2bab794879f2dbcee1dcb3d85ad88dd2bd5472813bc174ac8a8e57a3af1e6cab6a237a3559caa2b9e23503c17f659efb7f36046ed43f5269ccf37ec8f07c2ff5c082be84957b0bf876d64a6770937420769a72ba5914d1a2782137156804527e244ed6ee122b7bb81bf384cd775da7964f405ea9ca37db276d456e8fcdf283815f0f9b3ffce39133b98dffc59345480cc079725628340f98539cbf393fa1aea0afe1c3ad2ca95d0ae2387f01119e3600476d722ebe6b1b99c48290a3ad5a465225d36979c5477cbca5f684f4beca25035c8f04cdb7563cdd4ffae3cd877f19897265416d405fac501965c77dd71be8f36bb54e0b5a8762f6e5a392c94c43eba4e6deb5fbeaea25eb2e62e3ce11a267eaa4f3394fbd784b484ec34c6c40e6192946373e758e5b5fc747894c601101ccf1b67027bc189388b4dc4d80db284a26e4f9a69aa6fcd682999d02d77fac0fb6b1df4272a540b0bf419ca0242f8c4910f5db4c0ad34b07f360fe8704b8241284610a2d72975df36b53a4a0dbbd820a3f8e7086e19c999634177a844b870e27c05feeb0729658927a5146a937751179d36f14a2d04102f4a38c7db8ab561d933bd093ea724ec5a0250d3c96d553b1c3a28292d3e1891af0c99f02174d2695955c66b97172f1ce389dafaf1327396e0d4badcef44d03e42ca3ba68a57cf9a1f5a301b8b3050560eecc83cdda3346e3f85a8bc887c1ad7f87e8b653b5ed4ea793e6388e5e72e641dd581a4065818ab31e319f7d6908f1c47d39d1cbf5bbc6f4301cdb0fe3024f147180052dadca08eedbdd9c76f8b890f7a786ec59c17bc55d366c79caaf72ebcb7473870fcb9db9854eed18403fa2e4624b6dff83dd5dc662bbd5ed41167201e0d02cee6c5a4deaf1b72107710ba19264e461fb4058474ebd7689408f70726bcbf5bebee6daedf7c04b392bc4e08040b3378d858b88e7e0d6505a6f99d572fb220c18581b08138d9d2fd1ba956bf878d5fe70c045a72a75cf086f31a77c25333e654c66134b232dd7dedf5fae4a74ed4472fec78eb7f01d78783c554bfc726fe861ba2d60d512b0b21e8c31f828dbd24386a38f3b0b8ad6c55f9ae278af2034ef63149c1bb18c8b6c8b62119b41d47b464049d66bbbc32e074c100312ce085e312960b9719d32f30b4777d6905ca5c29e2d423b547b5a526eb7bdde31f8720b016683ab0ae953dbcc96a7bd4560fa1f87c41a86df563564516d986cd8727237211c7f28c934381d300475507459ba220a32ccfe23cce025dced1641d347724ed3d5a7b5149d0227d1a33469a21ced953b501c3a2523cdc958eb7005e00432a35e34843ecce65a1ce29fb91fe77ccae14797e4f11143480959372ea4881c4665c52a3d76380a554b1872dfc61f8da169e018d1aba7c46be5d042ad43face723b7f49227756b0a9c7b5079de96f5cb30116ae92dca86394ce152a68e7927672189e643bffa208a92e95191067c0acc6d786b310443cab97bd087d0c41a2a70bd23bf75f2dfb88f9bb68482608dadea2fccd19012d2f14add2d28d67ed07f96ff34c87e85048fd83ecec9baf79931d72e7b07e507134438f478064c3e861747257b1d81850d81edd052149851043e9750b130b53ae31d0e41cdf9b91fc461e4dd7595b10e5b042f5e960a37da6e49f63ae8463bad6b8fb01db48057c22f306720337b1be636c9bf7b82b3ffc4f3eb631eaa85918ee4e70ad62998ed35b50e9728e0dfd1ee6e999c50071571b3d34e516ab178e9694a40e01f5a1f9a791a2382eccb041a6077875e39b97eeb58991909db3875fb75a594bcd6adc243dbd426d51a3e99cc4c092a15f919f933702ea104bb7fb0ceeb80988ff5cdf40df978e7672551634f466b3e54747a7cbcd24a8d3b264dfa938dccd1ed5c1fbfe77573b08723519a6fb268ebaae94ec06c165859cfe566f93886b5a9748576276818790a9721b3e3622a57a89630a63eaf55fab223177d7fae07a060a542037df05b4d4647211167648cfbb4600632bde70f11c9f71967d93c3e5cee13995a1e68879d6727223cb1343fba9670839f624494fee437e8949a5af2d6acc39674023ee7f457e112958e6ccf1aaa92ca8a1b7b480ba3fe0db23c65d5527eba73691c8244fa4311cec81e203ae8a2dae273ce917fc6eb6c8e564d14aaf11b2aced88405f198dee72642db90a8392168d4f7819d6677707c0eeb9044f16f8364ee8edcfd539888972c647c25f541a056466e8e48627432d3cc3f7b435695458d48e9e3350624d3f72874e5f4caf44827a04f0c7146c46c9fa4747b7ed7281f3ea9ea3f3bba1e44167a1abfd4f7ef89697444c7481c9e216cc7c38b26c1c94251dcde9f4e2800226723c8b696746e4ab77ea43d5f3fdd28f03e68136a9e75ae0b6ff408dcf5ea5cf727d2a659a31499ee682b361abebd28c7c88a3dc07137abc85a72a581015280072fb822686dd55a3b8e8c062710d098d8baab32054b7f8cfae1cc29fadbc9a2b51883c6b7c886655cf2c9d84a271bd250e50376019f8ed2100625621fcf3e1fd2f2d16dfb640379392b65a5ffae876dcbf8047c374fe741d657c141c85d60daa72a176a5b1d2c3164d69d921a0ed6d388d6b85d31d6627bd885cf871e7d6b31f06585c8911407063e4eddca21cb6841269e0594c84089a7d84e932735e443c246a5cd4934916be5e61660dbc1dd9ef9ff467c7424d9095f9f887cddc880782627244fdaec8de84ee23137f6d5bb70360860932c5ccad0b7c8a6c23dabc29c8e32466b0927d23b7ffa4193a08d9c933d2b92c9967d7a2ccbcfeeec627f51af75272feed7b8c852a4abacbe51ed5a3642fb3704e4a4fda08fd87193c4db97d262872db9f954935f0bf8583a5db2308878054d85b40e09797ee2ce0ece681fad50568b410f0b5a9c293dc4d4ccad96c00c8753f1c7d034d7c2fe140c73abe7c6c287257e5ceae0bf74b78ac79ceea339211a538b80017330151b7a7d885defefaa23d01be0e504ea2f625a8b177e73b680db500fdaf660941424cb2ab3c8dd618490113a2cc006e0a92ca8a0e8eef7d891c5aff540c0d941dc96edcf75c69bb4abf72505c9f0f8bc3d328c1bd6a732d10ec5ef452498df059b7ae61d03b3bad09a372ddd226ff56ac18292b8ab5c7398c409a308079bae6cb4bca676f1e6b94c0a00ca4a66122419eee74166837d8d4956f7d0f5369cff042e150b89cb3d6fc85257297c420f300fd1e54bb761fcbe0aa8df0282809df8d2b99c060289cfa3a24aa7274d30ee4ee0033a65d550d533b2eaead670f363be6c1716f2862a08c4b2e5e726d8320b680c2a7e01730e934f210accf2e9c72c1437420f8e219881f722b577228db40cdb0610c6ae89a35013856a0b1f6a0843947638452e672681466a775720594f94e60c638018148106bbb64c1bafbf07f515c067db69a7fefb46a0ed47259581fbc6d83202105e145c39c7042a08b630a813a8aa985d910df1a190590557fe59506b36947d2c9f00c5acf275273d320e3156daefabb81395c7b2bf0b77206fe58964bfd9aa0d6092633f896491d1c0fa381297d76f8756408ceeaadff7240612819b92bf7db841247bcecaca42196645d9a8e4cc569c98bf2d501527872467efc950315e223b4789114eb0b998ee36dca461d7a9c4776c8e1e22711c072478152c525327726c9f0b6c51dde1ec423a535fc6da19868d62b93788331337249782bd0a0e79d5c72008e4599d3d92b7224b5a343f1952baa96d5ca4fbe9a659c9ff19aad6f8ffad4382af22995d93d065b77d68ffe0722c135cfe8c93b9472b212e7c23917fa95ff6322bda7f5fe18a50f026f147a2fb32cf6a1d46a42e73a93046fba7bcb6c5ba7b9f79d715e75527a6d693435575ed7837785fe88e077201476aa6ed27d6bc5f539bcb58e07bc257e38482eb1582121b6ccd88f3c1be072b64f2bfcc9b3d6dfa79902b1c21dd72706bdf3264f619e78bab0f02f561f367217fcb3ea06783db50901a6b059d7e220b198440b0d124f389ff4982e89b71d723f822be5d8d9dbaa8a70cc8c1f4ac0390e4a74d4b65f3e34822a9d77cb900956ed1e35d172fb89d8f4e898ecbf082eb844fde67ee5899f06b78a6f01d5a41a677067a052628235bc035846ffbcc202d7ac87f724286593c2bb3e21222d3142729fe2e3c600e9034b1ea3d67b84c138b2a71094d961a9f9c155515f23580060602165fdfc536754fb6da35115a1c8fefd031712830237c8b0fa736771bd8ad1072323c5487447ddeee80f6c7f38e4d1a64bdc4e2b5843faa54545e7513fa49a72135113404a7581d1c0119fb064a534748789d64b0756d78fa12262a4efeb2e727dc3d8966c7c57201453d384b2ab532cb087b096b532767c45a4f2a72673c941386524e3b912b9c233d85c549b16995d52e6cb25edc986001d88397f17313b724d1acf6287f23d4036d07a73e4fbff1b813468cb4dca86e6eb7498edfd9220625d90dbfccffbc135a7de47d42c110b68577037bd30e11fde3069445c183e0572dc52833d49fc7d65d8f9321d247a563e9659c453df305f429cdcfbfbcd08f572c9513e31793b566a1acf25ed9053f980102670d34c1bdbd622fffb9e52e966726c6a1591d49399157ec855664e3e7b359187cef605a51996b46cd2c6b257b568a6441349e82f0f4a5316df520769deea662aacf0d7f78039b32d30c773a1a37265b55e60a74f93aed24699d305f99d26de81c05a120298e8970823cbbb501468e06218d42948c072ec45b54b9e6fb827f08549489760004e662abe33c2537726d8a1b10a105c69f5c9ff874a85eba44e4c2f1ac7673c753aa8abbd78e81eb672cddeca110f1efa91bc4985456a450e12d4641babbc3e2806bd79cc1c91513772d1416fc8f4f9fe7037c513817d60945ebc2a07c9b9851fba4d6033d52657d60ab480796d6825b88d78f2d1ff0f5f5573a7ede624a00f9445055a12bc23b75141eac637b53db58d03540f207a1416e15a8a2d3be9b6f9982ac5fd59bf16f20360dbd63680c14a839b7381f0600f6e471f68032f012d05edb80b7bd67598e0e0240d51a3886d947f099e47ada4c99c96e2b45347cbc67f8c292bfad728e5c4ef1093ef262738aebf243c9c0936b0536a9a7ffb7e3104b4be90adcbfaf42e10e6722ceb20b2ad3b4f2715b9077b39cf1eadc8386dd5c845179b6cebdec21b06b072a5b35b4223ea01ef2e916f9843261d5c7a139151474794d9a93dcc15ef88bf081cae7e3a3659bd7d188075fd7c05ffb64dc8b084ae17857d066b8e1e34a43d52069abdda787c72f6ff649cbd35262315276991446bc620da5a140af4f8eb30729539d9f977a99a26051600f29bd97f525c4b01b089e3aac6637d51b8dfb52102737eb17c029f96b4f593e3fd35f121e73fd38ea594c0a0d2a10057e771349572ece8597fd938ab143fc9c7afd52015f98d4820cd2a14c66baff28cf7912472722856f6cb0540283e2df7661e85d8795ef79a36d76af6e54b5e0e6cb0b510144a22b142e3041d3df068f945d67490242f412ebe4b581f97138953f3e4996988727abb671496112fc11cec2505fa8ef70486b57ac573881f152f3df4526af69e72c2f927faae4f107ca5dcf6845b8b2274c4d4d20bf31d3639fd6bb816b0109a41e7c29de113e361c33443c781838a6849fbb5f1ba685fa3b57c0379ab35c2511930d90577fa72a53c1bbac42d2f2ec609fc320b4bb84572d70e87a89c0e5cf051d36da84c9e6414ab2450bbcc0393871bcf3a31ef7d94a576a5a6bbd65b296a664c5ac0fcc8400dceb39a3a17324c3097626f548de78971586f1bd9b2620e633de3fb4c99f35fde5c770f8a03d961b21cefecaffa5a42753820c2a038c3dcbd7238e5a99c1c94c12d024bfbe65c99c6db5f95888aa40d7d26586f024e83d83b72b0b8aa3e89fc1a366cca345b5b7b94305ae16fdb36a7d18189de080727391a721dc4bc2d70218aacd024abec9a385c0f0d896cb0bf427730a81404a4d2d06c727fed88e185361438bcaf79ded3b5cf665df8db30d2cc1b0648e70bd1c956dd72d18b3f1cfc4729fdec764496aca625745aa2ab72f92adbff9538b09f7ae5f172feb0d4856a9709d9d5eb39a69d980e6d828eb5dcb01231a80ce35ca210975f729ecd9fe212fb2ea6ebf66694727ccdc4cd596977d741ed190c793cf8f38fbb72a096147e9e7de1e7394278cc1d473f8fcd1842170929252ec86ae5d54a51ae72f267a99333aedf35879acee0ee82f4d48d6201844da897b5f6bbf194a9f0c47285aa14bf1013a9a51f9a8e680b167148fc0832d0b097913586a3a6819651b572bf21c67ff2ddad7a8e5413b4af2c915f44d9c46bab57cc6c1cf5ebcef947f943745c9fd380300cb50e2e729d6fdb4466f1899f0167691dcbbdf2bd50efacda7253cabad217d5b8dc8a58ed6480dba367c02fe919ba006e8bab36dba2f964d97284323a315b57827031dd599b10b3abf3a95650c8d45192895e9b55f7c943ec3d2b5d262ec7994af6e54a8215b0a0172d90fa42ff73b3c8c3650539e817c950720a47f02e8cf7665b72996212d95661e4df99ffd811ee80b1800477eb159ec3373a0d1982e58c664a939b7f350441816ac988be337279ac49b81323a4fa39d972d8d9350437d0746c3df300f21bb16423cb0f2ab9c210f5ae0626317e30ae486981cb06cc3abaee969da52f1c555a8966c8abc3956e73e042226de8b58f21694e3b4e92b2b1801d22187da9c67221655c10d81ba46105cd7c48abe4c13b02627212091cad86cb70c4c666f0bc271f998cfe340920fa1cc0880ef89d5ac059287234589be2cc8dbd3311d0d23811478d3d2196d7378b4c2ad325e5996534dd39720ef96ee3b534977e97d2a6a7c834eb4452b0ce7bace2e3b96189f671da55cb00f9b35d10ddac2af6c1d2e09c1ca34deaa34b4d32dcae03f35a0bd3f399a8905b4fcfd15802ff394fdd7b0c49764285348f036afd1029fec68ea48338672e3a12b6f5fc1475a50bb64e5e0daf7d6997021346b878804d4862b29a22a1f9e36b723211eebb1648b0c5d9f69866ae38bde2a9059af259175d7d6687f03ddfd60a721b6bbe176f507ebc7e45c1359c1b1ddec8595e9af1d80868482fe1bb488a335333a7acc7c8879bba8ac4b896af0d2b799e9e3c2db641f075061fecfaf6987713b07df9a4e835e27ece511b15f08108197174cc17cd07b6013baf88a45cea3b5930766687d6ee847c198de171c6c70cd9147f08b097330f1712a3ed1e94827a6b2bfd4e75c69b3c0d901fcca25dfcd11a90481cd12ed10d91b7c35e23eebe4d7239cee7eb15d344324ab80ecae5be5ec9f3f2ac1de49868af923fa0d8947899728a50774a5513914046bc5847c289bd608deca0848ec97342cc7fd0916d5854725260966e124bf21456655dccb70bab158e1e3796bfc2ab3b0fd306ef4ab0ed72435da270a35238c887383c98e6ae3d3e377cebf8c776eac76d4dc1ff4e2f9972ca6dd8cd1a0c853003dba3153ce1481a7666efdc2a91133bbe7142705519996155cdd2324a5e9f592465e98cc147b153eb29331f624e18baf2b882f50b2288580654e06ced9a65eb5c967c222a94ca7886a3c6ec838365b0e2eb46e066d0db365d2a98b5cb1ce7d3ec455715395ca0f27890fd2bfc8876b9590204dd5ddca15ed302cdabbc586ab9926f1f5dabe8943c95a83033c67308f8c8392d2e66aa764ec281311d9d4aaf7312af22bbefd0ebc570aad60646e6dd4615e6054f58ee2d72d0b65ed799dd24ab06bc1121b754b0d4986d2d2c63e8ab4960fdfc7273d649726710ad068c3429dc7eee0cc4e40e1d5af90b2dd33255e2f8fc5681b6ee026f4caa32c49eebe23c2f18c0d8a7142b353b416bdd24cfd8962ef69980b129849063190784cfa953cb8c8294f26fcbeed4f4d7e5fe5dfcf829703210a2e5df28fd49723ecb27e21828f5bcc7ee79c26d1b7ff732200e829250d6fd825fe695beaa72d0dc5490ed12e6f1c37b757c447e26b6f7d536ba55df1a72ce419ab466424d72cf71fc4fcb27af773538e69ccd68d38a1dedfc90a7c24f74ce349fefa85a7772e1de898fc8d8de1211a3c750cce3b4016a779eb9e55bf564976867a240d23272fcd534528cf86ad3fd48b73dd1750bd9f90aa15c27ebb42e42e680ace64d2f72dbfd6eba82e9762be70b86c867c8e0eff0b5fdb9337c77189f0ac70a89d1f8454e03e4aa039c7ccb815ebbd733e3855eb1b406d534910cd48b21a4656d67d772cb14330898c144a51546e2793060c15b8cf0950845558bb14671e4e5038a302e73222e44e996d02a57b1698d29c02b28881ae5dba726ebd70976b97db116cc7240688917190020cea55f1abd93171608d54b3cedd2c2ddeab078ad6e3683b92e4719af3f55a9fe15c4d2bb386f6f213cb732c02471adc9483da4ac5880b76b584dd5eed25a12daee3196fb1578d46ca115cf390cc1d1a1c2325aecbbb4299d43bb0a9371b8cd919975671aed01ccccdfa0142340a3451cb3ebcc121cd26774425a6fb1425440f5a1caf63c866da5a6b9c08187425321a10f002e3fe7bb310d7213880126a6d1fe9433ac4e151026838dfed9afaa33231cf21087e256e4d86c727b02798a965fde272cf511f86184dd3a2932f55514b3b95169febbba26779f3102eb21709d2f77e4f53c38278ed870eb194c1989b8e27cbbf1146dbf57df4172fbf23cdb1b74c288912d0f2a5da7fdebf48d91897c76d6502af7e5deb69ef47225d79b753112f97c39cd56c3872a17ff9cd1820c2794f60744fe2f8a1fb3ec3a3e4a39a60cf904dd9b613c72e24c497ef6e075aa3d9f7bcd6c148577d7ce4f5f33b032cafac6c9629d02b897b9fc0cf818f48d17002189a7e7b7b6394762f972969dac085ae8bc74787c70ad49ac31fdbcf719e22c3e96daa4b5115cfa3c4372638f984537892565986ff47966ec68f0c2617bddcdcdd7d25fcc5832b6d3155a79d1e018e91db01a41041893c65976db8e7dee750cb442eeb0c2d763e7313b1a2dfc6d891c7df0a0634056c078060fcabc5de247ffc79b8a1453d0bea80caa7249595938bb191c471fa2c7d60784c00084c6df717689911cef7bf001aa5d874e2ede8e98ba292a9d1c72084921e9c8ee8a8ee2fb58fc1c857cb038d2bf8f2d7246c10bece96e513ba21d1fd3e2bf0a92a48736f1903fdbc9adbbff3e1a648d2dbccdea8a6fed34e1b5a40e50a9e770985a4562d4b3c9b21f730dc136fce97b7232a2d4c52257222349104fc0272b69950b5066aa344b9dbfc274954b855c08721c8e1a126dbf16e23ac8a4a8144d70e73b263b8b10c995753898283ba8b82b723ebbfb06320b079f408d21100585ee5f193792245df0bec83da612d875cef9726bd70ff60f8854bbdf0f586893b73698cc53ca28d47cfe4d04dedea9c3fb3472980150b427ad0c7fa4ced7388b17f38bd4a88e9322e380f724cb632639c394416ca7a26e9ccdaa6b69c6b5d2857f80e3e6e449ecd99882c843a77e93459f13722da2f3c7818a3009012a4034ebe151725107cf1431f711e1037fa608d02c487219307a9da5cb83ba817edc23b15b466eac7a6c0a86b0a8e8ee8c212118b9e5726bf58a97be229567fcd03202bad2262b353da38a826a50a0dc939b1c75b7830a003d3c61966fb660f341e5b60a6f1ca70b083b16bdc8059775a9acbfe5672b55c5b9020abee7b265662549fb8a1d2034a166ded5857d12761f58fba5edd4f03aa6dab40abce5355cee9d3b13bd83a4f3d5030699d936a621184281ddca96d1720c388b3801ffbb26b03b8f55e17239d04a3cf60754628f5090002ef9ce24be03b69863f12cafd53c8ef9f1e78a7e54a804012ae9ecdba5a733b0ce6fb1e9b972ca2880bc634e233b555667a329d52c1a1d75d012b36686937b688855b5f59a0203a4c35f29aa38d386ec530702fb184a37b116ae29f467494912a50299d3f9522442b0dc68d343f3b9e43f793def3641ec519b8e5242615a5bc1c8a2ba05ba45474bfa954f77e9c9fae141d76d0615e61d30d932f17006d91245565a8db8f272299423def6dd27c36025ac5829a3b8d3f91c6325e79124dd7d37884a4ca54223acae3018c041908640d30f1ba9455c253e292868a4c470787d3c32ff5d25720287c3ccd2bff25879d522bc420bb361dd0204d5ce6616411fb6420ea80c9a883b78f87821129e5257db1dcff957e4fb8c0ec91ab6327c12f8ebcf1559c64daf019fd6686e6279125d3acbde4c663d46a1f8979f4ac895c7be9e8c7719c3338272c2efb1e101e97fa83901c5905c35731881f3c47ae95abd0515d357a464ab2572d4d09da587df0476dec3df52152900094621ae5988006e91ce96f7e1fb6c5027d6c5ad82eea9a462c73ae78ee649f5e72be341355641aa6c9b77b070327a7f72018dce2f99419da1cf590afe6efaa83f17c40460e872a26e3cd752d31eeb9872b932b0077443114d794933aba941d5242236edcbfff09bae25a4f2c93ca40c72ccfffc1382b56299b31f4c1908c6a442ac2c0b3972278aeaed2e9d89cb8e147292c30e4e2be2f51b5b6989d5d721753a17b4d87c71c2466972097966e52ebf14e8425507f1bda1b34c3623b11e35b5f5bd0c48ae8c2e1810269008075eb77e72819f521997d22c4ef64141451f40a95c552656484ea21500a9afda6eeff64f167e4b82cc036d348d0b55b0f3a028701b09a690f759d3b00173682eb4e4e973722b3ed4f30c1d001d1db142728be72ed30f71332eb1e50f290e0d38cbde3f5c72eabccff7d9e6c1a620d66ea62f22ab0f6b0ea7b6915ea7030d0f11be0d67b272eb4f15d4ae83c0b468cc6e51ce9e13ca9c8f4f36edac2d7e7a45355df9028a72e08eddd3ccb7cd392c20b9cb6db53cefca67555155bb69ad2b5541fea5a88a7289ad60ef65c05f6e5bb89bb5e04adfbb35a9f22e0557202b30bc8281249c6c6cb7528f33b97aff9b0fa0e6a17fd9c039eb784f623b2576abdf6675ee71fc76729f674485182d92f17631578d291605857fba529d044cd843117ba362c98fb972cf94f29567ad5cd317b06e5f027946cb50e5182b8778080b8090bdbb97976272ce906938238e50216b1c6a54bd01a96874cde751bf3ec365c413d1f2ee50bc28e643f6216e0c4b9623375d756b7fcbc0a0ce13994cd386f9f1e8ef2fcb0ba372f5b5aa78c03d1ff564ed5834e7bd9ae55cf5d9ec85b8cb110f103d678e16564e436ec8aeb35b209170487c3daa6ee16380c15248bb3820714cd6ca417bf8197206925783a6f821be14f7ae1c1c3a8d73be0d303e9bea244e51c5a0c19359d3729d42d44e79eb9649b60efa0d56177b57aa3c3063da74e3d8afbbb27fd0ad7f1877f1190d2c1c85ec6a0cf0a9bf0474b3f8541076cf40b396795bdc2c106a340f6161e56a3c83684aa777a967212f16bb0b944311f7e4fe6a623785125052df72e91a6f66e2e84b30f6d8ce3a7c5143de7ee013e7fcdd10f0b5858c61975643727d5132ca2b18b629c8b06b09dc4865489fb77fd73a2ab6fe5187dac39233e22431cff8d523e15ede2a0b117184a56799176e7713bdf3c46d94fb9d4f72583c11245b8718c6f89032a9ef08dcd2245b01af7ab0e0d2685100847db5cecb003372cd37df864d35d150e5538faf3be3db227f24913ec05a257ba46b9da9b813547238312ab3e48c7e854b02800ccfb24a4da2eb98ba35bfa06e1e78ee4cea68e5724edd40d7370d52c858cca39ad7d198eb9bf3006074666d230a4acb786051837294422316a12d5e13986caa23c8b6cc27aa0e0f771eee31b6663e67a3dc5916723a90994c29f14933954b99dd9a80ddf8ca5e90b8773f23833bdcc939616cde57193d7eb38345d814610ce0a81c7a7bb20c17569934112f6e492332d02cc1135659e0e3ede693a42b35dddae77a04ce814654701250e194935597beab05208b728ad3a5208625915aecf52d7794b37d9c46617efbb72facf21e45627c0f238372c3d5df22049f9d25b5a254ff00e8629a5777b1c1f7a541f5b111be8e3c50fb723ece63913a3482fbee814b2eeffd6c35901c9cf4e209728895d95db0bbf39a72eaf70a3607d4a72f7b98a38403c50f5e5403a11235b1929b14415fdda531754f4c1e8d70c74779b66fe9f854f70ba700df7f807a8ae969fad1d2386dc34d600e6cd22c389af8e5dd395e135d08efcb9820361ad270d0426e9b04deb3b9295c1e1d330a1f557e03b80d800c42d2877bcb8c6ac9dbdf636f562e0030da289642723e8a8e4ffff53cff0e0181fe0f2c77466056c60707ad944cdbf4fddd9bd42d72b7b8c7f120b957d056ad56549b71ba301f9a3d92fb7c70e579bdb25a01e62e7224ff2595b5d6a3fed07446b367a029cfe84daf35eb883d6a39eed547bff5ba69d0c612825120156993abd796b50714897fc2a05f6fca8ff4d1cfedd6c5e3f1727734e0014be6d375fd4b772beb5838d5e5945ab6489f494318625d049eab7272a6b251ba5f3a0ef8baf21c868bfecfb4cdede6d07972716e3e2d1a840cdaf372dcbf8fd8dfc03d99bdb85b272f08aab987a5de4f6b8d5e7fc43186fcc6ef90723b196c5a65957e9db519f4872f6ff7ecf79da1f3763f7377b3555727919bf272593e710756b5bc7d6aff6cde0230a90eb0cd6e053ba359b65433826a8eb669725308ce4f7171ace4dbe26f0fe344effb4605d4d5564ca641c559ef6934561c5cbf27311bdee13807f6cc8fd6f8e0e444b00ac10921c61b3c1c635c4fd028eb72e1ebc46c02c047369bf72eb6fb985f91d40f1ca7d5a08628d49c0c06db12b44def560ef18709f68115a62edbe71fd0f557bc9b7a7984e6848d0747ab791292147d433f5894ded0f1c7d716e7afc31b0303ada5fedd3895b2b160d6090bfa136f3549d1ba6398a45b07b8abed433b28ebf727dc26008c8323c74517872ef7e33a5f1c882ad066ec917de4dec3c5e6b4c6c2c196cfa70cd71523243e9740ae0272ea5ebc8c1a44f7d458db263f1d2ac4d9e96c6e01830d760b64dc8103434a4c725147474893dde777fa88bd44d2ddd33d3aa3c46a10fbac4d9aabb1bed67117724985e495997bf08bc799b8ac3e9b15f0a671084048256d78ecd728020bce1972ab45c522dd7ef0df80ca5ec353a005ae044217752f3c0a5489c41f8f9e432a720dbf4ac40ce549433d3c36177087fa2eefbca9e17a831fe1173d22f1943963727607c7c28b6667344a77d1ea71bea362fbf07b91e175ad19b790e093d3a07a72e8b5b69e3090f91213786aaeca4a1428ea876735fd2a2df8f2ebe055978700720a92fff0034b75c4e561a8132c6b4075465a37b9b1c74095bd5e817bc40d1b1eb16357d340be4d7f630254a34857c5551b78d867f295bbcbdae835375d58826cf83cf77ed22bac32d923090a2cbedf633d18fa80adea9431c05fd4cd3f619b2e0633704f2c0295865d888342acc304c88f3a5f895abf26217764ba9bb7865d72b7c0819c94cbd4590496aedec50a799ee9fc0d657ad64291ae2de95d6ae2d772952029365c15aab5cfcf5c26f5d0c005b49a2d864ddb4053eab0b876dd7edb7255daef72fb02787909ef46993639acc365e512639014d032dea7343086e1f57252694c040d9c71c007af0eed7ef75de35d6d785a88821b863ecab46a7701b97288c4c282e8ba3a1a2701109a8525d5cef41909b99870fd6ed192bdc164755e70b68e8dcbd3bd77abe42962c9a8c8278f3de0a56c195fe296d148e37fa6fb567200451f69bfa58a042b0097f6bc40e3936b511cb8c0e212f2401027a01d651272021c5edba70bc85d5d543bfedd247159878a50b3ee67394b9f195bc482a4fa7246c808b45abc76662ab1021eb9c9f2e49bace09642b0a96c8c6db4aec8bd5972d290a9fbf2bdad427d8df493cc77dcbc9e1853377f5b28dddfda97fc986a9d722fa11654655299c02a07a7e290bac91ea3a639d0cdaa67b6c198799a42c0d072daeca8805c3a37ce6461be9b4abbe47b8c8e493c32bb95aabfd508f14204091e4c22cdcb63f397ec3fb1457e6132faa760bc53210f77b02b4ab9a52a713878726e5a29eeae1ea92d6cdf44db0d8c5f33770262eb61ad82f23510e8b4afb5b316fc9fcb24ee859bd99df115627264130cb17ce80af455afc7dacacb6f5ee52b5b8bfd0a0bbc5e173850340b0056c4a95c768c14e65ba32ad86ba2066e7a307072d97d9d208ba705f79c7e444341fb821dbf5f3dc97283b8123c004be23db8c8720e6e69e3f5ba077bdaa2337fdc1510291465f6d41660c4a66da4c11075a78572db2282036a562db62811ed6e987c8ee0ff9845fcd7965bf5c50fdcf9b531ad46df3575781fe61f1bbe17e13e5cea5ae7945d37a40136723eacb9c1d95ccdee72aaf4e76ffae5b9140e484beb74b052077608e75fbb802fcccf811c10093d41726af25d3107db734908963b8f083ae0f43cdec3114934f98912c03e14a50f19722c3bf24baab72fbe2f11cb40ce5cac86537e3f05eb1f16904401081190bccb72b05e7105587af28fa64524020acbf7f426785d85bf9498908cf5d8514fffc9726d2e3bba0e52a3f1bace775ff6528d4fcd459d6abcb0dece180b528f0d439472d170f92966dead8050e314d6ba5f6988816513e60aca007dc55277ee9a95ea14f083d12bfe777b4e121b54d215fd7b0fd8da35f21956a60c556e7c46ebdc880360923ae600349ae6ab1c95837f7033222b61ab9c02d5a7519f14f4c6b2827d7289c19dd79342bb9a82bc6c9c8e0c4ec77c36ca121bada2483602319d60bdff728a90131a1ba36ccf8d110248ba539a731587574615c9a50a9d86128f91e61141f96b01b0b26aa2b1f6b6f924c2690df182e622a3d57907c57943d7b98fd56f7298cb50700189bdf2bfffc82444b0cab0ec81191d805f4ee2bf91f14d22867f72d662acc51ba37d7f227e5c13cd4052e821ff8afd8a1b4fc9d6e489b0fbc7e74e9ddf34a4e05624afc3b48c1ccaffb36898ad192220d6f17f5c0bff47cb42d772019bc4a953e108282d3945714df7e7ea09800c512cdf0dda4fd43b1bb4e60c7279b0043a2bea11f7056cfe69f461d235dabf43c8fad2c617081ac3e079fdd74ca2b358d51f9384838adaad7488b86ac61664156962ac87317bca91185ac2ca44c26d9f5fb6e04e4912086eceeaa40e35fb51d55df87da88d07a42d16ef067769d39c1669e2b9f167573dc43652c60714a7f2db0c2d4542a5a5c82ae183a0cd68ce4e5f535d85017de678e7046460c74e0dd894b2c70ca6f23f805a994174753edcc35b6971370ca6f251dfabf8e3b8b67a57a5a92dda113d2b495d5341a1f272692b0058f626aa49a7254fe47cd5b1cf8d3cd08a8cb5c60208317cb6bb73b555f3e5033db9d4d8cda28c7175f4527808452505f5569d4f72d5c3c7b52172f472a0d06753d1dbb79853881a6b30b84bfb1c78a5e2308022ba96757bd378dc50252b96a7b317172ae362a7200fd6e745d5fe35c0693014822b8da1fa2088be0c72ef6e74739b45da30f30d97505db24a20bbbcb67e9239177fdb2c5d00bd1da834588fcd1fabac5dc9964bf50a92598ae86054004a428d5badc507541a163a3d72e8970769397a8714f1d37794d69ddd8fccfa778a46b1fbd0d0235647b76e3f2d6846d0f972348c34281b77cfe6fa865e22a003b0c401411d22af08a9460522591e36c5aa17644aa1d4f13424a8479e881330f9941836534e9434ac2091b711727f3c0a4c97bfe70235c0e1a9963f078225b9c19f32a2dd49771da712e8598972873354090cd05c45bd1bb6864db8fd1dc3a920bc3a6f0c63e07759348b144b722adc5ce161607320a036dda68da200bd67484c1232731cd6e7beb7cb43bb5272c9b013221440b40fe5c32da7801b7652b27ba22cdfb653954af0929465dba5722f303df17e0dd8f8f55468f9e9a5f572ee1c38e4c39539c382bda7ac55ce134e2c283b7dfc68cf162b7111aba958907ebba876941b5e71fbe24a2f5fe1361772d3fbf11ae978e7bf7be483b6f0bd23bda48be3483cbf6ed5507ffd9d201af75d8d3877740ac1671f135eb6f65857091f6a444b61c9bbf99db5d69dc6d3c34d3cc742b8188f7ee0521f35b7f3f91b373ba2cd5ec022e0eaf0efd88701ec85b87280ca56898e54e51f36ad0f9193545f821d845357d4aacb162451ffb5f4578471f81fd18b57675e8c5769163096b0ec32c9702ee68fa785014dde8e9269642372470e09fe7ca9f93c418a8cf99ec1f4a134dc3c9b242e24a7e000ff795ed2f972881def7442ac10e2575c47bb1856909661e600d9b7caa1166566e879e16066724033ce049325667878095c4ca34feb01c10f7a6c4d80f326e818f6388a196a7276176618e73fa93d36aadf2bf566e7215166523d9a3170e16ac3ebd27825ed7274c086d3339e1fafc30c92e96a9c62dbe10227b86b119c1454c1f1ff0a8424721f78547ba8b4951deeb03181c9426c7130852f7a33ef665318851556367d7c7226b0b2275dc86654e51d14caae5f005f13e8612c4c9627b76210aad3ec1a3172eba32b475564b12d377cc22c859a896757d5e9a133618cdfd52ad54d1173907245f3502d5ada80acf597534ea059c80821f1af4671a5553c9433c534cd9cf172930e4a9a5f9031b87c8be46f90fbb42a62439210660384e3b150499589f182723383f7263ed72d270acc59fd663d80a536bfe71834a4e328642f10a93faa5c7292c539be23cfeddf519e82ad466ec6b098cb52082d383a2b3309e7e8a9c44872dbb2eddf2bc921bdaedd65a38f28cdccf8649c9bdcf757623021e0828dddba3cf085f247a78d9012857d5c26380ee218afda766160e5031c97d62c4fa7b730726f558dff07216151d045e9b02ba3a210aa3ba54d3d7b5b2a32a6849bb87a7205b7ec96bd7fcb75dd01deaf2a0957331e050d4ba0ac4069b09952972234bd02725b9ba2ef4964eda7f942c45d1e9ef9c58427e7dedd8b1be7181f697593da307284c0229f58636a402ba124d1a647c352f0ab8d452c8ac2673c36066907fac04ec0cfd670391b2dfa11f9bbb2fd12ba16ea1f406add48027e759f5a5554c909197f2c028d66144997bc9dbaf1535d1286c28f29ed1765bd9717fc3eebce9a5f728dbcd3d4cbc765a92f555d92b8af53c68028974ea65eb14cb6f2abf8aafa9972e7d06cd88de84e68d2ff54025aafd24eaefc6ab055fc22f1e8815bc46b2f7139f53ccbc1f54f7ad610e67c9cd02cae638a9aae686226a838aa2fae67205e0c3277c1406ab78339d688b489c236609ee83fd52876c6c3a389732b586d7503614f8dd52476c9d3ab55c2be426727861c8f3dad5ece8865910096101b3325183372f18fa9f0d54bc44d25e79046db93757956825fa1832c52228fcc12bde97bc52bf0ab9fe046fca4ba6373ef2ffd74ab09dd2cfcf69d88a27c27fd693e40217344ab7ee1f2aa46a86a241af4d13d6101505c42984f84a941ba0f2b529c1d858b72122dba5ef3a6d690c4e6959f007219dcea54f8e2dd28074972e49eb76a712a33b4f9af80819d4b269f468a8569d607e048d9c3003eb148d7918613ca6a2c9923a00186a93f1fd4fa1b7d1f2ae6496b6e478211da075511065974c075f2e822726542c0ea7b7818d46ed36e1bfa48939741cfbfb835de4f34519bbb4bbb661668507a99ecaca7919e8d9f22eccc35fed240ef47b86723a8389bb91fe54be30972600f9516006e64bb1061c5988e250c0bb17603a9bda2676bcb38aecddd78f81b8826fec9ffaa9aeeb4b3a3dc721e8797895b009e32c25dc5660134dde21170722a4cb0a054e1afbf8dbe0d80d5abd0fbeea95266adb3e59664a5d60c476dba2f318cf72e161496bc040b7a9e0173defb34238c7d53d583408be0c0935dd0a41af7286cce3f7519c55bbfc1575e91ffd2e7d36516c0d2ed83c86660838d2a6572fc4d0d2a4303995a9b1674219ae59bfe24b70fc158aaadbacc7f14b880fbcb72c7ec66a877b81214e37e5617eeddc0d1e7c9f6075181bca07add30269caf631715f8b79fdb3767d371df4a9f67dcf548064067052ae2b3f0274232b98fe9a74855d59c5c3b2a8afb7948bce9523a6d6e791ab7b8d95e8d14d638de0528022462b836cc60c36aef2038dbacb4227c40838a29fcdc8591d9785181adcc9cea4e727a73de4357c5bcb796e7c4b587df66b2c394584849179b0f99258c12a27e0b72d9ef3675b6ae10037d5b1c357356b3a86f614d4998a7c1d98dd4edccea413c72b53b1875731f17bf8513fbd81ed241ef280984c4bfad6a576ffcd57b391d7f725afdce3fb3e7ab9be41fa13e70d313621cf0ad27713609a5cd15e270fe52f2727bd63095b1770770c8b9cc4735c67fa08b5977acfc0d6ce82b8e21e2a3af0c7218c16e36a211b1cfc56204cc4173e7e10f8f5d87e8351f44220a808db2e422729d78c4869f93cadddfb23b6d389f43154f3caeec9d492b73b0adee8851dace594b91eb9755888f843f81ccd0f1399349089a034d77d9c134c807e432bd7a5e45b8ec7ed401ff53f6ab9021b9c70570e56d4545def8ddae3e8ddaba4a42e1d072525109dab2b41cfca099102aaa9090a5dad1bdfbda25e4a858e041678db877729e6439c6db2a1f50fa9ed85b7f6fc9a3a0569abc1334450956cdaf5d99742372c42c4368ff32d677125def05b8fd3c1c0a24f7383dbc61157d521debbbf5d5729d04036d64f8092eddf17eeec7c0427ec5c9aca7f2ebcbfd9632bf777966b35ff40282998cf658afb6c21cf938dc75e7ccaeab445ea5603a288149540b9e2e63c07ca32556397f30f7bc168deaa580462d05b6990a0ed8e93b4b8011290a214a84e6ee9ba6ce327efb10f13756d4d9d348abf2cf4f1a901b2c86930b9f31e25f771923c1ab412ccf28c58a5b9d6c752592d0859938fb12af60f92f3e8446163f44ae5b4b846750ab56cf97bd33c9543a331d2f609263c4eda13cd2134fbead72504ec9c1f275663215b8760572ac62254e88c40f8ca18460e195410fdcbb8b7263ce0fd861570df7922c167c26e0cc81a74d5a2a9c845fda8764340506762d55b3ae09e526826a5d7e83b05837d6e65155dc4efee3b1646a99932f5579023d3bae08f22461a61d12dfbc1625a0a61947a8fe09e1f88ef9898ea4f564eee6c3720ff9fc91dd5760bbeb210a6817ad9b7a4dac61ae416612e024bb268f0e2bf76a115a9395cfca43fe7ee3b548b9cedaec9721d43fce51a7d1eaef326fa7bbb572d2cee2d7db45e2762126b4e6929ac52d8e79ef84664f64971fb3a52aea94707273f545968cd1adbd18153133e9e8908bfbae28ae56108d2d0927e49416c0fd724ec0d1c10d643ec250cd76a30827a189aceaaf01a5f36baf3274a9f05b7f47726537655d72edb8440de28f7a78784982f59fdd8bd116f6f26f421348a4221c60c76d589f3b774e76307496e6763bf66f8c415213a7ee9e4d83236c2e04f0b2729231abb8a370c3fda66c05f330142f1fefd99c78361c679c80b29801a92d0c72402d77089e19577eb10f22d8b9ed4a0f40f530c60615ab6a3c3de8708568bb4f071507195767b7d119c4fd6b3e13cea15fb6043bcfeab225ceb23a249abd830f8f7a03fba3b4dd4ab5a414819fef19a57446aa496cfcd0efb69a4394bff79b1909325afe5a7211755a1eb50296e8cc2a64191336140ecce98725ea60f2ae8272813ea7c42a852c8d89ae23542d06a505a7d93db48f80277897d8244ea50eca72bbf8e1a2488b542ed90694a826a63e5df68c33898319c9ea82435d457db6b0727e53e744eb35079a3c126f065a2be5c0203d14cc1af5e9b108aa4d184486bf72b81e9ff2625ae7d0bc4fcb59fdbc57238bcdaa0f6b62fe47054a9c37a713a7725bfd8f3ac52c42113d76834dcd7db9b0621387b5b669a2985f54947e8194ba40bb83ea9814f036bafaf53300478103845e49fd98368c845be5be95b57977476542a97afb6da632b07a9da415b92ff4a76cc925f0881cc070ffcd0d37c07c7a720b4f30aa37491f9cfa5dd1f39b8d3fdeda924e09e89379920e0e8f9d3be84f72cf7eaa1392e218a8f8b29ef96c0472cfdab41195cf7e29efe5b5ca5b6513d972f78e4871a48480106612522cd296f626615addad5aa0a9e225766caaff025157fb65e3e175067d1cde6472bf9e54008a622eb896b80a511c4c03bbef582e0c7270499b363b52899facbc1ad71c7fdef5a59b927c60b0459d5d04b13f1aa1b05eb40857bde1df5f4420bbe179c4c797bc3a219d9995e9e53ce173d36594dfea726e14ce6f526fe541df44931bd6ea04e6d9e037488fb99bed25b3f2a5ae3ad472a735797dd6693089296bb2f79cc3c72ea9946530944af807d0878c950da8487200a1139a774f893c2d20a835ecc70360ea950e556c9b6639d4cd495f56c3bc72487c41e84851ddf4fad8da3c979561013f0afb9b7ce08ae7d989b61a786c1f718e3653f38a66f57002852182bdb819c1eba59c43e472f52df0939ceb025b4b72b69fa0a3f47d2afb82044792ec621b53f1a66b6bcf48ef4909103cc12ef1fd6a8c80b942cdf64afcbeb0a3f57a89df5d0a174e61afd464d60e05da938a79d817704391fae84fdf1fb4c7950d03adbf5d024b73b4e113149205baef0d85befb72697b96fbd4cb434619f030c93cf168ec264d3fc5e7807ca757745912684664254a456872aa68f103d74508784d62250aefb7b1dc91b977144f860afd3891f87223b13c25b9af287e8ba8ba895a5c4b324ce86d858b1baa42170fe31c2ce8e972006aa4e265408fb3f36cc9289f1817e987cf49264b48edb60123fcdd7160c972cb5df5dcd2ae9773b29dfc43bc699797816f42f65f562b2ed42c5205129cd6725f999e6e2ec49a2c59b0cde27ea9f17484f7fad598cdd9f096fefe1106f1c81f9397a6bb8c6a6e4b2d6c28dfa90a078418faae137804a3dcb2f91994a4cbe2726041dbf5f616f663577520bbcb8a39f838f4a04560eb862ab84cd34ecc24a1723f65e8b30517eea53df83b3d36d26f2b3228e8b16cbfa1a8213cc9a05b506b3c4ad68e086465e31d704743d2d7eced6692252991c133ac2ed78210be3e1d8772d29af4debe637b92cbaab5289eeea519e87206ca81403bef772480a843b6fb2685082107180f991ead20e9fd777ce8cdb0638dcbe21775ad0477c86aacdba372f036f21bbee9bff63bcb6421c8a4ef318243c02987a00aadc15a7eb677db7b72c93ca3af653ff628ad5a29a134d3bc9800d4df31dd5dba205486e2ac98ca8706ae9e8e48de98c3e7c13d759061e19fac3013bbf4571a8d5ce0646b4ddbfce3727f3ea33b0a50f6e3878fb65692bf04a75e130078a969e95f195fb69ba5314172d1429682131d085fa088fa5d1f281e8fe778da1e53cff7cbfb0135d253a0c92c3bef59eb028ade034538eb4861beeafae13e7024e172330abba93127a305f8724109015b8906ba9166bfe292827802b73789776f97006b3627f539ec97d57d727476139ebbb0140d5a5f771adb218eafbb8dfe175f87c9dc80c839d5b5c81772dbd75ffdbac532bc25ec13715e8d09c38d3b0098821b3e2824fcefaf2460bf72cd874cfa52e92e9bbe0e5d54fcb15fb7cfeba2b63e1fda897df28275551aa1721c3be36de31ccc60fdc3feaaf335a51bed4a09f6eeb9f2569c1c33facd73b07206c2b10a4e33dcc56eb69d9bf1c8d89695223b8ef73dda7c0b37954daf080501ff9e58340042e7b8c9eb7a3befa89cbd9a0434c2b00c87e888c0e737b46729727496351c1e95f6b994248b3f590553d5f92952ca140abf64838680943e330a722b8bc372691068600e9c7229cd2d8c6806d716db9cffb4b64acf8ad035814072f28a6c42627f8ee08b136f9245b9803012087f08b3ea7ae348cbf712c2894725d86e5875029b3261801df3c24b5b83b2e6c89729390fc82d0bb9059a2f08c13eef9be27d488797876a8e3405c03f371577ce7524361312bf554ae20f328d12724b959cd3d2a331fe6681a38a7c5047aca74978324ceed257524401f8610f2f72aeeb1c1219ad39524a4f194951124a2bb2d27e4a70ba877eb5c9c9e9ebb8782849082f16bd7f55ba1be4a4bd79453802990f3187bbf2e26007497948e4bc2472f591027f2ce021571664d548c87764e20255c26bfca57150ad2759b5ad283572132266c570f88f6e8fcd4604fd8ba190dfaade36625a3028f03b3ebceb0555721947942089a752e108a3a441a9c9eba8e9d38af9d9a61f05eadb4f2a80db9c1082d015603a159fd03b812c67b6046721c5cfefa3d12078ad3350eb8a285e6a721b7dd3d808ff81d27c42443ccbc4deafab1c7f1950c36594b64009c8093c7c720430411c975473326c51589f13ac12ae03ead392e6b6621ef511ae5c0744fd721457092ac4a8731c6a9f686b9e6afb07e3d2283a421490292703129c159602729324593c8f11f0b3be0c303d5b9736a4057d847d6fe6fd7a8678ef7be005be330a8a0fd0577e58ed75d5d79813d0983a9710184bf585f667df96bb95feff0b72ea2ed680ee858021581848820b0c3854c13fe18f66a8f3738211401e74868672a3b7fa25c066d4b136b0bd96e5c57e119376eb7357bc0c4bb73c0b5419b09e3967e33ba4c47d060c6cfe96f088871416ddd970c614412ba6467c6f42171ec568c1bf54413d061660e847a222c22ebf314682b4f8433a2afbaf390a9f41cb75726492a5d461e6dae7017db57f2f81dfc7e381b70e4667fb758087128fe1f677721e7fee45f06c6841354643296ddead751a0efc4eee28929cbdc9608473b19b7239a0082e44799394c6c77107c620d493d2255efb4683b427bbd311d64dcc48729198289dc61381d6835b2ee2a2fcee8f56a99dca89f9dd1b299a151ea9de095ecd0ecbadd250105e5ea10b4182964dded749c78756ac3a8aff69f6f180921833d3b639478ed49630d44bde1809ac79418d5fe76f74cbce3add716c5d0debcc722d771cb3986aa1563da453012d8cf0d9346a21fb970b44276d840114fd0f0272a1d960fe70614e6c70f6ccb8510e9fbd06505a75b8452304fe6af5b41db9a26902c5f31d4f007f78bd358d2f8bcc7a981b788b6a23add536d2ef4a85c84b7e724ebcafbd13ddaa654815c300490cd0ee65643c95499e070d26120bbb5f86ea6f9de1b3d5143d04adff742a44153bede726432ef60e2176140e58bd581cded31d0ed430dfc5abd06494ea15ced4a8f6b06ddfbee57af6c87522fbc4c5a9500d72e4328446979c3623f52d47cdb5d8c1a08fa9605e594a40046368fd9244476f72b873f0f6e4eaccdfae03f67c007b2b4823af3945d34c519fb76d5cc5714a2a038b3afbd9106d43b524fec47398c0212dbbcc5f86eb8bf23e5bd0572cf337f12639e0b2c44c9f635176c32221f2904a5bc7c22f614b881ec3ed544c954d0cc472896c1cd6d2a0fc06222ca936d2b159606b72aa6fdf662460963cdb27e2a0863584e1110e4214e672931f48f9c94c6d0c5b1a73ce2ab52a4e4d2aaa2277c2720eba64b84cccb42ebfe6b1827684bae52eadd308bf4ecc8fa0caf84a826107004e63a8e9b222cf52f131521e2c245e1682ee7bb6bf0023044fab0e8663e1aa2872c1e3a4d6e4cba459d89a2f0b13bb1edaaef0d48987d01ab5f323e05744aa2e4e74fa7c2b6ec1a3d3b588a6a9f14b505bd2e96403032b46873614e31cd7f2e95562235ea20525f92a6adc49bc9cf26a3b6f50afd97ae2cfb9f8fbfb057d758e72d8877785927fe4312e730f04ed6f6ee58255a51e883ea5f8aba6813903cd5a2bc2e626ffaf6b14ca162d6f816a781f5fa4734bfac5d6b4c38cc78e3814b3fc7207ae3b98b16afdf3294959118605828385707eda2bff873cbf9336c6587e35721542e526dbc3bff1f332fca9470c12e8e1ad12323bab54742febd842a65845074f8e5abeab775d552985e9880b289e9debb8d2897b0b29c997177c391427e07242e62d9ad026d14cf52cae36253c7449f69bd6cc562fb7378374b1a299a5b555f360162e4fba96a522f334c63964fd9ba612189da68ad44b33f7042b92cc7c3cf0d946fa548b397027839d265202cd8189875b69cafb00cee55fa737d8f24d729572289e7461632f2ddb561a264237d7803884dac44735226557dff7318bcb726d2645df40eaf64f1f5dd82e859bdbeadb91c9154a3dcf565ed70eeda9077c7227929e003ad632ffc12ae9a82c2bf9334ce53e3ccdad472764c2f132537e7572db8751e463b327a927ca6cb168d4ce17d478c051909237d8f5158c1a5ea27d1b26cc0efb41bd56d3280f0159743f9d9af1b7d5241eb193f5b0323f5dc8b036721df02b706929e7fe549bba62a598780fa57bdc74d3f82c26e975a89918d0ca55dbbcfa9a36dab0df8c3185c2152ce49450aaf57a80276aa03d3e014ff513531f2e99368e4627b6a14f62982b11fabb166b441a7d33dffef4b18f67ab355a2d724ed99e68b19fbdd13ba9eece41ddf79afc8ecbfb501ed5a78b3a10b8b212730dc5238801a331162d5c8cf120a8f6d18eebf86c073ac06184b5359b94746a747290baa4d62590857c2fb392a14d9e739848796b63d1b5c912c0c10d9074837e0ac5ac6142d78029b02d7498d17faa8777f18c10ace9d47e72a545fbf71f7cd6729091b9be9e6a50a1fd8ffc563c567a37c1201bda8c9b1a650c5980d2e710db24e862028399396c3301e7a2e4f76c1912ce631e96fc89043ff182a97e214b596c6eba5531dca72544034587ec7edd80921d965137901896924ddbc1d7220b192ab59a87512eff3665dfb35bfaa38be3482288334029f65a4b05dec2cb235750727478c89e90eda322fb9be82a46789a916af0d159797c300d38eb9f09440c7072f13295b6ae006779f53c523acbe74b4b0e186a170a77722c8b4191f55462357201da37288eaea191c99020140cac0c75d04a40d681f2b8d079fa0505f6bf3b72cb8b382da4c7c00366797d5675097bbba116e7c7029d208336ff8e8d937d4731bf1374a28d1afa8813b7e1119cb92e7a941122a4e653368ef130ad66ccaad772136c791fa5f86fd04dff6652d8ab3a2dbd9c7c82b2f03d7272fa54cb9a877d446bb33fed0b83d015bbeb547fb25ab4b9760c431c5fb8469fda3918ec2f71c759438ef6f556a64f5f83ca32d87cdbf0c9eed4a19fb42fb18b262b86512635b61f7117899bcb78e648062f14d6fe40baab54b7efed9e92eb84e587aa2fa7c77831c3fb4a24305ddab4881e554cad799789520b43a061b23949ef06e788453ca1728026f0bf655552ba7d80f8808230624a8b2926892e0abee3d7463d749d368e72667f9151d1de8fd15124a0e07c409fb2cdbbcb7c4320f445bec23d125152a572aa4c0bf3e74fa235feba74d4c93df421c8c760718c6390afb4662a8f1d727072d83f8d71cf66ddda4229b060f0f118e2cd9ad68d59b811de9096021d790b420a4850900ebfeeb674561f8d23e7baaa0a8a2f312530d2aff5099ea58262b7d65e8c9b76e36ed973f36f2425af63e14f2495d3724492155516992f6c7113f09e72ef8ad0639d850d257721d79a62728b4ffec759b84da16b8437931e2a9d28ca7203addad3cca5bd1636de001a97442808bd02529b6d46119c0c7ee6c5715369726f0d4f5891f4c3a8ef856ebe66674dcb34202a1ea774583e8b63cc86d244fb7268827579906a0914194af5ce6ecfa84cec51de0b58ad7b43aeb2e7761b5eca13003dd5c70116f731792be3acff8d67c7260969deb27cc61a6a30397620b2897249cb2f2378f89a5e24b4a0301ba373a1da7f91e6661203eecb431eb33336ce72dcd0e62d0feb8803239c1879c34f86897cdc40a6af3b689e91f85e8809b45972516bc73b7aa073861e706649d457735b67565bc25d3c2103edfd570a8f0f7b56e35ad7dac669f45d4c4797495eb14299cb04bc1853325de96b61f24a8f692d1eecf1572edd8611b2337f96fa611a0fa7b5cafb7ff2075f877ce53979b5dd3e72b48084f41660682b040d9b02021d6bf0610819c630973d9a568c3a468cb6b35e97ff11fc0672c7febbab8c39924f8b33c1feec7713a7ca6c2ff5685af8435b72bf4d9a13fd6c854f33486de8e368d62cc4c27e1534d6eb5df77de9e8656d3e34253c0209b877cfca3234fcb4c835dfaf8ed14d43162537b43b9c7eb65dd7ad72a889ee6142703c7d4ab4c457778f28e9a41c889823d34a5a47b60b4e39962b284e79301e72b6ab1c7b37c9ba6963a1b7eecb567cc30e7a6548d158114772d243b16bace7ac3d17c16209d0a1b324c94dae11a33e4cdbf30f5a3420e902418872de0de0a9a2aff6866128dc9cb5e8ab5b48aea842b6b19393fecd7124d82ae404da2612929e8aaee58938289df1872d9358e6436199b88ebb356970b9335a8c3761b232af56dac9a2763d63fa5f6423db33faa5a724680b644553d5c82c0eee726e84de7ab12e35b600f3ed2114b08703e766b216c18e560317322e8f32a633727379c2dc5ceb729577eca960c0513f9888b8d147b1353f8dc6b512255b28c9026e24c3d84468b74fe0196e08026ac32e0b30be5cb9c6c1a2795f030a901eac72641bb4cbee6059fe8379c5c7fe1e82c3222627a68673cd3a9b2bd4ac96ba7672991274b56868d547abcdf46caac7a6136a03d7d218d6efa9d58bfae73c0d4872bb13394b5036c14253ddd9088d02661c2c4703c5bb98e4263602e5b5b72ebf72c19a07bf4c6d53b79b97afa55d71187e1f8d0febbb085a8b700de084c98e7557dc2e3979f9a68f9d8874ee5d96f83d6616c6f5be840e37d36c450a497b4b04021299aac838215c78462f74db4c1cbec2c27354cec8ba86e02ddbf4ef50a9cd7283c23e05b494cda0851c8a354982eca9f3ba18e4c5cfa91938049148feb91744d2011e3b67422eca68364336278a996cbd2e2efb46699d3532d038bd98c15772a2ca9b3c7518b4d5c3d6004b5d096b1a0a5e2b97de135466b9e340a451cded0a047c060d3fdd70ef7b3bcfb2d33523ce27056d19f99911e76779055ce5fadd6d53303e5b795275cc78a429d65a287c39961aca82eb7a5e7e19549a5c3117f7307e1c1d92bab7eead6d6bf4afcd536e1b4fc88a83c7fff7527f19c89eb4f14b13daebaf83736db79f29006b5505f8977a639c50969ef50e71a8ece27234a76a72d4b524b6d5fef5cbb73e41f8a4b8c5b50aadc4b8bf84501ef96d1a6f46a13d72c2d1f8a2a79f3ca0d0349f0b066cf4b3d7ba6988ab7b12947daef639553ba52c1e1a19d33afdeb307438646b978133cad205b6cfcb1bbc41a5a6dfc66361840443f20d10aede925522373710bf266d0d9a3fa2ca4a473274a4500953674da572e8c45b23543caaad811c3b6b38a96c058a6f0f498bd3df2824a8992b090ccd72662b0c0524c534294d5f3191bda1e1497f9b675a63ec1bc140317999bee0f4724a19e4845b2cb2649dc5bf81df43a03ac6e2afbfe55f6a2c2c81d98bd2a48a2c2b05121cc4e4f3f4b1f2b6786a152d99f23c98a4d5c88310f66b5b3d26d96237fe86438baa8910ec805c1415fe535a2aaa565c5e206c2d5ae0fc3c11db24377247a1d7b5d8c1f421dd26af53a1ba1284d34cffa759418da443d1489d892b97065a12eddeb64986ca27bf240e51da29b64247dd51d3d0411d909e5d85acd58921289fc006ed2c7af04497c333716ec9f9b026ba40629d04d67f30dbefd13dcd58e9aab3a386e351fdd931a1f0299771ce7b7aafe16eeee7555c7080ad68d493722aa56a8e004cc9729a7ceca0ab5e3c1af36f71652a5006867308c0fb95ffeb3e9e7b919c7c96127e924cb4d8026b0d72fd35b0cb3692139a4ef40962cba8881a66c37d2fd84b91c2b99236a67869461a097a40451386b4b21e7cedcd969cf772717336c899148f60096a02fdeaafcfd31279d1fc7afcfebc8b1d7ec0c1a21072aa1ebbc88172b01b17d8f0dae4df1e549f8f7afccae2a7b029da99916f25034dfb73da37683e871d9402d3f18c4165bfc31304b997c9eb75635d84d1a2c3e306abaf3412dd62ec2d704e503eff7e36e752dc9c4e6f842c93b3f2174b17d38d3b134ccfe1932642a19d9ba7c17346ca47145690203131260fc56f0da40d17ff725e525f242dd89326db5df01e2797e5453e1e3fef88a4bf7a319f2752c548be72624dd683efb4061f4d0a8f7a3e35b79b3db482f09ebb2fbb9fb7ad416004e20f7ff41b7c4fbe0cf8d15c7cc5b91e5267f024af4136249a4a4646ba133464bf729a7147936a427bfab7e20cd43255fff55c8d2831fdfc7587c9eef891c09ff4711eb30de88aead2dab02a868cf8b2a9b23b7675325f90160a28c67a24c3985b349a37067b8e2b17acb94900334f8b8461ba85f9df5fa41100688d65704012567231f2ac81965211ada47fb2c4eb36a030c7c90aa0b36faf88fb1fbeb240a4e272948b9f515ed697c0b7e59df91c1cfa1a3f0bca00d74d8cdd731176b5328bc972f61582b37e75a0c994ce5c0bbfb96a69a2c885f6fc4f2e7be0f9042b35c2ba72e5d8cc5e280340deb7de1c9b4928def2033f17d71c8443a26be0e15452bd117249a21e8b3f4b1e2779333549b6a68f129d017ddb7e28641dc15853ea2378c0463f5d646e327c86085103b50c6b70b75db89cdf80562b213b2f99cd90baa3da7243a334b39baf5c2276774eb69a47bb6db5f77d833582759b368e50004b8d2d3845de989b56398b066c5e44ea02dd81acb85327b0ad4de19776e8283ab86a164b5aaa3bd7b6ad4421a0bd2256ba7b0a330ddf31e68c38a11a19f22272b8622a72e214b5062916274092ddc4a8adac708d24154eee70d71409f2c4cd7a6065d07261a3d663eb0550746fe89e2fe19b943eec7aa8c510fdc045853a16caea2b5067aadb7b1fae1765e6ccebe1bfbaf72195ced814bf245f98b59b176ae25f37063b04eec7ecad198e371462afde883ac635b0dcd11bf4e4753c50fa70285ea6c31b6feacdd5090094b208a4ece9c49384805161151400870eba75e681dab301d5725a84cb39de772205000f2b5562a889753979a7f1166f0ef73efda9bf876e6a7265a873aadc5a16d5f20a07e08fdad731528a1996d7eef07b8bee2fd3250156728c7e96503cf45d106bef9f8964008025ad0cf34425f6b7cf4ceac661ef1c3325c97547968ce54aa9de00954a217513585a9f8d2f1246fab23698ece99f415f72045a197a494ee71ca78323c111d01df271ae6c6b89a9f1ca239d698401552f72f2fea549e4aec4874a42ddf80d0cce5d4a08892ab321d2803b3ca41bbaf13d72bc2775247255d6b5fa36df3094143de18c4d20389f372c6d073c092649576b4eb787b7221ea78a65fcb0ddfedf3aba3db4d999b666ee8a74b2f5674c6a0aa91067d0cb8a3d74476f4374aede43b39411fa7b4c1648f0c23b0f98022d47194072c0981971acfd63bb76920b35ba92922ae05ff8961ed418c61fa7d90e23bcf172e7fc4ba0105ab7572d12986be1e36cae08dab87dfd755d89ecd0f0faf215d95d4ed89f5b8494ca8c7f36bd56bbb5cb27ae95a251c6da577d388c442a573249720eeb5a7c896313714fa90105ef134106e71dbbb29a43c3a665acc4f103cba264e0c4e0a8db789dd73e4da14fbb5cffc113d5ca4c7968d177c5f4a693399a537249aa3f4d30c9e08b5f27315cbbd136e5c6f73c6208c82f7b9061c60194ce91725f45b2d52c4aef8b4567f116fa4d025340bdc32a4ce81febc6512581da8bc8726da51885fd26471b4ca1d350127b161b083b21baa2afa3be0b8c1fdf1d0a2772b2bf4194b27c45eab0ee58e035213a5f5f7d8a96cddc39fd8ed5e930f0de705f7c4d8392176961a9bf644d81d94b2b2635a8a00aec6dce8d2db020cad02df944ea67bcd6b1fb9db43d0883e89138331a604a9e46bca2cda229fc0ba15478f3724d1b39526e3c7dd0ca5f19e7a1b1fe800daef2f84e6910d90f5b657ee057ff7275c18d0a9d4d607768326b165d8074469482e86a5567ce37ec3ef1a6d2e18f124ec56f6590bde08e9daea55b2846c343b277fca4b7e1cb6a75d938bbe75a52723bf70576ed1f7a1954d65aa9ba29fd5d8221294c9294961b29ba3f47126cf500bb62f4f139d90bf4eb6d2689606ad3b709fc144010b878db56809722c10142725c123e463ddbf698781b92e33c71d33be83228dc3f50f15e8f0fa8a9d2dbc663c3a611e8782767097d5098ea56ee19bdcea08d714b5c2fc8aea2634940ba1a72cd5aa79a79046ab698bb3b16274d375fae1519b1247d2740a39fcd0fe4055c37d0bb1839eecfdb37a39e2dae785117f0030dd4ce1050698898d008f335f28712943d68ac19bbdcae4e36bdc3ccafe0327dbf401d988faec664f8cbffa97d547256a4e1646070ad6be62bacdab9966b4f4c77f39db00cb320db5b34403721d41c08f4a64b57252a92c67205d3681d16e0de9f7c18836aa0802012bd58769e4915b620f01b4878ef58b98727576413153d7b0b79eecebf7f58141c20a02934e4729deac1f680ccd8822021ef52fdb3a10174443537da0d2c15ed1a0c9a7f12d8636a0031de63fd8070fc79e387eb5584b03ba888936f7f1b17320c13657b17fe72138ed6e5f63c854d4dfaff9db889911f25e4fd137b009317f82c461befa0c8726b50d6a554073b3164afd08096a5f5bd44f37c1b1a875e9a410880e40a99767273ae4c72712e3d57d71ae9fbeb47e08d3dfc55cec058e9ca597b58f3bebba5729e2792317e706ba1d6c19b657b716fb7c8e5cff990e9fba905ef1b9244899618e6eb439aa6f3bec54c0c919b9eae632ad02964a3de2f2a932ef25d72ebf32451cee1dc3dc4b78af7ab22882eb7d921c6a50949c57ec25f9c97c126939755970a7bf1b9a55d5be0d3222229f9d71a569e5de7a800c45856283cd68d21be776c7280b179ee527180251c50cf64d5384b944c315ae57841f1cc522128c3be326372cbe2465fa3086cb4d629e53b59854eca6e0e378dd7aedb7e93b60ed83ddabb48ffb91de97fcb771c29aa44aeace1540215d7656d11ea86d2592fe2047f67953798544cdbf3dfe4f5595642d51a8fd722a0e909bbc78f25e44bd1763a96f4484c570ce92067be5e90b4693822a2ab6be0be368ebd6abb22d0639748b3f721d772747727c84449e06c064f7cda595b45d81ef6548c60f61894d2978044febd9c723a377fa474a631577f50b2ce9ad76af34dd7720792e0bb4dd3d16820de94536b3576f794d9b62616e754c0fcaba44aa4e24b6f0a81f799957909f46e98661830ea369d2ae6a36b55ae656a58fb4c731064258869c6b9f988a3fe57029c7a68725c2e9bbfb42ab0506889e046468b19ca88119619227ca4e70b3db7db8cf0af72dbf8d81187c1970e788d1b2039d16a5f5946f65912191d20312eef2f28f7f93152b11062a21c23619be1529791621bcc40f0a466dd021bd02e7484265776446a6e88e41e0a91f0a1101d738b1848086c99af2a46a718b9c7fd57a92bca7f1372f49e94bf6b1ea455e130b3cda71b5ed2eadb1f26742d7c86479487098960461558c6997115c68030b1c1d451e5b23c66bd5479b81701877e020c1694349e7672665be9d4676944a81ca3cb7f2b88f2575784eac11dfb149eff54b329c4657372833cba607dde6d96ebd820c628c84e9f844ed86a43bba8ef13dcf5a92cc29172396937c4f76656256bff0db825bd3af44dacb84a93eeda88598245b44b06fe286513a08fd7c644a789bb3dfe55d1e55e5c1a2fd2c86e43bdefcaba13103fe972f0e66fc09b6dc00db07343c5bdf8d4530900560722e3a3ba156874ab2a29f31f567abfdac46c7577aca7163e2250ed48522d6f08affe1dbbb37529163f9f9109cd38e821836ade26138ce1f2bcdc782faa486103ca3fb4f03411924bb8fb151e6c7761a5280a66b82f7518f2e4e91943198e677a1fadb4a97dae0fcbf5fe780c09f0b1388d70cabfd6d35a996c1b8082963d557b75bca6f48116f209bf24a53361f4bae200d8489510de1485eacc494847d77cd0323b0f6895e48499da361c72dd1e6b2bdb6e3f09a58a865d76670707795c16fbe92fc9751e1d5133a8d8b32162d3d9cda10434d70c9269731a25e2deac29c067a8f4f89922dc370f3a0b152a4386ee719c8d3daf41fcd1dc040f1f05287cb272594c435924b37c8f6a88bd2e03d06ba3bbb4a99b22d206eafc877d4500574252e2d77bf42ca4a30219d1f372cd2ed886941d7f6e8c46c4e3cbde1f4162af673257726c0f50ee91f60a54d220d44403af0fe4bc9c4819a0c9f95b415f327c3bfbe4f53458803253e55d99ed148163869e9ecfdeb633f54ba0a07c917abd9c8781e220adc96af7d1c20a2a04727e7ae112b98a4a9398c78da722c936f0d179c0f16c2cb0baf50864800cea717275cedf5ec356240056a75c789c353af405cab9efc290de4cc21adb7b56db424b58b4a6d75f7fa33a8d73e442fbc6dbca3c8bef3ff7830c58e1cd4103e6dff643a60a921cab821db863d2ab4f8cedb62019fe67bdc69bc9bb14a2f9b2504f19723ea8995d4f4c7c514ff20bf378747f2d29d10f2d569c4a68b6e8b7950d26d672e0313588d568f3f5551266778d8341f05c2ded8b4b4a1476f39da4728ad11551023346d788e0b05d9118f0d134426763a5675c7527311b420469841d82a4771d2749e8945766dc91fd82a0ba0ce8245e52426682aa21e80e2ec1337b4df4c072115c3a57467334b5513d4db908753e07918b629c8d9bf6b06004435fd8512d1b84c990d18452eab8c47294a0355c25aefc5b815514a0c32bb03b1003343e2a72b2d79599534e69b08d4cb690efb1fb9673c4fdac149f61b7c5b71f31bbf6150dc6c0fcd29301bf3338693d85744796da955eb742f1555380ed1cf8abc08f8b722ebb8f3504049c15d963e17758d7147a1c09d462c078c488022579a700fe471cc2673d920f43c7959a315799554e6cf027fec32c48b91e0b615feb07328f6a08d0d97017ce45ed2204316e2081195249b9c66191568b1cf8d03f100780b0597216232e6cf513c8823a2ef3f789bbe6aaab0303754a98ad4954dc7d08f9aaa672e208e0f9c5a071b561702a0c84b3cbcd2ce69cefedf683e3dfc68b3b1a33b272d94a01b561abd9d62a4764b9e86992eea0e8b95e21c4b0afed217f706f024d22e870beccd664433a9e6759592c528005cb2f731c4880a3c0d427d329e5705372239a6ecda2f9d1fd620a190f5e55926a053bd183a66d011bfe4c9305d5f53021233519625ce204a874e670f6a9beb740d3dc3465db9e9fa252ed74e122d117727f7e5244ac382be4b1399fac725910c93bdf81af281128aaae12b6b9baf10c1eaf17e4c6b08ea18d0561990d3fc602846ea37a5c362698b241e2f03ba0c4507255b79defaea1f2068a46d6cb86475b1399e1b88659fa2794453f087316ddce72919ffc6faf3acd7dbf2a84ca42bd1df4d9cff74f6e05f39fa8a8233a40a30172a0bf28b856be104775a7ab876615ee4055b92d487af022de8621b0e9d4cde3253bc235ed3962b23b4993cc800d6b0b7bdf130027cabd97d4dff9f69ba3fbd80b0159599253dadd431612b95592468c4597f017ce03972583cecceafbee244b200ea1249c8f444398968c729e59e5ace2f5ac2675dd14d765428e4cb173a946403dad4f73f7c4de067ed3ec70597632626ca45f44f7440e8f1f2e7fae954a2948e919b17e52480f7ca4f6ed99042d7be7bfc180c1a0b211a8be3a5d1d75dd035ba740272cedac4ca74befd38d4fb33228d681061c7465f0671f501310049fbe0c4b5924661b09438d9d50346235f6bd35e7e7ae831914e789df275d9c925177728bd4960efbfe874c94b16343d70d3441f379e3fae3d6974fbe36d05ca8b62872947d6e07a4a2cad269d1c83781af8e3ae8ab0c0ccf10d5b73b07c1d02398e146a7d4d3ba65c881fd470fe2c250b9a834ec73092b0297b7e0829a92c8e0e2920f07339b831458ebb5addaa1da7fa3cb0fd5ce13620add10eadeec3afe2af37609f7fcf7fee0e75bd11a90601ce1bbd3978eb5486c9a3a03033100f24413e03e72e2b7d85270e2a53e2ae71f65bce56ed6814da09286adfc60add7293f46d09c72d53ba78dba9ea7881a0b1c6b1f9e80c0c4c8ade68186b1aed1f975ff568a5d72e560a101b11477c833ee4d97b6c7bf768d88f0ef0a7e5b2804902b33932ea9725c59ae82d4b42966b61ff30eddc85fe76a5007ba5115153a3cfeaaa27f238a725df24a9885dcb4c5e91a1175de08a204e7d7ff9df14044acd48535ef5fb66f72e5cff35e119c31ee4e732b19ccd71ca51917c6395ca111736a4fb3a29eec4827aa22937658d99d57d96560970bc394d5dfebec1d8d3a503bed27fb05881a180b75153dd97e1ffd1dba5a2d8b03aa004481e9957c530acb517b02cc42008dac7222640aeab75d5f2d36d12c39f4c7075f48483bb054f6fe417a13f8cd39c5c872dd69ff5cf69b03dfb8515e605a186351dfb47fa03762d9a990924e3873f040173134a5b82f981b371961770c24b03baebb5abb0df1f86ccfba4aafed8b601e720eb5189470d2be68297ec18b8c37bb5a70336cc7ad33088cbe373092c033c672bedd7a23068f090e272a703ae42930fdfd8b454a711575d3b9b6b297cc5fcd7205391b95ac6f90ae52bba0ceb6a59a6863a4d84055fe2dd420baca18fe4c5a5ec8ab91f11239c3499084fe96d9c4160f83a2bfc89963ed2686e19825fcd9e727dcbb66dceaf70fae621a41564d2e8192151b716ef8f4a917610ebecde3cfcf72725c856f17c541b493f66250d77b75823fc5e56def483b2d5cba43a1b084fb322c8f335aca6af1dbde1f53c8feb61d87fedb6af9f0f10d46d120140283468472762a101aa96dd906f92dda31e275d6a3f4245f27aba4ec74ee686b14ea86ad7262332d9a307a1fe5430daa5c402d3063fc9516a9d95cd70e6279e58bd0f4b85317619f7686df5991ee461cb74887f9ce5006ef1d4f39e9b4f44dd38ffa785672451d683782c0c5a179347360beef77071c70768f3b091fec4501b7c4f9a7077262112215d37c8c4734782ba54d22556e5624beaa557b78fe55875355e9dfbb6ee82a53379ce7873d48d0a372532fa914e07210c12cdcba94047a026160aec072aa5f266d8aa6e67a7e4e773b13bb2846d9cb6881270ba825cc7e604a5d704d727f69d5e2cba899806ed20e2fcc3d4ddb7539c40b3481eb0eb67b6d89d5510972def6205bf223e589e4224e2f9bf18558a20c26379f8f6af9d2e6ca7d60df9c72c566e13a9760743ee6b1f80b2ad773c13f1e2a2d4d69a817d08c6c07546cd649c4064f73c6e44db3b5b3783122685c98deaca772ed1c2838c7ef23ad2ce148720385e4094e77e0ba788b71a234867045130c3d15ec0a3286ecd1671383498b72699b1be1d2f2a2c4b03808024a1134559823b521a50c32fb95d04eb1b27d2972a5c989908a18b55b425b98344c69b89ad964b20c7e018676ed60b6133db4280c638bcf5882121754be681c7204a89abe463bc82832d4fe8b864be560d2ca5c43e5de3fed5ace818aaac63c97835b3ad189c78138b44cd0f8224919447cd45e729add8813d07afb8f31abfc57dc0385730ef0bc80195dac90e9260db812347b72d20e5c3110de5ecc7cc5c18b1727ab799996b655faf6d485497333174a2f4d72e071fd80af78fcd7bdb4f7765e1a7481bd3c569d4edce15cc5d0cae00c903e5107013ca5e775f88e0799720fc8825ab22b3b6b677ffcd5f60d00cf6bc7e3cb72eb1548907b733c0a9645adb43fccc448db3dac591ae2fab76e624cbde81506725a7e451d883fd446c9f553c4a1cfea07ab29cf1209336a0d84d2d7ca9ac94c726a78c5933ff0878ca1380daa3b297ff97cd003495586b566f68922929d3e6972a085c8a13f3141862daf39c6ddafc03fc9cbe586c3a4b4172e118469f72ea0728bfe8de2904e9d724a611feba061c06b0cfb3df87610a2e816a5ca2058e69972b9c150780df5e7be4d722f21ce803bc64091fb00283b8825ab3a3aeecb1e297291a8e69f4e47a31b864c3df09f32fb84afe7bc17ada49efca1f5251bf16e9072a14c6e4f48d9baa8ea89423e5011c04bf2d4f12ef85cbb507c7740923166727224b7f8573e5f9d2adb9e84bb38ee3acdfd9b649c45a5fd2b1213c1f5ee43fd678a3b0d193d8b961550c02d15b26b75d1bb08506492395406aff08e5e6380fb7259510d821b20f66e2f8cecff9b3f587b549261d6b4618a0efb77ef23c0d4b8643fe9514276dcca64664909cd579d30c8266762a995d861897a76425c7f975f72ee2960e26253cacde1c9692fb3055f24ce042a6761161ee02d6e25e18fd4c9721291a88c1c6db9f8d208da79b0b91b2e68b1606ce8756343694e056abc143a72dfdb41771aadd3d401e9c016b941495ef55f076bf93eeab9cf9364d4c6e80e001efbaf2b23401d5f56cd261467aa46c482c23d31e74e3f24c01c3f2240d6c072cd355724fa62f1705e3169a682fa37ec42b874b31b2b1989fcd68e773bef913bbe4bf87326c6f710ebac71dd5b4466c033ffce31ee8119e448813e894923b0728e2ec3fe54e441784bd64709a8cdc5ea8d0cbc22da6a3f5cd5df6c7404848d20560d82d578085895e364da854749e08e8c1b5cfbafb555fac7e0618a001244726b89eea285f28608354e5a3a1d8146641ca01328ccd5f6269db2fd0d46448548f46f39c3b323323c2b4ed1190482179ec516b2c08e5a56f192bbd78b9aae7f72410290ffab252de847e36b1044e664f489ab667a191190d283f2442ec53aa27228e449290ba33a2fcfb40d701f92d46a784d7e2ac6d8e11fa3448b7ef8e65572a339ff340112e5f8a23c711a213fecda2a17c1f04a5037c778e79f9865c808729ea5ed940b9209c8734db6a9ecf3ecddfeed1ad5ec2ac344dc8a19ab3ae61c45e4b8a27e8b5793f82fd47dd2a3e17aa0b51f7ddd43a78abec118af547b314c50ce60e56caa8ba2f6366ea9be95b180512dad93644bf294329ee1ca024f899747ceda2dbc96a89d713688e7555c29b64956321259cb366e7f4c6f7b04904c6a207efbcad027ec400ae1b94af3d36deeed445ae8042b8fccf8f030fb82c967e272ad7244b512265c7030d5c0f1274976fac98f795789b75b22fec769554c81847278eca5e7247ec7ccc6e2a8d327336334c5c8e1ebb07bb35ae3eea677943b8e72ca8e3703deb754eb65f228609be0ccdc00179611b6fbb27fcc471926eab2d072b2e860230ac3ccac5bfbdf34dc39e542ad56f6ae96fe8f267e290c40a644762c1308e1228d00c7fffc367e64a0ede29ddddf33a222859fa0e349ed1c2aaf183fb48f59d0d8fedce353d320e29a4878ebc2f7632dbd00ef8ce84f82a6a55aa1726d29eef5c7bd40975a052ee86e29f7afaeab010a8ae4ca85bd28fe3c0000860f4b0e0190f4720e15829dcc6eb688b6044bfb6245ec2c8c4254eca94f1c5aef7281daf37da2fb9928fe4b631915fd58ec3a38c69500563b9030f4c73a2452e4724d2b9f02305aa47a512873f5c07b45dfce2b8b9f73fd5bfa3c86989b10a0d872007b1114186873034b6f372ec4733279829adaf61ed4f467b7cff0276cc89734674b709b39bc8944adeb4f18d3c24c2fcc504c4ac29ed6de944fedb291b17b7261460018a359c007e346ca9014ee33b215c4cdfe45d4051d726456c752fed772380c14ad35c541f6335b9e4cce51b03ddc7dd8644bcc7bab0b5ae7ed694a9872b655c19a2d040b3b7f359c9e56e1a56cdb7f78377dfd62f75fee718989d09f72d6364f2679323582a1330e4f242cb25a7570db422d8d867291f13d430173c90e0b7f50af983aac70ac3a8452334a3810ebabcf9dc7912d865c7ad51824239072c68610cdb4b4a18626d9071a0af388916c2b821fd041fc23121d1a333b7f1c72759fd4522a1441706f9e0234149bf9bc69dd36aefff66a982fa73198be26c3544cf142e5fb7645f44b34928f96680712d5702d131ba3e58b8a0bf430bed634724a144f5eb58a0e63b2bd5432865c057aae17d9ce0e50bfdb3c09c160b179ac7277d975e1825422af1ce160940e347824d5c4aabd25c8b09187b5b3cc9c25c056fe79e3a2d8daca4b17888aa866968c320db6fc7f99c16e3fec35b7f52839312268fd1ccedf953c57c35f367c6dbbeba33b4f4e6441cd28fa54eaf2b7ed77c814d1a12a180d8ddea76497b227690b0095f38aebfb202fd6e6ff8fa9bcf780ed728e9a787f8da99dd1713711b62fc35759494c404164083fbd8131b705fbc52372f732e048153687ab8044958b800b6b111ae7355427f7ecbeddf3bf13bc4ec072ec28f7557bead6771ad6f9b41814d64aaecd39a464dd4c70f300ff5fe1049b6aced38705e6fca0107f8a2aafab107c4f03815d90747c58fa27fe800d5ce91e72fd41030c0f81be630bc3d9983d4d90a2b72319d2f35bcdf1844793c89ca2e71ea6f4186198ab2c24ace625f588dd68eca406fd0e7ad356529bd98f795c16de0d417c953503f6f3bcdaa573dc5039495de342eda98325140552181867b26460726b81184866bc4700ddde68cf64a4be862902519b08321764e344fcf4460e2450cf709ac18f065bea5b6badd6538cf305e6ec94dd5c9a11caa126a459597021724e1187605e30d1f9e8e93aee02d03161b5b87b1fd8619bdea213b6251b381a723cc3fdc104e7e603bcb37475450116a716fece95aba632193d1f3520c55f087297911291fae1b031b1c6cf1e1bef33de6085d93a3b68b42749c681548503567297f6276a9af1b7b05e9bb2ad9f83193ec9e9933bf1309c9176c3eaf3aa48e47220d7b755216f98fa31cf003d96e64d1399cf1cd3b6783160d8d41eb178293b623f434c8da28167405b2f41233fc36396026d7c4a33f6b0a13e5ddb0462e83072de16bf7d4744d620fd2bea31183e5d21ab1805ac9898e23974a00145814adb721de258899f80a99c767bbf4e3dd182693f06461e359ab0dbecde8f47e71e42725093a772ca7eea1cad3b2f59a6f3d5efc263ea20dceae0dcf68ec08c9837297217f91435e2b767707be744f4bb28230427a2fa864d65efcdb739aaff05a146264264cc7abe5eab3205176d263a23cbf5a468410a3f01e15194d6bf6965c70f59b55e172a84ba1946421909a36ef513ef40088062f3f861f5c2703f01bb080162ad2800526cbbcdfe5798163e517a569a167d0f3c635e92c7e98e2f9d451f027201402450395d48c1936893a79f7a07b7cd0d1c0e5952e309e42af3be2558a62e3a6171071db4548cdbe9c605e42060446e59f98bdebd168c482ef43e7e35fb2222278a235a78ffe29a125ca77d226bc2797d0117e2b5266c0ef630ff57fdf21e44c014760906a1d955b657abc5f7a99ec2b5d3aa1c3916f36ee52c4a87e4df729e7b92272571cdbaf3999827852e5a905bae336a2c89bed2de012bc7b56c8669ce3084349edc25281ce6e4649a4998853d1207e4a95dfb038ee09847b6a9e172f895a0c2918ef54438c21349f7a6062bea1f7eaa7dc738107bbdde795a31e83f2a5774c163c8566db272cf7642ce83689db896515d095e453638f59d0e15ed72520bf4579fd01e848d0ad64b8ac5f99cb20de3970669055fd7af8691ad646372d824505bac9222fce9ea1c9dc905b76d7ffcdc295c361b15fc457bd484437d498034c7491d2bbbc22f90841f861d0b57cff756fff2e479beb6b56f259b3a9b72dc4c9a97c78d473ac877084abaa48e6432ad946bd99cc14c64136f1fa8ec4c72b0ef138e5ebd9c8c5822e108e539717294a6ae8d95e6620036ae1d2c8711d97250a2f27b57c604c36ff277c17fb290dc54f657b5bf098a1fc3363cf50a0c897251e46a4895b6bbbf40c4ee209937c5fa7ffa3c60166df0116ce5fb57f8ed047214b81776ad5020f1b6c4f7a5db031268ae8c74025c3805adfecc7e79900ffc47b3b7657343bdf9edaf6af9966c77185cfe814952a6367f5622c973d464d5d44bdee80af26ac464df76669479735816276073aa75a02db5a2f0360ad0c0c83c727523c107a712739815bb0fef04d4e7d4516a94382e2f1a5f88f41b4df488d37222e91baaa902cc5311cdbeec5cef018594fdc4c36b30b316cfedf04f210171314392eb29899d6abc151d78371e9b10af019260d691d1892569a2cb78a505380480494df900fc51546d0987e0be8d4a48b372fce815b84fd5173b6b9e3c3e2e4e1e9f17089c3e609a25e928d9b2d26501d680198850fd4764a7ae17bf7481c0726ad885bde93b856df8d06805c10c69994c0cf8c7d3ede2018522fbe7555cb90f6475b211ce406eb8d38dec728af37733e39d6e6641cc5b9b3e33357b1a6c8972869e0472f7b2e60180ea7849af5a1efbafd120799e6c994dfd44ed210c3f607265ae79d6bdabf755c12050f1445ada24833c56eff2129360eecf6f9592d5ce7212eb69c41ba86df94082e1d256b7257623b79eee08e91a65de445a2c77776a7222c5793c6ff65977f7980eef2ed9d0bc2e5a372161a984779378dd8a568e587273120be9a8bc737099d2305ef77ee32e9f1d0b4f937c84138296002227c1b7077e4c720d458626948fb54311f560f24a92cde66d97f275e9bcb94960ffea6a72994ed281a1f71236ffdc9284e48bfe55c33261a96d2b213c508580c26e606b62778369137a1a1245aa9a3f040e6b01ed114ec76149bb73baea2841ab15a17872e05e2f6763945a3fbab3ac91c0490a38464a9a50fb3302c995cf2aebe243f9725e1c6eaf7541f2dc9effddd879a2cc095a8527a669b283e08315a1cb0941bd3da42b87fe55d512ee84ed6f0a1602cfbe76f1d1008b15e4e97a442a7e1da9ab7233313b62c444a352b15ce5548371a1ac03f71438daee5e2c24aee9e555d4dd26998835d8a65c6ebf5beaa2abbdfb20c7754f69092c6724cdc048ef50499a67726d5e0969370955af1414f0915f8fde55e73ee0ed6019b1833bbbe1f37c771f720efc5f5679d84dd9c0040f4b020eed323e2e107626713f3dd817530ee3db837241f82a2c4c8136e1e20cc4d46468ce92a387a2470fadf7eef97451ea28595136d96cf7f2c64ca6781e8add586ea67b07b81d07c6e0def67f11ddcc0592821f72506ab6ac51ad2edb08584a127e26f2dd5828b90023b27fd87ffedc74edf5b42a979960d2e1da25d4315818dd6f09505bca8eab96c12b5e0849120e5cc8d53119dc36dd1034e2fec74a5320a28e344deb82df133b5784552e39c8992d9095d3724a46873df6989bae57299b2af14e13b76b6f8a9ba6276881fcba3e26b592ce7247722f965836de5eed5825a1d06f3fbd799dcf2f1e7bf0f47f40e71dbcb39372b2d4ff11dc3ca6c90cb8681094904911566983ab23dc6668e3fbbd4be0effd72767c2344d84737c6b172a81087aaf980fec43fb193d6191382eaa5f59b6a2c7223b0691fdc2e861a11d369372179640b920e010ee411367b4e228dd1c4df99725e83758f24c560aa3a5f7c1f801a673211c4281e7474a0d8879d9229abe77217acba9158f6059a116f82fba14665b0b5a97f1519918df6de6d8f9fc6f407a52a23006b02f075329fc056820cc46bd7febdb5abab0382b1b2205cc6d0fe9bde00348d42840b6064b03a7a235b432dda47f5bdcdb27957818ac6f1e2a72192da72f8cc41da3523fa13650cf96db8226e1f963f154ca0f7c80f61e287cd4c092516aa324f1aace96f8e7dc77b7e6a29448cbe1c0fdc27f529380476cf9ad10269720c0ec0b44352e66db2cf048835048a88a0e2f2c8d48a25bebe53c949e8701e1687ffb6d03bc0eaf6ee4558c8162d9223933e74289943ee0e74818c06762de572c03d10a2fb2416f08512e18135d66ef41692baf314b37fb61ec4acd738a51d72792cfc9076d808aca0f6a38aee25f23fd1f68d5935a85fae588d5c08db6fbf72ccb3de4c8c05eb59d49fab9b1cdf16a2c1a7ae118858d34fdb1eca58f85123442bfb0a9fb2dce4913a5499e379b9e245a37997b562875b489c6654294e7c731cfd0c6c35ddf7dae452c4151e400d95622dc36decc27a6e57372364f4c3c09f64309f70cde7c18957c301859528ab81854f4f9b54d8eaea4a1b5b8d651ec1bb5546de0bfbd7d2a1e7dbf9f809aff233e744e7f8a335f5e736b5a2ff1a008e42720088bdcd2a8c0416b1ef346932e7a99933cabae8c703dab201367dacde70b8723b0cd69a7b7275ac00c74c565d75649aa5adb949a357b548b860f5b73855b772591d8df20d539f0ce2f5bd4afc43747f1fcc921465d6a7384b3b3f5200dc9a7255f6b113968735ce0b3cd6fdadca512ddda5bc8837497649b98326d1ccd928725e2dbe7efa7436618c991dcaec08fc3017ea65ed158928beb425adeaa794fb721c769d4a0247eb32279b9b571b2330fec7d81beb6a09271944cd0808e4225c723cd72d05cd1114d0e69abf75b0d5879fd895e678398c796045a0a71263797772161430685b5a01b24efcd8d143611020f41cd5fe3f4b0d39a67a62e7e77bae10f87be0f0a6a0f716dd50afe6c16e1eb248307117a4f1d4621b3a75b584181672e2d83334be956feda25ea343c5366b23361ecc7ce8c7611fe83602bb0379e64511d3b590948f05041adc5e3dc408065172282452fae0bac930f98fef8e7b707264880554b4e86981106d9e13c0d047395b829d716890f794498fa4bebb518d721752e42e2547f85292bead132ed4ea72496d620861f7a443372e4d9c5e140c644ec3b6204d92984a9987495c457ff22a2253b93b9eb13bcdd3956f1d40ce096f253bf41ad41444a33d988b81c04a5c4610c7fc10e47e7c50f5a227ec1a3252722fb9334e308f610cee32621b8f62c1556306017816ce6c57167d980677681e72f1e2f44f31fa6a7ec8ada094bb4db9714b7b79c7e0c5a756c55130569c2245726413a8454965c7236ee0d753599efc11d698e880f9756996de66e8f7a3ee86725552a65e0754c2c5f54266848e461902e430cc5a973578c30281a42f7feee816e4379baed9efc49f6e6c6e54492a68d0331c2371aca5f4e24fd738b4e3b2422c821a1b181b2f75e0aac6f08d04ad997c86fc23356793134f64504f4cb6dc7e72aebb2d185b5dbdd6b9b84d76f973dbc6bd4bfadf073d0fc2272f2f64b4980d3134ce7860a9c19226013a79d579a0b1ccc0638dbb69bb28813710efabe14b4b72549ad6d65ec5bcb15d12995ad8536896e4005fa50dc12a467a54b8b0c6deeb7219a480319ff71166fb0a4c2c5176d47be99c982a522a8fabf6cb8bcbfadadd23aca4f9e3f8eae6053c3cdeafdb8194891c53ff59aea211dd1814e64003347d72b23260ae97abbdf2625dc6f2d4510df39efbbbc34143252f7026b280ccedac601eea0631148105defb5a37dc7834ae8834bcf9301ad3e486d3e3e05bed3c2f453805f04e1b1cad1cbb6a1049809194e505f8c1c6cb7eefa28902b459017933725d6c5c59e1325fd7b3b1e6aa3ba57773d6b47b106ffcd03465ec1fef3e4cdd72790792a38b3cb2edaafbd4b35286375a9b0c46dd361df6339ce60e5b435a7972eab681c64da5169703d0cdcb95fc4bfe8bdb2f0eaab9b2083164e9048a06461fe9ff02f68246249abe0a20222cdf7d86dc250434d0903fea70564e4894f2ce72a105aeaafb853e1e268e7942d689655c85ddc9f33146bd52cd682dbcac6a445e8edc1149a68663e1bdda2c3c8fbe2e5eba2f98121e07f9e2a62a2bd3c6bd95727b432408c9e4077abd23679404af7033cda91f62928809e89a879c641832e66fe748afebd368436eec539a56fd0571acd5f132843363f6748f0b8308fbd0e2725b4f9c716319e16986dd8866942a1b733887b5f7e30b9bfcac68905d05d5446b21b3c56a2f6b2d6f8be28f9b011601439868c6421037fa7c00104d665cd98772af4331556f81917cd56af25f7ca520c5eb01f391474270290e754932f3959272975e8579096e7b71dd0065a84918097a8d09c9669bf47fe5276fc8a67ed8f3725828bc10bdf3cbda0ba4bf416647b2f5db711134496dee6c846424bd6001ce7213d1433fa422412962b62a8e888a0ad4f0d82dec07df038c214965027946b172a2c05ea403a1f2e96afab680c261c90272fd99b9ec9e5e8da572cf97031490720afe5f6fc77b3aa3a64a5f4e2f400f0bceae09d296f787090e69c4e89cc8e972b51aef620235b325ca9b34ab85b4ba82ba37b3fefdda889aeaa7f94e6fe0805d53ee844e9009436f208537f0c338988c19f2266b100ee21e3cbc76aade33d056d95c63ec97b405eaa247a531a5e4fe9861ee2371a50e68f50bfd9039c0fa0872e2ea414d35e3fa1cd3db60070fbd311a5479b08a995e2beecc2de0626138e247e46ec2841452d346af0c64ec7a5bc7561d7120c49dd615457fa5992c119f8a4f86a03a527a9b7dada5276f0ebe480006d44fe85de8f9a8e863e30f2a7229882dc83241e35e9ab3753985c41ac9b24151a2c33f843690272fe43768cb1b641c5027ba1373c0ecc57f2f424a0229bb94590a03c5f91132908e9b545223ff09e8728e1f6d0ae725c07ca8253576a4feddf5111feb2d4812d0d98e861f594d4d7472dcf1e140fc21dcca455a68ad53c81dd88687989a75af279272dd3263493679155bb9d65b0c13f5dc6724bd8b80559603f7a4db8bcd83c3859990c3ec8c2da952340a603f59b352c09936bf903a0822da8af3522a80c32d41911a8dc470be6772e5ace9a055a09d41c3cb7e0d998be453612e1a536735e1e6494f3087226378720405472c185a8e6047c075838398fcd527047e5dafba7dd6595a00f3b05f3372f4ed5e8cfe2e05373a6c701acbc7551da9a2747f73c05df4f7e16285d9fa087239fd4041e0dd92102e606cb11c05ad12fbc923f83a2c4663607389be6aff157294fbf9c053b8fd5677dc972216da2d0dfafc33e02c4fa1a42db135035bac5572da0e7cdd865e28401f4d4f211a07894b074413cffb058f2e9892c3b12d852c725d0e5461a4c4f94d0294fd4f1c564c6b0045a99e82f86f0036d4651389fe6c65d2977af45c974d0a3509b7494912cb7c5ff236f58829a03eee6fdba3e579c8720c10fc278fbd2f62a5a3210a5de1e1c8a988e6b961e12b833a523bbd1f69ec72683695fda208cc17f0083638fee577392c32caecbc0ab77833c11eb3acc68872d1f00aba0318b05f8e7e4a222c3f09052826e50b18f3efa96aca2663bfc36d725db2f397b59339b05fb35af644271b594cbd20ecb5897f3b46322ac76d25d416b740119ec18ad6f12a6505bc01a05b23dd20822f5ac1678d4c433d8ff2f7d17252376e54f9e2e410fae33f54ad4edc183c35d0330b77e308552f9a664ff88c72ba1e4c5057d5b0eb967eb78fefcd859f123e8fb85aa9a1be8eeb9bc01cb597726fb33ddb5fb5b4bfbc9b8cac05597a1b1e0a77d6e1653ad663e20cd2944a3972d03d84dc1bb51f16d3963e68e71284f62d7914551712c75e81a018e427403e72692926a4ee22735a171818be2a4173477d25e285d1ff79c68bfda2f2d77a5b02946f976f1ff8bfcea7af670d7a1ab508237899bd919192c9741ec6cc8a2b9772bf2803dd4680cd0936c6c6e680228392c5555d6f52fbfff72523664e82267442925034f0f4062b61d7b41e3e5155aa7fb04681e24645043a2753de86511063728ff1731104540f823592072fd2a482c9c9140f65e136f1d6a25f89421bc7831935cade972a3fa9ec4dff3d3f65c39adbaf5fcae9892dec414e831667d7d91372c106ff27eba0c1df1edd93b63bbc52d768d5c76714cb564a2d1c2523db3eaa724f60abc52c461cc5116dd56929a80799b729c77fd8a45ee935364a3729abfe437f9d1aa4e0af4974d8ca610caaa1e4b9179cd0e8075766e0efa12b538fc4507284f0e6d953d598fae9c2d32efc0d45872229348d18604551de9039982010f05424a9702aa9614eeb66c72ea28c0560b64c043c0bdbde38e9bb0780540417647258bdb1c16c465d8e4b2a1944bba8b0244fe9b0d7237e21f630966076a31538188c3bba3ddcc6a0ddca14da9f84c2e1c7237feef074df7e665463c32fe3d321727d1b3a4956cb825bb4be1110ae870908e7a2258bb5a863e70ec058d52e564a720d4cf7a0abc32c9a498c740f78bfd85c5abf1068a6e3c4cea1516c772a74407236d56171b671c769f52296b9c49a8964ee3118ccd28f8dfca0fafa6150576820ef887b9e1d5ee17dcc936f4d43aa1c3941daee789da89d780d3734cd46ed657294cfbd28268da4f02fbe77dd7b6b879c4ebf22624ce7863e603063dcde032b3087ceaf6f0d2da78a753cc04ca3345b59478e2e2e6d647a58bc72721f39129b6f9a88025e524c8faa571438bfe893db73ecb5a4830c384b42bea7006776a53972bb1c07db50636e3658eb1ff66dff03259b0e69576e5e47e26eeec0d1733f77728b5fe03de9cf49b97c722c695907e63c9a036f1ba74b59c48fbd67969cb04d220d066762cb6fb4d1a98f6101c78e565adedb0effaf6eb4a06db3da16c8f63272db1b14a1c452805f54b3e866a8729decbab3df0b8249de804fe99a7b6ae85f24db79c003db417e18c1bf003c8e528ab74e4f12e50373a2024aee66d2a8d429721110e17cf10fd84679f39e12cf016bb60f7a91c4c1b541dc43bc44e888cfc56beacc41c53b516df4d55318dca5d7c3af91716cb4b988a0645a1d8277f1f627274e6b37521b5a22084878f6c845e9a66f655d6c2f0e6e26b40f46ea4a1053ad5dacae3841c0899865fcaffbd631e30c8c7435757d0c989ab92c151653c71dd9728086f53742a3eea87660bd68c3f70a37d9b39ba69fd792fd85305d2cf2e2da72e9a7bcf71f7c62062f37082f74c0708c9930a0b4c9fe93ca8ec23a1c4a3b6572ab1c3df1734b25237daea50acc5a796aba027cec04e9af14dd3cc6c8abd0b6721fe01f4e04a8e6e624016667b2e3ca16e5ca0c6ed6247bf598565888d936ba0021e30436b3df2fb654ca267069e1ad6f56320c809e48b623ea9bd206a50a5c720718d5d5fbf349bd16b4395ae36b696adb4a7e067b22d446474522770159d9682df0f89668ecaa147b4496d50d7f4f6a2b55599c5d619a2748af1a40f9d86e725e3ba8edc96d889b9271673aa2bc234b7ce7a82be529fa8202f4d2b5a3ed756ec31a8fdf7ad24d7d492abb75672dc0eedd4644bbe1f16dc17efee7a494be2b61d6e645be66f5c43c0ce4789505a556d687f7c77295efaa8f0f677044be3d31464dcb1c0f32419b22771afc1758495f9899b0ac5c7a37adee6733ea41dafe4935bd5905b98e5e27f7c3424bb1093926337bae8e4fb6d51f174d80a4ea37fe4c6c4aa257ca734df65e23329426d38258e13beb8e5a2ef9a0b6cf1be03994346d705087c771a3bdb106594309f87cce60060f5b5acabf138386edcf8df1152079728262246212779de0cda46d52c8e4b8abc8649c5c30983a6e726ada403ccd9f2c794182742ca6a05477b91919efdc127765d40a9dfa6e6828a8dc028fa4c70872974732d79af5b7a792b8f920611ff729bf2d56c4b52100fced22719b4680d77294eb8d233621716c03f430ef07c1efc262cb7b018ae1fa9e0e9bab19f80dce7258c763417a05b0f98e708515f8446d50b79c96a2772a3f06c7212828e821c77214ce6c0189347986b6cbad20cf6079d83b1b020df482dad28de8f8e8781a0572160bd38a2893d104cdd4d997b24e3756712662aca8bd62bfe75a7d8b2e59b672a026144ce65df19b099e91c89fcbd8ceacd0d51aec18dc77d81fb0c0e330fa7259dd5710f77b1adca08b6df03d6dc9079e37eb4acbfba1a79ef78391e838da309ce6c4c78ce8a1ffcefdba5aaab637bb855d202d8159c5cff5d4633a966cc724cb3c0f132e457a8b266d00a160a365b3ba17337f1ebc8a432151e0720629c845d253762dda3ca327511334aabd5b280b62f3888c56bc9817629a0ed994aa7c724ce024afb74c67cfb43cf3e539921ada8370e2b7cb2cf9ad9a22103e0f6b0a720e03d293043ca660a7abc66bf3b355d6f834ce60c2c775613f82d433791f8e72bdf0db735d4659de995b29481f6bbbae305c6e7e0f20ec3f315081c62581b872855aa6b8622159f2653aee94eb188d98bb80b4203b81ae3590ab5c28a2e4c41cb926673dc0d9fe90dd1ec872de21e98e020dbcd5df70a326e31fdd4971ff71725ea887f2d8ec215f364c88fc7f49f64916608e2415e8ac2ba99bce5c28918468e69e9986054f9973b7c5c963e37c11702ec0cf46da8652ccc167687171916b724484fc3c3c9a66df4feae6417b5608c43479593c4ad0913e91c8235e674ad96ec4a2526ed264d2c8d08c917d0ea3e1ef279a8875790e7828cbfcd6c88758fc53ef02ec70c0dfae9cf1a64dc65a2bd2922b72aa0512ff9f754bbd449154df94659c6ba2bec9d0e48d9087bc7d89bc124347a0b1a1be2b392b2f535af3d66f3b72975c6a32e15f7ae41d872755d9d882c70fae32680b825d92d05d2c4f386f3970e0d80947eddadb03b72b5a579dc872d3d9e58c8e9b9727e1a3305af5bfa6e2727db227b7c14d63547603e2a42c05fc26e3e7b018f505ae3c9054a39e80b66072f78bf0baf78bc47fc658ec8045ddbeffa6cfb7a94e5da5b759662aaf74deac7201b956bbac1c392500d149a8797e044fda6d97ebb18959613e88869a494bdb0c8b993467737b801b69b5de39a5ff0a2660276992d916494e4459fe401641911e8777b24f0f470c2e8942086fd17d91100609c9512500ab0cd38013008a105072a5aa3f6d67cfb7365c86b58f92e5266f93788715d0567856df2d748c85fe0b25abdb46c55a9cc37ab28d7d5ede4714db119bc01d06406fd72d02a341cf2f5a722205da2faa5eafae2bf26d4fe9436503b014a05abfeabe1ca4389ac538d0a572fcd15048a14502e6af838a208ab61d982571f58fb55274498544d320be8b7f45b86d7b65f712fe734955e82b07108e932c78623d4ed65eefe5cebe51b8d330727e0ef38cb7a02d2a8857196d6828ce2fabf06f9826c237778f6c06515681f4724e10b09acdc7b777d5097994370fb239c4bcb6bfddbc3c92e731cc806775a872578541d3c7ee3b55e176c51195cf0ac5441cfa0749b5c772e866e27328991472474dbb31d5c630d0e639fd54263130516067b33104387a4934ab2d9bf8073b72640f001d4aa6c473f8f3fe1966dec423519cc7d706ce65418dc3b641368b211af26f9821a8b21df6465450064baa869275e649d209fc24988203cd04ac67b924ed496c25a9c0172f0d784e1205e6787354cd55fd9200d51f976b8329bb900b25fc3061472e78a562adbb223027a60af2b73c1c020733d181d3a078e6d3011372cd214d0fbddf1360c440a069ddfcadc24251a582dd81ba62bdc11f9d4d2f280f2a0320ed4636c41671f0589b3e541e095ce166d474a3b1ed5201a5b0d8cc67722edd8037c26553aabe8df0db4797a6592b21b8c6d15c5aa981960b06b138ff7269b3c8b7a4907667f7745aa8c0400a5ba18cb4514cf723049508430a303e41261c7d6c8c93a88603d700f9f627ed6bc5e11bd4a290cbf59b364173afc0ecf92f7d1760fd3976a02a1b65fd02b0ca78cc94b4a6a37e26caa7c6c747f18bcabd7278231402de3357353dbdf9a553cfb3f64f276498d00d6188e127bd3033663312d431cbb66128d85ca279ca38d6b26c3cff5fe03592dd9d54b2d6aca12b4f0d7251cfdaa494b542c7eb1dac9c7d029d51043ff0aa8095c70f55ca126e5f280c3d2198753d8d54256d2031e9ae84f04965bdbf2b0209cc6d8ddd64dff8ff74ae283887242ba3aa6dc952aae3c175d5275122fdaac8229f900d91700bc7a62cbf722e08967cc07fd44adf494303980bb750ad86915a8195d45e424d6c8ce62c4e72587bf723a8e17e124ff0bd34efc18436be87db02ba7471ab7fa22809fc723c720ae0de84c923bf00cc32d2400aff530c384fc93a30b85c9e5acdc57fc57a2b7227ab33a332c52407cf9bd645916692bcba1bb185ccbb1c331ba399377a3b04725cb816835bfef6707866416ede0b96305d27a8aa805daff47528317b36903c72734c5f6ce328f4430b933e36f17e9d23369f4c3bca56d2d27f2bece5ce28c971ba6473e4164d30731669cff53f9f9638c84f0ff159264e1240ffd3445c67ea7240abad700b6f1e3a223a73063e64a495bda4e18d6380a203f25cf082bff3dc2d1f4f1ce89d56bfbadd8a2d4c9bf0adc0236069bf77c1c897b89f628531b20672e6a6d7e2701f3c667d34e2c05ba9e462b5b51f39992758cc34ceed167d28136c4d03d6210c59b0f9edb06abe56bbd0f1efe2b759b1f67644475c1dfe927ccb72b4c98cecdb406ff4f17d1f868e06080daf823fe935bfc5ae4d9ffd9eab0e6c5e12f7157b7452e9466f225b505175926f92da15a39553daeef53d90152d140a72c5730999000c72f3fb2065f6925de999699edba9e2205ee91f550449cdaa607280c169782d72a08d8682accae7a4a9afee92ab32d7acd1453fd1a3351dcf1c7246612bcefbbe98a0d4781c524524d99eab2c6276ba10d15db430d867423d265b7eb9b080878c8812f26241a424a30e1ab697d44fa3d45686c670059a0074b4721c010340a84c02a99f3bca6476551d4c0350c5044e81a785ba830b3fae32c172a7f24ec36488a9afad9d65e1227d2757124db9f117c404c4ffb6ecee09ebc972d566a8b358f655171901ad53f35bef654c1bbb837beed050f5f2a07e7b53ed7263f5bd6af99eb00d9768833882d4dbb5fdbb8dde25eed243aea1904bc70da229bd4de3194b7e75418f6505b2e2234b4ce56689b30c4f5ea31b243b1fcbab8772d863c68c560e0a63c0e3f7577f917a90f9e651997e44a843ea73b3ab337f51728cca19fc7508087c8e90aff5bcbc93ec989f35ec782590d3fb20a14958462772d3a009e0ade3f7342e84a610e0b4fdedb5e6a22d742631c1152de680a0edf4726a34f0d9e4cb33d51822185f5dcc64fdc712131a49253b1c87d4911b2f637e509ee85d296d83b7fa21d10b08b9c227a895448d21cdd7c0605abb8b57abcdb83bb718b6a4a22184a5dc5f5b585996cfd7d1df16fb33f8a2266907ec5163456372e0dadda36dbdf9ba32979a1b8633e20b7e403698d10d80cbb81fd9e276380f72e53e1c0569899f95bb9bb7c02f02bb75d0699ba151568d5b468cd5ca41174e7252b6508d2b0d8ce098e56b37f9c25e5e05e031bc31da55cb691253d7ad934004665f9f60a673655b17e5a69730217fe5eaee7a2e8775b456169dc8f35199aa0b54b84289871ebd814ee13cf9ec8b747fb9700969862082a5735b6963fc89643cc9d2fdecd839e33dfdb901d98adc853a49c2812a36a84abac1c615c6ea59d51d05e1ebc099f4b65fc061cff9bf6e94791debda9b0996f9680a1c82f31d701a148f5481bba30e660de8118e7f62c35ee29a62f58995208f2ac6b7855ff4c73511bb991ee3e47313330d3d9bf88d67e28f176502928ad9712c63b26c3ec4c15f725d669ee46699a1029da1ff1701ae88dfe557833640eda5af77255816e01265725bfa9e2959fe2ae606265b517e6638d41e8e8e92987e9367f179005aeea5ea037fdaca0e78a4f06dc7caa1e3d84d39ffe1a2edb150963da0d08a7a57308bff56b97f468d9561d4e67e81dd6970b67a8d30bff65684f8bde4870ccdf1967b4063e4484cbcf128375033aad6ded1e022d06cc9a4cb207b7a91fcfaa5112205637226e90aeef2b01c61f06e1001108fee063b96b52854a467b2450739d604b7005d9e9669574e50a7a9705b6efda3969010c0d007f6a786519d0517fd7371c24c08906bce4d7fd47ae1fcc78d4435283e94d28ee236a431053ff08973fbc5daa934a12eb317c88126b3f699c0df27e50e264dc8bb5af84696f49e82cea875f3583b510b30991ac14861acefa22e01b0024425cd96baf938148ccdeebede645c03725eacc961a8357261c5157a1727dcb26fc3d94207bb751183bd9135ca9076b264176394050d761a1c354be2ec68c0d2f88e0dc5383f5cb7afe12042822da0fd72d34ab924be00ad472ca803557d77059d926159a6b7bedb4e7e01d0d48fe2cf728950f3c17f96c4d185b57e16b63cd86ff4b4709b6bd5338ffab8cb5caaa0594489bae523f9e10019ce96efbec0e26a0cbda9c6644b386f7311c33f571a1962722311e9e9232f6eda39a1d4247c45cfe288daaf6ce641aa502126372a73772272417ba58833082481eb03b1d1d2d5501e75c7413ff73550ac873b3a5803846872dfe7407f701db4d459b0a0cc9b641ea15864c41369e07d734bb7a336d5732734b14a664f4a0d1c9390f1bf75ac0842efe46df6760775e18dbf65c772e1aeba03c8d31dc9440b5aa825d05a3fb3b414ac2e0f41e46f0d9e91b098066c9e78bf726a5c7207dba6f6b6964f66b4cd37db9592c06df3ebf7aedcb5e5c6fb93d6b44ba2d3a3c0fe4ea1e3fc3e77dd089716abafe99846b63093332f6ff30c283b8972ff438aa4c0876332639f6091d50dac8301f79b7b017198bcec7288334c8d063f97e887f64edb5c7a5752c8ca51bcd8172314a8ce4c40d197722d9b7e5b36cc036c50b86464a99459e41126213185bccf0c15bfa721ca83184bbb7a78bbb7f41970298c3586857b97d6cc1b9180dd0e8bb57a9a0bc63c0a4adc3db5c37a96eb723423dad2997c275a4a333afddb4a01de2c92dc11ec49608b0615685ab7ed317277d1b633c070868ff20cb567dce417c7fdb49fcc6c28cf36166ff4d6329a84722201e9b0c5829dc558e503ad62fa7352a26ba6e52bf435b07082c5b919cb6b01106d9fb0080d42ab5ff4095be1ebe7a81973b308541b318177e74a32367a9b7286af083ab92fe5e53df1038be5df793bfc7e0d6cc1a6b70e5c633ec4989aa60228518c963c82f1a1fe46558c74f292555f704e52767b2c5da023acd6a5d8095de1fc393182f6b846c90756a73cb84418343c1beed4f16f401b8f5c7f97a1e47253a4b94106aa3bdec60f22c9cf84c63dcf13882075fae34fb82bb28f784819728e4bb26411c782edb792f4889fa3c1da1685449121ddf03a275aa0d95d372e72b589d77087c98f4f5b4c529ae487dea2ee8ecf727cbc55f845afa17974c3cb640ad066be4d766dd796106b646b6b617e9c7f573219e166fab9ab705a53fa6772e051ac1e69be0802aa2b7db2e41e352fdfd1c57e40f96c284a11d630a5ecd372fdbc88867072152b15cc844eb484738548c41af6c9f50614c89b7aba4e4b4c7222c4ee7ae012d930dab578366d1ddda2a981151fb096bb11f49158b365e7ad729291abf3cc1180ef8b1486f1f02bf97665e0bf738aa36f07498ead1b37ac9b081c3d694bbc3881ce50e129a26d5056fb0a49d8ca114e29d51c57ebef03c42a720c2d1000581256d40b60ac1647816e772190722df636ae8b35cefbd669776172802f4aee8add33c202baa7b15321b903ae8ff5687e6fc4c0cbb19718c985aa154bb72a77c774651090e00b88c792fd837806784b0d5763ea9dcd5e6f3760d1269fc31aa2781d98b165b1dcf43094ac278824a6dc3a1370b258d8612baf7e832e9c43c66cf9303a86fec416b748c09d6cdebf7c40221f542a15ae522dd5d6542274e126eb68df344bbd6d340f4a6c625a847223a3b2ecf30f91d769cb5e0e7a7256d051b7590b6d61248a984e9c4b85ca9ad37a4ee73125bf794b29a927a00d72f3988cfefc424788165af7e74607a3c7859e631524a191f8786a0ad1ddeadd4cdb58ad9c2e42c9c9fbc03f5b66ac74b2e9806124b240998dcf745ed2ff9bb67279df973bb9daacd09b372965ac4af543c417b838c59f355df451bdba81d60572ebff64077a9e9b2af6c938a9aca0cf7a950f6306dc6afac36114f99763f5da52b140222f20d611dd39b901c5c70c9cda5efe5f42b115dcd185741d4d21add5728a9aea37ad8200bb82b8226b1256eb208577c0b5c78ba6382a34279dd1867572b89c779edc6c1e77f3edc6dcefc1939b808fe17eff14e2d35ba2d24bdf149502c92fb2ac7f1e3cf5b51f0a861c9f44e5cfb5746eea32fac84f63b5998dcac8625440fddf3f2fa5d8a273b7b6c95f5254f71951f27d14482a69f07db10ba40009c66b53c7e9e688d9e106de12409bee5970971c92ea9921bf14a79bf001602e000d7a2a9948849ef40cc03eee51d58347cb2ac8e2f8065c28260aa16a4523ab72358ae0b571d13f3a51d1c5a66977fbae6aa07c890004e271fe9545c928352e72e19fa0e041e9355228a24c8ff9ba21594d88baeba6bf959a40b89530949ddf727074d1862472415c13676bf1108e7803c563facdbd77edb39502a632ab6ab54508322eb7577425e6dd620b37d752aca47632b4a415f4dafa360503f1b86ece727f13cfbf15ca4659f307ac4ca3bbe172830b71d78b41b70ef4f1e25a2e10c672c19b18ca9db8c3f34fee754cfdc10462fc693deb0449ac71dfbecba8bc2e4f72b21d959f48328662da685e057bd76721cf134dd605dbada016f36dfc5afc270c9f853ec5ec839ac4806fb8940cf02afcd4d2da051069a4d056f85868f899f272d7ae5cbeb1f2e4f787db2296e3ff3352e69f7c606142d62b4e0285d7b62e7d7221856e35754eef97e3c6a0dcb0eaa458e1e84c224b9116a73d86679c1dd7f672706cb8d546260ab22ba965d342bbb7b5dc8dd191a5ad7b6dc09a5ba57eb9e062b884d8f520c123613fad9668193c3695146449ea0dadee1cd09be364630c2c7246032dbe0913053efd597677e90dad38de296d854abc8426298b124083fd221fb54d8ce9444162b688d9dec6c17e35cc3ade4a060763bd1b49fbd59a57fc7d3edf146113f044bfff7ca11f10bb59f1338ee3fbbc643b2eef7af9e3f31eee18423d8aa47410c1bf5aa5fcaf657d0563b0768ec922e6a04687ca910144ceb9d52eda8630dd60992add4f30dd1b4ce0b241250a418b09202e5b14ccada51523d37259c8b26270b6bdcee160f0d30e048b6a963a95b5b05e1ee503fc1cfc1f95f07240175c1177f73c594e7c996e8302e265d8dc5de317f8aaefd6414ab33733277270744320e95a97e589112c03187d3d5e408ee0170cd1f1917fb23710174efd7257914a0683e3aea00b49f89cd268fc9b77fe1bc08b4a3dda9b13a62e6d5592727fb2d1bdd0b1794ebf7f450a62e72dbcd7ca2adf1fb236683b7e6ad87797ba72f65d945eb952f73e48dc1991ee4078fb83eaeb9cf573a5088cc57bf94a1fdc131a8f5b7f638f5ec05b4e3821992aafa3003487b37ee79c2797bb0d856823c730fcc4179379e903e8c48b5700b36ff4ac23bef1ee90aab335d69336b7fa392649f4ae13ab050d77ad11fbc619884031253b376971fffcc8c52b5b409ad7d01c2c7547a88e45d8dfc454459cb49d0e8c79dd6eaa87d00a4f0db3de21d24cc3904ffc62a957262ae21628b509d7e840e692b0d06017ba1a37618445757dbc408415b0adabf53d5dff67ebb8453b432dbe3843215e7d403849f881446823dc8a63726559e8072aae0cc9bfca55f51827e967664f14305561db6746ac0073c31ebb72eee0c488fb2c4ccec4c43ac7945cc4792f4059c13e188be5c8262475b8f5d030b1d5f86dd46004a411f0462df03ccfeb397eb725ffb3663fc607dd2d0184e1723f34fdf85cc732fab1080ddfe9fb8d2e31281ad4a8b600f815a1ea4d0d823e72e8e6a1490a22c9de5b202b48fe75856b975727b1f7df87e2b7c435755c8a9072ef227df5f19ff8af9fc49d32a0be78e65735450efdedb5e6168a60336792612b3e565a48c9375138704f5bd78274b6804f9d9c6345431c5626ab70b9418e4072ed0c5ddf947715d2da1cec3d41a371fb3d35460817ab580adfc9353167eabf4dfe209b191903636f427a8c9868b0674181270deb26aa5f4c7a5637c10a5bbb7203874f9755bb127c00b1c7a885876ce31ad86c457bc60fdf5eac1afbdff47732b6a7805393588c12b9cbaab0fec4f260759ac3d200a348eebcf618e08dcd567222c1db8f64d28e78bf036d45a74f9371aad8e13e483b98722d3fd1c30f616072e2f43b843465d938b1fbf51be4ac4b097e87550ba2527924c3c862062e935f72e75c20ef1092fed31ef34566641cc12973b7bb2d6a8f05b66285f3de93c94372357f3ad53025151de5a9d17b7d4c6cdfa39b453c426741174d7967d777e8b226d09288e6005b474573836682d1d265bd30ef63a753e3b9a2a51621e7ea750a7202fbc5ede3dd53486530837e6499ce4874ea14f79a060fc9cc364b970c1e3172dd98c4151e3fde783c55537ab63d7a7503e580402c7435bcbea922f78188840e003db7b5aeaea8ab21a8b19e02c7dd8e2e70827bb298ac0b47cd2fa92a2f9b575ef466d2af470640beaf1b4bf1e038cb83da1d96010813e226610bd1f4a89437fc48431772f2e557180810f3e4658b3fa45a83efd5224c4f51034eaa967e117215a91e3350f9497b10c5eac925940129c4d81f3506481f1b3f567eba38f9c17292e2c73680a7f379fb9668233d5cfc1c210ba11baf09d147928265972356e94c033039e0640233b4bae77ab13ec473dc4316435e387e69f0e108b8f054cd87118d77d8d00fcfca79430fd3a393512f2c2123ccf02b8a373fabc522f16cfdeb72bd10a9cb0215eca118ff1e0434a1c14e101766611b027130bd5cdfa82e1067726070c4081a096dd5063f788f49813aa62f30159d0fd079b760edf5d41a92bb72a51fc81d4702393ff5bf97f983fefa0191cd3daf035af45bef4e955d3dffb372e97f7fb370722d150c783df277ce2761fb1f6a68b6a9bac3cc6b9495063673309e848905966cd05cddbce96d23493f694a147063629c59345c8daa2215109472fc8fd1c0d1464063cf1bd73af34b5156a5234129b065055b2e318f6c287f8a72e607d3697ccae5c50dfc0bd59d1dfa3953acd91076862fd5bb11dc0b853fe672357bfb95030299a6ee1d1b8dd61d3d8057272243d3777258ebd3304654158a725f7e56b45a98a5bc2ba269847dfcef6b7d9ea6b53a54110f775fdd9ff293f30f7f4c5260f29e26f6a20040f0dbffa04dd298793e7ed1b4f923ee00fb65547b7252f7a6d766545bfe4c1e94d483a19a937f8a19ed2fda9b53109368f2530c620055e7cca8439b6abcd705f4aea6e71c7e9c2eebf4ec9bfe6ccaf8fd6c429be272e7bc5e94058d96fdaa168628cf1e6ce07cac2783bc59ee7b60945496213c183b726377f497d10db116e050ac0c3e89974343b8803bf63e49fafc519a5f0e035778df7418574c689c2a39250f0a4bf7c6bef45a7e458f277e59b30afaeb3f8b0a5e3ef7abb92dab550a12cbb81f6ed4088b8ba60766feaf2c36149433f52d65483e24e64e77e49be4ff1077226ddcad6d1cfe640fa50971c95e8f92f30ae5e02055cd535fbd8285e6d7fdea06790f506143643877434475192f499c5722190828d12d67295347af524a3a14c5bdf3045a7e36656ce887f43795c19651a6653272d568d1b65172b0072f0e840875ffb5990f18e50a55de77d4fbcf130177a38b72fff4ce9c88715e0c90a4044680743ac601ac2006ed05bf26898be6c06b0a0938ac627ae238480b8793a8f2661e0c54ae2fabc47205c3ba549d67c0e774d6f46f26c7e2ab649b3c832fddad16c0454dcfb97211df902c0f095ee21eeac347b4720d678d6398c808da5ee66260ff310807c5660c4dbf4ef49cd5426b82d567f572735de0f7798da53fd4bed3f8fbf1f7900281e8b742dfce8eecda3a9104182168a382fba7f09484b8a98631d36a6dd7bfde635e7d0f6ec0de975e717dc125a822eb436d01e41032a6415eeb62faa4801471c9693e5cc46f87c9318af02f205572a41514d055f3e4178a2d29f21ab7e42d44c77c890b922a87087589e84991bd725198b40eae56a5d203bfeb7ec795cb13333440cd07336bd04105901a17d3f1724356a4a74a9abd5a177095c55bc52bad8e52e64bcbe7ca30c2d8f5969e8e6d72f388d6af19056bb11b9dc10f5c5ce2879fcd7ac306f45cfa884d55dc7779c872681310831f3cbceb37c044f46a0791b0a4628bf558f3a8eddccf6da58e031e5a6833eed539881c1734bb57202b8c57ccef6098f5593f029dce129e340b1bfe721fb40e8ce7f42d50b27010ce7abfd2e3c523ac80c847e430b2c8430bc0e8b0722d376b991153e94dbd686ec97fdf9b3b9d54b00f580ec0e075f59d4f7b50187242dcb9dae9a77f758b1efb99d6ddbd4f72358775c7bb818fc78f1411f20539723361096c8298c9d47151ead33ab52ca978d2357b554f1804e72f48f4c4009c26ad188afea8a7d28499624016047dd6d089a093b0bbaa61a368807d5321e97a102dff46a2aa2623da1a9c0278f4b2f18139d19db4e1f2627354f3291c2aecf9028b524b3d2c03d7eafd096f52d624cfbbfbbc11c3bc3e9b4fea2c65e76580aa7283b5ce5935090ea78ea9776ad2c61a054d80e66ee375ac29ee82e7a37226c5364f42413bfdbc84628594127bd4f886fa03174cf5447bb1f4d89cf8ab75446a7230e141ba12f8992d277a0e9a1df35a3ea7968c2c28fb6917d2017c0a18b0eb38fc987e38142ddc59d4c88b392b6532cc0f2acc6930b7300ae709feead36848266948a524d28b3187f974b9df4b85827b0d74da025051d8a86aa3d689e1ce005f32dc3042e61a39242af4ba3bd004ac98c5330c4d676b49fda6f5edbca0e4b21dfb6afe7823e0f228473e3539efde28e9acca893fc15421b6c8d1f9be8da9ec303a02fee43670a6d31a2a7b78e20bf578ac45ead97f5e9272b44a7eb29745a072680eda5f6617d30c0ea81a95c1076458edbaad0fc566eb31490ce821c45a277291dc5073cf08f398cc7403d4d48c7737d3c561cc37a0d27d16b87c10a77a5e723e6f2f3defc582f7b688c603d17ee03f376ace472d6da6865576be7ca8bb921131af0d24cbb2401172931fcb26d2810b29ee7d17ec25a14503a192a3fd34f9721e1def8290ab297c30a8ca55097faa8d02c6da1250c95094ff64c19944f0751fdaeed69411583ff219416ada3142fc666f5fecdb7c27dd6f4c2cce3fd8de6d7258fdbb539cee83a32e4866cf9d8cf926060b027c0b82e1e9a95fd5b37b6b5452856104a1a725286b8d2b0d70558e7460b96533e3d37f0c99c0eab61c7dcbaa72ec536b71db9fc4a982f804022b2516fd570662e273834143731775881173c002608eb3d41423c337c74e625cbef7583aea58d538013c83e1c5fcd0b13417093bb400397dc7aaa1ac6a9d169d8e1b78c11f024a296bddf3396d147bc167ae2c726bb22a8c3a1e8d02e5e54472010f99e3348a01d44ea771340d711a7c90b5b172e5727ae9129002633d05110ac98575b4e6bec94610be86ed0f1ec779ad6e6e0cc164ce58570377a398c615feeed5b458cbe19a55d4e1a074b567242840cb5c65118322f05e24df321503729e8db30f81038d26acbb3b7fa2ce76823812e8cc128874a596e5fb283a1c415e2e1ce0f0abfe4420b75a8dcc6d568728fe32d12a3ce97257c538218aaf6c7d5081dae8eaf7bd514cd7e0c7aad88c32b3ed6516a728e030e86ed29639dd1adab1357d8e1088d51df00354eee0d873084666d9ec173acec544ff59caf8069c2a49afa4232237a28c6f6e7c4d59dd8b8c4e54b62225286131cf62905d9ddb9712e7a4f4c3f4083433e2732ffb737287246308e2e7ce5b12eb9c0f20b349d944a307d9a93b48d788c83c834d2787417e5d7354c0336e5bddd225c93510724c14abee224c518828a2312f1a58cd3fcff2006b1b1aa3e60f73a0bfbc9c92887c8287ecf14316a25400928f064a5799e4e3f6e02f85cf17720955f9171aabf7a6ad1a692c1c96020df946477854ed83469f36e99ad63dec728a915b09189bc8a3c04a2c1bac65f10901420465a9ead781867909788744af72d69150ef68aeb5c9909ac2ec3d8ffa6616254f983f06e26059b496111858aa6ebff03245d80598a80ce6e8327b82f13665e964dd9c66eddbe969ca691b0fd472c628acca28e220027d4658d5c4ba9f24789effbe47a89bb07634d4d7c9ed8342ccdb13da19c348490670ed24581459be8549df373f1ac993b410b4ab129a3b1a0befb482cd4e580a46536f3c7e84b154d35f3d503825d653028c93cb0e841b00cbf7fb3548b3237ff7d7053666cc8ebf7234b61fbfa35b8edf8d5e93cbcfba5d2bf66a20354f818231cac16dbe62cc9620301f8152b459000cd450e1d55c4d4ca38de28639edcc96866f2d49cd88e5efd680ea5d8b5c7a89e023fbe566930b2f69ee76deda7449bc0de90f43e7afbb015894c6c1f149aec1c71ac3316a3e427218299a3a5bd0075fd3c4b1d9b2696761f91de7c1fc9b7100250bf228b4c7ac722c1fab0f81582d6eed55a333953c33d00523d25d935ba65231a2776aad9da1728a287e3a58694d0147c04efbf6620e1a9731379d2ba8284d6a3e0a9383815545b46368bf88aad47c3858c5d233273fa984a9cb500157f99ba5061c2fb7a26f726eade9ee907443c1844a03a018feaad946e27f1994d57d7706778adcfccf36722261bd1dd62deb678465ecdcb766a424c7634dc514c77faf5580573990f69572c6fd289a1f60b314c4c4ebfc3c98855e267e595b2215995787676278bbaf0b72942370c3e4d4f1f008cec59e8dc4220ebe5f5f3b7b94520f95110244e245a072be299f8931ba4d20c0f052dcdd00d278af260bd9f19b3074b2b8c0e3ef280672e50711069328d07c86284453bf98aa321954e07537e241f1668cad63c6db8e2003b4cedc224358942cd795c1755b6fbe1dea8387b11104ca26e71b8ce9e224728194a5601eb820001812ff14dc1b4d01d6161af10ca96aac9b591d6c4b5b4372cc957a02a71de9d15486827ed52805e5f1ae0538c5a317e272adda4532324172cfe10e263efe3d1690fa7efddff97a6f57ab543725df843481d069c97e09b02b06e41f000a8c2b0564c30820acdf1bbf841cd16f0b504ea02868f2d1607609725dae7095e1da4afd195f5fd378ce6a180cdfdce6438e40e092fbe3cb16b58e727dd1905eaf118c6e0ffc8495312b135e15901b41058f867a10f2db34bd210c252f5a0bd4d65f0201488bfd35d270f64aa5e33de3852addb1500fdf4948d0f472f1718062a02ecfaae99a68b3943b647953744cd3546c80f257da606da7a2d44fda90e4f60c5ddd849df5ef481b9ac70b2bca1023d72b15789d71ffedb753ae72fcd3b9e333bf73c519aa36139edd96469f500bdc89308dc2ec1082a723577c5a56e4f132050e402ae3b15e1e19a36a0e0ad8069dd2dc69f480b82d22682eb472d1585f644445fa8ba2bf97ea6b1dd6063c561cd2e9c31ed22a67156de3fc113bab89558efd9c9afb16fc7c2bb644846c4e5234db8722f0d97bd5586f94ef3263de2587bb10df354b6b81d82278ec562ec138ff8d6186680948f92b391d672472f757d8ec3a56a0fbe8c447b1b2069e3e1ba0bffb9c3302e71d7a9fe8646b025763447db898c70535fd25a87520c87372bfb395f401a6c521c90bd7173d6cce063d85230c56fe1c9c08bb0f9d70b4988c5528dde6ded8716d57e211e9a7cbb264b05f4d9081ec0be5e8129ccf9228b9053776f9a8c9460b2f77bbcf8000974672022e67bae2720f2c8d5f16db5da5847f2e56520f1c4c777bae1e62947fbe2372142ca74aace62b2d7a8687e0b22f699cc6b2553610569654dd800a462e295772aaae74c62ee0895e77c8149247d24fb940abbaa845ee4f95f78445583715b501728a2ca69ca1aca388f10f76a8190fe03e4cb7c23a4d637799cb5d44833627721f791f470febd9ef1496f252fd0a0e4338a40c53f55b80e4f81e2a0b4a9878727aada7040342e07bd620ee50cdb154d8de8accc8bb38143628b5720c98804b729c5405228906713429f121e85181aef4930d85380d411c7df32a8ab890386f727c1759e9525cba8d876c7d914d045536b803693b82b704cfc393c23ca22fa8728064781d4102a1075f8ec1cbfa0d1628736da1df4acece436e12bacff4c681728eb2bddfa6fce5e6e77ba93adfeb499234c1cc2a27175c52d0484e71c781fa0ee6c25d568cee511f35ee23e40d609324afcfaebc0b73299276e8aadd10207426d7f3609433fcbf2192ef1743f7b1c10164101be0e7ee2c213c8af9970e5c7c7273c7a61ccb280ce72e1457bc2cd80f1153e8be361c6fd03b6dd93b85ad6e2f3aa5e3ccbbb7208ac91e9b0a485802adc1fd75052b839a4e735842944e80927a7217404cd4f16775d818243cce20e83e84e9dc43efb70240e30b8d96dc02958b728922afaa6f93b42530a7fbff1b7cd3348bbeaf2ce55731be6ee2eb5b0151da72a9ebd93dd5dab06d055e448f93ecfd09e3e435370ca7111a23654c78e9bacc1a19fa8c6f0f98ff1e0b2eeb7a84225f050ac974db01daa96589a50aaf30ce5f72ddf994cc76bd696dde9827beb1040f203991c2d8c136409cc57dffdcaf6a751899eda98c43a9480821e8f83ebb1ae0675564ba913166ae406e0f4fee0b9ddc726dead881addc6cfe76e010a1775dfd080686a5817f0f9171129ece7988a7c9688423a3f2c51f25065fe5930547d5d5b02169b6da1e25a93039c08ab60042f31a94af3b491774b93118e409163d8072db89d492747416cc774aafdc494b288972018719701597cff876e63b588a3b983531b442da8cbbf15ef02dd32a3903bf70cab3c8a9e50cd58bcace3308936d9ac6600edb28275df83809d26d0823ff06724cef2a633b5d27bd17f538a2329c5c4485fd0d405d57638b9307d39fa14b132fc31799a4609535e6f50003907b7b7b87e94ce531ed99644e47fb608ad796f27235f287ad29c9f3ddb57abc0f8800fe5c634f8111da9baa71e28c894f16be971cdbf3e9becf1ad1ff0dbe715c58bff75d706e0fc9b8e6e5a7856670a991ceaf52e6d60a8d4f892d38cd84b441cc25d87fd129965726db0fe7816f6813de2d984ff5d0761c8cc98234bcea0845fda68eb4cc3e15eca2a9ed632eebdc6471642772d41caf2e78ee9741a228df2908df19f367acc2762e0bc5afa7516c37943a2d3d667ba0a34b56e3cf140d6bac8ea221d8c200edc7ac4c22fd700411e299ed85729341fddc5396b6384329ab492bea168e71aa750e1fe839c83330ea7ed8e9ca3788ded0ad1f1404f599eac55673a3272037da2b99bbd65d1a7a9f779c9f6c0933a0b3c8c5a458bab74c8923fe72bab98e92a26568126f236016635c7245c2ec572f6a8d82a6c96f86638dada922373df182fec8a6c9067ff52d7aaf33191e8e722b82d87280db7c124b0de38d7482ff45a1ad950a4c743326e1c63d772725547206770da6cebb5a658288fa26150f63b1940ac5b8ac73284542e3d12dcb22bf7241688e830fa66808cc76f6357bf859922be05cdd7ec2b85fae35d6356a4942674b58b40201d069d08d2adae82ee04b4b18855d72da971b3a7c6ca01b89eb6b7221e9ac4c3fd0265d06e42822c4ee84c3114765af1d5b4a152413a3e56155e5031af1b7717e00ec4c3c76666e3aed77e1636783722a399a35a90269bb7ba3dc7185b271e4c9a618fbc984c4be0654e79ee4f8906235598edafb21a01b5eef3f7202cb7ea76094005c9189c56eab4b287c0c28d40df4c2b9b46691548ab483767251d3ed594513596c8e1eafc962a980e0d8705274fdff0e88f00a82c1f48e170bb66b8b2a93bcc08e438027bd2764062d98473f1e96cc11d0bc26e19398339943bee537635f9182a9db860c67a9a53624621af7b85ee5fe787b9c91aa354ed15aedd9eb0676995e12cce8b450ccaddfabc9eca775510a5a086adb8bab76c7047243ce64459db20696d3ecfafaa890fb7d76eea5750feffcb6663f588fdd3c3e72d1338bad03556463c3a27c480d2b847ffc533664eb872b02e5db434df4628c72a8f16ba868a70e0801fb5b10603a5d30056b931acf84a6f42a95b9caeb9654721afd4341526831fc18bde4a4a76163c8ad3977e628a4182c5587b4123e61ef05fbbd8a9cc791808f4e9bfd8ea4fbc527ed17f993392249fa2016e3ab433f6928bb3b1c16a51897263ff3cd7754f8828776b9dbfd7fe2eab252852f13c46024727df3b93c7b56328d7e14801be2d77c262676c11190fd408dd9cfe99b272440728ea5b71f2e0abba3482916f8993679e15bd258b0b3bceee0c47ceec45653f4248553338d787d0e1490028f90b50be157a452bf8f1107d644a8d9b3a6375397148b0b78e86bfc757cd842b5f0eabb4b9f2ec8ec3f019a28625cb227ed7fadaf726c362335791c92e2e2feeb92d4bb57b90094da202deed4fb801e2b03f76279399b65cd5aae07e2d3e5c41a4ce151531f495803e92be93b2718468d6245591972173341246304727415a862f6f2d7e5b6e830d0769c49028830141b8c6799a972f0152f5201183a065c7fa8c28240efd6133fac1e39fc7b0eec826d8293241872f98fbc4fbeb005f1d84a0de18a3ab3e6ce1bbb50c1d59f6483ada01571418072826b841b37ae2e131a5435856b066f7be1f20d00c44ed52776132c6706bb92729b356238ea75f057e0d4901f5a571836bfb4209cbf5ee853c12a449cea19f3091a54429aa0e06b8f1d8b2bb2f23344b648fadc65b47133aca9310f3b1362cd1cc354bbac266ce1e726b12903e0f7269f96a4f4c0d5b5388ce03cf840c18c5d72baa37b5bf817203456826662f589cf24198c955e16385c8365775a274055ea5c8ef0e7c2be266070cb40e0f3f5ec2a197c89ba5d743ad75231129e05caca3f3571953356992c1a615aab20d874afc21432609f2090b4078e28c3de05c0ad2517325dda49e46da2d1a37c07d659e4a925d9fdbb55e3a60f6132f245de1b4d5e720da65a96b4a3730018e2d928cbc7a9b9ecdfc53fb133d279fa3955ccaeb39160a033597d0f10ba3548ffe7024f0832195e1b0fba2ec165291870a802dd6a62065e000f65e06e8d2307565dff989204bc330c9881b6f4cfb47d999388d432246ecfe5f7fae61d1ec8a80db979f70c0da0d23221c5d43aea3a36fa7e4d91deaf72bc2b02c0dd5919d88f712bf1cda39f94553af50fce943a01f01312226cbc8372014a069fb4339fe1522f23f17c3b3e1b2286af20d948d56d99bb48ab90ec8772f6f73d0b0080203e855e096eeaf8909d549af059d37cbb49ebfbfac0dda4e22719cd395cf89f6fc5434b232721d64865ab2af8cf4f56d6f244cc3c59c82f3a72a234c97cd2d0e109bc6726e31f8011bd1648e13239c51d1c8fbe14d21363917217c828c1556493aa5bddbdec47ab5f84ae04b16441cb4845b8831178cdb0e372825a15da2b8273406fd3a9a2e8c921aaf1ff6423466b06f47e97eb1f39e8dd1ed6d9b342462832e12ffe78a1c4ccdabe5620928994fee62a9de9fcbd8aced7725989af7fe7e61987b52f7feaefe90348e69eaec5adf37cb74127f5da1d0cfe721b901e700abaeb1594e5eb59fe236cf80f3797ac55d2475beb3376d67e53e726af737944e178422394d5597e4b93d31a2b97a14250dc390ea8abea97e8d2485cbd850e204d101a83649cd475df90b2879a231316b6a5366408bcd8bd0b4b3015b53ad5ab5063fcafcdd717075523a1062257fd8198c7d00ab3d74d96da291572773238d45e239d72aff026648c23361cc7fa868ae0c36762711af7459ebfa3722167451a7ac8dc145685bd364bba1cd6917e7b8d88043187abd8617751e77e0bd04ee73ecf6c9fb0f5d1bdc031ba2b208fdb706830a90b09fdae60acd5569072641f7803121c8fe566944bc90990246b8d8e8ddc2ebc7623d4e8a9a78bcdea72cfc929263327945c229f6d1fc808a28c10f681cbf47f1de74f6beb96040cdd226d4e5be56071cf6367fcb5a0d54935c22edebcb85ebd9416df5d406ff591003a9cb09a28e7af776a7c8cfecb9df4bcbfba4c828d8bafe384814c55624febef726f247125b16824c53df144c62bfc542b9521e459e1f53c77a4a22fd66163bf721e1115196205f5259df90706ba1249ff8bbdf763982fba97c4c392272aecf1724a396793b4854a42546da7bdfc7dd63f015fdbbd6433b70ccd43b3c1ae74c104ffcec3a2eb335073907d8f083997de509650ea670404d3e710f14f9d493e6a454d5e324eeb591378c220b9d19c89b84a03291879a7b2ac4d6c690e6213901a0da75bee78934121031f183aedf751dd53a614c81256a96b3a6a0b0d98582f4a72e7e93c17bac96f8f6442dce8be98e49b79cfde34bdf318620492041ef402871efc4fb1d3023c99907bc2f940a0d752effbb9833c72b5cebe4b01a4bbf1157d72f1f62151a8d3d4d10322cc2ad26999cd8f6d1dd69665209f0d16556da6b58d725cdd8bab2ca31e0a943afcdac462e836d20eb949d47adcf2e1f7ae8304763749d8aba3ba6cba9071a1d1afe25e855498dcf58edbe5c75fd2bcc01440538d39196a10d7de66ad232187a480e1254b15d734fe648c94523cb44df899f7778f55232983240dd43f5839ffd08327fa1ba42869bb400a5078bdfef6bee28f5797ee728320a811fef65e266d90f67693070fe7ba58c03be8386c3f055ec3a3088a1f72fe3abdd8163d85c365f4e1a20a49e848f45ff3f9c5435ee6f0df4f92f13503723fe4396345d294892aaf4acbc6ce6726afe218267319faebde17779cbd1b7a6d9449470c0135ce9e466e661431fcde90eeb46264352bcd476a6daa47382ff83f673bbc57a672a93d4fe582b4ce8ac3ee3b72798987eb91d60f12870839944d72e5aa925cfaf91a5748d7df0b0f8d5a4c65f368a9c466c045ef43a5e43ae309727ff452156cad8ca09d54e060a299e60ec3d9102c527bc236fa544b643f61d572c034d4148357e0a1da3b1cb0439b2325457bedc542d0a7bfe3b5863916c0c3721c43bbe8fa89a743df841f6b6457318508c4e906df0437707c6b75aba27e8f7283ce44473d833feb8796de3fa39d12154375aaaca656343bd5eeeb8a77a2bd22d9c84dc64e04434d7be8b202382128230e54ae842627cc21e4d60ff508b6f008498308db424c20541a83992f037b4cdd660c36f25a80d20cfc697ba1417c3f7203017427949644101b26d9daf49d2bd671cf978962b9d749d5f8d9d8057b5d72cd8e64b4fdc92d478d61aad8ee485a8944c03e62f5054d878b4300e4f7412172f0d7e6e1a4e1e934eac2ab9f74e423e54d5d0cc8126aeb1f70f029d18789df0bbc4bc93b58fcd690be48ae9dbcd6ded8f30c2920719d3a4908215c32415163725e694bcbd8a18c777f6e5ab7502d83ad4f5b88f47d27b66caff5c5583300243d6ce6e35cd47197293469d8a0e113501e98d9e9bd00ec5c9c851a57ffb3179572fa3c7e92628595d584eee70edb75ae5b33f8e526eacd77c3e69f53ed1a945f7277c161d7751bb2367f586912694c48f28d023da7f1bd9b0c4e8ffc85f25b8b728f135851b5a66893a5223daa62d758fe63860cfe260efee126a3d7279411204b47ac8486fb35e331abaa1068f5cbbc45b67469ec9b39904276b6ae154f5bfd7247364e1124078a4fde2fb64a207369c9227e1b00bc2ec2982fc1b49d3f6ad17266ea10f69395aef0cbcefe8bfdee2517e797f0547ba6507369138380b31dd4359605ac3f73646b287f5cfa403a7916cf7b819d9e35e7b3815291bcea0852607176caf4e08ffb4a294f9159f1b178c82d39744d7544a3327c1e429e3a36aa3d7296def4f43a20d6f25c823f129d052a2a3ccd1e7ed416dba22892d51338f0dc243ebc40ccf58d3bf1ff2ec6cdc655482053d3623161b22f441f08541dd583e872ad184d245b7c12d4056d05bf09b414439d0988282c048d53df42fda199c56816b453c86afbc58e5122b261ea491f2c68c5bc8b01c652ad4df7c5abc990c3870d1e3c6d748b836f852c1b901aed86d6d8d8938b775e3ec694710ce4b2fa26a672514ef636712340a054e4b7391627963f90fcdf8ee49cd11e1c50d645c70a1672d037dea58c1ac8d8219a4559b26146e91ffa89c99c9105bd0d959dcae632c372d58648f708dbf0a9afb92bf01adee3cd98964ab38810a12087c9daf0349f10723e5fc22d663321595881e43da6c7aeecb0b7185bd65715335a4be86b6ba9bc07e4126b6e4c3ab3da2afb9d8d0cdccbc27da2e846fd59cc2f7f2eff43f88a83720a9540eccf497cf79857f7a76ece81438beb51b1843118d7fce87fa9884dd76ae225f2c596be7cfa1cf4b91db4d3616fd2ab2d931ec268ec81d2674ead5b7c725140136722b851fc1956611d5c4c8b97bdc9d5969760038deba29990be07a83164aba2a498e0a7ecf1d81c50166c688db411406566cfa7616bac2697a1591772aa528c4cdf8a05ba793b86fe7aff4c8569c2ce8dcac4c8aea249f743166638722cc296a002b11ad3811e0e8702befec3584bd87771ed721a0a995a6937cda13d38f328a3b6c2b7982c6b2b59b8ad8c3f4cd6381eec689db2b73f1f73388c7e7207ed28063fa6b52251405d86842c37eb503b139539d950c460388770a266e03a45284c03f71aa2f923e628fa927ca5ad033a687b5b7ee489eaac5cc48da6a7721093d53d6cda05f9754881999bf3ce564493fc985af91664dbe0d852a9160372610862c9822a4c8d88be6807bae099b5c37015a9b167201f56a0c55abaee0a14b1c7b425d364532305485076b5ec9171ff794c45e55b2a820221279fc76d6a3dc11737f910a620bbd06f9756962baac434443630dd66b8a12033fa901d054f21322b24db0948c054892949de0336ef986edd2cd190225f34acd21b69e89c087275d8040f7a2fbf22c3ea8d2f90010b57cf7344e1010c6e22a1b0064c1abd5f72a26785e9e565f185a7a5f319cd653ea4e2778b22389f387c99ce926f78cb3a0054f9d33c297a6f86865d712e59f61379480c7d7aeeab94293f99d13810e96f72f1315ffc632bd8bdc336e1ef457d975fd8ab745e70cfedb9f62a3d1756f8084b0d826827cec579049088eb473906868bedf92e56440bf9f7791ac6ed6b58a872b7679b23ca0609e9ac22a68a1a433b6718fc9018624b16fe176c97edd304ac72e1995ce4a4a45a143d31942df3c0b834f175a1fd9a69baf5da9c84a510b58c72c0133e75340dc807b733b081915facb23a5037dde209836f9927e6e83156ca72e0021ebd125e9b9bd993994ddcd7adc2f1fe2f51c6a54f011c314cdf559d7b393b395e08807d8c42eeb98b62644c75e4cb95717afe979e39cf69f210dcb7f2286184473376d79e2be8bb6ecf61367df56a2a3e77cd30280f24a766a397a3f172bd7f83f1080a9932f2b673544d7c8c78ba3c11e328146612b4ec5035ae77fe47e58d34daafadbfd44509f48e81435187d8a7052bb345e0e2691d5e3abfe23d72264b178ec970ef07642b8d6d92d831571cc93911d1d9831a65e618b648517572b37da3f5df1108b3038bf40ec8a560336a6ff27395c80bf0b4fe8b19be5dc57296989c2d14f798b4387acc974f6545921a15d2c6efbc4dc0cc264420bb520f72e52ab7766fb64150d6db146d900b7b49aca72de3eead4f62f2b7348f915782727ea30ffbddee5f90a26868e0c3cc730ed49cfd96ab4e98fd7af8845de7df142d4773eacaae39d471c2a00bbb58c387e21ff1a0b6b3ef796b1e41e764d4d7080cb7eef1903849ef8372c61503a46f329f9c3229666134abebc66bdd96706e6972c8a9d5301ad38aa7644468f2b212bf392d0779064384e0c12732ff648aa8081d60da6f4c963c582b923412714cbc140845ebcaa997b286bef36763b9d888e37201b264341c3d0df5ea5303bfeb3543df4297c194f4585eca5d15bb987e806547c07de7d7b1d0fa75ea2a1e46679c9e71c4322a924a94f2c88b5492db529fcb72792cd10944d60b2679db1dbb70d617fa31579c4f8ab3690b8dd56114629de872865977b26653fbfbe3eb7abc2e17005ef20832ead53299c3fe6778ce0805524cf04fe4d2e52aef729a84a3db39f6706d36cd508fcb591e5e227d1a9aacaa7f729d16a46b06753a147bcab6f0dfe627501c46675c6c305f6bd68f5b325f3fa0723dc1ad3bf057463fe5e8c5a0dc2b8976f33d1b5ac4b3ad2fa13d9abc2959b97236973ef41d1b200ff623a3bcc328672d6f4ca9d89439a3ca7157cb26e111674146eacb3f0bf64a04fb9aa3247e4df5a7d336358c9aba8e147764f9bc30ebd9724b21403bb62b238e297e62abfbb3d8f5987414c985d34190dd63715161f2f272fd6a7785f997f3c86a21a6875c52811cc78bb438bde48b9be5a291663ef8a01b14e72b6c9640428721bc523d2ec896d407d797def279ffb20ab6558809c45a72a8c9f01e4f94ba454bac50705584dcc251224b618405aab11ecec6eadd2d8072b1ef8c1bda94ec1a2a9c15df7cde1161177682a0756993c785f453a113767a726c69d79996495f52e4f81c32712838e68d7cfa51c29cee74d76188882fdfba729d55e041b3366c23e4d7d182b536b8db6bc646fbaf7d2504adb112c2ccebf51ef979f2757d940b0684f5f0c6bd3a70bcf7a6c0a08d5bc245ce6830ad40e91761a4da32e515bf1bbf411ed99b1bd916ac79958ca13ec3fb2880f091479e421d3edcf4414e6ac6179cc7ea10fd5d17d6eba03a7e194619fa8bd954e947fcfd53243132bbde4b0124a7dbf407945b454903e3663a28f78993fa86a87eb5aba3e56e3973fb6e3ebe09fa1b92883bc1e806f9ac493530e3ac858a9715d0066101894a734f2a093197087fcb1b0958fcabc52621a544e3e9ff5eb467b529b301536b72a246f92b408b3931ee347b4571359b6cf458c1bca9b6adb6b7d1c3456a97a21080b79ca58834b98a9a642d7aa33eb2ba958925d1b8221d6cf5f0fc6802a27e1504566927d08959a24c14d01cf129626bf1490524a21abf51305e82c32142e1721f81297a639e8b0f0c9c4f21d6372bed8883aafb015e8a1872eeb1e7bc1d776012140187c011987472e5bebe8ca1dfc66b488e3880b87a3f950cc0333af81572ba1f481b84b369ca53949e4cdd8766659ece9919975c6b7bee4527fac4e307721f9e635f7232102c079a70312a93e5d8033db66b81b5aa320289119a865e4456c80123b355716b32660f144e28838e753781ee73a7adc9137134842c7cc3c772ca5f8d217947afe5f0320929c88352689549768400982af57dd38d28089af572472e5dcbc32064a8443ccb0a8d196b1c907de543eec404fe673b1e566a36f6729ebd7e00a4ef4eabf623eeab8864f83203ad576a1f36ed3d71bba753743bab7284392cfe0d1eee972ef0e2940f9b85a870026eac22f134ebf2ef6a24d352b772b02d1773370e2279110c6ccca0f0798380c299a3271111b5c2578d6156480a7206527c2898345bd70f93a4d12b45adca747928f66aaae2c187772f0a0539ac1ea72858cda949160cf05fa03cc67ae5bb396ad93685c8d9cbaa1b2b67aaa9426e0d4e265a135cccc6c10e09776fa9fa4d5aff2dcb50139d74de96a85d91da2e1b3ea9414f0d573a02298ed7e9fb07e3b08a0fcae95c18ea100252b27f794ead72f14325c79dae2b2b3c1b396be54d587207a4d38221ef07a9cfa8de932257161ab734e2ae42d9ee3fca6dc7335e1c83b12273ade0b696c99f410f3b401d3fb072de6e26954380c9d0264a1389864fc7990a8a4f9ae33d03646518b3dd8effb317ca03caa18de289340edc539a94e7cfd53b51aac84b5f8f772c588df8f6d975722b5c9b07b75b48ef69dd65516e91c4a5f7c2524ffd0bd64fa0046a6f2899db72ba6e62e8c44ca6fe9c7463bbb8a24f4a0624ca0e476b268d0cf22986e126145097578dec4dee42b865aceec6d8818cdeb1064ba214688299f40e9da7c1752466e14ae3bc89517638eaf8389a68c748f6b21b53c1614d27da5d9a899e2be47f11c1b867f9fb817c4734b305eea133edfc303a17102e2582eba3cfcdefbb4d5b725e98b5b6017d7a0d92f0cca6d6f22d1c5eedb20c72b4caf0fbb9f881cdea7e72b2a47782503e905c1e3991f6c6018fb1f0a16ba8836d1a6fda3367dd1820f372f325016da6b0667a36e6e5a2e41c111c8ef4ff3262cc44539238acb13c29d072ef50280991636165d80cffe0c88cd0354542e0098003ee5223096356f41ec972a779cbba1f91ad161340133a1b4060f6e0ab3e5904e4fdbc3ee44a1bd2572a72c8328275cb49bdcb771e1df430382291f59fa2953e6b0bda726f0d831d33e572c2776ee971f12644068ebe432e659d9be643de61d5eb15059f25432e2bf3dd7232110de174378a6c9982204ddee4833dd4a4a9591f866a1e3bcc5cb29c71b24804e503c162163101a9f5fea233334fb968aa74e3bc8f06ad25c8ffcd51c4d16ddbe0b9f69da6c35a2d86655adc9e203690438ae6c6b0ed2815433b7a69d7554b6aada6376df6c081f10951180fafd298989b167e1202dc06333a158dae3eff728e7313f08391ac7f7aa603867aded793ac454a8d14e72081d52be3a19e518c0ace9eefffded3c73d21461f53cc3d5977cd3b1063f750f27aad8f34dacbab6e72aed8e56221b821f39e3a727465fe6ddf36cb60e3d3db22e0ce6ce5d830d1234535c44cf88d82523e8c540500f2b123e57553793224dc06b79e1547a15a1b5050ebf4321628cfc5d2227dcf19d21c65a99be84382823b2e42114451e294da607252dd63eda7c1cfd8011113dadc69e6ed3c18175133e5560a08c0fe95098f492d16eab397c56b43f6338a1f5f7c664ac082b36dc572975e707f107d28bb085672bba41f066b32f96fc31687dea3d994ad75b23befc8a5991790abbab01f42fe72897b04eca8f6360388ecc3b23fd71092e6cb033147d8cf67ea0a33f31b1a1972905211a59330e6da1cb418b756150cdefeeb72f60d5107176936af4cde0a8b5d163137af20b20a87b89da32d15625496c81f2de61775f9bf298cddba1b2bfb620d2cea013eba9a166487505c82faa1338f991220f459acad35a7e62635e1a3720eebbc1f53df75c77a0125b22ea72205f4bcd02ba527917f802de5c3afdc29061e31311b112ad1c516b976aabc1e357f3464dc26a07e9a478b37a547dfdafe7228622c97654fbb995feeebece3f93e35a721269dbc1e3453afd7392c5491d5701ed115274e8ecadbe70f966d3b21b165567e0bf37151c3c4287604d1dfa8206f2ad1adf65433e6ab8057dbb20c254a4012de495ce0ce6ec5221518909ff3167286437bdc14b1c8fff62309b0f010dcda3c2aea26a89ecd00b6c552ed3b8fff72947c56c9e0f4a13dc70998f4a44c5d4390c9456b1aebff5a83cd743076270772063337ff5188553b64edd08332a672f0ddd8eecc4df2b8ad6e3cd043a3cb5217f1cd12a058790bc2d11f833891a5da71000da9ff2b86cb8d9d07435e15e73f40d0461667ace277f487b4c51cbbe5ec95d245f690a4974eed961976f1e1d4e157512342e894854389032df911c995a154748f85301be6c0aab0721a91bba603725ae76dbe6f555fca836a7b719ed2407a3f2d32baff3a6c46a1a14d5534d4c358962d5be62505e6bfebf38882d6e0adc9f93a1702c08bbb16983386070c87d672b9ff9fdf074f7e68c86cd63d95772083ca3df33a8cd5cced63a8705ce993e5728fa88dc97a9f260467434d30f2fc4937430bfcf43b5ae5efef76d7342c5ad91600644080d5c0188b1c00f605536a2240c452c42ba99fb323b5bd8ce2f64bbe640d035eb7dbf8824f8d5e11d16efd0186b17a65dc7bd4dece122837518f60cd72e1eda33504a157658f7f855a07e6e4a8f2209c70a56e0968ada07d3fd50dc53b264822f99a6555d5db36aec3fce7b8e75ff17670fa1d9ccc272c1b4df7925c7249b478ae157c0a4b5a48ae97106f05a773e66c0dd9388cb19f5ad1edaa173769a3c3a8d6b5bb95f5c347ac13a0e9b22d071a913c2eaf38e7255a2d83c04b9d726830595ba4c64cc056667c937716d893b0de35ae7d036fe61868917a276ec102632bef3e9026cf301b637643e27c1ae3385c7d44eda4373357b00512de83a67277ea4dfad54d336d80c97c45ac45626d2df4ebe4207b4b04e59554cff14a2e11c01754105070877874e2b90cc8a3ebafe973d8c7dd9948818a77b53eef8fa331e8b48b497d9ad36778c249f114502879d023f1f8b304a3aad8b50f5d19974b72827d544f900c8eeca124474d6436fb0dc265232daf78f7fb3088cbdf3e6f7c726b0d754fee78c89247866355c27659865783e2097c4e3f88d9b6e0be12bb4772270b9ca1a25adcf220ea4ecebaffb5f0407759a2e6dd5714c0a8dcafaa095d72a984fa956ea42ded987c339a54cf997abbae8bcf72077a1994ff7f04a5bfdf4e7ff2832878ecb4f608b1d8a7a42ac94c33b2085f8d89c532d31af11be0ec906578fc70448c546919d7d78688de279f0d9d5dd071d2048a881a06f5ca2c247772bcf852b1c20a1d3b28b6cdfed27e0fbf5899420731fb6475a0acbb3bc7627218b020d64dbd5f81502d174659667764789cf766325c70d45fd896fc5f73a01c72fd76d552c01f41cf1ffed14cbded709bba11aead9f87299542c82edb48f4d872d03a10d9905e6e3e275e5be9917204066604fd58d873e04bec374e8b38da3f2c280a3cde7622415cc9f6926616693c02b3883900cf80b46caf35c076e8c65e31f44d4418ea2a02d744896f2f52c8e953c4b92ac04d1de7d8836be6b7fdbb52720fd240aceb41bc53efd1718b9f19da42ef7ec914ef38b7ca6a35fd3e1e34aa6960f4f2bb230ce92b23a78e0ecb1d93dc899dec8c8a1368b66a66cc10033b507256a2ce6d2481fbd9f8da664e56f24de472371e3cdba55071662f87aa732d7f5baf086f89728799ec3b42172487ce477e8b7b83f7c100ab7144825c461e6cfb7213f0f9382a066cecf63d3cf4fb62683abb3c26877e32403634201eff28271d5e560e916333f7b4d89c6b62c630deef288513c221c991fb2b9f2fb086cde663042e8f7776fa1dc8a66292e57e3200bbfe7bb377c10d5aa4476de0b1c70f6e5b30ff62838c14df574d076697108d7cb895e5cbbdb7ec02d117a68c76db9bd54620576301bc3d824d3f62fdb8974bd75892a3c5955e924a41a2d4ffcfcdcd3ccc632ba575803355ab39db011a292aad3844a4c3c519794d896022a0e7aac953d52f14cd221be473eaa3d24254064c9b20ae0397cf1c35a17f6cb1fd5b5284b88b724d7df9509651ff463a3f8727c0d6a9445b19fb2b271c85c5926b7540333ce524dd3f8623d1db2c12c93fdd9e4c39fce728bbca7917130ac54284873fe2de0e72085801c3a326617c4bc4c6beaeeb5b8f37d226d6b61c3d964c13ce6ac55a79139c2a79fda85540457311ff1dc59e1945cd34a4eae10a650bede4e0fb37d73f446412e81185dca1bc8909c60d588b3a4de94e6b91f07713aabc8e53210ae6e272696a5ab84544449a79b63a10156d63d5a2db757c319a2fdb1b7cccbc6966ad7207810cbfe553d651fa67b0281105156e4d3535bf4b01a4ee6b541921cc884372852a60108728f2f0eb1eeb63e2e026000dd0060d9b1c55e46732434b6f4f097287f7ab273628892bb25d76dfaf8236985f7fdcc5d96e5269736b00a93adfc8720ff8c4ba5383794e58beb92c681b8cc019ac2b1787390efa59e6d739f3264972a64777194db5599f27e81af099a1b251482588612cc9a4766ea40949065cb3727584055e8b81b94481c3ba6a6332d063fc683c9313ffaf3962f530a4ac310b65b1c6f5c8b88fb6fe01521e5390caf89eade2fbc5eefed0ff59b06254a87db91a7e630869a32c3d6de512434509b60f673143505f6325ebad883025b844928a7278ea0022048e4b68800a8164b2a65b6eba19d2822cd34dcb5081aab0090a7a7282c8e23acd76fee9c06a9519772a5656d0e13b0b2bf8024c97f7d06e2df7ea72af8a43c1d492b693b9078fe15096b86cb59090b3f0ad1a1c360514dcc788fe7235d31d1920f6bd32b6147e18c48640a1945a1c00770cf09f7d374cbc16261718d22247cb250a6dc5ece15f88eeb7aadc62f28ed656daeb817144dc030568036de4ebf1a46444629c8b4df22a070a6ce95d0f74432cd36ecd5c5efec4d18a4672cc88895aff3adf3e4b4029dc4ecc27f05591d583aabd5deffcb2b92b8973a972c4949af439b651e06af300d557936d1f0979891ef03d49483bdf9b76e15f7a6871329bb0118768242e896f5b9bddb24ec303bfcaabcf631a2e87cc6ed9523609f32c1b862ac8a45c53c27c1d9f56240d61318b1894019489252e63d45a5c1a7216bc783e6ba7a6ab48a34b60ff25cd3bf612c95787b2e2ea5f7911ba51477d72947c707fdc6237ea01532e23fc7bccef7538d2e81aee83389f06f4c1e4759b5f43791374e5c7bc70bd081e9133fb9028bc4ec88186b00f9e164c32961b0278349251bc8ed231a4b9e3ea51c15bf9d29824eca1e7e59217a29c7ade55723b1572d77fb790ff8e9d34409868323c72f913af5fc1ee554afd1e7558681d684f4e728b5db736e00d2957e1b68ab1d7c2316dfdfd799d90bd74386268d7cab6ac3410cc6ae5db16ee54840ae36babfcb75572c0c798ac4c4139b4745329573586af0a44f7e8b025ceec5c9124566ec83c3e7704be92be018028ba5f3b183c6d802b72918b13afba26f4915af89d78150ff3df67c8263a148b9cb90be395f07bdeb23a8706b00cdbbf7aa73c5eacf38844b3691a1a2752deb23f21a3441151b1d8a372816a7d2fd490d87ccffb63a11786746bcc461da0ba6153ce0b78c0ecb02de61b8320f4b537489af83a3a993df9783f528e4bd04b5a27be9566600f466d0c47729fb83526f77f95394d1b52c036a753833ecf700c80e8b5317d03106f58932e725fda0e7b0d7702441367be7729832f6fd871f11cfaac932418abac92575a9e3b9bd03e1a12df0cb211b837ad2fbd97a0295d9bc009c3718a7e715117c41c070c455fef122ad62f36d7407de08a11a37d92abe7ebd8b1a471f36b000ac72ce572e2b108e911d0d90a91c3e3511fdd103a13565fca6bcdf49879020035bcf2c13ab8a8403422460ddd82eb9f7ebff4cf4ee3a2491fa1fd692648f7e850a3dd007245cb6d84327b2d4e8f32ad3349fd2bfd0ec3c9c1463a4513938b1f8a20685051ed9e8769b487763b20ac6abc763723f02f21f7dcc9c5c602861c132feb0e967245469fc29e540884e061ba02536d87c17228164873e4f571017821a49cf5984ad0fcb9c61e5f35bda16fe48732714f096bf4e5915cd8a282284033fb0ec34a7266d009ba11600e6b5f721227e6622f4d51b6f686b1d3c7f1e3bc20ec9c54a9f90126b0896203626714ae2de1e11ae56758300bdf9750c08dbea0895836f63f0e8f53b02df2fc9956f23227ce23b2cde1b130f3b0b2da4287eded8c1f68c6c114e3288c3a2eab6168a6b92365b9cf3a7511b674c2897f15d30aa600f19ee64c5bc19e4f5ab08c1036c78a54bf0ffc517ce64475de2947b5f67265d861bdba89df1fca339de6d986e5600fc0e0b6a77286bd3cb49cb2b0b89c33c74e75c1bc4d372e7c378aaf90202d18f5f6fd1700eca1eb662807011aefb5d339ea25583c06f8f9745a8babfcb0896533abc380f56695525c3ca313a4de6193f1f42c45530fc739693dfbfe83344283b986257871d0e0cdbd4cba406dd5b0818ba5378ccc022a298b0e4c815a06ca5cf9d477d6d572226ca43d0d0b3c25c47709f96e5466d30beb67bd8e2bdab276f90126b0b0350002052e41f799e4deca89ced2af09cc880f439459fca39195beb2b294c5e15f119d1c531eacab735cffcd9f4e67b08c7c785dcb72d435b6c2e6f181bc5adfe12e8c720690ca83e7842529468c5bc9d36165dd1e3778e723b656dc6cc996a3b0a8899c91dbdbc118ed1d6031b75b7511ee1155446c3c11cc5730c08fe7e25f69dcafe0698c3e170eac128f027d533cd9b0a2fdbd566f044c4965ee44c90238806c289e7511098bc06eba4ecc8ed53c78653c6db1e0df1836d7d2a82de003e15039b085a7118bc07bfa802af03cfef8dc3623fbf3a758986ab3e5bc69cee9ba48e99e2089caf9a6bf9b3fc8ffd5b2437b4cb2b0ac7d7422e1c31679809a1249469e7c7d7c05f526db724b08104548977bbe6fef81c257cfc237bba17b1a931c6492850b"]} -<< (9ee7c86c) {"jsonrpc":"2.0","id":32,"result":"0x2b54397037d3ebbf2b61c20661c4d07dd8fd45c30b09e902a971131b333a3318"} ->> (9ee7c86c) {"jsonrpc":"2.0","id":33,"method":"eth_getTransactionByHash","params":["0x2b54397037d3ebbf2b61c20661c4d07dd8fd45c30b09e902a971131b333a3318"]} -<< (9ee7c86c) {"jsonrpc":"2.0","id":33,"result":{"blockHash":null,"blockNumber":null,"from":"0xcf49fda3be353c69b41ed96333cd24302da4556f","gas":"0x186a0","gasPrice":"0x6fc23ac00","maxPriorityFeePerGas":"0x3b9aca00","maxFeePerGas":"0x6fc23ac00","maxFeePerBlobGas":"0x1","hash":"0x2b54397037d3ebbf2b61c20661c4d07dd8fd45c30b09e902a971131b333a3318","input":"0x","nonce":"0x5","to":"0x0000000000000000000000000000000000000100","transactionIndex":null,"value":"0x0","v":"0x0","r":"0xc5197c7a0591012f96eaf419db70d3a9fbcb7b461e8b3e844ab7b1accfa7142f","s":"0x241efb3a03f433cbcbed1cc111dfce53e88a48563adf2281d4d9789f088ad73a","blobVersionedHashes":["0x018c91526ec9789a35b497c06e3317c1a921b86082300d651ad001a09d1579a8","0x0117e079f225ef61b292ffb0036ef8ea4efaae2c19ada01af7a58e52a0a22574","0x018d313f88d431d908dd44ff99ef33d3a1e86e55f4202643e155caea72c9fc9c","0x0113ad8a86ce2aeab9516290f52635be56d2a3dc23dce048ccae683bdfec0b9f","0x01c150e6fb99dfb0b614a16e2a087a72d47d1dcfe3a3fa070604bf4bee711229","0x015a7c4688fc9d07fc303b466f606f214c147050acfa852ee6d292f33085d51e"],"accessList":[],"chainId":"0x7","publicKey":"0x95a6357daf5d9f91c85bd4e1f8b6226cb18396d772c35620d071660400a543d8b51f13cf95d7191e958a12c6109357a4e1e50eecd92db513dab323a5c1fe7ff6","raw":"0x03f901350705843b9aca008506fc23ac00830186a09400000000000000000000000000000000000001008080c001f8c6a0018c91526ec9789a35b497c06e3317c1a921b86082300d651ad001a09d1579a8a00117e079f225ef61b292ffb0036ef8ea4efaae2c19ada01af7a58e52a0a22574a0018d313f88d431d908dd44ff99ef33d3a1e86e55f4202643e155caea72c9fc9ca00113ad8a86ce2aeab9516290f52635be56d2a3dc23dce048ccae683bdfec0b9fa001c150e6fb99dfb0b614a16e2a087a72d47d1dcfe3a3fa070604bf4bee711229a0015a7c4688fc9d07fc303b466f606f214c147050acfa852ee6d292f33085d51e80a0c5197c7a0591012f96eaf419db70d3a9fbcb7b461e8b3e844ab7b1accfa7142fa0241efb3a03f433cbcbed1cc111dfce53e88a48563adf2281d4d9789f088ad73a","type":"0x3"}} -INFO: Sent blob transaction: 0x2b54397037d3ebbf2b61c20661c4d07dd8fd45c30b09e902a971131b333a3318 ->> (9ee7c86c) {"jsonrpc":"2.0","id":34,"method":"eth_getBlockByNumber","params":["latest",false]} -<< (9ee7c86c) {"jsonrpc":"2.0","id":34,"result":{"number":"0x2","hash":"0x90c8b9e1e8e51584453f5516036bfe0031ebb595e47c5fd664bd56471ce618d6","mixHash":"0xf8caa5bee858bdf1581f3920c0a700cd25923f194fdafa96e47fb2198779c608","parentHash":"0xf9e9afb4b42515283ae292d4f9982cb0c87d2338c717af437fe39175fd5e5fae","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0xd51ab9181e964dcec962295995b1fff437d9504a88ab944324bc1227c0c94bc2","stateRoot":"0x724b6cd36d03f71b4c088b69f1db0a61b3d9789546c24ef32d59f1dc2007041f","receiptsRoot":"0xd50521034c860197d235df5876ea04b9bce05f69b7e89b96e597d9f6d35b1492","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","baseFeePerGas":"0x2da282a8","size":"0x408","gasLimit":"0x2ff7d8","gasUsed":"0x17a25","timestamp":"0x1236","uncles":[],"transactions":["0xd886baa4d7824402a508487d94b8efed257832170ecb5f5a85b8d2e15317728c","0x3cc701f8f4e4c7d32e1a55dacbf4175dd4a61b4b8f26be373b8f7b0bb4c430e5","0x6208c8da6ad2d0b72a65110f972a38861ab4d12a45397dc6026d07e5623f748b"],"withdrawalsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","withdrawals":[],"blobGasUsed":"0x60000","excessBlobGas":"0x0","parentBeaconBlockRoot":"0x062367f0b23e2d49ad5e770d9ad17b83c0c1c625c3f9a290cd9572b3fc6cfc9e"}} ->> (9ee7c86c) {"jsonrpc":"2.0","id":35,"method":"eth_sendRawTransaction","params":["0x03fa0c03a6f901350706843b9aca008506fc23ac00830186a09400000000000000000000000000000000000001008080c001f8c6a001e407ca86c726c8395afa91baa51ccdef6914ea84facd25a969d1ca5adc9300a001582e40a25b35dee362a3e6d636297ad4d5b925b94eb776cd41624e52a59e2ba00194e23ee0277c0491f2d68c15b319c73be701244274fe709219306e055ef44ba001358b76b8f8ebd26e5e1fc77269e789b093bd35c6e66c41bb97834b99243869a001bec47c8062b40930ff772e34c704698aa7d7b522eac621c6cbf3a47da99e66a001935294c3bac382287c160ed2f68a75c1e0a7c14eb07c6551253d48f400657301a0f51d0f2887cf9b6e5fe4c6e29ce3e98b4591816bc75ef6c47d6af8ab3859510da00f686420372fcf8d136d957549e1c2f38139119615be7d052d8998d72eeef6befa0c0018ba020000725f440b865d705e30c4e50635ffb8880ca03b3c54f294deb577b800bbd96de95f492879ee264c6fa00e358dad70c929da367df2b077ff9bde383b198e95823d40086fc262e380a73fac7cdaf9f34c58bc9acad86c02d09eb005f0c9ab697e1072b63422abc926c770b1209cdc72269d707d55a5a2f33226283e92cafb5c73172e69fb932b9abd683a505d0d8885a9ddb4e605d99e60b2660a4310a491950a202c57835a1a36cdb456a15b195675e60d82dac195c90da8ad4564742b4aea4394233bffc26ea2823d4e73826beb449e24d6605be416f423e34e4ef6a7cada440d729be513d372f03fe55dcb5c5c5f4c4c15618fda87279c85f5e37d26e61a2a3839a360dd9438a069072abc74476ebb9a2cef90f8e9f02b2d409c5f74030be5577263503f79306373898b6932acee2fe66e170ae841aae1d9edd4aa2dc9ce004a14d7b26d83afe28f5a962160ce342cfb9c387be02ac06bdc551d189ee8a9699a0f9b79b3453dc8ee59d0204efbef3ad39d1578d19d5d2361bac0ef6e64ec288f720ecd24746c65d62471810cd43d908d56782ebeeaa7797897198aeb7a8c18156fa78ceaa3638b063efb5f0e6e89e57d08c9081a6df387b69c7c9eef87f2e48372683c0d174154e91f44e298ae35eb49091290d958fb44580c97b329e1fbc6452dd754e9a6fd4e9be589c70054439dbbbdcb0b439671d6cc467107125d7889fe0615797e2d2be38cc4d000bf03e76d8eb6847857f4a6b348035df0c1765c053f72a7c12aa912d2217ccbe77d884693c5cbe38e4ef2827f2bc064aa24c34aec1746bc4ae7c53362b7c721451eb910a531e11f38d67be47ea95852507816541a025ddef8ab1fbe47b4121a3f83b4ce03c2ada83f81b765f97b7057be74801b80e37237c7298f79c673eb267b67d5b86887d15d894ff97524054b02a446bc29f2aa7285aafee00cce6d2992435bf6e0d73acaa9ed251065fa767f83f63e7963631714d00eef5c578103c1dd4c503832ef2b93a640c719aa20d0c43c8554238d808660b744837111dd0bb85fc71362161d839872e87240645f28dce99d82334d185d08cec03685ad953b9a194c8185ddd04504e2aae5545b0f26d6afefb8c159e8cb720bb0c2effcc60de94ccc89713e943e31f449791cdce5788a223e968d003d8f5cdcd756a638db9c7af1b52d1a76822cbf0edb8664ce872f873ada8f5f99aeda72fb7fb360d4fccaa306aa9bdc2992407c8b9cb9e437e236c483d6b69523ce9b72ecd5837297aeef9c98579a99c1d874a5a5e2e635457a9d9a427fa88015686c1c8299fe7b8a9c4ea4932d8111c1932aa55e4a8e4bf57ffcf6b2d7bad85769323f827c62ce225200150b3456c691c6ccc660b1ee5634cb377b67d950be9b13db5e832783956828eee9ecc53f7a433bd90e851234ee0b6f730e4ac41db0004e7a046f0276d99aa8e8bd165bcd263922b80c8c6a1a0f09174f47fbf89015945bce198facae21b0998f3c27ca5748d4fb0cf886724fb458b4d30140c16efb26178672a730fc18aeb640762836ff5a794c90961d2e1b091a3f7d10998a2273742c2a727666ccff7dcde67250a9dbd068f115641f70c2d4a5754e001c75770733104772ff31c59e66c6b2da2b8bbf1e535df008f459a5d1681d2cbe3318db91d2ac31720c1d27e1c5d33b48d680f669af6eac1ad0457b6cec68b7c9e90dc72d6b9faf729bf9ecdabd4272b6f9738a66865ab746c6faafb5953b9217fff4b75ee5c75c72a03d532857a66e9195d551b0212780c3153148d272f3dd089c73fd1528350e72ea1d388e2c72540daa8d02cf57c59aa2dfbdf1c5b8ddc8bd5bac24f28a9428727c258cd5ed9975783712c91854c0be4895ed67dd08b7b23ad9e97bdfb5418472b49b8dec126416e6360045e12ea0f09cd0eb94dad9ba7152e3955ae9e698662b2a0aed5105f244d032a58ac664d4d111e9c1a5d7d57a8b4fa705d47dff66f472952536b09c8f0c012e6ad19b12683fa0b9fd65cf3771446acc65b95101dc48720798d1a0b83e84f20b2dfa85ef21c1fd6d4a4eb7ecd205b954db3defec577272483a202ed666b5399a28383daaff0fc2f27f2225ecaff4500600f9980c2bfe7207fe6da07c62558570a80b6bc075664f22f9c8d46debd39a8b0db37b06bb8172852f76723db98f38122b0ff1e2715ec10a08f0711921780c965d1b14f17d2d28544a5316e9fa58d0101e138b0928ebabdedf24379fed3bde2a96e79ed6005f727cd09fd1df8a0ea4c1dc55481a1e26e5ba36b7a811b9d1101c3b6b9e5a30997218fd50ca2ab3cfa5cb88518d0a73b9d461eaa3913953a0dad87adf04ce151f72c99b998f3f8dcb1de0d08b7018527283abe2aabbf45ceb72aefb526670ae0e720eced63e0bd2777f4554692cd976e6f06f907533b00ef5ae1e601408122a984668d3dd5c246304036dd70a92cb28b5199a357fe595a331f438509401d0e9917272b540960c2f3a065293ddc673c46080311bb846425c8883c918b02b336e4f22087c91d3cafc7ec793e0bf81e4f9e8bd6bb39241cac67cc3190ce93925377872066d9eed8c80f537c805d026e14b05f207c11e7f2869302e145cc40f62552a5dbe76cc1bdbb41297697b0591a03e56d45cfbfb3075871dd5185a3e25b167195a1de5ee045eaffd20fafa8a1a55d8f092afcfb701bcc129ae708b046883c6b927afa060a0f1ac2462d37cebe220289b9584c0a12561b4d662763ab1803f13b3183c3ad6868a1f60cd375ae2528f9dcf5dd33d15c1f15f32ae248cc60ae4c24972df2f4860efb52e86e7290dac54ccd84d5c06f3cb6716a15b4fd8330cc72c4f491bfa91ba06bcdb0b64e14d7697ffcc32410fbee67633d08ced2d258c59ed02723e9ece3dc164aa0306c92a5d3be0c9633b2ebcadf66fcc23d73ea3333c880972e5636c62783da2fc25b12c00670d44d7d986c091d5bc28d7ef360dbc0bac9461e8d73b28c2ff9d97903429a89160cc51b89a089b5bea4a12acb6ce3224c292523afc47c57474557fb12cdec264eaf1d50894ab0a4672af7a89161e01205b2672e87c59922be00d0f403b0e542234c1f6a9794fd31432537b363fe67448e97172de20c11078745944c0cfe7084de1c0413b8cd9a25730929675c0ab5a217f8a3cf49a49e0df6029786c72842708147c5c22d26b33010027ab23d724708e07d972f90b30f1891c51e6df084be64bb1a385d3c487c0ba0f49cf6c9549f056a62c497603f65663881f59a7a842a943a3e1ee9063ef230d633ab29c3408054b12ea427796ff83913f694eb4448882f5c756b0d91290bbebcedb85dfd3d5b698856972a2aa234f921451e2b80d887906256b5bd5819085079f8d35337c3ca850c579722bb724ff8c6dc1fbd638d43a85051725f79ce43b672f162ea29bf50bd17b95725fd5a07bd7508ce2a1344609e6f100ecc5b46f4dd3954dafa8739dab7866834a997da64c479cbd57009cf65207df98c75fc593dbb5872461ae2891f72fb88d727041062e8107ed9ca159066a89a80fc018e8b60013252a7fb13690bbbcc386457e306eed0ba1f030f1338be2471ba3cf49bf14279ada7656d792003d9213fc2cfae34c2c1bf818e0dbedb97240442aef6797e396d91e43713a151bcda1c88872d5ab707ec7453f527405bf4a901b3a90678b1842bc4774b10cfdfc616cc674727273728d9888fd368eac3e2656c66c9131581981eb16876f5c9684e4d17a914bcd224e6837e722ca6b08975a4f3c1ee658c35ee262d0931fe3ad7195fe7ad772225e0e402913d538d53a4adb07161fd34c19ce499aadf07f6a223455c1a4e3728d461bf5ffec0c1bf25564d85c3d014756c94e7a9d1943334f003a348ee4e414acbe410c6953c35dcf8e1181ab0d3b58fa144818d7dc636fcf4ba626f43ce672bc5edfd4c00591e0feaf1be13c1fb2822902dd0f474b65eb7e1e5832ebea3a7223dcf37b3cf2d6083660593416d298aa1853c053e317e660d95320a254d9d272980bb322a9b486030db3c5b1e8f8321dac3dddeeba7f5c879ccadd69c63a3472101ba46c54b6391d5f3771466704e869646d4a812a7156c426cd5aa7065351722dd13fc4f61f35bcbe9bcac47285de76ca817fc43fe7df61075aee4bb0199a7223a67d6303077b9af9d6c27cee34d8af0c612423a10aa5337bcc2a8432ae510bac7446d42aceac2658df664b9e8a0ab0f290b2a3110a226d8bcb51716072c77260cec9f176a23aea57b81b8b209edd0199db44d1c4c1ecf86fefecf83ead0f7243316a642b25a7515595f0dbb53a94d1466bdddfe580a51fb93f42a42f94ed6e46f22f2c7d64b893540cbd87bd89cce0588c5aa00e6c7ed301b9b75ac38e7572ddc28dcb83186851c875dab3d1364d057905b174665259540d29dab09879d872a54d05219c380baf962c588a21565ed854b79511512eb2b3c52195321a27a85d9b22ba039edaade727baa2663b1e8bbb8a3f1c610d48e6db99e8b8d57ef00564f4d5286f95b0fb268496b407186a59c439cc1d1c34b3217fd277e79d959a737278801d106d46172c3992cb0c7db0a8004d19c8d43271253e1256b39d1b7ca572fccdc51a2fd9ba53698f1c6ba27a26533d6ef0115252f75e100c1ca73059c2727de87ccf38a5d6a45b0e952a8752c16aeede35995ee3c0706e3349879f04f8723423376acdbadf415fba88b02d53adf03a3e4b9f73e88607155c6211456bcf333ad58e91758d42fd545eb8d004d4760025c9398617add907b4a14f0a4440c172efa7eeb2cc65c736d5acaeea62ca8c93ecd1b23000c051a03f39c026dcd5fe3ee0b986e04a82de9800d21fc4ba6a0f11c4f6155944fd44acc7af145c96b01e72b148841a4394349e60a8e997cd89248ff9f8a91e089918da7eb5704da08bb545744e0d7628140ef7050288970f758d039e4027e23e7728778d3d939785d2f971446ebf0c5d9d705ebaaea1c33f0c6005cb84851fbe15be5e8661e90c8404e86bd75c5f0082163c9912c53a2869c9c4e215be21e7b4723deaf830b31e55d4377237d1d9aca74f4c180bc94131c7586d0d6d9e381afc1e3091af858399f4c9f1725780b896783ecad4c0b28c9d51b471ce264f0c5637eeae15407b0f3af438bb495f552e20b3d08ff1b68cf601d0728d89d40d36ef3f729a0b783f7a7ca7a15172157d13deee4f0b5b2d47752bc234de799a66757877e8b7c9b4fb2c78f7a12f722e03ef7b4cab87e1d38c8a9a250581ebc75cce6fc688544e5310eacb29ee0b72d623e9821b75e8f08e7a6ef6ed0598adc434218b835471eca656504c28cc34722d9cc67fa0a237316cf29ebf4a4f17d7fb86bed47037dabaf5bbb08a687628727428573fbf18ae23d3776a7d5fff5dffcea2133e9408b3310884625e494bae72e8b00d01684598e88f76037d72cc628869805a1f9896b246868c88f997763e7210436b8e4ab2874cc5134c40f569c5f009d4f7340907e42098029b3d2773367260a78f0b3eb2574d95a9699f6e2dc9dd10bee8d80834b6fdba42126f0ee5e9375d01e7d5403a914687b344026afdbc015f1a235bb0e63a425a346786d9db247221864f6285e4b7402b26b0e5d7ca89ba2f0f2fb6f8330a9f03bbf03f51355c72751edb9f223ea22d7975950837763816c8b5a9bb67fe71074884e68cd4b7d14a66d5fedc28a7080318d59f053f037c8a7ef199c8565bde910f234504a4f8d807d30d0760d2d2a5f582e072eebdc4384650bfe69973b63deb2806271ba524632de9f7eb6e6657a9bfd6bb69b12a37ba453c4215f4338aba34f7104c21f457583f6ec03644cef3962ae2b4eb8dc1b2fdc55a6147e2ec61eaf7a0ca65026d1c71729ac1df1183dfd2ed5e28b4b1ec3336448b5704761583820ff4aa711d43ef9572ec08e92bf4bd6249876122debdfa0056547db8296d9952201951204ee39632235c6d2c4215578e4aec6e20ae21cbc96c8777ecf9246a43ab53d931b33f955d729594ab3be4bd8273baf27ca988a6d61fb521012fdd8574e6e0c3e21ca3ee8b72cc6669353ef4f2c93e1cd48ca9450fb78b0d25ae7338fa8ab479681a63230c6747f3a32ea1bdf85c483658e73a90b4ef73a655b606e006ff83144e3f4cf53926679c924efaf5618694d2297209f8c1e5257c90f963b2af8d1ebaf7b7ac42ee12964b8229ed65846dbeb6ce363fbb5421301698cd43540bab61967a21d45ba41fe242eae1da54d941e18956fcaad647e50a1d209f014da731e4d8a0d90ea79472302a4737af3b28f9ec3caa83bf3d1fb9c2b774f080aef4603e0756821aed3b022873308ba1309defbf293349f62f96f6b40b31904de8256c23edf4429060e37204c761c02b2a501def29bbbab74af4608d81f85079203551b9023cfceba7bc38c8c09e314b03aa050a875661a598c599d2c99d7b4f329ee0baca42d641ca425164d2e5cc2aba25ba605516cd2fbd08fa0165c874789ce34b59b3268bb7b1837296933e4c33c885a5c1cb7e2ff75710eb2dec135656cdeb97fe7910c04f161f7264d70288b33d57bc8f7fdb3ad854ad18dbd9ea19bfaf45526956cec857a8d872dd52bdd2c183ab0d04d5e3412f9d5f554fb62bb7429674cc2f4c9f4b3cde84728537f81577251b18229731712100903903debede4b3f20f0e844e3f8bd60b9720234c9fe93bb106f6a163b0a3315d5e28e685f4c23a3b393c0b53ea5507f2345ec338f834efdbf77397d592e852d9c1e24396286fc609dfbbbc9499bd6ad381faef9dc37e340ff8af5b5cc407de1837bf7b2138478b8353620ae06ef1142df722a696cfbe2eb8e45dffaef7dfa487f4a1527a1c732521f76339cb95f360b77145a58348e4d753db49af8ebab29dbc3a32b7a5e0bc9286b00e95b70189023de729817c7cd6f8bbb18541c5df960135780cd1d66ee41431a020388667c800b3372a5543f2de5a17bddac83d2033d01707f13d422c0e617736b908ab72651ad1d72a9b6c7d48b349ba54c76ad533fe595f3695788b2b146afb9519816f1b468e237aa8fa0131d6d24c9ba6b791a61b0021a9531458b8f92550d2d40a031a30ed7705f24b23bd6bb0ac0c754c82b69ad533fd8273b6ef91eddcfa52a2eb3f71cb472eb9975e609017b511085bf60b7bfba92be5ada5e67eb62d9ce1902b8e4d49829f6706a237c9a96ac0237e8938ea67fb5a48e8b788db6f69e55d2524db7db987211c2dd2e935bab6273cf96efe9ef93e409fcc2507bb55ff75bb68a5c0017556cfd28381f4dcd614454b1e5107c7e393f4511b1e2a548b5754002fd9fc2bb8972c3859be42c6774f3eec6c370609600f18ca5c03d14ea7771db6659b21c0d6e72c266204a01c7ad6ea1c7fe126b71a4bede400b465c31ff7789543fe7d9580539def3c97a35b6b058c0130e3d436fe5df21b6e0e6183fc7111ed518bd04b2234885bc51c5f62543dd1184e64415036a5ce20b67951b92bcd387bd0fb411b455721a1a6f92b4878304a0777a1fe33b5e4bab6e9361281ee3d28f082eed910ca972db0bb356e933e831087ddeef4fef67ee158e65078e07df62772e025570b35604aaf349b2eaa2e0bcae4bd4f4d0c735029cb2f4f597868feef337aeac05d63472996af9274fbdb1537df60b463e0dcf81bbc2a882154e957ce6bb2a1c21177a657b42e4a12533802953dec211152297742ed1627a8d2a45b40da0d08e0b4e1c60962f026114450eec1831ded51b07423de5c9736723f90fda451bf6e7cf9b7151b483dfac8008e8c78740d20c169513c2af69fa951f36ad0fb504c69125d99336a90dd4ccb42a300996998bee197a5d0582fd2096fd291b43aaa49dfbb6934c7272210139955417c7af6af6d37bfb16a415d208fcbbf150c747cf2dd52f91f03946b76609e7ffd265ee2133a37451da5607cde89ec023bc225424ef90d7ffd73a48fab4fce19e2114d63847c709337623e19e846528112401fb7a13c762222b3575323c99e7c4dab7200f78cea3267c24798f006478912e041e7475ea3037bf11c876eef8d3c0f37442fe1a61240a40a84cc8ed939e989e3401827acf1fe3401b343bc8262c2c737038d9a7feca75f569b6346f136fe86d0b24748dda1df1ff62e6f52466216c9a4a90ef848fa049f0b0167b2e56f3d34f928d8703a9850af06234c40b01af886c6a984315bde1b1569d439580badef49bdd0ad6364b48694272a06ff8a6f083b98d42d6c00f1dc81af40a7efa4963819ebef50edb8100a6ab4dc9c3b142919c4f374fbf181e3d7bf46cffb565020e9e188b3526a6f6d80b1972d7e1c9c7778210149baa9de05a0344394c85331a15f1b590515ba7942ed103727134e872561eb35112755fce57d6b2400ab900c7e405583ec6f617be35bcdd72e34554682e14af10f19e1dd3ddb2eaf80698188d622b090110e99e91a862e844a0c31cc9fbec637246b36b40d4858c2fa00de567a7f840cdbd34e517a897f772a934dcb0973e8e57382822bf38a3e3478a4c707c3e13ac0f62be2244d6a28c722a896cd03a60d8592d78839ad0c89680bc19703d4ebe79530e1466af4f2a9c2c54d17ed87c1379ea47bf0af2057a6b6995c076eee1f333d296c40fa2665710699136616bd4de866353d3d41b442336c3f0d78d9becc6166b97b454f8812e3f49c53fcb00a35b80d4db500b0e04ca98b81abf7d96a45356f3b966e0b35ddc4472331a34e6b4aeba4e5a1f87bfb21a65599e901ae209d392e63eee670bfeb06372445b843eeb309747e778c14d57fa3e0b82a86b9cb4f59315597ad8583e550b7222b59a650914b7da66c70da38a909572ca18eba4904da171b25f324a610552722d971c620ae96c54ee3c61a1dd1ffc83929ac5d3fd2418918e7061723dce9972ba2ef16437735065704602837b010ef4120b72949dd579927f8c6e0966959772639a38b463324e1331ec36e83c99cbe80222694ac98d2da963b665d9609842723d78351a91b2ad0e988f5436fd31000a2589006109f4736df1c0c071ae4fb04cf2af5d305d90d8ac12ab97c016a940d517608559944bf95f1ae4de418bf5e432e8bfd59001ddf1de3aad559895efcc9c9d43e58c7cf85c1798162255b9d40472c95270726100c72dfd68a422efd637565e4c708662017f9daad0f1c6d3321a726dbb4cd804eb415dc2e9b58c68091f408f30f9e8734bd49cb07eeae5f3de6c7208531044befcdafffcc0832034042863f8e620512e17f139c224ba991941f91a4967236d57a9c6d7f7e57b6af9d9dd665fcda07f1c575bd5cbcab48798420c03ddbd497cc672b14d20b37acf81bde1609c4328a612fc919ef61d89a8783af97230c349e56ab12d199e5e581d97fb834440211d46238d63b0a7bf820708f5c472d40b17a6289a6753295e1414b055b16e290cc6e2601e1d352189f029ec553c723a1022a3f61896508a91022a0ec685800586d887c1604921e3cecd6fbd81827228a84a222441da4104e89a54b9baf7b11847921008a7651d073b1346b9342d18fbfdf44375d43535a6d3abdd880a2d2a3dd9c65ab1c38d3d488438acd0a44b658447292d28c46cf82842d50ba24358119cdf1eeaa24150cba644dd1ae8b07172d21735e206d64859d61da52bcf6b913b5307e8efb60b367a5cd33445baf4290ca3b83a594d89660de7dc5abd2645b9703acc6352f7879e53c7709ad4bab85872c68bb8650bfd6495d7b022f922f4341a0caa1d3b384fc4da3e7065b33baca723e84c2377fd534e4bd61cc33e59868e5db4b22c46eca8fed625f052fdf1025172412758c224c1534acd56fc0ad770590fade262983265506282cc663b97dcce72aafa67254f2847fc429bb3b9fe1a7598c8a6d63293e599e0b60944b510a43627799c2a3f001c22dcd71b7bfe243ae84f3987863a629a1dc77cd4f6a27770004f95784d596e0ee15f1ed17280d5417191390c858235b8a2c6c89c968f9975f93114f906f266b59d0e609b83d402314aefc80b2db59fbb328c9a9c0d34ec4c0121a41cb04714dc0aaac5daba6469183a4b7d8a502c05bf58b22e357fe27ccc2e55d6c92c73c9df2f03e1e4a6d2eeb5ae779baa31fc79456a6c5c35e2ed1ad25c18cf6ac0eb973ef82f106e3c9f4811744a5c85d64e39f74ad09e7b76733f84897285eba144f83056c9ecd93f1320509602a1bb9b5705dd2b8428ccc8fb225bf015132e0489c56f25af334c46d1c5f2541d86baff9be0c41e80063f359e7a2857722b76a0a96f7f87efd3c5c69ee7e8302a71171fbeecd35509db71c5aa9e3bee042d414c7f26285c2f0574e39c2b88ca2e19f33b1d9e50684865fd1f02218372726b813c4cad41944ac43fd35064b98bd76d7a40aa1a741f9e8d8e7cceeb60a961b2e8bfcc6924a12de0169cc6a640c93a07d288e6729b56b67c45a33d8374617230395097046b829b3a9d742c80fb510db106c614df7f0bbfcb0acd153e1b3172b7ce5b800226ed7aa4417e88fea169481544535d4eac118d27ce23f0e359cf301f0e149a4a9294ad354152fa8c1b8a3ecccaa4a1629ae735fdea5b40440d89395c311b988af77cc516c3e285cabc5dfc1a425cd75e22dde458638643737a790260b33c4b91430996ae1042d07fd7ecb42ce9896be6e271d7136b29b9456619725fd763ca0569dbd45c56a9d57fe461c4a9e1524f17ff8cc67f7017ebd8af59724651e352f2f22d478c587ce1de5dfbf873944f4fbba3ba57f8b5200d1a1b2f728b958194cb9c2778a4d34dcbaaa2532daa0b932f335d19d10b23f4a7c9d14c683ba430d8b9a253f149d7296a5699aea785e912c45946317dc0446b64df065a4b2a4aa36375dc259e060c8f3bdfda4d0f5c8a05a1c8f2d38f420f5c4630771604f7a19b9dd02400a518b25ec332566cf04962ba146f30135d61bbaae0e7041d72969f6e0d03933e87b6e1c31034c7c4835d0b6e138da69ee531618e5ee1882d729abe8b3a4d625cdc6376e4560afb3ee2eac6a3b1ad4b0b4373767b6e95f50c7268447a968239fcb5f88d3393f08c0e7d3792222badac8198bdd834f05015227227acd8f916d3e32c71a757fdb5f932c7545836518ce654d0df8655ea701f3872c5374906291a10a81a3ea8d6c4f28ba8fec672ad1a9664e6aed432315e489572bb4cffc619dd9527f3242eccca94471a745f23c8c87adc2711e130c210236a50dd5206b1bf55c9a7e959b861cd6e3f6b88557c574ccca44fae747ac6f2e11e4ef3e1fe94721b54a526dcf7b134cb3cd96a0360b561366f5e0d4ba684262238720822caa036cfabe338581857526f239b9f1cb7da065740a77c1366b4cb6d987280b0677636257e770cf4ae09baf9a6248555606a163d88cd89ceb89a367cc17229bc3e2f4995096c9c27e2448cdab8fb46a8d94c4be9ab621fb1edcadab881374a3a79980e57507e94a0513410a32fbffc6827339772bdfcf21b998b1ff65772fc25f1db03079310a115260d5d48669c8218f6669a9515f272c7ad9007c24b7090d62ccda53b621b8d783e7ba869f5532ec5dff02370496c1cdb0f4aa3fa54720468bca1ab61fad9d9ac60ee8762a3790332b471974f49ed678652808145993f86428a95465bae3dddcf1e51a42775fbb80283e2069b765145b3252dfa3f147210c8aeb5cbc12589d73fa3cfe6d1905f34da8e05a3b42089cc143737cbcf0f726060669160a9b620ab33b0b53cfa0368df6f9b151707ff9255bb97c733ecc9727e9210611664b753016c2510aceb460625d178e74199bb8e5de08652eaab045a17c8fe785a50f57b0e9321700e3980f1e25d20cc4c49b96f1bc3a4a3a359e272b21f07474cb6ffbeb039b70c73e5595e52668b4a71c6a128961034a60497f20fdfa6c6b2a6e2bda725d39d5183d86c189e8b743174ada172ff6f07e283b04534862b88b5d6fa97e7d295e0b8788f7e3d7d18ce9f20463d252a8fe9a9699b84726d8a4f46db945454a670db44b365204fcbad73e646ddd48347e06766a103cd727b901876723afbbd05d43c9bf63489828069f45a39e7a9cf4c8b58787efdcd720e84215f3f2ffd7dc1bd9ef53f1eaecf188cac857e59f8ca3b538a3faea7164da1f5543f7065a5271174cdb9fed9f568918ea1109999b2c51b87dc544dacd22bc68412ff1279d9ea7f3d8480dd5141e304d3c42badb611e11d8fe6d84cea9e721738cf26c7a454efb07f6a5addd841d1d6ee43af45e2f434cbbb261667f70172e946857a69d79a21c4dd3d5e13a33e686ddcd8d99a1b8d05a2027df2dfb4a50f906d2ded80e658cf937b0e92842ee75001638d8dae3cb1a2ff1c1af62f39e672fb20660f584f6e0e0be0e2338dfb57b568193f039d2a7c997b8b2e4ceef0d57237004744a6bfb035837290b6432b675da8f7f4c8f4e10359197f192b43685a727e8925e6397ab2a271ed173502b9007a9c3196e6042f7f436d9fcd7127b67572848b7afe1f2ba5849620e29a59e4d2c27490b9f210b7ea5f0b89786a433c3772c8c90d0dae5c1fc6b9fe79dd7d8d100de9f0eccdd30338c160fd123884723428682c1fab71584491b17c2db267fd2a7345ee67655aabd2f7f69a14a606765772ee2951a0d5b35e3b561a0cc7c6f84461b0d1baa3b6b7634d585393ab5e12604604bf610224846c24612adce0c1b1c8d524c3d8d560da4e076ef7a6d6c0633f72c04bb156759c532f62dbd30231609402e9c68f7218b77196c2c97caf893e6972b5f5312fb1fcf5de036c0743338ed219f8ea080030ef5e836346f11ac7d63d7239af933564e88e3ce30b92498e8bca97e67d8483d7ad36f6c432d536854a697247976ed387a52006b64ab2b9a9fd702e3aa77f56b1bcf4f647dc59cac4bf457203ce855eadbe5bfe09050a779ffde16873d8a4d907e357bbb3a6ee48b0bb697200b3d35bd94c26d664f8648f65ac9e5df9c9062ea73e374e10019385ba434e72b02d94c3c89d0e18b80bfcfeb7b06a3ce368b8e30df648818760abb38a7ab8726e508091f6bd3420b812ec1106345b87877f60eab8d86f3e55c88ce92862e540bc351fb2d83fec8b51aeebf5bf4d61838dffdf2a6b0c2381b74ac478d29c56525c4ec466856bb939a3bb6b334f4b5324aeb23c86b3a5fbbdce1e23d223389c50fb93307df8226b30a88e22e3f8cce83f3a55416bbef7f496ea6c81fe5aa6fa726551a6713f2e5a4d06d7d1a8a8a5b8d585981587c8b49e540f2757bec084017274c63d8f56bd5d399c099dc3194053d7afe5637d9feecd8fc0d15bbcad21230ce8f3c9726971d8272f442e8a2244a247d83c5db20c6ec4da60f5bf1606049c72273fceaa714eb238e557d7154593fec1e84ca1ad8ce40dbe9d04e9662eca70720dbc8ba5c3fe055badf3c4167000f45ed46e0153afdc8cc89357e6d2bcec2e69d8afd42fd02c4f8030a95579afcf22ec6c1a124b4ac207cfdabd98c4861e9372f5ef4b0839aa25ce81f3c188614c907b94d6e2a7dc687d016b9a733d88dd8a72038b046bb5c7f4d37ef494072fc6530c51bed8fc6f152c705a36994ccb69be72e82abfad2e5ab94a4032082a24380d384de19c3d748193b55f07a147ffc10053223508dc9ef9bf69edadf308b8627260ad21fc429c43e1cac0a7836d0176577251a774aee56d6d9cd096aa83c606bbc5ed6efab2ed283fc47688298ba410c972c7c80317610afb2f7cd47b9c366992789bd9b74fee7502244d06158ff2f44c19c6aa42fe11d3353967b8f9f1a99c04e6e1233d4c16cade9d57edf9b12cf96b72dce582cc8cbd310c36342864ac260e794b7294cd301505629c27283a00760b72cda0d591e69cf55271ca1c5106375db24426b3b77d382e06b55719e2c4422f054cb170f01fde17e0f85c8b54881aa5a73fd409fc53091cfe778c1c3726a5f44971c8cdb2efaafc4b9c1dab887bc67cc7289c2b5b13eb94085a2fdaf381597a4ec49c42ba91bb63d51fa462ef5da7ab3a0a3ff64c96b3dd0c21c8ae9c5829423e380cf5e236c0c1435b8805e576fed02fdecae6bd78dde29582a7fa945b19ba7296793a2637801bf64b5f9e2015d846e8aa9618f0850be1df00720b3c054f1372a215764b16167e93faa589bffbc0c98b840ccab5f834653bf560d22897c0c205b0f9496edc7f3b9929c93c859cb1d94f8b3afec9e49d135ce0bacd01df1b765a191cfdaa8cce7c847229574b8340bd5e08c08fa1c6e453040e600f6b8c0244180e7bc3b934a7ffc076fb0ff7847c6c0545e8a297f622f15ec6228c74fd817e1b71a125218d945c57aa6378428192d3bb2b41c22eea024b909aca6aa3a3e4161fcab1f658ecad2b301bde6f458cf8804d66f0012fc13a34e533d66cd75b2b692ff576a48cb68f8c643e87fb5db2562c5bfc2feb89ca51a6be2d0296fb27ad6c60580f05ce145cabcf3d85a4aebbfb20908f5ae09bf3ea5f870c6877e4fefbda7252b53e3a43e4978de8a466dc2316598189be2f6a29c748db3636902b5c43d54ea0696b097745d066181e1fd280100b954e96d89cf84b545b2038b853380484723255d9fbd4080621d632440f2273326eb42e72df2df1223d5b5d3b996c25dc3b9b2555324828e4a0572e7fe859bddc3a15f311851a4f1f5a941046941eb2975999f5b28c26e31e8ee16cd86b62b64e05a5e0a425e194a73a8463203f63647972349c8a4c2f39344ab2d40a1cf4de336b4f16046752d20e1ca96f77dbcc3edd72d541f657a30cdc60dd40513a5346d78c438fa381806dd583dbeaddc604dcc07206283566124a4adc1feba268c68b9c82bdd24bd0de6876a99c4ddc7170bda416bf1f4c197f8e10b0be89969deb765ee659a2ff26740f258437dcbc710aab957258426feb618f55003c2668f5e25afdd85d2998a5acb56333534adf1ed665157208149ac6c8a8142c52494d9faa0fc937101ed79516eeef0cd23d445146b77862e865c0f9cdc8cdaaab435e430c76b90f27fee48f6afdb7f8c6489dcc70cf8672fa9f85e32c9d7557d74b672c6125ae2ac2e6fa0174fcfe149a334078e35c3572897f6edd5b9f9217cc2f8c90360343b6878b04683e7cf9aa0ae44cb6784ede61882ba89a9495c4eb370d1174ea18292046b15f1ba124baa35dffbefa75f7dc7234ea72ff48da95e4ffb389c90cfc97fc16c2fdf0ee9eec5cf3790d3536afe922d724c59cb76f30c2b06653ee2e7e7b44968a029702f784dbbe39ce1b58f664724289d449247a8f4b828234327d4530acc61b9e76d8d40d6eced7337d8e7a7f4d50433fe553c213b49f4d44f3c9a3ae526ce5442966ca03cd50bcdb636c205014acafad449ac3674373906e3caebe45394b91678cd04431c1453420e3e49c766babe397677c7849fcb76de9be218355c4f56a7dfab1eacb4a68253eca09554d3181d71f19fc7ebcb73b86a847659f3b544835c2024b78bfbdbaa4914ca2ab1618fd818ba54dbac85a7399fc189b3021e6f9e2c3194674ac8c4d0c21fb0df5ab466f783caebf518e6b4fdf1bda434f6201590ae7bb048e1be63fe09d5f2c25380f28d5017f901d1ed2e44d627aef2b7d77857c43ef40f82043bea6af063f109047886b5c183534e25c99f92c6448d32653241586ff948183dc4ce09bfa83a2531a93dca3291042e8cc2d8bb15544183b9028f7eec39a6b28149d23bff5a35749723d759dff48de9cec1a1f47f225e8beaff86f3601b7c7d563e7cf932be7e8e92f3e1645f747a96f2c68ae4bd9eda44ade584b76c9b72e442a3786cff82d281572c29348bc2b4c0b89a11ff5c828e47b098bbf0cdbe78fb79ec77eaca5019bbb72a2ca5251a542e051a2594e945827d29c2c79a989ab72ce7c2704cb72957047722f1b34362b9ce6f1b0d356fa250c58dc14ff6cff52b039b5b8fa31cbf3e31b726a4d8a19a1ff8f4df7bf09b3cca3fd2381dae142f36532582ef24819a5ed6f1c6d03e76a23954b1f3d3dd03242e4a36409e5d760c2f2a58feb4f8f196918e22ec87a7459d323a1f34aa72cb8f61e4ed43c1189c629d019079f374b90d0409c726e719bdaf65c22cf2bd9b07b8ebce8b09cd494a3538c08542afd94624f84f9727d0a9bf7c096bd336865ff83f3d981597a4cef4c11800a58398e3693bf9c7372f0b986a5169ac0527584e35b6f2b117a0ec62db1132f97e42e5d8be5a8dfe72e11aff8f4b1b800f625067380a9e88278388a24f9a1e41f1644174b30debe0a10f5400820720be388813282edbb8ff6260d6d86f05a370d25512e8984cdbc5a117df350be6f86f97114bcc9f91b9ef0d1b7d4c269b534ea2cf5253eb4975fc372e50d8eaf85860a277a1c8a22deb7ddc3ece2818aecb747c1da1c1cd79c7e5319e01932f2b4a5a766d25bc55a6bc96ea1cf5c54d8e45fe1f3c760f2fcbeec8372eb8acb70c86c2af1b91be4ebbc3475ad9395e42e2c24f2a596c957fc3ca8c472628d489d88cdf262fd4c481b94ccf47050a390b01e4d02906686f1633fc3fa72e8733f64163454405069adae283e1eca54e9fa624842b29cee0b53f8e271563a1d6aeb77ae8378dd1cfae92d600980e1cc59cefc875994912e557d14c7d468070c93ab90ad75b3424f722eaf8cf926695e57a50ba026c23c27dc1b0fe66e4672d16862e4c5f6035573a63e4607dfc28979affaa3411aab9ff69a36746017cf727f1db1f0a2a6c91447ddc0d954f39a93a5de16a326914e648912f3b889505172c6af6bf8add873ae83442a4774c0133252aaab8b3da82ccd3c171ebae99ae61fdcffb296679ed2185a0da1f91b9e6145d8700dc67c76a446232cb5391c03512844c73613caf7111a4f93bcc26e67a298ee61bd5ce794a3980d4e5f334d860160d2a5d7325cc5eb32c30efc9a7acc232f5f9121efb4f88144055b16b67f0b1472c4178c4a8c9a81b19e8b1a44309872101fb06ff137ba241cf4de1a8b6f30192c6a4d2cf678a4f7b44de3b4c951d6a2f3e2103d26d74cf8490ea6c1e8a451a63aaee8506b5b3adb1f09419345dbb3a234838a581c894239feca75cc6424508e727a339c2d39207a862e2660e46e6ff479a114514c5b4cad5d70d9a5cb7bc5615ab3e67157f0aa7d305c09814424db0466a8c6666bf8e677a66a55bb292d077f724c5d5cd923e9f8570e35669278973ec357af81a5e55ada055a1eb825d814406319fcb64b5f32ef488ced207ba5a9c01b454e97931a3af20b66d3ff65d112b072ebd0c31588a7ce9265ad8ff1d0acfcd72c4e0c9dead9822c9bb309feafca2d720fc84f8e8b9d6c02f33d23bd19a0eaced178140eefc8a44169e8334b331a807205644e8a22bcd5be3d5bf0562152e1342cc8e752ac47fd2322fe17c1d16f2072642c894fbc046cced755614a9a11023cc8bf844f80506ed4c312065d8742725012b46271184c0c93e3545aaf915dced087326642971a684d4071d017dd0bb4727de6ea25240a667f52187aa4937893bb06e7023afd9bd64ec2a4758b742bad4bfa8ebfb3493b8552d1709dd5df5454ae9a180a91990f5a996c226a650aa63e72068cb21d266c4f40a035e1a275dd7203e9ca89a4897b60424a2eda322d57a5721d3c41a2b670bad22d9160ccd76a2ce22641cce570755214150840e1926e2e162c1eabd27c2f27d320cf48b33f26c2d0e19c5a6d70841b03f119c34dd4c10c728a5c9ce98b37f9e1b9c7ae42ab9759df743e301e06f891fe437070957dda2472691a00485d984f8887ed80d76d6fe3c1d71b413718870eb6fd89f72caf68ec72725647feaee52fa905a1fd5adb4fb417f8f3cadb26553a85b35d296e20c1697231f5789381c906167d89406818b90f3179ec486ae83a8b131e5e7a679a6e3d72bf3e0fc61b4c218650c69cdcd4ff225369e5476ea50a08748379eabc5518705604bd48646fe962b965880c2e5e78fa20c6af0cb62f5727bfbdded28e403bbe68f15dc0810445faf0b5d93a3d58190c683fe957bd9590446b63c088d20c0fe8720f774ea0db2e9cc76a68b86a97de04a574883a951b522f22a94124d33990967233bb0cf51ef94f9a935b71519392b21789a8c4bc2df1912cea8730a240b43f72a6a4f17f28f504adf2befcf9c1d7640e1fce606c28cfb7a5e81f651198373a72b7ca751c74bcb8a541264af689c94174862f97273e9947b6bfab935540f97772907efbf2dafbf05573b63fc14a355c1b944ac434fdb84482322f3469aeffec72f2395ae174fdfff9eb5c55acf32a475b692443686cdc5529abcd8c159fe44b729354bff820cd6271361c99a9b05bf625e14338bbe31ed460fb279692f0327d091df82b3148cd525b37ea9c4ac8b24ab19e77cf5c32b046d29fce133f8b6c5072cd001aae8bf9cc45cb8667198d74cec5cc190e567b5ae60e392d8f46b351ac729a3a63405132a5675fb505f2ab4215dc0885ccf050e4096217986699b8a7792644e70ed50f8e4097d2622eade50c0e6a81ad3438b70301f3359d873f169091302d9c90471f3f1255524a99c8a359dc871132573533903c43fddd0f5c6dfed272ed6bc6ff71367b649e9e998eafbf94527e18755f8fc38b54c4de94f7e7dc1372cf99c2d7b723cb10ed87b8f82bfc59aed82232af228acb90099ea2014c260e724c4470458ffe667dea7be13617a00bd8909912dafce88dd876814c0f86820f72caba1a5b7cdef923a5df5424ccfd11e364eb11545ef0295609ef945c7ffd4343f06f285545d8dd63bb1521d6210df864e01d0d7ca874bcd0f8fd9d22b4acff724c558d17966bd98224345d3d1eb8194d0303b787979ba1cc3dbb1138b0b4c36bea6cf5550b59dccda990a29c98432f92376dad2947c2698f18f194192a2e3672d49f9c72ab267ebade542a9bfcb0b4a926887cbd5287de3f240ccc76d4a7b672c8b57da84fa394c50a99f65958f4043b7e038b0d400ff4c363fa858e8c605705cdcc70736c16d747539833e8c82087083b06b70e08ee6b2eef2327e9d598c4135d30941117b20b3b69aab3e26dbc8d0192b6bc55ba90dfeb8acf4380e96f944a41094a11a9d8d1accd0768c0f91bde19699563d98ed4de518dd6e017d14fa95ee8b0017fca281b75e26b1d1f6ce434b6478df5231cca8db535d6a6146abb545c946775d5e357e55ff0a81977318e7bac6973f5fbe518f7b7f593191c2f72b0728e64de7ad65e82db570f889c248dedaecf13b1be5470ca6d0b177d3fa3d57107f1d3e1dc34292e2cc4e932b36f4d5a4317e39aff65b166a754ff62455ea2c5722f1a2380cbd7aed7b96f7d7a456fc712b54f2c9d73b5c1dbb3ab9c5424bbc22544c7c597e132ad1f2c01867a3c46146850df1386cfd6a9790694ddae0c572b72641050a3c65be20d1145354b295188b8c4ccc4e89816332ead09aafae6239a722a107764eac3605ae892a50076aa6396882e1cdb8d4a3e6dda5acefe7615a672ff63a240afafb27da83f0864d3d84151815bceab4fc2931d4b491b5ef3c6ab72414ff8ead8907f62dd45cf236c74c527daeb369f73ba8ef4299363a9b918f672c575466482e493b14aa5fa9d095aa9b4bdd5ca772284d5064a05c765d053d772778300be3179636fa53e66742233ff80b9e74eb70000411a1e8f237e84b56e724c0ea712d768c3e8ef2a6ac533f0c9ead00f22aa91ee39f1d89a27bbbea3ed44f6ba0aa5325cd3a18eedb4b1e671e8e34bf2a3cade43a05d3899ceb22af79872eb348217d8ab6718e0dde20ffe37546135045faa37f2c39a375655e01ca80d3d9cbac22e003e2932c6427a1e603658d5b95f5958216ccc94e7a72beb9da4a43a5f36505b3e3713ca162260d5dcb0cc81d563804f34aa2a62b3597055a349cf2e63c41d481b29dc46bb1bd824a214f8a9666d9f26ad39b4043988b6827780117278072ce11e196a4f3eaf900971c4a5870f3fc90944f44797ca3662096bf72d72cf8647cc86757e599e6a7be45541a7fe5996e1e987a72fa18afab16741819772eec5f28a72d7406fd456e6f299ee69dffca5b766cffa6271c087539cc3f72d7248748cfbb1961cbbfef5a5f36e870504c2849605d02780c10b6301efd1fae872222dfc3a5f810f33562f478ed2402fd37476c7ffbc6da32fdbdb78dd8de3a572047c752791181295bdb8c3b88b15b04f37fb0201d4c93ee5dd05db5328ffd17272a8a49c96a6d16f6ccd373410d83e4df95b6b37c67d924f5353145c0594c57200accb2071cdaecf996b5b3577e1768fa3a62848a6d86b9fcf5c5e41b495ba47eb9e72a5789f2f2869c268d3f4923fa2abeb23ee6c5d3ea298b1eb66ce01c540c0473d6b31849279bc101f00d70a226ac3c2bdcd3ec8dae5a1cc54f3daf1fc729038a21deafdd3f4b99c64382caac839212fda5cd959b3a647869db3b72bfa72133a281a64ffa150a7adb5653b4d02b0c11b5046aa1cb08d54d90aaa564a2307fb64feaf0d3497bdcf4f697be37f85f92302617466448a71791d8981f1eb3167840172bd35e21734b945153e61d5f4342a442356763fd36afdefff5c1464b6720b27dd928e8625f213fd100200e1d37cf0bec1b112953be5cc574eeb93e3c972df6324474b8256ac3ddb3785355f50b4b8bf5ae1f826c59d6e1a8df498c951726a1c2a16b465a865c8efd34afb0673b9769fe58cae7c934963248fd8e34c9b42730ae3ee9db4fde4b42d190bdc031c704bac130e99ac5be7d95e81c19016d956553d7773567ba58aad358abdf4131d465e7f003486feadb908d2e3ee18e912727aaa93bbcf20a952a8474a9948b9da16ef78401488ea011298102eff29753f20af5ab0d1d4f6dbff4bbcb79897e591862c9c4678ae74ddb7b0827761e0544b722a645a14cb6e45c54f08669e17dcb897fe490a876b86f843b7dc3398b3ec0645698165214adf6ca62728416827fb0a7377d4ef6e1d74f463858d23946a123072e9d2558b7e56ea45aecd87f75b4daeb1f9f1cf3b86d08f40242fffc637b61c725dbaf8ed7019951d89705431b87ff9025aa46634db6e6c0aa4c13c841f40623849bfefcff8a604b8268662edb22db9759dec7ea901294dfadc167aa0c5020c0be68501bc1876d794ba542c9f4aa607c77c0b0fd5676857d856a9407b1444ca721bc60ec75ba7a073026b3eb9e1df7a6f3387af1e79978aaac51d1d0f2c8dda72dec36e20f2609a17525afd1cac00475c776da6b33a158a2358091a1c11c07172bb2263c60050e711e2ca7974696ee2adb502f5a8f1737974d4ea673cbf8421720f1d22f6aa10041dbc87291f1fa7a81a8acb16488434da893b1424e424584772df17cc45ffb8625fada23d24e0d560ffc977b588516ac5da8013e2bf3787e855e2deeff2ccce459e06d91d2636632d9254c34de2b21bfcaf70fa22423a802b720af98a6ed4235d7bdb28bd9e2f76f1a7ccd087234b7b7d8ba68075f2e3e2de725690e1fe6d0391e1330fc2c1d2e5d806c8e50ac2629f3b284ffbbc708656a372af82dbec6678b1b7d21ec20ca466fa454c41f9ece87c25c9ee4831860fcdf07066b707862d6b4155ca17f64af5ddbc0d5decb21bacebfad34bccb8af213050516086c1fb097d4e9af5a33f70a64078f034b939dfbca88475342385e49f39e772039206d9c02470bcd7cd3b40db13afdb17f3ba0abab4a604e5f6f689d0cb234103c5064bb7da588cea362e24eef8b3dce39a6b0f2a1bd8f8ff995c98e38fac7234240ce069db4cecb20afe99e432e237b1ed2556969a45007ad56179ca53a66920da59f191276248403e08aa16eece8d8209a1a7c30eefa338417d60b45f602a994bdb2d52b98a4298e50bc59bc7d4b336eabdb78b58bc8f77b33782074fbf72eea4823906a915245ba227ce21050c43de9cd1e5d7052671ce9c656c170ef672c4953ee5c8bd94c5b6d611d2430124768a08682f9e982e1ec9c9c78bef1e345db443387deb0b04afeefb54fd6c29edb5fbb9eefd29cc807a2ba7ee799a29062a340b8fe8222ae4b05f5b259d3a525c6ac237b079836cd091ff18a043bb63c472ba0b54ac017c6c1c2746b01e9a8f9dfb1e059bf6111e6f6cb3938b6af24a28620451d4d1279555183f01430ae5b4cf2a08f68888b9a4fe31c903ab28940c582285516c9c0328442932f33cce5f7059751f994a65fe473bc90b71ea034503fe648e2aced5c6086c148dd32e202e703759608f3c70e117a1292bc1d97d8005395934b1c7e6e66b8696cda702a0c4d333b9ecdeafcc589567b02f38add161d98d72f0b4beebc84c2abf608b4949e302932c26cdce0e6571b247dd2e9a3b34c5ff08e540da47d3fd0f40247376abc6f942b1fa8c2f8caebacfdaa9b870d25a482c1da2b17e866b1c0ecfea6b84dd0b2dddaf852af66a0ad64884d9495eca112e97722db003551b61c47c3a773209ac8458b407cb3584a92abc8fe50e147543c33972a6f8d80f0c4063af6075e6f91d05cbe5e41a13269b49917beefda95f9f1d4272675e29ca9b47a3b8994c28a9dfa3e25fec4fa8442ed771d87946fecf6edcc5727285aee856e661341a4a5b87624df5ea2519acb7e2182dd16872756562aba75f05397a64ad1fb1ccbcef5a630eda35e93e2fc4ab5e730cef95bd86cf851a9072da00c15dd2f47738d2359b825e8f1a1c430b24110645412a6b72189f4f539610431a2d0ec5dc62bdc8dcd3bb4f71511de0383e55240c5d754fae239a98e0997245f7eb033f7cfd0ebd3cf854844826fe6e281a90e219865426d0c4749d12193277becda3cebe9c144ceffd4e3767346e3999bb00d693ff198cca2943a8f912723083e5f5cadca84da54580971f1218550676b1c96cf3f203113167b59f721672a456721b43edd56a72ab19f6df780b3d49faf1ba1b9ca9d6be295215f46d0466ad1aef96688febf42436e3c554d75b146259b26a066860fc1a0f9769bba212723f7686517a8be3337ae2f62e272adb74dc7c4a30fbee23c749c5b79ffebcdf72623e41b9ef3222791634e89feebc55f06c4f9f143b9fb26ccd98314412014a7216966554cc39865bc26f1ac037419f505e2dca216a4bb4f12f27ede3b19919570a11c96a06941e1ceee07e746eb2f71dd8c8085973d6412ea814538de59ec92bcd71572f85b5faf3caefac89227ff8a3d9070527318c0d72855ba59ca627e072502288e8ed4ca940e15293417b314834fc27e14054a73abf5f3c89362cec037229135aba9e1a39a7c2fa14d52144f117768d8c81811d62cd6aaa52e1ad8c225d7f205cdff17d48ef7d0936156ed200f4f5e522597eda373c77261c80a2391072f7f9f2215a46eca16dca439cb4d34faf3766683e7e074b61e7c44ffc05f31972bae706278e2101891c24945d0eb26a8acb7762ce0ccf3e5ccb0aeb3df3ea3b198afc1d3305230391ecb90c582604857c50c747a8c91adfec3df0ff80b9112372d64bdc24d661cc7b1e725155d0ae21ac6a0a8c87bd9bc9cb98284668dbcd3c72cec9d2aead529ff853f17d87029b824c8b22792518c825536b74e94c28b5e9021973426570345ecbc6afaeb2711edef2584c80c3f42164ea9e021f8cb442f42b3a24a51f01381da8ca7f83aef4cbbe14219a63c62c3b5080ad68a860db337c72b5724cb3d600867fade08b257e57528c14366e9c494668a138da899c86c675668b2fc2bd3e6a3d5c4630d10a519fcb9cb68405e1b4b063a58c0af36793663972ef084d41bb8388bfe41688fa5f7eea756e31d956ead50afe83c5b37e479bcc72dce4e029e4c4d9eeaf99f21fdb859d8c24381cedaddbafcb26ede34f7707d021818c7bf81cb778cb263f55290a0218c75261df81ae1d657042c3bc643700d3722284cdf8781c65618834fa5cc541c57888df2933af88e904bf69c5e7de436b72defcb44ee5f24f1105514c01c61b2b8c20b5126a3e77971764164caa33fd116fb492235491bc17da3270a6efbd14aaffa4b715988d7dc88b41632771d1ef5e72831557ea3572cf80975f850c580dedcc449bc1b07c4fda8898f25390fe174743731a3ed8b8dcc311f4e653af80afa7a079191105b10d3935acff4006d9b7ae1ddaae35dd1a10c673574a07773e3b9477ce3d81460c969e9d9bb706cc59967c7288af8117bf484b3eb2c742f2489d9a9d7292d3638d96c4aa565d4c84164cb051054e709926690d9fe1e2fc85dc3effa68adcbab35f09f7a120ce125b7691d7728d1897880eb67f34c0470c199d10da171597b2b7a073d10ab58d3dc47a57051823b620e716cf292914f9083767a2b1f7efdba044f16aa9fe95a06518fc78c347221b814cd35ce9f5899032294faf1523b4721ea348a934e90f1038053b061872032a5da8b507215806807b4e68d8d258365960ce98071290fb18eeae95650a725d3c1f995a952b4de9a7a8e7c39f00509c09ca67501de1075e9d9b1cef73570fcafe9ed32ed05b616cbc3735d627b9312a057858ce38bc1bcbc00e4555170b6d6065535d86cfa1c7dc05b69e33827530607d7d3cd341cd268319f25074f0fb72c1c17d04c37f141c5de965ff6ae296612ce4bcdbb2e9099e430b6c7b006a5e727d2f276939b93fe661eacc507ecbd7f170e6540a92f8430f899ccb4ec57ef4723dd57ec3993b844464eb816823cbf75787632b84145c18bc253d7052b6228e722143a2f1e2da33d554691fc49f26bc7d86b9ad4bd3b87d6a155403f952a8d6724a30732d5f166008ea89c0b4743263bd6ac16b19fc649593eb464744335f5572d0fcdaf85324b33522817facff5d9bccbb0e0857aee206785d5cd342cbaf9572da37b834ef81824cb82a1fc517026b669ebece0fbc1a0e47d774e4de5b16ad727120d0736e1c5e1ec7286f0469bf3f521a2c8b9fb6dbeafd8530389bcce753723e19b22f61af02b69f3c49dc4e4462f1fe3e11ff593ce51a0c9fd90dd0123c079e47ca0d0efb8982b4e79332e9c989ef062b00699da8074c66fc7651085e4863f61b59c391e5090e3ebffd67e778a93576e3db2e00edd710aacb2aed96cf0e6a7cccf0805521cfec2c401e15d7863fc235411b4aec9d58fe706491daeb425c5af45edb832f11128b70f654e2c59d1ca2e772ef8049e912da0dabf1c092db4a727a4bf3b894a1323eec4ed20454da6562d8e8f7ae72dedf41bd4ffeeed96e0e01f6b8c275845b44224f0976fb2a70d0380efdaef09bfa0c6fe27faca6d09c3267ef57492149e7e11ce12e25675633d6e1eb6f6cca8e85be6809d18ae4aa3ef6729a36295be5740285a07e2c2b71e7abccafba84f86604e3238cbabcaad66144720d33f0f1ff3353cbe521c86594ee35a21473283ff115807bf7a284263eaf5e15360ab34ecc03e8bd625ddb50014e7caa37582b69bc2d70afd18252161d47f472bb1c0f8478e1c3f79cafa86f77449f8c4bc936ec999aae2b08832a1a79c47672af06bc79591ee6e50d27b01210988c1bf23eb9426baaf517281812c702045346bbbfe0deaee78a6439857ee08a752b5907ae0b7f7d9bc08a330d613d933ae7645840a6e4399590f189e817e84b9a9bbc3dcd8413e62c7c8ac89cfcc5a0b2b372d1e29fa5367f6149adfeaad1a38cedac6c3286a90fef6349e87b5df4d90103724b9a727b85d07f0053f1648785d08417d954dc9f3fbecbcd0181c27a3f58d2720c4e7c0d017ab9fac0634f0aaf115f17ee5bead39b6630ed2cf0090230ee2072d7cae0dc1f36e42dc23664d46e35ff87746bdb9fc4e29d65677e29f051f06c72bcc86292e5aa8026ab878b82941d4e43921fd75206ecad01ad79bb1dda6aa57290e95a9d9810febb6a2e9846e194f60bd124d2fa05a39cb5506a09ab4755087234fe2d372da3dc02c149658a61fcd680e35dbdb14069deb015be3656016cc7729e81bda48ec5bcd319301e57df9ef673e5d416d534ab49a650789f3db6596a7277a7a3c3cb8efdee6d7967316517886771060fa10b1a76d5ea50c24388299572fa8c85e0c37998c2020ca6424d1b8d2d2ae1fe07b56acf5ae2f959c0f8422f0b9d0c846012fdb2617705f2b41ee700645286c53b83e4993cea958133647af95a7c120a7c6b873138645e46346cecc2ac233daabc1e1c47279e76d622b7411a723af54aaa96900c8ac766590059bd22dc6d996d5ce20e60a4fc633b998d760b157e7d73c2e05a49daffe6a06df57970471a55a22feef3381b31bb6a47bcd9387239141aca1545ccafeea238f240b52f0cd0d8f36cf7bdee70f9fd35590fc25272bd20c6edfc129b72705c9ba475061145a09e07b39f5801df6b13f973e0d03e720e78df95b934088ed0cef2d0726c217e8d1438c30f3bea486453224c100dc848fc14d46e590aa1510713a321712eb716fd27d9c39214d3e80aa73302bed577726dfb89652dadc67e4491189bd2f60f8d6c144648f4ee5e5748a71e16f895797294e10c4e43e1563465b15878d3028a358f8b999e506a8772885596219cd58b7296366f7348ff8125bea54a1ef1f28f15ad30e21c2e6c1f9d54f769a697e4d372d00bee5bab684974a0cc39f607fd0b6f3deea911d2ecf5f6fb168ae0d0142461650e0d27dc7cebd6a490238f0e8aaf99b455036bf77e91de7d8a88811142597299615d833667f691ecf6c0dfd80afd7e880b3a38d4453337d7c6c09ac62cb672a4f00bfa2d99f90319b4f87ea73b87b8e8234da3eb7a816706d845e4af34f272963fd020933ecf33dc477e5e404e24e090afef0e0c5b3cf675dea2cd258b5572b939e0522a07883c1ac720bc5294442852c3540ca8500995ca19a4b8729b3f5405f0a28b04f3a0316e70d7b86ff67d299ed6f17121a93388b2c39dc7b9e0a30d3ba504743646c0702c66c48c4a0ee132d5887cf971f3f6cf7800ca8cee5c087209551376b00b7ea6125a25fdf15927c4066729966960553f5e97c09e5f58a7724f0c796d29d032764668c483d799170cba7be3d80bcd0afc8b6b101c7f919b72450f8b7484e5bfe8c4dd2417b88de221fe9ee04c0bac334568d113d5b9122472a551aa79a980ef3f656ce80bcb74611b970bad0f742f1c9fc8dae2be4ad1d97200e7d14819776e0e6e7ebf2c7901af94d722006d2c4b117305de24140b1f8972b812332049e71534195d91d2428710a687dd004deb941cb5d9a92e1e588e4372afe2a217c827d44dcf9ea9a40fc8cc33cb2b6139fe465dd1915ba3ebb388d641ee227d969e2da4da1970184be74809671d8d53d8c7f69d5a0a588dfc90e2087230b8bb3e5b2ea4461fd62b1900c0be2a8b6021a7d2e25def67704bb421cfd06eb3fc87ef28bce4e92dfba02abcab8e4812e89f2ad86eb16662e44964e43b7262458a316c897636a4a13ebb7e21874bbe4295cbb6221232db4f32287f44c3d40baa3787c67f5033f00c410836b747759183edaf636572bfeb56268911295c877248c6ef00cdd3491717243496ee6ac07989179ef8ddffba618619f7917dda923734260fdf52422a4a833756128a92c2634452c68ececb856618b5ce8d57ff9672797064f81d65407057ef1f9edd3c4efd065da151764a387bcaf04ebeb0d77772ef35dc5b48a84f50199fc88adec474e1a7863a7a2e7d391f186ab330a4aed9722dc7ed15d66966d2a53fb3213c0eaa2aa4f8539c83a7b5978485acff79a1ee72f65de0cb4a9445fcb93c0c1cbc7483ec721c48f2bde00383a259c1f192f114433bbe6dcf17f6cfa638dc175aef50998dab11de67d4996ea7a08e8106fa657411f7b6f1e293c9fa1f75687c0ab5d34de78fada966aa7a9f3463e539eb9aded2726261111586e0a6ad6afc80fe09fba20f4e5015d65e78b82ec22c1b6fba1148722f9277e881281deaba5fc74a5166663b81c3213407ee6c2a226f09cb3d094b64a997d45b166aa389d9a5e8bfd337a22f0057e4d3b6698164b467b79e14974e72a5c932f8faa72a28ed545839842a48352afdab0af9ff034fb10db8853046f872846009c3c3efb969902c7a3fcd2fabfb8c37a56a074116d6524c8e6b1a01ac36ee8f89dc0c02c00d6a08908b853380bc2f6b5e7804c7c9452a9f3c84fcbb405bf7207fbc3521566443461292e6b3e12c87cca294cedbc0361d9cb14187d6b47289eea488bbd83e428f5d9e28fa394df87f89757670db9d1f2cf573a6cf4cdc72166c17108aadbe22a8658dad3e93264f513ea812d1f1b1350574e2975aecd8319366d1622c51d2055cc284189d8404c55e6031b87b21dbbebf7793c5dc815d22dc95f2dc5b09e2237a703e6a89fe016e17495adb603f43a7bbab8e1599985d1a7157d03ec736751c365ed547b08b1bd5621014ca75a664853d0f8f3e08391972d1ca8278a7fe416e95baff20f6ddd1ba812ed9427506d0c3fed6988bae163309d75907c43faad560b353159c0f6418dd432d0c119c70e46240437d781e6e9d72b0fe2840e839199daa7dca5fd8129656d2f9343b4e1937bac61c24183e221126a806266a2c6de1c389e9e4b940a2990e93b331eea4e55a2b1efa09786330f438fdde09cb8210ee07cca69a45876bed220b607f492ee10f7c71208f3cc9af5029530ec25031056372a28a0346b2270dcd17edeaaadaa6112f2225953e45f5781354468047d2c3b79103051c86637f570e8016e0dd7dc7d2cb577725429d010d721a1640a7d807dfcdb4008dc0c7c4ab0fe292689b0562bd9ad8913e112611e772dad8f4a83519d91912d6462961191e31ebf9ae9b343e44d41db450bb7e37d2729545372f0f558c7487bc900fbe223593118a8a51ed53e40a014c784181b2c0443c207c5b62b83bc236f96540dec0a383a5ed3c42c1d9ec01ba45f58a2faa9d723cc51e756834944805f0520681c14d9c94b8c66474c4f010c2f1f30b59533d7293fa40b63a2b77732256083a7da4c28f12fe24bebb80243f739b7e9a0480e03e5ecd05fc19e14271f9087b949292dc660d3161ae5555e576a0f2f40570cfa172d306af932bae4b202e5d5de30381083a0cf3c3b68c83f8a05451528a92891915a57590c36c625add55772060dd4834d96b8881cecf62b1652e410c38cd086a72fba71c356f6be5881c5257d2339972a0ae4808a0bbe862386f2d4b12671bde38f9b680217dc97d9e05d3ee097c3ebe35cd71e1c47d5cb1041e7352449435245db2c6cacad01ce78d51a4a5503c6d2759c8a8f2f013f12635af6aa2c0df50bc21aec2fabdfaefbe9a4ad1299b7ecf27c8ecb1472e6651ad365826d3a80736ff721bb8b0bca09644c969e819dea34bba6936a6175617e0229007d05c51ad5cb81c8182d1c656256e810859c23ef3ee3340bd5fc01255bd09aecb3c6ce06a9d27729d85d34fd691f6bd18ab9f1d5b011e75752e9b5dba4f3487a6546e3e306f125a941c02fc87c75105fd10625b5ac8701ca479ccd1ab512af709f5a8f22a5b6d725d25f11ca7e03cf0c5396f3651de652cd7b6566875245577cd6714f08f39e272a2c79acd5a3ee9c5f665f3048be6bf1f23294e9ba7a77cf4ce6d7cb9e52de572b3daab5d290eee0625464d5dff117baa23285160ec36e30187248b4f7a0cd93de04641e0164073035b8276fc82f86c67356afea8232b284247b02c0a3fab1b72efce48f1d190a212ebeaac3f93e1b350906a932ac3e7c4fcff09e8668d49686fa35d339a34f81a8541a5e65f07fcc60898ed2f5cf072263bb1a586d25504010d17a6130e8c2cdc6e441bee20cd0f56e9c9a88e5724db8128be834cc153e08a5d3c21a9c9b9d64646be3df946baa45cb1e907f23c7523de928ab4661fe733df44f83ec60026caff6f0c5fd090822a557cc2caabfab367685f5f1f9218ba8f0c725c387a0c4f41013b6c9f85f7fba4b86a843b34d9b2030b8ba293348012f92910dca0702f349302ee0e1cd9736e1f452049fe04442dd909582d13a0ef64981a354584100c2f6c3753ca48b2654cafb4c50cceb73ba08c2660fcce25aae5a69b72b11e9b832951bc0665b7b2fee35ab15f26a04020c106f8f3790078e1a657c27286f6fb2cb927fdd50edd08a10224cbf805d5e03dbeb836540bc9ea7381814b7289e826ab9a5b9945b2daf1a872135600ca4012e40bf18179249a9a756e26a772f2d8dc32454ced39d2def59959f2b96169ad93e05e042d534f20286879134672cf8a9605b19c937d8e55aa53a233f4c89f581abb48322685bccd6c1fab03f4727bddd027986b6bc6445aa67696249e288267a701195405bf19aebe3bc47286023ff37210a7cd3e6487b2ca3ddfaca08c12bace64216ad5ef55f4b03c16497d56d173e162e33386cf19fced27face6c449c781398a192f62d2dd02cbf9b0aea72a6cd1362019910f3608e87942a2e47e0ff6bc946f878e6eecc92e2a99e211e729074662e56ae07ca5af1675863cf39d9f6ace4b2908e0e4c421c32a04df1d07008b067c7630e83b0bb2e0fea9c657b58e672babeb67db69572cafe5f7e3f952a27db1230f673af00461cc3048a9920798c05f67f22fb86e511a8315e603cdb728abafe4ee527cf6a67e8ab200e98f4dcc3e1408d729742c04a4c0a9f3d5d4272493abf21c48e0868d2c5fbcbc5feb64e1aad65c0a8ae2a51f231ee5639f61f0bbcbe6048c2d32f45e0c9843581d706c9d67f14ba74b18bf2a43fd2fceb94487285b7c3c0c3b7fe422300fe6dc644b87e831b67e6dd7a83c4b91e5d05c0691e7252b2d92af825c8eb30cb2e2410e9c075fcd767520debed14da91cd12cea4402336489c8891a9b741128b2d176507fa852752dcbdafa1ede713e63a97e863a272627109b9abf2770af23b41963db6a941e3b8e663640163118fe4c0a3433ec53b73c07e8cd2b11e66fce2e62089db69364d4c3d9249626eb5e4195f980b4f5172a5225e81fbca73d1be16a849ae21864f5bde72078adfd4fe197cedc7362ffc72394433d1c920aad519a63c99ce89f9883c20cd4ee85ce1497ac6728f469cd03a22956d2f13fd9943267f5a3e6c7e049d7f57dbb8f53c8a8ff3fb7b92b78f137204d3b0b9226939c0242ac035f4cb44b5ee0f4426d56b9199b3f6805381618b7212ca9055dc80163ceaa58585e164fb786d92c7a38c3f81d6049eb61d3176947227206466c8f9e84261bc2d64ff5772d28290da2da141f2071b7eff09b32ef246615f33810cf1d3c1bd10e1f58d81d3a7f4405a545bdb47852d1cd508273fcb418c4bee56ebd17ce57b71ba5f244a6718975347d58f2bf24041262fb3282fce065657e73e48cea2ca55bfe288b628f2c132af9f1cafbf5e6831a1c63729ba505510763939d1ab0b5c953bf211e72b45ae0cc263eb0cc7ff0910210205443a5b4670fef94b182ef48ccb9a9221ffeec51140aaaacbf4e384594414e8c86a6b5c72899051976d72682bea7b1fcd81d31bcc83640afdc1672895c9bb32915421f202f2904c41c5d4a002d341744f1a2ea388f0eded928cf431ca867a6cd65cce6f725d0e835951cc50f6179ed2187bdd5cd8673f7f065432260332e0d78ca8ad590cacfefecb633677f28247c796097424b22a38ee1fddc9a0a8c3d6a1ba9c0feb724e7f20b48eb024209419f4153ae1ca41acba76aa5bc46e614d8bb28a6b881e72db86766c05ca56414b49a04ee2c2638a6bb906de672592a2b406217db993ca418f0073e01201b92775ed7480f70841868be2d5651161f7194bb48bc864869a72149726f65c9a7b35f10f2036f6aed4ccdb1f9e5eb4d98181f20f378385b832727965ca4337f2ffeb62935c768e60ecb6772a3ad1cd00986746e5b7ce4621df722552a2cc4a3bf85184413d4296682ef748196733e958d64f47ddf37c7ba0713fd5bb1c31522fe7b5fda805469b6ab0e08f92fdddaa9eb56ab07616ced7065e72b9f9b6103a764bef34a88f4d8b4163f6b230650444cf9dfa92b164792a294a2d5d6655f975ffd881804f1a50182b597221d1986d49c582132ea2dce3356d4b7223076c1d06a0ba18bc683d46e71fb486263b9686e0494ee3f38dbab8b9dece7267449d9d8c3b7cc1b279b3fc59cf1a7703b0c03f5162c8505805231e83592e50391c9e2cf1e62545d45431653423cada1effeb5aa55fe30f0cd3e50b20839172934b548abd46e1e402de5cbbebec8d51c9b99b46aaa2d356a96caae6670312726d384d2173fba28d96579d92e490f21a38edde77d05146e8720b3bfb65983f44338e4271c08698812d24299e48f0173d0dcd24f24b3ccaa4feccd7fe18889c7269d05a8b4d139055a25d87571bad13dd56d05560b817e562049af0d3370c1d728c2955f6603ff473bb2eb399bac95993983d9d6ab13ca2d45a0f7209555aa468efc654ecccfc5015c44c890ddf9600784714378525ae4cfc1860e6bae7df5c616611747c43184c152e68814da4765e618d110a1b21438efada324e96d2cf2f7254668c36b492fb1749090bcbf4d96e7fe87e7a098f34d505a84cb34fb0b55316fa3c1a20188e6bef1092cb9b03eaac3c6c5356f8dd2a639d3c7176796697e061e2fc0a9b3b09fb558e61ad6dac467d8eb03c9a9041cdf86bf7b899b8417fe47231559174b8d8cd3d42f643fe6c3233f3803ed061aa68ec5f70d0677576166172ac387e10ecefa9bc3cdf1d3b21f63dd4ed2f369bdc4e990fe04e1331f1e66e5783450eb496760019415ec663c4a8e8cfe8fce195b8c31836cc71bfbcc15b691d49899b463a318e2913749eb8bfdc9c746f9a584cdd4fff51597533fe8768b5729162b45fa16fa0aca5ab0267454551238f418c8173907232a2f3d031331ff67256fd6b26b0c27fa219c717916eca248257cc664ac8e5f4aaf0a9c6fa2068b472afb8a481a31b14ede3abef5591d343fcfb472c35f5d39c3253df57ba0d06fe21170e656e088fa5b57268ce81f34ac3e6b46fe1abadf3b5a6e90343734e1a6f6c829874689da5abc3c3e6eea3bfc0a4fe4f0f9a688fe497831ff581508a20d072d5c55403fe2c65636d0330ac546fbeefc7a37065e42137f5b99ff0576beed0728c6ca68a9d71d21079ffb8d1224d3b52b06442c7f7b49e055a5d983bd7494969c6f0ec74ef79ae99ce021f926537467bce267b56b6af617b1c71020f4aa64570633478086743921aee2180842de65dd85b21b4f23be49d0713db8d5a0c6ffe26ad0e302561512f987380b3e0240eaac15cdf49993451545fb59af493bc459a58f5d12ac0c0bd9f8c35ecb76f42ff02da92e17ab992a8a9b6fb00bc9e40e7b55412c99b97a2dba43ce02b9d5c99f87da6c3e62fe37575c336fa3368acc2ee727256781da3dbf7d02ecf6f5e97e47f85e7bb3585a6e5327ca3fdff3c8d50ca0f72487c982f908a5c05b5cd86a179e546883adb3ef70e2335852da7d82cfbfcdc72425d28809b116cbc1c75f0e5b3a224bfe9a2149cf5b753945a4a8621b01221720e218cecf217e11d085df8d32240abeaaac43cdc2770c09cca26a6cb725b92729fff45a01f003eecf2febc0c81345d7ee78403620e1906e7397fae8151af9325f3da0368e97e3e548e7aa1c9f80cfd953cc455259dcc3f3f3534516e1452f772a9011d36aeed7f294f5d2511e8f540397ae0c36f56286c85eb00d446a7a6ca72664df83de4bf46b23d3c45bed0523db2dafcdbce3b59cca7445403172670c572d2ece209c2762dc31acdc0740f455c9fa9fb6c865ee8180daa3b386c90960a728e54f27bdd3acde95fa2659fd18942c7f96753955532c1c103eb72f9ceb8c6397718b184236707cba956765e6a28baadb2e0da72f57aa33ed033b677c144cb3fd66a4c43810ed10a1a25e804c753de43874ff1c3add97fccafd79ca55c3d57247f6d988fb07418e30738845c19473d7066bf8810ba58c52ef75600719cc1d2728badb1b64d2d3a8eeaef7c9facddf4997a6d30cd8842f0f31543bc826690b772552b5e470f7b1cb59e13c5138afea8787664027e8237989f936560af01b98c72141eea09b0642214c911d804adee5032e7457ce5034ff3cd9e327988b7bb6e722d3733bbb43f9722eae132bdb636fec05553e262d25573525e9f022debfa163f5c02ad7a50c041b6513d08c5b02c908ceaf2951d455a7bd46cea112f569f7f72d0be5c11dc664be2f7cb7ab00d87b2265a7f32ed76acfc5ebf1740ddfdd7b509b0d71486e55c03b7441dc991fefb0b7319312133c00e0a9599e4e4b83928013d5c6828125ec16b7e29c115329c13b8f347209f94afc1dfcced4729d154fa9e64ce7f7b121dfa85c31d85548e9456146c603d18687da79cf36fbafe4719788b4f96d520de594eb9693a5201442da2766ebcdf77cc608955eaa76bcca50384a84a8140b831d425e225d0d74b23a1392dc9d749752a41d29b7f55047094a7ae4e7214a56789b01f76b963861fc612dd6afbc21ff45fd728fa831e06c9f113eb94723e9a06f34029b1bc92385c933821d152571a743598234730575c286362588f3535706b0eaacefabb3a4e99da742cd607f9b53f6aabef52e81b0bda5b0ccce90dd3fd0d5a0bdaa8d6ff558c20179da0c7b3d27e9db813f6e4370ebc5b8853a57216426962fed01b0ad60304825f54310af7c86724d4923406990f67286952807293f68d8aeacfd325d27542e2785cb806f09b74059219a9df5c8e1983b7135a482f9f8030362931bfd44509d0275d635e3ed79e3bd1cb62499d98ccb611aeba72c4940ae22974c35f66e02ee1b94897413db6d2d10b981b94815d8d8dff076e728218232f586c0166de1f633ef7a183546e20e715a757d5320d323e9011f30672a5ed1139ee53bac7c44a3f9e17b5f50c2c76d4fb4ae9fe3008670e93cfb1fe72de47b6b2177e71541716acdb3168510ae90621b34dfbc00f656c757c9b2d73721f8d57669aba99762999e58dc4de0b9f96412f8b1b0bfb02c800cadab58141721dd3b1ed12be3eafb9c8ef575789d4c643192d41233daf7c4d1d24a27f1e6e48d208bb29d00942623ae60b705d924fec35e7c87036a4ffa9b47370c171d31d52e1518995b47d7c887df5f0622c1dcedc953318cf4b577b019b2f9f1ec9d8537266ca8367dd78ac66e9172c267691fadb4e552f306611abf5f2886655e131ef2343f612813e35fce2b5ce780624d235c7d3c5c082789f4c50ddf12ea38729a2724a10c2bdd97b931f0bd75015ef926da7f76b607a2c4e977a13651ff9f91f0f6b74da05280d1803a36dc685ffc9a5cdffa3099649373fce928f8b36d4955b9b0fbad9c812293b62c51b73eea2086ca4f7b421364987ed14c460dae0b38d6b9672877b8deed58aeb29d329a2ccf6405a50eea164eb0b6bc270fed29fce1f15d646f4ce4f77dbd19726dc51501b4c2169424ffa49d1b0952c42552963838f1d0972287bb1d507a44f11ac6d745bbf025a977dde4949f2c618af6343df6c9f08866393e78508ad31a19faf7158a80c511da41e71fd550fc86dffeaccfa4325525d72f81c51741d2390a1c7b615bfb80f8aa00038c10864195d8c8026d3cae3f0b42740f172ac5a38bc8817bc2d6f96f6aa2934993b0a26c08d6555734715c869b6329cadb23e058524febb3601b42b2e825b92724224d045928d4d7e3e088172a0728d6b0170ad209dc6a24a320e654311347012fa006bf75408470ea2c9beac40726bfe8dacfb8097d8f0fd78f8dbf6680cbf919a0dadc9b94410a23ed3faabf47288f5d39c7d46ab7493906d98d50063a97d370457616d328f0019eecb11964554a6d2d833b29c2a665f1492d5f5a2a44853a1b954311a5fa79ac6749149bd8b687d0664d9c680bcd4bcdec671dee5247d58171e14e4d60ffe824040f35a76840e0b0f987f66f5e5770683105e917c0582f9e586dec26470926e96c8af1160f90313aa7907c9cbd1ed560a1985e52ee11dd0cd83a0907579637c75e0538f59a902298e67414d173aad668af0be34a6eab4f7ba1fac32d673c35d1f13d12e89ff2961d719d30bae1b76b39362ec7dfba1d84bb04de7075859ba05c2ca2bd6107e268792b378b3ed1bd987b5205a797ee81a4466977cfe435f0816583de8d0a32f72fc3be2eee30c9e72b3b1668d0cbb5d578766f4839939e108cbc87b4e0a144c721b9e40036f2e07b2f689b7fae997ab591566bcab6fcdc16521fc16d167cf8172b4b97bbd71ff469638aa3cf7f33f2d3c4aa29ce9e99ce22a5b7f6685d2f40c35acf61d3be8f0c0eb5eef802eb010cc4c7dff567c51780e1e5c71d7ad8b41187282a211548e7cfcc9131673537279370ddd38dea250b016f23670f3cef20517535a2c474a7058dec6052972835e65b642533bc01ffac865799e0fef11b29de7724e462e43546d15795d8c491a5e75591a9c7b783b687addc0e7e3169e8b801d72d0381b780496536df6daef53c514e85bf999216b0e1690b07618d336a3671e0725ac5040d5c9c5be752e4e337fb9fb5cea2d8a4ffac68a960bf99e4f672af67237afa00a01b1e1aa0ca9fb1c06c3ac59f97ae1e89f11031b44873d3b57089172da8d0cb9bd56d797d1b1f9cdd8d901f464ca56442268bccb6f0690f79db46872716f59165e29d283cfa17d45c3e98e31b40522528cf922c3bfc53c4f2786de3597187a45c56df95e581a18046ff32e8686f1a6934d1d45538156f3cc5857e372c7c90cb2c2aa4527779fc18f1ea7a7a6565bec010e72a2395754c00b411ff444efca0b999540f52cf97b0484ab5f8b0e0e7eac236aef7713ab51e02daef690726aad5086c55e08b9f755e6a9e89da91773f6be7f7d8b88fb2703a5a37eaf89262e484dd51b879eccf3a2caa586695716f86bdbf449d42daaacf4b6cbb955447274b9b0caa3c5fefce82a92cdccb2cc438e9ff079ba2525e5cd6be413a40c217269a0bfa6829d89ca586d83ac54e2eda3b26ffd6c5e0ba82ca21a3cfe7354387262a6bb6a83c70117f8f9d1fce0ceda22326952998a5f5219f4ee86d47cd94272a56d184e5dae945efba389516f5b1f85fb864fd81c98f4f8284a1ef1888218593bd9d997cbb72967931093e31502bff32ee0b7a6fb829153cd0223ebc9bfc7157fd4a4264278e52268b85b958a5500ecfd14107e2d66559230e40958fe7c2672ec4b99c53ea77db1490db15a9b0d167aa15988c7be989078612a1486f98d4a72aeb86a42e4a9af26b3b71f3c18661f8c62b4a58c0996a0f45f30397877944f72643e1e0a7719ccf7fb8118964aecb9f58c78fce1c631834b76f873e68f84a2370e9a4c3b786fc9b767a13ba28573fb134bc744109b4e9f1bcda35e8b1d69395ae8273e9d162ce0b0e92921c0fec960086a7afb1ec025d2203d34ef0c012f6972222a18996f88456aad34c546a259339f742e1d95f0e10322af70076861bf7372cdbcb2bbf341f041c9f09a2fa9dde029526f0e1d28ce0f362380a88896a55670467fea553df8a6fa619e56cea23b1b6fc9b128be553d70b1caa23d3fc084c708be1d8d5ee55dca20d61763ca13d11f6d14dca335edccaab5ea3c00654d46d172431deee61db1b25848f8663e4f0e80be367653fe83eca33e05a5f46261093d724a99b5f05b4a06ee3a53a452a172ef1ebb97e2e2d7fed18639b6f2295cb6247284e8693ce4de0a7e994ad557c6eb7331f200d7f7538d1cf01f948b90cf5a0a6f9db0d07255726c2b5aa292a426b9dfa71592f24f5aa7cc1b70e7b8420462ee19cb14bab26b72a9081bdd471486d44cd56260d461d5a026a219878e8c2bc93b729dd1962ef6e1c13f704ff235e6153526dd6f88a12e3fa2bc17c6d3297b967e72fc7782a448f40963d1b8f5539fc13a41a02790e015d3746f27cad7ba15b7cc7226e3fa5f6c99b60b5e6dda1c7153991807d9d6eff47f054c9b756f8f293dfb4bd8e85e9e112fe6a99cbc58a26897529c3ed16e04f0711b2dfae673569b1fee238a07ea9fb3da9148b84e432b7256a0f2fe11bd547ded5880b29de1c3afe57b72cb258bfcfcbc5e0200d6c899861ec4391258fa0701173ee2e00a378b8b727e0e42018d38aa7b3690250a2fa1f2c7bf6f82c83724b23c0584204766d4fcdca54baff395f83487971150c76db13eb551a83cee9552c3400f6044342a30eb8d6372c47a291ae81e7dc8e3705ad62aa8282fa19c4b23292e553a40351fad1dccb34153105a92a6cb5ecde0972c30bc42204effc5d5d6aff2bcc49fa0f41e7a2f8733280326fff005ea409b2c7c22136ace2a2cebe742dfd90a9c421474120f549172268d48eae7a08c924e395f991ecff795095033b20baf015e7be5564b8711863b493eb08632445f30dee60909c3dc7b28158179d3a4868bb74585e58ce3dd24721342a97bffbe22230d82a5a21a3992fda8d7b1e61ea6c8c7eeac28c4cea94272437dde592d250e4540cb8efe044616d3433b757f64d3e3ffadfc0ad23273d372742caebb4fc147a374bc75b7a87509650ffd8bf06d51f37fef2c1070857249722587a20770a1e0a07bc85755ec09cb5fb1196c782c2f209888a80fa145b72672b28dc329eaed9f4342c370afba872d69a2e8478faf044440c6480c736fc16d3c6d4452a4e8c75788ebbbcde6935be1750c0fa2c0cb71256da8d62325485f617259eb7d763864d9bb8df4568aa332301315b8a091ca65f5872ba335d3c626185949392c8e6912c7322fb4e9c7b356ba38b20c1710fa4cd73c6cb55e496da3b04dcb425e8f46c5c60e3365c00bdb585a6cd17b5d732d8975ca50fbd62c14654c72a089f063bdd7f6b469456ef71cc0ec78b10ae9a6abf62c54c2020eeba413fa720d2d02136a4164d0b621dd7a1a0eaa4096ff495572fe3eb285fff0c126a5eb32987f75de04bed8a4de252c8054b8f2174ee017d6578d3c3db8fb0d4fdcfea872a20fe76b8dcd3c97f440f4af9c22981dc949a451b52524d995835860ffaf9c327b45de2195337fe1fc3f9bcfe664f5bd25343ed54b8321384c332b5ecba5b072341d569e12559052dd18b843958d4fe60beda176ebf3dbbd16ca70259cd4e672676bdb37090bc95f0f2ebd19e19e5cca53ef38b460a6170e595c7052e68f6572dd398d305ead77df315508b6ff48b26ff15d536275b0d4f08a0ab5873082ea72d2bb13817f1a9b653e2ea369d9e84804679857145e39d8c6c7ce07d747702319a24b5cb4f0af0f73cc72bf41755220a1d0ab0fecf2c4fc9ff31bfb9dc94d31724220a5879984277f1c7221fc9d1b1f6bd758740f723935635e87ba5cabe14b48a446789e9bb7b255273b4792f35d837d2dbc25dfebdfee4a5757236bb5d68972dd352a4efdf3b0330df3995de9f17f88218258e2d8ea23b3fadf23c3ed0c3e726228595463e94d1762e4e913c75776edc44ff7906bf0f92f2cefe088b8235f284e166793becc2d2d9e400e1e0d3fac747e3f9d99ebdeffab8ecc2b63acf25b0dd8f9c0ab01afbee6968c67332b1277932026741688a2c02afb4f6d3ac0e9596472d3867c8d2433cab54ac06245ac3dcd18139a42ca91f3ae75338085e17d9672825fa02aeea8f973e2850666ce8b28966fd32975c0c1b8f299add82b4b6d745cb825da97d7e104284d2cf456945a2b4037c365a15ca163fb311d0d9422075d72e2a72255c0028953f87e617fc1d77739f822a73762d7131b5fa2cc36e6df53239d6a394f8d9970c87933c9f2aa125c43c43e396cd026823515a87ff868885c72f8a5077ebd1f77e6370972900d06979dca6a752cbf54883e926cd0075a541e2b6203a87c5151fc71d70e924834107c87271441f21992b47bc6a56adf76baa8261fa235a215f6fda5134db4c0d19e92435d421da0cdc9e4857b445a47c60e8557ec9f53d0899e3c254e2a93a0e46d935f47c4d169bd0537abb2531dc79f569c7272da68f7957e9f83680c950a2bc21afe105f300ca7a56610f92da278a6dedc7285ab17f9532c0afc986d4073396e7fb44276067710b7e97adb998e0987047f72201f99e2d9cd36c19273c57b4e282d7605c6d83bf2c650139b6e765e2f06c372bfb72d0f51184ba69b9c52c62db774a7ae1a7f527a72f28dd398b57546c7432824f20f4a75debe83983ac66c4f0d3e94a1ea62c2f3d7939cb08bef20d8fb1272e762514d153a74afbe3fdb22e75c322892b90c66c711ff6409233c047008fc72f0027da430545dae164b6a96c0b8dc7a95943b394c19af274c804a5dea5e0229879937c3e9c4b334b5b22fa46bb1d15484b314d6161137b2d7969855053dc2704976bade1e991917202218943876dcf44396331003c8fcd0ed1a74c12c3bb0723c9a7cd9260869204bacde6bbd2569c6074334df10562925f2857e7eb3316f72f7f94e83a55f03383b3ea351e2a1dd36f16d50a35db3e43eee9b05d3391a066ba7e3e75e3fa8d9a98d4c895ebe895b1b511524ffd2d36d0a18c8dce1b40b2c728f38a4bab141542e064ec231d7e48c380fb4610851eddf2cafab6fd81921a272f5a54061d232a35e4f87ddb2b0d6d082c64e5923cb88e936e7418c667fcafc2b796c5c943a20429ee2298d9ffe21812712f2f3995fb6fafda4632f85e8ecc4727423b24af64eade93ce3fca363b4793181d2aef5d481f5b80bf1b1dceb600469d206d0bbd3fde8f0f3660ceb58c3d554c946b2b3129d5a772830639aeb8fac72809315a98612b0bf6bb9ddbf93f515e7652c538cc9ced03c7d376cc53004ce72d167c83eaf1d546d8157ba8bd7fed5d6bf2b6885571e94cc553f4813b2b697722233178334805460d564dd82ba8a2a76976de6daf00fedc6ad7c081526f8a272fef52e2fdb75b86fa0f3e523fbd8ad9d3301bf2eb440e14ba3af28582d1177727c601ef948c6dda4d4ea08b1a3cef4a784e00298f7375f6e7cd3820891e8cd1d00ff3812e44bfe8449f690769d781af1d785663580401d611184af8e56b40772e967cbe8b5a97938cbcb705de28d13a7b48a19d54018c430c6bfa16c565adf721b4ec0482407843ced4a90fc5577dfa6b3d8fe9f42245179595a02ac1a23097254001e4d036a8962837ff9a977ca6cc15d5c6159579dac0d2f0c8da0565fa40231e6579083628531fc3c2aefd18aae631caa6bbc19d0c1ce6fa5ead952cd2b72bc33aeb0ff641f3774949a440a8b6531608341509661d5edd118745c02b29d3dc62f8ba0c56c506949929d2072d4a392793e1c940b302289a7e751daf3b8674ce4bbaa64057deb0e454f55c9ad0957470b5117ffa99bdc0f1d56ec6eddb04e50651aabf9efa1ac3d01bcd18cd801feb0c7349d87e41662ac8588c27fae38d5499f8ccaf90eea96b6c7d3c406f3ccf9fff627a71e28d1ff7475f9561c1d999839d109ae195d5d8f332e8d4c590c5bacc0435eb443bba9dedd4a7180706703e772e790bc77b7319d296971c0438f5158373f63a5029fe0e24073cfb6fd32ddbc7289dd2c9d2f4fe1e9170a16bb02434a6c47156cca6bb88f3598c017d958ee43727bdbeb31439e73eccc3bfbbb6bdecde95ba16a547b633214d0528095c708eb42a1ad819855284ed5a4095184dc84915ae5a70b8e0a41e80e204abd57db46e046a9c699402b6c48e90e73647414273e373dd11368d775d0aaceecf7311bec3c725bc592ade94f67d3d107ac287a3d60abf2030750d77ea1f192e4518d78a01a72845c4c1dac6d3cd4a12073608476dfdf28b3d2f3d5ed09fc7b2dcc823a7fb04b9555c2045654a493e1526bb8f08ad5a02905f6cf58de44508cc77f4231fd697222dbda1368aceeadd9cc1a8cef343d852b4d54becbaa340fbb49ef2b897a9c10385cad78cc37898d70d2a96cf231d72b1ebb782045c45575312833f46d64d458dabf7476203bdf8f64f184b5ee3a786cd4da07b94e140efa4dbd2e3bb501ab309e84b901f4899cd0c00b8f663bab27f72457e6761ecd19e63a1b023f0817db72abdead95b21ac3150305a1dc59bfcbff52c4c4a185834d930d22679a6b26d4721f03f6a92a2c132b9e0bfa53bbbbb8fe45a9580ca290018360bd99a5db3b6a72c8454ba610a7f012aa5d03837f67227c77a4f4c46101471a77add93b45980872efa94af1a93dcab971b5e5f80509dc0128508a5a214e1277049863b7e230e5728c7638356bb4a8bcd4633fe8cc2bc47d7be97c91ff5d331c105e5d806f12ed58633457c4e653fa24534d123ff665e34a263e254d2e8f6285320d95421047396882448f1504f144180dc1873075f5ef73e6727f9b325f5df63e70aaab94353f727b835a7d2c6d137fb9b02a4dabafba3f2407cb7e41fc0a6cca514bd852a9e072df3ee3dbb76d668ab28530a8a16af422c7ffee0f7464c0b8eb733eff86e0027258d9fb595b58d76e9bc2dd9d131b8ea711658b1f98410cc0b9e6b8f30e84bb728ac12aeeeea88d803617d5bfbb17c91d9bf3449f2c005fb7ec4f6b0e5869e713f659e92535dc5e868e1bb80c32bc3bdfebe14dfd0c66f9535ee11fdc42d39072d4d200f7a8a7198d7f3787529b4f4ef50cbfc47dbc60c78fa6e7140bc894a66699472914c91e41f3f94921598e013ed1f39c015570d5a545da5d43a687868a0350729460b5fc49802fb6bdc82a2c6c2043b1562db9fe0626a91c21502d4a735cf3dd3e7860e10a6514f64cb21bc369a6da3d4c7905749c515e9f0c21f595b172404691e84b87a2b97adafeaba76b7432342d130f301ae06eea29a6b93eb199722c40c72389ec908fdce5c22cd175ae4352113b0204ef0ad37cdeb81ae8736b7220eb47578a71404930c792291759d771dcaca329e65a27581d4e161c2b06ac729a2495e99153fd9bd5d640d214b9e7e7e21b55ecfa54a2e94d869dc09919675240367ab5530344707a148e7e96ad493bd903a09a23f773c7c0fe1abae8f5882789ca3785c370968759b7c18ef07cd2913026f0c1f5531a2a95ff92fb80901a72e7f9d5da844deb196b2d5a3b955cf1d34c75addda432c470461bbfdd9a7dcf279e9466c2924288325389d6604fb1d3e9d49b1d0b7dd52859c42d5cd35b678b38172e8457c2d5fb60ff66cf4126f17c406e12041c5d2db8da83c1e472ac72f74f43e3921f04765ef1c3c44a5115804456d0a6be18317d93c8359e48cc343cb572590f8f5ae85cbfc3a41cd55a0cb6ca9d297c0312c76123392928b151fc10847229691099d964bb1a640ef67a52da09644d9344b821cf42c2b274d026dee70309a51e00df248c07e4e07bcd330cb475da444976bb1b6b22f2fa9d1a6e43512e724ffd5d847e2ca9a8c8d55762a9ad6f238812699cd8b7bccfe7f0c430e598684a6ed9e1280ce4c326beb1f5e3d5deef8865920f0a7308e7b547d52ccfbd40c3620d4727f84b77aeabcc587f16cff96ef7acd7f8b024d092d726ae1d5e1627c664e9afb83a3b0ae45c008f011d2ddae56e5888d6ad21c8ee8602350d0a102144177f10b598992d2aabfe3a819301105459a5ca48206403f333cdbea66f2b4ee1335b54e6a9b82acc962868452a9247fb7585cc0ac94397dcbc9d278d0d1b64fe728ba715219521a540ca0b1bf3372cc9cb43352d1112efbbac9072a9017fe412570db9da69fa23541056eecf2ac09052f7fbf967caa6b252cd3c7eed022d57b0725139a22c66e8a30718704f1d49f1d3de7e8fe7cea3f2a7ce6547504ba465bd1a260464836c3331979165d7b2d4f636ec1d360d36089890d9ea903417d30e2372f888f0ef25412f36b015310a5ce37d9890cf84cf7ef0223bcdcff89dc15d9d72d185ae9aaf02c26ab9d3566c82c9e197723375fc148540897b745f7d66cf8072396448112ed724222f179617d779eea57e2e05bb277d5480aa547648a4160a5ecaf2779c8a7f5e58c2abe0164433ab3e44bb92fd30218c246ffe3a74bc0b73717482cf75a1dae3f2205530838c44813a33a7ceadc2b1bada506316079aac0972771097c305f06b9baa46cd79d8a0e798f698d78ec3f20e6742985b893bf53e7218affa577faf40890022fb201e0bed72d856114c6fddd5262d7936f94afed7722d9796000ee3d6020f29b035d9ae528e4284d7fbb3b90fdee0eade8a2e45e7140f489fc7f40f18e6347381f9807c20b0cb36af4e6f7b5a403aad5509ae685572fd64b612ea22514d547f85c84d447f090e6bb0afe578bd0f5601a7f3177867728681f34dfd510717b14b812cf4e6c4f5200566e0eb863ed480f129cf86f564727350178a01467e7f3ea04b8d7bdf91bfb41a19779533dc5f146541cea8026372f4e53d55a2e9124100b80324140e671113917ad00cca2f49a2ee44f35dab947218d01ce043ff3be75c2151a77e55f3389afd267766dd965b8f291706dde603574323ff72f2fde0ad2ef1ced21b0ecce8e85562cda48d39cd9f60556d28c43972e1875ae895d6362ae015c9c6f8d8ba73c34c37d0c0807fca4c2eb0baf98d2c7226257dfec72ba07058a5b6e88cb6446ec89316bda55b074c171518e45036f1643e5e2816bf210d60de885b9d44190499032b803297e22f1002b1322aa620944cb6ae1c1e471579d8399a14ed607a51f1da561670f1d5ea67e957aaca325a9c72fcaf36a6230035b15e2d24c301d1e88af39e4b38f18eaa687e6c8d535c5d905efc3dbd00f2112d1fd7692fb7f6677d13b314c614a83f8d905706476aecb273720c993b08e952fdc6e080c34296be4aa5e0dc7c4d1d24e516c5261c3a8a109b72fd23c751455258d97c2b9ba9f2f07e75a4988de3c84541fef10d8c2d3f022a3387f3a272fd8b7659201c68410ac5c858c77a489acf248bcb746d30cea1377472f2d50571220de07cf511dda2bd4945b301a2e820fe53db4575634bb7c48f2b6de8fb132d3e08aa127121b19f53a76ea2c5100ffbbc131a8272c1961a0d8adf72b333fd6120fc526ec888e5d86a04f9337003ddf5406346e70b1b5f824d510772e55bc617bdf4ee3d7f2ce4973127e2bab2fd48267d03945e5bc1b72edac79272ce6e4fabc9580aa0a29b08958a775389230f181f244ec86174cb376e96a0d85effe7e5fd73ed99b9fd1ccc0ae5d75f2600235fe23ddb674f10a52b32a119716ba52837fdf570bf7806ba10128db69ab09cf5d3aafe018085b6e86bf02e8a90725401e495a9de01f7c3820b23cf711aba1ab6515c7358799c8a5ade1568ddfd72e8ce273ef38c774290a281428bf80fefd2aceefeec6eb32652b9182a42f47652c21eb8596c1314536924182f45a75fb154270ffe417a66970b4252befc98b97260c2bb8e1976464571e69de825cbee8bdaa7fc27c9f5ece4832aff5d29ff14302789c0b96d3710b44eb7887226ae9afa8331b5b24da4d566eadfb0d4a0932472daba52a928a1980eabc8ad9d4870586677ffc3fb840d81579dd0e55fc0b672727c32a210b91ab76499f7d2161f34f26b4e7a52379bcfde808d5cd3f0fd60223c6bcc4d8f93e49032bc20d2713bc7204124f61c1fad396b6dcc8ff3cb97c01e72f9037a9245aacced94cfc21941ea4012a1726162943a25989793512f3426c0227660ac6e4c161ab6715fdaf922fbd591fe5a0d4f4855642319e9078d99550272bef00e40302f06cc063daa27e54f636489d63eec66016a4b169902e8d2dd897298d8f23aa92924f7a041ad523be9bd63bb7bbae3cea5cfef4e5fcfe7aa39935d65da07bcdafd6014baeee769a01cc5d162331e73798a60d95c4094bd5826b71fafdbabada4be0aaaa3b115588f5b19eedbad6e5872918d09cbe7ca3da43d527211ce36efbf96f99f474953c321ecac5c7d0fd3013163b8ebf185b2378ec1477272af99dd41e370d2c4a86a1fca7a92ee70f67794d011f1eabbf55fab1270ab722e2a492988fc1c389b79df5cf9f870c9e56896300de26f8e4ef6e40a55bcff1a8de7cfbff97623e6721f6ebdc7b9536276a5521f27361e3f030baa700ade2a09a0312d31f506101afe9cdb4213f0db46b1da96bb1697bf191ee1530858efa8726a52572bba004fd1d495ed85e2ad58b9ee5073a1d8964a6e0d9892d43d245a72122767acb4b2b228f94cc4e8340d221d3593702b8946ae272ed15c2cfefb533269f780c8ec4de80c34dee77e4cbe37019c310edf1d55f3f3ab057342ee18d452ff361d0a81f711babe426d11318f03298771ef1c4483fae5156b41ed6d672c292c2cc0fbbf35456246998fe76d20cdbfbade389911ea85527d094bdddb6b250323cc032531ddf03a170fc911962fa77ae4a2f0579c3ed38e072ddec39044327244a6219fb0fd99b9b08594203a2b471b7f3b8fbce31b8a2c08fe5e0ae51cd2418c547e15b086b9e5f676bb09b261152c1da433b5a880155878af4a41e1778e72d4ee4a53d898234fe35e2bc853d965108864335833206c8b6a56a2f00dca5d611f9e4088e6ceaaf0157352507954f64ffa8050480943b103f7e0fce7f8941272102a86ccd2c629de0bee81dc9383d4b2f7e994e517e48978336397d1dffd671b57bb10ac11cd31bdcc6adbbdd7f378137a43771bb3bc9263f4080e0311cebc33dbf8361021383ec99deff83e6b92cf19ebfc77f4d3e25bb3fc0b68ec16f36c2d97e3825c9aa7ec3c78bec8b33e442522da3c20f5ef35f607072a831f0fbef8198a36b57f0562691b83b8e0891dfe41225edc4fc598483b4bd31957b4656bf27285a7de4ca1f94002c2ebc5399f44608ed626e540d005e0a9e61609e327c5921053bf42602cf75425ee29d7093787eae51adea1b37067ec68314107f05026cb4e890301cdbe04162b6ab6579d3aa2b823d93f00b239bf9b1ac17acdce618bb2723df160a4fdc9afa75d02faf1cad7eee0850fad2cae98a1a3b3f9886db107ea42bff617d57bcd740a4f750289eee88102c0421535c0388adb3d3a00895771bb1dfb1783823ff0bacaba0d9992fa1153f2e4695df603e5d4fcd1cf34bfff9cd17292efeb2464b898fe95547d65bd69407d96be824b1455e952ba8c8052326c9163628b0c8d474815600c486840abd867d317953a7b30f0a2e8acd50ef343d7956531e34e56d43b7815402c6db7fc9c0eaea41b82f44348ffd50add2aa2b35b377275658d8930f66ec3d927399722ad7fe9cc200debd0645437a3c8684588fac2724eab6218aff8ec01ab1e035573c66ddbe1e1e8a7818bf95feaf07ad42f75c972dbd930b9281b32d80df0cb5078a2d4929cb3484d011a5cc07228314ac43fd30ec74abfc8b9473bf3f1f3a4c3cbe601931140629d50cb9de3e13b33ed5df1172864cb6581b9b560f0cadc8df35f25ecb6e9d7bf3ec0d1256adfa8fe79588220723fbb05de6b29bc283063dbe98ecd6e39573b096bf90278c39b52fdf551789572c81b4f3a9e3aae844e062a45d806a438d0aaf51268232f1774d81b9799354c720d1864453cc92ce1554b8b5b38fdcc0e909835cf1866fbee17702a224c185172bf6a6cc253015677e22a71ea81c36c0cd5423c18b35ccb4fd31b7f8b02e19f72f8be994d241ce09f2829344e634f77d50806d2e5197f3746b43f3e9a5ab09b72e30175e80993d13732d35015eb3cd880724876cbb12f4050d38d5b0a7a00cf72db7ecf0cda5a8c8d31e0b8d103cb408f654ee9c4a7f388f27f8339f9f79df4722aadf29b1ebdb019081f7ad8df3b1518a8f4a46da9b13c8050839f76da0f1e551ad1669ad2ce9b4af6a01f63bce3938bea87c7c6a67d954b5894ae7c67f32472fd0a01ce2ef69fed3e34bcb69e8a87c60bf2ce3836dab886b1135d9432d7c40b138e5beddd24fc49d109322e03b6181300d26fd5df01ad46b76e7ec7103fc226634cf2bea1df0da3701acc1898134781227940aaa31209b8dbed896f76a9e172650929d1792f80b4f438b669d9a4993a7369d8358f311ac56f8efcd11b5fa17221ecc4cd9c01853d7246799ab620f37af7d1967297983ae0c2bd38011f5f5172b80758f9d6433f0cf69c4ed0dfdd7ce5eb0587f39fae6eb6b9de80c7b05903388a995200593d9f990878cf42ff517bcaecfdf865585e00cf273f2b6bb1ad4a72f9f89b91f059a6600b84f371de317f2be368acdec1ee8095c054d141af86194a42a706cbc898e9ef06558c83b2fef2fd6634ae2a5d1031b05926fdba7d0aff3274cc4d443e00547490faa967563b9ccfb4d18c490690ed557d7531531fd5ad37fa489e78776e6b71d5387bba6b327eb5a962024008273d8c7ea144f3002837721b680bc65275d04460995cf3918739a78228979dc1eeecabf44b61b0e8299828225969548b4d9b3effd41a19d39cdf4219eefb23ff3c9118329e3a5ada342f6fe865580a33ad665fe3103ccb15b07ca75c883e7ba7985e0af83d390ba511ab1d3bed1d7061076bdf7f1c5dc89d5d448c0b4d4175c45275412655684d0f0f4c01354d68225d100a908bc01d33eb790cc98ca3304fb83a1fe08adfeec4c730c7727c4567d8296c0d156461b30cc7d99443a1e81360d92efddfcb3377532efa62721e782914ff98948058c3acd704f52d1ce07020477b6ec3d94fdb40f8ab90df72243f6c58d7b086baf84da4e69bf1fa07c72e7bd64fd5dfb7ce40a2ef40db777232e833010b8d9b6682f9a0922db7670f1d2875a5048784cd56651de84b98a57276b39315689ba2d1057aaf4f5485d2f3ad852a345cf51f31555ea468031e4572a2fe107164fcdac28c42a120797d3f58e36b9d267a0825b3dd97fdd4f460a2628917e7831855fd9529df48cb36def6954a2e41387f812d024860746367943c72a57a3b46830a754e496e6421b841af0be7df28e81622f56e18544143b85b16721a6b305f087cd77d67a79e860fd8432c9e718a1f36b5d7f7799ad19717c5b64bc64147e7880a2b876f936ac1f187be9a91dd98a978ea575360ef2112a6a61e18ccf4e5e8abe4f666b7d08eb14efc220520fb4e0d9d329a09a65cb03435c2e9472ea3773a8d170c983d2f8c0599348b2007cfe359d0034e12d973f9d0bff41a72e095dff9d22749fcf3ef0d63ee28a2c3090ac5d183f1cb5355cb40e4a83f7672af3b6f5d1ae28e46d3d987970677df2c7fc1dae2b0c4b3ef93a260f2011a723e9e752bd5b9de13917609f1c0fdac3c06db16151d03bb669f17d3384c8ab541724c633cf3bea4096c42c20bbb3b0b8a9e705ae89bb9360be075539225e642484fda136c9f278ca90b9ae0aba06fb962966955ae2f9aa3b0e62c4f0ba66328fb2668af512dad70d564b80bf62d51ebb1b7698d361f5280adc3ee065c2cfc5ec572c13ee1c038c822535d19addd512713d66a93e6940977101f9ca61cb420a38b31c657057ab89afe0dac9abbce07117bed69c305a82037a17e7e99b978a144911f00384f2b9b22aeb03ee7a4b7b1d06a400544ad3d3e17cc118f8aa469c3adf0690c6d1217f71c2839fe0d342bb6fc62fd0e417dffd4a2f76376dffd95ed55026321a29ab8c929c90a9064881dde00ab3734e43ff10d383e85a5366af95cda7d02bcdcc3a1d8e64192462628a979f9f5d4e416914c56b1c976e05b371834c1e7720d0ad2d4899ac59619dfc37fce7d39f444444139a72516c5092da4fe712cf23673eca431e8f4f01f5c9f47ba3a51d106f030176dc44c280541dae4b6791db0312f1ad98cf0d339149fd195e93faa18dc4a7df687b45d1365f746cf6c49e55072921b17f899599ebe975de903a552a2f815e1e3206b467581808944423bf3bf20e027d52aabd5236726e4bbe36ae7a15ab51cb60a34cd56e10bb1d168165a4f6a4e8bf6323b7f4044369c9409bc5b53cf7e20e242ea7943fe11f824e17bb56372f9812ce9a14dc0f87d353456eaf9e67c3b3b20c424c85ea9daa21c269393131975bb149e381d6b9d14afeeb909bb5ecbb544ea254f2f88e679022e2cbfa15472f948a5503310d85ae178254151607ae4fc9489fe667ab4c3debab79792d50b578b7461ce71644d616ebebe189cd053f53e4a04699189d5b8f0894a2b94d6f34be1b949ca2bf7e7f4a925ed99f74a9055771f75a0ab5dcf2fc2bff2b22e28d972a38df31d6e3e1fabc614f4f676352ae412cd97211c0ad22068d9bc65f85383720dcd2694bd565d201988fae40574613bab13277195885f3c6716ebb141450a6fe95fbda4592bbd0a3b045d9562b68e705e1470c9f7d655d269f021329907ce728026adf2da5886e0af6a811be273c011a788b19fc22a98d21e2f42f9e3a2eb192e69e395d2049abbbf58e4a52b0ad5ce6c4ba9c84eaaad2900c6ca9184f1687289bb32b5a44a14d186953b1e1a3e1e16ae2d056e5a4c21d48e3a8789e8f4f15a64cf00bda8454d5d44548c75d43b0b46c9974a734026408925aa3a224dcdba5ae6ac829e78ce7ce8ffc5f5a0b1ec99ccdff907d94cc4a14f2664c91d545c7a722ef6fcd58eca785d8f8105c7eb46b80e13596b68d62c0d542756579de006324930d412acf93fb57a22c2b8f213419ba10e194843ceaeac162526c4cf16fca50fcf7f28be04b79ed53cbb7d6c91db57265f8a33e989b19f47e9ebecb8bd930e728c8adc4ffefa3abe2fde7cc870adbebb22bd43c130c49aa99e94f0154838a172ac0037ed12e3ab3b19bf0bdf2cab095f6be1f8bcb7486e9677c5aad2d62d8f72b23e54a5881cdd012ec5f950a40ff7f224ff4c1f613a9f1f635bfadc3ee5f0725b8e77ca1885cf535acfee80ecaac48322d30b2d1bbd0a05617d418642757d720046a7c9aa7895e800d15a2cd443c6d1df2f2e6651c0990ddae68012aefe935a4c73c7dcc90f7f754b9b55deb40183f95596b5d8cb4eda048497e68160e8bb72276991ac73750c52a7fe892aafce7547880e98f94451a7d2d0cc783a5fb13d13ac970ca251d191c5a16bb3e3897377b53eacdee08336b1cba9082d8c03773d0193039afbde1ea82bfe70e2a89cc6565204eb83bfbc5f5c736e7ba3f75bd9d772950e9f0e4e8e40f919bbc9602422344bfe5a9360b59a60a90c4164ad256cb7722909523e2f8043be3beb40e470ec34a7726173366799827bb54bbc0b3243e0534b743732dd5c51a97f30ea99342c93d59766010cc44d644635a33b1c38916c2892a22aea2d51a4c9f6064b7c741530850ad43d512a64a9d6856cfaf13f8f9872050eab67d8516de6d9ea73dc60e8756d5cbcd963d4ba60814cd7c40660c32572473554dace4b8685bad1bfef41c5529080dfe0a0d1d64349551ae031442b714190b9fe4d39c0704d8efc09ba418e6c14884cd8566c8f607ff7d3e23a09b1a972b66036c0ff5ca5dfe809f4c33b9076dc1d38f179bba615d4581fd5a5131668724f2068192c930ff61cf51ef19cae6d2da5baac313507ddd8f90e139eed671372409bea40544bace9f4476aabf480fbb36b1a00301112caa9c0f50b1c44ec0572585749275e58f56b48d5e9f9835b622f16ed7486b25c16f421672c0caa4395723edc95142118a38e1ca888b298ea6c083d2ffccc11ae4b1460c26546c22a5072f7c9885011584d6418e27e31042ad2083e6f74d25db3586b5a5faac754ec75724dec2ee21239cf8ec1435363968e47cdbda201733bbe4364e9db2d7d86390b727846c366f28f95e4b5faa1f4b58331900191eda40bfb26057a54bda7060985721f370f24933b6e131ad0ccb56e7b4acbadb86188de47ff7b0348797eb199787222033ee3a62bea0a6c52042346b01111dbcca5316d918a6e536804be5305c767bc3df3fee9efb8452e5e1fb9701784804f53527fea8a256868b4a7389f1ba5722f5e8704f39bc60cb47d1f4b868f97f2b95dfcaf84ac07ff61ba198d611212728fcb85bdd86b63fc91cecaaeca8c6f7211fa402bf7c7e47787087591d8033a72ae94acdd904f29ed7b0d910ae574598214c81c55f68c522adf4ea76a0d110572a8ad25cb938902c9f0d3ebefd26adbbf460d238c514d1f08b9e74f9c055903728aa1ab59b9122be5396427c6a1dc8bb4fc869ae579fc67791c9845ff1e0c1772df60529cae0b9c1d85fb2ee17c3c7a14667b88c165404c1de735f2cf85a4d048fd4927cdadaa9342084463d413261ad6f135f7b88229582dfbf6d3a9aa84782d6fa418507610dad51db9bc71af6ff7544c121d08ba881b43f16287015d806472eec58fafe69e9683f0702bf7e3a77a2e7e18d6d495d892d48a379a94cdb04772a5cd4a8e1a149fa8eb9219d61e9765ce99a4bdfcab9f834cf4d3a4df14802a727f38a6a5d8f604a548b8c46dbd42fbc266964145c91c4cf8192aea07871c8835f952e524231ac28ba333ed9e1827287781c7c7cddfea08b90d2dfeceae77e05a3a7649473ec6156b43bd36b817489f2b51dd72724523f4d8c3f29786196fd9721086378eda158f6b6811d7711896c399de043c6285e71746e3af71c3a238ec72bcad3aba158c015172ca0b6258be98c29c833b95f36413edee1d9a8936a731521e09c4122583f4044c9c3b3210299c659e3ee9fb893aa2703f912eecfd7f3e4b5b97f0a99ced4f5f494c9df52d0d8f121cf40afa29a9ba838bfbe7d67fb8e572d6bce9c390f65d86c44b0507c3b038fd5d8e0bc1db51eec6b982a93a0748a805eba82bbcd58e1374756ac46b4a1f6d9c04bda2df80f6c0f0cc542ea77576b41b5d7cef59d43a20f4f4901b0794a315f2741feccf98b49fd896193a7fe6bc3a0dfd998e97498118d025a2c023c53260988925d2a390255fcb48977fc117bc31720b74eb645d3ca299ce583e692d6a04f3378e2ad4b566eabdceee4b95fffaca4587f21600f4be851ac2822b878659087097289ac8f21ecb63241118ecc28547722a36f5b05214b4d351e6b9f611b36a9f844ed574fea4213e14b7b2d761b67a721613d3dc9f4ab367c0d5b62dfbf656b9fceca190ce2be6d94939c629d1df603e280f6dbd53d848b33cd9192652bdf61fb9a77e7dc401ee7823577b48b8f5c203412c6c0c3328ad502c04f4a481a69395557480437df0cfc51d599977de71ce7260ba89cd96d77d69e6ebefac5f0e15def55e8e64a9e3e0167b5fea0652d27d096cb4c527546cec13fb2130eed3c2dcbc19f14a19726407b9d9371be1d6d4b2193f5a8a627c9c18bea643e0810f38821e87525626e637c2ce2e498348029ef739c707e2db5fa3695c409420c7cdc38c2b206399ddc52b77abb636e43885514360c97305eef0fff15a182d8fc9df7cb1f293c0aee0b0f62421fe9f9a57d32bc7726f00309d08f0fc020ab01343950d8d726447b5191349dada4aa7f5ff7e2910673207dd46e7addd67b83bd9e1d432573519c9f67111cef41bd296facceb0b640ccdd14217684342b1f9f1689abbc289befbdaf51011ae50cbd846f5c723a3c9723c7832d1da0aae42b1eff95d63fd4f5e3341895a63833a473f0d214a6b247e72bdf80defe4a628a718d957155e3e06378176c19a0b299fe43e3a746ebff3ee7294483925de676d14287a26e5145067538ec89b26e0b6d9337812e2185ba8640ca4503a9b474f8dfe1ec9a493bb78d25e52d8730cb0936171e4605ac0307db5724f36a51191eff6efd76386e5b1cee9dc6f91246ac906bfa3f2009acf0583fb16b1f6dcf75039d471f861e030e653807b6db4728c50bf8952ad49ba04017b1672c4d857eefb869e51fb46d284872fe05c36be9d67e0361fad4efbc326d169286058b69fbc41ce2958390b1ca68b4edacd1a8c26c8f3446d051401e9949eab1872d10549e4f5c9a625354258fd453255e3759e5d54c992737a570bed9d30757b45895ab9bce4409b836c507738d1074af6b005ae07acbe747089f7d8d6931923131fb25a1f800624c773c5b513a2ab2d7d4ef6231cedfc966db0fd8f0ea2417f5a63902bb024a9274711acdaec129e66b2525562ad0012c9daf9d1fa7ea383bd72ee8c94acd05bed5b37f08bfc1eabeb36546ecd2ec694d75a0d19345130da6505ac08ef6269c35a306d7f73aaf02d107ebea138ed1fed77a2d13472cb5bfaaf27d80d44da747f86f6e37429b3ccd0e04a346dfd5515359c1c59d5991882e25772427b97583c535e66c60b619e0a2950b4af43de3a556d10701ea86594b1c6e970f6ffc5bd41d32f85bad864de0370ee6764c51e23bdb29487bcb6c89513aa0872e0eeb2ca8e1ddd05c74f190c1cb0d2f2d63a727039c73036bdee3ed972e7d272d346cf12a503d41bd49e40de92d7d0d12862003cca732757e2a0061a68d816728d56765f7975bcb40405bd3075a3aeb99b03bff77b92f2398e2e50a4c5e63152044f7f7506aedc484d9b18173058cbcc352289f25ebc41f13d7f4e894866e17223ca9e718acf15904361f58675ba91bb3b55696e1d0dfb1f0894081f12fc696c4e9826142957b881f6b97a1234e64bea27db617c066f448b2b16f32738394272c40a8d88a782bb099b5c3cb1d0400aa8feee2335ff10f982b319d19d3151474971574ecc3a594266cf7fabb7bdf3f1d1e0ddd71bc9536d9620990f357cb21f72872f7956d885eead1ce57e37295f80617177d8592bd300a93e6bf0aada287837f25b2fab94e07ba79a010870eb4424f9d02d2706bbb989ab772b034e79e10b390db71fa5174249577619b8732b477fec077f167755eb4b833cef494ab15b5d722f73d6d077534c091497b5723685c908570b8d52f9812450d78ccb7ea48205724e2763e19db5e84cc9f0069c856f7b1a7d78d3fc3f5dd37e4cfbd875e045f772aa97ef0647122c9723f72d467c749bb9eaa27710de8131c40c658dec96e87f662ea0d69b8a060eb4cea912f34bfc5adbc2d41561e13206273043becd35f2a272c3e08600cb323e47ec42978cdc53e772d399a16ff293d77ec471007c793d774fc73dd420e1f7ace169df20555f439aa4311e540d83a625634b2ee4bd51fe381377d1405545f1870f3d7b1c16e825190a06b8855ae961b2c6414c14a49e01cf16bce7b03efaf623b97471d5d4b2ca65c51e97f1bdfa0ef326d823ce3488e684725cdf013b6b9bc2dc1d67ae8e100485bd6c06782c87f9068df35b850cda99d372cd09906bf6abd26a6f3728f0a471fcaed672157d37b5ca8ae5b74772956e9a70e5cc6f05de83593c7228754ee4ce18657711bdae4c39ad4238c8f12e3c531272de3f0cd487d35bf25ec37522f43c56b480ce7d2cdcb9d474b8054d1c51a8716bbfbf1c00d087162700b194ea29d2d673fdc50acd2e636dc324e612c48b574d3a6e775a0db997859c89359b456f79a442ab55ebdccf413850f5dea5ccf62873723df9b80e255138bf971db65e16f130031e5dacef1f329244b0093e76a71b780bf187dfa2581eca9a790d18a2518458ee73a98a0f37acfc822d45a714f4125f722a8807dec25407e3815a0da729077de1cebd9d0eacd48e8c5dad86f6185e972c0ad5fc43c8ab83c2f235b73ccf8fd4607fcd4641bedf0a03772ef8936d2bba62fdc051716bddb3a6367f09f8010344180b3668c6169e3df89005a53b4581b572f29394787e2c6f1292cac9b69bc364b8595e709d4e4bc0658ffef00833386f72e66631ba43a46568aeb112be4820fd69ba3371a2cd358ce79505286693790c7241460495da114f96392c586360d524ce701a1902f475a9fd40b94a99eb2e2d6bb41a8c68fe54edf16a18159d5d905197e3ca2113a4669c5cc2d0de95cc829272d321bddf565c801d9af31e2989d6493fc137329104ead0c29350ec7bb7318e0fd2d868245f85147ecbd22f861f6f879f61b6affb2523622772dc7eb43e25b8726274e516c7f7872a9be06afa63cac0f9e6e7489ad53ffe69f8bbeb92d39bcc619d73166ec27267ca3a669cc825ace8f5019989aa4abb431341b1d7330f493456c493c16efbdeee7c7642aec0569d111af14a8e8bc3442c1c85c8d7bf9360b37229ba3983d294d4f7b8fd66c52c16a54e9f4c1c55c85232df8084ccc01cff0c7272c2e03a8654050630c88c6eb1aa100a237b1e31e156c5ac8b7f6790b50fea72ff1e6a1a0d5f86dd5a30422b2c9619e275acea1cb16b267eeba928d986237b7256fddca5c33bd3ba5ba656d25595044dcc0f4e30e5973273789b7c44445826725e25e3fa0f523ce87a649446f6595e7df7868d2a84ea045f0acc7bca39e6d572f235773308476ec7b703aa81656080bef3712bc72d163058dc58a312944dac30363410b05caf645f386addf6f881c7b97d6bb7037c382e927d84a999c69867726528f0383ca31f09fbc0f9fafa406908b35cd47a78f9a799973487d34e90b47247bfcbd0db31ef089d0d8ee7bdd0f34ec7db5126a31e82c965bbf937e8d6c37233cfe8e1830985e90fd24373a4365359bfadf40516904ce5b4431645c1761d66a137c4512ce96d8522069e6418a3deedc7bd11b2c92b3c7c5659bcba29135b729f366fa2a916e1531696742e731226cd9badb767650d9bba5b8f241811e7cd72075d90cf1170814ad45f7e349333f6aa4eec77cd22be854d60505d57df822a5fb53ad7668100769e63ca6298b14ff254c25840a657e41c1150a44c88750bc2728832c3c14ada4b0387efb3142446bb1c337308d1fc998418301690284ec13072830dc242875b3c8785405ef02320deffa2367988179fff02d54d965f19f54f725634b342737ad955e50a2d9f31b865c3239da74db040b57c6572a54d3e8fd9069a35400eb9a00535b1a454da13df89e5e55165246359b64ebbd8af3298123172d44fd622183cfbdc5cefb8110f8a2e6ce1d9b86453cde3cf59afb2c6046d327283da2fb8690feb56c8f32dae112bccfede1c0da3b835f15cb5af0ec48686df727ce5daa9c7f1fd5728061b5bc113757832036dd28760db0a462d2b114c25bd59a22bacf879738e1cfcf1823f5be02764ee33f5dbbce1945ed3f5c97cef68e7443bd76b64436dff43375038ffc7db559bc9ce402dd2b23919527ade1e163f7972e6d7dd3b1d0bc826cc7d2f53196d70469c3a2c94df130bbacb303812929d5972d08da292fb563876111c7998890e7c9875666f6c0690da17217ac2d2bae8433cedf9563925aebf80e25252df01203f37fa0c8bf1258d6d455abf97519f116272c55ac89effcdc8bb80c92be8502c96243c7fdaf21de068639224ef0687e1f172fc1531922e6d60b6a66ff820ac32f1bd3bf560bb83fb2c97b3b09fca80a3346852e5a75853fa96151032f15acff30507246419db82184beaa2b4448379b5c1728e31a08ce7e26271b1280e789df2805adc13cbc7f517f9ceeab8ef282f6ca8325ab484501b77c5d732203066f68727641bbcdf5a1be7886baa9d00e71119b17272bd1a4f6c49b369a5a1a589ca232cd5a763e060f53c0835724139f1a885dd3ccf388b1704d31e2414dc236ed12e74852dfa158d87823a22a8d43a4b7118da72a7c0fc0aedc9ab5c2971ea53b0d1dc942516a329ca760eb28cf08a6a4a8a78608768a4a44c620e6dde7133bdd8c87aa5154263549882a506fb08349659c3a31e0e2f5e0732618fe91e6af9bdd5bac98fec60b517711a67e0556b05b6935974722b366acd7d9f9f65a34eea991539283cf881e72ee4c490601ddfab1a2f655d724918255f341e92067dbca1177ecb2243964239c61941151a3e588d6477a1097273cee7ab2806dd085d0830b4d6fd2b0a7c5d912d6fb23f869b05dd1dd5a80772b94482fbe9b443bd185a2eb06b81a263bc7ac52df5e1d9f60df7c49141a77059dcd11b6dfe42541e85030300f028bb602420c8a003ef1f6ab7ff9e395c4cf8726ccc4dfc0fc231b7e756bec8bc2de7923582f1a19a656dbcceecfd0737f4aa445932ef02edf7647f1767e26b540927c478bd66989a7ca9d99c98728a94133d7208f9cd13d295e978c4fab40fcba9d9e5c282511684e0f38b3c25b0244e962c16e32241875782e04f3c70ac203f5baf212cdf1aa3816cced6dc04580e94b4d672b50e3f924565c0c33f464a3bf464169a67395701bc6caf9926cbc018455e772fe78fb40075db8138fdef1a75aa3edf1a291ab788c08130566a253d10f6eb9572f414b01baa62966134663bced04bbdf498ee1aae6af6869b6c31f266af6ed9612e253f5d01f4b3091fc3a0160795e9cb0cbb0e7a6074e6e7653190d7de189972d0c422dca2a3f9a55ec13510bafd3f34b12ce3862e47f76fc6bc7850f2064a72526d03afe57f5a86a8ee9e00f868fda737a4f11565c4e42b02a071ffb3298b72c3cb15942e261a491117a603f73fca9e98b24649f5038a1ea83757028f4baa29e803a21218d77c212f3ab730ec07ddf30c1bd4c9ff5e52e0cea837e23a9b1d7285da6c14d97ed1a50477f5071ffb876d6067cf2045632dcb85b3384729945f725930b5105836891e5cf252e2a26d9c72f0a78c3549e8dfdc8bf4294bfaa52d72af70b4f0786fa848749a526ee1901ad7112cbd8944006426e876300d97306f72b05efd8460eafbda87c6239313cc7850078ff76fbd73db819192ace8e8e29d72ce4778da0c6d0c1978d1bdbb93ca45ce076447ea1fd20c0c3817191182d194721eb21967aa65f23648da156ed6c350b2a4157b3edd5241a6a1f8b18c3797c072612e8039341f0dfcb095e7cb9236e06308f4b624bf60fa2fd82954a512c78a7270478d4175b270d45765965109d8dbc218d60704f37f97e2c99ffd4014028b725e645a7f56fe63bb51409c67fa48cba7dd64701f148b0a23a5dfb1b7b5c39e7245f6b49245cd9642f944f4978bd24b63e4fb692c5b398283035d25eaf310ac2baefc14f0573f3e9a5a7423c15227a6199cae71e0dd7cecfa562686b72f10af1049708d48adaaa7ee6856ef457b9f51581f75aa58b02e67b557e0fcf00901147272680979b27d246ca965c8946d10efa8061cc0321180c4833e39dad98940ef72cc95714dff618dc1571d3255a01cc8c1a21790582f68a505e6a5a0c4548b5367e355bc3d1832479f6dfe32f0809a572d135fa80f8db9c38b2a0b68451e373f2e834b7b5d0da965f70b178e08cd11a3ca69df10e0ffd36b199387059c3f6f09297782ee017d87d7c7817bd0dd544dc0dde5704aee0d56bc811a57d3f3983af50557772a94a2e43e8d8eea3185886005a60168a07264c8275d615e5b47a4482272eb27f861289e034d3870657975340fdffe7b3dc2ebcb9d2c2f198e2567a57d72fd88f183a53523d0a3d2dd5f31a3245c510b57c09cb1b11f8eafdbc617a712720c45a1332c7c0a86b1a76cab40bc880ad8c9feb4119ddf3c6e92dde5a3316022a16eb07e97c9789e20f7504fc195fc3de2bb660ed7588866b16b41fcc7c4c4721efa30561d71a74c17db8e8c9e4d991f1dbc274c4f6d7041418203ca2916b037e1cd3727e361072be5928901ce08b090b4faa0c42702a9a7a47c2944e9cac63c5416eb2a79987873a82b749bf64e33795141e3e8a1e7657f22f1077811b6457219707e38532ddc7a2aa848e4d6b1435969b846d25163a0b3a9491a1201acac72e9ad7d64d28c8b722bef8d03af9b8403b56bd3e83572eb0b051e4cf31104737242057857f5d0c8c8e7d1de3a96cdd5e64e69d5c7be0010712dd8cb9cccefc972ea6f441f168f883bdfa96fcc0e619e5d45daf4d2d39dd705b034f5318855575bb716352544420ffc8aeeaf96c031632d47f97ed6c0d072852a04cfd9ea4b22676e872b8b22bd724a10f00e236c8ba1cfd829e97a87d7fc1d9bb187985993b7448cb400c6792be126b04f9d127ba0b72079607c4a8e7acf5c24762068a987da7235ab417117ac3f6412b1641ca875690af5f8f884d6acdb391eba92ed3db5691f377da3c2904ec2b6b0737d1a98374ceb4ea036258bf934dda60a516f7af0444da07274f4caa66afeb3708e5abc25573b9248b573593eecb4afa42f2fe4b41c7207f5c93d2b39fa0d84ac67d05df5630d3ee6e9238079d3504f924d78c505e5723c74c5f85a8be68c6030c1003010c25a25bd374fc4f0134645442aec4bd902726c991aaae3e2e0fc269c16a1ca789a1cbc9567df496a31311245233be97c92725a0951f01c2babb599895e84e05d69a9e00be66da11ecc11807a5276b935f733ebcb8e0aade4cd133637cdf1ff352a3bddd63d71774f8f2921dce1acd4967c60ce8d3a8a144625fce6f301b9cf4e5e421fa56ae649d33b19e7e9665d5e194b72aa6a7884aabb116001a9165ecba510f58509c928d271233f7d6e470feb77cb72d509b0c95f238b82fedd0235211cbb312aa22736aedb39a2abe0c62cab501472f547d1df0eca5849bd227d01ae8a49e063bf382391e277baa1a69acdc339707281b2521f6a44e6465b9dcaff9acb6babb33deb28426e030555c1a4481816634090be849a1127099f21cf291a5a5d91c3b3a0e7b5fce703e860cc3f6b159e1a7277e9f7369bf311f9d3294fd30916d71c8fbf632804d964d649c3ebaf876ad17237f7730a65f1705028bc07079d05e1b60e8fafb10d67b3c2dbd357761c924172d85c2fb2620f06a4d981a7dee74101059d330893720d850aa9118b0936a8854dba8ce06eb5b40e533a6d41a179c6f2efc34501be2e6e26bf29b41a89acd57d58c4956e1c738a268d5e9023928d8d78132f12ce9a36db06644bdf2a1706b0037239148d782e363e0da2c7eb453f531f9a2643b8e21da8c0178be16fcca314127229ae387a4f9005c7aed118ed837dcb376b8bc8ad734a77431c8f73b6e2f1af72bdca649a4e8cb743d8dcdd136cac4744ead5f14caa4ccfe82770b783cb4e8d67aac8e195fc8629b975acd2dc10999c944f828be3c729cc761cdf7e6667a1ef721f3c079802a5eae9b5234e2e56fe8ce028e39792c5b98b0557a415a91fc4db72dc8a5a1ed1d31cc2a124f07bacd753a8deb0bd4201c928cfc26f23680c717772ea399eaf8ef7d3f3439f0985bc2de29025ca1578207ba6e44271f58eeb48e372c007edd33213db66986185781b0fbe224332baef33ac86096094a6be311ad86de8545738d1f1cf960d8a38db0cebd9ea32691cab771a1d348e2589919728b43e4a917a0b3fc785ce94bdcc0bbb2f9b19465badcf3fa91f9e30405284c2915c725608ef8e4f5c98b5b881923e3b130aeba43e2db01ae79012e152e8febb742172e2d8a5c566310b81da34ba4485bfd72c9c6e46c70151dcb10c486c13b01fc07200b6b332bf425edbaa88538077ad2d525816d5eceb00056cf6889fd96845c853807dae5bbee065dc5bb6e78971190c93729c1ab2e4bd821afe41ce1c3e52796e4c31fd6961e3dec7a57ee06a8258f4c9081e61b9063f1f24f8da4f868b5bf3720385c52948fe8391e6e2d8a321f1197bab9c223c1a70358556cbde2d1747823615660b1a4f13969ac1d0abbb9a4dfe4850564b2235086186cec57bca9e0bc455dfb7cdf55512c28d8f8897a35de92103b5cac97cc78e2c9310cf30c34e1371510b36961ea9b29a0895e5fc26c483a7ca61d4c436990c1ed77bce154850390b7229c78eb503f3b57da4b85cb61425efd4c98a423d89d0f9c3a5ccb7fd342cc572457d4613f558b4bbf131bdb8515057c971c4b8ec674a9c3e47da3f1eb2864072fcce473e6762160f5607ecb524aadc077a9c934a2070f7ff28b6f616387729722240a04698b0a75d9491a6009784621f1ea64aa2d7410cc72ba3c69e4d0507653452e1391844d75a466855d19402630b695afaca85d202f13a4c05b48a6c0761c533e274e2a53dd7558a440030cc15a8edc60506d9e33bf970f06bb800613d2b0b4d89f2b8c3eaf75a54fdc1fae13fd9488082c95057f9ab800f438ed084162ca18c2d6c4ee3296e0510252a9e5e47150094d95790288445ae596f275dd47072dfe931901febd766215f9104d26cae5dd92d57d80c70925b5beeec2c5e21055d33c51673914b0a651fe0384135f6ad5646f0a761ab11a08011417bca89e68172bef3679b2d44cd65e648a60620525cddf065d061daf6d9a3ace86336ca294072121dc39974106d4d0eb7607cc5058a766e25e6583bcce12b8a73493c1dd75c6abd1b7a31fa17536d55db57bc55a455cf17e1e890dbe0ef4df3c62daa5069064e78203e5bb999b15bbf7b803b7168a75fd2c92185e3d2971558b34973cea1d14d434117a4c412293fe17e94adb00090a4b78cbf774c2255171ff0ca9d3bd041720755ca5814d2452f487d517816f59ce00e2224f3cd2799870fe8dbe6338dfe72b39baff1d5f86a64593be8cb587db53d39b3acd309626fadc7afb5398c53f672650071342115210beff5dad54bc298d4af57cc23527e22f9fbe2ae258ba4587295a9e0dfcb4a0ce40fb91584484846023c0eefa0170c3a453dcc260a90d45615426c3ba7538da4f129a9fa881b838db5ea93bbded6ca6a3d4a6c77b70deb744d5451ac989d3ae92febb112fd272c7a9e444b4ddd1f06fcb6937e974774167b3e8279237df51bc04c1e567d184eb1a75be821e18c68220744d9483feef39aac3dced36e15124c16d6d36b7ef2f4cad890bc7278ff0981a1867923e5b0e0c8af72fbd340308372ed781951c38f8ca35487309e3b8e303b478374883998d75522724d16ad309b6429c9194e07f142680d2901d4020d26af74d33141e974bc8c873c24443778964596d8724aa02cde77e691f15b2d054357470778aefac204e7cc72616b6c65361bb39db9d1bf681eb3b6b58c7b90c1885fd4094903f65444662d72dcc9618f20aea20ab87b46963f8819f99e2da530d580fa637b2c5e3ac27928299f96226cdebcd85703ceaff00bc0dbb5b8793a37af6577b320a3375334000772393315480d84555ec18454b243c86405aa6b55dbf55742bc1aa0d3609898564990e5e28ae60e55e899790e5336c1fff29e73d000eca63fcc1225877b77f43772f7e9c55963e714c72faa8f3fcc3d7553a394400227463acfd9b437b1050c8e724803ce967501cea9b5342c070265130190509a4168d4d474bc3a12dd337245259d40160539327f0f6186ad854df6e1541b81cfbf8906faa78c548f440a91c672ba2c5fe178b17997b91aa29aa0d4ac64797c375ba17be58b34a86959d5376e3c2d7cccc4280c2e7743b91910e10bb52c7446ba745fd7f6d36c82033dd7af0572cd2fece548005e251014fe08cec9cafb1b52722788b571bd340e4e7e1709440d3d8dc16c2889505cefe6182cf9e894ff87bea618d989fa4d60bb4e8f022f3e63880825f9a0334ae4898bb125ef02a273bf16787389802d2eec5deca974b7477261da98a8cb1be4b9564a2805df6b33e753b678e16e66d690c7fd69758194b86f6805e91b3aacd7c2cf63b503f6f2227e325dd707891ee6e412b63c3e6ba02772de2753afdbc732f286a3d513d69c165d2a35cfc20c00e259a5b4fbb819899d02b5f39d2c3498755d0d7e31fd51e779418b6bcc91a20c86d7717d5b7e08e3d172db0644b7a7c503a52899a721aa18ef333ed2c66876a1896a668aa8f6d767fc72a7d6a16a5b19a746c615221992d09c70293d8dfc941b51f76cea1c8a5cbf8d25e72a624efe1ac79b7e586efb9f50a07f2838649c0b74d7a7254d6028685d9872c30c091a11f58e282a77b5b0f9f85b8176abfc04e1804e0a93ee7b3046aa0412424e4f8b2ed492cbcce2a98e1847e868aed40b4ff6f8c6ea9ac10044e76fc77287875b5deda795088ec9efc6562df2281e4943c548596607b0dd2ae1d3eef90876237e5302c660bba4fefd1b322e56fecb155d2f8ba419c3a6210523cbb4151c77125bd247fa01b348e5bc1b7977c4e30fb37ade76573fbcad4ec3e08011632c9874541c9bfa44bbbe5e4d7787d7d686c58b094450e02c5cc5b5c4f3220bb50ae353e38cfe2359c01534948789d251ce15dacea64686e12015a66a51978f337210f7d9e8b751924867d87711e9aa8f489aaf14c16251ec3f57dde1ec9a43b951ecfafed3ce435ed06594bc1c7a538672b2f29c49ccc45fd13f84e4a6199cfc723caa60fd6144d06c0f2e8dabaecb0c964906159b003eed9e034ff903c093e811bca36ab6ab512f91c48c8c2539fc46fa591dae252373050a617d9ce3e27c9a726722b50d26076c6df448e393ff3af46c813f1532604192a72f0bce0da818031e9ec051f1e33e5856877178686926cf67345c90fbdd82734ce38a1ab71347190a2a2e54793394cecf411f3adcce4f1e8ab4aa1d5b1464d01bee4e59bfa4e00872069b8a353a0fa6cb0f6674314e7668921718a58182f686a09e03899f51cc5172eaa1d5946bad7b826b002e5dd9759918a9a56ca5e62f36c6b20220964653e372086ea0ca3d8ad5d30b4194c285ee02ce1979d185eec84e4e1aaaae55fb64023acb6dd64541f7e82c07a95407a2edc5452c41c1ffd8d1f28a92e4c0fb412105171a2bcdee5365c825f541d9df55da005cdf4514fea4ef6f8dacf83075869faa726b7e87ef839eef35dfa4ee86abc9b89839bc4487fdd4a12af0c01cec04abdd29dd2439c0ef81005c4c2f4879b73ed301fc7c17744d66f2e5125e052e7a16db5f3030abf23f88a14e902819083776383e7d2564b925aa1990179641947bd3ef68bff6a287106be7da6fe3857ffc77b8f1cb02d782454b41064e3c236ea8ae2969966c8281fff7d1ca6d30415ef8f085e14a80f16ab60ac26e8ed5a0291e485772a1036a9e055f79222384c95d8966158685c84f227fe1f7eac2a1ed4de074705193f4d30782cb125bdcd8c087632702bf0f312df2250c0b1b652e629b99fecc592cb0d115def68d71211fad1e0b6177b20436aceddff6e971c9675c9ad21f8272e66eb203d8298697d1476245a9e087c8c19794bd1d2155b0ac337e05b3266972242b2771acbff018b95361dd318e17be553f68049f5cd6c65c182b708554e5723caeb1528e937368f76d07b2b39591c3e1c3e1a0068dd7332c7216500686be5cf2c5bb3f93612ef923844d5feafd386dff726200b6e4c4c22fb3400ee9e2d372ec653ee772aa3160b6766e470173ef7fe434a2efee9ad0fd243f4ffb81d53972e2a26935640fe4437052f27b4626e475d928e7d30b6c1e67ece4082f4b3aa772ddc2676eb4bd3cdbee691b172aa19311aaa3ed364fec650be8ab7a70d8d18a72795e4524ba2f7c872e7005d0d899626c4651f920c26d7851f863fb103aad712e157c91c993c5a536fa7055437fffb8dc8123b8baf91d85a1bf4146597e8d3c72fcfcb2daf0cc48262384c6fa8f73a74ff82adf6f0c4be09afbd6ae687bcfc772e07d5b5b60866fd34c2c8f490a110c4f42b2731367bf4cce2b05d0937222b21ba8a7af3b65036f05f0206fdcc650b0673dc6fd8dbf490e7b08659f2492ce5f72a7db5d17e96247bbf76e4fbfd7345d052c16e9542ff3f425dac1efd4f166e67242933453a971ce18b0501f563f1de99d6f2e498af2cad9ad5379dd6c2ed98e0b67725fb125034d8b7817767312739163aafa16d78e0c1c95f2787cd5d0a6a11f1868f792d316fe9d97af020bd85bf5ca5b71f97c04476143e53773222a68b172106696f22db131d4194a8bddecb60be2fc2824a1fea15df6f4ab6a779f4d1472252345fda3bbbb9c3fd43c1fb514ddd1c8ce906fb88237c9d25c79cb1fd9fc722f40ac98b04f2dac65e7862755c35fa8b00973de3f379180757bfe8e8e1c71723d9d20e7b39515cd1bc49b0017ee1b1c0ec5ceebd9ba86c5ff2051b7584cc725d9774340b597e0bc5f7f61afa8b275e8144f1286cc246353e649c8f4cf386f72a240b9b5d70f71d980447ab922c0da9243afaa37929b994db2a53b04e688b61b46aeb540d942f821ceb32394f5f670e61b57b4b0c2ab7a26c83b1ef0c9465a720a7853fdde3947db7b8129b545a9b5608e26be610d0ea8f7e1ef11534d8473728178874856718e1e4a1bce5e3508b55e08f9690acea48449296f1c954a1a1f084a6caed5151c87c43c3cb0a57876c1124b52474504ceb4846a2872496acc1c0deef9c60ad10c3523e01f6d5c00edf8491cad95d96dea80c7e72533305c07435de0ddba2de8434da2e4439daed82f5de4616e516eefe0fd2b254f8dc5c4e8db2d9ba32b67f94be9d3f9844f8a4d5b04da01f790a674e39e121aa51134da07d2066f5b2528d0ef651b0cfbb2d9f8a35baa338c0aa8c2c7aa866a9f1b8903b66e1b019a64fee1127446917fb68480fdd641885891af5dde37933a9b2c0916fe924f0bcef47dc2ae2feba36862d608d12c811e634c2c2656de3fa78effa9295919609da79bed032672d10cda8a9f8962e4e85d8b74a540eebe1822ea9b2ded7e6a13bd1ee961ebd27740de3f50713b6c347980fd1f0f508848d05c6024dc19a0295953cb2d5cbe18295b7f8087ce31c2fd8aba42ad670074c64fc08c610d2647ed72737b2dbe16050173e206ac9364aa72c8daa148077d3f4188d16820ffb1747072fb6ec54441ee10415851137314cf8121ea834d0a1cd0c29ceaf76d310a43ad727bce4854ef53b04d2bc56a9c16106e90dc21e19cd6d020787901d4cb582a6a72759a5b0fc226c555fedc30ad439777f0baf4a838536095da582f7669cf216e714b9414632575f0b3810d2002d6a03a511476e3d1b465f7a3020402b640a88215f1c00073f1c46a267648d6403d41631c865c203cafe7001ac2b5166e6bb7df72f72464758becde5c7d89821fa60b18df2232ebc77d7a080c24800ebee1c459565247d7b07a4eb8584d325dfe4cedc79f75443a449f8ea6d7e5f779610709d260472f5ed353cd05d9a24d52da9bec76c866549b3de19ef07924eb7da1c3ff517241245a26f8ef91880ffd0fefb5b300ab21feb9574af5cbcafe1e73cb59df3b66b959eb04498704fd38c491ae5cb333ee8ac4be9dbd8f857736e8fd77df931572d59141c88ea3adcdb146d6ec1109286a2f8ce63aea6fd4969ec018fc87ea7535a5f0dc114ccd679e74bed79e419acdbde479957ce428343341aa7b25573f79507d99ad371ed4ec645439679b8b5879662b8fd7a0a79b521452a482faccd1be723335a6489fb88e7df659563e98b7f4e968576a5c2226593567f9c75460ff4f0d88d105355815b5d804f64bd44e830d7f6d85bdf2d6582b464bcb4d6688cef12930126974697da60f168d7e71dfcf6cbfddba8d5af9451c20d6bcb742d093f07231d948b4ef25fb18f8c26e3deb38904ebdb045c6aba1c2c66fb5aad8f4adc072295406d7911b00873023d1a857f16b2f66af04e1ca3d1bf223c79194d1b187728010637a652ecdb8f8675316bf3d31b75406e71e47c46448b9a86fdf4dc43272fe8716e0d6e67771feef952011cbc2059c1ce734fced89443eb6ac7cbf0859289adec4fe9fb292ff0f695b211e23de214d164c237fcfe7ed4742c3eeb3c00972e476d9dd883d217bba837959eeb1cd6db2e9c07b4a518c520bc9f0cfa4b2a972417341d2c25410bc08ecddb1d7bfd82911f8cf06b16dd0e474ad96e102d3c2580b360363a3dabb653b84668b1609057354abd0cee5b254201d2d0a0d2c229672c831f4d6bbe8d1b750ee15b59ac7dd9819e45b535f4b7c20d21f760e87398172a518cee03a026152ee8b7f6b01f462f41a82871a915373c36a8c98c47002c772415bf4c902bedaed4e754c7a58221afa45652f530e201c5030fc64703e59b3721b322f31171492cb15cd98a39254e4f730fc0c45b0dbc57947bfb685043564690faeb102658654af2858cbe12e0733947134d2f289a20e097c9adc45027698724d545b2a21b872aa3aa1759d91928201d073a329bb947f2544ad3c297a1426725353fe9d920956c906f5eaf234f91503955ddfa72b02e0fb2b0dec49c1ce3c630d49aa107afd2fb733f2c0ffbf135f71d83c3acfddd5d9ba40f67fe2f720ef61acd4752584213a2d97a4760f700d114a7f63ee46655d1eaf8e1a58555bb8d15a2cd0b84067ed8c123d5d2fe43c75341beeb0031b475ff72c6a73c6c9ca0a6c72f0b4dc8db886ce6fa7092cd30402dceb70bffeec054ab64aa628b9290d180072ed2d9fc767befb96529fae8e68aa1dbcc78ccfa2f0d8e4185539e1efd03f9b38d5ddfde84a81fe24119dcdfff399b5134b3e54ff9d41303493fe80c757c86d6e375ab8dc6bd6c5de093ce708085e4b2049ea109e24917d6ce18466afe65c2c7274aecdd14d9a96a239a62079cfcd501055c648ab6e4d86d0de348ec82f44047283c1b148fde83191713548f1653ba58b13657184e91a4c2709b20292616caa72f0ad4944d60851c7c7f02770885dafca57cfa1765c078fd1d9b46068cb7a9710afde7a6d22bb42b8f7dfbfd4e04095568e4962cb98bf6638dc2d1cb96676f1725accf552ef21f7fb42bd069750f863ac589ed6c6e7fb2dcadd068c3a8f53755d69c02d6ab991d29d1ed54cf3d0d1bb8d0d05434e5c46b9bd0b14bb03954a1c720d5c9c68596b254f91a3ba12b956094904f90aff378163cbbe0f7fc2c287d4728ee4e72fc469065d807326b6e400133af427c80f2ac2a6791f451cbe31f1130e5fb3e970832ce4ce2156814d72a09bd0054ea701be04f45f833abfc47a080e721a706be138f2ff747f4814843a95368ee5f9841928854f5df5a63dbaa2a6e67280b3d480e811f9d8f45dc8dacf0a6c9fba0d93460d24bde23bb482fa941f2a63928eb3be165c608a334efa4a5d0bf136b4bbd5eae2e7215d434fd3ed0deca772beb5e4a8d0310dbd000f01ceb59edc165b1a7005edff2561f1a271744409281e43f165cbd9a8f5af27850d76e3d5c6e1a5fc6d4238a72b7cd6c47347b025b0723f517bc145b7526e53bd1939535f5bfe13216fa028ffc622749427cdc08d7863f3244bdfa126ffb67b4d1e656f95c370e57fb3feab04abda08fa0fc6174b332f2daada26b0125ca43f220cb07e285a92f2af38ed5291cfc07ac282f089da9c7292d0e2319610b33d200ea3d389b6b279c7d586f523aca8212b85f4c149303772c561d21bb9ac74d479feca60147f3f5ee6c362c9c106ee76be7bec865fc19172606b42a3ba27547bd812a82473bec19f922f24d678caaed0f3a793a3fc358d72c20ce2cc0c46ae92fb9864deb69ebb3895c6dad1ef47100b60134024446178724b4d8a1527c2c48deca1a1ebbedaece30a2b4357b7c2146eb8844b063ba3b3721ac081f696a2d657b204e96f2463f477beab455824e256160666afb25033b6727f4b0696a42b0ba205cf6a5165e9ddbc69a75d2f77a09c9ef06fdf5ecc28ce728ba6f2eb07ab552290c33e83d3851ad71bcfbb9b1d0b5528828bb1f7f848845a6c6f7cabe729b84e63094e8f2e79adc68ef58fac3ee5ab9715c3194ef1f0ea725700d3d5ce744b41e89f693c9f2ad1bceb1ecdcba86bd782dee57437431aa7614dff12da26a05ff15c4e661471a8e35ce70fe17332e3eea8543e604c28392d72627b195a9977ac59644884df8e6b99589a32c9bde548b6932244b564afeba4282cac9f4e76e435881b8652e7c5df430dfe36cb5bbc222a4393964a86cf918a5d106a06be24b66b48b37e4729b789e54e8e5ebe19a7cc3da15f3b2e68d04f4b30ca0de1cbeddb3f99673bc1ed45e95d3a504a7adb86ffb28519640b68fe22ba72447ac1ec58601a45b0b815b0beb276b9be4112da91feb3787ae9e35cae791513cee766911dadd6f5ed2a4b606c2be86d84b764bd323d677be7e70639bc8bc9161f508429187b9af8f18cade69f3f62e0f40f7d151e262e3bc4ecc28991b56d551892cab554017c60e370855e69874d12c7c62bb35489f9a9213971743a382b40951ddf01b4f3265ed7599e25d7d4840fd271ff39bf44240a38c8521e72990a52e97a4796b69f60d1d032919671db01405d483b0d93945dc8da0a4e830f452d6b2c27af2839eb7909e7f130d86aed9dd1cc82fc9ed1c56d0ed3216bec5ca6a9728287069b41dee20ec74caffd57c9eb882ecced6c2e8348864ac4f86ab7912a7209f7d48821d7160f5e489f02d04dcd9b542c1ea1ba63f4f3a4ba28c8ea4f327253dee1e3ab7bf304f82c582e72e975d6d8e09b7cfa1f87714d860c91f8b2d372d3a5a14dca5daf0cd3876653cdcfd9b4babce598658508bcf79dd03f2665c7727fc81b06dc79c9d6ca0a9926dbdef25ec4489f67f4cf1bf0530ba125cf8257720b0c7416649c864ee136228543e007d247dba2b74536fe58e96ffa107b15d2203b1a753aa74081bb45755e1432da741fed38e722ad6f3b028ca543f47ec1c66f8bcb9c21a701cb08580d0002aeb9600884691d078bea17b7193c1d9c7f4add518c184dd6b3e266c13ae643ca3fed198d55803aa21d6ca1ad519e74f0fe486d721288aeb2835d89707441e957e8cecf91a284a54bd2368ea37ddbcd56ed929f1ce1a76e4d7d191dee3bcedef54b2b86cfdc9cf782146234f8c28536b38e1b2f7228048c9576f264f11888e31e30ecb9726891722f41d42ee0083c3b35a6602172dfd5e87d6d421690baefd4a1b1888246b5eb70bba319f10906b3178cf2d59f7277b669b4f7ae6b5436d62e78abf3c94907bb7a2b0027528ea56715b2d2a9467219eaf9a56401d73a6ca20cd4f7695360d6c40c02a8f1c89ffcf2a962843da61dd0e4d97801b0d47b9447d6f428375f4dab7806cddd73849a59d969d451e8c54bd6ab6d7bc0b787b5e85d10e057e5166b80cf61e27fe36008ce4923960327ff726253f16c5fa6e4da7aa91f4ff4d44f5c3e454b97f9fc2d3e0b5dcbb2114ab65b931af968ec60a770342589ab95d774dc22894fc592e541d1213efe20308a053c2c9577aded827e67d8378eea597911d621e859fa3e278b238ecc8b6f2e7fbb29de148a7c791d9440c3c761ea1bbed00c83cedf4ed9edcc8b94d81c8cc5e93f4fdf7d66195a0b1a59d940e255130329d185cc1be31c895ba58717563b9d201e722890cd91a8231266a5fe3006e43cb08b115960ed5c7ea75dab47ea5aa34ce77271c3abe4d246747a6af2a2548aefba041c07ccb2f73301486835928fd4d83d7281b3c6018efc74845e11b8662565b20e025e9702d534de1dff907ce61999e60359a404ff4b1707e34c3253c5d3c75cbe85d29dcd854f17efd911f10db4f70572db76d144bbae29e951e6b86bc28883487b1c9a512c507480ed023e65c01aa3723f0c62e6df85fd5e77dc8772693e4fad13127fe4fa33857b9b461f3accd6b5581999a25e6b0198793ae6ff5b02529423e537b974a266fd84f697874582c6456bababf3a480523eb47d8bd46a919a3bd8d01153573acc7d148f7f83927ccb25333b44b335a40492cd4d19c8522b151a48a8192b0838d020a863c1aa672ce20a725c18b470416fe1ddf39eed6f41024c1b2c1885fb1d19c057aba2b43e8f21f856df12fb604e2f10a6b0880cf29b592715d531071ce712aa176d6116e2c9ec971b21e986ce045e0d462ffed62076c24adfe4404789fc0edd89002306ef17b3e7727686244835fbb76a5fe99df366c533c92099701cdcc7415fec3285fc50e84f72176c4ed64e542e283340882086abfc6897c4eed2cbda98b645d0216c156f527247c412b0ad5c9dc4448347ab6d440c2bbeafefb8765c433b8cea1f3a1df747725db238c1a25bd50c42aabb58338484341429cd5ebfc86ebdad0369813b5b3647679f6c019ae7b78931a1e75cb2b5a9976cce092301935f0b529d547aae1b7172a64072f358b506286aea99651ae11e6730a14e22121358e9c78a2e97bb9f1b72c8110bfcd0e4c9d2709e401f874bb440f29df2a009fcb78d03df329d64f5254f12b0995a9c4c2fe60fc507148526f8a26c4c595c6106f4a94aa188be96e7a94787e0a6e1e1f0ee37cf439e9b067979156446f74fba6f97b4de24d5004d3cb75ce940d0f634ddbaccf765c143ae0186d39174d2c15ab7262c7ca0ea5a7080ad72d3ffac317406d07d55c735f1d7ec04ce36c4d34f3770cb7eec9b428f0f3032728c485115dbad1f558893fbf84c5d0f55946ac35c4710a028a77aeb6bab0ad172be0e228158adfc9a2e1df45ea00db5c95fcb6e7097509514491167473e840d5ebe68311e1349c8e2baf393857b9c9030a2937b8229bce3032c62acc5cd29e47268cd2948e93041d175f1dff623d9561b6cc662fd7792982860392c919b72602d4d4494046d96f5fe1500b609f3879cfba276a2803e98d0cfa76d8aba2e8d2a72587ace92d2bbb630f774e5dfa17275eb6eb423f71fdd2df49c7b8e62e76b6e3d3230e0412be31067b4c97cd05240c9ce58a0fd8bef2b116389ea794bc0d57b7279300f975a09ba88cc247abf0d76358aba81916cf3b9d48aa38dc0d1f54f9b05a1bd6f3d6a11fd83e2e9a702e1c147fbef01851235492ce0132a1008264a781adc0e883ee405afff8ec2c1523aa89264c0c7f501289af55fbf58bd58eaa024724f3ad3f4c751a624ade0c33b7f0204a74568c591f087e0c764461e4960112b5634683819dd9d4bfe76eab12775da169ecd8a6ab1f7e6c1abf4de7c9026245672b382f3bc4bf6f923cfc3f75a74c52960709754b91286a3a0252cbafdad8589726ef26def4a5571eefe51556ad9283faf511353219b863cad772ede287640fc1a57785225b93ceb0a8c3bc7e9dab23749c961b564a0f4e83cdeb37ebe1b13842e9aadf7806f1c52361243aca336b2c45dedc562ba661b691fce5e1f7d8db7a27278e98ccfe72b02962294a28449086c4b1491c45e1ce4249d8bd17e579fdae246311128538a7b61e00e3ba9f9c8b974aa767ec41a19ad0d09bff673d5ced3397241d654c4710ca66e856d84a17cc8576396e7aacd353988cf26389a853841a43ec890db1ece5b568656da921d40af538870275007161011c7cb5eacfd899fc1723b71be37ad5574b93203e6a9746050ab139b123ceeb7f3aaef0f6c1efcea26712da12aa85c87342df60326d42c5a877f6be2b2240fdfbe07f1858f6e1266a3725be212e9e5edcf035416bc9cbc51e97cc82c76dc1e196d754c5be546990f0172479a90b7ca98d0f25625ed4ea5d09a0de39cb0b4c4f0593b579467780325ec72fd4ab9460c6310680c95b184f82a8c020105d9778b795a468e440766b980dd1a97b0fe1469c0c55277843d8e3b13a9dbce5809770a894c2e38b47022ecf60d72167586fc2a7890a9c74a402a8b9081dbf18d620d8237d2c5b68fc73e0605ca41eadfc56f049f8cd83b6aa9dac225d9ed95e2a3012e6bd129351bdd6a17203472c0713e7cb11e80c756252e38c03a62ddcbfdeabcb33c38138f124192b574e5350ce8222c880fe87d0e2dc49b4f74909d0641aecd1e1587ad1f018fe6e360127277cf0446ee31c91865b8161c2b3d2c28f6fb6f5fa9fc907492c71805e1164472fd771a88bd8a0bf8cc22ae6c9d92a2219b50e0275040d6fea6031650cd449072d924b1a223bc98f5bb3e26015fee68f97a379015f1c777fb9191af02be1a0217d4fc92231e5cb71c1a672fe119ee46009e681f6fec88c1dd1781de87722bc172bdf487c45cedfb44a3a879662b79e0bf3cc1ec49e56abd12d8c675c8b5160972cec4970a445a24f0ba12d1fcbf8e93902b6b3a4ef7c984d91de36e8f38e60e207146c33b3bca878247bbbe179c7989b0f4d571d119141475a448f9062969ae72e13d15ca69005f95dc8c6ff039001f1cb1429570d430f7b5094a3f03f6592d027393662950be018a408cb48f62e66408f0554fa639a9c5d3cb3cd3af4799e033fe4177371274149ab18291bb9e0eca6a0b3d75b5b6e23a287323c4f8ad3aaa72ed49e64c4dd12217f9784b902b8835450274474626818c85c53d5df923a4f772a0c763e83c447265e2b8544b363decd872ef7e1e3e439bf27342ba8df290e235c2def0badcc66fdb55242c2e37bd14906e350f8d7dca2ddd4ad02706b37504033ddc3b5546734843598bfd638a6d865ee8a81bd9feb16179cc0a0faa0361c4360ee136ae1df7fc7bd20a14530e09bf4ed99fcf99f9249031b18cbdee1344590b3a0f0e45ca7386dfb130046f58a6338642f29b68c8066e02478f3f574012fd72e8fbbf2663ec4d4e8964d9d6d6774524b9c6417f3c6142c533efabedaad749300c250ea33d63cd92789fd96e167fc1026823faf380449b8181bcd9590f2a4e042accd5d9775c756841573a20524df2b7180a17df976ecd047aabe3bab1f64072333d9268a9daf65c85a70f1fcb4fafa9cdf32ff2bfbc8f44057cb20c07a3f87258bff421c91b1fc6e503e21507b7eacdbbf94bc841aa4fb2813c5dae15ab7f724e837d480f8ec4abf3352d1f7be56402843fe6e9b248eb891088913d0ae7cf72a06c6f9455d683d6bb3f2248fa19d0bb89328f038d12c43a8ccafe311550147215b02e0c7a4f2e6c8c67241096fc762c38a7a12e068b1131a9d10905ec696006ef297b85eef7ec95625dae430b3373087ddde983eb57951272452eb7168c082fc3d69c637b30e7563dd2d17a6ad1b93ece14a83463e9edca40df15e13239cd64a9489f92e37ec18705420be98bc8f1808d7ac8ab9aef131f4869867ea6f6da72e016a0128a2402a0c8f98050daa613961dad7e494aa2af6347bbd056fedc8d723dcc363e330ae2a68504be581f624832e082364a00b47a1adf300ad2dfdc05727151b85b1494f86183d9c93452035a0189040a8f66beb05b0e597e05a6501531fc1fd7f7592bb0386b9384c2781848aea2194ca2af8d311859739ccad682b617e1614c14bf5f8971316531242a66cab319e02ae365cbe7d8de7cc4e39d24a2460bdbb5cb06b9bf4ece8e5a40fa7760e466a4c02aace507b18b5743da2a558a6824f184d1ccf40ca7556756bd8a6aca249730c3e5596670e65a2dc8c14b24f74e201f62099c686f68fc947fde13d5bba5a499782a41fabf7cc94e7697827bab72a57f2d6762b3e30ba67bda936ccfd019546773589c14c0bcc78d775b2cb122720e7262be1f69743be2ca46064d93c7af69a43b8b8ad995ee99bc2ec194d896237dfc4147ae9cb64c39e336ce8ff1343a254343117808348c1c2e4b990a7654071960b5ef0e5cef8d656679c7bb3e294c07d83391849ecd0052c99fc87e510e72de06c2eb8e2f28a9a8b3a92ed59de6c9120f471104a78ac3f252ecb6f7c54272cdf2343dd1d535defcd5b3fb079dd632c83bab7d7368bcae4f5c6fc1f499405344f9681b8c92088bac43c3f3076ec4422e0429578665acde010af728f6269472a58c7737f2a0a45d62502a9d359c2bd1156f2126d60e4cb80306b501665ae64cf3ef929ef94b5a0a7fee56c7e725fed725f9e7ef4119845100eccddea39fe0720e65f6b3e4d9e2e393d84715104fee829038bd620f534d1d660eff6b361907728467528a79aaf643948ded3031c87b766d89ccc3f1564041568cea5ad0af0b72c09867ff5f1b54e53d37ba124a8921bc34b288549d118adbaf11f7d8c51fd241757b9ded29bd415d21f20ab7787ef11eeb3d6c55ed8a9762d23e221aad7ab7729f7f4218ee3250d5a27af0653a01881071fb07706e478a2ad6fe0d9464a01749c6c9874163a01c81c96c8ac9057a82b0354fcbe303731c003d9fa1d9a4c7a572acd9daa25146e0ee4505e8e7d4cbc4095b58c2ba266f60834f2f5e77e575f33a684a17b8c3c284fcb81e88f5b28ebd4e1d80a155ac2f87ccf4e3c17c0c5e6572e0abc6265f9e3e31d1b1926357c6724b3cfa00cad26f21c3b2f8e378d7730572c28de1d3570f0d757b7dedb990197ce164e2a12f357e7cea2f5ecd776914c17243a187538de3bbce4a14933fa7abcdf67a9b77bd12c0129c86a7d488bf646f6621f57ef135d90ff08f28c7fe7045ed4293d288766d4ca5907c7e65e6d1d6a07229a1ad32587efafb6f2df04569fe4c51148a628c68697abb993e76edeaffd228e28ae95bcc7ce81f2b78812c1b4d2c9a1f6b5dd0622ad3460cf581113567ac138a037ba3a24b89f92d85166ff9fd442d39ee725cdee0431b75d4cce5a8b2d24f71c821e663bf78ceeb35ef975762dec1ec71f8b2d9acbe30343600e43c0b3572ae04e15916c77103b75be29aee9849f1c3f94b62aa9e7499c7ebb91d81d38f08a69f89ac21e4e1c152b004ab00c41bfeec2951b14845e83bc1a945fe46f8c966aa6d1d919a8a6170221257fa1cf2b5e38a69fab5b7f4aab94176cd805c288772bccea3e2592ed60bf3947ffb1266fea64e742f4cd2d01042ee96f5e99278fb72b25e40d771177f78ccccf7764c07ba86e44e33867aa957cf175f23c3a7411e72e30d86b2125961b7fe328c7a620b3ab93db36c9321417e3c0131f095bfd2be7207203e97d6e72a736677a8127769c2eb46fd31989ccba76b7c1d537af81128726537300fc602f3e63308c812f8173942ca7b2f7c9bfc6adbd67fc473142d20531f96a8d0c7741e4e00da306c6c2262d6eae3f280dee714957f6f98079033b4720d61839a52c958fab558a0ee6bc06f28365d0ba93ef07577c859d342d3f20c7233a0f1334f407a6a12f6078b339e71041da21f97bf80dfddd2a635122ded3a727a836be4eb07b2b36d723b0905b0841ce8c981331031fecb49c957ca24d4e9724df3b4e9937a0aabd24c68945829930e4b4653a68809fa663eaa2e861baa1b04824122ef5fa1e28604d922ac65188a3cf1cc7e38b37704dc8e907ca81fe33d60569975ff23c96b8c8150f25cdea06749593e5404b5651cfb49ba125f3c8d2372b3d4f91de678646fa92b75ec9ac499c7bcedf93ff67661a60cc48b9366fe7e722fca928bb29e017a33665b3949db063629d281d9442c5f7ebe8fe2d609dfa96913803f168d7a7d0e245a93252f6f7e6ed543b64ef1378bddf4f3e16a41303a5541c551c6e6b0a85be1bcad0be29544bc72d86bcb49ffabc413acf923279d7972ba96e087e764fa025537998be90d3cc5943c8ed9fc6162959a7fb65385803150f971577eb5f95995be0e2a9e149fe1bd0d14ecbd87e788e3d6546d527d035772f52298c16b0eace2cba6f4be3945aa56bac2e41044d8a28104699177ca051c53e79fd6fd139ec7ea2da086b41489f51874440a85dbf4ee553af48ea84362ef02cc285f892519dfd8c57649e7d9fdff4d39e4ac1345d818c174a07ff0f071c772e60c838874684c106f636f377ef8e107cf215da014d4b637339fb390c7edb77274a6418ab53f680afd89cdc1f1b670b076f09cca22e472d3ae72bfd4f0c64972575c24600a5bc2adbe37dfdcdc571e103b99652e10953a944c203492ba3650721dfde2b59c7bc821180ecdfd9b71ba45d5719b8e4d2b11e931f1913774d59472b9ab7ff0f3e8aa55bb5eb9edb006c2e4fa1f9db600c4d2f64096a3cce224361b04f1d944ecfe1115a94ba2f4a2b79f68906a62de8a77435474f96c25b3d89f725fa1016cb822ec0c34d13ad61c7fa78a1018ec20e0eda46c3cea7b4e453f565ca6bf4e38c8c492b02d4d0995833aa0dbfa5d20603b83bb2a127b48d65dfd6f46c13e4306a2b13056e682c725f94e1f91425d920f4608c8e0837289e2c1b619723447711576e1e95cbe8784710a12a1f56233d2744649211705e137291a487d2b46604b637b6072ae3d8b0488fd0e5b687cd9169899eff1358885ec5dd9b80d72ddfe22b1ca97e565621e4ff9d1bbed7e33b87c7c9fdef62952db6390f39d6b514eacde30a6cfcdba53f187ecde67dc46c22217cf195194dfbbe5cf5ef2a5f642b623601898ce231f9b81b047ee6ed5f8c91b0dfd9523958584f5b042256d8f72be1d13f81f234eb426080140806c7600fbb7fd2b5093069afd901324dc2e54723fd4fe0de27fc786595589465bb0864d43c902f9d1e16afe90667e649ceb7137216b36df41c0e6ea57e4bac0e237c8d2cc3a9de0d06808d927e3fcb9faf128721046dbfd62a92316cb8e5393660839997231abab58e4cd413779bc3b1ed5213ee63ad8ece30191c89924c0230a63f2636233b12b7a6f9a4ccfdcd94018dc465a5677c1e6d56bee5a0bf02bbd15d37215fc31b39f3f7c0ab0fc6bb3a570309672260ab8cd11e48e6ffc25fb1583b759befa6e48daa89516f035bf6961b64a8272a4d8497f9f01beb88ba7a6e2431cd616ac5ef65671e51798953974b503a1b963c28217626fa834e9be3c711e86ef4a4c3898f93bf8e93dacb1bab51abf86887275350d3cb799aff93b24a3d7b61b2f2ac60449ff2575e1d45dce01697965ae72b443b507079d5aa76dd2a75ee2e58d52e4f61fb897c8c864423876de9a5338720340934e728042bed65704cb026c23384840b5c21bfc3b09d68687b629eae2341e204ec2b0cbe05b0c945adc6d6e83de57789dfb358ee526c41f71cc18fe3067663a4d80d66ea1f2eba3418f7b930f799a284cca8dd4003beec706afbe3863727fd3718461db229f989384f94b27d5abdfc14b444313dd1400d9340782eef372d778f9dea12688a3ab4181a035435bf52ee22613a9600d0e53836aba533eca3230db9f94ec4a8b0a2d823efac641ab10d5e8a4a1ecf53ca5659a26fa53f3f70a76baf7d11c3ea78a0127661eb4f023725f2c741fe4c7ec853615a1b7f3ea9e2663d9c0e0e0d6a99e55aaaf30148391b3a94283d95a896fa25c83f33f29458511c5134c76fd33990cd199060903b5afd575a36f4839ebf0ec790184f7ed58c772bff085949c0f5aeeee649a9e9f4e568e77b088ba829b64f9e37beda0b895875374dca0c8cf5c82b9005291f90807044518bea9c81539bd889e8e79a3394599512687ce0177a37855b0c058172e63fb71edff27bc9f33cd83cdbeed8edc0fe4720b8ccbdf6dfe4888157bea2605795a984ac86e4006064067f4abdf747646fa72e74d4b9c47a4786e070d2ecad30de923793a67b9f2097a89e73dea24b3696909bb3bbc30514f393d7b8ac638be54a5ff58869e1d1d6ba8dff8563d932cb3d97205f8503b4c6d1827434af84f040176c7767d215c954be65209c34e7a96a45458da8ff659411343d8a0cf6ee46ebfc721c5ad9a72caf6d1d61f0092407595722577b803dacb3aaaf19972b8e341bbf2bfc625aea4fc244eaf3094409b2faa497255dd63d1f480fdaa23d8523b74df8671b22783f6a2a70cec87e9ea221c484672b1a618332a1781c5eadbb3dc103a3f80a5ebc63beaae52bf72245caad77f41026a76db25168482771b12a72ddeccbd7c84563b0cbc13e242692fa96f960b557253efe8f0f097b1e3fd8d47c0586b9389f168f47de3d9ca7e1cd012b2fc1ed11a6a4394a0a9a48fd283372dcbd7b9a881e4ecfb2549568e1a89209d68606eb272e9b88d72cbd1eae6abe97e0e6873700478a77af0e4f0dd20e1a96ec9d851173168dc086d44320755e361ab87fb3db4195414feee9d670757553a5b07a2578a1b4e5c8adbb929966fc3af754d0636e5f65009b115cae1893740731958b6b8ab168953cacf30e2edd1bbe2dbdf40654ee60cf7cd94a0ddba8ac290e8807d1d7f72815533c1a912086cb39f3953d2703db0c983ea2ea24b3cb482cbfc494a058c54fec8088e075ed700cab88047b290a25b81f44ccb3ca91fddbf68b73f50af1472199bf764d9fef4740d88d556e65d2c7194e6b60cec6d21a1088d52aa47c8d07274a21b7d67c451aefe63432a083ff84af78f334a1ab5543b0cdfa0850c3fbf7261bb845d510f772f51ef4dee33fdd10c72f2353469653de3de053eddc9a2eb7292866d65b83ed47ac5c0aee4aa86c78d69ad21f8b29262c48d1ec7acf8999a728114df531356975b012137c6312c7b387389efb2c6802a872a3151ef062a53721db82268b7cfcb3ab4b0e062c65f4a94b14da3711814f2abcc90151654ac52726ab773efe2bc993bdee1d36b9085c2bbf81bcfa51d1f80e2764e91787ae23172222ec4a236a55da53b5019476be9adc220dc697cb3d0d7178ca24dab97eed072331f5f4d1e8f0fc7eb2aef35b42b4a1581e59db77c2a05bb68a437141303a1729b50e48e2df89c70274261e3a09491c9e5598ac20a807c95faaab765254050725c0bd95169f20d504d2903f3c132fe692a03e66f2bdf7616286903a0f2aff0728693b01d5f3739853729832f11cb97de60e3d5c2ad773e12e5bb28297e239872ef696a7f713de7c293e1a5c78622455b293a20371657c59c9146bfaaa83207727971c5803b6015b04014b7f29cf62f8132a76da9b5615ae69ba233615c367b725545f50660feff3bcd53b5cd5c00317b584225df64b3c704d449c872a349ec3da4323f9150e58f66dffb3ecbb73240e922a8010be0d906a9d33c3e86c2ddb1199a1cc3f4c9fba6e8fed4cfbe86b375159497620ff77993b3facf830794893e58feae4681b2d5c87248c3b58eaaf6bdf395602a7658c739dfa97c4e9594580972cc165bb16764ac9f15f351c68b799877ce62b2ec77a58e85d3d774b868433a72459f1812fab6319b2f09ac18f0411bb73bd4b62fb9487852dadeafb140cc6349e7510f51ab9e1bbef86387a33127fe75ecacef4cc47662fdd7b815488428fb533593f73f6b1954ddfd10f05ab387733f5c79807b3de483e888541e4ebfa6007259e3b5431ad18ef08e9dc06c30d753f4073572d85f76e7c21070f0fef3dbb972394ae327b60dbb6c2b9d361bea3b0848861e06dcd3e76c3a009768ec34e3c572d84453cbcd964edfc65166b7eebe96efa0e2dd58b39a60b36d7bac16c70efc29f466266ad9cc904587d935ccbf194adb2e210848c86843c076354a27880cf47207be3431fc4b222aabcec8b54b4ef9ea638e5360b1420c082970af2b24c8407227bde392904fd1523334bed58ca3f3729093fddb4a23afe922af59575442200eed1a1ab13023db3eeece62c53580c24b753a9b8039b581c684b3352484f9c4721a6ad9442c617dea145561b5c23b22695bea45af58c182e49df102becce96470bdc46dbb004feeb800145f43fa616e727749956010bab4e9f3ee81dbd3ab90729c13f73b991b96bd49eb135a9cd50ed38638aac03067f6ee3cf1172825004d72f36c6490fc3221ad852dc6f786abfbbb7f04262919f527f5b068c4fb2b6a1f72edf28315e1aee400726bfb19c57eff81082488b3ebf7274f0d5ff67bd11d1972346e9f25c760a91fbc00ca66b74bc4e5445cc49337dc8b8452452533c4ae1e72065384ccd0168cca235b0c22913e32580aa9bc81dca4b57dcd6be3f47d624e72a5d738518981c97ec96dd93c544fd9bcd116f1795689d4635f19abc7a1c3d841e43767f5bde731e013ba78256c1f131b7618704618994de3619349b0df1c2d72a666ec77a6dd7d0c411c723dde43e5204f0b7b9dc628d6a88f323e9a414b6f726b38645cdd856c31bc6393d62628f1ffcb491f1df40ff8e745811adfa75a6d72ef023e40c45033777815b38ce1c2a70a86cc30d58571ff7c3061cd8e02d16d2056f4af26bae5095b95782098b412b1de7ff6dc0b4d1dfd3baeabaeeec0ea8452493c8e4597307ff7e86a8b932e2e2e57f48c6dee0cc8fe26cbe4075a959e067245a107eafe16a916727a09f2fb26bdfa1f6c78b6e5475622bdd3af550ead50720650e57e758d57832a2f31797e63112bb7cbfb3b671ea3325fbdbcd9244f7339745d5c04d1fcc0ea0af962c84f0a0456e99d19470e44e9b2bf86a973be0c9d59c6a2afe4a92ffd1e38858fb551ecf8bc1ed958b652ac0433bb5cba2fb91ee372f469b95642493e25043168ed71cc0dcc9b48b35b8c9237606f7848bfbd5e0b724e9c1866c220f16672999b3810a8c89832d459ef80819830f80e94a35cf0d372d54b90b5afdfa831ae6f096fe049c48f291c8c21235689a8353f87c73b317d724d1f5e3a62c325fa9345c1a2cb936c3dd166bbdf1c6c36011ddd86f13f0ff072a2d8e48f34f476ad874fe53559cda202ff0ad264efa0ee35dea5ee2e0fb03072af3144da9e92011ddb01920bb0e928e04bfce6b91d0d0cf0e2f8a0835838505c4836361619fca1700da6866031e1b1b81dd82981e9f898bb8f877b544fce4c049915e9325ab49278f773b8d3732c0f3fbe84409c86ded180527dea020d596801990707e4fd8282038feb6d395e7838841c92b06ba0e74cfe9b5579bce5e06f72b164fc767ae72df1a8dde1fae27343b3ecccc8dc1cdefc05ba5a720de183e772620e0e3bc88fd4d016ad31fd201102fc4a62c47fee6095d28f32d575785aff220e0b26564d8fda9273229b93f6af5f886351bb5de09d66f086b682d1b261ec08ae233769798876fb02e4f2744ba0fe290c97ffb5d4ae178b89e0f4e7c07ec4724cf2e7bc833d06beb915652a1dc875152c3748866cb08b483527c0f27470c37230e274cb511c7dd39773791830fad9dbc197e073ebcae2e3d26e554017d0c5725a9d9c2eb781c0a7f3ccd94b50a20f7e2af42a94c3d626b470b6ce4e62b4254a4f682d755ab9265c2f1fdc9cf40f8730e3d7a6e7926f1838b280d8c302ee8f726310de4e7e2b9fc060dc9a689d0f7915eca7e78ed7425cf3cecd43227bf01869c72e3275b8dda365d7fdc4e2735ed5fe79d1c0a2ec58fc2506f515f31d2482102f53ce502724b9419b1c30b4c7240e588b4a1a04b6e23398cf52b67a6bb67e72a3ec367a1955f3933e1a48af28d1e1eebf73318516f2adc1cf0494927e4f25727023ca987f7f7d64b62a675f0cc87cb7f530668d92269a2e3a552bebcdcbea65c8f931a7e4f31a4e7cddcd08c028e539a2dc3ef7c25766e579523c25ce825a7250733df1d33b94e2132781a537d4a19fed5d616ac28073cded4246bcf3296755d47ea222520f3af130e993721df8c52fc0611ef3a173d8efe7beb87ce940f0509398301133afeb5f1cf85b5ee0daa0828030fcef453f571625c3e35055d65c2cb38712ec28ec811e2b1acb30a84453bd4b506854e06c49abfde9e25a098cc37200813cfd128833c705d5891d09df0760a3ad26ad7683b903dec7b7d2e985706c9705faff3bc6accbb7aa008f9cfa7914a76bd9599bfe40c6fe32e26bc0320f1e7ac713284d9845319c556e36db1f21f867dd834d5c244438f776b091c1b460722b9d45c583591c456632518a2b7d6aa3dadd1ebfa00e07af16fe4407a2f6c5390f62893dd2bf0922a73bf18541a4a56021ff610be50e7fcd367e9c731eb1814dd17e947e6583241080c0d7eeb82c6f00d64e00f2228d02853b4a3e01b943bf13da0822f3327760450edaaf19d3ab773b9dc013ea5c718a187f94a923fa454d3130b22a215de23b90c510867cdcfaa14e0d8e4f4f14c333c41c16d48bc0f8e94943b8f9213a96f149dbb93c98ad9a0aca8800470e2f8bc5dfe67b3e8db1f33e109923225d1e2c3a5d647fc884eb23e60e9468d5f2a6b03fe76c6d40be04bff672132aa3c855cf1eef070dd05c720636759f1dcad8cb1b470bcc98cc6d52aaa51c22c23c330ceb1ab0f6d45c5ad6ab2138c8b6c9ceaebef74458923b138d1434723a34866c9bfc3000907c9baebc9192ae0ed5794bc206fc9c958747a8a9543872648d1c24f93417cb03021f02f7250ac846c0df27e3ed22e7ccb2215c3f4bad72ab65e8a46963127db34b3a131e58e7bd5f08146f2ff199a2c5584441f7517c727c5d17be272c86ffeda95fb75abbbc907b551534e3103b93cd84af1ccdd1215cae756b22a050da1fe0f99cfa313d7de30c9b80a2edb103a4f095da8ee4bc38721f65e684967d77129f9560bebeabcd920070df2945859321152fa28d1291716e0af1c5685721c919be3b433903c2b69aaefa209c9a95434762c4eb61faf8015d8e0cc886b0378f852f4f53d78c5fa33a6b240de4efeadccb4acf81ea0cf3c972901a482c222e3a7d00c48465c647790d2661c2c4418c42e24d207873e450c1616472f2771c60b0649d54a6cccd54cc6ce6f38427bbf83e5a0bb145d6dc89db72e19782ebd65c205d2356a313e86bb626371723373a572bd9b567ec376921aa7265343ae7e1b236712417e779a3fd888ba0caf21ef96b8686d754444dc196d572ff084d203ade36acccbbe52ebfaa0201d77ec31fa536ce79005b51505bb193051cb447801c2210432b32545e43e625722531723f9c23a266141c7fb04f22067216bc2b8d4d1cf53a77872abc2d372c7eaaf9e606f8fcfdd3c26006522e0ad823d602f9f5241a4831d933c102584721117a6a400575d7212cf30e616f92854c54bb23b892a629fa4517b2accc05ad5a941a4fd9d5f2a917a8786ded4b61997672c15981a6f7d120f6a909f4fbc2a78e55da7fd5ff364a22ed2897fb15af5396140ef6d7e86142ac97a821dbf4d22f89d390dd8cd9ca2da67ddf04ea0be15c26697a0a12b71c8a913036865fc59bc7fbc423f5b970f93af9790d4c9dd9eeb5446debbeaac47580c0d5cbc40b5329a3c21f9e2244fca7c2a5d0d9eee6a40dc8e552f61c79d51610613596b15153a956aecbcb28b23e49082ee63e802372b8bb4572ea878bee7adc52e96a8d5388a9e2ec88080b331de7ba77b76a97cb3975eaa35886801d7a940c3ebb793e7522c06b3409575c3aad13b32e4180481c239df1fe727b8a0d546a7326d5153e2c83058bc67a01b132d45e6567e0c602f14803e2577236a075bd09bdee5d0b26df10c765b3623cb6255d5a301e86ae48a04e0671ec7266cfac6112b2c5c8a40e85224bf79bc188fb62186f1bde3d6222fd533bd8375a7c82281f49a4ab1c8e2815c18208610b234f943b6a8960f4007d675cdd0bb73c96c6dcddf4fd356e5632cf73bfa68c2a71c6894be5000dd149830d3aea06e553b97e8d02a123ff258add0e3c2ec2de428dfeb57f8c9cfde4f080d6f6c513ac7203ac3d6c386fdf034c79d7d710802d64fb892e3e307ca349ee6e9454ce2057145c6ef84f7dfe1588553e954eb96944759ef1541845bc9a6571352b30ac451672afb6724911b1a229c8640e2f4eb87668c8205dbbafd7cb242e9bebd5debf4072d38e0f0ea3ad8038e3bd81ba4ffcbb31fde02f2c69c74f6939b39626350880461404964f71f7e922d896888ed8a4ae5a0dc44379181523055bd3ea37796b221d1e935aaaa8a34bc830b0445753f6a49aa88bb998752df1ed0f41cffdb1381c53c5091971d613578d65a656444cb4422074f102c00ab91ae4b4f376d45f7fa243020d710092a57ef8dc6f120a49171e27d4fc0a7e71b9c1a83fd3b5abe3f37f724a83ea8f40bf89239cfe51356b5994866f57f86756b7517e35d69c06c6db07726c5ff54eb202bdde721b3ba7518e99e15d44a4fb6040590f1e32075fe7a1cd6a37eb36495bffccd172be7cb2e4d3e5e72d19e70a0e19a03beb84cf75399d1301995529ee4c79b77fd8bbbbbada2ca851fc3fb395bd9568df00649e25e9409a723f33bfefa45dafd85f381c597e36cee46a4b1548d4739a777e1e04eb7d6b8272555efcbf2e6c30f0123aabfa2958cdacf45b5a59a10fe2c23331ec1c17d44d602841b772ef703185f2d02b99d2ca813fa9ab73354c1cf6074c7790c12c5791722eaf02b20b5e7c38e2fc20ff7a49dee162da8093c5b87bd3592f5ce3a9bbb072002fe7e235d72ed4eb34b1f619e1cef11e9134cfb9cb62e27c4aabe79aec1b7266a435414f2661462dc9d1f42df99de9e407faf1644f819ba71fc5f0988b847206457dfd39ef9497c92aa13d815cf8fe5249a272863c610ffc1f5b1e6e1b113979b78a7f5fa3113a392fe47c5ebeb72b6ae516877e76f1653c211cb4bc4be67200b6925c79a3f99ce0477339c87299b8142500431b70d5b7ee16a2724952517255dd087e8ce4a64b687d9baec5ffba9ef86b0f62dd6d7b577087aa9538d54272ae86490c24a9ecb2f92793b20580337411cdcb4df79e55273da8679af9239e1ccb316a16a671dbb2995dce004bd3d0b354aa45d7a1d7420cbe1dc19f39d51f3c8e013ebf4b69f6dc78d2630c31491c2302b4f359328f4e6af6c8572acd22f40d1716fe75c69f594ce155198d8418433de577204d7d1b98d9437c28d2958f3b10f9f635221403d8fa6984270d4422dbe4e3ba1a36eb0685fe71b62123bbfe153376bc7ec8a5f1471a38ba5962747ec44ed447b71f65cce98339b53273d19ca15b2bdea37c94f50fb9aeb19a4da46b87a6fcc21c0fb38a0033334e2692c6dfe5726c989a16ce13e4bec888ef51bd426cf2263453c562df8dfa6f9feb2ff9a90d48129a487d54e1c297c6fac9e86dce1787a2476b072eaee2c2bd33d7b0613b424c4e7abc2f59dba911b1d4e8fedf0175dd296ad28a31b423f98ca584cd5fa0a972eaab641641c5ba04039edd70af983092fdc927ccd05af7408ab2474247a5bf52f4e886ce24762e80f3436548d7234471ec9277f5880d70c04f4073e2ea705f723b7771ff3b30d8b9fa7d7c8025067e1d29e62fdb33b5956ae0944674e0b9ee7251ba342d65bf0f8eeaf6e9aa82f65547abe16b17757f00a807254b25eb2a056e473522bc915b3b6bc201645ff703066286618bef3b1ce00c80cb14014033676b64a7fed700b1d8f404d73fde564f1668f569b2e2a9f5d89164fcf52843d61f721bd62cfba1e68945afc6d0e6a41014d2f8a30afb442a922f28a14f48ac04d07264531776bda4a41bcb75971aff99221f189ad32078c1f1b309a1f35c57fd2972d2f16325cbc9396f86b2507680554556125f8d4cfecf2a1c95123544f8a1c66ea894a5655c3f76ae1e2c6e43f941c98b2b67f8055b6629d0afb3b48bf4bdb95204b12c21893547fea01f53c8c51203f28f0f84ec3bbed696b43934acfc5ac8729fac003db457afac1de699d300c4b7a4ac648053688f11b667de79388e38886c15728227626689b7da9092439718fbfc1cba90e9d53052b7625f7873ebc770609dbf5f1d52138391b61d1667aadba0307db45b7adc296137e81a86149d2b8c4556f817b1db651d32cd51477ed35cdecf65f2d9e7622152b22b4e1a2a34cb0c23580d7c8319e2d4e3e33bb952f364aebb40c84d529dbd6c7cb6e1677bfe14fa7224bd9dae01e39c3b6b310695c721f581d51190964d346cd1f54f922d37b7066d03133f32cbaf1a8a0123a83377a161f780b2d7d7cab3f62b0189aea0620e6e08a05077d72899bf30b1c9e398d7ebc5d9bebbf4ee4397cd3011a502ddf7e89e0c5d819fae7965041d9fabb48b1acb12d6c02bb99c9f9cfad58625b97557d4855954f36e628852c95ff77f8faeecc191174558d7b7836cded07386956f77f3f7715831d599396d211aa10a7a7a2a796c10457521e3f3cc484df1c14a7464052e0681c8a8962e82eaf20c6657e66d97bc4bb2bc9c8bc63c44daaffb83eb08f12c721d574d995d23478ed601ca8422c939d783defa6b0c75b24774e8f7cf0d3d0e724aa4dc2fe012028705f97e6a2f8adf66dc848cecfe8dc3cf63dc7cd90cfd3a72d0e4c65c4a85984dfb43fe6f3f1a033d79ee2e9fd1c90c8307fa1133d532c803c280e1188f3013ab3d181c5002e560697f30385b39599f095a40fe92b6ea90721971932ce436faa9c7a5cdde58af12305098c6398f1b1e5a3787f89a19b81f72310e2474e9a957add1e4ca8ade82718e8c0f0b45b896f8e14f5522e978069a724b2ce06b499fff3effa0869ea943fbbfd1e2003f1cf34ef3c458fc854876ad6d0f4a526e7c79350882184b5c9c6ac33deab2106bb28fbdca87b5af1c1a8ca772c7b4a3c990d064d97b68a0f32b94cb606b2d32a840644c71ca1d3a10b15a227274f65fd1595a5510673ceb23b609eda2022c7b4349ade590b674aea111cb89724f2e81ae85e61ed4e1fbcab51fd76774d87d2a360c24c493570f7c9095b336724713a09e798abf79b695ce6ead40881727a1642c5c4667d105015927e7f37872de22716a76921290591933b03c2868a22a3a3fb2bbd7a76c0e8f7995a63ee8724d82fddde8bd5fc068a458db61f1058d3efbb4872e2772901f99c5f7d9db3a1a2ebf8625093823e049641e59b0f9095283df158dfb13d4fbe01ecd801923db72d8e1e7159fb647a15137ef46b4adc7b2eaae58bf8a67be62da51df16d196bd72c491994a8f9af6dce1468e25fa3f343ba9cebc24ff73ebb4cf107badb1e0dd72df3af9404b073867d6a035ec96570826c8969df1553122d815ad18597d157f23f00aea61924294e6dd9f400044c5bb603d4fdb3e6e993eb25f1405492dd68347935323e20a1e94eb7520f02b16fc2a578c6587d3a6ce6da450199daeadab5b72fc2af6eed17c5f64c309ddd299b142de15729f891b862c439cea9312a652076b28cfb44bf791670b907e1b2c053b6dfd37deaad1fe18f181743efddd9b6cb67276f1bf840e53cab5fafa3644850a5a9386495a1b3be437899b13a14f8c6bdd7261ca8d19a2fd5a299b1099814f4ee23dd0799851e682e9da9a8bc3f23dc13a727bb5664b1b519d68b0cecab53dd624d3c0dc3f0e4b9c9037bc7e697e28298d627adac39eb45e0ab575387ac75d39164390de80b9fa18951b374ec49fe43d7827751eb6e0c8d36bbd3535658bb8e29db433f8e9eafe9460d042b1600d99b851720a3cac72bf8300eefa7e6f7ea51150f784f964950a1b7318bf8cadea04daad72a61fa8df6a22206973b15eb3e313f58c5a16c5ec0ddc53c71fa9b3a7a356bb72467e643fd790313026cb52c90456128dd789996e474b830aa3b7c5be2bad8b72a8454eb7fba8a7dee5c86e44442a5e10082b97604ad950769fa4d5cd7f9a86519acaef378659bcc28249fdb86d750964f38d1837963cd8b574f57eaf8cff333dcf0f91482e8dc463c5c97bddaca25c0179a3f02f5fedaaa5fefee1ee4ffaa072c1d4b2e3afdc739d9cc4e796a075faecf53d4ce99d19e76ff8cb24258fa2d5563fbdec1913a116d4698b71b6a558f6c40896e41b5456c48ae42345defde64b72755881bc18c233abf52d469b9d8429c47bf07c6651957da6c29d00a5b10b27727de15d91d34151ea221963dc4da7086ecf96c497932f63918f22fa14bdc4d872496f182bc1d2126f34fc857e781ce7a8ec6f4221151a1f61998a283aa8965b728fe66bc78bbaa96fe5b64974cc877315575c43c3df96ae84fd214a1dd9c32f7287c6bf8ddaba88b2d69b5e6b548b2dfa9be8b0876ec5b4fdf76ec3cad7518572ba94c5b000745407d135546f1dfa04fc79455ef09a1ccc825e8e538341d8013508994ce069bd3a1e8234dc3562801e76a6d5ceb748cc93c3c5296a00eee4706f87c54ffcfcccc43c6816172873c39129e664bf644b466d5b121e0debe3d19e5264507969e82d5711759d85202b7d3c62351fec44ef4b5dffefe85a0365ed3172f1dda1a8dd82661a351ec3a96d9a1878b661165d837aeda5d7c72125163fcf4be07a221dc8dacb077e56f7cec17785dfede232a2e6f084344742474ce43490722e371d747778c246c614c1382705af66a4358b2a2de5b9b137125b09c022d5728f9787318f65c9a2cdfb0a1a2af5de9f42e0de74f0631c07e7a680a013f1685b3d51d0a3a1e7ce4488c2f8f8bc7e9656110b1b2b4354e109c297ad426af9b83a45bccdf253d5d2bb9b1c1bcaa262b67bff5bb512584af4b1361b66cc4ee0ae7206e7a40bc029a72db6269e14f38681dfb41c6b5a53b84335d5f73fda89bf4a5767eca8db95426ce69d24517b589b81d503719e676f2626443094b1d1a42048725de7a30d9c32c6bf67ed81e9c85a06754da9ec54b36d5424affce369d33b6572d8c4ea719613e0872a42fb9e3d66768ac66299401cb4bd8797cb9c181fc46672addb000036e7889320971c64f4a783758fba9087a82af610b14e3cac3f588f724edbc45548929ea5b27871dfe4db17fc7982bc1541422606c519ca5e96781d00dc5b76bd69a61102c12fceef1969ee2f12e7340eba41f3debd34fc359a6de146f28525ecec8fd9977bc2d85900caf52fcd5c9cb434fd776356219bd529427972a4cc430d045eb702aa80c80fe9a748adc901823602c5806876b943370a536a6e26b7892038ea7656d41b2afe5ede7776daa774d1922abc47dab3cb60604a9e723e3482a699b96b439f1201a2c7356b2ceed36c6ebe002b366fa97bea5decf040cd43eedcdd2bda3f1fd9d274caa55340980e7e78b7463bf62492f6f4b698b372d9727710082bb55aee143a7cb5caa47dc18f49040587ac2eb2c59154bb20cf34fe37c6d3a89c5368bf0d2c29fc05407e5879aebce5a5cdf5433e87c6c6197772512e7d8beb018d5a94ccbedf4210c3b663c960f0c6638300bc9239115805334771f85b89a50258409cf25a043352a75383e5fec72770e1d5bc5c83c76ec3a8723a855594e600af10029c03ee5ed8d6fa50c3bb6db4d55a4d88fcf89df2915172bd53a02fa70f97e6df380ce365016592d835d06364bf4e7a2550a4589737fe27875fcc60e45aab2977c9e95a84999ac299661aeda731132ab45188d346877972a888aa653e09d51f976b1ae4734d2c68c7f1a971eac9c717cb2fbd896f406b71a0909660d8fc76a7ece47bf87647bc18aba590b7b03dd290a04e154577638472469652321808dbbab7decae348c250284626550817ae05155e1caa34abe4335f43596af42c29625a16d521246a1228cec118c0855218f8bf1984ad80620c0c72373200172722b041cd3eaa6382348beeadea1d3e632f608512b61c431bc649725891e2ed84295b8fb6eaf285302f3e4e739dc3604b2d13dee20fcff5991886548a211f071365775e18a6ee35ae96875b47ba229ca8292fd1a667f06790fc92725507995c92526f2af6ea7c93a09541cb7489185e0054b2d95888dfe4c6699b262cef0a83f8ca4013feaa1be59dcae29d67cedd24ccd341c60659c438ec5251494a1b0e9d6acfa4f6abb6484c0e34bfa38f9042e7938335b1b67b74819b31ed724c8b82d70ef50fd8951ea59513102f09f2e2c8d0d0d6307c2380e959dac8f82e14ea2f6322487b5e41a8ceb3b18eef45a2b960ef4e237001a4358e64c37df6726fff2aff25d5d9fa7efd60e0394e5abb30600d10f04b00b5aa21dd4299332f725095b750204f8c6af962fc1844883a682420cd258d1733d86fd070a68dbc51727a3041fe209244a96c4618e2ad9b970f7846d1ec1d83a1ec327682e443688858390f9f8277389f028f5c38f395f24de2aac57b9127cd3f8fee19436084ce673ed346dd4bd599a0997172a2c7f2ac026297ce0c22493f8153dec83b4326cc71729b18259e0cccdd064f7ed36a8c7c96c3725ff9e744923f23737c5a30eaffcb4f4959742054bbcdc5c2b93a2da8d3ecf3346c70e9e7975d8a3e321c4baed504727daad350fbddf59058d264722008002d0d01b33ff0aa13e67f03a92132cd5736ca2ed144a22f0c6de1500dcb0d0615d06d1e8f8a746740b4f849a79ef78a9e60482af888704c3f42798851aff3cdd0e780148cfff45ec8ea50c981461b2ef572c94cdc9d0c13a0f1917bcf0b7ab0f2432eca675bacc37055540a0a107d52fd696357c265d1aff977629d55e667e05ec54ab2b41186a097e6de9ccd8f9166c838b33033d97f11a04efe4a8ba06dcb5316af078341bf327663ed8423ab7bcda272c9bd7bdf32917e9f301f59ad4555831c1fa1aef7ffd14b78908639429f67d6722d0975c3af983a27b4b7c724b13b21de6c45cfd8694a491e1d4a1b2093f4e472066ad5dd0b593dcebb1b6ee691778a9da323c5b405dedf72c3d4b2c7b00fb864fa9a0c9ce505716e5002c90bdf8c9ef887b55080be6b8602bcd1e804268bb661241a804f6ce090ce1de2448caa24b5be6a9aec08208a519a13fb502687c02c727b50de47c78c94a923f2fc4defa4f845634b716d4d0192068b81ae1df267f57263607905ac48b7e1cc4986dc022fa7ae3131c1831938a8de17a3f3dc54ad211dc845b67631b9543b5954e0915613cefa2723005c5600a767ba3b33d45c93dd3922912d6468088e0a1dd00885b98719afb535219f455472378ba082ab89405f544e5f489d672cfd9b4352900373976bbd773b9b17cefd0d14911c6854bbdb6b2daaea3334a1b08ff0e2740ac79b0a57c5fd9c29ecaf4a34d5657eb9bdc20f1f7230a1f8cbe9e142687c2985ba183fad0ad3a9521e251e5ce6aed03d6c95b22327231d9b73d3b2b9afe8c7ec2b53f5f2f89f8cf1a42e4f1b1f62164e8cdd5fb972ccc597ba4e290dd1f3140c325c53d111b2c39d1dacb77e024796973b6d741172c327e5a5feef53bf07e9d8fd7efcd6b24fc67665046cd4eb5289a98bc6d7b9727a4331e10945789f3abeb114ade473972f6b5185a1b0e5deae90f454119edc72e0d8cbc01d855fe8bd093c9d60846dbfcec7c16c755dc12e493d7a1935685b15f4ea9e01f7f45c460f78a7424cc628c6b7457967d303bed35c309e8dcb4bbf18ca9d690dfbabaac36295bab2f0faece5ee871c0c6131a5e303d9394e2b698272672c9a07a52770ee195bc7081d5bfbffaec9b1152945d7abc47330c90adcfc72558f7e4ff37343aaab6661772729bfcfa48e5b2ce517b9e78bfbb0567840f87250e0a6557611103227f15acc2d1b93756d0c9993bd8303b875531671527ad672f676d3fcbbed85c6cd5beb35a1f883ac7bebd563666479843fa83f3eda908572be453a2740e693a0d65656e6040395f76cc9d50769cfe3b26ea7a8b3356c7547a103d0abe0ed3fe4155fcc0cba2df3b203762b1a7af0ccf6f657b2a5ab10d8555627a41e792ee1c334f737a7fc1f9844992db8274ce1592fc1715e7f1e1ea572d48d55a5dbb7bb8f0354394ce954701a52f83814d0c0f50c11c83d7e86ec2d72ed551fb781894f7be6aea3ff4a5db497b9bf9bababb1cb63dbe1f5cc22cbf7726b91acfb74f99c8b4971ab0c737a1322be0a7552df7487a154d8aa1a7b03dd72f9b1125f582be11e385f3a0992c5d6f464ac4b3f99e78e3ada452713f7db00721da688c22932b25c67be24848c10aef729279c394d7391fa5f2a125af2e294721fd51bcaedc4cccfc15d3e08e4e9b9bc82d8a51015267d0d0ed8bd254a25ab501c9cc1a4d31e0fd8b7299cb897601061b646f292a92b77a9362b2523f979d870682e2562fd316af61a7947c517fc48987376093fba9745378810e6ebf15f31718ef976c38aaf374259004ae825e110b77e937f834220d1ae6c43391a1d9bab720378272bd0f7bbb917f0883780de1397d3cecb275a9eaeef527046d93a2db94ae4ba0154cf972b0c1be55dcd82ecedc9fa9d47310bebb0652e4f7640d77b47024a529620aecedee1c91641b3f6371376bf44b0a68c40b9f0088f5e83d4f390729ed173510ffdeeb9d1a4480cb1aa4375c8f545488fd175163e542fab0fa9ff72ba412e300e3e1f542555f6aff53911009c1651f8ac9938b00785e75d14244c7207aaaea445863a726c926507a1810b3e62a374f5f4866a84d94040c54dc2a872f690fa3aae21bb233be84ad8fbdfc15481a4f72948636f7c07fb6fc83b45db72a6af5a4a9b0862cd20709089a83a6dad7bf76758537a840373597e36423fc1729f905e898f92f234b0d9cb6e24a9e7a3dff4f7a5c3be981ea6c255be69ee3a480c33849afe0ae4482985c155193ef932ba85833685a9686c8e463c543c7f007280aa10577e19f4e10d86dde3e2577ea23b3bbd6137ad8a8ab75cc581f72718272cb4c58b76b43dfe1557666b919917bf164172dc9df63c87ce05068905bda900ee5bb7970da4528252ba7ee8eabd4b37f82597a77b6ee51fae1b7d24c4f4777285f177a5ef35d51046a67993ec589a3017bc164b2e55759451579de0c101831a0097e6838f10458b4e2e97ba636731d08de305bdbbd7b917cc2cf1d6e9f85a72aad0ba97ad6cd6ebe92e680d7b9584f8531eff2ec39e05c5bad1ac1af909f97296eaa7dfeaf495acea7d0cc7cb74af5468a35ef230d6b39a784fa9c5abc2787273296cec5464a0b452fd6085ad1ae5839bc468b4e2cac1f0344675b78702ce32e992407cdff2de848cf1a283131ad2507908b25f5a3f7c113456deb20588d402604d9c84bc3d48a95cc616a398d6e82aaf79207d9a5036231acc1d197a5b0f72e7651dcb16f779cb06520d5ba8d9804c7974419a74dba2c0c364126dcf22bc3f8f596b39c0955a20080641ceffb507346112f84927ec3119a30393940d3b823cee2a621e4caf5a2ec7c6683aaf0396b7f88d9032372b5c8688fcbecdee03af6802b1fa9330e47a9dcd837d366e7e1ad2d22f27cf3c80c11e4d7a3f6ce5febf72ebbc8780275c946f139060780effa8a2f546705b6e4ab0f8d927523b4bc4945b3caaab6f8bfbadb5bf5ccf0e839b4e4f9d6742c3498229606524a1756987d8142b2b8349555fefbfbbb1ea60d282dabee0a8aed71505d3e4279dee634458bc726e29f51079e31ec3c51979265fed974cf8b0f2ced64df3346890db86f4a05a106034d724861d8f4b5fd2bea7984aae84fbcdf94d683b62bdb7490c1610cdce67d6517357f9480302fca7e9e305e7230a0a8adc62d17348bc0d386b3c38262b4fe895bbb25956d84d0b632d9a99d357e58ef2f910f1426b6d67dfbeafa464b461f650b2d515061aeb3baf4a2accf70fff29838d4cbafc1fd2fc92c0dd171f2c72951b024d6a21a1a7ba845f689c02eed8478b2377d2f74efb0cdbe43351a0b4356c80576b6c407a59e3c1bcc8ed48d1aa68f56fa3ac9a370122e2130dd2cd7b721befd0133853b61d27c62731a70133c82e192b049ef89ce8c814dacdb71db9122137cb854ae29966304f4515117cd1f450ccd693929f594c01e16af534774472590700f940a9ef24f120d4343cdebbc4596e6eb9fd8b82cc17f30e409bd81972ac2e3a5baadde8efaca8c524f702ffb695236c873925304f351e427066fc8322453b2ac77e63911085b0e19bf9fe6d0f1685d73e0e23d4e1d795a1966cbcae7250abb2bf952a66943ae1d5858a5baa124491a07fe558e26fb9af44230635916e08144160663e95905e6ef41f7f0f530e2969a7a129e95e9481c71b40ba41b072532e2053e5a86400a2ead990d25406525a30180fca064c3a2a8e652d75ed2a5e4c38cde779b5056938e90467122349e38936476ef26b0329b2ee2f81ae2b5772aabdea7077f45caa89def7c69ea9adf74d0cf7828a24ce314463376f3392dc7264b41c8f216980bc51c7f8fd40df4c0d02df5e84b4cc6ddefaaeac6a5fc47f7202a7f8bf852239e204e9c81e0b6af9f4254b7d77ed7c113bf4634d2013b8492291b55e25d7c6f2a36b211b649f86dc6a88732b302f568a27ce072add5557cb0fcd2f1a0eb73855668299248b64256ad52be771f0f889f79bba695b7ff14c54265243a01bde48b0aba470e589e0629e973017dd983ec52d0d054c0a1f140e0230af07c3fa5ddd657214f437b45f83ea7ab34bedde4e6215c12b549b77a0207e7237f5b4c02e14c99c5dec810377ef9a8b5dddb164f49c1844ddf1e1dbc0c3cd5725c4e95daeed3823acafa9e73c255b1fb526ce89c0ec2f2cce8794c3fb1962722f66a796042032a0b4acb46fe38f0d6945f38731300d9dd0e3f6973412ff1860eb68840531fbe6a00408914ea73db3da3af480cf54995cf26f5855696a5fb472a4aa68bde4b33e3524da0d031d64e3c8d0929e8fc22e40897545b8f47cdc0a301b2803089d3e9ccb33a82bed1105dcc7aba16ea6f50373182434f5f2d04a697231b946739f3cb30e4b0d8de27392fb06155aecd670c454eafde4f229a56367727ce1a82c1d712d31cda7b26f2197cecf181435bbad58e4b6d424657811a551725bf1ba736ae875b371b9d6cd7361e0f343be0d088223a006555e6d704b284e40016d8d0e8284bb6685e1a577bba20d6e21cbee7e7f52c7695243e90af02d0a724645c8f6379bc24037f4937a88d1908c616ebfaa5012f69ff6a01e7828021254ae0a1954803520b44f67ab231ee149352169ff0f6e221c7027e4d39dca34c8727b048a291b445a79c1c47209c5d85eae6cd50038fd71708b3b1f8cfefa4f814a2a51ac48c024e5f840cdaf5435c26159b65085c88330761ae8976f07bb5ece3f221e2d6b8bc38ca68c07140a8350c483377bed243651d4dae7ef79ff69ccad725616fa7c4f24f84c07d55ac20ebcf8de1ef1d81c66e82df35c5746c9d9904d72b6ee30bf1ee2e0def9e86ca5ad5c9d42508b3d05a822730c7af8c1cc79f76b723ac1e8e02d991062fb91cb2a9c67ca3947393f56a299e14d39d2af523a37b07299044253491dd6c5102bccec9bea6b1f919af0b9f5e0c0ca782d4feab60465723027ecb036e8c13d4ad6e35a15cb39a99b185f2d8e748b5440667658c82bd5382fd500f9abbb823c9b14d0ce2bd142ed4fab5803951c5b8142df7816bf93f456cd207be8861e21d90dc3e36cc461b824556d5c85d7ca469eac65ea1950faf772d26e5e5ce8441afcf1a0035ed667c0f20c839a9dd50731325b4baafcd0270f7279e11321beef0b02ba954c7f176302e1db609f2ba3e0e386fcefdce21bd4f524989532ccc7104e5837efa064a2e57c0fb8d34dd436481b5fe256757f6b47947215a44574d3597b6d60ef41e6ae8fe0af3f23c7d0aa80c6074a1223367d73955c6062562c757109a82ad89077319c5cd828396e44c2256f55033398d0e4036172020c00cd1231c7772ff8745cf8f7709255918b32cfa0a07cec47c2e8c9bf32210c82537dbfd452d676a0609ec06a1897babe3f64238fa25a8b60344d417dca72223fca6300d49c44e30f12f71e34d8eb8b3b0d3a37680325b2fb4710fb31f3721f15fc9323bc208b7b8c5ce572192a89d2fe52e0ff1ca577cd1dee09ee678451ff53ce78be2e5a1d18b95ffa819497dce117986329d99e975d9da5d9d72c4b557617fb4b86e17dd56a162a21cdcb0198f50af0aacefbaa41c14b6b5f14c4c372b053226625d0a96d76d765808aae2bcff6e223b34eb9ad9df414ae4515487972fd2ee688eebda93f71fd5340e94da94fe15fdc8e4d741b310e8d322f0716ab7211b8c3e6aabc5588be7705c76b62d87db061d838ac7adf8f13d52edb257ca172e01ef539f5d5cbb6a8cb57f4cc95450842058b75276b50e0b08a5b55206155116bdb248f1b9dd2a08027ad6a940a2e42f03109ff9ada00560fbf796ce3426320013c6cf10c50a1b5d6dd45acc22feb27ab813c62e7fa1a5d4256d0293aede4725cc01d342867fdc9581f981f83cffadfa7b3368006e8b5e19f98d41da9912372d53b49c5c92c6091f7c86327cd3f4bba7a765bd1bc26495c90afeba1cf65ed19af6e1e8601ce479dcd2443e423430c56c5ddbe19ddd34bf29487a7cfd5ece972c497361a112389ce3d7c78d096dea863cb2987a066ba83ad32f73eca34de7c013f4dc5983ff1c8a31fb4da524e2c8b81f5f633f6da4a26511d1f66f7d28ef77296484879a9205f87242b8022668ef19ac674b985b5f5716587f462122b73bf72d8225188236d0edc822d5decc3e79663d7d48b9e5ca15084c42524b7d2d0c972d4c34bb51da9498a17222de7df8d22ab287a8ec99fbd8654e0ed4d6498bc9d57e65f31f1be699cf181ce0f221305fe6491e6fa28cf08b19634c92db057657f729fdfbc39aa29722ca67e4191b7d94b97a42151bcc1009d7037fb30a9303ed4615cbfcddb75285e7ef584bbde039815cb6ff77a7478d0078b454e6f48a35f5e721f010538382e1e8f299ff19b9daf08d1611f1371c0ae8bea2352ad7837495772ebf3b270418cfaa276d932cd41222b81b40b7c551ca616aedc2a26976088a0727f12f3d749baa1408e92f88f79090323b6540089cebfdee4143221576d108172c8d4b85ada48c9ce92cec674a0bb2539d3a5bf15c304d2949a4f569c5f64e3141f8f1d7e2bf99f70bfbf6433711e73c10e1b8d68bce8013ec0d173768fc8fc2a4317411da69fb08d302fbf706fb5cdf9a2debb6cab50a1ec46654274bd7dad101745eebee460f204854d48697659ee33226c2e71b41575691f327fd90f34b1720dd9b1f7e0ee2cbfe74d6dd1c968ea0d1f87ad1adb16fc3ad296e2fdf447a557605c4a6b4ec75ff3e64734e3b3f998e1fbd06023950978523da4d86aede3f772d1f8e0edba777877e3414514be8cdc520fb14e039408b70708531adacd483b2c97ae7566fb9a472df0c82d72aa592679f3e13ef37414d495531495d7673f5f37b1b8e47600e2d09ed388c7fdc246c2ddd38175466ffd50b8f81d0d12d578b33a94a6d8d8018e58cd57565bbd7960cdc9c02a4957574cb31898b26cd66560e272b3fdf2e0bde49170293bd43103fbdd1952cb51dc4d69d2ac93b0afa9fca11f72b404c32fba4992c1b46ca911517969768b4977305d48a9ee6381345ad60e195afa9ae0e3d5bb7e00297e2a96a617d384170bbd2c03f6f91012923f83f7bd5d7210d98bcb612bd5cdd5bff107adcd4208c5c61978c91a325ddadc960f1b3957029f6d958003950617bdd2e7b7f7b87b68969c9700da84e12bd0a5a2abfa692d720f6178669a7910c3d874d49156545cfc34c8b47c0966441dc979d77515deef72b9fa8d9466eb196cf82f72640531ece70276f578916c601a36126f911faaa74f3035b2b4d8eb8ae5f71a9c8588fb0a667ef7c689ec4fcdcbbee68218fe42fd1181413b032cab8d65bf33b9ed35baa845feba82207653e8130766994acbe9ee4e6208e9395d07f4a2b0b7df638725e8c95f408077d2b0eff7f77261337ab79b7267cba34717a7fb24e9273b11aa9d1a65a349a3b570ba83abc21b15fdbb297172834896f4ce7cd4b9763075b7c841e1d9b0102d59d29887deaf06fa51ad7fad72d1121a5e3747d2dfa3964696a037501a879b862482394e50d7a1081e9682626a09ab86f4bb33244c9c6cde1a1c712ba6479231102cda70e0075fc20cabd67e6ce40dd062868ed5bdcf2afcc0dd5fe58307cc701f7a79fd5756b22c99fec1f0728619352af31accb4c80d215f09ea5452bf67ae660b208502c486c065c6f13656033e1ffdcdfbd73db45531a6cfdd8bd1ad24f4accdd98a029b3181c6da301917a4dac0baaf502cd53a14699d90c065eb3e79786fcad38baa38d52fca69f0c8729376fef8b82a491d202c759e4425088eca4e730066cfbae8e61b9f3928d847721a2e14ee47d81110d5221469627c25844541b0a5290c074369de424c46f88e7241719963b751745999da14cfe9ee9f4bb1a1ba3717cda93bef1edd6933d0497265506edf8eba5709a10b18191fa825c97b6294f0a7b4cfb5c10073a25570c672a10ca96a6287dd4846661327564eb16274c199b776e785db992d183def058c724640d8d2fc49c9151d3c6405de4dd42cb7f059373f177c6bf73a4f161a9bfb7257bcc49c85bde420f033278338b1da44fb77829f22fc9ebc19320492f2dca6299659316e98c68d481b3256ecb31013a090fd6c0b5430f7378a43d1ed5bf3d1383518e4a7a427523665e9cf5e65dd8e0f286f9e8d615ea7043ca1309f70200772b2c542356857baeb62565cc5f800704d16ad6e75a9587973e94aaf8189fbba7270ea0d390be773074ab365b21dacff995204947e47a150f9d9ef728f0940e5725a28b8b444d5bf8094a0c3a8034185457ce278112dbe79aed21c03058e19345197683ae454c70ee14f4ca3d950cb687c8daf8cb81157cadf922017eda7862572a0156ebc9f840e42b5cdf52de496163253c908ee09c728f4b65e755039fce772e6b6204f9aaf3d13940cef30d16ebff6bf04cf4151e24a971d388f9da63b083e5c22b6f167d98bbd6cf9ba7257eebd732ff5eabd96780ba7be2ac6b9fd946f7251be791864e58d54433094d2f6c888cc5cb41ae470c6b3d15a22352e1cedf3536fe022d3eb007e5d121e569ea4d60aa5f7b2578f0089aa98c434f07fc9116d7271814ec12af58bfa93a090d4da084c16d1cdddb0a152454b5172ed1bd133487231814b89c48f3112234a03e10524166d1b26d905bca966a3a659c9a62982e75804efe740763a7fc3e9130a13e5d28eab5252f2719f43f916f38e7f569fbeb272268c4d1c4c6c3bced602929af6c083de23d0287254c77910adebf309f756c6722bbc9c45d533f9d4881d4e20b17832016531bd2fecc9f0064371d1f849038b72a800e3e70986a5c17e100b9cdb40f775bedb9240a30bef8ff8ba2853c498760e98c3eec1fc6d341cd3ed6acae1236ee661d331ad319003c2a4a3b542a5330030819d2a97a1c0cac4c7c8bf2b3226c657fa23e75ce7c2f58e14629a784c59eb72c9ad471b3712a08e2b59759f94866a04f63b0f40d96108f39243815f3023833d2997ba74b2150bd5e3e445fa906d7e5e73c01840669dfe9e2503ab8b46059a72e42773c9a11ba8a4e072ce51509546dd4a1cfe1ed6f9165d24c1d2d3c6f57a723aa736243d037f25332e057f12072a010113c41c63c80cef7a307e5f75c9677256768e063787ce3b3760584c5f34500424d1796a721a349238fd6b2b1e92b56eee5ee02a6c9f48e35dfc99cf0458670779eb209316102f739a44d8ea93b62d7254a594d40b8db0f58891292013c7ba1878978af8e1f10653a770b229820b1d7219a263090a07852cc244b38c4eb53ec8fcc75bc3b22be2b5a276585b5dcfc25d58b25e080155747d102d4d1dc4ab886fdbb4f8cf9a8db65f7cf75fb66b1b1172eeab874e855dc8acb90133b2143dbaf8e96057f3009d87b017ac0d831b0f8d626dc6619eacfe2262a1b7f93e34461162c3c15441a5c0f20a5423a5cfe7d2bf4f42d8be82b39009efae7fbe4d1acd8a133b8f635c6aad0265ddfbdbb09549077215899acbe1096b76475bc2e4ed5421a305515badb72cceb1bbaccc986c61a572e3c11163e75a4b00a5d64da78239fb6f2e1d0fd2068365853055763563f1837203f4f12e99adb8173b6c8dedd3d26715dfb3a3e5b66521aa1e372f2fb2521c72cb0e680a7b4eb3fee9a35e7135a02a11ce9b0688af3414634046f6cb1878e01b852655ba245833fb99372eb84845cce33b0209b3746918e5178cb97a6c05ab72db9a101d539a95ad29ebefc637df59d7d0218e4353e5964baa673c9504137f72bf12d1c6e4d2348b898ff11effb46dd7eecf8a385b9ed6bc95aa9304943b39723c8f6400fa56133581fd08e808ea660850d892048cf4923d04e004a7f348f272db71c12229c738b117b77710a8bc472130e865162b0274b91511f51230515b052b2ab1b635fea84f18ecafddce9462021c72012fafe5bd845a8dcf2edcd00072f838e8cb059d04ee3d580edc6665836e293bc39726b5aa587c03b1f603cb2b0e6307637f16375606eca50bebb7c7a03f5410d8eb685e3e2a181321bd83588d0b1a32f9e69713c91ad3788d376fc5f9c41b0a34ef2bd80739dde489f9397f8738f380914881de8399a9c00f0d501ab3f4b1c5c056a31d3773c6e31ebc845b897238b7cb3b10b127ad030199c4a6aa9d76db7701b05c025a9ad04ae3f0057f337263f8c7236642f721842e2744e6c690b49033cbd92503991b942ad01389fbf70b5a84750f25c83ee3d1684c2f83402ffeb547f3366c8aa42e51210f808507b3729a207ba87e040677e33050862dca2ac2189ac702348fa8af2120cb6e69c038722610b87164f4b28d0b10aded6a13a6d3a8818083fcdccf08bea96e3602b2eb404452268fd3906436cb651ef2f4ab85b0123974617ed21ae98f0d6a1c08013849402ded329b1fd948dbdc099edf7dbad3c4a007555d782d99d0276bd1389b9f72f0ab62e77a9eae52461229216b231e9bfb8c44c3897c36f69edd50a45a81e372cdbf8c2808ea71a44d5db0019f8d6851c948aa9b7f61c6e4c1055bbf826fdf7268976deaea442f2780ddc2e58c373f14fefb6c247fa4b85b7b81dee13946d92f26334f40ad4597ad9806d629ac4cc38e6a2b6a3620249b3b0998878d5b0981727350c28b3790e9e07a49061409d64730490c4511e8e0a1ca683c6b6de7ce5b72f7143db8ba9a3a1d86cf6b9d78841b8f049594f890f9117f158a58fcad669472a23d646f51c9414445ab3af575ff2cafdd8629c8a56b3979771c6504b9bc56722022e8d691cc6698d21a927293d64d63db3fda6d506e88f2a053fc6ef1f170592b1b5a7db0a964f94d720a5be3934420f3923508526c39dafcaf1ccc076d9e72ad9eb3e33247efe2e5aae73d143e67ebd084ee808e398858664bc11a286cda72360862a2621df987440af3cf7651312a222286645ae456c9e1d6abf24141f872bc50099e3de2bb336f0913952d2244b605cbc5470a1610717c56e1c2410d271839bae78804632a486b077d623a6fab6e510c20d54c427987e44a759552a1b96c10d23fbd56a4915c96008849dbf481d711e86ec16a626a76737b230e1b5b1f72d3afaf96a09eff79a1814aef99a82b40c88dee154761956a8d85ae561f18227274e3540cfab6d8986b0f07651ce4389bed1fe51d9e382204610a33d5ea64683f9c13066deea242511d0c9842c72325dabac9b4a5b7a4086f226b54cc08b8987249f8d401169883857b08bed6af1a2c48fd08610533dbc94a4f4cb3b01342a07207498c75625a1ec37b64552c60042e14927ff20e6dd1d2bafab0bd11b0d1ab727e70746640b17b4aa240fbaf12e3d9f1e1341ec63d240c7d3c316ef7b68f69156515238a877f858d595a4b95fc8f79799a6100aad6cd603fca8d6c1eafdf3c078fdc282773af4fcea180628121e04ad17856aa0c1676931c9b50f737f763c940c52f019acf1293aec9f59c15820649bbc8f557d4719ce37754032e5ce6a550665e4353f19015dc71b959543a14d74b1f98c30074a88e6f1ea6ae4afed7b139727a9325482478f06d5f464b38b3602901df73daf803f36f6747dee3fdb67282006f5681cb402bb0dd9f243fce7f5a3185e248a810c11211312b804781d45b02559d87e1b07a1a512d18e7e683c29046e448b78e78a6950596b4e293d6e27439729fe0159e07b022dd67d4490034077be164660d4f0dfe297af5302f8db6baca7201917de070bee610414b0ee007e0a7060f481fb0dcfec3418bbc375316f7eb08578e5933f59a140141712ec238a28d854a4b1fc915ab38005009cebe58e31d1bde2bfa052f2993720a1fdd3898f0217ebd7a8ea7624e42f8a74f5713f4099808d99f20f7200e9648410721f5241666e6b68b44610ba5f4843a9f94600025403204d50d0a68a1717bac89873c941cbc52ea6206c5f9e0946a19f6a82b2695a972e6705f6bca2ca72ead70792f003a14e0290f03ba4a9773b0f966f7b09b7f121725b06e33584f9823aceb7288d867f75ac2c68225d71926574ba4ccc6be6fdf15503b888ba989a5adddc5a5cd2ee9acfcbc4e9a016fd3ed5ff6df2818313fd2076e6f4bd3e63ed407b4da9e60db7207f7a658fb0aad633bab1c406ecea8a2fa72362dcac7319b04b4b625398ae313413fda5864963473a6f9a850d30a3da790683598e1dc0249b625d1fc6995497982b257bd97682bb0fa297124f14380264372e8885434f05b8773d68a53b738dfa764b1e914cbf4cb4a076183b6f176c32a72b3b30f01ae142257cf885a643905b5c48dd6f1d9ed101174e46ef20db19d8772d9b90bcc93db300f9c57a8e8b777119652bb42f84508b5c6124a7c1b416a8a72eea3c12e771af0dd208617a848d86563e1006955cf5f895eeec355f97c7911101aba992e29604f4fb23a34ad21a878e5201f142fa8621af0c9a62a4a273dc572114664a13a0597a74cc264407f70e65fd327130707b24ac13ae6f8bd2d706c7293780e7af63ade2c50f6db233c3e4ce0070b8066ad19a48d247169729c267a1b28d5062430ae04c44006d7fcf0249611e408518ae5f2cd371509b4d8487c6e2e412f5aa19ecdcacdcb23979445604b0d4fd8876823890f1c9600d95ec694557293df6685b9ee56560151aac4588525002ff2c3bd8fffa4db9fb1d21ad3525b72ef85fe73886c5386b1da5c07c7fdf4ee9879bbf9a381eb8dde49fead1d167872b408482b369a421d96ecd01a0259faf815e64ffb6027947813d1fbad9a2f32722097d2d1e5a97ec30f6acd5cee6587c4413ee7d978bce67b17f612d4bf9d777229907c128159ed29a4f6cd3f8b8e4477bb76bfc4b3817370a899213bb32a117294e21089d86ebd196e550074f1cf4d47e69f1e7430a5edddc2713e3cc451d0712b6c3cfc46cf2f6b3807ac88be77f0b1509dc3115d575a7a2df27880b321f1723671c60c736c984cd14cc8095def48d3d693447723797581aa63296552f7c072ce713171e239492feb3f9051434dfdcdd67b8b6ff56108997bab1fc9ef2859587f65b73fb11d63a57c9988679e4a723b3cc44366580ce16f169ea11e735f6072ee1566cff8a41da45031a1856e7fa59ee97075425bc9668ca57c944db8ebe84ea15b27a092fe914d78d1e661b92af03f0508d4836382e3c5eb080b633d121e6b259c19e1b4709df42d61b85439189829ee0e69825c83a7401e343e587e468d343254468cbfec7cda0a7687f6c909a94e92f935ca7ac8c0ece9e4f5174e3599724d9a875043b314daaf7118510cdc5e4b10a72c9e98fffaad1032dde531fef67121744fe49358af81199e36289123535e250693a624f8491146f98a2644113430565035534f00052c5976eb6ffa2915b3d89978fcff97fd7711100003d62069656df0940254adaccd614e4312b685e344a5f333b5653fb50a6383e3cb589446720f8dee9f50c762dc34d2d1f8c4bf630c9cf70770fabcc525284739cdbce1e261f78293b49a73f574208772acc0bdcfe34c6a2fd17a044161ba00a31a8a2e45727468cc165961cb756255fa8733e786b834b03de8d78ba49d91c2bdf82be918727a1d9f5f1ebbe0bb2a19be4660ff03a71f5d4e2d885fc55af6702b9dab5f6532c3a4adb25798475b3817bbfaa52e7dfdb880b457eea26ed2ea4980ea608ee872af1082516361cbc6f44a3026adc8bdb869ee0d0b86a3e2612bfcf8ee4b796e5cc64d13a410926c90c6a1e04ab273d2cf7769ea8dbf91c371bb3531f7fb1140728fdbb3a8d9cdf73a4e73e2ab881a46d106c89e66662a98d88ceadca6b6188572ce9dbd9919220982b339615fcb8666e720142e2aebda2efc67fefe599fe52572a150b7687edab0e3016995bb5dfcde17c8e20d57f743628837505e5a64577f72733d7cf8697c20e30f0fabbdf73ef04a0282217f88cfb8707495d05a7fc44f724b366a65f95cab324ce922c1be1fdcd9165f91009026e16cbcacc3bb8d70535abc8054de26dd26cbff27dc6f593b5b24bf5e2a5f312f2de05287caa63ce5c872fda3ba78b8c595a718ea7491b31c0283ac606650d013426cc87ff3c86fb0b17279070af85d17ab1bb4e4a57277072ada65879ee752fe2aa436295e5b3a88965a1d2122fc293cbf69593685aaca8e2d25bcec95bc7f794ae098fe3322405d4872c62dcb6362b03434cde4259f151d0a8c2804d0ea36279602e463dccb37eb28049b28b39a37fa42e16e12b14e1cee6fb03ec1f006656d5284cab75703395e33721f29765aee9f01757fd70c550281c918b441943144360740f855e41f7d7a7f72ff2b9c91ee4f068af7c3cfb14c8b599c256caedb9bc91e76321261316c26786f67394e688ae2eeb2a09e096fa50b6a76093f0b6c494d1109837f64ebf9bb5c7213751e7e9f1c36385c4dabc646ed15201846062b758afe70015af51306729d728e8258d624bb2e777dbf68073d3a1b9f39aa5f83b35222f2fdb1f5e63e302d720b1d59068717c0bc841b21f6cfc10e34eacb5ce1e281df43c05100cbfa5905728457393fc632912ddebc31ac10d1c1f3c9c1c657771fbc41cad3df7d18c93872101502743bc1144ac4233639d650ac6db1494967f5a0f1c06478c44b18d2c330e09b0f3721819f442a7328b29acc62fcf333de34ebff81de55302bb3f4d5e072f6866b5f2c0d6f46ddfe365028c87adb8948a0f2d8aea3a54344a9945b90dc04085ee0ef9fd33c5f8336146a14e83629eb548ac7cd5451f2b26d17ef9a7ff372c57c11f283a6d29458d689c2e8be705b946de706615e224cf77bed5f6cde8e7274f42905c3473ccd039e614d2d7b286def600d73fb493267d9e35d5128570a541c143a930a79914555822f125869bc15a2a494d20af2b1da524b3468f80dd81f0783b2cdb8391e4b58bf87e9056cc6e32375c0fe53153e21e1980b2f76b2ab52b6784fcd8fb936efaf43358e5d2d4267cb507978b782af0c8f034faf72c63432bdd79012137a26242d906644638300dc5d84527c211e52b5a20e84fb61fcc0721ec2246c6bf0eb09a3c4c62f1789f01fa8cd301e463b1dffaa406a7097242b0e4c27abb36875117ccf8cde8e5d1f75581a135126dc13b60ddba897bf59271d50e29c354aa268ffb6d14a05696d99c0fcc0c81ade5fa2f95757188e1ab958591af901051217e2deb3795072138389e3205599871b3de708231af2cd842f51cc7264f0cd761a456532021c8bb15c9b617e11f99e1d8685a01a8afee372a50db8728f414b83586ee025abf01cdba0e48454213324df51861d7f31f2194fe95e2772a76c52ccab06b639c386ec9ca9df2f092ca1803336a97023a07660e4d1d4bc725bcd429355b263176f01754d85c75f173be9b876990fb06a469b2c40ac8e662bce909de7a9f2d53c1e184e0c99e9b2cecc927a926a09b8e8072419d82337b356695a85fb6a1298488732d40a9e7ffef03e613fa7829d68ecd34cf28687b85872ba6b916f3a321d365da2ea6d7a6a33f6db97d76653d7c13a6d7fef2aeb003a6a397b29e56f1a26b06f9bf9a965aec27e4d33aae0fdf317b089ba245eb155947250f5bfd238f1424b5e08bb1879ccf2b69ac1d69c4e3698789f24aa4a75c17f5430eda004e91c7e2d21c842c0ad61e1532008e80dbc2d4e2e3d2bee28e1e36a72538e1dc73149f3667d4e537c136da3b6a804a29fd46ee933a7019cff98325b72b21da7e3b5181b6601adc11038160993c3e39d96a88169e8ee11c9692fc2eb5273b5a648dfb555ff9488873ab29650a772bd5c27139e4bb9e45fddcd5a60a1729dd1b06186db659828f3a79eb3aa6c4edc52d10de24e8c8fde60baad9a578f5dfa3365997584a762bd9ba90bc5b840a602c7325ce1e55160104f493ee0fdef6e28bd2b2fbea2b4af041270e7c58731ff5f9c926434f485cb0fdd357b7d4a42288c64562ce9b78377611443acbb99f84ac8853aa9d7f6aa47bc952a5eaa630a41ea98de651de8271432232534f50b322c716418a718ce9962858e2d7b5768cd21d7e0156a964f4bc7fbf03533c5550517e7ab47b722e883c2c4bce6da9bc1740c2f87fbf77184d1c0369a8a1980941acb8bd81e8a1fb9c4fc8631cddf935e5d05bd915131c6d47082a4e9e3193856b8be3b6e17b4adee5c346c69387a1e32bb72e35fc28d09a3eae8ec96c46cb3879481d033aeb9f5d50b393a4408d315bf323fd3696ffe6989618b906d8719ea33848d275b92671ccbdfb439c7bf89610a9572b059ea3a19b082be0a1362cedd67f651e4b168479be459e92803b46b81d2e472a1e0308a574b876df0d38a999c7cbb3aac1b363ddf2725323c037c28ece52e72e091ab3a9e3ac1d99e8de926c87482dca13b8af4bfc856b067f6824b48d0be4e0d88da63bba56a12bfb54921364e0838a9c070ff8b5dcb39bc052986413cc31877267c129d7303cc85f501b076fae5f8c82de9af5e3bb2f9a1e9c10eb8da230df01756cb0ffb528db8a310e3ed6d50cd5f425e8c55ae080fe338182a7565cb72dd8d10a1bc2ed051ca5554a717e76794785df0bf93dd5490af62e614a84bf872f0344a9c8194eba885b230351baa7378a5607dbaaadbabcad981bcdaa214cd72634f2520e5856b843845785bd9ab2266baca84ae82e34e6dd58e06294b3bfe72730ced82d04e512fa235773d58c26d9c626962893e0e9c515ff688d1ecd6db0e79f8ce1a4166ac7493e64ed61ddee367df5e3ce91c755597d97a02cf1fe04672a42668fdcfece3727d523e3fc249aef044ae0a352d24e68aa9e5123640ff6a72c23ec5626167384d551167ea5bdaa9a7455f0e392ec9881722f63032b2433a45c156c98571f7365e21d262d33da047f6b88352c41f6db8cf6c981e669db8bc35343556ac81672db2e4a0b4a962e2ce7e3bb8f4763dfad3a9cb95f77a6dd90d3b96f7c06440d7916ea9e9a4559ac1fb81d74904eb37836401b091463067851172e7cd88b316e26db9a94e19fbffbd576044aa15423ddac11c2a408cb42d343c6d03e1f7a595422ac7dff78c516f81bd245052413448a6cb5c320a960c0d5b387230c647f595604e0be7530f298cdafd961eea2ec61682db87997e9a8938df622d98fbbd6f828690d8297d9a3a2f8c949ffb3b16e14164a02df3df4b70d50d927287fe5b262a674d5ad988694e5b4d6a84fb29277231e163789823f618b9068972428d21d0d5899bb9e77aa5a0c05423963d5a1c875656168156a0502e1679d1721ec937d529c09a4cbe2347ff15ff07960c410883952b606d6d34fc89f4833b72c857bee7014d2fb125585a339db3bf1813f88ceb55e6dc61b9123b9e4ac7b472247b649aae1edf57e0d4ab4cde60d1071340260f95531fd4f4dc12a92012f012e4e9e3b40ad0f9efe3967f7a4176d97417823ba50f990cc5c84fc5b453989c1a476aafb1dbdae47b6ee5fed6fbe2e600dfa2fef306918b13d52bd9ea8d8b404f5726bf784040d2ff1750b03af0773566614b77c8a5354b70244d16383066fa3389b7c2293ae7704f7dbdd37899c31a1f3bc3624fd0f8e950b2ff9cb68b080227aed3e7ef76d02c3cee3adb817472cd61c51270c22a08b9332f1b22563819d672bad35e2d80272bca41d4b95bf3aa1a45fc4e73cdab7659d4f8a33759b29a2472ce60eb8529712ca1286a2b763eb021f4e59055ceddcd53e470e744ce298077725ee52c1761a386c07be387fa0db6c2463cfbac52884eb862006c42ced05c4c72b75ee832916ca373e0cae811fe4ec750666e81c55a03df90544154112d645d03805ef1aa2605c7300d526950c5f6ad71e3af983d0ebcd6c25caf04b56ddae8726d0c6cdfb2a2db719a2a5de7da4e5b96d97f1400ede3129aa8911373185a3072649fb32bd92856a6110f84700bffff562c74ee0f57dbc42b7f30f8dad7733172f9c51fe5add76bdf3aa501059f2422744e5b347b76e10eba955c8769977bd95b3a03bfce44c292f5a174841bbb50066f69b662207987861c73cb384746715648f49098b18c7f49e2f9d6c61decc42faf6f85cf9cc402ddfb0f5fdd3c578247348f429c74f3aeac1e4ac43730b38f1e1105f6a61cdd970ea29dead9d5ed62fc519b08729413be323814db1cbac29e3d1ae562fb425a8a56309f164759429dbc720eaba78ba4709abf9e792f154115faf81b50f1a33b0ac4f80b4f44a4381de172e66189349f36ccc37e08a487a092c29d3917880962e54140974a209051271572aa404ae3961945f1ffc44bab647c751818620ac1930b17a1f045d20b6dc82a72a4b34ae20d76a034d1323e3292e68d6734d5af434b5ed4a1b419205b1341e634141feae444efcf9da7394c103be0010d2fc8a3aab00d610c2f8218d5fedcd07291adb4e7ce33b6372f277b32dc484d11b7986a149d4d20a7e0f50bd268611372629ac560017a519741ad466e670a7a8150373a080c6d97e1c4387f897a64397253b490227090e26eea1017481cdfcb7c621bdfd88d3f3a1af9aad42cfeacb83b838a78c996bdb4a62f9b488b89ac8b0ea71b8d9f26c36d127950e7a7aa3fe5646baba12622424b6b08a5c81010ab17eb0b1fd64eadd6a6b138c1e9e9ff83560f77f3ca85c6bc10614199c0ce98305b9a5ddb87c9991638805e9ada838d9c327268d0b480f7241c708ee76bf5325b19bbe2f96ff420aa94d920aa94e6d78d653b8bf2e23a5a67c602f8d31bb1b4a428231196c3bc6c2ad2c2b281563a41ad8e72fa8f0c29f41b3bca0046cf7fd7ed75ff7f7cd597198725bbdb23a67786db3372078d34674b29cac9477f272ed6615de5fdf4ff47744f2ded0edc10d6d9078c1d570305e9a99f643caeac87396a1cb3af21ebc51e196f43fdde526cf3bd995b721436a0f4453b1c68a27e2dfd96900f5d2faa79e895906bd11dff07c91ff97f5b3e1e0737d9ccbac13062f0b039d819cb357fa6ba7aa37415a2e0926d6182d04d6250182df6f19d69bac0e18d30b6f37c0f4a73e17d99d7846ddad7a498743a2963c0b739bffc406d5d062dd0740168b1aff241ff10fee6cee71d89b36e3e0e72cce1577f0af77e06cb58b1ad304ba1a7f15d3825a37f693d36677e4768a4e0092ccea1ce2a0c0952769b8e73ba7f7d45b1e1c137b7928274f79f53a05a659e27250e4ee744d69cbe9b997e6ee3704f7e96c60f92d9785962c61a52f3cb4f43664568356bc8240604233c021afce5c51fddbd981e3b68d264f5deb09f06d79172aa85cda7bdb4f8c1522e5abf21a388e06deb714d3e86e5b2007989fea56e0e723e3d100a9057e29074911b7000a6f2ab7139f03e88c2e90648815cfb8e7d0c726f170616c54cc5d456c2a02a139880b546e4beb7194464f2e50e277f17dc1d52cdf55638102e7b4f108ee40489f25c4d922caa124c035b812d14385a023b2172e31337dae6fee5d8a0f5758a8af661454bcacc8cca5c91111ad71a189d4eb35e43e31b0d1b1afee0afad7a3dd306c01ac1dcd15c5c7f9f3eaeb986b0ecbf3e62690b2a444ee2d5ffd4c7e46807e3ea25e46c146b210fe801cf94b1ef3e991b50f7952172bcc9684fa02b665e206063885efc97efb9530af7791b82def35cb2682b46bd2ab6df9d96bec0dd6e0900cef83f21bc0a6dc4dae17124d9d9683d6a72e9af83e3292d0bcbca3b9738ecf309fbe4c3441097c7a41fdd3938af3ac1c46fd5054814a23fa5e7d72b479a423026a96c8dea752058bc14f78ae5ce6e5a4972a592c2cecd3dac4fb390d98a14ae6748c65e7e2e09c38828d5f704eaed0d9a7236b97aaf99f5edac8829dd63e03075efb1a10bbb79af3f99aaf701140fdd7c72fae52d0b830a7950b01d2150cbb04c69fcf38571eecc7f5a6c246dcf51a56d7251e23ab73f937c2d1bc89c8be3794de1ac2df1c570cdd23cb80d4a5705e65a021c8fcc4c94ce08ba1793e408242b5a904f90f6ba7124b219c8cd9be2fa0a1672f55e43393fa86edf0cf39deb3097581b9254870d7995ebd68e4bf531644eac723a3e9eee9fb59143210ea71f2cc9889b3435d76ad096ca15e4a6569fb14c3f724a3b4c3f9598adc45137e99e9794d74316d05705c29072848782f009ca9a32724be45328408f87236d5d724b86df81842cc8f6d8325d8175dd9f2d747e213a720d3e0900ba9fa5ad9b634ca00ca1c594c9907618d5e3a70ef4e0e8d8fadfe472181358cb29fe21e6318d92e0435c7faef8257a7eb50ae2f65e1fec15855e47103524e1ab072ad5abaadb33cbc3a32c1f250df01af52b38e1eac5f5ccb6d0f12e28012ab0e81655ea6b7b3453d1e163bf74f2743082f18cf1683b2e89868e2d72b836ad729ae02e5d4acc1ddb4f2b9b5afddccab8d3898da44c333a84b2f66f72d2328195423d651e4c7660179c57de683945c30e728d2c24dedafef410cdfd1905cfbc8619acfa49bc14da3fe2c06409f7beebc59adc0e448aa9cbe1b0578372a65f3393acd7e6736d9921f5bd8706fa66146e990ab42413729ed0cfece84072b9b1dd4889b889817652b8d39cc4f3e40dbfa382f24c4d7c298262e78d49e53acd04bc231ee5c7fc87d99116227672e09c7e1c8a9b0a2495372f10f70ee2ae48da7c1672a9e4cb96dbd9ebf58c2711ac7171e4d4f39279e6662672ce8f8cf5721ccb4754dc956efab37819e924d41a7ab5f2fc65f92237703a6c33bf6ddb2c72a6d79f2c56cbdf66a3d4cb3f7e1f8e1e640d6253e04069aebdc96620d30bd2721823c54d91e75b196dc4a7a1e1820d17075f11a08de0c661c41f08921252a92eb158bd98d43a089c358745d459e56e14b901a4ba855f9f8c5048fd084e21eb5e321832893e97384ad1989f27403482cc6de5d647b6687504013ca3dbfb5bdc727743585eedfbe19e94a4fb77433a7a2d25343cb7a3aeee0b1680c4e05c769e016ccd5e398159db364f15a7366ed577320b3e7123fb8f0022febd41fad15ad07262dcbaa0790d11fae6285681368b7b9f22a431e933a28a4912adcce0436de072158c528090c3aba93c2df58069a66f0bf23bb7ca09165f61083b869c51d50e720920e6242d6ea0d40ad658922edeec29133b10a72e828bedc84da3f99270aa5839c8fe4cbe56e28ee2a19ba352882c61003b897e1c63ce9dd43b8264b6120a72d1f44f1cdcba2aee0be2476bcafdb0e91e26337d56483aea66dd8e964868686ca2bb142f9a136ff92664342fb17321e45b6265d8aab33f041bf54632e28468729aa6d6eda221ba1a611dc1801cc0c759296e25706eec2cb95b19868be418ed4dd7130024443c5d2771dc4469fda13a57cc61eef86370743cbe956948b276fa723d45b2658408ff31143954cb9aff8b083c1d363b9f77b1bd3bd4b6714946cd729af4de5dbb2223e4ed1aed4a0ab7496252d0a92e553a6834668c2d3478f6697003ff58ef550ed52d7a9b837c6657d8e135b4f3106e7acf46d5a42041aa28ae72a7a1778fa1558c79d25d362646d661da949169688d71514110865d5e0394cd439c304bec9450ae09a7e0be2e4408fd18c3fc6f9e62b5932dab3dc41aa5da2d4ff8bab358de506546482808b2a438afee61c692d07cc3e675568906a4856bb472ca1014e33ec10d9dea7ed8c84ed690e7704fe50c948891c6be49068f6f47ef72b78f33edf573f79f62e35c96f2a426eb21cf8d66ecefd15d4bd9265679e9686fb2826a9a84e27f67c273920fa414ac4de0159f294602e66ab5941cf5e2b61c7217950fa0837eabc4e8d7d54d4449b5453b8fea4b5035c8ff4f15986c8c6b8617cea984fee7a7a806a0c10fc8f7c52dda1ad695d6c343330d6f2aed34a79e563f6bd829dc880b10e4b7fc08e9b6130d58ff48a163bb21788d6f4bf32a5a6edf72bb6c7d4a92b3d2b623d60d9910efc60ece22c3f6caf15adc55e67b81ae54033c848aee5be5170bf59b6a33e4abfc7de6117cfec2d47ce4f5b9cd283c097fae1ca2a8e395b3e0e9f581e7acc8797d0be225ea3846e4d3f733a563f1de6879ca72ae014487126f733bd1deb3d696675affb17800caa87415e6d828ad498542a5726ae612f278d295199f50cc47046249ee55120b46e3a0a8239e613edee3b0be4e60875dd1a9b22eb912350208231be25dfbbf7f38e3cc8c2c1f6930726b1d0272fc8036685df46c04fd6f7ff9d99ad379e28cc4feb5775385ceccf541d8b6ba7241848b77276af7bfb40ac549458933dd4ce5ed724632918408dee9730560af7215d976af333f4cb3506fce47957f7cb2300252c5b2c813cbd14dfc68aa8202729dbe41eaa207207ca5da3d3810114ea6baea935d352754fd8c28f81380f8a101489be1631bebd0a042af436fa39766a484a3bf26eb9657da8ddf47252f89e8721f93c2ee0689ede9f8c7e865c3e1b269a2e84ff9ad00677bde08df6bdbf3f0523961f51cfe1eb29a508f48e9944e630741670c1d229584ee903b872f81631172c4d495b8b408bb363e634333987c7e6145c980c1c2f678863e64f6e79f5bb272c2ec8e5a569d56f43567c4b5c13b52dea6469d5403d1f9cf2fc9943cf5e668723ccb8d2aea32e32609f790ec138894e4c2c26eaa00c6e8b23fe433ad541cf7722a553df0cab28a91410b8dc0585a4501f54d6b6c694de9c8bccee3bbe840b87283088b1fdfe6e7d3092261d66e7f1035e9357bc7cc737c7eb37a4df698f27a328557597c4d7c44f27bc273f07dc05651eec4f6b0de7e9e20fe32d0bc54b8dc72382bf3a715f11c1770505d2977be59bf35bf59b51ee951f573f7350741c63f0153a236c5f32b044defbfcab4bbcf3a926c8f627a708ae3e4a004eef3ee65917253bbec2e7c064638abdbb6821454db07167fbf74817d7f84184d7f5aa12cd240a4b8ca58b108e78fd532e841b3487efca5adea2b434d6ef99d7563c81dd014722f2dd1c7d836a37da8b7abb66f364a8311615f380cd7f2061f827ff455fd7e726c1a42edd853f50c23cc3a5bfa4a2454df24c89e225a27b62762a9ff12f23272d5028caff02479f5dbcdb7cf68fabe542d36adbfdb76d6a1bd6ca1a68bc05e72ee42335bc3332b083628b38e07928e9565f9c3f6fb5c33a0303d9bfddd554619b3982c556cfb98a9deca63cb104ead41080c96522e0c153fbee83ddc1e8f157286e21e1e948c5c15e46e76a429150d93d560f809b782f8bed72121bd7946d37283e127d110d00ff37c5088ce01b13541f4bd71454ae6cf5650353ef5b66dd6722f54dc07704758c384436a57175dcba5e4627d252196dc60af3aeefcb2ffc072559d0768fe479daaf8a5667012d3768f96585ec8a7f86965281d559e7b558a72e0e151d8b1d55ad938e51dda59fd42043d0dd912f37d0fbfe54134311082bb0c021042563e481ad798d996ceb7a84f964ced758faf1434231e36e88e16a9666a03dffdfc75b050744ef6a8b62ef4219fddb7f872fb0064e25f8454931952543fbe836af76103518115bda7cf743974964b0d61d9c5fc8d40c970a97f44f8c772229c0ea7a7860c4dcb8e8329066ffd0446fab94785783f0117f21225eeb59e725bdd2d5a0afe17c9f158a56eefae8332b3e84843fc46bdaaf94069fcb57d83727695041acfc63935bbde071a8993d80a636d7ac9ca83e9e0eec523d93e3c5d440e9f91d5023e674d20d6d120ad009dfce2d59d70e9276135420a72680f81e93e3e377e973a5fa3ed26f44f458238f85bd31ff32a36a1a8bcbdc66bdb3b60955353d4b09722018eab30e7f279e2dcce9234bbd4d0140e864db9016f5dc50a1634d1ffebc6ab179ad72ce26e4cb2121cb295e646ad21625d8c99324dfaf57f1b2eb9ff41e2526df771d2f12b5493e3e2f22330af57b903180059074874eabb1072ba9833c7c24590a36c61977c2668346c05a871457f81c2a36b92366678455e723d495412179f0acd1fd694e5892b96446eebdd3f9f023d703cb01695e7372f018b19f86e362ad3011d8b0973f28fe68b069bb7eb5a9733755e6baa5338f21e63cec4015c22a7851b49c117004856a285b744d5d04e41c8d1b1d64119615b754d27190a59a525e3f4e946fd5497f7e0708a9dad5b2aeffb7cb929e570080f0c4beb60dca39eddc64cc2d6bd423fcf2c4a4d4a7c155e7214feba8ac54f9ab54e7270b2e8822c024384a43c66fae3912ed8431624a73d4df9fe35c77c2e46f2ed72fcd15109f1f940ab9f3a5aa69d27eb9c8ada148fe01ce332515c097e061b0d71e2afc7efd64357129bce9725cb76ccb02ae2addcb63bd901b066205396d31f3677aa1d081e3c7a145ead23747f6c2d92a0c13d69cec0d431c441d718946bc511be09fd1b4358485c2ee00ddb2a83b8f1b2b11ccc7b832736cba7a2c11a835172f376cd929eed350653257a3ad5d5c71931f190a49d4da2baaaba23f62f577772f225ba73606f8dffc44c9882bc3e6ddb95f53a6d8862b18dfca23683dc95f74dd0259035f4d8af827e5f76173eca511fcbd433f4c93cb3a091eb797678caab26a714881206e93a6a6debd044cc21342a81af131461b371118fe1eb1804ea082ab59775e4c141fd2f56b5a713e14f50e38e99cc93c69cf814c7cf19f9c9f53f59b469f8aac474787bb9300d10cebf6c38b5d143eaef81232165b016c8c8b0c32f3c0f74f523d75d18372e6fc50d341b6da0cada397b52772ad8d2a86f718e2c6b3743a3ed4fa886a9fdc9fe7076aa9c92b12b03fa3ff6f6e02abc9bd6f9463b2ee1bba71c248eb53f4134695a39098440a478beee70fc0a586688956578bacc72db095768557064eb599d7a76615a6fe9640e2dc8bf4a90d6326175c15a6717723b11cdacd5810fab72206deb80a2950ed080a613fb3b880f9733e5a4d7d00272a4d96307af4426b9b090e7974d641f0a24647237c43120eb0e486e8108d1e1222d586bfb405dccc9173075ed31556e8c2b0814c92cae7541183fd8d987ee5872a9579f92591f9b565a26348f75da4db31cea842683c6d2fedba798237c184c72cecd02659a342f2acef44ecf6b92af671e86ed73232b8ab079dfdbd99b2669721dbf08f6b508d268cd8e75850b0b03f77254d2bf7b9f423210eff428591d0a7254724dea22884da1281004f827d0851fe38347a6d202eb3d4f076f4b5712eb7282c82d2c1d482ac450d1248f9be04ac6ee8ac9f7ca0fcb41c1f3e40358fcef687996dc6265633418097a5ab4098017f6f1d9a080bdc5d66a300824d02068a4723caca79fd94754f81cae056d63b223569fd23b6a1f42ee008725a9029c45651f5a6d2f56d3a4a531883860563ee97d6cd390b808c0a78ded55e98b6a1fd82172c68ae7679674666255ae64164627bdff829b98eb1082651757f487373ce97739ca7525d299158e4038a1f9964b92d0462b068a364fbd3f3c36a3cc9bb836dc31f417711e053e2ded6cf59443cf4555e2af56359dbc3f82e4c22432012e32871746a1e3fdbf19a10005b66f4ffaa15a5711bd113473ee2d38b8fc3f3e97aa1a008a7654bb512c05660f1752084c49489633f6896cfc88bb66d05b3cabc934e769aa0a628da7910ff928d45d85dec793dd86e99da066cf06f044de59f95e401d0adc7855af550c87c0ed2e0e364083c2a907bdf14d95870deddd0ea3f0bc5225408541f2806e4a669be47358dda430113fc2a35a38ff3b558280c088c6d57393673c7b9331ab9912e351077c06ac9555e33c764b2692938dcf64f8109d8517ad723292c8c3802e8c7612049558e86360fd87b421adb20620a3ef174326c1d07958541b74acf4f48f375f37f798dcff618d79a8042832c6db60fbd2801ba7fdd9728107f9dba6d5fd94934e0aea6f12ee578d1c337b0fc26cd56944e482a0c8a6032156bce237747a477bd51c7a4702f4f5a8c83d166f7075aa4a9979096661d97296622a32f99a62d8b9f5ed22fc64bae1e06712fd527e3997e255bdfe14a21e72738d45a725cae2c0d493390fd8c15651744c31c7797c4e15c0e576ea0a16c872b406d8354bdeff6f43c27134da5ccecd78d0518aba11a3cdbd39f1a00a2d3c72fdc46c4a59512ba33ef616097e23027a6a6eca22ddef7b06fbb49b4eb86f5772e67ddf282d44aaae8983168f952dd3d9a1054d3a7b77e221dedfb58eee914d72d0a37173a488a325ae715f9faacab101ec9729068876ebfa211320bfd07ed463e0e5b2a41cdb276ed3d27319f4533c559e5210b9fa28f8d2743a0982d9074b72201685cb3ac9464f0c00af394439a51a3be416421960c2fda4479273aa8bc370d5250cd380ec8f869ccf38679b2ae2e8cbfeb2d81bb1db42707f8dca176e4d72708dbea6c16907c9008e0aca8650c844e0db9a0e66239c099ee7f2e7e74f873bb508de0bb90df1dc54078f905d01f78c49a4dcfc876f771a0c0a0f321c85ae72e75a1f268e9f2e14a27962d1c723f555d23d36b08ae667951ac4b6b454c95f02e946d422d234fd72eda32b844e3bf786e7fce648b8890c4d67d99b6992be3072d4c30e43a59b36f6021ea8c1ad24d1dc75b84db1e763cfc83cdbebfb9fa7e8727c01147a364d1d2f55d7fede268ffe5fe1029bab33c00aef915db773afc30d7269ef131f436c7636f790b3fbf1b5f87c85fe0319b3a230df14a9c8457c428072a2fa18d5f06c6d3a156ca11471447c693302bb61b8f306eea8be6d5717fd327299c71e3072a7684a850b6a2bbee103b39a648ae7af48d1a9a8dea930a33cf323928f17fd07a6ab0cdadc63740422d572ef566fc0a6a8972ce5f9b1b5171c516d1375302d76fa3967f66549135e819eab0a5806b6c487b7c6b65f86ffe3f82220b5c32d2f9c725cbcf18c3c3c20bf34cd599ab3af2b42f8f5390019df052d962287ca2dd15b3e61e4170ffd67bbd72ca512e5c08b4d6dd9cc019fa634c70f522f14cceefd203d5c148426a65e87aaefea1571b1ae22a48488818bd2390fcc5c1d57798175fa59e20ac58fd49e96ae73f43eb8a4969e13c0b6ce279263c43ce8727322b998949676fc3c9bfae750c13db1d664be8e7cce6799999fd96a70082628b51d85b11208a8cbc32b61eced4d59ca911b5098ae69e4b6f0991be07304ae72ee4c4e24ac8de566bd95f57b29a7792c267625b75d85c21d3604034c6f0cd0722cbb18dcffc47ac84f3facb397929c163ab499494c7c0bc9d1b48e2e5778c46974971fe64425ca37dd259d2af7d9a43578aabae44d370defc3fb7d0e66281c7212d6bc9f394cff7036176bb4a8e226019a874ffb16118d728f80098cdd11004a32ba1d3eddfee39f0e598643528d237ffb00d7f43744a8ed5fdd672a56c94335ef142ce586cf4f3e9bd912c66e4aeb9d99acd2137460d1184d2c3a1a48dc1072e4799ec2775281e079ee320ad5684b21026cae95b3d3854756704e68e12b4e723ef8e2ba58b70d1eb55be56f6aaa3486dc89aa6d24b2e228e39ab6a4b6654307315a997212e7aa9545964eb9e4a0ac08b040e4620095d54ea15cb46a36c2dd608de6950627c797a90c1b586934c1d03ba9573e3456f45cab58d553b54b027854537bcf27781088bc6ef44e5b7432f8e2897aac4804214ba6d493c9e9bea1c67285076c40a75106a803eb4660c8084efde81e9b77e8e027f2feabe5565b5ed17211d1b1d78ddfeccad7f88c3dc8cfd45397ca3fa3564fd900dc1b83e8d3ddb672b17bd81f7bc84f76317f81834745dc9111c80770b0f5c5474a3d24ffa5803f72af4a11157eb8aa792bb1f99f87455d334842d9500f88fe33e4a6527f5eaaca21e459a38cebc713118d09cbb326469b13d207ec8247554d7af0490b3a11c25372b8b1b00759e6cf33eaa9ce55135c3d45b860b147e71d05e20b0d42c02aae927222019e4bd362eefc49c90221307894a21bf6b9f7daaf05e6c551a1b621f29c727b9d46ff165f9d543acb1f3735a9e59b9b32bad4cbba403a4693465b694df558a41cf1ff30b29d4cf6ba3fc3142b5b609a34ebe71842b9d0bc187c484504aa67dd6d61c23f92d8cb1396ba7c76cd02a0a0af0b9aea971acc64ac89912e704d150139a9d5793847308aa725a04476055b2ca159fe5af29c052d9280f62ae20a61c898602ef4049bf5e214eb8d2e4f5f93244761f184e7d2e2b39442b480fb0235943bf865bdde9f1c8d1af507abc63441136b710801ee7c3ae022a7e91309093a898ae73417462daca0757658beaddbd8ba313c9286dca86d919c369f4ac5f3727ff4b47007857d6a4834d422a50a6b503ffacfd2186c70aed891648bd23aac72f13717eab1a97d94e0ad90f615eac1048ef3b519dc9961ae95ebdcce4c36f172a65855927d31a5e617029931be974f4355ef27d5084143dd9fbaa939e822df7201b7e406aa5809b604f43b65616d44101834b5cb8f56b4ef03e7d008690b2872d125b7695c6a2a52d18c5bebe2ea6ac74dbe1929527c0b8ed0ae2730bbe48e72f4c6843d344e61bfadd5dae5110bd93fb0d18ebcf93de8e12af44d5e069c1970dfe8b5bc6208bd2419e6958c8990a07f2ed54ca8adc88b243e7c67f641468b72a3b43613a07454693ac865687c5841accefb272b88527db0d1a37bdc645ae07208a4a08c36cc80bf9e3f6206ebff1f7946d9ca07b2882120733f0af13caa1465ec2f95fc807ae1fd615f72c16832dc41ea21164dd37acacb6578d1e6665e4671ce9083ee706262a650b27599852c4030c01185bcc8fdf1cd5ec03c7b6ae71b21505fdb45e236db1f06cb5b25e5256e6ed00198718056e1cc694429c45b550b1602d9c40b8fcfe7098dc28f4242b484088c9a41bc488f43416bcbdcedddf8e84b65c24f9986a347cc2de2293fcc8330f01e3d9f647fbb19ee7a3b1322eea4b572e4fd4902c4c173de70e5641943c14ae356a1767f9bd4ea2a39bfe1f1bbe59672f8b01bfe2740c29199c536d7a363c6f35914eb24190073611947418302458b4f0db1bc877ae20dac22a8fb36cdea2e625ada03384311cbaa4d1d557860e2d40a0be74349402d6c87f7869b2a8ba858348038418f3949ff1317bbbae0cc46f1720740ef1266c9452634a2880e39124c8244c2ed64976b933e6e494d0196beca2f38327a771be38121a53ca272c3b0cb69ee1447d234afd1b2696ca89a76eeb57243f984ed859b72d5358a0d1b98de8ad3467b948b3cd87c11c66b3365b4f956729c56ab19760f1e9752d2d1af242e92022c143a8feb6d86c7884d436418043972c1875b7053b1ed6813ae1ad5a18902370dba733620044a5ac064931c3552307240707a611da1f0d1d088109694d7ee28333926278b588a857b64c1f1a491916c664c152243e816402aedeca21beba80ef1a9e9101424ae92eadc62bc77714a4f7b50d182e409264b1f5ffbe50361fa6c8116dfdce132108d41e5feda47839a72a57654abbbbd876b7af3225b432a244ab6f2b219bd33cad0539ce1fcf9002f5348b93398a1b40daf5e8d7a44ac80aa3ea7899670dc65bb18651055c5b9fbab72e0e14f7d683dfb0d0f90148988c3d4b26d6d51513514a2e055aa169b8211e756ab66f6d074b32f7d0051e231032fb78df1d0976083bf1b84cedf7282e3631e0c426deabae39c8067339f5c7dc121b1e548ecf46b0cd6df11b4eb4a8b72a56f72637368e32d53b7eef675df6751de721bfbcfbc4e2a6535886d6caa4f9b0ce43b40b59dcac0405c3d1c8ea1b625a8bc5a3d2e6973df726c11efdd26ca9b8e9635327c063c5ce34eec704777324b8487d45ae5f2f411776fd35e49acb09987df1f227a7a58e7a06db08d271a48c0a3c97248b091d21aa93f9f32789e44e067d621563478b61aa50a06907b83a1adf51ce068f8ae3923ebc228ceb22b6062557f728fe5c73360577ff8f020d975601a2b050f703dc49a7eead001f730f842438472cd6da81575c1ed4e9cd9d03847631c5fa0617e44be4f2fe736de6b4310a81672549288ccfa5a5b8e5650da99d1b85892884d10efe1ec429c4af9c5d51c53875bc11f5e7935a34b861a7f4f7931932112d95ce44d388832ac57d2fdd1a702c83d49682290e506f1e3afb240d974f8b8645451dcd099bb20bee47cb69759a43372cfc8b532671ff1e3412e666067f0b030469b7350a3306e29e7bc30a41069dc72baa19aedccb17862019a706a2b336be401adf75bd0bbcf334ec12a7da2e2c97290b28692748172ad0cf5f1c07a79725deba61f72e409afefc8c8f8abd0c91158f30c27119f2feef2fda26e511c9af092602539dd3eda487b33a3816bcd07682c021773dcb217ced42941e27d80e4d4f577449ad9392c973ef417c4cd79204872a35b55413dc04f92cb5e6f80aca5a936220f30fbe4cdca6e6fbaa95c273d5c6c26d378eb83b61db568e91154aceb4b6f8f6bb2ea5ae9a081f68efe66f9d5be381bf2b681979b3a75cadf77eb431b11cd226b2f2121f5b56d0927842c566f9c72fdf27908eae0b47d7306522a0b41b47e3d7b41a393e51fa29dedb3b5b2deab651c4c1e6b4ea20a7eabf518897aec3a7eefce511921c4deae6aa2f90c0a6d72442ea594d615d466b13deda8b8e9cbdcbfada4f3a686516333868d62d4660ffc720cc894ebd86aa537870495510780dce51ed292c03444bedb08881597069b3e72b70bf65b28ac4f5dd34dd057242643ca9b86bcc3c68c3c8606a69d48fc3f8d72c3bad3e2634874445896e429ce21b64991a2dd0182ccb14d0d6235e9616f5d723a53bd757875ca40212c7583f1f9c5a02d1c500f2c03406c7e7a6af27ff28672ebbdf0e9ca89b2c566abf5ad0534e2f9a74b16c6a46ee70afe9ddbf9d2b43472faddbbc325eac53b2a9a90cdecf0120ad0f872ce8ef0d4fbd214effa0c368872bf04e0a507e82a427333e1c695ada383172efc2f73caf21b3c95fc467fcf3572606b04fde23b13d622bc94acbe8559e95a972fb193a5f7188e07bbbb1c349e72d7b04d4c7047cbb45bfeb75400ae7854bdb121bf868a1eb2752901325ff48b72bb92e45f9186a99a67beb493f6f458ea07c5d2fba942e2b36657953a827835720ebcd544375f004fb60076510f0fb20c499673f5ed7a4b7ddd3841ee357b64724c9f96e5505c7d298b671f59d0cde58b06f68c77f8a86969d4745fc054dce2723a4f55b6f1ca5e7623ff8e24ea23af83b0aa2c009fc4782bce6de8855cc5ca0d9327c7ffea00e83a9c1b9e2bf115f1adb2ed2f3a8108b81bcd21ffcf1aeebe729ddf49d76585036f658babf02a356e00ca83dee5ee4cc3e693b464847f0e8672ee06b144aa4b0ef3cc725c77316e54b580aee0781b2f239192e721892b6e0f7287d62c18d6c02fe365f4977941c06ee66ddb52292d52b256b9d95cd96cf2a872a515c45aa3a0308200a996cbb057c62ddc4aa231c3cbaefe042527ac34c09a2b64741c6b429f99711fa5cd232bd874afaef89ca5fc812db41a776ed3c59a5042b7be2f0046391f10bd397aa1dc399bb677bf3fc2f415648909846c26a456da726d889dfab186ec0cfb1916c5d8509bf2d10d9fa292ac6d202982805908a3bd4cc90edf26dfebf6b502b62fd951a9cc98b1d8a8d32f9b16f14a4af2fdf806e272465496a3317fdbecdc82ae27081ec937ed2b8ec3d79446a3ee6e791c4a69a672c8d3a94d6594d561acd8667209c764325db17139c3a94a88d7ad782177a50972ff93bb9cfd4c10e698e5499c622f2230133b4df6b5de596d15104f54bd18c451e644856fd34f50930e13ae44057952575d052c0405d3ad97f862ae2a9fd226365d90915f1133e231ad4b04d4e87cb7a47058afe1d030218bfa253f499ee0ef254850f5de1f505698c78768fb2b4f5fdce139d219c1de12e35b509ad35ebd147268e79a32739c985200a4bb0f367bfb9d90b76b58403ef64f524711919c703d2c550f786996ac3bd62b1aebb420c861d9b4f079705bc6055535f15e62141f9356c6130c457db2fea28d65c22e3a152189a5e36206e94d1ffc4b722382b9624272d67ad656055139ea21c460a675cf7e2653c7b09f727d8fd70343cb58da7bd67243e46d4788a3fcdee2b5c3ae30b46b8e28674344f568560574b3eb1b2ba476115f745e31e4e6f4ea3442fc7fc4f86337edce9f7a74ed6237f39cf06c9c51e5726e73e4f60947e9a91b77f6ff95f890b0c5da2e64242591e33472471b1775eb7292759937d2a747ced24c3674788409a06350e776ea448e69e018c3339a163258073d52bed253a5bb791b2a752941b82764eda874d646427af30b7b79b8fb6c2dfce9bfe9fdfe0d15931dc23b6b1c416a93d96f4fe000bcb75d610178f11dbe1af6655206f1e6398624b1a0a89bdb0b935175777ed44e28220ef1ff5571e2b7111580eb647f2d4abc22c4a727bfcee7d4072c21dab3f93ab88342209a9f68e0727e11bf02bd74c5fd7fd4a8e13dadc115b28c684c0bbb9c2db68ead6928fb1c69e4a47338cab79c427f031bf517233b331485a197b57c1cd227e1f2bc7a695072340adfda015f6430ec2d5f265b967cf7b6fbda77bca36c3297ccdc1dc0aa8672ef3d8604aff772d903de687d01ac98e2621e7c0d883b0199e99075ecd27c994501a42af8534b2d4833fd92f78ef532ab014bc0253924b5036f7033c61117ff725c73148243c274e0f6234ae220971c056f88cfc1280dd7e31f5228b4c5f2f50e5c86d117b904c39c880b42d5bbe0755b52e0ee64aa68b31b90c63f0564ae6d72ecd9aafb318996efdac35db16aa6e3aa343c36cbcab6b3551a083b24a79b864c69e5cea5b35bfe18de17adfebe85eca84f248d86f67a174719c97ca6c779cd72f2952b7843f8ee1cc8399acd2710f36c7ace35e4bffbac2b9b701d57888bf7723ad805e77f5bf6789676da99546a22589e8a80a7088cccb0eef79eb262daa96a194cbf6d28a165b77e746fba3e38b38dbdcb2b01fc688c69cc8f15b7b2fa1d46193e59f3d2c889a0608a0d8c73b0a56073273d40584d11542022573105cc4172581cb54d3d340418785e36f2704446089f751d0407241e390b83c59644cef272ef1735b1f012881736a6194e978e770d31b3bdeb1c378a22b56c083b7a468d55a1561538eba1c7106daa6403526fe754847ee9a72857e5ebb8a26c37b263ee130bb9bc2983a714bd9ad71f01d0f3d246ea3e7971aad6b265be4884379cdf8172093cd51ceff62d96c4888b166a4d70329e9a4017cfc125ee4cdf82f7a38f0f72342eecbaa273a1973356c209f3db9df3ecd50e5aa0daa8d52e1e7fbd8356b31a953c060dc702b369b20c1fd7dc24ede21f20a1fbc901429f6941f5cb846b2d6e8530e83169659aacca5ec9fd82a7a21138da047ba5e4357ba971ff2829811a7275a0cc755860afe8ab92931757eee44cd32a4dac28baddcae575fd44a48d064f5af686620f2deadd68c08e92680c376eb799b21c88cdc12455b956b0359ddf7093b792c370463f3880c1e3729458c323093cfe33b2a5d701f41b0c119f793c466ae6955f81d9614072475aa9b07551f64a67c1dfd21731504d464273f60e8e2ba4a48aff7fd72cecff5cc23ba847888cf7b039538063fb537b5551baeb767572f0dba7b27d82c5389021ed210a1eb019d060262ed70e0439b8b41d518f3f89729ae8176bf15625bd45f76e658c343ae0a8dc59f2ad00e78bc437a7485ef1f872110c0cf0c9e224c805d4a7e91aa241a256945b51e5c6bb3b77f4f62522a33d4af256e0361e944156cf75b4ab9c06b6cb327449e9f185a3910b411c6e14dc59429660f3c83bd5c9deca930b668036f398f538b14d84034f3c9958708a7a5f4272934ac814e3189f3de98d8ee4d6c0ab7590502f63c8666b821b3525d9d9cf0d2d1abde5097ca14ae80321ed37eee94ce6863194fda478c7a937691e4b0ffddd726757e854541eec48186e9f7af4f5029b37a0845d3a5364264e4328218129a47251aed98ab6a6888b2f51f93e1f2f96a3eaa28d2f063210e7cb7e05261d30de0555fd5a30e5720bbb06eeeb1eb305e9ff0353f0b31e195fb76fcc10e6e4d28a72f077667abfd2c4750165c384579fb34f18b432b060e38eeb5bafefbc5e4259026a0e826d6031d201c210172a922b621598c4bd5ca8f4c38137f375dd424003721c87340c7080d41806f85ecb8c0934ff658ef59b5c9ace8e4a829d06f77c6b38e18e8df5596c0fee4b8c0c1efb7409ca79e161596673d0ce9f81b2354d1b9272202139b13c7e898e863f79f06fea149da3db983dec89815d9578d1908976b128c08a519e77e81a8cd6fe4f6cc27fcbebb4df2e40bd38edd9f95457b811a91f72da7758e2c99eb0438b41ae9d2c7fe4c64d523070f49b78516a7860b661088961c3ed7c36c05fbce84c2611ed10e1b075653f3741ae795c1204c1aa1b5a3854720edde0954f84224a7c29c76692d93240a474cd78198b5797817648b8353a211a5f68195fc580e39eb25a1ca8e82896c3582faf59982344f2344d7cb6952420720b2e770dcf22534b05b049e80f29a11f0551f135ad346ce8ed812aba209208725cbba4a6191b6e171744805bc47ef21788b416102c76cb5d5cf6e2d44f66d472a8f9169a5dead37a495edf11c44b07887bc84b02ee2c612fa76448e71c1538068dce5da171e08d9e2a1c7a214770be2ce3844e92e66fa9ef508dbcfa20d11a30d07688996a1717f0941d42023f8ad41c7bc414c27552b307e47497b0cacc6b72f2636805f07d4a3058f69eb1b33a4d71807e10da0c075c49385093a32f8803729ebc63789b1bc455d47b72b88eed4bae56e89767f0f433cd0b49e2f2b8ffd5534dd2d764b474340f95129580270d188a12cd9b6d21a85276fda20b01b497816c9e2a3a6d99c0d6619ad5dfad98af0dbc4c0542e4501dc64478464f806692867292d016e1815b74a46686f62881d1032b05a87208ec4a21b475609311cc4785547743c43bbae07dfff1003a484600701a0b1b4febc062121c5dea3735655e2d45f8158b0ccb15ddeb6997d89439d7282de3c64ebd63c3f5ee932623cb1bf00d010239783f4ef9e423725927b7de27bc0da470de32217e50b431135e2618dbe80cab9ea16680dc3b7dd8307a3b882326492170511fe195c3b92e60719b5af3e9728f20f10a34fd6d5cba750d1600768280c0303daec31a2835b655a4cc561c1872dbcbf1033e377e00c5f1c89ac75413f0c2d68084da3727b13a32ca30cfb5ed645b39f0efcf4ecc469ab34c8d875fb62a5743715a31b3dcdec9cb8a943fa98d7203660766871076e64ca11739c138506a6513c4fe56bed831d266281ca4449d64de597e1eb53c09e1bc1bf90dae0dda8bca3a84e4962b1b9e11d44afe049b8b7236a33e6ca117676c1ae6b27e08ca932b22b304681e9792b156ab92905b6d61083c4f9e7e71018abe0507015289265b0ec17c4e48b15f9a4e44bc6d3fa02eca7226aa8b28c11e9d3409b54350ff984d737082275edb41e8e209492ad4b48b52726e0979a6847daefe170eb1d7b727d4355604c3bd9bafd1c9cac4470eaf8dd60f8aaa2f897da42033c8a37dbebeb5292e508d41d635a955bdc482a3f5042ed631773bb61e15af7cc784b0651248a454ab3490c4d2756454548d1de8bfbaecb572194b7c4352a27fe92c1a099f354b16ca387e45b9faee0fa9eef991534b319c2670e79036115207e0109e4d27ccc002178226376c3962693511db20f491929e725971cf507e6b82d560c01e976f512ce56a827cc1da42457c5235cc1197cacf60af12c62f37a16c728a71fc5ef75a556e61bb835e1c704a73ee68de2a73b9f3544746b98095408b5cf9432ca7d6ff81fa6963d88bcf14325fe4a215a4ce491272c3211df395591b2a696879dd7e557b7fd82616e17464afb8cc08502c9113c27274bdd4e7b367717f23dd65109b620780191c6a0fe759a110f1d538b03a104b72765def480bfbe67d315e77f658f9aed0b11cd3f102d8121459a048d61ad6d8678dcff8fd8f49ce6ca0ae20a730ca1d47e1d76815b7d7ededba1b5617dd25cf72bb566220c3be517ef2fedb00f15d379685468f08285656f548c75aa56c1448191d1d3b297d86c6a5864c0a363b1dc39b6d340aa738e59693f1737c6fd6d21a490d4c56bcecc9de93e5152087357b0978fd100f5c0a24c875dd68e0097b638c713126e96e087feccb76f696a957f65a0123d05bc65dcd6637fa4dc4c1c1956a409b4c39f612760af8a005669aea384481abdf5552e1e8aff6818be54056fa547277bc73da58fd531fff0faef95ca06e90d5ead3060dd6c3ac5c0b45f6224b7856de80bb82da924abb88ebf22a455aa2614917835296e8d529841386fb5c3d800a4fc8e756c6faa45fe727492e00d8342114259fe0701a6b077879ace8c63eb372ca477dbf549ccebec767c745a11b1abeadc769362a020beadd4e6591b4fb406ffdf67e515fddedfd95e5ff61ad75d09dbf46a0fb07394b5433850e0093e00b72c523b99ac8c7888398c1209aaef048a3028ddcfd85b86831178a010da7d10a722e923e4eea6fe8d7fe1f9a53cd08e9a0bd4ca6462959eddfe757b1432c94b27273ee18f3d41cacc6ea2572bb5ddd694e7634760de66726151ae467c598a2a072e2f49e2d29ecd320ed8a10da50225f80e7362790aacefc99c828b8ecbbae59285fd43787ef16810e927eb2d6859c931a3e456377338e4765792a6f7dde1da84697c26f363c0906b8ae64d028a16c14b804497ed9943ff9d692f5d8ca08722c693d73bf00e12a8249386f938263ee5d7e1d5e1886091171948af7b8cf444bfd473de0c7755ae034ed69ee3126ebe520c3de24be8875bf275ffe6133c610b08572678fbebaeb73a36a1f4c98e21ec46e1da096a734bc9e514051f96ecb6debbd5701a42432a69cda2960146ed4cde822803ddd6ff8789e3fc3ce5827b1f9fa9408ff829d11823f4e6d96e0b9213c7f84e8d04f9272183f21b06596783da2a0a03aecb8be54d29e3a0480daa345c6a74a0543c215e7f7ee154f299bc869f521c238a793a4307d290b930fa47b27afc10a624803dfd77025249fc85814f2e51a522d5ea7323026ea0e19a4b92293a3c8267ed46f55d0a575c59bd40d7555fbe28672af9bd06fa74365cf6aa0b565007b74261049d944160da1f8eedf7f3f1b1e236490427dac413e6867809bd2087bf1dbb5eb3719ba25eca5a654a9cca1711e6a53f1bfeb935025e40811521a88c190eac787dcf92a6c0cab79523cd122a53f5f72251d7c4d4ea42969cf343f70bcf51196ab10235ba63df8edee53a8810146da1b10dd9d5101d34f0759d40045197140750ec383fe8d17bc459fb1ef3312644a72043ba7d87cd9bce2ea989d9e682f9a8d543abe8cd8aac2b614f36d6504e47072f02c06df67b8daf183df0c49e4529a7eced0a4742e73a8145d1cb670442c9f67bb4c73d7f07143dacd886709391d1a80f2be2b5e49bd6c029712053c57d8984844fc697fc881b7d8d55cc9c02f7198806f202d26854c8635c41011ad56f45a72fb00e794961bd1108a020c7136c1cac073d2704055637ecfea258ce4487f4817b32ea5c16afbd8c20aadfca190236f11611238c85e89f2d31df96d7318c8a0455eadb78a71c774e8d10c87d8db0f099befdfbfb55e179d36ab16c97a13c7cf72d2a2ac4934e1cdaba3a20cd98822fd2de565fa9673834b0933d1e170afc65932456d266bd4155bc480967e1a53d7a5e6a54a9f04d1ce70391a67cc8c1b30071fec41ac37db351239feff462768699b3b94287b4c3ed6cda2533e0714ac8925318ae77756a16db823b7eec67d1d7229e395f19eb9b1962f438107d3e6c1326916d019720b2e0196a9f434ebde40192288a46491312728330526db73473d390b0a6889aebfd8ed8cede8ce47b763ced801c0018b506afabcfec1415e28a73620117d3e2b4a5c37404f68df9f4ab03a268280277a515e35fc408e014f053f91bd694a94631323876fd85d6a7b6fe6c2977be9cfdbde70fa14673637c2d46aafc3122c33c55fb946b3bac1ed94b92dc9fc71f4f251a09f9badbef8d749c743ccbf724793c9c4da05973c0e86ebfdc90f9bb9397c62f40b2b71a2bde005a9d4ea7d7053aa36608b4b63aaa729021230683287890d1dbf6a8af203c36ce158793f794131312253a817c983a965168dde775626a7b46d257576bb40cb3da4e6951231726f74ccd73299c98d9e214dc37ef5ad4b73d5df3ecf13b57efe561d76245a36039ca57db8f22cbe2062ae7c470008bd14eb58a2d9c60da070d077f0ed5c3ecf725144df1e6516548f070ce53707a90b6fb665159d28e9a48bfa61bb09a7a2a3683a6ed6678018c47848aed6e1a8a4357963d19a3fd871f12897e326ec3d267624d32cadb155e5721d30560cc9f952ee2c4ec8ef994bcfa622c8ccff63fc69606a63bb1025fdc0bc7e2feb55ba862d889fc5a36c602d0bc6fba3d1259e79dfa75c44a5aabb1a9bfe0369b8d9ef9d92902ec4c55444f71e7f4aab61294a985fa6098791b81184c7aa6f52f7139ed7baf7adf641b1ee6cded3f730afb8f07d691403ce0b970c1e3c584a25093c408a78093ff5c344b669bcbb335b95c6be401186723982eff769e7d9e980a47808315e086030c0ec9e9c5d3198411e976d12bfab2a9b4edbb9aa065f794d4a8a944343bd4009f07010d4affb1a7ece99fe5b37f5729bb275a3784b5352a79cfc4114eb8b5bb108313cbe24306fbe4b337b12ee7371b8f81c836fd75f1556d3584d8ae9915de694da9fb6b1d7ae8633c1693428ff7259527944d293f18d64249f2b0755a93f77c560dcf6ac172b5f7475690055db72e2df20fdf13fee03bd4e6b4ed69891c780a8f49b62af7a764893eaa86ede37665fdb99cf81d84a426f0d427465a63af652773dbc6fa331e656c5e12246537e72560940a8122d353ed7343d26de8dbba2eb1a962c3216194eb5c3051c3466cd720daa033c4ef934a5590dd09fc7fde7a7f44d2ae3d0cc6f8c0fde0908634c93727df9733b09ea09de29bd62257a5f7ab8882db8c6ba36ce497492737deeba163a65010f39b1d1c86120d0050023d5d1684a0a8a9f98a619ea8902016f45428e09dc89cbf223e76f2d5e7d50a98b700d886923b4292a4da31753312bbe37cfc020982a34894655f962336e8b3910af1c05d0af3a178cac94be170b0c1aec23f27276ada63dc7204463e017bf0c232f77b49a4690fd00040c2ede2ea6796075706ceea3696b711327fa0bd4c81fbb7f19247b2034cd2ad06f436a51daf100d72e7217123c5e7ff5c68516951f1fac489edb6bd264655ca45507cdcffa3275f86e4dcd4a6a25850f3e1ef56123345d3f60315a9169682aca41d8cc09dac9b50ca6727e152b17ea85ea32e8276e09a8e2d8a92a17407e281e02b312f28647f1940d4e3deb440df301129e1a681df33a071acd13c0058ac28ff94709f2276ec543b369e5a28e1443fc8713051c08aba50c87743a5dc68735d3e4510477a21f216dc5546fa553e04825fbf8bbef900e7075818dbe251b3d6ec90e82d5db56cabf93040603190c8d80a4ef818f0891243f7fff2ff396c483277a82f2fa7c837a0bf8f325b031ee75a856831649d3f0c0cc7bf881329a205287ad8d57e0acba5453ac642d7048668924835a34d11aba8a0117f60c673927be0c416ff7f508c97c6a996d722d7c320a0a71bf0e3a496d58a72bce1208a4a216ca8f5b5f31d7ce788983147097c5699ca47605841d06b85d9261f176ed10a50b1af5a65659bd1754c99ef472e396a99f09f4a5ab9d3c29fb8da4f4b893d4094e6ff773c4d621d9c4f4ebaa2211e955f0a1fbae854a4bea1c7a34b335f960ac8e80ad5eca5b5bae2a4ae4a05e9c7e65dc4b1680ef8e347e96b821e2d38613cdb2072465cd313b527f1c72a472cd210e43ea3b3e173043ac17ec5a648d8dff7370b490188a1ba304d69b715a097d7c0359d2532b6369267e24b700b63c3c1ce8625a7b4e37feb97293e5903c72278979049918510cc46e9eef4938c10344405496d168289c6537af1ea778be72a96b6d3319502e71719f4abe161c07887699fd4aeb802b4bb279dbaaefd8bc723dc4769c7e8dcb90ab34331ca53c8a11f7b6a34e5a80feea730fbc28a62f3f72bac779f158184e4c25f102dd3b9fd39029f496f8c568e37768f99004b9682f72aeb7755f0695eb8ac722fef7ddb432f5071c186a5cd2a77ad04681b36fd8cb48e0063709941aecf2df274d20e08f7922ef68d76faba2e76be72183e629d5c6371253937b86a609090aeb808d991d9103581d32558f3d8600a0111e92a54307727a654216fcfec98440a1a08970e8a06a08235f975b09e9a87e61ff190ee5e372e026a66e910b7f8de309fe0c0071a76aefa6b50c3600166d2ed52c3d17e0ee7213f8ff746f5d20c4421ccb43ccb4e75ba5fd81db6e11c7ab9e3a4053a74fca723acf77fd231bb4ca7f874e5055af915e9fd9dda8c973b21d12a499e1d33f6b330c3eb161e37980c5dad50468448d85cf8af48030a9625f60eb119f36c3bf3b721f84e76eabd24883394dd12da1c3a2713f364b862d34c24427274f17529c6d721ca44da8e64486f5d690540346c7ecd0846cb1da97fc37e3fd14ec4f8f05c072afd2f5ada889002f633cf0276e6dfbbd651db3bbe27ba77dba66d291fc71ea1148a4cea54e7b426bb3aa54731da9fc6c3d23a7d07548fb92fc7edbcfa511001faa406c7693e764aa1f4eb342ceca078d55e116dea35319097976a4c46c4746728524a52029087e83200b31bd901e37aa9fb206508cb9e297139882f805da2772f81019639a6611c963c9713a5efc055abd31cbdcd450810cd0ab1cbcd143a01f0eca7622bfcb270eee49ce91e1089378c5098a0195f3807575052c901b063742989ad6f95a40fc08c2fef6af726229c7381b7e1a87f99f509b2e915af5d9837209a2a591ce0bf29e78bc28dbd5adf932f1777e7e74244b708dd00568362f8972aeb2e373a5fd17096b56eff4e8cf75f807d7b542d7cb8f4e363cd6a48a01b372f13bf48c8e4af501a97a40a6b5036ece124050cd3da4011754df17a9d75d1e54d5d22ac3865142a9a885750af08471ee1e0726eae506658bf68ab5bcc62fef723d1e2228d1a985e789d84573e27980ef1e4d9d8d158d28cbb0cec672186cd472c89cfdf1d079db964f42a09726fa23177755f5b0eecab05879ffb33e8223114957cead2ebd2ce2e9975bcad786bfdab9d27d402cd9513d5f50dd1f3a4c9c7f6bbb7fbb18e9403fcb3e60736cf862327a768f6d764c339ef0a1e35f2e4a8a0472608fe84519b0085e06562d73a81633b8b3674fe7c2737e55c2cd7cdd26721a725692180b709d8cc805e2c9f9faa701e6abb4ee64d7d8b96fc62dd393fb0aef7256d79a4f67660363320534b418ddb5076a1e786cd87ac2124d73e3bebf97a448b6c113e12667a99527a769c5d5c69a41c94b115631dc1eb618173cab584d2c7252a4c70768f6bbcef53a79429b92eb68ab1a980c98c24062cde65c082a7eec721c49eaf7f1e445237b5771e04bc0a4588c328ff530398ce268e728bace48e672599da7d95af5235309562be7a089aa589ce8fb9f0569ec6befc1fe825be1827222fefcaa617376d5180b834ce65205a4f5d4a8a4f8832c4ee1f5388371a0767201570fe2fb4187933ba5c92ddf5474826da06af6c44ef9888384d49435783d3e07a3561af188effa3e60e6c33e261b51b6fd9c357ea484b3b512bbf8f2b02f7215c857fec0d0f58875d056f04b26269e38293c3e6bbe2030e949233d7be6ba16a7d9a45540780b5cd09c5df89e3bef6e60f82b5edfdb5139f5bb032b1dd00e722c44a59a06fa3f4eff37ac72d912b65c744bd9b1a485bbaa12742c3381b712721206364c9c4895ada622af1e44d079c9e352a64daeef67f17c20b16fa582c103a09623cd54b80c388f2499dada8c6c85a6acb4d8f62f68bb0e24b00e6dd0a1725cbf2e66e6eeb22fa07bfbb30b9ab205f1c7644dd062d5df040946826e556f72aa9f72bd014f63af445fefbed9e8dd62f76b5c763d98008797b8db9604bd910b3da0c5f4f49691ccd852a7f6c14780a1888f99e242381fc3762b8ebcf720240d97543030432843bd8e88f3dc2d4900c5fcbd2dc7f5d68fb48c05d20fb7334c3b858ba7bd911064ba84faa7b13b0e7b95620f603c02dbbe9dfa03b35dc7611272e8f0df1136dddbfa8d2a40801ec198bc8201a897437c4758abe1bb2c20e64672712335a8bf68fdeabb5db655e9b2d6456a0ededbb466d038f889fac8a6be0872bfb8c31a03fee3da87e8227f7ea48b9e97141bd7b169cdc750d74dd6a2f2bf4a577686a896e97b41b1f2343a371a82948a6f48f7540ddc97c844cff6a66c22721d1a7a088559d77b653c8d53a2af00f1de540420b7cf8ee3caa110b51aa19a3c5debbbde88182b8d2c8f51ae4947843805828a12910619db26197ab099028e7245a476d59dc860419b604c6fdc8e9843164cf29973dcf3efbd448eebb4227d723d3fec6cf572a09139b65c77fc8874485b8f3557a4c6c6e2c30f0e795ad8491464cf754310e9139a09e442a26acd708515e3724e9414b0ebc65a90ed871c3572225008674ad426f58f0cfa79f0514263a33dd05e738f407c77721544b37b5f72e89e5a1ef2624dabd6b73bd68a593a3d380588c3cd969edb1fb43f6bc3944946b3e38349c1bfe1f1cbb6aceaaf6ae3802924709398761d5ccf82682771ba461dd115ffdb77da0174dec01c584e283ae5af56a668319f00eb103666c68fd77f72b037411417469bed43e5cec2045b964135d7596ea4fce901644fbf83e21d5f699dc7bb97b283e30f8803ff0d1749a4c67ef21a615f7f8a798702c1e1efddcd15ca6e73cdb1930525ec75aec785b8bd5c75de1ceb4bac61b2cd4ac9f61e0f1372c26c93433013243dd689863d69eefdf3790d167660076ef87f22fdafe6a2b9681d764e4716cdfd16ef29054e126f14dc26dceafad68f729943eada22c6ab21721371472511b555207e9cf72d58d1f0b4b9d16c7f9a417014647231cbeb76733adfc1b38663b006458adf884d5430864ea255580fdbd285d51ded5d7604a9790a970844d64b6fe32852d8c3637d35ae9452133fcf9bdb313f7df62928cf610d72718da8902671ecc6c5f237cc2d27f59c4bdfe81a9a4370eecc6412e496402115a90154e49954d4814278c944978aeff551dc2575c82d6c8486c66fd098b89672f59218e9937e2ff1fed714b1714184eb55ddd09b2152be469bbacf584e7e8672b0a8604732ceaa86979d6e7edf4057280950fcd84608e3d18523427cb0a64072208a927c0c0350d92a40a472f5a80794ef5b8c315c38ea482d35c45272d03b7243f2058400cc487b481bf572deea6140d391d55b58d208c6d8971e3547e54b72d0b2d0fb142edaf512643168ad4b0cc7231cfc67cb2ac259a52911c558f8897217cea542a10a89b9252a3e893489ae0abf14fd9c462fe900182b4918fae68d1a73f4ae996dc60cd8ca0aeebfbfb7dcbb98a51e4f132646ead68dde35374b505dbd644d92df84af4e45f17f05440fe0f20793ce04e0e2c9cbed80675f3b16db10838875e56ee291f14d45d93f7c7c4d24e5f10724811fabc150e5073b20416139f0dc3254c6cc032ddf68cfc72ac5461691183fa05895fc097d559f78c0d3de72045b949704acf81ca6781d351985ddf6097b4ff88b1f01fe8d258566dd6d5b72c801c842d436a3d02dcd2326dd1fcc975a543f795717daad9e4060fb48d7ea4caa0cc6478d211fa20ef2d1ce02ccd043d8e4add49b7818558571528ce06b2372ae74d178e00b80d691c59aa58b8cf5e3c4ccc25077e6770c7b9378914bca2072c0e82e24c85dd7b19e523f986e292afd6a18b24c1580edb174a8873b43c17918a24c842a04aff8ef0e20ca53f8ef707da80058567b57d908d09b8f311c684556c963cc386e1e7bf9089283b5d6b53c513742181c588ce6c2d71ea05008d8f272fcd7951a94953f284cc36d8c9357536176c493522d58b6ef5bd0f309a5e895515171a8229c072172924264188908df0ca987c41bcb1ac96d25ab02c2a15ec672a0643bb1021c836a367b57b0d08050591e731cf4767fca76d8eadb3036809b72a07c4eb48ccca91c359b809804070204c3fa487fa9e1eb4835398a0b00267c12df9f77284ffb7e859705f472f175d4d719a1823a68f8fcbadc51fb679d810472cefb1caccedeb6fc2224092434e67f0186eac9d4c5c7dab58aa9d8a8648cb44be4b6d3c12e39f087191eae9b5dadc37681d16c80664262733c7b244be2b63d725fbde0bc42b7a9a0432c5f108cd55676ab53d07bb4195eaec4f15ca26260564632e3a07aed6fc6d77b4b0a3313027e785db4177bf3b8429296f47a7439eff372a7cab0ecba1e2e70dcf91ce45692ed77c153655ccfc8833bfd5672df76b2bb72f768bd7f298082a34bddbc692247448aec6365bd417c625f469dfe1bd1ba4a72f4ca31c2004e19c7b7fde8ac08085c563c007e8140f5de0b4f5b27afd8c34c72587be8defd7a3fb2b3ff043dbfe6aed9ac3e6f585989eb0fc894a592d89c56726bf87e7f26af533e5d780c12d6e369b188e59d7087009f679c8b78dfc186187266d575589a8cd51df70ef026759364284b85a8d14fcd617533cd4c11607bd049cd772b53143843baa538b7f2ec218bd2395865cfc9dbba9e4c875fbe8b42d67240491b45497568960fc75854cbfbfc0c4737bcfcf9aee4219644228b48f3cd6910a5b606204d9146e349306d512ba49f38bced52f4d90f1028f62b8a5652355aae2647db9dcbf1046744f32b2510386351930b604f4c7536446333411e62107201f4a1ba4755c1f65f4b9cfedc10c74fcca9e46dd52c488e2efd689b3a926972d4f856b91d3f1ac9656ed85e1353428fb5443ad47159eef72f96ea51fa3f8f725acca73be99a0cf5f5ed8689219c5d631c227f071e44d7272391adb30b76bc144d7ff2a8118171ba1542a4d2f98c7547c748ec13e61b86d278b216dd055b2272aaef35a39148f120e18bb3e0ef71c294f12f26c184da925d115d86f116ea3147e39e4065b04aef2059e704b309f6cfd936cfe18a2117936c238b9dfe8c25b972605128500f5a78037a811a7e4113b58079f2cafb59e1c6aafd2cd6fa7ea26f1d04f0bcc192a79a1af8bd548dc57c5500cced2831fbad65632987d5a2d0c5c94edde415763273e0fe265165d1491fb716fa7427dd03aa2293f56d933777a7a41247b11abff7927375ba7f5ca7ac01b308ba771337bef9444e796be88fe419c47267fe5b8267b9a12abe2c4cdfe396b2a2965c5f1e69eae5cc4dc51f6a059f9a72f540004a5de48538377f227a0afbd0442900a11db1cba570b735cb87d123d408bdf45a0d8691041f6cce87eea0baaf4462542fced19416b61f82dbd50f59ea7242a67b4f194e8f53ccdbda79daf6a4d89a25e8d86df79604e5ab2dd9847c6600df19efd8e9201c3f21e2e775da63148b81f84d603aa10b55d7387f83d48417720febcdf146a85ad6d01b9b810d7dcc29e4122cc48fd84315c8a44f27d74c84188c0ba8dec62f4dd48b48464065a2d13a7418daa9c47672c99206777716e4c15fceea433df7dc6da0c7e0d52a7eb9e9dffa7fc4c3f7c36617c0d36d2ae7ceba72e0e214af15f2e52a6f09ca07e954a1b04ea61e6c02b67f2be241b66d18adac0e91a60820c8ed27108f2da625531d263cc5be9460c52b5022bb41febf74e725344e9c45bf897a9809fe3ec2d46e45f35c53c5e8dd191456b33e7ed48f03fb05724152417b9a8698a944536b39d35e52fdbfffbdcb01b6b61357981ee1a0ac1272e39c16e55a9769af2ae69c507655d55a26d0492b03a333df3de52c1be038ec486ddd18350fa2e40a0c278fdcd67013a93e78d74611f1e3b94b514b443e2a6f4881c34edd8f35ad66cc1dca021dca7ee804d0b2151948207e273e952c81e7710d9cbc0eee81f6eac260a5020b3875f7853d6bf728cceae9791250b68613fad62e2a4fddd3ee622b868560adcbae17b5daa385bfedc75fc2c1a04aa4a08f10030bc95f35af5b66fa5ba7d3707886c82bc60e2d70b638126e0ad39ddce738fbae3ab6ef3dbae58f91a667574c32bc4a8669e6ddcac0eb5edc129dbb3aa6577e3c72b9a61619d4d6a02052570a9eadd4bf388096d69ae721e7957b6c334d2711323afe7d7a6e82724ded929ed66af6b1f0b48775d783c931faf0e9a851c5d21a2147f1e7d401780b81107113deec15d8d716c9e9e4c32070709bdbe6fe6b6c519d72dd1ac661c9e1b08f7bafb76a7875b61e38080d6094ba8fa69a760d7450e62e6bf920eb79513f3df763aa675b3f9c46bc09e6254856ab95098385d8a0546b83723da479e4e33eb38871e6965a76cca004fb59893ebc7f81304525ed535eef597269e3769ff9a1b7fec57074d84fd6670d3a2578ee723c2c99c3a916c0b52173116c94e02aa24948515af32b02cf8cb1f8b73c47fedb82c596658a98b28b920672ca2e8b49ada18d119b774641b0b3672341dc10a28e8d362873381587469a103bc807fb6381610e28a0806b07da5e2ef5e3a9b21f9cf2c6c0757a615fb7c3547271a8c21c0eaa80b0b6967fd336715fb78346d46637e117490e4b776d72ea9472041f658e8ba20d97052090adc1b2ff831a885c8d3ad6036f54c72fa243cba05f9274632b6f3ab7c0bbc0b38c6edd4adfb59f824dc12ecbb338338cec0b4896571d6271feaed2a959db0295c7e2909348a6e6afd8d744bc7da97120adb861a4306a0b4866e801b2beee3ba9134a700f7c96ad69b4334a96de201777e15f4a7772bc8667933e853dfc33bda087e7aba1aa2fa51b84332a773f5baa5642cdc0d7548ba0ebbdd597c1687577ee1d6a69aa839c357726f760ea165bed6f85babfce1fca9dadaaa2e5b0e2e01595e5ca68bacc405899eaf9980e8fd53472bb1557400f86faaf8609a2db5443dd159db5ec82539daff0ae3207a02f140c40bf54294172b7a44dc042c2112f2816810d2f590c44377a8804d66acebcf8dbbd72d2175b729131704fc0a28abe1a15c6542ef35968b0d207972a9b78ec73bffb888d482d72705f370d6619f2bb9a06da5f7a8c2a1b375d5b8bcc672dec6fbb8eba81dfeb72da29a7ad81649375cfe09704b03989a43caeee82b6b58685079c8b5ba084226bdc4f0c25d7b658ad7948a718384acd0bfa36eb0e2eab2e4e4170723f20334f7284cd76e03d03dbfe7929a0dc8e523be644ba2948e12ec7fdc18c538e18aa9b72fdfeb3bacebc2a452d1c40645739bc432103860ddd367a745910f21e9fbd2d72eb95188476eb72f8e100ad30a336d7c8040db6621d0dd8fe0d17c8f395d31672bdec103c84189955edf896e2b04199717ca28af2937b483952f398c06dd2ea72abba6e4d3df4eaf5bc1957b3ff09eb266be7a38102d73be5bb5051b01bb32239153fca11868f9b1065d75cc560d98ac93f0514788152017c57d3516c507f5072bc5e9a8a8e8de06e9d48a7008f06af0fb2b0dec7d84296091e4772263c7fe472826ee998a8ff369d08aa8b6850616efca82c2459e8ada9fff476a8701e75f60ed15d8e2cafe99682f911f1c4e48217681ab62dacbfaeb7d77a38a8842ba8475b8757fc67dd4dc35908aad60cc234d03ba1e0fb8943119c806b8a4c5c03ddd172ade7d8ab0466e3fd6812f09677b74f2593b5a9596da6c1f4e8f62068c81895727f49d37135697273d94e70ba7e252806773d23c30cf9dad18577ed61292f5772394af3a9ccd6a8feb3a698bfc8155cdd66edf70f9f29a66c4fbb2ca085759572a244164db72ac89cc8a19b64cff91c08e25c3a6824b34400f2d8608bcca8d8634ecd61c8241c7d5fabeeeb3984e160884900bde6807bb2bb06e2555c344997726ae36c6668c7655c9774c65aab2e106e306617eb9d9a471958df4eda80e30b72c7848627a8a249dad85c5de7406e7bb8ebaf74e73932ecc1d120d7a31cbaed2e5b67e8b0944d1586f89dfc3fcbb08e67e9e95da606c52824c98a0b1d73b1990465c966cdcd0f39d51e2f7d469395d89530ba3e712335aea25f5243259ad08472a17e896438f1d7586e136b4d4c8ee48b107de5d01d318ace86013f1f67deb972a48c9e528c54d7d59fa761b74782d7fea3362e2c3de9f34d5290572da153cf72c6be0b0fd6e64e369d51550f6cd79ee6cb1703779ed4e9cca3eb393afdaaf7729af8497530f67b1d288187a8242deaff8b0a5dce38017b2796d725a1d319633528fb6c40daab1ae686370120aa70ded42a92ff5d12beea6dce54061d4bff44727dbd2d5e5768054a34a374dcf07354a32f5e708d2fbabeaa1d5593b2aaca97728de667b66d82251949db5fab984e98aa70556b8444ae5f477c3a72c55ba275723578788c1112e698adb463e5348f72a38f7645247933269e4c94f995c1722149bc89cc91743184b0f8c2f68a1251c66851e544406f0578428bef92caf4335c724c1ddbccd0fe2a1c4ba7b499f73acce71e45f195941f48bc24931e1c3916cc6c59f136f06eff5c34d70887452a474ee016c742a16259566affc00259248160438e31a9e62924f09a473032a430bd278988accad9960911774097a29cf390097202d4428a9753534b314908f55db2266244d4e053a86cf23090f6f2de1a6b9d142f657a617f468a4b2049e6b70e6e8c250c883119998a23b2931bd26403ed9872d8525d8f11cead9e91fd551601f4df43c60055db2c6def7941c44fe57d03636d61827f1efe162eb3dfe1dafe18638ed7d8a732dfea947958637738c09820cc72bb0f90dc6a64fddc9f0c97d932a726aac303282c5fce2ee9633fb274c692472714605e572335f0daaaecb7b79cd530f0fd9932f97245c96b420b9aa6b55e926261a2947a4d9244dd6c8f55afc081f61499941620639ab54fa3b0ef95a9f35672d77a4b98592c5a1b9aed77d5700730cba392e3381313e3c20c13bc8b13af567252c86483fd171668ed13694ed3b90453a56d837ced52f2430f509dae4eba7b72f9f7bb123451f11642479e32e9af18cd06ecdb08691af4d80ebd47dc72640d666530b6916c2277ed9cd74ac6fef48a2f7d83f0a56ae1ab8700f3da8f78ce78722095094141b8e247c2b5ed394fdbdececd6b2da6a7f91e91dd97b66bf65d543987e111d671b80b3c6474ce933b287ebf1d1a609e3680fe01c9ca5cf6e2976266cd229440721f105953260cf52d3a595475ede4fc407d4bb93b844737aedf4072abbf9e1e3ab88fd07a32e031c47294f40948f56996b3f8a9c4b087bab0ec8f57b724a508d990cf5b37cd8a3b7a85dc75acaec9dea59d664c45a441e6f75cae72eb9f6d2f6081c5476a375c192e1cb1943070520a4073ad3c5c76379e67f96e72725a1ef9e1221ca2c8324cba3b5fa0a1190e6d170df1c7b542e841157060f972ed90ea2af2634939db817da0b0cd838d5296fcb9e07fa7378b423a18a89f5751ae0e3e897eef0c5dfa79b7bda207e50a5a2ea2c522160e2471ba4eabac775d728b358e17f57471c2b63bd2d75467eb92a82865aa821d179921aeaf7289db3e72703c9e75b0c0e9491e13699db8d9d67b5521bb130c4cdbadf159ce46fa3df16c7722a375678fbcb9c6cdcac23a280ee97cbbbd16f9ac7d175183feb6cefa31725c9328dc24c13f1364588fd105fff2145caf419a07d6ed13967e2cd92ad5da52e994630b2c1dfe230c09c3658617010a702b560cd1bdb5ee573841a1f7c07b7215a99dbddaacf94b52acad340f7675e143327ff50f98db610c9b829299283d28b29e721f247fee14f222b0729a2ef6e6c7a981a8084300a46a282ef6aaf7f03c5a2c42c4eb214de181a61c99ec7dda3d99c7db4fcc4d065af8cb7c74dd8a4f02b67544acdfdd807f97863145f0b1f1dab8c05695194b26975e8516d89d5f737291c2bc682460509859a9a86d3537a0b230b401264d2eaefa13a7b9eaeead6d7266f54c12ca2f3c09bc7c9a39dd47c878543848fa581635fe3467a530e19ddb72293a10c934ea654bd107451a1456aaab8f73d51f0e5e0431ba56bda2aa752c3265eaa1160d16da0ffdcb69d348c31e2f68a684a39721264a8118252f95a56e605660babe1c799c190e96e21756d8a8b17ab5d46c63c742a99538553704b5e15619ca93257e07c0eb2c7efe341739ec8ad4daa6f47c0d3b2b7a2f269a74d809486fdfd7deed231c65c176b386b57e05027de2719cccc6f612e8e2b4d06c56d067b8a1b297afef5604d6b7d7a3842deac5207aecee9f9bd31cbbc2079b3288b87298d6f145f3342d3a674856e44164bbd361eb72bffccacdc738d8b33e3bbdb07241a4f63d1617a37a1623f9fb8611ad122a8ffc052cfb2b2bcb00941cffff94728f3c976b3c38fd60f094fdd9e73d8f1dfbc746ec308f1a88b8ad8ab1923a06727b618d5a518de753134a929927914932f6e06c8e50eeebebec4311d5601b266fa2b5279ba961eaf87547f6affca36de5c5993603b691b3b1ac8f52ec9cc23c248881c4ee913d0b4678af026dd91b64641992333a1c02b3c5414604a4e31e5e1f2142023c58a6dcd698bf32d0dcc75b0243e975a764475892549de69a32b1bb334b7ae83d3b1e87e6fd69d13cb5b9b34c9e82925b99895806aefac3abc57668657b17326468541c815a55e20dd1c707343442b300b725bbdc533f25396519c73392d99cdd979af1944e9efdf996e1ab094ac8a7b3d311980717268c9c72524b720708472e6f103736e8a59925acfd36b3fdfff3823cbdbff83279268a14355a72e85efacb661020cb218a61ddf1268605eb533818f47ae3c6afb725d3f39cf5723f6b50e37f0770b30f455d46c202d7bed5e75030abdeacba9a3a7f30e2a67f47d733da46c4fd1302fe9c4d95afb1d838236c652eb40fa72fd43f1a4d2368d9720d028e912d0f07f6501f1b764216bf495c7d233a2c73fdfb12a677de3e743672c5d902822922dc703a94034cc05a7e2229f1b90b7e3147216879b734e22e4b219d497b2692ca6852e00847540e473dc5cef4a8c18ad9ae1d5b43a2c2c96f0e725a25880f46e9195d88886bd8f5a17541d3beba1e76970aa19535c7e12e4a065fb35e985159dc2fdfc4a3e67fa6e70f46076a1d736f68ae1de9bb235336a0bd724577c1f0db83ea2c9675e6aa31080769aa82d489842cb1f4a9ec75d345c97e2d1acefe1a8abd88175bb2c77a5bd29a431cc0457001432666de0e1cd2ba7410612b26a91e944a9616a0af90c5a69cbf6e09c6f133311e50550db6395d4c8c412bf5471d00d275813501899061af57cc10435352e692c0a512cb51ad989e52a1728ce53769cc7d3fea79c8fe45888a51858ef26093d0808f7c4a156ca26860d834dfe7c8cea463d4411479a3b7a45f7d4076788b33a85bf2d2c4b76b0bb9c64d72cdd2f921b0aea0f7a9d423f9bb51bd84c51503c47015a0c6b4e29b047abea126d893939ab73003f0b1647669615e294bd003ab65d62d993ff4133bb2c6637d722ae965f3c1eb77ddf0bbea0f9b73cdfbe51413daa868127f946cc7a4e686d972bc0c9808faf9ecc5354a555498e1a3c4b45cd0a9979f39837e2ff9c98b7ca2720056887a90523e12067069b43af10314671048e18b1db1a9933281ccdd072c246d505b5d246ad1d63b2c2b6a5792e51c0a87c6ab9dda7f65e0be9782df734a087c4e5c73643291725a26158c9894f3b41496e98b6af4d894043a739b16a60472d7c478fe820c12e11f678cb3599155c7522602717591f7a619b10010b5d6517262d9a6f4f7bb90f9b3dcc339585eeb1d1ed8c1ec43328e2fc71ecd41de6f2772d719476cf44f75168b85a2da052ced6af5b638eac04fa4411fed9a0ec46c7a5b5f0235c260af3bc7ad6212ed092e68f0cfef2a83b6e3111055044a09f4888b7213c52d8db74aebf8dbfdf5d7c7120c1b841eb7f389079a1e81077fcd0da21772417f8246567c708d7b9214705775ab3f3a1bee950dd4cfc212716c122f10f032c631f3b16857caebea4719234a469930af35d420fbd3c46b423934dbb084dd43e5a9eb1488ad0594dfb90d4bcd50086664a459948ae9fc3710a58f33112bd272ea3837d31e8400576893e681eadc3a553647b261786d4d5e32db1392036a15725c06f5162aef22a8165c463dd168b671135c4e98f39cd4782706819a4f30fd72db2b007f98f91f7325a8aa9e60a41b0eeee4e8025a2b8122975541dab67cd3720e859fe297aa0e5413e8e22d01ce1033d2b6ab68891d3ee15eb8295b748b187288870d897309060c1a3d35e15b2e9864ad915bf36a9faeef7267126230f55872fa6b25f77b3568d784e585c0eeb3f07921f08fcd884c3a380e885be584d4132cb5129ce6588e55b1743db248d189704049aa4be53ac88fd8199f2a71e8ac44727c08d6c792cacaaaf7b842c9b599dec0a3643bf655cb53d3749ccf4f140bd97269083e32f4c4adab0efe2472c54ec3f10aa805501ad01916151361e2a36f244ab00f949e113c40186069405190eb9e3f7354d0a2e8dde5513b054fc5f6f8296ac4467acc936a52a8fc11de60f48d9b76340c547c3e149d1d70b4292d15af3e72f8261e444dbf84ca97b9619ba313a5f157f184aa6ad389c4c736983a1ed82d729e077b7a00e8b543fc65090387f2b85afe2b1586c2989cd8104a2fb054620572444dc987d1828f393f6a1d3a63b2d659eac4ea0568f5a9c8d3f2290da409592be8a341f5a0000de6a513325996d1cb80d3e90b57e7bdc62463fab85507219c5b82bccfda3a6933bb2db0d354db9b70e4503e0146aaa70e4b6913cc2b62a53540af20fec2780e54113566f5439930ab0b28d43a58912aa017e6bb2a3dd496fa66a5cc14a7aeae74e2a7ad4a11436bf7d084f488aaca3736fa1ef1a782c53e82006c560f697371647f0094d7c6e3b1f2f1270b812292efea39ae4ac8d9fa395f72d1352dd02b2c7e91eeec4976a5b339759c08363ea2afd7a2f1fceb9f4549135e2e8b2f3b702170d3c8de69b64264fe5a403b02b9880724e5b395934869bdb8726f4b396c0537fdd4927fae11bbccc15855b502d956e628b70219bfc6852c4d3fed42cfb3fe1fdd08650326422599a36355d5eed60b9af360ab1c006913d8197226af2ce0b2c46133e242556e21f1d6ecb5c64595b7e939215b9e09cbe8c028728dd832a48c64c9fe4f9bbaa19493c2704a213edcd50621dcc978453e1c663a12da747f7f8518bec68dcbd1b4dc025a024225f7b2604d688dc88eb25ecf0e9e20c50c121b372a6777c9e5cc4ecfa54ca721b5584a5d66af2b7b6aa7d60d1fae2143fbce1b69389141cdf8a169f02680c1c06bd3712b14e2f574282d2738a61672e25130612962b7eb61edbe7c58a28b9ccf81aa504b3cb3704fe76966b214ad1d60cbadaff5de20965b8f4140ed48197289f0fef543766ac9ffa79bc010bf26728cd878bc4109af81bf2bb3c209efbb8ad3769d8db271fa01b995cf5d3b20887229b44b8a02f126c117d39187295ec29c206ffb6d724f80aa2bb1f2931160de3f256cb4e7ff419f9f85b22a544c3ddc527e8f379318d45cf8b7220edb25f8c2053b75b2881b26f0cafadd4be99c2242b8f97d6011bebdbe44c1aaff2b69bae4729230d1c625f6dd30ed360edee413be9b8b956e9b13aadaf8832350c6c0077572ee74082a2b69f6ec0840f5b39839f7beaeaf2a7f99b97f63f190d641f321046523553a456a80ecc45724115a34702e378ebd2bcf218ab0d27cd78725cbb595447a1187ee33dee128bbd03c48893f03e33115fd17c2033d6334bde1f927680e489b4f2a46d85ede81d88fc560ff0c024d053852d5b8ae1e7b7753dc8b5a7d9b4873b64503e62104b14be5c38ecde669b72eeb88bd812a2dad5cde4b7adf230f72da22fc5669b42efb80a4c54439bfc588111843bd480d3c887a999de660ecb97237df30f3a11bded166ec8fa5c90a39666715fcd678ed7aabdf8908140173be7294898b38ee06a4401d43e1a73671927cfa5b376c3043cf97bf20620b5aabff6c8f65b34d9941d576209d79d98f561a2a7c29413093db971de5c0f1c5029a546fe1cbd0519c7c5c96b10f3e0c68dfe3fc528934c1cebd16cb558fa28bd8997d725f2a07ee8934b377b553114beb98c9a843bdf8ecbe0b2331f88aa6016e2ebd72de10839e74fcb0d8717a5d93050baa047a7915a3b2f78c664dd1cc0c01694a72d10f150ce3484090e94bd65ed3f3cce78412f8d69429030cdb6a86846365df019245524b34ba093c02e6304d8a52c7f8ee6cd6fd8533c25aad4552c24e3aa772d515e397f9a2cdd86407f49178848e7ef81c33d6b15f753de408b5f7f6c8db1b695ab88daddeac9374c5a633fb45ad2af4c9a6f0f657f40815ec86af12ab7872844a5c49757c403dea91bdcc0a7be5a269358209fd67e3cfc7f4799099dd9a1d07fc469ec7ed64f9d1b25fec921d3b670c03068e898d02d5f087143631a3c56ed88ff7d88db356ec9bae5ce747e718f78c3e6bab2548a48dff6231d09954602637409dd027731e416916429fc0dd31e4563fe9c40d7c5aef7e5936f3fa992f250701fe8a11705ebea2bc3d0f56961b1e4828509cb709d9a65d12642b76d1e571d8d547d4eedba5d16caa4c45fe6d39db3c61d3567b6cc8bf9871bbf224cc5112533c8a25104c66a130e490f431f0b54a30af3b4694062dddbcca055688483438795a57fdda811e806a09f8d59d1ea68526fae45555a7925fee462e0f9704bd38be90d42b7fd07468af992930daa9b7454d0844444c0ad6a6d5b52246f1295672bdd68664342d497de56e2b45b9a1070db63d2caf2dddb3845344396d60997d54a9208d25122f5de8b21bce6b2ce7161c214aa27bd29af37c4ccc3a221846bb7255a8fce2b190761f28eff8c4cc4cf874f5c1d39a98c86ef0d2930688d59b54721120d689373d2d8ca9cc7479c3e5f2955332c17b7d8f245febe1c5a873d22337198483b0b01bf03d2e96bebbb5aebd2288c5a0a984a72641278fbfd0e5bb3e51ca98c54411f9d8f1bc5b4adbacb34ec9e1c613809e7c15b42a9b3e1127eb967226a30e2232e58c76d8dec39e1987056ac58add60984bbc2a10a41dfefdc78072f441436da01a8af05ee5745703d8b38b244ac553a9921299259622f38a52f71ebe636527f2c8256b636fccd5d7778aeb724bc224c9e2265d7eaaaf400655be6d44ee3fafef452bb45b7372ae1385c620490913cceee9e16e942647ec76201c728fc84742c5aed24288a66613b3495790bf2db507120629f47aced760a0442a729760e066e6a38f5c49276863600a445ea257f79a6d8e81400a614b74d005671df287ca2571db3f755b0821db04177448a350c22a01b0fc94fc7b78dd32d4a719b7f81c4dc02ad2beafa097245c41d66d64d12780cefac9c829340234d2b86372af86b828cbcdf47e53e07804bced57483760fbde886561f41550d1cc7e37e17209f72e39b3f478d018d64521c8707c7246687f2b3510381215906472c47871627ea0f5ff4831180a74ea7eeb1ebc550ca8d30e910542261a28c44acc4c533172f4594c85f2d4a2490020b8e5036d2802fc8b885fd8ed33048b0b76560a0bd97204e93007e9ef0e6483462f50eee87312f19a0ec2ee2e0261bf5e4c969464041dea318e72ce827ea0a84a599221b980a1a6754cd1e4d499287d13a96a727f1216ac1e468e15db8bd3357b6869611b235e37842f9fc2adfe3d64426bc259b36972b12be116554f5839b4b10fe809b92d5ca73d1560610f38b6d78680420999204c6fdb033e3931ee142c114fc784918ab61f4f08937161647418571bbc00197e72b6c4ec0c863dd5a86f23e9791f1fdf778937c3a3aac8e197d9039f4cd247ac2199b02c01b326078e32abc24a947881c29a4b41e9c748331e1fc74e11b5f6137272d7b3954de20be5e431557484c0da77c043172ccd1d42b289ba8167c204a0723350aaec1d3f33e2e251088b463fefc7ac2eb6e57d6a594d0be212cb96109a3fdae07090f6a85d19f95e6f674d23fde107a2066c99200329782ee05c120192328ffba163925a3ff56c65977b425afcaac1f640ddc75a80524586af2b21aa2e7274ccbf282a3746bc853959848af69809cfa68cf985c7413667b5f6770367e0459e94c8c33cbc3aa2b8aeff41701e01fa5a90fdfa8e234db53911a400967782729b58f8ec0afe078a21ae12f1185a72baded4f4afd1439f55f46f20b2ccf6ba720a0fb254d945c242428fbcb2a2de0db6dab327dab3e78b04dd2945c2c7b019007fa21e0b93efb97bb85ec5e57408d4188b676ced60830d95d279df90f31d9401f087ea7c17aaaa89350a5536fd4913731b6389da336268e60215a5e647ab3f72428ce9a2ba56a2b3d55d0c774b1930e15a85e406ba3a5eed086f0618529f9c72a1413c4db016293b9060bf65aa346ccf40cc2f7a9957cc2a5572266b864c8c50448468190fee0856219f346f9db7462b357ee2db0eb32a5734198580c8cdbe3f9bc07cd53dd9e017c20eebc7131f09b12c5b4779352ca6360e75913b0ae4f502f1cf0fbcb866b27256fb472af7c9b8c900aad77d90da18298d6c0d707dbce7240407518757077177999e910657a49415b1d5e68d3a2aad0bf9f06615870e9e72afb5a0f7cdc408e7728641bcbeaa01523ec1654d040445a9e78ba0e980b4042d5249889935620c589a143f329543370175aac26c8caa54c14681dac5501d7e442db930db8c2dd75799653c82c040c9d9a3b7e96484b00d275aac283972068959b35b2d652899ab6859cdf260df745d9662fd74dac0dc260a781d98c0bb5a9c5f14aade7c0f5895701a66d018d7eba7ab0da36c6e2b602819bc8a78d2c57190718093a0d8879f64aff1948d74b963b6ba349fabbf0b56e732b7e14a96af7d9e4da965299db548f8b1a018d9617d8e8024917b25850fd80d1d1ab75e09d61d9d72f10c5e1018c82027e70b5e714ffd2922413a9c4fe2ad1f5645270a9da084e3244b6a8978f2c073ce2f34fe0fe8e1734b36e375a5b0fdbc91fa2e09af0d39ab72cb99564abe16c94a5af9da8d8971df17ca90ea7277c7873f5d6ea500d48b1c72e1afcd39fdee23000ba6c42c1c5c82333ef3bfcdc1958e3f3a96bc76fcbceb7217414cabbaff8ba8d48004f6e26778e78b53c5dec3d5167ce94557376f889a72f2a1e970b2f8cc4a87ff58c9d35445e2b3f339a727af6f4bb49665fcdcfacc45b6d5b435aacbb1165c90e4390589fb731329240a7464d807ebd9dadbbcdf7a726b3ceeca209c1c7a07697d740155ae2c5b93e81641ca507dbb9ca3d3d2843c7265e589256e245f89652e8fa831ee6a61553061b1fc231450cd33183e3a28281f663a6f103bd6e9d40973845ab6ed39188a7991705715063ca7082c917594ba5762591bd9aa371ff64cbd57014d459adf59f941d9338bb64a6e6d65f644b2e024fcfeffa55194c78bf976c41f88b180ae40bdf856852129891028109d4a5746034eb2e75364d0432830eb81f9255561d0fcce0759cc549b9185ceb1045bbfa972db3ddcf215af05c3f2849397ca49716ccbc78bdab9e13c9ce4098642c39e2d095676f1504200788ccdd92a8496fcb83460430e59a0ff85742babae6baf27ef3936af693d258a9485e6e9fbbe3a68369b8b39224c988ef1937f80ea8554fac47252643861d6566b9274ef1f6c7ea01c84a7c1e53b42278afb4a5f4a9080498c2814fa02575b881b39bce8c36f91318728f2d3474837c5728a6cf1525b1cf431720e019de6ce4379fa78c5d7686ae8e06b6ff50cd47b3b9a5aa7fe9e510363257236ab9be05216438c717dad4e97ea4e5f820f389204d5f11aff7d264f164ff4720c4962b6a51e62ae8bb33ea3f87115e96a7a0ec2e405160c6bcf3bd6283be624539d85b3256b276b252fa2d3e64fd3e9b22876a9c3ea94f84ccb117438c938725aeca381b65d5c3cfc4f53ebc633000440c428286d2ed3b2220ab0404b406f2bd5d67d61c7dcc56c47dfed17b3c9d6b550e4d905d699b9e2dc9e09c3f3f1bb125e4f740926d6c57d58908f692963bf4041d0b86872f182e5cebf5928f47ec00d2edcb101b616485f62597e76e867b73e97d09a1bc0bf0852518b13bf9645c4721636bae686491a07740d418aabfe5d4636ccdfd17af675d65fe75484e0e74d4253c39ac858607cccd971d7ae45a5e4dd2f3f3ee15941015834a7b7cc784b155016181608707ce63710669b522ae8efb0545c97a2621ffb57c20b13c4c5a1955df4dfe8b04b96bfb44a5477cf4d51fc23c05b42799d4f14542b69d06f611b3472e88f37fbc51e92ac6939b0eb6b4f3c0f864e063df8b5652a9b8192f4d7f86226e359772c9ee8bce3a07d9326cc26f3c8b2222cc208b719564b8adf2db27fd045ed67366d1d356af38a67ae5e36ef40f501f6ef3c88529549c3945d007c6bd8726310a276a0a82226d26ab478a026916cd56674dc717186c2ec9a5106244f7372320269599d66670d9bbd7ab777c94f54e0557f421ff172b1979a7774fe1ec072cac55986e478b62e20e2c6f96b1850846ca0749c6c1495c07e38c18cfa495b2ccd536af563d813b26e9bdeb658a9c92920e9f813d82deacd21070c42a9dd146b8d78f6916cf3b40a1f59c8caab5e7cd0b191860703ed4d3be1bef7dcb40f5a6a562e818f1e8b15e4d8e44f6ba97a6243796879b7faa9bb5cddee6a015edc4c3277817ca9ed4aec9fb50b036c2d3e6974eb7953f86926dfcaa924c12d827e7b4ebd4610473fa30819aad2b5d90ceb948cae5ecc1ce1e7df43b692da0f2e086372a1127eb8d03a721780f321ac020b0363e80f1639bf92718ffe4c9720ad07bc72521f31e7547e74a6e4058c273d72f84e615c4d8aeccdf902892dc15cf1cd7b72885e1dd68198b04f968f2ff9cff4ae877bd31fe0630f6765262f86fa8945d972e1be538b1b28c5f4e97e3fc31c1d08211652b1f8479b3e95a2523b8e5e5bbc725dd093ecd9ab795fb06fd38cfca9970a559b9bdd4bc449fbb3f0ce4c3e8f8e72f4364f961535a3cd1ca4f00b13e7bf2a2a993f93b64b49ab7233a4877c9daf7226fb4f409e62cb4b00782a50aa427a7daf90aad78bdff3f0f7e905f96c1aaa72409c3cea28057243b262dfe8622013580a518266b2dd5059ff389bab43dbd072f5c0885bfa35ae353aabd8d4d2018b94a84a8c164cbf5be0ecbb3748d081e8722525ac6d74c2b3254fb2b1475fb405053f83614c237d3d525b63551275547c6275620d949a73e89b0f701a8378e227e92836e993da2fae847a29c3480a4e5b535cd360ca796a3c54b28330d8ea75db0ddbaac3ecb0b0522c693e3457bc08c558bd1bb9e229aa8908c4800e3397704e413c3c845ee33842b88b6b4367deb1ff72d1b2dbbf12a54594b8f0a22e00207367691a15df423c28fa445f798794bdd542ac23c80ddb4b6a59b073bbc2ff21e855d64e8bbebb49f8a169753f16dc1360724c027ddcf21ce983b035735940c45dd51c661f340267ed0a2c6ff260917f1772162ee5d3b55473fa6c2fd290791d5aa5826f8e49094d2e1468f401bc0fa93e724cee245f6539c72fa4d8b683a9038ef4847b3726148cd0f9c93f71dc23d1e972eef3ce49c4611ff56d3163ad24a87d8bf84f88b39dc8ee5d25cf6b1395224317bb2910f5245f40774d001385cee0855ca2040bf6b8c3033a18999754ced40470f5923a7393fcf416204a1a534903ebb07fd0698a5ad7d35c62ca5ac72a66fe66bb888a2f9c0a9a956476e80d51ce6563d67355668f4078cda558c5f149b171387a2ff4c21e252c2d46a1df9981473d337cd5984b107c63c998c7f926cf5cca7229ec14815082317044e3f31f5c2ce03db6209d2cc5d5797ce4312dd53efbbf466d933cb90990a2952b7be3096e256524ea09c0f1fa8a1a157328a97bf6e68f2ddb2b8a88151bb31869ddb1c247e53afc1e77bf645c7b59eb0e1dc6c8c30427334139bea1ed6165b599a77704d5edbefd0cd7dbe27bb405062759ac061ace362a5c8c2c3b47f0a447b1a564b97f0b28b583707e2e5944a338235bde01bff9c0727eb50a00e2eb70ba0e16363030650b82ff3f1408c0f57c49b95d5edb563ca17220489aab2af4dd399a5894fc5d87cdd72139e88bfc172111b4048ce57f300b7219757308f205877b8fd29444c914258c5ec4360fb11a70953a0093005c21a972a722b606077d165c9b04d963ea2c26d263ecc9d2e8614d29bbe89f33f408e319c4499965961c1036395ac344d3e0e2075178a4020d61bb89afc85c048696593eefcc982f84b924814c6833be0ca5297f8b052de3b2b7c13922d6f8edeb66130eda16383c68995feab2ddee1e37890580376590f9b8e0fd8514137ecb46abed235aab9bcd5e32dfe1107c7e6acc6e3a463d0d2726faef24fe4b1ae1adaf72a272afc7f746e273e09bbf8317fe8746d780d00b560e600884b85e4f4e67f1f7f3318fb66840393ab95ed099196b16ca64a5f68d8480e533ef46ef842aca0df32e3af26f9ca82f6d01b774f2c8bdf4172bfb762994eb72e5ac227bb377304ba558728af2454a1ca3757d5717f2334a1fb3af338586358339e4d27868d055d8d1ab721ac40c3812f20eb1449cde6a152ef47f834d7ec1dce01da1cfcef787761401728dc6ccfb342b7eddc66cb9a39d63165c30d1430b7cb1a1f8d3dc9b2b957c9d5a971e0da0e29f18f8abf40207940ca92e6154ece26aa2b1bdadb41c02cfd8580ba417fc8954dd91cad407bbb799f81f8f49ce4574d31175f33ccc85798fb46672aa1c9bf42f339a1ea51d6d4f5938f9b11d599cdaccd71226a6d3887ef64f0f723ceea4d09bc14f14dc659cba94e3b7bfe5fa6c9ae9efda816aa13764db81057277ff76f7a8c8490456d1848065f8b3f3e68e1912de2073618cf85a448ef05a72b0e79a23e3b16ef59bea59085458635a5d31e505ed2c9544250a1fc54a442d5c80c1ea161c1edc0bb3dae1e9ef170de5759f5dda2566fdb3f4a72518442fa94a04ab0a2e55c56b6ddb4237e157ef5bf24bddc806e486982e832c8f448ce69272ec256d834b8e5638a575c592fccec98b10804058ac9e4cc0abb69160b1b62901ab40a2991b5125c7221ac14009e6f1b5c6de97ff36584667bef302992f5b4b3ee1deb06b6b12eb2e8c06f0f9faa5e8b8575bce978bc69e4a33ad5bad1a89ca720debfd2bc8fc958be54a7825ae0ab357f9895bf27cff818fa106d0b13a089872d80818e2980854f1eeb806182a52de21a28487a67e9c05f07d1c38a1f2fccb72beee8703f254a9fa4e7ea5a5f9674ae0acc512b5b9ab002e2eb9cda408099c28cb5a3a6464dd64dd09d37d76cfb879bfa317cb3a91b86b008697d62603084d195f447bbabfe443364a7d239db9c30cd226d22c2600175c78c222f6c4991da3728ec8b3796f7c735091ab85b1a2276b6c1354f713ccce27da8719d784d3d60a161e78c238788eb20d74dbbbf8df77636b85c3d0bd713bec16d316ccc60171f672765795fc4f84fa5c25e493af8f1b94045cb02e0afd916d5baed5ddd64e9fb94dc1d4eaec1c931f0d53faf3c4a5a588e7ea72461043f4e3ce3a291b29f1bb75728b8457685715c8623051ccc062fe3269da9b4dfb0dec1d621e63f69da70d5472b1a984ac1694e5f59d48839d1b5d13b9ded79e93de483fa4b378f57cb1bf6572be7b188f1bf7f3494b5e1a0d1b144c16ad67da27bd56e64311299a6e8c9108720c185b3353dd7a8b29f66488b3db75b95be2af945d806c486e6496ebc55781721547c326f1b3d4fde381dae114f8af94b45c647a292c0d677682556af0cab5723966d0af16b4cd8ec76ac1cd228a210e004610c74bfca88dccf12cacd9e1460c5fa25a3437c37e9785f659019601de4fdf1417123caa9caa5121c0c52b0edd72a24e52630a14a9221e25da833efa81b3b9db61d415712c2b935065d0a2f87b5e9d68b4e2d76c83d84d17d6f14e8a6c61a894e941ca99d2ac7d4f35b7982a3e72134e2b46c3a0b6ebe0541cc25521b6674f37f02fed52cf08014f491142de9a72c85d0d80a1a640d941bea5e97d7440f44968991010445e8d6f8ddd536afe2f72b797fcc4d5a2596f58835062d8f0e8a24d0ed5ee4678cf0b829e0276b2fbc7725fd9d3f0814bb86bb5e495673387e3e4f9088d023ebe44f05161fc68b4a679186e3d388b4c031ee788676c0f6995fa90cb688b429611fbdde0356e97ddadfd7224337ba2871a5502bb6fd90d6957c25ab71ae33d3d3e15410da772b6a0dd8507636cde1a894178baafe94902bedb1665cfcfeb64b02c460f66008ee2360abf46cae3be33fde80ab5505d7d9d812d2d4d9247157be54019ff6497ab31215b9e72387ae58e11124e040eb6a3ca0cb00d47366e83332da3ffd2acdc43bc91713f039b1886feb87a88f50a6a8010f168a369372d1427783f29eab4b2b55a19807072d7cef7747bb9a6d002450ade007e7a8a4cbeab1e5d84dc7cec8c4cc2221c2a10b8c270a84009dc175899a169e225c4ba5409169c6e48508defdd509afb15cb72ba925043a5dd097d23cdc03f4056b78b1da3a5bb62d431550bf22582b5780872f89a317b12696de7166b2d9e7f8f11629f8f66b3b3cd7f9daba946c88504db7205c7cc61a78640cdcbe13d8d51a14ed8df6234e25dd833739e5201be155426156d998dedce18851d26629d74e348272116a9b016db940e5c96223abd1a95d4721aa557f3af84ae8e5dea5402e2a26d65fd734725c390a68f38de0e3e69cf362c1146d011c86d585106ea2a325f45fcd35f2dcbdf0cf39c5dc430bc6fa0ed357220cbb6b8043a16ab712641aa876275a56cece878023bd13f70efc33a47e7fc7282ef59b49b65f033ddd127098cd844c5aabb06d82bf639b75570e44073d8785bdc4d6483eef45590efa5b6396568b9464d675b7db11699f8b4a97a4b2898f9727a11dc1258ee8a00914d0de5ca0a15ff0dfcebb33340c346f90fff26c9184d72d36c5258f7eba2fc8df2c1f5026338636d913602f28cfe4088d58e0acf2078721e3f5788bcf32f877ec1ae88c38a1bb2273e58cb12008f6cefcbb94318e18c721e4040205c7c3aeb68987f3a5821b20232455367e607c491ff901023553c876c43a0802a24f8e77466f5735e05a2e792675be1f11ef1f1dcb491948a2286d772cd39a290f36ad92d6e6f5a83f9125c95b881a326323c84165518520d553e6572d7a58fb7b68ef3a46ee6ee3231078da463c7af71e6dac6305a543033e7d04372fbe19125453fa1ea0e9b9fb7e7e56393b298994e2a59f7c04bd19c6fd446dc2c596482ade159fba4043d1317aadd1e679035c0666825014cd835c9942b912b720e6538d93a57226aa6153668d61e752e33f7f8ec9d32f128dd054e06c91faa724593a023b8aaf38a66650612b8bbd2f0893125162617f112efdd6cea667e5972005827a337c3749afbb9a5fb66a416fc65f3fde76daaed3b076fefb78ce472246e2facebf0b1d5f245e0c6205b29c0441ed068a56423b8a07b1da1854e0f1772a21bc764e2963d4f72cc2b68c1fee341c1ac37bc4ba2aea63b5309c854df8e7215b7f624230453bbf3dadd02eaa539cdc366b0ed7e64d982e3636e537c3a7a72a3b9019496745aa785c8a52874ce9eeeb3d9a4fb26ebb81276b8941731827855b087fb4b967ca24648f67385677d27e812827e2b3932d0171e97bfab155e0372af6cc0a38a913e6075cc0be665bce8749ee87b48e99497220c52750c48a2e47233aacef1d4f63e15b9ab1e33286a6973355aba253c2c9a4e7a1b450e2c563372ec9b9e735c3b52e2cd0eca7178c92ac51bc85a3ee692e7aa7a4575bd027de872f70f407f6e08ab83503c023bd039e64862d0a3234668a8e2022b9aa7b4b95b72c5edc3658b908aab77de2fb231c871bb34e59b1eb5f80f8e35aff11f01549b72059325d9d1a173e59968b11363163ca5751c314c819ac93435390dedcff4042553f697bb49d5a85c4ce0208b126e88a3f84c55b2d1c54ab538483870f227073ba42e54456e3b4caef2d47805611439c791c02a77ada6a07f83de959ffc81e8433842324c5420b9fe10520c9382299ad0b9deba5dd3c92e63bec96a916b10ce7227bec2bb3713c1f9355bc637d570b1c4220777c653b3ba71ec8be72351553872ec090b5f6e253863c287f5f3a683c1c33dd0a5100c1288bf5cc40c2ff4b07d729f2d66779d2c480e3d402cf881d99e59623d70fa0d35acf709f75b8c9b29622b2a109a3e72084488ee1e1840b50a63c16db1ce9dd8b13b0ee8d24e3615381a724bbcbd1db1ed3d7ab6a3e537590e794a409cb02e3ece3e8b85b2664741aff041214e22c85b74dc9f35ac7e662697b7fd5780b742760b443829cfef70737db40c7807df425d6e456c35f572fa3fafa8edab220bb5f14068436841214af8be1d466d44da01d5e52cbe6b2714a83bca27f65d233bd60e178658262bccd71ad5b372437e256ac79ee2b70be09fc419cc7860c3e4176f5fec73426d807c08e119696a35263d3312449812743a3fc3c296b845dbc798e04c6f8a8141e2d772412624726b949e8855091897689fc83e54ebbc4d9e999f4cd96c58e5620998c24bb1b7728dc0c3d38f0de08cbc732ba5af6f4b59db3b83fb97b87e98f59894316f56ca00096b1fda506c7526722b21bac2a3a7e7bd9247a9570f17126b67cd044a94a072a8d1698e692cfc4ab593882e007565312918b57b3a738663a81543d964814372513e5a249833f634801c8aa6304d1eb0bf176ab12b8a947368a50f50030d9972e524573c808913cc49716950fefebdf8486976fb10d4ba427a334deeac328372df4be656c68274f70ddcdbf8f4c63867a4ab40c4e727e420e32a0721aaa2143eab64f217b12b7dc27aea82af233d16de0ac321a9269f5d9eed317127fecdef68683952fec5c45c531b677e2dcfbe058b7bf9bc0b0ffcfb7b7afc03fc3fa251721aff3fe40281493fb6a78140ab7433eae6611c5af3f6ee9528dda431694f6872ca93840845cb04d9148b56eafd23243ed73441641938930e5f8a4afcfb20373645ae0b1dc6f22eba33b7e48df04d9b10d97e1e01f81b51746ee86c2609eb317257574149cf9a805739c480c3fb087a3aa8fe79c13fa37d84cd1d94ce0e78a3720685a266604b6f0ca3287f6de1a558e7d45014d6e6d51e6d4ab65e9ba9493172eebbf15cc8c7bc2d7a57ba2afdd19c7cfc6e138c709ad5feb5528ebda539c272f3785ce361d67c99bcf1653d2fde46d196b27159e64cf4c5e441152fc0ac7372283bd84df04c9026cd70dc246406143cc7c559cd77cb6d2bfdad60dd4a503272d0f3f001134592bc671584e89dbc7d900d9ee01dfde998606097986df37c9e72fb602313f355bdd3fe48c23c153551f77e4c3022d9c4301db1c29e80b331875f113de5da9af3971491b33e63f9f8df4f23c5c693f5f02fd495dafb6995259c72557b18b988ce6d087becd293abf1fe675cb99642f471f4824f950cecf3881172ec8fb4246e9b51fddb2a8feba492c210b455de4b9f5fb02532263a7f510ca83ae0833d90e10bf5de5bec0762674f414ef72e50dc7a05c4a8012a28b2b22fd9721f3b4ad602cb8dd9526c726ff82b77b606813e684feedfc5842f1508788e4372e30c7f2391c72f13c2830609db429c0cb70e16df307b18b827fde7a8157f1e7285f436f419c38a986f13e64e517c5f9ee010939a396c3302af90aff444112514feebb06cdc11562d77636fed3f29d61ae16e54e9426650762c24322c6cedbf724e4918d2b1af6207fd3830b0720e9dd3ceddbef2edae0870159eded9ae96b17210e834049f1c867b36d15bf10e508ac28e23bee487d591af068ca0c70f3b2415a54e619184784e1ebad7f4ebf23b03bc4892bf0de6c27cb4fbdd567c10eab6408a59d735887c2f1390f380ec04b7611ec75020723a30641e902880fea3a8117211ff2b87a6291b9cdb89b45955cf694dea5aaac6f29514d6a5707a8dcadfff72fab9585e7ab5c582dc10c23704156b4b6a65cbd87daf1b4b156493db74cf30722647c6fcb4b3f0b3f5943ec62f9bc9d605ba1e58efccb93aea84100a13aa5172c38866b51cb83181999343be7c11fc69baf392a129d4df7177549c1969afef5acbc5489b35f79ad5d47c97e5a4746198f6a71ca6b407d4ada7c293501c593f4acf2743f1e586cbcabb784d2745e70574257295ccdd165c6687a2b6286bda8972fc45f210da36ad52bee221f793abaa92f7a75233f30ee05acbc6ec1b8191320c273bb8a5b4cdea378370b12d7ea2729be299bffde84a93d3bc844d0c967ea90e1b38dc7cac5c4564c2cc7bcfabc0370eee828a7c62250e656a7d2b4fd6d2c57263232ed2f3a365045374d24ace568f0778bfd12ade531ea5d86bdebe7875af72d134353d5a08bab47fe83cacd6b70acc1e63697d45a99a2375ae82c0be248c26c5896524c384ce65bf3189ae799899284f15a496a2387db3ac091118ae496a66fb188e3d76f594d5decf5a2291ca952cfb263c5e56fc98626b1192d302a23672124f13babb8d88e1078945afbe3a9c9e879fdccbf27ee3b39cd6220301b277117e38c1173964784078baf9c4d9c739c56c9a8c788feb5e9359822873fb72cc1106bdfb4a0a93290c6449fad45212ce40cbd5b8cf4ac91adc1752d1a0be6378129613b00a18c8888870a7819604c9f2b742a0a38e6e50157b0b280f6d7c15434ae32a2972fa2f7aea7e5d69e4f948e9360414f700a53b1fd2515978acecf94942949d5c8dee887c0d5faa5a44333e3acfe37a6b43467a1e5357a7645fef32c4728da208805982488b495077193fcfa32cca4e472dfbf061aa8cb4146384c1b272fa5207976993906f19ff5924a9fd68138e624e0d743c24b6138d788a5af66d722a06c937ac7e3b3e995c7d74b499055842dcdf5860f8b92847622e01d8e6ee01c1c79175ba45d272ecbd74761ef719c511a042fa4c34fdf341db9e10e4a6037229b1601facff55188bfad010f2b76aab9a72d716b990d29fcb231a43668e885edbced38e624f55e361ec00040d383f37ba3ace7e08bbabd9ed303b27bfc02572a54f730fdd5465b355f30542ecd0d6d0445f16c3a41ef101a416bce56881587250a09a39d232a4d49416c63dd14288d1eec68ff86b60f5097ce5a2132e6d9c72d2978ae3ce0fbb454a8b65700272e12749f2374894ec68442178cf4636eddd6f4aa1ab301113027c3387084caa022b3f0fff0eac8187fea8ada6945cda4d577258779bc0e8a3b358fe1ad53493ba8cd82a43d5a78d6bf5be7b501c3b54d922727cf878c0324ae9136e2b41131ebbe9a54526933e5618775bbbdf4b6435b78c72f02db1d829364fce804aff14b8b989f1326582621798f71482feb7640324722a7ae6a70b7dfd530006fec68111e446e5f98b8c951528c7dc6188304e660dcf7297f419f02a65d545e8b37d1008f720b3c37df22c3cc645a14ea112d7b8b0cc727e41d1aa41b0e5bb774778dad403e8a0abc4f554cc8af2bb94f256275d78fb7217520db85876bff528141e21a1e143a1f2ec58d697f03e3da2c470aa5bdf3552f47abce8f7858aadc9b2ccf2a5e727017668d1f5f5441d9b0ed1ef478afad70eb57b95020a94d6876edc69cf9783b024c68cba6812fc324a8c7cfd9add007772d59539988de80174bfc459ab61610cdc27da193b3e699ded8d428a74d769bd39b4d039a30c9ba5c5d24b26c177967760bd3df7fa232d51b4a7a58ebb1a6a60725d9609d9986add29331d5d17ca44ad1a108029f7ed83e437df69c644a732b572e3f3fb99369aaf7aafb959d49e306957b20d0785e0e975fd7fb7941ad51f4f3b9f5efe360aaca048a451bc86b683ad9c0ba51fd93b9557d9f3d4d56ae8e75c61d3296ebb1da74f576c1b9abebbb732352f93295a56cc6edf34df1eee92231f72bc6b47be7d7964be35a0ca29d54d04b260ce63ae8e99d93f62f299efd452e672f63693442462799cf4ccd38c081a9ebb629866cf774b3afc5e8ecb3eb905ef72ba0da85a9000084fe1dcd314b745a929dd011af9211d4ea19f8e11e22563080fda2782177606348bcf188251d96c93989d5793fda3fdf0eccb25a92ef5c7d90a5ac854daf9c41105a07d52cd641883c4dbcafc1ceb4a7af64d889b8a281b1f2f852a8edbf01e7458314bd460363c42a40ffddd55eb1035754f5707251f91f372135f459bb1927cd0ceae76f702c26289ff18a4be4c06c176df4dd99454aea132767e2cccd1537c276905386c374bffe06f1994e9b18a99f84050c83dfdcf0972be70f97c4cb063f7c2fe8fe2b16efbfaca511ec6958b4aefa1f33e27c381b9723589299e8177e5603561b57f6d1e5d9b777fa79224be9cc26de676b357c96472e1c1fcf94e7254e98fe39314dc8c4293b78f5e7df5d8dda49b6928e28649ac7296bc2465d9b2d946b3c2e59a8b6acd59f164e3a261354233563fba40d34d55729b44100990ec8f3aa910b3206b8dc5b5ed4d4abdef0736e2c067056970d317281eb91113d14d7ce343f6b6f22b7eb2fcc77354082343961db621db156bf3b172f5cc8d26e070029bfc4fa97d11175e89b27fbe9b0164cc2211a97fe569900763343e1472e6340163004afe1a95b2b1320d2571c49cb3fc79b959d4ce80f18c64956cf412d823cbc782f22daf88e7608d8649dc359458e0e53871c81d9f98c82ca91c2a465c679f35929b55d04856c73bf4e3bbedc74973128aae269bc770015fe95555f304f0a2b166351cf1aa87c829ef3f489c7ef3cc676627fb342d292e5e3b3a9742aba40e75a9a85fe6c3755fd21c91fdc745f2b536b0baf78d8cb0866f1563fce08929f45002391e31aed97d684bd8bba10677835d4bae75c973a12172a6960543523e5b1010f726a31c6038146dea229dda7fb2789770e0f2e929f525491b17309b918d407c7b4faf8a72169523b5f01c61db096ac799536d6c46fd72b0f0395ec1d24612211a300adc390dd3fc4a68f4e2621e972b1ffe085e3e0e7235cd009af8fc96a87d2234fca3dbfbfecb6829daf7e6c682944e4d8141010772ab6d1411bc85e410b93dc2df879638a1ae74f80925a5aac20abb813d94a11772d215f441fd443867d54e2e822bc247ebfe7a17751316ae965dc7ab308c305c72a2259674c637a03b6ae515870901241463a2aafcd4da90b3c2af030e88036f72295be975a8dbde8f8ecd0553dd24f66c6eeb79c4237d1d54469bc95946bd287296ee2ead7af60ecc13cbc77169ccb1153e43671f485891b07196f81445d1d8720137e3fc7494f3223674d0171fd80e88777b079555c59fef210e56a57c548b712c8a7d4b1dae748fb485436d704d6d8f3854216835775ffb5e2481fd7bf18b725da5a10b6f66c3717dfe090c5651799728c43b984888d6b74a5d245aff9e803cd962fb6a187b7d1f5a1e8474aa253507e9d34acf583101fb27c1adb31a8b40725b862675fac40679d1a3c136786f7b312c2621d5870d6156067f7bfd685285721e582e74686bfeb402ac4cde07ba4d9e340505adac90293d96111eacff9b60564a021835a11e726e5231ea9fee1def7ac2ba22054be4d30d34e4f74b75dca03b9b3bebf32c39db9f0098f25ae399e93170a61a471da87e7c2f46c92e0060fb3839b56325b2dc67159536f359bec26197ad3b817376cbdb68ad1e590913e42772d78cd121ba3f956411eb107ddc36c0a7d8aab25e61ab7ba68b4a1ec861933272c1574d015bd82017809194f19d2305f7b81897a0272ad6392e48c78cb748557200a62896a403561fed014232618a670202f54c8e007bc0285c453a98bb8cb672d4df4b10c463c158131db09fb714cc59f59452e8013308f79aafeeffb3f27172aab70236409eec27dc98749194641412c57b6fb88ccccb849eec4d459f42da231871af8de60cc7bc6e8ed3628cced02421ca9f20897f72692a8d4fd356824311870a7c7015555ee5fe494ad5731f44d33a3f35afb6b5c59d45bf66ef9de23c0a43ed4acdcf26001da474651a2739558a54cc587f43f02fe6d881c4635ddfdf34d5be4ca5063346160116883df5562ce49aa507ee5e300d0ac632e13677ebb972f370a90eea14d53ae735632ef15c1865b05421781fa8287ab2cd225a9365cc1284e9f2126fb7989a711066b5521b3904c6edbef08f24f91eaac1863c9d605e57a62c3a5dae2f769347c6d63eb0d38fac89f3461931bc6e95f591c1b0b4b15e72b2d2fc1308d57d9f9f97daab048601fff7d9c0c852b67648d8b991d906769f6529e95875a60f8eab6104a8256ace64ccdb15f306b394db68c7af16ea60eccf725b29ca2465d2767835dda57993d0abfb34f3d6db65469578e69c556ab517df720ede69ba0d608fac4aff7c6a5aed0246368ab4edd54959c660a801191631cf20938964e29c085b078f49f24b4faf8e8112fa546954ed535acaa435a18d4ba1724960bb9bba76d280efd64432abf254ddc5398b58a63f728eb7d013649166835695398c3d24b3468866035dde7c10445e013dae41f54de31c28dcd6871c0be072eff42561dd463de3265251a433e9ca641e3eb7792c673d4b091072fc9e48e95a1da547d093d3e0340d51d459e0e75947850639e3e73dc2870a62c2651550b0494e8d25818d3fac903972e1d22a36a28e280b0d339bb7aeeab98781c562401272a7dcf49384b0ca8a71b5aea526200ad42eefcce7687cd0b549a6427abc641a724a0748be50d25dba981d2a5de1e7d2311a11b623b5a6d16066b38ba269c45d438b39bdff0ee6e5766ecd34ad22793fb4d759249ff4e9a5b4161052c1ada58272ca932a468772057fc10dd6ac40154c0a0fbe91f55c64b1afd3df6ddefd8dad7284c20dd3dc78fd3b865ab6d97d9bedd9d244e5f62cb779618496a7fa9f303e3cbed40d1a0cf3e63304e7ea2b8e3c684b729fdd6811989a1e82889c65cecb7072ec19e2edaca715fbc4232b4245318fc09770c8faf58158ac6e9f6ca7f432610fcc3273019af40b28b97431f2ead0d0fc58243b89eb3597b003a471b231372d723244b0afb637e03bc4e2c904a0b0ab03fd3a6437487dbc5142e8847734fb462c7cc8a4e230f1ce9187817928ddb79625ca844b0822a8caa3b6962c1d4355e67227d19376df0b63aa1676bc6d651a40703af7ee804cdbd8d84ce7ccc693fcdc72708911d383c4bf7d3508dfe4a522d2480fa2ebaa63ce3769ce760e692f5072721428be7972b29c6f3b482f94d34f7341d627a7f1651a5cb3037449b6e06ec972718bda450795597de566ca8f0fea1a6a40daf69b68be1ec12de8816bee06ae31c7946989409370f74e375875966c655ac86e38519df9a272d715343acbac637278c70b6c165861d6d85d81ef2004a4f8be47645c873e512b07a1cadbec8e4c72042323ed054df22334ad3c0a6b618afc4e2bb5abe7b6d50d7f3af6011a23b2720db9e3f0185ac2f7911a77ec790849da9ae96b280dc8d5f26431df035318747233840cdab03ed9e89758bf4c87536c477165e352e959c7cce6ab253d0bbb51269b1e174ea3ff6cdefe61d5904ba46d99c83e00b35303a883e0c8cc0856e45c585ef6960d898fdd3ad74e4a5059577c6f63624cde4528208789c7459bcaa829673b2585ba9d1420160c4c47160a8cb3841176f73010d088f931a342d45de2247237df0b90ffd750c20fb2434d0a2ab0374027b6acabfc2adcb69482308f0936720b73cd2dc93ff147123aca7da3d2b6ac145c9b938780baa6a77ef1cb5838086b18fea6b6659d72ce5a5ceb91db59cb751f17bb30aca0511acf696310d031bf72d71d60fa7de81d23ffcc1648429ca3fb30610fa5b62fad234fa7fe7f84210c5350410193f046e87e2b40e51afa165b67172511bdb0a73f97f56873dd69ca8972ec246abac8c5a1d3437a0e3c57dbd2a22772f8e1cc37c6380d334aec3c6bcd726e15df00620f39dff928b2c0b07c858cd74e8a674764b9fa3bb9ded7edf715027813aef67feed06db665b2e102b336a573894981512c6f1b31f26751ac6fae7274b7c193243ad70e6c10a10b1d9c25f447d74a205503672524ed0b0f546c1762ed97f55fefa9cd12a376fa6b9625964e40a77c6093b11ce89882c4e65c516814d64ea7eb638c07a613dd3750477a02e971af4ed2cc4e5ef8df745f2b7a7bf272c3d17a0def7bde7def4650a2067b44e8fa3ab5a1e17c06fd76882be8394e9172003e8ef6a01681a5ea80de170e3fcb4f29615bdf47ed5b30716b315796575172fcd24dc0349eb132a7b02d2c7d0e90e22991d9f0fc8ec15ff9f5232c94b50572d495fd860e4f7f869e33a301f180594cd0965a5b3d382844297d8e6c38cd2b3e1e71ccb02418f6865c1e32f9f4ad5a721bb765a5be3516f2139b92376d1bad6e8bc8bde4fe0bf58e85524e3dc8edcf99c8be97bd09bfe02ea3b8e8ad310cc37205ae28041e77acf03b232c27fcaf9a187e19f2939a5b1c0ae0a1b92a50003b72cd964f1de29feeb725ff9f13e6ad7e92688ce7bf7b9d620c5c23d442c98d6072014784de421434efdd14da4d34adcd9195d423fa71fb409ef4fff213d42e53174fbf6e39dc50a52c72a60eadd9ed4464fca6a04c5e64a28600f3845811933c08c6920de7d865298c8bad9b9cbc2b225050f8e09d38dadba9e8ef4cc870117472933fe6321dd24377a449a0ff393a9839ed25043bdf56cf6f3c6f01263702103005d8e94e0bdfe73580bdf467cc196d79d2ea930eda2298644dcd5334638d6719f64d8e659baf6a31f940b652cf022c5b99b73f2e394ae95e70de8b9993223d1f678a14abe402000c3107773847f6c812e93ea7b3546b5d8f559ba5e3e58374726bdd5789306175f5bc266524e7d2f72c651d014afb3fe812569205e4b343d872f07f816f7342f341818b5f427ca324d4e89e36aff479c83fe9e5292559c00472bddbc7bdc033e7761b28a587c8d554777e0620236da5ae275256b88e5b2be772340336a6696a990da17fe7b4ca8f3c6a2151539bc46ebdbc2357b3b51d14d57216cfdf941345e1efe6d6cd7180e3aa25e01f629c25b8d857f1c4c641401b2072235acc43001ac55bca0e26795d59cf1aef4ff84de7e56e39213750f0a11056726a6e4792d450d0744407e0624a50469192f930fcda87b7a552647c2c7a073f00723448a71ce1c8fb174daa8457d113b6b5bea8e521e3f7c9da56e1728daaaf16f447ff8bddd63467144028f61a36a87dc57245269d10116382d22f68811b217295909d0696eacc714914a45615a7663f248b2bb3a4800f2679e03285e3ccbd72ad7602f59821aca41a5b1cb3f1ccba2409688b26bb01cbdf980f94d067e54336e6fc73f42ecf10166393373d8b1cb8bf5da7b8da6c0a35a6517b517e86cc18722d475866da9288c1dc1fe01fa735a8a060199448034dc9c840c530dc658444343ba223d2bca2c008ce16a29b118d3948cc5d7e8f1fe971b0998bede260ac3172889b1886415fd2cf9d994dd55cc43b5fe57e5fbaca0645ad52ceb98ec565de52722241eeaad80315c3ee69a063ae5cdff39e729d21db17bc3fe9bc4f156afc358b88fae73652918966287182a317d0e75889cbb78fe4f0007a86217d1c43de7243ff40e12c17d5caa538bea8b630b09f16fc4e17b75290f5be98352ead2bc07202a83b8b37e0fb7e9be32f26802ade86f6cbfae52ad51f194a80d1e5b959fc5fd544fc2db0c8bda2bcc589fbde5548a0ab054bd13647750604a9025436e4b772fa2077cb976d570ab3354a3f9c4bac9e0dc70eba52bdb9ced5aa24b10ff7957230045956b2055cec9aad6fb565918a1313be9903a2ac32e49282b8371639fc689341f546191caa9f592508c3bbfcdd10d5ad3c9b4a6acd8c8d51b3a231051e72fcd611d65ffc262769b44e013d610a47fa6aced38a07336fc06d910f1eb11c724062191f18a15ff253e3c1a751f4170be7833ce50001638701d9e86f0881b34022707c15030ffb63a01c426647dca6ebf05c8462c1a2b764c6cbcf0c72e6d972a609ae889c47704f5f2e8b2f25d2e452040bed6add8062fdcb96d23359245e13326c38e0dac360c7d69776da752fe88e316c9d22f9477288c54db52743a173681f069e17819f2f9159e88cca4ba2989e449d2e7d15d9efdfaff53377b2af35729001b48c715d002600b71b13999717dc88b7fa51f0948916ab02ec3ae6c78d3db9612ba49983d7d557111745d88027ba3815d6454763c224f2ffee9e160b2772345e77150728550eb360c728b7b79cf3f20f1488a3d7d811e1e5adeaa5fd8e72b92b49ce87bd91c9ef62d5dec24efc9e96463f1bd559a34ef68cb9f257888e540c45572eb1d790dd0805e4825035afe58d05a1f3e722c8d41b8b81c70a278d3e6b98d5414397b46b8a1c744316c142cae5025951687a42494c41b0ba396e39728f01b80ceac4e0b6f84eb204afff9cd04be0147c98b494f7284611c16df9517260473aa5a9f3a717b9bece445a41dfb53da96ff42ed618a87155910d7cc0b37211e085d443900c4fe1fcafe0c6710df1653eabd6f1a622089d9e0fc0d6417e0b3273db5df6e93c4f47bb1c90468429776cbfe67fadeced348491a89018cd637258a4cf8f2d34abe3694d6e36b7677624b7a07cf25de957eef7423b287793f160dd2cb0d4c6f1e90c57b536e8b645590a56bd3b079d08c46ecda60b8bb9b3eb107a0a5d5dcbdb9b8c45046d8a0814cc86605ed6f1c6017efaec8811b44d59db0b20c0979b781c0452766fb421a53529ff717cfcfc54e75bad4873c1f16cf87c72e112aa1a13adf4542dd3b738a5d31c4fd7996aaff72c0ded2ca8a65b4e6ee372509db1b9af96551ed72f350bd0070f3bb5bcb525c93ff55113c0e28c5b0a8872d02a0fd96c9a8464fa08aef26f3497c5f8669224db6aa011f2d00398d952fe7283b66b94ae96d0658cc4f79d0fa5c724a6bc48f15eb48ade2f767670dfdf9672abaa4349da4b3a327e81aa64275cd54d5751004c16a3cf8d06b46452f6830e3b72c80a9bb601012fb97e6c9c2c3e12ce7072f232ff903f938a48ffe3aa6d1772ef83045601f093eb88b7edd19ac28d4791d016a698fa481171c317d25b5d7605e39dde19427d557299f331372639090848bf549951d8fd867fc70500248b7e728518ce75d5df58c564240c58031319099af0425afad2c48ad69b350de1dfa614088a4612c50250b28d83121c0f3eda45ca2ef31d5c1f66788c203b6a3788a71f7f00ab3ec28d3a58a44c2c79ab0ebd985ad17407c657d13baf1920f9b32e9e34a863c71e7f00b8df7fe141d96ae660e6dc84e9a357ed09ac684d03e4f3440c725b531f9b4d6d5b6e0e473ebb6fdafe98fa16fd479db70aad28752f96f452d972e21313a451dae0fc047ebd7ca7f5ac707803057a23a1d445796df0751afdb729a4b8667ff631cfa436474679941564a463a953a563c61bde6d5d3d1512d39672dd4ed069c9773ce26a4eab10df10e763fdaabfcbb30950cbd27da6f8f92ab86df9e6fbb12c727d43c75efac788df4c29c39a289787ed3f87e96d19571a25e54bf083cccc2c3d548737620063a761baaad58e75c9f2a319c5e315dbe348fd7c0b3e53958d1c098ffaea4a014a5279575d1e66af6c8c7b2bc66f3ce5f0623b1d7248dfe95f42709b43d14e7f04889ec7356122bf0565b1e3a432e0c0ad1605bb72096601126e3818b8d89f4653a7a8b98ddc1ee04f7b6e37bf9288b32a7e5a917264c4057c2b5d0747bf8711c4a2f6b0ace24ca15931d46b289e4ba2e891169472db316dc9b477d7bacf26adf48899de1d2d798e3d29feef38712541a1edfff70a4ef78fb14f20ec5cd1b72e6bb1f6af6b9c2d1e658b0fa4cbe872ccf9cf4d62176fb979710498caf4be11f5f9179f3cdca63589150274df9fea8d8532f53632720cc2849d8f6e5be4cfd93878c723d97517a8e6f74b0bd1e9eb5b9f01682d9c727904dc70c063817b8feac58b408d2fc4f81a9cb2c95366122c7459029ce96f72cc3038c68f1173f87193e7e6041341a4feaceb9b0ea6b574d607a036b294d6724f75c38c70a30c2a7ca76aeefd954c6c63b91ca0496a2b9d45553a5482729972d31276839df06105267820ecaadb6543076d541a60d2c84e940661f721a9b05b04ecdd8e2d897717c9037a2507d8a7020f16c1064da45cbe3b0b90f8dbc7f472f9fab2de1b202fbf9f106e0609c305ab78d381c4fc82ff6480f1661cdf4402725fa2cf5ad33fccd90977cdf7a9ad79f33f84913ee338da9391406f202f56485eef144a31f4a414d64cde5d71b1e746d3e43c64b78a00e89d425e4ffc878dd9712394ed524bf5d051812aa45a12568bdc1b3cb2749447075a2bfb84d8e43ac72ea3594ab9983e092709c12a75d1b326c74dc211de8c2199d8bc41a47184916572beac8d227b2a61bc20132f4361f152bba4030ef36c66b89760d3828c91db6272294b65ed9978bcd245a1d56f43cb8e243adb9ebbabbde299888043e50acaa372e11e5d72c1b8e8b3c058e043daa25a6945bfe9970bedb8521bf5ca1d7abf0b683666ef6674f93b848786c2897e9e983c17004d7a8137f3a9a159ec82fd899d724cb00a8a521981edead8cb768cd3f951e3472d8f91bf9afb6523837557f4c8726dd9bc70afa4b6aff7ed0c91c0dc6a3988f0a56d8332a352da6540e36fde1d726e0c31e5e8ace5c45673717821994c03f52911cf91b32c22688de20cd94e62573a4be1671a093d9657bf31e186406c3c50df76856a9e3de0e9268dda7e41ff72cf0b6b39abadcb33b2244b2b5520f4be248d4508d1eece9b620570ed01fe756a6193dddf2105417791acaedd0aa7e8100806a142c19d2ad58442619ceed63672523e06fc2940c7948ca57ecced663d6d84a828ef9ae94c757034412d91124c72591cf007aeb9a081acb6f37e1d819a42594fd8a7222f0151ec89b6e2a9a1627297c56cf068c2387b857cb292a81c8c8d9e885f8c5e0bc85eb09023edd692c8153424d3d06e606c9ccac22cb5ccd7b789d237820011d0a154ce71cb1d3ccf46725ace28e91cb46a3ba28f9545e6fe4d6b723cbfc6e101818ebed544e32428542b5e64043a52b2b6011e935abdf1fc678ff9fc3ef579da8db540be82a74d20c11b73126922cd92d4807ecb3ca7d13402f8449e22d665a72d67969c2525081b197272cbe15d62a86db03d5fe999b3f7fabaf38d44016daedb2f9124a7466ab9597263b4d709976c3e6530aa4b1504c02d822d964a2694e34d4d6308cae37df5874d3d9d49e49d9bf8b1ff882dfb2f13c6d3f1d63b9767d68af245f6ecce0feb147278e3d513bb8ac23ccdb9490dd5c0ad86a75f439dcd2304f4163ac55ba4a42572992c48f54a5387bfc886e03c28a4bcb8c7996cb28f239b756e1d4fdc72da9b61fe71edba6524ad18778cbbe949609ed153a6d4f80638e2f863e2c50b52ab39304f5acc4669c8cce4aedf4e738e66714a73d4969413e64c4de0fde5a2bdf51872353ccf6417b48df5efd45c70103ecddb6faddc114e4ea50ac5ec97b943ee5772e9010749551d63229bcba85452f355986fd6370509e5dbcd58b7cd55284f4872a371a1bd2eb0348cd25a302a1d06a9d6d04357436fe38901c25f3709acf8de72cfbeacb9454cdfe897c3fad9c9eda10ebb5d39125b33cc7fb549ec08a9893372e2dc24ea8d45f2240ed0c546a783f9e1cb07f9d455df4c696c4003c5a488bf537a4581da231780ba34a4263be81065997c861f6f56c0815d52f2c6e68f1bc172e480dfdf5481fa12eb8f42af3cf6a6dbf346d81f7840390cd40cd39079b783727d1449c59e2cb309c4d9a81e5055f2fdb3e88db1290be68bc02a5d11c2d17572e59e869fb69178dcb62ab994cb1758b55e456f93f4ae700f47d15c03f6eb86721557243b825131823b21c4fd21cbe8fa9b5ab72c5065775d1b74f2d2a78abf28b4b6c1e9f64550441b919b0f063cb3862ea61a2c715246629ed4092a7fcdfe325b2100d663ec4d15229487bb52e67cecfe020916c5daf7ef4c298a4783414006244775b0e8c60d279fe07f1c7c76786623216ea45ab99e4b064c33fbdad66a72b22237de9bb1956bf1a25b0cfc8c637a938f166dc393c2a8bc28020fdc3a47725e54a3b42e3cf520647d595edabe8bdeac94154cade94644a5479f526e6455469741a0465550f9fa026e35955472c64dcad103df22228c5cb59f724b5cc3a872460cff57b8cee2cc4bc35ad4c111350d92745727e93b12051970a2bdfc4f8a218e9fbdeda542684318403c2ec49d57f7c12edc612a3b15b1b4cd2d69ef413e7235fc47c58d7b6ac38dd9adb2c28f24a05e510bdaa24f7397da36318fd31a8c7216ae10c98e4590d810431dd03bfbdcbd9f4bfa17f2b117418be61ae184151858bb0be7962a59d75c0b21e697c1c121bac48ffbaa40fb0a32fc3771d298c64509676a39d722a609270f93f6864d726e6031107495092be1cc27320558be98d472974c6e2c3d965d2fc8a25e59c76be2796bedf728a8608527e3fb8c5f9ec1d35a966dbbc50a6f3c44ee292fb8a9cf9aff242e5ab47f29127c32896bb0112f8665844bee7fca729dfacf1e07876718b0a47f9f0cf61e53ba4e5dc0a52f7c824e7262201341eef504e2f8643689f3b9f5a137fb9ea028f93d0f950013051fb6e34cbb0c5e4140f94d7400d3ec8d3ecccb4c6a552c9ecd86eb3dca85000b901e862c6aee8da8825d5db95f0f56486af4af0ac32c77ca99d50713255e730b19d6304717cc1495f0ad4a34774ebce82efa76c502fe0567e67a44eb7b4a7d6316542a72caa333074d8140669a3c866ad224253b85a875fdc9747dccccf838b4bbed6c723bb68268ff0dd101e4a51310f1e26e73345e4ce9d16b3e5bba44b74aa6bbf8297bdc6d7ecd853db94cc37967daa4fdc257c04d540a7dac705c7ed901618f193f9b7fbf1a5184b8bc55fc6f317a7d17646c6d6482483c69be770e9070f383747292816d2af4f234b64a999dd1bbf3b957eee075958b0b3b39e2a5ef417c8eb5292937206b0ed76060da4d1959071986c088cb5b1ed9d33e15a0d2dade9a8a66726b5d26bb61045bb1237b818b3a8f9168b174fa430868847cb0e0a44767ff0b723cdc8f8a7ca49b105d64e44415ebbd1e9f6efce9f5000209a163681259aa8160c9f40b32755ae93d23d57661c88a6ea192fbeca1e262abeffd6561ec3f3e4172b2b36365e0aee56956ba98877b523c1009db319460e2cd7fde41fa900fd92372ee061f9c78b2f5374329a3116c786a145ba581a7035b6a0824d57031d31c0772d2d29299a3afc6ac7b967961d91f2f29679e9ddc404f31838661e7a527bd8a72b609458b0c7a307d1ca6779e0fac42b9ef2d43ceaa24574f21621148e7fed8721faa0d46d5e146ac258dc047f6b33943fa16335bca3de5aa6739e996908fb931ace8ddef36bb55165d7d0214560e4209d21c8de92844767e9cda0cf62e58ad721eb1d4421a027f648971d5e5b06235a1ab18ad27941109d3f3a0cd6b03080772e416ba51aabf7d7756da735c59f7e7ea97fbfd92193c7e221331b58abd76ac479cdfb3e59e0d2ccd01c27269714109009e9db71ea4767f8bf5d69c33ea8da870972e01d8a4e6558c709167a05d7f120202e89cb44844d645c792b73fea3b827203c21e94ab0207440297a1d66997a5527bb1378ea3bfa2e0693a6651cd67ec18f529fb778bf2e6d695a2c7522337f20ec878daea9215d6f0f898acf69e137f3426db29929e92229aa21983a8b781d9e05ebe2dfd33c7051ab5f01330d3d3582ef82201145e7d864e51593614726d6bc0133b3ada940abf34229111391770f07228efdf3db7ae1377ecb73f684de78954c37a3881b05cad57e432b9f8ab27ef7234aa378b9a7ab964451842d04791acc0c108e06846fc46c6284ee26f36ccbf72a734ad99f96cd94b9106ff5e5ef389a3afa1871bc68809a7d09a0d5be55bee726e6444761de3f02804012edd05ff75dc90f66053b242c86a3a491c8282bf5e72821c8e8917f13e7a3390f8c6f54fb5e1f75eed4d7e159acd4caea98f3fa58672298f4f424f63e57ef7e76685e4c1a34806b3f65691c61f72300479f3bfeabb507f64edf4c07d100a660da284d73c52bce7761d5f61ed66bdf08639271e8b5516241f639490dc67a6f96c291a3682ff085a2cd27bed435a19aa87316e739678726d17be75c14076e9880d1b6ccc9ab948c9669bfeec36cd1c523a5c7e12db687273a752a4f6da7968f9c78583038854bd5851176fd20d8dd584cf981895fe3b72ec8816805f95f9aaebfec0f417fc03d4e23c41c6677a52312bd439099c07f803bef9623e1145abbde749a8fdd71d26c59097944959ac5a3e86631bad7e19817242eb8e47ef93b6783376fb9c9a52685295e2205fc172182a5c18b1daed76bc458c39362e882299b89ae2829f563cc6dcc2978fd00a0860abfbd2b5c71638d3726a0d2dbc94fcefa2049dd5ba37be233d8b46588ab3478faaac3fda162bad5672c26dbd73da62db732c2135b9c094f83643a6664712f5d3acc26fda1a5f6b5e72df9b62e654ae83aa518cb5a9ebb39d1043208e00d487cbdc1c1ee5d0c552f072cfed92359b6b8a6b7c82c3cbd5c1bb86f671eccdf5ab0af8cd1c4c10cbbff8727246f13dd0098ed7bba203b1f63701366d724b76cc3a40d637700b95699f59377228ea920365b24991099f69006a2d8b20ed87a81979bfddd8bb644cdb644a37f337d618e901636ca94baaecc14eb54edc388efe377d7e8899b18cc6fd3418307d120461261ba2347ba6645c7f73a509455d6d0839d8cb46ffc45db0c511c672418b88e7d71573a62f5f060e155c7cadeadc65eb96d816936761d6fa023dda7206a2d3d6ad89fddefe506e852e28c01b509f3b6030c7e5d643ed9b9f7f6f687261202c6ac7de2899f143e491d2383af97fdf747f52f1b74bbe1aea794b73116c9ad0f7623e17cedcc2a43cb44a77594b20ab23b83bdff21305e861c140debd1ab29a27d948295e1a2615795fda307eeb45164d5f7aa3a9748e48d5443c06bc0e97a591dc01385c4d74da6586971334d236cf8669ffc7c5d91c954f22c9b9e447c9a72f265a6cebd41532f7c6afb286fee75824ed42a685576fa170ce68f388651b7f1c78159e1678a7617e612a3859a8e136a9099fea0f1c420fd5e24ac9396231337c8ea14a28b6f2f1a22077bcff3b167417c3163bd5567ca8d51112af7572a6b1858040e1c9d89c4d9f2deef90cadc67c97734b82674be3320308d2ca5e72f6d7458b9424adedc4fae836317f111ac6330cb4c21a221ac875b7af8760787293616df291d13bd31fb471c0e5bce9d7feb06575ec8f5f09d55ecd4148ed1b1739d2c6acaddf161bd880680bff377da334828dd43c2116965fae201581e04372c32932406a83d23fdb70c7fd536e9cddaa6e565bbbe05212851df3f2b677bb55366a52e72141b45060111fcd17ea0f97367454bccb7cd12fdc07f51f2751376a4c97ef17408a60075d66ac3955f76a544b00d95878110e8179ed0d1154bcb5724bae611efe7d578d500f083f39a54cd77feb24a254dcde7ffc036c8290591d28c4ff41194eeae47a147cb9e96abe0a99951a53cf304f209a9a8d076bf937b6722743c90026a087352da22ce9c9efeb7d9acc472d1ff797c0cfa1473158bd2172030b7dbc810b14d18d19e0bc3d0e2d9eb37a49ac6ce5138c029d6ef82b1fba175d9e5568f8fc330be6a53d9a501f93a22d169c4207cbe668797484d1b312e7723baf6de82f9fdbdcf664b0a208a1dabc6f7b919da1aa7c0a441f0040846ffd72be4c6af6024882d18cd2a5f6a5be8578697dcf76003b0d53f2160238b99e5b05bd7fea0780ec2b6564e2ef64a9056aceb73ba51a674d3c1af1880ebc3507144efbcb605f23b9a12f5ac970e8145ca63e9a047f8e7f5dd446b79bca6417f55072e6895da9163892c4c62e5dc65b13890d72c2e576d724cdaaf93ada128f76e268630af7d53362a2731e2a6581e63966f60122d5aab4272c28b2a7361221a90d7201797c61f794fd26d90279a387cd9bbe0bb1cc6535cc4af69ed0d1aa119cd428336850a9149ec5c139a60b9301c5219c343df8177c5a02537f84c772a209c164f2327e0a68302e35735168a563fe9784d864f5d21f9b75ad80fb2671fc85d854fe01e04c325b98bde4077bb21dbbf8622a7012230ab3fd27bcff80cd1b126d2a977fc6962baf62d825306248b1356e6a61e467955f39c869ebbd2cc89968c512c8c78465493b1dab58a2c7d66d1b6135d0bc4b3c4c386752baa0a392a6d65a72ef02b51c014b6ef05cf22cd8412962f1ab70cfca3eb1875521eb20f608675172024e02c54e8869e663e4422abde56081f405c802700b6991d9e1bdb3b910d1729fa30122ca445d3b67d9e06316835e8258ac3fa57a16fcac15a18b616dc5875e18c1a40fbd68fba936687fdbf30893522106277af921030833decebcc8c721011203b67530c5462412bdf3c2590a7799fd6f8e8c856b2222b49b61175949637299f2a75817816dd1c82d3b83767f10b3841f7912c2a18668525b106d5bdbfb721fedf699d687eac1c7fa4f5c569821af970422c510b3bd4d2c63e0c775926c4b5c143f9decc5549929046889124ba0cd71fbf94c4c09c35a6d7471c6352b6c7241ba85e98d6aebb763af9f34189ade8c79d9bd8651156eebab9134d1a3e5f572c652251614b6aa257d61adbd2bab096e2f468af5b4515d0cfdf69653f01fc03b20a959b779b23802a3e04c0463de68a415658f1a3760e0c32d27ae27de75f32cc3ba916e3b0de9a5d583b0ee7348760348d46138d40f65c137a2ad08e6edfb50605858fa5e3486a6f862ddf47c24f47abf86e891e03241f5d81fea0d27056a72bb1bc4a4cddca3d027bbf695e2c4e88a19b0e7f723060c408134e60252c1545b8f0de51d439627d82fd475b075f4fb797b2c39e8ee3cb507ffa6c35e0625c51d24383d94025abd924dc856832164f54940f2a0b8b3898d3be0d0f3c78d974a682dd1db2146c23bd2e8f1bb54bcbeee72e6df3beb903ef7c8ae1aa01cd38d383ee2c62b20c8fc3c94ec2d5479a29c4fd6ff42f6c4bfedd588e10c7c9f9a53037236875102fe89ac2eba5e678ba9f76932a5bf9be168df5cce35efd9002812cc72eecc07eb94c000aaf73e518121e65138012826b7391c42b2761a0771c1674f0ecd9460a02c0ce1792ef5279ac0408f0e01848504233712cae36a458713f59072b3522da8d52e26a3a5903043707d0f21196d00aa811e33c10e1891ae71d15672d5b67c4a870bed42d096a7c5fc7d931548da586613f13a363cb1eb4e631afd0e5d1f60827c3d1f9a16de791861b98f50bf5f63c13e7ed47ef0338be34f201f5798d9a1bdb9d078e11b9bbfdf816d6321f12e1b0ce4a3516822ea0df3716c993cca6671b16a5ceb5093531f33835bd0d002e96808e9dfa9b3c810217a0ff14e5c17a7f45f83e8f2495139a3d626df8e9c2e31ed16c5ba422def0ab50eab27e3727464ba6708ea209c5e039e37bcde2102e535dcdc7803ab4c62d6a49d90dd4072a7323a4c137facf967c66499e0cfe6099f432222596a784ae30c7b861cbb7272b52f1c99966403543dc0f82a7c917fdfd7608b6fe715b2df4a14aebcce9d4e70bccb0529d0b0bb97f8c5f2badc546eb2d8369d4e21d5ba14a7a06a677a8aae697a1721372d16d3bad5043753eeebd186ca7db928327eef4de1ed7fcea499bf72a585fc6f6dfcf97083a271325925058b600e483a8ab68d99106bb863a0bda51c671c1504ec5ae4e6f5f4f427ba3424992b8d336bbe91be08efe10d95843e51703dea4af9b0a4484ea3ebeaae8b3ffb545132889e4ccadf411221eb3b575d1f721b88a5d24e3803e0b68bf09bba0c963971ce0dc92981f24e5e0712fb84ade71d4418c382648ee1a5bbca1debb7f452291a4244e5ab4b945f3834fd762e355a72469bc7cac8268384bb31e5f7ae71d917dd4503063b2a825bde757f699701c739375b75cdcab23447e1e6e63b1ad94dfbd1758ae493a51a2dafe3337e5cd75e18db333a1b2f02c72293f289685d8cb5c986c80058c67afce945a5d65f61483407a80e71fd0b59ce8093c0ee442fb9b2c4866a11b86b626ff9fc4c05b4a676551fe48741af3e4e20dd8bdb71c0063f4e5acc6e56e75473289c1fefca72a7404c72258656f3da26a7582935fdc32cba8933b47aae5f7e319c213061fcd94cb59f727097e825f72dae65e4e7612d7f0bbb98f1d0059b676df02bed786e307a763d727c72652fae4c1f2894a4934a4667f5dcaf44678cccfc5c615cc334bc73bebf5c83e512dbbc73597a136375ab625f0bb2b96d2273e91c9005339fd91abdcb5a312b14894091843ec66997424e68761f81bd6acec26125b357341e98f9768ede72c0e80877d5ee64caad293fbcdbc1090eab201fe3b6504edd4beccd0d565f3f72af8a1d0026191d6572123de275467918bd8f81b57eeec35aa61aaf6c377f297288998e23b38d13ade31dc74e7acb617bfaad63abfaa2166f45303de929a905727066f6f480d859af70e445e9b8892424c4fd28062328dc87a5edc99412aa8e7230b8813d935f3bb67f1d7a17635001e9551561fa514950ccd0b76acea60cab4ff333ac08fc5f2639c9e51adbe537eb1b89fe86cc7bfa67cc8da36c970781695ecc011ca73ed5d8889faddccf6df039fd1d4e3f4d9bea48aab28b6751952a9672d31f9241812e5d460a85fe5294578ce4f67147e0d8cd397953a5a977028c6b72a12abd8c5532c693e1c0d266d59e71a5d7bb970ec5a1344a4befba081952583a7a539ddd0821ff58d628cd63fdd51dc54b5b04346d8380ee3be4da691e76983472f26e7ac8ad55515c7afc7601c1021b0d43dc33de3cd12f3a71e641c12516727ffd26e64171dc23a05ba2b9222ed9f5067dbbbca6aa00fdd7710b321835b04f3116c982bc8b50fef239272b6bf825f6c1e3cc6cee30ce721e3dd4f3555696007ffe814456b0dfa3e84e81476fbb2e6c3822840aae9d7137ce42d0fe9f8f9272975fa212b3b651382be02ff48918e2aba9224c7af179114b061589589bd6d56524c3b6c8b70012a1e126de0dfdecd0b481dbfba7866a019330f9f97b8ce38d726e257d4771e9fb413851ac83aa615294305c44ab481496c70496bf6b280b6072b2d144f220c915bdfefbd4b6a84e7be3f16eaf22176756c59f9b36854b6a640aed8be9830a052e5829caa086dc0e75ee45498d0b8e4a73ac4eb3735f43729d72ef14b64d71b86efd2721283f1ab3c89fa2a457a6801e11eb0da3b9526017867271ff1725b381bfd1031cc0b9a9b5606f6c29894364cc8a321655e01c8b3225039454d2aaa596be897add4a2f5b3d5c558e6a64dacfd6fdaf1e28d3943ef8c1725f6777c45c0033481ab3bbdd15520c36ef7aaedf3cbcb8aabf362ba70cce777267937d6a4c12e81feba6a16d685a22a9fe178aba037cc6d5196bd696512de31234a4ea375a6e93c2131267a0063e45dc1c35a9874110f8bdffcb3d25de63016fa6d585da88ad2a5de904dbb21905f6f28163bc9edabd12aa2f1be2150b463e723c683c958c59a4fe6838ec4f005643037b95c18384701bbb023e5c8573aee06641ac2aa2665d1ab9767ab7c915839bac83822da676fb5dd26256da648b4fe272eb05a4f94c13b8e97d84ae7dc3a2eccb958d68dbbb5a440936469f6ac9316072ec7be50fe0282ef77413dfa3d5f3b835462012c16b49e85a9d013e895f4d7e6dccc1261e8849972c4bcace4a04ce769abf52b061e6a77082225748a7e8ac0f4759cf04bcab7337afa0f2c361de7a2283d4a308dbdd495227704597d2cbd652447ff6a7a2c4a0c36f65d781853080f23002ca10aaadb077382a582f2c4468d7035ec906ab8ee9c96dab87c86e8f7f0a00c1227ffb024185a80e6d3d7c799969729e7e0f706abc4d5625632cda4d47dad085021b2831d3d9c74a59e00980130658d18a9780f2ba4152b25ea5040693b7d7e1400473317925098926164371c663725fe1f69b410ddf06aa9792e99767f1b6a623d1873ea891c4d70d684112cf9572319a682fcce74c0b521decf919fd14a9ad27bc5d178f3f0b7d556f5a374dce72f0aba1018d4e6ffc49057d6d0f55e80d9c962fa1bdafe71eb2295dfc43532d722ba6bb7a31a7ee6ed3e4668c97fcded38b1d46f90e4d18cb37a643612628af72832f0ea5d7f507e5c2f972599eb18360df56dd7bbda07569842d4ec7c7ebb07214284fc9a340dbf90d3a4a30bb6e88030ae77c05c30623e0c76d7090ce86185e077b4d46c0d42158335a0e37035a4c9e19e8b310f2b09b544ee9d9b5d59f766c88e485f8bc1905858725b94a9b988bde3705f0923e49c02e59bbda8d6d50ea5c52fcafa1758d8756c3d22373830f33b3fe15ae1ff68fd87db5f777cef888bf0afa7061722600036ec623f5c21d6026b4b2b5a18065570fe9f1af249709b8561ab47e3a738ef1a680640295d810076fb5f05cb1e5495de3873e8ff53d4d890200cfdaabb794b5905d215e510b42399a6abaab1d60f5e7568c8ab0ad8aeee5ca729fa61fe441e899dcd510da9e038f32cd5cbf0298385b409a88de9f3718a40f7254e9bf44a64064da43dd0b56622eee856aee60b47c2b1c3fe17deaebd44e436eb5a1eb07b066ff788ce4f89adfbc2b44877943006e7eadb13c91772a257b9472de66cfe689cb23642b1ae7c60fb8dbd9f8aabc16c3318fef0f1e69d705ced61277548973241a7630c81ecc97ae3effcf0c775755f5299af33cee2bfd99f4650483cf99d8eaff79e99ba289278c2509e9d8cda1ff6a2d762efc49e4953714e842b9b1e930ac9e25abe871406fa84a461bd69aeab1b67f4404664f12e195615a4e6e388f55c9a9de369fafe86172aa3cdae66bbe41dada7c08ddd50ba47756ba72f2f659069337bf4b1a18f731a9adf7ddafaf91f50e5fecc3667d68ee7df7867291427bed75d8e52e9c6bea08424fa6d43a3da8d5c9df14db680c4a717417d948fbb762ea47b0a5505efc7641434ca93ed684b1817b2c693ecd0c41046770d372312978d53a8eda91486c1c440eaf895b78a6b8fe19da073ebd64dee163300f48f9dba3313c72af3a8cd2a10be875eb0e5b49ad5f1c8d639c347d2d08aeb2452e4605fc593abacaed041ed20eb35206ab7e83976f16dec39002b5e8ea4b60b62630ccd64f169bfd907738efa2db53dedb6770f8c3c43500881227d3dc38a5b672984cf43418569226d50ed81230fc4dcb0f4ae5d5b3bb4686721a17ddf8058272bb23284cf744b56c422a641843ba6ad4ef73206b10733ed9cb8b9e4d02ab6a728335ff132fd4affaf77ea1227720e6dfd0a3dd2b4089b8d2ca5b7fb51cea9172631d0c5fe8b221af066b1d76e98a4ca22169cde5700f3fde3adf68a07d2ded727564dd4d77d60918fd367d60b4f9d5cce87c56ed63199a75fab87449a2edb77289b0d7c2805805383a339fd0276a76bb5bd2f87470abe5090d7a0c4a76ef7572f12d098ad9c9a517672ca49897a632722982df073b6b60012c15160f3eec7672e14b4eafbc136a880ff3ad8583b05f9d7112b6fe3170fe4319e478fc7d414c59c8337fd0875847889db12089164dd267328fe3a7a53bf00dce2ad010578fd87298503a7d8d2c595b2be15a58a7a4603e89febf4942f9d673a7add1b54c10f6588b1c180c5065a47c83cab28a6a80fb0a7fe66c7430f59d9eb2982c1a6374cf7240f486bd59976b61439890ab86015809f5c18a9b7c9ddd18b18cefa7d8286d45cc7d1188ff3ad5d6d35d66cf788dc0e6e2684ec781ce6d9d86a607aa99f03f5aec260338c3eb19f8370a429d7d963730e72b9c72fa083b4cb7f5d42d2519ac52bc4d538712732a4ac429a1b3a304a2ad86c23de403fff9e13edfd3261b929651daff60c1db2cbef5c2fc4cedb95ec41004f1616556363fcd7a01bba5ead1645d8e43de6d6ac36ccc3ec199d73aa75274ca487be9e7d812e66726f4f13ef1a9724a00dcb3ed50e47f3562734a335dadaf7c21a90dfbeaa75f30209a59c30637721d25d627a9ac8651af8459bc12cdf649ae110e1e4dc2a1d0a5cbc304298d02720d751a02afb90397a541e8476f8d03d215a578efa1443a50aaa9e1abb99b9a3dd92b2494d1ce87330d4d01704466d393a0d8e42648fe3db3aceb74a6c6f8f3727238f5ba7fe7eae93f588959893d5e7368623b903dcb97e77e61807044621c727e39a0a3cf67b6768a0266db2f89ef0ea7e87b680f16ba0de57e3e4cc2062872e667f69abfaf684bfa2a3032588f192590291d9c7c7ac792528571df8c473e70f0ec8f2c048607595c8f87be654ddd20ccfd4a1ee01fff1898d91695f3572872e79be77b25eb3e0e95eade1065be78dd437ae31b6ad23222a73628242b3cd6720524f0104248aa31922eccbe8287a4dd66b48cc7ec858ab98b94e4227dc66254e11479d7a876486ee7920298e83ba1ba6d11be93bd846f94f962d664c6bc1872121ea3d42be8f8fa8361ac751fffea32f0a4754b75ffcfb704ce676d91f4247262ea556d5518c289ce25dcbb9f742053313e6fcd1cef6bb66727192ef865b072bb1b3ee4e8281aa6d07cbb39e9a0beaabf454bbe7b05229f78e256b8ab28bc09833936ac94a94ed207c0494db3efffb8015b9aaf11d71ad5b05490dc2a5bcf19d2befc7b8cc9619dcede995689468c3d542eee3c5e5f00675a8ed8782a828830a49a784591d5fd2d3e77ea314d7a3d1afdc4f172dd6b1e50c2fb1100742ebd729e52d02cd378cdd9ef7ae7639904cd9a71b9ee6065c138430dc878d6db7813721cc4a2e135b28490ec472c0d018cb61870e175d0af73acb1f674b55db9536b72805c5de33adddeaa9347bce9c7a5dac5afd33a5bc32eded33f121b6a98f6021a851ace623e8c224a49abfbeb48672c232f628ff13a128f17de43dd9cd495e27225287ebafd3def2aa17f4bbe753e2622e092a9185dfa5a0985ecb81c93a9c705e1dfddafc92d07acae281a07c20a8c1548f7fe40f19e8fd1941125e7d0d2627210f948e6947fdb1cfadeb4bd19815121156343758e642f09d1ff4503385bd672cefd104a3b262ce5130610e5d3acf72fc5c3132513077f528c22a0506d7ce5727975b97fcda54b4119670551734fa0d1918313012a9c36c05186035c06644e72d3bb53dd06b7c339bb4d706b69900d66768b7b92ff821b84dcc8cd77004ee21917100e72218008a3a9bc94b87f8e64274f905070ba0875cb56a66a8b8681ab72f2224163512f0ccb7930d75a5cdd4035e1230cee6d8b0438cb55e52695777450b7bc5f0fadf0b0d0513419a595e7ba83260379e50a50208dc6a07df3852a4f31af4f578a05c452ba209cc7125c5b46ae48c3626eb70f4f2815b6409cc0e52d72d71b5f58e1ffbd2c7edb050ec8b689d430510d354dd28abdf6e23aa43eff6d61d38ae73af2d4285b959ad84aafd9e6dd13163c8190e2b40cf513f021c2b8d9722da593ddd6e1b98784d05db1b8f6fc5f734b1771eb4d9450a827145dd5c46d5b9ab595c2babdec77436b09a755f949e04f20033e26877c197b57135a8ba71f7221da7cad4fadb4f570c35e37f6c62b2fcdfc1228927d003cf4c6e33cc8ed8372c9b6a65601220ae08232e6aaec1614dcdc84ed538027c37da5f6945dd666f472d0d48582ab673845d4c1e08b2a3ad4d8f13fe0ef42384f328da0b5289b362a724fe84e65449f588f6d77e4eb721503bcafbdda030f41178641bbe01c51f86f7234c7e80d9f6d79e6a07b92e58e58e7688dcb51d64928236a4c77a43dd594be1cea25e9fe0d110ecef324512f7682f706f19168007d0b1989e490b602cc32934b94ed207966031da0f1ebea0014c450562edc4beb709e3841e50782b27e7f487250bba7d2ff2186d8d6c92f22feebdda3192872a9e6bd8d25da390c10b455bc72086916fdbb6ddc8253c4b06c9fa5d67350c1befed2bdec71347b177b324b816831b5c78fc4af973c22da5034700e4b57f3133c18bcab87fba840688f0249435afe44473dca5207dff5dbcaf461f4ab8143bc9239166f77f5b0effbc7a9738b4067dc6eda920502a971c36e41147de3d1df89796679067683337767050db67660caeb59198121dd907b2f11602fef322eaa8de5fbcf26b8acd53caff00cd24f6945d1e7da7cd4f929e1caa7c6c5f7dff36f3cc723d6a93312b082ad488c3fe77288a1728e026ccb7c13c87c9d1d9b3498f5f0bca49be4584371ccf6c4d2351272532f89a8cf0964734cfda10e6eb8513e4315fcd106a92d4ee0ef6c2c0606ee7255598f462776d5edd0594406dc3416d536680ce82f44b55f1d8795c985450872f04e2bc878a746d6ebf2d2f81fa1aa50d927735d33e368e7a6f05778a425e310e5dd6aec80500522cb1d96e9b735bf106464a41fe7f9dd3701572f01451e9025c7060e08eb33abf515bdf05f1e770b9c250c47019f9083c198b213536dadad72ce2fee40efc000269ac4a55d8cea052a33f1d030b67ac44bbfcc3a224956660e17969cf47a2b3e4dd74ae2db4685d6cb9960227b42f9de56fcb66afdff67337275ab99c38ae340d23b9caf992d09dec8907fc30c993910701894c27794efa872653e04d2b2e466fe851a3ea29911aad64b6f2e30124774abc97bf13814b3457222904d1e1e618fbe49c5d2e78db25c3ac1d6510d7de7c333d74874750613330fe52c3dd82ad893cbca22ca9b737b51c12a31aa783f498259eba99277f9a9b772ae7b79c33a98b8b82b75cfe1c8a8a2687d567106848d0a1affd6bd020c0b4a722194c78efb3d62a315a7ce7c9db652c55bab3cfaf515ed897549de9db7c7fe721c8f86b6cc795a152465bb42915a4fb918e554a5f90c1ea1b537d20fa519cc729d7d2eb6cc21117159a6a97e211f941dec0e1e369c80187de7af80a9a8e79b722a875c84c4a388bb653677a6b85d6a817daf6a6f7d7cc18ccfb83482847fe27201a3d59de921d17709fa015c28745eeeb6540322191366e2ea0114b698e4cb72e6ebcdbac0063cd74f1102a40602fbe6c4a0a51be55c65e91eb893ef29d2a5297607c63b721b5282d78c9b274eb59144acbe190c98b4a671119a7e560af2be2d35a464b2bd961fd1ab947be671367f038aa9179817229fc1af3a709321d10b7234837050b9a8b200bd0923f7f097332148ace13ce2433f4a9966a651ef9aca531a3c6ef70de4ba0fc6fff9c1cf93389da373df8b0883249100294d5e94b4d95b3c0d31c98f7cb2cd03dc3d464a1119e6743b7904d62f2ada980001510ceccc417e5a37a3116bf78084677c230cdb174e1d396661a6ef859e485ae8d4afff1a72b47c7fd0beb7f8631600adf25b5230d017202defbc75f6c89dd3d3badc2c0f3c9f33caca286e32614ff377af827aa63ef29fc21c10012441c211693925a7db0d1d2ae4dd3621b0f89da661cb8bf14d234deca7771158e644e279201b39bc2b322be055ed5a40ae0f5175cc674d950f94cefea543ddb0207e159cc4aaca955e509cf96e5167663a169d953cac623d562efb4d007ae07457ed1284e427a7a28c72e18869374aa260c3d957a0e47a2ea97e32a3b0bf205a850836a7d6ee1a53dd5859b7fabfb450629b86f9431d0eb6842e6f603d73b5c80609543cfa05e7be817215ef41f3d7d3db796e7b60f7c6e2f88ef31cdff06f4fac1ca21f2a03d4d1215013e412fa3cf90544e0e029f21dd7ded40a1011aa54427125dc4a5a12ea27ea0970a2387806bbd69a24c802a215acdeec3ec1be06b38f45680002c1642e4461722370311167e4ee0872520e3bf6bb6c55576b492e2abe45770e3c725f893174722ae704a1b5db64f050ab88082c908a08daee7e1b31eb0a98a68c29974a6be472a50f8187694f02262020724c6fa5dd5845d4e613929e5bc343e6fc719386af72d4cf14a3b6b4fd1635ce0ecc0bd8d3afebc490d38196f335e5de4923462e6b72224ed22916010a9399c0607a84ce4ee78292d15bba616421ba39ea88a2462510e43c3c33affbeb6d3450df0ab2ec0417bb0d4de4655cfed32b84d5250e4df2725cd831f11cabf5a02a8d31d70f0e9e01b9869c0938d9190523f760ab982fa472e46c0d8b591eae380662613c46f86d6c389f771dae062df67dbf82bcd22aa072ae3c618de5d1a478dc96fb229814688342d4a8df931d5bf99f7b33dc2c0ce652ada9f85b382f6c790eb96d58dfad27e60d44b4790a46249ccefb93e28b010072408c321bb158532c195ef7471a530dde1d089f0fe97b92118171ba4e7b66ee72907851f100ef8e5b0da6be4564a21d8b80f82b3af59d228d47c0309999891818cd0167f0a35bde29de637fdf892564b3e6a5609ea6fabe89b87cd8f77573ac23dc7e6f0186e5bf529f69eb6894fa8c625184cbe05cec2f7903b787127a8c92229f0765ae08462d611127f122e28956e0698e6b9b3e5cabc9f4dfd5c47fcea70887bcff7ba6b9775d6df573d329adc20eb560fda854df059304ccdcf9cca96249f7dc597fe4d40aa4f84b2b9e4266dd4da633faa8f0fcf387e23a657663a36372e0d9faaa61d34092b3f5888b3a64fd352012d97e02cee96f158f3062f0e5ef071c8dceda7e837c74b040722f7e25700255e99b89a239e4829c17c3b8d94a8a506d2edd93d5c1e4bf8f6e3a38a4ecd14a5554cb1a8caba98708ac249365f67b7245e18c5e62696cd2a0bd1bfa7ec2c8ead082b8a83ce26d07dc9460829fc4c2721f5838c346f14629dc1a422b1b082eb202e2eb4c63cd884637d7d41825649372e108c69b2f1f08660cc133e52993b9979136ff433c583314a0107e862b6b6e72a92508aa49821972fcbd2d10725824a7a40e435f6b0cdb94d2776429878ddd72e26040dabc5c52d8e4519704ed7e56e68fadc56ba63bf60b0115eaf9e3fac272be7525768310fddb44b1043b814d07e5dec70537f9e4ea4b258e54dd2670857227265fa1a0b8a2e621a933b1b69c69cf6d7a3fcda9da8aa011a8d2298c5c8672889ea3e13bdcf05ebaca11efff9ec4c813b486a4e4ebfa95bfcccbd0efc0c2616b92c6254935584d212ed213fd9d427b6ccd2bb3214807ba2bf42e608684b77222e1f5c27460be08a4d754c612efa3a9393d0a71ad438da949bff2bf0ce1af72a34cc4986a39311f1e31b76bb24971b77049d7c0fcc15975a7f866663bd17751fc9651a1abbedf7e36861871c2f70236bc2d8047e5020fd7a9f8f5872d205e72357dbeda8f9de3547b20a8b34005abeb40dc0970ed7ecc18b39563f66e3c4e72df7ae5f6c37d0d9f4f29288858adca3cd16792e67b3e1083930b74132e064f68558d4e85fdc3db4547a9023ebe6209622e1e508352be8ae49c2b16a95093a25724c2bada464a47f55f2e4db07a987a777e48cc654330b716f15ae9e580932a72d85684766d29f501615cea564f1f20866fa45fdba6694ac0840d5577c20b2d7204ad01af7a9d17b32422bee3b836d2976207c81aa55ae2fdcbe070b313c525725bc96e409631aa4730e496ad3eabe07b4487def1b9a8956cf5a8d11d96d7af726881f5f5777a6bcaeb1deb37b2ea8a539db2732f9bcad2284d668f924164f2725cc9114a0de896da0f0e0ec4ba6dbcfaa86364ce364bd263a0c7722243e1b73ec23fc24e27c0c4c33b0c2a6210488ad63b0af87e7dfde557222b77e7750fe872422bf235e51efd33a6c7b1992dbdd002ffa7112c37c9f3676d0010d7e29a1b722d0a8ed8fe4b1e35adfa92a0b7b254f9f181ec7c2c8c2b98b62bd014c740b9226a3dbb09fa6074ad82479c8333c7dda9b2e5ff5b3e390e0996de5feb41c28c492cb3b342174f389ad3e7c0556074f58232b1976b9146553ccd579a2fbd26ec721034f32e305d3a4e0c485e3220304884cc9902c2b718d3ea98c09ef9b0b1f0722b6171ba025e4a04fb1d079a3fc7e74e910f2836afed5a863d6c6dfe0a3846726c6671bc602229d9131ee2b8f28c8b4bf5836d2937b3599b06bc24467fb394ba0200007242e3892368f826928202014a6ca95a3d8d846df25088da80018663edf96b1c7276e91cdf9bf442d606e69563e989b7d9d4abc0c9ae17da8ade6d1e70ed0cd33b21138fcb5809d732da2d14fb3bf6a2e61d7ec240341c1e6990ebfa18a370bf70bf4b3e06c0288dcade8c8fc5d26020e6b36389f686cf88e7dec1161041cf3a27f39f4a065e3f059cb83ca4692e4a74d11b630ca76d8f3caee951d65c6130b9720e3ee46720a02f750eaf36e2fcf8ac25ff0fb829fd88b93243b398b31e7ce872ad1fff6138b11113782f3ee9899658dd6557e83e3faf8b5fe7d5f7bbf63f6b04b7efd170d577d46a46ef92a25a3e0e4b7b7e69d1ac69baa8e3fc186f5bcbe572c909cc9a695e0893505108385578385e3b80526c2615136d6e63038c7eb60b72d69639417ba1f95c2a222821b180eb007ea8468063fa1e5bb12ccffa64e79f04dacd048455e477384c8854e847775231b950ed41fcbc40f357887a41429c353d80bb07ef97b1a6ea7510b2376862bce58783502da9911c1a54537741dbaf221d1fff30f74c34d2c75f2946dcc8cd1d9a6eb7ddd465375e173e9b521a57cf92723ad30110f88209e9cf177758ab88b962f71da66942b16ece10929d12d7989822f623ab41e13cccc08b55dffdf1784e9433e85dbd7e128bb0af81a660ad184f466ff80253a771904dfb7dabc143d38611633d767a4ef55f54aac9592146142f1c59a40235c97750ee134bcf121409255be4f98ef0a7d74b4d06e874ad3426127254508844a248323631d9ae0e33b2efa532748b0ca6caef5de51abf8a10be9a58cf519aebccd45fcc0475d3307a470aef6e7e0ca7c1327dc080f10df1786a607255f8d69c206c3a4a5b45f7c555abd9db79abe794b4573a2d5309c4e67ce6497206a221d236cf2ca847b926159421654e580e8a9a15c9970f2a71932f678d3472a9c87088a3d7e2812e8cb94a94dd3821880c6cb832b7239478478cd714c1e9726957ed2181db6a869b115ddfdd954c92b26855caf30dccf9ff14e8d9f0839832b1de311391ad29a41f36d6aaf49b0c3efef4f21207fd616e7370dbb4e3d1581fcffe3ef1010deaeda36b12912098b5f0a1a9ffb31bd8aa32b45f29370eb78a72dfac286f99bb1389b7ea02835670ef529087a69295fbf44d2adba280c9222172b544d09d38bab8b6fee3d9b34c8038e66236b2f8db3ced12619e870d515fc45b582452178693e474af8ac47e3ff8240672e5866bb29d9c9d53dcd3783c7aff7265ecece0273f3633483ff29c00a7a844d5d66121a7fa20955b1f4f105c990272e733062a6583e2258d76ca466376653b8a8211b129d8ca3cbb2c3f0714e36f72f6a542445299ef5c8b788a8d7ee167d961694d21420391eb0376d123c7d7747224ee19fc835b56d7708c4ed5ef53894b8d7eb6393db0144160f5f2f3d083f55322e1c28db8705f318eeb02fe2be6e64c50aa7e865d9c5a5047c4b5976cce2f1e27cc738fa1191c3b4d792e7398804c4e986ac5740f8255d1b332bebd440bcc0ebdf813af704b0e9cb894a3456e311962a02037510ff56967ae029b72da7c7b2865b99f4fd034c5b3e646da0ca6d857b68af3bab4962677d53334c9fc81067172432651c566784d3fe87c42abff00ffbfa459933ef1b069bcbd1e0b656e09687231e28be276a28089d2313f59a38989d2f6bb5b5752d9d892a5d8841c7ad7127274e2686db5264ecf2cddcdeae3f5f16e8405bd2c200bfa2bf16ddbd34761ab0a2b22dd9f1d83ab5e5c5daf0252facdeef186fbe42b686d0a4f200e0af3453b187c89f1df5aed280339a6729976ba2a815a2457352fc19c7ccb1237f23dc9c072badb4a52614f18ee71b4cd99bdfe6a60e8b7d7645f0b02cd5ee73bad3876c73b62fd605038df663bfc34e49bf05246bdb09c2a57ae463ab995dcaa38668a8d4c526244fd36cc15fb5fd65a9c4e1fadb5a5729b5aaeb126846505127b7cec39725d0ef28cf34ea35e5d69f75f9ad68a26776d6a259c777367c8bf67e503b9d0293bb03f5b5f149365496746cd72a613b106d4370582a3ef7ba206444dc3ca13384fb2c221cf49c7dc77c83cc8bfc301e266ada6ae202ed2840cfc18cf88661272d55b81236f70af38a1cc93765c3c96d90b4d55422826255eafc9617c588e84722fc90e98571908fee32be1f6b2d061bb1f8252d745a2aa954e74b364eff4073a48697578cafbf91c251d9dbdee56dd5465a4ef4bd076c4d0b2c8d69427dedc720f2755c5f2a94a78d85de8e282cea48ed2128ddad2ac0167c85f3cabd8fb007215a6de78a0c2fe0186e1e59de04db0ef1b3580d24aff1ef6c268ceb0975d527243f99c85910c1b0476682cc5ddbe77fdaaa871bcba2516093cdbc510d7195c7219ef0c2b6dd3be1a49c0b1a53e9de50b95552055d22eadcc74605599f5008072c403391499ca7fe8c2a732e75e35aa163c08c122b0f451dfe52264381eb3ba72b928d1ddf6df22f67da5564ede1715fc6eeac968203892fd66e8fbfc979400724c6aceedca13b0bb81d088f2674976c1ce109a00e0b649ad063b0a8848c23272dd97c07c9e404640d1e4cb22bda294c8896b1fc7540b0d0b8f53f9b9c0d81572e90661cd22e1c020987734eade74aa9c2b88c3ffbaa181e0098294f2bf994e7268abed9d639213f1820c8b18994b0e8bd0144b20fcc5c7c8cc39748fda1a5e72eb164553b1808ea714bc8379bbc9c79d11f30406e8d6f3d6bd2237e54d95884d1de03f2790e2a5c19f2de57f0a9f0b40fbe181ef74dbe8ee247359f10c29f872579012383c5564be5024859c29c76ad1fec41d3ca436b512e0e8c24fab336c727d137d97367795b96e55b48e452305c8ec5db64f637ddcdd78a4d21a34a19f72e5b84ab50daa1ff6cc741f5ff2784d2f951495a0ec72684e88e6bd3e32675a72526777f9fa37c73b49e95a10a631d6858d96a68b2cd2b8540d23cfd7bd91a172594df9f724d7cb891b61ae29c242ce51d1eadaf1d2f3ce705e03de20469f4172cd952a29be0963f6e51436c37701cecdfbbca10ea19a5c6efa2a37f659f14804471d93744b05fd63949bb9c092a6357b0ee25ddd6d4d130db0257647fed5a572227518f2edfb456248f00d4439fabe0319136fb56e38ceb38b6be17102d65e3c65684edfde5681989e53c373ae88352fd7678d8bcbaf0b581a4b8f1c7a90d023b1ce365ec3fa36e057fa9890f9dc0465f04ac394e7244e5a23a425fadd94e313cab02d53c347884b7323f0ff1c5ac3f5368c3eaf14a0d1269b65fef25e30f968b5023d69f3145d5a43dc63002c0f44e4fe7a16f21a0d94463f400ff660e77c7263b632569e4f8f3173b05193b89d452aac07ef853e879b5ef4fe9b2b1d085772c7b41ede4d66cc02dc0f36c2322f36466cccd17e8805da3293b277b6547dbb5feff7c1b0fb8c99ebbb757ea524e9bf674e4b69ef0d8cca4380c8cbe6c2d41a72ee97c9912f5ed4d8cce4249e7e0113c3c081c47a7ba1d3a154fa5eee5867d563766390fa135c97777ad1e8cc36b6fe7fee01887a806cec45cdba6805a7b3113694ae8a9a79e210ded07899555ed1d5e181efabd3f5849d9162461175e9cfde5e275f2677f39560d7a5c20cb3b7602f6e37ab63be90fd0c57f68df10e5c48fc7223db15f379dad6b260143b936492d3a53f7f6b4d5995e79715432ef2a616f03b9ffedc45e6215fcb197275b911c3df99ded4f331433c252ffc3261c3a798a0497284f49bf97d855b3075ac327008b931c0fae68e60b61d2bf96188576e7c39721108c1a5e7a38b57f9d5af89161880028f640cc0453f91c7e40f04be3916782f1fc8a466c694e007e21b881cc04c4cc98422b41777695b7226f33b22c5ffd825e5812a141cf1cd4c97cf6720ef592dfc7a2caf25eefe9b6aedcc01e6d9ede072fdb80b5bdc84e147abcc4d5470f5b3fe049b49cc69cff43f6e64f423e5e89472aa88a7563095db659dcf9766b13fa1706495eaadb8e1517aac2313d4133636722e9ca61023eaa5d33086f24b515d6f13d89590bcf0762bd211bfe75f0d36d5724362242b2c33a458c18f6438ee89945de6513b3a98267c9939ecf4605a770945854c4528ee5a000715eacd84795da5bc0390d9fc625240f137a4e9525bfaf972e48d1efa4f88f4298a76cbdb7d5d038e1dc6dd37727d89f4a50a2b19a2b69772d45037bf8a000dce747e9d503155822f84a2bdc3531068d0fbc0f3c81dacab501d9a6b1ea0be95220c15a3bc4bf9e1e5d68d82b52817a8de7befdab98f72e3729874352f6f7c79bc14aedc2615bd71776f10ecab912e983c0c32f53c46c51b720e5ad27c5441c488996d1cda1a792ceeae05a394129c1d2cb89c6c0df696717289eb7173f1db00ae378346fc461103dfb836b387858000d76294dde1b6ab30627d9c7a783e39ef003200f608ee30739cb956e5a200f43eb710ba988148ed45146753cc8b9122f78e5e665bed5788727df58c18c1b411cd1f1c10da271a569172b27f269f6af132fa642db0b137000b96eb55ce908f52e27e5c8cad801a024869643db022e65eb71e73d984dcee3e8db1b5331ca24cd99b994bbb9d63e9204972a0e0b2e2fd73b10eacb2934dd8f249e634a2ad7ed42ed1dee783f2a0663c4872ef846762519d0511386c6ceaf3c291ad358ecdce8b9d0c20ac84eab07cb50a726811cce6b60f0f64e048cc9246db89d3c45ed01d2d99cd7ffe62e8a0c54c4f7279499ebdb632e2a5ef588a98fe11785801fa8a554cd69a494962255ad8d2cb72e4c2dffe4d2143c80ec0d984a749a9ae596133352b1d82ad46b95f31a962d272de1175b796a9114dc7809229d88096d1211f283f56620e21b50cd5f7749fc2723f43f33e87b95dd7b8d3692abf13a5bb5597373f11d589d9f203ac4f877ef272c259bcd5e788c73258672bcfaaf362b77444f4a73697881a948323738c793d7235c16a0a533bde6a856b7d60e548cb347f99704e832c3f574a56d46eadb36772f754421cab33bf9cb7049eea4bbf9ba06bcf008c5e2407e92aa05a84038e95727af69c989c870ae597f67575aa7aa1895e02625f3f41eb501b85ba07488d694dc2f8aa044a20c7d15699871bde18c13d8b2e680e95cc131cd00bd714ada31f72633921a3e1fc79938f5faef8575985e512aa6d8f3c3207870ca3fae099683972c0897ef1753f90a9e5ac29d120eda9f7801716f9dfd553b4b2db94bb87954f384d13ab0eb516b1cb08bd7e5d6b9e8276843751676334afe7388d19e24d000e7292a05b5bbdd4308e02c41ffe91a03cce18840583908e67623eeaae6d51318d7210ef4b69d593977a8813a7307b29b1b57922ceb1f8aceb3ce16d4ec306bf1872418c3997a2457b5657c0797ebc9640f07c2d6cb78469914c9e7ba0ba0e21323a9c4984aaccd7e249453cfdb462b6278abe48086efd79b803a94c695c6960eb55a29ba24af0572cad14cba15e63e20c7d23667f923f5507e31f4e3a0b81a08c3a96b1b7a60fd7c91dad77e633e062d9b87fd9c86f52c5699c0ebb9349b05dd472591950c268d22074809cb18c7602751d01d85dca05e1199ded81ff5e5c48e208792f6eb17bb6ef6c70d2e501185b840498b54b36c3c77d95b5f1d6fef6925913b77eeb868d7e2e8176a8ded07aac977759af18fc1feb5dae8c6f732e12b033725f35ebbb8605fb0b7f065cf11972ee5d644a4ee0271beb9fa56231d97954964643a7ee77189ada562856006b069c6055c59075d144f29ac1f190faf35e7444414ae7428a9ea467f13454195db2c256fd2ae2c6f4c1cd6fdbd95caec5ba09bb7200208a64d58abed9531b1b0294006fd4537fb04c298eddc282cfafe0c1da2e7237269d1b1be0b2d69e69e665c27537b67e231231904f5417a31a453458e56b393e46e0ef60346bd4976c28758dd2aeff995cd814825e5edb41ea3f700c2f5c72a705bab95c361014e1a9f740a0b846c333a8992b815caeb6ee23d2e6d3a6cd729573f2ddf58286d890a9025965b7a2a567a7e6837ed5c0ec85cd792bb0baec5031e0f4fc4b75ccf5988be1265954f545179cc6ad574db1ee8d5ae59f6aa90a72f89d95b4ec5b77be99b2d96181cb32d9f7040149c113197d63a1b06bd4d0f9160358206fd3509d8236383aadba1a9d39695d2f6ae25a26e86b8e48cfe5b8d172c6455293d87d58da098be94768af4e7cd27e24249a53cca0c33036bf87d2223db6e9f6905fad608ed33bb536e0bd64b913b3be2aed75925f76758ca6e16ed472988b7f79ed0a83be4eb945e5409dbaf17cb75b9b40764b1db60c47da46ccec700607ce0802738f10d0ea300c27d9352f10bfa283d22d81671a9ee3a47def0e727ecad455f38bd0e7b6d8d1bca3ef5d9ea2e26ac15f55fdb8cbfa572b608ec272e4fdb005e1e6424e9958a5446ccf6b8061efc731952b5eed74526c9b04ce7319497064c0f68664e41f22eda3b4ffd5ffb2bcb55ea1136ea756e39b5c24797672ff44c3f90e965f1d87e7e99396393926a3ca65c1d054f4ce63462ad239f9ea7295a0b096718854909765412bf7ff64f1e21828826e4abf5d5936c4dd5ed6cc72d9433ce3f7be1eb2e90f95912bd7557fd470a70e81f389d902d3483e0e95e572f01257b6443e5a2bd01016fc89591ae0385502f83b809d1f3b99323ba81a1572dfe872ac4abe2b7117f1414f768df162ae46a6bafaa03e4c2d7eb2c083ed3e7208528eabac4cdbc896699db1f389bc9b5fcda446c50fc31d2b0280ebcdbc6512b9da5c3741d86d2fc57023d3c422a69282df1ac93032cd2a5bf9ed83a153c728eb4703e8cae877e523e17d02e5192ecf42490cc482ce874c9582c05e5782c972a5b1f460c9f1642dc5a6e0a550630645d2301633db0c49dea4fa3963729a6b726bd2c9fd1356d07a75723f6810dfee70885a1f193c972caafeeaeb680f31a816a6e458cce56b3a988329828512ab96d4252d1ff5e8f1fc26a175788296521d604d5d2ba7554d641de9bd8f56214f99bd5ff7270ff9d1d077c8c4af20adf1b96cf143a6058a6be6f59f526fa6063398fb3167c18ec76cb187511d96b74d557a72279b52fe53326c005be8657311d7f61284933e60d2d87790142471f62ff13a079ebda5b518f1a45283c4e6fd527b7b089324b04b972e101650bdf18fb205064c8f6e5bd5317a6378bf66f833020df16f4b3c59b448e8364724da18eba515b5720cadff3330a15c896e4da2219a3f342eadc9c41d7626725b9e84f2889e54dc011b42d5b25a4f54c710d23bf6f5e429bab46be93187347f6bc3b7c0afae1a48280bb96333e513f1c6f535d2d48939ff4bdce89609eeaf988b89ede7db1e06c572e233444bbacdd443e4e8bd1307f9a998b507fab80011ef2daea199b2138406722674486e7b61885e8f4bee9d627fdf447dce307867373bf4e37b1edededd847203e71b1dc0c1909554c59f5fa61894332624afa5c9223845bcdd6926f3d65121d5735c378f6597ad602ea5e0c7c96d8160b898abd1d55dfc5148f2908b9f6f72f4e870bac75157e2243ceeed71119f4600db0714159d57807d226d5b06001d72a2d82c0d699bad12f1bae8197af91269ad2aea6439714f17a224e86001c8531b004bd8a0ed624eca6fd365d0add5b981f2816b9f1e7e8f90f49b3db6f9396751e1b6b8d48d764d14ec5eee61340eca11a02f3a7662604e6d837786f2832d4418a90180db5f0e65fcfa416e6df2c94b82e9be401728d7eeab75013bcaaa14e6725441fa66fcba7b901994e53c42fb9e856f9b9db516be192ff7a45c0ff118e972fd2c98c650eb755c305a069045ed8151214905b4dbf727618c95e879def3696df16289f05eeb45535a8da0f34ff8c41b0291c211f0a1786ec44bc43496e3ae53b341274b69b96025f69fe10554c94fde5df53b721a38790e4e072888c9096872cda221260cc5ade83c3cf1b603f674ef820a798a3c21c07b6f646f78c04ba1726a7367b726cbb636a66cd4bc720f2d417f3bca148c7c0af5b3164b11b529f872670b5c844da17a01a90632ce3db6174bfea2f707fbe86d21d10db8a9ee6e9f728966560b6a368960c98bee471499959987e1bce0f62f54c2dac27a461e2bc372ce4e00b699ed56d6d955dc5a97f9a282d2af0787cfa89865f000377eedfb31728c05a70a857633175944e24c5a28d90d1ea74963abec56837faa03d6d75de97267630d28824104d16a8682cbdd3d1373a2c96a2c1d995a62ceea6adb26d3727283b2beabce1a680791ad7703306dbfcdc6e6a89bbd58fb9dad60f58078cd7562692c45de661eeddf94ea220857dcbaed31026aac45a4833ce2ed173fe90ed472afd38bfc1f45dc4b569acaded3047365ff85882abff3d46686f090c69f55ad721516d628842d64b31f9d0c9b220a776dd9ea67bb59510e390761ce1deaf4e77071078e4958d97d77c69df5ded202996197f87322a4935c6696b08807d4e09572905e1c146ec6ff99a2304ce12e6a52e166454b139e3350fdcaf23350cf0f4f6af1fd364dceb4a29fd54f26895ba51981f0f404fbfa86b770805a521fa3862d72483d828fdd0cfd151a6d1223b291aa1641bbeb3fa0026555798439390dd82172825628aabd23bdf688465a475158751b5986779208ae49f0645ab6bbd1be3770b3db10c05be4c87926b60bb5ffb7d6f89dd7b38e030cdd039dfd919c4ec26a72dd7008f4623ad3984cb2a67acebd023fff0e8bfa5c1ef3a44b0b6ad3f9de3d722ed03b32c1eb7600a87bd861e10563c1be613d02e234e49801dbcb2def09fd0088327a119e43d0b6aee57848ffb2a220ff5b77d2920555bb50955f7d8f29b444b39eca1b3e18407c89f014ad65c116434739c3694d88b0c17eb55e1b8856eb72e81c5854469d7bccdba6c71d99025fd0169ab9789a816ae42a42f2ba1c19966d8bb08c5f4a417d2ecdc63aae651753a242233fc91ce1887b103c3ca60622c572799a3058f566db73a2a748ffcff9cb60f50ecf7f4140099f870bb4830d2bbd721623a96e8d3f1adc16bd4030b912a99a725bb55ed32ae9276fc7afdbc0211972f8afb3243e11aba13d7f08f1955d9fbc192169a41a76a1d81291628047bb961c6f6ab32d4258159d17df8589f485dfc784a9a0810b01d11299844d023cbe763d9f0f593711ad563ca6c6f06e06804e8ecc60d0f68c57498a30bc888f3940ac128f7bb0721a42a0a2e2e0a9c776c99e27745e0124c19681836fdf0364db2e3072b99d4b9a51e2411f1d4c0970afeff7c0e89cefcbd52a5dd75c75ba61599d59543270599d88edf5258964f8dbdfd4eff4a8fb6ae8ceccd3b7e8599f79285c6b3149e8ba3843e632a6d1d29c2efaff73ea7d695509eb57981a95b3c7392a9b930fbf30971d8c9c9a4557ffdf4148655e975fc5df35a2158637909124a0cf1132725a1e2a915505a7188fb554eae928fad569498994242793b03858136ffc223f6dea754b8cb16a060302db1f332f3975cad964d74110f186f53acc8f7bdc0631726244b6481f869901dabcc0d4c3e58a07a8b0c6e32267038a72b07ee2b6498972541d83dc174909b6142214c349e31c423d22ac2e7860e61804e475b1e5a0a408779c40524880a7fe150a7ed57974006883afb971bc2d872dff85c34270374b3a784dc0ed292bdf430d3f265b1a968830ace10921be1a52ace5c93b204d7fdd7257d5f20b15bd6116b17472ce44b8a7c7a5b5e21006328a8360d3050cdd656661d4e5ba481545a52358e0dc968ec6a8321aeb4a512face567a2e3f210e3522072eacfc961a95152ca6cfaaae1b804f12158ccd1df424751df329998d7bbb98f321c2a124703639a6b876c9cb90dc108f96f0a3122ccb3bde9d44091459f37f46df1ca9b99202e531f32e69a0deab605812152cccac674b8f96caf6a0467f8da72d487a50c9bf2f21f98376c11c4b8195f75c504b017e0db1f21041c7536fce072282a19fcbeb1da5e0187b39b648d5da8b568b1a0dfefb14becbb2171215d5724dab4d6f757e6189395c0b79eb8e054450ac6f7ba82c0563bb6e1b530e97d1958689f423bde43ab1fd06d8d736cb37c2cce110425ea9f5ad2c25d843fdaed2972fd392aceff4a388db75e2930e84faa767136560e9908ec02c6718fa2b704c5720b85f64bed8d6bc82146d4351424855e86bdf0fcbbee9a378a5f02c2584d8a5035f2ee9bb78869af720f5ee9a3cf66db40a372ff8dec05d400427b669f05fa723e53865440e2346792626499a189fe467daf2e7dcfb95edc27ef75afd3ed5d724e8fcec4d78b7e2d501ce70f973c752e97543940f547686a6372dd83e51e6b3f725e561e3e6652838f6962bec6c88dfaef187727492ec9c10b5bfcd8c93edb72092f253a44e48305282d777281453a849acab04b30d8cacb64968d985017cb0d59fbd75f08ab50b6db7c0964758e763c7f6039cf5f3f8501e653a3d76b04da71eb2c4b33e2813cdc496e48f2f609c68d1967bed16af5dcc7f70fcd37e3de4d6246aa4b8898c98d184120aba0cd710d1ba338233588807328548b54be9968346e73caa78b111079f3e3ce002232355c73e12390ff0738c73cbe7bd92ce91e8c7248c27f4302c2c7407a86053eb8e4398a8f557cc5d59eb74e8357997075034756dd1e07c4ffec22ac7077c1de51349ddecc7dfe6dd96810324d92fc7f1eac91154bfa91888ec2cbe97b57314cc075c9d6133bce00f5ff34f53918629b8d6f52726baecf02051385e1b5846840d0036a6dffc430b88b3ff5c02260b939c240f42ea2a11b6691b286eb3f7825a8327b6399ef01bd5ec372879766686c1bc30b647295390e9762828fc1df1f69f5f83ae232d659201c51952c98d27835cbd6f562720681ba9c82fb9b19dfd7b65c376ef82f465a2fb75ead29dc67a1f6b45fabb572534ff85042aca9f74813c6e43b36ec50f4676f66277e377032be4c201de44172cdacb6328d6fddef550ebdfe5488cfc6575094e129ed97e25cf903171b25b90da7b3db612f5a964283e27060e1015ab3fe8faf1ecbeac7cdaf117ae0b793d272bac5e03590845ba7bfa3a0c276ad610a646b2d60ac6dfdab6e2edeab6be548725feae4fbdd222a057fa7ef21840079f2c63a3d8849be2f458f9c9c28ff66d37203c914164b8fa4af5268e8ef868c406ec18137ef5b450bb229c686e376813249a77eb5cae9a458eea5c190b367eca61b08145ee87e65cbf9f19d4cb1723f1b721fe1ca204093377218f825317217bc8aaed68b2bb68ac274510c1f06f90604665f8cd71d3fc731f09a59e9c575407f30d8bc44d8970de2ca26213f13c2575f724f088eb3a2f937efc4cfcb4f4e21889a99ab584fd4fa9de3b001a1db5243935c09037f3a751e927443a2ab59eaf7d73bbf9312e07579f6d95ec07dad793dd07243108ae96ab87a19b1c040008eb09daf7a8a81e7101593d10fa2a6dc65d4ba3645156b18fa853d96d46dd8c23492d1a57b038b94751ccae747d9a12d2d67d41c0c70f11e47f56b4edf445a69c456dd41bf96ffe7e7ca74700f1b183264bc06720f2ea5fb936d6264464c3951c6c962a6bce1940bad89c28bed2259006d1ea23354ede276810c7e1967b992ae365275099a15b220138cdb975e0ac700812a81723392e8ebf7a69613c5fd33596cdfa481b723f7845a0ec199f7e5d93630a4cf7297e3d9c1fa3dd80e590ce70192ef6b708361d0538994081a42ff39367384d072314eb980557f9c3c72b0d2072375cdc8b85be86222b9eb8ba568606e39404e7238c8e93b8eab78de9e8e080d793d54ddae81b8b00b4ddb1049300b7915d5f872db1c1066528e61d3bf7d3c348eaf618ce63660f3ae50ff5208c6aaf08164df0fb882681335f3b7a798e6208b68baafb73149afb27267f6be198877c29689c672bbff27982628f6fd002b99c42c32b14eb9d3c963cdcac7cd10ad6498c5e3dd48ee0ba31a2881791af0a025c1f83a0616418929ba75e74a1bb47cf1978430a058dbfd077095ed2431741f8e7dbaba00dcd32aa5ee781fe8b6e2cc2340c1c4ec4bda50e3ce12ea5cf69e65e6c7d74037af511931e65e70bcead2f9399b5aa3ce72ab565996c520710a12dd8677aba8a88471ee2a8a063aab7b5c87e1c78684175652afdae18f01584f978985fd883e3853783a385a581a9d774f725576a23ebe2bf316b16aebf2b127127c119461c937a4da4bd1117c49f225014183d55b659b47cfc21e1dd1b5d9cc71be188afd93988f524f3c1e17d50f47264f465158ad8241e4fce66e470cd97f9069dcf77b56e4c0c59bb06e567b15ffd38ea2aeda874c4226410aa555a37bb605ef8031143c82aa5decf8197add96024d7db0fe5aabc972d86e5d5808829b597c46958ae6f950299e254131ba31202e5672b5bbf49ba372970743373e161a65b35362961693890b9ea42e97e7562ccc881374736d6528723e1cb5cbbd3a26901fe1c77f8e4509b7c69132a392aebf5db7402af49a5d13692d0307328e20c9f82b75dfed10a1386514cc015fc77882c6426582e806b3157256aa299c1401cecf97c1ff08354d91bf8b85aa4128b1c25ac8a02a1ddbae2072a7143cadb54006701b27f0ae87bb8a70c4dcbee423e3ed732355ab1da56c03722e26ba6bd598acc67ef361224f60b6ce4adbfbd22b18e30e0c50cd4e47609572cabf105e8487761c4096e7180c915dfb0ff15cbd55e1e95fabbd63cf796b7d1a3735b03552c638e18fdef7ca52fdd0e5ac878fa3e6fb5c86ced83f02b8eedc72ad26912021e6bc81df3c0179d0c0e02a723bdf626a0cd5a732972bdd5bc6c372ce370be8e42cd0b2e443efe49e2f768aeedcdb431ac849eca552b86e2e682772f2f9768e422775ac50baf0ec35164becdccc7e2349704f6b03d9b9b02a49fe724029fea3b7df2f17623029166c7be8b8809897b26ff7a3fb45eef792ed4d9160b861e600a63574cb77ca32aab7289c39b4879430010313271e08e5a76a23dc72bca75e4f4a17f71e3468813ef06e71ab6a87d43cd5642b3bcb0e21eb1cc0fa721eb301937b826a0c381874ced3a2c60a00b94fe426e9cf23d0d771a7f800db72829ee7182ec63b6bee12226e5d5ec8b4a1c55e204ba23d08c0e94faa24b69872ae7f862bf63a77678c464c9cc39bf95a7d6951dbe696be9215ba0a8b3b8e8b2a007cb33cedf91da3d99d4bf252619b8f9b1ddf70054d3ca7a87db511f4beac4939c86a8219ba9758a91db466b1193ad9a5ea17f497597d3eb2f69f2cee474e20199161ac4499f502cff18b617b0b4cf1f2a4a5a1e577f76183137ed253e0c47220a63047222d0e84d51e57d4b7b069ff9f36ffc2295c788490c0fd288d69486b11da742f4ca5ae143cb21d5b01f14bee79675d6223cef794e920b4bf62646044c401b97f57ebe4e88e316e6689716e5e8874993deb0eace9a0a4c55f1d0e0372aeaf0b2c6dcbe63bc9aadefd235d0a714a683fd3a5826c08e3f860e0146c3672f88b32ab01373de7a77c4a625368b963d24514bfea8fcd0b9c6f68b48fd29c72905111d290b2b46da8c5d4839e6ad878b0a149d324da60a090d2ba7c4eb9027294845ef0fa1ebe9f739153ee519d9187a4245c105376476694f5539a35556a5eb5df2ef6ac28db1afa3fe3d890eb6e89fd090bf4dceee7f7d399492c944c3372e55a77c1bbc0b19997c4a12ccd9e625ad28e319e3531cfbea947c38807166a7233df4bd7805697dae0a720c0e58aebc4123956a02221669a1eb5bf3f25437b72c338932bea59d26cb54b20a8da0c03d7ef86dbfc7229838a81ea99477042a372f6760742229646768fc5c8914dd0b88b25f4094a28cd52244c3251fae8939e039f0de84f991bef50d520386c58625b3d6e29fcbd3d89a90b016d2f941dba5772fbbb003dc4d75a719f12d0536c96edfa0a79a00be5fcd871653091b8b2a98f724c0d460c708ad6671b2742e57c422096fe0ee43a67b372cd40db947eacb14f4ca77545241bcedeca8d5fd8c2899fca83f7a73088ab1588fca3c912bca7a5cf5faa4172f2f44c34c7b4c27cf1ccdd52552fbae4fa26d31c12da4a3b2b9a97e5125a0a2b9a0367f3b8d9392612ce8892aded2dad1e37b215f2495ff971b25396720578d91cadd2f93f46c596cca9c752bf7a236d22a3fe3598b332bd708d73fd6c174d731fcccbf3fba89b6d4f5491dbb4f10cb20b4bf9b0dd0545d866c4992239f6150209b77312388561d247ce2352e96604b9c3188797a9d7455455e60dcd60c5eecb634c28c92d5d03b8f71bda67bc16ebbfbebe1d1861c9e4cbee86916572e3c318a150fbc8582824717b6cc5ee2775a4663c7957362b887895dcfb5fe072f5a9a24b2241be0d668eb05b40d174a39558ec80738befd84b0f5d443b9bbd3682e60ecde0b01ced0558f5475428fa6e454baf1592e48e722fe24d9c39c3ea4d1bd72726b13fa839df19e9d2bb33890e713aa8404ca105f323caeca322434972b2345a9f00d05f7265baa68e6e3c2d4db1c5e7c3f7a521ddae302f979c16d77233c93e2b0350e5b8d17ef5b38079b526bfdea0262009ad42d8d81bef88dfe872e1d57ade8062da3dda4b3516170ef7d6e7f48351404f7b6b1bdb6c3973a87d726fc2b99a4dde5eb684f36447abe32553c04400ecf327f796863fabbbeee6a642890b589cabf3394c05a655d59c258f52a6e9116f0921bb44da7c3778eecf3b3e0ce73cbc274517b3a10532c2135ca54ce0161a4cf383bd63065dfaef64621072281732f576e011669d6ca8e504b9ee68f7cddbbfed1386f2d9233fa9138e927219432bf5177bf1ddf6c291b01093f0b4ad832da4497f63e1e49ffd5678d9706284788d89cb450e687b1b8822161949143ad4feff48146106f9c4e16ec26baf5d1080d8a292cea7b08a1e14d2dcd59286810dcb36e00e8dccd31087e3c47d0004c4fb1c76815e7fc0382ba1565d430982d6993b0076a0ac8410d40f4c77949a5de6d371f08ab5466e3af1d705d57db335937c69348e58b754f7e312c9c8dd8172bcd6d58f075ab8543ae68f9c1fa3ae6423152d7232dff52cbcf2c0165ce0eb35090a8fef7f7f21c0ffd4dfa3642260cf91a6d5841fd9aa6338cff572486ee972a17909bb56b5a6fc1b0c4f7a4d3e9af9ce573fa836d2a532da3e3806c45df572c9906043cf205ebeba27b656986bbdc8b1be416efc7e70f2f2d0d4add06cc172354176a59c82ea86ba2da48224d31f970bc907f44e389bc74b596f515cbb31721577c57298c0835c6a151564216ef7c124651bfdb67b237ad6cac8e7f2c7ea19d91725a2c07ad78d26d6c1cd7a4ea1696a3d2d41ec582d42c1fc5a78fce9d5727441bf9df1493515b401ffb1a1616e957f1a00ab7e45cebb52e0fc3d2abee2725e09f02a66a0c01f4782a7ed298ce5e70f5b03a282a5956c10195724e6a1a248f18a9df7de4b1af7a273cda6a2e3cbdf18e07b304b90500cea612d3571ddbd7213e6afb2a62fafbee61ea87e41ceb18d6636cb7ea5a4327507e4ece54ef5067257283bf0dc3b11054da378775321055534242f8c116fe81913c0cf47fea7786a6f608f58c74c84021dd6686fac7f913e1c47620fb448df8a474aba3147aff539947161029629691ef6252870738b7d4796c0bc78498c8ee7088ceaaf2da61d7278e1330f5b2996184cbb3a55d9a7a0cf1eed70a1ee52f6932820c1ed65a2c472f080869ae58257e71a7270e6891d18a9fa28a138a7131a97cc78eef84739ef722678ad86f2af3dd44e0e07fe129afcc31a8eb26157ed6df3b7364848a9c27319d07efc40f4d8418062f48da86a309be9577410d19c867fa8dee6410a570585720d54224446c3b7e9621959ebe95afc59663f71947f2210ccfda78662ed85e4271e7be568f91a47b6c7a4c7de9790a0292962c9628130cbb768dbfd9236626f373e4b26a00f53e0c5680e954f1ab4a87753368c3068bc0da633d2f53b4fe33372267551a283109c717cc40073d248de93ef478f43f6114c70d6aa3c1ff1f3127295390de7687bebdf1b9aa71f27476d41378df9065d06f1d6a4a4083406879333d82c90c46b6eb8902625e01228ae8dcf128010d6ac5b0791b570c78d3c1d687277e42c4de0ad3a4526a2c673c4c063ebb42adf4b0c3c5ac24bd281c510de6b723a8ed683fdbff483c1ea3e74f4deba23cf1d35a6d1b036d9e6949178c2296a7254ff422e5414a885c2c1e859def9b94fe82f83a69e69412e710963a78bdbe0645e47be8cd9e24e54d48365da67898fc7265730ff5ed4a57214f24d05bdd09d727d6b80a38a84c29644e027ef51ae10fb7775496e08998fa866772d0ea8b45672361806b38d0c561940cdae408d9f537756bae31513020c7993c95b8730a7ef726b8431ce43a3efecdff34715f82609cd8f29911083931bda11df3f348246a5726cb7c6ec8f7ed6a1fe17634b8309c76e312484314ce025189970020d9a5b44725e4d0824a7ed0f366e6305f668d2ca09f052f22728ea729707147992b339e272ef2d4bfb5b0f48970cb72ddd395fe2dbb52246c85a8eae7bef01c9189f95a26dd16cba841cac9e5ff5059c4f79126c0e5d4d8edb869b8d5cfdfa6f6d7d84c03f7fdb4e57f576b856bce91f53f0320848c4c15bede726fff96bb0486f4be1ec72e9e73882736c761fcbbe7c361d47c258cdedd03669b6735a78fc2896d2b26f3619456abf982e113757a4bec7f9a6a145776bda7978627cbc478b5d0b5bb90444d53660f0dcdadb22a7d8ce1336a62bcdd4d69727f1f534e7bf57b0baee1e4d38edde03b7203961ced0fefe3e7b1f3f19f85ebb4e7f55050326215c2d8752647245276f2c9933ef98aa3ac27724d9c688266b58683b04a3dc2e4ce3212af8a372662814d34637e634daa7af87c9521890983b3153eec3d9a978deb8af6e3cce725269706120fc2cba665af80788887f241100262fded0b1e9b2545e3cc6f74a72d4ee9fe330e63d9c778973daecc017d06151448d579dd23e9129fc474cd3b172890fa08d6903dfcdc0cfa60e3be3b2b507b549046696520d8fae1e745a29321f4552cc4bde8aa116f35a51d29706ca045dd578dfb149f9e0acdaec205a928c726f1568954490c106f4d9eb566fcf098882f54502be9848f797d0b8be6338f769b897a02c4746a23aea96fb63e980fbe6e59aa7f8501479c4e26f9ef8f0e51272b7453e2b8f72b879c494b6926f7645b3d42079fd43cd036ae9ca3444ff347d72b9f406f04f3fa4a9f11432872ce17872b818de258d1d9a2f4f6a47cf4f8d1972b9984ca8bc0b5b38451681471fb654f2082381f69c44a67cea26d99cc22c7e5cb1014d6803c653edde4437d4ba9416706c25d98dab62d99325536071ad7e876ff54e7e53b509d91bee67f6d8e25fcbae975f6cfbe1eaac2b04de15a426a9cb18204b73dbeffdf980a9250d71ade58b88a227736b9454a365f62074f4bdab8b7270e8f47ab22bfae8a527b76521ac115359c32e08e9a6761f75d8812ea328ae72e83eed14a7408afe6053cf6a21a011d6cc02345a0218ccc43135e1068cef7d503b2fb8a058ff679f63c547686b2d417126bd61b8a0e47440a7e5cebaa1f15f72a44977a198b21b9f9c4cf659cd3b9edded369d4b239141dd984e817a55b6b672b98e21ea1c95971a4d69eda53fafc3e61432ed99a8f90f56cbbb7ccef8dbf0310a0bca5083b56b9b954b94dd173223362e9ec88bf50036136302eadcfa6c0e7276adb79182e63cb4b59208c938df2545da4c66acfcdc7f2951528b27b3626b109b6c85c6a7e871e20caf2b2efc7a652d0b6b7802151add9069d2143fb8ed9672f01b91551fc1c1ea82864b1f1a68d0d4e5e391745b1ce5cfadcb13f90a035105f0683fcae08a8a8dcdff6ddd65a682176caa644918a12fe70f22448734264e72cc04ea7ac904e4264631edbb6fa6c82bfaf891d7d1f96af7d9b9b0f1383a26727a9cfd7eca1a763fea7f9e1fb2ee6ac5597706c0c5536a3293f5fccebea5f44f46632d1c45eb7302bfdfcdecd3947828905a333c3eba678e04bc039e22d56672375192e19021bce81d73c4a3cfac80b50555f17190cdf7413346ab019464ad267280686d5b7d2166c9046958027c0eb1fb16cf556a4c64cef03f838c2d7bce5ca77797e1a3dc6bbf0c329cbcb9058305bf3c4ca6d15c90bf05205e92c6809272e776b2a8ea344d046a539070eda63045c9226bb47757b78fafaa7405b8abba72ff8e43397e7c410153a4b7e48171faf29bddfaacc04901fc67e58e91a308d124941c1d792435b1294ef428b8f63969e14e328c9902218152b102db43d6a5f2210b8dac079cc0a4abfa91259f0325bc79af25d7f6e1e862981c2f7d73cf0ae17259afa4d6a505ac54dbf01353b4c02c549b98366363327de63f3b81ae58951c60666fa29dbce88aeb7fab51d220331440bd632a3b5fd5fbe2681cf972f3ac014cfe5f6fdec6120126e2b94122938db926bf5dfd48201a3a5cd0858b4b033b0c72d2e8716d31ded9024d594d3c853aa851aa67915d4d6c81d970549419e58e6b438207e781522d7d8ce9b8709692c340f9f231b3fd310a80902ae15eebdba1a665663755c6f28c3cd3eb143302913839caa49301a6d9dd4538450c166f9f7068726ce06f53c330aaab29cdaa6d86f5db38959fd93636b59fe7ee02ff912f20c17208d3f394ac930b5084f963d666aed1be3ed3b20e1247bb4ecd7099e8fed11f728b435e524b32c3f6a2a589665bfa4c7e60baa2109901cfcc8cf4ce47b927c4375ea4f21b3f4c2d3b7e3332ab9e5944b63153457f89df1bde096a74c321cedc720064b403277ad522a503cf9c54ebf491cd44ecfee2f108af35e03bbc877d5f5a80bd16496c476bb600f2c97c2488304f5a2f303bd28bc251becfae9c1f466f44bf40d96329ba8755040929be75fa130c7aad3d33c6dc01824cd3b0d9eeb22f726b3b9f72618a0d9d2d38136b119fcf0f369d743b72be37eaeca74e198be117723af9ceb6ff6bae90c1d14d8c64ad7d5036218d8183f2f4fd5a76fdf8480256728149e318910d39dd7cb500a778ddef4a8cf0a2bef8494bf3be2cd6a737650072548fd5424b17689bfb1256ca01bbd0b0459f2cbe952301b6db06ad7d59fe8572ae884cb7c98b5cfd3a611d230744254e504336ebe10d84afc64f5357e7fa7772660c00917bf493a945d04e8f51cd87c0bfc25fd7fc00d63eadc6d022abd0a772b9a4556909ef73259f4446c64015d51c3c3c05e58753d539277ebf561a91df721b2e2c5f4d9f014c2b1430c08d8f3c656823b1a7e62af7f9d404b06785d5091fc6679529c758062678b4b1a658b262a945c336b7bc4aa6f32239f53a1f09086f74bb8c5a7440dcb1a9291fcd1503f99f1f5cd734a814d998e09f8910682a057222144c4eab7860cde45d347d8f9bb10ce3129e8533b5559d0ee8d8b3cb8c6072b4922086f3e07bd4430ccd08650d915315a2da0205f90afba0ba9e62e745c0528dfc496e69c40f33ab63e52f8c981f2c9f66881fe4c93cd7b0665bc95ebcce72c057d935a98fe1e34abf80162cc6af77c3c0886dbd43fa217b96bf491d30dd72a05f604ce0401488300187544c243f3b81b6c98618919c8c24b6c79027f1df601882a83f9ec2f861a96b15dc0be5da0a48a5015bea6c418c144d907037be4f720c80b1dd9883663548861e481783dce95f2fbeacb6bcad797935404db8163b729574d08e522ce42a2d60360362112066a68f78e3e1e67baecffe2534dde3ae72d81b7f45c71a499703e249772997c595b9c0236b3db15f511597d338459daa729588a1b45f21ced894d934b8e95385f9a3408051afd95d2756e7a486749e8f725df96d57d250a11ef3880720ab9240ade1ecb8f0df7cc98fd2734d4343b832722056de81bb81926d28df4af666dd3e6247a69035c741502a7b737647ac682b096660b7c583df527656c5a4e29c4f0d91b1b559a5d14b4ec66af4a478d1570f314520c842ca77c6ffccc54d8613f266735cf9db6f3627c4f1f9d294333dd4ae72ee68709e360c1a26d6d32d77d3f051b1ade7c662d20bbf5e18796681f19ce643b94d8f5645e9f4d23865f660e7a613082d71220deb3cf2e0d2a97b886bbf5072897e547cb592fcc76ef66489e9c5c10e8206c8dca922be20236f7007ccefd61ac0b918d7f9693bab6ca0c39a23185e9434ff7a60392002f55ecbae9779c907720968fe79d432663f363784940fa8e7fd3dc7bdd92e1bfa949cfcdd6d149d09725d6b3bed09bd2593f798e02beee02271ea714adf7610e966ef39ac0a18853572fdd320fd9213a82bf87f076c1613b1a3945d0476d489ff330fb1a31b9e9b3f7215b35460456d464f28cd4da03c641d51265b1f26adddd02c7e43a8662dfd303fa745c99917e10827961c1cfbc9bf64a676fa6a217e15f6d89e11b2f52b87a0236ed4c1ee6ef0399e7c7d13659b6a97a3ce6f30863019ede231d9a4eba133fe72e440f9e1131ee000bbc602fa660a42f9e143605f7aa24bf508289737964e7e72f46d5b805442d4d9c90b2a22f67018c072ab86f1155bd9d06094ac79fdd55f6cf9dda15c29f02d1d2561083ab649cbea404f5dfe7fd44463c22c77ccd9163d7269df28c0d29696ecf9fc50a9b2cff34a19a7bb51f8fdc67e1741b12c5a9db372a733179d6606c0b6c927287480592a72b24b48bc50fa3d216118131cdd2221724260b0fd2cd391e3131bc267a3ae6384a8cf802936691c1ad2030e1e33a6c630a695b1fef2d99d2fede941e20cac9767cfec4fddfbb5a804f94032880012d57244a28673807defb93af0d3210371d68a70c5c9c73d865acc5eb11b72bf91d77284a7d952901852a67dc361080605ba63708ce25774a4211e658ce5d1f266d772b705d5ea80c895ccf063aa28742603c7762c359fa9fc638fca9eb767398dad6cd83a55a5f5f6be2056d5c9e353bfb0b099cecfa83bfe74a8af30b93f0e2f8572ca2f50606845cc36b70aa1661780e32c02266294415f6bb2c4eb541d18a9b7107d6531e782a41eab99956857d3f297870dbe7ad427467297129e92b211ef09728939b49039fc6c72227870b3ef27e8c6e22cd3af2f0685cd1fa2c8005c852f5eee9eed938ee133a2e985d099eeb48650633f0d0c41dcc5eb107d2786afd0b11840f8a7319c9d4ab43fffa88746165a0f73f561a264f611bfc771ffffdf812c36d2726bad6e80a23f59b38b0d938190dfcd78ed77b85128315c829137fc6104576611b8fac59d5ebcb7b43fc8d7dbdd06632675b38f50e8007e43917020eb064d1fc4be9e47550f9d0252574029657f1b9c90bfacca58b8bf8bae7e8ca8f065723740cafe8fbd5d2c9ac93e3bb4b95b829481cbdddf7a34fcf96aaeb6df9de11ca4e77a54d768fabfaae1b472a993d9a97d7b6ea67339d84ef7c18c4f17d65572b5f17fed4e06a76c28389571e5dcfb74ac783ae0207b342dc4e983514fa3f972635dfac1eb6445225362461d49940e07086535c503838c95fae5fc43fa7fb171f7c52501dae702695c73cdde2cdb643ea09f56268feffd3ee3fabd3919f8e3723d50ef76bdf733307659d8e02505dd8d5f6edcc60cd236f12ea2b63f7b3010728108a130dc8092d5db906acbdd95ea2a70693e2aac78d00a6752ece4430bd5725956ca6d55c9ed0633121c6c4e4255d6bf18a6cadc17bd9f544b0558f16e2f722007ac52f57315b03dbdf78ac16775f787894b00612bab8fc024e1f21c4cd93f84eff6d4193c5aeb4fdb33d66fea9447d6008173d3928c6314b540c6c92bba722b7b82961aa6fc36623b4a057aa19d0fd62d93785188f44a37e731a2183f3372e0b96041f4f45b52938502e5c793f4b7c47d17b82baf1bd84c2893bb292b2a0d2646d7a9041ec87c079d3c8d1ff02e584d2d812cc69220a07a0287a2a9afa072948a46a0adddd636dc68409a9b03975abcc02e75282b2405d108a932cb044172e5d8dc5cf024ab8a29c2559418743284311607b747053e53337d375ce746d3385ff5bad0d82a125c6ea6a84673e448dc554834af87615b3ae113bdd97b5a267212744d3e2d88fc50dd5bcf67e97d957cde434a90f4671fc6d8b083a81bf0947212ac79d297d92def46af1316b4b9531faf0efebf9ca2a3a013383fcbdc0aef4e7f526b1ee49439667603f0c448a5dccd36d7bc80b37805caef21bcc0ee2bdd5b781a5da378799e4fdb3f889bf0d433cca48912378103dc9b26cdce58e7f1751fa78b11ea3a18562d4de06cbe5835b1e2f48944269785fe32f402dd7ab4f4c65f26e2fc270d275217a1ca30b39df58d3d325ac48c5fc0800c3d21375239f2ea7213392008c40a1960239292f51f9e13ca88625ee9d88a6b83b2e0a21156de1772ce23e1fdb9c684c0949032ead213720d54d199d60488b71adde4d7b9a8b6f772762b78fa86f93dfe9c8d3f469012b57cd7b221d823baf23c09b414b412733572294de6d75f5077fe7fbf0e7463e44b2b4becd8e4ebb565d5e26f9bd62054e852ee1d660df4f9b29688c4bd456bed5dcceb26a2c68e9822c06a129b78836aef72deb865c4fb542e0c96ded87fc7621302645c0553210e697dfd8afadb3b9db70696425034eed35d57ca7ea3a061cac38ed5a6ab44a530fc24e367214e151a4e2bb8827ead57c08c23960bb9e6aef783482bf492faa29cdeee9fabafd09f856572d36771fbe5d6c06463cea930171f66806a98e3ef5ca2416acea32c8af713d6729a53e7de9a991d1d3b186085db1b657afa94ccd4c67ee589a22ee27ba412234f90dee13578fcb651201e873d72b6a20525dca1475f3ecf5a96d1da4b623cb97284416d6429e36d6c849877b352c221495e8b385e7602ca5a292d41e6ef9f2344bc3e3cbba261171bb25ddf88e3122d3c72e3a3718fd21325b3f345832c39ad60d1d694585bedfd30a383126479546d56a157053412526ce43998ff2a2b16bb729b99ef8389b7ccdf46a0a7a1ec547e338cb0099a47cb6087b0dfe4b665828e72c87523608cfc9b3af2bf65a8cfe426df40e766bb021aeb1bf262b5fda7177872fa72e7b711df1767e2faff935e3569db9914b3b190b2cbf837152b1f04059f729831e59d9a0ae95b8a8dcfe96a78fb786f42607f46f1e3f861a4aa3e9dae6810d43be16dbd6983a232eb251a1fed532ec0c91597aa2975786297c55d5dd18e7234970ee5ddffb241fbc5514fe7d76c593b53a410472282795ae7a7b5ce1f7f4bcffa270663e719eaa1fad80749f7dba61fa15586a7d0e145d9b3272ff288db72ced254f37386b81fd2bfafc84c15508031b33527d767cce0802e305b8f31cd55aa326c2bb5232f8f3d1e626b97ef695cc0f865272b0a701c64293b9f816bae72ae7eb76460a7bef5a8a65954b247e102b25911a75c61cb3aeba8b6c65587502db77db259fd446f799ad009e373e0845935e4ceabc0bb17a021d4fa6137a976115cfb901bf93009acb9b50c48723f4bf2a6650379a534edc6f5f4338db6c8d57236a75837370958a6e492d668e41ba1b9a87d4e9edcd7bfe28de0c166a1e2d072ddef430866686f10f296cdf270a52711dd4e2c362fb133c7aacccb5de9e1ab72f9fb5f0735917b5e2bedb72cdc87d8d083884a1bbba46614ae054d29adcd0972ecd4a098e576a31f433d357fdb418bc35a7967ce2bc5450fc98546aef179aa72f96c51b6394e8a114c134540c0e30bc5fe978b1e3ff52cc54898c851b0abfa72d4f9739bb3f84ea9b2dfe56e4e80ed6360dba71961e43f3eae16d184630aeb2f36fd595d2cec322606a0e2062d1500321a3909b9085108055c431dbe78679072439eac7aee7fc12ad30f2d949b9af9a65005c17c113c04baddb5dfb017a8f972fe21e1910dab80de695f18922a0c5d1f880227d9070188fddccecbb79b5f3e073398a32647bad537d82ec48c0847af0cb5e5dfd1367f3e1337709061d56fae0e9f5663359205123bf10351de4e2ea94f9e3ab4c8d70a8a7adb46f5a641f5f9034d53c2c779d391fda69c987af94d6728d3437b23ddd3768ebce9e208eb886b7230eb9d05798d187cb67b5a654b33aeccdc1c386aa3ef8db0e007b949693d4c726635f4125aaceb553d39abf1283341ef21b57de7c8c1732de4b8d9971d5b3e35bdabc485f0b3c69ce310bcf54e2eb1e52f5e696e7c93db03c178eb9620a0f36d2aeb1c5c31e2329a31c2e931eeadc9ac0c333d2490c137d4553254737dbc3172c2d1bbc6361e08d45bd8fce378b7400c477c28d4b39a0031928f7f1be3139214ac28d889f495af00d7f94333f3c645124856cb4fa4dcaf1019aa7683ae57b53424ada5ce0cf83aeb082a546e7205c5f07443ff81a91e554d67c02db56a42fb01581de4657a4585a069ab9003ba6952c6737235335a95264a3c673c85a3577772d391a1039a2ff626980d702af48a04472a772dfa7405521684c16cded83285729f513a331376ac5a99d34c9cd3c83fa35a2a96be645f1a88f5ef8e22125a761a87b3d3129fabd6fc4242bf71715dda2240cd21ffb0bf6f53297d6ebe2c92b112bbee37dadc30fd6226998e1fe77e681140a762628909215c6dbc3fac3c49ef727b450650694619701b965077bc92179da2b310b6c4a7e38aff58dff9e8fbce72707fde030073e431b2d5877ed2e7cc66a122d71698fc925a5456861bb4581a71f3f4e68076a95767ffb8ca123036d083ba6151b4445364a376882c0111ce3119f7182f58d6c84e1d4b4c0bfadef369cb66f1ba3da68e2d573e1fa3f928613572a9f2c67a0e911d4191cb604c6d42519f0c44fbeb4d407e5d9a9bdec61393c9720fb26bd924268ff22d87fafac73b0d80974be6e91be2fe078208973e904db14c5de36540a770f415f805489b4d1414385895b048872d3fd8935cb100098de03eb342a494c1927f7777035a3125052dca6b5ca0324588f06389fc1d0db3d0660c8b34575d8770504175ca5561a73064df03986c3728b6ed0312d4b27b4d62b2725247acf7452dd465054b23cd4f3f4f35371936e50342aa25122314e98f8205720e076bed380f028f194224baa2cd643af8e042d2c99b778dd8d43d7678a8092dfe4216e6a7e821d56fdb929475dcfe68c4afb2933320b89f3b8ab538d69104726629e88b4314557f5ecf8d0877f2557a291ed67090e09cede10638820e96c1723df01dcada86b2e5fad7ae3edc74b770a244d1da7c81cfda60a35be3981af272c042c42ea67f3ab1534ee1a6f3e442e4a56fabee70545aef39d377fbad61597225bac4c4d2700cebad5d373e43080092ced9621b316f1aac5e9a2b8f5b78a05c8efec61abb0a07260541cc18ef8161f5d33c589093504a76b3f93264654fe617b4942db299efd912dd21d73cc4e662618bd7e0217c55d83698b521553d8123574988db6b866424d4cb07f3fa857561f63373f7e8d7385ca4112a1cc932f8a77293598e991a29e146bbaefd0650ef2b3cbf6e588af5ad4923e34c354b156695287e8226c6e2820aaaa2dd446aa7e60c5f5aefe2f6c2c62e44f144e89bec6ca55e07fa58fce63801f3fe9babfa7058b7a10527e7d2722d631ddf9d9c2ea8a87962d84a5616b1b3cb705750f08f842328092298380f2a510c7fae273f4879fd1a7221750e0d6d7b7b31a3d8fc99e9581b76fbc5ee62d2a1224f5faab5e025beb17276785990bef99ba28751918552eb77ee28a995d6e7fbeadbd44b4293afab6345d8d28ad3ea06e4328d9561693a39b0e1e9cb8b79a4f4775cad621b613e469e237b2f67c31ae7153823146bea76d16b09223c4b457626a6704dd6a55fde94ff0b8d8805f219b602403b1a7e5a9135c50d1218007399861da764fd04aa4fd1b728e6152810c7bcc2bebefc0744a0a27b9d221bd208e998bd6561eb8fa4458eac720f6ec7a24d9ba54449d62a2a1668749afa4a1db091cee2a7ad8feb5ff94248246713e37e1ba3fc98d5e0468b2d0921ece02142b5d66297a54120a82c7c9f8c2dc5683ca3b5e0eb0a583b73757a3f4309c7faa565b520d3c48f91ae37ab87be72971bc59cab6f134940a3807d909b97dff5d4e1075125e1c136aa055411314b72f7f412285708f2b033d8822ee0752d209c0a4ff733ffde4f789884ec59ec2272e9ed275e9d6dc97faebedd3802ddb708abd04d3db8823894bcad55a1d7b3272cbf4e37e04fd5dad81d9b9da6d6c96a1d2f9a77fa052a18245787a72bdf244e725f4b10f705ccfd8aaaf0f77479db887640761accd12e5f1322e23928fc5b5a72e0a73a2ade5319516e38037dc4576857bfde6ba59e7d0ef14cf0117529e06812c7caf9ced17063969b55a2fc1965879fe53a80e34989af91cfcf5d4cdb454e72307af5ecb2960ddc9c74d125dd41142518b35b3bcd9f4c54e0ff9d9f36627102f316705b36c0005c6a5c12936037f3951e49e5514e708746c7fa721455f06b7239b2e332d1260a9071ca20d797e646258fce7c12c8f702297d8c086741ad54522963e7445b957109ef278cd71cbcb588c38b81ae71098dffaff6e6c81445790673f86fd6950f700ba30e4150276477ba5ce58096a8f2ac55194b3d5782fcaf723795dcf0fce82d08bfa51c2110b50f48258798c534c86ab1fd3dc10a999e3172caf87960267f1eef2853dc05668f4ee84288e163e1f7418aef38017649c63103667d31741549840d440b95a1277c9d8fbb8d4607f18d452f667cdf234b3389722761df757ea0d4da4e630f32a68f15e3db4558aeefaf1569e3c3490abc346151d9eb220c05054ae916865ab22240fa1ce04eafa2b5ed09cb05aa3eafd7fb50722b3ccf4ef615bfa646a9057aa30bb72edb17c7136dbadf7fa3fd85e573b37072a33a375c7089ac4d2519fbcc4d7304dc2655ab98866045a8a4e5b5210199104df98962f381c0acbb4619c8adca36ab55a7706e7911822f7a33d6f3954cd29a3d867e1c7a750c8f46629a6487ffe38b2f2fb99cbb94877b516f159e28d714597264c4a12223a39d254827f349120a26a60de9393e7b8d6b66809d35319d972c7290e7f04e10031c3a19578395185c5a1bc383b08528905e85b8f7a93cad564d69f57e97d2af4405bee9a45d09ad1f359f0a2a91d898d0e7cd6098b9b0942b8c72fe88f330d4beb0413024c1e8aedac27490afef24fbe9054db1d8355a90328272f4e8a20023e84b7619619fb58c99694ce8225c885cf638af5299725109288d7287e4faab0cd1d36b92b84be142aba586e346b6df18336873592ad6be27382c722ff663f14d0238c1da3c5bc82633d5e4d310ffc5a717d966c53656b749d5cf33f741003262704eac21e059ae11417e409d21888f832fe7059bd512d547638b72581c92b3372321b5e82b1b7785c31a1b5b65c6af265448a913a7f5b471ca350a72526d902e2a0822b7c11180bfc2b9379e5b94bd4f39ee83cbcc75da0c165572bd5d17bde42554965ed6a1ffc43b9b4f9a9d69d5b9528f817ff9e45e995bb2344bbec6b33a718295b1a38812fd2878523f124cda253710ee70e5c57b60e98268a01198289005a85319485937725b245ce605ad0a86a355666d24a25c8af6fc18ff97a6baf63150a4cec7d8742fe9653ab900565be1be80c6e896d7112f59ba720f02373ddfe1b1086f96369270deacd499433c30ab462d5c082ba9a983ab7417084d675771e62130b62334c63bb4db31129e8ac85a46d22e32ff60e09871c726d83a92f59cbbb4f94031cd6f1df6561ee5e8503d5ef0f360bc9a69d93e62a51185fdc14247a2e3ba6ff96e2291c5a92e6c34f02be16e725d1b83dd768590b140a1a72e90511f98c19aadf36d310bcbc493ad19d9765d568fdbb1269b96b4e272b010e4d1dfce254cc230ffadf8d1f295e406368e0fea7119b0f120d85ff4c47258671cd4534ed9d32fcaffc55b54f2ab5293072949bdc3f7913663abaaa58a51ef6b5b6da324a9f1eb3e618fe355a696b493ee6a9b156de4ab5eb7e8a8df7e72796e0f3705fd075f943e9e4e77aa0ae32526a1cb1dba55e8452428a25b987972ca46a60f4bc858c03299aad82fc2a60cd092e27c87e09164db87d0a097a28667e107519a3fa44c50d705af19b3a0b6344886c750e2714c555045d06e21d8fe455b28c385d4f82f7bd216ef0e3f97ee0ecd3a12dd1bf8069e2eba4aac32a566727693e0976283f67a117299f3c3b0b52c4d48bf6f8ac3580e277c2aa5071f9b72a85e3344b01eedc35a85abfdf4bcccd2b6a1dea3603a7f98c9ae823c5d351c72d46ca1513e65adb97fd72b681a22744a64a60fa76aa1871a96072da05aee8869e21ae419c6b51752a8d6fb45dd3a5d33d41e1da60196a6c500ce84687bdcfe720f0fc253dabb8286d72eb45719dd95245abefa82cbdd720f10e174328df2034b131eae8ec669c94000aebfba723f00a803d126491fc5134c145b2d144e925f7282e1545bf93e2323a1543821a97bdd9ffd31d5006753d9da30988548e973cb25946a33eaf6c261ca4c9aafd9b32e9e3ae8d23927ea046e178e6ea52d63de3b72a9853c3452692caa1e3a97c500a01eb1ed199000469f0a3fea7070b9f8bf44726363c831e59163995fcb1731ab45c8351318e90bd6b730dfca79ffe3f5288572d3eecde5fcb9bcf5037de6ceb34d8c96e8567d2c34887496664b741988ff3e25b3ca110dd97b7ae86690ef2441b20e87c0aac6b17ad05d6ad20a6032d3528c7291155a31b7242a8f05ab761065df0e2c92e61a874d8981f898d271448e3e773a8a2309f29b3f3fcdc7389261f5198ba07a4bb0e7f4d45caa7ccf14bca29f180a80d471a4af25611c7f3f02657c5095c9fcaf4e1b53a1ce1edf497e37078fad4f780bc00ebc1237ccf29b3bd33f6a62a52844c3d862d9eb84acf9b401fb9ae26cd76fa70203621978176f7764d98e0dc676e39c76034e3d09d14bf066c379c67209476b569aabe8569e1a1b8a1f895fb83607489b4c58262efc703a09fcb89b7253da152bfd93cb1dd316d3663d7cb44b64969545531d270a23057cf15cf7e572c1015f12bbbbf2c7e518309d75765a88b0a6c79695f5f6006e1525a401f6f27245a218c80b836ab342c4e5f7715b813f3a55a6c2c27f1c3d26e3177b348acf725e906aa1fafa86c31b8732b959253e6b84b1550e5bb276be3b8f7e971302cb2ffb14fb53ad976d3a3347e8d43d0b62a0a1e942b30e20208bc3f7e142e9a94572c7d560b460aa287899c68ca969492710586b22d42ff1791425fa0a2260da705260235462f6fa1013c2960981d2055b0d11eb6e0012ff20bac9d0b45c9e3ad50b5d6b7459db7acb80c817433e8129b39f679ccbe8c1f875817d415267e64c5072a46977479d3f811bdb09e33b65441b99a2e28d911d13c21837f8846602e17a725a3665b74c91c1edf30bc9ca599b412aa60f1a011bebbd9fcda65b4778550e4cb60683059e3a717d30a868d52de7bee977bf7a7a90a7d27b40204251a1cdc3726c07ee2d92b0459c8f1759c1f3a805cdb317d53013091ee2109283c5bea1df72650d2d23fc643e6ec2a909388b0fe2f4c6ee18f93f6624553ce2aa67f7ef037287b0aaacc9e1422522869831540c69951ff42fa625220126a1a85d217872ba69dc81b122742275099366cc90c11b91584e7a33badc0891c344af5be8a81ea02e88727881247c16307b14045ce212da024a83e27564d046276f24267d5144194c8f596c0d9d58936a2469abb15af04f57f5c2397a2774bba972e0387a628df972a6b161eafda22a8175b5c05584a170e5f685301099e41925a3ddc269bb58197223d87f119bac62634d91d1d10c426c59ed460b0928aa03684400c1db7fcafe72896906fd71c691c468b4f903efdddaa21e0b354a5bcdc47f4ffd060dac4b46723c6363b56c58037badaa07548c6352ad26390b3773286f8233310617fa784e2398dfce469fd485cef3f323b0fb4a38a82f89db12a16499093fc697df02f9077203957bb21a6ccdb9bfcbd65fd4fc91725a57c3f12071d94872e1d17a86916272ed418c6b56d38fff0afafb5712407904f2d829913d96c1c9e5060eff125f7b656523e1b4813a058b8d64c721ce5e3ccd837232b8f06886a9003066525a9d8272dc62a88fd11b65d34d89a062304f3d8454dd309dfd678292892fe8d2742f3f72a7c135f3c3f5f025e82817441dd9f23ec265fba923a210b3a0ac05c14b14fe725bea2d8c0317672e14539158475410fbb806ad127d4b7a09ef47a310094e04720aa252919348e0fc8a0dc81d9ef06be84cbaf028d1eab0fb22f89426c8e64d63066248efb4ccea0851d25fedadb61ad4ad5fa94ed058c4e1c4736ed20aa42572727feaeef90f90c98b9bfea1dad735e9e0680d16b189284aa930aa82dbb0a00a9d45a363f7fe449ae793ec70515fa9df15ed96d84abe2fbd9b793b498754ba7274ee889e79f3cd6da50c35f9fb0fcc282228533c5ea6f82c19cc293d2dc33c22495397e77386e7d63c39d1028bb9af7de22e3067606ae3e707fed99a764a8e7203fd8db28330415b8cb981705117d065b9e04dbb91b86f4d8f5d83799e50ef721b11cab05a79d019622eb7c5cede881568f0051680c77dbfeb8053fb8b3c1072a755cb0c1a7328681a37c14d811e415fb3efb9c8f08cedb246b0f4e8072ca12656ff1e3343fc52df760e8a523f3b3b2d8ea6f8f180a0f890ce65e12bea38e972f68b470948f9011bf46338e66b67f0882d60dfa89b265e1d7c01123663e8c9483edccac32a3c654a29b820854cd2597e3fa910cd80e2ac010ef86e5bb97e92720eb5a2268e79425abddc48338f76d2097397bf92654679ba4b91460aab00f772cebc45dba355e5ea6a61d1d5687e75762d1a9a8d4900f6f71fb9e70bbcc707722448f46a703c969f0315d96a3dcfdd9c269e930b3fe874eadf55250934eb217231ae60c3920ee0ffc68fb9d5f336d93017fa9735bc1bd279999648f6e90f5d72d9fd5d8951c725980b57e691ae10b72b2e547197b9c045d89f0474f19724ea72c0edf805806e371a7c02c6ccdc322b89d6fc828a537e35470dc1043124bd4b05f0c8de1f36568dd13d0e496a5f89220e439c67c35bd2a7b2b4f7f2cc315636726faf25b98653fc0d17a8d010e4dd132e4172402d7e34c6eb9788675a108eba72b3e7f28d15904acff7f192568de95556869423d7a4d9bbe9d4b30fe613952568474330b4d4fa7ffad5a6f3398b01a04c05658274cf2c9821e23e91395005f0363177ca6b10d7bf3114d1a4754abae0ef4b94582af37b5503f9e5b3c82295dd720c74dd3f782b98738c23855f70037413f46dc7bc7e477d0c03db252b875e65720194ce246f713972beaeef0c90c4803aaf40a738a6750ba2c4f2fe9479d2b9729ff9e7f62a8742754ad251b8ded10db5227cec7045eb25704de6f4493aded272e812a2f6ea2447acb5a1835d8145e688d43f70180d51c5557f425c9e8653873c0b36b7025d54e6856bd3cd34f0ab2e8e5482f16af8c7cb411b16beb3cbc61e3917b636c3a11f660bedfb41103d573e4b508c5a0f7f51d4ca3cfe9b844c5b9872af70e5d4381fa1a0cdfb8719b1d138e8d52981df000e8b0c27e211e5b8846272980a1c3dd5699d470510bf594ac1a086e099a1cacd8920a14c1ac11462d9aa3a9735cdc409a9562fdb42b312d8531a9cc73f0bee5ccdf254b804372a79b87472ce36f697357e0cd045fe9be059d4aa579b29eaf508df78fdbf6259c81f83fc72926a4f74ba0a1c59beca0c200cefec4cb9fffbf4c03b97eee7b6cb2519a4251dfc5064d982da5d66549eff941fb9593feaa3563e8eda72894c5b4a39d20ac47200857a679b9b5795ca9649443ea51ace2650021918b30628dfeaa3c6fc3acc728d32439d0121a2407379bbf630c25d50e3d053885a7f25a0e8353b005b2d92728dcfbb7f980f097bce86ee08faabdf9ed4da19e1a7194a5ca786d6ac663baf612703e19fc02b26deb28791179990a211d68feaea5cc65b1ef8d06a221cabc254346f7f98d7644b0be81f702a19d14aa0cad6d467d7f7a97a261edaa7ce822a20d91b92a58ba11c6cfac4d55762843fed7a9a0746e6200b0417d2e351132d682fc989e87844dd4b790a580900c32806a63502b5600763718ed6eef288741e537211fb976ec601deaa9a38db132d369a055f5b28fc274883ee792a4637a49c2110cd39156e15af0a59ff909d6acba352bca12d48218e4fb5bed4a2738bf887a872dbb6fa51d0b1d55d8a7af0a69177f6130505616ba13d64a6cca65a88ae54502a8f7de5f84a7d526ef138dcda6d15eca4e44d15d56b07f80f1014a7459c686158345dfa177ca473fb01858947e475ac5b3b6a9290c5bb1cdc1ac8de586f37a869c67ebe41c15db18d02c049e2d70e3d3e1123f85264a03ce50b9afb6492590455b9fc82639ca425b649cc7584d029e211c70e26ac68865b8a16afe3fa7a337513e9e0082d68eb6696280adcc43e7325c4633bb10206f6fc4556c1919d2d04d072f70d69b6cabaa2da25ebea440c7b7899df0d94499052a9037215ced6091a13725a2c78a4b4f238dbc2d41994dad7663d3fa8bf2710c27b9d917ecc437361f972db4c23c91205ec4189fd43e33dbecbc2d02c456aaafc7e89f193182bf3ef4472780f10917cd87d03e25938498a4106ee3f0ab91bd1acb4627969d081793eae724cb00c36668e599ba703aa0fbd4385ee06b244a51e05d9a40851002681ea3a72a5fd7db90880a11283cd48d56597b43bd70e3c62c1a096050a611601c21e5d72cc84a98f7c2a5dbcc0fb5a41b882a0dceb5421c1bb34f6585a8b813b75576d1cb6646635a930b3441615fde42bee8aed543acf90d12870c67da8b596bee7fa44ab1b8daf17aa9dc0269019586f3aae7db8bdd010585f878208a1d427b31cb372abfcec7878f703960cd5a830352cc1de6f2226be2a29f3bf26bdcf4cf2480d01c2360f5eac61a838e4226c1fded3f971fe49570c8ac04700bc7bca1797837872158fb1e1017b3c2a83529ab745dee839449c6f57acd004b388dc12a9f4d83e23f9a80637771fd294235b5dd9ba2132350038facfeb83eae19c46a0ff9f3a2672435ecc272eb95763ab09fe8cdad5786b0c59ad5d118e8df182dabf15e9ce36729e82fc8eaed057158a1f4ee4acb4019c29090c2879fc722932d82de821e3aa06ed2db3322b02c493c137e627e6f6af027bfc51af8dca1729b6f1d328b2c0f87289d97f9601a491671fe4f7368fb788e81e57b00a92027b8db29b1a39d76b2d09fe18de56634a8bef507ed26fefcefd956afa7c3319d16d0f7937614f71a558729ffbc745f92fa9bd929411320523284b39b80ce9c8aca17b9da9a27a73785c7203dbd885533c422991ee5038fcf2ee5ec463c7efcde61bd760dccd8932f01672a9fbbd2d356c1ecbc15c83673601d699a326f2591f4cd065df548a6cbbee8372f12794de31112c2cb8a3fe110c1711efe7fb4785da4af5d30ea4acf131c4a27205262376a4f8a7aa154a76d65265c75b0accda9a55f0fd6843c5dc73c0120472261af7805a7f91e481855df470aad2a0ce69a97ca5d2f391e3d29c2ac787567252a08304c09475df8afb5d1762d5035dc388e0f6f4d5c2f59f9acb860d2162728b028c8f7d8903be4f4a29e0ddd0139bc79d936c35f93a657297273a45baef1f581cf26d394aa226e958f16f1c6756c84f6cc28dd59418f62619853a9487b44f17b92ec77b254ca5494d5974c92958a08d36b05f36e090c711fb1243dfc1eb72cbab7aad2e85989294981edec23b6f15723c06ff04a84960c7a0007b0491e23d80c9f330e142ceea3f90d749ad839602e03bfa519928c93f1994453260f29172cd75303e81c3e939925128abc31ba2d5fe335bfd3ac1712ea07c06930731c1350c89e9f99d8840af1a19c2cd5bb151da8ae99e01250b49dca9bc3b8e34d00838b1ed139b76d997d4efb13be041fc31b9dd2302941bba6496378e30464d4e9272a2a4ad67bbaa3a13b521405dcd4bc27c867c462bb1befa416c592e5ca68a3e72204224f0e28addd79af1ae5ea35ff9bf29dc4a33659e480d8b2da43b7c3056552b7f4d7f1a966cfadaad6f2c463fdcd5d58b059b447a907127e57673237b0272e9506c557778061e0e4ba6574e690efa3ed97031473d44854c7eec10c996d350961840f6c9714bfc76f30280eac50bc02c954a38c0a775e8a6a2f685f9395b72359c39e439ceae8af6d578e6e33a6b5d18756b9512a93aa0fc14362f8f60ff004dbd091d1379e78926937333661105086f7b070f4809fa34da2c538146540006fba0333bcd9ba893de7866e89b0892ad0535eee2e74f8a5bc3e6d4a6ef919744a1e89c89f9266d90282e0e8d5ce7e8071befd32b9b7dafdf16d969114522ce72e31e3c95820407af7e949d16db536b6add2148af9e85822544db69c10181df72fd718a3bd52add7c2e822d7c3c3fdd7fa34b42ca51dafd0b40867f062cdb6972ae47e025c75d1e0c4f9876ced24624fc5a5b4c724cb0e3a451c0d58042ea1c7298cea5e72f79d1709b0e10aae2bdbc3c3f1dc9168288756dfb86c5aacc614738f35a4520c8d91ed5a1503e573080274bdeaf69d13f8eef12a302e376687cee11ce4c87ed4cae0e61187d852fd24392ac0018665c6bfa46e6d53d12acefc62f72d6a494e591c60985deafa19c229f5e4bcc0181f52db35e071f47c6a1ef87246de97a3e9394f20d5570f2436369ed5b0070e4a7473ea205ad4ebe1ff8fbfcf91772f9482bef444bfc1e20df547df211132f1e27f02fb83f52a30371b3cab97d721b7fb640594481319c0ddc69b1c16cbca5945bd0c612d1d738e337bfcb98313717b10d5073638a4f50531c7ae8a7b97a06552eb4bb24134f25b84c765ebb5772a7593acbe4871700de68632c419c7147bb10209b378fa5a5353273946e648d728c24052d2fab71119c88affd8ed9ec4f7eb150efc4eebaa101c1ae32e6317d72c4456cca78df61c336469f8aab1c19c1b27ce34d87499f55f7f10a1d51f74b1f378930698df95b48d33ab4195a42672673aa7b2dd526ae9056e59efafbc15b72a890d6cdfb616cb688e6881a27303b2bde8c69f85d8cfd32de370fc070f91c40db77f4d7a6cada93df310299d8c120c3852f3e62199ae9af79c41fa41eda6f72438ec68ec23e22dfb0cbcfcab835eafce6ad250d2762615d6cc8552b21d0997263273f655f19127818bfc2274c1616c33efa774723b7b79045b2b0e279a3dc723260f31c1533928333ff207ee544bc93a9f460c0f0335f7d5771a84f61a6b472504f9cfe4dcbb38c9aeb5f6a30762d106d605e15f98dfbb8aa92f86076b79a724dfa0c3c649ca7c1360f5c41b7ed98f715b7e6d6e927d98a7c96c35aeb150b7231e38091113e8707444611129fbcfd997b39ddbb1f14a33a6201b0bc395ed1721280e25d5043334319c3a4fb729f94e467b05c45f3e22e681a1dd00223c65b722aee2a8ac4d52286633711cbe4f8eb29b34825977dcd485412de1848bd6c1d72eb5167d18fddda0260d35b393b72ae5edc6108a3678286060b0a710f5a860d5cb7295a45fa89f61ab336e4560c8f938030e53ccd2eb2351effe5bc5ab1155c33fb6c134c202947e6a05c539efcc381e888a9e6fe4a71f4cfb2e3a3058709ee72fd4cf181cb7dbcb73ef694fd0f89cf7e5c733b73e3eef28b7e447bec13934872549fa55da12db0ac29286548b50a3cc21d47e77583e6e85cfd1d3d5605b37e7282e886722064105b4c6beb635706880e09b1bb2c1362d5a540f0e229784ef77280989f1a969da4f3da1cfbcfda16b8f39066b1de21106b947bc9320bc31c7d725cc2ddb9f4d25ec6457763bd606cd18ae70db5333eef91da41d179ee37de7272f83db049a62b9b84e827998b9c0762c6f9c833ff046663357eea059dbbf2c7722bdf004e1abf6b981688f9e4ef042713c421525292a97f27861076f553be7572a28b84f1f5fc1a06e6aef5f477bb372799413a38be0e1d20e4ebeec3511ed87208013a8408e0fba77a8d7d27dfbbefd6c79e52d212da34110578e2fc3718f97237a1331fbf4c82846923c428eefbf68a955d0ce5461cd98145bb2c1492b1bc72b6c24c706b5b9edf2f3e8a88911e82779bac649828f33d4523995c207e851d7210a9d07d879d706cb39004523a86c664cb285aaf6889ba6a413e7d9a3c8696729b9e16ae30878d0fc054345b985eb71e44647c97c752d28dc0547c4cf1d36c725f25e40333a4069f17747f91f41522d1dfbd3fbae7ffff3bb9916d938d18381619da9ff462d03849fa66ff14c10d13dc15c801dc5dae5651f5f791d967bc6437d0f79faff41540f6bf8bce4b113d61d250d7fbba783c008c6055e34d6dd9b41c0b1caccbcd466355d7d6bea3c13818e6c06067dc40e819e4da52792d04bb10728118038a6e82fc2e906ee5766bafeaa2b151f4bd9524ba7a6564356f76ca0f51f35af038a36c29fa86853cd985f3f65c8d206859e1fd3cdf3f9ed040972ba3629b4d22f44c8e35dbe4be95081fbc446bf818de4f4f03e2503bd418ce366957729b40197be621a268e5b87da78f7243001c4c9c7789aea57da605a5e8114d0a72839c8b977ce2d0f474bd04f750c139aaadd6c372d57c66a507f1d2afdd741572ea6a23ba77ef740a8d694962a895447402383509bcf0d5fad117df40179b4c57a01b9195d15e0a068e27e1fe2291b0fa2ffa9c4588bec4c6e191c23176bfc372e686f0e1a595a1c2638642873d955572807c1f3375a6640f3f677604cfaba872bcf503d2b056f9c7658eb2590086b07802c83e4890ee24565d95a40f5ca38e727a996f1653ef1a14fdd5025e2d2ebcfb9cd0a7cb01d65e8303c01e3c740fad72cee5f551ad8eab4a5f70c5e262948669aa61dd66d70f6de81425411a64ffa0438d97ac2dbb6634dbcacbd6f0716a9e0425a8d51bea3e333cd3f1a6dc0ff5f072496e9fcb5e95e2084034f0702dfe8706f81f5b6ce820861eabd7ebf1320d93183f26fba8c4cd94d2ecb06e04e0617c06abb004bb27e53ba9639acc1514332600c6383b931704a8517e030af6539ec1fbd9395c48053fdd91b28e93ca04b770117d6636f8ee69aef7cee57e447d1aab69ada5607c30dfd3d00bf5a19ca9286272d8cdc64576aaea72af882f3008bf4f1dd782e6d3025ea7459b5a96449ef90d466a09113d0c547cbcf9ee92e332f09821b9e8ed2f8e8a0b54f4245f1a6338dc37aa253695ab4521ec8153e6cdd17215456beec4dd64cf3ce767f03c1442df95626be28b092f046743af4369feb8ddb194912fcb488d32a372de27cfaf6f8316115f3b5df812bac0a7a1cb343b81354c71b42835de379718e6e7769720841bf97208ebc824d0de76eac22fb746f24c07f3ddc0534230f3207b9d4b659de92014233f5ce54cda1fd8b2ec7fbad3c40c86c8495c3175f5a311574cb2ebc629ce9119b8bf5f13d79a6438a418e24b9a4d648dab370c79bba33ade431696780e70d572c6853152176f87a20e0be727a5b5f8fef6007f5dbbcf98c47f8b4c05b173c77232276f8391feb2e11d228a7097eb77662177e8a13ca0265ebad65397d0a18d47c4df0b34aa8441233ce58370d30da585170fc0eb3b3ca64b0a8a1f03d343e57264c4b953d3348c51e3a2fbbba04f2fe3d1be0e9ef91055cc51570a403ea75c0a62068dae4af22b0444fb2c196535cc55687a61c187606bb5cf65c020c1689a72c59b2bbfb36b1e78fd53c4a4cde0c66ab0fac56cddf4a1f950010cdd34e3b45e04c2517eb4570d995e4d8f99494c50d42c68641055d8ab6cc8ec892c979cd372f25ef0f8242bbeed50c56039c04f49504d654aa0ff13c405dd2c0cc04c224972bece42d9029b60f0dec88238d34e1aa6a881359db527426696df3407e19cca722d4914efec49aae63355c0a2e84bf04e80aa7e80c7ede0e42fb091623b09cd00e9d6088771b4defde8b90271753ae461661e91d25662e340e9bdb2aa5ef91a72a50b573bc8bff7fab2bb88946dd141ff9676d45940ece901c051c8538c4c9772690c33773eb770f4644a3992a72e34e6b537aeeb68f3e08595c68e12793328018cefc0f0d8ea040f440dabfff39c9c1e975d36ec940103ec0b6ac5733186fb72f1aaf5f1ad70c03eaf24ece8cabab51def8e2bee0efbe0a331e2bc65bb871c428acc2bd07ffd8b26a6e59aa3e0641f037f4413af971556fe5a13c19bb58e82728f7ad633e541d198d6c3076ad1a17bb5419e9c82c2848391d91f42f836e42d72cdb2d8f20019f2afa526a255993b39057853a5e76a3edae069aef20b574ce24147f6002c88457a72b269576c2e0363e0c34b62d210ab98b0dc87c438e9af08288a79c835ce7134bd8534148cb6915b356853d33553254930201a284a697d7872985310a81d0b45c41bfb5b88d0a12821506215bba23f3a7c61f9f3c10e9e601ecab41487f743272e1d93fcba7ee839d93804237db2ea47fa3f3dee2beeb80f72095e2c5cb9103453c4b09e503c52801c920fd5fd607178375159004ab4e87272f0aaa00e104c29685372686cd4164587f7cf80187f709cbfea8bfe863e3c5972802eba5adf13a993222516cdb88edd0550b4c67f8e4d07489381d7307bdf357280f7664633a7fe86a373c5f0d05b9e96f228b78d6f5489b4e0d222c514920172c6b13d57ab0badecb74bac67130484aa3d1e8df3bf29c9adbe5f1c32470a4431bda7623a02ed4b0bd0a6c6f19619f5b62d63fbfb336ebf73c138fbb18f8887726384fec1efdec7c1a016a83f5b341338bc69d6a70367580774a0960b946a250bb9eaa5174171d130d8dd52a7e198f3f785b19d93f9987afdc4589d08bd64195bde7cf09b93061a5df3f0509c01ffc217a98d35feca4348fd40aad2eb51b57272b7991f3219a460105093c095cae4c035ac7bb597aff6f8db0cf40a129be93f72066aa9b52b97865d6dfd69bebf3869e2d1e8e3ea27d56d17f43c82adce7c6241fd9870437b2a0a327b83dbb94d0b23fc005857aadfdf5af418d48903da7b47025e88e809687ec90044bcebcbd77096c056e7f34e94fdaa83f1f2b8964e26f14d288e712bea427fda1f2277ef002f57515d0b4bf6aa5225e28ad7459a2721aa6fd4b4fb870e6e085cdba99226e0625ac9833572f234048e6db3745f7b4d6e2d122b99bdd04c8143502de8026b918240b55fd0675f81c2aa49298dabf40495af340d0f685f77f763d2dc243ce8e38da4733dcb244575eb87159f5e87445e858a722cc8dec1a064e16350bf22a8669883557fcc85ad26c132b8676212b296b1b43968b1890ce39b308b88950848e7d01bd488fa8f439fc3a60b2a3f6106a517c666631800e7363668cf1ca5dc9353207bf5631817393407b69161c4289b95cfb472661105340e0e71117466a99b724ca2682ccb3e3086e8db77db998d8171ca35724eecc8e43d22dbaddb9278f52f6b719edc5052732137858794a98171d9f53272b8b215f2dbb82d010bd634a1f8a3e615b9effa296c0c1da69805ff954b30bf7240fb51bd898ec74bb5caa2184ff92eae484decd67f56fa3bb5f8e0375647cc48890f43eb4ae1c0bcc79ec4e41373bdb598eec87e662f924bc205a48ea1bdc843157344a9cf6a4563f1e695c6d5c3d62cc864ad0d5f0bc892f96ef1f7a05fed15976bfb8d57806b8b01307ed0b60c480ff93e58399bf1d3ce4fb7855cfd18936e8a4070f310474e824f127ef6adc4a44aaabe8741d9414d7547cb4438b16f50724372c450cc4f4b6147923530b1fe7143f3c6affb9a3b98a52a12c5cfbc9d931dd1316f2f6a3202e404b20b805896cc46c28589c221d86198d43b3e91214c4c515534691d2820e8d48c14f4baf13ccb54e3f524622a5917639e87eefc8bec1172affd80bdf10d1123a19c56216995463b2ed2af38b1e9f05b9a99c499afeca61d15e491297e0762165f4e6e723a9d420942c552664811ae378defd440ef35fe29cd3bfd55403bf00125e25a556a0a3eabda4e0b22998e6f5573a487903f93ec72e583057923d178f6aa4ab3650ea7af76b3389bcc9d4a1b0ed187c775630b2372a6f65a1c8d21732f2b2768b303ca345875372e930222dd3bbd627b7e4cd9a95c74a71e6f23eeca8a7e000da2a2adfb1165266dfbcd0027e9c8a3983c4bb0f4184035b0bb43347f4bfafedd21466fda9b1d366fc9b5bafce93c5632565a4d2972a84b9556b57dd70ef3060e359a671adda2fc22eb4b1869f37527b91742f11572433731d3e68cac8fc138536666e83ce4a5f89288df7d531e2a5957d3106bac5f63cbe81a80aed2198a995ab9e692d0a08b011aebacd8cc38a9f196f9ec8558600c2bc5ce73b86483f89884a8837fca15ae44980c296af3ba9f4c7be679307f72214aa45688c094c42afeb0c1dacaac84ce50d18dc7a73f649f5f90a6bc5f1335210319d2604abc05720b9a102f9d4ec7719f138a27c615d2ad2fbef94b5afd047ae0db6c14025b17c5376336ce7805a6b5f71cf97eb4401d7e36337d081fc572a88e5a989bde0d1fcb86bda8c93c39d5545436b3d0dd2de0f08ce0b965df7d72f644974c0305f8916e1275488f590253c6ca7fcc16cac16a2cc552055cef575c86b0768c3318cf1527c692eeab7418cc1e4a336ddae7c957843f6814c40ae21207cbcba565211307725e5399206c51c2e2f7153f6a6542296d262f0a5ecf3b72e32880714df4c8e19ad2005b71b5ed83340b7dd332051c337ce1cfd5f3d2c615b4ceb85b1f6f776fa5265dff57cfbf563ff745482f03ca9789cc189c540568729b877376ed93f8245b5b0b7701d24b4bfd86b48581e6a09dbba11c1ed94f6372ddd5f7532cb1a1caaaec98a8888000cda60dbf3cc70ce4159ec03c0be1f52f726ca02adc60b6f9125d5a29e5b94fdbfd9177cd51cbb52c1a89883ce093fffd72eab5e03aaf169a066056554293164192a0e3f393817453862e49a987358b7372b16d275858712db1325630208d139843b7e55dece0b6e62a58554ca6488b5f2c5e721f7ee996107bc2c8ac854bc04d9543da64c86d26d1b2dd57c645a56f5041b19426f51491946c76a81b240ccdd11516d773159a9171d7eedbc0bcf8a7f972007c02c5747c170300465f0fbe7a7fdef4e98c71278855a69cdfa9851a88d264eb568115823b31e9eb4a835010d8daa330c0be23acb121a6e9a51cb61951387279d66394320a9e40b9079059eb21b10821d12d451592cef0342b580ed0100548d0ce9a126e1a3f025a0107dfe3994f4f57d7217b63dd3edf05188ef395437b72edb13f23578ea2a3a6f1c7cf323f99d299a68a2677e622363d28e80a167a1826178012c60eb7a33e1afee62a6f12114f0cb2fe18e5fadc91eff4d8a140921525256cb7707fd9b89ddf2c811047b469a010609d7bdcc4b9fe45c27aa2e416db72e199a6f016f40a9405ef4b45d8d02ea288a14438e80f0fdce3bff739cce7a272fa2e0dcfa5d7fed5cc7c32ddd4199cf4240c3e6ef5eb4c986204b49cfb0ac04a08d056fc5ea621285dfdd2478d098eb346ee39263e60a547d215ce24932c896f215c54b6497e04e9439f537c2c30bd0be24b1f0a66a2414cba15f0d30a481f72755869fcf206bd044f2a3cc0d3eb2a8617dfdc59f97df14c9c6ac70849783172fb3832799a6e796a44b8e281594cb7b223fb596175917d6399f985aca78fe10c5907e03c363c1ef2d76b373a5a37785bde8d994754bbc4100da82656ba6c8872948014b37dfd81352c436dcb438cbfd81649ea059b7d3fd0e487d9c72edfa672ff3d8449022b6073635d1f013f60473603c8e049b4e3f1f2c12486211cfe343f9473643f84de21ce0325d794e39b8174da17f1428f576557ed32593f4216da11ff04d670b1249aeec694b4b24fc044c27deb19475f055c3f9bbafb7902ecc57295eed22805e7dc5d5e724084dfccc2e248843b6c05df80be575ebbbfe4a6ee722259ff89f9176bb2b78868d58281b01a275e7181e270edd7d95370d5340835727556fc228101c6b9b0464589d6dbbe894e3106e2f250e7d302448a4c167f9e7284b8f72688a1a1770e0e340bea078da5b12ad7240376d44c4f58efcbad879372d623ef2dc724674c6dcc4bcf2d3acd1147554a3458366bafc6be5717a9574372383538a0c5c6a3745878da3c93c9f01cb524e08bbe49435cb7e4bc0591bcab72def1374406459907fa67a27c67276deb7b00ec027c45765ca0d89b9edf05b71d0d2444ec444e526de63d949be3838c826c7b6282a24b747afa86328cbc631104362b36ddc4a24653d4727b7c50145a521ccd6b57d5e2e8f8eefd584f3737786c0a43d67722e74775334aa1034b962332d7c68515285e40a9cfda8f52b35a79373bb53c29a58d7735d5f9f0a96ac772d5eef1299204def2ced73ffbb2117c0772536bd81c044f03c7cbe044c8aeae341b5187ec4c2866c3f8222e7969b951361f58bb9bdbb2baa2c0bd566f53213968037608ef666884eb8cd8e87ed4ee9ca2724990e7a865511c7ecb8c4910af857a602d01eb2466caf7a5b2621d0660ff796c0b92d2e9db8ed0ca3e334a9961b1f75db507b1dc9bfb1d9f4472a7b518150732605b3591bbf31e45ae4adde73193d2ff429ea29240806cc6a4d785e6d58f1072909a5bd80635c42f17574956f60f168d31f573a93448fecfa459f5746563b3727619970d6a171d0e78f55912b1b6ddbed7acd514c70e7c6faf0bb912455fdb727f6f01712c1752c8861e72b026fa9c25c74f6d4b494dc3c578cb114973f00f721f435787f1330a7b78196ce9fa19a7fee69073ab2b337918c3c73aba57b7096a5b2ca10eeecbb504633f7aadf83469fce006699714c3c7dbdfdd4446f049d15eb839035e33ba8edef9a3bd56ab38340017a8b209fb32c872f39b060f06cdf45b58b8243d915664752434110df1251fd80c24b21c2916ac6775596c30455d75288c215916833423ddc2bbf4b304aa4b08a15b53b35ef17c67a5029a54f4d3a035e95a9c10a1f46ef9b2049d4b8361bf45990bfd5f00ebf5eef57a56fbbbc3af724340be6408119effa26d83ddf5f04cb2b053f5311f93c4b6fd2b8b5361ebbd72fe596e31eaf269c6415418010020e5657b7cf708aefd45e3f95a232b78c1ec72f8985059b7398dfb6ee506f0eb1e58f1ed825fb2656a95613e5d9bd0cba1555a8aefb11fd3674fdb8489a57a9f8bb1e765376d30a034e8378e6fdb0783302c5857f7f586f6ddd5addd7911976c4bca25bb9c00528813cdab75c8b9ae81cbd47275be35db446400ef2f55319f0c6d4a5d4d79bd6c1903606d0e1b233c077f2872739f256a2f6ff828d8194103004fef6055ea8f871d4a4d803065ee012242b072f69ed20a6714051b1727959e156d1fa3eeed37b5c4335c587f171caacdad2072ace9552980bf8b5c42b72f02b283db49387858fe5d6466e7ececd727ec3cda72c36d55911361f13c5c751058239c38b787c0ba2d16bf1286ddd5f0a955b10972200c0d853c3b727cda7ce3561b9cc5ed33257f809ab29e3b50453b5e31ea6372473837b3bdf3e3f78c0bb93a7ffd6eda62e2f1da1916a819ebfe84a5c56ba9722ea35a6cbbb2e4aa688b7be73018584cb2bff9f7ff0a430d05b4abe13a010f720c96d7603ae6db55b577cad7fd4d4762ae53164459ad8e5087adc55244e4bc72508225ca61a811743252f52177141022dcebd9ecfac91ecc8377539862133c72c682428cbc4348fc60702ceb76b73bc8237f1eba8e30e3d3a34460d34af45b539596a17eadcf36e7c67bebec2b64150756acc67198d3433d7654cfa0be822272f20d7db3de4bba4649c378e69c94d1d907e200abaee3c28435ed4509c2ee217269f3aaef3c96beedfab9dd9c78966336bafebe2acc247dc60b4352223d2f101bf71ae8485833c39757ba117ca456cff8f178fde2720ad9284c63a783aa363c15f6618b72be973f7b36bbddc70fa41d0afe22621715044309da60446edbd32a4c86d56613a7cac27a4bcc7f8bccce800b62da16c8c4d69708089c3b7950d99c72ae3086770327917d889b0340e1849de5b8bafd130864ebc70370295e67584872bdef7517991ed85065f797cd7e852a25ab2e0ca5a8e999f3c893ec9fbebae345120dd55f2767d10f6f23d96273dfc14ba80cb6afb767b2718345b6001a6f32215467f91142c5d50a4e8d5da9bd0e8ba7eb8468d7968471349bb003e154cf7472fb9dfcf5a733fb5ba5ec38540b8b01d940c7287702f87572888cab204850c17249852322c190ede0f57993ecddc3d16e60bdce083877dbefa872c0b2dc86d8728020ca6a2cbc19d3f0715ebf3ef9df4b3e01844a8c885af4c0e9f186c6817372aeef240301eaf68490a302db45340ca66d7546319892124a5eb02c819d1f4d7210c27ea6c04eefe4161f24b0ff48a8ce1bec3fce3dc89301183a53c8fc0f13729a8732f565d0daf7e4b2572228643cf76973ec17dde25a204417c097ded74e6b1892d1f99adf819affdd58ab2d24077b080166f9b75de6620d3c24f31511a47251ead5c29738fee8e389fe90f486305145b08d10c4444ae4136a6243c8fc0072f61bddaa217acaa1f4b80d2821927e5c28dfffc9ecb0e1abb8c7f9e6137b977219db5624de7874ada77865ea5a1b886837f94fb8b8c6612665cab9fbecc2aa72d9fc86999bcbf9ac2b107a53284a19e4a2497a9bd1ace430cb2ab79fd9232a54dc5c7e236e32fea98ae6dc4266cbb8f1bc2e86e9a4d01ed17b347fd35e513169d6601af3af4a56d30e435e2edf1992e7c0628e439cd082e4c00e72bd141279159f578f9cae052356c36dc3631fe95f510b4962a71d9a123441e9555997e6c7722315f250ee5ab963447db72822879ffda2ccd44c6c17a19d7bf9d18434c775377a6b240df0848f54e4c27a25a6de0581eff6c4076f7c56434bc1103b087eed6e3f4d1f78d953884ae0173eab1504b530a6a56ae5e606eb8e5e78bff58702e41e9fd4b7c92137e202045c2d3d34b2b97c2ae53a80cc831f84d11fcb2c4bb6f8303a8da5b8891b7a233ac52f49cc5a268b65a6d2bd916067c43c8453be40fbeb3b81fb5576f20e327b8d7d8ff4600c16eb5be2869bfc1a325447069280a3e22f72afa200521c27afd5d957ba93c11ef20dcbfabbc3acbd798e98a5c719f0c1c672a92e2bc0dfc566d34f0e3bbf81cd95457daf5d956d5312cde3aa0084972793722e78980a4243426b57477aaf670ca49247774e424b33a4ee2e0942252986e9729e36c4bc40106eec94ef35aec4f75e2084fce0ecc9ca856d783bf57f39df2c72e3e26274d5925d583064dd87268ae9aa84ed9ceabf138452c843c6aaa746367264e918890199121951f80166745233602faecd99bd89b2b6da28356761a529723649df67ebfd43d18bcfd78937cc6cbcb89e93571302969ec941d062f181e42241cc40c15909be33e99f2a43d995af258cb79cd9830215a8cc9d69df7d6fd3260bfa66957f6f961b96879b76cbb37334ae8ce305f2194bad6df82d5499c6a41215475fb9d56fb9da7c4bc24e9b9f158697b67a3d9c9682e53df42a63e30777722da470aa5104d08ed7637c4d1d5adc6af886356eba82e79b88922fa52deef7641b1a21ebc425ab1a9e92ece7d36d87ca42d0027bf3558f40b453adf655bcfe728e45a7902320f4c2601f18cf1de34a5b5956c2028570d272df7af6cf6e91504864190db395ede7ad9e60814e588d715520f21b87b335c4e8b9666f2c3e155d0b609bf172c1c696443943de1efbb2b8552a16d057122799282152279ca618eb4b9d3ff5d4f0a7835c7b44949bcc2d9592d2f00a85352ccbaeb970479939e175723aed6792800824446276eae23fd33bd1f6550b298cf70e684af4cd004873c616e4bbaed7e18f99fb2bf64851158d4a8bf6944a1b81ba8b45f6abae049601e27274aeb06924d80099c48abbd54c44ae4ba1e2654fb0d80713c4e35f522ae2a0541e0240d0ee6f6e3ab80f346821055b6eca1320fc967bdfecbb2c623ff666d62c713454d7a9a8a90294a56c5ad70c3053fd823f6cc7248de609200be4b730d22f91c57e5c808e5dab4fa0feaf065b7b241f8eaa3735f40a9cbd916814cf7061728c65ae7cfb600e24acf458077b04c3582e48820efc2216f9064e2eb94dba2f1b0f37a12f27d7bffdb548aa18740b41fefd8356d3275b478163b029dc09d4ea372b9d436cdd3308968d0224dc91f285e032421d5b5fcc95e97b6c247b818c38038e0e915fa03a651a5795acb8e06923d0498e5d6b5302a049588a696f76692821eab8183b8b93137e2d0a89df15cc01c2dea70beb58d5d5809e1a50d42840671aaef25331ffc3cb39ac4e7c841e319b18e1d7340adef8675bde895a8f77abb841a5b3fbcccfdb27094a62b826a6b30be29ed6f0f995f879b5de8786c6a6aa9a2933f0bb77c4d59d78153fc3b9725dbbd56f8306a6d60b75ea79337f0cc75aec72593f5c9a9162e0d3fac63c55e78337592f1ae7d7ab6871403a7e48bc28e874648a534be4030e04000dbedd0c698044f42f8ce9e1aea094afc1b5ac370d832172bdf7b5ec17a9d1c4efd54705ca7aebdaa8b1b1bd7729b3fd769683c6302ec52f4df71dde019f43a14e18f652d7a45d89ba5dd0b79c8f16691427022f1a9c955fb6ae04b6fa89841f2ed7e8643bb75aa65cca16586b94680d8ec715989c66242f4f4c8c678240a4ee1ddaf76ac865a1f2adc8fda19cd0918e9763d15490c21e72e8d613a671baaf4eeddc6e58548e09b21188855e49eda351b96e483659771c72aefa8bd697cfb7c5f720d0185dbfa8011a54b9f1e863b094975adfa2eba72972f23d116290929c28ee64a9fbd9bbbfc9d0cab1d52f6e3a4ad248b4bc08f738312bd3d2d25178b7511c9b6cd2c2d8efcb0db656086be59cdb06dd222cb78e6d72f509baf0664ee8c1f010a15d4184829138fe50982a17eafa2c9ace8334fc73723841978be01b0cc57dc9f12d3949b75539ced2a8d531808bd249e27542f667722b62fcec4a79291d7fa46e2dd06d55653b32e6b9659b5a586b389dc62530b2728851151efaab874d7e8be2cb9b06a92b4be5741bd5d7a9da7854df9c78fbbb33686165cd8acd45c880176eae8b68beecd9cfde064f2de4abba8c3e3635460d6aadceeba545929d0895d49ad03ca8d2f43ef4b9a35c95c2d07b639b6c254a8672b7bff3ca5fdcc720f48e915e8c682a476730da9d54a8b3c95e0a295a679736728d8eb81f7010d0e3235c2e4c1725fa752ff8d2640d28b1db11f3add41ca54b18af471a1aa03020d473e4d7f8cc69663b613d26fc21178c57b94a6da9b3842e72420577583d3bc5e28001ebb1d2d63b591f4e0e0bf06ed69315a81e9b86c8652f59f3217a6a5f42e8fcb5b4bb3d3f7c9674a7351aa22fd2872ec876d03acee6726e1eabeb8675a08dcfdd30a0661adbb4dcf0870534d79f1849d578b107e0e8685dbc92ff7f79529f0170bb6d931ba4538e400cf1d86af1b3c176f3a0221f57170c94d55d4ae1dac4cd7be196f52d59c0ea8554c61fd643c04a082b52500b083b37ca4bd588a63b1e015ca49ca69325f2a65e373c5db2398e75f133718e3b0072cecc590b1e9831b9e3ab47d1ca388ccca4a104fba8b589c31178f608002dac277058966d682f6237501a41ecdda0bfa3759622cbde44953ac3cb4b421d8130722d50949b5fb2878361c891b35a71f1ace26be8bac22dea34f13ed80dbd198e7246beff0573118c65a661104f5f1b052d3b61a011cff9458f02fcb62b7574e351b1cb8298d49eb0999e0cd59376e398034b5135629e042654c556b6a8bee00772496560555a956c2d5ad154f0ca91fda8987a5ab28f5a3b802372a8778afe32723c5b1895b6b850b414baec991b352b251bc243fad7fce33f2a1f4976f5641d48c66b9b54f0f595bf577e1d669a786dee3d740d2637f21d8cbb3e7dfd97d6ec333f72ed0c9063f2f7103f59f87bd7e53c4528fbea89466f1ff0a6b1269e524e4b5dadbf0dd8562ad8bdf81f469b04ec23e4bfef9404341fade0ba63bca21277722ef9a53ea2196e58b7fb353406f6faedb1b047f955e1eb518589ca9f09ddb172081f73545ab4b375094a2c19c10d70e987753d20ca52cac9dd93a5926ebc504be9928bf59ff7f1dc006d81844765b0e4ce9d43c6b54d90d873d370353afbd47230884d9ca322fd3fedf17cde77fef62b1307f10dac92923207cb0350caff85720284bee889a51b0b11a51e3ef7a2e5f3ea41652e7ac2117dfe50c759fbef51725af4b4e5ab0a8a42edc10127c766c977acacb6bde217cd3f48ab64656835e340647121b61fbae6dff328f7ec0e2eea1712ca73a96b518088e9041b0cd66ad272b846253453513f2beb58ec3b62f81fde553799d1b3ad94e9905d3e34aa70f9720dfc30ca1a4aa2c3c9fb81ef5b0813df40d099c8bc790faec7cfdeef41743a50d4bf760d25d8be64a8ccd584bb569d00f990dfd2be4bd904aa9a659bfb4a160ce89f58a4ce46a383b728896e8b0ea8901f3f055cca00df09b55e87c7f58b5b1ddcf85372ecf35a1009f03cc1299f69f4407b59623dce357dd8bd6d458c9f0f3f719f308f7a595e102d06f25c45498e6bae323ed0a8d052d125963a4ee44149726a0930be62271493e6063439e756c064f5acf41f923efb57c18ee7d64c1fcb14f2e86c7111f1fb88cfe4f514bda1af28f9bae13a6007f5be106770b2b83e4b193366456fa774997519af9f54e6b989a86bc62c8ee0b0529b8b221387a018a360aa11d7763bd97243a1a3c7993f621bb19df6fa509dea947d8af48725beaa2072cb7fd385d4a182d978194555092fd496beb7f9888507598d589e416175f56b6097eed55f04adc6dd11636ee221c027c810dec094759519d50ea210ce24fdf71f43427531933740f9c9235a18abf9fc66cc4f36c99fd25268cd3f9329058cf1723a76542b3a65497f52fc73746b33e2baa8d9e792afe46ff39f3699c61c22c5159d68ca24fe91a8b79d9f132475b3df77ba392b57aa91054decc15d737fcb7872612b3e21ac62278fb170d604946f634e0e89454da34971492952e68a4d6dea725a3b14f82138379b58a4566438a2ac81f11756815d24166842f691e5ec7b905f03b1af70b80b4325da803018a56220738b4eae140c581a3d720ced385028f272b7c5c87b3c3dfaccef647b78ac9685b174fa2df4afb31d96a42492b33a295f723d0ea00380fa4821fea6be7376477ad7ced856971219aee1abfd40c9e68320072a95d4aa34f490b980d6262484d475959807f653174d9a1a944ed72fb3038909621d8059f4103a2eac4ccf550528017d033ec32c1d89574f24db76fc570f12720710d5fdbffd48d04f921e33eaee450d0fc5439ed08a1f3d3f02800d95a9dc3538a70e9be4a6539b8370e25c0eb7040284331e9e0df8a436b0447adf494ced0d848878dc68b0ab57d9e426b1e3d4aee14bbf4a572a0e6975fbb0446464032e7264c5a794f35b4ec4b58e2034ce1468ef55a660b7573c8f5b8fab5b70512fb772b4c6907cc32635c6dc8f5fbbc29d3d96bb5c5138e5f405039f4de9dd1662ce725b1eee95069fcf9f4c4afc0f728c40a9df89994ff093d4c05cee5393bc8ee4720ebbb23dfa3ee71b4b2c40afd274b068d2d27640bf2e9aa32b5c01bdab04f472661ba72a76455da8cf1024f2f72c8c3f61350b63eed5d2c783b7903c3f9b483a304609ef677d876f9b13b192c00bb41236cc0679a1b8ca84e67ea1b08c1da1190826aa75ae83ba0870667a65c3601002c7d6e75318a198e02d0cfa1d7b2a7a726dfb4bf3845555c57e57e13f3afea7ddfb26d5676d13660a707f1ea38db01b08880a2530d42043ee4e3afd7f5c6497b5c6020876df1fd8cdc242c0addf79f172cb30be271ab8b494f8b2519dc67d16158a47830c681b7cadd4ad8307ae7db711d16b5a55068b715ac5535135893f46571e670cf63596ec7db681d1f037ffa172c828dcb800faf3520633873d5b46e9ca11c41f9edfe34754bfae2482097ae47221eb7d70f32a82efb962a0acf58411d7ee033a38bba6ed473bfdf95f758c685836f0ea2540e318085ba974bd6eedc5ea3b7c39be859ed080fc6826fa02b1c2318a8d762f36c9a3b75e425ff2630eda2d3f6da31c76e9dd6f9bbe5ea9d1cc6c315046b58433cf4fdc897ff8f06b428ec208110fca742cd8d54c5d1878ec7664483d0bce71c3252112f1bae756128947b0024eb48ab0f34053530a4eb3d77bf07204743389360c2b1487452b6789caba5963da035ae5d060f0bc737e1f6d660772813b25c8624a9fb9e0bca6897423f92684249552a8e6a173c8ca3cbd38b4a41fc253ccbc099da1bd5701a21e16415c723d6f009c4a9463f7cd7ebb9bd17f40721726f88a57d56534fa147029f011e22e8e84f26722c87592e5c2b908fa4e22727030ca33a55b2bbd142bcfdedfa8779fce346ea4b369a15ff7b255e73b51126bb745f3c6d975aaedb510ca47163fe83ca09fdf1f2fce42af372fb3ed78593f724d378a322e6c241f487cc07a2a91e70bb7a04d09d6b206a56e017aa5d1102b32dbe50f5528a06740c4e59c0f9026d592d6fd3f26090151509a3fc82699a1662e8fb85c4961d07c11c73370a582a63c8c1b20b5c0f495636221fa556752eba8215809934f3c0353b7c6140dcc3e49f73c75a8678d74562b5114430b9903e6a135df97eef1e65c9bb46afccd60efa197194048e3d4ac2ad712a1dc4609412aa572aaa204a30bc175ccc33b60e147629778b9fc5ec6b055e318a662caab398507725bb51d0cea4e188c6ff829b0dad30a9046745a9746a1d11830cfe2528b9ed9726ea50afc37b7535452f6433f5d876a557188f7ac1732a861dc15984f33877472d825b84ecc7e15d2f829f2f3da974e88e0c177209acdb2e84dc95382668f4832260160ab1252265ecf33f7dd77e0eb97a8f56e882dc4644ca127f899423c43221cab27686ed17b2eb05756b61c105cb40b26958639a94aab0250e319ba838a6af916487ba1f2496dce9f6c3edf5af6da0efc9c8a0bb3153a5832cdfe904eda3f968972a8577a59daa2697636de82953d82c08c132d6a3aaed32fe234de9abe23229d5e687bc415b83129d40b6645e86e268c2fe0b1d1e1b4ec6776268f55ab71b4251888f0a8312b8167afbaecb178fd8527ecf7cb44430173e6dc7c8f066a6cf385ff2204ad474d9c04ff02aa96bf6a2cfa3eca227dc83375fee335151c033bd49cd805e9ebd21c0f1dec6129261a021300c4bb0656a4e8ce22ba4d27584d1905d737807cc3bb85d0fe37f41241b76319517706bea2b100ba7fabebbc1cdb728be89c1cdce4752498b903e7cf86109124e9662dafb9b6d97c023ffafdef6d4387a1e6449da662ada630f3c128ebd6b35c3c04f26ae34f5c47a113eaacded77218f1cbf732c6d18a4a2cd423a9a4cf489c05770bd448d1cca68c2e9b88264857ff0e2f5c9f27df8ba966c7086b582c25e6824a28f64c4c7ca3fa69d1d06946721b00e161fe2ad7715428f64252a7ffe745bdc7e259dfffc47c2bbd3678251643663f0e197982d3ef52e16e69e2cafcb6eabbfedf0dd342fa832da7d3512f71692deaa5cfea3ae3abcc2be630e91c78bfda4d33a8f86055d8de0f6d14772c7a72bdb8fc263e6b1bbd9bf4720cc357d7d53efb0d275e439cbfe6425206b3e7003fa722e47046c7f8c7bc8acf0d35249b89039049469e790c818fca1c1b1c7ce272d9c3082e89c526a3da98f7ffdfbe7e6831c2096210b0e9af77c2d0c59f9cac721b584ae02e047dd8245f79be772dc95bd067b5b82a037df952b7c8f1e922d96e88d0b749855eed4cf2593837d433ddf4a1b217aabb8db859c869fd257cdd837267930d6f7848d705e446e1ec6076c7a9f5477f099393a49ea4a73f89fd7a8b7209491d2ac3703fd9f72f55ab364f212fc8b74cfbe381eb66cdc1d53bb978355f9b5ccb452f09d37936d61a5994ecca5b4b7db178ae58e8d5b06bf30df0213c0277ccf913b56e82482afc4cd6b787c69529c978cf03515f6d51a70cd46f75927209f48b8f1cfcf3266ff09d147e785fb94ac72e90bdbabe5a73f05472f8316a464e27e74e93b5c0d6cb533415c567fdc9c39e37640eb598dfd1f44dbf1955cf726949370770086f68f2326161a69c17a809ea506bac71448adde76a0118ca8b7259d26e802ad3935429cd052d93b5d75ec6a33333acf52f0981b6036b4e53ff7214a717ad7ba937ac1dec4c5fec03ebfea28a7a99c67277f924fbcb9a8f8bb372d14003fbaea7c31a21491890cc2a597f8e65496ac3ef89f24cbdc3afa386cc727035f418f6bbc9c7d60190900b6e49f1ee2473dc97bec9d8edcaa776cc689b726d3a8efd83dd11b294b251b17d20e46627162f648a421b1462464b8c3353a75c809e217d9628d505bac2537eed88556f16b2562a643d8c94334c4cc5b3c299723ccae56a3c98947a4d6a4e48a660503215beed2910dab82a802a232361f8ae72abd6da509f0a1e9155e7d0dbe21e732b4fabe19d467d5ee003ed695823e296725b059a8e31264a9a0eade3c0df3bf0d9cd96c7de7729350bf3ff3ac2f7d3785c92af4a7121b0f4a41ab0a60c09b9a267c360fcb75ce3373fe53cde634b2f4672d005f02c353cbb617d8977886bd47180493002baf52c65e61f206c7739a10972c604c9b4b0326da95bcda669f1859df86d83a089e442d87a9273bc118a802f15cb454cfe03d11c817d80316e686ae598f15c71efa9ad20a48495f0f7e203c334a29159cc260833ce76df75f93fa90b1b95ac20e1da2ef37428c3375437fb9a72c7eda8044333359a5bd696505dd3f23d55152f4f227ea82189323119d91fab7215e4c8f09931c40c9e9173a974f6792dfb0f2ba8d7afcce484307196d1fb0e724f87ac5f535a1144131df55020ecd1b4aec11efb97bcc59424269e3d55a487728cd186aecc7434d0033d968d1aba671af86b98ddd5b4f8e10529186a29f56762c4c31cdeec9a983707a44b3cc1cd1539bf22b81ff2aef8a14282273c01bfc17203c7d720a62fec8cb6615500731ee5bb7bfcb81724f26f14d97dbc8aace5be7236be8f3d03c66587830795fe99911db972641e6bc4a4a1b8eeabfc3cff22e4039a24c95102901f04028f682ede866593fd5187590616ee7b269baa4472fc7d409b68df7183d21f78daa127c17978ff62818e0d0e7339711ac08bbc6a9f4177725c8c02e4487add6a7b397043d7f31dcb77899a3ca29eedff5322283ac982df690c62fa8f549e83a1b8c26708f3eccfea880dec0f424046672a210c24ddc6ae0f2a14fe4b822a5f8ee29a2004085d5390f792a321e04f597b4d9d0f0327271c72fa4d0d1279857e5e0b73dff26dedbdac6015cb5a893fedfadcdaa8235a9e3b3598112005fa26257a84a349ffa3f19676d296ef39d04972d306fd0b7dd3ba0f72e85c691616fdc6373c29e05a2613ebfd06d0c46c653906dfbd689e50d010457248cab5ef49334fdbdc6d9804d1743e39e9e191e2e6c37750cd62dff6aa480c18e92dd5bacf65bb15c2bc6b147905910ad5cf84116dcfba2cd3598ddbd5407672093eebe19f0d524a6ff93fd153d7437a09a4cffc6e4924baa9648aaf6daa8372eecd2dd192240269ac51d122324d942d8c45b6e72cc024aa0c9758b7f3ba7472544a2ba647d1985f8ccefd523d07e48d92b845733d9356a4a83e7a1372cbca72963499533b899eebc6e4fd890cf1194063bbe47d1edb2cebecc524efcba6eb72b38beaf0dc6b1e1b96c630e14933370bb08c306026435d00eba8162273bfe3723dc2fd8ffbcf4c1d16aba37ff4856bf1a65aae2b5724211e4af6dd5a661a18678df32c080fe6a06ba2c521d48d79d166bd636d91f13b1d1be6286a4b5c583872f4a393b19fe9860ffa7af721a883bd8483ed2e36c014b973605027a757d8ba6c1f5a011bb1183578adca21bc337f229e49de119f8963227eaada8ac1b34890724f5112f092674cb6777b9519f90d8edb31b3d1da43b8ffb3ca22b5986e3df254689000ffe8544b4839e4c4a3655471bc76bc6d05fac276959ede00708fc94c46767b497a04bb36a6700cc67d1ab91fe93dd733643e9f2e717d051898d328523a47b31beffa6953492812e06f69f312f79328c5fb209c421e2a1b367219add472ae5637a330411585d6da7494a6b34952bf4709e301616c63a7f0fef618c42461b3d4d3cf37814a79f8df7fc0d191d74ba11f841e96926be8f89d90b92dc0c76acda5028b5a04443005be9c067fe9de948601c3caeaeb10a21eff06e6079861714a35b11530b1eef78e7494a3fc4688a5e4238a2eba6f7d7aa6b832062de8c472efbc20a4483ca764ec34913457cd27772e513ff17afdad196e5232ad34529f509bfd0eaaa87db1917846cd2c0b4cd6315a0165845a10ee05e38c89b8d72ccd72323e7d7170e2727335ee0333431852034085d5196ffd6b68e03fc56e5d481803302af3f20a2927025a75b42fef372467113e6115ebb4bb2144ffb19a0a556b724d5134706bbf319daeabee468d832475ea5dbbd990d7e7c805372b0fa75fe4720516a6c37740287480ecc2e9de7b6e815b27bba57dafe94d117d3f3ac34d8172c7fbc1ec647d98a64a22af661cbbc1cd8ed6f9becdb030741196b15212ad81729f509ce46b25c683609d04ae78973a6a3d9d7d554c6f09a82cb8e0ecccacb5720a1afe2dfc1930d00535ed29a74f91c56c0bbf91777b6591158e1bda5f32dd0fa126c2dd5c2a38faf76bc162faf810417d565131d5b53ad5efd5830614c7e35379b47da170281360aee6a66f46a03b7628fbf855efe89976d7127beb26911772bf5fc87983d270a06feaafdb5739c16dfe6d48893e2e7f9d1484efdc5e97374f4a7601824ec3e82fe7c63051cca3c0a0cdc8a90930231bd486ca69a29a766772113cc305d76a0b137c84f0199dbc4f2d1b6fc5c8bc499646b5cfee7a0162a0388da3110cced8eb774d9da3ddacaa5f3a326cf70d769b8402ae19bd489311bf72857ac460a24fd123635b0445c3afbf3127ab66501fc109604996f1d3dfab7c729724747c7a2eea74c11d488fa515f4f1d228885af38bbe996fc09f856e5d1c3beb494b6f22d1cc5cb2c81adc141581f65baf78a7c3df6865cfb36b2c338b9072d481990c5323bddff3141e9d5f3d2e6917fccf5317bf287eb4663ebf3bbef67216213ef21be80f03d2a9e181d1601889b5abf6fb829f6b164de7648e4f7c4172281ad508200992c39ec1020abcd5223fe1296fae7d2d31620fd35360d35b084fdc9e50969f38d0048f04c48f26a1513ad3fe50993e3a0de7781d439cac593f1f3c9234fee7301fa8fffa9817986da109db3ac00a92409e8ef89cacc9b15a8872c5dbb21d7a0328b47a3fb0454de86c08aedc17650c54d1de751856196bc309723ec82bc629c1a6f98e8ed5cc276e90ecee8da1312e39dbfba9f4ee991d25877242caf2ebd5e24855be817756688e0a7392a4afebbc6ff0b880a2936ee8cc1872b160bc3a24fc8da2745c408353088d1c390a14f38e520f3e631b4f2e360c9d72c150f585c0d06bd33623c0868237dc933150ab5ad0c58974052ca39b6deffa7278c4d6077d1a85ec2b61f375b69ccc1593ad1ff88a188a34faa59dbd4927457209767bafda636039664ec753dea231bad3dcc359fd2fc3a61007c3381b8c52725a1f66513115d1b2bb38f9f612d092bf4aeae00c8e81c6e17dae3e4411789f729acc89b678fc1c7cb7d02a621e8a55f41cd7fded57cde4fec54e6195fadcee7278b1c7c0777ac95134fee21be3591072bda61fe120efdf81f21ebcb0df445072c00d8e9a520234972028ee7328a99334c2ba5223f2fd9f07aed61468ec139172d1171f33a607be28f728826cdd05b0d2aee2577f5bc3f31004936338fa1f4072de6f757afd856bfdd7be899230167064851eb1726fd6b6921567951e40b883722fde2a0d03c3da2cc70719a0d07524e6affb8b8c20ddc6889112c6777da51072b98536bf6e98221e191c97d1d12d23f7fd64005920b34da8a2445fa01488124389e03c7624e927bf23d09c4c3fa57c455b68fb7b2586a67ee26a5aff0b862672d830d4bf1a7b3c1dc0638203de9ebb1d414d6d9bd7d39ee1233e407fbfe63b5756ba0ae70e63c8ecca0fb37cc7b0e67c51745dd404ae5a860d121314533ca466079e4aa618a0486f3371322c70606f0831536077d6cdbaae922a019e5e31da7253942d26c4481cb2dbaca5b0b27fb58c416d7a1ec83d700771de402e6835c372db7c036509ce753e5733c80dd151695f2afc26e8f97cde04016deb51f319b5722d491011a3f11d6c1a7fab28cfad1b887767032d70149ba19988084762967872ff02633f1e3e2703728426d4a3c3fd1bfe6cc6366d8dd7f942d52dc320edff72dcda6b4806214b5bb0c2430e3be446ee34b91300e4992c74b5cbd70023880b5e8ed021502bafe940c18309544b39cb9ef3723325764021be9e1260551e7de648f14fa6e937693043d98f4ab75baa31f8582c291b7bd685e10776bfe91a42a472b1de16511c5d5c565064b48151fd9299725289ddd62f69e03843b27b95669e256aa11825074a745e2926ef8fb647f7c70ecd2608244852fa150d4c9de9726772fc0d2273745e78823d80f689efd7a14db80e7f1f3f2d23a6c5d9fb38479b773410910d42720cf767bee840186a8bceb74b7453264977f101c016fadae4084560c0ec8403f6bd61d9eb539d1a0dc92e7654bb4794d5aa94eaaada3f27bf8bc672857041fb3565d7ee58e9893c614eb52e7d76210a6f84149dfcc3c1b498861972967ac09db251dd0725796fe9d6d0db9d8bb71bfdb4881685023993bb5fbcd472c543fa4e8932b3896d68d7a28741ce3032d4e64139583c3fdbf8db9b5a45fe2d274785b4b42e384dc8949a579ac02d863c68d20a34786046c7b8001d661b0f72053d33745d36bd7211371b26129b347da7602aae80e42144c7110638d185a972749188d61645936a944003a72ddd1fd839a529abaa82a6495cfab286a6b42832a3a97469d46f667f5f22cbf07298f94271d7f07c8834dbc6c570bd86a4cfa751f67d1c1203b56d6f63232362977d443e823499d7b3d12011d68ef739c2bd0c72f84929d1faa7b24c501d69068a68235232f4296ad42bb02d4d6e7ef265e915298ddf95cd6d4f5c969e7f4d085856f53dd6412081ed2b3efbc5f34cc905843672b005f0d6f6e3d1a7dd9187b252c2c452a5ae6b532b679789b5671280c882f843138712bf5954ab56f6d3d131a84af12485e0b8ff210888cbd863684fb68baf723946e7a058f165f5d1459b17c38194be8c9db16a909c9469d23814e9731b1b49f765c11ace56d8a58d0d496954ddb025afca589149fe37f6f1007214ff257c72ce11039ea2b8b81f118ee7d19f50bc236cd9752c9c07ee823632dfd3f113f261d6efd57e8e30faafd10d8a939f093b5e4986c39e6089d49ff587fcdcf3d05010d22ae3de1988b038771347836e21f3b647c4a7b4df89b5eba263e323c4d55547c747b3d8cb3f1ebbc338c548799798c86f7d25b359e2c5e91ffba7d272a45d02e3c176fc1ea6172649e84896ae4de7f0824ec8a78292a0ce6671265aa71da07248c4cdec0e89b043b1a7c2a21242cf564de5e94882133305ac2a561f6d3ce6723bca9c0e7e419fbbe0c8ca07e85beb2f5f0c8f797de271d63b7e92fb4feb00167777dcbcae50654c385e4849c1d01586d3982244d79208a26cae5e740b54fb724fac0bf882603af6a5686157184e0e339478469a2b3dcf91818a9718fa2939729f71f29bc4ea7fd82d38a3791b0d12901316b17eee10580c2b6b814995ba027232e75833b159fc8ddf93d7c240025cec46bd9bdc6a7a394bbec56f5607c9717260485430f3cff672f62df60c14aa38ec8ce8e2897c5c7637e57693f3cae72b37a6dda5d588a746fe6e770cf8297c61159e12cfe8775c45a639c963be0dfde77208888307e8fbf65d3b83646cfa93ce8a9ffc85064242a95371b7be126088ca3fdb2cbccc80f47561994645d90d2246110f7834b7ee14748e8c17a40437b4aa724ad831e86605851a95eabfe9bd5c505cef159fa97e3db8656b2fa55be96b512a63202ef148137e19d198ec3a83a2714da4dc4b2894ed247ace3caf1ae980c9723cb49637d3eac4d33e1f1c251350010b245b0b4fa970736033995a856ac99253502f58c1693e5811568ce981347ee853fe72493092099f22ee51c80876d4b6514d55e493b5bec51596c25c1d0c58323a49897e49d29ec31aee13d88afac59472def8c7868acdaab8b2b4ecd274c6cf894607e9af2d44b1f2ba715d08cd40745902ed7ef9b68e8bdbc8e485ddd37e8f4ab434cc40f355b937574a22f448710072045e7fa5d921a002d2a1c81bc7271f7abfb697a15ce97338a7229bfdc5f31b720bced3d59e137c8e8c6cbce7f37807d63edffbe6668ee23a0d8e01714bd7cb724c3f0d5b23e8882ff51cc69b0e9ef126a7e4c7f374ae528e5f5d658169a3a37222a08c1355d015ecbf73f981df7548edc2cf4f36d96f8a2d8228ce305c35124f33f4b0a1f10b995dc67c3412b98b423074606cf885976a8a6af326248bcbde24323e41d42edab2bedd04d1fcd24cc6e78280398878f6ae4779bd42bc178334066bcbed75e8c8e13695f0264b1a8892c6cd47f254d5e1231dfedb73ee1013b629e339cdc9646d9731b19ab658d2d9ddc865a55646c0af56bc533a8a8bb09ec72e127686479d7ccfe637ad67ba5b73c2a1626f5d3ebd6923b76522cb99b7f932722ba5bee29663031c823f879e6773629c9c40e5d2ba875a9f57a1b3e525a2db723e3959fa2939c9ccac89bf3035f316defea2c511bc12bf69d85b3b3de36649723c57e81de3cf0b310a268aaf8240809924f0722df329ffd60fdd4c10bcd07f72e4f52afc5e536367736ff1670558cb70bf1a58ed23f8041cea2919f811362572c4a669b5536a8fd94988c2e38aaa254c5d7529ac444467257896eeba2503077288351d3a342fd232602c1a3e91ce416c17c49c9ebb7cf25f08ae7f166f08a672833adfa7595c8be8bb610d49259b7aaf79a9736ee12d049907f626c1edf60f7236f44c564a8a68bb760297fb678f109f27ef36d69ac5c0b5a59374ab89f72a729d3384b5c43c4a99b91922604f3fea66f0955d6eaa66a735d6ec7e96412c9b17608e43835f4a1d234bcdf5195f0b29910109fba756ec59f7bfaf30cdaefb8972278d75543b099353816fedb15b6f33f4695d5295a8e40524cab84c4cca8151728e4c44fc086b4bfd789bf593f82aad8f671b94a0dfd4fdea33ca1a7b5df451432862082b30ac947f8946b0c4abafd744978107e986f1216ec575932cf2752246647c84e1b896ae038383fa5d361391d6a3a72fbba9e6e8ad5b89463ea1854a72ce019a2430ea5b1146b21d11e02725290f6902ee86cd5326195a32728bb8e36e057074c2fee6967c932dd9dcb76984a3c45306ac797e08c70c852d570239b472069ed72db5779e44f48a27542c0b3a7a0783934cf170e2c3efc41a33096f821a64472f2309939b611325017cff4823a4ff673b95d2a6cf7e55564dc03b3e7a5c24c5b0ad2e746ca169fc9ce7ec28fe172a0229adbeffd6d250e80188dc60aa724c4949280de6fb8477a990deeaf63aa8ec59aee793fe0a9dd969ac0678285d3c501a5c7f832490025d8aeda636d5825de69db936ac7b40cae3643628018bc87276cc20cd5063fdab38e67862b55c8f47a960aa0146e53a141521ed52143b616f414efcc841dee201c2bf5d8a7363693625c471a6ee5930bb3fb875748756c2499a8efe3c349cbccedcd22f849eb33bf5fb68006448fcbcd871d9ff2fa691775a351849a5f8c7b489cc7612e5983f95ab176042c3c5867202ad8b3c0458cf1e0bfa434ca7f539a387c342771598d7b3ade2bba06fd5b65e0115b44524cff233725538b3dfc8cc7723c13eb96a529db270ab5e76b3c82ba367e21eb7ae3045db72b2421b0c4229c97c8a78c70bd2a6ee8217ad08a6c2bf5714d8ff69455b34a0722228ed627d0864babedc5cf59725540763f162c87baf2ad5004b8cf31879a972be6339a63fad12af8aee6b0018273dcf09b50b9de4af5da7a39f8d46d4d4d3729ef93e3536d4d4d035313a3b507a77c96d3a7d47a845a6540f387aec057b6372eaa5799f1287ea06957d8e38c8a67094a7c86a3d48c8bb43a66884ba2bf86d72056a245b6bc7610e191562a1d932819ec584c5886b9727890f399245c7f7f5720e84090b7bc79b0dc5bb75d937c6de9da04ac12101f8905d15ab43fb398d1000e347865c539141fcff6119cdef262a295b0f1877332707ecdef9adaefc3bcc72a99e18ba25b18db0c5ebaa4bee056248501c72060fb4476631c03708ec254f216e6d8f17565a6f3ad20fa8d5ac27ba1e5263dfa8a15f6a85fdd61fa6326547262259b35d506d384299bc79c55eb2717e8fdb1527100543585bbf26dcf969d34d5fa1ed268a28636edba1203630f37418c810770d555a8f5a74876cee8bbdd072f6fadad3682c9d6f0ff3763d619b44774f4fe9c7a49c1bcea673b3f9449a4e3acbf6840587a3083621c567cf91878e068908783ce4676f7b008420a59d935872b8dc01130eaae2e324e183162275df7a2745027d30192b3e35fa8056bed0887286bf2f9b84e0ba2e35947c73eb8fb6db2ff1dc3b49774f66e152068a7e5d9e5de06b03b8e31ac7c76628c42b96a7ea8d93cf238a559451186e2d0c9fe6af9e6de72c976bc74de62ee18ede8f8ecc180ba77b0de5ab76e529a1224879609e7b6d4a4ab1ad847b81bd677a9fb4b98e5fee42f3555fa95059e2048817ce6d53b072835cba3b9146c25535b77b06e41a56eabb6dffc9632a36dd8d386b2abac9187283f6a828be55d9368f4f70c708d901a1fec54d1ff4ad9c83c38f3b9cc3e54e7248e98e39d5e6948feea8ccc421bfa11a563b5ea6e2bad2099139c2515cbf9772c9ba342598019ae096984965ed47ea811e6907d5181082657ae3dbd267c41f5a11d98e40a34c1c69b4f4efeb4680bb1085b104461d7a9567eb5177d37bb70d7298707f4be84bc9e58a24017ed0df94f855fa284fb2d81ab739cb7c93f4cc6e72859b992e04f48d624597c2b159a04d6bfe45057f347ba413cfd80656e33c367226d448b47541055cfde160bb38711fd5e54d69620a68dde70b1c7b1c3df95872958573494c83ef05e029b0d35f55f54b575157c295d7c33f41b9cd115fe6cd2dcb054a5d7ee2e8331ae6a3c20af4f668b8a21314d3b9770296afbb957bb1753f42919454b6befc53f039c1ae8beb142a80d39baacb8dc26c74d2f578511fe3469e8803c3ae0916808d03bc0d1512b400849021220a7a629e47ca1c2737785d05c326b55ada1dbaa40d1285d842f4ff41a6605d85b00c6f126954f249be880c07d8741e3fd90063178f6848609d7352932aeffd6d8a210d5af59679da69fb5772c4ffc6b436af8d258540139092f7a965686183b4430db28298a9013906cc4e7263fa34a34c9f4a914e0603028a0a2d9bd081c3b9928e42807f9c8d5d1025200e98d873e9e1f29abad4a4ac54126c73bf77db758c55b8a81ae0b2c0874e64c31abb4e7956b348743c544f2b131f19aee0d9172a749d6c8ebd5e3299a0f262c1000f9e8148f324648989e6a77acdb5ec64cc40873784097c94a97d883738ae47727874d44ff995ed3528213fbf568269a134a117ba047f5f0e6f97a37aa1a3587207cd5b9d45b35279456f58cc6bed336604f922bcbefab5104442c867d6e0a67230aec8f905eb408561ad20c9bcda13bb8fc723850fb5ca57dc16b87a3c8464721c31815701f06f7005e90be1b54946681f136a84e0dc3b67643e7977993c6372cdc2a1e90c2e4b786d649842b2806bff4ecbe30ff12217c2eb43630100eb654ed0300f53b0272858f339f4d5870c9a64399a034ddadd89c1578c9ea5edd8ff72b6f94b06cd5849518d96aaf9d0f63c3fe0a2a7212a7fbee73068dfcf8a28cd72b981b48c748affd05b247a73015817c6d3a6af12ca7c9793a6e0d1c559970c5b4f77ea0884677d07ad751d39d1ddd241bb9ec7c0dde2df2e5f3e7cd13951dd72bfa6f4c2efaa74b84acc43a38d417e96ac59f1cbf23bca7deecb871dfc88d07244cf32eb152ba822f8d6c6f180ab89912c002954a47287a8d95b48428ffb7972bf919a880367ec7d2f22406614b2093da24389f5c8e41025724f7619d64625448f1cad7c766c45e7f5e5bad011549647bb42bf0a67473207b5c878eaeee2c971318644ccd5f032cabadbce569129d80f506bc1d53920b21118505fd6805a3f2d41c8bc673f2cb1e1d5a229cfa612fef986f77b3089aa39bfd13711742bced472394efc74fd3187f8c2a04c394f332336000d73954f28411aea4167c725ab5c729b744b5791348440bbe68fa3875d56138648481d7ec5006b225633d714eda16139553bd68a1eba12862a073176bf2749bbdecca77e6360bfa290819d138ebf291c439efc9e066fa169b8ee496acc39d021c83499ab22e7c22e410599c5621572487b3b0dedb43c572500bbcccfdb00aaae511efa19983a72ee09d85e7311f7723b1b64f1f809e713dbd520b4148bbdba75f9d45ff99018c7323d39c685c8470407fa39327e2a5f9e44a37d301a8441593a17bb6b48b01f087cc3163c87975d2f0bfdab3cc811be155836349cb3251d313a37065fe4873f11fad8b1efbf42e846b5fab3f94c100160d8199d279ee6e3da10b2d056c8f39a01107dcc27bbe53f3d58279f8300a4f06fb1f7bad49622c90be2916d4d0c0931642a7d74fed81a5b72f3a09ed4529523c040e4468c5699016955f68abaf197f6bcc409a73e0255d072787ea4ac0d3ba4d24945d4aca163090cbdc68ada80958cb0e604f8a4b39bcd0f074673fe5ffed6ec07abf3b88afed092665a23f5350a4dcec17d07dc36c0a0721230f549a44c16cb7142be30dafe02d2fde4af1eb9795b6f784271ebef3d6d7219fcaa9687d10bf6a403f18338394377b4bdc49f1f5f0889dfcb8414875c681e2fc8d987b12ce59f34de63a449537c651248a7f41a500fee28c50d3fcffed5728bdb9a9798ee50281c138a0a65e348b22113af926fc7377505e9084eec2692725986f56e25b7ae5239687fad3dcd1010a7da370cfa989b75552230738414072c1f88f110ca1557ea8fb9b7de75d22cf356861a5024d2af19eb2525c3ec100972d0532e72dde562d36feaeee79d93212fd9b5007adc2917ebf4885ca4ff816572406891cb3ace32d850b45e77ca8c6937f9ccdf45f11a7b4c30d48505d50e4a723fc07cef97f240e12e1324b3ae19c01e3cb69cab242bcedd6952ca7134fee272796ff9bc2514c06a53adcce89b4dcb9b1b1eefc114ebe7483d28f1eb863e842433e70518f10a8f2080da0527c0bf1b73ce5fec3167ecde288d20a0a1d6dce472316d2b67b083f886484a0297fb3bd7068ead6731b8ec5639f162e1315c50d67215b27914381fba5806f8a213fd216800548403777fea2114701b9ed1e8fbae24b3cd651a6898827c269cf8fbf8ca75eebec10c3a66e8161aaa5b6bd6dfbf472c48b1c1c01db1ca839a120880bea3cc4f2c2c169d93f2dd57dd730ac6a81f3f43fed139c8f2edd8cd9b392485f5577c9ecf3a618a600bf0746b17444c99e96e1cd383add6026228a5cee153303e8552c625af2dbd312a4c066a5f0c0872e7b372cc7bbe9807591d76c9a2e42d07d40ada03996de305621a2a204f52bb739a0872477a31bf31755e03c21a1699e849b645bb91ba5406eced049174b775cad41b72f3b9acd12cf7f5fa01be8e3514a22112d1c5da70070aca9a2795b012f5087f7288037bba2f1e49d629d1e9451a00aec2aa4b7c23113c2cc324b44cd84117b02ccd28496019c521a83b68d19ce81a691a28bddb14e149f1b435fcb08ce332da2b8558bec925a8bec560cbcd854ee324c43a647972a0eec670c00feb8ba369e77255227ca0fbaa752aa3333ccf6367e4d799dd793ea8d66d7e0f6fca1f7db223723756e25c1fbe4e97d77d81fa606e329bb34f8bef6d205648818e356750739672f3e7d85c3254115735c176cae0bdcd5558344b23d3a01a756b0ea3b761f0fe15fcd0f3631b72ee4a88c3162b5e924dc730e7b337b690c99271cd166081e29f723cb93c7eee6f0f5062a1cab09c0b215d377404b34af86fffbc8670d1cd651e7283c1ebec7ba2444840cd09912082e13adf5d24841f9fd582e3c5856da43a7a729caa657fc1a6fab72b99b347659263c1ddfc7c179c2cb8e14f5815f9b3c1ed725a0621aa2ece77546508819ac9e1c513c17e9490abfbdb42ed6ebc70de541b7288ac7620d24240466198a812aafebcf30b8e1b562b890cdd7f0e754922d8d52af6fe42d238d565a0732a2419eca63ad55dd55814f472111e053d3a163d789272e60c31c18daf56a2381cf16493fb8763c808d21c5983b247109f0e1374c9b0447809c359b0ab7f95a786d0b943ca97772f63733329fc221002ce41850d749372ccfc1b759401368cfd96f7978d9dacb51e458e84886512fc48311159a9db672e23afb233a3acce51bcb37220af3575c0d57b791b726d7e801e86cb803d1f84721f0b656885269e085dcab2f03ac23351e472ed878b49e13f6b6803a640438052572371433251f7046bbc2a28a6bd50b350bd84a7f04496c031eda1ab9f72532a5cbd14632896d7720c06e8c996eb51349f07c2112fe7a6989f6bf2a4a3b14172116c0a9d4d1037048b0d32645e9bf5eead99f496b4fa422cfdafa5ed29d44564c3d808a5f972875b1ea34640df59274646d2182c2f0c15830d4ac21dc74865677906d04a1c4f469cfabc017b9111af8afcfbac6724b4edd72d90465c1802025467927b148898f5cc999bec6d5cd42bb8a7d648e77b0d52163d74234a8d0bcf391457aa6c0e303f7d6a151479b4001931c71d1e088015f071cb80e1745351d536477ebe269e34cc1a2e4d651fd80bc276cc13283390085ba20b189b52c890b172d9606742a33f74874ed063ed61dc14c40e5cec106dc4380b72e05cf4fb4dd17235d68571c44a95564a6766802eba2724fb35185e1b2a13d3efa07d9017b349321fe6b519b5be4d5cbdd8c6ef1fe3b7b7435e41d0ccdda86bc41dc4e8451b85728331f7395d1f86622183bc8498cebfbf9eedb956dc179991cc2e7c67fec7a456dbce88cc9526ac3a4a34b5d18d6747e09390b8c634098ffde45da5cc0e155072a3ab78e5749866194b5d015289b2124c8dee666a9d7133150d6d9e56de0c7372b0306878d34c526c647c14f6239445b72a6ac6fc76ecd88afd781576fb495f727be6ac061dea2563763a196a8e43d6467cf164f0ce9824f9a89f1514651f42724ca49ac3911b08fe02e4e9336bf629b9abfb9c89f9a52e226f7e07acaba86a72ead0a264a52cc8bd476c283036c534c14e9d2d36afadd336a4cc495f6997c172e8edf34b94d8764cab380336c54490c13305a0c970b5b68b34e6e51c4c8b656bcd1c8df0e67c38f5c94c3bcfc7562ac0e5be7df5745f4e7ab3629f73b8688001c81abb42de3edf4fcb159be5f8c7de2692824f7ef8928da3f295ab92325524463c4c6010f76cb41d8704519a4256d4348c2edb923f57118352a49f3d81937d7223466710803a173425779bbd276dd0eb8984ce0fdaad017f20a139be23e2d8081fcead784422d4876d239514c4ce484d096603ab3afa98134d763401057b07728c95214c72b87fc5522b65bd997e7e0067095689c87822110641cfff2677283ce49636c8fbcf7fa1d87e2cc85f7617c3bf0417bacb23eb5e7ccc03c5408cce3dd2119fcb39355aeee4ef438c8859638feee8a568a4f5108424e4929706473f728b61244c6359b91cf0f4bda115533b37c67c8ceff83c7ceee67cb1e97549b572f4f08538550b263ba7797e664d63405459e2827c657c37ee122088a227c1ca72e6d81d6f1b596a0cdcb152229c642a5909679dbfb3e0b55218767127415d14721ae49b6d6b52816250c94a11e838b491b260e751423453dbb3af499434e153722fb2da95eb302df5bb152395d5e96b25419d075d5250ae5a7fcead010e397a42548ca0b6145e20c270f4d2eb40d40ebea70745d93faff710bd28e28a489daa72a58004b40a8eb04d6ce0a91bb55df10da066afc080e5b906ce47b94a3d66d948c9c689e3e5ad08013f17eb628b93813514f9ee8b44154312e54cb9a8b3c15d6105b675e6b8729f0580517e7f486b9dd89a8ce748bf00aa651e9eada38f7e7372f4c0d3c52e4a88a049c1d851ddf9825e77e96d1700fdde33841a0f982727b20311ef9cc1c0607897fa5c4e55d7aa745d50685c23a247b6d8d0e4fc2f6797c224434a8ee5533b10d00d9fdcb6f544c5fc4bd0356e250363a374ab9836dd6cca5a563f04f602fa627ef5792da42d6c09cfb7ae70ba4e1cb4341b58ecb096bf1b72bfa0b64976f1d09173f514fe1299af90c1b65f99fad380c2b6f8962d84e721332d729fe59ca7cbfedb1bbbc525b5f87e07dede169f6ae95f2252b2d7737adf7264ac08d5be8be04320e691a91e83088ac8a0793df0ed697582a8717508532d722c5654620fef2e8b5d4ef139f55e90c4ee0fc67920b32679af5e3a4bb86459721e11c175c90066851973167aa13b920065c111c3a05d01724a04436c6f8351728bf7b2593f074d2623a1604fae65cd5d4bd8982ce582abf98c9b437301a1806925e40ebf639b9b2008ce7015675be3e2a80b1b8674032249bdc5ded6ffa2f43b62737d589016fc7b3a3162f7500e94655a448dd4ef59878abcefceaa6267d472de88f4398d275ec824374d2de11c7f029885e78922a0cd3189fab9637a2e017276d9269433f77f3c5b35d953bb2d1132b4ec7ba99b8719ffdc95c88e9c80f618a12fc434d86cc8cf01601e868ee4cad65587741b221521b37fa9c374bf9a8f72cae5284f304bf0bc2d5e5ff3e517010a840f9d7fd074842f04ef823439eb0401a031a79f612085d378cdcaa04f78399c21fdb68cc602ce1ce0d6e071dc02a272034970e861900cdbf3af2a4890ae9f73ccdc31b0d6323f2012bb2fe73fda9172199962aa45996f7565eb3316910e84b3922fa08cc00c38600e1963db4800af72942e920f3d442dca10d3c16e8c816efa258c011666e787b7d4bcd006a6354831a0030005965caaaa7cfee058719a618c6c8ecccb8d2c6c691baa16475eb02972e49e8d7cb0e2d8bb604350700289a13e584ca0f34a485ca416f4ea84c23afe72cffb9a0d81cbee0fd1d1f5dfb66f059867a8c50e5a474fcaa8f121a8f79f7a1d6deedaff35a8fe6af34d3fd1261fcf913280901aa2e3e1be87523939216c3a396461a501395a6aab3169244ae1f6ef90b196ea066f92f62111e5fd19835d5f72f11619e00726cf5d1348ba924ff50124d59ea1fa621ebfd65b7a61b7b53e037265b665d4920d74842de9d520d5d1ddff93e4c30e78bb1e3e3f68e9ecb7081472621d213e6241b9e2bb7b4f61332e57854083fa43ca01f45167012f17e76dd372fd2093b0c517a7cdbdd6834cfcff4c9bc9e1e40eefa525d22df31806d7c9523c93fdec4d2afee2f2e1c7952157e58f6de4679e1695204e5f6552e40af2977a63ae11ba976b579cafa29a817f7c124856779e451c182dd13225269266c21bff72c527fce0a0f0b2e8fc1a698a2979e48986d8335a6e68aa7456d4a3147c77c73caec36366eeb1d8a1f7d63bfd1ce35cb4097b19044058fce4b3a2715b1fcc1872fbca3e7b1eb70ceb84544d552305210f6ab824de79fa40b39c1de4c4d22c317229be8efe0877cd9b22f1358c87e347824269bccdf89c4a011b141b82230213720d6bb9338d4945d09af7e408b456397d0e93272c747e19714e29774ee62c5b22451846e6a7956d637552d14bc581592d0e7d7c38aaa286ce461ff28949689a1ce6b12d8545bc845f0d0b56f43d9ffc6e9a83f3812d115df47fd5a70e48f204726965e3a4849f4f4056f214df50ebbe87f784a4ab3216081f859dbb606ad0d2725cfc9108ed1f3d8aad6f43294fa2123ec18f1ead8b6fa52680254575f426817253aa117716579d130d7db68d6e7f0cc099c36c6d503c430a166a6679944d65204b6f44400c99417b2a8253631c3c1c141eabd1a96565eefe896569a8d51a171a21ad4fc20f76ea4756fad1da1f36252da6e39c7b0057987c4c0336166121cb3448d02abf083db158a97aa7a33ca5f10bc94a1adc3fdbe667d815528a95508072183d3d0a4a8d5780a33fb353caa67608fe2cce0707f7ff9f80ca95b9d44eec592779465162c3466e2b04ec8651e9a35ad76dc31b6a06fb48724774feb34e0e2d3d927a69c179927a3c195070809c29bc4dc6d7a2fd120873273acf3adb0bba05b4f7f62efc463c14dd92ff620318cb8fd0d00346953899c820bfcc53e5cc0172c98297856cd4ead3cc2259c3d6e867e9fa442480748b38ca4b344bccc865e348da23c790e5206bad5f135bbb1ae4bd3ff6f0a1d5821b626cc84b1eeed7b3542c7c79b4c666b107d5839e879876a9796d957c20ff42afcf49106735873e1b7472ede237f6799ebf4f76d9c4e9e3fb22d9c0b8b59c8ed7a6ad57abb15462df9e2f6ac937fc3137885ac8e3855e6561576250475d1287e3085f8d8ab25b09eec4720ec90cd22e35923fa1e807d0b6410efdd12efda70b4cc2929887975cf9c709329ebd6cbc8690cfda040b78c43c7da8f850e633762a1a2765321070faaa60f172b1e7312721800e20d6c59659b408d697eec67d3ffb06b7187940f0446dd90d72dc786117e976134c59d398c20273d5fac1fc479ae07e92bb079db89ef5e6b272f583da3b99fe007cd608c3925f976abf7ec8c1b1f87d2957a27010da7da7d06f1b763acb7be3f380b8e29efa9ab271d13e0d6d49d7f689138ccd7ef4fb73d972b6392bf6cea1a656c39459f306aec1267eb1ecd98ed79b7759c0ce41f2548a7284e6bf2c534f4aa136484591d43549680a01cf7166abfb21084e395258a0920cc9ff829998a8feaf320f0075a89133f764c2337e69f06aad15719a9855a09172f5418cafa7298aad27f563f9c01062686fd889253e14ef7ae40faeeeafaea87287a8f010bbe02f01f5e3e3f4b47dd42e1d938caadeb21f89feee47844b883772eb5d63ed99fa2d59a10a8d93e0402f30d4054f8272ee88c709c25a17dbf84c7273fcf115958d9c7e481c736a939c06387d8669708bb5b4f00b623727d46a85722c2e0acd6501af579a473ff814c61d68703d1cbca7520eb29fbd7642d2e62772f75a365a22982fde72deac4622f5554c785fa5dc8d6b86afadf8573873926372ed19abca8539305b51b2c6203e5ccb15942ec40b026f47b56e70b1ef3761bc72aee506e6d38c8066a0ed559b54c04a6146c39aff4e67ef92782a0ff6fb6a010ac6e091db1a00bcc16c5e32d47cafe5ea49db8bce4a393335a430390e470d7c72cb3924ce930c7d1c8071661a2aaff1e966e9cc612ba0737324a6bf293fc15c72509d13a0c87d7bb3d30ae10c7f284ed0c55002b7901e5b362ac0b411476a3f722377c43d2e992adc7c462ce14a5f29698f5ccadfb6e2ea17c5fe5e72713165615afced26cdc84a9b590234f762aab63eb45f5ab369f246f03f003d75382f1114b020b95e702f856c10fa5b0d7d147c2c4f2509d9f12e3813d34a733d02675072fcd9c970014d90de0e4bb6181a7d858dfa5a93592cd4c699375c051d706eaf0e27296f3dbafdad42f6a6a610a0d2caa47bbc221c3ef16bf9fe2d54860be83d3985a4672dd47286300f0e1f776b04f27b24501a5b9db3ef9e9b3bb3b85f25637219fa7dff49aaf8c263c9b3a7d5393038617e316830cd5a9c230eb9fe7bacc4374b4a0473fd688e36bd42658d31c594e2cd892f93fad2b36d09bb5c54a88b424ea26e493e532d353831e69becd134745e753ca4583682859d46a898b16fd3d572d5630ebde86321b8f13412c66b6505a71df22027f03e65ad2ed5e15ef3789b3a4bff15003c8a8672e3e732dd79eb7cb3e9bffdeb0bcd31ac09e7842cef5fe272174171418133d2117aef81ffcf5c59829f7909fc22831684ef3661be88b53572391c0e159915d48b67923778e85b2d6909aa2b083705577e031315ea4a99d73cf50102daf4005a93c5479d789cead8abbf4653de5265e62deaf5380d7e10786846cf8e0470bb8f8a365e960d17b15b1d3a6189b1e11305e1a7227ea2ef1cf672d8b94880def85d1723d51f87775e3d50ecb4049280d9dc3989fb2ab71cf31272bd81901042f62110d57691dcfcf364a15b1fda648b51a2199a21b1caac3fad6c0aa411d936330364dd96375b26c88c1cdbe36e8e480d93f5067ed151699f1272c8c8c335f1c06b6aa1752c6f5a86bde1f5d6eb62ad7fe36953962f81146dc972ad5b1cef4479572856e5e97ee71838df5d2dc391b4cf72507347b58ad311876c81d77657450bca5eaf97a54ea9fd06857cf59e659229088c42859572d5f18172f83d8cbfa69809039788736f25e6eb578cc3a98a909ca1dcf6e85b7423b67a722277816f8aab41baac19ffc023b4cf0961fe930693b9b71f7596b85838d8316a5a5c823c1d1e569d2cd0983b26f4591fd3a32fdbab35ff0edbd9acb92af439641f24b08a2f1f0afb5c1c990cd1b555f1e17e6c3cd1aa7a8d5fcda059a2e7365b698cab31e080e2403aa35f1c6074905e1c7f7b6a633f046fc0cb6e724f9e4e720a8e15d2c3091d2802aeb57657bbbde4211ff2438ea685088f36c288a4e95e3007591c1899d9cef3877934e66e342cbcc4a12eb95abd834cc70fde81892504722a187240966b680a14750b7ddc8613744edcfbdd09199d79a2f2da225a61cb3a862832121e820dea897020262902ca574514795edef982b4d88e6f165fc6f9293bc8cbce72b72f20eaed568cb5c049d64a0f7993dc3d90218bbeb1ce095a40726702ab4e8b60e64e3de58ea8b7856af655c2327ac1fddff4962e06222b78581b8e3d44ac9b71e55c63f3243de68faa1ceb6a9c607c2fc56ba39426aed3bc4572f41da4110f43f0151447cb0ca00a64c3fe9ad0303abd6299dcf12597a39c1e72b1d44a8e6b44ffeb610ac4e91f4548e0e4f7bb7d28f976ad03871e8d8335ca5520c19ed24d94bd1a8554b4ba42e46f65a1f2113a5dc1b553d07dfe6d7013aa009559281d25185d4057b2e8de3519a5f635c32f9b81d31d27d3d4602b68f5827217a300eb71c3d6d0b1d03416f5fd80c9bc6c3fd8c7e6d12f2cd14f7e48366a72c0fffbaa662ec9cfbeaef4f69cb3e27e6542ab5cd344b0538d61d12f08527d72ce73053acea103a7a9be4192b714c8a26e055235c945b78bc6abeb22a87d541f7a8f383579736d94d252e17b36374ef8c7793860fb8c21eb596721164606f4720ff8f527003c18875a7b16ef82f1efebd75270f8939932317ec130a002c84172b5e4b7e17eaacf19f4d1ac8cbd9a6f45da04ef66e6a71adf9e05bf06e1bf7e72d88afe58ac85344433026af332cbc67016b3915798362f160f04668d6e3eed08029b2e4975ed8089256367cbd9602a3ca0ca08b45c9a50f6cb7279d79b2e0f58cd420efc15d57a0851557828b64d66c91171b0ad153db8dee27fb6b09e2ef0464154a65f99e4e52a7b2d4b01fb84ca21849960af514e1ec7071f9039cbdcd67279c29155ccdc46363378072f731994edd85015cb002e9cebcdb88b60446ca15321b3ecf13037bf0adacb9b32af288a8f03d4f5d303747d0305e7bfd29436ff72c7d8c75975fba9c4e91d81227ef6719e6ba3e4c5c4d682b3eb4a87a9c73d40429cb2c16721e6d0f396bb3bc1911d6dc5e2fb4f8ed5580f651aea405362e19572bbf1a2c8d3966cc20b1c1aaa3a9fd7772cf23ffaa43f84e8046159ca222c427241dfe98baf85dc45198e1524c190bc3f8332fafe2b5e3661bae5e1192d77b172c90e4959d5ce78dbf8bb14979dcb91190417821f3084c2d79f84ecc3b1367d725d32abda2f3a65b61ae04ec4b82d2f2dd4acf513240c76194b43470d271f9121d7b01859753b37d5dbd319b27f26e71693cb1b94e46406ca7e1533c42f66355b238a34bda93d9f2e7edb270153187fb1231e1ee87cbaff7a1313801d6b6f60723e56f9ee075de744135f970cd0390b485f49e308a48b9efdb5aef2ff2bbf3e725c82e503c03553e3e81ca1fc4d43009ef57d99e4706622bd4e77a31a6ccb24330e7a7c0d57bcc9226548cb84ff62a224cb370d35d78586e8265b3e09284fb67249993944d4883feaeaa62bf6cf776c2a3448b78641cb9af068430e7ca65559720c0b49e4335cde0375ad641cc48291a5deb7b6d8f027e11fbbbb16064962667228a0005adfc87ddd409229b49361ffa560908f6308b8d38643c83a1f01963e1bf16b5712b030e4cf59b7a49d177fba14b3d08ed4ea31859d9935a4b30668a672bd2a21b47c3e963756b9b31f674e57e25bfd15e73dda010b0efe25dc17c3f5720d605e7f5342b92ed413554e2e2539ccd4ae488212abde456ea08535eba381720533f49e43e6ddcb8f504f63691abb1cb516387eebedfb59f230a03bb006da7280f986c54a89c668ad2d88d85bae1d278d05ac65908a6cfc7480f572c7e4a72a27accf81765471f24c6c9c35d9923087ed2896484d605df80e50344e5cc56a469208537bff8ddf79bde76d602d6ebe562b6213c24dbd6156a06b613b4b81d67261fc44b08777901060878da5c4a0d7e78b9184b739183d069483bd64db2bc8723dedda09a313e3e69bd08c6db4a53d3bae3ebe8ac0ca4e8257bc06ca8bb9ef72761332973abd069cfdc7d66fab9d3e8b4667fa9560fd22239374481bb7ce7972bc9dcd1277f3394bd22fa755ab7a6395a8d36631c0cd504cab90796d961e3341a559e3724c354c04c6b04a167e51f2f6e8e9c05d22d85954fbae37a772de2d72d69e57165f4bec08e3f8ab0cb93aa85dedfb87ef16b5860ac6183bce6ec08c72cd66e2cda7a6c6a8020a96cd99b4a962c16b38f368340318176b069e5e482272a859f16bce2499e2feb9cc856fa5e004c00e7b138acbfacf6f3c738ba20f46729df02ea967a7f70a5ee86c1acf1faa064fbdf9d45fc7d822864ba64cae84827222662900c17fc086fa631a508f7e380bb53fd8ffcccfb71462a7de700b2b8e72db3ebac75c4173463412ce40b483e832f90e53bd456109553dff4fdc63a4b729e3c8e7e24ce39fb0236c9e974dcf3a07956748b0d5a10f6bc7c81e27fba61472ddd3364ed56aa180ded29a277de486b9b2180fb0f75e5c27acd54314d0a2930d51ca81e5bd102027e7758ebbeddd3488b6e81c714a5db99225a14b3022f6402f42df680ebc386ae74a6cf0f8ba58c268f5038c3d2efabb2b07601519064cfe690aaa3856fbf7938f783670bcc54128a725d21c00a1de72966ce8c57ee7ec8b7204ca87db51ac2ea5670fcd89ff41a2de35abccc6510d30b82d53c56ef3377a62db828b1409ce7d46e2e34a1227e6269fc2a112faf5b17fe08a864dcb9d5e166d6224a1be354c23bd2257c72712a3ac9b6e55e3154c593ead451752872835c6721dff2b638a1c09f8f8ebae0bc748954ac4091032f8e95d89353bf54149ef1f7281e0d26df6ccca461fdbe4cb17e38b60e8d4abebb039161b7244f94331a948417a188c0fb3b00f29587da079bfb0264073ee5d3eb46849695092b2d38d0cb572cfe6b67e4402a268c8b7bd263fa81daec5d50b30bfb13ddfe44a47dcf43a79085b9fb550c6ffe8afa28f30b66d9b6e1f66132395a8347e87495b0f73a90d27559df49afd378ab611f862539b52e9b68fad7dfc1fc2aa874cbb34e45794c83d72abd45cb28ba80407bcc1844ebcf2108f9ff432ce420efda10542af81eae4d060811f3faa0a558e7f9d494dd44852c56c24b89e9248be8f9d1259f213035cd11a5f8213e6720820e640e50411cda753ee7d0407c09063b280ecd67e8d3a184a6ae9a804867b403502dacd2971fff6ecf01f2199550b6863b9699c6f35b240e7439de24ce3749276bb96e0034fe2a99e73329f5901261faf2a02b0f87495e0557225382a8746dd0c28ddb250b154cb6994925f253a9cebca65249cc14d76db72721b09f1707dc5a3744c7f87324997ed9a2efbaec28056cefd69798357fc95ba72777670f2f5a3109328f4aa94a00eab652e125e8299b2adc2114c327da8a12e3c76129b6e68caadc4b4950d288904c3349cc15d3c5f87a0e4516d794cd68e6272e48d9e1d83006656b488ac0af8404971f1a8dd3621661682d554014907159346cf5377283be5626c7cf10cfcdab47d6f02103a28513ad6af9d66a0f67b86393eee05d95cfd3aacd74227a4e8604c94ed14d12905dbad36013a42b79f89ab5b6183b953dc4a8101658e1cf6b310628b83865dfe3bae8004783d0502d2221db755ffde1b8dc9f903662d545ea95be528fb1020236b0501e97ed5fa3d5871153d3f64560a52aa990f4bbbe8917a32b998fbe84088ce60daf366e2ebf4757c69212c8bca983980cb964d575990841714344041d1690066307535eb0031d47be48e727e645d6eb1767e0963dfaa9f35505beff4eab20a19fad9b5e03178e97e3a1572778a0491f2e7996da31e177dd186255a694305fc69bdc8f32b13d568fa17f57278e8fee6f37980811cc0b317b31db5a4cfdd536d84c52ba5546c12297592fc72f69542825463c99e0a9da2ab4fef10616318ab869d90d0271ba629dffe57633306b4cfe60c8d57ba37d88071e6e5ebfa4600952014e7dab694439abeecd93a720d70b0b560e0febe4e211e2fd17100c915cfe785f5e546b56266d8aba7f534723bb89c0a25b489bb41ebe1052b134aea0e2403d87fc02e6e0a93afdb938f9272679b335b59917b2db52f583b892a8284dd5c2aec27f35b49a3ce64bb1271e47214941ed7d8c84d71f2ff56dc287534ac61a2cae634caba384facc78975fb081930b646b74590d7996f949b09cc9a99d9a7af0c069c03779efb1505ec0ae8a67262d82d0a8e08ff9ccf99bf6b8bb7c7c902cd727be5a70210c088af61967b08721f3c3402940dd1d66be364fbaad0f32bbead6130873b7b6d41f3f0a0d7271c11b8e39d57fa9221c82be1d2ebe53adc692ad42b2266c9250cbc59468541c0823b7ab8db911ab10762594db5378f2a7bdd6fe6a850e909d5aa29aa5efe3c3f5c72450fd43e4cb763602380f92048a41bc4ec5d302d691fe2c42160af864a22a9722712597c01f01c7f5afad82d55d31d45be3e2bed6d271a54fae088bb6850277231a9e77c9c396edab04ff735894fd487ec8cd3fb8662552062a975a787a6b7456d396381e23523037e149bd2b242f3c0c05c7a0631a3c3313983d104d867762a0edd53b405f9e51b2384d3e1e065db064e9869e0c64909d31ce048b1a72956728da867d532cd493e804b84b806880d1fde3033a5027aae4cdd3dc2f56147c9728013cf3e3c38d638e0ef12479a790e079317dbed90daf0d5cfc89dd13f528164742fb1febeb5c85501e71ae933d85f81554bf6df35d0e5ac9e6a2d3162342c724cc00e6698ae8199775957d6c5e2dc3d67120a5b90e959797b4d4e5561b9764a3c8b10cefb48a27b67f308721920232682be8f7d370c7c10a50950cf33e78472024318933fbee08bd2811257f17086eaefceaac024f02dc0c27536cdc7fca972a694d495d40f7e4d9e4c8ee212721a33b3101d6af472c9d2c53de7ff502b567278821c8f53098d1a0e33efe017471082fcbcbd6f0e8339629af9257118d13572b502acf1baf018d78f9a6dfa68b75f8f0f3ebf337f973e4c824ffae695451b033b69edd0039345fc28fb5500c36ad12e051ecb211f9bee845483495250ea7971f59264804dcd78e0d869f50f305ca3d248786042b714da5ba7001468be9cfb7205787a51092b258e0fde058ccff17e76a2e17dc7f35595024976d1174ba7a87238ab85fea8d5be6175a096f00d5fe203d128cda49550aeb24efec5792c24b3724bb678e98e917ad1cebe431fcbcbfd9d0c525d34d46d7009a0ae54a51819d448feea668ed0270a06ea18cb9d3d93f10b9151a2979e0ee0693737d2fc0e762d249fdf11fe08db1d86d4ebf59c9a145f8d77c25f8340b6cb01470d5b8e7c21af03343bfac3e12b01b41bc93bdce4149ab5954b4761d2e645d6e13aad170accff0f36a3a25544d58009a5827d57a5a8c7efb790cab9ffe1f55d78621ed818c43f578c35bbe906a2425813d514bcb4aedbbec42dde4646bc3f3eab0482ae219b145335b992b5bf25ea5408a85673dddb1b4189c1487bfbb6ab4685fc6ed27c94c757ddbbf4bd42e9ed8a00b044b88b5fafbd7f95073f10ba311d78398e0a9636177298e5434c517defde46804edda3cdada99ab1f6d89da10a43bb3abb57c1d10e72b69a5ab047dfa56a8fb368f5dea919bb01e7f16cc04e25c001f299e949b61c4027d396ba24a3bd178eb27ad76c4260d2738a8c908fafe97f072a750768920372e2259ecc3fa6f198684ed62a797156879494bcd1d17cc3cc4733e44b93412572bfbf861f27ace7b11712a9f8f7be0940391ebfb8a26396fee9d03c5d130b647283748a80faebbfb204dba844daa4564b2a2209264f9e51553b1cbb72b857794de505883a8e8c1ab6a30b2f0e4dbc3f4504354801e2d9c750a127c567687f151aca989d51915c7ace08f217237ad929f8502b57a28ca633c631e6e9838623e072644abaf7c7aade621df84d41a8c7e8adca85694bfde5ee716eff978c0b80ac72bd34830f3a15e090e5282322417cb5284ea4dd9330fd395b790190dc1c769a726c46fb97b9cd75a03821c842a9b3ae953b3ca60559f4c8337a5b9504a385a328f0f35ead9c6b0dd563713ae79b85b4331a503d50e11aa0d514ef26327655297266583f67bd82e9aa9aa6a04e7edc8bc097d85578752a9e4ede8f3918cb3aac72303fb66c53c78f6a3f39c027d9e1f19d9685208405b0e851fdd12ddaaff16e2ff7fccaee890c08d8e31f6fedcb8d0ec164966944b4c7f0937e0d755ec5749c476d8e3f9b482854d5b10ca32f1b8ff8af85e67cbf886daf0e2c02568a11b1d9724c8975b5e15171bf74f05160002000265974c7e03695484d8728d785db7c774305876a1c8a916c62ca27e7081c56819dec8207bccfbc9f13c703b3600796ff72835181f6e2bfd63b2d2efadeab022bae668f400278a0c995799887c250d1087248f8cc3dda8abb792b9be36d17091e4ba55678ac02becddabba4ea0f3b055772028bc2ecd650f1e6897d0f57e49f0e55021ff5f145812b80a6a522cb53eb0e5663a2b8ed1c41efe3bc7846320b9dfd2d0698bc1e358c21ed303ec267cbcc1372b64f668f241f53e079956dbd0bdf23d52efa951b477290e87c86028dc91e3c60e04dcf2f1f25feb455b5d75d8e436ea9604ce446d64b1728340d05814db91272333c40882788ebc3ac17fe5879b776e3b9d04979cd4df514a9cf6d52f4eb2172c6d56d48572b87294f8d07f6bb71e0da612e03f62faf7aeb67d6cceab0c71c6cae9808bfa5d649a0934424669ef0d6291847ee49eb4540bca2150b32834459319b01d5cf47b22ad1a9d13a9a4a67c5fadbd6ecf9da590b763fd5a813ce12a01358f943edd6c7b55830287e4559a0d3b563b0c99ac8bc3c2250c9b41a467ac9727e6337895628bd485082cf2111d4f93656c19288471944166a4590224332467227fbff335411fe14e3fb2409bfa4d58459929fef0cfd7936dc59abb6010ec0723123159175a68f389ebdf03ae8c1dff5285c7a9950885935ef9dc44068880f72c9eca4764dd0090c2b09b5e19df07b9ac45241ab5410a48683f7a592674c277234753739175edfd785346038d6780d3698aa1d8c68b4a8e94d1a74c54e731a4a6666491f486a76cd774d99ceb4862914aa7d238fc10e072c3dc8ba0d75d5ea7229ff4242d07c08ce2d9db0508d1a7b9d1ee3f46d963d0a81b96276525293665568722d2cb44c6fcbba57d40f581efa90f85097e3a028874542e0a04e03838272ed0165fd58749e0ca8d47d0a80328b1dad5e3b3b8e2eff5bf0fa97ebe51edf729501b9a24f13691961860afbb8910de0ee25d9e15e184f7bc25d51fd61aa801fe6480c9a4c9dc6164c62a9aeed314892d89b61374253d8f4d14df6df9a8c7f729e455456ef6794ce4f171532d99c4cd87c1232ba86fde591385046bab7bab40d79182d6f70767c750d43dd4594e25041f5890d6dcb67cdce353f723f146e65436073c3880a5a6aac39a72e6f07e7c7b94249eed45b0dadc81f53f38e12307f72a785e07355d80746456aec39ede7c1586f220e5c81368422070f4adba523ae72ac3ff6834ceec9434d2dea4b17964fa50463bb8fea0c956703e300790c396c5cd89d064c2191dafdf3cdf90daa8a70eb6187bee340851797f254fc671b8bd5728b947b036f5137c3476edada53278a291376a0a8105793f0f42d1ae0133c3300d95aec35849fd14af14a987f90061f466c72d5894c2903d30b24d9777f50b5726dfe3826f44033337c7b8d4c2655c270da0ea52b86694edcaace05621db24b50e06966a10b96bb268068aff9f6bc6f706dca1a7bf2762e8e31806601ebb13e3b3e51b9b7157a97e6ebbbb53db32d5dac4d544b3814d8c5a8a823cc62d1203d72b97f5361ee60a0f70735654c3e09938bfb7c44f008ae9b7e98efc7270d3f2541d234503a90e99f9f0ebb4209ebc88e19012cb24001bdf63d804e233b578f893075986fa31f180ad1aee27267bdf9a95514af43eeebe39e2c1462891ac76fb85af597139003e507f7ece1284346d5ad5b942d24ca85851ad5f2a882c7557df772d747a1c03b808245d2aad73a9039b642a9ec895694d834c38abf5d820f10a0285403a951a0b239e81f0f8bf6edc97b201d74cb285592e3de33ff312c911ac7724050a883f675f781bb4ec8e3532c5cdc077b3fb1b19ac17b536f607f7ed9321203f985a1c7818db81be906858a5ecd0addef58224e6208c6e764e206e1a52c2f76af1afc7ffb8b0549d5b491f483fb3301f1972ec2a887eed669289da36801728f8aaf9bf40750789bb57cfd8693281f5c81e391f4749357c875cc9e645ba072b2cd7e60cc7bf414e8bc901076a8e8da4eca1f88ab99ad0c96089497e12ec3727d91b1a126a080d0ba8b81e145bcafc036124047d8dfe304c5d8961c992cc772ee852e93343a0ca18daf9f979fe7564a8f02f4f8663e1085b640319e00423a70a2c9cbbfc81e3bb8f2e852ea0ddb419acdfc1fb526d30290b48aa037bf32123084ebd386137da7c594f35d2c1aebfaebc8e91de7d4c1b7f4f1a5eea1cf2d9d687386fa10f1a8c87514aa3a2173fad848da26d629fde1560b0de898e6ba751218aa906cfb4d528ae94380202575ce2cf9692e67cd7d0644ca66d17056b0e1cd224a4915d08723b672a1f68791c4c8ebc515edd6ceaf9df820a38baf776dd3ba7258f44961b95d7204205ba72feb5cd301dd1414ddf4a3f368ad742f8e27f66263f784f1ed54fddf28076634c3f0d930fdc7efebf7c23cd6b225eca61103fecd72b4b0e65faf0d48f0c28db51ae8f568b2bc21e68a2d1678eea977b7f7f5047072f6570d5407e4782739ca745fca8271e5bc75e79f8c41f508bd1f1e8e9156894d24d7f491acf113820cc1416a2b2ce2447e5ae27a73f02448414c55bfb5309972c8e0592eaac62dc654ce124de7061fc19f096b7ea2fad1724e75a3b628888b72de509900c039efe455244fd1a4bd1ae0235577354dfa43af102c31a7d8e9ec72825d8e3b33205864e85fb8387975719686c8dc7e037cc9b169927638a0bcca2a285ae1cc8af0c183588bcfccf650283a8ba662e375aab21261061f7fcfdd64722a81e219281fe91e11046e4d1cd59a7834023a8a34e434ed804147c1c3110740a49523c33db8861de07e1d660b2837663a94a31294cd6b7229ccc44e2050220814a3338beee9214567dd034f7b7f80f774dc10bdb781e5409b8cad40fb0c9a2e6adf0dbba35538a7bbd403ef95d8f8173a9df98d167bd63ebf7c3eda5a390d69e2cc24f84f6ce46161e36b942e92473b9a65a35d70c3bf69cea758e8242cd07238fdf540edd532ac038a9153ee6a9c6613262efa9c220a8c56050a3fcbff07195d6e61d87d0ef1dedebd3690ddbb9b607642b1b0cec3080da86ff8a2a815ab728147571f77997860e2359ccd612f25b70a90e7e8ac4a51cbc09b4e9a51bfa5720df5afd181da7d190276470758c66b74db61ab165192458e64eee18af77dcd681b0a45c4958fa11b113c9b7682ee2439465dd70a63598dcf5e79a32ab8df855e5a43dc0d466daf4bb986638d2664dd32b78076fabf5372e330c480252108c909aa0b9fee989f788c3be98f1b8fc91c0dba856b19c110c1cae06f7e177000424d54907d2446a36cc9df093d567fd116f1c3b8b90c017f593614e40b83477a2972f611cd8b60a19105f99eb96e0e7f3e3262e20e165f01063988acd251a5be1a2752c298ddc9ffb2050702a31d9021a767ac0986324fc46d47822b798803e0a67291d1389b661a515b3e9c501723ed319e9e6d4be01bc84802fead234bb554e2728db8db55e652e710e8f19acea74d456f33342c432c4befd2acc02d1897a16f3c7e6edc83d27522abbc295858e9fbf785f9bbf40dbf856ef3af207b9d13d58c723d1d45d17dd31cc46e252a618cc62c79f793f276619a5ac67d1434f54c1e536885694e6fc1e77f0fabee3df521ce6d947eaf8f35fe8a342eeddef1965dd65e64f2292fd1c5c2c6f62baa4354b9e44949e03bfa3f0542779e79ea0480bf658159ce60aa7fb23c696780de704501a3b4025c4de1d46c1d9e3a3fc3009df2e4cb30d7ed1e217a454baced30b02c5a4b5586eb012d2f772444dd64d6a34f1d807131894c502ae7bccdd4031af3441716bd1fbe9fa8612618ef630986ca07bcc7605bd7f3d3583be464ccaa9625396a5af5a9f4f1ffcc64d7e33aa538c9428a513e7237bdf1382ad04b4585e9c18e48154cf6b90bfe9834548eb3e8d4d3e9f566a712965fc6d891c543909d353879d8535333ac997cdcd331d5b0cd33d536fcf65a729f1a4e2d3f99b1ea7b57d432ce3609538de52f625ad41ff60686fd0919566f727cedda2e30e622b0d173a4c5794182e03f508c6af46caec822c274648ee5c8726bed96eefa115fcd06e11cb4b2b82e7e3adfd7a9c7c586238c9413cd01e0e372f71dbcfdbaee98510fa36bab4ab7a9dc94c6fc4314b590399bda8b04e756f24f86b0ff1fdcc25a0a4abf1530f3c5665e7bfc483a3bca19ff14a857cb22761b3bcdb6457b6c4c7ade580643ec40a40f57c0eed6857085eb8c16f89e551f99d8721016aa284e6729e7eecce0f1fa2a4bdda1021dbabf49c84825fa41e64da5c972176763e4cc70234551728b67376f292e7c0425878f017c6e25c0f5fe54a61b40301fc81ecd694bea06982b9033b8c70e87b7cad7134da76faf8b2c159361d340a22731d55331feb699264c96de753663c7cd3aa836909dcbdfcd1a659d869072f7b9330b1008d8934d8b8723ed55de324376710a696b986cc1244650d3a5461aef4d77f32d0eeb62f818daa6f059e7199e9969dd95c81a0e3918a3616fac3672a2a5138f0d18ac7956be9150eb5a306d0ed22f16e1da69ba13c61bc21067ce720c41fdb22b1a1ef8ac91863c67190916f50a409a89f09964fad6d5a2a939f7726539fdc4f10af22ae377d4e8220f5ddbb2a94aa7564e54756b71a129b4ac0572a17848cf25457f73de901932d23598bb5b57cf5ff9d75927152ea587bc439072d30d9f881958a95e60314be77c6ab1c97f6cca5f7eab70785be9cc3e46afbb7200d278e9c97593c2396746c97817a6fce7038db3132a4434a3ed1f2745dab314388dac2c0713c7c1836b9105b1aae046d56cbb315b384a105df6853d13173672d31b841109a2ed3e0ddb63e9053fa14ee741ed783a20e8f1f5d97da250a90072f23c3935dd9bb5c042ed17a5c9f1d759d7412ea1d046bdf2d244fd3256505e014e9dbc8ee76c821524720790a0af6ccad9a4e2d96f49971aae0f5b88288fa45a9257f37e9a65e0619c7408c5a10a5313636cb4c21d8017a71ee136c53cc6161365fa519f04f8a624fd4b52811b55d757c8604a8260d66e970ab1dd4fa302a61508e0c49942777aad35e5d78ecea8dd4e31e5708ad6e27f6bf7d385d6f445cd5c28e3f2733350e2c6ad4c65e7876a22bf243b762133cbf114eb3df1403d5bc7724dc4832675cd04d4a958505c6d809e5d8074481ba5c7f75da5d28b077b417f725e8528d71edb84d117717f11ad3482ee26a2207c97a95334395f17198988296fd2adc7e2097b28079a7ca830969be957ba05624f308e8451f1b94cad4dd65b189617666554fad704bcdf4ddf0ad1364f7ca979532f8533eb89c1d183c8076f72752886fc08750ba23a657a0b203b9d9af4415228e87c1f22c6768e7dc661b672e51fe9a04e4934308ece0a51b0cb014c098492e4bbcbfdcee7f678206f216b0b87ff2b1307a3c50393a4f59c8dfab87a2c22aca5ab8286906b514c82557d9872eea64514d27f9cf1568648fd67434d315e44501753f403ec7cfd158341c2e55979c5a32f0c07f12d7ed14c76c933e38f055b838d215c9f0199cd16d660a646725def2decb09e78a4136da47e8e6915be096403c30b84ea5f2387500621f954633d31c3fac15155e4a3fa6d47e6b25bab6bd98b95db50ad9dcf29bd4243e57a0204dc4bb03ec055653c2ffdd93e2322eb42555f20c02d21c390520b3582400b4efb3c3a9a48d079b683c2ee3c32b01cc357ef51ab926dab04bc850b403b5eb77254b1f5cade41e15b117875ee366b398f555f1ddc4ab376f539b3494be1f72033c046e7616de313e569b8cd7f8ea324ad9e6c54e7ae3a5c78304e2b9f55779d3ec31d96cfa3d0d8d967dc871fcab6880eaec3e38aef1735b9d38ed1126ccb1b64991930520bfb0bcc7fd43499493696e6e3e88b3b4d3e31a048be163faee2c561b28e9c19af7507f598cb2a568d20c9fc2d5c1db8b2660ef50de0d1ab3dc10d72d1715f6779d70c7d4249dd6d187d528b56ee7fbfd7a54fa4591753d13dad92727ab6df9414876d17f1bcde83cf0953d06ee495a6befa5a0d55b4a5c13a142f72fcb92f5d7d4226490d475e5d1607a5711cecf351ff82bea4d4cd58464c66552f3075f7842ee4b6e54bf3174913a7587663a4f7f3e74f5222160f7d201ba9c84205233c9043ee7994e796cb706c2a706892ae0a6c231478b38b28fbdddebd94722155ce74ba17fd571aff35a18ae6d1eb143d3506342053aa0077a6824df10e72a243d4d62eff7877fb880139b3c60bbc40888a5d76246d69ce51cb5b4ece6c50bb342d1872d173ad09989b7dc8f68938ba15dd216ec54d42d37d965923f3494a1c20b37eb8d502c82835d15f4d86dcf482dab811b6f7e2cc5664a70ec533bb72bfee0ba408e2ef1eba5728e936a42fbcd331d9d9a578a3b32de3bc2a06e225720d19bf7c7d289657fe4bdda5a909f4d6a7bab357dc5e8cbc7181c07ca559c1728bce670358b50c1942b5295525b424870948ff99824ea6790ac24b42049c561e76bf84d55ebdcd8b39a55ea635a90c2290bc69670798a9b03566873cf9574a720154dea552eecf64a692a6d60b185f7474decc668d9455664888273d98ebce4eda12203e5b9d241ec419323e3fc2daea74d939c66fe170b091a66e7dcceb8b082f3d56d971bf7829decee05b8e41eb2ae12b8be4f27881ac25756bd314375757f9afed7abf186ba44f4555df442b5b439010dd798bbbcae6780e84f01d4b1a52648797a52476ddc3fcc285eac30cf7f106a3d72a1a6f5bb540ed3b7e6bbd843d977ef08069611dce95efbaaac3bd963fdbd1b5d5df17ccb0d1e9243cbf0b5a721d1436fe59051179f47913f7da3365dc84e4152389f1b7213791fd47ed44a14323c5129956fe2f3b26a8e371d3be9dc9ef2042a7b7fe00b23d76ec29c0082202bf87eabf1531c1b34a36fa2fd85aa701e11a8e64d0772d80bbc578f00ffee919482202c40abbb4983b4eac6367d349e1c179b7b8f05346a8d4ee7948df68f37241ad5eb5a44db2c1263eacefdf65e902724375bd77e3555ae2e4f9e0bc9f98726113f58c47adc6dfa45c3b6a8fa7dc481d74203cbf716f3c60defef0805efb01c1571764fc382e06e0c92eb62128b2dcdcd2a0b6e12897d30c537b20b4eda571e91a5da23648973cf475a16a2d62ab3708944b1f342dd052fe62a415788700727e199b1c7f5ba2b829c3db8c72cd1159d315f37b02dbf4ba32ebf47e51ae7d726dc7e9a975c313b7ebec86842d27df3afd4627b8a3a59f0c630c52b609f5b172833f6a0573f49e2dc5504205d2a23c3cb475c15eb061a02fea208368dd572972c8dc0c3ee581ead34adbd3e1fd717bdb8cf85b35973794482713cdf4fcd7e7675a7ede91fc89157efc7011a3d7109e52fe315e447187262c8dd4abe420616972d2c8156ea5cf090b44db5b06d889a5fbbbec4b1382b23c55960c3bd636038f725b1e6352075a18d8aef79b3af00d49ccbae54e59a08f7fc056c6db97d3053572e7ce4390e757b031c212221cd52c36c3f8b442bf82070a7d962a47df7e1d36722385c55175e1bb48c95e00f5c845e5fededc22525e27bd579e3a7ad46bd6ee576b6e6f84b76e3671195d100ffae09314b253a7a2341c7e7cb79c2924e638845dbb3cab1feb5319e5037633af898c1e08e189f3ab5800f7f145a435e360bf9d14476e9e1150b33907013823bb15fe988132c5e9d3494d1cd616197124e0f22e724057129013f64d832c994b31b73b28ec8a6be39f90985e26333df3d8d59f67721f8b431bcb9d2529d774ae45308932b157db6c068906e886b84b954e551e2e2676ce59804cde33e9b9684a99c92fd8a3ac1adf71e917d7d6847dc0c430b6d6722f82920c6f60efd111c14545d08cf25e11cf2b8745385bbb01d6c399fc69b372829566b59e6792e8c9c59b4a34a2dd9045deb9ca0533161ed355cd0526a842724a918e2183d8c914473c0fd966146a20bf071126907ff5998bd518bde356c47225d857a07c6606d5d7d12b4408f668c56356e76fefcac00a03af3097ff913a72dff4d2ac95e907539b7da03457cbca56bed34c2e3d45fef8f8d919aba9eff4027b0754c6a7ca7c78e4530c087c9540d95a6c8e82b01bbcdca22b9454a1ed4a726c6e46a2214471840ff6190c2d91db0d5426f9a0808e3a9418d04e6e1743c4728a2a32f2d134789bcbdfb6381956e2a1f07d6545a130b5e8fd9a5224bedaa64eefcfce09ae383667669660a4cfb01a70a313053c8b85cd1a0de542fe5d79de7218aae7d6b3505c759ebff07283f98fc46df1f58baa5f014ea5d801dce225967213e6711027cffd685053493cca6b4d989099e35f8a1297d5d61f190fccec2672ec8368b0b2551a9b89bd26eb0b228cfbfdbe69110e9eccf36af2933de7bba9721d625cd52748e7fdda6545ab6288aaf6148c0dce201bf43968f3ead67ea77408b68d0a5671f00af060eb973e77b1b435eb47af4f540f71f6a734c41138748a724cd8a06a4e13b577ebf7d60d377fb9a6789b450cff1acb4853f3fcec68313372f466d95bb86a02f8783f74938640d6d12aa8fb56dfb292d19e6faf9f90bccd725d68ed634e34a2ed28261687b7864c6a88fdf46a0ad94153b7bcc3a160da58246558b529b01577414e63dfeb3f886b6669af6d9eb7399bbbac88a7c781c3464af8585a7661e6ecc3a03ee3eba22443bafb3dedc58fd921166b04226918eae672be47774afceeeb49ffff009b7feb383cdb78174f5098525a73b860af0a58e04887b49c6135c2517056c4e298c0845fecc34b89f7b63cb5b1d44a8dcffd63183ad58a27c8706de36ef40b22565aec778a854e1297eb4d8fe5f549de99ad8ac0729694c4fde88bbe78b744520e18c3e50a7ecbf2a9a6434009737ea334aeccb4726cc87d55e3ab325441b8c200c2641f83af8c36349dfb5f94c92422b730a69a4079dafda3363faa1a31a76298b3ea47c7162d47b131e51f6e196aed81c2476372386f3c7c3b22e9b9345756a38051c527bffce39789aa1c5545b37c488e169f7223cbd2dd2e4d231d9a5f0033a4f4630fb83d3a08dbef71865d89c82bbff7a672295c8d96146e5bd022342ac93751cecaf15474282c3ee32fd76cf3b7547d8e72485c39e068f786c41f4bd4cfa131d3dacb4c6dddad05637a1d179bad1b517d72e98d2caea76467b02cc9ba3e58eacb948f7aa7f9663ecfdd7ed8dae525dcb3562dac05494e754f2c5a4b370c4759e0423c08eb4118a36f57099e9d7d770ec472a252e9e10d1e3e100b08826f3614e697ad9242f1a0f31836b036c4b946ff0c72ee7317bf3a419535995f2b64f941da6dab9beca7631cf52ee014cd3a58e8d625c4c98e7f2b28b6aa30b744a98148cf03787dfa5f2a185ace59e3fc7045e93072c2cbc8c77fceb7dc76ee2ae4522367345bf863b9f8d5507227f241f26d09d5195c8490976ee3e63f16de493c9d9a52bd6883457c88a4fce36f4d0211bfc84d6f5cc4d6441532d403490dfa66dc4a5776f6df9edf9138d247be31ec1fdce55b72631b32f2147aaf4992fb4ca0f1a1402106785ae471d88b1feb6a29d254140b7278e13ef1ca6af5b603932d7304304c51897f9867086854088fef9abb3a48a449b322ec048ee88c30b59faadb543b4ee66e10672e8d567429bca1e4a6350ae3318bab50b0e44d5da54dfb771c5c9074b4859486d314c37e2d468cde6f93a5f8524bb3e26e5d51a0972e20294ded17a9723098bb45b43f25b93e9443e6d1a04f1bde6d0b1cf9817a1c03cd4fbfeb62e0fe340c27dfa46ef1dbea067cde9dd75372b2230a4959bc3b4ba16545d95a21a761c6f21add346a41ab070d70ba4f10b07228bb68e499bba03df667f658eceb02092bf4ac0d0079b56be689c7a229f81172660d54f981afd6f272bb3c26dd61aac08ec30f61c8ce00125b52435bde9192720ac84bf4e8d3a65b5a4bf5aaf2c1d45f9509f969d5c5563e5bcbd4cdb639ae72165732929832f4ef97fbf915f19d0b9d93175d0c5d53a912aaeeb956a94c3672e7ad30a7107f56e125d8b675e85b36ae30f6e974455b301167a9c214d3015e2d7519a91eda46cdc3c38c6ad777bd325ab12b4588fa753762e550dad9098fb0723b53dbc1436b8be0b6ab3c06582b2a88be1041c9f1ee0b8887970af0224eaa724ba853441e8f8e5b7749030ab93ee418cffbd5c0ffcff1f48a889bb071d01972dd0f66a849b200a79c0f24845821d1bc1761c926e8f45d97d1e83edff2bde572605cec5cef7f235b94d297d62049ca1260f52f826664f9eb83b9cae50a9fdf7257bf68abb31bfc0dc39f4073c2e87b5f60f3c48c70915237c3b30bb86e6b0f722edf19ff35c64b5149d46045b64349c9b67d88b3f4e92f5768b556817c148f721a2496170deeddec8ee30ba2b842ff313cc65fde54c3edd4f8267aee6fbbf74ebd862b870010cf82c3e0ef1ac7742f0d5be4a980b62e4c500674a3fe21ca2f72e3be5e0a4a2d669dffd9f49fa73eb44733b7a4425f7014255d3daae5500f39722306dcc914ae63aab8495264ef620d0b13b6818fdfacae862a7043affb1f01440104d1969fb27b72547eea76fe70805f6fc3bed7eb5e899e5dd1a2ad7d8dee52825d5d2daf0854d61507c0d887f969b94c94518885cc92e33933e30ada848d2d1ba32f36d0992b6c1d0291e8e4d77f252645d99da9625c987608aa990bda266b2993850309c9900a444b48d27a6b0008c9a7d75abfbae699cf5b9a8f84d361725c05cba0f98e8fb9ff92d5084918b6300273c36bbb76052c8ad83b1fada5f772abc2310afbe14320a5ff85a4c5aca13018602910b06b26bad520799d9f58e77272d8522f5449636e25c8a9cbc359ce42e7458df4ded50d4206e8345b90223972ae9f2fe3778ced2b2a5bb6c8a1acad62258af079dbf3bff87420d7b25cdfc5728158c499e33169a296acfc8724e49013ff5fed96c866c0e9a0434752da3ec5728f8280978205db4325a7f13428a01135bd6f931861fa8a59eb38b74dc0390d72ead002a61845428c80cf482f11ee627f2b762d2abf480e4757671494d54fd5325da58a7ccf8576490d73e64440440f59a75c0ffb7923e5ca917f9285c0fe5c7242d1df394ae4a106ab68693f31d3acdbf9c7affa103c287c15af681ce4e4f50377d5106fbe23c0a1098246bce2e2c8f0d20d7068687475dcf0e4a680e8715932d80baa55e37f43b717db57c156e9bd755367ca1f2f29168a88a2624b82adfc6ac933f35470169a100188c232f7f4a4b1bd06f08491ee46c17e4cedf441a6d9727ad66b4c281d6cf7e5f284ac2ce72d18e7f7f22694183b8877315c44c6cd9e727b8d3ab998a10b973c95fc72b856ff1714a9370927a4aa4c85ea6150be330f2616fb87409826c3a753e3a297f4940291bed3feb7a674c13136f9a802454ff572a66799b7bd3b8b7561340a711094f3e424f28a3ec0792415a1f6e9b5ffab0b195bdc4460640606a350ead7164ad09c52c16507c3f9192d4a46946bdb909517404ff834399fad36a197cf944c0189313d2b1e797dd0c59a99ba4b14c84128324434cb911d6286278805ce421b8e074cc62dd62211ff6c9a33b1828d463275ae726c956db3b9b85548ea132e9b9285609018a01c8a7e64d86e367b33616016587213a2df1a9a45bed9521d6f1610a1e7a4094ad9ebcba18788179bd13f71fc43232639dba40fa8755a161f5ca63c439efc11a4af30734febd3ff3aa6d0204271421fb9f68a5f33a00a50dd4061a44235e8b6d606e227bf2add00ff6318d2a0004055ab325c6bb69621b7835eb91cebbb2de3f9a3fe29fa71b2c6a7295a751ba772f423906c8941da800828248912e1ab08b1f4a5b319953258be5889206c2c3372e59f6ffa1dca4b790e18f6eb15d7d4803825a505b105c8107ae11d139322c972dc8857370bc7c2647effbf15c395f3a894f8cc6dbe5deab7462c92a2ca1ddc72a7041552822d4a4121814f2bfa09b7f35e2f2a28e60819222565d55ab2ba5c340c35bafb59ad91d729b7e47cfd66b540fc44cc3e7dd939956e3fcd871470d0489f80e52badc9d985f6475cc4d87abc338d2597fc3e13dedd55b71a684d4d4155a31411f573600e4061094d473c3530f3e83e009f59d3fb88962b478c97c6ca49bb11976e2780a6277e80243d2ebbb946593f1d30a9d7417b72c01e5b23d52272e139acd92183b67f76d5fbf9f514ba38bd6b5a01e39e134d660e0814541b607222c46acddeff3f8c234b6414ee05842f9aa7fc6b12b7a6d6a9240592ba62844aa5b3c261785b18aeae8dd7b1b44e3618fc0944dc3cd44340ca32470d9f79d9720afbaaded841348f2dde90cad106edcf03e51acbc20f840f25ae163edbb7ae49cc6b9c343c2df032af334c25b4203039e97a1c5f7ec6af311d86367c225f02726ae7b968a8000139a81f7e8569255c37a50a739f2c11c87044b5c9d6e7194b6a518753683baf46ef622fc7f9866204ad8716f84fb766b0f8a61ca7e5bb1de172d09e942b5b57ce4e3bed9d01f4c6c3ba03ec1e70fc6b74bb68fa5d432e457972afe7e3f2750496bf42bbf3a398f73ab8b59c0e5793858233307764bfe611ef72e49eed0a0a3cadc076b72aff9f5726ca1c5665f8942520e0d2a3e62ce9e469011987db93870eb316ff2f1eda02df47bad7f1923c00af5a8ba18fb4b4a4060c72f0e00cdf67d38372bb1f29d4c15e1b67389f715b5269e6215aeb5c5ebd540772387d5d3b32ebee548eb17e8b45aa525e36e6e25b48a142b8023c740f2163152f101216419a5366e3b36372791636708b5638f7b686e0c1e337355136b2cd6f72c5f0f88ee5e86110edb71bd5931de0805dfbbf6ba4c1c04832d0964b0893a268c5a7e52e16708aac5afeef4864d8418d959bfa6f2a5ae8b07e0487094d25ec13ebfdbbdd40e58a32cb8e7d59192732aef129023f88d7cd90febbea86bf0826377306f8791db4a7f41299e4bfb584d17aab0117787b3e3dc476277057d51e1572e1fe25d77fc2a0b1686225826becc4aa0804c17561c456f18cc58bbcb7977c45d865e23bec323c43ee9bf0e5afc0165e2fd1c07d268418395740fc14b3cc4b5b88385fc189727bbb6712e7b427c6ce436740f2dd462892c40a73a34aaa4be772430ae1e8cfab035bb8d1ef2b305aa532b9adc0769107f4060df69c29688fb572110471647bc6d18eee8eec3394c88decac3b9a32a00b3fd0c212823ff59fbc7226da56111c0ea46b5168bfaaf43e5c18e9066425de9900b71726a28282bffd69dff16e019e1c443c357da140d8aaa79c5b7ee0b7993e488533c28d0772ba210333bc432c29f4f290adbc9ff4e15e59e61234132698ce1cae93af2069cee59c1dc0c4582eb8d945604149cfc685d339e71c920d75cfc48cfe67ac3874848c4472c0f3bd08e551cdcbc6581226a4416d7b408cf9650f2db23176c561216a521b727bc6abf7a3c001c3c3c67c12136908e83b9480a73b7d955ce6186c472dcb3872048f824d698e927017ec390aa46e08aa556a36ed0a7f7cfbaa3b5374971f6972db0ce20d1fc5bc7c91900d762eaf8b27f84d85e1cfbc63d922132a829e5af8139d8618aa06661a0a01e5edd4eb2f01df4eb2f64e2a633809c119c64d13ce5e2f09cd6fe7c39d2b0ade917b4c9b9d653b8c5794715a4f6f6f530626ebb3161e72f343b9ac4ce47cc6412d523ef7ace218ac7b1ba11d5b630ae917fd3f19beb34357dab1b7380a1835681b02105c3a6d8f64bd4c80aa26b579843ef00f35bdcf72313c9be74f01d95966dafaeb409b2355042771eb5b6e60e3572bff0388a0a7038fe2fc4704f587a1771877d05b7ffd056ab08fcc82ad4042e44279d5bbcfe772f120afeca9437217f32caf8bd9c9ea450ac72ead80f18e8f8fa61aba81c57b720a04ef2b8b69a8504d8d9bea82069b6441c3943af5822779589ceae772a1464857b438af5fea809be22e52c3bef6a36dee813c7f38477b26427ea0d3ec6bb3724b2a0d49194783a9581aa25196dfa1def0a535a26c7bea98fcb8af693d0e68722a1c563b3b37d443f425e31869727ca722c69fee72fd962201e92e0430fd55723e5e71f2c6b70a70132ca1420f7b7c4fa5caa552accf4c80e2332d8e0d0b022bcd533177fd48e1288b6669e278d0edbd0e7e2c779da8eefa0dbd936ee8a42901bbc606a9158859b3b4c880c10c9cb9e5efc0d57a61fcc7a0f50f0c01f6414b72e27805a55a10b3c09ea9e339209edfc2335f04019257bbeff8ff78f37c53ec72913e8dc723176a29036ac02906b9dec94efb38fda918e5adf8228bad9ce02d4af9c2a931443bda6730d1e6fa914bef5f2a0dd30762cfff8c048beb29c95fd272f73b14c8d58e0a1ad8c14c386c103d8926294a971ce3ad2f13acac2e0bd1fb2fb04b71a9a7411cf5eb2f50e9d7a0a928380531cb6e56a99ed4a4a84742cfd7261d8415d90971737ac2249f512f1386156f3062a8c1a18b0961d1eda24057a8473dfeb29856c3eccefbb97d6283ee53a9d1c6c8cca890ed62d3a754c29f0ae1722271c92fa7fcc45ce5c2d69e53c0371ecf7ac1dc0d292c513766598fef12f6728a301cf2b2366ff7bd7220eac8410658c4cb3697595434af405eda3fbbba3972aaca91c34bb7985787784681c0ee2558f75d49670f42400644ef14d92c2eb300e8c76581d533f5f0723189b242179c476c5f4acb6898dc72ca1e05fd9a50d52aff9be2923237a046a6d8654399fe2f85786d54b146a23f7abbd773dc086c6f413f8115d4cc0a1091104251f2948e03293825341be6035b9f7f27867fa8ecf772e95e5e27a4a43dec22f2f81745505a8372cbb1c9bccea0df1172102f9c6edc0f21f6c7380962359d277a357348e74e5027b2671f5871789fa9594021585d2e168a543fa5dfdceb465574e3b5abc9922fae4a729f3e7d142a21ab41657df784316a0c3c8996f869f240f17fcc5be76d8691bca4a14f3a2e10af2957b69c6ef31c30577d2ecec24926fd8a8cdb4a782819eb8ed0e56c8cc9bcaa26a35737d8357280c6fae899174d4f32f94c6530eea24b13dc75a410e01994fd9065ea317af30957ab9a1333ccd327bf0e336269eaced49cecbfd5a42e9f1cbad33f8c07e5c82133cea26ad1889d5d8d4f850c883ae8c988d4ba78e501ac3d0fb0781baddbef646cb2ab9ab58180f2e6500c63f370cfbb222ae87708e5a7b9eb5037487d6b2d720cc3899fe6d311c3276730e9b979e89c22d156dc6c4d3535c40e701bdb7dac72497486148aa6011ea3e5ee944020ad862b3beffac85b5f08e0443d377647792542b63ddab0d4c18b7ae0472a27a19b56d44c4ad03b4b57fbd60f9861802531720c48ec6a853f3afa6ebf2ac16086a40c4c252d29a001d5e4c6f8a2bf46175407b87fb8844ced7be89713c4665d260314147a3c014f442b7006640d961505ae7229fd143ddc67219f437221c7c7c0a1b00324aa7d6538a8d9283fa1495fcd1e72a7ac63fdb98e507147bd517b332b1ddb0ca9d64c55bd4d58390d7721176cd96091e833548a12ebdd46b829b79a5d3326b1486d38febd4598b42a4a4f29631772544ea8bc1b34a0247d23a95475a6fe2bd3cc9f30579e636bad38ad5792e4b072a1ec672fd2ecb757be42a4b5c1178d54d7c1fcf7d3fe63eea24b40ae577cd772fdc646d761880715a5f0e3e8922db2e98201032bd08e60cc006d877199ab48728f6b361325b42b8bef9fbd4077acd2b2b61d2eb1e953e7f0bd1fec647555a224527cf24f34727a91c843b402ea493f982207cf69218080a4e9f841f0615da06fc04baa7a87c5f41784fb10970d434e9a7c1d7971c924784875ffe0944122254b49c0d5f9ac7f35ab2d5f44bbec14a59040c40ae7d97e472092de7c4a7ea70c4e48c8f397e702645906d66eb94fe6498075ed09c5b78dcaa16f5f79b67ce17d72972010aaac52b3baac343d8d9aebbc45987e8bfec19a974396f494c589882c72ac58ff08bb656a79b1417af5230dcd425db367e620ea17678ce4a41589f9077273c16ef2b7d15dc4a6d6bb3ada48e6d5ed44865232a10bcc54d7e435ed1ea57094776bdd6bb5838c5542717c675a79749f27d4e9bbf883146d75e141f327e972f3a17a711f3d0023042a7b76a26f9888861b76304400b68bc540427948a1c972cc9923e3b2e117b8c836484c142e75c8aa6057d67623e8edf925327597a997721f8ee2b210276874844e6a90eaa2fffd2306a6b4fab13c7a40aabcc3cf69bf1024fa967f24f63fc6c9bc2a060624b2ae4d22dea499a6734a4e0e3d179b144f7208db04fc6c75f6ade823075b4516f199629258df0568ca82c065a1b95ec7c701306fa3a7740365c03b26bf0f3e7b5a309959c5cf7ea48f20aedd68c2ba5f1c165ed2d7a3fd3ea689f4d7a5462b2aaed73b0a79842e9a902676414219f05ead72a29c5813dce292be666f73506c61a7cd001186313d2476022768d3befba13b39fdf25a4303ba4eb09c485007fa9a6476464571cd3fccd0350c73686b5c61bd1443a6efc2f0137a350f8f1c3c9a54750fa7d4bb720c209dd3ddc160446ad0a8729ea3483846db7832e052cff8aef02a5d04437566a1692bc1e8cd10f71447604e529fbf77718c2bc35518d4f0fdcae7fb9c6cf255a555c0ff6558865eaa6f5835448a1230448547a522447e1014bfbf54870438af91b25ee4c1d68c615ceb5772e6f7039576d2a0ea5ce176997d8be1883b41371420a69c068574f0a3afedbb7269260edb430ba72aaa13f945f4d9cdc5ec61c15ca55077a0df167adab2ab131172896ddcd6f614e5d34e32f39149db96a06ce72ee2e93b7a8aa4bff39d363b723838385ee230dbe0051a2d583d9e0e73a26e2856de4e40ce3449ee79580cb61d3377315b03f501be117c51b03c66eb7cd0ae00e8e83ef23527c5a6fd970d0a72b19861e2121dd93d1c0b7afa19d908255897fadaa698d0d502de5853a1e7e61d499e85040ded7b4c2b3a621404f9190673af78904dcc7e9ee111809a2a50e14209659681076936ea9ee84968cf03536a9199849cb8aa8bb0eef3c7d52791590c1ba09bec02cbcb861d969e0db08e93ddd63722e4bf35e45844d98377521f7c7249ddf78647d74d676edc8a3197c71446837a3009c0db8fc94eac8c33191f80722c881697130e1a1915afd2404ad34f76643db0debd7711507cd80aec925f0a2b788feda3bac9d24054c17997331d242eced64ea5dadb843b70d7cd68ac5c70034f19f6171802e4b8033ca196e0ce77aa17accc75fd399bf7c279ba6ad241ad72626d19d8bcfc2bd59568e4fc2d27a4a37d38c3c3ec70a78afb1480946789bc724c3951055e9c4f6ad245d77975ada89d5ef89e33fce86516947061f2983f12722b9c1a5c3ec7f0da94a4f12396c64ade4f87817c9966b2f1111a85be5fe6fb724bda676f191aa2370f204f3e3620ce7dba61cc26584f73ee3a7c74ca394010723273ddd9e36f9870ca3515c5150168f476341250ebea98e0d64d6fdcf68cc6720dec99989514fbc2013a6ae0b8bddf131d2663830beea2d96c79dfeeb07e591303bed24cd81b85b9623be1a3faba24cb6bc363da159ed0f861c9a23b0819ef7267f1525a26dc01319128da5bbfc14b7fde1895b97c7cf4b8d61d896dd0d32472c1c2c3ec153da5459c8d39c861a922b573a3982fcf903f032df89f9f10cead72d6c371e19eccbfdd01e8d92554fae4d3e92f45603c6901de1ed9ed952ce56672bf16d93a67cdd551523a24132ec050b60555efdfe917a614b3403081417cbd02019b54899fb83f8f39a70935115288f92676af221d91de5c0a70a9c2f9900572316e19bfc6ffdf3ec677b95c16f3db4185412736a5633c35e57b7857022247720b3ee34d534f0c05e19a19a38f08181bac779c7390fce3ded79c4bfe6dbce37207e5fa3b62351554ae14f5986853170c32a330e67907ff578454a1b6aa2774720cbebc3b7220948eaae7fca8c09c5c277ee38ca4e6357d32376350d815ca3848727ddff6438876f3ca8508573dc4360c8302172cf7f37d40d25421bf1df6b672ea065bae40f782a85a117c14d061182882ff1cdff4f371aaae95abb017c09d72d89809726a18ad4dd776adb7c41da9968511a6c4d0db4b3d56ba64f31592806ebf7a77f520c761bb469d22f28f803345585b098ac458d3cf43846c3d57ca4c7290e5d5ff681fd4264aa838f2b34e4b91599af6abc8ce918cd45dd97f6887516a9855173893f5146638aba8e996099b273a69e9df4fb3395fd85b9d42ed06f253f213a792fa5de955fc2eca3773bd5e3977861c5027b53b90a6299e8626d92e72dd37bd24c00d19ed31a23a7619874ffd152667e400fae05fa4cfaf2efe8bf07292811dae14ce29853b7c8c67ea7c13bc127cb957e7390367ab1a9ba177d51a3d2e7b7cfecbd43c3cc46a45aa9c07be913ecc65d745dc8b71caa7e0fc1473a3676a291568a64332b83d11b5cd6dda24d0d19961db4da1df1a7a257a3702acf572470208e65fd694d611a333c0118f73923c8abf50511a273210d25f64d0abf8721b5458fa61595338acb78fb5f789a126ba95e3b0133192e00e640dfdceb48472fe73967851054cf7e7982836693cfeb8c28aa3eb2addf06fc65f3fddfc6a7b7297697cf01ccb053f2aa213b5ee7aaa014e024ce97184adebe336c79d503aeb72cf178aac066d1e74ec95945cb728a4a36207ec112d773c309691a6b00fd89572dfa93065085eee15672be738645b90d1abe9f4b4e82622a8051981eb5fbf501b8d0ecef847fdedb1abd8048fb50928bbb37abf885620bf16547155d28dfa7d72c5c4b074e732ba91ee856146daba878a6734711af8ca334e55b341071aec1372df9ce30cee818e7e67feff21f4c7acb8ded0578247564381f17bdd170f1eba723e2170fb56bb1c715c13cda3dd9a50907a47f6548fad08828d0bcb5ffce77c54e61ac2286ae2a556f4e98d9b5f3a629e20b804455dbf580139fca6d5d7a31472c23f89ca7f33225a246e7ae3ffe36267cc6639a3cde528dab5eff7f3ae0f167253e634b9baa6df253c4dc21a4cdde8358ca6d8c522f021c13013b36f233a995216fb2a958738a57c37d7f47fcb0608a7d8fb1288330f852445bca6077efb73720d8b9b892e121f894ac8403b90c4d84dd0b61c032a08e78970abb5df0082a372514d63e92ab093c3d0a9a7be1ee512f87abc3f303aac71c35851501d570a217246d13bea19fd20dfac47de50a2d7c6e0e5ad4e6d0d7269ca23097361e7628972d9c05c0f113b7718e1b84a24539773c04eb7869d5853a5d125757fc328db1a728b8594ff0cb7ed7619d1df6cf124131b2666f5fc03d5ba07e48c7b2367235a14ce768f6219efc58da9a2981c63573bb25510a2affa8d12eaed7da7d864250672ebb30b27ce949ba3486df47b5db2497c655b49e7f984047b8ca50d5b96c0ef3a59e12e4a624d9f009978b02f71128726171e7d48cbe6b128f508aea3710d7972d46cc0cd68270d1ba7ad695b92d542c0f9c7168f65aad74564a7a2bb4c331f5cb8130b2554ebf0fe4fdf6c8b9638517286fd505adaab7d991a511d2497146072307f8937a41f2f8e220ae59a635a0694c66c20875a3aa96a12adbf30fc85e472495a13a7b42c477f425d26dd1f31ceac2ca4f1b854223b7cc988a347770ead4f58c08203a48639ff5b30582605f56fc834560464ffa309a1bda5f2f0a0f8555c8ddfdca69ba84a95d4f8abad6556572ba341ff430b5c263989497eff79f23472a70631eaf979ccf922c2223743c2292995305e08433db53a7565115c5fde3772984464629c9f3c52fa27581f024c7f79cccfbb503efce8a279899483a17b7126b56e78cca398db5c31fa87808048335e23bcaf34ee06f61d76bd5749f2928b141954e48d40b851f775a51d0b19ab34974ee6aa7ad15182159ea5149d8e1b8b399cfa542a4c1d210c63e10060a4407ba213b151b8760b21935049f7ee00446a72a39e9e4bc876410b8595c8bc5a25f518051389c865007a57e27c55548e88fe7287525520ef586208756db8e78f67a62630c860955f9acb06c1e6917d8ab6504cfaf3335511d4e9b36e4f80c1f085aad6e6e9cab2e237f6fab7f53ffeb8dc15154ec81ccc89b1c37c18adfe960ddc5dc64fdc2fdfeae28fcd291b2a6e0f4db3726fe125400e1a0960729e08d8713d3ba82ff6d44d53088b966817163ffc2fdb7244c10b4a732b1cc5c881ff2197d0e198daab03b676ad6281a8e2318d21e633729be57e032907adc1acec62efc198d85407278190eeb76d6c335c511d964a9c4e6d87a7e5a5cb3ab342defc6c898887e23d73e496d2c7f463333e108742a7cf72407d73d82340ae0cda24165a211259c50ebe6d36caf1534f9ff130127b082052116c82c61f31033e87b8e9a9af7dcddff1166387ebdbc2b222f24f1905307b72d78637f8e7b5bcc22ce12133b7e402d9d19fbd094006c4ca286d64d9a819c922b1b7db162b5255b89d050a8d76ac0f2a9d4a3dcf6e1654cb8c7e3feb59d2007202df0a5a38f3431e6652b85ba8be446ce89167365acdce2c4a939877263107459ad8790d9d40dbafa20fc4d7eddf69744683bbeed8330913a3c33635ca4d575f30ece6d94039f007ef8ac2055ecd92b7c0d5c0caf216f5ba02b47d2ab3f1ec729146afbd696f178ced90d57b16407fb1a49098a8e2c6eb33d30027ad2d00c5486149cf7620b06a772f24dc86f2bc9fc76a7cd470be8382d80ea6bd0742124c15e062547e84e051c28cff9e5104fbd5c0c4f3c0a94f4a1a4adacc34a825e8620236eeacc133e91a34b28a2fdee93e7c28536be71237a7b7e1bf16d1ec3b6ddb1e3d88408730354e2370ac399840c2aae599715d394611a0bae2341c84831cb672e00e232dd0f8b9533d7a061040c07caf9e40358396c56e87105692be134b1272117910d47e37c00e332b6321fb646c8913756a99444ad1378441d8d69afbe053184106f59ac05f63135a4410d6b9ee2062527ba559bf5aa61ae4f55a79807d721943fa8cb793d78b41352144727c9d6c3176f8c191bfc39b083b1c45ea70df72ea19bf24fc76c8a057330b9c408f5694eec1f3500b49b57ea11f62b85288eb72648ee56fa392dcb7fefda6ff983cf7f1e02b4ce0be3b9d2ad2a450c341b6e972cfa8b62c6b276ad5323e3ea0d9468d81b114ec829f1b3bc65d10a9d828fecd334f0fae03770950e199832321c54d97fc1fdc2cca2ca2465569c0a75870265d723d97ec62feaa71a9b49ff6543e32cb2137ef2b18a5898bcf83a739ee274c92720bec6e59e0ae409c53570e6d4eee5162e7b0e6d7d53ffdda79154cabe0994564413ed15b48b45422f045ecdf19f327e6381f3be3138f45892352f13c7591a75b5ca809285c3ea03ae5ba615b4f44379b91a626e52fc73ff76bec35e6ee919f72001b67015178b08c261b4f3dab71526681b3b20e0264737933e5b3306f080f366a35c083f9ce4d555b644897106b518d55d907d5b5e926dc6f4ddd0c0c280d72375f343b6bcd1e4b6e59daedb20ab52d9303d340393788be4f7c4712a7e9e272b3dcff360e540c3f6297b385662078821cf6f4eac580a248117c52f19a50313febfa32e8bf2705fb23e406d4ea1a6fe18b5201e130257beeb00133ae845a1d72ca28d885fc56ee563c15692d1ddab6e02ff6218746142e3390cfe3b5d6c9c313209b882bde2a2d4bb4ab9bb656d256ef482fcfbff938c2b8a42edcf27badf753d5ad04b25857545915bca90886da28e3f616cb973291ecc121ceb0dbdcc0b072ea6b05dd141a2a61aba6af8d12526290d1d61cfa8b60ee211c3f8d06b64c0109119cd60101f0fc2d596ea001c64a40e24f7c4f0effe55a8ee91cf5014507da308550347115c0021d79ed012deddea7543a5efdb065472a6fb4846aeacaed29723fc07c18d70c9f8f9cb25838a3e77634ed155467960bcdeaa1296c4307c36f2817ea8fcd26f3eb00b84fa01b1268e919fd3e9f7f3ef61baa6f9714faef8c3c72d6acbe59d38de355f3d89cb9979e14fc61579fa89bd39a203e62966c6316b372a2fbed832e12ca278fc8dbc37d4a0f3efcae5e20d8433935a33f6053caa23d36da7487231bd874b007992daa643d0cd0c704b9231ad5f60d9c43695a4118b10ba181e01fedce976c0a2e42d2fd4c8359bf199ddc3286ba043816f9af46c98b721aca48442da1a5c5ea09b5173ba4e55c559af7e3e58bccc638a29b8e00d2e57253401e6af3fc42c2771b7f34b6f12afb14b3d116850eff1447609c46cd5a4772440ac59eb2b4fdc8e676fb0a42550d9f133a071b04f17e8303ea3a7d8327b072e83d4836bbcd8f06058a7b5501fa8be7c23155ec95b2aaf3ce1a6c40ffbfac725d64fd00d60854257e8363dba7d234e43312493d841fcc81a023c32bb9461d7235171dc6364b045ff9734fcfff781765fb595e94792bac8a05d1b03bb2adaa0340496eeb05dc15925c3c80bfbb978e1b4593f17ffe46d8eb0d28126e8e29f2430af178d6b9bde6cdb9fb1e4a119be8516c1da8e983a5165ae66c3edcf1356a72e00e3d26d82e491cb750b84d6d602ad71b529747c26a7454b9de483ca4dd7572371d82f0c8e20697a3c4bbb95ee3d55f1f839d8ed7752ffac7d9095362aec572a6011a5b6ac6ecab5250b6594c4e3c32dd0164ae46b34eca49f30da6c0be6d726a9dfd86686cd510cd600ca23e767f69f9a400ea7ea0e17f8f8e31bffcc44b3dec773e4add4983dcc6cc1ef4cbd83eed6fd7158bf7a50ebf2752844b7bb8783e25fd122fdc984c7de27fa83b20c48b8deb6635b47b76b853631e132365559e59e2be14f74a5e30b861ed6ea0af88e44d69d78ca082dc2c7886f047336f1cde1d48e9cd002ed38a069322d21ed19de5f5b8407ff0fcaa9e167f0557a68ccbf04fb79497304ef63bdcf646a70c300b547a286a3fe765c4bd192cf7363e588d8500cd05f0ca8d6166504c324fcb112105b8d99a294073bd18d7073cb2985d425f631d78da838d274bbc7733eb0eaa3e75779c8531a9f3fe5bf75ebfa69c35f19372202c797ebfa9a57f25bb8b014145f591acdaaff183d75f3c707eda7747486a721337e13ad474854c0d9d7c7988bac2a9cfe78ab450550052193f36bed9be086bf9105b36c65f12a0be59a95547a0093d597af1c6abfcf33d40178b9b05a590727525472d3a81e4e6256b2fe17e11480527599ae35279f33a75fb2f4284cd4272eef5c8295b4e1db11ef7cd64fa30a54f52ca7cc1dd2bdb59ce7c30244c75f872c7de11075af16ccdacad4e3fe74970e7d01f90542e4ff74b799f4a23c4255e72e695270d4a8c460e5c8af9ccfb9aa6d58e0486cfeb96204a5934aad09eade41127b394143e740524c715cabd7276fad9f5b8facbdedb04a100002b82bed03a72ac3a89dde430a65e0f5cd4682731ac91d31638c3a63c729af1bbe55123a7740b7fbeb81dddeb85e5721822badd401aba86a76f5d577d137ae3466eb603711c7255a92f732199bfd45bb668d88f067f0e773ce50fff983e135058126ab9241872c73bc72bdfc1c29af5397a4eefafd33be08a87787c2d0b50b878158fc3b8426f4ebb665f6f97f589d9336903cd12511e93a4e15377d2d576e14e081272ccbb7284b58b4d2d1e361541eb9e03d67b4e0ca90f51b1cc47a01fa61cbf1c8a02e43dadb2666d40a084c7a516cda677a85210b303782de264665c96987e270b7c88721afc5b1ee1f37b9defbcd77f5d1a759d94e6b02e5c33b3984662b4dc9fb2ed7243619055aa9df4203bed8b3f08bfed307e3490a2059e57734e2856cc4dfb0f7277fbc63d50dd61f6a07e1653c86d07abcce480af34d470292fe9fc7e0781f372e850d04d411dc2b118f78ccc0ed5b2a40d78288902e99c72305afff7fca7a7729ee56abf8d10c6deb791c1ef757ae0ee7eaf7631d43417c1ffd2c529e211cb3650bda4118260d7e5b4f4b7a7de2876bb404c35b27e75efe20a6e165ccd2048723e084878a16699a46799050956504c28f378ccea36826fcc8f46c10e9a3cf07235eedfb0f4e3b3a1f4a49bca362c054217221d21a41b7fb78b56f6357927c67225f1b20d25c91a4e7eaeffd24724555b11e838b18d533080e053a86fcb940c72379050201431638e35603f0247a7a138f5cd0b245844044bf73de4470e521272d5d5d4f860a9ddbeb7dabca17ddc7645bc5b8c87f3b5bb5065e541af75f7dc727aa3ef5b2543d89a1d122631c744e79f30595c6f48a8255ffbcc87e93a000007aa089f7d0388275c41db37bc1f79f76c18507c378b671b430fa7459ee9fea97275837cced136bb0d739fb994950630de996b295f49e9e50f734b20e9ceaa0435d98d25c2cd518494ff04dadffd170f1b4712899178ed9135ea4071693c98d9727c85bfc9bff7100944a21e1e2867dac4cea3a7e412f2749570a8a0e0d7f1f73810d8755a9c688cf611760047288566f1997c37b2255e3989dfc9f7c4b628ee6c6ff10a32ceefe6c83487fd4241d1eac98c4ff2d4b98452181e87a904fb7b8c6fcd551f58b22c705a1f878e649cb3a8da4cfae7858c163700c40c42f8bfadfc22c5e50b556b87e6674900216eeebfcaf0879de53dab5b69b667941f1d0b251c241826c48ffbc1a7f57b57dabed7582ae503bf35827494a9522e02b4e6899a11722fca37c773d421a620da20beeb7afc95bfe7b82d3f326e1a3e9f59b219f50872da15bc4c2a1446113de79acbae39e3172cb59c46853612536d3235eb47ec1b37612d5a8c38389b468684ee9fee205869973c29ad68f791b0403f07094811094aebf3850b73ff0631d825723880b13d5b265d5f5cae6bcbd4f66ed4d0d1ab7072702cbae948fb86616e33942e3551ca70ef5a7a1af845604fde8da22e441a6a53f0501d808cc7f5d190e55127cf15516c4568aa2a727817f0e5fa44186bec745392eb166407378e6443d084aa7107a2de3d9a6f6c93c198c6885dc9df79974552def8e3f5a9935b2c5f7ff943f61435698ffc46d3749e2853b18a76d981f33e08a189f2d9f8fb2cc08e64002458d5251d507944e93b521aeb830fecb5ba370472e792cc543b2b094a4d7eb34953b26273188b58b5c712a3c9fffb59fca3e3d31db0535f58398c4ff24d18b45d505449561fbd36f702f9e5a1a79e64e8e20dfb49a13ab1e7f0d38ba94ca4397f9133780a6f29812d596d0c17fca757ac1ea3d472f892ed0279c3c6eee9cb404dbf379d3d6e12ae77b7bbaca8256aad66270de45cdb1bb76b8849eabb53a60e34976da92fc5a2b4f38540e5fdcde6fe6509451672bf522b9781ff90f45defcbc344b69bf557b14b1cfc14adc7fca412e877f6be30280e87cd93904c2f4d5392f4d3a59ee4205e03ebbf70910fd0b32b8d9dd67b7222b5a1941f8158f274f97eac1cbd03b387386d4754581c9dbb4bb4c141ebbc7219b184bbb5b8f7f6d5bf2142a33c91085b5bfec911e44a0b990b18439dd0e472e9566a6bd7cf4fc1bcb7b367fb7f9fcb4bd01d2c2d0bcdf0fc17785e9216747218173147c35668a0f63695178d6f5e99f28f585c04c9ff3776a404f18d0025367bd176dc50da4514dbc169d1d989b6292362a93bf302739cfd210d6543768972b4c3867866d1d6c131d79fd6b3efa503a62ccbc29a9860c872c704ae23dab02df637d496801d706a9a68a1e428294035c6ed49304e5adac835506b6316d9f47224ecafb03c55141b8b81cf6b3495466faa1093ccf3e5bee6b16faacae40ac74eb80f6f56409e1c9db69e577ecc65c5c1a4dd6eb0546d1062ffb0fe2b1d572c19e0ae2b4ae5f98400dc504c3536f6df98e4174ef523832d259095768c91081c72e5dfc3b5b55ce528a85b11876b5eb6e1ac9237b9f9b0a6f25b26c4f9e274104d68eaf2a0bf7f4475e394e794fdfa296fd79e13a048b1dabe936372b548db3e721e6f70187e08f5b4b44b2fb5fb800b27fff8496259d1c2876620ad808f889372055a7c5d51c55c0af314fdae7fc5400032abab0852ff09d85b36a1c0c8d5b772ac05da26dee0c2935f8636caa238d872a7f7bf7eb24a834f76d56988d28b36726bfe33fbe7a007c8042be3006eec08106d5c7c39bd585052fdff41fe56ffed72c585eda6fb73e1734cfd97a8ea76a8eac69bd5609cb7ff346fefd1257dd3e04dcf61103fc08fafa1bad75c5df67ca8bdb90cdfe13366ea943eb7ea6a1ad99c729ae9a2a42a10c93f713a09f0d1617214a5dbdf2f158be023bb8c58b70551d268073965567943d6bf843507afc491daa9fcfd9d1435834a64beda485d28dafe2c2cf361989b27efdbd29c938772c4f721e02791f0da9ab089eb5c171e819ba460df22c20a5242dc01ad8b563ce29d3ddcf4de2a59539929c6caf04d8929a8233c278f9b2a38234a26914420c183528bd7b30bccbfca15afa0bc7468363b540872e789046a890091fdc0148a5d2cbb0753ac11f0a2194876dd504d2e14a63e16722def74a6f56d6a1cdbbe6e0a7d05102853cebaca046965df12d35189799e9672827883d97bb6b30747c2e685af4ff50fa0cc3ca26b357e8d99a434d4092dec0c52fcb1505cf0550600abea384f52574b043ffe587bb5f8bb6a209c12c39c520682b3e719942b951c15b0f29b9f88d09196188648deb6834937f0540c4491f872a7ea87ae54810ab91ad1c164dd68e82433a18951474c3723ba75e5f410bffc72964ce53779eaab60b1d046b86cb942b3d7e9a8cf5a892445ceb4ec8c523b9872caf194901e2c7af0856028dd71ad6d536418725cd3672e91ff8254c4dea78a72143a703118203e95bf21bf352879e349469d6f9c84051b2a199fdebd934d3666318ae1944b9b71985e8bf97f534f1d1b05a83910f04ac564513a355427900a44d3090a5bd39fbb5ec113685707c157d53441a208caeeac8e1a42af2a9c03bc7288d678acfbb3466b63af00d1271d1b5f40c2c34bb416d68cec4f3c6141ed1572b4c2a920a6d1a0d54afcc20c4bd7012b347350b55c09c42d1da6b6144ef1c33cbf0f6edf6fc10e1c8ca02c652ac4cdb1a1a17ec47f9f86fde2da9c4f37c21868bd53d0f3c17204559f33984c4c3dbb9df1ecdfd0640651192f1d980d9cb83b2c55d4c4eb1ebdcb5da5dc770e66c92a8ca66212231cf0178f0448b24e6b848172df8383d2c543c34c2ecabb6ab1b1d9f9b5bd8cabe59755df3b9a95f885d20b7232d3520cd8e8709d915d1d98032aa5e868a738488fa878eacd5c0c7796d95238d46d176de3e7b63b5cc7be15cf4042f624260c89ffd2003e9b5694bf19fa097288a1470950e3c79343e3eee8cd031fc3022cc37dc805d3a74931a554b93d042251ab71ecc39da87e66c1fde5a06ca0610b5a97f721e587f9eea64dc014de0772ee267b80b7cddfb3e20aaf93aee6b0b22e48128052fb0e08442b15f6a5ee3566f5a857e4c1d677c6446ca44ae03fdca17ba9bc9d2194edda00ae088b8f2b351de4a051291c766d4386c486e919842005bd37b0aba30c2183653d928dcab5b70b5308585c8d23fced446e4057c3ba86262a582a4fb754d7654959bdd41dc7c472f258c2a3afa6d772927fd6b7303038531e6cc128ba971f5d258ba4262684ab0a9cbc5b4f191032bfff60afdc83dc1067567bdfbf6eb8e7aae076193d57223972cfedf6d38b989cb52f27cb8dd7cb88b97f573427fb8d6335046dba37736add7270a03470ebeb7fa5a3f7d7af70fc16275237f5b33a0f9ad1bfcfbbd3138406723c3f1a79fc22032bec3cdd49da880ed2f126239181e27a2328567d295844c47238c295464cb3e55802733a04ab5cb57e8b79f7d31c602a249b5244c9e9a9314e9efef651ebd2943940ec0db4472c8e853f8b2f9cb8b40096d7caaa9d16ee4172ced025732fc63c37dec0a29a911553e743764815ae482c0defcf8ef8c5546d72215a01681af72ed80df5c3cac7580b697e5dcfdc7ae878828cfed42eafee10040a008c7574f39c1f775bb06e5860444b61ed14f6b3d977e4f7f9e61063a2cb72b68f8e0020af4d4ab4e48f275019102360e42b993bafe1e9ee8fab327afeec4c3e0307fcf6fcdd10da19e0245439b4bfa2cebeba330caca74a4ea21ed4622372002b5e7f8aec2218799df4b2e360fcbfeab50d2c69f67f78c4326afa85f31772f796f4bbf9ea69b349789873cdfa43aa632472fa79da6b8c0e2db59bd1f696728fbac5218dac79e205415b548f9035ae9052235a0ab43f0648a232daa555896835ef773895b0ca9110df310256c0d82edf9887d6bb94e7641f5237a0c0ef42721ee8ccc3e6712e689bd283c320ee87ca0f298bd035fa07f32623dbf438d19272e35c36dc9b901be0f6429ff8ca20111d40eb37b91f14af13337e9921ea5d4e724f9a730d34be51d5d45252b6f7c19451ea2b65f91b50c9dfdc43fd8176903a0d88a8af17876be5c3ed4702758e37573d20e7175cd4e3ffe7235e3c7da7e6a372e6c148600c4cffff0b9fbaa0d5ba98ef79c84e06738f2f51ec2e872638ca9b7291e911ef6d7b713e50da189c3db6a26e62cae4e13a4db3da7431800576b48d72199207e208200a585877b22adb7e7c64320042f995e7480a2c1de0c95807e2085b3540e8bcea91e670d69680487f7cccc2bc5a47b1f879060fed4c5b4a5b375baee503c68920927ca3b9cfff89e21d2d5dc1471e0b77402492475b8e637834727dfc854d30b0c730e0eead4988173ecbab51fceac1eeda220e35b401378b63722e3a61b926062c6027df78c8530cec870063b36d87cc31d157e2adf298c49b72f44a578a218e1dea51e66a0c28dd217a710e36e59eeb0d52ec4cd2029c519d6ef573477166e29d3a8ff7b69d07dbe0a60277acf8c7e33941e1d6a9df09588a723323d0086eb107a243440c66d3370ab5e57fabdc955b3006d1f4f7adeaf705729672385f6d6ae0755af18aef6bf588fe75f7daeb0c5d30bb41e0b8a5dd38344ef4f72f8359cfbda1ae58c55aee3d0785d51886bdf187cf9be787e9325db7f80db5c9ee01c09214f911086dce3ae2f0c941922872c1af9666015bba4c677839724e81403f337b318c06d5e26cf946db8bffd03975ef7158b2b2e277cdf9ddbd5e098ed07d55ced50b8bf8135fdb67f227e52c17f67cef106605c188637819592604dc8b93f755232bb34cd98a2dcc5ebcbcfeba2b7aeff30e5d9bc39f3d9199576092227b0c747f4f2c1e0379273c228c8295ac58af431cd577e8d5b421ab6d72d0ae1fdbf8660f615cdfcf3b9ab96e900e4b4899c15e3bdc4c89b03ebe81eb723eb0d6d4c929f674e82d67d9b704ebbcbbd99b45a471721a41abaf11eb1263724a186313b3d5c7a68bf9b9889eb539d0a5bbedd824e11c95c71b19b9e12305424e867e007ad04762aa2db10e4f81a3af12a32ce2211cb2c0eac1a987a0d654724607992414cc97516e81a099c5c06edf09a259b050269199fa5763e1676ab24f752cf3b1e061cb16f327ed819a5373565a1214fedcef723a1bcbc5a8e17fab57339afdebfda1927bfd6ba95affc4ef4fba4ca3ac705e0687ed1c30e0726e0e72052c1fa5008a6ddd977d76ec2cabea8f3f97f467901eda489292597297b9ef254c41a4ae352734a5fb8ba731485c04b02db3bbcd45cdea641a47d7cac9c7ac7208500c66101a843263d84e1e1fd1d95244499eb87bc826321bb0ef7ad113c272167d7ea4a65cba2bf3fea9c9874adcada9c4818562304aae6eda87167c1296725918708d64121e55c4e36af402e6ca39cf945d99ecca036c364de2c3ebfbf91b455a92d671c735a73b2fdb901bbc6a619fe101fd476577ac7c0d3cb853771e72889a6740d84c5ff88a4cdf1338c11a9c79d70cf4346b1c59a0a0cff129a10172d9b31cfdce301c7ff5fd2369d7e58c2b5a908169cd93d55ebf61b1f756f3127252aa4c2fffae90fe2635cd3094e00264fc90d8652ec1946d9b97d3d55c7382553e47607cdf6a164c41a3e0e3008e594b92dee290ed43df5430c5b8fb4989ba13002b9f356063624793f50c6cab99df8944ece8c9fe56636e938a88c017f4c42543248e540176070a81e19262a8cf1fd0417cbb8c0dab7a493f35ae09afd1db728e6c076bbe45ec60ffd3018fa8b396e37aeba7bc99b25c3604dcbbf760441d56a07a54cf56015f362675688589c186149d0873b1a2386b419b3961e7e57a5d0df9f81e5fa396b8d13c4d5b95984ea25007f55cc35bbf77a1a5cf669845ce5572f1517687f061d4997dfca4f45fafacd35a33f444554b017d9f3be8a3638fa1728caa0e06013fc9142c9186f6693734e8dea96f2b68933ee1c47b55ca45c9532b639f988dfd15459cab67f8b3240c66d2cdf860a38ffabc6efe7b35934aeece72d82d5d72be8666c6a9df65f2586ed2c2e2bff6ba2bfbacd42f8e312d9f42d572f5c34904da8abb411dab66e43e1dd64e967f6417e0f12c594b920a069177fa5e89696ed6d3374da0cd05eec43ef4d02b40d3192f4d96b76d045a0b0dd825d54ef1a923c81fdc0422888bb1d81ea4d070eaadb06c08d76994d514d57ec853c82e93be8ec553dbfa20f4f26fe2bd610ab5cd4fe324c2347ea850e8ac87a27e8457004a7a34b836e284d3b56c703ef098d0bff468141393abf876eed50de2552872d10a5ca18088fb4532acff3d6964b80f42537fd5d1faff67fcaa7f4799cc7772a2d48ebb92599bd4b9629f794e256b567806bd244646c6ee8c05ed5b674860720efbd0a0c56f1864a02190883c211d60d50fbf116868eadabf1c5e78ff86f372451d1c0aa16c0735b4b66c8be5e32a66e7a25bc6a5e676c552b9fc98cfe84d727542b85557c1d8e5c6e45e891d178d0835634df01fbe96f3e5ac291cace64572b18438b1ea5f24ddade2dd335645626336af92594d3307e2f316c94b4df5cb726b0578d90e00a519cbe5ec34b597ab50a548d07463db4d0ae54fc23334be562209665023f804cca8ee424a7ea4aa514009404a9118e255d41d48059054515b72ed00c6037591944049a42cb0eab9f507e7847780180af306760c1364e107657252f3c43cc7a9f7607d81e805ed36425e3bb4c8fd7d0e782b520f4f771d1b2f7276f87f4094e18505a100d31e975db640f4cfaf0bf944c758f4bca9d4a95b1269c0d545dc9ef3a334a944d65cb8c5aa63ae413e567c87bcfefcc570134a2c305a65cb9f05568928cc222f019ac562bc7b5175f59cb1c085db408601b7d3a62d72e214fbdb16d3f2049322d8df7759b13b042d381179917a4f59ad753198a76e5c16324f04dd0310a79e259855af8963245cc9109e9c730138eeceef90fb39593eedcc16805ccb0ec3b588962eb1a4bfe38ec95812ecceadcbd2e83a97b6b48972014ff47d5b06d9d88f0776ce16d1491d792c9105d22c0c15d730e2395039e672a71b44171b37e155efbfcd6b286d69d995dbafadf912ae04845f303f2187574de5ac91a26763e2832a0fe5a97f66334a6bf8503636ede7d1b2e6c1080ec656367f2d36fa9a5d4a5205cf8c919c11c47a1fa0662bf987e7352bf5af271de25a722b0ab49b154d709939f039c3f6ef1aa5e0fe63fc7c1cc92b41d9398d2d842d7229e829ba043c664f0b09d826aa9f697b7e51116b0c271d2e618e65b6d48ce2208251f4fcf893277b6fd8aa2bbffdd1cb8b4efbb50e8ac685ce6442472024b17247c0e57f1e2637eb85c4e3774c2c298054fb0022c26f39c4eae91f23f611863524274af3eabbf2ca25f5df241efc94671cff373a32ff0bd6953bed82069f84274d3d1a5d0197226d67084036d9f788c47d5cfff7b32241ba513206af4d6a09237773e068cc25e980d818e50cec3b8c52d66cafee327240a4933804ef53017972e458251e6c7cf444c849781646871c36311a8dc3146f6b48a30bb338d07b523dff97113e525be29a8055688c2c03da6b10e3295b9406e00c43921e41a8d54a724da883aaf249c10a0c78572a7bd7e004721e405a677f89be82af8be3de5a6e72e338500aed32f163929be1b16d464a67bd76a1583352d36c461c62e134148c7246d8e0396ee62c5e14e43859a4e243a45e669dfb19e5429f3d2d2beacee1877271df0d55427268a6f3def5a38018299308ce279b6e01e81e6a6fa6d4d53fec72b49c83e7ba0d83ba12c16d8a33004f3c21a0af20efdda3c2281a16ba02fdbc13bccb039165dd21edf29269b3591a243102df5713947ae05707447ec43986651260f810ac39ee0b7c7646b4c3fe275b585b27853f0ab549efc4b862e801d10064d779a76c72a94cc0ca2ff6af7a7418a29776d29d95e5caebdb115238cf426072c4649d8b0dfd67c9aace0e8adf43ea485221abecf85e55c6573333d242a22a72daa864893caccd6ee16cfa1fcb5ee63de7b045f601ace72c2f609bcd030923724e3bd9e12fcc29950536f5b17b10a939f62a29b89340bdf21f65476636d3513540d00cd816fc48d81b040403aeb963e6d2bfd88aebffb899ff502b85825d38727d49bcaf53f705817f293de08b542b5172e5482f1ead5a207579329f97a952727f4d0530027f8e45aeeec2af5139675e824b0e3d918435872477bfff5f3b253fa139e70f668e5b49b365660a7af210adebb4d96ad167a2d6bbf440d61565e302b62573bb5626a9f59b52cb512ff2dcaaa7e269ef1f44edb2841e24b2668eb938eee459ee119a3f965bb44e296d549c86780ee4d3c9d3b0936b93b5da41c4cd69156c698187e46d5e74db3772b022d2757608e9777b0c52b58bcb53874ee5ae09a8de3fe556f2442c4195fca41217eb6a219d3b886b3e791626ee551030f6130451ece31a8cde00cdf7b5d60b8ffd17adb0408aeae10e58a0234869db6380ce2ff191b890cd60e031eec710bd269b888f1843771016ae8e171dc90c62edd47208bd8602b897cce94923229359e03e5e66dcbdae1971964baa0edff17888a38b4edbe9b36b12cca0776048000387ed4634d50eec738e09d396bb4815758a749872425bd66a7df50b945165b26c1fdd448d4e552564bfc2d4dc0afa43754a27de72555d133fc8075828113c67af53ad259827c81d345d614e842ec48954ef0847435323da426f79399385fc0629edffeb2eb91f83e6edc3ca9e6e36bd1834150972de7358295b61f6bf3d9ede8b565ec7df750d68091ec50e3332d7bfd627b96272d5072fc19a33d31bee52a53dfb9ca02cf9dd7eb30791d5f0a872b767a95f675d683757919eea2801a01afe358c85ca7a0ed3d8320e90aa7b2eca049d6450e352b2833958a86d2e3958c219338118dff097d9ec2f4ee91cbc3b5dfd15e6b34331543439517b2cc324a00507be425c9440e8320d9c6be9b49b3158d3a5f57a9472bf5b8005ee3c44d81687c7898cf07c696f77555b7808381e59e5b35e39ab680cea04c15e23759a3c52e79ee14bca7de15531e404918bad548166af48adc955671839788d0b58b1dda31f5552729a65b7f11cd278c4056689ad7b664dd030be7254ebde052a6fd259ecd8f959d7afa483b14227196ddc6d8b3f9a915ddb3d825f39515164c0a3c9c6feb06ec466d443dfa7708001b81292be2b64cc69b8f78f48a5805faa7b56ff73a0eb9e43c80c47ac31983a4fdd69ff3dadf21414ae52c572d6f9fa42092b14ef7be28229616523beac50575b74041c518598a3537c756a72252b478153af8cdf0a9dd19497a397310860a73f0cf4fab204a476f26e4c05720a5bb312cd6737ba35ecaaedb761091a3395e6b026a04b2f0c6eea13e4dc0a2d0ab2ad041f938985a0c6e83f1b6deed8926d5faefaa27b13a1618c6f28ce9667ad4ebdb02e3dbb82feb71c701c75bdd64ff967d4b1e837177057419d9cc5b672e8ce09f5c5d13778a4dba5655ff3df3d32b3c5d5cb73df8a7787e7b78af6c807ffd6f2f47a7bd9ab30bb9f5d54ec630d8d854918b26aeb9c68f838aac02e8a72eed6d6a044a67a774bb45788b250b4ff6e513f55aa86085120ef39dcdb84fd729ce7e6946634df6684b3edaedc9e88db30d7cb1e9211e58c858ba7089a0494724278273b989ae9a049b779ca6cca17f6b8d5dc06310c97b6b97ef93ccdcf7e72b8d8b3ddb4cdbf412823b654dde33a2caabf7427feced43bc0680bb94567c7519759f26e38e09aa58d0b0a4664d4dad952c599f000a1385ac54261d9093752720edd6b747968ff4d7ea8436bcdf23827bc38e1780dc20d51549391df38244e3879a597cfe871b43558ac801eb7a606b131acba0fa15cec804c23ca62cf50af729c385ee356490f6c823bd39c8b2349be228d93cd79b26d5811b2e47719f33101fa3f9a56c2600c045180c62ddc87b006215f7eb066e0656f5723825a0e035e6b46f647790cdfcbef7db200ed3ee6e5eb8a0cc4f2c5ab481ed82d2ce9028f3972e9bce8f9a4987664f37868c633243715ce56302148b68b07e0ee8fe9b0cbc440936f3ef88f799362ea31cfc6e3ccaaa699d15d05bfa32e3c14d8cc2a5842fd5e8c44ab8de3343720fd865c50406be52b70f1eea5495b228f8b3813329eb5ec217de2f297e06152fb0982aa91c24ab52b83798f6a5a2ec58e7f3b0e7fbee68572931f7d6c22b26848ee8c27c91f1d2aa20807150b6a7de9998d253b0e4cdb5572978701c09115deab4d0ab379fa3a0f25f6c2a5d41a091e0054ac44c7b664c572be05ffcb62e85c97fbe76800f60b358d199120670a5906f4003b67c18db84919b67971bc44c978cab9620a3f317129ea516e65bbcd1850f839aa87f3447e76727019c27400e16c5c58b56eacfe2e106b29c970a2031e177ce18e9f5d6624a972fbcd02b3d778515a2fb705a130e680b421d4197176c1264e118ea9e979ec1772c6bd2941be0fdb8f5037c728480dc729ec755b4ec0c1facb1f9f4386ada0d172810ad106cef247ebdaa1c9b3605afee41dadae4256fc07ce609341e6d6ddc372e78ea1e1ab9d83b2462a0c09c17e8e65a66ac14be05fe67c0c77cd21ada0cd2171242ab49ea744445fdf34bb11182e17e3e7ce5811b4cf2b203c182cd86e7b72b95b98ec98fa5d2e72ca146d7d18caffebef80462268f10b538affc39d69b36d08edbdd943717db414c07c841abf101828967cd5c2e8dd201d597c28fc793b5c3da75398518c23950cbf48acb2351c9a6910ebd6129d14e125daa281b6db9c7222df02c7c3c50375974762b00c3ecc29a50e783154161c44ca6e3cfd926c707238b2a9dfd19cc6ab2ef328ae3bd9e20cec537480f3f5bc5c688d246f6e11ab720c7b93059056580fc01724259eab1c545effcc7f40635a57d81eb94f08ddf85123725de9da2c02289d5e05ee36e8db971bb359340be6931035e568ba2cbf9d522baf3ba070d73bacb0ae4907db6eea2dea0754a519274e001bff5051f8748c72c89c782159b90067457bbafe5227282c3f25e311fa2c2eb693d9fe08034ea97294ee877d0e890faa094537c1cee342ea4460d2ad1aa3ee8d1de9e7234deecf09dfa7877ebb5522c8b45cb70f8e581512751d56bd1860a96047a93a17faf670724f1b897e5f272b7c917976614b7b415f8fa0e205b617cafadb2bb246850e6572c718d3cd04ae7f54c483d0f6804ea7073e43f188dd40b419058b49fef1fe1a15c3c313f1f92fa6004086b7ec5668675a3e89939fd4517dd8f5ed0247536c3d5a617a67e5fa4413fdecdaa3a54257b1df88ea5b87161b357b6b7ff1b80ad90d72af5a8baf98678eaa9ac8e878f71e2b1a81707b6af19bb616fe48a629e4f22e72b06469e9c4973962984bed6468d7b73e01068dfefb13a2e9621382a37652d8720da7e16c7ee9f791383cc2ca1dddca01df53b1e22a9c539c81db4a03558fd9721e1f86a0584cb30bbb62254ac558554c62a9542b91974991f7d91d1ee090631a0f68e733bd0ecea2c4b8eb5844d58727d752e3dfe8d039cd0458ece3ae60dc28781ae75660acbcd96a3e3352b290947b28f75523f37ab13db003cd595427df3f9328a97c529f51908236977a2f71822eb2b22e6d9935c78737c78e9d45b3300dee681a6847d62c33b591911958cee43144606cf150b5de5f1f4016c246df2072e45865b3abf37ae540a3a86b1ece00077434b847c1b95daf3cf907c46dea1c72b02b109385066a17a8397bb8e546a34f26159020d6a29e9e450dda86df176372d2adedbea1786831dd992916a497f3611c0e0de7d84e510fd716d80cf9b87072629d66510d33bd766dd331fd297c570671b74b24f3449ff2c811909a198b1b1c0aefcea4e7c236a8b520f996c0aad88d58a92a8693d6c112a7e87fa365028372b1c380b2837771d64f2192ccf67f99850ad88c3bd13d68f1bae3522841c41a2b70558c63a83c17f3fc34fbf72b844b6f4a5ce5f26ec8bfb799a178091d501172ed5a651a0648e0c102933feb4711cdd94a5f51f9232e8464ee9172e023b715724c60e75587b4279a3406ce3ff945d7cf62312b44ac0165d282ff89cc1785d1729be442ea1b2c48c9c4edde1c87c59be7b467b18779aaf10964e9c69ef1ee2e72bd89a240afd590d5702fd66a0810b7379e2fb87ce8ff45025879690e436ecb72fdb6f518b5df534fdf56349bab00d4abebc836ac82aa7f24f4855075e3d847243960d031f7365df40fdc10a9a9268be573eb665ede72745a128fb9a204e9a351e61c561fcd7bba07166d4dd2f702250600335698e0d8ac854527b73fb139327243da72e6c2536373d66b415f19b20ec3447c2ae56f182ad3447943318bf59f2ffcf010d5c6075c2d650db50ce8daeaa5479092b51cf34afcafc1a1eb8831be72df80eb2ba04c83c6358ff16f1bdfd02e10c61125ec5c6cbc96a356adf9d7dc72ef7648936afa7d7b4b00a6c0a3e64918b1385b3a791bf1f97d9408d3f0e13e1bf79a59f924adf738be042e82dd15561e0ae47182114fdcc10e9ae4bd3167907299147b7cb14e148da3ceb50281831309049a7ed372f11b5c3e13cc0daa1200729b517f3cc07043ab29fa510379b3db910dabdcafaad4ef03822c3d1cf6b10c7282156c49cf7f7239785f6592cf5c2f28fa21fd665b5fbbc6bf92b255b3d2c30fcbbdc44b1b8c8452d1b320868861d6114dcc96e62bb6af84a90dabd8e4df01724e4bfb4a18c0a64a6d4cd8b5e68443b3178dd92c070435963b797e32c8de701f5c06513cc7b4b1701b7ed48051410c3f9141cfb31aa8caa01a80284aa0e6205dd15cc48d2ad3a4f0740910d4b16b3ee464b11d375f3d964c41ad79586be38118342f26ed16466a2c604e800ee3d133f831a28f14f6e2d0be17f0d64e7fdd9a70afa4cc06e778cdc28482f2d9846d00cbe57524ed8b47fd21720f1c8251fc4a7237ae3058324d21d9d836bff3798f16bc82d62b6d8c2931bfc28f4b6c86799772e1de2b2b265bf9e6b2d2af9f6c05a9b431994c267300b8843c9d15d03a98a372dba456cdf1268b0a2b2066737d7a430c3c17fe225bfbbe2f14e8b5da6e97ba3c1e6ae120bef63f2bec8442414471118c9bc069d61b4103062a96682f703c1572d5055c515eb83e76b12af48384ba3d0b7772baa5f3e8e70bc9c031fd6b3fd719a04673aaeccd1df28c7291979471fda34d20cc974fa4331b4861cbd07698645bfab0253a9ac7233512abb34324a29807ca179470e32fe17bbd10d6ccf67e4072002e753ac6301895a55e40e5614f30377449d358f4c86df1933041272c3f0700f829bdb34b5796cf20fc262e8a575a472d379f881a20213c7e396fe075873b20c2d1c7ba09a11804b48cad67e9c3b35f245a4f22d5b532ef25fc48a7a22bc765a617e4376e02d2920158af4a761ade6c1f73a7691680e201abd26a21596725457865e25b5d5d1774370f71091d08a291b558c1664309cfa15f89bd9cf08a4609429d7dcccd73d8e854759d9705ca3ae049f0fd7dcabca83860f6353151a2d1729f741c7825bc13eb38a85efee86cfaaf1d4ac7c1eec1c71525bc2573f5ad9d72c1652cec2921aac191d98452f30977769a83eae069640d02288bb9a95b170b318296eb21b87f7050fb44b08f7a653ec57140b868f7ed636110153d1097b9db7200b2c5c1a5b9dd07486c783ca50eeb06a496f8ac33c536ee9f623d548ed0d350149aa439d5e668486bcc27fdf3fc5f5b823ca7f210997914102026c4bdd17a72d54a26eb134142d8162f5198fbdc5c65dad7d88d51c5b3b513369d930f0a4172e753332c44b78ec67bb1c30064c666fe921df8b7a3dbd50a892a0781171af4728c2a0d8831d48c7e78bc9a97dbf3a5b37abaa8172acbed2954fbab02718ca24cc615c9ada0a83bd4eefcbace4fd08ae0c4390ac95a9f103e4f8fd76a2418df027f8a4546f7085d7114f9848fa855211b581c71edf75c0ab1ae09c8011990050a03d21cb4d4fcae875ef79335f13536f5e92980d293b04a667d2a2373ac71de72fd2630f0549578cb08ccd4f5c0751ee00ef3a54bf5e2b9f5ea49b43a56967b72703218201fea41066c341ec148d85ec5764b7bb76c7efa5b3abb12d884cc1a727d0c184723025471196c9e4fcf3dd3b3f8d09098fe1ef2ad8009ce06ef910e720e6a8fe358d3e475125f373076880ccfb2e26895c976a93189d7c1e2d34d133d92e9f662fde5cbfed9c6a3dfaa21d8bbba20969f128dd56ac8f500f1b1ca092a346a5ca9ee8b7b503bd669ad42ca7d5c8b03e4d0cb6ef54705697fb5794bf472086506b7d9744c42248b0571150884908c9a34e07b4a2a91bbb103481a7866722a2e6dd7e6312db84facd3edb57c4357953d693207b3f3a2fa9aab73d336da2743901cabb506c0218c69e852106c282a88f3f34737d76cbd7ea5382f3d638a72e0c23afa7111afd10905ef52005f643e7545735da54ab0444044fd3d5a3d6e7240b2d3606a99481b9127a1d6cf3cf0572da62182adfba4a5f4b0144da79f25726a00a9541a54753239b588dc233f2aeaebe07ca644ff33f285b62a3b37f47f725f5ca3db8c9ce0930f1b33f803689661bf3b94d3dde9eccbc59173c78ced7e3283b3a6f6b49822fd4e99193ed18a64addeddc29e5f030d7511d6b4f139ba63721446e10609b00cec8b448a4b20636c583c34cdef6894d68fe913bda910ced272d6761189e4ba59d70143cefb1a1ddc0e73c36fcf758841725c2fc97501cc2c5bb7cb2c2838783f4b46c5d17f7c51e0ae03fd1ca8f696b31d5835edb58271304214aaecefe821dde3ddce54490ea74bdce606c9a17e7ed2adb245740e6246df31c69ee17a58bbcb1c3abd76ee382027c314e67be7409a8fd94f13aa2d36c884253a966b51767f02b5fccaaec211bad66f422b97afd60477ae2c34ac4bc925a072ad1d15f741be9bb2a655400ed99e4dba9891edb425662ca0be40245ae1115372ec4beafcd778430e410881598f6b984f4ebe38157a2f881f3adb64e79e5cc672e58d3db949faedf827e1051659546aedbbf18fee150876efe1db7f4856b259557573446c25fefacd36e02d52d7fc025f38adf5e917631132095831e8eb63b97215ae79ee9839620bddd5c72aa652288e78b90b8008106acedce223517876911f5a9a6eaeff54aaa4fbe433c29e9d71f1b11517520beb8af9cf6382e91a06b5721bd5de3af68e505e1c817c89fc2d6328004822864f9fd00db5bd9d61ebb9360490e83beba3699d8931d7c8e5a4f19c7df47bf257d445fe7787fbf50806754e3632d6784e82dc3d3be4b0b143e61e99aa9a4784c25ffb7f4a224e83ec2df42326b24aa06781b8233f88d622fac2acaa837d060c7d78b0069b48a4cc55dbc7fd722e237ee12a7a733f3eafbaa72f13782b2d43e401f3fa8f728e76d23151dc9b725c1b2d6ddba3e730a2f954cfb1b21ef309d42f8616a84b47bcad1656ff47eb129b1f50f4fe58f64000b6547610fedac7d079f46b6cf2324365ca828c197bda72becd4eb77778cdecbe26b0a15edc5194ac4551962c4c3042fae3e8af4cae9c7203845a2aacdc9c03997e1420a24be08e9024d4fceec68e41caa9337580e59c72dba6105829a0f91e50fd99a83792558208965fd7db09df4b319b76bfdd2550708d208edcac0188690fd5b0806cea8e94d59bf8b422edbe94f513fcbc8bca5413a7506e82fba2e2e74fea2d75cd2a5a63c26e7db2cdac6ef7748d64664e1973725e493a5e48217dcb20db9515616cc92e2556885265aaa00dcbbfe5b34244a172c4c608bcb1ac871c604faf5c251b50bcc2e636982d1276347c9991756a09a569737f83b4b67e5141c911508c0daf0a243ad44e99a3a9edb2d1f7728a6208364986ad24fd375ce902452295a36d6d879dab67b6d8c23e6566827e686677a79572dc7d971be3f3ce9af7d1ecb9d5db5ae627e9d0ef8c2cc4a65b283a801ff8ff729dc30ab87fd2b8109b34e8fb409651394efdce12274e32f49ce18976246c05199a891a3adf3ac856be8d0ee4952bd638e3976190b6ac01c7a46b0ff2d45eb4725eafcd8c38ee824b701a025bf99c07625c832b9dfe525abba7934fafc1a76a726b8a03953eed5f31d3064d21fe11068ddb63ba81c73f320df3d88643e6e09a0d97747539dd3251c21c0897b51447083f19ee4359a5106813c061420bbbe89a72690b5b193a4ea595883252249b3822eb3925db4ac23961295f1ac00713583772cc2e8e9b1a9b4f09b42c227caffcfcd444a77094d284e179af631298ca03862474149cfc9e460f7966ce12e4a3779ac0190c3b85a5e3ca3b1b7f16fd37717d3f6c99b556d9a87678be44052c4c864494f772795631f42342f35a202b6d7cb572783136b27876968c97b6a48e9ad21af66965e13b287ce2ebc0d5829663ee407269561be9af0d9ef2ca36c0ff7bc2b6fb05c6f928393d4f008170c1ef53e3fc72f15973f1410f8945c6f7f8f97ecc06454b9eb4dfdd7d88de8afddba565837a72603a20fd9d2b473f031d8f0659324dee14405ffceb50615cb2fe5aca1b94aa725044989a9fe5c1bfb478d5e17119d923880b3a6561acac8db90e74930bb8e2729028f14253eafdba6ddaff59a4b46192a169ed2349cfa137f47c93ea970c33726616a2317ce38e8a88e720d6a96e3fc4245e0321d30c0a74bbbcc77b3720bb148a17c3b0ce37f7ad83d110d6f5e6d656f68c4a1697c90dde8ccf8c1e89df17726f454c6daa1102e3a794ccd31cf03d1b28ea9a849d807fbffdf84afa717890151c3d6aae12ff803a45d3e75a274a147b176004f23b2599f02df4203983e06972204be720850a0ed140ab97a04fc9e1f403d47a1c70fbd3de5c43b433be7c9b4639f6b138848f045bf04301b63c304b457f5209a375606dd3da0a4b17109b7672d49f4f41732b890098025a44afa6de40642995e4ecad1fad34d1e56f2b0087727d206abd33661ec2e10e7bce62cac87d155f4b236051944144bf6e0bbb98a71eac4e0cace4fdd14ec4c419bc888b19a5c5d900f875872ff9510f16e6e245137233cdec536f6630a5013fc1477663643abd6fd6338c12024ff849f7b2e8231672a82cb5f9b8e93b8d020c9ad294806a57a8cc74e14c5640a16e01802b74ce627216d11c3c82ae0cff39e25dce6b8ccde483e272084260139ce8237f56fc4a9172b68ad67e3025f8867c11c203307c8b02bce28b68531711ab9d819d72590586727d003e8c967b83e3a6f75e0019b4e7ff953842f54188bc1bc91b3d308e5e1b2e3aa11c9764bc4b86b9bccd707448b9c516fadfc9456bb52550f0f5c42a69b83af0f310e863bd4921117aa520f3f1563ed3e001b01e0008cccc62f56b2bc30f723838ba1d8a076b96504d06e52e365d6a5141476c6dd9973d2b2127a647e5c972b4d0d990e41379c0f4259194874f7d40e9468989508a077fbdabce4480f2c605c0252b73a6b07d7f9e9fec429b1ff7e92daa97a787e52ffc3722e7a5be3cb76b03860a657316560674b42e29f24c12b642c1d72cd2fb37b2aa5464b4ba9b99725aa18ca1fa4a18765a86b386979b608298246a6d50f6d2681c5605540e1380723b094565dc202e2c940ae4f9eeb234ba5036dc826f00b67f7e1401bfc2a6c2727f406bdc6e52b44b3810cc886a289046342776819e36737a7c65ad26b35e0172be6a0d80227d9d407f461f977658783badd872c7ec199c7f3792ec486d80084442de02eee53bfd8ba9b6ad1de720e9a9ee5b462e83357c0204fce220ceffd0154f16365879df1d8bcb6febc8d7c2036505c2e5c267661e36f35ff7f3f982ec20b09b9c3e8e0f50e42ae552c0cc85ed2f47e19a9c92f559eb57ac0639e833ac3f66c0e70e87cb222eae1a937d95a06a8543e66b24291b341062f49ea61704717272b53807922b664f670ecd1292b047cc02e9352aa7aa0e9c88da50355b6eba555aa19bb26531c867370278ed9d2448156be6e3550d91f326725b3b43716a8e40867837c79263a82fe958418796ba8009fb32abcb6fb404be1124828574680572fb4b19b504f65753772a5049ef53a70579210a0f04c65ea6a51e18e7ee841172deb890e30a49547065eb431ec53014ed3e0c9efdb7c10ec1df8ce2f0b7b7f6721362e095953bccb2ba54ea6dc83df354ade35be5b9e839bf1188fc58eed31a7203538e5d76c8192fc4347cc8cbf93752ff6972a03c14b80378a34420ca7fcf23cfe22207f2545a8508af68ebc940fbe034f9897936feec8c6f335c656410fd4e19ec3a5db1c291d65a7f59af83bfb68c7cc2eaab27bea03e9e530ff7d95c9172a3d97c5abe04f0a13ccdd451587071435011dc2100da529433a9aa373b66e208b857476247a1d8aaf466fe4c8a00dc2a312c210540e921027714fdd76c5be363f9e9b9e44fbc2d1354150bca7469ef58720e51119e98c8a6cd0425e6d8bc2355fbc03953fd51501d12a51e934bad483e64b135d749062440cbfdc12d33a40b72424b7aa97343bc62d97cf735054578d36ec9565c96ed75554aa9c8276315f728958810bf33dab5f6177aad2a5ba4cd06d691d83a11ab57063687bf8b1b6aba728474e41d9d517b5a3929f24bd34fa79fed5f656eddd0b1a1f1446d0ca0b8af027ae373ea420f0c6a6c27bac217c104a1251b310d84a4b465316197b79b91bd72b16dce81e7677c13d79f505542d6f7f66475ca6da9b8d3598bc361e3e49fa34ddec3848b64550edb0ca952e44ef116d7eb519f2708d345ec04c1e7faa0986772d97f5cad195b8c79be9eac84f0d655c176a141b24723a3ff869df4c053c6103749f4ddd0584c7f4551a44a80a84bd99696e4342921556313179668385e228a550844d5f907c4e51e124041274aea50da8e16ce7ce819d8a84e4d15a84daa407271db46086bfadd92a26cd613052d89683c853aa3889d135203f3f7c46676af720a73880c3a94abbcd923f021eeed797aebee25f240138f5daea5988f4d6bf73ee15eb6e07064f71749ffacb2fa23cc7ef8553de3ad00714afe0b9444fa75d772271a05ac740d2f998e14d443d3deb5f405845484b8292a3b154a263b26b06472dc168866c5e22c78c2c4b392fce78a60b3b2b48885c6abadf0ac53b1b4c1bf72358a66427525402d486fc3741b839657b11d5fde04c7edcac518e4a0a48c1c7296d12cb9c6b2df2298d19f8612556cb5315c5159142454a437ae3e4e15bd5663d56bb726439a56791ef7f904c1d20dc26ca4aba6124aa6e9b291ca1fc0d42872976771b84f72cfaec8c1344adbc22754443502d910a667e0032ef5b31ae0f27227a94228a2c36efd2ee0476be69414afade02c0430d797f1d41669f5c014066106bfafbc23c0f388d7dfe1aec2fd913fa694f4440b9eca924bc4cea3290047727a7e46171519889a242b592aa4cdfd4c67bf06419717cc1dc40887208f2f995d1ef3bb75a8a31a52356d14c3020b12b5ace4a6055595c9ce754d3b7310d4c8720fe90933e18d54a36a05f2ab051b5393c506e5f3dedf2cd5c1063f67424d353a4d4bba25978234a303b512a98e4eade851879a920338dc3f72006f1b02611172e8ca56da1456f353c34926b700b06adf5954f61a6aa03406e3b38b5f02050c0b7c54b08d6a645f89afc5f1ebe7b5abe0a1faf7524d73365ed39386bc76674f72ba09b8cd1cd092d498ca9c9c626dc66da7a54bf9a6dddf8b158dfa801d0afc54ba6a73abdf92d118bb7ba8875c19cc2944850ba080b410d0e49723db225a5b729847d4192a20c9a34a87b2211cc726be58024e105d0f4c9cfbe525c3dc791072bed739edeab705d999481a923a7babca2a96bac3c777201ec8741bad63991e727a9cc1d31a01427b193eb3f451f813fb96077fb48ff62fa8784bf6b80fff847250d1c4b91cc0a08338f435f2aeae6bf6b65d348af255442692a8a7704f02dd720f3b260a2f74c8e4bb90e65f7f563931b814165bedb3b57a3a43f5d745baae0f0232a7861341b43b9a20286a0f8522ed595cd42756dc127d87def12d0bf7a329aff048631a56525442d78c78dc58217f8018be8fec43e131c9277eff840ddb724e8ce1b7e853795d4bf17d67825012a2ac0dec95f216f552ac4dd483ce99ed7254264ce4c8b4c64fd186a18421562ab984bc9c0ce56145bda7a0220589085e4033f9f89ca4faa4448e4f9fc6b9b017c94a8ce7767f453252d82bc21389de53728be3eb88bc5d948405135f5714ce1f6191a5554c247411fe5c9f17c275d33d72d1a7d2ca0e2be5c6ee62f0fe5c49ccdfd1dcd8424089e3d2f6bfa5bc2026a672e73cdd96bbbc920e5146eb001304ab95d92cc73af5f16d1b9ab3695ddc47c07293615450e3dba27f88a9758c3d453244ba274cda6ea335929152239a4f299d72555f23158aa00ef1954a227436881686e32bdb25a4b7c13cd7a21ec826d79c721a55a9dcda245f5bba3707b8d4cb579c355489b7ae9dba79ce29edfebe66513026d16943e198a1b18917abcdd66a6078536933f3f74f704554f3cb80d4a8f772ffe4116efa87a0fb17417a077d34b727c7cea5b5ef0914f870faa96882e2e372dfe9bba05e85993c972fea86b462d306b56b91126048ad881d8ac73e990492728bc9d1580e204076515b9a0db77a1f56c08df9350971195d9512b5a6e01ce51d0471a2920673e5922f14cfc6719e7fc92780d6799f9a23aa5911acbed35c2972e1723e06e903be200581032dc13dd51201c568d3dac408bbcff62668f411bf176420750622765cedbd81d08e524f36d35f4bcabe724a82bdc8a814244c56d572a88741afc27bb8b2af246e78e0ae25b959bcb08ea20cf6eba8a5227d63e07e722086a9da75f40edc3f02b38d10e0c99752a213e03fde58a3002cfb82a5693019b177c76457744ece91c9cfb6c078022cc8288f4284f0e260619edaea25450372f7e00643e75824d456be7bad8f277b154bda8b991fbd60685f57a0a2379321721bb5405827b397c30ca7eb93be7d2b01fd261e60a42dd18e810e6a17d4574c030bd8ca8f21acfe1fd8b38a99598e5145e8868b422f18e1e1a29e4273b310ff72e9a6e4818675e5a706a7097ddeda5a133d13aea4236ad8401e8039fc2514a6530d10166f8d91737505ec253ea5196d9d20199ff3affa7d4d987df09816a39c72b8a0f75909e1babc13e40ca75ae1fc6af0008bc55af2f67c94f99ebbea7acb72feed4c0d5583ad629aa19b4b29ad943cc9ec7f0ef9d3a11c96c7dc31b49674720ac9d48d385a2da3e3de6f6eae53ab3c01d9e5abc53d0acb818ec60a469afe15cbe5f2c7c6b090046466c30ed9bc04c84879db7239c474cb28694e54ba0b4c41689cf62d73986b173c1f35143ecdf2fdb68afa897247b30b296173228e69047258ce1adc815b266a6f9b26b24941273044fc352acd4f8b4d3edd983c1a35cc7271be9abc7a807077051cad3df3b93812d0746e10b035311301bd79a034e1b73eb852a534897a8f5e285c2b59e4ff291f95655958606b749014c09342d6bed372ea0d2d7935c32eeef5561e853f5acfd06cfda85d721ed9895ee4346afad054690e2e869e00ef69b6a2eb6c7e9268fd0f504f0104fe240958c945c4c728598072ce1426ef1bebec5f8bf4c7cb0c97b81ad5bc5d836a6e353fa34909559a678172c0265432c9270de3e5c226c4b76bf2d30d6705473ecdc192f06933b79e60a1475897e7c2c7814006aa16b3ad390e8710f810a6979ffb9a367a5ad23309f0e57225ea94ef3f6ee9159ed56b88f9226072e7d9493179372c8b4f80ed518266217207bf26fe7402cf03c09789c6028064d1e9b9a9c76f731a8e62637a9262d8542a6b4a304aa32ead07d5226760ea8d3b9829929eda4cfb516f26d4ab6abc12667254d5e4fb875f33df3748c68238893ed99e07a6ae1104fdb5da3df494e987ec72374dfe7b6c06d87891d1de606ab7a6e4ddd719ff221ba29c25dccbc3aa74fd48cbccd89a2a5180e4b734a4436d7cc4b3c91a5c402e45dd1eca00c6fb3739e844919fc35dcfbf0cd25cc8e5f00cae7681f92110a405fbc354177bffe70b2b7972e9b3eeec1b55681575da4505d62127df11c838376908e57539445a1f6380ec21b87172a60e5219b400e438ba8e1ae5ef80e2389a5ace08d2968dc7401120db1ce28eda14f814fb83ed80cd54d44b2e5105f4d1d772f0156c7ccb64df8945487279ccb95cabc154e99568948faf1dbf86732fdb48a2d8c4742502c9c4cef23d726518055e78cec4b2b7d38746328dfa4272c498fa716b1a7258020861f09fd37243ba109e77bb9d776ddf001fb65302caf819ed7b92a48ebc1625af1beedc19728821a2270fbe203fb95c52d22d05a87f80d67396e67566f6caa4c303b2c67a72042c50e424b5de959e7cf63f8e3cf1448b2810e7c198db8770355ff546f6fc72ed4e1f522e07becd2d64169567a646bff1415a5343b410f8f9dfea7d1f0462722c1e9a9af59a00af2e434c45f10e5419718610f0d6698d2a2b54e4341c5077728c289b1c30383093afe110bdf26198433b7809ed9818b413c4f6e729a8725c7297e55d403a6321dc3ddf39580231004e44f1d39f9799b7f47600bae017ee2372a7fe5c57c720fbe5818fc01e4e75c4e6ad5ee8ac55e66370a845899c46344f25187c6f252224bdd3fb4c81a304ccd02c97496ebc7faf0f13f5edbfa640e0417217fc5ae109ba280b1ce66a9941004174cc0c68decf6f401d0afa94f8c975c259a143daee3d4db14cdd90dcbb2d4084dfe6e3b1228e11a743d9e7128c980d6f72d27f83379d00dfae353a39fc84411531b31a94aab004b16500b1a2165f83f172821a0a324b8af8eb1a96f00c8c81db9db0428b328afd6ba3b58f7a58857be1110c075cc6875dd171b8a03c38919c3acf3a8d8112309d9f37ea81227a71a4b172d6dfebde5e61aedc3bdd7aa71cfa3033c9dec40b702d9270dcfae4a1cfc2f16309645614185ac8aef4c5511def162fe529dbad5a4c1eadec52f86cd8f729ad722ca58985d457e38ced833a0b5dc13a22ac6da59a3d09e68b7840d352d2c32f262a91a7bf3e51b6cef9464482129bb2b0f9e7ec189282b6d3e73f312b14019572e9633ae8ca999a7873c4a2412beb228d15f323f6ae60b7bf3ea777daca5f7d72425676b2886656edc44db9f49acc99e149c8885dc5d5320fb73c51ac34c85e7280ec44696dee4c43b6f48dd60cd8a60a9608300b34c2151efe2dedcd10e8f472651ba733e2c398c1f1ae7d1d9f93a23e81e5aa48bf5566f1cbf3072600c78336601d4359956773da5e7ba214722ddf5db68878fc81d3a752224dcb6635c35b72f33d6bc8d28a7e3d73118c7a14cf2bfc1aaac29838d268647023fac1be86835a84e84afda9cad3dd15587c09c0b7bb84d52fddeea0d0fb33758f3887f653f972d101c210635900c0eced024f3a9ff616b231e28670f2d49820bce3741f409752a60398bd3d27011cc732d0aebc136d611440ffb9dff9a3776cef0169f2dcb1359c3d6988a19a7388327c60845e676c3e05a6c6245d4c46e6ff1ea853d7f5f7723b965b765d7eb87c1d4537fc6a7eddf375d5d54b889b75316264e4bf7a26680474f329815acab88a8b1b6627d3db85cef2608254082bd0c95bd135868663ef0b78d87879342a195a467a58e9ed1cd805ad13b5df2ed783c61b2b98346abc4a4b726717091742bb05f1f80624ef9918b6df71020cca0cf994e420cb6d12fd5351ed0bcf464929938a27066856d39deab8a152fc8205705872a8f6cc873a421c728ca94df33b3d00fd3a56856abd002307bc24a93092aed73ae1ebbd35f7abc0165effb292a6f841b1df9ca8260e414028af7e90c615062bd6dc31e9983d8d436ad5fe09e485fcf4f4ffcdf557feeffa45dfbd3be798fff98f8114ec22a6d59f72becbb1f56f34b2594e40c16561ef1542a2af5e9a96aa04268414393b02b3d8726b8bcedcb8e2909b61fec8e94afb349b20378c974be901e0c4d111ffd2488b72f0ff8eeaf109d8381dd06003b8224198ff277c83eea08857a4820452841db57226c4d78c5b5147fb75c6e4932a82aa4fda8002fc0cdb5f5a42454d07ca19f54ccf3401f6fe0892c0480de684915f967361d956c66efc3c9c29598004f7cf6072861744f05e587b6f9bd4bc189724867ff0422b0f1a09622568019cc9d3052272e6a62f16e9241a603d460dbdfe9979804c9611f7a537f9c13b5f45db1ebcc4720c3e05f3c37ed001ff30ab2b3e9ca2a68c635a439509fc8a9eca035858474872e8b3f21489b51d2e35a5d613103d2da8f0c9e43e9fe5243b9c86f5a2d3fe9d72eb14771b5fe9f848d2459b3545e9ceb638cbb7633259b00d42e2bb3cefef71720dd58a273b657ba45ff65a29a24e709378ac24d4f0da354ec1df8bdda3f4782ad8f6594db476c59d1860694c84e2ae3d631f9e37797ddcf40a60a4988ab984724f107b122c7d0bf3b2b31145cc02cd6ff50afa0f2f0d7a5c74b8b22673592014a7a2259b8c607f3ea5cc3347bf909106d5804326cb0bb50ae286b0aae624d27259d427e777158a982bb46bfd66f775dc32acf45318ac9a443875fd381c2f65721b02a751ce06f79deaa03737d05ec82a183295d828de0e583ea7dfa09b7b8907afb3a9f2bfc8b996d1766893bfeca96262cd8eeeb0dc3b0939d4af667bc211723b907657a73b74fa738107c840fafb326a7827147dad7d2fb3354c30b5f59072b776cb2ab93a4eb9dfbe6ac23fd905cfc7a3a81603f50eea49b310eb9a18bc1e133cc393a36ea69514c3b88ae59f2fc8fd46105883ead6ddd875a00b0bd20c083cce12c07d84c64725ab63619044bd180d1eed0c9b829c6e62010c4bebcbbc723ba745cdfa5e6a39019b4f1da2c75bd5cb6e6a404545ef7272d1dcd8926f5168e370158a9979c5fc323a8e505c47db83f7fbae003a2ac4f1491bd9f780f0c4308956aacf393f088fd61d0ceb3c1763cb10ed1b8b389c24e4a7ba8939f88222049692808f19849b4de65c5e4a5374cdcdda5e0cd5aad0860f64f8d1e489ded13fa834fa051128af3cd09eadad37b96fcb59719a23246a84a4a10b8476d2697c720c8c5cdeaf3beddcb97a538a09217f72c34a048b6a1971d68eb0fa68f54cb67279cc2b9bab936ad33470761039c1da1bbeb1dee853aba0d23c14bc9b319d5972ca820230878d2436863b6d3aec5c0224d952896eaee754248e9bdfcdd6a9de7299faf67d8ce97a4d2e7da7f73832329417bd81d31d297a3dd85e2c2dd0247e721b65ddb62ec5536e99f2b6235dc6369d8bd2d5b62c47592f889dc1b33db8d75fdc46be92914ec9bf2981e35461f4e6512bb6f0ffae046737397a1a21f2f354722a67452cb977e770b1e54b30452880da6c9020420eee70ac23e87b2ca454d13b76a151762f023173817c6b386af17ed8e957c28ab5fc79be8b2106932652d23c7e4b730fe595c5648b8f0ce4073a48ae39276fa577c7360292b41a85baa33472e03f2b92c59f0ca3064932e6dc20bb660a0c60015b00eed8a1fe2c1b87f3dd50d15be26e30b8a5698d2d6d457b1ce832019e5689f701349fcaef0d8b539b4872da65ffc41cb7316263883c99e416fe314eddf1f8e39f0148322bd2365108da729dcbb3ae75418eacf4aea95b202046547af6c7c138a284cde126b741e3e63972b905112f0d7c7db57207e551e2874548d821b42bbf9b849463bd2bcca3f0ab4b227b51aa8443f502215b617c9d984e66d7c783506bf0e2c269d6de7e6d514a3de9fafdea00cda33d416a805c213dc233b85c59677652647dc2fe679f832337721d84e4fec399b6e88181d2b3c853a7f2eb6cb689a395b7ee728cdccca7309672ebcc0381b1df3b97155d92f49ef4fc8d83e1aff7e0366bf2c7534a3559f5ae2bda28a4e3cbd5681338c93c1c515346d1f6be1c44e359d3a9f9ac702bcd6458727788b03b810660a417c4acf7066a07233d377f776ccc037eade177aa54431c019c12388a06939ed8045cad33ffde21be8869aa1161948fe139ab6de7e3ba76437ac393e0039f15ae023f8ac35ff7fecdbff22600ffc2af27b63360a8dd1cb769ecaf931cf6ec0ee4469572a193ee30ebc404ba11f3da4c25129153ff0612810a9b489c69fcfa5fbef6318272f0bb0369bba32fc0a4ece827aba1f6b714867e7296c0735b5ccf647461f6efc72ff62e6577410c5bb23befb6738c543777b83372763dcf2ff88b7779c1d2a22389088e39494081dd68e482204ed54b0959983e7212fc6fc5dce355241cac2ea42a2ce633e5a540456089d0742f7f436b1d1c375a107c94ec82ffbf89c96d91dd9a1e63fdcec8f57d8569146cd4747c61b1096872beb5a4cec2ea99383cabeefea601878290c029a1c4c330e4ca28947c76850072224955f4de4403d6d296fe0867675654efbf53b983e3d962ed00df24cf0334244a7f633fdf6cbecce9c6df55a5a5e8b50f8881caf95b1b2e2fa8dfae62a3d672ddd0f2d619add3c3db50c9cdb819662effa7ea1efb8d968df2d40e3ed14dc772df12bcaf19e5add64a87e9df0737cf81ca99bda12ec44a4f663d6dcea3acca7221852d39051c51e106d2e8990f90a2b00a33a0e0aa0cd566bbcf7738303107636fbf28178197e102a9ad50ac912e7a80d895697e24c92e542af969bbdac7676e23c0c657a3a8a7744c3b729d13fe3335f5f36d2e48e349a5f300a9d86ec2ef67449a74626ffd2c536c953dbd9dddadc3f0815ce503e5ad57570034fb1e7ed672b1ab5d7ab149ba9af86f6d48c2abe5373a9c102d4a1d33334be7dcbfeaac175d055b7e2429cb5d2b9cb331d684c30e8e9c07d67cb2bc05d5aa749a72981d2c72a2c9d016436bc745d771f7f65ceeeb4ff568cebe5cc15ef1d3724b38204ebd72cfe7df0dae7e9dabf7db9b3431c09a6b022a2465ef0636e806c53c20e8e3c072bd2ae0f4159b270f92851adbd71133f50f4704667699029fc9041022d341812eb626d06cee08a16aa5f8af8e2a5de6f93e23c50cc1f304e37d3a47f21d088e6ed76d640dc62148cc4f72f622502c37ff641d6caee822a58fde024f8898ab11723bf59d73a3e2985d0448862dc7094af54bd111a41eefac8f55c77a9c39bfd95829865222a6f4514aaaded5279d9854aeba92eacc190860ccb80d9a5c739ea35ca8ce6f4d58438fadac09d7e23c08f62e362aaec86411fc070ab1e7be7164b9722c61db8e4ef0b0b4c898a19a1c1c269d4d9d2f9958744669c30f6b4616b88c6e71b1e14dc246c4665737c1e5d88a1fa7348c3bf581ae0ca95e2ae6489369e8503de9bfe6e31b1a232c832a9ab4dd39588787df362a9e8883b08c06ddb2918d72405ea759518bcadaf230c0c4f5bb0a4a4d94e78f04024cdbd01bd9bb4cd0d372bf99452dc7d37b7c5ff112d47885e568f30b8ab0e6bd735c8df3de09cc31bf72eb550c4a4ff84e3d8b302ca40b0f45298e19bfa94f2d299a293d9d8106a78c72407c9f6e49adeecb3f8b436cd7cc4d979cc59d6f641bd87df1e5d45ac7b453725baf6d142ccff5655bc8bc5adfc2e5cd762531af2ca1fa0066432d58ebdd83559a35bfaeda5767c0d79939f01e64fb74dea6de2f0b7ea2b9a0ce9d41407c4372dca93433ab67463139b78e222e1574a169518ce785e44488c30496170280b47280ff25e1d86f6c5d05e8f11cbd6b95fc77859f5866e177bb89695d83b0588c10457fe76fba770a35794d179b2de2231ca45a215bbecea2aeeb579f0d1fcfc4725a00b4eb11c50f66c58395d5acd145821af24beafcc3ae8926e1dbcd011c5d72853501aa9f0bb29b39f51fdb27304f97af641c84d66d846318ab14d4787d89722c94eaec64de1f461152f2a0163ce305af49ab82428b792dcc06aa464c5a551ebe5678129936b45eb14e6043deca27a2096003f8c70191a18534c09c5ff67f49a17c893a83db0113db17b441a831c5f4fa20c1d78ce1d4a852f474b141b09872e8988bfa5d620eccca64f3386879f977c6045dbda1af70493f7a5453050331720dc2b905ee6ee23e68b0d8221ded82abd23e928d954c2edca93bb1d5b7c8d62f9aa933f07ec58c0eb97df1f409671b6d95c14698bc459fffddb631f79db03772d077eb82862cd99e5d29989c17920b074b19202c28a14647144ab4d581d87648b233e2cff18bd7f0c94599976956318af263e394accb877ea128c6ff57678407729c45866759e417452190aea05d263bbe43a1b2ada8b3606a8c0ca7f5c9e7457edca7a0d5d9e75d6597a3522840e25630d8aef04927792061752b786e03714f93dce745ef935226cf9fe38b0924742ec152a7fc581fe468ca01b4d6d58ce031207eafd784f1c4c1aeb6e5d356cbb5f7de1d349781e9b007abaf4fbe8a4c9372dd8b1b748a1723c79c17ccdff0c683b9066547d4fef479d74610fea17fdcb0724622bb221beeb56e16e80967f3d9596fbca6c5eab9c55a08f2343be2da622e7292ee4f1fdcc679f983592df3b23ff7b4ef807bb7042741f138674284e50e9172f9cd91edffff479929c34ce5acbca4f5db64c172d32399e11903a8ebba8d922703da5ea94758ae475229a552174f60d998f0630d138f6d023fc22316ea37377296420b991fbbaa1e02683ee32e427f63213d3c9b66727b64a733af0105a0557244ff5e5ca2543a2c4f057e70e16cd66cf9494ed3ab9b7f69ca4929006f2b805e151cfdc697eafddc6284459aaad66ac37fad50c678946f65318377e84748f51fa8c8d5087b2af68c728353cfc97175cac10e1322b50e03efb571a4bb4e520b72e9e9c1e575c540b8cd0c24df14ab5b4537752abc3832a11006f8c31967b83706cce548fb79bf05cf82a6bcad2adcfe45a740356066daf1037b1322d2940f8d7290c182329eb1c3701a470dddf330eca8db071aa69242ddb60f206908a9affa561a2ec62160a07274237faa58b67dc3b182ade21223497b9fee61c3987f6d865b5b513b92b2b6b8e50fd05810e74e387ffdf106b11e5712b3f14ced1774398147574f98bb627af210499f126fe36db24a0d248f967871ae64914ec2c8d87f1072d228bac0839afbb147cf5ce96eb258ee6bf63c673189a13078d0399cea83f76e78709ac6e3276f1fd3f8fe2e2921a5d44faf8d50a6e6073bec4099289b8ca9727da67f54411178f1d44e68a37bf78f988468f983d3250c05e03fe68b4e092d7210925a917d768bc4fe61f2f9c988090f5d79cafb30fd7e37e7f1d4af5015e24f00996b53b6f5ebd7f53b0926acfbafb54775017281cc97301739afbf86c8d43d90576e59f633441d76bb9b12d143c0c2c430e179d8fbf278d5ca423d07bd43575a37911dd3b430369b06a09784153429bd5150aff811b0a24f06cdcc38b7b3723e0e1f0bcba69b778c86bd49b7bbeb2519f68d18083fdda8da0eb352b67d6e502cf26d2eb02cec70c0c96e1e5f97ba6fe1738f73d876e4ddf2d9481a5d769a422503abb8bd89bee1e90961efb00c2309f9466fab3fd1d04b9f2f04227c00d57207b5b481b26ecdb62e0fca7426d1f64287a61f5890546ba7a2451e6de6395f72d9f786394601dbba267486f5fcbad39d3843694ad4f7dffa6bd7954dc31f0a7233490073d19957b1152ba224af8ac9440fcc1f71b5037081d533cc1d193f293a894e143cfa7a6f9ee38f8f891b66ce375cd760fd4c83ad4bd0542449cf0d2c72eeb32341ad1ba9bcb71eac8bb986d2a104357e76924db602b0a2c14af766de72584194fac50aa6d2872c46c931d0c6459a899473b531f6dead4960778aadaa00f51eca9b7ab963f7a4998c6d9b1f2500d5b8274ab4e4dd84b384135cb9ff1f72d90dfefca3784299f994db72a8d9dd7a701342e8dfd73ddc3417554861e13902068ef91756a4d5ee49895777a95758760eac736c73dea4a846cc80e45108dc17ce4b1fa631587eca0f0879c29afc4b649d016aecbcadfc84434a29328246217243164a4c836e7d17bd43d6937ee04bb454264242168a72ef879a0dd9591a817291816b3f3bfef5215b63fba8380fb0c178b6b8eff42e5701df851aeb63d3f072a0214a55ab03a8ce7fa90d2a038bc0fa6a61b60850ac1bbcb534e1cb4d4cb821720131d0b93a29649cd171f0dec5d5f16d12a5e449a460978d0b465dca3eb554f3e1f0b6247b329ce765ef1636a04ec54d27caffca8cb1c416f8c60c5cf71472599fffec4b8371161474b3a467af191f686bd9521972a826105fd0ab2e2604007bbdf7497c0754a12cd7a681f4ea3d420b218e84a884cdcf2aa9f4f334544072ec5a10d3d0d404dc1e25bdf081682c9a9ab45baaeb54f549ea4a7f2e46d06a1f7ac1349cff72f9362d70de14a77fb88421dc57070a958d7f8f1ae022aa9df772c06a3ef5933ad44fc98f1f37a91c098274a42d36bd48891aee765ff5bbeb46725e60587e2d9a15952eaf24c59fb9f3b91044616588568bf6d5b5bfb688cacb721e23fb0720ec5c72f77afc40b53248fbe1e2341e45737983285a31c27c108972c3946c2b413b48b41ec6e41dbd7ecd06f1c9d2657af3d364aeec2d456057f422f56b21652f7dcd1ed60fe4e7a5f53f7c02c8f213a0beeb30a4569932fe269706045aa5b99004e1243204e6538dce9fbc85b9b9b8f5c9ad741e3e265d1a96e97274cde69860ea67f4e4eb7ca3fcbe550536c7a3866d1c034256f20895da80f47282f3cbca5e56fb71e8bab5bbab85fb4eb91eca56ba296e85e1620df744b1a3724f71a31c27a761b4807634135ce85f5d727208ad14943412bec27229d5de2507b5acecb36eea1cacb538b61fc16656204ceeac2d6208948e644c38faac6f6069b8b7de12459a7af47ee024fdaa03cfb3aa7bdaed095ed8ba02a770a8d169a372c4fd8f6d8eadf5db2c2d2dabbbbb62f0d786bc366ba0402e686708670d9a1d726ab765ca7e376880fb4aa5f46d9dccab79961f00f012cdc4622f6cb28c2c946b7e57567d646aa5074d9472392b80639f557c60735c8838b185c0a40370c0b2001dd03039d17d999ed6b9723aea4b5eec0f67597150afeef996c172b9ce8e5d7270bf24e81c6d900c4693654ed3b697acb2ec3d213039dbeeef8b184802b1e0694a96495fb2a5d236c42e4bc816ffd8edf7e827162698cc1d21e36e2310de9172b83b802680738cd1c4f4f50f0f4a19e5ec3150354dccacebd3176a537acbbf0b03b944a195241a2c07eb1da0e144e032ec7a4e081fee2898dbb7e1e694045072ff56ac06e6590cc13921c1132acb9ee7b4b8f92738be9e2f92593f7143656d2666919008b5d8aedc682d2fb7b3bae2d0dffe5e8527ea717b089d77aecee7a361b3ec8a89994c9da24cd9a20ed3e98535e5278a5f0296f2ac84fe3751d28227720f3296f2f21d34268a1dd7f5417f885562cafd09c6d93db9b8dc19efc20a0a7216b7090b3c480ebb371e5dcc1ce7130df6e79a0f08f551fc6e301a25b0eca3133a2d3b1b09ac863f2885f56b420931827fdab4bec0088e53008c011b41db9a6b221d15d7eb59b14013451e6557dbb4a8203800899916b0b74c2a2939b6eb04267b90501cbf33e70058d18f61118ff783c64e7a997d73e126d79ab626266335729bd81107c4037e655472fd122e451dda7e2868984dc13f1ef201ae782ea901722cf8ae8b052edfb5a166c6a78c829b78264d962ac61d7cd7e2454ac0b19275723e4a3d41ca661a22051aeadf6b23dca0ea4b1ca5bb35599c18d7876db924201557d8771c72cb53b0676dc1537afde97969c0d9f4ab20b1f261d9fbe3e084cb356dbc172f9cb612e513cfd168e54bc1d0edd3fda5a9fdf4f145bcc2acbb9aa372b115abffcf727fc957d56415aa8162d1b7e85c0f96b5a2b0a8d6dd85f0c9382ad59a13164ae3709dc54bc4ccabf8825035acd1f9585df1027179e405966b990f269798c9571018e1ba687a5cca4b02cf44567b76fb8d28fae8914b2c9442d21f9d8f246f8fdeb3db0a0547b8d38a3f240ec35020d6c181df2792d550eb1e8772cf23f16786638d4804d7f6172cec98ba7140501d5d5a975cda5ebfaa6d61a226222f095f26086096734c3b39ea203ad70e35ae6e49b46492c9a6116be456082f93c35f04a697ba128143ef4113703d5d50228f10efe2613b9bbb140a5d2732706192c94ff63cb0666b32080c53a145b797afd94d2aff2b3d5f9b0aace065af72600bdcd0fced10286cf89a5921b5eb5be59d8c27343e23015fddaeb0fcb9c416977e1d17c36d510074f130ff9349b5c3be1bd55ae718cbc959bb058f4022af7279f2ed8eb14b07186d89a96613a7b7e09920294bb5fda526febe5f7beb81ea7234489c65cc4d215a2b33407cf89c23d1b09402e5c55852f4d5044e42dbf76d724ca024ea579e9abf312d2526f64563e92a5e56a55316aa34f7d3a26a49c0ab19e21f5d657dd9ce372365612cf76829c3067d7d5d823330bde19f91d69d0d2000a395f1819b31b3175535630829f204f5a644ffa558c1a6721931ebfc03db3159028c0c3aab4466047fe596d90a7750795bc08a47d9e6a56b0a871788724bca3b7199467f515d3adfa549d000f922f74e4181f85ab53cbd59dcb6063fa366d83bbef939b1a07a30840365ded95a8f4452deeabdff0b1e03d72cb5f4eb3a8b17727ed767965bce6370ed2fbb594346b8e4e78c1c62ea012eb92ca7492a7b8e47330239edca8129b61c869ad473cc1b498c8b50c44311fa3db8134dc015d4320f63c40cc40c5fb819d3da84c629cbe095a6fb4238232b7923d324df397a1836dd720a3b840a566f2e58d21aaadb3367c1ad470ac00e153ad330af01d4948126a17290b65264b869d474a47ebf00d86c6cf91a96820f76ad264ce0884e681849e77274d9d24e0ad6b94b9d972a4e36c8241588b26939c7c2c3a5e10ef49cda91f45b46ea398da0255086d33135a843b3658b8a2723061efb4e8118971595c46c3972a79063147b881ccd42a03e1c88f3c89d5a5ad89f7319fe6fdf6d88257bcffc723f1ecb10ea001948a76e2915a17973979db40c3c8162df491e868adb6f56ff710e124c29662c283b2126595dc77f903b53f7c1fb796a71a9eba1aeae5897d27280dae1e9feb90f0fa3b2a04e9bd6c74963ccad09660c2fae4d13ed40d28d6360f864fdf564181af4baa3549dd3a81f10b41d31b63dc101aa80085f1143459b72ef3a179f48c83b487859f6ac9e52bfe9c82178560eb7241f038365a7d1234a72547f2ddc53f927d8be85a622bd6079aa7590bc81af12f3d94f311f50538be67235db29ecb512a25eed134b8e26b447d59056d8f833a875cd144ea8d3449ba172d964526fea439e439ab51c65a7a16891e620d0777905b20316a94f938f1f415158983e475160e1c82b3577245a51302b278498130a67beffccd9da18fe9c2c6689b5030730eeaa725231b4eb38478bbb7193f113be7d9d06520af430ef2da21602620b638d631e33433e7dff483728f03c28c0fce9ca62f0e3fd6e211a36fe72d97e3070c5d11aa789ce2e7679dbe9a70738de11dcd9c624c4fd05a0ebbc77727f8a818f923c7bb9c6896ab3bcb05879606eed66a794f536c1d422537ce6aa7251f6fd0ce5eee28b98ae4fbd9f6f82932eb3ab4732b9b75bef3ba184f9fc73272f2a85c58da1d42f790cc89225f6235ce4196f24d3aa57f6caeb87abf3533516e2d3822672719586037aadd1d166afefdd3679cb9c9488610689417c08bd7072285d8bbf2c3e14a2fb8b6db8093a61a044009a9d10bc6121856798148b47f7727e375c01fde2225325a84b1b91c7457388c65555be0b2a8e762fad4705f84e720928708df7526a6a95af38d9fc5f79b1fefa25d0a851844f85582f0fdb54ce1d236039958a3f0359941a8eaed957854bfeeb2e624956547d7ec2c56f8b929572b515e5510d73056f0908c168c2f3297aaf88e6b5680fc579d0c625f025c75863eaa01253d78c19ece64ae6c44ddd1089e6bca800ed92113e48f4661d1e84f97234e26e6b17cfda5a8afd583ce7ecf1462699735fb9058e334eee5d68b731000f80c82c4f06e26673da4a8b5fbb7bfaee7bef2de2e27da60d0d3d3227b5cca26d26742a04271d050101d2864987e840a36d598a26f2e50e0e78700bd0740dd3725b42e053d40a17b86a91e60f0acbc01e666afe9685ad8b10f3ba4e5f02aca02325aa4a3de426428b55776004d221fde26a3483336ee7ff7257b89e3a43f8db72fb3924cdca742d85e8f9ca6f0fe8de55fccfdd468d2550c9ca805fd4d5a1dd3144fe845f212df888f49ace9cb0cf4c89401f38ed6f58baf92328f12afec5e429b8ad6baf39056336f7ba41c14d3d88d954a5861ea2d5fce10ec6b82d040ab31d79a17e42de36cd2d8f312c03fab4715d45c7ca5324ae23e91fa6350625700472f69c3c893c0e07b1ea408ce10466dd4e30e4047ddf4d2f54db0db99933b73a23974e5f976c5d03a36c43ad489da5f6ca117df295b449f534ff6c1dabe43e173b8d8d4f109aa073828f31d72408773584fd71efa94cd85262e08f89692c5d7d3e1ec87dd50d27fb5cb7c5692d0a84a183e893c51a208d6a51c66ae78163e0ab49bbcdc39dece1b4ede5b6f58f9ae299a386c567215e55a3d81568107276f1df7219663f94ef6670a159e9c00427c7bb9fb838acb867b9d5ad93c37a070dc97c7213c702fb07e41648ec47ea8415c141bee291388c98053355b38fbf0f81c00265e3d1ad2fc99f44b14ca9fc0c8324f8b00b97b01eabd8a7fed99e2eb7a07e7e5229c33756b31f350c9a96fab7327daaa40c08a7f9b4f0ce31c614f230e816bd72521192b4950fc5f52672de87d8ea6ae2479c932fa724af6aff5338376eca592663377af49a0c3a1c9539ce21a4865ce8131b20e4dd11443538faa2b10e196b7271bf734f46d76253e8ccb0ffe4680af12d3312728e740e4a01657f0df6ac8672bbb80b5493933dc1836810d977607764240eb52d4d2d1cc5b941a06e32fcae43489cc1c144d5efc2a519c5d176303a64757e200d5f64375b0fe86512c3afc47277d9d4a0b45e6bf7165966afbd785b6145f53e1c0613f7ab2115566c57a72d26ced8c95a8b75d1b86fba95e093802b89cc1e6404db820eb19aaadfcb7ad2265ecafbd1b66bcf7328f19b3127fe3997732a27deb478f6e569b35cb66f6354387224186435f42cc7d6f1cba0fbc0ebe14f5f3215334482ad86a8e127102b1ff37237d972dff73d74b4a435ca5a4a9ec766647283811fe3f5ed21a5effef36ce672d4adb18fb09687dc5d6dc33d512139dc12c9af3f8aa35118e1fca1f05fface59f5d35538bef5260f88576a263a337f94e93ff7fab5adb3441b71cfdafb64a8725893d73abf54cd8af7dfc17ab65e1d6a16bbbf4ea7ebed0734d42b242cdfb407c7c124cf88b9a35f5fa7054b1a227f3a8031d3dc3f482bfb0b603575b5de7a51a9b50874813684187255f1c7682e69588de6d0aa0518ec7ee70f37270b1ff30c343a7db569aa209058e1dfc3fb28c506ad404b91a0047207a570e3505e5d8e09f35941d969f00ad43b07c55a80a580c0e40479d4cae3b9ba6c5cd2a28abc62724d3c994eb8ee109e20dc0d13cb287f0f4b543c9b62e5b932410791592f9ae37223761d9f677f117098590790581688e9bcba867a05413ff336c23424bb6f9e7291264071ca4fd4461e9e95ff8bcc6032e62ba092d75437a5d28b7568c594987246430fd209011a342a184a9529e162888b173e661c7b427f94fea9a527dc1f72d6045dc5eb17e693e542eca2117cbee106c850744ad89097ce08bbced6f8bb2d7c8f24574bb48052f15254c7b2b1b4153cc0fac7727c3bd3bb8e44a1d5f96a5f34af6889c44a2506dc52b32782a7d6440d0afdaf9183b12587ccf59721b0820745f161ab1ffb193e6ccf92408fde8120b2345c436ae6bb9b374cc84c1f94c47264c09917e71f8ccfc1e81266d0c407e3dd73631b522d50eed39da842f0cbcc19bd0570f1293dec4ddd07d0c46be5c2298567f65d8477cb3bc93fe9edb97b36727726185baf0179455fe7feb0080361c737c46154f7a9335dfa470b4ad39ec04c5092f511d4df61c623e4eb5af6f4eccc2660e7aba0993e24cc3289001d535b724b65289331fa5dbe354f8af6cc11386358517a1809e3a0bc0823643b30ed9c4605e3349361b8e8833b7fa5f562e54bbf805660415e8662d29c73189c9bd4ac724a242ed72f74c0928b57adbc581260c49d8c2983cf6c9b6efac639443812847254ddb2956a70e12c66076853152bded58a6753d33c5444d6c5a451ff22710d720e2a5fb666b0cedf522f099816e6ef3a3f3c54ef4f08c38f77a6e1a3b959e47260d946b4db25cbaafc074c8697a5dc62b5f0f517ac28904c9773f51fdf3801728b9af9ee732757df754454b6b9565ea7d4ecbae067f2772e444c508640ac0e71c338b46e4a1b0bab4ede66c1427756cea988275824a4383c2572eff3b92dc0728913bd08a2706965cfd12ca925573889b7014e7ffaa047418678e819fd0752729b99727e10fa4907b196ef4e10b380e9bb650876d116f8588950c4e37ce33c724769c182fd0c8f5cd7b52570f0559bbeba2ecac0247a1f82d5b2f2f5e5efe12cd46e7ff783e7792d41a52b753467b5a8549316c6c58096b711ccf41b5fbe564097091709d0ebc4983e58263ff6522cfd60ea46d42f85e47d7a93d3de4259e244a236be08585d7d4e11a214d0d14511c3a9f6c2b0fbd58837103c2da401a884056aeee39dec72bd0c7e4f12655a80073dee9d00b3200d54720056be010f213e6d96237733679d8bda0dc2c4781c062c351916773d9e1ae5887fd9e0a9a5d71d604ee956c7da18a1219e1c558b91f1d85452d02f76c612bd12c11fe2cefce74e724f8826daa6b134705a265580503ded4e519f1a5665757ad2b5a0c7f6bf7751723796c02c9794d5f8df23abc4709beebb094fba12d34582634a154ccaf513c60a6e25cadccf6af70f9c91e74ef9b9a6622b8d9c4da6f98d9b5e4e960cfeaca00170e6b0da14017ac14ef37194483b1dc4d688b61a49527ef121755e3af756580f65857b89327b3e4b67e7290b8fe66f96717ead1201b5b548dea7cdbf923ce305812e9881567d4260b2ea09cdb002cec395b75c89c7a23f611eefee10ad0d253d9710f1721868c3ed2b682d287e3e1e455f7cdb2df3dfcbfab6115cff22b9567244df62c58e45f2b7ab9d0594f50ca2e1e177ee566f15cad5ff8d3d776cff697276e8134073e70fa21f5735ad757223288be2907137ae4240dafe70dae7e95872981609c2f27b6b7884564575594bcd7a74b43b86d6c86725803e4213503b1872c564f810936ec29fab176cc4ffcda131b8af02cc224596b961e3f4c2a6240a72dc8acd4c74f5817ce835c5ae57631430b20f1e07f86aa42e8eda5858312c4549e9445424cc405c6d2e4f3af2fe8e4950820a028247d67da0f9a82ffd506fd972ca75c57bf3e8d47f920d8211fd4ba7d426b702440ab2c5a1b1f0a010d6108e21f606d19cc3d2e7c439cd41dae7219cd6a5cf7820005cf57460be6dbd3d082a6e33c72556f3af0d8dad699f733d77bae107850510dc903a6f9780045629d48a724f82fc7f6e5dad546d1762edbad4380e813df90c0a8af066cecb55acd4af89321daa5f3d01f912905207941afb758bb14ab2fffdb21df8bcdd20c20158f21a72b51f0232b6e23b39aaf5dc64d9101e5e91ea967bfd80539f009da72934233a58b038ecad520a97cd93ac8f6766a57d85f47a4572c254c1bb859fdabe9b0d5072ccdad04f561b84454a970066761f917633c6a7c2fa1aff87aaba94846c269d5d290b644b30afb15229504a30bd64ff3ef5fcb90a5f2dc61a9ee80d950bf07420211a7ed047a4a94e019a68587fb85a25b306b500e288f22eb777263d8269ee7271a1aebb794c81b90ceda784e4b2ec21018868a84a1627b6c7f063da9016ea041f00a58bac86adf5d5fc31207f93b054aeb00f17e11c0f49a1acc33d6c51db23abfb9bbfe8e04cb0ef4d6be54ba25103776bee77b7c3f2986f9bba6c2bb608729cdeb2772af2b38c388fb52a090f88eae5e18eacf888ca6fd7609d14d6a93a727e88964b1c3b306e644bbe5a2092578afed6200681828e0377f2e7cdaad36631244b481ef13496e9970d3d5b81043dff7dd0181f971428d89c1cfd5086409b3b730949999372cfc12ac7694fbc6f25e3a687e4a1aad7992d1dc97e2e53a6d9473e1062d43285bc5e4f8a5247cc7775c8d5d31e4a208a3037a61959421df32d1c66be805f0265756e0c5f76477745ae6d5cd25f76fb5bf31134877905a544cc72e1198c261f1b44e37b3a5900606fcd7b2ab04ac7e15f8792fd355c08fea0e972c565df61da4379ee2cff3f548f9f0c0846e42a67b0fd242cd0b6e8f8e3b8925d691a9723ee7f7747fc7d85c7896d85ea4756fd3278c0d96c34ef68e54a04ce722b3da7c74838f207a1b14a13a9766b2ab69d4fb66af4952d4e35104e05467772720a316a82d4c12f7c0539a40527a77d5e67b926cf64e1b20ccd99a1e37bda21773b801be5dc440ca84e75843c6a2764bb920c6ba477a412535b95fac9dd5c52212839c08b0812742a129c93646b10762ab14a26c243c1bb65955e83db09a072b49e4760e39cbd3c7282b68cb73c5b9cdb48b7c670ca56f3e7c41a528cf57f02079f61fca258fa8849822dfed85a57cd130a15994d4771d38e9cd9fa59c11e72e00b8090a67ddbab5963e509f581ad65156c036465867f78056a189c829b1a052c59e3ebd040391e931e570021020451aa02746a0a3998913728fe81066d9f11ad5e8daa69e9f172d46b63deed96e303db709c08767ab235b8b552e27c37d33377b3a994381f25004bb9d2acb8d3c5169d13d11d88db1b1c649dfb982c669672d71e1a0bbc77389b0a548327519424cf64dfd4479ce30f4bceb026ed1cfd510b0fbc1841c16b261d11e481691a72bdf4006133e9ff8967b23eefc6c254f45d722a1edc73451dd94f0e1277cd68096dae43ccfc194f94db8bbf625c764184f94034e9b5b8e0d4e7967781b5585447fb1ad7ad8206f4bb1fd51b8d2dbbf7a01c4485ae2ec3e63962d13218550436f8244432a30309a829e56b6936d82287e04b097c1bb01ca629c318fe0cb5e098573137e664dd6a1b00458d8b316f3ac488bd508aa6d940f9f6c54cbf20a0100df51bef687155152212f6bf40d3ef7514b7a84bf014dc9bd788f7f18f642c6f4f8995eb40cda9f59f645916e3df9c3502fa210c5e250f1592b97a98b3e090eeff3e43d42806d59642d8c8bb6ea7efee0e2db472692218b0cfcd6d7d35e17ab35213726cc3222660990131fe4b3ce909dcfb9d72c1e54cd64bc39ad5e73e08648da08ac0a0a2575b4eb45c075def73158f349272bdeefbf85c0d3bef2c04ae46a9458b29c4df72095f9ae58ab5e47efc855b7e72d998377227102fe06ad7faaf8184a1c430f5bf06657dfb7e591ec6a59725a672124ce0cf4618b9340abd85dd80d663bc4b149ddd919b1fb0f8da568c10374660b1be3b8c44c2ed371f87d3a003c43e17fecb75bb2338a4643741c6c636c2c63c62ef4e9178f3762b958717512e6dae2ff4ec563a2175d360cc727eb6a9b5df28a4b9d3d1715811651f84755c3955a46c1c5a7dcad36fb4b6b50e0d70f1c5e8724519663dcb5a6a6ce01c8bb9d8809994802eb6eb6783ccbdb4635261c9e866728a20e821ec8f30313b468ccf28ce7ff3816cf5473b8cf4ad7a3ab71e077e1472780bd72b5e780d4162e47aa0051647fb83380e0fdf1294c7df4e43350f49677262402a6d1e0aab144374523ce8c560bdf0ad829697d5894f01b1897eaddf9472b9b2ee2980a0f0d277874ac51885812bc6a3c2abe0021c39a2cf0882426bdf3f5c84c574ab800b58cc3d6d5d64306f0ffbd09c588c7a7f1fa14f92bf252a0172ac3f93524894ce4051622353e5508fcf627a2db8e7540f2378122f66a288ae475b481db3cf2bd27193dad6effa31d5b3977a32464bb64baefa4ee88c5104e61a129f0ba3390f03496462b3dba89bff4a8dfbe3d3453cc6b817526890a48d0f15497c1be31b6d27306047c6f4649be3fec2da1327b37053acbfdce9d2fad57172ae3c754e73fcb3f1fecc5ed3df58b72dfedcf47c29de5022c4b43784dcd0ec726b1d7f342e47125ff6fc5206da4f4414331959ee899d402f126642a792105b722d8deb1d1d281dd8e95af2c6acbc696c43384d37a1a6492bce815ac3d1ba9d7218f2f5d27682e41cce24dc14238111f9acdde2b4945f4673b083a572ad130672b78a43bf4e7ed7a11cae455404aded2e22b21ae84247ac176f9b62c758c21172467e6606b641325d58e62c29ad35e606a725e3f426a3cd34864e0db600a51a72a3d9a52d93ab109665543bccdbf66493631d2e0d1b1d650195c89238902c3d0d39455e8b5026b25eb6315fcf5812b20fe7d181c249119a1ee40da99e870bac72716251410bd6a42f08127083b3eace861097ba80a33dd4532eaee6389c2aca72283869c17e812e724b68fedb70c925dec13c3ce56cce415c631005d3b816a2720ad63e4657e68647bf81659b2e56b0e310feea88db228847442ee12bb7cb0372c3402a849884ae55c72b7fa6cd82f6745fdca579c84e3d35eab84f08d9f3c672a7c7acc42fd96526c99b49ba51dc5f91a417888e624da2fdb7eca8a5a5d2991e11088a4ab3659898fd667cf4bd3c0c72049b8bd97dd30f531a7d593a13eb6e1fb3832e1f906cbb1df33322e08307db722dd10ff71b95caf4c87b35e7adc23341f2f422ea86a97bcdc207d1f7b9d68b3aadba07d6c85ee9cf3ed164e2b4340d72002e753d23f450aec816ddb733273028a5b47fe6367a9ec3e7f709fd4bfd7048e257e11ea44825181a757e1377f4c999467917bd1e8407314bffb8909fcaf7093b55a4a71095ab7e41a80fd4533a285ac76c74970aa1ebae5968f75b93846c7231b9a3ea1c1023ad481182ea528644e089e77c25e16d9d5722ac6c79d5712b72658e8b348a1ffb4fe4ef65742ceef9a55f7dc45eb78230594161618aca5c2772715ce6a64d778e738c474d612c002edec1e9ba552017614d939d25132d5c3c72078e1780fa80a2836e272fdafab7ef00bb23aa5d6607f22bfe4a5057e1104072f23f757a9ad4df065c71ff2d0a4d3603f9493d665523782b4c5edc0955fa75727a4ff56e84d40de009498c07963efafa3525333deda4d8be381c106e2b3fc719daab5e943aa3cfbf060044134d721bc08670a19a0db4e8e1d2ed49ab05cc0238d2d96d0877c8eedae5e361362d247debe3c82cd3d3112d49f49ccf7c88cbc16cf5fb408017ebacd6c3b24e0b76f5dfc0a2faf62d99405d099f082bdd3ca9385dde6e9429053b172f567afa93fa4a5e0d31de975d4a11d6f7b18169c0c58d9972680e4fc7694a3fd7c089dbb98fd759d8f418674520bbc7185a4da975e0f9c14b34f9165bc634cce1489049027b178b047c66501ab384301824840a38cbf9ec4940f521b0103ae2fbb09e94a6df1aa3fe09e36e9cadd58da00cb81ed6b40b6b7251f5de015b39191862bc5810b93bee481a940561a35306e3dee052a2d74b0072cf93b3d2e15ee2bd9f53ff87a5d3bc56c7b71d8da50e06016f38e330866c4e725cdbce8b9c0ebc1b96bf1b29332e8444dab8d0d02363992315069febe129d7274d43e16e1703d3cb2713c2b04636161e3984d5b780c0adc75a6adebe09550941c76fbdf958e10127809a716f426d9c7415ebae2efb601fa31689c15b929f4c72e0800d1b560963464ead7532bbcb86df2b0f8b145cc92ee67bf06d62524326724374a204a00a9460c597a4ca9da573a3b14123f0c882d8fc522e9cdd5109f0684d1a1c4a19b9084dae107271728ff8a8eaf061e214e941d8b00500160d0b5b6f362004be027cd2adde63c937b274b32821ab83ce526271bb12694fdb2b326c72002df6a5b1cca61d41e5e104ade4f15b4d8465469bb5b6f3c05212e4461c441bc2172bfafb7a3af6043f3e58ec5ac68722c945c2c7a154cf710302a6e5d1f46ffb777521806ed6181bb133aea83a59e8941fc6cd881093dfa1d07695826c4a729afc5e2e6bdda22b4a72f680c0f81fe16e56ac92c3f9ab3fa5e64d69c1d877721255efe0c2cb6b6a857f266ee62a376e547ff94d23dea435c369808520ef306f1ca6d8159897e4a99a49f6123724f519307af9e4920dee51497a72b8718249585b07cba2df75413321753825e412a159359735bf6de96d16e127fb8988d55a725e74705da4739302bed3da9707a5c3ed126b922b6bf64259104449360d8b22235050a0bc4140d44702b3d8ba5d7f6905438e3b802dbc13d7f8b22d0f89093472c05f0fc122a6bcc36156a6970e5f17b3143a10cbeb0adf97ac888850723f92723b8ccc80a1148aeba6d1f1060c2e13fcb7177c3ecb3794687c96dbfe04d07c72e618fdbb743b99c338061f7c414f89bf5a40a22f65598993e4ea303d7062c86713df6d6d332d9ab2d769e49cdaf4a133f563e75cc189ab55f3945eb7e2e2d9180249b82224e2aaea865a926c2e8046f0dddd3d72fe50e5c734f846cc129a1772f717d6ed5a3e9ba6752ec10698949ca94dac00304927071c134981c12be45109732f65a3fbbfa6175c846201fcc24cb86dd1af5cf40b4179d10e6193e7aa3d24a0462812f85819b057b3636f3fde993e6a482d9e150d609270a05468ba05722d1b0c0bbfd95decdfac61ab61d61f327486c6ea156d22d68944ffe7f26395b372759ef6f65ede3df5045ca8991942491928095109a397187e8a659f0d9a9f8011dc622c066da309791d843a2a8bcb3831598c980a21c1fc0e5f4d2fe2696c197266283869dda56630fa22ea7022c32f83b127dab938fdf3f5c42357b2bdb82e7280b443b04e26f104438780a231c11b3b2c25c3367bf54eb11d772787644c4072f3201920dfb74010cf410daa951e841375e1857ef1e390bcc18cdaa05fc1855ae92acc3c9b711cdfa3d9bb397097f49a3b6dbc07933873a44954f19f1ab66817677edf9e43370c3ee5e93a0fbac4f9b5ed52061f225fcf57c45eef8e938be54ec938ffebcdc11a9ebda0b48f3b647920b1c12887b991e0c1cfac9a021e889b3305ae69e3d898dd3a4f674ab6a160a7a7549a116a8a2244552bfeffe7431d3c72c5cf0490d3b7ba56aba6149ac681b32513c75e79d3e18f349c747e5adaca9965e3818afc57ec1cc3e5247701dfb3619f8267622d7f3d9d19e9c9a507bc79542caa74898b58d2441e55f0d24efd35e0c8002798104f8e7b09d8e9f657fe13d872abbb6d41864c5d8ad5687266d48ac4f120d46ed0d7af95f27ce81a4c2c6d73722cf23be57a0239e3992733b465452f06d4fe4a32e0b7cfda0d14169e50b8737252938c1ae9e1a4b65861adb954735cb5beb170337176c63f1abb75729105b67214690d874ce17d16b35a7423d2240dac7cdefa5269a366875e44765e3c1e2c0c6ebaa99e4208f9560e12674d88922154a78486627252720880fb8757a1317205c831ec92abecb54cb65af375b6a90b0d7760bbed58662410260084b63cd3c3723b9906f8a0e4373cd15f1b43e9bb3f734228164b01a1da134d2d9766bf13ca72b4fedfd6937dc330557055a5ed009d9dbe15de56e28de2fed85f2d0b0c9978726b5d0590f22c410d1a7ace3806be7bac500c334c2ff5979f2d0291bfeb3b9472699b21fa80dfa079f1aa72225a62c8bcb7dc9fc32fa03390fb60a1f8ea70a6728a3b7cdc8dbfe53b3c0b6f0795b193268a4e85a60c6cf6676d1d6e63947a943a16756a36d1694fb908263689dfc25b8c7f7763aa3987ac205d4d20be6b1b9472c70fb40932107899f1893ac26a9de9a0fbffb3a95a140951354358b4dd4231728eff2c16205a08a72631544e257cf9050b83794b692f8baa0ecfad76745db6724b9438a421f3417389174b960a5e3669cfc054bfd17d108d5b912d19bdd73a72da2404c3cec69b06ddc0ee6cf588bba7f847649e9b1b8feb5064c3bbd57838725661a5b4360ffff0ad327f41df6c07cb512944208fde5d9db7e40bed20ce6953c9a64bb8998c2fbeea54a0bff657fe9fafbd595453854595a07fa2d4f5300953b7d66b1c02cf851b4b1fc10c82eab748e01edffad79b58f80709c5deca91fd09c3111e8bc5e70ff05697eb44a7d6fc0d83483551a887fce4f950d2cdc4a219240a1d77d2100ddf4c0b2335b1435f8b4228562eba078b5bcbcfd56b9b9d35d40063c2cd9fa80dc89daae90b825f29471e4f8b8906301493baedf5491ab3458272fe58a52bf17d8955a2090dbd0f93ab5684a85f37262df7148735a436d1da3d722403c45fad3e249704343cf8f645019221000c9663de3ca952f1bf1543e5e572aa961213e6f948a70d6f7292cf999ddb830a1e3ee46a82f26e35c7bd94af013b142eef4d0cda1f4ed15793e5bf4d46617dbf8b6916c32ab57d5c76e3eb3e321574baa68048a9ac34773241a342c0f5db31b351af0814203a5cbc141104e60815b278cd8c5cddf6a98aa9c2be1ad5db3ba297c8f8696c9af217af14795b073172b246b20bd6fde5faa3a49be13cff8d1ee404717153a1a2481890f67257fd30726f251d4c4235260a6d6c875673f6cc54143cb54ee378d9e72f2ef23173e087722f8b0cf1858ddc4e794d5f5b96d49396a2b99aa9c68752d5f05334954a8ad613765c67463c66983ea06fb7a8297af0eae6b5f0867d6de176dc4707d566b4804f2c444fab918e2beaae81a45f61ae730a3a5bc3878afcd1efab978847122f6272dd6e02485e142206f54201506fb2be57c8a816918fd49e7eb52100eb97ad12726996e5120258205a5c222f2091e0cd0de6bae246d5530bbb549109d7449aed72a17beff797c15f669b02949f72b77c21be1a845138f2263444b74989d7f961724e92df3b57e4e877e53a12a17eeedda27f73dfc7db727eacde58b062bbbb2a721236c28fff74a68a68d8d262fe17e859c8ed2c7eee1ea38f00f0110b9c83a124ee393733753fd858558ad75072fb38de1bd03386b1191f1b99cdb470c4a21f7295a74336cae76a9dc345e92b06fd23e06480b5526821987b1ebfb5215a03ab72fa13dfc5329605d0baab01f6c2fe2151f79c7e471f8e4592408a60fae4c7236b79edb667b608f76e0a7ab31eea76fb3ebbb1f3401ceb2e05e6e088ab7a5c0e72be7b5cc2c546c551e1a8200a531cce542369923611f03af75af997f2f5945d72de8dbcb6f227c85f63ee95a2db7860cf2e18cd85148f9a80c352180e49f1d272151b26f073e9f7a5f707a14f8ae68857d338db82265337a01666a74beb7fa02d79d77e462ed16f3b390f2c5d186751fd2c521c61b2cf1b32336438f477bb6f4a9fbda28c9a2e3f3b2466494585b79549989e8d911baa284d49883af21917457215135d85fafa3e8eb5b406cb2bc5ec666825a091e92cb5622776a89dd55a5772adf5cede1724ee4f541d459e4b4572b6a5077077f396ed45c8d4319aa0662654d3c8d84de51178f6bb2712bbb635e580c2525648cf7c5dab242390d839bb9a728a481d74e0f6ee37274aa4bfffcc4d3ac67d886b3bea4c9799abbed19fa76772387f48eb63c25beaf5b8522e3a935ac1c905d5f99ea6aa8e389bde71a42a3672be8eac95b2a415c5e35ba37464b3f53ea95807c696ffc0bbf246cd04f807c0720b09021e3252a7eee3e30bcf5ef37534b32b479ae89619ddfdba86214bbbc2127ba183317a4b390f10a5e50c845a739ef51dbf1ef90a06da463ce8c3c52021511b1b4bdb699968809af64a83e96596efc802360fc5d2afc54cad9d8d3dffa972a738e9d5f49a7cb32b9f02cc354a97d1533f8191b674493c405478125b23b06bc8899221526a583f413b3f95ee6ef6dcd63f0a1d0443c7dc3b2ed4064b33d57244c16a09e981ab7fe6ed2e33cf8f62f797773396287dcf49b5fb8476be8cc3721db83c410a2a7274096bd2c641042d2eaff704f6acfafbcceb78f3d56c3b96445a8e36caaae0eba153d04388a925e6025e21fea947e60c299faee3b469ac977291f07362f4c2e2c5a3f36418d038daa15a283342ed3e3903e78ca2f3779d6d72ac17ae7c5d3ce4f67325117e89cd88201da66a12fcd948d0fcc5cbafdd353a72491e39827ab1d3e8a218629833c58b9fe62d983f85f0dd5f77b2e0a2472f0a4db6c540c23320a19c5150e1088c0d4bc8896ca9dabfac2d5dc72c55e2834ac10ea685c0f5dde240b6af1022accbfd59715283b7d80b0928611e9326bd2fb06e21ba98584753b9b6f02c20f072992f94ef4c42516279d6418e8b148f76b556f20257bd31701b615f2c0eb1af51cdfac4c69582fd29bc423f6581c5020694abb272501f89300be43153c20da8f110d2a8bd8cc7adc9f2303db5e458aed9dc52f072aa04a71bf46ed231ad01ecccb7599dffbb8a1dbfbeb9378fc79f64795b676e513537f38efcc533c2cfd9370d731778035a01186b9c1bb461ff16b49477b78972efa3e9e29b4736e4ff3aed1ce1da67c483ba12a0dcb7d6175765be12f6ac1b72c193e58d5222c273ed95ee0219a4226dceb79811f1599210a99cdefd066baf721912b9e9753c89380b7739fad2204730d8f4963191b5927b011f326d5c930048b7e420deef77f565dc9c7d2cb27d3360e0d3fce233b68dc2ae1e3d26a0822b246f3dd4cf112d64d90bc3d2b5ab52fd567f922222ecbeaafe3f428742e809f97250015e062785960947df74e6eb5e4d60b1c0d5910a5f0909caeb02457ac6ce72a883a7175dca660b029b894dd0d3510331098b22a1e73cfe43490364c71e27666ca582dc78378a6e7e7b2294c4d8175f0f0710fe1a498759db0fd75d58903f72576b827592304305c3d2807ec273c7b0d5e283f7e4ea2dcd593c2ca4a473282e0164641ce28b7187421c9c0f5755f3775c5f4fab80aa3c9b54a977b1f4efc83556a804783dfd26f26e9d77d10aa64e7e018a22697d68e59272e03a5eb5a6e46af0670d1892db9e15606af767e8b8e9573ca61fda66bb35d6c7aab989acf9637289fe5c77dc42760da338463a40a24888c96343bbce9be8b9d23af8be5cf3b7729d0318d3cd2b72747aecf68340ac4ac4dec3a4665138ef7952b7426f11e59972514b34f138515596c6e81cf6679b94be9bacd30b76f1b49689b8bd88a9507572276e57d1dd349ed1db2811607d090c10a216c49c4c800205d57cf60d3a18ec4df62fa888bbf305bb8142b9918555b699e15d9ee8aea97beba81502c09d99a172485b87eb7700ab35a278db7d293ff889cecde0f1933f8e5fff53896d04bb0b5c4f0149da5bacae92f1266bf9527c13f5f698ce60e479cce79383d79938f05009ebcd22d2ea4f373566d532225b6e57de23f183893cf890efa54f80e235fd3063e9b80a50908e76382e7ce622a5a7d6d2d3aff74bb82d19d70cd30517e5b7c6727f359514a7b5bd0b7c09c1dd9b9bed34ef5f1a81931127ef0164ed831037292f9df8449747a3dd696a580c5c2c35e8118d92b84661c8816a8779f3ba1452bb728ac9436af7680cff112ad311e4dea4c0d892d78ec159a7d6be5140b83cc6e27230d23cf82c7f50f93ed1ba15cedfb10139132f03de1afaadbe957f6b5642ec727f9dd9b5fc29290b3bb75a14143bbe230cce6169095db971a9321df9c31cc072ae592e2cdf7dddba63559465e4186bf5ee48e4c920caac6fe1a83d09b1b06872c56052bc6d23b5ab5ccaaa0dc5c403ef123ab1a0617657c35e954d763390dc72496b3d40e8747d81774250440a9bb3f0328c5d8ca1b6edbdd2c763cd968bc763661f0f422b5d9e740662108841d1b360d2fe267972ccf11602a7ca3823013721c487ae18b1ed9cbd9752e921dc6d73c33f6d16809aff140c2ac3b9c70353ec581b0e5cec83e2cef9853f119ae9d35bac4473955223bc460ae9539dab79b4cd721c77d67c81c801b27d59b56274d8d4f5f8fe54247aab0131a6baa5661161a17250b6fa555fc04e53fd5277e168572c8f01cf03b4c7c70e12486099ee13a1697278c7d1a44a0554e357281b6ef8e9a2f983412df5a2090867a9a7665bd7d40e72aa7c76679af52a0e37cf48f3b18a19eac01bd78671a31266878e424a4904fa51687b8b3c1c24bbd731c6d62a4e1ae806f1c732d11bbfde3e98a2707b30cdde724aa7c4961ee49b4f8bbd39431583bd474930b090b3d0a2cea875c16698462f725a9f87893cd2ca2602f03b96d4101539d5885d3864b3487841f68ec34a9903041644e20333e7e598ede6806fc2d9a734fe542af6bfb0646e49c66b7c69706172d42ae912b98203a5ecaf7ca3c6354e296d057907bfaac12b73ed6a6945541e72110bbcd44ff0be0b42898ae706367f3c5f041eda0fefca103a4be88c20261b2a3556a1e986d7fa0ef21693e15baaaabf22e5c0b97a1c4f55d6c147f88a5afd3261c23e3ee111cc6fccf9406fd36b64b6136d034f1130c12fb789357a3f785d72d7c291bbb9c700f7c12204ec86b2a1abe2d1212644ed43544e35a5e6415af972b390792dbd7947ae76706b6f5df7b6e009509821084d9fa630ad86b1bb979c7244bc8891ab2438d35dddb1242f5965ff5c00c07dc9af8b1009c1486ceca6bd2de023d3eeb24c4d6003024144aef53df462f909b9269fd86fcf7750eb2c1dfa722e54534866a32e32a673d1c0bcdea0726b13714e377c59057065515c7eb9ba3ccffc66842c54b4fc2b44b76bef5837f0c035f53ecd2cb89e580dd971220ce3728a48c7fa5330f223341375b0b2cbf413319441622f6795b343a251e8c0820b7204be726f6e38ed6d26d05098670ddba73f649a3f1e458972cd7b7c6798196172b92adab7d5dc401956b65752519bf73036f564eae7da8e8999ad068912a6d9654d96635fed89ab1c0061eb07674b7efd8287d6dd6ffbe5d4a6f432b6c3bd1e72b3a0b2d093933210b86b6e5731178806fa0d8107e953cb04fe953f07f048f60db4a6ac7166775190c1baa828fc99ce6eed83b8bd681f73f4299e8f90b8610b6c60ff0f93857287f639c1aeae68445d9dbb1b473f8e066ed8864bbd3d95d81f729c0acd4675b51986d67181653602dd33fc621d66cba916e6463be89fd2651a72d8c2ea96bd54c8fd555fce194a3089820a5cc452a448e9c77518068ed1c78463823f09103ebff795d3c6f5f84b940368597b180068928a7a03fe60859e7cff2d34f8c5159a8880e1b4dc2d6110db7b869aaa62eafe15e41a5283b81d68ff933b8a180cde137ba2b545e31ba903ebfc28840887cbbe70045ed3f0f4cd5a3bc84ca45d5fc6a1323c12b7c1516513df103133c70ec1fafcc6e431b60ec4e9c2dd72ef49706c4f2c2b42c67fc59a0c44ef6dd9140a5c89242a9198977fc2ec21913c9f730efb7f729a64a03934e74c888bbc869495f8fee142d489b2fd714742e1110ed5a5b3c6de13b7e05d4366d98c30683f2fedf33057361729a51c3226e557727f3f3066a4befb3c0bd6672bead4f4815762a7c1e51d8354c8776666666b4f5c497e1117a80380d0de8a7aa400c53effd82c4e63b72291a80bc7321dacd6687215093126d59ea9eb16b61e0799e752b2cb54eda1a61eacafbaf5676c03a65d721582965780d50751746174b2ece5fe3be1f6a293f41a45a562f69ae001485c7258286ab2db66a060ab954ad5833b571a7538d31c4bf78bd5e74fa6257601372a7378525cb2e00a750d20c1a0caba1059eb3bbaad965b17c9def3c2199eccc972b6a99fb5d13d56f370a9aa5c4784fcd09edaa15c3c23665b2121823e0e54f272e689e5d48a6624bd63b59b60652924081ee73a78267948c3ce7f3b26073b8643f31eeacd962ced47fd78edeed05ac72b18e9c971227feff2a6c174fcd95eb372888b99ec83ec05616ec4f46ec28949475055ed08a6db8608fada8c72cb23c472aef6b9b75c289448fa4fff605ecd850417cb7923215c2bad7e75573a645c3e72d0ff0de07b48c6d56d15e2b28ac4041bd839f647e03e374174fcd3ac4f6afa7207c9b9531a01967062e2545508fbe12b57195441acec2bb05b8cab55b991bb5f52887bd40d804ef196315b78d74c8d62a175a3a28fc428f100358989b5fd855488b3d13c339031cbf0e7ac65cc25582a246251ca95b535ae42ae2d2781efc94cdc24db969f5e2fafc1824b3270b0439f4cd79226281a75c1ee63fa482af0a37287f509f33d0ba5c7a4549950cdb57d427bb163945d47d95dd37f3501cb1bf83d236acf040d56c4a724ab0fdd469b0d27a9c00258104f9a4906db5ea751484c720415ad6657f6299b6b2540ae6f4adf1fe102db7cb761fbc89e1f7f12bf0cfb2d5dc8a6e76c904ceaa6b5eb51cd08913c3ac8dd568d3a0399b9e8d6886aefc5385ae041a5b405f12d0a68e6cc6efe2a0a5a9b286b2f380bb019826f5ae96ebe7298a85e9d37f968fc5def3caeb02f225dc24e7f2c45b9d5173a50b02f67c4d77285960d303cc327bd034b7a22ceca7d71a47cb37601d5bafeb6d54282b40b207212bc69a7defdee4ceedf7af57fc805168285c053fb9156795a99ce2b95becf72ad959de6495f94c9b6c0dbd4dcc2cb5787ba4bac6c908f2a51984637f3acab722590df7b8cb404e0e7174e547e5f57d288b934ac67c7852c2e27352c562c2f2f3cf7e9b1a8ee756ed2477b1fe930dfcfda465dc03a8f3024295bb9a63825c1722e9a2b157df5a005f90a310ce621e7b8073dd741dd6299c18e77985d994fa972854ab9eb1f921fc03554bc276cb33cb7c1fe4531094a17c9bca33a17a13a4b30663a3d44aa1c898a04b68c5d99003f0a92a3678b52bdf40bad855b07ccf33635ad96870075cb69388ca4693ffd078523eb31bf20e8ae1ac2d60198d25636cb72a7f04b83ff5d3e2aa5dfa0512e75802d674be3d9fbb1928122de79161bf4c872096288f0a3a4ae5fd8fd0ce615952f7d17971fb42dc51eac4e35e7a181c72c70fdf87dec12bf267af99eb0a3c88027dd32a87cdcc38c14d3c8255fc1f42a7561d01d2c1173ac92e9e36842e361101ad5aa541cef14e26af8d1481309b72cee725c700763efb6bcbff56516d4bfdc9d374420da8fb698ceb0ff70f2cbad216426b2c3bfed642858c393d98b6a9b85de06c5ff06b5fe3ac026dbed75bbe1863870ddd06f83f385325f848028d0eaf2437a33784e2ce8016ea40f5feb5db248507247499fca284cce1f59462d8c85b14609f87917702f16d68bdc6c2c5f4163ef5ada2d446fdf95476340a5e5cef2f1fca1dacc91575d94755c5a2c76f02207033abd4b9d30529375c3e3c3b675a90e8f463373c062a751e1b5b2dfa6352093dc72eee045485bf0090b0840941998ffdd0c55afcc8776a922c5e28b29e5cc31722791acd87c19de28ea70fa4b768a008c3b3f267c37d7f1cc37577a3cdbd28e1f7286595ad707158bb98eebdb3d659bb8fff362af9586bebbf7a778daed21582b7212bfabedbe069dd59c2ff50379b2132b8cc2e4ce6ae5c9d54c5273d1e0cc954d0abfa13d9962526dcd8a3789294d6372e187d7be78f1f9a7ec19425f0c8f7a72fa4ad35fca056a6a28500bc0c579b63fc393b14b6708b4ea5830a0839b1a6c724063f30b3ef96c764fd67c6f2040470e3c4666021db167a032a826b3266faa72b1e91ca7cc9969bf4551c97ef854b429bb75cbcd3b3e4e1fc7aa6e073eba2a02c80f31a0a3356e8c870a9e7e0db977b47f3ff0275aa802b4edb6b0356378d1721c994eb27bf814b93697a39c6331bc705988ead945c2370b26de9e71c3ee23726d12f91780188a594ea7d430c2bf20eaf6566b4441cc5e15ac17a7da316cdd725c0551d9b116e9706f5e5dc22f1f8038b9d35b001e1bed5ece3c059ab50a07727e8648d71eca3844a85a1f88b74124552ee96353590eb87bc4cd7dcad75ef3724d636c380da720848028141e397a01136aa433de29eb2e9260dcd0d13f20267232c4c0f031da5b64ced89ac237f67d113c3f43c88473d19e883a2ea45a2af8723ca0f5a0ce979bb6e6b1f4bd731b3eb763a98aa17e9e4681f67035b410c146385013db6c6d90b79133cbe6f9ca582336d9931e68a61b7f2602a658525a6362247d8f4dade6794e556219e49a81fe6a3944d8f8671823b30b5ccc15d96e8a2a6734029f5acb0e8929581fbf45d4779d3df37fa1bb2d1adfac44b0e16524de397288ba398433433f225ccb95e20ce65d5b01b377da4f6288004fa5524214a1095bb1e97389abddc63ddfe15bda41029d2c3f384c28791ab7d37db381739a67ad727761ce254d829504fc654c2b6be82f91228ef55637923b3894b58c25aa4fdf725332d877a85e455b9e9cfb98bbced1022653eb7d7c4dadbc5e8b3652b9b9cf267199bb13be242ae6580d50ff7f24be7e60293750991e9efccbd2f150f2ad5642adc35d873fb15028c34b2d350d70c5115757f58acea4792cef672d88b44887325a1a4ad93609dd1f7961e9f685b168bf430cb8ad25cbc51719588aa395afc972ca0abbfa3fa53980a03ff832256a98be0414d322737c2779844aab4dbc9e09362cc7d194aa5993fae6982ff90a919f20b6278cb61b1e27ad1960471519e4aa72ef9eabd03ac7174923603b71355c8bc010fca027a35d3ba9bea7871cda718c0bb6692c6c9681542fc52269658a1df48d38f309724a6d313114f473cf49e1c0123c69d4745743672459e1fbc87110e08927f82b6505eb6458fd54b6f39a36cf72bc1c4177c2914009f2057fb18553066bb54f40ef29b90e27ea3b994160bfd072025a865eac5b670a650150ac278153a401e445e839e5e0779fa4e24dded2a37216641288b92993761bc971f078f998aa57d00e925dfb0d34e976e6415b1382296c979753ff59d5c5311ee756bb1525822ee2049d10bad52be8b5faaa89e29372683775c3fa1a435765b234e3432094bfc53b1a8d72bce458613120b6aec3af705a27228c987f20b6f58acc698f4dbc0cb10a53c7543e4f718fbc788c1d3436105f80d223b2dcbcbb10be4e73e66ac4689f616feb2f76b66c6cfde8b03a804c5c5724f0cfbbbe0b26882fd883c61e24561fce57e52a29eeaf463e62fa0ded446d79f7e18aee7587219373f650707d2b4c24ffcb936b69b42b5e255d91aa9a9948b16f84cf4ad4ab53dc114aad561b55acdf0281a1e1e6e9508f4f38d1279c925dcf9cfd745ddeb021ff2d5435e0a4b8f6d45588c638dc25c413ad870e8559e868872273dd6928c3391e070cb6ead3acd2ef47badd4222f197e8c60791827329720f7b9847dfb933e32b5dd753156d01b1ce1bab0e42e155fdbe643bbac87f981bd2d27e106fea8fe670875ae83879fb60f378497943345a5a83b3935a52ad7572cf633989332adc35f019545e0e6e9ff7abdc2bfea98af30880d66ddad1876372aa16e3f746f86c8fd51d8af16a150f7e65c009a629ce5f48742782dda8e45646cc9897b37e7ad35e23b86a12c2cf46a5ccd822d7ede2eb4389e61d48e051d05ce7683d1f401e961b9a9bf204b9a8bc4fb87da40b5df508627437da2c8f964f37629a4304218c01cf4f2c108e3d913c90e517b8e252cc365303bb02c3618da3728070c3cb5f89daae8fb1c6f94faade5d2b8f9b054c1fcc6f2af75bd93f7578724f42fca1908fccc2f4497d906c0fdbcdaaf713b826734100ec71a56caffabf72fc35105ec014c589b714971a91e59ccb014a87fed7be9333669bf82ee8599d728fb1e1c64e45ce7bdb02a54dd3e1ce969c2d9c5119bf4795d29f4b1d2482af72d272aa437c4f9ace5204a65879355bdf332f907ba08dd276729db28d8b83b7720e5f931af535bfb08321f0d727b81c5d61feba6f9898543fcd743a1cc26697171c475fc02a5da3b622c738ae8eec061b8b96786f3b4a9225ad3a7c2cd8218e721514c3e77931eede0777aa389b8bfb1c094c4da31cc96badf3c6fff10dc7897251e541366f8bcae7c0e076f462a16307ec88290e0beb0fb1559abfa160e3865aa758effc9340cc11960daa00c038a74752f2dccb0b136c701f80131a75690c728070c6ae42e2e91d5ca3899e7a53e2eb794530de6afc0c7ab73277ca59567272f267a45963f8fc72f64649d1088c32ce720523ecbdc90736d114492452cc3072c1fbd43e7e6ca6f7b73fd1fb2dd307accb94003d43adf17695197e658718782c3c6c849d0dd34b248a621a3e7c12820ab85cb7932ac3272c4c57186dafe7e572b49a033c0ef3476cd551993912afe351be49bc2e15243ff4cfce897c0b551152e15e783fdab0c5709fe3b63d9b6b53f55c2e7a7b93f07c012d5c390ea0c382722d8e5d20e6b57c93300534a57edaa3f5f67c4f3f418f9962e8686db646886630ac75b8d09bdebc3750f54137e9bd4773e71ddcf22071c6881f9a0a58da51f025919a964c971b635274845aacc0a2e81d8148eb7c2499d4126cad450d83b550722d2370fccff01dfad49e4cf8b2f410023f72e42ece1bb348f23057adbf96a472ea67caa793072df790f754b2ede3bd4edb5d70dd246565180963094b0229002141f5f563f50b0b4f0241d6281f75a2d02f0ca1ef96638a4112671426ca320f1675292c0826827cbd7eb76aee952b88e327391c3c5518fd1647adbdf7a020217205c2111571df66b6d19a967c09ec40d5e27b8f54d1041b645548af4f0ccb726990e941b64a2a5341ba8c76ae7bc514f18eb64855fb220d2a28e40e8d9eb5c16eba22e010fcccbd6944d63bdc76602387fb74f52015ca5c8229d6862c78c92370426c1b79b9a12bea18d6b4349c658c536cd7933b89dbb6fde81c47835c4c8a7230466f1599214203f05b174295bcd103accfc0aaee86822cd0f7f2a15991a7032949e415a6d47cc82623decef3c9c1a5842a5f6ab337d1948d00984705dc3072c667f2105433d84c7cea54bc8abb8f3750c538ceefbdf65716bd18337c8eed668f8bc0cb3f2b73702a298e1dc133632965973aa7d2d0dde581f73f33902f23207eec32b67fbccb9351815f011eaefcafa7034f8297da24720d707d82f34e9d7210abbd0e96be6bee274bb7a27432935b1d722409c29e3549fadc93671a54aa724b9e807a86db6398a00e32f6e2fba5b762f031324820b77cbaadb2436ed60b721122902718c60b5db0e70ce58d1825eb1f8ca719d9d1f3181543349dbfa3cd72acc5ccc7a87d6a5e0a0f10f04c8bbea4e11143562e69d54257c794e8f5ee5f17bf9916d1cd868d8c836ecdc7af8fdcb23b228d7daa56318e278854b7c155994008149275765b7a59b0e1e688fab8b6e01b9aaf44e9b1f8c842fb7a19e73894269e8b209f54812879178d7ea420c7f0387a942eb89d72e4e6895ac77d6b08e10524b520eef71e88964d3c2cfd6231ff4276762b902ec704b3ec15d3bf256afb0841bdf190a1b5fbd6424d2bcb7c9ade172e35dd9d0155a8c091ad1a0286be5338b6a084d41fad199f518a3076af97cd247f0310fe17b2940cfd81c9335d7689621cbec2f787278cd5d147bd17203620f5b7562b901bf2888f834575b3855a3f7274143bfde6d4ca1d86db4c3979748647ffdec48ccc50b5b46a4518c5fad3cf72ccc4f6c4d976234723b58a659250c4d06a7808d0373043dae86217da82d58a722173002fcc22c4f6e5c720f8a09ae6b4e04dfa79dfdc02288c6ebf0dc8678e724195b7b3a8effe0635655b39bd2f423ab014b47355fb04605e79219bbfbd07653a57ecbff29feb272c5c67dc83092d602a4facfeab42812452adb6d2038d040a5a54180d1f747e0a0dad23efb511b5072c171af76d126282e2e1e9ebf1217f3114aba0a3d26f3455e73c835450b1e48618d31e9e8b85603661e1439aaf6639396d50957d39f104c08ca949ad13eabd04222275b6b1f150b7f3d04468b2a81572fac2ae567b60d892aeeff0761ee7e744dd66b0c336fe9c56e19fc6fab43db672c26a3a16aefc5aed9516deb8983b21c8db6c7381907620b5fbc38fb7add491722df88faa9868f606ffd2bb1f1a88f65cbaa632127e46fad67d88bd83e3c7f66bc0e2e8fa7fcf8f36912ed4e11459d016046934769414885e85ba8544643f5a7244c44cfb3cc71dc178b535849303b0799e8433c9fca722cdf8d2281c9fa5f002ca136e812cdb49b6cee203a247a5dad3f358e27527b642f65bb13c970abe0443c9a650deb9c8bd2f47aaa0fb5710eb7d6362c5c6476ef44a3a067e201dafee72ffa22141bad6b33875910d8bc61c065dd216947d0ed29a88bd2fcf246a06f307124d4dcb4ef717faef8253044ed6af7373419d6b2c81d6c0886b7d728c21085928e3ac4dba5393acada9d396acd783e597c793e03594436be7a253a856e97d1ecf27762fad320396b42fe25edaa6263d444ad7472e7fcf06b41e22e7ea6f813647369e3ceb1bc6f062ed6fcb3c135261a7cc55c9e39407ba41b85c83016aba7204c5ccef9f3b336abceabc6100c9ac1398153e8b803cf9d8c15d80bfacb43e0838ecd59b39976a7abd2bc18ad2f05a8c5a86cbe4dd029ac853af899ec06952201327c7fbb7fb6eb7dfd0b8d2838d46cb2314e60f1c02d46dd8d5594f34dee967d2b2c7380824ead2b898eca59fc5c3660e02c8f9d83cf8bad2ed6abe03c355594997e189d3d2222bc7395be180033a515a1535a70751c0cc43bf14118482950288558c9298f6419c4e85eb574623892a1eb6a6a3c9a49685fab1530354693172c81b09e007a76b4469c5711c2d9d4312db3ba87501353b63dd8db4b324fe2c24568f71b5d27a795c37937c2599708806029cfabdb4c017bd1b2f701317ae3e72b8c35a7b467ff1abd87d72802ef3a3e70d5d11c0ea7e35fa93871cb23e13945858bdefe7ba98b7b4def89e11eee988079df385c3cbcbc2b958ecbc36241cc272537adf7522e3ded4310fffef72cd753dc06c1af69b0e91c4362ca63526e7e94ff22b34784953ffc2c70528c717718a66ff8d8f18073bd761f37131094a1df50fd87463cbca7b834da6b01431f329d63fb102ba9ed04ffdeef154121feebdb9534c3bb382dd7199a7c9416a0a39442ae424bf1d622c125f73ec22ae00e6fd9f69c85fa5b5795e1bba0f4da6ce5977b929c248321a8bbf735706ca14522a5e9d5cd42aeae376f0e296fbd2eaf3078ccdbd7e2854795a8659335ec649af9f122901a61ec9728a14449ca8a9eee417f79e0979656c5d4ba168f32953e6b83b0ec172cc0f006aebf119cdfb05860cbfc5ae7e1aea26a46d54da91b27346ef3735e770bb74d16795d0ed734113b0886dc83786b4d999f812ce872eedef38c43900e950e2f5f4f61db2c17ab7f0bcfa750a3f67b36c10ec224c914379d3c27c99117a72782407dd90b4f0624d6da91a08216f5b3417af5d05c36676a6fadd044ed91954bd2c748ec0526c7f08a35150c4feee2a9013304e6226f8b0c863ace5b3e475722248c916e87ca79708c9badf3422c91f730c11ddef8aa2810737a46f43ec0072b0023936b43ab858cedf2aaa3d733154cdb507f3068079bd45ee5b3d5a86bf23bdf20fea1975dcb51f4570d39abdc4cab821ef569ed6b461b8c9e2da877afc7202daf29379bc748a06d1d97c5bdc1cb1773902a50d12d428e9b83561618d9c7228566aa761b71f14758c0f9ee131e9daa34ac4b5a8c81639765029ea3edbc3724118f7f48a8134370a0504e6799d4ca9f072be14720baa0a575d42dde6cd9a54fa085ac125e56846da48401fd18fe0396b964835810e665ef43059370e472f4b62af335c052959f99cc2a2f7bea2cc09199fcff373efccb7c6dd148c2f77f14e58423b066d5cee62b6716cd55433b6f5be1ef555480e2305b5882dbf9ff33c72582e47ca1f4e522d9e11aba69b2a50bf4b8fea245977d22859c385c376e64e72b89c9bec9b86ab3e18c97ff55b99dfe48d2b4b70299a29853594bf8138731a726cdd3b4d2e2ab6030cd63b2b61399821e6278a85c02d8978ff0f6e47b2b996724f9238a1eb465a529bb8d3d5e13da40d1de964b17292a38964c2e041e42bcd46576fbf67a8717d41eec4433d6f6f8cf1a4a59d42220146138c58a5bf3f05a56c7d2109e6b085ec97086dfa3e89899612e16074f1b6b5cdec0be9bd7a8e48691a6ad59c840f72009828482ff30a5ebdb5ba1c064a53aac85e330337f5de5fd372ad9044730d3ad07c97ae0c8187dee07abb315e182ebf0cf1d6cde0c66a790572b7ba2ee163a33e77690b9f4b5d70b14df79238fd0fb8062510634f6d0293be72c9956d387f808fdffcbe3a0d2f174a28a9ea1058fcf18f5060798137aa0a1672c02aef870e1c972092809be8de7e95e9dba2eb913585dede46aba18a6aeea0444982d9d2bb8f67bccc46594eb1c41d297baf1ef1c6d14458a2ab46dab47c3572537e8a7a8ab6eec0bfb73c64b7aadb7e33064118fb347ff2c6c819091beba8727db71d6e132470c696ec6191087666fb16dfab62f4dfe42366d10632ae7de872b70c534f16b62258fa4eaa2a4e4f37eae7020e556e02ac6fcf7232a4d8d743722b3f84b4068851f0723b2936ef58d635c2cec250e5fe278594ba22442ea30d6d14e09459501e5de20bdd214c1be440796e8f3f97cb66372aea88e4670ffb7272eb25806b62e24cc77712d390829f04cda21c4e04eec34b45003edb6b3112f6675f95f09734a9144e052d5267eb9b56864c0986b5c6d5e5c462d6a6ddb0921172cd06917df6f6e95c4d10a67c941a640c00dd921132765825ec4baddbb161b4726b67fba8884acab02bc2814c7adda679acf3661583b057cb302adf5e5a2b25724670052f080ae52d08d2765eb81a274146babb39aebd89a493f4f6fa87d35b208ec431c16af197c9d81ff11393ae5fef184b93452895296d017db0ad926416604ae73523a083b10b216696176460dc0c8f439f306b461e731f962ee84cdd5f7229790def812020a600deeab5d522b45796e2193967671d1b050fdcc4c9032d721d4c286f11244d1490095094882a3b01c7c2bae78aa08a8f7fc80996daacf4726c3ed749119bc59a49642af884e8a45eade60e755f95175943fe41ee91aa0d72ec79a2d538d044c720015e4fc7c4128ce6481a573d7203166a5c8aedbe142c5a3a6b2992168c43588eeb62e834210a5e371f593ad070d6d0ccc99e4af20a95725e3a0b107e48236915b326245c2a44a646143593a61b23b9ac5df5c5040cac1feed5755a83964fb1e6a84a605f3138370743d2818d0c51c5f8d68de6045a6a24cbe17bf9eb8abe5ee7655f31c1838e702e44898596d655431542ff321ee84e72863f7765d9c6c92fda27d503172d9d0aa32e86ea861f5f18b87e849db4cb6d72ea60b11ae13babe79c2aa4fb0ae0603ff6b7bec06f71a3c14d9c8e01d079c1726349e7f9e52acb16b2f577b86b1b03acc54ce7bd6d40d6a8e5f633524a89b049bbdb1b7e6b21336965b543f6a218283da18a0ec426990c6c6cb8fb56cdf39f72722edbc714c77d32f5ece92581b857149dbbf6da6e8f871b340fe793e474c308ed92652031e70ed6134186a33a1df728f8a05828451757cfde53f1bc56cf277263a182c78a34379ea0f0b830e39b18ffa742d34d90b6ee84154f9bfba376837258c6ef78ba882370085e00988d3ba2db2b86b404ce549b8ba9dbd87837192472511e1e4bca6bbe8c0747fdfd0183048384a1063ccc26f9aa1bdf82da2f38d67294cb8a802a00951fb109c64e6a084907635d8aab52c970c47f235b7ad79796721b7a42571bc8a9a32ce32402daade5bf88dc536b7b9c4153e501563a88b48f41420022577aa139e34253eabf2205ea779f12d946b9fdde43a7adf65ac196635ecab5458507dab91e340fd07013177faffb63007b93d2d349d33646771bc9747261a5436547411fd17e92f41129ba419b7a3ac1554647c1706e758025dc24a8728efbc46e6f8e2345998ea7e75df77f6b4f65b38fae504cb6c2d10337ac882a72d394b6124e9d212df919d09985f2f41e91293889ef60a83a3ce08def2cc849729882b0ec27fbe09d68a3acb38b9fefd9f3b91c8b6c473e09c60545805d8d2572cd32be2344b7a297dad8e533838bc9e49a00d03279c6f621429d0392b35ca172d005b86860402b61cf4f6d7bc5cab9dc29179d4cc4822c1fa6a5d5c8b11c8d7209876ce6022b27d959884cbd88c9024a180db2fca00be55870556fdc7802747208f706ba9de504772f364b1b1c815fa99eaa62ce756b752f01abcfdc534ef53a132abb1fa138011769757c738b8760e611be4aea37349d5f1f8b7659496afb5af88655a2618f6ace9ff2f737edb9549c403ddfa2616dc974b0b6e601d0fb370076ebd875e1d3c3b9173778088f4e8ae69cb6e97c928d6eee91ebd6773dbfe8724ba8afc08ee13b180c09c38aac2a86e8f9684aaa032d9cf1f7b4fc27b753941ecd19be822266cf8a8df80e13fd90553b64381a4e0525b417833d8d02b129f07240b1e3bc53468babe5b55d323fd230c447ef9228f50cf55579ae6e8d539abe7284efad52317c1dca1af67278a29ce7008c9bf58ac5479aaaabd210cfb1d3a972c586edc8b78f490a0f3fe3b635ba88fa2a2aeeadaa61a2602d5359fde9fca2724e1706f82c60c630797e0a98341b9d16f883da9a7ba3059b5fc4ca8a76efd0667a0ec14ab5130d5a499a67979cb0ecf3cab24a6884bda1681b4771fc52836f6425d5ed9f1984bf43f1cfd3b462560977069b0a03caa5f19e4f19b33041365c2705f96e811c823565a82c18c3647b77dda4b54801ce2ecfcc9f6e211dc6952b72a5d05673ed02865ae68ac54a4a87fe3b1adedb23742f274c24548942131480623e1706ae69f0548f4df86cdd2979a3840075115bd0a2831652f9fcf27840047217fa414bfe652d0cc51871fc8da269a408b7d4d420bce6399889271454690b72822aef986d770ff7ae5d3135e229c42ca58192898d4cdf05ea5e45c440f3db12be4291efbd4427c7bc6419cee7b307b67aca6c6756f74a14e0ae5869c947f272e535b5db7f96819b7954c733b821a31d0e73f9805cf289929d376ad1a158f972470286e6acfa48c61a68b984f0232ea95098c37e5ed3e54e9233e044c7043a52e461732aa62fedd7c744499b419166e5ac17ba8fd2a45ad3327026a0bdef9650a7786c5b3e2c667b07afd07795b7a89d6e0c703c3d4c29bea2b41c5cf51e1829cf1bdd7485e0e83ed50411998b368a00d406fcaf80dcf5603b89419a5bfc6e3960d6b30b38b9bedec0576e4a50be734284bc85c3ccc9f12973577ce18d2d6272827dcb86c498aa78f0077b1058e35d6f337841a59d460caddd3def7749f9255837caeda74d6c8f1320c719aea88044aea242e64d51b37ae37f4b902764579272961ff982329aba2b5784143e82624dc26731d3cd3975cf40032332ac65116f726ccdcc3394b95359395c96dd6018b2864295ad77564e08dade2843aec027db72cb1d34110e4455e981e1ee96ba19f79c1750423f3c8dbfc6b91ee81be8d19a0a3451dc18458e56d484109f7f4c245853773bfa2b788530cb17118b2a90c72d722eb4dbaff3bcf4fc5f18e5d90d8da06df58160d3473a65e9b48cc2bd80246d48df6b1f27b989ba669336385ecef2cf7c9abc0bbc2ef531ac8a381d1fe00ae3466033c09a4759e3b5bc74d626593d917c3c26b29ce6eeda2eb4cdf23641df656459989f75967c701a1f6afec494d0caf7fa0dd4a32e75284cac5e170d6a274f62b0852ca71f8cfd8cd227c3f855ca28f7437d88090ded47dedbf5711084ba16124fe0a85f6dcfee2df41d17396425bb4923e29af334c0ff029664c01c9c983c72c28f986813099df0ca97d19e4e7d810b80b16a406a82aa20ae5b18b658c9e772bccd4ecbba1afd84f55ebe3dec3fd19418e9aa705b58eb6941f85ae825818f72be7791fcc8ee12be053c91fcb0b119c713495e600a46d992e9505c21a9adcc13fe93bd75e6c565b11c7860454a83d5963d51a146f32f038c773aff7e88100f7250cec9a6afbaaf3885950dfb96b191ad044ae159ee40943ee8b54bf1cb917b7285618a0681edb8ec1dbc1e3e4c3c77ae5c78f96dbd3db7a1615f8aa220725955f999ae96f951f4cdc3973a364af8aef2244e05b3e2a60be391115852fe86587262300c6f4574da644a15eb64296e394268971aaa39f7a94460d6ccc386d71a72665416c5d4125bf7d660b3ad1de57ecc1259fccf569b1c6c5ad561ce453b2a72f0cca4af0335f351a5433238e0f74365cd84526895e1a611036c408677b266238739c900922aa37d045a136568b8eeddaea7577e159762a648e4a2126caea76605b84a1010fc501b74dcde6d2e02762228c4ff3a022681eacb4bd2e2966b8072d9097a7bdc2172d3bd75aeb6a94bf9a78d5616f7b3aaf8569530c2f986f97d725f7ad855ffbb24e3ce0fcd8861cd96b7da470d2e6ed6718b25c26f9b255a6972bcf38fcbb306f8231ef6a22b9798b43057ea3ecfa95c6e1c3a9bf21ea45917725779a935a836388620059e57ee2d0410e64950a6de5b49bfd70a38183ecdd05194479dfe98c949199bcec5db9093ae17e7843bae08781a23e62abf2dd0418a076d79e30e0f1ff69364158c4fe990fdbfa486860bd6c1b47c7457b023267c4c72acea79bdb0427f2d59e328ca7f97f76d66573c0890974e5c5c9bfc74c3526f5071af1aaa4dbe36f1def9890d7534ce756454b2b1290e0a918f2e73f4ef9b4772ca5348220c15c4db8048a5b10b334eb190b3713bf9902badd578ae950275e872cfe3286cf6ccfecdfd64f3d715c9742a0be5658fc502fef6775be32736e74e726a9875448600e0eae2eab74a7bc00fe4e82df7599ce62ac1607447d4d63b34725221ce26d22cc217f15f53ef13c13a6d3fe6565bfdad3132c5728a7a2c5ea072f4ca61be3aa4173f425ccba0c172294af32b538b819e055003b521465fb68e2a1cee3171efbcfcd0df095b5c72a0b3e06927de096e129effe47470047e591135aee919a52ae0de46c772b9d7aad222dea497b69c85c8067b5024509acca05072a71bf0d0170613a70902d9857bd108041bb2a26a780ce85517d4ac43b52aa472fbc58549bba131da6f7f4b02148cbb3982cb2d9dfbaf5fc6b3ecf6f6c46f917206a35c6262d72ea7409eb4f021a182ed0a5d46d69d04584db585b8b685ea2443571911586a0497a8a120f8b2d2e0f5ac7b5cdedf94ce1b50a4934a21d2c477727dafacdca82e7099238cec499b759c313c2d0f8a493116f9d882f49b48ebeb7220a30a96238cd65b10e23a43cbe8b41ee3e025de879728771cf4f214e55d9c6e966695bbbe179d07b11695c9d5b3d3d416e0171dbf1a5c69537f27d93fba0e72e634bcaecc30d77225ff9c48d83a2ff656d1da5e5859fc0e999865b81cb42072df73c50bc3b343bf3a306112093bf36664e0e2f37917e1527955b1c89969047266cf67f426ac4236e99aaaeb0c5391d02305ed5572b68edf225a21961b2ec93318fea9fe16012ff47c8a4b8db6fa3b7b6937536e678c38daa71f098f22a3483e4b27e804845942c5ff4dd159c00e08c59bf635b33ed3e5a7c28406387f17463a5b4c57b665774118278cff7d0530973ce82e68d27ea8ad96329e0904747c645b3865dc25ded586fdc37d4d454345673bf5598acdca9010400275d18cf93e9f22eb38d11aec2bfc6d238a1c4bfe36bcfe0e6f918285c669dd3c9fcbbe05582b25bce388e269b6a7ce4d01f615c404af56715b77c723a2cb11d9b579e299854a35bc4ba25ce95913f8ac2dae96785355568fe9b2cb440d13e88df07510afc92a723b011b13ed27a1878e262c1a550e75bbd8170db7ffbfcef833d640c172e2411e36dff47e42be8257bbbb979ef4f512722c3588af5fae2810d12d754f954229321a0a9f02ec62e7a10e5e31c5ca13009110192176bdf3595f593e330f25fa6b721da0cf68b48f6c7e3253c4e5b2f49db43a0733c0fe5feeb64efc34149a4ab172ae3dd14673a700ae921dc1de50e72db77ea997f6486e71e42e2fdb570f18c25f871d94c72ed588cda3a60e600f438b241671f0f4685f429eeaca36cab4fc522ed68c59387771c4ad2391e7d46ee86632f8becedfdaaf5ef7b1e5f793fe566572dad7d88bc1e0dc3b8c1ff34427183e53c13eae2979b920d842986ece9789a5726f8e1ec8d53546021d7c1497e2b821aee04b665aceaa2e8333b4d5f67ee46c6c4a34f062df1cdb6af1b6f2ff7dc2170171436a37899e4af169fdc1779e943e728904b845d11daf421ea9fccc87eff69112b8448b247bb5694bef46f14eb77f7217ec8f8de30e8bff147cd2719d6e8bf4e535b8822b4d51d58799923c892dcd72df206338a5c06570440dc928ad8c80a3ea39016f4f4de252e2f441f933a17d7146c4757d57ef58890214add2c2293185ea3d3b045f8f96800fd8c16615110e4348c3a4035e408a6bb7b76fa0b2ae5dc5349392ec445046e2dcf13af382d62712c682a8cb7e1f14dd147d225ef5679a9e28fb07f444b66ea2a1bb7a2d3d002d728097ce81c70bbe7fa6ecf4e7f421230c8f882f1c32e99464e2823ccabbf8ce30f711608ea5a49d1d26ea2aa5f6ae3238585ea12d8dcb239cf185556a065f7972a44fd7d77499b11b09b7c95e524bced8348f884b68da03c710ae2e33e16979726f80aac5e5a55ae1ac5e161d9ceecd1025f415b0a1bba36cc4719e4447961351fa8284c3acce2dd60c18a916bb80dcc138f01ab55de106b4ff02d73c72a08372989f73a0977ee8e25a5d726923700f29ff78578e8c89545302ddfd6b41943c7221fdcd906bf13aa50d24a3dd8a31bd1427cf63fc8cee3930508500f08e2ec2724f05919f39c89da4b59e1534fd309ba0708e78ae36a4eed5aadc54b6a5bce54cd05938f5d25ffb9ae999fd584d1b29e81ac82d8d690da54e2c7e30bef81ae7728829b6163bc338518b5263d10689f7b3e6926c653b9cacfa1f11de006c5a56724b36e60cfb37ef977af443df1d2b7c31e18efc45268ec8b83180c6d5fcbeef011b4bc026f337859f9e0394b8af085be16d1359a758df3f9b9f2419fc09819d720694f448b7db36090fc6b76fff0cf6fdd103e5290ce3d5c17f506ce7ec6ce072f44df06156ff53981631fd2bb0a2246be5d41e9c33c7f5c0dc416bcc82bbe772c2b787b06b46eeeaf4b8bdd84879e3c0167d807b0f64dbc3d0bbe0f0ac14bc10b9dc1eab8f46da3c6cd2085cd88b6dfa591beaaaff3c8858a11935ead7ac8e2f79168002f8c33a9359accb8e1ba69dfba221dc28b04762f8bd2c9c271a8f2a729bf98d3221bb2d3fee0d911a0f6b3a55315347f4224edfd40ff2fac40db63a721a84c2cd000ecd3fbc2852974912e5290234ff3f1ad8d2e2823f3d683da83d7239ee964d2df8331b0b02813ff4cd9cf00e7498914015744dddc1857e36d2ad727c1d813243bcd7c203acd73f79ddbc886050c02af954cbd5d3ef35c510ec55125a10c4dd90dcc3e143a1bcd3a351f3837d6d26a75fadb656da4b36e7e740fc29bca3f8a9bc2f53262d422fac94f84685cb6773cc7b209f3add7e4307f62358722707170b991f61603445f2623461215f3e69800fe77848e2e08257b292147c72342cd1e683a422c3a7172b926cc73df94823b34532fb50f8b7d675616a5c0f729879cbf68f0caac478658ed8e419fcf8183cf6e82d8f4d21419fd30cad2e657248fdaa9be57b4ccba688cc1fd81e5d06c6265d78ec98edfac913506431aaa4229d124cd1222376760bd87a6810640448b3b2afe3f897164f3b3ebeb2d287d425746cd45653e82a18e30c04d08fb75d4ad61b37fd8b2bc7b0fd59206a1834be7279dd84526fb5996e194b5defbd4bcdff0193192f9224cd767bfa8c1dc8b25f72826b1246272ad592632a5b2e2260048b4c3f683cb074e76add634f838ca25f722dcb0ce9f62e3b54ef37e35e6e7355a9b0636656eeb5964a4f9524035bd9d91e38e0204fc423a7e7f9db005edc840e00765637f28507468573426f2ec173cf728da7eb35befedbf9a15bfaa0135859868dcf3ac87dadfe9858795d67d0e8e73a0270833643765699a5484a82e104b3ee166134a22feaddac6852f7b29f51351cfe5c5c9fd43e4e5492ba1ee28a84cac72528268ded1eaa2dc643676db55db072319c7db21258a1c25de2b4c93b1213f320631bb66627aeef3f2dbef51333d672dd281ff3d906aca805c78b12ca1e9c6c7adf094eede29fc9573c05ef3a445a69093b9873eaffb14e29abb6f0c7501dbff66688c7c0a6d732f5e7119dfc830c72deff9233f87b977ceb04133f1ca4d5442622d1076980bae28551e92a54a6487293d6a0763a9a65e9d86be3563a012b7202684171eaf7846b11dc2a83db9a6972fdc52b67ef4dd6c3f1a28550c9c2f7d833d097215b91108c7a2ece1d79016672beecc279bb820c9b73457eb2567a3d290ae7594c8b7946a1b956e953999124553d2874cb31f391e2ffe2194c347a236cd6f17620fc5f7f793dab49327af51872d6dc88bf59ebf25130413b5bb2c86b36199fc7d19f6e1905b4dcee54483490379dec98465686b07281967458d18d9a536842560f385abcba6cfe342f1e876772a3f17384e35394a9af90034208dd54257260b2c1d49fa1502360d7ce5579ea725dbbb9b0f54a431759d4440a2df9f9e35497dbff6dc67264167778c5145540642b86d6815fff51b835f9dba9e1bcd428c139f6cdb5a0b0a6d574d8a8af75e45c439b7572a7bf5c4fc8bf98106d5150907dae5dcb7d6cd106f6573ee5f3b498048fdddc3e56f47d075a34e19171e7ee106daa04a91faac1d1da5912365ccec17183eab0d8fcbee76a7bba7d87cdd93db0d82f597ca7daeb4f7a41d8bb34ed3f729a1fe94128635cb2ec5ed352f6752469b5fbba9fc5342342ddc8db259e045a462b2c7864ff31c707ab5aa4ab475e5c6e407577cf8b358827549f40e4a645b872197fa3e5911978e9b6e2604e9eb92c831bec349891204c041f617681585c3a61afa6a5e9b991b23489c1bfd62cffde5a7bdea3c1710d1f5a546fc193f06e3214674b1c2ca0f6322e7558ddbcda87eadf595df804b0c04fc7ec86c46ed9434467c0e36d4dc08aae622acad804a4f135d71354487845aac1ee0efac477534f9a721cec291f72f2492634edc0a4eeffc7e12d1aeb5596d7c5c379e5d72057dd4172b4761873bc69512be6cb306775a04743c9cd77fdfac2349348ba40dc0763aa72af3d9895804d9318eb60ea1f968203f31ff2c357a768a54e6043d7ffbab5f41c34b85ef4c3b3ba4c8f38797e1d37a97a9a9e27f989cac6266587d1511e73b522b7db093271e0910c036019d71cfdfe43e6f5707e30a23fff470c56522831015eec75eec88f4d567de21a924f323acaf6e140a17dad1aa35555aa1f5de64df86e44c30cddd71b76a3ce1b6eba25fcdb0354dd333bc39009f854b0f6a6b307c46919ba762b9c264e7e16ee1c910e5608cd9306b5de7c40ef57f66a7ccc08ea557267508dab634bab04d27138f1f31c9d0dea9f45246fae46f7b24228e01af14d72608632752dd7a17df89f1ce163483341f2670150a8ccec385fdcacdfed72a54931e4e986d6fa16953a235102b04088a9846e11456a70f6b763775da235541272e351ffdc64439e6c821229fa3fb7cedd67eec4c6a5334231b357b5a6d4f6cd724718487dabb9e3285d9cc6f246d0f428715d0710157cca086046ea3488f7664cdd07d4492725b02b778e705e3e85e5e9b8c8f8d9d7d5405c241b819d68a53d4939cf49b1a980b6b882190e1a7e8eb30957242be098982189f67b41293ccfb572018fe40879ad54d94dd965ae71cc2828195cc21dd96fdbdd34a8b1b0ea1683720e3724edcb2b5efec6e6d472841083301e58f95b9a8cd4851e4765b6a1e9b872c7de39c2422c16484938cee92d353659eecbefabc88549d470f76a26467cb67294a5e78bbde26cd3994bea867708c5ceccd3fd0c05a4ee6bdcf82fa769b034725321d950fbd1dd7a1e7cb004ebcdcb45b1af682eee719b6096ea905e9725bd2333888945aabf12723e826f9db7fd8221fa552b274117b870670117d7d9d85d72d85f809ca3d93e09d34611585cabea98bfd22213f05110478cb927ae8a1f1c3366b8f7a38e80d27432c2a38b4b34e5399d4b7f1afc4a5919ec94516dedda9f5cb5dfa2003d2c67a62c646dad4b8a2602a04a55dfd0b79c7df2a47406032de57228903eda59fa6e4430c2bddc2d1d239f563e1f729b30559f2f11eb61f5574072325a83d68808795edae83f9a28cf301bd083a77ed31c1457e7d1eb7537020a593dda17ab958889ca2a67ee41358be1b8cfdb5f76dc3fc1b6014a26c41f71af7232000949f049a3b26567f437f230e659047f9f3a15c869735544f5f9b5cfab728d0e3094e888cdd6b98613b97033fa1a8ab236d40ee83d61c661e56caa175a4e1b6cd3775441a61a9c93d82e306bb3d3f58489fb9667b0bd33a94c1169c6d4606708ae1ee90fc400809ec5ae6885089f527b52b695b5e1f0039c4dcd1752044c88597b5bc4691856c2aaa5527cf369012a8646944950cea7b620b55b88018611f714760a4afbc8278693f6bcd30fc49bfe30c142c083bc28b92333e921393932bb344fdad7297589c7e4547c2161f84fe8e09582e57fb7bac7b1b7f8bc686d0411fe9bdbcc1dcdf49b92b01e23a2533f33ce59276c597940babc874964f07972e67a3fab4fb1107bd1c75e530e048e5a365a7cb55e8ededd482ef974e1568272629cba77cb19f5723b2f3269df9b3176a55924d405212bc5c0cf35f2b1638072f489e54666f4a15b61e2cc5955226813294bb4fb35fd8c7e063843832c423357779f88bc3ffeeb06d61205b2aa3c672b17a256022f481c79a7ddd92c58819972a4c75c6aa449c3014656f478b9bb65596950c2da53119619d061fcaa1e4d8172954aa303e05e1eecd3f81419a68c9a64d541c5da82bbb3c43410746deb6d3672b33eae40e3dc75bcad3354004309da7ee3ddfb7d51bbf2ac12591a9bb8a1953f483b59ea10ea8906ed091fd9e39bbc53b8f65e99e2c106c8755a5434beb17d7271b1e44d81d2d7239cb7f3c855373971a1eb9d543dcf3ff72ab8ad86da44bb72dfd53660c41bc9fe8259e9dc7a0964384f19aabe5ad6087728a9539e07cd6e729ff66fc64ce0ac440fc5e1574f1177513e6c4cfc679d103b4e5c1e8618fa68728494e01207fabdbbdc7158c17b4aa892fe513b80c93f3c3de1dbdc5e97f988267971f61d89bb27894ca3f162a00beb0dd8ed7de782fe8507ded76e96750488467e410cc5c15620ce74389c0e653bd7d2d0952f5dec65cc43030cef13d4a1e472afb6394f8ab5bf916db047f71f8752baf73e667c12e62a2a52044374458d42723f1efa4846b5f47013522d5af09dc3d7bc324908a4f0732161ac85b53ac983726c21a140d89cafca9fe23cf34cb3d2ac9a8df82f2bc6e804c09c8b0668f7517210bef05480af5934f5e1af0dfc1a6de455db3c3695f485f030824b5abd522372e65db71a41d69db024a385bf641fe1c11494b6675f1c59810567c5e64e0a91572de329aa04de07d71dfc7e2e134331c3e7a58c0d2df45eb03cb8ec5a533a201bed34c9dc13bdf86da007e0ae1ee71c1f76d61353fc9f77e4331c3a11680c2372d2b386006f8dce0b985ae0f374b54dc70540de9421dbae0a1fb8b65011eb2a725e0bb410b3037af17508fe35ad87a77c25521bb8eb4da6b902ebea1e0d3a1f605b2ff3c99d352be624338e185c593aabb7f7f328809af648be2f841e7b2e027274e282fe7cfd83be11392e364cb1e39b478767e60db470b2ad2f8040df7ad91f12bc9ac0556e5b60781458b93b030d63f23460c77e02d981f4b1a2a62fc13272e1aed203d72ce5719aeb5186155bdee11ae0d48f10c1a64595428bf8ea9f8f729f277c303eac84c3b966dbcce01a768a137e390579f2d8ef99a0494022c8e7672dba3fad29fe8cd646422bcd67d304c2bb2c1afdedb901f172cbf33e4f716f6ffd3c06fff3afba315b7801cf6924ddba3240d037d7cefa7b4e892c0f16669c075f97ccf786de94968935dd8b4c1d8495fbe312e1447a188b33da61eca7b99946169f9b2e83ce6000bc35310f3de0484c13378f5eda83a1e4ce519c5e3092a147ab51f2bcc4e4ab7837938890c03f7cf01815aff0155fbc52560ec925bdb7e772a36fbd61f36ca524353c2a1bf5a37aa24eeb12d19f5ee33a3dd5933adb9bf5728d3653833ea561b0ec1e6806a26a810e340a9db08d20a9d7dd32421ad8830a6059e8809e11170e2ef48f89c7cbeb61e317277553724095f7d1db1f717aa627722389b5a022529a3c283f13d7bbedc48bafcbfdbb824837100e82551a0b21364553602d94b3833fd7297fe40ada31770d59e1ee8ee8137ca5765067507c800b728c605042bb24b735926d59be00f1188cf47496121d9b63b55df79e99e5fd2c723abe2a9eda8b3ad34c93dc6ffda2d97e4a108d3c63dc6c541c35dbeeedd51372fcfa4fd6968e5fc15b1d7386e4250601ee34aa6359b5902a8841b0a6b01c1f54aba878a923fb1f25ff9c07345142d04d7de2a7fa1563daa7bc2fc1fd9527d5722e3e7393ba252049811c73f13e3fc508c92835e52d146bd488af6e561fcef672da184ab46289348a582a83ae6df763f856d39a8138723257770a68657c134f2c33297d37412d07fc2c12df3ab4281e9f087923c3c0c156a07ab600ac623736720999035827d7cbe3b42e54db6950b7a80a459451273f75e779cd9da845c526725b2c1001add112879aff859f7ff54b62faed4fec849c01bd7507215bcf222e2627bea517105e2d7b8e6508580b581b082a6785b54eea226cdb0767a57dee5e72c4efad756db505e5597ab2691f973b20b2d41b10772ff8736dc0c6fd055dd4728700d430945d9d62cb045877a62abf1ce5919da53210620108725c1e05c2543212b951b897f29eafe6976c5c64b3ba4704b7d647a4dd1145a35a756f810f6e7217043a252412a56980e2a23b6b69d7ab966fd91beb63a462f7122ee02218e872a36433c82d25976b4d05ae2d8cdec9ac006027cfec50356d558ec800e2d54372623f63953faa44591ea132945067975477c1cd4e7116b3f32fa5ea544394821dabb1f57ad9eaf1060292e16d7128180b25971a418d08733ac9349a2f98606e72368f31fbdb9e72c51d496f209867fd8fcb8faaa7f708f944646c4f3727536959cb08b952c62df9d73fc027d299481d38468050207da5903db8776e454dc8e42e4eb45e56ebef4b7ff3d10da11acfc9418d6188145f39069e3ffda18edf06b853274d5973c88137a47e5a9d34dc8632559e2f1b9fc788e04569a9c9fb298fd119f1bf1d1faa0195babc50eb4f2c854deac9cc96389e31ec662460c3961cbd582e431cfa889ba10dbdd51cc4f29dfa8e29310aee87814107d9b378bd67122951726a37c23c237587ad8ed819575432399868ffeae5697eddd47dda93fc85979172cc74a46fceada4fea00fa74d73b168d9bf904287304bea51d1b5f5657c55ba72d43fca686a48a1aa45895a3e6d08d38afeeeb3009f77f870c4ca1c925ea2797227691c3e5060e7e0dde329d49a7fbe18a17d250f9b3abf7a6838bab1feced24a1ff42fc45e0bcb8bbc62328cb4da36b53a35e8e18745520274979f64b10f9872bfad257930c3ceec4bae1d220b178564b8e706dd421be969afbcdb30a003aa0f9ee0e3c5b7fa8214b8f6006b09008bdd4796af886765bf5550e2e3f7e3a0a372f0e026834b14829539689404f8857124796905b917cfd0eb303635f4dbf24d1f14411042b10eab1896e875f74832761651f2ff5cfbc67663a3c7f5d7b1f7f67000cd9c5f9068a6ac29bedcb5d4a48a378d2fb691b13d1f0ab2d7d4a81c028472af7847a2f8b5439273a606e53cd90ebd515d0a146276ff5f99ed8686a5cc7c494985c00d94ff3126ba3bcb9c93cfd0904e838d99c92ec78a0baa964f974fd147e916366d2d5fa5e832452577cc810640cba43f89394b57ad643ba1e6217a2f213cc7031b9e4682e92f2495edf36030b4eb9d76830dc241773501558c74fc74195ca2cad66c6837747abc2f8a19e2c41ca5a62de562ab8551e1703422377c8c2278fbc9e4fc962ae95ab18d1c7b0dfd6c7a864afa702e18c526de770850cec97271e5b6632af1b69ed36ce1a418262df80e91432be7b4474ad24a7c2567c1f7724d57f8b0f21fea7646c14cac62b79045eff0d1906000136bcbc8fc98afc32459bee80c5f6c80cf31657bcf0dd704fb0cec4646deb0adbc13e141a438e2558233e77d0e29e0fb43c82e41593a063beaa9bcaed5e3b56d10333caed41d9123856ad05942245a94f136394e8f16a0baab2ffcea41c0c5ae4e72cb537e6459403b2d74ebbbe39ad4a96857961a034dfec7553ae9a42f059d14ccfe46e1efc2d42972bc0021b56738a1b54858af0c61c2c258862efdcb1fd660648dc98fd5a2ad4f728168eb305308944cc1e43e6fce48ddc30dfd8c412e8508043f28e675b2331d12c38f1fbb5ed30c176ecd329e6204d0543e7bbbb9391a68530773d5269904a9728541c39397fbd8dc8f278ffa67b7c515feaee7fb306877f027ae6e57082ef145a4ced7620128565a0c7dd767e6ad49ce9da024e98d78288b564c348f372fc6723320f4707d6b5767e5ca1ccd53181551cb624b0205d66c526a985ead24f672085c2327ff3009e3b7860f1fa259400d74a399a2f9381ebb830050bdd1e0805f30c9b90544533eef68d4c3f9e1b33fe379d25ee78e537296e26879d441aa0820721b7a2e6069764e3551ade8f389750013cacbe570f663c4f2f242e035f988794692317fc11d60a92bc2a3a6802e2b6e966e0f8b171bfa6baacfa802f045af43722f4324d785e27c3576ab46ddd772cfdbf96404ae695c85ef0ba3a339fb559f7210049879e2faebe868dfc9d1ce07f906f0838394d5d646017a4e20dbd5cdb47254d26a6c506a039431e9fc531382c13101f93af3bd9e7b1badff95574e06835c05354dcbb17472563ddfcb8554a8a7f4614c57cf44d6d4a8fdcd5b9d578e5b72fbc75ddd93a9c6b68247b337fa5a191fa0a81c9b0619ceaa4bfa25e56a2073289315a92676b10ffa2b0f9b7ab91637b9a3ca1c55b899b7964c44fbe9c556b848c7078c1b8984f7888652b87f583fd2b92d49857c5ad7e3e9756925a429473472f62f80fcf968f8b8df0817a702d1c7dfdd5cafd78b76e1e13d5e365a5f467b7263187ea7cdac3ebdf8f8dcd63383968e54cc6470bcf8754f23e47c5cc73b17724471617659c480edcb4080125124fb15ec0a3f5b14c8722d3c21d92af9f4d910d94307f45209c6bea46c347446aa98dccdc8915064d920e9638d6cd504027e27e26b749d2d7bea5e26a59226adc5d3d42c1bd4ad7715e1be73ebcc26b2697c724f08cbebecdd3c7b77d64b972649b1296c048445fa9f56e4cffb4e1348517e15bf5bfb367d17c410b2c2198f09cef2b4428d9779e0b22f412a3f04d94e93597283f877796dab8e7f318ccc4d668d85bac04ec17b0973d26b0a4cc7968dda7e46da1999d041025f0bb20dd9b90c4f46260a5f9a978c9cf43bbab17bc239c169727e77d67cadf391e84233876b95d7bae1a9e401cf1187b1a0cf5b09cc882c7172902090b7065f1d8251e5b71fb9aa2661ad491d368815e473e868cd6f97bb3b5ad5e933cbf031d1782eeac7507acffee1413c5cb547f6196623330851f3a01c452d351f724fb60519942591d270442624b71be962696420530b6c5e37e21af072aa74b309aadcc723740ea90ad22588a68a30bdc5c93e8b3a7d947dc3b53c0f43c0b98c87d1de0e4af44d3a86274507abe74d7658c1cf0397f4a0ecc7db13f11b293f68eb0df9f62eb4d0732171bb5588d737f688a2e3058e2ce4309fad29fa7237ddb837ef0fc47b27edc73d0c06cf7ff799caabf30309cf657e2489c785a3725790afce5947c8cd738c9534bb937043fb0e4b6e83a4243a13b8118872be5472d90d7fedd4d433afa94cdaa6bdc1af4c0b86660e2e173d480f96e450baa3c572bd6abbd587e1576e37d9cb52140439642e11a143123d1794417f6e36029e9d720719a9ef40d49d38ba4b0d6df8852b9f1fa02539aeaa8a53e69df9f379a4dd058c377dac4076cb0965c0e27636cc7d6db7deab5eb491850b8bc227ee6fd5c372374af43bdf3f3a1145be4859171a0b7e0d319d16be6d579e3f89c9df41d1965684cb42343f72ef9e40561de8f53651c62d5f85c33f88d3e09f473e43e7fdac726d8431e77340b9b0ec671e8a46e0911ea7753237cca1b9d15483f966a6b5c54f87b35afd6bc7d197bb729441826d550806b8b4c4ac91356d8a9948aa7c7aa6725b36e3d3c9d0d2c418d778a1cb428ff83f9f093d0d2f6794618a7d0e7258ae72ead5a1befebcdfe9832032693377e387bcfb35b9ca72a9278825ee32a91d1c72f4ab5caff8c6c67f10dd33641255ab3bfa048a1535119c151df6cc41c47c9372f1cdd026d9f28f5c60e74c5c46452e799983f3f032fb6d1d27b88516a53a94727255cb2b64e8fd8414790bf8bff0233ab3b8e2576bc2950023ac947b728f96724edb73c2cda27baf0fc3be4b94b64dbd164ff008e186813e678a60f8581aa872bdfd3e43b770da882e838d0408831db31f5758d1706ef4bc79984df9795f826452f8ba101a66eeacde0313b89625cc2d6b5d396acd847c8b065cb39cd510c820b87b244122f95e8e4d9ed125e6b44d548549bc589fcdc3856b2d909e809c3a724ecc40c0c3b79189ceb19b08b53372d74c17a93c024de8158fb493d5af756f7206ef62c2023ba6732772968d801a7ccc908ee4f4c464ebc30a3ca9d51b2558671b6317e55dc6b2369a608b075783577b8557f780feb86abef20853bdf347864438218be1ead7b9450558b59a290542d8b30135d7e63ff6eab7b8c0dfa852b7727531a4291eca9e579a1cb4c8637ec5dda0936fde5dd4b3709d601eb9501d312e3797da695b8edcbb22877d507ae2a62f5bfca4c590f1664c7f0091b509d0ee72143c3cc7616b8606f629c0aab8ea8cc354696493b205d996eb3da8a1c1ea9c2c47725b12a340293beb231218ba241875c9af9403b17f6e6bad602e6e336b433d6ee1a3c6c8631c191057d32cef21bab35338459a17d3c4b7f8f1c3b811edcc7219bd3d389d8bf4c7a4997535e38fc07b0c244ecd0af3f3bdb94ce3c713893f726647ea38f4c6e9fe14025b2da73c45278682ab28bc16719c0cfdb7b79e4c00728ba2375a311ec0499eac766a77fb8dfa1ef6b6217687336ad67f866732cd680e9fc1a0080b82a922844e91ae09611fd29d862d49df3ca16563eebd5870ffcc0b25f1acaa24d8c652e0e1b79dd1f0a3665f563155b72e06b2d1335a131d4c837255bbbabbfb16fc82ed2fcab3d9a75ddd836e27ebad45dc4f0bf1717c5f5be672b33399361d63ccc1427e5c5a7d7ac6c10a38274be3fbbfd33f5e4ad3aed12372bd99734dea53c1c8c1ee759a968a20eed99d635dac7ce3a9ea687973d16d6b72aa7a45d70069c20d5666ca7c0ad54ab75281f11c1102f8d542ad24df90950e1a1d8a256c73ec4036d941dd0c8c098c3a91535242c1eb23ab427455183298e872651ed95d62ac9824f284f7d60328ac24ec9c188ee1471681ad9809a5c57db23b07f8074055601d14be99841be5186d03cc2e50b1e958c1fb601b732e6f063d72d4c43963f9862691f3b30221f08e74662d8cff07def795a39df69282943f0521a48bff5297f37ab0219ba3233140054713ca7041244e122f85bedf5b56b94a62afbfe0cfea602438f63b435d336eb6c2c403125480deb00a5bb7205df1e3dd72c6e101b7e470869be38ab80e399fc0e14c35d8c63ecaecce18e09121dcb1bf727182cfb074c3b907d9c8fffbedfb56f54c78922860a5e017ff604217402c8072ed06c5f31db61bbf2a89964f77d7474783585ee21b16044fa89fbb2a5fcbd22f3d3354bf1b89b7a82694eaf3d75238612695d18e845724a04b8268d80e96d3201ed9285a9210859623f8be9ce4eeb37a11da82c19aeaf6233ec739580938db1d7292e7bb9e415cbe858b754a7bb2679ffdf680f1c6119d10a8cb08cb238e6b721d6004c964f8d83234d01f8035e5c63f75f8989015a7f346c8fbee65e1ce21722f14b5c0ebe8e55edd3ec782c7ca091899bca0b90d05dde50ba75b324df5c2728a12ee11295a2ea359e68103d1cbbb8778dbece913d5dfa9b333cc12552dcd089adf9e6699fbb99a77562f9618afc080f46110449e9506c949e7b5b673791718260eac3da6c8a6ac9899ad7f930acf056b20bfd10ef97fa84708dba8840b4472aa0bb0e5da5121c218cf4ce709394e343955d3129b8e9510cb97350e66c33d6d801d39fe0282e62a9f4f5d993c75f529ef623f69816d3d6e73fab02350baaa3c6048265a16aed0ff288fef4b11c213d7b3634a77baffdd02a8a43d8f1aa3a26068f9ec64913ae97057b5232dc469a43ec820968bb3c174a2628bb6caa524de2b5be7222b9fad8c79e876346659dc60194e1012398e211a72e817c32e0cd51b7256b6a7d8dfb04e48b7e0a894391a8b4f73497e96affe5fdf719c4038c34df626fdeceaf8b92e9e5279d4c95355d4c2b48e737da3edb652465c18b96f62e4693220f60ea3905a81e715e2d5f0b2f7bb0b3bc92d9a1c1664710eec73162690242cc89dde3bb9077b706adeb25a918a8b9e687fc3f4c6a9dac116e143b5f6b97772ed740fe8a3597a943414bc69238408166ae02d5cd20ee28a7d3f37e50344252cbc268023af6d79775488c9c48beb10c019ddcf1b4282ffb8d8d5754105c60222de5a9aa51cdd0437908e506ab80043972a4e71d5b5ac8b08271df3765e847472419e1fb354d7ee4a6d3a61f01adcbeec817aa8576bd11cbc942b177a26a13c7261b574b4ff4f36cf5b6732137da7b7ef7d5e5c30268e9742c1d5f9c3d0cbbe7229af3619b35c2e04c659492baae288de39b8b713dd9aedd88b1dfc5578cf9a5225b019b26995ee5b851f9c663d35132bda0bd95d434e237c8103ddca99b6311072b6800e091029658be59fbd316525dd4d06ba00378c08b045f4df73e04d4a720fd6eada20df606dc05177d23d787d5d8945b9befbd5ae724e0405800ddfc4568a82bfdab525a27d113da495d6b8fc5be8e75b6de1fb65d03a96df403e9105725acb9f1f6fa8183a39c60c910af5c8053b2be4068929acea36e92bd38626c27208a7d3012b3ad1c47bca7c056d76056d14965c54d021b439b59438f9e9e0eb721e4ba3ff303dbd0e2c7ee6b9707ef46821ec95f302333c8d6cd139c17a3ad772aa990d657cede3eaa534ed9a8438018692c1688ff919d481e12e42722bbca8728f9344f21c03236c89695012a416f7490fcce4f7794a69a29f89c9a48ccbc3723a2025045f2670a1e73b13bec05da9de293e1df905cadacfe425f5fee668de6cf6cd7f340061777b429b450adcff0ceb62e3e6762b4645b13dc2defeea46c75aeb11cac29276f62266b80cb12dd6cdca18a04efc4838074197cb91127fa06b7255e41cc445bd252899cb7126ef225d4f0429026eb736004b0a5bd029a9404472944aa5591553bf76d99fc57e8820f6d03d043f0e39235abbed3fb5ed9572b072459873e4f50a8c50cf0267e15729cf5c66a6ae373adbe897deacb1051af71858b296809a4624039724eff8bef7a6c1dc696bda664fd1318cdb2c3e2e5f44ea72721d8dd80ddf56b464ebc0b36fd5f96a305e4e58947d58c5c614c9b9c3e31c720ec87f22a1b47c73a819b72325c9a09ba102b492d0f0a1d7a7adb982ec8f9a0e7ec7277819cd0b7c34e8a095a18297fa675f98161907054dade337a25d0903118f20dba3c3bc673f9c226d3180cde6b38a4722c0b0734914462d09f7f0a56a724736ee63257adb5986f33185075f71188d7bedb7c84b6b7c93f5e995497f3e5d480caffa44a335a364006386ca6ca2a1b675e8dd1705496c00efea2ccb3982240d9b581958ce3f4645aae9b07da135d1232fe21d50a7461b5657e2189892627213ea6e7b4f71d00eeb6800689605f012d5fd5cb1360584206af51e2e21a90772cb2e8bead6d1a73c24b93d31e526d2fb5ccd425cb6e248e7e93f510399c6ab664cff93a215598cfa7c83473278ab284686ace4f6a3fc08e2a6524428ea3f9072cdbb2dfe6b50a1994bff6b3f43049328f1e4cb39d9f72d2550131dcee1e44e72aff444e8aa513e219732ee19417f2fb17e534266489786e51159cd317ce5cc721ed815eafe3b4886cfc5f8fc656acd7fe704d91b266aa0d5390e0b9dd72acd726e506636765c6c7f527c77b72b3d61e92c8a896daf0476503ce56ed41fae4722a5cc037ab7ab2a7256d67cc56b13c0b9ccdc3ecece38e6d5cd7bdb32bb71ec72b71827843f1bb40494c84c11f77a3beaab2b963cbd8d1595a03607dd0ee3d572f069a2b8d448d4f95c02e7142e61935005865236150d4db7891f4f4d93766b2bfa2aa20271d9cfc37c0c95ae0441eb499aef3906a2ee12ed2570518149365c7268d1094f525403330f2d65f3710f7446fa4309f145f5925ff8f443ae5fd05d3f828d73332c663a8a01bdafa495c99735cfdc6b7959d802f183649513c93e397280962277dc609dea5182e0ba7efd00cec2f0a7b2b61a21aaabb0bf729cbab6721ddfeab2bb0f45f2320572359f2587cdcbb81979af08823026df3f53134ea47284e551479b4984078d531becc879f9fb1dc63d74b4f18e341beec8c1d66de47264b9bf06fa5d1396cac32b61226f2e79c612dbf7168a7d8533785d2048d16627a0e4f65b98b444ebd87c4fa8674436eb5c8bb7f369d631274fb5957b1b8c7c1ec0fb7ba40c5539b8b76403e291453c2b25913f02aa374c0eb6c190ec39f35872ec7bccf7d58c8cecb4b8b28c0a291b1fdd6d3be425382636e343a9f4f7f0d9724a8b97357c6f46c8eea68b129ad61977bf2d739c5358eb83fd9865e9e22d4072fccdd77b513c44d6f42d63df832a20255ab54da8b8eb0bd1adaf88336f6ea033325630adeb2b2ae9f3982144f7eb7e1fc2cc1e743bcdbe64506223473d86477274ee486f69038725687e16e6ae334d5d3c335bb9c3d1234b2d2f9a07e44faa72838702e8c44c079477d5d27c5574126b7db77ca6e1c2f26645624afd13e0c572c3a5dc954e04ac3ee268e0f1259471a0935ec9d4a0c6fe4954a891718de1536df8673987a33dbe8b9585035d025106678e7a821c5ceb25608cd563250acc35334f5215835785a8aaad0c5f66f7091cc4bddf18eb38a96567083cae800707b472b62a1092cdd7b347af71d7a4024db299bf799faa372be3a2257c824b764c721fa527976bdf642fd1799f70b04e44dd84e7dffb084f11851983fb3fe71338e752d56a10e7d68dae5a729b0f91f35ba7d13015cc5968fe5084a243698ff8d24672439fcd04cb47051ccbfa5018f8d46626f6af9ba47be326f67fc9d82f18c7da72edae8325d55238870b5281cc9db988927e027312d0d81251226018d4527b3772e727621d74b464459e2ecca1ed8cca151ed368a1dff9a1168c565341c98ff93c34602bbcf21b88e2bffd3d86da20a528e362ae9bad29229a88b2f84992d69872c9490c6aab3725f10dad286945dab91c474d2dc56e19705bbcf5cfb54f51ea47fe34be20f36d8330a5653c339140e06b06cda63d624bc0d040bbe2267d0f945785759d93cc2086985065c587bc0da16335d44bfb66c836e676d8d528b5ef0072c5a88e778e93fee2f72580ed4e9781b9f9547a073e88b5c19dd996e786f756210dbc684ae21f5bd97f61d59b645233f2964ded88bbc3c9406ef7f857991a59002f9606d1421e69d694cde8625ab4e4dc99afcabb9f80dd478edc399a699db810c79010f978c9720429633c474ccaf394debf2a084727df537b87164cbb592b4789cffe855c4c7b2b03696e33be678f9f522004ea8ac7520b916ca9246ba607259008f3738e31f64101f854393d47b1653fe848fb026b772334d2854f751ddb72c0e5505aaabaf6eeab15d46489867bf6f72a13281e2d4cb6cff7cdc77899cb2fc537cae87d5f1f9634716c8093e51963f5c8bd799f8797dc61ae81d792a4413f09c2f118f77464e220e4ae8f6ae5a934d6cbc3bcd4095ab51930f211dfab2a725a2ff31aad724338f89ef65b1192b4d8425357e9a274c7f3f313185928e7b51f4a44a527b91ecfafe5e5277fdaa702fe4801ea3d281387b0ee9a480d5e2d12724e1162cfd1a35cf14db7e01a213a851ba287644080618e2ca4a48be1e4307b636d9fd5dd007f2f11492479bda6555bb4a2c5f301158370b82d941cb526702d7251faa4ab36f96ad084287a1f1097e1138b315470e3f0b4701407a0ea861fea3ad2e13e818b3a7d9f2f44f448fe48e89b44af1b64dda55dd0f66c1c0f4b1ec1726df1af5e6a1a5026d665ca1e4b80181cafd416de85c6ee5209fcdd811e220a725ead2f87a0147d479d5586e4e808c062b0ecefd165fd2c14cc9b970d4de541725d34c0d95c61daed92933d6aab44d055092c0d7f6e4e04bba3870df71898fb12563d55b1ea7412e1ab6d5e6c90ad5b4015bd441eae05da3baeddf62d370c5a72171df66424cc6455e0fff8aac3a4aa6bfc017e7dd028efcbfae8d13176edab72350545bb64b65d8c8a0ff0828e384f3fbff04e272d7f8482bc9927d4e9ab995abadb93673a26b148e51cd1d96cb1e17e653cc6f1fa4b6ea438d13da447f2221a6573289be62c0b53847de6aa9cee81ebc4a18ef7b8a4e6ce7bd0cf9aa70710721aabc220d7e532e9b82d5218a95f1e2e1e315eaabeaa717aa4f5816de5ff4f5c0d063a8c210bdacdf847d1ae3c16e1d4a630f29ff41fa96e0e3a82127d6e1372eea6a70d2196c0d654e2b5c8bdb049fc338e4c370337e2ab89e0e7e37e51eb726fef19b0aaaf36eb2aa399fef97989ab984ee529ad93b568f0d55d68ec3f9f4edc7b0f18b4f6b88f5d8403d708f51d86b5aecf48d9044f16fda0414d34d6517266a0a8932e1e63d2f052cd2ba1d4fc26ae08bcfb88d949e4c1847f9754d47b72fbdb9037a11d72f206418650088857cfb926b5ef0ba481f9e5971600ac3142089fdd4ba99e93713d313581b61945da11bf1768722756e9671c9807c9c498e4728df2401b5c5159f205995851e13d69163fbdfac4f51af2e0c8880801418cde6ef6b355a74232a9542d02b635c8969702188af54ee2510ce231d1785bcbf40c724cc0045f6de5f063a8b29c88f68e1ec0955b8fdedec1b80220059bf348f5cb16522df05abac763b639630b1a47ccb3286d44e71a54b228259dc4e421bf36ee72f15da5e0126655f2ceedf728b84e9c96aa6a9f1c8fe6e347ba8d9a8479bd25720409456ebd157e4f6173a17e41194017cf8983dec26e51f7dc56c2fa7b545c3102e73b3516fea5ef875e694be06b1a93d5480e75bf4b17cfcdb16ced996127727c7da5c3d4a6e42b7db485abdac7453a7600e7b0f530c22d46f716f79425ff4d4352acee2a791db4f63bcfbd8c391d4f0049c6176e01a0629e4c1fb36e96aa7273e3adcbccca72d86455282548b5c5409935c1431523a53a7d742dd12d79a8721c67dbd1b0b4fefc17f7e9d273baa0895ee42fbcb4aafb6c04e14195deeb5572f667310abfab6bb958639282eca7ffbc50bdf0341fff759949a281f48ed3a9615b9382bceb3aed458d6be3627f1d67328d24dffc99504ffdaa3c3a63e0d94c111f080a7e46561d525a05fd7c6d8256afccd68f19b1a100c6a80d282f004b3a348b3652fba89a26be615832c5130cc055f88a4a426ec5b43ffb337fdbf0d22d2b876de937cff0cae10a4e6c9261ad9413529c5b4f1d6bc5fb0f8c3e7dc030f320191a123eadd44b4fc9506805faf817d69e824f7c2449e5ac7eb315432205f20b464719872c680aa016e2e2c9c4b2559b7597e0b05b6b3728ca3d6b1d0f07ac6824b6e5b693830dd755fc688d33641a89edba790265c8328dc504d98616885f726d79ea1cb20cd8c9b479e76930fc70b7184f4e21e9eeb2a0a763d2aa6acd0a2956ace93e1576ca17db8cb3d47e797c64caf2323a9858afb4ea08ab7859172272fd5bfba03d8df6257a91fd5c23f361ba3f5ff75f08228dc77734b637a2611e0670fff09039fdf5964fe3ae5ec756cd77297070e3797fe01869c73e39d5d92f729de6a13df3b50aaec8bf50c3e4334189856e14db10b55b53d3ccc3d92e6bc8725a5d16bcb557d9bf2db1ee102436da7cb00dbfc0504de8fd6ddde928122ccb726271e7b1db8dbfb2bd17597c6a76d3daa5ea4be5aefea04e941a924614db8e211cf9fe86f89ac0691d56beaf6af6c77e5ad9d743881b0b8ab130eedb800ab8708945bb409fc66fbcb639275d5b2380270c56f14872d4d395ac6d0108d1bb4c268ef25de5d2be8cc8a795c44fbf1ce068179b6a2014fbcd09d620540a3c16ee72f29e41d30f6fc5f4c6a6f3a5aeab23687e82ab919bf060bec4a7cedf564a2872dd11b3335d80e855c3964520b39243e14736039dd140f251365eb36543586572cf8b51ee374591f7ed476c9c63403e5fcb556dfe1d2d7a596ac0f96e17f8104324c9d0e661d85bdf4891103646696f91d020e0246da6424c0e8a908036600172b9e75f14e47e2d2e145aa12e85728bf88a095757be29b5c0d49dadb52925f372ffd3942304cd365334e31f0d7aae662d9f2d2b6c5ef6bbce19c987d7d6e62972406a9a8eb08608304845b5f5cf8b615b6143706e5864632e9e2ce3ec9ff80d722a7a03b49cf0bf74dbc15d146defaea6e3a0b4fa1f91bfc6dc03ec3cb6bb0d72b4d7c387eeaca961d9a947e72ba3dc6b8df904e9906bc2f289f9940ada7b227223b717c14d836aca9deb4dc760092505162d5357b7625e5e21495bf9ad379e528bdc920aef98bcfc9469a1feea9631059b3c5f612e0a3786c16d12f80bfd52277bcd1010595e110495f1308d5ea3d96cd4ea8c50b0bad1d87313c3fda4abc22e665e0eda4b4f39dc2c2409ffabf90f926ab232f5334079e12b8ae28bd8713272022ee5c291f0e411660b5fcf0f1c5ec82ca1ae3361c38c9974891b4daf2cfb72e5c314004fa91aea6f229a1ae1c1ed1a66e00d88d34bc1e151211044920eb8ba02000072d2b8245fdc8acc45eda51abc7d07e612c25f05cadd1579f3474f0bf1f6bdc672edc84233b9e64cf4576a250ba20f9e046f649923f4bca138366f8a6a950049057134d0d1c5e69bf44aae29927363f82496a930072ce078e4336cb9cfb1bafc39c5769ad0e6e0b8eba00055889232e7e5772adeebee58325683b0d74fe8ce26728ea564508f5995d65f8679791dd49d98003c9d434c4ece4896dc48434dd85f2b51c65bb81cc383accbb1ebb40cda1d6994f125b8860675915514561a593f0e08d0ecd0057c165099d9a1ad604f210e0376abbc05231021338b624b7e16aea272706b3c7d9e2ec0cc0aeaf53cabb103c0db77ae83466ab5823450824e1a643c6839131dddb986ea2398950b4aa1fcf83f06785534a5b130c11fa3569177735772692d8aa2e408a9bc48b19b95740a816c47de56840e45599a078f2f742cdf727265c26a6ff7d642de2d88d4517dd85e79234c6f3760e870ac3a8335c05ce90672f1f46e203cd79f43b2b4c693edba46c0e12adbb1612b51620e455bc828775346000ade24736b637510b6a5cb76c83a987798c9bfd2e214eb5ba7dce579378772505602d7960c2f67bd3889c87a55c197b6df8adf5e32f9d2ece1c3db4658643202307d055f1d52102aba8aea7518d82503d03748b2eba91d477266fa9d2ebd72955d3051ea318b0826171c86d837246cd47f9181fd063c26cf8b3c3805b4a07222db01aaff87afbda029cab138c956e3585586408369ace1e60537956892e96e365d46d4afc660206407403414fe56b67e1a22b989ae5dc68d286e4f4d3d1b72e4bc4a0c19c361aab8d9c62e876496eef4a65ab962425e129c4fc89667558136e927ba5a1732a1dd6a6d6bae42bebda4fd6dd541b8912c84524da063da7ed2724ef2223e796ac6408966806ede932621dbd6fbd6ca479d59a42c40ca84ae4d09987d8f3a7430ebce7d247b91888106df823e27b646fa69bcef254da9916cf27288fd75a8a6c2335f48a2e9bbfa378606ba499096331d32f8df5545f9aff55072c4954d58b430828b6ad76a1b10e7efa75704c9d3d3d98e980c52152fe4146972ed2987e8d6088e7624c1bcccb0a1a71eb1c62495cebe22a2be7025dd0ac53116048922484cfb7bbe649125c4961d79fbef30dd8aa1ea8b4373ff95ef42155f72f5e68bbba2bfbc30549496037fff6705901ab98580ec6e5301306978ee5d2572629a2c618ab194659ff688a691c06e2753487cfc1364d139c4fa153c2bca7a437a027c11e4a78af52600d75d34fc94b04d0a1b05c111398cee3dc50f5eacec721c6e433e6bf0806fc677da4c1eff796b8836773cb421aebb1d15dfaa7e74260dce491052b211962779e86ac68100c27e7045132870627fb51d76e240b18933723349d7775cc25c70e2619ed8db98613274fe47dbf815fb8af9d70cc3767e8e729c42c8d496cded61287967699aabaf5028eacdc013ba7f4ffa24558eb54d9172e1c0a812bb0acf74a9e1bb3d00f703864f6dcfe959026338684a373b8bc07332720e6f0b3aab05530ad136a6bd766eab07281f5613a1fb4cda7b198098692372215d2ec859fda8911075f18bd34700ac1d7cb2c9e7e2b42932bd619be3e71f1d209ff627c5a8c44f682b847bfbb57bed5eafd12d19ff43443d2edaf30280b1720730e5bf76a95345081660e34f9c0b439a99b33a74a0edb948738d27c5db09724bdf1080722deaf2eb8440a9babe7c12d9ce2468e8e62a1c7c61c51677d14737510a1bd68259ac059059ef10b51692e9c28fc09705f1a9103fadd99a39145e66eb9ab0515c02e1921f76c03100171d4aa630ddaa2273cb9eb1dd5ab3628a6d72a66dd0091bf48203d06e111fab66b7f89cada34b8c5dd947ba86b860b9e1b272d8516602b2b7945c7b620a2b486b633f4163ebe29ce5a050bea135a67504e7729594e01063b30bdd22cead0d57e90e2fdf936535f790cf70898e1b643e5fca0ca3bf6c0c12e7448934689b11ed0e74ece8a892513c779ae242da33b0b2debf7202e74f53367e30bcc23934d20859827e2bbb4253760141226ddb318f5ae15972de5716372fc995a7db97da7cd3f1a72cab7eebc1dcb2f3795e86fabde7d283096dbbe2f170be7ba36ea825221a6cfb0883c0123a391ddbef5eac8eecfac737151eddb7342835fa8f1169a9703877da54fd1dbb2ef09ec88251de86366619ca723b99b9027d74935825b0a19c3ef63db41a3a0dbd23e6ff251f055911783718723786f9998e9779399cdad7ef905137010968f6f6287c71fb58219339dd9fe672f23280c8b2140c405779b29176c6c63578c6023cafe0259614da27c9e578ee727f07140ffd14c8553a8050769f27f4e2b0a274ec2b4f85e9f490245341fd0f72803e17acebf56f026637227228b9bed6443f69bcbd63d16581736a26ed510c72f8afa5cff4ccd224d998b8d6c129b2847cf7da842f9599c9a09e86123818bc727a3a9a9a16eaa1d9742fdeb742f7057cf5b58a2fb2ba845971f6a6d0ca444c72e68e76c02da71effffe672307fb55fecf4ffa7eb65382023ca7e82951187f5720d2d773dd641b7231c2b7937058f90884aef5a393f6537e47f16e2a71379f41e886aa0675c71fd93918a8cb1e043ca84ad217628747550c60f428da3210871723ff1c1cff65fa9d949002b99ee2a93eb7931ab7c21e31362f3aa7296e115c16046b29a8155ca15f6d5b9a2bf2fa7181f10fa4eca084c874b725b9b61f5223c72c47b7fd2cacc706f21de46d2ec06db7c0dfbda28dd3b1a0666b3905378fa9e72ac1201eb36e1ef9f51b4f640abc6ad65768427f2c644576f9ca23977268a1a729130de8c15a1a3c77e18826de1b62dbca7d22eac88c9e36b5eb52d60d4dc96644dc9768fcbdfa327f0a3867912b9cf5f635e858567627ef682551061c7b38a22ef84277d43a66a7ec32d8e16647b28d9abe052f9fb0e43ad869095d58642eb6d762ec36c0f41a9b7cf5c2f806ee667db90c639ab2188d7095d19a4970dfef3728247ec3ebf1c213e562f8391ff2e9739c3839e2b136844c937df44637a0a3e4fe65f6cc26abd9e6dcd34ddae67d209b71935d1597f80f615bbbbca0bb0d1807238c2b9536bd7c28f0eecbb5d28faf63da114122d2fda9a8c4e98f4a9b7a98f4d57bde32ce41658e6f83eda4b65752b6ee198e0ebb76cc7fea3f0883855b5b01a4039e224641a3a85685a8ae193135681deef31ab86017be0264001e71f5a5e2b8d4e1a83616c79e44c2b64b12de1658dc91b296091ff62bcf93275700ead1642178bccdcbe9a53a1b5d4b290a4daf6348ad43492310ffb781f8233e2cbfdac07c98a12c6bec66fd1a0ef23ea67209499f1a22f0049b1acfb1000f0c44eac9872aaf60d9adfd48d5f7f3192c17fcb1796afc64ab836fd7a5158a77969cf887154ad87ee0be77078f1108bc22720b15550a40ce83d189989feaa438fd1bf99c072543344c7ab337ca771771dd5d2a331146268f13f5cf13598c2c0d754809d06723e09a1c1ae7381a69a84184584a9d40ce92d189fc1574cf24d17769cb802fc288f965b227b0b9cd3bb68e59436bd6af988dc73e01dabe08cb990e4918adb3f5eb5453a282872984f3d1e5b39870fc51689450c3d48542816355f31c9cd756a727d6fe080dc3cf2a7c85e5188b84e4541db0b5b6e8a6323d12210b70f76b33a581d8a604a8456e1adb36b23e4c41f10443cc96bc926bc4ae864b97628bc309072f9f9f5068b13f0834faa92cae14373125869df4102119ce1fed5d9d16efa7c142e03e3c2bea2ce2d145b5db5ee58e7b6d6e0f42d4a5b01416a2d9684ed5b987298a73c544537221e330cabc456d023d9efba0ff7f8de0e3a7969f8e0592424729b7646b1cbf2f3411ed898a494713947978594d16bb22808fbcdddc4d561e334179433722ffce0f28abbd12e2468bf08f3a7cc5835bcbf314cb0751ed092bb323658cbf42f1ca55c81b5cb707e529cc3018eae81cccf8f67598caeaf789af772c32f1efeb911ab723ff0a75e8c2140681d8082d7230ed8318d3f4279c179e8029de9479b2b35b281c5f22103a0ae4642f41473d4f5d890db345b6563c6dfb10798c0395314043a9f274e0a51bed92e3f71690c0d2ac8c2bf56ac0fd929e19672c4d9c7f8188c79e62a03e5af36c7b227fd1242b33d03a8244dfa578c1829f27203e9fa38ecb41ece49b34789c40ad362e580c3a1f593c3026497e591a957f1722e25e9548c2ec0ec8e4731c1e7f6255bb9f96dede3401a631fcfa836fdd67a7221e247e97601d49e2e5a2d623e2f2cabe916d7cea457e259e7beec073cddb345dd835fab3b23f0bd56d6dc54c058c71ddc27a599d91ed68558c3af181f9b65610a4409dddb2a85577ff7149056cfa01a2809b87a85c197f9adcfa81c47ea26726f1a72fdf3009273f25d3c5e60637c8fc248ac08126f3d8ce4c157446928fe729fdad7bedc18793b67e0b02be5f3f48fd0c3bfc9270e2f852cef952caff7e5090817979ab5a4d2bda38431ac8bb6a8a757cae21a2461d79d96131f6a7aa809428470d2c66a9897131d196f8a427dd033331877d7aad77a6246c7d3121418fd72fbf2388079624734a18cfd974bf3ad32d00cdf349d67ee424d05d17034607f7294c3a68937ecc14134a85b141ed588e9cb712eb520a677f1008a5f2a9ed364722a123479bafc017e90a03250fbe7d821f4a822306b60dd393243630edeb60b72996942dae2afe3f988556bfae63568ff73248cff7a35e03e019635e1b072dc72d357b41253b36450501170a9712d2674c7a8d346b18a70cefac367a468bfe8725784f10065f3c9afa090fd62d089bc759abcded38328a7c96938b7310dbc0972efd7f46079423eb57ebe2d3215a74ba3b7a7bd88de77b02613a40df31752de72530a790935c8b5ed7033808f25e5934cbb770e42e68781aff65ace2c434ca9727e0f7eef79b5fc62f4d6fe70cebd18ec62c8176e5d2d76ea2a02a870801e29601b0707689d7d3c87d1f524162a27deefdbde02cdaeab21561e27b8394eb63d7228182f3650b53edf2c38be442da42808484b17e0743640aa4633a96bdababf7284255d627572be8d8f94d0e6a4f0a698c0bbf8c9304bf0f77c0c059b63a9dc7227a41df64400caf179d7f946e560381625c644f5333e0577763181aded7ffe6ae3bb8cd44d15a3513925ed32a18255093cce4ff6da5b03e29c462d6730089272cd3ce41570464765b180cf9a89eade40ec11c6962cd5c86b587c0f517d2ba4667b21c95646690353a3e1484e6c490ddfe7de86103b7ee332984a3be9862b02729d46e508329741f7b208d8003d01aae4385e58f5b9af681650df4c03b0c8f372fca35a01f6738beca9541da33c27624ddf70294d386e748e724375ff64372d7250a5ce72760f1148c7c4c63703089570b8d958b582c9cec3235ade49912c617278cf1cdda94db44cfc21e2621178a28d58eac6b3c597fe3f07d10d979d3171721705bb3c796268283cee1d91f80cbaa9691e6dc9573011125f605bbbc806fe721d47acce4e38b0b90adaa5bdcd3cca6aae0fed3aa90bad9ee0fff119d883303d67f3f82e28958fb102c0bffc8b303e8b4b60466b92330ec613902aac2376697247c367a20dc76b2d4888968fc845dff5922b6bfe728c30aec6bcd8adc7218a72c65fd20c9f9d5f22d1b79a8c11f7383400389c1d6b3e3ab36924c01b56110469cf01b0d279867531ef2cd1f3e027b17d9cbfee6c9d364e5875a6decb83474e72bd416d2a37f9a27394f13c0d5e749a4554676301f2f1cc906f777f04df04b8722b7658c548a3556bfb31b451e3d005c01dad0805d3dfccd74379f03ac86ac872c33587d42d7bb8d43ff0b06233e667e559db3d78e8d9787461279c8dc1edc072ff108f8e7234cd035eb8dc48c6b3971a14baf8bd402da72c93b51afb9fb39c725a7420b1de93c067a4d84d91682cc084bbdd1c07315bf2daeb5f70cda2e8941e706a76c4231a41214ac5697ee34b7b14bd15c8652205e86714b507cf0a8196605506549c46c5de216d7fb9ee6d261e80c87b3bb1ac6d4c40d90bb5f93eb10716503cd513a67457d6b8c0678e6bc9f2b3f9f217ea54a1c0e6701bfd24e0611b72e7d77a80feb4fa1e7f632fd7e8cea9b35a6f377ea55f652d3544fe7ddabd2a6b8bf9c9438f4cfa31d1e953c88f6fdd67bfef6b33114c53c22998483aab2ca9729b4415e8f71f897f8f625a65397789e0e79a2e60ec2b041eac85943a607a2954e4d2d41d529051744317ce073febd7ef5cee004063951d131e537942bacdd022e9723526ee88c0a7e66a1071187ccd0cf032a17363e2d5df2bed7e35f3aceb3631f7a5d4533adbf7a7c245ad70bca3c6bbe97803c4bb3a5195bdcdcceec12872d46701c61f3d015dd94d01410d4113f004c4980ea4e401be282a511564466415e1fb196058d8a7e0b18d236c12c5685167eaa5cbe6f3c5351cdbe85bbf1d9f72f07994c0c982337deab6fc8c6bfb1e66ed276f0beb600f6b4cce1a91c62d1d721d6a1822379af03b5b9e44db963e1bf8400e1c67ae4f428aaa961cde9213a1729e3d0b7db86d65e05d1a53d1033e410fbd72493e3365cf790095eff091153572d1b6d5a848062bfb25bc25129ad5779dedce73e2114c1d9a8eb890fd7879fa708cf6826ffc72e1088f66a42b17a6c7e417120e5a2b9a1745590a997c0397aa05f34cea2137ffe9492d12c88e372b535eaf348db837e223a7adf8b6324e610146e39e9b682730f74bc34a7870d00515423919fbcace52475aa7c4a634a3746c036f586ac6e10bfba46b14609df2109fbcaae937e2d20f8cdd711b5dc83ff09d2a2a39030e4d38ea567d55f95ce832263d1ad085280814114fc121624a044be87247209ca5849ac8402f2012cf4c542582cfb5cbaf5416589d412568e17be131725bcac33cec2e3877d80702e7544244ede15686f17d8666d52036dabe8e683c638062d2d9255cab44523b4103d4c44934f0a4606f4d216d58470d17e60200846c3c49e3fe6875e64def7e6d1df8338d6312f4d6e0dbf9b95c19f776dbe87a4b5b780eeb9d9914e6c925c7e4482cb18b37ae8a6a43ab518e0f31efefbddcdfa02cfd561200a1db9fbfcbd3501a6f04eb49f02c5a60c20f807f533fbc47acbdfe728dbc939dc08f598ef597b35ddd84032ea403ee2d2c51086039bbd274c1b80072f62080082158723fe7b532086549cbe889247a27c7ecfaaa545a8ed52e1008729603c8e0c887f30897a82734211ea7ce9c565a04d0bf2d37eb5353bf91ef4e722b00fbad210305f3a33bd4507442d16de6c1b35531a3a24d5444a91c5e262a61407b69756d2b7424ffa6a2df838e5c0e54e05e7be58ffc359f7b6e379a25ba54e27d6ef89024eb294b681e92a988a2d77aa6e2e12430b88454d56cb7a1a8753798f4b51e73537d8024be5d7445b85febbcb712024b5cae7982830de14fc1dc726605a1254f359915982ed7285c6102bfde542522914930263d3cdd1101d9737289da5f6cd64168591481bc80014309eca44eb479beebea2ebaf569cdbeca4972bccffdcfd43b97fbde3e99952e718954f0052db001d5b40cb8c7f54eeb708413d64a269ebad377c50a3e735c31df01da2d74585dbe45eb3e2ab9423c2bc09272fc07cc5c0b5ba2512d21e4c52ada8ac2313ca9653945aafb52088c97d4179d72e6963255e69bc0fbceeaf575b9beb1b3c4ce7a92038b7191f67a78863fa9d572568420b70cf6a6598cf7b3af99be42c11c32ef0d58d802f673fa24df07d020480f152a768cbbb91f3c46f0156bfd42bfa44a1dde43aea77cf7d44aadccb2426ea551cdcba090691f53e344fcec543d8f8ad683244459ae2dd8246cdc98fbc0384679745ae6d5db7c7fabb7cfe738cafa5dfd3fc46289a2547f74b22e5c4988664ec2b71e5a800d950ac879953e70dab0aa64f3c260f0651b51ca43989b1a6a41f00b6fe0c0d623c1a65c61e03f7cf840fee668531f5cfd0ad67de58ccf8c7b7234eb7f4fff9340f38b6562f89137ba7fe460b75300c4bc592bf90cee02d5b4727aff090a386c0aedf7c88559b9a75b988fca073f3a8ad4e3db0b8be5f29c55725bd20204621dfa75b91c257b21ceda90d706e61b164f255ff8aa1db3b9cc4172bb83abf8dee622077ade779d6e1472f86288a5da3ab1dd1a63a96919cb3f0e56216ffaccd0a087fddd285d4504a605a915201b4dbde52677b602c8299a549e2f381edc3a6730df225eab17e6455f3295c60ca764221ef7379103f4d3a4bd5c1ce9cc9326ba1aa3858791f5d756e2d5f276f1a28d351f9f379960e4102f9ef85b8ba08703241abfe969723ddb975543f3d033607cf9168ab8418f8cbf60a80c366b9bb659202a0dc06b38b8cbffdade73b56530b0e16c264abb74ef4219cced729c7cb06ecf82f761b245468228c5862c485429aab0ff591ed9d8759401844872262168bc5d8bba09598f962a1d6f53c943b1fca24309db843e77e238f69bf572d1c8d68303cd7c923e0e74df13726140ed6de5c27dd83fbb7ee09e252b4cfc72c7e087043a17535e651eb1484ed65414c8e711f5667f96c1f3f41aebc7b8c5579b6f639f41b71403a5497104d419502243a491646c08ba2740ab15888c6a5e7203f01277ce77e90793efdc985d2a5904f7334c78703b0ce1c7c48691ff5358244d49296828f6d9fb2cc4f19fb7821c22e927e7d333e0ef9a7d9aafa4be4c4717ee6a911c405102b1a719d7b7c4e40cd3c426316a1d60ec27d2a97f656f2747721b9c13f257b5766852eb25fc209919ee0bdb63e0572d659ddcb53c143de8a45d50d3528ca3df28ac29cfb09751bddcb3a92d82438aaeeb6fdfaaaae1f19f6972895fa4f897e799b5090d40aaa3fc4e468385fe48e8dfa4b0775ab197c03a9e45b9b2bde7c15080a13279be1393728fc29faa70bc1fa3a4f239c5bf63b4f9af72778a0b71a5a46368c9726cea7e54c79dad9a258089bdab2285987a7cfd92460301bc24ee94ce709f0eed053864313ca0a3e08251a43dd1caf5011354c820d67200af4f117f6aeeba47221998750733c159f78dc1bfee4d672288639498616c6885ce88f3ebf7fe582fd11946af0bd48ec11ba57380ac83552155aa7e33229b389727447d2420b2974814ebebc05042bd6ce85e6bfa508e88bfeadb5462ef06729e4dd54a97ea3e21c47d422ad0ed24c6393f309762c87a788a61b72315a90d49453500192022b2e5f8b55328cc2ede845e78890116bf0861f782334e62b84372e6fe46b6e6eca2c108a276457ca3da5a80141a3ab4894e8a7dda45641ceb2e0042c77a1ff1a2c964f07333d6485f1688d4236057b709e0fb18759290119d3b72498aa1a3f1cacb0d36a492ab464ea9500e41a8f8365c1730032425693e0237726291cd1de5b4395a9940dbd00edb1190029bd321d59dc6bd6767248770a0e6725ff80f22d603017b08a3c93b857dc461a4af88d4fafd56587050b79bd99bac725c79bb1a42a22f8c823f57d85d69b427956e51c75dacc02b32871c457850d772061c90ce44bbf5307785baf11f66ac26b268ae31e94d757796e24b2cbff7f472419b949d826aa59d96cf2117c0ae18ec53e63c05ba839e9d0f55bcb0d30deb7238582264214acc2b8e8a13224f72910bac3a26f47c91ff6a1ac82c3fe332222947fc14a8c0267a2c4c579943c1954b097af493f0e52d7c8893547d5cffc32a6e5a6b457a068dc65597ea4d5fe43afbe001488423d0a523b22b7f7596e5647c720fb36f0841c7db87f04dbfcd1101bbef8c67aa7a2bfa39a036d848e9877db52353391dfb109a1bfa9a616349d1bef4c65aab9691d3dd6a9106008a76d1f129726a5e2eebe176686650ee28a88ce0b79dd7e92ff99915bc3a8146ad9d5193167230cd001838763b98827c1563d4869a22814d00328bb59d73ac05efba25ce9e4edb34553de85e9f012740827e94242829483f63e9c7fcda0a0d3b6983e37de0722f8dbab90a84fd7fa03ecc545021a1f27e90acc2bd5214ceda012e3c509525729fc1354ac574db7e3b681e270236e3d84e4372626f7421ecabd56ee9fe417f724075b57d12749239d7948041d9ef7a016d1c2ef89b7282166212206fd333812624d88a191d36ea85545d86272a028ce4c56a93efe943d467e08e36af66fddc7070ce0614e0c9e047926a6115faec48fe513761c9c7fee12746187d2b8010622c2f83ae6a52402ce96a346aa5d1a79ab68ed3dd916910fc42e3e373e796f5bc7230c975abc8413f09251b6577e512fd88b9e611bc5d5258884325ed73ac4f9439a45d05b4bd57b42ff2e7bc753c3a808c19bd225e3c57c908dde064b01e2e6d7216823c770feda4f18706b1951d2d19618a4064837e6d771334e91cb7068add4faf1a1e992f84fd231291a72688f804d4773226c14ff1cfd464837a5d8bdbaf7278f49dde3876ab21c49d62a831be567c9bacf8db20700d6cdf0eedeb7d7485723eb5e2ed96deed54c407fad8c7f22164eb8ae78fb6449e08d878585a91b979720f18c6f3159b7f27db712f514084c4b6aa626b2258361196abacb426f546347210f4da81d0620fe16a090f5ce9ad89eaea3d41ccff65801b5899ea331f6e473e83ba65b557254f9b77617393ba962d54754393379f1d24cb3a4d5970f157f672925b52b7373cc06ea3a67c5dcc44f466ac9af7d7d154a8118029d6ff00d1d9370472a79fe7b21c157e13e6da2c7abd39f9c601637999ab743bf6f97b9dc0dd216377082ef2f2323cb51efe4110a2bb73a0e763d72ff59bd58d3cfd4f62a99672a0ecfb2416485c9a472d605ad01fd6acccd8e02b8c984b636527d574fc495639dbed9d5b21310c3a87f555b204f9749bf1cab47be65dfef9d1ae0c3d8e164e72ca9bae520dcd44d9ec97eab787ac8e853f286d4829c58c28445957866807c627f7fa84f623dc27e2ef0816ce591f7102d66b41a9652a0e91804f537861774572da1f8a4a43da636d6f5f1113e21f4c6a35c151c17193f4809958d94e44fdcd72d5d582036f609f092be36f6452e3f358b3223d586624e63f188b46a841cf1e4a3906400ac6bd0c87184c8524c14eae101613d736621751a85dcb49b6e90cdc720161127a5a53b3269a601877a1f519e7b247cb92b0ac9ad87b0452d29df28872ca1ed7e1e75aee8b5b8eb6ab82f2cffbd6e2321640baafc1928b13194bcc457299b7ce71c75a98e32d6c9978c2155875ed53eb7fb55637ef650b3c7c59e2cf59f3cc7a2a13f215cb5b3d93b86f7a5e281c29251f3b78050ff0c744899be58072c3f9b9a7a2a37731249c401cb50da8440f5fd91702f8b1215be22a5c5e87da09f4521b449f215dd82b2512b7d04b9f915f36d564314ba639979e00d584ffc46d22809fa0dd1ac0a84061d37af13c31755732103f2718245af95ebdcf0a38c4729b5742f8765e9df2902bcc76ab19abc521788efacc6fb09c4a97c62bbd382372e5fd202b90bb9bbc468acd61b061417ee89f06b72643d4b51dc50159b4e0101c9d97329fbca90849a2d1efff8b96089443cda7e578b7e11c2029af7eec13d872b14da239ce3610456c626737c5b51f5fd44035473e3fb2a5f61aa457ddc406725e9361f32992cacb4f94298335313c5d47bba2d3936308b8b95238b4f3c9eb5dc6fc8185e50c77eef0e112cc2e925ff445dc6926eb1f7f84a2324cf7f1fc0f001c6281efe61d95fa4063b0aefc597e52a6ed12b3b8359b5ecbd39e6cdb3e6d430aa1de4382303e64a37cb12ea14dbea06180661feb8d00331fd6d2d3083186725515a4c87f47ca9bbf81e78429c419269be8a0812b19d11fd91753cceb9dbf725bdee925772426470a4f5ee914b15e4cef74c8299e056d36e0c3b338f039f85e3360bd6669c52544acc0907795a5f4267a1aefa4426caab83db6350a5461d45a9acdbc8f53d5e87aee624268329bb46f3bb38ae81c1b8cd7d676a77cd49ba772c5f772096910ffe769d12bf457cbd2599cce2b01955475f44fdc3387106ce1727eb42486d8d3db96107251ba167a8910b437734dc69be74b09aa62233ce1a92a29dfc33f3cfd647086e55aa1ccdb7f699bcecc6b9d5d459151e8c2950d7e1f725119beff503f45573037917091e6bcf36b1024758582ea4f08de21bf39a4da727648c3826ca0272ef54b654ba2e67b48ee0a04ee95f6c5b1e459b9223af3d2191e20dfb13363b05f152e4e1a883348a6854c4cf7b50cb798848d545d56e14572c74660327a0c10088d2f9f7c1ea96ba2f9e0c5df33332cda46d207f6cfe9db1f67c045348f78de9d1c752a039de74ed31a017f13677530b1d23a55caa0b7cb72089b58676169fca378a963ca246b07ceb518600facf0fc606996af8cef68990043968c55762cb1b2ff5714a5707f90843f6517810b28e9fc2135e02b4f90687268a10deeb28ffa9ad22aae627af1889ab39f889b9d32a8b15f3eda3e6f646172b79d298d38ff2839b360f10173c5c2051e7007a7b9d42340b35067d641240072d8ca986d9cc88fa418f500a8b01304f5943f2ec51a879a52640c82c7c9bdba726dda828c684a26112b6135a57df1f428fa096a6aac3e3c8e2de7cafefbde1b72ba6ab12694a92b0bf34d529a925496339128cbd65f17d760abcfdca731a2402d15179d9bce151eb06485215da89647d43442ae7ad25607927b440780dbbc357292cc4a7d0def9ea1b01e22e4f84ab36b89c2e71b0e9579d41fb38e84c1928272a785dcb1cd5d7a8faf220889ffcfbde05f5e8af4600a3cd322f67016cbfd7c435c624f020f5b6b516f8ae3a4085713811b05f9bf67174fdc2793010879d3b072c42d4703aec787e46725ff5b5f1f7c39e0f870f50799af3f6e34a905452cba55e1c13cd2a30cc17f5f743c095fd940467de87a8f9f61ab438ce78524c39b217297a043088bbaaba78a7b928683de7ac8208a23774d9dae42d7d2cb915d20c87254a659560419b0ffc308ed606765a45002c8840da153eb0e9b4424d44035a572c93c946a749a27bc69ed11078c6a4cec42a8ac50524998cfa657514b13705072f5d9065fe83cf10389f7495e1bf7bbe0ee11ce0ac8d220e5de04d0478e2dc472e1b0fd5ad9a1fe2b3de5985f7e4e5bf5d96a7865bf513d13c7ad9fba6006ee26da7c8ca93e42ba8d5a586190eed695cd5b4b4fd48f758b4b47428eb56caff219dd7deba8b9875a04a6273ece363f9f22f3025ade2fd3e5555da83c660e20587246181dcaf25384d9ea75207bc1188e19e1376d611169a000c719978a7ca0057272c911c94fea215f642e85542026726db155203c4ddaedbbc8c7cca01f5ffa7266e042d3fd85d68b96f430a34bb20faf734229bb643b60e4f131d1ab6ef0ac724bd8e8e6ec0440eae1be5e6c93c9013cd3f9d91123bf1751b5cbc2d08d459a0bec1e82a8a8269b68a683d18345df40cf88cb4b5f4e8f19d669712172fb8da308e2e3d1eabfb28ed2d36fa6bc6b8a39e89042a4d8fbcc55b624753433a9e49f724356fb16588315cca85fa1763ebd920cedf1a310460251fc4bd35b1d8929c972fa7d0a94ef97086754cfe88a6724ec583ddaeccdcba5b8a60e79edbd6f57d3726c0c11c3f0699eeadea9c385901d0efacd5fe4c9ec640002981f18d5b33b857295217e0bda838a9deb0ec044f4ba809138d827a27be16e7763ed2a8924277372583718d81373656a783a334a2fc88dd1ce2d4054ab072fa17141742abdc2db72a582745896073deea519fc634f24674b72a2020995b60d31ba709a4bdbef821fdc7b3563ff2801da71a6234d2778a7890222d24bf76db9d59eed70d5afe09552b648ff98f0ec6a692f1ff9da617dc6119c3dc02511659479c8f4a2a54fb9f9722eca7d4e6ec9ce9d4dfb1d64b29b710a6aa3d1eb390361a7fef66fcf60b5b62887a6851550f5118bffec22cd545a0e14210f5ab59852963921bb3e6459585c7298444a7bd2c4bb6695b3de3435f7c1991d9c51e1c77194dc45f95ccbdd68ae728984d699313bb8984a64634337a97d70dddb0b8e05761d35f751de7444f9337203d871993128f03b9d7e1f3686c92269a38af4030e5990ef198602ba9b791a727762d1471729b087a99b8828ded342f56520c1ce983c7fe12b22a514a774873330298571fcd7f6bb384518de099aa8a6fdc6878276dca39c72cc9a938e253772d727fb1b81da1761190fbe6c7ab17c81e650038f21fc31339a5f49c4fa9a2f503b1d90f36d9f96d4f6f38f35ecf555ee50a34aa8b8dc21848c90505354e7500c083c7d3224897b5c48fb85ffc364dc13c2e7458a448688951dd1d5efed28ed57053486abec96bb3d65dd2e84d110f579f1b920a0ba7a6195631342360f27b10bab88e5006d0b955ee5417fe864bf3e29f335a3be411378ad198cae7a91b8e172f52748d0834b5f368fca7d7d9773a078ada368d0d4deb592a3c2216893456772b0396f269feff1a6767fafe204ef6389cb874782e48cb2f7ce5e13af2ac4b90c0631cf59ea75575f82e6f81780a4c8d8f4e65dbabb3c54b09aa3a442f4b59172fb486933a77689b57705b90eec70e90280a7d79c73d8e1300b705344c020454b0b98d45b07e63c1cd1a95e0b0b0c9d052d759d3ac94a02e55123b98f3d865f4c1db977af44df2e32f0739cdc2752ad6fc71130d5498d489b6d98d53d4e28c42c43bbe548e3f545fbf69ccaf1beaacc226a82bd34422a9981e78d6eb32704374eafe16bb27743959cd710102869a59f1011b7699629b82495de993a6cc41dd8371cff449eb9c8342bd0aef812e91b3f64448244727bb35eac12b208d4e058be0bb047397f09c6db26381834fca87bc75a4cfe41d6ac823c1bb1ab3b7262058e72e0071c3cd921d951d02e910ec3889b3c16b3771c0257c3eaa134059dd5f3b1311c34881cd31ea599dc2236ce2718f9fbb943cf3f305c7570eda0916cbd51f7099ab972d606a837ed274e3bef82a7e2deba41f0202dae69538a73ab198bfddc0b2d2e5cd21f736ffff94e1fbb79723f386d42a046df0e182dceaa66dd3dea47410ddeb60dbe9c3e68bea6e51f68f178dc275367f96de0a49c4a97d1fd7c240d72facb679314b6129e7d8717f83ec151543c3dbcb42526b9c552c6bbc8709c57685cb02cf4a2ea4bb50e8d5deef53abfb37ffa75107017a9c0a1a247d5ab243c4d43401ec59bab93afa5ebe7f58e247e720f382be4d29488b7f421745d07c3156ba85dc96754784889ab17ae8556f58f755ca2bdd0732e6fd42aedd368ecf63872e1bb7b04c2f1ebb89d539b98bbe6f583116473426ff8f57b749934ab2b8ebe726fe52caefe168ff2d057d7a140410166d37415e6e0e4d6464d4592b4eba1ab7247c0d09982ca02bba132ef99db97e88b99f414f13621711c888c96df526a0c68002f76dff21652b9e0120961e13021741c01381f9f937f1105d26b6e3b77a2573f20ca54f6313c97db52b6a817b55d2b5e47d54718c1149ddc117087c6c4d872efd56b56991925e6e73bf1ce3dd7228419c2f35ae944eb48b6bff0b66eb46872b120a214823c63789cfdf18b6c521df774e875299e709f642c8bb089bb9e4672befb17d404c0cd657c36ae14daa1ee8e4455d9d9ab2228cbca33d2930c3de30d5b3608e55b881a898288e5a59772654ff893499f5c55e331ae12b882fdaac872d50c8b0bc90770ace1e1bd4f7dc6590ff37d80091dfced1aea9dc7854c9b40367e2201c6c6a6031dc512a39cb767cbf7579639842aaf34ef10c32cafba4c32722d7bcc7d473074c71993ac218fa81ee94258ba7c60b545a8d3bcbbb9e74ca8242eb46f288a1bf6d6b95e29cafea78f34a671e3996cbe79eacbe26891dca80172606fc22e6bead675132a345dce2d9abbe76d2e70ebe9230c532538123ec3ea26b292888c581bda62b8fea4c3940b80cba2b9a2117d7f9d0492023599170f0172e47a37c00005b3e8772f41fddccfaf6bfb85e1188fcbd8ad267e0f0255929c5bba4f5b885eb9996f9df2718ac846a8a2616b42ca1030906804c1eb1a4e0e1b1c4bec5c4609170057973298f51c4b3b43d3eb20a2f23c7a2b3508d36e09aa5d7205275556ed016d6b3c381e1f9a00fdfe19dfeb2d82b610de2de804896b581a728aa3674f74d6b3bacf75a32feab193372ea0479e9dbf34b48108d6cce6c65f72a2e7e833115e9d28c6f9bca3e7d84470b7b25d55c9878664679fc8b5ce4ac37248f6bcb60856c8f59261a0e51f930d770bdafb98daeed8b345c64170f98d9d7219ed7d3a2527deef6b9691021fc9681e0423a7e7a5dc89658114d41dea2f4772f9a2a37f0f8452380d5c0e629f35d39018029067e33753617874bdb864050d35ef4c50a225bef67a96e2e597d7f41e0a4c8fdfaae380d287ae95cc911fdc7972c703351b5a131fa37ae3673dd26e4c22da23dede8568faf546546c7dc1c4c5716ac94aee8d5311733d086e398e8595bf7ecbf95a757726a2a2a9540abb9ccc726bc59e0760099478c6ff2120ffb73a75daf1f9bd7d4b76d0f312c0dd46a7b11147974ed305772628b6953b2daf93356a72c6fcadb8abf48a0140b177d4bf102da20a041b5f95defd7c46ad7e3c6168b46aced8974d615f5f739235cf240819726dedf720a869870123ec71080c5b2bbfc75211a7b155b3eb9f34a181fbfdfa4b880480345d6e905a7c728b103f6be05ee50ddb01772518758389f94f70df8b7265ccd8441aadaa4a5f3c104f7c6a1fac533e8ed1415b80c6c66bdaace11f9a725d17d82ae10e2b30cfa91095a85660242e44e6fdcca5af47f3e4e06906ca8f30e81bf0ec37a454e55acddca34e6dbfdc334608e6ae4459140d26525dd051763bdc065a6608baec8cf244ff9162f40d598460c1ca2c0eae49457c4fc6da1c1b720a9abb016ff625f570305bdbfd0fd14fa703d0e7aa60288012018bcb78ef6872b4a2686c0dfe11d854a44d10728696ba45d33f6d383328d63a86d8822470827248a2a3c927ab762195f76ed2e7f48ea5f8e19601e039cbda7ccfb7e2aedee12098635e6731e1a4f48c6ec0a0ccc60728a9b8226b5e5dd138215076fdcb6c8a003e1086888fe903607d22af1dfe204b325be1c920842fb8c703ce58a21f116372d0ca2d2e9a5570832bd7e24358c4ee76bae425ef3a94b180838e445d53d9ef2555dd1fffd1eef37ebcc8b630b5f0944b3e0d19c077b3e17d88276ad2ffc4db72d06449e05837e65ccdb1b1b9c264256b182ff40b7b8b055973adff2dcf8dc872a06c713b4108dd16bfc68003a599b50f3eb949a6f0d1fc0f1bc06ce66a484272741dc284ff857f8241893ecbbb1bb7c771211ccc7cae9149575b6c3323509c72a9c95933cc694ca1c7a7f225ee0643794cbc25c6e63d9daae7e2948d523af2722942f1b194070154f975473775d9fb4c543ed547944e1fb396e4d62b8d1c2972483897aede68a8db6b56adea89b730a10a82936a0395ca2e47ffa4c85e14660b2409219f9a17b5d53baa7fe733159b1cd95113fe7090d52961521dfcba68df72031df191959a4ab41a6b51b237f37b2836db6a7980dba2bfe8fd5b60f54b1e72b51a011c75c375f2b99c9f2947a91613d8bcdbf4a23de5d0f5896124c4ef7e35a046db5bd91300755120d6f5b482ba669fc0ab27b5f432aef8d0c0c71e986a729ea0146d2bd29074342e71cac841d899175981e50e3ffe7d7c9134c93a9e01261067c54afbf5e7db5cefb865a67bad1377082c6993713fb5878dc0f0c566420e6295651b8ff7a348aa1911ff0d4ecd0269ceefe95f23ff32ba6a1dfd9ebf1a722e5a5cf33329318b246f4978a3dc58802c8dcee6861b91a038f84502e5009f410596279b356d0cff98459509dcf7cb3ccc1e9ff75a772e7929115e0a31438772a7ace703160ba3f03cedced49d6ab381a9ed8764ec502c8e771c8ebcbc5a2972827bd49626e732c97a7e8f05a2b7ac0082a96ad11252262753b89b9874878f72f9f6e66b01799511e470b98a500b394908df737f28f3fd93278f0941be8e6f31d1e5334c845b038d5eba3d88c13b29bc408e38aa16c5682184cdc805e43c8a416937318b7c39e52c57cab67aef6b25790c2c0f78197e7e2098a5c1c1f6627a58be19dbc55a3827f01212fe9d17f17629741905b7ecf931db77daf194dec0f97208fe2b5a9cdacd458cd7a8445d8d99267d6f4adbca2ffd5f0310d1e4db90c06761aac2794f9174b11815ab75735ee4034a6e923eb4573d44ec9eba944096fb2e451b906e4a8c6197b7a181ee3e819ba64ce79071439b0ec42059a01ee561c65a3c6d4f465c7b22d3e0c2ecaf13c74d308964e0874e93d0a9d72be5b63bf017724abcc07a0b37be0f3f693cf16046e001711bdb846d7a6ef7c1d1d3e5dfc560413f9f59894cf2de3774087be2ec30acb8d270f7f1ee9470686a04678b8721bb72a2f69f47962dff913c8e78155d85164d50450498d7e746acf2236888be818708de14b68db0cfb899ae85413f7190d2d152348d833ee3a93370ada899e9cfba1016289d89057d15ff8216a973afbf76cbe815f75c63dc177b2fe6cb9cde6097296f58ed67aa8879bd340c2a88ddbf046bbb7bc225902a68c3a8bb31bcbc718872b61e30e5205c80c66578af782d8d1e18c595ca0e85b8d3bf1bb69fbb47d50a72ac02fb42ba1c710cd174ae1d965e33d15547c2c341bbd0eca71aa863d9caf0722338965026c80b31040d4a1cf3accb98125ef3ab30eb808d1969d7afb1a9cd72400bec3a7fd37e81832b4bea66a219920280eeab09b0be361e0f3d3e17a18b36c73d9462dfe6beb8945ef32d992a89fac6f875698c0989d796229a0cfc0806673b176dc3135fb693639e05f39530d22ba351fb6e8160c1963a3b01e4fd511c72cf5950da5f9191fb99716d4eee74289a7caad31e0ae8629a2e797577f40f4f722cafc853beb7bda7c730828e9611ac42bd4a020ab8bed2404067d2925fa2e63cb4d19cdad00995b2cb42ed9f199a82720adb224762cde2056f219a381e5515721e3c4d3cdaf038df43eb6f3ba6c5294f53b40be4a25697b458adc5f166a5847238ce91ae8ccbbdd726a5cfa36402bc3706d73e3701ff879bbc9d0ec42bfdd81d1967c6437f8d9d0df46e26827184716a920e932a692cabc227b9c6aee5f34711576293435d24db3f209083890fa946fec56c68d5a19cc084e283d72a506fb002f0e9a1afa7e06a1bf2efc6f2de475aab23e179bb01695ed0a680d9ca47a26a72cc848f1471b11cb127384467c50a4b4bfe4f52d4b0f81b1b5472310ffc8697722a281ffbdf852f27a679387eee343fbf786a35c14909317696aa48381f38297273072b64b8128372c426caade68da4e8e25380bac40dec8fe060b426cc15754ace65aefca019a1011f4a21baad0ebbc5bbeac510a4a796f9712c5c2cb14b3972d2b45b54b4ba5f39bcaa803a3a4fc90a4c6afbe77711652af5c833cf3101ee2f61dbfbc887a9da2826ef5595e5ea77fa51f2c9b11cbecbe22b9d6eb11178872bbcd80013df88388d96704209100982d8a12ced14169ff2ae184a42cde76d63724b4a1487b62dce0a3fc079e31bf0feb689b10c53702f6fab7b82857dd8c32172dcce749d07f3722cbc89882a8606ea9b5f37619ada71a0fb5f91e7d8969e96723faa4baa857bda8b9be04b5f135499227476cfc63c4f93573b42b6caf4168b72493f1b4cc37d09c5028ba62e58a40ff0cdff39769508f22486c4976b15b269590724b4a00c4939c61cad9f0da508202a04a587328b52729b737d2bd963967a481738dce6bcef80dcdefc3465083a93e955061fb3ee6d1594ef53fa3a2b620f7201ea513bc46a7d9fe1485638101af6463bd74cce1ea87b1324c2dc5a1dca0c6ed86f4ad0bb2b2ddb95343947c3ec5eac97886995bb4db71f694f22fab95f253948dc1ea2fc2e77c0950b5543009022a901033a48b32f0c3520dc4dd6e41a117253018fe1f464e8e668cdc3871a8e9601e28fb7c089121aeac57766d945fdda0e9c60dbf078b76ea39635f0b5e648b61de86068d18f87a87ec13cbc4758b2e072bb35fc44aa196627ecb22f8144299c2675aff58da7eef00beecb8e2b0f404c6d6f5757ea93f63740010c34eded08222f690864f1adc5ee31c016abb1b7732d72df9658357f0314ca4b7935a66e2b55869223a8a4355f5c018e371b90c8fe7f70384bd26bf8985a13546c8519e1fd1b30456e9a5379cbbf5e96917ea6121d4d3d4a15da8d34a12cbcbea392ed41368954fab86ef4c0d143114a5c1f845ae77b727a68fcefae07999454bc46c841efaa4eb7f62b08fdc0182e46ffe3705f17cf72844ef8bc282a38f872e373e1c3a811da06f47618bc04bae48da7d38c4874bb72e8a40b9e6da59bb57927a20bca54819f55334dff0d0e1b0986668c65b5ad0b0172a301c2b7c331648ff466fb85fc061f39c10857ebb6a3f8da9bb84213f11972bb7fe58cc53c124258508f3ed72cd8bab3f68c3ca59b7f0389d0ed4c2a8fa7722c15d2f9fd5423a214aa3cca6eaba0b2acbe520cb4b3812a62c4f18406d96572683ff0fb2b2e8827b1fc17f407a1a25672194fa0a38f5d5b8f746ebe66b61615a2e160a9f0b4906e542dc9f0b2e578e42fbac241bef646edd806e024b24da12b5b2060aa0a6b7fac08ca1dbef94dd2d5559e48b62384afcdd781871cb76243721372041b667c81793e03b40f255d8aa97a513635e2e166662503ef96a3e954372982eb8b3e52f12dca142c489de1fe9c24f644a61ae34c60e5235aa3f89f7e72baaa6bf930d5bea357b44ae4093e2ab32fecafc69bc0979c5d16b102ec6ddf7242bcb90e97987a9f4f9a7056d103edc1c209f2307a40fcf8648edcdca547b10266ca50ba1f82fc1545711dc8147016437d752b26b9e1206c5ae917bbbcbfd87270dff14b1853658deedf3e8ec6cef278095a827be2cdb08499700d6c478183725069b5f77b95f3fe9f33c90ced3ddaf2d3bc7f0349134dafe66cb5b2be37d872de2c49ddcde37a36024577aa2c7b70a959e7ca09da307b7ab5bf70a862e106724c58deafe0ed4eb0200875d90d295b58c0467c11a45c8e6cbc107daa93f63d72bc18eedcdc75632295f77d60f96ed6b25f643aa3a1bc674b63b36c1f98a796722799e514d1b8a13f290bfcc3b2b5ab24405d2958127682da0923c0c4f04384729f460f03b881ef264dcedaa38d1ae2cd24a227dd0e5b796de17c54e33f7d0f358d95bd8a10fdc586bc6b91089d654da700eb4998f14e1efeaebdf647a44c88465b358b863ed9c414f1b7ccb3bbd6f02f0c5abe89be3d3b3ca1808c44707fbf7279baa00fe2dc574b268b675e23040e74356a6377297158266622e1e71918ac72f6499226018b2972d6a78bd3e57364bc2c6184d0566ce2dddfb57fafc2405b722c4e19db2b28a36f871642054cbd63169d4843b782aa2c9a62c0c6ad28629d721456cc38939f5185f4aae952d77c4cf5bda9ead02a77c2af1b9bdd22d6ab09071ef54d864d6a5a15e348e1df409393573f0203947626bec5efd460972aed796fbf5a6d7a1fde1377842c2a43e9462a3d27643eaa23267345e883f528fbc1d972abd653adcf95f0826d53b477b487073df9819c4882ba537e5b224d7ce903eb0b8809e527aac787769857bd05e48cd2ca4423249fd48aec89733cb551846f3b7265d69418875ee8060286458e0cec894dd925f56e85022f365b1bd85f2d045d72f82a9577f54e3458fb60ced7eb99186a5fd70258de05471b86cddac6265ab57297c0dd420325f53669fd4d2a890e2f57f5f8f26b905d863858bb0aee7ffdbf72d295ce700bef3639a562b9c842af4090f888d787caa8ab0d38e7a15e94465a6f2d31aaff1c3e95777851fb6b5f8762d895c2427c7a5891a8516e1eae1c5917720c02a1e9ebde6c2a03a3009c02db2c890de663df270dea61790f92898ec89672f8726847d43b2c9deae88431a3962a0e45c15e7d94950baeef11ee8683b69572a620df6543a488685d2ed1959c1d94d3ddf6c79b31540a7de7d161cea99b5172261d4bdaf212bb56101f0ba60db378c4f59bf883cfcb0d50df73a234a3371772efd31f100ed2f31497523b6c5854d73e7d4f4e2aebe8585ecdd5c77b3a1d3372458874a5ac22a74753e9181f0846d05019b21a604bf38d000a6322487c00f0721f595ea37c2a9af651b564ff47a85beeb2e6446a43f582ce600c53204758d6560d32abadb388f7a178a13cdb9c9bf882662b0020f09383e9834b412a1555c525cca01c33824bd0e37a455cd2b0a51f40f044555fcef9349c1c348c02f1c5d37238b9dd23a9cd5fbda0b9e298eedca255a54d97e6cd463d2faf7c1c035b377702754e0504adc7c3ef228b4b0f5f88c2449137ff408e9bd6b89ac25cf7a07137727220df0b2eaebdc58b20c83233f97517e955a59cc283e7608d988d2f95c4b3729ebe6f19dcf4dbc93a7649e11b37eb23230372cae2129166731ef647af739469fc1fe1a86430640faa95836d853a30aab5773c2e26bca8d2787940ab125bf87256c2640eb4196125d9a4ac217f449ca67bb70937f089d74ceb560a9ab4adc5723db474a7e534f1efc232cb77051ea9da8c70a6722c70fc348336cf2b5201e34fc8b9b2d297d06c60b6953c5e255f6c0301605b1165988d22161ddf500f142a723fcae622dc28da981d6b01aaa2de736b62c72a2a8812caeec444964278ea416c59c720e7eaa03e1ea73281918bd6b4cd843adb19b8bce34d1697d8d881b20c10bbd7c68a0771da31164b8a614c1b2f27c02099c8a5f39e28dac4f619cadc4d724df36793a913d058d37382eb6228a113704bcc1d25cd09721c5d3ab9b26cb94a79bfe43d867a7d21cf122297999c0b1e75e7c8ba3109b5e5c8b49dbb69c03a72d0846f48c01453c0d52c42a939f778fb163c721db700f34a25cf8fe46e65bf7209399e9ac5e436982b056d8b000028e9687988f0907fde292dff6c86872e532a02fc47144249f057de5dfdd35859553f13a90789b60c3448799f95efe982f76ad007889f3b667593f286e3c292c740e7f7e703e64ba0501028eb099ed9440972cfc4d426f1bded17b63c4039fe1e2b2cd04e0250b483cffd7d0ba20c4573977264370967b753b544d7c6a21625121ae0acd9c8e2ad6c84038bfe0d20ab836b3c63ef73a488531540c7e8e6e8e1afc39c4e35f936abdf492ae070b56888f695728120dd8870ecd1a9588e09f7ecbf8d23e491cfd7e321d9fe93eaa7a38d020237833681359b33cb234b98143a86257e31771edf916722872927d43e4b8e6e221199941d93d7b17bddd228a232920b0f96f3b637f003d15f663484325fa7c5cb72646ac9e020fe0f0e7b86160015989bb04f5c49efbaa1cf25497d945b88196b53563320727b4ef2907f6d4c9c3a10c9fac7f3161a11d18285b2dcd6c731f2da276c624051437c9dfef8392226de3da45939c618f36efc998e44b71cd32342fb6b146e1c10ace691158c205e63977814252f0f0566b12faca600e8b78583b4ec720c915124da0ab507523261b03177f69753616dad0da3888e66bb1d7ce337fc0bf5c32b8d4c80f64b153d086feeb01abc8741749ed1ff03907df42f0bd5ba4c72b35008ece0ad896bdacfbd4a99525903a39bd20371dc099934a06729d95e2172d84d265c44bb57047cc9f9c185040f61c875f90bf39143aedf3a6f930b1a5329e8b2e10c90decdc55d4c10361da996cfa72bebc5f8d9baf4c5951fcb419066722eeabbf8aa0c6d1aa1eec261fd2f9c54a213685b2a896120aa0620184ca7ee72b257c7c8d9feb89b12d97ab849ea9bcf9619a6055e9bc6ebb33f42036de29e4d5caf2af2e41cad59db452791f6647f16f9e2f21398834cc5be33b92ecb60110fa85af139c3ef6a6a640b4863bd0452129a2dc271a8e017cf4a879ad902186e72327542ea05ba0efedcb8df917f78cea06517583780eac24cf8b9a978b2e5b772b0cf87d0cf811bc7a9e224ad10b04ce037589e41c31ab6c9454c3a292d9a0a72a43de3c961251bf3b89243526a387412e4639de9fdba57e4355c3944b01c7a726c603ba035fb19d7e5cfa6e36b88499908976c549e923a11367018cf7642fd224dba7d786e7d6eae79ddf55f0d2ad5fe4e89e6f188dfe7116b311f278c146072d1294f5cb3a42758db5d7dc1f2a1621db7d445889ec735270ede1b72b281fe723ef10c27bbeaee8da89e94406704cc65d831548af9776b7430ecd0c99101530906f4f2d1ad14f20943cf24e846493a2953c708cc6a25f37a79f8d9b17d945c72c58c399c3c92c923db06ed652b5643ebba6e1309246b12c5413d71e3f0721c72f5b9fa6e6fadb8f8685b46a3ac54818f3934b56063b367203a637b27864e2e72edbd70deb8aa2bc83a83f14e9dabf42a46463337ddfd86bccb75b3e564989772f0bdc3ed48f6789487948db82eb9619bc284427750324196826c33f68440ed724cc2746a765d73b9ff5b14cb4c42136d1d08776b87191ba899693f0cb5dba6720fa1c9d652c06fb58744ad746e026e8f124f88553084bf641269b67cf1ddf772a35d0331fde1707be395563c701e4ef6dc491b7f94732f9007b32f21cdba83721a2ad19b19cd3d279226d5a47d2184a6ba4fd30caa7a70eaf2b0efb654b4e756fe200608920900e275e42be77670c86b1d8b6dedaaf096eeaef749aa5aabd372964410809247619868a164df5b0d5f422a83e09dac1e3b608431ff82552d8c72c547a1ed2a6a18ed194b389011f3b9e0c8c853d950906d3e2f1e0ec998064c72b31bc2dd0ac7628bddd8c089c15ed6ce9424b229a069bc971ec3a9392aaa6b0633efeccd959ae322a96d28758b052ebca9080e5cae0226e793cad04e2ee11b725c65acff3065d4d8ef4f8a5515ba55aac5e07a96c55d1fe03a39ad6c77919772be1a74947af0e5e1dba00f4dac48fde216e5fbdd78db5e37d11107a64b418a72ed7573a11777943e259cc6c5d42d98c5948e8ce3bbe2364a5e9542cc03a2c1688caf192803d2c73955369b97f5b53260d9866761d287692a6c51f8c565e10872183748b0679b6042f92f6b0b255c71bf9ecc7bbff8ebaa43b3990654330d887252ba63585457a849a785dfd4ddfcd1916f9eba560bdaaaf7ecc117fc83b9137223c54c9026371477bcc1d766f5ad2740d1c57ffcd4204bac6045e75cabd66d50c06a483f1a27d688110ff84ae5936dafe0668cd8e3b38d01432b3fde96d8c73af22486ea364e409a24bce173c4b82945bf9bbc81d21fe91df1154b4c1379720fb75bc4c28a8df77f6bc846809e5c192945fb75de0942d470f5a6b1bd7298b304d8f93f740c9a7b04dd8c59ec7217f24ea6fd4cdda10896dd6709f5d3dea1863657bf06b17ce3776e314a98f2bb1b09b0e2d98b5ca3b6bbc5e39593aad6ea53722e0f926c0c5b22e05d80dff9b78670f923b4a1a70329fdfbdb1542f954d42472af9075d3ca64f274e9c822f217b4a668aea0f44ecde717a1c292866024544a7292cec5323f7853ec85f0c5b045c2301871cd60831365572d0d6a66586494a772d8a8ecc6240dd0343613dcc567dcc6ff21f5e23771086d56e1e2315163c89b5dbad41ee7746e927ad690fc5851344d4b5c9184a7d55c2e81a40b06a2ed9227721f66d6dc3d79f2e0b3b0cb40507a6398fac02c01df5a6420c682d040c85c1a48f26ccf0a29c68d9fcc626d30cddd6442bf39057e49701537297769e302fee772f0a57f5edbf6b3a0aafdbd1f1b5fc952054a31b4b7741b3f0127a92b4e1d977244d0de20983c70d6eeb98367eb7fa5b9c18613622d0ebb65e368c884c7ca7972cc732e4b73ae6b3fdeaa84484a0f60bdd6031ff9383c9ebe8dd2e4c7612fae72987812246bf8a3909b443364fe54f3dd93c1ee72d5c402481a07fc33ba8b2b610b564f44eb88b0dc9cf133b7d03753cb93d5a3eac127929a2c31fd0b32097e689b2a7639022329c0481de197397f0ce4a1cbdc5fbb5a54abeebbe9360e198872c8369a26ac0f50846838425267844591079120ccc8e91dd9503bf3252d5832669a80440469501b639bfcbd8cc53422f8802d69ec67ea072485ef3a91feac800ac4e40ae03ff0a50e7be512d076bb4899e88d2bc9468b2e0a2d66733291d0de72da8189204cd5863cbfcc715c4a72a4a9495706a2a1604b3ccdc4a1c37cb6d472feefe7159237c63fd72ff508f12d0aebefac90712ba32fe24142943f780bd02ae98fe9f76a26dc0bf8495d07482c8628bc03067a74a4d4e5a73ed11b2542a7107a3509464211b50baf331b004b8015a13ca40c9784ca8485b1e8f16d994a6d72d625152663535fd6c965fa304d534a4e1dec5b6a61ed19e7bc564ebe2ee5c9105ea430f733ccf7438539f67df04a65a3abfed69753513ce76ec4e83255cfd914dec0251544b40644e1808e84e9362a0589d950e566893cdfdf03e23b494338723deca758772382546a6435bfa43bb6311d34549032e93ab766040d26ef534772ab9c187d0c516db26615f489d64791bbe73f3bf1047b39e53f0838fe4e6d0b0446fe46aa528abf7bb96b49cff13a1620dce0bd41c4667e9d1eaa5f7a3aa22b72fe5e34ec0aeffc7ff496e73b423ca585749ab8d763291f976b5b2b4308604002947b2c8e3d7a4dca3ecf998495da70794e1a0a1142537551dbfa17ed53619c72f7eba740c399635209b22d0632748e78881e8dd4c5f6b4e3b0a28262c9465772003dd79cc4feaebf8c543280ea33c2409110582bbcb26eb04d2b7e683c52394a4852a4df3b9579e49671f52d0bcd2538dc7e8121c67c65d97c6e00f605a2ad72e89777873403330b5a8542e649fa671aac3ec49f982c3fd7a74c4bae5fc35f72c3ce3025abe4784fa9d149ef69907dd29fbe84e78fc800627c70256e293f392e50cc3bcbfe032b59abc80b9784d04f2e32de99d518107e8ea50a643d2cf2005f5797e84fbda3d7813d9c67c12f7c5f9aba6f25e8880c6311b29ba8bb89628b721e346105c8d1fb6fe22322e0a0e4108b5015c8523842bd7879eace0b6f162a1bca409954cce664d8b2ffa4775bdd3534f163df309a7b37a6ec4518f3045e7e2a1c788bf36efb9d035c1cb18fcbbbf11e09ae4ea7fda158a15b24850764bbd109c1a286f5b5fc9595755543a3a0da0ed4b68e20dfb3453872612802e22e552b7233cf944b52e3d8598ddee7f186077a88dbbc16e51745e5b2a8f097e9a3b25c7273437704d11e02ddbb630b8d4f68af2be06f8dab9a7f128af4d49e683dd5450613b1854cfd734884bb71d409ac86f65e74a87f5d8f20576e4f5fbb47158fcb432b0b2010002780da0e6def3b42e823076518850067cc5e85649a02b5d10b1072ed7698ebbed78bc7e111339611c6466bffdb2baca0116a4d4458f6179879e1727287881c1c15c3331dd71971f438c11519f3a86c64fffc9df46b81218c69da3b9abe3e8fc47e9ea8b5fd9ef870ed35d601658fc6c3cffd8523d5e2dee46a404b79b6a9d5e0bc01e7d20e1bc46405b2b80a9d1e71e41e8af5708e28f3efa6e872ae8b852736d9b32dfe1e974065745b9f83e2b391179963c1104ba236c1a17a2eba76d5c483b40a0a4bb431da009cc5d3de190fc9d5562cdab651abca73d26c70eaf1649db334d0e55d7e5f003f316485ebfdaffeb1cc99ebc024a7c3bf9c8c29cacbf6ba03e76976747fb91b208b9bdb2e8b4d9a17d7837f99e1780efdb1444c2b56a5d3a7beea3e6d04919a7a643ffc134e627b2cba7aa66584a90929f73b7262fcea90379d596a5c104929f03770613719497dd16e99d28f8f57c0d6a33d7299455d5985729393b81120cd598b7bfb4b264de41f40b923101260241fc65072cbadf1245c029c22192fade19fe8c40d610ed17a74f440cc502357a1f0be9b722e8a4b997832be1aa7ef0fe2e5277d39c5928e3e9f10e90fedd2b2be2757530ad1784623d160139e8d98822124c9abad021a42fcd3ac66cf130313b3c0446c0a0296271a42588a0200320771ddfe36827109fd7ac4d2b6ae869d1ed81a246849e0772b0bbb67595ca31df9f56ac94d1ba10e51bc35547bd0adfa643ccb2fe52efdd22a3b4b5eef39f1b4404ce7e6ef66b195514d93ed59eab103521416c4237215238acfe6a0becf99f5ed135a5916dc8618a0fb460db8718373d141dc56d663bd6715042eaf8b683d0754f797a6ea90897be18c827d3bf8fdf1833d739cce72cf2d13a4798987e8744e92775eef6753e0b30bd3690c2f030f5647e18063e5722c3f350c719843efadea45bfbc582b93ca6dbe8cf30624895abb2262dc58310960a3e64659edbc9f7fb023a46ef4140ae0fb491cb2e5e75ec541086a93672a6b7c420e7a50dcbc42559461478c1025944a05186d3913e510eefa2d9f99dd9c72019ddf6649e8f26da0cfb50652a1fbf35aea045bd5a4c114c4e1b96f0cebab67875308a249d79c9410998cdf327b24de585111d29507dece30fe84dbbe464e727cb364b5ca2b692cc5523068bfeac12c7ce6c9ef6899d03a61deba1a5f4162500fcf5f70d8c62398a4357b4999cc4136758459fb1c3fc886803926a082c05a7246b15716cf9558674cb4894088204e4379018251701c15a34489b609d0c09b72c460eb43a862c0729883d427c0de51f03b0880224d33b67ec39e0b0939a9de723907eb69380336055122d47d82b366938bf99a29ba15f88b7d624597c8dd54725aff3ac6674f98e2d7271b50f24b656f978f8236de3cfba6fd01f7fb85b5eb1c445afdc7d83d95f8d182ec377c439d6f4d268b1f63a0d9047edcbe9801bb3f7266c75d9688fad175ffc3dd888e92fcc4fe5f27428cffa43e7a002965d68bf672b92798c33c60b7333ac379014c6adbdbc3f01dcad2f335de13ed61917920437295ee3ad5a6fbaa23b4556e0530b0d696980d752e98d4e1660377e07ae76dc372a2845f8cdae92a8331a48ff30b1f9ff396cf742c72bbb2642632f9c440340351cfebf43e16b661fdd5b3225d45d5682c2386fde5212f12c2227323e51f761d722b7266d985835d34e4e4ddc0036cb048228321b1219885decfd06115ad75eb377c355189cd94f4fe80f1fc8e87d30058c37d4881e2c216d60a45ef62e9cc40599d0789310bcaa84d4f40000b576181c47c51dd83de3978ce641f9074c85dea2c54cf2923c1346325f82cb01d20f0280771f0506a62a0e23c1fab0beccd7c2b728b36ddbfe9624414e7692fcf04c13121c2f707bcb50f3175606231101d376772e5fb034f597956f93887cc8ecd6e90463a9460dca41ff51d93b6b8866e9494725e6383c7e9ff3aa883ca81011f89d04f1fdae154e96e6a4973ef54fd7c429a72a0e541825810b0de0dcfb6684e6d2d6255a3396ca6fbc1345426e014d1d04449d92c946af67f3e2a2c4a623f9f9c9e6520a0e8959fd7e2c39456c458d88f9372a0b5c57cd8fc7d6a84550204730d17bedeca504b577cafe8804d303eb83eb4728662cc391efc7fa3000409f9807439bb26523b2c4036a62ef4fdb7b1cd0f937272729f76db97b1d3d58220933ed8ebcd5ddef2b018743fb74fa3348568c69b13f5073ce68d101cf0f661d069365fde46e4a8ddcfd3a3760311a6932f3c166c72ad1133e263479e60fb329aa014673eb059fce71f769540da0a7024735209a172974756cb154f4b706753fcb170cf48c92efcc8f073c835ea4353871252343a6bbf432684c42cc6acbf528293ee2a44d3714b97399271539fe5484da8b106e07211ab7a3ef0bbb649e1fcccfc22854fbd7d858ee9230b01375bdc2e527b7abe12e5907f344a04f42d474820d77fb0f40de2f754e7daca375b41d29144ec6ed44179f20ae6cc3bd682b2e9983d670c7e80aed5fd2324fbefe0d7a39937c2328372cfa2c0e8f051d159479d1e1f1a7d3dc9ed5a22e0d7e90d58f4d53b907591773e6c44d8f9567d84453468f36fe9c1cf9d9fea3d53b2c81210f689fa1a1acc6a4f174044887d829a63a618aa9983f7759f306f56343f8ff974736d669114b7337247b1039ad7d3ccb936f8ac1aa9e1d2aff4f4db130d794cb3e03c32c6834ed2405fce4ba7d1016dfefe0dd06f003f6b267ad016f92c46ba8cd88ad18f56df43724ebf50b7c5363489249942a56cc4bc25e39550047531c12468d98de9a70836553a877d6298a978f69f2825fc73541ac539335e6afc1939ce02cd4851f66e49722b64a3c42c53ccb5e75353f56d4334491e78f1db64fb9addf5ce525edc276972e9aa6c16225cc7555429bac345663dc65e60327d0524f590b5a3e26f598df17285cb86d56cf131bc1c5c87714f64a801a1a91a4c843c7af8d883224ef64e347291b7dd345017d413d15cd6d2cb121075d2e79c922e74e447474c65fa95cf174051d7a6898bb64864df3ed6b30235eb40e4289c3c40467240553cda4017b48472d74a75b68c587bcea890d710d24800570690be76788d599b52e5f65c208ea170f8ee6cfccac6c3a8526b82121eebd0f7da3e470109863fa958461deffdae1372a0b62315fd9a90683958694a4ea3392138d598babaf589c75282fa62115721727e8faaeb848a4ea68893befd42f3e0b22a7f1a10469050f4818c4b265cd13572a8b2960cbd1f04fe3060614e097212950bb43f0f9cf6926c4c5ef36e325edf1bcd4313bf68bafbcf51bfdaeb596f23e29eb95b8f4fc2e00096df756aab09291b9437c294f27ddd64f22d575273472336b8b862144760ef9b2e810bc3ef6f45722bc975f2342b380cb7c0ed96839e059064bcf85c8399167bb5236403594f9d726e63caa1b0cb44f8e3853b48df9c94bb0bad5ba50058d9dd35a43ddda363d4726e244223fb4266dc7dc2eed871fcc4763f3fb771821c0a718278b963e365927231decfd52d51600984de1ec376d24fc38db0fd4eb1b1eec1039c8afc69a69e721b6b02c4234b1fe9349e0cdfc03a00e0280955e9ac43b55fe9fd3739ccff217271f483392ab66f8f331eb4bb278c2fb8973671640689fae32a24a939dcc51172b393d2a281a947bea0c0d735741cc96e79c0861a3bfe78fee40f8569b1ea4b0bbd828582c380b98ef9324c5aea31d65c86e8a43fa0b74e1d29e544ccd8e8997076fe82d9cbd47ba7a5116c4b43bbfddffdb743aa4a7a6723ff6164a2aaddb8729326cd572f248f3ce4c0d806912773619028202cfdf6df3235d3841ccac24e4a450b6216b7658d2511d630386fccf372fe5089629c3defc40f73abe2ba61b772ced131b6eee06a3ff819d1e33adb73c528508678285f4f7c9b238964ca9c2172ff4bd2757e4919b7fe12119092695530b9df4751d4c643956dd4966d393404555b6e4af900c482273260dbb788e2ba4e54ac9ef8850b61c79763c5d4e6239a243ad1306142eada43ef223d72e5069ecd0be6d27886c6f4b640fa06d76eee7771c627aa299e022fb0574932c79d001fc6603b96d569df281095ca7cf5198cda72c70a347907cf0f81ada8730aa9c1ab102c620ce1fe8dec35c2e7c0bf1e8199043eb77bc55df56ec0fc4c711ec94d0cbbef66f58584247fa5f081cf262417c50d91fc29030f531bd8006a70134edfd9d1cd496a29150b6091ec6b48332d69f80085bc4bf2a943f1d51acea2e5ec87297d13c7a8af9e849b271c9741182e5b017231070161a85eb3f2b95ddd292003769021141ecb4aebd792cd5518e9b3f21e72497cbf41d4e40a12585018424a025e795f08d82fbc92be1b01c325f9183e652e77e67b32ed38feac518eb6323105d6af604b75a568e0fb1cf5bde1efc0be8d729ea88e52325514d90d40304073daeb749d7f0856d9483b2312ce309ce050573032017dd71ec96b27b59158a855771f4f80b04f66c6ea5f325a142c1b9ccdde27fb8c848864a2de156d3de19931945ec75018e90c6ecd1713b7da834abbe86072522b8603c7b86f9e9c8cb207927d8e395b67e9800d5b978629cea376ec3d1c72ab40cf24bd21f25dea5e554d8b13c9b7d1c1ff8dc94c67aff600af9f6ec0157298bdfba340ee7abf2b0179fef4a4e5b9a59fe4f21d5b997a4be03c40a08fdd71e4ed1fdc228a2fe8dd1a5bdd91e71ba7d0f64582349133998fa9492683c65372c8bea7df819d1bb7ff5ddaca72d1eb908c4928865c9ea268c77f880f17189372eeb7fcd25bc07117c8aaff68d3586ef116c8a0127f91257010c05dad5a17c1722ac2d8679c703d0c395b207d26c37f79e3bdac9487425822b7348b4d2755c61536b641c6ee943632c908082dec9025d2cd7da5cee5f75100859cf4631512c36c08314482726a6cc7b69d24cda9293640855bc766ac8ebdedd8a618afae913f72b25fc1843efba984d03193dd7e40cafbcfab6390f7ed5abe9dd7b75341a32a72ab21c1ee3b66a6465cd5d1e17732ddaaadd0c1ef2d41aeddcf8d0119f314e972e33e40187bedcb30f20d025811daa06d6b3067843e9ebdcc3be0a8bcc74a183dbc338425d8b1b77af16678d569bca598ffdf5673cdd546d907346ef997749c7232511774f2af121d58300c102f858bc2c9b2af0a50137693379b0214320c47728e631483e480dc4ad179e2e76063aed3a30be9d782d2b9d79cccfa31a7a3f4720eba3ca17aae183456c9187fb8a734bfd8a0f5551eaff0d5f9895dbb754028729c99cfcd56050fd5221dc12e2477c9f7f99e3439ad4ccab0dac48f116b2763727d56f34e34858a193dc6cdcea59043046863903a0d0ba870e7507c9a3869b672d6f7f050aa77ab92339213e37714e7b0d53cdefec3e62b22687ca0a55ded4372afe5d0980600a86a9d728285153bd21ca9957d7520bc77f76a227dd535ae4f11cd1331c1cfb8ed0019fab18e2356ae06a76d95a9d98661dbb3b1b089fed1370d226c543c15949f4b4d0875a4009ea82a45c12530ad7a23ab7cc41de617e63a722157912e34ea28fe9e8b3821fe2b8d4a6170acbf46392197d09a125def9003724630a0f728f13a71b036daf98440041d4666730849e88ed5d8aca16ff7972a72dd419773828c21593b9db94bcd0b25f7a9956a485005ff218831530275c9042e2ad105b2fdfbaa9aed4d8ec064f4ea7e8e1399c0f06710e04cda3e7e8ead44726edbd4187b261fc10317770bb4deac7094e59b4dc41054aac7deabe8ba34c072bd13d62c0e5a20e0cd15d2203b023d0bd6c8f70dd463f224c3307287cf41855f62bce29cb0cde628bb7b06e50aa5b6ba8bcbc28b3faecf51aa0be752cf446053cf9d46c900243cee84d4ac794c484bdf439e379c3131199d3f0179097c9124723208c733dc751e022bee6598c3a5f3d9735d7b2e4bd31c5a75a6a93781239b721e3c2aa0c399a6ca0829d1182d44db4d7761ef4e8675f282b7110e90c45c1e72f8f8d4911ad368c92fe86bd780dfc5a424e3bd1df6c9606af3ce1b468d86994a843f1b9f84a539b3e75531e94da35ececa598ef7f969d240ff263b38c5bf0d72de95beae02c5c8a4c6662c7c879dcaae867c603157cd2f243b579d1c1abe7a72228644b77e5854c457d99671bab9b2b9c41c0a9431f63b8cbc966056523c83723594123344cc22407f08fbd2b6a91ce7e6c6e212205992985fca298b1da6786c8ba9e9bbd971f75144eba54e2b5e11a6c5cbd9b1ae3084ae62a8a2e7742f8c2053b885679ef9614e795835dd181bda51a7916daf82c476f5740f130ffea3d57268d26e46f3e212ef7aa17513bf7840aa55c72e0959dc840649380829df59302fea2d5b87bab3ceb47affd0b8e71acf27c55bfccc2245ad36ed122c688ff40d047eb9ae9e37a2af091abf2f4edffc61742164e5f4b9f8a8f481f1846be83df4556700d77c7b0dca4e775187479041a4b0933ecbc9fc62e46b73b27f00960ea51b81b1b414d0b25e77d554ba29e52afd95ce867ece76c653530fd32b2828a2ff72ec8943b4f3d1eb3fb59d0419364b0e37de25d0e7a0b6876f7556dd01b09545726f2dc3682585e115c38ea5466fb3b888aaa4dc46355f8823d5b87ec348eb78725c1f640266ad0535cee892ced2ebd0bd9c4183039564eec7107d93ef48e00072a3a10d558ab9ea792a9acdce313894064b9a0ffcd3da7f34e8820e6b6fb2742981264663852eff5e054546c3457e97e2fb95638b3f92d2d32fbb62adae9a33114b901816dfc7d7664692de37acf8b5243838bf344de0432790ad25fc57e886187feb794d102701f943a335d0a5dcd339f92ccc6aef7fa888a287a161fa61a172fcf0ce4488615555151901066eb5c33b8612893a4f9e4f5e101230511dbfd37284fb52801cfad16c5cd5a1a03846e061d2441808519934af07889dfee3fd184cbc1346a84efb7df4cd9f8edc076b0a9d8e722fdaa9eb0d446510d2d5ceb9ad5383baa4ffedca14f153275532b2a2e9859ec48f66e49fd27943c0db45c764ab727e8c7fbd7edd09c26432939b6c899f41d7137aabe3345b8069759a6b9301a93ec2f2c938934188952101e284aee30962f0b8fc277836bd89a8d223594f1cea729167c28601ad63251b7156b2a8b7e0f1ee8b5fedfaf19c247a9d73febd751e245ab0e17536f1b673494113b1885405288a2e1b9a7a03f2e47bcb1cb8648615724d08fe7479a6d5ea33d797cfe80a5fe070e7bc499fe3064d3e5ef3759f7cf12f0031bb30cfccf4c097818458b1757abd859e1c0c2747798b6ea160abdab45a0d43215b7584fb8750845a69227021437be1a16f467c1ee5385bc0fcbc63fe351de0d1ed691fafe88e7e4f86fc3531f63df103e91a79ac3920b9fae166421ebf72a1f588b7c1504f873a99cb19c603216ce307dd7173a61dac8d019dc372e6501a48e52e03f19afdfa4a47f56ba38f8f5166f3becb0c27014118495c0e29c99c72b7185c4a3f23744dac3f85b3974dd01480a566deb7486a7ee1a2bc719c290b72efcd9475502960099ea305948c5cf84ee89055504b398b5b860ee00cd28e91638fbcba3f6de48210c2a3f1333360a2d497254202785442b4b80d6322a9330b72e8abd216b034bd71132bca62e4ef9b5b858e2d21d9cffed5075194c7089646729593690b82be632aeb3e7727b902bb34fb31721fe87c72957ce9b06f0fd99500a6cfe1a3bce3c8203ed92ec2486de3035f05d9b94ee422c009848db253add972214d8499363c33d311fad0e21d4bd7d3753a0a3c466246d2ab88e07556395e0840856fe7a5454859ae8db1404f4da75f48e9b7df43e937c37683425a0397614140620be6af69c0f3519e49f2517e0bf383693072fce7f698f3e2c4d5d0adba72fc26820769e7d7a26b4c9aae793f5d162ff832db2477c64164fd40486db10b7212959663265c184ac1a59e338383e33bf26f3c8ba45e9a706b39c8c32c92ff38791b43a38f76188fc313d6e4561ab9eabc387c58cc16611c173bf76a5774dd2977564f4acf1fdb38731e4dfdc51f7c9b7ad04602bd34a39a7abd821133f4f172df9dc91f93b44b47633e2d99849122063237de5d665436e58da2f2ce8740e92011f5f29c60d4a94f27fe11f055e20f00b3a3a3e4385e785d5cd403dbcc13f40dbbee0c109d2b7998cb06869a1fe9290285a8fc041413b013b8340e3936579c6e2efd52c938579829d75097031b10f4dd3ad2f5e607b0d21a1b3f935832564772c048a8311f21c7e2dc3c6e101728e0ba5f1fb17c5f5934a368dc56bf6625c5314ebf7b649ed3fff782991f220a0c0e9c1b064ab684e8e52e57d360f9c1da1a72379a3948ebef833d071b669317fad7c4f0bfba3f0698d21eb3ccd0fc958bd457f2e76ee2a1f9d0da2bce4c809ad6562ca0e0a46db498dd5cf41afd42d92d04721773dc560dbab4eea80d5ca68c39f9b25ff13d956a6397a8f18b1371fee8ce2ba92cdaa67557ee71a1a8b4054a45f4433e615c134b50fd337d274e741664c63902a31243d1d6d398b19c17ad137e0c434dfcc4423bc42bed1cd0761226fdeb7214c0027a74b43f7612e07b5501c9fd01819873780c4b13d6cc4aa305930722721a33cbf05f02f7996a235ffa1c8e7062647e8e2690afb990cf8d646f6c34f63ce837bec4f6e1e12c70e50aae5f0c4e6aaa594d377e8812b0484de399df22711657b17c624897e747594db00b975c71f28890ee266acb1c10644ae2d4b9a7c072a16d6441f35aa0d71b8f6dd9f225cb139ae2b0be06354caccb84d9a29717d07249e3153e6f8995fc60c30dbd3a87c94eb8db92d1d87affba86f61f91c628b17203387508c6e8a6dd74f1bc37ab06d602a8549b648e523c1af963efbfb0564341e497d903b71f046ab37a418b18ce5a645fc8ac305c653f1abb82d86c679ceb725dd16a99f6c64074b6783b5e8b272d345e5bdde4bfd16fb9a7ce7eff4f1b757224a2cc8c96f01746e5d3350891068b0d1307832002a51f45c9df2145291232570a4438ddd7601bd769dd9e20db5b658dcde2ea995e6e7d456b0806fc66b6185f09a84df67787adb0747ea058fa00324e3e7e72e3316031600025ed1d8af3f072926554ab9060b4204b46f65b6adc4302b7b3e3b2fa1343195d1c7f99c4314e368644c3ef4d4228e55d104825c2280d708adc76e3d83657e8a44fd9cd257a2f72154b2bc3813647ed3e0adb528fffb340caa79b9c59ed3ab4ca94602592fc17726fddac155c3a75fbc72f7814c8f01422ed965fe3c09108239d1eab28ad3e756d7469824f7b000a613d1866ceb55cc76203939163bf67134e99474bbd97861472ad2589b3cb7d9232cd84442dcb115b84e29c65e491359a30313965f4cee3da6ead658af305f973b2eaec273ae128db320ef0e984e81931f9b0a669c92d4c0d3bd4c4e6d8403eec252b632724fa7bbe7815544b969469e066dcb4dbd1de2fa45e50b10a8e5d3c9799fab96fcd486875886ba83164ff16070087c6840c6399d65eb7a3a357e37234a181f8456d008c92f4196b73c79d9d8e39a0b5a3922c9e4606fe4aa78245b981982794c5e8921400238f9761ab969340435104b35ccae79a6a624d0f677149d9d6c6e6be9812fca2a22402e39f25bf997ea7afb45bc77cb3720eb263899de9eca8958c3a5f604b0f810950e0f73f96b1994f78fd6d0e8b720896d184e64e1459906321c662a16320720ba8d416735c97decbbadae3d58183726633ce0ab46a036bbe58e1d5fecd2aabf92c1124f880e3a7be329d76e67cd47260f4ad62fc5e84e0edb87c86c9611f5d60efc15d3a398e9c342fd606d55b350215f387d62e4ab0d45910bf713752d5061ad4c91c4a46643c63e217ea74b5df385452a31ea34ee4fa8a64d416fb5377e1cc4b4be3b1fa65fc772f96c628380a72783f4f61c3a70cf03ae6ac91ab3506623edecefb3656d923ad89e7e3d658d83d03340b0a6a8a8b09ec043acbe0678fc4b75ee296b5a1c34d7740b1534c4b7572f6e6a70c8568dbf257e3a22ea105a823d736edac2404cb76c7e401b42fd2aa724d7625c40c985501a3102fdff28673b2ca138e251f778546bb5e552e3bbcce36a448120e0a2be2f77063110c6ac407c3fa5a9adda7199b12faad265f5017aa3d80a82d73abe78b5c61955a0418cb4893b838681ed2d8c187865477e67cc5b7425b3db09b4206e0b0f06f0c08924169632959e013c70104740e23a0ade93a2472dca5392292ba470d9de9e834ccf23c1df41daaee31e21bcacbcaaf3c19ac66441add940373f385f14cb64b0b82f8a37a22f3841187ed3e0d92bfbe6c64111b7251265e088a3d9cc2d969a8809c90843a843932ef9be8bbe1525d6a4e2daca817694107767a83958578631794a671c46d6e458fa2b05ceec88233ca477ac39272bbc7e908379d6d299f71bf0e6f2bb4003ef823330275ad0b37d27eb8e1d4d872b0b4e032c0748516dd924ca465975fd3f82741804314cf779e22f8a9e1d41b3e06f1f59be244be65865f39cd45148f764ed7ecdc6270246bd3db9ce73668f91f0fe0d5e30730beb01c58641c58abc6aebb1a8c2e26a89adc8ac3788260708334eb2bfc348a4e01aaf239dd4271ea0214e6fe1b8b7967f6ac103985cc2f3c9f72863d1e0b2da38b53f4d4cd8c533321c2b0cd6cafeba7216c57d09ae312f1ed72cc773104f79bdeff274610bdea9e4123f90d5ce5bee40315f0fab3f5cee3ba72744088adfb5a94964d4ea4a70bbd029d706e2e616ae441e7ba4a858fb12e0872faf30efd6da4cbecd49b2549f6b7a0586b6b903db7ff6b06dd5e0280eb03ae7219dee35c5374f591bbe9e0866e78f53933df15988fd2693c1f0cb9606fff54720e582d766a7f5ec4e62d01afef6a3bd07724412d939687759fadcb681f069b35e6400e221986714a074a5ee815a7a637ae44627ad5c83754b510a1f9fa185572c1dc37f7dc01a1bbe95ba99454518ee1bd2f63df7d5833cefd52335c8994727274da3f2ddd21398420cc2a5d4e57a6102bd86249f5f2fef6a66b732d389f1172a25aec6da4457d79cd66a42a36ceee1fb550442cc2f22cb631404d71fc039162ba028164d14c8f1fcfa84e5813fe09f15692c722d559339619a0c996efda650671428af553d9198732018cc8902c2ee9a4e093174b150f2345e555b906553b70237c9c0fb566f022f190d0f626851f432ac399d617cde9ae7d00286f86d61a7238eca3c6d282ab0258d75515b0f6592054084a2c076863d5800e740d1dfe7d2f008fc1d883ce48fe1561ff2b3c00ad5ba33f2a979101ab8278b357c88ae1377210870b0a727decbd84019f7ea7a28ff82235e9f506da35797a249333a24e412f397aaa1e9d58e142dc621dbe396e8ac938a6ef80ddb1e6f31ce0497bf689601957ef49aec9481343bbaedb8e919488242365ceb9c6d0d15384e2497c08173f1dddeef555e86ae7c06383a94fe3b46c04003c89e6b72316cbc62f05ed79e82b7283f9cc7e008497f6948e18ad7e258073921acbbd7c6ffb387c4736091855064c884424703068152b0ef4bc0cb3ce936d9de7dece455341d99b084b2bf1f0b472e12b33fd5cd5ea80e5ef4f82b31a340753c3b0abe238bc07d71790249b0ee47240133406ffbe38c4f26fb7ccd2b729230918ba6356df628cbef429e1eba1914cb5a2a6feb964b704c7dcd3b82d059e311fcd19f5b929ca0c0428599e6ab9df72b003ccb7aa0cb7470576eac6131eaf49df89e5738d00069fce98844e5605947232ad5b48d33f19968cf6b001178ac270fe0a94a14d864bc16704e5eb2a1577722e58364c7ec68c42c2d5f8baccc22ec7c1f60f9f18b33dcb0fe47e4328834772bd1a86d3c357152fc0693deb8ddf931aef13c0f327abbac531816555265b6a535da51d57a157784b359425fee152634128c76d38613a808afd0adb7becc7184cc961d17a42a15404132a09b6593517eab9ee18a6d5a6551b2ba13ffd31a82972b46a4b5765137e15145ba7d77d848360cf08a2c1daa5bd2f9191bb2a1c5ff47257f650f5d47d82f471d4bc490ee994b1c6fda364a0e9a00287efeb16d21bf8162d8aa5ab14236c979b3b45412c64e33d156bc6d9a8deaa58247441575314c872b49cadd248c7a20b02cbd59001e376c47e596c332e084bb42efb940385e32b512d06b5b0e730f925afab6d4c8d56b7b82a85d79fb7f86c977ec7963fc31ff622460d86f1debba435bacefc80f56cbc2add6a6ea5711c6690430e171e8ba1747271b5307b0062c2ea717d9ce652fd494691a251b59949ce45202a8aa1244a1372eee9d3546827cd6e9664824a12bb6e2d65d9ab2a31c79fda3938e071938f12729a127089c8c6eb00ddf2fb74715a2d459ec487eb057b0cf82cf3c96e19eda6724b72a77a285d182f54850f6fbf80b52c8300c1afce67afe5aee8e232c053c672bdd3905dcfc6f6f2a83b89d00d13a2176df56e82c47db2edb85fa030eb52485e3994edd9c0fe2a2b43b01877c6b2fa3d2ad130491a7259b9dd6342d64d8e9a72aa0475c7e25b1444bc7078e11fd60eee900f3cae0cdc43ca25cc6fca1cf12672c941e53eea58096da1757faad787ddd7ce17fa89e84359eef295e283808f8f2aebc20c25c6f31180291d0cbe53051fe31aea7e856b9577cb292e5ff516600b42d801834fce2522f26a0fccaf445febe363a9236c5ace3bd31bc2784b0236c04a0aaa14dd2f68a4ef531f71c1116c6d25430be3a3c60183eaeffee2324cf34537b765085229a2dc1ca56773c82d3551ab7d9d6ed5a6c8df84cbecd325bcc6ea7295e2e58f8e207e4075cf5cdc42d8efd26d8a3801c4e2caa4df5798af62547f3e1d1cf9af9449dc26c66c077743915dd53f6a4c23574b4d0626ad363f3e942c0db42492a56f9a46563b61b6c2e4db25941fac3428719c9965df28ec2d7d8b64720581627a956f31516ed3846e05c4d0185e361211b9dd2876ca648132a86c997261f395493dc06f7b2f3dafc888444bb86af5d9b52a98166a0a19739152f6c22e257cfc42a5e4766cd5d7ea82b02fa4f1b58c4812e399ed85bccb9f52647dc1721211528aec9345a94666b99dfc32e8ae7d0c82ce579c60a6d76dc7cb9bc38c67364a986e3454a19d867d6f4dcd88c156e877a5037609da2b4232f60b82f61f727dea51157f25bb26a2f1807d53f76fa139410ca931c485722f27b65fda5ef6727c73974ab412e249d3aefff5bccfeb02bb22072f8315ab590dbee7fbc394cc6aba9f419a267adc1599b144a3e75313baa7ea28eff37f3018777b48b93b9f3072086e13dc9d1f8a74c53a7e8a171776671f521a4dade6829fd2a9a324ecea960cd47da4ec44e7cf3b331880a50bc6b103425ef2caa04707069437ebd29786f12447f6a147b086dfc1534844e8315aece150bc1ea3d05bc9b39430940897d8c572da9a76679239345663c7285aa6c4eefca9e8da068ee22b727c4536b4d0305f7289421e9869884f9299593fe5027e1396dbc4f4a1a4cc86e747327f99e7769218f60341165adfa924b69e4e238f69950688641bdce891c133cc7d1780a3f17572dde734e2783061d800c3c2fbb1861771c8ff9c0e45a1004928a4fd14724a200acd31fec3bebcf7b47e1570dafb7af08e1deb374eb62e5f901d22ba9ac1fa4172f567bf17e13dffb4f870f2e149f057f64c9744a45819b3a5af463c068973251bc59910b992d6d4a18494590a2fa32ddfdbba255728d1a50af7c9c244c7c7da0fbc3fed9643c5cebccde730636838a36945ec7d0e4a1ae875b73960e7c3664e725ee9d2fc523540319b63ead0d1067c1ec2e8f41503767396e6cc451fa997cd72d8f2e71dc0461165e34ef6688e4092748d8f4c4619af928232cfc2f4e28b866140803046927ff4a3d56979950d3dc069514dd8ab3573b61f7fb8fba1ccfb0472f6b7c7bc6fd396e9f7e59292492f525cb27fba237fa39e77d073198a866c2e1920eec7c774a2feee3b9d3763bd54c039f25dca8f362e8d8cdca8015dff0c84321fae4cfe43c5fbc1e7d6df15f8b109154492a7d215c83fbf5b2a8b78649df37231c3cf5761351b5bbeeca400e63073cf062391d37d6e4c6943eb644bd68b09720957473c1388d5b6a041620657d1cfed45a3cb126f64f228912c632802d53272833f9701d113514996cba477028ec0bac525ca4e41c94d002d329e4a9e06e172429e713697b1d4109a0e003bb20a3df14ed731b1cf2d15aa013edbfd3cc4cf13d4dfe1ee0e53c7be1798f528d678ddf3b8f459841fb644cd3429bb012aea00721f1ca79be943e932319f45123c5b9fc92f4ff28b2f13021f7b452966e7138972914b17f9a4588c5388550ab39d857a19473f160b3d528277eee0e52588f4922cc5094f627a1e66dc8ba0320895338f4ab3d8867399cca6843529349efb0dc9727ae00d437bc290f35bc13c969554b845834bb327bc7dfc913c4b525eae107d72fc3b96d1449b3871a7fde5ff34ac8b7649bf35ea76f38e5605a778939e652e72913b678cb8392483fde18cc1e955e2a1933f979fd6f143d4bb86378ff6365d44ff01744bac2645fea22fcd184f75511f5f22245f53b1733391d0a683ca86bc0ef3da7df6318b30c59acafc64badf4486e36c450448e452cf94ddb957f285476b088c3aac7f6206f93209c6249c1ce507d8e728d681bc253d52dc330d3d255f724fdedfb15593be037059698cd91d512d0c49ddd69e74f01019bdb5f37cc9c30df3e68d3cbef3834272d2d1d800d10ef3a48d3183d5bb1fb8605f03d85ee66172199c31c28d750ce04ee018dd6d17c27bdc47c18d67c275b063e949dde886220054757ef7a78915d08c9e50b8b096a001647d94d6646721b2c3ecbcdc75db362ef6fb9ac7435b54c2bb7f3af65ac91c72e912a8baeeaf594543a1b9cc79987b58e01d05dd48e016a6d4f2f60f40580361c47100913253e486d31c9f38c5c16e7279ff860814fafab74ff2ee95ad33a535fe4c72ed1a60d4b91796ec9cd64377728b84ad6df6fd6d6f380dfce4ac5956e1824b065d847c015c94d608b97d12db722cd7c104d2bf6acade13c33b00325f86d03385e7453ac8faaa91f16936696172d823b93ed44ced1dce1878c44246ee0cbc910543b0e3c2d55fdef2d8b4d925728ea01c654d2226e6027a3eb7254db70bd5c9bb6b12c95283d3d23568db550872f17a6530eaaa2540990538b12d638119bc2635118cb61b6a720d6053614424726af27faa1ac9ae3b2dd067d101c2bed103cb2afc8f98d8d34046ca777f095d722b14a595605abe6e8c045905549c3f7ccd8a87d07ec64a846caad49940a02f72baa903c4f6f6060de5d2e86d8d1db7e1a783582d59af4e59b577ad2b7788197254cd9ae42674102e9affb8cf197aad97eeab8671aec9e323897814a06dd6b8727663801768342ccccf9ae9e87bbdb351a10042148db2746fc1943623030884729a0b8b01b5250de6d8b1f1cc69f5a727fcd04853cc432c652d9fe60211ef0472da50e5f151f37259a70f8ff7503f801f65e1a48aaf83ef093953c92ee3b1b9725c2ce2786362483a1d8d650f1f5b94702993922950e6ef2f56199b630a4d293e8f85806a73b79a09e19c4fc9a2ecc7aa4f792557ddc1d3eae61f15f5a9afbb72efef5d03c1cba4a68695b9eb184e93fa747513118153b8cb64af6f3157d22539527e9721d362e3118f44e1672811a8a37d649305dfd34128830da22969def57297711c4741a77e79188522797b6b2ba7cc0370e9fce1a69038bc089389436972686ab8777acde5dcb759f1f9be0f0895254fc0a88db412ece8cbbd9e8e3573722217476b56459998533f5ab39e27e80dcd6041ce05dfafaec9fb164e695ede721bcae6f4f4c4e00e75a15d4185b4370cbcf22f04f4c704cbf61dadaad1296e37714b906ebf1df0d4fecbdd87d9ff8b710b40918f29fbe1c78474b4146192a572510ba0198cb057aaec6a7c45121178ae40ae275585b4fdb930f3ccd686095e72f46b90b3f1ac630386854085e5c848ccfa2dd8d773d67c5ac0144dbdd80af372ecde6f31e93325a1e3c99b4eb08fdbc1a8d014e3b8c90f925016ea542231855b17a7499c0367ed451a779ed0284d84dafecd03ea4adc5e975c4f27bbea8023214c6e586c59a6529003e783aecdd26d46b9361c84d8c017b9a438178129d979723a88c6abf6f56d6d64bdc8828f91383ebb0ebed2d1b0fb0df606809c3ce222722780ae6ffbf333bfd75a92f909ef7ae850ae9ae201093561142521f941b87272eead1cfd0a620e8448202006179e1786deaa95238b7aa896f1447fc8dd44a1728ced5f676b2900a7e1a366e348fda3754a8489378f943028da759a83592f4e72795da5b50eac6748603bc2f1af71d8c0c12e03f76c259e0985829dd4f4a069062c375c0fc36eda7f25951db98fb098dc77f1bea19e690eb007be8fac4e8d9a722799e322f1e4db7fa53398d4a1de5516108678f2a3ca26bd592847d3375f0872345ae596211afc9f24ec5855a7025811c040c889455259e9e168f9bd8a4d0e726ec9cb0234d4443acc4087c4c61f19fbf333d3d64f89c06cc80d43129ef18654c8027b8514299346ea2195ae146ce1b0099914777870e1a5c4c8d00f121d8a16c46ee6edc690433f530cc161e1a8cd049a1f53f29bc0f124c1ca86535715025bebbe4e2295afee2df0c2f64ad7ee0caec3cfd155be81eaa79ba538f5599db2721c2fdd02dfa8441eb18c4ccbe723bb638e070ae1c03e5036a9e0d8e0cb9d3c22067b2aad27a04959f25a685f35667b57c9031a2f2fd5c3fe0ad0af0c1f367f27ae33c6562a77cb32fc231b647eee796f49c796dcd383c0464d2c3a6376e8c95c5d87f41c24d4ea21e72828af2154a94f11ff1cbb5fe3a0150f3912a3af561372f08a59e4ec8b2fbf55421a40486011de40e3d97b7ab64fe9dafe99efba26b17226bf8a2ebea1ee80ad8dc60c60b147b5428e775bbf44eecbaa8e9d2d2474ea72935fd83f7ddd224ca3748b99e4cb7da9afd2ec0102861236ccf8171ddfe5c463eb4dbb380137341043baac47f2887dd18c98f7fe6bb1a3a93aaca71482b44072730c619d7476328ae2d8e5a0fe455cf646a216a97233063c25dfccbbc301df72bd1fe9f5805fc5ba9015399e7e7a7c249e94491990618d43872868161950bf038b14af2c1bd2c2734113f2d0c4593016c659cb12f78f8e629e9cbf8d896a837241e762bad82d343b8c484168121355a8405f4620b9f69fcdeb2f3239836ad472d1a0da2afbe29ff5edb1ce01c57c5a5cc2e5ca40a92447480323603ebc146f14a626e4bb89c7628400a20bab6a0452a616c542e77900bd1d5fb5313f5ab2be724bf1b9f960998817e07b677b7eae0f80617eaf2aed13573e525b326832408c72e45404fc153765ecc03f3561bf7ca857e49d2bf7629918536c18286dab5b564fb518ea2e2d70d929812cc6dd0c208a69ea8ba2b3939706cce8259194013ce728e30a9fc5436c5cb625fe1ff51902c604da11228b58dc4270b050d5e8063043723ed9ccc672f23c78d10c9ef172f86f79b9216187697985adde1c274a4c7f1a721ef91b601b10e3851b637290cb566f7341b31ac93a53dc3888629c0eb443a2728f5c0d785363cb3bca09df29d8a12464e79e63df6df0da9241a4535c55a036119fb13ff5d422a19b43ab161e674e87895bbcd2482e62e2e31304b26265a5ff4c1c3a9fbad7361936b2f36935c1dac981097e385c2643dd10867f9dd042e96a3857496e6b90b785035eb730b20cfda871947243f6987849134b0249e533d32972c35494615e5fd3c17ae3a3dedbef03ead58527592c328dd326a89244ca00dc72d0c6b5403ddb45b7f1bcf0facdfb7a03cb96a884c73337ba0333c9e423cf9172ce336ce8d63e59b589bb889b8436f79c948d56e9461753526c7b74e9ca29e660dc91acac5461b41bf999206c2e8cd2afb9015c8883be704bb691e74f273e9972704edb4a1533e9ae18cf9859537b7071910225beb7c2da0a63db620a04cd5f1adba1c7fc7cad7bcd100d1c51b3fee849f60d886e7131835fd6f23b3e82c3fa7204831a48f6522c69e0899da86b0c62d59141350e79e7cffb64b54615d47f7d5572d2dd84362ea6e9eb3a75e0ecce6b831a211ad9630177a125c7fd4de57db7342b47df39ff94263484ce16268ba4da92250277c106bb330b434dda4280a95440cad35d7f9f11fcbb57efd048096f5c545b8e5ac238f5225a9bf299ed928ee572b8c864e34e738d6e48cfa4585ba65166c16fcb7030640a1925fa8bc5dddb8b429698514b9b9d33ede7082cdaeadd391629cda4cd6214b709d7da48d05d24e22d08e57fbae9616ab4e94fd31711013b9621ac77d171cf81eab8642839f422b47215340a549dc68c9b714a43579dbb6b722673fe3b44674cd532dc59c09305eb72970d788b903f4620d9143617674b8740be9a23e9dd2eb575b8a1e50d47093d1616206560335fbc3fa8c013eff1598600c52cfd8f95ce9cae1ba6ea2e9737de4efe3f7382a888ac51c79e5600f16e8565d8139b635bfeae34fbc2a810588ea058449d46e261f79e1dfdf1122e5498f11a7f87cabe7e86fdaafe5b47b09a09d77296666de2caa46069f81adf1e1f4200e571c7411577e958d81ce43f4e0389d372575307230fa46aa6600c54e559687d1a3cb5950fa87489da85a9f1fb0573a506a802e289abb922e7f5a8811aeb4ecfddaec522959a2053ef5234838dd504dc72839e2132d2d210e0d1fc93edd7edaefee3a806ecdd44e70f63324d784e8d376565194592be53596d241cf4d36aca3096a1473a8f4cb299f7f12e9cc8baea6372d0eb1022207d742e7f5f862bcd1abe31cddd8a057d87da611d8f2a0dbfe37172de546beb8dbe72586ad8ad0d61861c350e96c87cf304f5a051e78492c1cf2d59bc0728a0c8c50e19b5ed76ed13c5b2b3f3b5e1c02f553ea8081c194fa58af30b53cf58b361bbc80e21de6b937a0ffd53f553594ac1d49761ec7454e20b70da72d472bad53a6d737d896112252c7d0217ec639afb08eb923fdb1c2f4fac1e684416579bc0c2f2dacf6119051fe727b4bef5e2735e9aae89f5e2f2f6876e55b972c99e1009fcc365ad81ae06f0364084ce470dc0f6d5b255a120cd23f43c346e5d1156e8177e1adfaacfee4971ac8868c6ce985a5cb340043a40743d5dfcf3d857926ddd94f6ccb5a4760dfd9f5c4d3ebd9252ef73757ebbd59465e202dc938335f71362d15c7fc58cc8156727c58f5af1f4cc8d5ac2ac40ccfffc4e6da5cd4f4cbb721a3eb7c78762d970d1b7264d03f060738b33a1a05bf597c7ffdcf71e0a72c6bea37f8343eef70b2a6b1f43ece480affe63e9dc8157372f736b708496aa28e8266c97c47b788cc57b2397c5ce625f6d6d2a0edb8e10b50738f83e68e7913e3a903d3d5c06cbf2b9582def67d640bb84df918d67bab6a840de6efca71a6e15a22600c4a68e8358ce7ba53e5c38421c8a3cc2e55de997955b3bdbbe4f6788441591396f42a0e139eb9eb8184f06835f9a3bbe55ede995bee85be22f6347ff528e7f657eba9d2518cd7e8f5f4bcbfbdbd8310b959db2c978606fed8299368a7274aeb0e8ca281004de684b788c7e0832f7164140bf26f0cdc1320d8421eac54af92f71a30623894109882c722557dfe177b676859bcca09308da02e6a4ad1968b48be7fd84ae2b4ca0f479772f1749a69eca0782f97d00a69fe74c1b11e20c3e3b5dd52312ab8d6f96ab5559ed4b1f966c92531f6224a400835584d46d8e6c6365687bbdea8248e6139f00145de1f107e9674e2ad5d3ae4b409cb9aa66138b4dbc0daccc969b575edb6f68d72f259a4779eaef1ee2002f1603234ec2394caf5feeb2ed8206c1a5f999f3d897b571ebf2322c36050589ee6bf1c54633ae41b24031a377c0c3ba69dd757ee6caf1df8e306d190d7d59910cd9fbc1787c9e6e3d452cb764c0f947f330e8dcf4edefc22c249a261ff858033786898ca26c7e9ef40a245071063f977f31de1da38aa4cdc1ac93594e7b60252d519ae2a2bc05a863722a5cfb6176d5e281dedc608ab7ce4cae8f5c9a6742749371fabd89041e5560725b8e3ac28b9f77612c3f555a1ca4b45caec3de59c4a8353bc92cf9d05bac487217e066b9094600b354e39a114c4e451081c56f9a185b650f9325cbb465fec3724d48e9ac47dfc6f0c0c718bdf7cc8062892ef238cbcae1c6d3e966bff0945c727a9edb2fdb2547d0ea4ff4b5b750c2c7552e7c1b92ee192e2d288657162a1c2dd1d3f6d4e9a20d462d6d506fd6592ae77377da699acd08ddf96575ffbd745e72a45551b9b5a2c45c976896a02d866a48cdf597c911dd1e172af6388aab7d9872db9881ea1e1764a184da0d9d4f31e4967470914c9df7f69b8904cf44095d9572ecfd0b7d26c5c4d68340a732e482f43747d5d8a165464f118d845159ede9d1298a6cdb65136d2a8854b6a4fdb0b4b86705a98b0b56b93c081b2068c2a5733572ab696eb12e523cb46d81f9ab497ca68f5cc484c16d0ca3830ace1db72ee0832f7c2ebaa9f1ac8365f4df0fb34778835fd048ddaec73973c6c4ce684208cba1210ae8120f08a9088f0632de2d8f1d3c46c0eac1ad92e4c232bb574bc89cc59e729bb5bbb6b3a1ed22b1f8055db057859ef09abcee110e267d3285e733f7c44f72ec6e547014593656c36192392eecfe274e729263d85a78124388773b5b123e72d82119b1158127d61aea7ac42fc09790301af661e31a3abf220df90241e2c17227c5ab2c13cdc8bd9934376b036030c9e7dfd5c1923b8b04e89ca6e79813bb2778eb3fb46d0362755dbf0919851993a1c17701824322563f7bdd8bf658ed4f7282597bdc00753da70cc2323083f19fd3edc1876da2d10ae14149a44306c8a600cbf1b3c67c80479d12b66a02de42bd60b6a737e8e987fc33112fbc1632904a481fafc3484036ea139f99d4f74a726b26857e6132a6f49dac9a8d2bd06da0c972e46316a9ff4f81ca3ed7d60889b573fce82e8261c8f294572d98d4fdb00ec0455fdae00aa3ebf734ee12bf17c2bf50c0241477a55095bb1881fdfb1eb1390d39db09663bd7c4c78b8dab86e0deba5520e65b45c8410b91f870c231fef51f361d2dbcb6caca4df4af5e730bb47619dec826b995ad31a595631421a91db287b726c910e21ba027ff4ace60a8d8cd3123720fc813f952c6093d37f4f60c9d5e7127150de57af69e2423fc6d2e6a2959829f381758000cd80552dba03312460fce72c282ffd79622643376d246e868b7e77a5bb152e5840017fc606e82f3df09782aa807890e9d0a29c4d99d34996366c2ce21e130995641ec4470cc1ccf46b0c32971970de629107a9cce077b0bb45adba7a05da1a4a834b3f71761e166df6f4b7292b92837bfe95e1b7d11648d24f21c0475589ab590ffc903b9436af2089a0972d1dd7e265ea0266982b9ebdbdf0488c375b12d366421d57d3d8c4966fef2a2637a8a38e64f56f46111b62bfda32de2f098cb88d166925da9f657f3b4c7f638135930af8a119ea0930ad2a798e4c068e7c57025ea76e188c75c6bc725065591729b86f59768de493c34f104098784459189ed9eae180cf1edf60fc09e2d6f0b7242f05c9d813a9b8d51970357209b20a14dccc8fc217a20936f0701a2956e70722b6e52b6f996dd441fae8ba44f626c0991aedcb5ef7e5e3102423f923ae77e1b3ebb7164234cabd081ea05d885e8b7d732dcfe3411cb1ae617fb377e81aeb572019367261cd948a8d4d59b9009d53e7d59eb02c896e8cd86bd013809da26740bfcd6d395584452f476db28828b63de302818bf598cd9a9325d21bb377e338272c248151f86f1fee37fc3de93e522d4c8102c8f2462a80c7467e82536d2b236484b88e9d164d8dcdf82cd8f74ba6c9001035923e48586fce410cd66e77f5ca072391e532acfc3611a6c83b57872329b7a9d7da41a9ba4f25640f3cfc10413db72298f65d409854cc0eef300ebe9952297e906207fa8db2c23205bb6f071b65a72b94d18c2681a8799b9c34586ddf2e1b6801989b5297e856c68c1c50c8a52ef7205ee806da241ed75c9c4f7660d6b83aa110111b63e91efe38207d9a1dcb9e34f00091d75c303cf08d0055a6b09e6c70b50c38a004dc6f02198b13ed69b554f72c68ca70208c9a8d3cbe725c88c159b666a0dc88dbf607dea370ad27f5f294123e2c85a11c65c95678a68455e3f1601baacf5c6faa242dc5b879e8b1364cd025fba7378cc0396bdafd176396045e30174e33720f18bee1e5d0e5bf15bb1c6ee370e612d59cd130d98659955640fb7701decad7f3ca8716047e50ca9d70fe6e568750969aedc0f2b718f92cf50cef961dac52cf4dd82d6c033170cc5dff0a393725a9f9b107e4878d0d055562bdc118066d5851b6e6d8704b59ce38f24492807436a71d8d1b94c925dbfc0163eeb3b286911a1e07573406e8623c32e26e1de3872f1bb0c13e6e2f27ce0a0ef2c794073c4cdd78464c824353156b19f60f5bbc0725806a6585b88509ba6f299e45602a03a484b168d42e6358796f4718680792d1a8ef6b2f46719612e698cc163c3105c356e97cd0705c88f8e94bff1e907e7b1721cfc677c6278b72e20a150e5c14734dd675fe3cd58c7392a7b4e8603d474a87222b67ec37b30bf0fec0105426a2148d300eb8118bd90a552d89bd686a5de6a728d559e0851a2bbf33c2c864dce8c495f1dac1166c59055f1e98e4d1a4eea370b39aad08973bf7075ed400126afa785d0111d4bc002073b205355f64e1f050872c2b008e4b4a3879d12da596f5b2e1210616b6a8c6844ffd566b2206f2600332099470a73e350c27a0dc75d170d1929ce998c98a4b50b746334f4381904a5c272930a59df62b8e0b2e7aff7ae515459a5f5c88ef0fc4b2473c8dae0c6afb97e72e3a3d0f5ad8a232821d1390ace56f6d7b116fbc724b110df442b276d1d3191726db7a5a3e33d0bbe36aa3b37a6a45113aac3ee7af7632f42ec7255d7563145665af68190bb3aeb79de47d39651cd8c9a0c5cb519e223c316bfb96e0f5e522972d4d950d3ed726b7e56baa5d2a7572adea6830d4b0973a59b39fe7dca56e7ca721c71b57d7d317132b16d187e658d075f018ecad86300547616c0642e1e1ef021a6c3101c88e08c34c430dd2f35c3ae0f660a4f245bb5aa425ee7925278d9cd723f1c0fd72ec3753b430374d0bfaf372c2f64a8cbb6751e210ced915a7d6c6c7226470e2d4c3784ee2a1243389d631d6499ab6da33459252c949288faec5cea65526a18af74f73d900ff208e536d5c9d2c6a5ecffb1f0b7b0a53faca12e57e55cde68f5848dceb4b72bace20bb79b0044b28744f177c8f1147ae10d07740d0a72864d33f7b846d619ddb5b24a90500ccafa12ab75033d9b9f003327cafdaae4724d1aacd7fb101435a8603c2a4e620162ffc25f6335eb530fcf552b345f108e728d767cf4fe7b79f2bc85e88016f2e01531d0d5bf8f658581944e4a42936b9b721136dc1524840c8fe13ef639acb5c65c04e2e42067d7e1291874563a31c04272c784ac2c5520c99756bc24d169ed8d856a791b0dad63cb43af8c1909149db9722c2880624cee7004c03369901825fc860b44f347f16326a1c2786d1804692872a1b055dcbb4080d76e5d055745933a7915cf4679c8308cc43fb68330ee278526891a775db3b9f68dedab17a668af5a040f5268f1e35e68180305abc0120b527200aa35accab730b82193945b5fc2715ce521735b3661e2daa83c0421e6a42972589a66b99b0df8a1485ac9cfce77c518f927fceac7412d9b7f27d872e5572a0061f5f337da74c9f321dd3f835a764b100c25517ccb3bddd727e3702fe1ac8c7255d7daa0e4d68d6107c1f7fbdf1449906cf0f970ff02bf784bf9debe71144572925bd6f7789743346ae11893fe6ae36d80f643984e79ef779b7ce648216c78723b8fa3247b00302c88d84e35ed9488cbe925b81b770138555b324f4f8b08753d3ce6178a4106f84add4e61f52cbda3acb5d19897ec5d708323690a7ae68a041eb596d39c9ecf546e5e5edf03200be3e2556aa829ac507988be9dddfbb8afea72e6a0111d64fbc1f26cac6dd1a5fee09468869d458c679b278c086162fce1181a12b079def6909febea2009581cfb1f135d2d04b3cf08a75f6ff13529b153fb54ebfc35e37d4d0a77fca9d02c1933ead4408458c006f53e36b3fb95c00f0da319802f528b662f8a2edf5746d5642e60754daaf9782c30d2c900c66f5645cafa7298f21be6f6de754ef224a81bb15eb13bcf4cc81a8e88aa955a6532fa69ec6955dc8557cbf914d4cfdd56bd64576c4ad64922f1b98bd35ba86e60f27911af36036818d112985c297a09d742d070a03c1ac3b48b92e2a2d5e13042595aa204145f166f48bd6601de88011926334444b7a34b6ba4fc3f8905f664044167a1f37d486d81add6b76ae05be10d7bbe7d4f583edb3961509683dd23a2a0a017b66f5236b1bc6e0f974c2f85f85935d34701c30f016585881a3b2d39713a8b98813401722bcec1e5cb3e772ce4d12959b46776c7023740afe134d55d22f45bd80a5e33053243e8de4b6dc8924732f0a0a2357a8d3cbce018bd1d0da1196bb5a4c47100721f74c2346035624875892c08424db78e952aa3c85901d38a821aca41505a7d72fb5bf946daf57f821b4a4ab9ba34af4273d1437c2a9b788246b05b68afd83415aabe00cf88b27fcf2428605681f677ec4b1dbe881fc41b5abe3f331c36aab200ab6769f83ad0a8df6cf0d464e57cf1865f3693b85ae794ad415f61c063ea23209d8bb05613e2c88dc9c316b26e85d240470ec3e7ca82714c9941f11d85ae8a7245d59dfede323346b5c42e7d69283fb8b380695bb698019d250ac1a4de7c7c6720cb1505565e6ee8ce88389a7474fe5465304ab2ce8d8beae4036da776eee672f1b6cd1410f1cf68a513fba1e2bbcbec7488af66d2cf2db9c35bce960f27d572b0a9f5d437cd2164ceb990b303c6bd332352149284924315fce694e583466b724530d47da1d6183cd76760794d85e6ca63a777fb101b7c4c083220f601b4de721601f7e8bbd6107ce25606cb74150445c4c6f91349a2f2715606cc95205a3d728390a17473949208ecb8102ed23142c182d0ef3dfbbff55bfcdcfb08bb0b8e6ab00b67d20c0c8ea1e5dc564fe6eb257ae6a202fce36e316fa678e694d367987207124a53f63c1292482d3782eb3526e5bebfee75e762b33a76bf3c4e0326ff72d2864950fc612e1c8d90338d1ad0ea5e1ff9eef46c1adc8432f1b25c970d3e3d129865f9aeed8ff162975104047af82e7f36d38feb9698ea7439636f638ec64e729971ddef6c72389b0cb2893a2a706588014a617d0e6cc455e084aed194107236733ab861db2e7f87576451631868f3da9e2d2318f23004ef63eaa9811c4c72e2b4679ea07048d684fde9aa22777058924a2d445344e3c46943bcc169acae72b875dd1d86944a4178b82cf0d7b664ed07b42061010eef247faf4ffd10e67912d44334b5bc30cd36c39caec3a64ac0098650e53b70046f63f8adf11a8e4ac963bc849d8f5b2652ba556e45f320fc01514c4700f472d600433bbaf7d13d1736097f5c71d2c278f781f20593cc9b3286cdbc27741993a358c80cfd50ca70b88a6a050306abb35a8f4e5e8c1b265efdd9391b83b6f8a6d3705d097f1e4aa034ce72f84bb853ad377ea7085ec5805ef5ac9b2f8645b4c764d9ff75108239d84e1e72f7a8b05a661e9edfae9e12a7c1bcd3dacec8fc3e0b8eab11456651e3d369d572609c393a3a170f4c0ece6331ad4c0aed50a8c09a9d3e0e2adb632336b96b5b724570183ef7246a3f95e7e779a2729ced6ef1e5af8f37883a94ddbfe8e6b4f47231256890f9e5f7aede63f569d3957be54685076e12f92415fe6d8d7608956772ac521075a23fedc014444a79a86d40a62d82e305e0e5e4ae3917343985a28f726ca6176ac02acb507cd492e98d3b9f252cfddbe4b9eee9e65c05d59a55e05872a23a538162f4cecca6b94b621b01bda4b7cb1d41dd4d6b9952ff7c3ac27f8372a8cfa09e4d722a2ff5a79ee494e261b2e33747e6b057a45f649846c787c83572f08813088311bff55f62e5f1558f7082d95c16b92334f00b155801f6e9a258728517445a31144ac3f4cb1cdbedac0e86e499bcc6d1f670ae64c3f607d42b180d0f217b9eda3814883cf9320e349be695957bb9e5db877989ffd1e4c03f57f5727ec797aa0cae88e8b6c1844ba2326a5025e7a53f084e4d5b6825b1d7a7a2d472fe869977209c80c5c9bfbd6f0e08a59edaf67d64e44d5c20b49a1b2cc7672b72826b4061006da2c00b01f410b3cdd7471ae689a740706fe380c620666a010600d32103477b6ca1e26fd225d7f75e80e8a632af804e1a20009353215e2a13b463365bc85b8bfb4c3a0391973e1abf77aaf9ff2595877b2b70ca70d4275a31366e5f45e6e78a12d631e173d6f398133cec7441211b98666ab7c1c82f5825ed19723dfef3a6b6fe0af2d4c98c46d51060da0ad42c567f1812116ea35a75f04b8531df5ed81db606779058316dc8bfab2b1d6e9ede8efacf22c9cdaa1d3e07cd706e223a72f4be651fb26a1bf136784f277f98ee4e4672235f6f1e090b7bb53bd8724ee2f1181a8a2f126aecd79d20985eae8f2eaa674e3413d73d720e48a1cf007234557774e1278d5b88e5853cfdd8c70e492cde50e2d44d082c103e1b7db726727081cb4f1b911641775326ba3d42d55a8b52c178a5babfd78ab79291f1bc5f1b1e721bb6ce4c00590b5fbe667845ea98b556520cc9a5d1459d2c84ba400341723a8e725c14d1314c40387f5e95960a0d16d47db7e4636cbdb97fbe446d06ae2d48b8f7362269894abc974177cd42079353ab3c8363e55e88c6eb95103180ac72548f00fd7cfcf3a6637942a55a5823c8afe5070e12c16aca40137ee655995a72c84df49a5af54ef061748335d9928b1fb6e657307b71e26d222495994a3084077130df8b60c914829f3adedb1e69cd31142d9e212b484bcc0d383662bf24b67275caf93ff350993dadb657756fe46595d123a29bb44d2f3014b957905aefec72ebed51bb62a652a4601ac34c062c1852f019c922d1d5b5d5022b61d390ec282d3b288fd4992abfee3771b87844f9f7ed930fe53f2fe10063527082cde25f8c7241a66e5057190f69865ea9ab0891d85c2cfa701d8d813e5bb6ec3db67c634c72a754a919755b7142f624e8bc6d668bf2f9cd0bd7801f49e09c609c2025894b7263b4b9ea6d618d272f102bd7f0359ee5665b9865a9a1836a503a19a45b693641c644b93daa5631ec2a25b4703db094d6928be9d1db6f15f50260ff9658dba6480081a100aead83146809c852be3e72cd7320b11e0bf6ca1b86704af7dc4aac72c942ba5b5e04db81918f50873c9071d0f8a101301cab4658323641bd1beed172988c53ef39d5c217246a08959d92347f6047eb12b83b57d0df1ed0c9839cfe727112d2739e0424a43c035902a7c7bddcddcb289a4fa3dcef0283ae427f061172ab3ef080f6feef0dd4be611d1de3f6e51c49f7a2c07c73a788e109b95d0d3d72c6f873eb4fc910d81e038593125f8c13c65a50d7fea12a100e43b8898bfd6572a9bd5e1a2d27a30ee192a7ccb7ea6b271d9ed0f2997659656a9dbc865c84d20a6e1ae2a12b10ee5a0629aa83037fa8774ff8bfa8c4cea417ec535af2e9f5b872b9c93c4dfba6042f034a03ab3477a42ac9a6e604bbe3a1b00d33a7f3ab89ac725dd747741ab89e946cce4385550d7c3e92ea23ecfbf272d434d6743014984d72729c7cc9b389d454838936ff301296a67a449da6a693a70bb9d45517b52f18423b572d16d111260a6703d099914b2ad82524f0232e12e565f92c4c3ac1e45d5c0c2c202819382fc1256d35bc059af2edbc9cd12830a57d16e8a774a830daa372fe21288342e000379932834280fbdb3806a82ef5fc0afa30010ee2c29e7b857215ce7d992ad5623b7411a12d1731cd51fb46ba45d93778b165bb4ee7671cb772514b248a3a14b83288f87ba7daf56195d0c766b806469cbacb24e0c41eb7b37294f8fbd1ada63d388ed407b2e40c23a21a3433994d22c73c0ace89910627054751ac33c42c889dfdd0eca247ba953a180f033c8bfe526a1ef63f4d152d51037223fc8a153c8f39d23f623e74e4b9f8bfb5b335a00cfae392a21df8f61984b5722a0ed9a2898867396d2ee723787a2353e5cac95708350eb66107839c9ae71529ec6ee064abd41e4596424de866f8a6aebd0c1934a608ff48933aaadae09aee72502956d48ada8782fd99e0a32be4ad2a10f34d05ea991bd09ff88dff087e877277ddc6b0bd4d70fc9da12fdebf67bed25a072501b72791e9c0c4d9deaa20cc25fc0c28c20bb81621121856745dbb6a383a20ea4efd83a775a5f0297ed80fac72cb2de1b6080868156b2fed46ca41d624dd07d78487d5f671bb3d626091f8c57291436ba9cdde8f35138dd993ed2efcdf5163c3004405f614d7d054f6b0ec9c723aef60c5418a9f82cfb9b2cbbcc83f6981795797d2065599310e19ee09e395728b1a6414993188b4ae3b3a732b29e76c354f239a509c068449cac4315f2eed6a5c66318554deab51a9aedafc37ac6c47273e81ed6a7b8dd7d6a06f0c5592df326e44ad63fc4c2defd542635e36abc6ed0345d189bf6e349b8768bf4babf6c13268460048b35e271a912b11395cd38953c35abbc0cb1264ae76163fd6ed39346bf70db9bceb60e3f8bc200c2559af2dfb0a240f3ef31d1d770b007eaa4eb79e2c4ae9d27c31210065b23b664f9a70f21fade0344f47b25cca62ee01d7cc0c1b72c4b55514a64d260245ffee2903afb3459cf2a7621dfd96a2b711f628058549720be1611af8013c3afccd66efe2828b8b424cbf5f163eaaae6cd5052853bacf72ff7b5c487ec617f02b377ba694b30f4842e7f87e0c38c112a188052608972c72c147e9ce1961e3ee7d9859da591ebf475f397324d26946a7d70da46d5e65cc72c60b3c9d14ac37bbec64a3637d59ccde5542e99c7bd495a3a71c25a92a502c726f39718c80ee6c8d58ee28adb16453674f0ba8b95c3ad3bbab572cefcf599d5da74d2d28353e1073484ba0ab95f15f2e358f115cbf10476a34d774aab88a2572a2a56b000a14cfc70151c4640303128cfc3e380b6b69866d5d056f4f4a3e7672e22ca90e6ab8cbc1c7148f6007d04cdebf79b1cb9ca732976b4a0abb32990b72de58d962eb614dfbc4838026a9a8edc06171b750852b2a8e0883585d4c3fcd72ede01ec55acac7d52ddc5884e658636a9e9222b462b98224bc629a9824d015137dcf18f416d173f9c85f50b6a87dabe34bc4b118d361e83f46c671923272e1724e756830420f8c72e23e3540bae8ba9a93c28b6a67b5621ff777efc90b7a6f72c19a8ba1e6d97cef07d82008bbe75280c6426f79429d0dc77d4d71cf4d1b766b1e1584f4475285fc4d64fa8b6d49223d60bc2c5e422a83fa666a90ee49cf3f15e1dcbe6edf6204e5b11f4e772e19d8e1df69d52046b1d78fa04ca320f2987c72fca035d12265ee779c2d531aa8c3329464847169d2878612d222daf9fc97184b6d98143fc45c55e7b9c59db7128cc562abb56da834410353cf8893114b148e725a185c1f9b98b5365fc7ecf45736310f853693aebef6210c8f0e005bc7dbcc72ebececa70702484ffc7f45bd1436e16345d87387055e37130a01dc7763a5901cf6e81aaed5189f803d859d298ab4a7103088de4d15da9d968e561152f0ccf7453f5a5f17a23181cc005d34bd6bd132a6aaa646ecdd7ab2a56601454bc2194672f09949d8ee60f6b5d910a7985dbeffa1b226ad1abf4e5a28355f2c7a90dc20726a54b27e3758aa9df5e2c2dc65938b66dd2da7da69568abc1ec286d2886fba72c8db8c9c8004459c8179a74340a9296a81945f3c06d5376f484b250cb5cb3f72c2e1d8cded068b947b05bee8d3ea5d4fa3123466d65f832061b6d16ba92d3b7215bd2cfddbd895c91f714c85b207aa6e204617aa7b66ae0738c0deab52ad132bb9db5bd03d6af4274278553912724234663cc16c650785c0a72bd3e5fc1f9072f0b7734a5921992021db8c191b13521566497b5d16ee34ce6f4e9573122ba232a80bc4ffb853ac34ce48fcebf3521c9fea8dc2df0e9f234a6bd56c51d5c91a4bd384feaf5df7a9d9d2ae9494a683ab258709f2c73a9afcd4f540937f1916cd37b7b257508fdd2bef1573ad54a450de9473f62cddb2466e50ec389d86a2356b720f8ae6aac8f5aca04e57e38a962b8f28e26acafb6100dbe3e095b283e0fa937205427f7cf68a91615935c5105eaa5d685843f4694cd4f3b604178a4069e50272ec846ff89676c485cd26981dc28d3e5625cf1f1e4a47c5128a50fe90c80e1856a368b1bbe98f4feb50b75ca15b8ed1b5af0d968b93c1abbdb7c1ab69c29e3036d136cc7d9e4030c3292ebf3d53cefb7d5334ac1b6b18a3907dbea1fbdb941e72f06991922c682006da5e542b1b550fcd5c0c84305a2fbb6fef5f78f82fd5a172a0955b7b6a7aa01f4feb8e62e93fbcde1ef5572a0c002eb4683e8da548b3c21413e361decca1c23f9577208fee01d64c48ca91886087d5b98bc11cea83c7b6698aad7c5b47d3e87ec24f4a7f2b6997a14752f16490ee8282357cae892e28b238b2bf945ff455b27ebdd6f83ec2a49b2d9acceb047886ac0f77112e7c7acf6c720381d806eed8204f0fddbafb6abe263c939271eaa864ddb85a01d6e58e78687224464660dff53f918661c9e31b0af70d4a2c7d5dca431099fcea32174c13557265591df16890e1a31bbee703a5633cc6b0535d234469f51a2d5fcf2e20fca11b6283bcb02e878d7c01c3d621877a5890e91a691bda668ba2521a1538ff031b3b94dda95c1fe6c79f0d0506818d754f1e766806e85fddc77d4662d46a9dfad2722f0e36c09cc554eb25f4103a37e9ef942a3d705e8b1af4ec2dba6cfecb6c4f727d79cea26b8d137cee8551cadc2de413af4e6297170d785b901a408ce1a399283a056c24cc9c1f07375a016ed599b0b4d771a145082c4eb3bd5515b9cb300272684997df15990eadaa43fdee0f90055ade02556bf664a345c5ea42e9fe180172675834f5fe5bb520b8b9ea91037398c2b3d5901c908abb8bc6d7543cfb22f356f3df72585fa9bdc31a4f314954cb1b982ecaa7a863d2ebaf6ee29f813341a14f2ae08895668f06902525af83eea0331384724262bc2b6a910deb62e1de2fcc58d02ca651a77897fb6b5c699bbb7ec23f3b3d7823c2ed171c35f01cf1f9a782728ce88f4e1cd836f6ab818c0662788c9f53beec2593f8f977d68d72ceb7a01f72ec614852ac06b8cc8f9f014fe2385587cf2f35433ba4089348dbe6b1e05321252b67295ab41b890a5e0bd8640717b28c5e5b91915a7a40d91d9275c380d098729ae75474bb1555dd35401af621d4385692a45c067f6d1f716817c81172d95e126afb2337e04dafa6ba8623a0f4240df06ebbac1fd82b1ed93d48857042aa1272db4890e772cc0823b72cb7c6a9dedc75b8487913f2591c1f0e5b440f12af5272c79d64702497013433c27e4419bda82368565e5b29a65f1b2a656e1610e23e093188b469aebf9bc7319a7d3bde0d35f7302bcbdcf79a84f3a556acdf354b11723fa13f7766f2c39f5d715bd5f8c8a3835b097fabac13376a313f1ac1279bfe72ffa89fd45f8169a07c9b1eb34fe10ffe948ea8a7e6ffba17e432f12c56248472bf794fd60b29aff53ea15ac9b12ea96c025478cf67238c573b1f8fc563f680477c46690bb7d41e667a2957e1136d0293dfbda76b5176e786c8514d190bec7b729c9015491471d854a926ee7d896f1f019d0349ff9decd0929384ab16b25a92728877161330b4870c8b49abffb6ff4b9a97e7965015d4f097137f4453caa23372805da75bdd88b4b013e4d9e2623ff9583fe268e5fc26de016df1caa45d669572e0ab82cbf8760d776b302532295005cae403e86f0ea294f4b9c38af3d31ddd4f461c320b093fe95cac7f7d0c4b5fa04b86d26451f95b6022d12ec9224e1a4f72c32d2bc888d35b2a3bf7dda0d0b718bd018ad7b2e45b282c7fc7b8169355b372ce6da99c524824576a93f9f9d7f2a21cb45cb4f78b02116110f19d10a4c48d72ec79fa979e2b9af791e25b49d9a343ca81a3569f71526df6f09a8f52bec36e72767b9e67844d37ca0dba6a191101a1e983afbdf9aa7ac00b724fd9edadf29729305f6fe55b60e6fab6f8e5f13cecbd4fea51761a535082515e7830f4a3bb0772a0a4816f15ef7b0788830b45a0a39120def50b2f2e39a53b8b3c323bd929170d2afd841d162f161ff2321888ea10dc834cdf43b35443b681a099988705d4ea72ac4a4d1c4fc81e1470c81ccad1aa2dcde88802c1bcc87eb6fc815c9bdc6d676c51fb60f7c46aaaa0a8de9d53009970d2b93968bfc3093c37a1d0c1ac5445a248d182bf8795e0bdee7cb17344c30d0d589bab9ffc9e2609219624aebd9035247242d37104847096f43c1edbbfd32cd2bcd7443c074aa73c7c418c4f8f9f5e0948f7a7b15cd7ddbaff6f6880f6ed000fb87b70dbb79e89f3a273aa3d9628c65b72cfc5ac912201456e198a4660d5c526cda037ef18c97ae16794c2816317a0da7295874b332f3820c67a147ba77c4e3f0bee6e60a0d89835bc6b7cad2d0a8e2e1a1e5a28ff563704a99203316a0bc4bff1dff036df0096b0759e9ff1e28c347970e6c36c46a4903dc97c5f9fe43fb065bdcc47527146e8c0333ab4be8e54519d72f8f345c48e55230216b59ccf6c58f5f393d3810845955d40e19496afb85cfd7295f62455608c975aea7aadf0718ff31c93f3751b9c0f684bf42e7456a91c0d03a14b94ea7e9bdb3d8c16e77638a17d8e12ba125f94e16703d947503a7083d8722df2d6adeee6ee3e68d7de7c69079b2cf0b19ab8e7081f755eb3a0295cce7272846f527b4c192804b39c0b998036cd72c91df8885f5da90f7ac72dd3c86ec972f7e5ce29f1b9082ed9197dd0ffe0507d5f6e14bc4029831e7f174d2b9c23ac62ce1c252ecb80ddeedb07c28e8049c462659e449cf0ed9ff0c677e00942f1b27225486a453cf48c7caf8f6b6ffb1721c4c9059adf3da590b1831bbdbb127f8923a31921c9c4fc1b5e16fad46c3f23b9b43d5808e5be4145eb52a3baac25b89a4f9dc2b67085a95b0a0baa2f95245a1f39d8d93b447bcabf09310ab6dd0074c57270c9e5c575bc0d9612021211ca36b245b40fa0ef4fdd48a84a912d20cb5bc66626ee128653bb0807a4d48d00472240c5f28dad840486a0af63d9477ba80039725464030790c127718411ea169ea4ea5d900b40d13eb136c532f046be19491e7213b803a7ff253614c077394a267c3b9420cf5ed349fe040f6921683ebcd35f72451884b270cf981ca09dcb416617be3e066c0415d7d91c024f5e4c1de044e972edc13eaab2ae7d0f20b1b9de15964539736f6d840538370b22d72826bc58774befad9bed127a5e81b45abcb82683ef449c1805040b6230f3c7866aa6d312dd7289a26257f1a58d788534fe44965a70bafc89b6965e4b9f93975775e701a3cc1c9c0243606b124ef9cf0dfc8c71eaf9d970c7d9bfd258a96922fdd606fd677172d62e42a7985e93522dea8fd9b80401096ed6430e0c3d411dd4ff76623aabdc0003250e89a6e3418801d1a3826470b8206657a7ac3fd0e6cef1ed5f4ae2cda47297dd6d51b2106b6cbbed8e1d28b536f563564baa0b38d9bd75d9579cc4adbc23f10819daaa7d79f0518f98ae9cbb7af0d7b28812563c0df0a2b9c682d5f3081ff9d611325e937cdbae03351242060aed08484a26c4103d68c319938f6e7a3700954dc47cfb4bf49d8433571e17b9935c613c35aaff94ad4d98b20896327ac9728f0cdc3f85b7754fe5317267237b90688b274d9dc221c1be66e18dcce49f9a01c03ebe6cfa61858e3477daa172d31f18696f2bacebe9b522060ead174cd2a87290261aa7194e08d96403206dfb04c630df8e18386d9ee6009c5cc520bf91fe6f354f48f1752e6eb39f435157febd4d618b0b37e9895258392b64002954c7022a05cacd484f85f6cd0be62b88e030ea6a504e0bd36ad86de758a8dd1133e8951a7c2b06423541f66cd6968fb844ae4202ffec2931a58af2f699492ca460e67c72ba0f8943e9ffb4a59e9f2e47ba72c2408c483d46c6f50a18a0baf155ebf18047ad97f8b7c0635bca78b0eedc8b1798e63e1ede8fcb4ad7e5a9d710ef8485b372f12e789e8f4af2a80384f71cdb65180568a72aff804e8547ad64b2042bface72a443a5fe288bfa6c652eba203e8f9155be672b82c99ecd4ce0445b6707620c72a2cec8488a92b6a98360f61b6af999b6716a30bb384e996e8efccc3ed2afe76710467e8c1aff14ccba9ecad583766a3d73dcafef4ed496d7cca9ec8f1388f9369e77e6f3d0f84c89059041d4ca26c5cab477e405da5ad132d93fc46c36ad2b72450833836fc17051b66834f4de57a6ee60bf6fc1458c20dc3d54e691d79f3a727d3d0fea609e861798183fd871784530fc838180d320782c2eef6fc4ff25f772fde75090a711b3af23ef898657faab015b2f2670e8ffc10155facec84f237a72bb0cbc61ff42705dba08742b7fc907b5e1987f2195076f2453c0c5d2e51e2572eeb8a9b77c286597ba95d8169644c52782ef8ac969b8c0a7d816b7702623404f4d63f3f40f16fbca3d41ae7d477f602379047a4ed628931b143cba3ade7f5c72fea5cf11f927fa2f3158b9c1770b58c3491c922e5c590a7e1a4082927defa172e4515724f82ed857c70a517ab3f957dbef512a6bf91bee7ccc4940eca0c89b72dcdcaeb8b1ce25ed8aee507c414c2b0838c9151a019d0cb7f6bf96254544624f4aee763132eee16e9054ef0f4c3eecc3d4ac04e9c23ecd6cea5f0971c1f0bf1656242b83fda85e26431b58f7c86b75327e6fbccab836eeb4f3f99ccc8e6430100a593973e2800921c6de965fc3ca70f8c37a76776b356f7d4d862da52c3622726c5f0ed322bc488fcb86871ae0197156c89852150477512fd55bc14f4ec4d761b739709eca4e8e50761e7177b70ea0e153bf6a68a4af1f45708e208292ac8572481b55c4aacdc4578fe1ae4088205410b7326b5b11892dd90d2ff0a7a90edb3efc321e45cb76d91b4e304617285532c8af86e1095adab2fe16782917dda9ef721f3cbabe1129fdf40dbd89a6e8712a89cece5e8d494ec1b62054badb8239de72bccc590eb468626bb00a9394c75a183231c26a308b976039cbef4efd51a29b1d1c7cb0b0299a442586b89e72dfce60016c20415346bbfbf280a4c9336606a8725af951adf5deb50122442a7c33d9df7a05b3fb4d66e53eb1d34b3919d33f5472cb49a1753593b0f82083e3c5ffa9b16a34387464b44a423abc9e86794efddb72586dc4454aec3446a74e3577516988a6a9977523305b45109578ec92e6d70172875796a8c6d127007227d1ba097700f4c4ecd3de6c09a0ecff608550e716ee72f551a3ed774b89aa4c5467d1ccc24f457f77bc13cbc660463f5118547334cc1d97fa229fadf7450c109508c117de12aad84415700ca0181a8d0e2ca673982772bb055b1e096ca8ce72a4b6d29ac709e9bd4b969e92326c1301d4ec1e509341723651b32990a9f64195bb3808b76135576b4891ac4dfdbe6472512b4e0b99c369a1bc07a7f0b4e06a578a3ad39cf5f95bbb95e48452b623919ea9a3c3fb75f331e9a8861eb9d8ba28ac9fc4a9e94b2a1f11a9e8cc2bccb0b47beea3f83519907295dce752ea6dcf6137d1eb6feb4f7cab8b5e51c5139d16f55f6788100064bd7269cb8e8b336afe8952f20d2bf3fffa46544552a5924bc936795079b98bd4b0508b79d3c4603196a6982306843b79deb50b9f4d665d786cc76ac43cc159a65345c9f376358ca87624ebc097e389e2f1256601dc530151dff48fbffda9f6ecab38241871e8af686d784c8c20a932f8941cb13c300b45290b2fc8bc7ef88f51c272a3b8582c20f7ae3fcbc6aba5288d9c73559be654aa9bba581714d2bb1538dc72b181d0c704618cff25917ea7b3ff1ec1e8c02e545d246cae50dcf7afa3757c1d975a382853afb18e049b1499931ad3189f9b46af6804e25e647ad83045aa6f72d45c708039df5ea85ad56446de72a963928a436bef62a7300dbe2ebd75979e6632c06ae3fd4adc9dfb6e9950f043bea7d34f84a26d8bc6590c8736f85d6ba06463036338a3ae05f93e76ce08e1315515ef8d59cf0b265f8a3484c419097176722f87bc9ed658e75672900d88600e1ada3fd5f9759a86997c60b212cfcaf8f972aba0af6ac1725ec4709d4c0c10c2cde49a50e5c21482b0ebd6b4c8635d919d7234c60f30b74866041224f686dcf9e7e41436dbf9bb8bfbdedcb4dd8f4298ca725bf4df851d52b6c746e758ae5f834951102941a2e4e89ee07534658a20612e294304623095338a16e696c580083ebcaae4b30f0c60935403d9e33d8671657172f699dd2da66abd64a15ed55edb4e46c92a2bc8abe14b21385b0b1b59528eb572c1a4b68b09b63d8d00187379cf924872423a8b9fcef38ba0db8f5beb013e9a5be39b48875328c9ec0edd984d68c38f6900e8b72c41eb7160c035dd7e106709728ea8b7c7ffe58c7326c047d6cecb550c517705b45f0180d419b93e3155d7d660fe020396b82919d843de3717ce551c7c359491c20f131e5dfa475a8b2c0caa720f189835f7a23ff74ff11f519d8c333de098fe4ce0981af4eee7a9180d8855441ff9c31762378bd731eca90b2cb8c88c62e0591a147b9b7d51a5979a2230bd5c5f5430542eb0d20c4935bbd305376b888a17ae21f7b8c339ebeb664a0a10001071438b818345f313dd8f2e8ab01c99d8c7fa35647d1e53ec593c886f68406a03754fa758aacb8a0f95326b815b64999a986172368264f4a521c40989c3c4c172d4bc97a469f132fd79dc6ca4d0d4d83816a64f9616a5bb6279b0d85260b69672e88685840117eebf2385433377f4247bccbbcd814dac5eceb3eb9bc9470fa2722779f8dfa4ae5271e708910f0ac318733f91bb4e98c7923e610c3e91c1f3cc72ef452a4ca142715ccb6b847abdf08bc29fc7482b88eceac1100a7a2704e4fe729d6b283bef714bc19a1b1346414c256970d0f43b21f03a40d70911c5ec8cd85125d7c005057b017f8ae11f7f622a45e6947dd76cec8103394541edcf5459e2108dd429008d97b013f35a71a706c7701a5c3bbd67c457d84c6ccc0f2eb8994e72edc4c2b02abef9a37afba47888d865742d24d086ad98cc64b1acd76387faa833d20c4248dc55788426fa7084beda191e9ce8b82feb3a0ec9c451744425fd90721d6af7ce086ac0ba1569e51aa46b13183804ada1c9dac909ae7c3db27a91cf7289294f045bd5d4265dd63c38c65f8b111680fb695afd3d651abd5b2f40f10f574f762f663446288b74b312d9b2db336035491eb927120bd1f05c6d6c9fd402239921dc8a97a0487b84b3b5a10970b48390c50daefef8c02ad202dce14e164072051cb93fe3f0e09e535c9db537fc9577a7cbc59598f5d1199be95d440b3f7e72bb35bf4684e83450c4776bc72cc34a5fdf7109f6ff45eeb4fbeed91cf8b72b62569a1632739e93d2ca80d428e966f5521a4f1f9747a4f39df90efb2601beff23cce25b03de736ef9ada939e39a109cbb603d881ddb6950d590049fad1a1c62723dd18d35d2d67947dd3b29ddeac6c084c979f43ec831e478f0f03602719fdc5b075ee11189a1a191552df8b22bddc741d0a8362d5073332a9bea70338ac87772f94e2595644082f9061977961d393d0b080516bd3771cd834c3aad82e0c56b72bb2af58b5300db2a7d499841683ebe97a98a0250a2282219a280a03f2aac2a0118018168d9f4ae22f9440ba04d9cb6c63c3a0ddb6e8c9cdf0aa1b3559745105ce4c2cf66909dcad21015e94c1855d5327e6f7ad432de18d46d092e6b30675c60a2aa0a64624b02bbec234f3f2c0ca229ebcb883eb34e9c050629990a30a023722c1366ec568161a89a7b0b44517b48ba45d5936e62389dbd5f0f9272e619db72d78a3722457972e1904eac5e2d875292f0e05c06f2207150064f5a54bd972572aac1ca645b11e3abfcc49e63604ad402188fe6be968f774df16eb484c1db5f725a14798f809c670131739bd5b7420a9efc5986cba89a9115eb0b80f92f721e72e448fafbe9aaa0ca7de14c02bd512423de209aa9a6879bae1b6e979e65fa6a45f18a011a4312179bca4e4da3d0bd7d2030839c87183b2e711a4f29b99b6eb7723ea3163909de2adb7af7374e0ea1c2e11bbff30945b8a38126d81547dd58696cdbbc51ac0bcce9908a0808c346302e2524111ededcc75fa0b5d7ff9027b1cd5dcb84ccafa9a2b7694c141c9822dd466b4875029be0887361f10decdf43c10858137afac9b3703bdee3b7d0c8da84f02bce013ea42829784b5de4a4e0d6217e5c23b49daeeb1bed77e196b8be3727e212074863f23a667b3cfa6342f18ad8fd726d97b59c4c2e05e71b9aa56c49aae2a1b0829417afdb8f3173393d53e8e8b172871e0c2b2e1611611c3272234e731eb00c0d1f96113892f93f9947a772684a2f6a8a63b53dd568f48a57a9c59ed777916823748c8ec6a8518e2ed03bce0cef1d1eab1fd319faed20882b5278bbad43593d53d29ff990c20eaba589d5340e690d5fae8aa0991fe8f9196f5c1b4e55dcd2ea1ef4c1682e6237248950fdab766a72dce4f29108a1a856b7bccb377bef3d1336cc9841e3929ff4346eb25d195a5e726c9dbc097f12a40279237f52876e6ebdade8d35450f751c3cceb0fafd6079e3540317341c7558ff4c6a9ad9d3266df89632d44c7421e3474576a578b234a13527ab952b7686a055404084b3b7a3de77c368c5020edf2c9a4b2485a58c1674472ed68c6b8f016342763403d9ba0fb5ac6b7d7ffd52db71fa83f078f5f4a2db772a4c7b57e31b714133adb70bc8fd9ddac8f14979a77db3c1a4b1cbd00a15c9972b031f8cbce94b9dc1cead57797023bff6fa9b8b26f565686d1aa0f24891b2f720c9998b8c6d877703ff91d8617f2ffe2f8f5cd17985dc8aa22f03dde302321348a3e65c0b88f412f73d89f134171c3c949f3a235218624befc90f85c7e891f0c8d0c2045aa23e8cc0d62ab14852d84b5e300d70094d86c680268362aaae1bc72d840ea8661291b8c0fdf373dad36c0247e21f3cc381ab456fe83ec41b483e97265fd451f7cb979c8b723454c611ae47248ea725d1eef7329a4c0e71ca802691983260e8969d24262c75082be281d0ed24ef92e3c2ea0eb91ebaeeb053c445372b72f8eaf69c6af7712b55a4b616ea063666b033f946f7b35dc927b42732a6b721a4934db71770f8837fe98dac1791609da59c6f9cbd11b12003060762fe22f72f0cba7d27d595a8535e0a619f94a6eff2c74d915d8c3f15f9e3f030a7a2e1a5f76e19aff0854fd83f921fda7493273f418399603f3a025b35678818c7965c5720148f65e74799a8e83179e7c4ba623b7a2798954be2a22bb863a7a7145f892729cc4e240ec3af856349a5b559cc66f0724d194816bc9e2fa7c65ba9cab9aa17283c500656f659e615a74de39cadc0451cf7eb3916ffc12ae068ad02e105b3a72f8e6b7d9aa1e0d5f9de5aad4b31cc2f6634e58020b8029fda2d1f7643e3d3f72c49926e73d2b1fb9459c36101b2cf77a5c7337d103fce14e11ef830f904b63551c176954b3f327d1449bd0c2af96f91293ec68d53c24509f7bcc08dd66df057299f3b7c5833d72a6d6449baf9bd9e84c7300d719431e49dfebf515cc422fb93e317714d524ce24350bea0a4f7713b10d26d160b1250412d4c9a27a8ac0f1a15add0234e0d5e24f8ce549f125860300ba50f4249f69e5da69df9c8e4d3826014b41574c9273e97b893ac96e1be00c5b48a80097480239f75524b1e2cb6477a27221f79efc6421f096be0f0d09c8ca126e4fda540872b564feeb63941314277412adfb1d71528eab7e0de5a5d4b4e734edc8a9bb457893ed03cd0b71296245480e0de06c0d84afff68710acd8b7d9f44cd5fcc436b3024f7c2b6e493640b65d4725753c91811bc38e27eb3af4630d2005111106d2fdb62dc1a9a7194faa81de7722c76309f1301884fdb5a17a91922f8fc24b8e982bda308485ebda5d13659aa726044d4cba129b1e3b5c4ecfbddf66f0a9a7df487e1bd5b537c57a861a246680dbe7bdb2baf2691ab6c1c282565c0220029875abaf79356bf25e2c54351d434528ac252cd8f38e80f3c9a2bfdcb9848566b723c1b873060c348ee83cee780df4833abad7b0b4b53dc24e09a67dcf5c49e0ff274d451356632fbd0eabc762e5a724d09ad6f68634968d6dcd938575d3075153a06b56cd1aa68a121d1c11debb872aa041b1c966a28afcb2a8a9ef30e9b56ae2525b9b7570be9f3caef4489f8fc5d2f653c30e0722e1c29b58fcd5ca55e42dbf4c072ef06c53b19d5026dd9c6b0721786c52aa5c2f692116aa9b5f379383806f0c91a0a667748672f7dde0cf5a665a4e9ba4d82db7beeeda9370c094ac16adfd0988b194afb00cc02c929d75ed011f85eb9d70ae3a06085b9e729856c775a6df6052d2c566f8b7bf90467771a4b722e940a5e7ec36ad1e4fbc2f6676812c6adbf7ef3777a6a296b74f94afcc32656def5c2116e57efbddbc3dea67d36889c511541653fa472e7430b5859bc025d4c5c745b070eb7d57b11f843d45d83c68addb6988a8f47ffd7f3f321dcae292172a6191a767286f926dffff20c27c81ffc7c52e9c8a28bf9b15b7d64e9ed8a3072f9699b394d817331c5d2e7f6c594d3332ebd56e99c49e41dfb87311db58b680924dd28c5a4eae21134c133a540f267fc673fd7a54235bec55adb10a10e769b03ca60cdf3a21f22ade25159f9be6cc1547aa6412c074188fc6b0574f1e829351123f15c1d88b8fe6cb724aa19bcaebf108299a46ed57d36e254c0b7d9a3fa85720b13794747cba338063945965b53c6712b4ceb8baa74ad606bd9795b6bd99a72917d27fd9ead05a27dc5e8a5ae5a5cd6e159ac17296001840895b1bbd13ef80c588bcfb37169dae6f01a05a4039b81661f554a434d247cc2dcc776171070237298a8a03c153df979ed4a87d20ebfd3f9fe9ba05d67f7738ffdedfc6ca4d7e17249726313d075129561a411765f8357e1be88f2c1d8ab2efd838893cc12bef5720a63c23c6888d020b2572e137f5831dbab455d3215ea6cc99d62640830914e39b4436dd38ac0eadf93ddd2bd1e7c10746a31e73fab6b2c7a923daf6479362820c45df175dd5474d93ab1b707d1c5d35a0aed7779bfbd6377cf795af68df5c772a8d4360ff8dd1f18c8aed795b867d6d775f1e716d9e214a5beded012a430a972536a15b1c8c24d6041710aa7338cddd98eedda81dc2e52f56e559e6661ac84729da2206b40b359a8ea8b42c523ca335f84d4df66e39b8667d4cf943bd57c3e7269bae984e4283713361412e9693ec997545f0d78f12f317278c21755c7ef8972d620a7dcbaf7277718ad15078ae3c2e8927d13558ab422311765e75401309672260e373b78636b07d6823a470fd45993b9aeedba3f37210f2bb85f4d91f8c4109810a1345645a019f40dea8744d22b9d6aeab698d73eac31bf6a5b88255a7f7294df454181ffad526aa3fbc538e4323c0535358d7fcbd556dbad3db17618d672a01d19ccdedfbf7f9a60ba7067dcd7628a6f91c6cc89b5777d67b138e4867c722636c630275a3840a3fbe61d8ccc410c6c6972879ea2ccade7ddaeea1e0e14164650820e2ffc205526aed4538371acb394b6ca606d7b8be1ca6dc75d731ea672fe2e4efada2b812364a2214d49ad5cc4e996c6393abf7cecc6b35a6276da80541f95b59244edc78d59868b7fd2649e75e3e02c9bced295aaedb7cf6032d8392ca1d7b4103aeb428521912531a050cfbc35022e6a1584e9b8113486f5beb608529a92bd28f40450d7a1f5da68fd7ba0c4efc1420075d89c1af73bb2496e06d672e4f7cc1bbfec9c3382d069e06e02769a30ec9052cc1ec915f11ff4c05b0cee72474282a5ddf9c59a8ac5df7f27ccc88fc5fbe4ec35ab517a4bff775dae5f105da9c210b679ab86b04477c2ad36447a09e3d1d1801c09f5eeddd6fde2333b8d72bc6c6afd647861ee9d2fa403949af885234d1b1be5519a59a7950c18f9afc46f461a717fd611849b5a95279871015de30a023139d8e4edd85ea14f31c712bb59195ed7412e82490d589bc82702955843d1fe264e9b497779b2cbc005963121720380c5435ae6dbeea25150b001901466866b9807fefab9e435152beb466ff7729fd03ff4ab9ae7b2c5d6d93b54f1ec459023bf6992127ded0d4a092a0c18da72202eab6d2047215b377e61c0189a10d74825dfb44d9713fba7bfd15626967f720eee91b992678d77fddbcd87fa594daa110e4dc55515bc4273435685dd01512fe6df99ac7990204896dc27de8037314b7e7fc969b6b9ac4f9107ac204d7ec87256f810bab7ac1307ae37d481cfa29bd07a3f4aec9e1e22192868bc76a37aa9726d1cc22bd6018613f48949728d8dda855fe00637c84ba34a1c704cc9be85c37203bed1630222db93660d669d17462321c429aa857a2a92a8b30dd2f481db6f726ac60375162943f3eeec1d64d96bb208a81046c4b1d80b6218917ae9d843eb7205c0e245ddc176f68041baaac9297f11b5fb645390d809bea7deeda6fe8ee334ff1cc96fdaa1aaa826d4b8b807005124496e7340ae426dbb328fe532f0c6e864c8374528590f0de168ca73691d0e3ce615786daebe3f45ceb44681ffdbd85772660ab84865985ffc93b3f9e79faee739fc772d62878bf9e87dcbafaad3c4e3722e8e88b4f9c2d7b7b1cecb54c29c7c78541d067a7a44b5cb598ab0b007b9a9728ae77179bd88ae24cde21e9662a0cc6a0c9385bdf8e4f991279367ccccf37e67459d5a2c3eb92af5a37d93206a950fb01ae9e53451e115474c7df0849c858427019e21d5377451c0f6e7e15564f39501c84a4b1f70307031ac63a9cf3d15ed234f9fec121fdc69db8a6e44f96a382a2f3d8ec6865a4cabbf8bfdde23eaae757237ddbd5197c5427c532eb0304d787a70876b6def4cffb4c44f3a8081f3f0d83c58ff201a557f8c42137e9ecf357512f59d3296bc500fb90834c0edf740f5d63cf567793e94a1b12d93ad0f78fb937956d3e9e17754ad983e2dc79ec658d7e16bf7c3a7277de9207f72724527ee134fafbfccfa02bbd5983e4c51ae521c2c3355b686ccf302e68f7a22c52adad0d7e7b1c1340f975e1ac843e0e97077d337be72960a87a6928f6c5edca10460dcf3589d75ab8a7229431f9def3edae6f4535172a447ba4f1ee3154623b85a849b66c39da34f9b2b16928a80a0b314fd76db8972e07b04516cbc70f7e4d3221249ab64851f516546377750cc9b07ca05c6200a7292017ec84dfe8f213080ea7d93a1ca0a7f030506935df159a63f1720dd32765aa7130ee829069ac4ae9ab0c8b859382969010568f064822497131a3aaa8c045df1d018e3ba6edd2714a0fbd493be1bc7c59697db1b986579613233735463cb400973cc1c99fc5179d58ceab835a3b5b3e761114127ea6345915a9c1201af5d725e3835376b001ebf27601cdef3bfdc21a1b8a3fb36083520f064d173dc625972f03a712380d9896b24c2d60d218a5c7534a4bd249abbcfb445935b03a21cb572f28fb25121a96e2f4e9707b45203cf4d749fa2f061c1b6e4188a8b72fcc17953dce722e4851fba64fb2e99b74e06e32ad6c0dcd720da1f4b89e35d491b83411c25d88815cd929a955f2965edefe226071ee34656ee018d4841c91795334ff80a3ec234596521e8afcb9cb687058c8fc2c266318d3e67d1145be35521444bba22883fbf72bb9e89ee90d09a9ffe03f8a66995730032dca7b48708aae015f637728be68425a8c17f132678e5229ec9a8dff4412cff6ec1e3f87f277c8535c9c772384f208f66a9faac3c4070b1f437f8ef2d9028dd0bbc6901de8a371eda37af72f521f1ed2f101c9414c95b649c8992659f2cb2a9810ee88860889a7c8683c23d26e0a02a034f1790f6bbc70c8a738a9598ca16810935b110cb732961b8ce106aa8c8e0d63c0fee559d529fb31ec19438c1b38d90041a61ad348c6a64f9c1411dc14ace2034820c89b7bb267994ad503ceb17f2983d0f2a6c7f3ae50e4047e75a3da47cac88d8fc96e91a7304cb54ffb30b8e733c3c6285c87867e83cd5f538721ec3e1ec2057de09049d8162747bde2ec3c6d6410bfa6e330f99da403bac4e7264b706f1a1a3fb260072ac397a28dd816e390244ae4f9530d60ad938c4c54b72a6b97e85188ed5db7d797d1dce71d5e0b83a1961e8d3104d02eb8a9b8b6d097263e333e76ffb7ed3673fe2899d9b3d7b404f44944d488d63cdaf0b6e1a4119726a219c6bd6ef244e40c7444a99765204621d56528fa3f35f870991d96d39f3186722ea68eba9668d7f819ab9c739b6aa426ca270d219b3ac3ad3ad42395bf17250ae9ad29188b406a8aa044eb51bcf6172219e1d0fffe5b645373a9d6ac7b13c551d7d749843dd041a97f6006966cb1ed91bc6b67827902e2a4ed0fb7c05bf7219b0641e5f2619287b74838455dc91e0da12932e0800cc4d7bae2db1ed78947276ee1c09475976961b5e3e0fe246d62f64bbdd3536beab32de517e8a2f126672d0c7e3fe77c88aeb1444817bc3111a9ef6d0b3b0264d62f7859ac8694e262972a5ec0f5bc3ece6b95b546c15773af24591e3c76129c1214954abb801bd76f672ba887c89b9a0dd8a73fa60ec4f78630a793f006f182b6d73512e5eaf81aa2872782d6a3b28c438b615dc7d84ba371d0c0b79bcfdab406d8085903b98b7466f72c93ae378c428353f91b65372eecc8107b60c03e610e25680267177ca9dc60c7200d0a9eb3701cac18984ea25e0b08f54e0384986bdc5c091a17cad5136a53c1c9db5f2c67700de32bec14ef80fee512c3154a7c31278293598ed44e03193c8295cc356a5d8b22771c6ef4c88cc4c1301993e43ca3c9a2f12f143152e4407635463ad2e7d91ad4c9c444869b622d6e30134a870aaec64d96fa1f6c6261380a57270f31ce8ca21993f708a374097eccc3066e7105744fc66e8d24bdb623d58a172066e36ae24238e2ae7a22aaa900fbf0f8697d4d660b195e7fec4195c31a06b72a1c3de2703dce70d64e75e278f4fe100e97ef65c1b35e1b67e5540f73ec7f4726ec142d3f13ae923e0e8bacd6b13b8c4cd7e9d8666f4150b9d6a9288563e7072f77f7f43031843d3228939da03f77d4e5510d690ec0c52ddaada3e08968b9a72a7360f0d289c5e27745bd7bf2db0f47ddb199e80e6f62d256f285589d44d9d4414ff9b9e6abb43c11a34c620affb11a39e1e43eaf0099f9ad866de296bd6c2722ecee20cce0ec6d525ee2f260183431dd9a5bde106d81e31074e707522bd0929b3fc53a897197a77e5af448cab90c533574ede41f22df2e16d55390149687b72bcf37fd20fe7eec624cf8364a7e19076f1fe39282d87331e9e56292cc3b51b3f3d7f8d29f7d4881e8cd618fc579e7c97446cf3c8cb32f6bcb4263e3eb1b8b95dd7f09da67197a16691c15337938c228596f1511c46340a6babc0e911f06c1f359a8eabc8262e86a6585e4bf69973d0ba72c0247a7aa55a77111185ecdbcbf95b6271343637bb8f3cf2902fc5a07fb17d372f6c9994d0a2c670efb1351661307263abd0135c44c3cbc37251a9bb44c5d421e88a2583324832938d253ca7b2a7066f0fb9378c14d1165282bb50399ef875a9f60768a4924b0917bf1b2eccb7254106d0f4bbd5f2d39a2fb4ab202e5a5d4f99d7e5243cb32522e796835419263d72f034ec1d5568c32f28e90451209cfbaa40f8c85be62b4dc5e2a91f2f01e9e355cad6760527bf1ad431bf4c1ae11fc74ab0c008de322c720c89aa7dd3476249729f701db0260042cac56273721dd5bd75d2b6fb746f2474ba2e32cf8ca57bcb1de54f664da73a66ff1f2ac7f5f19e70eca3dc4bc83adadfb2cf961b4c9bbfbd72cd43e082230678a0f67ef82a96bcf8cbe7fc4bee2fe4cdd065b437e60edf1c72e1cd4542ca1c205e5dc525d62332bb4dfea30417f170a548c6c4673049813b72eab84a97ab2de666c1e5bed35a413835dc8991691fa22423e19df7787ab08e72c7d9a383bd6448aab3a97e440429826c7ff874451a3b9650c5515b7b5a30940553bcd7fb252994c3ae08f652c053cd72a8fdb4f8acecc384b8d0b62090ee456982392bb5ff23a52d35377c96f55968c3ee37e0a3a4ff3bdd2f7f6492bd6ee615ee41f576476d21e17691ef4acdb8f16b808260cfe9eb6e0c019831a17e1bec7225db56bcd1db4c0fb1377c7b023c98feac94c2008bd0c64ebe258ed6c4c210720d4dae4fac40f987632948cd7d530000da8deba74b80876789771fb1c7361b72b210f2e8fa5f2d95ad46f584bcdbe35d33785ab634213964d8d4ca31aabc671d6ddec5cc2891e8def54934fdd98166d51ec815288c891838ca99e4208b5ab67215fc25f58117c9865f0b5d7b2742476a53a83af8948d4bea59b1a88f43acc7726becd9e378175d8b4d17d48fc2049fc553cb492b99923db1ccf2aab50c8ef20cebf2cd082dd4799de69ee63438c07cd0e284d1117d98445c84ed633412a2cb72da5d33abba471c0b1e2fd412c2b14a0ec1d4d9d6eaea48afdeda897d337a263e1d020ef23be041ca3574a94bc798ef3284c645f5af8204188d871cce586c3b7213eb13940857d8fe5e9001aae08e592db3181c1ef3384de30f4a5fb6672e90721e4ff47bb060c463954307c7635a408b4cb55b37b0469919d837045a44862045659f40267c2b27fc4dd539eea50f843eaa8dffbc8dcc0d734a834b7a17643672706b16788571581e9f91faec823741014fd8440d1a4cfb38cd08683faaa50b7205690f975796043e0cd67802076c369b131429ca07e0c79c2784d0421bd07372822e4ce3c6e76e68bbdb252c9994bb66012dcad45aee3d45c77ddbeb07479172c66a7648dbf3c7ae88c3bd187fcfbaa2b71c193cc752b15b8fcd9b77cad36e0720c9e575091d9455d43f32de792f88a417748473dc49aa1a666fa2d352453a3ccdf6cba519aaf7b24cde55f24d8b8ff8d6cd78bddd16faa18d5506a2ef176321bbe1bc1a85deb2c869226cd385885853a92177515abbcfe9788b1352b1ffcb721ffef0bc0e7218f4628b81b5eec0a23dfdfc27a3dd6360a99267e73dbd1d2426305897b339bd5149528bfefdbfd65144ebfb74c65a92f4dea7e24b5ce861c86dfb09ee810c96aad397677c82291e98651d85255c0c4b0a67aa6265a3a89ee45f90f8f2b897b9d51c4bd02114bfcb9ef69a77f39df7737995586be56daaf3b70064fe47d6f75b4d46e065d407e1ded067839866e674280a4473c9c2dfad94f74c633911fa3627dac9ee3177c601f8d41ce9abfe8a35c800f37a69556cb5ff10472ba71438e5e23c86538b91b8de5c3f8ae668bc52cb1e58754d4050186e0059720c29b1fb5ffd45e7fe05ed9e12dfad8f9c2b5aea4f80c44437be7daaead1aa725564ecd3c4819559e79f90109e773b7eade55279fd0bf9e0dbea90e2d8eb9e72c5e7c05993820a33fc447edbf5e66e9649379c973efeba6e8b95a11c5d7d3f0afce69e5a003c8125341845f34b27ffe56ff1d86b6f3b9719656478b5ba20730e069a5a3dfe33f0bf033e3c171b766e41ec2853254b80df1bf112a9552a4d8872e07b6aa195dd1231f54977c30266503c967eacf4a79e27e42459dc1debef693fbce1fc4c39aca389558ee0ce631177e9b0ac4becfa437ec452fb28218606cd4d079ad848c5575897e11ac2d68389f5970146f407e030eb609256d6c14a047b1ba0731566a084dae0a6b5e0d22d3f54f63cff56ce6a29408c213fdc3a009c2f7244074f6b26dd4e2385bf13b0323515f5391ffe926e881d25862bb37bccfd4f427bb266edb97d973933a9f2614b7d8ca8235dbfdb365bd3b7ffda4940c1412f2e00058f27d658d50fc5a9c83d2039d5c3ea6086eb9582aae02967f9bc8bc0487282433a2b93597af4a43d9325bdbe85715e425bf57090b34193b4f5fee1fd3772128157794a6447acd03423f9b72496a46c16f5b8ab74cb01494d95e7ba9ce622609d7a6122e8f225248656c93784b99560b1a5c3e2c282a753d6b7a3e977aa5fded15b5c9754bf879623811921d1cc416cff6277d1ee54591c2adfab45d0aa009ff6f3c438c5cdbef7e0c24249a3f2036cc99310eaf62e4fcb4af3a44ee3387234aaa0d750cb9551f7dc81a365cd989087cd2da2465112dbea1afecd4f8163489b39086074a46a750677204ef284551a12343a0fcf0938ab8d38e4517905a3302d9cb23bf7b081cfd9ce93f8b3355b803ebb1ac1596541dd7dbba3d820773572d5e3be5b2d0a28678b05eb16fe054cdd6df61e2ede1e51ccad2b0a2cff646872f7ee7dd3a30f238a5409677f08bb4e5265a894d8ec41d1ec2f92cf64070cf5720257dfd9f7c24ab4fa8a14261426b6dd4b2c308f5965b9f34a7bf5e63a3f023674ea007a5f5bbccfab340cc4924b25368e9d95ef8ae6347a905b99d4360381729ace0377ee25a79cea5a4803649df6f286e391a9f2929421f831d0e3efeec83a501714034bb7d1a1647bb4acc1bda7d6683f35edc334d2aceeafb4d538f6c272d0171999a82fdfbfec44911120ee3898ed74a0dfc679a0f3165f1150cf7c60725a1ebb22ea450668f860fa8c855724222cb202f65386c648aa675693e3e08f72f0faba455546ce13f5e881debe7011946010904fe3b4c7466711fed337a03472f4929bf50412150ca90a7570d3d7a31b01cc323f0af1b721a221c366ef7d915437e3e02be6246ea3ae71ff1831e57243b5cd33e62a736fc77bc72391f91ff272d097d049665f9ec8e75792ade1dc9ef0e0c64d1fab212213cd1ec43ef43ef572a2b77fdd43134c634029330d3da98b034a99a95f67a6497145cf627743114a72e202a3105da28bb88af342ea2768d980151108b4eb3c4c03945861975cab8554da3e1cd6ddcd4da702beb863afab7a8cd32e9404fad06a194b1a7f8d7c739272231f6a8f54fbbf2c4fa91117f78b703e5ce29c558592b30b425403dbf4590a72b1e93817d40ba35b491748cfdcdcb379d58be1ffd542093e235060532dd22d3c526749e9aa29a185e1e5c065590b2ea7914d32d3a795ee766ca4d571ce1b5143024e2162cb804ab9a869cfc690e6df2580e388b1016f58817409e52ddee8e515bef3eb18abd848b5956b24662690e93a976c6299e5b20952727d5b8231ceae72cce22ee805f81cef4bf22b4e9f56ac2d29b05d7486bc8be2b610f0e1597464681108f9098966ea8a4541e5d4b4f5d5a8fe4734d3fe5fe63d7777374f8bbfff7237e909a252ea63ad99d8292e452eabb782dd23f3737f328e8a3ea819cc70ce72e4765424c38bffd422c55f68b598493344f8044702c1af5a52c084d49802683bf6862a2240c93fa0d9f8844a6b1bc3428bb1ad7165880bf3987cfa2c502297728da9c1ee52c197e807fb773b7dc53796354dcd373b91104c275170ce5e3bfa0bccd4f532f4ce3029fc7958c3ecea59df14c2db3e4a93425bb99a14c425406516756b6009e69f1557cb03aeb014b4d95b595fe4c12062c53a0692b52836d1646795942508725f12f303a1cd40a896fd3ebc042e7eaa22bca9ae5f5e43ff0b6c69e8e512c7b33b783f089e14968cda0e7b4bad5a5a1294fefc1faf1676d292af444f12c88c7250f83a24b3a19dc54431f70c62f8be065a91c51b2292851e9334057331032d129e89ae532d6e0af282b16f8c1fafdb3b91eaa871bdebe3fdb5a57268b8ba2ce637743b6ff013d56a5d81a84360fedea1c8ad3e191e49ecca186a72659036867223fb6c31d4c12b602f2e8dff48a7207eb77dfc7775a5a97882b372aa237a0b6c691c4bea68176a860059d78f2ede1ac398b33b90eaca2a3fe1fa02ffd1f11067f6b8e1d6aeea655b0fbb606442cb5d3270389746da239bb8b453724efbe76ab3df973081e6c02baf3f6b1686a7bbee6567e2dc471c2c95891a503d67936906871d2f7a03667a34d7b1f42cf93553110c01bffcb97e16b5bcc4113138996c07a6ced448c973f6ce874c05b0a9abf7fa73ee5689b9ea6a9ec2f03249d59cb0057e0240f95b11590f7eb17958ecf6146f8e4eae034543ca14eb2f044d51a03ae17487547bee0d7d3819809dd243c6eaac8e34e5d37cc6366f6c2821320793db379e9d2a2a2024863e03fb3cbc2118b77969e852f251a97c6487baa6720feeff07479a2de45a8aac9c4b8fb438c55cf968bb5c357a276360f230fcd172ba4c9ffdee5dcf5b8d793db1cbbbdd7372e8548aa44736e164e2058e29e1c6723825d5d3f7089be79aae07e70902febd3bb310ca67d0507caf489c0bd1515d72bf345fa822b8dabf4c32636792977c96e6e7e95d7c3d08a5f5d7fae05976ae72436a8930cde3355ec3f5459ffbb307ce1eb9e443ff096591401d1d98d927a472402126a4917ef0ae2f02785321e0ed1c324e40379924ec1979e60738a834e472c25ab986e40929347d5334ff5ecc94593dad13e98aa53922d6fb4ed75224327219d3517289062fc1f3764e70355ddad5af9cad0a7e52f08a64d9293e2f1a4b721e2c347d6f37a856d781aa0a0f1a6da93c946c6719e24a4ce2d705b8dd4c9c37eea22c400efb9f0cbb952a329527833eab6a42f16ed612e0754addcf0436ec2e40fd5e543919b9b8bdb5b50f257cea8a7040861a77b1543b5a8492a62306323002e1912bd1ed6e8d2678fae998fce9f22e3495f5c4273a4e0eaafa5fbb2782459b82e883038abdfa9e3859a0b95db105550540c47a74a21f7acf3a7597270e72a2b4011727f19a77f45737f92ea68e9b64f1fb98a905ef2d273e4c0e727f8a7227c6c635e1076fe117e4ad3bd6b49a38762a3500b8ed9ac038f9085bf6e8df728b17c2164a2a94a97a68c80b804f6daab58c5b92377a5990789e886e6ffa657162baf443c756b3ca21cd4acbe5f8bade5d63df22784db1503e4e8c0457d6c4724f6377084dc533c7d85521442fa14b18a14c90a02e901901a5fefb9271944672847131f38c426d53217591e36574cae1a796e614b49777aa364fd5372abac8729c90ff9999b00b83ad11aab27a3e8f22cec2f25ff92e354da3442795ecb96c7225ba483048fd69a80bff567f79a4e82472775bbb88d991d81b89138c1922222aa5196f4e844632d2e5df1b625b54fbbb85a27bdc01609687ea5cb7e99706f16557447546cd5c3ec2b56026aff61ea170c5ddcaad04bb3f8f600c412c27c7af2eb1c52344803558b5a225e13f754d1a2794699d1a6cf12e5518637a8d3fa62a72efb70d8dce8c81b46d2c7bd85c5a8bb12e1a835915b34e811f1c6fe5f350c41ea223889ce2d20b353b0b129fa221c703478de1a454435b3ff13b1bd0c5761d47fe9f9212ea6fcd3104ae1125093417d3e008e02d1339ad08dab71f655da8ed38fb5918e0c4286ef07a3dbe3a1b44e681beec8df268e07857c69c7bfa00304672c5664435b6ac2bac3fc5aa7af6e9e7a66189b7f50cfa74fdfd4b5fbb4491e01f4b180fa223659c75477fec1a7898d2466d8568c594b00d88f91f617afe0ca64c0f1c1ff47b24d5e91f8eadace87a2cc9768786213963babbcf917852afd370729e0233c0f9e181bf268bf64e9ab926b26d657a92df5b9218cb440099a1bb14728b0f6bc9baf1c3b1dd662e0f854f83b2de3a3824b653d8b40fdfb25b03bb1b721c14302bbcd875acaa43316bc3bd43d85b9842e473ea1b6c129c20281eb16e72fe3404bc161de651401923ab7b5272b98d537bc1e20f5ca424f84b1ea45756610b2ea49a037849a6fb32e6e082cbcb8023249dcee9bb8616dfc45e5f1e016725d256944f79f980d57939ad6fb537985f3c04df25638dab46fe9b63d9c0832772724fc832b79b7cc8665c72671df52f825dc1ad2d8f14e1744b424c55d4c4f5720c4950a1944715106a9a1680e25ba3e0311bbb2e38aed850cbd4a5f85614a97210f2ddde966e8fb595372055033f37c71e16dc5a72891e01d6adb3e37733dc6a134950336f4eb3b61e1327e82997cff2053620917701071995fedd7370ba684f249bc7cd371e6b4e3ab2d53a7db38aaa33a7f8d3f93dcba9f0edce18d1d4da72f047d22f3ceeef70c9a28eda336350feb9fd470641cc64fcd6437cbb74f801722639fb09d0ac2f0a173233145d94ce6febc51abf80af2bf0e0c6c539115ba54d33ef169646f2e245f4bcb586e1979d7d96673cefabda4bf8798c4965164b4572a423348ac349a721e0e9e48894dfe9d2ef2ab0f103fc824baf0f588c53d124725dcf60609b441167846e8bf569028f3307dae6b0cdd4b42d3c748ab63ae40902cc2ce3da997bbc3a35adca28b7877a5e37d6798784f7e047565f83dce0e22d7206251ff9acf407f129afbe552d3f0a0a3e1ce134da1bb3b18805a1a7cb22e57284f2db863ea7a5a14e1e9792aef41deed4bb5f208ceeb2f7017b357b69f852726057acf3083ed1efeafaae72f223739c7bf7f4ebc0571be366031d299eef6f724796667bdc10ff7fa8a04b5aca743843dae280a30ab4858f726eed420500f572dd4957b307256d39f47a21da79513c05e9d99a630bdb7ea5a4d43543b3e6c272501535edbd69c87b8e34e74075c17c1a47c50efdf35b4ea8cfc53bb3e0e15c51103efb3e1a1a5630bd0dc9c8abc149464e9eef70219f48522f6b63cef6f7d972c6769c58b99c377a22548f97d9f4e9836bc19cee5cf8c418ca3628f18af75e0d36e3ef52f6696ed4b79e2f26b2bb326a42516a7a69faa3195d5a50af59a4cd7230a7772677f7bff9cc8c3a8336638eda74071bb074387912598aea26bdb53e26263691f706159df5d4cbcb12815c97a868b50e0fdff5c0df23c198fe004f44418497e0b5639adcd9b1adce25447b27d0525825b6ab9f98e9e81d5e49ff7de372473fb1ade6c646508c2714a0f71484f54cb5d70b2728fa471cc46b8f7a5fae0e1794e8030d1ecbb7a5c79082480511d22ec138bf5ee808bd4f704abdfc048c55d012bb5e43ba39de2e3d91c74337c525c8821fa19f422fb292793fe9bc26ec7291cae084082c54ee70bb78ab5e45616c273e56cd1047c505928f02f3dcc5b208b7725a8c939d3afb3ece86731cda1ff504a9391a12b7e2f260bb8f24591a997220851afaa3bd4009155e2ff4aed573588d1646279dd2331590ac63830da4447250420d0da522977c19fff98a8d48b05745d60728eafedc27218ccc2d3f228e722699715d25e0256f0f4344de54a8014f8c451707a9fd3c5fdde2d19bb0261957c2120841c6012f32670e74c289811c672f39c3c202e2b753edab087c2956961bf3285b2ac531f13e3e9bf6ec1d8d29d622c6b27a73bb38010a6a52e9d4d30372f6919ec0c7bcbafbc8b1a83e9666f9e5419ee63bf52dbd5fa6f909eb110b9372d83cbfc639dfeba9cdb5ccb81cf92d37675d1efa46c86a7eabf490baf975cb5408cdf3d368449dc1e4a3818c9bbcfc61605864a1e8bccff4248d0aa635ea9f12976e5401e686ba6782b97b086f5ca927763557076c08184d08689154e19277725bfee95f42b29382645767ba7c69e49f330b3e037e0cf4f348d77121926351246cd325052017b992d4f40d9862669f1e4730e29080c861de4d34d092d8db56728aaba3678162ab306f407591fd068964e7378fbceb224f5b99ab4de8ebd443726a64b084fc4e8804743d9fb17fbd6140d06bbc4cbc0e100ac2e938de976f42723129b260f1b053e6f419a660a806922d690853bcf29132a0b27208b392338e6764c0daf93b5131ce061cd3947c4849da7fac09d727d9862c36d54dcd439a261bab678f3bfe7060fd126080e11699730b963131b7f42bcacd1fced997924aa23133bd3ab9ffbf358c8390908f49ff1ac45dda7bb8c7c7234c54a80132595e6072e9b505ed477fd5ace0cfe8edc4d35e4dc806246ab8c949f1339c2ce540139572c844f0bb7ca9c42ce3335cb705853825e15dd65ce23c91731d3549ad1ec3752b1ec8c9051d647f20422ef07c9e4a416ad2a2624fc8cd0a2dbd172f30ed0cdb19926578c5626d8831d6b36a3e75f3c55ad522ac1c28de3005d8fed20f719a521dc4944393e953711bd51fd4ceb0535a9b229962200547824b99698c0159d24e384da9cc67d5d263686f35199fc2da7a9e1a97e63b8336658debacd1ff29b25172aa8d777ad2f07f2169a5c48f574b23dd9587912138135a6a80362770877d5c724cf10221fc3bef79e8b9fe37e9f3ba9605665a4583d0a6743f616645fe7fd272f1f3db5bd3f95bd5df3df986f36e99042aae0871fe38e4f8214b506b83e210728a8a3c686f482af0d2f74220bc6c3d48cf6a194bfa33362767c20e622919d5721945c763f27e148fc3b3c891ed92b3f172b3252d150d9acf3d5d38b26feebc72d80b4164f8f1a458a8a3db90dfd0234320def74a12d41a9137f45988dbeaee7276a7dd78d3bd3027b23e6f9ef83f27d6a48e17059660f848ce0d904697c2b772c201409a6fcddbd7384f174981fbbcbaced154ed23a87f39643508a7ca8b5372b8cc8d61c5a738e46f570975216c48f636c79d7c206c29a80fcd084de90c943e2b393355654511fd7b07063903fe5107bc9d4ac87a48a5a09bb3864a42380e49261385f6ffa2a1e71735fe6e95a3aa0d81996ba6ec303869bb9382eb9daee754e9c71a7cba7061a89498b1feb6ce4fd49336589302010096519076ad2ddefb72fdece8b4e3e4d460ecec2650b9c7960626d5c1127089341d3fcc77c336be41276970312cf2e19f407e4ad83d22518d000c32c886c57494680370809732ad39722a444fc1284ad5acc0c8b2932d16629d494db625aed6b1aba224bc247d7b7260e784705628792ff0164355dba53a705981cade5effb6b4ef77bb0c57978a636c512ea7eb205dd96dc229464d3b3837a50ab015cf011b9900667659730d4b7572fee7ccffc5e8d533da605955dafd0033dc53ca43b1d33fe4e5d7a611be02561760754c208e5eef56630bec19b087bb6995656557c5b89a395143500cd41a10721ee028b9705ed98e3d84286cbfc11a33eba194ecaec6c08f039667f2b1b672460d956e28f6c9fc87b5410e0ef664c20c55ba1a57a34eb5cfa61ba97c3e2f3972898db06cea311b090f85ee67a6dd288e3be434cd1ecbbae46e70aa07a6a6b372b9d7218da7f7d9871966de36ae8444f9c15fe666096baa6626b953f8883a7e72571d86ea2634340a66ea73c2a92b8b6241f474558e2bc96979f20cc037d1714f0fc392c92bb7f3b94ed1c0094ca98f127a79720c1238d58204d36baa7f121672a3204e19664ee517519c93cb96cc28bb8ae0d8374c4ab4ae4a1aba4fc4990072a25eb69b6fd00a88136a7baf92e7511ca6a726f2ecb050b32d3870c384274d72e798e5502d5c81e630560589f1c3dee8d1db58eb736ebbe9fad1fa44412c3f726a28cc7b1a0484634b6a87e448c66232ad9cb1a5947324a7366c37674aabc972e800b90bbb4467c7f41faff50bf30af808a5d9f69d75433b7055ccf83784bc3288ea268b1b32c0388592ca3a90ec059c84fb1ced7d520fc4602cf7ad62a8351023b7411ea19cb873c49c4eaebaba20134ee62ea0d9c9957af71a5af240e0b7726680fdd55cbddb143ef9f872b13e29737bf61a105b773b1cd6993ad531f3207274b697b605df24f08c082562fba25b3eaf55b69d0f3d9c5ed8804a94eaa9da72d1295fc12f7b412c0672405487bfee11d7a6f7c7f9ec663ba91424136bef6772a785590f9bc4faf9fbc09c0ac71f1e9b70b54cbb24efe800d1a0e46d30de197261743900c1033bf05d5e422bf55002b8c4b2e7c3543b2f9dc87f98eb44eaf324f28dc99d2ebcfbd6c1ed0f3836060ae35cebbb4c25ddf4d864e6763a83fcce7299b7d0916d0463df68bba36c13e3a6798980f62dc092f2bef338b87e679948514b012971449785f4390d95f316ad9ca94b97174d741bfdec898b2f7bc4f74f3b69814d0fb3dbe1110d9e2069a8d485019d15b433a167a13c856b224bac58252d7c4877b41a232f447401d135536ffe40e4b6ed09a135cdc23263071f6d705a2d0bf8bba9729955f10a4682555d64774e3a25b4de50dce360c748b1ce59296e7241b02b0d5e4e9ad428a88a5df221595a0a50357c1dafa829520ed2a4cd8c5072d94e0c5420ecd0ac1cb9399e0dfefab7c1b996263d634cecf2cb9d0ab0a75428a0e1b89e58d47fb0614307e0eb0b6c9b6fa6e4d62610be6b78da1188f94da372119c65bf2f0b573586e6ade657754c5cc1f412f0024f081e17f1c4893b7f80720656f078f827217e5cae764045b2587910a73fbea9e41591d2a0281351f68772e69440bf03ec9c33d1f0f0d185f317631f1d9bb4530348618bb4632558c1e40b9aad5a3020fed74e519a135b435898f2c4c3cfe3d82a58ebfbf5f9f4e0302537ad663cb3abc50716c0b4ab0bcd53cc595bf7305f6ffeb31155bd9223e6ff9972949ce0a61a396df83751e941f89a81c68cf419d8d9d1871293308f349ce4177208bb18c6b4b77dd13ed1d16bc2bd5bd219eeb9c66e2f6d6081b41e3a3601137236e2e805bceedf6ddd4399fc9c82b3b93d93a2e20e487c806e1e070500bf91570a285955b88f276635a5e18dc7ec8328fcf3e7f0abe3d3372eaa3f4ab4a76772c2020030ed1846eede7a514bfc16669660430148e1398f02f7ffe986225510726dfa5bc4ef69ffa2ec8ca87077295625ea274f33427573224ceea2cff694f072f6f6cff4352d220c0e07ae76fe310730966c1ebed7ca433ca9836f63c7883d42642a88d86bc213cca18f0f46908820663f4b655a02e19b196eae6621205fc872c1c0ef0493ca584300ba4a715360cdeab582ddf2cc90761974a9b9faef2f821fc95f31c6ec27aeb6e60d035535ee77288b3e1c3cf0b84cab36aa977904a1bd720e35465eadc582d5a84a39fdfe596412f224bb1fa072e960684246cae02dcd2c458dc169e4e41cce1aa9245f33f6891bba7d2a0ae0bc56f73240b4e9c7a6782c6d579fce2cbb98a6edd03c9657ec096e0fb7d81304897bed464e5124762c7608d71c99a00a3d457f6dcdc99f2f8af3415b27366a48e0bf0ae3181756d7dcf172b0816b3af2f1fea9826adc1c7cded8036fbc2157226f2c951699a322b30f13600bd3ad388b31cce3d50634b6d6a620314d8e4a662aeb56fb2fe73ba1bc2f2872b15333d8f63148a0fc485a4a54c546cc66e9c8c6ac91f0509e3932b33d351a5106755d06259ae5e79b29517647bf2f60fcab264c7a998c47dc143a1f0d26fd078fa12beccc78b5f34e2cc9c43b7890707fae1ad55b625e65bd4b2736087b8a72d2c1877d5ac35e578f495d1062fb7dfb2ad1ac3b0ca59f308ffe5f3ea59e145cff743a430303a2c539b4e5fc6a51a3e770965391a733e5292411cabb8d25aa72d078bc49c34147d426c8cce80f5e287a7da8385d5c03f11e643cfb61245fdd723435a5d2237d44496670e197008191ddcc070e10b0538967fcbc22e8072ceb726b946effa5bf540c9e0691534e05ca85fe5e5b071454b6b922ef24c16a44f70aaa82ac201048375b9bc58829c0d4cdefb1d46ec34991d9fbc667a3113b438572f11f3a7a2ce364451c91cda101144c9afcd1680d90081873dc2206886650c91c2213976d72f111a26b9b8599ac12493e52abdd4f5780e0f5b570f29c4cbe427256a2119da18308154f45b67fd66540f33125475226f7181fb7f7cf90c08e7e7207a0f97cac0cc335fc8701018e060dac70ca64f7466279fbe1c846ddef2b1b68e04bbb69f9fd879c97da61dc784dd3e894c1acdb4f5a504dffd14e8942b92272d8b32cd925b0e2c1e8519237ed0d75b4c9d78aa716cbc6a864c3afabe7b466728c7979905b9685d9ae52932f947bf29df3bfc2dfbcdc6fa51677b6f41ac5d57273d684630d86ea665b080fb3cebe9adb595d1e62d8d987ed2512ff66afe8847217a3e58c1902f469d59c566b9419c0783ff09f439a006de3f692442993fcba724f6541fe77e43ea53a91d5187d0f8a4eddb6df6375f582d44f55b33f59194c72baf18910c8f485f01659d47f394ab0d29f05d244aa7c912b97cec43e486fcf5cb4491899fb345c68c7f81b119d7e4e336459c080d89471f4d67ec9a2af6d773f11af2e7c17c4cbefa53372d24aeb9c0fd8955d78410c0e45f11840ef8d1bd55a00efb6ddb94d31051b6d0aa2020ec2f735c7d86f1691f3a462592f83b5e65872468833184050a060dae480fbaa97734cd02a6638a7119ab1fd02334e2a0a0a5fd2dbbbf7f1783aece845880245abd7e3435a70a5eac44f35bdb8a028bee39b72f140b1d5016d4bf4338e125fc7f538f7d81faaca1d782ad60bd4d5f77c07bb7237c7d3635a8c8c516c29c7872f5ac758454761a3a08fba122aee3b202e97d75f3259c2ea5dc5a96f47deb879cd5dc38ef1717d88ea5da4df6bc9b1a88e0c1d72ef34171a1e67b303bd20e185f5190ac5219360263f334794ac657e1ca06199721c471d30f24ff0b388117ef532d3ddf5d77eac2bb735d8562a46dc7fdbb800722aa4557ffcb0f90ae01ca31d51c48d59728515711e7d8761fea140d069536402f66ed7608e2f80a03e7c0315c30231384bf7ac35ed25a92f8de77f07248c130e62a09582cd6ffaa498945e56a803a7c9f8c3f44cf774ebac28e9eb59c2c6f172f3c30674fec5bcd153c6d3977085af1298984ee4e2d0009875b469c98bbebb72755378cf665582d73fa7888975795d7b55591506b90b63b7c9f7e6eb9eca2772b48679cb9ee9699a5485851cefd214547bae6307b2181850eaf675765b942772ba789b21598812e6f4bf3afe18c88d0046f7cb52b1cd8d02078b27e5f2c38472a04cf47c35e45156bd34ad04020d537ef92fa47986b0091cd836f44f999a995f997bdbd8f003ec793a0422c2292717027404f0a66c398e73df857e983d748e72c5774b97b2a387d2f4366bcaaa6f98663c90004c3882eedbf78bf91c060bed72033838f133124b4d34df97432ad8b89c5379cd13206dc1100af95f1385471672a887914e230f25c5eb73124437b925c152d14766885e362ef2857f5fe41932728453585de0edc812cb7ce78c06b466e464edbc54651cb7eeb8b41a8e9f6bbf1046f97627521325e26305d89c89dd51683447347689b5d5475913020a2a0ea5726463d9095488c9dbb4e264f07f900257136e0436ade773e4a8767400ccb1697242b377ec2035e3bdbcdd372eb9ada090317fcde4dc235c39d1d6936e9b46c8727665b3714ee7ef2442bf00c1099458ebefa3cc3cc785c2ca3fc25c1515f3d60603312b3c5054a98bc95b3878dc812a367636298047a5d4950031940321d5e0720f6500cd1bee989383b14c8577f7579e09785277d234516f8cca4cbc12861644aef3408b5745afb3048f89824d0f08e2470f932d1cae759784ef507999788072002659c07769bcf0901659263478c485764e66cd8be8503550a9fc2b9208ed7273fa92ae99ae42805654e684c64551941bf7e950adef8d19e0f5685270608172e1bb0d8de85d83e78b0751ed80e3687d0022271d7baeb50a17ac6b0d4b5f11413b00fc35080c1cfb8d8d5e51c63338cd4a2fe2e1dd252f63037dbf5b8e824e3484a1396fab8cc9eee8c1b5b8ebc97a5ef985a0728446434152f27bc5ee8f0614e20b349a45ac9b6c4535014db679e043f7781086504e536971a08b663aa8de727d1103301a02061391dedb3d0c3b5dd54b445604293d85569121549f1e7de93555a9b9fff5aecc79339425400254059b0ce7e91e0e7f2290bda2d9eec8549b723d3df7f83539d26800394b22462a75f8c6516f0c392bbd80fbf615833d1ac9726779a957547ef445471a5d524042c44feb9fdac2f654e026730eb6ca938c00729341bc9f7df5dda6f43f3b0f6233c8d52bafcab1812264ad7bc6167e33a58272e0706d42dbbb489b7046d8f160be2f791914ca53444b773f4ee609743d35e2088abb05242a11569b6e9b0c0bdf04705d4956f67b1a66c81918d749155f0b9872cba9e8c5e1e3e150ef07e57bfa26b27ce103046065edb407a7f103311de59472b5c2e2708835cd92d08d3a824643865a112bcc2d4bd966fd2ce85a7a053dc5720beefbaf92eea0bfd7a4e1d9cc08c6c6867bb52a1f7d8b381a1f2afbd61a12724048ec9b2b63e66b42e0f790a2c1a9e06f984317d56f21ea8e143394943735545becdc9ca0849ce49390693cc187fb563c4b93816d9c4af02f754a9e402402171157c0a34a90921d00515126973917f8fc83c7da14073742613a5292fd55875cffccad62b23bf663bc5fbe9f620387f016279f549b4b3b815540e15efd05fe60aa0d2af8fda1e9e6727160fadb334862219b732f98ea61223f2d8bc8fb4b1172202b7e1acf22f692994670261280baf18e705ebbe2d2cffbb0afdc1337cef872d34b2668fe212cc2373af5f91fab6f33bc67defc4d37929d93f38e45777a5d117a3f55c7e94c8c246c9681b30e899ad3b29622def460cb1960b2e3f304576f72277cac5ab357944694bc471d002cd6e843470e10b077da68a98114a4ce0ab3626598c1e2416fd0fa0f1bdc912ebad361cf8f641dd0aff4b908d6664e2a721e721d030ccbd3220c4a6567d476ca67b12d613687b8beb133f45b211e36e2c4ac05d063e29be41dbdb8b5e1fd3b16476729184f8a2f7aab7d82464abba312494f49158e17d446a36d2c35855ff3ca2a97f57a0ff700d271ea9873a47d3117039b72619887f60537cb2f62f1ef75bc7a2309682b5ea8493fe2e3409663abb1a12472f852b2fd05eecc967ddb858166431509be4bb3ffe50e8dc0e1535f82efa09350f1e8a53a0997c76fe0f4ef8e4b09f06eb6fde9286662146490e180df5f7e737249816c2264b4d633cc9a2e9feb7523799798a89fd486c6a075e0bf944c0afe722152f145d4f0de15f0240b982f5007bc31435105eb4e1f65aed51f232dc44a11feac75b7a636e3c8d02d8c3aaf5dea4358af32506009c8297a81f681a72b1f5405b5883eae9eeca36f39dc5db69d39c09f7672304ce6a0c6f6e842c7c56488714b6c70ab1e9fb940b048c8aaa0ac31c4084d275b8d0160596c6c153f3a808872e144fb7549dd948e10399b12e783811ae1ef118c957d92d950d151b88b5eb7728586299b3c0e89804a89ff5e4e81c5f6ce04e8dbf361d9a612b43d00ac2b1522debf1b5403302c35e1249424b21f0d154c53854cfc9b114eeed1409c463b4b72724af02c5d46d0a3f75234bf096e80ef065117ea16662d555e68d2750d2fce050106b1ba87185979c03535911bc288b831825a97f4b655c54966cafadd92d249a5c14f34a42d323e0b77d492e9793bd543215f84ce9095d328fc906d33539272e94884808ac58adb9e82291050e7fcac8bc8d2514c2a0172508e641bc25ac6266330ea12e358abf29afff863374f35f52d3451550218d281a692dbd37c3a4472549285d367445b358ba0e4f56523c834ed3f9395070a31dba68beabce86ed146dd22a8697be55cfc2ca2705dc1576baf0ff3fda43e4b030409a13ca90c7d155937c319eaca75f0b490cc9d78a0bf25362774b0bd2015dac2a464fcbe66d54f726f8dcfa88bbb0af4e7309298887d551a56211c91005c8c581e1289dc9d41901bb545981aef3aac03c83150ac6764b3e85dec70fbf651f0d6a13216e687b616721a344f1913e7826e3b13cacd73583b571fa828c1e203c02985f67613251e657280b7280688c93fd99c5323968b542f97fd6beb323a0cffaa5ffbe5f3b460cc7283fdbfabff2ec58d702c3cff33673a91f2e75e7b6ad4a0c20bbce27f5dd72c72c30d91c1e4d0bddab41724b43ebfd2b73b7761149bae1a16100e0a2810f40172348fa7091db53a87103b1f87d3a4372b3669db985d3d03edff6a9640f8c06d7234cc9681df37e40cba8adbc710f5fcc16f577a33fb8a427ed446a4f0507f7a31cd89dba2182cc461ccbaa8ffc5c70e792e01369bfc9fbeb55030fe8938a22139007a07ee4f44722daae1f16d62ee8d8c501c69730bd0bfeb8c8b5651f1e5034c7a0e616f7554319d4616e7c7828a89201b8bbe3aca3c3de3fe92d99d4fcae872d7a32dcaf3e68850a6d6a84d9e8533bc6d575dcfb0003dcc92b5d3f04d379572d28369ab56fb5134225984a9b5e23a43443443dd0d1e4a52b2be2d6ae28d023ca61b24ffda0992355865f0d3cd59e09f39e0aefb08c00c8f7fe780cc1f2f1e727a47d3835c9a23f9a338b19d4d836e58ca0b1b26a0def7fdd8f254dba08b5c145fb909c343743c36447cf7c2842af180accf6fdbc2ad74b78fb9fff9278ec71da9d150b014b7b9f84bdbee975438f22eecedb478c28a0fc220b7dfc995bcb372ed4b8387b597965d202d75fd7200fd9e93ad04c6e468e6875956e033f52916727dc04df0c5930338b403773eb568215ed1d0df3e9a30537fd7ecd796c64b1c727c3fff0d8fe92200120c82e18b49e12e61a2199ef6ba4f615234e4a11023541139933eb1cbbf698976ee383815069c8beb7a5105bbc4af683093cd73c83b536994a888cf6c5da694f72fa4f71aa04a8f9dd6fb369bab54fa31ff07f697a9545c5a352f26825d6eb8fb1522c799f2a22442af58afbf07d8cae0dd5f9956ee0e72bfbbacfda8befabfcda4207ec897d7baa6cee86fe8e402fe61a124ee62c7923a30649301634b007a690bfe4324dc8a1f2525ad584c3171aa5c2bc40ea961d072c408d54f67a3be9b3f5a132904fc936708b1cb208013c74f8f1e2fc97efd7a72cca8b78735f9009d29de19a4746ebe9b4b23b0679dadac2c1c591af42e5b6c358d593a6e3dd50736c153ec5df4884993077e20875811d294c5993cd4af4d240541d3abf83b4771bf3ceee04a127638166771733901c81acb434a797214b53b21d6fac20aec2b709d26fd895a1e5f98097ce3fcadce38ce8b03a5f1eef54dd372b997d35c2257a876df5efcaca0dcb8b25b6671885aaf7acc1355b07870d8be38e76825c2b8828688e4fa32ec75ff9816c5a7b68ab71f9d7909cb38fd86832567c01e115a1756811aa9e63917cd933b27cf87e70016688bddc0a6b1e1c0e51e724f0c42295689abf5d67cbc4df12f1b4d49e43399b7e3cf53a84328cfbe3c1a7298f64fe028386e9422bacff27d321764ac3a7a54c49fff22094dfb7c0ae8fc72a39e6e9dbc9f45181f50f6fffcadfa52c57ce0cb24edca36156cb616a205eb72f2e9249046a1ab1f54a0f1a841cd2a51748d43724f08f1e21b347e75cf65a467f2d988be9a5ff40990c68a54791dfb3f5728f1f203319659050d3d2f6bf67c723390eab00dc0186823956574a1854f04111cd7b9925bbc53120011d60a2af972f4031658f876035f465593a402e58f21b2b98bffc0a1bef8a3186420dfaac072cc919fe59c65950b9dde27eeb1a42cd721f16db0f66cf41b58ae9ce2299638722ba767bf52ecbb4849d1e1e52402bb02a17b11ab730af53aef44bb76adb3fc5d55c087cb0876d25aa0cbcff8ca9a286a1baeafe7e48b0bfd75c0d8104b109412c7e68da8344111fa22e7f96f467f2d1e7655b1b3645432b90b72333e37f0fa72578ad49308982e85e2ddd791c2b630b45026ae2517e472d65a72ad9047d23c5dc0cf0d2bdd37b4b38cda7195ac621e49fd6bdcb04f6b66107b31524214e06c5a1973f51ab2bcd6d859504c8e0f3a6d606d11d70f371f9d2179e4faaaba3704260c24d2825808664f20c26436a8ccad11fab2894ea09a2da1aba779f0ccc63243275d1f6f588509e5a93a1fc92a322b152e86270d3a84368b442e3ac0772f9f00e3a54c1879abe619d0ca7be5f76132b2c61785c7290e8b9d74ec783be769791dbb02076137860c2faa0e4772f42015b542127cd512220acfd6adaa8d76261c193567e3974c2cf1b0c91fff53db5e33535d132e17812be6ebffc0d791c238f272eed649b50c1f41901298e323d2f9a6d1ccd492cbe5ebd4c6334801d0bb03d75b807ace36ab02029250b09633dc4b01ee88592a3dd118773a8377eca362a65872ccbe8160178d85066bd9a3741695d9dd10dd9d388780c7db9547ee7b3dd4857227f1a601ed0cb21f691eade52a5b4d2d0866afdfa643576fce7daba21d62eb72606fb912f29cd8e04bda8e68b93dd0a76931f44c513b64774dcbb51d6f41b872838fd75de534ede37c387e4a417e2be6306ba017795ebf61d6f5a1ac2dd4ea725abea740677290cc53c1b41fb70f60c2bd6afb645c50088869107fb2a78bb9729917ef0d007b8c224ae87eae597d61f98f3f8913406284da73edd47943b13d72e94f3b8d72a7e05609a6b005ceacbb91de22d5b641db0528441f310aa308c13d906419a37722248d6883e98c2409cd3102b448405b50576d941fd5ae6dcf8562d727120c0e8fc9483f6b941ee2e0de2a35dd91ed4b85fc38f9297cb724e8c529997e2447289465b3b40dc7e35bb74f9e9ec4fc46e46ca5223bcba820ff434272cb953e987dfac0855885b7cd6a2c748e168977a4fc9f5c688b9208605f174b723a5523dc6e1c595c7d5074560ad4a195aeed3b91bd8f8772f7c03d34e214bd725f7fc592a196d2d7834ca96da8666ca5dd3d2fc7953f542f66a949830f36626389df0e58fc887602834ea4455c0d023cfbb94b6a6374123bab2079efadcbfa271325290b7b9ffb5c28d22b77edfc5ee9e0f5028c517ba1edd426e29e9c844b722dcd32febaa7a2b4144639be3644afac006b3c6e4495e40505c7f24e2bd5435420866fd7af1ed3dbe588b71fd1bc202211743f0eeefca10c765878d766656272f27fa90934fb3c41b37efe10ba7ace244b00040d62f2b42abe2225b7ce5ee061b630d8a78641cb0d2a72a744fbf8111ca61b82fa0606f0a80fe6de74e11ec4367d62dd6d60dd9944d78823d9aec53717c3a03644c5093f5ccf2ce40bf557254df6b2469a6d19a28ede6485971bb298d5f3691662abb53da548e56d3221b68821b0b672f7037fa2f60a00a041a7e790d9f52225c869f1e1ef17dc70cdc990252bbdd2454e16e10a15094685c649f6141ad573baecb6ea524a757761ae276f6e725c345520cbb74e229bacb0df653f7f4e40cbf8e850c6ab6a26be3095cb69d872003d7b9fba7d02285238df00ddcde89c6e023b08d78035d6223b8a9bd03e6b0a6eabcf4a3cc394ef552bddfdc1d0b2b2a26e2c25e53c81bba8c67e40350ddb720bbaf13245d0f0d379411244b0bbacca8a18a5366bfd7db7a8341118eb2e0472dbfe86b0310082f91b04e404dd9d1fa7c88ecf6e094a88b6222a5bafe2d82872b0aa17ca7de4c44ead2c1a7cbb05b23958f6f5072d07f5aff2b89c7eef84f1327ec7cf05ab53b9f61dc7ea77a4228a5d364b93abe4b616e0839bdd1ff1a718603a53980a1fe5644dc20fe38f64220eda51c71e61a0f09f80e42547dc4b063f720422c4060fa67bf071d2ac0e3a946fbe40bd6d1078c88d226266b86a6cdefc200c4bd7550338ed58c2492a4c67aa0c653d51b1282c38d6c1f48b82313dd5764318c39df5c133f9fca6e7ae6912e1f37960e84a336be5ba991cdee5bd05903127d3d39e5a8243367c0e469d1d8ca3e15d4d463d43737f3028d669240747a22c72da027dbdceb60486b2da489382a7c60b6c90fa0da881c7b62baa936614a0d5728d058e6114b11cc8f6ff584a2afd03d8d23605890121d62e37d9e12027da0d725e120eb1f14e79bd2b1f212d4d608340eb91d1bde2cc53c0a4008e489e175302d422368757d82364264677f45dc6bc7170c75536ca348261f985aae5b64eb60d30564c6ad0309f8c41ac843e1934ece07bd468957e50e9c22863a66d1c7c5b2ee9b5ce7e9037d2dcbde0f8ec8bd47b6a40c0835e9bb2685e57b97f87dac3a17294c5f081079b21989ac132ab63001d412ebf41e894268ef75264a95c6b07e76ff34bc917051276c21f69b9469e666d4d6bc9cf847603ea57462d25ec4f81d71e17e40248bf8e3aa59f54842712ccbce5da0cea99c3ed8b7095f39ce01aff2136b57de67bd7aef7c8fcca489aa12946f714237fd33d762c5003aab0f39e3be82b7504cf4a29610e89b8385b958d3802ed43313c3f96f8c711b16bb63e5fc5ac72b6280f1c65e7ac7e13aa746d91e4a60d526681be028fb6503bbfbb241f67fe72ce4cc4145418f210701350fef081cbe16bfb3ecc9bbcf1ccbe13d5966aa6e8720a0c9f4e61362eac33c7bb2aa796c6cdbde86ff329f371afaefbac3a138de024af4086fbf176f4bef18bf687d91b2d8ae28d079a82b6d6e6d3c8ab04189e6362bfc3bc4348ed61a0f2f5c45c36de2dbc47719c0e5f42811c4b88a97fcbe63e725148560e27290dd873f43dd2ca8056f5bbb796996e30d3b70147a2a1faf9fd63bd72172c4d8a589c3262382dc9553a0b86507053502ee2596e30f42698451f72b7d3066bcf0a5ffb1871ca6398cae4053b4134f062c89d545b5617d5ac86d07207240f622a41c465c6359885e8d7dcfa0a9599959bf4993ddc9ee0ce71e98c4666d982a46480faf589f196ed63388e94ee6461dc81afc8602440fb69260b6372155595c397e802f5461c73104a77c634767fd6fedef641a528226d74dac9df72310df0914f8a83d9f5a307167a1a04679bb8fe51aed6499d582607388c242872d7df0e76958316b588357f19b132581bab919aec9c4299e7f53fab1e7b9866662d6651a7aa8168127b1b90c5e9d65cbab9215972d9c0368032adb704a29be332a5e5c7dbc38c60c3d6cf3e2eb49528aff161472fdb5a4e7be44c4079ff5aac09de27a54af3a78fd4a13b04bbb338868d1517b653b093811058a2dade2d8dd000bffe5bef4d109599b0dea9220642914f06b8e04df088663e6d0a6fb16baf7172ec3ba1dd046a1feb0ae8ded665fa8619cf3818c40507259a16a7259872825872e1e9d99f3652597c5378fa0d4692b3ebc45ffffb354cfce525ab474209bf977271dc1e4405f24979555a206b91ffd57b9de8afe9e12dea4c79af95d07dc5b21e622cbfa17d7c56e4aeffeba52a84adf370991b776d59c2f57aae80e75d8d717253cf21cdbf9352ef3180c6f459ae3134308f9e8ffb37a06c5f5b130a023c06726bb366abd83b07decc8eb548f6f76bac6c657f8c036052499bbad52f44d1614fdcfa9af11a952c1a643f8a1d1854b8fedf8d2187eae103bcf84ab2c78ef70072c65fa285bf808284ab9a9dce3cc35437487301e561e6ece6f9af0093eec2da7234ba2ae62bcfbb5b989c4a9f76407d1d8457e6f2b555213355a15e7341c14772046bff6f8aed754d0d126a2c2c447ac937715780b82577e5b1b3cfabe0710f72a17bba96e0a4d1260f483916f5c19066a74644d49d0b03b549700aeecf4d4a72661fcd0f46a0ce3eb0edb95820abb22d2dd3e3bb101f57cc9435204626781156e4459a0b6d38d156320a45d269fb8d9d5f3df1f8b7e679a7754133fda874827272dbd9f2362d129cebe2f6aeabbc703134726bc91b4e98adf9a7282e8c2648724696ecabab5f6ebaa1bc88c5165297ae0a4b9664de232142cebfbaf880fe7c0b900f0cf57bbc8dbfca48aebb616b85cd5878a1131b346efa91b6e76dc8315172e8c456865351baea4010bb89b3230b7339ee43522264216eb6aa074e4f48e65d02b5773e884fb4740050ee349abc3beb97c51cdee4faade75be767a03dc1d63f30a8bd2039ad592042446310eef8e01bbf5fb49a8245503920ddbaf7beb9fe7271f72a1c35d6f9cd511db5299389c5f36ab192d57c868fa0e63029514341b61a359b695eeccf0ece3f471c00507d00c75fef2b0ddc84a2fea50bd3c88f31bb726273393ea54f491a33c0c9bceea51bdd3766088617e454b6ca62d7492d33b119c33d98eff44bfcf563532a0bace636458a0c1a4d38cbe6bbb04ffb63e1984572227ac363e196d251271bdcf05135fac5fb773f9146aee93e6e4f04d903d6227218aa715d749f4864ababe2dcc5e7d34b06c99ccbce967eec14c8199c0948f12fe32bd31dff8988b71cd2700367cc740971271136df4652d69b9dc05bf3ada972524b77290a1d001b650fcd1d14cd733e92a1828ea73993a402400e6c62602e72e3c761e0518db8558652ba1b6d027b1ee9b81c650fcb63033f71ba19f95a595a11cc6628fae30cca69795904bc2ad455e1f14b8825a9ee446bdcfc0098b1da3999fbb38149c0dda921008d9650dbe6738410bc3e39bc8ba25b4b8c9d70c6c7729c2e4016c75f5de8bb6a64558b162c318f7cf1fd8a7214af67701f8c1f427972965df81028006deb52128519c3a9f52d750d83a68d4ac78bb62f07129db944720dc17648164f6424c804a8e32e51a3d8f51f700feb7ec62c74f6b75aa4ba5b72225b6a8c6e0ea46a9d599571741542d5dc07e07d7eaa904e50c722d1819740375f71686e6f04a838f176d13d2703ce56704fd55d54c9fe7eec4d29dffbe2987236dfe2337531c40e8cf4b4219dc78f492ec61ae6a898f19c76c6d1c47857a73adc842830d244d39459882556927575c52cf2932e1cff235a6165362aee3b04030be7711be66c9385844db18b1175ade94aaf1cc2f43d6d99519428daafe6c7728f63baef3d7b840f16eb4aab01e3055233968af70f3fd224a81bc86489ccb0721a51158ee8b766fc2eb31773e727d0ce64c8a73ceab4161b969338e61c1abb727c8dabb5c81fd1ad47ed1ae7fdb116f26086d5bddf3f6cc669f79bd52d8b7f726b82604b8897b9405ced84f9a92d9641420ef18ae82aed3fa1881befbd9f6f722034c97950aa6c11b48b732c595b2d347f3b34a11d7a6900c281d913b6a5d435800645495801981c0709e44392e245b103def98bc7d8f26ecd45961827bf9772300a6a96c883ed969e374f98a4893042cd701ff1798877120e18174a52be470def84a77f9d81a8c0b7a9440439d2d5eaa80b80e6b3babdbe5becaa79a86d7a2ab1cdd70371b3fa697dd3cdbc6dae469a98ec4e35b4c5de8c1287119d331379325f271400eb8116f975bff48efce76de75c42b1bfa38d46f914c16a4c843ec32bc1183fad77d7b376add6798588bf1ea42b1c1317caac8554e12550577c6bf3728308b4a5028c56937eec0b37d36344d4756b8b1f8e1a2cb2f34480fa96bb797222696c46d0ade48d2a1a36616fd8ff3c6d4e5d429e1436c84e521b64a3c53908b34e7faee7e91b4ab569b9f70c1262a7d39b36b705848c5fac96c611fd16ec724842dd929231b9d297f7609a4059b32f293785e6a6f6b3565061d8758b863f073902d7081647a2221952df69a584ec3f9d0d7dc4abf42befcb0e38317f1b4e7210c963647cea20fc02264fdeef68ff757139d6593af9a55e9f5c814a7fbd5f686f902ea703d24c021ae582dc597175ee59bfde5b3c498fa1c72f86fc1f07ac11389f304fbcef8d4e40b00ec153cc8e5b571c5eb6e7bf918628ff7aab8a71841fa0fa62af610d3d99c6418f1473d44ea2ee7aca00609958891de3c3960c8c5472e8b039158b19b31e7736b41ba667d7248aff1bbbd4fda630bfbf521f507b075aefbd322d099ea25e26d7634d3d6c947f3003a4ad72ed369efad7f344bc8ca172fd66fe32b77bc19bff9c0570cda234b8d1ef4a35579d42cba1ae26d7e74d4b72b81a4bf92032a53053b686151adf1568ad2d992d4ee7a04cf5e0cad186ffa64bf2cb5b19023e10245730c5e0976bfb510a59794702ee60605b3368c68bd927661575bf1eac695fe08cd62bd15e543a96810fc5b6fe031f664646e3866cd81066bf58338eb86908af526bbc346f3d3c46535a2233915455f73fa42aeed5ef285401113f9c863f0ac008b74063d1130edf1c0d9c7d9d5dfa9c09a88335d65fe97263cc6fe4cb013e61425fd43358e1dff0449ce59da4220f248c8b91bffd73ad35fad7e1c7d155bce1094d44fe613a1eb4875d306fc97c63228a45b0404f5dde724463b8358c092453b4424a5df77c68f2f6fc08315ffb458055e62e6450bc3572a3e593f8a039a8d9b89fb24d33818a88edc33b98442804b4693336a6a1d4ae042ac66b5ae9c43e530f254158cc4a414157a961cb3c29c0a86aab1101da011250f26815d3a85ddf70374aa142dbf93382e169cf192fdb6df1ebd8ca7b21a6cb727f179f48af72449788f8d950bad95ca73dac0a86ac87a9a7d4c4ad1f7d8dbc2620aa8abfad6576161fc29a6ce1cc5a94f921a4ac953dcb5a18a83dd71c3062370c0bd8c50772998dd04cfa611683fbfc2dd0c951c32c86b585e23d2b8a6c3272a795ad62c4d5709b75724f911f03a2319edb7214ecac64c5c839b9bb70df7020950bb599532808351804041a8ef04023b03e9e646155ea92cdb8e642cb54252147e1ae55e49396ecf4b15f6546de8be28c090d50d52acd299551effd8a402672bad4e249b6f651ed25cc890e11b7e4c8eb15e854a98db1379cbb808e6dcad8727518d02a4afb3d43d7a41281a25e36c2701b7a9470cb6a7abc08437ef287ab72212e38ca2b87bcd23bb3dcd036f5cf0d339abcce5ddd3a2830516ed18794c3728a8ef1b8a123515cdbeee37e916b64efd1fbcee077c89a91947c4c4575e5643110ec194f3cdee754cdc29694497c24265dc3364b07286a00a31843af169794120c2d2d320df98a43238f5506b1190eed72c01fe1e091d7dee9b849f0f937ac725e8cd3731e8b0725413ee26a6149be9f1b7d259615409f7351d2545a9395b672a0b69cab64f85bdcbed9933eecff7472057cc5d23a0c44bd8b65a81dd9329572d4bd54e9928c73308858082ac9be1c4218d0747ced910204f804265b84affd72cee1b2e62235b64dc00d11096888dca964d48d181c0ab160b4947ee8b6e1422836713c4c86dcb9109bf4ada969a37bfd66c4dfcf8a4816202c5599009fc5e0008e2a248d980372fd46c8192cefd855a73318c79b883ff9fad9de59f88c9aae72148660b154e0b8c9097fed9a1d6c8da3d50a700d13b0bf3be788460f0d043072864fc72f4b3477915fa1a9ae17feb24812cee41486c168d274ad4019632bc6727416d53e746277a9246b1d53d0e4adcb166bb0fb9cfbe3d35599765f08202172e9b074893b282e8ef44b4637505e5f5ddac381df9e6bbffeb42603d4bd0d4e5cc73a9bb8ebfcf8bfc26b2bacef9075215c3b7778ba6f5f1a6640349d06f57a72ba26da150c61f8b2dcbb4cc9454c7c46b3148f9a43a9e2146ff71ab1fc77545a18449b57ae6df6c2187fec1819a704b8ba58b64760a4e46b3cd0697136c59e42f1ec022aa78fb439afe8c76ce96d4b29f42441468d86cec6bcc2fa7d510c712b1945f87a3b7f2fac017fa1ea456b00fec01b81e69aef780dedc4f8ca21654344d81cca07818d058d041aa158f0f712c2b381152c6beffc2991d5135e5705ce3e7c4c06f815d07f57e7c5c4b99584506cac889b700c87c66f4b32d60a53dc5f6d99b580572bd49dbff2ab5f778ecdfae687d8dcc7e7ff3fcf0a52b7a56a324570d3cce59a0607555dcaa9510361254cdd9e8ecc96497bba0600fb2d9b91d3d8726fc81aea83f5b4bb30b520fa86ca8e111b0152747941f3f179fbb53e18b27872124fc85248f34884a2d057408041ecc0cf129265b3d16c1a4688257dbd4e2f44a7177de74e2b9371edc3671a3655e00571b1e5748f928c15217537817841f33d257c6aab8620863c879acef32dc885783fce9fcea877c3b91773bf46d2e7881c6dc0945c5acc3dd14f410111652b5ac59580389c82ff779f1fe219865f6e5072581eb6de291256eb94a75e8cd98806b4d238d6b7437d420e5596e2beeb6d4672e5b59771c62b4c31537b7b7632d0819ef9c2284e25429c2eadc74124718d9013e18158fdc8ea7f50bed9c5dd7113fa73e564b5be4fdd0730507699f7d9ecdc36d97d935888109b892170d251436de4a73c957ae58e8a89eed066b1addf07fb72587d353e3346f51f9d0460a92055a7a535a31bc23525b227e05b17abc726e372d048500f101bd36d47f4a87d01b912a0bc982ee96f474544e390a6580218e87255cea13e3ace2bf18aface061f0c49e0c2cb8d9bac2e0f14743b2264819d2672e80ae31d956dece484a66d372e127c6feda52e183bf63837c341ea088abd8072f57a1f78c6b070eba91f305f4e1a75bd964756a8ac0b8697f4b463487f56a17245b408a5083aa330e236416ec44e0a33ad42285dfac9df6da2ef1659c691853ccd4b3fc7367bcf6d8303d1c0860531b0271a4cf3e70e83bf9e1030c36708d0723cabfa8a788bebe80f910a82c88abdfce6b6505247883b99a6e52e5edf76b272a2212c4b2294ffcac19a8ca323692378dff66530b214520e9f2ff2a79528377203ca0e0b41186311016105cc47dc4b8287dd257af2fb5f17a17c834e97d59e1987a8bfe1ae79cc010621b95c571d0de3d14009ea100351045a2bc18dcd871a7287159191b80d3c5efc96163e68cc182ce286a0ff8602f8cfe816a12b011f1d22d465f20670ff9bd24d5436010c80f334e86e346e9d7fe21059bb791e8b4b067252e4bfef75000cb23e5305e065be3a7737e39b48d427dea73e1740d009cf1e72f168b1cdfba98c1cc3d8dc68d80ee1b53ff6b146245d285d73a1283fc065de06e1733d17af01b5704f30d697c901b465b7ab2f35bfe73f5a9dbd6ec5acc85f723f0786bca9fde0d96ffce50a9a06b2e0543671148a7409f60c1f18239ad2b472ae35734acc99577a5876b48cd39f6de19c7273417b2735db54601b7585aa4f72daf2c5dbb80f92bd6cb6e26601a51a9ab1360f3cdbf09a7ab07e68297b59e47247162552c75b705eca24e426acb6bb7b528b57921b9c07c4b8fc5dddad98cb7205b1bb7178a1259cdccec314d30d18edb33f5c5681b3d4b0b8a34e36e8d5267208ebaddc3b0dae74ee7e81f7a20384bdd39f9e0e73468fc5a8345a84beb02c696dac30eb96c508403398d7b8245eaf2929040c28f30c80d2ad41146ea43cf372bb016046921012130fa33a896398071f1a8462a470ced6083ea1e0eebc1c4a6713d8b5c5284139b9e401446f487752494aacdf85492b6ef36b23c0e25fab7c28c6afca529991018605fe550bfafaf5ae4936fe98d885d2d56f3e90dde822107270f9c015f98d7dbde9d632fb5b162d59d53fee772bbb337d47e464682567993292484ce9729dec725cc33f93849c9d8436773b0c0dfbb03c87d03556159bf72c794b8ebfba63799c85b7c224e3f31a2360a3d01c3719076039964b2d7f04d94b8bfdc40db793934aa754c436ce1de3acc372e411814549674878b78929a9a27298346c508f2e97a371a31cee8f7bf566d9ebe93594dc61e229a434f55720d272625c54dbf11f7f6825083c18a9073a6b48d15470220ae15cecd49b9ebdc8240288104b8d3a537da762a8430aa0b1d969d2455e44ff436b1fd6329a52bb4cc3726e5b130867279ad64e7d3f77c04516877ae89778608ca3bc3ca37c05d9cc6a61ad67aaaf4e30cdf14d6a01909b6255b2c415dae096b414f21ff3f0b5c38cc13e3833bd4eff7db5934889ff95b968e9753d67a414f882b38abd70e8db0420b6041d6e6eff0ebf0aea38148a65e709ea56b21cf7fd0fa309f1bdc0922ce6e146720d31809b110e6aac6e2901a8580390fb626b6242fad2302468ee77fd4d129b1dcf700f07549fa64782ecceea3d2f29a41c245751933fe01dd4c3c841903290333cfc41fcb8b2e11afb3976e6896bad1e49817d737a4b657d6f9536d7132194729b75fd5f4d98fc9be5aa6267663b84efa2dcc28c70fe0776ac6f0d4df114c07268433a4f696f2e17421dba9d088cdaa85d124b49dda3669b9d359e833dd493139fde6b962413136d4e1f31f5fdc77727361723cecaae0509df90dc50f9806f43374e20007c1beddd96b0597ad367c8bd3fb95e51caf158029b63ecd15d087172196a7fff37afd921e194a28563075c10d3e545f642b967ac4cfe3e4d7fb0aa54c37c93a970279dfb3097db8791678aa30ea250b9bdd2881b319bbd6d38cb6272497d5c5119f01481f12a4fb53a11afa8d46507abaad2f2ea91026c861e57305babf52ecf14b26a2d58a001050d0619f34814d5b01348b6d4126db629d8247c0a8842ffe89977e9a11beb417432cd403e7bb7e233998ed97ec7eb6d69006071722fc219a42ab2f3a30800ee58c1728c158ec23539028bf4b591818c90165253720f90357775fa2c167746f611bb54d0f3c0098ee27610bc408bdcddf5b32bb73201a0678277f5873dad56ccfa3d9327c07cdf73d7ecb1957a0450ed555d67bf1215a3017f3e139f527a2a22adda8a518364446cade62a9f3a32c1f03bd5d8de722060e9b522f3ce639a341704086ca7cfcb4b0c68487d4e253410f8d648b473722893799918f7c8294789f34178fe9e4c2e7c4db102f87a46fca859b407687936362acbd3c6bf785518cdaa1845dbab7dac68677d75dc7e01c8feb58d2d18b672ec70ea0c3816589e9be1cf41847f309dff6b5daafe5baedeb0621994c208387280f328da5211067ef44b4cc8ac249bcd3396a3a0f737cb94106f6c09f974e772de53c33c4af6df26015ed7ec7e6ad5232f9bb93d98351b21aae90baabf7a7c7219c3ffd643135271b26472d8e1d13db21bbcce528c4960da08cd6ac4433e9f7289d97eea9f16d0604e3a4bb0767080aec7ea21de6e0feee307aa78e0b3136d20a1c09a0f700c861e54089e6ce807365537645f6c23a20935ab82816691e4297223341ede40b9f3a90d6d0edc19f21198ef73a519cd567d0a8c82742fcdc04a72ed36344b7290003dfa694b962e8ac5ebac9569ddfca238f96b41c125a0c49d7292d99305726278fc473fe5be1fc959550a654e0221c7212efd6a2f50050da9726b9ea1f2e69454850a71ce74be29664c81f1c8e58abfe74016601da94729cf494e4a2c1f8b99c2ec619f33fc668c535456019b9a90a075d4d74517688c134b5ae4247efa231802e96204d2bf590b501ee480fafbd524c5291e0bf8ec5892fd5cb253cd87fd75a9dd8554baa61a9439f0e3bc9c1495e6e26ab450eb63e6e6237226a4d0ef626929299bc2953c719281b85450ec36305dedf6b54e3988c7af7d570ceb1d11613058e708ca782d3f9a2f2d61a6c70497dacf9086345f0bcfdacf721114f9018e124cd3ea82f25ca9ea03059c7db3101adc7aa59a491e6ea193ef726dfdb9a3bad3ecb1bdd30c5ea45eff5a8a424216736b21b21bfe21de3e174e72d9dd902827d82d16f01ef80da0d11717068c8e4c4619ab97a32ff2caff6a54438f2124555b27fe0429c8afa9b264218ac2f6e083992a281b206a01bb133ec427fefd536d63fc710e8128088e228f00aaa02907ef1373844fb4cf9f155c20560da38131e082ce0c96e8e02dffcdc779e87535f6892227909e6bea9781cb116b72d9c731addc7c6914bbeba1766324ec1ec780b9a674d76f748c6da73220998b0f7af4e1c4fc682742e9067334a09093cceb92557dbe84f6264a7c9a35d902cf5916793f3425fb2548578bb30d3e40adbbe428a607ecf71e583d4c03818cee38660945d62f0e3c400bd9cc658900eaef7ab6b3c61b793082bf2bc2972c11c5bf724efdbf347b89db445e0e54882bafb12955958abf121eff7c6a25541269b8b772460f4d9a65499f91a71c7c62f3bc193e0031d8e42462d02fbc69f5b0fa05b4600b9a5d1684ef24db607c67249da742bdf0aa16aed1937dde50e86f1a0a03867296b9b2a4127c18595f4b1e117cd578a21a77bb1d9523762b20afea2388c7487234be247f57b3282b5d71e0edb4de8a54a8f119b5b714c4fc93d18106f861b9721d620c147de04f4af992178adaa0f15be0d96cb285d763e2059dfeddc96a0c0417a37669510143e6638417a2b99e66ec9de26e84d7e74796f1c2f4357e22b072603573a5005ab7a54d9ad6adce0926539c5e5055c0a8e4788a52926e796e241e6680153150d92d8565ddd5857c037c32fb3fa1e34eeb60b2b4c0da9d13f6ea7230c34f1e27cc3e6437dd4311a1e8acb5c20c137ece3e7af65b1e8743d3feea2b309124c934f191a5dc4b9d1e02899283b372ea380203f40d161bab5f57e7f3488fd592142d8cc69db293ce23ba9248b42b6ac24b0e06299446824dd1ef020872e6317ee4e85dd9879fd907a1a5c457bff949b8296360b4ecf7ae2a835a371b1e166e5025db27ad8b3cc8a6bc1a504d7132c65cfa2377af4930cffdaec045a47221ad2af58a91e07fe9b00151ecfeb2b48c5437e52537f48927d0413a17aef67249864fe28c6d15ec11d1c7a57900f2729035abdcd1fd3244ff27a0fbd2316b728f14de512b13b34b61d37abe0358bbb70f59b08a87af8e91047975fc16c0b0729a10bc2fd922effd947a27e14a61eb73a72aa3f902d183f06cecf844a92d8e726753b89927fad72eba83e6a19f49f1c334ebadb50f0aff723c16a2f7617e8315de153ae29ea5ce90f418f7307d9d9b0c8063f77621ebcfc9b6e6302aaeea5a728c4b1a854642d06f70f04198cdfdcdf7ca626c061a15cab68da54146b2be7772150998d3e77f025d461488a1a0b9a3ddc6cc9e2729e45911a5fa84af04e983721256dffd5e8386a640da0cbbcb06f282e0faa05ed4801f14b7d4c1259987ce72f91ce73c8cf774e7ff986b0bfe6e766f84373aa63c3ff78d3e27ddd9b45a6538d55bfb2cbdb05235111923499e334d9c125861462be1a1425427145d89e82d72964a256a23aab9448c8b3c6f9f512df5c819e8edb7d49af7182484de53453c147ec7934b0d5c0e7fcfc1f436786938a4b9bb904180538db709799b34ea97ff72e0a798c5731fa0b5782bcbed967a3185cbf0d0c6cb91888ea22ad63c46a0d219d5bfc6341645fe3a0d7aa32644ff33f0810d7c67e669970173da92427ee8b6724c67394b3b6de40e4a7d18b89032c5e474986946b43daac9a3136ab7708f807271226b6c88c5fbc58643099e36a5649da0b1845dcb1f2469cc894726d80950721af1687fe35f85356b1f565ec3076b13c71219ad2bfe1811464dc07aed99b601b7a70c183b530d6d0607b2ff633d76e57f83f92052c2838067d1c9c23b25c819a04f2abe7aec2276633dd01d163992ddd9059a1fd8d2efc99fa0571a380fcd721dccba318305206295d8753a08eace0e013924ab94512e0a14c7e331eaf8d272a3850b74924c48e3ca8f307f21bab2609f930732bd75e1abb28cf437575df4729bf1ff658d1097e76071daa89adbfd36c26ea20e24d3f0c17c512bc1a81f64725af6404bc60df25b22fa4de06e3b68dee3a0bc59e1377fe43328154825ec3c7250a8586df28cbbde65f7b3960bbe08bd11a6982ee3fdf1cee3637ec50dd8cf08b5196c6ac0c7e5217336c0254d4431d9dfd2ad95d3295b2cbe51d26ea3319b7294b5442027ee4e5c9975b49867f89b138880da8792ea7a255d7e8aa5ae31294cef85c73c0e81a6663833efbd02df4efcc2699b062c25b61cf7560e073aab277212c3cb0447028eb8ae19a3f88faeb18183a2f0b10c1a475c82dce64dba7efa69263923d1cceee0f7504ccf4f2308049a0a880a29768717422d1ac84939d4d0724e62ebc6f14dcc1aa0ce65d88df6f4fbd459f2c3c416c25d604df23179509d1d95c69cba9c590317a34e4c379e3386d32b5243afbba600a0d3b1154e0ddebe20c9c5825a1b6728748737813b0dc6f022a261159e01ca650910442f41ec7c8e727093657a463d451e6598eee274a3262689ef5c11a149a59ca7c9ed1fb0e017729dc246d9e24e8ae5a9af81298dc05c240cfd8e76c9d51fb570dc2d61ef876572cca8d11a0e506a845ca279b46b7f4285461aa7b9ff053b1f5a39dd4840403c18d36c2575b7062265aae756970b7b4a16168bcc908445af96917f2852af1fc94b36a8c52fe41da011cde7927a3365953736e65e4b746c2aa2e1d1374e919d277281f97e1431675ab126fa6b1357661fd4d10604484782dd4ba89001fd380b6872662663355727e71ce25a06902c86ca193edb02443f3ba1b032887d2336821d0393b536344a0e8ecfe64cb495d8cfb081ff780ad31a04a630e8ec5210f46a2f4c0c95f173a2495c8004124bde6e5312158d31cc24fb2c9157746e66e1c3e86a13d0b604876adf64c2ebb1c51058bc0f9e81bbbb200f9fcf370325dbbe4ccfc1353504b5477dabe9a24ab5870b2749c93ac51a3ec8eb4606fd42bd3d0db3ec183d92676dd2a8015b35fa0deafab1ead376efaa79bb8625fdfe01f22e3d48a64f1bc86f85d57a03a1cc347255d115e1ca8fea574bbab08b9a93fbd0c6136186213f4229b9fd38bd77a1e5e1ecf2873d6477454f9bd5257c1abed6ceb0d0aff4d33379c8e24647e9b5fa29977898568c911e50f615d593e17c394d11b05d5f3a4520f26383cf9a5b3645ee59e16481b24a5f03f8545f2ecba434edbae30a73e9e872b831913a868bc0fd999b79b6dd74e2a83ac7bcf143dfb19ae100fe5b44cfe572bda2686a6cd8b9d85748627a152d8215282c34bec2e4daa73a34775e06826518cb680b0aced2cda1c312a42503b753aa6b6347b7b9ad435aad4f6fbfc7a44d4f7ad0279a6d4fb1d2c3b02dd7a21a9df40e74820fb6f7240e661bca722907b572cafcedee2e3150734b382c8ad0e75fc88f7ed1c42703ab6d454365d41e0a5e7247dea0cb32f709da51d159542c7830ffdfdaccc46b7b7e19479a52d8f7abde72372f38a108041ec5e0a5aec3044606e4a026d5c44e8247b880ef2c0b6b14c67296205d1d602f934b50596d4f31eebd90874e8bd59018bd5bbedaa5a0f6d94c634a075097e8953744e4d944dc5169384615a2d698c6dc0951756be791da78127262df97cb13d9b69830b8d2b8f349a94561a3071cae779ba242e11daaf9b527516077de25d033557d35e877915d43fff249c994727a4885a241f2f313799b4a7291f47c8b833dd050b54ca881d22135fa7b6502b5e36234d8feabc5e6ac66d0720e8d0f1153a7bca75cf245bb99aee96b6b5a5c9ed942a52747563e090e10f1724483bb68c897fcdcd14808884b3ea8cbd3e60c00e5c2d8fc6d7a152943354b7200cd0b9355ec52499458fd0469972e69b8d7b7e683a3f2f441a707b7564b1a01c13a9bcaa4b051cac5f05c4d0326b80921a0cdba86caca01495ea117ca79b510c2d613bd4ec1393ba295a68356cbd6f1a5680b0fa889753bea34733ee6d4360e2f3febb63055723d78415b52f52a91264999415379c8ab19b8ae601cd8897572692179b6136e70063d0cd79f1878333805346ffa012ad994c8d1499db3ebcb30968af271de5183d37c6240fff8bea6d44c1353a0fd265b994b4323a2fd24b57216ee0267f11d21e5aeef2c960780e969d937e24a190fa5bf5691585a2ff4ed29a4ba33cb26de34a5f4f4c98435a25c63f0c921092d6c9c9900306eccbef8dc723c0db32a736771712480d0ddd08d25157742f2326f8972d51791b20ebc612772bdc5869707af73cfc9be9e821971a9588f6d4afd13eef5e4c46d16e0f8c78c638f782251b6cdc51301c994ec9cefacbb4d62b2595cebeb33dead0523c084297271bd8ac8d124084f97d2fd77730f536249446fa06cc2d63c3a1c746661f7a0726c86f49082a261e880955306736d0c94884e1326e5a3bc501420b1d6a76a3172ad51f921d35e07823b452fe26de1ce2b62f6f84438d58ce2f29c0b82ffda5372dcfbb313993cbe6fe019d3972840a59755357fbda35ef588e90920a8c8a242315b1d07da29b4e8ad92e3b50a268d3d9ebad86988ce824c8e063e9839b71ed31df83b31d3ed2ef665fc196c179c4779830de677b1b212644aeb8300b2fbaaf072fe6610582044805080049072ef719b896f41956fecd86448330c4626d6afbf570bf8a472c9b886fd11aac5b8181f749ee8d5fc4b0fa9613ad29f6bb6f490d7723e7ffd1a7a1b9881762b5db9ede82f25d2d6abc1183f26219b7106bdd47b3472ccc3971bc5b4b76ba83dba41277d74e95bc8d9106b8b0edc57a609fa854f427291a564efcb9391094c3841db6c0f3a3b8f843e34681ecb282ed60e3f00999472542ae76ffa3773f8a46cb141d4fea4c8edadfc6038dd3b389bdedacf74124a722ff9f5a6e676322962dc55a6ff08e01b49558e18c1add4307ee01af3bd04884287ca087f8f5dff4e04f80b8c7435226ee423cd16a0d42283e4ee4c5d57415c72bc694ed4b1e7cba4bf1d5c85823542f9760d15d5dcde41f914d765e8475476681e617cf572d64b1a6c1718616d30bb9fde287a07ecbacfab39a9254fc94f5e72fcbe4950cff177fcaec94d8d02ceaf887a5dc7312786d3b6de5447ae53811a725b197c0fc84b52f1d4cf6ba3e81a8592f4ec85c670ad482f41056843c6809d5b63d3f7e52336ceb4436fdae789304b42db4d6a6a7570afa3d4558aff0eef8d728c29f7c2648d577ef7038a980c13840672a39109e499991471ebaf9d3dc06711c7c08f3004bdf00a6f62da068e3149a2d42662f8f797a360885946577337cd72a54a1db092adff9f6aabd444c6098b8ce10ca47c4fc4bf8672d32dc2b4acd7725b2659ddf81f44ea93f0c402b3c7dafb7a546c70583855196d4e8502188e06726c337c69f2e1e1594f37ad907cf751264c922c227d1516fdeef6cd6220cd041ca8acebbe0fcf47b2428c8a9b1afa66c662737110cbcd8e7a7b64699ce44de810d640cdcac7bde4c2b3aa931e376dd9b3a8572d3058fb05e08cbd471f05ec3972f537ae8fe15fef9363f97a7cd84919e2adea038a22220de9fe3187034359c772209de0199930f436ae486a62963dfbf3ce0aaabfc53dfe024f838d018f484772db3fcbaa1931d473a5ac6f7f6b41b1bf18d3e94a0ed97c410a9a74071a567372525eb19e9abfa26f46e59b5570a927affd09d80ed7c2d79ea909bc3cc7a55a72fdd7dd06c5241da290653564c2ea77277e425cf3d470f85081f6a4668128dc7287fbef609ab041b92978ac2eb4dd2044cf48b25b4ecf40baa3775764b29c5e720bac9ab544db76fec7de0b6756d21d34869688ea7b57ba4e8b0d2f7c5c9a2c16c5d0627a40648439900811f4d9f45a408bf7eb433608f854f3eb413b26a89423b23d57a72f7d5949d8d76ed5b90edf10f4c46b2ad996593be329ea9054c48e726bb826e3507cfe3e282a7292a6926bf76eefece4fd5fcd2446d0f86010686572df317241b7b10e67a83ff3b3a2796477f5f5f1cdde40096b483029382e2e0f4c244409098a0a5f682469fbb0a6d778ae42cb906134c34d645ef7103d7ace171252644644eb40741c3655e5db82d9d9a5de222cf06de36d3cdc3d98e988ca8f5cecadc67c0ce336b75a5b6d9f60eac11bfd946b2e8ef2f7ef5bc445f77c94f13847fdd3728d186d9d746cad92809301af047b1a0b247e1ed07b36230c237ac92cde6cb415815fccb3d04d8d4fa197a6864178c7ec06d3fffc2cf9d1612b43d8725efe1b84b1e9f6f3744a855761a39e1fc835e69deaf406822c0e3d818b1d7472ad176696f6555f584b0eb4d35b755da6b6498007acdcecb0d05e2578c56f21463911bb7d2826498b56a1776456ac310f9b0e944ed551105365b632dbc044b67282303206a674012d49e933c1f71f37f14337a6bec6a6e3bfe43a7232e7760572a6a5f9268e43cf647e1f1b6a1c4929c79331d4fbbf9a8768688c80a3583e7b1000b5251c75252ad10971629d1d53122607f66895042bff830b9614ae168d8b72caeaa046ca97f517269265cc3234227358fa7902ceefce3ce5ed7ccfe7c9fe720be7f8b19473d997b305821f337ff9902deeeb5ef97036150b6a3e15b745e919fbccf5e82c6b26119508a61c5be5194a97063dd3f61687ba9d0298d9e0d27272364f02de5a76de718d98f00ab782f14d6e3bcea03d22b7d939e4f2c8f4b4f072ecb23080501483620a86de60ae7d5c202c6d3e0505585b1415986988d9f6724143fc0c983a7d07266940b83b2a3d2998bd968e37476f1a2a781c6060f6380a3b7696f598e8ea1e25884bffb41580e6604c7e54d87a543668908e39f61791fc727df48a521af9689738087b469b648731a95b6e52b6eeec9894212df7f7f59072add56b70a81efc640ae6a8ba159f4722e5fed0ad0b47bdd0aed0597cfb78fc72c27d784fe9d564ac7f7dbd6702aa5c4fd649b4658a88552d1d71f83961a9c232b9bc17e2063c50f9f512c6e4182db2f16093813f8058fef73c0399956ec3df2392fb555289dbb18047a79ec8e1a57e332071b496d34fea9c547c0dcf1c8a1b35d209e7948ff3eda11239f660942b7819d96b2611a3fbfa2359761d6a528a0e72e7afb2cacc2b1cbf7a907a0f9e49ddd603e495c484029be5e4c171cf3dea6a72ffd7da80a7430c452317e057fdb687d7f4d727f3421f5798302520b8f0747e7287c52818d942d150409b7459138d2cb6da603f5089abbf6e960769de97d78172eaf083b0812e3bd5bfc4b86e63dabe0caa807a0499b0fb935e9addac93962c72885eea1a77f4724fd34826de1b85600fe2ddb80f1f3a668440291ff8fc57237237af3ae93832bbf17d0f741b4287a053bd6543d1ae8d0affe2b19338a0b05c7217a5fcbe621fa728ee8c7cef0d5c8bbd35be3609bc094803aab44f10018e9c72264f0bf0d39c7166f98cda6f2c89a442a6aa953ca4aab2b008536993c1b2dd1ca6819a00099490e565dd6a372b8466d5190cd3ece362e66aa463a830cb031528baa7b84d373526e998ef7d52e02058979399e0549ca5d643f86f8dfebfcf727232e2c54e02c98c47fde8f0c59eb0731814896f4a1b5581b8b41048c81d5d6a721b2d45313295437e4b3b2411ca58f039c5767556173d84a9053996d5d234eb722d54b16f13813dd59f9fcc7cb52281601778f9e2da391136908f2f897bf7160055645b08db44ba55f990b266036f084139e382e04954217a382b64337d3b297257db8d66dd230f90f12adead217cce01c37a6e214615273a055fa8dc5b851572dc23c44c5bcd0dcc2bd692f8d813c06a5d1fb1f7a81088320632cc3859641d725f0e275afe132ed4729994b3c4840e56959dbbb067400b6f1d8db099f40b957292ce27c4d5b1ad2300121081ff140615d141a1065e9ee17b5a94a6452d00db72d3a5a93ed1a0e53714530946e37809bd771fe85434b9309cca5072c793049d1f46852998b0bc424c5ff4660be15625d6a36cb696e6f860129db6a2ee52a41d72ef2ac29d50e5b055a686796cc96b573d69d63860fd1e07c00657893758c3f53e7bc905bd8d4fa1bd9d1df12ae64752d8dfad75d62aedfa612ad65603074fc425cc23a024c48a763b29eac2cb4e030a42511e077759eb4e5147a879b04e4a8a721e654b062eea35b7f59a68532b048e3141a1e29465f523be1cc753094df1aa03e0bf8529d88a053af590f65c410c7efda091fefc1423b4d25c172251aca5891da6943b2d860ea663715ef58bd55a9a9f151784a0f464e395e2343e1b41d9d2584bdd2dcde2ffe050157ab9a121584bc0b6fc3723cff79ea7fd05289d08c34772fe7b564bf2c642df0fcba01a388daf05e3e78db7dc01142e5ec72decaf936e2ea3947469e6d5734ce292ab7bf5d337bd0eac960d168e8852393579ffe4c01c727518f8d33b3a2284eff1dda93f10de406b2e6bfb9519cfcde121b65bff8ce07288c2cbed73458ddb7459cb15991423d938f46214b317b8677ee7abaf4e5cef7223606126a1b9cab16d26a9e66a18b91d9ea2697fb02ebe00e8a872f41bb71272fa12d272c4d29e4d2259aefa5f9ae1ef59868a344dfc7b3b02a8a53c9bd34c723366e561311f5795e4b37b0f65505ea1f3a3acdc761caeee5bcebdb84c4c2904a18fb6632699d21171c59b01c71d3bc3083d1d5387d441f3c647ef4858caf0727a1234909e9b261c7f5fcc39b696cf140418f3908bc0f60876aa372af379d219f2499f200d0cb4061c33064fdd5b55a57dc37db04e05d2481ac1039927ec30725f32725a75824ee177ba6ce84428efe6363dae6615a51e3c576c005ac6275b72eb83270533e4ae05b80a61cdea9f135d1a5ab3ee9628c58e34bc5a4047d46b4ae32a49b6c5fe57a83793c77c749831150b8ebe4f831488020ec8b93c9d02415bc1446ccf06633993c56a00dcb349476711d9191b35ab00e4a3f6bf163d27667292adf367194d3d7fd3673efd784182df1cc207b0c34340caf0222df036a9e272c7fde155c026ddfc66f08daf63ccd8ef25e57c625b88d9fd9dae11aba732b03a48eb6544646fe354409ffe78506fbc953adf0617c50ee441fe019c08a6b76621e78a61e8d8e118608a1137847d974ec5532d1186227d2121d69fa5f1b2bcb92565fa50a8840051a322b556a0e459b29de64dd2300e8803bdb1ffd30a41597272dc04658f347b9cd9096d05a86b1d4b78e025f56943bd76b26677f266fafb8f72cffd6c0a3398302b6a2dc1c079c2998de3cfd3e48f3205055e1e01d2953b361ab8658d62d74020874fa7f49f76f4beef61453c3c53a28dfc724ae3318ca61a6eb8e34bcb6a00fd4638b9681718278b2d281812e9c3cb603c7127ad06586e57723e9196f8d5994f6b5853b2c8eaf7094a74b2680c21b969459497ef30a76799726cdd931afe5195c0e2896d798bbce59a0885d1ffc50e2cb6587e2b717a22c34cd52946df875ed599c678e9aab1a2a82d7b460b91f2103647a8aaab9cb1e06472c74af4cd2d96ad827c79e1cc471e81bb33cc58e9fa18368b3963a215ff9bb272a4d3ff6318804764e5d2916c5caa450c5a355dd3575fd0adaadb087f869a2172ac7b67b87fbed3d026b1e09d51a0b9bf2361dee9915a655d4b8269359c08004b23fdd57eee24152e5f7b87c769cd9ebe8769b887195d8a6198c077a4aca92b72e942e012b178cd10f982449cc724725060e202b16f45d24ff6a7c672fa2bcc17c2fe3c9933d3a1674cd1c1b03b808c527c5040885ec40c54fc5d3cf55badb4726865f37bffd341e7d7cd7c97a428edfbf9dafeb36df73055d4d73b7e6e96ce729953ce77b84a6c0e3c45e1677c2bd9535df49cf5c7a1e2d310fbaf1b8652a0728fdd15a027437d175f59e26631609025b3238f96b0e0141237fb704201487b30d1844d8f4b2c365f3238e3db95398515c619d3d23ed03abedab073a2716a2d72c1c8849f2d2d7d3dc76305fddd431da2ace81a4de437d02d81072da7e96e9066f03e7ff8334f2769710e3f63aa051eab3f277ead6f62263bcf8d1bce5d1e5072538d8e070f39198140425637a1c9d5acba921715055a817cc285a71ffebda9496a53f5add2f8005dd0a5ab035324af63b5b3955bf81d347390d866d25e7b7e725c79139d8e79e7200aa7d1430d1d5908d62699dc7d9c8ed644fa3971b33c58723fc452c146cefc652b27c8327c84421d8c3221b3a9ba75003ea5d6803f153072ab8d23953f79afbc5ffc6c65205f84c172f20b402f0ddf776194abd721bc5b567115c61ec16739ef3d05af770f092dec257c0fcda1289328805419125d985c72b70477b7537c52c362bbaaeb54511d69171b09b6b0374d7e4ad13b113640ec5e052e2b2d8b5c363ab725609f3136718291358768e620cdf7106798df33e0b772efeccff70e2e0e1515f2005c572954a201483e34244763d94c6fe555427f0d72d20b15be8208c69f1369d1a9b1d7ee26fb23af6433131435a2db8e9841c94c4072eff895e7346c0a105d67825e79eb9f32afa5123db17cb49828d849e05da561695f6a9c15cea67a4bc3ce9e26fa95a342ce815c55d8f157456eaf4712cf31231e2761591f3fa92394e1692f21e72f307eead6d308f7f476248938569f47a015e52216a0bdbe0f2242bbe9b52889eb0ad9e571fa3e3dfc6b6605f6fc1e6d7c729fd7577b28fc8f65411bad1756492272769a7d96d177b193857dec4f31f0ea127cb58a0ba778b8a1416139554e3eda5c5663b00f6b2eca86f0408fa9afcf7a7224d5ef9d88f9e1e14ab13bcc979289a5ee044ed8f712910f98641478d0a471729842d7fdded8db4927268b9935ba55c3ad9c1dbe51b24e318483b44b96b85107a9450fea23dbdbdf77ef7003dc02bd774db682f7d69dade41c485f841e03b17267a6b42a8ab2c81aba6365aa1db1cfbcce4b939ff325c6c70e80cdfed16e7872828bec62130903aaf0eb8f6139279ccf2b6d6473705379872387f7a873333c72727cf84f938c968768f284650aee31c3db1351b3f1fd3c5e8601c66ebf945f729699ddf4c4e22a671554e60eeb8c68b87b82a93930ba3f0ad4e85439fa7ffb72a010b79703868d48165df07ad44f62969f72ac0083d4b2a448c04af810eb6148dd7e6ea421c70e539235c3ebcd5428585aa571341968ef8b765114c84e7928722508739056e814c4abac4749b1f242e128e267628e68bd45e4c3c7c0a16cb6515cb49c0bb2501e901a3ffe5427733411a06c1ee108de09a80764bfe3db651343160ea5fbe385342ac12728173d1b556b67cc490cc43ce4dc584f97b11f86a061277c587932e55267a6fc1498441343864de41082b65ec3bff06537d5320312728be6783e5a35b82e6c4060c2c9ea3dd00aa9d71aadeb028df426a28737bef0421a3cc2b4ad16cf695d04553a89fea84a79e73e54ee0dbe44b23354e9bde1f0727a5ee2501ae1c55983bbf7b1b145d0e9379cfee77f18688724afa84aa5ea1b3d1b03ca43748237d06836d627e38c538c2406cc3e9e746d538e0bab765bc48372d9f58f723ed61f8e7e989990f4e28e2d74eeff5021076e8bb0233343b80186097070056e0fb4a782c4d23c8cfbe816157fd029dfbf04aff3a1ececc1c6a86d72e4452e405e911df15303924ca0063dce408e022513a64a4246a1b3f3e56843040b72c105939da57eafab8212fcbfe03232a6c1443d5e70cd607f7c47032d5b7246e94f3e887f741c85758f5b15853e6606f7d91cec91f8db7a1e10239d760a7206651930ca7a196aff1eab7d925fa7070cd36bbf55d48332a0a4a58ce75cd04c9e96c32099d65a73c06135c609765948ef969228d6a031aa1810bde4e639e272600eaec52c01ad52497875dc4e38047e832a23e9cd8bc22cd4c35803c47e1d72440a42625a2be9b1936940b1f771f3b36783ca9be6115fd2746eb47fd540177249d1ebac48d5e14e198ba00223cfae8a5e6ac57b3f759f3d764509cb7d1f66561de98e8ee989dc118e1b926570fb49041718a55531ba3866e87f50bea9382e1c68969f453b140528c290868d50655d9c04bbca0b0674fbc8de5a3c2f1bd395436121c7629eafcc6442f1b0d1783a75bba6021eb348d00970fcca99d49e85d8350015dafc0f2b66659ac5a293c3d7a40a914259e4cc28409aa7b9a2b0beb7f93bca26eeb3b1cdc68a00e73144f2e5913ec4423c2dadc334194035d70a97b4e8722fc5f0ea87b867903af3c288a830e1401daef3ea7c09a019e41e16123dc1a00bc2177569b6411d5c61d9deed44300db3d4901be094b948c8d31261c18e0b900be4d1a7160469ff754e303044d4f13b7781259f4026572ec20e7f595ca28e2072e9bce9a6bc175897ae01fed89eecd1a3df3ae911ea9f1b791ff13ad20d7ae0729b22e8082d7d5ea6803388f75f85ef06def8a48c66cf60f4b657e89634cb0f72946b15791ab41e20985510eb640518b93dd144d617ad6eee610b2d2dc3160f3bde6b7d5b872413c29a674f6dc58c32610136671d9710a9ba02081307186bf67205f3a07098161649a4ec986482a48d5ea8f62afc9553e3da63279899c8482a72d20d8325d79ad72c4d2f8323f4a3465a47b3d5abc7635d20c4ac2a807e47b85cfcc5e1002630f0f3cf56a41218b6be23ee3634ad09d3118df81744fa7f6abb3a8473c7dab1ef204b8a628a91b9fcc0278f9c29f34cbf9e7a4916a4f1a541596023784a345367554d2859878b07b0931823bf3f60468e86e4bd9da586270ef872c43ee62f6545baf22ca0f6d91ad066373f8b6dc01927e277c50ee50f2d461d6f16aacf0d246402bd775057d1fbc213a6a46ae9f7ca158db9eaf54f9604cc994a6f28c71670581d7c4a33009440ba2148ed8d69b5888fa36578892635739ffa72dedec3763ffbeece969362403ddf3e44a137f374d08e9aa0c21dc8b6b4664272a19d060048d634d21d7c680b50792545963c0e18a3bed33ec191b92d33de21721a01e0957934b0e2ae3fc6594fd2ef1130145552e72e1b6741c893c63d30dc7202d21b4aa3566dbd29afd1352c43c3c776c30f7f17afe7eb463a24bc8bc69b727f7f4dff745556cb09014c31311212f8efda581514a71d46f2d5ecd4eb6fdf72bacbe8f2bfd93bf6fe2f5d581f0249005d00a3d751381cc4b5cae34318ceba2a515dcd3e84e7fbb3316f8b14e9eb502ce8b330e44ff1311a033c8087fee52b51804ac0a7441f01bb6067f91ebd61e7fdc5a413de24eefb3f122092b9556eb04f70414a60f1e2a420541b3ba8458b866c4afd9125bebed599f219fff7670103720b6131c558e9b4e2b5a924eb739249a9199baa77f6ef5724f657ddbbbd14cd7225605c77980cd98f69c20177e08ed70b555770e4f8466a20a24276d49606136be664f4454ed6efdb2f427bf2346c2e9fe225f28aa007e985ddbda7b37e2d95726960de6f2f4871e28c479fb82bac3e9e673b0d0a9e6096c5d2f47161aa07a172be1ad1ad8c6a47fca8fd6e54d5b9353210dc1db58f6aa24a61d4f56966b372406507bbefa162483db023f74e9afd45dcc788ddbaa620fd3df8f1a831fc40fc72f61b34b7ca0066b26d78e27d720aaf596bde6ca8cc3402cfd9ff3f6a73fd2e72769ff5d94a13ccf8d1e2da641559cdcc241ad4e0f2977c6407c5d647b9252a5f0495f62802473b273c8447afb198de7f1407bfc5e64edef41705c0af60b17d1ef0324de72170125048af835c251d7f27ba26fcba6640dd30fd7f9bde0ba9bb5ad466cb80e0bac3ac80526b1fb9c916f5d2d2b8721a2cc0210eea6a2c6df1510f914ebce51960faca4c7fd55cd47689dee9c423a50b0a31ebb685b5a995da7272cbf68b1184a0c2f8ed6c9ce8b13d2c3bc99a503c886a5e4ade0d34cb62f6717297602d6262ca33d88e01e399184cb89be4cce09867b489d5348a24752f31ac7207414bf12538a1c37b550033d921e8b2af19ad2c0a3493d1c2bc20e987285a728fd9cf3ad40e97b99541be472b80caea7535c41216281d0ef688628436d9a532ac91f64d661c130601adf9dc9c2eaf7ecf24b2e52c7c2e333d730e864385e615fd44182e1b990b4f2f98cbb73f652755eea357bfc64143711469983fd843b272495c55bce4a4e0ddf7dee65768962008d4532f805cf23d25187709b22f9ceb7214902b9c435994c49e1e8a6224df55603da57f0a39ec3de5fe079ebbf2ba207279f4a05e8b20973c4c470857597d46ba88b4ff9fee9a27b1c812310c6820bf72a0efdf9c42c7a1cd55808726af4c430e6c0821b3bf52fc9dcb7f563d45e3d547c7be16c887dd27811ca0824a20d56256e8dc08d96c196f66bcb8e6b237b9f27263e09b644cc44ee1a4ac45e21131a2be92305d084d6fa61bc0c4e23bda69787264750bef99187593ab565dd6d8edb697f3e9185cedb5ae35ab7c435d99c05e726a02081093e507aa63db54a605777a97e193d57410bf74241cd5e88d6e3171725fdc3a4dc0c7225fabc0caa28272639decd0ecf36c494d917a396fc941256c72a8270cb717b45f42e89c9a5b0c21bccef077e9089a6150125aa9664747cbdf72f4bf0d5761cf390601946c7e049d782bb7acc60c1dbcbd3a9b356b0576274772de801c827c2c2d3b3f96425e5f9801ed02986b3682ea7124d19aa7c41dc039729a099b81ba09c62f131921cbee491d44126b44021fd4d7b5fb12ab6b16dc2a7200acaf2e078b77319cad62810e5e95501e21096e679d31c1d4e5064ad6e5e972f4b6c083ada1c2f236e845a6ad06c842f74a27072577b131e4de71d98602e04bd4301d60751331fe8b92278dbc3f91a2741d74aff252e9373349c3528c382756274a5c5db5c429710406d139097000d7ded66879447f8144210357a0d2462372d460a7d068eb262c722d9569f5ca66d7c3bd751213e363f367abbaee309c7f72b9383ea8f24f17bf730c81c118e5ba258ef4986c9620d673ddfe11415b25b172abab6362bc4152f4927ac643b424014fb827bfcaab38640abdcd046097167b47657e3e6dab3137bedd8f770e1a9d2cf1facd2b59a56d759a2f5661e21112cc35a59f316a75ff31c260cb7a5093eb2062a8623672084b9fb862405677fcb802723bb9d61f3944f85656ae42059fb92d2a8c448f545b0dcf0f7976afa13225d442f575f058c676101cf1d4ca78fa24a8a629ebcb81cbd37dd2d890f17d8910da72e7b649c7da8a175d3e5ab50f91fb21589c0c581f1c30edd2799faa63cc7a300e0acce9403fa3995103151aa8452942f022e221f33745e16796d4df30c89bf672506b5abebb08072b4ae718864230aab48cef03608b1cb675e8ee0425cc8ce07285986ecc7c6306571cb4c3ecb22cc4d75459dd11584e6f3d7a3decba2db541084b1a53b07443b825cbd157efc120ca0a6f9e16aee7c139e6fc88315a2011a903820a186f5136f81bc4fda9afb87ea3762026d1e9ec8cbc175d41aa32ddb6a772a4a0b40ce04a400b213cd7cc4eed5ae0022095f1018f7f78366d74a916832972613333e1d7d2f1b940228308e39a16264d95decd2dc4d42e4183aa8055fc1057dc5cf1f4114dc15609f551a8e0cd8e3046241d8002fd65443d4dba01262e7272159aff7374c42b73116867c88055187f446dc3eaa6a2e68bfe4e427a3a125f722b02fc0bcb4c423d2d51af88b8cdbab455174f38de14ae2a1af666d1a9d138728cb775b1379fe3f010b1349478f310a347fdc290173b8138ac444170c617bb67006dc2a2a37bafa109b3dd5cde25d8c21e8d419a80cf1009e73e27c69ae0ab72ab00f3bdc5024dd600bdff1dc754e89aa705ebec95e139724bb9c8054e59bb721097e57889b321df41b863bec825c7ca549b3247527ebe0c4022631b5d63742865c37f22718096313d8971b93f4901e71b9d6acd14d264fc6a576a8b2a39937267cc30c2771706f30cad13a481cc4e670d4ad08ef45faeb14a96722f0bdc6272faf2e96aadf3e9ce723b43803283f7cd1f78d33ce9017ca1c8b9287fd0871f01b37ac9eafda3502d993b7f5c8e0ea60367a6873b82f07315f88eb89b559174727b1e46c9e94dd403a6d492289db26a2fae3c495f9541361c74e0a80114038a2ebf09cb928c6abcc8d83c7f94b16f6ebc6c61d4731cf471f610ab26350f63d6729cf769247d676f609c7286f87a11995fbf6dcda08baa1494b4879e36b6ebc572e8ae550b98001a0de1a75666b28e1ba8828e5caa217385a2bd5cd93bde8f197231ac5c42ba0a6e12255acd6088fefed85a755f4af4d9af253555863d25e7d772a84a7796fb653f126b7d3d6bc5db602e7f7b9bf7dd4dc3b9033b50709e2d6872f05a28aa613b182f290e469a950d82177999ba11f09794a3ac49adfd25c3c34b93138703022d0b86a52fe1c8c544f61bff3df33e3240f198b9f0619e7a9eba722bcdb9bd257cd92065e47b3945669e79279ec0bc0995b8264d1de5336eb5ed7223dfafb74009524f0d58124d50d244e21485f6d9488c4142c6cd4de430a50e6635e0b6ba93cdd3ac4292ee26a52fce27dfad1d091c653834a117d0744793fc72b0310d9ed84253d3bbc056e142cf14b33083a21f9687c6fb05e5d6f24e3cfe72d0404a182b2d9011cdceb4e477ce71e2918116c2d44929284d7f46bbf18416394178bcd1ebce09819c58598b873beaac5cbbd0182d7081247bb0438a60c82972694b9675b8ece2274b06e0a01dbc2146634b3d097d2358b0a36a108ff8b7bd7284c76a795d83db1e0c4b9489361e4a05124203f8054ef3b769c1cc2598a631728c416a9ae2a303f353f224d9f9e8c8ab5a428f42833632fe6f73516039dc0e47e3aa00061036cd27903268cf9bd9f114b032abd004ebee0ca94cb4553403ad72ba6269c4fc2af52a79e0a0ef1e994d7711f2f019fff08327bd8f0444ed1e4172bb8f1dfe06391778f13439ad2f64a9ad5198ba6ce1c5ffebfcaa2458bac62e1dbd4c8a803b117f59cd45b64d3500021441f59a2a7b40a4c66b64bcf9a6ce8a726367393887dc7b776b7225dbdb56aeef3d382ab82527e492359c63834db87d5990f8423e3a5bbd28a0e735a9ccf1e1cf4d21fda09820363d44c479b2dbf50c72bdbb23e8ffb0a4beeb9f4da5d9bb47a45c25e1ab71c1488bd3124b6b67712f7229c6c5bb9a3d6aa810bb0dcaa76b114fe0458612f33efdfbc9f581e39dc9a07208a89416fb0289c8ff0039d486a1a8da0b256407da62542528ca1a9d8c3a3756e6758f30065b3fff5699d59331f5be4bb409ceea52ab3cd9d268ed3398732972e1463296ab38c2b42adef1671eaeeedb73e46880f2dac6f6400619b693fb2f5f91a3901b32d18e0954a557c3b6cd9533ae0c85b79b9f249bc6a1ef4f5f797b0676836244997598544b5404dd1c6899ccb0d92ad9eb967535212822acb3c67c7224a78a49f1739d7fbfc784a39db8a468e9fd6e6c31acc4947085ab0cead061418857ef14ded750a02c4f97649692c257f92ff918aa8e257846300c05d96dc0725eaa6c4ba2c45319f45794027fd9dc65c35f2507ffb2ff0f6b1b4af440030172e003d14d0e854759f6159a0c549a86fb3f6a5cb8655a60a719d9a3b8eff95f3a420f1a89ebc8874f1cbf4f076e3e8ad982e40f0d8eceb4a57fb816150d16690906958ba59a02f1ab8cb0fd31d55b676fc9aa959b6769a20f051b5594b6b0ae7205148c8c199cf8840987880815180debaaab1dc46508a4f3873748489b676f722639343b427c275ff4a750b8e8e6a7196944d6f1e258e93d1d72f4a842f21628b26bc867d86addc5595e009dcb368ae93a7c20c51014ab66122bcb31af039772f975204b2706173aac5826f0e741a528e8b18d458663c33295ab0f9a4b2c7872e984fa8e72bfeccddd91007e1107a068f12e7d618313412a792ba1915ccfe472b1caf44a1d1042d46421a16890043a6342934eef2df08c414b0e4e2cde83107261923cc3f59ae0776ad70c4dd12c388422eab4fe51c3d7d97feee2444bd4f772c323c3f351551d5833159cdfaf244c1dfc2a74b681fe9310964331caaf5dc372406841d58fab54a4c0c7403a7cedec219ff5b3afab4aa5edaf83011c7961f6728516ff40643f3627baa176d0a6df290a9109ce448c3b9599364ee5d91b4f195ab03e79f700b77589b343444df74f8a5ea7fabf9ff7b6ff9ecee7eb174bbb5370d1a254e03d9f1c7e12e3a350639026dc96536a664c7d93c86bbb7ea9cb02df72bd360431b168775703341783b0fcdfa1fa16ae103f2084c0646ad03523c8a0512fc47363ca0ffd23ec8ef1086244cdbf61a3399459a121321f27515f4452236e6ad41d558970af8e1711d5877b2b9bebfdec1d6ecc5560be5885bbae15e24a728792d03a7282965ab5117ba52cf70d395e1bb9ff7a766131a917935450a6c069af3963fa09f8889a73c31520379b1fe942ed9ccabd738c94485d59a47cd4f072aad8810e064e7e2ed86faceea66d0bdb48e5bcd7032681b459914b09adc0db72a1ab63c39ce49ad0a26b29c6c7fb3675464c6ffdc007fae9686435066dbf6772d165d9a4e5090b72c254fecceec65a43cdc354621e1242b33305a2e66172db72af55d9097ae53fe4b8e20de3e89dce4d3bc30bec00700e6d8d14caf069ca8c0fc938a8efed355572d2634a07bfba37774b6076fc5827cb57a6d2092b565f1872dd38285ad24af2e0f6b9d0b4a1dcf66bbf9f873d2cc2e605a4220651c3ae8972a40a22b2738030ec18d0771172434750cf85907a4748fa602bf1f14c43343255ac4b74f21e6d3358165eb9f1a79249deb2b86bd6b61f85965445e528b23bef72bf878728fed481d263eb950ea531030a1ab4885b4fb540a301526841aea5db121dbdbc19b50b8c409610a3b2adadde01640ce93541c723958a97e6ca5cbbd659f22c10322724fcffd9f63393fb813563c5e89db310062d7401f15db6f040a972c366c5d7014c32b5019359e7fc5a6748f359bd1c581ffd1ccd49b3ed030d9f720315ae53ad3d8b73a5e7b472655dbf683193ea629c5f4457c214ebcd9c0fb120d6da3c4eefd1335816ea7447fc9db4cb29c5a0a1cac408cc0e3e8d973ac76c72eedc55d97097dd2c80533fd4aae5f28566aeed273a70a58e8df926cf835f753060e2d5300e4e68cbf4d31dfd1ef75b99daa87967887da45cdad65fb2d2ecf322db592103aa42745b608c11a21bdd8476d88568fd303b46b5b45062b154d8f372a0459e5c498350fcaf55ac9ad5c2d9fc9ef773ce4d6d9b4e3a26c5405a7201412c89c4628500e98a1de9dbeb88903758be4ca327af82aea86b8283a45b572f72279b4f0ef97999b3319d7a7a3b1edbd83a084582d46cae69e70c73fcdd87765de1f8d0e6cb1dfa437161c0b1e551adfe3899408a03fa4bc9991d3e32fa330f72295e5afde6d0ab63b3d41554d685bfd1a2fbbdb0903e8c740b7ac4301c14450902ccee1690815508f8ae52ab21b49bd21c5659bc3da2a38bc89c6670a1bb9772270637817d375e24bcad8aeecd0459860fca00f8f067238c4be98036a45a627215afefa2e72a4fdd4f4149b999903f48db40d68087b8682df7cd319e240174723a31cc11248d8176b46fa407f25cd38521d09f905b9f266fc69838f151624d72056e8fa7bfd39f79d20183d12e88ff5b0ead214b8881221bf85d45513ca05872d7395f6cd2976579fb5b585f87b2477c47a35d2f8235714d8ed9721bd6b019723b149475964b3a1b5922317cc07471bd5c0096395d3cd109274e767134e34a5619978578cd21ae22fd2c7a9489e270fc7a6778d651431c35b01208580d837072ef1caeb1a7a6ff2eb88a9bcb98ff2767618aea7ac4ba9fd697a04bdafb78984a51a5cc87342347f651c90e2d6539a0c196b2736d84ce93c385ac5a5005831c72c6cc5fce1ae9bdcb8fe07cecc08f6ed9387a9c88557199c3150cac843e29bf244b918a23ae5baf31845b9c83fadef69a467ed69cfb7741245c738b64a396437251bd3be6268057592108a09e6c34b50e4dc15040de9335daa128f6d61d17774265fadc3222c6b57154f0b00002fcea4c2efe35215e67ec6f912828b646a1af6efc8c26533d71d9daf3779b647898bd26f3eb98d98741f6ed5414a4ec1a9e822ec7ce1c9122e30b1262612c2c6b6819d3e02b4870baa7dc943548e132dbb87860027cb65c2f854f72875ee4148be0efd2b8238179088e9bda17a6b3f2314a591c205762cab325945b45129530b6ea023ffe93699e3bd996230a28d17219f0b0726357fc880b7713ad8c855e6cbf477fa2f910f8ed315b2bb4e7ddff48150c370af43aa07ec821c760bf37af865111c08fea888d1cc59b0b5912b02a23a523907298f0443cf68e57189065998c5be9564059870b7f6dcdc54f7ef431a41e22f972470669170a47ff5280aeda47c8309b3fd7ac90c25f6df3d11b3310310b57e45c1c97a7029741d6a57187c203b8af9d7474d736aaca585203f016a876927b743bf4aaac44e8d9c4ef524ff368b2043afe35d030eb7d268dfcd5d935deba7ca4726938b8f563ae30793399919a3915d4b86da5aba0316be15132f29cca64f7b31dbb2ae277f22ac8245856077214065c00a572e7cff5589d8b429a5f59b48a5572ab47ed754f3b6c7e45ff8e272016f807000f6df9598a04a44c40f8de69ce9f729b7038ee4442084a89b44a470bd3fd87c15dd6dad2a7747e780b36ed7fef61352f61d2f782c95bd5804d3a10572440a5e6d9ae496583b6ab059542a1d58ae0729e5932ddbf7d2d790c41d70c8d6f2aed50bf4d57c9df40b06fcd2fa8ae6c5f722e2d49b51b9cee35c31aba30c457b560faf290ac56b8b64f2c24291bcac4e3343a6a4c1ce7e97a8b92e66fe8711631a7af669c0af077490464c57a5ede65c1017aee728fd85234d178beeb51fd2b8b7b55bb5bc3dff708cabd34b0ae0b2599411419c7efddf39164d3b5dfea21e8728b067d9ef551608c7961fdade46ad58328cb8371b2396e53d2b5f5360d70f1ef5b86cfe2bec1e915c58aaa7904e53c4241adbb890fde5f7ec8e7bf925fc3b7995085855f32fffa20b0f09d422866340728614f45df818aebc92c945b43ada7801bd5bc2ced39b67fe496697098b00ae27255ac3ecc0d01273099af6d1c86e535b139e6c9fd8c76f29eb5db7f84263f33098d96774abdca47582fbbfd7ae021a947a0c36ac4311f264423f0c6115d511b727de8787da682bbef193b52d6e6b19bca038c19679509326a276c3c04441afd120ac9eb5c4494ab337c145c68ddbf659e519e52cbfc121107e28ad338ed53e37236323bd13338715aa5879f18f8637db0c1b867adcc8f73a8843baf775be9197235583952b1a54092396116102feb33a05479ba37d7605e08ba07a1e1809a58598fc9f935c698ac44fe37d4067d2ef8e5ec42802f60f7499eadacde675340003cb06b557a30acb740a6f01db61506ba960df4890b5fd28016159293980285e872e21e76324bc54afddfb5db397225c8edf4a9c4ebf4d8e6b1d0a718d01695763039336afd777980a78b4554ee9001276fc5af21c216e7cf76ee894d3fa6fefd727553eb0429cca9e970e5cdba145830be82fc3f554e1e49e1d8ac06df85cfa072405dd6987247e00465eb93a1e46f75e7043874c9f1d0a5ce29e5e22d3f87f67207aea37915105cd7b64cae4281a5a0c9ec925d721dc0f139cff561ee1dfc05723600ae647ff9984f2c1301342e9c3df73cb162b2ac9fc14a30b7fc61a7a0a3723334b52b92b1aaf304e4eaada967897ea12898d45a6882919c4f2f000568d372b1db3c55230268ee8e18c7d54ba03b29387c17868acdc8c6e725710f91aa2d720946a0ced349f18f881e80c05b23e99b6f2b498ada48960c8735d9cd3e1ce672bf83e4be2789cca5ac66d3d2b91211bf2c6af01b03ee74821b56a07503c05d512ee0951a1a796717272ff06ddd67c81ce895f11255abc9baf5a5a1f05b2c5935225a838849982c6ef75d149ea4eac17dcabdad2fef47daf85e3558616430af1781e34aaed3309607257b02db01671615ec01617478a1603bf241d9ea8ac3d07267389fda30e314d4c4e3ea37d2c661078b51dfd597b2428d3b2ae64a8d95b6724d6e58d882fc93fd17059f0c64d969f539883fd986121d595d7ada8006a2a641daf7cc789762a662ebc806bbf070c42e1293bfd261d890eaaa8eb285df3e67723704aeeaf7b61c3d7ebb2819ca4a94d5edc7790ecd39c46ca43dc0c238a0203a6e45f302db85326b92c0e7a857776536c8be5113df3d4db9ffb90a68688298567dc576ff0c8a1995a769aee41333a2bb57fc9976debcf5d7b352f5a74784f9728ffbc1685df02f6b0e60b546b54e817db43920361a60b90809372e06d127f5729c8084db52671647edebe54812ea8686da4f01ef20fc0316cf82c487452c59664546829315978c99daa8787b26a714474a18c71d3fa39e4d701550520d08f4610b79ac04b8545199a6399a758956e821a236531bc378620f05331d078921ef72fbdf128e37a0844ad74f974ce0ba38afbaaabf4900c25187b54013fb50262b72d66b6f5c563a3f646b8c1892abd2d15509687f8a14481954ea48dc05a2419b2378a3563fe4ef768fdf426b001eaa9667cafd5f778dc6a200d0e765980b08897261b430369b915fed1755a5f3f858d5c03960546798526441ba4e1bf814231b72d976345e6512e16ecec8e3d482832e393e99624e986d349885e76ed0183c1372248ebe07c859df886707f4c4c6c73bac4e0b07eb7a4d7d5c7187ec176fb42e0e6b26af644c700217e90c89366f97875c66ee18cc9efd5ffb472e8551d42c18721c35549a910166089afcc426b36ca6a79190342d25cd32eb8515360ce491c57167efe0a68744fcdafc22afa7a787e178dea5761872e7889b61355b3b8f81aa728beed9d9fba75cfdd9b6eecfc549b588962127015e3896800494e9442de69431fccccf982c8c7e39c003a83aabdf8217bf38ceca5b9b2b5c3eee6d203d67dd134d6bd92abebdeb65b5bd13c0762f0090be24943b9af3edab5b026844ea3ead56f66f6d215826218af814a9f113a91e225290af1edb121db0014a709be6176a726aa9283d910facd45697e9657515c7c79ec236145169d0a49c4c0f3ced1123727f6edc594b17421b15c6890aae5ab99d0c279ca408a0b2d403667d34ebba9872a06655bf7610e165aef1c5d17dea7351dafb541fbb315f8f5d85298a8440637214c9d29aa6b05f6a6a8c52bc36171f381eb836977c68dc6dda4874fc18a0c14df48e47b94cd934344a5d6ead0807a48e937a15713e00768c70010e5773371c7207d44a4db130c2babcad25406065382543a6150e376924231928e637fb5233720081e9f815cf714f0b5558d5987fcc8c06280295070c08a9c2f14bc4da728d72ccab6280acc12ee6d63f0679f3346e86e061d6cb8ee9c7dc43ee30b2c9d00a16b09d1072b1a3d6293825758c7433b99c1fe4e3226ace62d29e94d56f0e0c4204e221c3db0fb1c1c6a3e112e8d04cc4ee25e3b0cc9f82d7694babb25627d7c37277132e0b3a9e8fa1f05f2ef616436d24aa0e2afde3efb1b14b87c1118e2b3c1cff1029cc62032067e0ded17ce6edf76df78366a9b9cb8851c60a0e3bf43e9107d0e2aa48004c64b70780f0bc25ce91df38e25a3f467271334e41f0ddbd20d872c1930aecd2daeb4c0f8488972cb7b12096d9a9e2d4e3e44385e0a17df23dd931030b1d847a1cc1df015838d7efe4e31959b5819027af3f933fc7dd7c197323728e00fe18dd76da4316a1a4f11994c1ecfa5813eca75e223790808165d8c89272000043a9d9341fc8157a67f2110ebe52ea76216cd056dee1d7fc52550942f472148d12b9e44572911be907dea29ef7133b78fbc23d890a7ae03df5b032f622720315f81eb22ddd39324f9c279b7c1b81d8ea3767de3d4b93b56f7bfc031bf4034a2b756cbe0e912a0f243c2fc185038a6cafa74a9aaa3b87dd256a2474dce11ee61e060a46a60064acd982f51f32295b9fbe94b0d12c58adab771d57b58cbd29ed2d358b0e8a9375f2f7e36d24a69f3308cfdf1b1e517f7c79081ae39762d211023c38efbe02e44b879e508c212d17817706941215eda46cba4a93c5c9e86e23af6100c935a911e669fb7cb00cb890cc8e2ec4c95cc7dc56cbf4fc8ce2da2872dc4e0936e37b859fec5f71d6a7e9658dba51def00debf7e1344d19d8d6258372203330d99ba7440db97cc3603708ab3370937132255b1024abd91524ce77ae72a7d2f09a640ffd30ef2e97c437cc93825a143d883809ec67e1837b376a62fd722e6fadc1a6e71d93b241ab377cfaaac7e20ea758d2ccf2a76d4be1dacd066a5d4de05e013693384529bbe79160b9a659ba0cdf2527f38e766e3c5131d79b90728ec22dfd6a404c168a4c8b57b8674324ed82c5a070dad9d59311e600b35f2638a8811234cbe337517161b421992d54d0871415c7b0d2fd427d57ec425444037276c067e9bd79a4a21b791617d57e0ed09b346a42de8f849e13918d0e11dd4372bff1e939998b33a4d76b8a2b84fc2ecf6a78444b2b78cbeedea4fae4acde2e72f256f0773864e45b3b30f214e8b0361d97b2be0fee44f96161e7aba2dcefdb729e0c4aec242d4e53b571973462c6df3eca08e2e7a748a9eb119f68b50b51a4721b293e1bc9c60fe8eb25a0bbd32d73ae94f9d8c589dee8035b80e31059bc1072fc4d621e1c10880f9f01269e311e37d5d0fbe80c5b5d98956e13ae06176d0972ac4d878b814a74225d635c39f61225433955452265d26a244abf4709dbd03a320da32188559d699547499020775b402b8242fbb983e4c5742dee433ac1445072b7ba7366701ac73dc64f54bcfe37e73de3fe3ccab2679234b0efce7addc087440976cf7115d6f4b33f5fafe1abb97c8db0573a1d0aab1edd062ae78f81c8a916f5fab9678c090f474c1cd774fec7e7bdb3859092a7250a43ce22b68a5706bf3130b72e6ac254f209ed84c1e779e67dd12aff6be4d61d2ddc99ee9aefd54d5172584049d845f9903e245c45b347633ebd77d922b2727f1f2b9c88c38a5999cf72c3039f736664855291b07a26a48feb94347dc29a06d3f70141f05f63e605b450f747a8ef4db294b63879668b7cf340887004bcba2ecfe95620f7b286d780322487b224a728663186f82cd5d4329e0783201ca5438b5a61aca7c6fa6d116df27208a80b0116665f15d72acb10140774aa1619e99eb380888eb0a636a7de06ec727dc4a8ac8c9bad49dd3852caf1897b19569df903c4cb8076558051cbdd41f87263acabe5a8e976bba6d499d7e0dc652b3a4e342153bf259a2e646422aea3d52ef05dc3bb28395cb981826fc0d0ef5b3f28d10c7f2c0e66b1108e42307397c86c554e1dbf32c068af7747cf114535757222f7183595e55b9ab65729182db6fc31d31b8a623d1686bf733a8bff93ae2d869dd1c3edb7a7822e761ac298a713eb728319d72faf5093888dd0bd8377f7866e9a04c27d4dc1d81c4ba660d60da72843262f159b32a0ab048c9192efaa8b1ee6ac031eb504fc6cf43fb39d32789239346ae6fdf0826fb32ff60f821ce6f8c7e49ac34c959820c16ffcbf00d1f4bd9f3c1370805940032503a2456137eae73abf268aac4b3005897427b0c34c680cd072e2167e3c4a105506624ba3a0663d273cb2d80e1ed15435529a56a73f02e5f2721175450ccdc3168ed48189e0619dad1b3b40575142d93c09b756a3f5f34f771342d196c73bfdbdaa0c7b97805c01ffe3c2e304096102b0c09939322951607c726be4e08fd42ea20496da0c3395a02f8e235f2c85ff4dc0d4714eb7f17a64550a8ca85a78c906660f823bf14b1bc8dc1f376e01d4b316849f418e4ff89327695a0a4ea4bfadeb838b787f3e448bcd57f801e9a34eaaef9c47fbe0b306e0c6a76539fb087c093436d5497bc9ca9b9075006cdf908712e3c96b91f9acb492aac457c037779662204bf8e94474825884ebc20d44fea491a80becba209717057ace14a762abf72f07caecfef6dd5593c069c523e37ed129be6b1ffa7385a76034e10a13d4de60f49f254cd7896c2b805312f7f4c6a853d2100c1e5d995fcb57b8c214a2d2dadca707f6edc275def9ce9cea6f2219170a40180ddfceff5e346b31dc723c47798e2b7951b721b50f86de8cdf89e59f59ad7187e18d0aa748abf1415772496c773bb346a26b6107e441a585e89065bd06881c2049b093f2284f38572308c6edf374d43dc8e2b1e4cf3eb3d65623e84fc49ef1a5e7a5696840a5882f241e81415ad6bf0daa0fc6574b429b6253067a82018804a21b52ed7c4396940a417252180e2bbbe2a3b79404fe59b10e8f05f498416724006147246e195531870730fe02b4f2c4f339745bc5f32a92dd07eca45ad95957fa86e0a48990101721a772b1a849103371aa69abecadfe70e1828d141c2acff3d33e821e55829e36dac62794246cc5ded11dd4de57f2955b62099e64a7e62f9b9fccf144839c4f82b8f50a09b9b470eb3eccd71e3ce80278800e0f91c5b26e6a65d362ac31a815e2bf8c72f1fa21990753f0dbbcb4b5948566ba21cebdc6aed43dbdf3280c849dda991172dbaa310682a415b2552140665aa1fd4ab02b435a7f2a005e6399a0010e5cdf0de921c0139b401b08a9badd96118892f352d05f5709f2324a4dd21af54e432272441feb97aaa0cf54987e66059e1cb02b9c3e0184e5122d0dac2f1cbbd66c0d7238c8c5e4baedf64dd01def912ace3ab6ae1edfde60501ed387858d7c29661e720701e5633bb7ffb9809dbf0039f562bc9ad4c8c80da804b6881c58e4bfea1639bd428521515fdedf23511cdb015545109e8103ab0b09de964959dd06197f1e2eee3b37263b7164ddbb28bc8da19b2523ab2f44d2bd808e0f61aeaa14d1737f721183a06a0e77943ea42644cadd713eaef7b09dbc6197336d2244a0877d6e437230c3569fa2744e75988f3d60ee1d7c601af7aa5cd3db21630041ad8da2e1ad72edaef3d9744081ddd8a7ed088ac372d2c58183f8151a50331cf885fa6d78e172e687a86f4bbd3da87813d6cedb317056c83311609f557929b58b58b25cd6d93af9268a4489f56ee4194763617e8cefc4c9e02c70f2774f80307208ccd581057283419cf0a11fdda09373809c0d12885cf3c2557387747a998be7ef377bb5dd7235f17a748d06440131dc26a643b10852b668d2f8f4bdc7dc3c90d7850a9781726c1308519c085cda69ce9771720e6d37446aab04f1850ad671326241e000bd72041a04a7405a10ac06339c04c077822e18cfccf86ba7d3278c59cf8823b315722e0aec9b01dac241e859291823adc32d73b308f5b29ff489ab90a628ee1cd85e437d3acb89245195660714623871d0401e038b44343dfcb9af661edd54252372a1635e2e0f84d3148abbbbdeebe8374281fc5db1677086c6c965b0ecfb11b1346da2f53f0a57a5b2e39609bd5085ee37d96cba62094893f5fb8e3f4681d1bb2f29071914459048a0b987b5c5135448b7ac6af4b905d17a8b5627abe54b8e6f72d1bac118b9782418672d6f916c457813dacb3ca80c9b42a8c32eead428659672bf4a28155bf37de79129298368380a8cd6c8a406d73d2bb38b5e281ad4641572445bd77da6f2fe4da5d54b94fc888c442c72849b62e7dfbae14e031e10b5f468db03bbf53c410e7b8966b67ad267006547c35c4d5ca6402e7f8eca29d2bdfc0adf7743fa66b8399a2acb60d384b65eabbecc3425e7a7e763fc78632dc0375072f255e48480311d378302fede3cb024035a67ce69e2b917438195f49e238fa772114869882cbf6a618d5aa67697cc0688764a3ba2074ce1fd30c8a7d4e206e372e14e0d35ee4ceb775c8cf6971bff8aabe197bafca414c23cde7614303e661f72e95732c5e2045469dae3508a4fe5a46d0e8ee1de2a38e4199851a2f5545a7a7290acacb4b2f5d81040e1a9a9687c72a70b845968824c7b702b6c26e6df957c72797397ef5720f6186b253cab1be8b16a2965565a0c9be4c58244b7534a3c662109186852dd3994162aab5f5b86725a5b72516fd7bfb525d65a41d5dd37fcc92d318e4608caeb29764e859fecee3a86af10ff428df8820f0ad4825f1313bac0721e04e7b6ca416a30b483039fcda32fbd0ab3a22bf0244ec09a3cda2804a3b603cdaa668bef4b7639b3d62dbad475226032330f746d56b530dcfcde3b9d158872a3f39d33eb96d84f24a6ffcb51376c51d092d051653affe2d90b599d6d4b5b72db3a1ad1b9ac1e3e7a9a58d3b8720c627fb581e54fc9bfe73c99d694faea1f20f86e302bed875bab77b413c8aad6c93e96883df0cdcf73011532bc7d1da10272c9620a5bebaa2686f47bd31668e5bf0df3839da9b9c661f793d54450e54703723751521df97fe8fef78658a849ad5358ca9e34535f724d98d2cbe4f0339c55722e6b7ed1a11496332414e42e0c9c6606bc4070727787bf32e68ce3a9b4e84472193fc51cc7c3225c9e4c97cda550a4f2ebc3069ddfd39d1bbdb55999e5582310905ab15fef670dea909b7fe379af4a3bacd50dad1743bb6818e0dabea5bd5a72bb7596b811b781e0cdd0b2717640aa9ac19914d095bd9ae5bcca61df189cb409552b1e5832b4608db91e4d2a9b987cfeec5d1420c8055efb6396b6b09770044bbbd2add5c5f38a8fdc5ab0953d96fd9cbee0f3b1a97a4413e9f76a6596d2e9726fdfaf268b001cf667154259a5557a70bf9b6d3d2c4c7bd966eac5c65627a1721eb73f543f3d81f5f85c4fb5e77d517dcc115d9a64352306525e705eb5a92c54ffd3b3fc9401a0c94c725c58e9d4bcb706b09973d0c9a9f42e2a4ef149047c7254323163ec90b0fe428c7b4dd60c1171c09929b50f5ea2684e95f322068a6372bb01c80e352a966574213eb1d00cac4a896dcf70759bc7faac36833db714c772ba52c7c0a83bdc57007683f57a175a481bdd859c78ff68487ae7ce3e4f37e75b1ead110d6f18f18c3a3e8eab9c0c54cd3526a0bd19ff52e4727d330ca93b887213b7b929db57595f0e5e05fece56b8df5d6f5fae91d12023aa371e3d7a1ac9728aeeb9786b6b2033b9004082c4903e5b97481b52a4054e1edf58d4ea2b181772eca08960c5fd82afb4f6da4ddd91796f20a6132b46de73fcc16d0134c8ce0d2eced26111d6234a6b5f4fcb15690b31796bf46bf96a1e1aaacf813cbbb97d264debfff780ef274df325f45df3079d9cd426c27242438a453922f0bcec27cd776b6927c72ef43a65a65f59bbcad4cee1f1b6de7a9f4632b0cec2bbf1ecfe1b674aa7add171553d874c347afe8b28b9f46b702a1c1b8b61e1f384279565c8e20806125234acfca833848c43f66b68cdb27fdece0f52c68d413d9abe4c795dc7cd573bd179bc160d218847307c9d8956c1213d09848ab8c63bdea3f85028732b95119c84dace417281092ed26d7dc17d0d886ef384600566187c689d558bd45fc6728a22ad5f8979b4b22003e9c176f6acbba503ee4c59e4babc1f947418edce2c03bc820fa41d31f93ceab200f5e8432fab6ac5e5f306ef57808154f8f6d6d867369d21b4936ea6ee43e284dc7e21e0aae768571422a78397fb80a9f553fc2ae13f9c30719f621b000a0faa080a568b35df6fea10580e5f51487b908da40f75b2512f9c78e96bd60c7d8c53b5c577142bad5f112425dfcd57c1cfb50b351d3d4072ef462d760421266c4917abb71a7d9bb3bc800cd61ac0c1ac3ec0883f71565a3d68411680d7bb21252b049502e4812d2a9ad81fff99182c15ff22ea0da9605272f8d9b4902dff1e1b0054c2fde12ef8faa7cae02d6ae0cb5a628e38c25df7ea72c96ae46cb4e71aa33e2d28681e16a96a6d4d4e0495544c4a738713e5ecc4da726a56a0461a2d0c72c711b223bc9ca78f11cb04bc00f2903d90df733dafa444579e76a7974a405fd4dfb985c3f2fc1ab719a8c00f969ca8fdedb27d9afcc03800fb5951b708470d54a3a485784215ee1524f8842f723ae4fed18a588642153e264f79206acf5a2c07900c5530ec58d2815e1fbb9719575d43ce292591bf250721a64f7772f1e08143b3359be79576a5a27abd98480ec8692db9b591bd4112e872099f231b4c5ff2e9866a1981688feff3cdc44b161e7b1b3897e4d1315b486734598e50690518c5fcaa7f75756f5910b46b97f19c4bf72616ac80846565c279724ce9e0eea681be33ca1dc3e1382d154499d508761a7d7f67aad923298afadc5fbecc404a57a8c1ebaa6b7dd2eb4ba5fd88696d2a5b94552e4ba386e9e55f736a2fabec440551c13419446592c55401903d46aa865b3a10b8375f7e6214d7d5721e1fd2944bc121b47c2d32c08681762473673fda1de8beaaf15b13a3cbfaef3e5c6fbe9ca592822dbb174ae77f883305af59a8b70e2a2dfd0405bf5aec2043722de227c194caa6987dc0f020dd0eedd65a9b9373a593034f0a1eab6d7aa343729df39762d7195f593b1cf021597325e613abb808a1ed5a00f8b40a39ec59a53f18985a8147218971fc6acc5efda4da7310d03bfe63ef584eacfc6ba3472be707349b5fd0846d66fbc0543ed71a87899d440ce2c10843aa2e8f96b5a976db337230c8a0ea90a9b061230607258b9db33fcd995c048dbd94a86ad600664247b92f793ca061f9b883f7390afb90d6c0d44001bd9de93787ed48e237266a91d5770b27294463375b43b91a70b12fa8515a74cd33f65835bdc260e0fef678d3576d72f09676607c7e388f821122ef143e1fe11982f9fa774936ec60a0a89a490d4212595dbe28c8ac41902a12ae71fc64ce22d7c1299ae3d4ac3e95c1821decd7147279a02e03de5c576849703b8f5d73aadd0ee9dae3788ccf7a90bc59f27a4e0407972d860adb34a52813ad457d5e1d815f7625bba1e23306879ef95e64daa82f727bca565062478a99d082a2fdd6c3ebba070b0c2dfc970483dad8f68e8e286772a538bd4634b91762a37f529c7f7b003572044f0ac86b2e7e44d2815d9aba3772216d2a040f02093044f65454810f8b8a41381fb934035798f770c00faf4da17207b8ad0191042d085951cbb40cbd80bab6fce8b90f953d993e9be23f57d74f72d806df0a933c9e8364da4032d9a4456892af8208d1fd57379615e67c32a4a07266e695548332bdec21437419957736e24a84edbf00f266dc776c3b9443e1a372aaa99ce54c3b8fae9b75311a73c947436a0ee85dec42ee57a25244842a1f9c723aa9c3f415002cbee1be06db1f53ca953f85063603f5ca6ea07bc1d258ad29726f3acb2a07e6f26eac4d67e5e0f1c138e1df0af1e5729ee3dcc7c674afd5f017ec2b6cac5414ef91f63ebfc20a098b696ead2bab0020a941b8b28adf213ce87207552e16eed764d96565eb68ee6d6c8607bdf1a865a97f1f6e426f2e3f62934022d33023bdc8ebedc05e8b19c16ef7ef23c766039eb5ddc96c48f92809ba3239e941f0c5e6f1ccff87264b7983b17e7b9777ea23b4af144469ba2da060372743ffeccca2a0fe56fe7452bed81300227c8ea175a831fbb8c107dded2aee4cf72cf89809f2e4e9ec9773a02dfa91e672bdd89491343ee1535035e11cb888e57672c7d48fc4d1ee1c262d77950b5a8fcc0e0cde033e6b2999a622455f29cdbc9d22c1840b3ab27695aaa9e7413c31e4b30f4100eac1a8132a59cde1b981e28a83721f3cfbd96f6095f2c235bf545b5f9afaae4cfcadea898a16b1824798bb7bac5e0ec6dff03f16097b842ba1efd95e175e89fd9b446854315cd53f6298c1681472e09c7739e0bc1fe1eaee49e7f1d8c2565f7d437774b1f769b2bb2c4146879172ad2ccf56ad2f1bd098c24e6f69760a940a5ed6a2901fe60b23950bb4198a2f0a7bfc6b76e819d2cb9cc38619ad40e6daea59cad6dafb130ccc58b644c129511c94c798212cbba9b6bcc15a6ca6e9be9963d8a51e183d4934e4a34f6b4e898172723da883862c83fcd6bc8408ccd8b3ac35169a398a16bfade6fdf2034cadaf72dd68741f615e576770eeb5cf6c344b1e6165cfa69dc39ff01e061f25ab032572f0216df278b26080f6919ce362ec2027e4180940181c87ca73053bcfccda0e72927b9c837b0fccd270298c9b6a94e03c76594a458c55648583b7ab1bf86f1e023df369e140443ef88f20f1870da6ff0f2a82234d831bf41964d076b75bca40439f4648b17b4120fc6f8cb480146163aeb1546158146b8bd2e3d126561a669b7221595b30b58a2aa5f35caf56bdc42d0a9b76265fb5fa4842a27082afefca0001ce535eebc902e9a909dc1b943f62c28eee9fb056c0ce8d0b90c707d185310f72bf06f311c35741a1a0af1d915b06c579178e9efc05fc46f8d827b184c8c0f0723f1899f8f7ae276702c0d50ff53c73c4a85af850dc007b98d17fc4fff3c10472cebf4399b0f70cf87cffe5359080663c52bb4628f4be648fb440ecb39eb431724d94ddb69c1ed4fe64b68ceb250c62add19725eab0aa7f3c585087881525d672715ad3b0a8d70af735f394b4160dfbef19cfc4a4e5efab480784f2604f6d8872abbe946a6ff414eb18ad1b9d6638ac7b200503b036def411a170d6dfaceef223efaec06c7fb951cc577481c71cdc266331bd77de2be3806e5330d4f9d4ca693aa9c6a264fe44061703e808e329b128f81740ee31acf8707f515ac93159772464c8e1900f962ab49a7fbcc7dbc416d6dd0e16c490b08dbabfc015ec37f55b8772c36268fd410f97294b42234c67afa62eff3532515da5f4a03af1d061b097bf02708d729a10682f223fae8e29028bb7a520700d607f38f23eea5c7ce6fd9a967226fdc7df55a731e0a19dceb3d3ee4a35aea7722d8a35e1156b3ebbd5434431707af678703e852c3739cbaca2cc510bba67363aad213160e7f572e1d24ebd38278116b67c725ade9facfdb4a131572de7c6284bd59e4fab64e3c1284bc45fed72105d2b5a973dcc33bad405d39b5f354301bc178f9d061be3b598b6bcb654bb0bac8ac8e0ed0d8bab65799ffc3eb0a3158bd366b052c65b7ae5dd776c986b700275f910f2cb189850bc1474690c31a4b1963b3c5bc2d62b61bdcfdb02f2b50e37cb62f8a04c21c85d10135160b4e24597f028b681177e162fe079c332f7f4663beaa7506d9f5e1e717e94d731411dee8b3cb047c220bd1a931e6e36ad67c42725bce6c1943da4c611113f67eddb35ae954848f2ccb00b1ebe3cafab51bdf855729c0a11e551455f4160f1ced74a9ec98cc515b217c7d0db17af8bcd34b1dfc07294f2b7f8cd0d7384bad93b9b4277da6a668dd3ce13aaa0a561e1af58dd7bac4dec106539a6d81f87034c5c981752d91f80b712e7c49ab48e0b9469d3483fb97262b0dec364d454c0b623cccdc407bf2f700350b972c3e32a08c0f984e07c8132009a3fd89c1c47960a7522d28209782cc92355651e3959ecd27662058ccd6650b1ff3b621010942da8b115727af45bc433d6fed2ef4f7717ea2c32f0993c3b722c21855aeb2164f7b48e9331096a580afd945cbfa40c4ba2fc3b66e2edd7df72bfbb862f9b41a7b30d1d4c96d9a2f7660cee6bd246bfd73a14d6ea5b64dddb726c35a67b1fe29f292c567ded93d8287d6f7409c489a651ec021d0103b6441d72b12194c8a5d565f31da2d5c8b4cdd259e992e08a231e72dc846f8b556287d55d9d12c3af1d68435dd3a7967a250aff71ac2f22275f20a288f4cf3c1f7b2eff72c80a6628fe17972ef05cbaea936d3a493af54a6487fd77354e2ea36e9689a77234cead278414ff873fe01b888a870864371631960f58515f644c31a9f929e572b2c17331093a548818273c8bca30bee05fb62abd3500690c3229761d080712720de8341164269c31635fc9fe1ad0cd36cef98d2f1101b0a7e254750af8034319d044c1a8ea8d505a1f959e239896d83802966b9d889efb6b176ac5578bf3ed72d86a99215c67b380a8c5e83f92922c176c56ee8935279dee98057a653193eb727738c56a552c611be391b3bfd217002a67172b0d1853e637ae3db865ac5fc4024d24cd289f1bd6e44680e314981d3a5827aa5e336ba065b9705251f90688f155ea58910846d5b42cd192e3caf877d79537e07e6c2c92d262b72b0be93a6eff72b1b60cb2229d53abe39d44c80b1cccbe108a0956c3fe344f3667415ced7b87725d5f0389c502b3aff1bd0cb7d8707d13576c1034e035f89a16ea364ddd4e6d722e2ca303e3f4f00f3f2bf9f20e2615dfa61093f0d60a38dc35485c31b08504720d0f932d4f7fbba5393d9b046f9eb7dee453b29c60559582f1543d902df8244408a08cec9e0b4b26a2adebb1bc886a61c37a30f9bbf3ce9ce0323bc2447bf90ec73a6a87b235d656ab37d1b76f813c1874725fb8b983356fce4aae2069c04435557b2ffa8a8eaefe01a3c8478d7718eb95d6e74c75b852ff0c37bb240d7c954a2aad636aad7cc4e17e9fc7298861a41fe9540adfac0e53c03bd6308b34cbfa2315f164c0e7bde2318fc95f1ff031d79e19360eb7fe89e1de720eae767ca5e14253932425880f030e6b5c0fe1f619b6f3a48b06944c3b8f423c631c9819d65a4714738009ecc5a90038364df578da3e51166e4312e9b2bb96d72dae81764cde72d1b7548351177cdaf1405c4df7820e2e6d992aa37de8f35c7c165cdd22af20720dfa6ee5e2c9d7c3530f436e6317b01392be85fa84e56afdfd85a89af7407a2e2412804586bc7ac876687366e866bc328c7ed8cc7ecf8b19ccb80aa39655b972f634205ed1ea970d8f7dae41667fc4bed3c8391558a1b21da8668864752e7272ac1de54f9ea62e119d8297e6ee9b53c8cf1e4ae9cd0aea3d5d480a29c0ed1c02a0a699dd704a126de0afa64cfff734b091310c38a3ebd5cf920c45bdd86afb2304c320901b9bd01988aa92863e6a432de767905fc762d5d2f3cd724d133f3f7203fb9c23ccace1ec2f1fed2ffe93fdc7f696d621afe4e6094f75d6fb407b2972007e62b9a0958d4dd3363b013532805ecb0f057c2d196c4cf4d3523d0482571eff3f6e64be4a4b77a577121dade23d3b2ee9deeb5e2885206a29cf601ca4e972725c914bbab3d696242ef84f825c164f2b4dcc9efa883ab55b889516600bd07246fea267a5b72e124006dc6a9f1116b04a96d2cddeccfc4d94da1a4c6ac9716c582213f096dbb4ecb024ffcef23a0e8150c4d5cb450839d215a12d93e2f0b00d9815b8b41628c4eaccb6996334ce3ecd1d1afb28349b7b3717c9129a526d563d325d43aa1311e9b5c96e9fcdadf2f542046f636478458ccbd14e21598d3bd072432bae907feecc7dfda16c6eaa05ba3d6f4f8109940025529ac06b9b1eaedf72bafa8c093fe70b7bba69794dd1862e70d4a45b690c5b59bd29f28e5655ca697221a4aaf5670ac89e4deb049ce4ae2fe57e82d9777e02b8f93ed09557bca64772ed4100fb250005e8e23a15816ce4f94135ef8a68b78b7c2fc6d5d8779fd94e723d7b6aeaf98a4747d8d874df526ab384c175ebce1215a139ae7fdd503c77a9724900edc5323c686fb7cc830393523c1cc7fad8de3c00c53cecb969571b765a57e4e7bf4709ddbadfc68fbbbd2c9e3f0c4a3ebeeea645274a06416e2373c6de725ef1d709d9e010d883d1cf4f377da68ce0629a0d1dd6140e564e47f61436747236c05ef8fb66a02813fd1bd684de29a85f73405e56905e67febaef78004f1633fbeb6577d4b77000134f36082badb7fd6aa898bd4772fdb9e2a671b6fdbefa72c5d34129745feb9849448c69c015c32ad9d0ad7f816dffe1e48ee53154b7da72bfb7aa730b33cdc77ab12ab17d7412d10a15239a034ea4f5db43fa6006532b72961dfa39f2e5475e97a8289d6f066166493e1da3173ad606706a944132f6247231b0de88c566d9a69246603cd468fe338de7dab0b90f9cebdcd15752ab99cb7230da2790927ca05fc082f3385737870200d3f38b087808aa286f5337d4d36772ca0a8da32e5832edf3a0a1386cf6e3e989e7c368e61c5e6d04ed135070072772d2d9d2b8483aa88e9b445471769211388e57883f9c82be46c89256376d547e72548369399a6d1cb760fd029b93273b506713931f79145e03175d83fa92bea1720784f042df68040e418f51b315cc38a3f6c3586e65fc43e509af2c93673ccd72570dd2767a723a976d6d3c20dd8c310d0c5b416dde7601af4d71622ff06a6a6ef79f0ba1b9fc7ae422c48123d1669c99d54cc2faf22d9b84996e99790ae67360aaaa56bcc954e4bc5713df4a773df6eec291e75ea9a5b3be9c8157a8e988d822e095833e8efbebd39efd77b51e15543e1e4755f3e3b5db1f78a7e47e0c6905575459dcfb44bf4eb06dc7fe96e12429f7c9f2da943f839219d8738bbe9b19d47247209a7d3a0a04190629f4ddcf907aaf7602be706e4a74e2619bbcf6f32b0872f2331e920516fee491c360bc7feea18c659865e43a8e6b6b11dc325fdef41472609202a8147f43fff7b67e6cad5e9de35bdacfdfec21210b4820ba9c4c0efe7241c3bc5b792a6a8f63991df943740755e475024a534cafc49466f13f9c6b865ed62c444068b6bdd85fd7b88622d8bd7323d91a3b9846241cc3712b4da6adb272b0a58bb55aaa31466138856b298aafa9b7f7efb6817209f9572243c9c10e0569dcc02ecdc4a01852cff79f63d334cc1e622931f85b0473e94c2d0ec687a12e72e8c473d2697a2fd21f0829cc6cd9e316e732a9f215164b50a415b3b401ad3c72db8c3b304d7c40557da9f2beccbe8a6f6bf62339745a7c18e44e41a5718a3b7253de20baf92f4a6940c27313716163f9d0c789916b8067b2b9bd33ec40d7ed689adae7994c484399c28962c6a05a08b3758b4ef7edc5d4cae49930287534ab319a951a0ed3b6eae5b612eb940edaa6692fc96481873f503a706404cfd756d363ba6192d6f87f6e1ad6661acca5e7cb8e3292122e3b3013b8c59fe27b7e0d9e2ab030bc1a80592770289beb21d4fed7c7f909897222096e681fa008442395bb72de62f459009159bcb714c0745fc17ec824b5874758a03232e4016b3cfef41a72ba33c35f709aec4a1c0657ac39f4f7cc0e57a6e473865027274c3484c14ee337a6133d04eb567b96d7a96a1dffe490644cc8f46011176ee51c206df2c3037826e25f25a06569de22616e8b55cc34d70322c7d1f571f74b19d82757965164f472f670fe1d5e47189d2c5f678d3870be6a79faeb5fec33240a0246e72198ad5a720edec262bfd1efb50c88e045055544876b66d94a70b60cfa5d54bdc94696a772ef9b98c6616195cad813bb86345c98bf8af88524d57991621ed35c462da31f7236eebf579ab40bc987029610a45547a49c0cda1832c8091a901e74c6f2e5c77271e4885e3fcaed4f7eec1b9e0f48ebcbadbbc8c25f1ac366244384b9fc0a320a13cefb3d9bc3cf8aa3297b04e5fae2c3ddb95edf440c44e9af67cad3a2f185722c28fd2e38de3a00ef9b83888c369c8ab3d44b6ce8648be681e98d078e606d470b6160b6d80abd56fc7d82687b780bbe59033e8ebc919e725a3e333a113e2672410d7eb42af67f924d9245a0c964d42ea36920c270916397cede9e8efdeea572054040147055bc79e9a9ca18e58512ef09c6f79644d43cfea4a768039fa96b723de6a54f5a8c0d5243649eca9e79990dd298cc60f5a372440ab35817237a9072f7b67dea2a484847091a618e943a89f9eb3beef50f6387de6f903280e51c7a0229d9cdd2551df7df35a3c19e11d3b7922af57c10db08f76e128bd200a89f6272b81afb5d486a0a032c2575ee8c3490f3db6a146403afe290c5f29215a727a6729f3a14438402aef2bc893e16e633b117707226f6a0a59efab6553e1acadf4770c8828b4bcc0115753b420a164da4f775846ca93ad77bd91a077c61a1719b3c3f3c74a830ebdd95a5142d9bcd9df80104078fbdd889d9e551d7ebbfa3fa6de9726ba3c6812134c855158e80b84d4eeaf2377181ff6a2fb26ef89fb14e37068872d388919dfc47e3690afdb0a17ee6eca0ede8f1cb81ace3a5c59a7fc2b8a0df72fc03854a30aa606b5f8d280a4c47a81bdf3580c4df18a005122a4e97515fc67268576d71dc6bcd46d7401ecc5b8271c274000008598bf3a56c606c9fba9c374471ff25682b312618cd67158a3a7cc4e06e8f0d22e3ea54d1046c363e618943724541f109df672c9551361c045b708092bd8fd3ff052d068e045929b72ac70872620c3402758d4f85db34d32c686fae82c8152cd35ed52f369f709c9226aeda729c61bbc468eb7078290258a19deae0a62250601e220f634e828b746d96ea6572cea59a51a7f9efc9483d4cd3c54026d1146421b7a5de073a7211da67d0d1ef725825b08e1c15e35a93d35ef48fa2f4a97d0f8bc7f2a8de80a51b416fa09c9472bc6f77a31a36b6a5973a2885b615cf49bc6a5b02d69a743fb22cc2f12afaae72e07195e647f0deae356371a6f71a984d0960f2735adc5434c1a40b5df6896072b78af7be9b184eb1b0390bfc6ed8339245c8e93367a312552afcf80e4070fc561d7e80425aae1f92ea3cdfc3f90318960da53b01bc8895da0973b6c8ddc2c372e6c16be4ae05f0b6720774f30e7ed8142e1d33572c2177747b4af7a9b52c6d695910abe29b2b2fa54607f87fcb91ec5bc0777e44a72143baa1a4033296350c72b967f3e3e1e50e6640c31fec052453df38c875ca29b2242620b9a03e06323613deaefe18cde4044c220357e41e48f540af9e1e06efccfe3ed77e0a619a311572faef4a464df13c7585e272369f04977a45fcab783fc9415bb2258a17316501722b4669a1f65391eb5a2ed9127d62ad27c33b584fa0c7648074c46d76dc57a6727acb5ff081e3402ebb146ec98df869581887d3a328abe04d4bf6274eb6444072b1fabf20b82e511fad565cc478c1f1723535d9220136735f247d91abd6369f72421a9e7a894472e0b5b3af79cd894c01af1f985fec4b6a8ce51e62d724933e720b4b9bf45dc68e8df3758658beca692718e48f0640be377766b216706d84e372fb6041c2df8816ef7a54a088dcb667e17382ecbbfd613f20e51bcd4914db120a2ca81f3290269a7eff0a95f39cd0bfb3bde1f58590d079fb0e167f53bc2e310be20811d37c1f2e005b583b72f3a8c50d9bcb120a3db03649b0cf48b22971625511106a39a8973db85a3dface5dfe86aed801ebd8889011ed57dbc4203902563ce139af27073d8eaab7512bdc6cab42d385e9af09bc5fbaea57a4bb5607eaef15aa60fa94ea40c6e74351e87966d70a8d6ca31946a0ba1a9939ca4b6730913572c17382a82f5682979c80bbbe4c5eec51e22a7117affbf1db9d23855423ec1e356cdd53bd76d0181bcb505dd8fde9073e8afcedc6a1d96525101dba1d0d5b5a72128240fe686ffafd386dead4db5963cb6b0c46c7cd1859813da649435b064172a7cc2fdf4e7e4a6015d28cb9bc6dbdf118099c279543b1ad407a7a018452b672ebbdf302c03aaaace7727350603c45a986d57bb581d9850d0d0a13b38553b572b76df65f28938a2bd52d38240732373aa62cd165594801d75bfd11b6b736293f24b271a6cec991c428189c10e43a3490e746f5034659ede3b4c0b0c7fd1a69627e800379f95c48e117157ac6a18d86b2ec844efdefb24508d422b2ccf4759b72ebce874cd07f2b245cb4edbeb8b713b7f297b6c0fe33c6687291ffc1dd0f1e27b254698c9a5750320bd8e84072c25b97ee793b13e8ffec17cfb293c6bbccba726f207f867e9d0879266238c4716d125fa4e5ae9acdc1866a0306489de5ca3c1a3c51e85501711dd82e5f4d7368753c9c5fabf3904472c6e19cd3523551cdb81691edcdbe0139a463e97d412d50a790d5baa303eb11ef93891ea0c5ede1b0e26bf79b91ed59002d539c08b8bfa54fb0dff218217f525db45a0c1b9154047fbe726cd5c4a7c8b0b4aa10a64a0266caac5c7af5bce6a1d6e461f5bb34031c37e572a875b8f2b7870322fbf0af70482eb69ef488788978a9e9bed24829ff209c7c72c69906017667a9f86bee1bd6f30872bbf3fa54661a5c995eb243a357fc684572509ad8463baa4fc442bfc7a5f73a6e6d1826ce713a8f3fbd813261626e86a572a965cd9a80e5ac6a0f34742433b0412b8d2bfd5e552a8fd7d7d9f1d0dde24f72fab033db33ae3091e64f2f71d83a5e5bbc99cac0c24ed2338e47c814f6b5265abd744f88063ad8607f5d6a0bd6529e9516e1fbcb3238bb6db21a774f3b08a8721ec2a227f843e3cf9affd5982c8003112c12d0860cfd0cbd2c44f4f8445e026711659ee7f071e5156b6f1397b2f877add98a3ac7080c7555ff99927725d6ca41235a5833f6a45ff16f26baf07f9a609a5a760d0c6c0d9f0cff63977f0bd43d7256b3d2410d4951207be5cf358a4def9519d9469a8c313cc2358a16de897d981a0ab19b9c18f379f8c3db0a5a2ba0e2a6d0560a525087681176f0283543232a723fd28d119cf965f9216b561715dc98f930d62ed792b1562c26d5c3d4ed99692cb4b47aabd28f7c696726c38d525341a7ed0e32cd3364e15dd9605f7b4604b415838e1ba6dd4b74a234a773658ec04490d4ee06585f2d9de3419ab4808998787248748613f5c31f64cb56230c5959c9bc560f1016b20c53dd5b85ab676b98be72b8c46f1fd32c7a711518920a4d02bbda59169640b51e20dda97f0ed0103a537284230a892c77064d7547cfb7726d03b49134ac28a989891fcfea492d2937fc72612e9d8cdbab930cefe9d9e70dce7b255e082dd1b9565ac86491381941834572ed3213718edcbc662d96c6d4b6a818421ea06966752850f74fd72cf3b323c472bf78364334baa610511d004bab0ab94477560c7412be7b2332ab955a64d77772349047074f61d942abac590b27af1bd0c1c22358ee3ff2cd99eb368466460e14fa70f7064bbc9e878be45d25f707d0eb569dc96c41597f6fbb1adf4c37594b72fcfdb897ce610f4ae000b036e770542842e584452a517498925ccc617b9011248ee1aee0554a033b284c21366c22fbdf6c2abf3695837f3c964e862bff7a8172337f29c2898201788bf413e3b2efe3a16b1d3b428c711dc3ce9b60bd75626d3ebdca9295d017b4f159b7dfad5569746c07071a0966a410181b644fbc8806ab284b1951a20e6820aadcd02e2277a404ae34d6759bdbb5543d7a69b8b00ad43b0fdf2b549641fe989ecbade204c0a17c446534372e2b682b42ffbc3e38445eac7204bece9e5e00a45b89adf4ad56aa770e94a1ebea68385c8b6fee976b53b4e3089a6db07bd89b66da182204007b92b227cea5937b0cf84ad19f078138af574015892d1156df7d3e24007701caef66b9c91249ad4e1043ed147b4df7b9e3b9ab1f841b12bfe0b7339d09abf6f333878945b4a050feaff6465dacc1469d1d4af57228a0b465855dbecdb865e6f748d32e4a660f9f0e4266b5dc8630c2fce727c843d1a8d3ab8503fc81dd81e84d92226ee325b528ca2313150c88867da5dc00373ffb1144814049a8ac632fdf28e0725b215b9c5ed2a3824c3b6adca68b3409c072b74a3353602b7f9532bbab756bd232a1911794cbcb91057b82129da85a7f16708a26189b90aeee230dc89ec4ba54c8ac32f76893d2e9310def8c74b5e4d2a421651cc22011e7c21cde62fa0f82a8b09be6ce721a5165d33e58aaa6860632d1729e037ce7997d2c34991c15746355dc3632bac4a0ec4b02fd2f19e0809df4ec723735725af1b2112a53e5ef8df11dd201969d0a9238d56ba825b8fd4a1c4e525d08599341ab803639c6943eba1b568d3cc5be59b7720c0b83b63db5b895371472c8502f0c112616e0622d804c620e76e39eccf5427b81671ecd0c7eb4141092725c3bc057f02d527493a61ab6cd35274d71cc38fde6888efb20c562506894947233539b70f0fc25ecda1bd400984cfee071501f366412d141e79da9198c01b243c0619fe57fbe3e8edcb4d4f4e98535c9d50457417a64019641d2151d05d340720bd0592b7667cfc3b4ec197206c434d468c0b5903b9f1ab435bfea6601b7db72afa8161253308f07dd99c0993bb452c5aed503e92a4532dead99709dbea4e47293d6337fbd55823a9146916f7cfd0ce80babceb63397ac1928f3accf593e20020631dfa182a0a5a233d36d804b35ff13589e3de2ed3d7a0011608fc77761614aa4632ca19f1cb672183feb6d1587834dab21901dda4554b4663f445696228f72481c673e66856901233a4ab1078c3d72a56de2c819dd7ba49aa58358b1b50572e7eef424f81bbbba527e19a4ea4410ebd7d83275143fee89d3f05feece6c6b3c2f3f41ca9ad89e2ba64df001e81f6d3dcafc2ca6968f61d5aa50a534537c897299f46ec472f97763d21e844858cd7207010300f65cd95b3ee17fe61d37b16c72e6ca5915d06dfc68c17314d8dab9216eee2b4fd25fe830e880a0e031c87c047221d2e9d20324dc30b66dd0c2a275f09fb7be273a2c718a02764bb0edaaa9c77276de0bcb65d2b899999c7d19b1bec82e08fdea608c28bf5442095ab2ff824f6439ff7d47ce7cb9b797b6c486b0fbf40a0289d3268c8a0c83a6ce07c43fd58772464834762a81d533c8a8194f889feb803d45ec9926a230d34c9e991504b5cc6c3302b58d74fb9dc0bf94772540bb27ce1cb5daa7c19d389c5dde2412ecd70b7284a29d72b86d7a327178754034b20e1bd6390dc00d8b1f252f055aa2a3343f72fd4f3fcc06563de2db31c2a9f0bdb644710578069aed2202e64949c37a92a42cf447d479982775b6ab4b7db4bdc75912fbf97dfc7245b1abbfaec17c80dc04727081aae95e5383f247559dd85beb096048b39fca041e8c5913e1a7268f8e996df5cb3b6c5c217b32196f6b30da14823b325b0eae9ace3a5cc97b9831ea648e72fc372bfee2ddfc11e42c0fa91362d026ec4a2312bb67b6372a4d950959a9d47276f584aec19b46394287a1b6463cc35607183ada9f494fa03a82d27aba950b72b5b58d39bc060b602775d0e620596f55315331ca25e3feb5e812da0982c86572d9da6a179446a16e2ff0012f22d37ad8039f855b5a1b3bc6a8b5f8669ebd5572c9885c5b7591ef9d742dc8e7127a0017f783f79e7c98e4589587b4df0cd8bd609022f81a398cd2cb204c4ac9beff564d3f8f1be9bad26d262181267bd0c31372adadc0f101f24e9e8a76fc6205998f9a61a69eb943ef5d9511881996d6e6b15b55f1fa1d722aab2116e02a655484b65204d23061f4601deb1d06978e8ce91c49e1ccb210ba992bb41383cbeb74a24dc889d188162401a015637b3f09ffc5cf70fcdffce71317bdd66a0d95e039a627904fc93bcec34ca87b7c64678c427eb1722f246261759cd1aa63237849d388d593841da108a9779db714a5a32c1ced8f7206fb2851cd5f8ec972635f5e8020fe9fdcbe186b7116e8129a87bb946774ff72b776a304979e9008ba67df7062abdcf6a0ab3b19e98e962ce918ef36ae77ed1e9de19b1637d623ee567b15732b7b33280d5af8fe69bcb7aa24eea4aa7532167239df0c8210a5a4ec439b7d8929ee38b91ed05d61ce15d00c314721f7c68b3a6e44ffe191d4e9d5bba7dbccdb263bb7badd310a92052630f07cb3f47d609ab272c4ec3613119bb71a949ef03b1aee1dd53a8da0ca97c3c7267196e1a82386157252a9d0b0dc4f222d1919fb9ede65c81df0009fc94cd8359afdd681c3c6afa659ae99b317dbf3bc1bd22fbfdc525c8e075cfabd023a202d79f932875dec83db72f7b2bafdd23bdd29f5e9f69602801a32d1610e8fdd3bad5069ad5eb7a04fd94ecfa3b94fd92ab52c880579a9315fe7fdd5c3f8ea9913a9e537f28c2c4034fb72ed3187968df3bd2a5ad6a5ff7da5b63e7f773e66af2ba8277c1dde6d2f699072f3309bdba5573f4350b70580a9fcd67766614b41ab5aba52b317b767784e9035d01fb49cd3853f29869785158a20dfec7e2d9dd0c8b2699c1c54f4f014b8790d149e1c201bda907b622fd275c7685e25c29f68cec8236343b0fcc69fb113d172213912edb4e5db43671d634d5becabc0459016e0eb397cabf240e9675211fa50c8b578d9456f7d0a68d59fdf9aa65c31dfda4ac065ffb797ec503149f62ced728cb0ccddf85d53edd9d5ad96c5ca37b118b5ab63fd665aeb163caa6d0e8106561e705624da6e6327495539fd35b6126f030674799b155334413a01f44be8af592f5e4ee645d7b6b7f11c41249128567cf19555f5523b6211c54690d7ee77a73794e6f0d53f48c40c38dc189e67ace125c8af3a4f653f1e2e963fd684ff87213d5b842da6da7496527e7e15292afc6030089bef90b12ee7d6adb67e6f2a9bc0729133574ea15ed245cf57f4b9e558fe18de13bd39ec0e264d020ae6b08d46bc720ffff90ef2489e71cbee74f3fdbb099b3e6533da4954c32844e04896aa38c072fb5a9551d5f0ba7632eeb42352c5c372c177336250adf6b1012dce1470e8f715e3e754af85afddc2d834ba1dd85ff09f8dd9334e1f81b8bcdd9a267ec760717298b3c38097a204c7122f733645e27d48e16abcec9694b3ba915f4aa4c5f42242df986a9325442af3cb9d6b2820d6ec655f79f92e90c30e9339da1ae65b7cf247e648e90b8cec43fad1feb6c7527ab3b3ec03429a80bc198fd2a2eea8b07d3572c59afd503575f038d483f3a16ad1032eb64ffcd925d58440d767e2f70d4a7d7292d83d03e772235e02cf679d83a6a7b9b20cbc04a8d0c0622620ddc58d2280728c557cceb83196fe01b8b2918fb462de0eefbb6a1298ac5099a5ee883ee5d9722d838d6615747aba865e16a022f22c021635500164e2674613b9fc62bda2df00e8326434160cd1f1dcbcaff31f368bb9125141b1dc03897c06fe98155b7e7a0e1c60fc223633e9703e2783588d9375cc39a2ebc89d71bf301f347702493fc7678b02c8d787366049176e13fd70a76bb0316d77691ca339127b69052cd1b5f9726b1ba0431e1e64c3b1fc65c001126e50aa4913d99cba3c5d2a14dfb74680ea57338a94da328289f4eddc5b839145a25318f00082668181d84f37ef7da797db72243322b95fb70fcd087e833c5968292252ac5921fd6bef58d1014b61b70f1c11ec85c86f1700936d8fa7e4a9fc576da8a210d94d16750d3bb2567bc85bb20272ee3eed04cb402a8c39dba3a84d39e0928832578f5903cea77eae941151581072a493785ee9cec98fa78617200dc67c12d160c8631772032d8a107b6b4634f87248a98bae8635559202a01735eaa3a4c891432aa6e37def70273cbf13ece1df726998135d55bbc1642955d6148a5eda9f27ac35c2b59a3b64214fc8b264172a72bd906cb9b4db961a1538de36341334a3aaa627079041f42f92b3c034a09f315b0f45e6e4060c9201389da3409f974699569b498c56c24b2f8dad51fa90859e018cadf55e0e065cf753387ffd2e55a87dbd780880440f04c5d5e79e46b3285372521c906d616768584d77675d34e0ef152d9ea29a6e579a45fe7a27ccef6621723e1ad956abc2bf1e64fb6a4d3c0439ab01c72414e7a6a2da83b4fbe3a8a5262f9ae7b0430de735d8120ce67ce00b19e70ce733167cb5ff03e9549673b55e9a151ee34c4575a63f689423995550ffc3219d70bc12597537c87027096755d33e726925ea6c300ff97132474343d3e1cc2734e8aee66c4fa1f50cbef92a13e8fa36a0445169f7330902ad3cea35d08ce03fef37b8d7dd108049177a7f78ec527172117f9fdfb60f96a1d05e54d5d89d2c87daac444900eb5a4dd5489b2d840eb55f968eb356f4adce5b5bb44d1d819bffa84c622ed6b52aac17d634a6cb166f0d7292636756f9acbbc68863d1e332c08f67c33de078fb74198c356ad9f0d35a4d4282f3d2345844c48277ec4f80970d9d69511d0f592ba39698db7625ad9290481bfe248b3fe2990ad0cb496c27e141d3e8aa6a0d6c1b8eeb221bca8f9dfb99221e3a9965a3a9dcff823a0366e6b0abcca0a0fedde097f57dde716d009958ebb81e2a2fcc8a6edbd686490da40773d77fea7d8793ae258a1f0f974ced0554008714fbbccc3065fcfa0f830b7696657dce23fe42dda55f861780c978bcd014403f72778a515d6b4b6f86fb5271a9ab37aecc464cff16344eba162e409db37399767239b7ad99f8adf8005598bfbab005db7b8d6854aaf85a769b7818ccb9869f5c7227b0a1f11cdc30b78c667553b9583777e68016741aa0a79158272e1e8c68c25d207504f91b8afeef556af59d133a6457d6d5e44f9b05819a2c1f906287a90172844ddffb3fcb6b63df9d8397f469359a9954077c43b4cd6ed8ce6e039d0d9f159fdbb3c7a01aa5afcc55f111a21d39eaee8a470a5f13d021b0c163a137ce0b27c5ae50a296dddc2e9abf4eae09ebf433f49533fda5695017c43bc8dde842585d91389e55524b161fa25eb70772d146db126a731865504df88bae347c41a0112a9ce492f683a69342704c3d6271db8488934862c92f463d6cc979151534e871374a60c907b0b2d63064a40663664b15f6dba523af0bff52ef3feab99257a13d5b4011c38da7f97ee83e5d7bc924a3e892513eb03713f32f2739df6c8fb1bdda725c835d18e8cc187f64d353b2fcbeee13664841df5dbba34566db6ca7a0fa276e2bacfc334a24d743acf80d5d57cfafe6162cedafef8a750d8a8b890d1bf91f7247aff76fd0d55af675922b0dd4c2b33109e82b894356ea80ca4f1a065b88774696fcf643399bc44b9e13189ad6296ce94ac62338ca4dc94f7260b7bf1b869364b0dbbd4662eda791c65f6de40212b146353f61fbfeb746a611f65e1679ef8072e53c13ca1467b23518ad0c6acf6473a1d29289ee743fda8dab20ecfe6da6f522ee16c05a1ee1c93968156026998d551ac51022875f6b4a9fce286c45169eff4a705e6ee2a43ccfef6b89bccd8393de663e33d75341ffe974101eea82e964c172dce9b27120c6d9d8e7b8325d108695883d54445e3e7277d86cc15916d4026745cbf14c89aa56f7e4452306bce843477a3e59fbe02ab77fa5dff94c8fdb055772b0544048d849fd1024eb18e868b98930c3277b8125c6c15211bcd73dae0d0372fe6da645f46c4d40262258e4f73429729cdcfb33a195f697fd98da58350799722cbc3a12111a72784a354a837956ec903b68d941bcd05059699bcc0027787b0da73b6c40a778ee169cf0761463c0c19c0dc44dad31305c640c52f2abb2daff72ab602b52360607c594add83eee71423d33ba7083bc710d068bb3fd963fd5257209dcc21c905314fdc548f61627921360bee25fcfb03f0fee31174d1ed9c3a91512b4ba10ce014726bcee6610b5fe7ca6f2fa27d4819b40506262ef08e686e57255a116f24b2317c14b6ac9982198a807e06dca0ead50a9cc0b4bae81c96f15289d45c8273eb23b26b52e03546b3378023c4937a1eb62b6af36155166d8bc015fd463c1066db38536ad5045937487ecbcc1c20468b161d7ed08eaa99f64f074569b87dfeb52f8c83af782914d0f47f3b9e54c09ab0424165fd5850775c3913451d6c7c09aae349a03745a1cbe502acb5c4b85ac626f033a225d58c4e10b6f9e09da281b1ada2e5c978f9d0b4f4447ca6c925c08ad93255ddab038bda5b2bc600e12db56944b5e742f95144c43869bb39fa6e2e610aa9a5270b55e6833a4a6f85d1457dcbe483a47e345c7709c066b6cbaeefea0f8b4e8ac7ecc189243b3e7167209d80b18da96a4fcbd5c1bac3006e67a7ee566af5e9e2652264c0b93256e11727bf7bb6b3c175c609f6351bdf82c6a8bd3482742ac96403d331835167fbe9d72de26e9e1c30a23f41953a8c9d305cc4df90d42199da7c352a981265f60a2924fd8202942da1ba6da42671bb5ae2b9ff62eb0ebfb495b0990d2963034f483b4553db23d59080a1bc1dcbd6fd95e4980ae860237e4406a85bc305123865b079672748ee9e0e047e35934601953318cdfa71b84525ac131b2a1348296c58066f672eaf2ab25f6dfa662161d53610a3a81710eb486938df1bb26ae81729f79db9a72cfcbdbc1d5f8ccf928d58fe6e1138174c7b7229e34deeafc8a207d1e8b79ff721d6f1ff3ad43b8aab8c20227fd78d854831ab9ab34007d96a338fe2445ae7972b1f575767064dcc1095a6d8f8073a08bb4b4dec3f53d4ab957f3dfe0ea0a1f72764d968045e6fd7e5aa5b172f46cb0972e30f7db10dd4981895592faf3e8cf0b2f4524bcb44a66b0a516b9f847227d0158430f4930756738c6d018516aca8b72caf88b5a35d6e00817ad3c39127fac139e0b32204ecfb6b7df7e5405225d6c252c49b8f1e5b8ceb7033f1af239e6ba78e41f2bf0e0fe259bb620a6468653ea0d482851d8f3271271ecd3168b98f2d69a6c770a174f121d700a07681a73b552725f407f14cff94f633fbea5622f2ca3912d31a8cd8888e753c2a83b722e543c17157b312dffdd75882b54671572768b7c61f17ecd1e214b0e790aa01745fd1a07a39a53ca7c79d9e1f6d34d8cdb334ea559c2dceb8f95de6afb727ac3085b6f72046c0060cfb355502a49123b66563a8d0db883d7cc6c1640374eb88c3fb51c72c9798e849df9b777ff06070bb03cb330f7a4f879cd048f5062902fd48162e572a1ad1b4c55995c592bd2467822c0d72549f960557d10d5877bcdd00f0f7d6f69d8bd0e77e91db2ab608e0abbdb6bc5a331bec48a3873d798852e1f4817a9f119b02bc0a8c701a0dc636929366e4507588bbc9859a8b2beb8f1852e6d79f01b215169cdda332dff368a86be3dab798843468b829ea3a1f4d3adc0993cfc8b4972a9a656d35f46e05c5aaa0678344ff310c94a8d189c2621f77e407e054cc7b8728012980d434e5e367a1a31045932741045f19b377633acd47b00ecf971a97f72b4080643061bb48fb0e789ddd3484d419a98f14a08d257a864ec70a9c2e01e7235565812303fad648b41420a042e1ce243fea6506bb84f34ae1fec6e1422be72249637812329e31db8266e5d5d5b77b188aa1ba052fd914e38ff2ef45fdaff5c39ca1dc6dbcdc151514e3cf97fdcf7d47755f0a6ab22902760beb5fb4df9d372b50df5988c4a885698181f207c4ed287f930a37e3ac626fc0700a578aed98e36fb487e7d3ccda073d4a3b89a9ec21ea8423d1ab528d235a3c32ce21fe387527278da9d4c4f8688c4bfecef3bca0fffc1c8944e58c67769695ad8fafe5a847272c209e5c3085bf7c0d041257c01a1eead9eb44ad44f6c2133e94f1fdf06935606893d467980e51bb7fb31b69bb0be3ecae24362f25a366b2d3b66cc655efae22d0b2160ddf82e64f9db47927d203c426498dcedb9d38ae3101cc6744dc70b61679e3f3998ff0b0579390a395ca808bb8b92251bae375fd5e52973d88eecad1b72ac57c42c7a637c15a66969be662dc52b84c7e0cf25e8b632aa6cf211181bd27271129966ae0cc4d726f62e742a0f8320db9082efbc31c17d4151dcd410696772aeb6feb53fb66d9151316efd10e21313f9b56a9d7238c01252cbaea4b1dfe26770230a2708cdf73126215c84e34371191f113b9e51ac0685fb6fe6affdd2057274c5528517a1fb5da4e2b2d7710d8f25ef1b4165552a40b175c54ce84a886572c3410006ec26e1ccaa28fc70164fe661645d4fdc657bc4f2926be164e46f707298604cbea88803dacc5fcb905dd3b6f77b5965f0b62b464ec7ae4c042a7a162f11eca5a2fbc6800615ab9f2b1ebf0879c9c991436e5446ee93e8f12b4efe0206b5e1b5ae320b68b0cb4531a71a2755e7f59034a0ef01a26601424edaffc35302a5d039f5749108e6517261e5ee5d2ebaf895dc0e5308a5d928bbf3edefe38534b55d5ca567654cd6f0d66e5331a556de5d2c72061fe26a77d3b9253b1e3cfc414f320d391f4a016aa6600de79cf186cad582320db7d68892391ada2d890c2c72ebafe8ec61e47cbebbe7d2ed3c34c8bc4bb8bab4c718021884b9dddc43f07945d405a192a7cc38fd66f3aa4e540b59a509dd7f03afe51547a90e9ad04ea1953cf9e71818140a44d360a8b8525be04b5192ba898d73c457e785e59ca6109586728d8a5258e7846d9e2571bfca5d52609149d5d8c08cc6e57ac3dff8cd7684167227a188b6c9c258f4ca8a02f40293386aec957779498f17ccb5e13c418157f5722461a1ecc93b0f0ff3956cf5de24fd7203909652f05a4ffb5904bcde4942ac725ddaf8753ed0e8c110b6a9f1af22e8a63779e56e7e68de53d14086799cee7f726ed46ee9f518e8ca4fe5606d4a3520a9e70bbecf78d1f6537a81c8008b99dd1e7f341dd840a3560a6ef1d90f98240d3e24c961465b922d403e70cc9cfcbc5c7235f0e6154218e6f489483f53264c07fe220862f6ec0470d7468e14cc73af65362f9ba500d39e88909b6414d089af432008357dc984dc67d5f6165c510653c62997b1fd2a3b036f73e90fb9e7f5c8e4d13aa547e48a539962845fc8e6e55bb6726cbc224048f2b257fd278e3014b6edbc2b1f84f281e0b7e532bac5afa2ff8c0510cb5a770210d2329a618d29eee9f6386d80d6b10926ee07dee0f6af5dc06351d766404b5b77bfac121758494bca2ef9337ffaa7faa1f3da13b78194b465fb269959259e68b74b99f51d56b71a104de38a48200c3d57be632372b117f8604c6c91828c940a700070d56726403098d07373aca57c6cc1662d3ff9117328c86672f83bfcdc176f5d0dd5b56f80a135f8d50d928c08008611449b349f126128f572cc9efed5187443bc3c0fe3387296fa3f9c97068bce0544eebf0e70702037f74af8be14f6e6826b737de1bd9495a3696a2d889a094f7fd6471607752d0f38f6721f137d42d65fa334fe1680490ba6aacfa86b8944fc0305c63bec424855647b72bf89bfaeecc2b96fc4b485a7f518deb960acbabcd65a237e29ac0a448cee3d3ff789a01f645896d8a9a2f6710a2a7f2b7826b01d60572aa9bb663a4e704f126249377478ea7455e29f6876b5661b085081ef08351ddd9841fdcd077da0e1ad2e4602980e7d892b40e4fac2145d54c3d6eae5d307a913c2f9c8efe60dd292fc13a860d7add6091af2835f1af2cc3e502879e72714d9eb036dba89bfcf2eddd472295a3560e92cb121200e416e55b7c7daf6220ef4aae84aae2b1ff13325133d720c200d2c1749a88d71c0e908bf0a73daad98e52976da09588fddeccf8ae640724c2a6a4e5e149902d09767b56f0d0239e847baadbe531d12c9ba3ce328565672861ec5dca64297cd8ac74bfd11980025bb7380d04646c18d6f43e6accd4b4755ae0de38bdeb550679295d238617e69c1e03427d5b92f8eac3a91451426613472ce7cedaa3584391b586bc5832f7048744f03c220e1d0ad865285ed5b6f742772d606f84eac976fa19d763d710823d6162238372e1e4b59947058847bbe5134375315bba9042e5e6588bed4ee2628f0c6911a27be98db2de3c2614b14864a92723873f347fbe1f5cb55ccb84f564138329ea8ac38502766abfe444d9768202672cd64795503a4245ded5460595a2686696ea1abb1e7b7c16452e9b27c0d133a72cc64980193ec57879febd22257d5171f7699729fa88314403b12b04907f2b472e6744ebbe6e66bb2fc4006c5d871e7014af594160113bf6d797eb529e625d172aa382d53e16dc8ab2fc6087f843558f6d940cd994247ca2ed0b5d4e56beddd72d64f0b09f89e82a2766c0d37151f0346f7a0757360ac2c9049d599eec5d7fc728ea982030c969cb3c55f52301585b2b1e4cc0ed3d72725f7ee625746a9c66672fa3ebd1bc2c178ef4dc80a8537caf5ff73cc3681794411a62989da96e6e2aa14b96551d3a69b5982fd90d24f4552dbe24cd715530f558913e7217772dfa42b728b9f7b69b54bcfe086970e94672232c5060a0af5de75cf7b2fd85ee4fbbafc722c24b4bdcaa6b2a9312ba64221b3056cea7d7435bde5c0a405101d8656961272f32ca8efcc1261763c2bd83e48d88891c990fa70747835b2731255ac74a1d2726b346c549ed5ba9c165016a2b4d3b24b157043c71edeaf84b92118bb6fa3d817dd17b2bff7a0a66fed9a7687b3b1b9e58ed7ec33d1124ae8838e55d70aada072e4b410d11bc8d48bfd7eaefee0a69316b400b9de0bb642301379c890cada25724bf236eb4a4d94cf3b318fcdeb8786c675642743d61cff2b9853824b30b74d72355645fd4d3594baed53fe2b5d40c1a72a800c608cb17bcf4d933aa06f461772766e4fc056aae0cd10d9cc55e8184357f7aaefd3c300ad85e3144d50516860726f5cf981f295dcb5621f65eec23ed5349557c0903ac48f8696864cc6c3e8d57241831c9fe6a50453bbffa5fd8d49f8108b732bf03f30eab932e843895d127372cbec756d44d916e3441a98307d1d1eace6d501c48ce3a0f08a229221f107f972fa20924a23722f6586df1059491cb8708f93b5dc31a39b657cbd9789612b5b341f0399f6d4054ee8819a109bfb7eb65acdaabf4c5d35a678bdb9b2598ab57b033b37be41f7a80e025db5141db5236fe6a55fc983e62db4db712dc028b7574f56464e1e6496e258855e1c556b30c40b1db318163f6a601ae2c87e75506a7bbd723f2bd36b416845a9eaf4668c758a7d3b7fb85b73930413a68ccd6314d3fb7e5091c1383a35d57853953e48eeeb1482e8cf618274693c8b35dea57f125f4c40639ed1886557ee48cd043e76d8b6ad5ca2a1249d8e0dce23a727ccd2436aa4aa725ca2cf46ba9c7ab30cfef6860b2c515ee04af91446f949c64aa49b9901ab57728bb4cb87744af86b411fae9330f9f53bf468d79c43d2607257db1a2aceb0c168615e4f87e46258dd6fb3cd7d92337871c8de1de7c52c1dadb0256b2dcc720672e2fcffbe01b669feba4b4cd19cc1aa4f644d3798ab731054a422a5499596a82661f4b25b82f680e1171ad896b93c8dcd6a5253d576c7889f9a5fdf1436c17e232b972464cdcb7638a57ed81cc01c203d7837df7503646c0afaea0027ff5e5c7234ce618da410ceed89fc1045033ada056489707a2cb49a1f8ce50e800b4f3772578a6e852be0efdf17b8a22d687bff9b753b25ad27750d53926bedeb34648562cda951dfc0a915d93f802acd0e3518248157e66bdd105229e9448c49cde33067eeebabc183919ef88c55a488f4f3ff07680b5bca35dd04075a75e0e1ff18bd72dca055aab34d9fc5b6ab42137dc56616a7c6092753c9be19eaed04cc10213219292f1b2bb0c009e91c66c5f6509beb7c064bdd58c40cf269d72e64556232957242ef3c4824cbb59686a16fe94c07f5b4cfd9a3a6996d6a6971c8ecdbe17c3a726ebb3c7101c04da9b2c53de4e3e19c75626788416972b376a0810bcab093c6729eb1fd43383def472647c56c455d88a4993b68ba1f70de017662867e3c4cf970e2ab08438131e5b61c91266c400aeaf8e6f3c816ecc74c3684141e7ed3140972788d4cdde26134f964dfc6eaa09d70b980288ac497f6daa9e358f8bb8720f272f55b310fbba4fe96f788ea8f3d83d11726c30f1708e2e14607141393d5f7917220b325e96b2ae6a979802cd4dd598dedb6c5a330d07537b5cade4ce5a3320a7278a6c3a2cd3689ad125715f8aa526e355ed0894478652725ab6c74d39f805b7203f2aeacee0cd72908fb65e5dd39ce3eb80efbf0705ef0b2a444144c0dc3a57237346ec028bdac83a2a12471003de34ea8277ae67c17e7b08550c687d3da2d0d56f70bca18710e66ea23b2d048df180f40d048192bf515c088cc0518b5e5f87248840bada80db90f48d48c8706d501fd36c0ff744730b2fc6515e352388a6d72809e0311ce4d28069184466d73a6f9fb4344994deb5220fa79c27f300ecfef6692fb2ff1fdd898b72cc8538fa6c0580231d260517c6ce2695b5c82a541ed2272937d6d077750befb6f4b0c0b9172993e0ae57e188d9bd8675b1ece852c820c629e523833389804579e63f6102a26f72ad3d5bf0e114417d99c49cb1d2dd09572c6360aac5dc53b50f1b2fcf9bf3c36b56d798d8b5fbdfe94199df5f94bdaf26f957fe1251ac7900ccd7531f868c5ed4f9656bb58a319c29de2ddbf2966f81b72fa8735cdc943f9ddf6f9db735dd7bf022fec765dfde44ead202adfd71543f672a2f282229072eb61704271aec6cbb585837a31aa1016c35351e7f80a840c9172ab5f289b3f021541497ac67543a4d45d09f3468f852655907ccf1573192bb672fdcefd5a748700dc7712eb07ebf6c6d4cd6f7d96ec10ecbd3476554d76f42372a8713f41d93f9c7f44235641930d33a09c8cde8e5397276bb3b6e409c6c4de2c243d18513a59bc5b540ce668e5b2e2a61ad2abd128f75e1ad15b24e43f04be723d21d322f2e7a1ef06b8531a4594968346012dbc9786c5a791a991f5ecac7c72abcd30ac95be9db75e2ca72d92b739a8fde952d04f15667f1aa1bc483b0419725eefa28e6d9e342bbe1490b94df28df052d4c3e811c0cd15d78cbac7ffc7ac474c4885ff3fd80263a284621976055d4ecbc70b55e6da483d5b0c3c3d6e2d887075486e97145dab178942cc7f4c612d05de7e967223def3d9304a214fe9fdae72bc2d52e9f8ff080fec88c9d5eed5bc25870ff59f815f5230b0299c0bd8f8a6017c1a8e6c0ee0c6e9c1e0f3425613a502fbca918bdecebe4aa288353968adf10cec9f4cc939659b7305dc9877442c600bbca9f15060e0dfa12367c65c0bb50b11d6653dc864933b19661168dbaaefe414f5d7a7524e97e8b06bd73d0ca60756722aa8ad53196123307de5542271de02d7b54d5af7e47d1d72cb5b82524318771129c835d74b228fd9cda7b9d5e9afab2d6a55e3e7ba6ca14b26eec53c4b51d272bc672a0f888daf8d872a7a39bb5d17975c9169cb4fd66b1b38ab9e57e32849196e76dffb368a041ca064cf8163ca2fc251be9d6c32ec8a88d477dc0d1320101236dc603e6f259a735aa4c39143f94e3800f69a5b6540251b1666e1a79cc9cd720a9cfba0eabf4b85e1f4f0c72c9e4ec00b4f0b8c30d4eaadc4249eb4ee7f4172a1574860b850f16b327a75cf6f7865a981fcf09d61c4b010d2f3cc22349c8e72d8102d948f737378d27a3e6268bb411622aaa117029e2dd5b8f27e9fce7a4e724ae787c475eef82e3e2ca48f1a68a22de7fe9e53e675e661d480ee3a9d487e39a5475d4b6d6cb70cc10efdd143ecfe7c2a373c2c15d862d41497e4fb0a7fba7243a8ed8d7f633ab006b1528d6a9ae53b248f60231eadb56eb00917c6490bdf723e487c02805a2b1b7ee8895abccb5a973774445ebae4086ef2c380c53c95c206ee7a910633187c8d0303782ec8bf0d568895309ee60eae9d3f1787b80a83177236d0c6c0fd91e853e23ad99bd32010e0d342242ea5249f537a56391f47e137721ee3f2e53ec0f29dc922401535a1bde04e07c27040e1b343a2972fd4797b3a729579ce802547d4550d2d49a9dc32168552e56bde9dd71f41db25aa62fb6c2e528f484bfbf19a2e5467eddae6bbb2b62a24524554815381cac5e38fb5ec01e57221d1d5cdd7dae5211a5cdfe8fffac7de1ff6cf5fa70eff31aebd3d70151c587240f62fbb21d1a0ed613cea4a5a351abaeef71aae4a37c087c854f017971bb872003358bfb41a64f18a41bd45adf9b0c8c4a501ccd1e2463264c2066f242d84284a713d9bf845fd5a438600fc7b8effae2c5f19caf2a564f194eb69395d942e11a531354bf2c5dfd0bf5af887561829f9a00851481c4389fcd2e884b8c328c472c4d552bbbc257ee899c5a8b55333b119d1ce873840f003a38a85041ded5d3e721470416e6792fb75dfd32b69a1788287ab6537f0ba1c43b324c44af06c7dc03c7196eb8ffd82db8d65377c3ed1da5d347ef4409d00051c56da7129415ee23b13fef1d645db57da3094f2283665423476262098d9ee732ae31ec0276ebf63b0729ce71b7fa38163bac253c7d3abd8f72db8bb8b245d7d5c7c64a1520b11a4b572f54c519b8a75f7c45284d3c61bd897df82938a2e89a013c3ed033b1cdc939720c14d38cb5c2b06143578f19259f0f18c6b53fd7496c4db896b6c43d83971b472777ceeec33993965c5fb5f46814d418c678cfe53bd4618dbefca9b5befc8247272d9b75c256a0a1525c19918127bc4b70be2433703083d5ad8e110917b3f14727dd9b79092c03f10d415136d7f79af4bf2912364df6a87931156d92987fd8a72ac794fe94315253daf1ba253a4840470c440f26b573d8945c361f3682ecc1c40483d2658c4564b8bbaccda027634369f86ac7dae53617f5538071829b333903fb5963e13481da59abc3a2a7d571cf267bbc1e4ab3ffa82f516cb380963b7ee72ac6f11d079a4cf2d89a3cad9587c8696054027095403573d271cafd4c4f4290ac0f4d13f2790701319e42a75e7b5b0059a0d58eabae39789dde6feb1ee0d99073cdbf162b60f83edc3d96cc858eb99546a3a566dc7a86ecf881b473b30aa624255125f37c8dc6d550b79643766e7535c02b3bacbf0cafa40e3150a3b5fa8352d14fd85dfd8bae94cea1102cc7ecbf4a21957195976c0d001854598317b2ba5722a7138d178871b4792bb8077f46358d72f5f87ff3bb54e32b5e10d2ae3a55b720b3b3efab63af642d00c19bdc220610d9b275aabfbf986dd835e2ee85dcbd67202c1a3a1291f994ef56452ef748e5fe223e4ae2a44d40090b463c1ec72b6487220ec7f96379450c99df630a9f38a0c876ce47009c27d8a66dbcec2cb209b5a02b09c814a0e54b2eaacfa6741891821616fd0a6b1889df7039ad0685099076172467bf16dc32cf1fbba39eb92d30182b98ec7fc9648052b15ee8afaf0f4b0de713b38ab9e912b7e8e3cbed5785c8903c3ee97f1f24f376cb10bb45a15dc7aa3724a1d56a5061619104b4cd844d3ceefc78d4ceb0c7dae624ac205d40c74fdb26307069335014a5b0c4d24916c90087ffea2d2cdbe5d7b10905ad096a84181cf72d8801307e40d70e8be936ed17d810da20c2ffa553a67e90a496aa29086db4e2d0eb1b59e5436eaf5d31189ada441c7612026fcd4509b8c73e4b61520ad62dd05659aacc6084be0f2436f12d5e03c5b52296320474d8a04e730ab47be33fd6672a82d8f92540b10500ba5c534dadd4db972e403ab06ccde770038137ef80b8e72d2b8db4cdbaee8b9fa27d7d272403bbf2995fa24e66fb5cb8abbca7c79ddfc1078330ce4a43036460cef6a251bb253c929dc3f937999aabe31c444739c2dad7227e50cfdb05708a98cde04b4e67fcdea42f8c4b75d01a9908c91d446777c88721d070a638e99586ec196ada31da0432b7bff26267f5f7bb5f471ae4961289d724c844357b4d49cecbc13c5687ec1b4ef54d94a0034009fc239152cf7c287b009f7022c82a209b2548733060032afe9ae5ff0a5f12fe11473023fa62a82bf7f492cf097a2d4628186f4522ed78cc11fdb7d96f59a34ccb0bbca00622f3fbe09726a9727143441cdbcdf01f90db00eadd70a7a40967861374b25dcb71a48963f72674d62f57d438854380f43f038481cb9e06d29041106504f5eef1d44b16002010646c15bd1655894395de7139b686391c6f2d1c4349abde6e02d6084d8166a724dc49c9363eb10c22479f55ed2a56f65ee95bd94e45918c2aeabd2d8fbad03725d2989c9f786dba6f9704f0e3783957f7a57f8f6c19dd5713942bfddadb7cb728e2978163583dcc819a7b1b9f117ecf6a44ee12d596bcd2b9bca928147ef9072a8361dabb454d95838270bffc4cfeca8d16dd9bf3492ef9a76d91f1ccaf90a72858dc8127881fdc78a353b9eb8e4f36a151b794e860b942767f54ab9da0ed872a94a87c9ee8ac786c68747bb9d3397d8fb1fc7355aefc3d361c0752ba1ee5f726c5f006b471427404ba71994118350c0b9ba2997f6be479dbe9c131d3c5ff95eae29aff5872d8eb28096e0bf247362c07ba71e13c5d8f9cd150a585cb85e1772625293c3ded0f49053af11fd2458b670b943a497ce6242e516fabc9689f7436a097aeaa759390c52e2d139e2cdedcdc032d3903778820b1550e2f6c02c583c6d6cd2027ea65d17c2c7e3c57fcb37b2ed5f81f7907e193174d7d7871f23e91362a6ca614f11c9fd19f2e052f828fbf65e3814589029989b67f8382314dbaf5572a47cd5329974a7ae8c47c7dceacc144573bffd72f7b6a516c30f90e4f5993a725ec599ca3fc42dc28a09067fc31f56e5879f2b4aca2587dcd885384d391aca14e724077f6edd41c94e19c93a93c7cdaf6b923b876f849c8a0814776232fc61720f5bfd2655c4102594d2df23c79c3ba21f4a0361894ae493094f22e8f3397c40312490aa5bb3a2bee079813ca90195823d13cde82349d03f6d6412263e2e0e72b07eebca18bd99257993e49d6e6eb471c2460317f563d23bb23d9c483faf261529a0d6dbec47504f20cd7bd03dc89726dc58e2de1161cc42ca665a10cfb97b5af7ee4a484a65fac032993c3c4c6684d453c8254b8e8d6b1c011fe972c87da672147c666d74dd308a0a4b4e28baedf2fc2135d4b963cecb0f86f927f311dcc572b08ac4b89b1287a2bd1659e3f19970c388c1275d88e1edbe1dc63284ccbc83726c6a8712f9272573ebf586a5f1b9ab674b9fd3a82589c45ddc327e57e39bd17276d760b8a73b7c37c67d080b31fd9d778b08a327f7691371249c1faaae186a725338dce84673fdb5d802a6547bdf800d12f4e03ed695629799471e281e7338721daf82b98d6b82f9b5e87518403746c00470308e5331c5efaec1cd88bc759272e92c0998d1c61036aecbd52d0ffc0815c4ce38adb6e18623b00c74e7dd4a1508a72dcd9f256631086d497596fcae1b1cc9c2a03451dee02ac357b4cb34a35252913b0eb64328268f0d96d70fadab1da875f482e63e2239d2b03550a093c42b72d6ca7fc89f1d7b978c7f45cd4ceb9f36dc0a727288a2bea805c0e9defa2a3a7262c4390f47394f2a12820f0e3a27ece3695810cdf5f557512c8700a358d7e80b398930123478f2b1941481118c6dacfadcea71738c1831953137a36c476205031bad3d9cc4ef0fc40338989c98fa6063ceb146e9dab830c60ea8041a32525e7250acccb9b47949b83354ea46b0baaa1f0725a417818fd1c85248d156ce3da108cb284e59e8b0061112bc96da43a56e9ef069c836026784aeb61fbe0dadc05072b03b56156bbeeeaa819b2767382a1506423fa3c4ba246f8798e3c3bfccbd4a398ccb2062ba948e193c8411d10df66ae117d03f94e6c099629eb2e0a3637b9a72d085f3881692b0d62d2b06843f375af1ccd33006b6820223d6dd15d54a713572dde111b81ebbc16ece43843318608d992edd06f0c8398ed3811ea705b0e863726beb6305698af073da1faf2110131b66551d9273aa8536db6fc8cac61f0a4356821e6801b77d2cf20e3f799efc9ff5151dd4cd33251c40841a7bcf7e0434ce72c4a68fe052008940302d400a63eb67ac8b3a6cc533bbca5fa2f4cf30e1559e72a7e944db4c6cc05f286769a0ce78b9de45554cc9e5936ce6fcf6ede2c1d8ff725b4dd30c750bcf7824ff36a9802f14497b1a38ac8271190cd6ee46b80c88b572fe9b0dc771a8ef3f2154ea5c4fb6d7c2cf7261af1d503ba843b343807b07b972eeeb5364d6b600afc7818e255002a175d4a71af484c32ea4af1281ea089a2872a2c63b8bea615a9f5110de15b6bbfb4eeefc3549d141ffa93af0cb35461d85724d0bd1f4da2bbe3d4876afd3fc64457bbec7fe202ec7737d70b668399a9d9b723fd1c532771203fcff4857560028f6312b741a4a54b4c7c66ccca3a32f9e2672e43e87e9b7cfeb95d80486a16ea51102d3854710691a97048a369df6639d546d7da162b40b6727189f8694e36e59ecfcf43190f639124f2278b9e955cbdba64034878df1328233231c6eff4a2b82586c65285bda881f48390eb503a96d5ce9728cefffa4b12a49ed14447a36af39a588a563f1f71d3df85500784f0a5f38f260264fc8eef3db1c6272ca86522aaa0bf72af656759f8f8407994c641d1d62a672b593c02747de38f61ec88fc75b89e703fc5917270c9328715458f50509ffe37245a54b0aab7271302393bf1082b9f9c64423a8db63363f8513d5f3b450c7c972ef2764ef60bad64f3741dfc14471ee94914c6d0e5c26b0e4ef865b4d268f6c238e8016f79f1dbb1b39b9985cf2d47cd926d6edfb5002a8edd242bd5fe4fee3727d4c14762b7689dc4ad46490f184cc3ea0c47979f78a39375320e760ac284428412ffce4ced7af4c16194c336ec87da473bf1d07672d1dc32bff9e93af0c2e29022be706eb02dad2272f3d85868b4bd4d9ae147f51d1a0ccd199d2c9320e79723f3c122bd90760562fab5889a266c0024aeefd6db52358f3677f91153faa602af7b9b2767cc0afd22751d469410711584a1004f46537df638cf6e823e17ea172268698c74dc859b0a0b590ce29ba9e6e96d36926e985c81fc03a9ba211fffc7279dccd1f54b7e75ba0068a49d705ba642a928c34b1aded4d0f68cc427d25b57251721c813aa9e8bb6cb6297962f883b1ccc24e16283661f10bc308f68377e619f6f9682c0eb2bd3d0d6b1e9a0c62b564c091edd682295e16619016480b7c8667946bf54b1cd28182d34a30b7d07f79790adf7d48c76ebd8d8d64dbdbdf5d1c72b29690fe6dffd3b6c0d031b3c7ad4e8ed08d15db2397575d08b8ef215340d57210af18abaa658db5cefc25f7bdb9cd1e05568f5f30295f99b97fa0d0b3456167ec294c12e70e29dcf397d572e338ad601cab64e3eac8187e2b6722338f120d7228c422c133cec7a8a26be551266bf086ecbf6df6e77b14d390a0ca06c5b07d72a6a7c20f85fdfc2068c3052fdc14a6f98e8ba085ba9acc609cb759bb22f65a6fdaa38dde9cd16df08f584ea69a8b8cee6dfd57db11f26e15d5e33b3277a2da40e2a53f91f3e387b12fff6850d370a1435a22a14452bc2cd5e7488c795b73e01e94a094bb72774b63b16213864f3f661d448b98ab5b6846a9344fb014653cd0014384ab0d473e261b2e20df59e8c40bc1c5eb66aa2d966e0cec3924332ccfda53a402a906f055d43c7a97ff67d0086f743eb6b2588ff544d5987fe8e7c1f9687289ea0f54232288be969710d858348412d94a15fa22df811e39f632d34dff2d5d0775dd824c8ff61955fcbe1b2718e3b7579c8b507694a1adb989ac7631f8fb38d0cbfb82ecd201b67538cfdb5caf5ab03b7bd870911ce81aa54fe96cad8676724f3db3117d56904a3b6d83d9f21456b6cd6f1fe55afb4e870f9ed34d018fac034f42addbf422f6378306bd49992a8046b852f1b0cdd11c7b77711c7c9fcb69721d61d17377e28ea1a3dd641079dddf5bdd6b7b3e1ff7d5f818de34be754d22723c8db8f3608785d08f036a7f4b0fc9242060f0c9286382ea9a0120e65bd7b660703ac7cf7673bb055cdfc50b7ab57964d133ad022aa8e52d1c47fb6a175e4072b67ab0e68a9c679de145f255076c4e67a06c3c1e3448c5fbb4cd89f1c24a3e1e4310bb76da6307c529f7c7fdda5d2d5c74a9348f69d99930615b09ddcf304d722f3ada93df0aa925d8be514eb8bee727557ba86286c87a34bd7e2e393a5b5f72881263840c426ba0c50f084d71f6107ed564827ada91bbb49c5ea6b410e6d472d7b7a17c279619f0a4b1d69e410102e6293db280c6219472c4114513bc20ef720e57d6eac9ce52445961c34ad211f8d27a3109b207bcfa149bba58e15d1554729f1511c76e91295e73f69b05061e442e52b0c298e351dbea0171477b5ecbcb31fb79c3fbfcebc15dfd570f0d1a2d569183f9dd59f9af61dad12ba907a2bc6410fca0684896cee5aadea94c814eb49548c93a457b8e9da48df0da8df44a4a577212d1930640a64fffbe3f5c60f8c88d067c964413277223a019fd6d155a4049728f839554f59b369eaebf7a52ae8e179ff445566361067d300f01bc786f58f97247074749800234f0d86505a2e35ac11b12ea535a7432e50816aa922010bf6d72c95df5491d427197716d60e8bef2fff46ce7aac5ef4d47178272830c756ef31435ead97a3a86fc9dbbd30c168790a3109981241064c9630d47d6d9ba1393257243edef94cc897d681d0bebcd696a1bc42f7d2b8b085ecb87c389769db95d1e725d2c2f5eb15bacf47896399937640a4b944196a2f1f9ceadca5c06df15829b726554c1c2958d68dda1cb066fd7aa6b83cc1f9236cfe58a9a291f5b28338f150460c04a588e7c794159f46a2a403290af87ad10d3260a85f4fc203cc1ec386e728a78fbf1bcbbacc83667b93d5c333324611b2f057dea6303ad0e42387a073e1f92a5b8bc6d380861a5d568f07f783fd7d9bcd354cef56e2683ce18dccdb10b38932d2d604a180eb018b1cd6e3c8d7aa77e2422d8a6beb3b9a71c69195ddf6572de428e384d13a7c8c81c9264cd81d1ca1c162ba13512de118e67567a77cc0d728a13d0937933c3f87d181f8258978a054b503cdfae117a48bbcd8a620146257270c42dc0f261e3922ebb3f1ac68a86030fc930b3731617b48ed1ec4a96a4f172c66275ef3c75a9e20516114ae31e5d36292c9fb9b50e4ce3aadfc61bec3310724e5c74ee446b172ea3e9902990ee55173c37e8ad19cd31bff832d4001d7eb52018884c919167a0c9906bb64e04e693f88531c54603d6bad29b84b4208c7e5972e9553cbae2b1d3921d4eab3f4cd463b5670a9b5f86e8c0fe131d3852fd8f945676b3ced5044568f20be320c0f26b08394eb8b947f4bfa65ba3433a2e63002a72dbdff49b1605d4b3efe01617943bc28c280e0c8a2eae5044b5e2352c76284563ccae69113b5c62916afd679470e29fa89caad65a294fd48af9e1e8129683a772811bf2da8b88e45b416169be3681ea4bcb1db7d4f9ed158142b5688d33141d227c2be6ce22771e66f77c85ac3d8294764f0ee82bd3b94ca46e5bec7f3875f145a0365d16f6f451c19e129df81ff2fd09f97fcf4338b74ea8c58516a375a42f3cd386cdd2457f0e5b488b4ff8cb6cbb7d619729fc2ec6dab7a52d7a7addf1f62849863ab26c003829a86974082ceb058b52f982f3821bd6aa3c7c905e6087e872f44c28a78b45c93948a774778493abf5c7f4ed9537d914dfd3d3faee081a1e7214aff71543337d452e43639c2b6542bfa4de1af1114b0e13a6b3b4c1b3490d3168ce7363a2854f65633ac6e77f5212e80519fab3de999ca5c4ffefe4570f3601eac32b653f17b8b9bffba9ab994e16c0fb49991c781b4fdcb41b992cee41c7267ede4a814fc3cad3c60461eb358308b7bf1d6c23076335429f23b679036f6a72e6eef806941f9be9ca03454c692a45df71bdfe3ca501b3c718d8be67efe1c50bf31b8e839538141f0b324a744ca59ed89338b9a7d1fe459baf6c6f2f07d16e1ad3fa8169d18b15920c8b225017ec3bb2624b7fa4c16e0a181295ac9f949e3a72284db86dc0442a56e34561a48765163dfe8d275cb0be396f0503441774213c727fb68ee4bc5a2cc2e6d7f362b9dd60b4be756e1e0faca5a60a65fe56da13a4722b6e9519511415f57c5ee0122bd1f05b230322b8f35ec693cc23a1802a9aa072ed7a0c76fe0f10f28a49cc57d2f499b7bacecaa7c7f8d5548aeccbf954bc1c558f95ee0eee5f6b426bd0dbf03e798b39be5ff5f5e9af0dab2543fa4566a1aa725c4b87772c304ebdaa5da06efb0c230dc16e6ef08e977396bb26fa79e0007a72e38674300330b53cd20c29b1e05706546efe0234dba2398c7ff20ad529f59172e7b5e7f403abbad26038fb39bc08e07bfaedd8b4c182c46f65239e55c790345b1787254b8da10e0f4d595cd2656152257c580afd157c872bce7df9e7bab024721a6b33398c357767940b00714f98bc19430fcc69a0596df8bd03498e7de03154cc3f6550e4b93c49af7fa4aa282ca42621297b600c182eadfbe29b9ae6028b3a7395b9c6d8b60c9454a45fcbbc3af384aa4cdaaabc49a8c7b11b0800f4cb4472f9e783002ee25f3902810f7f7f0d3725a11800e769a21a6eb13865f7736eaa72e73fc1eb82431241fe1b21ed1493bb323f966898ab3dc713e7b9ce2068f7d255c303aa4eb2abe658423f89c2a0d16c5b01600e69aa687579c353cc59c7d1237227202af7d699962bb3c1d5559e6db7ebafeaf417e811d6f9bd48f4bc1270a732ccbefd52153414960dcea2ec245a44a52733a711052db8b1cc5334d0804441722fd46ed21db60c7850267384f808a603726dc85c9ea73544eb2988cfbae94572d60314d0d3c4e5dae7b83d6e1eedbe113c5a226220b300fec11833127c1c1c729d0b493a36820f3cda32b94e65bc7ee67b3e0c87cf93e48e979cbdac4a3ec5729b87fe3b0d25461cfd0d819defb838f7b9365da71f604b6e492d5e8189a71064e61bdb25729a670264bdbb176d5290704dde4ff2b5595a73cb344056c181bd7244a0c1ab6f72a764da0b0a192521c4dcdb2d0485de449306b48dc0d2a0a0f57219192820afbf1bc0121aaea5b63d2baef1f6633176cda8fb3ba87f1285721f729e7ca064f7e44cc5c48020ff4d02112d74a38f3175ae8bd414bc7c7b2c47f772086023014bf4348f6d90cf42cb41757f631bbdc98bb5a0dacebee4ba44d66f382c557893feaf55aa2b4f29656fdb24238d3f23615229f6c06860b879ce008d72b9bd06ca98e00cd9bc8c5f330a23954994f4420dceafd24764bf01a4268bfa7251d25169093f7ae1620d25cb4454388cbfbc5eb024d821211129d29a42145f18abe97a9e3ca55a33fbddd17ca910200b9ff444ae3164cf9474c746a89336740dce3059b82b9ee0f0b8bcaf5a1f0442f3592ba900908130cc224338a3a959ad691192888d7a2b246c26345bfdefff5558f5c1e2333022eab53b8af86de3cd9004264f725b2b66fcc885ffe8cd53a91bb9f4ad51f1ba66af97b6121fb490618554dc366fea50e08d2b273dc611e31d56527319dfd29f0ada4aab1091d82a9e263b0df6034b2a45c87106afef47c50739b1b935e745121218db5992bea39bf9b87244d518028800deeefa598d7d33e51c795c603294d40cd93efa851da68a8fd4723a0b3ebfe308b86bf7880673c9865ac925f12ee5cf59eed124ed23ce11d7107216e4ceb9c1e90536778646bb44567ff72d08851d3472291b231dff44accd383ac9a824b075a1ce2e506b838c488e91d5daa52c40947cf720f296982768b707728e74493a6b461d98ec9b22a6e5413175aa9ae10d3e6614abfd3dceadf2c255726ca5c2ff7949841d2b26a4297ad1787948cc5dbafdf8124860ade48ff597393f2bb2ea1134c38d8a7364ff69945cd602b291c91a219686439741df79a2718e725a35bda61e336cf91490a321cd7dbf483398152cd1b0045a71a24fd3a416e87207cabeb04bfdcdc39400dc9393bf21a5561012ef7fdf0d9329521bb5475a5872562577f7fa970e24f51130c8a91117e4e2e88082fec6d8306c1ae0f6df8b9d728645420e9122a27ebc6ee9a4d2d582783205821d724b8bc53ad23fb654aa372246385b43d7175f4cdf07480633a19a0a6bf766b527d94ffb9d993ac61425e772d88ca00e7c74b3d70693b9e8d5a22c129d9f7893bbefe63cd69caf2a3591d1721d41fcf5a582bfde58673c62e45fca9676b3d45c304bb4289fec72e2ecab373840ac8ddff035a80660074275154081aa6060268ec886beaca1e9eedf3cba5872194333add296068efa834723c71e3980b5868116bb30883e6b29f9fcdeaa3f6ecd3d4f7ba97feffc7ea9360116bebba57e786c6a97302d3c44e5788407cdf872ecd93eaf1da5181d1a002a17831b67a57d3f7b9a2df0a45b42f465fe35278a726b9c85b7ad4720759b65fae2423a0786afe0f5668c0ca9f853018eb401c83e10de97f781e5403768af411e981b939cd885f589ca0bf3dd418211152b9ba7ac27d6ad7d09d456783b5b3c309afe8067b0a9a8b9b2853920fcb54e9c6115b8b80d6b5cab64d66e78c2c1293d6a52f2644c14d3e981d24a1ea6dc13bd6bbe0a2e720e5199c1049d6e9480d5facd044617dd5197be6552efb82a4f9fbee9922df072083d11a57e7d9e880509699313c6dce8e672e8a9ff756fe803e5e0aa6ecfaf722ebe6f6089deb286a12b35b91c6f1b1c060fa41b05d4dd08550121627d4a7c72443e853d1393b81eff389163ea83d4333ddb2a8eb81856fc85d4c3c0c23cba3d671ac6807da831a02a338e25df6af6264635d3eb32d57444e207580abfc30a72434de4dd7072ef377e1a75590a43ab2191b46fb7ecb20ac8ed699239ad10986b47ba39c069cc3ad81914c2af20f7a2ee3b848888413eb143de76aeb08b566450bd14723f1f74cda3a2c3b1e4696274608fbc5834a8912e95ea2716d296e32272655c5635d5b2f848d200c5b38eff3ccc2e0a328c1b687e6335d8281a373c05482b94cdf1fbd79a16bae2b8688009cb5734a998a038c13aee8d8c10a5744c0738927eec2c0f7550ee274f4b9cecd9ef2aea361cc5fc8f82a6ed76a20d8f8d5e725f83bb256e7c4b1974998486c79c809036256f87649a691d9b14d55f1302ca723da2b9b20ca4dc7c5c23c7a8fbeeeafad09cc6602377d915665f995f65529272475363cf4a8f10593d56ef18c9bd7812942e21399b2ced00ab9555c633310c33fd72b5af9eb45cb98f2ae0ecf544a3a13c80908ec9fe1de5fc982711dd2a5972a72aed1082a5d0a341fea3f63413b01d1b240c126dc03344af14d8658dc7103df2341bcbdb548d566f60b0487e89277cf7c1be0c46a14c119ab9047b079d114a13e09d3505fbbd7b6f16a760d8ba703651d619cbc6185ec8ce25141ce4cebc0480f8e52b84891927360729307f4dabd0aed5e0a921f6831c8eda06fdb640f7726d10eb9d694f20c2cdea029e329cf5bfea6cb16447f110e63ea542c275627772c487a4baf54454818788b3b461f3d09a93d1f95de6a2ef057ff6a94a59f1ad7248fe1403eb5ee7c4690241b6b397d5ceb8b01eafee368717813d3012a7a4e51a96cf3e872c42e117108c49579a65597636d6f904f9ea085501e13b29aced6f68765c8933b65dcbf7f7a987e2ed6fb1311368df629bba0752f1b3b94767890f0ba53f4731f80b80f7029e4880640835d3984cedbbabc47239da743976e8e9934915c80cfb62e5f01d6e89bf475269a1a615000c72f06ebdde903fb72c0de9ed7240fdf5bc877c0e2f99d4c697f47a2165e5cb67316efb01bac0e653232fab9972747e81ef7edacad74841f12818f2a8b3a209c1b770787d13050cd03271445c71faaac5aa6378d898c092163c4fc8fc05a6e01c5958e64de0428c18c341f792515bcf3a11f5d6f47399c15463ea6b65c209721ec4aca12c49ac329bd2c369cc72227145619aec9d03fe62c7c794f6b250d329febf72fa1f0a4e964ea97c120b721d99a98c5c3ab1753c7dcdda36ad42efe60e903fdb69656b43f13ad8d46e4c72933b084c8039adb481411a216c3afffb0edb23c407297ee55c66d0e7d6f2597271f7ddb7144739400214ad7aae11dd877cf87461cf1a3e9a6966fe144b03c5725f408b2001b30c4a2e366f77f52bf00d7e1b5a4f2e4d528fa857e444b4139064f5faf138cdced731aecdf908df00228c256bcc5e8e9ce30837464cdcafddb01b10789991031cf9e54ab342b0809f548617d640a9a2a7aa1c8308c0bf402865722859ef3635d764c34c71d5b6052c27e0340262b4622a4c489775b3406ea0cf1717419f88460bd16344b6f9e811aa690972c8a48288d89baa839435a24e17ee724115258cda26828281c93fb0002269c6f3ab73ff8ad1059019851b1c1c417c43d1354ad305e3da2b2bcee40307df40a751c5f62e3bf5d14369b3e3b8171b411ca5eba856d69f9582c8a3ad27868970a3986d4e84caedb1fb693d8ac010b995720cef216e827014af8498da19f77fef3ec7f82a50b5a28ea074bfecf13fc3e12fa4faa66eb2efd6a2805162b32dccf667b96b59637545f48198e6bfae42174972be39c34469882ba9076c60ea010cadcd0b86cb3ee31e6b3931c70b2e7d712972660e50dc6d0c0e7863311d0d940265ee910ea7ef6df951db0ead0ff71d9afc160c58f2fc936be95658f096c984fa8ae8e64dc3f69c5a1d330b8de2261ab2a172435ede87c1ec40ae988838266862bd7fd3b445296bc86e7c31fe8cd96c4f235d96dde09f58ae8f6da1fd155a5aa7dcd91e03dd88f927c75a3c548bf8b662187244e5b418ba4433f953645d26d0c6a24510ab3631cc2075f127b967ce27a76d72db97c21a2935591714fe14804a2ff582c623eb103486befa54443ce9daf0f25f6822f5832e97f5d29de57fed13b05452f190c4e7b8d8c886bd5318c489279272933f7cb8497f6de4dd1d3b652107b0cd3af0bd5298f80c536865f9f38f16e97236c3e6ce8521727e529c73a2a6d7d3f8dbe30c22903b96e9914bb8e5fb219672b47b482592e86d258087543e49a08a4cb80a0b9eb4155e4a85c83100acbef768ade6a7b733499d79905ecbd34a3a4958ff2c342bf40d444f813c75f44dd9d918f474f524f68e224e1d9d6fe4b5c05820fb8b2459a3b4fbe9e7a05f8a42123f722d31da7057971b9dce0528e74aecb1a75b2329b330f3ce582066b47a96a8b172f5270a6647b888e735bfe3ca3a3d8764cc1709b5cddccf55417bc05a840e4f46c1b3ecdf9ed50760caf05f0f7baa1817b114d0b78935577886b199fc50a426621268cc45ec3eb7f369aec6ca53384616d6f32b34a0d9ad9e5a2e18487aaa8d438c096a708a40c82e70acccc668c3bea48744c22fcc820efc70783372fa833372b3df5c39fe8047a26d46f86a83fe99a7f423763f002896058cfa231aff5c411135a4f0a6eadd575416a6d835b15afccf0ede14597e3340eb51eb5feeddc848728bc48dc5da0a99abd9d00d3abe3be56d2bb39228e3bf85e3a82dbe11e4dec846fa2fd2133a7bc130f3a6163d27c3f69eb4065e65dc00d195aced14e020760b721fa2f713ae69278ec30bba53c05d4c42d3bd2d24fb44bd72484f7f894b43da690887b15869da5ac22f7833505074895f66882159dfe8c18f7e3ccfc73afb1572a20cb32024bcfc0795e1aa78d1d71d9eed9f42afe8620285a5f7acbc2b84921879541fd12cf62295d604f17887ed5916f75045e0601edf597fdc18c17091263c5a76233ddfe3c8fa2fbefe24d932569ee102daf3f31a9584d9a5bd4ba0ae617216138b27087f16acc6a9c8bacea937ab9913182d77033654ee40df18519f7966642414cb78f6bdac7d48007d735a73c61cb6f2be5807dd55ed753c6a97a1c135af0bfb80f42b169a93b48f982b381b22390d866783b16e54400a1267b8c70972bb62640d219cc0022812ebd4d4a61249a32d37ae292183147d89e083940c1e723cdd6bdf6460fbe027577225482ef5fafd8b3787965bc43a2165949a0bf65c726b68a3f87b6c6eb632c4c159e715f14914a2331604560fb002773006552a555d4e6628830f2109f03589da85eca74ce1cfacb13a794bebb683f1159f2c0934037d7f4b2eddcb9cf4cc657eb1b37e6919c748505346089bc35248d98fa67dc8722febc3c41b5fac34b6db3f011590e01dcaae34daf24940963c0b9a9892942e72e1b0af9f2b44d47e650e007ca78a9bbed6f7b946ae3c7ee2cfdf19e39e22ff275d4035269f87a8170584bf2f98fa70db287b7e3857836ffa90735dde71c92472a27319af67d93e04892741f1e59f3693c666b9f84ac81efa7f50bc07f6589c3f0dda2b4ed88a4c8873b6134c5b3364980c0de3d97831d8e56805694fe248aa72a9c7fe61e21a0f6bc2f736dbf491efbdf7796ec283a2c784c22a1757f8a5b472abf15ddf58e8852f8faef258698c66ece4fb369a58e715b4b0932a5c6b8c6e725bcb4659d73d2e778d1facb87daa342f748d0c5f0b6ad533da73e014c0542472c37511720cbd4506e444b4597f888f72ec729497f4fbbfebc1f16c2fe315ea6ad61437d209cd38621d3cf639c8d080f25d99ce3f675ccd42c21ee2b41c2ba272dd81932b02206e73204e6df9da89b9642b9fc9e6005fa5d101cd7fdd727a5d72b21afa5f25dc6a13e1023a416cc8215b65f627960d5c19f58e75f6f6a4a1e96671cc784b2df7c2463392794690f15bf62185adbebf3884f4cec4abc2ebde465bd3d180f0d6f75c3894067b423ee91a8938418cd1a561eed8a2c4c332a27a4752d4b10aef37eadc2d22f0262e05b07e91734a60892e88af85886a0ec36d2361724deae3cf2eb38b97f99e5d590000fb49ecc9351034d4373e0c6063dd78c1fc7213222717bc6d5ca7070a0ca289092392827ffe1ec5fb13fcb3cdb622c5799c727f576ccc7e7756a2f708912984dd5d9bda1a7e89ce7c22e694c1a26fff45e9723f9d4f5f02b0bfbe781053025f0a4ad2c410a0b0fbed5e313b72ce958dddaa72b817a2a438f3ecef2f874add84c33a67b2c1b2455a848b01cf3f95c7ee95b26d679d07463695c149619e0e71dfdd133149b0fadc9d808e0db32637bed9b834098a79bf2b4756b7be1445e1831413c9329e3e48b6bb4f86ab404f1aaf064af6722995e93fed8b33c791382a3fc03e9c0047d6ad0e58d4a5f76e290899daa2594f2333f85a26a9866bc7be2895d7862770d3da94ddab8c22736d8bc2aeaf04fe723d54702d1b45d3621e02f4d4f85377485ae46a4f6870775a6ed36c48341503721584dbdd14e0438d9c1a39dae1d49e99a660ca918f4323aa293cb1033915a739e497506e2647f2e1cee63989265634ec9e71e853b119f5189df77b25d51aa472d260419051ebc19ab4173527e104fc8f62ebefe9829188f773d9eab101a2201b114eaae86e3735c7b0e02e0f4680e9c07c587452b022b4d8102646cfc736f0722328b1777b25de87833fcd8b927182befad8a15477f9c1a10a2e15f2e31c9207da2d995fcc8f9972f520e3ebd4f57909862489340a76c86215443f518c53e972c1afb6dea7f9f14fff18a1e757bf708e3f3e0b86a7e0118d991f815e5429523ee21d0225b7c6c8e2a96d181885cba92eda913ac92f1d339769349023578f4a72d25a63773abc191583b717a02e5127866274c93439b62dccf48878232371db72ed968ef84c7fcff8141b8ff8bb319b34e03f23b7e6b3c1f22ce8d698161b9f7228fdea0a8476d6444e66e6af0f35be13ebc19d24070315ca1b7b88de6b080d722cd0ea6424fbeb58defd8091e10e66cf6fe5aaba2fec1adb8159ac47aa232072c96c32d5de08f57597caf087883e27c300aeff8e3a13fbc88aa94d0cdf68e852679f79a81fab4de6afed42af28b0001943b382a39261a1f52efb4e62560cb86fba38b9a63b37a85427f83aa62faeba0652e82cb5d23f573bad54509b408ebd044f9fd0e8c13d73ed5c1412a494060a9866bb66204c6138c65e4554b67dc18257b293ee59727a5984e906e6c37895b56cf3840e0af3f5d11a845a1a90c47444310e70014e07d35771cef0977659c1f37c646d243d8de9c72452f056f72d859472bd9385d64474774f8e4d083c0516b72c7494fe1b8c00b252e2271b2735296b723ad60f3d82089d2e4fa8174dde5499925971d8b390345eef226f749deae6297292853bd44f8e830dbe361ad751750f8824ec9fde07cd25a15f3b452433df2f5618143320ff155dc697242fc0687927d37c736a97321de6587b243d9a8be95f5de4aaaa77d92efcd90589ecb6c3d82e26f31e7a516396b6d680904b9dc68b3d72f5a7d7a33c686e31fc023e09956aff0e3bc9b8d4eddc6ca100f39f0eead55941eca88c7bd0641d3049180768406984922279cace9787f103b1eee954ef4bb172de91873a24bd3e961f5424263152d66a32823ab55736d401addf18118d435872d2ddabdb1a3aab936fc573ab8143ea86488b2f68b5eb545adeea31587e111a0d09c8f27addb5961a280463f978665a044a9c491480a8eaab8dc54246d3899c682f6fd16ad624f6846b15b21e1866b615b03479a3b4840511c2c70d5abc80f072f4c4e31589a9356f73f74ce807e37fd97bbbbe7ec5ba0a2715c6184058e92f254869ed5972bbdb01b77d53ba73da870ee4ef0db6c0fc4f3847739dafb078017236a0795c5dfede9adf536d728cebd4825b7ae58e4cbd612b96e9ca83be4a9a3d18801ce812f074168b978400fd9ebcf749d1d1f218bc389493cbdd572da10b700605d860939ba2917ddf518da3d3f762ab0cd56a955ef3dcbf02c8179815ff0c086a996c7947dcc57b4d28b126abd8c80d4010244226dd76785e30fb693e05562cfb48857e19b4c63533016470af7c8ea0dffd953d62d1468eead8d616620c04f02dabbe066342203fe02f19a92575b1db7bdd62da64f621d86c34241e380835115191da9123b8007d658922d9b83a1f171532b323d7c02816aee8f93485a972fc45ff96eab5231c7140dffb03eaf3e05b89d34c50f450295e8a456c51b174729104b9c748d76e2763998046eaac753b4690ff0a2d6f97c16792578c64561e72d5f269c2afa584d9448cfdb8058f9f0cd33a60184d885e7fc4368f9bb69636469e3bb77fbd649e6d5a8b8bc8d0c2863bbecb9d46c50d3d5505e107d71044cd3c247cf0a76c974554b362e23466a7a1c1e9fcddc7a3fbe72a4be93499b9d7a87208a8972e82fe7882e8d362c4773560de37083cc25543b354aef4880b933022691f36ce00d1883801d9cb6e3d2f39872f90f255cd811d50aacb99eb4e683e13723c25c3d4a4a7ef7f312b85f92a71a643b5a1be159357f6313df1965fad9d67600592692195fe2b2ce20e4d706ede7b17f3bfa43d93d699e8aabc27ffd6514805ae106b7617e825e8def06d1184bf2f98890df799486ede05f5d54790e8d7e64623740193fdcc66c768bbfa4cdaecaa1dc5b7de32f5f9e3c5d4dbe2f6a4d817721aa0b08d50df984554f86a40a44cc8da0f2f0fadb045a305912238b2724991106b8393f4e7ad7a492c3365df706d28b2f2669936ff3e7ab27f432b38f8e0682d05428bcc810f07a4eaabeee4ca73c696a3af8b1ad6e09e3b282417d2877a74561d890941b2d17c168612306dea55a0efaca8044104c01894385162306b56d17281af17d1477f91700df4e21fdcbb33492b5d637ed5f87b6dbc49d92aae1a5125432d194a997f222a23be59801b4254891511a94259242e1a9cef185a318b3422273891d85abeb8492be0cdfef75e2d8502f8c98816b640ce57555ce309adf80bf37e16bc3cc72285c6aad1f3e4dc4bc98ae996fad5124a3da3e9f092c52362647afe52aee72337bbf5c03d5741b8e0a635cbd451328745aa41552ac8d50113723fe4f59971d7b36ab188aa75d8870e9ee2357b4f68249cbfb6d0f00445509a076b2d350825d2db57ebe939dac43c7e3eb4828d6c3941399e03a5e1c77c6ad65caf887d2306d70381ef4ba462d81179f03c4c1a393cc3a18474661231cc215b72aa1a7391c6e6f33c72dadbd8ea424398165d4c210f968ed277b719d76e12171293ee4c83aa962eb911f120c67e7929d26fb12e5dcc76bde07bc9017f55e595368babdba3989cd58e19c118902127c73201b307aeebebbe36354b5a2fb77ebd727b177294b51da2679cf5a2b25f3e973c9893ace7cd0d5fb1c3a08aa1ecd114727acdce8b2f4f80544d77c7ed44e808c76b633bf757e119cd4cc7143a97cd8055861d7d30065c8abd17bc73d16ece3bbf0511442bd89dfd8681cb46645d3335727902aa4dc19fa091579244da2a6d6f5effdaf92542cc4937b215464550bc35355e0d93940596eee3e9173bd8da60af5d230c4bb32b6090dd2bed43676146c87246ff03963cbdb4764fde0dd6ab29064f69eb8d41e1439c8a51af97e169fdfb7237fdd19bec84fc734c525859a6f31378891b326d8c82b4f5782a67a246305300a27be61c3019a425c21d2debef4652a25ab27536088550b7ab692e0e853d714b9a225517e65a8b6628206bf3eaed4bdcb13258a44f8233ef12e78a2296f9d87262e3c60cea29e48136908f4c0ea6ecbf176d4893faff2838c959ce281eb18072a1a7b090ff7ca5c49896e8549e472cb1f503f8373e9645b6dc3782d17ade857215950d1951ffc3cc8efa78868e6a68c57157e015c0b65b2e196eba554717ae0c822044506abe28c76b715c730ba389b0429107ce5ace7d48950333660ad7054e613a69d375a3f9f432afac9ac009bfa88d354fa06d9d4a63f11add96ca1bcb7204cc2284b9c771c812fcf2c489458ee9e89783e3ba7173d179754163a9839272f35c3c53073e0b4fae4629a400a6cff8f53b31494d3d8ad4913fd5e62a114a58928b6352968c61c4b5da9d260071af0a1fce56dd52aa0ff1c52d9f74506aa33304bf236c486ffc7d426dfd8581ad7870fa46fcc57ce959dce7e45830e5fb6672185f55d1e376525c9282491bb841854f9f1cd427d5bba316ffb1c46d281d8a7203bba8ac6172489fe320055661061328e6cbc4c0b0a2f196f18047cff1e9ab5d07eb16332cbaf705f43479b755fe3c8daab0217f5ec6b14b367b029498a2a372c30586841530fddaca06f0a53eaf0422be6b425b82ffd21c7608f548d9a0cb72466a84d7764701316823fc29fb1ece943a34292cdaded4b10d1703a0ca8e62720c1594e6839c24784c99b443eb41622a1e8bd98ef87c5ad3cb78254fd7427e72d7e595462c18a4e61c6deb6cf15e9f910504b554cd5dee3b7b336c08a60f6b2f78ed5ddd6800c26b1bf0dd87aecbc5614953fde64ddc4c69fbc952240e44b7727795fff2042d96490e04ec92d7c925cf535938569361eb1e1571aece5d07127290e74f6f03f963a31613d60c073ca770afb0a5f30c86f4b5a2e39be2331ba57268295d35fb7467e79b031d92029ba1c7e8cc4a9a82b64b6231eec3acf762da300138d978fb4a804eb1efe9cccc4ba325b7c370c9e8235aed8fdd47132fc7fd2b0686e7aebd9052674a8d1188fb876d7cd1634349c8202905fc913a12f99cf0169fbc67d0f38ffa65e4a715fc351a8400464b21dde1d2283f281ee0165d2c6c28ed8e666d314844cfe8b7107d650ea4014b00567d587c2a9b2cb36ef683f3cc72512b061b4beb4ef3f795f5fa4627c69ce92df6f35f9bf7f5e00224881357a9720627be12a0039c10fc4aef983777608104c2562c885898714ea434ed9e9eb77208b29f0642666e5a25b9cffde167b5147e71ff5c2e0c73759344e6dbaf39ed2a66f4e19536e824dba038ae7a6eaacefcc3b1e2dd465404c5277bfcd150645f724798ee7e9afb6e5abe2c6a886fb7c718d4ae6e074bb0e6b0258fede76ef94e72a135035296d8d41851e0fde58fcd183a4a9b3c9e09954ca2263dea0629610c72c5ac4315085c393f9c8c5f0b00ce5cfe555993aa6771a7861d38c6b5c109564419b2af8da81abae28efac7351270754eff9c4beb0dea297dfae3087ed4ae96685cd43227aea80588903b82c9c2fe653dc95c4818dcdcb69cd518959a90abce72477292343571488d017dde2a88345a5f458aba6575049eab656388736c68196ef6875cc9e071d071ec6b54b7e6490873c24ba90b7218557176af8f2ef41e6e0029e4a154de60e61128366929380957f5be055251055f8d74329708b56ce2f7720da787b1d05378c15651a4ca4f5335467b295f780a886843bc047555ab7582722378233976dab775c089b0022e6c3c82705f646e375efa12ea7a26bdfb5b16724ecd1afb3b36d6c2dce458e94ec3f19c862d9c7acd5745c53c2c25356192de2f192aa447a22aaa303f42b1429d9829a6c98117f3559c64a6c40b5452d81aa20dd183b781cf6144bc0970a766f6a82215ece2d7f9b776ef2cd472dbfc523a5e4152e7576b4c6f44bb7ddd89ca30a1b0f41c6f34aa524501bd24c32abc7c37c8591a239edf57ad519a36caf5dd24d9b57525bca3339c17edf11f64a4d3552d4072cf8380a214fdcf96a702f4fb458c1217357f794d2b5901828b2a8c8f67423472fcd8aba44a882e78953e080b70620f1dcf2907c2237368f5bfc5ea6a235dad725be3a57863366e59c488e235818e7f8f098d2f30373c7521e74861f9b2d2081a1f518f23a7649ce01b66e1c7c07d280d5cd6523d664585912f99990df879ae6596fc41a86f03a6490e6e3bdb9c9adf35a9066ea4c449a2e6120d2f55002b0672112f7639ffd3e6699ff12e884499ac8862d9b260cae6cdec1daa363f36715a7271111276caeb4d617dadd87a7693b9f88acff37214cec2c4f3f8bcfd0b7a96726fc067426f3f7c0ce89080d007f09f34e4c9f7aad58c8142b65e32f695193a72d86634f848d1036c7eaf57d6441d0483fbb169e1dc8274485f9f1ff7dcde470b76a6d2f84d9dac0dc9770ba56794dc6ab92faff2aec26e7fb7dce209958fe004eb9128c03249a7c5d6cbbf64615db6c2467ca7f9ef04dce1e80f6de10c3ed872bf6fc5602cbead4fd4d80420418ec8a3de386b7c537ea2c697a60475c9fed572703d0a5f1df3ecd57a51d41ab64e910d2d533f6b6ea0e59219d8bf7d8b8c6a1951efbc0f36706cf4077463cea2e5ad785d529634d0337db4917e6489dd96c47282bfac091778a1eb30e5316798ee55444e60fb5ce40b7a67a60e8781f6578c72154807d50bab9592c5b2e8fe52518b164b84786779d7146fa1d594685599491d36797d426a7f62923214f836ab72fbc51c836ac6629656ca49cd4bae1413bb7294aafcf4738178c88a29d59c8e99c335a097baf1055c0e8973464609fe62a072791aa71fe33a166213d5babb9cf6f3dd7432f8b1173114806468d4c99f36337292f323ca935b3385f2c85e32e6b70206e65beb5ec4f959e57042c17054b6b55a834128e71184378c2faa81282d4bc3ffa6cc6c1e53325711c638cf3b525379721b3e673e55e53a5daf99f37bf749d74e05ee3d5642f9673fa4feabeedca51072b034392005f716385d831cb7d14a9b63facbc2a10aaa2c6924c1eab51715f663f03043e58938555e2dd29b6a59cc12ab41b6f63253aee8361464ab2212dc4c7251c8ed75038c54d9844b4e922b0209a1c63ffabaea2b6425ce7d4b2126c60f72b6116d457f7ace3b17e9f057d83ad01027b87f77933e6acb37e716229742483134a8dcd3a20feb22175a42ecd9ace5c6a74f9fd8e0e88f9a295c748b2dbbdc722977bbda1cc7da182201dec99c77d7b0fdd3f0ba68035c2f23e832e0b5c8652fcc79033c026c235624f7b766b7f92664f0c922e6346e06247ba7d7064a0a4a202949dfe10385b69a508254f04a8577962d7987576dd922c1ef9edb3b75cabb72469dc6b6b0b306f50a6562738937379f0914edc6a44a6bf7e74328b0382b42725795397641247e24ba634f247e728c1c84b95847e3d09845d845dd979b9107725001e49f9d804f800fa960bdbf8de4d901ad4e1733ddb4aab8957bf61a0f23723da38f3139f60d926178aa0412526a37387d6fc0da1467a22042fb1029ebde72ebec734d4214a2e1e2a61cad3442afd5ab39bab9f0088069b7c61c9471577572d91825d70d9e3f7b5647d8c8990609c99388eaf77954b9c7bf042e655cdfb0472bcd90f15f3d3338046e58ace79d1e29df4918afe01f0318e99139391192bb72d6c642a5b2ca6743ffe1dd775632b33b487a10be27d58587190bfb25fc6cbb72b2d0c220f51c2952f8519af90aa24b5c825653c44ef6f12b9d39f4db87b23637f2c0650287c898582895dd2743f4f21bfd12f7a14d3a91e9ced492ca6e08817204793a6b2f31ffc307fe50b05d9d69a8a695f0b86939d86e1396746a6934c57206ce8783ae6ba2bee3da8a9b46ef73f47b99d03af819492f746f8cd2e98f2a576d6fb2177d9b67218fe8708360121bf9c86fc412798ad48cdccaacea8c99c25dc6b299e910fda9485eecae2155b9ffe4bdcdec5a0f1360ae608c35e1a3d65a7257d7f8967326fa53e8d5e182cdeebd90a8d844cf4f9d1a48c67f8132399bcb723d115812b8dd0cad506282665dab060b3cb7daca2dd6a6c3a44fe337b4896472e08ce026767ea4f567d493560ef126e30bbcac65de066bd10202064e5c31b25586dc02bfd02449435ffba704c14545c7b55c71e36defb6db0549bb98a270e17241fc26399be36673b10e6f4229641220e997d1aaf9d486e9e70729e57d03e85605080335aae055fc8b028ab1591be6bab3cf408fef7e02c3343f6ab119bb274280ba681837475a33b0ce5857516872834f233c6408db1b4df0437ba275a66c722ffafd8a096a77eff713478510f1a600fb3d76ce95e5d49fa2351c760907e24eea69961b4ebdba78a9106f4007fbff8f74b95573d822e841714a16ae453fb67269fbf25208ab7d86e26d97a963113eb195cb30b50501ab268372efbefe0ef3724833b0178cc420443afd83ec53929c666062ba4af500b49f2a112768f96c467287e957b39d21029e320f5916c7a879ac0e56f502b9ee7093f0b4dbb2da98bb311904ee63cf58d6f558a957d7fb7ef29573f7e9861ecd95a10fe6d665d8423172d201066d7b27ea5c5a9895a682e60380bd724398dd308541fba7569c74448a727a782bf783e5c049afca74e6c4e46035e20d9a21175972aab3749c1b6e51bc72fcacde1f95d36846eda390ba7716274db4193d5a37b879e3d36a84e8a668bf1c7edf7b88093b5bb09e78657a4491b0e0e1ce38f1e805146803e38c1c1862cc72a037802fd9e70c50027e752d9f97c4aca5cdebfc24b602e30a1c03939a36347201fddf607ba58d40a5e6619f0eede2134e6e7369b04d9f9445dc93053eafca72f37c198575fa755114132a1dcf93754d3909226acbc2cc4a018714b210aaac0ffb44d77687477c962a6e9d14a87fcf2b5eb905e0f93f7bf8a679d443daf711729edfb03eb23b155e197782ebb277f95990a2662ee9d8dd2b38bae5aad9964c7295de313bf8fee6198ed6993643a21fcdfbe1c5334e2f74107633b97ebe098972458ea24c2ee2f449944dd7ffe34d80965e5be6271fb2fd10cc4e21113d07b72889881c8bacc3fd4d1886b0366d066d9bcb52e06e41d6a67958652b1cf02071582481f5522ec8a9532d39da921c9bc01c338cfb7f68076ed7a49a388386ab7472ad62c4cbd0cf46af5e795f653175d5aa54feb3ddaacd5b32bf80cd336dad47198a4cef8700ce46ce6cca34c03eedd0d7a83a33603c38fd8ad61ed9df328697728d48e6758ade1b7bc00d95a3d5b8ec81eac2f194e2abd35a6bc72f33206a307248b3c858612bff20def86ef2b93bda35af588bc8b59d5d3430dc54199d0c4c2f421d2618f6c3dcfce763bf20773518da1ee1913d4cafbf3b94644482fe4aba7202b90b1248c9d4029f8086b79253bfba3b8f9e24d2017f60d39d0ef270cf4f496ab7f90b96cddb6d3716f642803b8bc313cacafb65c940fb8c4702e3267b8372796ace9712d7c90d3220d3b8e61a90c31bf0e81ad6a476032e2e1d235316652286c57e540109f52491bef8a82d12d48a04c34fbf86f62a5c354c14f45779ec3cfe8c59e517e834faf576a59c050f49780b347acfe6cf9bd0178d56c9b75b1d72062dc7972eab2c4e0f6864482e5c2afb68bd39c7a2abd2415aa59e1c4225c02fc8ba7dd19b7ea87e7dda667a780918799cadac743000fc96e4bd11be8c7d4a314f23487a58267316c2ce3d82ea0d6fa02900ef6d26585ec20783f037c0bb965dc2ac6a95a45127d270f35daf44fd6873a4e94eaffad780dd72568d5f97fb1753fb470eb158775739495bfa184fa382c2d2cb7ff5f7983a66678672483af2805c16bc606ae883580bff6667d761d85d03aab0e709b8796aae18e0fae862c2207256830d9112c5c7ffabd043f54998056ac6049c7891bb3c8cae8025ea277212514db308a88f8395deb7043ceae6ac2e9397e6de0e11ad166701e5f4af12439672c1a5699ad3ad57faddd7158f3300560449fb3aa00296653856b08daf02b1a6725e5f0be7e31a661edbb3125d892940440af0ff9a6ce492a21a5f4d37b45c4272c2792477cf6e8c0e20fe18edbb9b983fc9171aa855876b7e14b3c05491a1441339da73dc6e97a023fe8bc7baf5328b76810350c4e6f5e948658dfe63ba2c917235f810c66eeb0f1c1dfc405ae5617c1bddace5c405006a32ac02e6878e61867284d89634a20c975f66c5cf5065c14570ca373f89a7711ec39c43178c4b3465726c0c6177a9cf5cd9f074dcab4e8946d8c4fe112e197c5b763c7ebf808f4249445dd791b7e1768b03719abea86fc34562bf60ad95eebfa7fbffa45c50dba4c2723f8a2e0b70caa997fc40ad517503d171a2932304b4ac09618990301f98fe953c94ecc8042c2e572635aba0c7a6103232c451d0f903e239f4c6dd22b0ad891320063971a025047046107992d61ad72ca5d624c95cf5a6155228782f484d49c91146bcd44252816489cbc33f29b49a069fe70ee956b31efff1e5d1abd5d95f43729dee963b2a3cadcf1b407d43d1b01eda503f057fb9cd0e00bbaaf25771af84537530097c971316f9659748bb03110c5e993e60db26e91cf58870fc9406d01d72faaa79e023e8d9a962b67d93bf56da01b6b7ce585c885de3b0b583a86fc0430af6918d9aa9b0e50cf35397178add734cd84c232f4b3edd6539ddbad7152441601b33b22b14eda82de6cceed03ea42e2ed70788d014abc80c13c5aa5f716ae91a8160b30f571df22f0dd63b6b5d799c417cc1ace627f47aa39b97c1f1d03f7d1cc7364578441d324c4dacab9292868d5796a0c97b815a8eb5cdc916a076888e72747ad051fad1e958c0240ab8fd51f8dd5ded8e5ef673a188a7673625fe16193725f2537a153b645f7886d32ca08c4b07c1bd93a97f27e0c24b95373ba152c672e581779fe4d8fce51fcffe07ab6dcb3df532b9982c35ecd59d3e22a915f050727e112ec81d838c75a7fb50ef5079bf491976031871a7ccd512bc432b7732e672266d65b71721c50254879b413f6f481ccb1c9114eb5134a931ead2ca55bb12729d659b5d7f5354190c5275b056da764f55485833fa8db512db3c15ddff396972918953ce00fb7c2bf290d6d28a1d90d478181163da98db70be4cf17482e1be72db3ab943c8d40dffea5bb24317644b229822e2cd967e86e791151158223fe0722d8dbde626044de95cf8f555612d21683acafbb3837b5190fc88ecb945c17772fcc01ed91958257a5b7a6361aacddd1ef4a04d47bfcc4ef9b422579d88a44e72f6015e2586dfe0189d16dc42b7aa104f3c6a588a3a5c069c3624e802419d6572e80f38a89de33cfe5a5793cde7df2c3ea2b631f75c5654daec419331668e1272b6f9a1bda4632846da03b412cd91466e99e667d329ac2492cf4933a1cf906125acb4d8656b1924c82e877dc8fff5aad65b7d63f27d51ae2a6d31bac7c7a4df725e05fdd21ee45d70bc706d1ebe606c5b51c8ba9f3cb664a8ef452fe19e4f20721d67df7c06e70c6f01dc9af8380b7f263e879d9383ad2bb4eb9cc0ad5a6f2252df5e9a3fa6c3976e4175bb6ceb4906db5b74ef522fab9088ba24613afb890672a38c988aa4f60aa0bb6f1ce896e697822a9c599ab25c7431233510936e89502bb8b5451e46e7bf1e17c1561b4c4b04c5ee116a2f7b8be70c2c04133cb3196372d68c5838f6f29e77efadaeba1c4a4e4417614675e19fa790999ec85bfe83187211cbf60564f4e24ac9b3160e6ce3beec3c955c1e18b23c380643e0904648f572d41a46c66d932c558f7d7fb6f580dc65b1e5e73e2df713c55df7d7d3a18c7872ae4c2222e26036e6c65907a0a13739e9210aaceb457c0a0c43921fd6d945c6ba020000726efe34e810d8f791443efa2519fea699857cb8bcbf93f69f6eea4163753333722d90c34263c1fa01d295f3b1d131883229863352f8fda92c8ed91fa7acefe7283d42b262995608197bd581eadc7e816f05c523e3af5268805d988d6d1b5624530ac993abadd6c58bb6016c4b64be6c49c96f6f72d4a8345914ccf5ebddf1e64fafc7f321031ebe893ab9ade89cc61b1cbf5699cfed12dbf2f578867c1257ba720e32feead6bf23e744f0dad8814e27c0ed3b9ab307093f3d43ff7c201d9b6833c94c4289876342e7bc6f78af0e51202f807d2a9d06d2f775cafc287ae520c73730a640fee6105980158d114200945cdf9ac52e84bd0e44a728f47dd2e768fb72eed8961f59036100f578689d044f1e22f53aa06e08eb2faf07e5b42775b6957256d5117dfb1f5d657ddfbeae2b1a5c471764056f1faf82524dae09363e367f49d0d9cfb6b02e9e990f6e41ba4f85ce98c897ac05f0a5a726af8b6916433fd972d592a1f8f12db42f80355d2873bd4b848d44141fd0dcf0d4f4fdab85abaef30d5511b1e41ba3bbfc082867f247d3e4db01a85244e4d7e69ff98c14de2ca20d72aa255353d5a52ba21d815ee6c69b677055858ad18e02a4b92c7e82c001751d72b8320ba07fea9be9a30d1880ce9f8abdeebcdb2f5c4f90489d4e561813528572292d002d659c84cdb5e451b3e61e7c09c1b8195d7f91dc2a2fff325a7ab0ac7222baed39e6c7b90041b28a424bdc9efaaa49fabfdfcc5393bdb49d3dacdd114e1d81adc020cfe78f7b851c03bb868d5613ec397d1975c54a49a8b5aa21745b727d673f0f510f818baef31ad1e0a3a0317ed86a437a9b62fc415c93d74449980f691427f94152e824f6acdf79a9723b51c6686e3e9e0dec9d69b9c7bf45d58a0f31f573c44f6148c75f5ec5962bbcb99ce5f890d86aed597e205a0f47228056720882d97fa041a554148ecc04f91613ebbfac34cb093a59550897bc2489dda57273d77e9a86944992c30c9fd7732991129a4064bd3f6622e7b866b74fffadea46693d7526277bd75ae74f1d7594eb95dc57d393f53c9ab2edabdbd89c8c42d91f003b8b7baf2350354218177b3103fd3cf9bf7ddb156624d8f11bc9f9cfb3f5726d27956da0f95d071ae0feec9d88e4862243846eadd7a114b4f604225798ce72a82611fe05054577fe3fbfb6231ffa772db4f17f9c7ca28b7090282dd37ab7723d2c26c9fb2db63a035276abe640c7de2114ae01e154d8736587ef54596ede72e5913a1089b859fb8347ffab3284b5c6312a3622d8f398e2a829004b3392ba725946e482702ab6d7b3992afc2810e38e86487c9c21006014a3b9253cde963072e6b2d13656235a5dc2c8b0557a49720126a9d00d93457d047aa9e3265d4dff7295a1aa431eeaf14d5afa086c276400ffa2ec3b1b819a605667a01b89d64f2a0c2c6e3abe8154e65760c6eb3af352f0f59d2b689564db855bb2e4100c6d0ddc7283a70363c96c2a4fc205fd3557a905874f002816f2e439ca48a103b6904b893d959e84bcec542292372fbbd1d13988848f3077fb8d7a67aa3bd173e8883df172d69599d3611f38e13b119ceca68a4be4bdd0012717183d120b74ab3c0a3af70d69c469a301d953843a440d24c678146c5069dd2bac15500190c308b440482b728c64a8bf27d120ce739f16910cf6c6225bd2ba801ecd9e8d27f205aca01efa72295b7aa5f52ed04c0b5e4e59a4bfd7a50c58797441cbf0deda069f3dd6925c72b1adff32c22e3452f86644910d6402a80e802c721a168b18d56182d7ab9ff84c06bb4155c16d99743a2fae84c2f3616736d90bcea1c35f62a8a9574deea727725309756f9c9d57d3d0feed333490e6246a1ed5a492407bfae041ec70aaaf1b72294c84261e6bcdcac9b1131e0c537ebe041767bd2039e3a1108cde8520437872be172e4a909813fd3c7fe34481ef4faa647cdf2f60c607c27dd06452b4a550729bfc32023ae3fe18b6195d4d8c75df6f941f1366f3fe13c0dce9d5f47ce976720afab531936c3b8ba9cd04fddf6c91fbd33c994916a933ee59adb54a6622875ce7f55997e353ab2522b227c915a3e740fba9b2deb656e68c24cf72fba543950e48e00c787516c8a26a52101ad989f870724083c6929543fac2beedaabd35f81c9d2c3b833d58943789181622bbe87e50329b67e775f26cbd561aa5f501c481729b59ef5fb914dd28941d98a8a443cebda699c9b7d0036ecc955b598a885edc69440b03593fb13061fcc68e19e9f364387087aa1ff3b83f57abb78360a434e61974a4c70717ee69b4646f0d0a16fdcade6554422739410e434d8aafebb2807d446de981dc8f83c51c37c120df91c424cbcb701c5b627cabb84dcdb0978e310532d0c210ecb4199e7b7540bb362222e077b9d93abab71fc26d18fa462a7c680d72281b3e7f605af0b998c748c0a48867a2076053a6a9e5d44d850df4f356ede0721875c1ab2c77826410357fd59da4ea2b35a39e574f0bccb9773ad7e684acb44f857f672a2fa6bc2815d1c5c3653139af29cdd602e03c9df8bfda580370355d72de1540f5443f3705aa3e8a51e38319caf8b789d57983b3f2ddb6f1dad9fc877228799850c9934954c3161e9e4877d41366ed86a3e1123e522d6c30853626cc7292d55668b57f00771e64208ebe104b9d31f4cf7b104be1fc1692858cc135d172bb8fabc5d08a61faaf7e44d01ff60e69445c0b349f2674f152a872a5f02cc414fe547b0001ab8b8af798bbd683c6dd6ccbf1fe29448312cca894af5a2617ae72951d94cfb821a8a1dd67fe0f2d0c687eada36ca9d961d6c8ac25054e148b367278c5fc79ec3d7c0c40afad8781119d353dfc163937fef613d664b5bf9bef57720bcdd55a66d844bb18217e83e0f68b58ac7ebb1b67a332a03e021cd1f8ade31e6319b2ca1cdcdd2a855ddcd5a8ef7acbe20816657ff08861fa3a1b3966de5072c31f3e4752e9cdf5177dc4e50b270b0b87139c07185e3cb9b5822ee8657ba6722a233669db0c8107bfa1e0c4853e7f1e2d9eac3cc66d56be8f080027f9c86939211b1a156a5e4b4eff4aeb42987a5f463b72834c17d0021e49bc30fba3443e4c87dcd4787b5e958d74a9918e93654c0ed632f0069bbd18b0598f2ecd8e2f897208e6deb56de96d55f61e1c1bcd67a0d9d4b3d507a09a0217457b90937eb101725f0f3d296f879102982c329565664a75c75a90d929144c0bc5a7c6edc6404e729c7723cf36bbe95c8ce138a3488189c83dda921752b54dbff73c2d026b088372fff588e0b26efde75586d2aa86e311a4b624ea2361e3f80090d304bedc6c4321831cb17609b93ece1bbb30e61f397dda36cfb9cf847d7334dfb2ca1090fb3d724211faa45dbf5135b292b4f2129092992733dbf2836348a26ceea3c401b82c72d153900e86976bc2c855579bdec3092abc62dd1d0764fb7a6604e4e1b743137214406eaf9fcfbfb6706d51e4001614557d864196c6658edcb28bb15916552b3d7c9b7a642546d91ae164316fb88e8c9d91aef1d7def4f9dab22e0faa9779d80e61c97825ec9f28683f51e885526d9e80073f87e5914acce4944eb3ab834dad7273bd6971656865ca220b26b5dabbf8aedf39a8ad1f4976a16ab08abc93543b720ef3da0ff55c85493b9f40e39c38750c8ec7a5e7f1c9d8f3dd205a492bf8a309dc839bd7c9240143fdef794c6d871ebd3a653bd88d892e870fea73ddf07bfb16ec20085c74237e933a31d1a4757cfc6fef1413e7b04c85bfc9f64cc0d14d0228400a927fdf5b9278e014da27b359bc8565c922115c40c3c1a4b91a97700db772751e996a34fdad544c01235148eddfbce4289077a28126c162867c7bad7252174cb8b518830958ee03d2231e8726c8217d563c4e794dfb9335e7b311c7c82f720af4a15e605d1b5f7d73aed5ddc0dfe90e6bb04498e255799770710bc9306772721e081e8ad4d1952958bc7cc66ce8f7761135c5432fa342a8aa4c910c40c672182ef4361324c2f509ec095a0b4ad12d76790d5c71493095d7d1ffdd4d30ef2e997b5c01ec1716b2208c5d1e63ecffb5106c3ce65c054c4a35d687a17af7ba72a7212bc6a6358284b7f26bd99b8f38c5b576ed24d5e9970c878cefbb83abbe72aee298cf361ba494cd67dad1df6855f8f0ce9ca2f6615b495f8b8e34aca0aa728466fb0ebfff58e716999c8dd3d7d0c3e9ae3223705efaa1c73e019cf6711472c2526d6cbe19fc0b1ff07188ec170370fa1cab511cd491c2542a8f8f1a354734e649a2be8f0797ff8dc9d5744ab048d5edcad57c5ec8240f023f0fc1d652cb7245a76bd2c14aa939a9bbbdc1d0fcdc15f717bfd17468e751f6ba767bd024fd72ca29ee8fa6f4b84676f434052be8a2f260bafcb3a120b07a7032b286e116de72049e98f3809f3ec13604e0245b1b4c121c18a81b0af490c8946b54bcfbff6a72f96abf7e3e701fb2957f9d0148437f55583ee4beb7c9ab637793277182c17e7209348aebc292c2751940578f642891293e36eef37a147a95e1d72b89d926b072fd2109caece606c3d0bbe4074bd8cef6002c2ada085c9139dd12b324eb9f13727cafe52b7661098eb136e4233035aca7e39228d07a20d2b10fad5d97b0b49a66487f4d970dfe9712b7a8f913e8162bb7c52cf8667fabfdbebe572a35cb5b5b4b9642437a78632ca5309b29fd96cb9253ca15ded2044cf200d79bbb77951f884b993014d3168482234f55cb72f9efdd87a47374b64848ca4046c1533d90c083725682b16eb8bb857b774690ab09ff0bbb21f458c7a8d5dc8f168215acfe06b03fd8e85fb1fefea38167e18e1c942f3879f7563e484c87b7388450a438bf127b72b8f2e7356cd9b7d268779d82983cfc4d3e8d221273bbcc40c5413ce64a750d72d8d67a69fcd088f00e694f31f86bd5b9491358df72c6734e8f80988e5c6fe4722fd33bf2f87f63d71483651435e2d98cc283f6c2ebfcf693074daa4d3fd579726774e96da2f6ae4f7a67a5c4c29c9e86ce4bc12a1984c7e2f637ef6c4dc2f27251bfbd20485f9cdd5319869095f92adaa7dbc7041bfd1d5c6942a288ae4d475b9270b8d786ce583768d789ec270d1366fe721450d18ceaa6ec160616366b3c72006e19f6e57a4a7b0811583457997d41ff1e6c60a2a86e0f3f2f1d5ef8148944f3c7ade5c5a69c4968068e30f29ee2eb734739f6656ae6d1224c70c80d266f72ed2acc35ebd5c9f1ca3725ebf8c128cde8404e4d9a7aee6d867e538f8330535ac43e62736ecc03412c50be271153705bc55b4932f93b19e97625d8f7ace5cc72c64087c2fa1c9cb0221384df79b14d14d1c70775264b0f5c01447211735dcf725fa3b2201b18ba5b98f9c2a6da309eb8a5761442f8e1f47c2b4fd820c21f572f6d83e9d1d8d8e99d24e1f99d019aa64d22933f4164de175a17ce00a9c6ee047206fe1c051aa7a4f2ae262b8060510c3affe16de2644b72524e950d9a87f3cb726f2aeb11f3f8f09db4fd83e7a978b260a961063defa7e7ccc66ad91eebac5526f6e9aa0c68d0328710b3092d51f2ec663fc5c47b7754fb35ccd25efe4bd1bb7290dc58e76d2f88b8875af416b343e73125c7e60f13388e156eba36e63fe8355002a4c1ddd1119523c4c92cd5f91c72c3613d312c7ba9948f010c217c936a7872bfd4d0ac6b2e7ca0cdbf600e3382b0bb459b69c0aa04f163d655a92f72178a729876672321ccbe4764454d18467829320ebce9256cebd47936ace90aac0dd072e81675d102c74a6f88dcefdce5df4f03c14a1b9003d193764f8e358d35b74972fbb5ae2d3937a8dd0a4018a8231829af184e49846b3541f96e37878de8e01a724006076d1bf1f68a22f6abe286c0f86358374dc7b7f1409fffbdd5c592d17956430c8cf2897860dd8a82349a89d247cd235a55593bfb718406f8130933578972fb7af0134ee90161cb26c3e8a62dee55fec9201e94d7e4157273e59017f02d323f835fb9eb5696ed924449c8c00ba1a54f66980bad4c5590df19f1b7bdc52d72b837f57c1bee1b60b122ffff24a8ab4ffb2779424a4d8e7a2ac137105d812c5f80806f34048eaf058f8a86a97b2e42aa7b1470b79d526cca4457872b547a807278c3a942a2b3fdae256e75bcfca5e9bec7d1e0227b28b48e1d5e1bd5e3895e7248e76627e91fca9a83f1388eebc567e3796ebce6ab2db02a653eb5f01c0ed87295c6da5134485e8f75727b8e51d4b45971791b1c3df7ffdf640c83df27373a72964507bff2ce7fe49cb9b344e65147fec60c05004781056b0e9c93475970a572f0371ca2d3401521e89c0ad1f5c8daaa6e4b97c2f2c96b62c4bd373f232dfb72d0cca5d875d78a2e22e58ac929c35f5e82be9c9724dafec77e18941f0fffc872fdde0cdb378341696890e87142897b0b7fa45a08057193e6f61de97826b3d55c6c404c9ae48e01e4443345b88dcd265c0d3ac8bf667196da02d1c8d07a9f297279bea37fc559fa842a07a4aef1cf3b98fd1a5e807ec9b6e9f651bbe417331b3abc8c352ccc7915d414eb9a57e68202f76e21d0e728ce00d4c7ce0165603960721ef1d8fd80b3e2d0f9b86ffefbeaab34005ff94fc6dc1e5974fe4e9acc7a9f724d54e81dfbfeba1d83701d2ddba944658b3c8c7eda94b68936d24e3bc5961372b2ed6ea5a0a195bdfc61b207ca1772bc70201874fc8588608725e645770c264d506493307453159f5965d383dbf76e9df99b73b121976c1aea3abd2fa3be4f6584670bfe831cc8566b5bd29e53baef8853c7ae6be4e344debebb49eaf387c046e9c8bf6c06fbb1ccb38e2bf244e16468c5f4a900496b6c5910125a65486848728f07d8519f967ef03df966d81e15eeb230dd811c9967daacbeadcd2084881872d5a89ac83163daa8450a545b80acdafbb080699355c0d815db185c05d84e3272485c224a8063cd03f42c669b7d81d1f1d0ac4a27b32f15a8547ec7cdfb4628621a5afb8ff8abc290909f72e0000823731fdf390da6b3b85cc12bf3c94769f93450bd3b8853af75c7f01a8e5e949656327dc79c78adcd1e3966c5738906b7085aa295ec0e62c10be08c7b9eaf7b710851031c1d341509ac68320a5f70b090327276c88f4e11678cf320a1ec67f9e4695b2f99d22dfeda73e7d59be37ca34a927271348f1dd5ac087a593157c216460eadf0b532f0f880f8ce2d40205cb5f65472a74a22aab2d1b511a91d3f4998365a2d09dc57f13fc2e6b4081134a4a1f2567200ffa7368d04ea0301222652ac4ae60624ab132ba31234335ec6cc484750b37281d991a65440369063412fc2cd03c067167e46a1546909b6b8a688a9067d197009fbe5fcbd5f430c91c076a7dde0cfea1d7351eaa4488f4d32da99da79797362ae3d51fb46eef25feff7be5f46aed58ec5001af53128f4d69f85f7626adda33e028ad67581bd94b487815bc674bc0b01f3762a292f01a0f2475c4953963ee537f76d7c604ac3ac9c1d01fd25b89c5956ebbb50db271269a86c906ce5ff8af372f4461fe22485bfff7b3ed30e4a09246ebc1d7610afcacdcfa8a21c87c3c79c7235170571b41aad6be1688fab2fad47ea09aeb3809c9a896a4800c99ab2a76f481ca11b0df8dab492087871c13187ba6c3c074a6afd9d7803b871aca31fdcbe72ffc43d35e2adc71aba62d0d3013abf275e22d6fe192f60a6639b7146e0b2fb7218081b07000bf57a90de24763bbbcadf60e1afd9c4ea91dbc0f28e6bd03e04435e85df66ca342a3efacd466350d15d783f9d2c9bd291474a64e81e637a0a621022784706164f6a9466904fa56904f5bd1d9bea53a9a685910c5e462078cca872387c4a2246c315b64cd2c485099ed279309675135701097f079050af0ab3ab025c2135b2b7269e31e5a6349b2beb3efff62ca3471a85d62ac028804baa2f327276227a5533ad4f954abc59cf651ba7ff7dc18d854bd04b71e2b0674a1e858c35a62124aafd5f4b36e9dd1c8e1a2a79eded1ea6eadb39c512df6493f060ae9d2dd84262ab3ac33d66a69b2145d7d89f1ff8fc42a421da739f7eb81b2aa749bb72ac360661cf4b1622a24d3d09999e9f4135f3633d2f5358d0cb79f4a4d846d203fb1eb93dbcadfda0b086d38a7104e4de15227609573f813d325c3a3630229312ee30bd04d614b1505bb93924d4ca0ec0c24458e8abe4b94049f27a32e5bf97721b7a1d39347cb0d130502c5e15dfb0e5b27d74c2b6f36637e8605c1c0965c0725c109a5ae2297c0fc5865b89212e19b9329f7889067b14b3f0a9c6f1605bc472f401fd1914e09aaafe5df6b2ae6b9c7e82f1dba6e7b8f837fb635b01db69965176014c559e6b29c2975f88b7c828079c0367f255a1c1f0a677113adf8455782e2dcdc1924f9800c0986d1a65a4cd132d3c2f31431007c60897e8c04cfd4f7272cc9b0340f2907e67a81034b0a3f0d443d8670ab8f135d8e54d209136e3280172d8b126ad6f4d99b738a96beda08810eb732d459482dbccd79338596dcd5be572bc8ca350c1c31bc9524f09b814052029b113b537d744612b8f82deb84de86612bf57102979bc52b50bbb5a30dd9d66532c290d2675c224b5671b664bc1f1e872b486d4adc7f9f292cece0ad5c6cb3ef84ab6c00f1c0e753071d041086363d638bbe679bca363a9d96444a5b49579598104f4ad134bf116b8f6ffe1fd7453be72f7549bcb59fd5d326d2d1ce670d4470ba35e904108f5e430d16d7c2911355672f3d1b0fddd77f0d555afa24bf12bebcdbfd0258e3708a3a086b0d2963679943db39ed4a1385ad76bf234d38ef73e2081b8a5aeadfbb604b91389bcb01f7879066a1db9643c256377b320947314e9ebf89734ffacab55e0d6fe6e443ef13dee6608ea3da8337c16cb52d3404da1f2f11b1c5455b2d03da16a6e685d70071e6072431c4e1dcfdc34ade27a27fc100b76bff9060761046d198ad9fa234fdfc70245283e9b6ca8f22461991217f62e4f6257c90490b631daec5f50df641cbeb53f425837a57cee43a1ab609b4ff0bc40355dc40fe31831110a5b5c05ae70c1241372a23412c4793159d0939c24e80c91a4e60ba2fd629cc05dbc9ac8611164ecc6017c64db8293e60a8e3bc6c19560d5eab0bc38705f77b1b4c28b24bc60f268ec540ded4b0dde35c5a3db8a62f496379dbb57ab7df83b000b994425c1bcf9ef6263b6de7a074fe8e168e6fd309156be188b00d8aaf76ce47fadf6283368bdb3f55118fad36c61a0f0ecfe3eb2208e7c1486b6877f2f9190b5473dd7256bd9d78c5e548546c0fc2cb50ea139d490b8aece57e0217b70d53d45c59aa174239119a23ffb15496a6b1403eeb1edab5a66eea21e530665058c9c5814506d6a5f7476fe35cd3ad6c9b9ed6e570bf6e8a19c2a666ee081d5cf1cd42c9ee4742f9e8db188723505561e036b4e1f9c881edb78126a450e3280f3bab2dfb87c9148390d31a024662ac2a3fccb00b9e0f0d7b80db43e690a16d2ef892a09f2082b03b26f0f4568868d539568f943a12056498befe614ab519e97b181d20e5d725cbed5bcef9d62f6811d0b465ecbfe876c8d2bc0534d461cb5e9bf964984c1da63716b05ea8b7277fd250e8d665ac0f7dc9c367eea224f918250582acf3ac2810c8c739e5d3c720d3cdef42c351ec82de8147c19e73349be7942c9f835773a7e016283ab234e72d50a9a7dce2ff1740025f738a40ad004c790f83ee81b018b35c5e490894eb272aa96eee686a9fbf7f50eddc4dd0a00a21e6a3d500ccfd2786d5410768ce36872a57bc66fd8c9c95dc6c2632ab07b03b1319ab5bdab678f108ee532cc1d468c7230659798f79fce6c73258070d3d97077c869fa7f7c22dcb2959a3dde1f447572c6d52c516ee573c56cec02507668f539cfbce0be5ce7c7b03e4ee5121d61cd728887b1a999409f7ffc14e0046c4561c7c8cc56796f5721565eacb116e9f9cb008b4e8254b0378a9d41d3d77ed813af576f39d4de238229563b4fe4cbe8e76d7231156f46837cdfdcb6fab825df5f9236f9c057c3fe24bd3d61737d7874ff6870ceef562bc042c1d15069ad5182fb07756cd61c52674631293d16cad38a05c472d8bd107775fd1557868f202dc8ac293604e12a9cd79d372ad549df1c5e3ed14d1a419b39552f35305ed5da55d5a2bd1afc3b064522fd1f81649b41a670f12343200f5eb9ce393dea8d9df30d0ddd39a3d798e4ffab8c8958ab56faf0b7a21772c34b993d0b0b271b694ec347863c3f06dafc4863eafcbd878da735fbc645a517a9fc9c76f8e118e53d36014bba61b155a195dcd23c6e544c02c44ab052c6c421f715f82d12bb781515d711064724d5dbeb49c28453ab0327c086d573bbeda97246af374bfc0971f3208b9bdeba0d53b550e1413bb325753c93e44d543f1b753fbd3b0049df19a2d5aaa108b7572d10fb6a08872bebe4911bf596008b899f52721f03444a0b0e69b1a98cf48b06843195175e488c5945bf8d502f54ecddec5e243254fe583856ffad0c970c08ed8f33ec3d2d31397800a59e542f5d2d533a88152686c184b37a423e0938ca498ab07c6d0a09ad04ff107a8cb3e81102055cca72cabc6032531f9d4ff4b25e6a4c4b00478d8c1795f42bcd068b261e7e71f988285186cdd29df27eedde19cbff8ca85fa840be35cd192e09e616d19997144aaf729a1bffd1b9202e3f866cf51436fabaa26598594832d751698898d3121cdf707253c949792dec717c7072887c2475d34ca8ce7881a2438e12b8b957d94165f6725a4bd5bd563bcc1c8090394f115fff869f857189151caf057f912e191a7aca1df7ea3a38d50326e9e278bd4200be403b59f4a994743506fbdd783a24a7d02e3eb10155db08cb5a619fe9539a500fe85ac6f7b66e46e0acb28a00f7156b47aa724c97a572606e6594df1ef7188071931c23ef827db3d011eaeb8652fc78beb7720e4a6531ebdc900c931c680d7b92fec5b60837de631579972094985795056e6cf3841b333f68018d4e81c53681aa8fae1be21577281ac070b96b197d4115cb54b532d049e68aecb43c306de67ca89375b2e45fad2d2e3d16a4fdaf7c5c651872952d8ba40b06df05401d5dd4da2e6299998d56c44a1fec7af0f706970b73bc7237012b22fe1de22e3d5b2008d4f8e675b65f1addea0fb5533a5a27160d3a0572c67d433f57be8d9255c66e1f8152fb5cbf2aabd1f17f5ff7fd6c1660947d5672f4f8a11a921f2bb9fa960bf9d157e27111f643e1e6f67eb135cb38a4a36aa272a9b7b54796c8a304dfab279c029f99d623ada97cdc852d8b73a907640e4ceb72680a3eb234b1bdd85f4171f4d96b9273662cb25cc23bf3a0b0597cade8e8ee72cc3a404f8991ccb9748986a5107d20d570ad29891c27acd2730a2bfe22aa6c489dc875cc5bf48e8fb29178eaef040484866433dcfb73e092482de86527c25a3f79f63aa716714328e72024998f08585901d62e8e911f1a171c2a8ab1edcb8e196a8b40c9125d498e9eceaab65ea315631ba7f5c6e6ece3294b78b737e9d2cf72de804822aada9e11642bf461fa9c424082ac7ff79472157fb9aafc61b5bf2615b10a87f025317b3f5075b249fe54d82e4754a1d1fd0c30d7ac98342332f1be72f066e3be8e7df6fef2a522399bbcef193f4c656513e1be4fc2b7e9262b6c3b727529afd06b68ec9549ef70b2bc0b6025255418b3965e1981155749602d357a721b2a816ff3642f44206bf98dd242e630a7fca74084adf365b39e1fbe9a62a97261de2b491891943f683380fbd8bf5f5675072a81573498d822039ff2139e8d721fcedaa849a621805e9eb4a7aa179fdb9077a5afe331414fff6fdeaf51b9c16a070aa87d914730443b60e531d1bb4bf2d473382781abc8a052a4846db35f427203fbd68dc0a33af4912b21c8f882e0e68f967c0b41a0b051a7d68cc5c8728b72da97cd9ce579f159eff64a1abd6413f68e9cb00f282100e51c855ed7448ab67234ff9cb48416347660a58df1af70057e7b3b3fc036c3862360bb267ee191ac5d07f007b811f21b5751df84618a0b008d52042a66aad6fceff15bf33b9222977271fbd12c6c55fa0a74024575e8511c37a8e255e7040d19fb07e41363238529720fd53b95842f3fe5196cfa59c9d245d8076d718d893ef2d0a80c1cf7a67f7c723281b6ed8e9ff2341a84675193ac0edd47b39b1cfe85370262a682e70bc87f393a484e99094d02280475159f254fa957198e74f744b59edbe31d07f94310a02c544174d934df6b788643308b62bf3db7aa840e3808e7823866891a4e467f3d4f4c0a558de8a450786b59d62302576616a837b4908e78b33d5bbeeaded788c072b2e49c2bb0aa52592bdb2b57a5fe07a821483400c14348fcc9940c3fa8172972430a282aa5aa51e42b69c1538ea2e425a38c7e3d9065f4517c36e4ef30e84872d2e07833380e3525206742f616deb95b9090c26e5aa4b8f39c87f5c5c7bd5e72a3433d71e8ca76de31a02f20da43227a63b0916eebfe21125078172501bd9572d9793d4467dca44b26315fc30d4526357eed6b01f8bebd7c852810aa09778e0faed807cbcf8cf3a2ebd4b0d57e5ff75dcafa3ce4e8b8f1c95288c41c512397725d5c2ae2d601f9080c033dca01e0b45ea1a9486cc8e5260c9285378bf62c2a3f7c9c5fd5e61d3dacb964f903ecdacf81dd98ed34a82abf55f03e170ca9bbea6dba9c311e5006ad0271cf193a64b94ed6432086bb90c7f68a224fa7a7a32a6a72854ef7b8c3e2136f91e1e1a0ac8e0535bd1b7fa2fa1d4d840ec655ac15538b7262a71e21bc7648355730b9b72a13873368ff666b612cafc04db6dd1c09ca006f994b364a0392e99aea2181d28b9b6c14f1190df3a82ee24b6baab1913869c7720903af299ba787fc6f105e7152c2ca423e1d46a1a8d21f0ab3adec2af9a88972cf84960b123cc02e86366637718e29b430091fd80195b8fb63d4a680e7214d721b581ce6765a0da1f501a83ab7b5e0d1d86ab4f9821843bbaa9a3e0b145be37209f224f8065790a180f34198621e02e7d960b78e99dbc71a85fe74a3aae5620b3cbebcb3b660d7ad4c9d7e759853fdd75277d19492fc6ccf3a161b07b5a0d4452168a2ffa6d5a1b3cefe356e5dd72ecb8e29a9c421f57e78ec01ac0677d92072d3c72276fb8fe13fbea3a806339293b125a5e7926b7d4409a2cd669962a990726e72a4899224b198d3a9a10403998197231497f8723192e5c82533029e72ed72f6baff7b1b378ca708605ca1888e49fe2bc559ad04ad630dd0a21377ad3ed51e7904768335f64ffec182b0bc2f464e6ebaedc75a96d42232a280ba87a37ce572fd60d642733b2187eef2e1a5ac70c84b45b327a6157ba76f047b71c8ea00ff72b26eaef6839128dbc7d5bfbd42adeb9a95f97b957fdfc5677003fd12011f14720a85cfb42e9f7cd04509f61640a9113405e534e84a532a684b092d93134cca12cb26bb2e5d3cd20b794c7812872e9a0cc8420aa1bc38228a75c9b72e9157a5263e6f434a4589e5a78a5d7befacf894071b7fcbd5a2c68eed3576c458ce284f72eb1003e2f5d1961e2eba92d693f2baeb3ad35975cb25a1681c81c355cda51d4794df73fcc098a494fb6875b691a235fd37805ce8a45ef7096f810519fc1f2d72ba9310d761a6c4a47f78e2211fe5531dec4d559240f708833d4de5f75aa4f8375ba2949296a6d5bf1fe948cbf8ff1f39074701918e3224e1bf77753ac369b77240e9b35b3d850a3b6cdb17ea37ce163e72c64b4f3ff95d2202c2e014a8ef967288e706d6d767d11c276612a445ba6d63c3e591e08e104a03dda571505b8540726f77ccd597e72680de3664a6d65ad37a14d1fa6738a86548d7f6f264ce1efa7210cda2ac1c56dab10db68daef7c043f6a86214b94006fc695912141b249c045aca2f2dccadfa443505929fc4184c89e20b90b23316f7e9b5978befce9d55410c624be0d9c56e63229c58a7a8dcc8728f7043d70a5d1ecc49c7c4bb36948f794a21a459c9755e65f2d3edbada30b126637eceeaf8b7df70d203c7bc6cc7284b5a01c9ae8d2dd81ef150910ec7099524034a213cfbd14de84643edbfb872e7e1727e0aa1da264d24dfba16b7455c165d52005e65fde46eca026acea9c3e246e3603c9643900d7ac106d8df5307197cf4b36a267747d92ea029c1303d80db4da97208a53f040011ad6908d3c0371f6658e3cd92ffb3b5729a2073e5aabf5f33710099feda8e6fca16534b34c14763cfd41660ed4809902033975ba8f0b1d08f6672b942313789d4356e92a5390cd6b8980d8b47db9585bb094776a67dbd81aad27227b07b771afa4c92418f23a1785c129c28b6dcd0f5e2416fef9cb341d3d91f242ef4328ef48d3b96f904ab0ab48c7d2d0b7630d7ec3119b7c9b55f81ce5d5d5611d9905d7162fdc714300adb43f207a09771bc66b93645b1a097dc4d30662a72f23afe38cd79e7ea297b54f589939d4537f806286bae4870b0d2c3e4f81e42725c07d6ca5b0a3747328893c5c8834995e06529c0e8cf882dc4a8618067a5ad3371d61913d03189d386012e290dba6a5ee2a574a768bfde11c8d48c001e7e9a3ce49dac49ba67e1ae64991fa78852b71d476a2665df62acab76c94fcd965a2b72ddc630006d15d0389c9d69c4b2bbea1ad18755f8b81e87603f4ea7aea7bc007248385f513821f4fcc4f99b7c359c48c5096d88440fe5691ca2259a0fe92c2272cec1db660aeedd3e282942636e76759d2780b76e560e61fd9e8e40c2fc260172d0fe0a549bdc082546d29ffa71d9b2223116593fd0c931f1e5e83c1cf4f5c272d0b30496464dfbf8675cf9e34c1b2df84c1be0441b1db4f156046b6fa2b45f72270ac014deaca8bba7a675d86631a4ab0222ecac8c9dfb15aee6f6951bb63772cb2fdacd4524564a7e7960b9819d21e1255624fee4dfbd0bf3da8f398ea55c72843bb33a62ba84eb0e64fa2aee9b6a72e0d65b7d7757ad334c7c32798a980717e6528070236bb30f8eef5ce45e178b19bec6cbd281a835ad6f40917cc040e87226b05cadbae1fea182336ae85e1520a911aa0ab8b1dcf7bca7bfcf12110ec872e6446d30972bd8456745e6b29cf0d3a53adb153837ee302a224f084af53a0972c16b5b972fbc484b16fb2e9efbcf33da97779a4f020cb3588347978146399b3f48b2999d7a1f3dd4758d9bff6774c049b6bb043dd50830a8200fe342cb13071961ff55864e23c311aa3c94d58a9da4ff7ba695df2c12e54bc6a206d09d0bfb0ef08d76bb273c046ea6848379b6ffbc9b72b84ed460780051bfd1766f3a2d69721efaee70134172cc7ae35d769a876afcef6f100c082c8a922c6dc2aee85b4c5f4d721f9edfb9b545ab4195b1b85e03d60d88f6e26c7269499c25699e68da907293cf84af91bb406d97559ae67b2c579c8ee7399be7d5113a7b108bdc8584463f89667ae40cdbbd772942aca5362ea2f7ce00f3d2fd0503ea65a1c75d1952fb57d3d3449b07637ce10bac4b09f7252f2a22de32fea8e709aa5e365315e3fcf872433853beb83767375334c1e6cc67ea8fc62ed47c4871c3d0879f979c34b1a67208c0661750864f50b89d8069ae865be2548a4f8a5ea513d19c834d3810b18b728a4fcba6626ed0236f2513651b99d3220bf5517a36cd341368df20476ab41d46bce356ff6cdf0de7df45041714c2fc2c7e18e57f14f9a5c921809edac03f4d500170f649f4036c41b980355f99cc6816ad2137de242acd989805e749a675dc724be7cd9898d2a27022a39e9552a5d98b0ba61ae0020081e37d5955627b187d72d83ed87b312fc07c1da800877888d3c2cbd6fdc3637ec9015f351d3ab06ad41d4b7074f0b07c4f24f304ea6acc857a093c9366784aa78b5cdde829e9928fb83733c5c51e02859f36075870300d1b8bdf335b47b3a54806d4aca6a1053b7cff72cbbfc173d0d79487af7a07ba223d529972a0aa56c0a05ea5d21460e556cc1f22c44598e3b30f1f1b9e3c5a853260ba7eeeba3068b992af0839c34f2fd30cf0081e3947c13b06dfb7d9ba0e8d036744e311f6cde810e3352ae9842e26b01a6950c1ed17536a392d292dfc97b34fc0fd7c61dc900f1c56207bdf0f352f1f7fdb70d5abb13a8854c7f95136b84d48dbce8815bcf99ffefb0fb23a3b3262d8195e54c7b665755e3250226c32016376fc9837e846fe74016043ddba1901038da13e65ac3e6c8f3e01600a7ff02b4da89f6f4efabf4f8c68087297ed9311d37045e35a17d66f6c6791262b39fa8809065a15c6cb4790c93a7ed89f4f15ade13cc685726fbc47f9b7c1a207efbe4f4fe8470b2b1ba4d4750e576e08adeecb1f35ff0b493b9b9512b6af17a76e29b40dc15228a5efe0973fe12fe424c0b358ec773d3f3c2b9bd5b816bda6b292279886c68649fadb32607870b13afd8531ee6f62135272e904b083d6aa195d9448a73ff0e6708cd4970b1bc0a0365719b2229de2c59b18a4b9a3801899cc1b94a35006bcd2833b8311a7901fb39e6feb63bf8239daba591e8d0c2c02312adb6d362838e577c5a3f28a59e83da4c7bc476e6b98f1316172b2b36a1984aa0d5553f4f78fae133924e501c9aea459cd643a0b7bca873fb172fc8a39e0d6551ae600c5e6ada5b1f09d51a21cac0021cff25c1d3cdac7aa9b72937e521eed734ad6dfcb8398026903da13c7c73d1ac5d49b8caceae9b5642e72065604127f50119b17cad9cf16603633a55944c5d67a9d80ee6a8c80be07cc72cb0e1ebd4ced12ebe4e21f1b1566a81d18d21baef30b8668ff0b25535badb61dc674cef23a17db8bb05f19b3433e3a4c6385de29086e6770d531e0898c0f022b960071e3113f724ac36518fd50d4d1469161089a9ee3cb1e5f1fc21dd2c591728137b08ac8f2ac32ee8dec06bc1efb961923c02fed3c11b0495c9af44a35897258be0a1cb5bfa9eeec211edcb51c15a606706a5d1f61d7011db98aaa25250672ee723ea46d7e0b25d41b19fea2547ed201f6e35cc246f1e85d677cff8199ee24e79daf28aef71bb7161a0a44f378c29e1092ae7fb64f31e9314694b16631e867f618e571304fdd1e57e1720506e9dad9e77931b8847e57be4a25a5face54197234543fca233871ce89665a5825bf8fb2da18ddc48abc271d8e3bc45c087c0a4fd085ff48eba549b2d657ca64b8e8ee1e1b610a3a4dd9403e63f9d91384feba7216f2fb1f7a1ffba10e5f245b11d7776b07b8b467d984be98eaf36c75a203eb7247977bda256dc68bc552aad1e5fb966eaee4113c1adb4e3bd62278024946b246034e67735357cdd7afcf8f704f7f9db8b946e5983543d83b34b5d8887b731072e4d5c85962a24cfb44e90d214592973318289856a1bb2a7c8737fda536f4f3598ca98de1c39b97f02d130ce764b905beee309aa1f223de03b3a774c7e86342456935207586d7b53147cdf485df3105b7e236dea7de52dcb3e2c3e9afaf72b57299fd0cb4ab3bd6059f8452cd3701ce6c19b502197f1bb14ccd87b1b6ba9bb717e827d682c2e38c3fd68636d3c88f8b7bf339229f314bc47312e4e509c6a28772e125f20c8376434894e821930d27364c33299e1b26c336a55281810352b3b35cb78e039c063125090a0a41a698ede18b067921a9f4b2a7b6cf9e9c2ad0d59c723223a471080eeec3e53cf1866000c477f3c4d6d60e1a5da7d171f3bda9d3ce72157fc9d58a740abeb427aeb8e0b0ccfeba99d6fa6302ff9beaa724901d72bb67e5f595af729922b91809c5a1df76ddc160867d6dc310f65b6a67e58fb4299f7219a67efaefa3c6768fca0274d9877efab6619efb8c413df6f31fa0b42c93787214c6e76bb0c6170d5099e9e431bdfa4d943daa4fb98fb80ac1d1dd7b85303e72a67ae89d1f41a803fe7693cfa17fa5a7cc2c460b48bbede9b862d9e65639272dcdc013d0d2f67534e491e7e45f1035b69d5f21f0444e3b1b8597e6a0790355727291fde1ceaf7f73bd20cc1eaee8fea472ce1772de853684bce42ec14e24fe1b17509482135e4e2b57f705d4b943d73581325ced7596e54d4a1090d144985c339047a2fac87b50d5d4ee6c37d841dd5b28afa0f89fed29843e718781e4ade672d1eb46c02725fadb88968c6e852a794ce88eec42eaa18fc6dc88452d8269c97244fbda60c6ab8b618b39a3685e2b0c47f45523fb087207a892762b3496b2b5721f8643307aff96526dbbf2c0acd330207e87480bfa6856ab74028f8d262d976c3c457d856c06a614cb4e36e7ec41d43272cf0dcd221ad3d302981cd596ea10728452771173009fa03dde8622b44f3238fe71c561331d6e0b5a6c83dfb273457242442adc98a2fa8ab68fc21a2cea82415295aa433a0f07b7142f5e3ffaf6d772e0ae20381f988ff72232f52eeeaf0074626f3d1a8d91446e3052cb0037c42172c5a54c8248387e17febc05f679afa7f6934e8880c821d8dd4fd6fbbdc5d20a35213e3af1ec2b588fae3c048f8f34b40d832ad6077344e2656976e23e98137d57801f0c90b24bf88532fbd20e183056d33642c529fc1d2af3706988c11111a64e6c4d05b093ae2583d767f82434fed8d536775868d5e03dcc64daf039dd216172bbc0416f6c1caaa61bc5e93c0e4cc159cf235eb72fe6f1a77fa9cb4fc0124c2dc2f0ed974ab9ec68a998131e24c36da69af77c08d11468f5a29f3e166be59c294bb826fc2bf22e26cc741cdc964ae8e6f68414b1f6885349121266d821372772bf15a11b68914477fb95d34297b976559bee59d65d3395a68583901063930972fc98e6594ec7ce970d9d5feb7e83b08ba5bce57069d9ec324fe0d364c707de726608ac96f37afe5e9e32a58f7c32c38ba0f68eef009b203b4d0a41b1f63ee1724bbf17d810bef604444c6010f14a99ae91f18cc2597881d74e0e87967afb7072fe6660668afb8960d67faa87ab0764dd8fe2d41a9d0324c3823c91260137f3417400673f2f5012e2bbec512e1dfdd96a3c57802b2f245dab370cbb7227785d720c4e3411039044472a2772ba7d15623a05fc44f2edcdb7f7e6f45a54b53bf572df1130330eeecae3a45b7db38e50b820416c9c16e20a66ade975740524c3d072c4a9a5e3ba8d187fd6fa67d0baf0cd65dcede7c5f69a7bcac33cc8fbde1fe372b85550b6b3867f25dba8503ce2099dc468dd1cc4337aaf7b3a3367efea390a72d7c28428a85cdf7a89cebdda5c2044770cdcc129780e48750c5160b78a7153721090d79304b9c2407d7d14cde0e6ed4b7996bac2a67fcbbac749da4bbf19ed7299b20f427f481f12976cafb329bc9138566c3d92b694f4dd54800dde541f4c72d9fe0f1a642f3a62c2512e592ae0d1ef70e7f167da4663af05b679f4a3ebff723df481fa7fcdcb640d8da3c1c0ae9ef057f4d75a55d27b0a2c7772101b7eb3726227f3bcf72f8debeb96602e031df364d7b15ab33b31a6847ec3d30a51158e72824a93a0b01ec34a1c14f321e7852d24a68b91a5d4ed0228bb2ca8a5427938720258434de254c421e1a7a39ff5916418a7efed25dee569e3e6944f8e65a4fd6557f68cd745f599ae3a48027f35f3f12594db6f64980e55b2de470052eaaef066e89c4524ebc9cecedb6c8bf6e6ecc1edd57388c2010473128e43548c9d1a2872cf9bb8979cd3110e1579683f2ad264c1877158eb8d8142c4ccaeb07cb7bdda72e4604624138e5615eb1d21ca0f45d29069f2802d3c4604c9aaaeb5584c10804d97ee12e99d80503a88caa8aa8f488327d939f3ebacb0d8b18169afeb81f3c372d468796076c90fe2acf57cf340c4532b5190be9acac03cfe96f3c30ff6c03d72c57b00f8a8601c661a2d0b176417ea621e7a45c1f6f3448271b324dbb7bc812ededff64dffbee241f8bb78ae6700701b3a2d99d704f683a3c1672948684d05726f4abc094bfb6b42e864b64e6c44e8765d874169f8bf162a9c4f58c6cd3cfc7285864d7cdf62e0aad1620e24518d50ceb3bd4d8a3f21cba921e1443131743672125a6962f5f10de3f18a487ac675f9feca3d3bd27d8b67a31ff601609ffbef08e8339353d92755d3a1da1d80ecd2ad0f0c05ca3d69bf47cfb7cdb2ae4b07dc37874cd741d044dd84763d37d2e61e61cbe856ca7ab21cedbc548ca9b2c1b8b3577b1938610582c926328530e8c9b97fb112d909429ebb5fa6b3310394b3828121365bf19c3fcdc9a466a117bc82e583095461b0def807e6b948e48628f772537267a026d110901f3239720291c7bb065f57d306588a4bcab8b80e5b29fd0ffd3c4e49582f031c13ec77f7ffb2aa06460ecdebbd25c4cb15c9bd2becf34ab8854c59376c1435c2033dfc461644975c917ce1761238b43e2bc639c789ce532ac85631c305df2fd6654c96654698cab0f68e340cb341b76003f5c5486bf114eec2721685c8763e635d75233d1cab35079aa64051450be5b439dec54dc531e7380861e240c2e053c894c7209e6d54076985bd29c40dea9453f49a39f34b1f157df572a366c1bb8d79873cf0e5972b901dd54626cd3663c8b5eb87996c40c729808a3fe90c4558c54997ea63a223295ddc4f489bcb1eb82b5f433d508cbdd675110672aaad5656488530d6205c34f81d36d96ac2e9ea3111a866ec02b94db4f85a731fb4cd1cee0c27ba136451b84237d865eaec1363d187d5bbe2c0429debdbfa63665d4de4c3d2405f6a356a7fde8327747fa258fdf5a64905d5d88f6682684d23076a9c6e28a064d865263efed4f5c0f844ed558bf41af0401f60de2de8e4633f1c60f02fe0a2831b80f6267568df8cea20c82b2a61c10b41a61078ba6c34c43372309264cbf2b015ac54795be7050dab015dbbe982a3c9ffca6ec5451266aae64f2d06121ee6c83f3fa1f5096577acb6123240db35d9ebfff51959196810bee372d45e1cbd0b776d24fe40400a5018414e71df203bbf7c63b1b50aa7c11e1fad72d1ae717aa28524f45a00e0ad7ea7d33ac0dd68c157e09f2685cad529b378415a066743ede630141664fd72c41b16b1bcbee3d13cbca5c01950d946e7d007894eba789999741d349981d38eda73398a96bd7ae852bc2a0c7cf1ce21e294ad222328f15f5f6cd7f6915a0e279ad72bf75c54eaa4e8dd8ee9fd3bafd208d0ce20452a244d1430ffab96a1f1a3920afd8bcd8bd766c8dc042533fedc15dfa443807278d6c604ec7d08883d3728b6e1edd31bcefe45635d1178e0fc25919a6fc14e0e471fa8142192dcd0f3811ef1a8267d44587ace3e12ab054e0e7e1a417e502e723baed9afa5859dabf4ece95fbc304def34d078d7c1445bb8804b83ca3b9b2472003c6fb16f867e8e02d5ac0acf512b19c0b7466530477d87c61dfd75d27a70417d79580595480d7cd78badf88a4ea0610518f5dcf3a8cf9499453f6c7a37d2061049d92b23c381671e8cdc283fa9dead962b537c3b6ec0bee738dae87a25ba69a5a07023d7d9c503c9b475676d8135c600edbafe91cc4a7dc8fc25ea4c9bb611bce4c9eb7e459c825d98ed04682fdf05d4af5233ac51f7783c5d0bf990ef0d72ba630c021143beec2226f2f6f496c4108f78b5ffe7ae355ecba77a0821bd7572151e5693d9693677b461f5c6abe458927014189cd431f64e025e7d4d3cdb40568f234399b26b73e55d428c31458b238f9332e6c8365104b5e09d3b3cbb0c2872f73b419707751c476fd9d55389dfabc0a4043ee53a05b557667478b49001133d61b6196f8915277d1fcb0f5d704dd1190a60378c4a3236d202a0bd310d25934bd84f62093764199131430c3bcf04446e9929991ce6316fe61a7c95bdfa32fa722a89e751ba10a652d365d268f0d792efb23ba7a0b39437abff8cbe58ff62a57266c022afedf037fe2279e63e9c23c73a580035495991df87c0b47507936c337201c9a1950d81d0bd401fcf7b02ff0b0a97b49869e028b92e0d856f6ab4d56172db3ccb2017bdcd6860c673de6a59da6f3d1959086b45eed7ab2b97d860adad245bc6147d74e3bb1bef685cc649bce019897639ca62fe2000059b1fefd1d68772cc22d850b51d3d2c41b87b1f40e86a0a32ffd86884f7c7757e1872dacede7f72349ef0242f9e754cc5ca7970c26d6958a07c2d477ae55f0f960c3f3e883b227205483ba25b39cd0d0563cb4e917d84d98bbb89ccbad122aa367ef932fe1c86726d211404dea1459ad269332949345b8e2168c729e329228dc3ca25401c9dd3721528e5d13b9e5762e9a166b10a4013dcb6ebf446c7fde21c576c98f203ad0f72d1f8fdd92e5d4ff3b29ee60d9b78e30dc930286b723e9fff7af174e6bf055f3b3c4d291ec013cba3c01d90c9c8b70aeff14d028d280efaed836cadf8d9003072378ca0c8dc43bcaa67cd6a10966584874c640c85d397c49817b4806b85896672f33e067123f362dd0303cb82bbf7aafdfa6d2344c355857347ac048f20d23e726d8be6dfd350f090160c4174f80eea907d39f6d938467fd7bd7fd0a9f161677207d3cdbf0e55f840f62f31b7dc2f1b75add95bc5053ded261857afdc1bb040727558fbdb47b1fac7019ebef9b2c914b72ed4b296a792d2b8bdd2a86ded233c72a2f1b43f4dd8e3fcb9c25bb6d3f6325170cdca455baa70dcb011b046c4eabe64c3676b009a54bd0bc43cbc41da97168d827317a55808ab064e6d7d3a6a54df722ac20464f19d1a9d4c6a438c8f252c4d3be3457040ae2a9414b1bb5b020e556beba7203359b6ef1de49fc5ab6401aa9c24dafb379f28c4f224d115aedfeab75544a18c6184d4e952d090428f1e617ca17555777ef9d86e9fe32594ae4c49175a87c27d354ff2953bb33d814ccfaf851a2faa751325ef97406fa998863ff2986e576d765fe921c9eb11845fdaba72e746e9ab984045bbcb8c6ae4ffca7d8aa93d1631b2b7e99d755922296f641bcf4981e055309f06f6739d9fdc2086e5ec0b6d9b23c4bb86eaa730ab6f41829066a5b2c0db5bf70b289b2e68051f00cc41816282b05709511b59e4baa5cc394ee335a3e3c537366b825d96df2158fec7a0cb42581a18cdf0e7119f2891ed5cf501a95abc19fc135b24c5dc58091dc1705bb107370b8226dea4f5e87aba53b3d8ade518f7f1716685736121c0ece63ed7f13972b600651296c5824e970e1d1a11342f60a54039077ed4d3822b564cf29ab4f4728b087003676079dae6cba2a12af0fcc0e891e6caa2f7c68d3852989312330c69a2f83894fc9d4608eb3c9922533388bc457e6ba4815198234ad3840501c35272f77b13bd9b797ca7fec161898997a70f3de666800e3e1e8532fa5ad8d3c11172f515f329a456e9809ae615720585ed771384be2e164f912db4b6786d8e30f272afd71d0c453819bc572a5d96bf323d058737b2d5c753b793ccef1aa1f0829f4bc3b0b2e720eba0639e8e13e2a166e5d217f1cc4cefbabe0a6cc6bb8f08b44272bdf16853bd44c4be48d7fe6afc360e10a5ec5adcbfc6b7224ed206b3e6645772352d9defa73bc2ee1f642b4347f60d4fe9eebb6ce25c33ead904f63fe3fb2e4387fd7b315a9606386644823ea1a2bc7d66c13c85a17ec2745680715a294b0d72e9b55d52cfc62e3b016bcd23ff6bb31a5c8529a583f37855c945f3e599de013c5aa51cc4654d7f3b542c6f51967bb14cea9b68f161658fc853ee23ce04b64e10872309f1ecbb50b3e5b8ae5a373f1ca6e56b88df3094d923e24a632e5b77434567707e9fb83f5c811b02bd84e3e82aa84137774ed39563352e1af9005005bd72b4c6bd01bf46c5029c8951c936a64f14421062032e6d96365e31de601c61bb6b8adde41b564f596aa882ca1919f7e0776b4b7ea4659a7c9ac14eec710554f30ee36078096d3062180bdbc6d7bcbddb74beb6ed7a182c824e09ed711655f132728ea42ce4d834a7807c7f42d605653cce6fe30b4fcd345f5e7fd3db4e2ba7a61f2738e51ef877b2047abd84b59f5df93e358601be37892aad704e69d20545fb72f2503e459ce761f59014a5e772593e4c7410e80210ff3c25b637f4f7a8cde14df9ccb4d3afb8f929ad49bcd565f6bc84a4c98c2d82b7be8c365603080593f71b67006b30c12562b2088e64568b603a83b05ea6be0b3d27bada20b9de922c0672be727dfe79d22d52e3f34a879946f711ae93416eced3cdc23feca4761c05947268f8ae151e76ea156b35a52372ed3a313f3e1ed29c01d39318434f73871c336c7109b5d5b51facb8a80d8f844724a4e3dde4f917cca2dcb518e429aa7ca21a724da150c323eabf2bc290674b32b2d41d6284d2debe244ce104799933732ca272289c27c12378dd4cd8507bec93087869114586e7986e57368323d7020fa9963ecdbd1f98f2760bcb6591bee0bff294f1a8532463f202f51dcbb6ac3c82b34f41c5f5b23c0a90320d1a7ac7cf83fa29511407ccfe4e47c4625373ab1f607503720ef8f4e10a4d672b6556ad89f4afaac1eb2668b34a9167b9553a67bdec3eb6720711a540acd8d2a74f420105ce2766505398111bf8d64ee4446dd3fa87c63e085eca13b8eb521ac309316ad2e7654596abb4a8b9bc7e20c7d0e8257c2ea2dd4126dcbe0044fd00afb7d9a37f30a00031478416b5fa5eaf85f7f1069af1f9ef7232aefba3aebaf67a45fc95c2e465372054fccc178dd208b2c65264469433f672206e6a088a318bc47988ab73d35459d55fc92cf06f008a6f1324fde49d242902fa02f8303bd40bef2199eafc76b3497babedecad3604ba6769146df7e6418b729203dc63ef6d11ad441d2e61b93ca19c8392609de275231983ba3bb61aa2e372d2956c105556edc0bd2c318963e82cf03fd422f961e696ce3886b8aba2fcd0323dd2a21ecdd16cb4c5f87d2ed0a91c41817dbbe076cdd73a5741035bf919cf727155befedd5d094fcdb847f09b3351cadd2246286a28bb3e047259eea9223d72a4c18f23788a2d7015cddc81db404ae9cec26fc18a3ae585edeeaa55565efa4c1168a5188b9904047a99f1fa0eee068bdb7aeb8fbe61a0905bdea8cd1b8d927232e3253f4b14c3cde1b3b4ecb1f5b0f94ca5735f7e7f2716ca268dcea4c83072f385a4b0590e037afc0d59c5feb3a53bef2557dd0615f4c1d6d351617bae5d3a74caed56dde4f729a907183d2bb4d35fc2f30f93efdb1179b9463ccac9a56772c3b2a0bf76bccc692403c988084600b9f1ff8e388398c64a51325efa2576f5723d3840f4328e4a1396494dccc3a0232c3d2f9d70d2ab13c548fdfacfd79b3d007e278c5ede55490945cd374e9c86fae06b1ef76f1e52335e6fbcf54ff28418542c4eb1b6906c274fe9af98dba401e275296830d85cd6853d75e36ca6829c7e7212c2cd913ad4649412b3ae1a175a45fc04ffab48451bf3a6fa56dbc87aaf0b725594632ee8420d93e0fea90997616a06d2d5aa3e3dd1b226754ed0a004ef8d7251c21a19f80460b6208fda75fb29a5c32b19088c252fa5d165cfd415e3a3d47220a14273767f96b2a26f912051e69147ec472c019c29909f3b7e202f083d31728689237470b46787df92058774202ee0fcd15f6171ee307a3e5ef9e83e58842a32fe3854914abb61f16be4501ac9d71277868ea3c8ea040a5e413400b0746b2b08ba42a5d5e63931020771d2c153a20a356cf4a14dc999db1930c864e9fbf5727e6319ddd9e2af75ecdafbc69daea0ab3b18d37f78af96fa04c3673b850a31006a15cf22b5605e311d61d8354da56848a0f5d9691ec4b345f02e2d4be5dff4728fc093d1ffcd8c795e9382098cebf299b68ef7a56b0c3394378584d975ed8872d5e7cff58eb469896a6deb3eeca51a98e8990cbff04d4d45064a87b7ce7a09529f465f90ed600386d57c23976c92b08a8cd151f5da1771dba4d4826d9e26da727ddb25583c6064509b5245e2f0899b5d4117b74a3ba22cfb9d87fad70b129f0bcf3fe8360b6d32b5b427ba8bc0d9402892f51b8e999141200ae3076aab6964140ad3011a873f79b3b72bd2c38be2288b07cc9bf960c337c1cc7d5df10c60ad282f84e8bbb47a6093b71b9f6e1f864be622b074a783cba700d149745e2fc1f372f702ac57334edef210603eedb2049e07bc821829f9a7194c812a7c63269e346e3afb083dbe365b395fa0096aa97729fff2b0ed1be875bdb634ef596098527f728d3707a7821e1b75d70b872ddf1a1f5164173789fe4585e60c53474a10d3c466216755f0099b862c8706448590654434fae88e8bad0cb77c6a01fe6da269e611e06bc0d880c174cf89e5a6391471f51ae2029f931f01272652a761513f58a772d36a165e6d23b62a286cb574a91e4ab8b95fe791dee6ae238249d06a5398cb72fe05dd11f64dea009a78578a2ec1e175c4d18cc8434b81a6bcbc732a2a68ff729ba0cd29f7dae9dda13bcd7b61d90fc967c7d12a37489d86b1f0c2ce10f92972ccd0df103df98a0619dec974359b91a34404842ec331f590f8b00cbc214291720db42856e8e4dcc2cb5da8eba8d12b114e27c2b7b631e735960083bc2fcb1e7239ac2b146d40604d5404b506eba7f05287cc2f8ccf50ea181850806894df745046f9fbc40b00e6ff033134404a0733c9433ffaf0328b6936e567f7aff49df4726f80a5a29535b41a9c0227102378b82650f792aff01bde3963b5be042e661b72be662dd93e72bdae1e20bb7b6369da34150db6382599c5abba40a3575c5789726daa4947c6bca274d71b5ed64183d367a2514eb42d92f05679faeffe7499d35b061c62f9398b3525a304545eb2e91d52ac94738ed8a4902f7a7417b3569212726c44827f8b44633958db8db2f754862f00ddf9f7d4b3212eee32afb18791c0721ce56d259457bcc7e2650e16084b5433060d3813dcaa79484d868681e397802006cd3247297094a0ed539a27d2dfbc4fc58a5d168347aa9926f18432ca9db714b8a8ec59dd70198a252d9c846b78cb41cd424df391891c3a8786ec71a50ff1703991448b49d2ebd0cc12a840ca710404a2ebdc21c1653769787e9f3812373014f77af321fecf30d340071f929e57de7a40b4013a60f7192704a3bf38d411aa315e764e7c82f692b616fc1b09f2a17be005bdb0b46ccaca67f06142950949374525e7f57c3fd22bb26a6126f67ab0adcc65351b0f69bb945f45bcfcd35d99d64a75832007fe44d9257cd63a1679b023a4caf04ec364043179dc877b1b6181847289c72a216c7c39ed5cbff3d62b9a13a66f8217731f7f2e2c95a29fcef8beb4724c51910c695da5f76db68f61c28d2990cfbe6a809f913fadc61d50beb61fe040d54109bbbb8f6803154d0ab7aae6fd6f3c25dea5574bc6cd494ea7629cd6e13221d71ce6de4d320c492b99cc7f140592478fb97ceb21f7d0ea07b0c2295bb47200b446c827a98224a30beb8beec23415e50e1a9466f0719b6f9822b1752d5672ec45d26ae09ce0bd2661d1c6d28cdb7df543554989a660f353b451b39f85044c284a9f32ab478b84a1b7167aedf6c38e483b7efb0278501dd268e7ec88455b725fdfceab0488fd79e704c7f1dc0390600777e9485ac14c37ccf63974f7c475643476a4c924b723b2d0df32e394dea398edcae2d2005075aaa6b49bb0a99ef2724a4fcca8999a20b3c4ac09a22737d8151383424ab5973001a4e06e56c6423928db6d332a84910e9abc470fed59ff42d6a0280ebd96ddf2e559e1b22566776b370236afef01c3205f09df429ac59d813fc9adef6850f1003e5e709847e07c2e36fdef41de817f701884c3b34d839417ce8331bd2784fefaa1c1236c0ef67e9645d37da0016d9ab38fb944cbe5964392b51b411ab1e2fb4349f4874e749f0d5c0a9740b85e0a40cc3dd39192bf8dea0502f620661d905fcdf12903859ce3c03306a0645fc50da5ca32b1542f963d70d7f04585d1cc3d0f5e56657250ec4da8d1034a0090299a10928704a17c1de5d3bdebd7397cc9ffaac2ed5f324f4e05655172727477cb4ef6e4f1685d8f511616b4b270f8ff229d3e43e2b6d14fbe536078723e74162eaf365d095daafa7765a29780093bf24faf6063e04f73acc1a43c1204ea8a067d195360b67968cdef43c348b3ae9361e8ab2e7772ee281be52bda375d840bb1aeb199a492e4a4818ce5c669735798a18156f1a0450b14e9f6048e68134e940a6e6c604c798931ac2f67fb2c7f153dc81c7fd81dec6b75a3def19f0872b6589b307b77870cbd0140ccb01e85d556e5d9db9bc5546a05e5e13cbc595b0841c9e6fb2807d988c0620dd3020a90478fdad3f356f6700999b993e207153872db1a7f671f3af1714fa566893f41c98cc5fbc25f1a7ac760abea60ed93bd9b7248f4d2355964fb097e065b82144655d32a5cfa5b405894fb1ec12be96163d972509845b369ca73ed639cca5ba6ea8bb0ee477194af8bd28279a3e6583817b44e09c50899a55800488d3fbd4c3e4a43f07cb2aed2afded9ab14be9297eec92c72889550e8370c1e6a0f5fa304bf33f810a46ba66f825dbfa5870956cb3d207e72dd5ef3be3e6c707ca3da425d4f8713b8932c328b01864d1a9f4ee8b2bc47594d8a10490974c5f50ce428e56df46832aeea6f8b8b9106c3a00cc3665c67de0e5495f88d819e7d993a29e49f24b830f187d11535005386cb9a2fb58d300d30ea3fab83b87c083ec15fb416985d00a2a85cbdf4f60772bf2495b2c7060dc54a2972dc100d56a84a3eefcf6ced0d75e0b691e2d09be067190aa003b470aa7722ec5c77dd640f57a61e5d400d33227047484a9a90fa4269559fad5f30f471dc0f0a4de8927c3346ef92c90fd5509382142bf5c468a628475776f6055a51f14094dd5eeef256e3bcc06aabe264a8030f5237fd74555ee0f59085217c9912b86dab52723fa2c573883f1fe9ed4ff693627137b6f937f518178985ac6fdb7f66388bce72ebe03e527069b6f86c2f3e497cb75ee526b72c56a00eae57a4e4107f17bb3472e741732b19608c1a54f75a1b85e8084d68ce684e28a1682e66d3562b39f27672c45592a0d034abda0a8cb17892175ae5c8ee4f494e0124b771f5d470312fae7242e569132db0e3cc77730af6653e0cf0cdc1facef462dbbb170c1b8de693ea21ecf9f2ef71ec616d37b4d39faf3bb5b32747ec2be188dc9b3a39e69c0d68227270daeda6e6d6f54d8d48f89e5e4f4dca8d3537127f1a705df3fcdbf453a7e2724c4ea60cc2ee1aa97f7829f61b73fd883d2dc3cadb6e416fb98696f337080c2c82c23614427fa18b9734b81f44abae525c22b7dc10ea5fcd6b22e9dbe36e0f246242898878d7f852aefcd00e14c2c69e9e64396ab175eac19afd3171f3384e727c8ab599aeab55a1784341ed92b9329de840c1b6329cd031a44b9e372421d3721ae8563c293b268f17f86a5f23ebf121223efdf2147f64111454d6a94be5097258194a97cd5dfae87e7555d013821d3e3128aa73fb0c97a8605fea278a6f3a72062a5acfc0221a900cfab30f639790e49bd285f9e03ea754d7a2ded51a826f2b08520a4b2a9dbfefbcd76798af5a7029b0bcbf0877771fc73901528380f62d72f15c7a9bfc89d988818a794dbfbef7814a103ed77ef639d06361acf158244b7281daea2d8d3a1d888b36de70ecc4545df881d8d8481a9af93b6b5fa8b9da1548b9bb2f7edf0c61a77026fe6b002d41ea95c10931e7888527b7ad8c3fcae7b93fa3ca8bb764de191e80f6df805a0024d60f1f0a4a80c018880260d217fc844f7293d6c59ec5eeb712d991f9ae6a9405ce1e815c509d42c5f75bd6771b97f76a6e448ede96475b058bc2f7302deaa3689ef33e477406e5b79c003f2555ab56e3729ca1928deeb68d3ee08c61cd9852796fae1fe0654b3205ff405e8347484808721976f217dc4a67fc193e7ee7d414c8a09016a707ce79ab17c7e4ff637fbf4614458ccec4d4dfc94717e140b903facaec246a348fe1da41b68c4d824cec15e672f815040bf78bebd9294771ee5032f039bfdecd24d096ad5b4800c52ea34c41480eecb78364ceba437ee07f7826654e4cecc7d7896fbc251bee950c7b700239047da4e0c5ecbf9b894c1664ea75ff51ff97f70b382b28966f000229120db94661fcb143f11bd5818c13838b85bbfd652fb0c9aacffc6092b482e3272b9a1fca72f7cba807468551c29bc3349762199fceea2a9da53684ad69e7ce4e87db535a6a26483da5213279b73eb0a99c8ba57b8b03394fc984708252f8c3a188509bc4541b6e93864175a10234bff4e7c46f7522028952b6a871a8f424fd1b6bcd63f2729f925ee06c9e87a00157c51dce70e6eb448cd726978084dff1e3b7c144ad5d5834a61859f267458fbfa8479824c1db8b831cd61c4f922e789a9d8ecc3079bd726c34e5383c1b16c6d224346b2b575985723d24c31ab1d3be2eb0b516421b2a41f7067c250cafe72fe69a05e3e5c53231e4e8ae19ee29a4a8b6dbbf3c20a051233681db386e9bdf06c46e45dbb4bc724b96f887584aac87e62eb24530a5fe291f715960073971d56b6f94771902a7c15427e5dd86f038a815b02eb8d2fc604e1ad2e92ebea6ff870945f28d09f8fad68525e5abb5c21c9ad74c29aed9262236721525aadcc0a266ff6d198de1ee2d64754381e2d2c0646623957669726eb25872a40183b00818daa4e974ff30ba6aef2632e37580ab046da2ed526eca9b55621ecb30d4b172c14f2f7eed88cf1c34265412b247393ce0804da493bb5b793d0a7262605424c456f502cd7561e536d2eea0c4f68088e206713272d85162c316742fcfd75e0818d5404146eb83b3c4d607b5becefa671332c32091b7fb3c5d32e272d1606d283178d3102ab65f66a39e35f1e2e1617f3960d839fc4fc9042ae40572a022b90c400235f3ef28a7677ecc0019c6199f28bff3f7aa2279e1ce24111072a8e2c8a1d1f45730a711ca0b470ff2de8eb3d0f053dd2305b46eb1240615ff724945628a02c774db90a40120f74788e1a26a4c68f9674a8925d1b8975a436e2539c262d6b22a528609b38e1e0e7ef707c9042bdf056b3fbc08022fe560ea6372c37b59159e4b7ad46291063a290d20f79f40e0559cd54686601cc5c26c2846727d93b814c6e809657a771f3af9be9288ee5eab0b5c4a0ef99b1dfe9a523be01a7f1cfcb1db2d4fc3c292d1994fbbeecd8488a150baea632e0b8d651818808c4d04c29561615f87844db71d2f8c03b90c5d2c092ce920c0d4bb73e35d63fdcf72ffbf510d01da09d1f0d7e867e605f7876828c6e451d6cbf3f50e3ad1d95bd933906d40842d5aa5c1131b9a6df6d905f74d07870d574cae26043d656bae95e9729ac66c411b301a0a1f4490ba824700e1e4c5428e731f1edb4063e792a3e96f40a5cac6910c20f4327f48369de3f7f7f942d9839c80bd2e15afb03b6c4a029172a60dbb628b48a4c7a43d1112127d8fe42aa7959a06c8feae2209d0c41fd676728c0f37090992d76ac3fe2c61a5320e1de4efecb3610f527a52e20e56655448575338bbe6faef72a31fc2e50387cd85112eeed7b830ca3ceba0cc2b5f6bd7532419609b77779de3b0b709e799ea1f21dcc366b465afc3caa67b34a756d076787296d622f0ddf6231ce811fa72493d23dab5fc9b45aff53e924ff786f5bb370a1266298a3add22579db977b2f1fd4236e9c72f98da50f39f8a985ba41cb4b47e721b4c60a337a59a76ce81e427aa04378827fdfa6431b90d495af024e48f5abe72d3ccb628d0afa7e9b3911ae1b212ccf67ee488c6b5ca0adb591ce9d01a3e3f7221c728c27e196d44816d066553abb87f948aa802210832e3489653dddbcf18720f4cd56747d297812d708f669bb241bd858d51372e2a1c152b0d4d78cd3cb844ff4d356fd9217622d41495b1cb92092c5d54f981fbc50844c5f3f9c73235c1374bdb2fe18cea42b68758f1f16d37a2db734ef54550de3562c72c3d6a55712c72ff24ae43d99684381e7f11444530507428044e32a0b9a7292ba0389e1052da1bc307ee3e9fc48749b4d40e42064d9917b873689bdabeb2d15a256662a0d33f72212702d2226f6f9ac0808dfb8a679f64654f3f1e6bd0af06ad0ac8b18c46d1720627fe3e7d3147e5a88f8848e44c8185f9efdc4a320d3c82c49a0b0fca8744722b7609f1ad20d10cf39cca404f8fe0a15c4db04f2c2979dfbe6c1e032aecc372968365e9e5f9fa72ef1d019f7bbb41d1890e82279c8536be9432bf9762b07472ae500ebc4aa0b9a4ea0a800690717b0139e0fff1bec972d6759162b0d653727274c75fb85fc9a48e34921c5837b5eaee6dc97cde3929b732f82f18567ca8e90bfb8529102ea632771b9a3062e7c62f9d25530a672b5b07655b94a74e4a1f7427133cec0d25c8888314d4b49ea8b8cb6c3a9c0724312f890b9b66abd28435fe72f00fe544c4bb9b54d7ff959debd829690b785919a377b79e9231cce3cb5c8c729be6d455d32b2ec502dac5467e8b97bb14721a686e717bfa067dd709896ee772342e85e770307fd509b81a674adb8ae5e8df44a1d23c835cf19ab46c0c1b7d725ce6ce9c60c61ace7dd2127f34277d6568e30d4329ef3e73dd6faba2fe765544f449eed1d5896a9b8914cefc40fda49a1ad99e883bd3cfbad5df59f3fcf858728c8982f49183e86238267620232a70dd00143101f93e6abb60ae56e43f0b19552461e97a350c99fea8750aef2fe25ec833fc5406cc40ceba959810e48c7a88108f4d12f5dc4359e5b4a805831e30a6f619f03180693af444c3eac7b71c1dba72f23c3c23f3f827f5808b347cabbc03a594f15c36a17282f2dccb9a163439695296ce2bcea5de5a62bdd344cdb418000687b8341e1759c378fb2fa47496e84d359cdd82b373c627c6dca1a031bcf52673afbf3187984f1a9d9a16b3f8d4837d7206bfb01c106e3bd7f60c975c38cb5d31edccb80587fe6980b6be9eb6bf46e54d1c75cd8e767975abebb92a81dab41463de672528b02830b74293a9fdc3eec94bea0d551d5d483c3567307f943821fa7ba2589aad8799dd230f1ffb87de120f326d3d9539a2701da86fc03933e80c696a5b7af9325892172b560344cfcea0f272b99f0db038174d95edef1bbaf47bb8065dff56f57dfe65bbb239f81097fdc9725dae8813cc0ee130759429ff82134f7b2494dc3c1a5e406659053a1580c81872d4b39d1867abbeb8f42165e5e7925615366812f368b60aeed560761728add872494c78931a53d325a82d15660b919834d4737fc7bdde0805561c68bd23ff9a415ae696ab186cfa9afe355da4b2750ddcdc2fccc6228f49c19f8b7402a371f60529d5de321b70034cb783cd27e540795fa9a178f6fd8ca5de08f167e2f6f7e172df06e607db12894ce7657a0fadc92025b2e2c352a7883f2f2551738fdc7b6772499ed458c65da929ed796f9ceba260bb5b9cbf91754054112f0506d4fd585372210a796317d37e4774c8ce4b204b878812196e81790e1719a69c05aec8a953728f8a6fa771bdcdee1dbb878962da5904821bcf4a7e903f5fc8c705c22c6bcb7235ec6466f71bdcf57130969a92aa392207817c9245a77156a586cc0f6bea292a308ca057d946708cc984139e0438996892759edcbe38c3755e3f8770bc3a4972cdf0d9af2f8e48177810aa03c8b25c254610ba26fe4f20c684c348d933c7e61b4534cbef313947d81ac009d70df293a270c603279e636281a5951f27be7c091145ab792248f529474d642fa8335bae1aa4a8b4fff19932241270b5f86ddff572fdbfec44acd69af64469bd78dba0c38914db0ad6824f2304e7c9dd3797e5d372cfe829191bdc0600ce0a25ff8ab850fb7e0d33cd30533a050c59f7ec540422724fb3db4fea9e5e5d54cfd5035b0d1d4b2cf4878d3e3a9b51f7439b41a18b1a59a5432cea48f310880b827848beb4792dff12dd846e93fc3622b7074846b29c72c94b296864ad9de78e219def7cd77a982f82a2081277e562a95f479ad0f9b672d494baa55b21a039ce6d8b3ebb16e46c822165141ad8685414544ddc8ea45a4920514c2c4655c8cba9581e06e719799fa95ce156200036358fa10eb6750ca33582ce311e2c2efddd2d3def4361e673d48f29e2191a1f1ed8f12487b71a5e784a0f4eff1bb1a7ce5442fc0ef893eba36a52526acca4a0927b9d4ad976b2acf22cf280ca38da7c6e9a4b6c69de9dfe01e9258f9243ab5c2d6824abcee0ee8e540d0fe132f98144dbdc4058629a9136521cd81a67970f4eb2a841a5531313546372032c2dad813367604ae4bc4033fbc1b2d90b9b253eb86ec51ff75b7483343e72baea396c01a7f113f0d1fcdd8f1b81a618df53c862267d95b904c9ed710e11485eda0a796ec0c58c803410027534f051b0f928b1419f0c8ec60bfe3ad809f172a02278cf82870450631c2e241d7d11528ed72e690aac588a567cdb88a0bac071193b5ec69e1cd15e97a594cd2a812a97da5b43a6de6d06b7b6a9d672f2387972d873a5291186c3cddf477d7ff24ee5ca1c058bb1e0c8f9b2af0a54354ad6d372843c9bd350ffbb857bedf570635425ba53e2e7f764eed633d48c152228aca27208068647ca971e044fcce38e8ae6f1dd74ebf68aee5cfea253fce5ddda85757266d281c367e33f3a0a40b6b36cf54317ec0646eed659902893a62321ba01e47205747f51cf45813d0ead51d0e45383ec6c60aa457481bce5736684733b753434c458483688426816774e40f022b28116e870d6f7b19c9158cb4f64dcc002c31eeac37631154100696b4313460d062a68f2b2033b422e1481fdee2ad138854e23dbaf129cac5987b03af252a9a5c9537170e7ab7df6104808f3ae23db754a2d151fd96e32cea96b7304b07aaa4e11253e7644d71d4596275687e103e0f143cf724842e2d758410d30412ce660ad5313b7d8aac3bcdda1a4edf7c82948baf23072fd9d946edfe61cbab35e93a9d6275da9eb7b296f0d2f1ec137c28b8c699c44722b425ba257fa456020eb4e65eb79b6ad74cced644b6c5d2997789c9c41dd366e70fb18b15d40d9ac7cf17323260f462545fbbcc67989967e644dce3d7b4ab37245947d6efa159af94f1356197c0b9a373c90fcda3457844758377fc984d4db72899c8bfdb6c5838fe99a82400d78f770ded4ccd873f3e89eeb8cfb3c225f557290c26f4fea873f5c481f8f5faecdd379aa1a1e5aab868ceda1ed41e0e8a085726edd31c252d365bdd347458e5a88f23cf9a3ef81cd63ddcd5d0a8c2cf8ea2c6063fb74164acfb27de3e177861a1e947c78503cbc98ff5f77bc93b869bee6917217d1b2cb2cf4e8a744619487869f40f0989a07cfaebff3cc97144cab76b27f3bd5110154b083442fb532a3c3c80034f22e3f387784e165d40ed65287da66d65456f11540b19f450fc6c53606588ac6a5e57a85fd6de6463c138d682acc02ca72bb33feaef2e186cd905a1b67106e118d6eb6abbe9839740556860e7d2afb257264073f75d4fe779d4bb0b3c01a9471663437ff97fdddc439bd5296794b9a296aaf2f6e42f601d3f01e7ca3becfc335ea92cdfac5952463dee2186eefba6bea722a4e8e9a9510756b11891ab086d50d56f06bc6018af61d677f1afc7af91898722f64b02a346bd178f0713d04c651fc2908c7c6ac743e8fe8d7907105aaf41e7234f48c38d1a550de8ee45abbc896676f2034b7dd63068f1ffd455ef27c16e672cbde120fd906d05dcddf4c4604ad4c7a975b9eefc65eddbe8a1ae92f85591f37a3ac36964e03445b2a935a4ee1ed70d7f50403c1451fce06b9b7118b25836b72e0a7cbe37eaa937b70862e9094c9c9c2b371bf41f1b7f4c1bfd8d6614626b97229cdfc80ec0489d76bc8ee644c80163d39d5ab41ad107d92493f41ae72c2c41a63c027529f3615c38afe63c94c76f100a1e5fbceb7e44f35fd5ce4732850f902aab16bb1dd7339463a3a4edb2b574683d200d4eccebd0c5dba112c00c0bf5c0add5727a9c76792bfb0dae1a9c082d4b5f0804124b09f4326a52c8c9af928b772b8a764eb908d3adaa9085d98006119b9daec95de69f729612170cf4a1bb36972ca2434215a146afda1f6d6d72f7394071203cade827f5fb6c1dae9ec6494c9487b597b87f1258ac8ce6f50f67f4c640b06fafc65c64e8a66766391657d7fde72c5d752682e752e5c7fe9cbb5a0e963bbbdb4e048df2de6309169c71be4a9f5567444b6d6af0fd036e51dbe517581f831ad46b9ef164efc5b9d17c23679d1d208b5143a69189ece47adedbd5d48ea1ba09897914584d4e7aceac6cbe130bb7b06b700eb4fd30850ae7350df023f4f7045fef1287cd2b646c16642b63244d75a72cd9c0235f74b28d5f562d41758345a4f4e59056f6a1dd6d5d75fd8bdf2e99e1fdaed470b38abfcdd6ffeb6ebee5d67da8ab2cf3b164fffb79fc739a0915df872b16456be18c9b5d2f9c6e0d77f0c25cc4c9dbdfaa258b113b13c3a88ebb8660a8909cb996106a4ecf8192515acc34f16c0c76cd26eac3ce2e79b2cd557c261642b8ba963a5dbc0d1a32a9fc613feeba12e142e4a66a97b5836d521bb93033f3d45692f309da7a6dc61b91188afb5ea7e4ccc292df73ccfb387937e39e4112572ca28f0fcd8c03e52462aec52ddae70bf17b12fc7c23fbacabe1a2c355e257410749174fe8370419d2b1e69b04790b941a6578d9047ffeafcb75a463fe4053a48ab35149100f698b734de6ca64748d63b18923a619073dd475671980860c12f724c103def94514d566f0908dde0834d73f08f578fd33989696dd0f1dcc664f3722ce45abf305d9b5e58f3044cfcded657757fb39e73d62a9650ab75e494814372ae8696fa6a703a1f0866343d139c3790c9e144e282c6686d1a3be49e4a8e475918d9656817bc56362aba9350395c0e53156215680325c150cbd8453a527eb8727b79d56a122758e589adfec5aabc17a7cbeaa9024396572ceb0b71181cde57722d8327fbdd3982b8070ec61865542187ee4f5138f018de720a24948a51b2a87200a264a09525b365f12890a3be15e5ecac52715c2bac1c756bcef73478846272fef84a7b1f070541d1794e3b9b2ba1a698a3b611960bf206dfb1182e02159e7286886a2f292e33cb6aed01caf35259c6db0846512a50dac612a0c81782d30272aa8236545c8b58910af2f44c5142776fd0a2c112dd179ff546517e065df31e56c4a54b3d71f8ae3f628b18388a6d0a386eb8de7e2877dd8ed12fd22498a0f172f5240737c4d7f71354020d9fb570d8f18c8f355aca52b55b081d7173c8eccc457b271e070c93269e6215770a31c119f7eeb79de3bf2b83d7858d3ed9357ee9725eb4c6df7897407335f12b4779397129902141def0b74437debc541bf6a5ff72dbd03342bd8ff02a975a70b0a1fb2783174792fb2504e431809c48d59622767212cf374dffe05375e08f970f2c4e3e15e886c5319ae26e9b7cb53bc40c0e79725877a7174108e0f147549f4a85fbe625d7401c75a423af46a0ecf105dca7b77210520aa604d1f9f6fe7fbce36352bc4e4c55cef6f7e12f36245d268f2f284e4b204984c83573dc33bec699f17ff2724ab719450047289c90a48e5862318c5832c7a78ad0cf37532593522a938dd17d9725529bf9fe4eea8b94d45102d3f9b372baf82ad199ff61f5ed50e78c3b24e5a5a4976e4717e7214ddeb41af16d760c72f53655acddb908e2c002e40f47630e675046c5e6992a072f30ee8b20c608610a79807ddd7695f7a265c708e03ac5d72419f4808165d7021feca4f01c652c627209faddabdde78271d19de2539c7800abcf1fb29662af74d7efb96fe22fe0bf72bbb047275d2b36086467858476b3291096a45a67b76339933b99339fbf73e872424e54bdb1abb3d502a95062c122417803d553605ff2fcd4306c1c0af56b5718593d5e7455e1cb67ad970b02628a98b879266b843db1f9fffe5fc1c4b7586172a29222f010f3fdffb01c378f9b55d5860ba8d1b7c4a939204c9805c7b3eb09373832dcc62c619c381d15f6b97423784f8a5a9e0513d01194d6b0fd56c4a6112ff09541fec113552036535c32f109d0507c386c29a16df228bd5cdc4379411a0c22814ca3ffa129317494f9aa16abdd0108b600f1e73fb90623fc9589af703f16fa8431d608c2649605a77167fb58138d23577685f83ef6ffd91f9bb405fbce72c74d420775378baec8be01da973eaa112f3f940162ee27a186c40aabafe80b7264cc8d250d59abaa6ecb63cef151699c17b87e327dbadea7c7e439d5b6c79f1ea7e1c4eb05276c603b8bcb4ff2413848744687a5d577c0ba3bae6033d5433d721361136f55ec6761ae6874c3c0aed3106820da1f7abf4edf7248b2ae68b127720822d59dfe2d849dcedc6488e917b219fce7fd8c5ac2c2233803746851d07072f74bb95c5839fa08a5161930de46b4412e8adb3e66392ded46d2454e03e0e802441efd0d47b515b4610951eea6403714cd5960f0ef030432a889cf6a9b62126a52ebc5a5290ea992486dab543c87a1805d0f5ae914ef4966b39e451a2abaec7209081286ef0cebddab4d2bcc7ebd33262f211cc4182d4202caefc6be9ebf213886f2e11ef389189a7db3749af02cfd269bfed230af47a882840949cce385701df6aade411fc941dcb35719129e347a26c754263ab09a8cbacd3a060901af4772e02237b486ad3823edcdfa59eda7868b2e1ea05de062794b1b5fbaee53d3a1720496ae54977f2294aff8b6a5536700a30157180b7548278e245b9941f98aa84612ac7a99a5f0c2bacecc209aae5dddbe8a87fe5543e0c7e3d3f84ada9ec2db602cece366308d8b05a8e3ca07a2f3b1a4a8c0af5f8f84c2dc9ff00b6991eb7d72a237178153b6ee641e86d2a9b40193632bd356b72c0367d0591c90c7e04d595ea4ddc7bde21a14bdc885894f35542f25340f03c31c8f25f54eacdeb1c8940c61d1c13f363b2fc0092546a8cc722d6c4744118cd9d6c691464c315488d74648726fdbf2278368a81bcc91aa90dfec3be4cdb8cc8ce3fef3864b060bc2ba7a64725859fcc38efd0017f27a2956cac60ef5fb531e42a7c39d63c97bb77316bce32bf56bb41bc6bc249d28e45b865d31666e0cdd4b6c6465f015df793d955dcbe17255319e0d368b69901359b765b144478a231dd3083e5688f63ee2ae9f675f800da9d899d8306391dfc25e1a49fd548b00af59540925879e99345239ce0b2d0001690d07a2b8e23daa99878f9432738e10e3fffa45e9aa29054a5c663baffef9194f7cc80f721b6d6b04a492d4357ba66e388da961a17227218b6ce0eff825cb72181539c91be847ef1e20d59dc05f9181d0b3191e1dd4ab74232319a4177add729424790b39411b3a21a7fe68602997afc10a61e47cab5a7154dae40865685b63692e1bb4a530e432566d1fce8b20f9e0447c53e18401a54268a6e5f765fd7372d6b2aebdc537608834aea51bf05db6abdfe6f8a8b836cdbfc9228a89e67f9772740eea92fd203eae0eb02834f7bd72f84a731d9262b60b269b0d5d67e8f3f22ce3c17a47b897c8fbcbe10917915bdde716cf19617f7ddd67fafafdb596ba5a6d9808986a00d38f90ea5a6168b9a4fb34724bc674f7b3d07d6435dd7a373699462c3b389c3e67bc877948a0650168aae234707e93a114133d4998da779511ee32847dfaf737049f6db320240f80b48f1dd8690af0dbb4c48e38728ab0b487bf725b6157405a53086abf478078eade329dbc16bd203a6e59b18c01aeb6a0f4242cf7d1566a3d23a79e19fd04fa923c8e1932d88f6b7135926e2d45dd963d44a710f26b8bb60cb2e95c4d16952f2ad2f16cf746b6bdbe1a9c3da21e8754a9725572b3ee3d9d5512b69d9219e19b367001b2132ffc91a38b06c15581418d0a4a1448e06fbb7fdab12dee58b930118f6008027b76eed60bb6105c06b81a3486634572bd7c528c3439a916db228bef2c9c1a5825966cfaf1d78bed9f84bd6adb71b472526019f1d8618f3710e70b533db3c63b245458d57da246c1a5ae6481ef68d87239caf3eb9af5bc858baaf61cb4ad3f0f12f8b40450f8ec6219e14f7a31d86b72332e41ac1f36b74df5c1e6f47de199efda5ca8a7e80e7860505c9c430f383f1eb94c830ea1d00022896ca278bbbf318e899d1afb31150e02336960ca753d6472f6ca9e39d81f1b804dcf91fdfb7d68331b8e5feb9e0329e03668ca76889ab158cdb4853ef1cf46690e5457798db08f549a3559f62342f4d1a3622a6eb7bc2d72ac0115d86d0349944ea5161ee2b513815f786efa3d617c88624ab5e5b4d0f44bfd113505c3914ca0b0b05c4232bbd1bfa0712651c7ccba28616357503b1d154ff8cdf6e747246031023b298f85136d7d8d46c80895c3d11374fdf291628108726818acf6cfb8b7884ee41c41dc2a34eb9e20daed91cdd8c4218b61a9192e1b72b25a8c3c64a8fffc24a3e40a694eeae8f435b1b14cc84b5f1bc9c7da6588e67274a3e4f6b54659ba4dd872c9ea733efbed87a354b89055de8f3174f81859fc7245498a61fa2f226dea749c661866f309741e46e53ea8029a2ba4736922c04072ecbf85b56c07ad0a0086fe4dd441ed389873a43b4d642d46302d5367bd91451c6b84a2f3a1489dcdee8aeeed4a5126d71739d87df097ed86346a32b36d9cfd72e73c860b91b3199e2a2eb2169f47ec165475f4e03af5743da1ad01c0f128ad72699517be55168746ce6f0facb3b71a13ecc216086acff8ff103791498c37ac36b2ade79ac85b387a89620f78697095e3ff97e19cee616022ad536097965b94729a5f73aabcbc829695a77bc8a4bb2ea1f9e886bba3065beef7ec5dad5854f472f3558561b0601250dcd582abe41fed07f4bf3da4c58fd8e181a9c3553f408a1af8ee83a53261ced3336e7520b416fa30fbc2478ec4282fa51ca770fcd7a61d0aceec857044b3fc870b3381d474614dcdf194fef064fcfd14ed70d753a4ad555c280c2b5728e79df829a117d819e0609bd76cabc945a745a64965f18f8f87ca726384f5a78d88c1c430cfdb88a9044e63e0594e013942f1f851ae03fdf0918572d35ccccdd93b8d07c74541de8781b4f4fdcb2cd085d9404af6b9fb58d13aea72cde3d40044fe27177bf0fb0ee19737bbb3819c425577841afd4d2dbf704d9772866dcf8e77f9b99ef4574b576ab6084580cddb3908e2aa000f3114f8d5b7d31b0c26337db47859762579b454a4a5f4da40743135130f06a046b9bb6d190e6d722c3614ca1a980762eaa6410f79a0ec78d4ca44f92843b713f5aab850461a273201b110967df5cfd80b42152105ba48c9a6e321656306ad0943ae2d1875dab20275b04a75e0174619451a29e9f0d3f898933e656f73856017f6ddb82ff003be72d02df4772270cbdee700ef7e65c3a8fb9f947f8e51e442255b9ee0849a8f3b72b3689fdedfdcbe92f1f69a014edcb46d48155b1a59c0997a5d957ac6ded11007aba848926c75c6636eb81e3f7e68506f385ec8c1a9988b772219032706634f72eb75057f20ae0aa87011bd7651bfd6c8ecd0b4a0f8d013dcc37a102763a47e5bbb40c9f225fcdb938cc57251bdd984f635303edeedcd5b637995e9aaf25fa7725fb45cd41ad803553e8a88e15e95ce5269c5b3e63bac4d9ce144906afb568e2cc759f8d04a79493c7e74b2f43dee935366999c0c789c14e6a357bb3d4981a3722d9280c860f9db6649f9c143190fa6f8f22455e69c6906447d5d21fe956993727bf4ec458dca456be897374855190ea6f12d1c9dded70b080e6c199930165a72a6416adcb46090440047e3e6d5b2e9592f92e90a88e234dc1b68e5c5f2b6ab722178d4a545612a6b625b7a25c996bbccf1c749092036e112cc9a30bf8409bf65b5525588811b19e9c22ce95b4e870399cb9db788281a5bb704e96ae4b06d65724a6dde458fb1c085496cb7997298d49b6e6bfad6f64adccfc8238cf1f9c9a414b51e985f651e05c4220126852a521a15d476978e7048f484e06186e934d927722c7438f54797caf3eb119124e5cac0045faf42df8983972a7dd218f2dab77e72f57fe961a78703c32cbbee8b4a604172bbee00c943f2eda3180398da664957729fcec6ea7db73e4a2f6712890021d9b15ee7fc005905c035755c9f3490c5550dfccf24c6cf344001cb47c1be29efcddf45f56a53ee16be67342d5ea96ea5027254aaab1672539d32d4e7c2da01624fd0ddcbe970f9e7394e83d15a13dee1f572d21b6aa8c057f70ee72deff0e7a64adf0bd3a8d89b3e8264d4af9f75385be658c2d5d5ee8b829f0b5af28df4cc5fd5c4fbe290d024daad8ddeba9160b8314c7208dd3c8c782e899c67ee50dcf6455aabd383e52d0cb4a64d055f6dd85a545f4bd3f20eac449c8f78ee8aa5ccfed549943339a1853546e9dcd43cb56eca296c7210bce9814ac8816ee2d199885bb4487bdc5e118ec9cf14c12d4db177955b2a7280dbef4fb9165ebfda4568d000e972d0d24d5f50623524112bd20e5638bd5f72cbda6ac8a9a8bcd7f37e253d75cc4308eb7bdca7e60b3295ef65bd1e20f30162cb31aa3d906b7337913dbbbb9502fae1e0e6fe80d94f407fd7c6ae81cc205135a73367a592700d649b22e29992a16bef5126223d759a5cc4fe4c2965820acc729b80ad1659bfbc4c57b9e24c29025e87bf6839d3384c2614bace42b431d62f671c4677be153c875e7a488dd0289dfa1ca3c70f4bbb43030cb4e104dd99b8e408de956892bd6f8501d827085ecf57593022211bdfbf8b0ef3f02bd9072848d334bdcc22e37dce09a2b9c517d451499a32eb6304cc91eb42ab9ffcf3500d2e5c1817ad4316afde48e6b0520d20484817c198db7d2f3864a00a3287e7dc3278e261689369e0f4889432d7c4a12c246a9aa6b6a07ed96bca11a726a3ff54f80f45471a25548f6ef55af850d8085d6e0de8b7cc361ea9cd62732bcff107369452b87281ea3108c3a5226bfc9f679fe54a33cdd552e58e5ae6d8febbd12796a588ae72a1d5e93bbe794e43d5b4fe4b8e63ff06706ccba05441fb17d88671b171ea76726ccfe7a120aa7af6576befea5a276a005e27e23f6e58ea645a4eebeb294aff72e8febd603216317d2cbd36b1ef3c3d6d3e496e23b646575b84605f01491d0d729c5a8a5b845e7edada25a6ec004abd2fc745e8ae141f29074049aa17ea05bd72dbb531c69de9fdb7d8c965a495d6aaccaed34e9a8e5d4ca36bcd5d2e53e5d811e3d5a64c471da44df5fac1c139cec424c2d3cd2e84f73fc5e15bda0f92c1702c39cd3209b7d956fd099ad7fc084d677c869569863ca9a7626bce06e1b9eb3072d42eadf854b0c699428e80d91a1310cbe25f1540381f95d2d17ba36fb0c7c3721d03fb11506a0d56ff6ef3e0af10be2549870aef679038efe988ee85e206ed72dd7f2330191ce190f2eedbd4fc7b33c172887fd3a016a1c805bf6fcb9d3a97722e18f7891ec7b9418257228ab333dd84744b8c8cdc2e1d136190736892adcd72ae3f35b63569657b1c368f7cf4c7c3bbe38e300900d507c77111ddd6d1c195186c3d9beb362fa3afcd6a3a032a998da459df5849e34076872f7feb9752d655722fed3fe2120765ca9001901065e7bed83980ae0c067c34863ffd36e8fb7b5a72cef55835de2b6d4d3e2ba7e1b0db2b11db3e20f0eb2f3625bd03372d28c03e725708193a5383b0ba7cc75fb5141c3accb02cbf2e53aa453c6d80923779aeb372914028545a4d2206a9c9e19b7fe3ec77604f1576fc38fc2a94ab4b221064e25e8d0e211ac57ca3ce58ba9d870d9e85fed7e097c57346c4c5bb60a8f169d29f72749fe5e1df8391a3bb0832c516e9a5d0f93b7c6e55d34314732efc4b2576b45faaccc30be87f8cf44bbaed9f5403a96c318a8899ee11f97080432695ad3f577236e118fc01986d08306c0c49c7ff1a2e107041203c4e8282b70a895fd357e353054b0a49a8210c403d77c2a1d23b4cae4dad8bcfab55b6aefa133e60c3f0405279a563fa3bf4840be898b62338ac68384c2771d0044976791be0e7847ae5bd05c2dff838795fd5cb8d169944f8d7d54b338d52d6b31a75ad747079521a389d72e6f799e6bc398e76cde3fe1ce9837a4c3d3071b63bbeaf6b72695c961e64020d6a47aff8db93da3ced4102d888c80475ce31a8a5c4fdefb7b4f09f5a946f17724ead62f21b01e5710c6f177b95bf2437025ab76ffeaea28887c27dd4b6e8b8726959144ba5f56b89b549ec829ce13549b6832b399cb266f19c80c5c7adc9367291fc3cfa910017268d0c48d0210c45e3b4c7db3e30f29d3bb64119e3513c2f72d9bf220bbe129d9706161d69481c34bd72e237f08f3a392cb67d3a830dae8b6923903df8d19807c808dbeddaceb89745cb14de17a15a2d43dfed7fbad067652b3836b8a739359c9426f3c72b9682d2cbfb685c41303dc45e24dc93d5b4295a72cb794e45ddd8a008880649c1c47382c556dbc9c5bdbfe94fdf662fa2f89a41626eb39268329c2ca6c5d7d6c72043ed6ec16c281bca31becb8bf0a5bcf7f17931d1233636a01e884168e1f142918bd85d533cb6a41120630f0ff2aee730d8ad5dcc5f36c239c75d67952176f7e6d492cc1b835ba4a807d115e9074c0868a3df6ee774b00b2378a5db8558d98510a92ddcba5275cfd90de43f0f38654497fa3335b881185574a2f2ea72101d83f718d273f735fb3f94c616e0d10adf680274fa582fee37d3fdca28ba0ec9ae467610483dc6cc49b6178d177fae44d4f0899f5f722f77589390974f9a9d46dd5b537abfaf3925fd984df73d3d09c0a33a1ed05c7205da4161e69ad6b747ac0a9608fec3dd0eb0558450ba06aa881c56606688580babed15f75079e46d358a94057b8973e1d832ad00c2f2f5e1d7ff266405e22d72d9ad1821ddaca859a481a6eb47572ffab8b4451d6658632f81a20ab01b9cbd72b4e8292a61bd864368fe62af69d0ce6dbc4f0eb2159aac30ba1f0340f9bb5a0c38198b874b1775985f7e7ded52430772251da12f362d72f6464bd5daf833911635733fdaa99a387fe5e316d74c8347f828afc25719cb13b70e82a427d7c4d9725514a92a3ceec0d59d3fafaad6a8f13e77b3363aedf04fb144bfdf5f9db4dc72130a3cb72940e4234377640ea0084ec0a11fd15238886e1b12c17bcd08633372971785632b22f6ae83f6d4f43b0da9990947c4f5cf791b4c1a2537ae026c374694c3af5de3f2d78e4a012e4b4dfff81594611c7b8afb06f7023c959c98986c72e3a44a0efcc6b45ab6bcceb1e13a29659110e2b956713996ece1f9c0e7ce574c133fc83d952f8be87d07c002cd74ecf1430a49b91fb9dfdb2cc4d8b0a0151f72552a4267963497daa85e61e661e95e947d9daa945eb740931f86f75293270f6f4284603e579090f64702408c89aabb14762c6179502f16ffb82afbfec82c24728507ea11c626a3f9271cf0de173e4ced8d4ec464d03f5dfd72da8b371fb7e672b8152924a8d341aa6346cacccf563fd85370eb5cb0bda729898950f703b5ae4e162866b5601a06c59bc4bf6d9075bd858ec4e435d99978bc108ef92dfe1d4f72a34f2776797f18498ade978c71a74f9bc80025c4f4d4b3686600bc26684f0e72ea74dca9878d6edac409683be5255953f1205ee67a438ffb987630ed0737a372eec401a41d66e58947efb329b0940039eac14d7c9b545a7791cfd14b956d0d72829b48211017c55b9131cb017b7bda3feb7e0fb6d862de9d0d5a0498a13bde7246996959d73150ed0ec289d61f47b3b044f8d19f4070ceedfa07d7cf241755721f18a4ff4135e5dbd667d84377b308438745cc5120b29948c1b1ea80ca4eae7266d2444e2bee96b0e654c5c2f239e201aac11ce319a33c5b05d0f94c81bbaa7275bde4ff58ffcbc53b35409b91d65d8def3d66a1ebe52292d19bd7042106587225f0b6982f18397b8b8d86c2afcb2ad52411af171e0c6f7c63b9773f91680a723f5587d2756f1a0f100ed317475ed1f6cb499cac0461ec930669c1aa7005465ddc8af04527ac0dd8aeb49514bcbeb9fc0333e3f22ccd79f48406fb923740df726b7d2eaad5ae98fca331dd9cdbc234aede8cfb70cc79ade102c8ab2be9324772dbf5ece884b8ec7a175d230234cf2e75e7bbb1b6c30c7c083bbe0c9b542adb28d5cc05aadfdd9e7c6fe59f1cf92b38b80dd6b2e0b13acb86555e81eb7b2cff72dd4b99c34206954627c61132c50de6a7e207a4ed6a48851f3d9bae4aa69ee02da803ab9a8c620552461819ed52c3e87a505f5299dc0b04cdcb4a43751c40d272db6bddbff9fe81902154e11faac13ec7bb29711ea988219e4a663b7b09d99c5bf44ebbf4b52356697f33be03b1fc243f227d56be56294ca82c0dcd2c169e7872710ef80c80c8a49c9f444990aca379c34b0fdce1c887dd2b73d88460c0190e29635b031761bf809bb253af9f22d9e053b0cdafdad16ba81cada09ee7e48d397248b4055e26885365bfc3f1d674db894f6b00b2f669af1c5889ff4872c8a11b72311ada6857e3e3cbd0c351ad46454609870266e13c698a3217e6965e61c95372f463dde5545e6e54b31be63a800479656c41df1a1f0a2513af166e8e28211b72d380b5d16bfbc30231380df29e78295219dd46ec951b6ac908d0bb3ceaa5f972e6d24d72d8a17746d3aa0ff3f326046e9d1362e0e3743e155559e56ed0337172f9a273c7132b60a52ff1be5b357aca06e5b11c88b52bac7fa9324d27ade5e1721029c9aa1bd52580330f31cdd3294771f26cb241d528de6963a8a3056271fa72b6d8e75f2b62324f3fdeaa6f8f4c9447eb7b10b88f99e001df081751b9337e72665793402195eb1f6845121b308a65c4582d6ab5579bdde2bd6aa91a4ce9c6726999b32ec8a01ff2cb213b262f50bdf6b36c8bf80fb44350275dceeb03a391335df6b76b0d688a3d8f10185392c2b35e723e43eab52ee99abec6eda97bfa6a122cb807f0d89caa6b556d3d227c3e85dc7b852676a5fca429c5fdb9413e77de72d8f864c7c32777f49357a446b1f531164bd76841bed817d9f0ed9a55813a6572c1315d5e242a9e51c5860a32778837d5cbfe273743d9e4d88408c004d3da90722bdb0d4a8c8c0e6a1c4554ac63cc774a5bcc3bb0c364faa3a761d0ab99b87f602f2caa5179e1f4d126539a51ae647fa6bf0f87cdd44f699b4723eb1251da7d72095eeb78bc43139436cff9c9a4cba30e0dbe3550263ad3f059316f42339e6f4d6d7c80e2acb0878fcb6e0e28f7ce0d3186b0f02b4f36877f31e830c2236de502a3a8cf9e094c1a3329adc070c81d7c0e18f53b7db3b0aafb2f7ca4368402f77209675b9234308fea5090c37dabb23bd6718b2a839ab4e1dd2fe5ddaac7b583729827b384db2d75c38f22f3edc9f1cbec092313f1ab69e7c3ca4ca975a270c072cffca366bdd0f568fbb65330aea46dd30ae294a6ca839089876f9a358ab1f6325524d87162cdf4c3b2c67c1764bcecf7439136e49db723021b59e665c81959728bd853983f3888cd966d6a02bf026258b55eefa873ed92a8f5c2ca06b3e2af72ab5fae26ad4946a6f1eddc807fe9c29a24bab45c9a634baba32e926c09cfce5b275e9758d8779179a5aa19e714d127a7c5c7d0e4ee5c1d8044d2d2563edbcb0755c0a2164944d9791d00c9ba1f6f8dd5c29782def4bde25e250f1c3c00a7660d8362c0fadded9eda1cf5e955885bf6b3024e443a795eae9f4310e3d6d3eff572cab8e3e9e4e197e6f7642a7a5cce8b5af3f603f5c6bf38208d45f7c45ac79b6c8648deb7e987305911f9ede5fe05169919e821bb4e9212bbea6438edbbd6007283ce5bd796ebe9d062ab66dbc885f7536d31cd0de7ec0359d7f87b24142e13411455a7e5916b83d163a04788eb97da272c143b6b3189cbdaa8f32659f841a00028e87d3da3caf950237c365c46ce8ec93a26e5bb6c6af8791a053b0f49c01872b4b10235c09cdd8bf20ff68da4f8ccc8987db07cbf01ae6655643ab0fdbc263ddf645b34f8711e4b627e55e04b2c30e4fadd865ecc4867f3b8abceccee8cc072757a43d015be028dc3bb6de23337a68ca935d5883b1daefe94360f03f895bf3efebfdece69deec56feae19632f32ca2ff5c314c56f2674e0f21941d992d21372277a313643dc43a364266cfe5a902eef1a35ad62ebb85c434ad73627055e4c3ef525ea2aecca0fe357bd5bd949a64ce9ce1797f57206b91142da5b3dca352f0d2036cb26357203112858ddccb79d96dd0f1beaf1df1bef500270cfa7c9dcab726a9e967cf2875b7ed8d103470c4697ff6f9ad3066100219ea4383584f1d9f412eb0317a1eeea0e67d17cc8d0ea3d7c1e023d8e28507bf286905ae7f44ea7c372fcdd8ee32007c48ecf71e3051f15f1d86305a499de43561edadfc887efdd5972388ef00846edfc53ccca5e7bea1ed76b6eda2352359bf1cfb036b78c02697c315689c2fdc76b54ae29fc3f773f1e7739a8137df64b5c081876f4421afb78ed721b462d372f668a49bb9acbd3e2334fbef56a6642e48ad96bd2a71ec97bda627239dabfff5bb8a33cbc4ab75b5c4ec01d5ccca2f161af0a33887a106063cb50723521d38e42f9fc75a03eafd5851460ef2834055346e8bdc3d1564763055bf772d1b47706828486be799e1b6f327a004f8fedac4e51f845e2c4d7cdffc3b3d04b6a277d6dce007f8bb754eff9fd39cad6d050856a2c9ffd949579e52115b01c126c0108a9ad463a552f2fd4a03019e11ae7786b30f3f2f1fe1170ae43611216721d76e896e208d6df0a7de5516a8fc1e1b2987c7f965fe740d1c897a0a426903bc4010b23b09473f45fc8b6ef90fe02493685e7698600fc22f9745555e0103535b53a4db24b2cefe2fce4e7d9cf84662a135d1e1689102bff76763383354e225f868e4025d246def32b0013f1b1b5f7b5044d40b0094d2dc0ed45e70008760072545e8b4449eaef7004d25f1e7dbdf41ff06e7ba9551aa4301d1d7d56f978e67286a6de289e007eaf948656365c154dbd58ecc270d1113d0bfc2295df3f2af172443de1e2676c3bccc6a20c2e3b8e0cc7f1acddd5c7e37ff3103ba30a9813e6720d7fa1f4287f214177a55b7cc152efa9e6e969a3b2589aedf8cdafc11da4447285eb5716b292bf10cf7d4e2206137b7f918b47f7a89a6912019eed1926425a1b7bbb6b23b3b8e68a7a8a42a32a3581b6d0ac25eff3a9af630f92231a39c62e6fbef787c8342affd0be84fe8f914e433e2b783adcc28231f23d3dc4f45bc97a72e4e83448ff08d575b14f1a9b0ceb0bcf577d95f4b89e7a567176544663d2d815d4b04a8c116a443fbcf0fe9556834d46f283204dbb86a1a1e9955a7ec6129d72752387e5efd8b3d0c21be1b56e1842b847b17d041e830aff314de047338dec4b2ce5c6997ec0ce2e21b9658729318547042978f7864706ef43343090083bbb34a052b4f9051fe1034c5b6d23efaf16c4a170b16c7f8b039fac6488765657880575b8ba56d262a9e50e2d9f19f1c658c9ad7a8a885361993ebd47b3855168595f8684630686b4a2f29407cd2178dc34bbe4f2e41c41ff307f55daddc62a5ac534114146225ba63b3ebb292b8b65f239991897a2d5fbfc6cb014ca7ff2a69c14720565021327cd79b0a8a8a10ef3e3455a75b06263a2810a22f7b90d36f411a372c27d1b7449c2fceb382170c7e86d1eb2d3208f3a68f0d752fdbb273919d614726db71aacdd6b236d02f2e1a55ef44c0f6bfea618a2919a3179e142f94bd99f08b78e29e0f5df4311f44d981885ee34603e120566c18ed8b73219a50c58fd5f724b61b51a971b7e4ab0ba9725cee3ec7c0c2d3f8c58597feb8cec5060d0d90172a76760b031ba889d26f3539d0312401bf51b302d56512cbfd37bda6c4c0fa472cdcdeb3e5349105a67691befa82f7d53c86642629c7351b5a880fcc359e74e72b22bef73f7e1d09f8ba096b5d4ed369d47142192f3a3eeb608c76268d437d472e0f4c3aecf0807d9e907aaabcf1409e06cf163bc9197ff808f58b8d4868c6672caffbf71a2884ff2aaa10f2a0b6a730cbab456f8cc1c6bf13000f30e163b8515bff68a2b40a49bdacabe5fd5917e39c38be4e2317a8c2d3080082ae89552346a299a413354bff11743d531a38c75e85e198005e662ca5fd6261a975ae3adaa31e11533112f406a4ec6ce3956e3e14f9f5ff4c352214ce33754be3e09fa5ac5726b3b37f6e642fa74b824c82de3d838d731de9752c733b368a454cae447efab0ad8c79f66dd329b1b9f904686f1f6ad97c587aee067936bb378dc0f9c0273fb72656da4102ba74745c43b60a7b480b90c856e4dfd90cdf9d9fa694b90a55bec727bb1d5738803886f52824def1a75a7bfa662f844feb73b18ec5d66d2d7ac6a72f63d1847a26ad2a3922d6a669a22bf2537b82ba23e08e38c1f37341e93c7b9729ff002897ad599cdf6262bb596c18460f4b53230bf1623fbc3d29a8ca526665eac064dfd6f6d3ad34dfa911aa40d980339ceb001c68d8c6744f5c260db40f20e5fc49b7a2fa6958b61ce0b34f799219edf01755c679bc59dd21bcc75b530f62ae55d228998e615e328524de4594fd9817ee746e6789fccd7703ac61ee7543372698264a5acfbf9d521563fb3d75459f3ce408c79809db4a1d8ebda018111a302888908cadece1b90ff3d7f0377f0a538a044fb6f5adc9f06811876322e91da72dab0373bca2b4a4f5fb2175363ec0751a2616d7de14ad0a66bfd6a72abf9df724d810590ba82a50da65ebf598c17a8640102ee76a07d000b88eba041ddc2dd215a3c40ba8d94fee0de48245716b00f6920de7f3278fac89d7a8df818437dd4722c0df82f2714d8ac850ad41334e86b60428d085d80539517994f5e9edaa4b427ce8988f45ce26c0be44e0b0cb7f6a5666d93cf366ca924f2bd5eb241660c65531958666cbf6b27ed07efc00b52ed6ba96d814129be480471c4c3b2b2c8852072f173f9d613fae360559051d345d1f485e755065625216db50490087e74b60f72977525353a80533aa4a20674be552b0d3c3897c8e600c7e9cb63b6a483a4b9261b0da2d580c92543d110647028303a69520c2ae6a9c7b751d98b852f457d256bb43c95f43ca36ac35310d6afa2d46b6beec43c5ab4d9163230f0b943b34607725285eaf4d7eed6cdb2a1ed7b6a91da9b230bc199c5f284603ae5f3179ea9a10ee5b109918e4a2905e7b362f65fea1bf32a2f69576ccfbabab9ce64c1046fb572922be0c026e1c931845496df1679f80a191b4fa89f241cacab07d2ec5d0bbf3afae59526d81a806560d70d30928d3d9e6dbdb3de1b144e13f7e09bf314ecca000748f665773113e3f49056514389e5cc5b22b98ef2a80bc89860a8bacace4c724a6b98927d421793a95551b94ce818cb2ce344e972e4024979533f29ed910014404b0bf73fcc31c97fcad5867bdeb212f7538fa32087f0210e566328c8ad12722ebc622a43fa9448ee53fc6ce078d5b3b7f4b6a79ec8bd8bd08572b4bd716c72346ad900d973fe10bc9cbb5058d2dfc0ae8a3c85737123c3a1565ceade8b58464f94e8bbf8113749fae423e0514fead33fa6c97856b0926226a8e245ad292c72109924c4b6225bb49727a74d7d4cfda73b3a2c7268ca78f526a3a89f4cdcce72bb830248c5324bee7c294c46872222dbfc8f5265bbce3448ac4732eb87993972bcd35d2cdc0c985b2917168eb9adcb658d70b06b90d488c9e2bb1e3c34910738a26077a42514aca3a2f0e8e8390d9b39452a2fbf927f78244cf34087299f464745d1fe11e13372abcc31fb755d6cbc8dbe7d4525b91d2f75cd760835b2c621727790faf76e5a03b6260fa47fed637a84d2a6771026e1360ee35e80c5971c3a7266ccddc7515d64b1e165251852bbd6b3ada99f880ded7938d42b6dd84fdee4720dc2d422855e9f63d485322f622a350fc25420a217ff7c819293952169f32b7231303dee3b055fc0a72c5d5664061013e798618ecd26d618b9dc3f2e94d6f2553bf38b7945d653817ef74d2f2f40fbc4516abc55236ac5b3b8138e9501e06c72fec5fcb79183df22c15947d0a0a39fd24b09b19d53a95f6aad0754ee53d57572ac7512e3a4338d04137c8d29a7e4d9b3111124220433eef0c26b12dc60fc1172b4b050c7de293a18b529bba8415ce72fd9f1c69cd83fbadcbc6e4444c442a443ad5c40a67c32e178f9d8988c34e9b7694382cefd00d993860f8ef8ddd685cd4fcf16a45bd55650f93a07490abc2b0de3b0a16da1a7c32bc67690210638e7e21ad84abb4afbe8f82735e07fd1f692e5d212b6866d970ee1defabaf7ac2fc3a067c942f496e58f6b8275ff74be683dcfd4c1ffc680aa32cb17c28a1c7aeea4e3724aad0885521c7aaf4bff7943d79fbe191baf4c89f4494af0b99962d0353953724c07b0cedd6075e80cbafdb9282c03d4d66b4a4232d4c4a8633ac903a168ac2b23c10e349f308bbc60a020a6ed135e6f916298b77eaecd7747195790b802fa29535a713d6a3a325156d7702c43aa08a966c4c3c52011efe52fb4dd02a307e9600dc5310deb73c887faf993207acef1a0282696aa9233ed7467e0dcc5aabe1772753f11aaad2fc8c5344486810da66d21b5a833cc377455975374b2f50728486ff648ca237f9a40640a40481cd65b088a87c58b79778cc69be6417e406cbfaa00c17610f793c73f25dc7133b36328c9e0dcdbbe9a4dfc94ff941c081b54f4567284d3570abe09d816daf4612602d73d4f553ac6beb539ff9dd66539bb5e6b7a7221a889ccb81fb71d9ecbe270b5c9ef3a1189654ab7456368258f3e576963f67273e2c2daa70486acde6690b362a4b11b4b68745ed54a082925898c76ab22bd03db315268cf8ea4852693c71fa4064d3823fac63168912532d6a20f7030aa3172c853c7d5d7b0d508b7f49c23264ca24b71fc4078cf0b8753d62935dc46197f724382a23925b24362a435eff7ce43b2685ba341d602e681ef6558aec0d1a754727688cd8b240b1108511c303d640db3c2468754ac665705d8c5a0bac0bd434972cd3b6b01ba885b522a09b88fdcb89091bf322e029e60aac6edc80d0f0daf5e523196768a4ace91609da5345fc7e0977a78faad2fbb8db19415a2cb379ef50c2dc7730e28b595d495f847f2a88d077c753fbd44baeed53d848ca14ff79bc90e72b53040fbc8f8a6b0c73a979788babd7fd5573ff1cd3f3de8f7416c3e2b41db72084a36d0d6a7f612f26f4e45e343900c93a9c77947725caea65139d6db886e725cab03f08a838dded25575d7fad446075553638c55b61d24df4c3142e24afe7208af478c53b11e0952fc3da0fefeb4ae044d74fa1bae1281cca9ad7f0b74ea72092aff84689946ef0ac1b37196ff45cd8590b40573a08174c4c6f91590ec9d72a41b388ef8420b13330ca26a2c3642a8878bf28effeb1cfd0f146ce71f9c0b148225f98d55e2b37b66f31ef5bc480361cb6843045d235abb7f4093127670257242cd7ed7f448590fbd461088141b59098ad520a05b3f558e3aa84274450b14724812b0a383feffab6ecdd68a9b56d67d524e3fe0e9cb73aa208331d1cb1d2072ea06d3b69c7b1eacde74755b041c55e07cef5173ecea0f82b76618073d58ca6f1e3f69f62b3ba59b55ee571aaafb748a181570d508deee1576efb8b8f4f1497252609db28518bf3f799aee00cb9f4a02a87f563cf0419fdc95607fecc4da6872f40560cb139572d98600b50020554b3922b8ab8ad1cf8b19795bf9c5b2437a724143ed7f45cec6dbe9312ae7dc0dfb88ae97f608d6565ceebdee26c7a0fab372b2e0e65083d2be102b930056d6220a616c68f1c58570ef624497138042fcdb6ae2edcae468ab6ca0862891f9e4cea33473692154e8ad7e9883a881f3bddadd72108478cf302b47cb8ada6f58dd0269054e5b9d6f79ab4e42f627123133574b723c4c1c433dea3caa88fdf1891954e2a47001e120fba2a37f93c19c6bbfd7c2723483ffab9bd413f43944609cf6186486a371ad4f9d4b6f9e98a21c6442ddd972ba1ea064673b4cbfca25433458d3d81b37c708d78d54b0bcecde7337f4cd0d72197266b2ed8ba6a6af0dc796648f13f77bdf7554afaf8ef57e232d146cb11c08c7e28cd861680d54cf701d399954ce5a84948578baf8869957434ccad7cfca72b82b85dce65866d7f49c7e7edbeab0062b790727fb3e7b1031bb8e61fea41772c48057c2c11470526d01f2c7b03d81e7e5844da487028189b6d45dd303040c1d0201122be7c5770177c1878a5728ef2c2248dca18ef0ce7392480f6ea5d0b172d5e45c0d0062284506b9dfb612acf3c62b8496a549b0229939fb11b64cfe66061e82a8531d4d52a917074f405bbacf458b66c0a9a4af177c60f398a50094197233df93d6a8a599369e5eb90be5665b6d1b056b452bd1b67987a69ebb16924b72e0d326b8eec74b4a82ef02f4e2b0e0ca88b7865a539ae57a691c03de3d157d1c5aaaa613777df3137d03e2f9ca742b0e24901f205c3eeca0037b025a6f625f72fc3ee11a24be8a0c0e320f8b6b19e189ea5af84291e1d0f3d724dfb9e3446e72a743a91b46bc674db99bfa0f7b6783c1602e965212c031075cedf684c1a5f2350ba5ec7f833173d7b74ae7a99beb8d8ca0f7fc0293df840f0d9c0cea43d99072eecc8d1160cea52390cf0e82445db0fb4235b3b88951d641dc24d7f4af2f926c2b11cf59f6f248a8f19855ef7e12266fa35717b3658807736f2cb6a7df916b2755388a5cab96e4bbeb4a1604dd6438341355e94afecc8f5a34e201dc9de4f04ffb9063afa6d53560cd64df82ec65561042acd72612958b930daf76aa43188b72573e0ed2d00d2316d7b49cda926ee4b35a8fbb57714239cd5f6c4287cf7823729d1e6701cffc669c701c8ec03eba423f09294209d7d0a600208502ce02ff562fd455c6418ba61bc9cc83af038ed4031e1ff09a4097369a21784df62faf687b0ea46a9596a021a7c7f106d98f5c3903d3d7661134ae1973b08f7bbb1bdae3d63512ee6efc225d12f5e807d72b53f36e111e99b21124cdcc3a6c383a6e42551e72310a2f40104bfbff6aeecdc5de98016ead26b370e72d60972cb683feee1e457244ea830d4463732ddaa41bc57b2d7e3921fd7b8dc185a891aee0076f31f131721b1c437e243b21098e4597997276b40034095d4f65d81bba65b430bdd3da8925e806ce83a095a81a2a497c8640e8b39398c11aca9819775e93401149cbd64b7241c561287d27f576fd3466fc6d1b7cf7fefaa011b38bd9390b1cfde54f65ed72987fb476ec61d61b475a8086d7a4f63b49a5157344ed61f1f0d27097aa9d363320f74b1ccbcbd7d9d7f50859922db7b2c7159b9881e7ded304d7f3fe92fdd272184271e2007ff9e65384fb24521cf73f2e5f1071b6027084ddc4edae73fc2372878783c9062b6a10d48e7d7e128814985e15c057da4b0704afbff8441e4f533fdc7d2bb4760d96694b91cf79196a3e9bb520da06aea34e6140b76b5c7071363c303f371e4dfa230ffa62e87b81deca87e2110fee9d6a85b2cf2ebf64c109d872f6121f4ed7c1d5414fa179d0b8ee0d63b332d0e734bc9899d5583153bc4f887288579e4b084fbb12c884f79d74e73fe2098c4ddb401d117d819157e3aaf6f563ce837ddd76391268051c32d04917b3b2f47946e95106efda8224c9427014aa72c3a4669566639457ac1be9787453b274a9c875a65e99e9ea81e3ead29f05b3424d00d19c6e6050771b197168af42d2020919a25135d6349ab2485268ab91f014eacb6099b4a00c7bcca13bda1a989c2709a719cb94b3d818f0be255916a4cf723cedefe095cc77625e40dca65dce63a77a41f8c3931c234de1f4aa69abc69e72d162c82ab36cac62326f00139a34d3e59692ce35064def8e3f28388a99861f6d9794db4fc17332de79034dea8ac6e3dc3ed308cbd5dfe541186809fb431d94722f1fced39cff07cbe24f68c93f083d22d59134f7bf310df2686a0915abe35f5d6d627339ec35dc52ba2bbc06a00df38d8067bc4f2dbac6093c41cae70ccd090d1609e5e59a377925e8d87af428ce5ecbdab37c0f278e7a246ae77753a1fe6713b1f846a09ab6dbafa0a3dc67cd0cafbcaf4a742680379ec6bd8b333fe00f7b7294224bd997d0a85da4af8e27010e037958c480e5a24f5fb43f27512a53fa8c21c14e63beec2e7791a794216ac278df5ed3263bffe75fb0dc80e45f0782ae8572271122bab2c87ecb0d309b433f30b91392a1ceca55b04c367883564fd7521272ee5af301a50a0d52fdd8dfa4d06687119a27c3ec56c3b89a4de6707c5841fe2ebd1dfe024efc946874b34f92bce200b4afac7d03e08092b3e3776cb02272a60d6fe61407cb61c644066fb9888dec6ec22d8244f8b864a4eb0d97b73e4f837872a9c47154e0b71b8d7f97ce476a5f64a8a74d56c3c983dab6eb5fc59955ff4572bbc60cddbdc7a609133658ef8067faf1d857b59a941ba37f8b954a70b01867725279c8e0dfeb46338a7a559f062031cc8aa4fe72a3829895d84ef07cb7f01b550421e81ff08e520cbad3678374f150e93230bf32f82861d20bd060dc1d7aec4f3163e47a11a9053010aa621be98352be875025d78fd54675cdca2f16eb23e872949ea28fdc14cca7f8d3194b3f55f895139723bb5a9e48e87d6c7798e48c6e727585ce423ef9821a335c452d56153b23de18caa290b4c1088421feb704f05a39e7ed75b10f4109acc22dc8fd5bc6fb62707020f95295011ec8403e2bb8f1f55a54e32bdc8a5b9ed8d3d0eb0125804c780cd491b207c41f18dc25c323733a8b6f4f22835026417621cc87224e8eafb9fd344c000702cd310a2fa7fc2b16b653720ea0b0aeaf6651a9ddaf4b059be02119b4f0a63ad19e473fc659ed8c27e4b4721fce399e9193ec14a681c44b03348c7e719de37832775345e2f08c37bb9ec5728ed2042bb80f56d7a399f441f67bb7451953e29dcf97e2bc198a03fc9ebb7d724092cbad6c7927de99bae61befd3dfe18e3982e936a6f009bd22ab55628b33564d2cb1e03e5dfdb25abc3074b78cf7b93b2c7277d99a1f2d8d0e1a7fc2956072982ab28d0f595f61d2423d179099896ce432f81a612bb56b77a029ee915c790261577ad4baf39616660222c761de44be0fce8b8a38f5e7327a45c68ed658126e667a41a38b24477ad2b3815ce9601ad75e24c043a20e946596fecc2c77fce072ac9c31d3c4bced6da780c5a28aa8341e261e80df2749161e72c943aa27d9101e10c02982ea129735edd853d93308275bdb9d257c20a2568f4e9af70cba58c071d5addc509f3969cc8b0d80743f8dc56ee0802135418f351be56490219c4af3178e35a2616789f369166c4f0f3747a795c72cc0a63cee6193297393934eba30724fa1cf90ee9969715a943dc615a6c2e14c9c94fdf3626d57b759ec7d1a542172b8b6b7d302d4fe9eef6d54834a6af00b527dee1f2eb7935d5c83e4648b77e522cb96b5ac6ae946c953d9d2852258b06de2ba1c00918abbdecca591f444946d72af7cc03ef6cf8e4496f385f9e8e5d97c59ea12dfa35e6c387ceddf5fa24fcf37bbb2a929d44a83758620b5bb45bd741506114b460a3909b6bb61373b4fbda872e00cfadf6c971188b475b128085b71fc3ac7bb850547d01f46c9d3f4bf3b8d7263d3d7863bcb293b6f2d6feadd13b9612d9b17b6964d86791edf3a44bcc4b14f7b41e5851012e7949126b5e58713de1d1a4e875614a8f6dbb346d395fdc6717209c171c0fa72ece346b2bbd77e0ebdc3c0537616a0791d8301d1a86624e6201f6eb2f9a4303b8d5e1965b8742474d21ec4e6ecfc529a5dae08c9bd0652aee772edca93e81319ba782537cf3ec263611796d6e8af8a8d31793b28842b1e227d72445875e29045a189268aba6f7bc083c4be8a9a83d3a60ceb55ea7d26f10ace72c419bf12f9f194f8c4f4c17b7748e4943dd9152c3e0359675a4f96fc3e043f696c90e6bc8ea9048aee279787d08dab66a98dc2ada4510ab8819f35b85effc32cc95fbeb05a2ab33e2e550b6ab0008303c7f6cb9bca668367cecee3b7cddea472a7a38a8f1dcd75a1089c87990246e37f08c940da88a79e21f6a7a401c4a6a9722c649137466780817755cfbcb86efe194572f2584feb096efcd3ec99059aff5d27d3839a5c15140ea731dcc67d31ccb82d5ac401147f7bd63bb01dd1a6fb477298e386389a7338618a15d366a4ce5b1b0ce10b77ef49bd4bea732686ebf277724dc5e9c53b08739fa3b35a5ef6db69cce370d3a9400c364e469608e8394a2772496f6c68cdd7e6ad44c7cb2afdc2b911b00ded27946c97e4e7ec65cc6cfaf04395ac9f654f939d607043823317180b5dc2de943944947e711d6b6737eed8905a1a491487791f3e96fa9a249f5e72cfb80c489c207a4e35ad703d4dd12bc277191281260e2623aa410c439144fb920d8bb1a5a939fa6312113848e0423bee1c72319434b3036f4cf0da406cf1274428bc104f212d7a6c4733bde98073d3595372121191a84e4d29c776adaea3a4ffa9b52add326659e8aa91e1008d8e3436220d5f6ae042cca884c20d9a9be014e7be13c4b5f7b527b365455282682f597d9d7236a8e528995f51d331bceae5431132de236baa97ced53ee94da8954eafbd7a0211e001e8848903a4d1ed17ac59529720146e99adf3903a76ce8503882a03f56d958c07f29bcd287067a7b1ab16877c4b04129c9a090b738f67f3c48241f73c47812f07eeaa03438dd74574f2c66845cfcd08194b0832c0b89af6ceef6b5296614dfb94b22671a43bd9dfcf92f68e39da326fcde68ee0a29084d616b67e31aa72d9b1b54fcfe12da3cbee8df6e688962f7b5c3f55cb0b0293fb3f56678f6bd3720dd19e1c528a0bdb78fa800f3f8b2dbd154b656b76116e0ba5007fc56fe69028e6a64a16b2bc230e1ea6a657ed51901df39600fa3c2403e3f2fba1d3c7c3fc36c08b9c0fc5a55bbd99155a387c65b5c6457c482489abbc88c93961c11a949920c7444cfd5b964fc4438c23e361ff3fdf2c5a2f8220217fae4d04e5698dfe00721a184ecb1802f95925e7770d0b770c8f88984a7afbdb328b08daa4ce2e42c172dc548de119f7992d72c23c0ff687f8f1f6a53932fd58a0467bf50ddec609351b72147515831d6036ab1583a6e13a5ce28a419d76e2ea909b89bce367f1550d125e238679ecadcafcf6dfd5956a9ef60fd9bd7c5f19832ab1cf79f3f029e23772210dcf8642641e9a2de0cb61d09faf727411a337c33374c73cf0e75698e8e33a0435690303457d4f51cf78d899d4d7b05ed40238893925b988b54f23f0049270b462c5e2a5c51783c78a27a31f6dc46580c5be866ba74dffafdd3ee38cc7a972becff8c457a58d14de42337e7bd9544950ec888d35b3efae04e4fc93657b8772e14e69d9df7a4dc39ac06a71cdcf1f67a17aac1b76767d4759cd6ab376f82562752a73654efe255c44d3d07a43a99e2fdbf42d21c7196b95aba4769bfcd68f5489e464e1fa94f246015fe0acd434afdff9219ae149f1f6914941fe57b3066226b99da07714d072528e7f87d639628af56c6d245552ea8ed56683f9bb4ac0503c648df580ab13de110f079fc6f0585e8a621c9ea9f9afb0af433c1e055873b70a6b525a61ae00b7904dfb2985edb63fcdfae23f2ebf70dfab4ee30589ba66286ca9cb84a0fdc4b9a3657c7871b7e2f1af6a9c0cf168f7450e42f6f4bd180a56489f6268b86a8571f0be5f1d46cc2b4dd77d86bcfea380a33cb5d375224e06c27232e700f5d6cd863e2d149dd4078ae1a6459ab8a6cbbd1960aa46d2f315e9d7349e2c5a604317a6b6c4cd0c263dab212064d3b2485f44b47d35bf36818fe99f7207d427c3c31ed1c71d4ce4d81ac8e6028f0d0e8c3c3be8a3e08830280cdf4c3a49162a25377bbdf8ff1d606ed1219c76246cf4d9d995df926c3ca6f918377d1c1d1a022c0f531fd7b3891dd6b2c2c8a40a96b350e9534f3ca97f158bb61195287dbc79cc33e545b40fd6dbd7e5e9b1cdfae031972f263530240cea1a9753b75e010445252acd73fc7b46fdb737daa1698478fb537f16b545183f7de5e95adc723c8421e2da3bdae85200964bed4b2c17f2a9ced6599081198c4336976b87861ec24017dc65f8ed593ddc50fe71e12aadc98f46c264a92ee63badaa19b17a9972ef314b04974b891adb743ac799204bef23bc95501d889cd4c3af806d0081cd721d1b3f435e5873aae355cb51d788fb94672070983808fd44784848782d2deb63a919e70fc02f1583858acafb89c4003e74edee603d9a6436addea2fee08f3e678a9daed07d94d95ff69dd7b08489a7036c40280be99ab5b61d6c28150e35e54b079d16cded8ddfc404af8673eb50bf370ae6becdc92d985e7f878f821ad2e16803990deefde1d959bcc9b5ce5dd87d600e3c2384ca633b4a95a5ca9705121e16a755a04d28862e8ee063e0640567550c7e65009fe5f28fc253d3b8f22def8467b62e2bf85779798e2017deb9a18c3862ec590636d25d05c553294779007a68185a7b669e26cc5aa6d446f44bd1994f88e45290babffb65ed44ad380fa3ffb2729ee46b65a997e0133131c66d124fd18a4d5e2d76d7e07e111662d3c412bee672c3eee2d5f5e6157225b5315e0235f8967446441dca9227f198654be9ad3f9c08cc7ad215f4efc8c474367b585638ccee03cbcf50d48cedb252f8f79de56c8672568acc450ce70252985d22d796b01e78d5d869e56ec1d3bb491f055d3601fc724799b7b7d805403316cdc8f970492c5b98c2a56f9edbf3b16e00e27360d41272a53836e2c44f3acd7235f7179b6c3f1aad2423533d26890f2dbe03d31523d700f5902634a0b0ee40cb62f41dd6aec689764c20432a66966fe7b304396240017203c8cb8c868fdca19d1350c604cf540b4eb70d989ce83ef95ae55fa21ebc580f8d6da8178bcde1d100e54cd935d70e8e21c40101a2b3f37d34b9c7f4d4f9b272bfdf1f421a8225237d3751686735e7c477eae1e8465d061928e03c9490fc1a72a6291629223e5a3194e1e6d3ac43ca1eba1d56163850715d25e4ec5917a9ac443d6adcfdc34fdabd5be23601b73c69e53542b602399c7ba122ed6b0806cd09727048f7d2abaf61a2abaf89a81a15b3a71dcc2cd0cbe78996b0745b3ff2d2da723ba6262fbe57ee32d054796bf99afe9c6b4ea0825ad815b1be19f0d3940ee7179d5206d5418295ebf8f13507a4eca1216f9d53c80f7c9640fe2862b9a0701c42d52cb850beb488211e317f6669208912c74805d1920b8c6dc7ce6aacf5622e7233caf681593f6ecdef876fc114939a706654d424276333da474905d1ae5472418f6d21cfe5778d4380988aa4190ad3e6dc83ecd353132a23dbf0487cd5ea290d05938af7adc9d478c95d170f07b993bf503cc60e8ee0ca5633062b8af3e63d1a1ab7637aac3ab908ec2f37a2c579dccb8995584b6679df0da5fd8b67e2ee8f21edb073644025a62cf0b42fa3014f66606b6cea839b34af5a08ebce7f4f94946c335c3b4b40be752c3b58cf3564bd0faa6036293486845bc28c0ce290926caa2b8ad7ea96559c37078c9d48236ace8704571aca1507a4e07ed0f8029aacba353007d267ef09615920068a0cd8e0a12ce21dc6aa5df0d753b0ea4f4800a02e8d066a784e8c5a653ca68cb627098f925e67cf333d13c2e6c2e232b46d7bc5594f21c07b7a4ce81b509e3dd8cc33b710133b6c33d58e6331d391d15008388a218272393a38fccc61960618389601ddbdfe2749aa69d0d5994db6c76f0e897e044972e450c10f8b69ce27c16a65b171120011ed0ecef5039eb5429ced65892899df52d3a8ab6a0c0aede050e077d8d4820d3e7557a9de8a7c2356b3593fbb006e225ca2babda016e20e6150fc545bea30ecb2a2f80c3b49805e47bb3d013518cffd14056342be10bcbcd2d7168695a29059519b7ccc3df06c15c4e0340e1b62afa655ff8f208ae1a09fbf888d1392217a5e021142d74c75bc6b341f9e322e1cc9fb064e8669bfedcf522a854c1bb554d89773ff1dca2bad2ec0d5418385d7300da5720a3d31fecf1e9e296994fa96abc6d076c012d08f229802825cb34ab4aed35f726f9c92d7f729ebecc8480d031b912cd228515d9d953910ba708ead24f3e12d72774259ba6e9cf4dce6df96cdd73ec8f40bcfff1be35d7fb3b3ee9d0e187404392f54d6ed04e28fda97637c6b56cde76d5dd5fb4670ab24bf313711806e8267044ce974564cf9854e943f9c113b71ee771a523d3bbe1dc8cbf3ef2ee864dc4a41bbcffb180ca414cdfa2331a70aa4bd1be63bf09a42a2283c6b2990ccb991a15c5ce8d85d76456e3dfee5755c30ce7c2f2915dccbb3a3fe7ae9b65964c2dea672cf80d1ada0ada0c17905324cd242fb898084b24b641ce886311e93a008a70d43b60537ab9de22c9e7434d4119316d97d1f705b98eddec9d72716a72d72937072332d5678da91291d8a1e5805988d2162e3c9c48f058efb506b72480610540272eeb7505ad92b0a788d18f39f8e172b0a0215261f0bb79683706f5e46c9cce972f329ae769dfc969049260be941f216ab1c6ccd8edae105225b4ed0f6a8dd5172af0b44fb1b535c5970c8085a9a8b03f1db861d06b3083d9cdb0fc7cee6b6067280b422ea829c220423aedccd068a04b9473900914e4149030b646d21809dc53ddc5e2e5375f9cd4a2e18344c23449b70cd6b0e20b45176a4eae43760a7a69c02d9b6257e005993fab02defc25422673ffd4685358334b6a79246cecfd67e9772b7a96f35d27d6bdad5e52790de00394a638ddd4cc07eeed2ee027c2090f151720e93cf91ca041113d9592da4a8d46f50206c5acd0c638a150818314a4814272cd7ab5de3432887413af91972ab27be38187122bcf0f68b6b3e747ac1127f102701f8da292a55756dcd7944d8f29592851c65ae53107ad9543773dd8a95525a7264efa5f7c951e906cae79d14e93527e040676355ab0883a222836c0f0a279502c55a92ca3e8e116b54e36ec96533ec5e0bfe28cac3f68f86cf7c7ba1778e7d6c464f01187aac042fcce76be9dadec6f125159dc93f078b4d89bf13b9a925917253e3d73590dfa260c201793f0f488bd6083155d0daa1d1f466ac832808414a72c924be6b3179bf723b575b0b0314cb03530ed0a96a6196707aff6fdb2974527217267743254fc5fd169f9228723e87fdf771a73b147a3cdfabbc86b40c2d1872e795339371f52f142f5c96650eb0f8f411e912c6d2a5e43a493abcfbe8299c7244f2ed7ba3c6ff736dc30581d367834485328d88dc1bb65b60a2e8820465d97263ad11a8323dbf58dcc3c95a01e86c59adedc1e5c3ff979e1deb134b7b82b221020db9378bc413ca8d702f15a14046c30f1a4e87ac51ebaf0fbf8dd97b46e7728ca40987e95f227143707427432e263fecd2c1aff0f3735de89ee161be3d397200ece0236625a1027b7f9518aa95e1af61df75572a46a602deceac8a98bf2472a609e583d748a9c0ae26f0afe1b38b7f8ccaf6672f69eb285ea35181622a16723993aeed56879b033a334f25a277f397f0f756800a4c575292547d7be31be62c186296aa1a68a39683da33f0814fd01f65c2667e1457d5011a5313a0281d6e1edaf5acdbb8ce8d1c3afb07580323f963dac840f3d998973e60974226e41631438948863f1e693bfc87079bcec7d60dc2e0f6087ae3718f69b3b5dc8f72471e727c88b07630e2f0ec19e52d1bb2ce29b93a5279e043ae3302e4ba33af590215720290f39f128adc0038b6348ab751986a2bbf874b51584e3b97a579214acd73720d7d03ccbe9fd81fafaef4b6ede19f7b4889cbc040237effa6f78ce6442650348edf771ce9e496182f4a7b7dd4bbbafc4eb47f3cd1ec7aad91c170431a887b72df3f3bc0382effc55439406f80cf4df0e1c8fad56c0ac428d5cdab0c381d122b6280c54526effb8e0334e7c4aa914d9a64462846b981698ac0a55969ed9a8272e9e8bf4acae5f0cef47f99cf18ed1d2518549319f3d3471704b2fb7c4e5d24723432ed86ce36d774ab1293eb8bb015fb83cb0bd6710490b53d69c468ebfe4b3f261eedabc2b288a93fa0a5b669ba8cde6a238ed45da4209cb4bf66639dfdd71f27b642b6a44940248a5d529ae62dc170194a57a9f42ef90f2e644628be83a816866d79ed78421caa23173320c29a433df38de37e43a3418cb0915d976ce6af72f60500d46f153ebaf9ce1e8da77261e00f42eecbefc6c67f6e4952b28e0433726ddf4e7b259c6ebedab9b194466960995c7541dc8d417fa6e00a90b1f350a4722623c4cd000089077ecf285d1414d5f14bb62684cc34adf828d231f7e18b1a72c0e3460661e2f973cfa6da97dccc1e923020dbee524ba7a05eb7e881078491726cbb7cda25f7dd39831f1800b8ce30c63e02f1fa8e998357d92f936010b14f72d091867e2a49118384188400fe4934abe456c10fdb30038cc862005131bd5d20abf712b8ad18580d6ccb8e3cc8e1e9c8a73dc70d9cb6ef69a39230d422fe9b72894086f060ac904407a9ae294e8d0412a8c479cca9e59452fc99617fa0b0601d4cd317140b6fa1681f2be1ac197bf779d5d86f3e7b72810d7f36e85d610ceb5bf5592f8cd3ea8c8bf444d99368b3fdae6c2a75e15e6b4d60b7dcce475098c3075c3727fe022c1827c7ed4557e12de98a078cd0985a3924b98adf53a4a51d3872b0cebdebebea1c720d5c9769c62930738c4721e796c06c47a4bfb1bdf58f6f23bb3c452fbc7c568c0bd860049bc1fefcfa9b344c2502dd4e69717a50f55f7d0bde30ef37d5b3d0af19710647804161499e3c342436be7cdf28e21f708597d772050329c623b5e872bdbce20a2372582cc9cc3b9290b6656e518f4feb0c9be172f4270a6722970930518b2f9bc28cc4e01ccda1197a19fd7f513cd7e654b00572cefdbabbcdaae4eeff2de035507a66cb579691e0d2fb74814bd96a44e2385668ae283848539cf64f682724ee0e24d4fed94fcccbd00a9def5037102983cee16bfd5f88ad4da2a95d494f930ed7e225cda9bfeddabd7e151ab61516a8fa368b658b93b099ce526639a0dba6336eff8d843e8fc6fbf611237525b4234d47c7dc72aae7425460d53c524072f9c62cb70518c37b863715ca170f3305a6594f16a8720cfa375c3c08f1298ffa2451aa6430c63520a263a7ad2bea1c95f5d1069b4072830c54d571462e80630836baf65608f5ed26e9e40a9349e28cbf47103266637231db4077414c46dfe6da85f14b09ecf7556b7364da17873aa3cabaca8ad03e722ba28a75d0ca3607bd38713d71c5b87fa0422fe65c46469fbeed4382ebf7c825879858f5b31b0022b82cd7beec70f5e0dbd9cb56dd4d6525fd7f8763eede4e54509a0d2d5c2e2ed2e78df02e53fb8b7ac80538a7a962e42cfd731a663bdc98728699765bf902ede869b47505ab2571c99aba278758270f839cae69744b8a2c3fc3712ceae2b89e14eded3b56a08462b1fca840b98695ee4c214a5e01f5ecb85ca04443f71050e0e9252c312b9f4a32f2c727d7ad19104bc69f85e722858f4e2517e905eb74f0c7d8fb89ce3b17ccfd1a7bce6ce4713e3bec9ae6cb3752966a719c04c91b5b34d05d46cded36104cc59da6d39665d9c24497231e868f29fbea72bcfc8c8feff22c7aa8371f7ea9e4ecfad2e2db82b33f8f690c87ca94f194bf7280945ca0c28bbba1d0c65192d9bff8007d9f8adfb17b0785b12feca2cc55bd1ef2cb4597f71ff99f5f17047d2cad19af448f0eaa948d9c527aa3e873b6473a72b74cd310a88429c7eb097bb33f51270fda81112b6d11fe08ea76414bee3f0911e86d33cb6b9d26643e7e6b13049c1243207b79504497a8b3a9d95c8545edc262259c7fa65e98ad070dca9bd28c3e4aa9a3a7fe4371ffeda07c12eaa17421f07211cf2f433806814fab2d4be551f252ec0cb55350cbf7efab36ecb905422cb07217a3a96ae1105c2c5c4e62d1e94d430ad72438aeb08fd80c299980faf4a1291725fa7179b4c61b4803ee6b2d5099fbe297ea1c28583afc04e470b6adc9e52a05997b4665823acaaaf7e45b774dd9e259b352dde97fb18b694ec84584b71048727eef8cf05277b7238faf21062098322ef5ef210b707f79c080d85274705ae872f3e810277a48bc198a021ed00d8bbee649e3dd835a9eeda4084e4a5524adf567cf0d7b89f8f68edb572f50f77880f5c0ad3f802bbbbee22e33d228481a689439b785d0c387ff833f29fbab430da5fc7c55590ea7012fe6a5438096502b32525cbee15cbb63a957ca8e4914181df50e56b9c2769b1faea989742866e67690e25b9a26e97910cbbf7b2314f5cb9f1913c4b55b0b4a21ddc30395f289c5768b367203cf37b809171ea5ca4983ef6a1b295d752766718348da7a122dff095106981a396035cc63f8a08677c68883866d41e761a9897c51e3c078ac8116fd0da2344196dea5b3bc88ca74752597eda81249d9a951d15e225af0db7e46a1a1f95a5472ae7a4164a4c757f83b0cf864d4ce67b7752513108f6738c3244b486785222372553872a1a474f510331bf4a22068265ad75a27ec6fd2cc61740a2762f58f3d72a8ed5607431580d23f57eb6d9c7304bc3a986928addd379f0e7cb79b96ff127205baa8d0273a98754247cdd12b708a850c7e10d3c462a132faa2d69ae7b7bf47d9094d1b5c994a0127e0da0d6b6627bf6e95c87932fc2f9ee30d172e86a00e72bda9004a205095c04448b2347f261e6287d257557c8a41e9e5b727d9aaa37e7222e05ee4e7bfecf2b5cf8d525c148a0a567df3dc6e83dd42a4353edaa0fa3a7256cfec52107a99d6bbaed5c5ce0153cb0d26763d9496b306c4664904fa201c1abd0d958756d259aa6e04336249efd55c1f722dcdcab46e0200c101d9ddcd9072e6a9db2e646ad4a0a68ec1b199d594ef706125fe1d937f5077692a3c7316702e4b63b6d4b67d326d530a41dffc32e05f1d695eeef2bdf6341bf43736f900fd72a079171ee91cf4ca9e77230b1d07411185bc790c1097a4c17fede614e5e9d87280325b4c7d2d75dcbd602e1a286d852cc6ca8e1e3e6c61718d7f7a0c63614e7246acc18cadaa1556b77569b3c052e0d31fd1614e8846fa3fbf34b511d214342cb76ce4fa2d7abaee480d3af988f0473bc942650dbc8c5761be6f1c887223b22a5652048ab4c74aebb1fa1dbb8bc05b50ad40c68ca33aaa0990b4946fad67bc2003d117a182a120dd8e5cafd0877b3d80389d36168068dfd2851bb48ad555937239c169c2c5b18a701d36360fefc0b4222160db5464dae83fd0a2bbbb0c7ac4729f5771180f245f7f1333bd275b3ee2f4e4d7e9a132231ec0980e26954dddb2728df9c674ba41bf333a905000425ca5f54e6498aeefea162e8a3259519c25557268865a645e96eecdec6e08401229b44efb9a76db28e1a1aedbc32712f5755672c4a9a598cc941d4f5f90237d123af2fc7591ca5dbec4b5c2584141f691e6a872ba8f7924b1b601c3767bc690a39378e63031d50a29ae586d83d1ad49ff508b3dff2e636bff43c7df0530eedb643613a5557e510bcd6b357ec22d8a3ece9a1f7218035d4ff559b92e11c906c6ab854b6f9e81ff28e3ba6ea75453804b3caa9e723f0172c40c468604c73bf11bf523ec9a6b6683fe98efda9a7c2a953d246a09722e7ab72d6b9cbbcd0e4afd68feb5a27309eda4cb703c39eb13299bfc98a039723e736c0bb01378d192015c11c046606ddd290313f5eb18adf4e48a4e561f0572dc42e802de90e5d8de6104b4efdb7a68463b33182ffa2bb194efaa595faae1729a799d6e74a19ebf3ec8bf5de5ce6b18e32aae829278f179ec64ca70a8bfd37278861751e065109c8605f91d3945dac33cb0af4840888503e381331998830a0254ebad27ab89c063907d38fb4d575c063b8be91e15dfd4bfad2d2a6a10ab0b727650989cee4418253fae43f829c700e00df3b51ead535fc6e5a3355290e5246a0b3e6b60fdcba5af7d0cebb5f156e55c15873cbd01e6e65def21557dc7d12c72bedcb8d6052992e57320d46dda3965ee925c5bb90fca2679894a5745b97a12726becfa1af5fdf0384d5ce9630c6f0605de1f94c85076250eb13f25e703703b720359b27318327f82822fb3003d1ade8fe4dafd78e69dc3d9c44dc9b45c499d14899f42b13f588b16e4244b6358282172584a140b17d63f339862e44a8104ed0adc1b025185fcde523c86eb96fb42050149dd289d421b64369900ec9dd23d4972dde63997bfc6e6cbbc9fc323e5d253d79c2c5c8ed73f91113b80c149fb86563945ffe5f1c11a1314c76a7c5e74942d041237add952918d4b02e9790c62e41f46a356d786ffbeba645cad56936c8bcac4d82437d41b89750c7143513641221072e7305f298e4f874f70afdcaa4545c27f0b2af208729656adf2f1fd14bc43af72c79a9a3bedde365ccf1fd4f72f049d8f90c909300b09b84caa68f5e7dee051088d433efbab35ed9a0ff4d8722af1d5fca1ae2349ea7f30916b532c80a39f0672924847d533cf2be48bccea6b8ac710055bb7de3e5a092280628530e9282f5872931a29ec90300f108e4c98da3a2c1a9728f0523863cdd9da53f18e05178e1e727aa55cf99de6e96fe1ff7c5c56dfb868532a723e5d2a9851f43f22e356d1f64d201a9a85b0c9b2cd1f97f8c1927820665c54e5175586195788d0f47e1b13f55dd505d568a0e483572bea5d90afbc3a3a8ba04496ab14d699df2d73f79e1eb35d36f1c3e9d4eafb40732384396f0afb4ec57c8c0b2dba50e968422fff9af22572ae2c375ba7988991acb201857a92e3ffe6bcfe95d1013ef65da529f377372a5ee078eb2366f1a798771d1456b5d7e4ad6e1a50f7c548cc294c99b168c45191534809701ccfa3b6681b539c25e37145607e9bf26546d316e3164713479650f572afd251002e15e37c2cf0426affd27cc53b367525b05258690d6a4ac8af578b72f005ee18dfef78f9144e3eb4e4be0d174320977cec522ff846e9f2854a206e7285ee5e8149dbc99ed115a88dcea180a84233ec249f6f98e61aedc89e600cda42e79bc5f251377effb477332def1c643e14b1b96733deb5a4cd69d55cde90f0523c640be111e0ee45d4f51483f8fad827c94fa671b1c4fef2fa12c504531c94722ffe75e7e3a1bd62d1805908d7bd0ff64052889c6754da039cb65e14e2dc7d7260ad439f2fbf92f49b38b6199b7e74f17cdc4b5a128ae15da3649b30b7e30c14841cd3fb1c0cbb98f36d9230b29a059048af5219e6064eb0cb7d582106661572b502bd7f648eb806f790ffe5f22aeb47bd5b01c033e1792bd1fc6b4d84347c25115f29306de28a86981006ebe3c59bfce16398eedb5ae866f64920a7cbeb93491a57d7306a88e96f8b6fe9b43dbca53b1948eb14754e19632a22b2220ed10f1257521fdb5bb9f43c3fd92e9fd8c265d80b4ed33968db4c9ad909d1d2c0d40c1625b551adc04873c28d98ec884ae49bccaf7ef0ed7a957fa07e0139cd8fa4a772c627af8c0447401200bd3835d0ce0632ef557b3a8702e205edf6b19a138c9f4675fa33f84e313d31df000a56b22445061f7c2c4ce0ebe49c7e82e144cc02e07292d8f1533a50bd1352b862d65012caf2e65dc693f4537eeb45feb0e56de70a72e93ddc6b1933fee8ed79eae86911ca3d6fe917f53bb6ea322d7146b12d53c472d19dac7c60b7292d6f1e7211e185e64f264d2e92ecffb9eb17de53889c438926d4f45ec59690adede270891d69f4b87b2b85605e13ba1f52af903cdace8e7f3ad19b770fce515913b3fe9578dc25fa8c5c9b90a5acd6f420cbf472a0792cfd2a5adadb791f63911d0e1b296092e5a725b805bd339dfeca5baf8cc3c236cd7072b668810466b707d81038f315ea2de14b6d60095975d11383b15f47426a803272e88b0df9746f1f1c7d77e4124bf3e1aa975d38794da716945974b46e2ae89e65b833ea743af125a554eaf9c8b8b3df1ca28cac83bae739a9e843b9aea099135f10b7a515b4a45454d50da461234cd632433f2429ae3cae5b3f68a0502221a072fadbf2342bcef8bc010e1b29266c8b277c9317df8c35aa0fe0eba201352513728f2f6581d723c31a2934e6fac64685f804a98b20289709f6d7bfd00d5892117293420f6fce22f8872518bc0b1fb1838c90bba8180d5ec4a4e5c41a338dbf4f297be51e55f65ce748e6d0c47746c7de2f5f904be49ee4cd1c41ddff6f58d6757260b165dd91b6c3859ce1f36ea9b83d7e19111f5a95465b8f5ab75676b5d550723bede4c65c9553da9d189567d1c588b904a413954ffd29bd9410142420dfeb72b4dceb7db73cd5a6356f09059fb5156dc998b325cea7c4bd0d2baf4f2c643e72961cfb82959cf89c40ccfca02fa4be66c25a96d561b9fd57525671e5714ac8720c27c11b8bc87938080c899a2652f06b42080d7a2c8d3beb75a31b01005b7c72193a612e9dfe5083618d81e7dab66f538d930b08ed9ed86d75bac6b20298e3421711fba171146d6cb14b11594f6ecc2727f0ee4f2b0ba7830dc9d9932796fd20c3df5096e3ab3f7d5de97b5c61b0e9cfd6bc7b99a0b231e2a31db7788b418b23a826e15dad129e885b17e84612f103df26742eb3d2d1d9ede65c58290dba0f346888cda165c8a2d182944c6ee81104431275cd488c672e07f7fa059eea7d1e36bd53feeac20efbaf571a50dcb3ad973e812ae70717b315337d3c8612e80d8e2cdc8f17dc1fe67fef5f722fa360bd7592c3a728f234271e3eed434deb1faac272729364443eec64a16f5d79de2b792a5df2cffb4e155805dee8e8b32e9af75472da744d0f7754912b24065c4ba8eb2bc0875f85ebcd25a643d7d5919d740b6b22747f44d71b8076d0196a8acf21225f12d81b81a15a9ccf809243e6d8b09f417293ad52c2e124871375579ca4f091a3d474cf8f7b14ca0058df187b3f4d599d72294bf0ff4fdb9ef84973b3067dccd7499a316d9a158bc17c42c4e62caddf2b72fc9928881e6584fa1aaa1cb6ef4cdc97228c0c83a641d4742238616fc8bcd26728b3194f6d7dbc01e2ad3c784bd79e387af289966b15655d46db566e91e20b721baccab52d1975ad69c6b2d28fdfc38652ce559fffe47240eb86cc2b0fb2275dbebe6e99d220848c709b93168a652c972e817e772f293f0fc8d32c78f59655047fa34ed8712011e6d8ef57399819284f6755d402304172640c6701768fd40d0eb4f843848019ae6edc269225fda280bc9917bef898170a3423891a860b12c938ba43c93a0cedbb93e3bc1d74269ae869368b31b5ad414c469285ce757199e972bec43eea55d45ce26ab00b685c4ad8afa3eb6f823d022e1a910472289904101e2abd2e05afd11be9a6ea5dbf98581b85cd02280207940c13ada125da9c17b772da10b51b3849c1c5e283ad18796d064cf516727ed4ca321458c1473bf6853b72c57168892ece04ab9128997babd85a8b33c584ee00f2dc3cf12eb260ce021d564e480f0e5e6e628b9b3428df07a8ae253ddc3de60fd41152082f76c9544ab4726172111eef30e5b864625a295f5c8bf9493e9d27c890d1f43b066ca225adf2003dde7f58dd5400588a95b6810056b9fa34e458b44c330e28c4f0f550476f3a59483bd8adc1e3b358eccb565ee6b847a7342d9480c26d6346a435587e069fa7359850630dc530283bae749dacca56d561d78c5645754c831cee0144f8144fbc5e7f143f6e4efd4480097c769e98c5eb3f8f63b8a02658b2cbd08e6a076665062e91b9120907002d32c06390bb6f096aca73989dd33267e165d113fff33ea99838f10507bc03c49c9c4e922323354eef0864d78718f099f1df42de29a5a17dc0644639588be6abc6b2df491be8f354297c9e100b4e7b7f19f5772d6d8bf0e49d4a144d23f438414ab3e9c096ec55c8c44f3907656dbc731570e2c7bbc1f975cf72f15a315dc875daccfe4553bc4d7b9b38215ad86531cabafe6e9f7c725d06737250af8919067ea3ff4d62d9c7fb3f30871a70d4bb4e427cea991ec8f74d91b50c08719520930b2782f5cd86719d81f8fcf8232bb13ff98e361c5484c8837ffc72853b39f253216c148436d128da82426808808bfdf281d784c428a53985d60032593c54cbf16834eaff3f7595f0cc768de808015332835214e9a1e5448e56db720edf83ba9f600f6d31045f286880ba30c0fe1f8ce3582088948e7d45a865e1062f287425545c891560944fd93e6fedf8ef5a387894c5723572680fd5387c0d69cf6cf55882a9d7d4bcff22ebc52edd9c1b46fed355d8850b5fc264e9decb6672f6827ebbe676c6b4be7a9ae79b1fc4fbb9fc48e5d8f837af57bc5a7cfba6c272fb53befb96388f88f002e55ef05da432ec797a4fc224a826e938c8e3201aeb724eeb9f7d67b93abf4f233d1e43a01b518ff7988cf326cb209e21041ede0c63727782f49102081a8f3d322197ea0656d8accf478e751bf6646289cb07722e55727b6e5e49ebea62865af5a25dbe52eb9523c01a3d884c34aa06266d0f8d77fc5f2839e83329f7b15436e960207d045d4b55b326d045ce67d8f66405af4b2fdb3645688494bebece2ef5f3a8913bd6747ed6c0eced1b1a9a3f1c3f13e77aaea93e834eb75814ed267fc5a5fab2828f360065d480ab532867d00dd1f7b1e88f0e721ead794787290d86cb24008cf3eee5de9ae8165e06c77fa867bb83422d17513b3798df91fd5ea80ed356b713612f95af21477a1d25293a3596ccb85a51837e720cd3d4aebf9b3ade44f1ab6240cbc1503c95e1d5eda06c120c71f388daf9ca0df78971a38a98320939b2ec6ecf62499f97fa29f824ec6e1985a1d6ca57032a2dfe3e345e0531b4cc577cf6daf209dea6eb433ba5557d0bbcd6375d3036f36472b0cf26d8472adecef2b7ffc02bd40abd5890b20e2e5727c573d646f2867e2f72204b27ce3eec85386d5ace50121e4fa832aa518de415a75ad007f49835b4cd1d38c9e755b549c2a6400355b4321162295dc0d3b1f2a3e943bdf709aecaa33e528cffa705423addca0f63218ad44da4632c01e7ee0039913718c66deb0be43e44e1c6cd90f26cf27b2e10857b9f94658af9e845cfddafde61476bc615e5ebd472411a58600879b7aae0804c2c7fed03d42b54e88fb72a3a7fd2bc4522609a8772bb227737f4b7338b6bc9c7eea9cea3510cf636f865e04f4b927aa55d296beb7218a0e8b324899d1cfd65895468d988bc41a27e32bf79bbf8569842ac382b3406565d0650c78a35aedb809f33d4833fd7bc8f7f663cd8db7896e24557ca8a5d72b4cef14866c923389644670c7e6816407f137e70caa03a28b4a4c50b42cc4d72656a1878888d2f656af6fe4c4f26a3f24f6d7fb2d863e359e858eb3cc9c53f721ca7bccc7018d84bf9476b0672634f5b63c4f1be99d4efb03696377902f907723958ad26aaef0ee2cf11da1e769acb80460613c14f70cd952f711749ffe79c7284c86bece7e373cbe1db05bf13fd9509d1746319f16f03be884b38647d4b797274d56632e2e32e8b46f1bfc58956936ed1ebb81ae9affba787cbc72cec009630ca325dab0f5122b4f0225de87db25e34cd585062fa9dac00fdeb5fbdc042a77294618a26b70e3c964a94ae4a3c5b24d8b8422f3fbccb0ec66278d880bfd82272392fdbf1d21ca64fa4d5517ac3555b5c7d8c16af3e7cc74b98d9f396013c01723c225190bfe8c2dbaa2ae1790513f4ea344ecdef8a601d99708f73cc261f0272f6461b69dad403a7aa360a1d3efb6217966afdb745ce697e8dc61eda8d7cef7299734e0d01b87f6b0cff3c726d777ba9276667fbcd7c36db943f3cfbe3888072e2254b7541a539f0e09a49f39a832a2df83102417d83eb9849a820e10fdae4129bfa4059730b2eb00b0bf25b181d07e2f8f3e4b499758d036af9297b99f3b3727ce6de1fa8ba82cf36d9a2dd38fad4167a53516ad3df53b1c44c0c67b1167b72c85ffbe7f3cbf69a1c33322c64b2eb1d1edd26788b41ac4d4ecc052341c3ac0d6f9e2fd4efd27b7546ff9c1217118171ea407bdddba8ad683e56cd2b844bfa7243b354b05cb70c6e2a284a244c53b53dc84ebcc37489a3a98d9f5b1561fb2e7286e92e3fe54b9d06d03f8772a1c09af09835496b1baa6f8d8fe352288dce5a7174d2d3502434d6cbe07116b3ac320a2e69d5290deea396a48849541b5cf9406f9f3d24ac45cb1574a5484f953a41170dfdfb1ff0d52507235ec645d484c1da6a2f6fdfd9f57a42624b174c80542b132040938a617f40942ba45bd4915e8c934bb4fe693022617782c98d6b52721ba3b1c2e85239de33120b2199c3e5c275772090e295e45cafbb1d74ceca36511e9223faee6bbc12872c2036d5e07f69216d72e22f4602ab5ea914ebe22c398bb0da8f03ea92ab7245752b2668724e60396a182f31d0e9fe787704db68e76f98056a9327d81acfea454a670fe7e6b52b06d40b3b3c1b0456317600a802f699d64b60e5f52432f7834add61415f162b609c7c7212624645406e1b7970899539a1065ebdf7197794d0045209d45983cfd998f572499141d1dae23f1710a6c61126427ecbb8b523cd869e8cf6ada075f8b4bd8c72ff3c4500cfb7c4a1b9f75a3dd567588b684e125e9a97fd6d004336491b5e842370f7bbcced8c95765117834c904bf1cb570596f47c9f71641c71d83d048dd5439a88bd97eb98620a615290793bd0cbd30549fb7f1bf7b2e210425bb229bcfd72295999cd87e8f7f9b33d49735e9bd00c2974a20035bf80f0747e3afc663a1e3cf3c55868967e15eaee7f2d43a0b98bea4b18aa666a71e67efa3e1aa90ff31d729a0cab21ab925f3a05418b03ff5c953bbb33b02294ce60e46d54810ae4f2c272834b3d27b49ab3119b65094338fd84866a93f0383f78c0db50288dcbaf6b6872bd9be5d4d89c9fcb0a9c8b73ad0527c3ff0d05c4c43a581cb28c2c70f54bf249083d8b26b6cf901a26aeef07b3788936065a8bc6376b9b709ba6f363e0d90572f2d9f5364ee2ef1a361e619ef28ae2fdfae9483a521c771725d7d0d287e45711744b8b8820b2854d626abd0400ff2eeac65414abc1f552c442eca0267ec479729d2f87ced3544450a6ea4b4461d6d5f160d477f42c872bd5d41f487ee7e188325d2bb070ca73a81b9a5a7c06f5bd665bc35cb3c2bcf507c27f433e39843ea672f2a986050b18569b7fb71eaaeb476d0114abc8d3de3bd0bd01e8a07477a03c7225c41f7d4c725048b710797f6491dacaa70cf240535bbff6f44b41c58c5daf5b75de392fc8e60cd1e8f38cc5e87189c5bb8cd57b0d74e8bb302c1fce5d735e2cb5d704cfcd75864170839d404f00633b9fa25949bcb13a1870058bf3af76d35ffc5dead3c3e4a8dda18cda6523bacdcf0c4b43ea0180e1e1b8229da7bbecd2725636b1e7d7c1b2ced1db951708c7b4ada9d412fb9ded839560fadd38f29df672a56cb23b8e9252dc456a209619ddf855c5c70e713845b18591401b28d8dc6b725a86be4b2fc4453f86f3cbd5dfceeb75aa6d1898328f3308b4082bfdbb2b6b72bde9ff0e937bc4d4fdc938f72a5b73a0d78fba7f77449c7e5a7e8dd0cf6c5f601255baaba450bae300f708d6f40591058ce6db1ba00ba65702cbd8eb7aa5f172cd68580264ed340c520fb1f62d8e778db952bd8e42e3e8da047c8d0d19148458f2ebdf38cd6ed7680028f1e11e702999efb8bd14c9c831869dd2d194c626d7724edbb5bcdb94a4ad11ea351ad06246873a993676ad538711114c49c0ae6bcc724af996a11a83fb3babfc0caccc09f53c3e75a3402e1cfb415256b0bd1556134e69fcda44bdff64755e74a965d5acc7aa3c97c541808d1619fe56ef35a00d632a45b33c0d91b692a56cc34810a92a0bd1d3e9540c9ba75fdfb03430332cd397727fa6319306d8ea428095825848f1a7ab1ecd2466f0500b0cdc9e043c02a76b726fd642b004aaea468fa35528bb3090ffba41aefeb40751f3ceb4beef0fc70b720928549ef730a81a90902e23de1e7226be8376896640812dfca75a56387a0472efeb3a17214ffcd0201f7d6d2d462a0ead26813c80941db0623d84bee35dc9726af3c421ac279443ce0cfc846760945e79f8ed8787226c667021f429cfc00071e5b081264c0aae562fe99c47f09d2b79e6c8487749fd2f8e6ed59b2dabe8447252c98aa5f4eeedbf088a67717919883cf2f3e1e4c1192b7e4cc17e1829151872719fd80a35aba7d2a663aa88aab25ed519eb40f2d27294fc7235907c31e42612420f83c112251490583ec35d8a5d807d5e5a4e8b3a667fd6ec31eb5053759d72a26c33ebc578d3c8818749cf75b472814e4ced691178add6a1dd1cee16aa8610df77df62eb3b143019b0e29be7bf59439e0a721b7bc08d9317ec9deb2beb770ba189d3eb7463a2e4e41d866f477000972589b5af22c35ccfc6355e287a8c08096effdb79c0678a33a1c065a3029a67f83b5c442caa1f6f06317d29225685b472ce45d0865dd91e603b9c458d0b9a0a777559e2be1443a8a0b39c441398b19272c7f719de765f2ef9c65977926ae9705d54bfeba7c7d1dc0fa9a8007caae67e727ab778eeb9aaa8171844563d08802be367ebea7a13fea57a07fcfb1740dca27234f46e3fc1406ca0c120a01488610ec52c1d0af0f23875e6bc025e64dfbd7872b343c9c110acbf29bc4b5a173998579be82d9f99c509c29ddcc99828d297d57224ee699206c3d86a44a2d02c99a5f4c16985867db89c815ab3c6f35153a9451b8d290259f9a47fae2361a989ba23a2bcd049c07bd398e7f675b364a8fcd687729865bca5ee5d24e32eb8a9b7dd0d6495245d0559f911ddb0fa6ec0b18568cb6a6730768bb5ece7a3b7b00205edd6afba66d54491a77a99e804d3a1775f7439107b20c611c18312fc315a04b665127885ead7e0c7b97e54111a362942aaf1c266d920e95c6cadec792d970930dcd2b3b5b96578d0e533fd07076fd882b72f4572b9736a7dd3587233269fdfe82102c4eaa032b65512f38f91c7d0a435bcf7077267b639042429196c6cf2323565188570ce77c1fd8a176ccc92d26249faff54506053e1c90c0d7818710aaa950f54ecf28fccc50baf74f3f1f42e61b7f41afb727fda8163c4e2983310f5b0c3e3e0029ec7796c0ef2b5e1392ea63c27ec7a6f72a7fb528f384b5dc15a8b598f939ecb7f598c6d12ad25378795c5907d3432450a727d83a47f249608c12063c4dcff54a31562381596ceed9a239e76c59582af72ed7291dde041ac7f6a8e801a9cca34a06b535fddc8494e8329bd184ffeaafd7276d25372c603c18d6f7cbd69f1bbc3f88f442604cb06d39787527c9ce1c8137295880c4de04331f5ab5c43f1567d13f603ff9d6ee56a8ecf4b2a84c7048cf9342f29dc45351b30398e1860c13288bafc4729e35342afccef85458773894a083d4f5e0ceb949b4ad80db51b1b30f94418367ba1cfb38a122484efff69e39d2d006e4561f23dba7c774526208e84b8cb0e48ad63f1e2912629c47c0457be76d444fd70e04608e638a20f4b0514e9f105d419ca5c248db4dbdee5db8d8db963dc72c9b9ea83420662698dfe719aaa6c17ca0e652a184a9a6cb5f5212a36a6df3972b430911279cc73146b865943b5c6dc9a242e4b965dfd538932d4c5faed16ea2ae9404a0f0020c3f3b3da0861aa16b10bebdb87dc243f730c845ec5757e667f4209cbce740b5f82e6f9f0051215ca892f30765982d1ae560406d21c11221d6772f83d1fd441e1328252fb492a5bd2cc10eca8403dc766f6391745cacd396e8e22edb97afa24f5626ecb37cd536f6817cc1e8738967038aed14cd95d1b1d2b2d722d1bf30ece818101ded499e5b35d64075bb5a46f8b0bfea1361096597c919172eba3c34bda6aa36d8f23b0128e24920f4209ef8a0ce2b7a95c1b3f3cc46f9b72130d8d8c2e6fee23af41172e68c2955c1b87b9f504ef61dcbbca56a0a6b7e37264b49e808e7810a5ba8e9962ca93e427356709187a92b743303d854a1927c60e819af1cb8770e372db3e998cbd74a04b25b17371d27ef22a644d3309785a3b72bd0ba77cde5943fcc2c7ecdcb198aad310285c79e3dc6d4ef0217044d8f0cb72eef8c4e82039b674c6b0e66307be774b8766b78f0abf6fdcb7d70556677b9b56a4293f88200618a21f64287b883ff104dfac0d216fe3eb64fc6901665f72732b2441e7dfd24e7c292915f77b9184d64820187c1af3129af00572474f4f5a5772ee6a4ab1ec1c2c3e972c0470b0610863241a11e50cd9e1756a4e1a77bc70617253da801d70f69b22fc36563c39e540eda2eb3f58a097ea401ef5d3c47a6b29189e8f43c77275544e83acf99aaa503880b1bae625264b4c0fd81a7687bb909f23fd3b85f2e1af67a09241e231d7f1018732f6ad257ecbd9aca74768fa261b6b720efc77b953c805484fa823ab4e82dc6c9d26f80a3e220a1aff71531fc3bbb47234ec0efa2f0a04c45dff65e2490d84c19afcefcee164b5634f2714f40f030a726a453890b36d978f0f2cd663ac2cc315a090b3fc213932d35bdf5e885f25ed2f9c5dc7e846d96b09f23a75e31594e1e966c92e67366234774dbf7c3de9206e10dfecbf9b203bacaf222120b1ff4d8bbfe8dfc0cc0caa9ca43138a06056632172f9343efb6b68bea9fb64d75401822187cb41c459eecdf8811528a570653b95728cf3965d7075e9c1a4193869369a1fa5e12b2eac405d359dfc0cc046a6c29869ea71bfd6b8a489bf12e48672eb5e92c872f1e964ac62a9bb6eb54b9bf0a59c322de6c07d3d892c3adafc73c6c97082846a6f8b94b97a9590434b77beaea79e40caa75634d813c98172b2fd3b857b1a40023b18e26d564aeb8861385564f3a572f9e33e06e58ab22559ec72a1b944365958043f7d198f3b781e4d2b4b449ee2727f282c815068b79c01df1fc4bd4211b1dbcd6102abfeee8e329cc96e0fdd912c050c1250d14bbd72a5b1edbf1a785afdf00aae73d39a395a86e8f279533c98274d7e7c22b90c88182c2498cae7073c97ebfc829e4048982bf6dcd2cdc939412c9ee69437e5ca0e00f183a602b70ae13beefe3291fc0ab5a23707afc615c6af2d6a63f53e39cae1a16bc38046cfea6cb50dff390f03230ff86751714d448611120d4d310aa4c5762beeb701d6d3bed6ae4a0689675bb2ab88fc4607de64544d722f90fd4802e289f1d1ed0195a8c304ef0dffdaca4756b99dcaf2ad64c5c08672d9aff049bd0dfaf5f88870bd242ce3349130466cb4541eb7a2d7e1cf529f54728f852d327dc1fb3ef192d356705c7603f6eefdd0399eb2e482b6b6d2ebe99f72cdd79ee9e45368e120080458da01ee0d32da5a4fbae55c0188bf6a5efb21ad72e1982c0968524dad64c5e2e7b5c230600a045bb4d8fea60c11c92642429a7d72044322ddfd2cdb150661992904ae767f45458dadcf6c6e2b6b530645dc2dea35dc9874d3fd607da38f9372b75c1b9b404f32936b747e3b7a3a068aa0defba272bd8c0ec819adc55aeabe9d18b96f4ae33f405e72c46ac723d7a7180a1c62a20b0c8fc795eefcc96a9ce291d56caa12c51c2509ac301260d646b7cac9977e84620d5dbcca7c0d8064e9d55d1dc888c9638aa3723aa89971ce91e7f9d4328219729c79f105cdfbd8c70e2b9c1405f84999ffa0304b84acc8594abad4d36e9f5a727f2300136664a35721886525ee9575f0b8ee3914843a9b6a050e3f34aa8ec87263aab7d5cbd6a42dd8e14ab94d43893e89caa8d00956b9cf01792286b2f33d72d05a841245fab6391db59b7584abb30b48b3455478bfedec263636c09298570d50b1e0b3d0eed7ac09d3eca1496876ea61b8617c96321c9b2c75471463fec7723402eeef4b8b0ed7609a5781c88d07e603f418ded705344f797f9e499957d672b9a7f31705ff7f84732d27fdc1c0a93031bbf85fa3c3c6603f2a6d8f4116df3766a11efcfae860dd5b1759b50d2adeed5d61ddf6ea219f1d971c0efa6e34b26ce65fc0111c50b1d035a92e3b6bb565b26ca6c299ddeede639598c2af5f963c1d0a47c1723bed9d4a62e184fa103185bca1229d21106522264355a29220344e72cc3012671c0c6838305b34e9b3fa583775ed6482b0da14ec763982c5c879a41025547c3a960d31c451726bbd5b487a00dab50df49f4db8b9c62cad006f551317285b153ba3ef261bf851242516a82d04d55c866f3e30822fa4a2342ab6969472619a1822a48ac962a20eb764820a33c201269b91e07d66abf9f5d7d884715d72362619f46e7b2b304a2deff20bf3154395cafbeccd3fe2494d9b7cb597cf5072d26d6dddef8a270fca4cbc2bc36337fee16b73c35d3987d77ffa47a8699d53727bf1ef5a4a8d67979ba7769e4a6b241f49b907b5b9c2c0088378b4194f6eeb723ab855255705d6204e92e2f76b41179bb7a90a71ca6104ed48fac4266263d5721ea38f5bd7e2cff5dfbc877a1890b389a7602316724fbf65eab998f6c9a79808499f23674f7a69c9ea58baba1cc74baa6d7f90f303aa3dd41368bb6cc7179308d1bdf252dcb9d009592cae3b6c69f9a71a410598f2fe5461c9b6822f136c444629d894e476811872c0799e50a28568b4cafee21a88c653e463bcb0f79b6711722b8328e870d656aac6438171e7327d56cd2cfbbe0f17beade78aec145c04de1b70219d5c4d8e359db4541cbe6c0e37f8c88b4b896c703087d859c1b5a24fe472a817add0d30a4d6d6aa58d37b81709cbfc3a574f05365049496a43d227f92772bac76ad03f0667f6949a798536c3412e35b57b3a684bf7df46b1ea95333f344d6fe5d842ba480a0ed48fd003ebf173f91204e72146a6d55ad413389c24520d484bec733ce80867d484ef571d600dccd59bfc83114548d1995a3dbf68b6d7013b697149beb03b860a517327520e9c2972460949a888c2372c30e5397da013c321b56d4281058827f09a9646317f37e4270941fb07a9fcd54f30adeb3b54c1e515fcb428b6cd5518540374145c9542fe20d329edcc3137843b2ec2d12cc944e87282af761537c93116972ee0ddbce69bc1f368c881d6c195dbd608b0708f077572b039f064c40ed000e258e6769e7c0c8988426ae9e98b110d7470e3b4eb4abd3c0c30c0b22254d50aaf3023e8d2e8123601216482365d77e9ee44b503dd3ca17251e33f4f52c1f4f8559bae24d9eba638d59db15b1584c4247f42257e291325547c9c4dd0e460f74d46ead2ca5536cc07c51fb7b1ae2884e611ab14f649557972c95d644c2fc1318d56b36a6030fb6f9653a0abdbfcdf196151d3fd1b25c6857247d7e230ecc8a2ae8c7970d27bcc4844c65a876cc6256684ee89c272fe683d70bec22b022ae15a2a08582a2f5aa477fe16445fd85bff838ddba0fa9170a3577292eeef517e236a8e51bc576ac909c0c7abc686471988a313478e4b6dafc5e14a4b4f0f9c02872dd57a5c95a0c368cc451d570c5215080a5969fa7733b3680c011227162b4ad30e024a20d1423b8d1ce6f81b4041a91669cb528e400f4b211e7272c1fe2ddeb8480b73cda353fb27d8ac5fd5ca91b0534df05d057bf539f6720ee22b9f2c818fade2371291cd5a0fd93362caf3bde7d64bbb002d89d24788b14ae1ad2a87eab34f34869ca78272294223cd28f7791599e2d9511af6a495219e7224a1c51fd4845fffed0953e2ef346d6abaefc716f885433f646c51a9cc1e2e5a9ba53414ef2d533d8e28f2eeb66c13034b528291eb40b1b768429d562a36b043d9508e3ed853d5451ae8da9d67e8666c7ae5340340dd54057c9bbf76d2ae7872d368dc8fab5fe755ff06ea7ac918d9c070f4906faaf66da4b9b050126d7abc4520d0c7343af315b9012388399c4bdfd13fab0d3915db3ae3a1a5749f99e2737215792abb656295c194e2c9720dec60506b9c84cfd578f60e7b454c3fe5617817fb1fbdc9937957d56bdecfea5b088448f16f140199f64f0010bdb1ed1cba0b729d365712123fe6fa329b027d0103d1dbc081468bba804fda19163031860bbf5b436409be56ea68968f68320a68809aded7f4f6f2d27ea263fefdcbc7f089577212e574e23e08ad6866cbb73cb7076e8b705f8933ea0e38fca86823b6731551721e3684b818a927a24602f3f29b694d99d0786de7b767532cb27a63a2ce2f1f61e09a8af00d3e6c6b956aab1ca5c1a2b7f7bbcea38825984a1e4961e7cebfed0d1db9f486b448f4d875082aca402e432fd23b838436255a2e391e50a17342e3725c73542062a673ce218a2ffb88b43bc9e2695f33776a1fec34c9e3b5d14dd77224df9cd39fe4b5d3cc1d79156448f5c2c9bdacb42a81884647f29a95e1458072696d757ae99d6c31dc0c769be9a69802a7b1c390d39090259d5ca10ccb08ea722f19459ecdbea0b8647f1e282a206a19086229e96e4edda31fe603fc1e72f34d6a11d3c91a240345da439ec65ce895193a9edf0efa542c311d053586fb08057265b3205db03228215faeff73f12fa690a8f1a145297737421387a70155c92711ec5ed4debb0bfebe8a1a4369261325a5830f3edbf981786d7335dc9c6ec9921304c2567a0edd875cea4abf6c521ab3f2d73c5b3f756e6d4d69c5748ce211db72906119593bded58dc2da8e4830d4069977f9fdedd1f48ad2c9a9350f2920063e9f210c73c671b3dc45bd5097fff7565f79c2a8aaeb0dc701f93323166124246baed164e339dfbf3acbb2f072d48e0675dea7d5427f4b39ec97a959e19f3661722c06b34534cdf17c03ccf6f6dcc4ed64ec78c3d28b08b0aaeff400c4303b5d72d084f182bdee3cebec2c38177a73f8cfc6e6cce14f30ed53bafa0c0a7ff659350488bdec2492ef6626ba530346c290b666083dce781688aed95663b3795d5c726a34483ba2b6c06222bfa726f95ecf0e46a9fe7b6374791770d7013634fe7e72d224f79cf8cbf512a20011fff4bfff5fc4c35b9d405c8ea4c4a7eaf6bafd7372c91dc619e36ac7ff62ac868f731a5dd8bc14801d3aee3c10b0135f2c4afeba72f3613918c7f6994e9ca448c2a62342e34b8d1196f3b0bf769b26f28332a92558f453417af701e55d1ee73afb4c5819453aa14dd1911dc3ab5c448091401da32d3c67b48916bad707d49b9cfca4d041f2f3c27598b984e20ca0210f43850f1456bdc31c4391e99882ca4f725912b67dd97d916958645d1f5c53719f1e552ef24244791ad3f63bfa8d67b94c32a176ff4e3df8c19c4a19dfde048425736fdbad72baabbc0f100eb4cef6dff2493eeacdd0e5288bfac9c8f763c3d5881ce5c60e0dce69f816723fff07f3b5eec2978937c1bf3466d2dacab7199244d4299928ca7267532d222315c09c3be296cbd04cf31371365470bdd4e0a20336cdd52dc1d572213ebaa115911b50f3cbf91661792780230a1f6cdcfd563863d852578b0cd672e306fa5a2e376944d57408200c3dece1d3e5268144ab2c3ab8268b2424917e721588e236311d3b27cf50c60540b1c6003fb350c3470476242620190539638e4417ad69e807dcd404d92a2c868fe794afa69554c9ac3d127dd6254a0ef2d4d60a505f08b636eda96127accdcf2fda3b1e4742b012dc7651cc7a3d5e6a2d230a5f9c68b9af788931cfd9abe124f0428f967d4415d22043f71f9e5398328f69ce72356723a1118b1bb657304fb1712cf7106ba1cdd8a8b2ad6728583ddfe5e32772cddda43c1aed04c6c9ab8fac434cf1eb9351e3703f661bc1470397c05b6a5272a0e31555afea0758dc9298d95c24af665cea2a5d8f588a82e2cd0af2a72f9a108977957ec30c87717514e395c22351cc1f6032742c42b7e18104189a10b38f72b538e68cf5923b2f89dbabdb30e115df1f5a076bf02462da07c9bddd57672743a14158f01b86ddb69dfa1ffd76a229373767bea32eac4d67c451024e7557b272f4a60d34d5d679c6a0f99845744e1e21f833ba0c69e5f2a7bb16b53161b28521286a8daf404a33fcaa0616d15047b6f6bb0e178eb95b93b6c45dc40b3aa01b72cb89c0f75be6fcef0c0dfe8f0d3d445d117c9561f50dfeb17400cf879f6a0772d9c3f5062dd09feb3fdc678342add332e9e5aac49bba567ef30a18368ca42772c2b3d4852bde1f674d6b9ff39299e0ec78ba23f79b2d88a604143d08e2308272bf01d5c1a5f9b2bb6ab74acf8fb033317d1430983c223d205fed822fd4c44072dccf8ab33396891b3a945700e6fae6f93ec4cf708bd37c79a69dd477de93b25bca6043eb1d5696199ac9b9d4edfc109514b884094c52d886ea3eee4af882a1099038544852a07a58c51c7f969a54a406ef22b13b2d13c95c811b937472ba937243f8a124cb969968b0d2f9840123e958e54b67637bd452a7d1ff3ef811d7795f2ad984dd62fbc6c503abe35f0deda87e7e1c1fd9da31ab463f0c7fdf6516b072473f9f593ffac95ca49267af242e5ed7e8f226a06a59efd62f9dd9a27af30d2752c2ff09c86b9e5f3db953e81f1d415dec96953e43f22890bf9db6442408da727d842dc81af9f7d5c973f7bfda67aca398bb6c0a58c10857dd84957c0553d25e343f6f290a4c275758890396e09f9f19b802ffb7a479e75ae41c86d5b3e7814898f53a616b9208e1cf83fe11fff154008e8b551c19e6d287da1dda7a1d35e57245ea8f270f3f1752cded861ef2568c9f258cc704890ba652b67d40eb67360c2ce453bb0caa8fbd96011df380042d4175205c7a847227bb03ab273541ba92d254c9072618e3ab226db5e191247bdd43ae0d1a12b5033664f6591ebad5a064aa30f0149daf3ab2fb86c0565dcef76c8c068d3a02df56bd81c913b736ae4d6050724d6fb6b301bde5008c8d31e0ad0d6ffeadef286d7627db9d76c718513876433edfed049a263592c2bd9c7e57bcb140de1aa281eb7433086f067350fbb86995721b48330f097418a5c97e07d299f5a3bbda06bf47f287a00b6b6075b864f360726bda4c6bda47cd5676ddfee0fbc1a58843f7b8d83527da5f353c46c1ebfbda72ff75e77af1e1645344d07555dc7e5af02fc5bb882e60ea7fc9e873902e5164675bcc5e0fc409e31547a149351b5c9a03fce73f3bf05be96103545d7db1325c50e5a41eb41d49fff185a32b76f3259f57860fd636f6ff0f02a201f91db20d6d68a1dd1d1efd846f194a506ba0172fca012c89d1cc33ca1d4a2296565bd642e772fe5361e66c6464aa3bf1be25d6527e5fa8e7c63fa92b9fc438f31240155aa7729f970b9c330bda0ee2dff20666728f2f7473bc0e0f81ee82ae2c0f7569c6eb728857eef925a7907b83300ffc2a157db8d701700695f17d0b990c7f19f95f8f720fa091cbcd602160ecbd8e5daa3eb160f628a9cd31a3ebbda9288479507275724bd004694a32047473fe6be246119d4ad8569af1d00ce2dd209c3ab1b5afe87210f973e6c5a7046b5a62ae5524ada871d8be402ad2802e85ff47b3bf74b5be095b41f5a144a28a3d4e4be9fe35a247d76b454dc289958c165ac74ceca40e41728901eeb6e5b9f49dafd368af2e05a0f8f59498a328e6a5a6f3243e2c505f9962fbe6f2bc8ddd2b4c79ec37a4df1dab14d5b048d6469dbcdb5d3eda8027a669720df2ac9ba5f888ec0555e9a4433b4935dede44c0be3b6388849f670e4a5d455bd01e79c34f75e1aac0e8067caf8dccd75c3e1033d1357d9fbf178ae3611f5a729fc355454b979190ed78c3bfb9bc81dd1f0586c3975a6bdc6ee2ed5fe8fbef69ed6f7a615dc028196dfc1e21e37137b0853ed7058a2d055caff483c77261712c1f1a23d3e71c44e416904ce4420729567e41ed5557c015fa2e28562a5d55e05b1a393d0e6ca1bce9360363da9d7934e1900a49d4550a7038afa5596113db655f310e5aca98ae8326f5d30bc484865e63bfca5a4ae3aeca4cd00dc0e183c2f172e6caf897211ab6a2510e7a928394994c79ee3cac1e1766ae5a56c241bd0b564a7362abb901858e67e4ad1f36a78beeace12ddb81591ddba6738b5cd0ca5685264fa6a23b099044cc214b9379b9eb4424992e1b080e78a1a8a89f8b069c71f870fff92beaa6d8612ed9dee8407dd331a523d50b8482445049f68389b6e110871f518ca0b37833b6092616a2a6f7192bacda285e2eec9d5d6728072b1b54d88d72a8566bbb44a8771ea6d4e67ab9fc00bc47668373e98a672c55dadcb38ee1b272b9806a765fa1ba0f21df6c5995358d807c6b34bf36f0ccfd724191e6b3892d035958b5fd9b45d478e7115123ab01f4ccec2e1d9f62920b56e15efe6ee371685013bafc30d50a2ee4903ed60e0f46ee8da809cfa6523a6ac2f5a1b5c368741e7220a09004f50db827ac5b0aa39b0ebfd92ea0d9c7d6bb26643da94fd0254f6f6666311c205f0c0089eeb06a831460eb081a4f4da54755f33cb835d664fd7765726062b6eb046d14aaa5ee4a95137f2adc3ced7db4c8e0c05e0a175ecba0d04307abff5aa7d8a4e507b00c747e8ce6ce399eb843fef0185c2baee9752945ee3217ac0cf5369d6253d40a94c9732a323394b4dd608fa6965cc7c1eb2b1a9521d858e71201cdd3b618b5c5f7f559f82655ed8e9dba5e28dd4e05179a09b0a39f5c54db0626d04ee4333e1bcb587b87f7b7f0bde7a3af855df3cb88dfe3908a96cd729109a7826bd238a400d97d2b555926b45a714edb77c466279da75489cf0b524b7c5c6717fb5088a2dbf5886443f36d21d114d22276ac944960536d64ef1d02721cafa068c031d2018db320b72a752d3f6fa0e393982ea7f7ba87a1e0d5c90a726a23c627ce1d825148a7d22abd8427a08528ecf82ec31463066b13acd3ab2872e18def4fc2807c9e25d19627489e7da858a4aa3664022a60602a7a474161c42752b1d96ee3ef44a8f7477564b1e644b482487293cfb4416abe5e029ce0122b2c24d20986493eabf19506576d256f75a23ff6eb46d1da0d0d6e565881a15d0d72b4512b54d281e81b6ed47942b8bb5b25915f444bfaa44c4945dc4ef7f0bf947266c45d38a1a43205732c5aca71915866d1e3991eea1df66dc3960751acaa0b53e2b5dfb882bb28bd2f95944865469f4972aad462d998103320bbb1e4296aed72e4cb3ab5d35866b26f59a0a442bc03f45bc7b0ce0faad9d3c9e58df0c82eb96b9ea05a8f94570c4a1293131a9ccc47b1dc4f15703b5e1ced200d1fb2e1759771a94314697116089386fd8fb9d8d36aec4175ae49bdd2559733bb17bac26b24722651acde2a15a151ce53630d19f744bbd5cb01866b58b439cb61f2df700b3472acf06d749a31a1f14836bbb3f9967aa28d1e6844c0a0c318f29a98241e677e725c10462651865cd34b8063eadbdefb40a9158851371f77ebce97f5476007f50c8733b582332da5d75645e13df9a26adbabdbbe627ad6b9fe88fbc88eb3952b728a9a41f39369c0f8bd38e097d8fbca389b3326f1e7a2b93ccec473b6f36f2972312f578be4508d79e289458fad7b9947b5e87d30eeec0c7720fb45c35cbe841b7495db00f3541bce79dbe3ab54a723f0ec58d153b41c7811369506083ffc70239d0d63726ff34f74d6c034324f420ad97ed608fa193850cc9e35d9a69530257285c79d52a4a00afde311b150b2043b2e1ed3c79018cd08b99f66b789ce2fe872b9153a2c4bcf725755b8716a6dd2bc42cd5d421a8191c11b46ab4fa61be2cc724946d7de08cc3e626496dd25eab6f7e0ea5890d69e2eaad9b5770504dc764f72ade321e4f1e347f4c731e8f3a4ba7edf79b7b80c81874ef986e3cbdbb0aaff1a741d76e34a5378fc5762cbc6c233c313cb8893211321cefbf02907ed412097724d03eb8fd1a3cc20532845a5440b1da291cb37d74e1c088ab456c70322454872a45fb3e8c81c7b3a5c0886a2ca8e33a365849c92aaa026eea178d8865eac9772720fe991007bddeef2d1b8e5cbef141305dffbed9b896318430188f4716c8a3bfa7a2bb26cebeacd902d7703ba6836c6ef605cd057ab13ecf0a7342fbcfc3372720322f2e43e7ee43c8263939cdc20c38d186ed4530169351c0268fefd150b3a697e5a92f90ab66867e5ffe4e25a73efe140675cac3609a99614aa92de555972998180c093265185732ef9af36c9ebdf08213b92a8cadfe9c1d004cfc988d572ec8f532480aef075d900263b9a6f69b5a812241ecc44cdd9184900d59e7f5a7269c321df9fc86388f18feeba067a98eb67fe3b3d4429e515027d39b12f0bc372cfa66353c28e8deb7a4ecbd375ffcf35a6c406489ac84dfaafd60174716bcc721541186225ef9fcc1a748e777ded0c27debb65d10a7fefadaaf8b259fb44ed55a4c352ec667148c4a507dfd595a1c08b31ae705a2c945b3e8a3bc3cf5cc97472f1922135cd7de0c7b5024f9ca1da39f7ad7ab5198bd82136d217794381a90068f4d64a0f7e0300152a1fd2e25da375d2e9e436a87bab670b59f180b2931dd37245bd6231c2eb00076b443e32a67b8eadb9b80bb4801deeb21f945d0c1a6cb172b1a314a47ffc8f1fe8267a1124196a33a76ed7647cbee7e1d8d3626f8aae42727a2996245f67d4fe6bc4641a383cab4ba535ec5aba5c44bd61ed1aed9b4e4272afbe1968e279fedc29f563ff107f15ba27a4a2efcb5b4f6580e03a4ae2c3cd60f4029409b416c7bfb26aa3d08143b4f97dc181c9ef5dc7de0c32a144d9e0650f531262d90f7bfb6f190860d5be30f0ab6b79a858692a51a86614823cbc366e699c91533c55dbf13213d9412afd9db867b68868191a949873662d33c495316772cfaac4f9f912e7b981a3b326bd3897e9cbb51e15c32f8b17963b851f028d4a61cc300c97ea095be5a4c9d822a99c9bb262cfb14f229ee0ea339dde3f2f3c8a5b82c96f44dc9e76f10c5d0971ec00af1ccc2677dc36ddc4abca663de665ccfa325920a4b4dc43254c5083d1027fbea8e0ed2465ab6d82d4aa1e7d14328a238c7283b02320767a8c9136eaf0a062c9479539842193774e0ac76dedfcf5fd7c047202088921e19fd939c97dfb17d18b926b7fb0e31762abaca485b42a8bb4569b72ef1c6506a6b7a01655d5da80cecb5233c167437b644d791f8190c28f748282038bdbcf3b96956970d9d0cb5a950b10e58485261155ba5e3b929864241605757261597253323d026da0e3d954fbabf223d1ae39a4b6d60936546bd6daea56e764485ede64fcc01bea54c44f4fd01c85bdb2b9c12c9ac2fb77c645aac89df4ad729ccbf8eefe4beee69aba6cedabb05f73fa2168d3908214c5f895b1c7afd3dd26348f465ec9b75904f55c9ceeddd0232fd416cafe983ade8aac5fadbdb3e87472ca0dd7fd31e2c87146ab4effc153df24e702b3692729a3246cbd2fc62af15d728642108caa48bbfea5afa06ba6577462303d658ea2cf3c7cad4020a49dc1be31edfdac65749176af6964c969a292f09ff76944ab4ec9435078c02642eeb19d121385dfae91bd768cce121afeff84f618aaba65e85a44b254c8f7624feaf8b072cce6edc8c63d3b2418b55e3d9ac03f107f540e7cca67a0c0b4fdf3000bf73d7250ded2820696b532870530536638000c5c96c1916185fccbbe71087169d82e59e024db2bcf173601242dd25aa52e42c92179e4fa4a4505c04b5460a261ce1135ebb2ebb1b8990f1f000a5bddfa386c1b4e40b443ffee2200cb04fee2a6894e0b3b20c684a385b3e031089a688334d9b2eb1c6a9c2e2847b0277572da1cc8a7723ea89330c94c2e2e641461af60f1822e5a21cc69b5e73f72b451b308090c3e72fb05e6ba913cc9bfb721ae30b9f44e14a41b2e626e2d04ec09a6f1bcaa0b437225d729e987165bbef111f332ee1fcbf1583da7db4144f75ddf109f63d01eba60cc37586c344b4a5bbb7e64408a392aa6d903b4c7547823c507f6a17a3b4ca1728c9d3508982592dc9f8ceaffd7edc8787bd65201b946e31191b1c18c18b24372f887769611d15b24b0cd6311e04ec6aec4f3e5ff464c1200bd50293b4afbaf36612ba6832380ba9177c84d22a443c907cbc15a470e4eb6d4fb650e11a2dd637230132b5a2ae1caa213832f6ff1fb3bdee81977dec95a44841aeb8c50dbd7cf20c5b5e3d316f6836fddffa68efd0fb807165190cb9b2578c4763bcb8171b8ae720542d7ba454c4b9c5459e3605e6ba879805859a0ef40928750efbb8ab2fe54727b58549b2ecd5ef963c8f295a1fde07b942dc24ab367068d70a653c0c4e81872e1c720de229d4030879435de592a5681e8f507d2327d31bd168921efddc7457205652f32376c9fd2439657b6b5f5ca2b89f7fa17738ab785453a7eb95358131c773adf3ed8e97e8b8c12b9c6bc439e3edfa0440a74df1b08814fe39701f0857282ccbafd6c808e65821075d6a8757be12a48127659a389a7f20c0ea4d7001c726c5219a5f3ad4ee6122d0b85c1a326e8a48fdd1b56f0a51917b30c990f18712ee249ec3d0bf0ca3ab4ec67f0e3ec98e67f2f2d5bb28d379afa66ef888e8f63516771c33e433f548981ee7036265dc308d7baa89669f416867c4d19e7889ece7267448a94aac0e2e5e057a981258a38862c01536650eea80ca760350c8b292972a4e234dd0cd96c682fa6efc128336fd45965c63bde50f8ce907b8c9024d1974281023d40a075fb91c2587b12e4d9b82b0c6436ce3fe724fc63cd886837ecdc2955ce8c81a157dba0f8895493f948488613d475b7c94ad6577c7a4116e567997205aba903908a899a52baf09ff62e2bea3cc00ec597353331ab7636444cce93729a151b841b9115874a8db362967915095718f8ff7d00c3a3abfe719332d6e76c95c266fb990c9fcfb560efebac72d522d770431c24c78fcfb4a4047a9cc013729af55488320e8e93ad995afb13e6498a7b8da2c08de445c169e1d54dbda7a272c144e12ee73ef547c26c9757b8a4d80b34a64962227f0de23887b398da650c27396be5c4bbef7e1dec6c594570b3a413f37246c451666d695e2a0ad7c9b45830e2efce71d787cdf0f086bf71cbca3288fc5021302e572c461340fe623f9279729cafeb9c3ad5faa6b426b183c5f2f9c3f6da391c64b627f346cf8039be454972a022240a0cd4b8cf2798c2650b32629306daf805289d16bdfebf243081cbb6724f73b1c2214782d38139af444e8186d2e3e271f5bf50a5fa5e544e118b72dc72ad45fd445b390339b06153d2b0ac871e9b512c3559e81d2ba889fc693dd1133dda17381b81f4643325bfe22c2e50ef8baf4801b7dfacba5440fcde183c6d091c5105db0e96b49d2a5345eb8511b5c2173d887ae33df4cbc4ea006de7bdd2ac4492767ed408cb9f3720a6ced58aa430b5abf9a973e054e1652e5d13196c51f672eeb7899f40f76ea08a79f1812a825cd80e6f83c29de2d6d8f220d3fab63d9c72e0fea9fb5039dbcb241997e0653cea2297a1a486b1a9622c172a1ddfc5cd1372599598fe1c33671da266f3cf91ec9a2a69870e44d7b48a404612f161272e10671ad31d5ae1877f89d181f7ed449888259d405de12226b5a6340224dc804fd61a19992357c114b19ebcebd0f28b1aeace4a38502d6d6fd74ca7d130024ed2bb45407d3e80137059051f0b7079c9940d9185d1a06e4bde9aa59606a727d31e19727be83160fe7162604bb6318cc56725578d4242c66e8a21fe1dfd6ef72310634c5cc87eb43dc15fa415614dc8d883813f7132d48e0fd15b10822bc97aaa050b72e255ea943c394ea90660ed63061e39b7820d8770ddffd4c8e5d1fc944a957f726260394d0704ef272adfbb7fabf7db9df4a9b2fbf0b376253104a42b2f1436725a80c15407d40b21b9c5461bd453388c737623ad79abd92803113796b5f673656a2e30ff5d8bd137b00679d76b5dd0d0dc6c7c7ddafa37fd0f7838c50cc824727fd3db9052d47824ad7628b0a483d2287c627fc244308173487e7a86ab143b72e475b2296aaad3a7d668eef6e4fe1bb51bce6e45ec47c68b55d63fef8b0a5024d36c7b7d0f466699245698c1c0b0b39aa9e2d67deb04e846f5a1431c53687e72ddc15448d7f134af5bf44d931d2cecc7e35cb586ec41e15edecbaa6387ffbb699488747acb1f9e45359d72d9ca64165f30a2c313b52fcb37896792cdd90e4633a1d9f3a0b97dad913eee2374bc4af0b3ebd25c3dfc87b92c7dcb41a24c86087204739c8fb9ab6ce0b8963069f1fdf2c468d3e46ae8062f348ef0e87dd2e87372e12918b92cada06a0dcce70d3f7745a3051c6ded02e083f8f54a69a23b8e6258419a651e64946b4ed8d1eb0b3549ffb14cb65e56dd725b6e9080ef288f7d64721dce9b61afac7963410af977588a0859da379c7b616503a424bb2bbb781c277282b82e673ff393a9af62cde396fd87b5261655a9cc34c7a828a824f42aeb5030590f73f51dd9c71ec5ccdc53e9e9af058645ed803e8dd9d25715892a04837d7273b2717777b8ea8c1c956adde4204a92c88fee66e06c54c41208ea816db20f724e0b1c768445d51e72eab467d0e646817b4ab508d87e75c26d082bdd693926463ac200b5ef0dab8f92af1886059b5b39c7a837fc8efc98d1d3716644db4883726c2cfbefec838cd25955cc24a1cd787e8b64981df1b9a3f05ad78868d3bb0800b984c86548ae3d5db9f4ae78b48ee4c36d7359e5c24d08b8a9ddc531d2d5d97201e0aa3c15714f0a99bd88cd4362b4d0e74986a0b0de7c1ca49b203f60cf5172f2aa8baa8a657564011b1d949656f75df5554561ed7778b353f342eb3b68796ff5f3be5be2f3faa6be59bb09ad337c5d1aed5c6f028c4f3f54c40e407dee64725b65672efd778ce1da6aca3dc98c0e0c585ddc859f91bfd8af831b5f5bbff772619920d08bf512d2df3c003f0f89a22af31354fd8b4708d7cc60fdd70ee94b728392bacc57fcbedacf78e7b4f3f6c13ea7d493d523d526af89cef5edf9d44b72967ed1152c1192dc16c461faddf27406f78234df9a9d71d2b6fad39b9764162bea0dbb6167d97c38f47354b0bb611932b23f2a3d6ed09fdf17cae7bcd5cbe632c7eb21e5e1419aa9c1eaea23348ae35f65950723516d6c6519fa4c29d0269172a3e058f8514e9f2f434966d1791bbcfe80d14fc30ab9ac100b6caacd85a64272994898c0f0c0278a3e68c524f606e2945a09771aa5febbaa6679dc9338d9523653f2c11c883480c162635a65783a7f21f52936c8b2fe04319b995f9813c3315d737dd4b9d89671ab949215d4e7c3128b9070c1ba4de440c5048d9534b4e48b722e4effc56205162bb0b435a82b6a42938dc8c4e3379b4ec4f07e89bf70d7cd72c84deee290a8d74c8f0576abe57c40e29bbd9a583c23c4c237dc7129235cdb72c873441e95a2ae67854a6297e18758b587a036cfb679effc8cda523865dda17287670796134dc7f96709c6636b4a805cd2c63e332908b03fdb799acb4365dd7298a72c7195868d7fc6e2e45ef9f9c6ef475c977b2259fd41fc514bc8ad51e4729e6dd44ce060c39f9584c1f6834d9e8734eb055cd4b515d1fc3661637ac3dc1fa2c66b4e8825b1affba21c4c97d9b845832dcabd50f894674415bfec79be07727d56136cb3a9650b1d69cb1367129261175afb0ce90a05e2dad75c489a145a549f19a063226a972cc0bcba6240dea987df0aac6e0a81aecf860772e35cbafb72b46a52f7aa0bbf19e7affee543896f03a0b838f8a99702b0b4c6cd82642302728fda9a29c64da2c604edd05699e6e9213ac514fddfef37c9ff2395c401059e653d0a1c937fb9fff0f870baed8834bc83d5a5af7a2775efc63a2d522db090a372fcf3238a0ff29017157ff01d0e37811c5f71eb8f26f9aa34ec71561bde4814727220caf0b02553c6fc2b4086bbe3608ad6d4f0222e4a65624664573fff01a9720630994fa37b1f54103c946d17a22434074d322a812ac91d879790f008bdcc64e976b7d8ba72649323ce0d99f4fe4a891d62dac53bc3e41f51764eac9c78a6129732f0b566b81cbedf4a44010380ac6e780ba29190c68bd81c3b7611a3bf401480da7c186c6e3828bc2b75dd5a1089a896484589e4f140c52d2216e9ddc60a72ca51c36cfc6715bbb80e0af7131dd6c317f09c34a8ea5cd2875ab78233451972496a00c124eb9bbf81ecb283ddb1da8927cc1246818d6b2d97756d1682969b3f3f401bc97ad343db4bd5e4a630b3a0ea532e6a0556e35126ba1e96185f83f772fe557219141985cecccfbd60c4aea121b0e3a5310885be9efd9ec5efa0a35072503f1992d410ab2159fde1176884977fc7555113f313a38e54ff2220a278f672e2cdd1d36fd2bfe0ad8e92ec20c0d9d57f346855c4dcbe93805d9c39d5e88852a8e3e311a0138f23072bbf457431f533935f09f8e7a1cf2384e87bfbc04ff072743e2cb2ba3d7fe73ce03345f4d8667de00b9d27b1dae7e2ec371c8bc02e47720871dce6750d7dfea8f5283a20ff0f9385b8963f6192796d0876b76503b8a4727f638085fc58ae378c072ca81ba9aa489dde6ee5d616ddf26ce93568e2bfa3630e8ef5c5e10a4f7068573cd82438aba3944ed33220b8f75af39cb8f83dfa28670652d653081f81401e10793baa539bbb75b5988d6d8014084e7acfeadaab5172eb0c79e068bb8d8741729315897d023e1b66ff2e6a3ccf770012724c544f047234b394a4a15811bf3f3f45d0a2a2fc2abd4e4c533ec67ebd872e0cb42c601e725f8215c64a7578b9fe0dc8e08c095a4d7f1927718adabd42859d0504604af86056c6ef8fdf78f5567ce68ce237676d5e258592b687e0ed187854d6975ef61472020b296cc1f5a5883bcf4c8d0ac7b95a0a3df24c8b036fbe0bdb5ff08235c372e0c7b8dd144b578647873936f710a095c217788a4de20c76988bea43cc52ea146b0ebdcc39c3d50b7d54e3ed278936c7156064241efb722d4c7cdcc49fd64572c1c09150d79c504fd9ea1ec8d93921fb6406d8fcfad611d6ff9ac4ca21b56572a23d7035826e81bdf64de14e97300ffb0ef390d8a2edd80ab0ddfe20dafa8f25723333e0b084515c914bcc119b2da97c9881edabd8721f3cededf47f7abfc538e08b8d32acabf808078ea33275be2232721f15965f2cbf6357d24ed764d3d6662d2e6cea91b446d76889bfd838761918b1f599db1864158fcfa1b94fc7c7262161852fe8fd72bacdadebc752d9d116fd191fddc7afbff078117a0a19f47b6972c978f340ffe607606053e6d7513c09192224436f2c4404aaafa68ddcacd724724aa0c7764b35ad2d2e3260668bd994c8b9e3ca7681a11bd8459ffdabe8496d7234280b61a1316899bb941405ebe139a4cbb0d564dfe2b2983cc7472af77bad727e15e736c8d21f666e9f179e86a528e82f86b7c83bbd761cd7ffb5d9518ce05651d9618bcd2a226f391ca88ff661598412d357dabfb77bd20fb6534167f0b20b1ca3278c85862196aaf455452dd43e78ed9f8ef8e6cae7bebca342b9bf8161508841cba4d44447a61f668ea5923aada136a69b419fca8c676f518b779ba035721bef8000f03b2189e75619179d10aa8879cd0b6aa6007926ef25350ba3ed050a5697ea9e3b5ff99f9eaf02ffeef1b50d512342cced82dca2d4e4ee49e9976272e9ffeb75be81e5dfbaa1b1e577e2b4e08f50f79bbbd85ade49fe6ab2d9e9f7573d7a08c72afc637c7cdf94e984603a36dab5ad748e7921b356097d8d097f9b13148ff2391cf642e362e3b7d2c8f1de3688df7ce74fb6069a8c059ec409596572ae3bd345f052f6a0dd067186b95295cbc4306a308c7f3be506fdde8dadf9a97251927a6f5628ae92c02cc9e54d8a56c224268f6811bcd82eeeb1dcc75dd34771db3cd79f004acce0b918672e584513c046aa3da0070a0129d46fab6bcf4508330176d438d69a757193417c721504c46f2c459ef62785bacef4f01e109653cc3a2b43131d575b278f18a677ea871bf9fc948f2ab97bd8e09c582ee49645defd72cc4b1dbe6f49368ee50f353211549db04fe3344542fb23b13c4929873d4edc7226f4f9076c12839d7b9745aaba86758242f5b775b06a0f36db5cfdd9518d0972e90c9ea991bd53e5c7d4ca0b27e6d08ef4431f9a0eaff6215365060e770bdd3c07f36d2cf00a7bc1197337e546e361c4ab01644f408121488911a040b4f0103d7980a625d83cbddd058968cc7243671c53e1fe347aaa398f73604643a9f6651a985f10a96a6e341a6f85f810f6b39d4bfb096217c339e6b8ed56b2f212d1ed721b0832a581d619cf62cdfa1248f9588c88412c8294f957cb7513df9004b7365ddd88cf1a8094dc83e747c0eb1903eed02bca8b1ed7e55e1ca336cfe13f637b7281a3cad0f778eb35cde583a59b437cebb20ad3ed3c38abf693d518e864c36f344f4317f39b622224de63990eb67d88536314b63216ea87dff4edf459ad34dd12f38cb1fb06f8830b0abb9997bf889ec3bc1c2d811187b1a0e51a728d08cad10cf1822de05aa76deeaf3dd45ff38e47b35f1e8fcc55571bc7e89f57370bf7f56ab23c3e4ada701919f9d6995836c27552738615c34ac56b29aacda10bbb5da132af4b8a0bc832e32b37bb8f3cb5bb667c104eb0f845fdd084ef8922888e7b1e727631068b96220a81280001abdcf86d4fa717416ede99712721198e7aed4c51723267dd1c92516910aa2ddc66bda0cd46e3f701f4962c49190302b6d34053c10ab7980507f7edc9f521a2be61d01f221adda94ae0b8f4541bb533a03a8b036472acac083e1bc7e8bd65e6039dce341d975ef31f404e3a74eab62918635f0ff772f6818add0c6de88b309c3dcda01d820c4a81cac06257738d5552c99975337972b7f5d123f4a42954458bdbd685b3a862d3d9a3a1dd1cc898dd1a6cfa096af97209ab7f13bed03e5d7fabe606fc7bd9db79e4c55e174bb634e3c243db398be808ed1447475085cf1be227fe413b06e367e08e0c8369aa8d9792f54337340a33727d566d7ae8a8505ed3e4413532e8881a47cbb0d0167c4fde96d2e0ffd597997207ee06b74e7157d4aa53a4ac9b35005857506c327ce230f84219e4b2d84d83729c6e65d8819bcd39e24ca745768ad697667321fdb008642969facac50ba0b0615dce714dfb39b1b930278912b3869e1f37e79035aafd71d1fb6e5afccb7cf609cc179b2d37e43e551e96ca6b20daf4b924229002e8953739d0a1c4f03da52565505baeebba916ab444548c75bb5800d2bedddda5b9baccbd50bbee7860073d164bd3bbd3c4f451177047c25cd55b3c9735e13284867ed5be88e01832d1c0a04b3a450b990194398d218a8feb054b6adb0d424fee7c076ced60383e4efa079b7200cc3e60e75ee72c257c268ebce11d0bba14e01201617542f8b324bb7b13c31823b813f518f969cbeeac5d35595e2d2668747b263d8c3e14a4257eb28009266a6d78def687a0828ed636c920c7afbbdbf4575e437f8ef2e7fe17b3f3353ccc7281a738400b439a19a0048d6689d98bf0024c0839872fa74bc60f651fa13a6b728204cff2d63aa349858dbe9929ae5fee3dace9638c2c339b21ce72b244a26172fbd0c72dab37d1eb0a1e16e3857f8a27df162a2e10b255b003030c7913630172cd2fd994844da1536f0396d4d1e95ddc917b36b9bb39261b8d9da2e10c9d687285eafa810811cea40288f0191dd8654411d1a19748b5b1d7f965944d6802e565544018a04947ec9d935c400884a01453a688537e4d6c5dc4186ca46b87a132722186063c5e78bd28fb13eef8ea084a911ca62b94dd9134875037f36069ddb87283d14d81d9235306b222b903602253e64a56735c0f70fb049df6b1b9cdff9a723b5cd2480cf80e594c8ba640540e24e2bff320b7c3b4d56e2fbd231cf8fe1d726f078eee5afcdd54eaa1022492409241f7d2b9ebc2ee9efd0d34da591029847228f6bfc41099ace1066ce6fcff0a11ca3aeae34155877d56f05687984f35fc7250feed43902eea0e25b0442a4d7bb4f8ed9e8cf8839c2c9fe4142872ed1978653d08ad2004e55d31c1373bf681c075b3540d752186ec4d90b0d39183dfe22c279439e4c7f4211da50bc91a632959ed7112c8dfc5479dedb2cd2df291c9aefb7242dc1ccd16f230d01578951c12b430921329f4b2bad330564cb9a39316b4617223c6ee1c03d1de5f8374e56f23686380bf4b0ed2aa43c65636ad0f23c05fd9642cfad7f61d2feb1e6244442e86d86fb827fac6ba00271543993bf0eb1e2445665514df7f31e0c01e9c6958b8233cc44ef379785e3ae0997ff638f1dd00b7f372138bb87d192a6fa60a18285c07c4d5066c568bd06d1cf5658cb184a413e63372df4061536f22eb6f7cdee55d6b9c851fd5f90028dcfca277a0e276898846ed15cc129e56b3f15eb354c62ae86144ff7d8277423b54e05e0aaecb6718fbc45a725a6eaf5c2238fb90b0479d2e29b2f314c8f61fb03a047efc3db59c9110a8251db74f561b66165cb34c91933bda9508016dfdb4b4221c8b262f466066defa1a35a7b965a5a5339226029dba68d0fd0d9cda67cae35ecd193a05579559d8964a72304828b5a64d2438acfe931a14f1d1b3fbeedad1b7808f1dd86be03e7209cb6580ad66e2ef9d419db05e201ffae32834dc00648b16147eb94bd0325a09514b72802f69979c9cc013472174b757926ea8772a8fe063dec4c64f147d166b7cb872f5231a6889f9c1d5c82aa0b8615c121917f0e14aa264e2e8655f2bc034e1af72b73cc698b01ded0dd68fac21970917eb7e923c5602c165610c1923098f28d872e6ecc9138a038f7a1a492979a720739f423e69724b97f89e41f1bfe516dddf3b71da47aa4eb20ec4dfcce1c2013d9ad7515181ca76d97c185a1cc6589a2bfd31de71285f344b98745c835db38e7994b254d0ce850293fb885f9eca108d517c2f6b25c114a1678a97913da5a105a3a76f06bfd4a19ce8ba5a854536776b797f09314bf58fa84fdf89d51e8fa787e685d6240e86180621bd36195b13744daeb872d8bea6ec11e43552a792fda3d434eb116749aa37d1395ac741c4975582d9a9728d3503afc23bad6b1f777e31e644d684fd2c61c3bbd4f1d1ca61dc250867cc720234fbda804da13e1ab86f04b9c2474a3788e026a862b9d36b01d398a4a35f728f775db170a516a4d5f446106b62a9f89a4c5e5a54a156086971c0c887505b37466300b4385954c5a34ee1f08a155f6166c905f9378d71df454341ba7ec5b645a32b69c81d5931ef14d15c439a9cce55654870988f21116ba8a392bc58a8fa72a5aca27528e2a4ff63ed6a41bf200e3f188efa90276560acd632ff4da8486c05e60926b002314f3e728478a10c1e56d2128c032929de12d2ef72f4a900b41f313cf72349f1618a62a27f4a691006d600a81f39f061d905f9f8cf105d9665bf72de91f8fe5dfda955932ee980fdd7df174524d647cfe4f87f310b886c867864729594d1a372a68521d016c862f99226b5905f71764c84362b140d38892aa6e4724a9e37659584f074bccbc0316abf98d8dce18a55cfe885fae896c796b438b55da030df9509e54bfc0b40dc88f1b979da8e6a548b29f27632cfde8d5d85e09305809b008746356283a0271b84eba826f2758010e5b78e7b517fb9f0e6a77b4544605a3bdc49ac4a2ba789cbda2a07e5267c6927c1dba1944d8ed01846b2aafa727c0939c8518f20786c4934b65f6cafdeb01cda2cd6c1cb3edd68d27fd669a70b2b404e557783bc122156945d9c619c1f8489df33c4c64d254b03457fb4f406723c28af2e8482a6641fbba588bab1cbbcb0e7ae2abc9857710192807cea6b647282322b2845cf36bb341835f35cdb5bd5a80d22b8dde30ff28957fb5820530172755f38c8d739053f56dd50cf396ee6af4f77169f87476ab75393ce68a5f87b72fd1a5909fc2e2e7286a823f25276ce7efd0fcf1a18e298afe213caacaff89972e516826411f1c2f94484479583e08b00c5fdbe858c49aa5716621cb83ba32a72a10a5bd6b208d22835e72c784a1ce81161ca461d3725daf87517b4b96884e52649b251c56beb8434c6cbe8bda7b2baa3590d75ad530a65e73ac2dd3f7dd7b43fd86e473740a100aa4592dbac8e04ef79de8302f73586b067cb56eaa8d84764021c909d3c33dbd3bdc2aec0d4883222dfecf2bef7b5ef0819f6124cc39ac98d7204d3800228b20bff04eb70ca51bf9833369d5d019e6fc100e58f66214d86833033dd2922276d021e5d05ed3d4fed35b1232b00af7181b1f078e5362eb6171872b300a8a8e4c7870c6374a2e0452e5c6b3611c8de5f67981b4765b21f1b059d72eff49c87d6857222f2047f28e723034ab7de36886a37832a774f53dacb874f4a5f01462ead7a107aa94a24708bd27a017476accb7f8ea16c539d4fbf5466172ba85a2963250e1d9c16ed34d4a2a27286bb5acdb7909fa7c8adb9768233f2ce724be4a58320692f71f400a767ca1f69f345e0f66409586e9d09231feae30cc91a867c84341e54a587f5bc3fd50cdde74f6444ad65a2a4777ad8af48ab575c30475b40b1ee18b0f3e647efaec381715695ffc1e3c8e8cf01d84e10fda8c6832272495fda1285e8bd8f22d67a4e8ea3ee8c318f0784eee6b4289e1dc25757adc17295abac8d60e3f1ae1bc001cacc9310ca7b6d1d8efa84d80563a4c7bf9819397207f1fbc2d0b257aeaf1ccd524e8d6a1bb4a02bd8e16595f83599b63f22bbe114653930d782c64ffa3d22598f01645ed4dde574e2c93b3f1ccb3cd7640e1de27285818b7f4fa418090ba7ceaa58d45c7aeef4b24cd08eb708b9199e71ac1f5e7269d7ae2bbc18cc932d8482de6e6037d1655e562691b155645c3126c7e450bd720ad089b605c206c10756d6345bfbddd0ede44255cfbd2b303ecfd301f23fd60c9fa0a53384e4a117b7efadda5afe7360a36e990c9deb4e88a3fa4e3d0650617220260ed9dcb35a9a64e50698d7d426f6657252771f409d7a0c72c2cff0695a72bb1d36aeca09b386f53a1c19ae66509153e45a9866fa5b11c4e6481d987e733477342a0e8fae8370bcbb488c64a384750276e9b33a3612105d0f4878dbc08272f1bf3701f92da5b77b8e0d1d8017c105d84000366784870fe257bf71be6c0972c96e3ba6de11d2eeffe85728e903e76c464d0e5f0a5ab1467ff109d89958f072747f0f7327304510e4ace3e0c5b53c8965b10b2a161cd9465d8d77195bbc5672056f47cf5800285ee6131efd0f3c85db86b8e36d3d3b459c44cf8fec6179c57227e32ad206fb10dc3c6c2a4a95d3a28976a57843274c887665be7e61a07ef918b1bade8d643ec6083c016d5df6d59404ac8b6ad7fedc4b48ba75fe1f06d2020c5fb23d9772d7e9794108ccc999a70cc7814b6150336dd4e6682fd5ff52f0eb72cb01f64fd35196932f626848641fab6e814a4daf9a2d8d8078510c8789d68272a4709b0a5dedd7634d36db6022ab7cf801be33d7494b06c89239bace70246751ce88bd0b30a23e54d68b727e16ba7a8229bc25072e95ede8fd1ad1651da66b4f4253bdff3e7191cd84e58622ecfe33c5106d164dc1125d3ffe9c79fbda525972f5e6297c0e0b953f31be702f63c937158b018b99f51ceebd9ea0485a2087c90812b11a64a3a3375b8e9b963dfa9a0ec9a25a98cc026b080f3834e43cd344140e90fdace84cf0a0e66d95985f6827e0c44bf07e676b2350087783b666074f5d720614858dc42eb7ed3a30f794dbb74a6563403d635ebda40a22eb2f31b227d872e06fab5377768297144c5c0168a9ec749b3feffd50335c7305665c1ce6119749b06bbd34da2fcd3fa95dda313262bfb506e53ce3bd361425a63c97bd14996072660f5a5b9b58d7ddac820e4129107e13dbba821d761323efb40d5a3d21ecfc5851fa17492fd3fb53e3e9d1cc56375bd95e5b2d59d633adf7107111cc8fcf435dadd11f00c06ff1ae4e3db686aef7e80aca1a725e8358e624627926d70956647206c22ddc9b1857246881ad676acbcdcec971b75578048605c93bef670d3bd87208526c249a48a0b0f16cf78febf6e55af0638eac1b1775fa42484d1e14e0c545221edaa5fd7d31bbadcc5233473065ca845e1702df155d9dc06fd18785a2e67228a43b32c035556a466db44e57a3f523665be418c60545132740fcb594e77c7205cc76d97d11d84e026f058e5c3c75925eb0f91e73f79a3ff60698d515e56d72b07ade68309d3b0c398b8c77e9018ed3beb2e90f696c0df25a172653bdf36172c3f037a1598bba105822495f07fec7cf621b24d02c792fe1daa154b679d87d7265ea81affe0de28e80f98db473d70485c6f715a1143429e9911be5c59cfdc14d94a35b8bfb8169300f496802b837c29f6827dd41eac9e868a1ea00cb0b091e43697d7cd77f08b7f72cf0b19a964d3d221f4bb10f325cf430352d69a1af1cf272153fab461cebb1c5cb7109391e90f12e07410671c3a0654067ed2c3e56a6534ddb9495017dd06430c357f0f49959db5445b327f0e8bb132f58f5d59f74436950080272a3f9389198bc8b59f9f4760fb13c1cd9637c93fbcb57c330ccb72b835c3b5c3f149b190c984e19f4280707ca9bf63a6c5b0ece1f815b550015ba7db67231a25633b6b621d99438b85cf3d5594e890c0b66dba817674afe2f1b369cd73a4cdfab6e08fca91c92885fb1a8f70e56417bbb55dcfd6a63f76655d26505f92cf7bada7f65c48ea720e4ac36063d939e1ba7baf0c416be54fb51aa5396fb92551d38159d1568f298e1102cc7a7100d46eb0903712b071d4c4904c72dbaedb572f5a683cb34417bd5f55eeefeeceb9962d127697dbdf0fd2b4710624f1302171e95187ab00615c7d1df9a02a165ba5405c80b95e30f15db7920108cc8cc7ffd52f9cf6f42dae71c6834a40d7c193bbd1007db2dc7bc8e03313b908fe42b1abd0eeab9788e8c8020f229b784a6a4e1f25714885115564b691fdf5b2795d396417271a996d2060033f9e3efa36d0619a41fac1ddb668cde5f8d0c2bf08891431e72e99b851e5a266d781b348b354e460bb59200b6dcb212b01c49954337863213725f81f2851edae43fd162e16cc8f7438c0eb7087305763e3986afa35109b39e5bb5ac31a7b04418ec77d54e4420e2ede6019556330f7a364a3f1732c4d1c2134c23e2ae111e9c68d4d332b8ae752ea8021be04b4b1900d58b402202d3521f377217b1086a3d9910799063bc1bab05e17e3dd28246d8ce6e9c2f7a6d72e2840472abf4d746019a6a307bd9ad4cc15156ecccac72f1c059be805cb636d35f63354cfd9345e81d877bde03b07b94bdc7a89be8dbe03821a699bbcd98c86201ee9172935384e14674bf4a8fe52408afd20ef36997bc84f5e0a3981cc9d5966699c172d3dee86f86967a41dca23e45def8d2f45ec15d9c8a6af40447dad2dfff565d72b112da38248373af95cbf802506b5cb528bf3f6c4a0fd7a4c7474fe632f80c72bd0df6a9ddf4287b172c312943b54d7fdc8b73ee4b7e8bcbf8f8b52d5796d3600e0d0af6fd7aded2a5cf761c687c782e07de77bcedc4178abc21ddf0d612d67259997fb63d86434c8bacbdf15fd383487afe25e82fc5f4bf3188d82f415dd512501ac66fb15fa40973e2d3689c72c37d6eae606b363f916a3b247106b311e572db37f633ae52de1a9bba3449bcaac6257e068611cdf62fe3a9681992e8b3672433fe5e395ea6fefc6622eaa5d5137dd0c025770fabe51b4fca0935a65621b933581233bbdea35e60808a36ed441a8213f9955938f9879d8044f4fbb3abcc1c72bb17ab87e7afa3f831eddf1a3e6b4a9406e17524be7a2ed3d8e31c288fec6e306d981bc79faeffa84ee6ed86702da4cf81037761d4b2684779ec4b9d8b95f772310f6059d6ea568e8217c85ef468bfcb8eaf751d76efda29c745673dd1350672e1addcfec5c4d834dd419c2556fe106b71857873bc6438053a573852450cb472abe0f1d32c091c4939ad5066fb833c3b293794f859d164a1c210e85bf7989e7271c604089999672acf1f417aa1d3bb8056d15ffc05d521b9c12fca09b1cde026667e5dce56ba7fa8a4ffe505225ffbf8bd7f4fc801a889fc585395dd07b14672205c1a4d390184a2728f17d3d7e4d8c6271b1920b0827eb5250b48b6e4bb6b724ed6df6e19be9cce4b088fdcccea0f399165e505b8c756a7e9effbd91454225f3e48fbd2c8141a4daec1b3fd548a354495b0f555bb53d4b02a1ec883c45eac72c2583325d3fe07ce936770af807916094208dbc5e7e07109dd163cf7ee8b6e723dd83c4f528b2240a6371f2bf5a387dd75138cce7515a9e1f73331cd9f0925726914f24bac50fd5f36c383dff8544dc3b6be91bb3c8118baeaf8dcfcd144db7248050b722b431ba8cca5e68147b631f5aef9164ecb69a17e35ef4c9975af87722a3dedced530b76eadfc3ebff3b12993501b619e9274887d4a585600498ec67289e3ccb06056dd7fdbc842cd04a6a3f8de61427d90eb54fcadaf0cbd54891a1c541d2a30e2ef7c6b42af41c0a7b1660be6b5d6f391938ae7fe8729eb1f5fda0c3317c2e4dcb87a1d08edecdb22befb99da6aefc565c0ac3c961f9e016b1d6d725054c80ed8f829edf4070653b91a36f4157cdad5cc90a33718ae5a96bbda87722d0e1fbd45745344955dde6238e0f311b502a277c3957f3e2d8197313e3c7f5533b1866abe3c42cbaa0a6c71d4976ef7769b43a9a84722efa1d4276363f9cb43c0ec22dec6998ace0e3034a6189fc4fc533d138640b70b834b18a3f3337cda724bbed809bb3041d98d57f1a1adabb4b7eed3bcae4328492752bae11e4c14b97223488481b10fa18e6db35d0eb103aea5298e93302fa0c7d96f1263795df2d47271ff57fd3fde0f5bd5be007aeff94a287cccbaed9335096544312ed21f87b17241b0c9f1b051f2949ab093af222ffa90c93612fdd7865818e2050ab220b0015aadb375ab1bf1ae380d03c7af4fbb199681d84ded4e920072bb2fc66d9b6cc8728ed6707d9a493ec5c3f7367cba9940df33c9060adb571e7a8bad60909959f514974ec04600bf5742588f97c58fe9bb9f41acca2c3e323061ff769c0d804e1a724e64ba6e6bf5e5fffe6eb6fe92b4958f8905058ea561343ef48358885f5f0e1b7201aa94bc0d627489d9e700f3444d454aa99e7c1fe22307402fd652a56a11729011b4f427631b95c0cc53df6bbe491a13affbbda19f463a2b4d3861fd230b72422ef6c80db74df3df08f5ea1b322d66aa242b5349e4a2b595ee98a1bcbd070766292548b865fa6816e122ce95790513c6509feed3b5a3d392a0b32301824e312213a4e1e903d543cf66bf5ff719fdb25d1661d8ca31f0537c10da2ae4f5765871e14d0bcaab43dd4f94e6098bebfbde01e34e5df47f8f2c6f17ff6993057e521e1c1614940ccacac6f34a61146303b8aa5cef7269eb800c5654cac6811aee517cb5e53765c73bf2766d390c6136c8f1af2fc66aff0b3f5e4fefa1bfc60eda7213154f9474ea275ea5772a90bcbd4058045e560f29526b5eba6dd2697c829f3b9e62690a0e1013d68c2553f0a80b5958187e684a1752805a9da5e8c86b9e695f22b77b4619eaaa079838a3af228e59b9f2f61334ddc93b16cf99b58c3846b7083396209bdddbb100216828a841d0a178c9dce6cfc59464ece0f7a859455b8c3270e6426155f653a5830ee626423f952c336ad33ae0566d6ee0ba47489d3c5537ee03f769a4a392da82c5daa6a5a1e61155df440bac5e3a3704f04a2fdc87464a9c61a011f2c12b050e969433f8b8a2ea9f71f76a85d7a287538536d24270bc22a08a570296618200e072adce2dc4f30c58cdbba87b1ad1210936f8f28af67a72a6b20386298e521bd497511bbd5faa8950b99389397e3c071583db7f08726c724cd0453b6965f86f9fa6fede5bf2aa710ff31dea55340588330fd5924138ef5edfb4181298b5aa58a9f378432591b2a8923b49671a1cca462509d090125c567217475efd0ebf09a93fda324ced30d8067d17ebdf2b182a0bd400702eec601a5965ff0a1209062530a78d4c5daa29f2006ded1667c6aed25d3f477b99ca691c72306ff00587bb7ba991258305729c099e996707aa98e7fcfa43749bcafe0a0572b015a05916c8dd0c913b2928cb6e7ba1ced50d85c4b0d75efc291deaf7f3407259ecdf1ca81a284de4d1e268711b0428e8cd0eaa6966411d1b15e6aa583e74722c96bc258727a028765485a11f41bb440470382f50849f3b55915a421249d372032de316bf731f6269a8a8fa58fe7032d2da3fd2776396c59afa4238e94c662312de46a49db9bf9393a086b9758b94a046c4e9afa721a5c9bab461cd9d6e7c7260f0a751257c863784175330764453dda0009f09f573a4399687d9296adac372fe4dc5f4202d421bac6e214d5227c692062bb3f66b0068fd94d0185ab9ac6c72d3f44f0e47e1ac605afe5474db77bd28cb3880143f7bd2a992f30dda0dff955bf016276cb837224d6dc787956eb8172013525b7da53dcd7f50cb5b636fa470059a6dda11218a0f66d61164fc9c48f8b80ed508bb537ff353bf43facafd93516c205046efa4a492f5cebb7255b6095933be3e76234064b1770c79fbc6e8792b7283d398529c751810737a93bc1cc90eb9a6479fff0228b387462270ecb62b935484d4770a29c4cc1f58cde6836b69f7c3b426b12a807b164755099b45abd46c0434dd4a1e7addd8ba92a23c2bfead8572619c523352a5afb9feeecffd3ff15f72ef37062e70445239d0f28783ea9909bb8dca1ec88fa9524a77336bc6035161728181aabf77a7ff83a33bb44c5d9a7e8f082b3b0e823af1326cb0635a7253d172877419f771e56001d84d39956e1859452e4ac3094cf1c383f4a141a705edf1727c42021ef0fc533ad67634966f8ba9e16d47b76cbdb1364af92b8c477625f70c9621e54e9373fa13660d877ea028af31a7a02f6610682a96930461115528d54b72c1e4e92d7588e76221ed4c789e64493a934b60de789973b63e1086b3cb1f36d908773234f520995dec17f165995e4e8948201c4e096de0fbc7c3333d506b7267daddea0f1acd352e51d82208feda68ff939d8938a7df0c6c52b732da89bb724b01b0d4dab92bc411ceb78bfed35be4e0fea5adbe5dfe7f95233fb78b3b8336a734ad102098865f4df42535150b00f84fd35dada1f4e480d7274f0948ceb60c4ff196f3ed96085ca5c4c318f41a97511b8c6f573febb73f31057a3370f51f4bb89febf9de435c98f5d2893ad6d372b6406059c0de4f0fe34c3070162e9bea44b7d9f7df974856097d2378fb4d26242787e443a9d069848bcde011fbbd31bd3cf782e288124e878b4368d57e80221d95d700cb376af2c2800e01b617a51e9900bac1483b3dc2c131a9fea2d54dcfb9958737780eda995dc73bc69d14ca972140d71a4438e4e3e42689cf12358618a58412eb7195e713557c3b6000c04dcc7c72026c045574bf7e525639a606f2edca081449835829f80c444ffc2355ad6cc9326a7e9171fa9a5d7a464fc2e1b75f5c50c0b09aeaf721a7e26083de9a10d46532486c9b5c41e3c56b6a3d83c1cc1f445bed0b32de9c3c522132cd59692371721c0ca4a678a8b60174a229c6fa644076ec2ab2c4e5a41e013219b44ff23b490d72d2d05b4efb17c9a87517ba9f822462df9e8134c416cd44bd6082e57090ab4b7288095bc01d56dd1ef80930d26763bcae2f972da003a8b32e40e84fe086606c32ae9e4d613eccfcb4692b5311a33738ab49d93cc689c196d5bedf4e03608cb272c71d2aad8e4f5386ff937b38748da061b9ef7ae5095f1a09e93b00d9d2d1a6444bd4d62d5089de1da92c5c53f2e1232f149c199ddd7ba02b6af00cb8ee6efa7245b2de635e77ff0a519e0003da69749ead5d3a25e1e3d2f3e9778a25b353732f424d31dec027907b7dbcc25641abcb524d70a480d7405f9b36f79dbe895cdb72ef8988486abede1d2997e4dbfa609088a386de938884eb681de7aabb0d893a72946308452fe02f679190460c36d31bce7a7f9c7c88c8ba661daf035e7e560350c02cb1c1e85e0f5f34a95fc0f542200403c44788e24bcaaa68372795f4f422722a32fae6ec45c1829f68844561f26f7fdf69b332c7e738402e747c1f855f9572e57831f8b1c7c31adb452daf2f458cda206fffd35d214efb7e7f0b177b26b1015e913d378d2db49ad366235f767521505bc607fe4408b00cbbd19efc42a53202b9d8b0fe1c0dc3ef76055c4272902e441198ea3714b5d8c56b30b63c5cef807273a4c6e123e6d2ea06f7caac9bf4411ce8342e61a5a4da4b999d5e2e00b23872eb86d6b8845127fa42dc0d6b24935068b65c3532cc8b7e08f20f3505839276235b24f22606ef0fdd01672c62a1356fbf42dbc76b3bea74108eef9deba36513727b5c0a35f57443c126b8fb65dfdd97d217964f90f1282e20195924f5d4b6b4722979fa784bee0bdcba22c63f465081e2608661cf53a7c66d97cd033f70d1823d52719ca0cef1091e45554e7b3955136d85569c301568eb0395fd461518db5c0c01d61a23111084d0524ab1439cb6fd6ed0dd689412ace7e48ceaf52852dd45720d485099124ff1f2bb35b3f9305ff0188b112ace7c6efa9d71367a014fab9372f703c34cd8826e231599a23887aac903dbd89b033ec99f20d318ba8edf036c5a5050ab0dbbc8ec15d3386f239d96b89a5e24678080e3475588839d5ea6f4771596522ca721052674e4b3d2e0bf034924ebd35ea9351c1fe111d0c0f7d55fef727223631cb1468bc09a1de920dca1b15eb27a47cd17b0b4f7d3f15ee8a863af662ac6dbc0b09d2056f7a0a221e2b92adf727fa0f55dc36d0561bbe6f3fb793172c33a71fe7b922a326b3ea457ccff4b4cecfb9f64a77b89406a8bf4e51b0c7d72cfc2a53813020594fb768f8234658180cad15e1ff5471d7e270448840d7abe7277933976dfe30ea569df7c6fcaeb6ec47433004c61e6855cfbe92b76c1971918bc39561c1c2d78403d977e054dc38c7e8e763871b634c2d6ccd58b83c8067466946596bd9910fe9a9c1aca3bc25f755277172750b23df9bee001deae88166a72bf1ae308f7de2b84733d1adf84f8d743b8eb3716a5558e6abd893a9c1fb85a38980c5243503e6d2cb12d6851effca5a06531b9bf7656298aeac2dff98ca16f724d2b67e9e8a00efade0a403c2497be2f4ce1be53cad20b1ce051a0abdbd63015d462e777df1402fd4c7483b389fb478efa998344e9717d24b3e6f247d95560727c5a6466cecaddef77644a6145355afec5f1b06db8be4781a17a39cefaeb4372a4eb3b7241a74364c823a190f235192dce8b6c1f2bc1e1ea5f656f58cce1b6726d6f3f48db9cd11fe4899de46988b32d61bf1dcd3b3f9bdbca634112582fbf68ac18fd1efed01911a0c37688fdeea15edcf69d33574f262e7fbd528549c10672f4783d375fab2c235378234f635f098b46527386d856b069cd8c48c2fc477247aeca745eb15b19d93881932ae85128b3f2bbcfb0742ba95042eacbc0d40c5118f8762a9355b41039402a2d871eebf1f0134e6ea06d9ffb94a673a3901dc45a1f105416a89ac3e68e9102c074eba5155c3cf0c7c5e236b24138c020e4b46d32128d11863595905e787c245700a04f9c7df59c1636072465896a59557afa153c6a2029b99206fcdb83ca340f36ba9b10fcc6aadadb6eda2c71b9ddd87709f0652eb27a17d580984ad216674a8c0f67e9379771ae87df3b05070fb57a109c64e272874facb9c93f12778d9d89b0c1deee488b297626a7a0cef72ab740686881ec72009dff138150159d731800420c10991aad05510e923ebfd74662efe9dcfbbf728e12f119a11cc05a564fc0429531f6ac084df7f8a8dad01e64f66c7c6dd85139085325978d65e78ce252ade46c9c995a7a81d603e89c9d75f20c12f513766472512a428881276db9519fbdaaf9abf63b2ff60da9c51ec3c9ff428e60423b6b72a87e156271a60d94320239e039e5cf146990c64dd5597b2140b9ad6d3cd90b720335dcbfb990db08ef7845866a376c88f677aaba0803aa4390f80fb7e836f11ddbf4ef8c672b2a876a3cb4c06612d1ba4dde8017a4d178e41b1c23b7ed51f028ee9665d7e89715c7a4d531ebc62f8d83f2d11351324d2e127637e67caf88c772b87f59534ff40753ae1e9ee4f5cc874a9feb57c3e4935212be3abcf805aa93723c14dd732af193afc233db0cd0886252bffb8cc321ef67db495527395338a370d2a01b92682dedc7183e4641049d3fe11214b2ac9824db49169d38ccc99470721a6d781b58fa1dba9102cf7e509d8d641f39468ab4db776549cce8e1e8d2e772b621cb22d2ac25f824c6f69177a5ea1797aae8907c4820baf5b782ed078942677c62fdc0fade5db2d9e75753c8fb2e8d3bdcd16d052138cb0cab0b6fa2334d0516cb22cb215d731237f89cb97edf732df9df4965a28460f9fe85261cf962c0148bb73fc2932423ff4215434fe588b09f2ae2b7e16f00167ba9db067c0c96587278f0efbfc8971675abf90dd54a70224426fff62116b2e295d11c56c709f00748effbe9a03468a4b04e881e13765807c1a8a32566ab4e205aaf427d1fec79684c4fc573b3dc4011ea3acf9ceaa10a9b9e5817134e97a771ff4087a4e163c09272b39e682aec34329736080a524d4bcc1585831570412f6081c0b619532872df72ac297acde89e3fbda62649c248ad487f21042afced58fd58c542f6af6e04b2726d2203b62f6787912bc941cad78f948bc2f73bb3acd5f1798eef71f5ba1fbd6d84d3824a0c8b9cd05e92baded87a10d8d7a9424a0b07c0adca1fbdcea5ff4a72768f4a401ff293d023cbff10419351e954e6696efbe86728e2d8703c8dcad272d4e17fb2140c78e62354c916156d29e7863e7ff63f3d1857639136afd55d0d72d26c1674e32ad53c34e8953c3b06e30c4252da99efb4315da9a417a7ff6c7472bdadb1d53928e7aca408922d6b1b7f7fa91331c7ce73f6072d2ce1426f3dcd21bfda2021c901258176be61ce7717a6716af4f6993759eff1479299a6f5b6cc7218dc96981b44500a00d32b7f35b51195ce0649a829d9ae4ce810f9b2fdbeb4728797bd3f5d072e968623e046c729d3a39fbbd0a3f98a586a0e97db8b11d0176795564f94e7f3de480cb30fa675bce9c6f963763254596dee5deaa30db5230972c1c42b5b2eed297b9970d93518a376932a93639b3fe67d3eb7e999129f36403421239e97f3bbbc58c85850e8aff4f9bd104a64cf2c588a5c714dcddd4899157284920db5e219c1959e8a2236cf12f8172cfe55dfb4b026bc6a9f0b081c567772d60f510b63e87f9f4c5dde4e2d3808012ea0a2fcecc0d9348261ed089f3f48729d509cc7ad4a9e0643f8d3dc9c3b856d89471228c4df084e99d6e878a3997321e06f712aee441c0f97b04fd74047da68361561ad2ca33da5bb487025497b7e72f5ad040886350fd77f93b3b723d327078a5c5563aaebabdc8abc635dd4ba4169d34e81be2779fd6faa4606a1436492321e6ae526dfc7c88a961470b6942d7f2fcef0cc8bd9e39e965ea740bab3aef49a71da1fff820c10523a431a1fb2e78c72e59937b846469a7c5441a311740b549ac15ea1a9ed08f2e756dc5da1f819d072b04c21c69a927d47cdae51ca435bfa492cc8f5bdc2d08e8e50fa214df8a0295dac9a8158c4e015dc7c3392136861357ab29026089166bb0b04a13d7172389b72092b4198dd481a85fa3a3945542873507f8a5c6210c2300d20e6e933f9843472ed7a1a71fe2e2e2d0ba1ab667f348c5016845b266eae772ebf4df05c0dc461377ca0145aa97531ccb4f1ce318e090d6a663c174b22cfec3661bbb309ab29507273b99d5ca51bc94bd71aee2b12c41cc5ed03a7b81bdd62b562380404a197657267e5f024439d1cbd57588f8cdd0a2cd04565ea42fa6554b45a6c2afdc54e762700a259f629a10ed6e95ce502d53dd89a7b39cc13f9d8ef1f748d5bf27ef7e8209fe1479e104ebea7b4a596057a775614762da5319f08ffd96905fd18e53bae0feac8e66bef22092ebabd642e41813d3ac9c562b5c2733f156d09021854f1d02f667c22681c91df97797ac10e2120b40d4ee142b385df5ade2f4b5533cdeffd7211bed3074d729c48d20b84e695bbea81ca832712137b203329b9a95db457b70abdbd71906fb84a49dd043d6d44d701c7f5e9d9afb8bba694b512f75da9599c1854b3c80cfd5fc163ddbd200e3dd2cfef3c9a95a763694d27d8cf73bbfcdc6f72104b67bc5b625f4854c500663936c71ff2078665350cf0543845c15a426a4c711994ff53a559fd68ca8de81d8b7cab40061f3f18593805b65bb0d51155a1755a2e64b5764ff8d851cc8cfd1059410df05b13bcd978d0b98cb99b95b61af9d972bfbdfc67cbb001853d09025feffff4852341446fa66a27aa24a7d2a2aff44572793f34bab590d02a33d0c033b42f1781940fffcadcf9b5889638d80910146949e02a89fd53477ad8228ae844a8b5a3722bc85b4463e3dd5c65c0f6d9a6595772bb761b881b52d532ec33f320cddd8bc73252242d329f79c1d7aa6efdad59747212f2ba8b4db127c0b94ec5574e5bb647fcbce6332c875621ad4e78a622abd272dd91b241f75be1d2986c210745d7310bb592bbba60cd044da5e343dce3685372af76fe0353ec703176cbc713f78f04894408a14aab3d606af77d0a4fc9bdce726ca382b287862e2d908013b39832eeeef3188ae4c7856ca5faf35e34137e3a0df5a44314f4d4040c660f69c4545d2b28f16af676f13d3ee7d53d8312b8469a72cd86335a1704339948ddf9d4d79ada3d277009ceda034463bfcdd1b50ce82372e589500558ca5da659c608c127e737c643717c8e5b6472ffc0c456fa14599872aeb28b07d88a7b342bf5b46c26aeabc746a7597b339b1c94d7a9d21d1ec0da37e49099c4c15d0cc1d82e7d4dd60b31a39ceb0e7be94733d8e69829fa28740272ff8494dffa721a147580a6c417eb1acb57f5eed22f7ad7aea17b50705cc48e5bec2cc8c7732ad31d04fd51f2f9ef1bed0a800f384902071241e99c5fb0967e72f43fc39507779aa04145610252c3e5318f617faf78ce2f584571f5f7a616d0209826255911442d1185b924eaace6a0b8ab73381fa20a8816a75fe24a89ddb3722c3a72cbca30294ff98e47c8c31562e29080c3f5dd3574f73e890d0d0a829d72126b1b0924974949ee969a207551e83ba08fad3d94201e6f9586fcdefef275590ab4d49c396c7af9f2cc6ebe2ba8e5e706aa42ef669ae4c307e6df9f33251d49e03f9e6ca407869f030cd2cc96079eb4bc2d746f6c8bcb740e27800d7b2005562a692ef54722123228d93f162af1ac49c926ea61aaefa7eb405ff69650e04872c0b659cd6e10be3fd112e05524bd277fd70ebb3f1c3d015557f329f183eaec676e08bcc75463a64740e91beaf6caba9c2a6cb632cfb9f8282011a668ab93a972216ad87833980fb00a3e9d8d5d35ad0e8feca71471f463e415c72ca622e36331ee1f928dcfb925dc58d72f0b87fee9a65fe01253d335cff03dcb22ccff9e4d276b69ed4beaa66e1ed45b242ba9d1c9fc8170142d88e9ba5e372c11aacd12456d4f7a3a05fb81980f00931d6333637de7db2d290365da3ea6aa28de62a2af25721567ceb7f5d255a19b2e83a5a962b48e959ad41f71b01f68906089f98e33ab3ce709add32abd21bd2bb29e6b339e9e821f3e01b05efda619477fa3e35e53fa72f26e442977458807ca7451bc0b6bbe63734df01ba4e781531831afa987b5217234ebf378f3b9e507fc3cc6d3856b2039bbd676cefd694af30de18a803eb46572c1f6ea0695ee470a724ef3596d4cb2b9c41549689b4ba4cd73838c497feb4a5951a4166e1f71339b282d7671f264e9ab222077bc4fb90ea9eff420d5f8f4076e0f9d15839ab7d1a57b0874e0989c4528258d7dcd7b95574c3342409f07b3e04db674011ddab3a91d17e87f27467609ec8ff07cebed6c1dee3665706280297831447caa48f23432f398780b5f994ffa4d4b1c0e37143e6c0b135a89229a3061727446ae7c564a9ae5cb09e1963ebd5955eaa1297df2472b087227ac091dd31872ecb1151f5e3dcd756108f370b556b50afa608799d855eb764c478e49916141727919c04828762b84b4c91f940fe0862fb9515e7dd48702f5ccb0fd3a0744512da92943efa31bde81708a83239493577df0690343e79c70abf723eb3250ed1b72915c5f69cf612fa0a876a16f536f85b5cc928b810302fe060548f5c1e3646a01dc24bdbdac62f8d7a5cb60b82b3c92c078104582dd40cfebe29a240d3997d872bec43fbb68484d845e4e6449ecab31ef2f3382e406e4d392d5d15004058e51727971b167154c0ee3383a62611399f2640728f57d7aab60ef03df28428bfd8a72d60d01bb1e551a7ee45a86b7a10748a8d1a324abbcd04b9d55fcfe9ced05d04dbfd384a44215bd56404b295bb65f1824a9ae877c493cab974d2167ffe03aac72bb18240f926dcca32711dfa6f3f874f95c73fe79bb948ae33482dde9ba9d7a7249abc78c661f07ff0a80680655386eec3741ba6dc910bb3bb6f8842569335f7270759512f32d3a4e9bf23686bd7df4d554059fd61cb99ee0d966693a75009b72ac23111a6d75e5eebae5b9edd8635df7470bb08f0ff479a9deeb0b72df2a353f9b46c2c0de232cf70ccbf161514d84045ebf59d1e454c74d067fef9a5a884661f4f337df0c6fe87879a6b1316fddc5514fd231820e293f116d95b52fe80f65679a4a8e7569367737d8702b57ae7389b7d64e2c291601957b2f332f5b5010dc722b0e0b5181cf8e13957d85a79171b25d10d6b549112bf33a93459b04ab931327c1ca3057028c0d06715b21f98dfc195c6d5f0c8550e7b116e1233accb83108465622d716b74a10a104b42cb6e32db5fb11d2ccff2924f3203741163089bc5c72e63d97a5b8a8ed533ed8ceb59365b829e7eef3c490877ca5375acd6151e84c72d890c495cdc2b1e0ce7da4492599dfdf7bb2c3c5eadeaf9fc9a71f5624693772fd752a388aae6d5b398e9b6cf1bf0aeb655658480aa25a5feeb8e9b7f494ab2bead3aebd577910f448b5c89e83a18de09d8326fee814965e4f35afe2c6794c1740ad585d64f9bd7925d3604cf1040c09d61146f023764eb49ce14bcd2a49a156b460ac0ece0c94f74a1d0e49aa2275f22b3b726f0a5adfccd79b1498d10042721c05a63975219c05d39926cd0eabe6c11955807e9989d10d866401d313eaef7295455e99d59fb0227207932a8c699337aa389d2ef6c5f498312774a580751c7266ae4d22cca8b6c52900fd676b36563be1137d251bebd5c8bd69b05f5ddadb7281f5349c4b4f978fe9d9a1f0f23a1ebaafd0d0fce984970ecbd4be082d958e72e654f1681f6a9acb374d1610b2614d9161db1155f1836d9b02d32b8aef408a72837a7cc5a8860ba7be468c3b52abb10505a0abde4c20702768b4a935be14107283891fd5391a5c07e0e108b8976516ef483428804d481a20e8588ac2f3f0a30b7db8402787b1f5a16e285d43abb07d0242b5938d97b08bcc1405472e703a6c027d469209a923ad6e0e2c66c4433b95dabd50d1d95f6770ae58458b498ea4a15039e5ce271ee5a0d006ec750eba7bc030050b317a09ea1035cc822a0329609072a7ef9f330de0b463da3d0c6356425b22c295ba0faa2aef1baccd5f930a364645377775b86264f77431155b70eeeefbf35161d9a6bc325ee3ab867df4ea119569b331ce9a2016ca03302d170c827579190aa9694a7e2df43fdba40298adf5734ca1af0380b0fb9b8c8f5eaf1779b81c3335f4083e8020a9af690dd111c7c69c180505f78f2557b572064f4c6906b9f462762693682e0f99bd2f296b4efdbcdd6d8c0ab2229e79f209c0db78ba5709f73b3db456232ebda92648cfe180a38478728aa110f17346c14483cae709cb0d984f4e7b89e8d37944b44d29ff7e16fe897245492e78c89d8e20cd0145b5be309d0c700a416513d56f9c905419d670ec2d72eea333b3e215561f7406e55bd6386eb3e4aa7f1eb33f0880cd07eadd953fb87271bdf385d3f760c91dbefeeefdb0a6eb6d4443cda63d72eb2719b15d7f065972f4b7c042ec6b7cc14cd2b18fce5ba6a830b10cd3ad536eca14222d9f5282ec7241fa2c2c89cbe7782d69b96817957b2e8785275703a31020d063caa18accc6724f10a65107f9145088effb225bb71826491ebe59a827d9deae7f45b1348eb57284999ba68bf5fe4fbf82017934044b38d13f796383767beb073c254320d4dd034cdc144717dfcff0e14dca136dc497091803498a0ed269faf10f980e008bd572038d7fd9aaf99115ae3932d4e73a32748e7cf6cd9b1a63862cc7e365b50582727051e87f6859cc0fc575099ebe91eb57450b54e773bda06445e3da74887a6b30eb105fe9be221f718206b08a2363a90ad6cf76491aa694d182dd43810d3ba07231561505f7681755dd5903754ff6cc2b918a9a3b7902342f11038f690035117246f1c302ffc23a7d581c9ad248aee0a1fdffbbf8df6d30e094d934c8a0ffc572aeeee6055918bba0caf493f5a2f4e7181ea2e9ac897891ad39473f82be60e225dc4a7465f00920a784304df1ce526c8a0d6f331d379cd5116e43737a038bb90d0ee9d610a18dc634473b86eec24f700b71850c09532542f144605a5104c3364d5244abd458748313a8d2c7460dcaf7fedafbe80ee042a78349464740b4786172144c77d72a4556cc6e05ec757c81e082402ac212a3ce321babd2e5b404573116939d31f55badbc4b620692078ce78c168646a359113d421e95af9cc3eddc890e1d85190de80d0e5b14a5d698680cc7bc71bcf28312fca7dc77e0c287252c5372df9768d159717e2b1566348ce0320c80c5cfa66426fd6c8e284ef6858551a972ab8db6be0e9760b3787c4b9ecbf773b2bfb6171f740952244c041f74a7da49721aa78ae95782c63ccf57a34440a9c01de64dbb7afc5996bf8ec7f5ccc3b5ce72fc5a4dd77511d349f0c1ff2affa43144eb9e1af8e590e835eb45552ef090ba0b4563a9118015cd187867cd1d7cd8b0bdb95fc7a4b53fb957d1787937173c337288ac7ba082fe5bd557a490715568b4535770743b9804cbde38e92e2be3d01d34ef5fcc4b54b3f3b452a50519281275b88ea6beb87a45306dd346edbf9b66237226b7866e274c632ede2bdc7b8eeaf0b4e5d5726f113247115e07d06bb4cdb66fef2af22bce7dbb4c07f9a487fa3a53a7ff7245aec15421fd520ccfb4bd20021e7cf5f85301e4e59dd123277e264885dc5eeca8deee2a61ed95fabfd6026c2b45863443ef584961b075003392ce8c40f93b8b8269975a0d491033f9ca2d2ace506fe3aa6c6f0acf7f48c791b88c7577a597e9cdec16a19b1c9f1c50cca77cf961b21d2524d35ae56f14974a77a685a2b545bfeb4a14461d724bed14f60f467672b377396560372ee3d126bbb0fdf33e78418b2a043254606bb53c56952c836a72a4cd0a7679f3304bcbbe4d130db96a64a55c92e35f707d834bfffaaaca52067275da9cd2bdde0729726884343ba590264a0cc1e82253e768e18f5924eb271672180d437652e4f5358753321436c2782eafcd7986caebdb8f267623273cbcec72d6f2a1a711d75e9cb6ea551942c80449c9a1becaa0b832e06f732a13101b4772947c4b3782002b96ebc4ca6e858c292d042d40b3dd284085aa1d01b5b5f036729a1d067f4d1dfbace455a2b9272a0c607e6c4fa2bf0406bb7a4d158f5c08a80b373c07ae5f539feb3881d297e60304d00541148ccbde5c1c4ed058c6ac09a355531e8aaabba184434b77412b90b17ae55c17f5303d9e2e88c4eb2ef72cc78c41210fbb56fcf9ceca4412e5de42b5934f58b1547c441d95b2c45e56a17ce51172cdf60d158ce001df4516e049258b1d4ca07a59dbc16557c4c1ffbcfa87511a72bc510e2721431134dfb7c07d9af1f37628c502019a2994deac0ba71d3820f472117f8fddd1bec629c7b698e9f86cf2d87301cb1138306765cad09fbf9906d74d113b136969dc9de31224dfae875377583777187e596f72e26f5b98f8c6e56072f660512bedf70e94f8642cffe72bf1f235b644648322c007a4d56ee836f18272ad47e6a267ca078a6ed245929ad6f5437377c9d2d36a39fd597801e868870d1b13c42bdb3101479ca530a40ffd3cee8c6df8f666af104076f7f3b26329f10372da03c19dc2f082ea9438af8d9e272c6328fb81911733bb103512d803b0c23c7251be7819e901725e576c3b1d80295d6684fa9cd682e6ae5217aabacf6cd1e74e01a940c98739b02674d5f5e1d8e2c0230f8caac2cb4ae93f9a96edc65534f672ce26940c72f392d56b26be152d8feed3b1360f9dacaec525b3d2014323f5cb16fa845bfa1dd9e52986e1f76fc7ec8d68fc0b4b680810252c14336f095c023e409e6e8d8f0d0beda6b278ab1a35d0a23f93d8d64e348823282a8ca9a49d23f11f42d4efafe87284fe46f73ce5ab151d557cf9a5ed1b11eb6903dbbcaac3354772a76f2c9b3505b21639779b4da7b30460a239223e8504bf49057b12b0d4b6840b9eb5c7a0dec8839fc821f280663f94f30560f36086c9c9155982169aed480972e781ee70a656fdbedbe5ad783d2b42d6073a376b288106f4203b2ff578d6d94301b9c972d14c37a1793c8a562fe3b187f07e4e0d41f776caa232ff42f5312909c194e05cf5e7aba08194e0f3ab8f8a90d2c47cfab494cfa4bf04ab80679b17723fde0806698a6a30a21b89587fe3e1795ffc4594742ef9f1068b7b34c79cea6ea94644f11a5671209f14a61a32b988efc5cbb5f5ae8b9fce7764332b10f457143aca4aa8c0bebf40be2833eb066af32bd2d70e7568ffab795188da5a8f3ab372200f757cfd32c63bc24bcbfcbcc97847ff3a591686e515ce80e14b8913bfe8720db72545db618527f81bc5c40459db861be0eee079c56a5aaf3b2da774205f72b89bf61557b1a76c185b05e6f62eeab515b27b01c48436a08ed3632984d45d7209ca78084a569fb679758529a5347e178062471708501348fe0089033db02d33028ef601e2c22bd5fd18957c222dcb1d1e6540834d3f467f0da33272b80d6872a5c26a1cf1b82fdcf3df21d4d79b444fbce92afd90752aa0d837b8ccfc293f39bfc547babe6167bdb85516d6d2fc0cb7da3feabcd491ef6d98c22bd97566426439df594c6566d64d63687e4caa0a8d65377f629ed864b2e611b4e7a4886526725f8b7e89a8dabfa5855cc2d048feaf08165617a292953b7f0655de2dce194772a62414ec205e3499c24ad5b0c4227ca3a9d82ce2da64259909ec08c4ab66a00c383a3645c9d681b961655e2f2e9147ff33de36fb76b5d4059b9746830fa8a7726825a1b57c4eefebbb40838b9f234f506001917c40669380172db9de83a0bb12974a356aea54bac75ee1b7a687dfdf284456f5dbefb77f88dfeab28c2be674309f3afedc017dcce2b11cca2525550463cc18eb1537780e2c2a6f630256f0f372fe1dffbc319fd9f5f5a762a2e6c41cb6d3d94132d9b235ce7f06483520ca8a3ac5ea53f7bb22cd89c6675d04c007cab82a7406e877d7b8550a4676e3c9529139cfe53ae0b2a50b7b8b8383e0d766620db26d13ba7a584eb4edbe0a1b43cc0c04cbeee5a83f68aa10d954ead3ae8fd7d0087f431cb499822f28df318cf08f887211e6bccfd6445a161dce85b4b58d3b18c068af57ad23ce00d1ed5e5365a6347255ad25294b443a4d25f9409edc9f3f89652060de2cf347616cd56643952288723beaa7febd60f928dba2c35fc1612e07fb6892b76f61b6e49d0275cfa57614057e64b74c6ee4c82e072a558166559ff5d75a326edc246dfe9650bd89bc47897297f01ab219d56171624917ecd7291f352fb278a997cf292fc72e7588ed1a9c72f49ae72f55cdf5123093d94b188c1d13aa4496d2bf39ec83a3b21ff0f6cd5a647244a56c36d40cb981fc990ee207017e3a238737a92a3c5036abb3ae1496020e5a123c772f982dafe1cce0f114a0f17da9aec1ef6fd0432fe1ff0d7e2587e37229a956513b75841154ddf172ce87e9be17af1d7125dcad2fda902eca836ac472aba68c2f4cbb954cd5ebc925d72514802498ff539fba8de2160f186bb941187240feb1b9f41e8618a607fb391a737e3ef8dc8d50ddfeb5fdd71ddc91b58f7c7296c3d5175181d65d266489c8d2d77839d36f2627e6d3586792fba0cacea5e82a399551c5c8126302ee3a44876da5d75cff24fcb7e58e17796f3fc5bcfbe4563f5f8eec3ce9c139fdc7289a79577f2fdc25a19c9708f61286f2689b18158c09727cb87f7aebd96b31d9b0ac0d8d2487efbfbed15528a9b0d2fe75c54207b037721dbe23f16f9d98e1b0aa73573e858ecb1e298357d1685bc7c830a56cfd50bd389e1da200acd3c1a3e83fa0351723a71e51bc8fdf0a84b9675345c607e2f30e526e0cf1a5da17b7af1c7ded4f20ee22f1ad0b03c34e71cc26ff62f8e4261d254e73f6c83525ed396da38fcec93032e2980c0bed7001760b906a3d109bad0aa57299e090bf2459b870ce2db7cba04fb52e5bd8207b0e38d3704bfa51dd87762972ae8c1c95affe0d557887ccf25b27416cf02dcbece82b25eb95404118e1240172651ffcb90485e7920626d13c8144d6c2f7ce256474d83451b7a8adae33db532d3d4875d1d2db8624a55a26c4b7a4689c4abbca5bfe47d4149e7801d0c4570572f09f734a31c9804829e0597b45d89244e4a976c941c1a1a98df20dc4b6f80759cd23d3b9b8eacab59138f0a0e9208281d0bf41ce5e08d41b8443ad89714b4e727ff2b6ec72ec561d8844f7481a1759bc51f567612d8e66ba40d6bb2045d69c72ddd3106f8a2067a1ac9cae10aeee8191682c3cba66b0451c5bf9841beaeb837214034071fdafbb6b6aa27206ef7a84e79925711648f86b7eab030748e684e072be4a786ff18f416e48155ae96f8945d020ff95facf96395121a3b7a127f3981d1419d7b9b4c5465d3ccbab7b8ce54648447862118e5708f863814e9853e65b26268e1f7cb2f71f1e81a22e33254473e9019ab05abb0518f181e2e54d2a75fd72b4b4b3a4829ab96ba4cfc76e06bf4a96e1824addfd7a2f13550bb2972959bd0cf87caa0d6cae797af4d62131724a507c574af1f68a7caa584495dc2b2b304c2f659f23d8eff2e4285ecb0757872892d2abcc94985c36d7d30479d0c4615e50721845169e493e4b71c258750fd9dac62035815844c41bf2c0d30a61571fb9467269af2b3037f0712baa4c7ef15a5c5b3599ef392370dd5be2213bd93c3448153ddcfe1418f368354402283767d6715549bd18dde624149dea86431c2a292722460c2179c377a79b7a3a5b9b42df408fd9d3a19b16f9ccfbd55db4fafcb9bbb772bb28e948119c1e07d50bfbd72e8f9aa265a41c94ec88e65d23b6e093216076721eb7ebd93c19564f27d9886566706c1afdeff7d91d99f848f048ed179cd72c15e574d761085b7fb7c04ad1b20615e1bc06919ed84e0a3d7fa8110de8b9f0677275f5626329053e4a9e60f073066d0d5bc8a61a733eae649dafd0f377bdad2672e8e7359ed3d0532b976f4fb761daa5d6071a5f3b108c00526f1f317b9b64fa72e0f580bcb5974fe69d2121b8362e8d2d8247789232c7708bf19f6879631d527279e8ce7425b3539ffeafda8b236037f31691a10a5281c64d58e1446384eaac728639ab6e8c278486ac00085e71b275fd5fa4b86086676c803dcf7487f7226a421dbba5abb831dfc9228d79095c419b191171b4aad36ea339906dc014a00e8a22edb3429259b292c77a4351c8df351e4c98a92d16977d09f819a1ca275cebb1246b27aa13c69def059db8819e49ea1eecc9b1e0b616bdf77a74f765c52b41bf7275dc98515859ecaa96ad41676add6cbde5eebfe7a67d75f28d4a1021a9806072ea914a23d0cb472d3cb6d35e315f0f3a8ec5a26fc8041a672a533870803f2b5c331a9ef0909f41687254a324d856eb490a3b0c1d556cefdce1c0590a261a457282813ddc6d1845d267d6582f118db12d7bf74b1696f3f989ecbd39272dbbca44199b11a0833fcfba2aef5ccb397fe836a5426528edae7dd31f7c7abbeb9a0e4abc3f820d311bf23a5d5e3edf5e11136400156bd343a35fb46987da148d434e72ed009ddac15e501cc8db44066d10ce829d37c75d5ba83b01d88e24e3597c9c72af4d3637d85c39cb70d2b37f168c717e2f53adad865bb74f06f80233fbfbeb727dea2ea0c12efb5da3805123024d8e15e7bb302d623f6f22bde9bb7ba8989934aa43189656ec17d94106a95d15b92ba390fe9dd694eff3a23fa2bbb86c4be77229b9803c1aed5019ca69a811f11980981761fce4e1e1afbff0d068d3a2e491728d6f0c27f1d40ca21787d30fdca46a30fcd66cb503221510f78e13fd20f389725ec60338bbf3d454b858f3900139c872bdf0f8759c095cb9f393ce9722952739a41240745d5afba12c398b0d0fd3de642cd0d2b2dd26d4352f639e9e54eadc72371a4abb12ae69b3ec21a358e2982fcb061e05997104e10bc58dcda9d1f92c1860e1eec85bab4070fbe4bddec280588781a90005b0e92441e5a8a9886bcf7b4d35fa6f33220d33dbe7e5072a8d46409b8d786f677148a0e2fbb26ad98ef0fc72fd4f55754664aba4ea488f16a73e684b73663ad34a9affc2e34e6b7272c06c7268a998148243b38039fc895d830f498a963d6c27968625e4508f4fb5c00bbe72ede6ebbb35f535db70f440e7e241b287fa9692c44c102f198ebfb4cc897e3b5f010c2c505fa493e1f5e1df59df53de5a86f0a7e407150772b2a3e86f4ea7ea723c27c8ed1607b2303fb99b1a2374756d8237b49a86db87afcdc4ace2c28c3772dabdb3eb2482aadd3907f7a6a2a797d75c75b0d9315f3ae026c1988cf2fb400a277f3104a03d388ad6e2ba0ec8fccde5e8348b475cbc2a99fa531893528e2172d5a23d82c56f18ffc637e3f30808806faca37dd8f440f12f789c565e90787172ef8964876c9e7ee89542a285f82683c23d59ae1c6f0f3b720bcb53c29e59a8729c6803827f30000f06f8c383f84870a8f5e84aca85da5a750846bfd24b4d675b10615e7dc72f99a685aa51bb607930c5f68d12b6de0526716e88e5824dccbe4fa309cf6be18ec2d096c7f4b96abf4c2c0627333a16d2b51758f1765c4b452b7297424e6b33769010a443b10af6a3df8f445e9207ae4dc904d166de2cd13fd6728a037ad4008f6d0444e5c15e4a8ea86555943cdfc70e2c9a4f8ffa32b8c66e721fcf8726380394b74b1978763aa906ec812df29dd3587a35a0024567b6ad63724a87b01631d872ddaae90cb651694749d867dd5a0a219bda3093754044460571ffc0cc515f546e2cbe7fa2b16bdced24c83cd539938daaebf92b393bcd2601723671eb5e729289feecbb1e007534d61c4b687c05114ab322609fe664d43f0c5c2d5c25d8ab78a70fb3acb4d73c75b647cdfae867fb8df28db4c48900f0336472e18c360fc409fe3ad024a8c132adcc8518b75afc71ec10f92783a01b62b0dd72641270f0d5d76277ae949254107ea456eae70c69ef112236d90214e11ee6e672a0f6fc4ea5f381beac1e53a78d52d7bb5f16a54a102d54c626966ec858fdd371e8c3abce2af722a349ab7dd2ca7741e2589e80d9ab3efbcc93be6b1605dbe9729c793287da75f54c5f3077a0f17de9daa065fca7515c23ea78935bdaa9cfe16910a327fdf9120f3ca5676b4a5d91c70b81a19099a5a90a6773caefb8758cd02cc6c952ef0cc80df3ec4353a9f899671a32b54f1ba58c4a32cc93f78635563872dfb3ad7c478046e01e88406311182b430c1c72e9c18bcce6394e26f420e8422f50e8a111c441453a9f75e3f017a68db5a73c6f159298f32627aee6af960fc272e26cb5f6c653441f926e4af052616fa114dcf22fdf588be93131743809d72072ec663d5b85cfe07f1957c9700bf36426e6348200feff2072bc4b5f0c2b00dd725d8e28b9ee6096c37bc8087187edda01c65eb156a227e58e68f4c2b5f2931638a5112059b9d796803ccef695ba2a5c87642eb641a131482d87fdbba38806d006a8e628e28505dae2913a505d25d1035359319fbaa9de229a72203d650d63973581ab6de207858f845445654e10d86d8dfce31c7ae7ed615125a9713bbbf7e972dfabe93fe946e229493b931c96313ebcc99fb05baf4d9200b5692443df231a29577e1401395b21136262f2b44f04589aacf924063474de5dec0a9c2e37c73a1f719658a780eae9db6d031fca40c331f973cd1caee37c833e5e9c787ed13a617229522e873563d21d98770d0ca5f287e4ba13ff985b643f12048a21b42cdf5a13c5039d36e40399954b9276d06ffcdc8adc2a50140798048059096d79d610e61b71773693c967b5c2f95ba37c276c23cafb6a0e0abc3944b0810935e5863f294a5189b5f59cf282009dabd9376663e012b7859a2af45c21858fcbf5fbf1790f265a88ca07d317618f3fde6e0588a33329c6ed79e5bea10ca49bbd82915492572ce12dddebeb5e6f2bd43f2af5be013bbb83fcb5515363584fe44da7eb5b4fb344f841e7331f05083b6f24a26aeff52337fb6be1551a9044e3ea78ecab92819d30983352e507d240dfed9440b6228adc3f847b8c178d955d3c430aeff81ad2347292e5ec27b61a5663b67dbf711fa91747fa9dae9bc2b95068d71add5d16e09772e884e5eea8d081a797138e1eb5ba2f16df909164110d93daaeaaa749c613c5078c17a23c4bcc61d8a824199801c930f3c280abe188821f68640b03163488f5192a8aa737955e743d617cc90117027db6f08637a7157259601c350b7402e7f472bf6b02b8fcf4095b9345deef690f5ecb4fa1760f396d7df86288ee428e5fe06b34c4aae53ff83471ba2207b8d8d9050ab3c5c127350dbc5515b816dae698c3442da588188ebb532d75570b1561ed64e06d13ea1755eba8b83084c3d187e2f172fbfd5fb79440f80f36d00e5a1ccb3c526c7db929ac181bb7060990052cdca36375419f5bf94b14faca9c5401c2d7bb017de065a7833dfbe18d78e9986bcaac1574a737330f7e913497fef4320711c148d9931183e16b3b50627b7ca385fba0728c0bd4b64ba9002c3d586be9ac1544897390c065bb4e819b5f24fb2eeb1524215ee18a26220d96fa44bb82e39236facee33b089c00e27c4a17909a9e65b92029cd59eb1d909ee80789d373de41f6941bc39ea930fafb3ce6debc20d0235c26049f67cf272464e6be184b7d99277da1b5ac07bf5787aeea5240607ea75c04f272cc2efed12c191d15ccdffa05515ce484c0dcb83193319e2a51751687f9d33d725ce6c883d12c1f6e6f4fd57e66c73605267021e5adca0d90b51316ddde5c2a1deffd0dea3ab2d586d775fc9a9e7fa7d8b42d7b9433ef24736523270c58c98911549cd6d9650834d174552abaacf793163670078f2c94ecef6acc5c873e81e77249e79667b6fc0dcd34efb6864601b9fb3b8397564bd15290c1b90be58f3b65724df59bc194745e94374b11be5fffca359805c856a9fb459de85fc33aab476d727a75eb5adcf1d22552027c1d6a20421439ce2e0a5a61bd3bee980dd2cb16ab722b3a2dca5d3e40ad3fe5002b87315d054e0a13807daac8fd3ee19b7d2e12717218ab1285ea8dd0d1d416b12224749e22b729de806cebff70743a03cb0f596d724c980acdf57151fe05664daba8a872c184055d62e0474843e1135737665ab772483c847770f03691c14bd9d65c0e975f0c9254eb84738c6d27880bea32a75c60b821d79a4d745445a86096b78cd92edaacebd0e113fb77d645e7229b2529fe67c07cfb124f010e5eb356608e9829f211297d27e2c9524813ad16e93878fe1672e31d3b7748a797a3268832ff81793ac4f5371e7e4c217cbc3c6dfe081f50250ca75de7809c8f4a31ecb7e56aca749862564525e0efdd3fd92a987dd1cc5c4472238383f9f3330edb8233d527ca13a926e64b2c16fa89ae82f42696fff84bab72f9e39d36259a03e0e53c8eafa5bf38a03ab27f14a88cd06a438393ed222adc72baeaf468aea7b275dd54ed527d37abd6402918aab3d640502aefa83f1c9398722ea285dc97e5714fd1afe6aa0cd0b1d51b3df2ac42919d427dfdd25e9160074f60c23cf44e6b816ac0f49b89e58ffc0c876c1ef7994e290691b09f5c03a62b728c8d34af3b08c1f676da0276d0c604cbb0eb6b3612efcf614339c2aacb50a44d04c7b4aecd7ae4eedc8dfaa6d1e3366d8152bde7d03440b536df7a5ce35b17727ad39f387d530ad3d5827e8d3978c1e69714837eab589f2cb4e36f9dcd4398276011a78b784bb97eaeadf19b2897f60ae5a912938b117d830bda8164328830705664f43ea3f893761aa2259aef9d5b0aa371994b6da1e23cd3fb302d57a689729273c789595d1c61d0754a99ca4d2bec6ed41ad31d22e1c1fa5d9bbfe0d0a032305176900a26b69a71ee5ae864a61d349db649fd40cff20feaa9ef647ea39126b0a490ca127f74d8ff0b88bcddc51ca4b456b5873f287d8c65fb90baea3594588bed034c201bf60b1e871cf8bcaa0cb2d3d6326795afb4ad508aa6d2e0eb3a15fb0f85697074cea7904488a5cd3367947ebc84b768666787ca838eda10830672e749d24fc67eb83e2f318085bfba8773f91182a01bce43cba5d1eeea4e06b430981de31cb66ee26118d610171d820653826cb705709d2c851ca3ab626c2eb930a1f71b815e5502ec350d5089744150f05b59a90340c4631af4987b0f01190e7276186a3fe961f433d52e491968fd0e29f8df058fad4760057a468613dc10ce005c2a59817c76ef60d1e0425054745d8b78dfe340aaa9992f4b81984662c7426932fa68bd812f96b08f05f8b2f496efce49c8f5c20d754ffbf63812c337971a28c63c1547974c0c8e1062a782a18457b6d679b336aaa1005f4bb15c11a4ace1724e853bfd8f5f14df7ea1cd82d25d274a36e3de9b92d67b90d427c34b3cb1607271f78e645d1af798ab4f93b343e05afadd26de1aeeaf04682a6b7598eecee772c4bda007443d190728d8e3086f8b2b7ab1b504ea02712ff64e85a76fe198d950a987414f24282999d19d60ba6a7906161447f01a5cba63b4ca2d8d8579127c72d4e0f6201f2465a0e3a49c056538e8604fd2b3c2500b1cded7f338f41abbaa72ce831b9306103e5ebe0a4f5950b1b44ad3a41ddadaadc9d457b9cab5f06d5a3a4cde078414462d0b528520edb4df2f7e4f4f4b813d23215a183ee25d0aed8372a52bb4fd80d6b67b9af78ee513e04c2a8c18ea23ab020da9f662ba70beb7cb4be36c801f910e60c763eb1622f45a3875ef2c642d9f258b4c1953ccd414f363728f214e2cbd9069e890fa1678a73ba1e6746426a4a221cf19dfa52b0ec12103727e3029a936b1d9010d8b49ad6cf7dee7c43df39e38508438025349517b6314724d8a8d36e16355841ec6d1b6e23d38576a15926f94b56e27e63070c264602d72d0ade38c7da8c100652f859a8faf48a6aed86aa5036795c2fc21b946f773517293010aa43c1dd7c9ed9d516aa6a9dc4364e96c49193d83939acaac2267723c723094e3fb84ae0aba92893720891183e598d77dbd11ce388865a6fe124b4ed3723e23a5f09c4531e0462b960da48cbf7eacc4c5172faee4a44064925f3c80fe72f5eac5e89bf97c29c2acd693dce2473a1a5d7834089cc3cc6592853075049572099e3b864e558aa2e87c2ad137e4f23f559b99caf25dcfd4d10af37ee5c2a972ab7495351d984ad5c2cd9117e616939255eddc7fec5711aa6393cfcd248e13192900742af7661c74bb58428851a5ef08c6d6b64a2d3339b562fd0473d3190772f02d260c9f3c3b046eaf30ddc3efb3243bbdc220389f9d00c1e1ccfd396cb27245dd8c4ac574208e525c4ef280b06d920ae25b8f27222036e0eba4c4499b867219c41745719e3ee7c716be106b125d41da58422949d378b850f9d1ed147b9524f55846226a1ccbf56a86a42d20de0960d9faf8792e3f3a8dd575c2b82a57f15692550ccce2606bfc1d63d4a1b29066b8845bfc79f78b6e66ed9eebcd70cb8d72021c3e2b44bb905ae5df11dad5f178c275adc9083f341d366f332b069f096e373b987e966f7e107f87178f7848eb83d5fc8d0a916947699f8a3dcb6f5c9d437243e27b1241bc8a508da0efd29b6251d7f0260ca843a61b96f7749e08eeb9c572dfd4a93a92c4c4eb7a754d45619b5ae618661cd208ddff75945962381483891e40f4bce72a09e0965ce7fb132669a00dbe9df7f9f32e9d74ef658153cab986119fcd60d41787a027cbe8143ab17548d873d0c2cfae5829d588c93f80f5af626ce288b3ff22066864f9619673bba33bb9395a11c27965485f771cf4f2ee78705d49ef63e1cf7cd55cc019f19d009baa4559cf51c07a9cbd34bd1818b4aa67d635eaee6c04859f5b6b40f8ab76c3e000bf37a1058dd2a84583c179bf5a4b5cbd72b4a56f73edb5dfea3c17222d7fce4b5475ae27386919ab52a074e3cd4ebc3a72b00fde5afa5dfae11bf8516659c63acc376b29758a563ec87f11574f0ed66172da95c9a0cf0290401e4b19c0ee4c31b7e033dd8cd22b84825a4977089828fc72c5701cc7024e27f2f51254808d2f2c4768b7aa5f644a5d28b9d7000271657d72826e7672c29d0b7ad4fee008ef4816a478dfb9057ee8ad65c9ba369795e31a729f7861586b588858252424e34643f971404358d011582581f8d44bdfe0dec3729242ba94662612e0fdda408e3c89c58f7bb1276169dc708cebd3c176a30bb2721ec7fe50e3251ec74d45d12a26b96e088dec5305eff8d13c0d15076286de07360e0d934bd9185b578fe6d9c310d2ef23ff3652cae5a77ce27f24b742b1891972c448a74d80a07d7dc904ce60dc7852383f9fe91950993668b3b4a08d3bd21672447a6e69265fd96bb75cefb914db986020f194a8af9ae32a98e4e02bf6e22b6d74912d723b398bc3346a03fda943f268a896db01862c5a2ef5ac33c27f361b7255c557fb81a997d99b1b937e695e87b193caeaca949f55b9e54aedcf80c537226410ce1a7f8255d964ca1710725bbf6925ae20881eb6daaf846c2a1037a40b15c150408bb71870973a89e6bf7b419de88c50f2bcb33fee6564b98595385530725e005fc1891161710c71659114ebcd9bc32c10f349b796e523f20238868635582f0e5d4a5d6850f26021c41b8b6f441e812f8d285cfee4b482ecac889b84f70e07347148ac4ad388693bc8d8df812429c6327ebcf6ffa56c420542fdff3dff30b93a196e3224feca8b0d7de63e84dede2e17f9ac7eca08e20f7ed9d5998d9915a0f37e0e0190fcd5899a2c6ec0184d30bef05b0a62ba4d825213c65d39b32c72999ecbeadcae41520a11b8c2487194090eabb46ea805661da63a29c3d98db472140b8d3382855ce7bae5fd667bcee66d2d1d48b45a807ababef623361391f96fb5b55f38c70060ec651b66e752f88add10e6a3bc4bef75eb839ab66f87ca292b225404c78ac73c2af057fc5b3d43ffa3b98c6ba1fa4e2391556c5b83d78d502b9d42893aad3681bc3d41e6868aa317137e5bc50db8becf75c318b6be135c0c72034c0927b43dfe8d30ee6b5137d161429a0c3bec1d510e356d5da304dece706cb64f45dbc07c9e7f969c96ec129e71e7ad49ce0eedc218f8fbd2d98b1d8b8c7299668aa0e0f21e60f4a4c55f3fefbcdb6f8649485ae1c99c6c1d4b56adef35726987379550a45b5f4e2914aae26e0e5008dee85a3a1ec87fb86abc6a4cfe1c72bc67fba0515b38644108fd9eb960482e9d04896c252ec5d5c6116a3c3f5373343788d37731264f6f58f905c858193cea6a1ff6391dd290ea54a153450a03d37220bd69639155af8271325e7942af0b046eaca4c11f34989e8a25712b3fc8ca0ada9a1cca8c546f90839eb6f1169c92c4f8280d30970a773821382933a76195707cf028364f3a222ddb220c45ca10a6e494ecef1e624a9ee9388c8e5e02c7a1722b5c857aecde5cfde3cf89644a30064026e07e013801e32a2bf46003e5d4a772b61bd5a1acd37da564fb31403b1315b60d44ab86749823f96aaa55659c1f81728c7558afbf5c974aca1e339419086a8fdcf246ffa2de08fb14f92b72b0f6c872faeadf5bfce26981e61df91d93d1c7d98b5fff29cb90051082bf07250eb1f0729d00a043ff5fd5c73e74f9b34986966c245e64b8f4eaed71d7bbf17c21f2383decba2185152adb0ca9d8d277a0a4bc6ad7dbfa5b65fba8f5a6081acdcc426572eb9ca173e16bcaf52b23ebb7ce2c48c51aabfd5de6c417be7365589b664a9772894e4fc8c7935d6e6ef74d8b025da2c88a8a1ba77c3a2b9ebeadf23e052d0e725d78c87a2f65741df57e4ee2e5d4cc05c1771a3c6099b8f63bd89970714f5d038d5dbd26eb8427056d9bc0991e7722aa1bc2156438267239dbf647349b4fdd2a7027ae3ac55ecfcbc40289e816a1303e5110aa58662566efd4bf26438d273b5cd529269b5caa382304d5c308e066c5e216d6fe0300509165fcf60fc06e16d17252cf6d7506f1d6a31601bd529bfbc85951f8dae5a4c30b538a04cde22c8a0572610317d9b87d78823177b9a51cf717fdca9db840d0acb838c2af684e98a94a72071002055529ceea6ea9ac92a4040b4a5f8ca98287d371bd4f9af366ca583872ab224001f69e4e8c47ce757d437f718e053adb35d2744ba25a56b3dc65d62e72ff783fd3336b998be3fa6d405bfb88fb88e331b663399f014c7dd1d53948f038429c76a6fbdfef62fd8a5546b44733964000f5b62a59962d8b06fc436a4bf03bfdb91a03dc0a703d099584989dd6682f11ea247e485a0876fef47afcb6364b72a676876c391ba3a1f170f1138ee2c6adf47fe3369a9a86e190c93865b9186972afcc795b78b501974571712cc912c24217cf3ec287726ed4555cef714c1911724fcc1b86225fd41e69e39e3c4b7c5f619197755ce5bb0c2c5f8a7ba59aba8972bf29d164788f0ab58e75d9d645bd23e375a875bbc1d58663334491f5f7d14e72e405e588ec3d6c4e411485a3315bf8e69a9e4d13b254572eec74d65182d52e72590ba780f70635a8fbef57171babef409c93ade35e1be3d8f4140d50a4ca2172c9943d12d4e3a6674e29d3b6d7aa2e5751d2d7963304efc844fc2bf23ce9a07299e3a3416e7987e2bdea09ac227fc5496425643223ed6ea6909a79a014ae794acf8f13017d6a906c3cc386db67f3a48bb3362d27d84955c988cc6fd3b5ae3d36faa82b6f4ff941289ecd28ed254612f756750c9b9cb81d06b34b5ecde1289c13437cc24d0e122aaed30464bb3f519d88fb8f247112c1d0c459c66915c252325a1be4a28d0a33faeeffc84c8b856164a3ba1248692b37bfe0f49938bfed702b528560f3e8c1ea59f09385854760e6ba3cc22ee67abae84ba56ddb8bf1a67f7f019d00fb8e4d102ed927dfadfa7bc376765c9e76b11597cf2d9af97a04814f023e822a88e1bc13887adc8162a1934a1154f9aca1c271b2303850d075a7c7884e0bc9583e0df5ca835cd901d7cc0a872e714dbaae98ee0db84d12515fe74f048972194521c42fb902c715f52e0fa5d17771d0fbc8543278687f2b6244e2359bd359d9a2239be89c97dbeb46bbe55a838c524600d9252f19a6734dbc022b60d3a836f41623cdcdf60162691f8c8992b4a8973c25a1480b1065ef5ce8cdc352f35572fc63f8bd2881e5509ff685b651739e38e9faf65c654cba6e0cd3c2f7e3132f725e1ed3689db754d60f78786ad41772080a7352fb148ac6544015d8adc8b4837205adc010d1e09705d0ce7188f5a9bf4b3242b9688beffdae3c0cbac2dfb5b772b83db3fe6e0e8697dd210294c79ffe201096cd260f6d7ea2c6127c0f020a8d0db6e0d5ceed941192b2f9d4f4fe8961b663b18d6cefecc67656608025069457589bc025f4c0fe0e82fdb82d5b1100d791e713ed318a925a64102324756ffe7c722f4d4b987e31f277d306ebd0e5ba3ab0d8ff4baec664282a75ae44dcaa6a5512e50539a049f7880f7c48ababc32a07806f636bf61c511ada999079c17d845972f8d141c1278c4ad34081da42b5aa6cdf504c65a775e08ec90957e2c205305072a1c0d4acd98b0c6429ec55d33f6d0a9c94737f75b58c1e1fdc8e8052967ec166f2da1489e1a35d967b11b9f82b54326c2252bff415358291f6d963da3c29b5728fb7a9318ca60673b70465493a8a1cd9389367257ce8e6b64e281f649a56ba725adfe7d94fea415628f79df9eeeedd282f92218a6e68d6f87c55408ef5a79772b765676dcc34752769554f42bdafb7f7b90150b0068e73311e5b4b827ee64b2b057e75681d1b02810b6bf7ee7628704ae093f5e0caca2ee6abde03eef432b02f25e0ef69993acc8b1523b8e7ef2620ffc314d5cce289f33cb6b9e425cfe7a272d80a6da4c034cc21e3291b230770fabc82488f7620e76686635588126a98c40c60c50021b9a775b8462660d0a51d1fa8dd8b655746c64a6d9e517bf9a3ddf27269ae39ba3aa12f157e7920064c1ec424184df7a6a84a780fafcf43218d4c61725c75e1cc6f62354ece3ab2e9b3a7c2912c5faa218e2b6ee1210af6f09da3ea0230165884bd7b9db1427c7e0ae1822be9be11f33dfa5de8c7cd58bdfd11c187341a767a6a43c71a47623b8ec97e58c32eaa2cb6e673980da8f7f5250245ac197285cca8b061c951fac7519ed5b0eaecb7795664fd96128273bf4b8d3f501f937284c5dc226d1b53b2ecb1380b74e6e1bdaaf75bfae2f0db4ee6d76205489d425d55f6759d584531e3f0ea190170ade879e5b7431bf76ff89893bf8e6491b31f59d3ec8f247b58dbb537dc9632bee4bc06d223470f1cbe1f663b7b87fed64fd17203ddd1a55b4a193439a05f067e2cc2336a6761fffd8494160616dcc40fd694255cd9ebd61df723ab418b4e1b072fa9c2ef0df2690e3cfe810d0a38b4210ec3103b09d4e9cb5e0f2686e3f2f797f4ef56be4934ca63c9cde9f847e587c2860572c8bd5969036af61515ff682b790b6f533166c5b55427124fb21964ac5a5e76720af3641d0a3aec7f455a798eff3f9df9ff61edd9d0761f9b0e176c1b1b6f925d648f8157e74a37292938a176fe496645135c69a675d5d8e09832633c1c103a579b7ba73545b1effec3f0326ccafa118b3a28c15b832a8e90b08c405ec595a2298dd9f8ffed10a579e6a6d0cff5093d54312ff179f21a12fb28f59f6f80621b7271cef357122c7beef015f75aeeaca8869c17d9ff679d0a0937ec3d7a6066cd66d6f8e9424a80a1bd20d4c1bd4b9d49df48865e7c25f49e86df9caf1aeb4b22726f79064a50850a90a1c72f9cef066967dda4e20046541e7be0e59eeefcc10d3d8519eb5c45e9d3f7312add81bf52eed127378cbacefbdae0fc1631775ccac87233ddef4b97783891ec4a84626f5025aa359b68aa5f5a969c556ef85649a80a243541e7ae591b9dde76e029bb4514fd7d8dfa0f7f360c865dff522cc465cc9f72730691f00896b628d1580729f318a8ac6c410a068caaff7a72da9ae6852271455153e59ce01651a3871835de5b6fa0edf2ef0d23911bc4cca0191dcbf03cc460722d13370c85f2b59a962e9ad2b64557f7113513e71e412162c26918e6c75806a97598bd8f0825a9fdbc1695a5b122dc63178dff872d52b65fe93e9279904e5e76ff9358bea06129419710cb3b2d775e4cc0442c741afe09ab159578190a6628b8feaba8a7fbf69e5e768dc843c4637abe050585d97edb9277696cfa6768cb43af0aea7e1f445923a43c715e5d5b701a603798ca2a7736beae514f050fb7957223cb85c0432a906dac43b03033b3a4d7af6d12f8fd2d8700843be786af0ae91c670f2d3e261ef4678e1a32eebb27909e5b5db529fd6bee2fc35078705e363172cb1b90b1d70876fdbe2db50ef429269313829fb328ddb1d6322dab2673ae3d16e6ac42d02a6d51abdc43f5f075c8925ee0c95df0517b837a112b171bb11d0768c21a8e2ff2aaaa533499372b49c8a8516148981c333af3031fa5c5ea842ea616d629d422de0b0dbb4aa93bfafa22d2d8a18aa95b91ff4d3e2469481ed6a3b501bfcee5005e3cbb3200fec7e62a321edd54b3cfff07f6f49b5ba7abbccf532b0a76c7a165dcf9ee99e5241eac8901f8698fc8a4f9a597f10a89ef7129dd612a59fac5aa5138a5939a082549cec858900f0b373eaa471f05e39382dcebbfb03e0e6b4ddafc0a3c2ce4d6f2446654918e50049c0adc8896ad91cf9c61713dc2f572cd7334ca7560630b45b960ad94c887ea53479b32b74423677ffa7939ebfb946127ae09c923e0c19a4906a69ea905f397f9fae1a8afbdae2b6880ae6806649c311073c1ee77172f8d2b35fdee7b987d615964483f1062396aadc6a8a254c794726c1dac3768f6d87fe79893e9d5df09e8e47a17d684ca06afdd4fae35aa0b01725357b2f9fe9c04bcbd30c854d5b6e739c3952460d2f1b765d74cbf30b607c472b95d3ff2dd91403ab79dd80b4bce1159baae34dcc2c93d31b37e2231a7a8d7409245e61f33c9f2bffec915fcd3ebfdf49ff8bfe20c59885ea257227b5485513d8b4834395e8e0b49250c6a868aa3908d3be95254ee4610586bdedca4b39b5b652bf2143abaa392c50afbdb43d3502fef094ac7479456eb9f4f4e842ecbcf5c72ac303c7eb0d4b67cd22a7ab1192799507950054a4d74db558fb87144ae438a7229d5026634915aff2a11d84f36523924da4c919362520f17f9d716ed105ce17241a9270e8c2f78d46ce9ba7b3d868018dca5d6a097eeeb307d420d59cc3778729c7cbf397c0ff3b987e08568853f572cbfd56dc673cdda9f7a14b991d66d0d72d6fd9126ff9813246ae65d77d0ea15f4400d726b8af386415abc505295d4061487f08b5b6dd502c41d9f1a067f97b57e881f44735c6e23b2992250a396a667338fcb67f799a6b05b8c6d21b489e729f51c5b3d4ad6d4ab4fcfd944256b4e84727ff24bded1bfd97df2553819cecd85d103ad16fedd036600c0eacb89accafe72aad39be3b2652980489acf0633cc7c912915c6f1f72fd5424cd677baed445a7297a233d6244b78f55c4949236a38925275064d136f998a0ebbdf445f9fee5d70530fffd2590db5ac8c805c81c46d30a6732ee1601aa1d319b3d2659a0d490067123a2c06baaec485e98a23a4c2138a2a7da761102cb9582cf4f91243b6233e72043e2d8a05c1a2812cc18e1062a123f795cfbe19b90674c6a54082d141976a72869e94d4a4dced6b1bdbf332b5bce0c87d84b5e9cde19f11e678e9c7f9e1ff002e35af729ba48b9d97f304ec19276c2885043d44120fa2ed47c00278fdb93872fe86307ab187e6250d7f7201354c3de01118c0ed1d6415c1f7e4818f318c6051ae7c8cdfae02dfac859fb6b3dce29f21c3d7c349d99f9705887847b01ef8fb725bdbb949994986f8546990200287f11810f8384e82590d0153b031085994481f98e4c518a2913376f22dc61064ebf4f2cc3327dbd8ced0e42181aa41e5791c724f001d5e6b4285514f0904ad041110452fa1604852b1a3943ba84b024597ea58858c4ad03bca11a63682a33299b6b40a18cf1c32335028ec31822ee7a364341434ac4b4291315421743687ea5d7ba12593568d63799051f84c75957eaed0a3725634bea271116ba7783ba167468499e9c3991b6a326ec1eb1356c961cefdb47222a579dd871a919bf8b52c2808aef04c2ab231282d52004e5bcc8f7edef23772b9c946ec464c6f935fe853e9f6a11c0b5cde804fe4b250725f55de4dac208772f5c9ff96814405b5117dde7ddedfc1388d3c7eaa39e4add0007194332c0d1d72c294815433add7f2875b53ef70372be61f62ee4841a78178080d13ba2381a072137d19f44defc1d256fe75a96d31927e4b6b754d267394fba34937d8d179a67292fa58a8fd83e75188010cf5b7a755ebea8f9beedfabbe0064b144a7a4bcfe72b1881caaa990197dba1f165bf5868899824ac61a9a039764febfe60580458972554290490cb9812a939c71a08f86d746c1760a5871e0bdc588cf6fd588d4bb72d084dae6551dd407f69980d4e675f3fe3e2213bd1b5f9c2649acc7166bab924148ba8dcc7262d2d5a7302f64ff5b24867ce72efc766f41b8a95797460a98b872fc9e756d13c1cb1ecf4facf1946995cbca51bf1856c86811b5b3c7515976b268b7fbefbfbcdb21dcd16b157901d6a2cde7788d7fe1bbf2f089ac5798e9968e495bba99762bb37538aed209f723191ad4a00427949d5d1ae4f249146e2099873ebb6b768eb48df5471031edb69cb9d16651422b6c9c0fcfe83913ce03610cca28e929a73bc5b77132114f3252d8aaffe0ead270f9b87ed9e0dfc8a64462588a726c37360bee544a44e995b7cb57b1927ae132ad4eee7809355a875da40c6589131c7eadb7cccff5fec14b553b85c6b4353a483be6529554fc4e6555295517bc722afb3848ab193c040bac64b329e09a3d500a6bf408bfb2497d49a82a140c061288e4d3999868b4e614a45f2edf85c0d1c869c925525e65d583b791aa08783e7234e125b8aa28c15029af12a007105af4557a498cc3f82167c01dccd1bd5234233bb2cfda8cd92b03e475a6be5dbca2091bcf9b28334f2193ecb4b47d194fc85fb42dde49197af4c2e5387ec62c1c0aec6b1ed2bd934bf71309f07ac016630134f0c9850ed58003cd3e970c721d7804789409da55bac772872a2421361d42893abccb9c81158ed36491a52a97b979c85acc055c4e586131765e4d3a588da6ed2490129c724c7be8a8a8662fb716c46d07147ccd7ef3e097b1d891279d3f681f721b5657e4984330d24a8fb5efbbae6056c4a7f801779c53ab1d257543d9924b15c79ae070f023dda51f4438ab22cd62807d4b55e59f65412a6067cfb14d00d81253cb2d294a05550955fe71f89f4afa0b0d0a883a36ddfb967c6e2747f3010372effd645c85ad35843badadbab412505866b0bfc996dd0168254a86a58c4e8572b8450ab2da67aff6dfbab8f570eb28e36be50e5604f9d44fb316061b595c3a7216cfdb0985027bd5919825a033ec99dd5a8b0e73a26761a7a8396dbb15e16553202e66266b9c9676758331a071b625058556cb44d71e22df17cc022c8ce461727b7c37604379d3ffeab0cc2c5547696fa947324d5ae96a1b81b60a4a2799d372d6599554f59b6a15963faeeb3c4a58703b701632461c06f05755d96e06253272fb76215abe5987cb151b6499cba6bf80877d935d652fce88a157f84a10bf2472214a003738a654057c0a03abb366543d60d58eeb58dc6806cbfd0196ecc79972b113cba9f161b12b4a6908a5d0fe50cb3f52b36472d1acb65c8551c2afa516423eb9afd1a440ca8857c9aa2d598cf82dff6f970fe2d59f06726d474c49b5ce34983b97aa34264569a7040e79ed23f29e79ace3b59accbc42b397080680a4f22a1c20d9127003e189a864b5f2238b1f7dbda1dba8846eea205ce356fd18a6d7041c72ce7a903a9be30e5b9db7e7a6ce72196973754c2889c31fa0acf6127377729e45f7106da7700587cd29337031eb05753a53096ac12631741e235a4c8ad7729ad6dd2b2bd81500ce9d0ace2f1ebb187c6c5bc69768c1420b04344c2cc21b48e42b018224bbc3ba6ae07da07aca370d965e00bdff9d6b13c75fbc23956b2d4bd769bc22d43fadd998ed57d023a82adfaa2d15250f9eb41b85286bf0603c9572b0510fc3982638ded6b8fcd4d7e5fb9a0f42b85d75214f581430df7ef173b42a1e972f30ff24ec8658bc84e61aa093791c90c2d4edc48ccb743cae04342aae727b52fb7e3635d9cf409c75cfd7fe5a4eb78ffb58822d9dc8220f3d3f0fdc177283c16495c3726ff476eba1100ad764a2a7f018651b0a972afde0c05919848823f34e9ec5e49f5224ca3eaf945796ef31e18bf4c0c5a3bb6f376369427745fc51d78535ef923adcd66d07e012c9e0f7805259b11af01f090da3ea59c03bb3826c795d1970cff0a2d9c6ca9c8c12a15ba62a3754396e3d5af44e05eac8a9dead726bf8b2b44ad55bf893357e3ff63a256e287eea047ef90c8dfb236e18650c0104db36a67673b28da8d0b64a3c976aeb144581c1dcdeef2b8412b4e9bffc8a8672136c245e5708864941816d3496b482ca937fd2fdc78a2eddd98334b04bf073722089c3f4abaad3b7714cca346fe9b8908e0b8cf9bca0cdb08cbb16096f67307222ab41bc29bac4818f95cc17887280c7af7b2576f97a3e37000d84ce87a21b7242c5af8803b01e208e46c66bf849597ee06b2f5e4c24039d64218e7f39119872a4da00734d8d2712e4095dedd8ed44e450ac163700edaa115f63c9bd58cd8972088c5763bea359650cc37b5b1ecc380d61d5849891fddbdb6e777dd45bbc6d72fc35cd8b6f269282881ac3cefc73ab5d8e1b0f1a2ad7ddfd93cbe7a1f511c672d04a7a9ecb7860a8c3207b85c1e703ff748fe315bc608f7b2c0924df4a76f446d930e8d947121f34cb17715caf95548e3021821ca9383a08a37d4e835b188d57fd04a02d511874efa5a87d2210bacb90755eb34852d59d95a97cf57810ebbd5b3f07dca80858537cb7b5c50d47792042770398e8005e411c45a0c6302ceb5b724477a11e2e72039c49e51a97c152ed5579db078b4dc8346efd40bc7cbaf2eb725494c8facded47a4ccc1ed00e28901caca8915d7c6b9836b469ecc7010b29755dd3db9c4cd60722907243ff02eee60d924120c711cc5cceceb83acf6f33b5372bb413ca3bc58745be95fff9bdf193b7bc2335e212cae3250c6728ffe09287f72cb01cd63866af51544ada739f68c0124f9df2c8724652721066471866b513a1eb3dc8fedf2db1bc03bde939799bdeff14e2401af94d742007afb4a84708ce80863649728cfbb35e71b2761c4c14e3b7be94b20126cd8ccd9c187abf603c02972837377d92407d973e66a63590364cdfd0ac6b38627cdd4129e326bdd1f50cc03199c51de2fd82585eaec659a6ccadd423b5a9e5f6f34a706cf417094486ddd28fc7477ba269c2071fb6c7cc11885cd2b77ec12ae2e96537fdd8cb5e6d4cbc347aac5b80b0be9ee20ddfbf6d849d42b787cee27b51a8cf0bba631548f5e0fbe723e739325a03f346c803e8c3cb449bcd0908643d6ec8348581f84db61712c0a157a3b33ba5ea84a03804c97a4a98e0fd75d10414cc93d8fcde3bd4797d62e3d72658f2fd5f7c1dfe477d760eb245bc199d46b9fad8e33057c7bd2e263cafeea4fdf65aef3a87591999466abc3647fef365ee767cfe62f8bdb6110d767fc833a72b7ae83dd832e5f1b7fcb9e5ef5c49520669c3d77bec6ec901426e90b8c929a726a2bd5d267e9d5ec805722449e4737b287da1967378a27fb394034b9c2ea8472df85ad1ae4366f1dcaa72ea96c1fe3c0f521404d84f148529d4487b8385590726609feb9ac0ae91fe03e2196a736dc65a746ed87827ba85c630ae7ad1c1ef272818ea99c3d5a4f5e34e9c32f1b45a7c19f28f3f159f0e4031b9957bf5de5fe546e89484861c1020748ac80e57f8378beae80bb64516cf16dc18c9c0e35b1e26a4018c3367076dffa636213c6de93b816e884802a0fffad9a606f9f311ec02f72fc9994d63f1ffa32e7949b8efb7f79fb3aeaa9f00db3648fe507099f4070fe435737e7a76e3db89ddc2e33c0800e9b6e2cc212e93f0bc5b4f472ad47a25ca672cebd557c9048372e42d4f162b08ca656bc55c9528b362c154368aa6dfa36877266c49deb03d543b139d7d3c04dbe08bcc95f14da3578680345411d9a092fa672f150d6a6d4e181be904e5c767ea8aa5a1c9bc865a02373db532c764a5f7f9872d5f20b908a5decea9d7110b412e82726ef7fdebee43221058ba5dc7ddb82fe3ea648991bdfa76e483f1796603fd50b7f91f37d56401f42f1824cd42d1c242172734ab97ee959eead1f0bf6dc698d852de8c519413c744813506a125148197c485e084f0bc7f428081a8cfb6bba0a2d6c2133a8acc0a833815decaad690693e169169d6169b36a2b27ef8d0df861e23cae74505ada5171bdff5b091497e5cd7152fcff44293ff6ccd1eff770c0b905da801eac26416e5a9420b30b0c73344747258d9db188746f4552f45532ca8b692df059d776c279be5552eaee95d961ef4004b44036b6ddea346b12ff4618a197a27f823f30f493c784185a8767e72b0ff72559043c0237244b84fd3197d6269c2459e9cc337e0f2481239738d5d9e2e0672ae890a3087c4ad56db9d32dbe5692f593e96ecefafe80c4792f6bf750312ec72ae3b618fe76227b8bb8084ebc1f1e6cc2927de3053835ffbf95e7f3824fe9b5d83e640892f8389fcda613e1a23cdc9e3222a503d547cd55174b0210397fa85720328e16117bbe6dca3767e06bb6d715d5d577871d231f9278b2ed6b077cdae72ba4d40e589238c549bef2646cfa9cb3b675cb137c3e6218947c89bf59fbd481b8fa021afa6831259bf3da8120a2a845cee19b41f7ed5d1bb3abc4eacf7a22b72d599d5eb4eafe8a3b0713b8410ae9efba9e42da750abe2cdd3a7c2ec0081087262e8cd349ce4307932ca6734dbd78c77c14222dbd5b8af5af95d756fba56c772e6ceef2ab1bd0a853e9d889141fdcfdc4af4f97935a3e900bdbb046ce6f876398b1c1138a7a5e8fdffebf9a54da793846b3ad2f3d9b1a490fdb4060e05109a505c219e481b9883467cdcdf42265bc3bcce2fb861afbb0c51a436a65b5c399d72bfdfe3469968ece33f6cde790b628d6fe23e91576889c36418eb5838ceaff5536940ae10f801b52b936aa9bdee8c012f833aa2e3c6a1f27f6280b23d338ae3726239329567f7c894c3b9e70e951974c5141fba477c22e8a01384171c53a12a10e8009d1e35aa87a76a6af894986ab17d3593eaf8da5b98d435b672caecc99c723e92357d90a2c8db25d7694717b8a7616bf7b5df59c6727c911b19c7ae0cbc3033464d5be7aaf934d8bab6c7dcc5654f1242a7169c7b01e7d33c644d31613e25762810f5d94e44be8f93dbf05a18ac62077f50155a8dbae98dcf0db79b6c1772efc88a3b75f04e20101ad01f99f4062b1c90e8c343c9238ffce45ff47b47a34a53e9fc0f4ebb02c7aa9638345c6f88387d0204d4119d3e2a708b3cba79aa7072ad281fe6afdffc08d368067231ee3b2487180046048231153df34565807a5872c4630020ba7e720114358b45fa14940fd3992a0da5c5581eb03da4aa3d91e7721a88f299cf3bcadfd4e2efa29c10db58398fe30b2f94270352940f9b782a0b701f8702325c616e26659349bb2045f3fe17923d82669bdc4917b0c1ac4a988e56b7c9d2baf002e57773249780f2f2e5f84da2094d69dcfcdb24f057dbd83b75728d638463dc55333f30bd8357a29d198e58fe186704af9bd24f269d61d390924c9b6928d2097430265da4fe0226d46590a6012be2d29f5abc29d7bf2ea889f47294743f9af2e9a1ff62275015c23f9f8121b5f84a376c428dbf323da7a437de7246d868138af02ca45f1c1de79e1d12517a0c3c6ebd4f126fc679472cd0ebea6100b241092e52288b5cfe6e23a227654a6e6fb58929e14c73aa01ac383ac35072639d9e3879f697df77cc76eefa9d990e656042b6559807e0cff3bf4efb1b62006a4c697a09c1a28c302449cca844e6dd646a5b10fc2940d37ff04cd0efbe2f23f465206c2da8b87c3d673abf45daf6b21d2bbf3406e8e3792d61a4f848d9a772edf6a3c0524f586b0ee3096c225a27e5b9763cc00acbc560592d4e8ca1c5f0191e8548c452697e39f88c50a5ba3abd0bbded738fddb198fe95d504a0f60d9472db2c21aa2fff85fa0abdf45b902599bc6e837d6b39b94b6433be71138e2acf5cd7740d2a6f16f3ae9b4761fd16ab8f29b49250aab248b7106356ee16a28df972078b68b8b5c4d13274d514faf58732938c8ddb212eea8ee309088e8bda189d722dac28a8cd76300e3f2bb5d69f5ef6196261cac26666e4e1eacad7e5507e4704f6a7c6905311b1ab7b13b94e138025daaaa7c259ff280223c9b671c22f303d18d271cb24155f24a4dfc2f72ed50351b83b1498d13501691f133986275cd2a33df80dcd5cdf3fba1eb9a9a6b3a467ae52f2071652e9d5ea092f326739a06f8d09239f7c6d2554f058beda546bd8bfe0b576f987be48d720c0af84713ee1b96d722021853ce40b572080624c897d00de4c72da2824ce5eb87c87a7453d16535465d82714bda877007a2955202f84118f16555d401cdeda9cd914023a6741b7ef72ed50e58815a7ac90c78f57512f70860b4a328438eb3c4ece013489caaa5fd035078e11af8007b4ef8307da798a57d873014cc921656315958db04a305fbbab68e6c2154b224e8d16c1d6d13562f8a18b8bbec28abe209a97eedc3952f9f180043393e5721e777d5c5a26c246e346e2b681afcdd5dc6ddf86c2735f3cbee1ba6773ad019bc30f6a2c0bd88f3669166fc1ab4996a6eb600802d891cc4a38284e1f1d537d9391166cbf15971ebf6978c60a7ef3a6330b152c0cbc0220fafb14f22cce24e4e0732175085b999aee1ac0925a7277ab15c20155c9ed7b9e65dde3907214762d5c872e8a1afbfd120859675c8b0e2632c4f6a9804352ed06515976d04b8b60189b83688563fdc174c24c60e2caa1953e9638d917529e72030877441c65bf877b95fda37b560c332904bf2b100c4c2b49c7b1c2a13c73dc81266d254911378f6f40cda536ca67b76abbae48767c0fe1bec5145fdc31c17aaedec7688572fb258e4aab51cef09212bfb53bc5c11d1b6d3f67edd23825d625ecb8954f290e76f76372a392cbf1fe044a0abf4f2d19d4f28235e8915b5aa5a04c1512025b4e29fb9694c58fe1d96f6b287b172a17ab435cb26e865ff9064aebc03e4e9bfb67a2c3c72f029d33c1be1cfa863e22596339348436637dfc4a89ebc187da938c156a607cbb468bcd69130daa53f81924ba09a943f8f6d6bde364f12269233a32725b436e7e47a473b417f2b871257985ed6c2ef3bea77157bbca1681f822368b72c0c567bf7525b8146f6bbdf96c2750a8793a1e8b18bed7384bed13067c7df672bcba82f381ef854c36d21746e2ef43d6545da0b6c998dfbb532c5913f8001c72fe40b9f721305c17c8146d360258cdc00173386f5616791fecc62aa23d56765f4b2ead49a1e086a366282445473b4760d339497323900587394dd4519684ac724b40a449908074272e07e483b43747eda4b476923868610f13d07b1fc01df972b9f9ec65b96fc173340e1fa7afb522cd7badf6a6fe4e2f7158ab5488a8c72b72f458504e4eb6c820a18ea7a892242f3674f35a0e729bbebb4d9fc0b343368e72ac7b164466b5dd7408188054fe1b0ed41fe62cdb751988296197552276193272cf5a5c3acdc79e0eaa3566fe5432213674ec7b0961967314855e582e124c3d5d217c21f720f1774bc599dffc69be47892725ba6ab93f76576b2a133472fc3572f151cfca6d5e291edb66b0b2aba12fdb0bc2b4e684fb1afd085f937cb3d19d0411468b5b24fdf4989924ec7278f651819695cf9cc7242356e1bfb393653ba672634b266a9478ecbfb16768a47bd04cc139a0a6a0b83b7752217ce113ac465f72c78a0d9626ff5bc709835ec5e6cc6ead81666aa712de10de83a7817eec084e26cffda78b1518dfe3a6b69113d44f61eab61d384050019f8bff8b869b4f35c77268b33279b3824180b488a98352471f58b6a66dfe417e64b510c93fc47341bc540fa8b9a53f7bf8a5656f06ec60a2863d8e3936faa65afc925b8a5e21ecd4d9720f91b7a8ded7a6476083a6e2544ddd93d54c0888f81d29b7b37710bcf0c1620627072b3648aca69c295710eb92d470266a4ad8fd1509b789a71a0bf6636b8f720131f803523598efb653b3835dd549d571589190b0a2e73a71f4e64083967f728143179533c842a7bb170832b926c99653138bbc45146671d986ee3ba927d53a3926867b184883dc35db7d353a9b78a0e4d05baa02af72a867777cb4a3b0147144d3f41a292cefa9e3b2cd8f1489351f2010175cd4d4f35b4c2e8a61f301fa72d9a64971613bdeceff1aeaa7a3d7b9d59105416ac01f34725653fe8251147372c4a3ba8453c2e2629d9fefd7a9b817244d29e763ff5411bae74b356f0253a07261198b472bc81f80ac6c4a10cf06d11252f29a7dcbf1fb60bce1ce409e7b3c72ace7eeef6203a66f4c36a6c9c609e8b3063f366b16667b0c716cdc2aa0e03f72a55145fa29bf6c78ee35e9579db87eb71638cd98354e3f2525969aa1254db17274e470065522b94d021a480888181117b1b345ff3b42203db5e089e6a725d250393a3702f9c24e6e854abd4eb28244fa044da9cbf9612d83ec135c3d06b78772e5ff0a8be29d1cf0422620d49dac0eb27f00d3845ab9718b7b43ba91751f9d72daf9e84a8ef4a0609038d8b55578ca51232a064e07ef086adba38f6856ca7d4e49f17b23605a1f0a653f6910753d7980574e413022e5f02bf992bfd8a334870e3bdb96ccc3ef0ea76361f6616eacdb7746eaffc8dfbba741b34a8715ad00277288e413f463af4f46b211db32bc6d15845d57fe2cd048420251cc86b89656eb72443918e7e8cd72cbd7e863bdb9580266b05adc474a0a6306f56da156b079ba5b147ecbe6549bfbf8f049648ca2abccbcac498b9a8edcd6060a7c19902ce2ce6409dc4904f764e431b51f73b993f2219bd27e299b0f3b48b7f0cf3e1080885c72614bfe251f1f7f2d9356e741b2e074382313a3cc0c31e4720def0e8348d89772bd566f6454137e4670bd492d7f76e13cfcd26c59b2be8185a64d5dbd044fce2b51fb3420cc4bd4eb64695ecd86869cfc3d923492f06e4e45bd6a89f15e447f72db480258518c05eb8f90c2aea26ef791051c3c417a57b236bf48fbfbce6474721eb5249719ed6f8bb21a23352a9290fe2a73c5ac3323b5ddfdaf5a4c8050ae72ea876d97375a1f5ae77be7cbbcd3387ce7cb544dffd94de38325b4b3485537727857c3a6c33ad478c33dac8f8e45c2aecf5c86ef5c50742f2b1e57d17e4d007267de423723ab79ca8ed463cbe3e3f4d1b1e1858253a248a813242cbb9acd46002e504ac9293d2e396ac98d0044da9672c023d0b4f37d169dd6a52bd618b7a072780f53ffa6934227ac5a3c93bf1540860ac12247f886c013f411e6be1cc9b772bf52b51a804fb4971a32548d21f8fce959db0897778dfe7831d3e03ff5c70072b9796f6f88634bc268e7087db5f02d1628ab74c1c6bc30cb3589844210822772dc75cc9d97e4933b70b8389841241c932207ed10fee537769e1a28f465f5cd55b30ba318cbd55921e045fcecb248e3a3a0a98516114b559b5524fa170f437572d85a895c7d953e7cd58dda37921688b921d733f16036ed45f3aef3f4762d8e72ba23c1261e01e0e3909f038a286d80ad6c982fa428daf989aafa356ffa703a72433c3913abe86d7c6c9bb98b1334876e651e2b389b033bb7156a32a0f471f1724536dd460cde835393fa90f1759451fff65c5cbf78389c9a278debd9fbf3c372b9692df8fccc2ce8a04659a7d3b9109549f16911671fa08da9e8a01308b6150411792dcf93b91e27411e75b442bddc7530beb355cd723aebdcff1be742719472f488e980dc6346abc87e54acec93007a8f726abb6e589a402203849b78a90315a31e3a27b2034501dffc5b5d10fe85012b5888204dede77282315d555f95a97297d78f05f1624fb51fef3ff0accc0203d48eac1407798cad568097857fad3972b4bcda2748398c6b3c9cc98fa904202ae2d850ad9128a5399cae408364010b7209674f20fe7343a8b471eafa395dc7b45fd4a888dca8f697ada6ee84c09ee84c00b977a606b9d65725e13cced6c5961ee715efc928b334a8ad8c9f3e084122729542210a3b0ca92e6c634b0ab2bca0aa8dbfe1ae8c8678da181812630beecb724c52f39f87bd2742afbf185c7e9b29b0c553cf0368c0cbfc93d0aa120a765172ef93c79d6305869ef717250f9a4ff2b0fe916a4c03123195b112d4462a5dd9723905b1682788f9d3e310335d172fae7952362cccf4c15d4bb99f0dbe5b2325721d9016ec703ac0c9cdfc0c4d18fbd6f9bae1f83038b770faee41c52515e83f58dc0a0bb5dc0799e9705c3c6d3cbfbe9fd2c60295a0617854621dbed7cde22a723a81ca8f956bfb7beb7ab57bf6d8a1818832329bc9803d7174847d9daffe860577a4eb724fcbbf595424c987ceaa348d01465dc130ce90168002d86d72164f723b3558c5577923d8314968eb73b4e9072ad53aa4e4eebc92926e8515e123d072f0ea4a8f39830ef9a13dabdaf069480e50fcfac9ec8760f8fb934ff7dafb4572e1960050b1facff0cc7a45d312635023f692684fecf9323f0a752ac4106cf3715b56179a8ec85b971ec7fb760c89aa5d2c86e40370394a7526ee6d274fba01180bd60eab0794c4dbd5043b7ef984c4146b3f3a43e4606d250ceb08550ceee55ac0ae48fd6789b1d9dd920de7581bbd24fb2b632605a60a3166db8f574ea1bf721e035fa9581baf46ca064d9f94250708dd0473dc2817ced3dacb1498fe0ecd31e51ea9698b0e46ee89ae4cb1c4b5b1751256538bfb7f50b2b0e52d34faa00715de4125a6d3870869ef2ee6c655e8cf58f0584921a381346abc3634c612ea3f091e206f3d79531fc4247434858f0e548e241c369d37c99a093d96f46f094f32138fdc9a41e16fb35de6e29ce70b83ca472b11b163036366c3a60cbcc16606bc72111300455048d62dfa0e2ab1a44ebfd19de540d27e79393011844b66c2455c7263e558aee3962776bab167eaea5f562ad9c6056badb735d4642e4ab0d88f325169eaaaa108de832a91ced8fb8c6c5903a649742e83529b0e1c3d979ef97f6d729cf2e87cccc83c7791f83f3a7d05e91673a4ed3efe625607805012058b454c729a777a2b27c2651ac999b000f76d78bd70f9cd1875b86f43f96ba5fef349d46cf4c16a7a2c465f684b8064a148e939381f3d0c6fda0a91bc29a471e07c67b172ac592a04478d1745db367113404d248fc1f7ca68ccccc8eb1c185f4537244f5811893a38fffbb95109256c2ee1a335e329bfdc70c9a954d40ecf8a01440dd872b55acbfb235f131cf09acfb30b9cbb5c7fdc3a8644b528875bcf8baea5b01332deb4d740960b00cc6106f9edcf83307e2f5b077107614c9267054e6fc45fb7721a385b4b57951c91eba12f5b6a79547c59ff6a47b43a19d68fc4b679947d08723f026deb356a8983cc0a00bcfa05460bf23be3ef305be701fd8406b77b9a917210f5454b51995216e9dabe72e9259ed32ccdd4621432ccf95615fa71780fe4727e4e7b8d64f64f35b239a5fed78f0806a1094385a07a47a47bbaed70dc826b60656783162dec474c1f3d2f6a0946d6b24ce168e6ca42a3a65c7d817a058e16728c281fa42f73aba477dacb009e8c0e99f63db5f578060cf4f4d5eea5cbd033720504e203d8ce62770d9e26e568b6347b4586e057a12a4868361806824b738d661536f9a9973bd57fe2068c56e105cb2e2bf0570f4bcf6bf2b9c50df6a4820035bee9248383849be1c7daddf616a6ed0d9a32ee6b7742e084c0e0856f7c73172d462cdc618188248e8fd261df457603ae4cd4f4fe820273c9d35894cd783a3072dd14df71ad7d53fe5d685a53433d5bbf62a0ead0b592cbc88f787f3576e8fa4fb1d8244f5468649a435d036a461bb8886e717b8d8ba8448279867e1b75e1682903ea5a3266258e21b8a0a9116ec8c7174a965dc9a758af588ab6f327edc55c72b6b0a90b875136126a7365fed685411a91f9d9a75888682c2c7b65998ca6dc72b21b38a308ec454f4acb57441c946a271e82cc0a801d16edaf2a45295e644872dddd0005c8451ebe8dca4dc70925f05a99f6ef2f9925af6d917170f704177572222a7b623254b22cff532d18400ac3c5aeaac1a03c2cc910cf3609152f3205130bf72bf05133a1dc3adcb505a5b47b6d2adbc0afe6b3c002f99fca75fdc97772fc4fedd17c0da80530bb0c1ccf071aa740530035901059ecd52e64c929e8086e7056656f89c286622da79fd29df43f9f450ca1680f0027b7f16cdb4f809cef72d4950ecf1f85bf1fe8411ddd9e769f4d0015904b061b98bbfbed5a3553625656bacc1170ffc91a57779804a68172895188954a25e968535c2cc186924e219f726dd0a75aee6d8f85a1818d6fcfe31322f00b3a9dcd18972239ee295367af7d7205cd40eb5d7d768227212c5d8c24a8ae1c5f0d507987c50e28904b79e482853aca7a2abb9a1c0208dbfb94194dea8393007162d668a74fb1ee08250942bb645a28d178d77d3b0afb86541951e1eeab09787cabf9fe344e646ac59b23818fef72f5a72c304942e2132f455770f6449b0ba2e2a4f60b92b256f9450bd5ee3a0572f9a76ec0d22da6af7fc203dcf3f5377bf7f5b8d1bc3472dbd1075228ad86d5720a6792d9df6c6cbf7b2ddbd31119afd8ce3d0f7b512b2f93e84bd755a7269672c12cc3ffd22cf79160bccb54dcc326b3af18e9473280287d7accf23f805e3a72a3f6c6aecebdd375fba090d105e86e4dd36920d788112aff990994134ece7d2b87c9c095ba673762d2132cacf8287ebbed501a8539585116d878caa66ff1007219e09c0ec3e26544355fb4d0bbb1c8d23fe6062599032da064719fe49d9603121f9af9be9aee632dd7ddbc02d743a09187e343fed85d42f49898aeac46048072732d344d0eb20d8436c33c8ee2fcb415b6d1cb47e5c182c95ee8e6166ef363726df620576d0296a64accef5e0cc42f5f19c52cdedbf7b35205a2387ee6ae8d72a4888569645a59722f1e7044aa6eff75b7d977431fec7eac6ffa7d5075ff1872b53673357dca8cf666dbcbd804e85c460f6c468868a2c694cb3b068fb1841572b735b3ce800a02bef6b6931f20e93dd45fdd29713d890a81a7c7470d31c6ca72881172c805990bd6ff3a8b9c42900be4b1d26bdf2148b16117d05b5db018df5975cf828aa3244265de5b415e3c1b248a2600778f90532b40868cde3f6525685690562a16d746f75eb765fceb014bfdd684cfb2ecc080fb0acc5895fed2ac382fcab31ec6e16795a7c2699a005b5bc205d7abcc11dafcdd8b9d194265019e9841b861537375cd4c700c4a9fe045d7922bc856f61e44d614e8c81856981924d772aee0ddec4ef9f9b2eaaea89c8e8cfe3c6bc74742640fc95888af58aff6ff95720de934f80561ad71a36f41fc213c0693a4a59c8dc115e4123eb6e696296b9772ad646763ae0da816a27ff43f3636fb9640310d8fedd3afbfbf7250861fc88f0259d0dfaed97b68fc0c9a5f82f8a7ef23d6700f3091ec17f0b9eaacd1e74df8477bc43287926447d4f9dc03627317abb0fde39c7278fa62a86fcc2e277d4eb27284165c92855037d0b4f9bc151e99aa7f421985290a62a205f2b9cef890bba37276db0b828dea9663d6f839db2c02999a334787053dfac78a138674487fd95e31da1400887c2a1b5cbe36767b15853f955e02fb61d9da8f3fa77e9dbf79418f72b4f49da4e0f17557a097c05c910e036e0f507197f8f25acec0c39bba1459f372a4fafc195b4464be6f9da7221d794a031e64502de0aba331d0e86db6946a88722b9d1684e4e5e6b4e0804dfe4125f5701bef9c599fd52be89b68f25c018f2413ca6b40fa717732ab4b48faace26403bcd8a1f7df1b4f31d13bc82f91a7729a72b59263be7120dee55133d905ae1d44f887c701ccf849e76bb735e87604c9f872f39b04babc1c5d3e9030207c29e03ca8f6a0c05e10d98980ffad0f3e439c5a726590ffde85741f3829eec6b4d51db1672a424014e2ebb6078ab07279aedf7a231e58768d942ec1dc92b37eaf76b5cf417da6878d191ef1b00c66e5c37f43df1855fbe980f6d2bf46fe5221aee6e475386a614804e3fee487d0e2128f95edd12aa1845c7145fd44ac75feec2f2ea53002bd20174ee11f0ffd17445b9126cdc87244b48fad9b16a397abe6df0d141a298b81a066ad12ef04544fb4642f23f33422c76c44de26f37d25366d2d5f4ffc09c916e3779d1478a666b577b4d89bca8d65ae9a15725da65ce164d4743a6d7cf16b025dfc8acd1b4331a481544b9d840872c6bf2c04fc1dc1708b21b0b1c60759bc7aa5fe18ea737c64c5f936298e5bca359f3a7ebece493c279e733f09291de8f26bb2d28275587a5c869a31a3e7e156726b6a168f5b44aea55ae6d27c9541890819697618dcda54296aba2bdcab011772da99eca3ee158fae458c9508ae8be2943b631e5f4bc906b772b45facc4732f7225705f48e9907e89aa4bc362d3c793aa383fb911789039e67b17ed824ed8b5726ab946524e27fac30bfbba88cf4ba4837e9a8098117b14e12775d3372f85b738800952202f7e9d0d7a93b0695a953e4b8a523f01a644293449584c7374acfc0151bb51382ec697d3d945ac81df2b2cbe722e99d4b625bcb3c88dea42bc6aff72534628207ab030a05a13e9ebf0e1864296af6dbe204046a131536e47dda63872608cd3d3eb69cca77061be25aef441a14cc513e2ee8f7362ea747b4708a11e0cd67a2a92a98ebbc65f782aa2c94580c182105502533d27f2b7af89e04f6da0724005df09871f7ec0c1ca34d8f0901bea9d9997c43c96b834aaaa8c01d367e072aeaef66dfd07c005f9faea9fda5285b363aaf20c3518c9f835b395c4b401627254a99cb74853fb5472760b3cb173077c92c7edc31899d1141e1152dc06d31872e89d75316f2f7b28b2a54afb9134d7e6c508eac88cf02302dc9695a1578913725ce7c680f669289ec4540711674afc90f87376c73566132a9739a8b79956c7722fa1dfd34184809e821f73c3f54cace6599ad98f1b69bd991d7e1c9d26555d72400ed9d0733a37ec87cfee2c23539a66efb56a8518eedfa1cce1d4f9d16bce568e40cd5eae0fe77adb2d5c4cfa5986bbe73f8132bbbe0036f32071a8543c016123efa5e9fbae1402ce15400d895c7da4b387f2b6305bc939d59e3d882c25f5727132c9328d30e4f43d7c6ad73a410845d84640068963f0de936432a1927b377206d02a1ec92a7ea190de7e2251db73bea836f7e0338fbf5a63a0a7fa0957070468d6c2847e729600848e49adad66fe5172d007123c2bcbd5de5ee79b1382737234a03c15b85302ea0c46ed637bc0056151d91921573160ad944942c04968935382b92841e65b06c38a6c83891cd9666004ac143fce042581bd15d93c30736272a2455416a935d0154b65ccf1b38a0962e1409de2df992a9543c81d56a22308725b5fae80fb45b5ce82b18dfdeceafb6091ba6ccf97430833a2a72d10996c9672f11cefd81ebf89e7081aad8cad2509b2fcb74dfd1d3281acfe8ba798230a2f729257ab6416e08ff891dfcf9abc488be5d91a1f2df2c693e7491c93dad4fa846177bd7f6648b0f417501a0022183f27e8378c7dd2afb30dbc0703e5c331ac913b95a6242140f4ff03d9bc84173c67d6e2f44a74b83c5c1a2e12627742c08b23728aa4a43c392834cf399142d22140b3b00e3d5c8ec1272c3262f11f194bb6437201a1c3db3de2067269c97aa20ad8da730292fad1c4ea584589e26c80a7c3fd72c3f00ccdd9790f23eae355b5f88cbe35ea3a48a2b3f530eb5f728427db36e2721704ec392a3ed067fdc9bb122b5d9fa0b7c4e3e08be026f178f5405d67c60e4dd6e3eac6f1aeb265b54db787e24b348c4f1a2552ef2525a2e0e97f13ff4401720fff02d47f63dc3d57f1180b079d7734dd68d59cf4d95f7ced1c7d90544668729163e40bd820399ea399f2e256a0cd13dab24b8e08ee3dbd012a8759fb9cd0723eef96769fd16bff4c7686548fcf7c4339af08a46e4ad20b99b39fcd1d88fe72d0da9d6daf14343ad80c0a764d3042211cbf75be18e1aff4a1a236258935a072619ca5947f905d4b6d89224a6ce7035ab4c9a6d9a1faac9f7c86c9989d75793b890a9820d3923e019e91ee9b882efdb383a911cf8c63dfda4000aa4a6dd5d2725886984b0f0ea7ca4e5bf8eea5c14a1eba2232baef65302f51e8bde2774781220e82ab80f067034294a63be4de46da044306886300f9e82171753ee5b1806572fada80cc513fddb6d47e0d812aa1275b2f0d3800c9ef2a610ec39b78e409e972aceff22b8ee08afafcf5fdfb3b90c97a7a02bb8acd1de21be128cc77ed73ba4c8fd57513844733321bad7bdd1c250f80a8c8a6edbc7f1205eb5d0caf0771d830c5246bf48896f6117396cb7dd0a1bfe0c71617ae4959d8dd12ce8b316949cb72e096ebf64ff96f875b61b2c918a810374458da6633b4c4578c29343280c7dc3222931786c51061ba926517876a35a0a541e1262a674d2fda1f263bede9810267509013f51e8390596839f1d3e27f737635bad1b0b3d781cb6a97417793ffb37254a8125f6f5f9fb245211dd2be3e090b501e3feef7209fae1897883e38124b720e2fb1f2769bc0151e376bd8b112a3eeb8e5eb01fb6b77e147d0aaf6854998728b59facac1ebd038da2543cea7c60cd8cc08fc7f8c100a2d96eb29cb8c85f5728f455283de5f0285ea8edf3632bda9bcc20df882506890526204f2f8fe6df03dd0ba6605a9d950c90b645f345a72105524bfe40e024e6430bd2e102be0bc7e724a25e2f398d70c9a161c4b890064106a877a02cdbcdd20c3caa0287819e1537286f595f5f2f06dc8dbc830f09b8381d054fcd80569298297cd7411708a543972fce38de8d336915b1d3086f0aaa2b55972bfd72a70be1cab36737aa1ad5b2e729d6234fb7b42fdec3b9a8d9e93cf8cb71114af118e1e78a8f9f2a052c5a4f772530490c9c5c2824ac96b18ce9631af8666ad473bfd84b2ad06f41edffacc03723c7815e3954d60b8ba36a42b4af829951c551b5fbc6d3df595055ec860049f60f8fbb433de2549fe6c3f7729806995fd1a221476b7e5586743aa8a66135c8d6be85254706c350257f4e7ad75dcb008c89dc2119ef350c1a205dcc18d0f941372b7e73011cb9e61149f879b756959c79729a20891a04e85604662b65c8fc3811767dc25b5f243a04a8e9bd3c3c401cda222e2ed3a0dbd2ee0d23529d54a2bc172039067b6c823f7e871cfa4004b0ed4a6460fe34d1d187aa5aeaf11fc640db73e4cc95f100a2809332b76bcb402a9a9b314fa34dcbc1607f08a227a5242d9e37277a48e1024007a7ea2e5b6682870027b3a9353db79de7554066336a256beb22c6aa6672f25998ec1c541e3df157fefa56c9c68d1d782badf3169af5398a9e372f605bb86c703ae27f39ca84fb1b5a3906280932d09358443faefe656bcb2e572c68a7785d5bb0ecca86e380f45316dac7a6fba1a31b67b3667c6e15809a7f97227ce54e1955bede88a4d3a0c523e3e545afd0b0b4360435bb3ed8460e8a80e7233b2f981565dd165285194c99827db039a716ff1cd7bac20ae91aa079437bc72446c905c227f1ec2e4194905b7e3cf72d9bb6d7921d56ca92c5d526302d33357952daa09c4309a44cd9c02b192498452fbb0ac47ea3612b91f64c4cc6b2845725ecb1c2276ca9aaa15035e7b7981fad4cff4713e1b5a235f184b1ddd1a8f662838cccf42c8cf33931b7a8a9e7f7532d5798fa01a4bfec0ad277192db73328c7231f4d9d6da7f0fed73594fa85297657b223e3dda587723bb5d6f4dbbba77b830a33c8ad12395116c185d27753648be6b7cd25b3cce007223c30781883af2d64406220060baa5e9522f14c7853768bd820663fe52879a4504fde9da2fb8a828726779207418c4db57e3d80e26991ab7e8afc647e3a25dc5c175887139d6a6bd7286df64d07c21f26f9ba8007a42fb75979fa6f4a8513bd228e8cc89f565375170e2a4530978976d38eb18fbfa4ef74bbe8cbdc3cd2998c883183d467c324fd660767298fba5fed820db0f4b196df4d511b51beffea3d5ca0f19c55b82f0da9b72f478e62567385fbc8e1b2ecafb15bbda5a075c191a01fba5dc2376347fa24d29e44f997190276b91fa5c4fb9ff3c252466f0b3ae1bbe002346375d0f12c4dd1eedbe0c1aa4364a36d990542b48d8d482bf512fcb21d3b27dbabe9bd278e75921516d1fc30065aa06121f7c7a503db2669497fe93da82dedcbe1df85869ea8472fb63c612730042e4c6769dacd15fa1f7d798d0816c2da774444e2babc3e2b272fb887377d6a7a90abc89cb2d85b7cfd3a27a28511121d69cb6c727b8224c4f7293b1c9ad5e763e9317f097f96dc5f8a75ef901f023a1f9999f11deec28f8e672fc64aabcad284cf243559589f21c59fb2a786e436009a8d718ef60b803fdd30a56c303e6f1a5b90f8223aa288b1232ad0f4c98a6f9164bdc43a081544896d067a309523101c68b151ef5693033207ed18d36b36b68698fadefbfdd61821c7c72ede75359e5839c1802b7095ff8e6a5df50207cdc33d3c8f9061230d8d6c95e7252b9a1a4c86fdd965e5c076e838cac159cda788c63dcce78946b52a4f720ac01cb820f1b770d3f70740179af515b18e137135a464619d8b9cfd322c210fc605695f001f4ea406934f82ea284bebfd4c1be2bd22f0b9b58b0082e55b4cb354c7207efe83ce836aea8503bf6176ee4d9a14905ef77e696020642ee8d0a7d8dc572a66c246398f46f27b0dcf0700336229861315de67a6fba0ced51107ef58b8d30ceee84276dcc1b419aa5c597b635d8d269928c475b355a0a2df223170cfa2b4075b4bcc846fd91917ef375de11f1b37a4a7e63cd860f17b16f3b4e9491af6772ef9265b11c8faaf01bff462bf4c98ba44de07c9844efb145e5075fa976478b72ed9af038fc82d6e8c879e3ef109d3d903e8787ac21919827cb4f962c23672729c4c3a303f0348fa6bf45e5e1d95de2f1dd9c9db243b5260d25f9dede6b91c772ebdc8afbc8dc02f38f343f5cdb946d66c47267bde0accd4616f00d8d8264d272fabc03a22f906abcda0af849b6ca54251a4a08070160ae415abe8fe8e01cee13c85642bb0e6baf03a3e116f8deaa6950d24fde8f00d2bef8c9a230e58f315622e3ccb53806e674ebc6cc9aeadbbd00da8d724cadafc65f0890612f2b8796d67297b459010a8de45497de7038393b1934c52c18916d54455b481f7bab128302726b7d12b7ca3f106c8e27d4311322af22e583ba414ba8fa109d910c29b372a726f9463a311bb9758f121617132dee240d5e453a68cd54fec25981ad0f8d639a72687675373ab7535e51153e5ff79f6c2192d8d7856519c011814a06641f910e0979f12f0dbf850a1fef43c03f4ba9ef50e3278cbe039fb15473306827ca524172ea7f81b4a5ccae92ae3ab8a63fc67f364f3fdf9d0587db23b62a0e4a4dfbee720c4869c8a7353c2b2b3dc88b8d7099040373a470f1c6d0fe1b5117a90ad1fe721d33277f51459e57252ca03348171b6a605cb964e31a03b38a1a0e7c878eeb720b209312e2c06317dc2b350e8239d04cb1cc7305e51f9ae9f545c1a56d43a2405fb9c201c6da53240cd5d36bde439478a8e7b50fc25c433dcdffdb3027d5a3726f2bc1904ecebb929d6c15196a8af23ee02fddd6353078cf4fd484376de7ab1094d078b2d4e0f6db197118ff12676e22db53dba375e1ba3aaa9f737e2a516e0cbcef3023205e91cafc998b577a3537b8e6ecdb920ca59e3d42c0018ade94d73149883fa2ff14b231e6c9bbe7d756149cd8f7932549fc3b5c1a818f56f9fb2272153168db9346a427e248e4595d861a9f3df8bfdb7f067663ee2f81b0961e95728dab4bbb352cfd3c1ff0f392fbe71d29d2dc0194c3907e065d4e1d90ce33ec01a918516b73c890f91463b536971c3e71bc3d19f7361bdfccb53a17a752231072411c3806a156258a0bf9f92ada120f9e91b5b480082b8c3589e61f331cff2f5afcb54da0a1e9438897637a4eb9099d14a775affda454b990b2e54abecd9e5e72d161995796fc1decf8cc26449ab042f0141c8a731828247598c06e8daa4ecf72777262e9dc04870f79eb17b2c40f9a36b6782dcb8ff226a4db1cc128e4e92972147ac351432aa12b7f676c103afb15bdc4ade97f484bd11cccb0f38c0ae5c735190f40788ad4490975952dec7382403de69cef6eab42e10f29ba0ea00f229f723670f88242a429340d5ea22abb8f025d2215c383bf6291c60390649523ff96727693576c86859dbf47881b495e0242d55a5daedaefe802102d08f6c45c9ff77247b231de2dde4cb4d52be8adefcc8feb3992fc6aec9d13e3da929e5a41d8c02fbc4d715db1b6a4f1a54c7f52bedcc52f9388e47121753029854d56519f1edb2f60aa1fdd0bd388980579dad3a79e84c13abac3bfd740fa0721976f001dfd87720ce97f3a03c0fddda7e8f9f12547d1302bb6df1f68b37ab720fd2ec02e77a772b17573154cd4c4a46aefb38f5f555f917b2dc215c81e44f7de27dbd7fd104e7246d51092b9fc8c7efeafd767d25a5c2ed538dc75f8a415b682e0bfd4715baf72cdd094b97725263c662dd990bec1b06d82f1a006de8eba6efd57e7b21ebbf5016b4beb3fff9677df6202f99fb5511f691931c59b762ac27fa93a9de081490357547ed5a02d9cba33050006f1a64976f1e3ae28ea2b85b0938157a1e14a9a4272b847c906836bacebb31dfb80f4be1251a80b25627cf4fa44d9dc946fc959df724294500c384864016cd3b17a579139929c8e80e4f6939d843445bbe6d9fb1972cdcac3a4e137facbf64357ef469fe10657fe8e775230b04bde4e2cef783c4a72654a06da35626496823bd0b18c7e8a7c13594e44a9d0623d4abf2937575277072c306aaed937244feb2f22d0d73c8425e32b57ee42b1b1d73964224b238b4153581c11a5ada5829e1363b83fb4768e0ce12101106b7653709eec44919718b237d3d66fc44c7e27e8151c6143ab2525d7877ba56a0d310d3acb173a70679c927268be8fb2860085873618eaf8f51c025d46d9b3c8941f8639c47130891103ca4fece8af3b8300845e4da9e548cf83cbe43a1c29844da5986dd7127f0b2de5926ab6d03a00ff980d3633de9970ab7db9ed2a86933eeb6fd3471b5f06121484ae72ac6ae6a8fa471fa4ac97b9564f34ea4fb194a94f0c5f0e21641eae4c75bb9433bb9c780aa87856672256415b92fc2cab42e6ae211585c73406bbf538cd72fc46dc5eab85ad3a7cf3caf8de2adf35345c5eee557cfc2470d36ed52bb5fce16458bdb1568f0f7a4e83d4ddcb31a17af0e7663a0f2a938ddc23f4fadd6f93357472381b8c1ddf6959bc8e717b675b4690946d2e098b5a36645b0d60303d9b1161373cc99cdd6ed5ac73a7e0694d5b50e2b256c4c10b10de6ca4ae112f6cd2551b72380eaff36085cfefd8c980bef0e1642a868469c68e054c9c6da6dfa90f7a737293c9d25cc2cdc446793f59dc4bb6431fcf04f631357d75446d402d7ab2bbfe72b0c42fc0d03b465b89717e6b8254a12cee337e94ba6ef1e1449df905a6e26e727a94ba682d62b18558b3341a20ee934bd250a74cdd01034425b297c811678e72c5af82cc62b1c92dbc11ae17979a9f01c3145dd4a8410ff3575022fd5405c8721d459879d830e8816a044b04d87bdd59d06f85add9d0a78bf1cd94e4fd4cb96c28c569955bfcad2b9389c8f7078e0dac449d2b23df7b11ad2666c99a0cbac51f50e57261f5d4d3c04a2397310a7579c3f5048203b7f10cb71e069e40d80b5b72fe163807c7cf8bc5fe6de1ef2e29d2e017ba277c24a37146fe81e9eb186e543d6442c78afc5be8eb077f91bbeb0d42bbddc628978db6d0ae8482f23437997c3f77fe70e8e542e32e40fce3a3f07178b8d23e8c57da91e980e329e9d465d1297239bec29cee8ea6a1d65447ba86003a96f2f5d0bd6e96e88619e83cb415a15e72b5fc893d07f3b2373ec16bacf1edd41b9ce95162927590c0193e965ca942ba723db8785043cc8f773a5b10ba8472062aa5e47dbe65b9fbc18b9961c3e9fab30513b4c65af0430abbf7c0ca76e63fe6dd067f08024e60c0d60d5a845f28d0097200a3603cde356c19dd89923ff9c1cb72ea8390e3415759f35dd1841a04fe43642c4ad2b96c7b85192fbef4cf0c41d7c736dfd2bb01dd1740c994d027e3ae247249d0a55f0795e8b67534cb15c30728e21ea36548b7b13be8eeacf59608d414721c537d115b14590086a37518b350e5bc84a6cc59c99cb53a55e5590b9492d46a7d3779958757af1e62542778382d18b8f8ff49f73a7716ae143740774d654a05e288d5ace6ebc00ef923150dfbdb394e09745597744034df7e0152453599df41e36a88e9f23ded12e8ce6b1b062170e9b1d74247c8d4c798d1a23105d27af706b04d9d6c0699a612d680e9584adb22d23d7fce0e2bfff7bfd1dcf2913885c172517f6f3f26caef96dc863b5a5e898002382e0d602aea9edc9f7fd8297e080f7235a09b2ae91b4a735c00350dacfc9c13c649e94b89f263790b4f5f13bd4a144aa239d01f3c79782731d2f994e410ecac964ed88745468a77928e016e862dd8723a6d5fe2c915706290e4369577d1275d382770103c0493bd94c80acf30d5477215bfa71729cea9fa3685e467b42373a12f8b018af966d4d37575678babd68e3308726e0256e5f7df67aab5537a6f02cb1b1e4ece932aeec878daec1efa8f520cb5686caec537dd7977159cbafce31f44121c78c7f8a22b2011e49136915a9957ee2fb246ed64e22e0fbae50d119fcdc8360ec3f8d86c5dd0e07e765cf7c7af72777bc7345d26db56ff0f29b7e3fb1495dbe45d847a89afdc885d916ac30fec721e25e104f8941ede2be297e26ba10a4ca30b7f16954ff1912dacb11f1f9ede59dcc4c0d999ff7f81580165174094f60a0ab9a73d1b6993e75c767dc7e9fccb72017d34ad9674b83b604d17c9555e68b30398db4c0aec9cb78897fbfd8abd780134d2e3bc939d47743299d639edc6affdced0c1ac0eda24c92f4dfb34b824457295f1ae238fad46cc7f154e09d60d42adf3bdb47d6ca107e5a5fa911470376f1431fb457b04f7727426dd31915a499a1d751847f46c5e2808edcac71295cee27230f05dc644729248d0918688c4d497252226415cd43c5e03d675223c5837a4304ac266bbfbcf1709ec864008cee75e9721c1bd75705e4ac1c22926a4a1324c72829e25e206b6f451f21404c7a9f6489ca58883eff65827e1c187bb1c7a57e772f810b9f82db9458862136b604c0290109da4472f34fc12314d6b14e2b47d0372a7b1f29c7af3d47a02400b18e2ebaa8809ac7d84ad66abddf7b2015409647b0d355f502ad139fe61680e357351874fa5715b4722fb7a3b0854dd1efae1921a0bbbb0b4c81f59afed64e3ee5078c0c6ebd4883222fd4da3a3c85810bd1d3b5b7256430de7b2db39fe08cd2735cb2212debe73e63aaf9047c31266d03786a1ea7231be3e0b1cc31a335bae2ae6849d335a310a8391eeb1bc5245c335a5f43b9d02aabd6d47126711df3a62d065cef4e95a37cd7b64264a324859647881615e4f721891e53e8684d36a65aec58ff8d3070012fc84c61b417123962d3a6686957129ecaa636571b3a7ff308ea148fdddfb62b4c746a46b702c9e517ed7615e3b4d390a70e6d4847819fd48f05c8b5fb5cc787c2c2b3b22c67179270371db78ad646136f3a1ff6a03e1a7034f88decb13275cef499c305a9d4996e135b05cde390f72a57cebd78048170a998b28e10d5ab008ffb75689b0eb65965dff8795e76d9b729cabbbe2137d10b1c7984c757ca7c131237a87e6715ddbec004354f168e42b72a8873b41941af36d7f996aa33ea5308621c472eae205a9d421dee1ee1aaf3a1ee20c350b784d5abee87344bef769b643e548f8402f47c183727478ce95964518a43d613997ecf49ced3166299ac1cac92203818fa68e998608db837228ce43724f9c539da3d05999621fcd89fe495931890a02ca65089085501f5d9b50c9bf72bcb5cbfe134ca32dacbb20a20aa143846f57a20f0ae71ed3ab95014567b48852662f29fb530b0eadace696568ca2dfe648a5f3eca306c55e1229118582cbd6721a4d7fed14772db5538b715a4403d7b78c4866cd8b7d11bbe870d93c11c88472eddf0944803a0b43b2e3b78d6357fb987372cda30a666b8e078ddfed1e326e7252cb0046dfa6e191b56e07b349b710413b50ffef061488d5dda8c4a01da2e3728ee5b08be61651aee441d383ce4dd37184b237953a78c89732629c918072817293a53e477ba7faf60793c77f0ada039d0a24090129c5df46f4c883416e43f0726f73a6eec1ef1915e0ad8b5d43cde2bcb0a3a1fb3dec215cc3e0b93e7f9ac717c01445ab483237accc016e4c81f7cc97ab75b6573446c1d5a1382085d6ac60724189d305ee54b50c2af27549762c3606ecbd9df34b3748d578f666e2eca02a72d8a844249337dd34e115a260a7ddaa1ded092ef28684df35a9114d1b3e81c872034a119d301b22f1da6df948a1f7bf473a902ac0509d9b66900e858849f21e727dad191a08cb85ebd6945f908bff7de00a363d56062e7ba9952e017f175b757231f94bcc09919e238155bfec45916e477fc971af5cedfbf14888771dc3927472b404c0d33b976e2f00ee5775e28ac6d2529222e64eb4e527edb10b7b5f7c2e72182432d8320fa78f8ca9710f6d9baee2deab7603e32948acaab66aed0d4fee7295cdce70dee2a173b2c6eb8d0b6fd7550e4fa09e6413f6862c07bde51f6cb272b5f5c2fc43becf065098a35a3e997cf5254dae022fbec3e7376afe1a86601e4fde028f8a2b0d501e91fb199ccc74505b561fa15f387930855ca2aae82ac80f72b7648b2257cd4f8e496adaf085c1eeccb28656b60dda8c5c6daffa57fba34f72d2bf80dc47d6dd7240f598e74436769408c11b66f841a0d119767bf49a24b97224a9388df0e69a33f7a3f73a611565a0ad6b241633ec94a069c42b8a3b18c56e6c21899654301fa5b327360ffa6d97af025fd14c2872cba9882c4332a76ea46bd8d03d1b8e55765b1a881205dd6582aff4e70a1619a0042d5bab61e810496772a09d469c6e7353fab53f716522e1da1058297322673327c8384a45af62028b18da040e22915ae34842d7b302abf4d7fd23bfa53927dcb17745eb31834c7c9272dcb42d3b1960343ed6fa3c88812201a9487097c0da0f330ab5b4d83dad7e3672ced745cee0e1ff8a3591e6ee13b87c46977e9f85f6ca36a2a561ab99d8b34272affdb81a194f59b390a973efe3e27166a0622d0dc3e3a7c93e4cbc838b3edf712bc73d763d0ae45e4664bd73530c91f06cadfbb334bf36fc3c9b7f0c3fcea40a2fe8aea4a1b95fb4a575e3f0a575afe56e1b51e50cda5bdba48ef9d5c666b0729c62c7378cab9e3efa9a7dad69f133526a1ca805cffbb2c01f0bca43d1059622b2aa7ec34ca32a8e6813f7bb3e92fdcb53f0b8d3e14cd010ccc6eabdb0c42f72d98bd5a5eb45f7d1363245e6e6f1b6f5bb7de8aba0340a702a0469a58453025f34d1eb45c26173705a1d8e1ef043a7a4faa45dc17a5dcd2d2209f553c9451145512d5547f38527dd5109649c2728dab3619476931cc88a61c52e335190b899721f9e889597a42515eb6c4eb874f47e1e9a61ae9c47566de7ef1a62f7e8d1353d27d28a2bf6049727f652bff16624e9046d169b9c1e559c20ae45c03454791362d0890bedd57cf8fd6fc8245e17aa669657664f9f89abc63aec475999b8e1f661a1008383d344cd8cc815e107df5cbfbd6f7c0e3cd57187e6209253c58db5a451098788c54afbdc8b2c4a95f3e74a0f0e61c705986de745ea586964c76e8c31464163e4de535512b60e79db6baed3be2652d5d54a4ee03925e7416de0493ef472f4e284a489fb36cccb8b62a4e7342cc011a092cc63eeeaf497eb67eb24cb457287c418d43b822ec8e75b025ec8aac78ec5b629ad5fe990998ff82358ec5a3c723f5ae99683b0cfbe546d806a2df1aa0ad6875d0b59b9ea460d901fcfaf18911d297daf5c442ffbe3ec853063e3c46a6a17221b989dff0cbad4c70a1b1417ec263cbe9a07ed96fae82069503d7bf7cab1497204a289da3ff64ce056f03fe0a21ff787f8cc268c77bcea8a263c18a9b8d27e945cb7a1b3c0c75e29a713fcd8b017aa9518ee4a8f62550fde6ae8dbb363add3779c01193f2550dc0afd30e4293a2ca111a56c9fc3c322820de5a96ec55a5e35d66ccc7f036a0a8958e68686d25604dc5c10f960cd9ec40c18ac2a7b8c54709d07bc09ff6330a4948e619785da663b90d3c74f4b9f4c70c47508daaee2ea33b2119452074225a50d786c581dc99272102cf5efbfcdb14620bb777a4ee7d7d63c85fb41c9b93860d344b027e21363728df839aa0a1c15b532789ed33eea3218478cdb64155db3715c2713bb2480fb170ba6a01f6828056e209d3f78c556fcd6c6cafb4a099f33fd3dedc3da3c3f9e6e0798f1888e0db26c1746584c3a5232425610bc76aca10bb45413ce6f10e26672b8ddd069968c0bf191eab1ad6189865bcc365cb5c89619ab785ff90e720dab6df6d7299945a63fce25b299ebeec2038f340b292b74e57b091588138fb598837286b0218dff6bd03a6cd137e861d38d411f5e43a80264bc1e0df9b58cdda7d236487c40a96638933396232bc2fd5cd6334f15e59566eacda4175379eb2e8eb168b74b176d0564381a4c4bc5ca2de504ff105c812f248fbcc9f18114a032016b7208f3c4742d88adba5579af6409f57a156e4f1c7fcf4d01bc02774cb44ca9447255562a2fbd50c531e9baf1ee7589ce2e428464a46e96d3fc74ba09901712bd72e1de9443b146384d37b6ee7b78b9c40b12c5b9becc2f02bb17499f49cf7d073a55e088ba53162e8def949a54ab5ac84166f56ca43f0a240a35744ead7e912733abd954c8e15ab5ea87f2707a7ca08c0ede09f339ff14f5a36e1956b2997d8b07a366a60d4fd601be4e7e29ef2ef37b0b5c69aacba2d0f719ca21ab6bf1ae517251ecf2f17f2458195602608e4a87821f27ab902f0bec03bf54ec914ba8e9bf54dabaf6b3925ed5d1c913c9c84e67d32668208e58f6a7fd2dc709ff063f5c8d09ee2d99b34c507f2a5c027ed81fd42188bbcb086c0244e30362d7133d4adbf707157b57d8ca128b2cb876d1f3cd6739a0ad4877743b4a987ccfa75fee96ee7472815e284fdb3c22b7905998abdad5081ce2b64ca1f88017b139a4e4a032cbc272097a21b3e38a5077bf6e158e569e32a901036cbec3a0d1aa785a4f696116510121cb2d3c43b06f77813b51ef4d3d88f646a666603464c368fdc18407c2168a7265ae72c56766057ab854833259695156f6cccd996d10ceecec5f3527cf9a171b3a16d5f8e3f392a2d624a7a21035152bbc43b9ef93feaebab0eca86a1fa3ef72ff17a1d5e89809d47054ddf10b8b10a47e28988564625cd375921cc5eb68e5690cb84c1998608af57d3471d1cd35375d930907070aadd18bb72face88cc43e1e0e8f97b7ab6c5b229b913c826b0b1d80d1bf43c7acdafa9ed7b83a73932d3f727510ce6a1a94a9ca51ae199fdeae97634ddee8c49f98e70661a342188507f772a5454177d5c276de03c31f515e815ed7a7570521e6f52d8dbccc999c9e30c472b12e1d6c4f058b0298f1c130edaca88ec752a8e669a964e112c594243e783d5ada714350982e221770bd2591eb4536d6bee42de2dda20940de4fcc6544459c23bf83667f8daf2ed7d4fd820a63a5a29058b7a81197585e26bd030ae23e45f91fba8b0298da64bb79070cead4e896a8ecfd87faaf336a4511b01289c1adf2ff723ae2dae67dbc0bc3c11c25808bfa06d60f757455a31b6c330a91121a00ef4b2427223d107e71f4903516e59016032ba295c09de91bfb07b6c91a13a781c5c4519463a4cdaea8ad97bc33d40fd98f87002fc1afeec65221f381089abcb3025233d28fcfa391d6c2ebc457581e0e96f919b972a7a4e9d00c374ec9a96490653372d66dabd7eb71c6a04fa80785a4f8ae3206412204d696f894ffd4167bc9598768dfba91d775b7a5b48a87159957e3a8d55f761595888f54abea70edf86c8ac32653ad2699deb92c093a8484deaf4e4803863d32dc6b4299cd6d8367821a641714c4308c0c0f66c1eebb4ff5bc730daf149693c482c1d67a3486245a76318fe8726b0f63100ee1fce8d7b876728ee5c74fb33b44f0924f6c9f8cea0a0283cbb7597b6a126eb766a877214b86a4fea51d830c690a5573400aaba9ab6f6fbe3a1572f5c86c9eee2318ffacf62434b1a09b8e8d8b81d510f5bd79a805befe87f60572fa0d6a15e342ae19799c79843ae0024e3a35da55e4928d85bc916f6b96543072817671056546c47f25f0b7fff1e5d496115dadace44ae7e2a3c9a3bb569b060980141a764a0f37889aa41d030ac7e5a594429226a16bba7750671a04e531a04c5d415ff8e0570da96545022fc8fa6b6cea5d5f12d0b7e7252077cd50f0598321265190bbc742ab9631383426e3dab9717de13e31bb238b492b4842c27f464b018f767bbbac6b479cb49bd91b380721871e4d7af2c6486154e8adf4ab51e8792258831e64e01d17533fb4be796132edbbe75a021a09eceb6da3bc42f27342d272a266826a4e06c3ae82ab9ab41bb1bb78a9c57cf085ac0ca15d8e4806aa9f717204fa8f4db7a40fc9588c04407a7c44c3749dd776ca8bcb94cc6d8f851bac0672793b7c9e237c6e6f232184b930f7591e21a88b24b47e82e28a2e44d26f1aab7278fd5e6539e9efd7538f710dd6ea7d95a8f7a1d02f2fa9e13e536fb58612e472dc02c0f382f54937c57f5ca40065c6850ae97511da48c9efaf9da68c2c32196e5369cbc0ebcdbb7f80b55779c0f17c7bb3e3a2ba0a97fa9e392165e955254472040dfa85e03077f0d10c837ae58d5b4da3bb4b596cd4fb39d332140282860823d270ec4a69e57bb791743e6718cb28f5981d32ea319425535f258eb7fb54de6a4dcd43fc51604ba97442165433ae0824b190a463b3ced6548bc3175c92904672302e7283821af1b923e01eec5ec24edca9d354c81a89a327952b3edd2071e972d46dbd6612b31fc8ae37d8db67d4b33d67327a796de2561d30364bb3fd9b494fe3195a58a410d559c0c387039d79019d78128a079fc8009fbb126204b41e80723929b27b92ef2ec0c9bb4a6b10eef321ba8313233a5fff0a1f8947b43e4daa1e0790fa584ecacae445400d348b32cde75f6bd56b999ccdfeebfad743448e5a7203bb0922addc8253fb57e46f2fb56dfeb7235459fdf45bca4bc1c234f327ac450c7bcf0f96671d9544a449b0231415f5a96a0261e780553c36f799f149123f7281b0c8b2fd09ae41dde73e68fcec2d7eef70e08eac064bf43b3370a5e45a95724d15272a51d3d66bdc7b610359407f44cf971378093c98772f0e804990301072dc6f97079080fde6e0d35af2701d50ff861a57e77f5d9115c572d42a9648787240c90bd6ef3d4255d2009275757c30017434dd617b13e355becb711166cfc33cc87d747abd0595ca61d1bbbd91d4410950d6f6037275ad3188b6d46a1ecc5963d3ef7ad4d8b70833b9e04208ad40cb67cd2a2ac3c0928257b4dd7ddef6feec72e1359e20f7a85f2a61481e3606a30ae7adc0c0b46e41769462842fc1408970510f6d6ac2655160419bda836e7a9dd9f1b89fcb0163fb88f776abe67c8c2af956e89f527a71e9ccc0900b94759e6a4f9fb88a19af1a972f8879800658ea641e72f2ad3f5db5d1ad8d2a81f013df09b2c9cda157ee5bfbc6070bb0e2207c069e729efe35eecc2d2f9224da1b874af46f18ddccd2c9160baf1ca352fd84a90790699bc512aebd5fc09a6579206802258ce633dbc651e2aec8329a2910ca73fb075099b4a0efd255944de9e1b80e704f14dd8da19924f6d8da189b3a4fe524ff51535e8903e4ab90fdc5a6088068ef196afc796b3c1a5ad7a1a278fbb52a36f0a165882778ad4bd6ae603e20309b999a1af5df368d3fc89c9ab55aa602d915fbcd7230296819963b3b95e38ad645bcb843ec4207c6720d10cb5f8fe48ee73b078072dc85f064173a2d4cf66b1afd9d5e71747410a8237ed30ced9c2a0a8a9c597222925a8f896c2f506ea3976052d1e8e02d8fcf04c11537754e3cc4c385e9bb7410bda5f31ac30a98b9213bb0dbdeb213506a555cb56e9ece839e63f8d9fd1305726add0225ffef0482a1381810851e1f887fa97b9d6da8a4c4048ae0924fdd487288d6838ae4490c5da06fec52ee38074c1daa6b0cdd944bae07abbd4055f08751fc357254223d3d2381fd6655c1ca21aa0e996881ffbd36d622f4037442fa20020deef65a748c4df615abaf30d51dc093d19cbe0fd4fdda2f6be8512f30727272d5132859884700a5aa529b1fd3f1c9ee3899a8faf7245d7a5baa04c25aae9472a5a778e6e6b917a9fb82cf59de4268616e7550c3061d81b62d0fda9e192a1f7297dcffcf6070706a07f77c8f51f5449775ba615cedf327035960c1cb2e9989726b4c930bfaea626347943a7647ad51729e2b92868f8342dc56b1a2bd8931387269c9eba9590db8a8ad21ed56a621209be7bf9cdef1ff33064e6f5695ce94374d362319fc231fa133c943a37dfc2779ac6d03b212fac82a3bc192cf2f0092e76b4aad752973787d4fa971c9f3a7a5a7d09c76daca90ae15dc060a2416435d003ce56448b972f83de54251217360989d4e30163f64166e308bf2eb34ed191d926d435e1b182717a763bc375cc5974205763aafc00d9302ab4d9f47e9dcefa93e0a84690f9370b97110f71a3dd57b03ea8457ee752fa302a5cd668871e6cd3e95726631303b00d43ec9f37d50ca05673772186522b6fb37f44c03b5f324bdb4bc72b3711bc18a726b74569c92969a687ac8bbec73aad0b8eba792d9ed3c9570e972161f7b10f7b66b88a36437546a64a7cdb020036441fd40a6fda7a7131176b618f83a257cbbd3278529491d76965bcbfd6184941a093c68e2511cbfbda5e96372ca8aa35ca6ab142f95d33c771be26b42177ec33596a8484d5f755b67be101e723c88ee7c4d00217a9c8722b8bac8a357b1f803459be08d555c7e6dd5d349fa72f6874f4203b8ad01a9bad8db1360d4e20fdf0f6cc064c61678043dce7c501f07e994c0841892e6fb10608e37fac3c2c4c2911e568915376beec652f19afb2905cf00ee076786dcc6f10c621cb2a643d8a93c900b5682966895840381f85b7772a8b7998676453b76bec627b6cb28e312c676ffe69de5af9c36d5009ca14de8725589c0ab3b401740e85880bf9a7376390bcf6bace1f49e6661e2e9cdbfa5f149a17be6252a616d50156c0b56c0904beab842d5c5a34a9999065583a997b0050abff32de3cfdd24de6b9622c3824230a200334aa73fcff61a470cdf5f0b916b72a0b1355c5b849162bd7ec4d56d07f64ede2c20bc4b7865fe88ca5258c8d5d01961cb78dcf10ac454f6a81a58b191aaef32d54a63216d41c685b437cc33d1c07216cd0f67f1472e9f13248e1a7df3f714b5e17749baf7084e6d4a90c49e66ea381a1daac3a5000dd8aa9fee3c4b750e4e8a9edfd4961b1ece03bcdc01d68a8526e5ec809b1ed88fe32aa8344e7c0b4636f568f14e1b55334715adb29231910c5ab71b53698f5875706d387f71884ea1e707df956050f4cf921681a39c0c38582db7dba27a162fd4d013be7022a6a8cb94fa8d1e4ef0e2fafd8c513fbb1017a622c70fadbbc6fe656d55ee8b38046f8560fe91cb91fa472d8b64375103d2b6a16eac9263014c08fefd334cb937820b3e9149a96cc78dd2ae921caaf0f4e237ea2e03920e87fa1c9a898c973217b616f38f754f9e2e18dc9f10a0376c7d191602652be6290e49932f0b9aab738bba819eec1aea1340bcc9dc906654b3ae20f7d608ef2ed9bb9728d5043adcdb4ee12142c3832461c3a762b75f3b4c6a81bf124a25847d4a91bd6e66f84436ea49bf6771fd8339b09a10024d3f8c082fdeeab8ec72a94a1bc11fb7370337add610b377672b2d7f8c5fee00fcec21067b20b9a98b72dc5b84240378d21221d5fce38a6ac2526a3fd6a062c14a97d2d5ce019046ca62b59ad2bc4d8b94dce471633012e55d3712538fccf67aa4f214e5bc95baa12c72edda9406c9310420fc40b99ae4d8ef6e590c4e57281c22153094f4b9d594a75dd6642b1e4751f055ccd08040c586e98764080c56cc299bcf0b98eae72c93a572623648876b07f621703b6cb680f74cb2605a47318959c3036694a3a67617b91a2a5c028655c624b6acdbd4dd60ecbb5d91a5fba3d6c0f8b7c321afbfef206a614e4d232fd703a786e67d97a8e2797b3c4a27813e22199be22a2d4e5b1e28c172f1e50d23195f6ad64714fe5e3ba068c5d770c8087d7e5cd1307fa79ba2bb03135c49ef1d36d71bd15a2def684f695d7807cf767f415159ba459b1f424289607250942387e92ea8e3ad5c75e41ecf822a875a6aa315e1d8441b3c1a96ce31a0720732e825dc229e00afa5126beaf5d62fe6daa9439a36f75af69043c7e090de6364128a86ef446d8befd6e0f10870fe6122d17fef83a2e39850b06a27d659c8725ec94c8a12fcda819853133b6f6fbe607d593ee1602a62655eaa1c1e4a405a4a78fcc02967c2d2b83b6447fffe2ec81a682e50909e3d0b5482877d985cb4d0722245da967393fc72f849b76914869c6cc2362bd1678ff3513b40c622d3773572cbe9ce2c997e539cb4aa5d36f2764162a6b52e57d6ec0a5770c6e93460aefb72aa85dab5af4575972162d01a052ab238468e481f831a70b328630490de844272374a588a13fb405535bf0717caf77c6b40642469680760e7ae7eca4ca5d31772bc602df1907c62ebe035ccd2a86519e9556c11ab332bce800783ddc2fe914b72377bfc78de7c01aa750fecc5a1eb2afc1de669418d9ebc4eed644cc83477544e41ffe40871dc50ed1a111cb22d6d29010d1a579fa653a45a850c2c82b395012457c5768b61d850a0d6ab12a83f2c8205b29478964e71da78bc53f51655cbd5728d303eafa46563a578bc6f6dbb16576ba58f05370f56e3136302e8240055d872ff0209bd2f32517e704f0def3213b82020dc937e12e69360c171558d971e84720b11e72024f1fecb5cb838d5792abb20c0ecf840fbcaca5aa3381fd133e1440eb7da42ec424eee268e10ebbe3a3522621faecc1bfc266a29b30e808f34946f33f1efa79b2ad66a31511f3f3f4965e67e5fa78e838c2e73c2e4626678d60ece72f1db3dac82c4ad22f2d6fa4d507445418b0e8fa4b4277728088c98e5b6ecf21cc14cbb4b1a5bcbdb5818f31736fcc01414b7a6849b4bab431cdf75ff658bed0b3f0792091bbf5697f6cba1da9094abf439278ffb47b64758e6067d771085bc726a319a0d52cd3164707a4e01aef63c5802bc263105a68f60e4376996df5ac6720988d4d62b3b7d702ebe318a36082d38bc83f3e3356768bd51d788efc76bb1720688bdd62fdde42361506f3ba80e2052b9578e4b9a92695bc0dc701002894772e370e0ab53bd28e5e1a5925195306f64678ce5f36ab4c59547e3581b53b3f86b7980ed756ac297e16864e005612bca2246de84bdf55cc57ed78eda8bd7cb0772c5f064aa654332e6a8b3c850c58aa5c572b376b8d4edc8d3471df1a4c1495b0c334912425819df2d6c52452d27e009741ce15503f30912380772783f7b1501531b800c2659b7a0c6c6d1b37ef61654dce83c4156ff56fd132a354e6de8218041f5c99d352ad4c6a84c7d88993b0deceb2dc9f0cd749a51c29c0d0a3c4301c9553add66bf76591d87cc753d388aaccc7d3b37241a00fa4c0203493c6059a2da721dabfdbbaf0894273946598d5bc8d7783ab5b2752ee65a9db1418e51a8995e46def224c01e90b11c7d67ca3ff686f96095b08b1a608bbe24c22e0e13367550359049901666e404137eb64226209fd5c272f271e120cad11a97e3c21409e184726d2a6a7d75a795e140431c5906b1fa2d37c82543f528810005b342682a64087278a4ce9768b9e35802210deaa1f942263c5acb11001c114ef3c919b2d8a70572c4e4eaa30ceb3fd1ca1fa516ba7094e42ed7716f40536ed2cb8ae28ad0c3a472db7a39d7b295e8df908f76c2c904ea8a6c7ebc3396afca22f4609b3190f6cb72330bc7cc198949350da0439d3bd57d44cee525b995b41c79c6fb0087df1f3018c4eefc86fedae9747de75fb4db9fe700d17c8091e824d1566509c3459d800372ae5e5895234263130eeb07f27b025406591e86ba2ab9060c6b6a2d270c2ff872c15b4f2ac5d5f8362c00b4b2daf9834ede010fff77aec6b20272a86b2ddbbd725f08b8b3122e69f4ed632c67327f24a38c15afffbfb0274bbc31813e5f294b72fa8cace66717e675ae374d26208d355cc53553f443325430fcb42206bf09e84d1df2e74d9194df67611b9249fe1d412c712083eea173c20868940ff53ac3cb72a148049eb8cb3b5a5744c6c7ae73c56dbc5e6fde3ab2e4d892ffa74fb648e0722995b092893115fc2a85cb6bb9e7ab388a136b0c4aea075024afe7771a3dc27264ff67067d9054d08fede5ce7ea78ffc2ad589dd1f1fc51ec5971cd9e0321672971f820577923b52af4525249d5c000d14169eeeba46b37fcab8de414e112256dda4a8ea63f549d8b77bc7da591e2b7c7d1a1edf011a1fe682d197bd9116a110b62351d8a73dc971d5ed828664bc07a522a71b28477f969b2214dc5a5278a136ad73878a9bf94f42fa268c6cd3820033e9ea93ad1d14ca3d689b8fc5cc9a1b72ff1011da00c773c726dacea1582ad8365ba10511efbb641dd5e541ab474ec272b76d4541a669c266d6d29f283586bbea411cedbb1fac89b7c6db34bbfde8817227a23090e7f2fa4028f0459539b916e914719d54e36430457849f9478605a4728bbdb033bd60aa04c50ed9fe026a4cf0916886cc56fe0f088a01123f9d5a4135b713ea35d73a7306ef2dff6af58077b1197af8a3211294b458e3a3fd7f451c72fdd328bdefe6e4dcc31b34770085c0405f2b4f00a4cd0bc4988abf936ff11543c1f700f379fed8250187335b39a58a98f6cd4016bd3eac18be052dc36943095fbce3582051a8b893b17340e5e968c8faf368d7915fe58fdfbe8b3abe5291b0024d7301ab40caf5031d11b79aa4d4580a6fa96d3566c1333951d750ff1d9a035b4940059516082ce09f4b9e7c6bee292c7393e464f717a34984f9e6b0f40e367221cec48216e2f21c9cf65d66249727ed8c8d4ca7bbf6e8ee36471ababec8c27294957d870fcee874302d67366d4d34a3de61f7ef789165764c0b7e869c374272737e00f9f95ab7da97d3b050878dbd8d7bdc8bd0f73925c2b2b76cd610e04b725e29f9115a7c2958bbc3fe57dff4ccbc4aded10d58801fc2060187b3569d8b727e392af32fe5aadcafcfe73292ce08b4968a3d0aee95d863bdded571da03d372c673e60832605816dc93ffd3c151ab0c222bf44a347dbb25edd2852ba9897272591157f85eb6811983a39cc45f91e4642240ee75089e6c8cb24ec4ba83dae172d22bd566fec48d32029ef5a344f7c21a07023a062b968b57fbee712ddb3da01b17627ae8062100e3c15df3471626c51232faf4cc3de87ad21204fafac6944b7253c231a005c74343d9562a583316b2585bed8171ce9595aca34da9ba62263007f1021ac7e189984b46b03993979fb4c0a6aa5c1e96f30e101ca871088368a47248f1f0566fc29422f475dc544b7dc251521a185b511f05c065ffec6a6e94f8460b52d6069f226154c71b32b3a183086a6779398ba22fcddf6f7231949b23097273a2c8edf08380462bd812dd01e80a1a25e0af9f6ad956919ca9109caba2031c44ffab2d2a4ca910bc58de1c15e0746f2f16f547076f347aaf7bd6e44a1feb5ecec6eae2b585f7cd8371b5ff947e4c26a2744d5a60694cb03c5af8c239498e4e74fdf74f7a2c9196147ff84cd28c87fafa99281695b300ac1a867ac30e5db0725b14b9d79a588873e22ce01ec45e977c7599b32ccd450b8fb737b374845ce8722d91d8a79b9f93df9c0e36d002166e2513102e0f6f8916921cc86e24a692f7724f04b3a0a04d31e8f636cc2de508bc97d612e052f2d4bfe19b5ac697da713c72244db35f2e62609812da19f22e7bf62ce56de6e6680d46e431605a443801a6611f8a5fa2c6c439899a5f60e4e9a4a51dee88dfa0600293171850c25647c79c72dff107b563cd205794609258dedb56f6f86f75d40fbc2fc81443522f2927fc534b253028aca58bfa7fa50d24f735767eb688a8a45d4bc0b79f0fb9744753270f3b0806f8b6fe3a06cdbf3c6390991be8d80257d77ba9e52202982e82b85944720eda5d2e7f563cf0a1d8448f3ce4284391e38ff50da8621669f41a72fa631a7233411d24ca414c50d361e0953d88b9fb72d2f5634ffef7342fff4ac6ecba497216ce8021b750a7afc40d435a116628e94e51a10a639270980369db8158d1d57202a157ad5d2de08464ad1bf232caad53e035ae05ee986468c1c38fa43200ad72bbe41bb4b01945a5b8a5c19273a7b498987fc0400535c817d2d8771dd6395d0365a4c0ef850edb0f77b71c61b1fcb38521cd9e4a994528a494cf6928125663541aefa1cbf2da4e453d2c2ee58d6eaa60f286e6ef0b8ab70c08a7a495c947887266efea9ef4a0e6a236b27e380bf4679df4c188d69471810f4b3e31aaf1b5716f9dddcf003f493ddd1bd867e147c0cd62f2431fe929ac284b60d7e805e022724a53d7260a55a7d308bf3dfb8d656f28a19728f4286863f8c4c9d1db773a0d08696cc773eb2bf2971d4777363dcb40f41f5bf52751c24c5a95388a88330487bf727b94dfa2ff8094d94883973366ebe5d1efb25e3e45213de5b5d3a603026cc272e9604a0e82cff023507c59ce336a0a542541b9f8d52c3ed8d6559a0b196af45ddc19c993c390f3c21ae9dc968856817d86aba4addbcc962b4c87565f969c89728ee88bd2e14e3fe8de6e781f02cc3e52f58923b2797fe4780c70f83386c183725801f505ca8209ab13ac394f384202464a7010f2a5f4e505c1300d32843cad72454e0772f971df59639c96510772120a8bcd76f802893d5699bd9ef758967e72aa990d2ba44d25c70127a6d6639c3f81fe7ad157fe637e35b6ada8b0f452297264e1f8992b012be12802ab3e45c0e5ae6417a79cd013ace6113fa140fd46d53289280cc7ba99c572645f965449a31c38d516476363c5d43dcaf3f3b174fb5272b44cbfea358d3784175f2f7e1d038acc1182ac692f7b9cec89d153a657d62146aae3fded12f09770c2e267624fa3c0f8353584def83d000e8ac89c1ac90dff6fe2d57d23ae2bfa3ae78a074c469d6ac1c86113b5bd467bee8bfcd87cf3dea755e5d0f5c8d96bc607f97b48c5fce65b93b69c0c4663080c036f4fdc8b5644760ac3bed8ae44f8685be736b68979aa1599a36539b2504d75dd136e8a30802cc1723dae8799a1e39f6aa3a92de63726f1aeec63e33b875f5b39dcf54911a99f3e72eedfed5b05056366b1afc2d700c3a9a3b18ddd07aedd1f90aa7df417bd1ad6728711c5486866251c70b0fac037f64a44f1f840b6d40eb3ec7f5fb27d0c34eb7247b41e3a6cac9b5e71d75d18f72b3c0756a013176001f6c16d331891dca4a172919662feb35a4699c98ba340706d1ba9446ee0c9cdf0797ae64121e21f4e14729be0494e21ee7c982943b8ef661ebb6439d656a40b758e0efab416276d1520724c6066956eeb9c5f59ceb54a72cda589567ca42bc466c5d9368053f69a16c172fa4dda8be88f47407d816386abae24085807bb127e988ef9a7ef82504c1c414d90f0750dbbdc728801cd40cd583809a08a6a14354769e352800d50508d7536722d30f6703e78e19782f78ef00b9c8832e3f483bafd66961b780442a007123e0083763a549a1bf9491bb11dd47a12fbb42ca904a0b3f019a6247eacd2ae3f937295d4e1d3c1a10de69ee08ede2f25846fab80aae05717ff3a5ac262593652aa462f94ade3cff0ac95bf49d79720154d481afe41ce6be75b04ebe4515c33e866723e90e78c107e41cd84057acfa7a2d00de64b6f303ca5b4cedc875549b511106ac0d74cd938935a7abcedc4c8713f877d08092f621da5adb88d2f0e43cda2c372979ffa88ededdafe24d640f025d75b0abf4bed045d5fe208196c4ea9cd1e0a3a6060f0f0f045951cf4df27999ee77a1069d8fcc6e24332b6adfb4e48a611921b9a81471a159172c26e31748871e5a442acb704db25af4df41f66a84e04899f32c211cc363185198f5a2b2aae6dc68e097af3338ecd5032b9e9b2daff9722b96e06abefeb17ccf0a31223d96b686cd68177e20859f00cc1ec42c97ba8dd575272ac6e521efe9ed6152490024617587026216d0bfffa273b66aea50e1f3cb30c7229bbc1e45095ddfb0ed1338b0424ea5418983ba7c75082abc9a26bf9ec666e40f51990ca1d658ce4a42ff4f52baeda88d1971c529169bae394a71aa111afc40ae2a56842e3e352b467726a3ff4719037c78cc5bdbc4531fe7f7352f76c7db872f79421aaedccea80869c4fe7ff4ca4f0a9f1c6354be2f5b024ebdb15842c7c72c347b88718e43e4574f07d38c399766eec066363f9126c990675f3efecb6bf7238c18689de0c4d8d0ae162958a4eb3e08d7fe7594eb6579b516cd3a73bdd5566fbcece87eccc35b13a505ff8745bd8b9aa570fa92bae8d84928a11fd4c02153590ff24c120b9c4e300b51e21c6f0196ab6fbbea27b6ecae3cbfdb95b37f77f720f5597c43881a3d373b96f37ca67be5fe7cc40342705f848500b3150d2cfed72621393dc73f4bde77197e94066570aa42da090ec8392f1f9893504f29c4e761d6c1b0f142ae50a12d4cd78b1a0cd67fa41642867552a0e2da909cf5c6db61472724360fda683256e65f5626f412702017fc92577d3df37d51e552467b923082f69f3fbb974b22f76edbeb90fb1534a5a989ca51af345212c5f037a07db9bad725c21237a3959fc013efcb5bacad59e4d4a1c4f95e83464bc5d1756f81a4cc65f4dd54adb61e63db7ecd9b3ffa974d0257ff026d4322fa5a53f5c82437e25a35390996a1e22fa6c73fdc1a919d2df2af7a1911752a5d67ddb2a6aad74d22f0a2267017bbee85da656dd9e2c53376ad0db2f2bc99820fac55ccd9a0526cdd1f432fd0b102e7bb95b7ea54fad981e07953929a11abb964c9da192969128a034fc5e688f8b65e24b4a658d0b63afa8384d833739959eb4cba57d92c45e235782117279e0b8c72f3b31cc2eb1ca6fe89004227ff2431507fe4cef315ffb75db03c772c1bb3cc74c045a8ea07dfd163a11028d18a48b93237930b31429d4a7352cb66ba43a716ff2e6400701595d08778a686ec43f41e79d3636393e58a118653bfc62ea233d7c8e7184d2f12ef40cb2219c497740f884972c5a14688b06c269b5f02aaba005797ed0abba50fcd1c33ad409837274ac909b44301ced797d07a9bad27203ce00625642fcf2a6b930d0b9327dfcf93aaf8bcf7256e05b869edd782b632a42d8eb6844c1053da2a27745a3e593df98837091706b5cae59d2345de5d05210bd83129d70c93d5f63df904fc49f52ab85c22d6e40c70a674f91005198836d3f13cd45faa715f9f5d40ae8e6fc6a8d91d1b7976f70a9bfc651b0c8d6f0c065720710ab228f557a6124ed0fca12a459d4ee3f8924cc9ec9b98f82d6ccdd2b2138985c3332ae477dd4dcde9875ae6cb1d74b8413cea5d6fe373ae86a246f8cb272fe894ae3d78b194f74d54cd1e7ae8519ce26a0e4b03c46e47c8be8e8342f92220f2fdd04e42c547cdff584c2f4cde8d033ec6c9223287e244f7c9aab99f100721358f3a907baae37350cb0ae4da13a23af6430db64066496c591a6f37ebea5555cedfa8f815426e36fc533e50ca9e44d3a28cca45cb4f87ec2b8d3ce89289b1da00b44a8702c2669b0fcaaeffc1162ed1b01a8d92af80d657df0b192976c3472e6f073a2925eb499fd280d0400e733bf4475cad17909d70525558dd993383f446634e85c5bd4993127f2272deb7e0f0dc91201ff7d8828d27fb77d7e6a139360cff16b29c6b28e412eb5059c6bec39f6c55c55c44506f75cdeb07162ecf34056e964bcb9c63b25996527b557a26d991b4bedf6528129ba581f68cccf788a9e7256d5364756db3890527e9bd036255961baee0ea4b1ece8d40278191344bd3a724f9d09ef59bf329b101e40de2cd4035fc0f3f1172ffebb1bacd34ffb70526c553c30cbc916718c999bd5f36f7a2fea63902b20e9899df1eba286af0dd5d4bf729318406b1e2e0cea935fc9d43fa6614cd8b98204cd63025985441e7a75c0a972b8660218b4c07a534ce7ce33d164a2efb627d3fccd1df94980725363d5e83b722887c2dcc51efa88ac69313b2525f1ceb6f54f4e0fe72f4ba6ef5a1bccdf4741cf91a51d92de5513c7ec50586d05bae1719162d9bf7c557f5b575502f2ca387261b2aa28b99bacca5088823828a53c91b0b062fb0e66a63c7a4d8f4efc46ec4e30de2b11c3a20828b459a172dd7009f486a16fb7de8395800017a2485d38827225aa7dbf87fa9baa42599528d7ad4930aeed62a2c32887477510d36995f57b397034bb6c0e5c8bbf66118df61dc61e1adc93ed70caa1eedad5083b145553e972a48d35e448da961b0bf1c07d3f1d0b008a7c67ff55a90fcff71031271a048172af931f0c8602a430256d85d68f452b61c348f27c75f2fbbb697100f5c2a8ba72399a5e507d8de2c9d153bc5b338f81324b2a48a21bb3e2ae23f4d704c1cdcc725ba58f6db577f1cca6e20f220f4cfdb48f6f21979117d501cad0fa2f0231917240e96ff790f6e2260104a7a93d66c14d779e1a388137864ba01279767cf9ee72fc102bfa336b92f3738812db12373d3a0354717dc912a7f1d58f457f163bdc72e530dea63cdef1b2420152710221c53d1cde5da2bba4570781f71eebca009d068365b5602d6fde925abf08dced8e53d1218c482938395d8f24e7d817cb97d828db683f761b678486d28455597eeca857261000b31f4acafa735e26d7a1e00972f0f200ec164abccb9d949e5bc97591f362e433159d5beafd064aaf7f300acb18a510397a1273640e75bc4552c143ff2e6472191cb55522ede71aa18d54ba2c32bb91a95f8003d8c72a8d845cfbae2c1b5bf6540b1a32e00eebe704bd74013172cebdcc6ae858fed50532a32840b5302dcca8016b56e8b589ab3aeabecf6c567281e9c53bcfa66a5468bf77a2eb3017b9b7f651ffbce585d6aebcb0f320e14b48c7d1705adc34afafb510905fa65404ff7f60d35fd4f928fbc878404a3fbae069602b2bcb0245126f39ef1aaff8c4d2a270a15e9681cad33be5d193143e92d5723a8d141661ad2c210c6bff299a6df86bcc71d509dcd9cbb4d514016a1c18452480b91abf351862f72c17e0f5dc0f65d00bd52a5220dc8a4498500bf73356e372edcfa584f3dce377620533599ca12360eb9a4ee6cca6369721c153da3fec50217d2d96819e94b11feac9e72fd798ad6e0d8677c8756cc05c3c0be7a96541ae69ba64e64cfc3d19f58a3cd9231b11d832559a4a3ea20e1ff8dd8c5fbe22b1bb72bc5f55193acd375670209bba19604864e06eb2c6b5363b185ab69b7c0abde07249a236bd7b661f3a08e92a63e7dc93707f5dc21b0f84145dcc77486fe308c87207364e3804960c3c8415a64e762f27b1833be4c98d34e1124ba009518c27fe2d26691f046254d63f9a33c71b9a8628994ec248b8297e9052c0cbe163db93347287d2e64564abeaa7cc1395477f5ebbbdf57eea15cb3f317eb635256c0fd7d072ee57dd23cfb4f7e723bcb12043bc1c5282440fc2660003b02583051caf33b372999c9a6e542c970676de18dd90420343487f5916ccd7a67f46d6d8f6d72f2d724b1ed65d9ac290a93388250d524f1e93c0e057eeb2ba447ef1b97f65bcf28e72849551e8856cef9bf2fe4e750fb1b745493c141d85b0e777466a9c6339f75d2dbddf522ed2eb6fb4e2446b6d5c0dc086b40baf030f827884b71fc8027d95bd7258f14683817bb5978c4f557aa293bde091fc875226b404cce5c4f58c89e84d7262bf3005f7e2d342f60c9484450f0991823aaf51e118a27651d379d61941bd728b964f67e8bfbcbc5ed4a32e9771e0b8600a7f1d6929e3f1a498dc5d3353c972cb2c56b683e572b3a471b731d5d6428ff3cd5f0676a99bb3b3dbaebcb5082d72b91235a510351e0830a559f3fdb1c8281619b7ed722a469322c272fc31bb12723514a5b8224ebbdcbb9f5ba0fa2fe90e7d61e8cb73504bce5c4c894627f59172560f895650068d74812e7db1a51ec32a6f7d571efe66486371a40d22433680484662c71939ab02610c93a238fd23b56b9c6c41d5bcdcabf7a689eae26b579966f1aa47f7573ca2b12062cb2b7ed217f82847ec26273d967eacfdad4229a57d72dbd94ba649f37fe802b06b911afde3304971bd6eabd0b50f891811a6b959f772de94e3b1d7bfcb6165cf379458d248daec030773e16287bce2910592e53d567212badbfd61c85007603a528db75e66f242a5cb459872da730b5fae006e26cd722e2a77c53857ba18d4ff0034459f54a29463718499cded0dfbd255e52320d772016a41eb7d09cab951c590a29ac74b29b27d9d0d0fa0fb8614df45154275940f215052380c75b7c3942efb9f598210d0345c18724ad5d79e86cf3d624c422163296e952344d0fd396f6f701731b55fe757eae7098bfcfeaf7f5002e762247c2668b2409c1a6a6f80aabd4d30defd7f408eebe23ba0089a04991e6ca34b518635d8c3c59485f75120ee6d532890d75784149d01f7b9cb86a2da811eb21a9fba7201d5d6e764b2f11611100bb378bb877074c92b5fff172b1a0d7d53cb40f43d7201507dcd923dc0a16c42b89aa1a658c433ffa8c9b782205bcfb7a42989050e72ad0826708d5dd244d4171a8883fc33b2462769c3facfe70b2b8a032d664f5f440a89aef3e1e69bfd6e81d230f2157c6fbf113058c167f7b1a0643f6f9b67e472ff782412170620b06fbdc86ef834358fbfc39784d14e50c97c94747c40ff515a8305d7bcb223cd3e4b10a475f54b59b5d475112dbaff41699937f71d989406721d8ae5b796d96a5880d4b8b51412ee0670d9222cf8aad51c157c2dba53bf46723b89e210fe9c1ab4f9af8082ee03d58c3469368d4871b6ea926487a4f7848c67d0ef89071d6ad8201264526252c42d1fa3b81cb1f92ece7dbdeae8d651923272605820a3a648ac2b85bbded1d8bb73503969c285cacb453574cac1dba83e9472664b775e41e2544d298366a00c617b25f665631ee2145d9906fbfec706577a72951d3fd8843974d93e57c165f108a4c74127d9767a7719cf564a0e6c3431e072d952dd5b9cf366a3651a74ee40c6e2b6403315ff9db8bbfb7301dafb7a4b1925e9cf6f544943af997b3d0cdbd228e222ba3a617dd30ab075a29001505c9190723d6eb6d68a9fb72a9601ffb4e058ddd9e80d7304f01a3a035f34717e7911e6720ac76b9fab50fb7dd92f112b3055e813d1b7766afc94fea7a0b40fc2650b9a5046c83d3701750326d9399a9958b56c60f0066833357e670851fd6df64cc458594c22cbcb869651913c2903ad4aa28589d118739a84d280372cf00f7a14e0b9722bc2a8e9806937a34ea115f2948e2534e3157576bdd3f2f07041857d7c212d7201cc3cad41b1aefe8fd5f23f7c8b15059c9bcb81d9c4b5655aa969daea2ba972b635a4fa31abc8d70c8992cc50db9512e562ea40f1b9dbaaa524fba40d4e7c104517de97f88ef72e33a31aedcd1ad577172fabfe8d3e375800b864ef17605a08081fb038e863e6e1623223b7e6c88bfc9975748a0d50e601be6dea334cc37e40698203ea3847ed14c17327809435a8cb1175b2bdfffb563a982b6ab2017b9772946a5189c65d7588ff560b8ee22ffa8d55ae38f1b41215711e971a3035921272eed50ab495af32f1e621ebd651f845d9bc989e9116ddfa01f478faed4e743b6b635273212795d1df93042f612dfa1304e9499c1c961ceffd8bb745db222c1f2555c531c3f29c2128327f307a34e0136f442aaeb4c992f4d1fa8711c68c9053725774114ba21ec9bd2b322c35d72f04595523955bdd10128dd799b2a32b63b872143ae2d23a8c38cd02e4deb1a8ee809f7f88cc739b56f0465db042db004cfc72632a575940cdd9f62852ec97d04e21c3564092f0f6c25bc44712c6c91d34457278dce84f69477dd4fe45e8bb7c399c02ba8d34b6597830a98aaca80d7ac47d53f8ff347799e83f6f4131d74ead619be3da25210f794c1003fe667f2084f25d2ab5b9635ca9f88f3461e4ebed4473c22f6cce19418d58d5247c64e15516ad4c7238b8f64917c8dc053529828a2a260b27239feb9254686cbe77bcbc6104e02072bb4e851bd2c7db2c3c50b61e3451d45bbb8e54c054c8628d7c01771344543b72e00d1dfc3a5d363ca01692c6f1f9f2c110a6d0a95e8a1c95d9fc5751b6aae829bd62015b79ebce6de2be6ee96b4ed4037241e961bfde94799c6b723d5c27fc7269bb591ca6b7c54d35de3a02a18fb9ad0e9d91e8ba1b9d751c04eccef047d472d6feb8c2c563325729c2f9fe658e20a9676e54d3de313c88122d289c3b68d00f668166293db40ca764f9f94cec9504f01678b92e66c09aaca06d2f003846f472fa9cae2c5fa980d5a5f0d38f534624f3ad55fea539de9cb396380736ff45a94e027389b44a8e9caac20df0511ef2a3a930590e5d93bb01972e7e4105e4b7bf72208e27b86f20b646c35f1d251566717c10e94e10fbe182aeee1bfcf578bd0e72fe7b0de6243dbd96901072220f13179cbf5c8601449b8cbd7c1340bbf4be0772ba1e87bc9c1fabc6741af0e8bd67b60af4be169699e10daa85b74cd372f07b720c197f6b525a5596bca938d7a61eb06182cc6709194f019658bf65ed81fa981df15a91737e2179de0603feb98c48652f310e761a08036f464159d6575d3fc672edff8f4f20df73cabcb752614dfac4b64ca0e8e0526a86f3009a23d40c53ec720ae234c4480d784e6b50cb0a274c9f1d2d238ebeaeb1fd6e4230cd083f8bb643b0f9777c42e9c27db96b833e58dd23a507edd2f60744a0925a74f9b7ed2f3072bcaac5608ccaf4ae1964b8b570523660def16e723886eae7b6fadb3b8b4ed37232002ff321a979395553642aabddff71b64f4dd006b441f1788365a6e4b7775d12a42da2df5b1fd6511dc16af2c39f70f410983aa254ebaa782ade1bafebb56e27581522e895616c19509fda0771bc822e3a860b017d8843df1cab9924b2f772cbd595759b103d0ece6821b1091655c1afb7f8a24373cc04e34b4d2ddb27c972e5e2dc22d6900bdfcec4991dd5411b00fdfd9440099ff77a3ad8b0a6d133cb700dc640217ae3191473d4fe8217abe60b750d90502ee08383f8c412485979bd726f38ff20137e17032727dc3a6824423b1b89088a680fa3044f6dfb6a8264223c9d31784458c26bfee43c7967e9131c455bc3c285f7394f8c3a10f595e173af0229e15aa6ea5fcdaa270c9f7e1cb07b0e948c48164f9275e4ee2e42d2682831390c2ef585b54c752e838e338090100317db5c194c313cb721dc2f055b8a42f472709128778705d1f98d23458bb266016a6d36c8480dd881bdc19334e27e78dd2b6686da624ec1d2c8f7186b3a37a3ac9094a425db93e03fa69a84c76abcbdf07274c6b0432ea7b10ac49c3b02441852a10927d19ee7f8c0124526a784e6aa5e72fb95c4d026a57050c6d0b48a4b1a875d685fe50ad4ad292be5799b835a77cd259bb1def95f4fc4970ce663d515e84db8ed472d9159caed0ea6a5357e8c8e74370cb615d20fb777d5d2eb00ab3b3ec90c98c6e39cf7fd70f44801c367460fe5728fda8b0a2b887089199b80d211a8809116f44b2c5704932cfaa9b7335bc52f727bfe5538dc1a4417677c8362697e48ae45e63a256dde12a14b46dfe050b2f772ba29ffe4d09e8aa2552498c621ffaf5857df746a4a50b3841f6234f6d4744e725a42639b059202e06495400f2943e0d7c3cb64625a43a505844d8f08c79e8f0483b4f0c14581b924e18744c704eeaadd7f8bd140c5954b7990c7b9a47340b47213681a311acf7d947fae1789c509080b0a0aed461af216c49f0c0df8b2390a15ae6778d9026ed5043fd27074d46807c819535808c2185d008914e484e35f2072c79ba8cb377ddc090151de67ff9b480a1ff2b7f04ade0e02ee0e0aa5f05fce076103cae4a424ed3ddaf1176f22edbaccc8eda51e97e8919f0a540c7fa4d33808ab9e132ce84a58aa6b63a9392d31ac9d81573d2fbf1230f717bec27e0df3bb6611607f6baf74016f3a08d3008ada1d30307648235e31a16bfb6257c9c345fc72c662a48744b73e36b68ceccdf8dcba9250d61214148aa31cc6c150f305ab9430351821cdede3073e3ef8f6d48c20c10f28c2c9b787a2c6e42be49f3aff6cf07200031cba832032f2c8a3a02caf11e6ad319e307e34cab212b3476024a6b9cc72d7bd296dfb3d81282ddf758d06adab0a33c2c5b73b8a3143b37250d1d28d8a47075e1251eabed7601720cccf9ebbb327ff995974a477693ce1ddbe8854611428c05cb26fdd823c6124f290c4f44de1f9d1ada5d5e2f87d3c3d755e7cb4fc1a72eb1148202c0142417928e813ed74a05433757977980fb1430c7231cacd51f772645c33ef35590a24f2a7cb4daf166096cc33c833a900ec1ff816d82de3638a7226061b74a695f5b83ebf54d53fbe6a08e07a11bca19d91a0fd24bc1c9d1e756a071347f7d8a63907bb565d9712643c56d8ac007caa89f83228d0b79fb93db0139a074ad1cd56d0994f14e6165d068a50c4cf60e3dc2b8a148304b99ffdc83d51b941d52ac020b4af15a06b0cd7939fcbfbdd871d96864a91384cb8865beefd0b38cc05fa30271f8ac5525737d34b2acdf27b78fd14912b39b709feff032ba872df7d55f6b04ee2d3dd437a8318708cd3240958c67b8e91a2797f802586d14072f0c4b63713de7e2cd8ce54ec6e8031ea19249062c9240d858c5f053afe867a72a19d2c5b25d9532374cebd9a9d1cc15d8e1c93eaf137f31513d786fb7c3b7072f1f6d1604f9983ff628715709e2a29175d4ddcaf9aa63a60edcb3d0b7187eb1da0f8e3fe34fc83f514dd15a073a5fd8d5a5c1cf8fce33903d33e4e68ee28877204f5ea3341652b07bc15e47a91965e8d8d3fde89ee8ed3c2f707d5d967613472377671c60e383296fccdcce577be296e7c9193f3c36f642ced8ace0c05437210046c6e67b323e0603a4fadf4eae1a39934b5937711322a37f33a1cb5676b7772f95953ce252915bd5f5f38d3da8e9f36c29221005ebae37cf574aeafc7e21f726c60e158cd2559ba252c4f78f08c55c41ffab3db61865c8b3b35c34dfa591872d9b019762e8e2cdfca5b12e69fef95d2f1be4315bba9752897a650e7ad052c7279bc5f4074636a6a30831de94de1b56ab1ae431f9d1d1637df82b7ff823a5e5919f5d7f11bbb91860b86f2743650a7eaac6b3c800e385b6cdb406adf58ded445b40f8087376b67cff6be5640ab4ad13b4b45f199a1acbfedfd5d36e7c24ba013b1c6741a88451df10a81b4c7bed3083a03ad27d91705f12ec1552c1b2fd02d72096a4deb00ad893c0d337f0829d91b7da8f241e4187dce1953851c3f2dbf83721dad1158de153e52bfe0d816a9049bad6ca08189006dac15821824b53a350044383d4a7d6a62d9e00d052435cd6eda25981839856f384b951d767ceeeecb0572f344fdb71a95cb57439ea7d5b88565875ecc52e67c95653648903c656f729d189e064276eb960bff78c6d495c72b12c7f2d4eaf1ec7a68c758dfbab9dafa2b72ca00bbddad357c3c5a72edca767400a3b4fce9f0f3a4f6ef23214115eb81077210a0daa92f1541720b11d3bc02e5f24f74af1b40d99ffade6b882e2d5620e772635e363bc470d621d2e5c2ff695f9c1ab065c2f3bc817cdb9fa22dfd832b2950fa61193bc5b5ddd423bf4158faaa1ff934f427dd474d2814ea90d43f2613c972fb41c84317ed2df7066290fa108b70b5d9117315262badad2e6fa8d55bc16572677705957ca859c14687020ab324b51d37520f1efcbe575de61f6cac22a96d720f4d9e47602fbffe3cfe7637667227fa9e78dda1572c41d69d08af963d62f472e780e7d6c3efdd51846c23d60a3849c6a777250da2d257a5f2b62df6245c2872e6898ffb755943f80f86ccd404fa39c03a993234e4dd2902b9f9b61337589f72040824be3ba7200f21f340c35bd8b8e95256dbbdcb946ea593d8c6a3233f773760e185657e7f54f93cd3cee2a76d521504d415a606b635bb8ee3554269dc7aba020000561f627b4213258dc8863498bb9b07c904c3c65a78c1a36bca329154d1ded2135b1171b4d2b42ae53bdb53971702b42a5e600385813266114b48ee0f05629223000c4f0d4f0732346a20cf5ae3f288d3a870a4aeeb858528886f6a2af05d8034724e38e017d6e8c5a35c71b64c4127a87a742cc8c4c047a39f45345e6849c321586049d6073122de1c621abf4fd8d016cdea9397bc9c1efacebcb394fe958de07251bdd42456332644126c1f028f4bbfeeb7820ca0f425ce0a9f356a7c01e07a2ce41da605e183dca7e749a245c89c546caf1c8ddf5167aa9b8a9c4254248e69683419a82eca57c995044c9060564efb672e97a855459c83a3b21681570d8a35151f7d651963268f0ad6fdcfaa68c0e910f435dc14cbfa77906e7949c19f34a65e14e92bdfbb92ce3dae5e16274379ee4edf2cc4c2cc4d6aaddb2ffba0460b4c72431621e3126fe7b89ca46fcdba3b9fb6e8e96716e12d082f80925cace08ba566ceaadb9eadffb89f2b5966d48b5d47fd1c804a6a1b4bc78bb1a1725f313b346f04dba8dcb942e255fe2bb1b6b24cbab2a15c059ddaf0ee0e1da4210d350fdc246436bf066e47396af196e38b4bb906b3e87835bf8e5a17702d8e55e48c3c8b481564f09c93e7ad41f75078f0599d2da1debe143ba93eee08424eeb0eadb5b32eb0c1013147d549708d537fdab403716024c876aaa3ff9ea2478b46d33b1f63720074f3195b0802814f043d5f1cdbd7c2c8bff6f751a1c2b06e15f7ff74caed72da3e0f9689003f98ac8bc9154e1c26deed11ee087f2389463bac8fd6d9b9c17273f6ac5297a3a8e71bab61f0ded7621c102b810d3160f7e296454b495f7a217202a774a6b3976bb689466e30846d8b76fedef39cb5b8b5ed89239b337fbea435ea8b40f6f817f2a8115e5d06c069df0c8e3eae0e814fed711dd641794f058d16f94ee7c223c7160114f7b59db4db37567f1479ff115d9c479aa4d4db59120c2105f0b1aa93d5dbc70c702c8630e0614cac063a2cdba4f6570a9366a268f1dc5f37e074a3b1d72bd43562cf3c3518bb4993994114e8b6ecc58af4c4d45f21c3729dc304099d693f280156cfaa675c27373b00539ae0eb6163e7e112f3f723fd6a449f741adcb5438e59c72116ce9785a1995a1ea094d7ce0af8366b2686d08b723783afeb1325098c0a8a15b676690a67c97467485de63b91d749fa29fd2bb47283f102c9c3b6cf69193798349d3190493ab5acd8745d8f5af4cf17b4793f08725dbe218275444fffadcd918d59b0f5d3e8d75d74decca3321ecf5d98e80bc00f18941b36c54c9fbb2e31e193f551fb20aff14125e38915a52caf49cb5cdcc172aa2371944df803e9d3a172e3c9cba97aaa2568b51c18cc4965732a400cd315724bc8fba3a05428bc3ac5f0d6353b5ca1149eda7567c3dafc9598d98fe53fd1722dcbf770dbe9700831a9825b33a441efd16c7c4afb4e2d72813c17004a5bc57202f97d267c29fc36ab54ff982523038912a8f70c4c946cc44c19dad3a6dda072cee0846af5f4a2960f4e5a90065b1a2135b500e5bb00aef73564b8fb2dd5d5726c3ebf05c31a987f4f0714c510af2773b0edf415591b23a66a2a2ba56501a072387aa10e099fd28c6b7c97f2644a15e13b2dc0e675e8f8851ea96474d8ab9472e2576530df86c8f83e5b8382d5ffb6897b4bb1487bac01667040c9c96339ea36550599d684e9605c59c376462fcb7dfe838a2463f4e54be27120bb8e9ec65b722fde4e8cb38979ebabd76c217fccdbfce837a1d35937348e7e18ac3c032b1272402db6fc185e4b6077b74936f08d7705e384f1517d5b7ab3cba0a74badccb5010d5c51ccdb436c2c4aa91bd16c151af51c65757ba25ef7c21788eec00449e9720ef4488d386afb7c3151a71007f0adedb440557ff5e639816bd11bf62ef62c72f2d1192e730b21575f65b5b5928f370480635d4ff53965decd585f8beccaae72ac3bbf35948a0d78dcca695559688c470855835c098f70ab54c8afbab6691826118cb4e075963fa60fb82583affc3e6629cde2bad22813e53bbb2a16a50dcf729bafa775f09c02fa4bfcd39ae67f1a115bf485512ec4ec9f73d5c1c814ad2c724f13faf294606a1708ead373643e9bbe1f6218e0f8a44b2f6deecde10a4af97094e50d0b1a2ff88f9c8004d7cea18ad4f4708cd7fef7024d1290b1e61ff3424d4230ef3d24874d93b77d7b662bcb45665ca91601b9a4da3cf9e3294cab06f34d6deb9002d496aa7a90d89075fdf9b1025c7f9904e761c6460862bc6bf2ddc05223df5d58cae39e2b2bc8bad23579027b745b2b8236bdf132ddd666a9fa1af80b832cf597dfe6e355c42358e0c1c6c281c644aafe3c88cc25b53b4b5fe8fdc316e313764aefd86962a4938081de90876d44bfc15e8c9c32dae3b8fdc55cae4072e221766607f2e86487b18bb03bad30ec8681672ce16c9221daea892677fcef72c330570a3815e14b389f429157550dc627113a1cdae4f7c4688d9498b9353536a2a655ce15c3b7dc999c100b0be04a3370098a39d9bcb55858ffdce692d9ab72f4a40d169214ca20be0d18e43c70b31d393dcd3b7428c3ceddae76593563ed08dd6badc695d72bb5c3e705b15be30e7761500e42519854a46ec618ea1dc87472bfe6a4bb6691c7010ab3da21d1a41223d1246e3ba9cd8af155ca6cfa0159691212adfd7f0301e7342cf7b6a9f88bbf327fd133d5796fc8c4484b25859038367291f701bd72ce361cb6ebf619f2e06bf5b0a1de9d805e786cb1c47894a299ef0c33ee038016112f33b9c2d4a7fb8289a438f4feb4ed005312581dc3c0ce738a6aabdcc5aa46e887d313cf8526fc2372dce2f4a3aceec0d99875818f131bd70c725ae476fa7c3fda4752ead8dc0714a551d1818a334ef46f522e131d5cf57a7f28be96c0672d518884474ff606a6285877d9c103ea3bf75319e50dbea40d0b0e729d2cf885f271c5cbd7b0459c59bd8a8df12f4a8582a4f00733f94b3d7190fd24ee4f4861264d88d54d9370eb2d57203cb9dc4e295579ba7cd0ae56580c52a64e108d4947cd9bac7b0691451536ead5e2693930b1627ee8739f135d531ecb3b72fdf70d62d9345e8f927bc9d62423bbdb0c82242c124c180a8c4b02e578679f5e33d2aa88c05682f9cbf321e400a8f22d799c4f5072852d43f21388b1f8861266bb2d42aafed0bbccc16c510326bca837b59ce76d301596466cef94198c70e66666e32642bc606f6784bd8d7603b2a14a6298bab9b1aa57893862176f2754b772bd4966d2a53f0b2d6a51cdb6feb02365683bd4ee4cec281539abc6609bcf207299900af6b4a1b51fe9bfd4a7f564b1cc0fdd5a346ddf550323df135bfd2c9805341360da440bb30316230d856d5b5b85dbac69eaa430ba8d667f18b464e2381709e3205865d68b9bae9ecf0bb53be77e70feceb000c1b51d2264e9600a3c5d08a1053860f382746cb4d37078d0310e8b50f6f80c1512f5d7bb537f07663e4172a61484487082841033bfa233d7980bf0757ca26e4bf8f6b6356cee90094176727a6bff251e970448197077e9d3c4ec559fe2cb5b08a25952a1ab03c9e33fb2118a8e1deeb7b7a1c4bf93576f408b6ca2d03b58af21f23ddc01295cfc4828a37260b5b6c8d83d7a329b34af0b8ba280dcbf40b0f5769cb4caef44404269e1b11d0854ce4fa63b827e39b1fcfc9332ee1170cd4bfb394908f12c9371d9d7c52b72bf8b8bd9b096f015eb4bc6ee2e71f75dc47d59bbb7251b2768c7bdd00d4ba8725c9c32178485f5cd18a4314c726ae9e864a876d2d008e5e44cf5b39fbcd7f87285fbbdac5a99d384c6ba96181e5d5ad5284c97d8e8e5755b29dfdd324da3a54eed667e730898890624dd9642c97cb4d3e2dd961252578cb5ac0c2d922ec1d372c9686ecad5dd6b29e8844d527d402a0f254594308e53182a450472980788c224b296a89ee86104156f52b4d06974893cb9e414b1f88298187edaa610e19aad72e1ce755943113f542c1fdd637bde2234953bc2df789f62d4be6722b255079272d1b57d3cef5b4abc3f64938b0e4db9ef04a05d83461bd982f18669cf48243017d2b89317b648da4864d4143d9bf0fae636529129b9faede8af82f36110cfe672d797e322f3c652351ce16bdd0a72cfaaba334f195c21aaf80d301246ba792f7287107bd70161b2d8336c5ca5b45d9273650439ea56b044e3b4f45f4d1e191c72d7d6e6f06d1086bd8c9948642ebdec0470958bac03ac580d8c93d995f7008d72b36a9afe638a4deb0160f4fe10c8a180392b45b34408c790ef022f2ccf7b9d19a13c670c1491db76edfa2dd3f76128b2eb6de3fab2d7fbd33e49af2cc3b9a36d5e9fceb6d0acfbf864afd65ceffb631330768840ff9a96365b120d0be44c7872fa0655f6284a716f0f6e0d5429996c41d221508f56c6cdb6f24b5a162489f872a09cd5eba7b1bfb11f3b99ec677dffe2b9c71662f0efeeb764363948439edb726e8208921aed66c38ea89d1b535465e5eb9076fa5dd11fa05341707d2027c125ddb722b61c604bcf5da4b5a4f6266562887e03de1df2cf3cbde790db67c2780f0eed912a4dea90926441ac358f025874f01e97896ef56c9abd6cc978425327727a59d3f6958e1824da54ce7eebd3ab3ebad9e87788b241b8a59113a7c0174b0da088d9b9f25424f32f5ecfe463c651de6561a0c0fb4624023ca89f1941f80e2e3a196a9c5ca14d27171f2fbd2d7e712c16f618d2213a14d2a891f17b7523c51f2ef8296cf927a26dfa3e2e6b822966e257d621acf2324a105ffcd98df63d323e46162e5a7a6bc3ec7efe16e27d5365c2400d201533c9372757f5d4295aa2931ad05878eebc8ecf680ac15b1e52f07d55f697aa79af7347d459a1dcdd42f0e772ac9dcc0a2b2737530dc70d8b138248e919febd86088f8897b1eabc28932af1664a9c824dcd97aabd8ae1fc14c258d9498effc1abc637c90f3e4e1fd8dfc23a72f7e5f1ba34ad5ad463c0f6c2fbe9cf2d7faeaf97b158c7232838b2e3c7017b72b81461b62138fb1fc35723ff0e82d116550e1267fafefbce26e53510560ed172d1f7aaacdc8a89060d95cdd0feab0bcf7cb742dd613693282fb07fd99437ec68315fa371afdc25f8880ea4069e1e97af5f8160fc218d843a5aa8343be44e7e7280b506da9a7b897375191482c2f298be2f57aaabe07f594bdff939eaed272e72a04b1497983d6e0d68d3f5fb3c3aaa5fd8f55d2f44303bc53ef1b81b22181e3e85f82764b7741bc82d1aafeafc50b75944a38966b48a2f05772d8ed02a7c374118f3e6e5b8f45b6233c8335e1b2b810220256f318301a0af520873603a274272ec39531e86eed2d448269d552fbd49bc485a6f1e8d948a0aea1487cd7521a272839953b7850781af2a26870ba08dc327533f06405972f9abc7f1b3a972747151a9b55874fcda4217f4aea3c2cc0a70cdb9d6700fb2d31dcda5e687857da22e2913070a2d04bf73dd32e6c7a1ded85242ae7cc49bcdcf51015172fd564eb24072b61d3b5cdda77a566c97eda645134b01c4522407d2ca37e7f845d855efc92472ad90bdfa80be96943c98d2826a25b2afa6edf500a53c73dff0a12a444bc4e95cec7a8f1f6b5a637a6f335a8ea2f23c5c9e62af2dad5c5ae2cc33cb8093b69672d27b815e52de27da36a623c5f1d143de9b1518bd7b2f3718e20249cd1f596872dc62542b531171384956a4de7c7b89714108102311dacbc8ab040c03a31dcc669f5534840260b0954dded631b2f6cfef04a2c89b967bf4bcfed5c7f1a3c5800d4c541318a624fe6153251b7b62d9302befe3df50e341fd0c34defc06a85e846b33bd09dd005a5b670b63087e61635e3366af12e0d7533ed212a0b34eabc95a3ed52c2722baff0beb5697de356761fd62dbf77c3627b8e70ab99ca3f7bd2f6a2e11f4c0c2a1ae027681158cd1dcaf92f8a1e82c50ba31f9ba9d7e9bd2571df172de8ade39fe1526e3810c521548220ab01d6957056fbe2e05ebcb34ea55fe101ca1a6f6d3a75d7053111bbcb061058dfbcc658a9238674db223bccfc22949d072bca2df1165fbe49d184a2c6964dde61594ea0e14ec06d54487a33e2c84ad442c1b86d9618da21731345e3ce83523b71b6405546110145510cc97e0b86385d47219c0633a781d597f2cd1e687438cf3faa07e68a45d02b80ee8a85d6a6238a91de78fb3dd3ec3656c7512f34bba2e53ece9cdabe5d6b981cd21b3d2e1d7467a7219910703d25bd7d676fe04419e3e709f171c19db700f9a9f66d3c716ef842049a08a41f4b79303e2274479e6c7c359e382a86a8d04160891af89ace3851b1244abcf5a808d516653adf111e3879c7a9b2bd56f9b85cbf8c0e1335844e491d5723bc11aeeb9cd244af8171188f06d5bee86f7ce8eda42602992743ee3cc131c14bfe860de7b2a56d25648b6edf811d275a446ce3e08d37d15f6fc6889503036727665d23cee5321791e593be75a54243dd9ab5276dbefb4207c2acf6914139272f00259ecea887e198bc305d259fba3f7af65cba92b23fd6ca328f7af009cdd72566be955a1d265378e1b84c881b0864e3d5de846575b722393349c5e520db0331946b0886e2c49b0e76a229c7d65c67468203a48a7242942e6d32d097aaa1d11d8a2dedd3ce1b186484919266528be95ea2877298ee05caf8c8963250fb44372ccc82750633927805f7a03155cf0dea4cb78c2f27ff07494def0a5269aa0c272afdfece4b67af39d8485f94b7059be39cfaa723237e27048d6532142969daa72f1da9a7087c657a70c2dbb8d8fac791ff990ed282cc8667ea169c2638032f272529b70d0100ecd3010d8a5fa2d2512c2a85775776800cfc08fdffd71d734f17173e6e49d363968a4c328bebf1f860a7fdef9cdc4ee04ac8017ea530c3d38fa72bd6fdc5df0eb0ddfb93830ac7383cf3dde0b2aada45e87e634e6fb5466e2847279beaf2ee1b1a958b4c4f0fc2759960a347e9b932473eba2b17e83103d34ce72fb7e231613e88d8739c203d7a324f97b0070dca08310f68354d8cc30dafc7e19ba14ee10f9e2e17d624022046d18a57fc6cd5c33258c2e5c5edc8ace749a044286444228376062ae52d116f9a556497ca73e5142ab25e0680acccc952e310b6d5637aafde10c92a9baecf44354cd786d524cba67f7971c4e21920ea4a9298772af7e015a5a50f8babdb52250c55d2682a3d52cdac5ff1e84042eea2fa22aef72910173795c18981ff15f37e72235a2ed8e88e4a11e2efc06da6d910da0ebd4614e08343104c4e995291f4296d7cf650932308658ea44a54ef46db8cc269f73728b4f0d0efa4f19866846e5cbfbcd62fd09d177ddcfea519ec260f98f1ac628133c915e0f6ebc9dc6e4322fd790ade9dd4c243892e10afbd097fb4d95b685f27247c470d29a29bd31587ae46110a4ed40069b250e3c7db1701fe7ee075067d9727e42b20765a4347f4817be029433277a577ac25dc34542ae5bf8eecc09f634729e6ca108fe1bfa555272c8a3e62e8f5b87246bfdcd6ed75eb3551a14d80a12726ab1ae7f8076a0252c70392d4689baaf07caa9641552471d5bd62fc94a02ae18093b27c4778f7483bc8998a050b1e4f38334836efd74a733b9b2256d4400fc22d8b5ae6c0ef8ab6773151a877486b547e446e184f4ce6da729ea1eb5aed6c65a2e2c3b7e7af31315048b7e1484df96f6a25367ff14f58619a95851b28b581c0d93ec02de8b765148a4ba365628915d6e5ad96fd0b9a822b13a709e6b4f25ee72bb4eff1ad7011d8869ce7aec048cd77bc09f85619352ff45bf636915993cd1729c5a5d8cc9148aa93286252df52c6d436ada6494cc37e4f95e8ef1fb2085f01526145d0266742a596b8e0200460e6f3af043c5b6ee71bc731cde101a6862900889af54f2f6f09e0d7c91faaf5bc317bf2def7718b1213db2b6482837cdfa0172dcfcae53b55e8990b306a261b8cafa5335ffb5170fbd100141a966e1492c83721a1a7b3a388506c51d4a663c903698c9b96a56ba9d9b160571b1c0a2d5f6ae72c60ab03fc1ce2eb21439513e0a937b543174c1ce28f2f0d7412cbf13ed9730729d57afbcb7047bb20010f6bb5201fabd19c8799186e334cbb275babe5b60cc461f810b007e9cadf039df1514b4fb6455ecdcb44bbf19dc7e849ee4bcd499d17238648707fa39d29ae55da45a8d9113bd4988a3bd8b8650a6315f9ae77f3f957238217aa8b768a70621a284bcaf5b51b0368c460dc8eb2dc0e58fcc01d1886372301ddfa1d4b777781aeea1d3be2efa799edf4f145cb90c415f2517cb8186c438906ca97037690d5a51d6cb29497a5391c6a2be1bbb98f367679332b2bc4379291551db153bb2269938657334d5918440f787324a21d04e615b6906503d1b567248e0227a6d71fabc3c7abb9599385686745066c5aec37cce2fb0b9167d169c500cac095399cbc6bd14420e063eea483876d920a06dcd0d6812972196a5f0124ec28c5ba241c37be98bd6c115279ee5ca7cf79d66854e3478a086e9b3cdee3f1050f6b75f46df6d6217d2f05a34316888afdf837e1373cdab8077bd05e1152b72a3c538f40cd976f65e25b8c9ed3aa213119029474fc6c1d6c9bfdd99796d7124d2222310f183f8697b3df6db1487bf7081c6380db57b3bb69a7cee47107f3d72f0179be9feef2fb16b8ef5a7d314a93eaf789ec24e9a625246241179802fd9724f342f28d3971b9c389aa54e7be928daf2851948c289c27ebaf6e1212abd6f72e13f310f924a8d8a8b8ffd2351c3eebf26b5a4ae607585ff3f3e35c26359b15dcea240589538d3d89c77d359d3559d7040374dea61a0faa9c960a6df38dc615350ebbc802315dedcd26b52c1e47629e83aa36909aec6f0526d116d04c8934f721faf56fd82fbda3b3fa87026f1bad9a7404826ab00168fc2b8d783c36587196e2efceaa7b01e3d8522268401940eb4681eb8db205bffc2dba2f87d31a7e39e6b8864ebb102940cf0271681c9685508d81b9c1461f06aa9ca60ef17d69741f07267eba18fcb8de99fb02f486a0fe54fa34ec75739001ee2a34bb51ee2afee0772183f7e9fb4b08f80639404145e6f07c92b4bf2cdba29bbfbd59c4d418024427260da6a723040114d9f77eac5cb41f2c3309404f5840be24adfb9061818fda472add77d2153b1eedbeb4c86839f4b4ec097f5caf357473cbd8d17f37b06ecfb7297778d5cdba021ebdbff29d41ddd7e17ef2da214485ceae74eea642df769d772e8dd77e566ec6d3a5e4a6e6e11964e4746d76930133326ebd32958c3a2dff860d0e7b4dc23776838bfec7e2eed65d397c78c1751779b7482d677406447338b72500cd3876398a08a36fd940ccfe832897bfe3add8130782cbb56d04ef8b47a72d0826034a85759b08f305a2c1bf22ea52777a1dacbe260a3389496e18a921f7219563283b9d6c663437e888d76d752f89c4f3eeaf347f674ad3913adb2117f0eba742b3c464da4508412416f0473f8ad197efb00bdcf60c815b53e7461a075667574b5fe52e9c80481a373ab41d38f13dd50282faaf970e0a60d624b9da53336bb952e4ff2200348d33d530aea82feb277c857eb954536cc97fc32141f6f7e5973fe1e0f5d97f65cb1851247b639d39c6ff18de878cfd958109cbbcec3285e72ad3f0d2356cf8fd57510a22d6c8bd45584eab4bc566b418599ac461ae75d275028db54cbcb9cbef1c00257e336290ebb132a9e6a2fb374bb2468591d6d24c072442e87c3138910533fc1406e47d5a2ed12aa3b5deaa62c48057cc979ea5d04182478631f4e9162cadc3009dbc8d062c8cee2741b013c65aa19da10e06571dc2b247a3677cb217bab523c4ddb1e8c3b597c097598175b93139c37391758b2ed364c98316f459d5c858c3fed2f9f9da98b8858f4de16f7cff21e963bd37611b3355decb6080a7699076df07c87280b876979cf8f364c40217b2c4d3101c33c3e25216afa602b7c6700179934130edb7534e9e88777f62e795391626178483a901b9e1d90f40b7094924471a4a4d3dec028e76000c5f42a03f64a6774d2f85a226dc9c83ce14c31f77c42e33a4f3a82df6ef43a7345042e699d53e8fed67923c13f8b946d328f9a08b47a449117e136f5ac39a559b5d31c04a48c836689c77c1772531183b9489c1cd4a3dff8514e03f10763ad53e4acd352c04d5331f7913f68723a818042f94282462c8a301cbe4c3db777acc2a932de13eb88b6e4c8a1df5d564e60edf670f51e9ddf6a1ba81382100ffb61572d9a0368084b90ad6beb97781224218cb63c798276ba2ef5f468ad9bd65951b09dfe7e1b3f48338c28f207310e11c499c045d9cd73e166b19efbd23256cc7bd4ab6333cc834559e5f44a2aab07273c950b6b9513d4ae3ffd34d2a17c061eea354d7bafb4560964e508832add34c6d9520574df3758f49050d4b6b5a2fc76096c6286f4ebf142d7b123ffd3bf28ad1f485d023a80119e55baeb6d3aec91a2ee9571fd50dfadb4efdf0ca9dcf96d6a426bce81dc7cf55de11baff48699243afb68f54b3fb5bbdc0f4b743f481e72de0e3b4e3720e7b10aa659c21e5b6c2e0237dd0645fe80bb152aa6aaa49a283dfc58d35ed07b861edd0a380d9eccc45f29e74098fa0e1d83bf77756b3426f55cd897f45d528e3ac6aad7803dfb63797b58af4f63053e798b765954eab0a40d0a468d42e9b7ffba30fd54ffa210b6b56fe3b1a6eb734bc2b3d64f5e2622b9181a140b899303afce3086fcce873676286e53919ca20e263d6d4553cc4faf24f872d2166e4665386a478fe633ce1fe0b47d16f6ef67528d63f7c061da6fc6161e3241d39d6b80a4603a26b0e49f5b1a764f3a9185b3b9f7e2d2670eee077c895072809a49ed14508358fc2de505ce2574630515c26486a2dcd2ad0c5e4c88128b63f8ed992f74ce2662a10265c42f35cdc57501f4d33d78f5bfd7bee76eeabac04764f1cc60984cd8415fdacbda9f329fdd8d647435e6d78c7e0e46800715a6fe72e933a53b21b40c1413bd9990985418a09852b27e79d443c74f46632931485f6bcc5f1de1f97fae13f6742763502d36b3e16d1d42874002196dcc3864886f415141c4c739e38496cfae112afbb17331bf4c3a6b7dc5e97fe796331037eb726772266047d1ff5c4a59b3d0fa5b921f014b8a1931b87aa1dc8ed47d495ce4da360a6dee530d254356b348688a382800b18c3f778d126c201d407b676a07969b077296b6f93096394b1ba290fa2fa59373f96b8743fe3b4494b30e33ba20498f29637936dcccba0ce63394acd96cb8f14150ab6c6179ae69650ce501d690f0204a72da9937f584435a08a2b2795ba31510839a2f57ffd198439c48d12d51a9bab97228dd385555ade287085ab63645efeeea128b901b58763ec43b4e9ac92f87c472e2d11a87e95d792c207c7afaca30dda26bb2f64ae5a5d3016ffee09bc8af6f7288926866881392a87f715736e74987d26cab4d9519e683013801f61c6aabee26ebf37630c2a0a9774b73e17608f8b07f3fe37be71e712e4a57d4b0878463f172be2ac0f209dd2c4503a7ddf755c9cbf404fff848efd1fe4b3c49ac1ae85d4b7273cb0aff49efc5c1d3797b08126c184ca36859608ee683e21993d13418bcab34101da8083f5893cc52ada23d7e12133a608d2a77d915e90dda86202fe28e9b0e0b7872f18669b9472207388df27fbde2f67eb8209e40f9727d30f7753568005ce253063116536f9d15cdd6dbf7148917e593f2833d2380a3c0b215473363557265fe79c57c6d76bee11c633d55d8cf26d2e759301cd80f7b78a306064f8dfb72f2e5a96e5da7d402418ca101cc27220566c3db1462bc42a1d9d953a108894735267ec940c29bfa1b23f53ac4684075f41859bd5be9772527fb3ec0de32d09536243a2ee832f96dee67bda40f957a5dd12c08960e1f69428e09d50bfea694c73938ebf54a609bb44e186b0fbffc43cc42cbffa931ddaf80b34de671be4e66ba5dc4fddb2ca3a09969bc57e71ea1c15df2eed561370c288d61bb8cec25b80afb60546ee9878849b0bbecd1ed0523973b7b5851ee8833060f7734e20d59a978ea7276eec0c4406add3ccc416c7fe5c85340f118477c380915503a1ef36d8179a02b930a43fbad75028b1bd4ade599a8efc33962c1bd76cd6c719b42a6f6b8373f2fa5b160273b809bd136b0bd045d22d8d83c6fdddd7ca04bd08d792a8bb4efa958974cca077664964b80783c32e3658032b1c6f7eff4363832b1b6c9ba19ded672ec1f23cc57f5f872b995559178381140dbd8f500dd15206e98aff2470d4ef81aeb1b50f123314e8eedd3448b0890aa1bf582891250dbeba0dc217c759e6ae6726f0c6b99862fdbcde75a27a45f862e010cc9a394b1edcfa1dcfc7f160311103388c44625ce4727f6bcd555b332e1427397e6c42fc69d478eb953fffdd1b6001860eb1b9f3e449c4913d027809c6c73e569f3721be822d9223d471610ed427e69960e1afa4046a678876b1892d07bba584a4c4ac55f39d97bdf1d65377ad4c772d4a35f7793310ec0d851c61ba2f627ce962262d03ed40ccb00391671d280c63e6324a61d6ff9738b3fd24c2fc5f094ea439b6f2dfb87e0c4f2821f94fcce9172888abf7b40c339be7f6d4a0fcfdbfd713ca6586b2b4230ec3b17c2ff0702520718b01f40ac9214f53bcec74063124d1f05847acdd3fd1c9a38410724d53f577250dc99b6f214afb37cfaeb7f4af41e068fba1e8e9dd2526c0ab1493f8a6295727fcdeb62c083771d065416d58984c30de03445126278228bd557fef192792709f6fae00202262bbeec3c5c11dde2b31caab1cfdbf50b561ae87ff9f83e6b0613ef4ea7f062ac553fa5f1be1e91019adbad8317928443615b58f999df7e4bb64cb704b0db855f0972eaadf4dac8d8c662bd85a12f6bf2767623fdd032f2476e72ac01d79871aa77c77c47a43c79e41a86c9f1e8c4434ebb58faaff1545a94fa72aafb1bba79c1a9e8bfa422e675e5c2c1edfe767a02f02ffe70b7ca0670b55a72f41cbda0249332f5765c10bc5b7d657b952a1740f7693970d6ce2b2bc6e6232926647e8125c8f7af12e8c0873a1a617eb3b4bc61fab6f67ebddd754b094ab55d31f93b2e3fe5b2750f0cc75723a515061c77bf7763928015d9e3127f5c808943c33a3a7ca1726b48f55cb49f083ef0fe17536bd86a10ad41c05ea2ed31e63072ecc81923f94960d9e1a7b1ec58b45341e053c218839aeabc7c0e69a7301e842d4d76f1c2e352c4e49c0c1f33dc9da61260e21045d87bb8710f0a061600b937721dc2538c830c159780deb512c4b48a1a371d68bff66e732c8bf664cb1785c97226d2f41a1d7b7e88d0affc85cea886400d1dd8015b5cfe1757b7c02ab3d21c2334beb142a8bfbd54969d3da71bb17a4a949c9bf742f54a197941cc10c2cdb572463856e53973ca655f78229d84c3c2b5df7b585fe9fa1510f36adbc87969441361d2d4500d129ad8c5533dd1aa2766cfc0aa019143d8ea648f377c5b572be7036c4b88ecd94197229af12087917681380867643551756c88ee3afe7e4a2c5672dd94ff72fbb02aca7bed1413f14408b8d17e30a8deafeda40aaf9dffb98fa6729b896c27f4de3dc3fba6c40654c97723043b78bb9b70ce5234dadd41eb1b2f16a4650143d1b653bd6e3d0a3514c4c901854a3a7adc2f3b54125e876acc279f724ef5963b8dbcfc4c8e5efd77be1c1edc8c5d78d6ae176c339f1ad47817e5025bc7201a16c79692e95d298654dfa5af821a4de69655b71c9e3e59fbeca9def51f8400abde975a493a47a1528d90b5ae18c9c2f134b4a85d6cae3f1517cd162207486a5a1b73e42dd0cdc2077146c8b0eca5c6153f46799dfe85d457e783bc5144ad2476917d020bae6bf78657f0eabcb23b2a1f4f48b20440e20f56abe944545c186e9e123099772c8e8b0a7c03d34f57313f71faa562b5392f58e21b469f971119a4d249994b4560693872a12d7e3214d49ff84e1859520a63fb5d628a6ad246d5615610bfddd7730ad89637006d455a67ae78fb9df2a49c6dc7cf9188d76c0b39869dabb63c384bd0738578992aeb75a8ee2ec946f2d12941b3c97ced745d72014efc6e85d1f64b84a617cec8afe48604525b0eac764156627c6cc0d6376909b11e1f19b8375f0a712e9d7bb06673333905b118bda7b242adb2c2817e999e72eef7ecc01ddf1f3fed7e278fdd5559dad9f4754cc35304f3936d780bf0a3c160a5161770274372a72dfd9b1979b860586003b61adf20a0d049c590f0b13db12048f7ffcd84b6ff1840543c50facc8eb29eaa5f7ebf5363780abc4d703dcec21ed8c7b94ef85816d6757384138d6663b543b41e6cb8cfd7376b466bbe11045444a692ae371292b4704138925c6f36afe72c9bad9f525ea8a8f3c29afaa9e32072dac74fd753ce8d7b3f228dc463dcbb8c7b8c3978d7f027ced5eaef7e97d03772030c3af9b6a46abcd808880e34c5d61ddeaa02f54b46e1dbbede980eed26bc72dd2ca4f58c4868d2142233d2f6779733e34c3e75d7c016a80cb279c6115172531055f7b0eb2d5f50a7b4b431cf4d9cc3b20fc305b722cf11a410bd0528020f450b7dc89dde724b05e2c73b7fe9d434dd4f21b9ec31f088126c21a21f7ac8637246a1723a1a7980e48139f1e6f6bf11442515d02bd0aa989305351e2ab046d0066620b95b55a08f2276815ecb34af344a9bc29690bf3aed91a374e5b4372d0e72d24ae696209324a0a0e754ecda623991d19c83044c4d4b011f2d9b81a11aa472125e5e0bbb9446a6592b083649336fcfef679a1e7c00cd28c845098d9096543c275b51aefd9b24e75f959da3367cfc6ce9a66a204632f27b17d4b66a40a2456a822641c1bd76b5414fb591f882d7ede9c46603ef36952fe3b0db1ef35bdd241d68cfd75ba1027b84a6b45f92d13422ed40e2063a8540153602598367d6f8e5726d7c65906ef159e436fd92a4a0a2ed05d1f1fa9eaad1e443525bc5ebe8da0b7237ad01fbe83a4b465e8e4797ba0d505d442b8db81eb337768bfb4a62ac84304da37c7b47606a932a3a2faf300423e58167993ab9120c9a9d79eaafd61459417293b8537a901a6fbb2cee05b5ee188b33010a2162ea4d6829bbfbe8626f66ff0ac5c26ca3b0febe97df5d5e3d2137f95da3ddf513bdce7440a43acfbf4487033d62017380f9c5f74fb8b40de31e32aa741213df36659b90e427e3c733858d07727cd89b064d3067c1bf104381246c4c0b2ce5565bc81a21065c36a6884423d3726a0ae2c46d364e6ba73bc1ace5d2ee19d63a4f8b8c0466df495bd6eb49e68c7200e442c562b9b96ebc41aeae1898b08e271145c3ce47de1abd48e940fce5cc72c85a320b1be91a35d07b39d8c477b572d23601d6294d2ef69b2dc73a0ae8bb724871969ee6a600a67f91153203ad931bf3662fa8802566fb6a06df48805965725c7b99f7b0ed248a16f1038d101f86f4516bbc56b3052b9ce73f19f732c8721d0f1a6917c13404c78055d45b44dbdc9f64347e011a7ff0a19daea8ae0905aa5d4d711fab1c781af0bb62c5c4eab09713f143dec67e386ce8d37dda75d940c472999e7b0f4152b19244f31d78fa0f96c74ab7bb94693a663d5e3b4e581f87a07215b9b77c93e3dcdb366e319c9c4194fe70ecb1df0bb75b461cec35492287ac64b0ee70fb214aa4fc1a859671dbf0d34bc87303706ea99dd8172054bc5bc80662e54bfb41a4160aca30f6380b1dee0844c45890a81afb1506df037906c10656724a8e141780f5ce97e906684de333b4a53ad0d3aca27bb5b5a973dfbfadb857722147f1d5fc74802221c9ab234ca162d52c3b0b1532eb345ff6d9560a858d6b497eda8a15ebfed0225aacf0b1b750bc468ae36a6f6a062352b046025068259f7292770d362e229301ddfff9e15c6c1327ccde3efcd0fe23d393de6035f94873455e499ac82840130f7c8c57be1f6d3b47da0cf73441d2f1160ec0f0a0e7d147357ea54ea42397815f56167ae7dd2d4cefd5cf94f653f2eb2f6414d2d7e41de972b28a4a658313126faeaa8505627a3c60ec3a5b08c01aa9c7a86fb6d526e41b725be0a8ffe88e5de5b60060e918d4c9983c34bf25add4553ef665f07cc9034272b5ddb2f140bb736a0f2e581af2cff2d5c87034fb2f56165327f4279af31b2e3289b54a02cfd3a90f71d01a44ae449e2bdecdac68a9851dad1ee5747033cf2972f1d55f858ffac7ebf2563c5a36edf84b2e51f36c2a5efbae2c16300af1ee23729240384420afa095b18ca2fb3454f71852135b9f15c87f1d1ec6c9abf0d06b47f4d43d3a1416f705f877757f826f10e0fd83b9eb2f290e08e84c7c8cfaeb87414995e4f506cbed1fcd67ca2898a042f17c13d7b49fa80e42e70a6062610baf5dd680597a0e1f2013be3df51945ab2a63c181aa11558986184af6dc4f66c04d2b953d92cdf3d275be28f84183c81c0222e71fe19f63c46d78a46da9a64a4633721996a5c244485d583e45191b39b28b2c4f175eafa719c2da1a5d7784455e7a4253928a37a129008213477892974417abee057af03fb158fc4d7b41070c690172f903cfb835d687197d16bdc7b0fa093b7864fa151e36658350699185f69ab572681c3fd262367f05eaf98b75140a9eb91cdc62a63e36a5b8cdb0e641015c2c726b35cd4a26421b973e8d3020f81ee44d7e55d9468c6459311258b208217547727bdf5c61d6ed4fd6d6e5ed700d7e1fc7e89b05ba038f0640c10f3c4f5622af7222d2fc42514ec5a833bd1d218055c6c1ada41a202bb5224190fb14e94fe58972920615ff1c36d5a96e78c80a213d88f37d8129df8d23018fbbbc4e5c4cb35a72c1d09d5f7b85d4c3a5ad5e145fd6bd8b21905ae10f3c353abbcfe268e1b66f72ee37cbc51a653eaa943372fa0d25477d4a51f1d277e9333b46096f1a9fc77b726e69bf235d91cdae59f873265bf066b60a1f3326bab8e5fa52e7d5e49c63337211076f3244973dec3a8d1210538ae4ea5171b17c962c9b3abd0af668547b83726e7c9a3a162875b1e5605a16a06afe2043889e64af075282dfc7cde9aea79872d3b653526e847a2f046ddd95a3f646f25a5d81ce57b4de62ebd42ab7d29a527278d41b680487febb76c978754201488007273fc56b66c9fbb4fdd83389f52872b096c0e19ce992f73a8e2631dbd48546b5dda9372fcbaee2f3aa0a6388df342db83a42a4c0f43d3a1c0049be049cdf8f3ecc27341c07245bf687a46b3be4e272432ab5d6e96f65765bc92fb07f3fcdd337c956381d8ec59f74786a5e8a579f67508c4c74318f96aa98992dde55d3590cbda1ccc5f9d6db653fe219fa12737e262157bdf2587b9500ab34d1cb7dbfb324a6695507ab7d58e4ec58e901cfdf877298015ba92aada7bded5bd22dfc698a059a49ac6c8524ad830cf972f4dd925a720bc37322dfc7137af65d66b406e162d14aee9d076854863d7a6777a7b6fb3e0e165d38ff03f053c58ed0ac9624938058bdc1a92d856bf11908d7ea14c8ed7b723277307398edf1e8c47593817363445e9c50513bee28aa5d26083a8991acdf725a40b9f0d20306e714475ac065af4b60323705eb95d05d1dd307e47faa5e1a72414ffc4157a8ca45bba28f463c4256fe6784588aeef1b12096344b9f0979fe16fa54b01fd1661f38f4935077eb878cb5efe57a03febfcda17f370945d6c133727b9fb343cd0f520e0d4cc38762be317101345f0b02d2e9b7674880868152340239819222069ccf6b15969fccd65e069e93773c1a21c582b8f14a431d80402d70b3d73720d6996faffd41d4d4e9c9e2724632719d3e2e6a18eb4d5d61150b367274ca9b2a973d2a390472c68dea981ea79ed9eaafb07f6c1eb60bb226e572d67202f4b74bc8381a7b48b0b6e4b770d2ac7414fa86c7a0edd96cb093884f08d2722691cd90e35fdcd8c062de1361ac6d5187465be0b3a27add423ca8ed6e507866531fbf6a20e45136edcafc89f427ceb3024decc3709c7cada86310a501f3e272c61b2c65c2f870e880e7e132a12f31ae61a62b4413f2cc1d93db02cddeb6d3724abc44c7158db7f30940571ac21e06122e57a4cfb9861a3e91029482f0edc0722ce6b834bc12d4968d7756cee5cdb113387cf763a993a25bbb46e18fe5063e0564789c3acdb967a8d8d0333af30102f10fc7ec873bb677933772174df66d8e7272fd0424c314f864308a79cbb53272d56aa504269df22bf792ba37624d5b165de64e11e457456915742a5615b45b0ca89f6421bb0478a4c97d0c499ce8f864725a8a5c42b3ddbf6abacc3edf687d31951de8a24e327f6562e8bf5188eaf05e72e1e9db08c11277f296a8476d5142411a4f96d4002ac5904f8319525af75efe44eb75fb1d871c242a61d9173824a9916bc9b89b5541b4f98a48a42dd3ab9f0272245c688b515cde3f865c22576d18481cb3d112873842761f84b6a86592b67772dc3952fc2d33d046fca2fc5fe2e23f40534e2ccd01dc6afa22193e55d5fbfc4047d02f523b1dfdd6748515f0203ffa3b1f5115762eefe235ca50fc1111a49f7277c3bfef179f18085c3fbfb7aa3fcca14dafd8d8452a62c9ff625ea1df90b9723bd9bb8129991dfa1d658d0fc1089e860a5d094be529abe362dbae8564af627281e928213ef3de38a6d2efd83df8d2840e392237fe3a05f706ae674312095756deeda923d5d43e10e10062497ce946b6c3062e3a147167cdc0ea37862e4c8a728df1bcb4c47a88192d24e4ba04fbef524ba1889b2002baf9605f8591a9139272ed711de9d567240518059419cff18a17e5eb1a2dca6da90bf3b8cba19ac85d72871b32a1aeb12bda352fe8b1d14e3c582d2e3ea0309ad437121e7a94d1f390721068361295f76ce6e11db231ab6065acf23680d9afa806a826abe157c9c627723984b79af138673e6f747216c6353ab383ff692eb2cb21bea8b64c27d30c8c3076a0de968c00d7510982d07416d4024fec9d23e8532f379fa12303ef3bbebf66bd651a9c2fd75d81a915bba0a2823824d57cc322b09db68e6ca102fff9f77148b694cdc1e7b8b3693cf77dd72c7710fbd5b435a54ca8818ecae7775b4cc6ef723e971830fccf6bf44a61d9647ca1e68355cdbcee351c6d61fdecda238f40a22ac1775e4666a0b91653f00ac96da7cb154fcf633efd358e29213b568775837919bc98c5adfa9437f9baed00fe753368ceaa515a3ffaab0728f65ebda01ac18172a0e8290d9302ef089e738b2bbea973b493634e52f536117308012c0f1f539b72c6b85ec70c9c12f0869236798035e1a5a170df65bc8436045081b4b21e8be572c2031cab20e7a264ccc802e56ac5d1386e2accb9e9f232a1acee2bdc0eec3172021e1d856b4d0577b0ad0ed9811f5a7851dd31b1b405ef6038f5a5fbd9357a72010b6164ef738d0b755a555b50eddc55e13ecc0bd4ed4e9caef2aa5820a6a3414db079c273d44656cb3990dfd8a56772054828e6049ed537c7aa9c8aaf870136b4883e942aa1048bfa8ac63ae1cc73bbd22033499df520f50335ff6d8f072500eb2052b15f8f27335d0008ceb326e6c8dc68b2e645795da44c237141b0e7e22878188dd14847cd50f872d1dee154cdb7d05008c1847ce3b58c185cf62da16a104d53687ab8f60985e9e3ff3815538b6d0bfb2a366fb75f7abee78e58050719729fcdcf3fa160a65c8db876c9f6805679565a4fda1babb44f678b7e04c5348f72ef6784b4a3d9dc6c5fd65efab89ace2e19fe1f63eae604191cb86d958928d572fd07f55248118b5811a3a3d9f88909f8efa040c38c7010daee672d75b3a26172a870ec280ca367eee30f84ceed160ceac47e49f9fb25dd7351f9c17c071fc1583aca97b85ec27663b80f31dc618c9c2d529eecf8366dcb30ea7610659e9828486af0af55e62e7cc0d77bfbc39a44dabcefdc4504ed59b876922e94c247076772ed1e1ec3f846a250c3f8c6ce25e73d11dc5a88686062d2b625f2860bfd6c3072a4addedc539899b7b88281042c40b56485236bb39ce6572a5eae720a56f4ab1786b715e2c4c24f13cfd47cddb291d3adacab8390c97ee8579fcb2de09d7c2138df7d2cac0f1dfbd2f6d4a80f1c41f06cacca5d34eaec74e8782af10d6acaaa53bbd23447500c82fb30f09d296e01b517ff02cd3d40ad530a683e57da188a2c7295ba595591b3f4d8300703266d734cd043856a5ba194962312b0f551ced2d05731bd4ddf04b6b78644bcba4d9870d8a290731ab0f2a4eb83829997eca74f9e720663e57fd21f39e2185e30931ff93eeaa47bc2e1e8226a8a3af51f03a8d5356ba2c77ae16738770c022e22d032cbffec3aa4278f77dba6a3b445adcc2bdd44729cb2a5cb702d7a9d68d8edf63932529617f328ce6738baebd89c786bec7bc10f03b2810883490bc1219fce85027ad71df8bdff78b831553254bc16f51450a1725253ad2cdee96f055353303d8672a0d719611e03641f070b21f866ddd112b461a46c41c16dc8f7e4ed39a450e226de91bf8d44a847bf65780cb3fc33bf05cc1818bd0dfeb2c26b620eca5a9040f8757cb9e5cea9611115e393edf4186f8a0072cd39db7c49b1d107af98ffba02cbd893c6904822e3b4413e213b8d88545ce9726ecceab385cb218b27bb65020aba6bffffbb4f665a5e86eaf53655535129811c840e6f2d221666c7990c1251b02adda2981a057a038446c5a8ec6a9500189372385a49298b93f9b505e9f7b698d485375485bd6a782b5c8aa595cb29dba2ea72f22605434b7de6209eb7c91b0242af33303da9690c2ae074435b03df2818d572037c3d0d8c60e09af0c2df8dbb730dea60c397fdd2070adc180051855f0ae072bd66d8a2122e6f75107007a26df9d39aa3a9bf3744a4fc1db2d56084ee7b5e72ca65ebf4adfc53912f6200992f4c7c804f7f84d1c8af4d6ea672db571fa32b6e824faa00fd2429665fcfae75703efb097a424d30aadcdfd851a297205ff69672be43aaa13a7db96e0c39e942b2e20639338f2fc0f6df6bebd833748281394472cf28f3caf1b4d9eb3f4ce96494efde18356320c4c4e10917ea661d28b602e611744fee2e61d2563dfb94b62ed711e60b19dcef26b97e9a6be7cd04d09e840e72b071f86b5e7df7e43eeea009dbc4b209aa35f743c89e9dacb89b6943043756078b7c1202dde9a9a8be2231c85f7b2f185d00fef4ea580a9a03ce8825dd36f972aa35711049ec3a23793c8f1fc235e49262042f13619cfe9f3299bd729667b06c97ca96cc53b140a313acab86eaae76538e471fb9d2f5d2ced3b357a2a2e96d722bef4fdafadbab1113d1f76ea53bb3d9449cc8e18c0316a5c75b7014744063665f0a269b4e9c5687527adf7f08e1c78c79a9ae6968de3d4485a17e6dcc104721a33e69e460fb8d6cd5a3aaf91b7a057c6467b4b03f38e53b934870747d620d233d8dc336d53c99acfb627e49d000961a73cbfc399bc3fd845baf8fce53f91672184f7284128624e31085020ac6c66a2dc388ddb7c28059bf68eb84614bd4a2728289cf4c210177b2715b80aa7568b450aad5c81f5b797e4d3675295c1d6263119ee98beaaac769dface66e61ed4a2bebd3659ac4101bba31d74cb0d6669b771fef709b4588c530b14567d2d602ed8c7e5d74ba4a0a8bde1ddb5355d2049f4f5826c9a64ab6708f3c842a824f9479f3f5e0a3503abd8604795e87c46789f2726cee3b04dd05e1e920e706c3760c532fea21f29ef0803e3ddd772c02141c0bca72aa009a9d5d2cd711435281c864880eaac682e8839f8dd5db53d11f6c4c1f8e72c345b7529bfe88ca1ba96d34fdf0682acf6a376556325fc4dd5779d4aaa7a955e06fa4380d6349c825e66e4748e34a92c29180df382af082b035b00d82fc9e7285bd74e5b829b343a47023d82313ec94a18dc7b17eab91a6a70389cc956b32683a965b7607ca1036f7dbd43a77d2daea6d082e75b54d1bac1b5843a13c0797724ff330d5f6df2f40d251a283adfdf821bbc37c97bba36c824f4b285ca0353c72e09b1bdb89cb71844ddfc3fb28f057efa3154431428f61e5b21be5cc9b99f272ab0140abea47ae17e09d9add023bb65bd70fc017848dcad3a622b5990b30be72c93e2773027b61e866a489a5a40a34dfbe87671ce9149bb76765a191db44547234fb9df8bedf8679908d0c7134c2303480eaa30a1ac0d2850d7283516855134ec1429fd9787851c05417f9398448afaeeea53c177dbea6483d7a68678d08387239bbf41e53fc6369700dcfe88eaf63c73fe6cf48007ccd8aa590046afbbfbd0d2952742aedbab63d3dc3095d05128c70e1aaf2efa625ec329fd973ed22671272b1c4bb33f81183df3ad822b26f9374c2338728377c09d0d591a5b179b41d3072fc18bdb023875be348409b28082083156eef7d68a86dc78d8526b115ccd0a2720494f2c3e52604579086899550e4cee5a211bac6cd7175bec11086b63d297e72d701cc5ea1b075c17506bdf31edfa38a8dc020474d9cb774f5ad25a81c6de758e8657bd9c2e2ea77b901aef65416b07db84f19adf4dea03ea6bb98a0f6393b1fc61a5f77c59c47f79930ea4661886a3fbb0d60cd16d0b0532ddd02d77026db2dfbec212f85513007efe39263500ff134dff04da8f02f1c0c5a0c3ebb56ee737261eea847d96597fa5c867ca9cd7e1763135fad43eec5369d5bced943556ac4725a86196a3e8f2367426d02169a8d0c320e04433c7c31091e17ed95fb88692c72577ac7e73dcbe57f31d5e48d725e0d10e55c1ea91c492cfa7c627f27fa4fd172ffedbf6012fa53f089ed67179160892f24d35f2e2b6c3e076cf2a8922dee0c0f7dd28c27a0fe11fc8a8606c286b18a023ea16bb8f6b57de7dfa964c822e78172e12772941cf4708f6e2d3397365590fe2d6b9ea26e797f474b792faba251f1270274545ba1612ae623bce58b0380a8457f4f84d94985fda67863066477b6d372153d4671f850faa179d0216cf3ef102bc9d94698ef8ab8536884b71fc89b2b1d8d96b03a20cd19fb65b1b3ac1e1728331bb65a25ed00cc8d0cb83717057c144d76ec12f527583032a98036221e61ffb54f41ca88e3a8b56a736b7a9ebe2de41cd92c06574ed8474e461f58bdf5e383c8879860aff632130d1a9ad20062e97f181701b5f72329bfa162e94dcbb0ee2bcff0e9835b766e24edf81a7b7d1c3dda72ffebcc0c1e5d74ecfa873243542fb3ebadc8ae740f643b08f9bea433fb788772e4f5e554da08e1565a31dafca15f43ab299d9f35e10c7871640f6939a244fc52dcb44f318c6c98478199bd64c39680f42234756d21a68949fe70771293f07034d4a3d4c17a9963225f7eaf33d191e3966a6d5e7416f96ada9a83f70b0b713f726970d77e7d1245e5633a863e89f104494b410297631d37b5fc06cac76d1a3640ee64615869430be1cd8e58418bd9c737f40dcd7032e223c30e88764d76d62372ffb84032f27b2a237252cc12fa2feecacd39d4db886277a365788dd2a1b87872f0df9046d0041fd1124d8365ca6d200fb2336d9591f643422c5e7a20ff908f1421aaa7708cb4f0cb863a1e77735ff191a8db1d66baaa1b2e931ba954333e5b51078ee0f51d1169ede73864a0dc6f4754b3e4546a07dbf74413cae5e7c84ef57272af2cb9b541fe3d2bffc68a6c01b1f9dcee86d095cdf49508ef292df549d41f1371fff56fcf5fecc11178beefed1ff875bb70df18742159aabf069b0629e3720f59adab7d20c9243f6d4d27504cdf5c9e753d35e65e32c525306b2018815a722ec6471cd069e2afdcc3f6516fc43762e01181c7d909623e3e0a3d068d15712596d3db75e98376680e4a976060a230d7eef288fdc85846ad35ce3a6ea61a0172ea23b4f81c793f322b1c20cb0a7d39e5dd6290d2e2f91e1c9ecfd195f7d79c72db35ea9cb65a5a048f08171c0525857123e9d6b2ecb15a328959425473d588724a21dd6e6321c04c273e3f16f23a9eede17bf8af56fdb3da3616937af640f41da3fbb04c1ce5482c046a2d87fcee427bb305e3e07b133005d906a2cac937e472e91eb9ccabdf546fee33e335b803f54fed57598dfc201b996ac72dea5cf22272f88cd3b8fc7856e7db38748323279f701a19465da7707cd04695f10aa3fa5772ed3d4a537f509074412509f573400288c49e00e3f29fcf5c5e2f2f2022f8f94b3fa7d4741010125881919988fe633d52fbbc7265f0ac0d2004061c7f616bd4727a576729f4d4f881cc5f67af8fcb60360c24094f573cd6a3b2a3fec8cb02aa727d802bae434a9351bd73c83bd15e5a2701e944d8b6ab5b72ba9c52c15e1d9017c558b41d5e6182cfb51cde9fe98ccb4b3e9dce85cde0367482bcd56e52ac7372b3930a275050ec5a80566e5bb7714a977283b8edfe0c2f20d0747b259cee6b48cee86fe8a8689566632a5735c94ac888feecc580acabac43eccac2d289adf3235633a1d6149db3c0bb1ecd901937bbfae754ea480e330431b07254565730ab72133993d60713661c85302f2c69fd064db7f29f4d44d0886f55e8f2e97abe7772500a7a705c82fc0d46d64d5ae3229aa8d2aa5adbd7476749b104032c37ef16720d1cfe67949e9603e0a0f0c161d0814ce998c434a96e27746883f7ffbde9f472a5eaecf140a35ca697b60736d18a394b554e74760b82a48a20f632c3eab0197215e836c0582eadc4d6255c089201e65b0a772919e82ef98f49799cd2d6a33a517a65c6fec61453f0d02909a46838b253a9058d04bb990f6cb35fc7bfb9f37872adbe75563e6eb5784e74db22b7282e14f2dab62857113a685cabc4aecc53a65d7617df992eee77dd0bbc05fcd74fe369e222c74a9fc6e515960b6315699ad254137bb2f996483ceab3b86406419bfef4248e2dd1d26b95ad5b73df6cec576a72184bb0ce3bdfae8060d715ae78483f958518b18cd40b81f1773e645c3c3d0e0f1d7115824fbbcdb3aec4a49eaba3b048e36bc747c649b00f0bb52c03c6ddc2235b43cb265920e7daadf83d3c9236759aa3fa429af08979b2682c60eb9f09907260f4aa339240df6b97e375a3d6a9ec893b9193712ef6b4ac0662c4c85479283b9f3b980b25c218c8e30028390aa3c09c4a31194b452fd3ade5ec74a9c686d1277f1b66ecef11a5adc3f41099c22ff509f7617374d741fef038f1055677b4d97208990a704d0d76cdb2fc44ce83381164465c98b9facb23a844c268caaea1055a919c91ea8e55f07b8070c3a8cee1ea2d677e4c7473524b848e9d4bbcb29a1e3584412611da824c791a14868e28ed4b218be7e009e3453dec652bb16e94c3fc26e6de535fb73461ffc73c59ccb5ba12aca17dd76b555c12930b97c29ce9289672a95a31bd0e93d01575aa8be660cf05edada9f6fc597e0fd415304fd06abb1b11d0fd09b5259149a61213f656fd01beebbd6cfbb2b09d6989cd6cd47e38019e7212b224584216a66fe03713bfd24355b750215058bdf128cdefbc125cff461e72b31dbd6f56bd4310093d1c8ae821ab09492f7914040bc39ff6c49ba42f61c4729c81cc4cb912b1a9383656a8c7c2bfc0d4165717c824b440f460478fce8e0072d39717e1d5d793e4aaa31d4cf3788b4f59a4267082c958753ee3a53a7ef3115fd97552f49f31f1542e193629a14854ccedfaebfe2856d1f15f3ab76e0c8cb31dd056ea8df09651a63657d4e04d674bd0807e794bc7a84bb99142bd2ecfdea172eb9897dc918de4984f1741604e3aaa3d9148e386b3b959a283d8e9fff8ffab723bd9e045b24284efe2787af355a10bf1724975267dfea69c54e34afae6735f727d53699e41277e31d0e6b67e311ab53adfeeb8e642aa7ebb0a8d8dfe75393844900319543a8006a962bd226900f696d9986bebe9a5668242924c8649f42d2e24cf2449d6568fe340f56c2d6b36770258fdf659e85ca46d9f88a452e8fd4a0f72d82a744032fec26db1d26ae05cd9adf5b0d89989239bf3876a9d58d9204c2b4e3a84a7e9ee0d9e0c1fe6f64eb869a0055f0a0767ec6c8e58227e3b9ed49fbb07617aef5e747ce03f924cf3f33c37019dac6e03d24601a29fcee9c163569ccb456b238c43e4a282c2ad251b89d4034444daec063d1460bd9d1149e5cb66983a72f28ad68622c18df17a6c2bb7ed1b826c821a8b8d526d24746c8a5e1f166bf5722c8b8214d5351c6e926fd6e5dfdcc16461a61ae7be7e9d25e2cb514e4b35057273b354d11427d15b71a12223e9bb894c302e45d1b7ed1211ada138d76a4b377203095a031786c2930c07fccb12a24dc666576e2f4b9056bd3d687bde24abb4723fcd0dcfbb3fdf3db9b92f8446f425be628f86ab7a967c11d2dd2a6cb1a07d727f6a9cf91a84633c7c56986831d9695577d81c927ebd43631582ba403aa6ac1615853990e8cab020d852f4757fa2b20c75717a0c058b7df9c3650955acb01572fb8e5376708ac783e1b81de330d975a7d8528f98bd430f4859b5ac3853eb763b7ced1ec27ca95bc844147ac816fef4f759a88a35e79a8c31bf829c9dfa655572c9976eeab1f04d45c880bfe3a410be36fcc111eb5ea54e9ed45ba57e5d300146e489168b8222e27ba0653207238eed7ded1ee1007fe0bd02dc8b89828dfe7c72529ea30a6956e6697c343e76cfca44266d684c58519d0432dd5f453f9b9a4472b35d825c360eed0a9df5fed1bb2d3c8b2afba74f9c65d9b95510994383b4c71ca33d844c4f0a3d7d1d417640eee9846472ee3fb4d2612568eac4fa2041ac0f72a3bcda3fa722b2ce3c2eb4bfafc151d47fc7f40f8b808d3c2fdd839408bb3116a0178cbe3dceaca4f453b1d80e10723d9f3dde3ffebc1ee5bee48cd9f423d8540211df292281b9089e0476ac240978eed3000e5d237601eb6733ea412cb93b72ce1f2907220303569e9ee22e15d68e708a5d591a11922810e25cee5d1e15857255122006ec360869b5ac26837a4100f01de6d4cf1cbd3975858fcc2a5029cf1b3606d2a1d87dcf81bfb0a4891000d06ae3702b7bd9a8a9854f0013582af86f19f09f7c59380903131c96cac554f36191167c3092b71fd43c505009268e6c280a467823694cdf391c5630450dc448433f338fd14e63d7c8e00b835e8896139172b1f06126416238017e38986d4559108d9ebe49eeee060d2293fb4809d66df469627a4dc3028e6a591a24f9b8b051e2d96f3753e6cf83d5296ef8c7f09cca9a7231ca494198f8e03ba12358b3660ac920b907e4490f40a1530eddee725b75e072410d42a6a6bbf1c63074250fe6b9c2251e8b791298b079060e0f4342f279257283f526e0621934dbd4b05dd8714fd7537cf624acc0a8369fc35d2fa516d99672a36442eeecdf8fd0d9360cdc3c4718bd81309b8be4fb2ac30c92e0a968ae416f3aa31dd49f696d3b549444bb573bb52f98ff6664a6c1b1ef1e2d73cc276e4b72e39be2489686c4022ef4601a5268ede24a240a782dde134f56f9cbb5ce744924aa1ba7815686111040ff874a84c77b1e341b21ce29a633ade18b60fea20164725c0cacb156c111cd5f4521d9df32a1958b650b08eb27058a356c0194b75a1572d9a22addb092887bc01d7bc7a86b08fd4771b5ff52f7d23b54b86e77ba98d85a4174b6e9a9b65c7979ffd0eb18a0a01cec4dcf3a947bb54c543726f146e31c72afec6f27bf258110ff55d9bfe9c786a006ee55ed8d8542dc655d29c198d06f72b874650fe2e5d97c0812750e70e1a886a078261f7f7dffe31f84e93b49dbe00553e3ca517ad87b0205a87d279b7f4615038ecb1129acdca7d0ed5a1802ab6d72f6119edd1f8009d6da4732212471b975d32732c798bb98d605da76723174d072ca6aed0e75ae06a57f8cade83f268eea81ad8e50a105d932c0b8e3ddc60a3c723ad69c3ed6bcaaf36fa8a403d64f6cc38e519e30dd40f952debc7fc15699f57287deb4425c03e8df8152e79a1cebfb80bedb1aec1486215a44f7ae23d56bb94f2b50d54226c6089a730d7b66d0c0c89b9926bb74a6d147873aefd3ad975e9b4517768050b0fd7505b252dc9c8c2f21b4d0015a846b201c73bd80d9a879661a728795522b375622ae03574242dd4eb06865ae8bd282d73eddf6001640bcba1272ada8cd161bbc0787665efe19c1ae847c65bca810542b27376795d35ed93f447252debfff50152faa9fe45996d0a9b31137e80b2f167ac2fd9315176ec26efa722b0c6fabeec8101c66994a1df2fdecb915e4cfa4e9d85576be849a5d1cccdb1131c4447cbf7d1a620e13d7326f4d77558643e22dee1e427863a5fbbcbf6ba716010c35f43b2c6c6bc67c1d33ce6eec1d1171dc192f6c80f9ba2a4226e8f1fa72a5da97703dc1253b38ec5d98912a716fe1d7f592be315e060d8b5d9ef3a16571d9d388b5371b5be8e6bb71de9c48b9a1ec0b3f8460447d5519b1343935e7c372c04d7623961cb842c8ed522e6b5920508bfaeff5e1ba8e53a18e64d7c6931b5d9f7816d9cda594fe7a2efe68863a534d7f743ee3001966235ed43d96481c401222c2438d0918cf0e915bb2ca7e9944af84eff757b2cac3b4141ec239fdef3f22c30c0847b61fa54c88abbcc3eb744b58f8e944fdebedd1e2d30138c7df096a726e109fef03bb5da1c93aafcd8de2ff6e25a7126abdfc87431b74d0e7dbd3d072588b1494fa396396db225631b3ff321bf3ee7adb081ab019f6aba2ddfbceba72add62c51951450edeb63c1a692b7e765cc6839f0c77e6b293b361290bc170d7227dc002d74838eb763287254768ff42a0d16cd9a1090710baa7b618a49051b72304020954a524512a8635f19622e6ce524581ef5472a06ab4ac7e07799663f72ca644a6efdd140996d1ab503c1aa7e9194a0513f29b647e7782baeb974aff97215c71c60d4e23c958da25bac246be51bdcdac9f7af8641c819b56151863de87296b99be35c524a82b5063fa8ca166dec415eeb4cb27964bfa2b1528be34dd972f46f102594b982a822740191dba2131d0a125261d395b05dc95ceef8f460cb3a7457fa257c09067cc9cd4f9885404f4741f2228f1a97244b0fc5247296157b7212b0c5fd2453261839a0de9a8f26845dab2bf7398996a2a033642f3cca968b01375976569e617c6866577526560cfe330f92abd7fe9edbd507df9a189327de72f4e3a6accf58a143251cbaa422653ecaa6a5d9e7dd5b7a2b36161989c52fbb72865a8f05e00c78914d7b1b1ded12fcda63d7afeb4e857fd3a5493ceb900432091e6b76183f6172c01e2211efc0724997b2e2547dc1f7ed8e2af3cfad6803c872929175edefb3b46e91fda122d6d86a53519976026d1c7eb6048dede3be34e438ea09e07788187474b247b23f3db7cf3b03cc611b6e088376ac24eb450ccb0b720cd5f25c7ad422d6bec79ada1a6521659dd3b002689ee02dd27e78c044091e720b755194ab823dd745b7ec358e70153868f88c39ef59520bc86ba1a6a8316c21a04aef3c5f95528b70e44d9587cb227333286514825aef8747eca76bd8d7a5420acb75e971bfa12ec41778d449bd2d4f0a722289f7c69f447fbbd9eb7afbe70cbbdfe481a15748df80f27e459131a3e52a534e6f5a26f8b291c2d01e9d255f724f97cee138015302ccbb8a61ced3d67454c48ffe2f8453dd3f73a271c76d7872a5696c3fb609fce67294c3d4038ded50aa61c679232161529a0dac0845ef3372cfde26f3e41c9260e55915530fe0fa312fe2a5bc40d958870aaae447c9b7a050a768594a79f0bba3e353936f8170286e8572a5499c79fe2e6adcbe1e690811067123c7ff22ca2bbe929f1d1ea27b1a1ea90095a873058f46f8bee8cef9dddd72b70ed5e34a544b59c99d568f6c98d853dc1d207c2c2df32260d7977ac8bb0372aeb07018e7e9d9d6d4cd159af7c6c4b990f51a356aa605d5ee5c31a1832dce72969b75de4c710df879eb725f7024941b9efd31f01a8bd3a53a88a1b1e87fe21d19ce35f30e2cfce4d05041efcae45d32fd2c0e37758884fdcd0d43212bd70472b4b8dcee2b8ce74b85631b468773c28a297297566ca60716764a1c7efa1d447240f2f259aa804ff6d8ed81fac7ad98d4c02aa9214ba7cd0baf7764e85c2e6016d0d3b0158f62dc3276c37d7a409f586aaa9169c43572ab78e96dc1ade4701a5a370bf9c11890f507c3f0a3f75e70388fa5bcf03d07e474974ca65750eca88c72ca01579b8a1a3c5871a73708ec911a8963fd76fb27cb3ff36a7a4201a48ff22f12612b3829393b72df8376d356c900a01c5dbf867834bb04398c0ff110efec08f6a52b4dffa6eb2f177f5daeb9c500ae8d8c0d9062d5dc7c91206f41e6f288723dd0e9689bdbc9c14363d1e37ee150049bd26e767bea2ddd5cb06268191668052fd7ce2a9810309ba71ea91b1f97cd515cef878ea33d14c88a321f22fc809e3e59407842995fe95cf796fce1d21c6bdbb73e24b5b87579567d19b86a83bd3a391b21322e384cd96f463c829fdc6d91ce7eb6d466d1d5712437785316a4a97972e8f0ea63e8b2c235dc4946235f14a23afa7fac1bd92e435c000e94590bc21a141c7c2890ad1ddf76b0c1a8ec48f17f76c56391d1ae25722fe3501a2389d85772961026d6aed9019ff854659ada75a7418fd5512b238f4a4b48d611f9dd09817278d8fce38f25afc92f750960a2455b645ef4ebcde67351dd6e539c0d336d9572527903dae1ae6b34e3a8b50b8506840a94c09948cf22c4071a24e93fe6f21e6094d94bc05a4c6c61e067ba28217346f67f15c4b9f3f28221e896f5a0f0cc2b5b3ce503cd69642746d7e175b4906c3de21c6b8c0fda6ba4d9586778265099a76f0b122ae779fadf09907de2de13b6670330cb5748bcd24984eea32619dd626634217d342a4b4b70b8ecb5be25ef63c2f10e6aeaec9375060dd184b11c1c44b050695a7085472c237a23bc397cb2fede156e72f4c46b52e44151a7b14244b7fe6028f1959ed986e98efd0f1aee84c9bb5497a950305b6fb4abad72b86f174e785ef7f0c2848262d6512f3a05e06bc4031c074531727c7b530ae06cb677c82d4572638000e0a306dc963be70ab0455a10a5011957a24da63bc87975edcee86fc372eafe4e7afed4d1c0799d85e956420c95be48a03c9021a1d7229118d2c49b337282feec35a6fd1e36d5b30f5586a9d0c02f85da908559eda86cf083a66e415e725171fba02a2a3993d8407562a13a1e0665632affd34b1e69b55f5b97b213a96c04e84a6602d5a0e5651ef586ed46c0e3ae6190d06dc0e08217719cdabf592772176cb4c799bd312a1366639d0bd8ff9c3ada4c368369858561613676495a7c72c387d7020726c2fdf118d2d5a0693176fc9a2ac514aa3fef28e1eb979b49d4727aea84d18fb75749c1e40eb452f6b8dceb33192d247d79128b47bf6e31e995504d05188b8fc37ef3577d5323d1199bbe05d797989c669e0ffe02e9ba412b3372e7e7f66d69a3ea131c06930a9e077b9f378d25247098b9c4f766f78507b445725fcc12c636f92d57e7f96302d2d20884cd7a61eed4419a1560b2138e88924d41c7d7c95fc49c11d30cfd3206baa7495ce1ff89c8a88f43dc5a12c07ad3b9f46f2e0d9c345241240a94a2b7eb714db116d0eb34b632790ab27705017dafb71e7294e026693c7ae30e140d3daf47b2b70cb99c62fd20175c4c19401723b270541cc9a5ac42a7e34e504cffef24c8d55efd58cb0155356e7d9caa59561c40d77716ed25d9d708074dd793e168072d8e6b4fd675046d64d3266efb1e553bc3b3cc72ca1c8a0b821346f2a50963f929c4215d5c4211f8eededf21b4e1fffb4c7d0c1fd3c9765c22aadafc0c3a52ed26735f98fd6670556fe25f157cce4e4ca66769724c00c3972cb87cd846f96878b639a2ff96d9b9dde28842fcec755e1e5e6f271828b1f33978a4904f1c313299c8488263d5171c53e34835d71cbc5a000d63e81b1793e6911d101f7de27a4855eb7c5996a84424444178acf2871be80772920708f26f2c31961bd61a495eb4cadebf26583559c387475b6d59644b929ae6751f3b74a10eef97f0d0af81eeba22e0da7e79327ca57676f6e072e170273eff41486da0e978094a88e273a58701a35b5a1afad6aa769896a2f99c812febc734af1572c21aedda1266ffb8d8da64f49e866d76b8d442b27d7967353c99d516eb319e729e046bc42d2397eb031b5fe7bee2c52be3604758ba4ad698dee0f47285d7836673f6c935a34fb97caa2251260c979debe8d960775dac6d1e60cbd8d777ad79538598fc96586f5fb36b49bd80b8ec7793702d687289a7bf7aa0a23c01936dbe70a307c71751e86f078924a25135e22b645c9856ffba3a9e3e0ebcd26874229672d9dba48a83560f3f7995d354999c537c2ed298992226dccc1d87094cb63e4e72ae6f7c80b25f4429bc3656916691f04f3310849fb2df121c5cfead7abe694a0e3b826d896868596fd564d05746c295a88eca07eed7f6be6fcf1ab8b68dfb8872a182bb716547fceacdcc54058a09aeb9d52ff3658e2bd56e210e522456c9f105d7642e1dad763724174df50a653378110928783275d168424f0b68b8f0954872a3a045fed7e281f10ed1ffd64c1c1079f68718d8236d3a804a20fb89a9414e2443f16c6b3b82d1c700c4cd3fe2ef8eaf25420e9de78e8d2ec36e8d3622200738b72ce19744ebc4f6032bb69ec9c2146b1712318226916a1cc6b4247e6104487267ffb7bef7eeaacd459f2328ab673d1fb1b06f5c60de51e22672ccafb63d14264e2ef4dd2390c341b274028617ee709ac0acd3dcc636639604bfed9384dabd72bc87d1fd7722cdd99c8a6b1c1e93b165654acbc54158e1a4e4e6b7af2818f37279046ad9fe7cac3f3c7b381e4371d0e193483bbac67aa94a687b40ba64e42872fa9d9a8de50529d495f3f2a8277de4c311ea159952dabd3fac9de8352cbacb1251ff8235e193a413232c4ae4dd861dcb2400dff247fdc9327d9a7487d9cb0e70ab536cf31235316156423db14635af7bf7cb52e4bcba3faae284ad28d6467c6e87f1c873ebde618da78bd3431c8c0faaec681ecec13d3dee45dd791f48d55772fd603c340316249633da194f24758f549e30f376798b1f441f8a9ce7fb0e2119b0816d1cf2cba50ff383e46f62e8a9b8040e599abb3bea6866fd21208d051e722512f30cfb5ac8f5d7bc68d8db7f0c580691a3108b6f4bcc536b893afbb3ec72d8bb6ed4309a68635dff02b4cf11b6d06e10550d8a728564b9adeb8313c14a723b8a29e41f1cbe9b4d25492c3e4099a3cdd59836000f82e53a92c8346808a97245208ff5694385aa0884bf5f390825836ffe25fef8102922b43af67204a925724a08435ac3441c8a296ee2bc5c2b8e716a75171843ac6a2847a7676e8ef73725db9a56c13cc8c316f5e9e3f16c155ab303dd10ffead0407af8fd3b9a2d4bbd052a3d201031a88d9fcb37d53d40b22565b248b4462b9103ee22d1d683a7ce13720ff6bd5b4ce6146b8d402020c1bba6f712257313d682eb27c26227ce1d490d3c4f56905219adea7115a9b99724fe52ab81d6f743eab9abb56373b88765cdd64f7c2bbb1052be908594d2f0145572441b7a9f54e0dd35391b2b7f5745e67dc472752d1dbd51a60ce2c06767344f2126ab20fe51248751a9c3812495d76ce2ce72719b2a9d45eee4ca81745457fdbaeb2be9af73598d95ed63b6179a89179ac365970b41422047e35e1e01d5f9becf259c28b1b992bccbe41a037a7048c65a863dc4683a040439299971ffc0996999f8d534b2227eeb6b2893d2b2ace9fd64e472d0be22a2878db71634a5eca080ae89d17415542b43082890f48f0c92c26a860cd7316072e02c0476516e86d0054e12800d702f58d6649862be68ff1d89a6b272e492013a2e3391540d36a9b7d0b909ecdb3d6c2ade040d39bc35ce992d204c72e8c1a1bcdf1f8d3ff295bb9c5f42a1c8299ca20c75bef7f2241b20de3bb7a569526d5ab3aff358de9889b0ec8802ff240c286e5b0fea17562e7e533e8bdebc729494475bfd06b654ac0e6be161a38ab501768e60bd97106a35d2274279a7ff72be7edd321dceec8ee40bf36502d82816e6f508ac797fc26485587b2743b726729c22e7f4fbc84857ddba77ba0eef2bb18f1f1f1e5a7bdd26def05942f0813a321615b3a73edf7d3bfd78dc0b4df04aab88f3ac693542ad9726eb769841335272b59da52e702479e447b06d2f80723527d5e276dd508f53c7a7466bc757da5b03064c7ad1922c0b7c59c878896a2158303dc17a04d0ba8d0cfec59eec3b88053c466973ac5c4745ce7970a9b920fc42146c70c5ecdee720f82ddceea346525e72faacb47b7a0608555430676d7cc22102231d5124d838d45c6fe833ce4f78e069b90fd2ccc5912da6210a125ec1bcaa059796ed5898e017acbcce193de6df5d1a767a9eb1af226e0d863821142eb472532ef727a007d0569118108ba409c9d972a9a0be929b8473b9db4b87e4d754cb0121e9f2dd487fe708b64b39458bbe2e72aea8ea6a6c9b92d3dd30832186777cbf8d2d61cb80d6f2f07a052d9f76be087209e78e48d39ffe3145a193b3c33258322b425307acc185c099b3030378d8467244fa20e2d33723b1caaebaefdcdfa87e1a4651afb212726486cb9b153448de191831e76d1e4adcecaa77668b2b687f0503bfe3dab00f79bb224c723706ddfb4d662727d32227aa396b0a5defffe3c2965951b195de471daaddbfb0fc79bc6572c3591d988350d5e7dd0d8accb23b2c21bc35cf66da2955b259152f0548252a72fed3c2e9b0182539f34d28933e88c01ca4e680cb3be7378462348d53dfe0df7281db1f2e22478f86af6ba797d50da24dad0a61c950e761a8ae761c28057f67000ce693135167c8783c2d2a105e57a2e20c018c008bd6d4ece3cb7c1a090844234f87a761cd144373deb2aa621e586e77ae0e5146ecad6c125ed9c9c4b6353844cf701d3d95271e99e13b82e084c776867b82516bdf6b4e819cdfd2c7ba3f2b729d09c2832c90a54ffa8443eadcf9c853e0c3c8f953b00cb2b20e862b4149dd54e541141ed628a498d9833aa8453480d36787581f81a52d02b23424c32e3090309e800177442d1d31f519807d5b2e11ffba530c6620945b73f3f216f08bec4072dd403550466836eb484d9b08d96e10a97ca30c7dd1c28f60566a1fcb80d00e72249fc72c6b18a4b7419170fe1ba64ab7394a2c7ec0b25ba345eaf5254cdd3472d60a31a61825dbcc93324470e89e9e11b6a5483ca8fefe47e80b662691b97c6bbacfc9d8a2cdef64469f79d5a8fc04f1bc3c8b7e2a23cdb22969cf5bd55b91721485e48266fb6b08c950ebc49b01710cb4b5e95fe3f4c8863fecc97b32de1072962453f22e01c8b60de7bd6fba90f150215e01ff521203735723f3615c4eff72c835117913c42aa03a4a33c1d7e5466ef030d0370a48ed55c2c46d9b0c3efa72ceea2cf73afc83816aa24a3aac4fa3c27e8ca0c8ddbab7b7dd1c5525fdc508618b0bf2f9480c86042a980df0c7246103b661682c668017a5b8b950182ccf9f72f0e653f5115a0c87ef352db8cf21ff01c39f1220c26d550c3bff48a4af1cce5e54ed673c02da98fa97d9727e2fa217f78947f0c9775573d91c9318ae281b5f72a2127ab66b6fd99033dc069cbdf7bbc4647dab5cc3b81f4fc03e464e96a10172206a6d580dd19b9ecb320c8b9e1071340cb0528ccd9963bc7bb4b794bd85fd7245b15f6cc576891ea36af0f329813ee2710380d71b804082980b63e627d587407954cc5a0d8420d58432d719f7c01703d833fdfe7900a7c6aeb76e6f6dc07172d23d219fd9049163888e03acf685926e76620e94620b94dae3442d2a7a2b892d68cd8ea06f0a70838cc21f5a8dcb2a38ba4864806afdc1611ea0ceb9a7b38366d3211f005bc6092148d7282f96bf35e3acbfdc1c23e47bfebc1d50c27910cd149be3884b53c7c8e83cbaf2b11b6c739ef3b4f5786f37fd62f2fe5fa6009f27723190e2f5f93c454b17873e8df6c5dbc3df40bd9b482e0f7852fe0d22ff1e03726f552e3f41b4dbc24ee8bf5fa4bd2bafc2f81b70a722dd011e03adca60fb7d728d246599486add62062cdaa51d394625b6b54d9d8cc3913b5861a62e17901272cd29b657b83bac1bcd42ce9ceb553f862f29f14e17b2b07f8ca3a2ca5c262472b82af28f47e730762e54255270cb6608462140e4f52d5d61d3acf3a08f3416358abcec3ae4e730196d339d24d2dc32692fe09a67d3ce50cf87a125bb4212db727bbaba56d098ab86f21bd96fe563eb5a46f093caa9a53be0098b6b67b7ff447216a26af84d636d05b68e0c645898e5267a78ee80b94bb2315f61cddc0fed745f82b63b203e72611d023b84593d7683edff0568da4eceb2c2e6663e0eb5c10872b45770d59ec740162dd2b6895ce3745d71fa61e0cc55869654fe67ee1df57572064ca61177ef9afab45c6c520928fff18f1328b4c7f4057bb0386b7165cb642c6bf6031f0acc2ca1e219de6cee76f2d2aa6135d70aa76deb6a0a3dc0a09f144a158869851f58ab3b23d42b05f99a9e6968ced459d2978498ecb4eb97706fb14bb315940f0c064c33aa1edb49e5363606d583f14fbb1c024e3f5d0cf056f20237110c814e673c7ec1117cff82529a136bdf801afd76406473c1810852be0aab3afd499f8421f1e465b586a79b02542ddb55dc066d3c6b120146f93ae694eeb2175a97b3d7ab8c0f187b98f85bf89dea464d28774377051726adb29d023c496a72842fc25c0157c7a18ec4f1641b4e1fd1da029992fc603f5850d6d58f64864572e638cc2368c1d04e59846f089c3678f50c1d88faef09cd612244ccd1eb6ce072cae4d456bfad692ea66c4765003d4a395200477a3e39799326b6268254f0f072810ec30450050984c8cf890c013d563815edbdb67a3516b97a05d215aa83d8725205af599cb37d2218c12dbc888046b580be2fdbc16e11ecf2bef202a9cc853564a3dd6a5459b4fc7029c17926ee757b87d15746e49c94e9b5635e6d94f4896b9a02ea67e4cd6e785008b80cbd3fe056ca1d03dd85b7d529d1ca67851521c2726f759036b883c5322f17e718675693dbfddc50a6175e7ba5431ab803120abe2978cd900e5915b95cc6cf3fb75de25feb89c5e3379e6d87694bd6707bbfe01e72f4b791c25731d79012d36549a13f3d445875f1b27802aee211200f0e9c514f0087bb1aff6433e656f12125aba7dfb81947eef43169b07d4a97c6600ae14e1672afb58bedbe6621ebae5dff8ee8a71d45be901272a2003373724f4e0c2976c6727416f38279d4d42aa1fc9f2c21941dbea0b3c4d3bfb3088fa98bea2c291e093b27d5fb04b677156b303288550870e86b8d5b67be7e13d699872fe63193710a725542daad9ad4651ead893c76b9921562b1c4ca589236f189506ecd7446548272dc4f6f845dc171135c9fe617a5ce218b4c428dca95d9ec720d035468a272e93ab00f9a4eac6797f5c987a0c36798f443074ffaa8405ac05fad292da4d4c94372840e04b2246450475ceaf1fb6e3faadc1d33bb9cd7476f24cd6919c1977f54722fd79a3a01986bb48f21ee9289e378a8106121c69058aeb491cd7eaae8aaab72412d12bf4a51c2148a1ef9f8e3ec5de6feb479a81bffd0c42c464b781b030560ba75a45513ff4f13e820df2c7dbc105b09fa471dfefee1622773049d8359197236f02361eb89ff2211cd0615f5cfc1853b9d50bb20ddb20b0c749fc25e0d5972e10e347e86dc83daf49c9296ee81831af0060688ec2dadaae55bea20a461c872d0522ab63b2034d811569393c15b5692bdf05909efdf1ca3ee20a3c455274472063ffbb17434401263618cbd967027e0777e47db796d63e3efad4a7dc4a18b086a219b26b4d06b9af27764d8652ec98b2d715fdbf5f3a58ce26554912e507572699355eeb1486bad4c572276f92d8fa8721f9f8bba058970de0edbe7ca5c516fb55e316e8fe8122f254259c37e105c71d4928fc3367dfcbd4d84700f12e27b72014de1c69831aa94ce437fbab84f51f6895598160bfc68249b38f25a239d1921ef8bcdeb4bca5706d932ac5886b2997c738df68c218c7b305ecd6a5c30b82c7219968381724bb8cc260ec0bd72942655449b59d4a6b7e271b2a1bb5aab7915332cbe0eabfd85b4c39cf50c7562655cb06891144ab77debaa9d569371c3aec0728d4ddf5b14fe066a7fcf39327ee8b12bb05940df4f69785b86b6aa43ace46e411e9d64568db50da152d804c957b4f32551fec4a61ddaca30a11f0955dc403672d797a0638e0766a9393d80a576afa9ce876d7af997a8bace2ea82d3ac9ad8c727746e7d6fbd6edf967ea924afca4e231629aa700e45b8742ca62fb78503b547284aaa17c75585614cbc2cf7c98e60e984f544a1a74dd26a20d1466a92b7ad572cfc9a5f049d97b85c892f78c99564cd713468be93ee160d7a260211951642010b07ca8e0d7b6a69cc86a4838cfd24ad509ef80c7221cdc89fa1cb4a966e46729758fdda5a784fc43f183f2cb175068b61d5bbf921bfa726fe8633b8ea554237252a88120d2aece89f93871f086ca160671b5e6a8ecb4346e06bc414c21d9a6728c702bd2a56c193daf61f1e24fdc50dddea2e4931b5356c7a1c7d4b85aa9b70b51504495c232263e4955857a14769d70532932edae748bd8789efaac9e54a80556338314a1d453a43efe89ec58be0e6e0d7d3c2ef08cb11561684a3e5270b372a3e4844fae6d1b7c7d8c350005e4417ef31a773f02f8610e4148441d7b4cb56b9f417ea56fc112fe301933bfd2b1a531833bf7bd9b4bb92af2e380b8a5337972808194a7100f86b429d486a8c9bc1e80bc83611a57882388bc2b4776e9c9447223c2df432d807860b6a12b09974042a24c4b2d3413745b33e7f6add28fdc4e724e0f8824ce71777cce4021e56f64eea1d992859893f353607d9c22d4f7712a72c5473e88204e7e8b1063189a92b9c0b259c2ce6847b277bedd9c5586350f8d72502abf0c05d381016abccce8c9814e3252973ed1a90172f3396357f0d248190f25f2aeeddac84c72e057043c67404c88121f00ab462ab69f65c11d54fde6f272fca0d764f911678a56768cdc129806c02ce322d02793220f654a039a567dcf7228ded8d6c1d4cc9c933748eac9967dfeff97632c8b8e2fd6fc8932b016b00d2507b62aa57d70862bbfd8bfe3f01e25cab18bef29c4995055c1cc594c913c7a0f9ee0bd6e5d5df0e0eb96bc53ca8290c61c4a33190b7e16a7baf65133509ac5635427bb71b24c1fe19bd8dc08397765b384a04de15c1835634787c9281b351b5bffdbbc45e02913c638b521810cb5418fb840dc3fa9ec46f418398d329cb84972b10a8530943573e031f31d597e3ecc4cc76a0a998123a07b914aa74d336b776f61848845a1505497f0ad471edf7a268ed05d3b6769912225c2ed075e3d63ac72de522e4f02e2acde514ed62f7488238d9bf63af87071f219f4f6fba9f530787236b8cc3d4110e32cea00480c7cf6e49506f01230853db0d6f2cfb7d5ac37b472d99114e5a3849011ae6f9240783f015c749eb9cc4109bd1db578e84f8e60902a509102d43175c72af185557e7d1e07abe5f549715f7f98e00699e557b41b04727a076403ce0209b2ad611f67ffbe2fd12de08fb0be6292112e1c432e77f235682bf5b3d4dfd3be32214fe4d954b77a022a241e812b4922d4957f43f0f43e4172119677c4b5bed85f8f5768358cbb2b1f247d83e88caffc49f653842f52f0e6316f442f04a2ec26350f0cffd9e71ef0a22d3e7d22f44cc348d838e9d8f96f2e72874e52a543499e711fad11a68d6f73cb40b6955326cb8df35d64051b1da8c67250e9d2f57e27da79eaf8d6bd450cb3c275e4bc73372f50f41f59714bdd6ca672c1a08b91002f1a25537c1db4254c9ec2f90b35d6f98ffe1cd20ea7ecdc8de0723b3186ced6398ed4ad2debb60c2c7aa6d9071c9f87cdcd3fa30a449aaaf07872a3b770652405a7dcd7029f44a70a28693d21500e721a47de73835c4090acf37269ecfe1a9c8e4d58d92befafd0c28387b9e0dadda4e27ea38d8ab9c99e90a872a65b71c6198b8451d668e0e4e3fb1da7d4f723ffbe0ca3ded1926e12cf6e7b683a47f2d7596e4e27b93393daa597554d9686b9cb57bb9cba35a9abd69b851a324522d515a86120f4a77cd24e7067e1c870cee5c1a77dca346aefecacf3b60164495ccaa1b4da6b4f6b3bf6d9119f96e2c9a06c6a5fcb5a34d29eef31846dac11bf5ac5cc762081ebf57c9e70d235a7299a49caaec7b150860a42d0eeb970715872ddcf3f9b646dc055c5a2223d98ef78a682e9f45e0cd4885de5bdab70f304723bb9b08bf47be8a97f57489388223e2302446cb9a90d8b76990267d9ef9c5c7238797376e4001960f667f90cfba5b0e65e6d126c15fd6fe26b868461cef15572f1989edf8a4330567f16a21833a81b26413dd888a40d49cfea3d9f22c081a5729f7b6d16d7a1fcef33e0758fe3483c35fbb7ce4c3369f94f6546c570101d97090cf229e76405ac974b918a60edce636d97abab42153a8e9f20896d35fc392b72715c7347d8cf111d01e0397dc659f2362930693eb5e3704ba152d37337da30031164b60dadd8d1aa10d275f39bfca0b636e75a613688a33a411199dd56e6db5c4e47fc79a375cfd628d8e9458ab8e96d4d538a185ab86b01f1dff582caab507231167fae9b9a3cc322b13226f959ddbdabc22817af6c9551dc3e2f46cf67a02ca1456a06cff0b9ee63e24391386902b14fba2edeb1201d2497e14b3df171b318fc5b7a1d76c0896a48f7d5ea5287d2dc9d69c67a09878687e604ad6af3d972430fa37e4d4a91df4a9fe6099a773748ab460c2cc6a5bc2f6deeba7244d2471a6b6efbb7955bb762f86afb8ef909a07db8b45582f270b6cae230c4f51e29af5851e1ca76883fd5455c1c96cb56206365e5f754f849a3cced2bb6655c3195eee472e88cd98a2af3e452a1d0e8de153e0cdebac26e72d5be9ef381eec93e5afabc72bac87d48e4064b2a887a9e03707b2332524a8a527d71428fd1698d06cc13d672bae0dd743c8dcffc60f45fa9f60102af541bbb1c6549bf08f9c6961d70837172f3289948333dfe0cde3ac6b3b025d18c2387fd10e2660eebd0b33c78ced72e725c76e3979b7fc4297235640abf2e12182e78edaea5f41edcd5731ab60216e272e901201adb6517a4afa4dee78f980cafc7c5d78566857fb5d531af9a80bcfe729c77807cac1fce07fff6ec6254318be8c27c90a240c573e2383d29e9820ebb335eee3d554f21f3034dac0ccc119e8367eb3e1abf55158ddaf32e04b743667a724f59f27c0f670bb0657b02533fded5044fca8c14eface273a99d933fea62580b8d4d17fd68d1d04d59ce94dca81534e6339dc5ea7b4ae3db5cf37d7b384ff6722d6f0e4bf8e2c1acb50123f29afe48ef4cf669baff32dcf4a35f3134ee6ce672f7622deb3a209934ff83eea456e4f23f8bbe01eeaa952b9f02c50ad1842dbc028b3f1ad5a19808a66a29e6d65e30b7c50a2c2ac4fd46d6a0431d419fa4132b2b0425ee17764afb6de81da5b267205bc87782155a915cfdb4449a328a7bb08272daac3a668d190bf2a7d6f611f630a48a222d173d04dce70e90fadf3194509341c320e3297c5b787d1b4f02760ba7eb7403ea4dbb2a3fd9dcc81c19a92d1ede2804801b6cba0e507587f45dff599334a3ae068462192a0d1d5e4cf77bda3d001f194afc1c528cf514bd226958b18953766793741a5f798d750ddd117c535dac72d5c20d459a1050e3ad2f3678997ed54817773fc27e0e3ea501ae212c1eebbb7253c5b5199cb5982016aea7b4ec961c4292c5f446ab4c7f605a7038c5edb11372027bb1b8b733b59230b500210d0c71b5dca19cbf39bcb50211161101585f3d72bd5815bd5b0c49aa234cd681f4873234f27ca19aa6d65cba8a5d04c42526b8427c4b3ae5cf62e767c64f50804bca6a634bfe62f9e6949246981c4691dcb60e724d51a18a4635f443129392530d56ac25565fa8e63a6556e9ad3fbfe9b7dd5272c6f6aab88377977cd47ba245aaff3d68042838762e9f0816c7e047eb32562c723a14ea1f1c2225d89a4a14635ee44818d5f0e8383fc341d87bc230db7886e9729fc7ffa80b63303b4a7c6a10a85a05a69414aa7685cc4f64c847f4b562047e0e75e8dfaa45010bdc6dfbd7eea001ac18737933441357e4588528e0b64f1eba26db46459b008d2ed3c7b35aeabd1bfad9bce558dd86412aeb779bc7e1e082483f0e5383c948de723b716af795b377ff2e9d0a5bf7e3c26871035cda70684c46606d5d48544213150e24bfaec343946fe2e6c677cf251c09a7b6d1914db41f4803fe1265a25f6771943f4af5bbe818097505bf2928795695ba824ee3ea53939d1db569df8aaf9ef770e4d93ca59a253f1815370ed83693e87ed0e0c6b5d8a7f97245b044e4b22f395da55b4fec24a701441566ad5f641647e76c4ab477c58eb77265290a283c14d9d56f2b094ec6997e7bf33b46c85169ab4afc5ec32120f8c072ee04c02acf4553924535cd89b0bf0e0bdedd28e735a1df964a718d45c1e1dd3d024b22114d778b454f7d0a1bf0f7ee0e3d5e634eafce6a385997a09abeed2468acf3844875c79d16b2ad3c194adab9a29d57c64d253a7b52db170eab61129e18c2589023d7ee99ad4f00329396a3aea76633bd19ec0f3091a150f1e06661ac72f283ea9d3561ec8b85b910b8f6902338a356709af834b4ab77030c7d1e8e1b726d94ddf065129eb7f2b1aaedf5dabfcaa6054307cfdf2ddc5916d42f340ca8702fbf89b5c718c244a6b58dc2716073132907bbbec0770f5a13db6d0155073e723294cf5498e6ac8ebb33ded164d0b58de8a1fe0b8b015f33d67a13c04bd0d22e5e43fbb13db943ef865ec8502700a93c714249ec592557b3d5044e20682f89488def1c9ff2d9199cdcb35504db376648798b29a14b7df7755635470b2dc992728d747827ed3068f2d07ee504a9cf1b75b20bb73bb68edb18e0e617baf03dd472e65766fb00f621e93f6e9a237bb9247bea332b493791cbc961430459fc28112ce95dcbc94937d70cc712d83a28ce6691f4aa7e824f683de329399721e5c8305aba54164672b8bd293ed2f4b12317e1ae8202f2977cb766f1c30bc05f42b4fa47ab9ee883aff1be970ff69e3a8cc1e8ec2b828aa7ca41f3d95b38e0999dd68a4d2cff82c0500b39e53a363f3e28540e5dee085b5257292644120cd9891f1bd5685f49fbb9798b8f2df2ef91706e2de471631ac9d9be551b23ced02d03ed8fa2723818b2a5c616017f65d68a9d67b16b7472915420bbf10d18707946bde8eb75723036360b3143a922ae70777c69b91b1f07849cfa38580a0c3c7b5726141c51725bdcefcca6f62903e8bd367c6feff4fa2e4d2789796c98dd6474162a950c7c724f59e8f2d3d14fb53c2f1569b4bb20dcb6f969ce2abc692cab710022670cd56a801495ff40b3a1fd9b04401bb52df359b889c31a03472c0e3ce9caba048cfc69d806f71792bc35e0db378004eb5370e2316f56d184ca8ad0d1c26b1d8d6a03723d0af6723e76e090eefc438ddcd5b4f2614229a247dbcc8a7e5e00031707987229e15cb871975733bfbe63305f1628f5ec3fbbb5329e29aa26ba40c6d4783924c583a4b58f0cfb45bd9bf5ff0058a0712c15bbd5db831715165ee675386b7a3f61e6d7eedf3a2088bcaae6cc27e518314aa07739068509529fe9ccdf1a77e772ca1805f80f8ae3224884a10fa815572ae019a0f7bfe88e3ddb71aadc09226c72c36629a2991f68669791d7ad47c62cf45c7ee09271a084d5b94d4deda5d89972f7eb48053be41273a906d1767152b33e69e94e0f5a9babdf3db89283c9578272236478b04c41cb7f21aec94d8bc3e9c832ffddf4e83ce4ab33ff3f483b13f472a25f121b54cca9fef498af4066fdcf4dea679632bfc06d33f510c490f1682472cb7fbabd653985dde4ec704774fb8ccb3bc3362aaf788f88383b50a7a3dd34721c6966a5e1493ad34aff0d2acdb584ae571ca2cbdace8fd39eec8d9a4c5337721d59b3cdfaefe8c64e6b7081ddf87690f15c97a307c7c778fed0f0039835b8729c8098a5638375c1a46f17ed9098efadd548d34ff17f17d4ceae2e3a04e0810017d22a7bf77ceac815d229682c01409b5eddc9a49550e013f553c26b1c7ba72c4119f6d2d0411184ef30226419ab1f228343a6d61b981a22206cdf440627b0723c6857ac7e9ebab564396f0fa86f387afee772883d63873b00c654555d406f72746582bf53e201867152c6485ab3181ef5761370e089c3a93d37c410457deb72142cadfa223a143c9f4e19beaeba4d4936442a4645f077569d6a150cf364825d5e88a02e689c658c41396f933f538025bfea54cc837637ad28353f002a5c0f724e4f1d3e815da506fd994e47b1ef1601212919b7b63a0b68b85a25704c09967254f31b5cf6476439311ecd5873c962364edddf7191e383cae3dfa33c88e6ef7219425878103fa12f609468d837c59a03344dc8ed34715de07040d88cc037e028c91a0dc521b42953c4cee9914634681e8168b54fe7e60cd5e03d503abed6fa72804bb599d4308c34ba4c013d54737dd7c34e71a564bcf3df0d38343712285160360e52402564055b634d070d08e7cd7a408d47b4e96b8a0d4017c63dbaf01919d8c73166134ebf237d7bcdbbb6ac115f4b92b3dced7f2b348b0796279a23543f1bbb06019be4290bd2b55b67e9f8d0d0dfb370f6a95f8dfa8557a4ab7bfef9720b700761b845541bfb278148d97bfcde9ee4cad890c3d4319913a2cec9923a72765ae9b383228e9694e48bf83777bc0d5e125bed50c83885923279fa11dc0272e4e7bdaffb5bacd3e62e7072fbe59493e31ebb7721eed5827f4240f01a3e7265b38271eb699cef7c6e83d3860c2ec3e32f4d836becbaec643c38eab1f9fc5c72b61a57a82904ef8117cbac3ce6364a16e7455d48ad3718f288dfc568b3d92a486acd97c79b19d42f2b61b0fd893a8849210899aee57da1abedc6ac86b189547231be230b2b980f041e311cf5f544925032ffce4dca2e9ebc99c2b5c42d6263728b7e3fd8faa91bb77c806dda8cacd9251b70742152cb95a94f19f162e068b472d3010a7f5727e4fbf8bca080c67360775faf3aa4343249ddb7d3e296ca35b672de8d5b21d719be4343ef09a38a7f9968825f64643d7a652792776de555643e722e82ea2c11b84f244ff27d06169b330e68b057d80a8bb38223531318c4a4374060b181e4f1cf389b067d8deb9611e58bc2b3733a410e7e7ee0eda57ad29a04728183972bd5358a030f95d4311949989b68f718450e213dea925c06c72158f372f9d595c6b5305500855da040d51e2668c674f1d83dafc48163a32b204bae8b72a69299c1e8b71a934a01c00aca5543eeb569972bc43f1e393b35e68bb47a0536ed8e60e719594be3f2b0d65ebcf8d1b47d9bca1a97027d6497a945e05e17b272c9a9de5762aa3ee473810fce408553190db21da01b1d548a118b12254934236dc6ab80303f50515e59f188f887ac2f9874374db68d07e40eb14016f0eb4f0172bd92a79c27cbb19642271742678c6be508455db6c414561943fd3970f09181722620d9d992ccb1de004d37a964b7df709d95dfd3098eb9be2daea7baca094d727953032a8dd935b77759c014ef423f55322a9ec4c46a808a9a2c8fffaa00fc724cae716557371131198447cb9a17c744d8946dc766763087af038c501cfb4672b53ee9175558631d1303afefdadaded2292cb8b5c1eefd83cbe9d9f9bd2f3e7221b8507b35f0f2cbf969113a1d462c86be5f08053041c6cf97e79a3666bcfb1745a19f0a21bc9c0630f222d95beebd1a348862d19e496458e8990bfe4e23377200df741efc9b6b8c9770b4bd11941ade15f8da55c0802a1e66af60236d982f729f5926aed3755c1da87e710ad2b070d97c01e27689911eaaf961167e767f6072c49a7eab349c638718b7217ae22e864823e2b12157b9dd49342c81fab5c9227215700b1104b7764caec8b9b82b98051b7d7bc4cf2199ba0da7812859bdef66723ed119657449b5d8141528d995badd618d66c5087f9ae09ad11b9afba225be25ff4693536ba72156d28cffeaedd656eb65130ce00ec327855bc11f16ac25a21163132bd0800e566945304d7c565b66aa661ba96f5f8e94bb449d9b8497fa0f72e6376ad177d59386df8b41e66a617c1fa367752c52a3e0e560d1ef3a37efb3727fb4e28e97accf3fad6e812dd045c61fbb9e76f36a0309ddaebdc0996e3acf56aeda61a996e3c8cbd648d7e1db0a3bf043afbcd9a29c97f9e85bd94f1845cb724d75ebf333139c27a5fe03daa6d996fc3cc35e479f1ca459bdadba6b3fd32c725ef32729ad631a9113cf87a4d670508361fe1cab8f4c2d46aab57ae54ef7ed722a08f749c61d3c2454830ed8ada2cf30fe041b57debb1f556e6c5dfeb5e019076befef4fec2b26dea76ad5e799e8f152da96380ddedb990a625c8c8c9d8233722cfc7835d6126f6ec7dd77dce982c3c9b700d6b631152076e9e8400b7df39d3c6ec3fb8909700c86783365b37a358d6d6a67d5ab66ea106115c617708d6e2a72f77a8796091c34438cf01108eb88d6dd063dc33716f4afdd9b4da3aef2ae1772d5169388f927535019e28cea6d1393b9a9dbf1a74bd4f4e8e61ac16113958214d6d11a87a7e989ba925907d255370ec93b0254aab22bc4642475008f45e5e372eae02306eb12a87acaa1462f14cb30b3c5aeaaa726215e702bbe1825f964c17265b3a09a1e53de58cf100262138eac7c9e50dbb6608a480a5ec52070d6786e72305a6471034b28c4a3d481a436020b11612ff1acb84e075e09a58676134b8443448ad8b5e64e8115065b2a2e888450416b7acb445b3f13374d7d406712af5472b9279d3f66ed53924fc4d7d6bf575651b7a0084beccdafaaa2245cf2d469b5723a8167a471955f372de69c342225c6b799642472327c44a4e8f732ca71ca57086967d22ec814f416b091bec3ac9a7e6be66ff600831669f3a9542824df2f68144182642cbc7cf465609ea7473e82f928c5d940ca55be09595bc1e37077165f72f49fb2ef3bee59fb0b877e5b8885b180dc9b55325d2759d7396939e7d75e1b72561e713383fea5458c6b6217eef05894f9c70dcc4649341d62785a8be482627217faa2ab50d9659050a575f88a95362173054dd34cec16e4afd14deae8bb4c725748a85eab590223adccf5db4f3fb4fc82787d23bda1e2565f9ff1f9a9414f727f99f2bdd7da8c273f484fbff48cf4d1f3acda3ea7c60c11d32d120c67b09872152e420ee60fb6d4447963a7edc77fd2654a6b52a0269fb57688fcb926e7657251a6b992676b07fd3cf4290d98c99a7cc8c57460f704962c25ae3f88693d61725fc949c86b0be8d7dd219dbb803fa58f43c8945c1a553d2c9dbf45960d7aa0720f0bf9436a259aada7db26cea94ac8c930dab75af1e2f0c7a95b17dfeab5d7019c6e92a1f065c67e8578829a47bb852cdd342acda0c270cef972b1730de78e2ee8c9bf57226df324635e7728ff0be4736bc75cdc24361da87d8432c8ed565f4417dac9c370668eebd2d49d7dfb3529c84de10cb0fd67b4e0dc4f0d410baa46722f5f5716fa3b1b9f5bd70d3d2b969ae967af91f1705a61076373b814b52c797280cdeb991ea36e3a309b78c1f9c38af45278d55218769c6d57796e81ca58be72f35b86c575985945b9c5c361d46f37c405b4aee880b8abf18cf0fe2b805c9f725e71b124f7fbf0c517a556472b129b0c6dda2c5c3c4576fdc01159135173904d2446ea86cd68198dfb89b3116079f3d9af23921670eef8170fb4ff5afee19372ae196bcb0f3fb51571d58c3eda76b83658a8eba54f2df6d05f4f715ed6a7bd44aa684111a3e65f26e8177a9d1fad239e8cd0404de9a3e2c56e7317284b98f17217a84fae477d2d5ffd2cbe80491fe1eae5d5d18aafec5ddcdac4a8bd4f76ef72092dd3595463b146c851f3c57a1bd896bea1918f8905907f14bec7b8995771165dd556512fc68c41ff3f670c2fe9282a135719f3d937cf3094ff5a080c94317269d663298f8f81239aa6865588cf429d2bc50d8553f02675a8bfd5ac755d401f58271c951796df89dfef1826da455640fdf42668534259173253d34fcf81f41474e9a020e55d74a0ad183e65c35d89686e9e851a53281c990267ff28c234df29fb93d946f0393c27481956e8f6dfbdcf8cacd3bd4f1ad107ac59f4d9354e26729b93dbb46797b083b0d2676911f3c1b0b70bfbe3de18c1a9eeb6c0a3221ff9600b6c6637b71c909e0747f17b7490a28e441f20129fbb55cf5293e6c58a6ab872f4560ae29abcf6cea2392d7af3bfe7a6823dc25be18d7143748578706a0da372d3411d4950e375a5b4144daf4c9eb2016121850da2bf2bce897c9d8b283a11133b1ba2ec969a2280961ae40d34348491ebefe6a206cdba601d5c2613adbe867261bc57fa02cd8da069029ea6343b6a49fdf17791a864ee90d41a3b152f3eea26561856d8be3428820c85a7fa33e053d01d0bc21b693bff4a4135bff724c4a114f443d7d16415764787a511c8433207d9d12d3806f1d7843e70c70dbc721f76729162f883ee64690099f4ffced1733aaa067a16b9583e9c92332f1901badbeb72b852644d61a0d8faf79067d416c4658af39ad3ddd14c71421de1738271cbc1723360d9466958213e4b0112a58a1951098b63a872e61144df39bc6b753c67c1720b27bc2ee534ef47a89e42e0c23b2c55c9db1a3c8ad9d733c210655981da7b6b9401ef898cfb49cca6f1faa5a94fcec2fbb3f9dd3d75b666d5ea26672a010f43d5acb9d233952d48288f8d9d44f0c4f545dfe32decd643f8e84811c33e77454111f3d88cc43a1671944263cff7fc74488eea708932d5d6f36e0577f5840807726b256ddc8973b48a38af40eeeff68552d23ba3bb086d40124fbc1df7c809fc06dea627fbeaf475524123415af8fe9c1ddc38cb5080af9f61c7b25982db9ba37278f28c13f93ce1af3a9b88148682d2725cf8bb0a8212daef04b9ab6cecb49355496656752e3e0640a7a48b68ac7a3002578a18fbb622bfffb17697754d9f167168e250b0114df5273f162c8c2f487f9ba47de1e08d1fefcf5568448972d04d72d537bf5ffea1040637f43ea08b3b2479a9ec35cb077fd5cc1ab915f5d845132aaaf957dae793216b6da8312a8a829aa0911393d28dc45498dbf2befe6331e472bc39b962c796a0d951e94638f91b61ead29c41a9b81ec38dd34d8b39f2ac1972030dd880a0f44d000330488d140ac4de65aaabe282a8da6fc252cb1177f0a77298a16e76d54eafacdc89a0e4865fc505673ebbdfd27c8f34e87443f527306a521027b8c44a85c84e213acd9a00ed777be9329ef0c159edd7facb1db4fb724c5368e3d9ddd39904b5de7fac093dfb52df3c4fabf5b2b053a41a0a0cba736fa9721f30190feb53beed54eb20115422dff21c5d13174d1da89a0670648dae439372a8b749e2a6634b51b3bd9c0f5f72983603e7e02f7d7fb01979660f136dd4ee72b6e669cdda915d3366c81f1840dceed8796811754ff83e8ad88112269c8a3d72f9e00180546c1ad3ea43f843f9e05940b6f2d8f778a72067b8945ef60604a81945cd557fdc0f67d4210563f6691b68c2909c2e88d7cdbd0392827b9fcc4df672f48623dc7cef9426d3ffff5118e6bc6f83144456d82d5d809a22fc7f2be4004ac16c702fbe6598194b12649f8c3c0341ab7a5c6516d678361f3a9a67e0086930f460c8a21149e734bd6a85617d26e15f74d602e30b7f4b717ad4729a1c359740effcc5c1f3339453f2d5587fc20ffe88f50f91d14be961e1f1680cbb8c7edc726b56c491d12d957a38b797fbe97e482f93fe2ebdad65fcd5593266d2f6838d7263485eca2d202abdac5ddaee4ee4ecb03dc2aa2088e822bf0678d602e33ab272d1662ea3da4270fe2fd3de404fb48615b7d6dd75e36c61fc81142d3212646d316003deb01918cefa8d9c05783e1c4509d370cba35887b222afe7700bc893592057e592e080619c06d6c1b8e4f0bc60a03814dd289690d4240f72a6a4dd4d606274737456f6a7a7ad6ca5b8eeae8e92854ca41585af3d8a394582d5567d76eb7280176d0b564c2659c0ba008e2fd7b0969e021096e850c22fa4a2a63e564c512a495fa507687236d950ff33facf07c2022d65ec26b6f41c1042c175f193ff2332cc5cd9fc73cbb91e41bae459e844415399fbdafac2a0b83f831e0a76cf3846728888e02e4e3bc639c2725fedfb5a6d31a0a161daa57bb6eaa686b0fe897c3436502a2d1bbf190808fd5ae23873751f770e2fe993b7f9ded197e3c8d9aa501272d7be7356d6d2f810f66536cc6810df945f1fa5c2526834e770a63a003011e77289d62e8cfcc035fef33b80485b4c43043c9da622f407f72a3cb285c3d116390baca82a74d35f9930e76ea4066117b88a7ca6eb85203bc6b3dbff4ae04116e572348c347a6033528f1fa87b8e054d0007e92c0b868cb10f7a527c62ab338cd771c7511b8ef64e427daf37b3aa32eaac981ad1faefe1062d47e089f6e9906d127217bc0ca45fc74f256573c0680f963e6cd12f4ed8e5ffb456737158b5ba9a7a725f9d7befe86559d28c7789d62c7334ef6643ccc739e9f83cf891db99fcf1f672b62d90b47457ed48df69cfe1a4757c1600710406de2028a31dee42edcf18ea727dbbbee307f277ffcdfa5736abd3e1279545447054cfe073f4d9e6358d5a7272069f4f0b5ff3d8d3bb9d4f1fae0205e161472923e2de27b5e58b74169d2d8e72b8abb7574711053215e24d02a31d0a1e9ea30fd2d039b0927f023c9b30943072c81daf4a70bf50aac09807b8cd524c6e46954188d203de2956ea2b45f69f456412705016cdd8f95699d8db54abf2327521fd454f504d0329c81ddeb0b1c71a370361af3573504abb16ac862d80a9f795964327de2cc3fc83df9992185fe5213c74719e63046b3d36ed3237f9ff9152981a15cbab51a3c2d78f2f711e188eb17230cd9edd40fe47f3543578afe3bc83f336c472ab6f75d6079c34bb5ffee8a072b7dda1bf2834112f5390116e60dee18a2b37949a6248d1a5a38c37c16a36d2725abd706504ea49313842f34c39768b50ed72335512a858ad9a76fbb6cd437e724a0bc03bea262ce718260f5206858b9ceecad43b8a3d863c0a8587d0a64bee72c9c17535b13669306174a1ed829d1f416f67b984348860efa67f46edad5ae45f7e162d27c2c2774cff5be7ae407a07377e5df0226448cbbd14ad3d4a640fe07204dc6dd4a7f2bc94fe42b705505155d931048b7c5b124b8b61b30bd22764255a79ad79a7f48d91b8390675661397463b89799efe569d5bccd14d91157947090c123cd2e0ca51807e03fe78b7c9cd14abb7d032cebdca35158948ba805d06617237f85ceb0f8f398ec0a39b508669e2a386e6e35fe5910447f8869370b01811450fb8467275c1c175ffe69b4e5abe1cc7334667a2d6ab168739725f433364173bb069efe795b55abbe4b277232dbedb5e8400d3b336bf59377ea5fd7b4ccd03728a5ace4b731cbceeeb12be4367469fd96a2f6bfb36f7d541836edbdf37d02c724f4776dc4fa127df811a2a48d1f271ca5bd4aa7c2dd0463168d49dc1de399c723ae841e369f7095404675ab65a2c3caf3d1f1d30d24e73b6336da4f0ec08b9729dabcafd511c38e828440060d1869c44881169e95ded8b9c5e0270db1e642b720e8d7907fa7648f4193dc95f86f6da229b4031d1d7955b529c3abff47fcbe572237d4560c05ea126284bd95830593a6ee649d165046b10fa411cddbdce9e960375e84a34f099d8e19705b40e7144340afdb51dab548a40e828f80686dde333365167e78eb67f09b85b0aab36c10f28a27fd2f63fa581385adcfcc41ab63b9772baa849fd062dabd4e15e33d074795b1d73ce82a9a8044d25b7d228a7651a28657e675abb409d9c17b4cc762fbadbef15152a3f103185d86059692e460492cf72b22286e728af13a65458f71fb7e3881b887bb6721e95984d0152d9445e04704a6f8d1cb83b5f389f786f8865099ba96fdb1732d6b3cc8ca9cf3aafc00a06eb26815437d96be3f86a1b2ef604b7d5a4e9864afdb4f7150303f4ce7c7a5e8bed72974ca2401b0ba6634ce7fa0e85d9e738c95c2e9e21310ac9f64f3efb7e199672c0f9465c86212ee09a081e0bb03d4c9c09b75b7f738e649f23a824f9815d85727cbd0e419ac35205e8abd47848ebfdb5deeace352beb78dff530a33ed8f64b72908ce6b44dea74b882ea68525d4bf97328f0db6ccf215eb3fa8c2c0a89163d72fd2f271dfb0f4ec8368aac199751441db17f1cae961d0502dbab82d4c3721572865032475006ce91fb657a7e000810b23a5b2ce2db73d5033b6fdd8025e3df72023658ef44b933cdf78db586bbb8f3ae8175b2a6937acc441e6ff75005ba1e121f951bb32d960226e1e4a6c6bbbccdca3ae68408cfef27365a17e0d0e79e1f530106d84a71588b3df465003770e6d0b0be155fcbc016bfe1685e75ddd020db60d576188002f15a3babbed6f0430b6df046955aa0900c5954166c7b14100ec74019022bde620eb404bfb14f3fd9a073e378cfc842e7856c547e91f05b467b1b45d9e66a971e6d67d736d7883a1a2aa85136641b732c14b061fe174d5a3a62fe004faf6692d3b88b2dc72ab16b72522be0a5642dda1bc7b5ee38e788445cd1d849ba38a2c7ea5cab7b6cb2bd0e0f4ae2bfdc6c98edc5bc36098e7d1c11a5a45f5ed667480d56a8ff1fca1130de23a5d0c2727627467d07f98cc58915fb00c6982ef7823fd644a9f2f755d28162c8551192c2bcd899878f86160b64de5a7b7aa4729164740802bb014e0f4e4bc956af7465550104c2a0918ea5397b03abfa838e72c58209e296a2324b0731e3f2f8e7144b9689b8e123b6d2622c1eda550d9adc1ebee5ac2a915221c82875910b8ec39a31407292565a04965dcdd50aaabb75f6720a6057f49f960456dedc0ea2abdf499c6520b59d6586f669a5ae4c03fd73896626d521dc8c22da0abab0b42a0a828399baa4eb3471e37254740d242f55d9a572fc60f5c40e4e0aff61c6ddc3fbfe47cd811b657d0d698c5788d7f76780c0fb724150959a31f479c573533c7bc88921bc2e0ae7c66c8f34574828a6aec529744b2b9c1e7119c6210b1f82d5eac0ab08763af8dd60b9ff40bfbb33494dae94d21dc6720c5e20fdeb7e6f2ed1bcaa804bf9b5b92ab044768e462b2306e907f85116ebf1dbc584fd528bb2ff3bdc21a3156a582d1d2d73898aac1fbbde67adec0642b5d14860657a19992a9f11bbac90e3aeb2b0c88f68819282752ac356362aa36f72f5ec4bf80249dd9f5569463f39781f80575377762b70385d839bb486877872ed0bc06f00cdd2177608c8ffc54fb6c307f07780b5bae9fc953628a13afb1b04c84e441ea6134f7de4106e6ae8e872c382e8cc2bbfd7cca24262dfb5400cdc72b2b0a4236c580227943d02c0abea567046aff9a550049daf5f08aba76424ff72255b04e287f90522a4621d022a1890ea120586c17df7023a24d915f23280391c945533100bc8fd5c6a71283775e36cf90f2a1d4eeba8a5ff8fcf8d1cf7c4f912dec606b21ad6542f741038e48ae8457f45137b3350205a5949be47a2d5cb7e4303850150334d9eac6125c953ab56a062e35f662dda30429aa6abd0d361ff3613a85988793f156fbcedfed26059df3ac22b8c1d089d79dcb31e339962a31bd23eb030636888c742c55c92b6f80745bb247493e1273ead727b444946704912cc3ff2f8ee3d424babf487e2f7e95a0f9a5ac6b0a682dc68d49769de29679bb0186a938d0765a29216176e3f6507629d5791143ae8aae007d161e80ddcd2abb324720a002a2f4253d4023a8de8ddd4484fc2c88874912d4caae99071aaef1e5c071caba0097c848b21a020bfb08bbc24956db18dbfbd79b19033ab9df5daaa5709720d854cc97628a16254889360e919681571076987e2ee802b4863c72cdacb613a31681acf50e8b17fdbab95bab14c4e39adc3dd04491bb6f85dd8bd6714b85402c0b7e1f85d2478e6d239866378f052ae30094435fb97f0af6e7499874af1997200fd2cce375b2fbd38eecd5e8a8fd9f8c9399039457131e9742a2a62edf04d7268e4bc53d2740e8caae029cafa0b1109f0cbc5b1dec7ff71a1331cc94eeabb7242b4f8536eb2dea275b0e4c3248a0231dd9c34f0a909735c6d68145f6c887f543b21cb64092604c03b2bf9ec8f292d2cee22a32ffcce291762a219e2cf0d14724da089b092869bd6f696f2d2d3f6d99b8894ca631eb2987e294b2781359bb71aa028a5534ec00f4c7cf4ea9629e15d2c5d17b23a197c4c2c4389ce62eab28842fd4dfb7d78208273531a5c46c9dad60dcacc31f3ef8c94159614be50b550fc336e95a08a68635307375e7b29996aa2b46d767fbb0694f110ee231162e7919872aa958cac6303c2eb3c34b7318d61693be7a1a1a4ad5a6d11885b5202594df872fec95d8a65e7d7da3e90bf26fe36253baa9d09957de4fee9a74703702cdf3e013d87321c47b46f25dfad07634a209a227a174f67ae3153ef4008d49acd66050d48b4527aee34aa0d5dc77bc9857b5c97e62bfa4ed73e10ff7f97bea6eb3c2570bddf2e3b87e2872830ca4b3e64afee79ce015a3bfc7e14ae54e6732b848b5a4e1bcef6c0fe8221f64b4c3aeb089b70b257acf4b04a18de5b013030e271b399018eb31a0ae5fffe4e627b9897086fbd735f63e2fbdbeeb5fffb3ede17f1f5de71e11e7feacee982f3b626ef4a2d549bf439db71c95e40747d08c46f2765f52a6e837e67f8cf0c818761dcabdc1deec90b84b5f7c295efcb95da13ada7d46cd67226d35ccdadf3a06edebf8226eb7eef73b455a49ac05d3feb0f30cbfbe3dae272e63bba3bf7b1ae2f2c1ab418c57edb4695defa25b05e8922e53774a42b700e38bf571b22b29033aee8e42bbef6f6b4d3db1e45a72ac91815f7f0a91d8b6c1e724790ab719b1722db158ce0c3a2b11a899a9dd7c0c4d7478bb6837fada9ff6272e6b6ed8bb87a474a51da269ab2641b586c62f7bff1ddea2bd4fca1a414a34272f306a06d800cca79d2420b1fbbf8a6580335b6ef023143705d3826181dac1472aa1579318a784da574a853e529e4228b81cca5719d78b53091c02d533322e072182d6581607be1fc2e143edd1dd245351e49e9949cb6b32f90b8e93ee37f1f1e34da34113af253b9a3217704abf6e6258cc4bc1a70e7f2ded85bb6f678bfba0203a3be41d0aeb24103aa58f6a94b0b4f187904dfab3b299ab07ffa5283c265528cbab7d65780812b6398f4ecdcebf9a0fddae3a9cf09b722fd2fcac05e1c9a51c3611af2f58b4a907b5dc3f5f6bd7fc794cd7a85dbe17634e0a2f2d34451632b105a5b5b5bde5b4a34b03acbb77e0ef0aef68c69fedc447b9b1ea13b524c4172bd037cab3327092a34e44476a12196d75e7af1d966922c20728084e6a88b6d725d6f3f04027fedb4313aabbf65eb47ec8cab589144b0ee0f48f1d7b92a266872e1227206beafea1c49c9974e734250f87d0968e603b453111ec782d3f4609c722303e12cceb6d0398a8dda28baa25aa72101348d251a4664dcf5e17efd4b3372d52ff9c2689812beb8c8e54a849e3c15f75b557c2b542ed99cbeeb40934951426ff96342a1f8f08c229cb6b769b0efa13409a4797587b4f96d640c93eb837f72019b13484ec67ce5f254b3ac837f328df14825728abb0020d821f61e616681726a3609f33250fb17fdcf2eabfd2322d401f614545fa191667f79915277af657244f62ff2b4b9ff8329beae3fa2450ba86c7c1931555bb4e853216186fd63275ee63103a918c9fd2206ec77d5b4be088327f24d091f25bd41af82f402f43f265423b53b2b9795715eb64506e33471c1f2afb150324126c4e52abbf663609802721da1464f400c520f27665e9597a776228128dce732e5c69405f0f2bfdfc1197250251c041b6a1b5f1756da7d51aa0023b71fb5772c06909559c8c75cddc3881f5cee926b16543115d674a184fa61b3f313f7f2cfc787c3acbe5b7ed7cabb5f722057d890e9ad6d504ab39493a76e56ed5a699b77eb434030c1626d3df32e6272830afcc0c321fd3418d384bad4fd11042217fb7111082460dee7dc90d69a3a3cbe7cb30e037577e85a1b4c47b0ae43a91e789b0dbe079bb61053e59bd5f23925e5810b705ee0527529d46231b5d592c32506e8564df2da59e9db751281a41172f51b6ad8eea3295fc8d2b619080682631a1b2bad496d9ad5958b0ff99674877273b0c0ef7167c4d51cc0c2eb68c4c4653a7b423843f3dea8f7a4a55fa1a0f572f970ab45e5052aa83407dfd70e67aae4e86aea0a70aeb01cf1522dd47e2ec872bffce77de00fd258162c05775c81a748d16d3a9284e1b85dcc62b56b478f6a72f8a8b3ee895e464380d5b1cbbb6791f733ad454956e7ca9ff7cb6041b256d672c1278d4a93b0c88f912126718840db2b464ed05d9629ec5c36005ae3f7e2442c917477fcf18c2aeacc1727375ee9adf9df8e5aee973a412eeaffe4f3be484e061677f983971be766f28f31508d26043d428fb7087b5c79c88d680a2c86691c32040a3424e57ebec084772eb643ed3c4e28b8473e12a3e7460ab330614391b211bcf139e7fee04f6fecb68901a6cd1284061ca2ec1dd56fe1d08f3b31b6f9fd729cf5b243032f2f6baab449879c2448e3033e1045e90fa17320c051574b903872e675ac99d43ec16fb3cd93b3115737456910453a4a04647764309e2a9a39a2729310304ac3ae5341af781ab7d1dbffac1dcdf2168f51dbc3c3e4f25738897872cafb6b2ee3b0fab04a687d8de54b36470d758f867e7498bba4aaf6d51bddc072d7fc422612b01e1c81c01751286f26c3be0a68eddc4d8a50a6b4de2fa2d5c4381871dd00a29b64312680dc2c45b11cd917ed90adb7fb28b0c4f6140577f7ea5bd89c78ac29c8a93ef7769d008a7bec30ae3350e80b5f8420e04134c551a66d72e74a062e22ec620eacf619764b7dd3b24bca6d1d7c347316100d52bb4146b254afa89099a3303579086252eb7e7bb8d1cd4b16a85fd90d375e26a3b172f8ba468d4fefcc63b8f8770e1ea947847e8e50f918e1d598786ecb6b561283373fa572725f895d92311ab75f5307cffbb02919be76c6b67ac4e52e804a407312dd257283642644ffddb1d2e02ca34d0c467b192f005489ddb56f8b023629c8e483a172a7a393f7647564c1d375702e61db53e3e6ab32c228a4d936046b78fd3e702b2da856c358803712392f99a41f6aaf7f09800fb17c8519f34ad232211d7221863d8546e6a2cd26e7fbe1e3e51aa01c9cdde198b46b8d87ca4305c86451daa17472c26f1b44cdfe280ca0f10a0e300313287cc5ae33b9391da1783f7733c4ae741fdf6f60fcd5bf883c78097a03813cd6b9965205e4c4649181ec5604855d33e7595303578cd91a9bedb49dc1bfb6df52ce2632bc8eca277b44a71bf152e86606723680143c9c9b97f81aef74cc4d000dd36298ba54726977e8c3841379c026f54b85756b2c1ce18d7f7ca826dd718ee0ff3ec0ed9ceb98d83c6fd9fdd105122572d3e5a578ba0946ccf86ddea710f451727f522d0bce5e7646bcf8feabceb93b5b59eb2843937cc34a3ae670199f71b652f2a5cb4595b9e582733a62145841f5724bb449ef7cd409e9e7d3cfae297b17ab135d5dd5b01d6ce00238cbd3fee0ee6453fe0f55c8789b775d2b3ab6cdc166d1e5a36b258a39914931f38618b3c1cf5e9338282e87ed53fb101db35ecfbe09e8e4a07b681bda7f8166cf46a343e542196ced4cb69b0bcef26886a32711e1c33331ba318dae8beec643bd4c7976a42e723f503b98548ca2747cd2024af4f43089645276e4c72520eda0c2f830fd14557220164b9fc43c75e05e9b902c78f8d406210846a4c4ab1132c9f360f10deb99430b871c696082b67c30361f9fcd418df1ac34d700696e941fc5d4b9023be20d7217d5758fb18d5f07949b9fedb094e195c2e9650fdb9cc83bba5920c61a433272dafb39871e3585a68dbb45c18da25fd2b50f56b8f48d036f04cdbeb721d6417211b6fdfaf66657069096efae23091b7ee4498fd78051911a055d15f92a39f04eb8c1d16b8e5658775b0107213eee720e80e05de5a8703a2e9c5bf903da7ad2721c631459b17d1d6458e2d95fac242ed83c7fad1bb44c037ac969b738209e29384be0898ba67774bc46c30526af6a9c6b7ac905473de5e4ad2d756fcad0eaf872821b8f8b85ebb8c3172694450011f021757b9424f9723332ca18317ad490e038ce160ac6c84a02f961dd63e9b462104da0436ca6225e1bdf9dfa070819687d4058c35bf052c52129e53ebc9d2895e300a0ed328ab7d28b9e2571e26b07aab37287655187e2aedc329be8c55e5460e7d6c1b49f490dba6698d86115cac1b782684bdddf406d8adf81734ef4bd7c8764fd340be2c6a7f7fbbc0e3237e4b3ea95666fcdef076396622271bab1e1401bb76de60024b4c56c464abb8cd55b89c0ba12f8083903b995bdc7e5caf61a0366d6abcd95f56f3d159a9b329a031d7b65a772a9981a95eef6bcd41b7298b26feb308bfe4f9630cd160d9468dbb9dd47517572be9352c3501905a75220897a328ce4fd991c4263c797e2ca5cdad4e7506d970ee9d779802d0c7d7f6dca641520b7a2d61f84e0630b457b3e17b1560ed3eb5c727538133ddc8ccceadf61a6becc8633f2f2d5596f51aed2d436ee94fa50c95351d9477fe889064cc36590cddb5326cfab348cc96892e83bb0ade6cfd4c9026c722e47edc387331ac9fb7558a6eeffdd6c1d64766019df6837926041736a1a767238bb7aa080f408c509026b801e0fc6ddebcdfbe878d238ec3d163c45f181a34f0fbc6d676b8fc83a23060bab6fc956bc3f506f913f44c92109cc4756b0f4e6725fc8bcf263ac442b793ca0fe45fd7bc2c38fb14421b7450545cbf89e6553d272f5a5ff9b067871d2e137a5623d787086bcbf36502e65dc81f690f5aa5fc2a738d60bacab7b9f7454e7389feaf3e6dc21febba0e2acd35ece5e37c80e2749b61a8b33090828d4c2465dc5f05d538f61ac70f4c4aa00d2192ff93facc2c37f88725ec30375b0cae952fe776c5dce08b93960792a29f991093756cd802495a7fb7296cb7d0d72a218435749d71ecf722b901655e9a79d6e89e94f3d7756523b907205682e68d2583bc09f4fe003f8bf2a8a1fe6861c5ef184814393f988a714c0728347be644409f0c390d698ebcce064f9300a8672ad943f353311e6452b315672850de11f361318b41b8dcd741b7377fd9fdf3a55a66cf894313420c12ad47872e094c8f25d34c6dbe4eeca01b38183b2319e8c603b394f806b579b218408d072a60a90c862b2bc7a2faddd633277cd63a2e2eb0f6822c93a317410fa0fde1a72cc3b4f8e6b79ca9408bd810ca90859906d6e4fe3c050d748e8ac3352044cc472d10ee171cd7a26f005066408ccc4739ad7c5a47d886889d1e8ac2a6bea6308721de9f6037220c0dcff869f4eca75908a89a30977684f0562253f5277d75f810052c44a2dc2ea622d0cfa25858004c21cbf01aa352cd26c1731d9c51c6b065e48a981f08526c7f45a6f49b0fc13b8b5bf7688deb22e8e4818284ad75321791572dc215a18a29c06b8462cfe7bd5e68dcef67261c1be094414617f41a46785081ce9eda33df59565af72194fa0a4ffb35f081c0e69f6203653d109e4845155b76d622c8021756b32a55e9680aef3fec84a58a604ee085ea9ca7c8c7cc907202a7296a2d7e1bbac1c0ffa9f5f43c7ac169281a0805527e750938d78f57b0eb6cd424433498cc0428c310f462491cbaf9373418fd78128141bf2431f28134075d072f2da6aae56dc81ef7027b56be50f0eaac41d1241d319d551d219f9915668ec72eb5f378aacd677f8a12339a0f9a83b6aaf1094b3928f7ce026008c7d4e20a87276eb5cd87f1918b8d4a3e81a405f29f2e7f4244858953c254414237f50e73e724484a36c2ab6d1dfbdcb4d0fc6897c5e74f4371a4ee8d712f022d85777d4386285d60756bb260146fa575331c9b69028e3e3942bc557ace8dec985b953c4ef72b19fc445751348846cdb86e9b59cc3a9fe2c4dd07dd3d06918c1c78f246bcb7255521b90a189ca72e4fc4c4e557bad5f6b2a82763bca69f719fd38efa3c4e14903f7f7a31b15eb3e5c43b88807f30511a720f61f5db93f0e2ac0dc3b51c0c5723e601230d16b230c62b9e5719962604232191168c60cf9776270283118d98a727331ac2c96c40810cdd1ed08539964c0aaf3e3414c20ba2830b40755f585eb72f429e74b5d32e2919e8c6599f1d4d6010c501608f2fcd785a5642e1842fe630c0b06d8f5e83dde7af02f3e4727badfd132a25ccbb184d3656ee5bfae96632737d7d4e35151e1b81a4f52ac620a8dffa0c095b53184bbbcbe1ee4bdeef0c4763ee55ff22ef9cbba052ec2b37539a3fc97c984169c3ea9fee871e68c92b3bfb1720697e6225aa6edb9fe817402504ee22d05004bf8b62ff1bac88de997834373725f17636f1e8295c76e005caff5aff55906fb2283d6b582bac5cff55a17253e72bf9234a3ee8a927fd6d71e09b48c0c9881633b27ef9c2499f277cbeb69ed0772c532d20c94a905b782714bc0d2d62f084b6462e6e445eb5f6184698904920312abdf0ac9cffea1860ef2fdfdf7862b985dd20e0e5049c643f7ddddf6661fc2727cedafe1d51e1daef29ae085674c0b89cf00ec73fb2b7c37242f488344a53d72fe17f47b7a1f906249a2d55ae88c59ef246a3b0469c3201617efb9c7d5a81d726ce6b84f3b16835cd26ccde91ad6682fbe065b3d1e34a43bc38bd3bf8071b7729e525d698a7d5a93ba83cd267e20d065d4da1aec19b7557da2251fb842aea372f623833d0129786127307bf196eadb143efa87ad78d122ddb1402acac18e9e562304f175902ad1f8745c5a964ac0db06877c5320c2e8ded447e17898f086e45420ebbfbba8853525547a3763c04a05ac48e7be46de169474144ed65eb451f67247cd0aedd357ae019138951ef10a789fe3621eb8d07eaeb3412daf743d3e80728c8dd323c6ba5a2706700d1dafa54dc93c03d5bf637eeb37afdd02a10b959972dec371be1f1820cee3373aacaaad51b6af69aca5964c615ec0bd75944907130fa34c1d86f766d42a8462949a793c8042a72b7960c02c6fc2eb9e0691fe8ff9281a4f0eaf7adf64ee073fbd8b0be78bc1a936b910403acf31f23e9839c1402d72e585bf5ee2dfdf3808ac4832437241040fd4779e0aff31a58d294ccd0023a52810d1473883819b9848fd28f56b4870540196be1de6bb05d6fa857d7110d06e7279caf9e9903ca2da2b848ab3f60758b9dffa764e9d3423e9b03b3fb76833127245fb6565e428679f79db1e33cc0c1d559378de8a3b323c4beeab74e6fc83aa229ca854701d6fd9a6b0aa3451cdcd397b8f692ffc76c8ab1a6fa9e1036d67740a3e17a1d8299a4c100e56a5a2a788c6b3bd01b56d8013a458303a98901e20ac725378b2171f05dc3d32d8768a8a044ecce36f3ad0a71ba1ff9f4b9b9f1ad0bd23b4b32521ef780891693705a147f772efafcb3ec9665a889e750d565585af7572c20e29b41ca6c1cbc3f402438f5f598f9398bcf997a9917eecd3d2b9244c71415f67f04b8a33a2565d27762d7259c61c97b152475dc15e0144c65d926ab99d729388ec72c9747e87b0a52e43c71d471bfb7615a6d60e24c03fae0338f98bf66a3cea833617634b298e5f11992f65e335abc2a093bf945919a24b2703a71566725761d1562242e166c1f7d7975b2a834b8e195c1fc0c6df819d1c3fb7a8ff2b722dddcb7c4936c8b2678a451ea0a10eebaa500e967f76f148594d5336afe9ed72f6ac425e6b44f13cd715c19cdb0e92afca3ff3861c3b17f9f2967e4f06507d725c10c71665d38884f687de8de6a0d121cc064a54128c3f20923e77aa1f77e372e30acde1e6d94d08dc36bc23701c7f211665a02ae2172728656a63eb1236696e9bb11402b2a0021e09ee8c7899e4a5236e332e15814bcdf4331410a8ef046d5d6b9cd89c273a8d3c0f01269b30642696a2a921f63cd309b3640d112baf9fe03d8d4c98371befb6a8100268fedc59c916224a3db68f259163a737c6036a63f37287d89b8ae924c0022082f137c8da05c20e8d8b8837bf5f3f0d032b8e3e800772d2e186ac33fb13a345390b0351c0cbabb30e098377ca48cc8ac508440f7e76723f3d36a47c376317b4fe0faff5024581880a62da6861ca639bb740ce79af5f499a1b1f8a278ce4a304e1f6fe4ef392ca44210059942ea99907e969783ae496725843b4c07ccb10cb408442a3044ae2df756bc127337754393b914bdf61eddc7222ab08c9e4f8f2a670e4f71ba0f3c8a2ddc5eaabf104c91355855523bfe48d72334ed72dddedd4882e9e4251bf296b968185dfccc8e7577d75a80d9b7415a324c2718570ca19d0c9b14c0c5f602f4fd129bffb3289503832e8e8bf164b431772f2e0822e46550c8a28a76f37f029cab72f024750c1c96540dc1293a0132dce422edf6f2208be272c3499c4180157dea7ad63b5ed362b4c14b87c86292b18065b7a87a28bc0404c0befa2848340250edeb5fb4efc71234c7de12c6ba192e78335b760240ddb8d20de2fb65d26e44b3d80d38fbab4c9f8179b2e57044c756abf72a185bae2e7794c96aef9041afcb701b7e601bd70a30a3906b9524aaee652bb680da27cd959bf615789e68e70df2e78720bd37e2447d65bf69dbe2e62a3bafb728b39c002b5ab40b0502bd5a9f171e5d14e714ae44142187bca1a6ac9d511f234bc2b6dbc90b3792a14d71e319c88e24d776d10475e0e9d60c21c22e8e1e2ef5009762263037e44f0f8aa3b68312cc4284c0182aa32b2c370a298f6853d89d472f9be3ac16878c45eff6f37b953106f8e68eaef935515e41b8177f9f00dd27406e07d660905e9f8dbd4d17203a8d3ce50262380807202ae9105b2c8b3c68c7f296224c8649332107b74618188ff9518d41a3847c99337c48f02a9e314137c0929f3dc02cfd67b7a39655e9c0ce1e93948ef3dd1764b49e4d371dd0126cd81c8505b86be76432fb59a4e963504d220524dd2da93eb710217530ae4669ed7d3457244ece087ed9b4f27d94304d51a50a02eb331de30f5f3cf1988e9be51f990895fd9cc0daf8ad6c391604adb9aa5172e79d7b8e5b6d188f456028dd48db89b8372f29174d198b29028cde908e9d566570e4078c97c81fc28130800c93e62b46e725a80a9cba48d392b2485acf00ff74eb1cc1da070c1f3e860e5c66068269cbe726f36a56f35456950c13414dc7d7b6cbaa38bb597d84d50fb4422c1bf6b90243444c7b561e37fcd7fea65389a8f16d15c2c4d1892735cdda99af83af48eb9e82e1e7d200bf7f31f65f696514db8fe8faadbc4b45e783a00d40558ab336bd15505e29313d3eae139d9e02f01aaa1a3cf2bc5085f2a286c8062ab25a68163baa472bc13d8945f0df2b68029594f84503e6174dcc0944a4286ca0389514b1ef35d2c4e57013a597b67104b4c78138da32f0bddff3b55f3b4f575b1bffb0e83ec21729bf490fbe0d8230900b6ef1ff62520800aaac38c208c196e16ab7dde75e2a972467a6a48f4e8c050ca42f0e63f62e43cc9bccdcd9c4259ec9e0940a9bbba3e34a7719382d62526cbf6d3b33445bb3cb2ff398ccc08e6acfc37a1169b94e2876ee113c58167f649446c1dcba9854a387f128386c88502434f8f37a73fc9471206f5cbabc0d9d6785e7f8fd32a333ee4df29f1889d54a7228cc8af541b8441fd72dba210ea1bfc80015e8e495290a7f73eb861c897f42d171d67694ca7bfdf7c7243da7b8682c3173b3ecf576cea75d0398d4134ee36c12189f41d081f4eabd62caba543766ab5a88fdaab6703aa91588b53357175469bcc893df251cb54211172617ab3431cd7ee9ba1e42bd66036f7dc5389f6467f5d5121589960f7196435725af105fb7889630ba2b49e05276ec29a28e91595fd32a2ee32ae2ad758f903720f5b875170a37a6385d121cd89c49c05057ae687bd2bd772ee380e8252676f72ac2b82ab5c649abd8de72513d4bce1b547a63ed4e2273c3156a89ce32a6ba9728753bfcf8f8c8c95e03b6ed4ff665ee6b9f9283b880ecd981322db381bd0de72c5f2ea9ad7bb3555d987159c0e4ed585272d6e3b8e2650bf808d70f222399b720109a5bfc9f0c86074596ad54ea57bf0cf16dc2fdbd381e233e28066b3a13b7292d486e29fcfb9c5ffc69ba1b77e33441dce51c3692f90c9cc1978fb4f452440cdb325c6ab72aff95683d419af45c1e6993cbc641bf1664ba60600c05c1cc66147e71aef11be7965736a5c22578cc0953925ddd4273fabd1284206d1637569712d56bc9f6c3adbb13f9c907be2627df3d3ec23ea9681990a04bd3322c3205c2b82ac2d7544e5af5859822b5cea35c363def69e88ed0e47055138935a67295b729d0f35e1c7c6910db7991e65f86eb9ab24990871d7b29c0e4b47d1c6e20abc2b108cb2fc8d3b4fd53b0678ab34cbd0d1b589583e2b49eda09508ed6f2297ec7236f2ccb06d78266eac65d8231081a38e90c3f047b655208dd4bd551c26af04727be04267b17ea848e3d15ba41e522d5a0c0414f5963e0e90178454f5c6ca3e72d7e99f8312d9a9d94fcb0d9bfc119ae7f1fc0b161f050a8e083a3b1d9cefb972ed35f8ec22e35ef7c24f59e2e6f234c087bc6491a3d5e65a7fca7b2ce5758e728bf791659cf5afdea3bea44216f525f87cf0b6becfbf82afd275aed828d07f3987a5733163ef9c4c37eccfd2971f4b69fe828393e9e6669826e69a71b7a01272394ae0eb2def2aca42b89e196e62c18f228fb51d6851cc513b00edac59195672c724fb01b72493778978d336833f0c333241ee0baf36eb87555f1a2a83591d5dcd6e0adad12bbd6750203f4e75c3b408bb5979ed0f68fb47579a917f88c141727ce6652939d00705941d8ff8c864b6bc5c9607250a0b0bb4847928a9cae7430128164175b93816b1b1fc4ac12e4598db4b96f2b167e35a5a0452885e02ec2c721b8e1725b03ef3b6132a48b6c1f2c4bbade3dbe90026ebadcc2a8819f38a961dcbeedf9ee4f8a8a0a40a04a79cae628beab25f44b911c7b473ca5fb16505d772caced0c9dce686091daa569985c2f90bddb74664be5e0d5c03522f03d70de532eaa5679e3c2bb15b70364a16cfe9e5822ef42035837d2aa83f93988324c6c47268315b9e23d96fe8bea8e01aa603a4664b81158159dd1dcd2b0601e906797772b1b7913e333d2fd42723416177adc6762f48e6dd33023698da8736f0dcc31872338093ab1244deccaec7b67d40e93f231f48b28d26326a5d9a5ac8c397f18972a50bce5ff8192298bffcfa3ba958111a4e9210da98bee95b3237cab80d07917203e61569c7238da5f2c040dd444ca78fa92bf86320c6e218959835250c89ed723746f2f7f682ecbbbd937deb84f153a22816455b7fb404c7fb4facbc149752389a94815bc1e53691003f3a6bdc0f7aae22ce70ee9f9ebdde4ba72dbf6745b8726547f9f526b30b8631492c90fb2272f786726ac1500284398a75825fc32ab972b467b70285d29d77398e713973062e06975f10026bfb9f747e5601afcd29f172e88d0601b691b1ba1132b98fa4628e5276d9fd45ccc5bbbb4a379d62ac64717271d1f12f5a7f4086b6d68c2f180ac20cd005b9a683039fa1ba3555cfb194af726fb5922ea8005294c72bcad588bd37b16eacf7b5a4c4398ca444f198d9c7ed57b1137c9d18939639fcc0671517d167d71dea7733eba2569a1143ec670dd139197b41cfb13a4939629741d0f860917d0e56313031e08939f1a511faa59ae2805d15212b6d8cf2fb518397f97859c4ba10715ab0364f2af73737f5e4dc257d74651ac29c1f265e908ce9f1793a42985dbd8520e22c1cad97b382bc62f416ed0f727db2a44e50407caee8aa5dd73fd1d3be90e85fdf6b3973d32ba74cc1d340c04d369c46f475bbc8e7fe41c5c8143fa36356d45acebd19970624ab4c97b0e5d8728f6eba7c4d3539c0412ae836e1aec6e1c7208c1160097bb65b3374040faa19118ae707f438359e7ee32f0dc8d1d3254d337a82e51d4f3740e3950713e04fb022c331a1e067105bd75fbac41bfe3522566dddc85a671fb2803736ac74d9e6e027efdd1a29af1a81bac7356dc2eb4ddf972881f517f6c72d9fb62447d04a20ca7279a75cdfdb6864a75cf18bc5cb4ba289147b037cbd94447d1e231baa972e79728bb8e0d2786a8b0b6c32f335fe80fd61fe663affa8dbaed7e0894a5a38da876f1c8dcb2c790138a08918d54bafd9dfff10cea9fbfc77cd3e6d647dbe67c24e51a9298def63c5cbb3114e509a7a42fc7c0963c52999dc187ed8830996b171c3720d4e062d96b8e08fc1077905e8c6a91cd090649c345a725d83adc6c1069f1272186f40d1ebfdb4a701a0ba3179052bf9bc42dc26aa994feff14a1415cb52c55c10b37821f19ab3868989d3b4ca3860e066cdb47f12b4f788f33f81f848a78a72db67a4d7c92eea67857537e07819a57ff5bb0c0883e3fa048a8369aeb23d62728c20f0de6285781a1cc1559334240d085fe89653f3b65a9694b9cf1fb4f5052ee2b137ebcb9d708256d11158f52e4c4ec527faf0941b9523e7f9c274c1097556a68e928b8dc7aa322c88426f03942f3bbef6c8d8633d59582dfaaa82b6dcc972b6ae73c0b0304783f225e67e7bafcf1681dd52403fe089a4ef73fade556d1d72892d0cfc7985d9398aaa227e135f4e7413438225196c93897d7717e0dd179f72e5b48a9d24a174aa2d88aa4e934b4272b45070e37009bd2057ab9e5a208793728228e1f52e58eecb172543f9155a1b9e44abd8393f2a392c6465773d0bef6e72b868c078b5deee700fa41b7f614569b22eff951648182767c79db90648a812166aaaaa5938def59a8701ff07a22ddd5a702cce1107a61a72950fa7998e75933e9f070e4ec04167dbf12b09ef7a534965397f7b6561b26e24569fcdcdd4d442723d99e69a856384a3146969d05d00c2c019bcc3489316a6a40aa2893ef39adb61eecf6153eccb4285f67570a49e59458e0a41797bb9622cd5b619b202079d400ad9067997b0bb7bb14ee89f692917aa208372a5ccf0401c97f5de41c3b86a6772f3b9acfa997bf736af56639d84c72ef9fb7da0eeec524b3685b5ce1f768da072a882f15eeadc7d82f17288cc149943bc0e8e268ca908b67937ebd85b0ba14702c1481b2730c2c7ae8d94ab46d4a90a3d8fa98cacf43940445dac74ae9db62c638d35195594dd8f3885cd839ece806bae470c0a67e1816a5f777452af206c0b72573cb69cacc0d460ec45e171f7c84510f48beade13ae0c106c4034fc3f7d5f40332994da98f70c593c2fbfc0e19552b697dcee1049d77c4c301dd741cf704c72508dd1b677eb70bd4096aad4e49357dba734fc03b36a3562fdb7d3c7fb14af72c73d47ffd10183fa2494dc51df395f1ba0461f2952295e666368647b32592472c422a42c45a5bb4b0d90385a33d19e7adf57a97f27f692c71b465aee37a5fc39bdfeac5d819c719928f13f4b1d5c688a056aa843862ddad827e5c024adddb827ba6e304fde98e41cd143737e179e7a0773eae5e6543feeab6827143bf740867256d1d714894852f3ba8c17ab464b2045e6c02b0eee0e84ad7f11ac41d48b4b72e1de94b34546484ea1a09b08c4e1db863248c3a0c0c39b21796714d97a50c75d8c09d1568ceff0f2882ba94f5f775ac46e7debaebf3f83ba1d74f7295dd3c2410ebf9d87eccf22d0f62e22dd0d022eddb420d4a84e5b6f2986869579bd34307215ac1b0694e356d876224addff046e407d79f1121161bc66048d1134ab94b100fe6687fe1d56fd6078f4630db5e96867c4dd115dea62acdc726c665c939c1265339fab65f563f46d2637a564e1501991889a01db8a8404aa193241d5322aa239871a394518370cf7fd1dba92f7222a3de49eeaca9ec39d100d859842a923f2624b3304b43d1ab7278afffe8ea6ea350ea8eba16291c3e3fe1bd88b630c3a461127f92137f3a671fdcf89128b936b3591fd184b5f350a68122c40cc1c452fdd2afb90a0b091be41da0453b8839ce35e0b5b6ac5e931707796aadded4d47442d233cbfb061f19ef84506b9c1948cdc49cc7984cdea233a65fb71d211ee0188fb728bc15a0d07a81509c0fe290121c60a7bdda08c93993b99d4f0c6e55eb461ed36c2a17a3e01bab7d78da9249883584ceb00d5fa45abb9e0751a624bb0e06efb72c2eb60f4ac507687a7086cebb7b7171e0a25e631825826efe1478bd08cc0c536b1ea4abe1f6cd7d64c787819a1b19a4a0b6a11eb1d89b8eb6e4e41131b98b16da452be601370c4136fff2d9880b05c47da1c362c20b32fcb5a9e0e11378b3a6a272ba247f400fa69b590b5e47f6ec4eea516986871fdd782a848481f08af3572e241535a80b1eda2d60634664d1132a2dcf336f4f263b2608114cc9f99bc0e72f21c6ad4ec786e2ea4468d82648705fc3ee433db03d39e95bfbd483131274014a035b149f114768a3418e02ab7df331e48d70ec0d9788d19d005eae207a29a723d4b39596a3a9b357e4de4f5861178d0e8ead9ae13006538a890f7d7c9079472bc9f4045111f186d3856aad6a4d864a2268621d2b9c0f768f98bc703959c9772cd29598e5620f1f2305ba7dce30e136a72b06178334baf2057f46ffa8d373b7240d2855345c025224cb696893c6084a006220964a7bd1aa1ae78907f77190f721e5085625f7a766c098b1e0cacff0233c025bbe850da33ce3817a4852efccf724e8cf84b40fa7c8d02ca25e147e9568260b61bb09dc2d55324e1230c8737744a4de75ce87c94339f7e82da0cd66378ff0091358faff973b7f07a496235afe75ae52bc061213a108657f1f86a2584b7e5e31ed0b4234772c9712dee5e6c279f72ef65527681caeede7908176c657c0e76df05558713513960f52c4c07a9578e72e25b2622f2f70e72f9ff8280db89d878cdc714949559ab845ee94a69a8e12472d1c77b83526313b96621fb8c6d77578cb9c15346e2825a4dfd1ef5c38460ea7266a50438ba27ce61a585a08d7f15b7230ca4325970fa598543a00ee1977ba372b104096999facc60ff7a55e8f277348a8980f0b7c9ef83d6133ee51f1033b05b769f74e9632830e601e5c6690080f3a4d32649d602f0806da696fa60a28cf37202bdaa3582f5565152c1a6e4e1b097b766133a43d8acce40439c1560a6738d21d823e98f14e76f59f8cb8d609e2eb73c252987076f54e5efc00d5eca67757572bf689043b059ebd5b0920b38d3282d0793c2f26b0ede173fb7ed1595f1113b1750bd16fabf1f2b4b31fdcf55cb5bb8ab8a7606ae0f5aa24bd4485b62992763721c5187f92c025854759577da5bbdc19d0a63a02aa0d06840196504f430610b3ac9abe0aa0e6a3d0b811fc67e1dd2d6cd6dcf6eb0db9922c8be3256210aff3b724eb9a371f98592d4decfa0ca64e1dfa27045235cde67b9d49d6f85342f7e24082fa43037feac74cd12158240a45a794474dafb72aed9b2181b9813212d94ea72fe89efe2121ca35305c59a81cffa07478d2fc3a0c4f4645315ddfa624e626c72ff1f9f9c71a9101600d931572f21a5ab48d437f519547ed97135a640c1e01b722f1dbb7a4eee853757aae0ddeb043fe2ae3bbec6bf3cdaac59e8909457a9f372dd14cea09eb0f74cfe4f74a30fc7cabe092fc32e4c97f4d6a551db7369a78c7228b7e2e9c97e4def54783c4a4b2a8e1cb6003b8be03d149d521628873ed47872e33c45fafeb461f532fba1479d8e5bfe01fc61e6f5a57346a3371b0591e5ab6884c5ebb91ee68b434f9f87a256bb21176023b53d66b5bebc4f99e6b66023be72cb8e1a11f02f88dfdaef29874189b8fa93014b4251c17f5b3e0fddf822f62172a0402176871178afa1fd403d6324930be445ff0a080a40f03f89386820024f0e6c44a7f5d7b94da87672ac275318cf4f672977bc11ab5c8f777d9e4387a4ca398fcf566ff31f769ee8202d63374c53baf5bf98ac758d594c667c48bf58a057720f3accc5dd9bd512ea24c78b98f53b3a4bb0a4a873739c6b0877ce82a5891d4664532fb9636fbffb8d3eb4947e0b4af32db86d5afa851192251ef96c6d9f4c3e11176c9e270e700c2214dc2897cea9af89440ce4d5a65ca15787a7acd7a64b724eb015b2fb2dbe94d5551f84f3b18604e3d48667598dcb4933e7984b3300382cb51e1dc593988a98b8ed30b7784930c6d7a8833bdd0a8cdaf9ac2a0677553a52b070298a494f6cfae684b82334259b13fbead0856ae4cfdfcfe40b5579b29d72771de0507738a4c20c45738475045ff6e94e99ac917cf01d95bab7b35db60b245f92ca8581dc12a33b916ba434e6fea7c3123961ebb0f584e14de87ba4cfc9723c335b2fd13e7a4d01dd1be0caaa18ad8f87de1098b5fe2bb1aef794a7feb972618d00bd1bf0f5fd6524ae75267b54e2ede302b00adf7c1ba23295efcfab6172884e596908214425fda57838be88041a4d84c18512622e8c83cd367d92738572a110c331a53c2e82813ac70b252d31de1af3ed4046107478a21cc7dfb79d6a72a0d79737ffc50303287dbc0ecb381ad50ffbf7a04e88470a90e047860db63372c667d675b21a4aa356f77ba5fcc095e8fa96ee2a23a83718bce2f65a4491e74494a91c10a575c777f0883d1325a8507236d458b85707afb4631fe608ccb55d3f8704a7fcc22e9c01d6d1cdd44bf13eb6cd0f17fc00ca2595c40893d2db3763578af65b89c66c18d63d0dfe9e519b909d532a8c27507dbe90e678f07a1018fe72b69054334f5574525eeb67c431ff54bfc1116f30b4fc978a8120581114152572f87aa7c182e80df41d3b9a1d835b8b1bf23c3e6a35ba712d1946e03c93b5fd72c3b16c75d0ff35bf33fe72bbf6bef04f199a572e7889939f698900840b1d6c7208d281146584e106b5e29051d7838793a26b3823237e797dcdb3ccf9f5202d72a68952b031164acf934e36f614c339dbfab88e8e1bf5d99c710ffa2fd11b8a2f76f4b61024327801418299ef6d9b3c68fc07f4ec02c939a78868c9927b487e727c87f2d322cf193883b5f7f119d0a1d4818916561ffc696589d7e3aecc0ae54379be38fd22ad8dbd22752b6f23f53bc7010f72b6f7c8986002c15ff39194197228418f66c0f8a6cf0708c59253f58e47cc86c2490114d5d6d60f5c93864257729907507e86f05510b221a2b3bf8b7b64b6ac8533bfb05507d37f4e7255d25302463b5aca2ffa7b87c1961295fa7214de8ea0ba243ee7b152ff626b5a8f55c72729d400d369ac1802a263210274f5d9fb1236c02a0759908ed18679d55390e1404dc53c6ea7bcfddf852c120c94176203778d11649fd525824697318ef2159c726cb2eca2ec0e42a0c870a0f1c172af2094f5541120d0c3af6879f8cef69ad4728d1aaae2b7b29d752829ac7eb37d6fa762dbb63f0ddc5575d75ff304d0dab07286b6f048f3fb4198dbd03d0047b6979697dac143bf630ff008c1b1651a95c50e9bd16acfedf4b70bd07923bb25b606b30f26c64e1db7f41cb1d0e5242d23d272123f506ab6e4d1568a8c43f4769ca3782e6a57ff1b689af4651c95873667321e2dcc4bc6129e4c4fca44fe9d6f3ee7bf9ebf329e1002aaeb4bfbf1d7d0a1a172fe22c726b9421ee923cfd268c2afb89b6a98e476d97d44eac3893aa8db1740727b9888d070dc66288353308b6cc6216e0ac90c0309ef3c6f857edd3845e0ba726d61cb58af151a7d445ee4f9f4166a1d5987340d3dac1e905ab36d70b7175072618e5a77dfa7f9b3a6dc16519cfe26cde6a7badeaf735a63a04dca50bab0461f2069d3b7033df9fb2dc160f5c02953e7460d30abf7e34ff6d21aee116eff2b55d588545638239972d3b20f09e1b317fdae141821e1471304e740cc014f69924b3eacd2d5e79d4e3e24e24960b7bafc8c968f116172e2550370392c288fc7d369f802d503d91d95a69f5615ad6134264b0218545bb2be5cc97cf86e68b7592a72a40564e4188cfd2bf996bf589916a1f2f8e314a06162de476a6bb5afcc93942510266581e7729ca68a0ae5e5b20c78e7be8fb481e2571673f30ae09e3f099372f833ef95c2cfeda8c69af8d34d237ebb43a6eac6360e598392796ef421eb326436b55f0ddcfecf5d8f281bbd4854be1f3109c8c06552d669b8d2befcae7178381a21774cb8f186b184e25dde3b76f6e166329a20f3270dd85de0dec1adfadd7218d8b3844583584a671beca672205c2bfdb7bf941e725522d3df5e726db6cb51feeacb2ff820fe491fd5dfda440c1d102e15494c72f702d39512750d1cbaf372a241bde69f81f10decfc66284d0d159b9c43cdc6eb45cf590ac44d29cae601532e11a869217d56170d4e194c94605a0d1359069c115c1df5d87e1da314149f72c45828f8ac0b4b693545957f0e673c2efcc1586600853a2b802f285ef22b807232660b8c692876e26f4e9ca3f4a8085c5de5df02f7ee6448d67f05e5d66d3971610990cd6fe99b265157fe9d28629cde9a01878aae2d11d835ed5afcdac3ba0c4816ad402308daf40d9b19baa0842e072be8ee8b1297213b157365408645a372bc9c1f4bfd84255dd06969eabe9d1e3333271a941446fb73e104f8942ee04f649ffc4c8ecc2e6a7912184f2fb0705c47d1143ab9fcf0ee9b605de4625c954a720a2c77efc385ef3035fca06ace482bcdb1b75d6d017f1c1c4e8a935a58d433041bd7f8f269d3f297efadac9639a72d92f7376e8e276f93fad2f7da99fa959b67c0182ed28318f3a002f26eb16c803416683cbf944aa78a202bd5f9e5c339dd7290637835ac8deb3acae89321b55bb48367322aef5c3daff98f695006b278267281dd05eaeb1c2096d1f89e3fa9a6fa7868bba9b56db129e1adce59c5af5b5f0e99712882975ef0ff9ee2806eb48bbb1bba1cef5bd13ec87d431ac8c3986e3e72381b1d14d4bb148a348e4df2e6e13cfd6827d7bac9153621190717250ba03917637392657a3496902b2f2502551c63e286b19525ac03344d8eb0e4c11878f8720a53b526ddcf3ebeac7b8c8582fa3a45369ced5409a1ff2e5955522e0b2b75723aece578e2cb0820f3dabe6ed6a076af193d2bccb76168792cb20553bc505b72185a9103efcc5eb2b4fd748eb14d0acc087210e6ad5ff09bce526f6bf305a430c11037438de3c0af56d2950abddc56b6b3db64b5659543cb22697aaceed933725010cc56a5133785ddb2452803ea7338fb2036a4c96c172bf92322a0c3f42a7230be5cf299c3a4b021fc8abc20b8418957a90c4c45b16664706254db895c3c7262306c69f628ede81c6639a3b7cdf4be712a59f7e05713cf30a1b22e187e8e72bd906180b5c780b9c8b2a4580e5a4fbf4a17205680fcd95c61395f3b1d97d772ffd2b8450f7c8722dd790d990f091629c59f5427f958dbc44e0aaa9ef3e72f724e1a429d9c4ea3471f1fd10b2e2b1e565f2a947a6099cbe7ae0c0f6472f2c47259c58d03765c207f55ad3b6b7939399008d250a94a90c9082a8483e04e92607256c0a8861888f8906c9f5f5d0ae4cdc7d02666f26044b24ea16e29c97b85637238874223aa14fe7b1b9fb674e40aaa9d6e7ad5cfe5e373052e370b677d91457282920dac6756d2cd1728cd369591436ba5ffa83ac3b7c0bcfe18394c360cd672fae675a768ded6d12140d9fcc449c50ff26e57c933b7c7ac95372b007e7c663eb7c4fe3e5d7dd9e11137f9e30178109faee7dae916cbac8c57bd53a7efcbb47298401995aa29528f6f6764cdfff8eb6877b292a0feeeaf7f65f80a87eb882d7232f6a0b9eb682debf702a86fde274932a9e308cebd792cc599efeb4dba8f1a090794d4cee3272dab5a0cbee55409c3e895f6a5dbcc6cbdba4065d88784482753c48680c49e36c461fdd9f1f10aa52f7706f946d5e59da53c8886477381ea8223941de8b9538c9a184ab1053e137dc3187c0c73f19fa2a93a4a1cfcecc95e15720361866c2991748b58be795e1ea2fe6f88f4d6c2131c9ecdb81c3e30ac5a48722baf3d88dbbccc93795a56ec0fed0e98c3e730ff93e409c7b84a58ee40010c35503d86ac26f83f787d952604cdaff7d2e28ac1d3b0fb38da501e72486bf8a7720775fb148f3821765642dec647addad13a83249d7d7f01705d6e03640a4eac4d7952c72b7f782e5f13096cff12632480f0c0cdaf2e6a92b418d2c7a812aa2772e9c9be013ab2815e690e02b0a01f73a9b09e9b90cf2950cb453245d5b6f915720204fa6bf21732dd89d311428ff69c2ee7adbf86095f5f72fed0ff8a6c736c729e6de41ad63d7d6859ce693414f612c69472d848275ad799b47118fb6720247224231f4c28b232b4949a2c04713897c41ead96a0090f5e4f9362d10eecea0672248090bcfb7ce86e3740a0b6fdf125124d3c6c527715a71d1ad16d0f5484cd7265969555b19049393ff3c4c049e9c57903207f00887f0dedde28e9c1ad683e25d02f55498fd01d7067359449cef39f99ccca94f03b8365a8dd4c3bdaf010be71f59e283e93c8faf702237f2d3fbdd6cac23db74a99aa5a94efd1fe10ccd10d7231e55b4c43d1dd80171e8fda04df6074bd99641ef1351b3f985c1aeb5c5e8872cddad955fc45a7ff3a0e9cf5a8739b43e135fb93acd690f42a5a5e4d9a11fa699c3993a362a1de9c5b494f854a2d89edbed6f1171956910e10046edeb5fb6972633a5fa58a0a727ffc7369bdf1dd4b179012646993a1b5ce3e2a97a0cbe8891cf7a0c7c4e7765f96f2c426725ddbbd3df1da6ef7ede9946fdf4aa9c721836f729fa2653a07403f23ef1ea3ec10023a421ce754805216a34135bc16eec218c96bffdc1a20abbd10983991b6e6a806af67bc16701036a0f672086bd7b53cb32f1e6a433b6bcd4df3e046c7cffeea37f6d672496272401d7ef4265235367a0540373b3af389bda40cc589ad08fc9ec01505a955b2cbadd553a054c08b47d0aa325f7ddb8b5ec7af33a6dd5d8fa48a19a9041ecf88b0622a550cf5523fc596477372fcf14a15b99a2feea7d081a742bab93b7d08dc4a1767410e57f640b577db8972bc9604cc130be03a7cb510e6ca252fc856b303d5916c7d3c4d3aab8de645f0326ea676956ecb57bc4d8918f3cb7cca24e27ae984582f26a29057ef024fc82c1909109ae559acbc187cc5dfc8297ca4373c26a40eb4e778799747ed571f2bdf721c2afd61d9ac4a3a739d5d7adb6661c00ffdb913c89b907bf4ba1b63e2e0a9724c9e5358965f0d720c1924f028cfd7ab748f03c4ed9e4a9731e46d577951764bc5fe5387540ffaf2b9c3f37eb23294e6c83d8d7912ddc732f97f274f55035372f3589941b9a14ac471791952fefae52c92318e7161cb41239a6512ab14a95638df815aac2459c9247f7c2f9206f03a6e4cbf1cf8ccd92d76569698081e5d0f72a0310c4ac2192e046eae69336d32b9642868574ad9a62d347b8b335099ef7849ca6c663b24b24b8eb7555f0270bf9969febf1b61ab4c61b80f296c630e624672f799d2cd351249c68411178d71459652fec50468f9d86ad485e6a3a4deb8db07b69eef578e0fb10aca7d830a45f3f0ad56ded87cd54383ef44839c5655516a0c611b86395ab57cf71c909c9bdfe54ce163c11b279fe6f57116794b837bfb9a594e497978b62c1bafbd36d72ae1e97b2e72b6c1793389de07eeeab0f753d6e113e86b4ccd89fc82fd85840e7076d4487bb0302894a30fef4b1a0ee9c05f421c7200d7ac246cce1ad32287690548d2e11ec7203af78b36a1b52cc879eaba814b72f17d6a6d77f9794cf3b9f1f828f4f3cd53b43a531b0322898f5fa1803bcd8e72502d466a0df84a8adaa4d7b496635cf5bd132a7700c98999f783a8d583ac7d72a8df7d49dc3670e905224a89a95fdb2651b568972b28e1bf3d45affdb0fdd80db94eb4681fcfbbef32359296eb36e629e6c4071bf830835aca29889f7868217247d8ec1a47d19050fe9313aee1b2a5e0e1c9eec9a765ed6d895af5c803825d6ec2114636d53eca26afb3525d9b1fc5d1791fcf0c2c3279a48651fbb8e6b7e43f8391643b23e0fc7c565c0506b3f11887c95817e76d4d0cc385fec1a41aae416661c8bc2940e09ff465ebbfd25fc318e3c9a21cc0740938bd341839ed85b0ca560b83b4d54ce22e45d6b546c27414b9ddfc4c8769c580fb556da8aa0dca7808408e94fbad2efac233310029cfc236603aba2b6b2a67daa01a4de8dcb12640256005465c7c2ec068000cfbf6e4df207781e2640effc1b6e65ab3760ab0ad108a72203240b804878229db5c9c0e8e55a61c956e4517cca2407c626da9aeab5a5e2223b341f0d7db6d1e0eaabf149c7feef53db7bcabcd968b87d761ad92fc827e7262c0e71984ae67c18f64dc1395ab89f425a54ca1c50d8d4c3a4e63869b6af044be07b7ce559227399aab39aab1189c30e5aec93f95fcc81fc8f7e405b2cf5c212be8edb663ec3cf667f257ba6f2816f10b976b8019e8957ebda1982a3a33707240cc0c53919a38decc9bbaf3434b5a1182c8b53e8fd0b738017aaf8dd7e54072912939f78e0bbb82edc15bd3b5295073c1366064f80548ef42f0e04770fa9d0b20776703126ced3891d0997a537b9b74f42d4afceffa01c74c86c66595cb3272e5e80abcf1b6ae2c0dc8ae7c0a3ac92483a2a97127409c0c8a313af2ea38ef729ceb44ebffc1f5717233e4f44131ef7d411ae873270ed37b137591133112c6722ff6f9c6b2141fe6befe6f7b4df274826846a140f7742ef9679ea4519b8bfe72e570ad52be95c378da62f9d689d95dbb5ed711c000bc152d4dfb0a417de68b72ce23b3ceaa7ee48f76fb2079e4c1820d778360566b7eec22802a75c71019a772e26d4df397425b3d34b62f71164266d878aae1c966afcbc9e72f87594b8ee75629cdc0a5d8873b85a4b04407450b1f8c955cf1268647b016f8f02eb749acf64c855cdf7b2de927fa177d32e9f16d146870ed47c9f6366e2944b4efc471a5c2727ad9fd67f33014956cd2d27662b7d5b162aa82bf526dce092c7503f72002e216b18f774f2973a054fb6678815d825c70a4884d4d97556fa89f7922301e89fa7248b5da772ce670e7a3d3e659dcb6aca7055f637eecf96a41a52372b085d13c72ec8cbba07a930bd2471ca1200f1e8175b2ea074e51b89b56132572501b0aa772205cbfa4d1c958c7a14d8283e1e3c1ddd92bf91e355d21a51b57c7b2f56ffa729bc98f032d806ac741bcfbb9b1ddc3db9665d7d1b7ba4986137c2236f09c415c11856e2ebea24a9b631f6a897ea05b25b2a24a131e31f98fd14930b16d4c0011d98e6da41128b9675afb6cada8c0869687bdeaa5992ea1f998c20a1c0006a4722ef4adf7e0431aed11ba9f907498db6414699785ae546316462878ab391b9072ad4b58505e29fac643c1aa21b1f44c5834dff31ef3ac870f7519b34b4101f57265da2754a6e134d9402a877a01e0872324cf2e71b8a1d381979a4bf24b40f8397ec2ac9cde5cf9e4ff835a63a296b8432b34e1db5d757121f13c325dd7a2d772b2f99192b9c2245889591e59c4e42eb3f2dfd5a470ef6aaa668fa770f0140127fa28ae240a5764f6ee1f7c28a7d208eebf82fb9559c3002cadd539bb7eaf2272ab9997a293aa43f841c172e71426147815d4acba829ed13d29f1b83ef0b34b72283f0cb84c367af444ea3c408f70df4942a741e5cce8b44f7673f08250d97872ab18ba753d68005cd775079cb1dfa1105c8036649962e04e692d3b43e62ed66324bb0ddd8533199db0f6b5a47029d495c410ccf012d83a95f1381c27adb55731e3811278d9970ce1dcf363d03823fd7ca3b45b686cbf747932c7dd5e3523107211214849f4926efc687e4c0c8f348633c89580d76d3e58165a4463bda05f1f72207ac208202a2496af4a47828e108468a17c3097c3c40dc42be30f767567a6613d3a4b875e8b308858dc6b3dbbe919564c71b12f958e1b66e2177339c039fb11def090c4186289fe6b60aa89d8864d67077022e7e01e8468b71666bad30b2d45b1219496cd8f9fd9e29384bfdad0257d77098855f6b229ec9162dc02c9065a722be873f39e8ca77ca69315343a009e5262e2dcdc41ed8aea8cd82f9b77fdc77247c33032c6df553231940172935ebb27eddfbc9fc3a75147fc801dde9455781971a7dcb8a9071a199e8585f1a556b0b1262df635a5293c44b7076804d9545136c96ab3a1f0f381117b2aa88ee6feba61db80587ce9b0ca47f04a3da4b42e527273a3543a3148605f2c166322749c9ec438f362431108617b06be950174a23872a16b19a8dd0d22adc948636badf62b3140fce00d809d8d38f7453d667a092e72a76c32003594939d67585b3467ac10857bfa90cecf4af0c3f63e880a942a3b72ec9baaaf5cf95a95f182e7e325b1864245f27e431838107749e9b96581517272a50a2d829d7ffb8f55825d38aed1a6bfd1bf64ac4f8c40c9de8649e25fa14272a8d5b5c01e5097fb286747784e0baefd86e11b493ceb3404490111a4d52ce172d8c57d29f09af65a7a5128ec32226ec5fba3a9e28ceac90d185ae39425db6a7295e564a7fc5d4de0f27493bdd2b08a31348274c42fcee753bec560370b589354f6cd5a9f30b3de9cc276248a203e8527e5a7ca3eb1993f0b98e5f1922349c51dfacff713b390e1199f531fe5a355b309d8fdc3087b254876fb254a5e4ae7415ff712a60b01cc185e25fa1066e2791f1b30db65cfb02b989a863151241881b772197df523f8699d9c6393e768424214d0fcbbf3e96af0bbef252bb2f396b1c672180f57fd877f70f182a4135c1f94959bfb1213adc27c3e1137cfb6d59770cb38b91f883c5f6dea4bc1a4ad8df50a5d5b41145ac490772bf16f0e4ddcdc76be7267a424cb78f07190f72f435e1aad52b154f704684000ae1878c841e43d4d4b72b1e9c93791a1b846e0725a6216ca1d5257b6c6f856ab4ae8f0248b150a83d134c96090ed2045d389038114e43b63c6de871445b0ce5943efff0b7d1ec85445687d616344c7920ca927a5f646dfbc4817279904bbc58e882c353efdc6810722728530676b6086534e80b50bea78d68e09edefd27d4b95748c9db797537ed09c72b7b2024067c2fd268a26bd002274c819a5f850d578fbfa87a505cd81e69a9b72cc5e03b80a5f8556e0c1b02840ef6442e6c5ca241acfb1d3b1ecf18cbc5c222d3d7e59023f75a2e865d28cef059e6cb5392b4653424e8336cda5c42c718e9f27ba60428683eb9a644d237083b2308f53b07be188fd7987e272cbbabec26e071b55ed016aa34156d9f5c645e08fa923f8050e723e1fbfc42fb3c3e0454c17e26ba00f369b79a007c4a9eaa407bb389645b0760846fbdf668a037a8edececaff2e560e7d852dde37d2fce2d7cc258ed4f46531451fbbf25cf5420da7445da34c2160149a3da81589982c2b720804a2b6bc8834d1a0769b5f9d8090e789f2fd0672f0752e63f425c6fd5233abd705b951c85d16ad25de412bf73e79f526133df255287f4f6f977ddca89abdc7325c1ad0edca3c8e8cff36256bf9b0c8de730da3230f06c602b1f411e706f8275194eb0b172ac9c77be07c9df9fac260aea04e63729a33baac5850c13c870242ae5786e8b3f86b61a81d08239a61d0a666da78797216789edc14fe1f3839765d3050c2269f4ab0c2efd0d6838070cf9932fcf61a7273b2591a10b155b452257e05c3d5035d0a520ed659cfb0379fbcaa3584183f729e1c4e768c5e188904e0182876364a353e7c98da4f13f201fe8a4ca024ac4772fa00e67ca924a44eb8b39b595bfaa4cd88cfb2aab9f53604a9028053d243ad02bbc28425c1f7a688bc7743baf87357b78ec9ccaf790ba807aecc6d63c28ae34e78558df36e0acd2e03d5cd60519fd31c2ce37e6028a0d438eb5ba0a4a4be7b728fec5e239df13b25462d82f8e7ab5afa7e5ecdf99f170b4facfb22cea4c0860c4ee61ef09f26a63bf1b0e8719bb94938d8c96a8e70bdb125d2b44e945c8e96727de1c76290502ee602c0a20309512bea253c886e114b0bec1af7381bbf3bc729a3ca9a254e805b8fe429064c61bcf982a379005bbbbab57f2dab8679668fbd64535218b9d002090a45866c859c1d87a430aa6e61b7117e53621580b6729d5972442a8af108d5ab71d045c2b722ae8669f8aba0a3cd51e32a4515350322457a72bac70f539c5ecd7ef5004f8cd75a5d4a34114a4cecdee5e9e4a252af4c02b872104626a860f5fc9fa38cf40b2d2ee76fae610b7d0622da5ce71b108e85b64672ba73b5f741734fdc4318998bda8739c10a5b21db58913da308f1cf8de00a2e3496782fda90ab1abcd35e74a0db3f21ab744d5cb775341aec21332dc50069177240e2cda68fbb957277ad64dcfdd58237312dfc1c9dd625c1f4f0f3f78ca07972131cb2d19bae5c3aa351ad191474902baf535b8963146b3d581f9ba4ea8c1e72c13cc047bb260dac054c28032c933e0c3fcd6cd8aed03212ddb545d4946f031dbfd663fcde79109a258ff89b92532f740d3c719048c18e268ced827b16ff1f729baeefc420ebad3c8ede06177331deaf6894a4f461290759f866191d042f7b72fd7259bdcf434bdd394b5e4ad535961e6efb9a373cd9ed3edf0ea858d7392c72b81ebc257ddd8fffbf7378b5a59923ca70e0ca5e185010c83c94957dc9388302cec208f11644099cdbc79783f49edab1cb2d0acb12ef968b8f861723458d457224f6353b0eb7e7dbac587f095b1c31ecdf7161722ca38a61a8317b5f3bba9560d1e8645a1916c8fe923be98890b1e5e427c6088c52219cab9ca02a539a1fc672f4e322c107b2e56acb48f15d77e6bf96748d2569eaa5f70e27bab84d18115e728044e275662bbb57924c1865798d5c50c86938ec10d0c0e6256ffc8d30be6a72f91e7293803ed9788f362b0e3c0a5dfa5d0bb46647ece54473fb53a55ac65672bb525f2a57d9bca32cce4b2dabe767a3800f71951681c4e749b0f691d13d9672fb3597d573c34eb04d1c55cbf0b6a8d3b26094003a6fb84a89dc5d54bbf37172cd436826b9bdd55c14647a41c82300fbc4e979c2cb937547d76d364780e82f721b4ec2d56160b19daf417bd157ffdc34c2591691ef17a88fca98f806e1cad9245e3032f986297d1dd3cfd08cd44ef38f59d7b9607aac9a579fe1737e5bcc0a3c7af9cf16394afc280ae327fc6dbfc79c86a03f7e68f50ba288318939dcba717238481f7876986a48111845e2ced5a1a56b183649ab1c92a81b0685552b77067220a014cfa9116f9e13cc44f5b6d49deb865d67c274219fbda2b7ef3512d4c538c2e91f8c74d466c6b23f259f0d417a9e2a4d7835d8936378d7ad018e049d16720cf64acc906db43b76792e828889ae46c0379247d4fc2ebb66de13369139e77224e9cd3891b802cc750867919cb04665d58ae0d0dc8abf92e8a7281cbacd9a72a1bdd6f5b14c801b9fb72348ac779687b03e90ebe7b52648c3eede008217712d3ec4a55e805af1082a2fff5a699d0d23e68b1463592a51a98f1ecba2e3969911a69a831ca7a7d69b8200c762d9d7712675b5dff1557f102c98a9fe3fa06d6772573ceff915f403e780def12b987d7deda4a3dd56889e8dbc74a9ba773b789c72e837b39b873832b9dff752fe9d53aa64f639440123e5405b6e819e531d0b4865fc009f5c23e06b164e8440f1a55ff7c31231c682742dc42eb312474293ef36729b99f63ff5fc9f258e09ba47ee8c7f346a1f991a0aa58749d3c1c38b7ffe8e4ba586c80a8c91231c29c7937b343d828cb7b9f6ee249195453821bd0937f0184a7c6d8eb53ac3e6ee2183b02b80c0c1af8dd60017cd2150be8f939812dcf3f54a70a889396c37e60fc17fb6bd78e8afaa723a1ced329b616e5dd9ba6155d99d72df06ae4e7becf3777c105b5e99265bec315f5775ab4a4a4854e8d3b114151a72e144b3ebaa63701af0230499a87e29e14760674ab60ecfd1af0b94fba8b5fa72109663f69e0ea0a126166b348115926e9fbe5864ecf76025d1aaeab414e8d772d1313e5536d86fe38b5e58d6fd217815ea05f1c648f45e9ffb7008b0af97df41ed657ffe266021cc3dac4e7e40adbd51f9f0c1e238385bacb9151ad51a08601fb40c4576a8e10584b325531462edabd998447a2adcc1e52c744eea0b0753ec721f328a761b662463441565cf7eac149d0d61e98c23a5ace517f48d6ae5b0bc14345e35e61dcf2ae2cf745a1fd7a72e49f1accad1745044bb313a4021c06f9119120fabc407a8e2a63ad8f33481fa5fed1cb008932dc09c3ea1cd456dbade6572ae0634792f7019d4c996f948d62feea3136d7db3de4fe5c9b48292c438130552b3930c5def53f3d2c445d471bd9de069b314c622e865742c07e25a97914e720fd863047e1e50aef57e5999f4b35c39355d03c47aa01dac3135ed7038ccfa8472b51352557575015aa86316199f7418a4fa5a85781df2a7ab86dd17e1c6fa92727827f8907cb536a3dcae25159b28a2996ea2849ec9a3a4a3194054d09a22047236aed38bdd20eacf1adef1fad83839f168568f152af4247d82e31eeca1a0a6059108854120c4fff12b5ca7f196248b4971fb3247669d1f74208577003cede56bce7d85235523c444290309d5823d114adfea754ad9961852f35f7dcc1e433e0244b05fd04d07fdb3093c1fb6ef7b771b7fd9114758533122287aa9ce1ce12d720b5d2a009f12cdf25047f16735c47815f12584f6a78d906ba133877006d8537230f469cc5587c771be8458b3f2eac1707ace328ed20695d3a4956b83a8c3912dd26f84f3a46881fc09be0ec001c4487e174a0c0559a6d32fd81735c111ae1d5b86907554127189e68cc9b8f203e811aff5fecbdc0338fb39796ec96a91a65e729523eca0e30155615220d07b889590d1b01ba5cd37cfbc0fc16c711a447abc724f091c29fd1bd99b28bd7d88177e7da03732258c937b2fb04a4b0980ff13892c42d24a0ab15479ad92eade681ae5fc0509b7755b3d7fd857a7d83daa60579825aaa923bbcd035a59d3bb5b0838e2d032344f3b2a821b172a81dc91da351b91724612dd871a56bd045ef4b762602295c2a4a36390d76fd87cf42c1dd45f85cf72a798100694ee3dc3055bfb13413b14e98eca1ab48b08b4506c74e8950cb28c7239ac44f8f36c863651a932841af26ca99be998a20d5bcdbfda0ea53482002d72fd4ace699ec9a606e9f7c6afb67cba27638e474023de6907abd408be6cf33d72b33c048481a2a198b53c1008217fd58b302b46fbe777182f1e0e74690c79bf3c8cb2ca118de1405c687fdaaef5627c2649a7c322daa61c4e6d94d6b1a7966c7234d8331aad0105f714d2d20a357b8577573d92e15fbcf127fc4b824477ef6b45e5c0fb6370a8ad853751f2e01a500c7e525ca04a1ccf69c34bd75725fee68a37dff697c3b2a0ea1aa5761a0cae060eaf4775ccaa2f2253325fcc9d3a595aa5462141695e1252079923888128645f571b249090b03491872cb8484894556ec7665d986ab83a9b27fb514213c681490c83f08aec8fe2524779f94c4d79176c6072a6133aed99235bd1ebc848f494c4fb9cd2acfba6abda892092034bcf4c79757281f9e75cf781bdf803f275e0d8e7f18b8bba310bd172b2b4ed2a0c85062e772b927c43a0d8c0611c9994bb3dccc708a9b505cadd15482a7c08a0fd0dee057e72ecaf768e28b0cba1b3735ffdd7154486a81de087ccbacc9d1f4483d8e838e9729892453a1be5cf3979c1a3f7d6978b4707df1482f83ad7999c71de0cb707e42719cdf9062da06cd65875242974cc7ca58b81be32f67db1d49b1f1ca4eb9ac556f9c2af4661da532402d823cc3c385d59632776b67d228fbae04c0ad234385037bded9742274fd2f318efaf7b1addd33f2df1831810dafa76e136907525897472449b9426017c5c6aab9b9b208a51ee22d125c7c082dca8ea0417e6ce328b90728952880346f21446c6e0c9bdb3b6287aa13a455cd2d0960c03c063cffb7f231a8b370f42ac3b78484416c4bb993a237c7dbf27248e4600e26070134f5de4d072736b5211bb447507eff787aef32743cf1e9799f510e37e52a98bf4226eef4672381f465f71ee86623d7656939c242893f84b63a37b2b8ef268175be4b8cf8b726c3623f6bbabf57181e474e225c7e7001dd6b68ef5f6f270b3a414d50efcd84e9edb99ec3c9230c0a92227f23fb399c05555ddead2429f80d510b59ed40c567299042ed60c8ada96679b91e3fae3f7ad2e05eca939d10344a2c8ec789931f472ff4f91484266d6b444ae76b8f4b9fa339fb2c953d62cae2b3d6998e84d841f724e7e993f2f32b1b6d7f77ee15f234e7716453662728a2b50b71c37eeb1123053ecec2ce6e5dad3e1023d9a22d3920edb929e121f0de5a347b86c0f39bbe0c35d6f144e565c6bb8f9a0e468545781ceae50911a6ae672959d802f26ddecb04637c1a38bb989c3a9655033fa70ae7d15f4c4f85371c4297304a83f81c61d7ce22b2b13d4eecf817fadf576962255deeabe33c5eb2589ea7f2ecf040b62ef7f7a7225bb84a62b80262befe68c9c9a9664b6b9929fcac770a8b63f03db7a0c506672ea6d7744a04eadbbb5e7bdf8bae113f47dd07871a03aedf901eec7423e90387292514960a7e54f16959efbab7d40f97c2ed6ca25d8e7571b13237d23edee2c12daf74e8a13376420e0e3d93151d508007cd6c37fc2ad5c1413d78aeccfbedd726b0db04d5ca985eb1db5f8f597c1157b6c45d3ce4ced18ad999f6cf1258bc67273c6608f7a595fbd383c50dbb57801eff97aa8cdef6dfd9dd3ada5670b703072f6077ff0bc18dd4d961edfdc93ed759cbac7a3f208db491352c2b26e3b2cb30608e7031515c124d752781bdfe8635b37fdd3612aca658c3ca85affa49a479a72945c0061f7552f2fc8f1d41abb4830e8ab03e86f05cbfb13f02bef7f6d18061b933a8f0548567c6a47f6ecb50710d4e22de37918fb8af87515c62a0fbff58372f230f1c1a821f122eca341a39918f2ba383ba0a51d7cc0471b83b943bc8f21728c92073d23e86330a11c12b232c39ab80751b4f67f36da8ca278b777cb010b72bb6d1725cbcfa5455e1f3cb0f3f2e76ac56d0fe4d51b798d8b21aa8791b99e729702193d879ea10c674a5c331d739ef2d8d6b2a7f9bfee7fc52516f7bfa7f5721d70dbaaae7fedb2432d7c7c1e7d42972c96fc68d5a44fae07a246088beeee72c084ae40522ae60e599e10ffc0c844c3c91b5e443cb89c4d2da4293250089a674b04c1916f891e1dd5ad24ede91f4f1166d19c051e1f810fbd075bebe33935468eca83ad767dbe95f1cae6cfbc45cc2a89390aec845cac5d3106ef1ff6aa0472025aa1f9fa006729c295714288168df6ff0e7017fc4354b684d3899fc307b3720a24b035b809ecb37e6e83d8fa0c6f4bafdd6f4585ce1967eb660a29499fbf727d08380dbc14368fc6c53ef7d8d25f94b1f355d7657b30f527debb7daa69a919c990304aa0f995c664cb6a908a93aba706682fcd610918bca77f2145e60714703c9c90cbcfc015b52712ca6356fba468235d81f84d89fbff4e5238aa62b39e0750d8e3ab2995fb7de43415bd1aaeb2a17641c665aa1fbebfd8e027e8bea869476a3dbf094e5203b4385127f99d4aa68e6538f81230d4d78bd9e573ad9d302372fafe6cf86a010641a5ee079b13e8340045bc01bd8006dbb9086b0cee8f4390531462391643e42577a8fa6a6cc9fc04c78e56fa0366a29c8af8837ccec211b716c50fd7197f052295251556b95851ff29364eaf09c8afae59eb60539d4b20157211dfc3eab4525ddded09cdbb536c2d4f0c6fee74d9ab42079d9206465f72fb7252f633c357480295de8ad656066515fa108e5141b0e432b6eb0be45a9fc7a32661dc6257be15abb9549ad2392c6c61c61a5ec9dca722e156fb022357b09d8d0d9707b51880155133022cf98c54f03d09d92036d5a0ec952adc5db731f45ce942005596de48e6308640c6549d1aa1bdbf0b4da13e7af69bf28cf6494fb7a96c72ae0cd714f3bbe37373bf9ea21dffe17c3bb1c3c511f24bb20aef5e0c5d44a9722320f6aa1d2750c43d2239afbd6584b62d23fa2b11ba7d2b08a7dd47fbcbb6725303c8daf1b1cb55d36cb4326294c486836cf73ae1086357a39dcafe863a8f69a6cfc4aad2c9f92563232fbf1e3b9e28d7a3f330e2e8fdc0ce4b19c560134572273ad866f3c3c3eba386976cb94bca580b625ba4b55d135973bbd94fcebdc472528afa625d8b0530894b16cd0a55900eeee3f72a218ad725947d1b2db5fbf235152e7d7d4f160367793e916b9510cb9e94402edb1e190b6d28ce57c0a3cbfa7212670e9678abc3b6b5492c4f7f42bb2bc17938120b78686156e05d24a85ba372cae07e3383900d5b7353e4b709f1c0587013f9e3b7ebd6a2e841bce2968f9672214fada9959bc63d7bd48417e5affd906dda43425b330720cf22e99b6a1d8072e3ea30dbb3cd9c8c2913408a643a35c19ddff9f9f417606a26d904e7ad249c0ea52a1662c87210de75bdd4929c812dc56fe1375062cd1000e9a9453aec9a7c60b39218292b7a245681791f0a1dc4450f9ceb54b8ebcd766fbf983c97ef9d107207f4f7a21b890d3667787e20a2820c5999ae16f5704df4ff65ceaf262308a26d617f0ca49ca0a80588b5a38b6800aafacddb11df8c04fa912f4320cb280a0472b492030a7c8eb71cafd7b8b479ca931f2c46f0b539e9dc6c1637f45697e2af722f3af3c77f19e1258befcdf850c97da0090ad548d6febefd4526247c687730722c42d7c6f39b12a01e26919d8fb31eb95877f9ef87e81c39112da7975fa4ca7277c0a31cfff4f76478f37fea39795eb9561d64d8136487f2a4be98c957f78c72305c680f730b7b66f8f5c0daf5af76925d7006bb753dde70cd36e6f82a1d5c626144c12edf814fb5428c9dc57253492cd44c23db7a0b8eb64c5017208d7fd80ce2a75bdab82ab386686d64fe38edab1bf0c4840fc8fe666e61a0f4e08351ca6854238a74eee1876239df72aabf10622fdf82760c0ecada74d897111814811f4c79da22870e9b825b69bc1a6ea8924ce2cf86dc6bf3d05dc9e3e93c6b74bd4f473461268a328a4bcea2413035febece64acdec72fea8cf798e8d6973262ccf672fa595fd0289b48eab44a499f71010208d659bab1ae45b2a5425cc6980cd64c14f1a8c386a255501cb504bc0feedcab19b09c9605058488e97eb0594cb463a172fc47c5458944e4fee8c961926d5a891f831d2cc52707a48862dce263921925721c85e2541de7ca5b6c5350475c3f5e61c21eb3c1eb173155430e12839073a172fc3f1e131a877eafd676b996c34537b00734eaa5c2c0d47a1410415c011586726c32a9a3347a8c98cdd18b8811c7906ef85e301620681bb106f16af460101d270c034bf30d0852ea81006d33a2e786386930a1b2b30c8ca2abfa5ad9c7d09772804fc98f40b7389f7f6226c4264dcee557a65cd0a93e66073d8cec18ca4d0c72420846e35bdbe1440ea9bf2488308585f991a5ed673c5fbb698574466324be72eb4c6221995793dc76bdd1029ea34e371ccfb45ef807e1e0f89c6583b157964ec90e67c67b9e0f477eacb3a0b719f463b992f461158cac2b3c681a30c243ff115aecf98ae45be4751c937f7d12d5dbff22febb98a4a6b344b022430c5be7297269ea87aeee45b284b83d955052f2efe3055889513d1ff5f5366696576f2e3272e374defdee8a99777c9fc293ed3d02f6a4294b8513bd1ad5df7e22b2a31419727d18de2e3fd5732e4fbe5ba3f17c75ab9a741f3c708a197dd2ada2ac96ddaa726d65d3389d8bf6c759f5c8b5643526fcc2e937bcdbb8d4b83ad1b2f369323921041d01ba29ef10344d7c6fe1e29d67c501d03ccd8fb3f2e2ac690afd5dbb577223a690e6b1868dd61a143fbcd2b203d0d0e210d56f2e03cb94ae9057c0532e72c33ccd4e7cfb6f07d7393124722360b55fd23121a72a3ede3d17be195b3f7c7215e27dfbfb89eaccbd88151ea807e7780789496211be28f409e22fb0a1957c4ec29e49f8e0ebdf2ae002cc780f33aeb75195d5c942b8c1d37caa68c0b61e1072464f36e14606376b7d8f3418f5ea4b941638c9cda164255e24a0690e95886e7258442661e2b1c0f7e62f16fbc72c402c59c17517e84043d291ce1e6040b5f73293e445d4f04fd5cd01911712b723b82b75804b4b6ec65073a16f99386bac2e72ff4d1f69187200646e2ec571ead21eececcc9b7b7cae39f8b1e8f2c305a4d6721bebf9a63640a2df8dc21b477b563788ddfa0b25df3befd4b85f1aa19080b172a8ff44a648aaa13f5dc2b6ef2b57e59e1878498f6cb864ebdad14a1ea1affe3a0802da9f8f5697cff954ba5a6e4cf397684af1b83348b90cd38cb78b627e596da0a20911b7116beb7b0f94af8ca5594ae8fd90b81ff2dfa89b514d3bdf400e72eaa021a17bc40ff16cb55a90556bf5f9ce87bb43f431b6327064573c8d6eff1c8a8410870e8ad2a544d930023d993507ef1d0bca8d401224227f445cd1390572036cfea696650f6ebeac67ee062e5832f4a9d987399207d07351d365871f5b30653726c7968889b200989e9eb587e2e455492fc0fcc0a6f4540920edaef6d972578d37c651d2196ab79f305d84e4474720c6e6e30da59ba595503274c1fd5e2bcf900f42794bbf0bf28cc11e45072e521012e5af9c01d3c576195e9a40d9bf72aebdf0cceaa067eacc85b3f457ad2752f5ed71d74d26560c40d0f9a13d02a472d206029cb12de1e6991483d2b11ccac6d5dd3e4cfb321777a9d80ed7529cb057057a90fb071bffc5bc8ad1bd348bb7d927e3ab97266e72028d1eb59e3f854d729c58a1d14651f8c873984de45f81c0eb25cc8c22dd1649b64f32b3575f1d1e23ad18d1f8f68e364ab5a3f681a5e745686ad4cf1f386e121fbd61cba1792923439db4d958d63322624bc0b5d6888178e25799f94dfe4fd6329991854fa6c1bc72a305144f7c662a91a0794a44db18f72aa15b1e0ae25ec79a4079bb5fb29632595244e797c4d8b029d04eb43bcd2f86d249298f7b80e4c3da0d832c174db69d7204a18befb53f5bd32852fcab427d455ec99e88e62d878007d831f6408a58a9727cb2681483a0307262696ba9bcd993c3253f120b184cddaf3a69b0991a863072022be267970f536db8bd34a2b95c54c0d4e98340da295ca715b9c22940893e36f30795668b14180b24985b2b13fb46426f67a14611b00d2fe4a8a8797de9bf72e8dc4e0f34d4c6cf8e098657a2bec05e3c92e7883e598a42ae23360d5ff5e972326b78fbdb4c5a12f5f87d13acb679124791c94392985b6921699acf29cbd56e88e5a39a0adb28ed4192118e09ab2f8eabe31395fdffc212fc88ca917f7b183b09bb14cd06aa461da6571fb7f1b6a69fc01741238f75243012aed2061ef74d729c21beba65d897ad1b36d43ce9c9219d08d9ded7e91ea8a052ccc7fb56ea9d72b169fe30cc310c83ed0d9819423f70cdae313e84b9e0b5e773a2187f01666e4fa9be499e691f58ab69f9aa0ea07a99a58141acfa7d733a9c3d00a6792b51324a7f3189f8214dd2a24dd0ce31ae0e00b67898b31a1f409e4f97365eeaeb3449726237e9d149e8b8b1e55a48ea0f145188bc3de86a54c262bb0acb758ba49aab72b23ddd1519abf6c7437e3a31d08a42e9c1ff0a685bc342032a9557a8648d8d45ab6e34a2f1d3d81176c6966b5c1a35d9bc68b818a40468fc2503608790edf972d6fb946269410d20d6151ce4fcd8dd0ead9977e670081811ddeb9932e99be1725385a8ad05276cfc446a7283aafb58ad8d9af17b7b332ca4ab2d63f86b6dba7250b1094bc540804dfe0f1004d06a35aa0658b46284eb4bbbab59ffb02707f914a10e8721b27827c48d94f8d355ea0607ff8e68ae9596f861412d97d5ce178e72068002c8ccaf2a9907d8d9a4d6ab6c2605e3832e33ded7c31857b523c5c6497225eb21a365be710e3748bcf39d04a5ab3b2a8cd03dbbd80ffe3deb4cc1b9ac5399a27fb054307d7409a91c1e32d3e4fe2be23817bff8bef4188e5bb549b37e72ed7b19230a061f83b6d168694d0de4eed28d4c6e1fc2c31c7997ba5bed859322e28ae9252088c6706984dac15ff56bf94e4f2e82d903d530b276b625945b182e234480d9771096ca65888c33e6f35f724ceb60278603c38751056f780af8432f7e351fcfe14fd9cbfbb45115840494710215d539f2003ce69b2bdad1299b2a72712507c918f0d4fd70380b0649962db909e6d52d633c4c3fb57d8a010590b572eb750962aa22d68baf58eb77f9502a332f02c1bf938bb5404458d9a6d83daf2a4d11e3e28d4087951999baafaae6b7451f341f8f2d1176de7dd91e54a7dd012f695286e41eae4373b87f2e1c865b36e783176f998e6fb59da6655cca5914a572cae9d69054e208fbf25221d7baa195dbd7e64db2a07e9f98c09ca8005233e072f1053c72932bb6d10a078d661a319159dd5c2f0456efb2a72f6aad546df47c1852616ea431325c926bfca623e764052d0c6b48b38454b446716aa433d9fbbb723b94a0bb54dfd9bdbffca6c37e1ed4631c83a832c4e8172ca1f6e471182ca772a01d224df3ed4e4ffd1e944a9c6acb08ea9f42a162f0bac7bedc209dff5534729824ed439be6e735cb46f3c06511088bf4e7e58dac990993b3930780905d264597d5364d61d46fb235481180cc8f9b4b9ddac523187ee1069776396d3a5d38724c4ecddc96601275a694ec3613da37c8fccb191fa4a661da90812bf8cfa914728931f9398d9b740e03fd34725a77a48dd4d04d66725795de8db7c0d3e05f101b5db7e499a7c04dbf30d06a5d9cd9c3e37b81ca350ce860c4f23a539b6f511772a126a031874c70408640213032fdf44af0d96907ba51ba1a2b2334fc6a50da462fb71848da5d922746c10757a3c03ebc9c62a725dd4ffd565e636ac3049cdd724d365f9f48fb631b802b1f1c32ce3d26abc120802f96471b66789168122132729fe6df10a94676fc4d05d37dbbc22f663cc81508da9381fe0379c35e9efe2372ca023575b085407bc0938a3e6d45a94d2a8408d22e5119a4d99557e81dfa297209263d02a0db4c4dcac282afdca381dc96444e42b2c6617abe89ad188c41e36758779165201e37a631f25a8b147dc382fffd5b6513157df5ab459f49e2923867ff616d2883a25bce956ea712739ff73baf5f8a59a73fd952a3718a48f416b772676805e68466ed7ad61b00779b1d940d62cb86f59b94effe68b61eec9912565bd7ba4a938cd7d82844db5a0a6ca4155359f707ff69a71375711594bbc0a5c53f06b29b3347eae441ac45d87908ec756d52212b06e00e549c18979d603e8121729a8fd42d4a5795fc787b0cf26c8e43d2d052a25f5b83244bd462ba70b46a851744b921cf16963f8e30df43f8d67d96ccd6c2faf54c5d363803acca0f6ea46a72fa19334316d860ffa59e4496efc6d54bc9a765283cd0854f2a5b0072fe91bc7225e9afb81eff0bea30cb127a0089a5d364891d71808247daaefe459175b44072353102a3056d3739cb8fe8fa466e4bd4726164871ad1f9d27732c8fbc66bf106db05723891aceca5b5eea4409782bb2da17c3ed64e2b72eadc0350086acd82727b217acd775fe9c9d7992c611e0fcf58a5f1d9a945c93ba0b9393e4a67816f595e12cabd548e730732c866010d7c82b57718671df20766699958a548cc2e5272bcaeb78fb9ab628cb90b025bf783be2ea4da778b5fa17fdef6f11cb65a6d990a5ff0dd9e3500dbe7c413abcf4ad475f4a50b2c8717d29eb47cd9473082185406b3c579a5ad2759b9c07345427e0f9a5a372f9af46a0592ae66697c75ef8a7c59144966c3561b0835c4ea6306c52a4ba26eebaac41317cf048fcd63a678f8cc72f7b65a15120965b7c2d4728d9394ab107407a3f26ca12554bdf8a50efee21d23b372b7304fc4fe2f56b828ff6db10368d40bd57e0caebe8051faacd58eabfa37fd66e77f58a201031595f4154052f7fb874e273bec1696cef755c9cea6a867071fd927e34649f6d953c0967eff548b625a4c4ed7fef5ea3fb40dd8343137db02a9c6e0b7280eec116ffa242ef0628187682f47cce5c84ebe05d4b05dff483a0c419f8bbbf529612fed98569292c19e480f2ebdfd6a907c40a6f8918d7dd7ff7212a58c5e1d3a8eae0f92e4349c2f6f0587eb94c8ed08b42563319d568c167d72a41360b84e3d740d50e26d451e378afdb674c96a01ff329d4887a5f71963c51abf4111c5bd8520009b44f729d622d038b05fa3773385707e64bfc4512581e50dc7f113ca3feab0c321998abb91578bc1ff7dbff1d3b3a1606b5e1bfda1b64901201317929661727ed66a3518dadea85c2da23a4e218d01e933297570a3b090577b8f3a5e9fa075e2cbec0eb1952bfa39e49812b027ea46efa4ac460f8cffc57220b65191c2a297642ba4327ed933cf5e7c49b1bb4789cf2f5d87782462cd7c7211f9c40a2f6d91bafcd5df0039b398080070fe2454351ca67aee79c6c7b1ad728005c21d7ce2658af12386299df8fecde3456c9b2a0c06e1e270fb71911c68623f19d611d1759156adf92362513c09d331c7c282e8ed453aaae0912e68276e729a3a0083651f3c183ce1cd4092c7111465713a89d7ea02f2e0ab1d42f84efe723bc6e07b8220f64fa805310d8499e09d9bcc37acecc5b27bc49c6584b84612727149d27a7958a6755ee9bec6e4b91fa173033eaae70523e581814b50a5bf66723fd9ee4668f12b51fe66d092eb6b8009d03cfef32ec6bba5108bcf003aff8f7298b9b71ad4e1a9d928a1b3f2f795bbe4ccf5c6496dfbb63293e4d30835846972137d5cfaf453ace7c3a3c1077ff7a8f55d65e5ce793b87127f706138102c4d132040bc38421f0385f0c44aa9d61430ef5aa80375ab1f1df29ce278f7d26a29726cc9bf610bc352a78055c9ceba332b182bcde56f5580cd2f671c27ecb4045c7253180ccf7cde363aa0f281f2c635708cd4c2ed078763b0dc6266336557e676721b4953860ff3cee58a5703975bb0f8056576263876976ceb2c1dcaafa20c5472b2845c571065b10b55d7ad38bf72627dd7fa8671588a2017cf29601005e7a004d0ab16852ded34e65dd984b8bd74f2db09159b8682df50fa8aff42cd38e9e07258e8f6e2be008a548b695f72d61c68515b6d72f373642c30be85cf9a07095b72fae5371c50aeec59f06f89015710c828f157e8fc7446022f7b65465e81a0e0245c5784418591fdea74d0885fc7cc1baee58fee6bb149ac6087ea3f435a9e56724259705441b2c8248623e3f065f876bbfdf862ef37a39007b2340196cd780d0cab1b2fa008100c72534b5a522cef8ed900415aff852020ddd188949016130e0e7389fb2646befcf7bfabca10ab5e2f59880ad8dd891ea72a457d01492b8dac7212103465865d186c44739d0cbd07538cda13b68fda9568948e1e66f2f0f6ea72ea941bacb330fe27af0931d03501054be0271e3b3f7246f18b84b8611a0cb2721db77dfbc245ed784c879e18b67e418e70c67143eab4dd4fd63d6c101de6927261c2c1bbe47fa788cabe34a2a0c199f51af422b2e934c37afe411c8cac069a724d19fb2948b32b7ea9e3328fd3008bb4d82adec147d6446bace6314ceca50c442458b2d0223b6c30b87d4e560e487d78976d619dc709bfe123bfb34125c75f726d2f0c0cbe89e0ad9524547efd2e002a831424ff6b337d1c9d207beb7e08c972603d4570a04793e8907c9f672ea29374e6d89c3467cb360c8d507288eb40a943145e1195b0bf34c5799c24fd5ceb440705fdb24300d8b9ea2f111433afc7317261fe46916be2946cbf5a61e65598160a7c372e641e10312eaa2ff875002059725bc5b63f38e6f96e10ac94e793177fee7ec36d90e7220b15f3cd1c8b9d7f0f16221c772241fc8d43d378df8096fa00a9b50efcb0e078819031a56df42c873e72528f66c102cac47097143725ba0d0bb8ab3c7ec7c612d3a40d9ce4f470cfdf55d52d55be21a5826e433d2552f1bd35d9817a31dc324d405582dfba180008175fea4337515be874e9cb42c04ae51831e5464888dcaf22d97a33671ed540036172b2c5c5af85619d2d4d95fff0bacef8d6c4b50727c101066c40c16d4edb4fef72f954a39f1054dd9317cd81f3877310b33c2afe73a6d8ad7c7e56ece40470b21841c65608a3fc579762a5cbcfa6aa1d357ab0f3ebc5a2b7fd4b06142ac03c394985898c2428b92beeddaf2944b3ec099515df8d45ac8a217a42b2808c155a7b2e576c0b6822b4b44ae8037b2d21581ff8904d1990400d0f11d515bf8387cb2a70c0cf5ff9b5e46c18070ad9af0d91ab1a16a9e4adc4fcaba67d617180e0b293622c58b6b6ec522082eaab2582d238443329b3ee32ca307efde1f51e6ef6ead63026e5765853f6870f7c2e31dc619270f55c8a0b70706eca12eb2f531619b0d4600859827e71fc3c738fc837ba450f3e096b950ceb888b5198874d23265634377237b09e7589404a45f402c0b3be5b48c866a14121f8804dd64fa9cb0e5f92ea0d3b6ffd35f82d9185495cc8ddb32ea35b8ae85d877cca6761b0bda17abf027472768f2b008007cbb4b439406c2631a719fb62bc3de5e425009a293a5fd93a84725179fc1f60ba6e5fc0945e8e48166fec2737f71dabfd4639e024db86787245727903e5871e869492e22b7fa5c493a44ec75fbc66380c13c5c18b164cb92c0b2f3403f39a3caa025211d8d445ff73c8f9628fd3ff154d9f15070ae223fa727d72d635daa7f6569face37d11749628dc5bf2d23e5248b28ae647c1bc4a7b364472b8c07b4b1b52f477dba6bfb0803a0e0cd4735534d5f270860cf0f683ad6b2f72405f00d874aa2ca38bc86b2bdc770274cdd3e2e20c61c44c9ad5cd8c5caa2872902c37b1e9de3c8603884e989dfa6ec8b209142415b9a46b11387bc43a5932432be419117b240a32f457734fbe56b044718062b703b3c06642c2e8794efeab72107059aaef259a2a997f39f1028db98e4ff1b2c0f7c8783bc1c88b3aec182a72648e075feb7e4bf3b58b9340e8ff8768c342a1bfc33ca67286689a564682eb1e24e22ed0927a41c29b14a8387e6a210b4be0f7066a5d49e064cd4b267ffa104a1abfbdf3b64ed5e066c6d811a4219650a5b06ca77665e75201476b38d3c89d720b1736208edb108a81efbbd05f4d9a5ff64e55596a754f7977b309f7087bec25c9449e02dd336567ba5a15af1a7b1750222f7618e8c91807ba309f2b8fab5f72dd8eed526c459ecfe18f7a2b8a2b365dc775ba9f5f335026c5d2385ee2cc7e1208a15a6b7bc20fe5c8e33dc2333ca57d70a5ac8a0a779004029e39012d9f7b1795070cac567928621721a78baaa765add41f851b9512dca1281d68eb4598c772aecf104c59f2f3d6df5190e3f388ec41a60982e969ac104db0d23b4166def272be87be54d4e872c4f8517efe429a926c16d8917a9c71b0ebe92d03923662557213f543aa7acc8b0354c9353d0a91b9b9d1a21a891f62b767c3be681dcb039672b67ad55ca0c9e904247cf824543faf7d266900e7c275c3a92c117730c3626222e864271d121a3a780868a0c8eaa9f86c6cb71860766b4e1c800f296a1138497287efd4fa45bbb8a8446416e6e7fe2f8943ffc3af0e09844020f79b0ba0ade86fc6eafc0c1d05b7504c7d14464fb806268de17090852a5e2af3dcc5dd67ef6c016cae81234f0ed458895e68ebccd467547084e6a4dddb1f32d35caf10b0db19723f43d30361044f1c1af42d7d545be974dd1177b8fa44210da5eb60d04218f239854660327ec5a3335291d5ef638be435653b06b2d6eab1e8c929239291643a72c1019e7d2adfe8bee86278cd1baa56265f77853e87aafab7a65be0446256445ae8960f097802ea5924e099b4023c4fc5f1e10dd7c826debedeb3aee1c90af97274f842a7700aa1b8849626652a18e22124169a9b58eee9287348f646e58f087243b7fa964a5271ce8d68f720c18102f7f6fca26a472a5ca5cff804a10a79fa72540e9d9c70e9e84d894353611dfb1c17c5ce21803e3536c01b5271b488f22d72a4e75de5fc054dc27a835fa36c8b520b393237cac2770227d0813f99ab612a4bd3695b180f42bed1c405e5eef90ab07efd231773cb4bbaa455095305b0c89572bf4b3838f587c1c0fba6b306eb60fc8898e1535053c32c97886f066ab66df672569cda6ead542bae72974af2609ca475a96770d01eebb8edfac087c84114fe72b10ed0183716c7dc7604ec0c30a6162765c5042847ae88acbeae960a224e401319f303b913c6d599c6d7afb54b195c9945d3fb346b174141f63d397a4cd33172cc27cf429186b0d5b4b5ab103acf34eb33fe3acd27a4b1df7e6b66fc1696985b6e13f510b893f9d635dec630c002167e26f86ff04d4eeedf7fe3d4f3322ed2725b9cd5c9e031243c821d658746a4e35c5ed55f590b1d4d6889742a14dc7729626f85cfcc14e4024aa18524157bc7a4eefcbf48cb4c53c1641be860c2f891d80e9071fb54f1a422404435654c6ca95d6338a696c1be3de7becb8831dc72cf9858f3cbaa6d4051cbf72aecd43bd125426de06b205c907c9af4f104f78c27578d72b3b31822abbeb6fea59fd2b7b74543be4e89e37dfcdb9b3a0c72416f531c60703dc8bdda0ecc6b33a8db7eba3fd134ceeeb7856fbb9a1618d4ac311bd08f0072f3f53f0df7652b026db568b14cc4605605739a51e1f9829c6fe1342680b9df726f1091344736bff60eafd05e0eded1c7830e1a07f84af77e1362ad703cac2b72f399ddb8d3ed74d5413912f9f1add05c8f26e557c3bf8c9a7a4f8c356e0da8723c04c4be3f0571f9985eabdc54ad8e5885da961883747e3f553e9dbed642347283548077b9406e335117bb82a960cbb45c74cbc4e3423934f894f8c42d2c247290f7c7ad273f51557aeb62b04b8b14c0418fb832671a444274338275a489ca72c1948d56f73a4668aef524b3fc9463ae4d41346e2181296e93d846460cab167262bdd490eaa1bbb3a05b950afdebefaa504295e017292ad7dbdb6f04f538a1728b929a40e2fa8abea6c86194f1b4620702df4b74339585417c45da5544424772ac1a168d2c9c97af407f7d1e74ef427b5cb76d2e847d733c4dd036962a73376dce42e2c3e6b2d4d18399cecb0dee55626b6e3d6984366a183e329150befcf672514f9f720571326a343b8a5a8a47a1fb6862892ee12d128517266301c65354472530debfd35b9d710b356ffed4f9a21a67dc72b2ebcf960e3d17f1976a04986dbe68c5d6cd2534ec2b8cfae550c4b5b887848ad6c65f4fcf2a85eeade221ec519e8ff291090bd658c2848886c121b536adbb93e3bd70cb7817e60ea49add6a01cbed82bd3bbccdcdd9d283cf73f125da2d850d5349e96a91a4cebf35c794255349cfe66e61ec9b82a6d42ea37d54f4fcc2e2b9e842024b42460728023fd17772996ee467e7bba64e0c623c5eca8c668e748e13e301e940b1a2aa29581938b072e511bf8798d988d1bf422298493c5d6eb01f8d8df89f8d86f7e29ac47dde9072c254303c00ced9bf67cba77b99a0e3096846a087465d4464dab8778cdf2a100b00b926d9f06b4bdee9acd3ad7a90012881a011d4c2672d5168918bf36e3a08724588fb568b1b41344f029c1361ac4ec8f9f2307ad34d8916f1b7b02eacb62f72173a3a90715741bf1a72500bb2d935078c57f1a651771ba87149cfc8b14f7c728d377a20676c60f13b5848a7e98bb4c8dfd3750964010c0de9b049324a581372f5209e1afeeb2bdafdedf02419e5d67bb57fcf112c03c6f371570cea3b4c7547d9e4b56ad7a59478f6bfba5ce2ae91754fdf8f1581f620b4dae37aed644d8d3b5f3248c364916431a515a24e3ee3636941c5070ef4eb99bb01cff70c05874e72b2fcfed4492bc6d01d2be4c42c1281125eb3e123325e4251d0befc1e74148a721975fd2f5e1adb1a3b6fc7e2ded38eb0c4552c09c801bacec1550663d37a5872e81f52488d1930fcc7698f2ba882e76d4023e1e9c2058eb8b2899142cf52d572d7f597fff075205f0fd837beb21be3baccc1d101646921effd7d8559b1aba0223a403e601855b8e0c4f1aaa4ecbf99847a2f6e3368b5b0ce8c3f77d8d9229672e45892eaba987cc0f704b250ebb295301fee3b8be46faa7613fb5b376e1bf211d29c80fe36aa7424dc93cece2fafba818389ee7275a7b9cb9dcbbe8e07654d259b80cc5508637d33b4055b563f9e487e372c60e5bf1d32e6ecf8cd086f8db126d0e71df2ce2fa51a3ead968979de0b19e74b160ccb1dc066c258c7b96a1a0672379f7910870055da67757d6d7cbc461bcb8d99e46c50780c5d78e48bc6ee2a72b4111fe20e77f1dac2807091c1e9a9ea7dbbd41a36e6582d8185da744b8790722c167ee1e655b48e5a1db233b8f949a1d51aedefc1d61f7b9950e4a9c72a887204a1afc9a996b0dcf6b1b53be99b7ae5809a00dff684651ff4211d06a4d43c1064d1d6310dc6fe83688dcb961fbed47fd71ab7337be4fa84574ab4bfbeb34672c8962652d3445ddd1e616fd507e27c2dfd20e7f1137ff70a5bf36599295a62720e4600ce131857304715ff62fcd4d4ba218e598a7d2f844c45f636210782857291c5556d357257058586c2083f4d202935352679d115defeba6398ce9ca269346311d9203a0fad08ea62095a94df6cad97b631fb25ff32fd7e9ae9d81f39d8728f78d22c328f236cc9395d79d55b73225999b111a33b3c4758d3ef0eff171d72e366494c59b58de48b878c8e77deeea98fdf77eefe7c48d20e300f5a38aced723e6c9773b4e502f1a7515d064b48d1fa7da4a2d3509831a4e2e2da82ef9efa7226419bc81c83b30ccecda592ff53228dbb19bf92ff231b06001f3370ba4a3e503e3f6ab9d7a79b6c551d60ba5c69cf661bb46fd558463405a1734771a3248172444ba12bc08bdfd47fe7d02dcf1241d9d71a1f88b3b8f99cc786647c17ee785b5003179bfff01d4576de17eba8f79af3abab915f3f2f1a21d05fea784d29423dc2257de467eb944a75d5c56cc0df6d6c3d53f16e3e1429309a2326664784e26a9d66e710fa5471966b18637781361098ef8c9d908ac3187e5137573280f62472fc851ecc2ede58fbd2f946d896bae0f3e89f2c79005f4f28394e3f1361e9107214196a30dc279b49de9337bb9b82b2ce0554070e33c6d8dd3c8e00adcfff46729b1aa160af1081c6fb8f0f09218842835c3962d779f4659b6eb02de87fd353720c9aa57d563276e8dce1efbcf1ea3655f0499c90b3541090ed43d4758666f3415ad4b35f58df0901d1d6952fc2e187e57ad025f0207a70f1e3bb4e73b970b2723e98417b6c1ff34032a85cd92d6290366af8d669fea14ac446ffccdd941bb739b2e09dd3df1fb9da22c692e0754b565243458aea50cf17c3070ebda1c7da2572836bd447f0985a0bebc6e6cef19280a23faa2b247a37f3be4145423fd8456072c6969496d3d202a748fcae929e34822c214afbc7d1444603fe65c89de036da72018beadd60c2d3734c948554f4dde32d81583c344cbfe8af21b21dcb3e597a7224c99b83818f5d46b32ad5f78bd92371aef477758ad9ba1088758cdfba06f23cd070825dd95bb2e9c4f9701a40a15550ea1a7d936791465b6d3ac8125392ad7297b2937a0483da5e30d19b08bcf76bdf8257af2f5d180d2c1d1a88b2dc017472be76e08add68760ee176456f3968b03f1752a9e9c13a1671b28b08679c6fe77241868108de605de58878d3f633e20e2ee1295b33bbb7051e4efcdf6143991203ef54848b6a4f130bcff034fef135eefbaa6f28bbc18a722f4ad0173475451236a8923fbe41bea11067f735dd05bb5d51b3fd31d041cb2f56dae85e94bd4e87687188440287521f7e9b0637d43c743135ca2ebf4742086daf5805c344d0fb0a724e1481fe74a654d0c62b4f9ff8ee8f1b5e5dd82d5166a46b07d178ff85cef332265bd30f17979fc2d075ca9ebb1c266b20521e42658a72c28747181fafc26d361cdb4e0002ae26fb046a960eef5f4db520162cae180e3976c920533d46523b0ea80df66cd6d8dfcd736e5392460f9f70e68b0a8d005ad0ece676497b97639e7215f1f92c927db144b685c761d51376b67f900c20c0bf6753cdac9df5752eb727e05c1701f37e85f0375bf8d9242c7c7cf9668a6895ca0bf1c4989df6d7ffc019ac006e0729e3e893e326e308fe8b0e1db0f7314b2035c208cbaa95e4f9442872202df0d291b5de76127577f583da40c6ab6a1eee44436827d2fb5b56c58f13723a9546596181cb60d24d46787beeb79f5dfe1d18b238f19593d531117a1aff7209f5c8d1769b4c188778311e55f5a540214ec3e73cc8d0c32d59fd2047166e4da4b5ea443d045db10072c6c51e3e4320bee7a578a79f568e3a4d295498380872cd3d002bdb5a402cc3368abb430be6b0912facbb0e70f8187cb924d55459340f3f8934cd479e5455a55e0e2acacd1c746e46dba15ea1a82205b2d1e3ade1f072607ddc0569faf5b607eea2839aa5799cb49cb4114725063b455adfb1d1f001624af6b314d99a53a5537b16e833470aaa813d18bf870efe24d729d8d02c3782727bf4629c827a9bfcc1b0af24de3d976038eedc9ff892b594fa3a85c89854734394eb85f154d1bb4f76680e4afef3273b4591e7a382d484da90dec4fbe3104e72b764947e2d3969d3b7b887367a9ccea990720fff8322fad87c6e182619b0417213e42efa8787311332052a11adf4645b24d7e671f8884c219b833729923770725a5caf7494937b57b281695618a1c37c098e65bd0eb9f16e8fbe77c70d1bcd16f66eb994fa7de54161c75fbae7eb7ca4ec485250d57116dc2f1c531430d1b67225b651253fa5e1a7d7c31234c1fb2cd5ee71ce238b508ce5f58c425a589e5272427fd7623cb2e10dc19db2db3b8a3f3e117cffc234d581a314ad847f885ef16a537d851f017dce4640f3ba219b344877d5ecdbdfca31d16a32701fbd53a73f5e365abbec635dfce02c095d2d6cbf5456a2bcc63f1db9109773207e1396859660665792120ffdba48cb6acd2c20968454de815112598dc501c7a29905fc08b872b535506ddf4e784f7715b3cbd56c939418a683eb6b307fc9bd06434d809e4172236575a393761f1825bbff74452e49cf8674f048eb059a803b49f1786e48a072529ed33d7594bb0d354b0d71cb5bf8aace8cf0478efc3e7b1796236aee12db725754623a061e5381d7b7942a4c6eec3b91c82ef5364483173b7c6f6c937dd0568b89c995ba84248cb49c1a8e0df7d5295051276538c9300f326ce05b00d64c7201d853c0956ba7a33f52ec6f125ee503659a7393379463fe39ce41284e4dfd72849e50850ed8fc4685c7f8b87799b27cc365f971fda6d1f35218f373b225d115c56ca3f03692f5ab0f6550f86e56ee6565bfa70314ef573ccbb58e52d91b0172cd9e7230184bfbadd1cd3b64dcd02086ef88af1cf4d30d12491ca7aa61cea972c8580abaab12015741cb0be886c734e79d50beeea974e5855a44cdfc79f3b37236c260c921bfe057f292d9b4149c18e7521e770f6d36def10a33784d1732450a03bbf8aae31710d74304d886cb3a2d14ce72eba5a34a2642371165f11e054e728859102cec0638ff84d299f1f17af9a897beeeb99600eeb64340d5c6272b45721c290e76621b20d1871c1711c4eac1199802b5614cd0fb2e94d8529790acb120a4ff064b46951ca4766fd37de4b28d089a0949070dd166975d3fb477e40d972c7726c685ffb994da5961dbaca18c9630436d62b8923069e3f53d1ae1eaf7be040af5cdad16dfde16175abdd3fadd65331722e411b296bd9bab3937e9bf2f5372f370edb0eb8c2f8d73755f3bf072413aba8a1ad9aab6c3154861e9d43e1a0d50d810b278b6db998439e06fa8b1285f8c832eb90da87b87ea458777504c768272a37c27788160b48279d3fe94eda8f5de74e7d2afd919b7d1996c30e60a217972cb25206f53e669959e7089ce864e73cc1857378596b72601ce96dcd3b09a2972bbc9903e4fb94b7e53fed422037b85395de675a5968c3f12b0e94e9abd433810c5306801641058beb89cd0e8b7733d70f8281a0382ada516853ed10a5c67da722a90cfc49179cb339c2ec83d569cec8786677289d2d837e427a5d57af954937273aafd44c78e9e5764105c5d9abd45275afaeffb08f496aaeddb937db71de85d6463cc6de108d904aa44e0f848cefa4d7f5c0b2c1f5ad751c9efa1e46fcc6d724a72a3151da7a55505af3aacac881a90b2e63d56bb7b310eef308220b7fedb2290dbdcc9477c9691d63a7fe4a93ea5a5eaedd419e47b1f4d29dfcbc2cbf67572a6d829ac6db79e63370cad5f2d01f49346794fcc8ef9d31b07d71d197f3bf517692878d946f0084ec0b1fb9343688ad33ecbca9804c3d5a46eb17869a3ffbe22f37afc080a6c6309f6323900ea6a42ca2a264853943317a46b1ad1667405c024c0d521d1b7f1344916d81edc3e59410178bfa001a9de897f870d1c760f728351af41fc4bc5a72d9fadce01a5af64652b32e2bd7d3ff0924fef5872db14d38e72962ea4939b4927e18ef62c6eaeed8338dc4354c5852f11280380183a5b5c2272ae30a239201b83b6ba5744748fd1541917ce553056779ed03d33e550ac53a9433c5c6003cd4346492284dba5bb6b8088b876fa7d59d5cd99fcc73e09c8557272ea8b1770de87d565918390a34f152392a6003fa8ca506dc662e0355b5fd985727773880225c9bdc25e0d8e30a9b7b4fbf372da5913fdcc2dadd268ee148c72694a7cd5bae0098fd8813092510fecec3d397ca4398719bd99fd06de5c9b79871368d3bb53e8608cae9ee9d4f9d2a1fae1522baf4a69b73b514465f823feb3907200f56678b936b5c426591ed3b162faaefd342d007068dd02c77c7e13aa741d59873954dbc32aed6951f4baa164336e1fdf24201f198de6731e74ad08b9a5ff72f27629ccd0c39dada964741cabdc5f361d891d7213767d9ffdebde94a49f70030c38117b09f40881e4f457675874080eeb8e36eaf2c7886916d21b3c2d397b72aded3dda039a35ed7b6facdb9bfc4e3bab9d0739c44aa28bb2e4f84302c95844c2f8aebb61e1acf5cf7c0289b3a9dbf257f6740225af6bdbb89bc14d7b21ea724cef143726cdd994988850f32e4b8a217a094991d9b72920440cf243bdfe87728628b6d0c5cb15d4803bcbcbf0feb3682877421cff4813e409445dae5e0d7272a901d64a7e173c829fea97546f4101cb64db30e0a8ed9fff1aef9ac435feed72d2a4dc316dab5247debda95c1ed79b2a315363d68a3da931cff6a2f9e5df944687ed90037b0d0a258fb0e0a8cd886ae8045e8be055a114a99124962fa543ff71420d2089c0f046423c5f1bd8acf32a5afae1a3251e259c952a9004b87059b3727fab118071d47cf079cd6bf32950bd29bfbdf81dce7bdbde2896fdfb71217f72b65e6010954c547a09ec99a98cf698827ab82ef5a163fe30dc93154effbf5229df3fb65adc034e06f417bee2e9c540b4a2351ebc427c92d4b6e8c6050e1e554281a6c8676e0a772989fa4424ac8c138626701b5fff0305f5697a0175773297727db955c8438b05d27fcff1a4a039177510dc2f2e195abf85d587faf4e62825723632164c83879535a5df7f088fab83c702a14cf2e27c96f29d5e102994592a72a1282a22d48d8e2eb66b5414c95a5d483dd10280dbb4abc5473b105a84e2f85cff5f671fb5a491b00ce99aea6306730ffcfe0f2c619e7735c021147a0e3f217255af792dc059918b34dd252854006c25076fe12cc3c159747e5d46bd8ea4c17216c35e7f477289b7fa5cf38d0c0fff9967e0c8f197a2137fa4bfb98fd9bd747212df0dd39d9e96ce94299c1a202575066f9fec96e21d0f1f27631bd05a72e872b526385c624a2c74ee6a5b7ba75acd7e1ea60db66da360577c6fa5bdca547b70ef4c950d3ea7542d1fb7265ca8d97bc14037b4f7bb3ad7e27a7659f09336c500faccfc329200f9e69841a59935f65b45d4a2cdf9343ccbbe8b9beb5b8edda4729e0661dd4278288334cab4a61523646cf048b799cabdf9e9be9314a8c8a24f72613f7afc9a9fa7ff896db1cdcf99eb37fb378cd631b578d0a8b8fee99dcc4272c2d645115f487024a9ceb8144703bda732bc90a163a4e80b0e6ecca4b0b5e40a49c9ba56093d32d5c66a3dc79419aafce7f5c7e6eb1136fc13909a6493cb54729ec7b9e2b32da92abce37be852e02cf36c7269ad36e7522968664d925b6538720e5a20959e051d94d11eefe9a757127025dccdf7571d66ceab82e13588a82f72844bf69ffad26ddd1b9dc107e049757d784f62f5085d65ed2b5790685ae22d5060b9abd9b316b950b54910926e20a844067a89fbcee00f67343720b66a49fe72e41993c55dc0d4ba13fcde61c14ff25630d9e4368936cd2aa0580c59cf5f5072eb51c14c2acf92c264d5d26c1e8aacdc7aa5bf25c995c2a81171a8aeca989972df2f0c37b94ccacef6ca6cc1329d12d13856b4be1668dbd4584b3460fb61af720d4a592997c10652a3466d0382a5d388fd09db03cc51ff04c41be06a10c3de72bea53848e7a0ed669244904f076ab901f80272cb7bc58a28543e9c8b4948e23b17d8533f69956ed9a0fefef16e4ae76e6a8f1207b9b73c9efc6de11487868172ee9c0eb2cdd07cecaadede0679cece4caebb606c8c9b67ca9f1d84ad7b652572fc555bdf04d7f84d84146068c8e3beff59b7681e9caf98ca9f34f479829ee77239688721e59a548d3925d832ef0f4c85c67d5e852d32addaa38cf3fea3d992728ad18967d465955e6e4ceee22807eaa703b41c219cbef8f692cca3cd040cce72ca46023af849bf866cc003ca2e5b9aba700ade082a651ee7aab25347ce44ea726d282db533c5eeaac63eeadde6c5f211c1e146d7156c991108b1a37218117472c41636e766afdc680b65266ece48b814943f2987332b49bca0d25b9ef667b4720b56880463c36eb9aebf9be470f99efbb7ca7bfa126294a836d9cf932c440c728975bf2e66b4187701eb212c39d2290963cb0c4bea4989542fcec43056dd56708fdc7237a68cc7a9c632080e3ef5096cc6ed12ac1d7897ea6e74776fd3e8aa68587f525275fc1ad869172f35bdbf22dd3605f23ab07003eb47afe58d1f3dc41d8fdea50e0eabd6fa2f97a303b3d2667ab4bf8695719cae23a53a5124ec3dad6231990200ce36713bb0d715ae1d6da53721fa1c392b734ed609f4297f9160393ce2db25213bc00e5f992c9e6842b735a3d7d228f4da2b7feb69de8a4e39facf7285ba89813660745c31e428b98837f3f25e248c8cec0c4ccb3fe9fe4eb289877222241a08c49e65e2ed5e708f9772602b6bfb3ee1d97f8c4826d0563d1cfb36441e15f940837f68d7df028fd91e4d98c4a695196a28e468f3f2a8580d4892c07267efda541c2c9a87be1cce90d84a4579a6ea424cce6643966ac87cefc80dd268a43525836299961b12ac2f94716f655ffbeea390043dd509d86600ac22cd8e729e9d5319ae22a31f85f22622099ee0290012b3e5cf3e2fff0655990603ad6a7241aee101dca0b5b33e91ba3dddd00fe67371ff06d531e3243523a093d22459724aeb3cbf69af9403e4e08a51a9c339961938677e9e993a4db7ccba11e1e4c9723fabb6d17bc0b65b3e5097149f0093d63fe55562b4bde4a268b0fe7a0266c772c3d7657d56c5f6fba704311d9e9ce73919405b5378cf96243478fd18e4a545727d10cf6a232c8ff9369f9ead55113ae40fd8239cba32f7cd60649e45df6dbe4fa9b4024417637825ae4dd6f55887c0ad80c87934796fd036a8bd80e1b0aff672079a73a97e3e4058138c6665a6b0f3f8dfd727bd05756648847ee8231f3e5f5495309223381f33dfd9ecab67b58e2f1bf0a328d84211f6f0d0081e9d4579f84bae56357192528a665ed51d5315828909b900cb31a4545da106b25ae79d59eb68f16ba9c4182709f9dcaaed75be3f231ef2e380266696fe9736d2dda1e330a7720aeb5edf65201448c3188a4883f8743a3cd0c28dbb207e78d84aa0b0e7b5b47202c904e2667417b53e93a2664dc713cbf1258b6dde6dacdda683e8107d47b51c5854d0a60bbbbc3acff8cfeaa8052031441d9d770ec6832cbe0f16e62cc95d59564e4192e868f7c42bfae22fbf43e4cbec844272f05e81c470d21023f75b983989690ea844756312816c3a6af0c6ee1e9ef67aa07f45c20e36c3fa1e6983e33fb0fdf4dd5a4471f1e7b5fb90d89e2c78415b0900eb7ae7e54916531f6cd90c72f7d29f8f3f6994b3056f783394a1a1a3f0eb221b530c5b25682b10f71106f42591e8918ea304645b439123d0476f7824f5563af65e24625ab35c5196415a5d7288eb3b0b8fb63f4944409ac99d52510effe1a2b518e1c687a358f07c726d2f725d0dd874781ef22ca6663c760495f85195febb65c07850cac18369c856ad1f36435713e21136508d6ef62707d1d158e0c96307504750b9ae24ea474f69c93b72e89318c5def2a5a74888680e24432d250cb3195f2f2a169026d831bef70e927201adcb77e3ac74624b4e3f0679cbd5ef10724287bff9b21b6096870b3a36a327ad4f8ddee5eb72e233f49cfa2002f957c427b95c5035abaf67db92358170431f1b75af2cf2d6fd8dfa286f37b6b9d24dce14c01de465e53bcf4b50eb506b9b07f0e1fa16cfbb0301e421d86bdf9249375edffbdab220b27c38f5ac2962de1f727fd8c01fe2325ec70f369da02666abd6ba5bc9c6a1bf07e6075d9dce5401f67261109fde7aabfb22a61f8047e4296cee16e0d0345418a9c94e1fa5e4dfe78a722589e774a414997bfaa4664f08af253c8fc451adabd7eb0420dfe78f527c9c72cf0350f440b6aa7b7c370e9f5fc45a993cc657891ed21e03e77ca8708357570c373335fca203ef9ace1b99f7f4f14459defd98bb02680c0c79fe57fae552f02f670dfffd9945cf9e8d4a10e9a691226d40621ba7f46b0ad0ffaba61af3f3217236351e6aae5a818b116f525598d91d527986aaa6413bf552e8976b5effd9cb29b1e8b1f5784975d524ed9d3e4e0197afeb36828db6635fed35ac6340629a20728d84642a6a4877e802c4c48db0b684604d23944642f7973e107dc4fa21009272d0ad1259a732a4af75c18a95324b4784c08724ce791c2fd05a7eabb19491dc000e218e5756d0c010febee55b46a7f722514c91457156eaea48570c42b551ed72ee910315056d3ad382d332daf0aa517dc9ece8b1edc41daaa3821426b18e687200d9af24df7c317d8c4f2a69b82187e1805c8a6e932393b3ed7634cbcb0cbc0f4b880fe39651490dce8178bfe184ea08ffbb60b68e73a897ef4d68da9a928c2a7438ab2051b699c9d30c60a52423b06e81e0bb2d81d03f497b5d01c1ed0316726b5577a2f98f142caa9ba55742fa36b4ec7b91f9ef776bcc27e5d0c878f62e72088da7cf624708fc93d8bc869af88fe3e025ca6359b0b5aa2e568b3d8d9cb22185249beb007a086c625519df41bb0f00eb9c95c5b556c21cfd1a7128e4094572e83db2c4643e388f4cd8c165a0e243b7ccc0f537deb657f3837f4c2ca9fc6d035724b05042c44cd11da8aaa44c285351069b514ba8783c5a77c49f72a10e707252bf4217ac5ac7a095ac6d2200b239118ce3e83680eeb70f7cd5ab67dc3b5e32ae80461f68f9e8df278d1224506aa9e6e0558a4672ff159111997b4bf13bfd72c4876df7ab663155a591e2733fb8851b805ee07da820e60de26324e9f4344072cbf711a56908991ff1b16b0e7010f03d1378ec42f54d0cbd9f1ae7a59d0ce067c9342b40bf0a3a726869b205a7a5d1818b0570ef4f3956896e9d5206c4e20500971aabe5f4d396de35e018a6604fb6b86060300497f0394a90a7a6fe4df7f330a54bb1694f70da389fb10547ad6d268a49df05e85bf57ba5d32dad5d5ba645119ebdb9ef30cd8729f8cb45cf22c3a8c7115a76292e63ebe325100d70d0770f258be27ff631cf9dca4131d849aca59304f6ae68d2a35ccd83590efc44a0b30f13f94db6a4f7a7d6692c068c46f8fc4025d1091636ae34e1f47374406292e1c672f0106861c8dd3f30dc5c2ae768128c52cba8d2a2613f907231d45c9075d02a7292b0bc081312ef7a4b0fe142f87c15db8632fbddcad94d0c52a435170bd5dd729879abfe808885d3025c8594fe78a0176912027dafb298748b57f7072bc1c8720c0bcbcc0fca2ac00fe83714e29f7e21d8a837c1d2e3eea0e779db000c0db71ad3072eac7d395c6200cd365d391a61f02459e017a40c1e69a81a82fb743a7f72dafd1d30fc7d2e3b318ff0a7b4e6fdfdaafb3d396bd5eccbfc6a3c57fea76672ed473004fbf3db75ee0805f06e6cb1b6362a72b43f05e1fe13f0f6dce6cb5c1321a0a35290d5fc204d741e8b4d28d4cd1f102436f5b1fb5815f21a1441ec5639d04e1bff8fc193482219a7b4b2fab8833fbd3529742543d39b71f8a58b9dd4028291a0d68d60c5ec91bad88e0da6b812d06f3d76815bc699272dbb3baa740a7290afde4abb927e48f97d05ca3db2765e3c5d51088b7aa4afa03772085baff93fdad59aefceedad80211fe9fa121c0eb81c8781c22aacc9395d29623c4999484f3d81eeef5b944e9bc75a3dda7270e6b5f11603199a5933bfabeff31f266f9272c3e4df33aa92eef925fdc126fdc3a13b9919728cf6cf23d950469e677cf5a472dc7ad3e825944828d7741a78204aba993cdf34143c51aa19f58aa1e02fa13a724a4a1e65f95065c9bd9f1689f57510a3d6f07a13014dfe218a8087a65441e81198d17f5ab8e04ea9ec99bb9538463d74159195e763a3c4a4567aadb3c07c6d72a296336b5820fbc7509c5f8cf661c61c3d2820b74272180ac3a1db9d478611721718167aaaa878250871cce19239db0ac0144ebef03e3ea5e53a852e1fcedb720a25c50a954dd7125991f44b887c99c8b59e900a31253193566d7914a0dca372ba44cd79089157a81030c8beb72752ab3d5950c74691b4684bc0b07d5cdf7c1afad267b268ffecb253e77859357f97cce0c5fd905b6e5e7993884e05ce0de972ac702236d90e06804dfb7a8c5c356eb69ce41aeb0c6ddadb4368b7b065350413bf19246dbaa694572a0c219f22e83f8d552ab2d62f8834cd85ed856442982e725c9f707d98da41e73a2d0d7a9276d2ae9a25f1af3f39eb3c18e1b2086896af72f16bab25e0302af1e27f1fa9db50c1f4e6db6b2864a4acede93b17cd97f60a56ac858f3e8ee9e700a856e296e8c04488ccbd26b09ccb0917d3393475d199c77276ef848b6ac4ccbb0afa2f9ccebb720ac79617773157c3fa8602da6ac07901721fc56ed01a60b4cca27850bb4c3ee82193bf5831af1ffb3747d98817ae9c666bff1cc675a1ce945fd2742e99acc771eafa247646daa669965b76b10656fe237216100d446c73eb8de2f232c277c20429b66c5acac03fcf8061903aa88662ca5dc0334c7c93c536e8be18f2efcafd8000eb48927957b18d11b33f37b355f82f728ade024277d16e52c4caa4b5f177dc8a7a6fdd7ce28284427e6ed2f6f01460729704c9fc623682d044be3397c1509889ba2545d565dd0f2094d4c01bd1cb7372541ec582da3e3a869e79a3c6fbae80d056cc98d10726168a9ef1e164c43d7b721fefe2b3798cab7fc267d2353fe9fc4a0fb8822df1c310e5eb2e3a129383e05b27cc88fdb484e47b47c1c995e6b8349f6647022b7ab692e3a7a869a6a5655a6c8d4e8a82f414d9d5768ab6ad9c364b29108cfebd3410b075926355021a5dd069e6f57eae232801b4b554c745f7177f28f658e7480360b13e7e798af0234a217282d23dd38e1d8a3b6ab34686e62ecce15995fbb1da9ba22238321280ca90a372d8eaf3e9a0ad8f2305e44f5315825ffb91deaea3381795761bd9508ba280af72c35ec8b3347cd2f19ef1c691efc27942160ef7b0fed83773a0b48054ade9c2588353dd47ecd14f0407cb0d3da52ccdc6926dc4445fce04bcb5f42efbbd62e67280305dd166549787c172c61a071ecc65e2071101c96124f29b22730146fdea433d4e3000b0f7be92d2c44590248d41387a6f44d64d6759286b0b0f276db18172110a8d90de78c100f77b67e019167603248f71614b3a99d0ad21a275651022261839453aeda5e21e41e7bdcd32b3d3e460ff8971e2e13126799b3076f8b12f7265bf4ed43efadff16d7d79b70f41abffb00cd37ecf219e11f36d7e08288253722a4fc40272efc2f31183a6cde16859661add94cbd75431d4e62e501896decd72f49e97232f3802c3725f749e4c1c4b22402358a3a2c8994706d4d24a8d9c0772d017463bec51f636c0bf404ff9237b4c92761b7dd650a3190b86fb3a9788ec236ddad4450d3ad951b4f928c93590943ebace098ae0d8fc6f62dff2c109c8e53ee290faf1a12e1b2871be3ccb6f9f20653bf50daa7ce4408bdaaefdec7e46e6725cb0335844b38162cba1b5685bdea8ad92500e9fd66efc72a40394a643051b729ddaa5d0d441b7d5fd25aeb9025064a35faff06753d75a575ab345be0d4f66728990657363a084b6c84b8098285ad2ab1008a35eb12d0057bc3b051e9a69d10b07719bc4ad6b33646f5949a4e83e7e4edf7484c40243b1fa6432703c8bf3ed72117789a33a95bc6791c09c1cb2934f21af97f0d672055d13e2887144982f5533b784225af71acda060ebe6a596990c10f9bc2fa998bd526d7cd011d2bfb1b957191a9518eee94971476d1415df6208c768a8e097761519e20cbfaf292e6b64728cf717d09022058b6511f9346f87223369d65841f0889dda989fb7a338579a72147a8c48661354f155ee261ae7cfda0d12b290e1213b7458613b310b5171957263247193197da2d12b14c93e3295fe1889ed3ce9d24ca85914fe94cbbe437f2d9c38d28ba107d4c75d0183d20fd684288bbee8d139028c231458382c2c4f7737d8be77a0b84bfc42aa4496267de84ec54d7088368162579cdf8d9baa27aa2c725ca1509c559db25a8ea2f9a8dbc80dd40562fb7dde56a3344bbcb445681e7672abdb4bcc7c043d1bb962ffe3d3c3063cee8b722e3bd3d1025400db86636b1b72d0c7c05d96e70794b937e0cf4e07d4ebdcb7c5d0f4fcd8cd4eb472e2172c9e15502aab5459d1b45e6592f5aa2eff94da9667dd069faa13803cd0a23c7b723e72e4b349593634444b8d170419abce118030799f3308009df2cb601aad90796372781e3801a5c986faa0c5a4691aa9511bff18a7da2ccce7c3c1b44f4f3c9606727f0c6d7b90f76b8deacfd9a410c785cd197f00abf5d54e7eeb17fd9927a5fe0254aae962af1b0bf5b9756a1d4c6e1d02c04c0ed1e5523dbee8e52df8515cd64869d0f8c6919c602a30a24db5c69bb8379e31dc37ffb6842c1ffe437278a2fc72843b7b871cea25e6926d2f8e3c0399bdf92e8d06c0d9235f83348288f6ddbe0290fc0020ab1fe5d630a1638003cdf860b302418ac295880c8134041a58a7573e55aebe4df5c4516ae35f9e6351d77cc07a8463f09de5e0db67624a329a72b472a27142b293b06d431d5c6d8c65a8c2b7caf641571469452c4daaa18c00f7524eb625c5e8cf23919f78f4bb62a1eedc1617ce0501550bb1badf13398b23c78f724620f91a34275a3a7f2a0477296d786306e233bdeba98fd5cbd95feb8ea52b60e658776cfeef77ebf16e63d3519cfd244253c06a90744a63a266e43ef7623e3a071a8caed12530e71e64673c6d127937afc62a1a633619017cb95c882421d919a242f15f1a84be9002212c42a3b717141b28debc8f08bc2d2d94067bc9a8427221b26f2704a2eff758ade41d395d962286a31b5c0d5e2949be89fbbff62fa102c30c63d93ff39bb1708722f73e580176790a9bba09428b0ee11a68765f84e872c9e757871f0342ab2d70fff31a6624adc05f92154ea7545def2a4b8faf1bd52ffb15bede8a5fdd1aba500c1b3cb7e59af68eacc1fa2c29156598027084244b410f78f460660a193a742e9e1a4223c9e04ba5f37dbeeece4b9336e7e90d5b70071f1dbecb1cac45f0c376305ce6d0e1d499628101975fb08874a6556701cc4572a94bc5fb7234e24861525dfae7aaa782d4f4fcbd8e48c8551d7c1b8dd3149e331f00b3560d0df23b0262cbdff72a09a1e7abb0b65b47276d669f647d72d3205309a07aafb926aad9936aef2bb49cb8b7885146d991b1d9037f8db5c849bc1672c290e1453fdd008b04c4be3cca31c9a4299c2ad3cce21a281b2d271cbeba2d54a28c932e5722f4b780a0efa41139178a9122a639b085b36161d54b0764054439525fc0495430ed8212ba577d6ec7769d061e18639bcb99dfcc1925db1c4fd5729baf59730c4ffa4ca5bcfc294254dbb70ff18215b05c30b4e96f01c3a0c54d7235aeaa3440d7bcc39592d9d64ebf27f93e44f331367ea57d3776c2240e602d4d7adacec0f8ab2b06e2e28336364768314427012ad2b4e7089c462da8cad6d909822b55a842e013b26590f9c637de94d6ae17c401af4d83a8834be6e6032a7e47a739362ebfb21b8ed3345ce9792f4caa3d0bfaeafd1ab28e8ef25fe010e2530049a5f138c48cfcf0fd6b849af2293ae3d8047d391f2807763e62a33c2d17bb7211e4fbe9e5ec3e8db817bb6ec361ad0c5a922caaf05583e67632f40eccccbb72c35c1c8200ba5cd3c64d8f005b1b7ddc3b4dc9ffa67a90f418a8f28f3f737e2a208ff407f6de90f025b49a22691c5b3e75c34cdbf3265b30358f5acc9ef8e60bc9eb9a111b5307beb741b095937bbdf327bdb6bb1abc10800b6a89e5da4a7d222c679ae5344272c6ab88a7a90d7cbd4eaa3db52db8540ed23b2b087980bb3b72cf49ed26e94fc777a81ef5163b8b378565f7c77d351af7879a8094c7067b136a1fd84f92b4f7b3582e25bb316f92ee918151efeda253d5aae5e962350dd4767290903f58476c00acb7ed663d1246938b8072db76709ff84c069d3af16622c555ec2f7a904e6e3aa11b707de3128b8921a8a1fa03427d95d9d980cc31615ca172eb92c84b3d37336f9dd95038818b0a959d1cb986616991195522b0c776fd381b7c1a74b11a7d261b7734c3fa32350406713508e924c018b4a81b9d4c57df0d724e1c5ecce261f7a583664bd9b27dcea0cb9f36f0ab1ebd79f4cc8434b3a4c5729d14121e4d0ea5c3aaaa9f085905ed9ecd3ecd7d29e11be987049401d5edc47239d9209ccc2c6ab240b57a9624138e33b971b8dd0980ca367593e2d47b166672fc790d5a430ed70a90be589a71ecaff6e3c4e2ab23d64309062b8c2f219b32389817b150ec526eeb6cec7d8be6aa96c8190417243041464e675f9079c390a97270d544ad1e78d78127e01f2b625da734da409c0c8e119d2e3e17b379e0d33a72154a537844e51a03239e6ec639d5445ae07d5610b850faa84875e9534a8cf5724da2fe2bdb44005f45235169670854fb3b2d5bba793e4f8d814b0b41df046931cdb2cfb1c5ad1c935259bb2d100d166facf54641d70b62e14659deef1257103cb40b70aee49da04b2042fed286b827714e23b8640e8046bde0305a8b7f98de2724cb2aa5aed851e23cbe16c97742e55e990f3a71446c154b42479dea86354872d17b33ebca86079423c94301d9ff5fdb4e977d7312c3726e467eef9218667772c7d589de4c41a76c2d7b3cfa74864367a8500bd152c8823a1ad501ee5bc53e509fff32f6a1668ef0ef42bcbb196da38d8d97a372f7d3778fc5d4ed5c99053a18c7d818f12da99c2a009ab15451087a57600c278e9848825d4c41d436da9fef722d00f51737a8b6017c64609508c4cf0cad8437a2a2d73b69453c89c55a27c8722ad540665af48156609f2a4032f619922ba11a62a4f2b9184e53d38760dffc72f06691e0a0510deb032dcd80e81fe871c83fa85c8d9bc928664ddd65cc3a0f1993cf17a512eb69b46f613fad903ddffd2d5b1f64634c265b3d2c1dcc7821c872932cbb97b0f4f4b4374c1769af50aac7cd313a9a226383a9d1267e5681cabe2f906e05b75abbadb97e804dcafccf15b5548e62279a8932b60c34cf1db7df454b67db66e5cd7b8dd4c2c9b01c967c1480ae8c0419bcd09956cea34a0c9fcefc0b3f25175e2ac8fcbb4b066d439ac2bf3f2fd6316b61e3ef661b766b7386379672b8e00b4c54bad9bebdab8e04559f1435580ce1050043a3018aae0eaf1a227832c59cdc4d10750495f65e37874dff42c8ac32e64f9a4d995950a9fb1074652272e1cebe5dde8e5f890d68e0ce919b2336b77310e403f2e02d8e02de729940f0510c75edbf89d40bcc31560c87fd183639659f9e758e5636e1f388838edd2fe611c1ae1ff16b0cc51d05a2be7269c2bc2ba605a2a48780090310db40177140c221469ae3056f56c95a2da7c9e4f04aa79743d06931aa7122988aca9cbb3cb723722b4444a25eb31934f5f5635bb8a2eac7db4f1e81c704b85d26c7ed7027760c5ad5a3aa39ff03601a73d6f6213b3ef3674ce7f2a8340512b3d3e91e23c00a8672174fc683efdad67441fd8e085a16363c7d29a10a26323fa97e60e23319553d398f1751fb842dab434493c72a33fc7afafdb13c9c2dbbef6d308e731885190b72700d721208ea2f59323628e13301bd844a2df1213bbafe8d2b7637b01d8788724f527ff16a639a217617a06ae3e3bbdb7118a4ab2ef7d42e43703509cda69872266b4d163529f061a6c8b4d3d29a970d73bd63d641d96461803ec91ef65f2172b8a3c36e446ccf646afc915711fe23ad6eee421cbb6db02732bb399b5d89b1729c208e947dfc65e823960d6079a31b12424edaafb90062fba1b0391eda34a672d191601419db1976f9fef783cb68bb760b721c9937f440731118c85e9639d85709f69be9bd6250742dd3b367320693b4f417165593d70ac7f987352f7097bb72a579e3b9caf7fb410fb53e9a11660d734dbb026fc9b8524711563b03746dc57247df0efbed4179e6707deff56cc82fe6aad984819e9680c7fbaecd9893aee521aff7a0abebd00776dfb09c1e77fcdd8f294973a4c7c348a7da0bccb9e37a386be932d558473e909b851f43f8d82310f6599760900655d9451c1ee0f4f3962e13f2522993f138de5b5beffd7d676be5119c310dcc682732691e1ee3be9b2add724d085791eb9901a50f07894025a69ff861294f8eb3f95c629842de0d28b82572670c4af755cd2c02c680c84cd7a28531946c99e31fb0b8415527eefc83248139eec41facbb6c9ed6b572e21e0a88b94425ae76f005144d83659a8e5add7b10241e017a04d5916974d7288d916a6c80f4c794b09500c9a3ab95bd6f3373969f72e11792f6d2a8a72fa20ae92259545a6ed439db9a9fbb04d5d566021274380072422a23713fac68459c0f228cbf893d2b1b5108eb6105f898ae8c0a8d6d8d3372426dcdf09620b147da986c170fa07233e3b817eb4033b05e7c10c403feee33721d41bdf0470704e3fbbb6429e841f77b159ba54de5f8a4e9bf20be10834b9a7273c4616478e17ab21f5a7274fc4f83c75ea0a97782ef84f5624af58f3a78de04af333e914ff7c44ab0bcfb70e4146670ff569fd83b8d99df34f2da8b546d380c06e59bd19dbd68c8432c5b38a099d1c0f5856d01eb1f0ee41e7634a28db46f561e47b6ce4262d39045e34c772033a72aff002da943ab8c2f59d895119ab17b723b7116516b34babe72607076378d3b0deb657eaa2fe29b42c8e567269b44bc29c7878f6535a378bb8aa68ef8f40e71928bd78368f7cb482d16aac01c1a7ea37285fabf87b0195b7035aa482ade67736a636fcfeb0803cde817cae2757d37dd72fb2bb13543bf4ef5b76b8dace4fec64a9609f6b40ea1ca8c627b6651312910618c68590af01a2bc5c2972ea5a9228c997097cece54891e87c2bceb83fce18e6ab00e202606bddd4d064d7446ef01e8622595fbe2800e784fc2150164c9320772611bb2518df55840d6ffb0d4349d11fb7c740fe003e3179e366675e4e02a9c72b391307ca292ef678caacc8ced79a9580927f4a91043d1f95b7bca80a9df98725f7b7897a375860fa06c9b477b9d24be1c40ac5a25c7f59fdae9b0e9423d64045caf8fced76c5b62e98c5ac4f8247aff8e6c13edbb5bc9627877269ffea75e72c1e29efef63855995f2f25c55e7e443b3e1cc9921eefcf49d49178477ffe59161aaec28084305ecfd26992bd290f35b51c647d8880af461278cb128b09798123b39017e4a4dee09119da191a29841670aa0ed538d55d1fae94c9d78bc8d0677248c9b098f9aa93c471f3a05a6b242c6f48b181218d0e5071e21c04bad731465a7deb925e76c769708d462854e653bbc948555d2a3742ab129bcf2cf6d39b8230a5a3ec435b60e6e7bd013945e07a9286fa7b968a6e1205c12820a1820c1c8445f8ef4df8f03e8fd56d6cb5a51bb6c99787ccbd455f7164e822f142959f52cf1b67935a7cf66d606384215592b1b4ebfe9b7dee21aa4f3805ae2099a96d9af819c5b596516d495881e8178a23e9c27a60b831e75fdb06ae64c45573e7710278728dc4767b78a091abc9de8d3ed1c180d46104a30160c1e75361165221856bf671975e6ef4a8c740c102cd437615eeb96b984e0c1b7e65038e23514806be951b3fabdfc4c2386b95e2c3e11cca37f6c343eb20c761a9456aecbfda763f24a6cb614fe14f90e2f28d1d7d0e6c1eeb0effcab91926a5397bda75efc0266764406572948a21bde3ad010d5945bf7bb744edaffd55bc407f0671c1a9d41f7d3d717d723b59560c39cf4c6cab80d6c836e9657de1f2577e68e31d27db42a27c52518272813895d67cba8d4a6794db7f6a643cf02828c2ae52fa974d888ece2d39319d72e217e777669612e51140b1dcb6ecf1a4e29493b1fc73b373e4e00cc08b7e2b2ba4cd17377d8d493e98c20bf69e0dd83e1ce73c40e3b59676e9c56afc0c8213723a904547b0a1c73e5c4f6b3961dd63362ab5b03426b84c4e1365b872fbf7c572a68eae6872462c95f6bd10397d8fea69374b88c1e172fd2c3952a4453eb376726e5060e946461c8991af64102f3ae814e371d832d40eefaffc6261f544789037ccfb41263787057bc7163d60d123fe9db43c2515dc4f913f3d7424ed09bf67027ba2ed09b7e743f6e42d86c99cfbf9d4eab557f8484f822367e684a7533fb66a6442bd26819ec9e173e4a6b84f336de041cb4bfe71883be57a52b0a19c471c722d001d3eb632ef7d9a2cf40670e12ca2f29a3351129c999d3902de8ca8fd0d72b9cf0e950a9b2bb9dc3a315c1fd63e383aeca93e45ee93fa8ca4cdd919b095725cb7ba8e68143e92355a16be60c959d5cc8a17d7203c0fb18887ce5b5cc63872f1ac1e2d5d082f7c50e9d28fb3a223a42eb6b59130bd2260799e9d64f50d2613b4ec678a380af2791de1ca3ec4ef9e9ed8e68f4b04cb4cdb493f0e7d18a75e72e0afdda760469ebd1ce6b2783d4fc74acbb88ec160b689885230231455b0f859f59e05b073d9045769ed0c9b14c39ea1b0322d1b9fb84ed16dfe4e5c4069a023e81af7c208d4618493740598d4a3e54b266668c7a7a743073869c9efd14be572a65b06aa5d3040363009d63e98f68abd1fe5c3a5b15ba183dd1135cc1942ea720f09ac9a5e9b27957c688f803aae0261c5b8b3b5ab017e00a804068be5c76527bf52d75f959fe60a8aa2aaa77eebcfe6fa8d1970232fd62229908201bf58fc727e3545c1592ff1882f052a7e204564405b408940ecfae59fe5239469e9da373424b7d328fe71ca3197f10f0e5e78abd90e50591df876f2e5bdd6a9e0c1c9a5058aa9774cb58107b0a0392b8c8bda555a993a6c8306fc83ef48e19f85b95f0e7269b6d237ef9bc93d9e716c1b246d9a62491b0caed91e8435279f64fe0167b672810c52c078eccb2d9d71288a8028bf8edb338ac02fd841fb36ae123be514c21a9587f9ea9537c33ea92222965e0e21d56c4cd494b68302bd085a5671ff34250bb068b96e495558bd22f6e159c588d4ff53c5d7362e4201b43bb7e597f1cf8672a31eecedea7e52e2bb8dfdbc2c2d0e4542595bec60dedb38f725eafb0a53bf61abf807065fe3eaa88eab6a3b8418d62c558c3dd5de16768e54100a2232924f4a75d2b7fa416abeca1ba287d2765c3fcfa54c9ef033febf899696f714767ed81a7ae6880bb604f0a761e6eac0ec0d09edc4a48f50f5568c44f50edce9d5826272ed27ba3188a703bd88dee4581b49019cb05623691a8d5cfdbc56c598a907d300a32e7d8ad980421b07c05bbcae6a4a0b76f0ebc3dd3e45ec08df4d5b47ff7649a6958bd8b596f461336fc400ccf357f4a402319a49eae416c15e4dd384c2ec379c8cf058002040a09f1cd72020e0d12ddff41a41b356c571ecd8080e28119e305e91725ecdccd511ba3bf79e78499b3e97df51d22ddfc29f58d64c671d44df727a7acbba0af7d67eaea938ec82aff9abe91137c137bbbeb313055c2922bef664ee0e6143b510596c00c59dd5a92cb2730e3716f5c27c6b966f7c1518a065297252bfc427c664f487bdf704f468747fcee011c98e3b5e5a1b3e1cac875cc1c2728d9aa10647d744e87adcaf33dc8bf3cb2cb784f68ac3abfb66f37e3eef268172cd280bbe9dae92a18eb6e92632ea4b03f1041d621189b7d9cc64d8f4981203460914edd1b0c007b026cfe7aee22054e87496ea2658fc8f424ad39c1813cbf50c1ee1bb2d0ccc944dda6475f8e1184646335d769132a69d37ebf74daf833462660357c3a3bdcc90b7d816424ad88b42c79dfcb3caf739b4b9f9bddf542988211ac752fb64df0bf78146c06292af391f432d5a0601833feb75df8c62e5bd83ce726ada5a7fcf0b91b414e92a63aa2a5731b33accf4c8899cfa1ba0800df96b3f729305e0acf70f2e6d76c99e3059d59fceec74ba9ca0c2992e07ebc8010e87277274a544fdcd94567d3f3bad95374872d1d2a63bfe4e2668353810dd4eff6fa6724fb10bc4458c3da3a8294b8f081ca149098f844fa9503a83db14eef598f1f872cac5a67f7e6a97cda80703e133aef7cee04d438babafc0b1305bfc744074dc7299d616c662074e38255c04cb2f78304531f64c243507c3e3bb8e13528c414f5f1c3b2855da46986d6a1fdf1365c27e2626f068a593658d890cb4fa23a9683f72df86c5e454da7322f38a484941a02897bd3d674c29381854274050aa0e7f9d721b144bce747dff4c520884a5059bc3072529f48719e0733a4a853a0579be6058faf2e05b72da607936606e206d78afe07698e337e72f9bafb34a405c645598720b5c1e8241db52b84af5e1bcba8dcde6bab2275302f07a744175a68c5379e4725bc44e5600b07f326491310c2bc77da924928eb6c81a7910c3227cd79223df577f78445fb111c626f8ff1831b6fc360f456ec1f7301c3dc32468a665776ba52d61437cc8c663e8bbaaa328806e29744e1b32b34f2b4102aefdfb742d3f11b0228723de2e93120d14f7653c8f1066c1001b61a66d99e74a4af9ab01fb6c1cf572c2e15dd5db195d2d94a74f8078124d204345a114e8024e36654f1a26285c0b72ad21d761032f75a948a80313a8efaf2ee7c39819df84cc43ac003270a6be2d4dd9053d1bac1e7c35079757f981b1dca85a76ecd7a5340d90e4d2ff784618d172b8d640bb4ce92a33d96553eac5cd88fbafd3d054fdee849ba0c66a9db277db729cd298e6691b04de68db3782a681308234254f1fb98f362558595d05efbba67203abd2e3e854ded68640c58cea4d8b3b3d685a85ec58a0a119a0a8053ce450222b46861a91b3bc525770483a15b81ba301b5db04bdc0ed82c1a60172eb8b9e30b2d481da7a1a45a47b75dc6c2873a55d858479515c60fb8c05117bfe8ba13b369362d87338caa142fc88660393609b915b79f47eafeb1d330b4e91e0e9809272339a56c20bd0963bd8920db1de1b1eeead8e7ff2a960604dea4a292ca1251e66191fa52d9e34247f36a91dbe3aca504a3a4cfede08146b1b7a072265cfd7ad7265998fc61e8a5dc2a3db5219b0bfe2f17f66798e73852726dbcfc083f0fb74727510a827981464bc4f7bc21e8897746555129e9d5f25f6af86976facb2dfe1729dbc86ed2e5556bf346798e0ca009d66bc7da85360379bf71ee2e8415fd10b0331289141efbdb4d2c46b692417fb5b0209f0759200c4569aa19c533d9ee1a565e0d012f5bc4b060da6fd6bf1d8cd9e23accf4d99d21336d0a301e7a2fe4c4872d8c2bb4f2367188eafa5d253d0f7421de6292cf3452dc48b776b967bd4826c722bedd5bd62237ccd395b3e05a318ef77892f205162a08f7a74750641cd18ca7216a352b79c87f0b05c7e22b0dfb5fd39e845c97e47652c77812ab4c7717d5f72bf15463da2addd262c400b579585a05a6c7e03ab823b2e946a09ad26966ebc721eed9abfbd5c46cb7f747b2f85b7a8caebb27529cdd4a28917a7557127177c469bd00df2852f16c2162ce3c49900c9795b58548f542f2fe428b02c5cf00ff1724824c4f83f9d7b83754192c745c46906f75e8bb9e1beeaa44df882ff6d6eed72c987a3b1a893fcf784440c0c31c54e4db86136fef1a45227f2dbb53f2b3eb21010be96ff5d479d573c782e19375b96de7245c89d28ca55a80c34b170de262872b387757dfa98af81b641641327dd95739a31770847a72bddacf032db77c7785b13fcdeb26000c297f747e1a53c82ee28d8eb647246d88a1633a6ee2b2384a32b6fd7eb39a5c79211e6b126e5c089041f874d5968df1a36048ca91d5249e1826feb2dff2f0125dd39be082fde8e7588f9ee60cdef06262bcfbcb14daacfc25f458ebc47610fdfe3f19d4c7cd280c64399cd5e90630489c0025d5ba12748e94d344e045abfb2d366f88433b52bce174857f6e15e462dfef2ac45da1449737c1072229c9f2790a7f03b8f05a982a1bb7350f8849de933b1d6d6cbbeda01bdd542616c6363fc25f69bbe8376e1542de00bbbd002f8c0fa2316f9151d57e12e55ab64b0b004b78093145f9a9b02055ff1cbfff9af22bd9f7a1d4e9b830b57987d0c721332f86d2d97e8df667923750a6920a4a7c7d195bff3760e19bbee6c5b70771eb24539cab030b2a0cc8fa35309377ce6a4e5cbe9af5e7a7da631fab7ecf45868e4df93453f5009f912a4edb676f0688898a3f965ef501be5b776a1de0ff975503799074940d2171615753e94a0b8478d71fd9635d7dd7522da6ba3dc76eb4d4a0a36613337c691002df7f50f176ef136a985b195132e5f1dbbfb26f6085bfa72f7fb5fa8f3addade199192673a6c8179c29e7f360f291c5b559a8ea4251c7672ab96c1242628b0a012b2f6c49e0f98b135e561ed5ea4d1cf37f5a84fa326d072e77f5283bc68f174271a712e1e19aa638801159955369c7557472dfb72097e7296a491918be62bf3740cd9d6895714c2651a5b0c377b9077b507dee040d0b7236598b75f409643524168bba4e87e0d05ee7c7c476cca90d426f97990a66d475e523e0553e35bf4e4f23be556cd02446ab479722407fbfa5dcd3e29f61ac216724b0820f6935baaa3774c1d8e0f2732f94bfd0ff2027cadfe290ba31fc6c11a724166c82dd7c4f95e367d8457b0bb9b892ea195bdac9b82cf5b44168af6c6d272080d03e19fe4286f7d45ae755e45b4f8ff5164158050c913f53115732a7726725449012c2e0fe459c3397d8b418f871acf941a4423eb7e85dbd03b4df36c3172492b082e5ad467a69e6a72ed11d4a2549c07564b7fd0ea2486c04717e790aa49a87e41f430132a7d22492ebb79071d920cf15cefefec850f32cad036ecffa472cd1d09def1b6aa72cbe095a14294748590eb1f1d4fcadb2d572455344eeb1b72f937dc79a88223281ae7f5b7a913504360ab726f962b2484f761fa9263b00769b178b9f88c2c1650faf0d607b3e3f98e0ef8ec8e5149f0d5f43d3e7aefe6713edf26e4fff3b9f20f11be32b970d24aa45fac67a8970a3dc9d70f157384b7ce4d78ae56b0da79c4a118b4e2a74ce080106e213119f4e2c5cff91cde205aa9db7269a34c83115fc115c197c8b2849ff660a07b313e6dacbf80cdb72e901bc3d35fc0fc7f376a15a8c3106e9af78a22f73d68507df0a361c713dcee714410e74672fb077ba220370213c6d23c82053cd657bfa9efe3f53e87527b87858552e2a572d8707a384bfa72ab9831d1cf89c13a3576a6c37ed698851b7b319e5e2862914b4cc9e87600034dfde7efc54db06fedb038f7e9ada5c588134436c332acf196231737afe1bfa2d20bdb1760b602ea3fa3fb00e027bea3c72368f25ab631f271697798e3402c064198ba5bceb3c19190924fbe989085c4bcc95250a27fe45b0a3f94ae15921aabda45ed29e69e28e9fc6d8840b6d0299399c6327454f04e9e6e654013a358895c4d92d37406d2b98bbf08a45b0944f5be687494fc20abb5b8ca721da78df9577bf5e6798881102ade3c050cd962faf4bc9b1772fad90cf795113429aad648cc94aa206c69712984ab2f6afa7064d64b27f2a9c652162c89f70e0874dc6c8b99d6b42808534cfdccc6fde199c6a0a6c8ba8575e86342a0bd707e6cc5d801b982e40478436bfc334d2a5e54e9d4c2af62a539ef76a0369ac665e047028af43ebf4988d8951cbe02da14b86e154ef42e4abf6f8b9080ffa51a071272ecc76bfdcf3a257cbe0c8b84516055b965049a5d3fbc21168b9c4a6b167a6630a240df6011f5e9cd06e5f4e64eb728989e5f0cb5882859417be705c5c61d7172ce603528e6086575d009e5403a88a1cf6e48ea91bb2b79c7bcf2730e8f4a1750327f5421969a1fdfe128ed8aff82050e55f240f188a0902d4567670fd5e2c6724c181f658dddf58cbb59c9daf2846720f8f1c5ead8a6867effa17b384d23fe4677dd5befcffc5144eb33b94830a8545897f3f256221670efcb97c66327a79c305cf26d8a602ec5f8a12113abfb0aa8c6865fbb233b21db87a2030af6c87ebb1223d47cb95c6b8372e95ce61b685e818ac3425ed80ed2c5cbb82245bd8b98191d0b7e833232ff9817a64ae0af6fc1c1326467800735820b8da25ab5a5eeb53f4f08d39cf19c7eecf59fd036ce206664ed3fab37d2263fddb4da2c9429b52e32729b91bf15d0289d64bf4056d38e710f56863cc6a3679f099e97ba3b6442ee804d6dd20425257ec5dd5ec4b2f0e48dabbbb228658c4415635cca2d4be08d478a723f8383fbe879a53ed00d3d1ca410755f4d5ae4590cda75628ca5ca9b0d066d10b3c204e78093d995bb0b17db05306be818cd51d51e4ab4ac88bee4eeea45347262c004104c94ad7db6ee961d62c4cffc8bf5729e326ba45df95ecc6e2a31d13708a1b15c2e92f55a6b9351d6f7bd905c5c992a3684b77e7f7950d39347418e72856d356c9c6becf1e34dc752fe45ec706f37dc78aee9be8fe039956803f476498b83c110deee4dd1fbed412c000646507706d5a94596e2f714e95694d89c97188b4369e8471215699cf8d7ed2af59aeb237a0c4a90cd6087b34d836af069fa72c74e8e9053c4d2c38991c0290bff0c231bb0a1b334c750930f93995a25ac906b49907c4949050c74500649d62cfe93cffa112792cf29e7373b4d76a5b7ac0172ea39d24cce131111008345ed1cd58087e1d621b2ed9ec7a263bf67ee53ea7751092e953b0a5abac8b5d05938da9be52cde2d7305db3c54a6c94933d0e8b8e272c0708b424b9da52c600949b3303eff4bad37691bd27a08973e1f499afee759725b9bfaadf26d5e55a6246c9dada3605a4d39bf2e63d9f62d6356f1879415d7725a30e73e30a855f13da7e5348f19e64364c73fed8d49c70d53dd89ebd9e01414425bdb5fd38627a336916cee62f42cdfd4578e1d207eb5c945db4f1f7ef68a72ff7e42e8717faa0892c6441be5612589929e7cc466fe2a8aa4fd951be5348f27bb621275cdea587a0d8d88eead8c52797fc9dbda8a181c92ef76cd3a09b1d372b5e5d6dd1f1f7165220ab7a779cf5220decebdf89d4dfe96e0e6a9d40c962a6d8164588c9e7ff6af26cb86ddf73c7c7c32eb7f9172ff40990028ac1ac33b1672baca4093cebf1602ed5078feb4df58fc24feef3a0f788946c1eb9874d323b216125c316e85340344a44521611ac9fbfbee8c58f7b5d46f0d4d44ac0e8ca5c372cd33c943cb2166c3dabbd111b18b96820873f14e049d0bc53ff23683acaa4872e7c1a0274e25a57ea6afebe314e876491507dcfbc76a0472f0fcc6fdda6c2c4b259925eff272ed34187b7c4793d2d910c875664db49efd75083949f9e14b85726ecdd056334c4f6e64dc67aa11e016fc42566b24ec45535df2d6eae2137b6072ad2eab82bb359cebecb63911b82ecebc301cec3185cfcdb927610545f2fd3f7207289df448a84f549c9459a9c12ca3e458f3aaf219dfe05a0214bccf254c0e64a6e4205de3ab476966d184824829c7b9aa4a88a50b84bc484aacb0a4e7ebe964873307bac69255d26e9792d8cd2c34c8122443e75ba52839ae063e393ff9ae72fa5b667a0732eda6e9cddeb3bf6cc8203d666c3f7014578b3244d290eecf631094a8916b2e43362ab563968c01342a1550ede620eb5bda6c435599b473dcea72e46fd297fcb0b9ff18b56429a9d231f95ebfa786130a27fd145393cc534dce3b5a36a521ba672f60fa95cf549ef91f39d5f68842793eebaaa542fc70f70a95066a61acde34d1a0d80549dea9202e9f74fad9f9b3cab5b2992a2af69a59d74c72d7622fe4fff61627b5696351112b63c6e26ccb92a04a7eb774ec3531fe4541720c768d73d69fc78972b994aa649b2da8fe1e5f74f8a14435cf29a2157ee5875b57eab269a65df96085ff3a5d9c066cbfded331ad0b98dd59f167406e4b4be819147b2fea053c2f1916a076c56304094c6b27c890cfc182115477424e91f6c5113adb094e9725480d9f1ec257a47d13407c204f5089073ad2bef8a5a8602bb06bcf3b56cd484ba9196c00df7ed8600492e6be787c04a9d37ea653b53fd8c46e72e3d0dc3ece64321059df3e4c16292cef5ee85e29d04ba0b53b2028da6326b9725772f87f91ac38c1b220c17413bc77a830f537a8297971bf3465b9f374128872c2ad9348d29018876463e053d0b0aa83fd92c4f3290c8f0bb65b73ce9d4c9a1e1eece71f1a5cc1ba016de79365e2f3a79c12368ac05a7c7d8ad82ce27115b672e234fbf3cd184b5116c3062e720964c11e17e55be980b6e7cc1a2bb9811e62724e5594a9fc09ded73d3cd69bec9943176348ae8280900bd5eaea8b30dc6ebe26508a8c412adcb05e2ee93195d216ec3100153ad8c5e7fd73e345362f3a6ca5726277f9ed7b600484de16188634ede7a39bb22165e27bcd264817ecf13e482c631072b51fd87c8e3627d0146dc940324c39a544857730ae940f65632b4d15f17224a74f773454e69f46c00b3e4890d5cdd0dbe9621387f1e2e5b7def048929a695c1b4c90c6bb0268fc7457f2e6788f51d2a801e8d8fad6fdcfabdbf209b8541af24a940be62aa79b0188c1df40e57bb06f02d017fb37cdd29e572c9803ad3c72d6f6e7759584ff353b7364c6903ff96b1f1df3363672fba803760a08a2d3dc57d90e3fb99d681e1116909b604cee91cfae58ddf05ce1a846403d7568d70d335bf9a5d18673109b1974febf6a31d87646bda101152de5dd0aaaa0fd5736c770363da9829610250de119748dc2bc07ec3e5a36155258f1107ac24e89f9d8de8872941adaa585d1fc345b57f824b6d97c97f2287800cd67e98dfcb800aaea1fe0344150697213fd1d9b7b846dec7520d8b77317e0011a0b1f356840766e94476d72d9fc10dd5d9607e0b8528b0969c57f9d96d17a417943033aa605c9ffdff20b72e8f3a5fe947f1bfcdf1a548017f947dd19d1f25592167740df4a59b1941f6172ac18607cf3d0b814f47fdd088434c4a8c3f0c68a04495666fd65ec0d6967e0330616dd54b470a89304929ca3e65b4c7602017f1f38fe5a77faf287b33ed0d411133c0d66aa307ba151a6655777a1de34bd4e134439c448733ab6917d1fad7a2ed88027e40f2f93d524a2dc8eeafc59789f49bae76acfac85b34ee60cdf42543867e6a7f794cd2e674b87f6315a26a22a5c34c6e94d3e886e22fd70edb590bd72ad19c62a305ac5218ea911db2fa26a3e1482fcb5932af9b656ab66bf37cb04727b01ba45c7bb9eade2629da87fbf13a8c2cdb6761711f16e186b49dc9152a3729a36184e231ca3d64302e1b69ccbbd85bc00b0bb6e38c0ea8fc2dc44f0d6d272a443b18af204328a5d775d3cc9eeb9a2591aca18e64663d5de5dc7d10688350417ea06437a3f1456c2de4193f59a7e32091a30daf2d092b697c0456363f7f572d5668706844b43e42f266d992f4ce3c08d4911f0e34995e89683feccd53bdc0c51e419271e7d80ce2fbeaa6a7efe9787deeb4145631406d868aa0ca170cc56713c6d6ad38ee095f089419d3ac5e6af9662cf72c9f707a6a1f34f6b3a7dfbb2198c7825f050d03545df4903cb86e719921a716ddc282757e994bc1f8bca85da18f65357baf0357263d74752f38b88e8b4179d07616c586a6c80141fd968bb81725375547f47dbe2752ad1b6801514d9fda8551713b9c1818534f3875f4d9d210cede08e91ecc166285711b0126448e4221fd7a00299732dbc62c7624cc9719f72ad9d315fa447ccfa441e614ac35d65f91b04ace97d01b7f6af538ae7c1d96225abaefce71be7c382414a1f4a725eff0d96e25db4454d93cfa4e5313235ede972a2cab9cdb7cbf55e94c8526fe5c36c76be1f8d6d6b83b2a3f2dc0d00b35e75721e481de7a2867b9e599ba680ebab021957bbb205966be8aaab7080bd9ec2d072023108fabacb2f807bd95f0d4fb8174ad488cf1cfc1fc5764ab03abd361f8272e418263180b290ab0a3a5fc2b4ecae5f2089d486f5f7162effcd16c4b6539472bdb71e1f24dd1209cbc278b0dced94693328e1e87f191bdfb5e3b59bbaaf7821be44c0a8eff72d7b7c21ca773f413fb0b5325c5a5630190845227d9a03df0913c98f2b4f266c6c76f2d1a9473b0ec3927f2e7b85dafb6d6f0d5dc2674ae28c729000d7a61dfa7ca3b6f932b36f82d94d9255cccb6cc816f06f78f559f987c172d441f36e066569a423e2272f0921dab51f4cfd840d8d3387f8f128206cdf92725179aae09af42667165b4e9d9b5f1829d466ae99f68367da6ae52f013d4fd572c74c9754b898464f48f452e993474964b83d5197f9fe80168182fa3d549e4d720c7a2dc32c193d193e34579035949cbefe4c13e4ff790c81d215cacc7bfc4e72dcc0fd67788f59df723249775f316eb89c242b1633e9c83fb32ae29506e7cd1169a67b443affb0511389c3c919fcfb928110b90aef4d28bbf2ad7f04f59a875caf8d87eec82ca2b1f3cd50f1e7ecc96e82748a06fc8b0c0e5e36bdd796fd1a221694b33377685309180bdcabf0b5b6d08430c3a0a4a4c2650d14f116eff6091ce2306be69635df76465040392bcbff816117069b620abaa8e261ce39742a22728c61b979d3ad4b023c67830c3b2a267103523f229552790fffdf7e72013a5b7209d157eeb82d75b089b63884acc7ede4bfab15d2b7db0426778bcf3e25edc67235988eaed2be0a5473abf5b9c2e17e2cea928e549de787bb613627db366b8b4ce3175dff1877048ef4aab74849a99d6bb547054ef1133600f4ef3ceb530acf6a532cf3b693ec4ffb318fea06eff2c82b76d64bb01819003f551cc9502f0ad61197c0469f213de81deefcf11523121814acb6cbcf5d0ad5e8b86e62a27f9db668aa318675b927020d2e3a33158bc43a2ef990e6350f0e0a84fb88e53dae534b72d3962912ef558b5c9107c61c6ef3a380c31ff1ad8e713949f2387d56d63c5a72f92a88efe5f1d099c1f05521f97162f3e2600ad1010011e42059c6f23bfe7d72aef27744848d109a8a906e02b273af5aff43a4ee8f368643a2e3debd78ff497250ca83adaeea49af0a7ee12467979bb1330b89a0c1ae6b4ad6b603131494cc16e69e95fb17ace9978e076f6ea60b63fd21b3a310b57fcfd0c7ea50607e89fe68f30f54e6eff76cbc77d9030d14c09985ba807dcba92011ec4485e72134bcbc72c4b506a0d18939397bda36cde6231d78038cdae94c55a8b802274cba1a684d72518c5ff5606f89f6b86f6e304aa03255908c7582ad5f74b58c088126589699508d539fbbdc393b6a3ec3abe75fce6200ba91b7aedf279f3ecdbdcc2e4d1f740c1d3b73eb6fd8cfa838a0a006ad000fecee5dc05d5ef88ca95de9fc7cade0e71adcdcb903a3f59dd57c6ea1c7c21dae6b8adbed0a570a4a391c4d7127423af6722913b3ae11f460baf61d6356cb17377ba88b90aab80e8e6d0d3e18826dc045724487206b33b68fc343035b9463cbc85e3483a51b35ffd2885911f5a91f4762727b864d82dae3d73bc980783c9fcc444e534c8f429d8aed9b1b371ff1b896372386d92587f45e7795a0822725eefe6285db2fa723798f746ec7d6c897a44b46720bc20c290a7249a66c09092ede6035e54b1611f8db4e7f5a2876caadfddf806a399b34c17386a061e6a8f807bbe19338fde3818a9324076f164dfda3b4beac7294d65c477199ba9cbf84a035cd26513c7a31f54025f9cd0f954af562904d0d727b9fd6e3daa60b9e7829a8e8938913f9f64e0df0cb46d22efe086728acda51721e2c0d286717d50f29cf48d713d97f44993cd04e8e6101ee3722d151b7384a265702cac899e2cde2fbd5d747f6dad997f8266b694475a9ae5d2a33c12df2240b24cdda3f63699a70c458ebcf37048032e0dfde863a4e48531d2f7a13cef0ce47369797e7c274868cdd61e36d5a896caff951e46674a552f2c1d13e62ac95cd72fbe8655b5b70714eaa6f3a02836ce8cdf91843e27dc4cd76397c609a2290877206c5a6cb426c4ec5fcb704ad9f91614135a65d115ec89da87139892f75930972cb4c5cba8f8839319ed6f5b4f29aa492054450b143de4d4e163f2ec1cf6dd5725122dc6639223d87dc1bb95ec3b3559e2e24f945fbc7b5d726e5740fca3ca9443d1c470a73b373b84014bae0e11012beea0be31ad9aa9ebff2352cf0fcaa733a935b6b0f5e6032a5a59b04fb7915b54edfacb44d5ed7947d08598485f589297292408351cd0602de17fed53825be8b3c5aacb542d5dca3bc3e60efebf13517725d954382e82fb05fcebe942a9829e9708fd2e31a8efa0d926e4268a01588c33eda7e3dfdfd2876aa4cfea2fafd9463da83bceb90dd431476cab5de0404efab722081ab2efc6fc201b29cb7820575d392fbca3fbb56e8c9f7cbb68b0d3ba4aa16ea13c40b79e4e4b0b6762d3ddd3adb67f569c2e43052ce91139543064ef3ce59fc78257c7d7163f4521da3afdde0b9914d3b0bcef2c8ce8dc1f35d3f043a60725f5eb3a32693a8bd7c11bc56e61ba57bac528bf0b1a97b2d5f3bed8fbc951d4bfd4fe436be478863a072a9be24a8566f1259251dc1de520c8320268474076d72f7f715d66c9cfe83e229776946758d1e3064c6f486b470f28ec62215caa1d0729c25419c58ed05ddbfee23707ec835d2d3819466bf8fa4c0aa9aac60b87c2f2788a617287f7f2b11e9f3642610e0c741239104f3881bd4b49ad600b6d1afc47231bbf30f94394491c3d6091523fcd710d8d60665325a0d88840499efb993fc72e4fc7fcb7e3733ff24ae173eae9d111d8e66910fe91101031414c59dfef9317209d7fc975408ea192650f8a0f6cc6e7e9b7af7682f86a809a1239ccd37a6f1726ae1996bd79cf4033bca66f7c51a75ebd35aa1e85da5145bc7cd305f78197072188fff61e3bb5ab3fc496917f232d7b8899df398ed96aec968cdd54f16ad1107645ee0bef47f730f614927f8d78717ff9387856d5bf6d6137a960a42d0f43a72c9f5f096e872a681cc86bd275f9f0d9d164cbe70b4922928d3f1ef61d61fcd72e249f98ba6ce10467a7d743804c72cf70bf597272663b323d67906e6d9c3c0727ea0559f85a79312e79d4c48429088e40c42b28c4ad3ff8f948dfe742bc5e950d042ee9e48b201c8c1d1fb5d743e54a52d85e6ad3a8bdee39ed4b6ef7cda962716301ac1ecc94ebd2406fccf29a9ad4d35ec8703b6e59c1eb7afb8ee0e24414389d872bff433f58ac215217e05632c7541eae192af5be29acbaa5b7655e1cf0510fb68f64b84454fa66dfad47e136297b00fade28d3dd21c50a4dba2d993a672be57b654e47f99208fdc2011ae9ea475507f3f0e5de60f013ff79e2f01709672b5ad688fc603deadfe283e130cee13a623fc10c7772624b0ec91926caf36ed5fcadda180e71e8cd94a2772cb768249137eb9542da91f047af174aa22266b4a722c466e16444d90f34f0792346451afc6294565c36af3187b5a99f5e606a849723b885f116e832c27c64b44b346e397fa9660c92ed1f5ce6e32591ad4e562236b708882d7d86c71437bfc7d609820a3e3a235f82c49ebbfea99a47ece994a050a9cfa86e5b53a80ac0b0a6d22223f1f5e2bd0d9a0523c794f436161a7784d99721dc8be7da939e7ebc293a9ec24a0009009f2461dbf2ab3213772efd12dd428725505164378479326c4ec0482f66676176c6e956a57556ad00d874b45b85cac2e50cdb06bfb41c7e3a09fab519b11c647cae6f7d3683bec84f21a2f8fa389ca72eea134ca34347816792f4a4ee252538fd934b79b54ffbb6a0330117b2821ca49e88cee3ce90c538d620a236b75d805509f58e663f4d1fbf77fc879583ba55f72ccb15b895d63367069d5d7fd3c475ee2d468f012ad2644601feeb78088649472543795c039e9410dc3aa2e63573df6ac4610437b3237203dd3c23f20414588540481e4ea66c4bc397144b410777a289458c0c287642ae9ff2695da43bba2b82fdb90510427aceba8545bf1b4070e0458c6277c1607cdbcd7685bf6a7c627411fe1e6f25406cec9e4a45cd9c7fd1c02170bc631998ee37d7ca03af595d8b38e16bee6c8c462f426ff3d203254c6803715a45e75f06793873a65e0344570961d695e5bb7c3d48795875b1fbb4400c41ed9d669d3cb14e1617e15e50cb579e37872bd3de42a04ac7f3c6bc316bd2f5bf159f73db1123bb8e621692139bf625fb133407ac715215c528d205dde95a4bba7970fae7baebea4daaf52b127de5734575e82630da6a7a9cce6fa6627dae48882ad087e870b12865557b6f6d670fdb55b3c67d0b6197dcae6831603c96477ae2b9cce413ac57e72d226c167dc2c4f5def69288c4395b17af2174a5c4cf4de3f912f8b91565e5862489b98fde29cfc4aaf72355de84ff5b3535cc589938e3831ff0fd37e581b52b0ece9151900cee1d32672ed64f3468567c67e35eebd895c52ce48ac73bc37a1dab488ce4afd55ab21897294920b32dad294267d1120fc5a5de22c38c424ddca0b3e37fc97ec48cae0b872fe35f005625480f0536d3767ea929ae2c6d80e1973a540712de30b7e73d8a86d10d8a2a5f51411f758c28158d2c57675c8a3f59ed819769c09d7c9d2370e8529c2824bbf5fdaf60dc5dc79e83e1b7b270f8db010b00580a4110dd8818fe185727c402b722deca81f664b1924385824ca5b9493177b6b038e167a23f4770e9314b31f9d65fb1e0c83c486ef1fc6721717951d7b5ce5b28e7ad62abff387d59b1e82c0a3662f343c294ceb9afcacc84e4e377ee9253fb53f756cd879918c1ad672f8499a08f16646c9e44e6a4e56d038b203d965fe28012f281d113e9e1b77277298e1df02a561fb687a44fcda509e1c1a35380605a308027f3d80ec9feec14872081e827b8f651e003be0cffb7f7d3b8b42fb2e34b18db0be9feb24297c9eb67291549273b9c40d2cf7fd987d633ac2d412f79984d3585a3ce47bedc83532197281bee3020f8ea77ca66111b1b914f0fd82cb3efb4dbc1ff0999c2e837ff4dc729c66583931f39876f55444d957d55a46a21c397f154f02b9cee9089c9fef762d14e95a282da6f6ebc6ba893983be992da3ef770a521cce9717228c20b79a2f72a076cd7270c58a27f67eb5d64e5f2969619824c16fc97164ff46c965e640f072b1feaa2a413fbd5b39b0b8b909336f488e089bcee8ffd9c1e54b06631f910b6f05827a3837508bb437b635896b6d7755d021ef0de382e1aadac385d728a814723c8691bac9b5b490075c74e9cfb83ccf70df448e5d764872ecac8c811187b63e19bdb679d5b90f583605375964a40cbeaa93c8da0fe02ccd8a6161807a3e7c72621edd52c282a09b3b6f60250abeaeb92b1238bd9607befd7ceeb779c95fd755449f810058b5f98629581c77fb17be0813531372ab441a0c1fd5ee78c703932f7ff3b428df72b6696d7a5baba370474787ffa0804def98896eb068884c821d61cb15183e5234502bc0a99bf2da9ac1f045186d50547ac87ef8d272d7d1594d0429f45af459c7a90feec4f9cea07de548ff4c747e85072a2d896ba985d4c07049057f08127dc8d5a5ee462d36a42a2373ee7b45f6aecbef7e992df3aa30d6bc2d0ba78be46dc619ef415629ff08f55700913cfeca688cf74196f26a24c689867218a579c3a0907d4b1366b8a072628af5ac38a39c857f20b53102b63227b1417261dd74e4cfa30ed58bbdfebb486243ac3422712656306a40f71cfad096490f72511961be456190991785fe8aafbec01940fdfc9033288aae64f46c3030d30572a3b1eff600582afe036a0dc0af69f2861206a24f88d0db019a9ac2d40ecc884997cfa0514a4bf063c658943ad3cf524d34a8d54be737d92d348729ada0c9a90e22e0403f549d0d527aae123ceb3b9bcf0c669ba484a5ccee3dd4ab5f1a002672bb202d82f37f06a2c1b444de3f01923b5ffc356fb8721864335cd06b4ce18d72f86454383ff248aedb3f82945ff0aede03f3ef6811e52a57033751051ac8b772157c0c851d936b736b2de1c143c73b8875854ac0add624f0f30439e22bba4a3817f183ced1255c053746ab154184018c8e742a2ba8e7dba85a1f4cfbd357ae16dcddf1c1d459ee7507c9e275a9a76743c517858e486bcbc8dc8f91e667a1477298fe49b2eeed4c88f16a9249e8c5a3f33403c06bc8a3e937a7213941fe12cd72011a17163bb68513a31165bf66803838f4366179a453b241ea435847af08901a71e18a509f03807a076d7eba8c73891ce551c1d38e5357e7d72da4bcbadbc50b683ffcef308e28cd03a2d6814864fe4db7cace9876496e1d20d8a8835dde4b7207b608dfaeb024fbfceeb59a21ec273fccfb8afd21926c2ee8b95b84d00f9e5f2c5367fbb2804cb0b976e93150f5a5d2ff8698ea98edcdd75c527df6509afd40ccb0a9d5206faf0a2fbffb393af9b70846e6c522e7d1dcd90adb485ad996770b9f1c6c7203ca0096a5320cd21e1ab4074cd3b645067ef9c57dc1445348fa6d72aaebafa5b60feed7afae08d0e8e56713ecae68ee32f7af6b7d277d6dfc1f64725881e853d7def9face7b1abbd8c44a2f933d438083c73a0edcadb26ecfc33172e98575a4a18cc99eac7afd5b0b5f4b288cea1f3acb7b1f30b3bbf55f5f1e9972e5c2adec5aca2cc36fb421bdd255fa0b65b803ee22df6d23aa431c8a58d7e672a4c5d6213b7bbc51321a121655de405970ef394e11dc86f3432b0eab39bab21c17ac9f37e73ac5f78fc0663a40ab54e5c67795a6974c1e64f2c43fbb670c9f72bbf3deef3003835792cbd6414b98b8e3b0b6462b3d0ad398ebf44911fd2b9e728e9074bea05bdadf6a1fac503d7a30109e8caf366e71ab9a3259b2e289835a13bd19b6c19b068154e35b177d587b739f0c887e4c1ba4cd10760b78a58ad9c972ac3623cf5a0cf3c4bba84ba5ff3cadba3118c7fd3f44a4de524dceae869f416a621dbe1bfcb712c5ca75bb2448dbd5f64a491dd25771b0f653f95ef280b947725031f00c8cc24244b5336a79e12ed2fd1fc7e09460cb7706c83b350e19f41572cdf097243e660bd98a239d80b33fa679a54814b22d5969cf99252a872ee2de10e24dbba4ed2de4e68ff09a04d25ef9a2d65b4835e3ffc7b036ebe8bb7f157a123af21e9e7d41709bb6a41624609fdc2c8aeae2f7552d90536e83296f59891872414aa5cf474989ddb2eafb9e2247bdc70057557486e4e5bf4297e0403ec30525b7ac0bfdea9e1074dc30f5dcf29db1e4ffcea31723fc94a8624177f53108797240b11459fe3d474692c450c7f09108d9c7d470f0448e74fb11aca6009cd92e7289cbd5bbd9abdbc212636184cd78b50928677b74b9992784c5b74f90bcbcef05a27b2760736c1fc832756050dd587e4213ffe4f4d0929514820230e81acf165cdd8c44a41708b4d94e7718bc2afc88f5b1d5ee882f9a5e54659f30de9db94c3993020b1ee96a420fc6644b2d60bd81344504b48373cd43b71040726d6b3000721b98002f50018ee5823e8a4f44f4384e95d3e20744631cf5610db7b7c0a9a34f9dba9d6c694d1bb21f1b86705f76980bb119f8af24819a7efab40817f6e5d5296ccfdbc27d9eb1c6d7c373f26bb21154178aafb85da91ac0fe61aa1c55969a7239312d1e732a0cedec03bb610cb9c0bac630200ed181d7e6c432790fdeb378206a92b7963820463bfd5831d657b61f8e808d3402399a675f2a7600dd70d99672e13ed1dd38902666f9ecbdea608066480b382fa9aae2ebe7afd558dcb6f6a071b106ecdff1d841bc21b89a98b3ac32bf4ef84957726a591bd9dbdf0bde17ec11ddd497ebcddac695cc62289e7e2f56efa8a3dfaf866a747492a3492ca2d2110dbddc12b08a447b201b0bc164cc61191907a8e39554255c367a9ad178fda93f53e6eed504f2af2a4f0fef1a8dfb49a75f9a2c17933d4a3e8191d4849d28a910729092d0b88ea2ab506e9af50e68fcd2872966476ddac6286685b917a77a7199460377b92001db880326d796137d391b00a2efd880deff10a1acedd3d4aa588e354a82b900d97409d78f80d69d6b24868504824e2a71c3aab116314d5d4ee5ce2c9585955e8d5f9a21b6d220d2c93ce162fe7d2e6f76ba5202c2ca0245402f0f723321e3eb239dfc00358e63fb375d2e8469c58cda4a1d53486b97bcaf8a3eab1d7dd5f9657c2a8cb7d5b4816b5dd32288e413dbf995c59eeb9d67cdd49715bb416251c0623bed7d9c29e9bbae4bb460d3d43f53178c762c80be9e69d2542b6672d20bcfd298439ac22ce0a8e4fb03e319246553904a55784d83c8513b7de32c2517913f515968f520cdddfa2c03ab499df47ba2565229e0385f2550a519c09472bdeff02da8965ffadb210e9f57fa51a888936fe0b3c44ac82ffd52c815e6902d8761f40108cca1894a75e895f3067c3be8a6855f096a96903e41946ae2c4db28b2d962e9eb688a51050047fc8db5cccb825dfa5aca14076dd6916e6c9bebdd5cab99e9ae645917a8a9b3f1ede14fb634b553b8cd020bcd43c2004179500ee47287253aa9ebf7b00da3dd032295ee0abe00dffceffc38d875b75d9555df80787270beddd47b37c7790aae3d6e436311c815f67483cd5bdd5d90a8fdaa6c908630feb9be894882758ada0a46370cb23252718c357e3284e751af4ad0524fa648447f14270bcd5d8136a7f3a848d9b231b5a3a625f339b3396be5e8842d9012b2317a53416092240fb8578ef972666cc78f601fcf68149ac92385a3ff6461ec4d1ad98f7321df0a3cb0ea93a9f67251c86addc9b83129794b9ad6ff590da080417210b7d70e9539a888e541d68f33da6559008c8ddf55689e9fc92ad6f527d4e21687554026076c1a84f5595d4f291f6b78f39a82dbefb903bee21617a12332a5720a83c24d45d09bde3d5b66c863c72adbd6cccb3ffc472d5b6ae6c53b72c77c4fc85b2ad35ae60fb180012e7c8c5d20e956ddc98707417fbcde9299d6ec057c0346edb266093c7c8b5abba8f8e40fcf59a814e7e4fe9951d1feacc799dc38c072048fcf07558575efd6b429d12615f1a28c7b3582b85abc5dd6d36ede4d0d5906818ab8cc57b097cb2a043756b4feccce95ca18b3ec6bc5c2c17b60f188d032501572fa29c7fe6d6ad2d3b47a7d38f0fe22043f9503c2b5693406e0cd10dbe572716dddaf2b5e4f93b35a996730946ce8c57f67050aefe67a26f8b31f46278e726498ec4dcde87f7f4c2bd680e512156977a6f37bdc9b10c220a8c861399c4c3daeca0a551ae505779ed58aa99c538632945a308ed74b071becb28dadf6035072d27f50cd47ba411d799e79f9e1062873c127fa59d4ff1ce502b775a4bdb243724bc154decd9ca65fd83a6e5c61af6c5922b7e2da7d8e56a809d9b40bf19ddb72adb727afdab3128ac37b97474d45c0fd466d2c69087b88702c772b687629967204cfff9ef0b7138329abc9a0818cfaa9fd1c66f0a211d198a562acdb3e16f572f8eb22975bf9ea6d6fc53c8f0b8e9ab8e09636b9c973d0d7657f1e397990f472cfa4224feb3a22a511af693cc95acecbed9d877e5c97a9e16f578c70f8c83b72ad2ec07366a88b606a6d4443f45e299eada3c83415e85aa8da0903cb97f568722bb2bc90bc99c5e56bcc8d4367d1b8d127196c64e08e1d713b09fdafccd5ee6ee0aad4fbdf6b909836ab10a085a3bb4eb097989ec88feabb4878c5b187221972e6a3272003e250c1ee02581589807cd2ea07e0ba3a8a635a80294a72c81d5c72c6b1f169591d286ac90becddae57c1a790aaaa1e65c3903a67b72062d310bd72d8ae9d60a939c5b79f15f64bc65fa3f9b0f95ec58098396736e5ca7c71b8b872e045598cc146014d8585f387c14cdc2400549279660d8bbd51d88da86ec09d72a8c5f39dcd340d8e6d1feb6412926ec61b3c089df9e6989b8532f336e46c0a01e59edeea18b0c174029614a164cb3ca09e30c1e5d99aa765193cb512b0a2b0671bcf2e85b54ca53027aed38dcb9a2472c8e8c427009c86227b247836971d756405f083de96540b6e6181eb3ef52355f0dcad73319c94d74ca2b67207783cd57209868ef9ece1a4c674107208e547fb9e7880c463a353de281c79ed9616baa01df34860251c08aabad6e40df11c2a880a4d590dec2cb8d529027657913126490f1b6caf3001abd8c565eef91d78b9e8580ad1055bdc61c69d03d43ad0cb4bf82f6ffe86f9e7bbb70ebae20b498096073f3c941878af534b4c3290a236dcc274493e739a350ddb1027faba19655c5b078cb1eaf752fb8d1ed12c3630e3f6f5d872250e6ed6136beb7982f70d2d7157b4bdc01c4ec7217a94dd81f37ffb7e4d6172d4b1dc1691b105d4464c8bdbdcd6a98fdae034914a616b59abc8400b28ea7972e8f5d323dbc8d1c15d43c2d90010de4c113de2b4d10a082c5099ef71ec5a0d72d46656602bda366c531609d59a254d8bd0aa00ef367942e317e2b9863b710d3cbc19c7dcf0d851c6a9e62cc808e3216b49c264f6ef058b2cbb2be5500bf2ec2e2980f24a0355643235106a56bea6a099915fd96a5c67962a58d4722dc66b487250e841e5af9e7812e260d376ea23c16e1de871cf757a61a3e07d6a15cb071c6bcb1bba3a03a0f3997c5f263516d336b859eb18ae65843fbc180c92d0449e61438dd74072f164b39a502dd21645a80b687dcab0f9d71c0905984ebcf94e14e3352ccf3b2f6c7598e2a7e9e78ec2bfbfc405283420af503e0b917e21ee6b9bcc7298f9cf2775fd62e688b0c8d3ea5ebcb4e96dc0355db2dbd1bb99a0bb0d53ac72cabed8a76e90f56701430dc2de622eaa17879bb74e9ce3a526c26766b95d561f040a9479bf44ec74f97b73d79f7b0325d3b393d027013e75369fa8ecaa799f7241898a4c24b435b914b5f807d7e6ccdfffe7028f9463ad6430f0803b545d8e72b482bd486cb242a286f9915725b3d9bf81a14ebde7206469e19424119d4da004c53c97e5e03678047b8552a43a7c9407064a8e4f12bfd05f82e273e2947b0530c2f76b27520b2d6dd9d4c624d650a86ad81ce3f6af84e5cc6f506b601abd01729853f20c2f127f37c591e54181aa6c5d33ea504c4ae689089c1511106f09817224af150eb5f92b6a6e1ddab8f8933fe30dabad5d2685437c3fa7de80f569c8726b866596306c6bd6f1053185b4a2f8fcddd0752ea9d655b0ab6932e1cd9e70726b9f9a95cc9f84a43f3a1e9c5d42423c1ba1b505a6892b39424e14fdcdf8466f1306c6d059946ff4bf22aa9236f3205e68a31a337fc675675e3f3ea8401a3272c69a1dd5ddd2b0be1c3b5ced5afea4823443715b353b26a68ad46982042c4c5067c1ffa391ea4eda12bde761d5b8c6df7767346fba26cd5f4ff54196b91b5972a5b2ec94c9f613d5fb768d73f09c879793d483f66f7da5074c87b6eddf71d972b10c15d46a56248a94a8caff6eb12d888621ade09ba62eab03dbb11dad687d72284a50f1170a40329aef24b5660781680e0a1e7c5a42148c78736e52efbacb7228170abf22e43a4ed8a51ba5bef17f7ca5e438d7b10bcd94974375f23a3b3872003585f46fcab5dec3523fd2b88ca12ac4d69351278fb1bf578cf8c7d567965df8ddb68d536bedf3a104f88ad4c443f6616c8ffda01dc34fe1890d48d87b3f6d45db42bb71b0a723c4a3d592d55e730ed6e937ed97a3816d46d3fd947c303172eeecd99c32b85ec93b9a2bea7641f48002f46c7eb48078010e9297a01c4e1d72abd7f13eb47046149f1c403f6e389ea350a788c30d449b84d3fc555c9be5a77282dd03cfa698049d608427574ad71874b5c14259beb0618eb299028b89b4504403b9d57a6eda6cbd86bc8e348f723479a895d14e4b81159a0e876f23070e722b43293327443ab3e5369a2c997143943e950486e00ac763a13a0277ea567b02724ae816f133d9a78f591366a1af381473b63bd3d0c714035b07427141d59c9a029992d1d39c932fef3f1b767dd3cd8b3bba34782cc45f06a6c6058e5b618ee70d9fc161ac1c68343934275374ab50996ce9b6f270573dfaa658b401bea829404ddc5679703cfdc0fbfb3ed66351768137fca49f3420558afd526ff4a4653ce130303b28bb83bb85449865960b75e0d0c49d045a5dff579f60a5fa54739edbba72a29e4b4e787e8461c90b13c60d6c7c9e456a873e778432b8a22468da2e158372252bc104301fdc140f51511a12ffd09d204bb0634df8fcaad4522449989d8926849b743e59c749ed798fa1635f489818635b5f3f8f08ecbc55c3c2f133a05472c761658ca5ef42695a3bc4c652f6eb4bcec72beb87babec31c9340f744e76b7295402371d3e1a8546065aa71587eedb51380073804e77f6281d3b1269afc2332553a25421a580de333eb0d45331294255f9d26603f469eddc89807458f6438682323ad9f79253b4113f4e51a5e8a64a370fe13c91a3fd10160f15068e71d19725158fba9d70c549f6c298611451073988a97da8241f063126a10e126e3f7b57255a9da1a3ac5998885b203d38cfdef082537850dcc966f438f3fbe1eaf6f117282764b720c4eca73c06afe7db014f8b8e3af2097fa6fb50910ad252ea6407a3cb9b110e48ae1a5e7120d84dbc7b5a6b9851d0d8a74117beaf20c5fb323cf8272574c41cf8d35ae1340e4ef0cb1b02bfcf10efeaa0318a8a69408ff2010d04d72586d4e0c4500c71afd2729c24021c986dc40d04ea4c797324fcb179446b55471f3b1ac7384aecf982d5253ec256f6b8dd89fceb37b5252bb56dd7132bcc0237279a9ae65679b0993b3a2c70e9c070fa8c872e3bc6fd69a6120e9b3e3c168765a776542411d320407579e0a9bf41b37035aa7cb3c1b30092fdab7db448cce93729f43e54449e972161fd9c1f529c85b55edb298bb35d005e360bef68ee008ab7249af4ebea6e9410078f9f015ceb998d7438e35d9b3634f147c7234e0e6017616ca4e378561019127adce63595a0f2b352a0a4603248bb4f1c5075d03a957e17216527ef90d8ab672fa61765b66435d4339175947c247ea6fb947719cdc27a71912489d90ca8a4450f7eb6d767115554c67ad19e0fd9d6c7ccebfe7c1e0a4b3724f7fc43d52ed8104e78e4756b3ac49ea76a7d82eb43601bdce066ac362a9e6001349ddc5f0fa89e95d160c116915e26e20f5f61575203e6979519c6bf4151a08ec4ab81a3a2ca9958e9ccd0ee46e2670bc7ff477ea2de280d45a6fa43f9ba93a6b55caf41b7bd2754eb789d95c69a799094abfc5401e403406696180e163f772e62123862823571a135560d617862ec5246266bb05cf6310751102429cbdad3adc3cef52c2c932df4e9e15947211ef75b2fc9333c33d03a544e2dd80f1a3e8725d6f0fce844393f0be2dbf1fa8b2f9db2a3a61a92253aaedfd839f3ece2f3b72ff6b8d61afc7ab422f3e43b86d95cd07e6d11accdde7720b7bf0ebb14d0e7a72fbdd0818b34834e92f697394b0866642afbdc5f60c9e79edc1daa0b9e5b6ae7206903d6ac59b62096be8f27c5b1662fdbef1fe417e265604bf55cbf15a5a79724600e430dae749fb4d4280431f64ba53c8baf201fd52ccbe5df9f30f22e8f0728e9d16fa83cf692502e79869762198f0e65920996af6be8137a0115d64ad6e3094b0c1bf62a7121816b80c72485bb00779aae042e0e69c132ea542108df7e472d44a4dde36552f07148cafa6c65ab92b08d3b8a876750bba600d9593d49876724c678fdae057934ef06eb3f5a31812d6af1d8c8df98f47632331438cb4b03834e03adede9d65823af260e2a76102e6b142e0a647c3ff4f6f975f374e846f3e72beed60bcf56cea5386e5c7b86c02152d5e1b108537ee1a98d47757705408277208fb658c450a04b8b7e80bfdb82981ee522b9c56b372208a69200c664d398342eb62b29037f3bcf4ca8f8ae99c57cade977fa06f8c7300195bc906eb48383d722583579097c8916925b94150476720dda0bf1c9ce958c2702d57016f12235626bc354ec28d003723c4800ab15233308e6a262b6d294948167a6f9ec9c03a3147d037e6f906f16f1c48c3b2719111b1d59d0879f77d3d205a20c1b80998aad1139905379d67579f90117e1bf0491dd6f9febcc1daf4c584077da25e6dd303ef72ffe64e967a62f613d359888526f54aeebc71821f665e52d84d1b0d0f1eed973b491c81223ed7446bd1bcaf7ce9686fcc4300057c4ae1f5c8820d0341d7f64d72632596f4a99a516d18a77d42ce94c5ad1fa26bb027a4b1f7e4e6155b154e867284acd54e77e7e3cde63966a38b90d3cdd25a8452538ce7ead8701aebc05db072e407c4ca7a4e5db44a62fbf447d8d6307dca4875726753f242d521c98b395f7253936093c16f710da1d2c95cb95489eba3e8c32033ccd1bd48613a46310a8970f475f91ea28d83a4ea923532eedcfe2bc72bd2e81e2749001bbf65c97e1ca07237627fd01e7a35d913b1c75768d97ebb7762afb83f9fef7b3e6db28f42ff2572e91dd791eb6dc3a2292f97ceb260b0d903712a7c7508cab2c0d8e75b0f8ae6724b54ccc04314e28fa2e58ce9c119ade979ff341e11eddf8df0b24305e7d6605ef1d0b893cee719bf3bab878ed3b6ff72e59a8c0d4676cdff41b5de6eed697321b8fe0958245336f233558bd99a1b6b0ef4f21fad3380fb443f3482fac65d0f18566bcf54417348872ac4bc4b729d2649d066a076e8fad15b83c6a3b38a3130555471fd7f6a2dda4043f8b3419efebc27ac0add6519ea596be1a6e9836273f47220ba5085798d3017a511824fc9f1b201d99e65fc36e6c27d5f5f842b8466b772029061034d3e5cb2b4eedab4b75dd5c39a059b681833d64daaf42cb7c56f5c725651691044cc3dc029572283153c4b1616c88a1c9d340e199cbabb3b52482c7286bf6f5c4e29333d6195daed90bdf5836a574a31c61f90fac9a628355616aa21e7745bab63b3c2c5266e577117fddb96d47bb0916591c79580eaa5463f775d2500fe9bf9f3ff8b356c5d39e18aaf8f04ff829e75bc1b6054aa795ed273846b7244ad7f35d4bb41e6bd445ec6cefff42adff821df285c2ee4bc3073064e586672102a09adb94bf6a4bd8a07731f98a6bfb91f8c8a82d2cb6d8ec6a93addb0be72dd15db7f7a95cc108922bc98a1c60694a1e7fbd879c9fc7555f758e7f6554a71cc40c8ab77dff1851987cc8dc2d2ab09685d7d86e4cfda1d813a4ab035008769fa2a0d01841417e0bad65b23682dcc394d82fd322b141498e71ea81b7581385ee63e47dbb3313e33debcb51d6577c8145eca969226ecf60ad2de4ec67ec657724627d27ac579731512a3114ab73fb3c8b8a0a412c2ef2a6bb544cfb30a727653e4226396db86bd7b6823859c3c662f3df9f8483fe89680824e77f0ee680a2440a6e37e89ba8234cc59dd0350a4d2bb800563a9accd4a47d4840de26d51720772e8bed6aac0399c0d64e567fa66c54ec826ebae5b169ff7afbde1776456b0d048ab1624a0b3f2f13ff516a9f3a8e50f9695f8a71f82c21b9e45fc99cb8bce3072dc22944cf4f90f913319e8e4111147079401614b417f5b15618c0a7960b424565229a32dc4147b724f9c01ca91d8ef7b5f4177ef9029f19c09b5e2a19b711c72844632575d9caca0fc23091d9e5910761f6dc675713ea1ff95a144d4f3133972e4c61f1d042f76b5406fa39659e14f3150f766b7df6e615c236054b547c965342c03cd0939dcc6a4fa91683e49f44ba2d84d156c4bef46568285052a0a3add658130d07299ae7a5422f74d8be4def547a5d46e8ee38845e5cdaadb59d23d9068ddee21d61d6727359bf6079023c85ca920c608a763da344a80fd951bdfdda4723fa49ec0ab70f3ed01bc6435de849016a9e1be00d83790eb24214fbbe0f75772a3d1acfceb0217330ae401d86bb829de77c7730713b17e4767602a834bfe5c7254586755ebbc40fa32d871813ace3a7ab996ac05a9cc630a228bdf8d1af8847201c9e5532aaea667db443edd3f698555b809c5925172ca846159564ddf69e772107f4eb085055584c448cc55a1325c4f1dada803cfeea366b44eeac59891f9722620d92d4a16948583bdb654964220bb4dcdcd4426264bcebf1067db4e294672fbf3056aee2e88a2654583bb14f042435e61c8edb3c76c0c701664d461620d17a6a80cc1743bb34db135ed1c82986f88eeff2ed04c9582fa8f67632a3117df72b3879fb668893b52293e1f18e7417f8d48cc087f63ae3e6010bfdd577ab31172183fccf1f57c2e9fd1953fe0e3a0549ed78f80cb4eec803caac6b271e223147217cce6db9e8cc1b93f3698fb2a82db29d97a9b4759e6777b385ab4ca58e64a72fd3821fb438c4c05e4987fb119cc4c436cc0f0a0c5a0f68dde531fa2559cdd7256437878d0300b73031147798b274f2a3ce5519d7fc776d95e139e0ee5c08272bb39155d365608d439f86f594ed0deb7f97b60890a4bc1dc7c67665bfd5d0672b78a29ff7eee6a45e793903b7dd2fa82610797dab228ef553bd2b7ffc311dd22dc5772778c5d7b09a93eae16886e907beafca3b19b774621e862373c261f613446d019dbd2060d1f9f9dfbff4d92e89ca85c22eeab1923dc0a19add7be18a572ae910a96fd3448d4b3f691d62d6a3db9e4a38aad5278082b433888f9327511330d3276b3b2539eefd7e265baf20d35844420dba64cbd845229c8659ad218c872d24603bdfb3ebc255a6c2c1da64fc0253fd4ea9cea5bf0d95fcd5d31990be4723ae476646ba6414353dec8a3e84e432bd9878c42060585f5d0bf792267c4ec72d5ee33e8ae2a00c1a7fe14912d325650c05e437f31ed9e1503b21d6a25a04e1577d4305afd93730770d755ca98b273f1f4d05f1c7ed4a70e60d9652ee9284b72de838cd18779457a02b37fef5f9372c5fdd790c2d5ca1fc90291309e8d0d0e061c4cb28059cdf046af98c55eca215dcd25dbd43fd04dc27325a39301c21ffc0be9645e666885afd529e4184354c53b59d596b5c171681056f1c99634f8ea6267358cf42f7c238929b15c4ae6a71f19af358b24d1c0a9829d311a4755be1a1572f59efbd1b439f69359d5806ea63c34797c25dc9ac1d88926151b3bd76b077972ad49335079003e7739df5d062c032c8ae0554816b7ffb6cdb811117ad78eb0689cb3b596127a1178eabb7e96f1dd5d26ff295dbd8bdafa57d7591a2d58096772b9e1427f26f2e86b67df01d87697d6eac943d08b8e76f961a5136afd6be44427cf384c77d9b0d20d697695d8240b8badb6fb3271a85a591b4e7072ae91a6ea014655eb16b268a9eefd02e67fe72a9987ea4fde74d4f30e529ab867c8d7fa2f25c0cdb6f33d565023c6a3599ad157dc319c598261275e2c8df0af25af433c641b68c6ae46fcabb9e4f52c5acc5409772b1b3742cf48f0dcf016f19fd42cc985726008bbcffac0bafda3ce38a5977a3abcac63ccb03584a83eadc082005ded0972739c0c5263bf80be72b25e35a433b9713886dedfa06f64bb802208382f11fa1c0740cf9ba87f307fc5c57c9cf99c71e185715d307bdfb0ae516a048a07d54372dd3be5fc62c2116920fb17972deb6549a2750f3ce4f408d0ed633f526f67b7720b69c76e88872a45cdc2151069a75a4e40eb7abe02e5ed962fcd01c52a613539964097d28951b9c9d920a7a7c98db8902e655a387b754597e3e991c8d870136324462c6165b46bf25f8a7d824205af477bbc5213ad020829fda124812f2f572b56b420fd4f9431f6f5d4d335e3dbd0f18eff6f39ca058c929b09198a9513a93b8cc4be45b393700cd7faa9133df78692b1d3bdf45957f9e74f4d01841999f657f3f09742aeb30d59b3274e9e345fb092346bf971cdc772233bc0b812d964e772dd90f4c733bc7b1d6de0f9cee9fe8d5f4a4e9e589b4177a0657708b7b60b4672d1ca2b06e8a32709031d5d5c14bf18e18183742a9fa22b9f23d596bee0783068ad3da4b0091ffa2c4f32400473a6ec65c4f3857c343dce56f979b22b32f6454312ff167d9dd15396df0f170703b9a818656c6b8ce0b4e927e396e9013b030772107dc824e3fee0ab4c2eaf87fe6260a2b8e0c1ba2179c90ea5eac7ea7f144353869e40cdb1809386fddf48650ded3b9794bf99b8ad9a6a76cd9fa809d7b7ab4996f4f92848b171d272f65b40afc2fbf822b165f5b66452c6537d8dddcd35ef21e7df31c34b21a4b6429188ae62f051204c10b400d2e5c1d4bf0bf0f748fafd5edc1a743a2c0200d8bddd12b680b9185d9782dd3b71413e4f8de4f545187b936e602de0d462cf80c0bea843e47f06f266106c5e85202ed57d875fcb7930a13f204f1056bae56842ea7c59571a9357fa561690bbe78b8f063a749960c2d6f45e46bcebe2fcd748e1320ecc4e7bed8fccbc6f6aa14147f88ea094e5af50ea75e4623f044f1c53ad278e3b06127ec2c2976b5f310ba69f55bc925baabfdcc72507727b72b90b3e1cc14bbb4d56c318a18f3ecb24f8c61b66a6b42893894edac96172fad869f587a1b39943df5e8447eb6124ad81730e9e74067b97599c89c627444c50e786363b0d9b6cb49c755f707816dfc9216b1dc572f7cbd325d08a9001ec72d7cc3466d68a1e2f0b3b8ded7a07bd3a0c853f91ef6e4b8364699b34969ed72440e25f649ffdeb6a0c3418ef71f8f33fd1568b7b493d0307b971099c9504c8721a7a36abe4e142b44fcbcbe6b3b604f631aefb7fb16af7c6603716ba2dfa4b729f699768cb15da499418f9f0df725f0fcf8ac7b5f7e8e432d7bad0b997a6b36d91154f8093c32fd039cf2d76a4c4412638b71d84ac8cbac1efe1cdfc8f33992fabf04f4ff4d23f77197da8ea44bc133122af7f4726d3ea01ae8c7487d07ff272f4f2f76ee728d42e0789df0c369bd212fbd0d4d51b629eda5ec9abe0e66a177200de646b613852f24cadfceb8ab8fd8e30bd299a57b6471de9dd931dbc3bc82532586bdae36e468cf784d5f2283c37e76f967e5088800aaa3ec0764be142635407ab17b11460423ddbebb26c52be6ba611a663ec0341ad11c9a351f09534a272f7d7b09452ebfc18b364cbc930fd2505f1385d2b03f53931db426265789b587290e8de6e656355b13a8fd88882689c6244afbc572f421f885944a0df49a80632754d9b59dc5a3c0796324cbeb5e84ca90f4f06fd0bda690e0eb05a219b79c1295e88a33762d2f1e74b042fcf679b02111b59bedd8c44833be4afc61eaac9e01335b3d47c5d80824034d32790edabf13922e7488a435e60ea67ca68bf9ef4871cad092cd5fbaed79e6f7cff7536ed046af92c9c4be83a9a9a30b8652c6ad0c7720cefa724a937722aa904eb51a1978af6fb285927e25563ac59f5f448ab3ab857bcbe8bd052b84cf7b7ffc4938ec80c959a6542998b477e8e5a177b12a76dea724ce25c7ddc8b56df056310fa53709381dc6cc32ee57dadf4a2112d3e029c2617fbe674cfce61bc60bfc7bb0860d627a2ad8777a9fc5bb09dd6c0a22de9288472a5e598d413361ea960cba61e33828a8c55b0c329f4e52aa0b4dfe57990651c0a8ab8df9b0618a3d8c748eb16cd0a94fbb98e4f739329d994e992d7305dc2f072fe08447de683de35dad995482780898e2e7521f0ea23b9c1adb728a3524dc6726a40ac8955a3e7f3ba48298ad807d45f054dffb7d15f6327f274c2408ce66272097ce9408ade2c6551c556dc6b58fb1f1fa9241104984d95ede5c9ed18017e7210da8d3fc4d27416070bf97fe785e00077bbe67ddcbaeb118c959cf1b77bfe07452e8694ace12d50e83b7008cee96b2dcb912968abb78cc5c5b4390771a19a67675e67d6b9f6f21130d9a9254183b27325d50d94efe018c8902e65045b9b1e453348fbc4d80fcc2bd5b861bbab7ae926bdbb489aa454abdb4dd843ff6e1de458236882152f871a578c8d60d4ed7fbc6dc03de9090fc6c5e26e79e356304915722f91e28bd27af71795c71ae6d22b614f1d9523996d267fdb07f10101435a5a704c64e1c5be22e10f92016a796d05a729ed9416a581a89cfb63fa10d40e48ad1b720b9c8a8a8493c88c5dfbf8389f066508dfd819eb05e198b49ebaa184f19d1f22294f349ef9e5eca965949a863b54986d35cc32ee92935660f950d4511b096e422dc1ef7c692d152d3d8b7bf775e03134fa891b81cfbda96dd1203a27688d35b6a9c043cf0f65ced654606cd90e9f84a9fde32f4047bbee13d9fe8046dd943c6bbe2913a3e9136f534989fdfb3f9ec05e5453989887d5bbe0a7c9b87ca557435e21f21869fbf06101030e61a01c2693e85f1582a6a0a6c86287288f8b6aab72f1c8f0cb04a3779ca0b3171fd2899f41f9d2538ca34957a9ea3147f7f5661b6f6ef6170e723bcc0ea6884f79a0ff1304c8c756b96bf846b39518bf39808c5a72a3633c5007287c8e0f901b317ef761d875e0d020af068f41ae0d5cb84ae10672c646a8a35a33b41989f1a67d03fcf95365bee45dc55167db5a687c96b418e172a0753d93307e07e073c3e75a86f8d7a901fe986c5cb575047aa954f66c71e9162aea425119f1caeeeadc74e4cb530b5503551475b3686565f521e89153f56a60e16f4ca3d84b7328912781e75402e7f0ba0f6febcf4c9d92e18de1d36c11b82b808023ec1d914d5c75119b115d604406405de7900f149547a30fd104bab4db2f0b715b4febf8b5a99723e743387ea9c045293e577331e74abe249d8d44452d3df6477ab98a6a99cc5aa67caf3338c9e0fc9af8048c5ab83dbb9c1aa4dad85e5ec23e65435cd830a1a181afe793776f3f7d6501f4ed344f20999a138ac84c03724b7d6451d5edb11394f691fe0a21b2de47f69089946a362f2ebe39ba9a93ae7268bec3ead9c05f3bfc7122a180404680cfe62dec6633dd9aefdfc1dcfb87df5cf83b3242781fe06d0e874f1680233bf1b32a999313ff9dbdef53152bfb41bc7246566d63ba612c9e0bc2a6e753028f079da25285b3b040d1a4cbb98bf50a6c72d86647b5cc8c91f903e30470d5d1486498106b38c46926fdc808d4cd3d640812ebaaa1b97f9f90e0dbe9755f35d40e375328e9259509311096ad553055754a39353d7d8a0d01d71e5348080428d036785beeec15cced256050918a4e47e57f7205276ed078e3ecdebc4333c1ab9b75750dea1d68044c06f58c0f10aed0b6b3729bf6002298e1b0e43b31237f2534db64622fe864dd3b89bcfb0999eba2aae642c030d16bc22a5e98c6c797226249aba31c92723c1d1137fd9105b238aa145072ca0199dce9a819a14778d7e5ff13d99a569f96cc0aa45445c44439791b9ecd72419e06bcd179a31c7943ac2a3257d8f7579d59ba02505edfaa49f491e70d6d25a67d9c43efb280e25310ccc42f2b698b3666ab2bf1df8d4f860c8f61ececae4b84d746bdf40096521df270bef80d503aae85776d75fa090a64cb83af5aacc872b89bcd81524b427b45fbba8ff51155e382533ce2ab13e521b72599b02dee2a7251657490c755afdea6ae49c18e73cb1fd47abbf795a61f17956bc817b843827241fda4c13d40c70e3aced4ae09858e18664f867d6d46a12b0143f136bc34930143d78363766d8e707369a42f9d6be15fc839598975c43947ff7289baadcf065af03fda97e71b8baba8cc4bdeec711aa27a5d904242ea16333ef8797b9336c6727130d5494906ba01115e405375abc78f0124a615716dea190657cc8193fdfa575d4e2a0a71145d9fdb2e3e3b7c0111bab0029d6a566430d9e5341cf605d7a372c805a535a5fefe85c012cb91bd152a5bf90473983d80ae6474f6006e628f834779f32b51f7b1577efc51e7c3b0445af2831151c4883966323e72cefc1f73271d89bdeb8b21dfc3a0b78d9c881e4c34faaa3b91da8b0b2170dc01efc43a3bdf4e1f75994bff3d925dad3c5cbb1e564b8d0787b1982e8da34bf09d83ba64fa8c457ba432be706407ba77a5e41aeb421c55e4ece2d3f03aa5a8d3dec62bbb04d15a19aa165bf5934f17c5736574b5680d9d899e4671583c5953f29542ca8d526472fc940b2218e3284027965fc7577ffe353b3b966ab9e1dc310520cb68248862643f1135853844c1359b253c6c118f09aa9a6bc11c34c9817d526e11c0dfdbe972fcee0bd584560744b9d47ef68d533b1dced027629a5400b36dcc77766e849230bcd5870e2464315f9efad619a24a9d12eeb00342b287e6ebe00a5695b0eacb7279bc128caa215f67a14005628fb1f8afa190a47ddfe481c394a07c4df2e6b872f1e1bb95e3377c13767ec7f8dead7102e8f6a077994ba477bf4efb8ccc3754727704d0e5e52a130efb7cc1aa0b3549eef018d248ce52bb9c9c8318ca5245cd72b79bd81a38b9845c655e7fbc960f90d92d3b3d6bea3e058bb8b68104f8396b6c8265dbab0948dfe4b1d3dbe8552d9c688450624419ecee9ec0c6fbf52b17bc5271e6ac618c0d6880d6cdff31a7b8b039bc403b221d564bb7cc45f30926e13072791982f34ae5dd777ef9c58953fea25edd113ae6eefc806336c4dd5007a3db7231958074495501e4321924116be8610cb780f823d8e74916607ed3336026b972c067c335014850e952b686c03579660bd21e55584af653d27dcb3cde6b4d1772cfe0a4592a7d8b34bd03b136f2839a91ce13b9384e117488c3f201ebbf51f27264ff01db2bfafcd9169f18a7001e18853e6801f815c22eeca14a62bb086203728d43be92aeab98e885d0204e25f6e419db1e2802bcff20f527d3323e4863f236ff20900237e75a32bfdc1b0da3c628f18ccad5d32e3b5fc0dad7bffed252637216b3d1d9cc74dfa329eea1e9c332ffd5ee27f787fd135388009e8805f1841b465a1e081f5b6a9d5d05ed9cf0e543174fb2fb65e13f15e6fb740a9ac0a6b11872a57315c73ec4dad3cb6a953b6f5c4005be20a9ba1a3895e740c10f0dc1e15902a850bca7dd6de985b41c663462dcee6b8298101b12d16e5813e8b07d32b90972a26bdd80076a0b3c836553fcfd93513a7bb0a917eafb8b33aae586c02425341155922d836c7275274e2b11dad5c21a17a45eee9637524862d706bbf5112a79720d83606c8c64e5b4cf7cc1ffbff94571a413ce0dd728f70813a10b119450b35aceca13283d98ae6c7b08ac8c73c06a7035699342e5db6ede1cece48622366c72052081e4d2f16ef5a3438fbcc78651c8960a847e942aa04a1711f50f7d4dd772f3e286f4ee80addde8d266e342627c2bd5664a724f11b03db1cd2aeb0c63867235cb276a786c67897c4071e8b390a86bf38ffe20582e8aef0d2487569eefbd49be3f09db23e2ef85168496c2346b869e814759712d88fed8a4103b080083fb1dcf551b4f3c36c53579748d729d9aa833bf2fa5ef00efff66c5490127b2bbe42b14c5ae811803d09471fa714d517ed26f91947cfd4c8a80e611fc806e783436728ddb6b62e799ba71837ae8610c1a854dbedd4b2a31c56229711c6c3f8430f572792524a9fd0970443a6b7831b53647fff9d0cafef1c23a40b966323bda548872a751d8ffc574e6ef7b40901f2234b7f0a12ed0dd7005ad71fa7c69f52c1dc172c893fbada53e20bb433d34d7aa0d31a4739f4440393f6407515557fdae1aea72b760410b43241d427a846781dde8689bc807a2803ba9667da98eede8deadd6722dc19bd5bd078f7f055683068f2f81ba86d031c3bd8160044b090ca4eddbec72170ff434a0c78e949d53511d649e165ce0f91a702e2767fc42744466afa6573b9179561b01bfeaddca5ea8e3137409e4849533df271e5762f0d8d838406fe21a37cbf3d71074ee240ab8f21e45de3275d6dbe90f2e2c83b9b53ffda32b3cfb551ce44fd932aae74eb7c57da599f9c03afc3dbc272cb8d5fa8f748ff51851e3720a3735c737c7b5b60f0e1354549510e6fd3f55c4da33573143875d42889c991517c043e27432a3fb3998b759f3e35d5d1709356d1b22f8dab20d17d3b806214f69c06d244eb9266dc967e1e7763bdf81141886f604f882908cd72a5523cf3572dfef0a3b42689ba69b5f491fd3fbfbb93f4729f491af92da928f3867d7ea7d72843ca8f84a2232f479249536bc7afb48b1da7c8182f6e2029f021c1b56ea3d3a49c06cd396b03c534c602fa6fa1cbed6a59c6160080eb20ceb1a4aa665b38d6ea50f021835a1819d74e08080d4256f5dbb91dd922ebd7e090dd9af90b08fea07bb20176328ce2683e37e1713b89a945927f5fe5db7831c40c881757d31e2c372e9f36bae410b9976b5637a7ffaf9e0f991e31e421faf481e6b73508799811972cf507eea53d09e2285f5932b74e617f1c3b5f21e569fb7093a47830cba5f5904a069934b1c170af8209b617cb7b7cab03e60db15b3642bc6ec0a42717df62c7240020ffcdad4929a8848cf16a1341c248c95b5c4b965927c4e7d476db0101c725ea97c6f75f2ae94682ae85694e173a783174f1106ac0aa8034ddb9cd1238921751f2b12b8c9833a0890d78dfcfeb084430af2b366fd5d14a751499f5770cc726d98fe2efcd020ee19a0c2e6647b81b537e1cbf660465416bd345c6bb705865e0d4294e2a4076f25e863f9a5fe0fb82e685587eff2e9960a35e0b2a534986e729d1306e320b5d6bfd4615c43029aa4beaf7e3f007c583df3eb3bfa68a4e8430a4593f65da5a9af80939b9804780651064f8a58ee68923eb64c2643d7e16f5472270f37c66a25a5c002b2a24e5edd5fe6ba166583535dc082687899ed6cb61851342b6103a4e30d1fa1f0e3b92873ef2712e814ecce2cb1aa262ba9c3615e2372aa1b6a4a25f837d8ed239a099c8429da6430b3828d7f0a0775b004a1709fbf22c61690049728fe6464d224ea23f753c9b2055e405bea3e6e0a8c163b323b8f72eeb99cb33c9f2ebea2955308b0d26c8fdb75cf8daf4f3ce339253b0f8df232377b86bdf80dde6acc2b5f11afcce8e1865a03353c7b9b494a8ecc43c990e6ec7257e66a8328823456b6b04fe93708cf92ef55851ab8eebb4065614a06824461339ea7e24a6fcd96a98cfc6b7d6d24d10e62ee1285dbad424df25fcc2b9e65182df0efcfeba8822203fa06cbde6642bd0a5576706591124f42957e31ace1279d72106b89478f7fbf3afd346b1cbb53c92fb45874d230c56cde195fc36bfa3d31727c604df72a93687375b6403eed63a70b02e4e6b7f7270b73b222b988db14127210a4dd8890dc94a4b7f835257da1009ba305a23a874df399ad4bd9735a0a1a7240ffceb4e840e8ceeee3c438bf24d283a7ddcf1198352384ba9fed5c8c4f9872ceca0a9ad75faa2b4e5917b482bb7b0effaa2c366805a157aa55288533929f7258c62852ee08fc062d17ec44ca62fc5bab2091d52536d87339efc46dd3fbfa4d27dfbaad4df4aab4b250391173168c414fce38c33ca89a845f6fb97e58c8a07287fe5209ef83e96794c94b0aa6edddc2419de109ae68a9da9277122927dad2727a1e3f061f190bc5f7f1550e623749ca4f0d0d2f8f3476289602a4ddb9ef9a103840ee57bef23142a45b29aa6b1c0172ca9a16afec624f0db5292cb0c4228222d1e22f4ba86917d8b2ffdf247b63ec4a206108bddfe9f720557fa281a4bc6c72882f82af69f1a2d740c83dc3fb6a13690bbfc2f2be4d91010eb304c2445c6e72fe8bf265090f4b6beaa95bf7d4e1aae4cfea35ceed6a8878b78a4ae2e159e872eba6dacd6b5f0a6f85f236e0f0cc7d2622d3ac608907257c3eb7bfed4353f772f146b1eabcac02a33704aad29e2d49d48bf221b483216434b48c17f927850472f68d20bf7dc9fd3c27b626ee0423dafc3817b7d1b1761becb8aa57184897b866d9533f48baa5a7e83c560658dae50d15ec47f941900a7423015f7ca97fbc4969ffa64061d2b397096f2a58301c165b5785991f36bcbf3494355bc93a034fe83865ed55bdbe2d6394ba66ba8f9f969ed16ccd0474068267153953460a69cd5a27027f37aaefb80d1f3db658092cae09d754587a4083bd81e1e071eee5a389ff72bee65b5ce53bc70f8f072e75cd9af86b1395e51dd44da301e37d5930135c597252231a5cf14dc5e96a44eddfe51bd3bd3494b9e538643b40cd61330f47ab4c72dec55edd042230c54379b49e25cece55a5416763b012ec478982cef96594e272a1e4234af40339227da67e9bf62dcefc694dcbda103333f992f2dd8aa62be57257295450631d0a73c1c020a6e095c06c609feee7b34c2ec62122047493cbe072013502d606d6e6423fdc5ab0fc2a98bdfe2109d6b1067fd865688246febe0c72e37e263477dab6f97e1e0efb4ebd0409b280ff53692152a57fc6136193b1f04bd23d2bbc31928f221559ebf4bdef8ff394bb2ff0246f04906c06e395f5a71672ab67770b318ded7312d9f61c1d58d78a8800aea3696b6df77eafb4cb9ed1637201a15d4f252dad073037c428bba8f50dbd97ea1e086ecd61cbe482869b9dd22ab05b12127627f88866130792337e27b90afb7897491822359bfdcf0a805cab11a59b8b96bcd613414bc7296c1dadf93107481e90f180c5232289451dd5b21e4476dc355498b61997d88c30286f89478c3bf800810435b5133888bc9e4bdef072046ede12e5aedebe4a9df83f54efdde2da669a33a392dbfd7870afc8dfb82d72bf1b31a23dcbcee66a312d5bc809d610ee532b3819e6610c5c66761abc796d17d38ad0833b37d67d6548817d40b4262c1119747bb51dfe991885990c577db672b84f963bea55b495eda14e55f9adede5a3ebf70abaf634f5b1f09d2ab3bd16482460d046ce1bd60ebaf25fa05ca5ffbd7516a550fb4acde402e5dc1e5a542972c1cb184f561c42f95fe55cb4c6bd733c97c77fe51df375a9d9731c21c80a4604bf5a9c4fcb6e09dfd7ebc7f7c0ae671dd14c09af98504a16df7e735384d61b72b22d6fdf46b77fc051f327099bb7ecfd90f16b47dd07f1b9dca3f064ac8a74633aa82eab6b1e2a38cb257c0a87b979c48ec0739a04ca9a651e0950fe8992551c105d3428164c35dae5cad30648c2689095b1be25b5156b696f4bc764e69efe4490e8fb56f76373c4c12342d7333c9923eda1f938ff501b2e68ef4c4fec854b7205446837114812b357fd535fc0597c63984c88a5ef9202a7fa1285dbc89b5121dfc34cd7aa393a28ad851566adff83f4e92c4a9173b301ac4e43f576d4699f305455cb2b49b07e21d96189134f23db69551c2d5cbd8dcadaf7f6e686774c9b3b40404a54c393134c311189f36be9a13a6bfee1de0c0f6da7c0ee1601d3d0901f1592bd99c623dd52438ff34e0a8e9c4cb17d8b80464ecb132c2b6700e4409c007cd9adf70249628f07db4fa078bfe878431aec579448592682ef632b53ba8b728e8411ca18486eba1aae1facd155dedd209cdc9fdc7ad4b7eade68af77db503fcbebb0b32fd32df6662ffeaa4362cf38484e5fd4d066df18fdcaca32e22c9c72000f69dfe1144be0850aa6a1d7e0bc54b14b87010df465f67285d585d9dc3b72084d6500da3053b384a71c903172de36f46f8750a3d0971b6ac055fbecbf68729c94c704da9d776119c08845636aafb22dd027b097353893650062f8d2c5d3723d7448c22db1209d9c7807b8bd8f4f8101319f59556483efeb545aa59509bd32574049fc0b5593ee11d838ed149f5d175b86ecb8aaa69531b57585261bc6b672a5501f443988c1447ceb1d437b4f57b10baf18560ca8bdd3f2b67217aa8ba5722869dde1cb234b239981731b1aeffea46fcc831a30dcbe08384afe6846c9bd5830c5b0e6f341c5c2c5c64881a311958665ac26b94678d197f376e41ff6f243728dabf40a66a244968cf3f9bcf3a90cf6f2ca362db3c67df7b9cc39f0f7beb11a78787164da9d89dfcb00664edd3290a779e692633e8d8ecd804c43f171ed250b1e033da2b09311cb073a0f98940fb443c104486f25203ca7d0a7348ae6335e30a0c43067da514aabfaa73f088f8e4e388a93acd10e2c169be862880f386e967202b79e434b7213bf22318ed7e660894a6dc7fe5cedc6792b8dfa2a44b280da4a523391b584f375b7af954f958e9f47f950c2045aa7bf879ae88f565584b6b4722ea59378b902014a3efc6ca73f7cd84b28ded263c0665ef5ee890825213fee72294cda1d086a7db978c46faec29874668e5a82fc4ce79ba21460f37f8c384f4ce92aeba34b70504aaf414af35ca016ef56ca3f3f1208dcc60536b85c2164ec72240c047dd529edfae02c1ff3db731c546f5368edd3ddcabb3bf6980617112972faa73068c811ca671cc003908b0d4fa453e0c8f3c952c2aca8f973b9c9cb92723f2c03bf931329a86131a681439d0c37574593f318e71d2dc14997bc7d16a00696306c08b383279b0ebbe9e8bf97e4a109ec4f239662f9b3decf5f7b80b8681b53711c44e323139973b4f074960e98030dc0e9425a27ec1013bd7f77da86b772e75028b89819161fe715279b62bdf928cb69c6b84ecc1e66bf53442116c66b6d0a01513e0201a8030078233dcde3dfb69d749715c5a796605ee582ec0a170772b014ca45f314df662366c14c4d121630726b2a41f45d922d03f7fa788064957249093199b3b74c490877026b4757a6e9c662de75bdb93b0302d6f8907e926672f663a55b47d1b724fa5a36cc31a101e3490f0600b21fc21e0b18decf50f011380ba23a5748c84c44a695ed3f9522836f5ea66de486aebe8a52314959492be4255f2f38788a4010145f6b31357f2a6e6326ed4f5acd055ca48c7a4720f14bc05657b9b4560a109f9f2d7359785586a8382b3c35a8da3ff73b4f1385473614f17229db53590803f3c873f37b306d1bc0dfdb30dacfab57e880d2c672f522348472cb021251081de92552ae5ded4e989c6ac6e40761f1ebca49f64c71b081689e723d997311fc9bababe37eb0cd2ad57122723eab8d6b012150682a744499683a3e72c66508cfd2f41f556348f609a08e22434d791c2f2c1105facea5aa91946e72079d3c27505340c6975cd314d8cf869291884431fe506dcddcc4a1cfe49cf4497847f82519c62804fb03580fde544be3c143036f932c6f92909053b7fb66cf72cc2d72d6d4c34f01f8deedc2b0afcdf9501e71b283dab9b615e74eacb0ef3072f15de8923566b3b74cc05a8410467a4ec309179b03cd1709838f98a793e1a11cc9ea8aef70c9b38f39e8841284f8cb6fc1d9d085ad2a2d08a8060cef2bd46c7203b7c596af85d0476984ec91fe6e8336f626a864f65817194e03ef860bd9ff725a7db7a804ce2d26a1a61cbdc89cedbc5a4ba0ef8882f84a045d622e90b0c2413e014df1bc11a071dd555ed44b839ab2f0b1cce6d7101340abb7ca4dd3481472028c73c6843cdc719ff86e7e832e5c8bec325a1fafdc138a69df0a2cde8372724feac6569eb1d228f0fd9417e60563e6b003d311bde4840e7943c4722f21b17294a59b60fd362b2fab7e902adae269e3f4fc3566aee4302b395dee8faf330372a956910535a596b9cfdb57881c56443c58429bb2801f8a0dee25fd6151ab1129f63757f1aa591d6f62c8bed55f26fe7c62d785fae73f704dcff942c8b0159a0232e66f1ab92e491ab4cd93b81408aa40b3fc652401279e0d05f010c3ec21d13e667d17bbdd69a87e2e35340867ab6b626cf5675994095ee3931bf3feafeae07244f69a88ef34d9b6647b9d2a812cea39ae3a1411cb1a910bfddfb62cee242b03acbd3f7b07e8a8c4f5adec3b343adca525c6f63d4aca1c1cae79a30930df4d6433f9d439d738723c2b1dc20a970c5cb65c0ac3072d1a0570693380f7f935b428b7db2e2d2c3526cc162c5ff371b27b365cb179cf61c232f4c0d24731460f0524dc0435ea0bc8bff94bea18c6a187233912ce014dbc6d7728dc836d465722f21e57d5a407a00a108f7412558202a3f60e7b74a9059072fdfd628fb73c8ed3b172692d69a1eb1378f1db8479bf7c54371f69b6a8a8666fe594ac30c0541b570e1dac25d904d510393abdac6aa20568be3c21ae013bc043475c4df047fbf93657181b6a89ea37defa1886e728610b1d891ed92458bb0997953745d3a021fc8c28228440300bf45ed3ea0c8ff02569579c4cfaad84baef46137b27ebf1326f1f3b7250358b55d1145b976ae89e8cbd7c617a29067961ce45d6097bec7c2e0a02bf31f8573b834ee7019569b3b9b9d22470501e0f2bc35d24d06ba405f495ce3a8872153893721ca0c55051603ea52414cf85dc853eb96694633fc6826f0515920329705f052fe68deb8f625363d39fb7045666fcf1367fff20617a4eec147b8fc8720e91736bb329a6f55a7a3b101fdbecaa703c00539779d3bb27bc1587aaf1ac56924a34de153ef2c5b79d44dd7d819a8dc1ce48aa4c872606f51266708faacc02e3eea0270980d87cc1a5e54a5339739413b793ca91726c8df71a5d36d72111720662687dd698324ec0c3471c6d4aa1632b11c0ee0bb8c683c27601a3f0ab9c7233e17926eadaf8fa0db225910cd1a33df0f97835315e2478536b6220574b5f72753bab4eec53d936d35062db35a9ea9a343b38e4fd7081f8cd2ca06da41bcd1432177b83693d348b9ae4d62078c4ebceaa0573dbe854a1f87e7e4c621a2055187deecd173219c9a14ea3970e0088e3ef65eedce13035355790693e744efa4f729637cb0e851ee529f07de9fdf29f864585629a201d3723a8ad74eac3973d6903f9a0e66dc2c9fcc5b7556ab53bec50bfad9bb961245bf9e45c0c0ea068814172228234b0f18ccfe789a4cdf5747dc16f5e150f1c522b13de94d55a85d99e88720859ed468cd5461b78db182e85c1e074bf0e39fd57dc2949944c8df5a7dff170d14986e314842724df5ab6b7c327ca4439e9669fb218272032170b426e154a72a8f1e631dca2db85fd17cfc3e6c2f24fe0d15dd997a03d348ca1d712295f20055db5188e0205ceab7fbe92974003db6d942b34e0a6420ffd9f5f59bd5a4ea07292facde548c750fd2fe5e7bcf7bd995bce05aec9d945538ae1989f3de55d1e7282425ac4b13d969ca8af25e9f29ded329d931b27ff3b2c0feb2c488a0f0a5372f4f054faef0994adf7da70ee11528f41e117ec34387a1de0d54a10b7a976777226b91b036b820a6bc8951183c8d37abb3ac0350f683de0dcd7c0d9787415dc23eecca1770348a67c8f49ab0b228137ffd52d3bc5b20a1cef2040d58b151cc672162ce330937319174bbf4bf960f5b6a6db2fdb48db21a9f46459803e63cc1772999203b49f5cb0ae350bde2b97775d080dd8a68d9aa3668b7b36bac7013bd471d680ba0850ef1f67764098bf08395b9ae171d63be4f71d35bbc11791909ab872e60bdba6de84a44d05e6d34822006c7d7575decc823e72264b47c46da744c972f24a175eb28742c6c1bdc1f22f3257952c7720d7025354fc48ad44f2b038ab7272e690e032418f5e5366687220c122152f5c9a9ca4e20b1ae303b542079c2b68c616e118158e6afdd19e39b12ac5f841f9c34de08d3b55821bdab294267ac8666dcab9dc7a4d116e927f2d5f9c9789ca0e139472f4f5de9caf992d26890dce7209fead379b2372979e0545f188ef735c397d09cf6645aebe7c4a7c43a9e6e3721702196eea0c9335a3d65c347cb034e02fb729d642ba94814c0903b922e02372a3449256577e26dccc0fd40fb8a43ae3cfbc7a2b745c9fc1ab7b120532bfe672a46e4f5bd9474ce93102acafc021a87d87550f769dd66139e047dc9d0bdd0835819070fcc97636744e7c62cadd849f79691fc36851d94257dbd61cad88635f0d63e2784fa75e8e5ea1be1e115f5947a2cc33a9b67c9abe61d32798897dcf7f721f416a64fb081211f909c5af9810191e93fafcb6c4d7978dadf670a3cfdeaa570c6e1bc9bce843a14d82c1fe3e9cdfabfef63adc23805f5110c007981ffca95adbe67d956bba5c7f812fa84b3104ab6189dc9fb8252199f792c00e4c8f06fc7211512ea286274e3d09b839d5e7e6d5717d904d769ecd5e33e55efcd7f02021626d21877f4bf731bdbe1423ecaae13d262b69827b8134d39903a321ee5861f472e9e083074f7ebb713f3e81a7b588ac2d0575ccba28ded7ab72dcb22ce288d64e3271e1bf2be86322aa592ca06d115dda3e950d089c67da1c654d3860f627dc72d7130b5175ec249e0eacddc5cda04548840866315e7796b5342ca3d4d785d77297a1fe66b33427fe2bff7f1088400eb2784d9fc183933a3b14aae6a8d11ed43728635db1b8ef2c46133c6c6dbedb4e8479e59bb601959493f1f41630a52bd5094c54c0ffa3d39ad119e703736085b17e23b6b9c73d4ae8abb9ddd853d9d5f57294672e66f2e5dbd0677873a1e9d9f55006cf90a5c1cb23b77a7493d359dabc72b47f6abff360c7e1e481efac06a7401f5faee73af0888038d644428b5a782f01f87a01f1e95f9e56b1e205f56cae7baf811256a39c78ad4a7346a6cd5d4641259155ba31170bd350264df1585da361b97a03a4cec24a4b3cf337c37e620a447298a4c77d7e3e524551aef7c392fbc2e1dc2f721b27771194b060b24584af037255a21715f50a439c84fd5f5ab8b53797624ad19b99b37ff90ff458eb01c2a46a359faf37e92d70b125d9b1802e9931742b272feb8fc846c003ebcfcbaf532e725e9484afef89b9f697f1c671991720ec16e9bb1e32b9525026befe07238efe409839a9ccda50c7477b8a0ac75a5f9cf8603e8df4cba523467babddda895fc8722b13e98d41e184b18fb4a2c546df4112a2a8be3d8f6126305da07cb0ea5f7b72556ac2952e8f563b45bd6d7c2a6807381a1f40aa0f0e47fa0003908b19139a59ac4df4959afc8c8c96bb6b58d6dd16105c0951cb262f43fbd879291740fd9b72aead1f0a8077394480ee753dd20ea52b635d52ebc2b5b4661fad48d42909351cfd7797a31a36fb09f644b328079d0c874ef326724d9ae6248545977dced026725dd2c7689012b86a21e610b20ff89c1e9ac90faaa80ef49d2c832f628163be59291d5c3e5460ad19bac2f379036394b0048f89550e890487f569e9ff65a383722e8719d0d4c19fb891c75e5dd72ad45df0e62b246f6f7b70791b4512aafdeb4b8c29045024268d534325d80c9a2db9308b44c035c589560c56c1164718ba3f078809e648e3e50d1e5bbace22ad75474918cc44356c564ab269d62d716b23ee724550a5ceaf32a25e472a09aaaa062bf166cef3c7d2733a1a1cbdce422617cb722856637e4c08588fb92c159188125381671fa9352c34c7ec6ea90c196a23df5b1b7997c1b89f820441271a3b0d29b263f0181594dfbe73384c4e0c0496bcbf7230219685649343ece79e6a6825b7e7f2bc7b13d220e24d423080413dd8364652127ce4f814ceeee6bf0634a7a20c09afddc0e474a14e95bf6f5d18d51cf7a072f80836af04db1bf14854059868849f17a74b186c9cf17dbd25af9151e84e443d9b052eb9e5aae89cea7b0beb34ba84b639cedd84f209f1b354e0149e49313f0288163b524b4814b9b7e30fc1a1051db9784d1abd1b29f594b35c242689f09272c0760ecdc5051682d1bbdfc2283bd8becea3674c224c05293deac3b81feea172b72a0e3ba2f71893290a2b477868790c9856fe271585c44ba5feadb8038551727a1cb1cb2d2a178dee8aea50d1c9b94843144cf51b965c18267ee60d011c810a5b31882df594ba9247f6cbd46f5be5d78bd6cbcb7fbd2a50f009258ade972672a710bf6fc3bc233fda359e0c4f3cb388d34bad75a13813bcbd10d4b787ae3c6492502c1ccac50b33c160e5d53564a73071d9999b13de153bd3af2ad84354951cb1bb6a21972293efd5aa6e8d58d5d1103dbccd140cb80ed98c90991242c77372bea8c638357f2d3e9dd000a9347b4102df98a991e39ac37852cf9ed1275b7a72d6d6e7896c13778c386decacbffb81c8d5b49760d0a4bf3a2f0522c18d8d304bf9011442e87c68009f9bc2b95ae401eafd729c2326d59491ac3b1cae39e28072fab4f8225d2eab90889d9f9872a92c542119c9cb0e9e388cc5012c0ae47961720e66486c7017fb7f044a838b2ab16ba4b9bb3c6c970d36c7df12e9696f46ea7265e0ce06197e6ee197e4e3e17a7d2ceac9b50764c3090e34dac9ade7e0ffbc72d6316aadbc7c87b93de097d5a6a66a74900f18d2616d46f84ee4351eff9bd372397e685bc2cb6e220e9b5f935e9ba96cd32ab5a850a3261d69e94ce155ab8372c82f904816d265028357cf7571ec2f8d9a1487b0e3f39dc676c1e83047f45172855120c96a9bf55f129e375f249f30355fec8ec6064870b944ed0c8f09a8c96b679def12426b533127b1f1bf59c59091e251011f5d859fc71607cdd96b754e4f8742723c5eac10489b267b0de9a74d0c21d547f2fa2e720ae3cd99373567d34419ee8edd279f3548ee7f869988dcef92f4b284224b47292657e784df80b15a72d4cf99cae9ea6eb90d2d8da96a2853225504d70b4868c49c9e580adedb8b0b72d7819edb2629d525da922cbdf0632c961c8f17861601f9166f9773a573e884722d547bbd237ae86672ce412ffbdf8d7a26017197f4ea0c3184debe76cc91c87283621178210c35583f9557ee0629fa967f7a4e2042fd6e4227c55817aa9d2d1ebd03bf97aa392faa9502851b82f41e2a03c6ae7e19f1cf67be24ce8c9734aa72bfc9b73c43e0d5ca862805b3b3d40743a20aa70e886d7fac8bf0b12e38cc637240915f4bc7f8d855651faa75a0aa7825a67d000f85c299e76956820c52c01c15b5b657af260251ddb1b24d681730743d85ae04054169b023deaba9814ba3c2726862f1fc43252dada984fc4cfbc79b65dfeb38c38141aebe7073dd52771b8c14aa253c32f6ba23ad719cc689a9bfd4934623802ac854490fc77e593993ef5935b93a4516482349b3ac98b55ff98796f38523fc4ead9113d8e17d0fbead184f2429d20da948c7eeece58ee4dab4ced596d111c66b810b9b0ff44a94d8538041185dc30a441ccd4e62846d0073f892993aa429fb3d51682801cfbc63dd0350e172b187c89bf9f7bd74b132aa2868e78a5383a87de7794b1ff989359cdd85bd510b3344d8ad9ad58c8e48509625956ab558329b5791153acc5ceb53a7d15ae82e720cb4ef09618d4f4d43cbe7f43a41c15c3086d10afc0496c717910dcb94b266049b6bcdfe659a22d8f0515fb1f8aec3d221873413f03d8f28c909bbb89659777224c8e773b30cb549bc417df72d7162711a38ac06ca11b6b6db08c06b052eb972bf57baf61b1c195d2f9993207acb9f0c80683f5fa0d1f11a6bcd6e16db4e4b7250b3ca681f913f2d4fad8f74178788a7380aa60bc8c3b551fcc7008caf4e407116bedce06ea8d752d1fb8f80c374d946d26811461e921e77e62966f5c87fd35348407e8f02c6d333855c9a08f0a03422d849b526af5f6f0cc4bcad4045c9a77226e91b65673dafcab63c608dd5a90ef1f3c929da302666a6cd8c12f880f2c81893d0ff85e40d26b6db0ab9c99e10951153bc5c7956bde7652a40125ea85ccd72d00bac371614754e1b977b661aff4ce438b1c37c1f9133f07500ee53e6659c53f5420cf986f8aba424b769d614f63354fb3b5a33cc218c92f67f80abec90627250c3bef65e7a7752e720bd986228494f2ea11c0bcd2b0eb378dbe13a1b11452ced33d92e622a12045766e6517f666a16250b23f4fde8a0a08795447757262172b9c1b09b4070e36355fa3c98d9f360efb65c2a98f0abb3e3d8aafee9fb60ff72d2d87e6e6f936b411f61114acc767b94345d8878e6fcb4549d79e487e097571222acc82a18ec118096eed57ed7f70fa9bde118d1d02700df455c58d32b3b5c726bf6cfd8dc00bd75d0d69a5096f57b71033ad16c450bec8f22b940b5f75a0a725f7b5279067e2a8b4a295f563dd5b4c06b8bf7d7a330ba1d85a7b42237adf9722a80088b13fa4c677b36057482d2952c67d485ba8cc187949088177fdeff8f1754cb547caecb371e91bcc20033d32e4520ed94dd4c5900d40fabb666f415f272239c28b33321f674c79e3beb9e95af7f78cebd3a7798f866487610f1263fd24378f4287efff93667853d3bef711b104fa0401719c2b78d7986f520cbdf53717258abd1d41035f19ae93730543994d13eb933553bbdca723b592c9b3d35bd8372c468f1b3a39f95c0273f6c0abf9f817948fc03398fb5d3fbae58b7ad60ca8769d6b1cc01a49a65793a71fc692941b9487afdf5adff38a7ff866a62c2b8be9872d5d48ef5637866925abf47ddbdb0df5d23ffb39447d491c523bf7649882790528d46f6de5b2e6dcb44d933d39376a99271d23191e9d74e7bda2e89377e719672ff3ed89b0838e0d323e83c46025145c3f3bc6cb1fd5abb2658a39d3bfcaf447239be1f9bfbe5873f06b7b6436314a5fc6ab184eb76c54c77af3517a28d27aa7266380624b7a919bcab8b43b08750a4f69a4e28456957e4fee2549102e74b7072ea6a735dd88bc9a12096a51a6ce9a76cdd0ed6ecd6e158dd5e92d3c440205872f1535c9ae77e41d761ce0660d697277106e12c3566c9fe7e3c3a9fe96a8ce5720ad70cea76eeec9f8f7389bd43349b5f796e55b9c9d14656c2ec4605ff762836e31014655756d82f51f76a8c98b1096b9e5a7974cae7eb7f3cfc86cf8f9b8f3e00782412b8993450771d490156e680babe43213abfb5cd661a84b21b3e1b1f7222a4bbefdb5bc1e732b09bd0470530044a2dcd1c124b16878ddb93e68aa7a4516bc36cd6eb160e8f2d49c44e7518f5056df1720f1fdd6d713522538972fbd45a28e465048ff30e8796cdf8da7a7e00c6e3307b8fb1d042a129bcc0f494d112720a7dc94fd02b2a6f975f5ef595544867eee151e8acab655f9452f15753a515725d156e362ce4c0a7b524362cfae1c164247850b8a470829c2b830f6b64e476205159bf888928bcf3dfb0e81e1742c4623cf5158c6b417fe17c5fb84a9d3c0d7292a30d3628cd1cd21433677cf58f17c8449f15b96bd9e7e88d68391b79699c7295c75de1761c595ca97d1eabbace7da7d98defe328a212af0ec5689b83b08f72fc0a46913be58a3708e2d3fc02fb7f5f9d3b3977c0f005cc1d41086cf25c0b269673646beb899043c4272df47e199df69e9d08e16e7cae5beff4cc112a33f641ef49b5571d0c62f817f2fc44e0d65a8eebd13fa50dfbc6e652613dc012c43071ee4071ba948ed43ce5e1eb74c0bbc15433e13c51466c144b072d1c9567b1b315792ec28b53e88c385edccb77d07c896339186dc5b661a9558960bc1dfd2d4922e677c0917804017445b7ddad4db9d78f40a7ca62b0ca76a0f1276204ca63bc7272bcf389224cca7a84c07ab45b83f593d9a155510f2c80595f8c9317f477a87239dd0fffc8547abf0a83356341fbe9ad43663cbc1f1e3ffc80433e3b558b4d723dd98b1241ca95bc8bb699f76de082382e40acb68038804136c0fabc2172ba6039c61dd2ed9ca5829d4e680f5eda2905a7e5fa26c96405d5d0b578db3c422b722574936b06efea3787e9977afcfd4b2fc708a59f7299e600e7de176ffa05747224227fc71334398a3d764e43077469154c2e28e552d83b54ff5b2bebe8d91d72427d5ae02c1df61ce37cc753eb0f86ede126458b7b87d9c2386f335172f17072dc5ece7d122a1e0c83fe163ad2e7efa63cb49efd7949b266ed657980713df772051724c8057074d70db4445338d645756379b4a3ada5e32721e0406d83130e2756c5d7b15cdf6966283930c55f22dd43a7d2a2b28315e2da3c585ae9e1aa6d72dad4923779ca37c8d2d91ce82ce4e0f53a2941bd678f32ac3f50309c7f0f9d1555e034d160d144081eff6ce14ffe4ce17160e01b494be23e3ccceccc6d0c1772150b87ff46a50559db5d2c3876614974846821d6343c357fe21c6ba95d05967298c95321cb2bb095b4521262d284133de75f52da0525d19361a17008d11e667272efed05c734eaaee7751680d212643ca7e8e18d15d1d5c7fc69db53b73e0430c84fe715b8228b5a4dafd1386d01371d7a702a5b11d9afb820f7130cce8928724fb9f96602c7782fc323b8ffa20caf48b52e400d4aaa7d7f574352ec1973b805989feeb696ac7ddea7b2ba1e8bf0b1b28c4c0cddb80fbf42dcdf6445f5b5f5727ccba712964543e48d06aba17d611896d34fa5a443fd9b2adfc9ea8e5e64f772f52cd3ea30107a5fb0893b0270b8afcc58f5f1e4d55c722be7ae92ca26c44f721b13a205449b08d522875121eda04ba84943e65ab517869e424fcdab781edc3e2010d609ce53e9f3a7f840e08625cf49124ddcd2480b29ef7a581dde47577072a12d296fc512c5402fc23aa04673bfa4e00fd0fca9085522e3b169a0df03ef72dd8adaff7ba51e21250a79c777bad2182754017a9c118e8e21a0a0e3ddf5b972ed56d14136ce253a670c929c699ea1e7a4d109666b3f2135aa39da4d49388672c6fca817e775d5bb55c7e82d6bba36e7d5a0f03ab3926bc830d44584d66c8672936e2a4ed4058d712e4e2dea0bdf57dbebd1f6a472277e5a232cade5ed5a3f7230e841e38c0bda1e217c72955d852cd74bb95e49c3dac3494f9d0cf3abedf57283b3479d00fa8b95b172e8e79a51286e0408e46a4640050cc66c55af5c8aa67205632ad37c851dffaf215de87e2221167859dbd74904928d90fed68c07030e727ac9cec37c6ad97b799ac2466e7711223f6a84132aa486bd84a85737c5b7b93de833c5adc72f1e9c8fddfb23007267a76d75ad3af88f9f59d53c656bcf358372dc6f3a72060173274a44449cb1d191c5219d05c7283f20017bc142841f4f517223c3a2ec2526541ef12cc0d4625b81b992de2a4bff3c28ab354ef0204f6fba4db94eb3898120c6a3c7f0cb00419b229c0e63a5a9230672bdeccdbb49f09cc942d85b1c4980dc6c76f8b7b03b6fa48151c4ed065ad46ce8b975c00da20fe43c7252f1596150829e0d094c8776d1c93acc6f754fd12cd8393ffffb0490b9ddfb5a7895482aecff762fa4de7fb19c8219c59e94b2551815c49a58cb6c9761103948b720b489e7bd391ff6f0d157e65901a42a9ca13769f19251871fabdbf60a0f72a9806fb08d0101393bc5470e5116a4446e2fe49828a8afcc54a92a9909f3825ec56733bef12c9745fae510323a921b619f154e4c175e5d613270f1deb761ea7213261b9ef42851aff6fc5a511c93e787b96e2d05d9dada1d5f901ccf6f776072d89af53359ea5a69e483fcc30aa67bd7620cf4a6227616388edd5fa9ed68cf72a57e152110dc09a05098cc895eb1e4b29340877e8104cccfc5db0fa74636e872fa0630060d236013514b2b4e9750cdc162d9af909eb7a671ac10005c4c07727296a72631ce589874831817ba8c3580d60ac9e545eff4cd05158a984e0b251d42243e8b6a13a147d4ddf1471982535cd984b46439e24cac1d77eb031212efff7257b61ea5b5869936317a9a3a32e0055d25737749c133f881dcaa6d37e225f67236a59fae8d0660e9f13a440bbb0408d8e5bef09410e24bb5dbf86c443ad4473bcc7b67bb772eb631f908da3e8e2e91560673259ccd5b795b4023bbabc44fbd4590b24c1e62cee32b9783b76fbafb0b41d5b72ce84332481ee383cd74372776722cb2f4152b8672f68cab0a2bb9821574d54740dd47c43aad749884944dd8d9195ee80a4f2969a3e1e5699d1caae5288892037b32f756043911a87654ebb2b472c6e387922de03f1cb299dd29dd358786d309e59c5820704542b798966aedf672927ebd5b2332af949e3bce6e4303a0b39c3aeb1c56fbeb5a71bc89b3faa24172cd41b6596c0628972eb457fdf0b802eb7e5350aca86d1a2bdb14c3155ea54d72d086021ae07450de9f8331133b36145fc56fc4d632c9f7ca8a4154f27cb39072b91f6e1396a2c7999e4beb1d09feace10da42f6cf1f46bad9b9c0734a74a0b7252beae540586ca4a31445d5d6dcce50d6cc7104539081d9adb6172bd247a3e49e3df85c4fa3bc886036bab1b91a338bbfde0b774c5e0dead58b509baac92e172414321400ab8e268dc935267cdfb5788f6dbf7cafcf89b0d64c553ac2aacd472ad32563c43c36e22d31f39fb0296da3b4e39dec51d0611b86bb0645c98fe0372f6f56295e58adf3fb827b077ca46472e35cedc546447d2458600e6001aeafe72fc1804c6fe950ec18f040ec197292b8865cfd77aa4a857380f8ab164107113727d3188613e09397aae8eac01cfc4917b6677a201f1d2eafc64b67bb85beae572d0008ec03172184ca388c74f7fb60f81e4e386705f1428a1c2e0e518f8a76c1f84b8ab948559df055ba65e6037a0cb7711525c64a1f5cb4e3b37c1183e34501fae4b7c16953c10b0b1412e467e63f643082fbce36ff3e21a9a32a51a3dc31f387d95e3c7f26bc2379b4375eff07e2bbc7d4ae4eb68c1d2d8b680566070086172bc8e6ad8c2b951699123dfd8e3a25650aa3cdb710d30077f603fa40d0194b57232c5b8accebf27dbfdd842267af5979e6f1793acb355a445ca14217dfe62790c3e11a7a5b9405416e7729fd785b58de14d8c827a945d2eff1b57f58b4480f96ed36fd8dee650943ac969606f972c567748c2273012ba4bb2141a214a2d485972f5ca5717e89e4491f7fe3f14652aa3e85e22488614689ca2f1dd07303190ca72762ef699a87449ca545c7230008084f64f588c5f929d2de940b080d8fad1f9574f492b7b047a358086eb9fa8e8eae495860ab8536b02d95bd5f589a97881b027a61e22da21c6a7ce913e126857c6734b669b3ac9dc9618ac3a371a7c1c713a4cd5ae6c09226b8ed67dfd5686937e7f6fb238870ed1b8680eff2afa9c1e55e572792f62426fb08b2eeb7aa9dd31703dbf2561a3a921ba1b46ee80008299e1ec72ffbcdda329e7bb6f3bce1636dc3c3cebf6c8c7f88ea9454ba23f7194f7a27354bd1410491a68c0a6f4d7a3abb05ab4fc382dbc835df66b04a4df21eea6774a721c19a729b7eb9b39cf639afa246ae850c16be6222253d5225584a17a5e5ecf72da585aeb516f8e74059895bfe1c6695cdcf26cfe528c6cea0c4ccdbf596c5b72d1ca3e3ea05b3c0ec49fb7ceaa2b80ced1fde82c6631a22099842ea0d4b97272a16c0ba2ae093fbe126231deb1ed1ee254bd4768754eeb28338a5ea6f58e0a726b19ec73c029f0ffba244a397bf5306268b2d4df9d88b64893a184bb1725f46e8674d182f3cfcde7b038df72ee3fbfffbc4911662d9ef0da378f380b10721772234a12bbc1f8d709cf531dffc69894ffe2150f7e710598d8a89e78db1db36c5dfd85a1ea5f31350be9e1a17115fea882eb36e38551e7f473f2850403b0374d29ad9318fff928364eefcae96fc2f32efe90395914216e45a32e07b872e1a29872eede17752629c6892a60498dd502972baa4c6fdd8bfe16a12185100a0b5b94372868925eba1b60e5819fe1fac79c117c2c939e236eaafe555bcec0378becb306b0195a7e739f58a124fe0939a8d4aee821a9efa93b9e2ee1cf78fb074afa6643a64b46f13c060901bc6274a9238d9fbcba2240a02177f295b2f0aba8d125f166be53b5e3f1af98e759f578eba8719937c03c42dac3a160b2585bc5e7b186a272924fa7a5e89457ea8821ff8ba741e98731bf55e1068e98361ca7ae8b942f067287990b1853916ec133674290ff7034c7507b8ea3d00bc0a21f8c92f5529aeb1f522e8186ed72fd295ef72d9da052b93b40ec66e83d4b286d6bcb419efbd64a72fd7fd2aa9511e9ef9a250b7d930251e25419acf408ce69b73d0b172566adfb72d9854d49f7f909c9ca648d69ff3b1ce98c54fa3b542499c36fc1db8509a3d95ac72f3bbef19884d07f852e4180b72590e294baa3c2c1c66da29e69dda900a62b9298175bd0fe7574d4977838b17090c5288bc65c4baf390fdebc9503be5a51725e596e06177207071cf46a29cea6997e184933ac88f968a72a0b31925dae0f7297f3b9ef225eeedce07f39d4e6537538355a959bde4ae85195113cea057386724671ec5085618d6d2b6c8418020a621a692688ea49e4cc17df9dc6d1114ef30c10386cc09430a96688ee15641927acfca128bd06b6f1245e6fd75b4ceb8b516caebb4aacc5ade81fbf10a9387b25a2110e3483b163454cf68f9e9c33333502725b12b1ced83577d3b543a9829446ee65b7e3d2bc1bf8513aa01dc421f5725635f3f1656407850d8009ee5fd890dd639017be6cfd29e80be7d8977443d69d8635529ac308367034c704378b4ededf4b6e6ed36920404decc168ecf2551d22f7725e52e83c89a25018098da84f6b89b0a13830c1d0ff30f7bd64738191a013b472671070380f129d05d27063b07b10734daa544154faf641aca4d276030efeae7289a1d6f74b6ea5ee5e496a9106054aeebeded2d04fc97e6600a34ce2130438019148b17726979f26adcb0ef49975c273ee771775f109b0d91b970faeb81a4c72b666dd903b9fd10deff6ec9295a0c4204653c9cef056ef1b0a29fb40c05eca721725ac300eee04a5e0065fa0b8c655ee00673a48df598d9f777b797e51463c6f858a0948637a4ab0c9cd6e3abcb51fe16f1521e715611abec39a8a4867020e72f7ac1e02760f535097d6b00c957d87f599eeeade6e41aebe7297ba2af17a1072edc6e6f84868f36b9090eacd0d5b6be684d1462ded61a96215251855949958720ebc9ce60fa3c42251cc1e3592b13441bc9719bd5c449f51c8b7059f5c6ddc0f59c82a2bf6727de36dd5978063203265f7be7b242f1c6de9811ea0955c2b800ef6e246e59ea5ef07fcf585da89d34d1dccfc9921545b345f6a00c861e306cd72a642ecf4a633ea1dd0768179e9e939d295f4e55bcf1c0d566950cd13e9e6d411903bec12dad56847e689fd3d97c3bb9aa8d8aa78ebda6a60943b9c0331b15172ba4a6e6600e525678ca6d0e2f72ed5dd90d47c85cee15bff14a57e41cb7f9a72ec81c9406ca4bf25b318fcdd917ed2a8b891f700d034869b07c4b6c7f8783772226301fd5ca062878ec29b4ea428760921891e61050b617ba6674c4059d3177284f68327e71bffeae074a94c0596746ec120134b9781b653021638115aea96721bbdd9b2b7e331473dacbb3ac5ad1e2692a97d248d76f968a6ab702063360206bf857bdde31c437b8b6dd1ceb7eb6d075e936335b448944ddfd02660f83dcf548fddddcbc1857b245cf05c8dad6b0515cbba61b2f6c9a44be753ed81cddeb8728044790f4e1b29cb82c728c111be9a399a8c1b1575770b388b13ae6331b031726c02ec95f80c63b2d9287690b363fa82bd4bc15e10c1e1800c438b738e880922348e5ab4a0472381d2230455ef54cb1830234ae9d210603398cc947359c52072633e63a02830ca25b4cd896389d67023a2458cb4144faee2c500fe21a110c45990f179d2fcf97dae9f6ceab9a2c3422b5d4a2e0b0240d8d25c913b18e6f0a87279ef45965e58cb03936aa4482a230ffcb1ef3c3d17aa3e14dac5077d41e07c728a1531fa64edd699a0981fde944bc725f159d2bfe5baa021a96eebd179565c72a7c11fd16a0dbf4f37f46d96878e56bbb0dca7a2a0cd5d7e5f949404a852f272a51946e66640a640118cea10a1ed8a022f7715dea95d336e48932c4cfc1bab1e9060cf5aa624cab0dcc76aada3474cccefc255bf275f9d18e2539adc568aed72712c56f6004b3365ec0b0a1b6cf1541c6402dc7a036107e4d6de68bc67cd1072505003ceed242407aa335da1c3df3a993993d03ab82fe4c2ed0b506baf824b3ca13b92dc1e61e8d627b42798af3f352fb429034903ffcf5e339a94642170ef72604a3b66b8392f6ec451baa479ee2434a17b59060165daae96ee3dbd4678d372fbebe6143bda185b61bc35b26987a3f54ba30527b0e712f9bf11fb4dc9bd3845c9dbcb91fb5b0e986f243f58d7fd73c242b3c83ada208f016669606f4e24a1728882367e14d794988f53bb367085c4e5db452cc3469d448692aed03d3e3a6472609298cc67d6af3b4c361e86bcc99b2b5f3dc3b74051b834c366f44d98681a72de75eeb4f74f22677e483bd94e3f43ac3c012d65ec29850147554862b30f21727864cb10760263d7817dce15d2c6d37bc0fe1ca64940d265dcc8695dafdce3727658daf3bc5aceab4ac41d6d809589fc39ad252e88d46dc2245a83aacfe51372ed617b82de061b2c07f0f3f02be9dd8dc86e97468eaf1e4c26c0b6c0ed002b5686e7a0d6d7a2afc161d2dfae3648874841dacf39b3d9b304161402fa054cf272c3da2c298b8d1b24eb7d8580cf0e0e46b7944613cee4bdf8023265546fe4f472061a3d467bd0b6d6e630fa768efb0421a32c8ccf6abe201c7a88d8948d41b0729fb1e67cf57d461d929883a4f248c0dd0ae4168bed14598bad564dcc023795153ec6b2c4794832ccd39096ea84228ea320840fa325b6b776d5d6cdb178081172767887f40f7921a1cf316edb6389687d6f09df8b52179424dae9b53b97cd8a33d6a76d3028f58f486f14ecc0b50518dee5b30636c4e4252bbc8fcd9cde90bb723a3b2a9a63ace4ee06dc911d18ecbba0227057474e1329c57f5d909afa1abd481e1487ecab14699c34de8e054a919ae99e880fcf8b3b6f409ae6600ebd92d9030ecd52d7e97ac60185ffb6f6df74e2d7e76b9511363a9e6c6e20e396093bdd726612be789c88bdaf8e9004bd4d57c94a3dcf8dd850fe392196d665465a0336351cb452d3fd3e0995d946eeffc8ef8c7a88db796fb68ad68fbdf3a51bafa1ad724e7334fafd9baa0bd3837f5b490af85b8ef0fbcdda6e3ef4a1641f605b60d62124856bd3fed7708a5c00f116fb1cd6f433a811b7f5d6a6118f7cba0ac0ce11547346e940d19706a9630aa8770bbe9b9b2e7f653c75373feab85c5ad640635e72496f138e96e60203675e351b4dacdd009b409bac1a788f2f2937f88bfd0e4972622b0c23062fee75c954904e618340f0a10c9583e4d14bee9ee882f30803bb162325bc2c59034f9f1152f8ef96bcca9d1a30a7b84668baaeeb6d4f4faa648b72a6f34d0f2256df82d220c1a214310413c40f4cf28c78f69399f8ca08ce150d49ecd7e3ba5fdb02eb9208f250a50d32f1fc97cb28529b6b18ce5b4b0023bde056847d6beadc8d7bb986086fe229a4d7113e8a3aaf547dcdc81ab3e28ad611ab32c5ed6334731a20e2e0bedb15ee7682c7f34570eccc2884e89d4cb97d35023972d1d65694ff3641ea7649030924ef405dc52aa97f6cba29ce82987c8662f17f72d452d52189f24ba54122d521a28b91790a1d839d40c375c83e305678655d3d723506d039b47e5704fdd78a308eb9d4ac482d31c444bced4e0682a2b2dc7091721b6c644b64a309f05ae4f2f385a97618db35a6e0f50d33a9b3a4d7348d34a872c7f779b14a6915d6eb267596b966a062561cf84cc60d44489cedda48d7e1ae72d6a78cbfedb8809999a09432f36309ce01b0164dbf13e35a470c8036cafc76637f1cc8bb60916afe6375508da14bba29575d4cfb6021e97e521e16fb79730f3d4406e71877b3d25ee2d6404446d3d2e0a4512f3022150f644108b80a6d3f3c23d3827bdf6023926a37b75036dbcd7fe73677a4057b335d8a34483355785c3c467af97d6821e10c5f75141d7ab509f70778c650fedf40674f9fdde5b384cd69688166418ea8b35039632246b9d855e6cbae215c0420177061d36fa69c89906c720c4842cf2ece5fad99232af46823c0fba5e4d1facb41f6f5ea0db7112d815b0fb244a4448b2fd7e04656472c2357c0f687b4828db55c6caec7d2d5c0c1677d72e51453d9ee3408ecb4fd862aa2ba91869ae1ec41741d312c003672b4c0ab4d7235aa40671e9e8d01a46109555ed31e96a0babeb3b1d0cbb1267b56d6c5321472f895680bdf4d64b1d9bf6a311661e1c1da303bc1025f8f3f6b33db00bf71393e15f3faca96a598532b7bcb84e8851841bff718cd96f6fe8d0c77218b64cb1d72f10533124c46a15d0122167e0453723591e0a16ff9faf44c210b23d2835bba72351e5b6a2b7a996fa616777f96cdb1be3c9e08b854bf0954179792482730e372a769e77ea88930156c4072eb8bcc08d8ab9d40f1d0ec5c447fb8288813ac1372de4bd5b7f5365286fc1e378d92688304f9d808f87ab95556f2e19787e1a542561949488cefad9a0b619534699ce6ac741c0f031cc9f6312ef8c6308d1a3ab22153e3607060e1c067232df3fe926f32f0ab11ea851998696d68310625566a5606a85275f57c8275e2fa9907d47bc0c3ee19310c6e954a544df32bc5737f46da724a633177546284db78b0a9c854ae061ed1b6d0ece470011f79740cb8ca5f7772eea4670b5cd2a36d6a00db1e1509d0489a6c45d951ddffa43999a9135e7e5269f7c4842355bfde1fca8f2b424a754d37c209ee694680296fbd2f217af70e017206b68fef124fd465b7b9e3c2d6c9a5ae652f0ace32de9ddb1a06623766ec6d5b778cf5d1281de4cb3f6a44dffcdb99db9980cc4339163aa93b6e4480b6d407725e4c180e10ef1c22f2f6dec667c054c3580ae3cc243a22af4842ede6c044b1723e7b21e00f82b40def21803472dee1b8c951749d8769ffbd16ece6974a8cf6399ecc80a3da83c66735c6f9ff19f9c4000b69427e819ea17e51daf949f2aafa0a7e0e4f32ccf0cb2a4c46d43bde8a68be3caf801513531d819e0e6b7692a6a17243b0dee9b51a3f3f5e270a7ef1af49bb01cc12c6ab138d081bc0c798349de872c3ee11b1ea132cf5897f36e5624888482b695dbf4891bfdb338184d961a1c048627047cb5a2c20f83fe8bfbb4f054fbc41733328d5ade0200f3923b6eb0775722a649442d72d4716bbdb684ca0af62ce1a8b9cc2046d54b0c68a197e872629721d324e208f74e6f619f5853ac8c82045389c976b1fb5b484b2222d3425ceb93e48324a4b33e7a79873c619362feb67a613d4676d89d076900e42f17877bffe377a14bbc41ca4e8e4a1f487393b3f22c0734cd387e423a52c1c49b7e84227c8725413ad770a7c358ee0bf5cf8b9af8ccd659f0edd79bb6f10303deaec66803a5df51b55a6e6de0de2cd91d53264c82c027b45c66eb71d695f9c51bbf4394e5672eb3cc8b7c41c8c2d340c352e0b5a39417f1c0d43159cd6cb87db7ddb1cf54840b37bdd9b4537a1759bbcf77ba3e5dc59b50714f17d50a5d3cc577028173aee640968e759b9031988121dfb25c7ff8596453d82b0384ec5c5fb3fe7efb4dd55579fac805c06c246f8763773b1af2d19cdd7b712258b49bc362c3fe49f84e4e95302eb695ba8d3cf6b6e257e1c7f53004670d8f21db9c13f507dade7c33308a646c98fde048969ff64a382c4bd9bd5c62dd3ddcb4bf6ae40f471ee0d4112a8a272aa4a5175da22c4df09b54f5ba4e2adeed1edcb3ab50d6cc5ec031540e06f6572f34f7b1379b3ba15ee24a873ba7954e4519815706cd96131d4f0f6cd3f90b94b33745d40b28fea268e5dc3c23ccecd34fbd928174fbaae7856791a8ad15949722a66555a13566a132e80cdcbb32c6de51fffbcc31bbea82a7c7b11c655269d72d3c91335657f03bf002dd6b15c3590a9ff7cc9326c771aa92817593f3ad62e721004288f4bcab0f2c8e9d0d61b11eac6d1cdc8269b86b8929ed4dfc038c0150bef97b3df69cb53dd00a4f9c870f3e885c796d066f5bdd49d839f1e93b1193072fd96044221eccfae9ff30480847bd7e57ebb9cfa31804283cf317fe81b7d7b72dd0b16545651f17cb516ee3e1f4254fd56bb9b04676197d67b432d36c02c4b724a3817f87ca0388a01613939e653c1f08d24bdcea600279bddf2e9a7a20304723c3b3a90a60fc2eac09658f2421f8fef18332b089f34157778411746aa6fd3721e155b23b508903e1783e5c9b1edea4e2041363fc786b8bd7075b09c3205f672b0f343adf6cfd3de7b7e77a8c278ce5291dd5fd0e753c88132891e2bdae5a7725f4f64878c4c425c9b72590f552795be311eec5bea2cb6987b0489c5d5ea57725c13f383ef375c497e191a2a89971ca5b957ba3b55475b1f608a171089a84e724552e0e64a28da043f4a804399481afe7ae69277e86cf0d92490cf3e6a90d84743d570bce2efc79f5e3a34b5d5a1cebfd9a2c4fc04123e6916c4aa97bd5eab414c7b5dac27ef1bfe2b1597b7f448030282f2b499d1ad442c74762a3173d60e4d91ac49aa9001811f6265ddec555a4001d7034d65e969f7e47925cdbabb7e2372d561137076b690edf85667695a95cfe2b1e9cec21b2088e9f84c6dc287033c72b8578023a3d5f5401d974f56671b0800e987ed5af59fbf34d1e1d7da89a94f72bb1ea225e27912d92f7ddfae8ce9e8ca451a271083d0a0b859c3f8e1056400033b1eee3da932a102e56f523f347c7139f293ee42c01597817176bb3d30963072f675c091dfe22793d134dc7455905d00586e29c594beddd7b11d059623513d63e9be8b06bc7b0c6a4c50f3475a2bd03fb2ffa2895fb907771a0f5625d74c693426bf3c335103d40efbf1d38a4f35db82d79964055ee5c8246f3341f2b49a4472059ddcfaa51d556cbd0223e8bc9fc5c9440fa6b8b43342eab05f291fcce59d2d5149d47d9e3737698891af14aaa5ace3010145f114d1d20ba3008f40f5768372ea285f6d0abdd5d1a954f93d947d6d836d8dfcccf00b466746052e82cccfd172406cc7a3a62d18289dbe2371b6fab01ffba074ee3bb0f5366340c1fccfc26872a7c69e99a2d2b26b8c37736372622a64d96018eea8caf6b9a9bdaa694620255d5d75e2e7eae8e8ffbda15ebf85e980cd924363107207291f303c0d743b613d184bc2e4c73df3b617515cbd595b909773de5916e81172625ccecae223fdbff7611c940039fb73ea85c24f104a639bfc5911bb7a70454fc09af39c71c29b568472c0649e9e1e9cb86dc09b044e43fdc138a1c8d8b565f30b31eab2d4d82366c072fbfcff6c333e194e5ff08b90b632492b8b889211512e3a47dfa0c97c8d9b47728c8d71cdb84509f711139b36e87ac0002ac4827569ecc5f85aa425a1d28ec818f1ca27ffa602de35f5cb09da365690aff7717335260bcc5cef5a731585f3c9724e97da5827f8b051fa8ca0cb5c2d203b35be9756ab09e708781bf1791e44277223889568081f66ed11fd92581e020ff7aad4f116f6c52468fccfd213b2ca7d0689368a0c44ed8361d35413ad0cb18ea37cadb3cb9220933d791a7225de50ce725804d15ff33b23399f5b93db499904e78fccf74652182ea8155b2f88792e7c72ef3507074bca906308f939a9318c73b7482c1e5d9b3408a88b9114af5818c57237ff8fe627f209c5d3257e9433f3ee287f9b597ddb71577aff3b10c12e9bbb72d12fda167081e763be1aad4046b6140c04c7f9df7c8977e40aadad6d65b4c672d7ea822ee7b58a7d141557a5983c4c8946cc5d0bf0d9efb94f1f99bac772742795cb20b3c78c29269499312205bc9f50b3cfe68afd4f10e9d845f626a08dda723e50e97ca281d39e915e09a64c23ab381c701abbac3ab4d2d891c0dc4e0dc05a8568047826800c6ad9eee730495db70579dd0cd83c118026ba019d045061d92f2b68a74d23601c5a552636742fd0f52de87246ccb8f481f3c449090cce3d4272b53e22eb9502c52181d991178f72d621830bdb36161f3da391222390618bf6728e97713a853acbacf56ccfb98fa3d3b743bba2112d3fbb9f50a7b9de520add50110478e131799f827176a95636cede84b16dad411143f01dbe4dbc65c586bc72832138d0adf16604cc94d71c064af1222e658cece439d3d6b74d0adee5ce9f727beb0c4f94de81e123e56d6fdae48093e1691a53dccb194e08776aa0a3075e724db9474f6a3970d2fd62d6dbbded79bfa7d15e76c9201207bf331732b94f240d1c28f7d1c0d93da18fee461ccf398e8d40270308532da38ac2f2d80ede506463c9e61b852258cebdc7941c67b64bc0a2c7557ee1b44a9d83b33cd4e2b7e296726bd3cc7b6d49ba4d10e0798d95cf6c5f9241794d9955953010cbce766cc02416704ad06f0b80252422c09405c2ff12ac2dac486fba395435ac70b0019022c236a84c4036931255261c132d690e7b54d0c86abfea093680751e9e76060d70c956862d09a2e0f6566d266b1661e8d377536d6f8f978a4c13cb771ca08c7962fe7228985fca1ddcfe9fdfc84f1305f233196f4d9f4eccbe64d0ad76e713e05a2d5b096406425052019786fd91ba9a340ba76ebe3177f80d1a86b51159057c884a4dfbac6ce36ab04d122ba6c1bc4325db026dda41e0e84f2b93f1f404528a42e849a65efaccdfed140743165362de07a988ea080dd2dd2f31f789344610ae481472d64d98b736d0116651cdc309f2e9c32e017777c6b3b37d54e9d8c2f99644d172ff46fcce341a88bf6a5ff7f4122628e95ddd835d60240ba0475c43aa204b962a7e5dbbf2d729c12840043d01d4c801841ec52fb9a4d158f75974362f36f25c72f908e1c7ff52b14e1395d2347cdf58ac12ea67880ffdba0bb704c983d2a3787294d49d386ee9afc6519d88f2f7c8522c88304296aadda5f56014443a70326140d341c50df4fc9e7c8f72f4d2d0f5d4b290acea924ae1ffa3bf717eb149f4a81605057d0e0a3023bbe3573180d223b4734320a9b4cc67f0bf0740c59bd86a2172ff8be938d52ab6203ead556b3d92bd10f9476f446e71bee797423e5468fea45f7102cba6ad1e07c6766258712054a75e5de249f273247fb2a8a40724147e4a2a951a0d5c00d8208b3a93b49400c5c8b8b30d910f26f3eb696f9a1def7c90ea72b484a434b13184508210091a0b820b559e7291b889528607ceaf1e1a47320e72ba184815d4f954daf7c72fea50205bcb5af6a113edf0c5947dd8514aba8e514b4fbe11b60776ab5eb2921769b27d22ef19063e9984b61f14d5dd104a2796d5607fb1729f653a3f34c813349d88855c9e9ff88181d68e1810137bbe43be03674bcb04bdee7c87ff43f77503547f7c198f88b1358a4259e7f193df2dbe29ec6472cb04e6155824746ee604618ba1eb691eea45e01c99552a495c20dc0bd440da7205ec60781e68097e5eadaf775e31164a01dba09ac6369f4d1cf5f5e99b538957b29621ddd573e06d59f256c14e3c8ca83dd9c55f775ce2b1766a7a3b4ea16b7256c6f0555aa7405b34531662f518818f85988b8d7dfe1de2494baf5c81d3b1722ea02db9cc3eed1fe25d72265795f4430a7c1b5f1d92780c09df21a0976aa9724525f1c9edc59c21e7967506c8d498a67bf3da9fb5a794643d53a9ecd8cc895914bb4328084f355109b6af824326918f492a214ec4ecf0dc80a1565197371c566d0edd53312760087f7d4a652d4e40df1eef96fb32f8fd7d38233a646f038c38830faaed37968b62a07e348253de747558eeb248f11e8f63fa6b24ce6f7e37724935b4b7ffc577b26eea7b152c304c6ee7a3843a3255da5dc12ad2e190228a41e02454f6d6ff16a1628aac3bc4e2bb404f48bb77e6632c7339069dda51bf92723346ad1c55fd6aba16f4199a162772e50029c23d12e85567d8d8d7c3a280ac2874715251ae0819f185f9c7aa311c2b51a49fb0bbe942c6ac26986be710c47813cbcb46f4957bf468e2ebfcf7cd7c2c180a01ee1fcd453805651a5fd08493767287b549bb618c2a7728bc79cfb6b16aa9f7894de45cbc0acd829fd249e2ca1024ed5ea04b2d65fd64124285b4267dc7abae16ab50b1ba0a8ad39ea8171c7c7f7237c61e34433b7fe68a9034031a332c4c58a579b67ffc4aea09bebd3ba715fa2871da17ebf3acff779214611f9b1fa72b96273e9a3e83743c4f6623018457c6368a8a46c9588e3249760002d18d9079ca495235a674cf8c24c41d933a469b6a3ecee50df540edc96c55f344422d6a01fe37aca2a5dda74edaeab8d538bb8c6b7286d1fc53dcd7e7db8fd0c164276e8508be52025afecd3cfc671019cdcb38b258223fd870bf089a4bd56dce78abc9bd127ddb8952fe09e6f9292a05fcd39f9472325e95bb07e3088ba8261f06a049215e2dac29220d7688e492522a14daddda6a45855f7c1c8f149ce17fe466dec3e7e85251c4e13d5ea4e2790197b0007a15722271e68a883b50eac77b5e04b94560cccc41f49feb737297951024083a7832271bd169ae12d5692ffe5269f400bb68eee09591fa2c5e899aaa80d195755b822eff720c0748ab0e4cc3b92b29e6539d164d020119f588b1d0c59edbbf8a007672d39357820ced1ecd25fd921d88c6f5c2e0de0b31dcf2ba1ad85f682fdf96e872f25b86149c06e8a320d451ac039c213b2ca28c08bc691df56d0ae8154b944a5a798a8832e4ce0e1ca1b783b82ab38e29e889527dcf1a9525a58187b4dbec5f05332b105d6c37832a283ad5cd08eb8050eb412795d22de06d00a886d4a310812b9176aa78b6bff8757e02bbf21f61c1c31dfc2a061b5032eb8a769aadeb5d5b41e862fe6251869863e674c632cb3cc811f120b03d3770148d695977fae2721772a2c23c6152be5e729941dd63da19dc1f3ecf864f91f48ea43c89c94cd7524c52c5887759255a11963d197c492c7dd2f8594c80f1c56dac26bdb8aa1b3abcb372975bde2a423f64c09b1b14af5f13706f3b5ffbcaaba285f61075ccbe247bdf6f66105d8ace5e7de83832a9af1fbf8d2a6de918998f25a7e886ce388df51c1272080c88d4b6e4d439338244df827f28576485eb42a3c6e18f7a7b7ced85e587728c1869771aed1f246751f1b559a7e11318ac031fc0f9eba87c276e58cdf3ac72204bc3013ef711542d1bfc6c5f0dc5a753925dc437e3ff3cd8dd192daf41633016650cbef30ad9cf4edd5635022627653663af45fc8a51e0ed84d26cf911c75535b7780ea72809573dd381b805eaca778777fcf82527f8d1638f55d990c2dc72a25f2cd3e9850acfea179eafe85467cca0bc0fd90edd18df1f3bfdf4b335a30fe4f7b22a99f1b300831b418217d6fe56c69ab9bce1c3b85cd02a21730cbaef726fe6d9fbd72816132a99bcdb86788d34cb27ab718e3e40ecb8f1af8b9668b55370f838b78a939fb6d05471bb4511c524a9042db5921161acf5737c1e7ad7027268cbd56e7d4f2485919a2caccf2870a08df9970843f4d26ab573afa8acbef2113d51826c0eff3fcd9e7cb6b6c272e4b977256c439e40eeef5f699a742ef5b772427d716e171a483eb6d5e5b82bed1c9947a57547b9c8c140cf92fc0ebc865a4774f0a329c2c2b042793c4a449f8f6f5356090c8bcc3aae1500821006b1af2405baf27e951b2b22c515255848a2205831eb9b9eefa92c90e015462fe3f52ea634e1cda18a29e48d7bfdfa94d26a9277e63160ca3058f377d120fc07da2ba4e772c93e8c8f84eea7e90479f13b142ab82723cb2b903f177bbdd3ed88d082f48f290ddca0a03c3974262d45938aff85ad6328d728074f9ca8f94f303981d2666a72e6f7154dbac0595b233dffe9b4b2d91f8bc1f31d80a05ea41f485bc363372144767602c3d9086e584897c2cd8e084306e94d6890d7975609f075838d51ffe127a0b743576a9cbab323f68297a4c1d24011fcdcfe2022bab0968bde432b3eec72aca6104050a28ae5d04199d02a8ed93a86f35111dda55eb21852dce461f33716a930b4b32918f9767b956adac9ff4913ccfe6a2a4bfe0cde015b582039cd7c5e17aabf4fc1c1f26b9f2a595727e3d99b07749bd0de0cfe4115ac326befa4a910ed6400a241a7770951279f5a53d51f90c46aee2d8901728969c457fcfe15e8554097fb1ac71eae4ce04a8122380af6204cad7e7ade55a117f81f5337c34c4472f09e015323b940cd0517cde05be2e5f158c3eca3b61cbf628f606ad232d42172a7009f53f2dc85cc60b390d08e640c4504f956efc367537d5f5bde2000164238811886be1beb0325972453092dfd1bed159783214717b4ef7583c6d2b0b366612690a1d62e58809a556873a3a034fba7945d1551d0eba533ed538f5b951adf2d6fb9d6ca858fe21de567d5d39be6da5895224a15a6966fdbe95100ce327ac4727731dd4764c0028c99733182d3740bcc09d3133bdc9cffd0df5534d351ba8f0f9791cf58d20ac94317bdf7205e4f1a9b3aeb06c6e18e8f34f4a08b7209f99a6c968d4b430436d59b9fe3e42ed7add76eeaf1cd96e9dc77694c97701debb6c472de37aebdc133e392fbc777599f5210ee488824dc24e2ee480bd8fc990548bb728d1e0316475dc55df84a38eb23f6c886fb9765b10096677d82a11c3674d12e72a4a373056edd401230442ad393aa37a487b216bc9ca0c007aab2df951ddb1072754f9c52e2c40b12d45d1511f2db1c9e46fb7b6d2920eab1d7e856dbf8b36847e8862d8ac030df867924bb87bdedec824f75c9d2a482d2b931e7273a5f5c8455f6edc647693a4d32447c3a882564fbf7111ab7ac9dac2174b544ce484005007266d0577c3a120e15227236cca2c9fb73e1e80137cc7dc3100947b4c48d2fa17239bfece51e17b1c76905cf921883bfa0de7285d92207e0eb7d1bd2248a883913c94bd8b41ee1a0181a4dfef4e0684e9c9693a6390ca76bebcce764d377a5d925a1384d6ca8d44cd7dab1191ce33be8a7664bf7fcf450583660cf5c9ec17565157c86b8d2ecb05e07659dd5fd4f7eb4cc8e643d3d0847016d780f34768ad0921c74793bdc3a5d763ef0c30166d17c96e3591bfdf42b054f5158dea19d6030a348bad332a8d015789b614464d632dac83efca5a50bf0c2679d67930b84e7768572ed31714c50b023f74f5df9f73ed8f06bed214e2acaf5de3c418ce62064142c728f5a78f76c4250be9cf441d1791ab2165c3cfeee62b7fd923a10e0d0d6bed272be10f0f59d4407b77ca631f4ca4b766f7db2118378b63c38f9c1bb1cecba3636f53e76f8313729fa06e887207549fc4f860cef9c1d0ce5993c18335dc43dd356db239b3d409b07f6bb9b73395ad7377379d6883e4c691bed6f74f1335f3253729640b3e6fecab4ff6030422b347fbda5bb2ecbb61e5cf5a89ebe4ee79a625d72f9b72894e2bfdf3f9046c172fbe62c90788bf30cd6f5bd66bc5b2c7eb8b31e1c558c6aeb799f7dc45f1d8ec798729d166234756a5d578825490a349b5749b7722496a926b440f5d76f70729edf32114f290cb99d9c0ba8276943e90353740b727d81e903198ea1b2b519aec1c575b0b8799e3eafc73259791d2797d7c7992511764ec7fc8b6892005554bd811ca6c9e5873a69c1a301d50e85d49b039a953472afc1f76c4e94bceb6301c0250a0143ed861c8ed098d1c5e87aed8a84ac07397075d9cbe9f8d2d820b5cac00021a4025317fd7566b25f222045bee468aa3a827214b5c19295cb9c7cc929b4019a678fbdc68c54d109698a28ef3c68653f59390934e100c342369c4765d99715dad12c6bf977c21ed3ec8b0da6abe504a00227104d9d542aeadcc2fa82ea9f8e457727cd52ddee311409a9e94990b29949e051287cb300f67e8b3f4cdeae095c8d915b1875dba995b6f47412b1b4179bd832ed72f1a57b0e725da610ab1d09eb83411a55296a1f19468e11af8b6893efd62e94724595603068ee2301b33f4402ed6b30caf5bd9ad033f5dc263e1cf10163a0120df7094db7d2c9494683726b59856d597c2dd8773671df8f630fea5fe4ee41dd72a57a593ec982345597600382224651687ae1a7706cc224ff549e208099c8f472f86ce4d57998d63ddff37538d31b62453541979bbbb96ca62cd8ad3a6968ec72c79ef8dd1fc747d1642532b72419a8105e21c0570c7a8dfe323b36256f855372e62765eac0fd3e8d7026251954c2fad8593437ff3ab8b9c4237b2ea2b416a2722a0524ef5956d81fac1beb37eb0e599807dc889507a9a9c4940a180a7fd08b7225772472f0ac749dfb33967d3739aacf77dbed10806c24e8a9094f8517817e729989d5646d9ce573b016af1533810fdfbe2054aa0f2f536afdd0c4ca402a6524f103adf7fc8451347bf818a33437bd4b63d97f5f9cb51976ab0bfa57e3e1ad7257c42b885e7af032e9cb9f19385c46cf9c5e0f8b5e96d58c8fe8e32f88ceb916d5dda96d312f12fa0d4bb02f71e0991ceed23a949aef72c6554824c031751b72252c371ee120529d92bc2c81a82275a7a6145e5eddcb44bdfeb6e36f04e4eb342fda2f5eaebc2922e45820b7377f5588ab65ac9b1865e4fca990e73aa430ef728a1e72ab6a12d998de2902998ef7974eb88b01778158dc0462467bcc63dd914f16ccec1b17839682d21aa52e5457c0bfbcf8d8e93bce63f5927ffbba6b56337257d6cfce125a0f10ec69bf44d6878b62b538d4eb92964a3512c38aac467f8872ca70f179c58d1c9069f5e4babe8493c50c94c2137828263af7a35f48807d7072cbd8a150d1d3aa3fcec309bca175c826057e8b15c5bab7d539683a6b2a51db72e141a9ce3acb6732ea4572ec09ec31b35ea05f6c6b104c5342fa57e977c99f24cc46f1200e8c21432a3200a49fa3e3b604c51ee2d7202c35912d487f7dcdc024b20fa87dd1ec5cc432b6fc6bd6deec7932622ee7922beea80120d7c05f07b572c35ff7ca3f4ecd7fa64a9e603a687574605be32d3e2813b27b92aa85b0568c72c794a9cbfd4372aedc280d6d7a3e2fd0ca75b604b8cf9b242612422d40f9db36917772fdf8ca2ba5dd676e95d920b0c01f27f1fe5b8b655a3f0bbd3088ff76200781f2ca2891414c63ea33d93598ac63d384f1fde82cd12883ecc8e3ab5e5803c229dabdb355832b56913b4067c399cd79404470ff9456e0736e9a8fbe111b72ba8504b0e0ab0166a019ff6a9b81ee98a38731931a235e5bbece09e3a87dda6cd5d808fd8b7356a280839994c18a633a209fbdb6ae4807adcf583f587288b2725ce9bf172102982f5018d1f85bdd71dd7d88c227736f7ee0a2f8cfbe54188a72fa82167ff84c13444a4f201031c78e4186a11c23b8d39de2a4934f4ef194563519e850293966bb8f69ba816728120055c2bb79e6e49d48e78f2c31f88a63627269ad971583c59d18c121e22c0f58f5455cb72b60a88cf2de78cc412e3842605d2494cfea53b7f33e9a668ce0f698227fded0112053774a31df40cdafe6ba872dbbe43d140b87506806b8866484762d0df2a3ca523511b3d22f226c09591f2bba0200001209fe3bc3497e47376dfbd9df0600a17c63384c85f859671956d8289e5a0be80165f23be715d4b7ad90d95e574e12982e22f0e7a48d2547a9bb1b3118c94388720b2e8793fcd5eb9df0e355cc330bc2d6542cd61c4730460129b0655ec0b1017265e5c82224054ea507a038b9a33449c6c4e15b4d1d99ac9a43802946eae53a72138900241618b8eee144e62245d02aa402b2cfb874dd12cfa4e3fd7052c3ed727bc779cad35cb518995d2a24dfbe2ebeadbcbf40ade7f0155e7ecab39c3a6572e139a376c8c131f557c867116c6d8fbfa3689c884464a10d481575157e89c726706d821d8be4389a32539f455864e95f8135f6285a8c729d8c1593a2fb907772a8ca004e0b272e27d76d45753e5bc940129142a200c573e15c72c5546172da72cb1d1e37424e268c53fb8f1b5e79b882f9dc2ba87e29287ac703a30a3382cd0c842d517c9311c681d551999cdd55be8c884b69a70868970f1fc51d1e03aa9e2b2d5b440dc0d61ded70692231f7e5a951acf542b14829ce69c06553acf22c03724a9b936144037e178666df3d1688ddde6fef4791e947348457b932acf83a9763668e521335acb1db3c0f8b219409b659d2e430bab1d384c40aeeab13b0e6ff70e6b15b486e339003c0ef810a022dbad68003d95722a47b73b92e68d55d9a25539d4ccba2a51ff628f7a4eb0ce6ebe064700c03ce81ed03916134a1a08a258a72e61f46507602af70d626c61d32c4202b8105d294b3ba5c8e1b379a994013877235ab80d1fa2cf9805461e298c501c14bf6b66b5f744d358983de2d87308fbd725c7b331bc30e577017a60d01f39234fe3e7538b6dbdbf3c4bc1fc82b0fed6672c7ad86c5273ebb0a14b16a518d773d84f28dbd3698010cd9ef3aee2fd9477a708e7071df8d3dad4a12c6722980b2118d8c6df249eb2a7585b785b766b4a3e7720e36e2b5664ae4ffcd8eb00a02f688bc38a83361b9dcbd3b1b5ccbd52cfaee2a656163679e585f019e479dbdabf649549d50d9044c8f43408008467c0a4a79728278cbd181fcaf3d9667cbe1b3a7e836820308ea6a1e39d9a8aaa86cbc064a721a0209ad7eccd0fae54a2259c76200b02953fc359b56bf4d681d0d66af5a2446f6fe624dfdbbccc541a4290831e7aafc8b7504b3e82af2e5ef6131f9a4a2904e4beba752accc653ca042e18ae06e8166d749365ce4edf8d1a851cdef030d7b72b9be3bde19f7cd6dd4f68af8b20248ce43f40fd1afb6676cc60f3ba1f2ee10729855eadc9d02d779adb52cdb3e03fe4b503fb2591ded2c0f57c6342b5f682b72445ad4b46ad6f5a7b39de1555e9be5acbfd31ff4f80047a9baaf3625d88cf0723760d74ad2f91c3be610ccee6e41a7363c2b7f1be9902c60c412cd0b8de7be1f7791d567eddf1f01edfa9d56830b87545fa942fe55198b676eaebc18c2d5ec530b9a1d73b29b03a2a6799e7b662c8198fab22f5f666f7e6f7899285aa298c3576a566f7fff477ac432efdce21a861bf4df6bbaa3dee085e01da7bb4ed9ec7b44cb330f33f48e1cff3e2316976e4e6087af3c770e46f68ef3582733aa769e7d398bc65f5bddeae7a4bb69d9749d149127f8033e77e4f661279c56960438428429a1ad2c97b6acf5e7a3763a6b86ee1f5d4cc3c7ef2a2736fdf01ee3ba01099a72b8f0cdc8610d10a26b03791b05d6f6657aa5ebc7e84bf8a20b8265e4cbb86b725d290e91f927013780ef147b982f3b44fc6d66377f7c1614f77df57b134aff7232ab43cfc1d02aa5fe060c7d31c71f1d1141781e31f0e15c8f9d29c79b1c7e72f68d04bc41a781c56f1175a966e9fcbce6b2e254884d9cc6d980892f52a0e872b6ad2e17e55b1f411ede0da02ffbf4c726e4847e3ddc98485dd664873f4481726c3529907c6953f80f3d41b85941f5c81bc550a8eb6f14cf4fadf610be9146724d5e7612282d6ecc4d6ba8d91308584d0df677bee9a04723e30e0bdc53db560031748d03d8caff8abb7257ba6f16a4b9247995d0de482a4438bf1ab145cd095ea05d42dda2465d5f2a79baf3079df8972662bf54de0bc088513924122faf9f72926695f901f519e27c6ff73018d14a87417d15fcf50cb9fe99f2b9a521e455724e8be4cb536155916f6445a4c9cecb8356ef10f716b2664c9ce2c553c5732c3dd6e34ed0e52d8ffe2baf34ed23f56cfe229f2a00ac30ba6abaf802c74e8090158a61de91536bf521c726fde1779188ed8a132a72ecd090fcc2f554a8cdd04972a6b5c4eb2f22487f23c5e777c420b99a3ca328b17fea2acab99ac0513947ef67a3fee97dee05a0ee5c03e61f42978a2add9397860906b86d8eb3ec8321421c7285e5d1f47732c85f1c17007c3502dcd75d274cc2bb2c721ce959550bf827e36d8c23df6ef538a35f24d8d52ac8339714f8c27446c47f03bbdd8db2d821bc7072c444042317fec13019be66449355cf8a3467d0f47439cace429d153388b4821469777bbf00a6cd82199655c0b952f8d671f6378c1f879e5e3f80870bae99f972804a3db407806cd8c3ab05955bcb67075b18bda7934c5329764e9d39155d5332480cf15e2629937da9a12485425089d90917e596b8fe2fe5dbd4a3e6b297b372422bdbc0c96a4353b189a4d1f84ed8d5b79dd10dd3452f8ec5f29b20d0c72b1aae06bd97f6119bfb3e5aab44f81063852e6f98359dae1d6b1ac507e2507f3d7294def685c732d08f9532e4acebbf46f730d0ef8a61060797520fca7d88f20939b7e79be8085f496692817caa3d127351c40dd1c92d15bf33a73fcdd7061b66722fd325dda102cca68ef52d8c4a6c5429e1a9549c69602b9411c12981ebf4b60cc929416ce28a4051e1d07a547309a485101ca93834a8c32e420baa4fcd36697225c394a30c41a7c98587624c3faa21852bada5b191b23027a78a2ff7e0d3fa726bcabd85cd8925c8f5df4ea095f04a549ba95f3f4f441570262fb8325ed83572e919242663315eda0025ac398119acb81e92aeaea25989f5859e42ac4c98bb2a77a4ea172399a2f04ee68738c9ac24bbdb65e77362357a33ed8ac6db2a29905704a063716f0ea6135a45405647252af3431d9e5a93e6cd9a43a71e75d8c06a72b4198ef3ab1f34b8cd50f45db8d1d125f72dc2759f426471e3df5a3a3bd8374782803326c120fe0eb6316ba3bd2fd81413e4d6cd05d664ea8ac0432b85d18672438964fbf6399069247ded89132ab2d4325da3b3ae510ab8ed585ce7b795d16e1e4f85265e1336aabdede51e4d9aa1780bb72a06e78e9e32906de87a5818f62316a7fcc2d8bd0738165953161005ae04a53dfcc6c4694e5eac68b5b65d443402541e74722e547eda6173f17b0f513d05ffc988960a61bac772b1289e5cd2d17203884becbad6700569de63033e9ab87e127ea05137119421ca847f082f398f2d12e744178706518f2add3542fe1927a0c2d3563616222a9dd457393586919853fd0115b4cdda31656daee729581e83f8eb55d5f666ad12a0d03c28cefa80a172551f6ccfe3602f48ec26fcfd1cd214ee98a676caf6136ed13139a8da78a1801ca30cfed4f51ee0c5dccbf03c722103789fc55be49631615c289b0f32ed8f667252dcb62955550e5cd8acb0a7d40a9b12ad5e7b7d50d8cb0bdecb983bce1bde51e75275c32eb04f1f209dd13bebb79c700ad2bcbdc0c5d4548f4b01737935c259d173c638887b957dc81a98b4adb8418134bdb961fae4ff6472b97c228d385f57a6263a52055e87fe5858819504777ce6055fdd4f2690cd5875eb368e3ba4ec72e438e955aa8bab59487cc1bb0652fd87e688f5aefa8420f278a94275870c9a72a45a8ec90c67485e1c9b02cbb2dea9265d4458c2237f4f476550f01529152933043adda7b593982bf5e9bd03603c9c098a37585a3cfa9b93a3c5d13f4719bf725ccd6bed17eadbaeb7327cb21dedb3d5b50119b0739eb4c99f1d1144e9c8d75669c1f93aef057750d56c39df4b482c16c314e66bcbe1cb250c09677436faea43b12f5e5b30b495040e34154dd5fe68120bde69a53a27aa59a8aeae17b9ecfe7282bf4e1f375c8c7d05b3c182d3f19dd55f8bbb0126886e1fc68e4928a7075e721aab491ab90c9de8654ebffe3bf6aaf7b2c55496fd3c96f6a23c0d135ea6a26c06735af30b085000c48a4c2d9bee1ad931776cc09dd4a2ff9ade8ff7dff516721d3aa06bf13cc5130c923ef1a6c6db7687082d7791209576b0d229076dd5b6724c5f01bbba686cb7fb91b3ff09b2c2070454ab02d10567a4d3408610818717576d04c2c144918ace11b651ebe0b87645a57e741e7b65e8730c82f27304a50672402f09f550195008efbaa746e79398ea32b7701a25c529eca9ba1947d08a4a7224bed753eb84a59255939a6b848e20187e3b2408bb258a55df14f6654e41d2721ab0e5aaa115c3464ff2802b219c7ad7cbef6abc67746282b528ad1ca218e2729ed05dc66d4f09c119277df7cb9541fa71a82cf9609db5a86a7f65d7cc7e1902d108b159a441cfb6652f39d77f903a18d4eb3016254dc9a6934f2adaaee3b37205cc924957543e0d6c2e991f91d83c9754240814f1057c47b04468f87426231d32586d2992a289e8c1ca4fb1180ecde733eeeba296bfb8b4c97861164f8ca9725d325834c0c7a6141e0fce91159e1f122b94e59135f464a928cdfc91f6a3e972e6e7f5a1e20df6460bd54868b3dc28ddfaaff02b7cd11a56992e7af4070d9b72bb8cc2ea114c26cc791094abcebbe3d4b20f23f6d91aa9e5555567420be9a4728649f5d647f6d8d08ead0280746da854c6cf475d2d3a009b82432eb21fd94727f11ddf4df8384ac80ffee273ace7055d855096ae2942c6e58a923bcb46630308b31c8e7458850c8d272171b49faf7abe5c6dddc3e9e3c41821bcd8419135c7122da7b0b9a7fcefc28a9c5f30b39d680f1aac916ad8fb7b06237cd7ad9ec1e93f4aa461ba660d66a3cfd6b9634003030de20eca2b7ce905cd9625a32c9f175e72ddec9f20a0bac70dfaaf31ee63893b939433284f668eaea14c0b0d11fdbb1872d5c3462539d489c9e4b69f02bdc3f39edd61db387d05f3a4c6b6cac2ff87350f421de21b1cb6b3ab46bbd83d90fe1bfe18ead3b7adcca1b202e7aa08afb93312bc463e66dadc57e15d5c405c43106b11f4a685c725fa0b11565c6ee4b35a49315090bd208058dfde2ac1786a05c38706779af261c62a2519a4576ca20c48821c7183cf9b962724f997613549a95b4e7293faf033694d7603e97478a1a4040c72e0b79885642d2bf65281f2d30095bfbc65e8547db6edcf91a268146e0359c559bf5b9ca031c693bd0750da9af3632b295f5cf9ec262077d3dc30536359e07012a4cfdc7f5370017532c0e0fe8f2f29df243efd846420c488b07331f06fe12872042cf95af3be3da9dcb6f997f1bcccf27073cb118178a1a952a480c59aff29723cccac3ac69af82e9442e087a32042d176ebda5e00cc89ad9e8ec68d6cebf8725d70ad643853533348df5fc364cb9ee5881c764e729aedeaa05bb584f2a230726975283675f3eba6e3f6775f765e9d6914b1f3532820ff89453c46075b306c044866b84879d1e3acc33f667839d9b2d55f932ff026f7ebcab133b84c7c59607217e86c3351ad341577eb9673a90898fa268826b5eb82465ab7f7cae408e3f372895794816ac2bdde15f0423e953a0ed05cf3fc6a731b8d12b73a0b193f99e433ae2e1b6cbdfa46f8d320b56beda869e533e8c4d2659a03ea5905dce9c675d072d23939edbf63f9bab51de1372af014ee8e9704c67532b304190c975c26e57f72eb210158bcfed5df58314e7e4bf53ebf3a188ace753fe8d0ad534286645d5c7231df6aabd7e46f46505ebc9cbaf66f0360bcf0f22d31e65192549179b1e4a57288203583928a082863d80abb827c0783f18efd56d4fd4ca3ea3da271a6f5ad7213e32e1cc739b91619ebc109225484adfd4a9bbed63109089ce78c57219ca4722d78a272c9b3cf68836b9c51a1ddaa06f4124509cc5eeec2fe9304f41624d25b629076ae01ca962110948250cc1dfdb12ea394bf5d81ec99965e7f430ca3db4e7a9b043071a6e4d7ffb4bc2d2c6193e20be23dbc8345518ae0c770fc144cf81421b915bff480c70a6b686d9be3c0da86284ef6c2e65baac3ad0625687d81ce7255a478f80a182ab8fd42a3af534166099fa78a7117854d319d5aebaecd6f830cafd5e3f5b0ea5c550d8445a9245e768fed7530a009289460e8f6bd30b782ec723649cce035d7a723ad6dc853e8fcb7a8f4b7fd68c9daa342de82e8f2c182820107148f29feac2c04d22cda08459754035a149866530ee44f85c37cd6833ec27288c7b98ddd25c725ae1c37a589556e783a9f4b5bdf6ca6de8a49f671b467357260fdb9a106302652eb7ee58f1d64d672dbe80790c64340a87437e8b2785ddd728c8186665229dd4de8158b852ed4537c0664a7e03350c68e547d3ad02bf157727575591ded7345c20ef63ff7495f9a236fcecd837b8a45add0dcafd5ca312f4a572b91464ceed9912d3ab5fa0d67e4a1e6f93b41bcf8c919ac25ea3254c3132e8562861e0e69121de04f95b542a78818ebe3c3a1cd43f7032dcf91b2fbccf87235949e13b56d528be26f7cf4a8e252ab22ab607b9b328c1e445f99ca5841b168f256946c50ed4a1e1e67fc1843d305c947d72de4de2b80df1d5d3c58b7093772311e1daac745f1dc2fc883d4caadfce57c2689a65d4464b1ab853528d4bb8e727d67f0c0025215c32aea8d011d8dfe9c8d42f04b66113cd201740646a756404f6b55144abc5b43fa4acb80a9df9346d6f8cfa605e9fded397fb40e78b919c44b6cf42dbca602972774ae2fa7df90ccf8100ccf33a71f7e7e0af80c0419ca3c7013998e7498a472d5822aeb200dce9767e691414dec834633b27ebd9b88ca9372b788c3ab6ecdc5c074fdfd6a4fad6fcc8c295483f8c9b94ef5c4548c26369a72bcd89b5fae419378dab47e950fa945faf907d072ce02d2761b0781395e92780353ddb55dfe557d167560075c2bc11b54e18d122bc51e1d2089158357a0eed4726f1af52a7c733d0043cfc84e0835dae62c60fb51e56bf0ebca898a0e6e554b3a731bfb2fbd4b9f5502ce4c572ceb2503fa1a5900e36e760c1480985f1b039c723f49e6b86163faa6ad30628bd2deb6da224203c9a0a1013dc91545a8997ba57231714d672b9413d6a513abdd38e3eff2eef8fc1169649ac84a13fda6d8a80d587c812288ccec52fc8fb3726c7bb63c10e662010a739cebc85a75d7cedaab0d724ab5ef0fad5c3c1aa36722fffce27e11a14514c9924da09e222376181ffb7b7230024adbb2c06e71e460dbbf1bd10ed1eb60562f03df329eeea9e5b9fd24cd723c9200bc6ef9278783baad3cc6f27b2c56721fa592434eaac98f3f9922230c72a05d7a67174045c0ac752f6e300b5a3c13cc375c2ac5f93cc4fcfc10ef33170f4465320181757251e34d55441222e62ed9924c65553b6f153267f6cebdacce6ddddc246ff523ce8fc272358906f78a960c6f3e2eb8a42535c9ef87c9ed2e0172ede315cbd5788ca6ba681587076e17f3a8c1e48cd3b09b5a00b1ae4b1161195848e559e0d0dbe5656e3a14e64a562e33ded3f3b27583186f3e80534222c7ea72bcf206f8e6ef41b12177540530a13a2af62215fa575a4d3e72cf0b97ccfffe71999386f4c6c5962c920a0ad6b9e024890f019861d6455ffcf2254938a5f67b14b077c5e1b48a2abb3109d24ec3ca46f27112034d2eb755b07c0382f02e07cc7256dcab77e0a047f589fca7b2576c98afd937927249861c6c995fcb8f8f703e4962ef660c672b213f863412cd4af340cc46a491f6c399e34f21fabe7820aa4b727458b89bc761831724d4332bce17d084b71b1af09936d0b27365b2190255927239a2ef07cd2852b15d9c6e473e93cb98ed31d89e82eed8698d014007f289964cdbc294168a8acced5f67d8056cd300d2e887c55431de4ff5a7a636a3639f8c72e4f6f3562cfc05346ad00f3215653db7d8f68feff912a114952e7ebd2c1e09725516c8c7bedbba4c570ee73d36af5d2a8be31a71e50e4b5c84acd082f2c5884c452f28e6f902c2abede9715122a07ca3a0300e7d2060a93f9f2e950fc0639372c8aaafb4d8296e92c53ec432046ff29c89b3ede58b270097bf7970afa2e54f3f07a5767aad2ef0b2a1deb0464e4f387e012a683a5a9fd650bf8fc5db773d1d72342cdab5e449f16a6cb54f21c425cc7e4f28471afa980f6eb06ef747af51275b85d44b90f75a042fddd38216a6676c1ee332a1dd9b7702612425fdd1ad521472a7ba05ae5c6bc29f5070d2808d67b08d206a2e1770a0dafc65a575440916c70d6be9962a23ebe86bf11de22e64a4c485475a83dcb091491c965b007d3d055e72ad3b4975ab689521ce54274e98b95f1f9c3dabd3318a082ee19e13573a2f5a3cdf2447a6f03db7eef4bcd9955a66339bd0b194cb1b903fe5e250de19eb5cfa4720f8fb014b0fa95861cfa96dc6c5d0a400d477921ac463042ab3a9998ed1a872a4ff6637ade104db20b23f7c3a3a177f46a61512361b99631fa30e15bde93d7259fd9b18d1c43ff4c949b9782e0526c5d7ac0af59ac8482b79c3c2cf2b85eb69f2251666f8cf2f18a1f9b2164489bc418ada50ec23128c8e54d58152a6a5c32466031c8854a9d27c51c43ee29ed8ebf9936f9c09174743681dde15abcd46e5726728fd83aa116d6cc9674c15a8570f79429c35bc676583d3df169b35c5a7de04dbd074c0d5fb24af9c6c6ae9bfe122b388db872382352800b8140aff928a79722f9fac657d316e75b260339fc58df890053a075801af279de8d16e6a64648001c129f02b4bc308d7d5ba85be01fe6d5211067d13d5750f65505a218583963e7238eb0e20511794a65cb35f3ebb78ab5fb22e2373bcbfb6bbd75ab206612ec272bc51ccd1be23303a9b2f7e140e4025e9fbc1d8bc44a699d93d008ca6b27dcc7227bae19ec583090c9f205f6d3c19cc0418cd2e4b520c4a39503d6e7117f0f872845c72929e7f19012178951105518ab16f2acad65e74525e459328049bba047223cb67b7bcb5aa72f374331dbbdfe0f18923d867727b1866392c6a918f8b1372cb4dfc94ab1952c0fe4ffc49049c9c86b1aea3bcd0e0b7417be9104dfa23ec72aca5d6fa61371a592c88a2dc94246d371e30e50e32d926ca36b54cc7d069f85387a6b8ca7bd8db7545ccecba37ffe252572e819220f670ba5164a06ae59a1752e0fbabab864f6542d82f09dfd821eba05689c729a9e24bf7fa7ab04de8d1e27207251b2c2b3b2e5ca390f4005bc3d1e9373dd546733caf8adfee321784aa11375e0ce917c7094a13bfa346ecfe8cfb5504246551fce773c7ff2e37704d30a50e217f15bc330ebbb802c0dd9414cd2f9c3dd1b1a5f67cab41e3ded009a3849072ee46298cc6b7f110716268cd169da709b98d8b28a68a5348da0bff180a1da40db1f8420df16b0264687779a38177ad0639d84efd05f19faa0ee7d84b9dfc29725d029e543c3c2e1efc61753cdbfbaaf2d4a5154b09c5b932937b620878a2af256f3e3dde13182c0631fa4583f3cd6949dcd72bc3244df7ec9de4a919846cc0071635ce50974029feff01d1f31927e617431171b6b21878c52207f756c3c4f23260ce51b7bd4643da465bf792c46ad080d5ed9f367826117291f8a65d4c601172dc877b37715bcbc3de7ae5e5d87bcd8ee835692e1fdd3a0243e92ff307394e545db22c32833fae2862a32d03ca7f3eb4b5126fc1d681bc05c0d0c7fdbe617d7237656d830f04ddf1fdc88bf93307133baf684ed68c52a31d6360ebe05efdbe72bbe7221c5aa9119575d8ac70338b4492928487c22d85e5117e8549523dff43726f377e26fbc9bd94e116903382c38baed7b5a686282e0442429becda1d2ca7721962b647661ece4798294519b486204f92a4f04e3dca32fac286304b1ee75654e8832234d5b691f7944098dd0eddd0c948fea8b430f60426c502303dcf30810f9154c41bf2adc8f9eaed1e0aa6f266a59babe3495afa68760154c41500909a31ba758c1f9f5d1a99d00aa22a38caa31f83114b3e897fcf8a6545283caafcb17219acc064349809415809834acb3fd6337dea2e1c382c50a925f42ebe9cec6572cb46aa08e0f42397042fe9cdcf976dccbf36a6d5d347cf72a03b1799971892726eb6423c3ff26551dfc92e14ff59d8cc9aa83358253c2c436ec894923216ad7201da14250a620c8f5380318eb83398fd3c05eeb7ae8e961e6cfd740fbdbd52721efaa8b91f0985e392210ebbef58644fb51c4eb56fdd06875e6fcfaa901d30059e0ba9429f78590ffbd33326bafadece9485c7e508a05a5e7b47fcdbf0519c68e900bbd8806474ded42f24535c7d9ba183255e18f563f5ed1ea1bd139b859e7257c868480354df2303875f553e415f499de22201539d63941c2986154d1a0b723822c03f2b7a849931c5ad29de9f0237310b3ff85232652ac9f253caebc6b0729401fc3354835679c673591818e37468bb2fe93653dc8ce33a84d95d0c79742629e612f31a170c3868144a7b442197e9f9ad88c3f8eaefb0a37ebdb3ada705074390effb1a42debc96423789f474d2fa7a08c47e9300a1659e6e6a2cff70de40f6b55e0bb16b1140203ba7f90e65078d11f47b7555d5a1ba7f60e9cb5ac4e0726d353904d17c71e29d726947bb7f58f19bf282370d865af1266bfbd03e64470415db086707b7e8fe9a0bbfe7527494fed5646429535f504d3f893e2c88aec972cc792a61903b6a2fa5d7546e111e8bbe32720b84392de25a60fbf80a5144656829fb544b8840746039c9fb9fddf7cc354e439c30255705d0fe25637f7cc178427c308933954d102509fe3d2ab3deabf46b8d6db9abac7619bdb01fa175534d7272b0f9bb07087b7736de4febbebd45d135163f7d4d2de1530673417a2799567287eeb944ed2d52c1ff2cdb35e0b1af107332d355c60f56c84287cad37abace372de3393e0b84413e0b96889385aafb41ee29d7dd697cb16d80d96ca294918c1282605251aa4fc7cb33d5b63e1bbd6edb547f087d91400a2deca6f968d754de36fa57f5d9170485bd43f347fa18aaec82028daed757c9d732b297da5ec14de23223c1b322b6a34e449f6592b66780ab28cfd293fc179bcfe1df515f1433a5d409dff46a9e17acaf89ad684ef4a10456b0b1d6a9f4def3579b93bea552b7849e72cf067cffa686c3560f975ec350a8b93cd095afda3403f14a5391b8aa7fdd3372aec535c0fc9f9484e18517dad5d219ffa61cf567f2ced6b3979f24a7460e0c21b93b641d34c04cb9f928444e30e90a2be4fff2085f069793e2a0571f176a4146621b4bba68d7305cd98c01cac95db2c45eb53ea7c548100a97e96e754bea2e7276d6c79440e3d4a106bea8e12baabca919d5677076fc963d1051bd636cadb2138764939d52fb4780680f84f8c68d95b4e749547569041f96ebe42813229dbc72981a4f2c859206d5692bf46edf4c803908197f4081a33a1a4d464bcfca2c217256182f293c2c9af92511c74f31bb8cd4cef2ddf8da39be5e6a3c367e39361d72cb1ee61cceef6c35b094fae1fd5b3c3436f473143f0a08e8cc453d06e28b5b72672e9c1082579cd668ede40464d208dca036f15562289a60227297db4f0a26720d61651097a1c215a78295149b1b36ba30dc42946d0d1aacde59a292b19f0a107ee7447827b992805130992c6a250e5d5400fd5d61cf814d2043d1ae326c7772ef7ac4e4d58c01dfb756c274dd82736add4da95e4e9ce74e4a13a4b413139272b9ee994914f4e11387b880fcd980255dec849919bb643fcf07313988bd66ad292addc8870657cb559e7fc07edb90ae93bf8cb0c36adf6840e0ea7ecc460d0b72c7c3e49815d652c9de4c42fb079a16668727fa3a7cefc51f6822a0cbbdf4674092750f7f46efe24905bf1311694205e8fd44d96937d1473f116142e592196a64cec97ae17960cc6f21c4ee08005c0826bbddeeb70c54b315e1d480bbf813377224f73d4cda568ca6ba75d6bcc0ef319e287eb2926d95d8bf7745312cecb0a57230262523de49ed81446c7128037125d262da1155477f298863409c6cf6c130512913120acc39d48007b32e345478fec159faa362bf47e885cc592523d1a3f05244c3a41cad4067bbafbefeac64c851224d92e3d62561f69132a44b888d2e3372d14c4ec267cbd1c2489ee731ebf02188a6412a95bdcee6896b127a69e6bb3a72816034d9f7d39ddf13e2d38703ae1930364835e08aac65e597d2d5a2cb9c2960c21506db32811e0bb9a65b77e283e022b13b807b552505711ae11a31ed31902e69f5fe95d7c2c64e52ea6d98a652acb5ed14b967284cb19297ebf4280dc674721d5aaa62956f4511f89ef6138fd5e04e68d75bfbfc925e11da18d52cfd1f4172a53ba616a97fec254c6e4943ed2c0d4c65f436e1ba1bc2e1481fa4db7e73f972f462b60ff63064cd9f982442761164c30abad1b05caf2a09b0739e785ec3f072913f86564af7393b03719089a800b7d0262c1a05d7d4e29477fb55ce0cfdb172bb4f281e4ecad90f241743bbbf5d9706235f58fa91456773cee350a07ce3a52650efb27781dc242f19c662825ab53149ee90a1eacc74ddf08e1e40da4c8952725bde1876e1d4baeb25ae81aa3c0a6abda3ab2e02b293d446afdfe583dab4f35ad78cda0a3cbeb752ca2bdbfa609d0b4e5a2149db1b4bd901ffeeae6baec2742bbf07c8b8d6269183bc9dce0e8754f508aadc3aa04c7ff034ba6bb5e0bd0fa51bb83f75f41f68bbd9c671347db7b10e8bdcd9376f41ca408b3ba9c13718ef6072705c8e7a9ece84c15a8bdc7bc1648cab5f7804869b29c315372f7adebb119c722bfab1100fbb2ba5e6a853b66fae84311d1cdd8d4598a22c882df28a3dcc19720667613e27a2c1ee55ef92e3c6bbf2e05f12366f0f9c1d4f19dc8c1ce88db808862e6aad847f12d7dd53f4926a7b446d460955ac0836b3ea0a3bce9a39255f72906d723d7f1168082677c82a3a9a1e1e8908ff5dce78106613503207cd19a118fa4522a30e70103ed91c947fdabb080097d6f68b268936a86158a1ecab9edc728b382b64c2835525d887147dd78cc2b2740bc82dff4eb7bb42a2f68977839f725096ef3da08badd3293e7bcb18f3640a80f0f81bd1db39972ce76a0dd17e83724d28faae5bc5eea72385ae7d9f0e95b6eada40e4b55722e5d668afdf9275e66fc8cd424ef7e41ac2355fb8cb37a609f72728671a4a18b51a2815bad01f863572f3d4943f90a62972538b684440754498e9ec97e2b67dfb021ece5078d292b07270d528bed9570a0b0ca68f0efe8aa036502a390dc5ba072f762b3708fcfd380489dbc7b8b985623a681e325362a861f20a1a73cff9f990c9445f8d1d958f111331b5b19176927dca18fd5fdc658a2e5b46678341c7fc4a05505762e2a11aef72a39207bda1b7904bc5ffa101eefa73b22133244a49e27817e7e053509ba65f2bdf7d6f2588cf02d3e49b3bea9af22d9c6a05fc31fe594dcc640de29980e6026cc580b088c1b1f85ed6be9682202f54a11203afd70b7da21158bf740439635470b24f9dad16f77475e3e439741a2f2087ae7715606ad330bd7284e800b6bdb572c0cbfcdefb307449ce378a896bd928be467d1e18d3372279e9d975bb49de1866424cf31bf7c97d696aea0485018f638525cce7c031b7498d6b6ab38efc553e723851c5a70eb1f3eb89749767e136a4dc4b493cf08f48ce593cf71b3c09b4ae54d5a4599204512dc293fe7e3d2db931c2fe8ba25d933a1a62dbbced6d5333bd6bb095d58327b2abf35484ae610ddd32d04b29d5325998a0bcf2c5470340862f4315f2732fbd7abfb46c54841b618719aadc51a218ef2ddbedcc35e5e0bb9ddd72ed80db7255d534490bd9288136dc37e01072806faf5a8d1b6eeee75a97cadb697d4879e047d5912fdfc7be32a0b0ca1e9a5fa1e2091a4ff17a0e04d74ade175e0230d4df8f0badf8c416516d8921f09eed716ae0b71696a379b4172b570c3531a1160bca7f5a7646a4b2369e4574b68bb89a0b884f927091f782e5710b2a3e486a50860ef649ccd5e33c8d9f4e8add137e9d075a0aecf00ac63bdce902176c729d2c263dacc5fa7cdf187ea337e198337403ecec7c247e04fb8527e8b625c372b3d3a37defe8a3fbfe6b59c6b2e03d5c3a4b0c27640fcc935e9714c4d22c59352f3fd8691e011dd0d44136d94a6550d10abfe4d4607b96becc49cf0ffd5562724bbc8eeab48beff523bc687d44d8e3cd006490e1afef0e3da179dee2a7468a46d89ab28fe026382b7d775817fd83157a77b35de396774a2730ac6e5e9f79fa72d6edaa5a7f3957b36554fb2d01dafa7039af72d7de699e22392bd4a8bc4bf9506c4c2f83e79c98a889a265d19efd173a2a529f47e0c8392eb9deb109f4eb4372a3b864c8f6fa39d068cbf4a7e6b2aff599f40e9aa646d24c997a9c104923587238eff52953d1be374928a362c4d1b2df999361836d88b4214bba72787a780c723853be92f43450b921aa8d7257ef0f4521804cf7437ef222be8e033e74a1e1721f3fae331ee7297bd7cd0e6f9487565dc70fa295e78ce5c5a2719c9de03af2722a2f67a22512c00a209c7b37cae0f2cf40b954efc7af5ccae2dc527b05af935dc60d333ccac7a7b9766cbe7610f3bed5175ff861659c3e3bf6f82ab979270c4e4978735868440372e6d017b468cc23126cb8d2d45fe5135e902412ab595dd403f5bf486af2bffc01deecb5092f186414a7c2791d67094442b04cbab6be9a8e5796a19adfdc2c22e966f34a210e3c33bc7302212d93f01f6047f3334b613a8772889005f3f901f227de7f8ad38f33c956f4322b2bc1611d159d1c8990771f252321ee7417727db0ffb78d28c0b5bd7e1deca4b5b7a06aa9085f1d77a6fdcbe121e78fb3246332f74b4c337e35bc2b029a1008f81c654de68962a2010014823d0476627c53d09eaed53ef5f9c63ceed319c496958fcfbafe7693ba60e82de95372adec0aa6fe6d0b347aac5bddf1f8bc6d977bfb9a82a8ad4fd69f45da86af1a72413e99bf34da83cf05fad645bd938e88fd5b12110d75f95fc1f9aa40d4af3d72c09f486804f69f1f93f6b1bba6240af651fd3e468608c49f7684fc834342be724248377a8f90380078c6871d6f1d5d8f352fffb59badb1afb1e6c0d952845c4abdd5ca7296789631e3cc23588780c4440a78cd862d0362037a0ce576575085721244935245fe6c4a4a5594f2faedcd48cc85cb06e9965f94df43a4fa99dd2d72ea71809929907682551cd4846216f07b3d04b3772565da5b0edc6a60a5803d7259ea35a59d03a230a637637380a40d2236e2a81934b08bcb5c8ba3525397e17271f70503eed2a713788fb9183979c2e98f0102882b668a98f534236478c7782b1b3ef4d6f1f8dba0cbdecba3444127e413ab38c813639bce8e696379171b1a726df83ee000070cec9ef438053c2770d297b30f051070ff75d0b33b433c6bcb725785191dd998ebe8a3890252965bb42f75b7f04ce07ab7f697a173127687756fa83c99a252e922caa0785976d281acf8a5f0f49943de245f05c617d606178b7246e9f073c75477356c89ab6a50218f1d5040b9dab2ec7a0da00d7ab9993fff65f10cdf11c3f457d5534f2850588f6d6bc4d8919dbda56a491b7d9002c6a8c00d73d216d45e0cb4cfdb1bb09d3099bcadc573b6722e111ce92229ea0d250c3866a607523af6e79d3235a4aee0774c584a4a4ed567f7adfe03265e8765694f2a72e47578c1f0eb0d941750f9d92e72b71f41ee266d4ba0bed80bc85644d462ca0bfdae6df5f96ffb19dd04772845f58ee5cd54d660a0d86a32bc2ada59f52a7b065369f7bb8da345c32b8eb2e543cf94522d609974db9ce978edeab95efd0fcf7266e44dfa113c4b179ce489eaf7873e499d12c5ad5550d3fa21e8fbd3f3ea2a4d8dd671f0d7f4633c2803f43f5673eb1e0b2dffba822854052cc848824f6bf115331dabac1f95b79b38e293be06cd2833babcdd18c68fd71c258f279b1e14c116667dd1828465983b18681714b3cd12bf84bc68ba8e07d04787cd47c2921b397243304df1fe77146d480b7c5c870b9ac5a09e7b8851aff5a8ae833f9c128b1a725fd7a13ebbb04b081b5e81a3359915c572c593a5fcdcbfcdce4efdf970b445721324f265f9aa30e6faea23dd449c623aae6ece5695d0149237e5e3f575737f7291ee2db3ea04e9af70ab3d83d7d4300a5d22b57b6896f8540e8e463497221a26b0e9f8c07ceb9b7525bf64135713e8e2534706ce22bb289b750dee303c2b1a72514bdfc92f4c3f69317c39f0459e9d141158e3aaaaf0b626017ee656dad815727bbb458305d3d5445089fb2fd7137eed651630bc0a31b771e65a385466be57726c03ff8f40cc449a032db350cb9996f5d7be6e44c3f7381e14c80f44e4a34e726cf28c5a1ec384d1b6d7bc85b3701d505229d4224a5dd0e8179243c1233b14729971f3475439360e86eb1dd8a07896115f9245bc1d2346f38172749b1fafbc0334aff0bdc14fe3d1df905f92f8d6869a7e25538a3408b514375684214f386b3a268d12a900072bb1ceb81bf7be1a388f32a05b72d7fcd05a7ba06df5d7b8d07260f8e8fd0e60c8b957895ba71392f623ec99b9423a632edc8dbeb3b6eb7fed7202ff556063e17426243e8c80932111dfa9d951762f626bae7ed46c7fa1856372434d3b30faa15328d0fd33807e9711b4573d3483a0f36b36983a71e6345c7172ded0a76e9e0af0df1f0d3c9ec1f382106ef8b85d36fd39c1532acc788e4beb0de3fd8618b05c24df81148dfbb05d81141f7ddfb5a4e41bec0e300aaa78579f72ea9abb6b8041cd0f7b94cfd1b77d9722ee697f661940c9e8e76cc3b437178372a2f72d217cebe5ed4d73ff7f6cef0eb3012bdafcc4a96c72b8f0d851d35b802a5567cad6a9b6e9ed5920e3489b21fc3289071b0d654d9e871b8407fb09d5c872342a30d1155c3132250477a70e7af8d16d2d5a6dfc496fb2190f561c6c591a724e7bf5e4026286e36f39e6243588fc46b50761943c43750e7f7497e109e889726bf3323c3ecc67917ce04110d9306a9229063407811491bc2217af842c979b72feee03ee33e9ad2a7110b56422e5418af308f16d6fc8e32685895456674a3921b5343d48f97e4e0aea4aaa14b68f69af0c3601c4db194236f3b9bb36730b120f4dc7ef0d8c7ec323e3cedaef5b256637e865e7cb1e3e8b8c5b79144da60a0b72d6ce0720a6451391f78dba878ba7dbe166f35a480b9a67b27e0ead3269296b298d7d63b3fb215b8b9b031f5b7af5fb5cd837cd36c5593cd5ad8347119c98a37270bfb2714aba37b8757a7798e9950d8e24aff304e44bac462ecfa792ed2063727b59b18d642c1e464a7bba0b8c2a33c499ef8b24fb80063af71919ad5563f4543c88d19241378235142c48bc2fe20cf696fbcbf8788ee1b00959ff70e4f12372aa9edf6c4531f92c7d41ea4001ad26cfb0aaeeb318b01a7001747a3d1b05d76ff24d0cd0fde7d68d0fd0b665337debebb10f7b2b667c6227a41380fe4d50dd729c0b5ba301e941c19e4e98637a5c583c892cfd06fc83f7128cb9d26064560872ba81f691009b16dfda774d1010213c446ed62261a2757ab0dba014ed565e1219accd1f0381fed203dd0e6c3fd763174f7309a9b934f40d7a8e4073158123b4244e4fe8f79214f77c6df9e8faf178b35394d0044d2e4726d435f2a674cf3d7a72811d498aa981b8ca0082e66a0945fab7b35c121dcaccac9993cc38c01aeee67254323efdaa4d70f92e36c569303695b2bf8e66b2421708aa328bdb6b56a66b6d580c35d4dc8243b4e0b3c955500bf513b81430d624e1d32ee3ec922eb1b21072c1103fa4bbce2b2b56e4372192a0c27b0fab50c9fca156ec639d4b13f5554772b3195943e1b4241fcdac244896c2e61f85a3ecda7ffac0bba8efe0fcf17edf72bbe19bfd24b420d2e26a1ccd8533195facd1e82cd8d3f48aaa1770ba11d7c072c504da9dd802fdd40c40b545fd9281c6dca764b9c98ee0a1cc8dfc121845c97259fbfc512c577280d3adff9e94b3641f280c874294741d76503a5b80d5f8ab727570a6fef48d37f91dac58a6bb38ba6d4805dcf81c1961522b452b19732cd734ba8d84766c61c5a62c1358b74622d9958dac481600283c3b8c57becbc5d6e003f6d16ca398c3eb68d70d38bcd40b36f0197cee38b50d2c29a5371185d39b0672d79ced7bc0996c305498177add7fe738c38ffeb16a082c0591b55134acdd3c4ed5f9bcbe46e6b47e83f4f8b5fce0d8b7cdc3516d3853b6be2db6901cb4b48f72a9f40208cf4a702e671c02d2af9725e8bca2436086a26df226dd195f53894772473119bcccdcc1c7b6f25744339436946f897b8aeeafea23728a609f6eb1397294ee6dddb7061d54b6c46dea869c46135d8c2063e9dd4bb58e871f0daa841115d239e770d3bf6791d015b107eaa6cd4494f619dc745876498778a4add4db9f50e9bb44221ee0f8ed36d1a5532a536206fd4daccae8a392f1d95b5576a409e0725f060f67b99852ccc1428e9b2938c28866abb8e91e8380065f95ce3d5a2aba72e568faab1797b0cd67a055cf59eef6138815cd341dc12c2655bc034a54d48b5dfd9505f7361600cdae5d8c90b7a6de968caa00c9ebdf9e6877d2439ebe580d722d9e4c2ed2c0e78cb1924f0dd198a3a274c5b6fd49e6bdff8104635da2bb6172d22cb163c6f4681ef85b0da76a7264d6aeb124ca6d87cb8daedeab82eedcf072d404b5efdbf8a1d195d4c1fe71fe019a7ba63fcced0ad8ce15fb2d95b0187201486b2076ca2bdfddb809587c725f4b43dfae1b533b5704722a5bc9c046bfda72048d0edb5d9250a2a64fb66231b6c07315c99ab29cf9a15928af404adcba0472f96b8132f3fa346f4c1f201a2dcf8bb4c335f8149098a1a2ffdf8778ecf4b8729727909bee47fe0ee70ba3fee13f6592b626a07cc9f334378a8668b856782b72f91331058dca4a017360e7e404ccc7df936dfbe1b5be47d1e19bd0f376be7772204eb0267d010784755b2ac63c9792d5dd26dd6171c1f5907087bc69c2c02c08e0c23c0fd6b535b3b4e5205d00bc39b9fed1c5d0298f0cbd345b4302f44a65722d2a02c989f386a418871b8725c4b6200290f9e071a9a6f12a43f7214f28a22a100ed4942eb82c2d54de9410e0457c081c96185f4594a6cdbc4760509799822d5d3cea3a802877e9cceb672cf329ac553eaee589ebc1d28899f73153cce2066c2428a978e9f568b5da1203913a2fa25deaea5698a055d9162977d43ef784d0722b72d42b34258a1e8107900f96a23dff096f08ba8e553bddc3ab97084e9a9c3a764a1b5c828b72b6287d03f307d5e259507931015218954fe62ed6db41d8a772484aac866fcf487b74c3999a96343e67255dab5b8b26145b117fde66fd8a531dafd2bd23502aee6e7883201d4c7511d2680caee28134f72a805d2fa09978fc70cebd6c943de5a34ae6a8aaf232d9187138fed084d4d66a63b8f52d3af08aa83811b76e7551ec2cb0ee3f476ea8faa8dca12c3288c034283b0aff9abf6f9e7a3e14e63e7bac572334e180555b414eedc5e53f4b69d903d2fcfc6a88e596a9022d55ab201071f5a08372dd495f1afe94b978f6710030097e2d114ca307a6e54672cba2d300a2ee8fd8709581e55082271b8e46cd773e7257b2ebaaa5f588316e72a5060741e774f001ec8e357178f8648c25be88739364d5d143ab4faaf46954729a67fda4d9a2fd750e1d39acbd480dad0c5925f6cfc98ad8dd95de548dfd3c72065a1933a8e94c29a9f4f3194568a1e103d3d498e391b188266effe884aa34723436679d85111c28ff33f54b742253c88ac4ed5c98f7497d4ac52b65370e4f3d8ea67bd441007e62084bb9b988e52cec1f4fa6536cbf953436a44cf1e3f049728099ec700099ca0b0bcca9be9945e70f758d840696badf3a9cadfc2fb098576e33db3a6f9b109ef3a0c97847e2abe2dadf3622bd71678c6068142a340286b57244dadba04e7f2f640c06066efcf5d4a12ed705dbceacba3545ac872ee2d06c720dc7c9be065fa8b2f266906223f154289d8f94d16fca153c4222afb7992fcc692255e65e95db81ce88ecc83b437ec9aea49f77fbd85aadc7c78cc0a25bc3e2729f233648dec1cd853eea515a390347ad60ce32d6dc4661b079989b57d9db45648dbb001a055c9c1f858499279bbc2021aa9ca86abef6298f45c7dae4ea4a8e7252e5d00af8564375ee02c2d4ade217da5d61199749337bbed2837bfb0a648b724862ddc3dae75574ac363777a6dbdfb12b19c0cb892b95edc75a879c2847776444aa0e0f027bcb029553f128006c1053beaefcd0220fe2389843ae684a90a072ffc6b4414f66e855294524349df496a374b44130d4bfa104f3deea4322840f72bfda441f52ddcf56751b75d267479e809452b61da92825b79973f9fa41f9f70ff5218d8e1f665deb61962160c0b75e0866d2468c63f98a7dff23e71779c11d31fdca7616d824dced6c5254f06d5c321d29badceb4fe05ac7acb92beb102d4472ed76b8f846fd58af021d802744bfe4cf2d485e182c0973af46b1409dda560e7233d1bab15871be8df7b8c86d12f4934779f54b0e9f08a4a50972777bd7b4767271185b48d4c6c44eeb3500ec67a89af229889bcb892d655a445d061b29f40c65d49fc05719a26eb2232e762947b6c92e6f030e440054c2ffba5250b16d0dbe72571aff0b5a4ce96b961c75853eb7242b98887122f0ac75de9f6742d72a717d415178b37fac03a6f8e0cdde1ccee6f4fbb88ae152ce489bc8216b8ec8e81a634651668599848c346d80cc14c347a691ba43e586c289cf6a4c4620aa3b56919d72caeef1edf84173b5c2bbc9fe5cb6e649b0297ab4b95359b9239d0e144426f77281b3a8404a71f371512eb6d8497fc6c4bdda0af882479c43195b1a627cdb1b1f892d50319324d3e63bb396d78065ca43e8c6deb6d7023219aa6dcecf563967729da25f3ea41e98212fa4dd507b8f82963be37bb913789363e429fe400afff4727edd3fe6baa179853f04ee53679e6ffb70cffb6ac6c799c27eb509d5d3453724e141e83e6a15c5f3f0bbf702891ce4a2d054b5be34e3587b71fbb45ad2bb680785246e6a7cfe33febbd1a73900090ea827c7881bdb7b4020aceb9378ec00da3765320898186aedfa75b07ac542ecf5a8020ca8bb5f25be026543a9aa6040763e478ca44bc94d1ec4a5f1cdc76dcbcd2eb5be4666ae4aead2b4d25de659de2d14ae2d4e6f47eae85511fa89c234872cd5124abc7247a9ae53a04ae8d886983d729e3928063ed01910e095f0736b3537dae79f7787377103042d0e52ea5c87314e0bdfe77d10c43e2a86c24f89b84913fbc86b0874a1b81ebae12fbee1adc65716b8487486bfa24db920bc4a516cb55c2a33feb351963f954a244dc7556d14034ea76b804ff887088108b96753527bf95fbe44f7509a6cc63d26f4e66bea9a1a72389637de6c5bdf2f27417b7343355e18c1b1daff6bfa4ff74f8cd7e4d44ff47288eee1179cd2ee863a748c8cf9c7e7a47515437f54d72e4148b05604e64f5f72212af67d4ac769d86ef83979bdc3fee323025d0b999f7a5ec12ee461ef134d725829a5f0a0e5b997a03572ea1a4ffb147ecff205fe97da96e40257f5f7e9bd1a025cf0f36041932a4d5e8a3d72012fcc9d0853b33bfdce8711e41c92e6e00e72f2960b33bfd451bcbd6e80c8e3d324cf29c00a61678070a9f339e00822570672d68c3557d98dbbbcddac4d8fb3f3b650e7e1a387945a75bd1ae69df0070cdd7276a37b0db1462de60e4ad94022be70ac354cfd0da3945911e3a8d2d255e57872e90fdcd19a60a8e03232a7288316b4e80f36fc3f476562b5058843d0963172045bf0c7cc9e71d334b87de1aee4c0baae0a43b784e15cb9fb985adddc37a25c6b955212629f2625b34ac613e3c6dd9268f99e1a5b8af0147c8a5893798668c7724a432274a71523590adeede7d1b9a106e0627710d00a47288db6b714ac430117e5b1d70680f60f23f1ad8d1e32ab04c064ee385f81c6483ec488fa14e58bff72503725a80cb51d4cc10710dc501510409987b6e18dcfc11d8777dfb2d35edd72d72d1a0b9db928edd1116d65a31af778fb58ea69c21f154b3fbd6a0c18fcb62172afe641bc6b85777f7be2545932cee85a090c84fcb1adce49d95017a1ee296e192a54815ab3fca591110c1dc84ca2dd3e6c683ffa9433ab82c31364bf1a730345a0093887aa84d39ecdadffa64d87230dc5a537a3ddd0ffae52c226dbb41b726f9faac98474e01b374e90e39a19dc8f5dadee0439e66d3dabc8b75219854466e382eb869ff90e7338213bbb3de2ad10f7bce1340f6eae95be4d3aaca6f38253b9080def0d38af6da2eb804137091e0fcd036980d6c26eab0b239a5b39177d725288e3cf1080c505f4beeea38bb74dccc6d0408404dab74e54e6e738e71b4a4df51f10d4a02eccf16d072d1b31b0d1e75edf5402aba452f591e36adc05b0bf7220b49cea181410a443d7cd0f440bccdd4b426d20ff06d8a129c125996c563972850ba2239a9a24fabc316ed7f9361affa15fee45cfa64d45bd71f1995ed4433137070c0a21cb4ce93e780f63be7a0c5f2124e835214113cdcc1d8b6a68c5281d47210fb58cfbb4e795c8402ded25d83147a568267be9b9f034d4039bc9742572a04c793617607235250dad057e7514bb09461290521cb4fe369211ea5a6c53557f184f7ece8c1f0ed0d4f6fd88309bc0e10dbdb65f81b698f66f4c4d7652d1720b5edf512d362825b01e266ec90bfd06e7b9b9971b58de055977cf9bb5726f72adbf2eb1b9932264a1fbae237ee7be0dd94b534daaba81304320714aff719072d498dbd9101fdab848bcd3e8dee4991ed66f4fa31d70ab2204db84025207617240ea0038c93e5ca5a1952a9f486c35becc1cbc94c64391ee6317dac3fedf286ac69be4d6e98b414e2927557f3da08678db47b3e0858d42eb9cb4c7ed643a8d7260b0deb1e14d40e94b1988ee29b856e4d91dc24277f7e2fdcbb04741dc2fad729fa1a3e04a594c59f9aaa52d28abbb2b1b91d2b26f7e8ea916422b81fbf33e2d53f1a30128c37bd6f4eb81716b8d3630d72127c290d70d1e5c6193fbd532227254079d91e0f24758ef202f97b758eee8021faf6d9b8b63364c03f6a1814f2c728fbe4fa1cef56d0ebf6c1ea4898fd38c879a5d180d040278ea3082b2c777983fae7dfe0f74e9c629fc9ce4244d5da21479c963ac365d4326f1d9547b04ddf0037d8c0ef5a3a88ace92d7e71a8b79874160bbedfb44896c2ca09f2988f5d1fd72d28ea87ab35ced4232ed2b8c5b9b5eabb53bdce6e44b728ef9a27475894a2a72538c16d175b8f2161d73cf78a2456c895a31495d642d23e7b928932c7ec21f725c05c3edaf4b2b2ca22d618eee84666846d87f113faf8c0afb21bb9ce3d7d472dc24b952836d8150f75165556e9c4e149576192a5587ecedaaf436c37a133c7266c35f46ae26e5337c91cd13ff7326c8cae7c65eb2861ee6dffd0902ba6d40727951ea452f0f9e38e1150e015de9e08eee9f38e23a22ffc98f3bf34926f434720fb321de8825faa9f8e4f7cb47c56668d16ef4cf13cbf5fca1e21ab23fcec472fa693c7b1981bc77614dc602e25153a22530e2dc299823d02ca9b36b3dfea972d9b06e7bb28d9c76f74c9ba686de16b99f3a69552f7655bf54cc0b207fdece69c0473ec87b0929dabd43733045e123b76c247e4935640b8f89295deace3af80202e484879aaf14a256d865fb05077847d118f395c90cc79e3edb8a48d39c7e722e0fdfbfc985804a21253f57de8227166af46ae7a025ed647419e956538f8316e67d2a98376bac56497c97ccc85d1c0bd86c2e3e30d2b18ad7a74022448b231d497ff727389c8788f4ac37ad6f03489ed628453642e7a9cda931ba24657c99722ae34735ce362186fe118a8f7c608c5a0a92e8d4fe28b9974894bd729e6b796132e698d84bbb1a767831c39b7fe6f07a360e64302cd4dc903229b216c9bab77289f88b997d9c70374fd6e80ee395f042c180f40eda7d23405bf13cf6fb92a2721d9c7ea71726c1ba32ce3f93d2db19e97220819f9041513190f6c8bf15b6df723587d6a93612239c155a30bbd58700db24821d018c4b13630d5847d408d79f0f4152cc32f8ec2f3a5f9b238caee809c283510237732a026e00723400c5b4547229966577fcf4e2ca97e4e082ee361f8c0237674ca49c3ada6e178ba03a0c96724be194d05acb20f62dccc31f6c6f5c8b34a96f228cac4f99a51bec5bd17b8e4e955403b25052578bbda19bf8bd86015f4f134c4b711ae7dbeca5ce1cecf88c72438502e1879ac0ac9ff17588127e11415bd5a5e5c272d8125a0db8c931ae1d0f4041a5dbed693cc2e341836d183b86121813b7d2dc1806dcb8e9811e2d7226728856228d482a8fef96f7873dea2b50063e8b858f94fbf28140a8311c2ece1e2f2f7a801c4129de953b6b58c9b837db80bf073515b7b8a6a7b919858f7b62e9563bc00eb1456f94730d3366a26510e3a882f30e34075212aa5e58a96a50de7531cb7bfc70e6d2b80129917c1baa9446f1fc680014d17e021c6dabe23e3d35bc72d4cc2f1957db58dd4e0d37a3bdbd3a861f274380896473e76b6fffffe8661f293f74386cb11421250dce28edaafaa8ac5d6620f2a43711d3e88c8a49655b4772b9c2169e76a0aca370f852a0a5a7d269e672d77fc9286d7d87abc08d3040fb0829979720ee9c785d1739d71a7f59f9177685af24727766d18f23f29db6559a0684dc4d008691739dd003bfb0e480e923e5bf0ba27e4b84e393f9cafaae727b729d55b2ff5543022bbf7d0425a1aef259adc898647df0f05528e47f4f915ff86bb71908ca716367f18dae352d947d7bb4820599e67a1ad5b56f901abebbd743728c481484a37ffc837094ed3711cd439f72d89e5f0db921e212357974076d3372e236ec4fb739167d8807edc75adc519e61b3b8033e7abf24cf51d44f2879f2728c2d8334178bd551604144f460a86cc8f66625bf7da3708a83ded8b61bd5dc305e2be94e5da0059211e13e3e0bd95f15bd4782367b31dc89b8cc25b68ddea13db9ce116ab3666a8e5892f65bdaf126c9453b6bd666e3756c034c199815093e72f337d6970f2dcfbe56ff436cd90fca92af4aae06a06e810a62be8e159cfc3b72f5e1c9d80091791d53dd884bfe85b3e37130c468f350865f4006249505967c7256f893149d3c6060c3526362276eb736f7ad62a1aec919b5e6db3c6b03a05a721b1ba00a5aaa13aee5fd16a63aeefc136e631e372dca3348ba2137252159697206c2f9948ea7949095e9ffd9837904abfd61f8025dfbbfa52fc062fc158d2b6d7597ff4f59fce8f2baecd71a667a7237c6e7a5b1c4909919abac4b5c0f0cae72b9795772e916942d8b6bdd9ca8af9715edb99659845b1f4e37095279e10a02725a355acefae0c338b70bc930e1b5ae646fef538841836164a3e684ce01180b7237568568bcc6fedae63d2353e2d8e0bcf4358cfe36a01f41a3a56e1622067c2d21514ef906ada7b0499cdba4b178de40edbe9171f361772e84e68e7e12986072d039b6acfea97e206ae8401a3196fef843c18bcb09f6e3a1ed04ab6962c39c7288f1c5014e810e333997239a245bb899b7eda08f307eafd279299657c19fce2b4573008cdf121ff0693f995721e129f5fec43b62ee47b3ef0571dbfbfffe4408226d652b44f6912e90acf3d8e5563b271f83a518f4a720f1a3fe768c4d98f4722f3d49aae3b670c1d213ff03c692536e2d3f180e6036d2158798429e179e4c72de89395185c1f09f88e3aa21f4d3f7990e245749db873706961e3c1b2a26bf4f68aa1583c9d4770b525012a7f9c1aeae584370aaae282cc51cdb59b74684a5727a8248416dec9092a363c0dbaef9f1f5ebe9861a12565a52a1dd9a930bb98d723e485f1c07f14b23a1ae1d3abe5bd49636f3b60f46c738f1f48c2575c7337f6f8e6dcc4c1a3c0136bfa92ed93f80feef7282c4bd37bd25282cae053fa346c672247a6869a4c75329f479e13c66f62b982e313bdd72f11b5ea340200628d6b569d5c44f07ea834f54049c38408b4f255d5202b0f55c667d73292592f72b678a6473c1d4a69af5c81200d92dc1cb53a8893133c1734ea2f181d87f4b7e88e6973aab5472e6454a1bb92bcc9a5b6d8208668e5c905762422c651c96f9e3c013cb6eb26898b5c4bbbbf41665ab9ddbc4e5253e4fa8809d25b58a302ac6cfcff7c1267fae59ae97a5825587f1f0188c6138de4590ba6511232f89054549738e234872b8f08b6b9571359536d4ea2e753c25418fe3c68ccf73a85344175b5d9755177215f6c809b882dc2d3fb4ed7bb6f4cca4209b431540dd240eb08b7fd2c5d05372b7738a9e51b10589c5920f920da4d2b4e0f309b267ec2c70a69e077379aaed717df63d4f446de3b697024ba6d3786573dfb8d6764bd4e94e736b16e82b213c7289f93fab09c568c3b77962a617ff99ee4d3b08d8552fedfc90865d4279bc870980ec7a88c54a8706babd1c50577d9643aebfb562f91d01ea3ea1c473b0ca2b7280cf78bf416f1e9b22ee7893d62fb1405c22bcd48d927375c6290d342d8f173f6f53215e2c704a130a2043833dddfd1eb581c62c4b2e2fde3716da1bffbb537260f71b045e26f1aac824f095346384c87bcac0d69c6fa2625a010d6393095e728eb48e1409ac416f405e7e7ddbdcb9db0f9d3b3774ce81acd67e7a84fcbada72b6abc67e8d7247567ac6f34407b5e38e128b8da4a8dccff751d3974fed7c2b3a29fa5e100c09837ef58280f022b02c74c9592fdc7b0ce9fb9752090fa6f798727494ea1f88be70d44ba4c5a2ccc6f24db878072b87377e77e1ad6dfd3604bf44272fc56f360482e39b8efc8c631ed2237c68cf3fc0003030e0f8850989892829af36d83ad5dec23c70b10a738194bd3a240c3aad72db260a922b6fd774de582f7f46f28df6cce10bd55fcfe7ac7af76ccb1e018b049a31484e151c5de1f5193f31e5a37fee9ae01b7b2ab665abb4eb6aaa9b28c691fedda555c2caca68b16840bc41a35e8537681f095ac13365e00217de8d902ead5834d5aac84d3f1a7d0f72b9fe2cffb46ba7a44520f2b19f62ffbe4534a75cd70da6c76fa423d8015e1572a31e8afdf2ad916ef17f5e3f4d0a3c2d19539242e515fa5b0736b90cfd9ce172105330ca89d94d92e7d4bf804c292d7e52129d0f0262940eaacad003946e142646f50adbd5269bf83ac501ba2339a9214dae5825855826dd1e7a2ab7c9f12e724fb19e69cf5786e04712e9dc9c4922b4ca1a43afa38d3f50c3871eec5736057292a97886d2f5831f28417a7ce08aaa21c42f9f90c4461b76fe688f31846e563a7ccba87eb0d0ad82459a1c035de34bffaa79f4ee072014b6f9e3f29af19f3504537e62a89fa3de54355a088eb1c6c5d044b54a871c2b44a29bdd2fcbf3257146db3f96fe7e754b9586979d4e3121014a2cf5c32fe2f6f6a28371a64d86ffc4726f5174d84ff281e1eee4c90c4b2abf9111b38516c605cdf49cc711b1be2a03723defa7010114c16dca2f74f8039fbcb2f07f0eba7e4bcec8a6da06bab13e1841ebfc6241f25f77b87a16656acfef0b5458e6dbf8a651b4a2a20dd86ffffe8572338e97d7b67229e85e5c54e7f3f6af4e4a03f702da9a10b3bc3ad63b4c4999729bb064eb6b1031ad0eccae694871256ba4c98a8355666f9c1ff55e6cf0f62447aaba7f00d416e93d75180d42f713da040e42445cbba3cefea4ee697b1048d072750b93dc95cce617d237b1058af87d00ece0fe5227089e757cb5c5a80daf723b2612fc4490d8fc2b66174c49eb5ef4096413aaa9a55c55f11f9b06f9f2f74c3f30705bb31ce834708c525b4c1a8d30a2601cff37b5f7f00f264d3833f614997249ff9e0d33d9bb0226e7e8d5af2b842c1e3906f5f439dca040a8264b4578d572d03eb8c97bd6d4d23d53b79a75c8601f1bee0617b0ba95bc13b51e2a9ffa97724379620fd499380881ce19ba20d7ee1e94c0d931555c1d84901802c6f2b620320f4adb37150c90ba7c6a692dd0a8aa10313338f6d0ffa8163ed676fc1cf610104d40efbd370e71176d053161fedb0d56799d9a64729855afb89d4feb063d594fa3833aab2062d15c439314e96b18ea4f6f44001e57ea8bc3716937c2ac7c1362dbe38cea0d539e312c8448c6229153aa89be11a5229c52a9c59c1ef7d7316772bdd220d717b9bc946fa2343b414937382d0bd2bae0f34ab9e6bc4e5d465c42485a44a0c1a09de6070349d049e65a2dd5cfeb2ea5a5e010511a5152a57ea0a43efd0b638792f3ea79fd862656fdb5519b26246ea58ff41b9e8329fc46e9dcf25f195400c68be177dbda61fac4fdc71ccbf1d0095542494a67ecdf4299061d7c5f7f6576af80501de5991dd12cd34e099c32f84e10e35f7ec0d64acfdd84d65872ee74a4aa51b1baa1e393cfc6c57d49494b62b3ca30c8de0ce2edf7fc61523a3089a5c36098020ad0591ae7c9d1338387adf086e72def31c6fe7afe2fbb1fe421c015504bdfc0b30e61b555016e011eaa5ce5770b2e10ff10150641e39ac62f72f882c1e91e98fe9f89a4db167c8d4a3e2b489a680fef0d65d3803f426f6bb76fe34f26bd5027ee12f663ac09d35537e60ee3c6a902faa82a8aee7819471322722ae290a2055b18586ea882075460096a86dd9038f12a56ab85a65fee18341f0a5e9bece22a85995feef13a6363e0de24a5869d00a2952271def2ef0b9e1c27726ad11c4f19f8de6b0786ae6092159903d5dd1f67705af59f917897acf9239c72bb4a92d090aa2d628c8631d074f0cd40f2d8e1640ed3d4cc0bd2c795b3a046303dd6a28853503e2eb5798a5603ccbfe180476aaf649f962e40e32b816bd03472a28faba9b4a1b67079726e7b05f2aaae77ee876c2b8170a7182db6f87ab42d72b27af1e741f953b4ac27bf4ea68a4dc2641e168401a15a8502fb68b66249b77222dfd02685dd9a28131d1d119984446d8444689ed995cabb0946cce2fd80952d83bd6c8ab05a0752414460df8405856f96f7e486d1bfa748444c8a951b077272195cc9c7be3c29d02dd489624aea9b1d54c2cfbf5b5417bd6896c28201de5206d9dd70ef676a1ba84eab46222ebd01166e3298b0c7d4a8b166b30362e8c64808978f81f0e967240fee736848b66ce99ffab42c6253228bde1eb596b521f76e0056e4d8ecc49a54663a5c30bb850d1db07c892738aae7a0813ac36118029a9b6212f41972d62828ed94741c1019684b30510876d1c850ba8d75a1d347f93cf9728c0f89b20addb1bcadd8ea2ceb46dfa6a9d45f0ec3edc48e15427c74f8fe3b34b56d24d0e8169abbf10f3ef819c83f39285fe2819ccc039e5a1d849d48396c119269958785ca231bd19fa0809ff72f37ba822fa96cc3c93e026476b309286728cc611d878334b077533c6b49a96fc504c77ba71387a21927c65f03f8c100ba0e32668cd2bf4f36fb0e338614eb2ee9fa17c0fe2e359851a87cb500bd0485177209d1fba39e5a6f26410b8db24e6c82908738e705ad064c2384cb23a2d7940a72d51d965ba0fff08ec872391caf2150e5d7fc283e6a5b56ee881f358347406d72851448593cedd75cd44f36ba2a3a9202391861bba10546bbbeeb9f7809eda76109d1b17585f3927213e4682ea987d503773077a310ea53fcb52eb5b461b78e0e8859e6cd3793b0bea6f66cfa1544bf8bfc04aeba267bf63fb8ed1edaae0c4a5a1dff19c494052b56cb0397334fde265ad3563394df7ca130c6044ebac2e0f01d01aaed90c8560b9ef1ebaa0399095f5a5773c6266b5d7b871ab8a8e8a9eee92f11b2e4447b08117b0323d97d8069b307980a5a2ff2b7b33d58e356f01c70c47298b97c9be4f50a43156529b64446171c25eced19fa818357afd865aa6ff43c34b187c50abb487f08f7939bbc655826a92320b55fe04a0e4c8dec75b6e2cbb172c1a4aff1458db13165dc1279f148a7b92d2f5a9ed6bc5d0e421c9902acd7f82f5beb24b61a66db84af3363fa4678903f41919172973512dace994dc1ac0aa77275155f019bb22d8c564ea885349377a556fc981406a49d7cae7ab6be66d169254ef855ea388d710c110c5964a40fb88c37520605ca3037f83fd75f0000b5284f52d3e5d2614480378d9fb7f046916fcfee4a1bdb2dfba8b749770a87532fc37203d31f2baea5597c3bf431591b1046abe791d1a4001d4b486cfa5a11080fea1d01a76abd440ffbf2f0a5d6f481db39375061668df6f13912b8f214531c7c227276a41dff13dd00d1b0cbedec83e1abbaf3c94b93bcc864deb13d0e37290cd42892bf6491cf0bb28c8e6b0f4765fcbcb1ee1cc325bab598b1caf1f62d41445e72022552159a696c673568102bf721fc8b45e58041795cdcc317a4fd9de97b266990ba16abf64cf2a8a10dd851812c1382d32c94b933b519fcf98ad480ab887b7293726b92ec48afe5ff279e960d78d87bb31d16cec691ee063985f389eb87d272cb1b43eb715de18cd0addae0b8e8eee045510e5605544a42a8729238be02cd72d5d4d3b1358f848ccc6350c2a51f593d14e6d32b68644f6ed1ba702e3483be728112b9ccf0bdbcdb33d620cbb7ba4e2ddfd1fe1b1309b0919ff30ad912eb82721ee4d03dac6e5513e07d9300412d84e854a1029cf0a5eb49a1564155bf00fc6aedd99d3842bd7c70dd545d2bee1a31a6d75137731cb9bed2bbb1fe89cd686872043a8495755dd0174bea4c639c6023320fab50b92e09d753b40b38276ab7e772b5b6bce7aa757e6e86feb3af7cd3852cfeb10e0e7cb6e781b19a2e1e8ccc19728ff6ffd4fe2be21cf23294b898485cab848e8ba0dc6c59d9ef9104e765731672c8a4a36a9885dc9b21a7c0de6909b8696f3ff23d7842ffde1d40877503519c72e7ec3f284687fa5b7f9134e142032f4cd9cca4faa0b30c07540d5062b7a8a9727d9478f52b96252381948da7d2537a44608c99f0119974e7b9015da2b0155072e66dfb0bffe3eeefd930bee054b0cc7f7bc370b1e1e0642340030101c9b6fd04aefbe5d400abecc135f24fba912a63884b2441e27009c1f9530cf7fecde70e3039c550aa074a1af195b268646d33c09e4f1a550973a48bf23c9f3c72310961726d301e5f60739a7d73364439692d8d337e65ee1cbbd9430882144c7eb45d3272146ef64ff2d571e3bb263f67404637cf830feae9e8de13145ed71814a303b272c61a583993aee2264709cc3ed1c845e256282ea780b5bbd496ead554213c2f7257eac938f2ad39421b9e6c2c70762ca53ddcba001771e4564b0001c9844a0f72e06f2b9724b6346ccc7dad5ca5d6d718404bb49e61df05e1e8fa84d65e4db9640e9dde2db9694d4ce7b355c6b291474f3f34bc46d86130f7ab69a030f3924b3be279add16fb3d4b09c58d87e784d0b5a86b6630563c10193aa01319a30e4e31a1feddadb1f020a430b262af1bf65cb5d9e436a61cab608c458f1405b5c10930bf8899286142f42372a7345921ca4dda2c16a5bcadc1e8fd0b67b0a4c5c29907250ae20842fe94afcd5ec3c19de8765d6707e621ef9de3afff6315b2bebf63f728ad6446415d945c68549b7a5634ddbf7e089c829daee5af2c60f6bd06a870e12a6565999c31c45dfbb00caf95602711172495bb1a32ecccddb578d19b350f172571d007d4cd9a072d8cd36ec89cb4cdf5701c2abebb5383acd7155c1882f2f367cc436b8185a09148422207a804ff7dacd9a944831f83623e4e48019d10a6e725b10d66ee2ce9610509f1f967c2e42f63129b64abeb2ee32311aad60aec34f4f867aeab704c245f1a1920dfba7366717cff02394b9a391a6aa5706f7681b6520231e4ed7cd016720c18d38c74b48985fee918dbe258858f54e0c7c39b6123c72ec4d964e1419295fae911ac513b71b7122e724e7bf7dc610a55b714f7d72a4726b0c5def28d5d7ec4d5a9850b798d0223d7fc5f241dd3821b1025e1a8d2f1d6218031fd1cec5d9d825c32bfd2027522e75dd8491680852faa9fc7d08d09ed76d8b2d3e71df73b26f0d866c354cc5ad307a48d71cb81f62e531bf9152b4821772f8310527e6b0f6628375f222b3decce0d33d2822700dfe914dd6526e09f2e5725d6fb8e5eab15380affdd8013beb09d392f078d43f562fe5374878c1f61361197eff5799bfa98cc93d1f2f14792cf532eae59f45a9109e0c253f503fa45587191f1de64662bd60ed896ff77ea0dd421f948e224931ae817383413de1153a50721c0810e6759b072381ec1eca2e614278469be9192fa6fe33996565850bc4f6013ebb54d56870c3d2c19fa0585ab2ff9754fc4aac81ba35b4de36458765a93e2754623e11e0feb5764a127c58faf839d2aac84250bdb08c7b0bb3b4c850a07772e4e4c11cc6b124ffa35e3c70e12783bbe462d98724e1fb4da7b2c93cbc23577260b30be72426a5f4ffc63670ecc08d1bbbeae8aad9a8942f092f6030fdae337270d3b2db18729cc9297b66202b6210e7951cc9142e97239f01e494f6872394724671decbb3db9aecce9c6524b44a524021be5adb49df1276b97c96128613fc582f6a1466869390f1584648ea1a491516b6109cdb07ae7ebdb69fa2b1cd2b746a55309dd7732e1746c18f2fd725dcfc65d65ec9609db3a91f32a6b6d6fe8e6e699226a8fd9738fa7123c8f07bec186729df97430fe70a755de8bcbae241c94572b9a9c4d546a0f31397c4934e57f17f49c0b844e4a90bd57105664ef7b2c7aa724e2f939531f645ab115fb3016132a57444fe5e5f9ac01b676b7abeb44ae50e720e54f69e12d6ecd376167f7c61824d7446f3653875a066a652fa4986b76a9c7232a735d09fc6c44c9596f9a87d2abefbaf5f82b162bd4f86e06b09d736e013725b4e6422afef09ee8a9e208e801bb52356ac846a94311e14c42229829c3bf26e358e50f81ee59092451b5f3f8eb8f187c3abad7deaef55489f018ea1a88b9101e1f63f3c9c33843349a4f7b56be879c96bb153a0d3b9bb73842f1b6476a9a52aa8e6d50723ac34a5f0d91534b307caf5173d78164352467a8bd52055b2cc63724444ca2cb0edb8c40b2e5f17f2a834edf294d5b4a77c1f6780ed552af0824f72605ce04743bb18643afdbe702df140c974a820f1d8d87373f53b48392de9f472ab7e63fa1092d159895581f74a572c0004ec73d6ba13804c559d7bb14d14e27238ed319b37a6f6b79c3cdccf933b8a213fc8823bd9047ba71facea8563df2c0c901902d6d44dc61602b28cfb8284c7a82e38f3128b5436b09d9d4ef6cc8bbc72b09624bf405b7e6171f9a4c4c2fd1a61da6a4bd7a599dca98e2bb510f0d57b39168a941d323734621cec73bcb98ebaada12464e3bf9ffb7f37fd773c6cc17e728620278b90883f1556b3c1f8dea9af522351c3436f5e49cc4c68817bd94f9372cb10a69a0b9ec326727a18025051e0e79d9fa44c634fa52267f80015d000cb07717348eabc5c45c04775971b0366ac3210137a8cb64b14e6d2211b208b5ea1721cca973b2f2d8c3f9bc078f65b348ebc9911002a041338f085792d8f7e987172a8a2d67f3156f42c7ff98ff0e374d2f74f2875e686d15c36b4f265f943b311051cfd50bf73d528458897504061961008975410ebdf6c40ff47685d8a52da10726cb07cb5bdbe841753a4b9858e864a2ad487ea76b2a1cb29b3bd344f92adb52997ba78227869fcb19a8d4a7d16fc29f1a484d9c9a8ec1bc5bd6bb70ef68e8072570d5ed4bcf816667b82b5a445f50591ce60b1b9e36b7aeb4f27f0759929c07233076283c81b6424912b7b3594e2b824c4ce30d198c3bef0a7a0b1e7d9e0d872a14d1d8e6322019c934a8bff2e2078ef0db6b14f4b0affe197c55613ae513e7247e5f9806959f9ae036e20d5f1adc831d3bfc6d94ce11131df2c5f522c5fa572157731af8df40fa980003fad3fe4e8b725ada3d807dc92362552c87ae043a6728ef122a9d9f1835e52d9c9fca63c156a29abd7dbbead3ceff7842180bfb48353966ee15b7b39de44fc38ad9dc2c2a881460cb88c8a834ccd0b75489e504f9e4ec5b2d2d3a0e6e815624b10bf5f797a4995bc8d028c8276774c9d54a9e23c797265c2b5ef25b18cc756b5d35d0ddb0423f0c43bd64c9fdb7634612eac3a409c72b9c456cbd5a733326576965142f88f9625a0f2b23b3f915cefd7be2e3cbb7572a83142e27913a84d87d0c9c1113f793ba07ab9d45ad33959f84c186cdbcfe4727de11d0b20a791ff47d75a3af4a130a30abd0703fbb9aeb8842c5a72996df00833f688e9553ada1332286ffec4c1b817616d2dcb6c3ffa72b5667768d27d0e4077d6fe2276aa4b743e53be3b22645242d04125f02687646ac74d2f1550dab9725fb4978d801f45212564924b7345a6f515363cf52e8ff54f4aee5aa864788572af31e907b237df6231417a5aee11ec4d24723413585e0ae4c8ab19ab6cafd332b13d36c5d951591812d616de3bf71cba224f9f0c5d51992359d9b1d745e76945ddfe18a57338e875052c20916d2e670949dc788cb51167cfbd0ff33b7c94320f3704c4f2a9f11b0db05b6d84cdde90af03a02f325712fcafd3dead160d2b267251f072d6e950049a5427a7fda7f70aed6554b38b62415d9977ea599e643afc72554423cb3cca5dcc03f3ca836118b2ec32b3cb5adaba7ce175a4e7e63a994f7230f41f6faf2a96bc2aa4ee041bb94a04154d6e1a0476aa7af3e7e95bad320a0410d01379b2166ca9fc3f86ae8f3f5b8ff992cfb84d5404d0ad70db3df7389b72f30f3a9607452eb4b3d91fc2ba35d8fffedca73297c6753567e3e5f9dcfc90724366a5c9d5394c3234ba157e2f618970a4e9d2eb2c54c5508319e4891cc7a172ef292c813a205cf8a86a48359850e8d2dcd4de28939e110db821b7c1528ce32f149231ce0439075000faa9e23c82203df4108378229c1685ba865f0bb90a5d649bd675cc851681c2dd592fa83dbabbc07ce5d01160564134b5cf3f81de93c0002db30298f3331bd6fb1503e2ea6c95de023b13fe5175a2d650f3bc295af7c1727a8e09591f073e351928de895462247cfabaaeab7e8511d7aeaf08a265bb4472dd40efedc487b255a65b9b99f7810e7e23d84b827549500a43150d0901c93d72efb9ba4dcdf7164619b763fe2094befb0dfec839608c8bca285fce793652ec166e5b561aea67555f7881f5ea2126229803a05d5c502646d80d740338c51924722b998e1960bec11b2400028d538bc733cc999dc9f33b5dd90182c5c3b1b173726a01d599369826be0e41dc12342b6b3916c5c85612aa3eaaa80990775faacb7246c0a42e8cf672e058aecc387923750a3ff26f2345e11d86a7999f6e37b11402f31976ae8a8969d96ce76a9d0debbe7bc3a8bb5210f380477ef92b2202a3f6540d608307515b9d434ac934a5c418ee9b3d8918d87ee7ab58adb3fb3f933d9572f43ffad1ce3607dfa3f9b382576d3e2d71fc6f80e257bc671eaf204677a5cf14d37246cf59618679d4055f7241317bc1c6afa577255532ec33ae3e6641b208574aa10b47f9c9767ad01b54934c152fd8d5a92275ecb51471079350a33f779772fca77a44404d23e195e3fef739e27124850472c468575a17d70d8d604df53c22c104bac78df31d2466e098ef40a863e69cfd0a9599efce8927a825dd0152437209b6df58604cd4ce95f1143ce0ad15ab20c987952dfac0e499ca1bde7e5bea721267167abdd58f4b0ad040197ce138c5de402548ab43901f68df47dbcbb5bc72faaec3be713cf703f64345765d06b5bb554a94c7b2c3ee175f19ee3430a1852ff70191bb80a1a3303dd81e4e80948f3e8892121874950bbfb71690ad39d7c14f0027e9395fff2802d5e664b976e57a45ef0a217f743c84eed025fb8d6a70c90b3e2c9c299e4796aa9cf73489e708e9e0ed2612d59b168b8e59ff7593036e71723711aa7e03143668e16b93d89b5ed51cf7c783d12d8716af35f7504f41534914d2a69ef902a172fe2192eadac4a6f0efdafc38468b76ba7b003f752c3631bd081dceeb1687de1175bac9a60fbdafe49196cc4bdfe97cd74fa76e36f2a2b280025d74b0292baf92c0a1a419cdeeee23336f9b84408740bca2233a1c9440107e27a50e89fac4b9236366c9ba1246ceeedecb3ed8b589e34ca77b047276fdd72d723eb763ace885c401a4ebd0371bb9883a892ac87d15e6acba636612c45467e0720ea473d96f5172209d22269833e86da55fc4adea8ba7dbb126ae02a82c06422e58806cfc277c6ee19607ae4df7b229e8f63d96b61f4c973424c31b6c6c03af72d5ca878ecc9ea1404e6fef3ebd3847ffff6860334f14fa878d8b939ad236a172185a4d62901b3fd15196c168f209b78b957fdb769ebbe45ee6e9c40c7dfccc72cf239509dec3a09e3460927f1b786c17138a6a43a000dc287724fe76a80b7754a69b55c1040e913b9c7b68771fbae4bfb3f985c34daa91b107d6e4baf85bcc720d33b97f44efed789b8ca6320f1f256ef11c4ddea8afadbefb3d6167b0ae0e720a734656a0bd2bbcd056d513c7990855475b87e8d82956efc9ed76a8da857e1424346d3d7463b3b67ba2c440bf99318a8054885f8ddc1497421f613b73169b7255a5b4c83aba04f78e0b9fb892e0d598cd251ce1c9d8feec301b3f2694910b4fb5c082f0b3455f85ffb3a40976d52237df11d9225fd02a9b82240d8672ed0372895d839f8b76ca4f5b121f8a8ea8c8fb6f0f3a702253b59691459c96e52f22279aaba6acf9cc1ae3d9a70641a188cb0ba416c8f656ee47081f50a10bf8f8f63c6527ea3e2e667dd296c60d64f0ef2f928498e3d1f9a8625705b715d92653fc371b0c3987df61b386b2c53726cd2fe79f198fdd55e38250b731c939093b507e0e729dd86e1504ea63882c601f9adbf28feadb4909307e8ca0012f8f089beee7722b8a2525fac6201c7de5608477dedefe6312c25e353ae638251398cb159757723e9d15e729f8a6dabdb9cff4f765dda5194ab5f9ef2635a4d53d2f2ef49dff725005dffb2c1a0d8698b3baf85d6331343283ffc514f53f1134780f9898155e23ac0cf22001c6cf6ea714cb961b01982a3a694f6a4ea167d2cf55fb55c2c301435273d8f16686a4c6b874c581e3a5dfd39c414d005c858ada112eac7e4d390c72959d97d8c038483f5f89092b55034f4e3f636757442452d5c1b483e91345af725e8ae7d331acf1def6063560186e8ab7c74df84033de0f6e73fe3fdd27e300727b1bf7241b83dc1ae5bc7080cade2fe85d15c4e154f9575b115e1474c9e2431158f9394a949e37d151e69e23bd2698f110e95186d8292a5cd6fd85eb31363a72035b776a31ebaeb63c079b63232e90266c5c738d78aee342161684a6afbc7946ef042ab5893440ed653fca13c6e0b5791383199a71afc2ef7ba67fd274754972d533effc30d5786479adb521ffa5ceab5565d86a320aabd3002bd99065bdd2375c77ad705e819d1944ede10ee2854d320154e39971842e4b65783830f1cec5720fde5813a42120324509d8219a498e5c708f59a820551c30685be903938ac3727d2451df9a28e768324ef2e4ca0d7bd5e48ac31ab3237efc98a54bee123c5449327691d9148a5c99e56520bca32d80b1e50cec99657e29d7325cfe4172695411b53cee3cd320cbb446dea3df185fdcf626644321998cf9965637236c0104c15a6bfa50a329bdd92523bf1f7b11f4664dd75a32dad02948e61331152dbd942e7225f650d1028fb09c1b4f68e9e866670aef52f90f303a01969bc600161dff782f2b191ad53d59483fd56bb90fbe360c11c65d42f20227f506d94547383161787276a7dbfd5aaced28fcfae0b4ed6b921332b78720bcbe1cc2f94025781baecf5e15c41fb9efd1713b950f8b304628902ae2e608c33445cb307636a19d6479f3729695a434cb23d29231a72bd8afd3ecef48e54dacb71ef6dcfcd8f44e8757016397fb14395ce831a0b0d6534754753b6141e3e7d4e254d0a46d8064e7dec47b720ac73cca887fb6cc66244b7fce61750026c19cba71d954da8fc075265233a8265e7a854ec785f550781370cec1aaf268e13f9a1d71cbb2f6d05ef161b5b08a72d06d50b4e6dbc66c9981d90ecd7fa0ae3ecd81631b1533d840432c765902e733b84ac1ae3a32d0213ed3bd891e010cc0f09fe641aa0dd23e83f31fec268930729644527ef354640a0122673cd29ba9678122ef6558b226c873dacf6840c02a72c1428f3383bed3a61efb57e67f22933a385ba38c911b2bc77a1033151a9c1172bc5359ecceebfb33665ceafd6f185d085dddfdd42545e7c60b6e4d0ed8eb0572c5e4574076ccdd82848e8655a1a284ecf9b86100373bd8f9a2d9fdfe9cd79850242f46d607ed38bd04b2f6457055a896d16ec95902997a025eba582148b61a72da4281ccb9a72dc6777219e6681ad618a2434c099aacdce50b6741f46ad697725572fdab791944b523e54cfd7e29331c3ea560dc77b6450fd3d4d6872342db3cd5c21dbff9411bda424d264d80960dc2dc72849a0ba4d7a0a5f338d17e38ec722de2fd4a02996b64add3665080edbaef88a4a4e1bf6b1b2606e950da8354d40a011e772fa3a3dcd7ac8d6f3db368e6928910921e23e751cb3a6e0df5c2c6d63ac0108dcf7ff0a2877e0157c3559d673005e1ea7bb3d30be26ff7b4028134be729dadb46bdd128bc68e5e05722be2528a8f2c432b798a63d92d91e07e326e2372ad68cef10b5ba10ac0d7fd63f8ad707d0dd2457be9e38e57c2366e8db0a683728d06f285d80c046224da71be4c9f1b5b3006acc424be1435736525a4c73c5b7227e58f8f4e954e5d0c7f87ff794d5dc053c3edc8ac9b4bdbeb3fb66f7fea70059dbf63c834e09f8b340c59873e0a8fae581c86f387f320b9e67f5dce08296172734c331695791d77b4f97c60c41e8842b20da1fc882b9a8b341d4016e45b793e056e9eb56af773980f56adaccf6af4e4ad1c6ebf801b8723f71c25264b325372d6fc97b2cd4d1cc8520c476d5348ef9efcdcc1d6d2e7838208c8acdfbc0ecb60a2bf3a1c592f91b8732322bae7345c66ab3f6fcd89453aa2c9e3e2d006586472beaeacc3e6fe3a29949b98752a5333ce0f3f6122bf2154cdb57c6f47d3dfc972746ec45fc8619a82d3c540eeec95766b9a4681b148ef3f1ebcf20baa11909f72e159a6c4f4443b5d1f8afc2f229f05bafa2df6329e2bd807e8e6d6754fdbf20256e446e3e19d1dd3dbac853a434893c0a6d695468553510991bdbb02660b53722b8d7fd4859be761cddd382726efebfc9d783d02c41ffa89b27ad99d34a6627203a5faf195b80074141bb0c00b7acb53ec7303013b4817ec798c83611e26eb1fc331f081e96176d69b7eb7679bb810d5c3710cfc982ded6d684802322eb975722a349e8acc4dec4c94c58b67238d7c8093519178ec78dd17ef175dd035b62d72394fd8191ad714324dc466230192c327f070248e054ec9599833b8819f10346d9322c1ff3c62761f58cd28fca65e18b10ef8e20d9a589351b14b3af1d526207238c2a10f7970b0910de2a6631694e1a2d68ac0269fc5fbc4b85c1d63f201b672bb018ab37a7e48ff593fab1d5d9f7ebdfc18262ae9fe770e3091f699bbf89d7252bcade98534188c96ce2dbd8f54fccef887c5f168ed9c500cc940e05da8bc728f04229919df9cc33514279f14cc942915fab1822b391322eb837a723c7502724d18b896b894d66694dfc576caa963b73d904051826561fd106a3258a03a1772cd6cfe7a7bd7b6df0dd8466840f4c47c68972a98428e8629844281d71a67ab72d661ce234bc262010d44f3b31285a6e48bc729fc935ee3a026652b7faf33636e0c73c9a774edb81ca101b64becd30fa6154bdb714d1042b78dcc3e7868cc8864fd832c02a2997d98e8f5948759752bc925659e2b6723b28e148d5ead84609619ef4dd915cff9f61cb3bfbc82e691bbf4d517709d8485f0e9b649f9731a6fba2341367f4942dd7a8f297cb74e57690af9ca308daf78b71d3463083acf3d9be67261818b6767c3267946f1549ba2a3de00f38ff954a38f9061554c306f47ddc442d597abbfc2bcd6953b582d27817c261855281df58cfb45426e1261448acba4721b0fc8bfee5ec575daeecc0e1812ae0623cbfba74a760256607f85f55de3e8536dee4224a219284c101cf3dae5a715d9260a9a28660124f951945635c5cbda4179ede6d8238df2932373e032754338855bf6a0432d3f849e01e91af579b754722c80494ceaac96ffad5fc9a7985a0acb339a3bca81360fd79e9221fdbc86ca51ffa8c1a2580ed95ec13a46d920869e0fad0b0bc3e051975363a07fa393d1d9729213e14bf9d8be384f1675e49d07bad25d80bb02d50d28e1c71cd6ebfb87e05e2dd02aea43afce53622d6e80b419c9f16420cbd3c64353f420b9abb68b4371722e08f18d980abbd29759c0c3d34fb5a0e99565dbb3f1df07cf3cd434915cab3b461295c2874b4d750dd35024a35518eb3188cbdc903bc7c7004b6cc0c2356d72ce012adcb4c9f7deb7781e30f195878d8b7b0aa643d6dc5f18a1cce519399f00499d5487bd1749ebd8318c04aa92bdf6ea02a9d04f2f6969a6336d073de69272065ccf07e6f4c129cebf5614dd8a331e7abae64b180f12757344141e0c139049e1eb6bdb1215755c17b0a0a918a858bce6fc6d1b2c3daba60d99284b9384b6297afedcb06bf744ecc6311e0ae3711d76f9e7b34759030eb2e67cef5fb3df7572cc60e43ff46890e014128c2b0e6012ce81c1773ac4499e470c6263262bddf55c00c82ba7e0dfecfe700b86d305ca5eb47077d1d44cee5ad2a707acc48f06817248009c013718dc80dcc291fbfa9969742dec938a094c871cb1be9e06bb34ee1f75ae9e5e76686961d32275097b93a5dffa0577a48af66b0c10d781b89679ce2f3864a8e73b4b0a76341521a37c754c06574323a69d69d8f6ce5b7ecf3cad4842661f93ae4c6920683101f192e70a3c2b582c256d3a8dde0327bb52bd1dfa9972495fa0240f69c0402ab08c11ed8d32b0e9b262fb00b55376e75547d97290a6722d84763ec13711dfa300f687846c93c69e8c825b60a803d2570f702430746a1a62a384a87c416d7a8887299c2244f1c93345228cffdb1b2c356d5fe782e5672da5543b6f196b902bc15adc757f4a230c4f9a1834a12f04459ba8f069d830cc72a028d32cba51ab239e1e3820cbb305da32fa73f0ae2427203d9dd28c5d623d729575f77a84d0030acbc235cd05214d484aec7346a7b5887883d84456c9b25d72ce2592bb242602d11d9eef1adb0093a7c962b7c2513e6031c8feda9e08b2cb00d9ef7c7dd8052a7af649c51b6a012002d19f287620263546cb66c7d85e116f35617fe89b190507ba9dc7938b69665c17cbf0ef4c2982e8b5be14c6f59ff5117236f97b8d32afadff60fc2153cdcfa1a04b703f8397ca75b18a89db4c8b886872ec95ab3a207b9a871eed5cc7fb5793fbceafd5a6748822f812d38d4eacf184727ff10cfaf2fd7a484cb51d2cb79fc907b35c1c416ee682e133ee7a14095bfb72782cac62ae958fffe701ca41adc8d76628118a334c66cf8e219c7c9677fac749a12b4e31a3fd92237df76e7cd6ba2f468dcff8f0e872357dd4ac84e2f409394af72b54bc4bcb710e2f3e8b8194b5aa81c9182404b2981ae6d6a64e4480816d72e6f5844b4a270692cc2f5d941a34778c929c5b7649137ac352d0fbb254182b72a878b060d77beb5326ee6de9ed839ba43635abbb936dbcce38e8c3cb0e38b86badc427aa0a3018e7a27c0652f6e86515a1cb4a8b41574ee87dcf9fbff22a4f1b6b8f315927584907dbf86135c89d880330f555066e3558310719d536bf07dc72001b3726418d0a59aa00194fe7773556b45ccc902fc0e9db998a0cab241f3d37520b25da276d25f8226db1b4521f07e75260bda06de4f323cfcf89c32e45224a507c1d84ae4fd932142684245f4afd73f846bb49b38421438f5750d52602bb7291b4afd28fede545ac1008a9430a8f8ed56010d08f16a75a22b92f028c5fe772747dad26dbeea65c1de621bf91495abba969aa417254fb3a5a67e38ad33ac72905ba411aa0cfc0a13e05eb6c55026982683a85fd072aff12a066d8aa1379bd3c94d402f7aea7d297982b5d909c93fff8b81a444c7d9544a34507d4604513bc0881ce28dd22fb1d0fb3dc64d69eb35299421e18eaa608f483937b5dae8b39ca72610299a1f47a666e5d4a98eecd27fca92c3af3c42a4ca9b55bd4868a1ec15702e040131354f8836de05e7eda89bc645cfac8d486c415f2d1bfda0391c95401726aa8f5e8c14702d844056371cc8db23eb6cf20420553a4b2cc8359cdd4b6a024bd5364d73f3fdf7312cd12698d7d297a6fa1fa20d68d5da44affa713d563da7202aa368483676b2401dcb03365a1dac61bd432a37273b255ea551e5fe4428c723000c31f0f02f21472f25c79020e06246ba11082e9d02042469a4f50ab0e74221886bd50bf22c8bacf546160cae6313e114587faf4f88a7a110e1f6c89091772a35e53a33ca02328b514206e40c958639ebf67af392d8599e651601e8bbc387073d2f44eb47df597cef3ca364fa708c9edd62d68df84e5e33fd46ca098148a7220e6f13ead3496d73c7947bf3368986fd31bf1877741803135f556c3a0404d11eb27086b4d35dc09111d4d509e2fe127a4f21835b1f679c1a91abdbff39e2a72a6f246378fc0da3904c79d64e560a8caf9080822b923c3de64470af53f38a043d48aac286b3d802e52a41c1c2f5722cca2dd3307e52484e5acc9ef81d6610059308a1c558784aeb013073389186ad9d250ad1fa2034031644ebf64769252717251c47ffbe7051f05feb83d8ad805d785b3e9e054fd2e55c840e495c799e00f728cb3299833625b4935a61650a3f3eadbbce85d7e00c832c413fec9e923bd7a72952cf89f7373eb8f6ecabeba2a6660222d3804b1da30befa18e5fdbbb88b395150f7445e9868f018b90d82cfd2e3d9111ad260bdf1f705ab5fce17fd4985f73f66161a52fa7dad40f1b83dd744deb561055ed955e955764e35b41a6460617323c9aa9b542c70c2139933252a880a7ee49948ce5dd02b83941d62b69c39c99572c60533a009a84c6c264728dcccb0fe1e2850f5fefa281439b2a2d1250d6867726af60bfa32c80ef79a94a425b66314e36a949a7551e249440b0b46d79438752cbffa714617efa671040707fd254aabfa67e58134d7dae9fbb3b2905cd7e5dc433fdb5e1aab0c7210b6db8dbd2cac524b450c91a5d8062b333ba788c674fc9472817b572fee30b931d1f8dd1e49d01c04a573a5c2e64ec1b1e48d796c0a23c772377a5ab3a2442e39b93a935b28d3e5e0043b6662689e918aff3d145fe30df809962a522c783a4dbf5d7f430d12cdf09efcd0145b6eb87d36857c1824145be90a7b0e5bfbae1db07cc07dd4978de3283ff279f709f3331d70698401f1c4fc6265de67925c857040e5df823e0b5069ab46e6f53a66702696e0255d85a6aec4c7724e740e9f3fb2a40c515ce9ad614405b19bcff4981e30017c5609c6c98480b850a446960fecf0d392608fc6e837beb13d4db00e2fe336bc07457c31e1c1e5c24da1fe8ffbc33a4dbca036e71f0a0a3694493819f93cbaa36844fab34e9d19dc7255b12008d3288cd059cf2214a6b527534e7b4325252ac9606b3e83e5691f7f3bbe8b3e07e88c08972296ced75da328e79cc0c8d819d6248cdad934968d73194363c668b71f89521e46397b4ad4b7f48a962d5cbf398ebfcb40e662474eb600174f2d54ed33427f1928b38f16e7761680182ab6748837099eaaf861154b31307252c9249a6aa1bb437c26e375e44916496d28e9990422615b547f3d5a840a1c2318554d34d0a6f35cfe0f12aeab1bd73c4a7f920d8881b6e0410fe8be4584ac727fb3305a0a136dd249e8940fe9f455c885d7dff0dc3dcc9c56246f2e26cffa5d98c4a405e772c2fbf6a8d88275b8fbc44e71839ee543b5c319c229875e9e1f3601730bd81e0a799c19c7e80bba0bacc52a94bd6cd866432a90326d90bc011672b5572f15e89e2aa507f49b29295819a695399f09ca0359c5ceab2a8178ae39723b8477300cc3204f95d4d2c000d47cabaac9d934717d06ac3d95bfa93a4c9e72aac92228c28218fa7afea94d97f3bd41238be2f0950149b433ce26a0f0432a72b400e78ffa1f941d75d8f92e51d4bea43c9942c5f72d286cb01755653398242c7d88626d0037c2238e32697d0c38ad294569cb68bb8b1aaef22fc31a8c9f82721e05a0a4ed5b90ec4695efb95e8ee245106813ebc016bd4bbad6651fc0602272f0b9c71cb846c026a95394932746f3d2a9172fee719f2942bc7a77b19760da720ed52a3d53c68335a4d0d6472fc7f1329728c627b62b35776143edbef173e172428dfaba52938d2c280f2ec36c0d3c92dc4c05b2457b76b8f6f10b935c1bc772e212f54e3a943fbc5d7a67ae12e4cc3e451b07efc6dc7c3c157d2786cc3f617298b8e340074f6d2912e1c18fa9704d310710062c062d751f794e478d78aab5048a103b42d686fb3dcb8965664b9dcc34acae5d34376d2a13af064217c461a16820b5551aaa74d790fa0a619c763ff1fd9faace1cd2f1d4a0a9255a0999d0ec7259cac8d842cbd5ab15a63b869e6c873a3f4382f0b4b2ceb922c1d0c106719472fb4e7aebb2c6b888021f37f09e2de33a3ada942243c0fe7684759306a8c11058e0a0f27120ffe9ad465ad696effa4fb3293c92afcb7f878a10ee1ca8ce19e355e745d5d0a5f2bd6d0eac8880cd6a2b5720d82c184851af73ceee34391177817265219ca013228450549d273cc2020a8670eee8ab477a65eac0b8b1dedc8ec87222ab887803ce3e9f5bae1a4b59fae5d53434a53af3ffa00ba09f5883a77ce138d7bb7d413d4a7348f95b883ceaec26b3524fc2be30bef74f51e2d9cdc3e4822ac9cdd220f0bb2612c02ce57ffdb196d498a13e53625d7ef0a9a6c9094f2c2d675fcc4606d72ded082216402ba3a40d6428c6f254e1b121a7cdbf7fc9d6ff66729937f75e110ea4ca06a9cfab0d1182c348ee6736aef6b3c37f48e7ff2a1c3b5ace1cf46825efcf62f3e5c940c56ce41e017565248aef3068657b237366546d72f34f814df3fcf253d39d192f81ac45a918effc00904c6ba0b8e132260aed2607f19f1603dfd3ae1a5e5d7c27f64c14c5ad6b40f11e7fad5881e27e00504bad2292f6055acca691280861d47b808322fdad23be6e8d9b0e7d8184ee760932d872406616bfab24b7b43d5299b5dba218288bc7341cae1a5ea813dd5405b8387d0424a1dc47cea76e0a0b45ef8e1f9b102d34b546445395c38077a673b7fcbe7a4d7055f614c2a7796876ac618d46efea983ee370de83a9a4cab6ff2685e35a7459718134da3b7f9e0958e9d2cdee41863a44f1b8fad02fb0c8710e12e2314cf72b5dd954037cfdf75d6272ffdbdab614eaf2a2338c27a7f27367ad05b039f3dd72b4f4027defbb935ea0d4bb1fd088e3e5ab9ca33d3f8d97e4b75b6d13b204bf4f8255648a8c43d60f0902a82e25e87465f7f53e98acda943206b526207c560e723d46a0cbd363cdc32d585c7f9c34dcbf16744d668dc1863d11c8e48a6153e672fd9008e7407d592678752301747349e9cf91696d02b497cb1b80590353833f72a0b55251217bb09029e901f0ecd9a0c4bad913a77793362b62f5a5a9610ebf58c53a916bde854b6fb9e73c3e375b2c561a567e587eac449c299cca2b065f8e72635496a93f0ee8ebb2654e5ad04c656accf13b5820a7142e682b31da49f56f196e683eeb5b130bcf4517102476f9dcee7024d8563108f96f8b62102f4f686257500fe71388986ef918ed8b95a62bfe8219d3bd325fb138ba0a426672dfc1f1728c30b73fbb80a7d1706e04746b39883991c89b160ddd7c96759ea1da5111b739216ba98f8daa11f2f2ad3c56b0806def181d9a72b8bbf78cc90b2c30d6fda51efdda7625d219a60b23b2e6d789457701b2a3dac698c561b43c6c3df8a9dca63522a47058ac296f8ca0da800755f80d353d6c394bc421e81f3ec046113dc93d221e169e8f9da05dd47d2fcf88028f2a1fe475951a0df920dc9040c0fbdede1772ab13a392e6066d176aafc6187ffa28fd658a62a85a387dfc3817539fe40a160a72240ec6c0624a98c8d649f880c730664f6321a514c100b191f692615181077226c84615150e57d78d6e0117a8afac3b04663a8e1c3029eaa23a7d7310a5ab46367c025be6a502b6ca0bf0a2569d78862db5da2b50d8b850d57a97d51ec886724642799a6f064941044d7b19685fdf276ea4a64ac40e8be3940eea055bb93f72f3b436fb3727667cdc2e4cf6052a9ba9831ede003e58092ac7b5471d012402729a4b496a877603f25abec8f6716b6c61ef5fd01faa5ae1b6dc34cbda3c42a5723d8312119662247d7ba543a78e39e6a892ddbb24def7505cf7388eaf8d57ea2107be5fcd09800d3fa41cf2eb074e50adbb9bf2c9986b690efcee82368eb7f16e208d4bc1e98a9b17e229d016bcf71cdd722db6af76fd49ee6532066720bbad53921d9e034c27a2554312211dfaa2c0d4593b496584171733266e4a0a03888839804041933a7693380e33ebae255f8486360e049b0ff59321654dc561f12c5e4818b4d699b9c200aec1dd22bbb79e94eedd605d7fbb375728c4a53da45a93f772d0622880a26f61333be4f5541cb274cb1e7fae1330b475307678784a7319a717fb0d3ac8d78296a5f30b5083180284bab8915ed3ecfc9f4cbb97c24dca9f90093a674090b0ac7154a0df5782b0ecc4264b134047e7534467f627c20daeeb3f72f0874ed0aa167d884d0fff8c4b8a92e401eb5205035fd9f63bac91168b9c4a720e49523966e90dbb67ba77eae5c738e5052c5ff7c9f4b864e37ae449e0d23f729035cdf85c54ad8f0bcb587de48610cc256537e9c8ca51ce4f2fac8c695d3b277fa3b3d4f3380055ba3cab8f27e44dbf76109e4710058c3d0ebcce0f121c8d724ffa0757e067459d9b800973445dcbc1cadc77fc00f65cc60c22d1583fd9a951c97d87593660c0034570b13cc2d5f3e9a0c2c07be96170634e7261e4aacc8645ace63aee6398eed93e314bd99d39a4423f3cf74bfe8d7e460f01ec60bf57c15842ded1686971eae20b781a2a451624ba40d5ff1c9dca04e115ff062604f54a2607c054350d16805243dd751b2d2aea419261834446c647f1570a4c9c7288e90a6a9238f4b84815bc13d77a6e9c1f5f47b4b55293a75f4b7d9b93b4d1a65fca15e60672cf8411215d841cbecf2e13fcfff47705e1ded7b50cef3a0043402de172c7df8db986a9cdd1f74ec11b173f52c1c0330d1dd735a10472a943dc0b8d1d7238ce6c91f903101fd3113b8d908e4c39fb17afc3b89c64ba819a51b37e52734dd9437efee91a3306dce337acc083929e7fa145815284bc52d83c529b80c75d422c58c1c04ebdd2ace0bafd46b7d14243d856922f5af748dcd545f7caf69e2b7267275f4f0a15f13c590d4f0ef4b2ec86ba453af9c88793b06576c0dccb3ebc724e0a3e15e453d0c082b3eb79b404644720f6f1e84805d5dfd1196755a363d2729eb2de3e6c51cdba4e96018fc319e189823809921d8a7adedb6053cdcbe45872d02da2eaa5121c24c967cad96393e578105e5abae0b6e0bf74e5d874ef59ca07a5692fe94909d2b1a1b2e420a264fc2839460998382a0ab14c07153d345eae52731b89cda40d957edc09d25db1a50b0829db80b96c5d82bcdcd55d9d7fae1026b61567190a2acfd0e3a378fb84f64c211f46ff5e6270498f147fef41b359d00964a2ff6aa9f503d2fd790806ef1ddff722b9d23bf9fbc06f091259bbd77d90626bb8ede50176e311fd100328a102fe914dbac82d3ed65ce952d8052d5421de6de9c8d99428b130594e450396e3377397499a42722c326074068d1dcded3f81720d5957b5f5f58676cf46ae4c4e9b739521cce527585780b3e08a15332e96b07247b1d23db7690074b43f65ca6fdec74d24723241f30ae357b6962432f920e472a1e076c41e94ce39986d9b62485faee985ab5c6c49d701aabdc258144bd2f272afe63e00164525d6e468b0b31f483a65785c9bb842f4e7f3cb41788af2ac61728fd7d323ee9c1260017754dddf0c94d55b74be88d7b6ac31f5801c56052a21726631a34cdecfe87ec0060b99c7a6de267709c4ab0096b7d0e2702a5d5ced9b69647694955506315546299ab821570141de7f0ebbad74b1c1a262f34d859d813a4b3bb98b600c7aa9c1e35812e0b9089c290480b2dc988101eb5197feb260a25fe326890ed06a7e345ee098b497b61723a848f1cde5eda0d86d7899266e6a7f16080c67a02e81f985599dcfc8771c1cefdc66defd9b325752553d2d2bca188272fe173ad4fd01a6abd99ded21f30610ee787d87d58c6e19e44216b89d6eda3172cb0c24c7420734d4a2558688855a9dafa4ebf6b233d6bf27241e869e0ffc583941d448108e228dab9c2b092331ebe1632ca32f21fadf96fe60a34b0258e217727aaa0adff181f52f93fd96634f810af666b305aa1b39672950fce8014a4fce726c5b7503551d8cccef5ca5790a0eead568ca70ad51dd72afdfdbc6f094ea280ae77c12fda97d52269d73838a7107963c42767d93aaa1a6c6bba3fea6a80336724ad7bdf10abaaf70809d704fd9b4c8362778ba2b422de34933c08c1b084f0a721e72892e2303a8dc2273554c281c7930b32cfbd19641c1f807f591d952c8184af80e916d1eb8a6bdad49ae8c4976e4df9431dca4d4b96519d99e2e38bc4daf7211fd2ff07fcfac81fc5311d5bf024e901449c8fcc54d98e1099232aad276543ea23f7f071874d499ef5b7c7c58502fe4cf7068dc4d91e0abe4604af65113cf39bbf05bb0cd1fdde1ae4834947f1185ab3822f669164b7226b96d7597a474ee725b17fb2a1bbde0293744eb8d5a8cb54c4af76336bc2a0aca3c30588c2a2fcc729c63419456f8753d7afeba4d03046085f52f32cc82440b4b03552c9ab013e63435dcb7da8636f80fe046ccbef091ff51a3a0f49734870f38cf2107b1711746720139109cb051455463c199319253f22a78a8dd11b9d1c8f9446176f63977fe0de290a2a0e2cc88ee36c67a931aa4fe97dbe561ee158f00fab0a8209be2d9fa7262d12d34ba25e778300bfa924ad0c7354559cde984b959541508a1f46f89c453bd4f51939ef2551e911f04ee137bea83a24bbebcf9e4ff22677179fb4129f572114f940f29a1d9b407bf0dcb657d8f57ef3829760f371f6a0d879d1effc92400e50daba8c994b65c6504c4f3b3a6d3815a0936cce32bb2849f85db1b33e42c637fe3fccfba5427d9f95309578365bf1cd208218ed307c237eca97e61a1d43672c423038971e7984b0f2f361eed7b2f28ff11456f78f4b21be674365779141672464085cf7d3fa4b734740b42b88409d7608b968af9e6e3c13a13143d0854781f6cde79af573745bfd29d5f1b8f32f85cd3e788c5c34cc001a6d8171b274ed05dd07bf174627ced6b14611d9193f092d6a822a2018ae6159190863c23e1b7b7725f3c7832c7db6908632d054cbc1621a2e95022bfe57af3f87088b31cf8c01672f731b4d27083809cd3eb9a176ab5828a81b499312ce3292e9057e8b8d20da35c8645b346ee01d021e556c01decbe7f52b2d9767abc0b958c87c1fedd4d80ec5f6eae9ee2ca12926ee2c4a3036c9b5567bbf7fb3adc01bdcbf174df79d1f4f572eba012e4bee87eac3a7f422f00628448e9c2c3bf118dfbc91378f29ee7539772d359472b419e96eb07731e92ed1177a0b5c291d784874e3a50900cedb96ee672167b551d543bfb3211ce135a2a23bf734234e1bcd0757b9d35d491e5acd6dc725ef4b4e39f1c03ec0e043b2dc9638b104cef227f25142b3af6d4f50af0188a72b57445ca2d2cddb27209b236bd1a93ef5a254d71d937ad2b1b34626c37b10c72427b6f9dc984c4aa8d15ec6cef921195fc6e4dac0d207809106313983c2db010788917d3367fe26a230ea95b7fe516dba0d1da576d094546fe01abdd7f2a3372340e31f6c5dc2fd52b1ca9f6290d620e08d0635d8fde27a749bc0361be29af7202418c384cd87d69aefc065e34bef03f56765f09bc7b2ef8393955f5ad155972a60f65f9b61630b5e036873622f3fad6e12c6feb246f5692eac8bb9dd6880d72b05c8fa0cdc120ac8fb26dc132b87fc68a8b1b31d31845e6056ce1b32bfa914e9ec1a60ea7675315efd93ef29165a255d7d0fae0018a4b87afbdb9057732852f6abaa2bbc43d1e022bf22f3fb644be616ab65d258f2b262f3c2a32330d511e72265ab02200a6cb1c7a6a9e7bce94e70f6150af6933214dea5506a46d64666d18bdb6c11e116a7d19ed1f37c5431f9aec61799087af8e7f5f584cc94f6d2eee72cb72f7b9515e3d02330bfcfbfef116c2452cdec2fdd5c5b5f29d413f8180ce72fd4c2f7ed4260bb355381a05797266bbfd82f3fcbefe6edb4f1d622e2b6e2629ef66549567ce19c41bd750bbe0bed8a4b0674bf9e96f65de8852785d0d26ad728eed000c97754f03837709c9d926723d06ae9529b9d6fc0c3539e3ca0ed7ef72a77090770d43fb650e288ba1fde1a77620bef1ecc0b4efeb0649e1c02387dd35c54f96d52839c7d5830f06d055773598f985137adc69a92ee602c168470ade72ef44fe50e6ed5a83f13bc46ab35b797f5a1279b45f7c1d8db3d6637b8f03f172fd58933ee5f90491b837862ab59110e3369ab66538840c1b342da2aeaa8cd73cb99a1e0fd2f964ddc4176cb24a890dc14b4d84554874698e36f76c6419057900bf12d4706dca3bf4ed339f04337eef9d9ff2c0245ef338c8a516200006f09d72d9a25162a3e261284f5a51d74518b189791fdb6bca77edf91f8bc5037864fd724b5b35be252318782a2ffda5195ea29abb6ae513957bfa1a0c876dc52be13e24a3ebab0a0457522555334f6b3e71821ed15c8c8980b254e02508adb7f415b129d71891c0fceae0b7d05f4ded435b7df120a4e1203e7ed247e4b88c813c80070b68215787fa9e97d1a49725350666895270d85bd2d6ea0ff262ff969a696ba35c41c206ddaab4a3e21f96e91eff12d14b2d611e04ef6542b32b60a0da0e864172df406b369fa62e9969f93179688f639666ed87c275a120dd76f44b467619b4728b95018c69e85baa3b3d4c723611b9c33ad8972e6aee0845cbc6996a14248b6529e00453ecd776a5d25aa4c26778a655c26abe3149b37e5d6491421b6eff21724ea503c523d998a8134540699d8f8f4037c706702e660c1b51c829b7e2dc476b740b964c344751df0e19654ca04c050b8fa4769f3688fcb2adbc32fc6e38187268bf857138a0040010dc1f124f19dbd9350beae332c70e2733f063a726c48d658bb72eda171a32f721314d80eaef3e19b9e050a1542fe7f5fdaef0d72bd77f588b80dd6dd90ed07d8a04126526533a3de9b88b168115d76ed23e0046c77cda7230a9378fd7f7337c4cc04fa017312431652aff73c8a3da1f69f46af6f2b702728f4549b5d48697eb7c6a19ed9e84ddeefc13a0755bf352954a95f2517726ea7244c3e1a2cbbb7635b70c31a5984eacd2a792c97c555bb5042a2db7a21829870fd6b02586e564f5d90f87bf41bf8e5de72b48e6b3caf6b93e0ffde58072c8ed726b4f246f0c539068d39111dfd2418bc52783a88fb628b102fde6bab26da62829c93a2565f84dfd2ea81e6682da126ba481f8be0368bf9ba93a7dad28cca3e572fa564c860f543e91250ea13ac5957d94b69631db0dd8214ccf75015a9d129a72b2448e783c3919023b36d24043b8738e21ae1f97fb8cb53fff573935bb3ebf720c50c34567ee54511ca749c0a4b033782e0db284583aac2c12f23bb6e9e12472ea4c49ab6f424bc307a44d40648abec942839a10be3cfa34b3ce22c95375e472cf9da0644d809c72bf70901bccdb3f7c827399ae6506f44e660403fabcc1b42695f329fe9d25c6802356848cce51be581c0e83f0dabccdb355c7f3dc634f6e2b4fe79580c0e5f5e1ce47b8d5cfe6e5288a1e60ebe521d71b62072f7f14e47e5b7fad5f505ca667f008d8dec2edd4e3325ca097d28872629f02a9632d188676471a0ef01b6446e70201ceb2573f0321589542efa41da523a81e49401b46d68872686df43dfb951df6688313918a89c8fceeecd47bd2c578b5d48d88a031ac68072307d9c4acef770435e925e7b415ca3cb94711a38cebaaf849c37f15a6328e72e50f9e088ec35ce7842bd70caf8e7c5b45d5b830a680be5b7af76cabc7ca1a72c54745c9eef8436a2e1ec2e4b0572ad85b877a9650606e8f8cd8fd4b2bc16f72381a0a1ea9f08886db362e1ff727854e9d52e071f744877fb0a2ff7e465c717237a410fe781e212ebf77d932b1a8fd2a5fa884f1b65fe19a8c8ee57d1d0e1872f5e2f65cbb4f03a89544ae43f4d0636e888ed33add968d6d466d16b5227ccd727214e541ba3d1491a12fb72b36f6374a14370cd34a742ff317392a21d1a3903d5d4c9336905b059c886debfea586055a7358ca7651f09355fcd51ca89de01772b172624b1bd7e9fdc74ad189c1cd511a39eb1143c2e2992b6a0efb1cedb7307273e80ac961952a493e7faff3fff00a63e0ffa611ab962babf6ed23fdcb45d837691487a6772f39bb7e86c22e97a8ad8430abd6fb9f18bff6565e3cfea9ebcb722ff389052879d706d1fa2f847dd33c628a381cc87ecf03429cc9eda3e2c7a272ba4b677c5422613b0d533f183e86fca20c27780cd143e5a367ad4fcee5adef72ceef7b30654c072271bbb8fa256b920490cf2413d516be843d6ef8166049563bd7f6775eb1e1fe8f1f18968b83301bca86245abe85cdd976aa38f64f2ce2a1722f1dd0b8aef7ac3ebce69db9350595cd0d8c5639ad0360c4f9b366e15c2c0672e7700dfb63b098afa9fcb5144f2e2dac03f2610509e14fbcac93ec59a319237232dc57c18b5a51f66d1c525018aa076675f81dbccf42144312221cc128250572341c4085e1e8486b72ec8049eda9959d0065f84d2b7b726ad251e2a4889aaa359bb6c6e918d20904ea11625f4cd1d3113d7a517c66cbd69dcc160b525215ec3fa21cae7878c822ccd282298af673a353e48eb968a5b92f54bd8f33080f6be17223c96c0d14ab798c835ec731da0060793ac7e22fa7777cb9e8140869b857ec72d0d0ae26ffb18009903cbe10728259082957cad460de456e3998aa66c9f1620cc6f3057b14625c2c51c374e4c6c48fd2c268f19d75af550d72d30e24c61d341861768c2c3a4f78dc713eeff04f7465fd1e5265d6b1e0e9ee16355e2bc0fa06127c9122af65530f28fc910fa8e052735bd54888247823b62b473e167be362a34a940e2ff601c71322cf289c5b6e942166a5eb7b757618aab8dba9fc19a3a784480eed74f9a117af4752888366777645d9e3a04100da504269c441f6e6cbf88e614ebfc4bf851d64380807dc94fea793eb5052dd28dd728f0cb5e3b4a0297def3b8fcf5e73496d569f3639890beee7be387382d0024fc39bbf3352f7031ecb4172b77a589294d80372a7158923ec4cda334ae61db71eb27e7e4e56d4e42a2efc72b77cc67c580587fcb282d81843aafaacb603ed6fbe53b201545be90e7b041f72988e63e29ce42bed08935dc76ab01f2cb3b8f35159b45a65df86940520a6a26c3c71e104515a046cbb9ccca7b5c92cc51e2b945b6d7f97ae02aacead4a861628f19d8460749d7f5a9d5f29ac5168888d1a9eccc8f33f50d6a5678e2405e4ad72012e332f29af0a15cd5b9e8fba0ad546006f7cae75d3ff1fa2b30d8389469c58acbf99dc2ee982f78ca767e9d3aeb368f2f9fd3d5ebdc66caf97c2c82067be72f1c14e68d2a57625e90be6077de6c6cb7190eeab382fad83eb7a359f844b1a720a74b6138faffa4eb0f59706746dc76b1689e00e8e4194c39c1b83c14cc19b29eaa15703c47ce59899af5a8f18bb15e910b1df4a48c60068ece866945a223b723b289a9c844020d32762aa9bc3781848eac73b78e92aef9a3af3cd364ec59672eb42062b186310cabecff04d0e5f29b63e40e4ea5f9faad72dedf14991ccc872418d4bc5e758b6520aa4d6123038596a6983867608441101a18de0210b3e0372d243abee8566af6a193676ea1b21b8e2af53913b3812a91d0f8869af75013857d0ca2482aef0ff8181795c877079b71a5e92372918f891c352a9494ee97c7d66b5c7cd8c4f5577d13a159b333a2126a5fc7f05054d57ed6a32ca36c2b90656721ddc23a0f73eef09a3b3bfee871e562037eca7acbe7ece2fa3b14fcc06cecc7226c33f6b0e698ba37041e4fca8f78ce0ef49c91f9bc5414fdc846ce1335887723c08ac0eaedde604a02f92f49b4f97c24822ddadc8e40aec0f14c8b5d01bb172813fbb29a6f302fa9cd6311fc9b53e0e24f0de7719849e904983ac339c53d815522671a48f48956e0304084883a586d61da64920a5b9e8fffb612f86a8aa8f6e735d2afa44e793a05cd8c4ff5c653639710f9a1f213d7e729a4f6b85b14bbf72e38f0b976673bc40f3bb10ca7aec16ac21844635db05ca3da445af338c7a44725f5ec39a05780319722b349b5b486c479384178c47737935ff7a30701ee5af318afde3939bc8803cc5ce4267791d309ee1d6877347d3a97c760751024a7c07725de7853c813345ab138e5c4b72c7ebe5a5263de142017d8463307c0b91ee816a180725625e3c7ce57d09ef6b1471cdb0ae45e8f4f7468f7d251fb5f59b55f872d7a624fa510bcc56998eab5a83e105aedbf283388fcc5cc7eae31246edfe5f7271171f890f02da0de355806490029687e3ad3afdb3d9cbdee78697e9bfeb8d6bcd18fc5234f0562883cb86471c084c84138d5628eb3ce8efc41b650ff2edf072e4f468c3e4fe3f3671e1b8e5ac51593d607491705df572e6fa91b255860e87552d6350b12a1c7fe1220b0b2d2381a9d78a72b37b79839a9ec0625a4d5db4c242c1b8d873b43c6147252b9cccf3f7add0d9ebfd7f55d10306363c3ca5ae50fc72b407e8308a48135254e76b807b29a722d1183d40be93a301f343f7a902e0763ec8e159ed6b70cea86c427d1f4652c1c3d262d01e30be85e9016b9e83bf66bb72df19c1c9e1d4289c5f92442181ad586ff58027219fc8830125525feea3ff1972660a79e310d9f5b635c7c59492ecb3882823b75f9e6d8fe9ca792ebfb267487239f417f2e0a2c96dd33fc8d95ba329d7aebca74dbb3dbb68a62b273aa12ad23817a602860666a1746084cca8ee84395820acbbbbf023077872a470a0654c312aa90633d220db94a88b3ffb268eb20f0caffa167168a9dc5176ef0874c40fd84fd7bf6eaf72f57202888ab71808203835e543d4a7ed409430c98c52a3b9bfb809b2724e0cc980e19d42cd45256bf21dfbd8f4648a88fecfd5bdac67c08e30ba72d6231a55dd992750d6e8325569bc7bb9a45931bef854c6beaae15c77980cbe1ff1904d50bd180cf85f0f8869700d77b4f0236717b6d21e601ca7b95b0ca20672006c0f1a228275d655c0be60832879b613460b32386caa273459f7763666ee7294864b79d9d0eb565b4bf9c3bebafd0eee309b427f3ee10efbbb0ed1db591c7273cb0a4642f94bc4e6e1b948502dcbeadff10427fe6220085ef33db60077f37288e7b0125b3a2a11085149d3f2830ac85979b55a524d5a6360ba84f688b745728e7e1a4b97bede2e11cae3991c9bc7bc24c7427b5bc896fddf93935f5d66a0054ec4989514a2a71371d2ac3cd9e0ad3b18f0556d3506b0dcf993a47c8fb2fd05a94793462f514d51952a51b8f790f7b9c4d17e2c961784d757e0362dce34ec72581e229887ce60c8985c745c94e5e6cfe0ed752f2fdbaa888b5762388a7c3929eeac0453ec59ca3ac2ce2060ddb4936a212d064905db4199da5ae1fc496327729ef3486bac570142f6b30946780b710f809101474e6fe5dbcf0b0a473ac2c572f26e4d58d1fd8f000c52bcfeda53ad83d29143c664738eecb51f68ea076c680acb532c715ab2c1d233000d3bbec8c8d5f92661c65845c71c3c19dca49bc1c834bda47eb4c5028dc6d06215eaf1a0f9f4ed81674b3a288a3f6b45ebf73f10a07202a6e48a9e85b411038e63be70919fff571d4b179353bf9546c1685ab4ed1d35b633e88e39959e3e89d40c632a720dda3545a2c4f86159189c61f85d60293265c2329c2cfdd5067cf524b23496ba32ed091fc4b17984d98df8fda12cabd05a6acf7ce9ded2e4c83f58539c36797219768ceeb43fd63023016e68485dcf2904447c4e9309bc2589f2acff64bcbadaaa0c9969293771a0c35b4f66e7bd11840945ddc9abac072f58ea9ccdff3f52085b0adc61576039d8a16aa20149a976606b40e7f6f23fd8c1a7fd748122a83baf74a194e20864790977bbb1b1a1458f0fb85ce0f6254e5ec98bfafa71e0456793716c2c7f68953a25f95cc322e8a7294bcf72e0be03bd57da6f57d3e0bfad7a50b2687f651d7a3be61acb1e70201a99de00317a6b0150eb00503ecbe586547b5b21a79cd781a4180b2c498b8f3e7494272766036b6182981bf3a367fe9025044c540f1c4bc64cfcdb6ff8943ea1576c0947253e086dc33e7decac314eac31e34ebfe8c7fa298022ae94b15a379f37b8c597725faa40c35cc8aa7868c271675a39596fa477d5f243f7b7331e71253fb2c86b2cf6c6eff5b8b529bb6b2e288bb1d1d17a926e0695d16be526588eb997aef695723d0efe1d2e5a62b40b255d7d128249526439b16c794a80777761b53c8a3c2e7270c7d6c0506d40d30998a861a26c6079d1dea58401f435256c420e28cc83d172e5ff190f7f61a2fbae7473930c69b17a0bbdfebbfff98f4f7caf1b104b05bf6dc221ed6c3beeefa2151b9989133f9d196ceec100b1d2ee068efb44d2b8248270f972dc44eb38fcfbf1ae3569d7dc1f193182447728d723a85b24107d1e557d2df090a296f05c6ce5ac8f4c1654cb1b0a63cf795a63b84facc9c2d27c61ce8855521b9dd5517d5a9f97ea0fd9af81c4b395518c6469e9372c1beae20f68722672291d2327ed07e56f7444d633395cf813360efd0f88edd4ba8c61ee4f1e506a464e842cfbe2727422b11d3fd49e20fd829945b971d29eae24e1088174e0bc9472b36a6885ac73d2563551165c0d925edff5de8a3c0e4dd67757615d00cdd9be7240f7823d7a9021708670bdbfdc6da774547ba48b063177de502d24ac241ec97256bc910573e14912db1e11f7e55fae4f46d1822d76d63f685dc1c187ea6f9c1b4aa9db4e76911932cf15b9bc412bcdd14541c88e3c37426a8454b057bb60e8726216dc17f3f82e2e44ccdca188089c2a329886377a09548fb087ee017b975d3982a892c8f6362f0127b5e143e5b216e095b05594d72a4f21854a6d2790141072929f04c7d47824ec175c16c8afc9f2028e09fb68c00b6163ab83dc51955d823ec58012c6dd2f21178769f7addc4a568d52671f4ffc68eda3ec5bf4a367a84f56ff66e83d44cc6703a8a44cbe21cee5cd5d93ae57edca7148911d57bfa1abdc09113ea4b2bde63429747bad975c658acad74af362111b8ae88492270b45408e4691def264c4a46d5c237bfc836dc4f70411ebdd9eeac0ffeee229daac97e2c972d8fdbe440203f41ee374bfd25f681cd42a8732a1d21eaabc10203869196e6a3d0fbfa203bc360f93b91394124bdd81c69758563b23207ccda09f966167e4ca72c15181f8291fb5a4529ece4900a740a54a4b28a18d301769ab373b0563b82e7206d86831555c2e514ea0d6f8d668c30dcd6d3dc22697fe033f4c22c6dfdfe207939c5abab4ffa41297572689ff0e0465c31b148fa66785b0685bbbf3555cee72a8c80e66adb866b6935434cc9a28ac5c82f40ea8b7405d577815c978f5fdcf27a444bb4db3354f10d41fa83d469748c95cd2363e38f73056516eece313cad3720bb0d19b4b539d9491059bf8ac87cdf2b1d74e4163635164e79777fdd289b73f2194168181943d4435ac61716e103b56bd7e0f742a2b7c0461f5796636f51b36d587dade07c9555314c69b15009675514ebb802c21698fddd0e9abe7b2377749f89954080c967263ba8926c0d8421ceaed2b577f14b46fb1a77bb2794b3f9972b85117a538c4e61fd3cfdaa41aafc202d8134af86283f8eafe6564b0fe2f7272599992622df8f051460bac2dcd0ef2c27f2710f23877cc9a4ae5b2751bd825721ccfeaf465535d0bb66c6b7216bd267129416abda020f2c7b2935bb2ebca0b7263d26af2f2b8dd92e827b2fa4b23847dc784868ba9b83dcd594403501ad1740825010d89158f2030753cd5013a84e3b2f9242b02846031dcbd4337ffbd4d07729119e107639403f7c601bd1a61d5cd59c23e18986ce6183ed9d6852396e7c6726e0a56ee8ebecde3cc4e4a383cad7d933d377948c8a006ffd9b31ad530a532365a8b10cfbe2f098168ebc1ea37e8610563277d570ea9853c53db444503b9507205f4ee3358a0842e5185d0a3d3d5c01fd2b34b2fb739a05ab468656d7585b44d063c2274b7cee4d4cf5447b3627e41242d656480b97cac659f2856eff5cd477253ccaba5c253dd450615ef1db986ff2645d1d147fe1ea639786bd8e49f612d72ee483fe4289aeb9afb31a674b0cd07996be2fb7be4431cfe939f039c853b24726bee30e3094506b9fa206b3ad388f0ef5a22fe6b7899a24192d3a2d0325a2463744130563b633d2aba17a0521fefdbeefebb6ac197ee93a0be39022d8f630514641c5c456de22ea8ea0d00b39d273107f7f3a401d7c1853cc3225dacd0d4547273a886f6322afdeb40d96259dc0f944f4b39c7a79a68ff13d1a2b6c5dfcbdb729538d520669b5348fd6219a94c1836c47a9ebd43d3d7c3370e78e36db2e5d972a75bfa8f72313894c7bb7522c6b16c1ab5960216e39c22bb0b8695c6e989d84328827b2af44abc22569ea65b54b0748ea5b11ef3ea43131d4eb603cceb9d15370cdb2433ff66f73aef52e8bc671b982e15cd99edc33b3a5590432fe8effa78725e85aae74c227bfbb1bd223654349ed816c6e13141dd6cdf41534cc475f81c7234672cfda9ebd0c5fac98b91fb827ed60671b398625aa2780dda0b55ba77fa2021250dcf8140d77aaef0be8f60156c065eb303842c29251618d7a0f8cd1bb472d0414bc6fac2f273006e95d8df87dc4fddf8bc219dc1eefe43a0894e691cda72ed11863c9845501af1940ce6c21c708cd01c59cabb0637a95c685a0ee096f27245c097a0b9cfdc44bc1d1aba3e0b45bc0819d9c3688fb53d79a0469a562ded4904792f823cb0462c08b6f11b2c9b45e3d012477e6e31472f6c5b979403e5406981d21d07e7aedd480e734de2b77de31805f0690161fd9ac81254f4681992fd72a6f8967a896aacc2a6e587a50eb5b660a5e5852c25a74c107c1424c813fce1723758f165fc23826a569397865373948f2466883a932c13aea7c262f3fea028729fdaf3692653e97b9f4e960edbd539a8e8ca9934f5abed5ae89e4650c60d9d52e0fe2978aeeeb32f5c2365343e572e27449c935e4ccde7adffa61b9fa1e1b6326d0e3bd97f4cc6e16192b1528a13c48d1e74c077e3351b7a4b775ef1c9b9b1728061e3e7d8ae62dcea07091d3041f529af6b0ad41153c0aeb51a02c418c6d26f1e9f8bb19f556a0c3f55143ac50fddb960118a5c29eb9600f62d4708a557db72df9703cdfe8a70fb32dc9fd395bf5e365f0c2bdef8ec53a76f8644258ade2d72bedc5a920b262542d0696a15558f9858a88df6a57faec1077ca47f27d800a1724e3ed4fab038b15d0dd5e1cccf6bcb4e7337cc3e198b92a4e3618cbdbc8d6472e8706cdb1e7a1c46130e92cba74e4be330d4e46537c31f18f5b5da963546c77292f2a92799689b4e1677cfb1e7117b46a89984e4c36bb85c2b4af5e40636b272eff55836b987b60b3b485e1cb633c39e19144e21b96d822d42e26c24a334f31ba4e6a8e8787143c18e7174c37286ec49370081f051a138ee4668766e8c3fc272aeaf4eb2e8ef8b65d620c3dc51345b6fabcd4c39f1e405de9715b57577b134533f325c463e0bbfacec6e85a8cdc59a672a9088e35382a05a7451275cc39e32691c032cd4a08681d1347de1a8c6a69395ab579c39b4f530dfd6748fbdff006b726f7e5019e6da9cc99eddb3dcbdaa220160a54ec23677a467b79c26c844144a27a5639eb523b8bf783ce383527fdb1fc5e8cbaf9fbbbdda8ddcbc31f990697872bd71007854566717791b81f5d4713310ead9a8cc7800de2779ddca43d61a5d48e3302ceca121faf17c0dd2d65c066cf97e6e8f5e56d3c53e39f6ee6f7660c6119d1c37c112aec13d199c279c81da1988bcfff6ffd5f975ea82c5801255a9ad725e20e4e368131bf89cdf2df15baa2b468e5d8081a4cd85223308a9a93980b96617769c94d7a04c7d0221dcd73bef6ea4549a02646b0587a6e1ab40ae79ab22726a237ae7b31253a517cdfcabf14cb7ce8391cd4bc5e34bff2535a290ac8a6972287ba0019d343cae7ca2d0f25880f951bb997d754c10d81424f338744d664672893f88e2a5afa20988151e0aa8c9612956b44b3a126300ae05090f8efc454d7273dd1f467702b0be2b84e885c10a40965dca6d0cababf5a605b554464240f61e237ccbe6bf3423883a67fd1b7d20434701f5c1440996da4017aad6cadb162672751a5851b440a66fb212b7ba693f8eb86fa106c87a53ef7d8fd2e6c14fadb65cbb20f419dec61906a7772efdcf7bec9cd5bb67fa516cdc9bf5e5702fd7106472413301d6c4cac24861bec1110b951cd3331a8e8805014d71f57ae6226baa397220f436fe8e53da6a871c252de8b2810d799f98043524c792c6b37b8da4975b7236fc3e88c8510c834ef79cde18df92725bc4f2269143a4680d7fc549430011729e2674ebcb18a76801a7146b2215de75ca3dc3ec649f2490bfc904d242ae7072b01e74392aa7610a48d22061e71ad0596c6827180714293fe02b2a8feb367572c6df18432174f9228b08f3ab586bdf7dc2066942bf37bd3531f528522bf00f3bbd529973a6472a93fd2cc7721e2c95a701726d07cde878906b1ceb65225daa7275092f2df6c342c7864c2f22d12cdf27323096c5f67126ddce9617b94392373ae82ac52bf3440d77eeff6accc3bc5045e625414705598e4aff0719d589c8c3721adb94006bd23dacb602091d2590720d77cf7fb6cf0abe88d6e057caade17672d304dd1e024307152c015ce0a2259f9f2766b2bbd4b64319dad5479dc414c972aa986c4672e8b379e2eb9642102ba90d47a7c89fc02b7444764831928dd09772e750a93fb02470593524dd2da355a239c11c3d3802c0f365f7100eadfc58007290ad01fa17eb5c3fa6d3d5e43ff7f674158c1cf3b376a40e0815325a5297c472bac8460cb08cf84042a770a9424df630c206c7cf17a51934bd6adb49114ce7307d77033abaeaf115292eb0b804e0d2316f19ede3f39eac0a0220672c1c0bf047166049ce66361006a346b7887d4e9039367f87bdfcc9b2c0e1cb99b074a465727c4ffbb2d06e416438759d1b6b22049dc4ba361dcde93d2395338169ff35e172e5f7d94c8d2ee131b0bbcea788b8d69275b812874ff05da2d355acc838302972700fc3dbf3d8586c84379b323d2d2d2f26705cf6d81cd83994c1e9c12c14517269d9004a5bd37734a4df0fc5fab596b93bcd6c590ed35f23130ee7a0b3c5c4173ab33da57b992c4604715c975505d4279206a28e568da14ab8fd34b2b769ec7246d3aefffa513682074aaa847db4307d55129f04eb25f53ede7ea30b5b472e350453abdb9f52a66e8a381b2d54e32b2c9e595493ccfce40de41da3ffec7ba57270daa57057514115c3ce264db413d42d18bed3de3bc998f80b2a8710b2c17472cfc39d8e04795846c5a3851551cb7561e099eb3932dc4c595795225264c42d7209e75d1e4807c2cb5342918e0552e2879988f378ef06b5bbc6bccf9f3bc72872785fbef4845f5de15b5d3abf99e51a572851ee2380b3e3aa2b0864a9c26ee70f932ade70eccc9a0e9f630f4b50b0eb88e5aac3345b86f5ac6227421cbd8ef20cd2495f92f2481eae6c1e206ab55af1a594827807302be07ee908cc37d34f2f7217278225303f6c230a95c5ffdfb3894ef51d3a1947394f4dc761b71e81ae0f2b91949c8dd3239ff08cabf771ba2c8ae829b233c0bf64676df9901ee7d032223f35b24b6170a112c15a6790ef06e9dd9941a11f72645c0f59014e536768920b72796907b16205f7ddbfa895345a84cde26465d95702e3c0b009bc779be9c37a57cdaa3f086f8f078584580f38b5141793bc87e302ea7d58c291fc6396a960080d3fca88fa46071e6a3b2935bd38aeda7358a94bc3895497063e7cb45d01b9a3727e2571d40f1957129871dcda93db29633bd11ed350bdd1b3bd302e54b0dd29728e43cd5523b0b7a445d410ef8f4cd3c60c16e21d892848aadc1e681ab34e87724d8e19fb31a3b2cb8037e3b0bea93da88955a2c202451fed39c5b5cf9ac5737240bfd8a32b3cbdd7447446a26398b2d4ad3d09fbe47b80fb0ceba954ecec0a720a2c0f6854b60eb9342dfd80baf821e7003c276e5e0efaede8a738ac50138606a4a3c4ee0406043f3561e5d10a8e410aad0a95634a37ad0a6ec7133c8f22f9040fea2c683363e8466be7a9fb8341ac1d7fdabaefa43fb461ecc36020cb80c27212c451dae987a1f917987c150de85e49d4e5a575cac54c09c417db8cb204f32b211c0d0d74f4aaf4876d1afb4bbcd3d26679f17fade5883f5b13b1c203ee477256fbaf76ba1cb2c418213a4fbe6c89499300908cc804b13a282d09e0c027102c4bdd14a81688b73ca65e0db9d4662a081d43bfa18aa18c4ac057ae868a4161723a7e83d904b11417d60d521ab60d53660031d3e5fe80e567855fae0c4296da728015f5d968fefe4bda38da03064bdd7f93965d27d4eb3d172772bef5b4245272f4ad0813d40848405c2172d9c722b2a1a6470a8ab220f916814f3566ecb5ae72f1f41582313ed064427eb2d7b21f7d658e619a9d5b2c67a8e73c4a6c828b95729b9e42d1862d5bf782df0177915db96add489aaa0d795ccafb6785a966f1a072ea5097b91460866c80637529e402228b18bbf0dbcc44fffc153eef3fa80a7472605859f6e3de765401ef1eb726ae9d1534af0473d76943aa7b14f2c03cf55f18ff71899ac9771f81041e67b6d9d576055773539504e05677859daae9d49540720fe3e80a4893ee4fa9895accac1dd8350f98ea48855a48fd5c6e01079d8684000ad6ee51a585bf7da9fe13a67a6dc2d1af2a9e3330a7353d9ea8db8c27ed5d144682acf36c00c58845f0946b0500bd6080d516bae46021d93f2586c011dbc67224cffb95be8c8d20bb02d55cca9ae18dca7dc4c908775ac6953e64b59a5f9072d2d745f8d7f8ecb23af6a01f41f72ed9153d5da887112c039887b5b04a378e711b12e9194feb6e4f96413fb7c8d358ce95dc9d698cbef87dbbf0105b7ff9fa298f86d237a00006f5ba2cfdf8f08a1d348953a35693add2f23b03a62845697e72bb56262d36cc2c603a0d9bad1b24949aa858d0577629a88c5b8761816856d972c34c3b1afbd36bd0be4f306c42b64b0e7114d534d09c283dba611f84b06965722fc738b68f5fbcb8e1a5aabd08b3040b29d4be775ad085f335c3e086875e750a9cea3e40952cf9152e696e437f45165887eb0165164955324a5cac562815be102199ea1fe99a987e953daec611486c313e195858e84146f065ebb0858d579672c8c1f59fe0db5befff6f543bfaea11c4ef4fa44b17f21dfa0abc450a89f213145bbe0ced603904813a9192cb75a28174a3dcc0fb1f25ecc5ccb1f0dbf2c01d72d4141c3b8bf712769d4a86a44ba96e5cff6eeb74aaca034e13e7523f2f929672fb243284ac70660446bed3e46d08fd7cf32e7e9e694fba2c696d145cb5ff27725aeb13c7a48963f40d323d4c9668fe5f16949c65ac30c6650f62929cb83858724b4afee451940e4612ba74c82d63e7c868a729e635e6ea2454a35a23da6eeb72a96114c878038c7ca72472dd9a92d69053e47f8b061a2d7154887dcd86dcb8722004a2e11e9ba00e802b18f1c34de714ffda7a373df448cc1d04c6c1d1fe3c72e5ec5d34a5bf9acd95043c31d6a318d95fe8b0bfd6f8d325a084512e596dc672ae0301b36496f6723b01e9fbcfd1ce7d55bca48f187563417c3be8879cffde3178252b48cd95df4d141ae7bceb93c0279ea643924dd19c52c557e2ede7e21d7210ac8fbbef1af9b335944c69ad06a50fa64bcaca7a9645b2f5d3bef1c6583072364e4fbb4c4f21fdff35519762526d275c3984e0dc722b7f6c7b66d7f00f4172c9eece1e311db26303a65f3d659b4b5be082aa1697762d27f5c2ccc83e876f7281c9ecd9ace83615664ebf973f1d5d4c2d547c71155746917743ac4e2fdc6972cae87df9b28d9e7e075162664f6ec9a0f350c3ff96ed87a09bda08051ae54370cc2fcee456575bd516b3e15aaa449d3ee869c5c11048cb874130f4589af9b972b81dfdb5b76b2e75b71ae23b15be5a61e589445ff182111452caf9135878ca61eb906beef5b55e39911da8c06dcc68c84f5c1c5838441fad02bd832592c13272c7453eb2146e0a419354851290b3776143dab1f6d8e6f89f05cc3a0e489f7d25875b7ceb66f54144838981d37a1f40f395a0038689a7fe84cdee5066d10e8a4f57fb21817afa4526bf5bdcca6cb1e230ff776d9225ba5c2f1a7f0ad2cc8889198e02491d96790cb6a5b762c3bb1e25bb8c72a8fd95f4c52d05798318f37daf193c8dc919801daacde981e7472848a26dce979394b9e6674b4921cffa9b11cc4ffcea3d13dea26afce3a02c5719c40d945f55a94209a8c8517259438408f0320f7d37c15ac59d4af1a023779f139e074d311a77919a9733e75e2cd3f915cf037235fa5bb1ca34d5af278b54404ebed32a67be4676c8cc634f375d2fb198ac0072abaec0d583b66dd247ed47007ac18272ee3bf7d7c5f0ae46a0e4c16648f03e724ea525fcd9d524c415163311b7b25dd22ba0aec2e889e88acbc7a81fc65d980c62cafee8e27bf119c373422f92cbf49ffd82019db290514f9b1fc4362e11ce7204c137f6ff27713580e2c85c409a0006f7580c7145093fbf36bb44695ff9d30b571b16995ddef2ad215bc75c77a51ed6c7fbae1361bd4211ea526afe5f03e97270ce3c7213e0ed9058d8351d3ae71cf35e5d2512836ec45e4c94f67faaf16e47d32b2371670dc9f9e7a3d2e7ae4cdca2abfcc2d617ec56b27805e717df59831694e4c889bbcb9f64c1575e46c5097d8aaef390b6199bc8eb0fda6478707bc772d4c7c752039fd7ced02deec970b94a76c6dd2832f23d5d61c6415c9e86f9397250df657838da36f04d44a071c28afacd9fbd3fa03502e55467ec07c061575472c8b79500a8159dc79bb3b57b5fa9ffd4e339a0ee0ba65e046186798d350e2f38ed03efe56674f731815a2c7110374364c2fdb0493f5be77fcca1525745584972c0a69ddae2292b5f71f8bf7e5b430d41162cc47a8c4d9642bdced988e8cbdb7277d2d88b7e215031afe86dad2e7d0f056042090f1964d718eee3ff08d4fb09721fd52b5062c425c65b298cf43150a55d26e40e07290df1b5a01e151cf2e1d7723a9e5c0e0b698adbaba4d20c8c2ca627d07aca72d65a06e62ef07b83b1702738281994cd60bc894ce69b7347970d822724c23bf9f5a97ab6ee1055d3ffbf6c72aa8cf939bc312917090f7e53cdbd6acfedfda495450b0e63bb9f3a20f7b22650adbf9124869ddd83354682c3d16c363437c094f5966b7cbbac95ad1fca46836b7110f5e2f8222233cf556decb8d37a6c3ebeff5be1822f7557d5edc87a133e72d73523128d2254aa92b5468ae4e35a2fd6d4b98f91274102452c7e705272d272dd0444e1e3691ca241cafc7d334bc78cf53ba80599baa38a1492d2e1d7d4fb36485d1e850775454c116be3e1da5576799d3c9c71d2c9242ddf84f37be664f00b7a5bd27fcf6f545cecdf2e48329f805650b6c84830aeae532fe71289e5451b394165935e1831817214bb6a21a4310e6c88daea078400ee05e3abe10ab8b6f95ce091d53695c6e614df3c81296a7525033a3f09509700cc6957e9fb8d69b0d6725015bdbd9c685174837ae3c00431da9c59cd79f80396ee4153d118fbf44c4a728a33cd70c0f09007432ee42f2b588da52fbc70005ec3de14202c16e692136d72862db8755a56d0618594cc1d0fa2267593e3bf411ca9bc11710602d33dbca9451c660cd5e41682760b89e073e5ff1f55bc2b1d2fd5e3ce8ccf0a04b2f55bc372b582e23f97611c04a287d8f73d3c35fa252c4470160c9203d9811eaa4d4b877291ffdd38668fe45611a9651a2fc32d2693cda2a8ae6b8c45acfcf2ba3c99f1428c4e128ab065d774e1c423fd4e072e34bdb2b991a68d607ee62f05cb087e74057ef7fcf1a73f066cc4d2b8b64e6ee1d8b2215eb85933e2cad28952bf74f31c678e89cc1fe8e8ee95c31c56823499ed226b3718418d1d8360576d6d867f52327279122b63cc2421be5537749dedbefac8b1d724722823b6c04cf319fe1f4fc316d4abd09c220d1a332443bc6672f690ba288c67b32b252b65aa3b13745192f56f182f8624cb580039762bb4e799c92ba11e1186a27991157225a239017d9c8072b7f7f1df17f80ce60c3560c6f1b93e7c2d201d95f4a589be1375de1535412a72a45345cf1b7bec87e4d1b403c6cba96849d9cc26833b0e3a977799406e60a8725f718d4f86ff25e754ee2302f6745ba354ae586f550d5ed7ce5548610389a97258fd23a7fa407661b1da724a4e167d5c6bc31c948b2f85134f2c452fbdf8f572f83573f3d16447efa10b4e50dd2b908b207e0100603acb3b862279dbecd3471af9780752a64f656a8cfd76b266b7b61e6016bcd6e8c7135eb4e25196a81b7472c6a9bf96e18720de7c715a2ec7b3f00cde183bfcdf02afae8b8dd3e27b9edb728706718744d71155780d8a1cd158808d378984919412a549b4bdf6c568bc18697e03e6d6035e013ba57c18f7048959575e30667fa82cfe58168476ebdec48772f822108ab8a7ace8bfda770cbbfa3b673f0bf91fd930f3226f812ff4155f1c723ca40fa336c44230f5f4a6deab30d72d5a345002fabd81be28dee26e167fc87237f453d2a1e12413d2a551f1179b2846aa8992409cd953258b775fd65f17de3528322e8d9f3557efb87318a532729209ae866c53ac514531d6fcf2322d241315aa9c7757d24b5fb6db787f57c97ba7cc43d2bc0c79a1d77330b65376ac727b72624a908b715161bb3d8838146592b833a6b6814766acac08356099e9928e0a6ab2afd806d60bff2dfdf09959b7a35483c90d878d08eb133d824d9e4daf5c5a726c6e4781a3fc7e095f780f885a9309912aa0576c281d927eb0d1be9b89b74a45e5ea09e273d6cc9e9e4fb1bc4125d3f1db928814f5a65d46c1231f0f18d75e722403e90c09fac6c6cc173538e29602fc8d80dcb0ea6338b5f4d9a2d11ec8d07270b393ee4ec9413440097701ffa296cde16dfab4387636871860023de96012725b276dc0d9bf8b98419e54b58308fd6f1b05380da15edfa8df10ca79b49aae724e78d1d619a81135b092c7f15bea555acc1e25586c3d7f19d640bd69f54c9b14c5c8a553ffe2cddb583ac7e562b7e50d740289b8ba4b365ac2cf3789971c3536a9c6c7a92963db4336933a815c4d777c78c3a527d77e47a2a1dc75551c9b5123ab27238a40cbf5616b44699c5cc7c37a2847a265d6bebdae10a38e0c7e8f8072d7def0af31bcca3de0dfe81bcf47c5d25dfc7eb8b15f757a6dbb4a2ae5a6a972f921b8984f89b76b8f340123beb982df2118eb6c208a0ca52599bcddf9eb6e7221d7e7ad8b37a47390adccb83cc9d47754c5ad95e9b47032e8869b93a27724728bf8f51b7839c20c14cd0a62f97930502c0e55ed8a51837026b103425ae9f97273d93ebd5748f88f7398dd83716e22b57888f00716ffcee960803e959f0fb3558cd599a9b96b2a8541fb16eb07c63222e5b9a9d944052a6ec630426db924be727c4d3f10ca1060067b40afa5df46aa61419961718de869fd0507cec448eb4132bd5050e33cee71ab703182dbe3bc4a74d7c34dec6dcd10e07592846d18fc82725f4c364a5dd0a2437ea71efa00c13b7e3f2a42f7bce2e904fcc182783eac5724270cdaf14af9280621640e1408192b9a7063decea85dae23c2fd743ad9552151d2f2a3d73b907294e9f60ce10b27edd78ad2067a036b9fc4cefbbf02baaa9f6f520c22c45787b3cef0059d2f023869be05923339cf908d2a9476e4d607112f72190e3a2091ba345eba52ebe9d7fb2153d9305ef0adce0389ef22f0d2c1d4f25e9f316f0c717eb5309b793f4b8d9ddd19c06caca111f6a74a76348a9cbfb6fd7224e52dec3aaf068dccd85348a557c8c7401f81b7db7d158eb69db96ad73f931abd9c2a4cc47853df8d8f470440b8addc00c44dbb997f14efb01c0940787f9139d51a8da1b54955c2d1ec9d757336a1903f609407e7e333efcaef21605f1ffa6b57671d2d96b1c7d8fb382debd6b8324f6b3b84bbfdbe1b3bf75e52826c140b729bc70ac74316f07697506639120dea8bedbcf4950866d89335a825062424a57210ce5a7dc828df8fbfff945be4ff4e1ccfce5a1a61d418a7fb1d7cb9756d9a72a07a362e137aff1587e293297d375913390f57e2c0a862d64c6ca64c8b483e2608db64a8bc13e42577b7299c5d80a8d9f05a2378a6c68b31ee63d0bd44308d11427289df2b10fe2697ee43d565e0ccb3afc30563cafc62e39e2cf34517c26e723a677b91bef970afcd9484b550d5a3b2141188a390d120127c5ea0e56fb4982ac61d5e92d6198efc8db9826d74da7202f3fbd3e39dabb03956965c3d43f6553aed5578b21d9d66e2194c8dff84b2ddb95dd03ae570d74d107f8cb91e51bbf0726b2136e3d24b3d8223769f6a00917361d66ef35ca05fab4e1b7dd044e0e0750c743fd393bae9e96f742d584120d17b3d8e32f7da317471074c2159b6249b8272dde50905404f530ba8cda74b1d8e08a7be2e733e713a7f2c784ebd4ca7634710f17abdeda5aaeed30ede5b48d846d5722ffa0b8bfbc6895062c7d041e7189d72e85426e79c638fab44fbc3357cc32051020fdfa393648e75b44e33921254ab72d972133976d8143f7a5867993e2474b42c89fafc881701f0a0f5a1b227b46f72f1f74b998314f2a2d001fa4f06273c92cc5617c1c8812a855df94568d0ea45119ceb308cdfa480c6bfed8efd16d49849dff8be380cda46f4ad7c3be9596f05423d3b4d96a0a1a6129c68be9cb859bc366be806a791e7ea3742700ba32dfc343e0fc232f351b4f518cf2eb42366e500474f999bcea4eeb5bcce9f0bee180780722c88c8343680d1e118b65c04f38dffd3865b6f1654da53804deaacf40d7ed40244d60105b72d4d52923fd49d50fadce850d578597dc844733c00b09ced0bcf72b243bd3cb2c4dc0e0b7e93a7a9bd9be610c6e0e71e109338909b4cb3c4d89f33d38f500b529e4136859a6bcd9eb233b4e29489909aaee2fd4d566b7ee86fb972908dbbae10d25ff81070ef08461edf63d199fcf0a6d688ee7ad75045343d492f90ec668e44b55e5de1930976c83896f16513ec517e890e70120cbdb796004572ef0e63b1f592f9bedf3264d5411588c530389492d55c4b6515633c1f82655c4ed96dc4b9b18e82c62cea1d0f54edf627c9a1fe92865b226bcf20bbeb5fe305722bfd94ffa0cac1df6f8c0365dcf82adf3666dc5f3cf70b0567e14499e25b59721d87a2464f6f67c31fa63070352277a957eba977c5148ed00cfdb6abea2a1872cd945a339d783deaaceade9347c23957dd49ea4ef1696d80b919ad4aefe7337200e176d54c2daf7298871fa8749a90157d508a43cad15110175c392af3093e72b69106d18dd6880d3fcf94679547288721d85b1180429df46670d7e052b0d0723322cd7225634118dad93f48f38c264620d03a30428a24a4a002573b9036e472622f550a421120ee5f6faafc89d54aed89e81cf10b9e39ac3007bb068e8920089feccabcc38cb2a79a3f3a9364d8412615ae5a035bfe37ede14c273c29351f08f6f90c369910f99dc42ce10508c61a7a7428ab70f643ba7e332083e00278cc72704f93d1e503f24bb737f518212137c9c9f75a2e7ba1d80b0a228d575080e4239e3bf2c051fc25bbb7689e5aa5bf2fc974e286671273f0fc327d1934a78ea60f46255aa6d571f153edf51999a3baf0518189fb3e853c8d8ff9e09549841c8872ef4242b4878aec1c1d2e717fe2a1f5c46cd0b0f2a21811326f95e7f1bf525e537b42db1bfafba1969edb6051b98b549e2d8be49afbccc28b960585b7c59052726f9eec8a532c07c23e945fff6c515e33cc71f4ff42655041184c6690e1e52a5f524790a8270a5038b9e3204e559e74ffb454cc9aedbe583df870af87bff75164d8ba0676aca39223781101c72af4eed746dcdb7c9aa27e96de37515709227e3fce6826425035aa0311c1e00fa0a0f2ab1090beb919af8f1686fe4a26df7c937238d88b755e61c58470e98fd89920b4d6667feeec2e87c54ee492289d3c1fb77261b27100399c27e6e7e083b25817054a86cb8642eb436539db18af54a0c4db72a071cafcbeb2863e51d87c122718ac2e1d102fd127b7d7c4f4f3d65ed7759b2e520be22e98e9ee43cc7cf9642b3f46e22ee86cb9e500c15340d3960d22090d72205c7d2cd6b135b1ab6614dee39b749da7d5d86912f5a755e1e15aaf6b4ff41a12605bcbcf52bb28621d2fb769667f8533ad06a0c38be3ed1556df5447de2b7280e73d3a07586ea8af435337c3c11d2c5adb8eeb3f2db40dcdefcbbdf0a5e972b323efd8fc87a9325d2697ef4b7ce4da8889936323853f62abf830f41f93a014b957bc937dc97b563006f11d7fac0ade68be387a200c198146ddace4817c8b72eeb6293394bf3680e539e6a413240edcb90020ed1fb0c1d8cd9f07e599174272bfaccc69ae1a057d849eec9a56f091241496dd378892a010786531a623ff194ee6d5b0815dccce946a7fb6a6590de86ebe476dcd9f86cde0e826b4cd42d9b272af9643c6fd0306296dc04717962074f3658c91144811099c8e989dd68699a41c37651eb02783d19be5f5c7b444e48d5e97f44ceb66d8a001ef315d1a5e0f48722f83b99fec0a5ac61c44c9b2d138219fb95bf5f81ea6a6b88e9972f6d402bc723550f3fa119d37cf1145537dfeb7ec4c8802ac550240fb599e676f3af90683720e7afde1329189b5d3716ef2c7c747fb115d1584149babc03a10b14833007a721f77eb28400c6458d11ba851012968b7774a2b83235c57d76856c098f116437261080c92cfcc06bfe62904935e9c92bb3bab70712be25f970925f72fdd2258251e529c7068e0e9cfcbab9c8fe7f07815ce79172198064992ea7a33dbe239e67251a4bd67102031a4c84f8bcca2b0159036ace5a9f06b4496cbf5b32e804b9164f63b4722ee6c249fa773cf8df2c313e9822ea26b066728a4d83ac10adb548056396d56cee04a9e6db74aa97b5cf5397c3ad3b7bbc2345703f724e55193083972c31f9e37d07606c6f94b3078ec93cd7f358f4d372e62073a5915e3e6cfacc84fb5ddc0d53a7c0b79a7c6455cf88b73d2737a1bc279f8118b0df766f39d949b72b8d11ee0e8700dc3fb78c45d0c2acf513766b1fd4896909d5e888a9f6213f572403ed39cc71b77a8d040ab6780b80fa2d64b614499cbd1e5b6da0e4fbc879b65095e48fb9bf0434a310778af8cb30c56491419e27fe6018d8278892c61262d5844d14437a342bb830632c7c64511026a86c1604d17260a14a97d61a26176b54bebfae6ef02cfe19e100970349465f51ffbc199f644ffd99f24609901994fc0720559f84c4e75cfc47a87a765430c66aeca107a94ceb1a51225d9044d04815b61e211e4db961beb378def2d3b9676bae7a47c6f2bb70615f51686f42b23c12e72dd1fcb26088e43e8bd1a5f13c870fb93d7d2abcab45b5177e39432183ecf75722c0239d668e45ad96e432ce6f4c7d5961b3050bf82b662bbd48fd5dd295cb501c24a97c7c9f8823bf79c328aa832bf6bd7a48cd2ce98e437d289d5245e53f1504dbcfb2092ac4b6a6295fd090ea1b6d29809f98a75f0ba95626431c478f23a72dc24d63d5d2cd5592fb9f093cf1047313602605aca1f7570e058362892d47472c181a3e7b961c3c44d35706a9c909a67e11b01e4150dfa889543ce42ea30bb72a257e1a91654c27fa90b70e07023b3511170ae3f8dd1bd0634b68c5b419687111d82f7219070e217a5185f42c4503d7e1f2d527ed965bda6680616580bd2c0293c92cd2698640c5a698d0e846b71fba8dbc5dc558b638ad467197b49bd54905650b1590fd7e668f9543d23e72edd837047b7dd1e253467b81371dcffb855d922a89ff39d627d84b0cc15417b1c13cfbf7c51ac82b4a3df7b70cbb2788d6e4072786b0cf1c96cf4e26bdcf92edffc1016490c29ea732ed88e78651e91f62d5e17a3b2a85cfe98f7cacf4b09494e58c700b60050ade24b197da68ce37c90a08872e8840deba2ac1279da44035447c21b9b9994abf25f7a874abc0fdcc89395fe7213fed2a8efae59d4a3422a86152840c498600c547648094efc0caecec18dda721c46d4b58eccea13cb122c68ee734cb75af315cf713e9c58557ac8d55123943d05fe5afe17033bbbcf417e5c0929ac26c88a3e30e49a28f10d1acaaed71799020245b56e52c5e6d4ec2f3c00cf4589665f423bb7e8fa2eda826ef3492ea4fb7299e0362ac41df61638605026e6a7ab52c85361ccaea46a6907d41115f09dd8296f087a91f331565f371586f2576b51bb55f5eb6ae03073ab2e781828ed52e472c9ccb75bea53f76cce8912068f34373a53ae1cab939cbdac666de6e3c1f9fa729c079fc990fe2c45d2bd38cfe000a8c01d1aad6fd9b9166e8f2ebbccdff841720fe5a2ea1e24fe09fddd1feec0b8a5a65537b5b1e6bee2cdcaf55364a9eb2372cc95418380ab6dcd3bcdc22a882d3a51b539c9b184973e845721fad26be6cf136f1df1f7c635ad29b715aaff35dce05c73d233eefb7278255afed0a1d08089723272c5076d182531393d43d6afa81df7e822d035d08c91d1a798fe6e3b72da44e629d5d32f22d46807684f22d8901ee3491ef14f92dd5b67f4f88f92bf2c0a7284201a3cd9aa00159289a38d68999a5b032460db50bdefab203330dbcf173f3f13b0c9c1ddfef3ce01377fce5e04621ba7c384264bdcf9feedb8b55a830d1c39ec452a35db01e5425cf88a15cac061dcf2222770e4c8be7fdd409d055146274a11dac5851738e6f77681c21078ff0cf3f754ea49ab4fd67bec6bf8316a79e472a59d551d05da3dc74df4522abf8c3e0b1ee961ec3366f18678ad39615b955f2cb61eb1afb331aa060b972e8b7a03f5d594693d766f01a9b8a3bd0008de292509a34ab49c4bbec73e5bf935d2cbd8543856f27c44243787d57153be8753b0cc2dfc197cfdadd06c89c1711e5ff02df58160ea07cafcb80881b8fd119fcb57a67280cb10aada3ea0f85eeb3e4a0e18655c82efd06b2c14f7803925697f1a497b72dc7947cb1b8fdb39b02b280c8f1ff0589264d0572080a77078c115e5ee985672e1df498bc948f1ce8548ea390e32cbc5f86d2677876c5d5f89fe2b4bcdf84772053ddbbeb68c7b0fb5929466e3ba23bc13feabe1bef1739cc12c9a39f3860c726d18dba37773a38c32abded62ab7d320fa92f89334a63287e2a416cc462cdf72492c1d0c0d6b397506d8b51fa2e66e6c511aba542906da0399dcbcbba65c6e66446adc03acdcd0f9bbbae9d0cc712186f861c0e0275e3e8c4e4942f0c3660f39dc945bc59fa38e15ed6fdedea335520c8b48fd96c7de70b1a08a110b9073b44ec11b64bb62cf507a5b4dd2547726ef3d6dcc1172a11dd3767b2e54b49e90017221f07f0690b48921cec159712885d3137d2d59bca7e2d96c5d8ed5c3ef20b7720495d2f18e23ff33dd18caebaff2a1a6f061f00113dae99f1391a7a34f9a474ac03dccc516aa856a118e7d5d1872de77f8ccb261d0aaba935bbfc837ac7e3643f74e97981ed26a9955eafa1408f41d5a7026de08510990c2f192e1c696f3f068182cffd15f5e7981a765194d97bd57e89f0a9a17db2d7ed72ceb70e4f906d634ce7b7b7f9f64866fc79f535981786d8b04ec978876aa884b19097bd92ef435376ce2d271b3241cf8a93593cceb612a81e6d553bca9209ecf616dd3deaa04c7728be7c86143d4be2e48ca4514e8b51e90bf2235d702a6618cd4cf223c0f01bc72a7dc8c377e8658e63f512671f44f076b781f580eb5191e854cb9292fbe569b12a684a47b58db779c32bff47e28479c033b8f8c19f20e8e47cd841662c70d22724d386fc5c2a7d982dbb654192933c8596d94226656178d9dddf31d9acfab1f403709ce061b37e3f25fb7ef5a9b3d7700a7b2ec65e468bc209e90b53f64da9d31c8a454a5278b22ae9a884f1b1e23b58d3e61944e8d6bb803f2c0f126581e28729efce8e68631496daa5e1cc88065324d4ca5eaffa34449d2204314849c936872b33036f369244dcaf544244767a6db5c49328ab253cea734a117efe33efaa96c307e47ab9b11a6c1c1fa422e7fea612ec7eb81b6aed4c2ad5bb26f23c7d174729c578ebe22f4ba0532fd0ace7903fec1b4434fb2b14b4144e050ff499b4758722f6218c04bb689fc4095c824b38e52dd74c6b5c923c35a254c939a8309284d72e60a25470955ce9961a6e71b967dbe3fe1b677243a30e6e16ca60a51136ac934563560c61752b1713f88310e2c50027d916788d13004199c815fe7cd468f5b726f269d739d9caccfe5407e41f4a39bbe27b73695a5e583e2899b167286aef530ffcf77a4687cfdaff1c26ae78d5b89cbfbedba850cf8b8713ed4a9d4339085034846fd10ce1b0078bd092351a0a77d1e7e21556d97dfc53d81f17c5a44eb021c430e793a96257d6f1c5691682794b12333e74892e7936950aba110930462ec722abbd18d911bdb38cf1cd88d1ee9ea1661e7dfcef525f2d543e804226cf97a722137a1a7b6e65981863f7a4d6841998804d079d091b63cf577adc8a1d3617e21427164b88a2fa2cbff6989ee9684d9f50482663ec1c1926dd29837d6d48981722d007bc7243e2a58d50a80d804479e1335388c1b22b51bcdda20545483e3194b7da27d729301c465f8e640c1d047a9182b86a662ceb2d9d5506a4761cd514c720f5deae91667c76901760c7d29ba5e2111967f7deb3f49e55c179393b44d7c72d9ca2981e5a353514cf2065522da063e3454d257e11ea91d4627e20511010672a509fa60f800c5b6f844f0c095bbce8f68f9616da365f2b6631bef4c207f50721e1794c932792caeae3b4500fd208ae5d763f1c6187fcea67313d95da3490c6679699177705e2fcf98634204f7849bea1f0f50b706e40fd6985a5d79a7150f72cd0e261882c0929d29da3e6bf8ff4836101feeb0783e7211d111c3d4884fc066fbe968abc383cd7a5717b4b409da1caed2a523d5382c3a0435a96de85d621f728fe3ef143192cc4b749762eb03b7ca8a728e997aa4d90a4c371be7d12f4bcf72439e207d3be6507807481c1af9955c595669eae2847db098b182b229513ad772307e46b4c5422b152f8a4098fd4d90af77df8bd9ad400fe97189ba93fb7827720dac74974aa5d3146f6c00b2cc9a4cedb111d8ac6c1cf712cc015999b19826729e73a485a7aa85b75d6d8b98557f0e0775b01b37ab64ee7654b746ff01060b094b9e9d3fc270605e4f9cc4d2f1e664e5e949e00d760eff86f99a0970d15fb47218f42c85785da34c7be563d242da35dbd930dce0b77c81e8efc53031215ecc7265059b59d7bdac725129aa4b672edfecd26832979ca87d7773466fb6e3d7706ff8d66fb7a5dea693b817f8280d0d2a04f167747e67b10b31590067b0c688a129b16ef8830c40c17d2fbd0b38dd6ed6220c37141ba7f0044fa33b7853ad33ee31a686056ab7a5f10e0e93f9cf2dab6ec07dd40ade91bd396549d8f5783143c54703b6444e52bef635ab653f920a9d974858a9d1ae96e9310132179bb48371ca2512e8985b9eda524eaba6a2e82709bca16f956de5b65d75c969008f8ed846c0722301ac4afd78b6b1813c43a4c52da0b0b93b30099ea052e2d8a8a1bd2b68c944aeafcddf36e6a947817ca53bf088ffafb12446fa94203248fe3d94f665637d72b11a068d51dd4b4e04009b7e20b36d369695e21d75458ba44bb3192d6bb62d724110c937ef9bb33b66635467206c63ec26df85400628f64c5fd5995a7bb5213035f9be63a654fe151b5039b319cbb2a55af5c48ba6c3a75443bb2b4dba8c977290ae2d5efe6a536ee0b20a2c9e460406c7c9629743773740109ef9ce0f1ab4311d47037f4e88f49f6754fe20b89b4a526239202f573d0604b6a657b6cfe134723f50022469710f623518e4d6a32aadfe4180ba1c8831ca2546064c9d8dfef772cce66aeca9322aa2d1a02b9f2950b8b8d157b4cd3e86fd2b9070203b22bb47384dc4126ab05b2447d2545a1bbe058af3458422139e0243840d76f96187b09c6174224a6874d6a0601224b34ea0630507454736a54c0f1a119a85430e869bca72c27c857333735bd9641fe38c8fd943632c624cbbb8ee0c0de31e49d078586472c1a1a875795ac085f62b0caf2fa865f162c31c153b1989f69f7cc8ef0b8723728ea997b39de5322131b289694b5d477635193a7b9be8d17b30cd22a91f10fe25965d52b0a8a531baeef4d0984b7488cf6ba929152783c4197566bbd774a7a535ce2341daba32c03a29f7970bf776f3f1fc0cbc52ef2153f59c528c094a40cb720c5ae2f12167333b702ee0a793a95244367e604796711bf1c5065fa2f49a9a33052188a6ae5ffd4c3737d5aacb637e6474905da6d7a9d399fc67b090d4cfe068166b1cae2d97c2916f4baba610bf0983290b8a8fa2524dbd8cbb223db7d31c723da79a23e2fd2157d96106fc6382b87d9ff58592703130c1f10ed6fb03cf632ba6096a61466157026877228b6077815852ad79c7694efcd32dd5883d9d7210729b5ccbb8308016863b9ceb3a72e8b91123208f913f6a45cead5da608b256d953c448bba3ab09a00f431ae8bef196103e2d5cb4b38ea2e02f1fcd409fd8fee872ffb551231a3ac24531c25e92b88598e9bc2aa07459b877417b2bc62dac414b47f7853bd9aef612bcaa6a17f774a4510aa98425c23baa2e36dee41117230ade7208c9d3aba6f7eea5474795d4882fa7e0b1b33821dae86061c7c10bd0dfa37a72a6a7ef15a97787af2a2078964e2b80c3342a15e3636560898500eb0953566b1eee4317415651c78318c85f152ab498b8e705638b15c43c570c6b08f781c5d9534faf974a00946f929127c3f61bc45b7b3e08cb986b676066d3f6749dac3ddc65d89c7b8038fd4402cc2e714895ad20eae1f83d7e6b387eeeb61df4fb150d8a72b6d1a398c08f5ee124a9233a047260348b2e6184f98c500df2b8f24bcbf37c729b3ebf0ae6e065a3af6b516fa206c68cecc6e5546c74ab461c45c682421e402294ca636eaa3a824e04563466f577310187f1952795d7ed5c49cc2814dcd4ee474ad28d12cac68ed5c6491b383f488b2037af5d77f9039c45dd4f9e6fcafbe8509224c2839ec70fcd89fbbdbbe271e8be28fd15ac92d06693b66c89df6b38b57249a6ba7bb8711147586363c39786a707d06b6bdebfe01f3cc40c0162b385390686fbe7da33e8c1bf1e6ad55c3e5226777ac6ddf1fbe1164f8b01848294fccc700bfadbedcf209dc9a570e18e2726708e8d66adf9b4a435f800894ecc07578625ab67edc8e2aa1e62cf8ffbdcb126a486e99de817145596dea86ba049ad192c7243f3a71da8d82fdd85ec400eb69460d522db80fc0a4f37b1bd9cf58e603cea643f5cf1683b7a598cb3a3b2a73b717e156b24dfa454a14d8b1770d170f07b365491149b9c49df457c6731964d7acf29f283cccab40d7ca9ae725a5222b1988c721568c3c8a63adce248df18192ad6384cfa248e143458ee11fa33ec1f4f1ae272c4dbf11a8cf50b2ab1735af5bc4fd276a8f55ee4161812ca956f994a78972672486c387e97279fac5c345e153ebbfcc28fee4b39727d5e31b3a196f491fe3566677e91b0527aec137b63f70bca9ceaa05f18f4f297aeab8afa3d13508d3bec7211d9679a7edf663a3a4fabb8859d95ad130834a01b0241642e89cafc623c830583202c7072981ac2950e8f6f5d6e0ccccd593e65fb26f82bb569cc3dc73d20725b63773d2e70a14457ecbdb6405fe794c74d868fc628dde554157bda8676ee72764ecb0da528bd8ebffb2955088b8379e66f37b860ff167a98fbea3399da0372a85ec67a96c38e20c9be87bfadb038852d995c7b4985a0de1ae95cb1b4379c728246d32fa6b2a3908592f3f5eaba276e137e161a3c26325b43a9838a361989721051476e7e3e1ac46336d022c7499f26d562e6d8fbb75c337aeaa78eeb97ad7241670307d6f3ac18455a7f71391c63a7cee7b7279ead4ba4c9976623ce75f6727d0b8f378018834ebee9e098680ba9c26a54468066d5ab3c51d5cbe194a51f72bf79036443982d244c17c416615596909fba6351ebe6eb99bbdcc9a52c98fd72b0869d99f220c3825372584ac470b3944d698d0798c83724797b5de015354072088610d7a78dbe4f025b40988c31f32ecbb20cd77ef484db45e4f08f3ed477720dbcaf0975e1856dadb3debd1ab0c1916add903f64c5eadcf69e785307b6fa72df4d94799e401ff4410aaffc9d67c29c3334e34c7e5cb47d2bc99cf0ad29de2ef5a0b101f6bccdb29b76fe248e614750701e97d830b6d0383d920714efc805727aebcf96ff8cc8076b110959b1ab633fc8489f6f138cc310cff1634e98dffe72a719bdb9c615dbf3001e7b3dd587577840005ac9d6a608c41d5730c3db8cba72e4f2e20ecdb8a6f53f80ccace45dbaf7fc38603f9e908937d94462b0b0f1ba314040a4cf1b6606579c7bef440ae46b55ca566f7dcf699445619ea6e69f83b2726d33ef07159504db32b58c2ede4151de2a2cc9254cf1afd30a7eee6b5c4e1b727a8d5e1c4ab45ad183eb000f40f07684556a7abd8f2206f3569aa4e7b62299522e93a5462eec600961be30362b2446e516f21ec00b790af4ea6ec848ea8cb025e15baa93e40a10ef951627858909c8b822d851d4d3e9ebc183de631b8b8ee072fc7d6a3e21e3d2ec082df420b4278aa8bdeff1a35a39614f2a2260c7d977927278d32e0093fc3ce486da15efc2443c20f394b4441123de79baaee2ff6537b1345c1af69dc9232041157dd06d54912b6fb96cc9c01c9087a5d35cec0a714e8b72d31f9687905c65435e3531c2a65fe489e6a6f144c46659b6dd331c3691d6ce72f8b28a08a5192143de1234ce2daa064044e9db5e756d4bb0fc68cf1836859517f2b0c68bdd75e1b882bcd5edc546f4a54866365a166e9ba338c735420acca11534c392174ae60c92e22cdc78fb728383c7225a2ad77a4fdb7afdb61952f33b72fb9cf8701b3a96c835b6d9e9eae176bab04885ca10efb1334db6440801b42c7230803f0668b906c765e225fdcfda340718ae5840ec97dc3aa55f59639df5021e814bc183ce9b168c50a7f1ee6ee20595babfc4e6244f69fbf244357b1de9f272766b6486e7b972805142f03e160cd51a66c2bc7fccbc19565eb9aea352d06072aefb404eca65e6904fe73ca8638a0f2427b29ae7538ed5148ac4f4f36246b87202be2fdbd0f2b3d39781dd78409ad7d68926060b3b33a080e2d5a63459ecca13399712503e44993fee65604789ebdb90b8c73ac34091bd94da84b7c74905f411826f489af6831d24c11d736aebd607d9bd25c72ae4278740d080befb8bd4f172e0efd29ea35d40628c1e42c297058f47176ef25336384055fae50fb317220062592441963f22fdbb537550c5595f0f338fc8c903e8d1d413691088827b581072950609cd8796e7dbf10dc9e15ce898edcbe193461429fcf394fe5dde2010f5729144e903046f5a2368b312af945e114ca2aaeca73952f58d7dbc2542eecea9317e6f3ae68c35b48baa0bd4d652438ad565ccdb3d3f9fb61062ee2fd8052bb372f810b4ec460d45d95ce571cbc1e97fedb81bcb8ac8de3f628bbadba9d500d3728ab8e641d2b61f2ec6090e5632f5dace1566d39366dde6c1516c8cedb30cc572733da11f6d5df0932b71ccd2737c35fb55038fd24a6b0935ac95a768062edf6cf34d4a1087031033a77fcae22fcc40a84fd2d843d95e6f932757fe900d8ee772eda238ff3fee8881bc7ec9b7b83e15d66012712423a74a993ad7730817154472d0e1239772a73522d3451d25f6918c4e08ebb82033c553a77eae199f01a0320b6732cf1a4b9bb204264b6332601a648d9546787392225f8157937ddd44467a42333d70cc16a0a9afab18dafbadb9a3388deef3e4dfa4ed00dbdcb23c21e98e72954f1e37acb8b6a9bf07a83f9342cce72c772786bd17ff66bf09a38502170f72aa19542540d71472677baedf454d3b782fd84c24517502718dabbc53090d2e72bfab666bfde16968e3c52fd73c276b77334a7fae05e19b7829c741678be5d472c4144cbb5b97a1b119980c98baf03a56137d7826a52811c6ceafd128678f247292fa8cc339af6f2b10ea780dcf15dbe6cd255b19113a6ffe9163e5e269291572203188e07704552eca771704948af7f4e991a2911c37c8d9a28126df13cd817283c9b44dd0bf2b9098bd7b11fdc34d23a1f9f75aaef843d0e7cb9ea5da10c9728cb83e30f42e76c3cbffc555383355be1292499c0be660a662fd1a6b3d548f1eeafd11089e8dc5907e558107cb6d6ccb9306e9ca4a3d2eef77d8544199e39a72f15c0a8b516a85d28007fa5ddedc37e663c6390919165fb75009a78e837c237246dd5d069962e5d51b5b41cf6a903bde7bba18aa4cceb6f8eff1b8826570c67239400fcb4346b1795d5ecb52f3d3dfc805da9c3e5b72d5206846df5322252f72f251fc747a5bd822716ab5d500ba8b5486dc9a8a08d7d1cf99ea84209db57a72e95742ce1ad1f0a1b013f89e11d353c2508c4f377eea492bf0ac4c3823d24817ce7ddcd3a49074fe3a43bde4c8703cb6029c3c19253d38881b31eb02c249a472674427ec6a48c6a4c7c8395dda23321b1e850261cada4e0db990428259c5196b2a8c49d914955817baee730db9292a3d8c322513d465f520ad7b5613c8812c72630c21c365919e6a3145b37ff5bb4dae04afc761435b3416ab69cd66a0432c7292a76ce8a6f5d778347f8b85f6cbac06763441270df67c5cc2c8295d6818ce2e2fb04d509afe0d38cfe75d549ea3ff85756290b2169fca2aaa753de0fd2b2072f0419f1d8cf7a4bee94f8ddd9901764f5d4c92d3872fefc659877f3d6389b0039a43009d446db9754aa3e138c9b81a68286292a71e852944bda8d9471d3d2372d69a458dcb2f42dd48f7eadf785ab0e0985570a97f5d86cb47b2aa59fa9b307230bc1151965ec07c94bc3da6d12c309c107c0bd326800496fe922b53ce2724431552020eba84e8922f8277ca1204242fde201357eec6f4a7a654307e87709572589b8901710227b6d8a20305d3994587d8630bd8aceb258605c58be0839b50698e24a6d51c885457b26b3d909a5234aa6e78c86aef67d1edd3292f35efb1847274c3e7a6a306707187decc38c41cd3ea888f006bd0935e1ca2062e51dcb7e56d433613c953084856c3b97893493be913787ae92c242d463fb931166642162a728c2462c790c41ac0480170e6cff025458da72571d0778c98f4a8811d73c08a728ac39637d61ffc722d4868cba8e5ba0b3ef187ca6539a1f8d478ad1bd6ab295a5b8b711087d99082523b14a305b6b72d06540c6c96fb41ec655bf13bba619211e3570a644ca8c6fe0d83d195090a3a4236a0e7dd2cffa6aa76ddcc9eb52ca9726a7218b024d46ca8bd98292e94d433a239907995e120bce202b017eaef27dc7258e70d5d8af70938df7a8d6d5366ad79a847d21e53e4d7344d9b6fd693e0746a28e8f863fcc733ae94b89b170fbf26530477f2be6cef96d4f9491debf8689d346e2c867a26f3c56e10955425a481a9f4d9d190516eea829b7e80c72419d5fc2b77cbcd187d2899929d319941c4df0e455edb0c58c599666d63d781c605964772d1ea36dec1586d5f5907088587390f289091cd6eefab633daff893be63396772e0b43a91868bbaee891e8b842c4d8d419104fdc5aed57cb65be4c0a1bc897572aa6d6f8c0582e52934acf1437aa1142b9ee3070f0811594801e6fc8052ee55729e8786b9efcaab79fec61ff346bde50952b06e758852a84c319421e2ec9eb83c87969461143dcd1ff233a11622c794075821702369b9065552a97a29f41f3172a103bb53d075fbeaaa7cac7708259bdd5eb61a9ef224fa0d29f10effb28b9c3090c3bf8218734809dd772ffcc5a023b649d10e6086be5969a31b8664434c6d5deeb07bf105ca37a94239531af5a7cd0b2a978cdeb65d02bee703e7a3e414d86aaa1efc9b8f946012614d80b500a169258a90962e71fb57820b5ded8b41aada72c63f689c5bc5bcae50b7294cbb561d178ba9e6ec1fe1c312198e740f1f095872fa84bee1a41ad310c043b4272800dce78ffc634104bf785fdeaa775211590665fd91e9a548320a4e7ef30e384abbf0158907226e7bda97dd23f1ecd4a7055d6ef0f8b89fce035d63c7e2c8908d987baa359b43de2d20b853bade57e3654bad722c5012a9b356f602440ed7f8c1465cb0e3fbb7072288b93e9f9f540dc10ca83db0ace07ac546051e9bb6fe4a3e7588da4b5150421d582b5b2ee2abaf321fb9726950a217a7d6224c23b0cf061b4fc74b19a2ebff222c6a4c0854a9b1cba666720feb2710ca576b02b8f2b8af44ad98a7c3ec0bdf1449e3294c3da77703639a7207534c4982415c4d32151f2dea29abfdd092dd7a924c6f4b4cf58750f468820b3d90812bb712cb816b395842a39dfed41dc7816ebd70a35725a702728a2572723e9284df0a2a10adab8d8eb0d580d13033bb645879e6ca461fc09fe564d76c72aea8f19f6c625b1384b651ec31bbadf8feaefc3acc8ea4417ec917d6724e6b513c32eb5c0f34242b90649c7608bdbd45114b88d247a5754c41c290ec9a4bdf727d71424222b5611d2933db1e71e775a9739e5a6dcc9f8b9be8921e1436c13a5e7fd8d0c287f305fdf879e48160821a4609250d540cf3a04df131bf051f3c6c729fd98e8f2b1520196c75005702507a53cd5d76a0c412b26aa2f871b307b8637236b95a06970ce0fb1fe9c880037ce5807857ae604762be36521ae0b5932b78123a90c4da96530ad782dc73fc334c9e1561501612142a6377acddcc2885c03e722f0e35a4b9a0b6121eab0c288c1bc4f34761df6a90f590771c033c939094ce11a1484cd1cb1dbfc03267030555359f952c552a312e92f3e5205d26a988a74f11e2cb4c533fcbcd3dc923e6bc86e55fe521a77e9b200f3a151b0c8d8d83dcdb727b21524ef406640089d7fe3e62b3dfef5807119a8e40137c57b70d2f3d9a091b11aedb781ce01b612f160960a0b4642bc56d4dafee4309ed1b1f2eaa6468ea727b2032a112ae65ffa35d0c7c1b259aeaee7e5dcaa3b262085d61b7f87251e672ecc4c12a0679afdd0f4eb417c24611c222ea66ca100e14142ce5d192d288e05bfc768cd59981b4ab7de2735208536a599e4ea5a027223dbc222af2f4f52ebd169ff84f702a8f1e10e30e5ea917e9a484a20c4d38e623df11b9f79e6520fc1272be330f1f709be40bc3186a88c8a906932cd6d579fcad9d179122fc2f242400726af8b9e3071bf9be999c865e56fd1f5dcc3caef749d7da5c8de9e41e4669eb02f1d7bdfaeef764a30f7248100aaa106b7c25abf71efe270fad7d440acf5a6e72b0a89f3f51be51c68864e6ccd015031ef99012ff89c9b565bfc6d47e25ab2d72077ca1c4582b105db1635f63ccb986bc4b6370560741e3bb982f3f47a62dd972e988b82f8d6a8bdea96f6e4f6328df2731109892475332429a14ee882c22b64f1387ef108aa11ef3028af4790a6a0eeb2c902dd50cf95bb810e0f048c88a427204cb93661699d09ac68b4f0212d0280b45bac53e4f65c42373739e24b7b7817237f5997904bef5032be4d77f7073a4db07ca1361b8bd0ac0b4224b10f159ee63f59fc49f7c6e439d5b211d195ebd2e9c66e1794e723c12f4d93511c7fe177954fc19a705b7fbab43d697af1624e735c80f3e2a4daace0292a0286b87e9da3f72aaaf3e7c3454f34e6f7b01562dd8cf831e5556186331e12a32ada8c36a0899721c11c0be754b1b978782eb5f03c573fa530adfe2f60a0d68acbd0f9954b52f41efd63a0c183bcb6e39ed624e23b75b3c805b35280762bfe53ba68811235fe5720e22591eb0b295b08a310ff58176675f382c78b65ed96873c5e09822d8e24b72ea766a494b54a3b9bc2925b52fe85c6cce842a6b982526a4ad9eecc76341ce722615719ccee6577cb0100b04ec904154cc68a8997ee833d1236eb2dd0bc3b572fd09daa1499edde939abaa5665499083ac4b33a2ae1e0726f6eeb0979eb73564c54c246702ab7162ed3751a480f08ed36581f95769ee2f72d4f989225a333e6b1b31fe3183ee113984c050566bb52106076e70bd3b7449e0c60db8886c847f6b5fc9dd4ae474ed6a47288835633bd9f8db199f2aa8dd438e91951dcdbb3ffe619b5d2404536b1980e8ada339261a37f5a36a38464178c3944dbccee28b19e6722e6dd9c015b1506a67419c9efd519800ec03307e1dc7616d8007e12d0f0ff813bc765e16a1f7c6fd08352a59f7de4b356e877df24765243d150b2f7efcbc4972794648506b65d34edab81b4821d75816496d82f7308e2b60b2f6bf90c891ca72a2b9a6d36bee72cff35c3a5f448d5eafed7bd897bc922faa2e4dc27604030437d54226e0dd1044db624236bf807bfa9fb930c174fd7fa9f9e6986f3f482d9f72f362e3f1525a3f5183fc828a26221b4114a2287f89f6e834be1f2a9d85a56c01459863b3e89675ca73d24c9a39e84ff2806a841f3de2924e1e676e45548118728291eacab1c9b3a452998ca3ebf8bf28912015257bbb58c468584ee724332a72dac87f8716589e1ce1b73fabd3d6780f87629261e3e6d2c491f54cc5850c7a728f54fc178efc64df40cc1a576ddaac9953900ee1cd855a1d311a3d980048552669e9f1543f638ac3accb37699c62a9e75110cb72e437c31f8be98686f1503c722fa1894a13a63de24d90548e2ba4efd63b13e150c390b99856655e20d771157264752b0e58be69091016b11dda23c604d575e3488bc185cc9386fa206c66697273119a096cbcc9614b9ec976ab1c9baeede7e66c428adf9caa292919ed3432620bf72dfb66e085ec9e7bc4e69ddaab43bb03dcf88ecbe5758d3c9d348b51c7158fabfa67bc498166bc501fe2cb321f22b61fd5f20dfb6aeccc61e87ba9d9fd72feba75714b77470454e6f45314b46a6b415a76867f1a7fa7c23e9e89b378821525df8766c5c9e0c5c8e8c65bd9dae1052e5d2462c5c28b9559c80b6164550e7217308fdbcad1ac0f0e5654c3560192c74f9a9296e4b57f2a44f20da62196ec72f1fc76708a519e1d186ad11f5d80a26d0ed253bd19722b694c516e626441a24746e00f5dcfcb50973eb53bac6f1c4b5b3c5d12a46db9fa8bcd464d1e3416820f59814dcbbde9f25659abbb2b1b99afe4238277e1ea0a42836ad073c0ae669172fe4619e5d695b40c4759bbb6f59eb52716dbfccad5f26ba386ca749a767d9872a7c19e9041decbcf7ceb39d05278ebc9a84eeda0ea1903eb7c32d2e612adc472955d53314178b30cf56553c2d6c9f3a71a45c12e2b0c5316f9665cb468bc16721d95c77f59ab2e311b6a6e38b1020fc029f2e92be94a025323a855c3acc1557243942bbe5a9b99802a33a8d12493ae45f50c4454c10a0f653fff5142db545372b9310336b309b2b3369eb74f8d4e8e92124106d8d620be8b048f557faf487c72f426442afe92936e77efe623a4206c4d66caa3316708bc36dac7199a43a79472d173f4d4a19c4886b4414e87a043f32e6f8b32c4dfbe06962300e13385147d727ed67407aacee94430792a841a5aae32ee25aa44f1bf4d8027cb6fafd111f2725677a52199ffe48cb63af1d138a8b1aa6b1d63b3f20717079fcb5b85597a517237b55d51f6a54ccaacf7ac14016d2c03e5aadd4f775e14f9f0a3b35d8ba54472c9373e66b8fad384a29c8d236deed6b824ed27dd60ccdffdd1ed053af20c261ed0776a0fb9602f57a3e6ff12846698774c62f14a1c7740ef505c7addf16f3a4653ffdc8c0c8b37b70d69e2e64b4117ecf3df61edb7547b7b5832d046a18953014149295cd69a353d0bd1b48dd0f3da3590bb544d82180ee417abe6ec18d46372b8c96790a02ec257429b7293eb1832d4813f05587001ad07e0c4eb3791c672185cc93b3ddd01dbe4b86a9361dcbb46a1db765d89bbceaa5449c4ca02dd2fab1cb77493c386c09da16682fc6b7416ed0761f7ed8dfc5778c892d0224758035e4f4452c55ce09ed6de5539c6860b5d4623182200fedd2c451ae631f3375817e6725d8a2073d07c0a88c17cfa7721dbdfb10fc7de93ecff54f98456e5cf246e57443997de366f9bd33b7b4600671a044582ef060d5dd2f4ae254c1e29fd0eb4257204f16aa0f5f3e1c72eb0d0551d6cf6c2c5970ac17e1593d2f6357a00ff45dd72313712b4c0992af59f0c3c264013f6fbb271f6133489255708e309bc3988bb322dd97acf3cce6b3eac89b17564c00c5bc5e695b1bddc997dea000c67b57d051eab9e24cbbc8cb98721754a6202ff4ac9bdef6bb3aea98b96b8bfecb023b110321eac8be8948a325b0d0c147e0c3b3cebf1eefc845c5e77ddfbdd503006867372a70ca7010b805e96ad7e04b041d06409920879001c5f2899bcd0bea5d01362728f2dd7ff7cbd71cc9b97193a0de0bba35cc72377c1553603891e868596a9051e3de57090f11fd1f9734ec84e6580028ab6015508c9d0237fa14cf576ce3c196724c0dc1542c0c881a3fe8723cff9272496a33128cca3808560294cccab7a525fc00c3fc44a1ab9a98f61a10c25a9057d5115804acc7cd6359ebcaa860b8ce3724336d2cddd94024b06a1fd99e8248170fcf14bb46a43459f5ffbcdf05b632f72a8c1883bc0bdc5008df585ee4e6fc88f622db12124f98fbaee32dbbc937d5572e935514d54a09da746079afd282a7eb85219b4f1af34f9fb4905d3434ff00372021438ffb6b8918d40362275b9326af35f3b73c51ee259730af593d443e4a40bbccc89dacc426cd43060bbdf961e293d2d01ddbab6ba873b501d942786a770101d83bab0c228fa03525e08b7d236b091206ec063e62aade730c3c54167902303281f6a6a5003f712341e674bdf330b814d8bd708aa39247ca1131bd29541a472811c8b113dc86b7c533c8cfdf93756b78c6d0c2fe73b6080bcd09fe47749b17269ed1811ffc2c4486b81fcf5f525cdb4915c8650e7ef9f9344dbdb281098c30271db7001e75c1dd26fa95acc7bda9bea052c2e817f329af7d4f92c359cc2c21add40f36104613658f999ed9df39e9f30d7783cbccb93b07c7d3d0ddd5caddd72fdb7b85bac8c7f297d4df6a302ef365aae44d3fae5e5ff9350549f6bc193761b039a35392d0bd08d60817254023fceae823760f47d127702391147288bd900728c37e130d3ecf5599a5fc819d441da619b8d9cdc66995953d287df82eefaed72e3d9efb73147b3db9a402eb38a2fce2376601c2be3bb8feb5dc05f4bf8a9277258fd78a70ca79e4c0580d4d55d6e5f78b3155a1635e9aeeb9c732afebe025172b52bd619026efa9ccc5b8a7eaea69a13fe60c30d0c0ce6effcd9e6c0f6c0c74c14fcb379ff4c2020da89c130172d9e5dbfb66d27cf57e0ba7b7a8a58ce57357219086a9543b38cf98f1b238c0f061541fbb4879ba1db709e4d4329dfc4c75c4574242102bc663c4b28a64fba98e2c5e41a6ea8fbc0f88fd173c821e4195bd27295b860fa439d85c565b7caf699b4bed72f5b4ef22f0c9fdaccbde75f63e3ba7274069729f29cb2d86433e281e7064c354a88c7a5f84708ddf3924716ab185411b3793509961f6a2017af3238883fb7abb50fe873bf13bdc958697e153c224f15a00e9eb0ecfbbe730ae4a65b2590c8e7790702decf9c77367cd3fd6cd43dbb721f6a984f891aa7207c8f358530bff102ea654a8d7040d6cce678d381ca659b723fd08d95d553c4e22a84034e65bfd3909095cc2aed63fbec1e3d1da471bf5d72a0bddbb93c6ab5148256de1cc4d4b96c73fa8d50ee4a91dbaae48ede34fadf72f1b6ac523bf47b2184bc4a298df89d0aaf76ede091da03fb4d6a1ce85b284c07e6880d1ed99440ae7b2c2701a9ccd854ce17b3c9fb02f12768b457fe5885de723ab0798c368d7827e2541bcd79ac20e910c663bbce7226004d5db1c4eec881371ef8a3c22606f145a5350c421bdef7e1a88f5ee9b5f51a4d65a9cd4da401610d2cbf681a436da7530d95226dcc0f97bb89bf8428acc3331d0cb59fd9f8b717728e94cec36dc9bcb7b26b6ca56f8156762967604ad8104d165553b820f617651fb6ffff0c82ba17d66bef413db15843dc7a557f874fb064ae79800bd560cb127211269ecbbc95db4125ab03686e94be54c4b1ebbc1e2db1c51ac3fba2484517728df7b3aa5741f86f8f5266fb95dcf29bb35221e558d6bb8052586eb5dc6d5772380a173035b7992f3cbbaa14bdeb4d46c9664c06157d5ed4ca9862310c48817274968aca270d2b0807c4aae9547da3d9a788787dd492ae6a4aaf7551f65f5019f4bbfd355c7eaecc3d369bb8dea2e921ddf257160bb9fd8a8c0e07057a989e724adf2fb456020b30143e15f85f5c8510724cdb790e5b68aab0423b03c9d0a25590765b771b9489b836e280775f9a2e5280fb1cf64067c778560e8a5377f1180631527f409b313305df7abafa89bd74bae1766afe34d2f5864890504f4694930b335b0d70b2dca71c1fe3edb047cad0a9e92afcbe4bc3fca0a98a469e1b330f72f4c17c360cf8e97cb75895a835acd57a24428eecc13b0b64b2ea834796daf9722ae35d35a37928435e7331fe939f5f2387bd481eecf7eb9ec52f0c0368220902331f16ed127a5952c626164d58a5088d2c84216385d5fb7337814c48258733723bd47584b069065f37a506d97e0109bd40248104d3fe37b332cc9ee47b4bb23f30d15e1a8b35dcf17c5df6e6433cc4ea9cb9fcea9efdf1b3375d4636c259e37248398346440f44d4100afcc4fe831f0abfcd67703a9e4992a11faa8237c71f72126723050853bac27db1fe0eec525416c01d6388396e1746edcff2d0391853723df7c82cb1d6699ffe391624b5f42afcf6ce9385c749af97598bfa7bbc4429199e1cf887fc331a39c22edfe6e2f5b94701ae5b2d8da7824111e59cc2ebe0987275217ee02f4953777b89247bdf27321814e1d3eb2d7f46f8e779d654cedca62c37ae28a25e6a0d80e794718d54a332cdffdf00895f08e9b30e95897c25ed3c725818fe11c06602dd4c9251ff9b2e05c616f093c2e79db7509e4ea66304fd8907d902367c2c229e93ef331bf23d60b39f4bd91afb805514f64674e3595e69632c2e9e2ce0269999164d2dce4d7982a94bc1ab79ec8a2e35508d26d15c98b4d972b9516715023593919339d71e7ed9379736db02ee31dcdf5ecb7ac60fd25dea6591ce37aa704658f943c3e51fe26e1c5bd61ff110a847e161d7b16021e281b472964df026edfec24718ba117af7211d0e4cee655cf728aad42653164eede26972997e408d81618950c2ec1e392406b3c934a56beba4362afe7374a687591d372910a7263bb4ee24fe00d292df76a0c24d7170702580b0ef5cf9ea806ef14ff1728e91305e3619057719ab21042b087bd4f8328b1f2b5366bb31732298b607de38b6dd215b1ee512f40d04b6c8c55d6a5ae3561fda9ed9077af71aa1ab53abd6727c55634d9b0f32cf87778dcc08b919b49ffa95aa6d68428eac297ec951c67662882f73973a275f29fef2fe0fb87afbbfd07eb3a8a51e098d9b1514d244045036bde77fd134834b0d666946bf8c39085ad25feec5fe280418fca2ec5aa1dcf3727954d5e8ee33a2dd2779188de870bd8afe17a3ea4e7702bcc35c7419d00bc435472e553542f707bd4811feceec517239f5eb97cbae5099624b65be31f1ba0b72294091c731b901d61e0bf24565cfb27b8fbfe6fc61f1607d7828346052ec8672d8fd12a34b9260d7b3fd2231f0d8beb835c2523c0c20af3cfaa5ce5a039714723498928dbb2084e778fdc02484e5364bcab314d9dd006f4ff693364294e97772c7cb7b68efcf9014b7363d35f266acb43205ff4c4291021e05714cca13069d72effdee103311db6ef6bcc7cb4b42a6ae9ec27e0bfb9d38283f660827e40693580c2388e260f152531d7290375d2078878ad3bac424817eaced25b3550402ce72c63ddb92a1077db414eacd2a54c70b75d61edc0986327a17ea8d69e427312b72f2e153ca906538611f47aac29ec464c53ee04c63b8077d505aa7597650854172df32bb79b971ac9cbe2417c07ccd7e09e2a6af7fd4eedd0eb642d9a1aa4fec72d8ae7ebf3dff873b8b3ffa1a9486edd2121a37293fdae13390cee82d12c340721af8fdd7c45848c40fb69b64362529f6d0c677a3ac2bb9a89e2d14976ccd0372600070adf2306a8a8aa428e0ded69ee391d6bde151e4a5009a8b2d4f2707996939688065ac90455e50cbbf6ca24bde563320562c7ea2f300797bb847fe996e599390d29f81d766d044ecc6055201d82535c4b11e635a3d54ed99fe3b96f962726226029ab4cc6aff56f5a00b3358291b20568028acfddf42d3dde958c2fb02725a41bd17843c851c89fb86d3e992c8ca8585f8c8b7e1333d763152cec55d71003d61bf4d76c37eedcb405e2ec03e7eb8e1cc722107d57ad7e62a1a0481d74d62001ed00651d8a92957df2817d973d505722337d96c92b0166efe88e22d32c9504699c81859e2d55f2a0866a4e8c38d40388526a3db6f7ad1b515f118f99a567295d8472ac059752c294fdc28ef12cfeec39291a8f32a5533ecacd5af5b39de72bd1310a91968819b5c8826f271a043a0e83fa545c2a267c1d167b72649c46a7287fce212ff191a14c073d327b1c72ab06124f7cf577982a45de3e8fd09b0567272bf51f8b5c73acc46085a4f81ca10f2101046f0e63abeaca54a2aab2e588c726a82be5d8415d500a2f77b8a6651ad9ba83db3ec55aee06cf308bd9bede58c1cd327e7fccf9d58fbd1bae41b0a5cc62adbe62b2de91d627ebe0a1291b2a050723bb7873c39b6c343d2265b16b687c2662c5427342d2ed87842071f5585fb98722c0f2f685802f75aefc54bb9f65471432c8c4d3f96b9341cbb127dcfd0c27e13e79656cf536709a9f3b3e16f9e2782625ba57aa1202d1c9d1bb6b297d0707a7253f29e10b8c632ba67713c8a148fdca4a975ebe0b30bff2589fdb6697543f472d380cde2f58c976a39b8a4cd413730f2a7206eb0d84df76690b2cae950bdb772dcf13b42b850a7675d735c090cae4baa92f540ed0bdeb7a979439199efb82839b46cf7191d211fa6ddd52c288a64abd6f36604fa77615a4c84f04f223bd49c6fd89cc4db388fc4ea09c4845ed866a9526ca29cfe2d8d8d903d5204e352f63472b6ad85b37464c529b5c7284d15d5fb9e26f0b475b510e65a3640092d0ff762729dae411f35035d3bfcbcf65684cf13007a7264fd4153dd6f9353a97232bca67207758c32db41f36b0f6eb0c0e302e0ab0ba9afa02d93ff00d8e62ae9a276ac7249f42710a2f7c508ae64f6f67058195d38948e79b13fd2eecd87dd5a22644c720c4908b089a8ef3f1847e3b5ce729211a364f9440dc8a250c0ed88ead4f8ca1fffa9b828c9cafe6c00c4168fb7c839b42cda47a48d38e447dbe692fbde01145dcc4e36959e2b7070363b152ff7190d268f8fdc437027ab9bae36bed99ce294277706dcc19057e0a909a4c96b7dcfe155db566741c156d16bfbc975ebe2ec3672afae9e4aa6b1b367b1bd9938e6e7986875b282b2806b652efb47e4d8c7bc1d72d8ffe26bec4d70fdaa747bcfe14dc973650c4ffacc9cff3823cfca207177f43fdc63edc503070924cd52e97472043410118427ae7432e5021d023694b2ea4272ed3a98043e794822260f3e7ce315199c65d52c27f274b0339fbaad679f1c9a703298c0b40b1b9d1dbfb41790a17078b179aa81b85e067964a05e2c9cb236b87288f8fb2f0795ffd107b8833a07034962a609e51ab2f408c5bae0bc950a9f6d6e9a9162fe237a433ef9ea38915afd46a828a4a95cb5b0d953edd5e01a4467d55b42abe7bcbbe872113589de8c99e3428f292c861346a9335874dae88e76a45c72f86245c672b877725e3349c917a16a5e50ae0d4a16d3a279054fbd1e4a45ca720603e703f349a4a93c0ffe8322e58c9435a56a76d200701cb7a0b9de1a12ec72800d23d2cacf1f648d68bc6052ca7ac6b69db4fe1bb904dcab578e8ed3624872662b8d29db1474b6799139c8758f08497e9768aae0bb9d78eb6eb7f32b3060720b0221f892587f977050aa48eab856b63b0030afe51af13334af103197b5e4729cd441576e1e9f66d5ffad11a50cfc40336ab61d29e29daf5f11482245974c0868c210e28d3fb2ae54d780f13734c11a3b54f7273ea74461bd70f57de6048072b7bc904a7cbc59f16cf8a5a900ecbc47240e23448741b159b7559a4f28865346e8c7f29932bc1af055f0fdde0fa40ab9d4c484a030d9595d882ad7277c72d8204e684edcf85886783d7e276116b8ce3d0f70f7e13fe5be23d4d87730848e10728a99e9a01dd1bd9ba2ee50aa999999a975ee02b07fe1716197ac7b54d25e434fe0a10cefd1f6d101b5b74109492aadeb6f93c45f046c3e61cea73d2eb835d772f0cfbec86b1aa309fffc960f1b421fb556613d702044e0e8c2f01eba3451b734358d6b6b7ccb433411baee457e32f9eec4f397b5bdba4a604b21fc2ffdcb24724eebc84384c4fe43e2755b3d795a512ad2d34fa7846c01f0aa35bdfaf5fb3e72e0aa3f3c98454f217aef4f1c5514838e133a61fbe9fb6fcc5255667e5fbfc9728430eb6ed36264db5ad5e94b24355ee60e5c11c512566b865a2bdf7ae9bcfc72a098b1f5ba9bb967782bd73dcdee702a10dc22192fea56b4900370e30bc3d44358be9843933479c1c031dc90584177d9c3a6a674699536fb27054a94c7936619c963e0c4d66e9799d0519c6c97d4ed0c7f52ccf6c8fbb51b726ebc16453c9872be2df92a56b0eefc82f38622187178cd2a8378da5ba7bb3aa3bd3f2d6245c472e6f652a57b775c0cc7f6b11a94f425c7e640026b0ada5febd48d7dafc82d46722f0a088999b1712e5178d66ac3bc4f410c471a09147173b3f6cf82d134a8b672e7a4a601948e153b4b58446ee233312725fdcc0daae8ae98f6694338b29ca62c9e6e7a8c4b820017a02afe1775c0dff008e6ff9fb6eda3e83dd84c4a68021225dd524c5f781ed7880ee709960908cb30061166d5cd33f2b3abd4227e21cc126688ae9e450397b50b5d422e1219445f2c7307dd522caa1670af2b6a26a6de203bfa69fcb0cbd31315871b2c38eb73445a735dcd8af6b80341cf0c2f9e552a4572e2a440ce0794f26b9a61768673890a458bf4980617b9f3f69deb35a35ecf8172c3a841b18aada4e3ff22f30bfbc0e06de1389c3f1a515b9dab3d418ecd0cca72943acf2c0c86575bcd35a653925b33844f83fdeb6e2f895bd0fd8253d31db5721d2e282ebda26f518844a0c1c753b4835d2e6e3b184a12b51337c254a206c272618b43164efc42e979d8d8369639d95d68e225a6f34c7972a4bb9591440adc72521db3c8cce4e9858a91dfa2b9c62a9229702258fc19a0f7fb0d90c53f6f7872eee07f1c3ce8ed1c0733c8f521940d0008f727ae025e95d546aa20ea548a5d725cd6b33dae0e438a11726c600743e7a27862e80a75a462d08b2fd8c6ba4f5072b26c4543a9d15d17884219c435219928226047c8847c10351ca5d9c99a8ad2722e70f4434d2df85af497365f77983a3a10b1db4c225bab8d5eac1d0e4f7381727e684a30a1cb020608e064c87c10da1509d399af5cd3cc0482e007d4a0836b38d154ebc58cfc72ca9a5f226bb05d93d3663335ef3d360743dbd6e0013689c9724ab3562872e27f4094f226732d6ede592fab19a1269ab312f3e89d30018415724107ae4796348b21e8e33ef79d9b4da9aaa0adf9ee2ca772bc1bf6b180b014248d93cfc617ac24ebfe7d6e8205d398bdaaa08dbd01683452af281dccfda0a80fecd8e4dc5dc5187aead618fefd00c369d11b2c25f2d57c7c7f458a09597e395eaba36b686ca9d082c2ede2c95323953b2cd69a0dc8ab2e3e534e96bd3a8213285a5e3ed71954a6643c5838f4b7eb8c28dbc8ff7d97c9bce9c36b12f540db0b2895758510275e115446d48568f8184b2bf88194b85101a01a31442f83bf7b8b2ee7877f6da5d76778b8c86266123cf8ce8c2e5f41cb7d4702ccbb4d284efa132973232f7861896eb5d2f55eb1e9eb2db579a87345171109d3cc17cda1e036a772e3d619adb13f3ca9db739817addb15dd4c36a5b4c39733f5da19cfe6476995723af640f25caa2bdae30d49b4139884616a75a2d30bd579dfc9a7f3e0b47f9f72cfd232738cc0fbb2e819f92bf75e5de432b0be65e1909bcd118cf7cc0ffe6f727199368a3a355b39f3a6960a7676bfab364e187a0617300ee111bf38caefb02f68de27f1878a85cd416561d7ca523d6b68fb71acf56cba28d198935bf585956b87b52d8f4c646b80870b73cd832adcf50b84e37be4c9ee6817a4fcfe3a8e29725aa58d5c3d3c709d5e116ae213c8dc047f365dcd87c7f5faaf07bf8dd78c1372ffb5b00584e1b8bd6842b8add4b01de4f2d4c7d3bf4abafa3cb702cc95ecb472d997ffd27c62dae2f770c27b728be650ccf4eda16fdd301ccaf08ba6cab1c0721029d0d8d2cef27bca1c5ff4f5c1bbf04c2e082c10daae2dc0181268c3e7a0210754798e0b99f2c9ad83d8658c22ea0cbcfe324b3366c94b889c01318c02cc5b84d7ffd1a808db969478b1b2168c86d67968f228055bd98810f977fccc89b3720722edf84a4cac14874cf0c79e620a0e06cfa3637ba2608c19f5b577179eaf2a7476e86b2e091e0e8874a13da080fbaa67322a4714452f5787fdcf8e8f98c01e133a348fea03c02fb9a5065c12bffc3ec8f6e8011e38da41452801f3452e5872a95d44e84ead7a5eff03354e4eaa28074faf30643d3ba292b429f523cb73977239d803483bd58f4d422529edfb80adcbc90994fade1cdf8cc426824bee23fd724b5db8f29f55fef413eeff5a39d74d1e0934048f5bb2a270a05e6958f83e28673f7ca8539ca814835587341e820334eb79137ec5f5093236a04ab75c9f274572faa4d5154637e5d7072844ba1a5dfafe6ee3f0e4beab3c03c7aa998e0b38ea72bb40bb401026872cfb8783cd891e88b2e49e8066ef54ef766eef08ac938079724d17488ab4d489ec644284d6a3bd7d3c926d0f492b13ce4d2a48492b3e9352502080f6531a2f7898efb10c6f29e82d6c0764fd15d54c56f5f9ec171eb09cc070259d1a117c7102f20c76ef421ec54c61a44c02a8320d10c566c57a059fba776117c8783c7ae3a1a82ab323044a30ed05c793d0a424a29ab035e0a9d083aa4e72fe6a808dbee49a3d320e9b06d491647dc96c7895e96a627415829ad728460b728e11e4d0ba45d48d909859eee5ce82e67557512c784a5b81c8a6ff92c3a9ed2171df775da8dac4990064336afebbd3f90ab64326fe6a11f3f8cea0d10dd28c72ff04599141d5ec0221bf66adef99c7717833ce1a00a0083686558c636858ba37d42e16326127d7c0e97654427dc09f317302947707f9a453566d284db586cc547f3429dc2de1e4cdb22b45edf423d75ab823113ef0bbdad25a85abc1549cc65fca13d961432f7e64710897ab707af350c29357e90f0bdb2686774f0b2fbc11728ead6315d545d89e189f75ba895f5047e4a5ae01bbd7694554309f6ab9b56f45475c55ba442f05ea25f96af077dbe16e79242416dd2b59b8350a6ac9b84b860c6da8c42761cf61bbc3c3f8d86f20187fb7c2e1e29d37ca39dbe1d0068f2db772dd7d34559fe2bb2b42139bc05dd31dd510ba1ebca11ca08def10d648df0ebf55f7f952b979cce7373c9b1cd9c696f59fccc9be3a2cdf7a540eda547e76cbd9725b16ffe54e7f780252354ab090ab3264e5d1a95e62fb7763895097a6e19dcc23b9566cc6acae5c821bee6b7e4ad0102d899d26bb9a968c4cf52eb5ce729c7e507ed82e93959dafa2db1ce28d482297ea06a330f4840c14248c9583aec535c17205d756375650457577bac169dc7049911a8fd0533e655ef985e5cf53bc88125ce470c982d3bdbc3e68db25a9736231fdcbc09d30e90a3fcacd02e789439e8b6badc3900c8773504890087c93a6866bd546c94106f88ce6f73269125b15576d5d4aac236fc1e407f7c39e834fbb596dccd8b94be240bf6a8c1b53728b73e4c653af0771ccadaebbc4a44fad95b50b9a60aa58c3e9c1e15404ef8fc3fd6a588172b8e388f744b3976f7f108e46a517112239a2e0661f6e2e5d4ea87e33bd49bb09ab4c29b471e36dc3808dc405dcb1ef91d18f438e1b26249cfd41845d8de19623e7e90889c162c00f8cc6a2663ceae70f34de3176cb2a18b71b11c870597c987254358cdd4979db158d837d3671cd0f3d8280081c1fbca170941e84467d69b772c07dde87d3e248dc7048088363ff998ff66383c1dfe313301facd49c9e6de3722c3daf32fc516f6063fd37f7af31a241f650638c91caa60ee320deff09b70c721c62ce565bbd87dbf1688921098437f34f8da0ad543616f9b663fa82abf7d1722e1634621d77f4913ffac0b9674ec265237e4b8f9c4c221dd93bbe15f15b7872ceead92255273b2c0784c07f44eac654f2a5cfcfb716aebd82967e2a05cf41723ba4040ba4a3894702312425f3028ed932f426747780f4bdbc9f70c782db4e7232b9de78a470a448368ad87f9f0af3ed1a3ad159a7e881276a183b248a67f972db43d3ae6d0179c674bffbc031df1e4520ffa10c2057fa8fb39317bd8eaae372d7049750e7ea917ce95d21593527e00bd439676a3babe07796669fa93f922a6885dcf23e92d01ff9bd90757fde421cf58341e7830cb69e91337d86f8cacd677291a988c1727af3ba643ad220b2ce45195818f96fe30be803990b5dfb4012c172da038c53bea0f2a61dbe9e469dc7971660c41748222b4fedd8109a2ddae97272ab55d66ce16fe11ea56054640fb2031c8718ce73d10c43316f458134d26ff1725acd41654adfea85d247919d9bebf726f7437199f47806e81d3a9c1e8097871e04075815d29276a12342e40f67806a80f2e5b4c003ce3914a4f12087ee7f9241a83579cf77fbcfb04322338fa20368cd3ca06666d78a6952ba1b2232c63e252d5f4acd9e4a9153955d309c1b57f8beb9bfa75d3c63f5abff9cf4af4512e05b72fa3475c2277f4dbba1b4b161d9d1cb3236d185edfc6936f56f3090979c329c7253697b63ecd20d903af8e932c352779c38e7e35ed78f2755c68a9d36eec19f01a922f16791924a442aaa8b1825c9e90f95fcfdd87f84407539d4c8a740c843275a2ed1be49ef007c790f4c4c29c8cd3cd4354df4c266533bfdeb128e18e346722cb576e6d84b1b3e2bcd597425a33972be3f508006d38c0cf2c73e37c379c8720d4774f48e648a155e435b3557b7efb8ede8092d90b532963b6e27871e141756f450e9073a53355e66a554469fa489468180f1d95a6fef955970131f45056a72b4ce8da4a6bf0f0c8370fd626fd8b1cb9a4ab08b94c6da6988074d47502a7356eddc46faa965820b3f39dec747e5bee401439fa2ddc7554203f6efe1aeef5f72fe14d429100269a1f3c2f49b8ff66280a1d1388b8358d8d24b48813938f1077213005ec0ccf5320c593f128985ac8f7dcfc3e10c2464f081118c9e16eb804272d1ebb99385142bd2290b5ae25112a98af61042ee1365ed3c0e71c107419c8f70b78d4e38dc7987c6b62d7eedd548c71e5c1a7d61a4bce78d385c7582b0f54572f918f9c0792bd5103bad85e6c0a279cba6b1b2c19da0aa89fdb29de332b6747220a7f0cbbf9c24461cc76d88a96579ad25dec81193b4ea56be80b5f5cfb33072e076d16789560790234110c3baf9128290fb7f440e31583828d1bfdb936e0556509da34c487f47d56db8d71cea0c327aa2e6082d2fbe54a9dd08f8b5b23d367215abfa09096d4cb3599599f8b4426e9da088f00b4becd5876537680411db8f722c276e0d4711b0f10d71f0e5e19156015f42a8a920cac7d59f8ca32ebd821544b6f7b0086d0a9e336dbae6efe844e74c4ca9dd01685b48c8aca901d658d7b50733abf63f13007444d089e25cfc015fc96a465aef35997befcf67a9c9095f0972e90c327854cb195b3af4b0d31d1ff7658f8a3ffad19ba7f1890489a167a08772ad9f850d1babce206aabe69e86487a89fe4b217ce50b9aef2f8640e4468ae97271486e5a9a4c1191e94b62a18511ce3f79a01f4077620ef424a4559de1f4bb72b4128547bff336380131871215315a1a6a11b0ba85992d8fa744c2188fe08645ed325d147e0017780f50cdac238601a0e996857b04bd7ca612d7c72868693c72fa904f3f61fb930b87e9b063354a4dff77a4c82d5316420c8ff4984c9a320272187178c58b70784c796d1aa75e51ae90873fd822ac17b23db55a99f4a1a47c5f2e57538b29802c6572ce9974b8236a7a5b0ebe672f803294b61e0946facd2a6e6ec63a5e451a7904dfd2749d7c46adc8d472de6c411dc8243a8d5ebcea14547298bfc40a333cc6f2d5735910c4c6a845b45dc62c404384d82d3ec824fd3d8372425cc36a4c88770e2f313de6a6b708348a91242dede84113134d967d33894f3cf214aea0ff32fc35997d47e9501bab64ca4e41fdfb7691e664f61a043388e1725292e0118553f9e786f2a0dc1e6099e2a80d98d5f3731f5c10c136b3e3e3cb63beb47112c77e5139ee0ff7bfaa0dc5b856761f37c69e66ba20df0aa817f60c72b3c0f4d2d5f71e77380b91be3bb854c56f92a121f22d7456727b953b8a8a5b721ae188f7e0dfe9a04cd4770ead33c93b5c6e7a36ff858c368753f1e95431907214a767ffda2248d4e1723b396519cb01a6652d0f6553a8c05eb32ecf38435e72894eef67be523c7730b6ed2b5b6bd7a56ec3eee5e24b25a0251eb86eea2716721d53dcc899a8054a4a5161c18fae7f9577efe099b63f0d467c0beb1776a5c0726c27f4d8aa3a7ecb2e05f6079a57e7d60cbe69790a3a42031c741325029739292ea8f7eefaea367327238acc1ef2b509d1d68704b4f7c67c77613eafd875e00c7f088d75b8d70c2aafc199a147562955464a8588812c153cc2dfe67af5a81b728c92440ad8829951c869ab0e995d8c1813ad169b82d687b6527d1ec52c187925227760b5e9a88d82786d56ab2c31519b57067496c275277cbd4f2b909cd999721c259ca8d543b31f3876bc21b279abafb13f3650cab54adcd28f9df7ef23937255a740667c756940647df310d19ba9f804c7bb6fbe35d2125f407b00e90c5f7288ae69cd7c4eef711690a1782f847299c350baa087ef7ef1a6a5f0ad16de7e72b0cc6b19b6a992a4c2e2b222fa9a3de84d6ec6040950737e1813cd7d6259ae337a1f26242f8127efca6bb391aebd3c21a87929b3001305fff67efef2a5fb2e54aaf7b4a9e606d50d3ff20a03c2ff339fc9b93eece0e5d2253f71fc7c56abc501df2b474c1882fa94700987376df4425d929cac72bd6f8b6dcade9045ee40d22d646280a4f576cf46beefa25093f18790446acc0496c4770f8230370977fe6872d078aa5dff3d183ded7dd924e6da2d426d93efd5c4f025283c788a6dbffc34723a7d98e21536bb9d3b691b70a93f025b4134aaba8f20b60383a917627a41335f956de03a1f62b63fff8b00133ad2205beb55bfe497cc54a90b9c5e8555f704723aff3518b0759a3bc069af8736614d741d9f2c3d50f4713d19eb4986ef056a7200120c7087c09744f3ec0bd4f45ec954e2e5e5a7e4b76997fe6eaab692886c72f99729102135e3e7a45762bb23f290e3bbb7670eb031fa60f4b133e03e029822ad8b8f2e0e0028144da6339d132e62c50af3b0026bfb3749deb32ac80f064c72158d693bb1fd3e43eb60dd03deddb2fe1ec31b8ac2badafc0ea04bff5c546372c8acb76ca92b2203ef7ea17cbf7cb84510e0fa1f17891d00e2c4c815fb8c7c124fa6737cbebeb1b16db8e6f528f4e82bc69bf31a179df89d6a7131994b37830a62957d859518e99ed5a688147e85c09a61e7ef8dc371ef1b5cb4e146b8850972f503b2279539644ed99fdeb3b59ba83c29d9d31cd1a1d81c854cef925c380472d2d9d3ef4a8a02d63084fb6f0fb5f0510d8c2e24c9978658b192e66dd40bda45d7f9f696572ae84fd093851f814636aefb972059b979fb197f1a7df34a3f5672bbbe5331dad4092de1c1b959d09a7ee1fe0b42ed1cf9e0954e0eff0faaa329724cd463aca13ed73738dcfcdcfe820bdf8814a9002a0daaaf32af856d72167014fee0485fcf9102e8d4e03d94ee9083712634572c1dee88a9c7edbfe86fb39d0bf7075d1e94b7045609e5a57a9ecc300cabbc3a19e0d2fec71a9ab37e5e09320ce1bd75bd4382136bad50d3f117ea3f9a3d840e8e8a7108f292e4b3402715211c0d4b5e3b32a4602c99785fadb7b7e99e5cc9733967a44587c7d250d18bc608728bfb5ed587323da5b29a3de7d5c7eaa27892d337e0388385d69d13f780185a7214dbcdf9dd41608a5885d2e3e99981cc833fb13d7d3072461bf119e0a20cbf40fabf16517f252d6ab7d0656315576d19f87e16acad448dd44ba0dc6dd3d24164e8ebed6a5480489df4220749c40f3e787ddc9f556d6c7bfca9a2a613d312c272fb3d2c48ab339d66f5933bf9d0609245413e53f4bf48696c0b7c8944cf6c0867798787cd8bf17fe178e8c2ee1557630e456bb1cf91dd459f0d9b20337bbbec6521f68dde04912cab2b18388bcaafd82d8b6b1f6324b92857d52be387fec87b72bc5e09345260203f23349d30ec0d029a0537b97ba4b740c2c227c53cf6059f264b3ca97358bb0935bff22ac1869c0c4e4e0e0ed5c7778fa91fe6ff7205f81072a44f2c518c1494342bbab9fbb348ce1b2056b097da143188671ecd9d13a27b3469f2eda2fa6338c469285addb2f0585f27eb62a7e85cef177beb4af529ff8d728b03f2622e762583131dc30ba953ea7d83a58c0fc890c6054a3cd91cfe84d468a8d7c81b67702dcee0e30c5b7e7450ed803245eb6179886c8467126eb309046458c1a953cb3f4c25b95cf81f43425fb964850ab433db56597a6273d76d4762729bd59b5ccc4a8bfeaa5f6e2574f581fa1dba9fe06f7cc2a84503960249de7c3b9a48b7d318b9264bd97861701e69f73895a89cd5365903b785034df7e3ffae726fe1cd6beb6617cb999f37aa4ce18cdc2ed27a005229637ca624d831c4ce7d6ce276a0342f5efe431fdbdc6340877198b683fc40c4436435298bfdae26baff7208d41c47b577d8b969d6cedd717e6f8c33267d5d162d7a786f8b74247d147a1dd511ccdb2abd83272be7857fee6f20141ac36d23a1faec056f61296797e278725b1c50ba3755f6baf7b1080ba5ab6acdea40f77d5771ad57e6d40c1d673b6672957eafbd76d8742242a27489cdb85c434445c2c9286ec4e982fcebfe100ae07281f81a867deeb4efd456423b735b8da141c14e918a548504af74dd75d5640c7288b1662c3f3f37e95ff26d4c7bf673c807814cf95ba7f466d36db3e1b3f6bd5bd3ed6bfabfe2b2536c683829d66e1fde27e72cb0199621dcd47b037b48b897725bb7e02e58eeb4634a4abaa4dd5b61edb4057fd9d624b7d7d525562b28ac6e72ad089cb6f4d6b90f125780f116b1863451dfe15b28957698595e60a532c18a267cc299f4ca7db128789912044a686bc283d795956e50e7a28cf163a3b23ec03a16db29de92d7929759762c8863394e027e6326ea1ab7dd366649e2b927e3cf725f6e6e3580e3ed0ca8c2ccde88e0180acd65a3bdcb585b12c9cf1bf34638f022fbfd3c7e3384fa2a4706fea1f41b6634d2e758e8ddeed616e129c3cdc6189469a3c01605f675bbbe7b40446d1e4afbdd1604e38d23ebafc25f4ae4ad07eab22870351a62a83b25c068bb57e21912a8bfbc6fbc5c876e3fad676af3fce2039a723f85f03fbeb108c42022733781b3f9036ce95c60c0a0e1ea28d0beddfcf80666470d7d568310cbf817dcb6994c4bd1e0ce01500253df27bad36f4436a8348772c2443498781753a9b3a1afc7cf97fad46b4d4493b53abc69256966fa9acef87221b2f7cccd91bbd4272dc97bcd03e1c9c51a72d2f42c328257ac1bc6bf00ef3cecc46213530337f704f291fd8a86b6af63482393481bab3bee213f5061e2b5723c137d65a989b43785747fa9be549b8434d60e3a0e202455df8c69337e192c07bec8f688a4567249dcfa2d8b99c032d19ed0bf06ab3c731e84591a737370597201e72504d84ed8465f0809058d46f20310f2e2e3c902bddfce1d7e003c2c39275409cd8680362c94d5a77cbcf64099e8857fcbcb1368c39b074f4f647c522c728400c99852d602689bb1450129669744d9628877e868b3ec664cccddfeec2372189e1c2bea56ba78fdf6831ef19749290ed72859e663da5cd344578398d8c96870835b3f324a69e1a6199d9b68346dd84f3ede7502a9aa65777a4cf1d1c25b72cedb9b5adaf734a2f5e5a0380f5226f406144d07b00e66a1356e055d492dca72a0eeff2e92636ed31a911e8282da08ef5fff5e12b5572eb67f4c495781bfe667312b684bd4789acb744db01a6538150eb9dce382910d1598097bc1bd58afaf0d758e8c2395d338960b2625e27b4edb5e3e47a2d75c36aaadb1a629e9091a197298bbeaf192db0fab78b30d866b4d827c68fdbd82aad90c121582fef71f06167272695552e4b075777a3621cef933ecf0545e386e47fd9c2405154b4aa6d1ae72e5217adbf181c4b677e620a1e0fb1d3e9537839293e8976e46d71094c5a9994b95fe2dd2cb848880293482bccaba4ef690187d9dc7e3e4171f6d85a32714f37243632d5db30c30a6592f0abe7329d70a79502188157821b7df1701590e114729ce21649ff1d9c2f3b7d0c8352e8d1c936ff9c7260799f166c957261eca01da72e7f12aec4ec262b23289b2fd69e3f6ed84b7a172fefb969caf82b741547ed372dd0c0cdc9ba01069939727dbb70df157cfb1509c1ae01064302019cce99a49358bff3f16605beb2753a7a82c3c6141a5e6fc3450d3791beb8f6d5c0cd2a85e72f53be21dc77132917985ab0041c1a9f2b268fcdb2a18742c84caf24aa7199072c88cc2017db981797e0dd1b65a9d2757e2ecc3429219710c43816db529f9922e10aeb81697e9a84b762b8dd1b7b6cbccde36ac284efa72ea738f07c901ced91dc66991fd9c3d1995f7704fe689875a93dc7474df8808bd6b2570cdb09ad19f7211d4d81559fd7065c84787bc0a321ed2c9d2c106be30e3c698f5820d071a4a72c5ee094600779781ee2ee2ba980123a17a4bb40f4a91aac30d3694bf067a5e724df749dde853e87983149a2f18129cec2b4ab231089f0c5efffa07564fe5e616f18b97fed132750903622d26ab0cdba0e52e883cf2232fa7958218ee2adf1472fa023c9f8f72f919bd48ac3efeb6cf2806ee6ba1e304a95f2d2b6ca6083e72142d25388da7b7f3dc4d8d61c03102f40038154fa850c97da47b2efaf48979a853bbfb525b8d3f2ceb1c64962d7ce702a1c4b80ec1495602fe619d39872e50a712352c0f2727f2968dbea3ade7ddffa2c0e530626457aec9021c90046fb4167672ed009faca9278e40a65495c0135f4fe177f90d56195f4a8a5230a512ee9e8407708184cffcbc3f7e346215c6a25577656b7f0ca37ce4cea41d1d6df54d43ad445c2aa1ee696793e84119484704e488bff5544e327db6bee95cce6ebefb309672ed5a8190bf1030dab9472a370de2a42ae97a485258169cae827ecbf4a13d9b5c6759cd5614314180c176dbf40b8586d7c79ec4d9040f63f5970f25faac1449720e5a3ec8031d5cb685aac8d732b491bed159c43f6a584735de5fe3b05428b86705a7bad75a8f82b6376a1c482642c3b7c6002c7d3baaf2b2428f469dc2627465b5564ed5d6d88dd37dc440892091ce574c2516e2b8bddbb88fa9750cf517797275785c9377664cf1cf4d99d76e597136331556dae6ea65a459d7c0213372d172f9591219346b9fff7ac62f9bfd0894a99333cc89a707d7c07a87acd227f5f1728b2fd303d8d37e262bfdcc76ac412803d1ee334cd7069b1da4e40a6e7640556c7d4f58cf908f3978ddbcf1b7cf98c3bab5d7a7c8e68463be510691dcc1d8aa1d297bd46b6c65df8a0f607a93dc0b0d52992938e7484b207b40407fa069187c2950c235c17db424d6bbeafaf4c287a6bd414dfcc84aff9fb1b87638d7cdfb5d72b0ce4468a8a130b2c4e7b2a77c79c7f3b103c31e7940fa28f538950d03a03f1736b4566d5bff4e1178e68d899211515dcdf0d118c089fa8e439fa9de31eeae72b3fa1d59c17c8478ce084ac54630059499b1c7683bc62d196f3b687975b82f724f8fc6e4719ae99428ab9a3dbde8bc74e4ec434fd64ba772f8a7396a97978572c4c56c8db24173024d73be0daa6ea65e16e48a60edec7463f6c431d1a52fbb1ef3bd8a01ce6a9a6b48ab019060755e575958f99d02d5568504a30532ace89172fb75c30f5374e6f481c938aef5258833d7e8a6aeb3cecf573b7b40d39d923c61b1172564974d7760319383245219897472a05a8525653b2b964f41e866e2b325435865552b57830bf65127751d66c8703d3b764861732f2ca11a6b70c839b8644cb92967530cce0ce6e165fffbc7bedf44d6525231f346078a2bafe1be1876721030c9955760f360b7c75fa7b93cf4d80029053d5dad791d2ee1a79aca301b29a584c257670d38012a1f175370f5b65974e8f40679b3d373e4752dd7f66a012d60b6689082f30c9d725e8c5c5d928fc38184f6af836d54c0565479c7b5bbaf71728ec7fea1f832fbccd0fed3fda022fbf59521d9d4d22079bbab63320a4e863056a64c3eda785d83ab899cbbf91a908de8d29f2b2bd525a22ceb530f5e0a136ebde0a5430a92d9a375392bd29f5a7c08b9563d8d6201b3be31e35243e970967215a19be98d611945af2bea0626a95b680e88e0d41d2c2e74d095096b4985401aa7d189bd80ff885fe13b298cf95ea2cd727699bc92865187b714c8ac710787396a8c4a72c5c0cf28b244cbf3c25f075fe32547ba82021edcda62fed5bde1e072507f465ae86f9e075c3f26526b2ef3fea6fc446f645520182e8475dcf3044b725dfabeaf0441f18abafba9c11764461ec7f75ddfaef047130752cb30a1a2d3723600b82b3976eeedd50b64ba1aa69714525a94d52c82c8fc160b420b539d5a3151b8a114e2d44ab6b76e694685fb3ca824e4cf02b9e90bd1617406ca2ab1da4868a0eb7cfce47f43df6ff2ac640deac3cb8ce264a55396bfc66440c1fe2150720d083b863c70f16d2fc579c4a75c15ee7dbd55806e192aa4ebd1be40526e59729844449d66a97fd8397ff27470e9fd2336cc4dbd69c2274cf57a9fcb12489b6d7b9540a5a2f9d3e657d5fc0909f87c6c47160570c431741aeb74884a930b1872bcf77f64edd556b8fd48b88610169d664c916335aa5775e1d37c08ba90723d7277263998bbb0396730744dce4dc50e8d0e582153ce4488f446ec72d8aed7b572304a83acb7cb3371d9e7b4864388a1cc28b34e6e3a3d5ea98ec184f9351d36550b5e9dc1eeaebf2ea7d395a52cfb1a25e129a4980e59313d4c90c336f75bf955e4e509f1ce172ec49dec2108f9429daf8321ba430d70aa24e705a4ee02868f369b9041aa6992cc79179ddbfabd21bb3c094bd9a1a760b06e8e98e1cfdac2300bf6d1f31991d25f44376345f052e5ce961fd40b7e2b6ac761b84cb51c13463e0d55472ea7b26a272d788ca8bd54d57f6c764fd52ea198d61dba6a07e43f096d72cfeada806c94219c662d68b62a4edf31478d901f52208f0c2765be196c94e6728ed55862577646a2ad0c8c71d512629f7dc0fd95c8ff1dbca988485435237472dffdbc4cae5f3fc7fd6d18dc863bb117b6ee7a17590629ecdfa45ee0dd913b0c8df7f54519471e75cc95b1a3ea79295bfbdb3b7676e47eb16ab13c9352c68e72c38c3c62cefee4597b18060c435df66c52e3362d9dece47163299303b6ec2e72b02eb57cf7a7dd55ca9e9e421e4f2bb717b6b721c81714823e2c68dbd8d6d2728ca6efdca42590c5f830b257301098439477bd5ca3791688c3d12f89ea5ddd728c84173a7b6d0de82a6cb7b99a38d7a1c01047966394d13462982f0f24867138788977f70bd8dd929e55064f4c3279475e1a1a36809809a4323d65fd8d6300648e324e87572a07900b0c6ae5d20d28c0ed6cc29a1dc928dc0f10003f5f8b0569e4b2112515519ccbca9a3efdfceb329ce7e10011abdbab87420bab040e7f46725394bfe7d8223dcb3e1b85031b0f0a6d9439c1c14f7c35a1d525989b2b796f351e2b92bf1443bf83989986f00c7abb60dce24c712dc38b4288cdd6985d0944723536e6f7edcc7f508bcc43359e07de2e172707030e923c9c2fa1edc5c13c9472e88633e0ad8eef326b98ad2f4793b1757553e73c3434ea11d6f8361a580f561881d10d438e6213ff903d9b16abc5652fbfd435c9930892c22e06e40220ad9c729c0b361645471c7feaea8c1956dee22de6d751a1cf353d64c657339f5e47db5c5d2964a23f18af25505e52dfa8850719464287f3c293a678ddcbc27ef5d790722972eb2a2c5cf3ed038810e782e46e7219511b38e40cf7782a8f1ac20dcb8d71a1f939777701631f6c617f7e93cdafe2509be10350b6d2165cba345feb41fe6fd407996de7b3bcdc2bcea4c1d70462dd8ba96dec069f2381e530909c80934c72618a45545df94a88383e8e81b7fc114513f5c10916e37bb758edfb199cc491133bcc81cc10eec185803e53fa4812a235bdaebbcefca655b5c358b0dd0a13d472f6d85534eef202b3985d8e4e17edf0e2e482455f8ac1dee5c96fa9a9516eae250eca4d556e7ee5588eb5de37f28dfef8e647645a3addc4264535dbd09bd5737280c03e810d3795016e544b1967e501ce19ebfa521978f038fdd47cc9a40cdc726755bdd8863390b026a22374c08ab0542cb7248f99ac750a3dc208741e6bc609bad37aee48ef40a0b0488e9d3ea0ff9e73e1e51da9eb891f1cae9138a4408d72c1fe8de8408cd24f19380319b087a3db7428a4fefda0f88c682bda7e5faf8f72eb4cda60bc598ccd74209bc41b0da6e5d832028bf82f588a84f597053d9028703b685ea2d6599319d60a5f2b6da45718daeb6eec19521d62aa291bd596c53102f80452400fd8a4d40e41f6d238618974bf22666d034e72eb097ee952c0691646b28ed38aac9f5f4cc0b3ff6e12e0b2279431619af20239f3013853642db3ab00b7687cfadd633db1e8c6fb11771e98927e37ea0d5216df6797f191d05de3415ec1faf45739b9c88990bf3d479d46f7f9e93dfed3f9ca3d9574054e7ff42c00632222a82102219dfab4d6c7197a1d7ebbb2e22c531b2885aa0a0e03d781ec5466ccfa00d73097ad5215b9969d42272ca5245939819ae25e0e830f21c5917f4e6f122f795bfaf40af4b415f5074a4f2918fba1a24de1f2865c783a1ded24868438c4855d6ab86f5bdaab3c947f0fd0f0bf6dca388bb5142ed6c1c7e5ab50d0c851265bc636c0930f451413a0d1b32dda6e9a2a6a5ff1311c1b224bd91d78879b72246bbdac4a757b88acaa42710f36699d478bb1c7892fe91edaa3b3989f7f84553c000f7be940325d58bb2bc4ca676c6c5edfe9298b3c284425f29f4154eb8361e86812f887d5980b10d4e931540aa8233fe4f1ea2d8460c8c0de56d1e097817239bacd127f9bbe85794574a0e2dc9ee43dd8df9d098f4feb5ffe6343a42f6d72c77d6d1e8c524019a620382a9a597f1ab0996c86dab4e23c3f194f4c253aea3817010d22d494d3b70f4617652ae294783e0d2ee112597d4925074eddd34f36726a6e4cc23e822fff3307871876bcc0e45c0abe358dc6aeb0fafb910b77518372a10d03b9924ddbf0985f179ede7f8afa72125d6fd9dc7c520cfea34264826c728b5d0c57c25aa1784f11df1ea2c262b06db8e53ca23936fd0e1329cc32a4270872b7916dc90a324fb2a4b6773feafad6489bb3155b3c96b54fd8198cb2b59b683d28dafb303d03e344a4ee4f6bc8555f8c8da870d62b809340104b352a1ed57235c0480fd33deca1d855d4679613b88ac99d42d6fe0ddfe9e3669acdfb0b197278b1ec01149b47409190cce09ad373628e6d1b7e64509052ec6f8760f78f6472dfead3c711333cd198088256ce75b5bb100c9c4a187c9556c383bfeb317a6d721c9e0331e630f0acc23ce256109184c4c3a9debbede104510620e6f76b11ca72bb717f91901172558794f259590f42689cdb2715da101a48157e324eecfabf0263d71ca72cfdd8a4741e3d71e96615e99c0961cfd3c45fa9b4853242a4b41a72450645b06d6f1f9c412f1b4e510fcd13fc3bc9eee41d2361f7d3c0030d21f47269557e1e60c31ec333cbb3a1c32f02ab6565c25810338390a7874a75dae65c72808eac5e2a214773aaf8932a2fe1bb3da572c075b7eac667ef4652fc0c4d756256756de8e8a2162ebf89e8de3abbc1ded34ce71ff92278bf1063b557798f67724a8e57ec315b2f3a51c3cccf047054a84eb99c50a5ec8dc2529c089b7e94ec142ce08e6feef0ac3f915b10068452f72a13ffc18f1cb7b415ed567030e4d8a863099ae80cf9c73a5b8dbac9be09907c00dbdc86ea1bb1c6d15391dd9a48f5de72cb9bd0ff8009cccbceb61f8cda26532b8fa35065a3af3e085bff47659e88ce6e8cc1c9ad7ab9cf2c1e15114ff7c58bcddd5c55455b9fe68b4be8e3d2e6eade7232c7c17c9930babfde3669f55323e75a23b1c67f3c2e677b28dffbb8694be6723ef3044bcdc162464f07f34154cd4c1223f07537e87f6a9a60de157fa5f61b725ff9fcd4a71b070e932ea5a98bb9ec49418c8dc3ae64f2231843f7309948f630b64eb2c8734ae97c9719f472dd6e875eba3fe7ed8bbb3b0cc3711edadb0a270faf15b028944de55b3dc74fca671c8063ffc1a58fb000a5ca0ff0a4bb41d4f8144933e27d550d9c393efc1d29938539d9d9fe9888c205cec38973cc2392c85114a1a044115ae14b09b2d362039054660e13a88fb206688a7443bdd8080a99697276d0fdef256d504cc08101029e2a64f63e0c281a72d5a3c781989c4d06f9476a857f90b7d6fb958211a3537dd97274092160ffcd50526fc7b564d85cc08045659c68d9c2ea553d70b4002ac51b474bd6afab6d54454d438ef18d3fee455a7572adb7df8951a5ef4b07456d2f4ca376a57dc8801154589096cc957f72b1bb3c729b356be9db88429f263b84b10064c699030cbe025effebbaa20ef6fd64c8d93acc4a83e48432df551c349ca090719baa89f0b79f6ebebe87a6321a0a2381d43175baa03332afd28dfa0599fe18d4a02d4b6298d44dc327d86466336098d82c30a533a844e9ce0c531a7402cbce1ca37bf28f4e55729d1d61ac3ca37057b622163fc3254122a780ab4664993cd742ae485823a8da3586a76359f40dbd9e45103a236b06dc54ab94b823f5ae013bf9c5162841370da39db03b6e15ce5a6d6498726879761ea1c20aeab8a72d48cad14fa3032bfb0b8652a669a394fc2fdbc79f72ed928baf3a9f805a72c3a88de97c2622b4acbeedfbfb84ff38dadef55049440e948474c40f7e276b2c982f01d07b42e8610a970444170e53ed3e1d86cadfbe5a58bf23a755958dd2f6287372c8c5deeaf6ee5b50bb0ccdd7eaafd0eb3c6d1e06b0522afd6d11492e331d9c4a38f8a2f949fea98063b7b59e968f5af606f6b0527cdb3c55e5607496d13d489e8a7fecb620c3884442dd6e3fb26748b9bc66c0152200766616da06f040d47529741dc2bfac4524a251f604d1a23ae16e5239b9726995b0dce75c2011a5d43d9184c1f4fe5be40514396b811205898bc46625eb14520469da1cd190c36b8d2955185fe5b04f9add34dd2f07e04186036ec1d3f6723bd9a80ccf18bdcd058226c92cc2c8e98487dcf98e81201681e6f773a0633f72bac04ff04e15f85f942ce6ab90ec4b2af2e898f1c620304f642a3ed665eeb26f731fb5209b4b27a4128dbb5f4a1b307edf2bcb323074229aa0dae05aaba7f15f5f19811c84d215a2416347f7fc440a208d27570b65284d4c0649b718f8f9853c51da73ef4e0526adbecdc89a3f79728d1c2b269541bd3b56253ecd8e9e5add7261f115490aeb4d4e31379109c5c0c1ff8b27b2aefb90fb0997faa12b68d5b8307be91e309f8576af5c7449f6455214f97ad96b2fddbb443bb7839ef33dd3cb6d3dfb15dc918ab0c6748a964ce141afed9962749f1220a3b3d83c3441fbaddc72a986af530f1868c80cd883c414ece865b016e46bd2ca2973237a3dcd8e4a151a0438f4a742d160db01f28ef95986bc76b0e16b0f7283419e768d5d3f71cb7172443d0ca70defbbb3e3e2ebf8bf14e541f0e46aa392195d4891a04a35b05bff3c565f8cf2da693d9538489c54e6572056aaa4e5fff808c04a21e1551a03aca21038ab52e9ef84e98b41a1e6f2547b77d890b8a67b715297dbe8ca7df24454b7725157f7d25ba920d5a96fb80eba7d97c46af0693ca75f4dd26a5c5f77ac876452f316966daddc5ddbeab7fbea1cd1c4b0b5d2d2f9305d530f195f8abdf369824fc2270cb92c549de1facc6cc8f84b61810a7a60deb1b1034d4ca3d25f0345c872b809bbf70ae3fe0f81a5050685ee43b18606c6fad973f6b03640cfd7c109bb725c7426cd2d25e6b306a5b057e39f682d4fc659281241f05202bd9e1e249b3e72a725125725e17c83764e6cd18dfbdca812a8e40de3b332457679065dbcd29672619b2d990b5f2ef575d96136b0066e4c9cbb266cb6f57246549dacbdd6916b726791068fad6fd02f3f424ae73df46081f9321bb69755c1bf2905ae5c34f58c72a203ae003379623a532b7163cc8a14fe291211d5a7dd391d651a74ee27f24f724d3e5c290cc04c3e111b4a3b9e0c00ae4c6121c1ef9b0a4e873f551d3022cb72794bd50abe599cda71bd444af7c3b6e1f5a6fc48bd0566cb5c06f8e2c77dc76d18b901bb3040a600c131bdf19ff9d6f58142e12a24c89f1f85b9f0dd46684a723099b2e62ff23689ed7ad057c727c148f956aef4fea7e1a369558e264626e5726bb6abecc0e48c09e21779ee481670cfa69e6d77fb512fb8fe3358a6b43f14724a36cc8cf24b922da737b58838f5288e0aa5f56af030fd3e9925598ef5054272da4a0293a631f8bbf6eb5c0313e85ab554fc3fc87ed69507491a4a761a12012f5bc73d30cbf1159793beb5e436c5c3e745bb18ea9c2f1eea2cf6679c0e4e41724c5d29ef8fb2def0d94bc2bb0b457c2a26b835aaa52d92ce767d1b782dd65672a86d25cd546c2fbe2558093043b623193a14257d568d9549533d0fda7212583bf6fec820316c18c14323dbadf13cebc70123689f22ad1d12cc122ff943f06b1d8042a586a66550d85efc6a897eb203a8524738ac1f3e4a1261f8c3652a8a5d2cf0d7231645b37736a082257be6ea55339ddf241e4318e27ed6192642be9bcb72fb2d22abeb81f380f873a57fe908cd4d4b0785aad8d2eb111ea3838bd12c9e27fe5d2d19e2bad7118340561ad68b491c73df06fd4e97d0e3080292be7a96f272f406b90196b1ffcd7532616b2c66ac616a35fb8d689eca7d3f3ed79047067a65fd1c6c6e832b9493ded4597d0d69d6d969d0d870915920fa462bd85e9b5d3105d05d181a07beae487d79d354fb058245948a7ce210b23213597fa2465c7733548648f3f2b649396ad7e08a1a1556e428e80ca446c726340894ed04bbf4343158ebb65ae9cba411b7383fa1c43e94816cd5ca47a3d43138de33d1665bfb5d3444eeab310bbf570dd8e092d396631458d1e1e18b25c328ac1e66ec5b135dec563e27b916ee62a13590aaeae755fc17d4cda7dfb88c9a5838fc50978a6586eb93125c1e37e1c0103ab9b037d3d847eed278e3ea9e9991a76b34083404f5d791b84053b64caa38eae0efd241e88ba604e003f6aa2e03c4d2ed0eb936eb03b40ea81d8998f99116b073312c1bb1dd50e0321a58202f53ab0e8bc7508ba298c19f0c2be9b68083af6ccb388489bec8eca752b9a2c2d4c89c9734756da0babb8c8ca972fc6ae308b4d6ebb342cdd5abb5b65d1fe3c20686fe372023cdbde8ead3deb372347c76846f215dc1fb077a34ab30a6a1e471ea977382a911c5483ea8bc8085726376f2fe7deecd36819c681064a4ab71041b277dffb7d153cc6ddaf60ff608727201b5b4082944e90a3cb4ed8941dbb2306033a0ae06ffaae1ae32a383ac9367775a0f935dbae324d7cc64c513953ecd504091987460bf138193b59147b1c0670cce5c374282a4eb5fbb63ae465dcdc3adfd026fb3f034574389fb5736d63302837a97d6c61b294452d6263e9815f0eaf4eaf83cff77a0be9bcfd44bc0e27272722f9164dc0415154494acb04329142fb586adb0fa2de7822818e2e92144b772f5c65c7bfa86b1a95f326fb07a9433a1efd6c469ded7e8b79ef40c1027be5b72e0f746a79956550a94547fb54e78b11676b48fd52a06dd7bc710bdd5b30e77640196cf8b9207eeea935c30f8ecfaa2a66b74009194fcc28b79a22190e6c59443ef666f1acca55929375b979f6f4e70d20cd07cfa3da98696dd19da6dfdf109216c293b511e822a581b3e06fefbcdd0cbec3f2d7fc8dfbb7e936e4f274f3d9c3cca42867d86e14d68b8e7c78e30047fcd51a20a521aefa6f765e8ddb344d94b2a41fdcde5fac93e53a482a9a06712b2a3b8881eb53b1f226f5c88e049ecb66b72c775b7370f2fc11a41cc959741450d24046445e46c77212e774b9b8938eb280a193f2c8a38fcf24d2eb13dedf10fe5479b201b67bf4739c85d6630d19ceaa872b9a33c4572a98d42dee7b5fe147f59ad81d494bd2cfa13953f039455a811f302c2d93d155ee17bdfb456510cee4dc98f1e895c675a94d7a8685d6dc75faddc2bb8717064e771f987265bb38262758b130df2be9f06c95a6a005972667bae82722d035fd76f51d73a9eb86855c845a55b400cb7d85ffe3831ef4db06b888e6572b83ae10d1027e3d26488f2fb29e06798f08ed42e800879f1c53b2ce65ce14e5b108291740bcf9ff55985be8c84646f1321dd7fde8047034d85fe449ec4ba6b5243512b5e769a5410aecf712a6e1ff567368e8ae5fbf273f36fb977d5f07206728e6ce8d5b27e439999915229da3f9179728b502fcaf72a377d35b08b34a2f372d5334c8fb8f01c2436dca5034f040d1bc13081775cbd037777fabf0cfa5887726c0da9749fb59098560698d312437b81c8de8a41df985a58cc72380a8f68c1727087f48d38bc5106cf7535fad9afccf43158aca3de724116d3e269784502bd72120bc695c8933f3ce0966905198561a401838fa5f90cbc49f873f7a7b9a6dd4b31d61adfc5d215c0e556d6273af6e3c9a12a12f2521a3e231add16f9375cb162a21b0c1a753f170cb43302a5e1b0e5d6c2b296efbc5cf93ce8fc8dd8d614387228f7d16ce4b05368b9376319903993def96a41b46a9d66cc7ef2ce2edc20b672ea57305a6057935f5627b98dedfcf3a3ec67eaf4f5cd1f27a3f7b993b9664a72b3127b28a5e546bfbb885c8d8522d45acfc5400807b3c3c2edde434a68528972b53a5006cfa1367537903e929d4c67b5680eb4af3997b76791d03002107aa472b3b8787f564b53d14f629eb8aa89dcadb18c359def5e59641fc11bd47cafa77269500cdf1a73c5770c4ce2f281e58273bf753d445ebd04ea0b0864fabaafc4039d9a7246e3f3d2ec1051ae9c62c80eb72b70cbcce215d2b1a703b90674269f72d7379019f65c185e8797d386f28b0b21fad1a7e0d88229fe8a963ebae38895722364d8b5fca935e665ab8c9082049f50923c3e0044f278e63bcfb67af6081f42fb5ebbd49943a7b8d1b056720f8477e5a29f488a991240c5c616a206c2bf8254ed3e29aaf00659a0b9e0b0dc3778a1ba26d238bdf79cd29f19fb7a2f424d7b5dece9b8083b3856bf96341385d6133eca3b2663c6e26f293881bdf588897f6972ee0c4859566b628b23b9ed24953b80453e1d066ba632506ecd5b46343a7afd729c0962eaa716ac6e943f7a82839a0998597ee3e7553faf908f2d82631fe1cd722b167080803c05cc75fc2f303faa7fbf92292c645b94f90fddbcacbcb1e8e0729f8a3766897e3d9c6efe4a5413e725ffd879039350802b4965e8ba32fa9cee729bda49891953725bc43f20af741bfbadeab32fd8367bb9fd58d5ddd3a5c9271a33f1b40e5eb396d834e3b752eb9eb0af29f53e14a765f47905c2b32dd501684cdf2825caf854c1c190470479ccb3106f791adcf3bee5bd7a93fa4039bc819c71ddd62a55f68419244db2c8e10f1bb58ff8948428fb797f0dafef57d7bbe0f6475a1be96af50baa9b945f438ba6bfd213b3841e7a94280b4c6fee1ac46db3e27226dbbeff1417f7c0b257059a5f13b6891fc88fed82c3e5773163eae15f7fcf72bd96a9899693d0f5dde207960618cec828beb01c982a4cbe7224dd1e3f42c418a57c028377d5ad5cdb0120c0bd60095ae203c3917b65f49426c4860d72cf9b7274bef3059e4e7158eeaf2d6752bf8a9b650d8e60c32694c241e472a2a43ccf72e5efb8209d7365f684072217966c21757711e1ffbcf029deb57d998aa0f95c2cbdc69578461ae55f716c4bfb96d792dd38efc9747ec7901786f49e94db727229f07273026c1134dde52bcf30ce76810462c7168524d2b6fa736a9864972b6b54271fd7a3b281227dee5bdbc03cfb124974feb9f6b2207616d461b0d130687e419af9155efa489d34f604c127201b325d51e2a4648382c64c52833068af5b8a725b04b16c0f27e361682889b7f9aa6073ced7e654765830d80d46b87eea202d7269fa5be6ab92494c2f3531bee770ccf772afc51a0f4ad06997c6daade700d559a2933cc658dd5a9b36b9873abdeff32e857e254551f3e04dbe3570a4d97a0b72b47dea515905b653f6cf8fcd4948be42137d3b7c463b73cbbd8733b2189f0a72320a01daa039c31408631c7e880fb7aa1af9d6a59dd7807eb308324d66ad0f0e83b82e2eb227afd24ce546921804d97178d73b6c80f0ec42920ac1c16ee5b1729a0c604c9f3fa6d3c5c0ce1214f74a3af58aafd2344e487d016b2ea37ae217671f88dd50f45269bd6376bc71255292a21a19ec46ecee7f9852a4706058375272b0632aa077212179dec8507f6ed490f96c6a5944fe4dddb2db5f5b298092fa72bb8949977b586ca67aa3f3b0a1b2359596e9926b07114b7f8296e5a334d7064750d28551b82ed9715f5a808dec467c1633ee0eda0ba9728441fa38c96e9ec2728e0643120377e4a67ea8e248fa8159ed633b3b2e83d62aa9ffd6b0b8b7f4c80ad25f5a421073ad42c350b3f10e7ffbe7e67a0c9f96e125ff6105c51c1df4c072e2dca7fae4ddcd67784e8b426487f73b9846b59f8af2f43452dfc6a598c394721c97f1edc0e0891a6fc960978d34e28fc20acaa4f16cfa272a98bb0e3a721217a0b7e83c83c08d254fa6608a2e31af39bc80fbb743e0e109b2a0b1aa3edc7472751f780d0e0cbca4cd910d5dd07b3fc3255dbb3031641aa85d9135e55d4d1372ddfaa3ee50317d6e846270f76ffebe5f6f9dcfc39b3bc36101239746e3a23f728442fd602afe57b502e6207e6035abcbbdd11e164ae254cd02e2c1af705e8c06ef8d70828595e19cac228830a1e08dbe804916138d7e42cf17d67b49b815a63a5aae4e2b7f9aff0699bde08c5536e473f3a810e7381a8fc4e061cd1b976b7367aca998ea2a8b091d4cef40c5ae32222eae1de47771115e0801a248af921a8f34994d16333281e8657b7a6505708f0050c1880813161ac1cc7c7ed314a29b590c4d30c332d8eabdd90e552d31e1ede54ff88943bd3a2353022e7b77e1cb4b8c72a50c2f78fb2ce7a6493570e5bf6114232b1a7c1ed9815946f8d1ea431b6d280380b0240335a141c30a8cb973bd7063b55d7f697a274cffc96772d45987dca7729e42c02d384353fd9ba97b4ee2621e99c0e14a491b3ab237d306d857b6f77f72b514a1271a48ccbdda529d4975afaab536f7b637abc4df4fdf250b80ff9a72374abdc0b77c04290c95930ccede3811f29d9a3e470b32abf2cc9cadbdc3f9a072049588ec94f1251f70c638f76264669c953358b03fe5e69e6cd89593e913677220fbb4c7c3f0cc2ff23141efc97a73a45692d694f903ba903990a664c39aca7292d78e1fff0bb462dd2152abd782892eb59607c3603962ce90b8ae9ab9a8315498112812442fd5cb84a6bcd7ad4193601276227707de51fdd66c8d29f067fa0c87e679b5f125aefba3f323477f21410aee290692dd3fd85cb2a698fbeec1821716fcd6f9a3284716fe13450075c1593d6b7f4d97b15e357a6af3e3fa2a262972cfab6bf61a957ccbffa42bba4f9f2836cdd05c8ee627c957cd97a2aa51e6a44fbbd60b982d1a7a531acfd571508f9217ab4a587d8fd777ddabd8493ddfae9038ce05bf9b6b728d279ab00b8ebb621b4009be65a47076ffd32aeda71aaee28534d661e5c7e4b360d803a0d9c78832af82905a5dbdef5be10c57070059a9b0a07228a2e5420d9bc3f8f5da2ca72a7992a43b200b1968d604bf4fbcd93d1b83485ac47494919a7cdb35c12331fd6939581ad37300ed43418bf0fd659693a116f872df0e77ab1d456c299e760e7b8084c531c76ae6500572a54fb53bc8d8dc4abd72da5ea586fe6f43acb6b4dc2f30b51de94dc67f10ec901f8ab4740bfbb7308249b8155dc24eb70c94b3392fd3eb6fdb8a1bdf5027af1049474992d5e0f0b03c727819007615ecb1e50f1b148a6be13660e2a37c5612fb75cb7c11c486947a122e036136ce118d96e29731c2e8755467b8a8fbb5c2a1aed8b07ad70f2edbbc85724cd7875d021a233d6a25a2d40b2cd14e1fa8e45480ac1d12fa1f927422418153da3961c5d9b0aaaceb0d4556fba4004ddfee5e10312da2012adf21444408ee725caf525ca696c1c5f97da54964e098cf01d88ad82a866a63e4347c9bd38ba172dd63a47fc47912d091ef3d8b3c38a8b57f81263bd6d2b55aa0e9e6bd7cf9237289cd01b5df883e3b05cc631d7c784612dca3859418399bb04511f348cb55860608e46b1bb6b3775729ca60e63e3b975acf75276b4c5b6dc2903e39763dc922728bdf25cce5d88dfa27a850f06d57be36c3ac3cbcacd6880557328dc4ec7bfe726906779d81f6d48c0e07a1cee441e6b7793f3940d2222b4b63189e6722d18972e640a8b7ff65c09a68dba8e03f212835f0194e118c8043fc1ce600f0718df97213d5b5904bbd4735a3d944cd45e5f501fcb99a604a4802dbdad29d29d8a8e072ca1b838c893e13fb593bfacac34034784c7ee0ab4cf4062ee6ef9fc32489a5729e22cb1bfe043c395d0dc8fedd6cb132d97858657c30cc6c2eaf575dd2d1eb72fab90ceb49160e71737d3e22067fa8e2f38d341733ab8db30989771bc40bec4a7a34d4d88637e5e497d14fbbbbca05ebfdcdbf676972f3eb0f5ad31b54111b72519041e607eca366cc85a812588a143b829039abec0c5eba2f4e49a06b5bb36c32d1a3ad73ee4b72a7b584bec68d134391ef1c1230ee32c176583f87ef9a2b724295e6a9ca8cfb46c34665085e3462f0c44240e7f864613e89d2cb644dd6b8059895cf8ced0e391c3c73d04459333ba0529ca30e02a64c3b376f5498c20afe69209acb6058cdbe1d2debbd2704684b869503efc1f87705ae9fb0959ac97a254baf6648711bf49de0b18e969abd00ddbaf2a66de47a54bcbc85a1d9f1f2bb55279f4f976f003a306ffb2da5c4ec1369b13c0e1cf174cb299af8d87fb8bcbfb2127c73e0351ab142c2645f8a574f171354e8537b1a9b9a8fe22d105e10c539ff139d56679eda9989acbcaa88f7b6f85b9c018d4d9d68708ef364d47f525c6ae172892d51023cd507b85f47fa17abf8623c8ea7ab8029538d6638db4992050daf3380552ee91e7e0e03892452a7131f5adc4f9425cd9e1506c8db527a4780fafe7268017dbee1d276b35d55b394b8ee8e2dc543ccf7c6da213d9e3fc8532094920ce2166c5151b0e9210d9b035e6fdd103d80bed0b0179cee7703cbfdf7209a7b72fb34c6c4635894c313f4be856853c4e134734eafcda230a81d14b598c06ccc5e86cace60b67f8ab854a016fbb8ab5af06ce931c7aafd58c546919674050ad47268ab18146c089f5de2d73aa8751be2b87c82341c2b2f87f697852e8413189b7207dda151d07a2d06bfab47c57fb99c2b8486466ec8aa7a80a491b48173246262164d3252dc7720c79b9c695a74823cd5f6d49ef01f0950659a79e55783d2525f6a76ec3301ea0ec0c520d95e76e61dfc91649853155ee567b61ab1a4e9e10172f0b4d11d4ef2899440b76b82590643496e8cfab3cc606514762cdb7b0bce5d7200aba299551bf5741fbfe168b3bdd4cd61b74d585b4148ae58fd18905269af72003d41795aa0a9237f6b461c3425cc98bceeffdac13e30d5bb7c7ad1193dde7276721e5640b8a11c10faf37063d47e84696bc02b2fcd3296004c2f76ae572166a6697dde3bef9043d39913c3f9053f65492e54b6cf92fd5afb646a87b0825b02cbeabac89a5917b5e90bf26a967446cb2c540a553f3f289378449d70a1770427351c9064d8bc8289b9971a29748e6fb218bd6c85a177b90467a88306b3c55916dec576348686ef62f0d95b19c8f12ded6bfac87ffdc5a7892ff6599f16390c72ecd26f4a99a23df68c80c8e00a1416cadc82fce0c29b479151e3e037e66d457232a050984253a9858a65f1f83fb29ce770100c6f27abb5f69a32f547ecefe6458c697f4ce07e65f99809a38a01cbab8e836ffb2e0b38d119580fff054d08197249641dd800e7f6656c9ad40ccbc84bfa8ad5e92d7f568aba86f46e26562a4272a63792fb43e8ee73be2f8c24bb0e6c41d8c453b3d492aaad1c339a2cf0a3b67298ca2b821481be5c13bf4452d3213e76925ec6c836a76852cddfbe1cbe7fe8723573ba7207196474ce6186ae2634b8461214b0261f52c30b2ab5ad21ab6368721ad2975bca2b8bfab924641031e327b182b5533f2b5a925a8a859aed3cfc6d4adf361de53676e621e355d603929c4237ffb1e8453c1e1c6033535320be93137290a8530c821d3943e6aaac4dc0e1c8257b52747957100b5aeeb4ec9843709649e359e5c275e4d100e1e7b12927208b59cb4e0098b3e8d4aad5bdabc1c76f0c72049ebf52f264171eeb286edb6e8a7ba9c294612b2d7eec5cbefd20b42fdf6872f9bcbef0ff2f0537631fd527df32c4ea9c88f41423e834b851dc603c469fcd724e183bd49777d6b6fe9394e3a1c59c10aa882731c4b81c63b4e48f28ba608c0ba48be72866c387d307f69cc22c8af5a43332af3ab19aa42c4501142837f4ad722a83c6aced925a81ef0d49f6ed5599853cfe7723757186622d599270e51f832e4d790732fdc15a2cd9f563df5fdc2ceaded232d3fca7f3839fe406205210ea72a70152975aac6cb1828c1d09f8688cf2bfd6a4cf31ccf0d80c17dff005cb6f72ae2780e024790de43155965c859e668365fd249f0cdbd3d7088432c672c9dd721afe821bd518005c26aa9e020d8d78d60239b68720451c9095c3d24bacd11772f175adc613e6a273d2eae8e4b9281565a8fd31c7a80f54562fd014f3bedfb9522148802d71d6f701d7c3b2398edbdbeb5ccbdc9ee2cdfd156207d1147cec6972e51a68970500fb0a5c53f2ad6302015f683ef44487af0d296ca588c6a1d33f1efa89a483d1bb41b0b8bed27987472473677228ee2baac00222e3b5b6c2b1c320931b7ebf3c9a62d61c6a3460cf273f8ae028946a1490e65ac5cd8a10c4430e72d49cd9671155f5e5ba36af0a7446f5efbbedada8d60e32057f150dbc95d1664630850c9ff13e37d9fa1b65578b43276d5a406e51ccba558d9bf3ecf0e2841e159f7dd45ae38d30b552b1e87cdcd2958734a5024c0563b07c86e9540d59a86e6267dd727fa2722d5a7b10a293db1062096d706bb96f1a5105a71008cc119e677245dd911937d57dab7ee70411ee9358f192b91ad7f8089037cc037dd431511972fbef28d8e9fb129ffc8d3c000c38695e1db8fa34fc7bc51e800099a088ac87169fec95e44203e179b6b6716828710fbf4eb7bb9579f4fc67ce8483e24d88b9721be90ce005fd16e8bf74659d89599a6172c70d530b7664ab9884ef2802b9845dc4c74dec0cc6f62c27096f5a7ce86365eab68b58d1a021e9a931c195ec321428ecc31ac4db6814a148ee9fd08e2ce1620c4284b4a4fba23838084135e8baf2364dc9e68dff6b7a5c66bf23c5b3472342b775296ef15309cb37d76dea14ab6c31dfbe33b7f68b5e1099ea3825e5a90a51dd2acd04333ea30320fc5da70e66fa72c7246dcbe34962fb6955bf3cc26369efdc6140527874fe3db7653aaa62a98635436215ea3eee27a5d0f95cc61a06e95c71f91fcaae9724091b4b8aae6103d272d6844ca5e8b768c1549bf719b9ed400fbd9de50324c6f4425a8ee4cb19ad7a533846d42d2cd4d6e3ce8a971c2a125b1d0a794302ba90167e40794a46ee068c72994246fd4a7e94ef2efefa6f43626934976b5061cc2952ee0dce3c07ecc6ab39926bc2653c548cea58bea3d03121577b424dfb2933bbec12b7de323162250372999b3dc561f95435f3e49edc5e08513a7802c48620dfa17fe4c24e92006c9172072b09d4966bbb35cf0d6ca4cff9ddbaf37ebb93652363bf47f1af4ab06727625bdd25ff50712e57d96a104fa240af3526d843dd3412961fc77c02cc82878c53c7409e1533170ba06ebd1f4da7cace74263ea9e67d48f2ce6dbd62012b1f397228f99a9a8e8f42c22f276fac6c4c07f0f4709f4478273ce6a5d1b18fb3966e72578079e82d12855c401e09c0d024b286e22f1f82ceea639cff11bd3f5446bb6a08f51c02fd61a7f177450a035942ce065e3320a551749c27f06a4949873a6d72ba37c379c948b3526846c38a4e9982a5178c3b422167f5a590ce752b67b4307279d50990124d5c43c06a870a3c0a283fe31a1fbf9f9f93bf38fa97112a49ef720d9f850834ba80ea80a8396cf504cf661598d7bead5cb578b7a2f1d7db1cca724581edeb9c94376d3ae71e421e60f579736a76d109ee15ade533b3cb54ef5b72d0dcd72b17142ee219051f9e6a042d52769eed9e8a25dc68d5806c8fb8af7563194d957cf6eadf7d80301df2bee55450fcc3ff309a286357782bbe8159a05772a19ff0793f48f96d00e4f999488dc6d2022bca87e9c40881e80f67e74a7659721c391e3dced4e7620ceb9061dcc41db6d49460aea1509e873095b0a6ab2a6a72da21ba11f552ed1e120f311f6de225a26a0796fd0bb9d236561e0d3943f0d572e3def47227191c1b93ff29f36b3f2cc13ddf4ecaa3a65feb460d9f031f1b1c724e5cf3d51b5d78d3126f79b9e113cc5fc4718ca90d4910929f92cff548025e72827d9d7b9b3fcf2130a7d8e96180a9d31c0804807f0306037a601c9d8d0b7d1fa7a513312b7bc091e26a42730d62bfbb2b1c8b6b4a909cdc1e4da9fb7f7d6a6f19514401a79c1b92fe9737a78f1ef6ef66d68a97191392f0213d15a5c3d00f709c525d525437d7f6b7bdc6a9796b1b7e0f6c9b5d300fa19bc96feb8cafe4a72e09553eb7ce7f33c0c738337dad1b11fa2d09fa6ff835cd0005c1a97840b7c94fdc9bd0cf130f32b044c0bc774fcb4f10570865894a38fff8e14a342b61865c72d1d65351856d41b62b1326a345c26b9b36ce634387185e96a68c350d05da1a72e2499f498612c454edfc9a5ce7e1d9e08f6b48db1aecb63cbfa8ca355abb5c724ea7ad8faec25bc7f6a67beee5947c6e1cf38229471e3d6eddd01c7c247b514180636172a0536d302863978e672271d81dc9d5a9e986aed8c86ef0a2f5c9d13b819be684fc3bd70e23bee35932de78c8f3e19c776e0be4758ea14450ef3c654235987918a8dad3f097b19ad0269bce70a716a0681ee32069c7dad9f402a3cf33ef8a53d2e33c8f1dde77024b4ef07fec4b2284333bf08a363d0e41420bf488724cd7861b7f4e98c9140cdb07d597b27d3819d5cd95f75bbeb730b41b678c733027f060cbc5f2e7df9e93aceee27a54e1fc35f4d2809b69481a9de2d37880a972a925e44ea61f1a975679ca771db1273f6a970244e1ee14a6362ca5aebbf41738329f80262b9789dafcada2cab64b63088203f6f7e34d0ee5bd0553efa0e16172e05db63895c263c67a4803759276b41ac596918effd545d61d7a5ea5fec1397211c39d7b3d6934b555e9d5cfdc93c030384a2d2fa4af01ff8dbc6f78d0e19c72e8ddb17a6c4261da1b3a7bedc77b644656bfdf15f9b457d185ef150c654399727f32e5e8b551dc5861a689f75edf84883ef759972ee5c670c5118bc432a482056e5ce13e6ba8a7b4bb47416ccd92da749915ca704d4afbf59714bba122224e23f33cef63ec5e4ffaddffc5245acc52cf769ecc730e6cd758decd81fabe0d56471833af9fffa73d9579232ec145738c709bb09ac0f58147f2f24a8915316758128cbd128e57da79e7482075a4da4d22843ce977f59815cb0fd9f51b98aefc5672afc22de37cdd4b9b9742ee0406aa2ea50d2cb9a2f29178a21e167d936181ac7239e7ba468e01ff0d0112fd313fbd43ee02b1f4d86ee4df9b90877603f7412c72e9dc990b523069877210c37485f68c8155feef9c41e3c1dbd7b0190cff937727fb52ac67bc6fa8a828ddea52d8ae6802ea06f9c89dbdbd315865567f6b3ec472559c656d9e54aab6cae0b686029ff90bd6b7f0f85028adf256645c96977010723252dca080983f65a012db636393d1c5260f5703681746bddba9b1973e19b6519fc3d450cfd6316dab82c4d20e221e38f6e43748ca4c483e3851fb330d0e97725d04002370c59f481e36f366d8476c62e8c45cc036fa1fc535726cdc2b571f0041d3f6f6609bc8fa08ac616cc74e9c93b245887a18cb718e0912acf4fdfdbf721abccca18956def48a5807103623f529f7857781d8a9605f7b489d1b3d61f6285fd9f19344a3da0bae39274e5bbbf9118eb7b44f77027a55ec41eaeaa5ca884554ea9b8b22ebd7a7f65ccba9623211c60ecaeb72a99f615239bcf39f338e7472bd7f93f2278e3b53bee4f7adc33a7cc7c5b57b6a4e4f6637684848e8241add2748d851d81d060efbb00e42d123e709c6155b209986d5a12661d649467b5b1f23b43ab4390279c80a63bf3aa6f75a2f6c96a3bfab6e07b0b0ea1f75ae607955723d1c1c36dd2e47d80305e28b1e6818a72956e66af9d69d966f45ac4e8fc1417205ee49b17e1c373b89b8214edab69fbe51ed24de046fa3865b501a6c183bf072901e54e7927ad1e16fb0cd3868a46831c877f7c63e13550957336adbdf615772881143473a1a13808d84e69b1599560b7f61c5437b3aedebd68f961323d1377230447f279dcc25a3f0af05fbbeff02c9c7e7de1d4b0859320b60af4e1cfc0172ae7514892320d9cd19ffb5c4fae45135e2bfd890de6a4b21d3d14cd9b7304772187e65d4dc5f83a97a421acd9df0125d695241e3ebd3bcff7c5bb2156b73ad723f7c474f201ed3bd91b884e05136c01bd50851853fe7d85ac0e4652b0c91577216bb0464dd48f07db1a7a2aea6b4b69fff8030cf8d9d6d5d17339054d58fa9543b85aa105ff7ea0f9ff054e01bd82c1985724832dfeeca711fb9216d17df725c3b545cc486c2560e02583e25dc43e16fedebb38115b0263682830831941b4b6eddefefb69636bd82fb3bfc34d418e370b210610ea60d6ec0031b3b6a5b57d8726febc1503ebc11e5610a1c598738bf53a3f33fa8a82448a56415aed7f7ba700a3ba63e4890aa5e2fb98bf5690d6fc2fa88c1e8f83e9aa65308a9dca0ee24ff7224c9efaca1c1314a85796af64ebc844d88626b2e86132c3c793e538fc353a172de3fcaa5a2b81f11f1f9b338af65f577e109186c924bf9dbf28fae962b9c7372a1eb1de718543696281422b86225c395094d306d1ca7df947301217c00ef14720e6de5dd9c73366728c551e6220c5e4515600703b61c72bb93040e444ff12823fc29c080723ccd910b2d9bab8676f3840144d767c89b08d9f7354c33e3488131cb570efeaa87493414aea3f1df7b3efd1286536e256ed64ac519c8b790d644674d8bbcd48189455bb19651361b300681cde1443970fe1bdda6990f14f2651d72aa3030db2443e87fe7fb0cce6b48ede21994c4f3619fed5c12a285a7ad46fc6adc86d2b766ffc5b1546405902b2128f8c9a9ad032e8b62137502a02e0902a0722bb705af88ddfda0ffbc64402479c6440473c306bf9f48df6cd04db72e4465723374dfa9f38f1e1755453955af44a3a07da9e76ddf9dbc77408248c21bb3814f0dcf4beea90a6ba6624b50b1e58891141c4399254940e7a60f63520ad0c79d72acc20a93539f317637adf75710d977183cd5c04cc8e013c0d8bd8fd5278e087270beede27857e52800f9cc183a296700fc4f432f405f4a406c0486a3a03fa611e2e7d05a4b9254c4333e1f0467a55414ad7f75a1a429d106536a12f95a5e456bb13640e5ee2e7ae15ea2643bd3c366a609b685feb67bc9040fd718c1f579c072b18035ae419fe68727c8b5d52c316d6bb593b14346e50149380d31c1124bac626e989d1dc023bedba612484d72ca54a258aea5cf106bee3fcf095ae21108e444e7539c7483262402972c0156358333369e331bc05de18a917969a110ddee8b72b3a96f93bb5aebfa812a91f76a3cd47889fd7932c0996a64bf377f88edc31d4e0cfc95a6f51eb9a728c10c0df532cec6b6f9192ab8d26b1affa2e59383db5c72d7fc89ac5fcecdf5ccebe220d63e56e73dbb03678c44fb7a7f787f3c570c4a45605e5148f5a04f1f756bcc5a986b60359395fc1feac1b6aeb6d50c4927b20e0d029f2cc173f648bc3346f015d54b13f0a9153b737ebf6ab120908240bb65b814fbaaacd00dcaf68ffa6296d40f276a5b622ce42859a65ffa82f1748ce633767278defd4851e410cef6761853260e53df1cacbd00a069d2d33ba535c8af28d406b58cc65ff9508fdda1b242465a1c9e98c12b86c97a92c5247577520ae019912b00ef280456281a094c2b92725bab79822ce9d45e48a37a1c41da35654d649771ddba27c68a9466cfa0a51a36e9597dfb82a1a700536713ac4fda454ac823e872a098116c453d2a53c82b011812aaaf63ab64cb2c7453bcf45735946884df4e14c4b0c70ef256bd5ed5b38db5a971b3138e4d63133866efca6bc0e20ece265172710ce777e814c082de597305b63d557aa5fd4103846243311e8822ea2a207172279f8ebe62cbbcfe105e8be101d53933cb841df4aafecd2f9cd77291b0413a721c3e773e48f9946e1c09b499dc2d255ba719e530c66d9e077d7e20163e2f770064134dec4f9f16b3ba990b3076706ea65d87ec8bf4d4ec74a5c817d7ab4fdb38234069690086bbebe89ee01a4ecfbe341af6862a9fe1aa1c439411c86f94193aa65a36481fac3f6d45522800228c41bd63f3c745e455fb49ad9435ab7e31d152f7711dca9362aa27ba8851a0ca05176c2544199a201ae4f0d642060f087bff0a9aedae5209938cbb15e5178c381bd57a3d8255985df5426a3af05ff580f0d5728ffe65cef68f7cb386ac6acd1c05e7fb024ad856e138853dbb24f4dd3f0bd068a54ba78f6f9f5c208ab5e0ea188948d0912e4d610c309041f897c99eb17499720b1984a13838de3b355049ca99159e32ef677022a4c2a3b152ae7fb724ba3772352aaf95fa4e3707dd55ba04b02c21c21f2437eea8d7d12237ae77960a10e5725239652f704d7ae83cc2de2ecb160fbbf37e3c953e9e303ebf06cdcc38003172b736f6565c2d568a382c20a107239af329ee855cd843e27c1699be7e6d989c6f28f045a36a23ba125fe0c8e728eac2cbab67da8dc33f51acc1c034d728023d4b28a96aa89700004bf80945268e6a79dea7f51a1ae1e9427ae94f555f7e3b7c0efaf6bad6e60b5369a65e6db59da42dd77b12bcbb6d7d5f4f8fad481da6793672f33b8ddec96f9674af0b7754793aeec9e9180f948f09a25c5c7a991294c374725c8b0087fd50c6aade69d61a1b5b346b4c816a925895944ef3e42a215ed237032fa1b2d42a3413a635ba16b5107821ad996f66ebbc76a4636ae6e5ac3472df2ce9be381c32965c21cf685972233f095c150b7a8a711f9160d5f584924591ff728ea381a9ad392ca5d17d37f4b2ef1cc6f90184b2a8d85e4fc05356431bf492728af4f20d86074b5e1184b6b85acdf0f479486357e2679db8ac4fa6df0f656772fc04bcf9b4cd35f2d8992860c5936d0106f3a2797ea57e64db037c3071629c72a369d1d51f31930634cda00f36a962e123ddd65ef23ba7ce69c99c1103b6a3720770dc511a7e6d7f63b8c62168b30d89e19f77460b54ad5ec23934436d20e512387043a5f092b4d927a9b8004b4489913198fa1f2a9408ea663bea78bfa51967711b59a0645425beca4b9f930ad3471622e1fb320884929d16b4d0b4bc7dec60354645878e3d1e739149fd434425d2cb56de29c95aba129488dad44e3ec72c5d80250d7371f8dff3421d0bddd4fd3ccf5ecf305a3b92c234064264abd7389e722239e3b11a9b20a4c00e7d649ad81b64e5e3f29ada4a8f1a0ac84d610e636d7296d8ff45bc2e85458b143690a5973f239af792fa24c1ed73dbe0b4db8eaaf4379393b5f34c3c21c230829721c6e713029bf62547aeb4221df107afafad1e3825806480dce8cc727208de66f9307de3145b47a1240dd912a678bbc242a5284e696f052dde92d05f629f5142a88ff566690893563507ba60b0af956c8e0d99ab722cb3f45964c88fd228eaf676d386d68f319e4c342b2fd8e38027fd053fac3d7246618eee303cfe0da821a16d55f969cfb486ec8a01f7e12d5b27954ff0ae902427bdf6e5ea761ff68b6779b711aea626bad683d8f4d475b4783831fdfa960219009ccef7dd2b9a9db26400ea2ff3aa6829e8c14546ece3b83039fd35f446d964867e3d42ecec1bd4878a3a761959f1ae07191a4601c81b7f0fffbbad42cd1845254b16baf0f8e585ed490524ff69f4ce788221fae5edd7798d8e98740d442e17e6e42cc3aae1f5b7868bd4d4c8b446bf8d961f85a61f25a38cc9b95e22678172f88f7642c309a777172fad1d52fb5814e97dba2868f54729a6166f318e7ec0727c61dfb7e180b3f83fe08cf9902804ce4384559ca3195df1be22706110c50d72846296397325c2b2d837612051d1dae0a9344006e0e058b58af68268ecaa417219bb5310ae9215743b4a30fc9e7244dccd877a54a95e17a940d117c85edf34729d203de56f6fe0b5b8cd917d8bf3d2f63f10b4180cdffa500fb9915f8a42de72e63f2ace000f245f455441332a7ef35603de079ba35ddf932ec2d4a18431a272d4bcc54d3bc253dd121219d23f44cb84c69eeef4816456e7499d946603670472b9b90f61978e678fc5b0f46eceb3ae078c9f032bb98a7c215550416069efe472eff3d4a8221b956e3a2304716ffb08c91759541b77e4639fa13e8cfb692eca6ff3fbec2b6613300c770ff1b7acade4642236bf87f1a7cb33e62a3b071a3ac57256d687a5bd8b92c388172601ea4e887789f41b623725441f761df69fa4056472bf34c17f08ee3293ddb2c6d86aa42e8d9c88fa5d9652e63250ef5032ad54dd3b86df1a9dbfcde845c05f7da7ec62fa9535debf06f0531af5dae571456e80c972dc114f539d5c8461d71786bc0943e89a692cfb5846cb3fff6db21a6de3620143517d720ef8f031d3581972fece0855f304a3ab31b7f1145480ff7f29d83e3072c71583fda05c3be1a98e39b6ebab3009897678aaf04864e1ca3da32a403bfe3c1549d298b4aad1ad5cdd131d8722f85b543908527ac84f0b386fffa774a86e610fd315dab88800eae1c2731b76b8e237bfe9b8c7d45f5870c54613d1a9ae51725a8454d567524b633a134161c7c2fc7f587d6adbc6051f20ed978d7844d72272fcb8f7db37183dbc61b870d504818d3748772c25d04c6c47ca4384add437404872a2d68c3ac650863dca95f58a2ef540f58dacebe6618b131197251c724aa46ff8de3d70c3fcce43c4ff0aa08a61c3236d715062b7983bca45bf47488a7f0c72e03b2d73ac0dd0cfd25a8d8fd53939997d36efa41b921e7390e00abc6a6b4d5996ba8dd241268ef1ad6e5d29789116f0f8fd553e7bc341a882584db83f9c8a2cf573aa47c4b912b24cb6c70179aa0cf9bbb0fe25bdf496fde727305387ec47491db36f2182b4b030b57f92608ed69f2167e5693d24f9f11610917d5ed4262d72c217c812d58bfdd9cdc36a2686869514b465c1ae1211a13278049926ff962e2cce18fc73476bab98e1f08f8ed011a787ff0fb337054f08def0ea1666d4fa934440e2824ea1d1cb119e010928836dd603a2d3daec3a766a4b7a099f7e87c06f729573b6eef18854c622d4336308498e73acc39756c886ee97fbe1d6b21119392575c238cd9747698a5b09967e56cde8c3c9f46b101745d868f158b0001cc9b54ba7ff2dba1f64eda90d6c788f2c0cebe8227b274fb3bc52ca0cb8b70a0640a872303e5f86f2712e2bc43a398e1f77e7a7e1d5bbb10575a3616c4fd0f4f294f332ef430ca85d108664c08122285ab2a838a2b4666fc0474e14a6e4c8ceba4ab8722176666401d5d8083dde41917a7b273ee86cc3ce1e42f30fcbe1c86133cd0772d137d012321137a963e593127b255ea510274df832d5bfa4fbd833ccd17e3372c846d9161fd6628c01296ad66e7f3dccbc1acb4c42ada0c3194064c53cde892fda6d3a1862d74b8ff8f2fb65583d5d3b4b9b8a9b2084db8235ca5879c32f073311c8c9f0d017cf76f776fe97decc19bfe038bc7709dd90614811853dab985a7203a6de06ebcc35defaeb7afa0406c6ac2f8d566ea194a78caf23758557e1e2725d40d0fd712bacf42b0e2b325e4911b985c7c417a4fbe06dd873ca1b96f79750c74c7414938fb6349fe9256d098c28ef59020d6a61d2cfa7512e20fdd0a1a772ff565ccaeb064ac23ebab82197a2fb0d6638371c148083242a54cbd04624a414727f4cf9dd189a8d2c611065d4d6774aee37c8027783b3959c19c47d77e61272de8e5a1b29399add792aef9a6a245350f4f8734721252ab9a9c5e7b7ab170f72d4627a80640a2debc84b735773ee3379a4f30235b13a3e05fbde5a508ad70b72bba205b24b01c43f0e884b1cbb53eb02b5404739e7e3705dbc64d0134c14de63fbedf33c2449e93f7f3eaecc0480251d1eb6d20f33665b89667acf387cd95c7271a9b15b749e9b5d5e65b31a556e4433995532f4d34d287395651ede6255ba72e9df4a43c2aeafafec853a3a4ccbad849a1ba65ae154d0fab317d954e06e2e7252968bbe784796af4216b84442f7808e4d7288762cc1e1d2390c874316cf7972b844236217cf3d03511b286d07c7ea56ddccb21bf1f442035f0bca404b262b721f7eacf95db94a53c4d0e148906c87e038cff5c49e82c7dc4533f59413b0dc727a78980ebe6a5776e6839d01b33144a9968e4444623f960df9848c779b09ff2feb684021c7e2e96023999a0d58e882d4b408e6c1c7e30b948e479568e34df072f92cb0e470e0e41dd70fe5924948dd6b087158dbfd1c7ec39600f9ac83be1272c55a8763c143744b47b7eac2ff1816ace9883140a39e64cc3cae20f176a5f9724e7d2063cca742feb711b5e112a66e73999e64551d3bfb2284bd56d4a0a29152871955b942c1d12e8e9194b624be09b3961e4dc50c31b32637b3bf7c84bf487295a342361fbf3a7d04fc8213f69ddf17cce5970ce00940ca2c6034effd952272ce16fb0a6f64c7311c5abbecd879b190ce1cdcfe6f02688e25df39ee6ffc82726f74b47b8b74db4cd301ee55f9d448325340a689281c4517eaac4f2a3678fd722c3b548ad32fdd89e9de5a34b0f2facde671a9914588bca909fee959d7ed2b72e84a5b0f350503a732a9781c61a218954b95b2c33d4bf91cf014d47c3bfb5c7255eb94c1f7a421a035d6fcf9b1de5d1fdb78a8f99bf36e3216b28d747c410972df46d618e55ea9e392fc14bfef4ce1627ebeb31f16a18117c48ae2ef3bb21538717aa917fc87844b56e964a51c138facf6874b67a71c6a0d25ff6aff2b2b0450b6ae4c768d2ae410dba200e0578d5048757e9296e8b86f50606a5aa28b92e2722cedec4e9b884dbe67060cb56163121bc38f7a1dfb7427bee620851fbf891c31b75054a21c711f046c9cd2b68975e8f5d1ecc7786511167be0ea15e6892e847219b00b9fc29204a02417380c4f1a3e1245c17ae9fe74d4d7d9a621848331f6721e78cefa1ab3f718fd04aab2f35a9b8f33d46cd4a52f04754a9d3220708ba37226de1b451f1b19c5d02380dfc273c5a66492b36af001a22922ca0488ad80cb72f36c222e214cf80ff23fc70b28c08d73e197dae5598149bd92d13e5bb1a2b603b6ae05aa1f8c596d3f4b0ade440f1210f7ebbcd775297af73190e424bc127e72b68796b1ca50a402850527bbb47122c00aa74ca407092338f164c6b005fbce50ec08387459b4145d1cda8422589651d37e7a9b30512b2c9ec33cd50dc39e963bf610cc6953cab45bee21484f2488027f29b145f46c454e3ad1e40ba7e9ba5d72b2eadbbd62843943b5499b29f77cbcf192937e3c9e252b45452c9989612ebe0611603a0ee687b1595d96bfb0a57015e0fc854eac59cff9047f17ee7d7f667a073aa7d9dd50d00e21aad74ee5f8f6e7444f69d2b4c209f149c8eae4d4c15518355432c36a5fe3176e21c2fa3b622ab8ef9ce1d32ca4f371d6e50e09dc1fc6544aba30db3f42513a0c58ca765933aa366bb3020a7dd0fdab7e5261e99ffa75c352d47fb3f815dfa68971b396b0961cb018ba3722a29653a0008c11a90db7b0f9729c512784f6dad6607e094ea44815d2fa470013366ce6cec8856d45390badf472527f9764914ac2dd975dc048587ff3c9db2d10ba70fd501d28f25d06e02b345702dcc2d7bfb98c9c4a0efb94f7e9bd82e5500f15ae081e8f07018c135a209872809a09792d7864327e043167b73b7b7329744db45f93c1476de67e10d439ed7246e4ff4fc745a06ee5c0155351d8ff7d7fcbca8d54fcf0c36d368e8d3c433b18e14e589f13051c4d2991e2fa932c57e78e1ccd17f637a720c5e4e38fe23e7d72d66b25fc962bce6963015bb3ea37a1328d1546771db9a57d5693f5f0501c957285de18ad8330ce9050855978c64b937254af64f2bda4255db49c7ab09cc8ea723ef3fabecf08406d32f2860c479f78b69adf0c96da0ac8ed8a343a37bb535b72167bdf76a0eb9cc46d5a1d8efd47ca4ad5cb81794b78228de04186c71a82f00549d98e8ca41a89720fc5b7f494c5c4cfd64d836677f4204c49ef17a150e59772becf0f6132b0f9d22be24f5700053626a9d512ee89ee00ed27e0200f9284d272313a3a6e5788feb4b2932d4a3a278a6519cea3a1e1724790b9d1506c8b3bb472063def27029833fdbd08d0b1153b744ddfe8888f8b7664c07a917ee85d0dee6d3d8f1764491fafaf2bcc50f7a49708baa13e1653f6e3152a5ea7d289ff48705bebfd7d5e2e0b78a191289414c43a1f599b094eddb556dad40eaa8bcf9698c9721e08fd1852edb9f18c952ee59fa226575a5a70baf2bcbcd18b1173459a16ca0f4aa349d185ece779efe1629c0e933d7f30f6b08e5f6b564484f6ae089deeb3438f6bfb9083d02eff93e130f4941b9237b5913ad2a3313abcfb430aca78b622728df3ae6eda65b8f12d9753f5e65f81298b0a03f158eef566ec9991dcf3e27211f50777415612ef1bdb970250c39500a40800890f3411aa06e4c05e80a0e98d722cbbc4586dc7634ece211d3b822c21de427a1ef27d96ff8eb90cecb2b43e9809d4e442af24999d7a866067c00ee413872d10bd6188f39a4443dee6782de6f0723054e378667c37df309c737166a61da3b3d905bf7bbbcf2fe0c4b43958f4f5581db7382c70e3ca1404310e105586d496297e989f52844a774b54921f4b3be0729e08f28266719a6ebb168156a103946b0f5c3eb10c867696d7cef1660c3dab72cd99b0be189bdf5712ac87a5d90ad94ce6f2fa5c39727fea8d8c85f0e640f3724753cda102d5a24cc2a19cc3bde863c68f8db7ce5c67c983e7933c46b7602e58d04bd3d4747b266ff30532e28d13eca58cf40bea8b32ac161757994de40e3e01ffad9d4a9736efbf2b24ada8f39ebb873d05911dfb6a6d2f66b2a91d325f0c729d72d46a5f43bca3b3953367d2980d6a468ec4a8a0f29533193f71d4973a5172d12abd9201e6c945c0fbf01544eab0c564135280b139f68b99c0b9a243843572396a950f67ef271dcd7c6d80a60aa6b5d569397fdd3b62fd4c81886f1071471465c67e9c0555da570b61e3133feee107b329cea22019deb76009ef9a3fc52f5ef3f1dfcd14129e63d03a062d1fff830d4fa84682ac8800b4051de088cd544e729f55684e3878c4a30fe7d12dfee5f8c3ba68c0396fba92f0185502f0dea63c720c3f4303beaa0b793a04abbba82fd3d789d97f6940c3be16c5472bdee170b1721453e3c2fee45860e0f883db7213b111472544e64345eb82a44d6fad8f58cc58f1f860760dade5c35ed11bc5932e97272858fdd703669b904cf205382a7112023bedab604712b8a1fe3eb857617628d59513fd9f79243723be634897786ede0cf8282a47478f43b069cca96ecc709a693d2452b7cc489e953ab5064ea5ad8c7233dd86f162f4ace5bbb3ebcb25859450643ebe6b43dace5ee282baac6fe72218da1d20fd31c6b01d5bdc1958286e972673ced0d0f0f86f7403968ac579cb4f3acd7f5e31feba15e5c89e8f996f1bc36532e9ce4a5b4f918412b4658c4603a50b09c3baa0ddec6c887637b7465a1f198ed76800c32f27516050d7ba386e05da72b8ab93839c3306a447582483cef12f76e9021e62971b99f731a810aede95d05b833de364ff2a5536eede815677770968773ea346b197a745a6c5f76d639ee572b44faddf0c9f40d46623ae8c417c562ab1d43dd011eb7f24c82b53f887c6302e0fb380e20509cc771ddb940b9148db7aa5e27ce578678103721e30b08fdf391dbcebb6e39c5e978cfe33185ad5332c236480ded5902007e13bf01268415c32727d98936fa1aa01aaebfdbf45f7122f740910e8042c4bafa8cb63a0cb031f7a729bfc62c8b225ec0c0ff3992d350cec36884decfe952127801388dc7e19c5b70bcdc9bc0a1cff7cda22683599c75a35d57f85e3dacaa16011607af87b350a660dd80525aee2d900d48b94824289b1929afbabed4954b49c299236ae1ba412aa72ce1cc8749c38ec9a94dc932d20f424343bbeefb0571e74f2e3204fa6a1404343b42820890ea463d6187248967e2e75a19d0707a68936004a65b17a79a282297274aec2fb477eafc238ff6b6555439b5891c233b7a71ff1082473b1ab779dba72874f1fd72e62603a1c30db5856d6e670e7f2f36fbe406dacdad92d75188b68677a39ee26775016014fe9e73edfd9392b021c8c6d6e57c4f37e9a30d708db6c2ed40b61fc5b5fadf4a288eed9299a7b62ab7eae55b8d187a3683c1b006212e27226c1b5fd7d151ed95797f77f8ec87e8839f2d1784df63cf308fff9b7f2c11172b5895920f47874dfddcc43266d63ea691877663edd6aa996a7e7ec6a3723ac207b26f402c6a3d7b5ebee9976345ddb68bd3717992b4afc2cf47a3d6c1fea4572fac6679e40b82edc47751a023fc8a66335bba35466a4f55c30287054e23af5722cb11283bd56f44cecab20b05a2e1148d638ec598c548d1f5d6d37c8f2d61872866476cd4d26e49ae08d3e15f49a02d740002619515d55c564198327ad24c911b66f68b90b2a29b9d032424fd4606db6e0c9ec73f2ed7f40021586452e179501a54a1e7ba18eb75f954333fe27575a25127f4bde1f8026487e2f436ad6eac07266ad724ce982c5de5c57db427c562e7ce025df4f5d59634ded9c4c2c854cc6530acee76a99081e83a2975748711e706a850ef9813488f8cd5cf9fde9d6e2797068f297ca82f460a835435c038bccc697dd15fb172344074d3ddd1d72c994a272db6e106837e132e0dfd5a9c289e60fed70b647662fc3b899886222d532c931001c83a7274e8e40a340f8640aecb096fc31df08fd621af81c919ab4121badde4718154c48a3b3aaaa915ab20f51871cd53d9f962d476fb7b6737a68f3342ee972dd5d14be720390c2a569010d8ace6e06ac6a9a8964dd71447a2281256b14c57229c47db936229d882d79510d673f447c2fdcb7c22d77058b73b1312434b3bd475ac15068ce0f25cdfab13088033682c74e494d35581b34d83563f0b28c685139e6d228457569115a013535d2eb21cd8f1696a37582fd963a1e788e3a3ac6d9726d585416aa8ce73d8dcd319ab499731894932d615affb46c4977c4527b6fd172863dee37d5e481f1a0bd1107dfb5590de88fd60a480cd581b1ac93bf93b3037267f254bb54ee7d97fbae8fa504f9e2957687dc76fd8371f3e64f427cfddd263d8ff78adda540e3e3fd5a6217709c9e84aef754a23adac3b72d989ec82a00c072189335c604de738c4249e1d75c1c91e4163341d514404fe8d9706f0687e3cf72821169f9c6c8a2d0421e9ddf0981f275f3df5c9739e7956686f5c8b651113d729c4fa9c84ec98e1ca749d148ef168653d514caff00aef011beb83eb5756b1e12a4224eec83e408aaef70d659edce9821d744caaed3ebe371661520246c5b6172eb535ed3f74a33322ee18a713d7fdd7505b99ab1f79ab3186c603b7bd32ee5721a03aa7e10de0036f5c5c42039c546dbfa516f801b516e5cdce6815e33de734f0307cef688397e895486f63186e00c46aefd42e8483cd7ccd7ed54da92b77e45fc0e9feb9ca405ae26d43b1e1ee46fad75b98aab8f69c9ff0bd706d7c760e372b39fefcde05cca30e96abaab374686320fb4ed2bffdf1631282f1dd6985e96344fbaa0ba8a57860a0f98347b71ea339630d748d39e997382af74ffd8ebfbc2727aac53d97cbd046a083feb8ae8fa6d2d07ff6063a1f67e7a4c5c63f5c27b9c6dbc67518eae9894f04cb09a3dc0bc40b1f208c6c04e46d1280d0690c47e70a830aec050f1025cbccb50bb1714af2eb1f8e6aaddb327b3679d4ef20ce89338050818571be0d59b864a8c88b457ce7a033d68c0cd99ee9b9b6ff263344307baf77284959ee2b3d1deece7a80e9bd344d43cc4bbaec94534d15b428e09ace3d96e49b30b9f09f2e4de22960ef7934f2f7ff5b9ec76b203ca4eb422ae61919f9ae372d5e01b61808d68fbce032836f76ae6eadef210516bf63f6beeeb03bca05a00720b09c90bb200d65924b02b7899aae631018cf8b870ac7665f99fbbc010c8896ea2f3af0e2e719cba749e17aaaacabdc443463e3bfdde9ae50a6b96fc55c7ad5437d81666eb2677da963ff40cb4330beab96f66c5b6445877078099b705711b4d0dbb1707e0d2fd11a99d6097ecb3d115258439d162876b2c209bf5d3e3080571e1f2e65cf282899e5f7d598e256cb63de38793a299436bbcad6570822b9ab1724a34902cf72e82fd48237b532ace570162bb27cc8339427fe83f06374d72227241ec0fff68d6a79185d463f38670bba7a43a8b918adb233300e76499e70334422ecc7dd59f39c99322c321a08719d0c9e559ba6b645e23901bcb1f17fd7c787211877ceaee09a8d04d398473b89233493e1a55c3b24f83e04c01dc50a0f349726087446faebc0bbebd76a3de88cb4f5bf66c9e73e5ae7e8492f137cacb12481307535ccfb5141193e5e6bf0dd951383866b4483e5e9f79fb5ec2a4d21c78522bb79f84c0b8b42eae4ef3d281acde1377d1020eab7a933a3b96e9c08e3c4166720666f25dad5964ffd8f4b394683b2da39ed72050395171f52e08c558048d1872bfffc0ccb433e70bb4fd08d4786e384878dd3ed23cdbd630c0752e5ab3f3af7245cac2e9a56d631ed1f19916c4dc25a36425840ff734cc76de2d9a0e11493672140882181a22b6f99a2d5f2973be3f5132c149832d3b698df2d8a9c5755948720acb3fb65b4e8b7b999b8e6597ef54248496dac7d28fcfe9928744bcc2f0af58aa33d9d473d93c96803a411ea3fed2f829e448a04624cfccdbc7a9848c69c31b7e7d4b4866a638e55a36f973fccd4415ddcaf5356b33c8fcfb4e176e38e0b02379e566acb5708e9cbe337ea814be8655c66fa22e775d3078d0fc0201905e9327ed1f8154cf2688408c6c1c2120f967b53e0bc21066bf073d7acbcd4c2bb57f72c91e567e291d697cf793f03c2284f66989d173454eeb293cf3a946dad7e2ad3cc15dcd2baf3ac4912f80c72e12fdafeb32fb66844ba05fbfb584c411ca39a532df41a5f0b3b2abedc6093813ebf140ab56ab91381751261352d33376af291c72ae3f3cc0f96dd46419e1c2a4ee55bf461762c3907505ebc2c224b355ba17da7281a4a1b042717bc561710447cde407d8bedef71eae325640b152602021466172469180ab2d36e10b4b8f6db1d87eeb0d0223dbc1d0d9b94f477828dae0bcac72cc8d5cdc1f393d78b2ebd9b23173d5344ba473db6080483e06e485d8cf90f672b6d88822027a7fe36e341a0bca325bffbdadf43f33b49ea75ccf8582c4df1a72b55bfbd0f444687a064fed8369a6cf6432ccf5a54e73a8942700d486c9f696621b00d0f9da8532b21519c6f9394aa909cd360d1cc6fb0ddadeaf27047815490377818db2c441464aa3af6f8fd29b0fc55d86a5eabd53fbff46cb7c9d75257c72942878cf610257b1860560ca07f6cbd30f026ac5259d8ab9ed56d8248c09db6e9bdec077bd5de7fc94e15b6e7ec87b011b84f33b8ec0ca3fe9cb87a12eec22726a827c56115f8631b7fc13ba306c1698508b74252c20358582317de742c439726e903ddb3cd4a01a1e1954a9fa9a3e1402d3156fb8659b76a1359fadb2787f684627f0c1ae17945949457b84e9bedcbf954088c9020bcc058d344c9dc81698729d571e4eb6da7ba9d740cb2ff86282452839ed21fef500909a4df1787b1b2f726e5429c5265f19719bb47071e3ae9de52c703c65768c5e471e4af9b501510e114dd42b350a8aab0943cf309b494872384a1c002655ba9685513cd3974720b672f7649354c940f78331cdb96e05e449572adf921d30c54fe19106b51881996372db39b4b2cc494a27ada1d1a2c89342b1caba89e6f4401adbf2407d6bed538b0325ee6590911d52453f21b6d31066f252cb50cd5604722d014b335f8326932b72bf2cddafe6dfb924c0379827a35ba4191b8c0c4ac04324d8ea4b1a3304b12072d38ec01268eaa53d89e29565ab2b88166fe176c21e96dc193ace7d4dc7bdbd6eebb2c280fe87f30b39d6d4ae3384e817dbbdbc48a2998243610e29561078d17222c645b6f03c2586aca9cc8b8be1f2869379fca6b1934a68b5d7c0d15f4c2b6281a23201d33396ac593f28ea0a9384b2dc6f85729dda88c4aa75c877c53d467256b05aad3a24a2da76e70e9fd4446fc61666eca013f55c35a2bcbe3e23c50c72fd3118697cf70d7da7e446d1c416de8990859180fb7af5ff7072ba7b273daf11aed026e49dcdeb88aa3612e956fa01744a98e29f6344c200b4b11fd52edf6572cb569cb2579df3b60fcf48149912e3eca688b2909621c431c4342655cdf8d772c88f1267bef0e4b511039c098225fa90e64a675e18e0c3999debb2268212c0728e0bf2d0574e2c8b13886989f04ca3c15dc14fe2a901523ba2378b917cd7c272ce157a2cedf42dd97b3c787c97a6116ce6757c2b79409a7f2462e580840f3a72ac74159eb5edc2cbf9d8aaae61adb4ed1ceaf5db6564b1558ced9d8f681a9f72b6752af7fba28cb468b2c41c90a7a2eb16dd6a3eed441277f97adb8047fd3772a81ebb13c9191e3144b8db5be81ac8beaec9e9c297764bb59984f10913cb2e72782f418bdf7bb876737755da9980402e62cd4ad429081dd365b8a2f11f10134c0c07e772d53aaf4ec6fbf92b083426ab4d249233a49dba214e5521af82876d5f0b0ca0641c6784c3e22a49240b37cdb3de53000248abfa283d16e5814f86ec7235995a0c59696908766eabfecf8e8a73bb80a3c13e0fce5278baf06f46b56572ea39275bda20370286a421b486ded12a86193335934c2af32d72cbcfb301ef70cb918deba9f1658225a6463b3bbbdd1b5e8c9c496de472fb2c27cbfe0dc3dd7269f79e17d34cf08148932dc21856bf87425a871081761ece94420a3699416572f3e3fc0cc390001b858e1779d73c169bca94509938a92e4eef3940f99d2fb9724120713bfbb0d4b824457a684f6fde4024e9f277df42fc9b4acd341397fd1e05eaafefd5294def4c5ce38c09e73ef4d37a9d58cc69021f0fc30a47e7e50460728f25e2ca93f3f89b689e274d517d7d0eaecc37e94aaa3ccb5c44ba045c2fca3fee49f2878936faff32d59de59f6575e6b3b7d61322410e723a653100801a227200a50b525dd3b7b5094c7afe2c013047c10667fb0d98eade492221bd5b67e939f1c92364b4a0638c0a4c5280605ec401fff15f61d3d1ccd83101b80a1915cf2a1930968f184332c8178825720d55378f72bf9b83d4d1c48e7401459379c7f072a008f1e73ae8c0c461abdd911db411044f3da7075c851d6db8aaacec095bba7242e227b3ef47a14e9d118f544f3c6ce719fa99693e40407bbdc3dc8ab6c1411f745a4f400fd64f49f3ecf0ccbbc1ca637dfd9c85b377ea1c47d69b1dcbb7f572e3dabfdb3ecefb329de7593fe65ff6d08367b13ca041bf7e7bdd507af1bb3e5ccc13c5e3e0ca56e88893e9caba066ff4615412da2b7691f0e838a4ef5078ee721f9c31c91785ffeeecc5b439acefe31260d5008976f929c16623dbdec27b030c3037d774ef65dd9f2e1b650271184b1dc3c74cd718cd9f641d4862e18d45d672b15668fea8a22d629359cd58490305d31b8fd13cbb10cab76b9cdb4d716e8b72142a76702b5df5fbcf3ac6858eb5f313857e0193160babe12e5086e0a36de867a81777c63dff0943f5f2b04c7b28444beb346ca4ea30a9ab8c57d45dd3a67b728141bc2384593a91951d2072a857747420afadf68598d92842121fc3b64d337292077e461b1990c66989fc3310e4a5b0547b2d61a5b823fafeb5261b7e79a772cfc56c6511c753739f83d0832b0aee5b1980e368e3a68044b816dee01a198b69b2274447bd2c63924634d15674e53422d59ba3b51f598d8a6d681d063484987208be1ea07a6717245f0d899fcb560c04d73f72a09de8087520d94a4774f35944f49423b41d3b0a518c72c32cc0b455ff4bea04f258ae0767ec4d6c30f6bbc9362613f47918e424d741fffb7c30f521c493836b890e9c7fa1f41a2f6a7ebc8d2b05ed2e8e5ad5259025a738cf1f5efafecfd3e3ee9f4b2a75568e685127ad1a0ccadcc9740089070776880c667aa592e68fa673d98ec32229a455727b6ae8907274d153a06ea52187c508878e31123955c01f907238365896434371bff2aaf03d05f7329fe2a3d7a0f909198d5e51ebd63b45560eb4b079aafe9538a3a63237722503954f2d588e86aa8aae1f09c1f3a334d10281484b3577ad80e72f32b7f1254ec674db42e5ce1f5bfab44f0c388ef9669e849842f5ecf498e7b5fff628c82241a01c57fe18d8b54921c9d8f6ac78a8468a7357999d0bff4b07985035f5d87293bd43a1ccb8b3a9ef0b10bead7af72879931a759018020a0363abc06be757725cc80d21f5b11338ade61cd5d17d4d4774cdaa798ca223df9c3f172b2646d972da8a582fb46ce0b4710711d1ceea80114b52eb53bd20f425fc237949b8a79135a0edc2c5a77b830cd32512b461f8c209fa16ca04d7d490f9ed4c940a3d094729ac30ebf945b2d56e5c591f1ecf3e6127cc8229cee3a89498bff1ad98a35929729744f075e375a38e09aaaa10a40e3b467f9f4b132858e03d9f03777eab19ee7237513f0a1e354729b315fca710a0b3a60588895556cc279b3739c5e9bc887263b6e34cba6f536805c685385df4131af619b811d511ea14fead0fcf67eab6833a9efbf1c31682391b7e8bf1c862803c51395e3c263838679bea4a1750eeabe97295ebfc339b65d75e9f5e6a9ec052509d66bfca52ca6ac6b7dab8f530f81e18722c0444a340348275efd4e95274eb8f553b41333d7ae774f32eae0fceb121597285f16dc645e6dc4258abee87983aeccf052b47a3291777e10a001866fa6d8772a2b2925fbd05b5ffa41980809534eda349de0ed885210de4a8de7338f0d84c3b96d16fdfd344b0db8555f3b8504e8689e02a4c778b18cf307ad2ae96cc6fca72c50f5451a3e38b904c7287a1313298cb9cc2483d883cb739d73a597b3d1f127224dbe5da48031f4d7de6089a4ca0d0317a3dd732d91a2e8fa0f5d84d2252b1722016605033012b0ea194929192135b8a7edc603d379541011982a00dc10e7e70d243f69558e5760e5fe23dee51c1f1547d3c3059defff25ed5e6109e8026707252e6e0f321040e8a5f9bbe0b2e2cf28627a98c6f3955fbcd99b284c329d408726f4205cf8c67f647770120a6ab564957eab480328599b8c494f196e03a43097234667ae875176764612c3cafb0744bcc14cd26cfb8dbe98dfe17c389637e577220461964e16f86ca247e7600dd39c7f74ca4c884aab7a05dde32f99b7ad2557254a9f51017d47916bd518c7e72509dc1a5223b1a554bc61bfc54edaab3a481729e4fce48c330d884060253b4debc5fe706cfa8d7a04412f30278d8ead2bd316f207ad7f0659d3ac2fe8a85de03ba13a672925073242dd8752eaa8161aacaf37228cf937328d4440ff67b570ac7103acf1d09a3188f6c8f3c8e18f443fee6cc725d6abb7dcac363c63c2ed30eed8d13619378275e1a0165f1bb6a576c64e6647266cf95693a80d46e2fa9b7db634ebef895c375585b9273e973cc4c8ab9f751728b894222f7ee8cc0dd7922fef213598227a52dfc28ec720a749487764df7f324b2fbd97ffe9016bd44a06215041a32c2647475f6d01a98161faa12e595382a72818c60867d0ea67ff8df4eb89e23aea04f40cd8c02924d253f7a5aaa2003fe3fe948c148eb05ab0fd515d94a6c34cb9ececc3ea06e9e0d72a42984660b8d6472a3f78f678acfdc944898a5e2856cd850a23fc982c8ad62f50b62ff1e5bf9c6725c0134e53610186735222206bb3edba13dc48016691b5289238b496f29163d725c6f32c423ea0b7aaa5991655d32ad22acb7be65b2bb373b249d12d913b86f43309a931a451f067c41589899d53e22f0811001a228dc24b855ac08b7ad1bf57202bc24010539a4bcab7db0e8289b761518bbe36720bfdbe87aa0bac0af0fd872b3755826aa6b91411a7f0a2d765c0ae6266cd8755ca98e518f80b3907c51fc729ed1d12df150d7617e747de7d236387de0c5f7c98ccb37f4e175bdd506817e2624967d28181c8b67f178ebd5487434875f1e80d31bdb1cf7b40aaa359ab86472fe9d8b313215451b5d53edb78296e95b654107c6c811a78e30ffd9dc50833d725c0b2c84b91f08b2deb7588bef806520eaaca438a03e7dedcd5f3e0d4f34e433fccc40f790e09a5564c47c6fa564ac5ffc652bb289015bb24fe906bb8a0926725f1bddc04499f31b5f99a5dfb30eafff18974c82733f07f2d1706c5a0931c07251bc0d097948553d4367582c1dc2bcacd07a2b01f2c99f9fbbfdd8795d43a072b43e8ccba4b9f6019f8c97c6dd54ecfbc6e1da9ec95b7a73c35ca77b86be1d72054d613e691ff8bcacdeefbb42212878e14fbeed5f9958784aa1a240eaf0e172d629952118eb087fcce734643ce2b8fc1aa12d13e2fd9ed37c095f847611fa72f7fd737b5a169150590bf64385037b8bb7abb18c0d31ab6119bfd5db3fc42c192bb5c6ca97d89ba3c7fb3d368d6284fd2e5cc0e4294e35cc7ebf29c46457dc72747fcd4dfdf17556cc7bf06f6e86da8d64583996f37e98fe0cb7a14b6729142168777a5c1f52d2f2572a2eddfc370438c2660f5fe951e9f871c8b2e3cfe36a725b433997a67be99911645ced29610530ac550c60c0ea72652b1ca9070b68222b6670ebbd742931564a9489b86d09fff94f3cac5f40e8f6af4b0e9fd566da4a725721d47c0afa130a6e7a33520ec129dc9ae1a231e48a216f007401c36cc9c96d7d58e9de0dc19fdf7e99f38cce7d4192a2fa1183904cbdf7e152e28315948261adf3ee632c40bea47f3964a2c72bc03133530103faa0693969e1edfe7f5e9e2d4854795d632a254d014f1ad6c13a6b5510c323b499b51f5b138d8d9dce8b136b1af6d61249457b25cd38900806cdd824b2c182cf7be25b26e8b0ee75d17d180c1a010c59e5835c3534557edfbfe691d5c8b826b56f4d6bec61b0048c5930944663685f2e23d949a56f1a2cf0e8c1d698f076d84e432c0b45aac309b21f787e318a7b9f0997235ae5e8e87649ac46eea0e4c2c13c2e07f03b558bedbcdc7e53729523131d1fda0fffaf2892d7525a3652b4722f5a6cbb3b8627f1d5751091594ba6f7cadb5b10fed972d1e59083c23344246dab01fb23345f3d12ab778fd3be65d071cfc8763a3e778f3bfef0b99819398a85060f3e951f3b928b0808c666060852f6b0e0d310df11f0a55ca63562c0ef010caad69deb1ccec184bb26733836727f1fcdc9fbffdc93758e051693fd8e55558e8842682ed4f9ae413bf9946af3642f3cc3de7333a0ca0732b216691ddd2eb29fce7d1e1979b9accdeb025335c87294198d06d1e4d4a685c759c09fd3cdc3078828a74acbc3f5219bef94b4fa083df3482742df357439bdc8a301fc7a3008d7fcc29cdff8256fbe901bff102c077218e0e7e159245938b4653bdb6e5c6a374874df23790536d6af79944297f98f6a48e6f2a86b92fa8877220826563461b7683da34910e96797eec19f3abf3f512ed12bb0fb85401a48f228b6eb18f68039d376860e35d4973fc3129532ef9a0772b211cd3d26c7a5b7c4903c7fd491338dfc2dbb197582b407cce7f9561cc4634c73e1c29466d66b1a4ca1cab5da008bae84f83ad724df481f32ec0a566cee621d9acb0cca52a44bb4713a16d9bb13e5e1e2158de21d2da45111068d37865f155cfece4e7120660b8febba1cfa30ae4e9490df4bb9711f086d3220317c03b05d7257e5c6cd660d24380bf5d3e522c305a0a8af08cad9a4924f06bed776032f1272711022a37b17fa722e508d371a5893a2763b3f24e02ee70b474ee40229d87d091111c4783f610e23029b4b72855ae6e6de74f51b9599c06cc4b6c16f0413587249629e5cb6fc97b6c27849fc157058836175ca27a957be13833fa7ead2a4bf72b04091eac5dc5f292526c3384c0e8c35d3913f7db6957a761e3019bd25eaa07224d45c2f1758afaa4d73fe78983225935b1ee3d4068bbd7b104799563ae74709f240a39a23ab66b6b47ddaebd5e6a5cce628e3a73122c43ee1f9e0ed05093e723957c2b1d82aaebf79b078b7c2addd7aedd192fdb46352d64d4a5f08cd492b7233d83a5aff545bfaa74a00a7c1ebc93e99a48577b22a61a321c7af85ff03a00e392407e6ed4d55958d3810cb225e55a91e8ae80c971d0371ea05229f3dc1b472a94cf0533c24a82be9b5c6a92b602b2e46e1c29f1c759e3b59f038048c7272722bad27b7401a922881a3e761a37045567b6a64ccebd174382c67d6116bab543be8065ec09d5e20b2336f9f2631e78c7e0a6f65be792caa97684f3182c12adb72e7799d720d6596a201cdddbd0682fd3bd4c6ba464e8463464de6eb807d92c672ac8f2d530422929f9c7987e54d669959d83df23b9636c08ed586b3a686915d72b054be5d40474e0344047890067a7740eb0f570023682c09467e53f221db4f728b85e722fa0c989a63cfb5c7ecca24fad1bddfddbcd251a2aa08d662646db952ace313bb280192116610e96c846b3f88f9faba4f78f964352ceed1eee62c9872688a77799b9b47b8d2f666a7ddad518409b655c83ae4d079078bd5ec5effea72f39cfb793e7819cb5d1143ebcb9cdd43a0e6114176241918026b0f1b1ba41e7209edb863cb3e4a23fcb952ba5e2d9b2733bb697c8a6903f5bc04112ffdfd907224c6098b6607698f3e9b45d8c25953b14916e182f4936ce415d200b90b97ec5a83b702fc8e4d6847a9213b3fb39276754c44c53de4f86c4797cd42e0dec2654d3e54fed44015319bd6b22d8aad7851745bfc07fffb331a10c53240442ca1ea0385ef3730a968e8cbded75b6c82f4bd531008185bf9ab25984fb7917d11cf47566251da25fb4ec812ecfaa9a9d96f945eac38cdd36ba18f8d25294c7c83ee2d66337fd3443a99c1a8440ac2bffe540dcdf348522c88d002fdb210096ea8f71c6c4d9536ec0085b082643abcc737be43eae8c18941b37a6b4eab8c3f4df7b37e7258d4cd9a399a907573659e8fbd7cd93eadc0cbae716f6cdcdb18b4c5b1d49d28f8f86e3734ba15a917559a1009f04973779817994509ce93c385e3bd0130e6023899f57f5124398d161fbe63842fbeef958da46bbe2e4efae7ea84edd2da72729df64c79c8518bf34ec828c58f9b5e3c9caa78fda2c46cdd4442492b37a65809cf52f320e62e204b1c99734c37f04556080f6360c256f6e13531b0dec64595720e19e0c8785eb9263325417e7d6aa1983583415c33ff68d4ab7b0e32b70f762f03ee05ca322122f22e8e0c014ceaba9dc3b8d6e1634f28e23b0ab340d563bf72d2f39943c8384c961f47326a6bd5b5ab48e9424a6a57eed054d200da836f4f2e76819206dc2d5390f95b8d8ee6340bb8c0bca8bf6c2cce44ad7945a5796a3e72d796455c0a9cccbb1a0d0b34fa917e06d312f179cfaa1ab725d77bbe9af4a8345c49a8a19b553ec647db2b9e44ef0be8f15e1d9f9f1239f3a08016aeb7b4bd28b9f73b4ceedb267d1082f32806050cb7b25dcf8373f0779448b43c8225b2f103aea9eaaaff60c3d0d309e9278cd4a7fd8d98c0e9bdde99065481c005b826de72a8074f3588f1f615c61870f939c54cab94279c287848411752dac3f570ffef72797ff58c9c57b4528b7af145f888e0daab0bb9c755a416dc0b8c3a64df8b4f72adec2341a16a126eb89260829b892066e31c14ea0ba539ad0efa1b311588b072414c1bd7758790b8d4d87d5429f3f31f822431f5f5cc4ed1afb00c1fdb6330724bff9060f17a3e2ca16a638baeb5220906be7c8db4a424ff236ca01826dc3d722679e8613836cb17e4fd1d637b48d6ed5b229ebb454e447b5889baf12cedee46e91b234d1bcd5db211f39b88a3c205f191df6394ffeb065d65e577c414b5f4727d35321f6bff8fd8e482b0482c0883345b87156e94f29ae710396b97f1744e6b5a68611918d3f5d4232abcfcbf83180040b00d6dd5c46a327dab4f887e25355ca97cae8c0a10e9e95c08dd20a84338d41d6bbdbb9b7f579acffd8b5ef9ef6a72272ff85b9544763fba848b8d150bd124f7374984e429212dea7cce642ce5224d8c3ca684ed1a34a5b18568ae52230784581556bd4f848227545053d0a5c98b7298847c02295a4ff659972b9a088db1fe474e00db40c90c1d1d48a30837c73f72e2dca89a5a00cedb1f3442c819af99d40c2e03c5e5dcab38df908b4a7909dd72da7f14116fdab676e462b6e578d8739b16d4a763a60f84b07393a01a865c8172fb7707b1e7e0ceb320d3c2ee2a30d53746520a789fbe7d7eee9e9425255370729e78d702450d0e5e80f41c8a2f4f5e27702af2a8b2f6a768fe1f1c169aa8b27295b739cb2b6b62957b643ea245f646dfe3c830e2fcf84ce2e017a65fadd1d71966a78d7dff35f5e122c6494177c6a41b3a3606c7958b906c016b2d0621306e47f975e083dbce908ac538664f8f222630a35b4d47a92d35fbfe6c2f267a8554724e027d3d3273434ac602c4c1d3438acb9dfff85997715806939f50fdd8c7bc0bf63bfdef6b86c91e792d8a2d24ccd1d70a7d720133849a2b7b04b532d9968647dfbf55a68872dce8c51a0f78c626b56a919e64899aad3e2c26b9f9fad575ca489aa8f97f13b1a41a43c24837b50a4b896400c9dc5aeee01bcc8326af1b306272b1d26c6c845e4096fff55e2c553a31fc39ba3d41abba79cd8dcb0bc907e09c725d410ddd6c64fcb1a2925b75e65c23b2c352295d03de2e32440876cd25a2e572a2f013eac262ee8a4a67bcac4a2123e2cd17cf4c178c18ce68b0d37ad5873572f13489012de4e4866ce298363a9d522d38ae73840006548688da7f2753e1327260570efc16fd47616783d4e0175eae833f1516f9ab6562dfd8c85f88dd250d7279dece37991ba2a14a2e1e6ebd47cf3abdee7709f8aaa4acd6cb295c1720bb7205cf84ef1bc9a75276936bab445c734fa93c5329a8a624d79fbf65a98763ec727014109694012f6e8a8ddc72c89535b9af77df04e25dcc0a80ddd27015181f72ec311dadc08226cdb679e829a04b3584c30a2185e365b88b699b3bfd91b69e72f1440194f8e252d640eec9e33eea29536fbd158c7a362cc888dc485bde284172164ee7d5c1f78363dc6ab29d0364bd6000f5fd74c8823b0f1444ae969ad6627238cbae0846241c437c1622ea11715251cc5023ee071c2109c63cb68ddb9159721a46efb121012c55a3b854ee391928cadaf3dca5702b37096088b3437e46db726f9a1f4560c1c61e9442ea916b608dc1e413a4e650ad6803d58d62669cfac6723074d8b8a84b02cb865c08c96af5d1cb13b7cef564005ebec42925dd6ea9a372534a9d815e953411e5f20e9bc4b54b04805072a1c7d8f42833d773893cf5fb16e7d749f2757db2dbd1891a449c1742b137b4cc18a2549d76a6fda1abd200f3727b7eaefeb139e00672e19f48be7d67500f849ca2dd4c47c486cdee1ae3c7540fdbae1221457f27f959f040018abcbb821fd9ec19f3db452b8ef1f505dd269955cbe6d1ae1ad0e3cce4f98c24edb4b9f09b85ec4c79c2312918fb0bbe009682724c6d15128d2d2515bd09cfab1cdd2e0e7ef2c0543cba348031728ed23810c87233505984368229fbebc3b5be7c8b28d0684602948e1a4175087387abf5e10e726244dc750f7a2f72c6c89d3ff3af7ab8e9d152ae8cca5e08ed0eebf1a0e8d772a57b8cad7469cea189bdda83c03b2dc0adc112225dfc93da69612b334bbd1e727015898745e2102b3abb31ab9d559d6c1ac95a3643efc3b96337b2299e660f727c44ac824c8471c985c85289fe904a9d2789538aeb42befd5fb55b8a1b97c272313cfa25c4c26a89752212064db5616341238847c40da6a996edf64a5c6c7b72ef232d1d57ef04568f06567a76acd0c198406a0109d463ac8c1ecdb433815972699c00c2a40cffc3afc705b7fee16e5dfb67d5d3ffe673189964f2565882ee68a610343e798496bebbc3e0227492e87ae086614ccc48dc89682091f94a818d1ba23be28248c66a4c8570f53eb35841dcbcd8bb2015bfe95883e02879113f8372a2e5e8c3fe6d3a5904794da392a65556579385a880fe0250767e99b18aae440d840ca5ca353b05cd9721f376b1e3a707b4e15029c06d598e4184bc6dffe6fb1bb222cf9c399ed7793962187fe2a140e58dd02ea081be533d634f04a8ac08f659ac096f40ee738d594086d8f62a2a71497b18425e501c4729f8563f7ffe74a53e6a276f680b4b36a897a211b3823f65484133112e0304e084123b181d16b4395508a4565f2bd61e4c42ed65c9113d83d8807450adcec35bc84258a76125b192372dc7f5ffe0027f1400808bbb8073ae741d40d29e5e5dda12e3543553dcc98207791131431772013ba0ce5263c9268554316a36b07f935544d4c717147799ad7219904d8df01453e75338afc859c7f3c08b58983e0f54946ab0ea0820485c32440a0d399f6882c82ddc2cae0ff34ebb6eac25ac70838c2b1654551dee362488726b70441d71d5ccbbc45228abde07dac5fe4ef14aa14dcca57183ff3309c0a2262a74b9056eb49b753816aca76ff0c4feab89aa4d9969a00cf34184538402de55692788b5716e9b297003c5c184eef8bca7a91dfecac16db75fcf3808e53a7c720e84ebea79f150e10025055baca4d96ef420ba7ba560e54abdcfccef0d4879504d6590c4d5cc6331c9a723445e2edbfc2fd7300d92b525a633c14fa638ff5021f45fac9a3a45ad54891190a2e60eab2a994926cb90d305f3b1ea493bd3c3ca2236f64f14283bf40238b72f475d7949be7abeaa08b0438cc1dd4fb63c1e0d2872bbc49e0648f032a3b0f7baacb4596eca244117551a5110ef289e0c03146bb872d3a0b748555c114097d471442f79248e6df0e54f795a09791fc86b0a37597d20ac5e48ac042e1beb085d949461348dff35ec36a25ef26e49c309b995a3755556eacb1fe6ac888fa39c284b587a22268731f78ac0ff6292741c4b2fd6cf3d83728a3792e1d85df0457b56fac0178437b42c028c1b8a90e10d1b456ff4e68533722b33e41f84e40cfeb365badaa9d4d470aecc0b2b86e8a3a7aac2f90fb4f2307268527ae8614aa8226eb1a99a67392dd7e99f48d177d2a0cbd0f919b4059e4772eae238f344eff3824b024e86e3892ddddaae76ff7abdc6876f5df0d6d6820f72825d87b30fea2e4ab6e91a8d4de7510e7fd5d92c58b30ad64cbb4b6e43c0a513de2d6d4dbbf96ebae20d0ec3fe27b57526d3e3f75771045ddc0114904155a54ecba65e962912d7b14d1551066081f285fbe3a6f86a82e480e39c304a72c86d72d94f14466789646774f33d43f05668a02e48a750cd0713cc024d3340f23cca08951a0f6060c1ee8e07b7f1b8ea8b2aa88bc486d3940d43fa25308970478d34726e72891dbc514df7b5a9f6e89f9421a5c4389bb9cc1d661f81a0cc16f9cbdd59a2093e1f5d8e8e6ffc422f6f8a38fd3bb5fa9772f985edd496e2d64c2b5bcb448a8f07a123b13951413e8ed1de0ae6a4d1a636e0a32d06f42e73f3848cb8786137b6a0549d0a1a1faeb3e59940377daa98f2e9cf4de699a8ba66ba13a1182172efe050c421957f82f70e490f103857ed4000eb0f48539cee9709d822c22c220e141797b853781ba265052bfb88242976c82389f65b42a9c1493c9fbeac95347202dec8671ed49181305ec5dcf4302aefa9210a99f1dd433f24bb63e18785a56c5ba75a3a911952cc3ccbdaabeae5ac3e4d2213742f7ee8c428773104496cc171394ff28c4925f077dc6ba40a9bb5570d51d5c89675e13357b7bf9d5fa023aa5fe00c40859175c8dea56bb60e9f5246e1d95d40c45cdb938dd273ecb0a7d4027226b124850ef7ce667e597d54fa5a3ddbdbcced5b7bde2ea63a3671039fb9c27220dbcaf4851545ae53f51a67e4daa665f81211927d13747f475667fb01eaa9722c9955c5a7e260610de51a6d41b37dadeb2d514c63b5960de4491a53d70ffb72fec6cb8af8b5c695df3ac289379beee8bc079d00992cf6dfd984713103581f442dbf5965375f22459a43a349681200ae57344d74f7c2f1c28b81fbccd5b7637268b86403f2e5d94291c043c48074a6deac198630a5f6d6616f9c1ceae4af3e728b3d9e9aa62a71143fe587cda3f0bb256b1722dd6a7b3f5a8f16d2cddb85d71670543a52897c4c9c9b7eedc5de0ceb4e456d0b033f2c94b75fc2cd105f2d447265318d5c2c1ae585419c9d2cd327e861a1062b04d2a9f14c5603a0e5686f8d1b4621ae886143b488933e687c0cb2784687d35f2e239db6966e1124731c7f2c72e765b47d8f4b1fad9c0c7e3fc0e1872b7562d68ff052132ee131392a2be43b72b40f34ad1dd2207b825c976f655a17232eafc739fd8f73489788cdd1eb604d5f14d7a9300b6d2fd3af53d96fded5d38a546ff1c9351bf7b5a9e4ede8241e7f4407c876b7f78436b30f5375e17dddbec6dae9d79571e9ef4849decf95b6823c2f2ddbc6b6935cb85af6fd0836b85c659036bfaabd185ea916fc7e77ed079abb72b4d0156e940f914737df038185d4de17920d4a26dcb8279c7de5b40a2de9e90f7bf13a5826bb28adaf6ce96eb0c2fc6bf5366c5b99a1f892d210695166c4740861d7d7dd378f6ef9d24e5af9ed173fad766f3864f9f1274c6563e1e98d63f7724a56cbb1c225cddce36ac4ffec0711d3416003b87b5fa5e41398077a634ce872c7b0dd559e107925076bc78e24dcc1d65d928f879c2f3294d6a7f98b44b0a46f9af4d0f87e2538368532aac334f7c4a149ab463da8fd16429d5b0303f6b7f272e2990b024563da86505ad2ac5943fa480f4c5a4872d0a77ca1b7a46c48720b72a29701ec0681a6e26135dbd8e9604aafba0aa8d1d705db8cc7d8ffee4294a97232d8da92d0219df53888e77ed2a0191e9054c6374d6adc712f4e884fb780fa729cf10c84d326bf5961a35c39a4ee60ee149b5952baeffc595c2534d4d7bcea72ff82438907cbfd69adcf1e95c7c70d88926c4f00527294acf98d59ae879b182870138c60b3c78b38c5c8c82c085c6459123f3da10786816cd0e295f34a8166726104736e27445a97dbf46eb0f08f2a55e79d80de52a0b7dac2a12c306797f3723e3e71b50f85f5bcc6cba862f7610662f9c8a377238344156a9df5b9bc546c72cbb97220f712ae35ce6836a9af28a861d5d58b9a059bab396235a0d68e94841cdabcd9c1ce78987009bc2bbe9ae3d22b9be7be2b3649685b8731ee1e9a8d4572fb71b8ab277a37901245a8867231c0cdcdd562634c68fe72d09dbd19d50e50428d2c619318162fcafcf13b7eb2b25e615ae1b8dde626ad5242ff39fa10485427b44b963088e7d2dc6e28e8ecb03d4f6c3500969204bb98528aa70189a0c26b729736eae4b0856d39b9fd2336ff967ad5ee9e90a13796e9e633aedb48ffc8ff7274904511f02bacb386b6b67e7d867097ed82b1f047df38047f13974b2ab0c4724055e622f7d414e381fe1eb5b23fae0ed28e55d8f1a46a6e3fc4d62a8928472748b2f212448ad4aaddaa3736750db547975e8e4dd59e694be3e2b1f6f6aada72047f93d4ecdf253075247ae13170411c3735cb37bbab8adde6b1bda8a9fe66725e5c2a0b14064c6c3f0173ec2c870a10d85524305184bc20ae30acbc1c48502f12ac68991e60118ee0aca87ed1f579606fa0af97c3fde001f0c4af0ad17b6619f76e0ae414675be5ab508dd92e0f9b4d8df9d7f0c4aeb7c225ec327c26f13546fef62399e570c4507b76bd62360d9ef68bebab7c6fc3dd4d75b180d16fbb0f01f228d918bc51ed06f6a8ae109eb5763fea1048fecff5b3177a02a50d0ce6a371140320dd0959cb4a85fff87bdefc338e03a1f3e50ff8180fd67741d1d7fefd627b38c7294646931c38ba47cd2aa9204fe2e3373a5f4bd2da51666b1524e914311110f4d72ccbfb4f408b7b2fa248db2a29717733b22f75de35cf443e5e567939d4e943e1ed000f3266f61b4100858f0bc25d89212f16963df155657fcb5d501d1c3b56459442756d817d6ae43f4501eaf5073be701bfb8aa4f9fbedcc26ecf5975039f0909951786244d0ef08a5bec02ee2f4e6910bf280f9aa651063c89ad459c58f46efd5402470f869e1b3d9f11101461dc03dd02177b198a3067bed26f72b22c95953f6ab98ab6be88a6619f076997e8d4c9794dc4522d004242587e6e7245dfa6cae4e966c4c622acca8f044f135f1c333b1d7b2c7dfaa810b8eda6c25544290935555f44b0d56532491bd84b9b07fee1129c82a197f6f820fea11a3f6a8893371a09d15e90281ff8565b7f3359abd20a7c1a2b4894f4cf396162273b41f7db2e3485bec79b2b6700ab340f0c41e16e028ad2a7d492c2f890f264e7f642e49c6061066a51e8d177635eba5e072f6f4450ddddfe102107e14feae5eff9724a36fe66a52388edc1ef3379b95f419e8a7c91d79c976fcd81f6a3b823c9c772a184949fb49ff25de7439ac0d3943e58d324947ddb031e784674859b7e2b7c192485941b0460e59f9f3ee5ecf11bd5638637e66b45dc2d70f057529d088d031f4e4ebe31b7de47c8d92050cf8ce7febf9648e07f1ecfbe59e0681add80a56872ddf6c831954d8fac4fddfff6c610a1924254abc6c03374e1e3414dbf1f512272df3d6a0b67ac7837a5447253b58207abf580bc78eb4f1a613be10175be0b9672fc55792f7d094e837bf2ec5518c494eb3190da8ac383193b5b15ee4697f6a07210609e7ed35830af57974a21f3b0989026abba124c2b28f9795f07ca951b727223a7dcd6b87d02f050a80e4b8aa44422a3163457bdebbbe695341c4a068f1f2be5fd251dc59a04cc6936e5be8b6e20885520060ecb73133730fabc370258955d9751178f4c48b3f1d3ca5326ed418c530014ad02d9e6c333488191e1b451fc721f4163ab3f24fda8b0d48a4173303de212db07f89c7ca7ac3e9e55284544d662f902e08ede5eb911ae821849ba245efdb8bdf2e1bc729f6968fa7994447724722d2f60344cef8580ecb55e4d56e68cfe786da0a7a4e563b11664ead4a83cd772d1ab823ccd966c0c3a09fd52e6024547d2c6beff21371accb1cf7b74fd2c143a441d85e32b60a409b4a1caa9502d3b1c311e2d6eb7d15a9dd37c059a21397472eb1aa85134e8525a658a6ff10f712f3388e71c930c7d7a0d1bbb9b4b24dc7a71f6d84ceec73f4e183ebcc3b9b91ae225375b14d618d63f4dac3d0403b5982830f4a57decb36916c99246b445cd3887f2dd13c0662348323a6393ca3dfff766724a05a2edfc87f189bb66e827ea2047a81d2a74d11595a17bdaeaf67a12dc7e724431088516c60cc144dc8c4b8ed13ebc406512a9a5afa5850273a305f4d797725b13219ac6e619da86721dcf52772d23c06c550482af3ad2bc40aba13d601472227b720848d5eca7c6c713b3250f14cf7362104fba2d0611f2d634edceacce728914c982f015befb409f6450a0e6c835785ed533f6faa7f80071365cfd72b1728d1602d099d0ccd4d082a542d048f746efa6a9aad6c45a89c1a0edfa2035dc725b77fd43cd3abad0f5278917214a68f4bd2a5932898e47d1848868dc2ee049725161f15a629037b7facd2b1b076a98d4499d2bf91ff494f3c226a39c1fdd6958a96cbc8ec725ecfd903b8627e6e42f1b91115f2fd2bb4fe93403b84129562d490487a086162fc84615835a1a6eacbbddc988382ecbbbf7b8fbbe01a04c60a264be72595357376e9160731105352069996a14161ea6c98f68d6ccedad6b4e2e723a1ab774f2ca062eeaea54ecf1fb06beed11fab5fa2deb3e9410d4217c145d72657ec459ccac5ff5175a8af3148c409c8068028652e843bbd84aff56da9034162ff03dfa91c92d6cef69c80fbc7ad092b64469a4aac4d091c6971eb4ee5c8e729129c1f0694f202aa9e8dd393faa4b6d2caa87f25e3188e1766a11983673d440f5b3f8df65798a9dab2a4f746b7ce2dffa7024305cd6854b0236239e88427472d515dff42b1aeb3f55a307cd090843ccb1d8807b715b9a569f18c7014a488b31b5857e26bf5a1e9a3396856c5bf7608bc96501ed0ec189f273b7ac5da3805f2da3b136f542b4a018a648ed026d769f8f2f0f1361042a52291f28decda72d1f2db0052fdf4304ddd9d9d035ad88979c31da01e1fef480f568d14e6a4505626672be5bf2cfc980f9cdf5afa357c42fd19f05162696b81d2d9d59894d2e67ae4a72d7060aa05231c0d751034b554f99589341278392f16d9c99d4090e531013473d518ea4b581c88d6904936cc3ef9b18984b5cf59cbfa36eba57456e0d44da0b72dd9bab2e27511feaa27c934b845e494e91850a7399ae5381139b6a62c65b0a720bd3955e64d60e9322d21f143b0780fda95569faf6ae63688b66886ad961f772a515523c00f2d36f3b6355d473d36b1252bb9763d8113d1bb2eb260375f6ed44d3ae400a4a101687f15c25cdeef5dedc1f86556e66d5253747da765dfdcc822f7cdddf50334de11eabf2126b454296dd4068b53107127c56cf4556de1896e27272bd4ca09ff5da9a826a8619db9a11aecc8c6ee036b462d26d161db09f3cb520b3426aca4d178e49d1caaa05f69a405a735546e5a50c11c6dbbb5e66dbd50172973ad52378739902c2ef1477c5c04820816196c000630f6164fbc310c079f472ca41981cf6bc988f4204421b470fe77205a3f6460c82d5ce616ad71907ca4a72785bb0a5694e88b6dd6d8287e2194c7ba6b9e6e66be0501a522121774bc33f46163df364a07214799e8fcdca546dc747218b68af18b949508d6340e255f9f6729d4ff0cf7faaa66fe7fc9acae22af25d8f2b1cc70bc956f84237ff3985ff0820ddb67e54c647bb932c8eeb92fbb1af81ca4a3240d7729ebb3c535f889b347129675b941546f07224172791d4fb775b65116b07d5f87acf1a8ec80e57ae1e9f3dedfb1c8ed87f50ba8859b087cfe3e9c025f52f40d1c1b91dee0a3d45c908ac2452205250a2ef4ef019edc9d1df6fc6fd0f7c8d731bbcdb11acc7767bebfbb2728740fd47e47185a344e82f52a8716a7573999e9f85e34ff8ad627370c0d59d72d69be3a46b5777d13e5d9bc02c38ff0a5493bf55afdd2c250303cbae7b49ae723a138f8ba7b81f06fab292187f01ec768b3f38d4483fd27640a74def85476672c30391366f4a98777f1915e76e8ecdbcd1331fe43c865212c87836d66bf6c772b5193dee8205a7644a064ff18d1575e9332b5f2da89f232f084463f6e1686a729960518d0732baa27384dab57558ff1c1df828b57bb5c579c4bf82c3bb5ada043f8e1eb04410f7cdecb84992151e93441614e4edf9b04752b2e0c93d26f71627b869dba87bf9ca461d0d192f9ebae37b5015c8b691bc49f56bec51c37913c872a867a203d321fbea55619bbd1befdf94bd7677fe2fa452e9e286d94354ccd872df1c046258ba79bd2593f3f8a4a4ccb3b0424946b375bdcff4d5f0f0d8f6f2725e363c1204b52ce189ffab421495cb0e055ce1b79d348e1ebc2ae005d297b972a8417b2cb764287f5a036bc816bd015d52f02829fc0320b6d508eda4ad563c39ccc263fc07511bfa6329d831b68de1a187f5e15d56b5570a0108d8300540a272f9d0e54155a4e88f3a483fef517dddd32583cc6ac3477d9025d85b0350497e72809d69eb57a27ed1bc7c3bed2c522079688da26a5f10075f0abc95064e399c7254cdaa9c294798c4c3888d9ea72a82298cfede72170eecc30599932994de5d7245a060905b7e051d62dd5fe54218c4f6566a877ff7cf9bf05e3ad818af4e5f729665d927fe20b334b74a44531a5dbb12674d5414b925f0a589f6a8da8464050c5805dac88d6c91999b7788d474443d12df5ef6da2926c724d6146d3a61a2a60e3d3830bb17e1ba59f0470401a94287a2266911192dfcfe2b0419ca73bbc8f4314e811cc0dd01baefa2f7aaac2cf02cb15399cc9b4fa4c26ef5ffda25fbbebe729c65848c5b2da4f7648c1ff053bd1ca3a0ac9de46479f290ccb556725c4f347297bd5ccec52284f69de24b092f314b5c0eb0ca1897c9ed74b8dc27e7c9e00772eaf835ec71f3cd1c63a4ce37ef45e514be15edc9c0a28ba90aa56b81093ef847201b0e74a9084a0ec2e2aecf9c1648856b7ea02fcc29994de858927626eb0f1c2479601061af10cf47272938ef11b4ec1766bee123f36539250a660e428e9943e881b68016adf67fb93f81b157b0641371d5ccc07076bc41d989f20d30e25c356657d14ed0d1cbd499da70fbbb87f41a15282df2aea098283916a1a94d1d19538598cefa048d47687c8d4966eafa1b7e5852f2a393de19d19222e8a9609c2c3badc07e83e21bef13d43271512820cff5160b4da923a2fdd4a240f1723a2b0f6b95e5a3ef724b9981fa1392bf7f9138e99a1cdea8b1355b1ec13207e5df0d7559de7e27cb0b24a4d377b90667003cbe9be6c46b4c05a025bb503bc2120f610872a5a090a25880cc4f9a69ac0904e71515303c861a78b75f7b94aeee1a05d2a230d99d94f45e5d4f711bf72a6e1198e8ad2e90d2816c4c065df5da79cd10d3be6e2ea7c534ed1192a3df5bda0ed788fa84f493301c56f0f1f227c067a3ccd6c5720a2ea336dbe028cfdaab9c94f01559eb8ffcaa931436532b2f3f027a4243e0200875d4c7d4c9a83e8a1eddc03f1069c266d25170654b870413b9e116cf9dee729f1039894189f3d38cd972a1a75eee8e72acb83f49ec042e338debc60ff25c72f943b79a72bcfe90e7e5d81b192a11bdeee252186530c184fa64355ad4555f01a8acfc08efde4a4c0a872d59545e6982a3b2a7143b513d313c3067855d707b247d95842d56d86dbeffa632437e8061103144d8dccfd516558e9a397313b00072d4a591b7849184b5886ef26edbcf005726cec3c3b1659479766985155ed2e872c680f4ed6b2dbdfe1787a18754f21be102934229d2df73ed72427c541ab76c2d8ccd22f6b302c683f38e4b2694c6fb85672f30b87e1b5db34cea2b0c9eb3be0c28ee1290e0b039c3899605f0be13c5beb2ec4bb77654bf9c74716468c3da3072b54f4bb7d4528f5777d1b23fb4b4488005ec281182b99a5dfadd147cf3a7fa72e4d6bf8a1d7568ab40f848ee28b10a25352c5ad065fc7f6d81ebf0addc139b727ff68d4454d1650f73de417266116665005997934786cd5c01d6889153c95f1f3dc9b5d4baed4a94f38bce78a49196e206471cd38d1791bf4dd6fc6e61d4e872d8226117c74eca83397fb6c171c5cec62c58264ac4cd9932f6f6b3b365a5e02abe3c08405bc6ac2b81cdc9b3432be6d405d89253c5de369fa019c9f9f0b03c72d517e6a6cbb370eff59b9ce1b3a472226a3945572348024f324607f382821b724a48117ddbbb85559c29d126d7a028a197608ab6f73a3fbc3e9ab127d82c2772e8ca5e2648f9379936b4984414880d45ae5c9f87141e5648f2173e45d6f5733d55f7f0956843bda13fd2e3f2e32ee5adece95a2ce89f76247fcf41152d9b8972d60044a38450038f2791e64df9347f68871cfb607e0035774ab1c67a7739a4728fd584950ff9011ef70bf220d5cbfbc75ca3e35f4790f7fbdbab809480d5922c061de747ac00cbaa3441b6678158b22be88e1067e2a0340aeede462e2d8232214dbb2bd6135b1818764e3cbd4dceb444085c45b13749271d34e5796f705bb572db674c616473507b4f60c6685a2d3ebd31798ebd363963ad6fbb145672b78a333b1526a3ce6f12c566ba73d4ae8741a33e194f89f1b8877c5c123b3248c1ea0245922843549ace0a8ee356377673a06a0b85dcdf7a0475d0e1851f63e8393f72ed638570d26cfbf0698d0fae89d3357e8473130cd9ad0b11215add41c4e94f7273617b3613793d3ff933fc12fc94144e8c44b2700388112d991a8e16e04228720bbb46c3552c71337b48d4b4b5a6e273fa438b4f8753e5f224a519f07e4af272abad6f876d1cc988b2b3fdfacf468431a1cebf7310f1e4d8109d5a4f3d819a395f0beb63b33cc2285f071777510981868c88ecab02f9b70cc40ffcd1d99c1e725c244fe708b1d935508f48bc5c6ae6a385ca0f47a27dfbb6bf50a5b1b4ed5014e839cda6399da63594992d3456fc2a985949b4738a9d77043f9e96526627012186adc36f838c9415856fd508ea3401038bc90928d587f0bb37520d9c5bc0a272bcbdff063c474228ca2c4053c1133bf1c3db2a823db0a7597b6d415f5a4c20413a55d57864b39e7cf92c9ed62294be4edfd8216a6ceb89e7d6fcdf68c072fc6726e95e1959c05e2118fb0ed08676adcd5b0c0b9e3addb38ad34e82d00c8ad1724dbea41405d7768388d67fa2fc599dbadcbd5457d9b3e69d1b81dde02b1b514771ad334ea0cdb2ec7161352c0b8defd4f6f3ae8512c5a5aa6e5461b2ab44e77259675fb519c989fc36adad3f49d743c7fb6595ea8535c6d8a59f8284bd5d2672aa148a978a5997700efb6e7459d5f50c000922e424a4df4155d420a1ff0b01453f67d57f2b87540773fee6059bd805b2cd619a162bc96dc9b3faab2fed4d4758a14ecc707ec79e62421d7ff438dc3f8501d80c54803cd237d1c3b9f7dd58f44e9ffb601e4d3c0e555bf25c5fb274d17495373ab7fa563a4dd591aafbad3db372b88cebeb06da3290ddf62562b294032f7d4d3d0774bd599a5e93a8f8aeae9353a5b3ee26f8c2eb35aac17d5b4a573f5fa253ae796945cbe845dfb6023ad3ce6f05b9c406f5907dc38a6090caf5c7380c444dbe1f1dc2d01334f5f63d516fc84de47bfef2a7244550d7f32f9cab2ea51e6b1a392d12b56a971b0def4d8628327205387cefdbb36af6e9e2048ba1aa2ceef3ebb1765971418f555d92239c18fc49c96fbe2db3c3f3c93f6386826574e8d3160a6c1b7024d93ef17e85b035482546aa5feb7c69f38dfc1d1ed3d24847c205c0a698ea4a9442c61837e4be98285b72ed42aa48a5f634495257c9522022870c28d8cdf9e95bac1c1a8f5c75652b1d5e5e8c105d291584cbfcd941a2b7338b0461a102961826691c7cbbc78109b61a2096ef2930a4481d6e23f915770c681bb11e7676872acebfde5f7e2745457dcb6aabc66fb3ec31116d5cfc66ecc079a1894c84017ed6c7cd5cadd8898af0f6bd1f2b85c44a14c0267b42055117ceb4752a08b5cef63d9771c77fad1834c641d621069dddab37b710f3fcef796aa391ff87eefed9c271863eba818b01887f345872c199cf1c161a8adaddf823bbc35d30817da0e289bd8079029e878d2a6416910fb9337870e581ea9d97322da740549ace68e5cee77eaba9df5477b95ecb7b2a7244ac2c1c147bd60bed71bb3ae8af7c55c58a08092bb208f6ecd7b266ffc6b672d17cb14ef707b71beebea29f019a141d91813b2ede0e1ac6f596448c0e483f72c95e75f9c9d97497923542036b1432ff46d9e66517bac14af5989e697469aa71a2fdca385ae749c98c35f8703929533ccbf50e45f6575a8ecee159ea07a3734285fcf0f9d0b550141db1d76b3d5d34872c44665d1d0dae96ef5132682fa90d726c049c597197f77c35134b29960db0313eb62f874a86ac953063627fce593433aa6a6fbcf6d225b112a1d5afb7adf461cc436728370c39b8062543a5b177a972eb748a854af5f946afba3fd1d7b9f9f1ee6a861992e325a5dec75684d3027b728a568b0ab1225d8c653fcd0a33ce9a8d69e9b7f83d7d9b1e1ef489c4f99bba725475f01b3ad0f2932c766f3703dacd36c5f0e3d2887774ff1c5a75d775b1a8720ca8a5ca64eb1a79b1a7e42b0c26196bba2ed294bf0dc2bab265b8c2ec8bea72300811499edbd78a3032c1f564632d974a386ae469f434247fcbaa59fddb6b0df6ac04db2683a4799ba937f0ccd168fbcf36ad64801cfb287ebffce9600bd655e194ff048f43e24beb54fca60f3b5482499cc0a8bcc520ab3230b6df60d1c0180c942db38689a1f01b7c93e7fb7a273d45e0235d83956c07dc8493910cdf2b7218de42e061f71adbb6833fdcb818109e90347a8bec2d15be6e75fe66773d9672f99dce518bb969ea6e1140e334ec447ffff8d1e376dc86a91d5b20650c207872174ad0ea842e32f1627ba6bce8cc9e570a8b5830ec5f6f1cf7b2586e43595053d344e18edb9d8bb74e339c2e54ecfd65607a7225b38d3756ec36ccaa225ff93da0d6242b3b76497aaaef82492351f03b24e39dbebd5f5867bb9b7ed6ebb0cf72f6ecaf46b20e28afdd100bf8579fc43be9a4e4f9c92c333030bafc47e323a07256b7d5f73d93554e70f20394f060c1978d7d43ade98c09b7caf5a86be9aa3b7218ffc6519595741cc1da3a3cf53d699fa6db4f743e2dd43b4fabef2290cc9472b3e8d052851bddb9a5ea5474f3ffb11f10fef68f0f9d902dc98f0a91fb517f72542f9f17e0d07d2be9cb248a90434be18ab493635689c36ff0c2dce836499c6f2d0b49fcc70430627362adea6aa64baabc12282dbdcccaebf7a68c234e669e7290bfc6ffacf6f08d8db08f106fe9b4727579f88cf2524e55b18a9c22bd8808727070f793b6b649a17e8d9491ee8c6439681dd112857f880d150f482a14c48e72f5eab26c64ecb21d68c44965241575b32a10faf20efaee3fd9e7349c533a6a106371d543a495dba0c9335e8117cc8bd1166305ed410bd36d3a3af8481ef33d5c5a31aed240a1d04bac67f9ef524329ff594048111700715e335e546da51ec872b538c86ed329d57082a3848021065e68d52494254c005ca34890a8cb5d698432d2ec0e46799e9baf45591051e5652166b217cd17470a94ba1719953aa8bc7d2945437d29d88232370e7cb5952482551eb3accbc01c066a4e3da7fc2f098653476159979429b57ec5c4286b6cd493208c1084a256c3be313b9ad37513567e7272bf7b4365b2aa5b6adf1c8e7289f6e6e9e40a070ae476df4e7f83f2df69e94f72503007011987a05ac4b55a64becdb091e6b2d0a6e0c7ad09418a8048e8940272585b32d26cefdccab895646eb715a5877c4cf62f43fb4ce2a16e4ece1aec7772a09bdeef6debae92ca0264a8432d9b5f69a9104196efe640755b3ee04bcb3a573123ab60305f1047ec5c26e1eee3a728b12e28331a1fba839b91b04eb25bfa0fc3462c405b4d36b300f678fe4ddfc7e999cc14d8d26faae3d3bee62778a54c722f5f123d01e78469f71c22003d14f21cae1e4023e87a7f266c2bc87601999b72d8d2d8081c38410a1b725530e72df118c37be151f356279c0ef669e49a74e025d8068f46b4da6f6915f4c69927c6d4430c0bb832de487266671624f7c77f6c72897439483858112b10fb64096b801dce73932d6d28143f58ee131dfdbaca200312d3eacc45e42bf021e8491fa5e046cbb223dd53e2960039a1013321e6ed3801cf9a644c48c3883efd73c5017108255a1e16d6d0092ea7f3da33e9e04a9ba472864331c7a3f442a673ab8784449788536436a13427b569f9f0539158e784c96ba45418db0bf229f67d8960d780c669d0e39c378ce592a6bcfcf7e2c25d5f9972e8ec49afe3df36b5183acc0434e5b770b0ff342be9c1ff705e4c53429bebf741ca47be55172ecd5cce8f0e0c94fadac85f5f96824b8f145828bb04a873298b2dc794fb18fe92e28cec001a00c8cee8090d2ad59e749797612a3b96089346a972616d98c56a85465a4d9b39ffb250a7e013b999df184f674d389ca2412398555d3c55008924bec9f6b764fc7874ba894b4b6d8c8e48c79e4213885a0cdb3a0272ad483d5d228384db2a220fae6dd2cb325f842d42a7c715fb67093a92abcabd72685afd88084a1760476316fe8a0acfe3ae2dea2d43e04cdcdbfd6d4402e82f147c6abe514c546ec079c6c5d18ce7c6005435aff32c42df6b644e78096cc75c72025288726dd4d14834b017616985729342f0f84c59c9b8821835459187a8e9720959d1d435fc4c2fa10726c058c7295385c73f2bb994bac01bdb3c303113ee68ace61d1a0858c0a122d79ac3cdc6c8b8123cccbe56f15223afe70f11571fd1726a9ad8adb08af19d32e6f43b2723e47d3cd9d259f15cd9dcb2754764546d3a5c5372e69188ba7075a1de53a83840866961b0bbb7162ddbda83e3859ee1f99d727c47c937f9ca92e9f554accaf7871d696cc6dcdd59df691f793a694e58bef8725b0b2390b1cbff96534336ea1ebcd09150c4a2f7019fb11db8cf9439cfb2ef721349757651aba53e1a842281778ec2a77c893b5bbc741023e050717e3dfba31eb5b96c04f60dd2daaccc9c5878c832e52de2a403bd7ba75dd217970074e3b83151e5eb9de8d59a4cfc3ad8cf13f8f3ecfc2621d934f34f4dbafaa6c999191f7225aa620738f2deec7c452b68d9d2f67546d62fe59adb6883713c030018dfe572f3f2af1acd9619780de3c9dfa5091759fd3a758bdc8b14612f2df4800a5c4f72c35909dce396ab9d3ef2cd37c18797e0a487c0796310adc25cf41b65b8f1233282ce678aff49fb40cd14c68bd7e164c9fdf12f116086ed3222961b072056d072010edb8c126f45e5a414d7476a65170d27bb7864c62c65c767b92a11370d1f1f63ed462b25933f6bbfdff9831331f1f68f858f5d600e20812fa7904f76a0c900ab6a2de3ec270e61cddd88a9363a94c1c1745790e0bbf2395a05d51faba60c72d075a2e0c0588497dc93a47db3a57c1658145736dfe8addcbe7837b999e1bc15bd69f48754efa54e69dc61d3bacf768acce05745da4440834bb8cef80acecd729e51fcc01c982ad0bdbfce99c706715cb9d76d4c34b61867f0cbae2512b97f72f614ed55b9de1cf420f8aa8955ddf976c888f87cf28ed01f4fc662c369157d51f4f2542da3c861ab47a93f78d20004e7910e45ae07e971b255ceffde8648c572696fcce38959707ea2c907ec29890c3673f9b7fbeba12cb3b7a2e26e56e06b3b15b1f5fddaa0fc1100e40417781f5dd8e6389d4800efa2759bff4445fc27647237ad83e977e1e658e471f050e36c290e3980f1bbfbef2bc6e0c1d3598215f704dd03bd837b932a2fee16d2e77b774617abc747738784397274391d9b5a6a0772a610121694a18604bd13d3be1d867c223e9eed62837286a42c52a6dca0a8007209e07aec3a03840ceda1d42c4be4d22bb47172af5d5e2f093b6b33470d4dc572f758aa987321c8a1dd572559e5ff32589fdddb6f2b073cff450bc995d78e1f4015bcce6e552acd999ae54ba1dda3d28af2e4364d664b097e4befbb74df6905720b22522f5b3915f1d02990ea5f63d38878818de00ad1a5c03a97795fa94a9525b7ee9c505acc468835d31ccbcce7162a7476492f19c1213082c5accd19174c4359a5bcdc0997e3be50fc5ec671ef8b0a663efd8624a196f7ecd0ff91bee0de6359973ef6468e94c9fbed9b0a0c17aa229c2f134f431fed5834bdb75f0345a272d085185293d802f422b779c177749708d155d76477a214eb6020c01cb12a390959fafe6613c6a95427e4edf9ba60801d30f43d09859f1257e445931618d392298e7b62b405b6a293327f4cfcf7028a57f9f003f66edf7fed63fdb811fe708272f1ca52501a7059c44d1ebf9268412b4726e4988a4a3f96cc5c64b1ecbacf5c72cec09cd3ce4cc0111b7ecf20a278d35c333ec6e83cc00efd99da14c4dcf7ab7228b8e78f7a43131405caf517b6d465626d79208f9a3fab4aa084426b076c5172739812f5c2b443cccc89bd940114875c076728d342098b36235a8cccf84c176359c79670e18bc06c79c76681632837a4e7b3a785a67eb3992dd944b999ed1472d4ba5e263617832d31e9018ec138f9f99820d131667ff5db94178276717b52095f7a037b7b8ae5ea0a5eedd3510a018516da287c2f8f5d61309ca7fc19d106521df7b7f60ea59c14b0a659fb1a23fc55fa95fe5f683ff38ef8bc5e2b854cc8728a89e04e6e477690f8177080283ebac701abfbacfdaf052f1474c0fac540a7087d82cb10a9d0f582571bd771527955643b7ed45f3b0947badf276b8a8dcfdf72af3eb6c3f4f40ab03cb766e2c572cbeed16f3efb7957d574258de5e8af1bf827cd7c08d4e8d9f56d3f34094a30f9287c3442a72cbd1e45664e64d50998d13d1a80edbf5bb034257f19b8b073fa7f3690ac61727efd4de82fc84b1456b7e4e672e6f5c13d5d98821dd87670abe13bee24f770cb1daf095e38bc4cddf66d49034e413476e598a1061e902589e8634e642f7696bec79b88a3e4f797d9ce73a62d1af972a2efde139ae4c44457ab344921cf9475067f2da9dcd9f11964cee4b775722e5bd991656d2db193fd70a584774286cfa89f32e5de4b8b3ade54869b0c89726ca27176afcc2531eaa4b2641f4aaaf040ee6ca9f78948dcfc4e093f0b89e902779676e4540caa1b437be6268f03d625977afde6a3dc9b1373f7516a939dd27264b842541048fed77d67f451fa431adab383b972295adedf9d0c2fb13dbde17213e30b8522b8d36ff4ce0cfbccdb9fb6aec236633f587798c6bad180ee95a57229be9d64db143000fdf57be8e012372e7ca21a2d310de2a5cc60ecf2dd3c3972b3efec025390b6f8510c8442b9502c511c32ef37ed99f932ac888f596fd070725fcfb060f1f27a9333766e07a87e93e76a1bc60950d0bb00b2ddaeae298bca72bda883011e0bc188bec270ddbdfc196e598403f8ddc94a186e7662ccb8303a72d85d9f61a92fdc59ecdd1c43b9b6a218e9ef4e6b31f23fdd1640ee8147e43872757adf9b022f065551b28f432ac9df83ecca566681e6f3f9353b36af9e209c2edc2fd5a0abe6eb7942171808d362cbf02809e14368ccee34e4395ee5d22c3f7292f1e7a5fd9b38310427f301d2bf80accc2ac056894a74b0230762a976476224b2281c921515d70e4731300de4940c254d0633096e3c5c746f1f532dbac553721236f4bf26a9ef1798b215abd9d9556b2cc3857d7fb3d86aa4131301aa993072fd0f6402d47ce557e01362448dab32bbf9022bf4b32850e612d3abdda1909064e00c7a0dac4dbdf06b9c23db67895bfe5d06eecf2c70fca6257d21c6dc79077294cd249f5baaeec2fd92153e69c9b31121e075740d4f2f84c40509881766612b1d96f9457e2763d677fb7ce81e4a296b73f78366c7a345a011d9007b73651b72c30946f2519063a2f3f351fa1169d1bbc4ce6f342c62b5e429d5c2e6c295cb18b0b994b011df3ecd6c0aaa12f9177e37050ef288b678dd54504e2177d0e82b5129f151b847212a66b12b2a21c0c8e47c07bd2a2d14c40c2d5c7a4f3ca0130339cc47596c0756bb5b4348df95365030474fc5fc5a79ac23de37fdede9badb2772ca5a12fc25b312d9c9ba98bd5f96abb2501673738679de91679073fb969a7d720fbc9c9c900aeb4667632576ca9c27d15c3517a5e3077ab1c5d573371e6cc73926a7eb191b4dc56c1375b2e60a1051b55659265f74a3f929cec36582be12c872acc091392ad62996ea71f813f55655984573ff967893f75e4971943e5bf91e45f6cd676f49f8c25b9f9f1ce854bc98bd6fb1ca334e5999c81fa4bd5506bc4772bb2cc54019a63e07562847e360f44489da024933df761344581e1b8d18abc218c3e58623fc53ad837f60d4f04a949c1f9a6f4af01d1aa7571a666f31e1343a7287779da487b6da708fc6e122abbb39210363bde2468a98a8488abbe295452a32cc0543f6735246b2942fbe6c63f45fcaab3003f0742b78dc085c53b6b579592c1ef36c6fd3c7a24d7b7d2f38ec91cc090eca6dd15a602b639b35b5f53c5286722d38b538f8c9bbe5e081885ff15b18331d3abe7b51d8ae72a20eb54644c580720920ef83c3f27ab10cd6a1edb4a0eb231dd5616148250e512b4c6f0d41d06a1f3e50c20cb0335c38e071c466ead70c2a1721b17f96a362affca348974798e0725dbde7b6c412499e3eae6063916bfcea326eed5cf926edf038459e9cf84c647270998b056af1224a083b741bbb92d4e6473b66dece26c1192b153670be7a8b7217e18f8b4edda6be7f338a293cf5c9d9a96b6299fd131ed4c7300a50a2002572dfeea10c7a204e76fd1ed28a28c7b069420195b1988fa6c318fadbfd080bdf6235dbe93005d5a85c2a10322299d415e3dd662c1c76198fd79f21045d706e923cff5d7bb6e9ed19f5bf356f6511f829cd6fad2d0e232405ea907a6e11b4787f231cad5d81330a9b716b8eb73834c90bf4e19ffe504663f1b3652d85419b6ce9203bab507b12ddf06ce86d02546209e9af5487fd05641a69823fd277bbaafcd6728b638cf54ae50ae8c243d0666a8722cf54bb7b7c61ac4ffe4b1cf2ae4236c04dee51ecdd3b9df91306831c8c27e4aa41e2e50b08d68b30849c4a0d0336793c72cc26f6c7bbf553830aeead9d1e54dc807c2b4b090d17452e6904f059190da86a92d48472c35b7354b2aadeac99008ed54235c0735b58879a4e58cd5218073369dea36e517c750124daa7802c4629114e8fac4cf5c23d11f9cd70196d09db2b72980f3c9a32404100e23c69143553c2d46cfbd178ae9d0c7c80df47be2e597d08d93ed9fbf9b1bc4a4d427951402e9aa8413b824f07e8c5e14932dc8222e31f726e4cb6f2bfcb31c67a120971801045f162959e1ff2fd574d8b0b3a3274a44a72c253ece93b6a62e9a03cfa4540a82db5075328c64a8ee6314f70b6fd26e2b0044d972ea10b3314aedbbe1015a2e5e5a1340073bd053b60d77decdce5d2a3fd72e62c79bf45d78b1450ce83f6ccf82f13c12a7735a1030156b44bcac025ce6d7213e6ec2d02c69d873c4a1db612e1f20cd3a4775d377ab83952f8663a841ce6067d640c7e5a073e243f2b5bc12ede9e56ae3386ad85db8b9cf978a08488f1f64adfff3db1d0f4116d50db60dfa02b5d2810583d9d35050d2e3cb52841122b03722598bf8e4da2fb6a9ab3b0a90c37dbe825894ed2c64f5612d756c9a80ce87b472aa20b9dae1f9c5f19f65a0802007b0431dd9f78b876985b2773c7ed36147f1beb808d992fdf44087d20fa0e8633e6e19eae565e96f75a5c2d3cccf48c68d207e7a04a4586c72eef941d263f00463a643fc8ac0b3c6adea01622740a81b4f272756ed157ae312d19980fd7ad2b110fa78f6475ac50f693f675b731fea6a36020f3961180cefddc2c7bd13f6b3eab6ef1a126334cb3b19702d650dd127288f2729c179aefd2c7bccfddefd8dfe1b98f14c6e7d205cf5908f0528cd95692d5db728f3759ab83c4a2075daad183402de78e75f9d16171c2a5fe580ea350193c77722b42f89edd94a3d9cb475c2c46bcc5bcc2ff82b2b1b4123f489653fe1417b33699c0b66397ecd658d5e8bc33dab20036a8408f2132185d8fc407bfbdfd6f692a707f8532b53ba921a22e57e6bfa9f5f807ed1111c0ba3d1033bd61afcd9a1072072f9649ff8ed52aa77ce81b41404747831d0bcca4610370ebc5ed2ca213df72b37a2d31dadb4d7b71cf51b6bfbcd427f94c8a05e3b88e739ca38471b133de721996d82b87d2cc25bc246a53dc1e55be753786fa1c47d834230cc4931b73d372ec9ab877bcd5d07b501d303b366edd555a62fc25add55031e89c461cfb3f9472e801824825c77a582fabe33147797f4ba660ae96ac97b1494e34a84cf9ea3346b64979b0af6601d4e0ee5aadd8bb51082bb44dc77a8e63811d6a194adda68d7253beeec32b949c1ff727768780a58bcfe51e7d3f14a19fd09a449f35b4e10d04727d0cb25c10802484a2309f10b4183eef7d57508ec0cfa4948a337c9feccf2695c269a9137821865e907e85820352443190988ff445827e74da47f0bd60f072a81ed42ac84575782c10b1a67736eda2076e2635fa344917abfc38d667c93b72e7a3a3373d26efe43e13b3f81548bd23b90a71e08e98f90d6fbad6df898861660a3da1642f0eaab2cc7b0e5fb34d2eb5c209ad5c6f57b4f84e4e88bccdf1bc72fef25816e961a39f75ddd3f803cf4c0374bc9618c4849d9f447519eec8dc4f720e8d5f27e93154c0817e4c6f48d3fc03c00ec5e2bd886b3f0ed3edc8cf4f3e722d0d0a55335878e61ad51b282b484dc85c3143c9c871af84c67d19ad51c31646245b0bd2e506ea18662e0e737f00497a2374903b5c8c4fb69e5d485303be0072ba17cca7061f2d9f4d2d2f169b03daafeb7ec90af35978563b5821bd15e1da3cfd4709214124847c86b4c305e41ae88a0353531760cca9efda362da326e5562562838b6a2f8e979073c92888d4b3d562fb9a773cf420c4fcd06c2a1ea9219c72f2f9b34888c5720b47b47a2655c074bcd8d7ffe06a0bf9266d3b24e17599d072f885553db88791b76692a39a1059a9e81a847aae9fbacf8c1a7919e9e88a2072c4e3a781c2401726ca4d102882ecec8d962554b25d2f5e546c548bf6200f0c72afdafeb93170526e120cc21e040fbcecb98ce419e779ec69d597e8d177d55872e5dd491b2082239cb595607e6ab4ed4d179ee60f3777f1d5d92ccee434b38c720df789ea4f27ff4d41b06c73ff6530089bbe179cb5f753542e5d8eb41bbde8372af505764118981e3e3cbec2b106907d2b8f070461ecad7cf6329af2603317729ddc5b3cec5a014a83b4d9ad5f931fbd0b0879a5a3682e29b7079b0e5152df72c1cafad25fdf2533dd411d91e47779ca5f7fd3cdae6e096dcd790f72546ffd72a07310e7be75e2626767fbf33f748edf1e546774ac4aa6533e5014115c55e43db02d51815b4c95420318563a96fdcc7dc0b67309fd748e00365f824d6df23c724306578b68c48f89ba96f77d3ef0ad446b76a1518ff178b6d3b15133f3189a728b38e850081f22952c6e76528cafb4899666e851ef8b4cd7e302fb5fc628de72e6a566dfa7fffcc9d8af599f6239cd5eee7ceffdad7430a33eb18d4bf8c2e2724591a1877bd080460ff1cd5548b3ec863fdb2a5b1b29592e4702b96095319f1827e4549235672f3a7b7c0e9b9f8c390e48a35775fdf26b75a36179b7de0ad771755b12d226e9846154554b79effecef858e35447af33e9ad2a02026868b21e695beff65854157909709cdbdc5bffa314e89198e7573fdc73fdd6f568a05ce772ad67e17fbcfa54ecf73d051d1e1b6cd73ad326f236f3cd3f3753e6601d54a372c1b4f8bdba68af42987a768a3546c7dda77e1f20c956955551474603a2d9f47246f12176eb11098ce860c0c95257c11cf5638ebb52933afba337ef2aa3d1da425549fc3645a586388a2d5571772baf3228f9851ee14a01b3afb1f15643ce6e72ba42ffbed663cc764c99831854ec69653a8c0a168946460617e73a2db9c15f72148b75b486c88b79e4c76977a70d967a56b9c44da27f04a0be6776e7adf01a7284767ed3a850c28001395ef400786e52316d669d50746ee10a6d1eca189f1472782b83f9a8377e18cfda196359908c404b3585afa20fcd80817d439a8eb048043765e1df4ad26fa6ca2c9efca04c55fe0eb977a324374337cfd851d3fadfcb7299bc569080dff48584e84b155ece8fbd828e81465701158f9db344eafcf1fb723d98887710697fefe82954bffb2988f17bfb986db1e6d2d9c8b7e7946d64d87208f2a5b9e054ec1089c40c1eb64986f16561fca5eedaf113121bb25cce2747210a32173b9a94fa106b9ebf55232a606222f2c99e219637dd16498ef66b0db97274728cfdf160750da25f0ae58901461ede8c2f08f4bcfc81cad60843bdca4b0bfb61fde3366f24601d120f9fa9f1f86c66f3c98718778906100d4d1a05664e13a94c7c5fe06e771583d280705c4c55cfc30741121bd4e84107da942055c6815dcf2f6360d537bca3aee7277d546b6534954e7d407665f087549dae0473dd510b5a63b87d47fc953a009db891a42c03285bed17e885517632e5c472101f913972408bb2506a8a176dde86b898dfee171f6ff8412f0b1edfec519e72542bb2224f38a56f435b7d571a84f6659f01c048c0560372ffe7476e96a7af026a3f9e8e72b06d86d01cca0cdec65a6f62357dbccb541a5e7fab305051071c44f873345a72eb69ab5f532bb9ebfdef7659152baafb0bce4d7cd8c87a70be4f35269dc6bd72ae3e006fdd4fbbfffdbdb916cd06ba1bd3e9aad6c83c1198437ce08091dfa0637b4cd02c547c5c3724261593159ebbfee03511d5b2a6dcd353bbf64998c14b106dba669a82840719caad9c7bb42882ca990534f5e9361c7411bb015dbbb58572c3975df28d02ed8b148aefa14b66e8bccc08f6750b54f87b5b924948c0c9bd72111c97b752480c221f55e0d509c8a4215682eb8565a847aa6a5feed9ac31e554b8331a84f2e992d95216bd15309ef79bed9fcad6d73d245b0d408c9de6b83746d5d8bec9d55d960e3a51ba859fb11b34645a9c4e8af0ba43c2e5d5cff5534872bddb691473145361e5e6a3c03a4f568bcdabafabbda19722a4229acb67e7bf270a3073ac8815633a6c388380cdf43438649ff9a4a66f1c5b7b68cc9ab1298072bb6a63d629d27d9ea01c6c87d46375d6f71fa286d55c7aa1236e51b5fb475d729c05e455af93a8d827618aeb8788c77e2f262004c8c12eed8faff027d13a1272912d43f0271dd04eac5753a8138f15a0fc5f4ab7bae846068a0f2d24ec0b57504b21807b21b067327ac4dedbfef056f90f6ca28d5fcf5cae81254d15d4343672ba423395ed3d9c0d95d88d8450f86cea95f3efa7986a454c2dafb36061423470820e05b96cabeeab6dce46f57d997ec7e52a68d5e4fee355838ebaa461952f72fd2df8e70fba2ebe668c4eb88f9b464abdd8ddf9823c23c74dc7196cdd0be914e78ac4cf6872c5950f7461c3b917df6b2d72d2c6effe46d1acb59c5422d837724e5460ce805f4692e6cef698c2403ed7077437ad259525a07b62e42811a0a172eef0e972d5f6a99fdd6af2c8f27cca44ce729dc18a72602dc3deee1500a5260b94c25f2899407462e6ac7ae291e4b04bbea5fb32ec6ce2af7ae8a94801a5707276fb8facc08800564dcc7e5505b091a9c609b112c87cb3e38eadba2cc6c845055d1a26031a432a91c4dd58bfddcaf93baa725ddf8202fc915675607a452d8629a730f629f318fd41860006ded0bb28a70ab07b6b2a8c116f2615b37f00f53c720baa13bfa6a5483b2ae7407fc7f307b84cf96ae87810b57dc87e19da504a2f43bce83a4cc064f1c580cada107dac1e22134c9f0e8ea78a9f40d3948aa8aef4722371fbd780883ebd6743e8a3dad3746dcd565b4cc0f2c172770e7d19d2b8f94a912edb456385eb119cef63491939396a79b9fc44a79845b71008bfb085f0d0722b59cb8291f1bc758ca2872896ece3024392e74906eea8178ee45ca28e276a722596aa9ad4adbc39015fe424047c8e9bc1279ee59cb93e585f842dec164bd51e8a30dc98b92e0333fbea9e56fef743fafdf53f8b30ae963176ab2bfddcac616bd022af5612b4e837763f785ac12289224347c99f1a7ecf1f8620b5847b0a040bad1435a5c9ff71a963faff25b29e61847373e1c0f516e11229a50ee5c1f4b95febb824e174bc14e320e56db96fc933a2415c206443c853777fe9bfa702efc472662872ce9929d566771997d94d704d30d447869c6c42097a62a511e3b64783175aa8f162533c57207782d10fffd5e2a012266772e337121f0046a87bc5e32472107618d6b34477dcc10602e2f5a4485a5b07802f6e6064378ec1e5f0f5db20722df0dcfa4ac6d463bf522d91537a01fb97ceb04c935881881c2fd5301a42ba725eb87a59cff727975c5a584cf690eacc36649eaefa32930b0a434f45e38c6b7230f5d9a00a0b7021614ec7c9f04062f66e93f2cbf8ba86dc64e2c4cc2560f2722f014f49ffe6d9d0b5914b98bf1cb0cfdd770360e28fb940d82b244fad22ab120e55c8b6381ffb63db71bd2ec7225931a24ce420949833b0a80ea5cb49329872ccc86b2ecc5477a03272bab499ae060f9294bcdeb7a6adb56bfa2af8b1006d72f534ab962ff77610e3e4f1c47fffdf48f20b80f67c1326264e067043bf6c5c245ec91da78feaccfcb1cbf37b98a2d3391f6cae138174b62b809078779b3a1616c6834040f948bccdb4ebcc989f8cc48c34d9409636bcd76e2b9d1efc35b69b72471691f7761f3479b96611cd11327b0b708915a65afbfce178f2df7ac3d7f872f74a19c104ec4904c8378fb472374e9e77439df489b8b2bed915b90c297f9c454cf9119ef0da0a5c7115acd7031193d1681db1036edeaff27b6c5d0f1d26046643844a7aa4c5f44db3fc97bea155af7a22ea5118354ad1fda2c9640f2f86d5727ab1b53bbc03a50d9cdd5c31573ec1dd2497563bac3cae661dc5c7dae405fc7210e5e713f9daa8c8d7e510dc2795058a33ccae0b7bbea523eb9618c2b5b1101ea746a48d77ac384de9d732fc41b573f18d9dc6c4398c5471cb381a5b8d456614645517f11f39c6894ba6330b740a61516d33e176790338a98a5d31142155f86b98c2a12811108eb17964e4769fa1ae8d14f260f4075935a193f93cddfd5ccd3c6e6c588954e4c51ba79a47a95ce05a4ff9464dff004630ec860ca6ddbc889472946cbfbef4bee5114131bc2a0106ebfaba336a5bd80538f1bf61a3e657d5915628f5e47cb5971748f53fc83ee944b0e69aba598b0b2f14c4945ca4b17c134b72fe6bd4df38436e84e488fa4edb25142101f44d34cd0bb665c74e35b59ce2de576c8e753042a4072e03605152f62e1a003d8e1fde3316dfe0f4a511faaf88bb10ae34931214694165618a50571190b46d6508c3c0efb22371330594c85cf65172de68527d83ea8a79a202fab2d44e3ee98ddf997f87ea41b6e8ee3ba15cd9236f7a16dd6aa837aeca192d1d07c67568f404b97f6f923496247bbfc4274bcfb0727a58bce6f66729c4439ae9c93524fded33b984c2cb23add484f17bf56e42000d7f90368a251eaf34f72edac0be345954d047c2f0c764143ddd0dfc42fee03550d22344026a15501190a7dd893889787aadf94513a711f859ad5d22664c3a7a7215512a9b7e86ef34ef2457aa64bb229bade699183b3ab307c519987de6b2c5725607c5c372440b4a7b214ad8644220876d88b772dcc0c00160fd75de0f05c672a362ddd847bb301263ae3774bd0dae7d6e6411c84a6f92b25aaa9b7254b57653f9380eea87ddb455e95a88f0c8009e19fc867ddd8e24c8968c3798dbe8568c7260f39edd53b5f8c3aa188acb12be1890e51834d8941bffd850d7266cbc18e572b4ff43ec53dabe6db14ee7ee55cdd05a4e83f0c84f63fd860f2e6f070575b47217d43fc9c5d11b0902c570d70476ba45931b9b000f8b17e1697703219d22372278deea0d6edb306fc0846b86ea6807197b090d7f3dce67287cd65299d4c9d472187f59e792f5e7bd597508b9a88e32b44111a88277b46a167d918ba958d0f33218d435f355cdffb36ac87c4b79cf142269df4a9e03aa3cc90de3691f1831e572f5f58d43bed4bd3099c2b44e5bc5be6f7d9e0fc20c57697588609f048c20f50d944e69c39757c346f7b75ba6cedd8e1df393104ed658f9bac4c453050f25ea17045c7da948fbf5f86d4be43f8104fb0b0b60bd7b7a9be659eb3d3a23dfa0785762d0b8f154aef71e8a2d70dc705abb98e36c8cfd1c11f1f103fa616c26c0e8720d571f2eb11cde0ee0fbe434accf503bb01250466b2cc5bb513781a8229290729fc2c4f88cebcbe707ffde50d2611a15ecb589bafc0b625fada6a5f9f3c98872b205094a46ae845cf7d068df90d0bc8580978f437b60d1800c2112cc0f7b0d496b353685cc9f8e65697d6c9f3cfad7e556b6b1ea10b3eed431e2e0e3e0f1d83804bceeacf7813a95fddd17861ae8cb6dc247d3f5e745fd5884ea21bbb418f2728ed379a69e2281fd3974589a2309e2fb87942f843d523d997bf090a531a00949616c82229e1e08bb180a980fca28ada638d1f507239689275b6533bfdd6dd372e16607e28e0439a8f961e05b3e96e576b0e80347b7100f6ccb23b5b5f1c695729edce5b15e07e3cb98cc86c07ecef7bed211b9c50edb0025e186fae095a3bf72993e55b5c5ca21f895127bda4448f7eff770d466299f48c2ea3a0259f7b5be69d51a38ef2d42ea30704faf4f85570bd48c8bb3350bb3e6afac50234a2a8b7d72c594cf4b50f7206a8b975132ea8ff6f472b44cc69a61c350463010e25eef5272bab6d96cf2ea0f4ae48d3efd93416ef9bac9816fc8d13129aa7eba579afeaa7286c61e84186ab6fa87065e2b66c7ea7b690cc74f257696f4c9016c9f48cc141b866e5209924e53d2443265a13e48d367a2149e4bac830358da850876be0b5072734800ab57f75bec8d83caad854e0e04c36337d6031a04dfb4cf3347a691137210a507d8cadf0affeeaf6b34116e600dd8f70d370f56ca72c63c7142b1688e728f7b5d1138be6df034e2153e12c066be78af62f82ebfbee82dc23320d6ef3c722fd42eb3b85839faf9f45b2a7833ee8bd18c09016feea2e34555a2768df5e572f78edc4fc192d294b61f24030fad4c560b9169275b344ade25a26da01e7e4d64e06699851fad72bb783a048bcd68bbd02c14be987603682d3f4db07b06a98034fb3e4e489cd069ea3ae0047e9b27e2ca3179b0f4d8bd382d8f5027be99f733720b073564dcb30d10ea590deb2ca3869a066f11620391978c9eb587a1f9111d720363f613a6b93eb297fac967e862229213fe8bbcfcab5e997bf8239db5e27e7236d930fda8bbd87b0a1b19870831704f6a6756c52202a82db718fa384bb1d33893ed02fff0684ebe70694276422739a6c62447bdc71c82a574086def26629b1fc8c6c817ca6368e47cb9463b248bc31717c74234a4fdbb5fd3c63e3bc0cf0556c135254f3dc3781ec5e1475f519c77242dbd5dd66dd88d3b95c6bead7cd760720f766f400e5e6bb94e1525aec5c4ddbe90b4d4fe9748492d01c8146e355e1872fb8feb0b887db7a97d8d0a8242538e0e0158f6ca70e58ffed96f9bf56d15a04a8b51be24bba386d3d18834ea45f731afd528e47bfbcf7090e24b4a3cfcb77b0e71d67ca16504f3af2d445d9e7473b5b5a5542f1b382de3f91c255bab28f7bf2787aff9c643af60af47c49dec32f88c119cba5c10e52fcb0d985dc3e740648f72c6357db833654dedf6c98446f36b13d842a0d510d4330e24bb365f29c43331232173b2fe1e1957fb8be074c545aec72b60800563c941c97bb15c4930dcbc69720b356e71a2b4f64d4827659fced81319d0d1c9b08faee9bb5eabce36980a4872ba81e273dbd5ea400df9c13e18db2cd9d3f78ac251cdcba29d89671e5cc8bb72ae34f12c05d7ccd93df81d956d390f56c328231a2062f013173983e4dc89a03503dd42d3c20b51268e558e99c3d9a5d442066e459f72f79d2998c25ecd498d26ca6d546af8984293e300c801fe66f8690f632ba60a054dd8c91cdc9b4b39a627b9feb7aafebfaea87d8c78224f5d54492f63c19ce8993c3f01bd42a6b5e8933810dc3dc4d2183ef8cef1b4d8ef02da8302026c6a18fc9b1835b710d3984efd546502af5628bf99e2f30d63fcc743ffe8152e4d2259d97b579fb55c1e786b9e13cafa1e64b093bf1dbcfcc3e3e2c44bc94534f04259518a8977f228867c2978723a473a8fac367134886f9ebc39f6196d1cd74f241216d14bfeb5eaea8d234c7294affabea619e6c99f91e3875ce09fb78cff67f898eeef51e6a02ca8f530e372a374a7c91a6a4069881c111c24f1b7c718d11cf928658c6b2bab50554fa35672d93bc983cf1580c5e09db674a809261a472f276e4f8250bf30e24cde24027e33b8ccddf8826068ae1f5335a4791dfa19e3932e6c887a56ff5614d238f3d62a266bf796725e8b49ffc368bcbf823818c434564ed87481628f335452181163501f3dbbbff1fcc05cce797d5fcd940fd00e9aff98a7d12eecf662564569db6ae417024eee4faef92e81ce06f555a319e1d7933379d70281e457f685247d38e7a93925de068fad1af183a65237c6a5a00d4ff83a3efdfcab9930c076a228cedd69110c42635a22d2caf73fde9ff1e5f442b166dc5f65c808f518c99f4ae7fdcaf17254af394e6a43d1561467cdc4f9fbf9349802a128d41e9adc32ea9b7b02829172539e5c327d16f94f08616378dfa8a52bb395817765b167b523641f7befa19a72902e8d59188412a068db575b54b1e5c0c35ad8a269f52d48afb28902d8257e7278562ad57a5594af34dc75e04e503f63b0f52d331d38d23d8a0dafd19b6a04725f66c821c46c1c87371cc653ed8726064568a7a99cc304a1f0fb13959c0fcc5a863b19f44e59bfffb3cfb0bec3c797e7c13bbd86646cf3378a59598298f4cd72b2e4e452aa28a924bada5e57a7862ab24a11ac3fc5dac1ca5d6750c9c741f03ce45c0c4cb4319373530dc3b5152ad019c87c8ac00ce9c8d3b4b5a0a1dd518c721fde82318d996c3a7fab8e6b3e6019bb1cd17cbaf3eafaace44328fda9deb472aaba7f5bb9ce91cf7802d87523b1dda7be4f8398c7f834f70ded8143e10e1b72384c3b7706692364c87cb801b48114a744d8b0c9591136c3caf2e7cb57b84556672c964272adf7e096857baf252be58a3bbc1eda85b8cca925330115b24cfe36137587c9d22ff03fb101abf628c0c7e263ecf055463fd449e60d6036f4011272e82ffb35a39aff032f2aa863570aecb10750224eac69e1dbb17f685540d56572c5932eac3d4408fb1f5aee0c2027499f25a065e47e2d403ef7fc8d033caece4b801af6b4c36d6f05908f8b7a39f58cf00e800f13477fc680096e66941bf473028979381c6255df3983b781823b37199f1bfffac85f82763707f98e4fc83b507219f47c44d9fb210a18f8e5c6d3d41e19675005b2cdfc19b6af83fc9cfb57f1720862963192d106d8ca6e217a3421e1e5e23f41c28574b04bf1b8f9f679b47f4a94d4620a07817ac90f177d5e57b9899e6cffece4cfea4c0f6de912f2634610022e83243135434a641e1654ad9ada8b86fb987406cbf56a1db1d2661f09874e66c98ef36ffd12b8a90683cf89a1be68d606822c14eddb95839df3950b272eb672508ff91eed3e31d911a1e99c23fb285958e689ee6b580b35246544f5184b565c658ccd51666f4f2822fea041d88880106b262102cc2d6d30b8a6a56d6f18a5720c2184995b2f298ba31246d26a9571aa96c1afa02a57f48e321669bd9aed5672d86d95930fe38bc251d1205949bbfa235cfa72db2b823bf5122db2e12eba0172b0c9784b43fca44a87c325003496e5a30ee613db7842c9159ae1b8a8617b04366d019be08946b11cb53e783dbe716e535367dd80ec5c693c94e1acee4a0e7f724be91518a39a552aa2ba005a9bddb43b2097cd1d1c71ff064b96c4000de9d57296768d229c15389d8bcbb29c9a83c4f2738447ca2dc3e375d8f8ace8af9ece68817b1801032e352d2bcf7ddc016231377c62168af3f1dc5b41bf6bceea1939300acbf53cff391ad80d918f6a800fa0a61f4c85822e0df3d2f06f44dd7b1a5d26f4a59980e17a8336102d3d4a132363483e62155610396e8c7edcfd2391ec7772293a8f8a100972726b5343eebb80d4989a20588a93d4bb2b58bb9f402f17d6176991d0b6956f422f201aff7c89a0159e82b4ae65eac317dcaf6b97ed47a6f0269da615f8480a741cccb31e390af3ca5da08439b3168d863953125db05a938972388d0638a4be5632f3c9d7e689961383bbc5b2a5d076ee9370848d71b8032572fc7bdb1d87741b4b8e23395f9f2bfe09cec9171a7f3606cf32123dd548eca24aff69721c8cb45e108b7bbc9bd16f761f477a72f81693ddabfbd78f78e357287219a19f55d487bc44d7e1fd68b2ed30f781bba4b74084753086a2a1bd7fcb8e7261473c589a2b5402a238ed883f87f4fd903805b5aff4b5a3cacd6ad7e589c307ea34d533e21ce7425e5f36f8d20475ece9a1fdb5775841ee099096b09bb89372392777fe1f950ee19da8517de60b6d8b89b826ca33acfa13e28112bdaa92e27290f07b598a02ea23a49611265fb984fcd587e9e22eb48690a0179cabbbd0cb724c2f356a476692120453d21f05c6f565b6e0d1a9534cd086cb65ebb9cdbb98416b7d518dab362f0e80c2f377dfab07fbb54ce86b92c8745feeeb872a2589216c5f4ecef76138111fcc374863e375e666a5f1b5f037edb897464207ac6199ea72ece1959e62275448afcc437a975224d66d13c12625043f868eb9de463f201a72cc9159738d8210374edac3f7b708a234c824bc8beaf222213961124c035d1e41c5a1da2117b3807ddda22f489f5d51f199fde9554af1ec74aa21a3a47baff47263ce12b6de75135d3e18dd70b95c4f90b76144df21863b5797a965c35244a20dcab191c1cf6720d862025285c475e297dbc9254d658bdb2ebebf6613b9639e3e3ea69b4669089f2e6d858115d5a47a6c4c15112ede15d387790b17f77492220f23e74dec3a304d7947159bf788ce0bf375c3f03185651981e13d80e97478437255e54fc5f9f7a4afc88786ce2fc015740012df162f545312c0913999d65fd351863ba38d69d7baed342388f47eb077838927a11c300426093e9c4f80a845c372f1273544f253a4a98fe4af58224639387d7b89eae3067b291c725b1649cad73524dbc24247ee361b6f527e87bc11b1e77e9c03283fadb533d20ddf706994a65c26aa7fc6841cd61889ad6fac83a6b0a2938c6b398abca9955ed80c0d6ca55d34797b86dfedaefeb36c068eb363f19f3a36af3d851aecb7d86cb45ed0f372dd1d5206ef29f74977bfbeb66379f4a88c018a72f9d66e409d411a8011da089900729476aec024d8c362bc4239c91bc144ac84a024b29b0622e1bb063606a9ba1272bd8651b80e96988a506b92d41250013501696873cec8f376c3c8543ddb05cd72fc595335d954c169f2805c29d77e40758e5bdcfbd774780f02bfd3cbdc6247727c744c8acecfc64b18dc45576353f00ebe95e271a8be1be27dac13584c965a599544f1efb3d3b71fc0bb15dc37f2cc1a937908f46bac66e3e03e36b85bba907265f0066a53c7192aa0faeda686c115f9ce503bddcaf08045d592a7e950bfcf72f7fe2ae8a67abcfa8cef60d632f88cafff51afc3788704fb0631db9de81678046ff79d2dc86058d7d7780a735b3f61e1336f0366309649dbe98f02091ffcb255fe7809f62cd246f6345bb1694da9de805acab25617bfb463ca885818813d317208b658a713cf50f9e17eb7a72b073346842c4727eca27db9bb46f9ca9c6b97728d7a0a0fca980c4671a6c0a5e24b3db3ef395a2b2cf32fd9488eabb497f54e7234d88cd6a79d2ad502440de8eeb2d8b8d7e14306cd21e109824d7a80505005724a4395379368d9430ff0c722dc511f2611c4237ffb76eec2b5923620e009387270b15d23a80f1a909d4a0f910388cfe9a9bc10dc0f257ac5c6675ce4be6ad672033e453a6285efbd6977e767ec1af7dcd84699cddda739cfe9da9e3d4b4af02c4e431b42d20e418bfbee7f6bb628a0cd4b16feec387b641dc6e519e17e8a6472dac0b9d66d64d6f03563de0e667d89d362faff3126db43e0d73d41bc7c6dae72ef06e723e1a99c0c8d8ab59687701415f8a28706fe99a395fbb80233806692724fd03391a7d4cc5fbb4a9be5c573c51d261a33d85933361f1a8624fc104a4611602967957dbb25ca28ebf62f2a3eb7677225cd762124d16837a6e0f208c93a720a844389cc1ed6dcf4450e9653ebecd8dfd7085d283c2a0d0efce4e865d6aa15e296e76c2fe4d0122746847e8d7236cefd57a431a4146ec724dc2c2b18632b7252240fd21622b2cc1b320db88083344b9a5486536e3bd1feb78fbfb7767f7472f30a649a5d20c01da4f1b3ad2d8efbe59dbc2c30666e3b296e1bacb5eedef5723d68dde67342fa38b331a5c7e9c7bb3c359213499ebed21b0c92f515691e9472f815af6bd9476f91be39436f8298b0b6507692876a3d3f50bc8ad2a0a65cbc5c8dbcb97d2a14a10ad55fa6fec761112b1a585dd2bd189e8c8b5d0574ee7dc2074741a72f2d20befafae348588eee6634a38ee6c6ea6b9c0f95be585ac6251b72450810f445b3296b8e84b5d0e5aa0a61495190d3b55d217ff7c9047497f138688cf1096a5e8a837277e7a05c465f6335bb7acdd4f1adbff6e0c289e94265e548a4e72de7bcba627fc99136bb97e220bbdc3a181de73c82a96a69b8a06047517272a40e672dda7ad097485cee22014b6d528ff023facd4fb85eebd85df174042101f43bb2f34d5bcfb24f97d564d4c0528d8355d3a0c2ebaf85cfdf1b74b2c27252ac8b992e3d46c7d008ffc9cb65b7b417bee7ce0870919a05ad48efba496a72195de5598bf6b31415c9563f76f968d8f68de0e60b4acf317371e0ec2b63f272df57fe03d0fb0400619c78ebc2b186c82f369a04badd3ae71716a3d061c9ca1cae1387e0aa59adb58398245aa9eeefc5cfc95d2c7bb66bb902754f105a3a86408c734926ac610344dcd0b828e83b60c139a828c54ef713a7dc96b4a6ba291c72eae9d78e7c14fb8c0cbf0b56cb19b641c28321845579c3c0f68ee567de0c084faf3af7a59301f80e813e9fa2f6694c8cfe3a046da14ced42a538d7603fe71c2fc6b36af54de5c4118a55e8d09f174bbe9f5f7775df811b7bb75997578ea4a77296ab7caada89e9b06caf96271429f4ae83ef7e1e106d0d80f81c29a4d0e81a721f4fdeda8f6a893f3c7219c9123606d4abadd391538412e84d9346d59c85e6729d31ed3aaa4e7b1cca0d00b29ab4f781af504960449e08712e1a976bd38560725451566effe79f8154f266f2330a8511cd8cbfa583a5fc5b2935e2f7eece6072bdef2a9a7591661d9b263a760821dc443ab95d452d3c37466a3e10fbfedb5e0efdc86d1e201d81f44a2b3452bbdf1b1ded2e8f21e1496420d36c9709e309187247039223074a1314d8c8a81068ef993d5f79963235334c93afa581409e5a2256209d8863733f4928fac07f8b8e5f8d5e94005c31641ca9c94ad6a8a802676c352a1edb415810b8456148809530ab0916c1938a54401cfd0b39e74c57cd0492727fc05932e52ca26d9c7999e4109817ee547cfad5ace87076f90b6614355d4224c4e646c6320c1a0eb8b7b210aeb5a66df1ca5652eb7518cf7081e1a747cdcb647ec39f4aecdaa2eaf8361a7ecbbb8050a4dae53458df2f8b51009c915240387286554637ae4df137ab4c21ed2c7c9f91c809d6681834714abdea25a56a9917537f30b1cf855e2485a35868d9f95c37d48f8830aa970ce8e13822a0f171b9cd72eaa35e7023098ed08cb8ad12aef6868f01b76e7cfbf869c9a5a37a2332b1f572d1d4f338208001ba575ecbfe4c888d6c057e237e499b4bdf5c1511cdcb24fb727bf29c155b56cad88133724822bdb08a9d8add1cf9980e4fd1938624f1c4ec7207f10afa80087da49d9fa6ff874823c3cc42dbe0bea27d6918ea78b422c34b724eed8c8c85965784a41649b5cfab8e74b56b4c8988e22ca381f72d0899a94d72382424cc920d8dfd51e66f61d2243f269b3209171ec00a06a826e5f937260c72dd2659066f90bb23b2f30c21790a72352a0b8f91eea32528af843900051c5f7230d0c93ad5d588f5032586ae6680b9f6c0c1cb9a782a061e1b4e8de56b66f672ae06df511bc3361265e4a927f679cbeb940e948384489d06145996fcde4f167237c314dd5b1ac06820c70c02b59bc30a7ec3b6f31ab63e6e657d48e7a684f14b4c200c4867b81fb221aa5721e4946f4ec3ddaf37e418db982625ef91dea4a6720e072128791be59f53ab7a05a1e0392b11c54bf7da9d0cd0f14698b19d3a896fc58d866ee06db963cf0940e8ca1b5ed3acc2e726a46039253f275b46e2ab3d72fae3d9a0c96bf5720d8a09429c55a6555ceaf47581aa555a984d0ace15d45972545d8e6233d26ca07944f325eeb8fa29865d15c6d4a16899fe3b6f07db354a72abc5f1007e1452abc8cde655b8401b5418c33fbe5c37fb789de2e18808b752243605e4decd271eff50e5dc06d55bd895ea9e78f4ad6d1ecc25600f978dd6d643f1f1aa288ed6306691c4433dabbcafca68b5c856d236de16dafc5dc1f343a27215c54ca10ca6a7e4251ed93f89039090b7bb260606fefb79781993e30326a6726a03f2c96a17906bf8e03a2ab3cf1d4178be0561dda857baa2c036b2f73b4403db3393c30485168eaf2fffd6480a5b6bddc66a85c50f28ce98cc7707065ec5463b54047d5bc8bdba0883795a28bbbde4a1cecb5d3adae4a74dba1f8f64716272983a4fa79c8b2d3d44b76b995df6285f534bdcf89a79ec7d3822815c86ea4e02fdb1ddfdf36c9d3e2ece867a277194df2ddb7779c4469b7b870b033f7a3a555f0133ef44498a994ed71743cda6559d8014fd2a903a3c8b19dcb216744ea3a115e0ce675f65943c3eb6f7189c57ca92ab49441b5e4db247ed36d1101c5609a16e989c697874cd923526793d42b7b089a393f2a644adf2d84db936ee052b032372656b973c9c69ce73d12c0c74ed84a9a8d1a330d394a97378e112ccb2d14e0172b706fd5ea3ddaac7d2d284b4817eea2288693dbb155d32f903280dd1eafb40726f58a046c69c0a7f25fb3ce2c6d5a47b15d39798f2cd4efbffd339d166bc357218e17efdd96e5b32edd7ec033e54b384f40f930a5abf754a620719665dafdb7282951bb7ab3390c6046ddc20c7ab543b27fb75a2231402428f7cb76fd784426def804f777d949a625a0e49c8e926b604d1023bb3d09daf59427a2278abe6390b76330d71fea3bd3a1abc735d1b14e86c135f9937e61836ccb21ab6774f7fc372adcf2ac19e5a4be939838365433773de928de1d76acb8002c31bf497ae623d72391fec4337dbc1cee9777ac48acb15a9633debc5628f5d357f729feb2279410ad6e9bf744d48b6e32e9bff87ddd5e076225a9080bc8b19ed89fe2991f76259726ed80e42bbe2b682f4b8cd04aca174c74857a074f3a458e6f918264f51704b701f175d7545f8edb8855455c2385924c09279f818a151aee4105de4c898fa57729626655ed08a84337128b55701903e90ff0a8c5a38bb889f4c0f12fb5b8fda72b8c6789db3d067587b0d03a9836b7fcbf538232830d463f7b73492b37f758a584dab73d185150144b65a1d3f9714b0ee453dc0bc227941d4425441b10368913c5e35faed8603a892616fa6c44f4f79fba61c62386fb363c840c1238fde34a5722cf4e8292cba193960d46e638250f6d1b3c4e5cc69d95476c0e82ce68fc9af720a15fdbdca91a2fe3ba6077d2e92eb179bdfa1dcf360f8143a2c115d83032f226b641fb131c0143d6251f812f2c70845a39003c7234f3a1d9c28d09c310344721cb634f87fad5f2ad192ebba26b26f2d3aa81ad7509f759f7b49ca2a15a1ae49b8004b0adb42e13fbcec508163aafc18598b35eab87a087269fef17c8f0688120a4ab165a92b0f93820ef7b8174e6f9b5d9c2b8c8edbbe360664fc147ec27c0b7e28b7d39b5ceaac90b6c0ec61affeda8ff84a61c8b6a1e8e1f0fd1ea6da3872c694312238839e68ddb735aca3203f84db5751aa8cd3fb26083c62b32865825673bd100473720d4d6057b9067f1804ff73443e89d8c680174829f09e84911372685a27b34e869f5354dcdbd7104ba873fe4e7d4075439428108e1f55b4897472677a14028324d4db4ec66319d05c45345305806c48235828b9762d92f9eecc5200baf6307f48099280722c1beaab412aa50a0b83b556ef4b5609d2fa031d9c72203eda5a475a6d6a0a854a0f115ac6965f234928f12f43376083d7156bb2160ab8c9b0efec4962b1db6710082be7590823b8a58d4095ffb7be23d1ebb6f8637249cba83ad72294f143845178b07becdfc24cb1e39fd38d726a9d3a8ed5b447f90126b0a1e4ddec46def3ffa7b37182553a03aadc837fbfe5a42a25ab104354b4c1ebcaae7dff9b214f2b3c4b881eb54b3131f4b0a43e2e4b7018748b725217429880eb8e541e534ac3304ae12e74541ce390a41b9a2410f05d5c21b2122d391785dd6121b08b5976da3d3be5f3e069db0c95b81c5009537acb8ec35bf54aebf541dcb231fd0d9fc353107830e38052c9d2d2b932eab08ba1c578af1a8ee00ff423923f4603f443c96a68c2e80dff6e01aa79a2c0428472805243fe3785be2f67d36ce056b97bb089fad25d2bee81ecffa04b59975c9bccaacfe6b5a09422d74282cfd4f7faf0caecf25d70b2e3e9af849701def8f24246b0ad71b67e467407d19d5c0b7404129ccb72851e881a4b9a8d029a46bca0725be960a7ec1a338082b4fb68572d8df60781f90126b08251d56e9c49be57d611de95fc3854c868fb7237a88a4e781f785d24a2c4e343f091f9a273e1b0f4b750449c2ab10174b091efe93c1c898c36fccb45ec0d4f2fbe947d5f41fe13e8b243fd46381ed37f03d7e9760b1e621e979efe5f31cef0c712b09053f4b26a6ea1e588e0c882f522036f2a3cb59bb0872e29f0317a059c207f4691279ba2a190e132ddeedb82300229b9b0b1a985f8b5ce73879ed4852afa2f740ead907818fed23d952251f0f2e75d7c8312f77267000624d76c6d1e36724aa319b0892e78ee8090c2e43bd7ee44572672f35fd7c0e9e2981376da939c492efacb838f9204e8201a44282f2235d0b5c42eb9b0b36b1158e39c0b053ef6e20330c7bcadd38cf825544dcb570e4e46c0414d3de9ef9df5d028501ed176a995f6c975674d"]} -<< (9ee7c86c) {"jsonrpc":"2.0","id":35,"result":"0x85673724a579e8de3815bfedadf7a87d11e8a0e9a5d983a73dfe4aade6caf11a"} ->> (9ee7c86c) {"jsonrpc":"2.0","id":36,"method":"eth_getTransactionByHash","params":["0x85673724a579e8de3815bfedadf7a87d11e8a0e9a5d983a73dfe4aade6caf11a"]} -<< (9ee7c86c) {"jsonrpc":"2.0","id":36,"result":{"blockHash":null,"blockNumber":null,"from":"0xcf49fda3be353c69b41ed96333cd24302da4556f","gas":"0x186a0","gasPrice":"0x6fc23ac00","maxPriorityFeePerGas":"0x3b9aca00","maxFeePerGas":"0x6fc23ac00","maxFeePerBlobGas":"0x1","hash":"0x85673724a579e8de3815bfedadf7a87d11e8a0e9a5d983a73dfe4aade6caf11a","input":"0x","nonce":"0x6","to":"0x0000000000000000000000000000000000000100","transactionIndex":null,"value":"0x0","v":"0x1","r":"0xf51d0f2887cf9b6e5fe4c6e29ce3e98b4591816bc75ef6c47d6af8ab3859510d","s":"0xf686420372fcf8d136d957549e1c2f38139119615be7d052d8998d72eeef6be","blobVersionedHashes":["0x01e407ca86c726c8395afa91baa51ccdef6914ea84facd25a969d1ca5adc9300","0x01582e40a25b35dee362a3e6d636297ad4d5b925b94eb776cd41624e52a59e2b","0x0194e23ee0277c0491f2d68c15b319c73be701244274fe709219306e055ef44b","0x01358b76b8f8ebd26e5e1fc77269e789b093bd35c6e66c41bb97834b99243869","0x01bec47c8062b40930ff772e34c704698aa7d7b522eac621c6cbf3a47da99e66","0x01935294c3bac382287c160ed2f68a75c1e0a7c14eb07c6551253d48f4006573"],"accessList":[],"chainId":"0x7","publicKey":"0x95a6357daf5d9f91c85bd4e1f8b6226cb18396d772c35620d071660400a543d8b51f13cf95d7191e958a12c6109357a4e1e50eecd92db513dab323a5c1fe7ff6","raw":"0x03f901350706843b9aca008506fc23ac00830186a09400000000000000000000000000000000000001008080c001f8c6a001e407ca86c726c8395afa91baa51ccdef6914ea84facd25a969d1ca5adc9300a001582e40a25b35dee362a3e6d636297ad4d5b925b94eb776cd41624e52a59e2ba00194e23ee0277c0491f2d68c15b319c73be701244274fe709219306e055ef44ba001358b76b8f8ebd26e5e1fc77269e789b093bd35c6e66c41bb97834b99243869a001bec47c8062b40930ff772e34c704698aa7d7b522eac621c6cbf3a47da99e66a001935294c3bac382287c160ed2f68a75c1e0a7c14eb07c6551253d48f400657301a0f51d0f2887cf9b6e5fe4c6e29ce3e98b4591816bc75ef6c47d6af8ab3859510da00f686420372fcf8d136d957549e1c2f38139119615be7d052d8998d72eeef6be","type":"0x3"}} -INFO: Sent blob transaction: 0x85673724a579e8de3815bfedadf7a87d11e8a0e9a5d983a73dfe4aade6caf11a ->> (9ee7c86c) {"jsonrpc":"2.0","id":37,"method":"eth_getBlockByNumber","params":["latest",false]} -<< (9ee7c86c) {"jsonrpc":"2.0","id":37,"result":{"number":"0x2","hash":"0x90c8b9e1e8e51584453f5516036bfe0031ebb595e47c5fd664bd56471ce618d6","mixHash":"0xf8caa5bee858bdf1581f3920c0a700cd25923f194fdafa96e47fb2198779c608","parentHash":"0xf9e9afb4b42515283ae292d4f9982cb0c87d2338c717af437fe39175fd5e5fae","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0xd51ab9181e964dcec962295995b1fff437d9504a88ab944324bc1227c0c94bc2","stateRoot":"0x724b6cd36d03f71b4c088b69f1db0a61b3d9789546c24ef32d59f1dc2007041f","receiptsRoot":"0xd50521034c860197d235df5876ea04b9bce05f69b7e89b96e597d9f6d35b1492","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","baseFeePerGas":"0x2da282a8","size":"0x408","gasLimit":"0x2ff7d8","gasUsed":"0x17a25","timestamp":"0x1236","uncles":[],"transactions":["0xd886baa4d7824402a508487d94b8efed257832170ecb5f5a85b8d2e15317728c","0x3cc701f8f4e4c7d32e1a55dacbf4175dd4a61b4b8f26be373b8f7b0bb4c430e5","0x6208c8da6ad2d0b72a65110f972a38861ab4d12a45397dc6026d07e5623f748b"],"withdrawalsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","withdrawals":[],"blobGasUsed":"0x60000","excessBlobGas":"0x0","parentBeaconBlockRoot":"0x062367f0b23e2d49ad5e770d9ad17b83c0c1c625c3f9a290cd9572b3fc6cfc9e"}} ->> (9ee7c86c) {"jsonrpc":"2.0","id":38,"method":"eth_sendRawTransaction","params":["0x03fa0c03a6f901350707843b9aca008506fc23ac00830186a09400000000000000000000000000000000000001008080c001f8c6a001b0381c687bfb6a8a302b1c82e1eaa40ed61cef26789ea4381ae224873b2ae2a0019fce4ea55033892d6e138b70ddee775dc5796dfc6bbb34a42825e8ce156300a001ca3bdfbd891bf00f69d06b24ee2595a7fa9ec16b33252c2826b2335d074b41a001549da3d2e959bee6f2bf70746e9c8485b0531eb86c80acfc4545a44c9d9e41a001c8ccd705a2fd73edebf7b293be6e2bcf81eebca7ef11dd64b9a5d7af975623a0014d15e626084ab62165686e0bd08d22a612209881d2e8a25d761ad91b9ba4ce01a00b601d43215c076b233f55553d32fa2dec9bfaff9a42766dcdcdec17dfe2db7da0374d58681d0ef37da749ec15a9fad320947145b589fddcbed6c12892738752b5fa0c0018ba02000042f28a46039f894d3a0179d090851ba795ef081ae128cf54ee4e496d3453244d7223c6e4c3a4cd9abe650069f81a3c4f7e3691a9681ee7a56f47f3930a13e3182224e4b555b13cad2446afd7f98a567b1edd9db720973de9cd3871fa30427b5c099b4b48dd01fb45c761cfbfd4180c469b4a5c9f59a7a3a7d90ae4919a3f67f4547c8b1c35751542e53640ca73f04b5f721ee0b0ffbea24e01293bd569a03f89723213b4e23315598acd713d683b22af7cf4c8834acaeb466bd46a5ff8b4c77a2f2b13228e935a6026e65f832915a1dbdba1dccdd4cf80edae2bcc441d05222972bf69c43017e6ee3892840dc99d34df070026d42f1f7d2e73ab8b455607f80725e749c09e3ca3804f8fad6f3ff982e5e6eaccb211e02ac43a55dac2dde8eb16723bed72530c8408b2ec00521de5631c807094a9fabe1536b07db63e416bedce47184ee2ab247e96194afcdf995851d654cbacf908429caec7b26010cd7e95667288e4074802ee68c158889d5a9b82e73ca7a772f7504033fc78bc808b81ce9572bc09171071242dbe4aa5334a5e96d0752d289324f08362a4801fd8915cfd2d726848275e75ab69b3bef34255a1de20ee6415fc830df298d521be22730e94485601a95f197a4627b58391cc6793e2a1795def041df6fc0b683399846ef8031b7296501843457d44e057abb2058d40b4b3b3409d486157c1ab53ac0c499fcc8025048a1ee83a125551ab31181d743e4edd5472cdbd54a2ec85efd6ba777d527272dec14ee6499dac407942871945a365e04a268715018c47506fb7ac2b20f8e616e64c2e7e57d1160fd360d380bf6001bbd9e82b0ce4f42f389906f193db73d31256186ec1eded7f463a4bef6600e02e83081a4cd1936b109300ee411d86b8f57217ca7a250f2b946d088bda7e5ef7d849c24c8dd651e36889a08a9c991cf09472095aac9f0173a467b61393ac6384f7c522d05cc352d4465f4fb2e292919dc172d1fd959096c1906cc87a56ac9411078ddcd27ff29a438a19bdde5f201c6c57725523ccc2f48ad8c1e06258c5efbc228217fbdbfc3d7236c01a6ca63eb9637f188ec79737a64733aec1dd2bb1fd29d00b7392bbfc89e7771296dd2b35e3974a51db0f4494964fddce01aa844e9b91b0f946f4bdb892f8085e0f7b2e44be38952ff1366f047853007bc98c009a62dc93540d1784eb71c97c2ce0bde3c571136772f6076f99bbf4ba64780703435510cb9e75cb3ec4ce92cf679aea0a92e59b8d165da1c89ca9d62fb868c5ced603d7e8d655a8ac714c7cb9852e23e70a8cdb173626db919e1c33d16034071fe3f9c2d7ff2c59043939d6a90427a62916da8e6072ad1cd9badab5de7fa6fe8a9143c9183f77eb4b03887790760491eb28acefe54d86377798aab97f991fd941bc20322e4d9a0190f2093b0c6f06d0c02ebc5670727e7e0008850aecc290778d907292b70517e70388db6b880f93d7fb7dd39d6e39dc844f17d55ad4cf6f6e45ec0dadabbc5e17a44895563dbe3a52a6d1a15db57276ad26a9265c26221451ceb4df710a97b2f9e04d9a2fed95bd7272571ec5a4456acea2c1cbb2f9af233cb243edef76b19d2542c21e0d6fc5c6800b4dbd711d72b868f363c7803f9e275b01001d757cc09a9e6f83d3cc23e5d9ec229f9c214326497b7791d459ed641f89382bb74056bc81af901cc3a82d9adbb74b979807fb72ebeb105fbe91e4158ec262a0e3392d48c0d78965e4fe1a5d7240a14d57e6c711dcda5f0bfae396bd5ca9cdec7920d24ca1e5c97bb13b75c3f61544678b4a2e6074c60bbf172d297e096cbd931b9db95f62b84ffd21f9523afe7272bcb7e0c3728692c3a6af8014b73fc66400b4beeb2ec003421240b7e48bb4f55ab288a9b37213a5ba7f84cf7f6fc99811b6bf7b21aa71399b92b43626dc879a0cb03d1aea6d9c94cad67cd89e6a1f79a8f80bb4c9c1a9c2a8233a8ad962640244ab34ffb16aabb3a536dd30b7b19a389f5196e699f09e410fd40ed7f7ded7bc8bcd85e029722cad2ecd554c2a22cca5989629e48b4b2d31c042fb73651a8bbb749295a01e728af0cc44a27ed5aabeef05688c9ecfd2b944f6142b9b325557e4dccb690e287268131f4c812a5928a310b8b461be8d04e605ebb334882e745016ae79bbf6b572242d4c21ba953336b47b61e4e1bba47d3eb4a3b99197d19aecbe70c78936ee72189d88e940ae4a24ca3f9f7a3ad685a9988a5dc62db4a547a8989eaada6f962f1d24f4e47861285742821a9fb9be98882dc2807c4ad6fe9b3582fee3d567ff3e688525468c8cea19718284b311da25ec189cb51f5e50d6a01ef0e10b43df5444c1e81e84d08cde28c4803cc1b30c403f45e8dcb200a9830bd291145516fc6e39e564f4d10c775fb6c33b041f327ec16177bb597baa76e0e69e94f644f097b67202b3e34ba7f2297604cd3bf927f5c0dfd45516bfefe4bc055aae814a51f729722ee9760dbefc78883fd42ed28947afe48423fedb578df720d6d3554aeef94572055aff372bc3fbf7e2e54040bca62cb9c83815b6256518889b9768c8ad12d97225e09b37b2295557b020728301ec668250987d5ced7024c507d67e79e1187c34da569cae1dd893777586a373548a65e104f6cec373339c7866bc19f08f79673394214cb56d92b9c537158dc3e7914779e7599e85370202c589e1cde6c23f7d6c66f59aa7762ac9ee16577ed4853fc0d99cbd65f91f0c32a6a822765f66a9ce72e7d37c4560d7154fab0b3b4fb97b2c85a4a78f221413f5ec39ad997573682f72573427ec65ae059b02329d2ec04b20990609ee8a8853dbaaceea4f6a2eca43099dceefc500c432454da58d48fd09b4bdfa436a50779fb3eb5b335db9daccad720842cbfa01a91d88bb12eee3322caba8b63beb6524e2ff53a96828079a05404f223a7362bb1e2d881d56f0f76d19cb1bba049048ccaf4b9b13183e39ce26ba013ebbd2b87019978cf73e01f12f1a7cc5e4e3891114cfa3a46b0d30fbd3ad4072279236311730cbafa0f90810ca8215345f6f5620db5c23d3ee56297fd6e580723db6310eff733fd7767d9da030e6d3d67e8f534276c969ae9f25a1209acb5672f0972c670805324e70552f84d7e2c6efedc945b1a7a67ec08e2979201f9edc4ecdf980b4c4b82a4a431d4a54c7e42b01e01873b52f089ef9d17ee7a38546d81bdba67e18cf401f0b4c3f6817d00a152d35a18c3289b2f2723b1581dd50aa9e72f9b3f8d11712f21cc0fe7bae5982fe4a56ba4237ee83d91b9bd502c0978af872a9deb9b56c87c8d8a37933a1b9a7983919da3d829bb5fd2c8ad2d993ff83017229e185bdb8dd0ec667f3b77684f34f6bacf7fc2fc09e957c53c3a3a7be7f887207b3c3983f757443d72e106355e0abbeb28c9eaf92a3fa3a8affdfb2e7129c72a5e7689106bbd04a9c0cf39bb404b9b9780237a802b51c490865a13a1cfc0e576732f7647d45584ca2946cb79c242ab20dcd29565c9dbcbc4464be15930b4e72d49c4d6abf15c35b704b3fd32b67d194f7f0826d9c1620df07e5429a3494c9720be28d3e36fbe5b2627c68817b280c769a5da9f71c89ef626e4a9fc3f858a5711b0ef654ba8e0491b984aa66653caea03bb5a4d249b0bba9e53116fa8f2f6719b9804e08b3a6ba4c895ec5042cd16a4964eaea59fa84270cff64086ec4d48a681488acaff2609e1f8a41fac10e208041ade95400b4630afc8369cd310204fd20c4b91b741e01b7ef9ffb02c6af75fe2bfceb741fb6ea29c5a113f54e2e60fc510c82f34397b489055de97cc41983f9930b37da2eb500e583011d09fabf2ff4489fc00c9d402b6a918df5173a933b906840e37f9e46adf3f8ac54397ab1b92944835af164e8789ae29b110adaa771d3f1eeafb298958d0a69d2b961c6242f472d37818a92ec18700756e586a7f2c126376388910f047a996328b5e5cc514906306510c8e4f0e8f0c83b592c5bf450157979abd46bb0102759e93750174b265b601177b1a0e3b77ef5a35d078b3c3b406d830413a374220e3cc9d48797342c64725a5a2b6420d104ffa94e00d3da508e53cc71e9a1cfc802ebd4481f1c554114729188d6d61edc2f029f55fe7f7640303e69b14ac985f32e0046506e99dcedd672a0a3b1c0209d3eefaeb7d82e2aae10d19ae521caa96a1cee1a9b4302e2f7ef72d81a5df99bed0caf628faa523c9a7b677efe6297c6af39aa7926ade375f441728dcac8e8da1807f066f090441a11a43ceabb423249c1420914e813a121fcb1725a11e7e64f065c1fb76960b01fba36aa190dcbd276cabc963c276eb2fe26412bf9bbf76e7919edade32a914d950d97a1056ca6c998ae87df380916912a663c0b7a73c328ec3ff0493e73a231a252100d5e92c8d02859cd1c2e97af83bdd20872670e642371d7d856a9e2cee1463c3ef1a2ec29c7b4b8038b756a37c609f18d7229b480d043674deb6447d8fb2e7cb6e69f29ebf7bb7ade6494ac02566105877235a8f29a463696abdd795be7795e9845909c901ff6eac28aa660b992155fe672c9435d24ba7ba299befde74a8ab692f3c5fffb227d1ea528accbfad6c1803367db9894c9af7e13645cda5b7f7cdf5a882f9215f461778d9bdcd54ac044d7fe723c0bf336ecd59df6ef9c0666796e20139f5f6c7ecf7d961519bbfa4bcb09a072f88267c86652cadea3b050e3d03be72cb88fb3f6cd2a616770beb1a826de1372e29660897ddb9fb62f3e02245b8ed2956d20f0c4959ce0883568823778ac4572f5141103348f9eacdda221050f4437672bc061190a37818ae4e9e9ec8152b01bf09573889692d9bfb88ae9e545fb7619184806f51b20c75d7190a40264888b72f22504d0705b8d31118cba282b38b8ee607ca370f51efcf9c3cedba7577cbb4fd51d4d84b3da57c2957f8b6a6dde89bab97fa6ba47104b270d433a595901594e2df1216d248fc236e5cafe06a48fedc8d4cc4510a9faf74afc8923aea6a4455d658725ef8a4a9de83fad0b1b8748da1366944842e92573794307796272e84067f7257508c1130b6dc1daa197e6877e581b67e2a426d5304049e07ab76c9435711d00dc16851b9497fb0b50a6827809f3e582cbea4cace9529b82328547670a72cf99da0a3776c4014f5194472bf314ac40b911a67bcb0dc09bb407340fcf7b1f998ac16e81836d44758d0de9c3191cdc0067fb25cb91ae9d2de320d32f24a972f7fa0db0493ab3028bb57f142519bc3a2e36483d1aae51613f4272f615367072aca3fadb6ac34bf6e5ee0498e2069cc0a1fbb919760eae90ad097a73dc97be72ca83971d75ad72913ffba6976c450de9a42cfcdc670ea1949bfcd9dc3a55727261bf7c58e652fb78a221f87ea285b6e69923e7d697eff0fdbe8ed8b93310007206976b485003873267ed6e639078cd6908359075b8c3c32fcecd0120c2556872011230767146ef48defefdececb220b374d81daa2ee4ad9691fa9564833d4d72e544d50bd989980ba112ee5915703c863b5f2800bf038dca2ed7fd35fc3f6a36d1cae737993d9d8a1c2346ea156f3b5b73ef7b7e1639958a062229d660893f6c4156ceb2aba02f703a62e1dbaa4c8e09cea5fe84e359b088e02dadf4305f957284f6383b87991fafe99544eac394ec87e3a7cef5cc07288ac3a76c5ea1d30e72c2175c948a73b941243b7bacd95866a65e6e3a768390fb3b38716385c5c1ea21325c892493a07490119a57149a0d94ba269e9e9f3500f22043032d78ac1ff92c08407ea80804915f7af5b478d229e80456a02cc8912c2e6a8adc2440ee2fe9728de6600923c49a7f49c8adb69c0df19bec3d8f94c451f82d6da883d4f5020e10ce7a6da3f880ced80c5b2af61851ee27b1f37fe8cb1a7886954477f3b76101726ad82d56ea5654fe65997837b4deff5421e3e48b01fa45a721a382f2f7ef7144fbcd338fea65004e361f23285fcd98c4e2359d3a09cda69204b934b7659f646cf1cbb6467d6501b5d617fb6de6610c50824c618199b3e3e0619606529e9357049d9e1c1570cf9e89666c45b2919cf03413ca5997b7ecce56a1e9b747fb015672d0d28f091941c1ff1ef05cedc4a95fb6e6de6124623be25a1c6d2adb1ca9b739b7fd1ca0959c593ad8157d87d9149fd43db52d970e08c256e3feecf4eb844a7286bb8f758374b1987090834869ea7285629ad38222327cc2d7c608b26a0cd5726545c1d9533dd5ebf6b60db48a12115a3020f89b7911017971d98e7f2bc68b72bb779d0a03c38b4ac8d4dc57f87dbab7f1633a9cac8339848c68996bc3a36720ab3bf060579ed5dd17de5d1ad6eff1a42095e8e2ca123e591e0b009e92dff62bf8bf44bb1462b25aee50aa07396039928b5b1aac4bc849d4b0e4a65947b5a072853f60696af2d1c8036034ed3a51dba8407e7a09c65ea80942978460b5eccb7290afe564c9413d3408650b7050ac3b59d3fba99acc60e5b03f87e62f01c14d5c07a16000a61ed8c841f05d7952bb604014216a9942b5b422b3799d15aaeeac2e57f353e3621002109572e409b06e5432d0e0e768fe06703585d491858bf9271105c316ce1e34b051dc5cab76d9ef323df3b2916a35631ecd0396a85e035a9872b5ed3b416f7dd1864f6a74604489ef9cb60031e8fcf3d3286a17d03ffba506723e752dc916fc7b4f6b7851b44e4ae1cb8ad195c6eb8620eb7a20dbcfcbaf8249601996e83001ce6cecb41b38feb9edbb2f665f4adcc04f1b85d734c3a0a39272dfbfdb7884a1f9d9e7dc34e69247b57cd2b71955ffd3816cf7694c3dcc9c0872b5ba3f8308ba41a3b9e56b42c403dadb675264d0b1ac10f4bd3b6a5bd9a0d91966c010a5388d651d21af334a09304710a68df53b04fcf3cb3700c1ccac7fb572831d15dc0217629b9ef1fae141b812a1ce0489879199c3b3081c68d0ba9e775eabcab61b6ae56b4d41aac8a43ba083cead5f723fdca22363b645c759498f4758cd031428ed3e379d18b2213b4be517fcb8c42cfee9cc70f0422774bfc1412b72bb78f2802049dc58793b6825db6006ba632a15fa687edc58b19ec6464b877c72d2a83216163849f2e33f25b87f5a2e266c4e2ca7055b20ca69229a32eb39c97244fe004d937f7bf7729096a2f7623bb41537b03601f595612a1b7c4c327cf7725440163142a6e1b87b568bcee7bba3d32c37a899f1c28947f1720ef65bcb46150e2f8ffc9dedca209ef5b4f4fac89c6286973c2f8fcb643d2dbeb8ea531f427256705f5e3b81a8e1e8a8052fec93cd189d620187173c766e902ea495c5a10f7222dad70fac1744d2012d6b28604235c425cce633003402d580abdff89f15e5725660709571718141f1177e47d8a399f4e0213c2c49bbc5e1f1120660f7c65f729b5b9e20cb2be894ec630a5440f8d2ca5037d34c3bc87df202495d11f65ce27201110ac28ff7403a7bf73a68c6345461563b3d2ef17bb5c6ddacab67824cee72b35bccb0f3e0ee5c9ab3e4b15cd0344ae4ed7be4b4768d74030722860dcf39721984e1bf23947579abb7d15240527e2dfbd82fe03bc16bd3ea017039b66c97728f5cfa19aa9e6e6b863cd3cb2fd181b5e799ed2b30d2f6665f1645513c4fc243152ecb0e6537b190feb3e8e621de75cd82e8398991bb12ce6ab44faa41cd7f727adf904a1f4943621923be11610232800beff73bff9a754195b11150d3199572275c3dd46e2a5e2e76975eb1eef1f57b7901399061a28bf0b6da1d8d52681972427cad5ea17e62ac21f67fbbeda7cd502d3a039bd7cc024cbb74584708d38272f3eb3553bc833daadbbd8ffd83511e225180ee730e1bf5f3cfbed1b2187a0f5d41297463be2480f64c0a70ea985cd4dd6990cb5b8ae37cddf65b7ce9a092a572ed0fdfdae986538d3524e7f3942e5ef338766756656ef2b47377bbdae31e9e460988bf1efa4f4ccbe87be572b25ae2c7394f8ade32855b0bd9b0d45392c88c36deb653d600238653296f2a299007b7c52a161933d3e400e1bb07e68cfbbd85729d0c6b33fc5918c31eaae0f061119fe099967b0ce3e9ad06a3fff066d1fa4672d5189bf665f5ed23ad9aefc9e50bf0bb29037fd0dc2010265bb77b0cfbd1337288501641346051a6e79ad2752de87fb523f7b5e433c813a8b49d806fdc31fe5bc7071dd698abc2139a5e47dd747f443d03d80cf8e925845cfd4942195b062172a030d49d401a863c1cdf22fa61030da5d5e759151866056c15c0d4f75fe8737248936f7bcbac6914f7deb1b89e2a5cf2840d9c23ce7338e07f8190fdb3a56e72f19e644e97296122b366c0072f76fcfb38df9353536daeba77c09b9ab4408b7285b1ab2a21f3f1f956587846a2fc74dd57fe083614e1477333ff27983a6715722b664cabbc555f5714c39fa21fd5ccd138ea2b5f84bea430fd2c84a942c58e72622935cae55f9fa0634dbb338a92161be09bcfc67e48e60b24002167cf0c9e0ca1940a9865c5bc0ad8a7b9dfe80034a357cb53f5645baaca61a1bde989086e3d3e7a9e6d76fb1d763a48cd67a35681911a500c815b47693bec0422d8dc97560a38c46e73a017cf0ecb4595297c4a0b6c78dd24ab3c90ae16d5912021e5e55820a417892ef1b641e616c1f71c54436cae1a0ce4a5e2aca12b204f4fabc3de6e72da039c3b9423cd7d38245b27e86fee09b0ca38a7d356ef3651bba782b1d484452b2fdd856a8a26f4686d995bc27013ec81e05ae4fb20f86347d47b9bc879f672498ff21a5c1416ca74b33ea660d33fda71832428ec08113c77202c8b81d87c6500a473e49d07ad645d337a8587c60721397a28a520197263651e39bdf9a73071363b3cd34cda0662568d90dcab6c1ce8d00f82a2a14236161ca2439025012572620a31eec931275d9ece585849a09cb9ba51ba588c65404e3d699ab91146be72c66a97421a27a48757855401be25d6ad8058a231d7ca81273568d26bf6586d72690f0299e397be216a0659f5256f2094f25da67be9f5642a360255a08b9218723f7e83d2b43b514faca4f68cc12023b01e0452f8f24ec014cbcdb8a758c505310e27dc9a57b1808c7b3472994a842de67a22939768103f407973d33f9253017296db323e18fc335e71f6b0b711db8c19c842eee801679f4e5e7d409d39dcc572e1f09b316716d9a5da5cd8854bc1a8741aea9d31427dee01ed418a517dd7a272324b31140db3e6963ddfbe4dba3509e85877b517c36d6f09290e475a3f8880193ed97e83758762e9582fb61abee8a7f73f2a2958944925b3d6ed79e3b6c5b65af211caa5bf53459a61af48a83f34808b2aa63271ed0dc42d41f50441fc281272ffd95598fc3b274b88f29c2b9ab8f13c912e55ce3d295c09d7a4577df385c6727803ee79b1f6263d38c9a2857f29b1452346189c2ef91458a99197d532628c409efccd062e0eb8478e27695f3d2b103eedd9b37a1eebde8431ff6ad7a6695272159e81fba088911b4665255d2e862566fd79399da5c9981141598ec67fdeb4723040dd0343770e2321bcb27f5a293715aeee751015bf73c3b630286b64d9c73cae14cb344cef534ee59147306d63e496b2b888554e21837d3dffd90418736e72d32a794e67e7faf108986855175b87169f237a48fd7a0bacfb3f46f1445758721cb791edc2dfd4a3c9a12399d5fdb20b45162643b348ed0db8460b4f871baf727c8f9e379736cf83c5917df0702cbfc6596ef730041017f1d10ee8d10aed1572b02027094b95cba3c9f4eecfe46d8b4679a45b82425e374c5edc6e3dcf28ce72528bc7143350957b35d9ab7ad3830606afa435dfaddf54dada413288428273724aeef2ebd6bef3a58d628885098ca9e2adb58b3b25f5fdf53ed36937fd563f72818e502bdce57cb4f906bf254a0293426ae09cea19807aa59fdcffc298a2a172eb26a44a247480eaa737342cc80672cc4ba58dcd0d74d0a78581437701ac597280fd2aac7582ecadf093e8d4a025cfce5f82dd41707977472c07ce2ce09c81206d1dd39d9acc5a32bb95cf69c24e97fb6b082b1b31ec79b87bb7475bf92c18728b6e0a5a331545e2f5a4c6f462e8017a9c37417387689456b3f72099d2d15145d749c0dd6852702e798211158651adcd245d2ecbed3440aee8193cf16bc0ee70ea697d9da0a656028500c72096b6d26c8709ce126c4a758226578dcd5b42587275bd5d38ba918b03ff20c4ecfc078e0c2a45f2792b14dc721e0a87c51f7b36723bcea6564f5a62f30685820f0c6f93063b6ee23ff4eb04b33d7f76ebc6a76c6e5b874e159a16cbb9bba0f53363128e851db2981846a0c64db00cef65c4b60444491a36c4e0b4f2a0c28ff5f983a0763272866c12825113eafa700cd6211b5311fc826a71feb6673ba561f3e0a53b3eb2c458d822228ea95ce6a57f4807640272e41a1240a60f19d110b8af34cbeb796eb115aaade390ae19af3909292f6b9f728c065873bb2ae88f004a70c87ae86e9b305032fc1e9ade304319d6aba45b9c124b45f2cec20fc874154418c4753a8e179b3bd02c3b283c4068fd20229929ad6961074a0e7950ae573c7010077905a74f2226b82afc6e323a46f71a29b4ba42721e52bcffdbf1c70cc1edd6071819d15f04609265893b64683569e1f45de76868398035b6193e6bf1d8f308b585e8f30cdd6ec697aec64db77d7c0bbb627f5d49b840c2ca6012971d3ec76159879535e64c3d46d698e19b62ccff67b57c0ba372c1f6093adbcc42f626971435f0d71ecbb49d79e205526ca00250c3fd4e9d690d34ef89e8c08d5afc2ab28b49653b6ae413c7a965cce4ef51ce3dbee844050c3c8f9ef2958d53f3ffc5ea403f4149c72e176ae4844756c351a30082f9bea31872945182c6b4b229aa47f234422342656cf1cf5cbbfcd3313e92917c4768878c2bef878bda9bf3a14249db77256c50f75b3f03138594c70758d95edd7795a6be7211fc1a569c34f94b707c62e1b537fa5ef838f455b8072a40b5df74ead9b0d4728a3278f081803f52626ea25166a0a486dcf4aa9c1c5ba56cb7515b5275c2ff0d2c1e3e83239940cb738630d2e7aed499a097b2a4f3c639e4b05318d4dc9a9e2338139609aae428d12a56482deefbdb1d572981085b3e0ae9d04929440b377972db6401e6d428e7e09058057608f153d4fb871c2b975a9278c39c1784c09ffd4f9bba40058db80fb77a5cdb0d30db358b27c55f7d42908d52b2a1eddb2c7bb7728f6b1c174ee9088bfe4a5a6f3441e0e1ba9082165c308cc74c1cb1ffa0af1072b2fb80388b03540a4b27c35e4e5c233e832cf6c3b3e0bcd5ff7914cc1c3fd33b6eb8fc918cddf3a28c855e6c87a1137b79c80852306b7dd73cfcb991f47a5372fc9786a169e9a727894a46f84db8ac4f4aac29d8746a2e4f2cb7d47ab157ae729748896fdf5043474bf30c58da54885ab12a81c0ebbfdf0441bf6bb912f84d72bdebe6eca882130afd03ff6573e8e546b1e4fd0697fc7bb2dae5104ff8571b72e2aad4e2a071336a8139ffc012c0dc1edb703fed6e3b4e420a5b42d8bcd692703ebfccdef46ec7b6d1ea9cc6964a39552e6209ac1704e385a7ac891591adda727a057fd254fa0fbe3d7eb8927709eae3b3f1f485f2946dfc6d81abe0b6467f2c0ae72646fb73969579d797fd3fd67f90bb5fdbb74ff84ba287492c32112ec572d71d8e6c23b0b16954d87300abbbb45b51d493dc9181c603f58d1712a433b672cd00132264ba332aaa51616df36ac14fb29f6c341bdde591f72f9bfadf9c243314db588e4da88acbdd007e6316eaa375381a52b997887f48c4639396cac2ce6c44351251574c1b9473ddad5db0c7c3527a53aacc86817eae700681bb027fde72067a59e6f9f1ae5ba1430ae2abc6bd568504e1139e9ea0177d82e9ddaca1b12325c6f74a7880e26f94065c36046e35d3751694508664b9e60a0423a36529981f35d00d6525dfff4a9d80c9384d2bd83683dc719c119e8238d798d22bb3734872c96da12fbf608e6ce460c3e142d56d5bbaeaa6ace278281af0fd9e0dc839f372959c901c62c8e604e1cba72c491ce3413695644763b5b43b999e125f74061472a7352c8705d082a9cab44f17b550f488ccca24cd31d00421a1af60807b0d257248f7db715f8f744f8aa6cd0ddce16c7e45c77bd5e0fe4b4b84e5b44e30d16a41b2de3a2f4b3dfacc07414262d413a1752978de66bee545eed6f8dd917d000472a0c72a78b660b41ffbc40847769808ee032b2073214866e1ef4832b99d5c850293b61ea8951afa25502ee786cc83455b1dfb16e6b4133ce3149324ee7a86d15fec8f64010a97988d90420d2cc53752f91b24aee1a7cb1a84a45a71c7859156129e9042f57b8d508e62a9a360e6373e2125ce87155aacb2f67dd79deebef77e4e921df61f334702b318c582205300962aae02461163cb0ed3ac57982360e42547666e92d4b162c6959b835a1797d3fdec31cd88fb7bd95f8a6caea9465ac5b972c043623d0f14d262b2b4501efba25d4221fe2b60f92c2b61e6d802af5be1da62fb1826a3c206583b7f6e6af6bb547f763776d7e456f9ce1656ceae151a6d1572fe1bf26d6bc78f5834021a3bd18c3872058d1345395d9ad291ba453e1b7f9f72c12bf698419aa38d0d8b0fe1de0db268efa331ba01be472a3b361b2c592fc03f01fc7a76b894c5e7f98f5554fec9dae39c4de40f55beded88f798ba1dbfec06b8e126ccfe9589a47ddfa1948adf5aa7053726df6e22da64a8fde7310663d4672ad0f121849bfad7f3dc3e96251e1033ec9c53bb249a9b973049aebce26f577729002778a29d1976cd193e9540c4174477a692610a019ab71f41f978091a091723c20bb57de09fd7e537a737918797b282a3ce0e1c13a851bbe208026ded2e12674b9bcc7c00b4f5adc7f7ecd0b62b4f38b94d676e8ecc08be0cfae719ed0e6727dfca888fc28c1b30d4897a50ca78d14950a245a7c5956a67d450e49def278243e83c9a29eec854072d88dd1b87a1db57d6317f7fe6bb6779e4332cb967ede7269717b3656cd350938a047481736bbab4560c105e816f90cdb33d097e85e2f725e527d70796198173b828f0c2c832bcca84eddb80ed6504ba938c202f8bb0603b02ef3a5f73a69a3ab5ab5f73fcef93ec7b5e4116e29454558cae33402e40a2c40159b0c3de32ee47526aaed1f0013ef7066be34d25bb822e228badc2c6f3b4b9366fc97dc0449959233fa05898ecb3ee0ab8e76f6d5b0ca586b4efc1149bf56d2c7ff7a22670923dedc38eeb68d6b166ec05afa40431243929939448869155a5426b5a4b533eb7522e6790897a5e0a5d36f6084d10abeac293516ef90020a72ead420c4bf1b11b3116e8e603ee8755c5a4ef1b708d993d2e94500c87fe0ef397c4e696ede580f037069d82cd74f8b95f4ca657839fe5ecac4f0a097d9f84828478fe505bf3a428d577671b8a4ccd3b58e73640c4c0d7ce8be9d95cc841ea271767eb99da396bf9002d03b2807e773f3658609ef005050a9a699b396a57d4c72670575a7c174ca79da48d2f73ac1a7ab27442cc335da2097bfe0405a12d37a2cf6103056e633cbc5e2e3e6aa7445721a0422c2cb899140d415a1e61227622472b4afd1503825b5ab06d5c208e9cedc690f0968f177a612d235803fbae63ffd67adb8d8e8000677ee70a5eca715fcbf03348c9ec4cbec01a98bd9fe31590964723c9895ec70d908943bd93c80044e55a87ca6ffaf7dacbc463caaeed9001b637288195117d568f5ea441d082d929987e0f240af33ac6908c59f81570a254dd272a1a87dfd090d98905c3133a6531d35f2315ba1710bc6ba7dfbe26d81d26993720f137332a5dff438776ecc08ef7ecd3d3a1e67b4608cf1aac875e957ebcdf113c8786345aebe3e56b9dba4d8dbd1561fe991df3be7a1f4e3c50df892b3793072156f4f2e50a20873f4b3bebee1913607d83d5c0c314f2dc782ceba07f746bd7241226b9003bf453c145ed1db29bf484473299268cf8bb4c9c684cae8e7362e72bd31681e57e91e557c6572e1ce7d2eafc0b914b199f2f0bfbc2d415fdf989a431997f50ed908ea80f44b5997cdf6debbe5b5a484d3488d13365f59d81fd91572721fb5fa85046b7b0139d0fe6e0a39379c4e4238712538abfc8e66ba35aab02a9668ec231fc0c18758e022e64a03e440fdadc6b843035565e759180d2bf017721c60416947c0b4afe4475f390bf59bbb6c268aae290c2ff55732bd0bf894a4721d8595662805703d60196432458ecb5586cf0775f5603a598dbf0a74669cea42d89aceb930bdc71892b9a0a7b0838eaf45e33d012e386b40d9b805171a2a3a72856677780244c5b338ffb0afc8bab91e10fe29284966baa303f9754e46f576727c07ebd0a85ccfadefb2099b5ac89ea7f0c15f8a985d6377166983b564ad667247d9c50304bc1c0e6805cbf8c8ed0d676b5a01e6bd0f69e52db11db57785de241874b40433804543ad57e354220b972e61f9fadb4a9478bae0ae641116b2ed7245944164130a49f165283fd4e0c5d4e0a28d6fa6cf9ded6ec6d1609705041172edeb40d8193beedd14f6970c520408f1449719eaac43c9505dfae1bf5a5c157271c572669cef4e58ac2f5dddaf06d774b603fc7c4382c82e01b0947506ad517277e18b557462a953660f9a52ab2d72f6aa3d1fb3b79fb4f9ce09e1c3e1bd605ed2b2c1e37a0f18e14365698ee6d30ede6ee82b20d74cdf38beb650d8863a4672bcf9dcaed46e54e55ca27e2f07f69f4102623b0dd6410b2a0c2e75bf8d74dd0daf401a12d9585acff8b76dd5bbeaf152ff578c894b9a677f6ed048c01464d47231b3566f26707403a84bca3da8ff146b6da720b40e074cd97ce61d146a1e316a306f6b9011c8d1019a284540f8e302debb91e13d784f1c0b6ebe6d8fa3e14433c98d66798443363dfd2f3f86e243f71cf5e5126736bb4c86375568e484436749c05accdfb6043ac4a58fde441ec603e5619eee66c5c8584cc0196b5543097c727926cb5211c75582e0ce4c0cdf1d6dd0038bfbb82cde5b73ccf2afe7beb687720e322d624804d1adf458c5bb8c3a5e703230b5430bcd73ae8af646d235ae03720a9c59cce30d786ed653b4f77f3329fcca550aa5c9393ebfdf95bb36a22b3a7237e55cfc069279b03efe9de578bb8f9151c9e2968d16047f4947121b6cb1622892edf28942ab95bce4e2d590be6c2c6e600b22e1a34b30fb21b1e39c3375f6724f0f30e72318b39726cfcd255ecb9ab80d5706bf85a79887321b28e03699bb473e687b86228453c7d1a29147704a5ba28014e8644bac7678e82dc33bf4ec1872569cf51ccc8774e6d1e8ea68d7de1110bee178169fb1544b4c2ba2095e9fc5300eac372da6d849d26cdc433931ebc48885ca8103bd9727534455f574c4e910723feac0ab402301a5625aed6848bc538be6ce5f75f964a50dbe1c9c2fa355ab20c7e2760643a9ac62085aa7f5870f16ae7d63cd2d9b5cf28e69c30ee68c678672ded1ee6855d7c9ffc8ed805560b6a2a8a60c6d75178cfc680b94bbc54a36a772c6c519facf1d0404f9e30772f3292b73e96d653a1b8bbeaf9caa201527cec9725391f0b2e88d4dd9bd9c1bb33cd80b07823b216a3f493ddec1e2b4b6cb1fd872d152e39fa583a6ebd6d729ed42d198c1c588876ba3efe7c04dc98810950d9a726bb871619477164b11a44b17effce0293fe1d4c4f7df1f563fc4b8654897f072349fed7210cad147d8e85a1dbf28b643f4cd6bcc3959e536378eadb7a8b98b725b634259bce7f513e4cfb031a5cbf3b7d6079302fe64a5792f935f75f7af9d61bbe3a88bbee877b85cef541177d422f787d97afba07a083800bc5c5fe4675219b5111e37dc470f45e5b4b07e7ad4ff8bdf4fc90233a2314d717dfb26f2407f7285ed5f3d731aef63ee617ee2248f6660f9107e5d4c51ef374efb3c8c4d8c0f72c3f5915228ef2d4ab10c94eb320c0d83ccc12f8f8f4b7c1a71be97cc2e609a72c1a8f685c8cddafd625ed4a0ca5b096a4689a838ba89677bf817b9e66d2bba724813354217e9fa53225cf878727d89b5b043d65124d740fc89303bb47ef49e7213dfc59f0f16ac3630f39d000e7164c351f5fbfe758ec527ff4e0344aa6cd2013d2f5b83ce615bf92b9042f9aefb663e3c24b641900a16aa74a85f855afe8b72e235aecc067d160ee2badd1c8967e1e8d5d76edeb22dcebf9556c951e513a572116a39d86ca7d3d7b25b01a657c753e80db36e1d541c74b1cf7ba52abf9d4a72219822d580fb76aa0be64c74b789a42dd9719d28f3450e4f43386e11c44a582f07f9679e425269cc1eab25bb909c0edad2b1f0919dd32f1999022ceadcba1124007c855965b80967ccabec8c4f4db84fc3beb6ece05ff756203918081b57ea729ed756c39c293a8149a56631e3f49e19abee4ae6f349149909ceeaae8da961588eb1405580fe7080d53d729a00420ffe999f53cddc73733741f7098d61d32a186b149554c55da024148be3d56699652bdf505bc3a0163f18f35734cc27252a2ef9b925564eec620e1e48868c31adfa07a2132a92873b29bb11e4b3eca4547972b267d8d1a94ff3f0e8d38d4e3715171982f8ab017c8693a3f795b52158086772f6191c6417caa5463ae9188e68e638bb1ab751adf8567bf5128f8051a62f0b62ad410e8a2b4951f02e956e0310db6546f78a86dd739b8d1d95e9fd3ffdab53726ac9efcd24a362bcc47546463898950199021912a85b0727c3a2bc395877707272ab47369359dc617abd2a2bcbe4c6d4b0f3d51cc687ffba1c7fe50d602ddb0180dce033b929cb2f6a32d70dc5b504c68293d3bf0d06fffd01598d95fdc91672abe40976035eb267708e8c643d36d0ea5e9d0ad9e363566b879e828aa289d5720fbb825e41e4dd1a7c58e94f2f7036a497c2bb6cf6629c887c644d4f125eec223b40a3b1f2f6fe220fc92c8376a6f70f89025088a65e37c3863ee7fc9e6839727e9b91c156daee24aaff1a1e67e219148033d518c14daa3a54aad26b6f88605560172b0a8935c987616a97ffc3028f3f124d1482a6f5b05343e5d4efbdc3cf72c6366279eff9c7391748f7b00ee5a1253482482eb3f8cfcf504aa7202f1fc06849e8b0a423a8d8fe9ff4734b5fd06af0f771087e914379491bf725e39d5735308a673acda0e8cc2d56d4d102183ca93f30eb9cdbb2a12914cd41ec4a54085172ff697868458c297bf5c79f46d421c0248cec25140d67b2925dd3a053534bac4310b1ccd86a602a19f1054f254e291f616e2df46516edb30370a1a1407f0655720691f874831ed184c04a58a13b7e4864d795605c9dec07406d72b595b0a33f72a216a5687e1eebe793ec477e02bf64b62bf53477949082ceea92cfa4d98ed16dda27c6996818024bcd88f3b5b55116fb43d7b861c0487ffe652bd72dfc3ba37228ed053e561199b0a512815ac7de36c2a2adc1fd39a8486400046f18d9f87072766e7c1ffc62669e9b87a6c1a4d494313379c0fe93a8f764b98658021ddd8c721d282242a3296daaf7e85df263e2879f39f7fcf67d7bcc509326806acc2bed724c3bbd5b57ef0ddc1e6edca57294321c3f65989a21a110242f084d9936f73d2afb388412d86ae2f5512848701d3ee27327f2a1873153402583625334ea1b1d72b15d0c3fa0fa00ec29ef321fdd46f913b4b4f343938a25fb51c890e01eda2872e225bb42c0bb3d72601d850fe6446183db91812e55478820c1349f0f3c492f6b1c08d55fe725a12fc3619d146af7f4ca9427c7ee682db6199076f86bb4bff272959b89fe0709b946d50c85feb543d87c602c9df1c7db57f70c10957470b36166c5e9188855250ab57827ece34cf8ff0aeeb2d691a11f59fce0b23376d2d6b872e5bbacefdfa4e2da89b737cc40f454e9f5c0d1bec4ebb3b0251e9a2b95840118273b970028f0d1f1fb864a8773ca844478016803d56aea3963fe63e4b5b3733be629a3d99ac7689b48fbc77c95dcb930974260043443644aa2c4532a2a933c72e77abb3f260cf1aae8827edd2a709ba6eb39e5ecc31143ff0beb2ccc896473229479a07c2b39ef7a9c0464d5273d444b9e4f7ac919974f47f472881c6fcb4b31a87d294e4ce1bbc7ba749392e5c5dbd3532b8b0f492a20f3a1129a1dfec3b07239eaab3e721c4d2823c240209419adc7ff12ed5fb17c3aea995c92f039e92602cc1e86b8b9f4fcef56f4df1a3ac89768164594281110ce4975eef3a1883190725038a973dc3813ecbaf63323fcd7278cb2f0e5378c27ffe80b131e2559ed9872bf1d1d5a5b12e43d68e86b9c1b812a855be4784c48f6fed106bd45cc0387737249b2a3955ccfca6fd245a81312c6e816e57ec81921a67dad2ce41cd1232d9c72ed2743e35e67c1d4b650aa953ba0249dc9e05cfe129f5f29061b86ef52919b72ab47cb73177fb95816f1cce30865437a7f28eec78e530a2a56082c59c486240bed99c6f2c221fc998d5dd69123c6c313ac70d0389604dce191838490d74f42725ed7568418dc6b64febef81ccdec95fadacbe35f9facf54b710e619948695a5c162b092c6bb9e3b3068a96e7612c4e702f01985570eabd706ff4d0d24987bf1c20ddbfa2f57e73c8c7c7334540aa4c6acc64331388cd039611d2c8b5a7243f1ab1e2850e840021c377e4e6d8539f1d9955146c4708967c4d9e2dd11870308b4db700843c9e4c2785099e0f4365e38a0e7f1e52a257108194be3774e8acf5c172b8629198dd57e36d751021abf1a406f592116fc227410749f92762037d935572d9592367879107a5fc3bb719db965b5d42aea11c2d60bff00e46a72785064c728f0c1d66eed0a97672bc02c839338ac2a16feba5ddac92d0a90387e916e50972a4159adf6cf13caf8a0865075600873283d84cf9186f556fb9c391d31c3427720392a5b984bcecd88e6d70ea9c22ea925962d8dc7cb73e822d61f8ee87e47d3c5d6070b9d00694cd16a37444d3413f5589fae059ffdecf2bcde3a781a3f69153b03fbc979c3662338edb67f2e52a0f1ec31c2849c31df484a979610f85bb4b5d8036ae4b6f4d2b47e900cfd3774ae02b2e4595e5ff07d23080728d7d6e6605280bdc7d9724e016f97d5af95d944d32d2d6042b2a32bb5ca6254ff60efd2837546f965590db70d18c69630b2a9af5d8d42a515fe2b8df885cdfd626ee24ad3934eb495240cfb19645e5cedb6bb05dd33c97f2e5f47345a4c1bce1ea35faf36872252c48445437403c78f00e4ff5bb1d89ca1a87883c3182584f034e8a75db0749bb2837abd131225312ad1171328a26a8714b42a53e355a52339922795f83680d83bc81cdb77d94c931412728b7cb7c51d1df0dc4ff152407a4b8f0ea51f7a20dafa0f7b3835f3af3392c849b01bab66b7f18beefb351376fec65bf4e34f02448ff17c56bd80ab27aab85da2318728c2b6f293c2930215a9742ba6ea828a2017207ec7bde2f54bb494bee51790f4478d744095be5a3f73fb6aab257c3e6c2207249f5c5dfcb71e824269457d47d4d6ffbd1caf74874d20a01bbc631d046542c7298ab24d5498136ad841b2cf4123a2063e1bfa29a3bac436deb6b6d36131acf720a7045b64dec3a132418d5201832c940297a172ced730512fbb58d46b31611726118377c319937336256d858526f456827c35cbcaaaacf258285621747ee7c72e0285f515e88289e3aba8979f414b73612e6f8e30421fe3c89585f89f0e58d727c7d29897011998190410719fa8d1ac7058b5e0a09b050fd53532b77c91723502de58006721a05f80bd11f3baca1c636c13f065a2de5ac9c3088821ff0181372d981b5e22b1af8e5dd79ca9d075acea139aa43996fbcebba992475f16b517f2f299dbab1ed9d7e53f7cb73ce98b7c32e90f81a141a4a5f265c248e40defb5b6da8a1163577f4b7b2ebeaee8dce3db314bde492517cbbf3be112bb28e876d5927893966034209c116341cf7176b953561aa0fc43ae66d3541b427edca45488a72de1e2954503c8b4cc259d04290149561a199e75cd1c0f99182dac9a9dc98957284d097187c3c97310348ac8571dc9dfe63ffb635b828898480c5ba4fdeca4b72c7d6e106eeb8d57ca9dc8a27e4e7abd9326954b5137f056c9d9cb7d819823c337abe3d4747aee91631f79c090c660ebe6a06a289ae11d7a44f4532e2e0ca92726011d66b290708e743c628f2d8df6146c79ee60d75195c692876801dac208c6c3807e9e4e8efa073babaf18daeed7900ee87f555b165d03fceb9c66a12ff22723efd523f1256de6f693f84d6ee844942554933fb0f0e04b1e036c1d4a670c8722a1107f9b0d6bab7784227ebfd60a70f35ef02f1ee65889a2b678af7ae441e72afb98689b7359402b962307255f480c68a402bc150e9a670691394d0db1f0e72a9bf7229bb52ec7cfb3915c779985115627a98c27675031578861a60c67216726526767f96fccdffcaac2c458dc3100e8c66d44d665b984bf595991107147152fc6e1a7d36c0060dfdeb044341663769a23395fbbc4c318c529412b869923972ff6ba295d5d0098b80eb362c5d8016325d25a6bd1f1a37050a6709671fe081722df53a468c7f57613f4daf52c751c94ddba1b12bcd1b1700673e6e36a000687251595aa16f4e53ed9c23229eae7e68130146d8dfa8951e683414182402b6ed72a7afbfb57e4d4b550642f36aedfaa4a49f08fdd3d73844cb3566d3879f5eea72aabb39019afa298f964d9283f1665ebd36e58fcbd584426cd9c12204a49b4a128e712594b1e9341f0d5c3938bfaf1fbb4d678027767cb809ac8f45fd9857b946438a223a2e79b8df5691f7257996b45a4da3476192ba3ddb7bdf2547f2b94a580f05d4c5551eeceed3937e6ef3315901852c08616f90def1b3f80ee64695616d7f3b172330c783aa5f6d1e0f10ca753ff26fb8ac144908432382654412ee11465422fe4a0df32231c1d40bc7e3ec4ef7773ffa19c640395850e2b849584cf4725dd25c49c0a954ee49062a3734beb1340537756863011993c74a9f91a63cb172bc9a527f98ee949f698decaa8f08bdcb82999352a59bb4b235ab219a2be7a372e8ce2cd5206c20cfdbf2bdc184229df26d6e142db4e4e741fcb2c046a867167289964aecab787ce60f5ca97c361cd9f99e474e9c3b0efa71a5be127e38a224428b75f443570cf207fbead077eca385770aa8484b57600a21ee9037fbd6def572b56bcd904ad0db2d440410db0e475358e735211381809b8d49d9a855a7072472edf72b0f31a82a34139ea38eafa33cc52e6d5a743acabb966b519d402096cf72f8e53ae9c1249214d162c0c7082ba3a1700f1a0583fe1042aa738e714023a23bc35de3be43f32271f5e7ae656f142426198fb234131971b4eec76ff9f46ef572f4c949c4498b0b7039f08cdd027d0d0852b2de343a6b0d5784559d2e3c06a072811687ac84f54170a3896c9db37afb448afe56378b3c051b9ce8362abd9bee363126c30f5ccbb775ab6d9879579d72b3359a1ce6f5bcdd5be0b2c88882b38772533842d05d6ce0ab04b8ee6b632e393ae84d7fd29de577be4c82dd4ef9690a72d37ee44fae3ca74d39843b5e6f9080f0eeae207d428d900691c66bedec744372adb69e44a4d58d598bda38dc7a94f9d837a46e0239fd1b37eceed5efe7fc8f6ceb671cf9f96d42da3d92d470cdba4412228d62505cf76938d472567331f44672859d9cb36f5be4310ef83d37228048a4bc55fe27ef9ebc671a7b48c0e36f796053025228697e7d1657862336f92c62642d4f6c26c2293f84b0a4622980bdcc1d57aae029aa2398814752ed65ae77b7449ab89d22d3cb9e12a3961c9595913a729099a3bd447b9a24b5b3715296705440b9b4e3e063bae40545f7d7790f76977258674675910b851c5a17fa737c065715fbfbad4728c485eafdc6daa63feb81626251ef1efb3c59d14f63f45051aedf7f5d17e96d57e7ef6796b38ca4e93ee2346de61b865904788b337244b3684268f587968c47e0ee2f688717800e7e2cdb7275f494b0820d6541a26e65b20666adaf64a83d37ab4f54f5c2fee2f9319c1e532cbce8f91ce4f640d52ab714cfa46861bb43a783aecd652812ab1c4145f77d283c4aeb79a759a21effeba117dbad37ca00bbdc84599f45039a919c64a7ea95728c89020de46c8ffdb194488024ad4489b165bfcfb929d24a4ec549d60e65f6266998bd3ce591f621c0610d1424a25aa36cced30eee0d89f1188598b8c279a87212f56e2cd9d805d8a2134f89e3c64cd537d8cae37af464601c8f02c3a0e72e72e538db2b307e51a04c4470d24065706481f4fa2bbb7bbae413354c0f9e869c7240cf40d1c28df526824d65ed87c46a5c4eb50e87cc0a5c38fd2f2bc13a34983a6fb27c9c30fc437a318acc829e93830610fd1fcbe46202106014646febf79672d4c350f0fd521c5ad411a6363158fc9168f5aea52d985231cd664fc133fdeb722759c6ef647902835342c990cb88ab63ef92ed1e334b2a3b423a38b4a627327208b768d8334e172f0f9e80001ac1b9fee1bb4c8864b490650209bad55a8b7772bb54011ac8d5bcf54f95bad8926c941bfe0b004f3a00e634b4a837e206782572064a881a38f2cfb710121f377ce62da68ee13ff748c8ccea1d99f88fceb17c723aeb3af538d70ccd1a84a1a4547c8fcd763cc5603e94349f5d368c65a75b3463d341fa7a9d1d4c298922d2682090a6de11819917443202eb61017a420c35f1074efe97d67a13ba4a9a87f8e0985262bbf00ecbfec3739183972e372d9921a872bfa8670429ad3e7ddb17b01c730acf37d4b0bdd7d271c0a370bc27607c636d09b4d4176d29353ef32a693b0a62d8af86fd7c86acf59e3a1b9eeb1e7db4a62472ccc62215b1e47376cdbdf14bbf5ccb2d9ec7450ec6df39d021bd362074ca5272a8fb1cfe9f618324b3f724ab022d3077cd66586b2c0e75e073d888e233fa977210186554243cbaa09883d9e59ed92289670d3fe7445c0c44b48dd6052f215172f12ce054a1fc6c3290f1b6422ffe3d7fa161c129d5c1fca4a17d746a02e27e726f5a85379003adbd665116d48ce39b8c6e3f79dc60bb98f81837d2dd3081017269aa00e937f22ce5266f2074d9c3970758aeda0863cafd1723c6d565a8e3d972564bfa8792ada2a136be569f80df9e817598b8778e6b84a325ea8563b2e1ef72b83e35eed0829bf77a4f5297bcea8118b829a1130b84424756c07a50adf3777211b0cce6a77dac863936642f039121109eedc855aa23a527f0c6f0205bf91305355040089fcf4c260ca59ef41267c694d38c2fdf792322c3c8ac6b01c6e45672939a0081b15749350eb904b2a6c2016881dcad02382f3b18b1ad400821fa9072be74d0462a7955711f74abdac1002b85c706b73b3faf1562d141ae9bd3861b72233bb927815a0ba5083182c066d4dbf50b2b319e2b457db17359ed31dbc3b572d1b7d1696960c61da770c51745d53046734171787ad3a1f7ed480cb63e28c1727da5d88eecee64233e7591337c6550458a64468e576827476483c8b41146136067c3fbdab5d548ba8f248ab3b3d9c9bd191f16de6f263393272b8df565ff1d72343d75b4ade771a6b8429782177d94264c8a5eebac3b15eec658e2cc7f2dca72557b9246e819c7547fce6f188c903aa4abe4c3904ec592de0ad38d49dcd4f1729c8fad61f3a0dee61b838bcd679828873014684f93e0218e93c6508479641a545f8d4d22cd6c48bcdbe712db725fb62e3f5087e65fea7e22d30698100827a87259a9824347180c364f9bd438a3b54a3f61759ce7554aa7b6ff8ec27be0c2920a78456269028af8223ed51ac8b34f9e47b31e62f18b9d897704f75e79ba7a840b829ec7169f8fc869881a6794a2666142405bd5fed56401feb9e3afe5f5bf7c725f9e1c4ddf6f411d8b2caba31eb999894c6b0da6a3ed6d064ebe515d93eff10f784109677e8dd3bd201839ee92d29f31bc99f7997a306c686caa4161a8588328ad16398736598e9febe29586aee0c24e412a7dba6d514c7ae3853ff8b120fa7258d1a83681ca515959d1b6fa66180fc629880a7bc83bac110ad66594c6cdc5723f1bdba4e097565080fcf4b83f8a1ac145dfd9f600425ca255452e8b2d1f27729fe6d1c91db19de03d9809fb6fb0ffac41942a6ac92edecffa7279b82ccd756d636bd6f8379405719eaf2c63fb251bff031f038da6b1fffc3ec487eb6931a67285050a8c32f694f3d3efc5d14e45758aa5818335720e76996ab26ca7b196ac62e31304cca3c3686b7b5446b2a5148f4f12c2d9e7f8613db2b60ce79e6681cf72f8e0e28c5d7eb758ef37ebed9722d725dab533693a25bf7fd5956f1063c4914a291b7a099b34e47af4943c010be2229053e161af9f1c6b402ac9d1c973533172d6b52482811897e5e8e00beee878e1acb70da96b1baa994b87e05fec8eb94644d4f38d297866b5e88a0bf3b07c42f09ab57df6f6d3aaf0a9fd51c28873753e018f69ea541b5e85a21f317f40be10d62266b40bd1ba25d4d28b4c9a7fea1852726a8265d41613c4389ed7a1372b4169758410ba108b331a2d1b2078aa2737d5726322f2d55a9faf77cecbfd732acf78eb7cef3fff21412de0a3428b4d32024a72c96b8b4c06bee0bd1794e370dcf2eb46a984c658cfe7abcd0dd3914ccd6cd35d702bf20e38cd3b3658f7d6d080d8868177da94ea510bf1bac2974376cc44f0579a0295f1272913bbd194fb9f7b37146b2474fa0f9db2f96f827c989cda846146356c9e757d9e7e7d66ae5851e722f818f37f3a75f738a06c92e46af9853b010a220f1a5cb081b18ff14ec133295e64e25065c6f98422fe44198add10c3160072e4d357c31df0d77864c83120b6117dbe089d6a4843d2b52de4dd228e8d7b317258dba598aa18db95cb5de58d90cb412fa5e6c453814d1d6d60ccb65f33e1ba72a15e970c1fb4749ae6637f19471f9e535f5bdc956a4e58f59e7211c282727f72f694a3485b90253bbbdf3220c8b85a647671a07f73b64dae7236a9efbb5c3e5cf5129a6cf67d42580fcc366df542c35663a4638cf7e1d4ce6b5b8756eedbc472143b8e754a5faf313246aa135faa0fb7dba54851a84f62653aa7a1ef947b5230d68ea0da2816f6065bdf2b3b0b713321ba67f7b2583932a7b1dd93f441976b3c715f55b1535925547f4af110e736a23e6bea19df9aca44ccf161c24f240097729994bae5f37c57e88b4b4038270ac88e1c7113a33f5675628f0be60eea57974647b82dcea2b134aa6d8c865084c15b39c6c501ebed3f80feec1a74fde0846a726df13ec832d9979b06b5ecdb44f8ee0c2b58730e8c99a6fe065b3c9b1275d558dea93e5c9645ab6a731a92b21dc13704ffc89e6bebaec180389f8cab9bd7e63ff3f460ae63342f3a84dc00094c389b39da5a18c7e5fbc27c735c6b16ee2b8f0f9a198a39206a3fa3ecdcfcb222dd9cce5a691ad29f005d43c3231070a8dca22c65f7d44249ee4e40c4e9b5a4234ddcf8ec65b1d3a8ebbad6f8894509988256725f3734875099137918c62a8fef5c57d8cf1bfef64716c818be78db70ac8dad333c666f850345aac16bed895b5489b45d02eea053cf039b21d7ff7a9198224472d88b427ab2165847d2cd4df740c75f894fb61f40db2a7592121d66512e6218133401b274f35cef63321b0140a7cc974e282743bfeb789714aa0c68c772a41372f637cb5c1ef07d504dd7f8ffbfedef714b9ea62324c5115ea16961ed13beb55bd6ef5329b9334304ff6c73d3e1fdf9147117a44e86d2c78aa8b84ce74fbdfc72471596c722eb309fe99e7eda5aa7e14a65683c60017fbdfc316d9ce2cae1a226279c291e4178c331ed1c30c2daa1b6136cd20b46514e6c504b5fb1dc84f2fe6baa9a77ac38e63f4277c69d2bc42d8dd2ebb204f8a44add89fa0cdd8b2a243e3506bd6044a059a4dc5098da98514393e55c2a9021393be66114c1a5ea335c423501cb9c16619a99654ccc92c29e9b1d8d4414eb1d214ed41208775d2fc4ad517271a7fbd0a09c0cbcbcc3fb2f1e7a953e3c432ec314e051934143af633ed5117076b6ceaba2107373b09558bbe68b0e2ab5395407116942075d15999fb127fe2ee9d3c23590d7c08a7fee013e7168a0e00ebcb530b10e30328e06fee9781ca207f6f688ead121498159031cb8ac61f0fd4ca365796e8a56f291a85e6abeb86d722d6a76ef56fa0a6b6b4eb6bc7a7147def39f6f07f9f7136164465a5c86cbd17225de0098a50c95092f10e11b128da5eb09f29f2435eaa43e31724b53aa57f60a87ed300b5186ce4c1c580704d5dec4384c2217f8a43b13cec6ce18e4836c5072a7c9de0ca43265998edff4de33ea1c3b316033143785c4756272fb44632605377a12973e22589137d077ecde718ad41878b58ed3b07420bfb6bcc2d3c8b44b72e8d5a042d36f8a5ca90ecf986e4424b5705c98ceee60b6daef0fe7ae3b8fab3c731fe9b311acf6d0661922b5fb2da3624dd0f2cb1da0c2b0e014803d9543ff72ab39a1c55d8232f1a2988ac16525d633e966eba63520f3fb197ccd4589c396729fd7ae2cb356c0dd4da30a53f820eb774e9da0be378fdff6c503b05bdc02613c8f90806e62f23636cb68bceb916c5632552fb07539736c397f8386dbb77d7372d16c85c1c2399ad21da25ed6053792453c6c4ace28157323e052b7ae0b70446e06781179e2c5786fc9c2c22e19669d09a81c8e39480e844de646b46b138a9521a30cd8feddd6a38b1c0ba40ac1ea79073b951d9ac57439d1d2303dc34c7a7c45f567b68c439a02f0a1da23e31c72cb03f9f18aedaf987a2d59daceca0a674a725d0e1d06ccd9194e28b832e770f9f18a41be37d7801d82e0bf82bc8954f127728af72fe563e1757aae27d837a6dad93c8962d04ba407aca483b3d9562bc200724959ccce5f13d3df7d8b234bf7296e92cfa77888c71ef1da09056a644f947f72e8d69cca6b362f847c7e3c4a6a4eddd7e648ecb9565201155a8c6bb4316bf072b1a164c824f8a579ec720c8251e444acf851f8a5c461493a11b8a008a77a6b6726832cd36f6529eb7282b2a7700d5a5286a5ae7607c9e0b7ec25b4ee5cfd6723d504064c907d2911e333d923d36f067b084919d7c2cb3f3adf358c63932b6a52fe48af9f8ac9ac73d7c9176ff20cca9fdf42d34108915808e5493a3a1b478b72d01f9ab6e7dc88315fe35574d43ad5db8156d4bbba9cbb508b8aafd4f1e504723fd9e6eb11ff4a89642af141a6fb4c4710cf1e00d0dae6d83737cc5afc1b1172951958c6b45220b43756c79390f89a8ce17e53a7aac16588238a6e44ff72765940808951b4500d4b34ef4631923630b5768a1edc19adbd831a89c9cb89b423727a50827702a51b7ded6a92582937c78579e6caadf08fca1250357fc4a1c08f1ce934beb2f2c8310433d6034f277a1af966072a314e91b6d058eded76c8edaa729c0746b9e155320d2df26f047edd2208f9fbc5e3a03c1cebf983b903ba9d3772bba1d8890083e7481e8d4d331e3ea2d80650d72f15b5ac4d68c7e3e46092205ad3fbe3e7ed0fe93f0d0b5166af33de394a67f951704435e8b77338cd89270d721ba780743099a755e5dd0ad348d5451de4f1a12944246883bf419d58a236bc728ebec55957ea711b6d5857f09fad5b34ad4fe28c1f619abd6168d77c4f3d7f699e89650e53acf432f12638bdb0b7fea862050121f8c8ef121efb939049e74b477fae1f3018a713914da07cbcf411d8d10e623afbf393420a94ffbe01f3586b38de29c47cc90639feaba9efaf4ae76a7185cac0c3e9f930ba8e0695c336501672e75d8b616b4e94db538d1f76609e84d5e67b98f7c1b4492dded0c969169fce21093177c286efeba254bc79cc13bc074d129158827dc27725ee6da677d73da912415ec8473959260d9ae372071cf2cdeaac321b211cc84a3b6280d615ac54c137d069b9827deb9235de0473c0d52ce40bf54565224162179d0fb6723e9c7f101f503ba7ceca247497ac4fb5fd5eb72447609cb8dfaf649f22fbe29ded64ca99729c8d2d202dc11b6b563a493239ceafbb767abd22b04db685b62a172df6acac72078ce7e7ed25d1b283f26afc9841fb42ca2f147c00555965f71fb68472fb472778e2956aa2024ba1415610cd5b6ed46723d7dcf3c632189cefe38abad0754572730621821931f5d9fdcbfbd421ea4ba0d3053ccf707311bacb1f15531fa48f715c78f883004ebeae5062fed48ecbf7b670df6d8a6ced095ef3d5d7ef90f54c72e7ad1fba2dd3d68be86723491aa55f39afd53e7518f1dcc4d3114387516d2109ac5c0141456377e70abbbbe214c89369f394135d693656d7ac49a9c170af017208664abd3c24bb5d3f376243ab3ee1ce7b63b833d52434b2ab6810fa71f65e67b6cd4665cc399fa5f59caa0eec11639c3be8aaf7191f7d4465f8f2346b2a6372dfe1e8b598eab647a8ec6e4b7004e54bc5c77a5409c8a15e3e2b255fb79aa439a7e7387af0428c97f153e8004e0452c0b4eeda8b3aeecf0410bd2b78b5ae1517409a97562aa358321b0bff046d036dd2272e3321845eefa9946b382ec78f9e359bd58bd3b4f9ca300dcc3a6e683cf5b7addac4662af35ea02c9746cc60dfe660ce4754e714ae89068de25f78b86b759571429c15721665780807cfde109637165a58d968bf32fbdb0f8897b330e706cb6094c6ac34e0dd780193dc6812dbc372722da084993e5120459d63cb1737bc2a52c6279063ebcc1f22f2fa73f78f5472b19e5939c26d657c96c8310a04f4f3c5105e7a72b5e9b4fa8444d7771f0252168866568cb3775858a16ef60eae8641839024d89347c61904ef2bfe882c742d7271da6c718556b42e17ea226827655b637c24155390c72f05a455da8bbd6921721da4ec1d944c031b8a689e139324f0bbc92c18eddd746c36500b72398b7ff1720d0634028399590fcb0520f8f82f3f288de6f0b039efd25be441db17a0f55f2b51ed4cd11cc9a462a7526b8e28fd987646553fd9c7ae66ec7f8e7e5d80eb5472de022d095b5f7fd06d931328ce26be9a50bf6d2073d6ebbf6882325d3c3c4772d1e02397b03fa710f9683eaceeb4f0c2d9a020e5cae01c21a68466aecb8b1e72c59e2af09a42ddd31e5de1f5d2d02e3b2801eb9aeb6e5d91ebb5303b81baf7721d344218993371adb8497c84c2405fd48dcec00d801d7dc5cc01e61ff7c583721dd6185d4a48c73513579abfb8c8972cf4fa346beb3c5a20810fdaea1658b52b40aeb326421202206c9af631a1ce163c92654880c4692ff3c7c4a7178d36e0499d8040c449f089305333b176699cb91d1a220c7ca43f9189bdaf50e59b3b97728fab8d7e38315480de3d309ad05c974e6e90eb20f5228f8ff75e630b40b69472369f4952fbc97d7df4d37dd9da3c4a05c6de1822ab81f7c1efefa6fdcaec2d7215c8049e3a6f3dd26627717a07d55cbca25ec215d594fcd45a6665c5e1bdb73ce9f7db490d8eb27494c456839a7b82955f314c22477ab07d40aab5119acbec49c0c3b7fb2b1032f6abdc565e54bdd9b6d36a85cc08976560b7fac1fba628fb4585b330ac84c58d446c540ea7f6a09efc62d03111db25bff8b43575c71e3eeb7293e6c6f1317aaa69d70c6682ae947f72a649b9d04e8b3a24ac2bf4be8f7cfe72620716be2116a7caa807c165703152e33cbd0c8dba7aa691440237b586f4a410b6d8525ccae724eb4988390f65f7ad3a95285e9577b5504a8c446debf79a6b727f2e2598da3725e8a19c4e5aab7b3d3a2a802d317462c30f934e895cb1e71e723e801f2b299afbd07b14cda328e187182aa8c440dc2dbf93d27d1208107e761eda4073d90362fd32c98224935a61f58c1a141e58e13007e783c1baaa87d10508d63e23183a449b10ed48d9914b78101f4d628d7f00b1b13a0fef2986e40960048b658e0f6e2a5501a050de531eb4bd6991a697b87f0b93ba2ffebd826dd27e60442836f9aefd3f822e3ae175e2bcfd681476d5bf028d8c69498acd67c0d10632842e8e56b443d5bdae2e4b883245932ae2e83534f45d6bf473f4f01250eb98725b4662fb45f6aa121b13517edff04dced95ffbc44bc3ed86c812692dc91d923b7830e8fa0af86ced2bb90b9802b8348ef09bb26b06e28bfe680597701c26bf72c68f874f1cd677f6c556e2904ffec145f8d193ceb41ee371c0a40461a8d0aa5b4f25b541a734debcf485b293989a12779f8abace67f7d08a5c7a83713c30ec723460180b49915525694490dc4ae2e5d34177336d0bef2e7d768e73e38855e0472c49bc070531b7312c769f1e360e3c2ccbfedf97e896af72571842fffa5b024d499450676699fbbbfdadd955a731022026e59f9287497697c2345c07947f0772366bdbc30026e9bc8909eeb0b26239ca215898c4573ebb89fffc0e1676b26e4236b37cc69cb6199ef37de98596f30421dae4514c2b7506cf1cf18e798726bf33d3e874925c3734ba563668124b646d34aa51edcd8946795f97124da7489adf72ae4d17af14a8cc38d958fe10ad751f2034cdee2bdfe063d42b681fb9d745cb724b200f769d55f073d71544751bf4a3085e2b765850f21041486044fb1b0d9724e937352eef4af8e1fb391781cb6a5b61f99c9e93649508801132f4ab67773a25610ec90666f1073d5c52ceec0326838bb0e000e6403855bdcd3d419dd7edff4a1ba7f68ed7ae40081644529d74d70a41fba5f670d31265695bd0e18590c23e3300a777565dd336f7e423c38e8793e90072629e5bdd3eaa3b633236f8b485a838bef50afeaa270523180a2bf195d86fe066eeedac313eb30ca3b263c41cdc656ea0956a3e13f2ea80486a2a2830e432fc05bff34ace689b05afc762b281d876724257ac0743e5d6cb41ab625e444cc3c1de3d6586c178934a456f29c849035572f53ad3a5046cbb8795c1bbc39fd758f9054c602fdd96f94943beae63bef46051d1c36b19d36f9082592720c1a66a160a4071931ca999d8d266fd0e223f806872be90ab416167a3735389795ea6d811e2a832b7f863ebc10b4f0deb75eed2657284517535455c8b5befdb4a90d5ede7011e19ef3e0200294f8d5345acf4734f3e891b1b86231b12b6fce9d693437af98902dd6a323af7e657550d3e3e11fd39727fcca042dd75795b85b70dd2610c41e3df36e304e9798b4661ae75be5b5c3a729116b1c15a604d8c73adae46845b814540c2944900adad37e0d5ca32816e1e72804d13b625cfe84fe6984dea2f159cd31e72e7fd5d567b69499d9649cae74572ba5925ad5a5475a2f1f3796525adc25e55a2165c64ffd03ec4f45474995338723ea46b0756b98812cf03694f89587fb2a79e617561a41b2aa89adf78704e1072f37256e4d74c138aae021d0aa5b237968c7d04e720b5ff615c5e9f3d086a2250a0823fd4468cf68cf957c56b1792582d81932ba50901dd40e909592e570000726300501d383839fab8556f207bd95550f486f8a2980db3f470964541973fc86dd261cdcb95b3f751efe266561aff2dfd3b3b85db450e58bd25f0fb172d0aa21f12591fe5c90596031ea82407787d83b11fa458d3280a006ebabe554a8b5fa672fb964a1033a0f7bc1e0cfea56af8ae541b764bbe89c2213fd639c9462853cd72a05b780cf3b1022a982588d5cddaac44d20a1e17ea67c67de9e00eb0e70a9110effae340067965779fb4f607297203c59aa41e8b633967ed59c24cf7c2954f72610abe1ab9b46213fde018ba2cda0eceb6aa87fb9386df73cd5f9c1aa5cac266d0632ea0ba4e9d83694bbd8e99b19bc117294565efa684d367f59a6b35ed8b724544f97aad050f14ab471282be994bf22f900084b607466be9e4e6cbcdce0472a5436cf030175634ad21180aedf859a75eb748ffe15e0750f9290eefb3b67f43f163c289c8bfb5eab518d8d5fc22b271525022dd8a037f73459d5ddc6d6eb200d65dfe6ca491cedb835d07930849be27850e18049df9bfd596f01977eaadf77284fdaef7d2e43fef31d9f1098f43323b03669eea41d423a162a6d48ca1754b72ee6ca55fe996d0d553192068e98e496f4e9221e0ec04a4dd31ab4a8836925a72c887edef2df44870523c99468121b773f3c6b6bb8511b3e14abea1c05f372472f31aefda066daeb4fb329fea10ff7bc6cbae30847eac1e3a3bf68882211f327215b22328906840750ec02f9eb540043fd1c18024f21917f30ed9862f15ee497288ad8e656f7cdac15eea8d7c108732251f5820e0c361d0cc2c9f5b72c9dcdc15dbfc7872de2083a5d770761c2fd8d701c28d9e1261f3a57676c0fd0c82d10572af07e548d4b0367f8b86734be8bfd103e378cfc4f7ee68ef6f7b78768c12ee104106b291a6f30a2270b4fdcbd1abdea04ed744d5fd30c027932cdf926d702d720dde6d31ee7dab72db39144c0447bcceb67c52c2d637ff66e50c8787a0f7b95db643e221ae6c641714b2148f2f1d41c95b0fb95929aad67b3849be52960b7a2040879bc2c9e53736144ede7cf41333edd08c1bb2cf9d6c2c6031c1e9bc76ab72ef4c93c3655ecd44d5dafad04327080b5a39dcd154efe0e1685bd2472bfcc02cadecd70960c3f84d1798bd9bdb82ee0fa9bc7ad825954f675f72c5b77b511e728fa410866b18190976ca366a84d6070d1c7df8997430e88c0a701029e340c95e21fc65164740283168c86b0df0e3e2886053a0da1df00fa669c374f0c30ac35c26bc34054a6ca3d495c6d615f4e8e9342c749fde999a911bb30270d6b2a77e72055a2490f90beaf2f3d95f3c0ea653c42da1d13b6d14d3281df03156ea9cb71297c586479d35b333e4cc56dbed7c11043c6638c67ab6008184f19048c9d77a07c2d2fb242db1afd1defc68057feeb9f27e98d4883edbfcbd194d625f3f7d7f4df215b4a9a1b4a325563923a7cce1f8e519c94a94bb7f17fa9b79280688ebaa011dafd37a8a45342c7a20109a91ba35b2f7319d71464b526eb6ae202eb7b12130e359748613ceac8cb9acc0c6d881d541a90716569684a1d7ca717dad5522da7214a7bffa0e14305e39c0f59c9f2ccca81d0bd245950de876d9acff5228e5526dc2f683b838f0ebc967202c2d734b5fe2ff930f5c7d0a76cd4762c168f430b372361071915f17357ba28385e48c391f16e9552b3df8a49937ebd9f06c2126240adb972476f12eaf9bc678bb2ab098f16410e34fe214aa2ee0729a563235a8fb72881588fb66700716cf0c0abc7f317cd54f93c12eadf24aea81b68e47887b5672c9132cc1008bbbe103b49f20ea7f51dea52a66abe0a0371318585f5f3a9d6736644cd78b6725cc050e4f22ca16f94296ee9d1c0756d56668310cee27604f1e032e702d808ee09e1fdfe81990f26723a86bf8fac96b84fc29468ed6ec7a3e59344a12d2f1c4d4dd93a68d1c42bcda0b2bd96cec91f9063c123d0d08cac507771916fccff3e48766000aa925e7870ffc07f7c4f9fca93e6c2363012941ed4d862e052c67cf57043d0d539b357712eef09fba299d9c7ca9be9bb6961403c985531ee395284f67d83e63dc7dd646f61e5f4a310bf642cb02c4c9d132951a1e039572eece3af5cc3ec43b33f418c46865f593b4a712ee63e609617ce4152b10ec06726ccca81510c4b2bc4142556a69366d04f794f968d2c737707ec306aeb593e60f8665d28d2ba189c2b3458076da1b93e793ffc66d2e32b2ea1e6acef9e0c17672ecec9d34c2506165b17b2ff6decff8d13d18984e22bf40a20d330f6c34a6390b1ea79d0e41d46d1c67988b8f16dea7dc2da073d91007ab6b93c017c276ff13160c5e4cf8d00e84e2d4ce839533a21bcf98321e321ff60800b4a791ae38264d72d98546086b42b23b9e2581630f379da287717854cb02e437f1a3adaeeb9ded5ab1759667f3cc775237566b3a462427a2a486b4e2b969dafe6850d7dc5c3b6d4055d15051eee7f13be556d8e247fa196f650b0e2026acb15b9680293d0d0f815ed32cd72c1abbb2e9e29bb647ea80a718025f673351495c4d068c67f841f4dc5cf6ae9f0582a8615019d681462bab17305179fc5345e7b84e1515966985a9ec5660f868075b131894bc4f81c8330ae137c0691e5d4255d881352fcde995c2f52c8afb0ca028f73831da42ed0a67a4906d4075d0e95d49213e49a1840130d2b90cdd8efd9f5b5b0c0f687966922d44d223b11053830c165d9ba36159722434af72c1f7ec532871d8110b58038ed84dc44a3a6d86c53ec71fd6b58a99cd275714074667ddfb55eeb91e2ebd1e41cd06a03e4a41dc5b8256405b8ab807c4e98b452afdf65d8bd3036ca7e8f0ee18895fd46175af773dbdfa7c25b7fe18f80f778c729e139cdfaf723b185157b63cbcdc453d8551f590f98387625bca52978a339572dbed52150fcfc8085bb588d078e3fd3744272d6b44ab73e23a8f5977a375ad15cd6ee3d5dce144b6f9217443720a7bc3bc006103c21d9c62e5f9e4ace18def7213d483ffbe2c4dde6c5f39e6395cd537c0b62c7126462d4f74f50f7d8c49171d2c4bc8f2e01c9154b904f3e6c045f9d4faa30158bfedb11b7b65bf07c5119172d90be20954b109b6bc38850e4f5b7898f3a11f8582357a2984638bd8f4638141913d6c0cc207a067525feec36ff59b50712ad8525673681577bbc5bf76fc00722bf6b51fe9d674e2651b2856cd5939d39a84936351870fb6ac8e276dd6d6927256c20c646c835f96f2e4aee77f452fffbfb9781a9b0601325f3e32afa4d107728687c9f28fd3205f566069d3f9faefc3bc6845b5f96d557ef3e89440fb3afc07f3567f2d4917bb433212188fbb38d7daa1708e58e0702e70f079bd1767abd85928bc6037006025cafef20f076fa7c933953d3cfd9354ced7a39092f48eaa437250569d72cb1cbdcbe2dfa8a7f0cd3e49da3920edde9b581b18945cc806cc6b728f29a67e66abc6d10df565d14a4e70ba7efd2a32a8e0f3a120e4ac39a3cf2872e94a9bb8f0498348cf9d8399e8a23e25d425a2aeca6d0bf1244a5878a82353727f24505ef6507462fa69a7b2b21fb19031f9e80ee25c8dc919069e3e08b8d27233db4345384c995f4b3c6974d5aef861739e33aad684b6cef86196e630848b727f1e59d594ef916c376d9f7186487dd194e304847b67b2a202ddeaf357be65722b486daf739474822a83b68f670f6cb94d19aaed500b435d070bbf1d3a4346728688b4043ef3aee75af9e80e07bcdde2cab9b59dfd8d5dc4e544c11eaf023319017e43677548560ea7338ac1172b0c49d433162879768d9ed40e14040a92a972e3bf868b90c071ae164703eed755fa9ecaf426a0efa5f8a9241329c8c676e872bec03d242255507686c210335ca473d057b9f174435a777d6a34029cee0a9872ce3c7fc0dac03cdeeca08a43924520f2534a6cd914627520236f7eb2ebba8f1bbfe84b9ea01260dfcc01c9f2a47f2199ab066b5cd2ed6b0b0748d4e635a3dd72103134ec30ba85018551b764b907c53f7c1c34161ece73fc30213c8f625a2362e17fc10cab9282788b1f86b4513a0f98bcc09483c37e327068dceaa70a531a72fe8d69b99b8167d5d190298136047983100d876d80923272b97f9a2aa8c67672e345d8b854f0b3ba4a8d5f64bff4bcda477a2810cb6fddd872dd9d5bd53ce6380032b6c803537dc2ee5fe27dc278d545bcf1a79f0bd83cf0334a170b79fa3572e320d5a229ea2937aab56420368a8e92c648acc0cd1ebf69fa655431722c0e72182a42af7583b22d887cac2d744c638e11aa71f06dc7c99b931a5ac83a965e723c42c593e8c66c3d0cbaa12cd40768aa9a674e124bd921ce15d83b8897fa69091a556004a1632c934417070fa1ce87ef79886695587371a00567b6c7a85bc46f6669cb368e38a4765206b3ab4d4acf8b8110c7b9fb88ab621e09426857d53472f85d4b2a6cad6a0f468fc21b791254aff41e0ca421ff415793b03b28e388977264ac295fbc881a1ea8dc24cbde236d703d2970c5a24c6f0f198e9b5657d68f15e4034911dadba47b496901150e30f1bf7c9471062be4478587e681d9b03e4872766be08f85567bd18e83ae0e263fdd5b571364a9fc126d30373febe5f7d874723b391c86d1ea37eaf6beaccac3145d4dc02827855fbe7e6181689889284472141b6c5b03b84d5b986f3e5c2ba4cd460503bf9f78ab9f5dcd82a83ce26149b61fc86e6d8ef894e1f7ef721fa1f34cb033dcc4e9edd3d3d8daf1e9152150bfba7290b0d5ac8a28ab56b7b6b4672dd7985d9884416c4d7a27688207a9c564f898724fd0df360a7a1cb619428045e6475a4aa3ae9ba22f7b212c22febf9e2b8e0772ebacac307d7aece6fb3d2079215cf3df241a0c355cb51018b721a35c19cb452566e50ea37313a099b238a540a9c067421840c707adce4cd66e353a5617deb57249990734a2f8cf38e4c81f6606b6e91691e7577a3374a09ace0769385af2dd6680d86738aeb434ddadd761123cd0785e5af91d694adb253d339f58bf55ff1e72a105ae171688d88e28cb68b7ac81a35dc5f0090324a08ead36766afd47f1b1725d1a0ef6e38f2d43fa5179a0eaef9aa2453f0c5ec3aec5c67a74fdee8083000e48335c4a9a136f45ef81786a3f89548d868190e69c4cde8ff13c5a3a21bf992b9ecd5287f88ab480c2cbe3020c1b17d74408d1b6cde08046550eaf1f2ed6ca72ec9ccbde2ae3046344405b9215e1f2f81a48f0e34a81ecb7c25a53c96641f97207203a2937faa8ac1b88dc7df47b78f19a3a37f2bb1ab3c61d38b4883fe398725459fa6f21361cfa5a8420d481e45c66ea7c255e95dac061d0e90fa74c9de53c38b07fe74445eafb6e1a0abd8360aad4f2a125200b7d45012144d484feba1d4acce89af92f89a7f9bbf71921f0c5bf0b9956dbd439b83a60249b551d782b01725ccd7c5dd1d36887e2402b21d700e2dd349d631dbc0c89beb01abe99e96bc86914eae991abb4195346f900ffeea19e3ef1e482b8181f8b73760fc58c4bc09d486d2ce1f344d9ff9f7925901c1746cf756f42fa0a346279f38fa1dd580b69c172e6a781ffaed4e0656794ee2be108c7e5f9b89c97de2a7b4027746bdbc4f37e38858635e690c77d848e895a7b36959b2606e07505ae16550b5f83b49a04f64e7223c425763f1ec451782ca0b66d3325d1b3de6f01ddea8b8c10717606fde87d7215caa2b366f11cacc9f7d7de50bdaf4b57179acd55f50b4c7cd49f434cf72c1729b21867282e62471948bd2e717cf8c5d630cec639a2c67adc8223ecba3937722b8b86f5ab04de3c79c36d36c3c7f979a90192bd6bc3ea8e4ae40c9de550da72427bd5ef8b6165c796886288ef119364886c965103481ec0c05e986f23969745c4c2791b277eb96491cf84588404e4700cb8e5723a3a9afb731a2772f86cf372d5f49296ecb03531f72a38ef484616853ca441be16c4d866ec15f73ac3e43025174e0f383881ffa90d60c295fd052396432d5a76d0144a7b553e90e3560579729eac902893b609d19ebd1a0a012e860989881e85e44d37a6c4f7add6530fe87293df8ac7ad1848a2fc128555be7395ed025f89acf902a90954a2cbf3e2657e60fa8053835571a0fe2f99ba34efcc91efc2f16014bfb5b9c421f059245ab71972ab96b40975d3db0ee152816fc70cc5903b80cd2ea5a45c4fc5d2fd094ffe4972feed88ca8dbb451a2eeff87b0942e4e95a8344613c5b25fdb4e7af5f3faa4872eef4cf6faa987829766ef3361c111f7b1f36f86f9caee7839c56d8462e4fc8726d9736bf19deb68b53a09ea1dec9c313466ae24cb4391c987b76f8ac4e18b2723682e839ca16bd93992a0ca64137e44363b9d5816bc3fd46403a217ea760a57213611d5a45b545b4a2c02b7cb4ed36d1fd00323df4fff60e24e14deb5e80166dc84c5c14d2900992b91c6e34bfb0799680c8599966ae61dd7d26754db1705a725a666cfd4ff6ea11ae90b6f4fee7764beb5161841d738d20693d51297671f64352c0b1207963d4754efe37d79ab82ef4aa727cf3bba56f6ef11e7646e857145b61de0f67845145dcde4d0f0738430ad0f28006a815c5feff31f675b91b79e072a1f7dc95a55ad3942f3b04bbc9818be8902898a5a7df08cb4050d2007265e1727c5fd2f2330e5babe1de55dc26d6ac3ec1a3bceb8f8e83c7f6fcbb04f1380372d5c5000a7e1052c916de794dbffc9540379495c7b06f2bb0cd08d30bb6e9c372e81b082a4aae27777074389f40665fc670ff64482c4dd6542bb4c2bf91ec404b4583fd3b85c3200180aab7f32c65fc5e076dc96d152bb8f44e6cda5f6deda772765d85fb2999b3c759465c6193cce63bf4ac2c1fe63fc87946a120939fb700450c0fc31741bc0b409b92ed903e6b6e7fc7f89e8a7ba188483323a153822a2b0dafeca27b770a06508bc5c847a14201c643b8ace128fa60803995212e8bdaaf727277dfd044db5e2c8f2c7d9f57709abd478453ff2962465e02710c1563f59961a89af0f36dc6a69ce8b1238b6764a0501cf96d3e3f4b678de9a744ce6a455045d41d5f39cb3aa38c82652203766c5a3f5df5dc168231a8ad6e9d8d3f4878d672e4b1eb089ced1e276e6c2f463d323dfb5742b40fd9714dfb8476bea60486f672dbc67a4c0e02300ad86ad35b1b6a792d3639473aa5f2b5f132cdfacbcbe5303055b42ccad4a5ac52aaf0938517af81a17a5c3815727cbc2d2f3b499cd452ed6a90e8601762e07c22a23e0a07bc6f8dc927356f7e1dc24905d146c380ee46aa6a0c7f8b42c0c664f5236ef8063e7d8fa1df06b3cad4d398404215ca689564db72a820fe406b89c3ecb7e1a6531f8784b4a3680d091e1bafb6e96fc60394c19a72dc8445e2d6fdf9f7bfe1e0932f5c8046cef679634a012cfb2a1962f4e715f772cc2997b06c0190d76c80e555713f598e02b0f40716bcd9aecf1eed3ca09a3372f977b6637a99814b583460d6dd9ed4c69af782b65e1558b4c9a4f9d705e14f32c90455e269f3a3ebb7275ff4b36e8c78328c116c0364f94a5cf1705adddf646d28ac18a18a8b0ba2d9d15e97ca596e0faacdabb1c15ac2de18508e01b72f5410d6d592cae5e990463011eb04f5a3a8c55c00edb14c26efbaa7460c13037714726657c3d57ee3a53a751d292bfced9adb8879d6e56818871ae1a988d1300acf72bc36bf958f9ee9701b14fa72cb300466ce336fa63213f0a8cd151163c7edf072c708cce34718b331a9feb1d661bb85a88f14d9997979c2410c0c83a889067d567c342844f108dc42aac56fb5fd805e7d0b18ce5bbb6ef341d0bf29a91fc0af72e9f60a2aacc83c1f97d7b377e6615237cd0376c64b69afb57675c95bdfa8db6e227317be3433ac1829cb24e53755eac00787c97d1bb29e3a9c98c80593a83f728ec5d790b06be6f925860a5f4b3eed5bbe4455167232f61c747495bfc8e9114f611f430b80760f72a912db44fe624b0bcf5ed254d3ca5bf0d14cc7b1b946c372a82bcb05f3712a1b710e9cff725cc86e8dc9da66e2ca43336cf422963662d51abd5ee5ea317b80c091e85b26472cabe9f6fdfaf892cd71317e3e4c7ba587c57211d1c3bd5f773988d8320c6b51833c54adf4ebce2df973f4a445668550985272708480e14667ab2e8da88748d84257e91a45aeda3e4e158ae9a171b40aeae5297fbb5795caab4192978937b1b0573a61c5c5375b17ada6e9b7ec89ed7a2372728ee1dfb4e4b891214c8d1b4b22ebb9fe35c4e8146581a278104cc4c3f8e99372d9ee827dd4c08c324c9c86c5d174d50dd10e48896a45f3bf1042a49e49d88a724997cd3ef2f8b8e14b7c8364b59c5a1c26cb77a61a2bad902dfe5c98141a8b724e98d7d5810add5cdbf0d762a9eaf204959234e4fb4411eae683c91ecb0715726a925bb34c6dd3d7abbb51dc83e773d4d11678d4af10ce0c00953f818d271172eb1ce283d8ec53fe2ff0daf3f33ead6fe0e6e545b96289d5bc11bfd8eeefdb724703365ea63ac2476b07e1731858bccccd1a83ba7c4f82d2f0d4245832faf572a652ebe620f8f67939bb29dbfcd8bbe1551924257969a91310d619bdbf4381075f092637f15d258a2f53a0e073831b503511b9bf33501319d7716c6205c5db729a19b8603571d57cc49fef869a6f21dcb4169296aa732c23ca18df3638524d72c12d487fe4b36458540b6768163081826a534bbd1c65906ac1b6b3218fe89d728974733366c644450be2b7be8ba54d83ce2179b854a757b55d76e33db7ed163ee7358371b92ec4141683476e9e33949bc6f2ff9db39a9f0f362480d7f5d2b026338e010011fabfbfe5c88527c2fece3f7a9c7f55096b671071d0df0aa77b256b0679d4afd997d7b895a0a9c5c34104a90c315ecda4762f9c0fb1e7d262600872192ca6635dd64b93200808d57bc99d42792826487a3b6fa84d93b22032c09a7231f1780c00a98fa08822168b1ec86530a18e210bfaea732bd293bfcdaf488f7073f17b911bb1ff553c79cb03f2fa8d312cecd18cbeae366e5de02b64d151677226627c2df38cd180acca751e2b10f88c776c286df4fecc4729ec17eff7cfe0230451f040a49ec4944979682f2636a505b2a80ce6fab811f79a69fd8299b06872b3bfa4276f553b63ba40891a12938de4f377131fcae7691527c33505a28dc7471a1d50050b56479dfcb5db575d1bc2370b2d6cd599ebfa61584661a6bdaca347213c41a5467d89c9639b3682dea80b2e7a03e739326aadf1e4d3fc24eaaade1f7f33510f3e75ab3a623f4b7f34c8e3b9926438d16afcf97ba87689ed8974ee182557d3fa47cec601a5c49fae4b3b0463fe76d3f2ce233d636a3e182425cca5721b8f35ee7d397387dc245dd112d7cb1dfd0e72fdd62fab28b4d340f42008c164df40ba08b47f576af6a36591e9c1da369127c5b01d2f399f8522729f260d26729c9a213728c69c8f2aba828bc975266382ab072c3e0dab86927de724c1d2c1722b2ecc8f52f31bf2217bbc79c7486f6f36ec8ffefe1a357fa5eb6dd34ad1a26eeacc53fa18313db146c926ab1627dcfb885e47ca1af72597fc51ecb3eb8306052c3e4564a85c3991cd61c70aca247b110d6e8a5d0f75ebd48b36585ccd279d087e38637352e147e4c5a284deb57d8cd637f7297872c3971520463729104c8772f8d4d64c005cdb62ef9663b336860e01360b2e348bec1ebc3b677e65a57140729a2fa3e35ff962d1ae3e824c0dbb43fac75cfe6dc53c277b0c0e1157ef3e7c7254470083be52d1515866f7d311530bd769331ad3e225cc3b7f7ac326fb2a4672ae44415a6db9851dc626c869a93221ad0c2f9848d4dfae049aa865e9393b874c0dd568fa5c780fbd2e40d8e1009ac775c1c340098c24a2bb87c3b04aa2428a03b8194d99ef53be0125df1d299946d8b79670dbed1f512babb10e3821db041b3aa1ec2d4ddaba5481e6fbd1f5873f8aa835f2077523cb63b4b3e2e027443a0b1d64353167d1aed3278db92173e884bd3b9211e7363248a63fa7e04ae451bf68726dede1a599750da307576a3964edbdb2e8319878279c676e3f31e851f1019372312ed36e8840a28d655e0e2b2dc106e982ba72bb74b4b1f7df5dde8b15578d14543435277519b827f9b4d2035bd785014cbd19f4dde61c91c261fa6c4151086e59b74de2f3b21e82d53e64a1c3e0f2d4c089ecfd2bc6cb66abf1dab8ce30ea7239d5cbac23ab795a3c1fb93a7f4c0d4052667ed67cf43772cac622be91fe1c72fecb3ff49a5dd60590fd2a8b5f94d9f45a94e515225aab8f184164188613365734c3fc080d5e98f6cd75d2c91058edd59a2cd8d724e31e5ecc4f89a48162c572354d5896d3e0f12bb13fbab86dc728190bdaf2c37ccbb2d068390accd6115b727dc6975a444ed626bffbf32bf0df1a313e1473938c25ea17955aeb855a274f490f891ef9fb8f98dc2454ff00ed29ab2c8bb2834554ba0285e9b5ae117b9ef26a0b4f48e9088798995a7fa799943263b9b54210a71ec79957fd6378ef2cb255728c67a370daa0190a5fdbad785d38287c39782143ac15efdfb7a4a7c429b6fd20a1946afb629d17aa1786b9d5623926a47a511bdcae913c46b67daf44b167ed71101e9a5bbc940ccb3fa96edab741b5a6a3e0b6fc4f92ef16d08457ce607d043a5462267c3ad0fb564adb0a72617d430a7e487e7b09a4910ce3b4546e8380d07263ff64c6bc255d056427c004ea0e9076d1712d0d5d720493869942271f2b3623b238b76a29b44d2e74d656aa5105892686cbc39c546d5ab516892e414a311872d05300aab3654df392e8378aa22f780ec6873b15799cddef80e789df9764ea72706f6e3c4056318e1292b2eb9c6c96c104e49d199fdae088dab1acd679265e727ffa82f1c5def00fffb4bc3c7f98bf8b0bc39c2ddb34bd14aab7ebd9215abb46e22c42c3227e127054fa1fc764013c9b199afabb17c984fc5d917cb46c8d207228e1c5c7996dbb4250b31f6a196f67e01626d1d22de2b09ca90f999b862b7319be8a075d3424dc4e9e5369afba1b3378c488b620153670ddf9756658d4f29c34596cc46470e37421198b68ec0ce91dea1373315329d637fd6bcda8aa9ea5e6603020afd785b531a9e3f3ae6cd504be9b146f6b77db455400a2609ed5a9ff51600ca9a5f195015e0d28f89b4549051ce93797f29fbf0381d9e5624b7065e470721cbeddb6bd3d0768d2b697d4ddcb2359cd2184b8975a3f67600a02957b2e3618118aec59488e3716abb04148316e22600879dc6672d69c878dc01912768e524feacc6eb3e11d112cc801683bd589d633a15a5ccc9c01e92e7550020a0d1374584ec4c73cd2a3c852e4b836dec5c81b2164e443377e4d67b217341b18504c9d24469acd3f9f1c0a72f53e41bb4d55609bc70f04657bcc411ebcb6bb9e03e34772c9ae3bbb178554cb7a71f1c7cc0316395c6d574f23cd63129507d26479013772f8650e0c9d8f4b44ec42d8153edf8f01e492a4c482a0fb9afd7fe1658f53c03d62f5944be32f8e663a1d5ccbdb78a439f9421c30c5e1ebf2e583e64bc638a3726d38b2dbe6974a7bb905885a1ee1c20999afbb7446bb18dbc697b3e5af5caa721d97ce1258a6ae41a9c48c6e683d3b33d136d5687dfb2435e6395023e9320d5d71d78577ec74f1866fdba5a4954228c0b2b56c8a3e5ef26f0b1be575cc1a0940a02a4dba3d8fdae5273161bd11e12f061c08979fac8ce163c07c87ebbe469f19288f10f783880fa60f2048555d961df08ec5834b01a0f2583e6cb9378a9f886607d2dc93487c9d9fd23cc4075544097d84e30fa1531ddf3e043e35c5e320cc0287576f7703efeebf2f3cf9a83c73c9bfb3f026b37b153c77ec5d54b72db40f64c404069466c978240d1b6d0cf6866e43f05ddf36e67f6f36e73f454faec2427270ae68f47ce94d4668c3cdf32bb3ce600097f491144bb40c2d004b14ad500f4a9f07a0a242f2ebf9c559c75db26923a86b295569ca7f54c2d1a0a3dcc960b57213e55d714d05006cb38172bf3ebfda6d7f50ff94bb4f668ebc63f1d163c3921a6c56d786bdad994b82b201bff4ec3afb5f6d908815d2ab7b81c3f6d1d88fa45a673f1ec0dc94d19e0f715322454575f31a28ec6a45b9ef5bd586ee078009ad14f36da15fa4779899b3b4f425c1b21f8ffce6692148ab200fd4c102407d2d3a72db5891b222037627e1f870248931d722cc829576fe33a1c04690810e9ed77072d94506b6ed2887b0cac2f747dccaa44f9783b12bc743441664a15ea79c13987200c2e61ba8387df3a6262f52c26141ebf072920a25b5febe9190b972a5f17d2499eec0d36439b1d7da38edb0709ff8d7dc5f486b6ff8fdcb7d3c879141711472ff668bb6480f1f843c90441f537201d318eaf1f8385e4d28ab717a6f8e4ed272ec6e61bb2eff8d69e0db4bb91ec7395b1a87fc4f51d19d56b72c7de795ab73495ed33e967c39a5b668d91998e9eb5ec388cc4e4ddb589551e82f91b0de53c072b551f427eae78f854e7af6a1ab4a0e0b05ea4db6fe0e61fa3bac8f4ae1aa8429a311950cb53a5fb5eccb9fcdadf8183af20dde1beac65335d120a55a1e78d626506648a1945488879e35231354ae16f25662cc904807350f7200bf0eca4cd6720523abed4ea2b2e792585232d68b9c0e9f365d33e5bb337de3f716624a36b80b821af31374d6b7c211883dd3bfa75d376db2e5af4551cec534abda2113a2fd725a4c1df2a6ca7b00017e7c1564c70014c274011a69ab1e98d7675800aee6e119cd98aabc21ecd6b124346df299dd1f0b5bf7fcc00dc996e3d6110e02f4b55e722014bf7186caeb093c367a29406516ff2d0341d5807205691d8748ff6f29c57277bbd1a67a674028b11de08d34eae44b6416251999e5eb4fc2702f04edee1d57eb8a17becc1d88ce543dfee225a596af6398b70ed8853a8123bec25bb3b2503fe431762c2a0e53e46234731fd69169635cf3e77c9bb6fed906655eb095ca4d26373ecc5ca5866b07774b0ad51a7618cd7188daf0392b4776e502197f588fb572af78e75d238efebd00465dd858616a50e15fec9a23ae864aac3359f168a27e72cac0f78abdbbd4ec1cf1d599ce558ba209649d7438a30d2d79f8226f997d89722f118239214107c045ffe23a0a1ccd89698e5b20b3dac3eca2899d9c154b1a057a080d26e6efdc19d9d09e94099345247802cf81482d052419d4748d652c8770365d8c40f984ac2934971e1634fc06b48845c2788db5315bd9bb72f6fd1a1e725fe0aaf5cf24ca7fa0dc4eaafbc83ef50d23d1270f0634e973d5982ee32014721b42d6fa975b0a16dc346879262070576797461c0ada926fd40bce4dc291e272246c6196de4065579445872cff45cc5a522c67727da655bfd2756097bc08a8028c09864a8b16f74a14c9bace83d8418c0c455ef0941d43202285e97dc68740721564e85658d490148c21189bc47030adce5bd697d5539ee3c415aa57796908237736f026b4c3d612e269b2158aa3212ff064aaacf211d2275a9dec68f1f5620c2ec69535c26d02b0c7e80d548dd4f57d66fbb80d6939fff49d089147025d123aee16c795e08eec86c46b7e45be4093b50a1833eb91c164af20b6813d7d005d5329fc5c62c5baceb0561b266e18b95e198409e0360207115974deb12f848a1822b34fe30d0505eacd3b8b99f5c6738005fc3880733af2a989912ae76e12b70a72e88a4cd714fb8c7b162e2057db42dea15312ab194e5cbbb053f1ca66ce55a5721dff182900b079bc585da934c50e7d634481723eb596544394594c7746f79b729b3e388fbb29e9d1c9c2ba98f2e43ca3aaa0a839aca6991c84a838d9dd9ab9726dea23b8dbb05880877daebe16d04bc53c8a9b5c86b7a41602dfcf6b0352bb7294e04c20e0637a3fa07a8c1733c3d8acd7e419dce9f74c5bfef99300e8385072633256a884c0b08394895327de1fff4ef57022ab98d5a97d85cefad86a1ad950814041950758f04363d59fb1a791ca81673909e0bda48fc8ae320e03e5ee3872a4b52bc411c7ce45b3c247f4621cb50e9630ef59967817f149f68d3d99195324a9eb3ae8a44b5fe9916f976303203016cc7452db5f22ce5c95883af955cce73e997a402c81f618bd83be81891a8ce9f8d29a64898c8271429ffe7161d4b41972cc10f84c4d70122cbaaeb32f16c66c855a50b0132819aa1e9cb9336b4e2128167ca73c716650683eee0707b022c3783fcf11e64ed89f7a687a9b869fbdb893654938cbf13f85ae916ee62b419186cc37079f01a7608f2b84fcda7378216deb325a351c9ad6dd58b9c773593577d6660673eeb6450dbe6c83d802158f18f00e724071c77c2bc29777716a4f1ff024937a4c1ddff88e1ebfda52bca7d456e3742636344b73cc36ddd2bfd7affe9a806385810f41854783395430924843732f2d7256aa04b71e449ab33e9aacef4bc60336d8e0069596f177159d76a213ee2a0e0756745ccf028f37fce534f7db2fb35ba41e66e58d3b08232d523ddad88e1fb97222359fef157533069f218b2527980ece0994383af73d18e80c7f39f8b828835c048b76d3623f13cbef01aa5033301a0cfdeae1ddb236329c240860a832266d728cca5068e70efa25f340282ebe0858061adbdce6ae93d9a0dac26cb69d5c177260c55a68e6c1a9349a60b9830c2a373692cf04c2f1b3259dbf5bad752e45de72a176f4f955f7a9750262f8416e398376c936736c019b25d1757c7710a0212c42232a8f5a6fea46fa2a1c5bdc8d4d078e1081fb04e5ad9eb4d015cff75e649572a8500d7d84328be6dd30efe24f518ec5c4a6bcbf67ba7eac15980dfd4c513036a8581287d037b212858c76f5997acfe2d1474aac5912e3f553423397ff59ca1747ac7d0a0c1f12cd689cf9b5ff86b51444dd56b56d7093facb6244e499981b72c83ad0ca07537debaa892091c712f9a277cd1f440d94a81683b9fed688dc5972b0164e0f8ebf0bcef8786cd73c8ff5eb8873ddd3be5b640ebb8b44add4393d6d4083a46a76a84d2678495e219c2f034ec5b2a81b2184823c990efa8b99684072747cff98161e998e5758d0233fd443451ad6ffe5cc289be497d480946ab93372d3d1eb6af557c6342f4942a072810700b463a55519e4db462e48994900b22172a481f0b1725ac76f660b2983a4047f9a7f223b3b3fbc47d34096a06dc69fd472e8fb93a46e93ca06d04d8f5adff3abca0eaa7b93d3b9d58efffb4d10abc4ba72388ef2f54d1eb6e1cbe58af2ed33e42ecc688c1eb2b6f83c5da09ef075b92c5e73e541d767bec981d14fa001485b2b7d6e8dcde41bf8831ae3181e79aa743b72639c35a4743b51042562e7788ab617b16330ab21956817aff863ef2b460661721d8243d571d9d0bf65446d917d11bf08fb3575b2506988f0d068be53e082ec7240f04a2c453194bb4156df4cd6170203bf0337b479348c988a9739b7ae477972bab6a14975970fef7c8ed5266743a843e4292b0765572806f1e9094d5e0082726ee7c95ec70bcfd599af2392ad1f5cf95ca4b8759db2580d58445bdec10d6112a90e1eb24cae8cf687fd895387d48d6689f8a16e2e9cf8b09b12fc86f20cda72b3c6e7d01be00082e2d8de368b79af44148e4b487c18682f3564711f07216b72d5aad826666d6428c3cab07e011ae2c74451a4ab3a99aae42d6f96467e4c2472cb86194adff8d1adf734e5eb79cdb72ede8314f4c5bc6e6479c57a17c41e3c72f031e62d09ed962a562a857980bd0d374142402738d57ff6b0b81cf4a8943272ec76dca48ccc207f577f12ebce12efcc3f8717a73f190bf5c7e315ea27801c720dc71687cd50b8fa09202c9e26928874bd3a3036d4b04cd40d4660e6576d5535b571e8069622d0d8ce098f57bd028f3ea244090c4c1d9d233499fa845f94ea72623e09f106783c653ae2081403fffc9916366c68a3fd207dbcbca620bf67d60de93d3a8f0d61226677b44b8b102d389dceaf4f4c023c8bc815c3995ebeb25f577974b6c1d91a61a128e1d6effe0ec83c09a38281f3cc65cfb08a54bc85d970654a80c0f427958e0a82670939a5556e163c155c12376cf07232df3eb0bacdda4fe340c6878d69872df1a52d563096295125abd59b776b41537401d08be67ce2704a1fe9c7f40a3290f3bbd560e5c1d33174a779bdcacef70f19833acb87593e7277d7ecf6f574482dd40db3747961167c3a8bc741666b97edc363fa8fb819607236e0b34eb707bc03b505f1fc6f6df95baa3532d4cde0383eb6a3dc555dad9d72a937a80fa59aad944a43724ec085b0fccb09dfd460428f1dd5b6548db40e3409e170efaedeed8aa848277ee3fa40e62495bd96062f3f8a65bbae74d32c6ae52e01a05abb726ff9802085bd8b44b9341d3186de70d4ef6dda1e7d85b88b566d721fdd7b44a04aba7a2af8a351b54d3c1a0a660744fb16ef8302fe276c2038287252eb383fcdde71d485dbd3f59f6a89ee574037d7791f053443af50c1668d713cf80e275f6d5a61b376d88056eff4ce523b86d9abb08efa60ef3340e1629c7910712973b9734f6a7ee1d2ad22767ebbe80a44df06da9622ba55d73145244adf72c14d68ca4b4b61a30ce36b0593051fd1a0037cfe4a02e86c4f1f7f089a781972eb72fe48353979da888977c9caa8fe8645191e046e77d8c5156bec67fdffdf721d8f1e7b4661a42ab65a21ad3d2fa50404e7d5fc0896e92f2eff7c46b42310720f173f28e4692fa39a43ea8028cb3d48b5b9b4d80cb2905b48bd8db72422983a6ce6f61e071cc5c0f5cfca3b3d9bea591597cef2fb50bec5ac091e9c4c9ae94b387267c9ac319ed534acc3cde2bccdacc0d6c1ee7d24ce529c87753a2204867237c536eaed5e00b3bb515a3396bce1e5198ccb6c1f87508309b3817c2c6cf67204b13267ed139501e107b21f94578db1a55202966f1de307cd735813005e9b30619425abe5e0fbf4c588b36c700e957df10b48ce69f37e8f1c2a3868699bb43d3ad3f12d63578b44c2beabe09f96c267952806472cc015744ee32efef238d072a1eba0b258d17591964af6bfc859cd5e0498e4469ef8a90e2312e807490c9d7211023bef9328b1f23eb62483b74d08bf1b53dfec15d67f78361c4db20fcadf72c27d860bbb7e77885b031b679b79f5243fcffe014e688c67cafb42256e59ae726447501808206e1cd4046b0a307248cf627380e0f54afa0143ca8ffc68576071b8b01cec8e1fbef2cdd5ab4b7147ea0e65e1600bac36903612fa6317cba5b56921d3bdb82b6711bff3624ca4d8721031d8630c494d485a57e7b079d211083452d9bec9a195ddc82377038738383095c76eb85b064a1ee55232d3f10850c1e5725e8c8181f310e94330f156f906384198714ecf23132007ba66958b9fbe1e0234e5a7f2838dd936ccb23f62d1fa1fdfac345b3086c3b2256a61699c1d8bc10072e31cbdd635a81332fb7dcd810becddbdc41680d71f1bf59007b9ecbc97577e1f051201c91f6ad403efabd3bf6caabd3d407fe267d503894d8df2195f34e542116885e6d0a25bc9265d206b6d41c3a16c258cbb109bee426559459b9b426bde3f7232f8f6168d6037c5250326ecd241f42ea90a60b1b5391444f27d43fedb7172d8f6195279f01dd6e5e74d9a842e1439826dd4440fa5aa9d17c8f2cb0a771129a0449d56bdeba46eedd325528d23bf496a696ded6ebe44abad0f6de32b3b157279f67c6be95ad476264c57a2088edc02cb65981557a4d57712978e977146c272861b0ba138427ced9f031e47df26ae637f4ecabe9e5f8d245ea48a46fd8e4c0577fa70acf0487bfbdd85e45abe6baf7e3374b5084c45f37b322ed8ef5b511972984584bd611543995a9424330dbb56435db96b6878188d981ae2dc2aa65723721ae56d031049fa9d0b690205bbe3bebcd6be42ca6ca824402a3a299e99915a727dc078e81084ef5310862f5d0a16c2a11d68ed580ee7918e3c6b9f625da65271833cf4e0e9e717f35410e865987b77e617bae5e5e840cc0483132df9c8c00372a41b4ed0a0c586d0f61d0aa4cef5e4572a3a2ac293746fb0fecd4ea3fec7a972ab1a14d3e243d97627b5db08ac00d127f0a4f05c243d8bbcdfc7fc7a83c17c72619ca31ae6d3bc74b3ad8d6e990be121b41bb6936fb9620d1e539c5fc26f1272aaa9ccf96d572c4c0ffec113910d0ec78acc02781e179e2b671fad4fdd4d7f726cbe33dbcf7ae033cf0166bb981a40c56a93d61e998a2782a691aaf4d5e54f72857964ef06840f5385850e07081c7eb509fe73ae1d674f94f930884ff784bd215f751eee36201e9c233d48dbc9be8a145fc3d43efe7bc1c4d45e11c1ccdea77262c607a0e0ed170de0cd3cd23b415dba9218f9a77b5b1026ea37f64f3490e60f0836799674360d3a0bb1ab2949784fb3ef009f1a4a729936c1c891e2cfaf2a7259d01cd999bfdfa4e83988505582e9c9efcd0303dfbffe8605e8c1ac99770672aa5370f4bba7941e8d1ed2374b732b08673c24efdd55a4106f6fa88c88b4252565d847b855e2c698a0b34b7d3cf8d9bbec63f5141330f748bc33cfab7f679572ab5431d29da839ff3ae0fe90cc22a37544679fb826395856d06f2fa57c2f36724b16f74d4f3dd3b34646b1c67e5db6bc682d6a916cfcf45f3fc77985a296fa72bf84049d615c4b99dd9eb7bc112784fcad21d515329b0c3c67ad3b12c45ffb43a4c2d184e9920fc40e7e051147b5c9985ab6491de61b1f676e446fbd10ccb572f8f0be25c37ca1222a2c4fb80f082b2e1c3fcca6e846e8e1477109bfbca07972014d964b41b555e5d1d14baed1bb18cb921c8cc79935c8019fffaccbad9a852db8ae601c0a349a166e3d7515f7bff5c4480eef1a5262c8264c63f31b4dc63656a2a4de3ad45b2c09396c068b9b58ebdc0b35b953b16e8610f7a3be92103bbe5954cb2ddb2597c1540a10610aac389067f10216b068d531a96430fa2f200745727d5ea85c2695a2bf722f6eba0209cb2461418f164d955c22572b69f85e9747725fbb81e889f5cfae0f93044878faaa0f9b95556fbe45f79a1645f07a5fc35e3e7f3eaa78ca72a65f5e2362fa5d32ad39e7c87fbac8a20244e6a7713be44b736f64562e626f2fc4e486dccb81d8c71ede56f51e01f534e22f63a84843c3ac980fbfd610ebec6a7816efabf64aed33050863cc728645b659b422f3b635600ede72a3610db759d30f8711a66d718ea1500a5b5dd9d25a20e6fbf88d4c24de9028727065121b3c560037d1cafb747249c9178cbb71d859b3373fcfc21e4dd97f2c31ecdb33c3426a20f0eb21aa50fbabc5b3fca3cfe5d61bc46784344547edd0432de26e1c50715f719045d1356f99a5f0b8a177c58e794e1d8cd10fc67dbcf5e572c339e646016ac013951257ce57067936bd0583f6f6e62381fba4bc95f140c605c63afd7614ccb422fa7ad73630fa4cbcaee30fecdb155ca1e0145295190e6f726693a013543ece94c7545d56d31e993636ca7af44cdfb1f8250bef469b492e72da48ee9659a06eac248a0ab86a0a3e99c2e448e78f146451a76a31b4db7d0472e7e6f139a3d8bcbfd16137e11de97d91e01ce81d905e8290d34a9d104737bf3ebe5724b301fde46823316346ea798fb8a4d6f533bd50cd98930bdbc292090272ded99878f227409d48d02fdfd5e8e554a7e7fcabb281545139e11a6b732e62726b91957babc45d202633b3a0abb455d0f35c57246523a7599700c1dc85a5b45ed9b66e1178adb728289f3b7fe7384e89f28364b6f1a55a77d099a0eac627a5722e1fef6514c6641eeafd9f24aa84c08c21d90ce214e808b74114d25b4b99a37211fb84d85f28b26398f11f55420312f127e132dec79f7cdaa3347105ff6f1e72d672d6e319c221b0aa02dc28404691558683f52bc02be7e42ca9bcbea2b3524ef1f6bb55f726c3b4b9ac391695caecb653aeba828fa23d85a51c1a88aaf48932f9c58260ec600a33838bdf6025ee2a2353362a1238f32415ba0e6068e7ad6e7294e27711b8e7f548b99d44693c1cddcd86fdc22318c3f4ee116db4590212d972269244db5d8ff0322df4a21015560a4bb47bd90c12c82479fade67418167e73d2d380f3ea3746b686a858031a7f74b15497983dfc87062e1a4b3f9dc44702a720aff0623f9823709d6c38586143dd6469ff127f88efd9afae0ff95bb27781e41d95c0515d910c45dd0ed5692cb2c1a91daeb46d60758047d45cd76dff4c70b7205db6e0ad9cac8a46a36b64414bffa7351c11a7bc36bc23a084b86a2c8a72172aefb0706c7d07d9764b8ad423e57a197a72fc6b30ffb67ce6838880d2dcbf972ec671e0fe9559b1956d4b425374c8be26c8a8cf8d32202afa0631cae0c55c47217222643132c3684bbe091cc12e3300b497fed4721045c852fec359b1bc96033c7f1df483f61f72c4fa63b9d489d3bec4d14b9c42033ba3915337744d5174972fd9f7b648ac8fe71e30697d4e6010e190491430e7ba0c9de3f25d309bd31ce72ee2252914fee76a0da39ceed7f1e59e99bb81ef08dd9a5ca718e04b2b1443572a4f7298f36f6c4a95260912564752d57b92a0ed8cd015615ec6c821bcc45c701c2896a4b0b68df7bb11023dda15ca03f3d06ab518001f922326eed4e3f3e80726236a6c2d531fe08f4abb62eb96ee1b30dca1f6c1fc8c7b16e54367a1a531e723f73e141eebbb495227f121165a09916dfd3f4c105eb690c2cc80d60048ea74f1c35d295345612db524f314e08423472b197403e8e17cec3f24059b148750872ddb786d09b6b62b5fede6e1cdc2b42b0bef361a667c910d75cd2688e207e52024c246b370aca6a61eebbea9b7e9fb7f03d23c0fdc16b2a6f19914c8f34773372a86c51f16cac8de87e5fa132d76f0a650737d2789595f2259e14603e148e1b720b1560db975f632575eda865754112e9cfe864b2c17fad61116344cfad71310746ef3a3f7def6523b325fbe6b4f5b8e029c88b4071c4a1aeed83a5a6621a55724de1039282af26bcef24a6157ae4aac997316f75af903a80e780cec87714d3724b1500dcfec0bdb7dbfdef5ad6953a73ae9ec05eb14431f06a1370555a2a1c725e8af50646cd9fb8177214ae2c41cb237e8e3c3d781e582cf6c24c5d873564729cf8787bf9f4e860e81839a6e0418274287db9e96de0fcae3b7dba6e2970c06870fc250cd509013ec333602dd38efdabd10e046e5eb2c458f6c07bd5bdfa03450ae81dde12a3d6218308cf0de87121a0f04dc8794cbfce5fcf8384cc126c0f7233cf812efd6fec24b360499fd9c5bd91cb3bd4508d1a38d98ed76d5e096eb472ae9f6e1945785682b0b04276ace4d33d431de688a45e19bfd3415db0fa71ca72e14ae6763a0f96f492c531359f502a2a5ba571bb1fb07c6d5f966eed9681eb72e6bf8b4a4b9be37dc50d53a0d8be45bacb747a76c87f9cf822602719a6377b727842894346cab11113012a4d1278251f16179ac4b945f32ca527a18e55bf21727025bb77cba7faade14e61f965b8973cb2426cbe209b9bf9dd543d8c730ca510b50613d6e099baf15ff7d8cbddd08b07c240bb3df12369cacd4a15ce9f8fed723ce50e528173ab422a9c764f24411197d92bf3c3c604a400bacad374670061727f16b4fec68a369aa03c604f01ed38b032f293b76a3f654ed85e54dd4fd986727c8252a7d36a318f3358291c525fbd80b54747262e5646506c9bd1806158777263663867f0c6244806a614f7bb74c0aa6d8d666b840e6dd5442aac9e1eb5a472ccd435ed7d9a52f152daf97a6685afdbcb3fbbf97e047b4cc7fe7bb5fecf360bb3cd8a2951c8eb3e6fe5bf49c6b66e95b97f339d6687a65dc403e6279ccc2300608fddeb0a89c7c660fed1ee29c14ca242315edfc77a32f2bc3e5300398f0472c512132c4c2bf89b90e7b6adc65774312d868e608bb507bf5b5cbcf38193b772c4b8ebd311e18599897e29daa5c9508c72a8d4bfdfaba431be8dacb055f04e72fbf5a4cfb499e355a138826aa3e139b7c07e3521454a3fdb36767c6a94e90e72b482f7490576ae7cc92c34d1fb7a9e8c3644482c507bca2cfe7ffaed5c18e4721ccc15e5ff96e1f96c8bf44ff69ebf7d8e915da0f9b7a3a26852273e0a801772ec083d5e7952f86e5746baa54d7dcdfde6a909e35956f6e4be922c854a6bf24b6b5dd6b721b2d72c30540cc29ade26b48fbfc176c8b8a1f7f2eada31988068647c34d0a062cf37d0131a4afcce931cbe5718499c557ff3193b04bf452c1b9c3bf8f1f8cbc8752f9e4aa13f23c4d7388e31919ce3984aaa1521d78fe18affaa720eef0c96c8afc61f7f66e253a1df0446ffa7b34e69bc46350fb8bd65de23296a0f5abb9c5353e120f1ccbddfe3757d7c7940e66182e785aa783c56ac9b322666918c8c84612ea16e555001a4201de322471d58420ad0c2a8d4ee748450914c7247402101b61ec6269b40607d95d9e61ecd2ee90e0d1c4255a1880422dfd5dd4ec2444d219fac70c223ac9137e7981a160d30bbac69071cf7e7cce25e2eadcc443ff147be5217f1f6a9b83caa1fe39d6746a4ca0100be8f78ddf6a87af32e6072f5f46532ddba91a937250a7fbad0de2b2ab72643a1c63bf7938aa430cba3cc7201f2f9783a35f6c2cb2535a238048868ababc9233705f6865fbf3e445203f043c6139b4960f12aaf14736ed58198e366f441260949493d2f7fc78038a295ff333f1eea0e2ecc08fbf8f24c3ee7b7c0b3a5fdc0f544f894eeec58b297ed72164658e0db1ef618b005f37864c91952b70e15f54b5e83d89c2e1cd9d4727f2ff4722e7e6e3a5b3ab7df674d8b3c97a658fc18c2197ece74354a31dd77e3f70e5a5794346211f84ed449a2a1abf88ee456b181a21bcc02d1489a093c3ef51b9bda3907ea55891e0ea6ea665fe54f828fbc60d7f49969c91365b0d986cb26d4adba72f9bed7ec5177c05447df48f63ef08fd0117ad731355fa79ccb79972bc2b552033d14ba0851ec958419579e07ee809f57fcf5cd8fddb58cab39fb2c3e229b3d72ddf7b7eea7f6eb73a417fec6cd68d7921b31355f63e0403f362b5a25dea8137220eaa24f38dd30fa08f88da2682ce0a665c662d400730cf3b062f18507c37772741117ab3fe4e2b6930adb4f10e4d8b372a141f92cfd84fbb0f3e0a49001d833d8c95c9715120cffb78df1e72d9b3d4c23a6be135436a207064597c350d92872e37b64c3774fccf81c2fea513b08b36d63d7ae65190b84a8bffd3ff91f54a4728db23df296ff405ad77f837e70c26dae654300a7195429c208f1e2433504bd721faf20e2a60cbd36812f1a8c506049158d09686c14703963f607ea541ba9f77285b455ecc637be66d7543823f7753683b0e226ff9d668014013389ee89407f539a1f8faba7f3b8c58b74462889b0cc357a79cc223981d68cecd8beca623c3572635dd2d564a354302d4bd70001abad2e51f2c4afbb8af81e7d41c57cffdc3934f1320510bc018267fa21831d2abd5c88efa03f9b4172dfcfe87536052761d429cae1bd02c6111e55e9217f20779be6d5c71d2dedd8270b709cdde94cc210f82efc3f4f93f4b63d6a6cad02f4c943d4ebe989ba2f69ca50f4260e893ce8e3d0722593861676356ee6a11d41d074a9becd1f73833c1f72b0d59ac38d027338351087685408961890002b6305ed6aa7b73f8f615c3c6ea07733899f293b91df8872ddbb8ba182eaef97d232e199208df26b7c299252a8d2e836b7cb3bd67dc05a4a9194857b91b28e541add2f78bedb792ee726676bbe3318c4ca8ca2929d4fb0351d3028dd4e51a6bd1dfdf8fed852abf1764ab6c8a0f4bfd9d2dd320c770986723814e3af2cb28914fe815deff0ee3d19e4a1bbd39b43bb74e0816cd9dd4735720fb0012bf1e8200a8ba7fa1986396791b496a7b92d58efa872282cbf361b8a02d10f5a59cf46744c9edf7a7d581e01ae34a939e11a4fa126c42527522af9d6728e76f6cd2759c2d0d3662e0f34092c954014e676cbb2b2b6997f1550660bde7210633655d76f6d4d627de99794d386d5be24657a55c10721e9809dcf7e21717231325c0d02f05c0a8758ddcc7696e212bf373d569f807dec41e3f16cb77030729fee16ed1e4068b3da5a250fa3ac731afb69aefd2753b98e9fbd416f98630f0d20fc1bac9cbbc01f2d93428f32c0b10c58f96947422603021827a4c0032862720dfef29c48d7554ede708dfea1f3bf60ebed55ae9e8c2e5ed84d9ebfbcbc86008375e0d21e005d7157cc733cd9e9cf7c9597d9ff9aa4624a16002d6de8cd1736fe314fa931435cc248654c817cffe5c7f4729f2c2ce008c667417386494f0f0b72102f27131c97a04895a2c14e80d366281275abe0defa8e9a0730f178104d72faf17f20767987d81af41a8740395f37e3dc9ce0421b178ac5d75bbe01aa167289668eaa68e9c92514072642d11949b5511ed50791780ac9d075340d992547721d40c08cb5f39bf0218c45318fef0dcadc70d8114b41e782645c2083cd70f272e40ec3880e6123faaa858300078743e8ea5a374e94b336063c1c536a5a9d5b72ec3320614f0921b15b66b37098e58bb325de185a01376cd122e422e8230cc24877cbe0ff621c7d4ab2cd4c8320c216eaed6cd75ef4a44a009702402d597da52e629f9d9b78ab63f12f956f7e4b905817446844c8ae581d8418348eb8d8618572fc09575647567873ddc4c19b32cb57424e6a2eda551013b267c89301961afe54e309e7d0072acc938bfe0bdd308abff59a40fa557c1135bb318db08485b628451a5a0b6b50e08c705b8dfccbb9b12c6ba193b46d26c592769d611f38aa0b2034dd5b54ce72c52ea94833d5ab8ffb7d06d4fd7c7b725f35f215e1f7374833331cb020b3fe8dae4e6a71e1a032cd81025d9a9fd6d9d5400ac764bde2e6ccedde60dd849c39113ad48a5e04b08218f0b0eb845138e2bb02718f4da2ea61257c127277db672ae678b170e2d455ad2467794646fc4ba4634eca54c87cc49d1c9f4672ef7eaf1e7b8e2f787faa76879705664961b97b96c8bcf78908c8ef56a6b03c721202b76091af941a7049bb7bc7f822836e15ba12e7682b6e945d8cf0cb4fbe72e64b8071bf7467e0056cd2cc0c6976b9d21c627f354367d4588a2f9778cfe6109b1ce07e49694ff034224398f6d480ee19fc499453cf66023e732a5531bca672fcbf68ce97cfe6316e5175e6dc2554f4f0d6316704a5bc5f62737dd7de843130d71adcb65beb0b7d0c03472ab2f7e768ed57d2c5b6eb45d2124d57453ec220628176cd5334a4c3fc7df1ee9e561d4fd10dd318bdcfc5b07e7d6b124df16be11361319ade2446d818aac3128027a3328d1c52abe07420c67adb69d5c2195089505c920029ad22cf4529345011b513d46b010de9d6e7991453531e1a64455440723f5dc74f217ab07bbb402609753f0965f3a3518d7f9e224cd557a4ff3e6f1672fa7d1ddbacecc84234e49008b04521cf73bab01324b45e4aa7435d9015bc9b22f438e11b4b28affc527fb5b0ce2964ad951ccb2de7aafdc0e68bb5ff58cc8472ac494b6fd6b2d09f3826984f94c9f4a6700450768f13090f306e01ab44be9e72994bb9d2289d62dbd5dcd2f80557d0f35a25e28a78de2072f62678eaecbe547288caaf805fc624e7663b9555aa2a8e094cd196f1c8f3a874fc83f433d13aae022243dc248877cee3e7a334363d19f9cbd183f37f60bd593effcc02b8702510720d8a65989a7b51bbdb630371002ed7253191dbd964f49d80ded000e9f5238345da9c28d230d8085df40d99e806b8a987d9c241b08877d557559a0c48265650729cc6304b53ad2bee8fa030df8e0779c63fc0d96e219521d56c4019efb8bb15692b00a9dc05e2d6ae30d4696cd8389e301b7613fdd42f7bdc4142dddaebc721726cf6f303e02e4299040b434f824faa7359a5d8c1e2282fc801f147006d75ba72108fc9b8d3e8f8e1be34a986e63c1ce377650651769c6d9fff974bf1b0f2e372ac872e21d48350e6876df4ed6a378de30a26fa109407c7e328d0767fa5ef457111892f4f91e52cb07c8f221d215960962bb0a709452ab3825f39b5ebabcbdf72fd44bb7be74409416150e89928e8180623dfd7c7d6527bfe8335d72ace5dd83fc9a1188e50cf35226b7a7cbddbd6b7abf5f57eeed1d04c7fed60febc8b45c8724c59f2c3c7573337efd2208c232e8403af05843b2bae313d47d91bb45fc9de09a15853a5dc5c46a38c7aea9a9c93e97c94963691f167f0f8fea4a3eea7528672998fbe736921368c2adf8c52f18a254144cff7edc5fdab03d1f9ca5a0d74d47276c619e6ad9d9656ac8bed72c03d8d8c05ff76d4ca8c93af6956f4b5539a6d720d5804aebbba3b14ff743d96df202d2214cb72061349289f3e9ccf42e3fd84699e0f19015438849a3ae163f5f5b2e35126f5da536da2c4d086e6cad1b2548a1aac602a2dd11c948f883f1b7c31403c9a2b1584a5c3d2306eec5ba4bfaf9ee27292f79ceecf594922060dd79a184bdb73798323f53e5d8375a869339db15e0c0eb6ab3f9f4c6e98eff7bf557fc15521b6725882d4a32faeabeb433f241d720472d6640529d4a6e0da497ac04e104ab69abf2368a76a81d9479fad7756446a0c1afabb4fd2b4113d61468e017f99a5e8a38b068adca79dd14f4decd89c96bbcf72989a008d2b363b95558036223f373f624422e1ed0e7910092bf39b4a7f967f2b5fce68e81d45d8c3fbc33418d6918d16af62956517a710096e4f651b0886e76156311861ca6dcc9b517c0a5f7ce4eb641675b11c5cad3a1ba321cde4be6a91653a8379c2ae9874aef272ef7db0555e0356fdf01da928959c804bd5e31ac4f672de70a21d3a2c655730bc43eaaaf42d55cbf4d236d2469c1111cfe224ee54f6727592d84e506c28e8ccbf5547adb810f5964766f5a0cc41289e23b53b82372072988ca7dd5b20a67657d7fd4fc77ac2c80b298accebd43162a9e7d6300a7b8e728bee9b15599eb45424c8dafe97fc5c68fe1e549a8d55c067b656861e80e1ff7246f91a748b16d27b98d0eed1997e87743c3a38df2286d2b4d367c851212b9a4bc4b2fa8b4a6822376966697944cea01d5a355edc41c726254dedb95e79eda2254b7d8b14f02f4eefc5afcc0d5331be0cc6c5f152b0f0a567e83c2fb972cb131652b368d5617a98884ce49d1eea0e829929fb3588b782d4224ad8e4a03b267f705fc13deb807f01a55ccee3d21df6171429dae849918df68b3ae609c7a5ae46727318fa7cb1f309661a3813bad975598fe4f4cda7e265bce60e37dba0ef2fbf72563738bb9e3cde76e468253cb79c57a4548de0e068eb0c13b23cbac604ab82725a15b1ddd26d83bd2c8849d02e070e53be47464c1171a956da69f7af331ddb50942131f0e6bfb9bae83a246b5ca52c35266f6a15f471857051e14b3b5e0515728eb71cc896acba588ff7b6128cf81922356190a23bddab6e3c7f3b1d56c4ab72bb9ca460b725e952814478544fafd731d285ca4526df32d900bba21d5b1e2c727e6567579211db1476cf8c8d750f3d4d2ce1d0bc895c2534e98a9123a5988172625c167c0ebe1e4bfb8cb407ee76dc31e51faccde67860de8abbe3ca75ec4f72d83de72ccf10c7069a3ba1b7f6a2cd62c374c22fa1c66d93b85e829aec8a8e605f9a695601d3f82455c1007d3a744e0b3136fdc77c35f072a16d17a4c8a7f57293964dc82cbd876fb62393a48e7500223b2c82233fde814b644e03ecdbd905726c657ec4b78a1bc1728d33f050f3977d0236e529a53942c4036b6a346535a606613c952b3d746010de93f82d0696d61c53ff0d4580d8a5e33a6060bf921e626233a4a925aec92c90e0b89c1911afe439040ea68209131e1cfe7dfc9fcb4f6c0278a1434d1c21b5da66210d9379ac3c771d5cdffc157b3d31d3796e338db69572de1327c3ca7f6825ccf6896efaf848144ed03a16a93ff4124da2702eb58f77722b5e305088a090b31e3c0b3d75a6668be9e6288961275702cf7f0467ead36072c41bb173188cf15cbc9bc39b462c62c4937e55d86ffc6c1fe0597a24178bc57247a591136284ef86b9e4551a159a6c3022094a617ec2e2d1f63a8e9259a28272418d6a2cb1aab0d735030d8c3fa6bb2decc2e1215efe661119c727bcda229459323ef0dee0fc772fc525c22fdbbde38e232796326a6b3a0b5fba00e20339c203f2269d31cc8c4e4b38e24818892f0762ee28f0e96789ca108450e7085d69ea3c3fa7cfab193649e8c8fb0eac68f3ed1dac1593edc950cde0976546670ce1683f452b50c158a30b7d16f83b084c0416148abe45f17a748c25b4497b65f9d802726ec70b67ee6a128447e688e3ccbc70b7b3d88a0f9bd8fe20dd66be6c8f68ea352400ef9c7df581a53a3f33dc90d1c96c5654a5e66d620af5ea88f3a2e094ab721688db3cf96b17f7c3a8d642559a38de6931f6cb7b3a99a740506b0113c4a97248bb2c9b92afd6e4f6f16f141852f1bcafc50038e8f0aca5c2a4dc94379aa9723f8d8d4412b0ba99da1069ec2e861bd8d29c601a6c07a3239adeda419636fc2244af295b3697e360ed3af6d6f4dfde4b2a2da4b761f3cf26fe258ac3de4aea3df450efe8368da876ad17d5362485e5be5e0c06fdbd44b5bcbbbce80900817d5117b0b0239c5fe1a9ceb43037943fac50939c0a7fa87efcd297f3552474e85572c77abdb77989183fd315facb79ef13418ef269d4302c7f8258ddf6227b0ec372ee6dd338cae28c63e339abcacb101f3de1feae99919e77fa2d7dce9785f917723badca283265e757fd3b9fceaeb6a957a95f67b3846edb90fb1e44453809c120978357dd4120b00e25896af8aefa32ca3005b473eb43222818e46b1bed147372a0dc43933fa33a6f7c5f16e5513d11f19956ec6190c817162bb9fe275fee9b690ac876408b885d6d7f6341628adbb548dfbee4b559005389d7db8f9001c0903c118b0b420de38230b3f88dfd0e6ea5710a94e8ac0d4464bf6f94abc0bfe9427261a30208a8f6c38aa0a80470855dc5be8daa2447edb44d1bec9396c5694c976b299ba3954e64f8c6008445a1aec9e9a95d43935976fb40f5446db881f8962c72015e9abf59d6fce069c5b85eafbb5021a5ac9cf5e327c945c45d94775e8e8e72cd66b6369bb3781c65a490f4bbf830b6f5d95df0789b1dd405d99b5983ccbf72119262cc438bfa7445771cbab1416f3a0b90027e9f4c4ab7af9d126a6b5f2b72b0c6df63cbd796ae5789d26bfec8cbeded0f83091e3299d5994c8f5e8bf12e51b97b4e35a5f00bfed3691c5d6c1f3a1c5c3a9132a164ef34778c9e0e57449a723c9e7cf1fcdc93ac86331fce1f0fe936d3d566e146ec4d83b3426e89ecfb2f72f6a90428fbf1f4ddb5fb385605162dda0b8dbdf61094d2e3f73e57425365831084175cca6a2742fc4f52c66a0866894d49efebdfcef5dd60b0ecdeccec64b70f718c4eab574c508d2b8a9cf608060527e0e71ad1058e36fdf93eb22213b57672167e8851be1249f4e9d802efa31a5c366ba634bf91b3043f37890177555b4a729368ee79c31b5764a93b3ee4211098b738bbe11ffe3edccea935dfaf7dedaa58c9e191c6cdf0ac53cd5afcb784480a031b6d59912a5cda5e2eeb2fc51f63c05cff456336be830761b0532231f48a8ad49362480774505479b9849e2bb4fd85166788a56398d601a491709d0d47adbfcd7e5f9b4879b549a85b7c365273b9396513fcef8fd31e090eb54b2b045548eaab5e9e8d61453f62cc1f4fffea588a6400d3a41e826b3d2bed43d0bb015424de51f214d3af31e43df5509b08f6c597d472503a7bdcd6c801f80b81f809a09fb4362a22555b0e160c42e99e6f1045f05d72c003a6018dfdd2153f2b1e2d0ca3008be492ba65bddd91ddc8fba344fea876126f8e3e548af5d625693b2e2c420273c0988a153c39b3159f41264a4854342272eb5ced99db53941eba4b597efd6fca10cc3eae82913e4ba4ed9d7d0308e53b728fc05b26aca43afd37165d110a472cbf0195f996449b008d18143b821bdc6d72c88c3298e3aa69e48fc02cfa99600e17f651c117864231e31a38ba08b0d8ce30c80fcb0262b8afce0252f22764e4d4ae4a8d05aa191efa510638f16767f5b64e45820cc083550269dd3220f75c44ecd3d2523dbda523d766eecf14ff6b35ee720bba0269747a6918441cb482a881de17f388a2965d3e84da790d264068718172c046f1ed31737c22cb437197dc24fcb5ad2f6881dc15174e02894812a7337972ee905ddbe9004c704a2ed5ea316521947c471e45bcd8518049478cac4e17835419ba14169b4b71b3f0ca18785d2c4f565bdf05270248a2596d160efaea8d8b72e3494b1231ab0d9111c3d1837066163cbac300f99a0014e660b7a4ffb2f6c17253e2d91e5c2b6f1c054dc39d1a9fd975a71b98a0203b730b827d6a969c23e67218ab00fcc3cf9341940103b6d5946a334e0d697db1478ca03acc1cbca2584f72550b080d82e7b0a462f38edcd83d2799a8533f6177697f6b69e8b78483fe3472b51796aba12846bcf6955dc013137c45464bc020b6bc030fffacc5c28a8bf172e1fd47a9787259bf2a647a073accc78aa0943b78d5c9dcc922244d4a51faa9724cf50766748124f9a0ad0ddd8da4ee668e3fe4ebf582440c5af330e908df6f6ee7bb0d7d51935ae95f591eab4e447ac1fd6b3e842e240ef476acae4ce508ca72afca78494f68e47ec06d38b1a7d52c1e4f9feb2dbfe83e86b6c8041687d40c7251f3db5b18e8aff6621d20aac6ab93405e72b345ce25324c383c823537151c7256989feafe20ca0b89cf4a5600e4628240baab11a0397358e16036c50da420720fb92be657b0bc7580bfb0c83fcebb8b6dae4d1cb15e20c0c5ffe1339263a30b1f56afc36af298ab4a1dbf3e73c206b3f2e45dd882dbfb2c6bc867854112e90dedd05120daf84ac98ef8422cd31e3319fbb463027cbfe131b97425b3fcd251724a99636cdcde8ba57a39ff2a2f0179d528e87aa4388aab5fb22dcf99276cfa0308d894bf754db8ba6dd55a2ee3d4eca597f1b232db9d82c1f0e4f703319b6c721851875d3bb4cd0940d3480207a8e2839abbbde3051dcca926f67623e79c6d1aca75c998e04d8de88493068d9f670a5eebd041ce353ff42a3fee8f159fd80372f1ffb5ea475b23667c65c98dca2a91b0f64514b7f855d0f54a18468cb7f14d7284fcf452f26e6eed1b15b5263ec2d3ac6662200efa2a2bda1bfa117441861b72b5f3e64acf944e442d4c7572721f36376567d0a34fc5f02123f1c8aadd2b2e725b0676c54912ce9785039fa376d342412fe22a59e1cc17b1849861c8c56601721f4efbeaca2f1154b1fcf95f3b432d02570afaebcabc025978b8f3ed587d6f2bc6478bebd986ea7f7b00b2c08513f00908da0cb82ad44d3cb0f5c517b7757f7230951f9a0602bb28bf0928000afa78d97b886eb850f2f1f5e44916d417df9b721f58192dcef8be216accceff79507635ce775ae7ca2a7b95572fbfc1135a350088270d019cac21b01a00ad0d2d36990a00fad082872ae415a1810a1d2ac0615ffa5e36eb5b8df7084d85487b281da2cf76515d43a9dfcf8e01ad7d4f1aa3817241ad0c6d703f72565f766042d48d8b2a1f019ae4fb8e9e47348bdefa9ab992139f8f3bec3aa73656682b05ee85ac340560e31729beea24a5f6d8e059dd4c050360c1fa733f19cc255811d0c4740c49fb71aa9f29721960cc14ea1f1ceeac023ca6e052024e42d2c8a473c3d9edcf7b7c2b87e4c1a30731f2082b2b485b907b72900d13f059e44cd0fa211d1a2dd5e81a299da0e3990542fa7db398ae6030b369456b31f2486542b00484ddcf917450b82dfcd03950d224c7ca20d159e2673e7261ac779d17090b5f37c433521ed819f562bbaa9b45b6744ed2e036af558efc26ef072b54bc8553766f8be37e53efd55223f4d1d2eeab57e44719e5bd359fb2289630ba0b07bc00687d2708e7f683f670e272e88bd3dd3fc4962e97d2d8a81e4a08eac9a5ab3cc850fc848537a353640fcfba6dc3ecd7607b39347ed34aba707273e2d7357b9fd5128589591a42b50da60683a1426a9510e195f5e8ab03c16572b8773a6e19988cd7a72e0a9d0b41cb73901e95c0dd4ee46e7ad35359aa46081f03db9b52384a689faef11f278f8697b7f86b09595e2b0dcffa0dab334a39e3722f9e615e449ec2669ccb6f2d82ae4ac86cd1873e6f1db00df1d3d70d66f32430356f5602523e970366978f83c987c447224123b8d3454865c3901c1d864ee972197933945039dc7e5ab42b06a2770a0e0dce465f3c2ed77028ae0300b7e6ab72356986c92cfb75e314d2245fa1af01ee5f7c82cf089d43ea381215ee75e3b7364d2f5d72631dcee174775b1e00edf7ca839213e95d009851412443755d5415725270666c0a1d7304b335bb1fef299aca273911d09943f54ee75a2ff798a8ec72b77c9b604291ef5c0ee19c5a78044ffc504284006adfdc8cc0abba2e1e5d8b1218baf2532c79aeee70d5040447dc37433e7088dd745c51887be7fadc938e927215a4256d41af4e7d3a54717d14aac47c1c3fa8ee9317aba4416b1e639262eb72ed864fc92d05be0579d10a429d385ebfa4489843ef53f9cc16537c3d4ae56d7219d76d0cf2582f70a1d2f325d6b5f939c96d143da647154b4f2378db0a851e72f65f884e2faaa31cc0e718cb10f59ea40ec9d11a72768578b172c75436b270722e7774f7591e182d4f58f50ed9b4ddf3cafa5c83f657f3c0088502d526b0ee721e6b8ee145c3be1ea9580b3d7ebb0508f56dc9798f811f9ab357e28622d20f72d2d1bc5eb2af328fc1b6ed9ec0edd79d90e2896f71031e3954c0c03336f6d421c3ced65bb176257470050e5a47ef95e3f4b3739bd18c1aabf55f86a95ef95972adcd72a9393ec559d72a3da06a7821e7feea5cc7551ef1300ce9e99d12433b72ae985ae8f9d30f057274f6846641b9c3a37769eff939198e59d2795a62202552c5c722a154e382fd3d285ace8fa55488d678d6e554415c74a815c7d044e5c24ae813800cd895f0541fd88ceb4df6223fd402d2cc712b1ebcae5beeec0743cd72fc857d4dabb4c0c18d676329804a044e30aea676a64b86ad6f0e721946cec472ec5b58b376246606247e3c2ae15628fd434a03ca28b7627f7dcd81fa84a55e720d15046c71aa6f4263afdcb98fec9332485c6b1cf13922bb0b48268aa3837a075d30f961c43e7793a0f64143e3dc51c02e9bdc6f46541eeef41aec350aff4262ff8f18e3c3a6c8f28eeb0834bfc3ac55c90d68b14b44d5e8af13c023dc84a572d6e58d6a5c5f5e88737b6fd13c261161fb36881e465d0f46d8585e75cc21d201dc87197513c9b9c7c923c74c3c4f9cfa2fd3a71e7b9190e5f12474d243ca1c01440b158b9175ee444ba93b6512cfe90f676dc5516af76c4652106582270b8d53778789f331e826f2cc81d24105878892aaf894f71ef076ed8551d551407fc351f0ead557cfeb6d25082d5214435e2d086d870fe2c7e940e7e6e7044eed52c67245a20f207495584fd71e54ffb0cdec402033dce04560813411dc393c069ca37209b1f2b6d2a96f518f2fc23de129341588287a2ed1396ce9870134a4478c6d3156e48e01cde992fe36ce4697376de6d61e67874fee6b3125c3309cd1d1401272c5377f9a7f7355b516f9333ede84c53abc8ccfa7ee967169f8c8c66454f3e8725f63ea79eb1278201967026711e4153f77dd57631494c9fa20ff0446a11ce972c427e20605ca8ead2fb234351293cc66752a970cebd41c379806186ab09fc238e330007a6602abcea1f70210502b074f70c4bdd9c552fcd1c7774f794a1f3f693d441883f6847cd6537b71c48b398208edbe22986a646ca08a24ad682e798272d3dea304311763f57fdd047e0185e77be508f299bc44a1ff1354c1518b825c7238861ff267c7d4f52ce93835b53324059427ed5a547dfeb0db4e8af554d2262fe11dc28171c7f0785fef052938e5b66d95ef75d7c5c4818c9e448f1932ed5b4477ab666d2e05a49aab0688b3f9d48b00d6389ffe701b70472e511458b85dfc60b081e0ef5f96f7163be57cebde5ebcb4b6ba286dbe7ffea8f10a73b7cf8c23724f5957aced3ed22b6da7e2eaaf64d7294dd3c7934cd2df39ae52273f0a54e472d0526649f3a6d79202425b9fda79c13e1b524e6b2113e79189f4a7695cb113723ad17c211acf2103286427d0bcc82f35416bc4d8bf1134085de9d3af3936d772a3d9688f15e0c484748fc14049c8da848fe46ed1a9e42c37522fa9bcccdcb5726f7379d3698fae3ba4433ff2a6f28e005920a6b17efd8c81b9e5f14fa8e3ff72a0c29917d6d56ca190605a44c81a274c5900158d615e1cb2c5878dbfa046d154cf43512bedf914bf66b33d41acd22adc96e0e8493b08901bbb447cb36160e065b84c8195df38453dfe9c8e3d90560e32f0d07db8d223ef03821a6b6a93e42a724031a34f94f4555dde1224d86598a1b1aa8314c0965df455447a97d8822921720bbee14bb5f6c6253552abad159c6d1eba74143a6f85fb60c1c23bc1122d5b332e8086c61799cac5f4b298f29133d1ad70048e19bfa08f403a341c4e76041e725773e9eae5dff719c42ac7fa43f1aa37df67e06d8988f772f8b2dbbb0561cd726eabd22a534eda793e3570a707de2e921ce8fea3b8618046cf6213b783b49a7207be8f278cfb6c8b67205ddc0742c870c67a1f443015906fb5b522d8df34ee7252deecc60afa261494f2fa3f72c30a55f1d4dadccc3731fadab672cfbfb862728f6b17088fcdc91694996188fb456256e3d0fc5301ab4cd2197b772cc9d1ed72ea2aac93e74720a54fba4948b63679da03424e4c83b5350c9a424bf831720972cb49a5adb981cff9350d9023f3f725e16f8ef5380a37bbac5c257b41feb4c1728b3ee2a6986b3a59cb2cfcc6e12ad3913b00fbf067980a65c0670054c9695e72dbd1f0ceba33b37bb1728771177d048ce48f6ce392eed60506b286faaabffd6e6a6febd03a96b45585c6e24e62826cafbe79464b7f5cbf4466d35926905d527206e69ed654131acd17491b1470c25411a56767984566793d446a6edb87c2f172c75824fceccdd97dd736465f0e43aa3cc96190d70354eb85bbe30390af9bcd722b1d325d9ef8ffba2770b338d48f1cfb3924909d84e7f01b38e5516221e70972566ca47941bccbc6c94d8d8f6d47c363ab923bfe57c670cac1a07236a814e4725bfa7af694f36ad5e66affd754bbc16e276d4f5f915e98ee1e7da4e9dce77272c7c965236fa4eb39fea324a7ba02497d1e777fe2ccb4622b908f4d0757df6e72d8a14fcfb18ac756ec1c657a0a00ae42ac0b6d5cad4485fd428e32741a79b07287eae159b9916b92fd539cccdeddaa2d1cdbc5fb81a8a6f718a62e9384695048bce6bae7210aad14ae8ca1dbb2245eda1daddb5bf523f76d1af6e9f7cc452a2221054bc9f7ac0762920fcdd897eef019715948f3388d3bc520d10dc3df71e727cd094d10de02fb9609c0ced4f751e5400ea9fc25420146993ec522f721e77a11597ffff50e678937bde619960c34acef1f2e4eeae1e0dfb96ba06ffc9e6475502729e2f183ba97d6a39c616391bf982d0cedb5e57b866254ab0891715267577222827859523dbfb2d9ffed83f6716f9a57fce19c20c034c5b5bd402d1ccec14b205b63e52852ee2735e692fa08d9761ec6a17fc86ec601c6a94c38788a862e5607d78365d0d543e69b7fa2f8dde1c7806dd26c6e9fb46f3c239695ccc751147235acb4e3983fc9fb989d106d08e5bf9d16d87c959f0279469f0e1b98011dc83df9bb2c2ddef994d046e3ad18a8ebca37563ef8db4e937169fca51e1ac83e036d7601c88a001ac657bee2ee269cfdd8556ac84e897d686036aa3e36ab2b8d1972a89a6bee54c405497b4e2df9a9ccdd4f7ae596e31f25b7c17ff254178495cb107a7ec6ca38b678ddeed9f865187034e089d86615a0bad170d4ca7058dd696872d8cac03aaddfaf53f668fc5e34f0a4b6b2b0c2d422111e91c79261328bb63d724d967610c711300d52cb67c45de2144d3504d3b759a5a14fc043d7bac92b6813719119192d467f97518a3986d1e3e0910b302012526eaffa5db10517efd5af3102ccd5abe88529a0df64796a4a26da62f22877f063814e024437487ccd9f2c62a3a28f6a05ac9abc709957f3882480d588798aa6bf9990c809cf43919035871fd6b0782cc7ec81121056f8842086bfd7e4a162a89e1721e0ad32af97611f7472e5918b66879686be82b47f6531f3e5d2d1b420e63c90b4605945d223182a832bf4a41d718d0d09937a742d00c52c95d7255b301de4f41db4ccd60ed81007dc073fcf09f9f8eb8554a14fed99989940d41e4dbbc66f3354a78262c7aefc753772d9ad56c769f9029fcaa34bfe108d58c7dce1a39fdfa3836f5002854e18b0fd3e665f580e5eaa600ab4596fb0a4eca534abcee6d5f946adc9f1e547d88f484e72f6fbc4c705b78b0ae1329095ed604f51147239b6ee03622cc297f1728494f0721b1bfaee156282809a91a252fab49e85cc479557bab13f61414a1a7d17fd0072e0301a019858874e346991c7dcbf05e293f73655e077042fcd06a82b4f193c721b8f1f841bd945f7dbbe3262b8aac8b83dc4e7c14b492acae4d272c8154bb47291ea8535141f3ad7952c463d1f8a249978139680a78174d31472fa98a27ae872008d3414fafb12500034dcaabe0d77a2b08f07946cb16031855bc3ec3687766191dedba8c11ab85edad7ac8fe8b4e44e6ded07b2aa4f2bb0fecd860dcfc2f4724332342bc62362fd22a32cb2b17ec2097ff9ccb0b2af60cbbf2fb39042e9a472aadd8ebc00118afe8307267be88ef9a34345375eba8d64ea1933bd16f1360f43a1fa2d045e15a5c620adb5b4fb764bbbf16ffdb1b2cc2163b87de5b68f774372d07bee5d778fbbe3dcd6addfd20a9339d9935026cad25339a6ed1fa603d9263b73902c2c7c262edb983fa2a50a18e7c4b301f3b59e48eee3fd4ea6ba80877972b7255702058c35df7cde791179022543825abbf778fe5e25afc7666b948bd716ad810973a3518aaebcce792b4a4e3fc5ce31965d92f8ed01eba41f84f77f587280b76a80ab350fdee05fdc8b609db275cbe29495c4a1d6f783357103bdf01c725709c9ae451d7afd66ebb80866c5948409c7fe2b3c47809f42eadb7f3ed6cd61fb58f34ec8984ba8651ce6c943047c45a4ca860531e8b47cdae55736554ffe722aa4d960fcdcb46ff6c8b9424323254b8f891d5018fda41d88e831bba4eaeb720119606776dd24779b6e3c1281a7683e3c7ba6c49128edc488c108c2770298724efbf9c9cb81ff9fd0fb5b72e5da06d579b79391feeedfe56f3d68909677367260fd5f7f694d991c935d9698016efef6e00e05be9d0b518bed19d709d7576f62e71643015c6e15f3d32b7c87d6642af62dd7abf7659d0f385667e849d18d92721e064dcdf23b7c9dac041389aac57e1d05fd47925baf0da28815e072439c8c728a23cb73479307f1b69cde8d20d25e23154f144d66cef36b644306b0de2db5727ab3679df2a1d85e23e61a357f929ff86776e06cb5bb5ce9b147cdf1217d4972ccb141a26b6bd049517f6b16f58449c964a0e11429e26e0dcaf1f62a390e5b6fb82be1350b2c8cf71356936338509d46f642180a9dd39e02cec9e9d2d6f4541dcbc2ab4b93801040bd378ca1c1b50a01ddf1676b54e8196699e62ff1c1747472dde46a7315f6363cfe24d65a695ab89b2b6cf523f5256ac1a3df62447ed7f472272f80730c1c3da59927fa45e5bf351ad0bc7ee735fcc867deeac63cd774264bdf317f2772095993b839992ea3835b49002d9388f729cd50de62a62d164c1a662b3ccffac649a1cb6452acf0a604dc35d2c21486a82311743b54f3757535147242379cb1320869f695795cbe9575af163f19fba26becbdaa3528aa941c716069335f670611f1871b77b9f89864bc289d877a3e04e2bfafc1d34d911f0d51c2398577dc9c8443c0842d5d1a3aed05e7713fefa93d0059f93b512e5e6562c48d4e82a2570c199f49daa33f65ca1b6b25deba18a71cac43ae138418c6628e853a10ea99a006c1820e7f93de530739725df98a9dd4fa2cb4c80b5d937c46d61cfd724bef2cf316124bc6c8b17495c6eb47f969268d879f7447b36804850993f8974a515074ecb2d9b8dc774dd8edeec0fb0e4f1a5687fb770e600c543e6eb004e21ad7f3d0a4daa003fe5f7323603b32ae544e1ea06356b1a70e784f8256ecfec8274849ba978d315ac0a8a601cf6130d16377b8917e782e92f8f75e97052ae58401f16a0c0e6f7303de1cfc845b842975fcc89b7e065594f1f80ec6f87807399e72070a4632e96b0601e49f5d712f7f72c26c4c3d8b9b5353cd31e592901012e9723e117bb19d4ce9c1bd8654d11827b4f0bd1f57447f501f3f23b1a2012e828f42cfa1199c415ceeea5dc257e879cf18925b00b9f89fbe835f6735617c31a0236b81bddf628dbab8f5da8d17c3a63d7c8b0897d44898008aa32ddbef5736716972faa83b2c144f656d78b7b76657d625c0c93acce8c557f06158511884aee3df72669c08602f5e4833cec5bd98988a40c7ac143940eab4bbb48865d3ffa5cb766da9cabc6c284162e2942b8555da7adc996f691743eba6e8d5fa4818f48b1b5722645ad2a4bb9f80f0ac591c08c738c5f5b31763a2c3385fc484573d0e26e6aa42fb09312bf9f8a8b261a4e1509ba565c614d5b7b355e1c4ad1be3642755235672cb9cebfbb4019a7025930bcce398559c024766b81167309fdeaec86a8c374e72a3a0ee2bc5b8bda37ae44e82d907f952395d26a4085fcad6e236e22714faee72ef70c4722cd76f81b26e1a1c3a87c391b36e0becbeab4e3a0547ac0c42c926723e2aa12d33f6078c29ee84006d95dd1861a384e149fa5976121c959581319f5b1dc06b67bd1ca086b154273fabf5df146cb4907a10acf0cfd37ee53c1ab8f72d68630d648de862f0870041514c12eb9acf2cf5af5e5302f6666ab9c678852e72c2aa55d144bd5bd9fb7169ad243584d12f0e9868c1d271b540f06c9f8c66ab728c5a8ba6c6a4cc82b7fd9009e18c6c082a6c92d2c4442fc0b1a9cde253336372bf05d8ffb0e59535c4c883860a6f2e20740feab72c469e03186839c090901672108bdf24957439ddfd701f2ff3faf6f0a8af03462088d624e455edb3fa5e9872297143ee9b604a85fc745f94c7e292d37d824ce63b1c4390651e538035ea0c72016e8444f64f4fe19e0ff9fe625f4d655e5a5adcedfabf3a0b9917f8e675fb72310392888dc264bfdf294bec39ee2c5900c5039d974f15828b5ad071b469b619d5e1aa8a585e8a4309bcac0086e4ad81c2fad929bdfd0e876f72711cd9f119195f52cf41dbf115d09b4c027365418e2ee1fd8a1ab38ab36ca8ebb0faad1c167237dda654ec7b8d449752375cfea135fb11851c57b58fcae4b0b13829b1302a726750861b76fcd2ed72de22df738008673f9cbfd680fd74657d183d73af9a41720a2666b3b59e154a132a316c49433dc5f6af32fa7ba17d1c2075da192e8d6114879c9d2ad32356e53a0c11948d117a8ec2bb332680f8a2b4fe7fdf92349cf472995c1d6670b4a77f00893282a9218adaa89f092036f68dfa3b84d0f063c8f9727ee4d39d7c83d0261f44f8ae7ebf97e9e8693fd2fc6689c9d6f3eea82ed67272e101fc87e530fe7507bf66b3917abb1d6ecd35f8904c2e381f2b34a96ec96c2cf65d0a4bd2a8ab24c4e080f40e066fb1c66b95e957a90c7ad4876c460035ed362daecd5d7d2b41cb240ce1c6d07a00658ff3004b1d9cbe9b385c4f23175154112e6ba0b91986d94c08a1bd5b3f7bef8ad42b4009de7be5be4e9f38c58523a32c7c8b4474ac9c43e2b35b40735565426a5c4095d11d77a50d748caa10a9401772c467a65ca03a77cc3f012b70eae2cadf3b442f62f89054b6fef95a8464f7062fa50f7fd7f858ade1bd6b7fd442f360bd64c4715fcb344dccaf27d0d73f8c4f286b9dab64c40c1b6974a8166b3d09fbdf2a391ea4892dfec55c0638ea627085726f690d1e1fe26e82260c4a6c7ce28448007053b93d9381e7e1814b289df32562d5608f00e541662342f40b738085c1325f7965093ed20e603c0ed3d7e92172723ba7b92ccfb6d2a2715697b0d8c20caf3c1466f4a5f766fffe7845b7e018674d7ab455afbceb9d2382f5abb1f4c3d32cd4a2799b9e0cb775bfea7d94710f3d0465a1afa28e764132335eb3f927eed26f031c69edd73c22572f37bd5f406575724f48908dea7a9edc905a48786aa5952a9728c5e92c225404aeb85ab10faba47224310474dd47278fe5aebb10fde61bee0820a322c86d213b6d297d151acba710710b1346b2f785fb1bf9af20543fd1c4d2634e6bb66d763073cfa7ade70fab7004668e610f14ae9221de6963ca7f5142be63169491c854cfe8de8a7d92e20a163e3440ff338d278b08587a1d1a412d269734868e6e51ab5921b42f5a6d21d15b5ecfd7723a4b4cc843677c02993144c171dda2a4c39acd44e08efe37db387c72ecca5f014896c5cf4d5621bacd52f7cfbb66be2c8ff51d63a33960e0c8b55b224680df6f8a59cedb741d7c557ead152ed626a587f5d265b74ebe8615f7bf99723b067d93caf7b1ea35218bd21c4d2ef0c480d50ebf6e1160e1828d503ea9902043b2e8d40b7761672d239220834940d2607d96bb37671ee58dd149cb40812b63ab6a4be10c415eac73e3ef1aa02dfaa83e680fffbd8e8d9c22424aa20db2077238102e9557647b120faadf517716f928001bea766bc3ff9ed359e5b7fc040263ba21fc42c33246e5aee2269d5997b6fb595beb91141e3ea9a641f4ad53db65728c9b5ae2fc85bfd602fc1ce49c9c40e8c46f703c43f4eff59375747521ab8f72dd96f5c2e98785861a855cf7731b5159dd2c9995654cbc9a545f85cf299721721898c3dbea82cd737193406ff94fdb815252ee47bccc821cca6cd30eb017be34c3e3fa5f2d5b1759a1b942788c96cce00ea2936f8b15470fdb604f656ba10372290ba5144c39568df73277fa9ab74b1e7ea027ef0cc868c7451c20e397da2340a5593d0d31259b45faa369ebbc061ea61b3a7d0c68f281981dbeb18159ce00721bb35c5f2e8ff315b34f8eabeb6b7d600b8ecd530dad84a7fd4ba3df8c500872d0b8e9b1d9f6dea54035fd7113eea96357fd45bfa39d5e652819196f1863857220aaf2c3262d714894bffdb53e142eb7f01e0352cefa3b55b70c10ba1f989a5cb931b6e49f3c0d336bc6610fde7c3f8a20628c0f84a456b4f40be7272deb1942b3705ea4d128e2f3aa9bdcc03e3092a78e97893600d0abf8ac2582a5e7fadf72774d76d1afed03c53b0a22714f837712b44c85486ff506868ab9c240ca420818c8e889e6cc1017ba59a95204c33a91fc0da1eddde8b6e304a35e797536e2db445c4e5ced8a348a409679e09314e03319539cce09d9d2aaf77f966559cbed7c7291ce88f7ca838a3add20eabcec32647cf02d0457f59d4240e8e970fd862a235482e55c9e6169a83b258abd9c49719c42f0575c96850b3169fbbc36db711c5572766414bd3629eb9d1de4941c3bbd58bf16800058274136c39e54fc7e65556b006b28e0260a81bbe2e6247a28ed85baa0a41d91cb8f0b3cd2592b2f874e798872fe96b012884421606d617ed0da440d5021a5b3d4b0c5ecc100b49f5b87f20672541dbbab72e8e940f7d00bed9b706b85cdfe5a4306655bcc67a2283e7fd34c72a9c5e33257c4d713d973e88376259d2ab4b5620aa33ef7a8a0a467ab5c17444fa57a4f178bb051308c16ddbc1cc0581911c055a9a8ef0836843b6982a23c0b724d40249f96f3b203975aa6754ce5f33b14b13cb41dac0af71ab34601582c5a72c59bff6a2e9cc3302350c52e561f82eebc181354c1122f3e4b6f94af2ab2502ad2febe69dd71d4fc0760dfc0c66d44d6a21b213a4ba29aa4f362296062a84972c789a146d21145acc77d6dd37f85104ffeeec7c58384e2188287d642335be1723be37e21072b3256894b64bf376f536a973d0f196771cc39a6f8bd6107945668bfb74a24fd9f75500a5678d6e1cbf371f57b2c7ddd0fba267e15bc4f450bf872192e1a60ed500f590987136105de7b57482d8aaeafb593aca6ea017743666172ac60a728b575f8206eadd6b11faf6b701e658316d302d53dbcd977929f077f18a3adbbee05b72497eaaf637c6c42c99bf3330bfe63f578a770dc79a0d638b272dc6751a86f07e36d277c237b027c34f470efb1e3366107b2d0b2f14d4a132c728fc88e0dc3940ba0056750be9a88d3a870b2c80eab0ed11b702ecf99ded02472849cd53a26e10e920c8d7daa2fb467f1e066a024f5e9e3dd6e91b46a77836a72b8a7e3640d271af8e3dbad7097f68fe7888b3effa3cdc6d6f1bf404a6795dc72c9a0ed78e994ad047537ac9a8e650f81debc771222d5e44e1740a5f79a2b3939591dbaa766ecb7f8ed1c118f703c98ee0209aaad7a4c631f70bef3c38f9b517240291170548b6faa4629cff4d88069c3373e2bd7590db0bfa6b04e9cba94ce50b61859148736a635487ec096417033824388aea391ffa4c84e80d21abebc78387da3784a0c81004301e66aa1d8080a3a9da1a43dde5cd10d171f178fc78fa572416ef299113395afaec8af7423f48b0a7f78e45322d8bfd09da7da1a6c072231bb991ca162d1ec066f19abad6deb27b3358882e21cba1c872084abde245b1472c8781ef7c451822451cc040260a337b94dd2134b5f6397fbd0e162b042dace68a067119b4403d7d0caddcc494b1625370fd40c266396f7569200380461375f72f16f4d12e8adba9176128dbbdd29468dfd6660fa29990901f22a8dbe5e61c272920bdad6dc0735351c4fb6869edccab01a2f9d565d1e69821baf4c87c8e9337213b08ee323e63d19ff715c0da38888a0e23cc954c49dc193ea16781c71c56572689db1120f42449c86ea8e1e0706264c19112f4c4a3db2a6a882c3bc1474e06d0880997ed7ef93f642ef9e02cbad3a279c939c286bee6135edff8d4aa668676fefe7e0575572cdbbc2df2ce84458fc7a8ea578207285c1ac949610e378372740e9b4ae66e84c4bec62c4d542ce3fbefad0f7e2d15caff8af9565b817e43926721bfcb963e58dc2ae2ce63ecd6ddfd2f2744e9031130e379e06c8607be5d4e7106d4dcb8909a4bc84e76a61e727e5f8b7b2d1c77f00b1cfdb1d4641850b11be58f0df81cfc58900f7de607a63eab76ff9970dfb6dcfb8496604ff440cd69e5972077f25f5ed5a061191e8959fbd02d601ea185e8b4c490f8b59db6a5b17f4577131cb3d8102cfcf658a160bdd49765fcd635bbc709970c37a4d82472de9c65b59253767dd265dabd6309e94f3aad2093101db23de39a9e4cd0833bb504da374296e4522149bcc1a468d7ce6c06da587650efeb9e039f94f4ceb3d0f84d49e89729b4bddff30d112c193cbf04f8f57279cd706e572ddc67dadeab87eafa402c772798704bd9834c862842e6ce64ae4d3bef1721e8f07e111f0456addcfe1ed2123bd5795ec20494bfb819bb007c1f5579f44402a4e4005a3a3731dbfd269acb6213cb043358d98f4495d713b7f78337e795acc108f59893f4c10f6a000fe541d23649b7043716adce9016dba262888b3c863704390192d61d24ce1fcdc0dceb03683ad8d61518ebc566f8b6b037445511d03e112caa9af031812c5869f5d99a3729a070f979781b9e0a6a3b626ac49387e77d2b16286741dd6752de789dfaf5f2926d168585fb19be16dc8488d5db29f07f5fee86dfb36b5e10e0e1f0885744b59ca33a4a13fa3df4706ddc7ef6c78f5ce894ca6807fb9ee8f99a87304192e4e4f59ff0834ab8df67f1c5e869289320ccd95c0929aa065a5f3271de7e3b3fb9a284fbb82ac8c85dee38dd80dfde10da09ea0852ef620a9084a6e48626c3bb28e53dabb7448695700173b6ad508495ef94cc810ecaa12ff70df856f48e96ee64037f5a45d3ee9b11b84f8ef72e1fc49fdc0929ac64a120fbdb9db499a66e5166c1672431709f7e2e3b413d12b83fca49418b5e1b83060171a8483556c137aa0b928035b218fb26dbcaaf841c3ea62757a2812da91deda3c8b830fd03a21903133631979403a8c031a3be9361151b66ee3afe1ccdfc15e365b267118085280518972d9fc6c2de62b67c25fbc69a1792719c9fbc3cd7ac675f84e2f712b4adc4030070f813df058f590a8cb1c9ad89724f58742e28ee2e0531c14937e48ea6e871b72687564849de0e988c984070c01a0c48c99e84a2d2bc33c1c8dadaa36d757bd721b984dee103b0a3d0d25348f73f5fe5aed8080ed6a8ceb56a90cb68c3ecf5f72d1a10a2ecea95e367f0b2b123c38afe3b3d72bae964f994d70942123595a26208d3958426f44295a83abe625bc7933ac75ac47b7cc3b1e4b4fd0f089532ed5725403aeb58a4a663efcc3bef2b06b15db4d221cc53d4a7d857db5d842b29c8072d3fc0336e3a102ac2f21456a9c4b9a7a66127bbe1fb985c4dad398c369029c72fac7552d91d8712ae34f8409ebe5699f4bb817c696559f388920b8d092c6d37213a18e9adf824f63a25b1880c75aeb214f83ede3505892617a4e627f7da44228a9caad379e153a8e7cf3c5f380c757dbd01163a69c69103cab1333fe753ded28a500bf300845fc6cbbed4bb6d02dc2e4e9d4217715f133e15815513042176a65a50b26b8f2ef6cce21a2609efe41bc7c2ca5c65fc69607e17ea19c1fb762f3729de281bacfb8ab91fdfbefcfa436db5fc1a441da3ee0b46104c5fdaf7c306c72c5036559a433a9f6a2b5d69073f0b89460b46224a41e7d647eea7ed8fe63ca288a218ce9b8b04339b29a623d35a45dfa14ecf793208c160c6453bef1772d7372af654501dc5253bdf5b948cddae933c166f737783049b5b1379892f03cace8721fe8694cc639fc1b37b48eefd52346d376e6dd67f067b82b5adb4320ca59f06cc923624a2a57348de3175fd20f0aff0ac4e3fd32f57513bf8edcafa05a93677251f6b3a6431614f1adb1cc990d4892a339beccd75ac28112ea2dd43c814a967256d213a1ed4b01cd6b55670a29ed6170b0fa96e42f2b78427ae3fa7e6bd15c59dd3e76b5310c47c8e0ab9f2c142be540879a59b58ee33606e446e134a3706472e045cf3d38978604e47f4995de49e3a3ca0f699bd76beb6a47fe3a799d444572ec2748e07fff32705b50d0b4d0d008ada8a4e2584a9a228c3773157c7eaf6847cfeac095318c37b22485163b11a112d149eb71fdec05d60212bdd522d956a372b28d23f41f42f2298e815fa65abe03331ab7789957ba0fcc4f185741b0845e673b153147a59e71fd4006bfc794decc09eba164b8252398431d12b8f451eea372026cc47b0f780aabf7f5476934bcff808bbaf54bab69173503d0c1814a6eef30b17d4b04e5f07211ecc7a3435dbbfb9ef37a4c0baa0944e865b7050a81c8307248b4ee0d66c59b1c9a5036f71fd3f5cb9dd6d730bb6d153889167add6378a53bf44a0e9831b64f5b90f1f4820a171997d67c4b796b60768e2ca4e6755acadb724b6d72ae59cbc41db89556710545c9f28317bd4e497a6392c64111296452cf5295994df5aa9dceb1f99627541612cad178f537201ebd842cc2c37f8c0e74dc72d65e52fc2277737bbf148cb6e71847ea2d53a7b76b032120673523f65008a8710369673dd861405395f7a95880db9b7c894c2f7f81d875eb7395e4934febf747864abe68ad6173777d9338d447146b0f66211bfdd942eb087796a35e7e142772615c2f89d8fc87a36de57b4b17517b0486a338427b2ab8b818cd851755400172f413278d58d0eb034d95de62c9732fbb5ea481a9b3ba0b96aee73065eac31972d11017cbfd184e9d9bae8af6d9860bd1bcd4c85b5deb71051c08bafee5c120726f192660caf17e4eace1ce3cd26e3de957118438d71148dd612d7e6ee672834333edbe6d17baab820e084c400a8573fa5c8acfbe416f6cc127597e2bb7e99772234b2a37b65b192c1f3ece1abe842d557485d756e1a0dbae12525673c611b56175b922551d7520625e4b482e900c21ed50ddb1835ea46f63c9ecfef18bdf40724be1b4c87898b1fae796dee3d1197a4f69f64330e18802ace479798149e4fd72eee99883772dfacd6ef15b44675623fae9c0b9f581246083b46fea27b534c63cbee0aa4e136dac5c028c2ac2ced8882769e66e037b522cd79b7f85146598e5725f43e9c7576b1b4bd3e2cbe9a4d6c82424ccf7a8fc59a00b2bc594aa1a5410723156679e4d639535e890c3d420da85ed4d007398c22d2334aa8a11d121e34b723fd6bf893097b0549155320428c29a32c40fc62fbe9f3023a3e1769f614a11721425dfaf7ddaed4161b514455354355653347b72caefa9529f227ebbaa712b72f81d369621d7e60c6b38fb8408161cb7b82658bbeec6bc987b54ca8a5bde8572f41666746098b101bd61c01e9343ee6037704dac6869ecbdd2708d8cf6e81d5eee48b239fd06e014bd47ad641a634e66950c19a09aeed93ccfe5b7f9d87fe372e93ab552e37047b0d51616637663b275959867c028b0e01875b7b2857ac04972250144be056c984d7ccd17581d8d8680acce70eeafc50e695a1bfd3513db3b54d7d76228f4d2c47edc5845aa4403c0b3d951e01e0d7daf062203834b28a9d72e564212ac3a66c45f5f5df06ddedd4f895fcedb8cc62ede13349ec44cebee4a030c89c4a3b627edd6654154302517d2ddbb6423549588b1b99dfb81a085b29461a5e5d02c759dc929edb921ed61307f03f09a7bd5cc928ccf34b27fab8e3b89729952ce6368adbdab5b432b869b50de80990ffbc4b42ce9ab91302849a97c79722f8c9b759b3a022af41f1c80200c8979fce3602314f998491571f99cd584c01e25f6bed7b2b76b693d4a510011e5a0afe0c66ed74281b257b543060e9bea54725f666bdae6bb5893e35cec8d7da187b3100b6dae7eecb97f526ad41a3bc4be72f9523497685fd0441f0ee3ecb1abf0e4ac6f2b0f4d4db772ce960a9fcc73e0468d99927d4b0bd651f7465309a475b40db5db94da9e93a0d770b27dfbeeebc35b4ac4fb9f15fdc367f236c28360a5a36e3e2d6ed7915beb7fb982b75d32cdd852ece6614c783a67c3283fed169ae1427384db1331d1cc020571f5f1e23099f27282940ab8644e4646335b12e6829cdb585012b0d87e06e1b08dc9591e19f32d619e08f1b5f818ae09c84aecbfa7b7739885e19b49663c083bb31e6dde7edb2a09bc5d3eb490d181a5c4319c10a08801d30ae7314fb97e21b457c6c5a52b38cd0531ac9c502bb73daccb12156956b7f62e22f7d32886503f7f35ca6504c3a4e50f470c7e42bd9a58d1bf08b3260f9145ad476e54819ccbbe6821d0f8785873ed02b5888355c00237717005730d3867f4ac52985cce19539dcd4bce201bcb270672b5a2cbee1383a86cc730ffde3db9b95d0ad28edc45cb1ee1d1a51f3d3df1fa536db77322b91f1cd078769902b30b2a783996dd9a635417a22dece6336a2a19726b989da6b09f601e4538fa971bdce911ee04e5afbd1a4272442c86bc32ede172f92c2d74fc782d83e1a1c7930c7722859cd666a57fdd2053a6d1081559ec3a722708efa96c67b7e2e91493f9b9ae4e6ac20174e02a2a6e63683a9608d74a4a7289096b4c38c2947fecdcd7b6ed7bbebbf0aaa6fc284f2a9bcd2e919eafa2af697ef051f4c5eb6d3ff2a0bf67e395de1b2cd10d334c2658c138bae77228494f0af7c94181618ca9a1ed24721c03d4bfeeb5dd65fef84df70c78511fc850fc2672f034b00b0f3e4a8d5d344e0cf5148df886b016cdd304ce9d75452892e79b2772ec8bf2d21b240e027b8b5354cd0e4bb980b502f481cefa43e8ab195687b610724dbd0bc7646064ebb7573fb7f6bec5befdeb5c308b980c7808a3e4b62fd1373b20a5aa40020a08f4713b79ad4bb512aa44170e1ab5fbc0f70a397d719273163408137c693d6c2d239ef89ba57daf28d8d072085d292bb1c99c38537930c88d1faf70064eaad8423faf5dd02fbf2f42a768486536e2318d248b117783cf69607250b81d20ce6b048de904a9b53d0b45d69ecfd32c5fca7e1466f353ef7b42c95ece88d841eb6d9a5429ea48724be642dd8af5346719062cda3189d5e15d93e2699a9d477276a989370e1a9b5667de4a2b9449437eab70afad0dce06f73e1979727d8d7f6291db5d7b6e457428a89eaae961a191bcb5eae686c2851677c9d71538a1875e8f2ddedcde56e7bb71b1ca90a938282bbb03c373f4e7501c908ca8a571cd946ccdb81945696965dc56e31c709e16c1cc2c7ebd0f83b7d32ed2a675f2720f7293dcf85af5ef1409fc1a9e76cd238babaee7ae1d6e1934948d44a7824f2834d907ef987ad8c451b0af422ceecfa448e1c623509adcd0f20b0e19634cce726110804dd29bbea9a9e0c8a99213794c9e8eb2162166ce065b8b1fe48b6935725e55ace028e589e7ae3f0c09e07762646682433a7bccc31ca47a844e5d0e4345b0f902013926fc2d6d7fa8c9e9beccf06a280645e1887b298f406967800d275e2bdbc473c96b4d54e0c76665e35b84db40414fc39b72caba89f16e425bc77e20aaf66ae060da13f781598b01bb605341531a44a8e83a5eaecb648b1233759b72dd4a409bdfb6a5669fa51c3190f6b7363c8e5a7534cb15f4d7055e0159db5d723a67329a97061932aca2f3a8b809fd1074c316b94ab0b0fa7ef7413673c0cc598496457c86c7e92b96dbd00f4c3a241b5bd703e7cc3d84fb6f5b6fd66813d0720a2563b1c0fe4b7838fcf8ef5e60a6464511a6cd71b936eba692a24071b80672844ea2df4a3d2182e280eb0e8a845dae9e2c79dae0088e45221e4896dd4be47202222a24ca27c58b1526649945ac1111e333234bbd8b83cde90719c654638403faa136c7ac0e800108ce1df71e063bda33e5870f757ccbd0205d47295534ba7207ddb2d2127651845bbaf428f0d15aa08322482d91a2e619ac866aa2bda9fb721911dc09c2486828b86217896fc137b47779ffb5a6bc82c6d3ac0ea5c1a288726cf9c99e786b78198fd4de52ffa1065bdd110166893cff08684a24a9eaf62572d1f5557b3aa54dd66a31dd1dcc87e1ed420d175735321bc73c053a17b0396a72894691367d499756e78d3c0b39ca1b93792af951011bcc5f39d2e3b9c69f6724a1cbc7da2a789611391b7b7e27d0bbdf470f50b569e8c69cb4f30634e4161469fb36b71cb09e85623aff4dae7f027aebec21939fded6dfe28bec859b8b4454255939074e9a27c9c69c6c900a6c15ae51e7249f6fb60de1036ccaad432b2e0130c64c35128172982eea3e7f33e47af6be9fbdd0d05de35bb04a0eb969f6dead5779eb7dabff204669ede053c6613397ef544a3fbd13217ff72c21a88f618e6b72ba000eeef42c5a7e48f101e81fff71884b435905397fc96405b801caf7ff1d72394f576c58e3994d4b5213174b09db6f1d91bd796ff22613aa5e62d417754b283f63112603a7682f8227b875a19d36b68b6e1ebce92f26a21b2142b626432b729745dd8567d8e89bf3167360dd6cd33c85926d39904c17aef58983ca4d2a497224a3b9f1f425755bb5febdb5f0359285ee993d3d703d8a3f04f8df047c91027264a4dae6c618326eba8ae3416b1bbe6ce44ce3af591e4026c82a9db71ec98a0f45d0ca23882d6b6baf3496b289f9cfae2bd196fa7f5b15e15caa02a328794f582aaf78b8e99e2e2d033a27f37b081f9e1d3a7aa0c1535aec40d2285a02fcc072d1074622d87710892ed2baa6837b3d0d15c6fb4b1dc5509b7425c08546ed6b13419b93eeeca4d0848625567c4dc5c19de101fb45796329e028fb135218e0b572a84a3791701058836cf10d1dc97a1b15c76be53ccd947e589c6410bb06df6d72b1e0e55aa22bb2a736271d5b7e84f89ec1d08ae674146e53152af2ec92c5d237d52c41fb62714bd8eafb900f05e3b41422b6a90b86a3e44b01854a745b78b30acf33fed321b04d402367c1f42a15a640944a45e42caa811dab83216878b48c37a27e5b9025c231422db222f742d88f0198944bfbdd3c7dcae528bfe22fd0507243c18320340ca07f7887fd3cee4ab35c84ae521c3c3d7170999e947de6d23d6d6ea02301dd2e17ca8296cab75cf3c0d3b2f3a4fd505e39852b71055c42a58d725ab52fc160f15f484cfce3f1fad34128fa46335cb869a06337248e64117c711dd8337aab6d9f2b458f8fca04ca73b40f6bce303b6b9eb924bc75f7e2a11358634fa6a55a61863428dbf51162440b975564a406b37eae74c12cd8fa0f550c0958736debdd9acd5e0d5862b6a1df8a1e4fb4b2bab92bfb0c417ef8d06c880ed8726f6b37b636f0e2b23e5cbba83fb7f2eb236d00b062656ccad6dfee33f83d047289bca2722bc97b5e3943297250ad42446c857518ca3ebe914014b269135eee72dcb374852e6703f867b18d51aaab9f8fa3c7d89921b1087dd605bf583ab2d172daa27b2990a0f8e573ada053298b90a774832b97ee0fe09b982b25db15658e72131499b122a66b790bb54dfc33af85bcbf5d3a28311ce422adaa65ada6647b720ab91684e6fd1bf61c5e3f0f2afaa8f5f8610af26b1baa18b33511aec3c27c72786e1e57142786333c6a48bc7b46539c3a48ccf1384cbb2fa69012fc7d159a725028d4f9eaa9378af4fcf61b5ace72ba6992f9e55afddce98edb5ace0d0ca972d293e0941f64d770e733abf9053862580984d0f177b0dec0623a2c170e5d28726688bdfb29c51b31168b5c6d8976e591d2af0fd3be77d325bf6ce8d752267d722ea2bd01bb4c26d226a0aaafd8044a75d5667e7f79135f5804c2f87ba44775460f919dd15ca603303beff82d31972d4cb1576fe9e16fc725db9e9bfd30a769724bc5e068d640e5462152ddab1e32926a0806c374ea7424fdfd2ef9ed8ae21a1feedf8232951a0ed0946efa97b1fbb4ef250345f31ff0b2c89cbd0b87886bec723cd6964ebef26940e0e5a60887baf34672c126fbf963c51859e7f121a486be725613fad7ae3e5f0176029cb1fa80d2238ce56d72877e6ecdecb480d30804401165bc74e6cc4b9ee09582a2d562ee920ef6263c116f89185a82c9877c9942344ec63bf851f91fd71e157fc152e983e40e612a3dd18d3fef911389e2d2308970094cdb823723840a3aeb7bf29d0faaa79d953b9772123324c030661907bd8a1172b5ab41540200ac9b17ff702024618c7c446e1e7f231524a431ba13cc2567292f8b35176f6aab131a1f1d2fc8e24c8961825bd022363f9c1c8e4a86eac8d44b727235ffa1c6e138a14bcca5d36168cd640d3eacf55d8b44d37a1d243c57995f6154e145f501baebaf0e527d1184c56bb781cc1f5ded2114d4ba42794b34b10d72581090067783047e1b3e7c91f36d831053cc18c0d6812ecf10e48b7ca2adb672cd3012dc7084eac76ee0d64e4f7c47a2dda0c5b5c3f23ef6f6a656b0b7ba184c061c25db92231590427a2041cb73da6a8b6a1e9e12cb7ddd35fd1add53d4460ece638e1876ecc8e7e17fa109f5569a2509302be72b194aeb67ab350dbb0de57260ce7d39703cd0be8c9274a67b2971b23441e015c793dce29b208955ba146d374bbb7b9e2dae4ee166f127be8eb25ce76cb698ac46a7ee458176f08a2758a329c6e55702740a1a8ebc6a0bfd8f002b2bf8ae730d30cdcdc242ecffdc7860366066f6d8c10897d1979fe0b1d6f7a358ae3268dafc6d74dd96f05c6bc506ff1d72926df2e1173ef8fed14de5de352675d18931e5ed012d8e46da0b6b2cc3b8fc72a3c31b642e310c28396191c3c48c3b6bd68a4fcc2a4f51a4b2cc6df4c5763d728c92c045ac97e2b3b9b706e7a1989c6c8921e4d984b1dc77fae02a3a531c4d487deef2339c9a9071651ff5dde2eefc808f30528e568d59b334990272d4748d04887f87048226d33e0ca92bdc30ce60396febcbeec7c3073f03b6c00d207efc56d2515fe96d3c86e4ea030cdc7ab09d93c7b0c067bf9bcf15423ab31d047c9f292ca406fde04accf3f75a148823b0786cefb016c81abab8fc9cd76ff6edfd06724a59ba8ade742e4d0cce20e7021a8994b3f2ff605e69123df8e0d9de86591972b41a6714dc595eb3a598a63c3c0b877595dfb2ed08ae528d2556b62f50f75672a7f3b186f567b0cc2b0276bc6c650ee5cafc874e74b99e19d350cf0c9d6b1b725701cff9b6865a740d3c6d4971721714a81f85a959a752bf6ea36cb07423a43026adb6fadee7ab5eeb1585be05bb936be3940c03e3cf08b160d8b8820ef35472ad09d37f649b113589c3029793f37abace59e745bf0320379dc08d9420d47e456350b67b9ce2e0dfd39efa180519f85f54657ea3439ebb039fc6155729885d7264d33c6e5707aa00252286ab7f4c63acf33c99bddaf7855a1ec3eb80328e557034eb54dcbcd9165a3d71310b37546683c8a9f245d5e179eda0f698f64a907372012a7789e8ec4a1e78098e2ef6e9991f56ded4c37c68470c90571e090e69347245ef0e94c7de884de10352a4a4bd61521cb1ea9330cdabb797f3b14d29c1db720abfb47c9b3919c2a84968b71b1f99d9f45cd5f5adeebf85b6582979d823a21180c965041a910d996af3bbabf5bb5db1e4fe311c6ed9d40c9e56514c4bb16d72062e1c70405869528cbb565eba5b78b6139412fdce0dcb7d5a47fd7772aada72d0f2b2490be0c3efe7321cccfb570d50049d167be80ddab34571acbd1c843838bba94607dd4b934024ec85aa7ecc471c6c0d07169161144380d6aed3d75e4567ac70b2a8965bd77b1553228f96f27f6bffb1dc0d89aec7845a31acf7b222de727c881f1d833ab16990f737040de220e2d0be9d5e44c5019a7c69d03158c138720190ea6f4e4f04963d6333579a89f74e207dfa93622284a8ada8ce35aa5d0972b89a33f191c57e135b4528ef93f8dd72386eea74ab5738b576a2735bea3fb3724865259931a93ada4ebab8e80d7d3115219c9b68eff9df59e5e0390e3e439972f99fce6c1c085a76778d5659042facf3ca4ebd2e2a47dde8d560a4b9762f5172ae2116c3aea3143300fd501cdfe4195c5880a776a4c87ac46841f57491937854cf74f6bf4920a6b3655cc155d67fe779f548c858770619eb4ed877b798b23a3fce2e9c60a0bc22be93f0c247a919572a20ff51a8fdfda21c5d1456fc2f8a331bed0b6e826f5dcb0158ec54a0b8af5e26b405b4607dfdcb2b24f350dc230e9d72e2fa3d0926130173d280a17ffe88b184a5ba9f7a911ce7065b0e303b4218110ab6e703ef4fd175cfa845c565898f3af5e2bdac59e9bbd9911f1df00810b03772221649f78d9f739bd865875fdd8188896175a87235857f1dad2ecf4ec302e852b57b5d90f53d15314a04827dc3b5e55165a3b948ac910f7a94287cfb12b55f72bfd1312307c06927190ca4f0eea256deb403ae3a4714a1a24b0ea540afec925b9f158caeb9d1c5f3fd8fc4b28ef8f16b22b4b24f0ebc22ca356aaf0959c21a72e3a9614fd8caa9163493740e7bcd4df368a6ac2fac8f620c317afa1a1c8a430b96243ed6f62ff2c8bda12e393122d6c8a900136613b857a47828aed047559372f839fe052ab2e7024f051f85e3311b5fd5c3cb11820e2f3cdf6f2fe04656397278c6fd3ffa53be54b7e7d2bcaa44437a06ff6f765b6209ee2a2dddd7baa65c1f50ab4b26a27a51f3553bd7ac22031fbde398d4a0207e3f13a2ebaf840d9e1572bc14ecbc6524af4d4baae22fcfb7adca2f659a3e2922ea67f964282de49bc672470006b57f374ff2a8ab885c27687e34176c94d6010ba355d25615ff27ba364ad08c34778ee9f7c24c83148b2882b6e08a4bec9737328897455b6aab0c8c247217f870aa97c2e4e08c2e87a17bc3c95500694ed4a9201cf788afe9552f5420077a4f35db701db9a62d351c439b147d3e0bfef79e829b5ef41eea5f7d333f2772f351321a8ad878e91388dccbc1104d012679597ce7444fbccca816a90fb28055d6d5a5f7af13b5ca8f777d5412d832355a9f339bdb56a181d1e281a631d95b729fde48701bd65e8dc853176d41f71a60bc04ebb8e1620bafef35d3382a170e62d7602858b92be60952cdd46e7933c7086ebc4d764d3c5a78344b6886582fda147cb5885cc57ab4309061c50f1898e8b0d010380cf206c7a7fbe45859ae15c4336b6e3e47cee19823ab27adc8f0ea4901a584bf950a835b0f6733838fa310527295cb1a0c0e4b19301d425357760f5a52141ec4b6a54feb7e56d36da69e6fa272e5d266e059e8014b603dff23a99e715b567637385bbb8f65131fdf18d072d56d7df762302acecb1d64feb22dbd5dfe6e8574751b87b112f1e4c6eacb525dbe7219980aab2549c03bba39ec05002b87dda917edbbcedc0e09fb9d1b5d646de6497b933c7770393e714e8355da5ed5d54b5da621e211dadf327728998529dc802f6149c09e6d5e1ab571bc3ff0410a7a217e00c4c258de10306f9c86501df5306e07b8c9747570dd3dcc14527c1a8c3abe3209b33d2acc7e4c9c9ac742b428914bd182a378e433a5f65e081c716d84cfd656ce5f64ee7bbc1b284ddf75f8ebb01e541869c3d7a0077258de5c5c8dc0c0f8c11c311d25a0981ea36b409b5281db0137ad32bd85f656fe43c18ce32123b154fe3fdb0447141223594691950f82d4721fba83d1479bd4f21f4ce936fc6e92e78944d288b36d3c9b742f22a23cf91b724cd7a3e7c3144c6ed4bc51b6c949b682431cc6e611a2bb655266f554a31c06723f7c39373418e458fa97846105b638911d0d86449d84c7dc05799abf7160cd56d04886bb75275ad56317d383e5f1aff993a196ab188ac941ce695f40da184b61da676bdffb82325f86c55384b0c6efdad8c880ee8137077354c915f93bfcf07271f730df743984a45389655a260973a602b33cb9b3225fb7f969d8f5f56735725426640f0034ccc4ffa29689374bd6a54446f36ac7112fe103c2d57ebdedfe72c919a8f7898bec24152d23bc73ed1ccb644d0df7adc84fb5bc4b53ed438e7672b5abd6e7150193628ab0d7d52ec9f1f0f89105dc2a7d7ab764758be76eb29d1c5b4c4f1e3b8290fa4d3d44c1096e25b2329290af9c4ca7fc770aa433817ae50e8f9c06c903438989853892d57178c0616ffe20e49bcad16a09263ce91c91c47236cff8b1a980f750c109f30eae7f23353026f346dbf271614094fbb52906a272f736724ea4e4ac7b3510aed62683d6b51e765b65a7818290a569bbc4fd7ad072ec1c931525a2030b5180e8a3560b84ea61a1cd96101cb9a08c423afd68f8cd1b643bbb25a8f98b0f2222152c075772c408cc589f70ee31c88e12024bf56b03017c58b75b5dca11a68af1d8e94a601df51d4ae7f8800f741e6389ce15796b6772464e753c5971258e62f209104c87c750b5ae55f1eaca75c70d6890fddf5d2772a5b83c1d16ccd03a15f4bce8c23cb3161702c4434e96ff269b130631bbd23e72d644c87ee7c7fb863beca04d1887b30af7994c1b06c8c896174ab5409d62fb1e12d5765951cb191d51d6d0f540d944f83ef5d13f04b52103b51e8256f107f30b61468686ddaa13f409d4efa6f9a0c7f27fc47eecec2e84290f3e021cd3af816924d5a7209adf1989fdca3b8553dc87ff582bf71a0e453a081a15367661177829fb580fb73749b9944fc247af6d00e0e714e6826aa46f6960d95cb084c6502e601a192ab8069c8a972f6dcadd02cb913bb03a40273b155220a3c071b892af837216079c3d826f41b985ef1c2eca56f6d3454e0d11205e2941e0caeafa6779d572fca28b68cf7541dcd59b1984cc4405c9632fd5df69f69c273b9d1794bc52c50ff865ece89dedb9f6401a4c6923874f88d8d85e5dd9f3df2b899fd72c24b4893665420edc0f37b3afeaddca0df7e12415a5109a4afc965ec2dd909f3c15f61772d6147ab7504a4d38ea509a5d73f88bad2fd604cbda9ab08763ebe494c9d97372d562d3f7bb0126b7090d6d5a964cf82e13858cefb01ad2e2ee8f5aef68ce38720345d70866ddf6d2ecb7e82cefbb61fc55e3cceb936a7b03eb21f586d476cd72cf2770a0005814cda5ff6f871227a745aea18be62427f74df8adfb60c97157724017fbd39e09a776b41ebb30d818806a69e52cf4772675bb1116f42d4d1ed7726ba672bd030fd9080a748d04cc07e30aead92474c719ed6043be7812a310e54d31ce1b8b79be04498304107c4e73c01a16e990ab7b4f23d9df14eba63d33286ccdaa45db3771bcda1b8a51bb430641ace54c23886efc6dda3de41f9100ebf2726da08cba65438f24b0802b6347e87e8f97323ad2fe9460ac2b3e4373148c0672f7d8a51ad2459f401b97bb5a1fb1947252007a363765ed5d9423929addd7a838281206577bff578d94899bc8e9d657fcc59367ceb12f96f2f0f4d394a3620304871edb179642fc9c8f42503050f8fa610ec74076cbb4ccd4049acfda8a65c3106c9c02953d79ff063ef9599b3d75206bf04dad2352d46389185e0a77bfee72728d3a98a0e57b90e417b16b150ad95aeecd3d30175f160bd8addaffa18a5a892e490b78238d10035d38ca10cac9e8cff4bf1b62f179418ffdf37bdfb4e6dc9872a88909ece8545e432de68ca1c37d312837b39ca68000c5034106cb38d61dc772021350d2391c2016c08116019c8e168bc4e539babaedfa3758828f6f9bba6b72e523f41bdecf318cdd398d22de34c1ca449897e3978631160f6247dc83eac772448ad3c83048ed2538ef24906e99921fab15218643c422f8a32e00ddd02125726aff7cb121dc1b7100727101dcb3319e649295399c6c4da55eae2f6343a78f72c91b3131010ee56113bfff3641fb2495eb22c52ddeaba68f357ac7333188e172575dcf08765944280fb4992e13ffc869b0ca7e0e58c791296fdfbf50fa46ce2e9160bcd6b5766b4728777e4158351585e10e81c6c5a9baddb19bf6d0f68d007254b84d3cd05c3220f77fe0a4dff37caeb0ffcb0441fb6e6ffe9fdfb6d9c18e7289d0a1d9953526717fe823ace7ecc6c8cffd3114c28147105d85f271458b7972be011f389b748c424d92c9e3b54dc3b64c8d4fb21588b52ecf9a489ab21d2d3c5fd3ce7dfc053b727e542ac8f3ba72541e7ffbf9bd2ccc5890c17b395242da725ea5418caa391ef9f9bd267fa68ba62a2f2024141c4a5f012cc274d23771537248184749c6cac6f1db9d56b56f69740ed95d37e020f38df753fce3c03bc70e42c57cddb8e8458fb6d9dfeb50dd624ca7ee53497e36afd401e88c41a527a23872ae0ae57a85554446e562d6dcb1485214233dfcd93728793c7e16da11a2488c7270c27ce91b57d4723fd2b87de5d84cb8f88194a28b96cdd387b89421cb0e6c1a94a36a2e7b1d5b362b5f8379e0616788d063a6035e757163940a8a043f8e7f281fae55bdfe54e18964c391ee9935bc139c58d8961b141d04807b4007ec8d8156d8bc24255198e1fe220fe46bfd8a363d9a3d2f3755fbf6e805c2430c498d7572b8665b11757174ff018f71989a64feaa45bae967cbf0be6c7c430194af62dd72bd04c800480d8d1076122a9825cc2d3d32218baac804fff812f0e76a6d6170720d4b8fc7968cdab135775f6d5715595cc7153ac98f7ae57666e116eb091255191fd3d2beeb0e2781adce3f7adb423473809306b15fd525a7405d44412305e9678aa9213fe6e779c2525f8a7b61abe60c0a655656792c675a011f740396c65872b9445ad9f21d5d48563406235ec29b1fc4d1abdaa5c65c695bf01b526c85d072ea8573b394d3190851df6ac23b65f5c944b29095fe9720538b1858d8c408b27283ff0fe769beea08212c6ed8678b5f2febdbc3e311d75a5a50fbf70a71e99f72c9cd6a6450330c315a3a110f6831fa3dc28143a8c57663eb556e6cb07c69357271d2e6b849f130a498a4a7a91d6bbdc4cae08dd2deb8f7caeeffe8f7f5ed7b23ec84d2df83dd77c952f5d3b32b10559697f6676a846105cdead9f8c4875d9162085c2342e6c7f27d063bc3e51e75b14769e4e1fcdee6b6337f2c9b638bec32002122cb6f1c6fef2406cd980f86b5e51cfea27c31450dfde813e1d7febf035f394d4d62e0de50b8e1fff3f9070f82a729614e02d34fe311346aa1c2e413b7fc0ec25dc8933aec527d07bdff0245be3ec6130b6b3f01a2d0d5cbb8b5b3ca42894482a531ef36f1fa6a772520c1416e724f7b4436e80f42ed11dd47b1f092f32072337a93a613c00027dc89f8b16c0b566dd6a409ee416ef2209ff9f395ae5cea7286fd8cd00ea2b6fd18efa302f292f082d285e512462737ca392917f094394114ea55159257cd95919ae6b885ee60c01d76c004828b931ca08671a5052df01d3ebceba6cfba59c1fc1aa2d4824002daab1d6e51f4de3cdfd93e7ee2963005883ad80427ed4583a3f87af524c749a5790c2342e161107d29b71580c97392ed9572d8664a3db235d333d81f1c84175fd10c072fc6168743b17d7cae91ed9cbfc2593e9726ed03b1b0592f62b0d0d0b09f4a9bb705834bbebd996ed925b3b1654f72a9193e682d3434bd0b718c78cb3e2081c9c5ea00d3e69643edd6a61c652d1e3d9e34c604adf2780320d0cc77fd4966da1088b8ecbdf766ec61a85c5aa31900722d9ef77ab3a6ca0389d74d37e5ebface65b23d1bf3651462ef397be7367bee72e8cb39a6d5ff91b6cbc22bbdc0916655fea37c47ae8cbef0d63fd151c7774a7222b3795d4f8ce37176ff3c99083f3871d3b41ab6360fe80a86d5cd5aae56ec72baa289a8f6e0d5863085bb70163d54248ddeb4e8ddd600951a13bb147da2f968d106495def2e69931fcc7f8c4acdc3c4696fb1f973c27012773698c39bbe9f195191809b3fe44f7bb6ea193821a6b2f3dd5737b15bfee76e7969ee9c84b78f7234fb696a8835acdcb8c689c34c46804a795d67e3105acc28090b9616a60a1512fd3fffece0627c33f865a2f217e79303b3ede1e097d5111fb84429ac22c46133ee78e50df58e7ec29a48c5954442339684fe90685ce1ec39b4a1ccc761964772556c4c48a6c5e4798b74afc4ec876db26168c42de680a5ec329817b47737cc40d63568c6da8dc8657681f1dd4f6e458e503a6930e2f53e6701c0711f1911ee4c3f6e3e91ffd61f6f5fafb70157ad17bdb574098ed670bc79479abd8a3d560967c8fe91d3456abbdbc01ea8adfaddfe7d11a58b037b54d4e1c2aca07191d8111c42def531bb954670b2844f50fb49d19bca19ee82345cf677f990c8e434335f676a7623bb8badddbdcbdae1b632cdadd644ff72cc87faf04144a43b940998bb722bd92b6372b4bfd78907add2b1cf21b27dc14ed932e555805fc39efd9aef7e46aff9167ec555b81a5a44a7be567ec4aa71d92abe8a4f9d66096c006aeb7dfe7211015c675781dc5d3dd4af28e55634c7eb8d9bddb656e98c51fe3562de162f72547ec95a2040ff77c8801bf9f0c6e2443bcbcf9db2b4bef400160882aee8a672fff851679c8952e91886ffebd8e78196a41df5fed505ad6b3896d9337b675d336fdd5a8f22310eac5b512bd9d5e02381d9f3a1c5402348e127127431a54d01505aa357140132a874c297f8009e35900f22a4f85cdf969876eef4065be8d9d16ad48de2813f23f3007cd6a379f72ef17366d03210a4a56c334fac0ce68ee85472c4a9e559c2df2ac63d06d0bd73206fc63271b635d9c2d3a3f7b57c33a7723372c4290dee4852fcae5350bca320cae3d52556411d5a2815ae226dca04c0cc571f292041b149af2f8419f540133183a64e6dc3e7ed9604795f8db2ce566121940d21b52378bf01e734b7874b81c53c853da80acede4e06b62f2046d738d0bc2b72f8de5da6683da6e85d3066d0d0459170ca12b9c19094b02944d2de4b8578927251274e623a3585358c05b431abfab2af5b526d10ab9354796d812c2ce75fad721ea1934a1f68a0f5e156c15e60ce42551138b7c5c5779d7051030ee1e7b4c87256f228e9e9f8b2dd61b81ef211497ce3dff8ea4553a7d1dca919ec6c102c1717a0cb4fd9a1ac40a0b49ec3a4c276ff37b5723bf07b2b757fb734970532b64d37a8c34171f4beff84cdcd8579661f3b21517fc057b7efbd960428ceb502aac72a1f48e98e312b148eaa9638ac89b2f6d35c4f55afe3f37f3b397d7c606854b1381cf780c216d6bab92ed9c3491cbb6d1051323bcb635fffb57df0afd2548d8c724d1d7bd62ea8753ef39ce14e0f5f508df88183201f7adc6c21b5fddbaf10d82abfe07249ea025e72fd8243610ebb8f4279cf3372a22433fc0515e2356fdceb228e025e6845a60e0d10fbbc7b4d4ef85f1a15d96eb8d1c03cb4c8014e640f6f72b09051fad965d6255e91e22c68a386a3e3bf2368378fe263d5a24297d3d9fc72f28b829d5e4921f23c57e167d7c0cb807714f854e69491204b89a8d6e4fa450b7b6e96e5a7deb4f74a838b9080ed7fd3b7b48553c8e26214cea6238af8672a725438bf2d51f907ef7f3677ea5de6acb883e9b0d9497d143e62dc972dd8da42722f6cd3eb6229c8b88d36e2bd41ca8377ee5e16524831951620421aae2c9bd1725049055c2090c04a37f4c79750f5084ca48b928dc7e501fbecc4296ad7e2cd72e78727054d8bb430ed0cbc488b9ad2053d2d3a78c731ec58481067b9750fc87003ab02e467d2d96a8cc0887fa6d1c197be459682222b1c8b2a0409e59801a3724ae31d2991bde74c7d16961b29b936bee6077c50e2ea874d200ee790f5ab7028c3aae480d65bb3936bda64a40cd516db22d2e80c7714e009bf36f1e2d5d791722b198db318c1de22408a6ead872f4e50466ab3cca89362829808199c6ca88e50a1d83030fd9404b60ecba20a8071a4d55d6f1e0b48ecfb8d8bf267f4911495722535ed73a8be524e09c8a221d2a431ee52fd65e51d66aac0eb3534960e430d72db6298328e1c4048c6b08acd048e60e244a42abe3d274ffb911f8ecfa6fedd72e4166fae935ddade43726dba039d9b1fdd316ca714f454a5e03bd22573b5c50d4dcb98fbc86ea122046f6b4d8ebeb33b4ae0c2137750637d5f8a56dccb79b272f92a26763aee563b0b7b4290d24f81ad99cfb46ad035f655957211535f3c17723a4eef069d4566b5ce025eaeb4902b10c78494ace119fa69ee3a6cdc4a604972418e93e1c6dc698e78687cb4c02d0147f06dc6f1cd84a40fa45610f24afa6e7264f8dc646a2cce759f9f30cbba0f12f3f4c71d832aece6ee5912995f29034d2f07f9233df2ca338dc6290c9553a8d5425c8eade49758cbf2b46b7037ef8cf72a713976e38ca351f7adc417f43b4b9d8b79e61f06ec7684a3adaca13a7fbd357111b770da6d1723d59095b6761d5142dbf2b2d26052d859c7b541afccd50493722a6fa1756b6e4ac1093744cbe78995d85d1396ef9ff811f57a60244c61b741373433f2b012a91e472f5c657fbebaec68d6819a6a6fa05671a5150253a8d12f72147be185ebc867eb4a1f58f61e87135a958e79eebfd082eac5d89ae73dfee572133d9fa728f80582cc4ac46c14a1a8d2fc2f5a00ec00f028c02f91d091b54472f544787a72bc1c39fe9dcad4552d30194059ccf6629671b0d5780833b0cc27720d3a7b7ba5fbea0b94abff2158fbeb62b9227e2578926cf03b07ff41e3796b72f90bea8d5c6df143850fdfe00758d3b2709e0e912b66131e5a168bad3a191b2edb35f72124eaf0c843c6f3477ad80468fa2179d49109043128a6840eacfb9d248708c2f060e3a5b59a36ac20b639aa90fbe4b5274922c35fc4f13f5af70ddb17d07691249c1c82bd5e53ba139edd9d538eb53149e0ef077ce5a45fb2b2893172d892d6d11259e9dcfa8c46c0c0729404d4c2c55088a3a5048eaa05b2e5bc1b72475654024148fb1dfc5f0acff39f2a2534024d137e920c400abab6d029d282146ebe8c84f1c3cd6a3037b6703f2b0cf44b445d734d1be458784e636ef270e97269a0a10c3009a0d199a8483e1d86f37507b0936eb3e4583a9b59794ce7aebb72b14710505ce9c149e78eced27499b9c2b1d67b2ff7b1951151048ac0ddbe9b7251bf96f88fa36d383d4ad767e57fe248730bef368ab2bdf27477d29c3e66717299843c72ef989f2637b481655ed29eec5d2715a0dc3e42b47f4e523571cfb7723757bafad586bb827ead542c380a856cd0c89097fcd1545a68063dfb5a6c253462ef47d7e7bf0c833d34ef05979f9f85a573ef223bd889efec65ca96cc619148fb0a45caeecc5367fe9d34031ce5aa90cbde543a347b27e0c1f6ccdb74e66f4fbecb3bbea2e8413e0b23155b06527bae23af0737301ff4cc29d808f984d07872cb749f61ad7d0b2189ae40ce9a659987b63ec11ab0ab4f2dcb1f7595ae09e572a49f31f42baca93cff8e8efae24417b0bfdb8967e0f7031443689ea8c3b5d272fc6ed3c3f97c3a8c0595f92cb72a7fd63259d162ad3243990d087d5744cfd472575e84e293e5538a0ee482268851b0b9049a2097f89a8db2ba780ab6764ccd7271a34b258e37aa5f704576df4e113a1ae8557e61660547cd19417f2cf922422fd2fa5adb2d9e8398954f1bd6b4494f53dd8a0615eaafd744978c445c7be42b5f74bcc37605b5ade5531ee2590a1e5bea8250681f4459089e7fa35c83283c6734c0036d35e4a772ea0e00274763d0c41518c1a8c5363973e2bad0018dbbd90b46596588b6828dbfc872dd3a4baf376e43fd24f71fc281dbe364e178779ae8fa3250fe2d4fa0a4460a109e95608eca41950d59e74445e0442e50029080ca962172f00ece09eb7c0ae186e7616ecc52d3a5e231444fa19954887364dc01a8fd1c5e73f8c12c04888664c15e9340e7aef3c9d0fc57526b8e34b1feb384a2405c5472b690a6bdd8923731b0d93bd9c856d7c0178261328d1053e612b96ef2c4a793723463ab9070edd8a722fcb6459c86ba7c8f642548df474882e8de3d5040d282726a6168c1fd8cdea8c5daf3c428a0950c81c2d162053f66b15f42e3b85a3ff872b1542355f35f5af33bff8ac6d15b31895f34b774d208cfac86e1b3574a4cc0725aa36c96d917509607b264604a5b49a528e0b384840d0af1e1eb32ecec957d72d4965e776db20d133a32340a9579c39ba9c65a2435b4c11ebdb2b6c9b919607222032759d3171cc3f212c290f92a0a95f7afb85aec9e52267d80256dfb2a3855bdcc99a3a7294f6faf743b754d0853c69e7e5e7a9a60ac866ae432df4d6e98729e563a6af0d6ed42256f8ddaee471a3bd152782c8ac4b55f3e6dfe5dd10e69724559b5840f633168efad08d179c95bbe71af17e47896f6333c0d70e49b9fe81451554f0ee2d12c760ece92677b2033fa7fc9b9c1d60c3948ee333cccc9f6b05f9dc41ff119ca38ba0e0ed49e53961ad27c9ea6bbd6958853516454b13cee4e07b1d931dba338d4e30412792b75d0a6c7c12d07a57ed430228de9e5ec3b9aed7261376fc06cd7378ea9413aa826174445c5fd814604f12c672f966a90e00ab95ef3becae8b0c389563b99b727055b48a2fa841618f32b2d3d6a5646e5877a9c72f44fa6db88d5414cf0347fc17bc76e363a3768af770eae79eaedfc02b35bd40a18945b261d8f41a846eba1951eb60d00280298e895eb5757a4ac54e4b936ce726cb40cc34057990c3c53745653bd30d763abd981517151ec6e6ce12b25fa58721ebf212e14a80ddb15a424bd107514e6bb58f2f72d1e0a05c5dc94af2385217294c3349e93811c270022c939b681c8a55a7c745f85d8251aab1ea8a7d07f1772ba898691d64a6fc85f4c1d63b9d20d4a366aea7267a750e96b72b830da8cdc72cd212ff79d529601cadf8a396a2b8116cc0fc2cb6fb3f07567f74b1a04e83e6450dff126bc350c6f7cc911c6eb63f5cefe303ebba21b57a6caca6c99bc98b672e323373b0d07ed31d38470c8a254dea8b123c5e18c0e3490ce1375f8295b3f63943b899327804b2694067380afb6a196f9d6fefc67414903410c933549abaa72737a1e090e1e26ca089e65c590c25dc0897eaaee946edb40a9879e762a90177279c5335a836f72e67b1f84fd4c7610fe17ca7309b1a5b8480d3881c198616c57d338263d416c4cac6e7e1d58cf75e2a406b4fce9c6abba967bd49dda33420e72d26a1c837a89b3f6220b2adf9f4593a2e3953e28ae1846a3fb70ab3ed743cc1d15c20bae2e7e709469b128b2ea371cac99279ecebe912582ab0b31773d512a5d61fe72ed5e44bd45dc4310d2aafbedae6e008b73094925de4621bce7ab084e72b1fe66b3ed06fc6587a7a5635d78b333d5cc37908dcc9a7e51d2939604bf3672dc32151ab3ec28750c42f33de73e61a9bddee07dc93194a36c0db770871dc4012a83a5e3b2bf56a6dbb475cfeff50b061a0dafdadf8fcd9fce46cb59f878ee1ecb08a654422fdf36e209b15af171776725adbd0081c7f0a6ae4408d162b4c4273a0c18811b8f80775980025e15c7e1da7cec3688ca31ec0a06dcfd8f41009f72a52c0efa10eb0b1b0187d9df5b0b6a3f780b250b79ac91b2f7b5121ee1c3a26c67b14d6035a005bae353aa2d4593716dbe01ae69748b63ec94b0fb8b40bc6963934ab5b5b2cf91301bb08943d13265ea3985962280219240b7509a836736b272f784bf9f04731e91cb2e09773ca6e84348b6b608972f23570541c6065550c76cf3f670f4f84afe244be33a2c20ec93871c30b2a0eb0646528589c3b772ecac71c75ca577fff81dc5d93e48f8d1cc6dc26694e732d6d8054fc6b2bc71b0580002203a9056a7a021313b57b4be9f7539ddfc1a3aa3d60c4f6e8b35e1788354187265f4ed59295fa0c7c661841e03a4d7e8342c1599fae8d10fa6722b78145a8f700027b6fc932513700735556471a6d78e8dbf38e584f418a5bba0325712808e72d7c7b3539bce160a4e616ab2b1aef0adf1800efefeae2f256a07cc083bb43b4bbfb3c25294a4d8605bc3804901799e8bcdc1870d297a1e1d4eef5424e4219e72a4a3d3a2ac11f6c7cda2eba1ded223da60f8824f6021455eff3213623a623c6f09a493ba11706cc446acc14f4917e7a17e5093a1a892240919814cefb86b557253eea049273f96f02ab11b499416013b3751ea91d9fa28d6f74edc85ef37a07234b9cc97d2b7cff82d42e85147043bb0917b5d080886723a502da3b167f7d472c33f553624d0e92b696edbab3b27d2158c5e649c418c741179e96ff13c3ac17294b5b3c5362bf7a33d3ea2ab1ab9718461a82419bc4345707394b9e04b775912b0c8ffabd3a9b086bb0b0430075b093fc488b65cfffb20f4ee4207e0ab657a6b3c0295c6b46d0900fb6a1e207ec9691dac3588d1a6499d8f8432dafa067dfc7224563a5ac21938a79b8b40be019c73b82abb2b3dcbd63d70d0337d011b9910029bd9d8955a0209625d0774c6cd270dc13721ea1f78f8140ed1b439c737c73772b9f181c6838f559540b489d790a808853ecf3a5db0eb44cdae6e2ee8d82324046c7521015b1e22b245c707546a774a0cfa62332028b452b7e968a207e4957372ae3e9af8406c7567ef0f12094234e683648abc13dfbe0d1777c8c3adae879036174c248107a17f8426e9a0892d082ec0c3336e18b38da23ef91c77da4362ab441ef10f12db94bce94450b94ae673a9645db8c0b5f9cdf8151caf339b15763413d956a9a9e30c56733d9ff1a81fbe9654a2b3ade04330f6a457fd511599375a44e4008d30d0de0908349083ed41732877576d13d09d3bc43aba7413ecd5cfcb7205d7387f9d02a002d5e57e8b5578dcb7be34488cc37e29100ae747dbc57339724cd608211b0e8674b878e018f9f020f4859c9c4b2d7b81d12e6ebd8253dff672915e30080a17c0b483f78beddaf595b1158d95027ba3ea95a151f373425f0072a6458d93dcfcd950152d403b161a7ef6c3addd63a0344afcd40d6d589bf23d72fb985ad455c07c998f59b47d93b994a4cfe51e012211724e86b5ad083d19bd72fd4ba77718df06dbc45347f4591acb0cf2d57f91bd465b8c87be6ee97c7efa70bdf095f9ebb52a0065c004bd90f06cfe051d962cb69a890648f3b67643880c234c13d031881cc4440b7ad446df64d0e6e07c199ecdc271db4b6528557b7d2c72d84e8682b26f2610de7e2db5926c8c6cc0437c88341b90f59339be7241ebec72a641bbd7994ce21fd1846d5f0b444e806516f8b4c6bf3a8699530185cd656f4e9f1a62be33dd4e312155f848ce0661cd7ab2f206b3d17e4fbbffe5a03f844d250139e5e8938de485b90b55d68ff92060f33e18fc5393f4372296fd75c018ca1c780bddb678a3f2bbafc9afd7805daa297664cfaada760cc7bf520c11c535c27260d1e57327d1e8612865fa98e327c263a1e153073065bd94c8096806ac65b97285ae9495e5aaff4124eaf027b93c16f04821e80aaf961ccac4323f2a4756ef728407b1bc6a79109693f2f080c97f80b288a198281e7cebdec80aaed84434f37204a7bb18bb61530859c571625a3d8d74584ae74b50f2c8b7978b80bd5914f972686942e6e160c519bb6b48b4ad78da82c86899fcd930020e1615fac03447c026d9e83e99283dfa0f22c796f23d31bb2ea92b1a8aef6e1c52babf8b79aa14d972fdc3befbf460db0b5ba93e9513dfd33df474faf954305e08b240e27b77463872b45f205c099010a9ebc0ebf27a43d76cf94d5aa94c653e1921a35a6da7a5f0728590a419b4598480d0c2eecae360a0c6aa1762e829ada7825c71307cccd15849dcb516059c5161e5cd3446e204ce969bed1e948b673f8a13a70a6aa0fc32b572d7f267751bafbbcdb0b3cfdf0a9fb31446b94506c0748930ccf43b6dd8984672921bd5328511c39eb3409e486288a9dbd8c20b4a6a3bdecc15f875ecca31cd24774288c7f332c8b317996052704ca24e06a05db1d544a6fa8d04785cc5630972861eb02a770993c24ea61b3bdd6cf004d5f6ed4a0b518d19b46955d32d3c7c27ecbaca9677c52e69fed897e848444616afa0305832765d7ca54eff189b92b0721f4300f680705dce7d6a4b3fb1bce908b7484b704c5069e97be4d57825301e72557815ea37d6b2a1d38458026913834519adf67a3f96ceda63185c1a16cb8172998f06b8a9dfe4fd9a969be81e1a7260bce2c85673bac0e1635a0ec44c149372feb7d3daa5d6f1c8eb3c4d9d1c420ae1d4b1f1c8ed5d3aaedb5f5957deee0d72e5e48b55891f567aa54c5cff5b7c0318aec0b39a711063de66e820bfd4334a101d7bac67a317281a9f767a7d1df5f5f40dd689d47024ef29afe4cd1ce7e3de724cec07342e8109dc209a82a635c5134c1fe023c3d1c55dcb6cfdb1db26d5b172f21946f15486379b0725141a2bc4415fec6f422ffca72c8ebbf536f6dd881d5fd595d0b4c67c89f9b73904c8397bed9249dc0e150893b06885657bbefc471772540d8bd68d80eb6138acd9d377369b9c5c3f35b1e18524c1606d52e31c670e03d2c1b784d42fab7cd1ef29caa90206bd5fa205bd15ef68cfda0528845c07be72b56d4ba28a664e0b4dea5966fa127f80548e50db79804bfdc5cebe131145ce7246e4a940f864e64bd60e0a0e8349c25abc81f9797d50e7ed5ed741bbde1e3e5a02bcc4cc047270dedd89005146203d97b5c946a8a8858b6deb8423ea80244272ea0a889851f256a18a94d60623cd328d7f81f42799ef5ab17aa7f823e7cc4472c553b9766aceddf4b6cae71aa64e102da078a11a222557d8332c6127d9c01f722e4b994199fff4ca3c96a421a501144aa98a48a06af91c03bf75dc84ea82d63e72aad00c9d647e28d94b71881df098284eff7c88de54a4cebcd6a05e5057fe578cc471c05de0aff3762f8d04f4bafa593adb2d39a55c6d910ab8703e6afc1e40f0a62dff0cf15c034b3bc0e4b8c0b1295b23ae26cb6606d32505d80d82ebcf727a0a570b2fedd543e615b7c2e0e4947dd920466e25c77ac15a44ee23a5b89d7288652fae4fbe9f70af82ba2a2e8600ef6c32c1d767abc629d72598fb344e77728322da4949a7733101c3f29c8b10f8ebfe544dc844f11e390425c2f15401463617a410b2277d927a00c9add85387688378ce47de73dbc8c87fc70fa9a710c172364638e5b0ae752a23ee9318619a3ad2636dee5fad3c036863958f37842fb351437ee90fad30a18da369b11f210127f3b361104671f00ce8a2eff40120a10772d51867cb90c45ca65963547bf1676cd86c86872b6146c612ab5033a69e56c072a027104815385d7aa7ef8f52e861310a1b6591ab4f5cc11a1258dffc723e6b54d412ca47b37b40e7b2a8acc6d9b6db44ddb527e30d381d634853d2fede4112729814882405d0ee2bd3225a8156594778b45276c6157d2b6b977a60a61a7f605421f595f1f9ae0352f73c2dc984280b72103c88a12d08935daf6dfadc0754f3728991f0fff08fa4ea359bd3480562e69a8d85ba45e431820e9e3722ed23889843aa20e0b048aecd88e309c3b376bb61fba4232dc5424c39a11533b06e0cedfa72f80f4c786654237ddc205e01eedd51c312decc6d13e1d0efe2faa843c836d45c531da2553208ffff19a43237d99381bb6101016bf92926fc542e38e49d6ebf7287833f6da012c0e9fa7688d443636292bc1920d4fe2bfbf2ab6262c7e6ceab723464243b282259d5eb21f5fa226cf592f2c6ea86b8095294047fda2b645ce10670e0e1a45290566f6186afaef8a16523ccacee7821ef9d1e148e1a1f240a171e97757e466442f9af091f22a0dbdd45539a2ca4fc9cdd2121a62d0b739498e2722d356359796e84fab37cbd25d68c1802dd153a665bb7011de5806df458ff5872f4eceb76b45039b33328083cd75455c6ca56957d0007e10d8f706d85bcde97721afefd4688c5daa5b4a3792738d79ff64f5638f0a5170fa75d05e8a3182af972661da222d1e2dcd32f633c18979eed14bb278fda9635464b9b83be0a9278377268be56d9a92ce27369e1772be810d55e45eb2bd7595b2a7948a796c0cf62b872da18c41f550fe3f591a745fd0747b7fc901b6634c42ba6b4e381a28d6fc1b636bdaa9a623c591adfc17606968acae9de03102b7162d8aab0b2a60c09c9e63c1ccc54f6cc4bfcbdc902fdbc58bbe67296b867c1b2cfbb7ad6b55ad6439a2eb50212944ea0acce6e3dbce2723e569b65af36f8f821156bea43234f5648488cd417a55228186cda8d27a738125e6386277f138c0195197cf327edf9413eae57fc72ba2eccd55b15d6b55a266a132f0d3dc85bc870701f6db523e9b27db8a892417289c32b9a5c76a269e1ba78a4d9817925f5d8a9915b5ac18d297cbadfb2cd385ed62c25dedd6fd4cbbe1229215ab2be691976be5366e4860e132de68cce41b272d18b75e2a7b8880b570bbafe585296176a4d7940656f67ab1d57c95feb8ada72bdb8b4dd8e65c66656b0586ac6a52a06533536205da8843684dd4b1b4ef856729eea0e2f67c3f502ccbe78018590863223eae8311baa821492bd1a6b9367e94db1906a3f6aec9716513b48fdeefa878729f53d6544edbd72c3e813aa655b253e414d741642b4f6d5dfe67b11d0cd363b2d918d507a4201a46141d8c7594ef55c35ea10abcb30c18f040c09561017d81e2a65dc9977b1c7a799498fff3f532b72c9681a84382bddeac4063862fb037ddb9508c5d5dc5001cd490a8f1d909c174f3471633765a2688417952c10f2f2b43e72a07ce4025029226c6ef3cc6b6434296aa5794fe631d676c62fe8b32b0e869887b104caee370f9f3a955a7466408b3878e71292ab48d017dc3221e6e30ba3fcf2f6a1875aabebdbca9e960b3274e7727c9e11d13860a892656dc3cdd9fcbdbf0c5ef73c6cdcd6009edf99df0fbb6872b748fbeb944e74041ae14a416d57f57bc340b199d4bebb5d523ba023849a9b7275f837d6c149981c413cb568fa504cb40e7ee8e0f945201f5e02899b6336db671cf984eef3aa1e11fb5c66de842cb8bb2a3f759d65b8c80df02be3c4880dde37222acc9854c85d6055c616cbb3a83d19b792ff2daa86e573f4e74e4b501e307204666979a4ebc7299799c77f76531578290e1d624892f52f76136542b63adb39c4049d4c72f6f99332e61caf60957a31105c8e81ca4728cdce6586742f161072d0d1c9c8cb8cc7580b46289c3d7c49c00f310d4a84325b4f53bd3ed59b8f202067c340e3210222104986429ea0ae8a9df3f65c2671c788aef7d24df9a864e572636ac74f41a5a47a98bee5823dddf777112ccef58011b959241bdcbb80cc9f6ba09036097f74e4c15708c79759e251bacd0eaac581529e757344ac50d5bf42729408beeecbbd42d46a15498697d945c4ed38b31f81ded461fc3abd4b7f8e6e0d4ecc8eb477b4e7e67e2ff73556d963d5f2fc3452d1897cc5ad6ef96bfd2c8a301c2eec1bdbfc84358f4fa2dfa6f6995a39084ac1ae6ee8604fcf3b15b1572047954d322ac8c90637083ea863540954a6c18bc9aadda52fc9a86597312fd051223f30dbd08a06d0213e3fc3b924d954a2c5c81ae336947387370919d4870c1f5ae5a2506e99cf18e63430e548a5e2f0e8c8f2cda42b4f0fd675294129de5f553661c3a8d83dd23e0da1b9625b9677604acf9652ddaa610f0b683beefb8c726b5ac6d3277e428c9a923c9da48f6b43ce0549790c3fda7efb4d849824c4fb21fb09663e61c83c384b46fb3bd09b101e73eafbec169f9adb9bf301b2b15f764ff8722a4947463629f379f1d6e83a934e601b6e25f7174d4909c1abd3f687f4d9815083d17c7ea670fec5515d0a595a76241043775de8f260fae74be6d9e58f016572ce86dbcfe1e97661ad835dc70b3e1d61491113a8caa7b626c87711309bde1572d513e7ceaff10519aff5b4eccfc7a634e77c74b560c3471feb7cdb9228e6a572fe20042a47e6ecd8e845cc9a6654a0074e006e79a4042e446f70af592225f972c98b26bb0043f59fe9c29dad4092a0fdb29522ec06e43869a946499f64d40817ae095c6bace7cbb306afde5beba7f16cb534f6412f2896d4a9c2a28303db35659a12621803667a29c24db5b47716f9aa40736ff85f23e51f748e3e56307bfd721fc5ece38d14061e198108c6a429ed2294dc9b8ee6951c3de9a8718372100b7269a84c8741900a8f5fbd6bc38c9c61cb284245f0db738aabdfab75fb9c78e84def630697098d2f946bab2474e45e15b6c6518509eacc610081771136f6111866069d3b3773aee4dd8899bfa7282086d3d17bd7b20d17efc2a50587386bd57072ff021d8642c678ea3ca28c8430d388053325a340ea79fde64c2d860101f1c872a1c4c35c5bc3a6e2982a3a9fa475679e10f994d9dba7e0a5ad5a4df500aa6872cd969515188ae53a4af0ba1ab159a44bf096e4ef24f12610f54eb036ae708a729d8a951bee8a354fab05f4d618c5ba087acf752f8e9383d9bebbd8031f0a463f26ae025d0e873176ae1fd025ebf4d8b9cc0f1d455a104daef20078841979873608a713dc16a769262aae50712e5913198282b4a1c7b1d7245f9e9934d9294672374c10a022bd07bd45d1602dd548dc6cf15eb4c41a36de2545220849461b6f72c7480049551d5e2d050adf6bb47885bb5ba7cbeaf0bda08bad36ca33916c7f7207f536caba642b773959215cc9c9a30a7cc556cdf76fdc205fe5b2e9d720a7094721226132bab1bf17d6d09cfcf9d21ea9eebeabd51bc11a52828baaaeb7cb72733e20f7ec8c5b3aa8d2fa259e100325ec85e703e31fbc034b5fe08982080f72950e481c9c7ed332216cbaadd2a5dcc8763a9c74470d320e6814d6146a321a725b123ef7936fc156ba00c1e09063b423c470fa83e373f67dc7a461e4d63c7972b979a70ae71574aaaa723e9552f5345fdc03219d8e037725e3e4e34c90571372b549ae0c72ebe09f54252425bb91e75134afd3e8b4d0be254cd44008a5204a50094b08cbf70a738e7306618ac0108b48aedb14d2401409b1b3d070139c494d72b0ee2d6b22154e5563a601144ea429a309c2961d517eed7232ee237f24423605ef232c253e90a18f7fc8c381e09508c5c573ce68c80313b253033a69d91ea743aa593e97a5b9dd4cc91c04829bb949f4a67e7a54994d1c51f6f22b704a37057212c98e92fcc6baff3584fa29b2dcf1e402ea6a7c66d7bd07bc9c662e2bb7a272f37b260c6b98499c62f8040f6ae94531ab58a76ce3a0690ee8b67f430f61ac72a81b619d939b99a8607f7ba00ae5e92ae00ee74bbe6884951fc30e53e030446ba3b22a3209aece7e9504232890487dd3865f4412eb5ae5ef297ffcc5f5d7ef4a7e85bd0ed5a3b49d71146a31137bb7f8e51d5dd860f09bbcd1e90143e6116f72b8bdaeadd2996897480a53e5ee5bbcce6f1e82a06bdc9124f6ab00982ba8365a03e991865360e059a9b59afc43668c8c1066c5cf158e416ea9e11608a8d0a172e3bda6e7d4073fa36209054e65d7b156566b3d77db0c929ba803a1404bbf893dedfac6176b2874a85a13ad4b0ecb44559eab0d5c717736a6f515a0508d4574729aeb1e4e5665fcaf6c6830b2ec9d4dc13a96a5f5ef263ec1c81c31a335f51338bd3b237480c5cca48bbce65393e4be1e2e30498b2d0b0588db164c0d63d254728d878234c27065fe9c277c9355fd3d1c7f2d411a2311e2de0569c55093a4bd72c4d1202a776163b2f25427c1f9959d653aaa60e0befffcfbb3dfff22c2952d3405fdd6019fc408d3d12600506647c727882f6911235aa79d289d0385eee46672e28b8de1920e2e2be6771eaf9215f85be749cc727216389bd1e4089ee42a751c451191136ed244768c2985e410d764a4c76403a076632edda63e35613b6ffe54366f7b4c879372e64d61a335d9190df6de5fdb899a1bc8029953b475d09d2572db4ff0a77c072cc9e8584f992228cfc7e25d2c4e81c34bbe1397d83d88da2b31e7cb1af4edb3ac9cbb5efd1853f172aa1fac109b49f657b0673658101fb79908241e17fdebc6a2a9f3dd8535e48ed804c6ad7593f4c7a93c3d9ccf3ded55b072868a5426ac78946eac22276fb7125d9ac699349d71e15c74267e5aa6e411f04dc82a2a3a3e41ec2739f0a57ca6f91768aaf2aab03bd1cb4484d901a5592dc77295659a4c11cf681bf8399c3deec10458e7fb80b175858574b354a4ddf1c996727782526f8b66cbeb27d4c1093f22c5655aab4fa58cc600b7f86c9b66c7e02572a68de4120e8befabf8b0f0dd116f2b8dab3ffbc8d5c45cebe2b1ba722e101c54a06f9dda388a91e52fdffcd503c9305f3f61eec470c10e69b5a53ae48660165743c6a78cf2aee5956cde9fc1d3875d566c68529b1e1e082106079d6d83532272a1d6b64f62ffcc760aff71ffd6817e2e03b89a95601f04879c99329e6a85b85a39b9a0e5d6bb72da732a535ffcaed25cf6596f3bf75eb7d27ea8da85abd41109cd373894998e4f94d36183f80f31ad88ab3ce5ee762a73d2c330c096077704411e6d1d0ca41a2388e343411b41aed797951a5607ce66dfc82774908d34db772cfbb3b80cb380d39967e3cd607ab1ade8478781391ab9c700879616fcc81654192f911ef6e938275a79b7f2ed48cd6311bcb99241577d2c6c9502b2be76434972ab4950e0f848990d434117a3d5653b392d42783032f503c28784851c22e76f59c75c91fb8175af2b5b6c88912feee593c3e6599139737addb1afaa7c22407d7282500249cbbaec0cbbe6e992254ab2f0c86b1e0cb4570319a85ab2b754e0630a181fe08b18d06151d17ca166014849fab59ed49f2dfefdc09dba79d6f4c7c1720209d988e71ed6fd10c2ea5386bb86ec88137e287dfcfd37592d914f296704555aafccf318f85588da6e20751d06fc7b2f9b7e5379e02f014d4307bae570913a0428d9fb6489e747ddc3bce36629e510601b602e910e48bcec508d12d2979172be03e4425690ef7326807c2d694c89d27aeb7bd6d5f25cf435dac393d214eb72da41d54485df6767964f1cf6764d06c777663da8d30ae26b4b31a7249b434072e6c9defaf59d84de7d40e1f4d568fd60fe332397b5ddbcf19566b41beddb6368c3b02ce623d25420513cf9157f8c24aad78696f9f9be11e1ba73b22d74dfda11c38f79e381d8e9ed7dc71ddf3a9f2ca5f09e73730866dca005b9956495a20472dd2f8433e5181b778ae5d410259227803b24791d45a3b4e6a97edc83d45ee75263ab71695674f407926eead0a39ac3fc87bdc8621b0921819c9b0289dae33772349b3af9d7322d3a8d84be08c57bafeacc608d8148c8d61611a51ab94e95e63cb962bc23dbfeec28b0b52c66ad2047e0335c9f5c3f9b8d244df110f22275d572ef7bfac087261b1a3b42d70a6a10d51c8d32d0a4efd854f3bca72d9a40a27c724107f507e9dadab8dd6d7ce74e1fe542a56768c23bbc7683b2669ac10c58df3f31a916a65dbcd7564c4b94bcd8a869a8b70b056aa449ec8abe4691fa312daf729d585ed907cf3dd96ed959a1a112412da039d6376f6ad9bb1d2a587bd6c411729fb2f625a79c8df7f6329b33390baff8c5a7c73aa39237b2b316a506aeeb907237ba8454af0ef2222b63e16dfaaa5e21d4ce4d2b1d444aebcb6549b57fab6c4731285a84b35eeb6ef0624934e00674d54620f1a51aec6bfb3e5f150be05759726478e74d2e33b65d1ebe3ad0635ad928874fa9548686e5bfaae1ea6af510c8728c7e5c6793614be833e292f20a3dab06c2b396601d4a4af701800e8dc00ebb2b72423fb7f824ea14ae5e4cb89cf10003327f87080aa039f845da812a2193bc72413e06d599fd468ba8d865b5d1c9f5f820290fb2d51381779f69fe011be02928124274edbbbd5068bcf588fe954c927bf96e6ce1bb323d5283f0543073357672f8d1483329150dcea45909de99c3f23f377c0e43de70d632c324297c124a7f54889c90f617851080cf958281f878c24aa5e3b7449ce112695f960ccad658385652ddeceb6bd06a21b6c221aa568c41f73b3b12f091a68a2cf2c9dd9323826e29ca698949c7f5c87e496cd474f780e50ff4e55148bde0154b49ef14242d68266b128cb5d65d3aa287ef26841e435d51d5c139ba9bf74f54b5c44565fc38037a11f8798117812d78a9472a5b99f083ebb383567fb0fa65046554346938d1f0aa720e19561dbad4a9775318f0bab662e445157b6052194e79bfe5f384dbbd5eee72653388340c25dd798fcbc6df0be10af2bb8c63626f0ab52b1cc27e2b2ba7a372c3559c98041f3a938bb33f8f93a5f1c709c584bcc647e3bac4b16e1211cfe07209bc6ce9ed47cad3b4ac20c6afadfff9edb4fde05f8d45bb16395ea4bc8b4a001f0b2d909b3e3eef61ab74ddb4a2ef7f6ab1f576b2aea40075a477d631f6d4082612739ad0995882ffa07b9d0ac3afd817af7057496e590b39d286fcbea5745e96c703201f82f94d907a497a7584c007107e6eea8859f5563b46a4bf78a5e072e46e7f4a3a3372518f05a5e947b88b0c2a058de9ef09355fe115f599a1cfc672d7e541c10bc82b940816a28f68b15eb10d31ead291c27b3730c802381c87c138b707975a372f6e0a6423b8add6c3573dc7cec85bf2eadb18525205c9105b4e72bc681f6d379dcdaf2e077a9f52f8297ebb59fc8bdc571b7187a54bb7aedff072a0c32d297ac4e925814a0c128b49705398ff51bf03636a827dcdd5fbb52ac92afcb1c057c0fb93815f6162b2aefb0c00d240230c7a791b0864b57d0a96c20972ab0af76e80e55b3c1275685421fef156efad26216470d1a8a26f6047d94db401464e326d7b7b5a94971216e5419e016f652a26c392f5257704a86827361c4072dfbd61471cc07ae1e9466de9a3ad8c48e9d23f2568378a11a1d716cc0fa2bf6ab9d91190a77cec9381324e16d54339a3ddf79c3669fb8787e0fae7be4eec9d72a9c42396e656a2b9f4678bdc5008fbd50d232ec58476885f9faff32348902e7211e8b43cf752ac24348c7f45d4c3b81da46cdc4be81f3ba3f966225e815c061c56977e75122e374b319d9c63d316e8a5654094b8ab927aa6da1e4a6d39e2167207fdc215d07563acf45a494a98da65e986f12f6e492f60faa86c635936f7e81b4a6eb5d48032e8fe492e65a80f25b0c81b56c6fceee0fb56c0caa56b71bdb10213e751b83e3408ca0f41b0bc54f674d93fae6b1477c7ff067cfc1a0c0d1b5272a67bb966ebd9a3f8a20829a9c8c2e5668649277f22ebfa9e703e52cfa5fada72b6855f50f437a379dfbce4e51d991bcce13c8f631d88c3e78276a0fe5968266ac4b9ecc328f071fb463337add854d824c85fefa4a45e2094ce4b30db7127b11f11ff5db3b8d5ac8d9459a0abf937bd9a6bc9628240cf8e4e2dabc9b2d9acd931ddc01dbe3e19e1029adacbaa424af19e966a2db423e022b7f4fd37a76c0eea72b1de67abda998feea6c48b728fc3ef5fa858942b76778c8bb15bce6ac8ae825294b577858b2bfd1c7473da46d1885a438e261f02685df9a4b5b674f711d192727053d480f3692907a107717217280d402d3be25845a9096672939d6539664306d447c45b296f6d396f086bf73e4234c8e55a593099e6ed08d76dcfc0750c88721e04c4794b2b52eaee2cf9e9e1fec345b697a0ea975eeef32ce9260169ed6002b3a9f7bdae958afb92f99918190d219619e39c3b158d035a5a10c25226dcf9635cb2224c9a1ebd8c844c6c903fea5a2af19b32c8886b25f5c4b6d94560ecba5b78d0f5ea9c0b8b1584b3ef1a490fc8e47341f8877193abb9b567cfa0318e2172e09df95a090be65dba05d7fef1234479cdbde1d11623627a721aee1090e298727f1134242d24fc2de3f9319a357251f55f8a9c46c6092b264500891a7c8df172721809b0e307edeaa7cfc70abf8251406814b056f8b66e36c04030896fc9947261fdfd6fd31cd83f3a889d85f47c07f33f637df3669be4104d9260b6679d374a82306370fd2d0d949c491170ec0b960147bca81e24e7cc8e41168b8185c0ce07c0ee721e37d5d14e639098339a0b996c7e46502774853784724ab87d63e434720b7a30cdfd0707f5df46e8dd18596850d04fe74130be97be3234daa704498c0a396d655b7ac2eea7013549efce537bb573415d2df1ada3a00f4f1b6842192119076fdf6ab35e1df45e16f0ed314434b51484441e6bea70be6daded8e935f006c20c8db11b88556e2fdd09b2074094279ad6ebb320aae3f694a59cc6537f32834b7b613236d32bfd33626099c4716062e827420e0960712428573f060569f2172fd233f0c8e941dd87b9c5dab30b13c7f84cc68a5fcfe97d3688b968d8b7a0840c22cc81a6231481fbb8d908ffa0627c3f1dfa950b35f84b8637a54757fc6ad7218043e6955dff0cb14057b859078eefd4030ead3440871a3973edf308d81bd7236a3362d6b2663bb2fca1d76d6cafb90241d72c5197ab2685569b991864df67276d162bc3d68f558fe74d78bcaeff2f195ab24b78e2a26163cb7a0c6c8e43e1088cd77389546b1f191c673897c69534c226bd8af8f19140181bc1d876b55d772e314da6fb06e21a61947534b60c3a7d79690431b13a10840115a0a8bacd9a672ea87f1f32c4fc4c16c0c015a68cc23f68c4f3b72a2b061ede117f5d4f6c92172ce7bc68f6c200cfb0a587f58f4f0cd41495d7836d99e7fc17092a36d869ca3727706399ad585a15c4ddf792b724ed25d5021394697c3efce9c1b559e4cc58861f62822e51377950a10c250019ae550378b11102daeb58b3431160170a915f45d8f0de0e1ee5ba041733b7d49ec56aaccc2c80cdcd3933028d256308bca4f6572377cdbb5e92d07bdf02cdb3ea4d588c8ccfb7ee6ac5f7788181804b9a621d372eeedb7384c1eb3ca2d554673acd8acc9b1f9b6a56795883a6bd8cb1f8965c472d10256ce7ca2ff81816f88be1b98c1b5c40ed4419d9e2bbc529cbc03466ffe64bbddea617f5c803cf704a96ee972f3017f9655977d62320c07902c7fb96059098cb9dc905434315e532e3d8108c05917bacaea1b145f7cc750a75396c86cee72b0b2cf818bdc659be551a69b9a8af1ef04bb24ceb78f3015eea1005dfd5ee272cb44eb8b1f70aae829deedbc835030c2f48cff9bb3de4b8564cfff6e86e64347f999730f4c95325913dc272d0cdaa0f71d2993863493ecefcdf93768180cf75e5c93c2c3d19df6d1ff8610e9047c69d75b863bfe7ac6409574eddd802f3c48720a5eab27aa9245d4911662abb92a7d248695b99df56028c32deaedb1557a06675b622a53547596f14e517533537d4a7c17f189bd2ece479f2c0f98273aac1872d7b5919e1a94983bc064159fb5b204917dc0f6060b63aed7ff63134a1ef1e626e27ded76a915e3dd857e38a2f3f8e0af203284497c8c5adb6f0741d30d49055edef0112727bbd13890cebad6e90f0a483200a2a04368002d5b9b8613eeda127287d4f30751df1ae13d9b5e77d6567f9b8ad6c0c0807dd71b42d4375f45720068d1da3e37d75be4d11bcadaadafb981a808b66af40a70bb67b8a485e0c07d7c01e23cddb54c645d04fa30716eab21b6c9fcc0377f05db3365f6a2efa6f379fb2a220ace930c80eed41cb1dfaddeaa52376e55634bb7b2e9590cf4190ac924c1600b9cdc412ab715ea105aee8c5cc28662511b57c752b525a03f76a2ca26ee0e378193337c7e8ed42a1b8f62df75524d4ebce8551d66ac8b54e5b6c79d08ebf47231cee2e30c512f442544660e9596c0d5964340aaf196aeac13794ca8c85cb972c5403f5bc97f7fdcab5aee8ac7da56f491bdf1a8f23f35d4ca4b6309d87983551001f0ceac6354992ac0ed3ae22044d812c8c17bd2ce51df8a767719984c9402db6577b01eb15e79725d3fa804924e8ab3082a65c2d6c6510ee434e413b44226859b242a486a5b58ea3e05a24354e9cbdc5ecec9870d1b3cf29d768b756c0d727d2475b9a6a7b6cba7b95c6c431ae22d36a13d03398594ea0f5d89500335207233bd4a48cea49491aa8997b8964bdfc35aafbbe5c82ec2150d9c1b785b67127113dfa44c0351c81f176a1460fa532babe6db921d04ccb86ab913044c08922072762789c8dca945c38cff9e5f3579f393ca8d7b8b7d90c9dd6e8c40bd3f2fb872cc654cbfb4a284dc81def6b9b71d63738e224788349d8ff75d0dbc95904a625d62807e41509ad77b56221747abaa3da392fc6c9541546ec44a11a8d4e3702d1788fb9da7c0ef771550b2c2aa8bd54baf45b3fd6f64091f3fe2c9322f071cde72198b749539fcd913176696e33538cbf4ce46474e88623e6784a723ff2153af725fc338f239df7944d30a3dcd861c746075b15751350d730713111bc1ae21ba72e42fff81c5d1afd749245a75b9e3c3c1ade4166c80cecf5ee7f93b6cb283057230ee37b520d84540452acee94b3bcc7f3d62d12754875329ca5fcac29aa546724c158f32bfa8bfc388a088253dca022c73300fd80742abaf59237c757e3ac77265b902b742e06d241cdc6ffe5ea9efe7dc4c895ac5274c3049a9a09f29c2c2720b3ae8c08329b952518d03e94dba291efe6316b7d66d2297934008845d97750b2d1d1806e187c379af9da7712c1a74b3542bf19dd627fd60d8b5aa3f06734c726b83272074ff749fcbf17035651795985d696963a5bcdebd2373a8851474414d71879104adcd907388a29f755ea6c7710ee029291d48d1dd9d8dbd217bd144725bd459ed8cc98483285836f41a4c05ac5de4314f2476997505f4d974b7188c7273618f270c4a238837756f1fb38c43407637a47bc1f0deb353e619cb42279072b8094e808948579e1f1845432d8629d4151fcf568b7c7332b2e8246b5e1980728ed766d241df80583b4fbbd78c3709baa89ccab9fdba6a5f0215be3e1157a1729606d9fca438a2700521532394cab7160a0d8b4d884ac644094c758042d9017260f6f69ef53c61b969a323fb99fe480cd8c6fd2f63e27193fa39e15dc5ec306742668138c3dfd9ae9cbbb2cf9da660720082350af464cd95e7c045e89dee57727e8ab9d042c17de28a26b87685ac9cc321e846ca2dbf5479acba695a69400172b97173963c12a914a1857731b1707f53a190a84208df6e38a571e23e3efb42565a584ef556552f9ff105b11dca8421b88665cce62644bd2bfd01f9f5965cea3120b72ab78da91b7a048f05f0cc4e02d10feb98f1d1e4963b4433b7036dc7d872e533742fef4deeb84a9cc0a1419662120269c09072ce721b2d4357b527dadc4f4f9656e820ce8c2a9af161b65dd538f6edfa4d6a5b3b3994843d7d9f3be4f11fd1a1b372d834d9071bd41f9300b4ea35b7dec91944927ec5933d76ef6b7075722b63f5012793ab7c7203fc4e9eb64c27f970f43d7469b5b8af7c217ea117437268d2e304674bcb1cfb235e4695703856ad12c542662a4000f8af780602510f25e395311dc510b732036ffe5ff530c9daed0aadabf71f355f41d84b36f68bc7710fa53441cf267c41ab09dfa57f5a996c602d13dd487512c4f011f252439b3172d1e84949f955904a96b8319d428b466e3ebbd8e22d6d182cd3f28c1ed4db670e3ef6389d010c83303ecc60fa8119965a975f44bdbc466905f04d8a08a6758f4a3f06dec983fc18711d663777209626f3307532aff34065edc8162e06c2a1dd7290f563b42de590a159dd780148ed0f256a5d4324f84a273bc3108688f5db1e72eb7e870ec328760985f2f2faaa3f3cf9053c88334da588ab9a7f39ce120e8a6a0aca9c4e79fc2570be67f0b6725e134150946ceb719dc1a41d008590ed056772c41c8675e1109f43ef2d755d4a2eb458571e3d83a28326b1e64d142b3f6fb26e69c25b8ef48fabd431280d781097c6559b607b3fafe54f49ebd16a5b43679b12fe9a23324cf791194e5f046be302088706b7ee44f87f9c8a79149a578ead1f7206abbcd49d6c166a39144ef04a33fdc77b6d52ac4c9d611f6dddf5a1d7c7d972248924a730d54173d0dffdbdde42af5aae089effeaeaa9d0ea1a4e6d0bc8282d415e01d31e6bd7537a019177f4d066bfec50f5c82f3b44faeb8824f8f8857972a5f68cd39c05b335e410d87ea15726d4148cc81206c4d6d430e16f0227eb6b329c4018011744d40da558e5d0ae0590180a9f7592c78b50bfebb07df21b9a27724a013b218af1203a314a74a801035561cf2b28572304f398ace79039e0eaf7720ca0efc2e80d0b7d930711474d5df636d035957da29f098377401102db92840697fa29639eadb2222ccb3d1204a7961eb4193cf08372e747549ab29c28ad934775c0ad5095eda0f99404167a91f1a57e02af8f900f52e978f170eb9ee162a472262b75d50151e5d1177f9d9714c55d47e7384b883799d223ebce4ff601ab2672a25002fb4b90af1452f9c41208a862ee6133421b23e2b8c335dea8f130d61b72c311896bf2aac1b5dc98b2fbed4753a74afa768838d8bba4317092aa1f21660aa1111524f028f42925d1896ea44715bfff572a4f3438728751a072bcb0f52c0de8b4306c9897b8dbe27d8149fc614718babba9219b71b6b6a94043835aac6e1719ee99a420c70b99c3d96f1512188719936d22d4e49b1c4a001b38fee28cac0305918eac5de2baafdb064a1b39bdc36549aa94be45035f0cc6ccc561852e4004ac37a8a8afeb5fe87438a78f76de22379bea1aeaf87df6089e2f13fdbc106e72eea29a2d41713cdedb64b297931c1ba429b1bd7359a2b79740248bcdf50c88720b409bfdf5731e86f89ba6869f585b00920cd68d49d87ee870c656ee3e8d7d72f4b867a44390b0fd16db0f8cf3c216ca2e1b06481d64454cb06f780535736b7276f5b2f70353f77c017ae23bc19c9802d67c3523524dfa7d39497dccff536972310acc9ed5334085f217d144950d619a1894be8b7a347a38bd18dde850134372ec0b97233f5a93a2e67f7bcd074c8dbb998d1ba4d4cf7fecc4b800fe3dc5a27272dda65087e4bf052f0bb7d985a72ba21e0bd7728320bd4c57ac0ba10fc1af721aabe233b5d4085c579c955bb024069eef9244959146f76ff915fdcfbaf1cf2bb0cacf05428a6ff4ceff17424ea57d38fd0d4de9165144330ef6d74f94fc7a49790575024d8a75b57c29b1880ca434081791af387ee8b92e1b4a3a357b906c6d264e3d8690f35415025d82665adf59ccb7711a013309beedc40be9884db578725721e284a46c6d287120f8c3bfe9e93d26466ade9eb064314d39580f115c1d72dd35ff33cff288ef8e192affc6fd2d3d5006b78361196a75de6fbae3e38a51721d78aec88f7c268698214ae6359b7ee1d2061693e06fbe92fc984f4ff5cb98721250d4e0f8cf5293f47a0819a668148b736f2bc10181863522c9d1c0022eeb2a03641390c5648ab02b9f7e05a83f983934e3aeb16ff404ed9a10c2c5b16ffe563db91cce8301af509947e4755dbf4090a6ff4c00531b65c69bea8f1978b81c58cb551695443cd314050838dc91bacc113a6544603af05e2d3be6104c0c4b036188106b2a96096f1642879733de8558d9c1bdb78419b0ce2197711a09160df7598ec2e7af789ea71d166441c625cf8185c3e44930dbd0cae87c364b71267bc959aebd505a7332dea2e4bf4c96064a78b134fd787c9fa120e0149c6ad2af06dc7281ed6e40d3ae7ab26c2affb5e7afe3a31db671d3804f2824eaf35f2f30a5e55304e9ea350700a51293a53c6d2fcf298c5494475c8bead3ece514875190434972d422ed0fe053d6def813edd82fd5fde16c7003469b9b15ae5ee0f15a4d4fef72cebeb211bac06f23a52818f5fd305839ea54129adec521f25d88576122c49b14e7192d527480914665eb2d9a33ded4e3cdae587a969effc95df96d338756cd31d97925fa78b915ceb17f30a673d38e28f75d18a7b1c97596058bcc961893fa55188f2671e5454ac778980226d38ff94f6158c9d0e7927fb65a8570d6cb6612725a864c085babac27cd15f6c7ebd59c1b3d9ad82d711fb11bca93d8876d28c7723224a09a639102ff63a8e68e349c003a6dcb4f5a38f53e0366958ec0789bd072e683142371f69fb3b9b0db2de54cf7fe9eb473a490d749ab9355be29c55977725ef4cecedac0c67d993777daf832d8e3531dfdd0491e5ae766a7ef90be1576728d40ee0acc6f1b69a464e6c9229d3d52011d4440ffc9a8f3f32792076b6e7514e1283e39720edb7a40d3a7134ba8f65b0bea4ed3dd1074f02c77ffd60f24ea6c46d51d6e38aa7b78bdca0ddf2c0bb5d218946931e2211842efd1e136349a9f48db354cd1883563495705cd5c67e264660d3d529534d3f431df6ae5caa79344284b95924dd99a7ecefb5f2aef109e82e7d169344b79e88cc3bab76e91535b453f8a51c0870ee15c36c8a3bab8297bf4600eb2b7777ea292c9fde4524cde4a8f2cfaa91218cb3c788003fbbd96220c7cce63a427dbfd878e5141476ddb86ae7409e063932ae0ff5985b4669e1e25440d90342f6f3d9a1634f306701e62755a43726f63b104e44acb92b6b2fb10460b5e9f4dfe75e9920eed3f2f5e31fda30e3372731989f3832a0bf2fa9e20dcb6f2f36b138e795b0aaf6f30e8952e946c9bf209acacdfe0b387068b1ae4dbfb737b2c33e05382424789ddf79dc7bd9fc316b972f548af90f9286852cd5ae3aeab303b3391eef039540a1acc57f429f8fe3bbb72a46f8dda2fbf4d39568839c537f017cc3b531fe045acc248e0cfcc0d7603dd0ecbdc50b220a0a8019e45695529349f6e286e69437c759b3dda2f91f149f5450cc7ce6f60e01992382ddda239c5c2261dbab801d5d1cf0b0ff779706ea8ba276bea7eaaacc9e05f1192f402722712901d00bca26662159cec16cdfd879ca02872365b9132ade17a8cc29b7dfed95f38bfaa2e84706b05f255b41e2392f051ef72055aa8f319a6c860d95e08f58ad866d76b1b18f00e1ea2f91995105f05e34972cfe591152cd701320a6321938d8c6d15773605e89910a751687ef5b4143f447221b4e5831ae130c4e1e55b517142aa2dc9ad5e36d188606bffd9e2a78091e905ff790366101d5e279702ece5086a054b7cc0c00c275beccea5c290849ceda94e801078efb0eafce84a8d54875e2c975356dee4d26ec41a8c9eec809136f432720577357af56f5a7d6fe2d0a4a85763ce4ea5db60f56732cf38d6f7514008767232afaa3a58f43a94791c648c0297b8f942b16cf75eb8d4572666c85b71873e2cf6ceacae1ceee21e376e3fa33c0d6b4e260b023661feaa5a994da6c5b51fde725969dfea23cbf35a560f690c1d441de903eb4fb71584bdf20a372ed76dd4cc65907bde01743d9b38286fdc207ee9e2798553c1db21024227e308956cde69be72f52894b30a117f0f231d095c34dd97df676fb47e4ce073ed88b0bc88ef73e572769b8f1592cbd0eeefed1d011c8d32be2e8d752c932570448d86f989e7fab2721d9006f78b47efd981674aa79f8688900c2aef88e85e6130538b31ffa4a6225e90dcfb07a3284f5e4389209c082a3132b53352b484effc71e90a01c09771ba0910828d5b15c8e1c993e83ea9973cb539e64acebecb69f7ee6310655a4d0078072ae21c7e66b04ad6c302c782fd68de0d04afcb0f790dd6637041bcb0db2822275f8a90277ac07da9f472441aa427ee39cccfc895e4d117589d224ee8dac5c7595b5135818e7520dac1ce939f887133886121919ffb91e87c77f8f97c141d12721a9bc7fa821158b122dc2ed88b61746193835c81a40187d9399d733839c4876c52f4d9c2d626192745b60fccc5cb7816a550a0beca5f3e361454a978ec435a404689b65038ad51b5b3ee369594ae88de7a068f0ce8e276873ba30ded1ac69615906039e27c8060b06c870f5492f45101c29521379d8d2c3ed9f5782b66d6ca728ee17a8874c1f74a0aa5463ba9fa8577f7b4f1e25478ed32de2a67c864f11f7276eb249ace836eb7c30137fbc576f29a0948a34c6888c9348b96d90587133905a71ce861a5da00d810b7ed096df843d4b3eb980ce16e6e760bab54dabefe4172c2e36139d166d893b02a3f02f751e10decc322445f6ea1cb2eca42b213bb580baf49fba5aa06f266211a2207ca7982e6d6feb0b9762985ffdd8cdacb48bf507255c23578f2c5e19985b0c36b29d10b48858c736dd42785e81bed45f0bdf9987266a023fa77785529f298d7eaeb893fc8e3ee84c48b28c3982a3bba720e4fe07266d50bb7c283da68e16cac34a8772ac6a332aa49dcadcb393d54a819b2ddb472aed469e79034214a67cffcb0b2c2b8199542fdfb8552086f08917e713ef506413761e20c826f7cad806606214e3a99fa918207894725bc64d6345a4c961b3272ecdf75bac6054179985e845d7123878af0c86c7c958bcb57cbe8e3ae5d8eba7261f9225d4b485c50b71b3c8faa457d9f4a9318ea56ef82ebfd910bf65ac9c957fe979c61b9bb4a9c5664c7a4a89c40dd96b215ea48229c8f12f6502c1384bf7202fc66868820919806c52309a1f245a3a564fa8cb580938befc7ea85a45c1c1a1899b9b2e16a4a02a82b083ee4253c93836e68c3fde45311f143aa4dac5ea97229a236891b07df0bcfe7e63604752743bd197452ef33ff10e3f56e38f37e49478cc12b7ecc879319f723575b8cc83282fe79071fb5af2d8ee6821571a444f256a29724c61b4f8723ec3413ba5b060f11e531b4a5d45a35558922c5ebd71df772a2f8a4e2c36fae18a20afd3f86a4a5164293e1a5beee0d22a692de2c52e28933adaed3ad0c417f955310830f77ac1accfb2f62c85537e93a6a0346410bae6507fe39979fa7654a22502409ec2f05b18816ee208222473289fdfe704155e1904951ba416f8e7be664b4eb1f9b9ddb6cd5fef8e7de59ef015a166764dd21c7ac0ac879da72eee4c9d2bc44de67c3cb3da8dae2587136af8a0d0eca46570ad9566120fabfe6ed0c2e9371af92f9ecbb7a8e827bb3209bbf0f1c75aa403230f8ea7288ba0e4c27e4777ef02afcc0254817a8b2352a8868be6957a852f1a91fd5db723f5c41339cf6217a281646a9474da3ca924103792d3337dee89c3008ab70677201f78f2b18ec167dd782df025dd3bc9f861864de70c885cbd55c3ad34b097548b41912b92ce89ef501ddaca035d3f7c9a96932abc9032131efed5f897bd375602156acb92be32a31e9b4663dd8d9e863978dd72141643c588d8b49b8db934b72e4da25abf58a7eb0b23466e94f5ffe4a5349c637b83d8f1dc7c8f6b329a85872286b3bf149d186bd3796875a9775cd9e2ed4f3e7ea49cbd40adf3570b87886459783d58939186b0d8668542d73c0f0e10f676af161507f39ffe31dc880f75172034bcd060c062f8f3ed2261f6a634e164a52c16dcdbd44e13492947c11b3bf028275b5064f4d043b7c5bdf20dc6e1689ec5f9aea0a8b062ec5e455aac2235b0b014943a92d6e9ce56ac2be11a1154b69c5069ce70e9f750feb56841073ae19416254951183d52a58dd8e9c9216fdd63e07230cb9bdfe1b30f9154baf5332ab72660d56630200f8705b3aeb89bb7beebad3192da6857b815b3b8eb0137ab8ef724fb348183e071f5df6dec9d563652fa11db65cd5f691fe7e575ef8da75c3446b91ffd1a11e9335c13719dba6c6823ccbc46d5dafe7198d808b2bd51c23b6153399813db2deef801f6b39ab94772ef99db0fc41275369aeccbc2524f73aa0de2408a13acdd25d2e85c3485d07f7d748dd9ad9ff019ee7a3cc033bc1f022b7f2411521cc199f83375e964601951c21ad84e5f3b47c9da64d2a727066ad915e887249f5abfdb085cb421c1ce6bf5461a0e72b13cc2f432653ff152e799bf5f5ba72967251539d11318ea48f0f2908bc9b8186e3ff91627e441212f6ac31239072131749524d489bf19271fd62f78739952645432761316fa8a55c5beeac353b815c622fb7e38fafbfccf4b8fbafa79acea790eaefcfe80c3a4c3cf976f365344a7279bd374122062a6e98dc289e8c29c00c6e726e762a873efa225861616bd55572a9ac448a77a7bb268011a445973575f3a0036892ea0372688638773524748825537dfc232f66e3feb22af15602f458eabc174dfcc369ce95eafb7be4177f98725696123d7ec9332808d6fa443c3f20486134b7c39cebe19ac171e6d116e66572f5a57f732f0c5fd785857357bf8ba17a8c9a5115f5327e1d28bd68281758bd494bc0c622358fae58c15dfdd264909d3f20fb26d0fe952f342c0932b536e3b43655ee8989165172d5073f407365d5e01c3bbf7728dcef055fa925a77de64249728c667ae893a835c4be1e0c9b811679524efdf0c082966be5250b505acf639072793468a14803f884a2f69b1ea4df4881f3d792fcb5f8aff574ec66212454fa72e1b40b3d33b12930144dbefc83a1f436de9ec40e7c8e2e2ae1b74e11628daa06e2b5522d3aa3074d4b9a1dacbab25224d0cf70f8df4b98a14476d4338c57d972f905a8269b0c3639e7af453830d055052e4a2fc664db72deec4ffea0e8d5b2729cba9561d5e47a599962ff8d697ba66a75f4003aff846a16683535ba0d390c7261ab5439d5e0c19b9586865bd7d0443fd148574b944b4156be9da2b4f36e8b274a2a78f758b1d5954c28ec7b0a826ac371a16e4ca0b57de8e12ae88427a4d45494b98d055928b202150c04ba0f4a30fefd0f1d88cd4f6ef133a87ee942838c613893eefb2e6fef365a64fadae2947011ebbb7433b84ecaa38871ced7e64bac72b54bc95aae84a94e9b0b43178d309d1cf7f8faf2b45fa3cfd73f63ee4afd2372869a88a8cc619889f2b97c8debafe2be90cdec3ca8bc98b18b222702a554c7722f91e223ee01869fee6da74c12eb9cce1b4c2b4372c417eba1b1d15da2316614d6bf0592d25cce0acea8634778fea3b5e33c2daa159db49693f228907bf157722129f18164d69727100b4114bdb849690dfb69ae565e85d3575290f25536c82150666fe40c064a96ebe7183c987858e16c5ec3b64a03147e9d22434ffdc03872a7630fdc198304decab2fd140beacfeaf189a21626e319b5d575151a618233727826ce2f43456ef9e64af40dba79bd7a637a761d96a2d3c77467d23aefa73972ada3ae7cc6712b70e847dd29bf3e3e425b4a36cefdd153452a8efed539369e72c217f59b8c051da41e767b0b24510686983a303a3c3f27924aff3ae212ace272c770af6a43972db7a1e70847f88cb0926988cfee10756f2d92d4d1b704e4ce18cb13612088afc12b4adffeaf29d8ff0a6fae7083512122ef36b40b1c3966cf6fdf810e6627252bfc733ad9bbfa68c6288b60be783f8a4491dee072d8926da972065d6edd27e28c946a1d3a0c89aed8b5b9d7e10c5246e84263dd8b29c2b76a72df7a5c54033145a5c8bd576f33e9ef7f5c214208a18ff05ea1f9fb8d46f06772544aea5ab1cd995efbc22bb5c1f0c9420914eadd3dc03f6280d75fe15412a372c2cc9b98adc7ed88c30e4b0208fbd90b15e6126c6814fd5cb54a2c922514dd70fd80885ec9e6064577b37a4f7d1dbfabd81f6bd562a5b0249d5376566cbb047229051ed44a27f406d61e67fff0a5e7a831cb66a86be5b73e76a5078b04a1a07226b328eb9b8fb6b4abf2bb67052668d0367d916e59fc8773f5d78f11ca8aec5e21632efb4c7dffb6f6c299f0b1fdba9b469cf454a70a750a38fbb1dd157c767231e74af3561af0714d8b676e6bd775ec044252c807b8882e6e0c630bac9a8872f474b130b79582e6b114628d5a88186a76dd876ecea26f8ae361945716f48a5f33e83a9c582a4ac151aa1522b25e83cf1cf06f87f92fb2ac719e90719de1ce371577af131e95956b9b6183465a4da2daaa9fb3a74376095038201d4620a5f92f8a98a36361614862c234a0207d289f3d5ea6a228bafd041211a37d30205331721f021ed1937ad7fa21bede951ec327e835bab0aecbe6f32ebb9311c92a7694331c6fb7cd194779f0da4f399e7d25289f0cd6a7a8f2f3da399ec5daed6b72dd725ec4db797cd8b5988f41bf2b7258d66b4c27968288faf476986f191895d7907269327ac8fc51d3cc07beff66dd12c6e52516a0b62e4ff10946f3824f5626d1023d91e26d3b138678a8a72206741a98cf9d6a8310fb82bb1df04795721c7bf41fdf2c560fcb87b42fb9c0934dcbd6271a8bbbad7fdcb22aba46dca18a9d81ac72aee3fe972637ef5f381cacf2c8a116b43252e1e9349512148edce18e49c6e572a1ab8c4537fc3e9ab35b6a6bcc8f89f173e46e5f78ef05e0aa14f108ee9a9f7211a9f32ac0754ea27f85364eeb359c10242895ad0266eb5e2d60bde88bcaa472881cc998c79059ea990f8a61fc845a98ed3dc41506fb9f2d7e2de3c61d6b2572f73d2284c023a8fcaccba7956c3a10a96240fc9b2eb9944a57cc94de2569df7220f29cb5020abace68fb1808b6048ede856ee8aa8318402b9dabeb6374e12d72e6a03e51d8cdb4d7f8376ebdb32b1bb2e0efdcd07ffb394145d4cc828233407204e574e02f76c1c8e21cd0039d277eb33cef6cefe7ccc3cceb2ce81b87e7bf725ea39cef7226091056a19fd6000df1c43f4bc1235394a22e035a13c5c127767250e0be6f88d4411638b52704a27af78bb708006ac980e540129059459c387c6f0670e7ba59705590d6156ebecc80d30a75835456472248c1854794f8cb2a8248fa2a7f47f1a692a5d0e1a47bb92882304171839198762d4d83f26b69483f227207bdac429e37d27566da6d20f9817d8cc4c41f9de98f99f1d2fb3a68faef8d65c6a9d8693f56829356578356764b7e1b5a4c4b6798314fc173226dac59cabf718a64a7c41da90317d99ae87fb6d7b71a4bab04cc929f6128ebc4eb5058e3927284b71bb09be0681d627b95e596cb34a75fbd94f49cfdd7b3366ec12488e4ee07c6bc70ed8dc3f17e229ed7c925ba484e19892a2d17a6e19b5b7483ed1572263b0994577959be8033fcd08b0d375ef714208d8f0da95588ff0e321cc403be9d40f27d038b663f2ed77dfe3ec0ba27f4af5c6df646ef9129b83113d87648f79e72a0f2c22452ca190e656ad5eccb32727129e982269e5d388d54f7c4a17462565efdd2f3f2a3f298f45239791ab560f173141dcacaca4bb500a8da3462eeda7e72ed7b3bd4166bb22f1759a36e9fc85f80a9f07857c72b722eb2523c7f2449b02a1e64a4c2945eda0d4c77be8d38f6749e97a4702f0a7dacc3e2eb35d08bf4727295a655918a07be70b6e9506d0374db22d49d083e654c9ff50af6e3e812c8a2728ec81201a7d7d54c2221a33a56c3472359daf13d92956d06becedb50745f1972c5a6333cf60fdaa66a92cecf30b06e4bec276688a1d85b7bd1db85d425c6a272515c8d75db1b8f9418742726b340f6ac41a9431d10560d309373fe92c9653072d8c0917b24507c706019a6919df7f336bfd478bf6fea967c93f3cda165cb6172f60adc6c6e3ed4c62e65da4bf8236cf700ac1f3d9dc5db938b4826f2b5a35172a6ff26bfe2c73a18d538f368e401ef68753b586183a6c0921ce284704a921f6ff247557365295c475c64a90a0878969920d49c8c6f4a5115ace396ab9bcc13724e15b6d9840624c2616126fb798c3848c5c6504ecf3f1261946d33836181c472bc450f54f72860cd1751b165dbb614cd1662a5b89bfda001f5d26d3e57ba1c72d7de36c44553779698fb2d4587a11220e75bf588f3527f07dc54a768f287a11344608eee60f4d15c7af920d225b3610711f7602df45f4311bb149a510bb8ca7295cddfb0fd8f863a7cfa0e13309011bb09427840072de7a4031683b68ab53161bce404fc2b62c3398970ce02f5b8dcbe8ffb4c930a214f9f761ef95b99d4e32356db914527d37f5bb8cdeed6ef189c6914215fa6487c4d26a43e9dd6f0c1e0240330c3b495da4b7eadd3d94df807be491d2fd67b517398d021aea0f34f1ac10d210ea3ff95b5176ef1af408a0341e75497a0c02761efc9efe104b365617fd472e41d39d9e3055e15745287b55ac5293b3c4c3ca360f933edaec1602887db4f01bd500091f6c233f17f1462c1c896eba1249d8df7355e2815af346a5c03bb2d1845b4d1411330c0c37e75952dfb7d7662e16993be51d34cbbf931fa7fa55c1372ce7389f71dad7e185b567bbe50608221e7517297dcf43cb3f183dce478fca5669488c635000c1be1eb354c4887a0985bc093d513659fde1dba9b6c9c2a3ead1ef0288c47f212cd0c0f08c15f27b3e194d7f9e06cec8c7f0417f65551d307b614b21da5bb66a9db72c4d7fdbe1f133759240a7b3c20fd9d57962e9ebd491e7472279861239103bea7fc9fd4979ca80458b0035207f8a81daea3f5b35cf56ccb063e6bbe04b0b70249533b8778f0f4adc79e3b9212a0d071f4790a3d6d13287a729a4238c6ac79fbba118cf9c54f2180d60f45feda4888ac45e53f52e449e9d6721423ab66b5fb6be97f9133177e6ba07eb212003f95239f4eff74c6b1b7501772e3a1fbc1556f03c39b41cbc5b59eba13f955cb0a0d0b004a4f1b423ee3103c3758c9b4ad329dee619f82e79d61953bca558368a94609c27a489bd28b2562084760fbd764a5f5a2fca3da0c8946a77bf11071e12100361a0d5ef5dce5719999616c9f25dcef55980721d8c628e1a3e921fa0d9450e0e3a50b404d013da808687236d291f52145d5bdf1e4f86acced3cca4e9738fd22ca07cc2a93985b68fecc72b4f5f0e2803c9698a897f0ea41caa67a9290d9e63f0f602d17a836732df3f40a4f747d27acd4a86568806b5be73a6d28b7ba3e5b1e8703ac51ce113e4ac8ea72d6b48f400775ba24babadb69e0e0664563df8a9163d9fe3475cba9c19d25657287006decc9eacef66e1d8b7059303b497d6c97a7940f4754bba7f9ef9296f801ef835f8c940539b0fe5f235f020c1a560c004504d71830d6780fd7d5e493af72386a262e61d5ab486d40af1b73478201b8e0386e46746f1e7cd2aa102e286e7207f5c65e91f8a341592f6942f7b8878057542f24110cbd94f76cb0e4c5d13c72e11794ca5eebcc67cc07f41f41ec2094b171da807f2f3820b8b43ebe0d042a72077da80d4c4eac8e5f33181f5e474e58a8045d5469eea5073105e7b04c78db4f7997764fabd99d1419c7526ff61c6df669e70d31ef5b01d2d3c7da271d40995bd1fe1383d094d6ff4ca32a085d1bfcac606ddc8e206fae066a405e458be87d33e2ebc176294aee5505157f4b7d7978e73f935eb88c1f963b44dd6c10d90460729410cf5025e9b7c71311b38253703c34d21b49499e5e0f488d36392edbdc1e726b424e16f6e98c5de80a0d4ad793e8bb19f71fd157892dc3edbbca68a79f9f72341884ecbe822b84fbaedaa020207224f40fdfbcb07df13d4f2a72858c986d721bb9613d2b1ccd68f01bc65d126a8f9edd276979fc63417b5d645a10d52eba0bfb5bb0d5c83b9dc6f410943618381ec72a72b1832ad648e53edb6abca4bbe672c8474c0e1710f24ff620f2950fb120175854ef40b60bc95bbbd143e2f2f215727c9a76fe391baa7e9c45ae1c9cb4c545fd28420169d16d674f154cad5e6d014ed7b4289b5673e3f22ce7067a7dc71de1b0d5aacbd630d3d1b6b11d6892cbeb722e1749ee2f20d5a01dee1d1fde4cf1706507f71217351c972f63a96efae45f72de78e694ff57bc88d0c85d049b8225fb7dcb2d4717bd7494caf3070fc59fa717f734ad74e4ce2c7d70073d5d356106de6cc4c1bd296cbc45a0fc620e6862da72e6f1ee51438a221f3fdfa9918084202dbff36b05e0c987b20cc762c5f9255213a91eedaf67ae15801592e3fd25b994bcf5e61c3b59068c7196273bcf9e8aa847c6103204b5d8e86b516521fb7a05867c59cf5b6e771238596a41f646cc247772a487546fbfd457b79c6a0142385d664c5aa02eae458762bbec143c1ac75d7436e6466424b7d47a8a38df5a040195a1f7b59bf35e4af87d7078c3f812dee4c83eaaec610bcc33ec6529679a1b80eb5ae68121fdb7b2e595b55512477d9475675ddd3ff2d254147d8825771ef4b6ab8a2cabc6bbc6884a8678eeb244a249255672799e7e7c036dc5470cc06152d71a717989a9baecb0a675cd1bceeb90cbe971206da65ab1f11f9bb5c53405c5058821677d54dfa4fdd90fc1a3caf838a5488b727a4c11857c663b4b81b6046db6982d7715f830209b96a6d48884c7d233f54b1107c67d49e13e708e588eacad8c8eed20990d7911124ac6659017580ed8b9c3725c846a64f398d9a87ac3bbb3352b0eb065fbed37f744288fa7337cda486942721aae6318ba10f074a865dd4de5c72f1989b1d69221d5e1fbcc5044d9507a45724b99433495688ccd06ac87a2e905fe2ea30100ad2bb5c1214afb3fec403b2f72844c0bf8e4f65b4371d339b4714d88632ecf2c758a6438049b98884694c00a728f91612e7d21632f43b0a3d1e68a643e917d16dcfd2d32c8b11cc20d2aa3d172e39077b84b0f9393b49897253c6ff301add19ce2e5be936373035a437a952c727fa0c0beb88b3dbfe1cdd262a64da1a3f1393bd175084dd52e65f1a638c9a65ec6e0d5ce01911f3cb621201b785ab0c200e5a6e022ce7fc9fd656148e2d97b7265eada78fa9148b4ba2e3f9eff0db3348009a14f33ebdfcc46f9b837ff967972bcfc64aa29a4d649867ca8ea542b34bb18345a4efdabffe89ceb6c55d3dd2c53a634edcd24873c3792ab2da23f3175141b7999316978aa3e7bc48fbd52489272294235b46a8a7badef8b672c146d147099506b3f0bfee523264293da59e04f726149f733b14a1a168b9c975302e25e1302018e69102e51bb79f07271980c897244e0e4c9abfc1b05dea566e3b3f56f4651803f32a6c0b7775a8ef990769eb758e05111777d3ce758b8c112de17e2a92ebf77926c00ed9a37d2cbc90883769a7253d7193e690bace66e919af1b607617893fd2774b5f884997b965286dcc1d1000bcd42ed71688877fcd583faf85c55acbc64cbdc6e561bf98515d85765f69e16b87df66fb8a9fcb29a8c8229b5ce79cb861770fbe13ebc0202383093d409757205b3407f72d2dc9c363675c9da18fa799094c1bab2cf3066b6629d766ba46a72897625be34b831ced59ffc159c29b8bfb0c1518fac1a0e3f7078f0a2b125c472bb4bceed4caa916026532c2990e4afc07a0b51462247c75f12d23fde4874424c1e9e4329a0ccff5e4dd637c50fa33d0a9c9ee0ef55c79501d5ae398a2730fc3a212745b91ca3c4220a733f58c3257c6e827562ddad55622ebdc68456f78ace2b2bf9e422622d7b4fbbfa69d5b5c46ff92ab6490bf794c5dba642d6c10f354d726fd8cb597ab4e2d1776c85a184bee7a163237031689c98e79dc70db299d510720ef6b9b006cbb5fc31acf4adea5402756890a01caff2bb914553093867209072e858d9defb28b5f6ce5d97483f6253a0def2fe7521e0827b6f6f111334b83a49d449229634fdb95622e8114aa7835a0dcb21a099788f33a944736ff434ba1730b3944fd2dd4e38a220037ed507de007a86ea92b236abadf31cacbab723525672c69007b1b2b42bd345548f635612774f6270f57a318140092fc34d3371834336e64a12fdfdce6fe5f5228c74d9c786de9967209cb85a17230f5dd5d508c38372956025cd65d0d6756410529b4f21232ab0f61a347603bc51852d65c450f53b6b93648bfa2e5cbb5bb788c2670c89b9abbdac969240c7a839166e8c84251eda721f6e78ae3f696bd9cd835dd8ce69f7ec2923d6c54ee0729e1a38f751fc57c572c7810a0d5e3ee4b64c5800c390c003ca83b25138e659d96c27a7d610df902c49f706254b032490007b30b8dc814fe4de3bd34763dd6006ce04e24cddd171cd7281b73012b71fbaa5b3929a51f6f48b1ec0f67224099593a547f414399c1f8672908d03e8a7fea610fe94bf31d52c0e56c6f805771c89ec92372f2135a142c137ac141110abcec88c21faae4bccc36c8ba62251be5d893ab7eb10e56e4265d638afc2cc76a9b079632a119b8e49bf7201a333f4536e662bae4657a85ffa66697276ec10b489bb5bfe614c33e3c0f65c26403ed10fdc05b28e3c4e5659555dde0c758f2cf171d9a49aeb781b7117e3958a96a7beaee51ac5987abfd983b3f0ac1ce1332f412e9e1be211c80cd7ac6e8c27494687d8fc8eb83731e3ecc1385c340511af40662e606213e48ce75aad3eaa5ec0773ae19cbca2d7d67a1217b434aa726c5c5e60b329a2fbabcf6256a432fb7b65071cfa03aea38a17e8c2a700e5652510cd20e3a61dbe22ca181e10d7033ff703d433506a21da337a34322aad0a7d63079c6739e05176e2617864e5bac4369a18344f5c33cef6e1510116882a153b72c61587ab71e8b4d11a50d6ca8ac7ff5f3f6ab31a864c4aafa6a9168b1d5991215da30c16bc9c47413b78f65c7c00a5c38fd3e1a123318e975d38b1a441837e721b02baca4ba579d635ae40c932c475c54b8cad2a098b9610715b343794072872602f026fa8ca86777be9e3dfa52f182855a22103b08f7840cf961a0bf2e9a272cedc00f31e83123cc1668465a155e843d3487c9832aef7915cb0f1b54c2d387229608aa54ecc8f537ec43cae809fec189a479ea2d1e4f6c4693f8c23c699573da65357cedb3c99b60b8d199addb95fdf254668efe19b4db1274f0621eff74772db6cedcad87a927e2a1e371347df86e4483cc3e3c15623d337749a7c16caf87263415f13f6d2da87a5bee579cf0b8528cd4a1a4c069db9f66a8fc4c3bd9b09723e57471d58299d21d97a7d01af47c4076a0765d5b26844e75953a96c4cd05f349c7336ac6a639438a32e1a7bfe54acab3419e274e00dee04f9225150a84f2472cd53d163e1cbeab5960dc4698e590ed637399b4a8d32899ea70cc3f2c65101030b113ac5ab5b554020ed0305a0004e27b6084552453809d4ce19d4edc55e7572183b0bee2a65477663c850ce61866a4c881377a717b22c337306095e9c7aee7016af76d10f6dde960a778d0ed3b4307c210e25231816e4c969e1eedccef70572a0e08099b69011627f662239d8e93207fee22d4fda310703cbf3fbff1f4c095d699138ce52b863829e9b747ae55e368efb3388dff98c2dd65d5325cc59f37b7226b4d4138f876cc33d7f9457e1cfffa838bfbe9163ea92f4fd981c4be63cd0720046c0d8e340d214a4f48f45f609d9bfa993dff6165941f73f8f8dd8b9df6e37fb2ffaef5066ae7381d817e3279b98053fadb00028787e2eb33f9d48254aef2ced9d63f231f379ba3bbbcf52a12b6be70d950b311a8ad8bd3c114350ae701662b9896aedbbd987335fe94d1375fc2fd1350a1a92d2f0d0eb1d6ae86dace8a953bda30bb94048bf7e0dd4c81b9911a907488c281c43bcc196fffa4c258c00d772eeb21578db5226fbe243c391b2014ccaf6126931f441595618ea17d6cd69ce51dc0d6773f8834bb958cb7b6287d0176f5c69ec44374b67c6ba3396c22fd89a72854aec2b2a73a1c8b4309e2731ba79b6663db708fd65f1613e62d304c1e07e72cbf9fdf21f33aafc2e1a23f1353a8bcc7bb68da26e0f1e89a45ff79e7d06dd72639d985babad94cb4e3ff74b563f1bb4b43cbfd21f4cfb68b65f875a66420333fad50595537d51ddd961e7c6ffa01dbb18ff8f50c95aa705b7355a36902f78725dda4275766809ad1afd062e2c64810d40b71dce47007265deaa0ff2de4bc2725987c457d2f73eff9f84171795871052353e77e91ec8167ba397a95d054ee76cd211dd8222bedb7b58f71fd8e0555f0f6946161d2da4aff381782488c1e57872ff750861dd47fa310582529a216f05a29ad4cbb3e7d1893cefe7005c84723a72107929953b6c1bee867f503db3995c8346826e9b5b66b5d3b0d27a49b999b51b9f7eefc2ab6323da3468107a8fadb81b990aa88b7df5c22387db71d7ec45f272e3a887ff12bf5e38b051dbd49601a87cd16f2dcaf7889665df74b2b73aba2e0bc93820b4453cd791af1735bd76a0cbc509c1de277373241aefe8e662c4beb2514e10595537b097eceb53c3d3d7f4dd808d5e85e860df3ee2251094f25924a7717c6ac3482aabb5f7846af7f7e19e76045f2bed2cf9e1bb3182621898cde8ff563400ad0e07f7fc07c31d9cbdb180dc69a09ea1aee8da31781c5b4fb6dfed9a5ef33513f800a7d5489457270bd44b578bab3cecdadbf52ac110ff851058f43a1029be7da57834f6480b9ec85da5fb4ad903287b7c8ddd4883dc6179e2649f69024f4c43ad820425921fe11dea2eaac9d2c43cde440bc02761ac165474f27fb961d74ae72085341bfc8c63ea705e982dee0142888990eb3be41c000b0900b6b672c746260857ee4bcf73ff7b280a53a6dda0867fcb1e2e0f9c3840353a3969230ed36ed3562cfbf225280e30b73f14c0970f662e0c7db6f9dc5d0612eb9b5f8b72de3f82b808ecb85981b1369a981342316871298682608b400838a50b2911b072ed1b602fe7e4da73baa2a47ab4242949546dd31baf31dc01f9d1895f33e4aa5489bce5a19ffc8c004d21594fa0b4280478ce79bb28b31dc355cf0c796a77fc1063a539142024758498294d43c57b1eae84681af33b17eb6e80c3a4240ef9e272443ff0752435672213986da64871eb89ce66a685b1bca6dbaea3cedc7360306fed9549e1029f07b7f4ffcdf63a99b5b60b4bdd6931ebe99e50817fec576a0026f3d34d9967ee1ff558922c89b51a6f3994098701096f2841a58d8ef2b1b365154bdd7e146fabecddbdd26f1f2db9a8920c849ca8ef5c11758656d276a62be572854b8f20a02e1d336e76b6ea137a7d47bb16ddc5df54869e092c5b1b5d39d37247a7925db3870f797b56197f44e5c05b3add54415929918277807967e5e65231cd517451735807452422802c1225205d2db9e0f20d0aa465a10174e17efe3772d5dd340f17f13dc2a88766717537adb2a05b7de727d75c9327eef6e29b783272e82c7a58e0027d7d15126622d35805475249fbd2205f1ded19efeb973782b872cd728541b55b0052b2ac0a1041d7d37484a62934dd209d7dc040036eddfcd33772f4d78a92416c2af281d1dc05baa86d608bb0e5fc84462e6e8f3288438c84726a5984f52b5ee70ac1bd78a4d6128263a231f99e85875494154a62033129d36fd773371c9300dd402781ae90e8312bfb83b26b5c5397c5f300f308d5dfe73b4ece9995164e36592e8df7cd85d2c7bb6674b8b6d10093e27f068ad566fa6b6f72145a6bc6b6ece2e04755861c42f6df195deb4ed2f26cec1ef5cf0a3c81ca333114805b307cd4ae5b1ccb1471f26bbd81b65253f6418d44b20789efd5fac17872b60a72f2e31167b04014a19601de3af54e30032cf86bb5a6800a8e800a5ceb72e0399748ef1ce395332098d9d22071bb02ae16f0d66b87bf6c81b5c207f52672cda8eb40132889e4f92c994a493fc8e188236f26de07dbd5bfcc0fd64acf88727930cdb9614698e457285af8ab17580267e19f165e1dcfd555a1021fd9b49e1ecc7274ca75532497b2fb83a5d8e261026c6bd2edede2afb0042a61111865e85c3577baffc4deb7a3672e0deddf0e7d92f3e765a0ba1eed98d959a0fdff6a6372807f9f818067caf9e09a9761f3e8dd6bb7c8aaa5f80b39b9a450053a1a4e387201c4ba3344bebcd932ab2881bfef6e0465cea33925df81c3d5cce1502922186ba60aafd25b1244bdd824ab50e1e140aae11240e9a5480909855be088d3865018b1e39b34f2c5c0125104240af77f386202827902198c139fc1b37e38faf0b6045c086d5e495ac06ad52d0dcb9639419b32c56f79bf7cdc9860207fb2dd91de631c93a1ad74ef32c42138e5d2a0887f8d0f47d303d22ad9b6c3a309e6b8eddf7211a033b0101259690c31951227d9a6677391724b74d0aa18184866e5a74984514c5eed4400684b112dc5ae3087855e41668f6a718b78c90269c6e23b33acf1320452ba0ed32fb3c97241265d5e4e43a07ec52bafe0b42fb6417a372acadd0a0f882b6370ec87ce93b398d01ca7985a3a09f9f558524d301bd1fb956c3479117257dec4129f01066d6702d44adcf28ebf70aeee7ff2ed536bbe73b8703fb94e72f0bd9a334b4f5b226a36572c61a8b1b99d6ef82dd963df58aff92b4e5d9e3072cdbf7a32b6c012b1099e2573a2a26a7c57008cc233484356f540fa0fa62a1a7277634611dd6830aca33719a786b1487d087049d6c94bf0bcbb0496a207cc1f6aaacdedab1866cdeaa09c5df0448720347a0263c39c74f790b03887b9de46f672d8a1ed447e7f5b7c114e580cd28294d2c1fc421f7e7ea32c498a10966d0aff31e58557df61ee4735c1ee11263598bc8634152c267e4278fb65fb3ad28c17030e483f9e05c4dc35aa79715d1de1e4c023e13a63ae0c4270234cb32676f8fb7872868209d877e3a84124b82bba99c00cd1f6e466ff726aba1ce2ddefc272a13472c19e68955686c9363c13c63c9457f56eebf53cf194c1eebcbec62a267db611723da8ff34121661b2a76755589a48769496107df006cde42de04af31f1172d6727133a8787b57e870a38f20a0a0bd80da5a0b3d04b1e9cd6cfe7fe91508cd8f72a64b5495396733333c3e1c3ac1d054dd58cb8c7402c80d8df6959ed39fa3523844526c7b1f079a6b84b39e4fec973a2a882360fd5016b28c85f4fdd43323807260381b12bb1d7b94f17955e2ec098f2f285466ef303c6f77c059dcf3bce4c224aa416743ba20081c2c352204553075ce32cca449c004ee674497cbecc4c0b872131c5c10808096f644cf3b4f27370c647a4fb80b956311aa61818924558ff03ca73b2895c78af465c30bcd469a70ec2c4becc2c3800b7204a3d40dcbd62c3c72cc9d11f46922cae116fb1792b93cfd40a6d704dd5bbb50e20ffd43488dfdc372fb39788743aecbb594b3f45e8970df13b01888ea4b0fffe776b43432753076726378363182f9435b93a6621785167a22627d509b14542307f4b545d15e6eef620d49aeb192a56a0d642757439fcda49eb090c25b752745460ea201225e649d3aa0b8b3f7bd3e154e89bcb772d11d815ac553b75d6344930283d9329082babe50b30c5ceb406d9d9014c1b56f82cc152079c9a0a4c8a508a4bf0b6867486e577277fba911f8abe38fe5212b0e80f29d60951b9db3d73d696243931f8615c9432c6607becf2acda22d2bfd93b415bf3798e208879a0c6cdc91707ca1244c82d559ffd2716ce2bab78c0dce0fd15036a0c4778ab506c734ba4e094624042a879b7290b57d03da417de4be9d8be0569ed206103df40e3f2619a22748507e5ae4af72deb3704b428a0eed24bf82cd0036ddbd14ff238586f08cf8329d0c57f8679372d9aa3a3609ba6495e59dab9c462fc6028a02c7dba01c31cb9e35b39f4c7e5727ef614be64cc1b9e41fa49c98eb372631a32f8356633b4e06d30782741481fa722a200f00493b8a2d01d8e65ccdef05a1b702822fe44ec2540ac942222f49d672e2251239e2a344470adddc4796132ac1dfc29926269e0bb48363174fd6fead7259714147f6cb0265793ecee9fdd022de23e46794cc42be0b73210c17ab4d957240876585602b3012adc6cfad8da53b15dfd7fcf2647c8bfbf0cee3edf9398572c02f06588b2777c3a0834c8217e1e91e865165335c3eec89d5b73c2cb8ea6c6b0db6589de5a94b54bae042045d998e2f1e04299e2cdd2aa8e010ebeaea3fe51b20e7e3453a517fb3cd311719c5d4dd5cb12a0edd24bbef6ed3f12ff6e07eea72fd1271f144d65de48bf048233bf0941371e89dee69040b9d355d2d828be3eb5ad8f7ce55968cbd526d88bd9f94159727dc2870cbdd6a80d48d9e207a4daf8850203d30bf61f699320ce7bd99e98ef176573d21ffe0b0a92aec5153335ca640720da261ab9cc3893684ebe36b20a2648cc6aba9d78d4d71f93548757517c7481a172ca24a175f508556ada3d780bfd8ba60ee49c7888f976ec28e1ea7b2182472e778a388ba3c55c8caffb6902bc21a4a4e5ff416840810b37746e4a3b5f3eb72d505d75bc070401bbd71f230917f6da9ca644464098bfcc72abc598d34bb3f7224a618827fa8688a895f792114f91738242f898ddab31bd63e42ceb7481c4072481d748a2b8755faa5c4bef7c84fbf2bce4423b7b7761f8f40e5b0c5d2385e24cc330afa2d8ca6a2e7791a71bb523dee89fa05f2f3294e4682032fd43b618a629d846d977bf219861c2e1d639aa9a5818c2e9afcb902b01e889f61bb8d9fa35777a5d541f62d1c5d40aef606ef0e2ebd6091f9fdd37ef4dd728c42dd73d55c1516a8b616c2f83c9e6d12961dab7d1658892e2940302cd59a0d5d81ad69c5b336bb35977663461eb8e826c76f6abba6796c8a4074b43335d61f2101cdb5cc3338944c7156615e43ff8c51a9a6db121250af6474889cf79a767c92a3dd731e0e727892e914a78daba6e76f39c9ec173f4ec68296f8fb0100f5d05695e9deb10b491c1ebe66f52a97859151a161997af76f69ca0cf1e6d28c33f83f20b07319ef727fa82c2201bf37c7135b8861eb6c1dd8eac0ead79638a9dff0149e385492c5727ac0fc52de4bf5e498655957c3b9a25c1fb78f92b5fcb3edd01d411065343d7205e1e2e1d91366f79c077aa89a295700879120ae50ff2895a71b5bd129c46a27fdeed1349b19ee4b7ac608c1c71fa6d41a8cacdbd2a7fb08505cabb222693172be3d51c575b40e284266956871c30d1df646c902eb842c3b95faae056a738d3742e4d09d246251e8b2fd64a640579e70d58dd5282af601ca6c350d26f0950859be550d953f28bffde16277a8b8f6ea2c220fb32ea2f8774c60fc2a6543d20810adba24e109a432e463ef113848b6db4231f6facc8c73afef27379c4d1743570cead1053cb1187930ce79edf563e83bd15a2673eb0f5b23a1ededd04edd0d2e5dae78b07721faeeeaafd411b60fab07b86fe3fe8c868cf1fd91899d457115bc5c8a03cc038df0b92500e8460ebebc8da73b3cc361c60dbaba8ac64b3938594340e97c19eb225faf62b1a6253582cb383e365de0df7d0117943d03aa9ee0420b05730f867f1fbc0446e7966aea3b4e4dc62310cb4c5f949c308ed7310edeb8e572092d0c208303fb8f928b724ff935ebb0753592d45d5a3b1273a77ff6b0048672bbe62ddf699e789aefc4b477c7af49606ab40bb2468d237da304db7875ca877223973359468b98428aeffc040d39558617e64b4ce95ae441fed90d0660b1a772327e923f4b4a3135520272197e86c39c97df75924eb2e1f0308c86476770b445bbf82c5316adeb72d6b2e9127a0b3b2b3a5a10d5cbe6e53b96eeb0cd057286722cf94fa8fc9d58bb78cce2f21630426238152ae4fc90891cbd15f1de3cf5ea728197c4c663a6045696d2a2b28aab657b4b73939dcbbad5b1b492ebe451d3ec725f9774e2b6b538247ec5dcf844930e7767dab382ce98763c240c0727b562e92e6a54331507e2b2d5213971122437da6e6c8046029deb7a9026facee55cc1b77296d09b7dc8709f66c323d329b388ffb2330f95ce9c82b519aa3b501ad685e17208bd7863464be3c64db3cfc7c2a9d18ef846f53127e84a8261d0251accdd3f165acf4905fc411135a5002b7e5ca835393e5c624371721e3c803911af3222e3724af5ac8059df3982f52a63610d0324a5ca44aa97a12052cc3cfd75bc19c81243b546aead87e92723cbb60f45139fc2b8e8f7368ec3a57943988940c37d6d887287ee8f56bfc69c3b711261b33237a7e8faa596aeb600c1226bf37c1508d7f4652d89bb74148dcf750b59ce3ea551d808cf18dccf9d15fa299029ddae5b16d072b22558ed39d0bf0262e67e34989852e8a5130df1e0674f5872e0ad58d5eb36723974ae5083ccd313dc5776b5f321009ca6dce97c7c7493d763d2b275abe494561a993fa4a9e6918a20bbd7a0610cd56181a479a6f7cc30b99be766478e260d17de277084db4a5b190db7f22db8c802a5b918bf517fad49c43e336ba9637201724b245456df6f2c3152dc69e67119a4961ce86023105c54a926b1bfa24845aa727b917c7d9375d34bf84821bcd846626f73b7fbb06a2d8c1ca49bf6aad84ef031473e971890cff2a02b3418debae410aac772dd74baf352fd5caa8522bbd5be72c9d98b76f45cb2ae081a2485578ba6911841262ef6c84ccde22b764d65d17672f89f7098c3d7d337ba9fc81622f3968121f77c56c841aaef0d35650d11a3b119d841687d8db8460f5f43c8cc6747d94b07476e86959a88632059ffdc57ecf7728d9f96a3903f7552914ee2222944c806f6b3af0b8d77ac50cdf111c90123ab72ad15d3e02d5463e639769ac6c01aa96af3b032a3ee199f543464cb78d74f440bc373943326112a1b351afb90f35632b0dfa2e29fdebeadfcc6a52e5bbe2fa172f5dc9e4ca7b0f4e2a7acd0aa270a14abe6bf05d0f46367b955465e977576837239e7c8f20b46427b3b19861a62022d828d7f74b083c022f3719ce75ccfab6532c0713fae8d35797efc23fb5c8f892c8e8fae37121a90ef7877b089805a816815fcb730054df386bc008bfdbcdcdd3f6b9868d54b84c95c31b429a07229a22972fd769bd24c9198a0c7d26c8206e176924ae3fa3ed8795b505439d485ed723b042fa108a8838b8589a4518c7f7367e52d869044ed0f78105283652020392ba130e47163c0badfc489f9f54e50f7d74a1735d85f7c37380ccad0608712a733f71aefee0ab999a517264b3d0bf84151d574557800d627728fdf2234d0c7d07d8f72eb90be69f13e29e2464ac92b7bec2459c45c200555376077f253dc28c5d551223171c516717076e48733cd3cdeed4ba562a78c2751c8fbbdd135ed4bd97643445ab60b6267db4c4c38012429e1686b9cf5acb3efccbf97723e268f111be9e438ec37c2df0ed572695a168d3d5ab9ad634cd8267643713885d5f7e8fc1fd7a172929fbf04e874eec33114bb097f42e8d4ae0a0e377e2fea9e33a655d15f72e172d1b66b497b3aed1f079e4c364e216c7d8151a9c6d5d3c792945c42157cb2ab7262b4622ae549edf658193d74abd992a0add618138d0fb0029290f846c7cca7256a1d772b9b938e465b079226672a58ac081dd093efa9e706ced6fcd933398c326ad9e6db7d681f1481b1d92239cf17666d8cb417a3bb43d9d25e4095635c29723ce79af98e1f0b6b6a22bfa13ccd24c307010379891cc030371719bc1a6b5e1f607867c794b5bf82ec10d004b663a7a0115dac40cbf250bdbcc880746346bb72e417c15434376fc4ea16148a41af3a70202c677dcc7cb3b10d246bc3bdb0e672bdb3f39acd0aa86667053375e5132f004c40f12480b906fc68b414717f6b37726b9c56911f48e29939a7ae42a3b3eba2ff3500892e9465113338a0fc331fbe7264e0876c67cc96afbd06b4ea9d407edae01eb72eead0c9271debdad8c0bded708207b79ca66d2cd871ce934f9910aad6c0c5f439fd697677dd4dcc998940547290f8ed4e0631abc9bd2d3c5dd4678427a2aa2842e40bd5f0da945dd7b4a53d72c8e05639942cf1aecddef8de1b062472d4d41e07e3a47b6dd9d91b931bc03c72d5d38116c2e3a4932622630f523fcc0b3b740bdb9ae1ccc1aee9103a2cd41072759f2ad0256bcc1d19f667185ae19e34ac3edd007acf27a52ff1c995884fd47206a5cf3f32bcd1ec4d024891b0967b3c518db6753d2ec8ea3db489972207bd72e444a5cec9945e8bdac3f4b2456fc1fa085751a0bb96c1f4f347ee2b254ef1721823d215ccd985f3a870fc749f693142102e644c1d988ce4adb27f637884754b2a93dd00dac7b431eca6f87cee8e38050d1cd0fa1682c1d0738208f5179eef2fcbc130339f895a9fb391a06e37d2289e9b5e8fc72b18647887820d60de1347546d83e3493bfc2cdc505a0e294d183b3db74a62fdb0f096b1b9d0e42596ed396bc566357a7b615ca12a8ccad610d781f11c8e65776556cd90f12ba43f2b820472a39bbb37d727e333485b45b68d3e23155d3e327688735a97380678409bf5f97274b8f110e531712ba579da1c35b2ce1b185dae423d29ef954b61b459009cbb725a6febba8baca3dcf48d6b6058fa7be1832898bec236ee306cbb3be7c5c3b368c00f6338d9d381b666873c2931c0c22cbd7327db107117abbfd3e79f6d7db922646d6fdfd75ff4669735b4a3fedc4a52cc2cc1d70707db2cc323af944229c272dce086a9c1f74ae883a995a38f7daba653a227a87e35347aaad50ee6e3056872f4896120e3c1076ace1a6ebbcbc443c4d5991aad5cd4121f5ffb00575d41f47290318ac39a5bed0689fd1ddb0ff6354b2cc6aaf25f8cd06c92d05262d30b0472c9a301272bec7b8f79e2ef06d69dfa8052581bd367df0d4be82a2235acc65c729d22709ba54a1983927d61f2ec8ec43822a38ad4283dda5d3a6668b543b345194f22eddc093a49a806ef5c9fb8237584c7c53bbb414451c98215c6890a01c13f55725e0f7cccba436865f101f51aac2ed209dbe5e74f83c7ec4ed6365bf686724fd0469f402b3868192a7b4b4bf82ad3bfd3e56097c07f83236293d735e47e6f2bf4777efa08f46996fc55dc3f3d0bff3a0466d1400213d3f662516799249a7228c9f6c674ca495120fcf4d10bb6fdba27f1c80a1e07f3c1a93fce05c0612d72628a117c066a43f7ff5707f3ed0270de1d27023492332e8d1ef62d036d5896721707f3d7b41fcf1078e1ca4140eb20eb46c956efb2b11f53199b986d310bd861aeb3dcac4a593e38117b2eca0be4c325802333c39527c9bfefa3325a81d826728760011f73798f780ea689be09d299fa62262cd41e6c440dcbec803ffc22a6729fb7b71222a0138fa4fe9120ea660ab27e584db3ea41ac74d2db3556df1fb64bbac2d4ea4b744f0374ec16741c0d82dcf7f7514fa3dc4cb0df49a607fef858003fccb2e3ee078fca60391e9348ae550ac1542d00b8900dc7e5ce35cfbf99b17248413070f8852d803a94ff83df52a3a667ca7bc9a2fc04ea2643ab4ef807b95eb6b2168456186bceaddf2d72e3872b9c9059aa79c3b0d1c0b0657a1334d0225ddd3e6b67884651c88a4cc592f1570074ff1e66a556c23eaaf5e218075c339672eb47be2ba993febf0c4d38cffde2fe5c61089cf0e6ee625fedbfbd1a1a0f94301080a336386e45a02063e55234daf12d72deaf38c6b8dacd42d8752b38b0a354bf3c4e2552efb961ba2b03ac6bc7ab7209973a538e4a233ee3d4c2e8aae3307290583c007c1e456926bc7937fd3c785571fb5e5c83074ec3a64f7170149daf3a16290fd0338401ea6ee93e73609b1fa078e2cfa4cc7e7cffff8533d00dcc5e281e2545741bb9118df50f938f4cf809ca64e15d9cfcfca59aa17dadaaead2787210c3ad1ef435f5f1aaf614050ade1a484681d78fc8065d7fbbaef1d020eb50721f0c8b53792c6d018621bc32d8804c3dcfc09526f6403d7222eb1f0f30bc94721764c70fe245ff6c0a26a0385ec78b104745e8fd9cfe998c0beb13f0fdc72e197d999e00172c3d01e27e140d16ae8dc7e5e90579259f5b0f9cd47250774a3b7206494ca9d4adcf9951d5ebf483bd2eec2c3821fcd66960bc1cf9e9be1d1f4d53a6e119be85c163cdab4a90b52905095ef7e15bf2d913c59fd986c97493e87016bab1bc89f62b4fe5385c488da222e50c43954fc6b67837af87d2021578cf1f726bdeaeb25081ab306423b6509b1711f15b7d5f0acf761625017e09d7fc2c952b7ade7a5f112c51e74a3a75cf0a8026fd3dd6107e1793a875f840c201dcd21272049f7654e23032b28782ae889de1b5e05e93327cf14b378884eecfef5e460a727b221c7144e136def403df2e1211692627ef8d605602e000a82233a5385598722a026e28aba2bf994bb4f86528f931b78ebdfbbab80ccdd3c7792bf7deedd372002fc607ba940fa10ae7690fb5d66855b50b08607a9c4745ecb7e7c8215b6672a2b81e937346a08f806c50a857dfa27af8c43d20986b3e0df1f9353d2fb26a60f952cbbc2184792ffc7470c40484e6c873ae984e08b76a1834ae26e0f5954f34d3526a827130a15c30389a9250b4e37a09e000710e8c1a0fb7120a34ef397572a645536ff0965fd7c62247d790943b4e6f07e6a0526d50d7f9c52eb0f9781618f3dcb946808e7c2aade7ac5ce099376a5a4195b5d05a43bcf02da7e5b761613b440c29b95c7671a831e9c967800616a215f0946999e4a0d3264097b28a40e32cd2efbfb2945f045f775879aafd86a610048db300ffe04e8ed4feaf207220b34347726839e16cf6d60f4c72f092ba006c8df60a45471cf4894045d386e2511072d26cdbefa651ff603a40172347b607fc650f6b1a729593ae83a0d5c6d05b4072fbefaaa0a71b3d2119eeac100580429c7aec2492aaa6bbfe2c4de6c28d507572a5b7bc05c48b17e3016624e591d4bfeb507dc1bc52b4657764f9367ddfdbbb72d6d37fa1f30320114b1b7d3ddd264788977d0386ce0dbc99483c83bc9b5d1b222be47e0a4a27b0da7df4d72f09735dd5e0f95b98c6b8d6f7597734c248061372589d2423ec7c539d78ebb5cab3bb6555767ffb0a13d6a9acf36899bc695d9972725d7cd22eeac7cb5af54947ebcb54b6441dc1248da189838f869b256298803e81e78e13df53785c06feaee60dc73d5afba1b727b1f244a46416da3ee1319138a0373edf6f9676982bb2a2e2c0a844453631513cb57661d51711796e994f565b98901a4eb218071ead9aa61aa0d6768f52303ec05aa5a49745eb08f8eca597720af95da7964990c2e480f53458e42460bc9bb848075599129d8bc932404615727c1c633ea390f797867da7e1c3c1e0141b3c71e45d16f82bbc05ead184cf0b72810dd90e6002435f70abd8df0bc4a62e701398fa81ff408c9101fbd277cd9f72df5559813825c493aaec68ca30399570acb4883a2ea86d859b757037a8af0b72bfb1079faa06c0c1c48390ba69e51f44809760367094bd91482c37bf09257409c4ec7f55980b9daebab293fd684adf614c2e0c28d3ffd54ff59fd931d1d4c272f2acd080895535b44b28d81c50f105af36b90791f9700d31334b8e17f2a08d72242ee8d7254b34bd9284f7774a61147889bffa7bc7dbf0cdf74bb7af1b282472740b40a3ded17ef650b802cc898948a6203d203eef04b37dcab322dff44aad72261b2c40ca37b41cc4b0e21515902888006f5918e9b2a2ac93e09d434f03a2724b8629d21b4a3a1fe6ef69829daea39717aa09fb02202d0a3626aa27477f0472b68504f3325b6c0779be7d5b0a9f5878be45bb46b113ccf81d205742596fcc724bbee810afa48aa57b049b5e0d1823870b5728f3b14ab4be40b107417481311ff85bafc8a78b31ecdf2bb838339a7fa5c8befac3672ddc5fede3a87171b99872e9d18fbebafebfc6cc3651941d2f01ed2a7e9fc4ad9d4078f16e57b313a8db0c60346390c59a5f31622aa2f9a6e58c10c89cd5bcc3c202141e22e2a9ffe9dc6dd3b9391d35f36e92bc3fac33274cc0ba510c24a3349ab7a862782d013fb9a27220e8f7e568338c40cbcfdb42cf5d2702efb4ad5f2ffd123b8e39891115d5ba067a39ce36820326678461be24ba66f2b7af920e5f155c6034ec9ada2b01bfa162b9370f8d0753083cac72eeb604b546f3c6bf64dbbcdbd32ad29cb953c3943b726a8fabd7d6438baf99355dc685c2f05823f6dcafb78fb8f4affafebb8e4dce723d2f8972b785eed9c3971a782b1976b33fbc50d82682d7f0984e33c700b2017249c948fd389498fcdfe8a2b9ae588885030709e70666306eb5dd0abec8f51510cf10be6f162491a454407eeee8c3a2bc4a9477acd7db5bf0da4dc36b833d0572096ff1aea56a77a019e30990065b31cacbff18732843a5627607a090ec21a064dfdd4013482260b4d5ea69fe5b231b07cfcacefaf68d0066d40e8733fd6ef700cfd2ec2bd54c658a7a0d4ce1a119179681bb1a156c7955abf103dbeef4a9577297b97a3406388a2f88d9ee599eaece0715a4a57ddb7e75464c0ed33acdf2c77236f842d8988652c9acc06be25318210b98c6187736ee3d05fd1f790b73b59d729f6188c8c2c1b04f7d9b7dab704fbfef97521c605c00b9b8b0e8a6ecd0667d72f727eb86d671098b059e4a3400440d4875d54d859b5c5b000abfe803333b1a725fba05e272322e70dc9717667310c4f7bd139d740190566d0a9898522afd0272e905bc789fb3805e6ad4bc60acce0be5b55e220a82b5d6c528b290ab2376d67290fea6de5490271baa4b450a3c95cae6701130fca0c13a1b7c44ebc56ca4531db9e762bfbd50b01aea3099b4c9ed485376e99eb70e6037b6a40edabdb3f39372d008d3b8d58aeae8027d4973d0c9a9d5de9a3b750f7348f6450163f5aa628372163c45b391393aba4086b1a94dfec297d18847dd3527efd02abfadb1b2dd402dd4568d559b6c4d773d4ca63b8e1f788d1d93ab014b36c5f7b002b18b6d237a72d364f66e6753da6ab41bed480f90855f098938a87e32bdef330ca854a9d9182cfa5ae705ea7047f312c66ac45955276d52c544c87722f4650bad752f5b627d399d162ab283b436a23fe6c85b97bf83930ad86cf2c29c1d382751b440c388c500a1dbd1c73a6634828d3959d019c61182becfee74c471a969f12b349c241e7872b1254f1417705cb4df39ce63bcd109c782b00fc019c0ffbd7b64099b9aa8540745f82bed010012c377f807853385b8131b403befc07a79ba9acf4e1fba2282726d73482b3fd8f15fe9d1e6511180706b213d2d1428142ff0b74b6c0bfbd7ff72ad364310f2df4deee4ffaf2648a96b4069d5a9a407d456832eec288ec5d25366ef65f1468bc841353019e48ec6249eeb69286ebc07b19833d9aafd51126b14722116bcae548e7f5c3761d81d18c935d5f19c6edd5cc068d6d4673df175e31f68cdf4cfcda075847ad8039a9a83257f0f8c3b48599d1c50171ff4189930e8dd729b5d9cddca9f8a7052e3d57fde9c2e3c0b45b4c8ccab66bb12298319e5e85972d2542895a2147c098b51c7372024195a514c234c0518bc302b7130ad8cac9872bf4873bd5dc466054ab81570675c1cb2453db14f5337a26e41c60a67892e19494451c008012476397f3853eef33ea8b38ed28bd5bcbe597701f2e8e0f0f6e9726681c0b33251f88b4d6d863fdb4452e9d7d3fe3161dc9f72abcc1dec7a5c8472cf7e3cb92d94e2339793bcc7cf9a1d1c925fafc8e858354318f88acd0002e05e4e00b46e2aed64113876054f0ef797d9520c67a32b3b845d98fc3acf3f99587274ddf8a9e3a240a58eccb60113d209afb56205db8041f612f527631155e05b690011764f2791ab2b9737ac297c30441eae25db1b842c077bee18c89676d2877270d62eb33cd8ec6ce995390c1300c2acfe31ce7aeb7c696b71b833d4068c02724a609c4dce642ef3bf171c1ac39fbefbed8af2254d95419f91b991609f583116e19d81c24800a74e6f7c6f6960c8838bc87c054fd4f9ff6a3a0d8ad3dcba15726318670d5a4938d7e75929e558b5b3ee9b1bfafa7c2e498b0d1fa9ec2cadee3b01a33b919484be4e74ccfc114411410d5bb941a6b1d2d23e4ef92396483b6e3fa0107a45d64bd642c2155ca24e8fb323c3127be6815abfa3d851b9b1c7446f729377ee4ad727d4aab99e765af00990a39d898512fd5bdf019f8a79b3e57a1467666daa77733f68ff3e26d1c5814b71ae931e83fc6069bffc0245bf2735deab72a52d4f38f2b2338b28c113618857138ab5cb8a303016412631b2cc77422c5972a7bbdd800a9be06524d27b5a1f4299e6b0c416a82e5f1bb63aa2adc552fa3d7290f505b7624fed446be7c5f2e0f43c8e9d4b55fc0f6aebcfea917835a1f4d65cb11b6b54d73c0e2a02f2dc3f37a639769dd58b120f0887e9744f4fa04e2ff514ecf6c7e22767298ff694d6c32512c6f32724f821f13fdf5fe345588b37b83c727ce50a75560fe9549109689cb6621b9d60977d5bb86f94c9ee82735455cdaa5e8d30335ea6b5f688005ba8e1601e8b3f4c0d7ceca304267ecb915bd040449801154fbd2268973850e90ec85813a1ef85682926ba5e139823d986e50f82cea972335ed35afe038372426700e8708484d6029c749604f968047738caddd73b042bb91764ac9ade94a8842b15f45e2f6d68fd3c7169175250de050418957fb8c63514889b62255bb0b0ac9b8793110f85cf3fb8074b7e81cecf25057e019d1e2e7221813954bff1290cd2e1691b94a92fbd03682c308ae9438b9914c4b7cf2acb358e75559ac09159802fc71b61f8106712d2496447cefe42aed84a46116736cc1ff6081c7f3a720ea2f3c99889a084c7f1596b7e483164b0ed5621076f46546a5a0aff8b2b8d8370eba9b039a6311089dfa06ba723cb8ddd7fde27db0951435e7284d5761e6424ea4d8a058824e8cc73befc7add6703f670b810718e8c929c5872c93073125059c7e62cddce87174b820ab76f976a696cb91aed374c12bc247072b67a0c4365682fb8ae06eadce8893574909b3e6346d996ff2ba69ac439c8fd72abf7233ad41402be66e5c87acf0f3fba8ee29c0da287418cc4771766b42e313e8afb4c235f01ce738a35173c43760f16b3b04d6a82c12b689a7f00337a7aa1720b48107c612f5bcefc71eee82438c224f5b3cb74120a4b7dbacd5ddf9bae1d72b5e78e1152a3738ef3a76d1dcfd134d2d7669e646b47e77ee033ce3bc4212c658334285dcaf1ae214cd5583d68ef9523f955fbcb213510ef00d848fc660f89607323a150dd363dbc425a4db7b1d1ae7680cf35fbcd0b253ad0a6d0ad7119ee72642552330ddea11168ce1a80da2c6fa562a5778628d4968be0eb544d65fb187265f057c793d0bc75776ee40d41cac97a4e36b5a9b7fa0e99840d11d43776f638c80a1a3f650e2ce7a13fbbfd6344ba9d0e31e07eceb9832ed0dc72ad9fc4e0720eea57fdb078c3af816c2c540bfdd4fae995d37176122b59a27fbddaa7d9cc729aad981785a8dbc7a1f8e4ed89321f01b006c2f1bb4646bace44d3ad370516012bfc98b547f9e48289340fb6544e128f0fba0f1d9f7a5221bb1ae3307581295c1f73a73f70084a44ac5f3b3a4197872facf7610af1533a1e6761b87b5a712e1bc1091ede4fb68400bd37dff43a43848f296a72caa7619e84c691aa5124f2a6726c29c6eba374af371e441dcd81a99c4c5425ca999dce4d0470f99991aed8ce72797760a1374cc050f111fc0af1ae8865eae3fbbfe44ee4528841e283721772722e9177b96ceb3ce6710c5f59ae64ca293ffff1dededa3f5e66ba9d63156c1972bbd18a7ea6027831d23439d58a24af0c5c7cf40074f169b6f4231ec2267d1372f9e1aceaafcebe7aff8e863a9ae80c9d5e46deb20976fc867d1ddfa6325daf725609a269059b88c8ce3ef64a316e1eb9463bc057de245160715baddc6806036f13bb9ff34677daee7bf0817b0aa3d70b56a6ed21c6f5856947e6ae8346277872aa31feaf079d2284e250aea982f273da7bb0f3fd8f792aa0c0f91984448532721c86b9ef8a7c78da7eae846cb9171a83f1a17cb14e14d723c3b86feacd0b686e8f5be70e402fda5da4c6332e1f0c2b5a795a6534c641d15a398c07906760457227d1793f900a9e0c9b9bb2229ddef80eaa8f5f66d00d1ba0fd8a55cb9302712f53334337f1885dae96288a927a714abbb85e3e76fc78cca4649af01b46645c0014727329c25ce154972646156529704ba265d30ae7da50caf962fd2d4a85e372214d78286a0ce1c9a2bf3e3822e763d4e38816453f6f6c2091af1ae2335447725b27b3ebab5b671f233bd45e43e9d2f86e64c868e9640a776a3f60f1f5ac2015441fe5a01f7885c6cc7ee7e69617850a1a5d7fb21d0acea1d02209853062f45fc13b5a8728e109cd4b61deb9cd61127cbe7df4fec4aa0991f74c68e27f764f72277e1f6efc90ea0060de03de9f54cf1635340bcf8d0c9899a2f5dabed871b87269f1c76ae0addca4db37ee641d8593dbee28ff69cdb2e8423558e6f57f84235365d4c980d96e8dbaaa89bd2cd446fab493e14200282961bbdc34e1b694a6a371f83988976393728d0597b0c16ee5e2abb3d7b987a6c1fbaee8c09f24f0457572a610590883903b91291ddc5368b2590dded3a7effe50df82e9a0da41ffe23372b84192c5b90d74b7cce324d998f8fd3ebafcc81172ce76cab5d0db6b88e2d115cacd148970515477152b1da12a3debd05d252ed80f5ad17c57714fa876e26755e3222db86c802e9aa2e2775759abb1324f2076f0df421f3215db813569f312722cf6118b129f4b3b1ef6fb6ca94f032f9ccf562a7519a498d58bb934a900817290adceab1df7b56ecd40824ff2a13dc8fbf2ff979211a66dee8b163d369c492866176b1d7638a4b70beb3466d020e318b81ef0cc7f3ae5dad9868d0b46f379327ff0bc986cdfbdada64c334c02a39de866dafe9d0119d599f9aa6ca486e5a16d3739c0a2dadc278a6838fa2b26e24ebd69f6d1b62466ff23d83cd54db262b472b086f22562fee949205e323ca29a6c852263d38c4a5340ddd7de8ea4d72dd908257fec47dd9d5bcd4acb8b9aeb8d334afc39152941eea44cb6cd75951459367263747cf2911eefd8254b63355c62dd7a46bc74118a115ae5d6315cf41e0cdf725c55d8ad8d6ead0c365765820b1989ea1e9b9c7e6b129595b5d3360c90cb9c72a3dd18a1b5bf8784d3316fc00abebded581e1ad2b1321713871937e14cde553a6c9b61debd7e2ee0405c7365197467d0da355d841723c34cda935caa52f4e772b3fd1c03fb4b2fa9876a19130033e227e1142a89f6a0cf0520a1c82088b61772b60a3890d1c9188801b7bfa6761098759fb9e9283b0a733d4485c2d27d0dbe0da765d019c6c0c93afc45f618796e67200a53c5bc789ba4df6a8cd70129ab5872276adbbc386da5299b77bf8b822dbafd32af94e81b3ac1a9fd6e1e05c0e6e91bd7e3412d0508463ce0fe63151d6bb1b377cf79f85e33dc2feef3239e9207a272d349ca61087b1b0152f738e6e0e8f24ce21e611a8fbf142fa6b6d1075b80e1721d22f36548068b1e7e158300fe696d36b048b3fec7796ec64e70005cde754559fb0fa0825585835b17cbd27bf91a833e5b97e4c83b984e9027b4fac57bc5b733a7b33efba8b2ae311bf7062510a44b4ee8c82c367d75a8bec4465fc7e25c651d943fe2654e4984c8b80966921a07e147322a160068c00d79177443d4b5e7911c195aa2de705366119f247c82c5121357f108660f0d18d4aa9bf150937c8e5d726c666e3a124d493c6f29f63ad64e56cb2e32b0c3db94f9a37a8af4b90a8258596a8225447c9d15b9c72496e02955d2cb08f4a83d29628481db579f48b8be81725aebdbf2b2b341ca52087e205e34711048ef80de64d21c7122cf630abf83c84fbe6b88650ac53b4a767b7d753f71e7200b334773e5b4b2b02a7290a41e53cc72e89b935685ce6b31fbd726e73234a8a7836c16f4e150152662c64b6e36e0fd3e13093552b08505ba34a8a7d0e5c4d9b14ab7c98693ff228aa9572033f0078f15fb84da9ac4c60528c508916eeea15d49110142cbb0671e5e31894a0fb9619b72a22750c86749c0aa31e7481744432e876cd91f5fa2cf7cd2ef7a11f425f32372dc1304c8981239fc9126b01fd47c68448b3c20316dd1b2c18cb8616bc42d42363e453c2dffd201f2718e2a8507ed177ccd3a96f02feb366bda2d1a94ec4e3872e3faa9a0663a16f5b4a422bef15387901b6c813222467072a61a29d8f230d256b1bcb9250f0f2af6c4b5684d3a490d74da1681c1f4e2ad5cacc61090eb8350720b80cb95cdb2f7ecc80a58967028d4ffc0917067ce4af3b7727efebc2237cf29102d1316e2142b25cf346c078bf3aaefe0b811fbce9d5246a6de8fdd5d776e72fc6bdd046d4f6a6814ab0c408ca21fcba2ffdbe3c3cfd7c1ea38cf4274f4f4720ffb953ae7084745627a84eb5398b56824f51c048e684462b64ef2fc3ac5567234a4a608acc0f5f7aef5e5a761af6995f39b7ac04e5ce570db6cfdd6c954b30ff37a20193aab62ead8f342f07280588c197f57eb3015cd92290d9e518e27425dd8413d5c0e1b9e728b15a8ee016abff91f57e5f60bf6dbb7f87251378cde9b6b084494500322b37f96c100833bcbc5fc4ae4f23b296bec62dcb7ed2515eac231a8950f17588b8deeee6ef5d6c44cfec2f46a1dcffd22ccc78a80742df70c57278dec6cd976fe37aeb3ccbabcfd3abad2dd8ae2a43331d3cfca5cf12f3ecc8a720b07445a8b532cf6467f5d179390f7d563b2cf22db3228cb56ee8ffe854a765b96f518581962fba42e101ad58b40812252a8068c38af9470cc308829889dd57242a0dfc97fec0298808b0156f3b0f25c9913d907b62a3bb2b7655e49dece437275a0fa444f16a1a5644ead42036ee7d2f895a1b0e476d6a1bca41bb398c205465e8d78e7d857d9bdb01085d1aacb33a945ec6b827e233ba63ef57eb69217f67242782da37eb2b98904e5221fafbe4038d53832eae7546ba0a7934f759118b472631cdbcea24d06064b3c8334cc1b980730fd841cc29bdcd4eefbfccce0e1ba723267d39efaaf408d4808d5edfc2027881b9c75400d661a8a754c4a879ea9af12cc8d238bc320089a839fc721c0fda40b0d9bdef15618df86b256ae51ffbaa74683dc414ebcb7132aae3d8b4970bea14b90519618d102e78276bffeb8c8dab93f8812ba1040dd28c59ea8d0efca349168bba4ac13189763424f26d3306eb87072ea2878cf6a0bc63cf931dd61df1c6aba2b96e77011b20b183cdb5689580b4b460ed9a59cbeedf6f931ca5b823fcb4160794b3a6ce362be959161be739fd96372bd01bb5e425474c239a3074a13a3bf0f720a49fa0b3bc1adef8ebd8dd20bca7244d8953535e12e8f2a3a302bc55c16b4ae9dcdfd821246dd432fefed5fd46525d6dcde8c7ebd287b10cc4d77ae02989cd79cd5eaf6b6cca241656883071bb92558562ecada778f7586cf278d602f360cb548b8fb0de66855612ba373fe289c72255e8f1d3a16d4b3beace37b1a2bd69295f23abdc47c15570bb70913dabdaa7280f32736a575a6b98d7280ff9a47e1ef56387eb18f09640a556d3906c5a557724af6e07aad80c63cd0052be982a4922a08a302e71104968f507011784b851a726a48a12da7dd4da3f5c0440c1a4803e5d132f4eca9db653cef837cc8ba364e72c323fe77d2ce510b9874979c48ed6ec018cd8659b321e7aa5c12e7946a5b3747c00ff856af131ee8699de231861f18859df7f27808476c36366bb74df3753a20a43dae81e86c523c5ea82c8f10764b2a9561b7f93d28fd1d179acbed3cde3949fa6a501cd2d4ecbe7d6ac5db194b5909ab10afda2576a830863677ffdda5f056e1423a3883491b68f84d784f9104528c4003d65a6d23d446da16a6aa040cc04aebaf40aaf669568ad421f17ebda8905100359ae7dbd9de7cb9018ea01c99b472f3fcff6466210424d3ca953cc42f34e9862364e11aa045c4ffc97b3c951025722e36574367fab58177b995e406b9c5b5cd94f49da23f7ff5f8e7acd320f9e147270363cbc2723b28853cfe1b537d3e54cda962154393b02488a041685353f67260136b112f8369827b1c978caf19a938cb1a27aa853cc61c5b67c75a28bd1d01aa3a650f40fbaaa59989771453255fec9790e2f97a4f8832cccbad83b9f8c07200e3f5232d681036a2066bb0474a09004c6437d0c42317f5e5e9050b3cf7fa722e6a76e0b0918a7945f7867cb380a122c5014cb89a91b38cc685fe3e96676a721212b70b677cd0d8dc1e5369ceb6632f46add91c32fd5f46f0aa00a6d5d4aa72f0d820eb32bdc740613aee6b0bb06cfa522ae10e5128ee8f26bed955b313f741ac5263f4450fc1fde14775e9e92557fad2354823cf9f6cdbcbe94c6e3ca5f548ac160e09d87ddd8daf78dcb6be5d6a2f1b8a6a1d47c06750d49be8ba58dd16722de37687accdd2675aad0defb6a52ed5108dde1e029255f0f5eaa61ffc722137b6e64a078447aa7892d8aaed8c4572d564f8fdf806c09ce1842869fb2b6d897266309bb7fa0daa78254a8ffd69d154a65fb6959f9cb32e6732cf3bc5bb40787289726b6ac0498466678dc9c0728ad07f7695189d77e0ca93082f742fecf41720efb0f52cfd23b150d151fef84c1e20150c08f44db640a93c709e5202f40105727e022de4b5dfbffdaf01fadf068cfe7ab9619b2b9c0a98d5e37de9450e3ff90993a520457d4b0db467f195b4424ea419b7033657a0882ab063aa3d58b699c71e410ed5b04ab29caf5e85e6206c61e2937026440bd3b5fa61aae835e37cc7940328875aeca9752b24efcc61cdf37d3622aa6b48aa9fda1eb29d1064b8103598728081a1d115144a3633abe7e6c2e57ef8a88dbd8e2117ad26315a6362a9abad01e6ccf5d2f6a784ddfac01db0da6e188d0394c7f5d8563c6da5c7a16e4039451eaca14e5ae0e39103414ebeb49efbb3d4e3e79696b070260babb3bab8d0734c72474b24aa7f0b2e21ccf06e4695133abdb3ee37f6f5fd7028f20407f5035517729e1014342fb15ceae9206cb3e6d6fec36abbe7bc2f1da2e7cc1c4caff4d48d7049c6ed268471744fdd0bfddf8471e72467099ffc547562bf62c45c1a0e04de72d8c8bfc41c83e89bb5af2d15e05cf74b7fa777ffb4f1963e0d408ef2f71d05519b3ae5e481c2b6e883f9fb83b5f820ee4668086dbd749f10b991d61ea54f5972e487ae2ea8a749b6cd6c37f9d3ee086bd2f3f45438d2d65310362d09ee9aea4eda128218d8bf92f5496d29df55a6f68cae1a1637a0456702c26cd96d3dd43372c4083ae76a81523b243fac9b9613671c9f1f9a67f9089f02357133a618f9b372b896f10fd18b2dbd17dafc15eb8d78a0eebaecb26dd07f575f9215a09d3559728c5d3743912b10f81e689f061e44eae19e06de39e49c687de8faaa2fbfae392bf68d0b4e505218b0dc56dea4da2c35c83c0d7bbf3b8f0d376b613284f18c7836a851b4d9b4e3b2a5526d4ca98d16772ab73b93290f06ff9dbdec0b8a088d020b54915dfad2a19039b7cc7309c75dea7cc01d69e382f2ea5e3a1911f5032e5d6d34dbb2543f734eda1028049569ed8721bc35132f1bec524ccd22532d4bdcb1729b762efbb052f6bf475a692a5ea93606f6ad99df21d5077f34e052bca13f8a720a7617e406d501bbeb3ed27981a70d0e1a9ed53f2017e8218ca10be078217772a9bc08b52b4818ec67394fc80b57579fc8a8d490356a06366291444d1a4bea72e10f2e4b4b6fd3375af2ac2bf86a77a682be37445641041ca1cef059efa84b726a0f917eed9f05674fb57cfc1ea02c47311489bc1aac19505b1a7df954528d725029666130bf17b055913bebb6d9b1bb788a4af8d49b3a869fa1861c993329724749d0315fe92b088402bc75feb780fa51eab4c84ba62af82d7b3fd5638203726de11464609c6bbc52cda8829e10520a61c9270c558f7dedd56d5949e3872c72247146c94e5f3d8fce0241c32f3bc33b9c54a4bd93fd15264fe37a3ebace605f81ebfc716cf654e238f83ece025ae68e01814e15ff3472e56246f866b5a5d672a9bfba1fa5b9630e2e24f1173a70a2e1244c978a18e3e609034d96e94196fc72f54e5cc36d1ac98b177a194d448b24079e82ac4a3770d9fc67be7206ed248572ed6088bb16c18777924713e0cd860e37885e8e5e4b2c42af9abc2bf08edb04254fbd82a7bbc238f7f5466f192d937cdbbb639703fd2b7d30e2237fad57773032c26ab24aa743f7e1e34a3f714b2471f6b6c8fece53e9d0a0889565266b3e967223b534c9f48b6843d63db0dccf10c49a2e679acefe892c8f171f9ce920f92f720a98d8bfa111eb49162e9f8b8e5fa324383943663284a59f2e4d92959c61263058d25814d08d6dc7752d6ad4a72d416ccf550a238bc378eff5293d5471ce3572739b5a668cf270e2596cc2c25689ec9a7ebc96925f3fd209c688ba1a0c732325e52929241bf74736f467d6e16e34904fd83a8c25f33aab6c501f1b3c8f3666721d93be096ab58577231d5f929775de42f6caefaf041f046db91d4ab8a25d423d93e52753fb4b6cbbfa570ca450e40c01caf47ed8a1a771da9e8ea2f17435a872d76a796a26e3c97628420ad7c0fdfe85fa4ee961d029e772c64ead8afe1db45f3193640c32a8e09436a42b296236da625e9ae8360629e434dbd5922334a806728e07a749090fbecb27908ea8fcfd342afefae6764b123aa2d9ce2b06df8d813e6c5de30023a4b5226db147723a484795f994a0d15596527035a9424bf27ae17263c9271c671cd7f2ec4b68898d3d95dc987d2e41668f85ee997489c581890872816d7603053b68a4bb16861a1529df9116170f57fc5571253098b60bf2378072205631e90b76f8a19a057421c1235c1a2c119e00759ad7eba3792b2ac8c74a72d3ff2dc3e8812939cf4f84ed1bf34f5ef65c55e78d15db3c46762327d3714c72dafff78bcb7d7d81ac327d3027b52dc02993b397956f481009a867468d555372a8304c50ba2866f4f4a22b7fdb1bfe0c4f5b0a0f3c6965a65a6fa4c7a31b2672c540afa0fdcd89a8f0dfd6ea47e8c4a1fb2ab237a02506fc3a18b3a4366bfb72aa46fc8e6c812e1bcee8ab1ce0caf6828debf791031c6bdbc4c1514692c73a729032863d721ef0e31d404b5c5bc53675e3e54f00aaf6303b784495b66d960372dab25081ae7315e7281171a23eff9558f9f79ca9f86b72194530335dd30e0b72822d25f7d72cff8daabdc5d3a8413318e6579ca1136af7f906b43b02606ebd72d2df31af6c7613a5b062fd58024c6aaa34d386639e9a3a5c761920b467a1f866162c6091a836305ba40ae5a070b4780f2ce31681f3b4bc2dba7673ce67cdb772ce8cc66e2654be22760e4ad6b7593e63d00e482ee482f6276a7e4a5b74dcec72705ec2beee81898d47e2ff7497d17957fecff4b2bee81d96b3142cff3fa6d072b469a728aa39084ce1d6e95b1df7111e99596e957b76aa56d9d3c4bbeeebe972515b5b9293355c84f64c7c041652525bbf0ff2ca1e1836802be53504dc0cbe72b186473c2b669cc89da5b51f2ec861a8bc2b3687ba715714732199abc0c37b72ef08f63d2e8b466b871205f6304009816aceb92117f459044a7d41811eb5fa60c442ba3512b3a9ace0dc15f274eebfb4d4f73f01a604ef1d54b5cdbe48162472e5554c61281fc8c179f784d587c666ec357c08d738e7d0f87df38c912f7b101adbbcbd7fad0a420551812ac6c6a6fcf703121482cfa623238cbd5de3b7d24b2d03b077ead760d50ed73a526580dcd755a3ed5d9a47378421b3757310b193fe668b2d7d60dc950ee20772e3d5823a276490e4d9100f2c4b0a648b0eb2295e207214712b80617e9b6a54c75c3bb210216a1564a78d27365b0fe6169224935390530c6ceb968fea56a6e9e4a82a216279112a81cb3f1c366b6af65d79595db5ce723393edd721c59d1a7f51a0d9c76467e687a766a16588049910c86d7f13fe3772ac871cffca5f75936c1a3b2bf4360232b2f1419649fb42ed9f47d3773bbece722fbb708600cfb9c5045ee3aa8bfbc220cd86e281ed95ab788d55f7eceeb6132d8e7fd389de8c7bcffe4f310fd5e2521234b65f202b1b6c19fdb9e09662621072cbdc9d9eb6cc11b6636bb0c1bcc417f8444b49cdab2ba691c27ffde5a95811726ad9036b44eda64467a8fd6db01a270ff83429df3528cb73874201f711adbf72c890951c11cbb3dabb383af250c517f4dd2ab60df0240f6c801ac4a7fdfd465110b7832486563341133532ffde7c72a5787c8746297b63235fb08c7a52ecf172f6254dff3ca73c3e201f38544c1f9f39fdd27ca81c67af389b6d56d860ff77704eb191417fd77e56a78fc64cf9abb491abca0c0b125d1e0c86b0c27509ad53255bde126458d526fdbfb4b6aa9b02cac8f86b8baaee1ac29d9cef88eb582eb75fb13165b5e6a45a855675c850fe3aa4de434d4c68a98a3ecf466d07658bdb4317f5ea3fb40f4c4ea9f23c11bfc11698047590cc8a946b43ea203aefb35b409d5735cf13f28109698cf7140c6762e93192e0c847be3c908e03ea5463d78c314a264e2f17d77b7c40bb35cb1d7fe3e70a70026e90e9b90c734ba53b825268c68d723f8c21012db5425f19da8d5e1c5a5060a955a9b2128d580cdb99d4a64ae46f72cb365c3cbaecca795ece38f1eaec7efe1d0b40000a54e676a10b821ccd16eb49ebecdaca08a4d6b6ef2bdd2ecf7e1c905d5402712a3d9b4ee08a102c406cbe62a739925a727700e4491b6a9cbc192191e128cd44020c441cf4d1dda8545f5872e0c36940e7d809ae68f27dc5d2be4327bd6f1ab52dff4b2fc81ef0623690794f6f3a568918d208ab795ec81f0828a54b3b9fd324a79951ef4b76acd7d42193517d9508d3d24f696404cae46e57b69b37b26d229cbb3b922da3ff9835aa9088720c45dc9eea60c90f7b3b0be731f01ce299da6b80db6ff297f5f3d6849986a372cdd3446fb2b282dfd91113a2ad01c8788f70dca2821505c2af3b0a6b7329d77283286d80958819d6281ec7b4bf16a7595ad66096b43560cbba28f86c64b72529f249d69a823792f34a8b416e6b2e4c72df082707771bd00d4faeec494584db395d90031dddeac5f8f5754b17675d34774381fa382cde32d95f10cb77943f024f69f29966021fa91f4709fe83ca326cf21cd1588414ae0f12c47f56d461af2564760d5630521502d9132a3e7e8cf6e7098fdec592b0a5f632dc624da0b162501c1e3b9b8435ea4ce2df872bd516894b8488e251fb0433fdcbf4e41306a87437721bf040cbd16ac101b1302ad832755cfd6fd8b92bed729ea941ba69297797b1728efce907dd4278f59a20c8bfcea9d9451ded6f376ea834c535e4af1eb4424d2775159eac4c0518410a3702b595db0007665301ce3c0e38d7542e8c85159bf0634e74473819d8b484c11cfb92014bd4bcf1cdc31e306aa8393a8450a52eb66b726bdcfe82c820aea0de90c4a74821f2891355240324b4b8fc7c16d5e00a48e2724e2b63cb80ed590b73599de3e60771d7be022a46c77473f91484609e12902e72a305587091199498ef8ed86f4c99c805f44942fdf623fd88f99f2f2208252072e631b7576a8591f79a7a24f91a6f7733014c528bd6eb31cf62b59a872aecd342678c529a547ad131375802ced25b05baa869c204098bc127d19988e1c45ace724e9303a476b077ab4c6ea8276026a733ffaad7b46fde63035272a44f49d2c841787c5e0a17241b8203fd2d397aa41bda1d9da642742b86a5213703240db38072b342bba1b235f542accfbcc5cc722c17e8fe3427a53907dbdb6e75a326852672d96cadbed45a80ae0a806b80a8fb48603511b94890d72d111b04ad9373b1f07294a198397c3d8afe3ba091dea707061d3f0f05e8c74d4dc15eb527543554ad65a0e0762ed51591e44ed909057e985036f27d61c4d06693dde29e98447bc59772a51933f42858ee7701b9ac98b9086cf23f02251e2e0a64838477d76963820372eec4f0afd1c46092c5b9372648c45ab3df6b87c974e4f7bd35d327b147f799726f366f31b98546cce9ef4e0e4b9ee7cd9993fc15ac4f68970382a523e520666a0ed9dc3b4c200fb3427af1866e336261f8e10c386c410980d4700ac62bc444301f949cb3475c825e7e3cfd918ce503b291cc78f27c6ae6d3ff75d82b9a35c072823dd47e7ead476f5f907b6b28b51a25d2db348921c1109d6e41440d634b145caad000cd3dfb08faa60a30a0fd27f834aa480e92c01f107b31baafe2a2aa2972ce4d51e461b248d2c24c079d67d01d84ee1c7936940922de60729f4359b7b67207e820ab9c11a369d58447e5f19347f4eb869e1fff0679b5d8d62e8e6d2aab72ad6767b9cadb9624bdc05dff64695917496312b92edb40fa53f7a2a34cad73724a006ba19a7afd322bb728a691999522f54972239c844830c5171ae7e3625d08149c72f959e2e4be4a085982615ade677f686d8fc691faaedffe681a9d4fdb72a6da735a6a3ee8a54e3237a46069309c9c9e21787ac2cd216c4d54ede7024c47f5f41fb76882d520dd3b6443bb21717006c3fad692d293a6453e8d688f5b7e08230a201acc1235410716a4dd3d8286a5c2b9fc18901cfedc9d9e8541ee0a05340bf234f4637090c82525da97e72a96cbdf4ec3fb11d11c7e425b76a22200bd7256eda169a9face734b9de605b05d0480253d2ac3a5a0eacb1ed6719f16ec5f3f0504922e5a2831e64e7e801712a140bbe2d6d979e582fa35916d079249a6ef72c84b9a691663481c2ba10c314d95593d36b14343e43b2cc10c360acad5ef2b723841b26a8323ba60c8f31a3a7834db3525051ba166a21beebc4cf8105aba9472747a68661304637167b0f245933e4fd6baca356e61265be0f078740f20d062725a1c8581e88daf3793a8ef966290512a151771ff3a8b655245b156c49172fe72a009c12863f37008be10aded9c0b628e7f4653b2a937862563c747d282d5f17210f63437edc200a9ff44a1363ed3a3fe4f86acaf6c424eb23d5f9478476ca2721c2b2146e397e6587aa9dcc6b22fdde36c87b3f6aa892890e70b67683fd6817279f6f8ae92e3bcd3295979a72afa8b77cb5a789e97826918bbb8ddc5ef17d85b00e5236487b213f2c70aef4d335d4e2a4e1c0fe75e4ac55aa8384d140735b7728d30d080cded58b19a71fa60d86c524151be93108925edc2656f159a98a60f728a09b4ae3f9e9b3e8443c3125b57bea44574d0ac1ccfe471b027f4eea6f7de394f1e97330e4ff92218fc5f759eb0d20a164d0f7ee5a4f4688a6184b78c98ec722094b621d5add539a4bd39f342c1d0ec878622cc170fea8519907ad8787ca07264b860e4176367e9095ad76a55913e0c4c0add898fe3cec209f456eda4d65e72fb31e50349663c8748a32e8e99d1cf4e31ae15b85c80f79ae2b958d0c9c22972415bdc2e862670fff9ba489a7aba36ea5c9a12a48d8655d761c8d3b42a8b9a62b617e065da9c653b43c2cf004f6950cd54e81ab9288221c46b85945506331223b441c4a9e90f19cce068bfc1cd67a22e8d19a88a00c56d003399b0e548e2407269efd613fd44d30e01b0acccbae97b3b500cd93296e0b93a25e5af6681835c721cfca789936c917a7064a41a6c1118b1820a93a84e946b21502b438bf771ed724feeeb9c404a05532717196d3c549c44364e289e2ccb70947e09c0e8ff9f2972c289dac4d4d27f6d502e0c42dcdebbe41732e942c4bd213a9ee55903925ebc0ddccd6fd56ac3e8686d4493dc76f44a0590f92ea0061366b96d3ac75c49715b72048be6011359380adb0ba2f1026feed6dbc650aaace8ef3c34d38ddbe89ae0729be544e191444fd1fd9cd64423c2b2e284fe5ee868741b086008823241b6407258c3539cf832f188bde57f21e357939d8155d6620502dfb08bf1f073cce52e363c6a7d2c38e420388fee2dc43f6e9b54221fbc380d10e0ed5066e1285398f8725f300fe63c691fc3050e5bae924d33690fb3a87f3316ada0aac352e3b9eb8c057ca40e1f31b93a7ddc5db5a68f3b0b675a5341cd6a157ec355ee85e7b4f3cb72c87f0810c829b85df580b2e9d41adf3f3babfd36882241eaedabdae3bf74f83fa4c0e07c26e83e928b9b6eb183594c1d09374c25869c9a5243d69925528bde72fae1d620169f2dec086fe1d4f20f7fc734a01dd54c77b8613acd80e73fbb9f584a4224ff69e410256ef826121da880f7bf3fdd755f0efc2f42b154d9c74e9272643260e94bbb9354470b1607d517ec346d432f2abb4cdcb38fb95f5f0c0f0a72ebb291a25add671632bb110587af7d67bfc7d543b9ae6eaa6856f29631b00472fd900801eeb79df532fb97d16104ea0f2601cad4d970a721cfd093538214e172bb58f38f6b36911a269950284f4f9e0fb720822a45c2327f1a0254ff4968e972f92cde6ba188a5f6874994b708412250cd1c24d6dfaa5d8a0fc43e4471deab58c2942e6c391bfb6ad0927e494983a0fa36d9a482f16b8ca5b992c8aaec3400723ccdd6a450e79ab443759d539c7532eccae5bd65ae02cb6d96334dce831af351f7f4a18ea9da01c16c82476c5db8d94ad74d31a10d9501fefe4f107e2b917d7292cf70ee662562eceb2ff8ae667f51e4308843b2eb9153c3f75bdead70185e72c0721bc5d423ab68c926f826c470259d30ccf9a4c25c2ff8a7d0b4010e5b2672691db4a28330ca52e174a937b041e795eba80066f2e5c01105be95fecbff0e723e5a8471526aa7dafe8e68f999fe5cee7e3b26c7013372ea81cbac0aefe80d723f56a17ea41164389076bac3a6f273e1aa78fe7d06e1f0454b10f77c2d687d1d2dd3f4276fb4c1cc164d47797ad1f1a407438c2a5ebd5a46f6ac75bce971fc3f72d416f595e0cd9cc5bf6838d1490e862e26997189e2132ea99df7465a770d729f1a3186d0a62c8c8a38233ce027f82e29198dc2e61e00e864492bdd4edad672a9cf0c46b6dddf9985243a23935bf30bb11a15c6cef4812f38b6f466ff46791c7e55dafd199933b7d16092204377529f4d6d863a3654d7d285ca41376e8d9b294ad9289fd4484e60b49818fcee90135720781ea07c0c873bce604c26f442a911bd38db7549b3f207575ec51adf81a5717abc978446ad4179ebe795c9195994729f73f05f37b349a2af1813c945489c23b2391f4d9aead13ea3b5bbe738525572c52445df60996ed38fa2081cfdb9420417183cccd91dd2f0a7d39ec18f42f34d690ab3d750fb6ce86596bd34c8d519c7216d59a772e8c17e971ac91da2f89172b331eeb11ebb804cbe03af4fa1cd8afd015044e8dde28c971bddb1559fac1f72384f17982e15f89c11e53a48c78654e76d7fa3d7886890c704bb4ce461b06e6d59f535f2d662dbbd1df2c78e0f26071673b4743f318df9b1c9c04a79af5295728ae7cf3e4bd387211d26c50554bca07cda15b351f8568b66fd6f8e856969e44eaeccb83380544977f9adba1ea9328f331b2af4b7a55bec07e065e9280e85f2728ec33d3cdf32a8d65a2ed8ae0681077dde73baf1e124de74bb33c10cbd6110724a8ebf268e6970c06089f27e44c6dcb0bc8f4e9675aa91dcda4cf84542dbf972c74846837a6fdd80d044929a214fe14d3924591e4fa1938f25d575a6b402163a818bf2d52020f9f9cc3480bc2aeffb763df78cc0ee1c9c9ac223bca9fb8cf9449b1566742b7e96c6575c21c7722ccd4217a84afc93fb0238aba7326331154472dd1f10e09ab10e0da9813e3623e29a20f090d0e164708f7776e49f7c0f7128725c4ab1810a0a98a84ef580bee5364d69779b5c04b154c38228b5a5f53bba6372a97d1051754ee3d9c0ef094712b887923d95db5521e528ab10ac6c232be48c7224de5c62175171a2cd2e837b3b4c73d57d742abe31258ef95fe8e6d41be02272db75ab67bb0863475e46630ddc0c8e1b6a2901433b5b00f491fb09e40f97023321476ea76080e73900f42aeb3aec1b1ae5f6bc19637b28acb62f663f8ac1eb72e0b9a3b1561ee2775df6faa53a85996592a0860a7fc171fcd86147ea2692d172c5455472ff30ecf93299b6e1f3f1ad5fbb199044f0e0287a4ec85e4cf5f78972c8c9523245f5a3e354d643f9d0b440af231a3b2515ea6189782e9f0f80aad63d9409a579a79e55d502e2e4fc39141edffa16410501e6763e3c434790b8b65e6bcce1ea4ad80970db24d90b68debddac912cfd0889fc07f395f484862113c094dc2f864dd5afebb83ff3688a3f98f2b75a04e1fe52ec7cfa24f9a970ac4db1f43b7a399dfa3684f4185d9756f4da0414da0285fd318361fd34e473059b36f856a909509ff3028bd3040afd00bd42741eedc3696be409d937d8a2483536850b07251647ed243b2a8d30f440d6519866930cafcad40b7f89ba162b5d5fff0396c5509feb82c807a1e4dd70a72e874ee0cd9022f73adb064f4c7e4c24b315c210872650320a9cf3e876a8554c5ffe8233be077554cff4999504d528e577fe9d129721fa180e0327ecb712dbf823dd3e8695893e50469c2dceedbc5456fb1664343721e481685409adb7144243ffe25aca8e4c044a312ab7df5ca0a09cc5dddd649146b447f02defd2879cdbc19d365b3ed37dc187404d224e1f64977a883b8bfef7254c449cc0244a1d34061f87fdcf3e4818e02fb89074aa8290228d48a714c4239394842bdc9be1db4f66ccc01a037688c3c86e37c6d583cb48772b7018fa7bb72b12b5aaf7d214128dee1f1a21c84f763547e88fdcc25332808a35865d7fccf72c0c5f20bb55d1adcaa1f5d49a332c97f988abc93351cabb39c4d66f4e458de0e300c9d1bf6323703d66c4d7063fba93b6d7f863e29612bedb9f0fe0cf624a572fa3995b9d40dbde2e2883bcb512fb30265be1166a4320f85e0b6f1e740d14f056459f4590f57c0a9c55b02cf04b5b48c1624f37687302c4b36da9602da1c65720f00236551252904f4a9e056c8b7a39afc7d8ca89ccf229472698737d15fb4727188c240879a8ab504983ed57a35347b353af1c02c76589a0ef09fd9768c70728831543c998c465baaecf311dc8136f69edbc7e955e75eb156a5627c90b35972d8f61a66af0cae6c7e28689598c67dc993ab502d45c85e5cdf3581242d03957286dd705e7e8a3053ed9b112cc5f0d93a6291ec4fc055d05e4d6e79e661a787723115e07f043e54db0f9e2be7681b352f11aff8e7768c49a44c4a4f2abbdde13729b75085a46aeef73d8b800b1b8fde4350ed5b6def4ff1bd2a65089345a64772266004f457df58dd79eb17cf97b7a15df3c48f137cf313191a2345afcf1e2428693d03158f06101d6f465c2926411b9ca7b6fbbc2bdac0bc70b23fc36d8e5672a969ce05f1a46805d88da70207bc6fa1af41013c4a295c612a7c87e9e4a61c050a2d4cc23b684805c027eb4be945257b3a997b1e9bdcdc56e12eab5b30e385722760f78e62ac703d5bae5ba558c17c6868f57c76fffb7b7698766ca7fc91c772be3ec15da908f1920cd3d850715f9546d4ca54c5636625e689dbefac16aaeb728bef111ed7c4c795f9890d8464174ba9ff826200d3c400cebb6c188abc44e76718c539ab547f9ea8c44e332c5f120feaeff0510e77bdf664bbb921ff2786cb72aad2dfcf7b4cef0e8cd9f987c570aa96a25129a2289f7f7e083b1c7c66366a72030bab4d7e0b8a6b2a61d71c14220aea41bc92d6469ed261e95a8eb99705d12ad4cc0fe8bd0f7dd3a04e58486495b92206d46e3e424323161edd240a959c34127eefa54499af4d6e97076c6de995bd306360242112c76fbc75993530de26a13f68be50eeaff8d24a1e95a8c788655dabf1720c47a36c967d9f455c3c27b0d95381acc63a2b689b984b8f7c9dc650d80c6457aeb607f6fc099c2c4bc08a4d9e2c6fd4ff7991feb3c52fbb677a299c64e638f2ebe4935840079345fdf874b62d42898a57cfaf9d5dc24e8f0ab52836ad9147e0f4ca9173af17cdf8439969982472b421e4453055cc4c0ab15061f2afdf8907777346d6a7f35d5eda5c2c98008072e068f05ad33c4ba069122403d7f5dc3221e952ef08f85890c8240cab9de55472e9ec16fbb5cbe6f8bdf76ac578419b8254783b7668b96056c488170f6c63d4721a12c66fbaf276b3035e2b5a66e9a2f3aa20e8c250b5edf26534ae43c37b0e724feb5257f7c4470973761cb72cfc32b32bf46aefa526ca039e7864d9d80bf8727192fba38e4a3812bc40f56d17b203398b39866938f19aea5cbfe34f0afe6072a558a59ac03fb03be0b6ea031ee9ff81f800a0112ef57643eb515a1a132498725b93193955063672cdf5ddb0d235483a7862ab03443f33d8abe7c20bab35355335232c07d78b02a0ade06db5d92204ceb689d6ffdbab35323b231c6c3d09e6623238aa65ef3f83f748ece02c921524065e43d64da437a7abb706eae436e03c62c4356c7d46033acddbfaaf05230f6a0d79855479e5df0cdf5dd2d5d546abfd721ddbd27ad127a43b737f6330a3623ea7a97e792234e4e7dc8e88a3f730ff616f4941b3e50b60c16f0475496cd17c2a8cd2c301c3de2c3b5ec802eb665ed0ff720e8adbb51400dd295e1e8ed4e4f9ba0106f76b345902038e0ca8eb4c60ffa072f22b7d8857d024834dad650a1382e01fcf41574d1ae57d8516f198fd5f5ca514fc6806be2541ec7f2764fc937ee75812cda8ad4775dd328f357e5244345ab3486e0965a2d9091b31b9e6e5755e0f422591aed4a123e0befa6350f7d0e48b3e7238465b82afe9d8d74f8ac277c595f48cfb33332cfd1993b3d1591a50fe859b72cf972f2a33f76ea7f4a84be0e6578dada900e11b70ec897e29c91b7b85a5c372b1aa0e2c9c131dc42285934b8fbf0a83c34485d2f7f7b3e3543c026a0df27339bfa3a73153b276ae0c7c7501825415ab9ca3ce8f368d7f0ad2b517c67d05fe71e860e3298939e7493ea094c9c80a6332675427e834d65751e34d915fd2fd2b727d15fe8ad16c5e059f68b5b8bb6bc169a91832365d09f3ffa28aba9227aba372d661fc33340c42d8e712e9f4859deae1a2ca0b5d5d72bc40354fcd8ba1cf7b72cc1792d2a595fcb392173d6db0d1016bce60bd98a329ff53c47e0100df46265ebc179c16146d4ed8aeb63cfc67a5cbfa035d534f837b6e4325d03e5080987c72f20ffeda50238def3d7f53c09ebc5e24e7cd288ef7d11d3b1d42196486961b722adba6451a472564b145b6746bfeee9b358518b3f8712e36189209e1540bf472613e1aa4dd46bce2ad80b986650224d3d1620a405813611885a2ae0632b3ac1ac0e787ba3d0d144d8ab789ac89d1ec395d0ac428e10e36c59bf9a3623da97b72dc69315cb3e1ef6cc7b1da5f68a35b24f81f166e23730e65b0e458299230e6723c0bc84fb0960bad26139339b7420129e5401ddb6c4c22265c6607ef208db77236c469a1be68fd6267e715b71a45b5811fb08a0333f495f5fee2db3289a17918962ab2b466bfe1f86ce2f263460b33e8a9f805d316f99f2381bd00b3b821de20d9369606f7019ab000cdb78cd474e23a78c8c4cd90868d665c9c66f753dffb7209462c5f4a876eb5f673e0d107fb7618ea4c53fb68a82a1600ccdb00e66bec72000119af63ecffc6756379f8c521aee807c4def11423dbab8e4e55f52bf8b41247d6a5f1ec4616e6d82aa5338cfe3a2f11e0acbfff925bcc4718c62ebd158b7244406c5d59b2bf4249ffa2ac9d0f1c4bde8981465e7cbfe0581d6a8e55b5c8727403fc60e6e2d9a78a53f42eed32fcbd0656822943f48b2d5243a553f6525470181ad931ed469911e68846fe16ff343b1c7f6a09516d419908fb630e1882e44646a6d4cfe28dc54262b5343f733bc930e21cbcc8aa6c6d4fb47dd5ea4049aa148150360b056d3a61e5028951dd0d3dac5766d30c92bfe3fcd020c2e2b86bb0726dfda121590c80fe977f9501f0ec75660ed8ad65e184a7b3f6cece9e885ab0488e0d66f1c5ff5759f007c998d78df53766ef03d8ff98c3f788be59da5bfe4072cb0af55cdf26cc70957d99e8e3edb22c656a9fbacbd6def3c8e4371a96c98a11beff88bc36e275bd3a46d2c164bde8abe9ced3a163feb22ede1bc747645713724a4537b5c00b36d66e73cf059265123c896a7bb7fb6f6c223e7fe8a62e587a0f8316996e2de26b912ee0ba0f7405154749a54673edc43cdb279a5e03be19ae72353bdfae12950cfee754e5700c5ccc7b2c2687002b0359444ac14663961d037240a5f3f5200fc3dd77cd343602136b2692baddb37ee33965069802a291dcb630ea45f17590da09a42a01f452cefa450ecd381dea6b3121d1f40179965c1036727e2f6fd970a592456dc6ec7e27b5d2d18bbd4a0745003a31c5b8a9c05ceefb7248da0a9b4a3c126c2b16459ab64f8ca1679487eee00fa634bbe8444faaadbc7209e90337a9dedb1633e9348268d97518ff38267c04dbc483f6f89d121d760772933373cc31d028c4d9986091c07df656fae696f9273c1a757e035c5f4bcba34ff3bc842cab6a39e4bb46f9ba68177c7c3d8ece3f51c97d146a2aa86abe4d1572e89f9aebed0b4eb3308a810753c986ccd407842fec98d9557a47ec4a09c3a93e6b616d3b0cfed9235f27fa421873f129576adc5b2b3cd8ef293f7c5bd6a5d9723571d28df9377ed19e01da923d624ff2446ee210f7c18ada031ddde4fc366200c42afa4efdd4eac4bd0ec02f681a86a675a1b21ef167270dff37e938618d48350816014f2ae91129179c31796ca2197cd124d4e1076d347ab2cb1111b70dad72698555576dccb1f3fea4df949d06dfc7efaf6428ad1e643adda0014595b4b072dc7366d7d313ef9dbecb0e681d8000d3e4d078b1d4ae7ce0a845c8e4af408372bed236fce570d14daf48330fe2205a02160edeaad37a2c3333ac01331889c772cc8fab067ed83e108b316ff487a1f97136f412193e69389d3569f2ace87fb372963afca5e69a4e945b5b1c9858e3d915d9c2f7321111a69cbcb59d0b72111e52476f38ec132ee5a41e3989156269e7f52daa2edcac95b839eb3c9055e92d1d394757da3978a06a86b8fd78f37438354c59b76054cd4a6f89433cccd43d24c652f70b0ab44df205c27e81bfeb47f71b4f711fde97ae269d885d28f1c45ea76f17bd812ba855673409390a12d182431713214bca4f34951315854f7c8e44a083724e56ecea0911e8a7aadd67a29542e591852e54568f97da740625a39211920372cf039621ce34d5d40a3e9612e7ea33d1e86f48dec606bf27b61302dcea5ac366372362321047b07718148194c46655ccd6b98e39838796e253a8e7ffc7077c7273ef28d4c19b21ae4993e9a9a47614975ee0c1ed2580ed5ab60740ddeae6c7722200100ddee1b7a270158ccbb73fba9f25df38f8e9e2eea793bc9c4ffa31b772dc9ba520372d56650245873ff9fcf1c00297847ed29a899ae44e147a0e53ee7282a54c87c0918bb942be58adae5b14217777328c05c789bd125ea0c05b2b66366ea4824d4024670d000b9593076c40fd93502a498575afb091b635268c29607257290d82d687d7a919a50522ed9a59611d49e68d086773ecc0e37814a6bbd74922486ede8adc0a5742ab4703313bdec57d3858d0d06711dc790d4458a1470a7248e7cd051a976eda873bbfa9f827be4b5630430bea52beb5edda645df97dd972f97270e709731f3615205789457868a5e9b4e834848528efcd8492e932aa0672d5b475f1d20d1a429f70edcea7d27bd58a0317eb14d9e1b0df6a69f73bbeac729e6da96307a36675573b6976df9940a9102780a5aa7d321f14c82982a06c9705e301169b2ed1624254414b0b34531c4b2f01054a0c25aaf95e860188a86747725f9518d3b6769cdd4a475c00888b1d20982289154548a5a914e2ecf6689a2372d88f14ae4f20df613b656a3072d45ad907ff91a6d667e8e763f95e4972c57072fc21931112651b3f36b88939d4c9df5a96e3ae03148a95a96781dc54aba144720899de874899f221096b1aea994cab84dd2d1aa54690e5c00d7ddb16d900ce725964950bcafe75efd6f4857419c754eaedc8ef81be1cfa962d6da319bdf4fa7211ff52930daeb8b98ebcb521c1a038e03d1708b597e0f44b6026b1ed3be2d96c174d3702d15b7cf2e43ab0d2b77d9b07a76ff78dcf7345915b91bb371e0663258ada65ccd46477684c8e9b5066b91e3822225b6029b8d155f74ad1e1630bab72c5d849003d72962cc19be9c09f265fc5dbeb5ecf26db947a8df7b475f63135729c88e066f3e72904fa5f3be2c92b2214084a76a4c630b46f2699c1fb8ccd85673f89d8fa77466ae03007deafede8127d76cc8efb63a06fbb3150b3f6e489451a21b2dddc2b8ef1dca746cde3ba13f6749f4721617205c88dc5e4439c3e8c9c72262bd6ecbde51db9dc4c74ea73987921191ec4b928aea6403fb5a06fed4cc9679eeee5b15b87ecb3f863e8848b24ea56e6c6383ad1516809a1441999cb008e728f736d54a2ce729d30402f71a329bab2456306b951fe2f221af7197dcc930248986c33ea6f28d2addec91cfcc85cebfc5667c4e804b18bd20c26d91928014972b7df6cc18da57cc68c0cce1db28f520485790f79c3426cb57af59e3c64f75372eac5ed0247f5fd25a9da121b81c596d68ab79fb113c0dce45f8c1fe98587a5516fc6748f6a7a1116568ad362944cc65ee173fe63b0211a83c7d7f2135f7de072c7f2bfa0ffb8b9a728548085f4f6b448ec85e41e80d2ea13630339b53d7cff34f85bad2313c7e8762d7e7b4cdc55a5b3605fab5af313cebd1e161d2afb7c9b723c577a34f350432195f516e07889e486a386e66b612805e8b7e020bfed24d372cb37acf42a0883599cf241ab46af12feaf31eba2415b11cb69ddd19625c5575567fa4ed7677e8f6149c6c3eca07dfdcff26591f6c96a98d634bd0d045ed42e6f18100f7a7c349561241a9f599e100a389e4ca656c08f7227ab6565e2b945e5727a3c70213e51d7f9abf753be93e538c62b34acc6f2fccdaec62c277449db5372a9624dd7dd9c85054a59c75894bec83f5f4f4d5ded6372551d07ea12894ea372f9ce48f443a28b9421686a6954986551691e39ff605104128f5bb61118866972dc5de021119d9351aadac0ded7dab2d7ae090757ef11a251234b8229357fa1724d7def29fc32aeb399b7f79a5eb56b49c96e22411850d3594f2cf52d0da17b0755d4a4657834657c28050e04c39aba94a088a13fcc0c984fa4ff9d72c8ebf5720b4edc1295823355cd026aba5dd3ad6b6bbbe664bdc7ed2b9c55a7d9f75dd17241080a71157fd4d3e1262702173df2bff2555047331bc2bf2ec6c84aadbb82701a7293f78d805f43bca4ba2f75ff5dbc5520a2b49ca2d4a096cf3894413c2710e49e40535e70df784108d1c9fcd89169507190a1de94376d3c717372f975c7172a9702aecdb1c429a1070b0514929814ea740db0a0a419c7dd090cf4d771ce55a44b7d01bd752729cf37ff3938c035e8649213c87fef586e3902b887a067857270b8cfafd3723efd46a562e0cb57be9a3c503dc6781da4fef2c202db9fa14972df3488da3d8f653c0479c72108484a1509c3a3a3238a2487d811b124436ad6721e1dc8a4fdf97e56534dd439295a71c5a2836c7c5115bcc1d801aff30d03c7722d77f98e12b6021f9042da14d04dd5e372a4f7497b288af0e7fb5bf7b80892723dd6643684371af4dbf9dd964f5004c0dba726035ecdbce8e5848a2a2fbe197254e3d08e417c62bd5a11675f7f02f871af6d7a7671f400b65d93dc7da43ef8728c225e4d8dc5a85308d72b98c7309d0dd48a191a30c00a57236fb52764c68945f1e9ed3acbdb4868c75ada063fea3449daef3e356066b51ad59b2ee6d7d38272a23d401f0736c03d3da71e2b990e6d97bc05cfa90378597e78ad0c0dd723e57218f744abc5c90b8238b5bfd6f15a08aa6e826582188e9cb507c6d81702c6d900f38ae17ac5d8c9d079121c102cf40e65250cbd554e82eecba9cc8db66c8db97269e707345889749f4b4fbfbc3395c5fbfc67468c67b5d73851587f7591c5ad727ac9288dd69467b637c9fbf560958334ed3ade582ed116cda88459950b9dba72827739e2a79b10d2927342e62d325e7bd238090dd080b9c0d58322a1d581612f16696763d59ebf962285a8c320433edef305c6f4899ab1946961a28c4b5cb94cca6eeb75a80318e8dc23b8e7611b286f88273e3bf585db362c333ad349c2c51028ad7b08e3bbb121a1d253bf8bdef0d157f47372e1933fdd0e1798304231472ec07dc8f5a9f631a97a52673f3cc92bee6219ebb43b892ebcd3b43343267c7272999d9bb2d195d17a7397245427323af4981a87b601475e40801e67d63e465f45d98a0e91eeb7e6ff8d7e58fce4259c08a373e1fba8f64fa65952c1ac53b18b726e0e3150a7da5ecbe8469c93d066fb06ca0617ce07d47f568d61159b8de2cd618a53f9a88b98eed5a6a2fa6bbbb810e44553440823c9273d8d20d4838303013ec1caa91f276870d7ddee9da704fbcdf5281539a3f9e54a37ebe22f77ac8d6772d859f93d9ea99279adf1ee5b18926fdab500150dbb2c43d9d1398571323556727b7caf9a550db382098e1dc7e838067e349e1cbcc7fd49505878c38046914b72bcb4f1eabdda80976735c57e61ec2ebfb8d0a5c591f91250c8a4e7d360009d71ac1e6e87f4355a7ed02f1bf72bc495a360029e61d8821ca77ed1ec01b7a65744ccf2453bc2b7ce1618dda972bc668422b09fcc9773714df094c5708d5d7295725f220e86ee9e58eb93246acc4829ad82525ac4e76eb7abaffd1690c4805dea72928e2f8e598cfe07270e4b16754b12bb589fce10b4d34e634a35daf6ce2544726243389463b2cf4e6c2c74794f12b592974da5723373064e4d90449626c247724cdb114a1e968ecb504a2c31766ec0315865a5aba50b27ed070be94c11d39172e83780ae7a520b008b71e505018d4d9878ff7500601c396bade2fcd7456fe43aef7921ac7b229adfb45a47687125ad2e988c3222b40a86a81713394b166d3f72e91203d9e53692f82a194000ec27f7a992f74c8a03d62dfcab6434314e0bdc1c6ededb18ae781033a805c26182206f4bb58401ae0ecd63ff2fa8d92b7cb4986dc6938701d74953073d8d5012be73a5b0ec371eadfdf1535f33123210a3a1f872b57b426a08df5e00e8fec363156f4b903ee2a289186d762202159b9ffa11d57203f16388eb68fef5cb82098b390dae6ee8d53f1abb13a5c09fd6cd84c94e213244cd0aea3d776b99698780d08a6ff25930e8ec7206050b059e7a0a77d367b670247839a5213ad378ebec8b3e87d93265daf8f1bd054fa15e07a76b235650a543b6aa63b5454eddd0ff86dbb428c0477f6c188bf024784abc9cb7cdccbc8a1f7230351c3b8be39d923e594efadd8205512b269b242e2b224eae585cd6e473bf1f3a0bebd6c05ccb5577671e31f43604f2dfb2a594c761cbbb720ca26db46d574e706fa772bdfc5a074aa36bc5884f1039182eb0152dcd8dd47f08a32881dec15c3fe2ac5c2dfa911564a8b3e820b4fdbc3419d6e5f81e186028ca240cda646b5797952ff94bf75bc6b9c7091e0cdc2b3c788c0ba0e25f374012341ea23c468972be325b9b02d44e92b80189be2cfc8c233c5da48be50280035b0fa45a623eef72578f79be88d543c17d21ea0a0f5ec218cc7fbafb1aa46f9dfdb58e7e681ba72139b7027fb484249397d988fc409a5de80fb55d0ebf789b8aa5b6ae2c0b3e9f728905131e78a1395378ffa1f44b37291affc57653cd7ba582315a759b6df28972d14ba3aef3b9e1f96f4d76d1760fda06c299472bd946d3b77299a19e65764b44858e9e0b7d3688c4658b248a50b354f0776e3a1fcf0a24de3fb86a0844658e510527cc33b94939aab58455d69998561c2bc69003586b2c150ac93772f2dfde723fa75f5d659097580b3d310c12b90e3ff03dd5835f0aa2cd8096e4f5e33d2872b60475dee13c6862b6076df0ef494c0595186fdb7eb191079f859a234f49267204a5dcd3b4f411bd3babff31c33e9cb4b9627e507b687aed19ad15bbb0374372d7b0a8ee93367f7ade9d75526b139c64922af98af9bb8686a3287860361b1a72dbbf6deca6394899c4f0606a8ca0dc8b4e459d3c68eaca978dcb9ccae874905560665ec59fbabc4f0122176d0a40bd8dca72be848f99ffc9f1ca8e11b41a9372be2719dbd31a8574938c87dd65c851b214b11298efa8792e82a67cfef1594b72751085ca76d0717262b4c963a005613d587fe64e6f3565cac686c5dc84535c7265f09a7747d3ceead69313cd40ac29c6d901ca1cb4ce0e6c7fc48affda9ea7727e113bfea817135496ec74e633be87e48b1493fa3f3c198cd7cbea0ddcf49572e50c9b2ecd23d2ddbe0ba069959c8f3da5695c7059c9fb7d4fdd1cb8a1ec737202487e81813a0b6021c89bb185a442bbafbdfe9bdfae357a63fe0f9701b3944e989762d20a13610e3258228ef254edc992ea615d5add8dc0d11836a99b273b72103a1dc41fcb17d50b0c5384c7c99bc938bb52203d680c66b9b87f7f2ebffc7248a053d87fcd81da1bf9b821094d01616210352e486680d7bd95e49e80c15b39a150e41da07bc052684febf96c95408aa97ed417403233d5f64a98312c561734a3d7dfe1ed6d2b4e5b0100528d8fbd62dd71d0ef85668250e5b2a195272f556a89353d78dfeda28e5f0e10f8c20865415a379a278a83bebccd3090ae3a48c57281897b93ba53b792105c45cb9512459108259988907691cc10bb5d2035ce0234ac317d1a1d7b8c338b93adbce5897c61669146500b5304fe67efef400ee17d3a2d6553b30baa73ac79d795d214522dfc1ace8b3dc48e3a50d604b876d38cc772d85e0f5899ffa9d69222031f6a89ee417e17022445592b59f53130f55c25384a2da91594f8916f8799a23082b2eec3439793380406f8605fa0a7047a7c70ae66a4524dd61124861a07ee4f8826bd1a1a373b96a018c1e8b3be07248720565572de732d854b1ef362573d99766fd7de3a4573b5003cfa2a1725faa408cb3b2c72cf17d43f858c6709e745eb5244ab7743b4877fbee7614c1174cc3a4de4048f19fc31cbcf3b4a228b22d9ff1ca9560d029956528ded4cc0da28b44d5ae330176ef6a215bda267f406e833402c91c28ce9988de72207c012c7422e2b1678b5c072faacfea7c4785cf911161a46f3c5614678b5b19ce411470708ff894b24eade5f35de66725dfa984f5c6747a4ae9004db24ec3be7ee1265a22e376cdd671a6d727ceb8459da2d53a98f7ca9a7ec9fd2ec98f9c1f5d4034468061fc00608a9b616280b25c3cdb0b4f89854d7741505997426f5406c35764a201b685bec81a14626caf384f2f531596a3b0ee053b6bf29e941baf27d4bbd3aeb3c1e063ba33ec31f7d0d78633e52804c1432eadfa742d431ed6d29c4c5cf6c617f0dc6f4e8b3b6726e7175c76c9eeeea80810135972ef8b1a0272a32862f54381ab3e3fe40537f7204c2d22ef6a59ba226eda37e7977e0d14d03580e2329ae955e84a989e6819829415d22cbea9f5cf58b1091252bafe5abf529d135c2317562a4e039566c43290014ba73e2a3559f0ed2b3805eeba9d803c3e5779704d53e1bf2cda432583383725603308724c74384834fa6712a62a40a1815715f8e8f1a2d028ec1024693a5722d30d76a41a57cca9abb12246e96164bdb4630acc4e8413524d25b2a4345a672514f5df511c91787d2b1d8ff4fbfde52d3e14f8b02c2a4ba9d1fc8ee0d9cf95e7d97146cc796fe89e7bb1871d1a360b06ca6e7ef877d622a2c3e5ab1142fe1074db1c776f50f8a3c19afdc6052e6c2d0e80ce815820a8532ea2c02b03de66356ae901be6eb3c00e5a8529c1771e25835927f06a1db69c052bafbfcf0dfcdfb130b36d62198c81ab6d9b79261f50575439ee1b2291039ce753faa033a0a5e9372da27a7ebed97e3ca57dced4bee94749174fde0d8e7e0133c983cf42e992446720f17c5870c813a7f3b66522e68690c8caf1d89a9824f8d78e8077734063edd2ac82dcca2c9ff6e597a0aabedc8d92ad8234b136faa57a929fec153972f58ce72f581dae755f68719866d3308bfd956e6928cadc1984f94098c00a5504ca475720465e7456d367ecacf6fe1affbc6ac117485a033266481ec382b9fb9b688c4727ead39780853281d0fbcc01bfec59c0bddcea1b142acd5b4d2d6a4562b6c197224ba97db4ef0226080ff2df08375a17f353b5c3e3e149fcef63490be818fae72400e57d5936b2639304d94e86e31b538287a184194fb02f0685de93fb9e05932175b8e857f583232976b40d658e4bfacfc09cfa944f264c40495e5538689bb721e16b66f120759a747093a943f442368316e4a922e74a09f50a54775fac4bd26c407698d8e911aef537a5962ac5958df5325d16870c5eba00b9cfc997d1429724b1083424ac1f6a6e540368edabba5e63f577c5bfa4c83cdfe437c8955aa8b72c292341096423a127f732ad4ee34193c6148fc8f6465af609157e2a6eb221272dbb333d57a1262599258bb1b6d60baafba93b25aa5c239de1a85097fc5ba10727395021437bf500324455533c20941c31176b8b1c898f047be37b5fcd34f5972c4540e1d6197a216f53de3a9992aa83787f6aac6a381c09870ea56aa5bd7575f39ea0202ebd72a6d55646d3beaefc5e2fa7744392bf860f6168cb62504341a72bdc855bdf30537a2e82d47dac6c182202013b289cc89c3f25f801818790bc24e2fbaffa00ec21164bfb2bfd6bcb59f4939d99694eb47e79596cc8dc40a3bdd724e2fc9da5d956e7969e42522cc497877bbb3face11d303f3bef891b37374e572de2b6d15bfd40a9801fa22d57b34148e205cbc4131a73974ff3858e593e4847254434af35e649a6bd4363b1a1870d334fd6bc6d95bd811c7231907c21f201a0d45aa52f13cdcdadd8127cd297753e6ecb82f3797a709a6f2803b764485a2cf40d6f05d5b652425a734fbad83514fe333c02d14964e3898bddce8e4457b5a9a5b52c05f8d436843a24fa43e81036f2f72e23803bc577c2aa7a428f6c35632d714b363edbff407cdd6c1d46e0b92072d022c8bd4ed2d3edebd3ab5e3ef4a74ca023f3aaeb59bee1a449b31d1c4e4091e4fc9704e96820cf3003c0bc82e448e2472f8c76a9679aa90447cf0e03fd18673f4d10f6afe932c94c5646b52d91baf0432dcdec7b055a150c0c868e792d9520dc54a94488dcc95c2f4340c1a8092344e4547cd811c2fe2a196180e9745d090a41bcb47417543bd920052de107895b5f3724e1d4229d6bbe7eb105507635c2ea28721915fb2eeaf4631a22be6709d981572940dac523301417697d8aa56faeb1ed06168d263a25fb17597e558ac3e0c11725cec2d1e6b306b794af9e614ac64d82ae4838ce0669545c632ba6a618078cd5aabca751bff9eecd166ebe38e6c809e7210cf835adfe7d382be0cfffd8d94c1729b021fa643d495450f622f4969b5b26cf1c9b3c4cc3535ebff224b86f39ff2724b1aaafc0fe6858071397fd5c110ddc915acf90107f645e6666eae3d377f35728449491377b81c9927009636aee1728c4ab657083a22eb10285841d76cea56551b9b34024d04acce29e0bfd2c607c3d6fc0e8db8cdc71e389cfe4345e9d64372351580a9be84f0f1b5e2dba9ec1bff79265569ca66ab3deefa7b01e04fc75b726d57ea6a3d4b373c5ca9def98f58f64330793d1c0f384bd9f9453c21c7165f383fde3021d98d7b4317a5ec3d5fc109857b7d57f1bc7c947644cbfd57bb137d72bdc62bd9087889ddef221809221763f2bc0a511f9f15b4ceaed6d68c5838723578fe99b4172cf6aaa7b53140fb00bf32542a44f469432fd2bbfd749e6f3af0067b2ace56e55977b15bf6598a9a2de150cecc18befb2037a276499bded4a7a3197e6997e311e85ec1e12b1069e1c211da1b167bef2bb200edcc13afd9717877728c6aa3f54899b1766e1adf5b442e4c1db37012575854988da8ad302594a1ec06ac9b45cb484737b34dc15b5063fa273acfe53a33e6fc3839e001a2d18620ab72e9153ea4da6fd750fc33611cd5381526a0b913cf3af3e068b1d22c894de9d272e562ab83f484b90d35e3152859f57832916e02dc7653bbbf6d5b297e96a6ac726b9546f8e397e2f40a7366dcba83e6d4f8fcd92ffcca635a9126cf0a8d383472bc0896dc6d0e3c5d9e0a371c3720e576f9050bb66e6a931ed7052d0887056b0295cac643ec3675a09a793fc86cd32a8cc34fef69b20d823d5b25aa2753961713042570c421e0345944436dc6eaee5f4f77fc07fc7a8d9b64014b4e364be9176859d291e0eeae041a2c2b6b3647a0823e2b3cb004d6101d6efd9ae4029e8aea7200f1a4ae2dfdff51777b57c7df3fe452123d307e2d4d473382b8b81182eac27228bcdf5aa94582918e30ec939e48366502ec4f17b6f8beb4d1e1a7d27346a3722283cf82e0db932da5b420294f4b20570893acfe4622cd179835e869459b72721904fe9e3af7cc93536cb0626b0d0fbcff59362471e868c86abf6a119f533a72165d5b6cea5ff093d0fce2c555afef9238432b6d6f481abc48a131f65562d072e7c41fd15e4c0213df69ecb17e79a7f28cd29f27d0fdff44eeac28c0c44105725df97886e2a199304ba6432bb3db5cf9d1e641df2cb0b18be2972772d41bb172ea46e013c6e45b4d90358eec7e53d8f11269276c3523586f5ac34dce1d0588688d79c33af989458dc851d6a178dd884c2258c3cd1af784895c2a1c23efd0ac724fe9842f55a8344e537919b4bfb9997013bec3abdbc2dc68853eec34d762c572e001529527129adcaf4bc90e5657a6b68e1c72813245eb36da232e390f251a72b21acb5a47c7acbafd242db83d6470a1d3b2c3a922b44fe375180e57cdcc1e0662d0d886356a911d2f0677695d8ac89bfcb5a29826080f3ff526d99b832f07725ce0a84e0fc91129c8d8da0c9c4466c006a91c211a41da41e90fc22790c33a1a38a96b857b7aa79fcfb5f72b8bade7a0566a8fd549b388a2dd2d63728a3af0284672ca6b0796b3a9a30fbe36e29cae3cec2b5e9f56ea2997b5bb5d3828ec621b5b81dbac967984f92544b0fa27a0faf94d9d6d3842b5c08ae0d33b9336d17572730d662fcd4e3ec7d67c6085b5bc5814c49a207121dac6c5c35606d1adc56272ca8c941b00c3d30a145bbac8e9491f2a85db86814851b7fcd978f44f729d9f42dccac2aac0c674aec9c5346ef6ba21a095b7488515f423f05009107d29a18f727fa4d2872aafcc662099e7322d55ab62b8d84b7a4b77b84185fdfb6bb4582572d5f71b6cc1a0412faf0ea74be3dc236be2e0d49575716dfeea8986095f264704315eeacaf3b7f27a9f75df8804f7cb69b45b2b6a4edf5f176ba174e33ea64072af4bf5ed8effb98e6456c6456abfac147cde8d9dfc05c29607e7743621905e727df5ebfbb6d0dd674d4a3286b4bb081439564b02ae2bbe03830d84a054d21972c829ebc856d58dd7485a943306626c3a77f0e12de060ddd1fabd013f2e33947220554a815801d990fae6c03a4fdb18c2ffd441c10b0237596d878e51dd35e1548196842d273844a0472c588163691f02a2d9503fd478507ad0e2a0c91d8de32967b461848a842873dd79bb6493d2d4a1ca640ef689caeaab3662496d1650eb233e471036f5d521b7779df74333a23cff4c8d44e1e07b698cce706464413d5372a084c25d0793282b00a3bff0a45c9bb36b6bf43bd6f65aa3c5a5027d2339d2122776f3b04ada0b3b6b03512a85210254bddae521cade8680efe689e391e1a37272d5cd4aedf4fb4bbcdc4e0c3778a554004ae5d435187ad141ae957718dda772c4a47db0909877cc5294b6af6a90b8258deb16b2357286992909c1f2370b6472ae5c43d9a8487f0c8cd0b2f90d5c96d73fae19c5ff8d8ff62b104f47e76c4972520ba40e95e6c53699e1e380beb08b440fe67d33f5f633ba6e6fb2d80b01ef729dd2915415b06fb68a8f5abec4b7ee0d78318999ea4e45f8f7a431625d90b60e00b2db612d2dabf6b74a551b974b2f26fdc24743b4496b9f6278837b22e7a872c1c94497a1e4dcb564cfd3484a6fd1cc714c254ffeee6911846ad13a591fa17212453237007fca61a91e3065ea905cd56ef8e1bb80a1730a29a8a4726f0a97725a0e85e4f1a87ba36c7e747b1a92f72782db4e885f378579cbce1fc87dd4cc7273ba9d37fc780d369c4f292f5dbfd4e511dad3a4b5c1146f70747c219c2c3d72908d197a496614296b172a14135adf0ae26646d58c6bf89b943ef0572b6d46516c5f725bf2e2507a88f5c74b6fd2f3cb418dd5f23d61d7c88016cc566e1fb672dd32c4067ccf54e08efe7248c96fa5010810b20bd95e8a31f270d41d91da67728adb15883f65150d0ebbde824e629eba038d1e5c8f04a5b896ff44311abace72b2cd6d6331cf62262a2d3940a9212f4c4534f4a9fb7bb20aa3bd8b12aa188b72e274695a9355eae441cc17ee65d5fd3a6e7a41d52c21b551478b97671cfdae72c8fe725099d9aa306bdea385a4bc9afb55d57d678d315227e7e4078f0cad5872671490990711f66ffc3d458a5d25c5133eac25ae4d0a578d7b9f586f48127727a6956e7b19942409739efa4f9a306e3a7ee0073c086192613ffcd95397dcde4c988d85ac4ad9d9aa7e439d163f5c8df83a85d5946e086dfe3465d9626331341352e2297038557704344ca28087fd9af827625b806c9ebc70077f2f3514b9c9499ffad15ee36f8ad1af8ed774a09a5fc5bb48b3ab802a10702b9bc7ba1fbdb272b7d19301ec49ab7c63307afa03f808cbdeae01423bb23e12d068f7e928d66959023d2075e295cae4a2c104fc107dc2717e892b040d96926723b6d5e475c303538be46dde3ecf402d66d59d15b40771a3d1d6db37a667fe2178fe49f9f93567728e9189b4507b73edffa0323517f841fa14e74e2a9c397f44e1ed6d0abf0e6372a5b6e78e57121071431a8db613a66a148c67d0ddb5a9f9e99b52564a5e79e872b3c6801b577c76aff48d708b929854df507c1eea74ecf0163dcd4d4ab5a36572c596abc2c4b78ad670c24a4d29d130df0b2d0d8e46665dafb58276bb6eb6d647f77218564d17ec5ca1b11d1e51200420821244b2d2b83a75b2398ffcdc0f787222fbcd7f3598e5f3d7f7fd1bfb03fb0c1b94df2fba07bcf0dbc7ef5a3c7d1c29fc2a325b347116686f0ee610d9521557d182380d6322c5625e872e966c61c57223a5ef4ced0a04256275765f99e145fef6986e8f2c1402a6861485712cd79c72e7e1fe5a7e4fa307099b441118e8ca1d906a6fb46c57a3d0e049f1c234010572348d4e8cae71ce3a553a7e69c058b9844edf03b4f7845effb7fada9bc1c4e1626a50149469a522172e3bec02e83e15267088d4f800449c334019b085ae4220281ed80ada304456a315ea3d3c48689d128a3d03d8bd18c30bc12c8957d2aef91ec129cd12113471da98e4932bddab1bd9842c5534727e0827275a6f5e650b997239841b219d0749cdeda6699450eec2e1cba231580165863dfea7ca342e875f720c8d372a37aa205f0985475d8033f2252e117585de504f80df51278753d41737aa59e7f1fd9778797765799652a67d6cb5473d00b0591367b1a81902f6e45f203d869f6df41c6e7697ec8cf2e1a0e6a58d341cc8ed4795464758b0a2a2966672111d0c882fb328dc4b1df8f286a092831d650bfc5666c58f38a5755a35249d17e740ac3ec59e825db424b3995ac782e4bb0a57887b32992229c7f9dbca0ba472e6e69b6462186ee7b62dcd556a8b79c1f649a0e1a771c9816431f0f72c0b7b725b8a7e5c509e6bb91e746319d822d9506b90e819cc871ccaffdae99fc89e0772ed42a6b95ee96a407a8bda8a7a8982235f0964f3947d0e3f9c963aaa1aec746145fccfc6e48090f7ebb9a260d7d34c4f19a0b983a554cdbd99112d8a842ca672c6b01f0556a95ab58bb6ab1b04f8aca5402818ef7c1379e30b899c429f69da3771b30734b65d55d45c947a122471c911d593ee86d43d5febd63b82e45e037c72293d7c07bc7626bf2d278676a474406913cff14f805a6d248ae76c19651b1a72c0c371197585bb98480b3444c52339dbc0f07fffc69755590fbda38fb9a50572fe5890badab91f9fb7490c9e2850f6fea89c1d387a4a7270c9cdcbd804f55c2fdbb50929254cbf136eaf1a753d040e96b7ea563869b5d6a39524de448c32415685dcb3a88edf97154c8b329db808db6e680cb62a6244c6c3626900080fad69699627b1743581d6a40af5902fa844102a18de00b3444dde6f9364de788c8a3772375a700ec0a663d58960b6d0c3ddaef4b41a68cbd9e97320ffed5f1f8026d116feb37c5fd07274fcd619192576fcabb3e1daeb43b97dfca179c2e2eff3c9692c63786c62b4428cc4199c4fb0af7d8820f8bef3bb89f539901334a21c5751ce72eb88e668a06e5bf137f17fc2363554b82982bd582d92d647d6a558c1cbece73c470695e464c896d4855c61042539db117b25d2326b8495e193c3c140d6123172d19aaba38fa36623eff5cabb0dfda70b1a34202036d0b4ce59e89ad51b70c4729d5325702ea4990d7a5be4ce0434c4e025d2eaf883c0c93bf81eb10377caca02506f943d190054c3a5231b3c12720eae9102f64fffa0665f18b82032c12196421d94ddd45a361aabdbb2e6c1a58614c58ea61bb1f725990fa998487f0d48f61fade3a1e7ae5fb7649d753f5f77c39e25116cba6db9a573c091661735300190487a6137e367415ee5e8848125046c80ad376d09a9b7197bf260b66f8ab3e7617286a05f6fe66a1f7ec21d8facadde83d84de2c83e7d6f81b14190af1018943372192ef228790eefb42db445c790981a8ec53d6deabff7ca961a54f47d1d6ecb724503a935a06d96656e4ca7dd1d0f49130175b73097060416bc3150b8051497729889910f270f1a7311c14535f763b2feea845ba3030532b33fdbab489bb898723ae6ad3c16bbd366ae6e6f325a12fb85c0bf442edc803bf51597b32dc7ad7029bb52155c61a71e0016e60a549511ecb98eb85d2ebbbbc59d8caca4485dd46519bcd43ec3dfd5d3a77d235cfd6af8d8f1a44d86e7e42185057cdb634f473ea14b7336467622a0f72455ee7ca6abff86e61b3a0c11677204388e6d84f6dfdc6172d53cc6c7d25dac5b6a85ada69d5bb2b4afd29df98c10e33662162afd418b9972e715172964a92c9086b67e5c96984cf2d8e61316118dd81cf595a120c998291ce4eef62557f3db88dd425d2ff5106ff3c73138b07057e7eb3fcde63356d75f72c789e203f8263b9efa4bfc4366663d6523966410d0b6df00b17ea09d2818e802cd2e07624cdcce9fd46599151114316fab97ef5f7db38c08c9d64778558cf772736449e65c0f9e08e1e87c124a3cc8300533c6afdf6d9a766964e5c0af7d7731a425ba7345585ee866f5c71846304e67d7367e01aacb7e3a9740629a164f9706c5fb14e13ccd2c726ecfd538ec47c9e2a5f002303dbeef29fd9db41d42812a1013f2b9449c239ec6a92df5d64f797e934a2ad5b5bcf74a5bbfb659f03f8b563582285648aaba89e1f8ea593b404cc9c0b6bcd7d3b07faa752a7e1f6519552372f65bf2e18636f8c6a047c688cd66f7c4f28cc6fc9535f0aacfe5b2a83d85db72ca5523c93b09c675300ee9c5c9829d46e8b51936860ef44e4fd14a55239872297f0a7411bd9018fbb2300735e7ff9f7c97ffc1428e008fce1f15a6858635a3725581ae6b6a42c48fd1fb9708342736ab65e7ba67199a0823d9f8386744a27b72ba549c4e3e35a22b9af9ccacb32c30b0224ad9983185f6860fa30d74f279bf4cd2265b4bdd4898e7c8dc95a7e5fcf1a29780b2b3f67b47524701d749100a686c9d78bcd5edc306ec4778685ca45db593c5a44486d5fc34ece41df9731d9ef9537a139bce19a9af4f7c19b390f36fd248b7dbfdd741d3777c7e8d431bc7afaa36c4fd82ea5033a891cef8d647984023e5840aec4a4c0bb4cad61c4cbf728bcd725d00abf667edd6653773ef58da3b09f8b0c655093b1472af5760bdb0db27a23e19f8ea214d9744d4b0abd3b0e89bb11f8c3ffc189890769fa8404949d2acc370ba05e0a7a6ed65dad3e7c59eb94fb1044107975bf09e57b82ca3b2d006dea3727bc5bb1f43435eaf3e8a786125fee51bffee8091262ae4862812aeed51cc3372e5517a32749f00796acd51870689c77ce5dbba2efd55609dbde6b6c01d215c6a9578476171caeba9f34384042d9f1926047d148bd7b60a3917d70939d7cfa87254d60a5d79c0087e370a27f496095229e1380ec1eeb832d3aa6b8451f7d6ec728698bd6cbf2809b5c4c9833da64f56b36df827d5efb1ded171b134ff8bab5972087e44ec90e4aa853f801929d2bb597d6744c77716842c827a5156411d173072d7b8e3362f725c90333db48508c97338fe969ce739c88c45737e136d201a2408bf83f71c84411947236afb32c3c9d133908c6b7e025eac5247c2667db3391d72a5ddbcae231a3b2a26c549036ac2275c68de41d84393c584105123603ca04a50d0b05a93982b73aff6a2db1d85bd9c847167b889bdabed305ca3c4489baa6b72771cd01407774caebf1c23f3535ca6bdf08941f6a132b601e6f5dc4c3449071184fb6b7e9dab774fc231a10d95b51b8d72548eca540e58ac4bb66022c31074729764be1236404a38c53b8d86acaa30ab94c8ad01fabfcb6450da6f394fb9106f19374060766e5d8365947caff55f11dd14e3d641ce69c729914a77550521321c839c9e4eba816ca1649f8664dd859f09bdb8c07cdc1f625ff69315d54b7cf872677fd22cffac8534b666085179d46cfe4d834a7e981e38e0a798394467fa687224fee3c9cd40b0a8fe2e9f58ca40faf72066872f0b24c4fb4571e2512610c1727a2f49ba93391eae4211ac81cde6d951c442f8bde478d44951bf1ca8699bcc3b328741c85cb2d99a3c7b0c43eb0abb27a0a706710ab6a090a805eb6e0a78fb72a1d65f2883e4035f439f63a4d9ac536fb96591add96b79798c8c2a88935a7b635bb7ececf5923cdfe20ea142c2c21f1a7687990626e5352e2a2aa9ec6973af572c47fe6b1d2e04810362636fc14823566a0e1bfa61d743097d702254963ae25fd44ce960a42b0015ef564d9f0c183818c3015ed6cb0ed7e3ea78a3f60ddb467245943be996274539faff6f09748e139853616f063e7759d2aee7761177e00372fcb1916dd0c31fa9c2948af3c73a98aacd6db7f8bc751cb041708d2f2fcf4e7221e2d9b181a6b5d28530cafebec4db1cf326b6cecc512879bb181288af178e7229b8381d52db4760cb202fb54aa1e47e3c6d1d2ecf332759cd46dc83e5d20a45758e18d15cedac30368e5b3fc532f2560266ff287007bd2c1421536d83de3b50c54e694af5052321fef2fa204c6c3710ecffe79db49e18e04087ac9e8633995e2da4f677731b7297593bcc1dacb20561b82ad66910cc863205947351e4c0d54686915651a3e4d8d4d0b793aa29d78d633d2f8b91863cdb8bf26557c97fe9cc58a53ccf3b8b9f2fc137646cf11499ea716e4c2c627d386ddaaea491cc751978649e6565eba5622297d26d3a1e2183cc5d3e390bb6186d7954b725ea2b5d28c67250ee811f1cfb227a291e95635670c3da47d9a3634bc1fb01bfbf238cfbca4b677e460592a684aa8391e6692d679d29e89f24fc8db755268cb9ffabead14e0272f9c0723c8ac7dcc67e8bfbd741d1c93701808ac760775665a78bc0aaf1314f50225f650188598ea1cadba887bf285f390cdb00d546903ed60e5a954bad4e2972c916227ea88277f850e19f8ca6aff7adec17f96aa48962d456e01fc82d657a72f433fc405e4ee5c72fffb6a5dccc8b702b8029c4d50c108b729345ed157a901c32369dadf6efe13668747a14a665936c98cd3d1a3ed71fc11d060ebfb04a95444e1cbe36637a52e4423aaf366a0fec9e0188dc40a2f045e4b2d3c5a2e4fc9162b295db682dec1e844a336d3a54e03b31ddd376dd90f2daf8cd6c24e995d2a2006b67904e631903882e4a9b1c344dd2f5c1d628c2a2958a928d317d05306dc372a81a9fe448accc82e3b1f731a14faa10503aa80f2d7b8206846aa50637ee2140986717ba81ef8f9aefb8b96de8758a32aa6173db309adb00735e76cbeb0d635d22649f6583b724b5f50c1bae11b5f3c8a07909f39d0c0e5cac3df57e1dba9b729ca9d0a54a12897c0ea8d1b6528edef80ab2e6077d67e7459ebcad71fc22f64b54a1b282afe5d840313a3435e3f224341c4c8cbba7e86aea780804b8a741bc7267410befbb4c9e603cd9a3b1d25c6cc58de56f8b35088737a9fb86feaba5f272eb4b50b9d7994dbfdcd17f29daed53bdea4d3f2e4d828bb4e2c4132288868d14e04fae93983bb6191eb6bd938643162781557e28f5d3d6ae4c3272bc727aac7268c7a568c17312821ba1a435fe7922771b93daf12691d4435ed6e12f5e1b6372734eb25cbbad19c5a8312c3c992249a4049bebbc7149c6dea5c1566a989f7a723cae502c885e6fed68f1f73a3ce68c7f1fb4f63d8c3ca3e315b0b85e1d979661963f64499b8874d8a800e3faf4720ae805c65d4909200d4e3519bc59a973af72d533a7c1f15799847690d8ef6b621fe23a77f46130c0f208b35d2fc8e0e3055bcb11c6c425d34c5b9ddcf33c6e63949bdf059164b58a00c3f99fa3879cce5272f02e190f2bd21a87e5e064b28b3c9f30195d95c1e717e971f888156da4bc2f72af983ca9d5af9415545a76fd15e04329427e00e0fddb6a0d181a22ab6eee86725980b86b2f540b79811df1c8bda36e31a624774d15d34dd8938ae5a5c6c89972afa9d56f62433e7d471333e2e7a8893f702007dbefaf564a4945df5b5a351035eb17e1a1f3ff7b25037a216029cf8b46beebff1a60fc1f82cff23854467df62add293e2d8ced4568245a86814175ebb68b5c96eb9834b1b16271feb179a7770db6663d5bb0e0f0885b09fa4c534acf98cc5cfde5450d266c8829524e783b5a3d392d24d6c185f6906a6fc7489ce249430692da8b95342bd904d1afe4997421723909b8777d2b65902cf7c728382b0d7799c04c233c8450b065c645f3f129160e31d6586d189b5466d954752da7d00b9cb00d8ae566add7243931921bcfd3c62787db7fd15559b2bb6dca9f229b6e94f8ce201dc94db8a6cc326dba1d23e3b411e2956dbfa431c5fcd8b29e0544e6a50ef2f78a109efd0f9f21e555104295e751dd0daf11aaac4cdbf6db5f42eee776e3a3e30032341bd4b99a848cc677e56e72df7269e191e1bc4d163ae4b926ab3505244741ec50522661af02dc3bcfad5072fdcd89186588c70315fe0f02310e0c259cb6fd9c76f1622f80bdf443b60cf2721d76068068a3b31c093df22fc7c3cefab248aefcd301fd244f3091ea69e7b94293b230820fad6c10922ea4db8806af201f7bd3e4b7b4cd3349b2a443d5ecca726952c60e36103f5e8a7ce5e02263d89b7f7ec4396f674d74ae7c19a2b52792260839b6c2418a0d26a5565aa1b754a7d4f94fae0c8c1ad13bcfafdd1da0e27b61437b8abb5e426e82e804b40a573f7be95d91a30cd38e668364cb6b327b5d6872646e6e318e339994d8caee6a9215506d34a60c924fdd71a36f55c7b9ed6e642a15464eeae56d49f4951f3531c75eccadf03b5e8ad5f8c1a1746252a9bc254e724415f33c5f53bb331f45f43f212f8f1e3748d5f5e60b615a66d5ac8c9ac3fe724b2dedf41a39696c7e315f956fbaeddfa06a767bd97a78467fe86921ede9df72b6b727e7ca618eab083b63e4744ce111c9e5fb8bf5d60d546268df77ebd58772a1248fcd38a56c3553d10e297d7ae4b2924f08a43cabb5b37a076c9979457f72d7f488df8e716cb2c75f5915cbe8f74e99a36b03f0a9bfa8eb271300c846f9729c09f7c7422c88c1b2427421a2756c600fbb753261947a499b6f1d3edee7a445388726b441d89f3c79090ae01950b87bbb030d62a8c16cd646ff237fa1c9425bdb65637cb8f92a1f16677799a76657b2df13700e91c9e7cfca11a6222c8e9460925eb303f225dac3d70e537dbd84fa5897e62d33c423320c5feb6b881369d736bd5b909719ec2d43bb947eebb78e7f2c4ed5d76f26ce2121ab610bcffe8a343aaab162c33f169794af6ff9d9ebc3c6006f0bf499e36d7685c4a499613b429972a4049013677864ea9c5f919e91550806327f22d9ba9b5fd3f8be483c44e8c067f10dd73a513e19a283db055c3f6866c838ac21a51ae3c4eb5cb8bd7dd2a21972b5877b7de65ffed495fc03b8b33dc3f7620d9333eec7539917dc4d279e52c40cd451a19f2014b3050b82dbf16a751d4dc6a932fc63cf28bcd0f3dab23a964f4b32a7e76225be4de45117e973fbd86d29e0d8296f404f0c1bc3357b62187d3372ecacef59595bb907fd48460e2af2a912a421a2ea44a8c85fba2a293542462027fe545a5635736380e4a0e59e7dae2cb886d7cb94c8871518fac10a29a8ba837223e2d2ccd60172a2a649a4e1c091331768b285273f98c72b7b2e8a21255f6f4907a8c7e92cd15b4022e2fca3a18c4f4efaff20f2c6592e65776afb723558d07281f762469fde0bfd6fbb3f76b94858e45b85cebd911f86690f0b367edb32ed72991acf179b80e16b0e297bb7274df62ca809eaaff278731c0dd428f544507c7288fac942bd0a32f7a7bad3b4550af583358d6f34ece3c52b33f313b21f761324fbee08846db9eb8a9ccc530986e8ebf699337b40e5562281df81a42932872f729c8dab4c31495331dff857e5e9a4823d5f960be1fbf8a36aecd17593065365726d065acd416ed928c8751d853a01def098bead84ed937f4d7045471c537c5a72f838919a0ef0023a3cd6b77b9635a9d19bb29dc9363026bce601dac1cc705172bc46d2e879f9c8a10449c70f606acefbfda0d118e8270d6e84bcefaf42f90572f79cb6eb2ae03bf7e31f6dd29afb66457fae8220241c1c5453c5cdc8466453347775e476a0ff27834099da3ace9187d92630afd85594751ab80086e406b52e722fdf0eeb970dc4fcec69815214e93253a88663d1bb54ea456da5d153e77d3938f9a3efff219b8d0254441d18286c0809cdec51173cb6f7e704335efffdde62727400ce021932ed4cfb4f01ce4886bf5bd5093a82a3f96d16557d32d12c71b22b0c37fa0fbe94234e82f180b497a705eea6bc1d2d97886ffe73f79d017b2a2848d6e13a5bad55c72d993c17501efba44c6dbcf5345bb50f1d343cd377d004a87247daf318e80c1304d3871fe80b26e06e12c8d1a2daede6ee29c9cffe1cea9d729a1edf44d900b73e580d389ff2ab1c806538eb12b4efba7e515a4bbe64185672f15709ec5ed42a64b2ba96f38266eb89fe846066e3608ed259767125a7e654725bfa15def49c2f36c7155c3bb98b9dd6c161aeb583493bfcca089d6160118f72dde927895f4008d4f9db49496627392b2cedd10dfb78c3917017942f2517e94cf31cb8af10ad1af7739ef76254733ff875fcf4419df86329a719b6dd838ba008a83ec6177ebfd15de02fbf70b802fd6d4bdc653e1a9435f262814948df92fa72e1bcae6c15b68fc45703df9e3a38bc2b63ea1c11df7276e33a5470c93937c47288da173dddc328e7f66f48172e29d7069195ba8b02232093ae6f90933d8f1d4a2df92040c63ce54a7ea095ab8abc7fec4b875c03f384669a9a84ddfa35def7721a864c96c33f2f5d923d785e88e93e590bc9785672efafeafbb9a52228150e7232c7d396fa5432fdd70ced9c560216011c81161eb4ddf1c80d62406e31d3be729b45dafad8ca759f005e7c8b8227eed1b9afe8cb5c5c2451398708d4412aec72510c13482b07628a8e424a481c14a3fe3e4c16e02481e9d64cfe4316cce1a172938a0a79a78ab0524e899a070cb9d69d410f673a10f9b246ce70d3c95d6d4172ee8a9b0c92cb7aae5b12e32d42fa082202aca8c90c49ca12a97769c5e733e949d218ff68171581b120642669db258704d2332950a5f32ad0579243a3205b9072c3b29980dcd7086c40e8e51f3f214fb9021fdfc97bb362680e5ba6281aeef3629e0add51a379521255355598842b7905819c9ee69953ae7ed24be459db9a3b7255c636571ae21a635002b801d2159e784ed4cad21f3407ea363cc30f34790dba0200001d7968ffc995d6a495071668f4a957bed35c6ef912f62c667e5cb535cb2bbb1e534be9db94e8e60111018ebbdcc041f2bd1f3b23de78267f44ef1b48b328cffe72cccb618e9595575bd72f31536667e97dc96a1f6c46fcdcf33c444389823ee01b05f1db9cc00e5df1841f0cc6a9499363147be186c20d2c48f03d2a97b3e663726e21c5641e00311f9ae7ea2d2ddb88a7e3e31a4d275784f454d272b4bf331b72a2ccc3fff8712b0222b96bfb9eb203bc902443ffc94bf72b9b7a20a2faa1b3561bc6e7e1fca9e7a3f1ff19c599d87adfe1c53ea891ad18924dc32397e04cce72e0551cea83fdc25291bfba39aa157504dd4d85a5f9b8ba715baa7746ebc14b7215ca91d9f9b0e4fa6865d865ed728eac450152f07be01f2ed04fa3810b62673c8ce780957aef49d174a599a9c6e70b7e4a04faf130ea35bb180da7a2201bb46ad873f6a365907db3b3cf0e41cacebbf701b48a525f762676f9db6d10877d1372df48dd3d32fe4d302bbe2604193b93aacf69c61c87dce926f1eb0fb76a436172c227b5e3c4d8dda7cf24835c8942f3fd84fe7cdabeaee3bf43d6592c250a4272819fb7bb0cb723fc0df5954d278a55c6bd6b01c0c9dc19c6e66b45973b3b9f7236d7d0f37420004473538bcdd4e24b72f302b331b89cebf5d9eb4b9e037ef772dc699ed01f916b443542d6ba47b495fc84f2eb77eb8375aecd6cc0663257a3301b26d4013638371daef6630962d05d80d3a055dde62601d39612d8ba7aa58f72a4fe6911c2067f292cc1b7f2ceed4fc2bb2d94450a47f6d280e0da1f8866256ba8f21c0c6c4e4e8f6a3c5ce00d3c8503f1fa76aaece38f8bc660f98c97d6e164009a3e806ecea5ad06ad011faf4127500e3cfe322f1e397d60b650ab0800b7725fe9996144a644f6add19dbb597ad323e65a5341f68f18134fc1c7ed6cbf1372663bcf815d375e9622974d4eb7c26c0f9384fdf0f384b32bd19bdab24d5e712a38a65ce79068a95ae63846d95acce5333d54ac82399aa0f44c7c31f45f6a5c723521be7d1000213469967206d7e620e60a30adf4df7da92443542a815055997232bfc215ead8d3c0080f7fbe165ceb66298e7c25deaafa1e1ab8567fbd5bb67262bbd650132e37c19102a49b86a299ce4350cf6bd0605079a49e3acffb33bb0413fbaf0631c11a64d045fef9cea07a0930222276a674693676b75e14b014c0116b85134e9a8e18690ddb92eb5ba4e2d56d733d2e2240229b750eeefce2cb8e3c4a91bc12529c383d87c7df3bce21dd140281b0419238f10fbfd7449f466041464920436f5bc03fc105bb81ba0c7223c98fe7afecb0876c028acd1d3df443b87270a919a4f07d1dea4ff3f45bee0554d7196fd16349dd90a5b7da9ac10a99b972e738a9a72371cb02675286d26a272b50bb3983df94b513c11c9834d16000ac7298f29375eacb0087fe99247afa0bbf73ec9f2a77e1055f5e06dc6135bb075b1cb03bcf55fa1cf92f154d31e84b05a6fdef6ab1ca5d62cd40257ae7c6d09ffe3e04a24ad571c567dd07985bbb4a06fa7269759e370f404d5e509840b863d0b90f04b12de2f9bb31e431d6ea999bcd4282b5c487ca2bf399b93be12d48e90d6142a31dfa1063f45b131666245a009c6dd4408263fc0b80ecc1ce239fe38e2c5a47a1e5244e59a3b0f107d020f1666d45a94b441bc0965776a987cf1a25a0d4ec4093111b8e2de569461884ea04905eb020f57b46baa99f64d0309387e48fd5c072815cc72ddc023ebca201c6b243d7c0a1e6c150e51a0bce211964d5bd8e8c77091596ae1148a7a6e019ca7d7afbb7142a070c8770ac8890e8e8cb43811a6b077297788190a50742c9453a29a0c3f9107622e97630a652934967afbbdda9dfa97274761999ec3d4b17817c38df53972f9f13c42ac4b18cc62b98512f2ecf201f50e1d8f41582861a7141da885fad4b9d74294a7d05e7ec52c95d79f989470e9172cab60c6d01cc8d71391830b3c7f01c97fe356a104f943b20e859db689c40057216d0c435354efeb9bd1e3a0c258331b7af2b3401ebd7396dc33177c848afdd727e5e19275f1ed46b5f520efc581bef0cc87f1a90198480f905aee61efbd4de45ff24c63ca3915863d2036f3cc7cacb5a495d56cc483c942b2e7730f8c1e87672c2080c2988f2e3592696d23a94da86f659ae70c388709e6f41d7d73006de2142ab7252346b5e6568ae2a61e71af85dbbc55a4db6d71c61b78361e7a186b614448962404edd0b97d12ee415dae6c0f2f854b557768ba75d538ad782eb312a5c36896f252f6aa16c7e438ae0bfaa980051bb6096ba39f14c7439ea17ffe4683d7286614d00e27090d115b45bca09fd76611e5986b1e97adebe85297c0935086272f28bbcd3fe6fd8d36a715d158e04e4c271fc653dc684612deaadc2b44e8c1b7259cc25afb75d878d780f1662ffa2d40ad9d6a7ce983ea3abd08ec16452ec9372d7fa6d03d1a659054f023d38372cf7e9572ffaa4b41fcb20a01cfecbdfa7dc72ac246e7ce771196d305d3953982d6c45a525092bb1acdacac45aeb794970c139cea51436c5531248589353c083665e7b4adc42c034522408d45bf7edff6a2c729fa906b5e7f670fc110433df4db2b273f76302499f139bb8656a3953b301ca72a0a399f70fd4985179fa39a7d5736af36c77388dfb839c3da59c15be4335757248e80d5cade422dca9a44d18c1814a0d64dddc63729cbde3267166caad895672b7eafff0991d0d4a36732cbffe1d24075e5bcd5c237c63e2567adc2b44479872515ea8cf397f9e38711bd7e3dc14f2ad1b74f6fc8e78ea565a28e41fb20295724e8549ea69397d1f4007e3bc03c09ccfa9a06f47e95c4fc7903b3ce412512772a477841904e334477157ca0d158f0b161e80131b4f98e6328642d0f2ae2f4f726cd1e0473d3839e49becb24407e19c4ed5c50c2271e28444063d2980b3a6407294d928d4a11609410a9e3c5492191044a278fb1b7f989501ab8bba43910ff63c40e111d7391851adb679a845b5e4bc53fc3d651736387fd2c7cfc4c80211397226d7ac9aa9a9e341e6d9c9b9f96427af9a5a90aaefa37d3b88c09e09bb209d72ae598a92569ceeb53528bdd32961dacb3e7495827df5007156a12bfcc4f87c728172d0ded2f1f5df869278868f6a4198f017ae4c5ed4bedddf866092c8478a1097ba6c6cc2898649b39acfb591dfb0bf688c01cb707d1d025b20a60e11aa04536d43f4514072d4a773243d8246b76990e621c23be672dc9c54dced39c520ef7262492b79485de0fe4cf1c36162b9afd88572ac7f3558d59f7cbb292e38caa972b64acd178bafa907ae85897c3b7634c54abe500f915a5e340bef71901e53615b07d0a9623e876912cad54b24a8b1e1030999a722aac67e5fef0134947d0be272109cf71d326b67e28709578f97c8eda283ae1babf03e5d928ef44ac1cbb44f729b44500f99bf2c3891c749685977ab3e8781320914a999e135006aa15da45672416715b7ca9aa6ce9bee3d1598e63544efc64b694ee675fed213e133a352c1726e46d4c19d6c9b0efc8fb9d6e0cc612bfc0238bdbd2e11113de9913e66adee7290efbb9b5d2670913b74840a4010bfa10795c0599e19091fbb5219d7fb9fc332a111e163073eafb13d18a21905bb914fc5e293c0be00af44d5d2d7f74855cc7241ae061461d5270e5ab99d45915062e9e5e3a3c0b9e77ab6dbb762f54bfeb65cbe1487785c1eae5ab48792cea6e9929a2a353c40c587ae19aadddc45ad387a651c52a68e2c1e0f368625c569553dd9daa28268428226908cf4ce36065792797206939e5dd3285ee465863a2cd611dffffbc4a1e74c2b69f7c80dc25482acee6e911b687a5eb79ca6d0a9577d504185cee7b8731e329191a4eb35ec8cc23f2e72ec3e1a41429bb7c5fd50147d22f81a5b810a276b87d1b6bf0e5297d2b0a82158aa921681f194ffa2b6f85905700d51bf3c4184365b73709f62e277dff14da838602691ce9c868dbcb7c7644474694aff07a0a03bafad88f0098968821d7b955a07753aa6dbb46da47fcdf6a1770f62d6fe1c8102f6b9c987f0f865e8916bbb723e3c6bafa3abd14741b7793c78b032481fcb00889082d12fc44cbe134aa62f72295895daf4943a5f33fc543337170e2ebe33d797ac1459397c1cb90e9a12ac7203fbf57fc4efaf635c900e88b23652fc4555b87b3ddb92d0bc79589d40e2cc3def01b86b81855fc54aefbeeecea75d9eec080ffe19368205f856fdbce2acea3026401a9771dbdc2027eb8fb5e468726f5f7e1e979d5c9e062c77e078bcc6b072a4c9d94435c6c6beea4297081a84f9358630d80048d639355ec0583205416c35e5edf081f48d3a35728d80aef36cb7d341ab8f1d52b3df7923312650145f237273c3eaad657ed8eeefa1649888bcf8fc0d27ba63b00ae869dcc46b373f7974723603741b5510d7f2dfd4b85cff1d42358839e63d1c5f074060a1420d86337f72e9b4d6c3c90bac699ab1fc9d0058cf69c5e5364214477579bfd278b8e220642cb8a0b7443ae14452718f52df7f82e297ec06404b1bf8fefbba03f4dbd9c4e00c88296cb650afc014adfe72c3ef9fe98591f3604e5bf2ef1adaaff556a69da07208eb53795ef35fe0b99c98838c74f908ebc0935cbbd86fabaaa3d4c231378f7206b1ca62dfc33bcaa10f5e12769719c8676cdc2daeff27fb1fb1b2a12026de724486328dce58cda0d3e305cd35fc29b7c8918f983fe8c8d0d5d042e9bbb1aa72c840860b8be5591262761abe6b94ea1fd60923342f953f4c0c0ba071de643d72c7d82330e4edd4edf447fe4ffb818ad941280b74942b7498b686f7f8a0233f478f9465952863354792f00c6dc463d8bc1399dfd4014477392ae921187f83df727f27ce091eca5bc0fd1faeaf510afe488eb0fea9b1e183dfcf163703ceabc7631167a01c7cbcfa9f1cfb5430fa94a4ee36e5e61d86513a1242bddb845a86a472e7b585bb86fde22635722cbb7e8125296ec322563a0ac9f51ac9c34b027d8f2016b7d6c2644d72b4068195cc9f281d85d90f2ae1d417967e11837abf7e829c40f93f8751487b3077404d167be541abe1bed3b5ab6b0d01d091925aa81f0224722e8a1be6a071c286c1a5834a1ebd66f8bfe9dc3a58395e45daf677fcde809f72d69dc1d37241c46be65a4b169d38ac6e2cb852c34e8b56d40eddff2390c5884ecf023a8f4cc77ba85641243f7728c400306dc61e9a1683213a01b425a4160672fe05a41576e6b6e871b8775f77d8421eae742e5a96103d3d8443166d0181fb3a395b48f0cab0d21ea4a78c0705e5c60cecaca37162abadbc7a1dc1f44090486b99b1dd79fed3d8d3330401d2d5d77529b09aa1f76d8c8be80537065c5a994461808e6a37c549e12bfda5b14f12cd2ad90bdc11e58ac52f99341b29d2b4bafe0e08d7c1e3d712b81434e1b8982617339a73485ccb8a96b5bf241ecf24463894584837c635caf11c9aa1271bbc3993511ab6cdc5d2e7a024557e264443558009288e97d96e2b3601ab9c64e19266d710d5984336eebecf5b96f52463b682f81f727ee08838141b27649f7943536106a7087ed688479b16b6d6b869313eb167f532ab62f712fc45ffef20a4635bc5efc9e3d852ce6d6c00194cd4b8e84e8bba6c727286fbf445da9ade8d6246cb62c76f81e51be04061c6634eb17e8769b0dcc672feb96156de6932a3f8bb32bc53a0b817783ac45000949d8cc1673d5096812f721c018fa950a15b99d356845d93ddebb3ef2780a3bfcf662bc9d63a2aafd292729ba8e40896e1ad0fe14943aa4a7a15fc2319146c0eaa3baf6d30b3bffb8667723e195d7f2607e71a29ad1fa4f006da7797986c5f3a4ba3a54641d59d002127727c3b217a3809fa70eaf3975674d3d0f3402344175d13ccd47022919665a615722375f883ecb318e3c45b0d77c2fa76d9c778ac949df1f610caa5e946b0d9e772544f57e1ec29586b1238f2381bbef3367ceb60de4404bd2de0fabc9b8699b417d1f33f3af82a9cdbc9472c944c737a63035f48c44a55baebb2898367af5d527236f1063f0e5369a5233b475c0612aef3e7a5b230d1048638be00446ff688372f7e4c8c0d8c2a6ac9b7d2c8f527af62e422b37a94c98e310c3ba30ac02f40fd72d6f1ef290ff7df4376daf2f45013a0bca2bc4c27ac3a8a87772361e49958565d321a2249d9cef06514be27565c28e452900495218f154190301a01acd1cba672322e8ed9dad580a49b28eda0e25531c7203a880a99e59efdbaf34fe9f46a2872568206c87d3ea9158dca76f79061d5cd2908a1818838a0eb918cb6e32f683b72134e376e16c2fe548e9d68126353b8972beb37050e8aeb750cc93f940ff900370a9b2f4e84a8abefa729f0edb2436bf8fb16f080850394a1bbcbc9af024770682a8b608758b528a59c31bae75360291539547dd014e6a42b2b16a4a92a527a720a2d1401f463772beaccc7db3f869991495f4147eeefc2bb1f460ca936108e029602cb651118c32bb2e34e6ed9ea429b843264a23a9614fa1ff51abf7f3ab372e266407da2343c8b1812bd724e0686eb6fc3b59a8b137a01c50eb2b8f734ba72b68f046ccf8bc5c2cb67ae7c5db399e12f591f0a6a53854d9d2d4bdce004ba72a01f17601226465db006ec18b97b738fd92b3fd79da60afab08c390150a01f0024c21d42cc91da706fa38756d286e2d6da37060108aeb2f1e6eecf33b8aff8727690efd22c62c11b84c74b7cfe3b7393d4d30f8a624e11a6729a467adba67224f1c0a3700c628199578ffbca4aff5c2760f5c1b11c5eef724f5687c61a14fd72a059a55f019965cd7f1e22fb058d008d1db6198eeeecc573e921475301765c06149ff69b87eeb0a3eb9113f3bd8e7f2491ba68dd16616fe2adf2ad19ee4a41723da83272063eb8eacd04feeeb57c19f306241d6dfae5265e820c44e87b165441687d02f686a0cfd90ed540c9b184812a69b137ed8aef5df79a31da43b19e28140cfd2997c62b4f63affcce747d9bfeeb83941a91ab0c279b4a5d79307cf40603d60e03af016c3e00791fc8f69482cc1197601d8a172860f9d32b58eee1dae172309ca881bf3f121466a31b64dcaaa2e131565bef52ea1e3ccfb5afc0fd1748726f57cc2d6c7d1adec4a842cc0113e62cc4d5f664e09e5ac4ed9d782926524572214beec95e044cb1897dd7ec0f35db586af54f7f019553debfd5ffd0ac3571575a3e4920b1e95495e3b4bed3b62fa1282c35b763c31ec580106480eb0c408472517292b4db1b9533f17fc7c45d2710560d79d1b96984aba84b07d3d237e70c6ef22fde5028590b9cfe727d9667211db119d80b3de3028a16c6920a5bccac9c2c87c3629be2690bf9cebdf2dceda23ed49b55ac2bfff56abb74c0f6baa76e5a723c50b37e73aafc3f26308ef58cbc9ddadbd240ad4954dbdebafd4063157b692eb2b06e0fd6b75aa1ba6bb62f74cd0ccaf74bbcd0eb7cd21948057c5978a0f472ffbe67abbd749694fc54fe18dd28312261653f50fb586b2ce185c3369e6a2e7209cf3c1c1adc3adb4052f7dea6db0e5b92650bb38f477c72acb0d08daecca60842aba3f98909c4adfe59f75cb6ed02ffb739207ba09e6c64e53ef1c5205e89035337db8f07c4b8ac6f1dc66d99f99f29cdc331df72c7f61a70682d160b0435224c957592fcb41377422773c2fc712620dab8478849e6c00e433732a8ef7e38726506132e7f426e0b229832122c877d85515b40a3e724cf9ff8de2476ca74a44038e3df7b217dccb7becabbfc908bf26d57f8dda820ae0155c4cd616d02fbb272615b194295ddaf10ca8576c97d0fbeda19e7bd519480fde9e5374db14ba77f3fb9792deadeaab1caf2624be5db4239b644aa166efe49fd929dbe5b1463b8db72b51d59cc67bdc00115fea751ea269bb3fb159982e430db22f6a7e495bcfb1815bf53915b1ec26a056290dd170d954b234d0a1943014e7b8b923be0ea5d71f62c59cf23ccaedda9c907f594fff986b346c36e540a0e07d824db9458464f41110370ec1d92f1c08aa77bf51f82b68b4968f8c755ca2535ac4882cf8ed28a8959568ebf4746039c1034cd484a15a92a0df9a182e4893a39f4ae7e80346ca8bdb44140208ed49c979ebcca4484ea0aa6396105bab2b1c38452ca0d2c5525989ceb3fc0512ba12b685f1123d174126e6b93ea0f054758352f1d940b862105c67659258bb60ddd1c8d5edfb9800e8ca68f317193a9443e4b4618c60d44c8719d003472f218d31f2686ff6493a3b08658befed34be83746946fe3d9dfab1277f9583d6698cb51acf4358257665d685e465b599dafcd66139f31efd7b4f868b6970ce17265c86ea0261006d8dec568d892bfdfe78a78cd0bfafe83709d39e21415ae8d720f7389e4a075c24de59cce8db8ef575118621a7cc0d22db059eac4956baead61d166c588489542fc7a9897cdfb92e3614f326b719a93aede76c73343fbd1a5720986ac0130c08a95f1d58c4a326a11b21ea155acce2e6eb879e337c6bfcda8288248f2dc17552ecd02f7b47669606ee734666fffc87e1a810f934c611dfa92728b1bc99eea75b65a089491dfb88ffa5b77b0283c236980705138a266d3bf017257fdcf7e84efa17fa328deaae04a2b99e4432c17bb116baf6250ed3dc28fca101f687e7341e1803b968f388e155b98840fabbb7c240d11efa5660a5af14b91729f7147c022157a4266b896da6beeaea44a43acfdc9b1bb9448a5b2e3cf31d509fc80ac79fe6c1cfc1bf78d461eabe396304d64dd6d1d57390bc71444efd66f26e86fa28fd6441732bad53ff35d1c97d28b061cf95c0cf96f58b25a5eac5bbe7242a101f16fda95829f8f6fd89fcf106148d5c218c69d3a56413f83ee63c1c838cb9c51f20c224050a7df58272df94958358c1ebed22f613fbdf6c9a5154041728222d71887a6ed3886aa2c7d1165af36e63f21617a201e2d0a6b9f9a4e92ad72ff299b28f5adbe282071bd184ef765b4cc3494a12fe5a562eaf13d45b1ae1d62fd6999cda17d493becf037df64a44f4282c14b6579363fb7a5ee5e63f65c1f720b6de78d4a0b921caa8692548cd52a64d5dde463776be2d5a9ed96f03f81ab06a19dbfabc2938f31197670165233081a5495c74262b536efcba9526263527c594e0906e9dc49cdfe2abdef1765f19b19c50079ae36d292537bd501dbe115f25fc1f88dc0ec98706e05d6c0de18603ddd02ad39a79966368b796e27fb487093727afd1edbcd179faa8bf9992296782e23936d86d00a828716ee29d21952265f0a3fbcf0ed35d67b3da06ee5b492d8c0583010b72ad6aa7884c087399918f41672515bb981e0e0ce4f918fe63ce9e4ae34b94c652b9100f65a7b1bc523c6c2c06aa223f5a2e5b176a4e6ecb8e549ac4ad9edfae84c7f9d4ef1f3833d7de838e94e51044620de32dfc528ca386f1274c02f25efb408d7d8a6cafb9b39446d9f73648e8fbe14c592d5ea3ded6cfd8fc45f5ac5f8245ed7db5eaa335f464be14e2b724ff20c432c7d768918fe6dc1b67c9da125272c3bd866cadaa75f805b0cd6517247ae647eb1596b88067ab4dd3c04f9f1da3160025654cfabb646fee4a11ffc72f29357944faa33e9959d8be3e517d584500b431cf51a7a0c174fe4c289a365727ea5f350e993e9aded2033605e0fcb3632aea9ea9d47e4c057e2ff3af74f7c548c9f5e932f9ea73734db915d69045db4e2d7ca57d8e92f033229e6cdafda8e469edc4fbeb4c2915d74098c684c080ef678fdeb279473340c377b8385838c2672bc252e7d3c39bf3140cef40d347cfa2b901f9c6332832233158b2461c26f9572aa155e0415274f2d513e57d6f976f129235c010af8587c3b957513eedb5d4c7243291cd9f7588e369c7654e025dfcc0a9416b4936eb82add0c9f9e3f74304f00f7efc9dd94f6b6703b11e0e3a28bca24e648888013f9f17de21db72e5adc1d01718a426df57554c02ed97bde1a90787f0dd6e81219b7be212b4d4d92a5baa15b8f48e7a932c3bff37bfc6370ced909fc23b7c8b0750c0c4692404244bd69c525d9915bda5aa1070a93695dafe6670c7d9cc2c34447d673d8b0d761d5d65d9072101735fdbe5898f879089b2fa066053542c8617a59c2b61592bf1619cd3aba4a3bd8bf274dc0be119a363d5bc7e525c8b2aaa0b9536149a3e8dc9e73b813eb72fa0228075e03c99b38946bf82f9bb35bcace4bc7c88aff305aa69d943b0a10728f575401e27bc9b319bf447a7c9dcbd126cebd1a50a28c2a9ecbc055291ec572bcf855b246f88617c0db05b49b33c8b0ae3c77f2f9e4864f3d01c2ca608cda7200e0f9e8a2ceaffeeb5b28d61045bba4aada00767f4fc352fa1c142d526cbd721e3125812a0b78bd1a7ffa444fc9714c58a9190ad0c9a6e0601b6cd952c6285936c837a3346f06bc6b1e617ad3d7a091e424ef27aeff4c6931975658f4f5230192ddb9807c62c35a294a4a1d6118b8b19a4b549a6de0ad50c5290e8c61a4c072125b575d782353d0013bcb06f83c9260685901be216174e3c818af9164ba6172a346efd3522b13ccc23797d8d1d2160d358b4e399e35f3842ad2dc1c4f92033c9cf5db10e31d01fdcce2578c06b07f530376c26900f3572c0c9326bad4fc92727fbc60e84a05846bfd46e768fcc229c31bed451fa62177cbc6c493e6cc142d54fe04535f88097c38fcb8dd458f038a5efdfe42b352e2ba9b7354c33eee7d4e25ef20b5c00b21e55baed2d03f403f2403dc52cd1123c2711d161d1e8495c2947279a099cbab0058298a66378e45c85b0147688e7761f603b9e1ecfe95ea0acb7295471874299743d12d34ce8594d3c590b25dfdbcd152228001fcb3b80a8cad6d1ed7b0ec930b7a0ca071fb5e845e6efbc9a2fb56af3aa5674eb912501547cf0e9fa5d0e65136d79367dc0422fcc6864f91c3c5d81e3df88fdda68e195eaa8f72c86de4691cd7f825d21fbe621196a9e44c1332c6be14bf5f401a69b0051c9a7260212d82a72b843f9fbff68e9235e85e03680861e62dc6871fc9503200df694e249eedcf9522f6d8f804e8d29be29b358f89b80e002433c71bf670be2c1ef772264fe5c3045171035fdc9530667a0aba23ce2c9fa76fb27c2809d2ad4fe8e67293b642ebb4a5ebcce1c6b35569b23cce2dbd1aabc7fc4d207d6653794713d7116f313f8531b1ba9bcd816a74f795b171367f9e1df6bbba5f8e160bc67c763f02d8238686d9c42b2cfb042d6c7b54e8f34ec99b1bafb87d06d286bef8aa1373450051e73967b14478741de08e07b3eaed931c8ce42d2f42efc7e98e87da52b472826d8e83f1a0b67cf1a3a53c2cd1934b244e9bfd3debb1c45271f3dd742128727afb38b220c64227a024be95663f9d52ec583d4601fee0195bb85fc63cf50a5ed2e9993d44f5db70548c41a9b83898407262876f45b05ec259565d3750bf3801d12f9e5cf576cdb4a3f3f98f6a171141920aa3504ba6a4d3e337266c348c49246cd3b590e6afe44b130d9faa2066175a692531f9ed1eb608b5dbc4b6a32f830568805e9dc6f321dc9f5737f24036087464dd7a77b395fa14801d3825e2e5d672a7fefa2ebe4da9d6e02a780ef772e9137f56c32d10701cc8bb4720b16bca4272734f273f0b88eb85db14fc1ed04c8c9f9a26e38b6f2a677a73892e8197151c7216bd38da3be550c6c74fddb1a462d0fbf9635c9ea0c908847144797836e54072bfab5144f7a770baa3fbee7455f1723e79340cc2be633a1711e14c33d692dc586f92f14ddc7793da5435375f94d19b2141438f2d215730a0fabdb90f147b8a50b702d1887b514e92c159fcd8b95488496dec2c4e690e8354cacb483a7c77a57229ed00133b718876c552f233d92b3ec932dc6583d647dbaf787a016c14394d729eca0e1c643289a3a04b025ffae7f4e3fbfe9e82d68656c675807d7f1d30a718570204f169994d0bc1c46a394ede332ffdab59fcfd5891ee28266d95579e026e0db737e1fd985d32ec7f560617dda83b4a1f7e1b600ff130eb9114eab5342b5dd0ccc2b087a3d08e971269e7e91a9a7075dad1f9ab4f9eb0e53ac64869213b30e9818947e3ff70851cfa47830e12e074ba719ff5037f0c58f1f45fce8c99cb31e5b4fabfa013f22b025ac6b78cdac4be880cd87491f4824fe83570f4390a2571668cd79597fb65a70f30b2dd5eb31f0dedf23ca12f77ba044e2639218ed23b72011e53e10143bae0ff9c2d47b8dbbe77efffee6e63eb4d2e27f3fad8bc070b723d006ec8e2a6ad75bbbd10ae0ef45a62d9d9eb0c9f0585cb1961a93d59430572a9b2659ec7c651de774ed0f7956ed1c79549747bd4814901578c869b6623c72d8a114c87aa36b48cd8f867029eea5b0f2f90546c85ab2ff9c2e8e6a910bd936c22d4bbc544ac4d932ca1f72f92688f766a81bbdb2749fbc8357d697455c3f00d1f3cd07bde87638a6a82136bb1643e92a15df44c3c861ebd354fde2aee793649290782095ed8f2970383aeb2ae6f685aab65a3562950b12d6b50a8edf9ed230ff255139d07f0ac50fef536f6514b824f666fa75eec86d35c910c40a6154d09729930d107a81396816daa195e48295e6017958df03f0143aba906d469ed0e274933946e1643e6bc24ba05b2639e13bebb6d064b06e9b8ce2a151c5d38bae21772559e32eeaa020bffed785a7807b4c651f10187b415b5aa27f2134593d05afa20683e1a7a91b2a2b1b8d75fe85156538c0234cbb504ccc82e6cf60f0a479f6a726f297bdb7b86d4bde0a2eb6442a8e51f3c74875713de3a6bcb82bae300ddb872c6ae698c94924e73f11dd73841840fecced925bfc26030119a206c1de0ab4772a518c8241854a308f842b366af7a1094745ff659a917174b1b3acf85d0b6f14d79a65d81b586a03702626917e97cfdc3bf4535a981ac1fdc6270806b4390ab50b70ddbc8195b308b6bfaa538a4acd36270496f156e9d8b92e21f68ed3f0f46715bc69d54db46a7a1c067870c09e192bf32822ab816969d9632c621f993cfc804a04e04406289b4934747a3700d3881312afe20957a482bb35d48c85f4d2dc41aca4a50decae20f6b17eeb943f6144a856e07cccda45f3d91f6805f11741cc62be54a8ba16afcfffc9b799b47cae6033447d42b8eb144bde47c39d4cdf154de7204c4a81816f6e7d0c99ad6aeddaf1d5a039387deabd7f49fe11415c74b746109566f4e8cc1ea0f7a87680ed3e128815e847b083df9a19beadfcd4431dcf9e672b30b808153399ea858d395c7983be3f6b5c5e7f113e701748b069bfe1d5a1e1de750066cc2d8d45fb1fb2c44740a743e60c0da0a21c7251a7c1a0f7ac870df4465ffd3108029f847fe1677f02a0571cdf447d24f9a57cf858a5385341dc2b81d1db2a1a99f2a7075355d8d2cc5bce50131af7ed45b40ec69c5735f7bd7daf672188012733cd33761a2b73bc5fcd15dfe143b1fb60558527e74a22a7d202c897252bce7775ecfb3f6468adfc4e1e22f7267566902cf7f72b8a8d77df588bba92378f13436d25f030804ce70aa6f6f04dc22ccd61189631422f8aa054bbbf5496e06bdb7a5d17689d908a46f4fb73ab1bbcac52bf01ab6c149618f470ebabcb66e20355f6b1d4e3028d3c4bfa190b78ac8a875f40e158176883f053b4c9dc9893c6b5fc626289ae9851a804272a95af9e334a29e026c3cbec964850c83bb1a8f683f53819e695d409bc2046a1114f156022762ada2d75b1cb9e2a88ef51fc45d72a6c73e6df9fd24e7284eb0057df7a585e2da41694e196a4dfe29a8d028efa472e1699747033241464ef51a5f36d71d9e9e4d3795537d4f93da395d107f18dd72d377883f6eb0e0bcbe174110c5ed3914f7844d524d5a81d2f5c9037faa8df6720f0754a2afcfa84e5f8e07ddb7d627d950b025e234ee1d5fe0141194cc5f0d35073662a883d9890d7f1b3a0a0a3982299fbbfcf4e6d7c275e222fa220c2c11725579ec4437d856e13746fb2023f3f06e0880de22fc500a3f6442058d72d50b2a4b63f571ebe94675385d115f9e98d281a9eaafc9ed16aad6387432d269f84d7282dec0ea4ab1d638b5cf23175ae7f659860815fab6b7f52da5cc956152af76721e2d95528181f0a9245d8489d6fc95181e0df73c98bab6370efe3dde4ee0dc4ca64fff670cd5da69288219b3bb6b7de0bde8ad3f9946b64daccd734d48c5357299b04b7fe163d7e2062cd5ee9cbd1a8c9ad447019204ec603f47217e5aa0d272456a46cbf82efc5fca84ffc9db8c0eb17d863384b7e097539457355544aaf772493a9820f7cd9ff6148053f2475ef7102759e6c9bdebf4efa49210ae210fa16727ad1bb3e9248bee8822577145c91298d524e4e417383d8e867f589e93c4c055076b3d5c9bcaf30ed665ea06e5e60e01b951d1c0494de8c85a29336afb3e0c725b7c23435a11eff2f1241ce77778c686205da3d2b6cc7dc0782f00a8fbd3f965bf37d85d9e9c8be059f8c89c028aabf402c9f5c26e790fa82786676afc0a713da9ba528b1ae514349aa0fecf527477e59eb9b9171bd053b62689fadfd7624b7269878f1ffb48b98224ea66fc697ba8eb6f925439e024631b91b25e1f1d35a40a2e29a9f4caae2b4cb6a14a33450ae3294a22e3b30458ffc9603edf5cfc869a72ff2950d1160c5f740bcc594d69c2447fcde4a69cdd9fd873149e77e3d1641b7297baaf3f30b7ca8852356d1302c4d41c8aa4e835022f19a4255cc6a7d2c44268dcf1e557d738fe162c5e549a9195be73b688db74794df04a5514359a51c2ba0eb58d55f0c139791417518aa72bdac89fa284bcac113a85c37f26e4a3278d8c683d8718ce5b293547d25bdf6ceda1aa1d9a7128a10a2bf8610363f893d1d5a0729f4847117b2b0e69556f1f5c4b82de3f0ec5c0e27b1715b81eebb31be92fce72eb7314457707ffc1cb4d9370ce556730a64b44adc3278cf5440c74110899d1726d2207638c44bf09b7c65cad335d8c74e0da171b1aaef747483f3799b7b00932c95692cbd4fae732b3124f53668accc44be3789b57f65073e90ba5a09906307220a2e6827437480be478a9ed56fe0020bedc482dcd99a1bc3bbd19c6a8bf5172722bcdf5e3a376ba8e73f5d38222a9caa975b0ee663935bccfd8e7c904def35b0e1e181b3f183d329ce07559bb13e6b91d51681c85dbe9a305d681f637d01d7226b3dab621b24b5fc3e628d8267d4e169f0aad320e9de4ef97a1211c90180b72c7a1eaedecd3018a370ab3bf07bf89d3344087a1b76edfa82b291678112bc87238ed8ddbad9441c3fd5aef163058751c50ea9c7b27a831f42936507d8411380ffe3b28974e12d70ec96bc1880c9a4116600fbd14dd97a392370adae93adec8724d578677f2591a24649e2ff6a504e3c996fd3e804b2959f0015b6b9f2f6936726d8d99d8b0e6822c89011e6ae0c9b747699f8a1b1e23d9d930c459fc4f8eaf72ac55300fb55abf9d6da43817b73b5df4fd2cef9c2d1c386c262400b47697e072f4b6e8d353c3ca5231c51f007aab49002ddf90f9d00ef2dd8b018d4ec1c9d37277c5d49fffd30798c387b92c75b7ed2fcab2c340abbaf686159b67842e763771ecabeb0df42e714f1fe70f90bb3a9aac75bfbb5e119279be29aca61c8c431272f40ab7deb3a3d740d71b8422cf0d90c5fedd8c3b000fb180c0756c8f3dd6d3720d8f02198408069744b09d4ae1f8f69f1803f59834b4e3d7bcb55c1ee5aeaa7298bfac385b1e533e34b03cfe059d7b60c94e3f546bb67aa07c7065dc51aff4723402f2b0991434aaf04118b37b6f63fb0aa0b0a7618ccd344ff0eb83e666b62dc3dc2a76efd6a5c0ffd727bc77d4855ccdf99fd718ca60d565d31efc41b0da72947db8acdbf45c83a147179288ffe738f2d153ce108e54cbd3197511746bc072461672158451d517d872cda4c6c874c230e15007295ea734dd86b9820eb1747242e9161856e3e5abc18d840967fdffb065135ac7d710b7d588c6965d9032b472953777dff547f0a795b7116e466ba66c135bdb31a6c8a2b0923ea93cdcb0d57210b73682afd40bebb4e7b299c5cad6f77d12383966a9ffb7e39a7b4c181f2f62b5538b4d6db9a477b1429564466b6d3f64f3a8f7d5f15be33c4a524d85e7be726db0f6c6466dbda85502cd1920921abc0846a34ee086b1eda39647feb232b572b269be37110f4de68d776fb224cf9cf12d41e57ec624855b38800fa0de1c8d723f3161760c7548f4d8f52325092c77298d9fc00143daa5afc24a3f869c133453762207fa3d04cd49c28cac148e3260225348b5cddf3411f82ab7b051cf4f8c4b215e7a8e699bd996ec231871fb9ee5d3532981efbed88e3bf5aa4bb9df3ba0722ac8b17969c2248514a16ad5acf3956b21ca1b8f9b9ec980339c37f8cc7fb530f44c9ab1f48fc0f56f29f61df29cd39b9c580e2f25f23684dcb788f555d8074a5c5ce8f44652d4d388d9138b370fee1008bdd34f8bbbc9240f88e3a5646115727b7ef8f67972222f1aced7ec4d6a4957c96bf278a69757c2c03740d88f8e8c6b6dee0d3952ca3bc9e5ef8c763e1a416986156e5bc4b77c5ca09873a57165187219859e9417ffe6cd0de08e388eafb43b09f772aaafd026a3fce0af1949c31a1550647c16407d39540f5cc38e60beac2c1699b444a9ae22347b8d70330b64fd7203ee1023197d6329860cd5e0cb3ae566f9e463c1d1f6dc013a89470c8aef8f72d4a2d171bbc0d3c43571f593bfc287b0fc6dcd5c59aca29f04e75df006c0b4726705b614347f128a56f2db0e7af9292d0ca7af02a39f0651f496bbded69973721530959e1817dce4a84eaaad47c0bc5fd411dbb9f8acf7528767e4d6a617a9728c0fb256f04528ab39966ecb1b762d8650c1c3f6878834b492a95cf9fb3a2572d5c51cd68dcdb6d5a2abfd724d95d80ad61a7ac7b65edb94f40c8cdf9d4e13727f3879751cfa923270f792c35d0783504508fdae7e8531306acf043c1cbe1972403d1607dd2f41c2cbd77bf5e653aee938f88d3072c6be15ecf615c84f0c1523518068d21a520f6029f2ba8d032e3130c007a6a96f1996fd0837834dc4fa0972e6736f73698b564726fb28dcbd482e3ff7b761426433cc0e8426ff88b56b26176f175160dac6a682748e51dd16b35f069b65d220cf9beb9c54e96cc3deffd328acfcad0e9415fb16d540c8aa375ed9d0494d863e27b4ee59ec56b458dacf2872d76d48ddaa9aaca718eb750693aed3334ca119e57d9c180e208789e28a985872ca95535ba99b3dd02f9a76b8a368162de8ea2514f915399316b27f9f44b1f062488d8232c0f303d46b576315125444f10e63a310290d44a1d839eaeb295ab3413c9b6348830c7e02d086ce189862e8e543664168c9124a6c75f28daad349610ec2d5d3e490a8f2bf1d0a387471334028adff54ed09ac85a784be52007707253d53f68fb9b8eaabac2e314ead6a822926dfba4367e2f49b810aa2a5900eeda40e78000207d23b2fab24194b4dcab82c8f0bdb3053f932ca7a236a6de2c1b599727dde3c021bf37ae4fdd29e49e191b49b1e679ca15ab0708b0f0264764b033172abe36b60bd2143efb24bf2e465cf1d73b6167909a4c3024c9b272c693765920a6653108da0e66937fb260a562f5b97816f9a3853701cf40cb3936fddc1b98872933bee231fd36a130ed3a3893554c4a37e817e82eae07d633ee3749240323e508c27ce6cfc722f655a0f6155f17af0125cab5790fb84df231d95f786b33b3a6e0b3ab816aec28c6e1377539d4cbd9b9fa85352eafd4f8f0f6bedb6009bf6f45bf58ce8c1c3fdb14a14f0fda59a3e021f7ebe17aed3574800441cd13e1b53bb20b83a0dafd27addc4774137fde01356175bba938338e016f9ccef13ff8a617872e1e4560396b65afe703109ef34ae1d48eb24881d22c6667673d4109bb003cb7222617f9cfd67ff49720d8ed70cd83626f10ca02cd57601f1799ab8e5aca0f333def63f345ecdef1ca668db14cb9c6bf8b76c350e724d88f4f0f17d7d61ab7f4fa20aac66c38b67f1669d62d9f7d06bdc1da5b06ce0e6a5d592d9a23a39a7df7229f3abfdfa5e1277ebf495d85bb9bd04e3b32117c51f14e179b5bdfd4046c230c9007c42f9e8a0d6c6d0b1d34ee8a146aebcf994e9be4c7f7e03e5cd618b6a72c759c8f5b47f5250e24a19e235ee1e7a542ca330626f98bf23ff5b7789e39a72626f3243f4f72601d795c4f93523c8b088b098faf215496405878e1397cc5772892c69ef270b3e6e30566584458c992f45d486a42ec7884e7be1f78f0e8702724997867b72cf51ab4aea0ec38dea44cc5ffb342fb27d390fd55b6e99aa7a137261cc8886f12e129bca17b682c60572a1d370a9dc6c349e340bbe47887dc7aa2323d4cc7bc513b4aef6a0da7f2cf7090d9a1c014e59582110a127760877f8437276f743b8485efef0a0dd1f17a4da6bc33b4dcb93e54c10c7ed8c53deaf8aa772848db47f82f73bbaeeca74b592f49afd44b1d36c5984eca07a8c5bd821736c393a4a24ae18060da3828163858aa34845c92d2143f5fb1e54690343e1fee5d73c1906f9dda4bbf97fecec16c3d08b99cde702a5cc5afa26f1a96afd6de33557728042fc7a2927409ba850f4cb675a5b59ca0f286ecd6ff474e1798d7c2f45b272e0defa1d3139cace3b779da1e8f84b3564f39e00e98f32afc0fe1c516d03db7252f99097561a6029567cd4049dbcd31d6610480482412e1c33692fde1b80f272d409f74551cc984375d5534c2514e32c971e8c39a0bead6a0b959a3440f1e87270bd8b163fb0b368e008b176a4583b44fa6503838957a7c961a6226039ac8e305a973aa975422769f8435fce1f8bc58bae252779da0a04dc324697cb4f0aab721efb26c51329134c379fbb46bb3a35444dcaf1381cc0c8cae10afe3c7c3e957222b766644e365d0821e412eacea4be85fc6a17d983b565f60963d66307a77900681826ffc59ce9f768c04a930f6c6549ad0f7589bde70f856e04fcdb0262104b23a9a40a726f596ff97a65d486f599929b5a1eea46208e9aa7c510bf992c3672d54cf491e367a004c7dfb0e02f4ff852a2b00012a7371f743c210b083144aa2949d6bca518bbeae059a59218dba69400c04e7464bac0d8f5d75dd27ff8e01a72255693704a6a346919d6858fb4d40957547eb874510feaf77a8c44711c30a2519066322a9d1ef735dbfeae68d346ac53f5d5afc478e79d85ac2cefa5804d55726368ebb77835f17be4e5f14636cbd6af4caba70ae4da8bd1f1171de027980750d29610e8f41c2ff1c1196c5c46d0ad9ecd8ca18ff80f71397269102b1f2c5f64bcea4ac6a9945733b5b1cc2de73de902e8b9ebadca2f9bc0f6ff28f04529c05056b47416bb7238aca5632d70bcd905eef1219b338ebaace4feecf7627563723983d1e39a36564e7a1ac0a1628729b6a74e6bb1be9828a4a0572e3d8f22a505729c43532c29fed268a136a89c0af7a153acb0bb70af4be764390660089695ba625848ef5617a7fe2a0008ef81a329e5c1b95c4751766df2a07b76a7cb631d6e72a42f29ecae47ff0b5a3f0085bf23b226b869088d4cc4d6bb1bfd3f44b83b6d72505fb0220aa2f942ffbc4c42f4a9b4c1ea276837d031b97fb7e9c40c5043de723bdd9dd3dc933f5c17c728f2e691b9dc4bb11920f0b687659bd001b62f7c9b084d596d5c5c57902b66c9e66877aeb65bd5036456f388e58a0795219571cb6e72079d4afb7f974bf1ab53744faaa449c87d0d7ea4281aba394d74178d683ef472afc8103a182d3cf0ad18b4d210c9692f264c2eedf518f8b014a49748c4973572ae7d7514e498efee660a2e7561610bee03a2169484e9621c244c0073a9da18723b72e65144b63c1600b748b3771715e412d8d7e28b0f85d0e09aa79166d6dd72f0265e95c153fc6c08019dd242f3bc77fe0d72ab225e32539dd0384d9f7a1a72e78c9a6ecdb4c324a3f3f10dda6dc457cd5b9c1757266d99f669482602a24f72fccff021b117cde9a67b8eadc83cf86bab66ec8416a644bd7fc37c148322aa33f13efee9466480cc03147b4eb7c7588835788839e10c09e1054ef5aa6b5f0872c38ee51e330e13c0ad90228536b0751811a6062468cd31d2ec730c8595bcad72735e46d464c1d11c9873a59ac88eae0f44c84c079fdb09241a60922d3f431072ee872c2d8c149abb2893527e233538ab3f002c98dc1bde4ceb2680a6b7bc543635c6890263cbecf471aa77b6d351403d77f8c79fc1e0f49a0b38f339332ed1575e35b22702061639abdaa75dcb9473937ad2c0916d7ba276bc92636a8c71e7727b0b23a1c5c77cad17eb7ffcfb047b58ed591bcc39f7a3c8ce06ebbb3149a90af1d4b8ea272841e84cb4e253f5599a99616fdfa49b94f62c64c7a1217e08ae6e556a12100b3c02043828f55669f5bdabfb3e2ec31367961634b21a1e9f46df7275f4417c98ca22add29aa30b07de624661efd2ce02f54823e9ad0d5f79c7b04d15250d25ffe65069e0e55a1c5aca128ff6297bb505bb4b75b42a07d374f738726e1a5ce81f71650209a9e6fc42181670ce0d43c9d0dbdf20912d9d9d74a6c751405ca2ff7a74fa7995c1f03d97e130fab00cc5ca77919f066d21b74355251972667efd17e6481677a57ac243157ad7d3eb7c2b18570dd422ce4dc38e12ac967215f7bdc41e0d7ed4352510d31349e4bf0ea2cdbeccbc1da27d2691b6b317b572f800035c451837353b67a6b2eec06e69aa07049fa3dfd5c262d7affdde5d892ae748a88ca269db736ab1e9820436a440d2e2719c642a76e37ce4674c0fbd2a67eee0767d9d43bc314665d57ef68014fbb8837ae0369264f4a411b40424a29f725f9d2c51c8b027f4dd5f8e902353a2a74860935757fcb8143fa6007adb045f1a7d6e0d502dd5887ec1f20bb9a1494052147ddb9160401fb5667a44ee48a79a72b38458c98444b94deb665922dc5b5a3117bc2f9baa0b1d7518390d88991a8c72f362e8f92b43f560562f7cddea11150cedb66f0aed39dcd872426464fa2a4046357205e532b8002ece5514d78330c0056d23a28d6c11f0d2016c95ff3c8e7d72e1b496840a222bcda2b9adb15210e56814f00f260339018b797a98852e284c7277e099b32256d038d95f8ec2ca3733414ec94403754e1d566083416e37f6593dfcfde9f34b75c207ea2919bd662af47c9a7192258dcd6766623a371bb42a71728c2712b48fdd25d0fd142aff0907007c97792d784593d9edd1c73c747bb29b72b569c8dd7901112b518322d64e2525b8a0ea4d942013c606739588b17e77bc7297db9d3458d78417cfa60b6aa45ed0f217b00989d7ceb2bd18424e69fb295d724cfdac5c60fe62faf910ecd31ab123c62136db5ed7660cdfd42c37ad7ef9df67ccf08181963352189247a95235de54ad9d9a5cec19400384d0dbedc62ada192606c370294daa03f5779a117e1dd47cb31d0c356f33b784e3e72a051ca7659938b22281bb556653a84237ce408a76e5a5a447768a643da18175b040452fcaa91c83d17ca224af8e85c0a9f09f0b57f6810f0d2a2227e1a41701a6ce7dd4568f726a54eea19efcf9c2a22a8d6e027a8d732a79e7b3c8928267869e87103a6871722386c45b97b8de9b0683dc9c442aaf993506ab4fb982fe29ed383d5de0887f72d7ae5d2803fa11f25172f0401e4866683d5b31a8c38e61bef00191a3fe19fb7258918f9c9828dcfb797179b6a094a86b2c680264951bfb907faca8e07809cb3de057baeccff5a4251af658933fd61712d51f00ff7126ee1f77e9e6256596e872957cb729ac59d75fda40cd6cc151b77e5b6f99af5910ab459358a9d216f742723c776da596c9d6adf027495d938d970b0fd3479f451e4a7f697b1b4a1f6c2b0f7b3ee74bda5578d2ee39b82e3a800ac25ea0183754f1657e3289da5265f4c272274b6fd4d09aa3c240c1c61d85fa6e16b537d7a8ba2ceb335c345305fc0dd672789091da725faefb45168b6e75a8e7ba99c89d89aca8de4442b3ab773e19b372551fc0ba9afc97bc69a64c73dc144c882798bc2bb334b23f6999a5ec5dfb3e72083b9fdee52d3afbf65ed05ddc6bbe06b841c5f069eb37aed58f3fd93f323748c8d4fbced7402c8a7057d91b380a248ea2b376f8c62cba5a28401f5957ed980affe211671f1ebed9d374cbd0b19425222028b4237e8d8e7c9b607f9ae47c0c4c6194e0e2fba79196e338058951da652d9a338025c32e38ac98e0ed5cd895ed691bb3ab278f1939e9eb823ca10fe8c4cd155675facdf39a15e95e0078999ae1722b69e9910fc3f7498fe04ec294fa508eec449259b379fe402ef4fec7c52d827273eb93df84a3ca3e57184aed90f3cc430b834249a9255b5052daeb515fddf3727ca2f49eb455807a4077b39749922097260926bc11b3f26182e35d46c63034727ab3d23cc6ceccf9fa1ee2124b1094fa5d551a290d919f9f203197c3b9a6d9566303ee03084340221e3254524724d77adfd262e7e4d2465287c7341eacb570251a53344dd9980a4c5b5c9bd4e228d6847f9a9f1640dd07fd780fda8917d29672b7e22dcb9764a45893bf683dc00770959b25e236542af65fe61184f358396c72e5f1e5e1533901a9d6745f34f668ce1e1c742bf678bd31beeae1a9258c56d0622ce81c05954c2d0be494cfd4ba3655a84467f555f9d42751f3b51848407d4572c92c2aea759e19c3ec3d4d4cb4bf7a4308be749dc92bdf5a8684b336842357721557d9f7084b59218cb3863b00faa9d3ae3b5fd5127647c80e716a5ddbdff4275322b67960738d7fd5f543572aaed1c67c8538463e147c8b76c64bf32f2b2b515d47eb8592f06c06eabfcf76a6e874180d77094ca4eed77aeb1a5b51afde9a7230c6df9840a91f4309f33bb55a2da8ba24f0cafa691c7ab7dd4ab2a0e9f8721f0f508646ec54842fb76b4b69f9d30c1bda45d26e1ed8a35732fcd7aab19dac728bdaee2d4e1818b618f87ac28756f9bd67131ad988855034b4b1c8001ca68f7296cc011d3890d402ded641061daac4c32b7331a483e74a9d7c3bbd6e358e0772e383bffc1106f45249a9e3eb22f8419168ed537bdab5c6809b4484161e578a727d78a67c25ebb9ad57ca984b96b451e97555cadba15ccc9249b3009ff2d2227214b8462c34f860b2d7e43b846d71ae27929e724705dae7c19354d246b42c7472b489f7c4cc269f4415f5ea0ab062adde94b98eeb87c7392325eec2db737e664e315d9f8d2fe6e9012222b6ab1d1c4907930d605a425c1927f47a496cfe42917209dfff3b133ca82bf8d582512355341361b19bda3368df72f6cc82723741c957a2e3d4ce0edf93da44fdc743e3077c1522cece17d25a09cf20ed63f36aff7f7271d6366a07c5d9774f2ea62c777bccdf47c2904bb6a7cce0b55dfbbabe965e728113f92ddc4cf796151c85714073a4df733669df8b853dff9ea330219134be6eef87d5d07b2434c7550a82beb6e04332042bb1e7297b0bc0c8a3afb2452181320f50811e7058ab6123e2570354d4a874afab141d3b6e1b46d5d2c7c021526172719a73fa6048fd5bcc9905110f50c31e335e501b0f0c308ddac6a994b28b537210666be220743c1a9d04d9052eef7db04b65cbcc6fb73a25b86e0261fa555f725924308b346e2f7ef5df6bdd51fb021c2647f317cce109b6562854d221bdd272c2ae280acd2a4a7326ee67786bef695f049290929ddf05fb9f12123bc8d648729177c68450a7d57b0f4bf891f3c36a4ea24bd4ccd882c03e33586179c45c7672259568c7d4ed8e87d4a9ab2c1f33c60adebedd7f0f95af865d9d60cb0b53bc55ff964598afab0da228ce1ea427148490fd67e30bb19be087d781e51ed68c7b72957914cf1fed5c94fea8f6ced63c33b8072d06e4dfc5f467eac6d81964abdc37a7a91099aa0568bc41bf363aaa244094a7e83fdcd61f375bbb937435cd26a32fb342bcc8a5298be7ae5f2e4df27904a075d6eb1abcd29c9692461dacf1438a72c540d303c68de82f89fdfbb24c2dcbd938008f13fe355c5d10fcfb55ac11a272c3d6c69047975edd624a545006cf3274bc01aadd03e0bf3385c03248b87cfa7245feb782ca40c850b8c4fb5e6324d621bda83af32e271058a048122d8a0d7d04acbee27df35095eb112f8c3d2e110fe126fb52ca701c3d99282ea399203eff1d2971e7255d005525d1a7c2b88e36ed2fa19b95de605e00df1fc2f372b99c376340e379c0df15e7ec5bd4799033fd4dc31e15658b72515e4c99d264d97c09fe3bc2b0b871aa3e6b97d0a12c1393eecacd0d810d15f55573af9e88762b9ab2651fc1306f1242dc19a7ca3fa75683c781ea703ac81f098518a8ae115e4401230d364ef6d1a644a0c859d98e3334871d99991d21e7c5f1be9c7a52d90f796f75e502417f156bd62fc1015b63d88cbeab701f8fdb0a98e2b15d422fccbaeb2637677213ff68b5eb49aad6f514048f009a55773e5ac269cb0a5253a23aa7a1dde8045dcdc3f2b7b09737fee866037cbac48e7302f1ee09b231371177c1b72b36047972d42437f10714a2f3818a60121c01795a6bf9eddc0e0a91a075e6f35b9daf1372e1e1a1d7bec5c711b4cf4f73a489767f8d677485904b064c15cf0cb9f5b0ed72b25385d320fe9d144998a043c998ba30aca5d4739a434a270d5f32bbbeb80940d02deea802a80093969855c949d03185997dbb984a4b67f9e0c396d80512e5722818bccff8a89907d4d8fd7cbea24a173152606791c8b51f09cad86768b9de7270443d2fd41ae215efd7b1e2cfc0e9f40845ea000dca9b3071e1fb5671c9c372da8dcaeacca7a1a341c45ad2863294e6de5d60b36fe62c1c5465700bf809c572af706f460e32abbddbfad820287b85b15207ecf785d93c9b728e56d4f5f2081df7b38b880556a99d01cc557d0ca7737baa032f6ecf7883614792226d4e1bfe72e495236fd596f69998dbfc0edaa05f65d0c4680fb9f74b2679609ca50d19f82e232ba65bdd6550e95640b437c6a848ed3e19f50e3af9752899055959c76d50720030c539ada989c3966199c6980bd45f5526af2e4b17bfe316aa5c5dc96e8f56a0f24cf5fd539f8a97c17f61997cf2a7f4d8297f5c5ad965bc95451525a9441546853b80f7698dd2c5d8cd6f64e741e6f7ad5ba4b26e759ef66f469dd95a1072bbd972bd559b34805e9c7271abebfafdfcceb1c883a284ec8008a95020965a7285885d58f86fc7b3cff890dd762fbd9bfb6d332a720e0140f7faf22c2bca98177d87f2ce62e874b36c4b917e492b473a9355c614aa6f2a8f7f386c9901718142774617dda16aff3874caa1469a47d6a928af62421b491bc8520afa77b0566503c0261f40017f0295913ab122e6677714e81edfd4ebfe20db0def192d43bb08727ac07efed5d7b637931f4a4d2bf33402b5e0e8d8e61779424cbe4d4a9b30da7202f0cfa78ab23c723a22fd9f889a3d0c689128a69608274c8229793e9d0b9672f6203bec3dd1e66ddd0b971fce4a2f46238528fc67c1caa7cd7e5dcfed0492722549c5912444e83162dfd178d31372671d9cf900e6ee6517674fa3c833bcaa726aa28b1e56e331805fee957856b9437d5a8a435245c28aae11f372a87ffde76a3c166da1a0fc37db9f2bdf263eaeffe6c612a754c4adb81a432f123474f156720f34e2f494ff86859442cdbf7b0e700a7c6f5da16859f74b0231f7f839519272b03ef630c7ad274baf735390d659d14af810a0031fbeb85f040749243e606772c57d1e7936ae05ee80d75b259a20a87b2b9b200639ba3ea34ea74a5eef0426721ed03f21866d81a8dd7ccf69218552bf6c66f7ecaa43d2cf8ca6df141974b0295bacda12e7901a62159de700c4d0ae2792f5c9b5e348041bf0cbfea5abf99972edd688b2c732e1ca6b0ddb2bfb152c2826671bcd37ca5b3ba72204860dc0326df35906cba70969ecbf7dd437cbdf9148e2f814cb48880e77f8eee963e4a348724c6d7cf21a9fd1a7b509f546a68dbd899cce58d80dd1ca91207c73f22158023af608e2747d1bb0f707084ad9f428bc593ecfa7a07f3b59d7c2a4957704ef317245fb2f7af491565ec177af59b1a03348a0f920ddac2cb88b6e4226a27d12c872dfcbef568f664e246cf7f4e1a05e8350ce82ce7ff6c5b8ef64848d6969cc2a729fe1fc6a80c0816b02d8a7232b3ff1399073fd37c67f805a4b83c1208695c17271f0f83d84bde20a73e69a03e3b568ad557bbfccc6ea0d2e64827380557efb72d3c7079e250ec69573d2c6b97a85a4eb89c81d1f0b6e8ff94ca29e4e782df772ed430508e478c2a0cefc2188fd29b291888549cfc0b6abe266d64692cf9ed75ed1754f1d9be7a2626315198b011b5042692678f8cf2261093935efa941c88772d1a63e26a701259fb288be14541d5b00cd963bebe47ffa57b5f361bca9e9935ea75d82f41a2376166193ff550adecf5d37fb1ed89493ce49b942e5c5e5bfff21600eb85f25face82bd554e9f0d9dbab91be62bbc979c0e59c3a89ad3ab2a335f622e134b201ec6dd967e97c8c4a7a9c46dd05df48caf2833c9af0a46f483537254696df03b39bb0a1d4d479423fd27db489c864905b4d78ad5b6a366c0d3a4727c98f07d5be7c0e228eee9cb4245d0941e050fb91d3f881c7b6fd1152776f56e74649a3179a3d97d65b92aa63f6718117415e1fa68e5c3e4e9629d9ade93cf727bad6018d57ff5234fb76a7e9b47fd5ebbd88c002573fadbf7648bd2b0f6f3649d3213826fd40be1a54e5e9db1017a61f25542c90e9cfa4142f43909c1398372ef6a9cf9620104be97c4cc7a9bdd11170c9fad5910d291e0837765b32785b172ee615e9ee72f819b6f2dad041c8c4205fea64d9868ef7acc2261b1dc8fdbf872d712a4293ceceb68e04b22491c8f00b82d3ec8443ca5f2c55ce07cbf702d8972bfb2ab6b77408383948d8a1351d29fd8affa5598b048f873c898861ee435b40c49405356fc7d0226da193ba8586b87d00c94193654519cbe420e896acbd80f6383bd24ac1e5e3ed982ee9cb44ad2ad3c7d1103de40c4cf074c6217a735efd97239c86c1131ccc46d635b5766a4a4fd349f586a36464936abae5233d65ca8597244ba48eb789ddcba973caa15fc1a406b51886ac02e5bd61176a8b67f4a99fd72f11d1ad7e81da754d56f9138c19886d84a7843b3312ea8f37a92704d9dfd261d56cbb56d01b3afee6b1cee6bc4466280c5b43039ddc4519e497862841d5a4d6523e5252cc10ececc28ca0c7c36df15db9c23f3737907c84024a79a0f41e4bf723321818956a22ac5ac22ba5b265d5e80ce243b8505d50d9d3c38b782c25f347217223fd72c8c2e87b99386da6895a0a3cbfd320ef21698f4d3c169936529aa72ebf251bf63de9c6e3c419c700d381afea75c21a886b63d124f0565e71939c14523929dd03ca93033be65a1317813040338365566820f83d216a30232da1c8b30bf08782ebebec15a38f6f26f2cfcfda3445a35585263c778ffe0b79af7f5ca1bfef62c8a5a9d909518566bfa1a63a7d5fa7436f336c35c4f258080fe831a3d72a37fba284c523e4ffeae352d8731295c81dfbdcf1d2b4cd17c6545f7679ae12e8876c1088231ecbc4cd533143e60039a424769c7aa9f1bd9b9e787f0b8ed237287b5b8ea844a077ad80332511a6fd6d7ffd42d067614d4e219ff1517f387fa0e03c9fa4c8fb3c1b80eaa3e3022676364f84dcce7d7ad807485250ecb7287f24a39c0ecf712f9b8cf9cb3e2e6e515c0a13eade7074fabbddf81f9607cd779c4728bfee587ce628568aab68e0289cf691e68a8aaa85f899f2be7f420336469d472e2a213af231d14a7c35e6a9c8a338350bd235b9042dc9010601c07e87086d272a322d959babf0300b73a9c915e2c7ce108363d0714d4fe0f7cd4265db560d572fa49523b586836306ae8b03a85110ba5393b9bd338179cdcb069dfd72ff65c72afb929c47d3cf089611cfb81a477d0b1bf510fd346a918dbc560444f986c67724b17f0ef5c132f50fadf7c144c5301a4459ef51cb535db7814e2b3a862d9d272e67de3d6b9463a80f4a0576f887f5c6abe14416e758893e070941ec2278f8b052a5acc13ca8fae556a7988c540762ce0ed255776ca852f2a65d6c8e247d39a72b816252ddba7dbd177b0dba8e282d15c44faec7dfa0a8066d03c10b89c49205c274dd631b534d9456f1bab17dee4c0d09dc422415512988ff24b5a9092e68b725120bba092537e63b7f43947ad46945938ae892127e075d0357f8c790908cd58ec4240515649cd5e3c3bad5db1183330f0b6b15d03d91bb0ecbab3c9db9b4e72f3b5bc96e0faa7192467dae747b9de0d4fd1b9318870cdc9b98796206c08ec7288ad685d624add26d2e53a496f271fc3f8a9eb7630ac9895df5116103a83ab728e9c72ab268c2c519b46bf76cd4420ab05253d1002f942656e1183f6351d1306540be8dc0d401924b43e27751366515cf2adc39e1d2b24b979bcca7497d18707b29d693e04f686b652ee2aaf8c2d92c34f99dbbb135d87730714b37b181049723d28463484f5fc0171d11b417aa4cad388d91350daf17d9fa329ff5a5996286e7ab0b59be8cbd22907eb32ee2ce8f95983db3ba706c9219560eafb9c6656547269c4162f126c7667dca2fd6cf7bd23a6cf08341a7c6038f84ba6a52e0e4f6b728eda4192cd372ec279da40739dd135ce24dfa55e2c3f57ce023c4ce5a4b04e72bb8e8b59a4bff87094c69f6e141ca4c843ed4f65ee42cde8dfe0c01d29c5387207e3bd343a2c3d6dd18254fb4e42645ef4393d9c04b42186fd0f44e57589a10225e37798f42fe51313e0d588be3b2331e846381d6c000b8f781a08f3a59d3c6a48dd39d30dc6accd3ba27c510672807befa317b9c2eb00b7e753d6a8d7524b72dcf8822f27f40a166d6fe06b624c0e3cb52fd67684448fbcb59b8fab6892ed72f163562a59f241163c807a5c3fb7f92481f87deba8c90f61c54426d1508821215e70ef08fc4f4a1d4ea7a5132b0baf88cb87d673035865c1d38037e1de36722342c1294d91137f3be6b41ec0122033ce4a93ad1606873ed44a1a4afb75c17672d973f1539d7943df20ca93e40446e24c406edaf9a9050f889f3fe6a92cffb872fe5082877de80ed7cbc337c0f8a8a5e3b163dceb8135afe5af6a6d347b6bb072d2e40ed6bbc258382a68b73951571c31b419a37cf946acfb56b6ac2bb0d3c67227fe8c730fca6cfe95e33ead63cf61241f185f888713964723bcea2a7b989d728bc277adf4c32f25a6637a53d098107560f68e2bbae8487c2b940848039fa252b3f055aa315d86c3651c9fbc4caeea00e204ddef66e946d63f14ef89629d0f0c8de100253f01184fcfb1f6ab2771f098b982510cb3a76ef71697209e950d7172a39e1c913b25ecd21625cc3ed36da3565963cf23fd5acd12181ed5aeb451be724284f0ea5db81357028435c19d7625b768700e9a07c8b7df089759c0e017b1379a3746ce2525ca9a67aacddfbe1bb1a193be3349c1a0e898ad86bf8cc69904442e6d7525831837d3ffb25ee92d526ac81e480cfb0ef99cde04c02c1419446772e108365ff5275deed1485425bfcc46717443f2314d363fa7c6641b136b1f937296de2c6a653392d3692bb9d2c8941367dc7146e6e5ecc79bdbebd577d7fc6a0ed669d6bee098bc6a777f38899a0bb0d0f1ce7799047f53bb9cd6600ff98e54721558158fa3fc9378882c41d848521d340916e21af9bc0ce1a4313085dd0e592edc12c914a431ddd124c68f84757224bc1d17ae112e3c9fa82edecc10f578e81ac4b1ff6ab5bc31d1a49f86a3f3208c47422ebb63c895de3cf347392ae4ab9e72f758f9a56c6514463b0273c9281ab3b05927e115fd05e696abd330db01d74572c4dd0ab859e5233bd1632ef10c8e9e1d353cda8ff30b931002a3765c24375972d3a6b054caa90d27acb758970f7c8ed809ae85a6f0a738c9ac7c15452328ad72abfbd16eaa90ec5c399feb7f80c20dbf5c0a8468b03bd9cdf8d1c35333d32b0722c3762f02ef68a3814fdeacb60b550400b5dcfa0795ba0d333d1f3d5e058d72e9ff98a0b0450c9338a08f8ced0bc02b2b6f77d8dc5aabfaf829e4507408b5721db2eb3ac506de124e2688d3d178247f96c85e0d42447e1457fa660815649e7202ee068ccf99b2a3417ef8d8a1eb43dc7bc5fc473a395a354a9257bc1c890972f7cb648202c6f7c3d4464a64869a9e1177370f9758d3a5dea1505adb61b67238c6e057504f519df36dd9ffec7f4d17458e86032ef258f5665191f3e5f082e17228b718a2e92bf123032f2618188a76904f2d46b9e73992c6c05cc555a9bdae729de88345349e75ff6ce08cf5ec11089cdfd9086b411a7df01db0ecdf678c0f72ffd2902064c408314b0d0380a6546bed770404b6aae934e0dd61aae5ed005972e740cc6df51a11be189ffda355b388787fda848ff96212e10384779de8524972ca290294d90b119d04bfdd1a4ad416eaab846233328f510205bafaabd32abc0a4333ebb75a99ce0f10f1442f414a8424165a3d920386564b8ac6884848ca104fde582dec6b3ad5c2960effb24f709ab2f0ef4b80127bb1373bc4dae5ab5ffa41d6ab6c8d8b969bac8b52faa6903b651ab492d1e8497d4310910acc9520fb7272d7b02067588380c9c39df09bbfa564de320b590d2ad5d943c4e18e009140b3726933a486c7f4a74a59b37dc51e32d15e4169252aabf1671ca61aeb711dc55d7204280c6cc5c1ba919041fe3488b58002c3739b4f2e439379b608e91c9977e25fcf7252ec4196854734d73c201a2bdf0a8583545ef16622d7f2a00ac44eb58572619e46e4a9d1f2c6cd46676c81a1448055054e1c5999a474965bafa0ae621b72097a1184a482cdf877af88bbdedceab1d7542e501f979d653da174b2f4dc4b729d897a23f3792908a947f30e0f43b927d41aef8c184851cec9e91173fd404772adb0226f9f7b7ebba3df9cb4f710550d90eed4b529179ccdfa59ae11b37eeb686ef4c0bab568ee8422165f9cbd2c7460df031bc4cd885b1bbeb5bd254e778d506330cb55d6e662467f6d36d600137097980f6d1af5ad132f6bd672af80621d009b5e257b246dd5bd846110da25fd6652b10e3200b21fcd21b4b85c9036d1031f288205e4a22758952225021684009bf7d8a3113986ca35fbe8a1119f5c21c902a27a61cbe902e69ae854f7d26af9bbea420fe0b0434a7d97d18f351d53324c1790a1a25040994a30f6c7c21d904c00c28b4b0876fc0633d0cd6c1c9ce4090072c61136e637eb7d8004ab60fcdbec151955c90b94f871aa94fb06937fc5e0aa721cbc86aa30020d9f1dff23d77f06effde6a8a1c1d49781b0cbdd359a973a5e72c267fd43fd7b8f17c5f083e73236fd7d35b1ae058a93229f585364cfc6f018680855078d7281b80b3aa2a10adb63343c6a1226fb06252cbd1689e1135b05197212d65a1366faf524f71beebf1f59158f061f587942b8d743e4512ab4d0f4977225804dd4832b96f1946351c89a3dcb6e4b705b7716263d2d41bfa9f88cf50538b85f0ee52f8a7f84ce59b105c6b6926fd0485d867cf27dd934099d23f79d7372f291808cc59ae7840b48f02ffb4fe231cf6d830348e8bdef45c73357c8a72a4462dd9cd6f09d2d0a0e3e7f0390683805e6bca76efe4b4bb5a2217a1471db827215a28f6cc65ee695f130586eae6ceb574ad8ae310029cb19f5daaa97e418987219870659b72b414f49a3fe2fc7850dc5fcb8634156a52defa4d57f4e8d10336542355a87a0f85282383e845bfd569e994b013ed0a3501c46682934bdb64a096fedd6d3b43c4513fb977b5031e007b32889ad9dd663ee03203ddfebe358e70c72adba0dc35d649d37009657faea1a39c6a7d7d901abbd4e9a4a1f9411bcf6e872b28740bc7b1e53c9601ab62ec94794d324da75f0b5c0fa9ec8d655056e795752e61b99c6b70a32c18d92b2f77b1dc776b09ff5dcdb996ba62505ffc954476b05340270113ba2f2f4f012a0ef8c59ee9f50accfdf0b281a020a930654ab368472cb0606c61363c88fe5ea313fd224f33e2eb7fe8f3312743e71a6bdb6191a7c04bcf7bc0dc59dfb84db5ceec10dc8f2177875dd28cdbe10fcb9cb3c6502b077725efba6e8c8b72f9b746b73b19d8a7eaf9419c0afda349fddea14117b937a56729d9fa95c8047fafa58f12fd92a9b5e67c2db258bf3f277bae104dbbe3dea4621793a52f59558217eba18675fe5c4c81153b36fc0739aa6a1aed37625741c04729208c3f758acfae40b93d5a20f435ea309667f62e664ba47a4c14c298250ee493080e08f34254d2a83c3a4edd0cbea08b3dcb211d4ac38fdb420a829f4480f4e11fe7549e516734a4a15230e2c2f0525521ad64e600e40c1a07cc03cdb16fa0d1b66aa14d1a9b22f49182830985b3b1e86317fdabc96dc9ead46084ade5d5772104ea2b2b7dbf4ffeb567ba856f3d187ab093e03619ae9b3d4090ba9d430fa55967235d02fa0c0e42fe367a7d7941ef1d360cfe1807dbd82ddb4be87ed686b541a2c73a08a02a7fe685e130e2d3f8f4ce8c025c477a7d828d2e6ada30f6e11726230de888fcc0a6c6e60200849d26979fb24225a4b46d93b49ecb89d2d8e63727b17c6d8227139db44491324bc86287af839889477852ffdda781cfe08807072ed644caf422e4d7b2aee93177e9068d8a59b958f586dc670e490ee0b64b7d2721ea0faa83d365938c4718d1ad1c3b92b40d29d8e9d4a2506b17b07c1480ee57295965e39c6a1cea79785150954496fdb392d095e0954e5bf8503060b11d4b97240684631befdb37ffb41c6a19de32f68cccf1f40cef8f9f354dba99855f64c7262d48bc6ba87b7b8b1a962f0ce0211b8981dba1182476d2c01a719139b2e57718d08b0abf92b45c38adf339b469efb5b4a7e2e5c2f2a298ea3a201623cbf75729e2573de866435f9585c80377382d6a2f773a0d3d520c7f87e8334ac9e7b7b72998279430d880085d629c5d47e04a85506a363bff8711a0c44149ef493c1af18282841593099952ab2f9435959c60fc40103433822255fa3204a5de74f3bef413a48151acc2a99076b1408441aaa4e11040df3f01edb125ddc955f644b24633be3662ad73cd5067d232c31663198d1de3050b614d4d75e21a40c1777fd858272ee54f6040731bf7c5ff61a991dcc619ce996b8642fe3aac0723eac1a31c467724e08bc42a994b87da133a066a95ca93e7ab4e3d38e2c692ca5727a94e79a927248a70feab883d4b9a01605a7dd8c2f7ef01b03b9978e96dc9f2fba529e742b72878fe7bddaa8d0543c07699afe2aa85f62493b27b52bb95d2b2fbaefa2ff487267f5fe627c7dcc95bca88208ad9e726d473b7e796a5224e2bc4074b9a677fa10066affcf9dedd6e5bbf7315b518bdd4408ad5de8e66bb124e4feba4639c298723708f04ff6e1e99ec3d4fcbd08164d8081837aceb7a2ea5ccf8d524c9017c5727501a02e356e5a741212b6bf2b2967ffa2303f7999b4eb118065cafed0e7ad72c858c943eef41ab3ec6442df152ee84838175b8693cac92b6ee8bdc3f1989672190ffad8aa81394f33aa21dc41bec78216e994981dffee611440ccb3c5679046d308f24ecbf428aad6b69e8f062d1a94b2b677eff76294fb6380e2097a8bbb72a3cee5c5b9044ea2474d1061ab02d034c9f34013d6415df35c8f6c6ae144ae72e68d12560023aab6e453e3c1cc73dd95129d53d77ed0c7a8dcd1280221a2b14b3ef3f7a382807fbe4421045f1d7579f240c6c6242e4657b02c4f67c22f15f7726e3893f476134b313964eb66e9415c3ae22dd756c84372b4f5198ca94a266327af422d054ccf3c0a0e073af2839aac447945fb662771bb772ce47e95867e4d01a158a713c69bc4285cd145ff9a8c20426884891e70a0df5a128fc97a3ee9897255f5a21a4ee829e40c78ace3ed5f1c390fa42d646a022d53bb3824528580b772ff9621bdc2da3503698372866afc5b9edc722607031b7478e8d7a2c38a069672e0e68acc2975147728501870e7644829286076c79face5a2eae85266b545f97249fa62e7b50e7e1d3a1772f6d493398703f559305ca6f34c54f6a0f5614e771e6004d242b49e66ee441ec160f652dd9eab64cee754d0df2ffd0247d4d9d4e8143477344049d37aea78a422b68f40517a89255fdc5878a1dd456d3b6cfe7f9707971cbfc4f82ce1ccdee3733f828b51fb528470e4b4bd5f2521036ff69345273ad8672ffac4792e8cf0a60c80ba768912591f0ca73389ada8d6cd5f09c0ba1f724bb8263b80e4e3da04196f1e0f7fd38220a94fd79d5a09d5677f4d3d40f68472d746fbd5e051490aa6decffdadb9d1ce617cdabe63f35e335765e788f0bd3a72e74775547ead797c6d9b969d07741b55bd213452d28e793850106f6d4f69f5725a8b0f5375144043c9c6a6d5d0c6b751d1aacadf4aa121f017913252527f6f72de9e30ae781287cf30289a2becf07b56b2d71446954964072bbe2d2067017b72e1706ef4ba5014ddb17d3cbcaa998bbd233480a5f52a0df4bc90bb059c3b1a723ed9b147b6a699767e47fa0c22d33907466772e947508828c8581ce2cd2c6804628acf532546f7b7e70fd84a6199df9f3c235da7d73725016cd6aa5363e3d5729ca37c00f79e9dbd13a169b9ec42b2a4176b1fe6f69296d5190e218e3162b9728cd7fb23285823351381e1e2afc73941e38122143f61f8fd1c1b7431f7ce3b72048818ee90d4ff50136a5719c046489792f0abab0e317540dfc202ca24c6da2b1c6e6268c662cf2bd94eec653bfed7592629ac06ba238be57fb06183819b110add23b6dffd29ee12d66c8e3a1c92f2c8482ad31b574a6ceef5b0cd9dbfc557725d3de097f062c50d0f93604a9f98ccc6ff50a40b7152cd1c0cdaed93f5a6dc6b51f284d59fe4f7736eb580ecb1845275c5b217144207c29b487d38bfc2be87378e7b5fa85853bacd76475a7d51bf43478d29c98e344114ccec5c1a67bd210d7227c5839cec34413ed1279172a4bdfa1a5a3695aeb5c1d86df0da422ea6b14653e14bd2a53f4731a1735f93e1deca79af73c701ec8a238e001457cb256fb72c72323ade74a4452062e084717f554da87b26bb158fe16ae610253386235922994d4ebff70b4a83c57c3756f54307a5bec0664656dc022bf9165b0a7fd0ff338401e03da012cb6075256281a6ef0ddd3e81b54728cf3fb42da5338d94f589667e533c4fa01807103d61ee75a7de81da48e7fdc1bfa0437172751e7b457025ec5772dd5f632d099f740b7653a38710454f9b27acaf9bc690b2bdb7a2fd556d6543725fcc671539201d1e7753be2dc4e836bf236196a27af48711887eb1e06f4bd12dd51aea5a382c30e70a8f433b338c5c2f18d7806c3628526dd5484196d1fe63028fe34cf317818a0fdb6b4d7d0b0e6ff0470dc0b11409cc02a086679351e15f72d3ec07a75bb98e957a276d53eccd7dbda3887aac8e111eb90a8f46e67eb24f72dd784552e17d06880732d6afe4eb1866278182a05cdb5b7d849a5aa1ffb5dc72eaf1ed9ef6e911c30599b4694841dc0a1abaf237944c26b70eaf505d71b9af3302b6574b5ce7ef2d6ee910288dfd71a99671d24c075b836adc0cbab6b5c40572087457fa6fd8a670fcd22417e45530a49ce6626efd6fbc3f2c16d86c3c774119e01e69b83bd5ad421736109bfbce20e822ea32dd99c1f3ab99e3f63ab1312572a4e57c9bb86bc30174f68172679bb4a415883d1abf984fdb98c96b66a044bc72a5b79a0cf999512d61bb23324237478c6513c084ac5948d2ea776435c717af1946d305eb76736bd74e27c16be88bbf789f88a38d183a5ea817e82986a41e6372e2f9d9e81da7c46a99f9cd22dd2a459ba08e5b37887caeaad11ea1df56556c72a64fa7f3230495a80bd15dc472fce90f7498a8d465e129d52ac58c15c387c0727f645442cf33003bdce5640bfd8ab6bb265fe969479cd792b94cd81bd1ab1372ef0743d0a7fd4a6b2f02ba2b89378dd4b37135ffe40cb9e9893b83820a6e2e722038601627900dc40f6ab9fc0f09af33128417ae0a101da1ef338cb0aa724e72a3e12b59d3ffd08e9b4875fb81ef8768df908a89ee525662a32f886dd63aea4bfcaba5f6e0fdf00207ee85ab72f9e851f31c9c6cdadc58b52435a13fe1597422b98b36c36bfbc3b57b988d7a602a594d7c5763d560c1f1c1b9e76c26f7cea30ec647e6596d9cd1da7a4c6e203047d02f0d2307b1f953328ddf2d008e4b9cdf72901f78a1d8807536e041e33a731b4dbbce559a8a94a292d0a818be02f8d7d904703ccc3d05346935712f097742fa29ed64003d3dd4e0025561f105c05c03e645dfb290e6006262a5b46ba13f77910791a0329ee0599d6759b768483c2a630f72aace081128b3b1a4852dea4f7bdd0624e05e428ceb425d04922a06872a178472fed460dc0157d0e206a323851d4137f0deaefbba21cd9e5ba7bac18ef163b85cc0444c6d01a7d04c856e29f84011d336502a32f0f1def4a3a7a29a47f0da8172b2def3022b6483c4febfae8c39c5be1123b504679e8e6b583fab5b5f02132c72771e5f9c9f111ae7238d0021af5769d592e90f204786fc8ab60d8e76031a917249ee635d61179ee4e749ddf20aa41cb27055ab20040034045eb27fafc38a3852566b9f35ce62ab6a25344f7f210d3f81beba08c95a3a2a4717a4fa25b1b65b17e390def99d00829cc00756aeb3cf3b7169b0425b824ec472300bee9ad00b45725027c0518cacc9657ffb0f50fbe86185d0073973218589c397f52e0160d11d722119e4611c1605fb6da556d7b6ef97191280425a68f85f66f940e6c82f33e76de628189eb597cac730da3f3b8bdbf60da9efafd69163e6dbde0199dccb801172f83128b406ec926797cdd1807eecd8d7dba45649fd6e386d27711324f7f26d72c60e38f0de2e3f84f9cd7b39a087abcad291e59d98c7788c62a9f411ef374f72d9a1ea4f7893c9d4356a4bdce7b81f1ea16a5e15d0ac9905cb747445eb7485728e715644642af56b33f0c8e542acfc36bfbabb881c81ec50fb046d99ed2774724958b3d050b449d57a2b21a01ea77c613b5a2ea0699f6b894f8a03ea4cdcef0ee1234a12e12f8b2c50e87bf927f6eb20bcca75888c23b2eca814bcdbe4c9b3249545db7bcecd9658238d87b34e72512d9f5bcbf7d20122907e583da0ca59ea7296c4d7371414bac601de7c06afe4682d7008ccb3228a9121411f359b7fb9b80af17bfb06e59857e95059c45d98a7db28fdf2e61735cf51809cf1fd58082a3f72c42abfc73ad32161abc2e25767b60818eccda9b7276c86111df43d4d5e32600843a8fab82cbb652a0be816ebb28be460d5511e463ae248bff16436f57ed9814d3c5c3ec5d3da58b0241d36f6ec38c4810432cd794ecb75c9c8f804c368c70220eeecd5cd57c52ab48660af3eada774a790aa8535ca4421eee05a6aae51a12a3831aace275515bea086f4a2d35687dce570b7bc06c986474badffecd155102d72906161e46bcd93db079ff94e57de26b18295043cbf1d99d5321c8aadc7b2c972d017c67402edd37ea89e16cdf215354708db3b93db99e3808ae8b05ce820b272f17a8072ea65dba6795c09b11587380c8afd62d5f5816056325e5fd65b38a803f304f3af56445c56516702e70bd6d0cb3e5d0eb54f6333e9c34a2e6196e3ec72c93128e6d2b7c96ae744d5433501a6610256c7aa17ac317276fb21db66a7d550e2162ac34cf8b1ba9ba44ed8f2ca0e50335de81f13f33eaab2aebd991f573c72c029da7f03b7ea07083857f6b3057e5e8d3c11b4355538e16dff675336ffcd72a707ad786bc4f5155b31b4031d8404c82469fb4e955abca47b96c3f0e76102533a5e22c7df664f37ca252f2599b9059e41f28c331f5b7623b50dc7752c16a572dd364051395cd6f5de1675f372c0adb88342e5fe3faf435c633549c0ce4fa57275a7c50c465f5a513a32432d8847c3eb27a0238b7788276e259a62555d6a8272f15189ec38c380769ef29aa41294c1efe22f01c83ef269181cb94e565f426d0554d8ede757e2c05fde95edd9e70f192ab83330b437904815758aa41235e6964daa5846ba696e8030b7afb9acce5ea73f2384d0fac012b808bb797e119bc3d243de9c1f8550c9e603f14001880e99d51818b81391e58803aa8d6977365214f0142375882cb4e26b5b3d0e5fcac6c6b216cf1950441074e68bf9948cf739722272756d21ed80ee66741f54c13d6b206287b177ec38378f12a6404a1a8ab2973772a08e00cf726f7c17524cdc3a876224de984786a41b9674e58744eb91b4d46172771773aa573ec1eef917b240cc136db33be3b0fedff9b68bb3898ffb5227c81e78c9bc61b67dbd59b3d33e0cf745b56677df6a8a22eb6704529c8b057bfd6a72155b0dfa35db91aa18a54c294960905a67303b5537f61110b5835692f7a8d942832ea523114d09cb8329e8571dfa243db00765f02967600af7fb66c696b5e672b02e41e0f69f5f1a6ae9eb6eea2f70cc85a078f4b6c4ca2c15c1f63c979af3082facc48bb9ee5b3f776048912aca731a54854be6d39e0409c30f1382b9736f0e2a2ca1d60f5301455b7af08f0631a8ac1e2dfd6a3b9d1b44557df1b3fa646d726d1ccb63b6912e9c05c9205d0e87c1e410c90db662b4beb491d795916b577772a652cc7476097b555ed6543bc633df0aea66e4254ef75ba2bf59ac7b85f83672dba619137c999f73678b2d406ace123ed0aa36be3371b09a35c49af15ca46b20e524521053e0a23fdb989455988f61f3365d0b943f2347508e0b4e1b6bbac672562573c3247ae0ef226c7874f6ca29bf28dbbef5a596024011f9362fd29f1972a5ce88961f8f7e5c01604f14bbce21c8c8d4bd25a42076c4bf6fce7b993869066702eed8dcaa98da8f9800a993b0149a37812a5692a099ac22a72e1201f88e72a5641b5d1d57fe30fcb0e3a19f1ae395908e785042b0b9f4b37e4354c7f60872d8048e9b36241e30c8240fa17ac61a842fc2399628f30e15fb9591d50ad4bf724a8ca1ddbe9792f2e08b21f83ab6006900e3feb084a0718ae45448c911aa6d1d4d6abd2a02c95347b8673b3eecf06dd83ae403777bab93c4ad84fc35fb9d4d72fd34e19d4ef14b12fcb5f6123cbf77aafba876b082d64341057110d96e850733adf519c4a6a2d7b9c2753ef742c08aa513f963908453d82cb9b7bb6f56375d5181e86354f07ef8cb3ad5da8da17b377ea95639c53a6a9f4822fea4b88beb591ecc3a74e007d2000171dae135c44ec5ca2300e97b9274c90d01e0aba3760fad3e1641f97e38c82e04e7cbad267d80684a727b3d1527093df1f60131613a2b964fc99c7f9777a46a6c844eebdc1156b0b034ee038880c3696d7f6d8cc2a1414172583474326fd6297c35c15fb25f58d0bee0ce0535a0d1bd54998a4c10bb244c30edbe50296e7cb2642c53d076e62cbf070c53f7e2b02c90c1d3fcfaf28a61dc72cfac8c1ddad89317c6062e8743b8f93ee2549d89498fd8dca93e69d14b9fca729531b78c5be9fc03ebce72c451e881ab0cf2ed49e1624a4796a940f1adf5eb616cb001ae433c0f46c058c4a15b24344532a30f90251dbdd15a91020f6beadd186651b358c10da65e7b7409fa503be8d316019b3b9b789fdc99e47b22016b0772108b4d4fdcef56882334fefa1fb8c209150867eeaf0b180c092d5e0377be75161ac06a582708d145d69631281ce93697c3732196cd3bfdf5799a61ce5c6e57728596e523fa4dea1ad3554d7945477334ccd6d61821b23c39211e88204133b423f1445f2d7569c0499b554dce389532a628f68bf4dc23fc067e5d34dd92846e7244602bf5a4ff9ff18ef37a9c019273f33f9bff8b8103aa6498ed56ed50b5e472f67d39d02a5e329c7b40735a43ea4d6855c437466ebb10b858b860faf6676b2b5263433b37a23232192dd73d8b0ec9f400159ea376f211b2f1b013407282c36807c2ccab139de68814e2ebff10dc7b7122e74cc6ff2763e9067faf18acc0944fc94c6e0b35eb8b260d9eb939c26270ad1d1c8cfda36ae3199e0903cba0ed86726611170b079d5523a827bb0643a9bb2f389959a66b29ab55ff61a41d5363b04e4c26402629b4cf392a435262bc90e60b6d53c87493ab68c3ebabac3362bf0b7287589ea346e64d1efe9cd5cfbfaa7bed74200cf254ac90bb1718a8ea18b878039fb67d5afc33a0d2ac3da943b7ebc302f19cb36154065b2e3245201b1bc85f092221bb493263dc7a7a3412b38f619a84844ea33183543fc5cbf4ede31c928972f7f0e53bc1a7984f6a6634272f3546870271b3ce39e0d7ea579f713fe71b1e4d48441350a3485adab84612d8832a869e276ff025306d8b689ac962e2c7868709f15d969dced6db4f67977d3b06786d02654897ed7871c85b16508740b6c1ea3af5579db135cb31a74fea8b0bfe0b2294931b0938dab8dbcb9d34b329d7421a727bd63bfa44a372970b04da29f3244c66d5040c4a81fde7e609f955e708e01b72844aa5f4053ba895e42266e095d714fd36fe189986f8e2fff9f49c3e019f79159791ba04fd0c5b32118fb567bb7537730dae3ba4df0431a8e7ab626290856b72377d4f180111bad2c6ff3e9012e41cf7c460e7b94ca858ee27110752476c6370830c8efa1f7bc3e68eba08f57a707ce2c6a746672d9406ab9cff9f65a61ea3052ec3254bc78fcd3188516a0d586983e785e82b5423fd92cec30572c7b53b180d1c57590731c66d80177bb0d3c8d584416f56fdf49607362451017ba9cfd5202c6f2e64e84a4ae92bca498d2caab4101d3bbe1c756409b23d4c1235c659a59b4848f4b760a2810cad5b00f67ec510d26eab264581bee0676c17f885ced7b90d4796cd93334147175030f076dc545ec45edc9160af9739aee847cc9dd94094cd72e474466dfdd26a0a812f782c22d887756dc4f68c415cbf369cef8216020bcb7257505e83bc89c22f40e356c10a68c3b6ba166eec16dbd7c79d0851fd42eefa05d04bb6cfc581fbca40ed248bb18240ce774fdb1f18172726e9803fe52901cf6f5e086c128b1849c6f5812fae01f1316e20bad01d75c40492b88ad9f7724b334c05bb697e51e862a762b008361189d562bf7083bae4958ad540a40da2ae63c4722c6fa0b3b6d9e95675c1a1df63700c928bcd9284f5c6fee64e1cc57bd6aa1872613b42ad005a9d0377e0398a3934f6cbc00ed64b0242ea213ba8d536fddd333ca770f5e7301671103d677c379b9eb23caaad1f9df0c63366190c4fa39d604115ea63dbb0ee73bce19e6d81ef741c738e7f1127fcab1df02fe3e5d068414ab2039eb26e66180b72adc80b8c7a5f54cf828e0ee0d4224b98f1e906aba69c1ab566f7338a792a8eb8e7099dcbe797d4d37658f4e458ce5ddbb25f7d19198c5e0572b27ba1f12b322ebd7f1683bda6f9abf7eedbad88aa8152ae0281410f32e05472847ccc213c85c9c35098917dba55e7733b64bbc1b36e4c5e9e51287652eb237257df95363ce3983d0b3f365057a42cc1c2fdf7ced3a7eeb3eea6a99d4b76b6258562bc5f2f892beda253115ceaff2aa120421b301e67379c190aa2c5854f977252d20320fed19cf2995d36ba9078698d6f16759910d28fc70d6bb367ae1b9a729e801ca7ebdbfaa71c7589d1b3ea8ede823348d2437dc880878caab1b1d8a172edfd1f61792ac521fc972ad9e793eb566d7356c042212a29fc7c8a984797a4722fc654b41f05f4414d2174b3385f9f16410521093589cc4aa4e739ca5ce78744fd14d3dcebb63126e0fda0ec2353e066c753f849a436ef92499daa12f0553949fbb61f7be69d3933167e51e561b5d988bcffdef4c80cfd51f47266208030fd1a927fe105867aeef859b0762b7daba0903c9483c06ccfdfc0e319349b630f7c0c35707f37f206362bf8eaf0a810859980c80b01cbf229a915517111ea57ae405a07dee933fbbc2385a537e35a41dbe4c7862cdfdea43e8fd9cbb8a259686188273ee31490192184ad60901ac9495752b90933a86c697848cea0e7202a1e918572e661b7a6a74bee82d094c61a5ff83ebb03919d27996c9a755cc13cd4485d9372d9bf48323b7a11990617f047d507c09e7c135dca2286aa5683d73c85f3881b72f75d229ae80e753025be5b4c70280d0771cd2734317a5ed673ad96b8d806be7220cb7580038de28761b8d2e5a06918d57aedb4ff9bbe5249fc57d5212526f772171b5ad57af1cdf08a7ab9dd84d03a10e634daa4d816aec8cf709424fc0dea725a225fc150b0ffffa4bfc7ea2958f2407cc15865e313c7d61965dbb1f0f98832b4ba87ba24848bb30f565ee54b4e5d1efa6cfa85097cc3739f6d03887437a322d105dfd79d3f034e434128afeab3689a1589a3b30c95b62cacd0b02aa2fbc6723561af7002a3666cfde67916e6959803deabcf03975e377cc876834253d4077267fe64ecb421802c066673b44dbb281657ed637c7ac632a1fc51bb2e885d0f72dbb4cb501be1cf87b306d10a67f236b684a5e8a8c2bedfc7172dcd4b0aace12d73df1d5c303332b4a6b892cb952702de1d063ebcfaa2432e713c7553c6f754721fe213f34528ebc00a3dfb75f9117101b5ad3dd714535faffb4e2d90205b9072ba0734a45f717590af43acd391dd2973f6ee390fd5016c118e4328a1554ff3555ffd11b7ed66a3ea5552a474353fda57053a83ea7b04ba59c5907e3cbf047672f353f7a3643fc5d65da941aca656905f9f637e7e58120eec9f85e6aa96084d7274cf6cd9122544bf8c5c4221e075f6bd1ca229ce08d61bfbde59c64f9e141c72c5814db8b4b3b0ba83d55340aca869152dec6339dc360af26e258e03242b6272672576acc4b8c79ac8b87714128c9f6ec0214a109aa820e3121876461954f21e66704d46994c4151a23cbc3d136535e95538e3c279097d9e7bf856f8ae904972ed7412ba116db4c5db4f7fb0a677c12238b9ca885ab7d2da55b4b486bbefa919e2cc061284df9f0649b1906dc7cc8050d6370b7635a524071e52dc54e0955f72dd2471e84608a33b98e6dad6a790f5b3a432331bcbf66f92bd5c483fbb40877225aec04a924661f2d65822f9d8ab0d7aa51d911797e16a5d67bb7bbbbca6327275c4cbedb0fadde5405dda5a05aaeffd099b960f5d386fd998529003746750325fea6375570d3e9213ead9930bfaa1e2aed05dd1f271f8deac4fb01fb500b2291f2bd0f66b6db9a1d4ce08ce312a5c1c02679131c7234aa18f5ac392684d224d5b8f6282e75af9d45e086a8dd00ad970a1c7c568d7bd0ee4c238659dc3bc4a7250f1a223e5e47c91c33a4f47eaa9205599630fc4c82f4b272f72320473b7680ab9109dbb8ebf6f8ef027c998e3938c0af0ee44f7f8f32958f4b9bfe0a741130a3b8ea7625122876ae108690ce887eec7264e974277a806fc7ab185458fe09c5d426d60734bdd7b55b179e76646707395bb0d451abb8fc28b9c03208aa05dab72e0146c3ad971385572cf8d35ef5778c89383643bc78e3e39789f321cdac3c17246d5cc7f99a6311da7d50185645b3684373b2c797a166f41aca2a7b2ea590f72482104b15f6fbcb511622532d04a83e7950b88c4bf02f8c6d30f09be3c5d85724665c79fc6833a6f1fa0ea794580e9f17a4070afecdc7f18d0ca30f320063b7242c13761bc4bcf625385ddaa44fd482040d501e1baf9075cd7485e385cc01b72363ad9568b9f64eb87539b052577d05e598d677ee7688a36722bc018768b8a7250bfb22e4fc0733e9f178e166fb9412c208da7b50bfce4276affc057abf14872f981f4247b92680eedbef476f4f4e995242988fb5925bf93268d00640333c97291a05c3009c01169da23f536ce6b86d09a190849014aa185f315c270b8f88912507ec455e353064387efc459cec21fc8ea6b643302f48cd8644fbc45366aed72152109c80cab27a4762409f57dfb2b9d1b344a210a7965a065e65f294b348972f3cb9873473c119f4436366eb82408f22eba2168aa95344f4aeec4cffae26f714573d5b361df20742b802288de2e96ff2e1036c62f32411d973bb1cf0c847b72d6d5f648396049a8ec8bf3cc1ac46b1e403467d8857194abd9f59c6d276ed4727e379bfac17a99d483bc18138c2e0f4a00b7933ab838f8aa67cad808f3fcce72433cb82be7ed20704682612881d525e0623bbe8b10081810c959568f54031d724fda3423f32280a0ae804257e7f4c30b05498a2d952b22fff9fefce0b45abc0ac26ca6a568a28a715a1d49a6f40417558c69ab7cf4274b06cf7031e8ec1aab12bca0a5d88b668bc99fcc9fd934959458037dc18ca7bb84d70a5cdaeb8fceb51aa88c623c0d8dbef9f9f0ee5c10a7cce0a18b3662c240f746829c14f4dc4315720b89623782694edf15bff1e1f97ff0e26cd3dbd703376fbaa9ddaa20de07db72ea9e9dd7f589a42282b066de5aeecdc5529763ce43dd2929529196cccb8adc72b1104e010e5ddad5025b8f78c9d769107718f569582b717fd99e1e13c83b794532320912a679cf422f1d7f534b0578460aee94d07f973eab7266dc4b35558002774667bd0b17eb75738025f6507a0bd401d1153ad47c4e3735625a88654c085236a4d7906b9579168c01a4738888bee85a21d97bfb0010bf76d0f08ca044117226c57498382ecd45b0153bfcddda0f3c6790be2d8b34c0327d9ca8ab98bc937240941fe3345480d654574256c67f166d87d71e22855fa6d7c5979b3231701318d65a1b15423dac4fa7b8b434f38206a7d79b2b5a39dc39ea40e9158087de58727257d996e030f8d7bb17ce972974cb8ccf0a4b3abc0c8dc1ed68e54702fb7e723ab7c3596731e5a08108ea81b2355a544515b44b0b94fcfa309f0d3de81ba702278aba9e76499194d07f67ab0b679450147dd464ceaa3ad5776c88d8f571707244e6cb2b62a4ba02477ad2ad180111ae12136ed9c55009b8336c7b52426a923388920eef29ee0f07a919a07f53983f0289aafff8dc49d1415f6deed1f69fc77286f649e803a7970f5711661e94400bf3cb06c5b338329244e71e47fc978fe672d2c0420ee6aa750fb05d79ccd480fda8d3f7aee6500da86249fa9989a3f4e34f0781469b353f6e5813ff3b341dd25b9c547d6f433f6b430a902541b2973a4e06bae5f163095606f436f52b93650745f34f6888c22fbc31b8a8ec7cd474104d61f3a7393f9779023801adf1815be3bc956ddd08641fd503958bdb25bb002d0f4401531d9f73c1394bdd1f73c453c0035fc8ee70426758a246b218841623b8b30cdcf31db5e5ca3d8ff6522d9a1053d44a8b28b48a41abafbf7166e45734789a21d3a3c0e0de2da2129d4e47a7929d0099e57268e642770923543e4600312acd23b2f4769863103d78aa61723c9c22d854b247a0dd69e3f545445e92954328447020ce3ccecb5419433df06ed8f56a3f8191d95ab7002bae43e94edb6d2a045c72359f1e51788464e3033a1fe7d94675081bbfcc110c3490d659021ef669e3d7723b10227974b414afea5b74943998b52af7e36d5d7578aaa904f9dad0130bc0727b0bae9c34d514b1c6db5cf02da37e3949566a1c7e94a35e66c5425f12382072d129c26aace33b3cf0846938b0ca992a7def5af29428b51a8b731e4152d8515488759a904f676290ffab13a8fb44f4d2953039271c1ed32e522fa7f4e8a00172bfd71b6e5c72ba73d8449184150ecca5b014b2bcf603f410efeeb019f17999722a321a93b0ba0fcebdc3b6032befc621096bcabe18978fdeb0ca2a040dfde0724f838c4bacbd6489f0107287746637d6fb9429fbf71007fb26e68fc9d0c7382d479ce124e2c277ebb32fcb7df48336827991624a3bd0a04854b585e27fef615f32b4e90da7e756a114f302ed9073cea6b42627424e217921cebb405ee9015872f12548319db12379222c80cba85981269799222f18cca8a78b09429781c98e2b08bafdab7cb1183f2171c822c75b11e8a7d9ce05d98008f9b91e150c75184872003c1582199aec4c252c3a2ade40912cebee288f58f0e7a7d8629c7f1746ab72ac50bf0e2adb5b2eca00495af2154ec585d3d7996283cacf65a673eea479d721d9bdf519b947a6bcb49a2c9d68f9da7681f39b9990f7a63c04a0d4c7d3f49b722d8c85ee464898a631b631cb82c37aa08cd0e36da42bd8dbfae2e462ce857172bae3bcc613cdb8c61326ea2c7974b4ba8d047066627d845b9b4aa8397b49985142380d01586840b6e1176566545be95de7a11934263a9c07d3845a738c79f8722e6f685a556ca103994335a2bdeb154dcb9a46eb05c092f3de31e6c2baa53d5e1614bdb84b0871c1e17aa4b91974659f786e123958690fb0af31abd84097d9203ed746008125683175e8999706b0d476b9cc9fee8c19ff2a725fbfa3a29aeb0b331e547293dd25b1e5dfa628ab3212db7ea06784fcfdfa14e1f43f0c2cecdc72c5f05149797829c4937be9f6d966e678d2b5de6da0134f9dfdf6be2bc49f4d509798509ad946294c6fe8eccfe0bf8be9629efaa5bc8262bee54b9341a8738f6485b421737bd84be705b263ea8ddab06ae4e92a891c59a857ed09d1ab56b2777264aa8d4d59161215ffd3cc3d232f7d4749f4f43884cc2f834a8fc772b3521772f0b47674c58b125934d7185abfab21a1579d0f2d09591fd62b0f20e19f151d7225c363886054d1b3dd7420d328b1d2e1c3dd0f67a2d7cd0e39fff5767a6420726167885a60d662d9b1b406b71c6b0c218ad925c3a9143742a30f6104dba60e724b87c4e0d9a1748786e985f04df4c65dc926d14e3040b497a940c38957757d72751bd52ba3087a885e1f582f09b7ad4eb1756d3d6c5db3bb245d98e765d69a6b66afec9a6c2af04fb56bf19da378c296f0fcfbbf237847e92ec6b8c937001736a088f72876ebe33ea534a9489673ef8ea8aac949144095f155252112fd4ebb7261d5372dd27cd0f77d36a59876b347146a4349cb9205638266bf15900af2d43b3f58dde5709db6cfee7763dd7996acd8bdc3bbf5e42978dcfd86104730de70147bfe65da1f7f9a7c6b234359ae9982786e5c88cba36c2ba57ecc26fc375bb17240b7ebc858f451829024f4efc854c4adf2f579a533f0f2d066e075c50821af7205735429409c71855904765c8ff79df6c5eccf4f43a619eaaa800d9639dd0f3fb5e3478ec72f9929ac1656188bfed777ddf519e3c53d7d21df89dc37db02527276859700f1b4f11fb0cbc24394e0a09dcf9f15f2d0b2b2dc1d0c182d825e206d8c095b79285aca0a1bfeb966d248219fcf1b6975c1f7613157e8301a008c251f6b48bd6e4e0583c3a8ff757bfa88a6cabcf1c4e55389985263636336a74f1f4107b6b2cdcd3e765b71d391ff9b68e53a25ce8991c061b42c47914e435e2ab77298f00a792c76667cc1fe4ebfdc4a511a29aee1086cbfbd1d4820b2cc78ffa4601d0aa37ca973b89ac110ea22496c123e2c869d5fcaf282d8d5599124b5217972d6b33b5ad35b6811cf205c6cdd11099c94f27eb280708588941c190b9eba0272ba2eef186b55192fce8328e3fe96e9be3529ba657f514a31ce5ebdb0278f2872ad9236842e958aa244d37ca943ad66fff6ebca07416d6fcb25f89302c349e2263940ad1d187af5871beccb254391a805500eaec98201fed603545ad8917e3b69c88ee449eed88ff92b1a1c7f8687721a0fc80ff8fe9f9764580f680166725472c31a63ec294d9e085f750e113bb19753438540f338488ffa6a5e99ae21f4df724dde29b0496356586e5eb0ec20722b8174178ff0e9249293c684f88db74343311f19b07986d6e3be9c573e535ee804761911c8558d8e1793b1000bcba1bfc96905fd625e98c4f5b328d303d59243c10b03e9fb2e010dc865da69ecee83f13542dd08f0c1a5f802e673579843e5a928aba70c152714739c15139799ead3e61a7253720b4b4c0f9b1719b47e77c4c69735e30fe2544315f55cc2702df7461596726aca17eefe5276a61d10a57ec179f321273a846a10a55bb2ba99b626bafc6272ceb69f94f41727b981e82c816a6d3ae64a49028a0156678a55e9d0202e8bf90f93f765e9a605eba40dd3b985551f543e2122c282c9d675036cf82aba8241cd72dfe7f84c40677b43df2820f081d8019fb63ed546cdac2c4aee702e09738f2f729c3d605efa475a0a5fc24d1cae4bf06bcffdaab88b8822203d53b78954d72e7229cf5cdb45f842044b3639f1ca016c92d1713459c8f055f04bc3f6fb28caaa4ae8a40484190cbb292b73a21f1aaab316c7b378ae544f82220dd89e982f3c5822fab3f8c732723686fa6e57dac08f0e1ab74adb9eefc3a4725cce4feac1a1733256e6c4430b65e1c200960426ea9badeaf1bfeee96acfef4bd70f3e9bdb88332cfcb718f14287817de0f6d1f877656eb5fcd8ac4be475c7fc100d42eb60fab072c0ff80e3fbe796b24205867f46f670f51995f47028b3a26668a8daeb7d23f0720e4803224a0476b38e3bbe902f900ae30373c5c7763558891e15c297f7562e72cdc0d768345593d8bafe4f1dab18bb375f6136a28f1461674602907388fd537272007a8c5d7d488a88afe911e2eaa13b9700daae2beb07da4a85c6fb0b72b73a87e118d0a6ae3e201c61b37eef3019ec42551c634bf372169aca8449acacf872481f160693abe9b22d0c557a7da92937e12492be01868d209412f4576d2d4a725af432b6d6df8b470c3bfe1245719f53b43b1ac385a80d36c9b5ff9c71c4e61bbcd20d2126d6e69117dcc33fbaef856c09b40a6d37df8e80e078a63e8dd4b87248d575a99d511a624bbf65718f5aed51ad5b94494df9ec222258a98fd88e6a720f7aeec078339a99f012b9cb12c7b65e2f4fe2ed5fbf0531a6909fd8f61b9a6c3baec6c1b8dff73d8f381a6b654e5c153b901f512e39c2904c8d0433d407243d35b928550e7a62f185960b8d50ccc841e4e54a201b7f7b65753e472183ff12609a92b2cf03794aa9f6bb2fe719400e198f4a3dbcc0e6f81a130dc95e73487036d795bb069b2cfabd238a4e2d4c0b086c04b8fd37ce2a7121e93e7940ac6fe27235d88815fcf7b22b86027fe2093e831bf238c0648a0b254472b63d1e7aeb5b726f51af447cac260370a5b21fe9c242f589d2a9e285b7c943b1784997324acb0e0d120a55d2ef59ec034e65c0675f1e00cb5b7708362f583343a7ff383896c772cc17203b03cb7a32aa25cd3c5dbda9fe4e787c66f319dde721f7561551329b29d9d893e8cfb6af0059a91ea68245250000b2f5d6143e8d6ccde88d834c506e3e6cc0b2f0d71b9e3af5d7c0f13c589b8c7188883fbe26bd033340f6631633c87215a64e58e867c5d1c38eabf1371d82dea606c9b64a2e308106704b18cbab012f00e7f3963cfd60444c5cfc234fbf2c93799981b2fbdd1b9e3b320a7a72806544e9b0ca26140b5de32667bbbb5f8187384ee6b457f75b9e253c70c45668a2c772e24891a5ddf9616d161a2c388f671aeb6c4f59bed2811f49b81ef56b968c1a72a84f8128cb0857269b0e3916c97ecc7618abf2928427dba3b9ae36ca0dd26072e50a94cc352f6ecea611b0bdaf0146bc5dc1ffe198457f92d8135ab798e132498fa8dec35a57e6a013a3bb388b80fad9cb6b93bee8f38d3f202aefdf6a281272d9982f243eab2417f87d4e93f009051930418e6c307439e533ef2b7f6377a47254775bea30ea1bcd23fa88a0ab915459c41d42012ef66b38ed6091a98c4b95723cfa20cc5b0a02818544f3f87d44c19767c0c3b094030b4ba0ae0220a283277296393474edb0f33a30f3a0eb1dd1ba4b30801e1200a6957bc5807581c932a27245ff576e02b15ea8b4b24154834ff4fa6f4888abd62eaea8757e2241b3a3c672e45ba6f9f25ce49ed8eb60c218e38d00a9463b6b716443278e8313673340b23cf7b44b16138ca513afa183a13a7295262fffbc44240587d511142ae69b23cd72ce352209c7bdbfbb9ff03839494bb649f2b751da94f676f3684db6dceb32b57214791f8ce638e7e18d0b292e9cc590916e2c4454f4e292fd5fe2c1144b29966cb51deb87476bc757cc81cbb9e482297b5501c5806c565575484b20171dd88d4245c17774f16139ca7f3ccd6d7d6381cb27846d9574c0dfe6cbc7666f5f6cb3228b1696d24860094cfeb5b1cc3db5af9f725d80c276443ee4b1263ad7ef318b725646b02fdb155f112a111b5f58eb4263dfd27807ad33f18b47060f9de26bda64383e7a263f2799d95f683c97f696e0532f7d1f76b2e943856c57390cc9bae6726cb8851451fcbc7f7ad2d9dd050977af828aca26d6c42150d3657fe77cc2367273a0e9bfb90ab5fef8c50e5b44c11b83a0935a8e71cdaa8d0b8167cc47c5bc726204e8e79887f25ae95f8ffcd21d8136d3f538dd52ee5b932196cac1a28ba3722195ee1e214855f6f26d2d52db1743c60839c69700e166777ae9eb448e6b3c15e34da5f272088e24e807aac403b863182201aa24a545740d491612a7b468cf16b280ab344d001d0d138ab05ca350d011370a5c25c30d90a246c110fd47418772223f581ca3e18dec39b4f8079b24e1a92eb0096b29c1d0598cf06b25359e736bdea36b3e79d6196eebfb2e620665e5178fcc6b4d3d2245267a9061f4afd7b1724514ba73355aaa042a28072c8477cc65aed85c9e49de1c888d5e5e7b96b0a7688c9c823d944e0a4549702702b1e16b2b07dd614d123cfa0fb07f43e260f4797232a0e1cd1c2f54516d7c9a2748c9e1114dabbfc387900a1fc304f1f013d0bd2c0dd7acd440a53a7f3c19ab4610889a901482ad2eb0e60b8b744c5439ddadf60f4e94991b0d36a2556b3cd3cc3b3e3da679445357858a799981f07f0d1ed7e71be2b39bc52658828652289aa8a7f9bfca72f578d4fe9a01b29ac91510a9c9d86f258bf9415a58a353d81ffc9dac22072ee9ee8c3680d8e28af8aa35ee8b4d7e7244a6b1b172256cb9034227b08a96a1fcc2764e33c0ee80ab303c37cba7d2de654742b9ccb66085463c26d681180162e879e24334f3728b2a979812471203d8664f697cf36a890fbabc9306f7c9ba594ce4674211f91e034488cac9b19e599172d821a5d6347034461be4de10209db08d825d3453864f61bc9fc9e4855a3d5a7232ca7ec42ef914b974b9c3c027d319a7ad470fddb8a775385c9a5cb7e6f32c72334e8ce4ceff183d1bd6b1f35831415ee2f8ae69932c59c90714d1c6b2648d729df0a46d588ce12623c430a8111190bd2b945139e24e5d16b94334e1ddadf51378c207c60b6f5344c99400e7a07fc1a8a036832256caf94405c193599011b8548bc47553e3dedaa1ca75e9f3312a1da7fca7df681667fc54d67f45c9d5a4ae727fd8f8df9e9d4ed2bedc67ab4cf3a5510d5ade89339cd4b991eeb00272a08872a0a7d9426d39ece7f47a2afe64a60ff950cf6e01562a709cc486a30c2f1e2a160a75da710d35de5269b4ae65445bcc2044adc41fa3639eb2bb5010e308380b72e7de9c66251fc98d4d0d829a4f4532cdeebdfa243f8a20cac129276e3f5d6472ae24466bbd67d7cb77ec5b7f29fd64f1c1add45dade0a2c8f9ada5019b950172182f718119e34b30b330a1619adb78b901cd0dce1e14ed641db0273944020572f87c0a2d0354c9a757665471d6ac3f1129e96de0fbf8d7837cdbd34808c0c3726aa5e740d4dbce5fd38da78059d541d3d7b31eb5d9b69d9335f866987224ef7252dccff2c5e8ed34eb6c2d3a4da42e05ad82eaa91a094030e8c0d7154da6c67224f3fe5f53cfc8f86dde05f5775a20406d3ff9781622b7c65ac6b4b446d68354abdcfc28a0a9aa406dbdab5b49a80ea73427fc282fd7fadd2fb5bde78e9324727129baf602d3c1890990df85da29094813451924519db774f6ec7450670e0b654e2bcec14f15f83955a29bf8680b9359f7dc002b7f8113eab4bc156db8f50a7225448b0247cfac0b6c0eb0a65c2c96c07965270cecbdaf71b757d3ec708af67202dc3a6fcf2dd12513da705f797c3f478e67e1c43e7e173675dc8714d819be72b953dbd57107046ecb9df758ab75683c51a2815afb41670118032ada1a526d722c7b06cf3a5528f4fb5a1d265610a08419508bd362c32bf724347f328460b57227b7469b94c5f9e7a0868fcddf278ce815ad9b272d3af6de1a99a7746bf0e7135bcd31d832876e8b4afb4ba2e1fea267929d2bf84a87972dbf6ae5c6ccc6ce72a11c4ec0f9c5c347e856eace509551edef19aa5041653117204c12e8ae38e91fb87bcafb3d0f14a717cd84381723c772577c0d0fcc21cb6f54a8cbb8e50898183741b49c5159c93c71d058c8269ddb5d76feec4d4871204db7abd6edacc34972384c70d181e969d5ed5d1145967e1931ae2d915bbcb7d35bc9aa395d73e05d5f5263d046bdfa91bcc5ff22d798eb74b3fe21f0827305c62a405e70beaa871e72e8939ebab0e3379c5aa38807a0ae2bb519ff833294fc29d5a1dcff4ac1e34943491f0b31470c1c1fe0ab65de990cbb111e3fcb50e78a6b773e27eddf11fdf87207595ff5721d14283eea7c857e1b6be743f2ddb14bcf103353b6c46bf6dfc872ad8b79bd3dce4da54c8e3f90a340e6c0e0703f009c1446a7b8d5a0b46cdf331f8aa39e4a26c3b551cef66261b309f45a3fa82c70cf29b16819f9dd09ad9c6d1ab6d7a319c26936f8db134fbf906cbe7270a061d2e0a853a09011c1929800767233b3034293d789122c62b2d76fb24059501d83bc1979d9615bb63999e3b54e5e78385a8786c9351e752daa82d0f899974d227ee8df8e9c1b5e43f4ba48b170728f295f5a24d0177e6f13411e280d96c963c6f82876318c20fb9a96934fce3f726ac99744acff7998ab4be9e2180cd09a1ff67bdf4b2055d1feb70c229ce1c1722ff187daeddb0a5e5dabb45c314267b9f6b00e07b3d50559d689f87354ede32467a927b0b2cf75cca2f054f4081d81e1a8a7167233e98932d9128ae10be91e7236c85357dbba5346939c6f0ebdb7a1df59d69482b4a5250d7499b0ab2b4466723f5c02bf0887d9d33b4dc824b02d0716cd73260360cb5f07795147be908c887231974836b51d5342ecd9af9798ffc5caf261b72d72e40ac75a9c8bfe5101057260ca934ad6fdc39f1d7735db5854dc19918865c0cf10496af15887236586030444cdc2b8bdbdbdda1dc847638fdafda18462734e6a90c95a7baa798003202a6f53dcae43ba35d931ae4063f67f23e47a9cd49fd34327b67e62f2d23dec31ad669303b3859a975040ca0be5405f44f685273d3b6f6fe05fc4e1fd48f4ce39ce72f1895e0d98e24e49495d90a803efcb8976336b1a1194392f1e55d864d2eba572397426a9a61d1f59e15de167a8909932a4f3ec14c672f2ebc2d39e8f1c027820d6790845bc2e2068f1e5d1e881b088219b7926df07f19021383899b0031bbd45ac6fe24f41b62aebfbaeeecdecd6ae83cd9b43e34102bac5c9f5491f05d0120fa7f355b9f90ae42d9a4f3c31879f7c1b1b1a962925367e5b58873afff6e32a5694ea0ac570281e2e01361a510bd6a65cfaffd8766517ac1a9947e3af722196721d94a8562ecd9e23a0808239593a41291aae8ddc77e23a93801a68d23cf19772f161c4b74473ab01d402343e72134d26b69163480d50ba2584b6e8b63763b331eda0c0e68223bca2562fb5e69e17c5761a35a08d9b8e92db06dd227c9fcb76722be6d4105ab18865c01a445a2bd3294ea0c5ca9d3454146dded1a61956ca16597a576210a2786c99850cc7823da92227a6247f82d141a5a726d81eef9a948972c945b957fdd1a0c5a4cdcc5d1b6943da37ec239c8563bd43db87013c552128727e5efd06231052f0988acba0533fb2d220fbb71e1485875d50218107de702172978d5928d86e3e256e3d3cf1a4a75ad11e9ff5f3eaa1b4a75360b66985129467a3a82d2dbf828a629aed0f568bce0eded57dad3b665695a47b501aed728f5472230d3f5a3b0bbb509140365f856b8a44d610283bd49a1746e1d4344568a6da723be935dde4fae6918c1e054745d6a0ddbefbfdbbdf16db80ea05259ab4aaf172c29ba69e07e3bb54f4b377587775a0007e6b19873c32dcd34144d0633f384972289056a0c737219a754e78e9af7d0d7edab995657142e6eda2b1978cbc610f723261d6150a118d7b254b8ff2d794bc49b7febcabebee177214fd1939d5272672e421fc56cc46ff165a328af6d8fe7548eb61a04f571e6e0b9c75bcca0c963b72d3c26459202fb182bd689b8d4db2f5c7a57281d32a10aa19be77bf33a2dd353869ae59c36be1a01482536a8fb47a0131a2a78c3523aadf50e91e07e30d1e19500b3a79771a6bfdacb5e1da89d6ee148cd5483813326b9bea04d66c6e54c5f91838fc9c2ff9cc03e2d043b0641185f0bed7875faa61f7f2c2cb86a9936aab54723f931ba81200516c5b7f2da8bc60fc18e91e9600377b134393c5008481ba1772b7ac49d7277694882540c4b4a742a56dd46fedb6e8f287ab00cdd44bfd3d7f728540f6cf7ffd05b39a315aca3dbcdcf39627b8efbf75336b05c9088a69fece22378573c8fc7543198acd833da5deba0dcfe164992f8621f281177d2b8dce2a72afd6a6f99023409bc14554e2628c612b19ccc3d63e15859c93c1f5a946c8d44e38af4e37c7c21e3370ba7f51092de8a9bfed01764cca7a63e7b7d6d3e250b44186df697e9c856b4602d9abb13d7568f5ea4a55c68ae3a99f1fd8fb2b54456072c81891f80bcf45875fbe3d46143df85581893a8dc68567e4d799dcfaff91e07255d24b09b21665e22611f8f191140b257b639a6bd4fd50d6bf0d99da774cf272e3fd9558dcb62c710206efa7012d8714027683e7a4b411d5e63121ada4dc5016472ba5a4fe93353d922ff283a9e6491dccd734c73b9d74d263cd25623a3adb72999aeb284d94642169d3ee8107668e01c54eef8a4d7620d1cd9c29f9a901f772a4feb67167d7b1244f114614613f748d534b02ab1e56f7b790303328f71f797212e0ee8665098fe244b6e9030778330704412c3411df518dbfa758cc2c38b35ee334d58eaaf4dedd7ac2ba7214cefe0c9643a61125efc002b75310c9ed0a9372b4151f1de85158dacddf6c2b47cf442a6ec5bc12f773f5642f875815ea2aa972efdd46a542e80cdd01e6c2df9aa84c79016076acb27601024341c053110f19721e792bb3b07eea0b41e04f09ecd5f7f9ab1169ac3784236ebefcb06e251a874597106e3f4218cd32f6ae4b61d2c51407d8fdf830c0418f8a74cb0be094a308723bec4b1cfcc49c661436dd4c4a49edf9cbb9a881f7089f4bd68f30b05d9762720e7398cddf59573203d6d1cbed0c338272c3b535b7feee29b6d3072df9c5a972dbff526511196039842953262e219cae32f5f1dc8ebb1110e9c9ecbb90bce637a0444c6229b096a98b4359ee40a53bf8a7c6729f3bed8f8e21d43782f2850b72515f452822ce36ee2cb36efeff6acfda89219ac67a14adf6c4daf534e1e7b72908a1498f2e2eac09f55ad48ddc90f278740cab2af9e1e1125a356946d3dcc0723b20d5e383977dd45c095ddc429c2e829c990419aded86b1f5e7072e3389e716f69d10d93983bb22ca15f62f07673a981075a9fcb08a0b262befa3dc014fdc2b5a8b7c69871a4556b40272ee1002be5bd28324ae6ee49454ff063846c82bef72a88674a47cb5f4bbedd34a996eb2312f6afe9c13f82e2b374d677f7cca71611ff9f7c0e9ba93b9307e6cf6f0cf258df71c94fe34e25052e87b60ac5658d0e47268a28d928681aafb5846756194880365b5f9243672ce4c42ebf555c01bef067255d9b6848062bd20cc56e672bdaa00d39d9347337a149490773d11e2ee343e72211a2eb4b7ade1b064494a34d677fefbece448cc50bc41b889f978a7e5713872c70da0209a6bf249ed5699618e8f207c55c5083fcdcacd5be81d3950730b4172b77a04677586f43b8200f7883640d22e686e47dc001b16b3c5a4e00d3360ea68acff83347140ef59ffeabc2c90f4c0c0427f8a259027c616984af00dd7108872ce6ca6a41206aab06f9e22709f5e38679c132e2ca4e2c24bc9cda0363fbfa81d095773a27885773cf5e7ad68e0605e2399bfe5c7723a2cfcb279df8aaccc5b551155bfb1458bac6244f72593b8ec52619b2ad1313ad12005c0e71239e4544c725b3e7bfec839d2f9b16d49a65ed03552ebbd6b12ea5219f61dfa407f2ec1cc726954cf0f970944d4c672c594b8f5ff65ad315a811a380c41d78a5a6d86810b723245da5187399b62199d2e1d3210a8d0c5f55f6f5132bca60c6f755c49880572de91fd8096435c3ce0f4a79d52ca929883e380123f72bdb07c8a6bf3a29fc0729c1f1e7205952908631a3e818321a8e7903602c58e57189c206012b3472ee719e0f7068e3c9aa87e61aa16e97716d615387f6e846f3ad15c1928098375bbaf4506de22ff4ec9c2280c99bd2b5bc7fbf92ff4c8c5e34ee8f4eb938fa58a5d5c72b94420de92c7f37d65ef1be5b2879965565be8d0b095f14e94b7d33e7cb0a172d593e573150b53fc24065ce703589dea98b3367afed1d8994bfd0f6809ff7a7222c00c8730be7ce9c5a236a8dbba5cc2665275e477f0a2cbbdd98d4a1615e172eba081eb1bdb1bd434f611380f07d6773ff9b7abdf316b5af80fbf9810d79f7293b074eeb153a739a998b66d43409833e9d55ed9f7bd1fab4ce989239028bb72acf88bcf792677d1c81d880d9fa3ee97966a906e9bda630e80db4b9c429c9a0b25ad3a2301eee8865244fba8975549945de6ca6d57207780f491d6150ed2fe72fa3f159a41d0be0fbb863d712670d349b5952f8b03cf6dfc42b99de7c3657471ae3630ae27c55727d4ae38c860914a55c261f29217011566c3a9810aea50fe7218556e543e6c8a563e6e90be80cff7ce48a5d92c81ac5bfb53a083f78663d7726f9ac0facb2dfcef6845cf42205681a956a02cf7856ee39c89c0149d867a0b2e11e7c796cc307550e694a4b7a4f9eca1bddbee0e7e9f3611bff7f48d7fa3c7729ec4567a7dc654d1a2eeedb58e857fc391587472d9aa3f825a26f29ac8697166dc83dba40ce8ff9fb05e0d422ea3688eab43d776b2c9c0b338269050d434b072c0d1d922b52465bc7ee4ec4a13d60ec35c8d37cbba4e4fc7b15e545db903627236748bbc53b7c4dab7865177740199bcbe696c6360f67a5598116ebf1ad4131e35b0e2f12447d7e00372a0123c73ac41a2da89242c284fdd2c57d416840502723dab545b383e19029d3a7bc297c9b816e181456f28b334709fe4ebe8c6754e7246dd83d4b554a4aef81b1190909457fff32d96251f36405cee9a8044fccc0272b1cde48c3ec12106a643cf51c441b5dcf43b6339090f246e04c9325ba88a2e40df60c76c874eb1479a5baf7209ff31063ff0bd4fcea6b9cb9497a7b951426972da69fd2a8109dfaf01b68f41eb47678597fcbba00b62431fe8ee8da0a5e57872d07d2dcdea476eabbc68aeff5840e415133f671acae218136f9c695af9d3dc72ecf06fc274e382e5852e6a5d14427b026e88e9995903f7ad6a75456c6af8a6390c08e98fad514fbce1d0658ecaa993c4bcd45684f3d531a23396652b9f4c6d1950346112028e8b959e877ae17ee0e7c15baf4b4c2df111ea934858e3674d2e72762360a01540737d6f60ad3cb6a769192d85db6a28b370fe9946246ecd62cc72f0f9330d6fa9c8ddbe4d352b9e0eb5c99b01cbb6c643c258c6778621d0e6ea7298354b2e995209969dfd650fae05b076781220eedae58b927ec1882b44ca142d5be65349c1154fa34704fac2e715811388caee3e3e4e585bb43bfbb06d6a2972ed27c9b0b91a568b13ecf5ccb7899e4c8ed0934bcc95c73cda740c532dac0372d5027893acd4293fbb2d92283867fe458bc6431567e16be44fef93fd8a20bb724a340469e2306325a225c4687e98ad608ced6331e56b75e7839243735db1b50d1ee39357ae5085c6fc2873aa8cb74337602a48d01d22a867739a21f00a87a072d525fb017e27d272b8bbe19bc7474705c1a8e17b8d3c452e513f5f82a781cc72c312d1246690d9b7aa03c10ece7645dc30a95d6ccbaf92e5fe41249fbceaec723dd900bf379e37d23cdf2bc6155b5534b5de0162cd45a79fe6d01e2fd0739d72494632e531c0bdf36a6be5f6353d195a7c8ac0455772e20d9aafa4c0288daf724ad2a40544573a12816525c350cbc6150ed1254764cf6338978b0167b37bf0726ecbf33c85b3d560fb37a06fe9f0a9aa7efbdc594b6e93d29e98bc9722b1d972bc0d809c2635317996709e25c32634ffa496b889525bd4b8dfaaf13af27205729ddc2ee26e52312ecc35dcfd78ff94b32c1f0fdcd0886c52832606f52344ac1c18bbfd4807e9d863f53e4e338be367581b15bbb5d43d5e50532fcd8545b447729128633abe83dd1911193aa26a2e60960c0c1998a0b626e39a8e4293053ee334feaea5f331cf5ca9fb7a0c00dca3390c21b56edcef55dd33d7753b4d8e65727272c539aaedf210288fc1deeabb1330ac74318dc5db9cb04aeec525210338cd72b196ca7334639dd1338ebb2b4d40b18e3163be976dc3926d2c5bf528076157656818031b61015e7d2849cb63c5628ddf29f58d2e03c8754838298b356be545609484a4f3a3c038dc68885c0e12f833f84e3f639f48efc525f7c7888cd1e14c72cd32a38e5d8795fbde6ba85c82a4f0fad2cec5da45d5bb132370c42062278c33f1dff17a38f458b074f7da67cfa76a0d5863eac330ca06adb8459a3851bc9c60407e1fc4216d301d7490f40ffed36f2b59c796bc365ac255a477201bf31f6418c4267c4468a425d2e2ccd280dbf5e9699a12981501f329ea6dc6b8fd89e46d722dc891afec783d411626f19d463d19e30995e2f07617541fe47611a150f827672631ede82fac61a5b1f26f83dcd20617d3b1abf3121600d2c236b0c16f155772a160836f1557a813a0c110be5295d84e6b37118451aa70bd32ba21135e506972eca2cf85b0590070bdcf8604a06def59cc0ec86242fc6c9750459896f8c950724e08e5fd2ff907181ad1a6c32b8efffa08c7eccab48f088818666f0c8606b3387cfadee1fecea74229467a2bd1ef44ace471f47ff413f1dbb7f91b2a6873fa72585738a2cc838df5a872a73085b95383131978c4393e317623b1e0e3f656d472c965b58f9b7b17ddbe52b3f957a1f3d2c72951edc347db4feaa2217999532b72eb529eb3976ecdc5a09e7857ae963b627c53f40bec1252f4b269cb559487d752b091e8bd2dc4543436500eb493782f7531096fcc3ab01649b97fbf76f5b6dc72748e851de527a1e63521e33e9cd5369d1d374ca82c83df67e36b07cf0105f72f3f85efd5d9b19a2d3a71ddc79c1c4c79ef2d90c4e70fe014f664b201c9553f726e12193066f64fd2dbeab7ec39373398303dc3b052534fdf1699757c69802834ea6d3786ca2dbf1c26dad18391d7bc9c7beaaf1ccd8bf6f57ae5a76bc2fb7772bc73ec79476b14510826e22943463876521538525d4515d1cb3fc2138c023649ca3e05dedda0ef87d596510d23885dc0b19e84a82e6d1c49c4d2bb23bd9d6a2443c5d54299c77f5ce56eb9261eec264d9669c8d23d808f28ceadc901e2d4e872f8b3cd1630ef887cfb2d7d4934daace3217f4ddc7c2c6b3613443bf825d4383b3bf458c51066537ca3d7391248d9cc8d68ff202ad24558e67284c0d8636f9602859f69dc033dcbebd91d6a95991c924cf156772fe978db3bbf32a152404eec2d4b095c4b8b2da5450ecba11567cd3dafbfb28161715e7c12430de49a4fe8af723448c22222a5fd19c7cc3ac208ecf0d7f19ed2fc061beaf21e020e735cb7392b57b5370ada2182af4f287709294df6a8e7b01e522c7fd9186d81e3baec61a9722b037cc14bfcf3f20d8322b0152769740ee264005bf9e500162c9a259a617e7211d727c733c0301c71c6f0254a06e2917371a02a6070b81440f58d105b9599729638c0da2c59a89615668e2a95cf9f95cc15a156f3718b6afa54daf446fcdf47bf1a70726ec1429cacf1bba08ca4de5240101720d1604843b423ede8b4714d7271ea9c2b0536b45e08e6856ff5654426d6c3a1f260c1f531c7ef4bccdfbcfb53cbb9478930ec9a27c5c155ee18cb892817b344a870f0383bfdaa73c877c1240d4518697bcc0957b637507accb8f0278e29f86e47b37a1ac19695deb9c53cfd0a720ec4aeeea8225656581084ddcc263ff49148efffa364879372dd0392d5d27288c189e05a5bcd06279cda3bd29f2f89f86a98bd3ee4b8d1f219541496134f0f901ec8bfb27bb5a09065327fa022070708e2d880c42705025b67e4c62e6300729fd150d811f72cecc2801c617861bb833a5523e642e6e577f9887a314629036024ebb4d35353982dbad8c064f8212e146390dfe803f5de385bd661fdfce28507cc16cd8bb071c6ebe093557d2ee07f135653ed33507c2a6932e2aa532d7216239cb6249ed4dadae6a93c1fff14e6addcc42e52285e0daf54f8f12f60415a707216df1fa64c6230d02e105542ae1b8cf8495e7ed461360d984cdce9ecfc1a3a4a712699c4d701bf51a93fa29bf959ba26f8d35f0bad404d16f59cd7ba9efa9544069a62f29672f2a69fd9ec876966a2ae8d56be71a75fa63b44215bed71fda15eb9ef0feaca906b16ff75f425a643ad3207ff7830839d0b147ac14fab6ee9c51a93c9208e063a25fb4a41fb0c184cadd46041db7f4c689a48365adbf3f60b6b72deedaf14dbbba31e053e8ae2c6805539c3d7f5de1f88b4f9e45b634c0bd914721c850df499b7550380b4d6c266151491a48599cdb51ebdc5e7ecd368c5a34a099e544535bcde1d30615c87aab245482f22accea836afa4ebeea6668e63fcfe7226f64539be7a59ecbe8d896cdcb63b021c5688823f073c32b08e8e53235cfe72a818e790564366c35ec8d5ec7b0c5a80d3f66b1dee6afcbc29215a28ed2df6723f0bc288f4dd285924aca8439b411a893bab258a52f3f5d83732d3f894747672b1cb061820e9e0bd57b38302a7a5d93c036266c87dfab4c2731d5fce51592f54d087fcd02e9c0ba209f8be8f5af657cded62daf2ffd8f17dabdef2daf31614721409fbfe4cc23ae0ec92fca641db1279a953d8bc5204d9fa87f1f5564bd2bf7213168ba070c331340a8b4b359dcc4830835f11198b64b9e1527b4f075b64a1160fe467f7c21dd71b8458df3bb9a77731f0f151b5f6021e4b632af90b5b975c6ecb9c47e40beac8f99a6ac08927f0db9db567ce703745b746ed98c329afc8907263b8546cebaf8ddc7b7bf315a11c2ec8c8ce379217b8c0572d3c61a561abb97264777b2e4286377093b81371610d39a4e1de5fc60ba538ac3cad61e865b40044295ee78b97408194ac93a2d8c84cf2eb33510c61f737861484977a2bf7f0100bf96305cc6d5572dcafddf884c5ac436c7b661a7ba6add5b7c464440ca13fcf720b1e83c22062ce33425d250f3f93c66d788d13e4e42d02d7636eaf2def24cc721def095269f027bb6d8d8983de0a903e892c4696afdf8acad43ab7a88bdb4f13fc138a91d36df8e6d1540d26f1dd4a0f82176000fc4afe46a844a1754bb7ee72104331496d531d6602c9e8bd17db3ea5f96260514af77496f606f1ed1737772c18afcccfd1abb0261ef9757eca762ec6d273c33d932000db07f51dbf2641604703107942b4d1aa0305d42c5b22d7e9ead778fe39f4d8effbe1ff3a81439f113a8939e6f12375256f5b0871df9f668793851b0bddd47c338bc9f289dfe42f2e72fb584543c5f8b14300ea1665a7d4de3af5c72ae7ce9698d76b296dc44022a27237b2e66855330daf112c2cb4dd21a1a180d3f22c404a37fcab0416b9b56f8e2c42c29f2b11a5902c20830c45f2ca80b1d1098b7ec0eef90b495f8bdd58f49e7227b3fc5f8638d70e15b0e658d3bfc3d7478c9bda1ab95361efd3c2c20917a0723c77c6890697e235699c800379e1cc969f9b3628e16995775763c5fdfcc8227298389bc3fa2ea3c38963461d9b5b319dc9e183d05b3a31f54dbe10628de060725f28bd7dff0d0c165b476c1eba7892a6124689aec3cc4d1c25b60e629f4cd83368e7d275a808c08d32dabc36ae459d620539ec9cb8565d5b5cc6e74e1905984c26f47368ad68013ed1e9d2a750b7470b7e3431cd5ca49dcfc7c642897b1bcf5eced5a0ae3da2d5e31c5746ca40898dc53e21dc32bcbd91e5ec1ca32c64d163728a291352f7390570bc344818637a2c1becba6d3aff56a52e26ae9f4ed42f3a72959c047011360a3ef2fa3e7d7fc8aa395b60f1da78f0b8d5c753905fef68193b29a77d501a2a54aa73f78ca7a0a33333a8c918cdfe8db9acb1c55103c6d80072d8aba331cba5df4fc81b41f686c90e2e70d336cc7d224f105dd009bc60c24f72495c2d2192f0415618cc863d59a4d8a36e64adde02ee4bc26d258b65beb31072f05acee74358264d7556ef8e4eb52b30a53646d625e87ad3ade83f23eeb13d223e292b353b6961eb99ae9c38733c80648f85f86eb0db607b146f9d3c1f6e517252b13484abcea7ab0f32dc36c7a3726f08245a81087a743e6cf9ca0ec379135664efdae046b3f611f9b90e766fbd124f09b52a95ee37ab0750800d0a7a440d369b687873d359fb41f41f9a91b2bb751b119598be35800fe382ff0e268e1f7e40b8643b91e3fbd48a1bf21bb0a6f8cbad050eb042ac58ba716377361596a44172bb8ab655f7be8a067dbc2fe75614e6fc34a9e57bd7d47eb4626be1a1e6543172715dd7dd6a83286f7a3892d28bbcb6dbf149f06caeed30b6cf4e3485a1fe3e723373c7c28a7d625284a495e47d4de6ca81617210feeec2bd421936c5fc3667646b079c58eb755b1e9235662cc5494f5c52fe86600d506b6bf5b18fb924334672f1be5c6ada8199bd4e5d8a874894aa07a43ab0cd8210c2ba49e30652e12f0e721119f9bb8a7cef8f0a0aa7daad41d28d90ff220e756534447577a824273d18723c34dc6eaa0640d2fdde8c485301a6ef24707de6f1d56022c00eca69cdb73e7215ca378cf3a76959ded53d00168c355b775309399082702d28565390099c9a67f7d087eea764f38acbbb08437d505344af4a6f5e4931e25e311dde13eb8d3472a9cc6cdb96b4ca1a1071132387c064ff75aefa97ad4942a1976c36fe3896b772236b58633dd71ced2b4e0fe2e382e46c8b76b883298b496713d09fb621898237747dac22a035cfe5a22268623f3c6997cbf6abda38e4f3ba60d62aa52126514d09501f25226f1399c29e145cdf9434288fc587bfe8e7cd062316e017e4bd93193aea8624cddff118e1bafaf2a75a821528883c3d11b5f4e8c9fa6395dbd6207217d7dc13a403667cece445ff194c5c9823ba98fc8c01fa83473d7e74ae7726724210cea5fa7c3f1b3735d9c0cdb8aff6f95413b8f397aacff860e1fa2a92964f1a4d5c2d278ca7d47455d2c0396d499344580324a1ac64f03393d28952d74972e322bdc4e31648543c1bf3889cfbab695ccb5c07bc652e2a50b07c02a8da2f28a94a00ade1e2d6bc2969f0dfe48d26c25354d29b11349ea5539949abf6dbee7232131bb2fb465246c80a6f7b3f0e010622dc1f9d6c9ebc33f5ab7f17d334f36b423ef1175d4418051900af561af6b091d8acc64b37424c95784cea89a123da724455d3f21bd33ab76ca61b8f6ffd7986bb30928af54df43e497b941cad37434c2c5be46cc63bf698b6dcfe8876771a8fe073a747024b5afba2e06c7b7791cc7209f16366c220dbfea00e2c899bd71a13dbb1ff0ed8112d9b420c6b48f7fb7172ae6ac0c8b70974e521a740465b11b6c3f3407e81427c17564b84704e6654a772d188325108747491c78290b1111b4de75a70160d3010a11b9baeef98a85f06723b7221a1147193779e65894aab979ca098007f65c13bacdca0b0d5ce1066c45a4a859c1e4d03b320723368157b61ec97e5143fba7f6e64113d42f090019547726e9c9e42e9b639ac2c5a6bdb24a75de9762a068b4ae457f8b2f478d2bff10a7212ec2ce31969785887d7990f01214fab25b7fc4fe232e2aa4534fd00f47b2b6b4710574b964e13f23583c93c8f5a7ec406cb07131ec53bc592c68896b47d45727386341adacb6e7dd7660f625b892a8da939a430f79a83a8b03ca4d8a6699709ac2292bfe5e4c2d687ac03efb2050523c350714f1e2f11599a1dd2799dfb8d6b1feea1c497f5f88a42393f255a22543e1d9b365599ba99da4f316da627214172b8bf00646f789fcb7df941efcc1da4445151f3a468f8579ce1bd4f2f8ccd9572d7f3efe41ce4d71e4880a4b3aaa220211b060ed5678275a28cb5dda65f177172ab4c0c566719512482c107ce925c547c284b85abf53fe67253e9b53dd694696f4022ac3b6d5a06baf29fc604a5356ee6250ff74d12f254bdee0248fb4d618f2696bb6aae1d16f11de006cf093fbb36a1f61c58cf9cf2c41d9070bd3974cfe34cf36f085cd9187aea891b1aadde09de5c5d3024507af9034deb2072898b257a72f1131eae0d8a39294967bc5c0df499a1c504d38b0976ba197c02a8275ac64072c2b22404c21fc220ff07e35efee5dbc6dd895fc6b617ad21c43b750987c521610d76269abf17b7202b5ac832b38a4edca18b143a2cf624eb4f8212a1a34de872124534d2f5c1f28ef23eb378bab730b1fc213c8545d68c07922090d0b4c9d2722c6afab38fa8027a20d219689b3a0cb9d538e0acd8136fb842e1655aaad5aa72cd9dc2928698445934579bc13aa9e5f6e6818e4ffa4bcfb04d35121797421372dcb379a23fa6e8f06191d0c8bc6a41c3cb439a0d10b88dff31e3f00cd7c3dd31904e14ebf22374d2efd76deb008081f63c296b990f9ed342d682227719027b08bce8d63ed47bd3c3c707179604ff7eedcee51b798193eb3e1020f985590c1f1123eefc63c599700aaee688020f61ed14dd6b6b4b25f5db592371c1cc6b5a937203076751e3bab24d5f142088f03268a55da34fd4105d231fd3a35f22ec55e6723cc62b033589d06f8bd458963035830a4b79cc2417c20dbcd81dbe1fc8a16272a532c8921f4568b943bbc33bf52e45b21efb5172d7be43c251ed782d4cf7e449a6108ca5dc4824eb077604e4023c46e7c46945057f11f867a549c1ccd3ff0d1bf0a9ce196ded696a624c1d1da761acb76789930afd2a012f5a200b2cd8f270199a23be3f0ebec3aea1c0287dcba384220f9af124fb9c327d172a9f908faf650c2376f31ca35fb0859d1bea89836ca5720d476f42b0d366f88ecc44d2ecf4fd4f9ec9c4f7dd9cc4c9951741cb3849097b0ed8fd74c57141a2d24f0b1d9a25f74add8dd5d40ab421d0835c5392e1aeaa7f819ad3cd6a4753d9f22ddebbabbd22724b43ee60c4d209cf6585f5f64cdc9b1bb13540e9d6a2a7ee5860d84d3f850d728c63cc82f5bece624f4064539a1e7d08d81e9406367563be535e68062e43052e5d778cf3057a24df0fb462f739a2469f730f0cb9e8d9479dee1a078f1c2dab5d0b63e8e812bd0a67e6da1fc057622f6251378b1ae66499499bff2ad88362f940429521ae1ec7e144e804022da1d099ac45a3f48a7a6e937ecbb41006144263720ec2e55b7c1d3a87fe2bb90d33de45d2c712ddd88fb24e68aa7a750d6ad70272514f7f6e7ae07caa23d45620521122292defdc9c0cf411238210706f69084f3d9aa59a2c073374d6472543776904bb1293eb6f2d67de58f2293db85a387417720ca12e0f69d869b51b96ea3014ba36b36f938c36154a054096116f70278c2672225aa21b150ca6af343f400d92f30f9d23916a5fcd396aecfaa647990f4a807276bc7cbaa8f96992ed9a19f70f4af4253dbb5a0d42ef68702b8d6673bd774a72140ad2661fd9cc51de002c4d642e2be2ad4f6d646cf456e288adae25fea564721b47a709361be00d210fa109934a975ad6e0f1243c4117f1bb70dcc431ae3e7282c492f5d323016b431576d80569efe1e07e64ce2765a7369eddf79225bf43486e04121969c5d754e164bcfcd4ca623aa4dde88ae2c829cffe9718ecce2e882f4615df398c1ffeb90d0c4d3cc5b64816d35e6ed1fb04ce41ac0b8557f40658728380da80971aa50cbe666f0cf50bbe5bcc451214c7ddb5c517e755e5ff66b572ca109f2b7c2813b5987bbd4ee6b4ec9e4ee0c25920e7d748c3455f995a653f729653ada64b6cd28db79149091ac6d314d431d03ce7ba6d0586c5f7d8572409723229c9c76f3ea845e235d265e221600b6777a76c2263219753f0d09b0f0bec72323e7f09123eee12996bab3c687d73aa54af6b898d4fe349cbc04927c8ba4672ae90b8f235aface445ce408c869902bcdd09d9c18a1e90ae968ab95bbcc22e72390c4d932bcc9e1eaf6c2874ef871fbff3bcd3c7a1920ae32bfc5de81f841b4e2d78962b508625386c3d1ff2215f230a768dfb58928a7b358484afedd711434226c4b11e87f2e1aab31d4da465b95b0e4f7a18aef3905f280aa2f10e30e0cc07a30f05adaf31701d6f9886e09aa1830a77f836dcda65c1bf320276fdb290582e966abba121cc73be8165aa14c74480fd6c9d000f6b66183659da6af16ab0fc5667a7a24084f79fee61af2ac29253c47f1921cf915e915ded18025842b336a200b0f4d240fdbf97b90a8b87bf9d6bed4a30bf870379d348a69a4f6b6e03f30c25890d308158b924b3025f7140f27665edf919d13942c379124a15621b3c766a721c0bfa7fa7ad79f5959efd97e0de2423cbb52f39ac0693a50785673c5289bc5e6f4b22dc8a54cdad9ee28d265e919e1e22565aad71be1c88a1eb818e9652f072d0ffca880ca649c1d7b80511def6f29e3e9dce961722b175c738175d6dde9072d6455af79808ee5b82711faf3f7b2c76400cb660171341b6f1933e43eb6b4b72ad2308780b48ca96c5d98cbfabf3506c1ad8bb8016017e56edef3e6cb6fc83587a9e35309fe2307a0c1faad74fe206b59c4019b061f5328fd011ae0938e5e53a11b9a66b9bc32bd0e94f54b04c378568817d92de61c55bec3cc8ee735b808a6436913719aa84c6a6c61b829a83ef463e51b72edfe53ddca9d274c063b0ecac72d7c64108ecbf75d979103abc0e2023bd024a8d41cf126e05f1a19f58115b061a07a49a6abab0b85ec0679c99ee77017ca5aa95c5036e81c513f5662c45995a35dbc69cf43c41abaf9c419adc235997a87d5ab45290caa2b83693d65d0c1b4d728c19ba48ff00af1fb2abb21d085b6a03985fe153233e60190bec5599bc386551f83cfa524cd9195a15bb95df343aa9420af670ccb1c1d59df3fa0c94fc5723241a0ba83cf5450f76d3f1a262994b3e50eed4fada35537c31533dc60463494972f0b999f8a7f0d19b08c4e975d83169e72de565bcd40cebb20acdbd766b8b6a72ac0979b094d69e18b0f3c9d863bc5306785e91bfe6173ddf87997c2559231c7285934924a67cf09e10f0ed5de1e57fc29830d73feb9776268c1603373b927863b8215215fd7d6e0ee390dd844f680417d575686f9321782c9b3b67989478fe3b77b2008976ac133553896048bf1c99b8021286aa12857d05fa6a855558a9d472df364fea35bb4d0d5ead0ca9bed6c2de1faf6fac9231dd2e64a382b6f8df84232fe8eaeacfd25b1a1a7cfa9012eec4562a1fc5b3279b9c4c8a79b45d22b4291449de5473c1ea56ba2f67398f433826a0e146d761f5f1bcc2b5a19ce345be8672fad23a6c9a776a4b4940dedf70c121991321209d3b63871c944514b55e198e72b7bd3f5b3fb7892473bb74ed06920ce212699a6d2861ced4c85ef3af44e5ee7243381fbdfbff195fb0a0a7bff70b10f1851e851760682b5fedf25cf96199da7295b92896aefa39f87aa6e1f7b9502098b79967e7ecf60234298310f0a3f36f394ce3051cb4e6877f226e40d8c804d82fa5ecd8ce4a8b3437f5eec9cd5148860dcc8b9ab6ee40442f0b8e71f20800a93c8b6f8dff4b80571db1c4a95316cfe837aaec66b61632a834c249f3be7144769d5c2a3d769487c6172b0ccafdad197572ba39ff639e65cd0f520f2771128fc3ea1899c6cd3de499a8c61db7a52e849f45bd11ead05dcb90991a4364dbc90abccd23c2aca7e10526fc657a9664f1a20272afff954b9805c55dc913b89f2cc1c7f4b946651849547616dd11209ff394131f3768a55e6d5eab760734369703839572a4d7abaa2ebc354c2f8e2070b56ddd10869e46c2955cb76923a7f482bd1460ea0b43e0ac82dccbe0dc9820300a0e24724eee52d1531829f401d933422e25004cb504045d5bf412c2d941ec1a5b9ea4103d89a3a648f443209cfd0c6bf52c0c70ee2433ca76aab5b2e881a31aa38adb5736c72a32ded2d8135de8416f925fb2009a34f6a4263503b888569abc90b66716dafe841bf6b0559ffc63e6993d175f810858c8ef1ca0d0f0fa5fc082a401a2633aa8d96226f07680c4f65501b9ddf3ab75b2eb645cdac6643ad3c8b8838fca723e744689efc7c9887e6a0f507ccf99ac102cd46afbabc816ecefcfa785449406efc95d46e5a6f6eb9bb53b6e1f636ef5918afc16b1216455a919d5454be98d7284d830695383453440ffea924f6f8fefdde699d0a277d3149ee09efef3bd4a178709d80f373aade8bb305fcc58e1c3f5d197903976d7dda68a229f6d44ab896117d6dcadfd1c209f8d4b06bfcbd8cc2d2d201fce255c7c32e7d315a632e775724ccf87197275b210089b383e8547d9f0d04cb041fdadc83241a12f2d15d83f7211a8f72dbe6f1f9fe3e1f82566588414f3e0d70d37626017c13c6a98aa1d84724bffd090c21e0f65f494a35d98365a67007253bcba9a2bca130988d4cacc7b726238060e0ac11a0538de651ea2f29529589258b7f27b032873cc74e08ce4e53ef536fc0d7de95f8f62f3b000f81481ea9ab3fb3b74988c40ddef0d79a501ae72d8b21bbe77e52f790bf58b9cfc4a6fcbc608fd4cc6bcf8f33733ab3bae56fd7218f201357e4ce24decb202324bdc7f4436179e399a8f4cc8ea79c9624932c572c417148532b1d06fc3118edd89d1462ed4861f92cb705dd1a1c750e4093a397273beaaeb08f73a9a0a994c961b267cbc1c73ef387348698c71ad4611b0d0a572b1d4e49d26609ffe69bc8f7be44ee2f4473b9d959e575bba67f500e64bb7a46adcccf360b97ec165d95c591382466054e50d7555aca40dc76e9c40f1559c8d404c3a8ca23cf539ae9541ba7f8e76b5346d3f653b8e1b432cb8f725bcde013272d63aa2bddcffd578fd142a2ce96864f8cce0bc5ecb4183156acaa896ce746f7236997b181bdd70a67638da4c8263937543ef8c1bc6795bf64c9c87e8bdbf6672f41984b1180b96b1e52c2330a441aaea6115e37f459d85529897e4d22600d9726eeb806d4aa93160e965b5b27ba30bbcb8649954a6182fa94139ba9d80553372ffe2ae96584cdb6dbaf48650257b6f0001d89ed9afe084abd468e2a4c95b83720322ac0a6f0ecf7c22a62f0a64ff0fa004d8325b534f435e4cc9dfe204c1897280bd80bfd96eb1523a202c60fe0a6c1536b4be4085d5dce0626586e3693e8572db5624b23fdceb418cf9ccb434a081628c91025e45e8960f3aaa67d307c1df092a4ce4cd9df5c82e16dbc67dc18c4740207fefb697c9961e325331e87959807229ee25eee04e052e3edf8e9c05a2d9268a0618a90fec23c294032c5c44558d7204b6ff0f82a926787bb8b4d9dc19bc9425d529267260a95c212b89a5cfa3ec72888f5041caa9c635c35a9555d48215c7d273083f3869c28350ed184ce8958b72fcaaf839de1a8fec103fa58fc9d32ea220dceb9ca61e05238ee54024d0700f7204806de013e57b4a9064ae47d41eb99b2ff702d64e9d1da0d1e37db96301787228be396409d1762bd94a3f00f3fe503844b6ff16846bb308e91114326da3ea72894c5c3276345208593ba9b139e619810b6cdcab292bda98e6e7a7934d3937727ab71d76e277039e1e53d72b2a7d5b80effdbbbc50ab882387ce9324b60d20726ef79cef6c789a4ab7f3d1e7eca3bc405fd43752ab773c322bdbd55606da32317e4f08cdbc6f7cfc4db9ab488dff98e9411f1a88ece31529fbc1c117898399721d36d960bbd6872b0eae828dd5476c2e706d516a595eefa4d66e715ebb7c247271fec1ef633d817930d415b8159a94080acdb13cdc57152f92c4716913d5e54a01a90cb665a4ec0aba5e65f5b96403ef4e9797aec98a2b5f37f4f400994078726aea07a263596c7de53a06d1466d2c9ffa6aea511b32e3503e333e9737a4db2109cd2de3a4c9f5819f174f79dbca44713d9850f7ffd7cc90b04c9c507bc2de7230704df4a7b7e26d4ca08d7cb66ecf36f2d54bb9322a2fa54bce6689e4809c72d2c65a0dc01f39ebd586453c9a0dd2bd6858b3be57b0f74526a431267679df151f5ff8d7cccdf8cab90d14a6b28cf3a03254ee8086d28af77cef5b64f22857680616072e3edb26032eb373f3e8acbf5adab038c96dde6b8dbc8a5fb2aba36372f5166e25799a0e5fded092074cc9b56ed28277f898c19f0ab40d3f9621c06572f1091950adcb30a0ba8655d2c6b079e1ca7ce7ab79d05395b9374a676b0cc57202f40a7252c08d1ef977886909cf271fdf5559aeeb7ab11629b0e59dfe8fcf72aaa3d86d244d98c24e98805d65d7fdc776f5ff1585999d43cd75e4db47b5b37220cba56089d0276315757e7e22eb41cbbd9253a0a3f5e60d83717b096f0d5c725838bd358bde14052390ec88a33fc474ed826a856e360cc0b953347d9880f8721fda85eb9848201619af4037bd7401c3ead42235a5cd22fe3b049d0e9c646472640076032db4f41ab82252859d02abc2373dcee7c53eaaaebbff1d0921e1c472656954338a4d23b86b8efbc12668d2fb1b919d82df9b656cd37efd88d3629e72374c750518917d879d7e62f620bce7c80b25c124940b095b825e6dedd1ab1c72d9def962ebc7fb6aa4424a599069b9ed543126597dbda0c652b2abd172f5d072929a331006dd388081656a0c5465b1a0ead26bb8a54aeaa2809da13db19811077ed409d429864cf677ce6d0ed48668f62c181ccfec1baeb5a7cdb55bdedff97222c7e25aa418bc5060f990b589e5ab7c9f68847cd322cdc365e7e2b1c6944e5593a6a13b3a9daf4059a6da96f6d4619b9134e27eee48d5486a4eb33250ab466e53bc5ead4102da65370d6adc622b88da05598c3cf254e15abca905179c1d7472b63e74a8ee4570afb484efc6b406d68988e998c35a98647447e7e52fdcd36058bd0a681ce12375374f1506b880f8ec1ff05df8913ca9df36b66796a71329624697d286d8b8c49cae2e5615e31c7fdecea84bb05a9d272d3d645ffa8effab4772b95bcb04a6502f41c17c6216f3e6f1a05fe8d3708cf910c7022596733538a131d68e45d11269c179579c55a82e65e2d2f5384ddf69861e5095daa4d2c9f30412587bb5f0b98c3d18352a84944108fb56cf5ba03aacc684dd8c2b29d257d9014750d5bc465d82143f6d5dd2df92dbe65b7109cd05f6705d2d2fa7c463a1805638aed6934832a0fd4209a449c064058fa3532198078785a579a934e2daec586c72478d3cb3bdf12c57593dbb0cfb91d682df8637230775cc8119eb20f424e37f3ee21ea9e1386d0773d8fce2ab19e88b19b87adf4ff7c7d9806e7b9902d816e8725d3a53d5cf4e1d14c3c691706fa4e3ab13fa13474997203daa5ee86059e46130786868e04bca0c7644da3c5248fc483a269837049c71f8f383a550cd4702ac52e9d9e4d918718414e3f7212ef5ede5c8573868a1b483bb36d391e7af0fd104723c40a793f800a311126171dcac32be070f4415b159dd2c0fbc859d2d09638d644b43674e66f6ddfe54b8dfadd83216e72b764c3ad8a5e53e3beeedbf69c6ec6c57fba2e89a350bbb6f16e8f070b1ac79e4c5f1cc21568d2dbd7380994eb6cc71a76eac7ed6904d1e089964913b5ede39b3e9973ec20c9146da953d819b6950035f537488d5420ea733f7887fcb6582524ccf52dbeb16c01d9163dd4efe235372c132e3bcc7176fd4b551ef91bbb44dc6cd0293c5e56b3f41243f76b73da6b045ccbfeee4455798e614febd874a6b31cca45d7fe83afffabfb97323aecb3e2c7211bf8566a149a60d3cd34f44b89cad3f486ca1adda16dfa96a6219b42bf9a7721d9f9200994d1ac66eb7eb5d4dc805c435e23aece0548486f24c76d7bbcb1a7229e6a724720522bff3463e7ec664ee71afdb59317a60d39d827cf05e4aacc772f31bc8b6c40a88265c3c4ba49118340eeb3ded3c3cda38d45ab9056f86641172734c64c1f386b6e05f08d345d5452c22a64636d942b7fe753e539bcbcaebd772a2251dbd3254a8c2dc53a0ce449fa4151251d08d097a49bc83ab30976b001c722e9ea648b351e1dfee81c73aca83d0a4f5b9b081df3500e5c1938bc35baf1403e5f99f5c769be25549c5da76168d54ebce5ed86fbe2d34ba8fa0b985bc8893526fb72bf88d15d995320e05fbe4aa77ee706ec7362cdee58a2f259a5e6181af72e48946861cebc5f839f9c98b1e4f8da1f35bcab319eb7dc73968f4b4756f9f72803601f370391d4962899aedfd9755e402f2ae4bf09758eae9ea0075c0dc7512b3db6ad4b07bc32482da3bdb768368d4e56d02a462bfb19dfdea238af7bf401842d4e5e1971b5114e43e5f602441be40809004e8c49672c4f13f120a9579af7250db27a2e2d3f2ac004b5d6ea84a78c253fa064ddc4e6982f81df125b04783491e67cd5dd1961d8c4326bc0a5893b19dfdb752d6c5c2dc63dac1d00399d2730c57f5f517b55d893df6da3615d8658686fe7be89b29aedf4caf735f369ef8af71edca8c60c3e32fe816610f3efe856867bd50aee5b0f7c0d30a7b5ef450335f535348a09f1aa1c366e2648d01308a19e0f2f4881f5a5b2f495fa0d4e2456ef5724c0d29f98d419dc631b707eb8484edd3eb18d277c6e0c64488766fc27e878f72d40c79dddcdf282c37a222f392125466b6bce309ae2f5342e7b15b09137ff072128f83c5af7290ec752cd511c8994e268798f42f8bffc462ccf54bf330e9f37262f190d4389e05e92858da0e5f2a7f35c323da017f51e64a2325e1b7154cc6720ee4d351b19241a6ea845aadca5fa3a200ee4c2f599b004e2ec1c7911fce7672e28593158411e7f6cedd7b83d813a0e2959e7e104db84469f4a2b9da508f80725cbdb5ed2ed4eb0c1aebb5ac9fdde189011e578cd2bae2d3e3ef6b76d9db7772f9db578de8275bbd8135953e6b29ab1a27cb05f516e2b4c164c3bfba63b8746a31f2aafe6f462835313057efa23f707da8c56bccc8951fa78e4c201e0268c772c31739020d1584f54639b496232ec51a4ae9053cf2786e53acf842257f95fd7263ee591b4987d0f0e8472902c76c9a9791db1822d5e35e85f869ee91217ca872c369310b6baf55bf996cacf8b57efdd830d88c5512c8ff7281c6cbf965697e1bab9c6d47c62e17fe3f91334519dcb94a93b67cae675097210ac46d558e2101729c53037674f54943299aa1c33c716a97d6c86886e8e134250bceb38c9d2a372c508581eb750c0d989130d5d4bd28317c2035c722ed9b9f4a8cb8ffc20599ae3be46a293b4b71f57ade13829cc01597904962fa0ec0b7af0d3ecd720824247272ab0e95eed8911ce7f812f4b5f7efb5fc981516213bc211f8830de4f330295b69187fecee2ba697e3c70850ec1deddf69ea72b8d5c4b751d0a4dc7785c258be72a3b91dc179a04f1921027145b9e27ef15b85d1679ecf95751d8242894eec74498e3bd88981f25a5c2a4c3d2ce01420b841968b4a147c26496e519e9c67bf27714b66219953a3c113645def8403d1b1713e4dc3f2551677bd033aca05f5c79e19fff68cf6493f731da7350aa0d21882c0258ee175819f7bdd17398869c791914ec1c1ace76631fddbf0df592f2741e01961cfd7b8fa9a0c257c226a80d45a2506a7bc274f6e78cfc01b3a3ab1c1f9d54400f5971b6718e0ee39e38f63799eba72a63af99556da2973de815a96c1a2cf1c759b38f68b7664403b765c9fff5f907217611fcdb2bbd6c45bbdef1fb2c81d502d3073ab39a01b80ce4448cb59f28c72e61fa0c4eb14586304195883936dc96649d1423541ad8165b61607175a8ac47227fdcd7d7eed67a575939851319eaaaa3e6de7589ef315b938abbdec0cf888163cea767586395fc4a9220650ce96c659bf21201146fc40f2bf4919551356be720a716ecfae6d1179f6eb6b079ad38fa3e4c72cde2267a129cd7b2dda0991564afa8d24e0b538f28df2d436d344e2b5d78c14a224a01a4c5a7407f19a32699272b285f98aeb48b3f17605ec0a5d74968d5dc5bca1dbb11c6620f0cd73995db17299af4fbdf8291661b74b10d4900ab54381c0fd2d13335d2fa7e5928af0ce9f7225f1460afc713e262d0c932513c10e64a809b3431983fff2a48cd8f211df5a72262ea327c5b2c243fbfdf6135bbd709dd8fd131ca812494e8da6fcd7b0863872bd4e088f543edbc1c993fcc3ead2c8a7c5b8b1791108541a7ceb4754185c8472c121e142aacf60988b6b9d59acbfa7b040a733ff9bf06d6a6306211d804d00721b964ce77f79e874e0eb8e3c2888d9e860aff6f881ab07cade70fadbb1c07623d67f15e6a37b254039d7f346bf42c854e234518d0598fd20cd02fad02b287e726d4882b98e45398741935354c9afc6ad3be79808938f548506db51e550b15a72cde596d97cde8fdc68744d7be4f3fc6679aceb04373fdd440a941eca0dc106727c19e9b0aabda23a906ad343d1d486264d5a1c4bb494302807734152971465011d8857f83649c11112ce9c731e22b2ad4249352d696b55a354a5b51149e3b77252a10d2a098d391e68b8f516594eaee17a50b27c5f559ceb05840580e318297293a25b6891606a98a74c3ec3e6aa7e5db6e93db7cd4e8d16c1bcbd65f10da172b22c5913b0cb92788a944650f458beaf53c7ecd9d40fb3d55031cfb809f968568ca9bb094c536fd4a079c1b0d2867af2495223d1d02f28bbb705b22498b69972d0cc929162333bf4d0ffc0bb2b4dac102e861acda19bd3e90c81402ffc2f1d726a2e50e30ab8adb4da5d7e276c49b7ff7709798d2d414a68dd4be613d042a672b0fb5ed1e6d0bae92c821ffc0761f824556daa635fb9441b0976c9a68c19e972a023cc7dcf3229889b9b9d7ce9bbc1fb6a08a3331cf00bce8dcfcffae026a0729088bc0539419f8da267fe3e706389ae30058132da0425fb7dc01f6c14e56d72329d9b80c4a2037a01e42977f1a35b0849f3c1d261c694cd5518d72f77273e729216d2ea560f96607c409f8240b8936cf075e9fb7b297fae0bbe2c8dcdd0b5624864f4516fb24a700035387bfe9db1f16516b0878742d8b586db64837b59a65ca1728b1b69e1675a2a9e880e458a42521cf01174e47df842c480fb9805b0ce72dc24ede860656381b7b4c03ef59bd089f39584de3f2b9a51147d769e3737b872ee6a3d08beb6022ab896a9deb8537e1a2ed2bd411e0823ac315390c2b501401e4fec8e943b129d80338b748b0e256d3d20fa1ddaab5e8ac0329df7c3b10ff5098b88792aec84e21efd909124b6c5a310b55fb3f07b0c74c2a6469cee75ab0f72bda1e8805657a0e8623467c48f6a547dfcd183c8cbcda94edd6c9da698403e724c47babeb9e1128ba5d6dd3b120127d39561f52e9251815a7a460641e8255a404fb56c69f369a58497681f689c089ca3e9e1785f6e89669e688db4f50c88d572d2a95e3ae6acda5e87c976c1c8f81ce75d9d2a44f21c049cb5fd3846dbec0616d006df16a00c3f7264e6e0ab34bb25a7f146a59d196483969c61494ff98ef4327123a0d8164250aeaca829226887b26eee69a4f80e631f58463fa9032c79f0722b3e1459a25e60f31c570476be16c38afcf0db223c3ec10b319d3185cc63d35099adf4367f63c9b4c5a1a4e210c7b0999feb2cd5ce3c317bc1b26ea046305472ee8f68f6e0b9d2386b96a2eb1b27dc9ffb0ba4a70dce18e836cb92ec00e4437209e0d2bfd8ad08e07bad44e01ff1535d90e3d526dc84b53d4aac5955bef9aa7281ad4215020b350bd7e5475a5b11a7801533de1edf5baaf5b68a080c020adc5f3140c44549561c21093914f18fa89bf6a3b054aac5b6211afc7bb447f15ea560c183741fe0266f37e036b0734223de7dab1a11533004ef1357f0a274b40f2e5e3273f01bb146b785ef153cbaef4815dce47671fd51811f5d2af3a1bf52abb2724f8d1d5913b57a29bf625978d6163a072d8fb240274a756124664ce447ea4272975a9fc64bf210278dc2d273752b11e8d65d9825a1a4f03bb1987999460cf653781724f743b712bd54dc5e0a709ad4b096919f29b67db116f74eff179535ee72e7ede6f0d8a566491bad15cf408876fa15200e8766a09c59a6eaf52e226e234d734d628115522ce932031706b7d1978315c88a70737be1a335ce0f47fc363c72a3f6f7d88bf37a36db6b39b9a27f3428797462ed9c0008e5c83e48d797bbe77273fd1b1cb2540e6be0c05189d748d1c242cf75dc4a0b9b5240b839ce3c028e3bb2eb8a8cf7a9f8f8f2a209f2cbda73ffd797876357b919d5c9499a55122e4b72fd8a9aeba6022429da6c593b4df2fb41193793144f4894179288303506c9035f2e97274d17646d78abb82eb306874e5d1cc3b58ec82adc18c8119093a7f9f0722efb7021631ce774bca6db47254ce4e801c2f396a0181e7b84d6f3271b9f8f72258cfa2512067d264df02cc8ec3c53843a7cf8f858eb2d95e926d8e458d24572ad8a327405a7248ddbb83ef78ca2a6b999b7aaa907e94e4522478304d491b80319d808e7d515b88ecc566806c44947493afb4ba24bfdd9a3c74beedd2445df7200a5bd24ce7c8d70bd1fdcb71d1fac2ba577b2bbe798befdd26422047bdd3f0e252487cb9f470db4c6a5c4a588c2c8706735165ffbb96ffa59b98a9eb253d9721719f86fb0b3e238b0fe3ceaf8254b2545d48a1f0f2428834227af0121b9db295fde621c35230b9004658362ba1789e237dc5df2e10bcafdbd389a1061933872ab23642a60249367b5bd17172f3d5f7ba49e432cc07904b75ec922f238eac372524ef2dc41e95452fc88b51ef58b5311abffb04c4163446ebdbf6656a520637223f40dbd82dca70bf265ac3a26792da434434d5ec4d1e4d4b701c643e25fa15226b861c3368feeada3181ef2ff98381f630d95f34b4e7b5410c7d59411b60172c89840899c25d3ad59a164f8bd80eb664a4075d6f73d1a8117cc9bb6bb3a1f41fcf4be02162eb1fce2bc2d384c74719c0bbacf7f6b7b0709b953b355ea7ddf12c86bcf2caa42df5a4b64b9d047473abdaf83ac340893d5c468b64887e64c3b1f834f6102fcaf5a99b0741f6f97c54056d170cd125d2d8f09797fa87d84b70272ca7338e736093a7abcae3fe61fee9aa4d2b7423f109a2a7ae4000e72eb5b8e7293937b6d623d6b103a683c8438c1ab130536b55e19950da0711f346167d02c72cd58065492db7b04e9c89254c9b0c0d765b0c9c414a203bd0f1ae54b93bce272fdbb60b690e6800d9a4f0f3e967b86eff39b5ddcb73ed7d65f51dfd4f0c43627c11d132365c758d849fdd236cc6efe0566630fcbc199ed4505606f82ba3b6b72c080b3434a87a849d8d723125fcf523970132fbf1ca132fa4b20287f88146f0577b8db39e67e460058e84d45e3cc55e5c69f11e3498a0446316150ac4105090e945566fb7a8e5c5376c58932f15e07491718b7da07a0a11302a833a56f3940728bed701f39844cb98ed52df8660596bf97b31a2b08d32e662f7089d1ea48560573317ec0baef818bac7db8bf3bcff4b1e921184310a5f251b4a768cafd4c8572c2456a3f6946197afc4b9c2aab9d2cc998271dc2f0978662f65e27e2a8981e72b943fc63b3e7dcaf97992b3eb525c2e189f0fcc4510a3bdaca9580a116ce4172b0eb471d193e6003ae7de4f38738ce682bd5d552c301beb83541c3545b5f86722c977bba9e33b9f23ae1891d383947aa990aafdcbdb01db2f455110a57ad29722d94d07a4fdba5d81994fe790668d2049f2f271d09adcdacbdd6791b80edee72d4f7ae2f3ee3d06b88e01bde33e30a857a19560683f209ab3df74761b4e7aa5a0aa38985fbbc1274a6cba801d22a915ad6b07e8d79bec513aeca1135edb48772b60b276ebaff8421660e1ca02336fd576aff9c61c80c10ef3bcda819eb986f139e6f558b67c50f216e843f896777ffeb3e2244f5486ba218f03a608543c36072132137f0c525b58a65a90afd9d8df5163b27dbeb408c996f967cf34f09edcf72adc9c3987f59dd5dcd6f7284680f6ba028e027a57c7c5bb1981ac1686250f7721ecad5ce960e5432e1735055eb07486015539448e91922fecf9718b12c4acd72a0bc69b41336dc71b13582ca6118a40cf8bfb7266fa4289b4ee745a82eb8050bfc56a9695753f561daca1edb203fd04ae2318915832e61abda1cf4143f17b97203c2c2ac0ca9081bb898a3b4315d8d08c1a66b0fdb4ec317a8d9eeb36b798d722fbf0a2ac944b93516de161acd98cb98fc49ae669035ea521cf2214cd10d7872998fe452efa488558710f5edddf920e8fb7af779d3c72ddc3b14e2bc90cdab59bda18b432682d67ceb5478bc3b6f94256a9f265835d2d5457959581df1e6d77280359a496e616cc21ed88f1816e031c2ad870aa8c968aee99e4f2759d8cb536dff4af0eb6868d81d67a138f9afe57faf748bc9c7232cca3ca50c409437a309726a39edf55b62cb334ea1e370e4a5b34934476269b9f0b605d4f1520fc84fab6038b06a963bc6c100fcfa90e9605087390fd1afda201e5834b5791207f5b57a7290bd297bbffedfff24cbeaf1fc9cd4b89e890e1ae9289b19dd0f0c85e3bafb72b03d05122b20c82b7a501f8db182c089deb121d943c112b4210ae80485f705726e4c272df0639f5ca57eebe67cff5d0d5b1e1f4723174710e262e133d499b472a52e44779bf77ccad84883333cac5d532df3f3863797277f529a3a58a48d9a72d4ecd91f5b07f3849525e3e328cbc8f0441422f1e32c4456cce68f8cb554eb3f9d35222a98109a3386ff828dd1dfa86b17f6e411b01c49ea942a0ae5c2884c720b6be7d2498cce2fba1f93871f5e0012a62dff3399da8ee33df0626608b58d72131df7808ab0c3e601ee9bc4b53006598325cb83ca6ebf5ae8c7b0305cbbe832c4686340c853b511f8f2cb20f21551f68302bb16c9baa356d22e5ab4df919444bec0a1be0eb5b36ebcd35004a41cc00a1226c2782eab7b5b01081bb17ceb551a8f8c346b505c16847f3fae34f257d09f6f840e0a1bdb16a022f206859ab8b472eb2c33223d28aeecbb08dedf7be695761f60e4f15d6fd2a3332cc5baff97da72e7c19c95887d45a7a452f0ac65f92d266c86e0757893c29b65018e1c9293df72eac82436ef7d4cad2f8c480f2f1f66342c2b1ed69d4eb13c8e82957d16d610729241cb2f7439886dea1ea2f866b66caf915791a0854a2537e1fa1f164d915a72cb024da4b77098672279647fc820c35db2d2051d8c846d733733a29e8688da72b174603933770486fcb564627b85c5182de1c0b7d43f08b51827285b63380261dc9ec395fe2c9e86d39e9731da9a7ea78bb766355ae7b971e0b79be8472524541c5c48b5d36930731157789425143343bcf683eea60701588ca130a61e0e5f66e98e2444de06dbda56a7e1c953a38f327d710c1b84f489efc24bb80ca5f22915d27fd2b5cfb9126ecddab65b9120439822dbdbe462dab758dbe9bdab5d67c6148b9031a02c1d2d99cbf52d6595648e3541bccb61bf61ab0f6f09bfde906df45ed7fbcc981c96d87b31ce9d289ce67bba47f605d142e36c29ab6cf2b3c5828a72cae5404c4435cfa9532062fcb9fb00ecdd7c2baa64b2bf92e486730733739472531d41e04b08a8a3cec0b9929130c9255e591b2f8c254a6f47082884a41dec2392f550c01779eda2f4dc6bf05d282f371a8b74573670f561a9e3e72691475d055be3c775ef47449970587df913c91636817d59681068db8767e8a92985865772ab71f8d9b5a623ac6995156d68327dbaa1f54ba4b483226ff8137f43578a9a72ef7e9b0eb7fa4f3018508a79c3853d3fc727576d0f1cf7f254dc4c4589adc97246106ce15addbc51c016ddafa71dc99913252cc65f23e5cd5b99a85b1178ea725924a859b181eacaf19f2e89ce63d182ee03dc03228baf045fa2a31cc53ec07276ae9ba08923ba0db8f51b867388ab4c826b9eab035b4931da154be7d84152727e906b182e59bb55b41d8f39d9bcb7be12abcea2eaef111c93be9401c6e5df617ffc22e4bf310428684c91e1c0a6d5cce2f081f6802d2bc00977ab5fe2e0ac72fac2fb358ec2beeb64acc38dbfac83b215aafe545ee57a365854ba90571296727a15659b811e5bb3cab9cbea3c4678c0e29f4132b1cbab8fb68028bf81433b7251448172d5a9fc20a96062830e827725fd73322559cb77819d50260b86db9e726672c60ef9d63d78163d337c331dc9d3098019c3d853a70a890cb49e324feb7220a342a04b134703a99959e669075fedfb6ab55999baecf77b121544e6607a628a7f07abef41a2ef4cab8c424780fe688a33662953365df02f93a53e07df36226a4777070e662bcaba3d32dae81d22f4b382879d54d00f0d52ebb7d90e3fb60eec52bbddef40121c540b6c5dde9c2f42d19a0504f1b3d22934b9c1966d9e1403a088f1f37d739a94a5de8fb63bdd37892bf9212e190e6ad6bb38e38d4f2114210677e40e7fedc026f6147b920cb5557d4679fda11f03a3ed86119fc920b65472e0501fb817cc985c71f22e7654c172f7f75c30c13bf0f9ab272f61d925c67e721a889754a85528c1728143c7675d2bb4d6682fa6d5453e7baf8855540585560b7e34869126c46c2d3ac5494524230634644ac913f097bc9894859062561eea39c6723cc1c958395e909dcb0927ac68d187cb156b784091cb047f534bbb73732d8411b1db22b848eb693b68bb64faf7f5c8f95affe2bda12916022bb16597ac72ba36385e528a855515382d1b2ddd93680a21e05093286a8d4fa4d3d8ba696605cea32d98c615d102c4e249f325cf68a14743604b2a79897fd610f4712de43e3cd120385899493b070d13333b644d1df77ce091dc33321b72f74c54a532cf5272cd64aa54f8688c8ff1b3b59c62c3a10253158dbee6a5d147fd65a37e60a15672132d8540df74b2d7b0b131d8c7f48692f881f063fc74fa44589037e5f9fa39721c488779faabfeb81072960edef7486008116946bbe75620bd269d5aad99874bf21b53fac2c6496b58d18631de92c07eff36c767568d1a6c98840a0766201d3c45785b372c6630605cf4084f7b038229f1e9b2b825b187bf45db1a56ffc52b7282e08f4ee150c0e732f82cbf7b85f59bea52a5456799a06425d79656e89ff569777182815d7436bee8d33bd967595b8bc867afdeff4cfbb6d856e35b8cee9e7073fb10e112667ace1293c32c168021f43e830b44311dad761b71dd33ee61d106f37b7822fc23a5e6342afdbb586386376b172a4ac54ade13027a28f8b7ef5372031b33b1d62abb38eb849eb3cecc538917c127f21788b9cd99601f39ce264572fec6f8e2b098c3f725e2bcd8efe1b1df70533f23b36895da6a2e54b12593f272dd3927828144495cf8ff8f6b6817515e7a323b5934355e89b69c4c33f90ca3722f11e71c0f2c1438fff22d755049bb70fd8d73c17b50562f34a2a0b2493022095dc0540311fedc52919a615783417aa9cafe30e187d3683c49ea73f183a53d176b259c9137f69e958b196569cd1ddf67a1e90531a16fc0780c4a43fdcf1d3e72d563e74684b8aa51ba23acd34b8f41b7f7261d5bffc57a790c1cbbaecb6ff672d2200ecacdceeae5f98884be9cb6922d8e3b7f81d037a7a53dabce5219363072e0c24b42c0392a94f59d3ca18a86377802dc0f9a861bb0eb08431089622df45cfbd4f8a3399cc632ffb0eb2f1523e946f8d0dfee0cc82d9a04c904b84a6931725204c6abb062792e9ba970a69aae9deca11b2a02e82ac4554dde7980567173201f8d402bf1e2c146c0ea3be3dc102380dba6622724862235860a8e5f1a4671196a161873770d9bc644171f5b91f66cf029f39f061da3fbf799dd55a0f53d4972fe26e50d3ec77f43c12a3358c75f2cce4e47e2534f1d692bb6b33a65b5246d5e9959487e09edca01a796d793b541e562d1a87d7353358792f9e61dab9025ce1b3e72af5f6ea23e3990a11c8eff6b89bc4dc0f53b6ddcc2540762760b98a1e03bf70c346a5e063e8520d74365096188ececbd3b55245c5989c526d898780e53720dd938e2fa1a1258a5a121ff9fc6a1747172c65ee65206d587c90fec8f3db972cc536426ba9380b579b007bc12cac147e9cce5af3ed8f9d65139996c17030a72cda702a86a438eb6a1a6556e4d705d978b91335ff9e19c0fdd913c02a16b3d203f0aff7b0431376b5ee77ab7dd75110ec651fdb0d88f037ae402aa259732fe1926c5e872ca4efab41f44a818bb020f865b6a2ab266ea6715d21d0ccc3c05066e79baad436879f5717e6fa5d23b7be82263b63ea29f87028dfb3f55e816d06f72127cb491dc75a9ada2cf7e9b1e4d146ecb33ffb7d15bbc4489732499af5a20721486ed63e6e2812323c11b3a218b47f6bf611c597295b917bf0ad276d8cb841167acd1f65657e7516881ad19a61082fc64556c548dbfa0e2126f6f61f21c7172e15568658c09e4c3e13019f38185d78abacdff0e34c9c8d12f407618fc3241493c061c465a5b73ceab853cdd964689f3d5014891441c392df2e9f735f98f27723be81120a568c0ccedb8bfba279e6187e04622c43d93a7b51954d2701805ec728777e7e71050c5ba569f6ba179c299cf6d7c0d63305d8fa5bbb0fada0f2651727475c196d2b32b8c0b69fe2983150bbc9599b218dba5da1721f97d85de2d3d0d66f3c8dbd7459728dea7e77b8be51f6e9dded0bcc685e87a0b1a5b1f312bb272b9786d6b86edd9de873fab56f07edf7bd20d9e70254bcef1456c706216a817449d032d98fb1be8d44186404ed9020efa2e575c724c2c62a8b7b5ce97774cb01627a6eeb083c434fe6ea3b9a524283aa9da1c702d0197ef51b89ed7383d217d72aa8e5fbab327cfd550b144abcc057252fbbf12442bb127a06dba5b238468997227e907cdd3789f0a0753c42b0905e1ae030fbc0c2c6c0d44512256c8f792de13da1837ecb5248cd508cce92260aebb9a5eac1e80d99c48ab0586bf8edc4bed54ef36a158b7cbf64854931997f71871946233b53f010ab08e1911fd248218ef72c57b6b4400878d721d81d35640d60770498ebef10494926eb2d7430cb6446772ce47a32caafa3edbf00d64ce3b2e0701668fbe1c2fc41021aad299224eaef44e8c6cde4af92d681356b3de5c08482ce817cbbe82bb5cd41ed9e2d15e83f8a272bc5a9bb6cd4574d2b4323125f3c0ad41649f659280f4748dd8a1d559ab00e74afd8529c2995ebce9c142cc5c10a6441f7f7114ce749811d350c2d986b5b3357295ff845c77c1f341b04649f957158d8717304d611c7e8a32cc3bc9022defb672247eacbdf1bb1537410d2a8f578582891ac4920a362d921520cd8d9688104572e41aac81ab7e80f21d34001aa1526ceffa6f8cca8ef46c23c47d6ca4e72cfc71ee51744ded4e62137c239614517201458b2646e13a0fd84dff59e64c383c044705f1039eb7faaa3b369d9c68229f576ad8e81519cbf6c053afa15e6f15e1c07241ca6e8350fb61311300162389462651748a17b46cb14dde5baa7a662ec69e5b4da751ee917773350d2fffe5dad89df728c5e827f0be38eab19dd6d7aa9fa3725308d3bac3394ea951e4a9e7ebae8c000906fa24351fa52fe24bd45514ac7c07fcbf3a71f7c7428672e46b366c9be8a11a69fbfe80e2ae17c839c50ee5b8bf72148e0f1723cc54a198fb8988028abc7de39c2024ecd871b0235a76088d86b2724c14aa7c660a23600c28937f09abf2ecccacbe9da10c255633ebf855a0b78972323f2b290cca37f63e05458d11fdbd5f6f3d39e4682b12bdf24ede69e7a5d3728e60f6bee7af5167e22d3ab7ba89e6b3c68afdeda6237c6ee1577e3aa4aaec72091dee5530879a4e4063bed41e2234a69e80b1e90792e025e0603147b374ad0a7fa363b487c3b86c69cf3186ae32c069a5d7477eec6f2ba86637b8787acbf372a3a77eaac476b71296768f673612143da693228c11dcc483d19c30deb2da4907f1667ddcc5af5d4abc263485d6ebc94d991688dcb53615148a7f841c63141a7223da35a8722b5c7a015ed2bf940e1108acf6aaad012cd26953bf626038541f72fcb0d45770b6e2717d473ad51a5e222765ff2edee906eb7db0d1cc5c420fa76776bc6b48a4a9c6f585971f7212b8320441e8062f7337077d296b187c3f4e6a33ff4f782187f8add9fa328a7e3bfeba1c219fa48bbd290e2bb8300c05dd19f910af978b4f44e57bafeb4c6199558fcc4f53fdacf3f5ddf50fd1d6d07bbb911e70d05484fd0ff96641b1d814eedbcf6ca6c6ed314f00b9c191081198a3f9cb8972b1f1847c08e633dcb5605b6f1ecc8346cb635190523d773916fe3428f35fb51bf20da946ac9cb50dcee20d4709782210f3d59777e60db521835bb4b8df3d8e295310bb4b8186e1e262bc927973d121b7471e4cdb2d0bfb90aeb8d7f45c657d5292c7c7df925cef5f080fef7bd31eb03d7e14a5257f7d92681c944ce5134d02724d77d0c77db60890e4f4eca63979edd485351d21c93642f1a0d7ff482b7821347edb488dbed833f15e049cdc3c214916437370935800197fd53ebba358054e09db02d19962dea54d791c68a2ec507a9a7466d2af8988d913ccdc8fb8a80f393f741640274c4126ee5e137386844ddc31081be9ff2e1a5d653ed5f834735c057218da04975ba2fab419afce35c491153f5bc1fddf55d73e1db94199456ea81e2bdd4b1f4c1dfa5ff854340eea3a5186cbd16346f45d1287da4ea328878c874972cf502deb5a53251fe9fcd27f16ad28a6c57b26c41b757c9061e7846c051ab472a4cb972d2c285ad46323be6ab7b60c18378134fbc098318c748ae9c4bb73cd725277a3bfe3e366ba45ff1b062d16822d83ecabadcbfb6c690f38e6491076fe309d04eda0ea11bd1a0ff0eee506e77a503452c599b93898c88804ffc6b77f9172fc456eda4315c353815b2033832dcf9630ab2b2e3182a9990746194769f6cf6aee5eeb356d5a62bcbd33515b87a4732bd796583a4529f4544a106008c43e5b72052ab9a3ac008c59a9509c1afc2b11816dc47393482087b8a750a3665f494f1d0b54543109d6b9f3913c5fad81ce99f9f6451d2a4ab65ccec8b8094616d22972c611081b24360ecb81faf4773412e5dc8c21c34bd9aebbf04485c475e9a1a60b6b5a477e95d61fbc32eb6dba6a9640980389ff00f6208ca86cebdde5ab8a4f7240b58d541426d9a5e228261f53fe10d504ef0acdd8fd4f679346c91aafe35c50c12331719d30a482a47820527b7c5b6015adb6adfa212136216b40955ddecc728c6a0e49702a354d9cd5ef92af56fbc13f8e54e8537e1c0e82c01a423bea17722e0bf473e00ff803ce1fdf47565c26d6bbc5ca1ab3976f1a56155e37d98a4472630a8613b609f338f5b878d929d21de848e0495f7a0cc6717bba478aa3a3c2727a6d0feb5967945038c037db3f083492e5cee2decc2b9cc382470f0e88fead727daeff9ae6652dec1586b390568d411ce006404d10e4b403defbced6cbe51a317995025e62d934b240aec032ca2c996064447ce826cf5b12c977d233cf53334edfe80589a2ccd70bfd9486787c71eb86f70cb6c6a1b9528b085ad837dcefcc72db13b32dee3db7b91beda99bd5678c5790ef3f53cc163edf2ed9ce74948f4e72ba3e1ddfc751a89210e61aa12c457929261d55414e12221f69851229baa4bb72cbc5e66a9d3067760ff010ba9a0d7b210cf04017a86a13bbd67706028d159e59bc221a807122e0f38c8218816987f17cbdaa231ce59cd4cb50d280fdd4ae767210430a55697d69604208950a772dc467737079b5fd0d8d359059fe28cc907e60eb0972e94d035540949648956069ac242b4275c3b2e0ca64419295a844804272b18eb8b7ef833ab875ff4e50e139af0657106d36082929357e7a99bfe164ab72615ba38ebdd1031ad2e43cb046bfb31cd9b39a3af6b84a1a259da417672b8f09380b50eb636cfccfa34028f401e0f21e6aac2ac883ba8eb2b1b7413b2be0ac729d7c39422a0cef5206cc70bf370e13e8bff9a5fdc31c3992aadf9641090238728f8c96946018affc1953b4fe3d7a96408a9a7e29260e891d3fd8ab3772c5bf2da3595a4a160f4c21d59feb3bcb0ea9324928099daf364031cb37dfbb1fee8c727a244c775160c2770ab134261d7241980c13fea88d7804a1d1c31536587806720283f9c19f7e3c521ef57fa9971ddfd4db7fbf103b14b9812278920e71d50f723ff9c534574e698677038b1b24bcc9be2c92084c69dd80ad1ab67b7cc207f96e90f216fc5254d2533f7721c771979be21f7a7c4a6cab41e0468bbde51598ae7255f51fcdac2d3477ec566cfbcf3789f1ea54ec3e8039da72aec29afee991a96d22413cccc1b174965196fcd75d8f1de74250a6460c76f2513fd33fcf5748e1729f123f77c9561e204c426b9dfbf0db12ffe76d6102720b4a0a17a7679cbcdc72fd37546e5260ac0f1f486a7ef8fef783c4bcad9e767f6a34cc642a882198fb0bf5eb567b374e9c212529ab88f71d1ea1e3ae6b37ca4c392d7e7b70d8a2b4105475ad751afbf53a7b71025c326ee89d9cf4b43d7c77243043b3857fe3c3744b7220770673c3d2d4e0057f9615bb36e6fcd1d51d9693eac963a9fa41e4006e2702745f0d486f127a171eae0c218731797d33f148f9d835d7d66f413091728d9b72ebf20fbe06564526995d2868be7c5af449fa6c32602dc3f629538f4387dd8b444474101ad5fe7903decb134d939c2958a009df11da5e2b52f771dd1cc2b3be72b29fb3d8dccdc12eb103840b8f35dc8e5ad3976b030a10c2ba6b0fd283cec2723d00e418be8525a12fe85b21bb766f91ce9e9c9dd8fff1a008a89aba342e4772b9e49fb6016a63153430145255c32697366e363122b7b8c4e3e5a7335b6b82727149ad9eac5fddbf03a344eff39adfa56ad9171097b70bdfbf4fd3061621e9728312b6e96a0bc38fc96657e4d9c7221fc519f311bd219681c24f41468bcfce720f3eb870c2f5698921946332e54b9d94a7e368f939ae8e9cc88d52407888f7054a1ca6c3c1645e7ab77281b4c1eff0fe4883bc873dfb8021806ffe011f6fef7293502a0a46d680ea480d3ec197135723730faba38e08ed5cc3604ed9222e3b169d958c1956af4683240ce1b3ad9ea03653f50e8f959a0116d23dc00538582672df29264d9e5bd4087e3e8b385e6f2c8c7cdc04096d8c38ab8444963b2338b756428eab0a824cb6fce8fa0521123fd450c87fe8df9cfc300170e09cded5d710069396602cd7a53633e5f7ac76cd2db3c5e5116b9d3d4d5f1e4d949c3f5e97342892eb14055187237636c50520a4898ca3ebde99650c8ccdc3fbafb69a12d18a2154eada7d5893280eaf6b8556758e985c5abb4672187e3968cc092a82a01a43721c14705de94225c8b5b502f00eb970c560281c96f4da3d36a6d85f513e91287220b5c337b6e6694a2897d60a38fc258c8201f58daf57321a7d86571fcc5de37282de5754ff0d752b46617e39f249fb18e01facf5655728765838b1f611324d726a32fe4a1bf5126a15895393bef40c9cb1f5db604495d6a98e309084bc5d8572a1ffdbd6762fb462af4a4dfa1ef2826e2e31ae4c765dec95b85ead5e122f47727a5000b95b5938d6457dd294794fac087573c60941227e1efa33d9f468bd9b1beea2493e081255fe3c5f5225bcdf70eab7262db36b29f85c78518dd5068147690c80281a1e5940a1432104b6742f6e7488c5e0d0f44f16752a861e829ec28a72d494d0942f27049811b57f548425780827a8875cd00a37b7a8ddcca5e91c6272771cff05ec190d38cde02212282a36136930bd9427c7e5706eff118e85f2ae6e59b5cace3951be9db44f9e9c4c6b27f58d6f19abb774f37f3ab05a09b5704072cb3cfd7a6de78e77f349ad19f7ba684bbc8c62ae9524f7873b0451baf716c562ca906bb1a687851d6ed7e83e37372dd687bd20bf47df4bf779acc3b7720ee2723712eb393d3fc97cc0a5d334c084aba8ccbb2f04a8b3e7557ac115d96c16d272532df0955e6f183c5538355f5d3534f6dabb2457890519ba8467def6dc1b34725aed90f0fdc94e29d6db7fdffeafc1806c11cac390ff17cd4c9698af0fee6941257d6e6c5bf93a626582fb83c1701139e4ee2ffae934f0a37a4b870d4dd94a72fea95f79bbc9e28212b923562bafa71cd29d0de52d3b2690931819d8b4e0c10243f471306457e9104f95e4394998affbe7dff7026e3c533a85dd8dda9f46982286fbce6fd833cf7f0e278c1dcfe631592fd297d3c1198da76942e0d2bb1a9a7209ddb68a94d0187c5c9390fc8815dfa15b6050d9885fc82d41836ebd410257726179dc565d71334820f23fadb603af2c944ab6ce50beb766e76b7018d4476d72f9e2cdffe364fb85be02a566b0cdb30eeed842fc7231e2023a7724457917d06157c7c19e59df08178102e2b0437785a9264c2f75e2fe907a17f914b03f63b57285b6d3b231b479a4e34641defb3ef01c3330d93c4f5d586204c6b8ce29eb2b72530fda7280a2c85e6ac21d091536a95f9df38639e17dc4a6d071ecc1e0174d724ac77095a07cc652617ac63d9cdd13e90bcc0593cc7f8151a2906ac9f9736752b532e8987cf974afe0ec911eabe28a50dc8a2fac155840ba9eeeda99d119ca72c1979d12ec74b5f950d23fc0b9bea0962ccf276a0baf6cdc193fe63900c73347105120699808d30c95a5709a8bed511ed2c2bdea637db1ec0a04b5f1d868c572a8d8ee433ce8538cd8bdd2018e9f40b69d427f94f024143818f5d0b87b50ec72170c347d261859720264da474b6fea34fcd601b5d1470196ffcbf4e3d17eff48cc14dc09bf0dc4f0983bf8db8e720b2bb5337d0f5e314d10d09eaff907a31605fe98932e2f23b55f3d7118e86346e511a61ef8274fd93644c3b0bdce9bf7b95496a2211ca06b904d7a1176e3c797244f10a646dfe70e95bf8ca611f9d34aca10c0d0b5c418b55345d76ef6eb50338aafa9f96cc87ce85fd64ac8617a3ef9d851064dbb8403d9f6299a516225180472609d71e5180ed5c08fb5026728f367c572a3719ed00ace61fc974eb0310870b748342275146e35aad442d1b030996c2d7287275859a72505d39177371aab89a16786fa983318967cfba691bde73702bd72544102bac24b5d01e26ad54789446b254fc3edd390bbc4913ad6309e8e0e4b3a8450b3287d1586cbc70d4d5b9dab6fa7140e006546550e51b7d16fe20bdf8072f7fc090dc421eabdfa682433469ff885f564548caa8079743acd7a0dbe7e2e7216786cf13dbaa4ebd91f59af9d54009216d1c773be3bb682f0d973ee5c1ae672c3368c0f45b05c9390926374116e3ca0a9eaac7c2806ba2bda14b2044561f272cba47edbc88d6a3c0b9036e8f80f7ccfc956bc4180456c2c11f86403f6fcee728a00eeefe810375bc3ce2a024e4e1f8fae58ddb1a39d745e541efb61548cf54a9d9d7703c34b7ca838dbceabb1dab8c197bc1d48675ac9d95ed9976327c4e161132032e751b9491edb8b537999c1ac33fa0398fda654b0c9adafa88c9ccded01f49fb244f2a451360e9cfd88ce347ff393527b3870e51c7e7fdec0abf940ea4fe0d5530c46db3d320a2e790748eab5e2ed3af5c798263625762b145e51a8aa7243aba506875b2ac73272418b24868fadc6d88e78855c891158c82677f3010737bdc925824b6c180ed90b896b2cae075e30ccd4d6e62982b541c58898f27f7272f6ebaf94fccf2acb2fd05fb5d068ba5adf4126ba6b151c8b7119f50c1ecd3272874a0dd19038c5487756f28a19d13a3b935f486e4f3b431fecdc912c7c5cfd728b441c3e3fde84ed6fec0f290cb91c13481c26be88bbc2b05a1f68f39953dc5acb1b0e26a2f55772292d75e5dff9dbc9f9e8b8c8453edc51bc97fbe197088b077a17f33f7a4abcf3be0c5b4da0a94976c5ea0a971758222002da4d946fb821098a96a04ac4a519fe9f16d7c07c455c5f4f62226a0e8f22df99aa151409ea41513dc3b5722d3cc989d42033e3cdd128b61bce8fd685b2f5ce7335c7aa59cca56d637c1b8fa0fce39db197ec8c2b8af148becbf7b2764a9007c01dbe6ca61e2d6343000bcced9095f34f2c69e2ff5e27e8198d003419998e21c21c27b967f1db72bdd94fbb5cf4e01a36f2a17dcc9536ff9a5053850aa28f1d5b98016c23d4437295666c6c9df925250d63ca1a633b709c3c041339d1a2776c8961685e39c4311a151c18df673117b4d17068e52f76f9c2c5262f4f34d5d3c489aed598899bcc50f979b416a7d470ade003cf2f0fb0abd0f74d5da5c081f79d9c93d61095e9fe72fe4dcb11c9881aaeb44f73275ac87203678851e764df48bc1adfa092d4109e1b6f9dc7cfbdad1f0322cb82960ff58887187a672f07b12720b21a49e96abdf972f7f8bfbd37fe836fee75559dc996aa33895b6ff0c9501f1860d03c56be3317379db258dae682b315998e02c4e86f3694462946937dc1aeb4604a796634278072aa65a99da20cc93414a48043c66fded668a86cb249982488da387bc022971a2d039ab6c3cd973a89cc2bce02255ce58f94b71fa973fefb5030a4352e87c6b350193d08123a0f2bab0bc795495a5408411f288bb69632fa2f3ec952a5d3fd467292ceb856bdf45cbe4e1df4d150b2ff8e0d49cb2cc33d0fefa3d12c89d232ed29e9ef82a72e82917d5cdfc9388cf3bf32823d9b0c947c4ad73c1a2fa8776882714eca29b41271be886c905b5c02c4fd7cd08d304f4b7063b263f1b5d196541a723c6fd4f145df9f08ee70c7fd34e78c4bfcc265722757f60ea886b1b4bfc42472ed0144b7a9ddc98df54e94bbe70051fa753b34a92b39d3c66668bdbd3d7c9672c796af2792eb8dfb48a1f39547473ac5c3f8a5f5ce3a1f97f2552a1f65b70370c873671d7bc642bdb67b6bc78dd0a950815785a538cc515edaf3b2068f871b7226f5712b1a8d9af9a66d32086fc9f9673bd4a2cb9d32e4fcbd94faf0bf2b8a5821b4e57c16196128ae16d209cdb23fe4bcb50bdce1130da84e797e6fe6eb570d08f2071f649b3a6cf81f60b76e9531cea8e5142b236ef4a7cc98bc2843c9ce72e0bc2307cf58a0c128fc8931a8e7cfa6acd619e8dcd9af94b241f2b77c126c727bebf53f5573557421313169fe1e200b5bcc82f9b5653194e506ae7480b88f4dcfcea3137ac355be58f20245cde37c3468bf34633db2d57efda3fa8a8486c972e07f3a63b9ab210124252cdbbcd69c4f742e80a093f71ef385fff88d2636d040d801514179f10b95437113f7339012155000c5d85b4d6c903973b56b498d980264625bafbd2079e902357b42130176cffed1240443ba90034f60c51535e99325f0792a4b0d4700d66df815479f215f7f6b448157378ae8e60caba24aae85037258686b6eaa7766461ccb83757e3f050a151458b34d1d091045bd39bff9379972347f0d14d59206171337b0013ca6c06496f7b76e3d6ab17c32c5370c6636e9721b43f6aed5012eaf6a33b5451aeda5b3787ae9db7aa0be30c79a6d10440e7a72feb9d44dc41002d2103014cfa3c0dee8f1e7dd183abf1a4b8f96eb3383ddaf724416c8721d4191009a385c99609cdcdb5983b69d332755baffd1d66b6dd78c488f12826f45c64a8298f19a0eb30ff62e17649a5121663db7961f5d3a7bbade545ff70ac3dd9adc420bd5783e0d81deedc25cc1ffd97891262329a97780ef033c5a3e8fdb3c5cfaf0b6b8980af263fc6e4733977fcfd132d881cfabd831690657bf53db1d55809931b96582162e69943842cc00ac15d5fd8806dd93969761485096eee998531120b64c61554f018718edcbbc6e71f51cefa4873b02fc72be091f9b1efdd8e863a05e84399ca51aea8ec28b83d3981e36e0e1972c3e5b209470262f5969788520ea0601539fe523d0b65ea33533f886a2141896c03d6feb3b234919b39fc1571ba80ec585e55e3dc72eb4d42659c831456376ab636596df47fc727c93b54bc97df75467666d10cd1fbc9c076208a0e1f5d75c20e272005ade5e7299349600b4c30782f51f237ccfdf322e1723d7596c2c3fac0c3ed94944f9e2725d187d11c586db8701ef779e83451be8fc5385853c267e9cfea9e56c8b098c1dc61a29734fbbb0e44c03da1d943739efe757fdc1ec47de890bf1220301716142c066666ae50363bea16ac906eac7c046656d8a8fe35f2f48d2fe534a86a48172ea3810535aa3d35a1062139757fd5a21a4a948c933c979537999d1c0f234294f14947c0209588e0bc28db8427dcd0a1b75ab973c83030161709c4234fb2a2472f438e2feb8e32cdd749270e4e30fe68f40905aa0be03bf547540ad9adda58531ce09eb494beb1c72e49d17343d37e0376815d522b6f7eb326da3a75669ef81726f65c92f3b54635ca744d5b1ddf737bea5709e0192c706326dc55c171a529672656eeb7e4accaee5fd557657e50f3e262775d70fed7067f64a815190d2050c0e1d6db2965cf8d617fdc94f61b66c359eca821a625c7329062e130d137b7b1867a0bfa1318ee38581e66a1b316ab6400298a55837e358ce27b6f6d67d101281721cd546afb31f86c83baf2d7af2f4617952ee9f0557d64c54068fa02aaa512e72db4e789b9469359e562427609de5d6574956656a34245fb1d8093195006f1572ffc21712af4ab966f13a4575a0dd5ff25a8abd825b8416aad52a42911a1b2b6ae4ca2e68716f2e619ed5eb5fd23dd3b28331189a8a6df21033ab4b4126efc1070c18524fff5e460e6a3940f0c4b07be8417b60b3be9d398edb0139cf97618f72d79708995262fae5578407f0cf6f54816c7376ff82d62abf75f93154e3fa6e181f7951d6834b984dfb54dafec49442a40857b3e51ccdde17a8c110ff0d9a2072c230dadd009250b4d67055a6c43c85dfe0b00f5247812c7371c2cafa39bb1c7279472c3ffeecf266dd827db2d1a5289b3ea1bd2380bda47e511ead546ca8ec319a56eb96265e7a0adb920f86e03bef472eeeacb2c94aa23fa2d793568485ae71384e244d172b8fdeee99c71020f6a6d5457ee4a8229308022358c80c241f6a0f3ac64a0d85ac0dc84546492df415237a59a0a92b83032ec78624779fe40caf06845f3a1921df01d1e0384e55eb00b02eeaa9071b2e868a0af3ad45250c7d1b4d9c188f037cdfa1d148a144038d894a14eca600f29dce21968e5c5aa3f6e5cf72de9f50605ab5c4426d04d810df607f759f16fa6a4a9f47792df1384975e73672e3d62d44d02229963455085e91043cebd5252dfda761839abc31403e743df8536aacad3b4a83aa3032f18a03c13e1ede926d6b177c6dac57b2b97e02385ec32b7f698640cc767ef3783671e2da395b475f0ee588b0b2f73e33a3485d0473170eeab861e4c93080ff4163d5a3bc0f89d03f637aff5e9f5a7bc298054db4081a7236436720500244deee01c486e9f33ab415bdaaa96735e2263953941d227a8c306f53227d23e27a48669b884b64890a811a1cff7d36fa437a695cbd8dbb811272ff57019b9310643db60d81422eb893ab9caa51049eaba3b18a95dcd71a7bf572a469ffdc652db485ab8aed8f33ea6f0fded55d7b986cab237645bde506807472984c97ee89d42311b719c9f0ee50eb3ab082b629ea5f41be3e9c0f098aa11d6ec0cfc281a561632b0d8b918c26713fa23cdbb8996b63f119c614a852cba62c72c1b15b5116cf269b696f8e978fbbe228591af49825b1fb0436b6c84927c99372b01aafc8efe1cc2d04b2b270954908a5a02d99b50a7494e786474fe197e4697222e8b8b0cb60e63acfff3b49aab8ce142d31deb142cfb29bbbfe9038593d10728efe95f29229ef49d837ec5347a6a133ff32603299c26d7a08b33cc084e103724b6b4104471b82906161333d6191226933e73b340037571752fcf43e68fe4813b88664e7515a69dfb122a6c6df88a500b89ac8125b38c70a77a9574eccbba672b495df28e57781186da3a70b633e497349bfd235540c0bad7c951abad4760b10bf2def3ad040cbe3cb25052d068f964b982dc309f72f7d265d2b6a90bb80fb20ba43bd2d576c0bc64b8c4677674b045c359b50d83b4ef59bff6baa96083963725dfa76fb4a42342ad4a43fc7dc4de39c098fc6cdd13acf7642fcc147c9656d729f27c0bedb1d48a1fe852d1650b50bfe101403a716a7b990553e2302ee7fba296638e2ff372503ceb336a6a968e82b903a9bb570201c6de5bb3a870b09a178729bdd8a2bb7e17d5d832d92dc981fde1f1eeea4dbda211935bf46449dd23b372cb8de855335a0671a82b61f03ea3679d7714a706b8307e7e19d4222004433b840c8f10864bde51f54c3f10568c8e095ab9b157df9822a6b61fa67d955ac81de72b5f6f270cff06b00d8f4dd5d13d22e3d1c6b20c2158d579ba03de8e8da9fbf72826c08a388ee2e8800b6c4eb7f01e6ff38a339238e5ed526a2f46b36e0ff2772382e157ee59aee4241f4bd7d0b910ef4fc272c6d9484328212ffc8ab44d5be727fd08d507fea2e4ba67903e4217decd9a5c63ee35c7611440162d24813fb2c7272bebb36e583b56e92d50d494b0eaeed6d1180401af05a9424a38bb633383d6143127b83ae2ce2b01d7c0b27bf610300e24f14df335930681e0cb47e076f35433a77810a00e82697019117f622da218a908c4556f42efc9b570f5e2d333e6d72beb2fa6bd3c6d8514c2d2b6c27c06f7764c168bcf83d4e8258d88495be4cd1721d6c609f53be26ecf57b09d8c0dd408fbcd998d536e7b5f68019bc2c4fa10b3040b1a2339e887eaaed6e98174b83a9d2651b0f4769a7da4d32cf9b37d60e700c14262b466e73e374a7739e876af4c8e9faa6fa2cf2825687869d4e832b9a127296d9304707526b9596f222a6ab8be063f34dc4b383fbd09f48d8b017d58d9a29bc441f574ff94f7f719ef5530bf2f0f459d7e0ea48e04f5d2853acc0fe6a69728d90e2a2982f0cf3e46e82f409e02c6f01b6461bb8cd8837f41eccd49d700f43c47877e4ed9dbfaccb437ea442130476715e87acbd326a647db44a36536b4f1c4d31c4a761b046e6bdd173b4982b5b5d17006b9d4eb189b9d3764ae5a00c397221819637e1b794e4476cc39d07e09edcb6fd40a896740b0e2590afa0c293e003b44b0d7730d220cb2ad051656235725def1439d3ea47b9b39c1394d9d08f5a72af11220b1c6e4372f873188cadfe9cf6b7e67c37c5997f28a83b7d87efd6457190549596e360537b696e5106becf8fa7b5b07cf2be941e908493a16e2e90b1726993dc8b26068c189902a018ffbdd95589837e3d2c09a1847c0e47440fe71572cdf33becf6f5b6f8ac8cbd3020c100e44bb47786f6aa343f1e1f6e64ade50602067e3f9ccf16f82985422cb41e52cbc8ea21475fad70ec48fa865ac699ce4e72d5037b2a113d5f05f54721037b6adae949a89ac59e6c735583eb0af6f939e45e83b1ba8f9da9e3d588e8bebec1d73a2975d4c696f82cfe6e58f349298841c8721d6f48fa3cadfd98f2c18f5ef0132b70ddfb17933f7ed96eb83340f3f9bf4149b4b5d96b6bf0bc91e834c16c7591bfc534d33ab3379781f3d7146b68e5f8e772471828596643a9762cb90119d00aeda7c517f9e6077de9d67a52437ac33944024faa6849e6ad607e87cd70f58c3143c4fa7bc7cd26bca5408b4675e1a1469970328667ede3edd1f1229ffb9f6daa2bd58fe23ad1753ef6fba53e162d1ccde272baa4084ef4170679c75c1387f95c3b684065a479ff2f1a657ba8d3a56a11fd72bfabb6d241081a15ebcf4115d24c0f1fa7e6611e4cb20f1c5b8cf237fea5ed7287b97bf98df20f95948dae767282c33268b51c735eaf1fb4868e144dbdbfe76892212dd82ec4822ca37a521255017fee260fa4a5a04c4f464106622b1d1acb4bdbaeaa478b0954f617eb6f54b3ca7fb7d1e34528f248dd33fcb93ec767e5e172d0e48eedc749a92eddee0551f3a93dfa5981459142002813b0ff41595bc45e72fca1949594c6c74d84dd5ee90e480e5b2c75f564b4b5335fbda722c008a6a61bcd1ceda843071b649452c8ba5ba19190bc4c8f474d3d4542cbd14f3b3b9a7f72a12a6f7afe066faa1ffe8faa1b2423c31d2e83722cfeaacef44c7ea03e361072e60a0b369452bbaad538ecaed1aa3ed4d92bacdd5ddd95f1d5d4b2ea456b555948f95bfbe4577ab9c4d71d4cfd8a0ee28c382ee730e3b5eae59339a82396a8723a68289942468220a6ec1747c368aadb2d6fd93a65ca1315d0964de544084b2fdc55ee6457b14ef6a520647cac9484e6589ce208f6fee0c97d6e6f658d2385728656e44e3e6cd9a960ad8849d2ef2c4c1745bac20e35bb774ddba43091cb29336d15c9b1e48e43ebbad21f15b90bef2fcb6aef2cd8581420a63ad22a1ed1bf72ae922fa49840682e4c8acf34bffbce97e6252b8348b1937a6227638a3c1c6872cae7e6088385fbd0b47dda5c36e73604621cae8d72d69b99a3f13b06036ead725b8ea09cace5b8abf35943b5c85bb9199442299e8a6e6d67c2a8e13ab5db657201febe92b976eacca9df5e44b0f944b519f08e3b89f46cc528ee99947afb4372981a595b281ad5d942f2b26c1da5974f0c008ac43d6b559d16e768e8c3bef7720ddd779021995b20eacdf449e0f1fe22cf6a85e66b8706400b5d03b4d1bc7257c99f222b3bc2bd0c237bc20b13a8d8bd56266a1515640f8a13f1f4ce0cf0a072b9b7ff2513f5f8fe103e8f0c98c464e9a1c2c362ab3725c666dc3d784173c3720ec4ca2eda3f9941ba51fcb67d939fe8329ff91c8314315d7657127164c5487208c9184621948862c5046a3d56159121251dc6559b47d69c6d31c82cff5765180ccd2c92de25bc2816b53a7d5237ab732e2562788affee4bc9ceb3f4e8f60359782f0f4d041c57761fa9defd841562809e642311d0ac363078d13bcb2a308b00b3c2a4cadabf36b6609e3ea4cc30ee91d66311677c328cdb92c08af136b7d272b92153b5232f50aec7d8e60291aef7b83713d2367c72c09ed1678f4948d7c772a1c8a8f9c1d0e1d425a1c4b0ebbeea611f379336406375ca7b641f5bad5ab927d1d261b6fe62a69d15a5daabe7a7df4267a546f73c762e21eda074cc32224a66ede9ff9ecda573ff2f486e7aa5a635282c01cb05cf2c7339d4cccdcd58a83f7255dc6634ce0f732b13319ef317fb8960639f96d43902e01229d66caadb35ff2345dbfdd602c642c0070a514e0da7339144ffa5e6b4fb2ff4fef6dbb77e2be253bc6b8c62320c14c6c44c4fbca39fab9c52dc382963f4303affc62ada55d7183831838e3f23e6d50ee0cac963e805654a2569b666aeb7bffbe8fa8300c206945902bdb0dd9a143d7acfe26825b21b33502290f90ea49bee49a801817d3256663639d0b8bc31848bc4c0a77e7c556daee5fe1b7e864d1ea87d0bfeed472bb36e729de7b7719766036cbaf5eb6044c0e057e41169eae21c330b513f47cf0b9cbe72fc08bf109bc6b3cc65f603739704c9587274d2b6f5516700d847c3d0cbb8eb723474506c7ff9dd6db280f22d271e9d511bba3d08861e2f22394c60e0d0c4f949bc5bef11adf44aa5373297ad8ded48154cd2554e729bfea69a46544c30eabd72888968a6208f0e786d83174cbfc519fe0197080d464f69c81b317cb841e5cc09b50b1bd651d2da4bb6d22dd0277170d892a1bf72d4fb0b6656a829d8d9ca8f489ef144dfe0e1c558093c6c321d2e50160c4dc6b4b4a58c8966bff2c6bfb5b872c2b3691a6a21deabaf4ca07ab16d2521ed4756e825385be9279beccdadef034fdc4f191529f95cd1379c69653fba2c66c073a04f6de9e4b0441d84b70cfb2c72b19da43416ea018b8133eea0f47ec3aa4944aabb8e820a80dbcc560f4242d85865e0f4a8464e5b1df5c24840b4394761433c7e93b1579403191bac2264ffa2727c0cdffe1d3944adc30694dedcc852a18ba853a44d751b32129092ad8873cd729261a4b3e58f6ba2c11cd28f5eb7539d789bdb04acdfac994a7ac320ebed48729198009e58b6201c172b2949aaba0d9bdd5ae4d1a474a666c2fbcbf6eb2def598f193839cd1e5d6f439574bb44c02e9f38230daca4dfbc37a91cbf1cb5d8873971e2c60641e6423948fc00456f53ddb3c864c5a6de3165f0d9c30097497b4272f38b64dafb29218e00b074cf6102322481f91c4e90f9470d2b67bb881beba972bc6585aec494323501e8d9c05124a278009f0c0302fc7cff7119929045137c117283d33372524e06f28447eb02d416c11695ab369414d9d122c85021407fa37227c2ce7da6ddf04263032bc6493b318d54406db87428265e7f561a3e6b0bb3665b889965e5923c67cd2ac024a11d27d53ab767300f20c96c7de41d4ca9bac6727a85bde78a15dc925f95e2c7e196379825c1f96617d41df17cfd5550c60cb772609351adec9dd4a46ce2225912f1ccbab86ae2bd3014238ba2f80b2dd49bd1725804f927dbfa1bae8815b34eae4a6aedb0cddfb088a14f12c17a7ac4d9ca0820077f00aa2a9da63fca3ea2faf4d34b0e130df2ddc0386b3e7ca7857e279e6b72cb68654fb58d3957fcc469f22ed087813a62ee31c21288a55a7cf5d99138a07295406b7149bb50a62134d25177831c035e78cf638e5c10acb7f8fa00f45b9372a23e08d358da463549008e2ebebd91a85b568788f7499ffa3c0c9b8dc81f037296469bb0246a8121a7e6f280fc474e71d040428159fe1cafbeb23bdd7a3eb472bb4a3b22233d9d49c6a37b17861b29225c2bcb292902f63861560fbe80fb0b7217f3c22607ce6537125ff3a698e3acbaacfc626c1b7eb5e7f2d460e9c7ee41433de194e27529f7fa88d63e316855e97e01502d7fe6e88111cc2a3abd5203b52367e16e15fa60a32f5479f7bbfc6045b65c7aa9c33b1bbbd456012fc6c6acb672333608219f3624a725346d6374e3b04f305d0c7b8d9556443ccd99c3e2e8ab72674702af4d95253aa38e87db15da2cd455cf4013bc41b20f862f29aef6f7d5727cbc6b0cf18eb62f408d40ac7f3291e672028ea889f4de46feae1fc1bf4c6a729bd7c59f58b8659011365b8e89ae71c2bc734bfa4928fedb026c0fc5bc3ca9725a484a7d8b9c9433dcaeda1c33be1b8130a2f219691a42ef10ffe4db770a9a722f6109ceb72bae0da3316f98b2b7c7f39d0639728341018ade841ffead3d9520ad5d4ffab1577fa95cd541061cd80ddda25eb3dc6abda940e0ea6af1b7bd13727a2cba746050f78d7067ed23e1928fb4e91cffe789e008a90103557ebc4bcd72abd3a0c8e661f059b0c881cd13166bc214b59edf951f4fb9181fab40491afa72bb804128dc289eccb66a9960b379eecb28642836b1e7f60f20b7572b79700c723093856c9e5abfc61d3f4c609f2b824b54605ddaa655ebac77bdcd437b9ab230f1380558fd8fdc66af557b890e9c9383780107210da80f7cf2830a9c7ced4c7247e27881b6802519aa587321b5c305fe9990582e3e74528dcd2691d8f6fb7133e5ed94b1def90ebee145dd7487e770436e1b23f3cbc7e6e1a43c9c7e5ab8a572fe2a6edb10f4206931d8c0a9c7d37b7117f7a06f306eff50e6fce9c2a1a8a172cb88bb7630e96dd4a78d3d913244bc6594e028f5f4bb5d13b733b4fa29289b42e41eae0b005dd27217a9d203a3c5cc583c9e7e5fa11470e58c4543f019a9c372b9091d37136b7e9bc5c3fc3e21f40bb74b4cc7fc7eb23c04726cff2601789872316754299579e5440fd87e9da61dba476cd64cdf1ca93178447fbc980456ac0ac709011dbc25b54c4885f7aa599e15e8709d7f70c757b068d5ced8424dba0a7270f787a9dccc16d326fb1fa0dd33b8a87fd5f2ddc85bc40881857f142c240772077a22d1f7e505e189b3125c4c6702fc684311e2ac4a9dec12a66b7b12dd43720a3f509f96c663f90168f5be6a2d5e3e801f33224df6984155afdcfeecfa6e284f0a45afda8da9b37c2e178eefe017d74d7016a7b86c7ced04d857c8339b71721781ef0b6f0336439e500a950bfdb8a342519db37eb0dfca83733f7e88583172c09de6ce4045c012a20b4af7d916e703f2d7ae9b8896f308a6f92603134d3b016b4063a6a0ada739f9a73ab3d90cc53ee3ce13580be2d5e56aeeb2077b7e337221cdc6535e2b85830af8c1b0fd846ef5b0edd56ee4bc271b23a90aab5748892eb63cccf01be491f6481341c502425dad7143c7c4626196a5095049646f2aaa358b27758a28805cc5d83cd584f84fb614513f2343810527a484058543c74c80729388a0d32a60d0e99dfbcfe16d94650ba9f449b9b56ce555eac3e83c888134720bcee6f6eb7deb148522761c3a85f7bfd35c702d9d9fe9b916cc97b161709e1314df82c16a7f38f2096d00fea6435303418446a169adced29223c9053b070b72a3c656357c12d936289e554bfbaf3ab937fae6fe0dabafc8a4fc5a495ead863252c11e525febe114c1298a7049f357973b70b358057ef92716388419164c4b42be2200548481fa16e2047c4e1c60749a3b91ea3c65c06efe35fab44858ecd972eb88e8f8544e4dd042e02ecd5c4036e5ee08ae8c1240769c6e6029e8db36fa729fe98a6e2d6795786683234d7d20d2b2d662a6fa1551384d0044e5f697b9cb30e283b279d1fbbb0b9c37463eed0133c32dcd3407fff99b6889e987177655253d6fc9c9669188320984464c10ad589b9b6510ec795c3af183fe83e4ec12b03a7258d18d2278fa27b585b2d121bbd1a9ee3cff6c6a7f149dd41ae03b03a1a8c2243c554539b4fde313f1bc9c8b75f7ec4fd8d5c21c727341e7c42c76f23b0378430aff0e223490825a2fe9c57c6af3d72be4446cd7cae741a98958fb4821c74f35a76d42e1caa7a39608200a1269cc1770df359d809a6ae3101d811528ad34d36526cb4ec5b5c9b6b80f7e1359c77a205f7e1c02e2bff412ec0ae63c6ef0d59740dee08ad0871f3d1408101d18453b0e94544dadbca4d3e38a0792c3d9081f126250b9a410266938522e39fe9f1cd929bcc72caa66144d2fa5cbfd9e451fc1997288a585ded6fb45cef0c7df4a7a4072ebd808302f67d1caefbe50a34cd44cef0e20ca7b38c0ca99ee2d3479d8d7ebc6cae7a7e4be74ba6e7b052a767402a14d02edebbd7ede4a6b7653bbc981b26106b6b71648298055d591b297c438a2702b6f827c083981e9a4be348edc3c8002227d9970a81ea94c61d425d31e68e08ee5729b43736cb82cddadbc623d7ebfd02d3817e9069164653bf38751d0abf181f472c4ca4f16f0ad6c9fc36e9c4e19f63df36bc61d686ee67c33f751140ab7397a725dbfe7d911ca6a129f7a87b395244c0b0bcbf7d875394af9102ff523a4f14172bedd9cbbe7bf4409c6be2be0a35faec2ea2659b3a77d14e4e040423713bdf97260793f93aa8beca033c5a6c73a6c3d4e3f7432aa3cb0e966d56bc84475e9ba22de5bc25e1bed02d1d5236ccc1b94c3db3166ccca585107c09a03f49cc686f4366d89bc539795f1ce2908f1bff5c8d01d873fb61f994f5e40683f1b148ee7da72c7278901ebe99823d54683fcf820fe00fbef4fd49251a3f4dc9c49311292dc7263f292e2bc34b233868b6d584eaf0d4e6cc773e2c6664495d99160cec01b4672566aad5c941e125ff0b91fd4f9990ac74d4bc4cecda1c866c6aac03bc574ef6e2ccfe1c402ee4bf00716d2e06631e071d1622ae3292471d25508a98f7e04b55b30a4fb134407f1d5c663643c85b15c28899a413108751c583486f0005ec3c87238f25d1b4c91d292fc306b7a8bbd740fdd5126de7747699a429787dff5256c725b08ea14e6cf90dff259d0d1733fd07e693d620285cf70876e18ca8308355972a273ce8308235112a41e8006bae31bb3b8ea9f710f0304c829d85b8db2816072875186ee210a433ad2d730b59b7ebf3b48e16a979162d2d378ddd92022986772956c5f5df6cfa2613ca8a01e07e9a44997b2d64e52c30e254a7c342b8b8f0651c38e9c0483cd343392c674779e1b0ba8f0e89fe8c7a0721c2dce8bc727c6d4727a868cc72e00c5c9d0d7e0f1b116dc52d45aea837ad05f71b3067835ec325a72d6a6655aae44b64e6c4e547776b443e7549a56dacda66c16bd5ff4e98bfb9372fb05f3c717a86ee4339e17b928bbe8abedc3b3bc3f48e9ecdb09aa3792250072edb50373e6e31ce6f0794e1822333ac0e0eaf1043850ea77c165dc9ea200f272c04b48fc5cdfdb7dfb7614680244a41db98b53753e0d17bddfab93761379877292da25d50dcab577ae5be22549704b67b4fb40797aa3dc14a6bb26cbaab60122fa310b37590427006e2e4cbdfaf862446ca47a911642e277a41848e390532472d84b09fcebe16a895e6501fc6f47fc464f8d1f355d47c0535d5b4713ae7a5c72a6d7c61f486e14bac699df1e5c1c015e5c484ae1c44bb0e01f6c9693226be872d3234b08fba6becc120992b4784578bbb0a19052863dbddad98da4ce0352444031b13776e9f724542a442bc5ea45ce0a025961ca5b22ee23467de62a3e633a72d585441ff6307a0b4401090b7bc7d9d92c9807a8ba8a7468acec8e332280ae0c2f92674fc9df3bab1b04286a7912ca63504f720acf8f127051afff3163dfba6422fbbe47307b32feb4c2efe1054ccf0d98aa2ce9e8272b31a453ac242d0c315613b029f9415e02c10215ea3f7bc95d4e92152aad2fa57dcdee190e242fb85b49c467664b846bb3168c7f7c3f31ff6de5560aff99cfaca70d0fd95385183a094f202862ba8a7efbb8354469c31a53b365427850b561c202deb67f80f3bcecd2586829450350be851f5ebe97194c9c1d11781f2719c34e149cb2cf9da1f06e9203be5edc62f2e8faacac72c0bd1242809d0cad3aaea377a8d9c36e3d5bfca7be2755aa9a5e6ee759340a7c76472faed4f1c1bb6bab544c0fd17952aeae3e03b4729b09ac2610efc4094d1cf6a0d32d8a229a0773539e87270216cc13b68d97f54b32805a40382933d60a7c850bce5498edd21710e59c44558075d961dbba9a4b723506508e7eab7d0e22facd291a47c09de9539a6d316ee4e1d27fb984f5e29872d67328645116411e13c83961b6e91a026fba3392a6b95afd96c80644b022c672e68b70ef1d2bbf19281fc8ecb111a9dcf415d74273d9e6a906ed49da2de5e624d76ab4b887cf1c6f2777e528f1c91d7401acc603166e4c8344eb38acf43573722473d8ab00af4de8e100b61c97f1c72711f9c35396adb42d4348a786306591150761ca51b54a6ede86608421a937f4a4e542dc7763395c840a80d4e014256972e23c763908213279d4c62ab2309a2b94d5823171fdf7b54d43a522c1ad95894f439801222555913a86979ef728f6be38b2f158060499469b3b23296c14042972ac23c59a95304d42494108554e2373c6e7a8fbae7f5de6883088b622431f1572112b17aa59e002e3d873bad22f1f3bb7b1c1b9bc481558deb21e23e77228ac727611f2e5b9f44ccc0cf694bc461ce522a23a696aa020210b6b3c082dada9117213e5949121dfd636c5d5f57774c8d3316bfbf834a1d21d7d377f71bd0612707202382268505a111416404429603d30dbdbe25cea24693fb3ab259f2f982bbe10951d581285aa4dc52443323777f06d357b1718e673bbd39f5bddec575a15825edb7f6e067cf6e15d15d013589e2bbd2add00e34bcf02c311995be3924998827283b6bd6dff74768ebb040536d3580f62d4e15a0b807397213572c09e8126b74dc3a9474f43aebe52fce123e55da3063c63785daa150e5e37142909f98c871d468af18a8dac75bf923c915e0b9896c60cef1540e1dc2774cb4b64ae9755197a72e340c31f9cbbb01ad82aa278e3fe915458fc0f58cc8cd6982da83efc837b871aa69b4df21da5cf25b85ebce0f3e8ce24c263f9ef8f137daac6c3d9eed104bb72dd14cfc1e24c291c2c14e556d79ac1b674335150f60808c7d10063ce4cd0e607ee18b95fbc1e85416f91321bfb1389b1d583e0469acc6f4d035bdafb97ff295fdcfcf0f618de5644534d778d2352c4174dff9ad5ac3862c88fb6a59088083b72965472dcc214db72f33fbd4b8f675f8f31638b1c463f8a1b98e01a6b20a7cd30af7ae61e14b34602274348bf6ac732edebae25ebe613698f163c7d84f1b827723b13dd667bc4cb55c22ac5bb3a792568dc4c56b8987f88faab9e4f595cbaf2727fe8319b902a726f061fc0eeb04dbb9ace924f9a29cd28bce5030fbcaf1d4b603f9637809b89db4615e7feef67b900b685a828c035fcb22f529f178ecf780672fa6b392592eb38ccea975590e6b03b8db0fb6fe5fae73a9dfc842e9ae6035b72e1764d7c2c9804bacd393a3174d31b7c80a40a4a910d5f5873dd7fb4d6f99572724aa496bf246d67e4a18b746a846298df3a57ff9e118c0ba7e749ce61a7f3726fa77b453936dc39369a65604b0932a5ea72301d9c64435932668eb90498962d8bde081bfb3fffabaced3a592187c00bc98f9813fa5da4b62730527607952672d8f1214472ca0c2f6b937e972b9477eab5d855194a4ef2244056fa75780cca722be398a1397cfd80d03d37c618c28630b637d00c7225d03ffb69cfaace867672e9636c2a6edac5ed1f590f82b3cb224e6bc811cc2ff433496fc6682e7af40455fa95033dcf70e146307a1b56339a9f8ef02b8ac3344015c2ebafe452a1484836902be3acedc296694cc4bab0a866232f903a9beca6393ccd0223ccab646da02019a328cd912aed804d9b0a72c29b606fe31688dea9c5a02c8472bac02483f0721e3399f87f98cffe1ea879ceb751ea17d9299d2395f95770f049ba3de87b0572794a98775d78209e222a4a7a0b664dd614485978ecceb996e51e32b66c97007256287f7c5ed3f3339883a28745c61f29e51b81f4e74f580d1326dbb5443be7479a26cbbc3780a3881388bb689143bfd88e62f8b04c70ee1151cbab22a0b50d72e22495a2c0b16a9f1d035892d8184e82a7dabbd9d4b528276a45ac4d800e1972d8095e314955addb57a6228b9fd47f9bba4dc8dca1d5970e98a09fd1b13ada72e1f349f52a88e5ffae1542040f4ff04b97b155712a714a180197da50560c007255c4890e699360b7c3459d712b84bc4b9c9f48b9eee9f41cba15e664b8d12c3203216952aaf4769ee731e240b590f313d956c9ac7182068df0fc618800329d7246e5ad1aa8e8ef7b5c5362b589592098954563e06408560d6359a2e426ef1f408def268d34fcfa267af079cf967f3574f2a81536eca6f9207ed1b47a0124b43a0a6740d0674f4fcd219d3410105adb406dfab0ede629c65759c1d602691baa7288934f6d33d091d4b36e63719afc993530d1d4c9e9f5408cd21bda70bf1ddb5b85e01b2079add702494058c9f4bcc424964443fe44cea38436ce26c5a53c9204cf74bc1beee5d8b93fa50a241cb45db1062c3d3b5650464655668e7ef982196fd3b4eae314a2b81cbe095fd5b148926fd736af04888889a612684007b4c4d7724793de461c8e42eabea09bfd449548b19bf9482d807beab6400bc9881443527296ebff147aac4d0efa62d1914ee2070dfa163b8f44097b60ed91092e563ccb7254dfd769b5049067f466201161a829547c14ef69ebecf2280101009eb38d6672be417238ec51d32894d7410807a84a388ebdef3a3960344c26e51a62ab612172af836585585a13ed2660d69b8fa3f85176f960ec2ddc860e43d759a17c004c72e6e017646ac8d743a92af936a35fcc725bab84214c8dd36f9d074caccfeccd541954f30b7e7fe03665c6a432d93a6ad769e91a9111b6303c075d88be1278de0754f713ecd51a41cf90da927050537dc71e97fd23112e9de3d023655ad1bcf77225d61f04f82c0a4d54916b6cdb1d5274ed12bc8004b829274749c5f9525bec725d22014d2d01bad1957ccd28beb9ed87048b716f8c30af26f4e7d374a6b113465ba2dc4a7ed6d6b64d50fea397cc4e365639ea7b8d7411973fbaf80e0f1798721db05e8a98b838303176012418c5243ec49a659c8408a81aafff7054f82745203c0e062b28c25b8840f33094bc20aa5e182838dcf08ba4ec17f17c5f23329a72e4906aa3344093c987ec47dfd47f63355cf70102c3fa315fdcd06e4a09114f72888247df76829c62d891c5a7b96239f56091c74eaa6781611060913e044eff72df507da33daa15ef9d56f021cc90b02044d9760e583d5bb3f3c9c5655f892172176a4c62d2b375b354ea1df97826cb2ff9fe612891c318403b1a206f56f5f3249b231adf03aabb13ce6f297f3095d18c9234ee6ee9759b8e8ece31566e09f7728b239a22d307bb3bee350edfcd191c5ad996f3c12a9e3ba72007f306dad60c72c5f2a634706785b59bb8f99f33832be6616ce4ec26732551d382118c3eb892729f7b6506ab5701040565d5b6a56c1864e7e125ff46f83c871526076e987d8f4f8543967e301d7b401f234744e09734ca85214c031fdb479cb8350115e24b0c1f7478c6473ea7ecc52919c9bf887e4eff2b141b682aa6c7c746e5870ebe20741e119d2280c313ec224e9b684aefdd75164188b66c50089718cb9369ba806eaa69f6066832f39bcf137c1004149e9275ee43566bd3f337488c3542b443438e8c7275b522de0be37e4b3733fd433884ce2b1e305900f945afd549e601eb582329729d512d672ccd89783bf82d9bdb8e95168ff3ca80d830791217a8ee9a9f371c5c43a8c3f418507c501a845099eb236fd38e47b3123bf20d9bc6dd444a4df1db721d5dba24e47b2da97fce5c4040a78429e9a2f83f7c72d840c871561d0a2ec472d9c4729aa3f0e632e714de3ad1c5d01399d9e6b85b25353f97e8a36c52c1700a3f8f30f44ce0019bc9f0c3799dd550a79d30a28dbe888df4850a2fe2b02369728ebb922f4f0afe1e21e280280ba35e31cecad68c44a4bd617e80c820e2fa540e9dabfa285a3a2647baeb174b6787b120f37a1056b0ab12574a2c8bca563fab7242df88dd88801488161076c109f3e3d2b640c7a8a946598b17bedf04e336067248592039bd87609b15d63677217e94b7b497cb1d23b3d8b76ca11403c197d672cd2ba3325070e31221ac76458bbe67034527b4dec3421d677cc3e9bd7af7f1015753d9c202fbfed095437ffb89594c5d65d0e7bd4fe09a302e38868c6ff1c17246e91e96d245e35af3b24bb277d6cb1394ffe534ab6c1d0c97dfc92cecb90172789e705d01359340e0ed02f71fea8350697a5aebc0e88ed06fb0fa5b4fe4ec1d51c6f94d6d156f7c07b1fc723f2f3a5b12cdb3de641ac89a6fbe8d2947def60a942276db1f1c07ffd26eb5fb19c144107ccd87ea8adbe3854fd48cd1bcc0840495883661500a27f5b81437e5fd37578903203447f93c357bbc86c7931c394c3da588f9a2f6678a226a7d81338783aa20f3c7dbbd13ba906e2f8d34ea81f7b047f48e427f84948b0f68f8cace46f40791e6c255943f1e62a69e7f5cae393283215c5988963e0d5f2a68b1546f8ea799618c3105386a1c86224f24392b871c90689affb2fd7549a712f3f403f18af360d7126b333b3555139c52cf54ca183e177243d77f75c137752c875c6bed5452b812b28dcbe198653ee7d85ebcc6095cfe72cfc8ea61d3419e803763a290be47a1a466ef902c3a6180525c4af848f19bc87222b591426e236d2a49c1d9b5590ec92f71c0ea64ceaae0531942390620171b0bc7d9c094d3b1868c393241e127ee3ab49e57637accf22bff0a0a0f99a334a972f42992425c68c144a786886d662039d378b4ae6c8d36a047de3ccf0c67c4452b0b9f63991dd4c3ad1f1b8076b9b149f655e7515ddb46ea9f80d65b515b7fc972a3ece2e23848a85df796194142cd11e9f0fecfd417b4874fa31c7cc672139c0aa67b560f030b3cb8598d51b575852a9ea6db5dc62f08b4adc5f613a35ab31558cb0263ca99ca66f114aecd053e2dbb1930c26fd42f38b80a85635cd3b1ede17299518e2d9f3cbfc63996bba131eedb64de5e935404f652edc922720e3874737228132506cd1205dd2180ae8ba28bba6ddaa0b3dff22c6d6c9e7826e7f934e62556619cc88f90a431aee23d4446a0d6ef824c0182bd92c3f23b9de4f2b73ba572238d4fe7ddf22849297cf3b971c57d5ab19c8724f1c211d8a26c081abacdb972560e811910a2727e1623d4f83f4cf253f6ac09d206ddafa4d4a8467a1c86dc0117a4727e9cd18c263b2afee98715d0b2f2319beb24828eb8e2231c297875d872d029f499ce304952fbffd883e09432ba8cfd689fcbe8c67107ce925aa9723e72ed663f181e4a30d9f35394bc228a277d85c5b9113278aea4a5b4f9940c0f4d2d736cb139224456c68c097b028e899ea6cf05c5eb91825abb64ce48bc3824fc338f09067ca900be7d8b2769693edf5ef643c08d602d19826e249e86c4fb19ea7288dd737a876d32d9db1e13360de9a964a4a69fbb905870142eb04588649bcd6eb40741ea24873f9bcc52b452698d4182a79d90d1f67edad815aa290fb34c1e2e7eefd91387904400f599c2700419988465ebaa260921653972dc1761989c783e67c8cd756ad589173156c5f7bfac3414ca6ab87c8ed9236ecc8fb084e543fe4f274f915dd8dd0e2cd2e4a579adf356e9600421293928e916cbec630b208a3d081ca1d977d011872198fbe8100c5aa39c3ed3e9cc5d4c891ff012758a299d2c72efb9d92179ff09939e5b4d742c082ee9d1cc98e7f7ce22786f18b4946f31d47267406c2367ddb2d0085a0afa21c4f9a3b9730fe3f04b3caa2e455599fa84c9331add5f169727190eb95c38cd8b9aac889a9e87cc925b5060124f15027e11c072d358cb799fa339ec54b6b453782f96cc4d10f78afc3f34b8e8136a7768f166532e5e3e801fa1add32b33b9bfa0d59977325c32e11513b18d25adfae6ef04e911d6e4c57628501ae91efd72104081c96ac9991089253c87f7b6f8bfbe05adee6d6ab7f8e9afb0ce233990f2c8ca8985a332b92f0a143f1cfa59d984275daf9f72ef212e17dcde1c7186c6c6caee2024fc90bf91a17c362110147fb8149558ed03e1005303aaae43bfeee4ecdc09d96831f0bb13588442e041cc32c4906df9d97236df94d26d990a2f41e2ae755e60846875cc8051e6d01441e9d12c49caec7f10b5c3836d52935f2ee400714996ddea6e52228ff3589abe8a3cfc88b8606f8372999271aed5abea29e013480fa640a5ed8a5c5ea87d5bfa1267fa4f24094aad3e7b3a262a9c99101e9df14ac0323410d35e9f2db5d4f73573d8523950f5e42605985be62289a18b7223677d09c43dd512e22aa298e22a9855e8483087d8ccc83ecb2fd5ce1793409e34b7e2c80b8f2f12f471d1acbc5b92cf03310f2f3204fb721d8be8649d465e83cce8fdf65201526833f0044fe7b841b45e74c0234d15177265d39f411b1971fa38ffcc3743254c0b181b70fa4e238eea578ec6a2a3c415725e425f05c9efa330942ff5d014abc38cfa0675b025f820779740c847d276c6726e70f8d48eca175a4a562156b2c3c5c444e06d1aa17d15e626adc8bb03f2bd3c443a465c6d8c82beca0dacbf553db3df4b6405745cc92caf9d667a5ef6b48072d0ed259635b7fe352a020e3c9fc8fa407ccba5ee68ff4ee8afae5d99e66794725964109f048e2ab3133be7c2db174ae4411c1ad3845036aa58f244ddc62048720c8cd41ecec318225db9507410047f3bae16068b888cb52ee20917c0d2e00f399a9c2ae97fa2c45a12d691b25b466675ca24035a1ee9e5231097f7d2dc2071720ee4c218cee836dda88fd24a91ba948e56707f6a5628948b36567a1d57a25372b19ee21f0f94fa7320eccab0b7f23af4f43c19fb7b95076eed810d0616ac002e7f49182fbac935da85bb770fe2db18859c1e8963442a4bbcadcdec2a8ed35072a902ecf18cb67e87d74b95540753d6842a2ca2b89350e4285c449ac3bad6c47232bbcbdb4ca96932c48e5cbac2a605448d7b91d318b8faed1708c1ba30c12f723987eb55d98b11a54bfe00aea0d9f5e580813615368df451d6a67b29c7d0d9728735233ecda5dc76d77daa7bf1cecd240c0ca3cafc8d437f1cdb5c29518a05398862b2ac54cd0e22e5191abc450672b3122a5f6d743d9e9107cf4431c35c49721d71c3593e9ca602f1b1cd181f3f3bedd9fa7d035f8d1b15265bdba716131c4afe2bab6836a98b8ddfdab7dacb167fe09b389f59ad995746a7a39de8a393b672aaee5fa136f481fa2e9c7cfd948cfdfdc2352e7b4f614e571a067d4c66ba5a3194a3947cd1062999460efaa101961b077f2e9e04e3591bc345a36aee9b589372c7434d689be2cf5d174a399bd669b138340d087dfbda8b66addc1b4f64d841722c553927b6594158cc11c25d2c0a34d61c9f115ef3ea96aa0a9ce94b45a7c140c4ce4fd6f1d1794b172fdbde1cec42f7cd7aaa1c97e28ab7c4a33f2a1a142d0f090d817a246ba761cbc93ae1c6103c07f98b134ee38575a04f569e0814079872615c4b95cc767c0893b592198eac0a16d12afae11edec7c78e2e93a718a8aa6e5d77a8ac8ead067549ae58baf660a9941ea871b9aa3c084aa946e166817b2e729400eb10a73d5bdd52374858a3ec94995bd564c32e674a4874d0562790b47d38f3c3bacc528584b64e7e3c79db2eff79afe287cb5f45172d45f6023a278c5736d7e1b70964babdbb225e21da21b00895ff78da4173b4390761572982618f9272721a64e01ee48f9a3430e336548e8e22332535d19efbfa1e8915defd71b3bd00dd5c4905f5ed35b0ea109de94dd471a3122f55dd3374cf3a0bd2ffcd93a4fe01093a6644b23c92251a0599126d097ab0bbbc857a7e830ce3a7f81498e4bb01704aec27ac74eb206b07bb0881a2eb08f0979a4558d48f2af01656cf63e7082c724839a871191cab0db0884d3059521268ccf865824e5151b81fd80dd0c823d1721731a7adf5f8ce55777a46f3050e2cf3f6c1c13976490cc48904b266f17a1172c8fac68c46db9d485e28c51c8b014e1d70560a26be58236752a3399d70defa0f31a0559dbc112f5a7aa64f86c6cf992e874c4543459ce7d3ec89e8fb4f32fe720256ed8d61efc580d24dd98e0a0eeecd9a6ecdd1c6343b1d751362e8e49ea0327651512e9f95db140817c8ebadc30912d5af1e3de81765965dac90dc6be59772a1f5f8aa721ea600ff4530700c1ee7065f61d9217dd4b53edf869233b7365372b256362eb56fca7ec9d255120ecb06e6062f460d1e0b23f9454272789240645368e6d5990080216f8d41835726d34bc7153e6131bee3321df314252b5d79aa6e7872a22dd72fadf8286494f2d673ace57a733fb8ffa7ae4a8e210bf4c18b014ab853bb914fb72d7d8b548b47ffd1cf1bf9478afca31cd344a9bd2d5f95134172f697cea137fb67511a5ab09581072cf3ca82413261bce9e0680ab2d6ef89b17228be67cf6852657a243604cf9335e4355f0bd0052f719cd13d221d67803431361d2abef3a7944ed02ad7380e63ebd4f478e7c90870b5e88f3fb8a4361923eb377507ea8eeb17969df60c67a84ec9a2ea3d639f4552aa1a8338a8e106e51022725b3cba7111e69552fca5d97e1ca29be1a0ce43235ea8008298a5b750347ad604361964968388c6646c786d4ce82c028993df52b2b7a9b551b646215c6568b372081de1b64a6fe9c33b61585b00380e2646bdc77c856571fbdfb2487a27387672b0e190961f8dbaabca7dd9923db23c27d7f84f45d265fbf7ead163df8055045e7f2c8bc1b4141435834073c333d28029e59e221367562890eac1ba0987b84a72b44ee2645dcb39780fa7e9246cf9b89a36919e03c8c36673a48b46e183a26872ac7ea70fff14ab3edf4b06fc1571fc94e383795d87f5ab69c1d34b495958e9720eca584e02bf34380293a8bbaae284c0681b0d26c1b42a4341323dca3a912d72f7c6bee884e719709da9f9a149e5af213022e03e4228039431e6801fd91e137281f89b4227f4f7f21524c6b95f9677962cbe28db3bc79c783beb9d4b8b8feb3cebf3966cf34803faea6c98324389167c8b26e84986a77f0ca47e399ab83d4872e3c0515e5a58fc8393406b33304c52cbb12e1a5d42a35d0a3e5ace12ef988201a3cf0f9f5059502485768824e0e69db0ce799926f7c03e4e7ec7fc76ed42e6722018fe3b87a5dc71c0c4923d0ed5e74f3ec872c96ea776a5fa9d97154dbff272a7674bed1f60d4cb5cc81095cbda0a33c203eee384a04423d41c8547d0911f5585ea68ef9e72f5ce8ce0799a19f50f063829db17150e03b4f9d123151f386372807b4b8da4c3db07c8e6902ac14a5ffbf427574a9506680f2a0e2e3a6802665bccb04abba96775c3e7e5444841d248abd1b23ae19d5dff622b9476294ae1b3722421beefa494eb4ec22dc0f600de9247981b50e95ad3b372e5c19a1905603d726466fcb5390674e24732bba4a1cf7edeebdae24a23757e4d55eed2ce3abb995771858455cb976d75a50b7e87d2668b2e4e9348b93cb249d61d20283f242bd22c9241dba08546287579df36fc9f0351d300c5277c19298b83f834ff45d2acc927bb494f3799a8e8e988d2fd52aa08a882bffafc293dd2b01d8c426e64a8ba9e724b0ce3a4841633084e54fc804d6554e39947fa23c0e4ffced75e564142a1b672cff820f4287435dccfd22420fa26b782bf38eec67de0a17aac26f0a27209a8729aac0fd5a62fdcd86cddb13fb022332035b3a9fd5998591b889d5fc9467f250cb0f7dac32cb9e0c9ddb35a65dd9a331cc0103774a90435ea32dc01c761f2b972006adbb2c596a605a580b11d8bc4b6a8ee60c4426edf7ea68652da1851d41672787b121e2e6117d839edad529559f9c8196dd2cfb381d4389b0001e2f2f53a7279a967349b81a99e64860bf740b48d2c97ed81db817d8710693515eb368d78724eb48df1b068d0f8056b31f0cd655d9ee37b1d32be22715727f36bdfd14fc17238f5a7da9857076eb4f938fb61e16bc005c3afbb87ce914a21dd08fbae66724552ecb40202308cf11efabc8d322fc0829b76ff940de87749376c8b94eebc82725fa1a91f969f9ee7ef1837b90593888e8bdd7321892ae5eee5fbeb0986776872291eee69c007a7c08c111c8c9d662578f217e5de575b554f4bbcaaa52bce7b721c77202c1bbcdc4f3f98593d010816836c7251b4202bc8171173fb13ca417062b89746451d9b212994cd38cae6268dee534f48b21d6045677e91606190a7ab2f1f7b251a763a6e4df89af52b8a202711cadfc663995ae7f0751d5b9bc61cbd4f033c61f2ff82f66a2ac6af2227ec6fd764d926f1cdf017ad03e7cadecc7f663004015c21fdbfd283495cf0e163a8ea0e9a4df4265e6c1aea6c61cbf32a7d7058620c408e9a402ddc726b9068e05d39be35d9c2c557bd11a80d54b7522e4feb7227ae7dba9f52261d530b124ba73a1e21acada59ae8c77b1cdfa679c7242ccf7243409011aac480d68d7f0191db2320d5c94af52e3627491f618a695a6b91f372ca8863623f467a3e3416aa08c87382ef4442ee46396d75a8c17d6618592bca72aa3803974966490cfbefb6d7a4b358166a0c16b948f7b366d3b89ffd4f697a1c4cb29e758091108d3840383b019611fa88a6106d9ee12a123a11fbd992fd3f72cf4ded4241b30734705b1afbbfcee8123bb13b9957aadb44bfeda3c1137b56728dbce7a085ebd1960924d5ff3c3e0ef0a5e915b8f85d322902b628e4dd54cc59b3fa1ac58caaa9b9e9f8c5f9fcbbc2b94ac4e41276bd968e42be03ad5552ce6984dbb352870f130add726cb43951ce34c2068e041b452563a64c4f72db373372949b6001ad964630d712026a20160e6b3826ffbeb99c9455c3c5063f84e96515a50dfb550b679a52b23b49b564c872f397bf5dc46036f189f96805a3ab66b301d7fb324eeaddc62040620fc854d9e8d9abda80c8572e2f4d6cab20bd8c9ea66ecfca7c2bad4a422bdd5d02906e9965f778cfc2b5f502c9724d480d7989f0ff4a3fd2f603d18323ee38d1bd1da7859e43be45386e7f9d6af767c2fdae02f737669ddf5c4b4bca7957e87d9b5ae3d212aee3a0c2dbedc2c9f3c06debd08dcf4872f31b7213e3fc50d38808c15b9bdc68acfc84c21c411262acd4fdffd5ca554d44ab47ee4f7c25ad97b1ec9172323b810a19902838cee39d77f0ca54bba51ba772132c163d06f160d2aea82a5293089ff6fed1f12fe5c96b58e8b37f230f59450510fb868e5fdcd7c5769967759309c57e4b5131545cdfcc6b69332d6f6509e255b2048ab6dc3407cd8e81ba5c2bd3d4e7653b5e6855ab53fcdccd77d24b1010726e5c117ed10ce5abd930ef6184852058154f73e0a8e3c02893c7a23cf4dca3726973fe595d6031b269b799df4cc949ad87417875d03a7e0df621ec07a8fe1c72c8705c143e7c48ba74e407f68b93cc444b105685d4c5421ba25dc621bf5b216eeb345642b063e634d680b210997506ff6890ef1fe822f0377575de369806822ed08f7ba9f727e419230e78b2f3df043bd23478f751057f99bf613e4493aae772929776a1176ce8d1789b3d9e7e865e7f0e355cd21025d53be68d7292b6ff10265b6400fa178a50b491f636ae8439c7d6fc3d745c62e166a7db362b756ee0124004979f12c753cd8048b709c9370573e263cb2d6ae9e2bc35a17f6ca3c0eae054ee924031621939c5588dad24c2a3586f47f579e3e8871fffaccc10d31eed237267c1ea1585dcfe053485c4d781749a2edd5baecd00e82a75b7ff5efdf6bd060253ee66a47cb406b10422d1a890b1e770589f47ebbc41c4b6384a596588633d727fe58232bf61403e5091f760cb0b5c06e2becb8c6a41cc22ff0f74111d835472443681ef1c5c578391e04a80b8d4be0d9d18e412e5efe4bdab7ad5d05579f34f86cfe43ceb461083076d37a274035b7180df518a3cfc446b5cecb50443995d72e453449d3d758e1e83c078c19256ade0915cdee75b86948b6e7c5e1fb3806415b0af9abebdf36fca9cfa56179fc303fa15901ce1747c17ea908dc7c38230de0e3c73cca47a1acbb12387b05bde79dce2c928973d2f9442f70b9b49c5f029403b0d5d0ce0b09886412f88b301f84fd035b96acc132748a54199ec44ca576a7429b6e73d6690469ebf9449814ec925ecdfa9d4e88ce1747a36da181b7561f577724cd7d0333faf59a5a629e28dba8a4f5c9cd126428a6f54019b12bb8444cb4772377e2ec64a65dcab377cd7a79d1adb1b61d17125153f5062f36d5613ff21ae49e2c416f774b6a8ed0cf9296fdee3001b42c286970f6c237a5b0888d0f62975405765d8c3ae2c3a5b00936ab99ec17f004ad41a0c6b65bf34d98d75d7b377303b87ae1d83e3ed7eb4d59ee44cd8684951bde91ce9097ffcd9062fecba18c961105ad4732b0bd6e6de81eb0db8dbebd4e679be49fd44197bffe319cebadae1b072fd2c30b889b7a23f5830a57fe7ccc27a596853905420ae7a223081bc5024fd72ffcfa78ac735009ca031292a979707f64baa140655d991a19fbb36a80ea1432b776cdbf053ca596edc5fc3e532c0592521eeb1741c83c5a6049147bdb58dd072259db78167e0cccc56ed9437eec60e010a1b30e398e39cfa943ba2a0ab260b4f0c68e22a2837765a2f078a71502fb04b04db77b250211921f9891608ca288825808cc16c47f70dca5e3094b4773b71c430edb3f054aa5caf24c2c2553bd7310e85bfdea8f7f0bfe3b58985b7ca4ffaa08b6db0c8838c126e3b66af06d6500172a5d3e300b9967057340bc03a8a3c0bbc0d9adc665ceba564ed490ee65357de721155aabc10d8a498fa1ca5aadc5c196e898a37735f737f43e7bd5cac51c7f0649476671a8a11d2ac6390e921db2b03963793e1bf39c42fb47dabe03ad140c6728bb2c39abad1ee8d69655bafd3597bfd37f1c7c7071cc86a6230f0b83e4b4f3fe2eaa41d96b2d50b7ffd9c9b1b967002cd3b45dfcd0853af8a262b09d30f8d1dd44f733b057e7a6041008db7ed050ca2079feeffcc238a2eb507551d38dd6e7263832fff0149effb1ec103b66d6f604cd421e634011d1ab0a2993ef72c3f80723d39918436dfd426cc818ae05fd504c856885f9f3f03b0e837658578ab177e7263c72b18870487d8bcdd608ab238e58bfd3196e1d4b4b5dfe1083da539b01727b022a2e7d4e0c2768a4304bf5f8b19da79987a6868996c9aa90da6e0f386d972fa43c1da7438f7848e3614b5f7d25b21ba9c3fe399f59e44ee9577f467d23972993c4248381577178d5c521c83d3ff0f435a272aa6fc5a420f1d87638b86ae72f4f712ec957a68df0c0e8bf8c4b4ff14da40bd981c359cf269d3a3336d55bb729e2292e3fae6e8dc57ae2973c711320cb5d0e3592c06133e2adb80c514ee68722dfd28ab407ce271236c6dc959abb5eb5030febda68d6bd73491089d584f7a59c55903d9fd80da1c7bba370750517fe59f21b5d6805c4db72f90fc197f975f72eb37f3d8777173415ccc4c50e819d8f4c831d1f96eec29b735e867b01cd60669a1cf7cdba68698a4fb7fba090f102dfe05e1e627351c6530d3badd97ede76c720238ba16d630af488e0af42c4f7c1f27783ec6ba181e67e2f2b7e398b7ac9b72beae235d9e1607199444e99cc480da6f4398da8b93af3c59a4717e273a706013594def8f8c3aed18ecd9139f9668785a10305319cd133a6ed27599afbd68bf7258126ec57a764e26b41e682e80c1417f6990458a4778482cc587f620f6d11272c46a7f5e66a98e20655291754074060516c74c8e5bd6b4c4ec25ebd5ab629525c2f25afafaa5576eaf770bd5b58a9d75be57fd10685aea6b6b74541d845e8057ecbd018838e7ee196c1f63b9d583f822e5ff2819ecfe5a6932bf378b59ea7639a7413aa6acd35520d5e03a9fccb9bc6ba939da0477b00d9e4ff090bf4d7456724c4e5ec3c833b937fc8d49c73c7c467b8be2ec90471ceab6a935eb25ddb8924398da18416afb48344eaa9dfb85d152593a478552012c53f6195f54ebfaa42872f0993bb766888fb8c17e3dfab1c6258814d7d96159c7bf3bf8a24cf369273c72fa616e2e0d44d30620ce2ceb10b55586e9e2da474d0be805a4824a57d63a0e32b1369e57c2585147dfc41b46d65090d19b699650f09fb70f49473546d43b797220cf621e44c7200b350d38ff2566c9f57d3c3c1246fdefd7bd3ba78f106cc94ea52e32713f654080bf0c4977dd70f0f870dd6e48a277012f32e783c712694272a7bb235cd45c2e18d62a83e4441a8deb47bfaa4f043300b2d7404ba0a207197260e5afc1e26df65673b70a40a90b197b4f3b1beb88262e3305d9edada806d3729c45c14bf027db386702aa49d6fec5e655b91322c2b66340520fa63de37f7e36c35bb7b7163970b461ec73e798d106f633ee9e5389fe6951cf799fc1bf039472a2b72a07a90a708ffa4a8a006d8d0689c979288f4597ee90876a3883c281334360c65323114fcaf6e8812a788329a62dc3bbc146dbdaad722cb6d18690344b728d2510baf178e648d86e5c7da23a766066a5bf3f91606f59c527e3242a21dc7244c09ee1bd7b290c5261180e15652d9de7d1f5196845d7983bcf4a335f0b407201f20f591cb0a127850cf11462a1c8da50e83218ccffcf91ef7e653a64c19472abcdee51b4d2033cf5d56dedb719b51f9117a60fa90a4019d8515931bd120b23a92294c8b07625457a0617743f1f0dfc7900c18bc3f4ff0a3fed8e180ae38c726552e69fa0f422e0591cf27ca1d8d28094e8c85284d80bfc5d04beb297620a7235ec050c4881aaa11674ef5bde160212d6491a94bf7b8289b2e89b6663b84372973dd536ba6ba5b1d6d514916b9bc7a7d45b26608ed6b5f62a43b21a05d0402aae794179309dee961b1fa8a99e22f1f3d8d3171015a9ea665aa02d82297f970ea403f0a6d25213b70c67916f486aa24e23ca51c04e2baecb913e89c2ef605b6b8389eafcf421f353333853a31a792b17cecb9b049b1c9ce3c0488a8128d3d7704939ce32266c34a8ab0f8ef3ab44d5334bd6eca7658974d51fb1aed2c2f40a7229ca4e40e8a294f5350d6c8aaaf815329de783bb9d0b59abbd77b61cc54af72adf70003d1f987b979a8526f98aeff958cdbca08efe61ed303e00d232b36cd1724def0864ca421c6bab2cec4f191feea4a56c2606abc2d2b838110fb11f370c72c3520f2929eac7460e630f23ec0699d1878b21d6cbaf4e037635d3d20b53d26c4e38d1e358ddf07d45dfe750e87c54c9926cbe205f2cad81d01845666f287a657a76755875595ab7114fdcc7705a0f6374fe2e82ae5c39896f792fdc4ab574729b4fea0a9ea19a11c2ce7bae0162549ab979dace9638cbf88bf3633dc51518723b64e542341e0295e69f22ed1af008e38b942fe4e371d35a3456829ab6d0a07271b31871635f0c9aaafca254c2d0709c68d3b1f247323574eb38b2b4e92fdb72da49f14d2fc72521d0808dfd02f680414b02bc8b3a330d4b5573098063eac47263848a86954c39827b8c44b06d089215e743c6a883b1035527a062a4e41a464f40c85cf519d68e3edc95c8cd05344908045e33a6f5ad845c0576f9a2b2202c721061b75edac978a390047f718dc2ee4fa61ce6f1a2be7060fd33eb855a7425720522516ac49688f870250a606e461d917a53dd168ffcb30ce42be3b04c5fb7720f6b7060d3bf3396a253f989d8ab2de7d387763a46f5b8b63c0855422cbdeb4e4b7b0ab5c4aaaa69370116bb2c2b727a853379cc35ac503da19422731d707a7233b3aca12b5d9742f56bc3f029c52359cfdd052c7258ba56d7a883523a513072e8a687f5f0adecdb657f4c39b6a860a2f998a31f15ea3ff8b1369228c43e604cb76dda98de3c9388f0138b77bc0074d9f26c5bf8524f23d5134d1442b3f8d77263b7df8d02d7f509f43709156579df297b5566dbd63c7be46b0b46338b2c5b72ce033d49d8c67b41393e60f44532372a75bc9f3472f08e2730b3e2230a3cb0728b105f13abebfb1d4f9f691554dda1743c87b32d98dd3721d18e1c7cceee624250eb3e04e01befce6ca83daed93de1bd60886d6877bd04abf6b5d97d0d62d572b434a0633162749690c2666f8d93d11b6f0121604c29b62ea187892436d9af72df01f27ef671a04e1de892701b5083ba5b8327b71e9759220934966c650f11725a77ab5933ef3e22b5d81334943be326faeedd00cdff7a3b61ea1acfe8fabf2f134b59d83b1cba3ef6692c5a6e83402988a81ea2fa24b53f9dab7777319cdd72f8ba942f4098d9e85d4c492828dd79addbfca0b09b42295b09c87d6848233d72e95cb0a4231152e822c659f708d48b068d5e2e13636ddb7d6a705f69893738723fecbb15abfdb0951481a0067296dd3e342c901db65093d1774bbcbdb3367c340ecac33777c31e3dc06e61007dad629e4ad5f7fa2a8b526a5e2425a4d563673ac1e5cc8ddd6de6c5a8ef707010d90fc0c80ecc7d11ee2788e4088bce08afaa721fdc1703b089d1ec986912ada14d59204f202f230c8331cccbdcb21446e4b43e80ec8911a1d6f7812c5f2b23146c1c82d2377004e39706d106311f3be915e66b36ab1f58c43ea4c623b5905ebd84dc5d1c87bc778c4c10ff85005f022fbce572f37e399436e34977d88d6ebcedeacd59031eb2539b60ed66a6d3e1aa32c5b7721aed2a56818a789cdbef5ab15b966cbb4bd516ccd669c42a05fdb89634923f72292bd29105d2167609cfa9ad339d62564d8920f88fa4f9f38395e027f49ca50cf23794a6c0a594c33914d00ad0a1e969ab53dc982458d6da3a71bfe8f666b115310d362c59140871ed2e9ce4644e99e9b06f7294511144fad57d7b9c0ce730141f99fcfd400e4aba5e9c7f512b83d7dd3912bbe8f7ec336424afd8d4fd48e072a60664b403503a8d606b0da4c783d65e89bce393164a65e13f1ace07e98c5372a9b73d19b5feed63d15b4f20971ec2c2ebb29fe4c96ffd0ee05b43c8ad96b67227714259a046498548eac190a869306af57e1b1bc061a63391836c886b16287286b7892056b370b343be4d6a0b3e2d0734427dbdae6b13e3b73eb4ab4ac59372b4d59bbe2a46e183f8880bf7faeefc83900ce577e2613da0236b0841832da5728e0ad5d00216859cb22a9a690a3e04b0a51e5dfa4a98c5181edece4b4caeb03fdb454a0af078f072006591a5dc6d8a6479e76229f3a158a30bcaed0a91bf5872a464c35c9434a73c12f76e3db19ec31d3cb6489425f93df3b89277eb62aeb051555fda4f5dcb5519ca6af526806b2dc22a1ec15b176b4df9a8f1ea75899e9c72b206ed5bdbd6974553c45380f61344591859aedc59ad163f954932503f5263720a26897a3fbe19d4b0d4a5f1c4671edcbec26788033cdb1ef78f1016ca17a2721adeefffbb0481011d857054dd0827eaddbafde9ca9cd9832c0c2cde833fa306840f88b59156118b99f2ce371e337419d28ec01a0eaa9d1435b798e86ced8372262fa861946c8f88ceb742a165eea894866338902140c0bf47323c0f84829d721920e3202b7742eb8d5106ce70739291da591a7036f22e0df5b4f2d7e2954e725eade57227fbd35f8faf1ec4a72e6bfd6cff9740e4efb914346fd8c933950d7298e623130bcbea47ac09475c0a62ac325a7a12739def2a2f6852f417d49f2f72ed8f5120a40fd5da4a545800687dfb12b0a8ad9017d8abf2490fab77e75de57210935fa08c1722db9b41e7bf18b824c5119bcc66308d639ee5c8b637786d4c5df37520f9604cc831a976311c319cbbf33adcbe50bf745aad94524e28f79e783fdfb52d9981d804a9454c35b1af56578b12fd692b790049323032a86e31354a079584151bb1ef0a461ccd31808fbbba1e23bd1c933281907fa28201003df1554dfbb0e19c2d8d96991458cc1cd5eff43abb420a9dd77f4382765a2401f3ddb472171fd54d3800fd02b72fd8059eb479469956979c53102d5beded8d3cc164eb3fb04f11fc823a43edda7ffb957122f2fe8e6456e25813557d27bc5c942ee3c063041a450398df967dc7b49aab92e5dc81bf6923511f4626d5a5b7604adaafc872bbd4c859789f58e53c8ce68df4f9b29444093adf6551d367b505f4eb735c40384622fdb2147acda62a3f9720887a436e24cf0f9455b374db469c9beccb751c729f922bb6cb35576260678765f5c5716d26e3f65b20108096e4faac0c52475472d9c6889fcf89bbf079b99610671ae7f7e3ab513cd646c1df48a3158ca2ebd872a3c44d92e0964b970cf7dbd2e0285d0fc2ffd24fdf2c07b6094e5833c5d3f53df06b05dd788e2245093fe712a9141d6b3c51cf8ab8f3dd517fd33b0ce109ee7220dc61b46459a3f3fe2ae17ab3ce2cf7739a86f7d23afa01b1741e787d46e842610e5cf919acfdedcc1066bae9ec5d50ff3d4e4fbd657591c1999a0a3df3d43e0f016c2fc8e512efc95dfc5944a1461fc756d01705a768d35142eaaad048ab5994088ff748b1be900474cd65735ed55cf9bc65276a505f13e780c03c03cd8c49263d26b94c3e82e91931148ec7e19321dc4df1a8042312e92a28a65c28d2e959dc685fb6a60a9419442763390b707b66adc89640a8194c0115742332bc119f72d115356a6e08d5f33dad98abe6151ea41e2afa92c48e371c977b7bb516caf572b6b5319c3b8fb0bfcf5a80071b9d6297df7a20e25aaa7a8b76e872b6d1b0c472cc8a5c7bd942754417d9a35a301ee4dbe8330f97c9904e3adadc86323eb3124e9f825e07cb9b46f214ff4e44f8ebcb64172542894ec7cd6a935f20d8d7a6e56ce52aded76548f2143c79f822c363a2c3b966755f5ed55233f72ae750eebf1572ff4d8fca68d6140d9d6ecea286ca815aeaef2e94a9ea5b5656697c66e5bf7c72ff37076f3638cc08f790faabf34d2070929e6e42e7481aed9d6ff6e17be1f90e28d981daad690b115b9929492215bbdf08a4dea000bb973995c49cf0057ba472ec87a4ec7b220cbb2e425972bcf99143302abb4132b3a549d5cc131951908172202d29e63ff2a3d87b920c576a42112a3b3db9551f24b5a73ce69fe1dd5b16722dce38c22176bd08365282775a01b49441fc1dd4b95cab721171863f3cf933669cb538f110741ae3f81b4a9571a116c01a48fd1327c9e13c0750dee8cbbe4772c30990515dfc968f9085443c80c86af50c3a94bd0ff5ec91d79eb86be8e08f26ce1d55a59e9465984eb1d6f96ae4e2ae8ae8489c30f5029b5a9b1bb6a844fa722850e6ea552e9e01c2b94ff2e2a56ac5a9af1b2415a6005a2af9b8f7ada985126e4cc94e0f771c5397dc790cd998403b45cf4091947ac1547bc6a83970d50e028d9fdb1b41b261ed2c682c8eb29ea593d43c1ac437bae752e2f6c58d168d4872d43f919d15476a72a298c1c33ee213dc73de2a099688c614d34a3d1421ad4e725782c83131304a6773c47bb0355343201c14e3d7cc6ad9e512dc84e1aee74a722510da592e55d61364753eb63499e3fa1ae8fcfb0a1a3c13502da9bdb91c36727954a79b6bd0ff6d3ece25ac4ebfbe15c550bce1b47feb7fe7fd7dd34c5bb70f9775c99249d16c91702a05c4f1e1d99993580151160719cdf921d1dde89ae672dec03827e1af4f4934790d9eeb1c0f785f74f966544b106332d050d54fd36772c3393a773812aa041662e92c4b0cb580f9bf5c117b3fbb191cc66c6081349572756b3387b3e12250b50067086ca61c01bc0f961b6274ab4c6b277c32ec174d2e96a6344e0c49c499261384c1ff9787944ca6b1da6380e320a1f0daffff5156723b21c8ddd12c2cbc30034f9c3914b1da52bf2bcf3a3a306985ec67ce526e7c72f24f2c1e01c3c3c635e5ce620b6712bd7b0964e72d9c25f918c8999ca3d2ba2b575678b68f3d4f701be9c6036d12cfebe591d2b1a78f34a44f9151229e78ed72a8d93b68ff9939fddc1d3095e77aa2b8f561a4e308fee9ee6397117fe1438272b411be6abce96dbf52e9b252e00cdbdfefbf5404f541cb1b116ca502840c9d4a4d6b93747386c5336864d3967addc4ee6ec92784ff00d2afdd644bf85064c97292f5567e8d3a99b1184a8b6838b7b605d9956609fe45923b0c5a3dadc8ac3e72032f1cf3d9ca3cca98db7bc6aed3ba464c164e070d6163094d14101ad01c0372ad8f253f9b1b44a3b9ff16ceab868aa3a389bbacf24ec2f58fab049b89fd791d1f88139a85de7bd3630ebe325b1f83be80c8996332f0a19f489c380687cb5000973bc29ad899f5fb7659254db824012549d5673ec05de93aa9136beeefb9c072198c3881cf51e81a188417e474de9edbd86e71564055780a5032b98307cc0d72d840712ae973f86cfc3728df9face8582292eea1e7f6ccddee3425838bd12412728debd21fa31741dda3fe2b8035b0a50d8cf8bb35d83cb9299dc24905345c72644ace145bce903fd80af345b6260d5f0f1029261ba39bbd93de5594dfd40e502e42daaf02aba2c6ce0e2330ecac410d388eab9535383f000de8611e1fe672722dbf086f65dbbf1ec7666bfd41d84557494c58935201e9ea4ca096d8c9d5cd72edd5781b7828aefe90b5838a90949add00cd2c0bb6d2825b0aeab1641269397297c5a4f548a125fc5cbcb63dd82d20e511e9d8fa45bc03c7eb476ea2cab10e7269e86b0b2bb89abc0f9bf3875e845f803be57c38a7c291129126b71ed9e00c72d4e3b8d8e419fb7b5c8ae9423290a677335a7639830eabde86079a80db93b326315a57313306ae7ff293ca11c4f8b4d94550059bcc8a5162d8fb59a28621db7242acbc1b350b12976c4ecea7e70f777eed8f64baa55b93c51b8e4012908ec572bf9f665b79322d84cfef331f25639f2a5c0dbb71537437f9288a5977b859054c6831ecac991e2f8da2e296ec5c5ff61bd6dcc51f1ac92e2e2a107f96c0d2b272ccdf1ed1af53e642c55239d1e1f87c1149877b7dc89f0ee15a2b02d43312ca7279a50bb10591db0e1a95096721603f27a5a8ac49640dfd004eb57562a92e8e398d79a6b9df0ee2e176feab695066b9b91d6f0e535d5125cd01aa9e04ca72c5049e1a81a1c3d82941ef9c22ec1ae5587f2d8b53a85d85533482ceb0cd9ecb1440612094199d0875c8f5b918218a0e75d4c61afd66ac6ca920b85a5259ce1f3a4959b845a198770cf8fda54848457dbecbf3b6a7e871fb5df689dc18d7eac2c8414abd05a9cb91d11eae7121749845de0a842ab23eea4e6196144d938003c2fe0a41b41431d28c1e93e06ae5fe8d9693c81763a8a11516593bddcf187911c8aa72bd80e12e2d290a767a31982ba9b97e200fa8573796180e3dcda9987d8f4b1e7134e82ce3b99db159d5b3c137b345ee8f6e75a8b8e34fcca7e52ca9de447021729a99da43e475c8945744b92ccce9ee2da65da4c4e06e92427b0319643e15d0306be2a5c21a31cabefc94fbbb2f25bdd68618cd31d2fa2ec38e8b1a8ca6769602a0d6ee19da2996c895349f84b71ac08d9239cf1ed9a38899d96c3c0126dd63723f0d36e07febf8ecbb2188a66a893d5f468f87a3d31c782c182bdee0ae5459598d5e838d9d4bf9893a631ee29a4acfec961a1c27edc5794c1a5e2495dadff87236eb0236c285fd29f89b26ff5ff584b93c1db72d0516577d8d50c39901349872289b447602c8f0342f603cb6edab96ba4239b3683e479129df4c5641cb4c2c727a28f2eecc7e28e6dcbded1b44bbadf8e09e4582c691b90e65dcd9b0fd9c1c724cc9a890f3ad5e5a532c15ce4d59d2e2523bcb7187246ed38b210504c1b63d711476cbb8fe21f76f7a87528c08b6d1e26b8d081febedacbe64a8c0d2ae611572c9939460780be1e2939a8f22897d5953679061b3fe4f75a7cbaddaf580305107faf1330a91b4881e673f9c45611641aa7dbbf6c4b3630519f80773f815375872c395ac5d3a342b918d6cf44a3e7d9301b72bf054e0f01583b860fc4e9b743019225458d4b68188c6b6e4789f80c8b492d89aaca15cfac79de77be99fef45197275923949bee5b3b0b287071ea2459a040e83c7b2be77b07e92ec4414f99ecd724c01715ce52f736364b565fa7d37ddfcf74fcce82fa4488e15f2c9e1e67c3d40c98c2878d40a990e764207c875593bc38c21d99e35405822a886bdda7b0b704c69f7c23b113ebf0ffb6cf6c257065421f691d778872b9194bc54f505601fce7276b501bb3c589f87ec0cf943922d8d80c93f4171d68488b68e67c18a66e20c72358fe9ce91e776f6ae8d7d592909c986500b4734621d4f0af4fb445a79ab5572a89d25b180ef9e183c04d4691b15e4f76bf91e43e69485820918c2041a66545c9295689c9ea13828925df36b0ad0fe2a2787f96ba57b88a6166bd910b0d1687262d221dca69ce6793538785d95b501c96c19905f95fe010a6757b0e0b4834b050b8c89f7dfeb6a5f75cc92420e59c70ca668b79dc66a380d0c6a46b1c536c8724a4b8edf2191e176dfc36a5f40b7d38e12007590ef21d052cccb361319229272ce5f0194e3cb95b5c1a5eba7d71d8648c1a33708d8b8590f83df9cc4ecd9a44eb63d04887c83e0d1d1aa7cead91b08a0a33312dc51b3cfcbcd9da101164cb17292dd3ff947745396bbc34f06613ef3297ddb2548c671563e3d8dea9c375aa3488d3b2a1aac32442623be75c11892185aa3b99d0c2552d3917fd47c642bb53c729e747b31ec1c7c93286ac193df196bcccbe1bfa28f8677604bc8c10fc44eb17296bc41dfa38f00eba54ed88669ff0b79a6019b13de0640df19d675c216e1e86cd22e5dc6da7e8a6b46b40005bb82a5f0db7106d3bfdd01c0b1c3463464317d7231f031aecf6f2e892b6d0c4ba061063d42e6a77013aaaab93c2a4b752a076c60b14e569cb39aa4b202d1a7e9623f157a331ba7fb51b957d561c34770c94552357130d7adff2ae9dd465339de7c5c96d5b1f6ec3c0b26c2fb1b717ddee8d5ea7246ed718b2154b7aba8a3533cbd936c497d219947b10c4edd6cfe23db5464807263ad7d4365e24fea47e7f929d9cd6a63db3def805e63a1b57d877124131a1172d8f2e93248fc5aa1ef2abfb0b46858392af47bb6b95bcb265c3e7ee821182472a11fad75db2101606bf775836c6ff1ddaa0be175b2093fe573adceda379c98720de48f6aa71ba5040fb9022b67a2abb9b00975b64520f89cec734d80a262b772a14208faf8423c018ed8b590f747e3abefbc454fe301b426945856a37a44bb4101758916cd897c27249563c3c95dac97e8931c392934775457f0482d191ab972fe53c0d0c62fd64d116df36c07b372b9d98d32f16f47967ad62b8897abba23725f40228fe6f4acb22697ffac48025aef5867c0bd69643a95377702dab62c7d72e13ecf3ec2147e0cb5f5bbf65ee3e956f598c9413aa45253285d6c87d3479f729258288468038081ac1bee07c8c5f25e24080c6bed75b71d8079c110013aec7274e4b1f239b4c339e3a7a0b47babf781925f9d75e47e30aa89354c8ed8b26072db418a0995550dae13548ef935c3e56f7c25152ac52f888c156102a59f9a0172d8a393d0446b31a39917dbb8e16ee454f4ffcf931c34e04df43fdce7f077140da9a75dc631f3fda64940a6a5b1570de72d17c05638263aedc1a6378c84b9170a55d317cf70e0109969fc10c1deba1029a8d771c889d6691699aa4c019837187253845fa6ea86269398d199050ffffcf3c3fa09b9c844bb46ec53d52c9791725deaae92dcace33ea3b029aabe006da8c122f022bbbb1878d298974152d446103a92fea70c62200977299fba1a42df8288ffd172e882eae87a9fe9a8ca52b8811f900ab8affec6d2388836371debace976149a5db19f5e6ec39fd56eaf55f7e27219292346c6c138480d03d078930d6292957605682809337ebe22e97099daa924f067d6538827bfc1658c94a27bb42f9e75bac4eb761e1a6f228878bbba02fe725c98a23bf18d666a5db1faaa257ffc425ae4f09fefb8bfd68e92b1ac0b82f172ac5142ea18ae89888821a2b37a30b9d657d32dde6da5f2699b90e9cb55df5872697e04d08b0d57cd8dad622fca3c4838e1b425c2035cefebe4c455d95b0d93260af88e7356cd6f89fd08c0022ec0cff1ed2618429325efc5587c4a47df700335b8da3dd655e0777115d4f0d7a810dd5433b848f3ff28246e9e980e6338ae1772ab4c4310e7e57d859fcddeca3e5eefad0ba39d65ad4fd617eaf3d15890b6bc19adcb430f4ccc5d46f76d7d5294f8fe6ebba1ee6eb71cb77f7301bff7f0b2656468337e05779264faf550850f4231fc4bf5333ffacec6497b9b5cec2d3f146e54ccef97b256ca12fea8edc2a407ed2861ee82082fdbec9c866f5e215ed3c32f72dfdc46fded953306301e5eadca54447b2a8214d6d6cb2a52119439b0765c7172bfcdb8eae3e487c3819bb835a8ab96ff8737445ce43a1b2117e3437d8773ae64ac3f67e3b234ad6ad3908c15ade10ac00849a0cd73b8e4186efae286382f16725d9f3e242cee559b2701d951d40e1b095f7f03d372e039928f74387b38f108729d80485d62f8469217576b8b9075ce9293a6d501f66897a1bea69ab9fe1e0c3b3ba68903377ac2199c6608d004cb4577236122cfadf1934170d24c181f3c3072dc76eea76f676c96e3039d5565722474a28a6b04f075eb8c25eb26cc1deacf280013d7bb585520d356b6b032f23d0c3893b0327eafc18fc7b9c106050a213e722dda0cc6913360658ce023e31b6d35375f1f9108829d1cbd36a3b1a10cc5f24f617b5e14c3d6bc50e5e7846ea7f1ba1b18dadd9dd72f0140b29ab054a472b844be14db929180c766a4675bca517772a8708c6021cc69e83f2ee1ec34879ed272406af343e09eee6661a06c8606b56ea7dff30d55aed53c1e419394db59bb8108b06400d82985efff0150793ae613543b84133f3d0a2da1f131b32dff04a4db7250e8cc313aa08fab15079d90f295e8187454db8bb5c023fcd7d9c9559ac58b3e01112159453314ae1898adaa90e2dc77cc80d8b3bba65feef47a8ace5203826ace88651b28e860479e86643f25bfe9ac28205ba4d104141d42b31cab7411a7724dbcc09a1d71d4577eb2917678b9c3c7164ae9b06073968860151378e0821e0d2850025aaae50183cc1c2eb8d5353d62b8c5de1414aab4c7552943bab389245e590deb28bbdb518f0b7587934c04dd46a881c78278e107a10961bd91506d544edeba0d831f656b9549a88a3fda98e342431f409efb9db2215ede72ddf620306fbe973aa2565efb381fe3f1d0b111a1afde38b3e85f69b66b296b2028737deb6cade59bacc9dc2d4f45467e96a888968059632636241c256dec112ecb2b66410e51ba3e183a5e2cf2a3750babaa5fda1b3ddac2f36d640aa8ab4281467499847231a419a0a113331b6aca996b589a5b1905d128c597950dca2e45441b5937c3727da907031df82f75865d921640453d08ae9482b99239396466343b0fcb216e105918864f0de5beba152afd312978401aadd84cd12ff46bc55a9dc00236386304801ed116badf38c3081e1a798163d54d8ea753458400eef1348e649069970b72c904d9f4b3b94e1f7a19887270d7787b832562730edefe0fbd37ddcf688952724d45cfe70d6bd53a1a276c4a41030d7377b2993cd3d32a1335de9a9be4d5c65a9df11cfa8547db2f11f2922a212f0a0e18fcc42e9660dfe0e4437e21a75c144847100dd629cd60754f0b6f4d0cd0833272359a228b9ad73e1cc53f4cc0057172e8b08cd78619d6fba2b06b7afc4e8c80a6b4a2555074c188ff088a7286b76a72dd135acc10e090cf47850a79065aa3ff7e51c487f180bea33ade47df766ec1460b975dd29d61d3deac995379101d7544de0646c3d86d9a48c0362b01a4ec4c0ed75dae563d8ecf546d6cf558b2c850feadaf3759bf2e9da67baa533be22d2c728de21e445aa078c7ccbd43f20d4355c9b4cc5a851a898c9ed786f6c550dfbd7280260a5bccad76d87f90961d740d126dd391ac585eb37add740b0833ee1f9c180645a27bed6599daac91dda7f1a489fe65e83d3ba4853eed43f5a8df37e9b746d1e80db40062cc2aee7b3cc847757f3cce3b1b85e224bf774188e748924ccd48123ededc2cc5430d1d5b169a7d44df752b25efcbc10c7134188c7ea8fed7347268629951ab1f8624c0ae84161327d7dde7ffd1f999553ce3d3405b6d4647283b88c8395b846a68fdd14f7be32619da254b273cfae80d9561eb3455d43856ea358d6bbbb699544606dc895ad1a08f8efb886e1a7835e8f450e9c0233ee225fe7292274f904f1545289495becceec502bfc403b0f6d5a2d2687f6910d5c995a67273ccce1fa071688612f8420e837fbba4e2b9a08bba6b20390f48785b61ffe625c6f7a32021790969947867fb23a130fd9703e3827270777c61c96c53efbfa172f5201eafe142967598c6d534e13b45e9ae3d33787c0e9cac0caabe1dc098ad72b80f909cd7fcc345e5616279e8a24aca7c7cb8f059fde116cea12c648a418b6bfded79750514404456b9aac35fe49a846e071fb3b2ecffb8a3cf2eddcd357037d666fd86f22a0cd2620a81d2bcbd7882d798cacaaedc82f6cd4ada0c2af1833a51f90f0a8004ef1524b0e824809c89c7f9c313d1bf3b635f6aa332c9762ebf39559d82f661482cc9917dc7f823c3bae1776a4516dcff26f24bb0f8c2780cf759cbbf13c3055a1d3c1ba9d035617389e085fda368701f221b1f4e65f0053d2b1b5fc1108562d697ac79aea94c444b770eef2102cd0e12e31d61157b131df02872b4a0c05538e468e1cc9c3729f58291c97f0e651b2d8ce0b4f63f225a6906d86c80b0efd0e7ff552ce571b4c163f04cf75f2a9ab978575b36498268f90b1b8f7206f200084c2a1075667be1dd114944c17d2983c54b9d2ec478f4c94609b0cb3f4ad8985a37f937b66a7ad9e825ba7ca4439b503c27b072bd1de6adcc55666a7253815386c44622d10c34e8755317fe9d90a85c99ce56f6bf8c2bb00e4cd40972fe5c4ba7a5cbb3de70313256c0f40fe541126fb3043509591f9cee21dbcac12692d1058e851329ba28010747762affb765fbedc5d19ddf44218ed61d396f89720b586de3eea999825f5dd0096b078430c59edf3b55321cadb97f0591c4eabb65ce028e745ce6d8cf00126f6579cbaca97c259a61fe3d43ecebe16538f8809f39c586e32e04114409dae8004fac2ad641b6acb904ea4a741782596e163eba0f72aeac5955feb0e7d6508b01c8fa70ae513ceec70e3d4fe1008f64a9fb6ade81729b0cacb7ce02b0924b8d755a47757a48f17f85aa7ec456e06dbbbb88c8e20772e4a8bfe7608c49eb68547c2a67aa00eff3cedefc1dd5f1f278f9e69fe677b87259c622a5b260bfc857b8a9590b17de421a6a2988fa28b9339a008d6878308b510ed3773b01a7e543df30da07b9bd5b6afb66ea3f83879207da9dd5630d9cc0721115f4704413132541bf03996abf8580a02a96d6e09203f7d0bfd980231df972568afe889904b60b707de7409e51acd8cde6988a7cc206de5e13b16e940e3e6dd19525945b4cfe057adc7b199f294ac278ab873aac01ed1617454c7b7f0ae8722ec7c873f36c04ff27b3f02ab95e1a29e677b0fab315a46e201fd1236f9fd972b98729d9847f274b291b09d2a4c28094df96540cc686c7b12cb39c8defeeb6721a42f94c037749beab26d3e7ac11b73de00dc26cb21f2728787da2084ad69b72e9a4527156b3660177f3019cdb4bded6e0920f558e8d0acc6e9f9d18fb344700adf0af025176001ae9283d1bdedf51cc23c68a134610de918dbd95b14d3e270ab0ec408ffabad3406bb9190a119e2fd0000018d5944851d454c5adc34c4012339e31a82de17e0e5f694a144107066d8c4a4616f8711300d1eb1562df716fb1720f19a1ded0abacb793127886c174c7522381049964994d16024cb1804c2882684e7b6cb5cd4af23339528f792288427ecf27374bc86d4251c9571bb7003d88654c2347b1b9f40cce09a6115a2b43d62cf936bac482e0ecf99a9e18fbc8820124325259b2587d08fe5669ecfe514a49537d8d205d135c5835339fc8c801dbc5722a6837fffb14876a59edda4156002f96a35355ac44a27194a60461c6dc97c0729bd827f930663d8a34503c1a5c72cd08138ec714e85750503d5d49fabe8ef972cb1ad914dc0567defa78ca5cc1dc9514a7ed8396bd4d8ac2bcd91356b79bcd72595d183e3e45194b5aaa9d065c4b52999253f8aaad2ac478711878e4e7a7fc21592908b0d33f6603ada7276fa5b0a87cb0c11875f061683c90cd8447daeb287256eecbbf0ad70ea74b116822b2b6f3f37f5efaf80380e692661f6660563c607247ade32b2d7413024566809c5a40aaf3208d04e110ba9aa616736129dff3f33c0014c88dab65920a7bda2d5e4d83abe65a962ec50ce2f3ac2a391217cd2987403bb8b27776caf580567d20f2dfd636db59fbcbcfd961adf95555499cbf734a5ae5ea61c169937d64a648fad8ee3ad8386b901f99d58c722ce27f03560fda6672c3eae87b15e208e75b7ca2df31df4a643a6afe75af8e282978a816fcad90597202e0fd046d87ba8b1ebdb46c2aadedb996964276bd65a01fb9b6f5c3bd0f0472f2407c6e49c72f7364f564163685ab335854f35368c29ad6f1d78d6ddebc807205c3ac66a340a7942dff21404df7a9e6f3462d6c457c1a8cd0f48e3ddfb237722d66c3d241818ce9871fd3d36935be08ce192533901cfdd429a1bc951a706a3cad2e276e37d9a8f5aa49153f1f42c8d650fbfe026687c26d0d70f9cd49b217159c2e2fbe49aa034d0c9faec776fb31177a93c4e5e598242d6e84e97201d60272927848cdf83f4170b85e73d440a1b205ad562a13848869e18ab5ae15e261527225c59b8e1f580e57ca93c2d711a7ce9a0931777c75f92c4645a3e5ffc6f8db725c37b7200992e93af87fd9be1a7314951deb3a73740a89bc6bc010766dc2b631cc25e4baf6044bca878ea25516ed2e01f991402fcd0be7068cdbfd3164458872cb9ffb8f8cc0e4546f2d63adca3d399b131433b7f22e971046f289d8518d6430320c33b7a9c919169f10deba0d6edae9c65ab6351322d65f006eb9cb31cf0d72a99569154c55cc938db0f743b2ac4fdfd7d8ebcade3fcb4598a77ed89bfba072281742522de55f88dfd46e942cda3bac2ef5a6168af04d357629ba5390df1336fd1457836e46be8d55d51fad19d720fa8f02594bc6c48d7cde1eab5e4345d954ea38b810b55d189e379dc62c37195e06e8d6b5e9d90ab8f4ca7b84a29df26072ed39675f7446ecb030f5c205ebe6de63377a16b58505a6aa02b093f2d2bdd672d64ab0ebd44ab597470db998cfcd145513feacb2118f1068e5a954e14120d60c9200244434e23f76a25d2248dd61c50a2ab63503507619370054bead4057f3603a1af47303f33c3a4cb1d4283b604aefa6c35207a80c69a86a775157e4f20b24194413dbffa5b077805a4e0e202e6048bfa731a6adbefda442ec2655a81290720b0ffaa40e0b056f16c81ffda69f15a1e14ef5371b9e5685b728e92a64929a720f5c566294782b22f3c61c1b3afe2086a4c0a53d711eef9e665dc09244fe3d72bcb6aa6dca4c69e5a610e2492ad3d0023e2e4cd16307e3d93bdb7a24dc613b72299d365c198ee95db6e96bac70c842ec8d90b5f15a4f37e8f010142b1defe772fc894f9af097c600a84a7e5662a0ae0874f72d09e6874b4c81de346d2225e072b128648a80cfa9707c6bebbbc103737bfbcd37deb8f71b0742b60f44af55c872299a840b7da1dfc70503143e378c881bf3ae6e9d00c0c9eb206237256738fc57fcda0a6d809da1d40f2ee74ff4162ac707fe895d1ee3feeb4a6089725e22aa72419c68e824247b698515f45ce291aaa92ea7e176920e10a419ec667511f5e872c283a1af9975c51d2eac6e764c3881812106270ec49fcc396cce0a8891c3ff0ee45ffdc6adbb4582bd13a60abec3d2dd8fae94a2d1d1867c56eac73cefa4d972985f275b597c9199f14dd8691121fbeb11bab06ac80a23f8b5dab20a57199b6e1f87e6656826169179e9ab8f490f283f5ae6e6029b9d0789028e452997b5002cb146b22af5c84f7c6380e93cf792a08d04e549cfab4d49e6973deef0e4813b4ff57949e8d42e167dbabcd73fb2a3e513b33bd4674b6913b0a25fc1f7e0d73372dc1ea4634ae9627037b288f9e186a1f77d3ab575d2ef737ad8b8646b5a80ed01e9986b78d183b216e429dad8c344c2fa9ddd9d35128e14d13844954a63d8817210e8c3d9dd09cd78138ba1f307c552aedf4ffa198a121a1747df4965b65d6d091346c725ad21835df2e1f20767828d5db378add76917fe8315dd6bb6bd914b0f62c94cbe20dfb50bf0ffae9360bdaed6bd2316735d3ed7978eb5e3992db43e72b9b52b004f7d41a645fb54971eb4f228aea3fe3a4402db5f7dc124da9a3faf3877d841b4ac4de25c35ac5697745fbb9874df79a0f267bff297c631739e62ea72c7aec1fb06e0f26821e87941464aee927ec5c25b8437f4a4c78f898f2b8930723dd889ef8f78195ea9c2c28e9de694d3887a98e864161ed27e929a994fa1187213866af12f0ecb4784610b040295cfc158f7df6c1f8e46e15a0181f47673d3729d8c31b554ef8d3d8f7be05e721277c8ea79a2dbb8d46de81290f0217a722b65e1f508a6f8b7f1f09821e7b04c0ff3867ef5cf4dc4abc93ee9da9f368bdd4472a9ac5ae44d536aa8a36aa3bb7d8f6b37094758d4d210552ce720a401d4f84d722b67d693d90b08cad8414cfd8c044b702e5a0910059da6461f8e224c259a576d91282b791da6014cf2e9302a1d0e708427d481ec9043ff40690cd378d93bb772787d668d33406c34ef23f7d7bf024ef6d3020f33e284861020e9e20e807c17721b058fd22c3ee3864c8c30534ad2a8c9d5f83346c35dac3e53ca5715833cef728c9dbd95f678e8a3d2e1b4b77ba58347a5835ed8e7177df748af18f29e371c72517321fdef86c752426e438ee93c4b976448d159abce06692910fde8ed4c5c6e7ac592f2af9934f1972dde223242c778ad3450afb89e1f126bb74fe1dc60fe43bb110f2fcbe400d5ad60c7e18236d1ee7bd160904a1342985df7cae27e1b1972929dbec50e01a8ab5bf377aead41204b7c670a45ed2e39b64cbc3908e435852bc2e7b3e155d32d3cac37f911c1497626db02b485557ba6c442910342bdd586638fe1a1b25067505e94fbca5209d7fc801dde2d16d095216e75dfc8b21387ba13f3cad3a48e2f298b9e5d1a68aec1e2a0cd7fd720067c8836f42cfa5131747472120b038dbeda652f0c0e61c05b182316bf63aaafdb0f9db87c1c84d2a8f5d321f3941d6ee4634db69f1bd7a3cb8f90a4bee62182407e22f081cc6473815de46db07ee42f488ffbef427a0252e2c9d58c447fba0f0117b8bde173dabc46b0ff008ee08e96a3af8ff082b075d9d590cd617b1d623f3bb8a827e8330e9af6fd7160cf5a79098449327a23ae9ded550ce168e4154d419cc9cec33fc912a9f4c2cb72eb17929466156bec551c2e8c819fd1a491f3caf00c8e24297541acd65bfadd4151538995fb1dfba4730c30974c5099ee0014ce496979d473e4010e667f818f725b1db291b02c7f30c483b349604ddd07bcac98f55b8d9fa684c882b5fb81b072fe4a8906f0e02f429512c99f1b290c5717ecc9975c5f296a77192ab21c8df2729aa18dba4fccf22e1e217e7f98f823de143810d526208b9776bfea2d8eef31013b403ade9d525685e0c4081a8f977d810d99ecb37d9fde9ab042d3685e18c872b0024f14278a09bdc5429abd13ec8360ba496f15ea748c63969f83410e083a58e6b89d565190fd145a47ec6363d0aa079f16175b3aa05b93b5ff2b756d6ddb72a1d9d4d3e848848511b1e4b9132883c68ad3dc0802e2057f21f4a17796d8d272364471c9fafd3a723da9e3510f84fe2a0f0e35194c421567948113fd1810f572caf199e240bbe4927653ac10f642fab0f2de0a6f9b1017645e59f6e909eb7f72a706aabc0247312f9b4034e6b786c05d4d1a1f64ce53e9bbcb593ffb1a96de72738b2c033665886d40501237873b066c572e80ff5081da95f507b65dd3d30a3e11e8bbd78144d81a3164e9afa424d3952e14a2ea6fd7fa444daf5ca2bb6d90098afb725e584b94917fa35a529665e689397cce81bf71c5aaf8da92d2e6be457227865f8d9896aefb9276cf8473bfad7bf4bb3e2c88f333546cddaa15619bc97271f9305db20285f809e17dfc73acfff814f978460e4dadca1a5103d601a56272ed35ac20b61c1d0bc1c0fe4c8303ccf1b53dcd2911cb532624de62ce34c60d72bc1827d8dbc92f4c86952d2a136d55260185f7037e3baa7410eef1533d8d0b565e8a4db8f97a645b9aea7224b1b192df57d7c69c8fbdad85a96aadcc3d33f372f39330e8363851583c7441b64e3c55ec509bf0cc7ac1d089ba63cef8d6f7b672c12ae21d80054c341c4c4eeff3bbfee9a1dbc3983027557cc92b1adeeaaf86726838dec5ff4a8bc0fcdba9874b42a88bd7448cee5da662fb26c1777525e7197295a014ce8ea702b759175990a31744266d80308575ab57a3425fa4acc3c6bb28bb996c0d109059e036d50c5a340fa0207e8ae4fad1d661e225176e51e11c4153cc7a1735eaab9d00a570b63733cf5d0cdfde2627cd802da7ee2c1425d3b6d372ba4e3ed5fc6e36e01510260a080c040286906e8e4184c8f2c478963cb57f587245ff1f61c085677b9ac79ec17982a933f7d814c6b9a90bedfd7f3130f3ea43727439766ff813955f955cb4917a4479be0da4fed03a7cba615cf96eed21f19272b4d7700d474bad0768a8a797044a2628840ba81b69fb1dfd883afd755291e472950b17cc6a09bd1c84c6f80433e9e6f4a0c4913c98f56bc4f62e474056f195728591966dafaf91c55d2982ec439a6882b0ea3db3ae150118b8ab8842d4ac1272c9ff9fd3175a141ca1ecfce9adbe849413681b5a8ded4c2df68d1c63f2cd8572e8447b7f158e382c72de364af0de2704aa4097b8e62954db08ebb2ac3f134806438563f0e9b2c6a566221a7e324b4d1b094c884828a45856400b14e482d5287255c659043a56a360d74161c3955211130cc156db4ccddce6468e3002ebf98a57f8450ee60a4015dcdca956d953bb6890bab6ca72f4ab366d5290ea541e3cac726c825aae288ecb9b495e92ef5200e069bc195842f815e56408a487b2c3094e70f8a221a78c4584ec64363b74af9cef28d666a13069b7610a50017b0ed66c8c7213e828d55289907ae43ef2350505d2df462543fa56aba27987b9ee8efcb4ac7268c50e577e060762c313b895428990fc3e24985b4b460d48e791d76c6863d872ec0f633dd696ae517a97719d3f1d4dac33670e9ebc412b020b06053cbcdf4e72b440caedab8ea2a9e4576c6378b51a9e6e295fee4118ff05a353a5b9fd6df772cdc0a030a682fc0ad23c1163e75c7cee839daef9e812cd7fc21f5f9801ff8e72bbd787a0cf1fd50e0e496029d901943df1f3a245ca5cce1c17c3e489cce8f2724fc9c6855c75fa2cef585552587a4e22737250a48a223864c2621ebb1c943a103929e3a6a4002f06ad0fbeeaaed3dca9a70f861c58f8fb2daccb5fa33d1ad84c73c424f9fcbc669c541fa8b2c64cfa23d021dc6be7b5462ffec30d9fd43dc746f06866c75d40e974c5b427db15da7fe232f377411966f507a2ad52e8f01bfc7267b98df67d25fb389f14cc996bf46c2de59df1a4e0a96f3fff076fc504ddf8724b97a770e3aeb8aba082f441b90a12a62eb50db3535dd95eb315cdc79f37c77255b8e2b97834cb79d79be823ff8dfc6db15ce8d432ad230e18b948996ab5b4204832b579e12ba23c3af0a3ea981444c643d52f69f475fadfd9c2d08b54a3b772540a2a1845b4f30a4587677b94a32541f381bdb3d6461b12413882e3e2ee8672307f0f95b23ef9bfe1aeb19f9f76af1d381402d59636a723a17378c60bb6ea2aa1bc2e86992939e7a2fc40798d1eaf010baab1c9e5b16ad27dda6fad2ef45d72155096bfe9b2a826173d867d0292570a60e255541d79cab8dfeca0cb9314e4686a4e00aa1294fe45bf1cef4eccceaa1613195fbe3496434a2d5240f9ca09de723fac7e54543c766b9bf4194f7ab39d2c9055e086b0a0b8fc773d9b4fc4f1311ca61962284a7d9571a7f05301eb3c11285c629a1d033c79fab0469d2791c29372f58b59ec8e2fe36bd98d66b2582ebb1d8137eabf96bd39a650cec243dbc029269d3bf0ae3cf62650c779b357be4ba6552873cd39a3a6a43871e62f578ef23f72d56e940e91959304fc44feb7bf4c6defc32317e2f833b179f0d53b97a6b5ab46efd35afec76c8a341581f9a81f45cd89335d77dc6b8905869b43ba9312a06872aa9d193254e92cdd76c3eec9c55decf66ff53d1f71245ee25794ec0e6efa397242ddbbfef8f9ffa82b6016a41d98dc574b74d5f188bd451e9e06d1b4a2abb91e35977ad3613bfc27e5be59909092d9933657145f12e6a2bdc2f1f5f3dcb32f723a0b1123e496a7000a78fd8d48138af0e3df6eb689f550960c7dd1b8f3b3b872147d5ba47d2a8227d91a0c3df88f910ea587b32979114faa50c266cc21fbea4345a396bcadbb03b44ff37ab7642961d6b78102a3b1aa6bb5c2d3e2f08594dd360ce8417abfda5a51eb1fee307cf5327d1e45d17a246c83e7170149c34a116e251ec1f2027276158bacc520bc4ed2f82ac3b99b5440d48c2701e372caef89a41d0b428bd2e89a6cdfda4ba3ebe299cf51ce474ee9813b719f8e069d13d5227f72c672eed23bccd10e3efd43bd293d4b7df686be06c0f09b3c71cab96306399d7234fd55e7dc23a1215dbdb828d6e7534c36af03f945535c2019dee313b857724f86f70244a4f1b113487aa9e37b8675ca74531d79159c0e6c84b6c1c92b899472e323b07cc4e206c83680cf4791eb262ad871332eec800aea262afe0a624ed372360ce5320258fe00a53f12883ca7a56b525847ad72959121e076f060c08039721ca237d1c4a9ab9c37ac99b712863a1c3a618fc3c50abeb2fab8d4339e7cfa72904e757d95fc4b9995eea70e12605d76bf383accb103e48fbeaba5150bf39372e4857ea191b04379e952e143d101cdb3bded2adf4b407a6f67f81a3caeaccd72046bf33fbedcbb128af14eb49e85f8f86682b79b8879ebccdb97bf748e7f7665aa43c33e7b76df50aabece583a38bcd0164b5dbde868429ae81de054c619976887504a6f7adce458e1e10ab2e5d7921bb2d0a6b5cbe25341f788f8664f1f16728a2e9ecfaca8659485ec19138a34d92c3c1c73d143324d3f832678fbf2fcdf7232f323f863e97f8dd0c6cee863fbe7c2a2bb02ea262cbb70b2f55c24e188ac24870bbda30f8181cc697b9a315f160528cf1600c68666ed5cd39efe946170c07267563f46730589e8a9c22ac6ad93ef4c1f11d3899ccb57b7d0394a82eeebd9536a9e258ef9fc529aa80eabe282438e0a40d41146607d9f90f1778756f30aa472436d80ccc40d0a5f016cdb77bc5f27fd77fc255158e83bb95a8722af68162b729559e3208b07f0f470295397ce69e1aa628859e42fa5bda396362f4d0d275762a80b1f12cdfdc21cb8d7f8d690014c32dd62c4f293385ae24dbcb10eabca2672194174de69c69154aaa549254313980356627f4d438024d4f24604238eb32c31838b06fbd0dfe767f2da39dbc05b817464a9b712ffb3c8871fe75ae45173944c0688e553bbd927ad4bfd24de8ca7eb92aea198579fe96f115f0aa70204dda772e923f0bc735ece7ee8cb913d2429390810ddfd7aada277814874b9d8f5db2f7222b086a60be8135c21df57a826c34a0b3b42bab6a50ad03d2a36dabc180e27724609c0746271786c2a41d8bcece842ed5c08d322dfe85305ca0354ac7a1e3003389c024eadb7f1db6242bd651067ea115957da7ecbaa46bcf71ca8d3235bf93f28cb485b6d9799f47bc7a88e7062cac04bafa65b43e50a89f7c4472440b39d00a914dc5b27466769f07503aed7bd21f4c1da4a70f285afe46226d82bfee44b07ab285051ae90cfe02e7ba64a0bcb5280547bdaea4cf6fad01d3e4ef2a5baff4e5e575ec0da7662e9bcf2121953280cf425afe3b86159897301e3c6048992b3441de1cee83752c8140d61ce9bc42d918149046c43cb87bd545c5892009e370d0e013bea4ff1bd5a29242c9cd0a1b881311aa0997b3a77ee99820595e653d3ad4619dcdb9c0747590a03fa7bbe792ca296fef385584faed6e89d2078110b38bf7264539e4c5e0348cbd7e4b65f69434f7925a0752ac6c063a854720fdd72c9de72942cb3226bb111a35eddcc0520c7584f21eab6e4759f490f260e8760112eae72c99b4b82fdd5095e639922bd0a40f62f4e83d6a52ec944a665ec5129d996207278c929470129fe876ec2fced8d8d8b970f30a7541a02bd31e5ce1b54a7a03772d3d0a60d61fbb73f502833a0a726cbb8cab2bee989b19866201ce8bb1f45b472de0a58faf5a1f368b2e47753c1aedac94ba2f040b1ed419fc327d93d5d42fa7209ed5a2d7e0eaa57417b3e5a0ec7e5164b1935f8ee2392f4ef4268ff4a63696e430248966a4393067c503417fc58b3d35861f3f713aa59c39fe0141323236e72e9f0df78634c88331098acf18fd675a67a4a7148a64b0f5c2890fc88b9624b72006296772e9a7b5d15682463e782fff5c638a470e91f06b904a23ed734ed617248387dd326a8dc19ebdc15302774e2e51dd95d74bfc8cb80569975e7cd2bfa0096cb5b67beb376b8dc241c66f5cefba0c1a92a17f0c44d21072c2c5415fa8c72247b5c39422e6b51bba090a9ee0af912a7a302f35be38608922e48ddbaaf9325a95dfa730d9f6e622a75e159e96b4fa9a13acdcb840fe868fbffa0d6a645397281cd9af120ea7d5a1a736680b3b0693796e602690fd39b3e8e71cda2fe2c5e72e78a720f10dd75ff644acafcab91c9a1642722faa92a6c2f713962e033f62072e5382d5804597ee8a04459058b09a1a176414188ea7fb3f0457f71b92e750f72674c40303e9b0f8211c092930ac1a4a4684389bbe75f6c6cff9864fa9b9b16729ac7e738994d1e5bfa52eef9c3eef61a5414960c2f291f071a5991b053cc7a5d687f4604c0903b25df81300316792f46272eaf2ab2a3c4ae614c4ddb4fa30f7228d11aed62d51dbb316496fb018b5ac0ea6fa942d0fa8d72eac261da00d824727b252c4dff0c2b58a059d2c2191243e7a82fa0948ebbd134c350a55834b3df722ec4875b7d8ebfc86b628e40def07a7451c37e5e25060cbe02f5b33442d5223e9ac529c9a8d06345663440417fb4443d322d69a1236503a9bcf2802272213972fc36333d6e3f5f606e26de0666eaa53f40908ff13b6d333ee0a0a8327353c172b40d4772a9fd4d41c8498d1407226044d145f952c9cbc161695b627faf377172e6277f9e24458a538b94920931aa78153f12783dc01d96d33015dd288977b5283e40fc8f4c9b33214a2b06c7fc134df1f3bfc1ee3218ac5256f7f3d89fbea327a488edf039d1f210b37ce61456271ea3ab35b9408d85c810e9a62c7979cd9672bc3063f418ae2643fa1f17984569381d7ed83fb1f7671848c6ebb580b0df1c7292d88f1837d73a28a9bc1cb0c50f115cb93a7d5d3cfc3e0827effd2d79977f2e155739419af43186885e1677fe958d79b1187bc94e4e7354837a513142b97705e1c7c2e4d73ac50570a59f2a1c43bb25a296fc008e9372c4e0032aa1c4a7f772213a6edecb20b256b5ac449e1116a223357d3032a7fb58fbc8be8cb4c6d03002e28fee77452ddf0b000dd40e99eccc7d496c31708dca86eaf2bc50a2aa0c716cf595ace761cae6950c327d7f1f1bded3c2b6fa02fa802c7371b71df9b86ce87283c9ae98a9e8783a44d662cc53917476fbd7e56b6dc96250b8146244467e9c7242e8a8ab254fd293da01250f1e923d30585f377028e8616b67c84d5c74f60c724e4fdb685665268876c91d28ff49bdd7125afbd5b6ca5cc790e1761ee4a4e572311f78b4186610fa1ea2320a553670f3f978718d5c97510b73e1ced2a0aa17725ed6c27f4a7630c6e52786504e7b49705be113853e48ea16a56fc86fc761c15526aa05382f6c0618542e889e57fd34b9ac1c29e17635fcdb8daea9bad1b9be0963f4fe4a30b3d14b28382ef4516a541223aee7da90fef9baedcd76ebd1df9625530c930102c1250c7001ac78c18d5401717233732f1fe338ce866304c08478242d1f29cf54e59a8aa4d9c73ec119f97a811462ae3c484248656a432285b836558d55e31bef64a6ffa229ba58ec49d9a93f1affe3c5d76416bc1b5d63e60bf07208563e931bc97248760a828c0598732041b3538efe3b1add0d7506dc58a68215a2ff8086b33e74c40ed06643bd33bf99830dd13719cbbd991eb61a2915b84172c994cfc8f178410b94f48f22c3b15af313bcffd4da86cc17ee8e79a6f2f34a04c4be9e17191f956b4725a0b1efb7c9395c65437dfa9b3b5e8216ba39ef8d8c72d2a8796f3d7c83899f970787c03aa052026f087457491ffdc18b9f45aa1cd468a263889d791c960400bab02dc7014f855025a0280b20dccd4eb819ff110c675cae4894a585e62abda8890e0a7492fd52b38a86fc79bc3610b707f1f7167d9572a759b4e627724bdaa769e41193ef299c82df7861f05336fd7be53b5b645a1d72205069a1f93ff336e5d29d65e6b2d15a32efced6dc82e7992e5d3f1508466c2fd5f6e9ddf2693e4b14c87a5a632414e78b59b2582ccdddc5b826ae299e141d72811fcb4d3dab60c7da863cae52b119e47ec1a555056016a5a19f5a128bbaad72766e06256d17a7d7f2cdb143cf32f2a1366f11d817f1ab63f4e39b9939ed187238655e442dfc508f84030eaed2ce9a98f497f375ce2b0f817621007874867922295550b52d703711170f6e6eeffb2edbfa7a94f4673453694acffc4d388c7d4ff003fa3c32e5a29faba4ac42e7c02ddd3bbf351c77c6e5a93068d89f6518a108e933f49f5ac66ee701540344a7e3943ccf21e0cd2bd65ee8b1304ee75401211d2e14548f6952abce914ef4c3b38a16ee0a7bd238ce92993c782484fb00135f727abd67fe0c3966cab84adc93127e69e792b7d1313ade4455daeb609693062d7204123e0ba5f3b012965db003d5c958750594b8f654e4c33386aeb920514a6304f842047f8bf26cb6613765acfff8eff98c58c8e9e945b409a38ed56fd5758272bd000c61b1f3c95350f6fb1628dde48cf4206824528dfa8326a2a8fb10f6e83aa314490d602ee34f3ad897fec405a86dec7270dc2ece2b7796e8609fff3ca57292076430964a123a428128cb242e87f68f30fde8929ea117e89b236d949fb3020d21a9fc56fff276f374ba581f56e0911a9c0c3d30472244e5edb3e36cac986cdd5eb31ef7e9b134b7bb0d2e3553c864a663fc95e01fb9903ee6bf418beeaa7276f7993f051eafed077f863a854405a91fb4b8ecbe257fc3229f1c72a5d7c43485721b222a6685d85165fcbf397b1999795a781a0817d0ed874049b2068fe24c129854979e1be0230e5acb6424baa0c2e393e480e5966a74e38722d79047967251e223a4c21ad59a465bba86f94206ed30851a94955d285fd8c7c8ac7d6fc76cdf739b5e84434aa6c12be8a0269d0aedd6094dfec4b1fd471648a1e7c10d8a0b758d2bee3f19dc0129ba01cff38d9e816c6f621c85f3fb6b5a7b2b423296bb7211e067b1da5f0f7064c36b8752cc913a12bab2eb1316f9e82144e228c7b401678bbd89cd92fbf12394fc4d609f2e8e59a996a3f77801d6ee17e354823fe5be72bca0cbc4d6a7a5dde064a0283fc59830240ecf947dd2236f36c5407470b23414b81b7aa86d2d6ee36c05c4edb062b7a81815c9dc6baa3e2a89f4bd8b8bba25722668a3caa627fa333491c7a3efc205649e5e2e419aa5734c39712fe6c4a21972ed49c560d15b9fe419666f8f0649364bbf9a44a62fac7646a8d6ba6219ef713cf03249a64c1eea7ccf0f5fdc5594c3ba9dc33261ac6875ba3407dd9e6219bf725bb51cb244fce61215a7147b8fdaf1b5feafcea8932714762fd81b960e5bcd72a7f3f5f756880ca3a53112cf82d37c3cb2667d57f99fb4521ab9002b8a3073724249aaf22326f07b2ebcccb666d18b7fbf57cba8c51b2f938d0490aa4ec71c72a9e5ccfb4b4dc5800355c4cf7da5e927c25fe09f78aa1111d7013dcc286df072f0d7cbf9cfedba2c2cfcb806632b41d956cc3ee5ca339e639e159ab350bcd372dadd52bf0a542abacb39d718f80553634816efa71a3e338333f6d94b7c95422e411d18fea56054ea4666f16b58cc60c8711c5c2f5c5bbcabacb4864f42e14f1d89fc92473aa45d8459ee2e88385006cf5b37fc51f677fce94004ad9d17322072db8894843a25cecd436746aa14abb19c1371aa34841d90cc93f37d4fc0f7f2283f39f4fc1733622f4c282a18d3effa2fd14fd69aeb4b2eca9bce533070f04672b3961ccf14c649bf8c784d352015206b6fc34fb88b9c8d285e96c3cd6895f51c7181a5acd7566779feb38b46b85064716fd9a2209931c2fb9675f9e13b175c1254d3d3a070130b5ff489c6b8af1115383940b0402d4565c573299e71e3d30423aa0395ae81cf6661aa81deae0bab46a53b6abbabfad2adee3d9958cfc639b550ff9f5dcb6a3930af0d59d6b8eff0ebc1a090462885549c9e52bc0a5d600ce272025ea6f137cab17807c2d323f2708e6ddab52771e713d7192bcc92ff68fc1850684df4ff7f648e8910bae821f2873061f25f189e6fac2d69f808527d7eb4d13e327827aa3c4597964dbfc29e520922932007ff6966f4f8af192771014166cc7235a975985d07127c49df4da0fd12b3ceb2a9dde6982bf515c5fd3c68ddb60972bb6adcf3d73a142e37524922e4be566e289997b21acc68ee8a7971207c2fa0232f4992202462bf0e252857cc4330706d5c5661389e865a0303d2d820e1b9b24a4c97e02ee1bf2bf06b33f3deb4bc3dbdd6a82026ca7fb5362f061e654a841f261a9ac86a58b38f4611c12f27f6c145178417d8237b16c946cfa053d12fe4c972de3368374ab187bff1254aaab5e2495b521f5fa9fcca1ab9b20d52bd56ee67723e0fe205ec581f98b7540af687b37f451341068df233c785555da74f654e8b2801c7968dc4d27b0f6e12cdd2520143c62fed654d76995b71e71daeafc9befc1a0678a93538fb52a51c0547c1d9f27911e40df46d0371e200d22c7a90be73824e89e6eebf4f38e190a2242e7cf816907496deed0a04d9ab74b0d9cc23cfd0e872e5347ebdb2cdfe93ac69a45914604bb46293f5dc0c0d9f214b99de6eca6f6f05592871dc9673159dabd7019a541e80a0d9804f42db890303d659d9b7502a4b728955308772b72d0a39c4e4e3514161b2c02b77f76c266210c9bcae9e588d3017207e5131ec9e31f1b06a09d3c373344edacae8a3af4c93c80ebb2eeb444d4f6d4db0aec5d83193868009e82cc6f5215929d99dad9512e768620d2c1382ffee72e2aa7cc6ddf1d191c37b32de563814ba9e899e0ae5ee99fba5c269ef42557360e3f9e468d6d5ab35d15c55eead84c1e6edf5d4acbfec7cb50c6c82a6b1705b7297e1ce03193efcc117c4eaa60a616e7810a86507b4b88ae29df5654a54567c7235cd36e93c7f67582fedb1de52ea990e240ecaeb0f9df37e4e3d3227c9abc1728c992cb39469b4504373b08443fa961f2d342abcfcd7a346f7f36080205c21721aadc731ecfb23127722a92c0ef981d802729a25e82ddb63237abf2c3c0b813e850f51dafb859066b6dd50a5883597b5b431680343676f81a011708249844572cffee8c7c590afdd6f0ecbc59bb35621f0ee78983f2f1cdd71f04c6996f247442ef5c6c6396ae4aa3f10b6dbee13922233ef7b06c8850e0d74b64130c920707266e170eee747082bed6f94323105da18a0c59e7681ca12ed946bd41cfe33bd5911ae14359e4201c985a36643df4d6b220418f2f2b78b33e0dd6432d5f668df4d3410dfaa01a1e7accd923da25775b5aed07e06990d8a0ad713224aebd5b61472bca71d73342c810af375c14e55fb262ac743d368400ee54871eaa14dae26265bc68b40095c6a83e21852ccd1a601d578822e6ab8f6cdcfc8482e5e8b2003ec727f92f9ec12037236ea619b681833e2b616d68c2dae8981ac6fc908fb4cccd772c8ad087ff41ca37a2e44f05921bff7a8ee629f02dbc434d921bd267c5e2e157279ed56093edc69c89b8f9a266a96156f6a72977d5fda8f5646c3644b0795426327fadd472234d66bee565b83aa48e41abc83b1105f80aaddfc3d46e6c51c3608c1206161e1288c63b368527b3a96a4c8d076fa60ca3d8424edb65ebb7f2cf77225f36263227bc3b07534c776aa201aa0c437f31d524d44c19036d31de80f447258e422c34bf172c5cd6e80f0dc9142c006c005eb7ab7d23b5ea23b4b541fd672f083caf9bfc567af8ad6699f00b5df24bf8d4fe6604e7a191fe2766ca821e62082a4456e12e601d9e660cb393a74522b8f1631bfefaa4d57776e3c2d3f15017280df93b9ba78d55cdc06753cf243cf1821964220c21eca55600f3819bf0cc448aa9698ac5ec1ffa1a1612bdb74ac68539909f53a6ff2d45ec22192f5fcf49572c3209a98215c3d5cc83ae5b9233db5ee4d677fb6ec809f5aa8ebcf7a7b070221fca671d928246955ccb9d93ddb0796d4a8ec44753e4cc09f625cf4b3c39d8f728ea36d8f53748fd544d6343211723a99164fe18ada28a04c00d4b7c840942642a87ace480b4c9cc6f544789be5d3d0391cf3bb1eef48d0668a256b680767c3241bd97506650ade28e8aaba23d418058d35ca596873b2e57b7a794e64c460e302ac252f5d335027b2ac7088398cd2d77ec65d632045ff23938406665645b1b372df1efc52e932acdec488c5b3c645c955a6f037fe48857f6b018959393a750b72a2d092358a1c97dbb1c761fdf5379b7f39d3c2b368def736d55415c710a21d728b4eebbd867059d2dbcd0f2c57ac16041ef6fd58e4d9994770f0953f3610726a709f1bc73be13fc8f038f0be45a2f1371bdae4f51faf6de89c1e468680034872d3bcdabc3a6cce2a2c3968ee2af3f87e49734ace7e76a17fc858ce957288e672035b60d250392f1f6bdc912ae1e19c71b8d39ad96210e0ca3520dc8c97beca0ef9bad78e07d6da6bf9b6b7e0244d22f5cd5e5595697c171aa685bf7013fa3f72bc944024160ffd2c9cf1fbadc4925f97bef68d076275a313c59dc647005f837212ad3ed64f26a4098f691e673eeaec5518a8759741efc3cb90b3fbe96a0c7872a68218872b56ebe3172dba24a9d68561e73d893249140759a251b35ef55ebe3b3ae9179035d5c7ad66b8885fab71d069f3465097dce554ad29e333eb051cd872fe2a4db7776cd23a751cc2e2ec5fced327509575b06e02180cac9e67d7f55058cd136257e0e471ccc2cb74d9503b205ab615a352adcdfb4c044a73a34074e06cb6a5bfaf7ab289231934cc8db22a07745a6068c8ef9eb311921d44356daad072098d69507651f3d2f4d36023d6cad48ac908b78b72dd5c73cf07317b51bfaa549185f61c81ff4a563963b5ca86fed8437a7b896665494f0bbf9438a197a16316c8617518079ec36cc4dea7016576d0a9fb523dcde00fee2ba8d626a998d5a272493784e468b92c3795f66af39b15fdeeb7cba7a32f79ef93e6cb1dba9991835258006038a561c0fd7054957e176ec539fbbb05a66109c000002a04ac5b82a8622aedbdf86f7da271fdf51792112e1e8613211f96a92ef521d9eae0a4041cea72e061581973ca1f827e7047bb732bdb353c39cc73c2bbe2343a3eb7bbba6ffb728b0f8dc41546d34665e515fee7c4dfa2cea9bdd31998b5e1e6b1a8c45a88cd72a260b0951523db47e162ea11be225dcb084936942840e7f2defe3ab058a92c0e77dc16d5cf506153237317976773dd0b7f4a927d96e093e9660d929aa5ed3f6960cc3043144cbf7f01edaac55726377d752efdf804eb7339ce3fdc49e949ac24599b9c1dc6d84297f632af7cbd1f9ac6b550fdfca924410cc6ae5ec50bff1b276f46f5fdc5712cf8f60766d5408bcf0abfaf0baeb12527774489b3ee132bcd15420e4ab7529b4b9dc9a2584c72299c7241241c86aaed05b228096a6f58c5e45a21f8324deabb47ac427b129378a8915938d5b5dab65188644666572835e7057251e480cad45d4a384c1e5e640324a5194a3db9d43c67f48d8c9c029388df6c7218e38f4deab5ba62ac14dc3076e24075a1baf1eed99479f7c20e4defe487ec7233da944da5e3a57ceefe578b48d4f96a397fffd91df02c51a1c1d626ba21fd631d297e34a3a4800b50e41bb8dc3739ef89b8922a9b2c02715e25513b39afc1729636d127264aa7d4d6fa97c6c2b52480d40ea18fb5fd53418f611e468d436f6a40e2d8ca3b5c6079b9a350f052561cc01cea69be21d9901c89607433f3882926bfe029991159d7f8f5bd4a88cb2728e7f2ec4885cc6bb57d0e83c3ad01004d1c9b2b048ad15535859238dfb7c925ff08f01f30537998323dd8f7746c86397169806562a04104f4db5353f73f116ca89db37e697ae2e83aaebc24b818d3b0ce7291ef66916ec0268ce8860dc9e9a3673cca237171ffdc095ed01d5c25c34654216c6087cfe9bc198da5180873a0f816be9c94ee1d141825457bfec12eb353b84b9e6b65d694fa1c9a021d4dbdb41fa5c0e2bb09e463313a502a1e85b8db8acd11f06a839db16b453c6e521493e1bcb383d622f831e39263a44a64a0ea8017735b2da604f913967e34c2813063e150ca95b86a981c06c6beb3d396a6948b7c4f4a7a8be3401276d6540dd7e06d618c4de6f67ebf744176985cf7e5e084e1617b7225bf0b750db704ca391222d053541eaef85b077e24db466c8941bc5a6ef577725f31d7003f39db05c39385c0565dd622d6ad8f2c781462fb26d77faabb9e7e729eb110f971d3f570c2522132fccbfccf5d87707e28d8f255e938e92407e003727fa1a3cc0d5d105fb1f0ef0dbbbecc86146c2f00c430a84cb8fb6e772c09807297e079edaf3dfebfb9b86d2921d5b2ed48a0cd98628ac6f4e88dcb34c6ff9562732884d777ec4134fad80853ef62c71464378450087fb99a6f6aecb6ce961a5fb87c7b06abb73b076b39fd6568d1df5447838cf54f00090ff87a0b668de50c4af0308e2ac36e5e7b0aa718695bb259813d93642dee4734c573b85f257057fb722b385ca0862702845210331df941606ff8f5f00b3bc2f232834dec827c9152460fbc218e92f786d37a4996118231e80be0495fa00cd710277be7f50f123e4745cd109505adfad0f60485bf7a7cb62978a0723af3970a0f627a384fd20fa1174ca72bb7de11336fc69fe0d92d6ce19e6c48a45a5a8bca03d762f7ecc0b70e607201cdd438da865447416583197b06301a6cc784525338ebb780fe223dea1aba727a7f87ef11ba8b1ff42be5c153d8d85089755563c98dfe134155f542153e80295e6910598075cf9e57e3ced44b099d6f282fac42713a2d8e494326233db4c60828ddd1e786bc6e091bf9c4b70b785edf2dffce227b191faf0f5b8cb04b05fe72f91efc39cf74d8f5bb5a9940d76c7ef9105015b32429091fde41cf9120219c72c417ee0e8a4ebdae4ce87075e07e09e5621a9221d5435324f9ffd6552ad11b5ae75f99160502d0e874904240f4ae5db696b5f88efadda42b21aa5e0d13b54010b62e392b65ef8e68926fc25aac5fa2a99f52a1cc061e1ddc56c6f388580c4f708162c806781a965fde55f8d57b624bdc2cc4d262a4a746496aa374947ebf8b72d91ae6f8dad346b5c58c15a46e45cefbcfd881758a3c123e5bdf7fd0eeffac3abd96006a942695ff20da10746d8c44e00e831d9e159d8c7eb512b785ff471d72679ac3cef32e4ebc3bfaa5927344777d6f4795f258da649f2e31734eaedc6872281a6c0603c0c2a3e2b4c985fba3cd7d11553bf7831b0c2c78394de1859ae42c30142196c052c98c1d52fd6593e9e888e8a95e9a14ca06c863cdba163032fd51827bfe9826504cd0ae31d8cc5805b3033a97b06cf8b787f98fd4a77a31a5e9724d3e9cf5dedf7073c32ea6d91538006b3ee7f267b0bfed7bbc0f2ad29a81df1f2fd5b32648769e28fcf4cbf1b24d0e1b5fb1328a16282d4c4575e35f00946a0b37f53619258fd646d724334004fa8a3a005912f5ef42ce6bc83fb3384cdbb8147bb430bb9d39b121a0f7f19512e92ddcb475c1a304540c0d0b492c2b06e25c72e1feae2af94ecfb14c9b1bd10ed6cb1f964ebaa04649eae5ef193aa5eac143729162d4a9ca8166a6705e6e0e113a9a22f2dcbef0473b85a0b55b2a31a7790a6429b8db3b9339a24c2c6dc086903efc35782ede4aca9e5be048249f8794aded1907acd18eb99ae5fa03622337e2102dda8fe34886fbb73b8abee1ecb0bf643572ecc3319b92ee240a381a24aea3d1dae339e0d576ee305c9773fa7ec7e04032535e9354ba4703c0f6fef85d17ddd0ea8374af59017e1a1082b55f7958cc21511752873cc4f1731a946be7a2632012f991730f21f4c8496b10bf2c4f66305aae7263397b887bb8eb41f839e74c89ee4d61163ff794a9aaf8636136fa72bc37f717db97aaf4eb032b2c704873363de9809c6627f1ed05b543464d2b92d2a1b91672c3af1a7b7cf02b6b0371e9ae6cdf5b060b248f04e57ba7b18dd98c2fe74a06727c0ee09509e8b1bb9a51742a2634e0493baf16fbba48701f9560de5cd804bc399a37d83a1fef56a7d053132687b1fd2079ac225c4a5a19e25fc2c7a8e11aa42ee00a8af15b740db4f2e97dec45a9545a618c82d1fad2afe027946dba338d283b7a35b19a88ecefe27ffd2a0555268fb8264da1e3d31d88bb13cf9933b1bed02a6482de1aec69f03626f996c32d36a9eda9fb08acc05256cb508c5fad63fef072080b7fa3aabef40bda5acc455bde296cfaa9bc465d07f3f8d074cf6ccf58c372208d9c23cd1ce5d3082d5651d7c2f750099b339c4dbf2ae4211c364d9fb2ea72dede7f1dd68d96756561e61c914a15877cab3140957b3f0ab853d27d13c4963870e3b3d415e54c39d16cc47e886dce626cb483e6b13c9c715a6e1d96db52ec7228ec0d28b5006d540625ba1b4b03675b00ece87e6100efe8f619d54664afa2512d336cd5968b99eb8e8f8b637365f40c4607126b7f58e2f34fb6faaa8e41c8178e6d60191e5a1386b98a8a9c56652407eae4c5b5b701222dfd00ebfcdadf9e72bf4baec3d315cff596fca48b8f19a0678d9c4b36059f54179dbcfc7f5855277227306222c68c610b6fcc53b597ab5641d8a3fd867bd76812850a802d42cd457283dfe31b3cd2cda6cd1fa729eede449309f6c7193b4b83b88b152a7537e5043fe0ef1813fbaaebcd543db4f735ff994fb157873f8e3b007358fc1b7b02ddef722bffc793c2212ffebde2b352e8b161c46a843aa599b3b01f89bb38203ec98b3da24f741778cbce988cad8c4436718659ecbc2a46a9bdf2d63c4f70c6d8fe127276f4c810e062b7bf561ae098ea31735cf430a9b11939735ad5ad8b3207509a00230dd4e9b59b8957d04e26d96681a5399044f82bd49e2d1457429bef636a5772d027a036db843adb7427932f0e6771956b492f28f4b461764d9f57a10d69b972fd5a7cd1115e738613ce14c45b059ed50c1931b816d580802ce16c0d4eccfd72ce719925429589ba395f3b5958e7c788979d41f61149f2b44a96b8f66af90a720933cf81db8b7163c0cf9bde2a04a5a03885298ebd417022e7bb349f7dd37172baa12a92c4aff504c632d0a34f2c040a60851ab993f8f00438e59326d199381e6ab3767eb234fc201d23c72601c93d138a04fbc4312c85f69810b1d20ade76679ad275e446dd57e181b181f033696f72a2ae7dfbb3c8199df1a538b4e497a1725c3932106b063bb6b5cb1fddd4975dbc2acf62d30146ceb0bf23724099037e723ab8cc0e324d40c3e44e2478a31db171c7ce0e42ced299b342de8863a03efd1a2dc53aa06b6d67a867d2b827fd533efcc9fdc18c4c122ffc48d35b0011efd73ac173fefaaa07cdbc0eed233e449618914df433df008d8f9beb537a311f34167297264fa560ba32609812a3ffdd9a664b1548087e17c95ec1a53eea38ae5f2e454022f7ad89579b00e34edea8c084fc91b99b21e2253647a2238d0747d10abd728a0bbdf04fd18a88eb0fbded8e356717cf65e966818be664095c1e8634732572e6df90f36adcb6ba53ff8402547fed377d828037f7bd07633e6f6e48bdc6a4722dcb52bef5be7fccf316ec9d192c277d32348fa792b7ec0d19d8345ade0978429cc2b93af954e311a3aaca8e5798518ba1ac021b3d20f55ca2354820851b85552d2eef3b0b244cb564a380078edcef77717e7046d46d73e2e2b69db5521627600d5e2e9ee608ac46c73953cd86eae22c12d9232a9d54a4ad969ddc6595618b0eb77a9e1389609836d6547f13df869ec6088a25841d21625c43431e2e7f814e58e34cf5cf2b39753636accb4cac42b6b5e4c65b89232e29738092b577da416872cf1ec6d9a6da68539aa4435a88f04f34b450b57dea5b6964f9724639557c683b0883612e8c9a7b34a059490a387000a5b790b91035f464a541a524b80a038e72d3a97dfad362a6e7439fe1b385c314f5738a84feef8092085be14843016f8621c7ab443a3e2f054df8d4d7c6f55ea51c6497c24e488ba9963992ecbfadef4a4bbcc9674d3a67f34a29910bb2d1894b751a230bbc36f226ce0bbfec4875de76728d47766769be8ab68fac6775eb96e1120f9c4edee3f3d7c9879876147b81d1728ea70ee7d49b138cf909d2bc3ddd1cb92a7e666de6ce0bf1f0401fc21de39b726db7af8e3180f998c7054ea1aeb60c3b08509ea43502846e2499012d82cb9c72bb1a48ada0ab3fdb0c2a5507ea755913de617a35bd704b747b3cc4bb007d39726971a3457fd2bcd4316abac3d74efacf2149f7d3583547faef7b31a0ec609572ed7bd6606ea6eb3b60dc151e689c5e6b458c5b8ed6538f86bdbd2ed29cc63e12d137354a094fb9e6780a8f7e2b0ec27f4171d7e9f01eca718cb34e7436f853720fa0f7a64d60b1860ce9a7ceb2690936924b5f887d88a5c0cdf7cba5f5c54972af3e52da2053b61424c1dd9de38d66747f410f36e59b15a73a1b516c52f91c72a6dc1c3a5e19b87acb3a6d02fb09836c7579fbcfd9ab274d5aa158f8c5770a1849dff96bef77805696f1b93650de0ca837bcd861c5701569acbfae7cc9f767723803615b81df9917fe543a916920c8c0326aa8b6152fb2afa9ddef895a89b572affff2403c62843503153a28b18001c0c5b9fa8b93dc8210d38db709fa8b237262cc628211f596b4562abedeab2928af83194028fe896e92ff11066cb55217725a229bc909d3e652ce0d60af134a5016996570f29e9566c8929e92424923315260800082b9a34872c4019477f1ac4cb05cd3aae3f2c3146211a035272ee169362825943863023ac146026053da7fcbccf77bbf04cd6045b2343d9a6250fcb8729446ee42cef4e4530d4f3e8a7b6cb9085f3336f73d6163c44d5722606d098f728121864ca9076a77efbff4a4f2072989e618ae327e76ab2b590aba9821083a311b2c79d8320602b79cd09fdd00095a448356267c65b4298875c8af1954f6a1088286e12d35fa05323de10b00aadd1d5fc771448e6e1f9f7ddb214e9386b3ac72cb73581fd7b296399a88f2e07623bbbedf3f76f4deed2239ae692b4c3403bb4b0f9e9eebf2d4c8e3e374d2b108327972e7fb0170702d54bca43c29a44622bd4e8309a84004c7dd5a633d9acf6210218c0ce55f071bfb9fbd10a6b4c426bd7b721058ff03f386056daaeef04d5ef8d65c38fbdbbfd5d76c4e7be4630aba82a66f9daa3913af4670966583c88379b0152ea7324577d0e6ad9bbac5fa19f01a3372120b9f2e872501122947162d86889568f422a47242bd3c637ef9e28294933972a5901a4afb74bd63bb80ca681f4bc7a294243bbb6bd336dd41b0e1b011edbb72feb2f3d5b85beec9d7c7a5febe41276f707287cfc1f431dcd0b42ea42ee7c9724d1f6e5cf8c15c83d16609c104794bbf9d510f37b1c12cff40175496f949502117bc27db3181c05ad5ec7e3edc63e6d7dd998a06ca62c37cb8d9f59d5e0cf072cc9b7c96f9a7070736872f5ef2a7f3cd9e18a1eda249e20be6baa6042a867e7217a9f13acece541c2a249da57be9fd2bea92d90302da3913a2c011f70de7957224dcb99bfc3cf6f8368b0694ce19db821d7a7ed57c450750fba028d1874c1d7296ae15ddeb2ed3e6cb556391a0d3639748df25f12653bae59a13b6a24013f5396ee9b040eed862b24c3db056f6881f7fbdef79370a06c6a230bb09e4085275722d08799d7fffedb823ee86eb66848c93efd55877c2fb9dbffade5b93772fba1fa6a5360ff06d89ddee8a1fb1fa477e858e69764d9d6507bfa392c9512d5e16599298aded260cddfd82e32d04416d6ae0ecc8c9007211b5ddd7b708cc7989a472dbc96bd293df837ac3b34f1849e2bd266a2ae423b4625e90091df11075b86722b324ff6096f69682ce23cba6faa41f86d00ffd921a588745d50b6d1139f3d255f12c1b05dca6071a32403c71294903c1e1d6846bf06bf28df1f1cc0f3b7e7b08ce35818ff81f34612043dccabfb9e736b49c51706fa5493a15c6085ab51be33a7067e60976f97c3bd31f0297aa77b203647170434bc177ced7c7df4230832a725675c0ddebd423b0c0762a66a8c9933595810a659f41d585da52021646727a58f29470faf1482f5821fd0e1f156bc46e891ba59d3e687675f4ed3714334c8672b4f543a49f1f3004330cf51bf426ed86a5a81828c2e36b42dbc05411bbb1d15c251849e3357728863812b06895a8115b5dfe8cf2b4189c834b60e193886373726085fea5788685ce329b75f3b2179855a2c1969093acf875803ee79e82b92f72b51b0ceb87f7aba0fb17f841cc8f9c24cd5860e6b4286260e43199707362fc00516270b8646829bf937cb3305fa5d7cb7c91ddd253007c8e7a7edb1044a89a6d9c2ae78b5f90fda52712c96c2d30cf7aa4763627b6c5bc140db32ef3d2ba8672e0efd10d8a64364caf4b111b4317e9405cfb8816b46976d49c9cf4fb78e3c272b48a7ffcac4960bc701b185ea84a10f86ea2e2f603f240536dc0e6c8323f4a72bce7bacbeb9ef7081251795b7dc3dbcee5487915aedea81c4ba6247368aa35728e1871294491b22de01e7ef56f8d5eb8d5ee23d1d15799dc75339fcf45d2a052dfeaa04156408d050458c1a5064f46d09b415298186ed271e4a5f4d74bd6885f7f5f486c27ba1aa11757d9b29950995b727594aa544d598821c8b145f677cd72991b3e7931eac738021df63d86cc1d694ca9c8985b8b6c8f99a098a476dcaa72f423d84ed151653cbf6abffcffcbec1d9e7d57c500f66a209eb6a074476d997296852fa415bffaaa98e7f83ced3584d245eee252cfee6ad04785c1c187051635622c6cfe0b33ad633da36e3e82ed48d5453aa3559aa9b953368efa8615ae3272af2ac6f07ac37f156b29d2eda5e855ac35563c487ef067628b5dea6705fe02728d69675cb13cc58e02f527109fc068faf08aede7cab08eddab42e8ce211631080bc5cfc29e47e00dc8ddd876be1003e0517699dcac7d61515270039a0acdff721bfc1362cb19e20bcb95c0be9612d9be66089b4fdda25838983b905949cbfe723ee22ba54a4febbbe2a8dbe0716c9d4b76709ec0aca03937f25a4d6a92abb572a487d52ad7ba9ff5458bfe56f87ba3ac609a1598c9bd338f5d63025e545cc46c6c286a32a20cbbb669e804cb1ad553047df17cec7f1e99f44a3f5254a6e71661adb2ba45a5c8c46825197bb91e164a300459b1e1bedc693d564951fd65c5ff69f4ec34241a0387d31b22c50a1ca11a43d3955452fc1702b3be0dd879c4e00372b6fc036b05dee2fd6a5e475b12f124d8c9bdf17a9799edac684f5b06eaa43772daaf988f33e08960a9c4af61b166e908695a918fc75882f4c8d3104ba08f2b1c59cdba950efc7b785735e4b9835c0b69d5c2f9d8ce376f8d0d7a5096f52d60729be8bb18d1f65e9c4ed65af5eb54e244fdb7f539e1467758264325c1d705434124e40c084eba4a06b4e91037225cdf18628f84196baf8a93450db8dec1cfe307fda647ed26d56c2e6addfdfffdbb0b30857ca6181344e6e1e0afd7ecdc949d04f3b1712fb73f6c63e256b90e0f4c39108191c5c33eb2e728a071ddde45c31b3b13ea660950f18041ccdfbc780b29922eaf90a899685bf6bc790c425b38615c72387e4d3055ce2bf75688b01997b3720213beb93636e90c477d2c8f483d792a073eef4c37bd54cc773fa07a7c724b29e4801da49cd808cd12528e90ff3235b272812f131cc9365fae177d797848239909eb271a1c7e1c03c56f8e95c489adea722e2abb141a5d04d2a23c23ca2c48000dfce821067d586cd8058e8c53f2c79372e57b3ac751781bfb09fca1d12106d225442eb7c56b9b0f81f4d28a3957f346727acb190c77f6f532219d625fed487a7fef0e2e54e94a2bba4e5689ad4ded1372d30b18f080d018bf8f2e599c419b4ac36053c3b9cb5fbf32c257a0da0d4a9672fadda48262763bb4ddfa024347085b9b583eb014628b90397e40d97d71d4487224931811c2007cbcd2bb321c5dd1adc5f6cc4914e3667a1f7de47ceb88f1d767892eda8fdd8217b26350abcf598ad6fd53092949864aa3953c8bee5a8c2fd92b1331626c3b04c53a44e8d409c0686d1912f1d53b960b5c7fc4871cb647373b72ff5fca231a6afd868941c6668a1214c27ef29b0c4bc99e060d93eea5a8728130ca877bef38bc4a241dbd7fe0926259ae2bd5afdba00673922230c90d545a6e6cfff71865f51c8b714f321bb01c85ad86ad9bfdf14ac721038887ff571112cc72b39da0f81b52fa39742e52765c12b98b01e3ac351f78b5fe5ad51c819c3f1d72930acc3cbe17808133849d98a8395c9ec7bfc79182d5b77d89894136ec624f7268a3f4474125029dce57e1262cf2f683b3ece80a8a9e0b00a269d4e1210f1372ad88b14fb2da20202b38b127ac487e9ef773b1b67a142d13bd215f08f31226127a40ca32abf59b442522b6d18f29feb6a085efa4b9f4f4a1431e69ec245eec72e81a1277b28d43733a5fe01b63c21f7039e274f1d21c0e30eacef9270a3b2266e742835cf07456bda3592cdf0255c5c2162cfda8fffc44858adecff64289ef356551a4ed7ec4b5be7fdcdb00be2cc0051ba87354afd56471493f054221450618dca28d6792b131c50bcb9c5af2d6b8e83b0841bdb4d273e1700ab8c93255014be0603ab0ecde64e0e39df3903d56f12ba95bd12ef7b33c7bf68548244d754872dfacef9308f24e94b9c497135685efaff32d54f353dbfa12a80241e4b08fdf7273c017dd696cf0410a53ec7baa0f3bccb4bcad79935c9f0a432a1b6db66db804fff01a0c7dd631ce11fdd2aec93333f6514e21f255e90a5abe2af333bbcbef44eeaacbb3a8a3d59a2c609208c3928bdfd63847a8cb14c345f995bf5a79cc532c2cd53e835d46144b908e57691c6615a820d2b80039927be699f57719aebe90720b0d08be56b133e05d35aced7db3be58d2c074d9f646e8009d28f192956b5f2a801a4723fcf81fd76f5b7c2651c010035dc7420fcd4040b2e69d3c4c62c11908b7d47fdadc66d8e69f2529cc2428c7308fefe9c450fe69b68d719ec3ac0a4c725cecffcfd5e57096c8728b2c59e4ff16ac73696353739845ad758847cb94f17243b09af045d6f74ff25106e553ed9a2db8b4dd73323c709ae34ce4c281dde572787df1aacc7a43530d1710c49dfe6c7a8352981c6e092ce3e3a53f905f06eb289b41e41bfcd59d30333e5c6c3b386932f335a08949891b3929cc6262ad6632726d9eef80c60f46b2e4ba6abb314dfbfc5f1672d1677f2744370ec8ceb46c590a368f11f795522fc73b00b4d07d3b4ce01ae795699259f85eb350ab4f8b2fce728ed7fe21d3e06782f7c02fc5fbd20fbfb0d94dd39e58f5a91c5f0835a7fdff0ea55d0052d315559ec582a9270e6cc39d29126f767a5d820e0f99fd14c3b04a5bb507198c21198ad88f1baa58f9612a623dc6d1e02d1d8c4e34b95ad788e72c7220571233c628d2fd300a994f336b6e8594b2ec892d64d71f969292d993058f7226f48f4e9f12fca17969421f189d80a9a71baae3cc72b91f0b2348e77af86d0365695db8e2413fe7a5fb1282c4b45b8f9b783a1acc42c2b6c90d9b7923b017728cad5edf172961234fa64a8f4d1d8d38c57321be2d37ca928b64392aa1e231722bdea53663c42256a1d48843e8e59e32f4a5d971db37b48784fc33035f5014724eddd7f26b486e06b732f1fffac6d24504bc0c0faec0ac917e16cc090c7f1072ecc4292282bea711b54f974ebdf3c0db418731c5347ff6a721acdcd6ad301372caecd238f0d2135c366e7e7bb3b226e750cf94c7b64fb0dfc3a7702aec712e72df424040ee6f2d8d878124e8f13eafa6e6ad83b779aacf68f79c3f14997c61397b5298a89ac04749bc0d9af61c45d7a14454af73abd34e3b34f8bf471a51fe72be27ae33376a05d7cef6aeed2e906db6353f800bed2f8000fef075268265cc5780d7a48cbf9b6be42401940fdfbb73d347bc1df8c734357e1ddc2611d0525572e61d368b43b564eb2c5e1123d2bac7cb5d9592df7bed7cd35ceffc243db7d719edd09417d2ba7e99579647a943241b908a6df477569f160bf0c36fcd3f350554d61ab08e938d3c490a598cc3ae844ec0a4ffe851819e61a39ab9141e381f4a728562ec3df8ac0cefe477fc3ce6afebee61c5ed855b38e0a280f4abeeafb49022f650b39e6aa8417097aae836e048d5322034a2054b61ce165ea866d110ac7b72a12c70e563dafca791b9e1089d978ffc3e101e65c14b5e7992aced85253f9f3bc11c4f76bd275c54b04dbd4da36dd4da2c55485b04bd222a7c0ed2e472ad9c72d6edca2c76a40ccb43ea45111f3ee4d36daa71485a1bde69ece498e146bcea726d13b464881f5fd1809577c53deaee68d1a72b353dd62d9ccc2a8a0a67e5e5729e092c83ba862fca5b253bb0bdd7c5086ace7f683dad6376ef1336862bd2b1164b7b780b6e7201a633e2a6eb16cedb43822831bc197fe91e664489891f900a5ec3a17911771cb6a4542380e471b97f8374ee0151e5dfdd0de23ccd106e807372ddd995fe54de63512a060eb7d5c5e9ac6536e57bc559c1b0931067350acc59726bdbb4eb96e96ba936b141bd8b2c9987884d6d04b191c6348c328a56a7963072c52e1ff0a826df4564f218720e436a0a0e57f8dcfd6dc9f4600b4a309fda7b7275518ee39b81b5d57eb77b43bc2ce8a4e875c97c83140673935a71c4778f6f1eb101a7ca2e2fb53e61c0c0765fc3f535a5c7563fb8dbfb773071386b32a5456b080c282c881ea22ca8250f001ed9413d1cb1895eea3d6bffd94c8508f09fc87203d9485a68c24b4d413f9c74d599394d336981f6503e643ad5778ff181ab5e7262f128c6c945b7e7b4b917237d5ab69418b88fe03c41f544256686dac7137372f22752aa235dc8b20f804e58a46959f627108b1c09499ecc43cdd3ebf4216f7156df6060815aec40a356e9f0b80f59c6db4e1f8f6ca7a946f0ee909a6c931e22b046ec6d3e72c1139773a96a1bd2807c39a1684f0de43b80faee49753d028d725cfb0e73a7dd1d242b5a90867cf90a5e9c763bf584faf9bb4bac82ed70e888727865379be9eddb806cb390a59a5786f9ca4008c7a10633a9e730710380b5b60703fabc365948dc386e05afafdb9d3d389f923a26e74ab407498124f4a059ee729cd68bee628a72e9bf0109d4a7470e074c7046d5f3690ea2ac0056227d944a726fc49451a0563d1f60a1072916d43c924e25b97b7b90d7cc099a6fd5d07e1272ba3cdbd50b0da32942c003c0b410e2ce639c825048ee8ec54b7ec271cfbcad17680fffe115abd7694b5497b8ca42ecd9ea5cf07c45079790e61ecf0a28a01172f901a725a6df65f0fb683e76a30b34a6c1bbe53e124012f5f67cba1c15afe472671721089c98bb0b05de457db93f40bf397c2ec61d9467cf7df3bc2b0a2a6a7200492367c4e4d0dfc0655b0bf72bad6980f60ef3f20bfed4c4033c940713c53cd2636fbbd0b9e577fdc58081261ca760cf56d07ec71e74cefa07d4da14901772b1a229a4b71e411aab8d58605219324728a518399e2eafc3a719b123963316468d99736a53efa522c8fa8b008c4b27ffb9d09db60fe9b18ce0256d2cd56f9767da03921593688dc6e701a0a1bad3c9b8aa4d0dd436abafbec106911719d0d0728f1fcdc90373aca17ccb47f392186a9557db4b9d21025d99eff6b1b1e5642572833f131adde5d51725029d2bb2cece8c3bda7d5b1f2727749f2682883af8a83c46b7cf548108ba1704f4f97107ffcbc8fe67df6502103ea1b806328e1a102a5103f25e01919e5186dade3daeb7414a046d33048a09e732fd43671ad2044f39444fe427cb69cb5e8aed1891380b7c53b9086213fa9b345e1a2687648ffb226e1c41fbe9ebadf1dca15e8601b6df62a6ec5726dcf3f7fa886552b5033ab8c71a72ea23d8459e49a5f3db74e96bb127fa800fd4fca8303ba1bf737e9b2f6238bd07d4b798cf672be51f33f71a8c5ac1c09c80b5cc854da62d235c177e36ebde4b72aabf5d1769a0fd1df59161abae735a841b438309c868aad6d037653cf2d69c725d0473a4489f141e340e8d4eee2e1901c509cc501530bef34bd63bd4e4420d72c411b718a2fb0d5b6d6cf80c7bffedf62b2270f09602255e203f6d77496a036baac5f58933a0b595d8e35763270708acfcc65ccc4353cec644f6de786413427222691f6c01757cb2e0228751991f3f4626badd17a52046c424fc16abdc905b39c3c591e68e5282abf3a69724e9c5dbddc76f4837b3234795ecdd4d9452fa235c346db5e308d2026add7696eabc6019a51f6e3992816f5f59c7f54d3d86341c728b2b90038e3cfdc8bfc1fca9fb5a5af2fb9f007b811a16fdb7ebc7b6a0a16a68735d4c8b78088f71b2a494f963261f7c40f61e53e5b164ad0d910ac14b1ed30765fe5bcc5aa2bb9ebdb4fec1ed50d3f7471361626b099aa854ded2a7184b7e726df3413d8f07a460a087a09ea9ca35e865a57c7ef24c13949e860408631c3a4dfd193e05d3e975ac3b24055a0c5a7d8861ac2f2ad17008bdaa625210c421f722ef86ae1228e52a7315d2ca6a3b78d7e8f584ae9c638fd1522e5c587b0c7812364b086beaa51e13f8ce75c6b573e1ac365ebb938577def14688038e00ebc1f172f89314e9b61e2bfa14b469bbc3db21b32b2cc7572416e6ee53d5dbf37338ba7224c2966e1eb761f1a5eb5395d7d8d550d08d9fb7e876acb4bef5299c2045c6729897e1b2735f432a304a220d646b2a8d0a150fb13599e638a1756518331bf47226012123930c36eeda29c8e6451c3ada9fd77e0a95bc8a6222f3a1051b8e9d4534b33baaccafed9e72b00901b535ef9193f5444c4840342a87dbb4b7bb1fba72c5f303a160411b543aa52bd108f2a31e6470d25c4727244723fb9eab961cdc559165609cb37d34d3ddcde90e4bd079126b972028299e945733b336b9706a7629e80f7cc5924deaa728106ea55c60c9ed7855b89d5bc9b27246aa737e0eef3f2cb3f7a0412ca4ba79386278b06cdd541e226119c90911bfb6b839f3e7c2636e7241b7a38fb41dce69d1a68e01754574a64bf79bf3fdb56c5d7d5971617f9a1272144bfcafd9bebd9152f02979f066dd8a6b3dc028d5979a56c073da43a28ee537670d50d50514b602f0a38dd0b3f906a480d18c235d2e88a812dedd21adaa0a72d8f185897dbf26204cfe404571cb900888510c3d17bfaffb6c4eb1a002ebe772a285f612632830c01c226f4fe459a468cdfa86997cc318b66edff943db653371b80117b7fe937ddb599d5ca3f18c22e9b02fb52099ce703080e168104b2ef77252589649f6cff15be3a359b9669d9cd6ae29872a4d702cfe74e47f1db41a7f72f724b9b3789a82e4dbf601cc34908261ca1d227dc7a85e5d012b6573ae28f372afd805c1a5953f673db68a834a58e7ede58457667a81b141d73c8c68adf089724ba8d5ac3a907967cb5f81b48dcecd8a329bf52beea31ee8cf041e7ba2fd34724e65d1fe5692b80c1b71bedc61a484e97cee5619738b9955f10236e1bc28f84b8ef1fd7c60760603c4a7f3e5692bd20ac2ec0fcb5ef93e5172e8bc80fc1481306541078f58b0f680c03dab77c5ce738d099851a66c0d900fa1d6cec911a29e72a4213bbc4063c87065765042a88e8e4beedc69a275380e1780abe23592d5e7722059bd57e747e5c3ca564221aa8a653210f3bde800a11e09d8c529d09f48987287f810dc1092f2a720e15ced3b74802a6e2befc26c2bd7ed26d39f8bc6890e6cd29a2358d960de26525d82af9daceb622c12dc0b710209e25b793e2050631072b605d11439580b3184790c4ebd30c2922cb9551e5fd8cb0244d84017952afb552a63f60e55e0fe6d3184d237fcc00ac8cbba9f4d0fcb348f19db4ef20fbd7672603b8786ca0aae07692fd2b94a269010c2c6f411e12e053ffc1c9813dfdcd1721695299186b1b195d3bd511c305adf0ff1ca611cc64c2ab30a2e8c99bc5e627211c03efddb8b4306770e05dac82984fbec886bf8082509997f1a7e9182c6304884b073a2bbc07db05247d37a1f8e6ce8c1b839654a50b1b50e6c4fedacce6672c4f0b4beb5fe2e207579a7f47d5c551a99c7323408dc8fa4c323683acccda272cb90f2b35d6a2234d176cd810b768a481ebd9bcc0cb4f68cf231ab25f8d6c17214f2ac57eb96fab052977f74ed0640a86310845aba64bfd0fc94ef39f8df910c2b88049dac7bec680536a133ccdf9e47d42d250ff92f23250438bc3aa4b7d7728a9203015e049447962632876ac7da208c6b21ad843155437671ce3986b40272f77ae729a87c0b5a75ed3c399edd88bfd6bed1d2662aff40964275792a21f12cf74496bf25494fecad6922ab8cbfe970f72e984ec8bbf7d563749ba1dbec5e726aefec7055ac592b22168122149eeb586a48aee5cea0536290f5b6a0a818341cb3515f5b0e005b8c4e773787d009e926f33730afde8170d81574db5bfa83044c3e719918949be684484747e36e66f2420666868bb31591f826858e2bfe5c994be0404914fac54888dd29824c05a644c7cbd2d5810f60dc9cb7242ac9c76b4f20f8847b7f8ce5ceeef4fb9e2f690fb8ccff604171a8e7318eea6c563c5776816211bcd5f2fa540f4844cabd07e2ec30b064aaa4619e9c5d0eaaed5f33a100b472726030e434f9830e1f7624b5fe4dcaad48c9e1716dbb547ed57de50f2a054407bd4bf4d17e4af5643dd2f62baf8b8cd1d17f807eabf3e787e73a18d167d41672e8a37733e4de578eb1f5c4a82642cb3cceb18ec0eb6cc25915768808811ead722f41aade4d40fadf731652eff32f4bcf0d4fa2ac01dc5b729f405fdf005e3872bdc53d560c308c13f5611eb4144ac5805ab1b42e381aa80a9d6beb5455eddc72fa741c5317f30f0e92114cab5417fd50545d1584686a9225644bcb9ecbc4ee6b50efcb776c1b32ad98bb3c8fa95fc4d89f5a18a33604d77bb618efcfe8492e18d626584aec36f7c0339029f40ac7f2e99f4407d59865773da4310f7bda60d772c893faa9a38e74af8ba1321ba690205e93e8633614ac7e26290ae27b0cb85b727d008cc30b8aceb9e5c05deb57f2300d8a60752d987378658f112bba092ec157687ec76e916009e3eaec17a815c627163a143ccef30c8ae516874c17f4dd547249ef1cf2e42f2e2ff63dd22a9990b820e6c242e28490782c389d7228d7c9c272e1dc432c7e0761843f7b4f1546de93527c812bf6e34903c710202b6ba7f76253c37f2a3e0bd2ebca49a7f5bc1a4ce2d3cec87634cc37a6911bc51963b36c393f5610f2c2389b45b90c73fe25ea221cdf55cb2434571ffd2db4741c1c4ddee07261144b86ace20c49a82db8b6483da58f0e6e63831becc0aaa867d9939dbb231ea6b95678037f1cd1ab8f8966a590998555e2bd9a4f0b75cb17519bac9720cf72e46de6a2bccc7bb8d627e2f243ca15a7031add9cbd4b042f3ec5d04a208f437221a1d23f802c4a3d0aa46606f23a19254921f3394beffe42a9b87fead3efbb7221e855cbfdf744b443dfcc5226df76132d5bf9bfb08ee82afc0e1c73c06317728931a5128d8ba77b593f742ffc54160b3f119413616607a244fb208b5f040f199aa30999e4e1a64d66003f6c90c8ec23470fa2ee1da2e00df1ae595e711e8b00919bb6fbb8ee3a734ae98cfacfdf6644bead3629de6eac6744f5d638e6da4f729670ea2a0af49352c5cb4331c02c75c26cf843edf145d36df97c98d86cc4d63d39322715636e27b37fdfcd09504ef490e53ef747228a98b47fce1cde50c87633bdf62130846daac0c44162d8cd8e949bedc4a27973b0dc94b945ca81cd0dfb72d4ada58aafeccbbe646fcb6fe638073d03c97646a848078cc178b3b31d3ddc72148f2441054cfeb35f5bd01bfd5b9dfa23b5447a6d0b74b2767daf129a701471d20cb9f6f219b8c3c65cfc7453bc99944f75884b5421235b8888a1b8f15ed22fbde7f94f75553e391819faa768e217e4439bec9ba67f5dcf52aa932f720112725015784443e2510bb91d8c9f55a4c22492c29cd8a8e1aafd174c1bbd1921c05dc50e933d04214e024f22e31b906c48fac8f9b37c39fc07c43504b59c18ed51726c0aa37067d9a96af424933d11acea80d97b41d76fe504a51de051d25c411a72d8271da704a6571f2a9f5396c88cbfb195e5cf4a82ac72eb96cbed3289bafe722abe4034ba5818fb252a7eb5c27942ea0e743d3a1cadc0454e3bb0578489b572878d79c8db4c944fd1313d34ab0456edf43cf96b797542528a5125c67e00be72b07a09c6fed919a62ac56409609a923723101f5476a55c5333394d27cbc987252ec4ae4bb4b334428d7cfd40ccf55e85cc6dfd75c4252d28bdd0e832e179721240b0a511ef2fdf95bdff886e7bbee2354be9e98421246255efdd8844317f7672c935e6c1694e51eaf679a1d16c7f5d4c1662e790c712a41f44f51fb0fb616272c885e9115ef450799414cc549ff85a7fa484522d0c38483fdb1073564b2a284059ab341aae0beaed13281e80748efcf32de34011186d79c32fe4b78486cb53676713a0dc776b5d1999196027a0cd249931eaa6de5cfd8a57a996dc0b36896a05de80c9efeebeba5fe97f14cd1fd26e9199436ec78305ff272cf5e0275cec915680e9b4bbb71ac6bdb35a6c73a865b8145e769a9487f1b27c49f0001ee935fd7282d79a377bf9ebc74f9d50a8e8cb646f9e173894574ed8d40d0f748ee1010f10210bc0c2e696ea31138a072bec973d88f1f2b0e06d3fe0160f43788fb040c8726409b356837adc8dd758ccf74dde0e21c9ac3e059bee3cefe6e7aac191314972ea0f0901f38238a9afc5f7879b658524c95d9e936e452664f306eef6d9184f36685d865cc9e39fae775b681f8e26b49471670ef98d872995ca98e00338d6cb7222244d416dad663b0b3ae648f2381474e4eb93c6f8c39171f33fb7cdd61db50a7dacd318a38fb7205514a97a20f0c20acbf551e412a96609b7c760dcd2b2627206ae17bf55cf0b0e20359eed7e76f2b1112bf83460e65fbe2c461615f84e270085574b93821d49a0533880ee954373192e018d89683e54fe8176971c57a42d72a5fb4c52addeffc6d3a6147a9d186cb28b2474e55408a458608730af2ec9f2724198561d2d74dab082b8ea1710c88f79f04b861b15049e516328d496beeeed72c3a32d2b9b05220347cca5077752db912bd1c5d235268f33281975f0a6f8572f7d09d9bf0a08de907d1ceadec2015242936e97f98d896b78e4dce54ee10a874752e9e5c5427b7cbc14ad8aecb364447efb45d456e87e0be2c3fb270848eee76e3cf5dc717136e3b332b0b4fc8a3b0e8a9b64e87f6740198dfe67ab569c9b8972844b322af5637126804de3aab0a4943f758b0de11df3c811df74c78042864a4304128c8510ab73c05173cbfced26f1fa1a9ad4d75455fd8b76d619aa9ee4f6650365aa973082111b4d4ec78b844452e452627251881aaf9125dde8917de80272a47126d50e98ca6473659ac8b912facb38ca19b731051937bf196d5d5446ab723d242e1264ae46de796be4c7f44c7e4fb8947790303f429d1d912780836d375e97ea349214f8d97932af61db40b34849d5631af3c2b2e5a5333f4c072e1c3e724fcd47caa68332caeb27446aac2c759f864d1c9edc1ba5c347b7373a984086540dbf0f3e37493a105955e003a4354f0ce8c7967ca7291bac1392742605803d72fa6f91025e426fe99c680ebd629582b0bcf2ab1465502291caa6830cd414c172c2763c1e07ac499fc9a82f517de43dcea75d9709af520e4fed6be2ced0948672054663054d3193febd77620cd7c67dda2728388913c2ab9baf93142e02497c624eb4b782cd2fae7370970a5b1cc0e7ea624f32685f15ece7cabab9cbb233241cefa035004af48a7c63a35ac6dcee9443dd9437bc02664d3bc71a47c50d8d8716d618e4931ffc9ddea0539a1d08ab19e973a16d3259f15fbeb767a618f65f6a7264bd6768c8b772deece452d0dd56e056a9bd022db33043908dfaf0f88818eb00c09a3fc07f21ffa4f7cd47b7f50ca77a85f85ba97f4669ab6552cc4286e6e9728719a3038dedbb06070cca69ce8d3be1bf02b6fa90fbaf5b92e99fbe68ec6372f203696002474422954e45e1f32bf6c7a7d6a93dafb214a7e6829976cf961a72f457711eb1715adaeb2345976bc30940c6ef48218d12da19c818e88a169ea146e52ac8c4cdb0e3b1d686516e8f4e653bf1586061d590f0596326dc46f7e4f572334ba669b48dad2d03547951981638814f560bb1e4a48d3a494f75c9398108198d7ea7ccac8a5c71c1d8350c7817b141b95724135d7844e260d544da64755072a77c46fbe9b61c6c87ce101dc74d4ed04314d1ba4abbd91f8d5f83147bdd743ca3e2e266cea14cfece6bdfb4de94b406b46c2b9b8381852d13777c82b09777720e0b4a666a97e4076dcd90d8269ef55f2c7412fff91161c7ff4adb143526bb726350be13e981a82f69cf8f04b378c9128dbb8ea38437eb43880a4260c0135472aa9262e174150c56f178b8168414e544c4ca412060f9192e638404d688680b70618a077e8f7d9934197bdd539aa8ba820dd27f1fd253001349dc3472798cf872fb8e56cd2e8fb06a51744c6cb743e43414d5ec0ce84bce0861ba1f451d0a48721d814d94f094acac4dd47992a879df35d564d9bfd00ab61edf9048711ac2ea1027205c1432f594821da1e0488f1071537ad612a95e145dc2f23f09b5353bf32daf544a1158903436f231abc3853cfe3d4f251ba69209a1b940a0b2d719305e11c55dadd7b52e441dfddfb44223bf5dc190c812588942b1b47bada56349f2892a3c61b3a92002dce5ee1067fdd1afc1e256a20108a35005c679084bdd68bb42720e6bd1fcc2615d4e76f7d2dd334adb7454c378c0b4988a97ea958800953ee734d975252e4aecc66c63275fe17453fc4177d8b2ddc7ae81909d4d030c4183df72210710fdaf67c84f0721ca074f8ba028b77c8d62626959d1bc0fa1f934b46d72856a13787e1488660dea2d26115870b8b2ccb8f6919913ee64217311ce55b76bd21425c3965d67e642908249d1eabf6a93c6880e77336240029084cc34ab11437f82ed6e957958451400d0dd1b4caf3a23d17059a0080c59b7dc20c720d3f6724d3ed289ed0e18651c33642802096d753eeaf0b7af4f29eb60444d3e9d922e72e8d920701ab54dc600e669130f7f79efe8f73c919b6c344b2224d6c80bafad4ea6e791ca45805e20dd82cfb02d0ac7436c7c8f2d724bf6c512a7beac8c77ad6bce0b0e2d6a2e5db06aa554acbebd8dadbd2bd8ab806d0392f454c89f1f14b97211a995f3bea982fe8b3b04d414e7f361886603a9b5b9ee1625625b8c4ae1624398b876ff761647993d1eb61427e89dc1037bae7c86b8407d6b644b47f4d5727296f990dcb945ffcbb9818cf86137ac8f8255fc902414497cb133461388200e3f94ab8aa127c4d869f4fcb760cd56f910efbe968bf4be6f0b0d1cca669d0cb0292fe7f7f260eed74f333c8ac1c03ca99073c9088ba006cfb4aabcd8ad9d671572d9c87d3924f186d76ee33e984d465a8f7d99ebedf50abccfd3c1c48f15ece7702bf65b10fe73c4be2ed775a460b5f7974775b08b41f6ee261476c032f56c7258e0fc458367ecd9d88d44661700905c892326f1b3d901effb6bebdc733a220d72202c9c3295812168c8e7b69fa100acdd824f0a7645cac371fd781d9d086ee2723a9194786ebc59a89fbd510847aa8eaa826ef14e67cf3635f912f11f341e624cc7fb1b5efe2d9987d8b5b330bd95c6d17cacfae1473770837975a1d75d960f5be5269baab6ef825dab5e439b5596b6800de6653f17e3dd6647f410861c83ed72fc62e19ff344d052f55a6b3e8be92dda7a4cae8fcc3fde6d257202f62b945e01bd7a437e2e2e3440e9bcc4f03a175e6d66377cd24adb50a1f8d6448967bedd72d473df534faefcdc0e3e91c214062a37d282be26f5b5c0911c30cfa501e841607a33f4325db60a271401c1c004939493e2b6cd26c343bdb9121c5c0edb018c6583e9606c5f5a5201df26fc86bbd5fa4b76bae42693046eb52db57fa91767292e8f4feb9c61fe45b37e9982b6a876b67a7ac613e9cca4bb859c5afff092c05b72059a19c5b39724bb5e84b183337505a7907e17628254d4df0f036d2e59ca493ab616f72e237574ced0812a2cced9d7a9ac281111a9437a4d054121cf6871c272f1f993c5f9f7cf123f512467ff20e46f45d70b3294432ef81d7eb8acf67871720f599281868fa31169767b127683067c0cd0a295c963b775de6cfb8b5b7b1e00a87766dfe0cc8415cabe3688567e00141be320e8c63428d173d49ca080e3e972f83b677f2955b58947aebeacbdbf6f38eb217957936faaa1199291ecda3bf5723fddc61f97e192527ba6a0c3b5a0929bed378637f4853316695891955e2ee3720eac3d6e6b00b1e4d48948fd6b6d2cb3db9a014e52054be52379ca89e9b2e6119400c583c19bd053c6af0864fbde1f794643a6202098918a5c518b1506cbed7260734badb45e04d321293350d724f487763704ba2f4b6a721804356cc7dfc762cbcc6bc903fc9618e3a756a4f91d70cde94af89ec871ad9e87fde275a853ff72f433440222fa6162cf9640aa39774b82adcca3130e58f4b5107941d19095f20ea8d747a92fec6c6b7fd13b23ab8c31cd088b4747870abf5d83aaa1a0ec1546391fd2983d23c44cdbaf77a54652ecaddec41092006dc300c159aefca5bc052a5c9ee0af5c88f75e83c3c24360ef9967a85d4fe9ef8ef2a1482f067d41c5d247726612ef96c247bdc2c3db13589a2d8aecdcdb5c993521be219d47936aecead27279ee558efcd40b5d296d5f438f1c1a778474551637bb1e2e3a5cf081f37e56455733ad747c1850df5bc6ec233735d43c3710b1400268b715e3f536c2f5c25d72868f3d7c946caa07dc458c21cede75e24a70fadccc68d1afcb53712141762472754cd7ff4c3151253f96ebd146588eee075203b1104e5ec35d1510e9d900811d5e2ef22445a060e6d3dec16e7eaadb27bb3d11838b326c911a98b3b8ee4a2772b36847366c7bb7b306e37fe4a13cd13c1034ae6a4be3afc557cc616111e23f72223492fc31f46844afbd68bb454fb7b285e7267ceed75cdd332fe9e25e1dc91b1949b538797ad1d31d0c2771a9d64efc80c6013413434d146586738ba96bbd72c43a832a895998b9dbbd7f4f3aef1131eb42c3e45219153a01cbb925bdb66a723be0f192300603ac673448ef597c93a81f12ee4e5d8caabd852479589ab04272a5f60cb5a9855899bac23937221220aa265315b5600e80114f7d1c81f293fe71ddc8c37543fc359efe458d2f50889373fca0ca1f38720ab6b49bf756a278001a7c60fc33c2183fa3a46af8e5e3de7558fe0ad7dc58e02f5018e287e553e44c21a78098844798f3d843bea9a9763f8ba3c3d01f04f31a5c1ea4557ad1e633c76321bec4a24397d9896c5f409c6dc518b96496c02c39860ee9124f44c2d74bfe394e7aa077ed981153673f4673c14ee9d6e0b989e0a7c33529c132629e51a5064379eb8646cc1fa2ee3206cae34bdfd3b22bc959c1ed3cb18f00b338c94b3994724e55c1cdd80ecc58180e3e13f42dde40ebc49395c75f6380201dd9050e8174177d3ff72b0f1e624b7068b964af70a4e162e1282bf60a41e4a6612050d2243a295002cf57215baa383a0a3134ac18684d4edb2b8477bd108db66f4fd58df25e727041cfc1604d28454ad62dc1503e6e4f095d8aba91659a50b0fbb014c1329e7261bb2c280c308715211400ccb819f8502daf2f30b307ec3202628e7708302472f50260aff18c037dfded58d76f8a54edb91982e68d143f9032f1bf0ca2baaa7208b3f0312ef2fa4bb069e89975adf56d2a33ad7af1be4579999328906dc3bb5d984138c86194529f25ba16237e813555dda6be9c401b6dd04be0e752dd9b6e4191386b845cc5be8710020f4574538dc69627a907cd040b453fcddca6515a1527c3c92687108a4d8150741997c2428bce701b478cf4610aedd3176468bcecc672a620829b59bccf4e939004c455659170cd1fec23117cd96946f75b8f0ae6db0d439ad44c196cb6f16866d011ab4b2989419a8ecf90e48ad193d3a9c110c339329f526ac5b6c501860ea7e3cd64db1819d4d21f257f8999257e0af8cee8ca66726bf8ed6daa6a04856490e9481fc3cd0173bce59b7637f953c39db0653d2a0072fe1e1b7b0ab11e67b103dcc17b6930a7509467ec2f5e1564833c162f59a88c72f70e73b794029e440c4808516606c098b719377e7c35e07e32b033e55b77d51429f4d60251020c44d31177229a04ee7f0525331145653f54d836102e1f86f51da52371cb51028c767710e68ba0dc0b18a6cd3907248cfe31ae9b8b117a26cc72163307f717e4f82216f3a10cc13f152627b8f573667aaa298c6a10a0169d9a4454db5abd7069a43c71b095b807f6efe917eba1e0d1d65ffdd0a852a6f77d795149b9b421f7477275a9b694501aa6e22e2295ccbc324f1a9b9ec1aad8c60f3b16679c3fa29886ad8ec94e52bc5d4f05ab1b9e7dfddb73e121e8776eafa952d43d87f6a9a4bd4c6d40158f532524477388ec26a9c010cc129f624d7215e00bc3729303d26b84c9a7b2f377f939ddb2771b78f75583ab7defa731b7a6c230ebe134ceee372c1eae9021f10e3cb4e216c7a530061b8958574ddc0049756e22b78272f0cad9966c98895c25de48cd880d059830bbcefc24e5eae68a243c2ba07a527289a8476273aa4e711c5ea7a62487e5daa893b8b88449a5a676a48faf88d36272e78d6c7d66ed99b8dbab67e17704f733f1b324331359ed645bfd25d42b5fb3271bbed34d4e7663671641fd9e6d4b5bb0b8998654fd04cf3e52c814a334822057de0533d69eb4982ec58409fec3536ab8d69c831300df198bddfb92ffddd0b9066847dda2d474729a5c61e1a85970ac63e937e50bb50214fa84ac90382fce7672c3184106412ef8a86c679339a6ac88752ae3b4e1273b357fbc8fd736543bcd46b923052bfcf53a94856080fe8fa8906ce02b50984451af5b98e1efc5de6d1972cb3dc56d427de79bc3f11e78b7230c3943c9f7135cbbb91b7769f2056153ad72d3d66df742f50a8b09733497d44b1ad8748bfdfe0742594f5b17e19e4fd7b872dba274f34c046be656621131be9797467dc87527075ca80d1007af76881227729abc93d7b5931573bfe66425deb90b805789e249ff2d71c63f53024cfa7aea069a8d167620541d34cf23a055fa3ec22e39ef69d59c4bedea23d6cf912fa4ad72b8e715697ece552264f4c2732289ab7cd4f8cbc187c2461ea3cc97f6ab1d98091c1162ba82f54591519e994923b7fee843259c4b5e1e664ebe0a74fb875033720fc75aa65e9996a11cf3aff47dc22bd9c91c180bd392a9ab6772040286e6f5024c3de2201c8380d0bac730718f466e34204c063f68726794e9f3a3c4dddc3272ee7ae34a9980b65e32f1f5906cb26eda806f20fd6b4282be45480eacac55fe7299c6deaef4dbba617ea46d9733b9fd14acc4ad83567c424b2c470244a7ec24724b9fdeb1ad472d25c2ebf305f0604d13e9b17a3fc879643f989f50bc36d28f1abc4be4fdee1b4a9f857b1a8668722f7b0afd23940c25a392b227c5aa2db36c72d999d8835cd3eaa1472f0733d52637094166cd4b2dc7629072301d72ff5d03728d5c9c8b6030979c56aa3a2f0eb637a32798854f050ca80e367f8aeeb28ab72b4653b44406465b52558ee82ba7ea22ce6da7fa757d4b1a55ac4eb518cb8e3c402d9670ff7ce923f78ecb4ed2bcf40bfe044390e2ed604ffb6e6ed110d255a1032c11201fe90ba6a0c49c41d13403cc26feb6c0e154c487e04d89d6b0b928ce3ff4090860ac4f293f3d893281196fd442f34e7003e34197c0e23836318325e768907661a82e6d05caa18595a6c959abeae6e21b3068e198e48af3f353d32f1e72f9ad18d5cb561a658fb76422cc0712c601a671ddf7428161bcfc322de427b431c8aea8de2b85230efc05754067ac4383079c14a2e80f4af780943d1332147e72faf44e9f9dfedfe201c191f9d173858ffa626a4f15f07332dd5f800915470a72842b580f819687cce975557a3c62bc7b84874a7cfdebc4953624bf32e75d86727e4ecfecef20f4e7f975d18a5c64165f02a5c8ee3f3fa8dcda60038655450842d4f8bbb2d30c0659e7df13bd27bd883e99dae25bd0ffc3b42906e65ef644d272e9c57d4fb25294c9d3f61a5abde35e386e32c7d8b7dd6eebdeb218277383da4b561da8b432c6141872b3d59628542f9bf5e2bf1d28a6eca8b8a31f05b9e7275c834616e29cd92d3a93e104d57ee0f48697985fc0447bb8ed1a162baebbb89472b1f0b4a1f4e47b78e18951dd4575ded8ed85e8004a912c81ce584b12827afd4e5d4afc13b7f8251c6b1873d9d48b34f2b2bcc73b536fff0c7f936c4aaa20c326aaeaa425658f9cb0fd79f82786fcfcbc70f20dc315706e39ab1192b3c7254d4309e245d7a24f4c0659e2c59b6271d1d050b27b5d71b0518ac55e99fd604d3972e65376d6377357c849cd5dc6640d5c7f27f1a77d397f207923254662a6f8b972e9c600f84b138b307cf76e5d6f552090eb6888e82d64bbe9ae13cad105a9a072aa79b5510ac272b7339eba773cd07c99bdcc286f98aa28153b2881bcf679c572a35e92e8f4322568676b0cf55957a83be65846ab3bbccf6a980964b6146b3672a2980908c261e9d352c4952c76d8f9a6371e3cccdafafbadecaccc2c16369c0fe9ab5e3c495508562d2d2205dee5ff635500d39af139a90c6c2e51f4b1d7d27276f25a22fc0d8768a6f606912499b6d803d4475b5ef125e79ebaa2cbf410e838241960148d2a85a4d155d2dc45843524d6a1368ca2293a5a908e8a73b881d2723d67c7b42ab971f0c468365885e415dbd58a9e28313bea49f6b14369f388e572c99faca5232acea6170d8b44c300226d13d6a2adfbb0278a29baecbd6a4e895ff01f44144ce538d12508c037ecb916530c6e3a7f78127ee84721b44d8d036272ff10d042e93154786e4ad77d6f53fda7854ff915d1196ec4f398d619bee4674cc1d7ddb0fe1f8c7a0bbbe6ea33533d6f6ed8fff954b215be5635ac0f5e9f5e7205edbabceabb447c48cbe08228be3572c77e3349e83707a6452e14ede85ffc41eaf22f699fd58e2919f8a0dd02c2403d2189faacc7a6432222f20e059ef52e08db0531c8bf3e48af22eb14d0c1e45a905704854647ced89c4c026579fef52f72b1354f38709696413472e7bb65357c2aa22872fd1d86bd09a6e945399aafc47211085ef01ae45bf01fe77e2be33d3808ab991e1d9e1a4a100e63bcffa7b11f72cf140728455f60d2da5489e0ae7c9c2fedc8d89427975556693e28bafeb10972128305af6389a1dec7846a66e91a8a87b24e41c813cb5a8da949627265021d5510d8841800e1a5d2133637eaca09c9d45fc26ef558e1f913443c15e62d3d96727a8d83c7e42a92f6ca58595fd9b66120ea81471851bed65b6c25f06af6ad74724304fbd78c19abbd0032e6120eaf51b629117bbb401b550697f5ae96cc446b727ff91c9c4c0f56d86efa2992f8d96f89e6fb904115b850636af1c5fdeafc76102cf67000577d592d4ad26e9734c9512e42e4595f73e94eaf11363d66120f5e72b9963abecbff45ca3e0144bb99e4fba07eb4afa6829185800072780400454b72690bbdb65c6ade7456a613c61f986c75a4d3a895f13af5520170e261c5874624ca32aa44202f93b676e72a28a0ec783209b5f7da6e67b6ff236325e8a0d05372ddfe3f62b962750b83b5c61df410036243994e93412fe2690d098d4f9b9929586968dd921545b7e9144a01d2b925cac9336164d42cdf2a2eda6799634d6e1c0ac40c3cc24736d668f2fd2c755aec137544bea6e132660b2c03890119140992728f326042419d59ac01ba4184549a9495e1f7b135d02a4962d8006b39983269085a44fba70722e46390453859d36ccf697d60ad2c9d4a1eb6d1cbfcdb17c3304d5fe8c1ed80a2bafc61f808673d80f843e72f05ae91ad23e23a5565204a93bc723af51be90c160ec6152e5f48ffb40d580524808f121f2dc884a9cf8a58f1f328c12c01d254f9c5f567502c88f3c1484d59a75f5e808ba99092816bf2a05e4d386c2a66ac4af9eff2f3cae9cbbd508c9adeff2cf8d947bc2f3224b86faa428c724c93bcf64b086aca06a7e657e2d3b27186e0839f21a4619f845316b9fce69c3ef291765a2e75ccfabd7d2cc5f5565d139ccb067468815803d8b5216fd1dded1daf80975381eed616b75516fe1198189708785c538e3a2d36f0b5062c5fea2472fea96bf13a20890577a08f56329d7db5a617ac3caf8bc1def4639496ae4e70726b2cee39b6a61dc98075884acaca73df5e73bef64e18bcd61e47f826d5506a72333b4576e65e202023ae5fffa832771cb4c0f432bf8c88eaefec7c85d0fd194eeb9331147eb0202a090318c3112e741bb0e48046be8a297555500d24cc09f7722ec0075bc5275d653fb4205c33d4dff6d567c6010077dca16e625e667a192c12564e23f2039915f091faca1f8a1ef2b8bedba50f567cc5891c92cab4bc101f723ff0af817f4db1ab22567a1b4f9373b593d109352a8e3e2203c37a34b9fb7f27c10beb0db20336b87e339508c2479932bc61016b79868d0de93dcbf281bde972e2ce084281364cdd4276ceec97ed1fefdf5b5404f616482c3ae6930de0348f22a07d8e27952eef0b03e1c3e4639ab820013e1cbc2ab7eeb010b332f7d746857291eb71bbe6ce33f2627cc7765a1614d650440a475de1aa9ebaa13e1ee561a06925edde3c011cd09616b3e4d3ce2b7419d1c80c695686b0b38d276a9edf717972c6d55933d78397a598800b48c1a92a68a1561a68f04be28d9888197a942ac25cf971fec702cf267f2f4a5565b0ebdc1a65522015f15e725046851d55bfe5eb72ce465336886a08fb6892278d8c032fa8ac58fe86b45c165cea8876ed05579372aa2132670b71a898a76ffca27f1fe7465c9ebc4e929f82f59d374317154d47728b88760ae7bbf3ddc2765554e657c565b3d2001d4002ba2dfdab2f8fea61df0068b5f5da3bc62b488ae99a0b5927f55678fdc95e9aa3422c674602096b333d727a8356221cb1b009a9fa46a2684a54b7a3a35fcd13a3ab836c340080acd72072ec11fbe0a6117a19d473abfc7924bdd7baca05265d1b5c2791ce3dcf304741721d26e61b8fad2e3e3c22697cad6e8dcf6b09cf657b6ebe392cdddf1f003cfe7062b5c9e44d515ea40e0f52c93ad88a0fcd666800165f9b6a1fb5de96c24ed67252ee4e1fe3254ab9e55a516d022eec104de0cd4a4b3234a8fe4c8625a1ba67725d7fadd106a803849559e6b7039d2a505edbfadb217392b7be839d34092fe77243e0ea90318b7460c42009fd4e96a7eb8bc8a2f673fa4502a4a25acbc95e3f3dd6f2a34295c1f6350269c4df9833d2d94bb27436130c746911dd17bdc0172a3697e12ef8a461b5b6ad0fb2e2e109e238c0780c26b0e02f07aefce8753f0b2172c32b28096f6e23b7526284ac77fe5cd396a693cd08bcc438a65bfc34d806d62d070fbb7011d7f19bc58988bca66f5636758fa89ae928834eb0816dcca8eb5472c20a9185b0c929998cb8f5d2fd89133b14d53dbde29f42160daed142c7e3f068490dbced8bab081b852e1a1dd6ecfdbab393b0cbb822ee1e6ba7df7fbedb3f6c734b5eadfd9c334629be36ff5e9ec1682bd1ac27189f80aefcd099cf9e2dae42340f53ef59e2f90708e5a8af4234052f2590a27bf31d4b99b6758c30aea17f7224474798d579c25ac196269dd4f36bf24256721dfc432d48f1df25d311ba3d427e82fce3b67bfcfec951de2a31f516e0541fbdfd75391758b2ca8af5dd25b5057ff8a95d149385588376802c67b2007be9bee2f350d1d4ab8e26f171a64f407220a9275dbb7985812fd58aff0648ae2ba9925c313d42c19a592e8b0ed2da64728051a9305a203f79366091f026c1a6d9a82caeec435cc8d32c97cd44333a8018f05b74f182e44ae60af64aa5af463bcaf96e452b28d83dfad06f6a5b157d0c728d1f85677d0c7ffa23ee0b2123232427cfdd025a513c9f088f0919ee417511068fe65890690c0f2d57b60816f2791ddfec8610a1890ef5dc3dd47e411abbef39974c8c3a5fe4e8a663a1330a3c83a02b407f500ce5d0873740f8b91ceee58d608d9de900c10682b655a55609bc6e43c560b03d36c338a88ecef6612d40035072cfe7d97e2e564336193607d7376eec82d8789248c944aafbda61c790219050728b5896a345af240d5c3be6d27649859400e4d0f9f3e8fc6f49b12e315aa1e172e4fdc5a6e495d420fbdc52ef195d04f0f19451a36f90d0208884d4e58c9d9172ad04d8bca6abfdf606e842a728e38a75133dd66114cfc9824ecb8e5510189f02a4667f677b22dc28e963a30b8de82fd7186da33f6d9fdb3db44cd2a8ae27fc55da1611918574d063d18464ceef89f80d34ddb1ed23b0a1912662263f8892ea7272253c5021bbc80807b5d93cefb5ad5ecfce0ea54578052e91d951a6d4270c32a9730de385e997c0241562dcc3f3e1339338bdc635edeafd6c5ae7c7f37134729424806ade18ca7b5b3d6eb10b8a6d050f3de4f219c114614bd94f7220d76a725400680e4d320d56173135c01c2a0fed1db9a375a945aeafaab8d803c06b7e720750e4516aac7bfa6edd5287ceb0cf2ff70f060583a6acfbcfd3e1483ec5b0720a5fc92a8669cf37049325433e0aff5fb6b9e3e502cffbaeefb78a081af91172911fa28a61bb2cc82581133d8889db849402a7ff7f6956925c8717fe868ab572ba32dfb0a3bee1ae3a1e90f544e6d23a81b254a04c7744527d93b71f729fe27272c2a8fda6972b71beba066a0836d2ea6c7b8ad691d5863e6cba982c13314572e8d61f22c60a9f07b726ab50ebd9d0ecff1867bca29ef14acff129cbbd8f50351b0683eea6bef4c0181c293f60ebda219ed21c9aff40b2f9f752adb6d8e1c225a468401fa3410ef729a6386c63c4235083d7828642ebfa100985ab576b21480736b35aba8e9ec5dfe5312b42c5c6e689f920c09b95874dfbbdb189bc8c33374535235797b5a916f141f30acc90d332c2a46d6bd246a462e9c9aeb5b44f58be72be73805f749812040c3a4db9b30921c24253b865d01a4147f281ed04dec84706b8937523dc1747438ce7a160c7ba27cdcaa114a5920d458d303ccbd949c9f372c372b17d426dd5a1f4c376568715fb6ca43884a611ea9b5719b61fc286369c667713288401f4545539015b9d13ce70009e586a4df7a073193e8a934980e2390a8b5a5ec8b0240e60e2823eb242e8ce9d8b6fffc9afc121c43552ebac545abf729b3a005f2f4acc8a37bbf071b5e033ea6ca50c78327db856b8ccf22bd5e3272a8ef821e71369b421c84f0e1dcb6557e25c6133a4b38444facd049904abd3f92cf067eaeb87c1ef4ceade7355ce03acae59ad36bd710d4dbfe0528869c668d872ed6ac0e0fcbd425617aa4fa26d3d6ce112ace5d07455eee9e3450ec8ce43dc722ffc3aac2e7258bdb3084827f918f26b6d490f9eed2fd85497672f252d44e172d4e8db60710657de6f138622865692a7e222f3a04884e14a03932f76f832af632b7e985b487b0626a68d0b21f8a6423d4cda7a89565a875b98d70db757b0c972c083b1cc1ec3b45f2a38615531fc1b9d7863f28c2670f647b3399df9830ff07227dc1e4c8b7c78f827231d4f74f8853df0d0b68732894cfc31718f2a6cd680722b621a93aea71160f51c01875b35dd8a14ef1c7962de6f2613087735fd17fa7284b9b68e4ed4354ce6cf95111a420badaf3117f5b53da82bc9e51b0d28fb8b7247b9cc85191c88209aba72bb030c1faa0799fa9e02d804ab9a54b27f6eddf028829fa01680ad674987518f36beb8b18cfa6783f5c1639f4dca2e7f3f2ee7e272a13c0cb93120c71a7a3b3780793df7afe14a32a7fe825f223f43f42f4fad554c41495fa26bc6f58b286148ffada0ec8d6e0e6fd3a32e1db52b4b31584256cd72bded55280221d79bee4099d335c80db836aa338c496d28ce1d83a95056fe7f32cc31573d8bd86fb185f204d42da5a2832d51b84805dafd30ab008eaca727a5247a694dae116841011d5d3ffa137f806b5b72c6af2db1cdab3d42b31ad43759104133c08ee7e377458319f791c6271f86bb199e0f5014e61491d99de108f90c72fde3954b8ecbc3eb1276b0f5a91c199d9154c950d8321177f7b4b37eba4ce309da6990199d2e8e0a7fc7c631f1a43e0b84b03e3e9377911991ff32c8d9959e72146fdbef513c5c538abb14f5866310fcdcef42e194566a72154602ded89e0803e0b035862551375b5657763f5a7cb696b7e2cf01d83cda8f867a9e7b37be287227a595ae464ada6e700d933a4345bf26b0c8ae0124f91fc76251e9e8668b5641842c661b20e730b8356a096aa52c4424713bb55e70fd019fbb9a2161d098f772fd9bf8266dd4ebc853e529daa6ff880ec29affacdfe567ca852546f1ab693a720d7045bf0754a0743ef78934b2076bcad4137e29ea58a677ebc4e9df5bd95608bd6b9afaba9fc82aef7fc44d6d0726b3564fe28cb438f5f9f9b27a5b558f8872fcf91cda575d16c7df60187a46b3abbdd08361b369886ed0f5218028b3a8f9598a9e67585e408df83a3e010101d430b50ddf468acc6297a5ce1429fb01e2417245ff36a19122ba34c627866ea47c938bc0abfb73ceb71abc18cd4a846abd7a4918e2be3cd8733a728c25ef512423758a38d5895d5848e7dc2df0ad3724caaf727d76d206e4c34e9158fc5dd7ba90bf73c2206893582550fa0a091e4c01c7ad6d3eadbc9aa95b00713d8b3be035bd598b91761f352f5a1dd1968f4850b173831ef283022d33e9bdd2d70e9c90ae6a80e8ebfcb1d95153dd95402710bb2c434c2d41f043bd51b1c3f05fc0e4182bac2473aa3edb9cfb59bbb53734529f11818572ac82f28c042d107e3fcbdba978ab4dcd76b4d50ea57319e8082b07c4e4138d7240da87c7df0959756dce4430e477efc28acf1bf5d426f3c6ac0315367ec16d72e9b8853c3d0dacd71ceae9e49c5b128e1b7609d173ff857853511c29321fca7262167f8af4b17694f9c065bccb7a6951309157ab74955f1783c0ea502c977b2c57311acc71cdb21e3ad1ccd29dab364b5e35815a093bd6de340649b2f1d394727fdab9d912e470b8cd19d0c51ca92b0e5545d1ba827b0a4a5bc60743cb26917273fd34a861f3d1cfbc19c432fe25064ab5791c74375fb7164e1c905dc3c68f72df1d8cc923dde059ffa45775f332e1d6588b2a3f1adb003e64da2102d26dda7200b3bf911827922428762792198026c4fc94380c20780718238583a02118cf0af74eb665e25c5f87236505ed82f5661379622f5b3c4fdfd349d17bc13c56f20d2e4f0b829a43a67beb82570c46bcd22b4f1723de09e87c0cd3b347d36f9ff272efead02bf816f90298c43e8bf77d5c24e2f5e53a2ebc8c14e1d1a5a3808dad6bf1e5a7068c7675cd00416b05decf41d9ca67876212b9cebe2547e26492fb6a7299b1dc40072013df1e6dd8035828b48f7e0e47f7596c2f5da3cce58cfabeaf40c9baa433f147cd3aba795367a29714a630b55a0b4fbe75f36e3665bf0a6daf2de50036d2db4f8e43a1731af89a18d60c75edc8ed989fd6808d65af455f76e2720af1453a3613b3b51a0aae25cd55f5d7d7b26553301cddf62481d30ac059f0725ac0fd1456dc85c3b368039a9f6aae3fc8225db414dcd664a89f383f6a94e968b2bfc1cee7b81494064198cab73e76aa863b423ae755b749ffde86afe6892072131837ba2fbe4d6dcc533a7c99103e9a3d07528f4af332e9df6224eb25b590160afa6b3f1130aad089b7cf40843e298aa1d0284201adb4e4da64f20b7d59aa0100419fed763712af8ae1760b53385c359c1bc319f2d71dcaef9891c2f768b7381da9de179a6c84944834ca3989c4b54525984b66582b6b8fa5b289c5c998fb726d9748010e1371713d512888e93e741d3aa45c75e887949b98bb4f458a7dc0728b62a79d2ba09bfba316b20324cd83dab67a3931a929c6f93e5add6b06037272b2e1eb0c547d3f850526c15dde7812309b46171b017797fb6ba2870926c9056e5ec5f46f4c954c8ec825a6f6ab5f7032e865b39be23f796b2671840c340f401c28f6a6e4943caa0e497cee4f2f475c4e503312245dab7be13ec2c5b27c4c3e723ee57eaf8d3d1c063a3336d73ff4eee55bf5a0c297c64b1bc7d7f395bb9f2072b60db6dc96044802a0dd05b6975971043b713c344cc4ee2c6440fbad4040a559278909f375780710cb2c07de43a1c6cd89c8b6ad33ca39693481e57f8854fa0d9e55a18840c3397bdbdcbf05488498a611768d418fed0282b59065666cfe2371f1b5f3ec7895a58d567cd5e3bae5e30237bc9744108498449cf5c46544893972098ae4c7ffe236bdbd9f47b57fea970f19334684acd871346b574d7351342560dbfe23bfa3c86730ed23e9c4a8f89f8b2df180690b6e20fb49e72f34ce18bc13d8ae546a1decd82503e7b314a98bb1efa38d5aaa3f504cf1312d0a552115117217a4b860ae35e96a5ab60e49e3e398acf837dfc76dcf2de1678b0b729bcab872189ef4330aba6389d8cb248c3fd2bac36646617fb6a1745a62ca0944b9ad0a7201ea8399a7619cc4228412e22ca4b0021f3218e312d8082dd80fc3c6d6a9da72f600a1975425b51bca93ff26ae4cd39d478cbbc2f0837ae6e7fdb207fc25027214b8756faa2e27070ad46db06b5af48b5fd7c2117117d2f85053a54fa52db02125b8ee5ebfe0cd3f28612bb55053814e726740e2dfd93778cbffc70fd2ddda723647b48eb01c3e3c17da6658c8fe828b70ab89cc228b3015683c025dcb0f5c187631d5c66191cad0620b9f52e8d57e4d564c711ffd0109e3971271634ad2de6e64b6ef6b9d9df25f9d33845f28f1dd48ff79c66952ce1dabd00ef8d670b20272f6c728b6e8384ab37f4bac1585c2c0dd640791f6307dc20b892175fdcaab105631d6450bf58c8da3aaf4fa200764f348b5f85a7b82da3fd9e6829137eaaeb43b6ec428ec6d1f5c5b1b2ac20f02a3eb25bdf6a6d534fbd3ef48054af42cbf1d72deead36fa02d2641df21a50019d0f8efea9dafe7b2dc3aa723975eeedbc18a008380557b382743d2628d0e64392b78a7cadf0647d9fe8273d0cbb2b8bc042a7246a409b04f4bf8f12191d30f6dc555b3326f8c9db36b2f9b26a826503bd8b9724c422dad1fceb8115c312d49a8030fceb673d773d72aae0917450c0411574a722d546229ad9491166a7dee4dff5703eb49c67347113435278626c4bdedd5eb720f4211369b8ce688512b66995ee8b553b577ba7e3c19adf249286ea29897ca00ff7d461193e9949915be49597d5392f112d944330804c074d67d6b8cbd75c14cb565c5e246e914debe444f5f106057eb1457df9ec0aeaa33dc7286026f734a72afede12ccb4b3cb5a66736acb43d65d5ce35e6c86cca0f3c13a31204565c8072b5a5bd59ea38013c9cf4474c9076d1d8671c1ac3d4d79a33cb6283ae47da4c5c6e0a795d317bd3fb12bcba67700ba8401ac2f6c2e8a735906222b35e50241d22bdaf57c0767a23a761a0149ebcbbf1754795d7637824872ff09f53456a0d6e53eb6edf2f0232aded1fd7629510afd39665990ede43c9d5570b70d8afa136b039420c1bbf099ee7ee99984b8b58daa0b0666469ad0cd08d1220be2a4316617172fc7845d77a7a04691fff7c1705e5fa3ae0e2104800e810592a7216810c3fff720bf56f50eab3ba0bae18be650cf6739e25651d8998e6a39983520e4e50a2fa11a26694d279c046637b88a824702d7ebc34ae4e9f2465e6694fce5941d3f701655af06395066b63d51192bd0887a7387608a1d3f999909bbe9cb003b10c84eb390bea50cb04fb4dd2e0e5346d91d841146205b0e4d0fe573314bbc25418730d72dbae59d84bfcbd6672346627b6a794f9493604aea4a3e7b606246fb4a4d6b87278b81f0db2674afdcdba76b1079400d829e44f23a06a50425738ff7d4a59e75da1679c0c888fe40103c88156d46d216b2e3b5c0c6d6e7b6d2f99c9ba950f3372ff9b7038f4dfeb9e3e0284ac0058420c961abc89500b6f6392d331080cee527296aae5f7c2fa0e1cbba7f8bb5589d4ffde9b1c19d4f8c3ec8b7fd172aa24bf39705f9cc3c89dc14493a27d11f0c3402e7c340c056a81bc7f407e8de8354c79281191d97295d92050c470aa2d034837517453f58ad1b172546c0ff9fa9aa78372809f507a1d302a8eb60c290f3533dccda04f97c7441c8c450868ddf3593de6725fd3d0cac2f0283975684abad70e2426d80a84ea333771eb7b469eb00ba4cc726032f8538f5c8f37ddc74bc07eccbb38f7140ea4f5757e87e43b4698a1be0b1be9f5ca6a3b62d948d2a9ffb24a39cadc5ede4e2bc0dc7759c990ade3663d7707049f40335b905ff3f20c1e0f442b7b738335ab6080a94840175fabc2d271e1722454f07565f35b9b53213a7452761eaaf072d45ffcaf68794b50b71f880441267a63bc9984fb3cbc5abfd042af5af6b8473342489196cad71339f3923c55b072766d5d3958048db1fabfec7d2116613ca97c34ef58147f13a0e7c1cf7bb9484a5a84e1dc9ab52551a9fecaeaa857688dc7f64afddb533c97d9c0b4a357fada72ef94f9ee9e9ce05ee5eb19879c51c5ba5781068e7b5c459639d6d7368a346332661964ca60629ecfeacb44498ffdfeabd6df35dcd1201f5cdb59bab2cd71c7723a3a80fa315b48ea04d3d4f21b33cdb9437699d9300ffaa1c50dd47182c5834e3366a70a4468a8b4faca09fbcfd9a9b8fdc60272988a32b12ada7dcf2d6cac724d4e073e6e17ca31bc59149787e7ee9c57143ca4376365e8fbcf4441dd8cf16ae920d266b5d1841802f5d55c4bb83f013712adc24c361f633c7939877e5ccd72b7deba3efe15639b0c7dbf6941f5c840963b57a582137569a2c7e97dd3ad0c72e2d6f51e19edbc36f4fc26e05a2c51bccd9d3baf1f589c1af8e8c855b4d7d672a937ccb7560a67fad9abf1a8fd8002bb018f100eaa894caaa3b2cfd1f6dd787208fc61d50b619f65dce83b764a9d08de935eb53c2a92fe15ba719e2fa5b1ca72e819d26b33476592ca35e3298766ec72976dc073f246d7eabdda0fa3db0f55729ba5ee8538f5bab04a2ec2a4f08683d00881ccbcdaa8318bb358d92700174172db3e2fb9f586006864cde00081a543a4a2787b46a99f920a65917e467d3495722f536367a26c25aa3d08703deaa2b42e952099db36debdffbfb95cd5faa343404ab864f924a81b6a6e4c4bc06944616ba8f4b9cf130ec8a75b51fa43b7389870a53a438aae91d80c7f741067b1b91c88a35d31a1e5a4ae1be20ab2f64e6498394d4bc7b7586b15a36a0cc232668d147c60f2a19f9877789fba4d695480082a359c5587b4f724f0e4ceefbeafe6b66023303048ab80510ad39fe878386f9e4c16b92cdc933f5e7ee04bade5a9423a5b5ab7ec6dc29b3d67cc112cebae1cae6a43705088d131a140f0f176793106d2ddbbf43840f1d915e26f64a12b7673fca221557d661f034242206dd9f4ab44766196cfc7a40d0c3b498e644077a465ac6d1e11137e4bf5b0b20185b4e301804f8bad13e83d95e8cd432158a1356c22afa87270bc95959b232de01c367ef76c80ff03545cc8fda87fc8e4fc24a05c44c8977299f7f00bfebf7807e8554436736b1d8affb76dc26de25ce8774c1f427061662d2e012d01a61d357f5ee063e59770c851608dfc9903f661078545b9b4efd4547290584f7d5f426549194d6a966ae6e0501b192c745b624117408f8891ab033205bb466124696cf7332ff0c946096999a53687750b13b4ec8e122c39b1b74083725df6b2f950cd655d8b26e9a5a93277235dd1879aeaeb2c57c53e3f79325e2fba020000539b4c4c41a13f1f0452f5a35a6743c4212946f7f32ded28a7d9fdb17672cb541bed89847788ea3b304677870eed7ae3519523aa55b2e760402d9151b0fde1b9722944224cb2ff50adc29b1e3e14e1d453bc0206896551949e33b4c7739793bb50963554e8c456aebc9a9ecb03cf9f3530608e67c9e461152cdc243617922a0f61497504f582c3a8de3dfeaab1d66d0dd03638dedff74d541f0592ada2c65ff167771c4235ebc4e0ff71508335df8b07be8d75c302954977d278cb68c47f6cd272f1eec4647c1c7a54068a7789f7d55b26dcf1e67eda2cd23ea44e2b6393b4177280ddc190686816872586c58c80d133f80cbd997a03cfa9edb43ddb4568ad4714d9b27ebc1c1da199e060504099e07c3ab2620ff51e3182bdb9278bf895cefc4aaa50ff7c4b91b7c577a096a1fc70d061f3b30e41646707d3beacd2a458b990220fdb799b862657f3ec0380e3088a85e52584227c2de387e9e0a0ef493aecc97229590dcf6f43349bb0713517924fde163cb438c5d90417642ed48a2053f2054d39e6955b30b71365930904744563e2dbb310c3869022b5ff017546a49c86581a9b870f803b86dc150b0055065263108e242264b293fe51974da6e112c3671f7227247ef18cafba38e29ee262d4f70309fa10d6efda4e240ab8d3aa821c64f972db7e93c5ad625c062fca34ded843140d4e009d34cf94e15ba9ad778b41453b28803160727552963d2074b6e773d45515ba53899fc4f75a5239c69fbdb552737257be8ea83e8aafb91f7640f8d75081d0c5e6dee7f38ed80326fde25342d108695406088ed6b09d112653ae547abf97e7f6232ac914a1314afc6bea822f443472edc89298c6707914ecd0c70aafaa9e85cea421f15fd87e879af1b1f68f39bc5c5b9e42ceccc65cdcd5b21b072a86cd4caf2284c5f3c83cb0eb9c511006969649c442ce8bf5459c80a292bfc5d18f7062778a6c2f9131a4bb5bcd251fa7b83a72e5727b21dc54b42f5d9ebd54be2a95ede1fdf22bfc4cb340723da505534dcb729ab095f23fd539da571b5d1f2dcfe0789b9a421306cc54baee28585c5b12976dc4d4100d2dcb65306eb36f932f869b54d57c201010e428a8dcdb0c3f54039969162dc86b5a6127abe05a33889bf109470482d235b92d634ae3a5848a1ea9be72a2cd0f3c4c3a384e6653ea7019179c1453e1bc9bbab745eec4964a8e02bdf77286caa4902f37c5513a2036b2fcbda1df66212dc60562abc99d2195076c198f337bb835dcd0938c7cd3d48d829155f234bdf145108a21a71507e07a22684e2072b03d63d9f3fc2b78d137249eaee824abe2d23b5b6fec8d42e143e94aa5dcca6cb580706c025dbe94a3737ea20b5cfcce363a475f2c2e9cdf1f2407e1d836b61dfec70cd104b85be468c595e0002d9f60f4507fe6d39168bd9c132153e5a88f7248a49b886b3501ab68645e271ab5727e94862d9724c9295f043d6a974e6e5d724b08d67f0bed98e77773dd988e802ca5ac521157726ab5c790cddbf1d1999e72f5ddf86c61f16eb638229353425ada84e32b20e35f61ea3c49370723e7d992727e8c6c45b41dd00dde1c3c7803bfc35e9443d72ca8b39060fba7048f98f3e8725fca8ebb550df49995baba3ff8c26fb12cdfe22b0532065d21dfc1df08bc5672d296fa323b4ae484e8b31fba985137cb1450c79b3e4c43689fa0d45afdcdf072726361417cca92bcdb65574681877e0166a0c32b272d2181e6d94df80150f67239b73d6e2de851d7f342d633c86e7652299f2f15c3cb19480f68b32e35db6172dac69ae42914dbd4c1b381d96c275caa82102c5a722ef58419eaee65fb630972ebc4ef8c701d0bd628cb5157a2056f1e62f1509df5bdbd2757b133fa539fc9722debebe72ee37469184f63edaeb60bd784f9017b88e0d988e9c9899546a03e081207f0badeddad45e04bf0d0b127c5e1c3394b2f138f3096fe5e0f22b498597241c61a454a6e25d09d5d488c68e377efdc222ff30411c74467606bef852f695fe50b7354ff96197fbf0a20cb0c04e694abc270e78ee92b3f86633685fb594a72a1ee5fa1b71d3dc35f696b434876ff4c098c71aa187124eeb722bc47974161701f76303e083cd176f6bbdf1687185def3e19e29e15b409464b615e3df008917284764d93be86cfcb1fb4bb550ed05890586bb78738eae95a40eca09c180d3e729925b184d106f06303320d4ed989d98ed5354a222f07d5e5d64251bfb0630b197ce3ac58282779cb7fe197cfdb88f32d12f0c149ab5c9d83bfd82d680da531017cc5a3830eb929df5ca73920cf681cd7fbb3f8b089f6298fb6173df7ce245e721a34e89d6fdd5d781e1c620de6d42e9b2683a8a4a7ecb582f6e95fcd25ea2972ac0daa7ed9becc0bf30bdb68403605695bf5e146077e4ee7d2848a0bb9aade720dd7f3dc6ed1add2e77799d2760e0a2998cfbd1d98475454b6df03cd5b775c145356ca088afe5efcefd22fca451eab4f55d433d43e9fd44f03e74484445ec90c8de02646bbbe84ad5631b3f6986b36aa3c80f0e03323e7530bd52527b4c2fd7245c70fccb35f7246d8d206ab13aafd746f03ec569dd27a176dc3f6b1c6cc57222988b7f78f7b043e7126982f97b4dd6b61a4a083bd06920a11f4f25c25b0617257f3f7b772299f801305159671dcaf8d703d53fe44ee5e77bacba25b0aa66b72a30a6c0b1f09c4e237c72106d7289ce17474070afc8891aec28d3202eab9ed6433cd531061b32093c577178892e31d66fecde3b122b60ffa1efe4f07447fde72dd2e7ce873cab05d1eaad128ab6d2428ce2cbd075d35124948355caa12d42372f4376f1f468cb452e29be592d16014bab553f9ce56492a00cdf1ca890bedf572c7de450d9cfbd9bcc6baac01895f143fb9a4d62f91036286f18f0449159cab72ab188b3d3580630a06a2b8887df81af4a43abc1f4b4bc303acf9bf332e00421bac30ba2467cf47dfe1499d9f814496f4020c8515a8ae88668efe7175d3c7d0729a1b86e060b69b0b7689adbad1fb97f22a35e5bf48ed575db31b078bbd9ad972d4e918be308d8c0205e7c27a8a0b997e6b9492f48fd4f780fd0f27d5f22129412cae9ac3ea9fb4ec7dedf26ee0dbeee07fac3663fcb10ac462609075648b8372d9a500f120e9044a2e5230e56bde08c2f5657bccd861b175334c1de128510d725205d0a504e1aba0a58e9762b6b119fdba4dd382aa176e792c1817d7c3638c727273db9ccfe684565943c3329c2606a919d9d6c5cbbcd285ab815b9e3a121d5dcde0adbb7c65a6c762d7cd914da77bf6b600a7b3536c1bdd0756e789c1ff7022026244b496890f1bd20c0a2c144663ffa0638e4798b63445afa8d95985ab89572b80a2e63f0ddf42d52aa70ac4399d0af0570fd074ad7bdb21b8d797731936724f286c06b97c4873e07689b7ed5faaa8f14e8ee427908a11f9de3428e48f98728dbe01a3546f954a307ae5772ac46142d079e7c1c9aa6c89654ed5884b9645724ff045eed37b781aa028e5a4298eeaf77d22eb02474eba5dad8c9c3d5e189c7298ee0ebad36771913637284c5d064056548583db6c5f11b1024bd9846b7c9c47a1a7057607d5dc0d7d75008c6c34d9833e348f0f966aab9553e82a6099fb7142f5deca367665a872210f961920fc219a735d065d519e9569c070d1695aba5670d8b3504dffe47003060086d5c5be922c870d92f0c9536acc973a135f761b4c720803d893c3f67a2ebf7dcdd39ece1b580d8cefe7c27e8680b9e44e7fc9a1f772ce66fc795bbffb033d7e4f2007305b37e64e5160286001b45049f3322fe2ad72ea1ccf9b097ffc7b8f7f3f2808c9f98ba3290d3f87bfc2f0a82c32be5e5e4f728f5ff6dc2b76d001150cd20ee46bfbbedf6486f634453a8eee4e641bd34b753989f428515c6164bdaec606b977d90922af5c6bd0df572a887690cc53870843724e92815b0da8dbf7f9874550e254d4cca77d8638f6a9b4d558432cac501b76726237f7a49cc25060066d9319f4eae846d77acd41a8508404207912f24b8ff572dc15e7f69cef79b56b186896a00b23afa78b7b3a33b8011c6edcb55703f9af2c19c9b964847995d034f8fc68c2c290affed2871b26b2098d8ab354d0b293bd2140a78e2da98fe15dcf3ac2a22b9d9879be350e389bff3e01acfa9904d2013272cebcc5a3e6d80ff2a339f9ee7545a1eb40c5d941f1d5bbdf3abde6bb951ece196c589aa506da4a3f1d2cfb3bbd9c3b20f267d7757b6f963a2e9cf57ac412280db916ae68a1243f8a685c8f6093c724bda17c5f51940223cdd3e2d570b975c63058bdc4e97dfa10aec6376c06930026db4d3d74416e746413fd825fc12c52d12a256e4e05ee8f7635548e832a8245fd6380df9459256c73bff481de59a9546d72c5aeca17ffc8077ade153399e502de951b5fafbf63b436242eefb761f89c0c722f398f710e98ec00676b4815643d51fa3db797f7c33c8bf7ec6d1c4786bc3f725f88b8833c361b7b31aaeee6f4e8b0e5fc3100a8b180ceb6a86d91bc9893e572e9a9eb9359fc0037011909fdfc6edc34e4a1693d9747346e7f287ceb81c4687233c7307b428620538f6b17d6560c2e16e2055a39e37c9b54742bf6dca8585b72036c8132a4c7e97f0af08d71d29228ee024e6c3e444546d35f17c7d52ce69d4841b993c8b4ae65210963d0180f89d149352d052962e64637cc5de19135ccf07214be7685d8011e3b9ef81be7a4a5ec47134c2e70d9139b02cb357fab00702472d76c9e62672ea6c705f54080db5590da54e53b8e04a3abdb06c5b30d10d6d71631e6fb7f00e481b4be315eb3f9c9c95f0b26a26c772e773d0ef6fe35f7fde8077da53f96f58d394d799ac3538d35323801eee1fb735293ce65d31e244934ae72845facb4b882338a2bd7c2781b03c505a6f29f49320e54bdb7286fe9fc90ab520cbfdfacfb188d2ff1f7cf68524d1213976a93190d19197cbc2c314b67396d72024e9260fef9ed26bde7bb83cc2e1bfccba001cf8ee61d8ee3e7183336cad572568cbc5b366ae45d0b838907b3df935ba180732c76a71e4812e54cb54e65cb141813259b01dff1925fe96a94f0c214c02b1d12b826bc3a4a41d3d126074c0672f3963f18826594f39b8b117f86bce72678a96cfbdd69f8b79d9c4fb4a697de722202626249ee0c2b6e3bf195a95b7a7df7ed025fd54c831f1cd3c6f2b48b2472e1446d53edfd297d3e73470f6201457840c89451382489c60a54963836bef6720cf6ed7d34a0debabf8c398fcd923537793509d484f8cfee22405a060c476772ac80030b42e8262a30d67a5b0443273033626933965159d947879f85a135b872d2369aaa9479e551f1c0ab87bd7f93ae6c95900c85a62e39db10f195b4ba66727db172ac55caa5eeeb439ef3a77895b77ab397e5b1ee2505e58db7c9e07aca447550ffbe8d7d8807383f61827bb3e6f1bc99cf3416ac148f02a3410126a60172b49a4d463db28b61f8a4708817bf55bed1c0374f733bcc5aa610fd330545ee6ec07579f1018691db5da69e934198a8270cb9c37a5beace1f562249a00f7837693fc93c11e0399c6ced96353e1bfe0b2cbf5f0b514f8edad5658df28fd61b7e3f6f670d838ebdb2b3ee91b96f9d997a93d30fd691cf46fbd9234f4ee1e57d0f2ebdaf4e3337c28f857a47d9029c286bf5ed1629e820674b92415a35fcfb6c1048698ef7fc0c9f7aaed3877def2753caa42212478206e5be62901f31d2590f69726b5b38d3eb6743cd1dbab1bf39db30843b79897897d469bd7501c4cdba8c6f72073411ef93de466b84cbce126bae6ef49a74aac6b65f9306f5c56d6deb51fe0cb191da91900bcfde4fa6d1e20f8edfddae25cb31ba8a2d699d89f4d722d5b772f772ad0f20f2cfb2920adf2185009bea4f1905027bf5c4e93dbe0d9c394c5a061abb8bcdc0d4668a86680edc80137f28a2a9e60b059a45ae51dd670bed2d067284c4140fc8283843ec9d4136393433c9124bca4611a56ddfc3e227768bfd5b28af45b0a6b88bd3a38af9719a5a4fe99e56d83b830225ae331b409568794abc72b126fd5f1b676ae4b644ed2c2462cf841afbf7243577ab43e81b933b574a29381d6afc863fc3a17b695ba149b3530786bf105905997081ad8a9ffa3675d5d3728f63d8d6686ff7c218d38423c8033ec96d3f912c0dd23176d10afc761a40d7728e6f56a5e6b62d725cb3aa06aab7429a91f1e9f81ad7b8f8484561c5fc8af372969440ce01aca3a802b8d846986cde0fac4e27d83960bf0cba75587f731ba6729ff4818e7a9f34a0ed5dcec773fc228d85c209f498e4198f4b57410ded781a647078281ca8c30b6d0e33c6c8436ea5f9f17142b80aed171015f93e72526f2926e98ade3ea4c0fbdd4c6022c53c5de61449c59b38278a8457f95a74dc9c7a8c21b126a6e91ea173e4704b02c60890f4ea0efca7673092cdb02c4e4488f7f9db729774370899cc854e27270bd98912f6971f2bbcc873f7d086db2abc8b536c86726c798a047f1857f26ce880e988dcbf552b3e1b1ecd2cc26188ce9156ae3d8672143d0518c975148602b59efb8a56dfa226ce04684cc8108ec564c5aa54a66d7202bc93de7ca7708c4cd267623a75f2e9a93f67f5403284a86272902359ab8c2b1f902bc08aa19e1821feab6a20e873a764af231f436877a3399ff0add51bd51e38d1e670f57224c3b9a900db85b339fbfb1d9fe9eec8d6a8c57556d5ed93a172fad30bf5005abe892ddfa62747b84ff82ad3e7b7a8eba70e273aa48ec0e5ba727795ec285440549576aff8b5c0da3d3bfb0eed2134c0761e7c2330c418723b7212e1afa0d5d5ae0c6c4127e2dd970c0bffff13d94f2d1bcbb8fda91a1fca0072ebc4c0c65857ae934ac0ff6faf26479d01b3011cdb3d8791b845d7c9524e557266b52367a3cc8fb322d9ffe61b758132c49d0c01b978c0f7cd80aceebf6a48136fd3f73aef8e159f16cba82dffd75da8c86d17da148daa560967be3e97d685723afcfdcbc78548de8bc9e7d012a68f79720ec8ccf93eb6720f817a766912c4297c4eb2b0ff295010c920df77403975f1427d1af0f5fd4914b79aa9c6a39e2c72a994c481c5d63ab46798473dfd7f65a7ac6c55e25967de4878a38e130443d2432ae0679eb906cc8cb0e8368177adfcb79414ca6449ce847fb59ef5862c20d472be5e185457d65cce4201a91bb43a497161bc717b0e443411f5a59a6e2b7db07258c504187717432cc54ecd6a731dd39254bbc320b6ce0f29ecdbc1e2ff3c8d159707702024862da8e91a0a18f3bde30f2d4379a2f3de6357dcaddbeba45af772d6866c84b6ea8a0371da9bac49c842f2707b6b7f90ad2877456e1f827d67cb573099413ee8a2661d85eb7e7a648a70745badb51ffaaa8ce33d3cf44279a7f5724126f633fa7ffcfe12c7b72a24ec32db3d1efdefe21fdbb999859e7217fb8451454fc22e193407400e3b467eae8c2f28d1d7e34ba4f3e0afe5517ff6636da672b3391ff4dfe27691ed959b1ff9156f96d177b300a0feaecfaf85365d7f778b725b0edef848634f362f40a4fd28c3fbf372c7a4cf87171d88d71ea56cf0140a722f1d9d4faef7006b930a7ff50e04887bf16ae7bbedbddc035fa7baa14f508e72d31ccedbb38c98344339fc97e8d7c1138c4919bff41564bb68cbae314fdef772c26f376c3d101ad1ba57c17b30fbc8d2dd8404c0cb3967a0a2b70309e19597724610633db0ce27c65dd20fa0747e4c14d9456029d3b407d26e630d5ab2148772ced2d0258429e241c61fe4849235fd04b62cd324239bd2c078ef2ecffa564472bb0280d542dfa7b01d4dfaa6cedfad238fe4f1848e4210e5577e082079b00a1e2eadb3f2091d7ac9d93de2c7bc987c4653442749c4b296af37d64d6f408db1729077ac4b337ebb8be201fff8fd5afc95c040b8ed1f7be06f81cb755fa9e6b42fe7e0f288d3a78badb011268271c3c9cc2b3f70f92808c2956edf11d67cb91c72d770334837c32b6bd068b7141f26eb46203efa901932a60f4c3082c16fae8a729b9a7d3f674a9da48fa90a3e2b7e770a56f5d52855a62b97f0e034615aeac572826b5f25c6ffd4f168d8e36c7bf99415c261d159efbf2c5aee05720f00b3e747a2ac1dbd9a5085bd9d1c45207cbd15e7f3a4c91438fc15fa7cf022ad444a3b24bd212cae4bc9435c528bb5c68a123e05a35a270147d6388b50f255fba50f07724f4a3e21516e7910980f93ed328512280826d1c928337b3f8aae11fba2365172e8de2e8e3ca4e706d7b5521e4879eb08194dec4af71af5a0892f18e538a76a72a1fd61824bf6e00e956d9ce6fb32db12eb87fdd2afdff9dac10ec94689f36e720533dbebc0c77a2bb9daa932eceef2eeb2c4d78d083376fa8c6284ac7a54f64a1fe73beaeeec7a6719e03ee9a60d06d40f06fcb66e972ba20eab4bddff6db53fb5165d342a4af2f5e129d715565ff99994049a6cfa26e852e7f57b1f5702c8728d12e5baf9dcbaa0ed128885dbc4fc1757baa4dd64a72892b587a1751bf11108f35d4387cf21e505b0882f77b87d7777a77b2ee02b60d4d19a18a2a11b2d2b713b027430d3f4c031ce46a0661b23f07d0c3483c1cffdc7f6dae34c8bbb2abf1f9ca2129882234995b0f4c3f414bfc7b3dfe0b3ea92da3b540ebb71faf1fc680f6ed33af081f90a3501c2e6fbaec5f9d11f78eaa0d9d55b813009de38f7d75249b75591bbae2a343e5d01fa1cfa5271f4371b06120c46188c80e0a031bac6157271a68bd2a88fee97fd01141dd388c35603b65225fb4cc61d14d64de087c7f60bddecf3d6eb057204a0e6157374480078f06ccc23e5c5c9459059e1bcf5765c5bec0bb7339438e93e09d7ba4f5a10d5e3f8cc6b8cb193f2e2b8767ee1d2867e720d606ff7c56a858a326a875bf7c50a6c1f9c7c5a5c4b183e62ec5a6607d26b725adbf06d9bcf79ee5b84af4b734e9c5baa50461083b872cf341f6d5928d3dc23330481ba9914363dc3a362f21ea99c962662f8931c156fa59a7fe2c25dc8b2093f3ceeeb6ef163bda805ccbe6a677e7c5f06016b9720770da6e5e6d317f79772925a8c95c49b5c7d712c5437665b89647bc3d9ea546b7b411152966aea70a7576c91ab4c7139c89838bf157edd62df8b270a657a366061c10abce814b80dcc7220f45567707566141b74b9873f6a01205afa58155e3a436f8df364cfaadb8072f8aa766d759b64cfa64adfcf66b586b1cbe790c567dd85fb39f3babf3457487242049bfcdc76be75d1446d45e91a578f37fcaf50b886704fdd31496a167498726b841e74967866dd835d889d64e3eb2c4b1bc4d9b36b06a4f0031e3eec5728722732f50934d3c3f94065a9f5a514bf98a7a7152f404492a9cbc1c98cd736f06b46eb5e7dfee403fbf92918f6ac9e29d7c13529db5887ce356c1069d4413a7e72d3ba4e5eda9f7fc4df32d9357d330443f89f66f8bdac1fdba5a7cebae0c19b72f5153c410cb0c80d552de964ff48166d701e912b47f3981ee026e4930fd5f872258b18606bb1ac5506e5afdd8113be4abf3d2b88935e89871a1bbf964440ab72489aafc5de148d8537312dd73ebae1178d45773f6869248f02273d0302d1eb72a06425d43a16664e3a1aca02d07186cc27f615e15f4088d021ccd5b58980461c231bcbcceeed41e916f1c639cc57e4476cab6abf08bc698abb13c76550f0b96e4bd40611bc970ca92cfc3766759ee4beffd9333815102be0e4e49468817bfc729904a591fe1e1117124053c8a2b5f46fd6e130152c926ef1b01c8ca0183b9172e249e3c6862f82a87824e6daaa56ad3c507f5905fd2d3a65f0cfc52f427ed57239a52c13c088c2c08f516095a9e8427663b68632de49d5347eda98e476a75172de96ec7deae0c997559f07ea4777e01a92e4a0d6581c7eff57ceb76012953f08ec35d2f905678db9ec1185b12654df085ca8defdc6984bad43055d77553c34724695ebdb06ac0ec579f4043614d296a8c01aeacc6a8fdbade10ad6c8f0cfc67243aa824617efdfb16336461e1f2ca9e72855d67830363540c88a4c9a646c467290264fc544255e58535b01ee2b0cd6d94139a2b134c585c48583cdb139974747b1211300236fd0273a4126b883da40d926d488776fb30ac1ac8fb2dbb136e572a7fc9732944306d783a2c08dec0cc951da6222828fe84b288e37581a1654ca07f2b3be6352f7a0342921bdc05abfbbaabe8172d17a11841ca25fc85339f95c727aca9503a2c8c800ba85d805ae759412d89ef9aa603c10663e65c45f39f393728684378f387537cacb997bcda757de288060a6d61703fb7632f74fd95d010c725c278d8337c2e4d3aad695a8545032d15c8c77d8d3fcf9de4b830a63ad331264244ed69ce580e909bf708dbb40c78836dca0ebc036a36d2ee23085d362e9d3726fee018f0529bcd14bb161bd9e579e41a2c6fee7c9f30081b644281c3b8e31723cb1e9c08af94ece3612d7748e6b37758ae98369598534a3a21dfd653f49e572f59df3e7ec66fbddbd6d11d8af8b2a3a8ae16fce3cc77939e3258b05cdeb33727a6c5345241f1c536d4c81781664674460446d0ed6a6850827d50730a0ed9a7203038b29bba539d8a4cd28d239a14b026e908c2c09f33c3d328fb73dd5f2eb72239e972428f9eadbe4dd55d7b852bd857c14eeb36aa6ccadf598a7610b79ab725be6386929c08e2339d060eb74de12b561c6df2953db3e5390b429ef1aedb272b2d3edb7b27698b520daad8dba3c794ef71009b418ba25e6e44ef5d78d6a4029b87db2cb620a5886b4678334578e434b9b58cac2814bd8655825289ec22fe5722c2fda01847fb71cb0dc4d57a191c38b77324156ce897578a54d08cad50c2d725def6b83e49d5eb26f19d3723a4bf17465b6c936514cc3c8823b4e289b8e0d72b067b025d91530f2f6f48427bd219fc4927f800de15c1b3404253932c4b8bc5db4544b5647452a5649c6d70795cc5a78618327cb5ccbca71b6f04302bc45a3230d56f986e95f28ee203133cbdbe089fb83fcfdef61133d1c0d3188a714f86a72bade3d634c24b5ce314507b7bff07b79e599a10514a63c404e6f18fe54a682641df224f62d889e53c2e52e4f2d003ef3af8526f489d15a3163a137bb5af1e172011651f1374ea462afd794abd257e3f1e3b0bc26cb81a9ac2a54d64523dc6d0f6f7b990e1540354d988a869b29038ac254fe892aef43f1dc63c705b5805386162a10d3c78fbe804d3cd5944c938d4016aa1ba75df61713160aa70128e4ea5447f8e1362eeabf21bcc6162bc83916188777da253c00e62d65087f308c4e1c4172a95186e094422938cc0911d6d9e36349ab6435d55856af4acdebbb8288b90242f4ade7396e819ab50dc048e2f09fe216931fb7d2a307e17d2c1c095c171c747273bdc827aa12c8befa33cca325bae4a82a334fdbfeb0861a7f5bc28ccb762172559429cd48f0f965ae3f55b82b36db55909a862ee3447573236bcee76c2030721090efe2141cdc5d3e7fdca241fb44c08a5c94987659936366d24e5eddb90d72f5d65788c69899bb4f79d78fb5d2bc1566008f584b63482081c6c5dbae78cc31d0fb6c213f46f2416a07f7060135bb6fcc6639b95117c6ce236c337925800f231b43ab7403597aa39d29832d38907618b38c1575920a0dc3a3f12e30cf812f2ef76fdaa4f133eaea1ace48710e441e44d30adf388da4648ab869d98f279c6058df19252c6364283ebd95f7332b9e9c6f62c1b9e0fe92204a1289fbe5d12dca64ede77d8a30bf5fb3e762baebb6001c17573ded15fd2072a507a268fd51c6c472f8e9c15852aab6acf0caee07f4cc0e09919c64fab1639b847057c97ab264d1724c5fff554dec6480f06720769b9e62c8f4e596c1c945fea8731bb9e0c6264472b5a02df4806ff9842dedab81b62ebead818d5b05bc4e9f4af96598a5864153723798ba06cff95eb8b6cb8a7289c1c289e6c50caced57ae7c5698c1550c58225ee1d6b2c269d11f3bb2edf36414d2cc7d9eb8ada2691fc57b88e7c90d14c8ff72ba97900eae942529f67096c2d5c33b230723d5aaf86c444b2ba108f2b619b67241faefa4cdd4c38b05c72ed2e1450a0359b4333693a5e52f31d483967800ba33844e930bb3e5bf3fa72654909779013fb23a6c5d1cc4afcba99531efbbe8f3723e4f1c21a815985a7c020a5c09575996667e44cb692ee943559f2d4e335a6e726f9ee3c7534f5567f9293f876fee08464ccd5d27256760c637c98830c148f905883000299f2553fdd5e084be4b4a67e040f3b9f701bcea03b50706245ce7e31d3f4982cf38f58b0d58d0380780fd3aabdd7adfc52d5819ce4072719aae2e4a39f9be0b6991b1ef8016c79cd9e1813b4aaec00c4695600730301d7a8cefc5740f9360b0b96d0c81dad8cb90d25a94221adc96adbd1ce63bd5c7917deb2c96862f98a940b3f5fe44527ac301f9fe9f2fd5c1c7eff1ab5598cd5947478bdba4845e5bdad1d18b1bb9ef8d3c3a832b5f3642ebeadd0f66f47fb9d736b17bd09f8a09270ea0ce6efca1c09c54192210aff3697f506592881996996872aa3cfcf81f72ebd12733f72a4f59e84f8c5cb2e4c00fd15620d02a038a3d1678265a6dc1a0727cf9ea8bab085117f12dd181cbbdb5f367db4e6d70cd2a8ff3b281ff51725072636f6049065e8a72cb54edc230c36e8e6e837ab4bbd93e1897925b0d480c2b5bd55a0316c392c06ad400e2bc73d8d63fc5c34c60af7c0d0f204ad9ee6b614056043275da8b54c5f4ac1d055668832858ca5b9a1bb783fb160e6a15031841c97207b65671bb5cab0a2a3db5b3749309f007b4aa489f3ccf0a12ccf38ea43a5c60c4cc0bf5a12f776ed5f26b76da40f90a88fb686c2234c041dbc08546d5e31e72f71342a91a86778b95e9b2c49a7cc0ab016d579bbe5684061a5ef48fffaa92720da0bf8645a39708714522c0d5d0eb3a9bb6ad38b60d2f357a9f5ccf9639b64e36329e1ba70a94ce821fee2e10c154c28f686ffc15f964bb6b6c572a3756e36a1ba38aefe2cf08e9494d9b7c82f58cde989042a9600f4f6fbbdb4a95f1a270006c138cf745bc4a27db52bacf32e2bdf558e8eb5dab3a8d9760aacc80b6cf0e7249e9864a924c6a55719620fdd3e9e55d1516b924432edf98b2ce68f31bf41172381959191168571077388dc2b7dd4dab2efc19f19ccd4fef58ffdfcb64b70f5be02a7c15fa44a226dc44c19e1ef4581b0ad58a2bb9b30c91f1993e5e84746a08a360b0f3bd460c78aec68d73beac2271eaa97ccbdacbfa435e361655b9fe8272d261f0ad9a2165e0ded6777822a6b4b068e3039ef0b0f137aa4d5297048694720f16071df3a7a0dfb2b5a3c2cd605f5c6c883eafc2a9ed0a23b0b90fa86a07728dda7c6ea9666d50960d85008739bd21b1a5356bc9869b3cdb3bf8e31333476f76baaa902791f73d3c6fcbb9b9220868e2251b80d0ae19d8b523bc2e2448481d9e9acadd4d29e32f73857814eb1ec801532cba19d1e782576418a7ae0e4a6f7299775b94f3f074d24b9c90e993b340318430245f089e63b1f44d6986b04dd072cc29f074297c82f8286cfc03e7913a57bfb6df3031e3616b63a607100f09794b50ca3b48b2e550bb1bcef253422f25991853578b861e2205de5a8a5d04b60c21acb0f806f846d05c8bb1415d989da90fbb40a8865921f09fede82f6dd569175d09844329822c4872c8b1fec86675d70927ffa9be2334706070e15a00e0dac5458959059813bd145b874a7ff20b2e26578ad5aa349d58d4348227f65cded3de724948e1614994ecef4a949de37055b610483e43778d5ac4a2c8898eecd4d2407275d2485be0784bcfe2bc361c8f4a74be1a421d88790e0b1139edfdf36b0e3a3d5ffc200e1e82f164152eeb56b1191ccddcd4962d901142e07587ed3ab389a6726581a558cb6ed6bf82083575ed6e370338a6443455df60e6fca3ce06124b2f336fccffa86b0fd133d2b994c3fa5684c3e0cf6d897eed3a5a8cb08906be3d7223cc8491017f8fc4a5d33ce1f3095cc8a2ff73d906de2dbb4f781e9dabc95b5f21b12546a12f41b05913d502131ca1ff3ebcbc5d9676c5cc031a9698039ab59c7239f3093a5bae731434555dc1c2eeef90f41dbf3001c06371455e48c622dc22724938000ddedd707b41ab03f95c70aa124badf097767f51144999fb9eefbb7d72cbd82089a537b86a27f77ec817257d51c17b20b33127a6f13353ab402661007278ad7129137acd60167b844fc8750a7e8f52889534081fc3cf1f8913a745ce72899a4fa9605d78b6df1b8dc5501c5ed0e4e16e9afa19df9bf4a61ad57ec5661b72df71849bf4f7900193c9b5b283d4ff3d3a7f1859c94ffe3f18cd46e1874c729ac1effb98a1ff33b04662e69e5ac2aa5147ca2b77b79d9c642370d15ffff8723fe755d9472642068bd281c40c16b7cafff73e054c78e4b9ad11bb179b93be6fe853788bee0642128b5d83763e1d66da174d08fa67ffbdaf8907c7251cd910220d11638329010a589ec6d2194a66fd0d99b391d056debc496cee8360aec4d672b6b401b4f345352be8f9781e6aa30896666f06b8da90016719431b4717b5c0594a26d005fc9c38e969c7ee401505faa7fd33925246dc96df172b60903ec6af726ed13968b930ef234bdd08a891587e1ee0c8558661d26ea32fc65e3d22f9464ca385a56c80081c90be4e2de3cfd3a875adbcc8799ca84551abef3a6137d3575d80726d31150c56cbfbe9fd7809e7dea6c5946d578d716391a5eb77574fa54f5b2ac163ee4b24012632fa34a1b3378dfcae6050f577699fcedf3eee68c95a4d7230b27b10e39f78ff37299865a23e8c994ac50b1eee2dcd38585c6ad3f8e9e654192b53759f55a976adc84ccf1eb8b4f453e22c5e833d5645f1b82d66fa324d0b2c6a30c76a5496af61509298f63d573a7353ebf12b492668642a89c7994c2372fcb7991feab78c2710924535003719c4da52e4d2fb056f085641a68f8459df6d457d076bd0f7c78017d3f4ec2cc1c83e4573b1cec4ce7c652f63948ba86dad7221849d890125ddb13a45507ad938e99ad0abfb9b990f4477ab8b7cc4e9f728612e8179931b9892dea530c3623dafcf1a41627cf5ca3581c7b8712b4809aed372fca61e83fa1231d2e9f519b9f3dbfe786527d9976aa256189a83b83fc827007208dffc7472484f0a10c7bfb75d4c006f43257b2b6a26367ec73861730499552f0f327d3205bc341f524acbc3b5a1914576e6ede0f8d0c3153d4600a4769f525c02f4f5769455890fbc91c05af4816e222df8d5289d16bf129fe2eda420582672cc6a2707c7caea1499dc3c11187a17f3ed9d52b54cd690788a733a7c59f1743ed103b37bad940a3d14fcd1b35f7e342ee2e4c71d5eff71884afe41e3b9bbff72c2dedd85c2fd52aa6b5755d571fd6d2674a0a8991c87f2ee284570ec18efd4727912ad99952f30f08f0b251089d137d97359c621f7f43b4b8585c7d4c3d52072361da5e255865e80b287c12decee912008e485293c5d29014d5a8f6ec4c69772b2f7968232790fd86919910c56315a2e775877cd0eb2c4d3e45f66f233e482729f47c9be31f60e9bbd9c788f11ac18aafc6061fbd927356a43626a8edf1a58729f81e6d8e2b6a4daddecddc7e0d05cf4dab5086b7c73fab29442ac7178ca7272edfc9f93f1f2b85159d9c8b567303d52c43ecccd26eafab57415118bb2b43c4a49463ea94893d365aebaa989b07a636588047a349572435efab0513e5fb7207266b5101d47c93a74effd532f3f0bf86de60ca8ee802d9f9ef6c9c7bf3137a772caa3c0a3a7bc5ae0b9bf43e620f57a5aec751768d0356c5a9ab806c853ff5072105f9b5b8af362a330ab80972470dd4e664168d573dc2c1c80a0f6659f012c72f7a6d9b76b4af5f8f5935797028be604d4acade47f11cd09f170ac56b5de3572e3dd7e0fab7a6854ca5be25d7c6fd6c4681031fcbc3526c4dd9018b96ccbd844d4dcc9996d4e687e477e72c2204645800ef5abc1548a3f1f38febba65709e7726fb28ac1d298c64f9a2e15b8cc4e89348e8f88d1dc315c60a9c458c9ea874f7234f4cb425b582e17597027953b107dbb8a1476e0bc780572a71c4f463cc2ee729fa89d6d5878525ceb1e2cdce9d8e1d7b9d7c02b79f33722f753d805d97c5c724b4dab9ba67aa5548689eb8bd9d0ff02e0cb83361979ba4981f53ecf484d6072e5b6884d682ccb23a9712a2bd7848ee1e19e38a027f6b647b682c87b7f4f2472fa16dba7fc83aca625952a3cf7154a1947a7e6324b4cb98ce803827c3cb9bf111b24888d7c020d1ad3368117bdccbe778c3e38a2c349c35f58f7098a5597427207384838e0c3817216731afc174e214b722454ae364b44637b174753e0b3d75cc44c339f427d8ba1e4b085aa4b36cfe5331d9077cbfa34c33e860e44cdad18727e14905ccd548d53a4f4f35581a3dba762587d1905e9ba0b9da533111a24df72de82be1a5f19df5c0e6736ac7ddee79a6d0e6b38a3fd51b1e3ce1b69da6a8f0a865d90b4126d5927476ceaa935962e3d43dbd36a00954a38c684d800e09e3a72a1980ab5d69bb19b20c97ea0a99c2808fab7ff32e35f481a5bc9e48e6c80f5720c4078c3c8eabc624e269ccf927b0e5419a837a857868acaac213bdef99c0a72885cd6647e9635cc5ad1be6bb44146488ffe289da20c556ed8674f3030d2df72f25049dd00aef62f5226958673dd9581091c75c9e6e1ad9a89ff9d31bb0b0472f09ba225b96fb5b00349c7e7e9dcfcf86762c78ecdc321dc8a2a27bb233ee90f1d2dd39cbb664547ec71e0d9aa2ebe764e30e25f28dc8a4dc0a8fa869cbb1d721b1fb3860d05b743f5501a22b427d124923fd9cc677ed5523f2450ab5c5023728893be6bab7f7ff25df53baeeb4378907cb27dce353bcbb730cdab860857d50844aaae0e6cd546d93c32f62b0c44ac2ade92d403fb231408f3ef452e27e82f2c0bd046ba0ea65179d401bcffeb7a467b9b2e2137ee698fc2291ee318ff270572bb9fa211638c5b305f20e813a005f391e071881c7fe368ae562cfb6ecbfc2d7229fa518acc1969f73ee2a47b1c4ff7f6c57d2d1a03d701763a8b6efd03e57c72734e2d1ad09ca3870f9924db650c99818398599ede33a92535e0c044c1612f72a2134d9ce4a0fcfa6c77826899d5a315d0f0e7451daf643dcdb52d9d96d4296362957782d6e7f7b1ed2624150d5bce3aac1a818ffdf6a60b2fe17905ddd8e1701a41429ca86d93bab0ee2f84dcbef80bed94044387c46047f28d5652d69f16719fce61f91c744c5017f1e104ce40b03e245ae4010964249b22b9df3a36e88172b8756c3623bbf8c1c0ddb99416b20b20555326b09b1f6a088e5ea8c153632e72cc3502af6ffb6815d4990830bebdff13733f98148c1f300986a346bf2e152c16eb61f1314029a5cdc54b63ce21f5c0936e4c52d56155c58f31d9855c057a9910ea9230cc9c299f269289e6f9dbaaa027f880d6bb5903e22c5c60e7749101db1ab935b438bc99b8c7108c3177dacdf73b4bb0386add2858e17132871ef57c6c1cdf2cc41b871169bf2461ef2ddf3560b57214721742d24692cb1a2f70acf3341ed6f5b40a4e92df086ae66a168919558cb7d103209f430817b152713768850d41fed12ee4cd4c2f9274fa78288a891f97875d48831c0eed88608907a69062b372ab24b85bf108c7e511b05257dc808cb76a68ff129d0aa572538f730d54b0db542965bdbd75674273d21631b099fe6f5beabbe03a3847e77ac935224a1e0a5c726ccd34aeaa93b374317342a7d378f88b81eab2c69ccd0f4076e8a9ee8d7bbb15bf8fe1fa0391ab053fc6a3ae58f4820358b8ecc5c358fafe7e70ce8f90bc045041c6bebf2ff361dfe8f956f7c4ee9f32eae680c75050847de717b4a432036654988337be082b99c0a7f492b1fb866d542c765e976dc2a7701c93c7127e8f2f4aba496fafd728343a0dfb65ed2e60b552427280b0b23e94b445443baa67b7a7725804a7ffedf92895890ca24c00c0714c3f0b1393ac7429952ff166b4b5dee67218db1d60f6c5adc0809c72fa0ba297db2bca93805dcfde1a77d1f4ffd016bb7200b4ca7e36fa34f45030e0021f5238b8d21093c5aea766e0f3ff27739c4bb172ec74768ca0e808a2c7c58754c0e5d53ef6edc8a192e8b13298248db9b83cb57231c34f802fc7e0a71c2bf7aeafd1ad7baf2499ffa4d0f0619a0f933ee06278725d1d7f411eba5a620a8676f34ad4aa73976032834afd612687ed7a25a4e7dc40b6e884e958c9ada70908a589556a9a131c45a189610fd72c09b6cbd31750a66a8e7ff2d4433c34c818ad11f99e6347ed73c22efec4f27dcbf88fb2e7fb73377240035ec7e7d87f43a65359dab00bf92caebcc9ac0dea38f4f1deec67b24f9b675c6c0e95f2c0a0ff1d8028c24523400a22f6b35692a5f2956c2ebdef9ca18b72a7258674a0dd01fb86da1a1a18152a1b7833c96efbe55bf7c62567338c880866d0ee8fad03188683cce9b8e05d1072d607f32bf20f76488a27f0323dd0ae0137b2e9a58c294608238ec7423eed70cac6bd10580a616958b6c61970f33e81ac7230af95c8db4ae468a810ccd12ad81ef3478a179a53f4db5d5352b48d06a19e723cb7ea350fd502b3dfaa3cf717c05443cc667781ec8ad06541400c275b1ea772c06fe0786ab8ed2fb119c40a630a241353175d0a7f966be46db4e55aa539f772b3ddccb5bf0ce45969d24a7145851393d4e927b9bafde16069e8bc259b289e1946583dbdcc1cef37fb71c55bae1c31005650bd3a182b2676653c598aaf360a28ea96306ff332f1b57f6c62aa4f369b87241ec0553aa5b67d95bcc10a5ed54b4b43b353657a625d3032d1486f52c734937c155b66ce6c4e69c3feb783a0099172544244251040af098f504b5e1cd82d2a5e316d7d24777504c82b004e4487e7724fde56225bf61350f31874f2fd96a5a018839147364ea655ccdfa8e14125fc72b6c5f96d40028b8cb4c2be070a43af2f771a009ce73a49233ab59835c096b072cbfcec4096b9ef17fc1fd7272a8cf74d1d8b1f2938159bd8168a43154efd423c80455dc4d42967f7cf57d999968fdd155e8df9ee23d27472616eb932038fa7729f8bab5a097f43a50c43e62a6706b6aee79886b724b3af0585bebda42e663330a6398b9b7d61981b8f7f54362fbbbb047f850821642d4162fe725a9341ca4b72f1477433ee7ad1d5b0decdf32823e1e6b7682e82a6fe3cd6ea048627d0ef57722ff46ababf992065e1bac1807da0ae7d37ec6049d1ef95405966fcca5fa8b6723b5aa1fae9e8799dc234188350f9672d3213eef56240089ebd7aeff4f16f7072283bd5ffa2d9e00a7bedf72834bb58ba9a3f145993970259fe419cefd7fcfc569566e65e86fb41eb668aadba85d710c968d1f2567edf1a09bf48785aee014a72c20d7d1007f6a0299c0e1b6b6c0d087734bc8ee1768161c2575a55a28d5c7246243efa8cf78d252fc926d3cf54139e5a36487e8fc304911d6ecd8e0227ff4e295bcd59c6f9db6a2aa467b6e002b252d98cb158f6c1f408f32f9aaccd346f8d6ef84729ad4e0fed989b1e04d2230bebeaacb2fb8e0d002f4b24ca4b4340a51b72b6b1bb1b1dae17565f2116511b37c609393dc6fa7fd91482c29de4dc7a5f9172ed08ec45f353a68f6b0a8b91a69d33a3244c1e7487613addc8b242898079b1116420106227b7a9d622aaf05b654454e79f9c60a4f5d2d5860b9e0362d7e36c725da188530fa790ff66814f26bf2127dbfde1b1f9d33a7f8ad4721b1811903072fef91682638301ec54f68a58775ae9e2838df69d7a233263ab1e1d8117d08872932666dbbdd889e5e89429c2ee0c34f096060348cd7d9b3b232e5b7fe00c460daecfec133c65af08a71a7bb66916580329d911a7ce483ca6f8ce40ee30a29e721a51265714eeec33c9ff5ca16091c8862491efd7b0e8ad2f56291915fe8b3a72b89efb6d82a31c02bccb1cb59b7ee2a9f4c7d372b170034ddee150bd0001e2724506f75dc9e1885cd577e0cf04b143136c35568ea2eb4443939096e2c600b8728ddb86c51a788c081b6b94931904da0bc927e880714cfcd28aa6d3d2c8761f72b97b5fafce2d0482a371a8f9f169e3b09b54c75843dc01a41ed4ad2c544e0e0aa64312db3af89df5de3b10dab4b8f9210864f2118f1a16821b19a79bbebcf1721c413b5e65ce2e2d1a23dc4745ad176db59124e2b9aef9d7a14c5cf7bb38310e66182f10496a927fa552116918268af6f7102272c078e683358ed941730d2e72d790eb84f6bc2e72992d057aab3a249068b781d027bfaf4c63bea4cf528fb8723c65bbf45d5aef88dfc3d6155ab2c36ba6bdedc0ee546ba9f21a3a8eee4669726b22c8980d13848db60c430b0a45b0e7a0e9c39863e9b767a977f9a70c1acb7205df9e104c66cdf33d07ef522759ae9373d721ad98a449d2d0824a2edc29cc0432b6e68dc47bb20e3e0dab94eadb5aa08c5d6edd97f1676a1605b967021de3728375cdd32b9b1fa843bd619ec2ed53ed2a78dd8165c317f417acfb1efa09b46d9a2f3dfac920cd84e642a36f2f00cb316193332765cd98fd9cf9a806e0e29953bffb7b0eeff10419d199e6f6dd6b1aa57aa6160b913be25711b22692a6e0be72e48552804c28b9a6dd331b5c2b6fd668c197195037d302db25efb30ebd00f57241c631df8afb7f8b5f0224bb4c712e485acfa2cf7fb7af8533a2b9b63b3af223e9880bb194bc3a52690c8e13c4bd71f5bf7d3986f1186198a274f51efb5b4b72085179a2f1a890996dd8009e9c4827fc314a0653d644aca8e23eec72fac04154e0548b784b4d19fc13320ba55d917b92b562b755c551555115a0401c57cda2378ad335078cf850fdaf810d8564f6688cdbb1ab0d533a18342eae5ddddb99cb1770524e1d0f1cef2d4dd67bf279f92f8826ce6564824d274068ed05b0067a8972e8665df3feb3f8f952678889592164cf11e1c47bd1107c8c6d14680815a36f3b39c2c4d21ea0729418f47b24ebbdf6c112ce2439121567a51e4de534ba8b12383ba47ef6770da576ebc4e533a725af78aa70388256dd5f9fbd55caedb890a672fea3692b198b02330ee83fdf851d50e309bfdeb2bf7eda7f1b0d59cb1c438337d59e89bc6f18923a5d701b24970339b22dbb18f42f9aad3f68f321820afae9727435483a1b9ad1804155b130c1ebe7c5b61a34763e87f4c4b60a4c7047642100e9c72a1746a0c9b33fd290a64f8d3375f64c1245dc2bcef6837908a232383872965049861e420f8a5bc7bd1418b35bb66b05f894a3cae6087aa88f89cd14fe3665d965cf84fe554cdd6a12aa88c83bda196abe8aea995a0c68b5284f81cd2772c2524d993e86e6ad5f9f18b64326d59ce3631829f96096c9684cdd82f4a39672c5d1e6d548bf2584dc265950a31b995ca6d86f5bf22faf7b9d794b839d3bd4164af9f36c46793e4286e5bba96b20230e9910df264d8fcefc0e2eebb3afbb2746797987646227151bdf20a4f73602cbe3324746542b6a61a54e2a661982319969642c266d63082b6b364006b516caec563096890cdefa0fd93c0181be7a8d897266795e015a40fc72bf4ad294d25590245abca70adea61c4a07ac930bd19eb76cf4a8f7bb2a306901cd44d9ab86c256a44d994fda784e8e74b1d18e2a8d22a5007cabf3290cb5d810415465f05f7548477ce251562f88200ea8d02af633c00456b7e1beb28644852c0e45a64f3c28cd35665f1656ef6509664ea136e22d8083723f8d3916fad28ee0a7336025f8ed8d1d482725cac9d579d7461b7810b33f0c726822ee44993223e900ddac57275200371a44b3a5f108a47d10f039d2d693a6616ffdc6a3999894799404b7963288d403cb599fd2d0d72e98bc0e894fde9dcb72893ea48a8d797e98c3acd3c9d893103c7ee0fa0cfda3607dcdf29de521b0b7570b3341b741fe3ffa837b7e76d45aab3b9e6b15cc10fe2f5c5f05b745899e15728d51a35b06a2bdd84cba2c0c3224f26200b6722e5ecd00130733f0c20ec23e7236bbe4e7fe22022a826501f05e36e28629ca04df709a4a76ea138560619edb40b739857aba2183d6d2182f7a12a4cb29003f0e9360e0a8c37e600c402e5a0f722c6f644a59e2d4fd51c78737fdd5f6df9cee8a6a19bcdca7eb97b145170dbf72c91d0a0588e68649523e842b5627281ce5206fa5fc3d8c9fe2f32fc95596e9729c341923c014bf53d8c6ef7b9472e50593305636a31b7c64528eb11fa7d22c721162816e7cef352be7b244108909f97e6214931183f660a1b4955bdd215355612a3395f7a02ac4fcc5f6db8d4e8b3fd457cc348c0ede03b6537748c4ebab037288c1cb8ff1d7e01bacc7670455ad47c43ec410d7825367e57290e670dc4ad33fec9446bac4b757301100dd9b75b23e1c0c5df451512583ca37aab6677a52be04c44a5a604aaec1165f5e02c047047d0ec3276a6effafed0972393de86fd6212a5731fb67fcaf908a62d329a1482a6acb07dc17d61d306534a3b0884212866f72e2a4c5f54350b0767557282afd38452c6cd625af5ee10038561b293747525172a73cfea45646e84b0efe7268d0f174ee5264eb403ff04acf848005a5202f9472301a8f2bf4888d05a0cf16c8eea143a339194115b930b80175c62c5148f26e72ab964e8a40882a729a71fcac8b0d5cbd4960fa76b79b1f8f4e86773abbdab172e5151a79b233e62b74ed92fd22b83fe5119966646b6f03839a17ae3a447e9f7254bd7aa9a5b6424be31a58f5bb6aa589d23b78c4ec61c564140287411359fa02b37cd63e98ffee0511452fe059c70b4b5c8024fea2045ebffb0595a39f2a887291866c31a90167a1e02df1670a2a13b694fcde206ee8c0a0daa0cf6b31960572fee84d54c91c617196448b98bbc8ac5383c0bf7949f30613f5a5a0a8e3d4eb7216d0aefec2b5e32dc011240203c5c0c8e076f2d2c7ac63a89486cff70bdbf67221a2950e3b3add70e8a24227c66344600b59d0d28431d744de11e98e96feb1726e60bccd89f24a1f2bc28489aec3a48774db7a54af3f228db4b1e502046b1472df48b1c0434b745e103ebdd4ef71356e624accfab8182789a8eeaaab7668e4724ab235bb75ce4b3c82ae8348ad56e0837dbaa07d5a3f15a55642ff3ab0b4fb726398d69d99c04c589838bdd28a2f0146bec5b5c33ac431183b251392b2c8120e7e9bf2577e1430679f6f47d911ed61161f3b980b4880179821f894e1689c5272559c4e1e2d59cec866443e4a3181ce3c1cf8d3b1143219f80d031d5e4fd46f72cf82622c359c6b3888c5ed1ae9490a2f42d53d0632f7aed13e7b42b9a983ca39a42665d29fbc2ef2ed036bc9667c45ed28bdfe37819a2eaa0ef30ccfb64d237205f16b8df78e191713e8106ffc0ee958226d68b9970b990c1de68de206df0172ba52e654d37562aedfbdeb8e094cf5e0a6e28a870f1fa6248ba531fb29968072295303b3f455ac1359dbbeb271df0b36cd198a0681edb39722610cfcd8922e725d5f770ea5579fcc82655689b15776341271544bb564c1686a2d64d4445928728a500d24fa66f319a900c3e3dab6fe183e4c36c861a75fcaa1e22745c574a1721de22f7891c88957db6c61a98ab2d2905cfff8d48ffb5d8ad65390dfe3cdf8278a12fa02517cc4046155913f93e1bb5a569f37a5efd4e13d256dab75ad78b51b2c755111b8ec7979c6cbc9c7e15bca7f572a74f72a29c4b9270ea309e49d525fa995340b6030270d7bbb27855a4a02e1932331b84b35032b9c2c3c1379ab1908ec31abc1c2f860a1e55bbed304a164528fef6bddd426c060887f41e44d3bb9721c889b146124bac06496dd93f3e965a0fbecf4918428e55e85c9fef3b88c6e7200620e73801ce498487043ece59893c2c2c6b299622038dbe2be87e70429b272fa6d4c1a0a90ca0383b9781d07227c7ae9e62287b1920a11451de5a64f2d4c72728f2bdb310388e07dc5b4d06972ae583872ff200e0f3a849f6556629a34755f95eb1c174759e58900e0261b632fad95aea7c4f5feb24b421e1347b7477ed172526a95252ebc97da9d685196528c08c007c7d6de3c1c435feec01d9b4c16c57282f6f22494f7298ce37afa6f8122d4c02fcb6467abde0cb3a2139a5740c0ee72512fb4a972c3ecfe42571387fa9ead2cffb62c9e6d8cd0858cff2b4ff66ce67231f078da992cafd56adb6d6bffa777f0c092fffb84e1fe4d799cc8641f2f5b724e5a054eeee3fabcf5b6173290ec7a847841c6c8bb7ac3665f09188084e64c72346126b74104a446748362961b987db26ea7a8885b830b2729138587174f9272a268166d9980d1d8c8b3ff14459ec3b0cbf232c253afc9c73b65c548d4e4d372a49ad4af1412fca8c103b761e9a0d0cd692d9e46c21ecaafc9d759592b33b5728685fc7880791bc9da13bfade4c1464a1134884b050e21a5e6ce955fe0399a72757552a4855fc288bbc848bab1ac0436f3e9e2329b9d876ec93f8c6cee03510ef389423623563a1bc79267204d33c7ac9e66fa8d542d00aef94d5bc463b116721f895f46943f41d5239186749af7c967e2bb1ac92f4614812a317541e97b527201ea030d63da6f351e6f8c4d8d03e4acac3c522549a40cf18754f177a9ebee1ae8a843ac245127e567615666689f6abe1c40dbaa2b540fddf58daafb0bdebc48980d0e89ac41777a219395c62423ae60b40409b5e569cc35fff1d675f7608810180c006919f8475917f178e7fc52f4051bafe1798b39d66a69603518b8b4320c2d68722950742e24d3d62242fe939025f9fd5b547ef19524df4a910e1c5591727a9907005497cf19bcaa7a0ad64eea2a1e7fe708634a46bab5b35a2ddda6fd2ef8b253a4f20bb239fff1822b1d4c4d7696b4d06ea646691df42d7fea87520b72733164c0325b117ac37495a5abda9d5f0c8f987e951bc598a6985f9a5df1e172e9bdc4011af0fef17d0554c45a2fe976d9678240dc5fcd161ca2b32bb9038d72c2694b227d4494a4d593bee299511b8cd0ee01c0a4e55d6306d1980ce1c727726dd7299bef8081ec945135940392ccc6cd872e1569b0dd05bbc908ab2164c772725299ed19077e8121fa2fc31e7f0861b81c8cd04106d4bd7926bdf3625adf27074da6ef80846cdfe827425cf1b54356553ddc7d80baab50c41e7b1052ee7a0497c3fc9435e744ea544d63837a0d830450ddcdd36f6884eb04389c8e05b41372cd4317405cffd14de8d09fc11c039befa5d19b9dfd5bcee55181dd71ad5b107290358cfd4c761a4a4bdf30c83636f14fd58f440dfc89672895e1199e0c2f344d152fc7e5e6caf01c471fbd164938e2c80f2e98b654f7284ad1b6f7f917267672de94c76b468a6129db2960faf2ebb2d21800d59b2ed896a4425d5e75b4f2367215689aa2e262b250a47219847115908b74b68b992dcd24cbf048d124eba3f9725fce819131e8920dd0f0ed3a434bf6cdd65afd2d6a56e4b5ff45396fe5a22f2b72cd1d32e63f690fec284fc02971876dba972ed47f5c26dfb4c1a030da51751ffd91b043982f539ea40f5ada3581cfafb21fb55676008c7f91abfded855b4814cef47a1226f8f87b08a0171b884e12171f073cbe8d45b599054971bb0d002a7260db73baee59caef74f20f6809cdbe7c26977cc8f518234dbe7d52382831d81daad85a4ed0e19ccdfd253f6fa755dd9d1ad993a41bbf428906a70ab39cb854612b9704f7f01bd37fd5e40cc9b4bc8d8e4128f7560096bb1c8dd9884939a931728ffaa49af7d7162e85526563e0dcf32f253ace95156e4c1f4146c2da0cf2d97273e612db0ecf5a1b64c901a59374cac10c5a0b328580037347d9edb7b870bb25a584c5cd7c4540ee198da8a10025a8a3883390d715300c5c00887a91427ad6725461ddc433efd3fa23dc94fc3baf3a681f4f63d2a84edf994c15eebac86b561112fe9e87c5e048dcaa4cfe4646a918b4a29016660b6d9b8913d64d384a759d47f36da7ef6931b8872605c38b442a209edadcdb9f91972a5b87f5a290e3f30a72d1735d4b5979e183748bb9156feba642faa2f8933352a6894f74a20abff0a372deb44e8ac8f837cf88ec4a96f64071c47c440362d6fa95b3aacdf5fbdc96877210df4840ee1b4219e4a4bcd3d2f03e5c2ab7f037a8feaf5bf1f8a4f459818a7249eac4eaa8ed21155e9d6d076f3d866477e2e626b3bd094708ec20451deb472fef0f2c0b36dbcdd58ae469270a96c18ce5a45b2e686be5ee568fa8d7013734724d9a96759369bf514943e23d2dc57090de6fe234fc12145c4622bf95d6cc1d1307bc117a78428c69975ce294531d893a9c8c841154105c9f7d76a437a14d8d7214a65f12797c467edb20cbe36f09064a2df7ef8cf705ce9a502118dc9bb36225e3d6cb6dd3460a77747198e6c7eaa5b567825210777a0dd4e09d3119df9a027231a507e1632f38a27175f3694fbd1507c12deabbf0ab039f1c8d1e1338bbbe34cc487586f50f14960f30949d0653c75f8f3822376fd7f2473cf9d4bb6b6f9d1f373ced33e00afbcbee5273d41ce5084c2ec571193e5acc0a57a55da563cdf27290aef36de252647b17ebc524fb7c59d28cabf83366fbeaa80eedc19a88ef8c427a01e0d257cbd0b3ec03f9dd607303ea30e94591c4efce734732ac34fb4d1872069958f045580fade534dbfecf124d95678799f4ed16862eca45cb3c38b4c407d12037d63a92bcb6eb28ed518c236429f7ff0e7b1c9a9f06e5b57b177bcf5c0bc4ab2dd23ff1e89f4edd39eed8672e5c442f70379806cfb61ef5c2a69b46144d398dd8367af47101467c09650f397bea57d0d1dac90c1f446a1d646df8bf956fc9c12155682df7dbe9dd7ecb63e1e7f88de2d69a89f54f06b87de8a9a095d572fcd1c2f3dd216cb2b281012d7c0360f88d8aca979813e6d106e1db10149d64720c3bed391c1c118d318907d0554f682fa7661d6804a40e18993f2d4d1eb06c727e51e4f6448230cc9e79446b31679c0d1c7f031cc727b6bfa0e19943b7652c72de614b1f4807918f3527943eb2bda6cb95d62d0e5b4b2b0df5763ba5bbf6b22d2d8e605762a5e40588cb58342dde81b2ebd92c6a51cab00fde414cee2cb8cf271d27bc6a01087aeb621fcda2d4e6068290d9c2d6bb95f533744ad48e2e1823724df9c499239f74301c26d4a950396c1a75f083b629526896f9e8901d89b1b072d3af87ac6453e2d9a168ecf4292fa974714aad8f9aad55ce2fd8cc7a5591fa729cb98758e4e7406db2e3c2abf2bd3350954c1724c1f4fff269a9e037b88f1412ad75389270c98949a66a2469528dfe06f911394ae885ef9bd99291a813ee4472b25bcd4e41e514e4a80e5808f84ddab5fe5c060415ef9783cd33d0a73beb632c4c62c5455d0d11743d71fd904922c59cf47ba3b21ed905b03c0a10da0ed1823795b6cf87de37ae8d5e3997d9074a7f3846f508d8ffe12044d100c33cdb4c9772b1c39b1e4d80d33d71e542e3bc6e20e930c50773d9878a516357e4e67c2fc67248275ff6588f82970e3e043a9e19f71c0256adfc4ae41e7b1d545929994d087261d3624cc335596b4f451e3b1c66b114211ac66d43b138e1fb3ef2c46f95ed723d43d169f5bcca1c3a8848cc26fb26328781fbe803b955b3627cbf0ab4295c32b06768ce683052a03074eda5f75e358e863f3cbb038facb920b99ad2af651c72e6c23da07226adb89b5b5d8d7b1b1c957e3a8ff37e60f56e2670d8f9bf285434e0e8315db6d40e0d1edac789814b005a36bfa566fd6f0bf62466dd229683a9136a8f80eee44a713a9c0e45b5cfcb6966d00071b15298fef1ae82cc930b73972cd30cc61efa80872dcc2b790a5b4db26d70d8a5ab988076c83fcddb553509fa6345520e28f4fde93239decc81a4f9e664c878f99a20f892bf16507b82e46af908b9540f2f17be208c6b582ea006f1b6a6be4755c3a7d03f5895948bb2731dff6cc5028b5825f61e104dc886b0a42e6fd2320c01682a5eb159e697719cb841ef40b420df2448e2f3854e31c3b8beb9e76714a49bf27eec400fce67da3c568d2d724a853f7710a8b017a11c553aba9de69ed7316426a2f52a0e232919b0a4356472cc6d9684e0695831406c9f3220dae01eaac316d808ca623b9130ab975a42856bac3e6b7daa39647adf2f34af4cf0bca725db85273290f8225e94726251e05835c9470cfc09141474b740e97d644b4850b9c020f0fc900fc282ffaf1160f7526499f4fdb26e28e3b3bdacda484307fff5d730d6ba82309d806abeeb49a3d9a07219fa6aabccc1f18bc707baa20a71035ecdbd14c1869abf44fcaf985cf141dc15028dba370ec4b0d63526b4a34fa4d3a6ff9af1cda30d7c4c1f980efb36d4ae16c986b9dcdbd609947d5b6397f350e5b043791507486a09aec99e3749152762724b6c1008595a372f9c4842db0154b85ba15e08a5b2dcdcb78d9c8af3dd73940f60e428d1043e2a1636db18646842e56d43bb6cf0da53cb003c0bcef34067887243e6de00053d8fd4a2d5101b0e49ca4ecf9a982e137a3e3311ed81d80e493672d02524f10e839c54cafb216150c2e694e0c1d9c3ca3c1c28b32afcb0d5b79e2b2b75c260ad91339117d434db89967e0725d338add1e0362e4acc137c50de9f72bf9c63e0847a6ec55e5fd767bf549b4622eb37885b4cb7251e2b03aa1d4da072a96c256f9ec1ee317f18b7bc1e9052acce912a8cd26c863143506023e50b017287021680964ad8718bfca6f79facb1cbf81669903b746fe2bb2bc1fe8d6233722444fbe69692c97c4f16066c611b86fc9cf7d5f5c629c80d8772787952900149ba5eaf04200b027a3a440a82bf95c2ab5f1cee3ea55f047a78f833a721c8e972da366efc6796d001efca2ee7d5a024f0cbf9789fdb91f4bc48259ef45a3d354d857926247fc8ada2560f0f6f44dccaed209c7e5ba53fbbaac6d7021c380efa72850ee7b13a6867e9ccb6b4fc641e23e4eeddfbe9303a797fe91e2f33c34015498b5cc28a463a51866feaab9d34f34d0b5a84918fd2505abec2290a7719e8b87298a73432d16dde9b9a220f9d06a251e4e2b7e86adce4a83d57607867c1167b0f080c47cae9303fb6a354b012a33c96e88b24beeb8030972452d2028335d5c52a6f0a435f0f962293ea2e9aaa42430ce87d99dc402d3f3fb2a81723e1ab9bdf7234efdcfc214ef18538ace9cb85f27587de1f8ae06530ba3d17ce20488b724e59ec9c78aa45b158a23554602820c2758e899523b12f2213964e4caf6eb63c8c151b5a3803c108f3f89a3800eabd2701ebd7e5c4aa5f2ce0cc1a6b725a25ae8d72ac41800054f1e5ed863dbf211e8b506ae9636482fb6ab4a2c2eaafcb3fd160508523251b635956f3c7985b2a51ddb3615fd0a56a91933eb8f1fa23277af610720a1fa4dea6e8a9753dd6297cd840956faaba522c9ecfea5f3868982802dca74c2119fe55f36247962fa5f0738d0fac62a0ab61a1e0a3efcfa039e8911024ec1400766a8234d619aa0852bc0fb057a64c40afa3c3cc472374e862f0cf2336ce72b0678e3e952c9f20e1e697f897413c85301701817e9eedc638bf0d27a5a2706f0c10a1a9d70f4ff687ddaa51bc6a4573af0b319870d1128f1a3906b52e44c972fbb70a49d72490ff3a1ee55983392f167a6b2b291ac6dad78c381ff50882ad7248a1aa8e98b87bc44001ab2565e80dc871b77bdce0c4ddf0033f2c15d727465c5b4eb2a8674c3ab2d167578a6b5b021de71f445f06f4cd62f5e29ee867af2b72a1566e2aff9ccf48f828720983f9cb2e3ff2d15d0153a49102a786c82346c77272e8563c5deb5dd849de7da3ca11ce7540cc0c949b927aee3adffa360c83ce268793db7e1fac3ea25c180b99c2d01d9e31cb740b2f6399695d09e77974a5c655bb651ca1d12bf354f3649481cb6d9dd566d391eef6df1b8b8ba6ae18663d0772440984981f52784258f71593283ac97d7f76340e77d7b170bdf1dfbbdbc24f722bfd7c711ef08cd03cfde5d601aecc9b18b1ed65e9cd96bf19fab825b7a5327275fecc7272d0831b0c6addb3353b70cbd4e1e225ae8ac3f8238becaae643a02f59abefda6e0438e37ac4e4da3140a68527bd33ac37aaf80eb990b80b3303f02c20e4f178128992d6a9af23a8caa361c16497a6d67c48892b171b261ed4dd8972ac3546f9c3df1546e387e2fda2c1c219c36a1b8d4185b98afeca5ed7eb6a1357679fd7e8f75222f180c24a9dd837873d1dcc95f6c48306420f8a18a235e60e72cd4158731a7ec9c2ef70167785a57c18cc428c5596bbb234a81af1cd633ddd0e3872fd1f8e34ae9f13cfb739c0f03d2373505e1007da80762cc81f04a2b571727a746ae2ab20208a476cc88f3c2551621d83246bf39624bcbf2af9da9aafa60bdb18ecc640b2593441bd17e2d71bdb4e74d7e8ff86fd71e974490656650a553a3ae1bee4281a256bb3cb0e45248af6d511facc2802ae0fcff391fe8f0c4d55729640682799473c17b6867b27fa0731119eef5c5240957dde03a2aba1a1e81f1574afcf8314d3e4a365603b9622a4088db1a5a1d5a6cd56369aec67edc56e6837dfc65bcde369542c0c4ccc6abd7788a633644a3ae44756b8a42b04a55e986f722321976337244c990aeb671357f1feda52b3615089c0c450b1d8f64b87b3d8720f07dc69c5acafd252a7a3023cc60ffe014782f731fcbb6926f372ce7172b51a0954c9d95cc2d1b6bbe9464e0bc0849a91b6e3a32f189f16e29e23c2cda12372d9b7d3b8c56cbab9666e754e99ad792e765b930928b28a86750968cc8bf25303508c4046854025fd215bc4b4676b423e50beb2e55f4badbec442eeaa580b9c72e13da6838a3b7798e19b4691637a16d770259781914e0d4db49f1e04301e45564dd5a8515269ae7440680f9801cc03878b591fd6a75027123e5c9c85517d7a0cde54be4de75f0b4f39c922eaa4344b3bb80897f98d3ae39e09bcd160df574f68bc85dfde3dee07541872fa5e22f1b9f1701f31239c8528a2b21d3abe58ba9972a7780df8af737332fd07763b3887d2d38ddf435e93f116938c3984cc7f68aa726dafc5aa4be941d4a27aeda7798197cef2dd5b30ab7e507a9172467ddb9fec1b88a0b0a3c28a28ce798b6358dfd271dc685efde1ff7c394f1e9ff12b29e25d72c17c77fa23e9e93edb61206a67770f4c917625d2860fae0125926ba9bcae1d72241be6a0dc48885a0125e59cf3d9c8981194a8f291fc25c3700627a280261c43ee6d56be19d0e0368a5e35e415e37565e445ea2ffcf30f2854e314fe2aa24d720d4a285f965b5bc99bc73252b84b16627056e462a9c7eb210af5bb81b5fed57276266d1fec33ddb42161847c30593fcf4f9985533ed63214709d7dbe497173728d1775aa9b929e611c7bbb75dcb45d29797feb1e80bcf0d7c29447a9ecf62f724b12aa6bcc03ee5c9eaae17f2d8e8ef0d71b6608743c46a27a2f02f860777529f91d532d4f079830d02a2bf064fc955834ea82df900685ee276b2bec49217f55b1b6f35bd50886eea9b9fda68d897575ae9b773f1895be3efa767bb56093a3369d928bcfea97dde7b5fc94643c00fc4fb7294502e6b0e03a4350b17d2b1a7c7240c7fb39f4f2ed6005f0a0bdf2832a8a7ffa7bf025a642da07a2d078c92fef7211ef324b0cc21888e34cda6c6d1b59b91176cf51b8c2c6821f5932e69678f2389d1585487f48cabc0e0397696e2a939bb3e91e301e854b0f4a854f6220164672ff4b183e8eda3aa9c7f432de2557f3a6a6052431e668969744896d6e313c35565c05d12e99db32a751b749d63275eb420c07e90c59e8146450ef2b207db2bb721085d97cb19864ce5da53c4a1176d569984b841f4b2ccd38e2832d24ca141f6d382b071077da311ea2f71abdbb5646abd62a073a9cfbdbff27e172e430c94b177a96be02a86e995be25112e32af156240a02bd10d22668695a4720a051c9f6315c84ccdbae60ca5b9043e071a7b80b73885b3d9e22ad628e3e6b132180471d5d60e07d607aae6852a45790cfd39a87fee7edba646ebcf7cd987d1959c6c83672dec26bc6332f27c20b3a8daf91c67613eeb9a050b0dfffa9eeb31e3d30e34d7228875b4dc552afb6c4d23617d31569a3ca3a62b286c0d39f2e3797ce0d7172722e8ce155e400ca26a6d6cd868216bdaf7e06d4b1a1869bb139b2019884d59d7239a2db6ae1a6f9a88d80a80a3f3cbf0f87c6bffe1b5c71c2d57024b37129fe3eb942f0f4fed2f1c83f92e2c94670d27df0f02523aa7dbd29465a8aa79e4f8c2d106d3cfaca0abfd83cb4b40b8e9d26ccdde663e54c17cdb76f6a77db00d65434753e0279d5024ceef3e1c7fe1b91a502b143ff2129b335550b977aa567ffe77249c96dc3b5ed9e41297654d24d543fe4c685d3eb2bbe60e6eb6426b09f61da721550e6b0b2e9a9902992101458717d81b8354531a0bd25348fc6201e43a6a329caa3e0e2498226ff97bc29521dd366ad2f8af25bc4da3b7764bb5bab846a6872f905278275b0c99920441b2e890375fa12528c149efd298ce57d1d2b9158457203e40f8e3f0594705579a57e2d0e042f26e01e973dd3f88c744d55e5065a9572c5e0e047cfcfcfd713c0ca88def2b51a416c08413b876e6155521f32c0fd2f723ee8588c04d7803f1c2521411148b7c44ae193195274ffc0b6464d4ac7822272df0f8c050e4a8acfa6c6805de98789d82de7a321e0962f7475b791fa01f9e4729c8d6c4b0c34182e3b2c585dae684421eabf4b04a116b95b6f75d1eba4c43772b367941202e9d5d7e66043bd3324324f5dff221408cb16cbd1ab46566c22bc2d082607b3fd62bdc01498c30a29b6f21b23bfd90dc49a9a92bc175b4824bc7672c723b581997dac34c912075b7d9cf9fbc8c70274bb76d62d1246b4ad6fa74372677dc4ba775ae3a6695748eab790027e3369c17cf0d44de71a565c93821e2c37ab4d4288ab505c633f8e859279ef1450ccb8db0f5a0c40858d5ddc8925867b7225ed569fcfefd96c15deed80b8a8455d7f20c6b98887d1c12cc24e394b7077720ab01967fe2119bd4c81998f43851e3b72fa57825f3b22c3e0792e5e55e1784ffaab0f45f22dbbc23cae68bd5071f533a8cf09bc8d5ed0477ad8ffdd1dc33c3c0d3a2e324c5b16dfdbc2326a3677f01c3590844a2b0f7a7c5f27ff999c27ff72dcbb319d09763611546f337ec2d0065516bd443c10c6e4a3bce93447218e3e30f4e42e7dbb164ef3125a0094e3837c87a9b599372fb9b266d980f60cbee8f572ddd275fb1dd20fb84f233bb6e4e5f50e638a7ae107bb16bac12debb7c31897721c366bf3f0a44fba48c1d2e3838428015d9983bcb34ce7015194d155156a3a72a0020b7d099b784b97e55f507ef1982578304260dfd367f685d8fa3a633592720b7037fa5a97d1388fd9f1e575841f6197fd17e0ad55eea452f0bc9611e53f72d3673ab4c2f8fa52446dce859a9286f6441b6a722f65df1bf463310e54c2a672ecd21e7bcf19ae093b3d5c7321354542f754edfc5b9b3c1aca3f29c0ca195d4fa058816cdd31517110365b825f98041af48427e8c105ed1d457237a2bb01f972529ce09244ac97e03c7ce871d727b724d65ebd96237bcf75fa9797b4aa87666f4a010d9b1d9ea00d7c5b96ef4f8a48f9c5571f0b2be12f22194156e026b2ca375e83ca6f0f7fed9d048446fe55db842ee1ae5112890c8233f3ba7fae9c05dd3315f470f8972509d8a5c52025b9c533c61798ceddd43888230cf73ce22fe458723d24648dbf35ec5182e25b49e3f2986e2f1a6e26bda493675cc6474cd31f2355debd4b1fe0ce7fa95bf2ffc320f1e649496ec4a80a19ed62c35cdda101cffa72fa4126c8c506b7bcaba43c3833d0335305bbab4bcc97013b110b5b0f9afabd40e597963530721e01db31610bee8f2c1dce50bc62c828b56eec1e3ec587e32e66b9505fb49c24f20e6829af50afbab1e09bfa8e63c40eafe2a225ae574d34cf72c5e8e3a71f49c701cad60a1506fff39211bc7a656e987c558c776c0dd69f95472427dd8ab800a1c6d6ff79d97ef7969c61e5eff715dc239e5796b5f300e32c72fba372a8976d592b05bdc9ab77b96f0735001dc7d51f8e7ee6214a4bae04ec7267ae02f9a3c9c46e31ef818a4227a0406975c09dcd58c84665112b3d3425e027eef9ab1c2613ec281d527c88fccf9539344c3fa4c613559cbbde0c4c74ed265e376ee26ffada26c5e09de3e6dd2048688eac421450ef0adcbbc1adbe19a9cc7214bd198f070f0cd045aabed9609e1b74725f476e3cd027bda284a68bb82446723021aa94feeb761c86c9a8371361fb4a1a5e0517ea7adb3ba26cdc2818084007deaa2fe2669ae67d0a90d3bd7ffc3fa8ac20e2daa67e0886649f57adab127c7266f92de189aa77887e8101e8bd87a6c69dd421e2bc0df81344a94b589ca8047263fe692fb904a1e35fea2dabc72fa9c30df2d3263de534c5a95a73f65a33cd720d7d1b1c857bfd2abc0f451d8b81fcb528849c73f5a61ec0b47ae8b8d5961d725fb9464b61b0ddac57d7f98de9f90c57ae85c8d33612034f5e8633b6484aad72bb651c777e2bad4e0ad25e6029aa90a7f74d45e8e0c6af5215ac737655f250671a98e4e4faa0bc02d85f72b4ac7e4737dc3d5c4b5a3b0d0ff95b26b4ac76c6728a99ca5550bf459f9258dae8255a2df301f825972fa5d7a8bb428169ee77d1723d20ea52d08ff15e1d09a2aabe1b46f7878dd7a3d4f25f11b17516c3be1d9872ecd2be25564c8b24f75b652ba20746129d7a8689e9e6a60557375bc31d7304137ba4cc4117373b470557784d8f8120a9733ee27e98051294c878550ff3b052206e3b46a329102fee83cf1829e4a89333ff0587e51f03884f0a2880c8a2b8565011cad4352b3a27cf300cbfa1c408096c7ca56de445d030c1192a35aca85a4e7240a18c442b88b3fa5fe32af6ee6211c17441e25727168252809ae1d7e3104572eea73c39899a3457e6f6fa75c06611a0c328b420028ae2e6f958e2c67e689612deba3ae24d4f18d0b79d575d4965d29b97c38fd504479ed37a0b7c2433a92672ae33982ebabe4ab4cb78e095124c8ed362320e50d8dd9326f9c3440914b30072fea6301a73b0092d58a62ae548ba8f9b962d93473be7be5b64aa16ed9ce66472ef225d437d58508ce40e7ee20c66567a0f8135ab9687f7c666d07d9421feb372ebc27eae54056075a653875249e12dd6757f753b8286a356aece9bb45f94ac38f8c6b1e56551bec1cce9d1feaed8022e41b2de34e02d7966b69c0dedb9d77a7295a0a9abbf67c6d16dfc4dfd3edca31d23544ebc290ed0b050c1a8d68432fc72bffb7c4a46edafe24543fe5aacf166b085842fe9c6dfabb5b82b1d0df4da17723b917e6f71518754b0866b84428a1f0f5e516cb5787b092d40f238615588c06383ced0ab2dfa211f3b0cce1745dbe09aa7c0c17a115e2b2d23aac95974c5e3727983d19c7abbc10cde4a6e8c9f741775674a901b869f6e301446b908fea655594e3770ac5a40648b6805ab63f43810c5599526f276ce03c74ead73dd3e1c7672e3b7c2a400cbf02db88e631615206d36b731cda7655cbd96e2316ffd09386c3722beb0b70e0d8956d3614d87d0590bf1cd5095bc2718a5f27cc7bf837839cf72a4c8866f5611fb53926a50c9ed7befe77e38e4dad5ad7f86ccde4b78ed9e4372b67bd2860754e554266275bb33e48f0b1c6c6a2793682101019bb1b96fcb1f724d2f4629d6e0e4fe829ca5c2c66817b652a4fe35f245cc54c4789fb0cd6f8b72844680e3fd346ff416e65c70ba73e5f18fdc14422db13ad2e001521f3fb7e01ec803a1f1e8f62e81452e42b645f1828807e0963ade4b8389a9a8785c9beaf011e1896d8c45ef7c6f38f8a41184b3ec6eb1778d0c9e871bc33c43837f4eafb872018c443e1de8479cedc34348626ad484b4906a67d6444323d665c2fa9a47cf7268ff4537b9ec30ceb3d10245b78eb8c879f689f18d6f3c166e899b1c2472ec0340c3575d5b150a6ff370b7d8116f64538de9475d82395790be3e0e5a74d09d72e4bd6a092973524d8e0452a825621a8bd512bee31b50737b3a2abd9bb73d33722d348c0155c0030e68d9ee8f5f5159887b7bfe54c83f8e944cc9ef7a42245034ab3b60a8668b3fadf8e556e475d3b2e16686c6e3271d02ea5b2c4e10fc3d6257b9011970195103e21bfb043a7ab4a0878d09117050e49bba79a1adec95f3fe729e820d66d864623f6f115ee6296ebdf2ae58a76f0c7adeb255a659c35a92cc635193552973ddaa45daa498b8c0bdc90139b755b5451db41b3da4c5789f6c2344bf38e9702fc6b0a2dc1ee6b476c905eb5ef15fc005f23d562dfdaf5a2165a2726f253f97313c58cc652d308131af935933343928b920d5d25f5a86a82280f70e51da63a3704dfa6bfe17b5610cf22e8b59f4ef3d5f26d3d206420e38e73da502dc8452281627aa4cb92fc422d3bfa6ee86d1d70bca8105eb58e54e3e46c1e13eaab742f29577e38e7ef802a4f3605fc0bbc374622ace61c12fb4512f9b9ef8462746667a71982d9b15455260557a51488505f30a9a62acedaa29ec2f69d0c045f026ab5da1aa88de9dbb60918790b2fe6c0d60a4af5c9e57314349767b066a677ae87aef8f83c51906e6b292fbd7ba5ee54a551f05c4a0f4ec24586d950472727560ee07734ee7697b77a79db725d7e84a8f10527ec89bcdfb7f62ce785ab0567cfeaaaaef0d4d5da3810a63f386ea04a493cd14c089a6a0801d32c68b737f6f7919e469bb7429e134eb1208c7fcabce5bb83b47524b4590c9aee9d81ff2b572b017dbb7797b118bc8cf10362ce58ae5f29e171a792ae04e60726229d3b6337283d1d6fb8a492a0c1dde55a9e7a24ab6ad8919c98b9f1f1f13d36ada1f43c5129240e71a950917ef91ac4f773cd8efb3c1b4a903b9b0b02fab4ea1a33643bc72096eb0807326aefcd4dce21417b68e5b96b6efd2a57a98dbf80bec65f90c3724245aa3eee60b8bf6c6f87f82c998a5d8da85193850455f7f8bd51580fe680772209b9d8fb1b97b473827eb376f758a50f053bba5704510d61386baaaa008a47225d5997c154d494baf6e84be08822dcd3f06bc8d4bebfef091d49dc8d273e1723470284e340c63bf5082e15c3e9c9e72da3a1d2360458877bb862c2fd85b1672b5863b109aad9641ad613806971238c9ae16ea653232e453e863a0ddf8100f56944e57a136ac4f8d633d77976f5c6e930cbadd198201b77048eef12d2f36fc7228628ae52c3d3fa9bebd9fca3f507679c7b9a6bbfc1d58c94149ff3e98dc95498faf72d111005ceb087f887765e136f2e9242a04ecf4133cf77e3d2796cbee3c480651635be9e1ccb301f5bc2be80d846a4948a53a0ec26c36e6901fcc11da72ad96e06143aad6ab6fec487591b58c62568bd5129c7814c03e075e06d8010821c64a95f39006e6802b59d17f5cc5f52b26566a4abaa31cf08157786a6318b558f6b109e8de69fa89739d4c63f7bf233e64b10d1ec1d7f1baacf01538c9e84972361d1c85a8e71423b0620e5e5fc261e682c4eb3e25b9a857fe99f910d9b2e3721fa862355fb7663eea513f39836baf3c8de5db9490e16d44bcfc9e8ef6175b1152bd891625eacd2609b213bca7b6b4f907e4e22126469a8bc02047ba10c26c72a5239984c4256016cde80fd4006d39414a38913ada23cfbb266950b575ed751700e62005c2a4ea0be3aefd7a0e59ad00ee8d2209f46ab3bb476a684606b02f36c5083c11e9fe15788fc118f148a12ec9066486091394dd628c1bbdbfa0c8c1721d79fdd87967aa2f418efd9152dd04eddf4e2f3474950bd1c99f21188b3a7e467f232e98a08bbcb7b7c610e4c79b8af444e581fb2f4d3665131a0d65698b6f23379c5f3a0d5e1b9fa8816158a9c31b47bd483dde226d13edc62b69f997d0e272ae4c80d5853f2550745657629417d3d00ea78fda01716a10e353fa726dd17e0069b3cc5724cb6e511a6ace3d6a5b6fa191b51cbbe3fea5c00569716e59a4ac7293ad7a051a27d8f4f1d5415f3d83afa83c76cf821932f3b62bd5653bacf3b072031fcc58dd5e3dad9633cba5972383aae3ab0ff30b2d5a5d2006e5f9db3a5c3e006eda0315b12409bd09bc95d3ff2f9e6881d0ae34aa1dce61e973d8bdf0627279ba96eec0859e89a36bc9749e19bccb551c5b9cb56cbf088467ed514cf5207292e8ab64144ffef9bd4cf95d11875e6fa8068f25b0f78d2e9c2f5ecbd914c172fa7822c3bc5f7f6be9c67d40a350ac5be26f774ddf0f8247edb4402783641d725c590738eff2cdec8379a9929bf10c3d52f9fba5d0e4bb0f54148a5110bb1e6e100c0d5fafc40dcc74964c69be72d7a55a52f681d80e9db8a947fb9a60da75296c54bcb5ea107704caab70ec387e8105e78b7985518c3770b1a160e34c2af072bc9dacec16c0cc91cf422311101a8a55ba3561dcfe25b7fd68e63b54744b026e85cae3c3c776b77666e45335d0a93652e19eef8e4c06a9a440f6961865bdc46f76158f88da9fb016749659a363f81728224fc3e8cd82758d921e3dc6c1d5d870ccb935a335063c3e38983107f06d53809575146d51d29dce1b4684964b42505ee55ce5475d42516bcccf2efaa51872e931d9660add3259e841545a0e407d9d726878f172719ba8ba06aef188b741446926a18006ecec6a7689812b08f1b0a50923d63703254f1ad68442c65c7b0c33e654ba16a8626a65756287e983b10eaf2a6d07f0f31567338d10da73834cf3925f3ab1527e9d96c0565a0221e90ebbc0294d4f0dc15783ecbb0bc311a0f39587c1ddf8d521a9f82a81623cd1b814cf8647ad047ea94e3f8cbc2fac6fb901df49dd2ac39645755597c7081340907e18cd06ae47ed1cbde0468c7e577f106c2d919234d83cbc1e2a88a812ddec9014491a347ae710f48d5e69cf5d0ce979c336c3ede9f3223c6becf43d0916be912c9e00662742de38f757c5f597770af26e94058021ad34d3063b91df159b849fba541f00682204e2bafdef56851efa49d8dbd0d3855ea0cd80beaab8b93ede5b7d68b208dfbdce5d9d537dce60968e57dca9c7329fa6c9e8fd29f9ccd03396b1f9d71d01eb41bd5958f41c288a20caa7a975c4c31686f7d17a6f78dacf2b6f7ddea718720a9e45e1123ccb9ba6c9db18fdf7e36663984405091004103411c2c310e1b57259ef19dc786f65b6e1fddbbef196a2759a8b1ad0c6e53cc4c41c1c6cd629d1118e41cb8bd688898260ba9003325a7967b51f32a0523bf4bd7b3847b650dbf77227f6b5d3605bd636dad8be28f307761b538fda9dbdf13125ed0647ae1d62601bbbdbb4a53799a706398f9c72cafdcbad34d55490cdc71f3ec1c3897325a096167a23e50255fa532cfdaeaa0cf6d40e896e7da6d0f17267e84559e7402cd2634122bdf234ff59c6efd04f13cebd2bf985ba108a239ab209db4e1fc667593f877236e23884b196f34c70320eeaaf16c8fd83b0b6294f7830335c781012e9ab94728a2faa19b0cfe30f688ef4d3320e94b060e46617fb45d68f22dc2e2ce006f37284ff53240168be8ce5e1a942adb616baa0a72b8ea1f7d69e74b81ceeb5b98b665cf5e3c81756f357472e68dc4708cad52719cd7b4997d0d3ad396ec7b9f8cd72d84baf48adb75c889e6616c8a619ffdffa8b687a1df3341e04d752543c7c3b24bfc7faa86356338653a45cbab072e8781ccb3b77c99581c9ccca6ffc9ae285729ca1a0704a1f80a7da702ebdaf304cf3d164aa555b65f6294e19c5a9640253684fcdd6e0d511b1ab90e1d8bbbda92c20caef91e54901e4cdcccab97d5cd76c54a7b6efb7b5ef7af12587c60889faeb18a4ce873840a166b09146a0b685c13b72dad58ddc4b58f6faad10d3ede3fb6e88df52f6ef0d5b625550c8243a677e882573c70ae16edc14c0b407c105cf62b6519ecace0623c160d6af45088963f27772010ddbbbff03bf927436f01e2585a43cbf611926fc54f185b61b02822d0668279021758e1a45c87b901981006a031e8269e5666f24e53266ac9e8055c073b3315c9c13c2511c2eb977825f4dca278c758d89b91ddf7a4222248bd513dabe9f2669af5246d9999281502024887baeafae805320b994e36a7e5fa0f7d5608c596dc37e3cd8b41e96438971dd86f42934318ef57ce5297b35aae2dcaa948270d6049f1cf53bc881726d8b9724219b9cc0976d195efdd2f55b4e802746d2f4814a5aae1d164e231fa060906a9cce33e695a7b929504b6d216154b6efc92bce013e57b0cc978eef78a63843c26d54b27fff12926dddf5a38f915e3bc4b5ddbccb587205fbb81f57355be8396584a01b07ad68fde71675566505b9c2cfcfcdc2f0392b34eef2e1cd8517f0d6c5784f28e19f4495b6d6dbcd8a9e2ed5a43ed378149e72bd620a01729b9b616b73a95aa15931cf10f8c61f20839ea5d4067fd71eba322620b1c0676f8a0b7f6abf07bf1023fc7c271f0506913f5dede89a636cf1860a01dbe8ff56e3efc6951afef333203c8b0e51e188002cae18859b5fa87fb9c7b572266f6211dff3023930e5fd77c2a5cb894059626e2af53bf575ccef2fe7f7eb72f143ac50231ed1cba81aefeacbea6ba02a6929e8fc98452f2acc499ef3bf73034c0435586e15d43780fa7579e8274b2e892cc1acd32c941bd24e97228928a872d4cedeba4fb69ab4ec61597c8aa5fb5d712f220ebb334ed4a5b0bf4f829cd572e403784ad70f6911405a79c0f62e80874a2bd4b5b0e8def4988bc90591595b7272f0131a458a3c83effddc40fc67299b8a669ccd2ff4b10cb7ddf6d866fc554cc8a38535628090ffcff05072cad3091304d0672009080a1b0276f83f2f675b724abfc03430a4e44aa2920742ba6b38782764aa87bafe6411192cc9a19d369172493ca901e782646ad53603c491fc12f70175a4b3d9ab0fa5df83223cc685b8678ac8e01289fcdce93b97ec336cd6bbbb23c8e674b74f5feb6812503dc38b3a72f64c82ebd51e715d0a0d6e8894c235e58a8bcc3cbec21bc3c5e4425966926f4fad724f38808235785f62d47e1b7274976a7e5f26a0d16057fed34bb31452f6727a016fd52777b445e5aea71261edaaf081d9655f413266084f20a7c825767c726dd3faa3317f1fb19ea93a26830c4931f5bdeb3aed60573062fb621cd2c63e48b35d0d3f83dd2bd9c96c6d2244f58442f292d5b19ed71d7511098f6ee73fc15a753ac2f6d55778c9d130536279ff6ca968db4f8d25b4056cbf050251a0f0cd523ceb7b74e2ed876eced70887b86120cb1b79ab958ee9c9d2d8cd2d2402266072f030e43899db4cfbe874da2f1c3bd3ec474f6e21a18b978edf1a700fbdd84972368e3bd1d29828ddf6e6aaa5f63b2758384a4ae097c1674f113d85c85fcaf246d2aee4c0b356ecb9d81318a36a136c1c0aba2e4580fb336e01e52ec23602c36076d317be0af939f59795ec6e11d4eed0dc1a58918232b015a089d526067199727ca428376262e2d1c18527a6b1d6eaca2df9c1f9f5977ba91cbf7b14e59258034c94005529e13bcf7eb4f1df9bd383a18423bba005c9d3899ed0081691ba7572d26ee91490fca43a87ac319389dbee8266fad8601aa1c2d7caf456f2c5e83f7262e7252e803548acd9efe20d874709026b97ca5f082b341f76da9d627f27e96d99998e03feaf07d72a234961503e3e41ddd221a92eb03ff4a7f84d29e76a2f3826c3541311e488940abfc6f16ed152adffff4f8243dda6657894df4d5b52cb721931de15f87b899071166278b3591422db0ac361a9b2bd7a0e6d5265318d754df6391999e6aeeeaa371771f17f9ff3f8568e792b4402bf249c02118a7da4831396fe4ddcc99f99b7509e8c617762c39e61b3fbf3af32371f629dd9322e777572d260815655c4cb4253e43c88d4d338b2185ab15612d44623720d9dc0a3bf5849e6af5ea8906fce28661bcf3a9c6f939a055dd02c8ea21888765641e4ef992f72a0d459793ae5a80a574f1c5e432a4ef0244bf537b18eabe43881e4762d88ea72fe931064fb5e0a705b57f047566bdbc403cd97566f3ec98085aeea383bf39f726bfc1d39805e16187dcc899dea2126e79e01abf94613a2445909735ed2f0c7019ad30ed669bf86621a77311cf9ac2451785a4b2080d9e89499cc16d423b3a6632dc592192e38cb2bdbf58d22c657e8c87b27fdb27bf0cb206f7eaf950c2c6672b2c4059cf1c002e38786b8405c6f4d36ac2082f8e13a0b0ed1b25141a95d3c72442ba505c4e09e89fe1a057f041b64eac3c57b3917136aa558c7436cc2da9f3b968e51549dea7b676f155035387379c0b3653e26c91fa71e02bc1948a88f3272a6bfc972bbe9f153b29af633e4b8ca6074fc692e4b91535a510e35471a4faa72a75c7211e516bf6ad2d6fd146a0b0daf41b5c5f1d30be591c1b6ab7414d4c23ccf9865ec366538ec0bc17f01046437c2bd3c57e1d49b9ff4f842aa47cce03c7239a25dac64f86bad49eea2d22d56b3d2bef71e0076c39044e914733739ca207291e76ff3412f8290534072c157230f1e7d34ef4645082b78f7565b1c49defd16a5cd98a79e8cd96f17675796389fbbde3e186148268ceda9f310f5aca704431caa590707c39b79d6cad280e3905349ff6c47119ba53a23303147cacba66bad725711f7d554c73b3e972e26685f2bf512e73f386cb2dbf8a106973a62829fe7679aa598f2c87680367f9a3542e7db70a4dc6789cbac8468bb8b40f47dcc57c94e596e284fbf32f090044ccc4a390238b6666aa20fd71b0bca01a30083e3a0cb72e0a31641f9b5403974ed7b21a7c4b13bd989ca4fec2f66614278cd492d2a884339062ac938676590b7cd8e84456620a10af79e114972a1a5b8087cfe9d7bc07218d2dd8c770de3c85d8c8c1f48391c19b00edd64deeb5c051ab1b4a0d8ed99726b725342f94ef0987b0d4ab919cacadda0d0d0152a04b8d5137f48ce56fa69721a57cae8a55cdcfb736473693359562db446dadd68ccc01e1421b90dee2fbc7269e493ce555ed3e517eac1128030f2ee6ee74f460451cee30814278a9a78f77222007a027b99a0b411a7e05759ee0502aa713e09021450b053a14e1363bfb2715da9fb703f12946437f40b98f3b2c4fc2a921a744f0f300d11245338f2816472d1853c48f7317fb0a597ac3934c6e39936cc1b4f003cc1f4dfa0063545a2a657fae5d3e9d35098bdadda6eb9a3db019f4f5c48f75c1ceac727b8b79472c3d442af09160ec2c567dba2b46d3c447d8090470a4a770bbbd1648519ef36ea593372d70e390bcc6212f265098fba445ef722d56ccb9fd498ec71bc05b35ef56c29723a1cbdd47d76fd79d73a3d6af06714f0659c139887bfb07928835226caa58f694f302b145949b39cb496159214ee5ce1262e4e9c07ccbaa5fc8eba2eb4279f5be655a658135d9699277d461b891d88e2dff66b1e60c9c932f14f0c48b3853572d97eed435815692f2a231f504ebcfa1cb7d9a4a44d09e2025b0d153f64af7e721e6288024ed792eac6cf200d275b3837252686e4fc88ce32c3cd844fa4344f725182dc991094231c6f9e309eba4c7c1e1b895b3f7bc5a77494393c701dcc30725eb3722cd916d56878de99ed950426f1ca974e07a956a85d4c1efe5ceaee2872d1c6426fdfbe191c0f4bb45e7401c61f740de5aebf38e50af6c446ede23562723e9b4e11991c8a3840177b62e72610a66dad0e25ad1a4ce2588f921c7ab4a272bdec5807ef6e4712516c6c556ae70b30e7ecc4ce5152eba5deb931a14e43f072b99c070748e64d3f90bc50e7d5f1ec3df6ff46b83eab8f3a27e10a043a4f5e529a6201c5bdd49201081e409ae0bdf064743c8fa8cb8737293e4a6929f804cc7206174cdb8757cb9d5af18846910a4f2bfd1cdede223fd6eb75b64502d1850f7222cf980d62fd0f4fd6a4f7b97ce256d7b61ecb06ef88a42e3c488cb1a41c16727172935e9f0064654236b890b62ff739319c10cffc391a3ddbde0697bbdc716eb772b0af7c06026cf894c7c8f9707f51ce93f3f8a24645d0127376fca1a57b720d03a147d0475d3939e62aff7db5825ffcac2d399416f82c4b4656b125599b72229a1e0ef40648ee6a93418141733b4f66e896027d428700ae5f70ff6019f3042d1bd71e5f15462428069439df27927a4136082dcf1c63a8d12eaaac26bd3772e808bf416e25048a43846a27ac23eb7b6b4a9354bc99fcba5a4e1e7d2c96d4723ab19e7b3b8cb2db82e57c09a33db0432ec8ac6e7d69d466b45e73d6f0b44072d8fb9d40e06b43b14be07aa108d82310c0c6b98047183fd6c782a1af5738d72871d4127ba15949d7bc605b2bdb3fe3cfd5babd3490b20f43ffff2f043de9c0559508e8116e49ae997b0dad850e17942b93c12ced4426008804f6b9e3027211724cf7911b105701fdc2ec7c260dddb82e4de90813bb63f0c2c718f0913b64a972db6cdda50f29fce2b5b48eaa0ae8e18202c9c65c09572bac1626e4266c8f2619ecf3693098b9bfe0fc5816607354c03a8fc0f72c2715e9c72809492641edbc72ed68962fbbaae31dd55aaec471d3b34037d5fdb058efc0daca42247f3b513f72235270970cf7c2c5d299f5522747be9ef5c1068c8fc49d39182807ff6f877072ceefd9b8e01cc66f5d8cdfcd2c523c1f9edf509541b4e82733175a1f1f54df72f4d84c390dd6518d83cc06de44ff82d0051d60d86dcf7fcf8b95522edaa02172e502a90472174eca5accb1bec3bf88413bcb80a5c04dfb9c86f896ea15a2eb72050a074c1db79a1f3d4d034639c01c8d89dba21ed0a6c987d4d563cc040bba72127670e43496e9aca08c6ef66fe3727ef574c83fff84f54af4b5116d0448a60309ff8c8f4c3db30e70f14fa66b0d9735581fbc3354a707abcaaaeb50771cff72c760c8a1f2f4bfeba04c3937075b1742a6ddcbaa7eb6d3682c8030b32075813e5604ec819bbfd44b4314cd8c0dbdc459daf97854579d9998805734a6c1f8c93335125d79c9cd48a3fb80b192d977d736fc8c2c00c5da7018bb25335c3416ba725a67c7b545461bafbe952e4a5cc4029df895031e136875ce59af4972bd65b325ad34f1b2c95b1425b66585fef26cb9236d92f43a990ce0fab2f1b7212722144106bd15ca201c883190349502a26188d2da66b1671fa7e981b4b6fc0d39a0ef2324e1cbde0055eb6f1cc67a9eeaecbd2a56e1617daab566e2c1b6a81bea25dd6bb7a56b947952c0ccc8407ce9896826f91a3016309654c63bc7c1813f6fe57e4cbc128bcb0a850d5f875fc70b11301a92a9ad4a7f0673dc75319fd0e4cbd558065a829ceb5deb99e9f4ea95ca88c933ec500b5c0d3b792f43195afdd7ecad043ed1cb5999a778e29689ec9eb45992a154a12e538c0283bc8dbeab3b15c215672321072a6d5db2f36c7c288828792437164735056ad7f08962d738c3788bbe2572421cc43e161e5df86ef9f7b1a67fd22f59cf48b370ef6d48adfdeb63a1867472f77527975765e2977f1fddfad412188f21a6e6a626eededb5d1524ca00e16472ad8ecd1c9eae6e16516ce09a613bc19f00988baf87249e7498688d6e1d686272b4640e1d02b3a78310d6b4f95cbd5aed192e931f8da40a04a7cfaf1302288972121c22bd1ae1c44c30a32dc1b0d68f7fa3f7c32f7c157bdfedcd7ab8d0b72e2a72e1a0e61e6d22c177b8bd24390be6a832d4835dcf9816b735f797ecab08674c67f93b97394f29fea34172e813260397a8298125a15b03be90741bd6bb82d372aee5f090d87e873d71c3b446d4547bf3a804a0f4df292dcaae8f617803e408181a452bcadcb1be063f722ae6e7e6cff934d25cb41c5c6321ad9f17ce6262007207d68ceca8346199966dbf4db9bfef1fdc54a9e7a96b222918aa9e5348522a7247c4c366734e9caedf1b3c6cda929f4ea60d0c2201988af28d8cce6f500fe172464eb4eb1449f0e2fc9e76357f874113a936c70f99c65d432915926773e13172c870c4b8994be4e3943bb1aae5fee01e739cced816f6f21cc22ab233d7eb7b22dd4df02fce8326275c29b6dce40cb40b1f735261296542cb676258b982e52d1575789223e3ba7bda47b31d54c2c1a0f3bc256e54efc574d1e92a5c15fc0b1c726491d0bfab61dadd4019aa83fdfa4e77a6112436421f5cebf523bcaff0beb672730d98ec454d1aa872604649af40542019cbe4f6a4d005201a8f2bb5bed32b72f6033ff9876602a05111d86919426580597b9b657e31b5ace62d0178efc95b72cb158b1be6a15a5b0b9c4455c1d2decef5700fdc5e970b30ad73a9de7b0a0c44ab453c2c70199be486f0b17a5edb355ee620d55519b7be9427ae6dc4d86f34726c83367011d93473a22021abcd04da298c86c5b2e9fa51df659d6011530fd32502574a31cd860b0505eba90281089dd87ac8c16330566497f1236581f3de1360cf0fa729e410d7e8e9c1e6ba03bc5362a5b5252dc0efa5b6048f704476f3a0726c7598c960b358f5629364dd159a85699a128ff02b158e440bfce6b2ae344b277e54f330db8f3a2e727691d723a77382934402b8e13b004af68357ab415549725a483dc1a9397048aa1d08bcaebcc2d8edae420d142e8ac047a9826d5a56707221aec432a504fdb98d7bfdf79444ddad19fab212c0d0f522f2710d716ca409727312fb0547f42bff573e3db6ad200fc1539d4675ba44f9ab10776328e3f40959d3c08ef99ba5baaec7f4a963b164bc15a51ea54d7deb7c6490f9fc529f263672056afca66c66c92fe46f56e1dba7186f75d93baf0694c9036b1296567cb194725202474c74238c5e480efe48d271bf930fd3eb36283c050c4a99081faeff794b481221d2c72b858056c1165aab386f86ee30a3ac64b05de2a12911cda72fbd3d1643b007984bf6f0723e94c83ba9c96cbb8aae93ab82135c12577887f3a90a724e2c65480d12d605cbfbe7e289b3cba8b26cc7f55a39b04dba62bfa4df33047200f18a28a00165d3aeb5b5c6c886818d602f8d081c2c5037e1fea6655c37ce722cb81bf1b74e0936c74f73886d33472b51464a309b7d16e26d82e6f18a7a63634f39b27d376f399fa881312a79262e2f8f14c3585ec70a4fdfdaece5c95b9f725a2bf4e3a2a2dd16974e59b03e112ab07a05caef285bf870dffe7c47d87b4172b5f02b6639e776378626eb15488c0ac62c5be22f8d98512b4a1f0cf261e4d272f645a84c30e3564957308cfa7141d99e87bee402bfe4079c3aacaf0a2cf00172c41a436af7ee39cfa845d4d5deb9ae7c749c0fece33e25deaded35702236006f4446454fb838608f50c9dda40738734e8d832cf5d866a80d97ddc60d9dc48f7243667365924907122bd79f798a8e23bc6c12d366151fb4b80f50194511de417255f59defe018e70c3d450f999877bf11f9afd307a1dc15a0ca9be051bf36c772eaa1a04eb572d32db82363d5178b2e124522346311a25a7928bd00295637e87275573a0964dbc0994d83e44d9888d16db491304e5ece3cb22f5cc6a498eb1172a5f3083800def764cddfdb9b391b2be1cae648764848f9192e0b535761020b6160596630f60e11436d37d11defe54d3cfeee15f684251789ce02cc1901e2bc72fb9ab38f48eb6d65569bfadd63fa2be32d63179288df069b08388dc79dda4172c8c83b20296d1a31ee3e418e87b0ed65469a8d2ae54f3fc82911d6e877f1d572c2a2d91f51c409224ae7259c124721089384e9f2c33dede085cac689c4ba454ce3ed7acd5be574dcfbbd5951ed310997f8621b888aee99e1023507c1e7354272a5d0669f7d52f7f3c8b92f074407e10e7faedeb050b752b902c4eafa8e115772dc729efe99af67273c70eb28459387a3ad8214666d0f3512d95bf6dd16261b726e51ee8c743d8eee233c9e771a712ad6169cc59c75b41abe23ad1055d3d524726ac4b3dbec7718dbc44b6bee30bbaf0c2504b262fa47b6ba64ab5a30943c4f72733b877e1b707c6ac691ace6902a1286b6fbbf2ec512e3872b80ba42cf741427c626835d679ff81e37a4063c3af7e64e70bf452f9e2b89cdeb0a0e050516c81795e802e69d23e5af21f28c7baf1d3c92b4f4d91be1b072ee674547086945250d1e6674849371e9296e3f16ecf1ff6edbc0f9ae9c153ef1a40b59d1d79fbc6c724283bbaadfa2a9a49f34aea8bdb33b3cff6ccce6c707fbbb75b455b1888638727f4355fe971a8094445ac5b372a1de99cccadd0fade262ab2fd867ebf467a87237577d24e54fb0b059e3efff383ae2c730cc3f6222f0ed2e12c34624514b572fe7e8a4d8087a43c7de8b78c32f1a9e9d50292c27a7e17451523ade30d4306027605c1470d9aab0be2cbbf16b0d4fb96648e6bfb6b597b51e2c4a30b950f5e0326b7d18fc5b354d25d45bf35a66be8968e97cfaa67735826419ce3b33c8c3a1726f7e879ec87e4ceddfe6f25b9fe9f820fe3baddf2a8916ec52b94a19e43004516c1c1e78e8ba520677a265687b79bb1f564090e57608820edd313b9f23dfe0271b81e956d02e0778eab59dc40b3683d40855ad5e0ccc4f251fecaefade9c4848aeef28bb6dc352aa07a389115555c185b039b0df8ccea03b463c333dbbdd937285b7c2f895a795f97b8a14ca1d0293a323270a8906d39958d93cd0fbbf3f2e00e54649cafb65a430cd05b632f84c319d8a6f662cbff307e0bb93d7e63e2ebd4a72f42bd7e30a9e780502def448b573281cfc270e10b7b480dd7290da4add345d686aa5e1dcd4b346dac87422f830ed13e66225b4b47355d9cc4a5279af0b9e7298617297664b30f1516dbd85f07d97756aa441e288ef4817b9e5054dbd07375a89f8666a1ec9611c2411f8bf1d31754d8a5e68b88abd232bb4c1ef54aa03772edb356ee81cceb3370fcc61e405fc45c0337c0f4df18ed371d6a92eb2f54ea672bc089e394e5e4fb3bfe0f0e4080d40e52f560213dabd58c2114d7d708fbec07246b2ad58bb84aefe6e3571900a9afd7cbc55ad69100a746f37b1159311ef6c720ba9a8b7fa72717e25284442d8b307b063631873eb719e39abb70aa1daf1c02b9444a307587d85b4077049c8522b1d8de2bab459c14d0fdedb82713b01852172cc45493e7b41de322af35faf18123da0ecea3b7cbfcab937b8865f33eb463472b827ee56d85b8e90acf4a213ce3139d0e17ce3ec32586742e04140c022946172be34db3870365098a0b455606a114b4fae7a6867e3a0c356967022ae5b9d760c053cb545134eb585548813eda704e17c5ecacbaf267e4f11d84bf486402b471b6b77cdeddbffea9c92f9bd1a0d6820739c5a82121a708130f7e87a61f802fc7220531ef65bf1e44cf59a4b5de40037e4a5154d2caad092e5d9cbaf0e52a52d7221963834c828a98bdf1c9b417ea48bc822a3d4cd0e93fc06017e53bbf2cdfa27839e2a1eb5bbc37c77ddcda3e31579718115fd604711a05bc68df315d0b95b724207bac918fd3e220b01af569f568ba109071fdeede6af06f0b5f479bc327a7256563725244cbff14715e07ffb178fbd4a4968f67fc060fe7007016b4b3f73724327c3a3c337bdb5b8743d88284872315c4008c71fb87f23fed43869d9041672f6318e8ae5823efeb71d3c88ba52c6cfea81a09c526944265d4db6064d8712729071065d36e46cf958ad9aa8e0376d6e6ae4e9e119ede786db967a64d282073fec69a39d86db17e6b2399f16641f809bb2fb51bbd5d4af9fd654e35360f21a724963c1f736529526c467d063c6ad256d96696c526576525039f149b571663e72c33dadba8f0f4a98551bbb96922316c58bde816036ae8cfeebf8b5f5e3f2c6594de2e3cff0c9031c40d7832e369267c5f56191307c39aa008794d818e9e82372bd6929b1e885ec0f269d428530a111fe96c94bdc97813724be03ee11a1db8a72890b8484cce52da6e6fe60b2c56877c1fe2b0bf3b199c2cf7b1708c8b5adee72659b2238f2028331ee07221dc653b12b05296b4599534d4ebb2d936220f67e727e2f8be9cbcb313d42078bed2b508da069d8cab68af09f7cf106596d17d2f65654b78472001877e3c691ed1faf54d92128aee051e0b29c764beb321b45908c72398337daba4ff563a75c750734e6146d940f56dc74894e53f490562232332572ae9c36aaa950eb7460c394235b6fc8dd31fe5965e2ea7499458eb6350fbec7721ad2ab3d3eafde571aaf3a7b3c017ea7dcf953f8161dd906087a0c0a0a54137288deb88e29a1bad29f440f2f46bad59b2600875d0a1daf434dc70ae99b6e7472381de62b3db7f52530076fa7491ef70a0f5ee2340e46b12118303fd2ec526a720b98e92f29c3b954c5b8e5931740976c3cf2a544a6dfc973e949319c1a94317263dcdc27d9ff39b4a3fc57479eb243475a52dba4ae2950d11209070ab698b872fd118c879fe13bd6dce45840756873075c9bb53d0e00e874c52dad74854bd30679033688c6fb8622d1731be97f0cb6714197a47d7f6e4fd307214fb4e6b01372c36871a929deb05781b3874916674e7627d69c99735ec40de976b82593a3626d265408c2f7eed3ec54321ce17e901d07cb66ccb23c86e37c1743c31ad8f74272091245b2bb367f7e32480782919ffd778d50141e3145954b08233525dd6da872b23b701f1e8459912ded470ee9986d019f3e6e93fde9812362246415b276d2085258252da90ff41f96463591b054b81d84e05d2b1990b4d7d7cb4b20c6d9ed64ca622dde6477c3c813e3e68f096296993b9127dc77368c018fbc64047bc4435525434a929d5c67673fda71bdaed90ce858a489b09315bea76ed7609645983a725bb010df4238b5c0ef4ed007b45958c483d442608e97d8c7a174cb50c2bc4e726577c41dab8d2406c8d398d200d41177c09a052660777d87cbe65ae24d52c34d9d81202ab562b4aebe096b3fd78f13313124a93b0b7699cac69d19958cdc42727ba316fc35136fda96a08d9a58fc4bb3efc2914233b39a3ef43091a7b389154b0e6030679e564c34cf9de966115ade6f7cd40d6639e21b2686582978f5475a7280e2efe61390deb431177fa2805238ea93c03b6167b6e84c9cfba112ac9aa60ed109cad772da7a2c2dda643eacdac4fd44c87782fb5691ccc907ee4b48f11049fe521eda208fc6efa9547c93135dd5ee26cb51baf5cbc33c3592826a6ba796728a09e5405e0153ce8c777affd6b259c536079551ddfdac9d7c387ff84d0a684cec53a49585b5ad2395c80336e21e28e6f20f8570e8337bc8cc104a3fc3401672849cee4d6eda6ba5c1dba2c5c2c30f09e374ad714c55ad606b7d01b42ffc773e4dbbf906a1ea1d716bb582c2a25cb1b37c5b952822e607cef8183f22baaca77231ed1fcc429b23e7ef4c50c7fabcca529f584ebc6dd834783dd06615b03ba6721d2a0e788b57b19ef2ffd81e19c8f8508a27723ec5868b6e72b91a8ef722085eefd85799beead4de3a67cd9480cb5741ca3998cec44532681ed00c1b2b97da3d2d045658f02e43a54ad916eaacad850db3673cd3ec18b94f6a493c88fd89617255fb26dc5f35876e1d6ffe9ac6569cbe138f0be587da2d9fa01d11d8b38a4e722ed7e10c9616bca7362b4fbc80dfaf9c039a6118c7ffeb4bb808951c8e86187263faecfdafb0d825a25475fc0ce21da163f3c7c83cf75a092188994b9d387d044f6084a0cd96edbfb4c120b3ee56e65de18ef7db2ed0923170bbf1c1d0d40b722d957d1dddf19175ee70203160f1676b849f10d664ec234ebd39909282637c725ad2c1028e31f0542b88749a462ed7ecafd6e3de4e81cde45e4ef8659da788363bc8fb69444ccd9923fed61ac6772f5f54a75a84978996629bff5bb09d47ce1758daea7eb22f3f19ace3c870aa9ab7c9de1e7b6641c3adcc60017a3094f4756ccbd948c37f80ac9cb2256a7e38270e3dc59abd373bfd937cbaed8c81e2de47729b4a7a39c98449e1a47ea5f8f2cebd6a62c06755e1feeda9c4ad3e5b88df1d72a9f8a66d71a0318031ef1d797cc48c32d6f3adc9ad86439ccbb5fd1ac7671772322e2b538f49444259c40308379108eea939ffa39717b180bc264d0cdce6cc65064eefdfa0fa86a335f2d7d5fa97b79b9dce10b6cbe1e7fb7f273bfff4d7be6ab1843a04dbcca932f6ab1b8ec9b73958f5167a504f516c7a755cbbc7abba2f72abfc081e53685d13c7f342b4825ceb36224320558601a1060183f9ce857f4e0b3731eab01cb6649333f5b34242ad1ac30e320cc7ba5ea3337d2f9539b9473a7209137f22c745264c2daf1200b28c8fdaf0ad291f9052f8f6b29fe5f552830c490c42bdd7c07c40c7f43803f094745c163eecef78a9f44f81675c1f5755d90e7241c356cb28c29866a5df2b848ff938f94eaa7328b8adf1be9522e8177c2ff615bfb193aa2c52829be4ce59112183a900174aa3baf359242a9493d788c1d3a97211495619a7940e9007ba72e24deb2405aca98ee617de24f264f0cb85b3ead36a443cc1d1eb9899b22a4bc1724d1123dc7a48620574a00ce0c61c791397765e727e59281d5c96f3f946d324cee1bdcf817be26dea1a125a6d9d914841c6a265723c92fba1ddedb9d274f0a736c8ade5e5af69f4f31329c40b53f078e540c84343ef60f44c76368ff6e0e2386978c28929b2a159428f4a7312d711984d14e2fa480c76b178afd90e53453c4db76f0c7ef8d932f0250931cbe39e92e65b2051c47238a47c3b666d561ec190e0b31191961d2de85e4fd77d0459f4f09a6df8b18a72daf0cda8fffc82dcd55c2c11735957c9707636d0c65c1667f4503adb453fc7331eeac901efde49fa49a26760ef710a63e28fb46e4a18e8c3297d4b3332a2f37219598f85c569a1bd512d70b741e232213a3b563805a059ada75bd9b08cfdf372d636a40687e87e9285babf359b52e7b398b32a2a7c1ab6b6b377478c66685d101a3474a0c3f82cc4df4f610834862c2173e509e511d19202b08e393af1f982727fbd08d05286d1c3acd15bc36f322cb55afd6160133fd73e75fc99c18092947230960ce95659f48aa513246729fc1e082f1702501b882492c57587446bc85d0e4612deb9695c766ffc28027439759be9fd8f48a180d4dc32305b42d2de7fd2727869b93159a7e211609123fd46973948745ec039275d4bb7cd2b30c63298453f74fd6cbede09f9a1d18c79ddb55caead5939c67ab334960ecf17a19835c73572f73606caa1d00ed3ddb0e396e57f4ff4f2384391b2de04764419991e37fa593bed10aa7fc12b9f490cdf772aab2849b7fa9e1809303c9e8d1c46821eb5a93c72b0facc78d62e3639c539f79cfe26e9fee94186e602180e2632324d22a2da982804a523d05ad7cc8922474938131f32b86b90eeab071b4fc2d25136d8c0855472ea3250968af4fdfa19ae6e11818781e8797532ecab5c405b84dc793ed86aff72f918901f65e92a04b95782829f93a3ceaa733f2da45248e4d460c8f9d2ce6a0412f29c4a0cf7047c6627f020f62118857b6adf891f68fe16db4d14e362a2652118ecf235015ccd1aa48053a14dcce7b9065ba12062330fe06b8c83f03134bb0ee0e86f780e6ce0990eb0935d23f181a94693e8c4e30691439a72201795172643d1a400f3ac2cd06178e0442597284bf06ad09fb0944eb37181b3bb5dc18a2372a29c0adeeba9474b69955d40aac9cdb348079fd22cf962c79dae664f3cfac06c4109bc4ab3346c92c21ce789feb3d8aae794eb8fa81b6465d2c72ae60b50c524bae13c182637f1f51fdab811b893e953cfc970e63596b40cda3f7bfa812e9935008fdc83469a0ad327a757077c92865258904df8b8701bc943748b1b0f770e05aeae9affab9d44b91dddf92e3a10230c4d71b0a40f0509327c87a9d33966e938671c08f15d25c72248895addacb91892a769e5553cf9d2a371c4eae825488772d8e7d0761f0788fe2741c3e8b6c2ccb4b48e4cc7b2bc0e2689ea09fa9297c14a8919fe782bcc4de139430f75db13601e214db44ea91c1abf48d7bd762f6b3872c79963f39245bf5523e01b26002184c70a1c1628bd587fdecb007de2906d21526a165f88f75fcd65b289b0340dcfc147473d09695164e61b45677a7f6091eb33c8e42293aa2fa7adb9b0a5196e734b52ff5c28fa5472b56aeaf85172b5b82272d9fdebb8ee894db51773de8ff2eb4e0963917a4c98810632a08e16868b9aae1deecf46ccf44c9b2e9f8165dd67733668167030baf46da6cf30f77c5b665db45bccc0e3dd09130799ef151958c4d577293c6294d65106aa3f650e00d3d24b61644fb68d9e002dbdca8b2aee50402ed025fcd4978e94d00b74e6bb648ba98af27250bc7f09e28d4ec4b35d9230eb50bf80c3dc58a2582057f114c69c3f67e1e87224e7a0eeedd80df7c319e49a64916a598c0af27c4d95d9fa284e987d2fb44655e1e02f8ced34f1e52850b0fb65e7a266a488e1448307b4e30b8f60c9791f7b72243ff9721a0cad53851c2ab97c34e4fd59df030e7037d14d9ed245cb34f584724c5b7edfb44380f53d9442c66d136b3a69b9e1c7a053581bc4ba197a721e7a2989e2b00d8866eb9f0011d8b2d13e51b6fb7b8574988ea6758a9c0ddfa980ab27edd51f70b6d82dc4a0e8bdd10e0bed7cb9f7c6f77b94dfe3e32d58fc99ea687291e5a9128479ec37b295e0220389aaa85ac0f2e5699c2e16dae403c2ef413566d6279ff53ca880bfbae6e41c03317b29d5fccea54ebc89fe97d74286df59dd4386836fe34c1dbb7ad5dcb8a250820a0252156250a90ae577eb30b679b482d77204b9c49b6a0aad415ad20670e4d33738f6d32417f6270dc59f708f730b2c0f7279a7f84e1f1c34f9b352dc7f3aed46c9574b2175987a6b143eb2a655a2c387180fb073f069cbf537bf1a646915a934a2155cd913a96ec50809704fb84659d7726b2fe63eb045a2c5ba097aa6b3eb83c23ba49086f9db200c47d097c07b234d5f9715d0c28bd142231ab3297bb4a64a91de37dd7c1fdea8278bb4e5bdb9e388096ed6dc71e9df050892fd974092e942b883476da9ad36c31f9eb184cfff18d072b07a234dbc00219f2c23c50605fc6a7ab7d489c175e41913756ee67cd49ee3726aa0e838bb6da2350a6009d9c19b3b9135b9d711c4c38c9d769954a2afbff0723067c7fb5b11d8f157f981107979b5e18011c6be6ae319630c2cf86921c9bb7275175cdc5c3558786247222182057f288792825317c61d73202e999e5b63fd531e21c3133d29f0143d8c83aa87d8aea3a43a573b7444c4219e348dc0893e9a729e97666b0b5cee764db08fa2c5706f6a6690e0d94c61ba29fb6019ed47f86622a392cd3c37826eb93c1469be33fb8a79244149094160174732355bb0f21b197263145a662112e5c5af7f9b9f2a32641544c03d4e1f7c072f5096ed242b84ca69dd4a06123a690dd143099720070d2a62fc8d815e11289935239ed16ca6b9603e716e9cb264d647ede827b837c50e82aec2616a6ab98031e95889ce1f9dea8c7271ffe0e8cd2c6f679eca27d44614f66e3e7918c8765a9568b502f26f8ef40072a3dd4aebd9b79bda8fa9da2706a9ec3d2b242a989eb82b42f118842bf7b40472b4c9e28564d5827cd39baccaaf0bebc08f7bf838889f9bdd62776f487c37fa6158863cccf100df8a5031e68af4620e0e9f7e23103c4793c671c4778ca07f915ecf8ca03a70ae50b9201c51dee3d1ce3e75a878597f68cdf6231db41878668272d462f181343f2053eeb1537bcd710aacb46ec9c4e4b778b0ad5f4c40fe218246e0a193f4c3093ce5855938565920ac0c12890ca7aea002ef25f8f23996bbb672ca9ce30ab92821f55dccfcf1194e4fcf4d4915656f9fff7d262afb3c6e504939444ea5b4671ba843098639b4edf9eb789dc7f9abb65c33e1028481886a34202234c849af131b9caa798c7667242c62864a65c9fbfc5bbb0381f1fbeb83e9a972469144f995e93a8f723a24506f965a25aea15af89a3d37f1532de280b239a47290ec244bed249ce263033335a28762b27e3f58032845977230310b2a7bb38d72261e44e22e269926c7c1849d3216de69294a24fc78b0251a08f52d8e9ff87445d394add25dbd979ddacf5739fef057471b133083499cf18b881da6b976547b72163651c0de24ee5a7b25dd1f57f2a8e5d1e1c96122f6a5590be0b7aa88512e72be15e942e18994699c913a1c7c8a216cd7a6c40af0f255a322bf0b13c9ebd572c1d94eeb117cfe5a946af1c5cecffa1da6105172af6c9bfee05c122606491272afc9547a7f0ab1d7a5cd8853fb7b5f4bc4cc165fe94c1b82fc7f7b29699b207221509323ee15ac51668684b9e26da578b6089348a4249c60059eb9339468037299acdfea6ce733e07441435d504f5a273a2cf3c341fde8e06bb32514d61f7b722c8cad6853dfcc141f2e05bc620d5fd640c3e00e757ab08febf537d1806e6164b53c1a2ba1151aa05671d8ecd09afd3b8b9dd357c492d5d6c5b4a559006a6a00eeae50e202ca3021f4d751fc8381cddfb6e959eb8019180430af98408309ac72b1bd00c46636c5718a4ce953b4b94e330676d0698ae86159e386e64432ddd972641c69d921100a64fefcf7dcb160c1070960895365dffb15eb2aa608334f7c250a98ecbb6aebd6525aa6ec8b4d25c5d71a1bad1bbfa639c20dac9c16b9e888086abc9b5a75ab174894a64c64fe35b7896ce8dc1c219e5eff707a649355fd6c72b52cde16c359cdea0bf00309eaf4c12616ede430aa1e7b86d046c7e64e0c077282dd34b5387cbbd3d1f462a3de5d8ee57b931586b76c0f15a5be7c1995aecb72fc387fa2651ede9c3df581a907e5fa9cfe2a314a95dcb3198de570efe24ffe5343410ff79f4b97e29a4d9a7c1445f9f59f8e0cb2671540fe9ed460f171f8ec723b9519ef38496b3e05626cab483e993b6b4634044d767b86a5f994ad23b2037211655e42a540023b8f99384353a4b36dd77d47a2ad087dbfb6b5393bbd28d672e5040727b92eee49a161611a9034258c4bdd8baa5baea74d96bc5275c2590072d8296aff7e827e161c4690588611bd08322f22b41b640bff15365a085eb710726610d9732588bf5c18ed778ae7a7bc3a71c1b8c49207e9deba8c043cb27ec465c7aa8d57fccee0c589551d68a517057918aff38b389281defd20fbff9b0bac7261a2223f61e2ef0321720802c54177ff34ec660fa4a3e5842bb74ba7c21654723c14005e2d9c1616f29b0651992ea16ce3160fea5094a06e7b9c03b1e820e472315c7e2f83d837648080f90b51ac8c1dd43ecbcfd2265649bc41aec6179bb3722b5db87b4be233af76dd9b6bfeb34b5371a30bc2f32a2ed53615ca3b0a06a972b4cbea55dfa098b18422b8c6daa381f765e851d7a777fd7d0ee17df3b49e0572cf65d37c0f53591ccb63ca93d6c66b002e42ab8035b447a41604b90de3fe2e720f6be8513eaf6ff8cf462c9c9ed5b86d6f0494241782c725573f819af1f3b472317f8947af97923c86835b6b4301c14f5a9b8e79531dedcf148feb3f9ea11e6b1883fb61a11beee6d31f77f47b6e53f9e87ed91fb49a20d4a8dac433b071bc72279e3ac1fda853d23392205b04156cd9e0fa8a130e991fc1670f5efd3bcefd72218ed7a69bcff12440a1714a867458eb6c081e8a1dcbdafe0f0678aa850c0744fc9eb6c241a6682c1e571a4f4edc2a4dd6834616acfa95f043a1f07905e1ad72570ae8dd339cd118f48fd0a1c494fce050dfd051c51d811b817e0c4496c0f64270619fb3494b4a5c6d947bf8b19ba3a31981c498fc3ae4f0ff4981c1f007e672f5a6487d46d07be708b56eb791db937076cb6a63a8381626add98d821ef99d62ce0ef570ec9b91dd69a9ab3cf58c7df4d3134a7130154ce2f3375e6844d7a00102cdddb4dc94041c0fbffa05961a00e712842447b83c8092a3e4a07e6d9460468d0fdee3fe46282c82826bc1310e4076cbd5f181357758f3488def70cdf95372a091cc3d8591e8df0d81722e6be10cb589e20ab9754e1dc60eacebf63fad154f365d60b0d5ee333dcbccdeb07a11063e6d3d7b4ed8cd28956d2833acdb5de2724bb4fa203997a36e9f326c7c2087942cf8c07792dd13590b6276f5eb1a65b2725e1241c0a4011e4f26ff88413730dd1256183358dcf219691e93f51a0cfedd4e333578101c8410ff1e5f581533ea3e0210c6260a07dc37273685a1960f72c57264f487483a01c3d98feb2dcff0d57010f5629c0c9b0c80de96d60ff5118b2272d04f64158dd432f08e6e87d11e8e784b2eca27c2b0254fbd21e30f03269dab72d49873884765a07e36cdecebeab87fd04a6348e853819ce1b9d3ce95bbea6b0a11cd234e3044ba5d6f2ce9fc7c52554bce300d25d240cacf24e1db8ca60aee584e1d2a0791959500a6fd98ddf8d2fd53a388c2e42b0f3605b06383e03ba3127267149f2b2b5fdf8f5f60b05feae1748e71568cdb3dde8fd00ab12cfabb6b2c72258f347cdc8b2cff1fd5d991e234aa4662872cd1923701303c8c5e6309b33e0338fe06dbf8aae079080ecb1fb58cdec693689680e8e9c24db1c2c6a1dba550729cd7e65ffb6ba31552560e05581f7cc739c70a93273be218807a5a286eef7e29919d9c340cb3fc170682001ab7245e4223488c741b56746144ed64d8ee0f6b722372225f2c308f8603cf7c0bd9f7563e55fb6e44feabe1ea5e4b67ba7f66b17251154adeaebfea22b855e5aae25de31ccfbf05025ce29f82a7964f325217bb3fadb526df773dd9c6c03faa98d59bbbc193ca5f26f6bf7e278ec58f4bc1b5a8277605ba3c25981ca0671aa1476f9419c8c24216381290bad5dabae6e615e60f72c5dada2962f4f5c1ad0f3e469dd51ba8a8cbfaf0ed39c6a981abc5692070242135e2e267222ba7efc06f92ab23a56b4d7a2e26d81fb197be2b2c25c9ae08de723e7922533a15dd31cfa5c244304941b0406a6f41d7fd4ae343a4d29832521b72b0c45ad3314fcbed7dba41ab763c16ff2fe35900002e27be3e3297a002e84c59ef1f77e3b17d972b46db7af71a711e3feb0fb649ff60a1a8feaf55994d1da67298b0673e02bcc799ff7ed4c9eb10875100122ade5a026d4434df4df76f024831332c9037ad77fc974bb583ca12dcc6ebe90c4ac423c8fd727bbda606e23b9772c0a58294cf31e69f439d63ec0405498b81af47ab739ab5142d6dfeb07454075f356c6ec5f4df9ec88610077dbc3c284f45438161e5d09e68e99e78f1a45900721a66106f0225099fa62ee2221295ee3e43e2f51683608c8a3d3a9dcf7dbc0b6290a706f6cc9b2e22c1c153f20fd7c9499ffaaa3f80d20cdd57f7cc1a40a6c825baa6c7969602e54f1baae69fdea9370571f534509cc81cdfd2f8afafde152772ade0f9a10306d1a96ac93323052f41071091348497dc4722a1650f47a9f029722573b5832d1a949c0800898c22ceadf4c763cbde645a213de2eb56a28f603f1f5c2aec2cc1bb1247e2d8a61cfc9b75edb438e3ce8a7ae58fa87615757153f637f832855f5ef3b24b1a91ed3cdd6110ec75ab9013d6f64261fb35258d0a7578172fe9f412ed91f8a6734b5fe67e6a465586512ae9b04898baaf13425476671e725985216772aa4a622d104115fa8b7a4e88d5e09afab715da77c9f38599e1217249a86d4bf68563fef97b6cb825b62dd55bcd06ba73ef0c6a6d93fbc92e76a47224a37e633eaf791ba20b7110959e6ebe55880a34e397eb54927056b7d4a18308d77c7aa7880a6f9a95a496b87ef1edec3f48667b6c34b03e760273a8166c1b726ea095044369acd5008c49786579409897c44f53910c9248fb6e3455436a133565a1f76ae8c613ad1b4a13db6723f6bb7824f0575b3434f8fc559831abfa5b725ec2786fcb623f27987f7f9b88d79189765dd57e29968a2e76a21bde77091472b3d3a9ba6272fe7f70b40051893da8346f8c7f68dd7cd2efdfee4e0011fe9667151994597807b3558318dc90aa01c87ed7b6dad66ea2dd197319fc148845c57251700437231fbca90afb4102c67f1f6748ef23d1841cab46dafd5072db4702724698fe6f5e7f151a4a24ac9d8d19fb0d8e305de977bfe4f75519866474ca0272ce5ecab35d73e2cb3e1471eebaf7b79b7a8d4a3b843ea6b47e8d4b42e10ad0229a7b4329b84eda16c9c73a9aeea5c72d916c2c5cdee6e758b3b97a08f7525772a78b311e21ed63570d95799aef4c422ee5a878d84fd925f00802afc1650172721457787df65727b19e1150f326d5b278f9defdb047721919ae68df56a572ff19b313c72b0a5bd2cffac64dc13370a0eafeeb84304ebc09030d4cbaca892b36727e525707c5c6b500208bf7258f3d122f5237e4dfd7ce9ed859956af170bc4e72daa3e12486e1964b3759accaedb15d49d434f0ce6d55843828a9c9e474cf6372876ecf2946384b6e378e5bbaea77410ae251e021db5a1a60078c122876a582162ed3f0406df90a233576462d301a6ff6c7f1244b1a10de6acb6ac1ff57e72e721b62e82bd87078d179fca15a34c6745b067723c633defc6ac1e9c588883053243e2e2f72195d952b95c6de0909b0e06702358d50b6218516ab2f36c82477c72a6396b5d867c0a65a634895ea4d1cd053c4aaa40e7d753b14cd34e04dfa588672865fd36cb94b29a4be971001e613748a7ffe58d5eb81c3068869871f8eddb17290fb0b15e3141d82a41e5724bb14711ebea80d24da8376dccdac6b71b3e0fd0220b782f82c37bcc888b83ec77f267c166332cd4813081a81da9cea1bdf8c5572b84908a70a29e9f27db96739d7be3e3897c71cde9633c28701f6b675eb657d724e6ca063bbd0dd15916918485403e6c0ef80a366d49a0cfbd8fedfa15931c772a8302023f828e9bb0d6093528715b71662f8b71354d4d4db0dac1b2bf7c9497279cc6bc05ee357e8a8ed9e78d8d8da5519e14883df50bb8b4c65d0de58830a0a5cdc9e6191131326d1ecf578c0c8d56bdb4e8c075a69e124412a471cd9c2e703c6f0d68fb3280dbc8ff8a7c52912a507aca3bddeeb37b8553ae1bc049efe950bccb69bec6b708b267d1b2698a4ddfb416108252ce16301f6b43b7d419e0efa726b5d8295f82d7270d907cab384f54a0450a32df2982f36da02ee13f1274a644df10c028617eaf7a7754dad6344bd4f503248ea1bb027acebdc3aa19d032e96720c791e02a8714e753edb8391c9d7bc16de33bf93018e9914a59f0409319a220abb5943d2c64a149cbd9d1f3f00a11e171f241aae63129644bd94b01bdb111372f38ae4f7cbef6c9f739d910d4d09f5084f5d5586c9a0d3a652ab045f89895f0106a58b01d63598db2f4f4e0a786dcf91621f8dc7dc55170a8c00937669ecd1726e1476bb0f3b2df90742730c0678fd16a3073f0424cbd2795990f6e62507b5722613ec559932a195f7a2cb9fd0a095e25d13a1974be3fcc7685b0043857ae73436bd207af6ee88a9969d495b114da92d03b760c9c6f50b167b9759375a0c1328f21b0b05b599c3f0e214264291c196346d1a4882bed5bf06f762d5d01fa9dc7210db9a96eb331aa41c18579a7ab4424cd15707f8e64f757251ee151e03fc9d721073e6966e0d1367620a9dab32c4596a29fcfc8a49c0ba93e579d3e789391c72fbdc19a0901b2b2600772dcc6cfde37b02deca7f06e6af2f981546417c7cf66f86117538749a40e731003d9c6fdb4c2ee3fd0fedcecbee1f9be899361b1f8e72167c6806e586d8c58f9a559055c3a52b563cf4de4f5d7a2108378144cf2e6c725204e87129a8f36b1783e6c51265c484ada8a74832142a497c3f95261c6f11729e52f1416b7076c1dff6f10c1319a16323a27832c11619d08fa961780511eb413320cb3a45e990d41a0dad8bc2eff8b2986fce125a3d1b35cc2fd9bce1594572c7474551c076245dfc66d803b7642ab07cdd8473e04c7cda48199393f45ada72b292135664f7446f87da86193a3c7f7fbb140771dff5a5a64eb04841b4015072234a3754dea2ababa092cefa806a453530f6960fc7135b40de4614bf46551e6123eadffa4f9047d466116fe6a285f2e87ef9cbb7c3e56e2e2426321eb246c672b4702537c4d893f0b90cc911a4655e67dc24aef1f2dd0aef72cc68b77dcf3b72f68d539454e11a3126a5a8bedaa423ae7b34afc7e0343ab6dfca068fa2978d293f99e3e0e07c84552ba4267199f549c173ebd7898f90fd0fee733482d6ee4e72d0cfeb5492dace0ed3ea6ec702e60323c13f617cc5377e64b00c58feb0811c720fc77276a581258374d3ccde77133ab6ba38fe35dc1f87bd21b17a098de965729818ef892f58dc17913db3779cc1004dae08db366e0a9cec7915252c139da37205cbcb314ab2193f723cd6398305cc206055682a627b21ed45e34074a1913e33e450e3c36cac7fecaab313e3dc31e4de0ba82523a4d34f27c6c48f43b8388272941c0b98058c2bd9a35b2351eeca17eae57d797040d1b26ef564b95695937972781b80b6a737adc0a423e18a330c76b43e0628fceb839d03be3441d65eada7722a74171f2291021566efb3e3c6666f3d11d98ff4ee3a1c32b4dbdd2f7b7f817243c5f626d10c657dc723f7aa1dd4c6c0aadf5681833ef1021b9014cc68f3f671ea48dadc3c9861443cbe8352ccc9225fa4c1981b3fdb2ca65eb9dc4c54228e72483bc31b2498cd136370db0723ef0732ba253ae0e29349ca6dc3587e16aa5d721a3dcd634d3e3fad7bc2f99dd521ec45d581f33efa4e30f923a2f370823aad34387f5aa0529813ccc61ea47e4b8686293503b3a164241aa5802c3ccab3297b724934733ce134a05c36f6e3bd97cf6e4d8afba76ccaa5ab6aeb7cf6c77eb1066bfe2e4c44c585e3929759f9db2ec0feea428cb0405fde6eff3e930d89bb0566728575da918df89e65bc5fcf35857479381c8989d07b248534fb52d0a2f25d9472f7583268a7e72752adb620034558720d7bcafca66391653c5eb61ec05c5a6d728b9f316fa6d9a99f80b01ffbce810f3f0beb482528560412090e21f301713e72adaf3d54d1d1197c83d2c0d90d979cca572ce9717ddd6d58c645fbf699b64672bf82ae16a4a1652473b36b2af3016bef03a503d586846aabbda2a8fe057e4872b04510e5e8d514fc2d1180c8a91ffb436ec5418edabaa2dc9778f2dcfa4df57254701a48b2e82c0a1fa33484e2322d868103f5baaf93e8133e64ad07c8182d727763abeea38a82cb26e5d85a5d1222d2832085c14797933e4dd2c77eb1a9bc72bde5156bfa93c3d231ffbc77f1736d733012fca54f25c849be99d4dbdbd1e472255eed1f0045af1d3cc71ee4727d8912fb544e51c22fd2dfcca483fdab751c4a74ba95f565e669e9d1e9b8d0a93422df82f11f6f535d71e1c71c760ad33cd672deb2698c804de33606f4ad17f9eac4b3ce25bb1b83b981645025ebdc7e257b0a998e600160178495cefdadc528252e1a6abef89f71dc19b4f40e1beb76db83288f74a8cc6cee2e8fcf3a31613515c4a020426407c521d844dafd44261dd12672f164e067f10ddbdd36adb2019e941986b4c02993578daa80dc60a13fda6c145ed3fc271cf9bec1e7cf68792e3537b409b43050233dfde3f40e303d731605ac7265d442ae70a377768e5fa0dc959777b6bf938b16e11dd1f69eb35ce31a88fb22917b9c171ebc3623d2ab60c172729eb29827ce6e51f3f2adbeaa2419db1f1d72299a0aaf656349f9443f8578ebc043f35ece448c9ec2d982c212de3bfe0a49729e6d500b984d912199fe795403ee0442f6c5a8bd9582ed1e8cbec23946845634930af6bbc7a270fdd7e046f227b1d8dcf4351240ae6eb5d61fc5ee908dcd5227440d3a5a5127bd8e7f2eda3ab37a18e63a6bc8f07ca7493d60393d68ec846272a56cb51fc705153971919fe1959c5417bf2680079c45b2018aae9d0342643d04e98ae0857b5f7707a47d1331e73f9fa0edcc56f408631cd9c49f1c732c5c0f57fc676cb0a433085cf97f943c206e73f5ce7c8c1de6dfe627cd0d102e9c1ea9007f94cc0486a61a4885b758e85b3adb6c43e31b5c9a9c40b27a1e655b3a3bc6728760a8265f0780db2610e767adf9283b81622c5fdf2d1ccc8c8e1856d0e63a5a374fc58e8b674b9c25158cda60bf9a52b5fe3ddab0bf14b341bfaeaa04098172b16e23b2ce26cb280633419cb05ca8ebdd480093f82e411c94e82fb22d04ae72b9ef7cea676bcf422f9d0db8d658794526f4b9d2fb23e8688d8d1e1c56fe1b23f77486246282df6458a4843cd0babc418e941c68efedcb65aac463676658ed72a88f5400c81ac043f46df9ca2c396c72ecfbc32aadb20aed951f9053483bda34c2fd072b66fda32dc0d6a4a9ae6915dae173886466cb4a51c1be611785c6a5720402d4c0d9775ec6b5b03825aa16d819e15fc0e836a2509b074bdf65f4e5080a523871ace03cf23c2ed47f222a6163e0f430888e09ce6ccae46e005f4d7aec72a2f7b636fba3453064ac0db1e6dce08221d61c77bc770ea03688c67697f26d725a07e0eba1322240ef9cbdbef48be0f224d7e1012246f6356aed9b331c35c9725c2ad6a89d2f44276f903cfce1c0bf19f63a164dd23689d74b9d2354c6473b720ed3de9bc5f72ff7147cc2a5a52b78d32b37dbf7a99effdfc46711bcba3eaf722a39f08ccd7e4f3ac3e56c16d49fe1d848c838ea062f40723daa32cedaaec172a0f483e60a6d53048307b226f7b498c6b5ae5e27271ab5271397cd820e17016880c22b74da041b2f789c7d01cde39604f4ecdb29cf9e4f36c194eb7217afcf37ff5ad182f7b3592a8b2e19d5c04d0d992ff8c151086212a7598b9fde0a1207725fece61087632f4cb152ed58612357b731867ab2d118728df043348b553791229554738afbd000f103d315ef8b1f95e30256a45f4e926c5649d2fe4f7966b2724b75358a1a627f44ba4f76de695582e8dde8b88fa0c8fcdc66dccae4fd764f7276f75a37a2cd1fa4c538a3c9600717e83a9494100d755611d40cd18b082a5561e499903e73ee0426c8ee414d405e5ff7ed7eb468eb801d42b562b7e497fcd872f73f44a906ec13e86b5ec59d55f7ba207b097b33d09334163428e88aefdff07295fdc70e5a6e047857b35adedf37acd02802d222e8efd3a11433e8c477e0de1839845096c995a9fbdddce583cfb0a1386e79200dabef394c4c433891946b9a6ffda80688939255a585fd0cc77a2a552ebda632f3658d1314b65c01cfc4d7a46256eae56b399adda9e44e35c8ca0d407a49b28e92e028ad9ec32f69f65899597264ea12cf8c716c4fdf309df5c03c5cdddfe5ea49b4760bee8c589d4048f7d57224ed881924e416d40693a4aa374a841974e4341c46dc05a4a805cd6e6797754673168bfa7b5a296b0a3ee747266bc3758f649313aca8bc6d998e30be281c82552918c26bb40f8e8c3e196f672ee7fe2d49f5c8781e5dbd2a22c3a28691a943729da1a74ec9d8cf2a941653d07e911c8682a2761315553ac6dd90f313a61a4a2b0e0d48a48408ae420d3a145d8c4a1c28603a245e4022b3f655855c6a3bd0c972ba20239466e502c125207024802d37d16e1f68c520800821b71ada7dee612472fc1b8503f0169c3ad9bfbea966f8d66c8642b4f339fa4fbbb70bdbd20bcf1e72eb375489dd3d0540181566c06175d4a67cec91d272f0750427abd4cbf909842c55748df22cf991efe70e63e8ef2c849ec30eb2c7e677892e15362082960c895602debae3486b89c8661036e3f419d609aedc813f791c7691a3a01ff4b96d735ed5a8417367d0673a6ccc96dbb03a6c257f974a02276d469d9166b239302c107275adb467270721cfdb2583e9c6819d85b0f0d03187c9206c29bbee1e741a3c72e52d91bc01458b8ae9c7017b59ab927775fb37bfff667907ac78e4473ae33b72d00540414d5ff3e33a9e64a1f7816414fc4923ece1e40ae176f276768ca71514632ba7fcda017213fd16ebc6b6da18d945202ca3fc05619b81db6014e15fd772e8d40644bf39ecce211d1acf97d51d26ae848425908d7152fce9b45c50a515728a287d88dc59766e338c568a13ea8498e26568665d514ccb9d0bc0367b49b672aea54de67df7012a0479a84022e27effc71b7e18416a25e456f0e5095464a8729183ad633fe43f9bca6123085f3337f2dc60aa87d27d5f37a6f30d521c80ea3e4a38a9d1cb18301b74b128b8ce760377bf6c6a23d2c501b83d419d73c25da77266fa7a4fa715d865cccbcacfcfb2877474fed2c08456e53885fb7690ecc99e72951b79b0afcaed7883e458ee624857954449e47ea50a975d2cfcb4086f73b1722795cc3acd5236d2a0096162d9a10cd773a3b60f6a4ab635eb46411b816c0e722043e786013763d02fee158732c9dc049de218d8413da765e4c26254e7f7c8729943201598f8d8eb9888f1ec405ff49e99789720e2f4a93ff11d865358c2e8726ac9dfeea45e9d5df07ae99abe9b400d4cedaeedeec5fd8f92383ae6b642be5ba2a38f7fd674d81d15d56ad098a382c5ed5d3699ad6368dad377cac486e0f872813c3d0ee47ea393416fc7d279aed03f1bef9bfe7b8039662d382b39d560e6726173df7afab9bc378d9ce12726dec4d432a12a578bc5d58e691e1b79fb761472e4a2ccbf831c972b618066c3a43cc9a1224d34f586aaa11d7fa97f8e47bad17290f1ded0b4fb7f751b4453da4ede42fed16fe5b97990756688064e2fa9acb7721f820ba30cb6e8577c2bdfea641762cd9626f67a55708fb92f8ad64f272b6572ab72ca13dcc6cd8913e1412356421a151bef6315bbb4f1908072f334e2c1843332519d972b8c137e91b56dacae3ae5cc81e7380c537311b63cc787315672e97213fdcd0cd5ca373918a68606733952696e2130b2cc161417985b089f1d9a38728b885bd9bf7a01c4a05da5d66ddc7333034487336f38a092b194772b5858fc727b24e942e293ebcbeff265a1dcd7bcab309d86d09b45802958e24b8cf8c8f07216f6456bf2ef893d9d2e8d373711994d5bbd0383b0cfacdc4e4efad9ef6b9372f42b60a44c9946af5af001beb362cd8374106915c28d4bf0cc2f685b12f09c01fc75cc9f58e45c3091ba63ec94dc6374cd48535e139bda6e47328aac22f29272fdb6ef7ce9187cf137f45d71cee3065ce99767b31702c1104207cc3c11913a28300e081b9633937f4973449caae7751c07fd8ce1accbb5886a45fa0c6a02633f907d9eb5c4c1f0552c46de8d303202615fc8eb7620f3cb5797e32045a0f5e3723c50a52b200df57fa8d0e98d87625a4e41e82b74ca52ce20a5b5d52c4991ac3a69c80d2f73d3670dc215602739491a0dd69436c4de937f484449860ef11e3d722e11f7d82d2b324dd13d7a425a61cd8be7876c0a502789891eef0eb574a2db72dd51ffdfd0be6a6a1710cefbe1c8d7bb7c0e48b64c0db07173ba523de7b33c726e6e54df80c10b1752d5554c525a3f4c76928e6614aa9c07b0ddee89c374f17208c260e5fc34f2e105a23c70be1a64a382e488db06b96fc2d25d442d0a16e12421353a1877b0f21763ceb667ce2f9c264fedef4d01b4caa903fbb5842ea1b83c672f463ce11c59652ccfade6c12339b85829bc8e4e8bab7c38b1678b4dec1718823ff8215576e8973dd8fddac82cf798d188266a3771b260ac3732b56ae993726a3b9d8030acd070137f8e0a450ffa77bf77b9826f8e0451a467c6e1f72eff509bc241d37172d07f470ea9dabcdac88fa179dae00e11d5cc35e6c6404e939955136282af6aeac9589dbd82bce86f99d4d2b21558793fdd423f8c4bae2f118572a5e2dc6a65fa671abcf31ef52a9434d60fcffc5ceffeb968481f1738a63f4c72b8a0fb16e59d741c9b46bfb3a064f3a7e045374262105f7fe91a997228ac4c72efc71f20baaf120783e99f6fd7299e62053d1b34976f49233e372be713ef71721d7d1b66ec83d527c1734919537cdecfe452dc6eb57d4e83f01588a42c2b0b4c4ce95736140d305bce89ce5dff07e480944bd4886349f29076ca05e441284472d46d37acf136441bef1e793130141cce2d4b1a6afc62fc4117289eea89c394062fa40f6b2fea8dc6e4668c11575e4752f44e6484d082d3ede898ca25f5c60d50f185265c3e12b64df502dd9aa62bdd537d01ea27c4490dd50d28dd64e03c367272b7a50419501f4d6d7698a42a0344a409f3a08186a918bbaa914946e5e41d68c5fd70cb091a96a079e01ed54de41d663df9151a174985b49c796d9398683f5e2d591f221cab019941863c04be964cd3f56d85d402f9226faf64383b2f2d31727d3ae32eee982266da7a7afad995d1af6fb3a04ef5e5365d00726c7df36eab729f074f06391de5662323c7a2c0a082582f4d41f448b618c013353b33c6496b72fc703325977b48a599d598a6fa67fade7746989ba7701fb974f97d3345184f207886703ecfc69403e82ade097b885284b6742301765aa0c24a16a66f1c9055727a8b6660da4f1b23b2f47d4a6904167a2ec29f5f5b72028e2df686e9e8f7b47249dccda9bfb188512a0e674af6501d045c440d4b59ce37eb7460e392d193cc3ae6b63f9752f72ade8cb0db00b336935095df2ce697fca94b27db292b08753172d9a27cf3fb465b95c665bf1762247db550abfb107d2ec9b46da9db061af92640777b37de6fb86f1621c15adc9c21c1d13a38cb7edf2cdfe35762f5042f51d7729790017b3d6f3c168d5fd0ee956923f9943681be4dafb5094d4dfd6a814c0d10c07294ba117acb9f65e9fa98a9f4d928bd76e58bea1a366046400acd17762e7223b9d1f764585073dc8f4b12d7f9301cf96ff38f272d65a8d1e44c0fce90e515af68c8e31d6916f89e2ca37d6b8173e5413ec94f48329d19ecb8a2c294f7c03d0d789543f806788ee505cfcf19e903b97b6a85b3701b0cc03ba336a17c735f6e64adaa527b79bd9b405094ccbc66e0acce5a207fedaca707c65b483e87d37e72932c66a93a258632959cac35085a05f609853393fd98879ead12ed879bae1655dba1e7c8eaa67d79843df6a5748571051150daec23fa7c42484a149d32153d1f6c49a7ff6a1854b984c6ac1ae7df47176b0d8609562d13b71f0ad9c33df182718a504599f778b91d14dabe7ea823821124622b4c2cff422f70a16afe5a7cf65d764f737cdec53b9f8bf1c94958c0b89bbaf50898b34835ea12455a2f1e6eb315ba9f0f99079f4efc2787c2ae25b47a8280544d81a45a5cd53590a584cb679672acfd8032fb5ff8b214dddfba71204c6243f0d3bdfb6deb3ba10d6b5f1477bd72929e77c1f46045bfd8d2360a896bd3a102a336ffdde551a3fb1aa86754922772da95d669b406577fdeffc42660619fa8db010d710ac1b6d3a5cab5d740f7c033f9b061f8cd8a3e4be1b5cc39412582091019d40d739166fd47629b6962b20e43954c5ae96f8f27aa4b46e5cc4774abc59343ab896f72efd5a75a6589ebbd4872f28460390f85a9377ae2caadc14e956e28de3b4f62a72c3af609998ee2d69a498384a9fc0d3135d8e0e0f9381540e898d3df1b94aeb6bd1e11efbc03440d3f13c23a78a3a5e847e58d4950612310e593558d73a380328cf88af25f668d15eb51acfdcde93e2bf91b0e844a64da5f49a6932974bc351f7d8780e2a38b3029a472fb669614bd797b431c58e57cbb20acc3852272633fcad87fa200d3a299a72d36b325b36c05ee12cb2bce5927deafbff451fcf1e89cb826f8dac9a93edb2f3b7282488af8f70997fbb3c8b81f28bcf07f4bf0a365ddb4cbd24e4e3918d8887e72b70ead046b40a80048ab579efed7bb8333da1b4410d91ac087686328a3b3fc521d31a89a4b3503dae50416652e66a3d8919021d84a23849f3249bb9e2b90be59bafecbbe0a4e02d60e21e86633348e608c738ae3e893a529afac60db8d4daf727cd7677c6a8386774c1c3fe6a0a30d6ec755b66242fac365ac13d346d69a597220d20e134e00e8675ce37a1b2dd96a85607f171669d3c7430dbd3e0fdf05a437f00d274fa76dbe7cab67489e6abedd9746f8c324a17d8d8bf2fc9c2899d3f672430b4a55b4163a38fb9158a91fbb4637bfc1a399ca75918eaabcdcfce7cd5b72861f4eb8f88beee20c8882cd0717837227fa256a444d996b757814dbbf84d8722bbb9efc6548c9f10c90d06d94a4152add12178885a7636851bbde8383ee6672e97b5657c1422a38ecd872ef6b5d267c36ac1d171e32039d91d5e228ded2b672d2550c5ad471e9d7a389eb3aac6507459aa37a509cf27af1041d05c77bac0372889a6e260f8496ac15cd66e0f097d5af910650aef981cf04563803722bbbdb68c379f6170ad68360b45c3e5e53994a32d97d01b35d46773cf778fa117602ed47b033baff1de84f59ff3117a8152edcdd12368abc6755dc1a5758636a6eda3f72607810453e3853f08c599284d3d20fe30fe1f288748b8a7fd69de2ac72bda97275533191460ca35cd6dc11dc1dc1b02b31e9439e721d181db48f0cb3db69bb62b5b56ba232d419d0fded864dd86278ca2da6c6d61f3aa7aa3b9d4844b1e01f72706b9a63ef201b26cc3cb9a65b48baf6abcc718f3c9450b990df037c9f4ccc2677b03f34d3f0105ef01fc65394636efac2be1b689018175ba468a550b3703972e81a6d58276885039f5e7d0b8145ed236f4fb3336c5458daf8d91ae63cb57472ac83bfae80d285c391009932603fb315826440e202974d4a9b35eec14bc381012befa773da26380a7963438abe507aa1d527bd0e0109b2e59f638d5217558c72d6fddda826d142eab395497edf3524569d15e406962706a0ae144742ca5ddc72444542f0fce4a84a8d00d8e940b0c06f47026148b59f524a3b28941ebfdff172d5af980e726e40c8e28dfc34c73eec98a3d5be0644fd0fa04a31542dd3aeb972e22abd346439f89838d531f1f6e171cccc43bfaa9ac08e5ba5305fe8f979fd7210a2bb1915f10329e39fb34c3bd6c2f46a4096199ac8f7b9d78f490c9618d072b8e8826a222cc61be259055994804a19d30e503b05b9cef5ca5c6a36edd040729b2f650645533ec06a6dcbb634936119605c6bdc93f930ad7d5e82c103373939825fcfc6d21496dfbca90918745f06701af5e8db82795363da5d3a11055ec81bd01bbc0f327cc9f321a3baf3af34a437f1417a88a2b15223134033c6e4b9b472e8dc53980baefaa4496e284b0b3aaa469115b619478c9996d0bb5f61c9b23c28d81c450847e8d321da9931bf010df3be5f93994135a00f98724f3011ed9da023b6c2b39b783ebb9be367a0d97f0d8669fe736b8ffd16d759a198e8e425bdaf72d7a81b5a6c7789fd441bc630da48d7f71e9602bd50b6bda9db61a9f8e9cf25726aef0154c27a08f0262603e949d97097e15b2bfe814f0c3633294981926676729417a819e12fdaea1e332b69d3da788535dbec146990493ac69e777fc568bb72e1a82458d024a04c5dd370b0426ad1e7f5762d0029986ee1c2b1544d79d982720961baddfe4116ff60eebc8b9c63e60f0c20259bc737b41ce6ee9716b366a972329ecf00c9aaedc03bebf0fe290f182d42e6eae72aeb45ed268e9c8bb60f80687d72b85c099325cf1677b3f4d059654c493a701571fe2d6a7cd965edc402ee6483d92998f34ac5b80e5f06caf98caa55d14a22bedc4eb13130b90ac62eb84d493a28929ea2a510ff19ad45e27cf4a7424c31f0a6c6c5c78e5ab0ad05cf449972bf0c17bf08811cb56c16fb1f5516200080b8849346151c7fb0c4b5c920a4d872e152a0672a91b141994dd3723f7646e6b08f9c994309cc606aa47711f5939e726296b22a2ae7d0ff807399a85f04dbd4b59d22392fb0134e19737de4908abf3b072d7ffe552fc46be5e85e0f3d4b6125877a730675a88d9783eb2745b4ab5b64ab6a265573256f6405a04605bf156aed1035f3f83210ead38bb3048a4acbcd72092fc0809e07867b9e2a04e06276116b8143f5b9bfd9ff1656a8b285ea6928728f5777855c185dfc22d8fb957e89cbd450cec8feaf95aaf6a391b341dde4b67227d596c054107f0dec0a5b32a39fe702e09df5c92496db9fdd61bb86df845c5b599a2d2789563740d44c24f00f163847b7c410021b3c2a1cc1d273580f5c0b72aede4698ec0e3778b40cc8bc48fc25e7faf45c686432cc5f79972fc1863eb666a601e3d12bff9c29e497f44b5cd48f66b7f8f20de7a617fb0664e28fdb262e4f7227aad43a8eb4b9f62bbb1e1b2928b8b2a4e166c182345fe9cfe960f52a4b72fe7c1d6dbdfe25b7491c1d2de8914eee2530692ce6b1c3178b9e76f71bb00e72843b92a82d0bfed79ab2eef44b6b3b660a5f366af7c6a60a6e143a09e07bc572f7eb336749135c16f8583f9919576b512a2ebf988366127aa7e5ed9bc0f30f7233370c06983ec56d4335e79a07082a4ee4ee0ca3fb29332a0c111bacba65493cebffb6cac562ba080808016498d68856dc3b6b4b46b07003e4f080cbd14b8772c913ce81b89d430974973e592670158ec161f330f12038aa98eef442c86ddd7204206dd6fa91a5b354a9c1000fc7e5aa5eb396883cff9b4f4437cfc6666c165208e9a9af6dfdc4ad2ec1a46b9c448fb83aec2fb5feb61e4cee8a4d8bda34dd4087764754109094fd91157c51b8c87025c6b60747ef2cd3c5cc6ff9fcdbb46d723fc67ebef2dd72a18871ef241265de609712d08125e7a4f63df0320f68afd630f2409335fa78ecbffa8112b87bec3445acec66c6ac9e6e7bc297ac5a8ab6107257a90bddd4cde82ce5c9f6f560e0337a756b4341ad3fe522e258406fa789f1725c6a179ac18646923550a0d0865eb0aff57322d2b76c2c658eebb547aeac4a54c24498b2a889df5eeefaf6835bc6af337de2b738f0569ca40f53d726e02352725a60832ce1c98a9013e0bcb13f9782f6461b4108595cc0c3120caa5a1446cb0e602c98b0302ed6662169d63a785e88b17a7a054996aca5a7949b66efe99aab1db8c6da3fe73f8c6c6b63749891ca231d7612855aec443f2ac62592ad25fa780da714332cf853f647ceda77c395f5d664f72f13b80a1de90dde7ab69d4fc2874bf0e122521b79ad452a2b50bdbc9ecaa78ba80fdcc254a747e5c4b1175b25511d9a0bca5e7aa017f87400aa6a9c11dcffe7bc895325a388c332c5e199c0d2f32bc5eb8c8b880c71b9670e0781f5a69b057ce84f27371238310171260b476f466b21907fc3cc3b68734ad03df2ac5db733c628e54b13fc1700371c8eba409ef67211ec7ddf0bc3545a969ce434fdbbd56937452ebc8073407cee116011986b477223f56994f4d07c94825be310a28e39d598907381b31bb46a5335d57a7c533972f2d5f234407c49a8e52dbdad7ec368fe782033a88d9d2597d31942eb2907d82491002702476684e34eaecb1f4e72414ad0ca8726f72d05d32abc02bd96c2f6559dd21e3b6715d693276ea5122fc4ec834b08313368969295af4362cd5457b84186e81465b686fc8a07be034d2c98c7368cdc63b73bb45e8f781fbab1ce3abc00b3262d2263d4b0e6346214d61578e9ecd99cfa79e0c8315568248d32e02e2c7251fad2a26f71dee585542284fe7e4966d5948efd77c9d1c2dcb50cf0e1fb5e0d3f5b469747b3fb3ed9e5b72a0e202e89a0e669a7a9eb5a7a4b4dfc7f5f67a872e8dd452dbf04a58141ac43db0e5f31688a730ad81daf067e697d98c6312848721fd2f14b208535402ed5ae51dd01eaee51cbecbd0cd3d7021406a524cc2b4365ed4eb75fb381b48872a1aaf8381b63716ceefa75ff1518db267df82f9f5d577270a7141ee95baf6fafcf238d5747d9b5e61c87f6c72aa0651a0323aecdc464305482b011145e8ebf905c0d16f637511a74603c10071f5c17e7c664e218941372aa85c07d168fbd77a073218d1d64c1c797942a225c87ce15ef8c6389d43cff2fade2f6f80cc113af77b2c092563272562b51d39797370adf815620b22f0b7872268e2c375e1d86937ed99e7ee3d8cf547c2c9ab93dca2ed47b188b391a2b99721961cde159c95d478472a93cd9df576e45305a73ab1482a86c5491a19ea90172839be281d5138d0309dbf5bde27858afd9fc564301526bda52e539a505f5ce721db507519401342d9288211fec2e6f39cd7a3b62cef86d133db71303b136ed4d25f7c13c92f868c0469f46043a3561bf96f1b021588140c4a8ac7179e7dee871d6742613f43eceb6756f11fed4f128a9308d174308f12ca94eb713b2c35eef7278652cd11d8c7ca05c233a65c1f5c3ae3944251d1f76edd99a37adbf73130815c47b5ded6586f158829d3703759214b1dd6281d26c0f3f323050aa31c7b69872f8f120acc42f705bfb48f7ee49b47e1abb255cfd248a91a90aeef8601166a372d099a560b563b084f190d90ff0118707cb21b0105e483a16f05e85434038a30b6aa0e920b1fe4ea2381e2d1bc0ce39f67488217249fdafdf157f37a416add9723fed2fc5c5695015491de8efb0101e29bd597700349c63a389c663c371a9c2009053a2d6071312953e4e3bfbc93a6ea44285e4b08b9f23e946f0d5018d405930a75fa45269367e0ed0aa1a28556d1626c18865dc8e2899b935598020cf891872cdfd42bd80345471ee46ee00733a9623e7acf61b8ba19ed2ec4eed1703308f7278d97dbd9f857e2cb384a1a0f6a9354eed51515748603356095b1806707ac00c3d5991ee8a728b921ebb7ffc6423b16b2abade21ce8555fa999c5cd1f73a8f368dbbbdf255a77566b59344738b749f05a866b5e2b5db9d65975e6808a90c522cd23ef70a37b52190245364b34ebf189c1511e3af254561ae40d35e126c9cd8728dfa73f1129a35eb8b5189dbd3e823a9740ce62ba97d4f44bc0138fdd6505b7260a268baeddab45dae78d61e8552b94af3053f0cf1f546310da1c833b25d1d35396b30ecf680d48de5225b54659db6aa97f0f8217295fa5323a896668b225b725da75706ee7aff321db09671adf4d38b43f65372872a9fa7fa37f8e04b5ae5729e4c1f7d7f91791bedeaeed66100404f8da6ee2e509613ab52f1179b3ee4c772e3e0ed5f2e9a17703afcd5c87cc59de7e4fd33d9e94c0b9a9013d283690bc6239e620f35c77bb675e5fe08b8be4d638daac499fefec6c3a7baa52f538e58de24129630c3d4c8642858e61d74bc5bca34a73757939d3ae291ad856220afb7df72745c8913651f8d35ad30d358d974cb987b75331837f424db7fd7de8445f2f92c39bbc69decbd9f18bff68e2b845cdaca8674f878ba6c0e250898972c139b9b72b2a4c4dcf3011b613103a2a1ebdaeeb300786af27fe65af42bcf35fe9445a35b101f372ed86681080ac1183778599c98c04de91b25d96a38bb39260b638cd1121111d81d40694ca18e56bf7630da128b4a9d49831a9b60d26544ce6ec53e8572b1e532c2867cd49d4d22dbb69bdf46c69286911b68def9de75a65cc982543d6fc4b3b71324088765b20e6eb939991aa59966b10086784582c9f738910acae94fb2c5b44f71e1ff1a084ac0f1adb8cc952bc03b2040c2bcb4489763f32352dd2c1ac5fe61badfcc26c7cfdbd07605f653ddbdd92f73cf27ea3f339dadf376247214b93797cf86af0eea7bcc73988f0372d38a398439481afa121dec4c1184bf6fe149b7d3271c658c4b31809b2c3b856bb8ef29fe09b5a63dedda07468b8f36728bdcfb077e9b906ea4abe4764a2ac8e70ec74226d4e2885d466e0e84908f8772c384a9445fe2e2641b6da96cbff6f58448fa1af001a885639b40ce9c3912016d711878c844a8accaf506f07ef4b34cc78b7f3dc668ce7cd4f0490e692b1d387205ade4f6f1b2f68be870b54994d172a07665365a5c2c01042af70889e8d6247215084cdbead047aed1d736ef4189c2e8404f8761fb0f74a2c25a439944913072f93742e7d789309ab2dace2b8dc6c5c9c9ace9e9f38d25b39be72f18070f8872aabe4797b5a6011e605a2b18df4a21b72e188b7f7420e20760ce4af130133401d573d3b5687025f40770b7a940e1c379cdc4275ab0835caf423708cd25327d72a546f9b77e1d1ef6bf80062cab164170515c4ccc838192e2568b34bcbe862235a1a68840c083337b6b809f96e7ca7c8681493dc111b240ed28e5f48b329a287259a3c32a34b583545b63e483fd56db18c5bbfc6b091762c9732ad070e2634e1a27a1131e5bf8c5d1dbdd91ef89c72de2b5c290ed61e65e9d81268bfc73e9971ef8ee8202b22bd9f622405c81622ec0a1f161e7da277a8eb43a089d2a21130d72075f5cc1adea946fcb694aa495b48a87ed88d2ae3b677294c683175c16a3b77249d4799ceebb503246c0e5478b6dc3bd3f03158bf0322e08a27bfdbf8a9f853b1241612a36f1b273c66a77830e3c41dcbf9646e6ad70b081d4a70653a2a30b089d28e869d83e66f621d33d218ae094fe45656f71698772a9af2faa1de666ff265b786f85d90c92be6a9d6dcbc4d41b598a94c147ea6e6c72cc07f7500b3c403b744f1c77c7b0b2d02625911478a6fe97321829e060954bd6ef65cea7e5e6cc7207f6112e24cc66899a1d5dca187ea4936e7501c8286ed1c0456cdd80c2894c485176c8670d56949924b73fc815a42e46d81edd792a1f9983942fb967ba7e9b5c93683b2774281b322b22fe9ea11ec0a6ad11c4ca251e3bf2c430194fdcaf23727d316c6c45d3c8038a4d4dcbcaa6fc08cc9376dff327d1cc60382716f135882250af57c4739d562bec11adf6c5e33646362c2d29571c7127ac0e83aa1b59997260fec368dd0aac7e18e56e7188636894842fda4f0ac4d5d7c9f4aa92ec0833724ab9b1afd8f73303e8de0e0e25c9ffeb88db79c66ca76c0b833814626fa36e72093bd46c33f37da67f85db5e9c57bfae7956a2d7318b238b87eb8d5fca021672bbedf7789617e778558a621050f116587235401662c7367498a093cc62b570725e21c084cdd3d49c2995ef6299f67d8a3fe3d6c961e0ad45858dcc2f919fab7287e5c15d95032ac883c7513b4ef9b2d1f75391fa0a471637268bf07a08382972e11f3bf9edc8ee60b6fdb4288f2da1e550e11b8d9b18f9c79634d40d21229b347029b29b0cdbccd356cc237dc1cecaabbefc748f7c62084ac2d228435fe8675677517abf45e469d47b6525310cd8cc12b6e9fdcc2e6ff1a957ed8e18a688ce6a3ec8a7c76a76d9ed3f41c4b429e6fe7ec3e4ffd11063d8804e09b9ddfa53d10d4cb18d3e91396a655d8841aa195dd82439c6ceb83214d55d6f2d21c715598021e14936582e89f5727750306d93a61a9afd500e8f549ddd7078869417e5246316e14b9336e404571d1a10afb42ca60cdb5910ea7b7033b6f5e325ff011cfe0c7201273ca7297289cda307e5b34a4f91c8192cedb6de5144540aaf9ede291321724a9fbd0e8d580f51f1e103b7a4b0cc2c85d79356f60e5a2d6dfd5808bb234772da5e57d5deab02833a61f1b549ceaf434cc5f4954269e478bd6fe4d15fdcf1725f03c357cdd6d664589aabfdf88ae7069661e9fa8b67e8ab68431b3eaa856f72619dcbf0a3979ad58376206fdf2252c208bdce3ac9488404ea83ce8e2deb35344bfd4ec8dd0069025ca002009e04b8dbd211d8e95eeb34c260472bbd59b02172b8947c8e68342e446f1b81a38493f51fb515dd54d43ecac67dfcb601d6ebc74c4f996c49797f1fa8807813a80feb6725d3718aafe1b550101a173f1f5026c4723178a5df863211e43f42ff013312dab9ac65db58810d05f2e25a7bfb8d9161725c4ca5f355a468f878d03207eb0e1a6db1afe05eaf4c6c6588035ab1e4eb057250220d3660672108f3d1e4115cb6c6f0c0789738fc741f8037748640106e04249698ef6bca5b7fa5fd7538579f10287531c6d57aec9ec04285456b55261c7d7244d4b868357d8c5f85049e14bd2697325737df46ca7cf4e6a2ba35e6e9856672e1e04efa91f8e6bd4834527e31216023c1b648225d65a3d47cdcd060fe6637727ed5dbb7cb4558b6901aabc2ff660a2cd5ecad27bda11a1c69413f849b71bb6934c22320518fa6529c7358b4cb9dc9bc861f6559ed12aca054b19782ff283b1ca78309f1a6a6e3480b681025099dd419ac84e082b7ae3f5c75f8052a2abeff4d185c34bb54cb722ff02989bc3f8e76b67c7ad61349214449381452f5c76788721e1ef2cecd5d7eb0518c441d8866fd85341719895035a1c4d5e2812a862ea962b371de36cace6b2a5541c4a1868f846cf0876653c2961e4e593be39530fb8c3f7793776dce5d179ef0c45c54f2a08b34caa2f6d173abd610092be81cfd826916d3e638ac126e48d315a9f9b809f492648a4525e571e1c21cde313fb2d7404072c377cae1fc376883f916a116c946afb09c51f6a18becdb56d5f80a81f0e76403018774a68c326c0318f8cce4ce0c100caccf31ace9819a729ae4765480eafe09b97ae61f279b707ad44f6315d907a39069b34db2849826c44a3dda7248cc81541c236a91cc017eacae432ebb4988f4efceefeeac109d092e94be6281e86a594fc27b03725e39c8da5c2cb93e428967e7b857074fc82d4fef2afd5f69fbc54a0965e60c7ca01c83fb135b8658f89a59af0aa38b88ba96abfb0d30d8759a0f1a722453e6938a6a736d52b39edc644dc5a3e04dcbf0edd3821dba246bbe302cb6725553df3b09b4de6d46c19865b2c02cf6e199c4adaf47fbe78d75191b9f3ef7328fb75d75f985853f575ceb5396cb1af39660d1bf2a45712659a32ef791a8d872b5ca9a2f0901f79c239069c701c2ca6be18c272ef4c10b272d0fd78fb3255e6978f13e847c2f02200cb8bd0c836967093c45064ebce35a640cf6f3e34cb44b2f846b99c51320e7f4c191d431eee34fdf3677a8e7228083e25ac2ec3cffd2bd727b6c2f2b0c4810547476aeb9ec46138dde9415ff7d0da58c8c02351fa359cc7238cc2753e13a31a4017681d448114a5ac14c8de18ab10c74ed313fb4b5c4dd727163c3640d294552a505abd35f2530f06f522e57f0d44f38fa61826aef8db772d6c365256ddc2be1d0b49b4eae3e50d7961333fb5b9af97e495b98c71ed49b723f6533818cdba9349b6856618fb72a2171f2ced7346c472a6e0fe75ae391d86b54660e4616e004ea83922a957d1edf246c4559be08c1e4dae3a31c4c63cf8a72d19f9db59a1c6def51b938c8f06aca7642cb964dbc0af5cfdd9996086e857d729a8efa616fd892e8a3f1ee14cfe8a5e54386c8dd8681df541c1fdefc7a6f9172b403b6ef46f86d810f5fcf8a365d13bced07015abd28954b57221c9f085f3c72ac8e52d59782aa4101483b840d61c2cbe8750397ea984c729551d09afe18ea72fc12edb396697a63cbd14866d91f03d79f315c0a783efd92ed2319b431693d62ce2fce34f2c7400eb85e95d65e239ad6f24621827a2b62ab48c2edc58922a1710ae63472474e4f281d05bf5cf53c926e04f18cc506524553cdf31b982ee6e502fbf97b67ab707747573054929671789daefc10cad3f24bd6e367a88bfe3a67727739f5586b3e05850fd3b7c02c52c6ea02cd0a32d3d5b6d3bfe25597f12060234f5da43816652e2765268ce7257a9ca9e14605c08f2cfc186261b337376edb40bddaa9f18a004f9005ff7089877289f09caa87ff83a4ae28feb65ce9bdbbbb728cf31dad03ca05a1a186fb2bd842609504e39f53388c3316eafbaa16e2d7f4415090f10057d61a7055183811a4fe92383530922521192d136dc12e6cf4fb55272ab44cde2ab3a96495a376d46ff17946e0ee42ddc69d126c042b2967aa247372ec55a4b9902984b3662817fa96e00f16f2dcbc302745a77cafcb323fb492af36f7e2ca2796e7bcc6b19186fdeb5a49ba1a69cd9f1a07f34823769ac54dc47972ce800ec9ed25d52a593db8c0d2e8845865c7d3c444f2cdf5ac1e2a2e3e5283724cade037515ba9de944262a1cfa07fe66d639be962bc2e291b801b27da001472889d41d3bf599536eee2980d29ff493530c87b2e8226d4b1ef53b25231639c72713bcfaeb68e7f83c964e487f4ef018a28daaccf4f3cf04f33a8f477a92eb903c9656da18650d90f036765074f8ff3162c1c063694da0ff2351306eb40c8bb721b3ab4803ae9fa42ac803dd9f889a811e08a80f10c351228b6b61e6c8936b718a58edeea3f29917bd65c904da8fe55521bde7bc89783f1855e015907e62ad5529033c96ec41eb5bd554e2e451dff2805a3415a42c18e4a9d3005903f6ddf7d15b60a6bc0193ecbef601c6a987d871eb0710a142123382191d20bff64a27ac772c78518c86e6dbda4a0846884008500719f1f6e261c80e58aa90d6a0aa461c472f30b008fd956d1a23874c4c3789d13acb9d684792dfd754b7a36dfdb838b6972d53d66a5a2bc65d070ccbf75e6b670fe5396b488156babd82f68c1106c6fa50a12cd0c73de74d1b4673c0a1fd288f806b70cf7f74501fcb7c08a91e04d9e1572349a87787766a38afb554ee48bdbbe1a463173e03d00db61aac02ba5a311c872a39fdee1db503fe0ed94a5a3014bdf01f65e10b2d72f711ab69ddc0518952072d1df850d3129916277c4ba13eb73509632cf8dcac055a2a2e10d6c6634083e70988923a147e2119faadce4839dae7c51dad9b12d380e120cbde8cef153b429099e8e8d84a5b398ec0a70c7c53a72b68887c93b4d4426583d83593f9406a24f72ba5ef1a5d889141ce47865063d30dcc8a65a5a58642b5f1f760ad6b8bc07a552aac1923ddd5d2480a4bdda076a30966e353cd431b3d343dbf3ebe9028a59ea72634b1dfb07bebd119260ad67fbcb2695b143e899dad669892d7530f1f48bdf36275dd0a983d8e660da0df4b7e473c1e011b968646587f719547f8c52799d0872937898528fcf36b6c0c79c34ae8b49ebcd7886d79402febc4beee904080be2727bbd1a386457b10ffc21210a29698a41eec15c3c5145893e53c264eca81fbf7200bb22a483af86779374885f474768ed724b631bcbee62c6620259de421dab72bec7f716e126d6feeeac56a8a11c735035c44a0b186f33d454d0e77dcf619372610e9abb28cc86dabfd266883bf9387c7589b67b0c6f192d5b45a804383f6b4f5e32be23f828db888dc7e932ccfa566fab69fadce6838bc913aac7d008791126d23d86c0c6c9878fbf138277a97d6e81e60c507ba8d9444503e9903700ffc21d3ea6de82da3edd67c19f4787c326f5aaaf0d1d71a2b8f397b81f005be6861a72c3db29898c797f1c9136546f36a1029997f2aca75885fbd0247ef4625cc9917239215ad24c9f7622398ccf20b0636fa9870e5196877c4ded0e3e8c188dfcb172a75d527ab1c25ef1c725aa26ad86c04b15c4c79c3783d7b3fbb19209bd9d6d4025f7e5bb2168ad0b6e461b1fc75de8477de9b1190ca0aed0b2f079f5aedd6547e19e8bbb86345cee3eac46d440702cda27abdc8558bf78b75e0356c0494bac54b18ee63d042730675201def55c7697a295ee3bf970c94a95b56548b3f06f84728e775245176ac3742dee895b8ff6878dc4b053edd5d9cbed1e66f28a52b56472f55f8a987f0cdcbc1ebc1bfe532e0e0fa2ae3ee67b656c28710358d773e50a72c5f40fb93ab96a7b28f7e73c13e825eb7b2a437189906fca3c421ea55ad62372590da46861bc120335357333f6f1333fe991e8283d6a4db498428ff29e5e99247b4b12b51ff9d9e2f81f65289c55d8885851f44048b96e8ec36201fb772527729be060630136600b8ad03da48ff1fdfee085037389c46eabac52fb2ca7a04f725ad748c6ff67e74f2e569d45ccb15b6988dd56ac391c8d76e009c7ee9a2e4a3e8e4fcf31a2c8d4de341f0752dc13859852284ea86f87322a73e7f821e250cf72a7e764f6f68846a2ad61127fe25989f4e27d797b45d6de04a061cf6f77c4033b73a2446533f166f8cabe86bfd492a457fb65aa08a38724f42b503f973c150872995967b8235c8a88d17bdad44379cb110d7971c6416320bb0ccb4a5ea43ac60f81e9a34dc7959321418b146553b5bf2c7344fa230a8d44169e6496843b882f72033861c3a3b388c9cd9a01099a8e8f3b278c7dad2f8abf43229252cc849c13721ef38aff254cbf3459a856a7f968c585f39c4370ac9c1ff3a96c9307abfc6e72121719eb5ed12a96f1ccb916841a25d001c7e57ea196f8b86fc37bd1f5f5221a0981cd71ca1d0493f096b51643d705ba6a2ffffd800c9935a51cf341d6b82472667d6a531c210748ad680b605b4e91fe40682ac041e9bc6fe38ba8cef25e86724379a05f7c2d57862a1b8b8e17ea62b5fa446251dd1748d5787303807d52ca72a520cb444a02e2d1215289e8bcd3e250753e2f937c39fd1f36f83a4ceb71817260393e374c03f78cd7026a17417f85e7607195c6f2933b0ad386f69c58173472d4a22b1564fd499146cbed7514d2de9baa594dc6384ebf4d371a98b341f0f5390dddde33fa39837c971ef943c953507242923561c74d8cfbb16680219a9629728a60bf0d93340b5613139a1ea2509377923f5a857c4bcdbed31382dc2fcb3e25ec54093d59690ebdafcab025b419c9c5005f2a913ae95a0ca32861b46a3a187269c0fd7e80139b82a3b39fec153dae9f6b7356800314d9a97095d86589f069565dfb8134829c7f2814bcdabdbde8ca8f7ebe4127fa2afa960194d029a97d737277eaf3c6413c7e79c5cbb8acd9bc587bbf25a337194b680938f4ee0c0e55451d495271555f89b1dd8022350d6527d7a0f268af0fd471b7b9e54e09c76958c77247e697d335057c8d0642f7039414bf657bdb7f2fb44a5a4f5402fd3fe9c0ec455069c90081ec43ba93affac77190753910b97b1372a54fd2955319ea4d3b9341da1d03704f3676ef7cceba490c58abaae70a41fa3f16896a7ec3438f1092d272c31c40e883c1e015d23577af5b6457171a8194646cf293ee267090323c361b525e9580be95a594c46905a570f6a9ba7b21c3ec84ee27bf81546cafc188db5b72d042543a9a5aeddc35c72612a8e18214acbe6f112311653ece043512cc81715bae86b8c579b57d68cc7fc2f431e9d7d2763080436ea96ae27d0cf3103c2aaa7219c8297990b087e2144ab19f3711391a6f851ff5f4135af64b0379ad27052672e751803a7289c950c14aa8f193a7e1911f51769b5ebac5e5b7708eb55f37c101275557ec9d6d27f7842476d1fef95b2db40ee762cb5d8854635183777522e4726a6fa26c5f64e7affe69bf17e361e1cd682656d8390f6e5e48bae77e5bb49936e999f640437c582eef95c962eeb97e73bee0404eac8352f0254c2e4abb670572b3e79e150a88f46a8d41e7a5e1867ee5e4617b31949fbea6e5c1a6f2e826ee097630c4d62124ba152ef7d7f262324deebf62af53d2862fbb49fadcf94e9dfd7281479bc29bf113d4927943b3f2362c12a4c8dabc2160cfcfbacd7e371b72f900a266f65ca25cc90017c5071ef476de5351a12600f92b91396754d83b3bf31763e76c812d38ff38a7fc96474dbaa5158c5dd7f7791e796bab337ac32d5ce58a2d38ab212c9c0ae91778e8063f18c47d33068cb1caba83fb5045216f07d7cdfd47eecf44f72b97d7e596dbef967451e0c0fb64d082905951a00fd692a30dd23b72e731bc880ddb1d4c8b4d4c3630e078307e1e19b5352971083a3b03fe97f1573dde0dacdf3fe176d53029e4ebe07a5374b17a93ccfb1e7d374eebdb5e0f539772d45b36d1442a50c9df5fa955a1def51811fce36dbb4716cd3575b88251c0e272743d2bcf521fb340174f22bc33a110e8e3f68cce648ef9432736cd062452ab72eaee811eeab2b30e7f930740b91ee367f8765fdb12d4a2a58f1a1dcbc12392077023046e7aaf8f0f135e6463fb10cd333ab0fa4112fc9e6502302f6e8dce466f0c8a43c1008c597d96b4fe77c0abd85880388f6806b700ee93e92214b7a617405ebd114b310e2b97b208e7a1a1d6a5e5f2916a83473965c7995730574f446372b1b0f48f343a73a460a0adf6ba46064328bd6097d400225fba75ace8e091b759f1bf8fa980086e91d6c2c0c1ca216ba6ea4f9a9ff0991c0b37f05ac16f681172dde76bd782486642fb4856363bd6449d43f8f6eab702487e14357702066ff855501ae113c62becc039ad7440803a401d9928d8c1362f28b8e0a09a1c0c196c719414ea12f91c022b93b673b84e735baba311afd644d5edc5239ae5924a90124865f5c5719ca553cc76b6a311f302c8be1baae7a6f2d713f24652df232e0d5d72c030c5474541fe47c71401272a4c1a81631be9caebbf4739de3287b03fc027215bc02a92cdd9d8d700879768df255d9cffd0532a9c198d77225b83fe32f2b272ba77aea5ff72fc60bc2823a0b73c33495ddc67fc50459d30e746fe574bd61b70b865f79fe6e509bba9e33ccbf20f34ae781cab3723692d4aad3e2522d241a26ce5caad403ba58fd8ee0f7277c489e7cbcd1d7ee8131c85893850cd1e49dda972a9827fb86c55f7a24ee58da750481528f591b796ff11552d87bdfc911f32a372f2720d3a039fee5b4b72dd6c42ba13f5f01650685d41b1458836d0caa9d8a92c2c951806b5d743319df2e1248079eacc42e0e00bf59f1542c601f08bc62d37727bb245081e5279379ec4465d00060f275885506cb6a575cc76ff26e9892ca472e1dd5327e51dc9ca2f75992fec05a63595109e4d2fa1bfe352ebf7b0da6d377230ce3a970e7ba68ebc2b3cd19e4fce30b79682e807d0ec3248358b1962d45472d474cc6df83b691ece2b36b612459a574380dc5fb520b9f33e2caef2f95c70723b1705c2ac57c44cbe07431156fba96ce88d8c02c9976b113ee8fc5081c762724e7991711cc9c682db719ee684c4931c0000c73bb7425f5071fd6d57959899727686e7d1305d306fd7f9fee07b5a43e693539ab44663962bda0ee2beb494055c13393d1c28714d9432e90b928731878bd3fcb773227ae67308aed6f12eb33472f49163c8b0e316fd060314a1fa819ad7aa2bf4c9b59a8437a2cae0e4c3e9d902bbf5e57afab027cf93c7fa0eabeded7c7d1abef2120ec9105a1e8eaad416fc722c16b96c4417075871202a10665e4c301a7b5ea8014644c00902b36ade37a0723ef05cba5f3691ce84837a37db9dc408016902c0d21fee6caac03bf4651ba2722e5c44b2211f0a707cef0a1748942807ddaa85d685e4a35c97ec9819c87362491f60901d97e34c6f8e5205c40a7c3753939152751d7482e5fe12f77b7c22a92f6c4499d2e4234ed34041cffcde03762127e69a04cd60dac2868a73698ebf5972a9ec903bd0c82d9798f18588d46adac48f567eeb735ccf49fed42ce35de09d6a757473668d15e43e81f80aa5bb714e78a508b352fdb9857596970937512bf772364f8aa44e99e0a9bf98cc3494e18826c5220009325b71aa67fb229d132a8502b08b5d9ede25917ba02f8573b5672a5c92e91e7be31d16aff13c0c6eb7abf972ddf092a98e07933723ab388e52fb2dc29633b8f264eedd31f0fe9097ed9a7958d96c9e41a3fb82eb03a135cd59617cf9624f4ec0fca72a21659da07bf2c0ba0ae1860e2dbdd934e952bae072b0fb757576fa7fd8fca55bc0029d9cf7a4084972cf69ec7e4facbc45bc5a23bff8c113801ed690375ef01750392a26c436db2d409420b1b6ff1a58919abcd55cada9dcf4dfcbf9691a34756c847dd96111afb35ce38ed89c666b285095c8ed4b48a610697bb5d0fe221fd935ddb5f3a6324b8613ad04eee0c2d831a2145330e5cddc7721fda3816f9c596f805da71b74680362723a23911d5c651868540fe65ab6e7ac3362ba5b7ca0df198caefc860dd3a7500d4ad4ad27dd6632f134699ef6d4354a61cc3b5919ee79372370e7c514543ec71252c1f036e9fa02f33fec27e11124c9832ef4d1a6bf0893b7caaa8b4c15c4e872790663934638e677fc2d11c9027775f773a195719c5f066dd8b68a6620a0c71017edc09560cf5a42844750f4d5df07868b47b9c4038e6845c6edb9adcdc7522a008e24ec918d0173172d996c52e61e2f7519d0879cb5d283479842efa95b3b722e2b5baac46532c66c4bf9d6d4a47d32c6e838ced912094fe7824ac11c416c72c094fbfcb42dedfb90484f71218811daeee05df6171bb4f6c84b3738271b1c48e91cab89b1801957d88e2eb896531a7e8efad8ee5e1bd34429047141999af372af577e225a5e93456dd0c20d4dc0d48bb9ec8dbeb8dd169e9e2e951d8b8427721f456fd170f8057b55d51be280e8738cf9025ff27b4437b8a6644966074f7f722f1e293c6bed3dafc5314b21a7f6ca6e0e4d73612df4c2fbdd298bb65e9b987291df59387803a1f9f10e28cf05e57906f60386311069286fb946092e3e9e6e724886e175656fdbf70ace1b7e4c95c4bfc4fe3a3b80f191c9d7b2ade8d0486f185de8907850648ab32fb177eb0facb0d5044f140727b658afcad042858547b6728a9d523a1012c412b5c1ffc26f9d9cbbed2c02b13bf9f142656bdedeb7d74972dafa4bd2cfa96d0416e113d8fb4983668552df03c5c3007fbc764792100f6a72e9a1ae6625a5d6da44e153c1eb4186fa1182a03a48ece5cf37ebee235244e572d57845f5eebf494ca87b1843613e27b9830c96e798e07bf6ed377871c4c1ee52d616e73f750e2a25c31a7e22c5186f1eb67cad4ebc3291e1bb57238a5f741b36d4d02097a662ff4940ff8d0f4ee11d76823c1fdea110d871f6e9152ccc875c7276faa66181dd8b2070ee7ea0aaefb8e5931654742b372062f4b29023270dd8723c84215197f58b41a7f9d461f983353d0789e8f222bc55f1d74b6887a5943672aba50c371ce719acb45233d5e524d0cb5965cfaf4adaf4a9af6756d781dff5490fec773c99d7c8399300731e5277ef4111cdecac83f5dc14afc440442fe831641473084b66b6d82b688cb74939aa3597d466f2d7d75bf2340fe71b05e5785066a5190528cced291a7af88624ab545c274864183497e82b80b34dfd43615e221bcc0d9fc44ec9ae4844dec632507ce8e66f641b59cd556d8b161ed0421eddf5725301fbf6c3e49ae8113f48f6fd4d73578559e0e0bbe1fb5f76085e46669b61722d94f97409038141ebfe4edc33fed1dcfe8c7d5ef3de8b25ea9e2d338f22bf6719d971ff5bbb1b1036c8a3f24a9701ffd6366b3aaaa2bfb64d8a26aae2d2e62e5d67718c763d632fc1af36757bd054dbce21de83ee83b24d217f110f98d42372623868c3dfee59827ea6c7b5b45ec973182bdee96674621af004a5f19576f57240d46da7adf3fa761f7e7255d48ebda080f3c6128d11940e5dfdba185b7a3772b97c4820eea3dcccbb3f3109c3ec6d00db1391af4fe620a58cc10a82d98c7a6604611df1e7b0a1b3e3478d3e9334fde64190098dd9b080b8128bb6744df76c7104ebf02771a0421e087a4da49a672e2c9d7022e2ca99618b551730cc391dc372074229f58d260b1243027ecdaa5883c0f62e16aaa7e66ebe4a530b21b767355e18c5ae5a6eb8736187f043ffc14dc9770d8ce17dce7de6ef31a5d045e5dddf72465ee5c0afc9fc533a6aadf252af6c0f778c0852c08736edf44fb62b65aa4f72b5b072b51992b89323cd9f0f32dd435fb47f4d21970631c981f243e838e9ac7226d79fa650d35df2ca3e8c4f39948ff3cba2d2afde8b6215e8d6b585b4f404729a94f593fe2d51fa284b9d432e69d5b564543ea2fa1bdb4292adfcf6d01c8c72b5ee90020bce46dee9b4bf508934e7ff8a56d3d940a8c6f4fc241b8c2ac673722285677d8d082f692f4cb8189cb2a83ef5de0069c7e4c3dba3e02fe9846a94725b85de02ea5f2f50b8843413731912d501298d099fbbae10ed22bd3ee0898372b3d0283a700630c8c330de69c21af9306b18c60739e85dc02a734474a9643238264195405b411757a661db736bb6993121f4735ee506532fc401d13790e205723a5efb301a438f0c2975ff64353dea77f3ee58ec39aff4c27b7610c3087a387270e19220372fccff6c0b4477406c77ff599a8cad852d564b803ab465f132cc72f422ac861f41d0e9dfb8562c73430d58c1d76ed51917560f9389464f948c1e72f4262941b224ef1740fd650dbd31baad89cc6f447ca41ac92e8edfd26a824272db06a115082150ea6faa710a06dbc2e931028c6093621fe15ad701fe0ee61372f4dcd051e1909411ea1cefc9cebd71cd4d384919608d2c2afb960b96b1f63972a4302f7f24b2fed5752e401fa76f70634b95d1178cf3b2404dbc65f3a3b2b357495d23b5a5b849cf3242c337eddb912519562b661bfba734d757bf86c1c52d620a6aaf866a2f104b372906b82d2e0fddaa3399c2c74d828e819c031f23106801cbf1d69c7fb63e850f1ede54ebf6341e4da3be8519eaada3131e6d80cf00be7202c5c71fd8a4e7d64ecbf2efaecabed5f3d65b886d5b9c337a573b50053c5572170640b47e580e5004dcad9f1cbd8280ef80a1fb3256cbe1c4ac57952280126bb184ed7c818d36de16ed483647a3a888f41c67230e500685bba0839ee552e06190e03ff5ec2ed1e9b9d78110103fc7c506633bcb776cf949767c3086383115722c0266c7ca3c3ba0675f71d5dba67b48b01b581cfc1e9ec47d6d001250307258716aad737ff8a2190c0930adc2796f8582d86b1871893b2e90189b30696ae472a559a6725a362d7ebc3bad1c52e39b2a9108175618a9f6f78cad8f71f625cd72c3568ada5df40ab447f0b516fc834486e6959d3f5aba490d80a30defa23c9472e1e599de50218b8da0f10a9983570810d3a63ab6f95123eff30bfb2e2f39a97261bd9124a768e74511f9f8ee68752b2183bc0744af3d6418439dd71dd8ad30261433b0ec1026186c481081892627a402375d647e64c9c4bda30c04ced76524723209890c7ab0c945e3afe9adfdaee8cff04c8f863db87501e77a4f87e6cafb7244d6a26069664f0fb980822cffdb6f5cfc7981c6befff9621b9585e3a75fd972e863916d6ce3178a45ef5e22cc68774a59655fc0fe4cfd720413a2bfa52552505a3c978e3a817aebfb8e75d01398be976e918ef407d8cceadbec4e82aced91723709e8216096267fd22ccc37300d3892710687b9e2762cfee768ddcc8ad1dd2b60479c04050d35113a5b5d05e865f97c623bb386daae286c68854a16485ee772254225865b4b3829d5c95d5ee7e3273736539f26b3e2d54c5a61772dbba5741e9c10c972a199aa470c938f353694a579a80d1a5132722bbb69e116872e331041878a7e97d2f50695b91b8c1afe16ec0ca596f042278e0645df626122ea615a725b0d11992a1a7a96cea3d7d7f82d496d9cff0d3cf3ffce302c5f8d0d055a48724da1b7f0fc83f89cfe5e9a630f954c70329afe988c065bf3958afcfc430b4872b6c677aee93a1105f6d69c6ece91452c47fc740badb59743eb1dbc84cc8d2a7292c24f06084522dc88a72fdc85321caf84911f889086a2a337a96987a6ddc30f414dfe096daf33f90fe2cd40af05187ea8fbb91fb53d80dd038b6993417e5f720ad63e0efd615a0f56c078ef5762ace52b7b692560b08b472c39f3f0f70c6972a88ff8f31caba28687ca351c07b1878161ce424491ccfc5f68a85dfca6bda172613ba4cf8d23d9e810ed26b4aa626eef14a13854ec01986206a23428bb073b72c907ea4dd06110524db4f1c5b29977d3fa254d3c8e621550f666751e3a3bc0721e5818ca31d7f8f2fb24f38cafad4d0f2d116a8ea8d7995baacdcb302a038a1015d8795bb35d18d105427acabf0b43e7ca0aa5f764b460966601b6fae30946724fd0f4db18ee97527d00791c5a90789b372bc6dc6c9b2d94acbbb5d171b5da07de81ab4df017a985b6fe0325fd1c1687f2869089bf221ff8e01b0fdc80aef470be9ab76a40fafa343f1bf57ab3b26f8a0f71c640b9435d6fc1ad02a89338c272492a7a4ebcdd3a9fb7e59786636f397f10897e5ce504cc2f3cac31c4183a180edddead382517626fd5b1ddbef7ed77c544b31d475a8a38accb2a314020910958f99d1a3e001809d276bb06bdae1beec881bf2d30c718348fe8e384f5d5bb2b564fdc487394e5f8bbf684e54536dcdcdd57eccb3406317effe6fe1e95093832024665336150a09ac485557492dd2e526b1edaf74e1bc52a6ea1826a434273e014a9755e0ecacc0ea5b0af210a211ba9c59e1aaf279daa133f37d8374b52bdc772f0edcda5caaba87bced2b5873037dd716cd638b4dc194bd5908c1c05ea2f9610ca561cd0a46fbda881af69929d0d88b15e37c51bb9f4966d793bdac87e53b972360c13a22666761f279d8a23aae3c179e996b388479941bcccc8d0333e09ea290e769e444a5aa3be28b995f964c9e325bd7bebdab2fd486b8a83efff92ab8c7221d6037885f7fce6bea8d61588c7bce654fda1487c7800d2d87909f98b588e7284fe295265802ce4fdcdd875c056eaab9fb53b030a7fe49be35e66a471a12e72f66d86106e8e55943139ade480fffdf2f8619810c872a8ed6b3554394dc01572dc519836a7955f6c57b2554c740b974bcefe0522b7c35e7c957adb666540dd724ad887689dc7489c47f11a7c69938474e1ce88cc4e79bd86ac2ce2d21d353a72027f44df6dd61c7c352e40c0956dbff0ef3524e68554e26c725cfa5df83f43728d0926a16c82124ee13e8dc21042194336fde722782c9e21e0e66c134c77b8254229bc346339c85598366153615e2acf1ebd663ac1fccd0ad75279327dd730440fc1aa88ef31c8e072d8e04d4baa72396afd31fd17943f65d13389c0cd6cee723a01bcb42701ce5076fde2dce62c06478ff50f7d18c2ab32324a3f9285608e459e126318af3550cdef662148715fb61ecacc80511be26b03a8a56320fb6580720413fd4688b25200daf73e65677eff722669bca4e1afd0945518cc715f4d1672b04298432ecc0277af984d104a9583eceb0016bcdc1bc0f9a0a694e1d136662582064583bbe3e7a5661918d11f051e9f12af43f1838dabf46b5ea1fcdacf06680d60644ea6ee3d609f534d4783bc33967bef23fa44222c6ede414fffedbadd5ba24a01fd5551f9b1ae140cc3f85752b9915ab281ac272666d7d4d479b083e372922495f980e7c8c7d9c54cb9d1f33d403934d5b14afa3b538da6ac2acfcb5c1bf120d12efef098567b27023b0e4753e5076ed15b4d57d37f23c4b1e712a23a72096146371984f6c28becf9cfd2df113ec7ec58f11f64c97de6fb4098da3d2d72df31ddc2b31b4bcc452ebc25f3fa916737ac77f902ed550cd6a7410acf5c413663b5cd6e4e90714bb5d27917a1fdf00cdfbc6a2296e4adafe4ff896a973efb4d95b89af1b6d18de0334ef24fff1af1ea5a50281b18f1a32802f89af7dedf127258eac97f6462430e3f5ddfee7f1181e241697b101ef351fd8de02a43a92bc772bd7cb1ef80ccddb8cb7a09fadda90f57e6dfb16e05be2c739473f01a9037d3005097a37dc19be8dc6445911afe281cc8f0df043aa8696e2590049d442891026d5d30bef5b04e040d9484402979bfadc336b1854b2720608f1e9a010f5455c367fb9c89b7f06980d7d3605f05367e8c9b3236aae6425c11cff20bee245d12787296d261556bf81013fe5808be7f00804109f5ac92b11af62723c31a6a62e69d1c515207755399c11f6648c267e3e7116b38d4601ef3490897273c1244875d2835f0e374793804c4529912715d618d07b5626a266a3743bb266323e77a45b524722c907277d5cd12ca12ba7c32b230448aaf9d627296f9c3f149f69c9087499a0d77c8e9e7e73a537b95a11770b5d689c22097d26e001512ed53eb48c8982df3725b33352e48bf79745c5f2a53f5bb133177793549c9bd51d02aeba4362b977038a3b60b893eca5dee2c21aecd087fee3e3e2bf6f3491ee5fb573a70f8ab3cfa72cbfb798310fc36322baab875084d22340c1e63db7f47c984d20f5abe15192e680ccbc15226093133c302ba2f3a7d6ae39048e440716ab5a61fdd820b63ad3a44bed8c71b71f524482b898931a23e6be62de0de44d5a2e1880bf5ac7a5bd3a034c4f8d5095ca33986d44e2f8714fd630b5cc290dbefba356cc7d8e2491b2733729e4153b28d25e8ac558d4f64ee63c85d04708a28b6eceaaabe4e58dc978da939b99e944de1b8e79c77c04a38a325eb1a33d1145e47fcf930f208585722641a728d282b0103a1adf049a3f6108a53a1b4bd0889c2f54e82315d2416150e93f5724b5853fb42be4fc3b83474fc9381f7eef81cd258d8f5236dac53a4c7b949e772714adfdf9300b6df7ec7134e1180e167d274eac960c221e9db5ac341bda0307275bdc205b785c4e21a0351d689087449cf906bacfca40c338c77bc6e83ac6172f3f3626b967e7af985155fde05a258da5b08297340a3b3d2ee4e1ae70b3b2f7247d0cfd0c2063ecdc772d96fd580f7a8104028889b79f024f6a927c40eca3f721ba74c4a38ba6016a9ba45c5c9954e173daebb46b6fca82d4314d73ef531ed72f0ddc665c69d54e775d7841dc73118d680ada0686434f2183f0f69fbc983d872445f9cc9b141f732b6d6181f61a5faffae805d62ddf25e10569b71684f54d36ce977a404ade197e0eb3a47bbef246a7750e29b9cd1b072b4fd60b88acdae4e72769690f105c2209629ff4ce2167459a305085e6a16d1849b7d1d7bc43c5737352c8259f29d10ba29f1235fc910fea32b5c75f0441e98375cb1428a98693689248ae8c6cabe03b67767f254c0c33ce59ba1de5dae81aecca2174af9c38c9c2472f663fccf76341e379f1de102e5828d05b4d9f6ff71e4425328782e1064c75f5a3830fb1db27d2fe2736afeb024cfb88561815bfeafd4f67fee56276d05dc046decd0be95b8c25c676b30ac1eafa3492738b97a08a441766b4a6f69d6554ccd7234bec167d49198ca41da0e1707295c92d926caafcc234bf6946821846b98f37031541ab341bc6cdf833e835ed38b258acea04f9c21c68c525a22ae9192022672aa06e7d74be63dae0771beb258ffba86aa63dbecbbddd992fe2a872e89495e72b9323036b1ab5d62031858879bd14944ddc4a355c405006564ce0bc1c5345472ed8bd9b593a5e721eb5f3ebc54158e9eba7bab6aaf8d0091cd8e3aa2eacb4e72e0325c2121273306b9691969bac640196d405fb0947700678b99a3ba93ddb7304a9c9b6fb663683ce7a538ad3dc1632991eca1f35b0e101755dec92e1a57a01391a34232b02f9e6ac85af411935a436eec8b679106fb395146cf8a37ff542172321b28dbddc136329950dd53372fb7bb74d390f4a3c38ce56ca77c317f155872149ccfa23fc15f62bd47cbeaa6d7117bf1c7cd1ce8bd9ae7b3e7ac33e4a3fa0e9f9c0671884723cfa19143171c83bc1899fbf825841da0eb808fa5aac4a4fa72068744f2f07cdeadc55ca84a865edd6f6cf43359db64953092f38d4f0a84ba24c98d88a0732cf2cfcfc37fbc0392cb9fbb16fc905a836e21c589a5fbc433237227f8182d69347d2b3974004af748fd6274452616276749773f4fc5033f2bf17264ee9aa15c12f62634bf612597f5b820431373b4fddfdc12ad447b93d441dc72e807c129f22e77d9012bfea39c53078458dad47d28c45700d7766b68e8d87c72ce1995e9faab0a1091cc092d1881f59f829d598c9c8d8de6537fd7a4826e2b22e25e54ea968b49a1d5bac2d5c916bf21abe182777058849b9ad434b67e727b72924f03ca6edc9eedd6f23d89dd4e8657b19732bf46ab9692151777886b1be62d269b178247a6d7d1bd7510ce433f868355f653b5660cfabcae260bcab9c41346f6233b2f84495c5ad0c8b6981085cb95069808eef49e121d6ad36462724737721e49b00206d7a87dc308886309ba6ffd82435d20fac616b7357a2a28d5b0177212bec9db28bc550bed9748b761032809f639d797fafbd4afd6d56a2c2cf2a1282012413f190daefb77bfdcc23fa6b714977c3be564907bc7ad4be49a438734723f7d9c542d2f1e7dda371b39403f13e32852c779631e158892e8fb0960afdb72f107bfaab3da9a0ce521ab706911b8d29cc8ded9a178be432813f36040bc267275b63afb43cc78559f5dd90fa848a9cd6a62cac1b33b54b0b723db878235f4564356faafedd7581511027e598b813f6fb7a9a0b4c2acfc71c7584361328d5372fda3eed16643657570d9b3ff841f58c292343b16db4bacd4a194a1c6434a5e726b5e31167e03c17bb7d28a2a46290c2a2b556dad79cc63aef6c1cf62c60dc172f3d52b67d0b862bb737079e6c1e28fc0d280d16b09c2b92765da279de874816c4ba3fa645a8886d5663bd9075d8d7ac6c0d15fd9ab1bf00223898162f8df8772aa3a52e921192aac41f6fef3f42889a7bdf35a36ed206500e6c634456a58e41107ed202f3dfbff5c8f8822ead6ed8a3081131eb3f549ac78ef7d09190fd6c072df1227876ecddb34ae3418d557ea98a4b4f5ea77de300fa4d359cb9305077d723d18056ac2f4dfa79e6b7478edcb2c33da45a4da0a9e11effdf335babb8b250b0eedfa056525064bb1fd15a9e5c77fdc59788bad0e46a9dcc5924e7090dc4a32256c0007f856df2b75b8c8e3f3097a1a78dc61930f2d2583c7f555cfd4013b47db0044a3ffbb1f17b1eabe904c869d995548cc4222dc396953ae68c7df9aa57238d25342b2a86d0bc4f3a18eeb628a14f18731712738f21dec2b21e925f5eb35b57f312303c6762983b2f86d85ce39c7f12d51adcbb2593a273b5a38376aea72b4af79418a929ad51cf4591daa7d7e3883e303f3fea6980602614a07f4971e72ca4228f166c9928808e1278c5bf3b9c9cd72f2099cd46b01daa34b598f445c726ae10021f8c70752bf0116f794beba6d86809b9d748c3a74137a844b8f09e97267655c4b0275f0fb83c56171b1a218ae1e6a2a7deb7a60a448044d4039528155e5b6965b98e7785df62bd5f0878c77879c20720c7e1f2537ba291f5844940a72d8c36a77efe48251d3f71cfdbba897ff74db12f4285bdcdd714e072b44806f463720f5a5c3545258ba2e8a87a0056cb0ec2a425b4f71764b792e40c8adeaac728e1d9480b2cfe32f2d0d4954f111cdfb44388a7579210082a912faf2bac20e724459614ec73d4fe82e056b4dc6bbe384e3ff0f746df6f5694197b2b5f626c572350bc6de118e85c85793872b4b5e3e121ac46b88d150b1a43ccb17a99559a027efa0c907d60bbe8b077c212db77fd022a17776877e6603ff11e53fc6c7440372c2b3cabc16d124c02caa8aec8314aafcefa3f5633632e28c8856b9a4a0be7c21f8897172bca4b7f6fbdaeb46b5bc6e36d5a9b2ba9ec817320a09df2f105a0b7246dfebfc9a7bfebad64bdd5a33a9b769793fab34aa29387c1b9175f817f6bb3d7abb7607fcaec358e9aa6177d6214e87c34582e125ec8fd1b34128e70f4d906f6cec76ddd480c49e4690999029b77e46561039b8d3d0968a0fa717cca74d1772767a3d07ba96618887f89448797129f8cc6d8a555e3a028bed5ffdfa8651c80268614e5fd2935ae5267aabd372abbf5181a6a837aa159210b65d673f5b3727723f69fbce445d40616fccd8b7c6f8dad7eb08dae9a5547f994d5385918ef29372a13e1d3accde30cc913cf262c14af8779c89b44357c860374b50c0e5161b417245517f55b3ec5cfce0130d1f2092efbed644f17dda1f5f6c6a40ef3cd5183f72ca7342091ca17558d3280485f22f9d20135f375bfe6a5ce1fdc0db1dcd2e937224e02842bc0d8c3602e8fed385d1647d2365f4f923026f59aafe71fc20c2b367daffcbd6a7daddbc9e61ead1abff66a71a98a982d4c2ffafdce046fa14784772b4bc039b747aadab861fab86b8c38cb6ae13b824a5f7b498d7c928d006931e72a56632e193b37dbcdadd4ae36736c1d15f8c7ea6f7c3f2717ba0a939a20078726d4720823bce750d4b52e6c636ca03511ae2213c9fcd398aca89dba1ea2e97724dba658e4ca135b9ec3e9ae8f006ab8c16641400cebdb86b55322ddd7b43fe251f8853b92bf0e3bdb263f2649c27201dc33f251f0b2f42b79157484c4f07fb725ccc4314b186de7630ae7926e1bce94d5cd7a4a5a3998c28fde060988d5a9a726b76a63dc8ddba62c5c98ea0df2a7c79e4cdbc75576cd986534023ff5931d972ac0cac2aa07ae7ee6e53a83ea191d116da7e0d802cf60f95ecfe452d6f8756727979a37e5c3e87e29a32ff01381fde9b4247e7e76b8b45f2e29e0d20c4fab14295e05548f5edbe55d502fedc97a50c7e30e24baf1e217409731ea5cb74111365439fa81d1ad7f807e8ce978dd8d494d699481bb147a1cb9620a23805f62a4772ddd027c484fb01a51ab4a3d2f0161f7e549762c261c18f3442cbf510d36d1f475012ca88a28145f222078035b47be76306c5bea874a2ff9807e491390bf5a24edc56a278b00e3e165722feb44edf717be7b6526bc4c0fce7403cbff3f8ce8772a338e4edabba64f6adeceda2e52ddc1f3b34b739add89fc5175345ccafa171718ba870bcec81168d2b44ed4361422b73a430388313e91dce5d4f1678c6c3c92ca92df6051f3f1c31af35b1777bdd62092583446bd84eb905ba564f91cf826f7216965d385baba0988e13c30138b9afe28bd58a0db94ce640890dc2ad878b7572961e6e9e89b8707d87b3bf8106503d1ee3b5a50956a70b4f53f495c963aa3e71b3b1734644342f50f5105700ae114fbb733d5f5817634c930dfad6eb6a3a927265bded47eb843f1691069792eca375076fd1e495da8fe08f0b246155a5967d105056ea116f07c6da5d31d5da8d2d38c06c86065100e79d584c22f986092c0135c1589c202859e35b449a6be43995b6f4c143a0ddda8c560ad2f8aa3a1ad3a472a154cb7af514938a831194f2067d698864d104f2c7d42762d111e9975c835e59958454f2c98998c540b7c6787ec9e950e521c6556ca739283b4e5f3cd4a8e2723012b48bf2fcf012c9e22674256ccb041b61517c60d13c144db2f5b3e7ca3e7243d960c908844baffda07ef9e3926bb14e3e5565929fc81be51b1bba04e2d572066d8e951adf4e1b47212eafa51e171aec412c6005423dc4d391904efca2f6648a30177f213ba8c910feb38660c6fee82d729a419bdb05789ce17044a367a8722801bd9fe70b18e9a3b7a999709a88f08c7e1c66b300a74fc92ad73c4842c472b3e7e1de769e1112b6c7d32233227ffdf9f5be886530e8642dfbc0934a3b43340eee7aecc38aee9dfdf3e118c3e6d3ac754d112ed4dd0a8139a28da7ba3866724afb7e8f3ad3631e373186be9a24933d08a3787381e146db95899595dbb4167290bdbef09e0156d42a1e9a942b863a7f6023e239b76fab9ea70388326ec5317288475f746a902a7ec859e271e0316998c1bd7de75bbd2326439c6c210857235ff967ae4d55e5638e29d594f703a5a5ceadd4f1a1eecefd7bc7fee510a15c747258874b3ce3ee314e6435db194142cfd4eecc93658fee5c6d2a89771bf3698757297d71f8dbc889b967210aab7a283007395f62ae31da71476803108a8697d472b16ea1e4040c66439c406500adcca541d32ff9e384250e6bc0ed95f32aaaa337528f24f03b21c667b7e652b20f368e4930a77b0d07081728eb64c2fe469adf72d19b99219a747463e3bc46d0f51dd0144da7be4334778b7d7773636b979d9772654da8f7ffdcbd10aa32d3d6eb2420daabcaa1761db839a40feb39166a9ee31403a59218bb7d86cfa5b8b46c68258911d91c4ca0bcbaae2cdda55204f641547231b7e81df094a5d3ca31dfdf38df325483340b92a1e265aa0e08e3cbe40b9272ec6a3f68f2c7c30df4ccb9477bae28ea0f9a281e03a40b86862064e5a4aba429c42a2dfe4dc4d0c942c61c55ad31b1cd78bb2cadefee18cf169e961bb8740763148fb85d23798afaca3e47ea75559408d130589a3725012c9a4c59abd54aa672dd35e3f0cd0a8562754e5ce7647ac8d3909192aeeddeb9743f2b9bcc5cdbd047553ee621a91ac6ebb48fa481f6e942cb44a34c7851271ee9564370f97e456f72e6a987e82588d7ec0b9f4119268dd03ca39ee66c41a612e3f76e403da87ee272f3e6a9e33d25c59c2518d8aa0a564c8de98886bd54b3f1713b657484826cb7123b596f7b88cf687146d8a675b8ec70fef9d14980009f293c9bbea81c97295d37705fc133851ed2a007d563dfd7ac80f39e6476c0f54a81aa0cbbee0dd5d5f82e87833fba80907f430462e398fd465305cbf1f577fbfbd4dc22783fd62a9d3666804c47de224592de0ccb7e48f2ca37d44102bc5dfaad3742ce1c4bdffe83ec727ce37c096ca7eea9d1fdd427bb0e2d1eea8a9b4157e833c602327949d8dc4d72053e8497d534d15bde4a65f986b244b3268ba0fe60bc00c7e7a048b171cd334cc585464ddee91fd084d0aba23d46a7619d9771657580eace8c8c32b5ffa33b72b0f3e6481022939e7781f3edb64ef46b7c8a7009a46af3503ffc9786b278aa42af8c523b1d95e4d07d56b59a4caf92e570092c2fa85418447933bca0bb49ef725bfa046eebe77a890822f0cb6497c7fef813878ec41d0a3142082ed7b16a1e723a5fc28d0fd6e1caf8bce0ac81b6b932ac92200369f8af32ddb9a78065a5dc364fc66b55abbc35b76fc61fe13fdf05c55fb1c5c55588fcae90dda140aebe7d6526a5ad28ac240ff5e7c4967ae5000352d21a9ffb3a3e94133eaa8cdded5a0062e62a29c90daacf46461eb80f401ebcbd8a419feb5b1ef9c83ae003be3986ef725077857a12f97d1f1ed10e7235e37e4c6c3fbfe8e53894c12700c97b6b736e400d983be38491c5685f7ed631126b2103b3c07d812f098f11eb900ae35358a572b527cabced6aecf09b19b9d82a8a75f0e5c973ad7010c32547927fc4416e480493fabe96117edf717174f6261b73b57701d379052a04376f70832cfdbfa08872b83edea45cf0cff0d23178042ecfcbf8410890ebe674ad13ad96fbbb2369f24a3b3d0c58ab67bbc6162204d00b1f8079ed8428357fa7162c6c704bcd99bef76aca30d3a92b03ef41be4aba8a2cb331991a56f27fe5b052903c12ad6b5e497064a327a719ff1a026424b12094dba89a4536491e811236a116984638d4d0cb7472df333ce159328f1d4f27369552b2ccf23e0fd591242e438f95cb09ec0cf4da72d273ce6ceee921c1fa574c9a43f8889c3254c958e30a1c0e15fec65695c04d72b29dad8d41c8a6be293cc477cd9b3c2a45365df16c7315a871fc510ed7c6e849e1bc0ac00dfefeb21b52542b1791b73d681619c8eddb83863cf91f98fa5b0c72114ef0a7316686b42af3204774cc49e35bdcfe20ea7774eb1104bbfd3489da720de68d304335947bcc515460719c671083ba0c6b584ae1cfddf02208f978507282b620c14704895ff134c48822b5fdd0c0dc56db23187323732f9a8a74b53f7269c27c6b77903616ffad6c431513ba6ce5fbab60eb99eee5228c2dfc6c0c89720a12746f4add207658174710b3bdf0ad9ef52caec20dc6521c6af3c5f7c02a5c006b6017c1151b004e040ff5c1e559f532790c48a6148610b72a387c48e258728b8759b3652db4e903276bbd2f3cc0fb7c672d7dbfab694fc0ea427d71c1cc72617883fb3d1f05c6788a2065497cc27464e5c74cfb74c50402f8ab61a3dc2572024255ad5ca463e585e3ef8666e8cb3b50142d14424c3bc4817328e32458a51f46dad059e084398fad7f91d8974d3dbcc220b0be595310a5d0d49ef406ec81698ccc941d3a8b94f8debc71f20e8fd28df20c52789db839f935310e9e72990a54f72cfde00b899fe7867bca3b8d22fbaa332fbe13a693b2c78bf0ba9fe6e2c412ba2bcad4f0ee4d6ced638b102ec14a2eeb59e40353fdaeca8d7674cf0d59d2721c5c695d914edd9ff9a20fc2b4d7aabd2b2977788cdb90bcc94801f4170fa772dd77b8b5f0815b4224cba7cec139a19f5d3e2aefd76cbded63fedd88f28d917254652add5615003f04f3080cf80c0afa3419bc9b3faa5f89a9bad85be944d55f9274b4d074d89b2b1e6c0fa6d2771cc80fd9dcccff187416add9a9a04a7a571b75eb48bf8b0c8be1d8c6072c5c226ce096f019aa126b85d46728f62e9ec2de725f48b5864c46c773ed208398fa77cca0fe620b39be18ae809d6945ece70cf54ddd7aa8013ba1847d0697791efe66cdf7d85c40ad9bbb1df6278450f2a568307288b7e8c6386a9e45fe37d38b1f87f34a9fc5bf7cba257b1e5ddccb73beeea95682d3ecc72dd9b7f694104e96a976f197647cc3259f3642ba870bc9c219ef09724fd557e10f10e06246f559c5c97e4687e04c8d96b50e239b36dea8338bf644724acfdcfccd858630224fd59f52b7850f4fc5166e45daa883b657013c168ada72076c92a33dfc28feb25290e9631be9d2c1c62d496c362700a8e5a10eb8f2ae25106556e964e6d1096aaa163c05e028cfb827af61baf95a863d3e3e2e84618a720cf0d4aca445b3b1506df9a955ed27349d77ef608690b024e07c14a6e571607208f27930ce11ea16f9f3fc3ae30ec58e873c8d5a334033409a517d326555d1725f86c7732d0c04eccf1dcb9d0a7783b5992575e97278af56b16c72a5ac2a1172233e11fc9824bf27073e9506643d7d4abef186a5b3daf9cf9a40f03070219d3e0cfbec5416c398cb37b1e3dad963d4aa0f92b2b1c2239d898b49866382de6f72068653a3e30d9ad1934dc3433a408ab05c4a84a4696a4f4909536c0f36d48872e4a37385db69b9fc063a7486cf60e4e990b5f90dd65885a2f92ed511b70b1472812c1b76f30cb84a8131dd6a3c0b4b33d682860968fc74b5704f5af72aa53e58029a6b9cde8f2b4499aa8c8d69b3ca30caaf54d624c2f29bbac735817fb641724bdb86aca0ff2df7c9fac0c78137e71f5062c3dd644e5b21779ad158808d37721674ea78e57cd201f17521277199c089748e5808829989537017a14548f9a27270570cf4d781f5fb11f115a944854263a1f28e60a8911eeee6f3fb627a7a255e0e371d59ba78a32b70969133e977122d24d2c94db5e20cf162a295f4525e2372d33907913eafd3d6df191be4a700b9582023a5538b440704a9be11926b4671724c5c0bc3fc887f8040948a39acf2c8ce055e539029112cc2b8f05bc278b28972bb08801433c573e2a19894f9b689dfd327903f018fec403f582263a7fe368c727b9fd2df31da35abc812a9dfffb9f2e03b4268785639b0980c86b8b87e480c3969da20b210cc11c1c2b5b645362b3a293fa3fa256b4e4c10f9f4e9a8e92eb87206edb49716ddc636949eb465963aa04e4b2f24a5087ff349d5609a8013b8bb72edaf6f74cff681327d608c8724694991817a2059bbefd1e1f0c3df9803a4d77294d6d73195634faaf1d4168760d8eaa3d56ee20cc13cf51a090753396b4f96723266be7325446e0a8ad9ef65c35e6bd2e2ba9e449b2bb201a0cbd9c1f003a75c043538081bed2d88361f0bc0f5ad412b103540ad346242f86c1274ac668bf372d5440dc2fc5a786533694e7231b8ba8b2451f8b642963429a1ea90b4ca058856708f0bc85c6d57693f9bf2542a6892f659eda06c13d2d517b057f7ae58e71824920c607ddc3935e0272508e4033e5f6aaca1b865a50ca3f67cca82fd12f1ae72beee9bc32ce2f8b30fa907c32cd8d5d8264331e270d666745e20a33a0fbcf172137b455339a5f8dec9efab249119f02b10b4dba677ec2f7478dbbe2e25973a72b6d3351b9fcc7506c1f7f1482077ae726e816c2176b77bb40efe54bdbcc2b013d7a3a523ac04a865b38ddf47328d8b33292b975ed380b86eb7dd284e2b8329729d2b5594b7ca2be8d96529b891439bc15706ff46bdf026590fcd2b42f0a88363056450b1f4d7b9a685c38dee6c80bfa058511aa9d1c03cd4c9cac7f31f893e72ed227a1dc5d307871fdba44758413cff6d4867331b524ee58f30cbcb9c2cf872ceee61b9ba09ec37ac63bf00d0c65ca1054ed33132a574f4fb46e89fd0a57c72296f45b1807af7cab8d86cb604b858a253373dad51503aed15f28629615a49568c47a399c40e492dabbe9ce5241b11d91ad1cf95951166b2a7502bc61968547278d9bd0208429b8a8e87c53e0a0c3ec0e09931f7d5e020d366b2ed69790e4116721a36fe400d4625295bfd09d3e80504480ec94ac1374230521c2fb42129b572d46755c83a1ddad73e7a767640afd0e2814c83c1d4cf19eadb351a27d6224d43a58a52426a79da3075269c9915a54534bef81f5eebdd20031ebc35c414980d72ed06ed1562953978544eb2b90c5024374b477b6097ca42d45cc2e14b4ef60172ebaaff74289e258ff93a3eb4f2e7c4de4d63d956d257c957ccd56086bb92717256ae16682fa7e8599dfe69be541e9f690f09b4b9e4049da85e047ff0bef200729bc650c1932d477145d902320a1265a3b085b5361fe7fe1d2011cde1acbc675a76473f2860360e68204a662a8d95b06c29bef5e141808b66e72e70a9097f0d63eb930e0dbed6a46c75581a86694fb5e2a74704163008c27792c6a70498ff3272f57e85b18619425ba7e99355db0516bf1d8be346e0a722e4240f90802195525af77ee9193011a85106cbd20b67c1c2938f6956de78e9b9824079e7726e58e2193bf4833b89a10336c1d1e246a506c136319c2e61b50df414cb59e6c5e01037729256b50e94cb767bde6ef215f329294132dc85a4529dbe782194c933206fb110e075f31befb16848e5b7e242e58d6a9d52374b134f27b0ab60c7aefa84980571cb329f7e15cf97bb3593fb9ea0af0a207187ef82c2e4f65b13512b6d7348d672ee30f4227f20d856f9e3a0c8519227ef01cf323084308c6be029d3496bf7732d406f3f251f78f96f6b6a690cb50940311c91728a6757c74b8a5dc9ce6ac6057200873768f4c24cafc46e7ac2704994871e7742b3d9ee09d14c5009dd60ab6d724a6aaf3043930e6d0addf36a68de950c7bed53d8e7b3a4db2716729575931f1fa477265d11d973697c86f6d4668f13cf8877a2b3506e7c4e5c8075ad009bf0723ed56c6a84fbe4cbb47cb904fc646f2557c6147fbf29223acd8ecb8f49f55e720ce0627c42085616dbe65893d29505e87a22db0aebef7dc6a09c0dc99dbd8572939a1dbb0935da9c77ae6617d5137f244c5242c41b827428240d9a18c0107e720c02aea0aec58e786af078641af61274d76f7e0982311cd0c07a38186d853772df25b87b129cfde136a90c5b812d8b0d4e49eb87e29b183409abbee3cf455a3ab60746ea0c2e6485aebf55f0b90ce62dc1ff893db8f0215ecdfe91b34c3fc8195706c4cb148bd2e332ee3302e1f110ced1299e4b1bf2f0bbbef3db6d44247f0c1856d51a983ddf16ef3b51ce989b63662b7bf8ba84ff18c407201783247072723c1d66fe21cf6333ad0e881aea618aa4dc5c36de3e1b544f0bab85c5f2ea593f9ea879c93f439b22e3887322b0cb43c3c7604455a7f263172a6fee417f4e1f58d3210190957d763156c5a16ec3729cabb4bb1f09b3396db5257a709428bf9a720a786ec57f83195631f6fdd0a46aa511da05b7a97f6fb9dace31a1c9ab273d04e4658c0b54c9af1daa37c522362013e5db6673f81c301a3986c1d40988be88021f8ad1398fe7e153d7806869d8c05cc2eaf04d5d6b010bfa07da55b0694d176d951ca055bbddb05ef8d1869c865e2170d3b09895b28f4c15ea59bd2f8b2ebc0bcff37e72b20b8c0d860dd794eeafe92d15b5afd1f66d26bddb4cd29e779f8101bdb3e3a2c1f3203db234d186fac0034950bab5218eb92b151514e655df03423c2fda73954ed84361f89ef30bd7edc369c74f3af1762a998b9614d26000cfce72635e3aa70391193979c405fd2d44c202c70d7194ce351c24745f417f338dfc7251141d57745acce4190ea413abc69315ac84c8fc6241b3a7176d202f14105b724652ee14fc11970fd374c52c4e5b758d543919d60c5a28577d9294e43bc1a772b7e6578de8a760dc08ffb0ce036bf5c7338991adfce589ed48d27ec387afb072096aead1b9a58ade97b0b60a9116350c310b8156cbfe2a2dd26c2ceba4fa2e28e26c1da7166a4d3a6f20ab13b361f2deae355da0f3b7961f940f6b6210f18372f596a3f062bd19648dd965bf20aa95b273c34ac11b38aca671c0a29e401f565353a780e0d5fe354eb687d68d15ace98e7514246542b1fce669307c387ba33972edc034227accaa343f3607705b3079d9131eb087aca2cc1268f999c49aab1f72c3c33a8bfcfb5ccae13c4cde30e9c13f970cc18852c823f330a9e96c5c5c5672752e5d4a80c83ff5d65e57ed7b00844cb8ca82ffb992fed44ea91a3477d36521bdd56aa670be5cea3142c75f2011c41f1685f8b325303f5ea4fb67d6d132e572d5bc6d0b61aac56db79a70e26089e3d7c6e9fb1ab1c5362d22baeb962006cd723b200eacf2ceabfe976b5d2ac5867e1289f7df0a498b24895ee430e533355b72c34c31eece112c2ddd7af859cea00d5ecf4dd18d680fc327284e0994aa8a2c721585e4a5ae1637879e6dfe1e6fbc1df891af206a4421ef2d2bb5063fc7ffb47237b8d7547e091d1be01fbe8f67f5117327a9baba7271ff849fb15d6cec49e74f72593260fa4aeeedee040bb9fbb4c4ce0c574f96af370132967a735fb9700772f5fe1188cb43df0e3b2be8c4463b80030427da7a0d8528ab0aa765eda3d0a3723b6534f074ad9aaf2f1c57fa496e77ad5a5476674d3bba1d3d79b6abb65a9e521fe5db844b3ca2b1b5d8b21cfd0c610232b17c07aed22464c2c989b2762e737219c02aedfa67bb1a78f8a158cd19a7ec7c291cc6857c7436c3bbcbe48a4b1d72fbcec0c3003def39c8e2118bd866d7b55778642d219a1a17a0888d119abd063edc355cc3b89b75a230df7736f5df8323e70c0c240d65a53ebf74e0f45a9d1a720d4d6a698cfbc5c12ce602346b6eb349a15290ab397893585c9dbd4cfc9471722a872a2b9e649a2f4d9813c8528643d054aa5bf32eb676d111e7f34c27816b2cc3e509a986034c5f32285d5b1f85d5f056a7e87febd156dc48342aac250c0d72b5dd0028b06ec9d7dc3d358c7bf0b43d650f8ad6106272e3cd262a5bb326ac601af1cd868d4ebdf647f44d63544e47710148feb791fd8965de87b22391fcd072dfc8d181db03bda6bc65a79f0b5d77ba2c3f414cf4a73363fa0874fc6946f572c5d5f823f94a803209018b4fd673604905d19050b9ca14f0e5cd44fa080f2a5690afea574c19fe59b419eb555c512a8bc1488bb19d2588df8120c5b8ece2331a0cf6043458be6338d715f1d73a11b2593691b0f52136b0ef0a79b509ff781060fb99d97b2d3a8a71497e6653d99c0390b64c1f387a06884c71b9fbe688be0d4c77cfdd55d3efd077bc2b5116bbbc76ddcfae94814d478d01d8b5193cd5ebbe7253019dffbd09c4be412225dd00e81d19786a6a40cfe604e3e99517d7519fa42360411324499c8f9b65b75547ee015da53606e0e225923e54416c6a845fc6a801bccfb75d79e4a3dbd7b326c97a22c6755ceca1d0383a0e10438cf3a9b910b772bf0c7ec2e7b4754064161f655d2eca531e18f1fa323eda8d09a6f187b8075f1943dd0120a45b2eff341d1e07ead87d638e336ab65e2565a2cfd48c167340db72dc7b713537a31c12a2ee17598dcaf1e85338d606e5b1392cc5160f691f599a729ec12098de4bf2485692cb6b23bcf0f7ff28ecb65f846451f6065309af545472b686e55f80db726f9b04b0774ac451760148c488f26f3d56a5a2a1eb7b17ca72ec7ffd7201d011a240d8a01c3cf08dc11370a06558276435e380f1df282b5e723ef5746b565a5178b0507720945ad8a7153af3db7958de02e644159052b8e872e71c9d72e82286bb4ab60931bd7cf734d33e97f8b60cc65a843d828991222072c2dcb45d0e6a4243df27ad8bbe8fbb9a45fc43cd12deb2a18692ff00bdff4b729a984f8b0d979675dc8dd53361b5982d6bce7b54df8da65621a4bc191b422872363fb69b1a25f8a8333829b1ed31e58a06349592c3c03f3088620ae98bcd8512133aa16abc36cb28cf39ed5412829b6d8aebedc7dff77eee5defc2e30476b972997aacf5b6aa4d24bc3c8159879af4162530a91c51c2d3e3a4348d12d6d32f72382f6ecba6ec61cf78349c79ec81552176f79bf95764bda81367020e5f4816720b07b8ca2df029d7990778635705a29cc1bf64484b0b5e8645bef014a110297206d951b22b376e5776618e96cb26cdd8df881d66ecfbe1d86858381ca919f172a03f0654938df6f761561e9eb82025ce45a87ff99e4824d86a4fd593e8dc9672ed9b52204c3a43e34ddf86d27887f25b4ec27d58372c0e318df7b663e918fe72109a9c5a58db492c53170e146ebd448b6503eb4a1333b134a6bd7a39dec9c61f2e7e96f129ae9a1ad77f13c03acb62d8f2feeb76bf24740dea4b66c31671a6218cf78a8c951306b786de59a6bb8732a6123a7fc125300718e45a1acf28e6ff7253fabfbe6dcb8a0cf9c449f487b5e7daf3994bcf9c1f9f80578e858b822b887262f33201b253feb6a09d6db390981e01f372f08c8de57663b841c4946e859e2181a5377604b88e7c2cb95dc05b2ae59beb7f52c16ccc10d322ed462aba6492722bae39f53b760b6f4b1d1864142312b504f63b8843963fdc136375ad68ecf41c26b6fbe0eef1e0fe6ab95aac3099d56b870f87a10f96da0a1230522d45b49872b6909100c8c22b9b4b2b60f3ec0da066fae10be31affd684094663c0bb8ee2722730f4c63643c5729f8f4f50faf35cde67d829dfc5ce07719fbd7605f0ffa87260da3ec22947137e1414eb2ae11d1312359736bbd06cf66159e4460d45e7387208825d49e53587f46cf7d3067b43792eb1c42eee2c015a741acffb0c2af8eb6e763e5cf09b60aa01768de1ad235272d786b088e9e70a5e02e4a30ddd88d812722899f130fdbecf5eb3c3c9ecbc09781bcba5bf5924765f21b8765399e977b33de23bb61ecbd0761f19832401fa8df3b94030b496e68dfa3f49d30fec287bd2547d9a49941234962a466e10586b492906e43319df0b8a4f67453083a1d3613372f3a6d5593f7a0fa2d5f83748d180904b561940a158cd199b87b1dcaafa7cfe721aa376e3c38f677e7b6640374eb01d2bb179fae9551065ad9b38ad41fa7e9372d0ec8804bea8ad5e5f1bada2f15572ad8dca57d1eee47666e7da37915fc4fd726243711c3f224182948f9ab452ee70886bc1c0e49ac14e1df6f871448ceced7240108d96dc1ef67f187ae6dcfb88b22e4ea1cbc995ca0bc5a7d184ac17c057725513e929518bcdafb4dced965115b04b75d3f17446dbdbb744d05bb119955372b6b818e2458c7664538cac944858be34ac8c4742d7be7dcd41d34c119e52b731dee71d9c6cd577744517eb2e5ee85e95d37ee16608eb5a97ea1042ce57551d7229038816dcd419b9232e719d3e5bcfcb474c0c143991e03dbb16630576a5cb7299211d99ac766e27161f98ef1f5c738b985edf7d00bcb1c2bc6f3e44b8227d3c6066a932b18262ce704eacb105564373ec290c615861eb925d0729ffa298ff5016e2e0fbb271a46b7b7b1cb38b3d92c90fa3a5edfc9b6a42ee3049478f408372a1a754741753ae9ec63bc02eae8d91be64c95571a0e16076bbcabccddd8edc2d77368b3153caca8c04e98905721af7f8d95a7351e997c8ebf03b6ab2460f2c72b7a7444349545a89c655aeaaffadb6f85d6b6594332745f9c560910ca3048064498d2b1fcdd415203ebbb4a784a75461d12e174a1fdbefb30ec55f7bf7823e2f113b0992493d4ffcfcd78bcf01772ac93abae12a1adc4867cd2e49900b7dd472d693b13cc522093f3cb02ce0e3fbd6e28d3d842abde7cc50116b968a87b47c032beebf4b2ebee4921ca7945fd2105baf996c919d85ee959778d202f86728c556f35e7a893e137dbdd8b25da8c88e287ad2f558a0c7da0888da86e49645e6ec72959a9d4224697c60280a43780258fa989b729874e24e389d03e844b2a21a98476b58efaeeb567aff7c7bae39fbacbff679f225fd1a88c2d5178938dc3fa9ba0edf8976ee642cda6817d3f6742dac2e818ef2a705e90df8591b58bcd5e1f4af081690895d5d0061e3aa647064d456bb39b83f4fe600e606f928f5811db238fe6d5499e4300d75b99407a7a25be7f21ec6ae7428d9d702dfeb007d937b843d88720b3532501aaf5a712bc6cdf8a67e42088ac6fb64abdaf2b23e48dbf1522e4e72ad35e633b74c05039b67c1142700a0b8d9d25f2ad62e66b1b4ed961fa478c9725c9796c753a7343efb23bd87b1b9f493e3cac7367fcff3aa19fe5db4ffd2177205d9c302b6bc416a4b7c85552ea0ceb5d5af99deeccdc85e78106f65dd46372c27e63f612161497f6f0568442ec599f4e444b18e0abe45d539a6a7f9fc9fb772b5d3c76d83d755a4936cf7b4d2ec6c23b3d42f959b25367e02276aa82d684a72e7c00e4ac70dbbe2264a4b15833ea65ff611e9cf1ecf6d1dce425c6a7859d2729a3d5107c3213f9ab68cbe26a6c939c4352edaa0c77869cbc99378d52a78657244ccd7225cc0c27305339f4ab6c06570e62429fa6cef0be140d5ce17094af03d977bfc3529254c03c344f9647cb89bd65e67b59c5e5c84c52ba339cb2ca6ff09a5aeff4dbae906f7506342027b677c0203401a64c52bccf5ffd358c99d80c472847705c4a259c5ffb0bb2cafc4a138ffd6a1810abb176e877a6782bd38b29a398af732fd438e583c602efb7b9a5bf78dc27b579f08dc8f2e2a21341628c92072f6a0f5b271e50a26fd4139ce183dbdf52fc7c75fd8aa700315d9a5c5065b6b111c3dbe4f40ad86407b1e8136bd1913bbf03f62248c15d4a9f5097a9e4335912d700dd3a0c99c86ab5ee5fa8c2d27caf921f130b4213a7bf306dd7fa1e4776d7290732764735ecea34f2e6167e1a08aabceab663e4ae61c94aaef7eccfbc5ed36c342b5180baeee7344acaba58befbb745964ac781f5559c3a95c66cfeb4e7a72772d2c898b2248189048d3cace5a8b4f0179f88f6f8ce271dfb08d1607b6387254043d40c8f64a2f4f752e1c2dbb4be9616aab93f7e130971528b1829a77663804a98b9480dd1058fc911add76e09a62d42707ae5dfebd1528a52c68dcfa081671860b17bb3a00bc9aabd36cec1a916e51ef3c2eebc84b8e6b65d58fc22de672cfdef0bd16e1ad16e549021d9de6c36a032add0cdbbb29660618fd087c3e9521587079467c89e7067033df8c2c111018c417183b2896424af5b0a59815ff137269f993567298a9d96e67822bf9334ec30202b392547919787f3bc2ae2cf13d2591df9120a7c70bf5f13e60bfcd9bb0b41e0a630858891e58fb86f7130befa872db483526761bf3ad47da5d72a14ee33c6836b076c7ee9e4359c633e2ac4a65721e4661a9775a77ec7c26c9bfe693538bcfbf95a52ab43344fdf64c1c97b33b7215f22c68b41e7caac60b319103fbaf6d1f27e53772094ab64cbc6ef259eaa572d37f03283bb76db0990e95781a672b7f7451430ad45251d8d7d7cda0dab38b4f27429856714587bc39736eba19ad761f5e507f56b4c4edeab4f91b38aedea17230bb3c5d412cb4ebb7e604abe77a68ef731058ad50f8c3d3273272b04a374e72f6abc8f1325d58a23373117a18d2d27fe80829566da3b12de2d5127f330f0f4e35a400396c0246ae5c22ae4990c189a7f415cb7fe37a82880c8b7cb6ab7ebf5c73ad7bf890a6760dfb57e48fac722f3deabdc45625943909694adfb24c79671f38154e6dc0d53617c87d65a682b8777b05025e00428805c2dbbf76ae525e3972e9c9409f12b90fc9bc5cabcbd516ab62774ee2e3eeaa4b9b70e3c0251ba0d17206deed3479b1423ba789a3242fa66e65c9fd5f242a69ab5619946c8bc58f1272d0ace56e45fb84fce200f6dd2a855a106e2c33151b2b9587d3a16bd3123a81666f1ecdc4a455e8dedb3a35b5e2263c0f45f00b470c74d8d41a356100fade1a725b94c55e51b6c2b11ca2396c0a72473251a15a3d5aaee1650b82f9052d0aba1a0cc488c92710a5e91f110c0da13aa4eda5d2b9e6a79d310d1d8f10fe2adb297265599b8c028b808e6fc08a33d745f46c119a3750ef60380d3caf610f72ae31720b38de546eea53edcb00de9ec769795437de9fe2310f346dcfe20588649ebe7285e1169bca587e7eb27b0051e8ddfca629642ef4e7da8a8fd8546b430a5a03720efc47cb8e80b25e20c27718c3bac3f0f4166b8f1306fabdafb770249d882872df13a6aaa771d26f6a53a89f7176fcb961810c640e799ddd8dd8a266c1c9ad04d7d2a959261e526c4f3be0fac899b5c2c52d4f549ec04b43618776d04a51681b5b0f43af7ea55ae08ebde057e9d2032dac1f7209da02bb6d1bcf23f41966c872f59a627502447d60bcc7287cd3564e08494a1d0307d2cf667ca435fc95a2e272cca757dfa05be11d95ca35dc3d2c0c4c8059f9c1d53686a3b945b88a1fe1867224c359035eb70d21f3da5a54bc4fcd49c5084e9506e8145fa1190ab8a98aa22d45b335a17d8ca391b684dcafab2569213460463cb204699aa60264585c94a972f75d9e89eb9d1a053f37d0c9a193af6e8675c6cd4c4cd6d06c9120b65c2da549270ee2dca5cf33e1588c47b25ecad3cd1df41546bb719888951b03cf1084442d90268de13e514a77b8b7bd29aafd4337ca244b29acea72f3cd59984982512372728bde42c4b2458ff4e8700996a7ea9180fc4d3a30f6cded18fdde407491ae72cccb769226af48523a730bd48a51f86256521068ac8b4b270a22b76b45675563fde33fdcd80cd2e46b5b431b82a2a02690463c69c2b844331260e8c0e3c9e7661f7a73e9d41690f45d70f6b0c1af0b784e1776572f2ec9b2bdfa92d7c0b3590f38573607ea8e6b133d16bbbf0da7c617808263fcde44a43f08bd41a596eb6172449c4db0558293b2db93806bee6302d03c1576ca5f8119f69c19820225bf641cfaeb5b1c73f522afde3019493853dbbf403db736978b3a017a00b9639bd1e472abbc349c774999edfd46145a03fbddaabdb431821070b2d9d3719cf47a70b10f1fd2d32881a20e41bea76b74fb9ef326bb4a590c2d75bbb13e888379bc4f7f72dd5d48b18e5868ab225a3ef81a0ef81a209dcd7eaf38427219d9d4e2b91d1b0be71d73f0d777b420e1cb9ec529c82350b6f4a5bc4e316527f2e938e926f40972ea8afe4e3ff7a324389cfe079b6267b996a4b4ac75b96f849c975477ceb3ee7230a4684cdcd3b5bfa2cf7e11b445b94da7701df52fcfd2c6d6ea0efba02e6b72da97ac5b4fa0acbd0248087bc3adc778e787e4fb428655f621d96697d2b073728ca0c5d2d814eb47e68fa39b25bc5c5e05668b557aef766808810b64dde08472a7c0e09e13400182f549681fc21ccb73da7ff036474be563525fffc1e98cd07253a27bddf31c6c2cdafdcad5ac0d2ab0ba20eeac470a1762c978dad138d277725cc998d20b685f16ec47d2d97e542d7d865f051dd1e7989905af4316d6073e6b14725991f0098c9aa834b81eb1b46cdb46e882721f3338868389ade7a3dceb721d9fda5c25f574c260d71239aac69483583e509015c60acf973d0b6fd7df157223e98f36e3dfe759c6f4048857fd139471e303a011650f9a7c7a2b9f8e4e0d1773f94307f7aca2b295314f8d1f3f63cb3a01ced6f248b94d30ef195744f3264356f3ba419de36f6fd0b3c893058c5805c217026e55e6a42ad23da7249c15f85488982f6a9bacc2e6dc89738ff99434bedcf77ebb183955a84665fe014ea3e849275a85ea245334769315ca40bebc39adf0557261e7d3889ff7ffb49ef4f037662669049321a340037bae6843bacdef1222d1da9e61d682b404993329ec78c47265fa332b0f959a375f25acf7f141a70a4a0e35e3df5dbd540a9dad855bd3b6284adb6c66c89d1f72d801b49f4db4a426ec8f0ce3f9e66e8b03814691ee3ac712137bda48e7c379e4975201174faf1aade1fbaf7ebe28498dd1c7630c1fb3a53f997b2422046a30caaa5255ac97090efcc5d3b40a8952f8ebd8d97ce86ea1be7287792c7185206a6e9fbeaf35f22b3aaf73411b2c0dadde9b5b1eec5119fc3e72b7ac7ec693dc163028b2b833db7e3bf1b49e6eb3d989ffe8bbdcfa402d372909eea1405db3d90fe1b48eff4d69b6c7b6998d1d146e6e432a3cb3fc7511f01b72a7726f71ebe9fd6c052643e9519294adb11b0ed7f20c7dcfc5acc00fe5ab1a723020f7682757d0c6094b84f498ca0b6d4be9843c4ba409278c41f7be2b626a7288736bf14a5d6c62577e2be3e1ca1c7b61bbad78fe61845093fb8f26c58abc72c11f0af143898a832df4d34ff7f412d3549dd69eb6daab45ff0e5d72a4a79e72800097298a291ee5d0a74707d9880e9b22c58ced82d80fc9f9c412b900156472fb36758af5dc7b49b56333e8eb99ddca4ff01f38ccd7f8b5617f12c3dd5a9c728ecccf1c90ee2ae733531a7ebe832d96ff05f3213b97c12a0656ad807606ee724dfdf3fa8e1547f91d5b9a9b2c2c7d836c48132dc88f5c47b568d686c3d1985937b7bedd1a89845d256248540cc9a86da8fe73df88773952c48b97a3c5908d72b57ca2871f9e9bb39ed5fa4743956a332c4ac7572719bb0bb65bd33272c3ac3522d64e6e835f1e7317ed0a0081e4cb7177323fdfdba7f7d75f31b3118be7010f18a5a38dfcd1ea765c8e571471e91513870a791711819b578d3d4757bcb5eb7254ce01300506615556371ea4a3d5bf3fe0acb04e7dfbdc6ab348564306cb93494428f55e11e33faf1826a9ab809a3cff43a3b124757dcf02ce4c80c092dc88468b69d703fa80114b6ddd70cb4c86ebf52dee172baf6c1cbc31f9083e7aa49028afa98ce0ff852282d1c075fccdd0f9401b6c6bccd99199ad8a473aba7be07c722a770981c04ea56cea84348e46ede9e4283946c20e3d587ad24c6106b4ba3b006c9ba50e43a957edc78a38f9fd347d56448fc86c32990add667c375f16e8bd05a18f956ee120fb7f814bcc0bb63fc1e5def9cd16a88c9bc64a7bb8e06008597277b5988d60c97729052a46a6b44534a8d8e998e303e69e84b071f63e9b242b723106c26b3fc1a16c66f26a95e37c62df698488b1809174d2f9580aa5d16a2972d0eb67fa986031aac26ecbdae30a05008dbe55348a3c07af1ea9818fa87cc57262622a62d28bfb82c2bcb35359c46bb65731b0882a20a9596a5074bd3e566672c2f1f1583b05fe380f8e5bac677c29fec5372242cfc468f5e2689c6c37f4f372064ab5006731c4adf8555cd44b1933972b5a7a09fe5ac722c5d22bb8991ef550de9eedaecaecb792b848d1c85b9c0e5f80f793cb06d8f0d496c951b7e5740f724c7e40ee8abfead675e6b318483e96778b6c102e8cafdd0855f39326de90e4722e0153f6b15908e8f9ace15d89db1d9e11d709a7fa6f3b33ef26f3e8ed2ec172e28323bbfea0230b34e038c82c72ff436287fbef647e38a0652b335609909b31a5b38f22c1c2aea38640381b3fc826b47a07a2199aef2a25e0ea4bd3ec7ff13a0980802a49a56d68689b423e4c64cf9f528d0174157a68bcf8cc7257fb7dea7247807bd3720aeffb35a4792d61bf16c02d342cd544d4131e21d89e3a5b21a07293c040cfc42950379d37c630b480e2d38940f04f42dedd6ddc92077d0d74d072c37d0408cd6255c80ce60c1d13ff18a98af14864a37dd9e79e0b8111b499a73beb3cad436d822d7e43f6842288c6a59ad802c9e782e28ea79f9fec0e022fec5746acc78497f05475d085b21dbcb576b59fda81db2ed0a2bb0f43b4361d5b13624531bc6e76a608fb8054780b045686550db8e802a2e9321616aa1b2e3af9c80b3cb226085c8b648982b5a89b57486da80c3bc70c7a3cc96b381158cc2f18742ec844392bcbb4869b49223884c8792688299e3c4a4482ac3b52eff60912df9a726336838b5abb21980634cbeef65fbcae42caf580d43583509b469c82115ac872bd30239d4339772cc48b188eb2d6b6eca8c8056f71437da19b0640bd60301d331a39b15cc75d13cb4f6fc2011a8f1d9e9148f244857daa9910e1711173461c6c0f489fc8232a4dbf28d40abf7a42b0f6c8ad90334d3bb7b5b7c6a19a2244d724f146398af92cc999eec9b9b5a9ced309543af5efcee21d7f9b6c804e182c3272c845238411c2b4fc0ee967448a5eb76f484d7b2d3eff0cf50379cae8d3eea346163c99e7bbfeed3351b4adc90f621fc9768fac6e95d37625e1d4cd5d9ed64f72a633c9c38b7535c00be5f2ed5d7987492a84a78292052400bde7830e9b2dde228ce3109ff1085acb30c5ad496323b1e552947c9087b413e4b9bfce5b6a5c2a727be8871d165d19d5d9f70c7768affe1036bf82a6b193484511f9b702698e9a72aa248d9089f655b770de5d6e5123f5bebc0631154d4feb0215f497b93ef0d3005025a92d5b886d5d2a1408ff0a1ceb02698cf17a45552bc6bc3bbad12c6c1872aac49e4e03cc39d4116f341645ce2683e0a9c05673855d9452662b3367f646727b5684d69b1ae8bb41413e6f6840ada358b863054cea19701c82b0fd29004b727aded4da14cd428c47fcc3f92f4a5f34021fad987c67deeba58d24615b87ad72a49a37f57c83ed8522bca3761c6a7a0515cc8ef718499cd609b3ed1d596dcf728ca94a9e3e074bf0bbdb803b89ec130731bd7f2b250e6a99e606ff90990c6972b8a5488f02986333121e6503004c889e00f8e26f98e24836b5e9146064448a60a8dc4339e707c7d76b77426b6705128209efc6f8756acac161a5cedcc42a0c72c1eb65a0b17d0d60922605f38e2105b90c054b4ab7c27816e748590b42970828bf11ca4677569753cf2879b91411d7b3ff4227b43078b98b8427ec36fd202672d7ae45d0f4e0264188f608bcfef67cd5834efe08d897d31b1f3045d2800e952cbfd33d1ad280c77f7dbdda813ba1186471f13887ec0dd24bb573d552b800d572fc435e91f2cfc937d6de11075ec60b1e8c10415cfe7f036dfe3188523e7b4d2551fec7ca1605ca6f468f16724efd858a585027a554202b0b18cd8a8d1e40397232d03f6fd6f8fe558aa3bfba8f7d012fcda6605f48265f8f10088b2d62d7b27204850ed9bf2ae8acb7956ec09e7e433ae4f05b235e6543d1150f35992a957e721cac8bc5d3463bc2ebe03081684de8cb7386717e4b3f87585cedbac1376906296b341377b3748089f88746ff3dab30fdeafea4402fa81d965b70d5af0bdcc27276934960f0b18c550f4afedaf785fc40a91a6160978a08b735a2563d2fee8238dd286940b1267b8a54fbb0275ad0b0864b20aacae01b9ed49d7a7921a6d040726365e6ab22a7b129ab92c31c4e6b237bb389096e352a2dbe466cfb6ae9e177180e238b777c7ec2dc5d7d7e12486da20a72488ff121e70a72587ed7721d514d07f4e8e0a23bf8a1f933ecafae9c6e155b5bad4a579a307a16b602f7539a70c1720cd0e5eeb6a816eef31503305a70303231b18a685b3b6f4ad83b5e4995ff21724f95f75721442f05d7195f5f7fa6a05281972573a77c3e3f4976b88e14f0e16cbeb93c6cc7361678cb55ed188ebca35477342d48b2d69c79b24b9f6fccfb497253ba037447622f705300600dc377ac8d6811f130e49078914850d880d4c16f1024182c038898f932f3ad84bff29e2db0f3c87ae960af0602d797866ae27f46515cc6b9804886b56db39fd07f81f168d067525bcb7bb7aa057a648af4fb369d6219031eb1a72b9e7de99a4964bd94c543aa53fc5faaea794b0cb5f430b6dab20eb040e4e113eb6f80d1fe5f6e33662cbb8f30278e94ef9a0c45db137b608079721bcd3ed7ac65defac718d6d8caed839d91bcc38bf84121ad0f5d005a8f73e444505a5a950789ba3470f0d6d523076c360e6e15e6c166cdb195d23db74d110572ead04e4f699da77379c130adb14cafa68ec266bfb02b4c1eeb57d1b3bba7db3ca2715667ce67ed1f7f9aaa4165c3c7d729281c5c2bc102f03610f503f1c0df3cb6702bb4b0948572f5b9477fc7b2b843bd83bca5e31cab4e04779ec049520109c221fb351c2a91cc746cca36c8b71d620784b8761a986da15a8dfad27d9eac2933a10a3421b22ae9b0c94de9d514d9c5d8939f423ff0772c191af32562cd6172638cb5c8dfc74aebf14db523e06081753f49d1127e59fdf5fe0b4177904799725d0e4c89f55b80ed205571dc29905f7b9da6ca15ff12e9b6c1645959bcaeee72a9552792005c96f28955d3100061d4c3fc9d1b836fe9d270c78514288f8f0072556510d59a4f19f907c627d9df9ab4d37229530b5ab026e9d85a7db8b8d3bf41d0b470e05a654a1d0db61a9921d5c747b252101b548e9af249ba3cc9cdffb33e50dbb702e879e2c6abc0d4c5670ad5dfbcc2a9307d2aab5f9da054a2150b2972a3453c099b0a0f52c8a240c435cf94bb3539a52a0ab54ed4402e074a363bda41369dbc062becd599d26a3880374fcd887075fe58ef18f76a091a889cea2ac8037794b8b1c1702e47ae5b8484695038f4effc653355553576f506844ac4a87d21a693e296e370cc38c3545ec38e2ae86b7f654be32f10ecd044e67e4452821a55c1ef4f90f523215e35454dfd45922cf6b6433d67603c4874c87f543bb3baa672c1a8675d1d2b8cbd59c99efa20aa1195b37358f96eb1f1c66864a58b5d5f602a484012a734ac712206be7290908a5fb59ac500f769be1d866ddaa2a0996971723018c6b229831b71829822a4bdd4867d0bb5f02df78d343cd5b4f93aeaa65a2e0389044191f9998a7a3a080187b95c5d2421156dd0297343bef042160d65e16cd01185ac5c0d453334ed81070a909a047205ca84435229616e3d39f4b3190072aa3e2790ba67300ca6664ec3937d3b762fe4e5b80836c4df974b11bf193eb4725e46c80d16587c0023a8087adf9cff41f8ce369ab323d7e94c046a54dd3c6c72935096c45eaa796ea040afb4d5b19c932b4b6c102db2e478ccb3e91fbb2296725f90342a17244cd193eaac5b047470df5659c702e9b3cc55a249025cc384f172a4f75c58efcb8679ac5e81fd7882613f7678617b8aa3ddcc00b045d5bb44b4727158c93e0f49219db3ac5355525127bfdbfe08ae86b83bf6d023cfb7989da5729f9fed1bef72e4ed4cfba5be2eef24f8d88224a34d23dc6573c1c80a2d41855fd90bb7f556b48d1e17958b8e09a399ca3b057ed01eb44164eb448adfbc8ae05b852742facc478892d016080233bfd584e7b955d11139485fa5b72d3d1468a0729b02bb1cc0968910107b272aaa2fadfd4229f96b556e7000b5403de5f82eb372935c847f869bfea07f5740d85d17121d4de3f79a44deb45192671c0838ae3968a7ea2e90a15e1aed5f4fc060d8b71e7f34dcda3fe26d2b410aff0c31403ee772ec4f8648d687b393bf57109fc75429e2e7d3eb9cd6bae74856a9f1ed49807672f30204d76325fbb5f09b3e6914f77542488f279a65aaa574ab8871de16979372d4d85810ff8ed03adac67617ab80409c593d1c468bd78bc8a01167651d8272720120c9105904451321b66b7707da49874c88483815f7c076f52a046bbf187272b362a863234f6ecf651dc6bf0315929ca1060cb91f00932593b0901e4ad3b572eacd1c2dc7ff6a42d13310979c967b46968fb1ff5abd924daf011054cca8500b777951fa49ff6dc925a8f28000e0a50a596c1a5793d859e0b24f7c1f2555f70fe930e5f1c1056ff77d89aebe70796ba67b8525f906bf2cc2f935f17c3a004b72d5532cef149ebe29a7042af7088ee3c036362257a94fad5e5483c7aeb7445472213d680f8db70e41d2d1f7bfa2f190b9899d35350a544c04837ac2f0e6f23672c8a67953ee7dda40aad4040094f0207d41d25d6ac34189516ed77df8ded89f721baccf03d802f4ec1a31abf5dd20a701664d8f3b0d58eb980daec5f06cfb5a533a04958d5114f1d2cb35211d29c097119b8b9ec3c7c18278a122f399a7894e72d536231ba35e5a371a7dcdf962abf1c5fc6bb3a6f4c410afc3dc1771d8cfa870f62bf421471b401fea71782a516752daa7d1026fc18500530cb5e2888b37c6058d87efa6300b94fbebbad0c4c443333a15f58582eaec3c5db76d7df101b8535d3ff729ad2e5ae6452a22898d7b3fac6b8dc946072cc06a0c143ecc96b41c484650c8bc48380592ad4be13157e79dee69c5caeb7fb984b4ed12b37d89fcb84601b8c2b819a2d8915e834242d54fdaeaf1ae31b61816f7fb83f9e54faa856a5963fbeec191c2d276d9e71596a5bba1c732caf68e8aaa0a62f4ac596c763f923c72bd53be38873422cae4a57b52714909c6fc5c9e5224c4f964c7194ecc8ff08c72065ae240631faeb15d4f6281689de592c64919ab5aa95c32a826de32cf234c72b527e087a32b3f681ee4aa99bb10f80d88931d88d2d60632a041ccc2e65df5723987ddfb0f27cf68cc878fa4179efd83c03b8b6aafb9f03fc94b8878f947647212439dfec565c60d401d8fd12107bf4f1c835ec066ae7b197cf650a29155837255e828da5859f211c0239ee24e8ab1d28abb210a62c667efa4047b9a9cef311ed5bfd38a331a01cdbff6a8c82ca90e729644455babdff8f1db5eddb5fbcfe0462d306f2c7380cd3b943eba7ff05cbbcf6a796cc80ca1b4b4ecaff33a01789b1ef3ade792d77727046f1d3b9e9609315eea8e37b3d2ea27d1dbb2004574b8a442110597be0588e58140d4f3107a2a1a4e5fae9ce7ecdf98a646c6afa1a36d7a4adb0d75b27f1a135c0c0b1fcfdeffaec3ec4a4da5e8c1b9521b99e3b3f0fa6972a62cf4394f45126deebeae682937253fb75f497fb585007271824ab5748e1d2643b3c5ad74fb10dc4cd4912b09c14bea1749785a32ae9011796d19f38406b172c2a30ddbe565e23221c346b2a276d2b1ac09ef48aa0df0a98ee1178ec1d0a4703f2317af59e997f6b1863523d8c351f709e2afe94171ffb25b73bef028180672cf96e97b7d01f780d9a24390a0c46f6857807fc66582d5667c5100d50c2dfd4cb38fadefca3ca8615b6e1be87023466363e3ef4a8bd2771c769a7b5cd684235c9ae99eb8a9bd9c9d901402e31b557226a712e51de73d57e73f53b2b9aed85372bd90f4b4a026cae8d6cd56b3f17675822f5719ca290b1f3fabe68087020927727b0e9fc86e6e9196081f3355b36f0ca67265f620bbf42555df6dff1c0d77c4223f00945db9c009fcc02d45468e1cf7d9b5d549f8eec915f72f683b4c3c8f5b24e5c31491be5b5a2bc3ba1f7051ab088081129d99555c6519443c1c8ab346a872756080f36f822bbe5dd513db1e24e49f3729d753bd626bc5f941ee0c78e34872f2f5c2639fc855cb54d5eae8af2f434c098a5c48d5b1ca8786223dd927aaed2c81dfe3d93a7ec5fba36886e958a799bdd61a8e6761f615ec2247b01e9e3fcd2d83764b7e1eb7ade3f9b9083e2039cd4ddf5aa59afccaa0cb2a63991cc6c69572c6cbbc2ddc782d56f594818c11a8fe772c21c1d6dde2a5f347c65222bb20a37265776b8c2a5f49e04f1e64774c01c093445db2e1ece40f0795171e8788e9cc419b4fd243b93a13008f69c3e41e9d54c090f67a81307dcc14bac8aab6fafd4b727d707e4229583788d8967e07b2432554b7dbc5f54d192e311b85fdcad0181072cb3623b7824b31eb22a07fed0f81563b6a1a741cce9ab8befcbb44f60b048041d3ea179f9fb7728244dcad7415f7ca679a021f557032d50316b09ab8828dcd728fff81d0a8b3f233399f7617cf386eb4389e2708b49f063d0fe4de95acced97214242cbae3ed7efbe0d2302634c0bc91dcd9fdd2e7497da93963dfaf5d78ee720df285462f435f9a3264a5ef02f3937a14c2fbd587c1db6b2a51e2e95ef490721b324a57e09f6dd9891b36561223f0d8f8a90cb98548b2edcd23f6dec209e0725394510b1e317760e3964935426fe841d28d074c08fddbb06e2cbf3e2707b25e720316d9ad997aaebba2e8e5cfa4aff502dd7dd85260ee1c648c83c3b1a93767f2baec4b9fdaeb9daf421183f86f122c3697433a5ee2c9f4f4aec636a9413c0a14e686aafb19048097a97f83f7dca7fddb1bcee80c19abc8e9e8831130803d610c97baf46e16a1a52d8e3726c5224c2f5be2d97e1368dc1c24b31f597c34174a8fdbec6ce3c9393a511d6433923238fe972e0c33387ef8b4e6ece336cc45ee722dd133a00a25c3063217d0cc89e21575b8d2cc750c216b356f54755c41b4a2721873dfe80be25f9edd1a35c143be6a620a8125bba3fe99b1de4b120ff922e51a9cd41f565b06bbc87d12119297ed30546536572905200dfcdf1eba8cade94e01879f999149c9fee00255bcad34ab09ffd826e8ae61583328f2e3ebeb629a7f72c978e89d7efc6cea7a9cada962c1585ab637d46c3b3351e4b76823fe1683764f38357fee6775edb86be2a138b2962fae6763e2a55349dfa7bcf2aab8b6abcc7281db65e76f27d22b76676f20729ccfde4ce616899e3b957aaa91fcd6c0b4436ed7f88810c939fea57fcea08fd3c05d1b2da0aaf3ce05a099138d9177c6484d725ca90ed48c923b071134e124d959fedd0473a47096b81edf438ce5b215d8d7728c7d671a6202f14e68e33a2c7413ef7f33921d97b27bfab86ea00e0d9b023c72227ba09cca2ffdc626e1c4cee28a801f1c79e5349f9e745cd6846458a0f2c83e23b25f536693f6f3cc3f2ce3c76147255a68ce53e07369ac38a9e71a859d270600229d09a578ee624f6b1dcef1f2105176cee5581263ad2907567aa73304bd723e4db3b3f5eb267b73fb681544438315847f2e1a86b35cb77b9b4605702ce7722ca516ea6eedf19958641cbc4902f89f35e3a89a7713dfffe0168beeb188c526ae7d0f287fd26a5b743e503283e7ce6351cc54c6e2918a1daee719e1df0e9972e6f137f330f204fd60c1b26686ba6c80d210d31521c2e95b1963c5aaccee4072c090e7363f3ada77505aeb2d8811c95a1670a97751985fc3a27be29860661472fd600eccbf33da5998aa1dc392348f3ff61fc45970f109d4dfa86bb7b2d67b39a59ef6eac6d86833deee455ecfb4e0c13a98341c0acf45e892b2871977aee620e47ccda76eb64f38c0aa6870781008b6fb523f0edb45cca5abe323a402da4a3df1fadd628e7e32e4f6c7e8806368df6c8ead792bc857f9033aa70581d2e57c0cc31395a1e2669b14cd7480731d5f8adee7576c603addd65dbe70a84c6d0bb3725bf58d55a5db2177966626f21cf5587acd49c5ae7794cec3c4db2761859db5729866569394c000422ad01c2932b2eb6a0a87f2eae9ec275e7414f59d76ff5b72765d85ef0ab9cdb177046b73df6a47b464a758669d1835b23e07a9dc2b8dfa7220d16efa8a783994b12de9286ede8bbf4c9ecf925dc67da7ad63dbe44a6b36729a16dae1c5995d0cd7a083ddf57ab6dfc8ac6ef6db94848ce40ee31c271ef872208564dcb8b66a8bd8c865648bbdb5559c22e3cf5ae130e0995101b54d817572f3456e3674f772087f760791b0156503c4e90dc65798572f032ac7ecf1841b587235e46aecf2993f1f024b14543fffa77c2581d22d6ffeb4f06d165a1895756385983c577464862e0d505a00a7d230303aa5f6cc78406d819f30c4474fe48849e8fd5cd8fdba659b1234d330a051616d965a6dae1b5f492c08474f1ff3826672155394ad31251cbd550c9b6e69b69e04b59dd5dd25415de95901bc60c69bc567657c3f0e45f0ef2147f7a6834d3043bc2d7cb9679fda24055f2720dff3f7f2726d29a5debfff62b23fdbda5e5db7d7a8f685e002869d23519c7404a49f129a72f0e296597e87bfe79c6e62c6b86988069c245b3acb76817eaf8343b7e6b3c01377b1e51a4c8153e32ce794d8f678dce96a17b136f2bf1352da9cb3ed9205c06bac9f4a890ce518c4626e27e2a88db2027a81a7606a54e0fdc63848226c3d3d722387bbadb8e5d33283e972a0bc9cce49721ea718ebe035eca962b085e1e25e215c96c92bf4db709978e5e8f933d1e8419b64e315ccf854504092fd5750315f72fac454b9a19ea711efed5299913e192fa4392223afdaaa07684e0cb13f677472069c5b05d6a4f0927e8477455b3849684172098eaa39b358e6ebef8372169972de988aab9f74fa3054113af0f818adc86dce66ddb54909872ffe14294b84c4384005dd33366c822c98967f4e11e4b9e13e6df644882d93279ebddb6db78e9f72f55d3a55b1ada8d1a5d8f7859b6532db1ba42e11e95953d3a139d6f037aad87222ffcac86ddfbbf2e7c894a2ac81960052af8857035ba06930e1334240be9d726d6a7326ec5c5d855acae95bd4939db4e43f88ad968091c3460ba0acc0b15b26d0b29043cebefe3c0435a3e490a0a62e79204ec818c70e60cd38c6f6cb0b9a720540cd6c1bb2d7a920aaaff241a3f9dd6c0362b184666b0e52be91895d0a4a72c8fc84e4404b16cf1b46b5e919698ea3f5458b1d74032f9c1a3fcbfa6daf93213542d0378a115a682f1410b5094ed7cccf0029cf8246557b71c4c395428622720274556cc61b59e619dd140e8a0057bba40b879f27279a4b417d3f8f8ce4ed04813c4e6b761dade6573c2bad4b098e46a945e120b29460721d5bbba0d5b0c2726844571fef52d1a76ca348e4b8007ba6652d00d20af91bb10849c8eb6c73aa0d0d628741a418ad4fa4c68dc8f424ee30f712c9a8affb42627020ab8cda2b824f4a0ee5865d399ca31ac145a25e0fe9068662d5148f87f0cb8e7dae65b1619472987589d0ae01c2b217d743f5869c46574051c23bcf0dd073157472e08808d37296f8675220dc5b453ef8f1c7884ca36261a2560ea99c67c185a8743372f7df729e9af4569f83746d5245167f5e533cde19c01ae947360216fc3ed77143e8c072be43cb61cf8836d1cd914734e0e2ecc59cebfd4bf886e30ed37ef9b0b627de3a49fb491b979d13888058347aac6586341f4084be356955cc9b71f06997653772f92b59d52e3d2af1ade42a7609a97fe0b3f70cbfff9c0876a31c5348b34bb8729ccbf51c43c3d6678297b23d5d13fa7f2020a26d47185f8485c2fc1b77687a59ba0b3f27e1cbb12a27cf74863d2a13cf77c5a42628b5bf8fa6c0e6dcc88d3e72d9b5be6cd6095ceeb98bd01bfc5e9b5de8f181a3131779a002abd69df7a9f62447c3420a49d3f4cfbef32360d9001543e3ed3086c45fc480d39396a328278a7206346f1e532827563dd2064d2750dac981fe6887fef7bdb8850363eafeee2372105cbc9ed7abe99a1d6273682577440f70a111c65283a4df7ae45320f29f161842fb231a2f91f4b0a65a0fbd39b8ddd2e80b82213d84eaf7003c3eb6221e75721f9e52f5d3d6034ab7c4f6e8d9e153f2e17eaf2a0ed350c660b940df8ef5740adfab9907bca32a42b59969ce963a37d06e37ad9e933dc96759a1c0c63c42c533765f84e9c40e4b2ac28fdabfd28d34e46c6896d10b904c1cbe513e504358aa3a0d7e9f55ec63b9cec98df3fd0664dc8e890ba4c4fdbd05bf23e7fbbd0b3d3d7214360777b2527fae8aed383b1b158d324872efaebe5a0988a21ba4ea58819c5016849236a7be12cb5fed07ca25ca9d942b2e49a3fdd78c9232519da0ca2bc972bb62af3c597a2cc8bfa1ab41b3912dc10bde0f57e9faba4f8d41d49dc026c305105ab2596321c10729fdafe339be928b1601ed20f396a8ba540d1864c62a9b72f42af51d63ff621a9489cbbf5ffbe0c3ff87606051720ac7bbf785b8cee0c372acfd27e394760b2ef2c591887d4076931308a9a2e0c7a29a39e1de70e0f2d972fd299577499dc454af47de7a5834e820774073cbd318a229a3ca122150621572ea6c1c916ad106e84fec6340792117a077296cda41bc1fb3f423605f0cf27f72f9ab0dbb9170c87c83a91e3d787b31b2a20f01df5e69dd3565d7f4d34df013638772bd41d69c85d3b2c3e293d53c64beefaab2354730d0783ffe778ab61b63256349dc56091bac6433aa04d9f7750ebeda929a97ac2891f3e651ac7aa43456728ea73c282db24f03288ab8ba64dba3104073cebe6c049958e80c871d47434a723d093e9f258fb275799003574e1bd59c80ec346c623b254141fb7ad64794e0729d465e9b00327e1ff36dbe98b96e22fa3f688ff5576917be0d9d9d13166b9972f750b64765c63041f8ed7de30acb45eff30051acdc541357e6a323c487998d2f31273554bdc12148c9d07cffd38f01b7cf5430ad181e93be1e85af08185ad172c4ca95d5cf2f3f4c6e6b1bd53caff139de4a35d201281b6ff82c0ac827c255378fa8ba56704888ec105643dfdd6e0ae58943e78c02dd942e5f9ceaa1f1863472ecdcb079f520098c81e66b17ca6b5d4d6ee499a62001493a841123bc6426a601047fd873b30632deb3530e90c25d27d196e8fd823815c6bbf4f63d912001d63056a18f175cdfc728ec42a53b0abc54ecbb58b731a832f12994a675204fe485727baa59592f058fe8ed793b6965926fe885486e986b4bde904300f708848b610de93f6564c5007ab2fc9ae007723ce4764111595e040c9dc559fe3fb9d41acf3847a5b8c5490e81f6d96e10dae8d41be92f6a662c8eac1a0a5f3b2157b08d5e7251b9d52b77725317fd1efb7db272929400b5244fcffbbbd0cc5f411ec3517f72f7237dae4764dc96204dc41e1378654fe5271b2920aecdb4b93ddb86ddd86372dca660ccc06722df2b1e09f2add206e74a5c9f4de236fa092ab8dae0acb46d480998959073cadd50c63a8c00f7c2f1e6d63af8281c8cfdab245b073b3021f1721de596ba4c9e319a6196c6b08da20a8b3fe34766f590390c373e6ca6ef5b70726ef76a6b105ecda41e6ff37a6fa8edbfa30af085b005e7546777afc632295772f215c3d6a896b2502607dda7d6fbb0496656470c47baa41b799405b36c4e0372a26096e6c6b0edf02a5baae4926c435e218bf3090381012ade53a92fd85d760b17efec8edff7dee0aec24ab7de845e081b4e440c749f30e8d74b717297000772c38d16eb6125b542d6734a4f429a97afc3d31fc1eb299aa1bd13404acc681b5e9d8de7e94fcc0069ca9ea51cce9ec2fb9fe918b34c9085c2f20ca8569b8c4d72a2e1d9cdc4fbe7009dc81e85f7e3ad0e9427c8df5c22966f584ffcec74137872a989f726ffad7f36561f421483e261c4d4cf8dd728cd1a75a4a8d356ad2eae722535e122aec07b36df7ef3e389766ff149176f764258af8ab931488899c49d09470950be3c3c487ba2b42c1c3bb3fc3daed36da538c1d59a05ba3094fa59c213a9cf1484b5f580780eeed6ddcfbe588337504b50ca0a2888ddfb7d4b1dce55310c8cd1d3cbee157fed9b76cb1c5c7915637c47f0f32f86a8fcb8ea7f4a74a07238b7da9ce261326bb05c37a8b88303ad2475fe89a4709c8cab08ef690744e7672bfe419ac0d8662c1c834c1d0c51a44389a37369a30d79c866757b4f580fd5288d01b6920a861b516e5d38fedc97b565c989b5680f29343a4dc3666c15e3f641fbbdac95ae789f90b2f068fed4a927b4e9865bd5ee0afc10bd5020fdb589dd7207b1dcdd3d340ae9cda68f76ce79b6e52ee2c9aeee6aee668fcb95f5e2516372138af349143c439f6f762f6dfd978a09f2d9cb0d59990b01ec726259ea39867271cea2add9fb79024e6f49892474fd178f2d2676a9a6c9f72eb979f2ad08b152084c54439e92ecb7f027ecd94259f3b457b4f5093fc6e1e50508070174afd8000655bf58600dafb213d7cd08f7d87d8c7bf0bf058129958b01cf2b4aa3daa9598fd685e9a20ca48a2c4bb804e74d0eeb1d275f736dffdb7aca7f715eb7db45724160c26535d6a2742babd5b889d69000d3ad27eb30facac274c1c4dba077237218bd460ef1a92a7dde9debb7bbf742dd5204bc9bf76bd13b6f59ee5515a9ed56ae583dd73e906830b427c42130c07ffe59e7a2f5b7dc23e5a22914d32f8200469c0a9dcd789143762329262d4a43400234b8ff68eb0146a71da9a8281c147a23b4b4e13aa58b13d85b6946d4b8177d6898ce633adf8600717dd57572c95ab553542046f7a77999d213c8e8d5b50b52c2a5f211260596ea2a26673760b591db56f49b0bdc19496c2d2d5c96a1f6be4ac298818ba3c0e6db5c81113618e31001722261c260b50fe5f333a8f33b602d7178546610ef782368e37337269617cd667280d39f6150de7b420f1b54be36ce571cd4f2f938680e33f6a9d18d26882f9d3115b5fcf181a8581efcd4a9630b7b8119419449205b482c6be392c7c60e0dc14d17a2ebdb4cb45ccab070c18b19c610ea1a6e82da164cb236dddeab824889a2721aa33b13d8674813951381b79f090e98d3f911bf23e6c56f6dbfe29214b7eb726a4deb83e47cf6e7674aa622acd724fbcd9b1356dbda942dd12c6148abf6ac728b9df4b09307bb9b50f65be70df93db3c26da921b00b9da472a5b1d84b0c6f0b8ba014c45c6b7186f8219710c864018ab54643af4e101042b5e302c14ad8a33f8226458f5351d00d6659bab3db3bb6813fff407d904ef990d64e1b7a0888e272b1d8ec85efe3b97baae2a8e9cf8f99a9b6050e7f63db410856de5c589ca91b72a94313b711becbab62b65406a72d54036da86750fcfcda4d2cf2e6a327b8a77268e22eb5da09039f85a550fdf7233aa0744b74de8f069c9f1896a1e47948c8727849d564e8f0b454713f3d80c08c2824d363864cbfae35b22bc6bb7c0323887250ebbd2adfaac97e8e334c745092418666c78b9912c560dff577db3c4fb01155ee219ec27ac251dcb8deca845fec397cc583cdb562a52d5044f0306b66375072ce30678df9132bca6ab7a43147f8f2c8c29645ad319c23b631216ca53c38597204a2267f5021fc730bb32f2a547e08479fbb90b4a42ebaf7b3f79040c0d41972f1943d95ded4e7d74318a29b7962c150e4e324104873596f5fa14b0677ef451cb07e35ac40947a7f6f3df03c4986566591880e7edd43c3b6807647ce2d9d146ca42baec6fa24d3686b5e4d86de2cf61e3329b1dfee8b4bc5e32d9d3b3dcc5572d408dfd666e4a91168eb9c7aebd22542bb4af19436be5ed88dc0ff285ff03c66b71e9cfefead8b74c7b7c3e00226ef528f0aef38b128172ab19b1854e8cb8250f0663434b84112bd47922ca89c9452ac5c89ade6de38ae1a8d86683ca0d8907275db7d0c06b6500e0c061fec38a78b1b10e13212bc0a0c14eaa90cc978d27b3ce5744a43ac1beca74b4f8dc20e809115b0fcfbb6e08b3d168f3f0979931efd72325937c6e24bc5582a85fb24f61027d207ef58af588e9ffe16f2e2e591ee156281cc80f91bf74953449f6861896869317cc3056b0f7cd5a9d1c7a6cb84937a23603c5d2afe0439ae3ee6ad389dcab55980f4f6ffa0e7ad503fb8e872e6108772b883502e13f58163e457d90f52fe6e68cec490820308aedf28ea54d3b68f6f72be6907824a2a55e0b41c7c57e813376220aa5053fbf1ee34f68c17a77e6664726c10e8e94f76af165481fb691184055257d929056cf1d26ec0f6da5bac8b6223557ab91d5b98fd1f289f9cc88197477b08a15453ea1f821c7ea8201154bfb972995ee9e26d224742b8f549de7f4a90b53751ba75b5378a449b1ca973d40fc900cc5b8e411d28fbdb84ae4a89a9bdbd9ba93dfab2ee1d89c3ef28f280e3449553544e0d4cbc23d7bad803e0697b464387bc23720bf50bb780f2ac5cee303cad25e4dc21e625b80f053a3f131d9623a34698399413fe6469f4d2759256c7cb3f44bff490c881d96bde59e2ddd8a239feef637daeb118a3060970edad478e847914d2e5c4cb66fe0f42d7d7c49083191e28615fbf15ff344d1c21c68a44a3e402727c16a9076341053d787aafb765755adf73f2a9c6947a359498febe925e8a216ac4495a0930c60571bf7d7d6026b304cbaa44bfa6633083e6a7b121b2bad92172c1ffc35e8be93711d5ad69d15a7bc7b343b6cafe8d65d8ecb9d0b03cef1a5f2bb05b5f84882643e0466313cd8f932c8861bbfb3fa29009d765d184d29f0b157273186087dac4f4774e87face4baa40c782a5914ca8846207fe33096cd07632723616b35204b272a3ff16752580268c0558c7a20c5a8d8482f89e6dd8d3b69772d4a2519188d4748a890e2d8214f79faba212dd91448b067e7d540f736e692472db532c895684894c3cf3362ca290362a9556cf0757ec61317155ecd3a5f93772531d73d66ac12d55f4a9f1ea61ba8158a6ee3a9a49eb011af3a65b7c1deffd42f65031fddb569b332ca1c2a436083df2dc78ef93e554cd2232685bae1ec54672e7d3d33f7149c19246048bfc0a6a8227f2b58db752d383c4a52a22e682f83c2caa62d3d92a1276696f0ed186ad4f0b0b3ace0dde8f2110c5fc80a0c69adecc724e747e362a724f45b2405f562276077908b063e7d4c7cef3cb9c12b3679e3a5b94972c439f589c828ef7c0725ca4d458696865087bcd8817d8dce990513cf2724593e08654fcaffc042b83e86046b2db8dccbe756d71e779b94f2bb685dfbe72949ba3c455048e5aac514c0b91dee702d50029e7ff24d69c5051340b0831dc72fe5740a083713f37e823a451c167634528111dc0b0bb025e258d8cfd5380145d5eb5df20cd1c5d0471b724cc3c028ce95c3dd886c03de316ce0004e887447c7259c46d407e998583a71fbd64158c4a6c428fb708b3fdc13f0c1bf75069f32c16ab9175ab6e916e8dfd06915f2a71c59bc6dc601d9b8f284e4fafe586a609db3181a58b8cae188b52472f9ea7fa2a0dcd482b91cfbd6eb05cec5a8da00141ff72bb2c4c8c0f44d09a78597d87d16ce3509bf4603fa7eb0a9621aa830387533772b56210f84a19ada5133e671e29d61051a3c10c3f064b90bfbbb8c3c220bfaa42bc53a256381a17c1fac7af44815acf5a0f8f9c527c8fc28aa205fcb7b9194f19a13c250906f7a8bb46a51a81a83e6bb4536e679e32790ce2915a48578a1d2072d8f92391e70196cb63ce1ff08ba8e121c5025e4aacd947e6db22b6855e72e23c459fce9b0c5b61982f327b4e678b7d7b395c1a11111cc0a1150929318a4206725a6e08bc5491ffb18356282eb7c1445a7d58b223e300a2af90dccb8a74055d722eb8585d0966be7845d4affb391141a6afb9ceb406d3b60530ea99e0a9751a72ac662ec47d361c05d7e77835bab1698d2a7524c17ce17ed7819ce11ae84f970549e83fa9fbd989054880c556bdbecc5627f220e9e4a740f85bea74f8d9765a4268039b88d5ddfd8d6d2c810d8a65c2370f8b68c75a37d73b1bc5aa5c01652f72e51358c54350d6af9d43a18846f1db1c08a3d5db8570b31d8bd335837a4fa472bd701ec580dff596b3b8f57414689b8a6e2522fbabf546ad5bb043cd40ee11124e1def57a1fd455c8f7a36af734b61654c8463e6c29f508a003e6be15edfa272571bfd5710c92b1ca20a19a6083a778be042e9664871770ae2120a63e5776472cb8157d273ce6a1eef9b969412acdb57768d5df667bc0aec1909c0bf27b2802b101016109568e48185bafd4e62f36563c09e701af81e29f9a89962cb09f46072253a33ab9c5a6ccf0f0a37fc69f39394b15953156be133a9989b06c077c388701b014854e91700393559441afc83fda561c668eabff072f196567c8a3567be720392eff4df692764adda81485235322692da74b1af07c7acb811cebcaef42e7210ee5bb739fa76f7103df3d2dc6ce1da688f0fe5d3124db56f47b68c1c96ea723ae58f232743e7808a75f9cda0896bda10ab8922133d86c74d8f938f0db0f9725196bfdac75dc4e93baee6b4d236db07d3e8146f2f2dd0e60a131d00ce7fe072df61d693e63307eeeb30acf10fa74e961e81a0a1f170f3bb75a233bfe35eda7251a55a91940fe7d14333e840e626a4c2fadc0ad9ade8cab9b7fa0bfbd4782c7289fa87a7d8cebe0b6c43bcb71d4ed0652361d354f629a1d4134f80d2b6ed6672d04e212ea220dd83e7c18470e12933667c86e9b89d4208bc1a0c709eb2ccb959d2b2cb4fcfd41c919e84f154b6fc0f9849d6263d78f853a98355de4d4a1e9e7256c3be34abe2e2adec378c6a5b5df231f8a7ae2e3f9f9f82368cdccc91f3050c8bc6c18faf1c21d4ec92df9a2b389b15491d02d32e25a0a6d176947bea41397252d9ee47596bbc99541df6620719dfc5f0705cb7f9efdc48a508876623e6f10c603f165a5a85a9fef024717ded7988347c4f626ce3c6f04cc2d79b060a672d49f0ea688141653ffcb576a1e02ab512e16b6364978524f26f435ac4ce56322c723e64a7462aaec2e684a4a17fa64c53c310357bc91c0b999463b6e3865d523b15edbd9f1f3230f5de027f6d41172758a6840d40e19fa9ac29f177393d738c9272ab512a97ec8e61f3865016832b214d195de5cf8de3329dcd36682a75818f3472a00a34614f2971fe5f5877895b7e6f8e95ee40e5ec7613edbe11bddb64a96f430c7ef447b8bddd355b92c8f4ba4164ed007ad4083702ccf90fd3af3549381f72b4ca7ec4bac4473895ec49ac113cb2110464e7f178753c7006e4a3e99266410c8632ffcc044b340080ab69a604b31ca33eadbb5b77f1ff137f7e7bb8cfab6335b783f83063ac3e34a9df75d14d9842774655d4406aee8a9fee00557b607e2672a46dc11954d48caadfe654b95f5f4ceb5f9c5484224deb2aaa46464e56247472ea4aa58758da7cacf299a25d57d330d52403cb6ce3cb71b9394e1596f7720f72a5d4f06cbfeb02d527558393bebd4260872b181d1d54abdb12d2cfbf480c164558d53738478b9d72984a801f6df4f54c9dc3ae307ad6784fb80e9aee23b82471afc3d8cc3043265f3ca53e02f559d9bf9caf4d821604f48cc4d1afa9812643724f87bb0f0eac6dec0da4129eec254ec604be0b4be0490947e9eac9ba7e4d4000b42d9f1ebae4999ed7b8f3ecbd5bd878127bbd78528d476e91d5fa7007afe519d2d3e7ea735810c28446c4079bc1f6e7f2c22f9431ad61b657f5ad1101d7837262cdca3da64a2bc42db7ac9e5f4fed4e0face0af8150b3391b22b16e098a9169db0c0dda1d1b626420ee546a70378ecec0a5653d4d7828a8e78546e2bc1c7772c84d258384c5b1ba2c58da085e68ca08ddfb7a8556962e2584e7e66b65a576727c645f0aaa7df1afd03066a07b08248f28381caa9f04a9fb1a8df39c83bf37727e133c4574e692ff25f94de38d81ba7b9850e4cc0f273010a5b6ca0ddebbef1c72c2d99c9daff4a4ef2404b4d90be52b3c1375e36d2a53f70251417c2eafdd72e1f772f7baf7e13a6bd5b4174058edea9b0ce0c24525e65bff64450c9b18b7241e536e20dd5ac43db1ae21e80cda431be1c6f65633671985b5a518d8b3039f1a6b95b9411b798cd368e798cbe080712deff215f0901afd4f120fec5de5b06d1596ca3ad4fc2dfca43c215bf6aeec15c7b44bb19cacc8efe434238f0df014ab696a3efd3411cc01121bbf444116503a7b70b005efdc3cbc50302ac29b00002472630375e2807fdb41cb828aa92e41877f02c9b1f157b0557f3cc65a835672a77209c8e23b69ad9926f3b14a62cc44b9e10f483c3224f061cbea70d6a6895b375a37040a44f6fbd6ad485934662c0752078b5ee4332492a808e3921bf52323d272725b34aacc87495e984d87c77f3610daec0609010eb028e7febef2bc9cf94c72b63475a6db5deec96795a457e491b33cda33ffb7d111bc4e8c86c2e506f8e8186b180dffb1ec4f6fb2c27a0ab9888bfb383debc05d41b46c50513d9ce37cd05d94d057cd74e88c907821dceae7efea1cbf0d295378383a6096d3b35969c8b323d5879667e6bcef17cde175a2434e5605d3906e7eeb6155b5519ad315d4eb96725f39f3d9235e5a18a120631bae4d9bd5fd9dff1115bb63b6ae37cb6f50e65d022acedea411b86f4dee4d8ea0ddacd527c76700933922e9bf3c78475f118e93724e3d3f6fc99a4d04c2a6279e424297f20ba80c20e8022c32ac974e666b4683724bdc345e7ed70ce7106d76843482c5f2b474e2b1d44f7353aeed4e9fa554ab7282a31b254e72b005c41713bd0615c95c379e0d5ecc4a74a98b7da5db162d7320db3f05d2c3cc6803e8c0fb5453fb57c8bcb3398403d0a6fb22f1fb3c83873e6e4396a7ad78a08ed06bf1caa3dbc3c26fb6d263ede0ce1f6516badd430a720b72996a11491ca06399ae9dc1451be9213f2e2c0a18793d697c2a33dfea44bb974be890e0d50fed333b90a0e4d04927a1c9e96321e5748a5aa0a176b504f605ce7211ea292c98aeea2a56cbe2c77562892f13bf3500d7f0dc995d2037ce19a34072c98ebbcad43cbd21d7d9e9c5fe6413cd1c8e0a66837fc7ddd45f2de54f3df57258c415e11b94bf9f9b47ed9c0267f90c27bd86bd91f2180ce0d1caec6b457e722f93d454b407e386ffe65b38f3e1b8395d25dc73d05d724d67cad9e6f2919e72b48ee5cd47184bb2587beff57134c54bf3b34ff9b3a32584ed2f15a3cf8f0972a13c91fc14fe92c89edc8c18d8032e34f4fc8d0b78be4b23581facf44c552c089b83ace77cf25cc69805a774865aa6c562ffba99afc27e84b4867e4fa5706a72b0074fb3e856aa045cdc51ec61d53ac62e8f8d9415b8291b6f40b6fd3d5020720d70055473b0d383e7a258ae06c319fa9a7f77cfbf5f30e5ee9b97c2a7fa0a7253bdccd80cfd1f8dcc7bdfa8467a84cc6147ff4d559f0a380874bf727f2c15728a1d6c93b51bdf532dc263ff368aa7db021e57bf09a174b622aa691e2913c1565c3e559bfcb8bfe84f2c2c89090a1280702aa91c7a87a9641751c26392c77f017fe4aeda68b2d71b346c803fc5dd12734eff2c61b316814366c615599c142c720680bd8058ebeffe3d5d715721c46364b095ce7087e83c62409dd6ab54571e3c947139cd7d1a02bc9fc5c5d4b9c20ba09640e4fe0230c62ef11b687e724ad13d8490fba824d6a0411a4a224db7e4936e2fee56c4156accebd7dc87b9198999716296c7baf51391f229b0c9e159acbba4ecc645e29cd902d11abfe90486dadc72d63d39e391b93e1ca7279ec03939cb59c5fd712221f11e0a499a66851654e572eb34a8ea48f5773c3442ac24fe516822476b215c62611dd3205ab33277dbd0633388a3e2ddc97a2a21866d3eacc46917d224c334b5f02ae71d012c4830bd857258683c76bef735b8f1df8776358064594c30cf6ebe4b53ebc4cf346a3d588e62943f37f7a4ace4d40bb435bbbc09befa9480526122776770379c6a2a11c5146b3f0dcbced6340d9f39c0d9f419d3d903895a5c1339a8cf5b50a4ea478377ec728f625a10fd0b675246b541df54c2a506577059dee5fae09ff62ba3f7dd4aa67233caace8cea43f943f49b8d8f3e1f92b13d8b5108ff3eb8f0b75b024cd2c9562514fb8a7bda29c4ccb9a53e1c72133dfe8b448e2c84298720655cc138898c223dd0a22a2da674dc67c9a62e27fb00a3976f7fbce6407b56467b20fa567a22272e1b73faa9af0ce73ccb5b5c5f9064c9c4dea1e92a1ecc253bca03c728b6c5172aca35ac300399d513c0ad6811dce6c099f444b4c3a01ba5d12d92726ee638b722866792a00c7ca1b7c1d4654ff128bcf50b7ed7eba04d45d9d20b1c0508ec243013f4427d5170e07d7703c7cceceae57f4ab1cff2af2438ddfa74b8f68b8736a817193b9df2ebe76b85943e59d5513c352aca60d8510f965ddf8f53019cb2972f33da6ff63f61fcd9113e9e3861e5cc2769b4fc2dccac0d13154ae480d54097252cc2e33443bd5feccc77faf5b8357f6b866241f027dae68a7563e02b7b70e5ac038809dd9e79afd15029994394a44d4f2738b6bd5a5df26feaae30fbd860a2e3f89a368b6550581a120849f3b6458722dee10a75c1ccf2b0b32cadf4dff0f723b3befd412c79d2d2b2ac81d260d873a796eca7168efa803a6325094dd4f334bb869ccc62af1994af02df957f7b9f60f1f3361790333da8bf3f25abb7cc5d6580bcfbafbac165b6b50f27a17b4d7d0d00c6e03ea3a62a27f38df1b32245a9b178cdef54dac2df066c560a3a8b65f364a2ff6ce95787c84d83da0a37718c1ee72a8011123b7679aa396e39fcd89e4726d2d93583ae0f928385ce4b58fd2f61072972790c3d467f8f8f3ebe57a1079166f494cbea9724c823d336b6ca2db39e0411a7ff0db64ab672d5607a3c7a43e5c82bfc307ecb3820f0ff06481f91523b046f3eeafc9c6b585149b269d97076c3eddb50c2a87b497c589a32be4b20a407072b07a10ddf7a4911e19041c708576df73a0c8b7f0e733a5e38f03e936385c0572cdbc59f8a83729c8bf7878ee89b91b3e62c8b04192348247c52f27f5b63b3a7232f00986602e8a18823b6b7ecc5d8e879d2b2322f3415009d79532548fae4072d101a15189d407c309f37e5cbe38c8d9c4e883109e36b201479293c0e731f8720b43a314f3a27a5ac739ed90eaa38713b220050ce09f6584fa4edc788eb4463527eaea5f1993960ced1e38796d199964f25c5dbc31905e1356f1193495215c37b3f396367d1ab4903bde894dfa8b19d838223a062f76a0097e57923e3603ad72f58a82d8b8a19579a4b4a24ef48555a7eca97bdc243488743556fc7a2a87e872095cae7df43c4cf677e41659c9089053c0efb2cfc12425ea0358808a76080c3e72b0d43c9c6ce2c9f9bf7877e6f3d4e4e749988ac8ee033c868f568ca391d87274d02433ed73f7df332ff870d68a66fadb4fef46e49aecd27b926ae6bce91c6d13e89b914cd7577d7919838e9ccfc600b36a8ffbebff9e149185f6189eb92a72a16fc13619944ac9b7456d9b02e428fda61013dce318ba4dad50daf768fd2772ae1aedc841949d933a4b3454bb853b599548313ebdf1bbe962b6d186b5dedc07adb6f74a0cd156044d3b5f0bb6b6187746022feb29d1f3d9c37e229cc1383d72377386b95c39eb4247acc334992db51c5ef7b386e71de2d1bca5b6a65e957b7250971755381ccbeb0733606c0e28cb37e3217363707cb26c1b00c6c8d2813872a25298fe46590032abec5474c4903bef37047ad3d35d83fb368c9a16518b5c72ef00a571b933deba112cc3cad036515fec71597ee569d63b835c3ab341f52f6211924d5bbc1b0ef0d742f4ce8216286a8dabc385fc24cc9dc21871bfd297a07228fe4d9e9235ad0974a655d5ce9613abfe8a271271a693a966dd17fd3e096961211f403b08d1c4bdac0f0c6a36fafe0e7e98f230e86918f34fc89f09f4932c72d02385e38c4bec5ca8a6c03b0bc675e6de7f6fedb25d8ee0b630f412c262a272f00069d6929ced3ee003bda8d06c54126ef1ceee73431ebe664f4893d8a57c331c114785905e3b1ecb530128d087d7743a356b751aa8fbc40432f88eb4b0f3726728e287dc793b4e83f3be4c6b88c4c6b7f5508b5bfad02dea5a2881551ffe721c279b12a0e21b04ee838a9265a72f90527bf61de62043242db37d22fc9cb76c16a523d7ce712ec4e01b9bff763b8b8ae3c631870739279934790e92009c761c078464864ea68401ea630cad250b6bf8a00233ca7d7fff6341682bbfda6f2b693b42d006a6853335acd61d1c75e0c12433b668c810fd1fc17873cfe99fffaa72d5c9497290189f931ad2dd367daa28108b53aebd5ffbb6133c08325301046f2610f197b17b1fec70167ec2b2a1485043c371c345e9faaf3da3b25ca54f55e172a04f51124f265b5b84fae8a687e05620acdf4caabc38a1d5d5eda499a99eaa7239f806c550fa555dbb8ad0a73875cc01e23257aee4ee99ce1658573797b1037276bf940544cc9fd0da9254468473a741a19a91c10d5553b6c3e9636aa5f957725614af1a6b2ff516ceeafa39afb6e6d3a64f35fa7b8746faa8552a99599b01720a90a1cba1a2faf6360130755e0d55e8e60cca1d44bb3d96d59993cf2e2c91728cf1a7c37b7f2c70c0182dfd593eb7c33e8de35daa6dd235b3d446910a1d030358d49c4210f157a70d6c3abff477213e3598d0af93d49249e707a8f054b7954014b5c11f466654e081ab8164210217a363c1d15350ca9f63cf1bda8770f308278f104df8161dca3385a23b0c5437e5087f9807e1100db849273328803b213f72cebdbc113cdd07b5f8abef37a46c3a3e1e50e62d233ab1d8dece56f65b4e7b72cac152380f9c4723e74bea379a0369c8ec6433216ac04314b1f8d95ba7b39f727e977f7f069a7fff479b4227782db351ffae009ce466bdf9fdab822f95863337a56b80adcdf51d7bd9859988b976add053f18ae4eee1ae6937235844734f2672ec1ed15112dab9711e53b3752f437ba5bcdaaa63c0b9ab3c13edac1c08aa2172b06990ba3c220a08515c28834f171e410c4c3a3c2287a03d655669f3ae323c720c645e4711bb4bf78b0c93f921b229e5b232b40e552644d8271054a7932ddf72d5cec1cf0ad64efc99fa7282647feca0c7803d6d173fcc29475e5a66638f8172a9d6f48d611fe1438c57d9fe82481cbe213856c8d7c7da654f200616d84a3b729ae01ec6dc2d53f466709ec8117d9e1f7f591f29f6d3f74b34169a921ab841720a42b343590412c4e26691ab57e8aeabae6c5782b9fc085327bec0519bf83264243372e47604c67a6595019a92d80404272464bddf209bde884038eab0671f727e25fbcdb549f13f2fa921410c844991f34fc64e15ad594c57cbc109c785397204dd0b9dad09f049803d8aed8f52cabc1b93972b7a1639e9625457d9f04f0c7253fb44a79f1ed77d347d7984b3af0f293888f91fbc69ca81591d10496233ce1f14733f6397f1bac0acc1194b001a022dfeeb558072565df414059981bb37d552f29a6419069ead3b554ee83ae26272096411c269ac5b37f8d4c6af489944727074ee79479eb6c0747d00bab30851b0baa06c4eb0af0a0ce1d7c27a6ba20e3c72437f71288e3f77446fd9532d43e473db30d907ff327b89274b79a9331fc40b72d437b8db29729c1fe10293d4b9c20f85ae8965eb25c808ff0b9594f4c929ed5926a517952b5cf170e89d84f8863d9e9bc3b3fe209fa9eab980485fb4053b580ab93ef1b85826a212da88e9b21673ac9944cdadb4b9a56a505bf747cc488ab411bf6e37b34d641713a652798066d3b18108bd5defd900f4bb10135a1f437ef4054daea8fb519371afee4fdc41b9ce994817292883aa3afab137dc600a3fa67472fd43173d2512a4ee7c23260fae04e5cdef5867cb23e02e4a49087e147b153572c442c4fa2c6fc3e0c508c7e6f6f8f4709653611496cad15173b6be2fb3c12972cfc05623e7e395fc59220559de1736b352099fd21503239e0514d9dadfa2cf1d0641eb2ebd6cd8dd1bb8e780d0282872b833beab3b3257ae7d453a80c4fcc756bc7902dadecb6678b06b0d310ef974b9c389d2de28304dde6137b05632298c72ccf784ed44e63418d844e7e7526d6fb06540db532397e311f66691d196e0ff1c11801477c60fec8c8090c0c0fc73c0a641a2f642827e6f3daed88cdcbe18a972725dd258b8303cbc59d01ff5f1a6ce4c5bdb250d7c8ed67832a806da58ec3852db89d0a4b636515879bea941750cdc461903d303cf4748b717eb482790484a547a00783f6c8f86bfe04bc877215c76f24213c6d8c739e63b57db802647634c724849ca9a6916769f004c3391541a353a9c68da0a856ee15b9a853eac8da096511c91098ff3c420b3ee468a2f327e098c4b213c59cfd5c25c3f8006d2bf9a8734e97ed423d8a5611f74320389bf2823f59163edebf31610d29b649684c940051aa3c11a78d0a42d4dd86abee936842a6ffda12cb2cd7f96c15a330f6d0e2dc554707304cff24aff99267c5ce308c65edc2f6d84cf08415a7ee4e974d0f3ce7d727581b7a6a0d24056fad9971db30bab8d419ce3e9ca12f040e9689b0fd7670d72170e0025a05b41fd633c0fe746e9f587d19e7bd76ccc7cb05a59a44fc081e47215b38d7a8df52e56c8b52a87e2905f175d4a218919511d8a83ff1c9b4ebbbe6a7dbf28e2456521592e9bff1811300b45749362f3e0e27ed9d5f7078a5329c9721e87a971582eeb73e8dd7892bc03772a858a8dee3c460568622afc9490e2424deb029ce98f3080fc53aaad787489a62c24703f853d8cdd35e9996daa0ab591494eab37cca56641828a579c215650bd186c0e6962f528c929d916dccf09b0b7304aa3d4a7ae92b5764787f6dfc1d5d40cf24daaf7829f8a3a512a1eeb7410dc167e6fb05f55208b6b2a48dce9961c9db556a59b6759fbab7f53fbb79f66ae680d10433ba0bd15503ff3fb719ed21c6eb41f4cda5255ccefecacc34e44a84a1672d62b4bb95df1311f3a3170788d31ae96a6c01ea3c3be06d62380d830bfa2da3e9639a0badee965350dde9f91c1a9ad0de599ebdd1b61df7107627131d876ea72bc852dccbe5c3a54c56c51a7cf2e964fb34a38441a9fc5dd52d49a90aaf87e3da80cce366d1152b71a740ed2c340710b204205a8276fd6c5a61a417488b62412668ee3d28eadeac13b6aa0fb1b395a294d6447f8c48deb288b40bd90a308646cde0e71dc286687fb29aea81a2c8a475719760b231c29c8d7adbab11136c04b4662a28c5248a86cd2146147f65f00c4eb394edac40ed18d40bc93b0a77962987281f04a42c219e3c68a074c6d895fb2c31503ccebedde4f4d86341c3fea794a72ad47b578df2311ba14de232b8c7ffd5b86b0fafb267233522e91d1321bcc9e011261e3db9115dcd32e72f9d1787a291f730a20907151674f38142092154c8b293a64c98850f362c7c41bd962b90a339ac881c4b32767f232f12f3006f84bf16aa720526d6fdebfe505068296c58b0bb6547c723d417e1d5899df72afeae784721d40ed6387849fe0c3151d53d6fbb78b5df9f890ac17ab53daa81257ae242872aa27721d5f35182d38cbf94e8edd8ba20af08f6369e8d9f8f0983cf82668205ae496f89f111449615d2277d5bba03ab8da92857179fec0fa557e61d73c72a90d1037d7a1cbcc3e167e94497e1251b200a7de03f3dcf70adad63e76aba9acda720acc9b30ca397bb50c9f4711dc37579f9316f77cbfbbf0c673b68a630dbdc364bf83f697a77d218970d0817a7d026bf2057a40323e4f35ab3e8839d8f19c226a67c4f6a0569dd17659f152f82f8c2f6b63c317277d7c649f33e1df0d5b5f075a4cb186893ee4b84464884b041f5385c1905742dde8bff622ed1ef84ee953592ed36b7455b304e587e211a78284c9b5f22093282716931c3aa9eb5d898d59705e97ec250d517f296a3c1326e6d987fde7f009f480895fa86c008654cdf7bd6607e7aa47dadc2cef57d7f6bf4f0efa6a3e2bb2d7cd176b511e04d6bf4eb504e372d9fabd36a9749e2a51c481c35b2a79e4c0570efd12463671d89f7d654c19c87212398bbde634372a61a05f8e1f7e0c16050725df8e1e3e8a0e5f10360072094937d2d6194ada70df2813d11f1485998f10789a62d63d74c293715003720b0e72be9ed1d95995b05ad2ea6ec19656c8bbba960ee0b09579a9e0d4d1d3ac4d897292e4143779cca72e6447c9f05dea2d5071bd4c6251620b9984b50b0557c10772cc7d8c696cce6a0819cd3add775f4ff31f1ff6573e67aca9f61e1ca06fa1fa728214674c8c7c2cd593f49ce26cceac637e23c25ee83d23fa29e6e37422907f727217a253705d68fba584bd2706f2b41f29060192496e5a80e42a07d4657da343480e049b35dbd5e84410f97920ff15d0cc58a02a0be7433e655494c3a5c49e4a7ca477d89188f13d38c297b9221275a956875dd9593e54896756c77876144865de0c912cf7d550eb4b68d6342a2eb9404e92679b9822e66f77bb7a1c9c5dcb42b08e7effe9b1b6d08f8fba754c0b56962ec4805212c8d9a17436b45bd5545e1fd80d4a84e2e83e5c156873fd3da6ab4797bbadc78e385c7f86779902c6ffaa72681a88722fe781bcb1b6ab679273fb9af8555fc10856a197daf8e07e5a1a21720394e7bd32c33511fe571f847572c9b70d6ee40414c0069f8bc13bc5b461df72fea370a7573af26f8a731cb08de9d79ed2aab62bcfbc0cf892c13536bc720770983bbc955c87d7c3ed123ca8d00e002536e846ec55f3fdb998d0c78b3debc00ec49576e7140ad4dd1a294cee0bd908c3d9bab4a1b6ff3de6e9ed7b9900b6ff72c13f5e7c3209301f453d507bceac3f6f68244c2bcff9105797bde71840ec9872c135e7419ee06dee38aa44a489b6159fb9a3e6843e1cc5188675094f170a6472ff13eb3f463a22286d5f889faeb24e6bff3d37ab4eab49a1973d92989a22751881b14c629cd760f011986eb929402868025600af70da46072968e8d7a5ea61081857433fff395f3a9cac7b432de2c659d057deebf66e678a594cc728420aa7721047cec76ededb12e1bf266232a18cb8a852574a7384f79e5384fb89b2d99672bb1ec388425dd52a6424f3c6655c6bf00a385b1836aaf88a7c9907f2ce45197126c3ebf20bf7a6ddfa31f71610f3aa0ad568107791fd869f60a24205bdceed72b4488301f4c2bfad881def00ccee1f001e1d5b44bf785a940ec2c5944b05fc72e49932847fb3dbdac6ceab8785de744a81655df4e4c5b43a099110c2625fac06c0827b312f6914a5f4246e311d578d1d419a6d7dabe6e70f60e7661126bb475d422db35d6c5e7469f4dd228cab55e4ec429a4340c80ced50e26ddb9bee2bee5f32902c7da4cfff01c5cb47d93a2a530883896371d47e5661f7b3dd2af0dd280845711aa21f0b0f6a3354ac1ff425e9717fa86068328df5a268bc8052304bb0721a8243d7ed697a599a97caff12b12a2757fde2948d01c46c25fd7c8ca0120772fb3f4b8d8c576e5d763b1cf3ea159372903ef451b11375f8b6a27e181675d4561acd07e573dda79a1caf095f65ffce159bd6d9eaa554263a06085eedbd056958a23ebaea0608c5da1af8a2ca827060b7a8029c55c08c60242e6417d3da102d65e7802e812fbe2b34aaa9ec28606e114b4a0bc484e06e7d24f28bca8b0158da3623f99caee7548d380f2889a411fb6b2bc17d8f79788317ab9650390a969e8d72be9910f03cf865a2827dc5ca8df0a6402b6647a9fdca978155d553e7f210c7401182a860cb23bd553c769ec9ec0c27ce7060dca2a4d221f3ae11297e070258717b1b0b4660bd3ad500d43ac1c7431ee3dd98c5c465f12fe3ad8a5ef64afba272c311f296903487adbd9c65fe76d1ca5b694832ab8fb4bffffd07cf21073ad434d01f6fa2f2c9c57baf6709e7bba18a8c0c0fca45c4afd3baadca5d3e6a69ac7269d2c9ca892c66fbcc59690db7a94f1b2b041f5f59f054b3008e9239b7b40772c7d4301eeb8af727b100741fd20d144f5ac6dd9c7e34320db9fbd57aa15d7e720b6d1ef04602ebf7d2cda9c9853a041d8789893bef98a2a165458e68fea5427123118eddf2a7e8cab0a11d0c50250befe2b6923f645f91ce6b5043f4893bb4727cb4b0d65af1dc97f2845f30c73a2d92a2efddb14ff3675c63078dedf60d2c2de3cdc64b2846576001bb00404e269b832b9608c8999cefede5e9b9e60d3666722268f589aa971711e54b60a1fc72768f3e019337f92941f83aa4c9d3e5cc9122f931b2c0eb620ec1c831b5b181c7912d6cdd2c306772d6441c7d11f554d40e721bde82a4297c3dbb16b612d7bf082aa9ddfaebff74fe2819d6e555c4aa7f46429650d1788fc47638e08586753b782cbf14766621bb19e3e6560f9eadc6cc6f5eaf597097a06358f28edcd8406bcd5f706745c0bcdb1a31b37d0ec17e0d00747240fe58e5b64fb8d0697d41babdc50ca1ae5058c848437466ba4fa317d3ed0b72e4d6965475f5b5a47ac16c45f1df36b00ab39248988af12dd6b97bb1b802c772e11a8796ae26ec725ae3c2b806b601b2caa0094bd9cfd9233d6f0a7966fae1723cb8f329774b32142c7a5b8b7ffaa1389962c053a9f94eeb8a576640bb033172f387a5b6d0995f86ec3c6cccbbe3a03f96a4468258d50886b9534edf56fabc06cfed677dd8b73fbc84d99a89251d4466c9751cd103be43558d505c798c3cf6720c70c78ac5dc5fc61e426d89522753bf3a5a09bf8f5615ab0ae327b05254a47202f4f98bf8acf63caf76b80fbc17c89075e875d44f188f3ae8d9a5f9059eea72dafe3c5e1f3e7e1bfea89da7d691629a340e3bad18f5c8d25667e1aeff156a7296c6e8b9654d725aad7fc9a93fa22b79f490b8db22700838de49e697d47f67721fc528f41c67e79ac85df3eaeb03af751ec42cbbcc4bb95d46d0e29d477c6272917b8fe5123c1bbe0390f4319017f96b4d22fcc58e9a739a150617ad6070ff0ae6b33a8bce44c31cffd209caffcb40749f4a40ccb0e32b16391f1950ac07b171bcde6852d5ce30f53424e881a60fdebeb701925f706fefda3ce30dbad192157285e571ba87728a7cfe19d7db845de62494ffe45322f55cdd18d135376e9590728c8511abc3b5e503b85892626c75261bba3d7ab1f17a83fb60d5398c89101572200b938d69c21fe2b55581a3537883905a5249529a2947ec023c73c786a9427290684db82190f7f7620f4479d931b25d34e451fdf71b6204420006380df1b87269982bb138ecf410e7552b295f0f4a765def727f795195c59ca45d6418c1325ec26e89009b40536f7b8bfe9dcf8e71bcdd88b543bc85c8be49341620b66e38728d4645d9269d6ee6567b72d36c6807a19e86ae597321237e28678332b645da7264c38e178c6d17e0718405ddc3f94ffedab1e21359c428313064ba77566d75729f6b774cc95fe29d0130528407f14d3838d6d2c079f8b28b98ef5545cab7b04419d6265ac8da10fc3b3c8e6bd3773c29fcf7f3a19cac7aed0c9211079c13ca72cd4b4d063d6303318996a8c8f4a75b6e79321211e9d7b134c8f0b92682ec132520208ab78b814cfe47c208ff397f5829a2038ff47ddf940487492d80f97a52728af9c59cecc22f4794433a338a4b7951761c64feed8c10ad3b13ec2a85661b72f8d148c9837a683c10663cbed0c66571c43b8e1fe04dbb027b412dde258d8b729ceaeda729987a4d6b15e9baa7d48dfd083b8c038340171f08ab29510d92e3725e86e3510ecf39ac6204960ec7f4aec801ca2c1b5e3d52b8609bb6b3da25cc7248db1e2e45415bd5631fecc8d310120553074025ae72cb3ba5df72a1d73a8c701f6a3ab2109df9788aa83751a94d29a53cdd8f195d8ca80bfb51fa80dbd5fa7291a04226565d1c59a00b4a0e7c4b830793d2356cbb6a5da954d63eb1e14c8539259109f3c1ae1759711558aae0e8e8b3ee3887b42ccdfcc6b213ff7f6294c972d9a740bd41bdf585cfe52a8b1a856b48020877e9bf42163d54cfefeae7c4e572ec757260e1191feadb76678c25228f7aada86a957b92c268c830c5cb75e3cf7229a91e5983d7965c0adfd0ec780753146acbab01d563835f26d638efd860c709e3a1d06b10d583258e990fd507181fd9550a49dc402af047c826a7fe243509417f17081c545312f9086b2060c4a92883924986ecf127226b1e5b91fe4dc6b0722a1768996ec387d8a3a6ccf093a5c97d4522cf379df00b89b9c4de0a8df28d721c65ae24ffb1b72001a68abe9fff0360ab07f23f24eee80eb7be7387e606e372d036e0a86769f50764a96877272b4937ed3917e7cb8db7f4d2c73ab2bff74b72363ce79c23e2566ad5d1708231fca9224b025c060c39851fa294cc94c66d7772cde9f14f64f1459b46ce9615add21e0f0b10015a071252f19f6933a4d849cc27d82c05c1e9ac7a90800c0c3c3148909b1dab3de946b2f5413441251decbbdb0ad3f67f088f00d238bfd13fca17b3a48a9627d42e282aa6c5e3afaff3a21bcc06978fdee565c1e1f30396aaf03b2daf444e09cb180e5d2e0586ad0bc0d9d6981b4c850b346351213df09d13e21049965a9432d410e251f13cb3d9a7c0ed29127282788be77f55c329c6ff45ca9b7f2d2b42e9c32f834dd441b514bfb68368ca72583fc2dde402660ed617b795a2fcb1d781dca8e322a1cc2290453cc6fb5e5133fa26da7e62b77b098bb2098061e65a978b51fc96dfb295e6377e7522d3949c1cd861ca2e3e124a7510e737ef1577f48c48ce7be8c921f14502c15d5c4d5e56722ff57401675dc613e160f42657489db3ff4f83f52d6e92495e5745d23d8fae724eb5d4fe02a7065b5289daa0209a3721f802c44e8255132af9b17801973d24729ec13406b8673d8c8686ad9780486ceb8d4f127d67a930c114da4206ccf75a46d5b73b572c2f27f5599981ca5b2438ec7165901a8117ab476620027e23fdf072eafbb861325f10feae239470cde4af5cd22a215c61c6a657f9620f5d98df80720c626c3e429323e22b3c3c5e0966455c8ecafb1b29acad8fed3be8a3ada0df72dc0b557a7298e3478a3f0d64dacd64704a41437dff6868da43e382e6b6e1ff720a09320d0d86c15dd0975dc821f5b3c5e00c2ffa60c1d002df604e10d453b012c243680610798737208f9ce478a8fc6a98dde22d1650efa47e9d149f331f465c30039f317862ce07bc0f430dc00f3e39822d90d9f1bc13296707622fa4b43072d20bd0d3ce2de2d150f0054fcd386a6840ef53a9a1f020b560a302d406dbda1b53577f790bd1c759ca2fa43d92babfedca3bf525f065275acaf89438b40f4d727680542fdb79d3f33426746b789373f3d233095e60d3eaea29b346ee6794b34645d87106fb9d891748351d567fecaec10a5695f40a691d8c5a0023f45ca4e357afe05269174fc8882979be2980682bd13da59a85fbef38457db33df12830f016b51e5d40c3915eab6c0d612b36cfa0d4a247cc48b89d9b3f5d0b1c6780d41511c1643bb03904fde2d16ee49702b60bf4b6b6ca8d1c0b8c6bb2df969a465f667268d47f46c2c0871766a3110b110d72a970deb88adf33cfb1550cedb48fd268720a8aac4d28d182b314defa20f3ee7621c175a332079c09f5d1134d591000e6539572f23dcec32e88fccbf4060a71a0e86288f0d2158e183d31e1d4f4afb2b245721ba7bf2a684234ea0416d2238ebf72f32a34f658b781d4cb6ed6ed19bbc066db91872b4cc9c19165ac9c3598168e0676e03546fb37b0e5073e6c2da9dfa36109cd61d352fc9138b940ac82978f5c58340fc213c44e33a2e2b85b221e83cb7232f162bba98e343e59b513b49f02ba930a4fa62baa776d39dade933ce7af497249d431ae97e684f174f3404547e79f31cbf8a609f0017d389dc06db88808d50e544ce548f60b7ec278d358d4c04e6e83abcc025554e95772e699e4cb946e2f09c137d19bb72725f03c0af5589d327cdd97c704ca9a6df35216e4e0d36ee3f372f38f9c63a63fd4f0710229250b1e822f81ac2d84c0cac62a7c055b3d8213a47237bedcb43de4a958b1d04465b02419e91d6de86b2d794d2e16e8eeec0a9d8772fb32c88b80926ef9ce8badf24089e99f6497458471fa69026631283c2db48251be8761c64b219a66349778e0bad9818c0a01d0d715e3599381c2b19fc7e083393dd2ae9674bbd6dcb0e8a1cb8e3eab32140349dd6d758c2f8fa78390869ff072a689899015cc4dc63a289d34f9e0f94774f61a7f71114229b7392b797ce67366aad8328da88069ee62d83910ec76d61adcb00cac209f0554768f71f205f52e72065ade005c2082f1546b1b742e24ddeb7771d435ae5d6ee89ae1ddfea406b60aa5547565c6f4f584dece33a69d91a1cb7f72a739170ec43aa452ddfd0a7f906a84832e4c5ea91be09e789898ec1adb30f562b52c55c69925d907b43d60c7d672523d867f039fff5ef7bf8d5ea78224831c1bb0590209ec40bc28b59761579e7295831b1d71808bce2ac6e3395105a3a5c509def01cd91c46a582850ce99e2172bf9f4fec43ffdcd0250bc317a58a99ca636873e6e755a7de325f51db9b26c672850c5c7a65382c68927a35d61278506c19100d986ab7721a30100ce9fdfd46727eac0ec9ddb0072c0672c303fa399ec157474a2e85623d7373814078f2391059415518af5f83ef9af766f143109f4d5fd6c593b443408b71c164c60da67e047209fdd16bb96eb48e5af1588147e5493bb045516adbf37c5b6bed956d7b0dcf7243264cf04612a1f2a0616cdd57a6f6c028e13f56c263fa6b8da30769a7f73472a2a37e645e865039a0f822a391bc0a0563098fe3851e85b51a8e3d47b3bc7f21ff89fc5f5c7bed1ccd5ae9a70e1d9f9f785d75b24162169e727ec0c616f8b94fbfc1a927e9da7f25f20912dce600e6fcad8bbf0040642fb8cec7c94923c48e725afb6ddaf5946c66e520e1d6278cec31a84d7858dd6b8ff273f71ee885d85f726e52a8ba52fe2af086556e73ded8edbd539bca2af5f11771f11545d49045fe037b01772db8d11d402a7e5ae77fc7ed6eb4a743bb903696e86782c338b901d6728a9ca2363159f23345a192be4d969be3ba31557876e042b919fade8a894bfd724cbae5879aaaaaf5fd63eab1057ee9f98f223164e09b5cfcd53b8995ebb5c672c460f2dd08d6f692a89355e203e8dd2b8acb1c30507806a1492deb3ff156cc7239da0414d502edc85e46e17e264654eb104e66670088876c61ec18ac2b2a6750ac9ad4ccdef953a21a41f85b5a676afd6d8bf7455a3afcfdbac989db7e95815ad93eeec476f8bdce98025cc74f0470c09ce7ce6ff6fd3b20acd3c8f3cc1bff0dc983a5eb8193b62457744ca88cdb290cb0ad857fe72611a25530297e90e4b61bd802a1a2d483a5e659c2295073673f16334809d22bb83e82c82656e5ff46d1720b4321c44e4add891290bae9fb88d132810152f31b9b8581fb00aa5426761c6fda42b9c28ed4dafe1b122d393200cd7dbf4d2b863c37d44e66b491626ad54d6db8dc3565f1ac790381bb9c898ad3780f78a4d4323023eb5b9c7bea892f94a9586109db7db786e115b5520a1104d18cce5fc6b9a37218413ceded7f08fb0a517228624b017b377f2cc50b009fab9233d62ff6ada37ea12d88b59af5bfddccba193227241d6a03f063917815f740f0f70e3f4b49f1b18f78d5e34dd841e2f3107294b853c9f4459fb96e17dc0acf6791889014ca6c748083876e39dfd6e789d97210223bb53e9974f7f29f52d7cad22c18b446977d83c339f95ca8a08360472409ed8762b88aca155c517184cdd372dfa1a25780cb119c42032f666b66de945172029df3ee3c1901c041f0e029acb709782ff2d196cb5bcb033a8f77972c0ca172720b2202eac0021f730d152160eb3a6e413c71a558309bd61f34d259ff622118b294affadc289155725a2d393c51cfc5fe61194f9b7017e65bdfc661e861566b132e7887807263208efe4388d5ff4e233b35feb178bf1b37f2501aa0dd643672a4e0355fa73f38918daf4af3bdf3cce455a1afc15dd137a516fe861e29eb26582e1536ef6c5c9215a97f2b36ffb431b26282df6cadcb7caff60265b8d839981d800caa65ed81d3a153225ce8fe1e9875e9455a34a51b593e183b7ac034887472c9be415ac16b43fdda0a5596d3332253ea28a15cb73d0a2421d16ae73a4d6f720ba1a560737cabc9677b082b0cbc7baedea32cfbff42fc7ba2ced0d7f8ff4f459f14b86dd80315218ec340a1c5c9734668730d00b006412433dc88614a54434e54a7f0c03c379a5a2a597c0d482844aa908aff5f7b27c24973e2c99c7bd3a77289ec85f8710688cdbe7dce0b8c01a7d8f9f9d0521142747dd4d055ef7848cf0cf4d975845ae5008ad8d74005bcd4a343cc6df51470f93f2ef7914925c0c50f679093503fb3ce474dbdcb528ec49beb87a3f7986eb85ef93ca3f33ca1c1721172e00ff9aceb26b89e6cd95a9badfd35516fd0e0ae2b1e50679129a62e77b8875099652f7a06902bfb526a20da4e0cbf4cd0da7119d66841c0bbd3b4f253a63f0a3eee80ff1715f7d3229d5a6f762bf0c84fe52a85ba6bf4eeceb9471b922b9072aa8de96942164eb87c912925a9dc9657a4983b58203dac9927a6ba8692182972602e151705fc5465083a84635528e031a12906c1c0cfade3e11f2ef3c082f97218d3b54ca14c86c876fef574607b47eeb9b8777072e5c94de9c896f523de22579f697447422ce647cfd9513ac7e2e91ebe6b4a5fd4f7f5ff8f8ba022d4b56d32a5fa0dd3a7759a32385356aff7960afa4d6a449d3eb0accf32503060e19f2072e1fce66c90b6634657b4598dfdcad0a9817f7b8e991679ace08725e2ebbd5872d1bea42129363ee7fea2b364f0c9007f1deb6cdbd24ceac9f95b4800026df77287b7a9f40194639d7366affbdf1774b8dcc750bde373f46c8c71d9ac67f0f072a3331d5c9a22e2ef93d436edd239c1f1966d7e76edfec77d3049ca8573cf0072bfcc0f5c202301515753e82683d1a51357301b515fd42b9aae6c2564360f107250eb29aa771a03e514ea4fe98060e05f8d1c5deb3ee885e03fefac49642145721c16a051b35fe1ea5e76040a811e3f2401415ecb1111bd51d3d9286db7fa4b72beb6f779be78ab71d1b0941b8e03c1eab58c496424423c8bccdb20e112621972a76e5fe4e76d603f778f967c772e266689b8e8913fde98fbf10112a7124d23076e0701cccbb83b1fee1f7f072dde3eb78988865adf034bbd3ffd69c52e525b72795d50467c3fdb70b9d90861391258d4bb6c05f360a1f22631492e6858192315bedef75f774504eed48fe64a32822c1c5197334ea1d20e1392b70919982e285960a9a6df513be308c84ec610dad45c40462608bcf5337b38704f3649facb3b1c785e4a9d939a380234e9cbfa6bd9ac387cd099008314f8b5d5b9c3a9a2a91e240276ffaf623e960ea43ac62d868ea5a0bad9c5c3fbbb7b9f6af4c4546b364518b2b4f7eaffa2c0e85aa3104c51efcc3139fdd7e5eb50f9449567573c4090930e43a34270adfaf31ba10c2234ffb696ddb82b3fcb773dc1a398a960345b25f4720659fa8abb99c4580bcf444b8ccb00cad91f1b77d27005b87f5f58f81ea8f15f5bb4d4a068348ae180c0ce5eaa9a76ac27a35949c4762292a2e483f3e6d232688914c277d81251d2d0884405ecf6fc2fb14a9332f524cdea7d9b2031f56a2172540df0b6873dcd6521729ce297dc3aa732a97b1a0eb19ecb1e543036197a3772bf6ccb6f15a14e7e97ac45a5797bf39715b0095657c6ac08c90ef1096acaee726c40c6bc4bb55de63f04b8642c30a6dfe35642f8149e72b173350823cb9be9722794deccbb2f504785041adcc724f4b1912d1387121832aa1c5b0772eed94172f68d38e0003956f5cc54e880ee60397029d4469adcdb11056a00cb3d325377724fa7710ff323edc4db5fc962f4f6910ffbd2072a62c689d7fc52b024bcebef72fb894839f24b007cf3e1e7f4df450d4d4a981501597e940004a1092ae1c6ad72ca0406bb2c77f58595ab71df15a2479eff86e375345a0ce7d0a9cde5caf7b972f557c5a34c5eb3a838967320bd6010023e47512ba4b118f44ea946e9871ea93e2a1fa0811343e5b922f5d4061a7d093719c900eaff801dd6063ed321fbe3a708b968ad85e58c5c268316a2dcc816b743d7ad8964f183d331b59f91d6c7072e72dc6237b9530829691ce42210d48dfe798d8832e667734559aae486eb613581721aec2cc75e144706f7a7c6db8fe759b8954f08cb251d3979e97fc6f47b376b566ece3efdd670cae6bd22a2e2a439bb4dfc4b5589ef4a2b2ed4ecd238c7f58f72427f9990421a580e0ab938164ee9eaa75830a7b0a46aa5d6a15d823819673472c7fb32129098c147f86292614ad0754cc374afc884943427786ddce954415e723b3f7fb996849df4f33b2b1258f683a94c5fff3c3a310af31c866fc7f4b0cb7263961f01aba9d57645b54d7c214fcbafbe3009590aad30a00af10c09236977620f1b3b09185e432ad11392936e5c21bb26d1cd5e9afa90b5103e6b2f2c414c7290c7a739c736cd2a766085909a06d6916de1d42b8e27dd91985213e633a91d724572c3d26330e4e71152cf02d0d5d475d50f8f2a2112722fcef64693a90a4f724979c988ecc0342857e9383f80a572fed08d590f8298100bb326d7815f0c8272d55d5a1efc3f678f8a55a65ed516d78ff3977120ee4308fc6bdd0eca2e45294071ab8b0c82c979c2fac355deacfdba942dda03e1ab1cd9d0724ea9ad0b8dcb629de2f2d7da45be5ab609dd3b99ab057b51f3b6cd4886ca33f560da16396d5372e06595e091fbed3597f425c175445258a4a63b38d81a0d47d3076943b0f556401073eb52151ed1e770f15591727030705ee7fc7cbab16a5ed45d807b4afb7e728b53903fb909e733a9c359b7f72fdf7a798a727c557d3942cf0c346007f54b729f3515e8e7a32e8c39606cf34c3b8ddd5530366e215f7921e9d3e9cea68a56723378c014c060b435e2d7190f357c9674bdba6ad6febc6be21327fac1dfc729110689c1dce8b1ba53c33e07e593e6950d1e312b4c36c8c6a4a4dd19903bc93a72864b0813da65177761678ef3d3653c7b6164f066f7afa35e061672b64b317e723d86c566307ba95185f46f34928c4c60f8a2bd387d229f934b32339a71760c02dc3df21b5f764a67b566dda99f921536fbbe83da5b0408e9668d37fe17e2177274e37b13494147c6d64dfa1714ea3aa218a705a898343da1f76d5661f6be7272712b3e166b2ee63a10b2a52e1e1dec8a3894f4a4e2761f3d4a3020014503126c2cca7e3bf445ddaffe9221f3a059a4b0cc7ea3c7388c6e8bb5c3eb7c35d0e7359ae2bb42c3721ea67dbef9692bd183efc792f6f747525da7ae0003f087c02652d81c323afdabaf7e3618812934d0a5e000aac049df46cd48185ae3c61b2e4c7202e1e1c53dd58885df5db87d86bfefbf109fa0bf9c86d9c3c46bff8595195f6a418e9a7d2a1596da27c833ad061824d1a92672c9d661906dc0327cdcdebb787275533af39a773a57aa2d20b506af71b10bb240069aef8b8bcfb45a5c2983fd7242eaf277945a477ff6d5bcb66459e4c75ae709ec84c469cd9375fc2e62313f72d5e31fdddb80e3d615deb06278d40a02f8cc531d0c2079eec058558ebcc84728b04eb1f5d44d111da77030e0ca1e5f3d98518bbad13e650e1c93682aceda8b724c90b6d52ec4662f2aa149d90accf1a4b5ebed5d0d56b026a35f4faec8afc472d940110de0ae9e83f80d36521041fed7176196f5bec2fd42a0ec0f7b3719917261883e37597aa5e683de6baff4bbfac7d96179c65b109c85bde569122136187215b0ee515d1047f9b3d293b866c68d52d8cc96beb5c0811d2ed453f0ca258172151084c2509e1cceb867c735020e4bee09c48cd015cc68fadc89c46303cc0a576a4c3734418db7d3fa05d681599f200fb2067a64eb7016bfe244992086eb3d721ec3359c06d23eb3ca4b9ae41cfb00881af7bc9eb318a429f2249d666a5fed0ba2e0f7cda735b370dcbaacb7448e92f97211e5785a55895748475f47240a057281b3badb870a9c77c90cc40dd9c2cf6f2ee0e799d5d5c7ce42d739c30ee0e7724d4f69dda1e52ad00c09ee6f212f480cb2ed8acb4d86a3204d6378041376e12f320c645ce62763002043c2a6ee86af2c1bb9019564264e96cea0eee4da4ed272d54f76836c4242e0498bbb0a64f9b1c44994f1df8581e0e6a730a3e11b6e8c72fc87dd6af1ad91d9d2e23ed8e854b5cbfc9144aef6634e27ba22c43c84d42c07c39c81406813e7afb75b2b82f64d2a094d932e55d2f0ce501c943e088e21834c0e87dca14153e7dc735d2f98c2f657238257d46774a271ee8c9deeb73f76594d2153711b505fb8730790e1fd8e56cec31d4185c63c03070816a9e5f81c6642191f088e936885ce9c85a19aa3ff97390ae60240561698a83cf92260840b11ae72c39953fd5dcbbc700745a5ba06bb019af3e24ab30335de2403de289f80559b5c1fa684c4b237255d73def036ed9a27f4367fae142213c1b24a0519fe2d411672ec948d1fc720446c539ee8f5c407b147baa8081601b64d5b120e22812c74d106752184e722927bd1253905528ffbcd9d73ea9dbe347af1ad8b7956ef3d741f161c8e577e2bd0ff836d7b1b5937a27f2782ca9ee13da3e111010dc9e883b9e519f3f86e5736de297d34be6769653b8b68bce398987ca8f14f7bb0198e5a268a729fa421c6eaf1707719074d582ad688d45b26c9d4ac560661f8d162061e4b3c239d369d5b91b9f9ef59956113b3620015adb88a6fc61ca5cfbe75e1ec5f66926996f35ed9ef3109a98593bf3a9ba10b0fafb1decce14a063d7d22bdb58f8a9d72ac0573343acec7c545fb67f8d03ce686940998806b8ccdbdd1eac8635e4f2856b7f9f30af20c3e8f883acdf50bdbb689f9e15a707b58f9ddfc49bf50e2192645181dbf42d2a8ffc47e75cfe9126e2ca15751bdae1abfbb6a7788d238f0044b727b056e250d093c92515aea4b14cd3853b0769abd46001350a53060e0a0352f1843d9904f338429d48be9f6b0860ab0fc88a0b7a5915c6d357d6fc5e6b03a5225d05e5a15b0cf5834501475f56efc635fafb03b73c5b35ddde7c3f3ee7669685a7d9832befd1e1cd33bd7a592f50fb5a756c050e2b89c913505f65d3f81a3f16996a6604e510882269c5bac6d24e65325888eccbf4a1e71e10b37c74b21e6b772c0daa209ab399f282561f88569794098f5b6bf79e297826c08392dbb5b3f1f72e29392c507d55f197e76e5ec631ef189b5321e44e95588f45f3b12b7057d317203a610c7e1f08d369369f61efd908468f35089c29dfc2128c9e12b2cec179b6d93c1ec6593a67b2364f4549fdc34c158490f318aa2f6ec6f4f33100dd352565c25dc388ef2a63142f9e61d5be1da59a5733591c264f6de389f318b9fc22ec772aa36b3f0def994b00f48a63dc4ae05ddc60df3089b86e54fe8b334ca784a370326a3294b058e71994ad5dfff048916e9640a323c4e0ee11152939c2a09dc49723397b32d4ea465bc53136f7df89757529d83be5f1598f55fd937032f10449b72c658a872fcb6d49ab053e40485122676a324c17ba18fbc070420752ec9cddc724c2e95662e126272c7fa0c790570aa213e8ff7dfec085da45efe5ecc7acaed72425f5a5143110075b59850c955ea8b34d09cc542559cd17a1946dcaad6ac3e7249eb8e9b1fca77670a1a5dd7d352fd18d94809ad102c78c932361d036ae9e7723f5fd162640efdf09e1eb50640ffe0e7e477dbba4c46a6a1162f0b9029ebab7228ef32caf3961bb8374c0d75f825beef04c7ed9b324e9ee96e0a3549db0e3d72dc3985bdbee5bc10b8c8cdb1e13c9a64bdedd4be996576018a3cf6604d6e5472dc2a9cfd6f6dabc2f5abe9a0474207620fba059972233526d09bfcbf5c697f727e545e163830d717b361ec0917362de3572ce6bf98551e360014b628283c5e722be8aef6b2ff2b7cc35f6f5724be208e55b5668c7dc72ec3ea447dfd1d61173246fb0cee9fe25bd510403fda3ed0d9a958c6e970bd1a0eaab3628257ed3e5a72cb046748aee28166365a85cf692c4cd74f28ea130ec1c7cc5cf6b805ab97f172609167f7dd4259189cfafdd3bddf2914cb6cc6245313d25e1b3893e326ffc7121a93124a0ba3997ae525104d79736b0f563163382639e3469a131f5330b44c729ef4fe392fa8ac250029256b5d38e9ff57d4a762b6b7e6c55029485ef1ecdb57a507c8edee36204dca67539129f3a00bbde9b78cfff95a1dd611a307678d2d4a4cbd604f57f38a3478a62dba9697b6dea8835c697b340db9a36ed1d1a0ae053ac76c84524d7249be8dd602bcd35c4fe91fc80473937a5957eddade887b172672430d51a77058e9ab6b7ab1b7acfb4a99dfad0cf27deb4c97804dd8846c3da3723dd2ebf741a311b288569cc86c2b969459f1e71c3f86805c248e069167131a72af3f665a2046e0ccb8759cead7d43163eb995dc3e13c759fef2f42a85f8d5c72c6e7a0eca2a675f836d72b314dda558b3d69dc0d96b7e88828576222e6a5034aa0ab7c095bf7df2230da45358e9280c9a0753b8c45a20777e7bea81ec026c97260461027015836070240cf4c920ce7791cb1b19be5a746d0b59ad9dd635d2d51caaad4e28da2fec0e159033e811137fc0e09a41da59a1de371ed0ae490c3e572b2655cf7c6626857bc3c7fa0b8ea8d95a1ce1bae78d65ef9301c15f7cf5ca23cd574dfa10863e7e7b11a4abf752cf10d01913cf48e7812718da85aeb45017d0d0fd827f013dcda5e7a9225207e9915e5942d5d20843a8d893417ef6ac0b74c722a6fe4babcce6700f736951d219e78b17e84801d2d5e5fd6515b20414a0ef152c7c081005c0019d260e699476bcb75288f3005c1290469cc2f961fabb050ed725fa7c739eb3a935881bf50e19bfd2c7a2fc9098425e4a799e8b0484c9385a872ea72be5dce155289ed7a004d47860bb2a4a0c3a0d14ccad032d2cace9e6342418685305884b89568da6ba45ab63184340c9cf23472e61cac57e7bfe1c608be1ee15baca7ee1ae768b46990ba6b8f040ee843ee3bc363e128b590062fd8170172ad7d14e61e40dc049bf130d23e58dacce9e1b2c25955466641465d14bf4f274e991ddcf998e760e982274304cb957f8cd2b7bcc4003f1f87ac98eeb9a8a5085035216dbe55f1f7e2f8668ba8343e75e498e8991e8d2f858fd795eacb2008fa047253ae798dd8d21e467ffc2b7862d15612017fef119ecd2b787d4f0b48d8f47242d06b890b23e13b263fc9823f8e5a16c07e4b71138643be10390939a4616672892fa192f3ff0b91be56790e073eee067d267ee404ebb457ea0860df807a4072a8b8535251e6a9f5b99baf9ee670fb085c04ae5b8cc9176ad9d632eb4bd0f57299230529bfcdce068a12575d5929ec410fc978a652d9c73ae534eec7c07c0b5d8a63a69f0e2374b7a7e8c15505f22f35fe37bb72a0a716c947cc36ba16a4d572ca667ebf3ccde9f2a95b54922c16d6ed6c9a7198038ebe838fb96b340f21c27218590e943254552caa4d8e33803068502bf9f81731f3e190ac6f0a847bb107721d6f87bf35aae0a79d3bbc9e98cab20e63c6deebd3a2a82a884617ca8044ab72dc9457ef729f14788ef321dddd4398fd958f95212ecbd50f337eea71ec802c721fbb8ea8fd17f927fa6702808357350107ec6631dde7c6ff07ed8f7ce6192d720b8883c910c5af3df695034e4a1334d3c8763fa48be6a997e5aa046decc504726d00633010a0a8e41afa1999735296991bbd54d3ccb482ccda0a09b773ceec72c432263b1aa855b496215d2f1987ee23874a8aa8d520a4f75ac164fb3865723647e7bd9e3b8d446ac0d83863c0ee686bc88250288eaf8b0390e30e3663aad8728768e6faf8cac1873af624124e1663d0adf5a9dbd530d2137ab88f0751df1c72817c56de29bb3188ebcbc7afce4545b6355b74a7ee59ea181fdc21338e331c7228d362a8dc2ad6ecb6f11ea7cb683e17ebe5cd06868515cb5c22d6752518bb7242991a8f874aec87246b3b0b133c730b6938ad316d678c5a67ce0c1d85d3652f30e02f894e133285c3115baf6c6659fc33c26d800d61cc10e857bad6e202a9151cd3b3243529def4feacfeef10d89425c9347abb99b5f6be17e1472e1b7f8272468d4f37f9b4c08f17db14fd428d667a83c5b782711562020ebe27f104105c5e10017449b95db7418e7a929edd2193ff183815a35a36d92ed2c49451576a21728bc566a059d7b769a7c8ec6a04b0bdb96e97cfc8b72c98422e4d2fe8a7049472b3bda247adc0ff2963a78d7339c7bdd92fc3434ce3be8b7cf6328780fff6d341838bdaf08bea838035069e581e453240a2e73ab36869a7ab5e93db0ee7bc98728698e1212bab647d47fd26bb8fd1267f6a452c41b401f64e9b6179c8e794c016a65dc55eba0078ee1f153374f22e341fbcb7a32be94fe4786394d5c4328d8572268ac3dafc8af3cee849cdbff2afec5255126d35a8fdc881559d829419e06061d043492ce9ea31e54aa2043f340ce6aa9d8a3518da7742ae4d8f947dd6be3404998a417df1db32ae439c9162c603bf0c08e2f46f0ed0954e3e0dfe08fc5e207264a0af1f328811d6166c7254aabc08bf6078fb8aec757bf602de73c3be8778525d437e3908099977005e5c2d5db207a2a70ed779474f421e5a257b00b72c8072b2d0cd16015b3a80a85c773ed8ac7f922a015ee16f5c522f23004293ccc31a05fcb1f70269f408b2eb160a779436631edf10916a2cf365ec1d8436715ed255725279ed015330687f71e316547acc45f237e7b7b3c7c4415f1b04b52aa792a33ab90c9f6633270448931384ff33cf5182e6edcf2e5cbfeaefb9f7575f87c77572df59351c52ee99ea818988c77a45a819ff69c19b32831fe249f7ea825f2ad5727ba9e875370c51d3d2864b08f875d66408621a0c5605a369c6bc538fa115e52a59a94741de88c7be837eded76d366da34486849c7856785fa6b516396e993072d4a9e3b806560e880aa22e65164912236cbcdc967b05b94d7b86cc959815ff72ce46bc6781cf67586f005fcee336345588ce0be34e7bdcb8ebae049a9cc6b5061f9a42a8c4aa87f14fa255aff5d38b62c643565d54018470adf44013c0b35a57b72d87386aed1af4610b4867cb89cb35c8d0643300ec3a8d3c548a455746d1728d767c8fb9b588298bf65ef25a8eb4986418a473d3cd6eb438ae4f51c4316106be9fa7afefaf72446f8f88a2f455bd99aca0e247176c2cb51d4c1c6be2cd9b72bfffd25650f8246cd4631dd34cd2c1d6a4aec6e44268e87a22892940e6bc717290bc8d97f5b902cc41942a03d556e9f4ef28dc68288e4c0590da1aca1d1afa72aac89a0b77aa1ddab8927ed5624a717932c60d75f9a34d0402619269f9eae1723143800876d6d468e8d0959852c085a813c431f0f33d56ecd09fa63fa0cad572148f5882ea92401c993beb65c656fb0ae337e746fc0583a55411d24aa3966e56dfb5412d9f89e8fbb3682d6818e9c7be28ccfe9756ebab277c2bc0a7c5a67870e0c054876e22a7e6aced586b385a0d79c4f46619e03e56b3b119fce8573b83112aab8c6fd74d4585205ee02512b32be00469cc96fff1a095cbedc4d59196ba725feff29c26d65a72c6518cf71c264415f937c99bbd5bd8e7f8c97062e670ae724ff55f8b996845f31ffaa1db73236926f8c896e0526ad4e3e26ee13c1abb0872c60d3a2eeb81721b4e8de6d9d599ccace07eab08babfaac8a56d03a522e0ec72a75a68dd6aade3cffd0ad7ffc2a33bc94f202100a0e4f17728378c0ec716de72cd00ad58f26fdbe1bff216c1a8dd9844c216186d878040962a4f6b10f27422189d83594fbb4b08d9f30339416b46d3d16e93601395b0e96dadb4479ce711af725ad734435cf3f7ed2aa771e4d89278adfda9578fe3c1163dd735627e119b9d72abb6d8d37661b389cf1f9bf4f5d6c7c98d5310e6879f5693c4e883dd49c31770deb5a5df7bcf0e128044e74f2ade04b713232b936818b141f96c794779573107cd4787e116fcfd2b42e1f20e102993b5d053c00a7b527f348b7ea95a39873b72ed97ae486b934b62e18bffbba6684e9c36f5830da5d268fc03715c2a7562bd2e0e3802bd925616ab8e1dec63495205cf88950bb7647fe0e3acc9449ce5e5c8726aea0613110e54429a4f4f2371fd0258bfd5ca39153209d609c6f47ad66d4172ba3ee9e61e11c1684959d0c57813fd5d603fd561269adc2f8eb044024d48ef72e9f9b49c7e1055263f7a092620e2980b543537108679936bd7ecf4381b1f75005eca99a6eb44213da22bfd371f47863462f17c2c4ac51c940d931ae48fe8c67297f0f8dbc5f2b406b9e02df0649b0926c26d648d0420497f5e215984bf23066fba4edb9104d5b4c68557afcd86b2490515d5b628aeef24f9edb5eae8827f6b7223a11949b9f1600ba5ea0f28f282acf35fb7776cc9cd75cf323ae4e48a5b0972766cf67c9a8aee2f8da571beab3c6d68cee37cca659e77fdaf57dcaf5245fa723a8f8bca4986c5abc61dd24878bedf1d151b28b16afaf357ee7c3bf8fcc10f724238ddc266408caaa8a354e7d4e74403af9608bb172b78dc1d9a82b088fd143671cab600a6750b2635742fb04c34e2340e1ef1251cf116ac62c0de64050543724ba6d3d3fb4b6922e93bb1616f9bf61c320134fadb7e32cbd5c56848f4d4257277a87ec4040a346bc24ffe9d0ef0c717e76a24c09089dced9023494b19c830723ebf25ec0f4f0551a081778cb20412d5c82548694079d01dc3a961fd46f5dc4698e64c9073ca7e9dc16f7be2ad0fdd1d871972839d366bc3eb817f88f5cfd3012dcd1c2f98864e4a180e0340a12a2ee43ca3fdb49e47506cd350e78e111fff30c150ba37dbeb0e98e92a8a1a11360c25250dcb58678d4740a575c01b8b8063724de3734123977cfcf7be9487c15424d096a80aee8ac8f905a844dedb005aef7201609fb754fcb7ed77b95d8630b84307ae43a868c6d0f405c93926557ffd673a3316aa49247a4718e0a900241fe471ea5a7bb227bc1378f973890bfdee4fe872be2a95273178f3382a7891a8d821928fc434a8eb35a16c4a20f703258d5e87720a9ae688afda2a85f4baa11a298cff83485e2b701cb0fb623c88a800b127ea722466f93412ab0e5912946468cf515a9c98f722a08f6f7aff50f3fe9bca97565128f949724c3a30a99269f0ecc8b0b38c59b2a8f6b305e094198c1b631591d925d1bd286f69c0450ae89a8d69294c01c1ec0f9d546a0968f8adbb72aca643a97243d965e391208e7b2edfda006e0813805496770a15c5a86d80bb4d5f899399726bede4903bbcdbaeb0ad8b15d94b59824e9238dce1894d06ffac71a4f3438c72e7400eca13576395fa7bd958e800c0293b7b0fa893a89c58ade2dc0403ab113ae51b8e9a4ce7d699318a92fade0d86a19005eae0d9f2c026415d59d518eaf7387cfd18f3b4021edd5d397e9740db73ffd3dab141a8f315479411bcd8d1933172e8b18c6c8cf8348a6e3b1de1138554b5d920f8c8401295cb9f37f82d9415857206091b9791e1303c1c149f6e83db8497f21bde00cb6fcc815dba5c35d03df857227972395df6500bb492b36a66d60e72da75ae5b48f367858dea062739a139725f482fca6773b6c077620dc7ffcbfd8eec9cfa74e08be1e2538c71ede050fc488922cdfa6e7f2822f87dcd6a31cffea49d2daf5ca1b1daf3edd00a3b290e4572e5f6333963cd5525f87b8d072232ebb22cb1d5cc85aa1b4f6a0a56c2c7ae02726264cb372029788d45ee6cfad5f3bd2c82c1a4d940bc4ddaad86b7fead18204bb017b43a99755b4cff3036af6a52c17cdf367e6e559daa68c7630b8841481c72536fbd540cf8434302e2c17a58d1615d8f67676043d7bf6446d6f41a830b5b728dd5de5b35bc399481482a5f668c8587574d44954eaa57ec58f858475cf08a72b01413c3f2eff908e13ad7225568fc8f39fe79d68b5593c0933d771a6fc27402f579027f4873d4f4a3d6c175d2942321fe818af0e07abe44b23b5a6674f761727e6f86dda3328afa77fe8f159c612e0cbc76b67a9b4c308bd5fc8cca5970bd4781aa60c38ef56ee467eef29d1ece8e4e81252c8f84b8abb87fcf8a5fd771c8720dbdb81b1740c16e72c616b57f98d3682c81f66422a801f5db400cacf337b072f7149bb980930fe52526cc9789a65846a0ac0aeb1caf336eb84937d0f5cb46311f7412ad9b3a2a63249258399c33a0cc3dce727ba3e9e4cfc3d079fb92327424b9901287fd5ee8824f478e24025c54c544b2df018a3be0d2b0bc9afd05f92d0c7196e42dd9fd4c8ed361474cdd7544f11000923895b9bf8403d2a4970cb7d472c62f9ca352db2cac61b7edba5e32011c824c0a7340a7698e69c36c187465d0728d65a609c1f257fe9b828c44ac60bdf38090887617a1dee8d18ffcfa1ddecf3b93cc51278be1e8022021043ff57c4bbd3edf51a099f8f58af86c001bd141b8724e11b6d46da8e5ef95002c853989f83e96d359f3df938f65922241581781e272c5115e733510610d9a49ad2d5c8f2061ee8fa8361b667545a35ec2f1411635726a502a5be6a3bdf5ed06c3585bfdd61ab61b5695d2ce70c5a25a42bd6077aa7256fa156c3ddc2fc7c02d8afa4f2f584edf9f218daaec1864958efd203b8adf6080a834fd5ebf70147d70d50d538cf3a707742a962b4c719d71af09ceebba3b72ce469fa60f4490c2574a1a93d9cfb38cecb91d418732622de8992ad0b6f25c726c8bbb48be24d8fdf675700bb9e2ec4033649cbb49d6882ef2a1a9f382e02072866cf90a1046516362e738b9bc447c5ceec11ee71cbcc7a26957b01509dfcd7269538fde2305a2cc6957cd40178944a3f87e3fd381c3e25d35d6d89601840a72a928da4a852ee96a066af7ded51b389c30d91297a296b4ac6f4d6c059a035600dd9e673d2adeddaf165ee62093beff9a57f8fe5030147d9fe4072ad2cdc6f47282105eca7b6022773b4e44023fcb4a1743ce8382146d81f940e2b6e6d3646f255fde4230666bda45476f370f7452ba4330e6cb5ab9159e92e102076b82243d0a44c54edef3dbbf3a80204adb601a389060eae949ba444bf30f19443ece33eb72437b8933756902bc4ab22b36bc5e355a044271952a063ee73c32c0baa60a435f4829048dc6c5baf2c20cd1b1e88ad129f2a5481c01caaf7fc15fbf2b6facad003c7e450771a9b2e49ca620d10530ea116810ed04330f3d847068a6f5447a2572ec18339e835f3139e7192c1c0ef49a0f4a9df99b829cc0bd66fc184fc85f9a724354376b26448422c36a3eedc47d941ffc9fe1f1c95e87c3bda26a98358ab372512e2705c71855e2a434f5b1b0a6befaa55bd440656e61a6bbf25074782b720cf4daf14a03b32aea918ed7746b9bf44be561c6848f27a8d1c1a2b97fc79ac73a7ac0ca82d742a7299749c9802061ca569bdfd5260676fc2992b9cdcab55a1b728dacc62cda9135326d327338fc9320b4a22b40f6646317648cbeca16e2c8c8188445bad733413ecf57de79793c35e02c34142e783eeb8b329b00c70efe4ff0301f58037c3c8cd8751a8c09291e4e16969807a057c0382a2d0d3d42985c81cc72ccf188eceb8cba6e701f87a6176c14575dc14f59dccd29e8dc14500e2105f172e1f6ce872810c681c4acad8fcc65e272961b2831aafc3980b4175cd83ef68b63491ec00c06de5130a66d3025c51394fd76d6ac69ef4042906eb1e58a51eac07240c925c4fab13c74aa041e771994c8ef7c508d187bb75d1c0f94362914e3597202d2e2e139279def0f5a9e1e93fed6c2b0852b965b29c846ab9589783cd04f72afbe26e2587476dd873ea224fe0fb55babebca52568f45464e17eb8e0076157249aaf05b1fe8d4f03b00fe685912001a5c6ab13019b42a20c9e8c4065b187c40abfdc1bc4b348114da67493feb6ebc2ad98e570b44749c67430e6d60ea032072cf6f0952414298b6ca75e7d0036b5bf5ff374a647987d832046aa6e4c6a4a1728558bd767c1ac58190207d35e48506cd22cf6f1959d50efe4c3de6f02be5c8506fbf5b0e19e77c6c5e7b418a183bb2b9f564c86c6ea441cff161c5d657c4192b6d715e45bfd223022ad0c14405b15936a892be31066fe62707360cc75ba61c72dec2164fa61da2f58dacb9942e0acb0cd69a0a549cc3c4694835719af5268e46d1d013c5d5e844bf696ec42ac85f6b3c2e38375387b811d54d688d78ac6ee912a02db6a1f51bf71d714418ee677713bfd15b46e07909446890fcc79459d1860207a92b77c25bf1c4871beed5acd0bd1ef84085aef8042d2837bb01421aebd12a1b4426e5432b6f629d9a49fd7706e7c2c17450f897da31d4b4c7ca3d1be57872aa1b2b0d3a0e98c7753168f5f5c46128897b4b18db126fb74643e606ef60af72e194e408f2840e1f62f8979b6643857ad0cc06af4a81ce7fec1513f9c9efa872eee3e7616f53dfd422bb03547bd5855881ad2e0c99753c343602de5c603fa372449848cbba916001aaed29edb1cb9f34915e6554a0c47d141259637416ad254ca8499d251018feb653f32d65e570b00870d41b13ef69ea5fbb996aa569cc951bd1dbf63f40cca9465044af04845a5b5ac958117f4120850bfad5dcc05618003dd86cc40b84f54360d55057bbae859ecb5981136702bb7967025b4121ad8d3e7254d67efaad4625e7a7c852cb785b3d0c3c11d6a2767de3f8499f89f5b8815f72934be3dbd03ad20d08c527d093b5a2ad90b39abc885403aea996f7ca60263a6fbfa5ca949ce124876753a4d03c2e0ba4639ba2df0b5f381ad1c634fe2dd6157281371b537657cc9cdc6968ec1bbd282819d384302509b906fcf52592a9764172dfde6ffc8a7f383ddf00b79cdab9a229e018b8edebaeb9962a7e4b06d5adcc724767aca15e01228fdd4c674b647775f9b8710858341220de67454d72df214d2360d93ae7f23ea84c0f068e0ba461aa4790532fe220e63e55ad9ab951a8248a720779e0bc88c8a1cbe842eb2ccf18f493ab3fa7c7276850e127446e2b6d6a6b725aeaeff91b178f5f37ea1d865d80ff2bf9496846ac79438115e80c413c37fd2ca29c811605130d8373e951a248e66251b18bba0936e67d8fb8f23cdacea8405e0451853af08faa2cbf63ab7fa5674e73c84ef636780d4662544af2dd551361722d0a584f41643d2240c7ca3f95385242b93ef9340b9dd278afdd15e22034000e1a4c8fb205263394f486e5a1887b29b2f7d458342501d225803dfd2f5598f77219b8be53681a0b600fa3b777a83ccf6d4b6d87e1bd3bc802a4541436f4e386726cb132eb1bea507a8354464ea889ea881bc22afc9cf5856db87fcf2740e0fb39394d03cad6f424887a00d50c4d860a5aa3ca5876b994e11c936ccb651e408963c4ac2f2fe52b8cfe5357d3f427ed014adfca47135a86d7e4a2f4f2c398973d4edb75ed9f9c22f13e9093bef2de79dc5f800c06ef4fbd927a82ae6ed118cd3672443b3282f056f17581ec97d178c1bb4e2a52b29b6ae08c7c077adaf009582f48042aad41ac8a7fd9ccbcec7709005edc36df698bc36fc2780c55da22eb38422510e16da162b2766cec5d6fc7df493f63254584786d86bf07016c9a0044b4a272ce662635c0e4f46d69cb0e27e716c001cc24f9bf5503257e2a25ded1652c8072e091d0aa36f2bafe962b65cc1787a800a3d3bee30897a9f333829b84a1e2e572bb8db9c0368d3a7f4f2dd34f745b75d1083f5b6cd75bab1c3f387cee2cc0a872f1f54b10830aa410310f1b1425e3f7f46a095dc65b1e9b6dd1affa9885af907279a8504fdfcab5d32514485018f95dc98f7be53744287b7441d04a2bba899b72222d33edb16eeb930c0eaafcdfa7d81e40e5e8713e548c1dbafa35dc61c00c41d808517f494c93e48f3ebfcb55bb2072855ac82a47022d7e8d950d25ce6c541ba4a0af75a4470c3e2e1316d5a87a2fcd27542996f4281b73ef17613767d10a16c36e3857919516a3bcfc767be2e489254bf08c17448bc38b01da43c86bce1138151e2a498b457dcf8a6a5a80d46605dc6c16628d2b390ca64ca2decdf59f4b423f90d5e0f7a77fe25a8f24d5fe90395e5968ef99daf2ece2fbb42741486851720db3df39cfdb3856acf1f93270501d7cffab843a5f0fceb620c04699a9fbbc72327808ed721f74ea7aa5acd8eeaf50ca719f6791bad5740551b6585e607a9c4c7009fd6f0f04cb7d70815b6c2ae9c672e24c77762c7caab91aed74e530ff9709b58910507a7c89471f9fee7e5fdc43a8e6f78a3609eea197bcb496409dcce670384c9c692ec33710983f56d3be217d670c8a20920690da497cd546da2a48337237d07070fc5e868283cdf78f726c2e3eeace885c19dc24e6014a3422b1604d72687b2d8cd51c36ef8516830d78bf96e11defe8fd6bb65085375a4f798730c6152eeb1e779ea684e763e56e7bf52dc00e5b4fef1db4fd7f9c235497c64375787207fe370d53cb39f2990baed6026049f57ff019be92e9b9dc731e3bc158483a23d9807ed5e0f13a1a399ff442042dd460a3d065fdaf851ac52f69aec5f6e4db7251ee8a1a55ec62a12f1771c4581ddf7d7d4523535e034e7143628afde2db7a72849503e693a4bd0be3771a6cf2ae06bdc28211729aab083e7fb19ae4a08a1f5fc60fde3ccb30cfdd578a66dcaf57007995cd2a744d17aa2f40d3e065a801dc6bea59126d73768ad968307119da5015726fd8491cc1704b8f8fa28a06345c535feac33ee4837963c78bc7e2e60b1d6f5be268240995d913c46ccbd314d8ea60720a982e2e23d5a51d64be0c6315d2f3befd82cd537262c57866a8619163b38b721adfd260ce73c4e298a69a6b3a3a0d4e742c0c6e70865a65eb63cf2728571172cbeab36dbb80682262f73fe50982a9554a707bb6f5110dd0114ec41d796a287242b56e56b3fab78ed2fea3e9eef2f47bd386557753aa8ee90146db84dbdb98729d2450f659c030d1e2f976039c8d64ed7804f4144b1a48cb284414546f7ba37268f953cdf19ab9ed722e9aee12f1e3d2ea2699c0345c8d3f500c7604c64af672ec8a2483ed3babd6fcd41c2a3d1bb4386906d1d1375cb2ef1e2dfdcab32335335688c18fe836f70644c002da9cee8eb5856404f8802737146ae89181a3edcd077844e69a8dd3f901f2e09194d114e14c114e8cb41755ae4bbc72587492289f72285258708e739e04ef9b5769a6b5c46b4f39fc7a2362cd4b27203885c709353bcca5b0a84df05ae0397d74e89eab177137134d11d4f21f252af29cd2fbdd87236abe88e0a0addfa876912675b26274b47bb8281a3b67988a916235f1898a4d729d4530d33b2c86f1367a6d1167cd783493780d7f250dffe2cc6d38f237f98b7262d5ff0a212e3b8221c941a9b0f8c58beddac501f803a7deca421843f4bc6f2571c0b2241d2fcae4dca91169d9f7689f54b4b13f2b4fd7d6747958e7823c925cd1d57d8309e31073547fc8b05a91ae274426fa529e064416af4a941afdbca7724cf24210b2818f78487b00f8f29c387c79bd75adbd127c343c5548627c8d614e6f5c294f6fe5b8b51e8599a23263215d0ccf692a1df3737d96cfc4277eade155971c4a9116dc28992267537d11a2d0d26fbe176144ee134713d259a7e2456d721f6f25b04abb4252f0c91bc6af548dd6b7ab87942bbc6a9acc68f9da9819de57f05f06809029fb5926f2b1daf1a254c4adb558fe68a44fef67fac9aa30364c725ec7180f35b1911c20907a69fa784325eee4294dcd96c1a8f0705b560bc81572af6e81788064d0c37ed1722211143b053e75a740ca751c8d08775b00ffe478728164e0185584249e6130f81e0e78043857c99b63e33cbfba7800a43d8292810a23ae14c423ef8e7b69297df58c5af397294fa4d185e8b836efaa9f8d63a378533db33a03c24cc6f78b771f9ff84abf4417ebe42c383916929dd51ecaa60b2b72c172cf872d15997c162593ca750ff56c1fda595fd1786db576beefee6cfb70591fdc7ce4cc5e76bbcc0837eb60e029ef497d775c9db4dcc3618cb7c8acc49d63b5ca5b5e8dec98b04221fbfe4eb07320b291e4116ff824f42046391b454b7365cb7a9898338fd7968cb40c18e7488e055ddae9b59eb1b4505418b13825c4b87271252fb227c4361ac177e2c960dea61b91139297af62f89feb541a1b77f3cc728235ba727f62c35b9083ea052132a3f4c01b4cff3b6631cebaf104b5764a1e1120a7946334c7352ef281386bed81524454e0615d6c0b076ad26fcf9d9ce23b72b369199ca6fb147dca991dce039b33c598315e88f4283ad33ccd09f7d7e75e315d115ee7054a1a200e64b201c2681a4a645ffc3586ff0a693a4e36f7c2970072662ce7c0c8d8a47db7e1a25beb492bbe7532e4ad8947fdb833ad4f65e9e8b603634ee3d06a9cf1ac18afea0d042c767f8c4721b3e5bab84be7a5fc2df946af01571cc6d5231a99f8bde6f72e58eb60bd8abf93944e9a93113293d6ec5ab5ad153af103fe92c591940621d1d7051289820bfe9e8582dc42f76437c1b7a6fbe15e935e5f6dd439b6e9b12d91f6209c202c277dbc242fe653f51b0d9437db6bb41019c92a7e3ec3fc9f09db0519f3ec3ad0bdd619dbd3bf162b2dc4f8f4dcbfa17203f65f9044de2a770b6489ecfe93bd59f0e1922d33ec5c016a98d0de02e518723073e36756a87f54405d062c64d09a5f8822c0677fd786c26b308def1ca9b772ccacec3101e0d01404754e026fa371b33b6c317af816f7aeeabbf627da640e72673d45f1d2c227f88d47fe6a22a861bcdf12ffc20340fd2ed573ff6400c848102665700ecc7339123f5614336c3d12325cd9702673d6052dae121b03deef57674b957546b89ff493d1abd8c649cd5fd607e4f2bcc43fe9da28c929de73196c721f9b3b48399f13a06eabd422112f7c0c5ef53584f02c107c69cafd1945981972ac6a628d897c6b13cd799b4c7fcd858ac435b8a2e1f8db90036e45d60fe7cd72e06c575384730b4a1125c31c0e8ad8ee433a3717c7ed338dc603620ae156222379815409a8d7374ee0969378d7f75b343173e06bf3fd6ec843be405f3568ad5717938695459bc7bec2d366b687422b9d6782a8c40040578bc5f355d23b4865095833dd1b01d6f32d4c952638b8add5e1f4bff8d162d3a2d1cfebd5a82a43c34c5d6746696a4eb214464eb5bcad21aade7a43b3ecfe958193924fec0af2738e72897b9c96a4deac769eacf8ce6889418a4241e83805978867011c56c3c9ab0e726a5c5d5ec42f5fe55659ab5d4cf38af67691ec16ffe255f665158f1b6519892b262a90cff4af8276fc0ec745bbade26132bbc0151327e313c7a4487b969de602b3267341e9e07d3c65c7ed8a2e45c5b283be9651094a1b968398f61582855072e64aadbf988cfb1bfdf17fd2caf1f482f9a47ebc16121445fbed0513ae99eb72117b85afbec35a72ee5ddc7cba5695a72ad3ce4cb642be7976831a3ea3570f729329b8f3de6f24a415bec1d89d34722d3cf0725fb2056f40e782d87eec4ffe08a1f4522b66a283b110517b5958128abb59cbb62600a3092ac4a1fb0bd47b6d3d5736e30ba21ec8a712b865dd2c33b248cc49a796d1f515da78524e878e2ca1720e6a336bc8236971bc63f71fdbc7b0a23e6d78820a128632f39296ef006b082559f310a5cd8bfcf22a372b790cf1c2b0c4aacdedbd1e21482415b392bc4251214b30b48f5f93c5700101bfb84aeb5940b7f7b3372f4717b1eb2e6f859e2ba9351dba770fd8aab7c35db7ebd5750e9094201d295f88763ffd422cce044da08d727ffe11dfd672396ca496cfa043350a3166f42ae0f88891b05c86a8a6e659f8303b8883d7e16b91d4c7713cdda1e00cfe4bd5663caa994919236b466156ab9b00f47459d69f34b429a0cdd4a672d95998024ba83962955f0d2227eef8d606fd72a3e2520689941e960d909b7fb71d162aa90d50b38c543ca6a3122bcf65bc7172957a15e4a2e35974b89771e4cd9a57e368131e151b1abbcff01297354356f27224cc68e36cc0882e01de4afc389fe9a0284508083ca2f05dd8e014aeef322472404ba7b3427dc9deb13d078fb880f0c825513e1e5c861f9aa95940c8ab47b1629cfc1b2905cef20263d9c438dc605686f989fd8ba3bea5612b25b5c969de217291d92bc4385ec4343d20e505656a41dec8c025d1c464d01d7a27af2f6f29107296e020be26e44024b53b3bd8015d5dfbb7d360770cdd2a9e680bd543b32884724f631bfbc101cae27e0661e7e836363659f2737dac7f765c8f44e4262741d945c20a5b2bb7771abe6e60b8984e4b10b0463eb99810381af56398555bba278a72c8375284656c055b1db8390b7dcd1a7e8df9b64fe2f80a8b954570aa218a341daf0054def981dc6d4b33548362151f99b24f65f60798799e3cd851c434c391252ab53d626dd6246da09cd8e48cdc0c96733dc28bbe55cb66d0f3697165d648335cec3bb82343624194e3096b43594963dfaf1990bf93bda2daea6c1c62471272aeb147f847f130487247b01649105e68aef2716041b79f077d761266271dbb72b3cb3713cd45519ed045cd1f7314fca9e9279464fbcf59e0d08d9a16f16f2a11e21a482de54061c4a4d79883f8c58123254aa192f145b2bfd03e8679ff056e72daa6e0100a1476720ecf3be649b269d1cf8af99288f5de5efd6c174fdd3dba727a69e0a2e96eb24150e7a6cecaf5bf70a9d7cf7916e955ad55a68ce7fc180e7276c929632bc99f9ccd5e2371ba1b0049597262b0c7c07c6efaefb0fce50ff57202b3fd1fe2335c9a73a07b057607b69535679633e5a52b6791ee6e0f98f1ab72b6af8b9525b1723662956be0917d2d8de2a8778d51e4402789760c07177199135c683b0fafe0a2a20857c7ebdcdedb991eaa30d1225afed6dbccb775d92baa7263928adddaf5977f0bd9133bd461cb34fe32c4f34d55710565e364a967c3ba4307aa7b3a709666ba9ad605b2d8e4ba27376f094e3fb2523c104ec3a4ef161a728a2a7a876d3f0ce2498ecc5bee0adee910a3d764f4425d636cc560a0f41e0d723f5967ad79f16d2f00990afdcc123254635fd0ff8c913ddacf891275ccd69d720c553098bbd5548e6c027d00d1387c39bede8ddd3b681b40dd41340647f16a727397adc9c3a186274390a3ae95fd8e0f434fb1595e48cea724f27bbfe5ffb1722c82fa08250636c357cc0e84135eab205523e3ccea4a534d9f2a02921e1b6372ee2c64be8100fa949eb0d923d5cf1905708ab16ec04a33c8439864ebc7421372d71757b9b788ea078b8465d36eb548be790f81aa56fdf596c153aad3de3a1e177d2e048d4ffa0c388c98f4cf14aeb67412f7fa0099240f87826a26c939be61176ee223a4ca6a301dbed0feac73c5cb0399983f73328572a2195cb62476b10602a13125332b1236b17944e369c19c0893ff4e77868df81d019eec59dfe9442d3346c7f1f6b46ee72b61e6964973b134f2bfb210f27c9edf510b31f103666b9172f056bc26f5640f8543fd2fb6a4b9e638562200168edcd3e6c85af7450e07c76d22472730e3c0859ba7ee90add53c5c9a4b1a5425aba640f1dfc2dd096aec4a1412f502f5f582737e63432e02e1ff2874f52374609b60fc8598dbc2a51c467b648fbbc6eb4dcadd973bf955989f4f4ca370b973071eff5497b4310c597d871f47a695fe8bb71404ece9ad9af78458997a206cf3b15e52f3ded8f4a8aeafc112539dbe74d21912b03edb5957cf061abd2626e18c450b8237fba48aeb5e41bc1049e2c50c25980bd3e8baaf1be41250caf2960f5685cc9e7f29a7a000b182ece9452e8be84d042306d408a7a0b7182ed52268a5720e4fc04821047df632867eeb5c397ecbd865da1081654dc642519776e68f4cbde1657d5811727468cd4a6ab9306f9261901e3d4e13c9f76231c98aa9885cf1865f6d6ada5870e2319e38b23d72fd0af58382aa5fd8f3860842bdad0ca9bc59e38795cf91596a618273765e9c727d7548c4a6aba0796685aa340526a00b0ced6ed684ddecb8d6d6619c42880e69d53e90b3bd6f85fbf8c82959463b9cfce81e52b0d1f012379e5317432f692e0566fccc403c5c7595a35bb98ff991913c28445488f9307cab1dc6a59434085c72b0b8e20bd2a91fcfe02dd15547433b932dfa8f5ceba6b1de897a4e24d99b566e3ebe33c388eacc0fe9a3adc8d51f011a1289c396ce9cbad54feaeb7eb5e93e2b1319b216c359ff1a71d1f9878795eb1f3282c04daaba54e3b0b87d4d3e6c8e475fb1f5fa8da1d9a0877c34d602024b247eaaacf17b65bfc0a508099bf1974f7299b81e7898ab6bf36f21c669bdf20fb0f6b4df7d067b61416f9f921526a9ee71b3aca705c9048ab398c593f27a979464a2ae396aa6496da9dcac69f8c2dae05b79b90b5db2b777ce4a8b4d70721d2841357d15d76ca63f640bf9299314689c1305845624adb7972f74567933d5e06933ec943325429f4fc9e69852055a959f110af1850af21496ac9bc8ef0f9234d491425c478afab3b171f44fa6e52b613e7206b4e0ad65ee13828e1b2c1d2e703ba136301deb657006052292047562ce2e1a4e3d8527a455292f30a9ae9b1e3a07a7e54c6e7472128c2cb08d765b84f11659850c8db718c450aa362b468ec520bfee7f994494814ddb7e672b1959c31dc9728184abf25a7c62b81c75fb55564703c8bd295a5156477dbb86d6cadc27d387728688ebd7632a340ab6df86cd75dc5f1592d4768dd6d7bc95495f3d274aae2e72a10755e2553705eb791e053485a2cd44c42ab21ba1624c8b19518a83f9b24172ce1c2d38c0a06875b3f41bdc062ea73d3dc2e7cc48ddf872748dc0565c95e8721903b063fdabaef788122c7337e73ec43c05f8d3ab91028f640505a1168877727895e7e7e000829741607acceb5ec8cec00e39c135607930eb11abd2a52a1b32172f723e8b86e326f564424b7937f5bce10286ff7f2b050cea2f5c32cb08d60b6f0e0125ea83a4c272aa6745c392b1f2b8286d5176b36963b9e1ef8791b84f728be018c398ab046a005ec7d8f0220150517ce679d3922d0a96da404c26b0416860cfd38b2fa0f985f238c30426e170a169221bb0f2a0deb4aa1fd2d7546d0672d4b5818f5502b1715764d26feb32a9a5bb6d9c1ef5f683be896ef887a4f3d0727879b3e63ecca3a507ae1e473e8279595e497bdb7cae08a526d68f5f81a92a72a097b8e5f5e3e26192a94bb3c5c4f4862c763a43eeeb0513a7cba841821a0d725353c64cd243903e3a942709979d72fd11fd1218a5f489062c3a505810027372b6d66d47bb23621a9edc56d485941420eac4c9a2ab58ec482d64806c08711172b57e8ee507b040725452fd42748b8358d2d3de96990ecfccd645a2bf2fc84d72291b26dfb2d68968d2429715ef6c8c1597f1d8462ffd64340a161db02d353159d199f309619dbf067236bfa03afcc3010ce84d358811e8f72390812f417e720a644df3a873a11e61e60d8a75eb7d9da1432495612206ed24ddc80b11a580c16123a3237447272bec09a3ccb5f939bd9288a9a2db64b17d106264a8bef413312108b571092cfc8f6961acb408c96ef06ccb7b1fbda99001ff9a17d4b762cdc0720c00371d9fff8b824a26db9c11894bf843b0c560466b9faf5c20efe6ab9b6972953d85aa6e1d25038d631fd2df91059b5f10fbb17f377054faf24ede8c47a472513b91f076b664c4bc34a29e3137a682d75d7f0438dc0feeea01359e2afa902029236b4808750c64d89c9a3ecc5402cb7580d232d73be8887a32c0fc77a19409ec3d0981f81cb6712ee99c628b2e87575c6b39b3d2e54007c532170756cfb572cb0a49b6b2aecfac736efc19cf1941701199517444484cac81a5e2f5f22f9b7297daabcf40a4cf76915cbfe6f14324f79fec5794f48c84d46a57b5f73a4ee63c1eb049e7e8bb2d8e4d442a9dcc5941d73fc3ba95e0d968bcce95ad2a388b2c7280b43c6622b7f19da0e1de5e110a84dc6f47e2af8dbab2adec9a65fa06523636a7aa265cb5dc895c93252067b01ca44a28ca578f05582e519665fe8f9df9463ca05732a4371a4f658c2be678d00a7ca23c60856def690c795bff68994ee10872d981258717e6274dde5d318d6a6119dd8357cdf892213024ec6072510745406b772a608638e3e253afb53eb70b484ee2ce492965c96de60d5d89e9c36fdc78722a405b21f6b611aade2b9218647fd66f08bef92ee50cec3294e098a4e484fb01f0a0e99087a9916353543c26b07f0073fb77966f6bb183b5ad86c97846e58f72243768123103d962c0176e5a4e1ea1a4d89b069391e5ddde7234083c17891a6ea3020a1f7690acad7ffa01a2fe6d431336bca0973d0e983af74bac8d42889472c692d0bc0de86ab997091b4206d332ca3999b73e128e4d043b665f371faf3b554430672a029f269784fa267468f77ee5afd3afdcc938662ecd92416d6f48d9725b240b6acaf26347fec4c27fd07f90314e6cf8fda3f8bdf3b028427fbaf4a9605b715263baacec0ff848eb12bf4d7610e0d8c6a71bc5837201be2248ecbabf72c9f0c48036b5d3f76a51cc521142d4cdc27c318da6853c47c1b2a471b0d524726303c7668d0a948613f017368baecd203940e7a073150c854f6fa752a9319165811eaa68a216aacf7f97aee004e18b6050697bc1ad14196ff25831649ea5c74ec4e6b3dcb6b80c20a53675c5cee321560432ae8e4ad4bdef505cdc5d3a6a39722fff872d9543196db52835fab617c933f873e115c0c075d1959dd9aa55932a72d0f6dc203991e7b4a9f2d9fc8daa64253cdf72f05e8a58763cccb7deedd9ed72d508fa6009016f94737a6fd5153d14efb07938b4bc3b4dfbc9d11c4c2ffd615a0a922adce8c0ddf5eef92401738ecd26e6d58612c18b27f288033cb4bf94ba680c7a2f8df08d2344bc24cf60f7898185019a1dc3625db4b8b3630f3ddec6c7726cf374ed3df325a75d4112dbd5790eaacb391823fce405c3be599fbfa49ecb57832f0f1b77bdb965437bfd5697d8b40af5367b7b41c5a9cfaab2bda1ad00f2410cb9210a73e0110dfef1c5c45265df65938a9f05a1cee63337817be16352675e2654441466aeeaf9d5124757d3b23c98d7301ca3d5fc01f18cd6b24e55f8df7248b7aa44e63d9ae368ff4e553e227a01c97c980e1c3567e4ba3b2e823933b9729cf281152125579491ff1af5680caa07d8ca76260db89f189c059fa114b358724b39986fe62e2c0dfd8c5f1c3f620e66b01cd2faf07f500d8bfd4fa6a97f09728da7ed3b44ca1f787671df34dea9b137156e11969147c90a11ab0f6fd951d03c4c00cd781277136ca9cb1a35e127b66bec838060dfb18d2b83e51670065cbb720060991915f78a2361bd3befa2cb8b0b2654bb7e1447c8198ca9157c4682a7724fad086fb23f412ceeebbbf576c9d72521372b595648c3708f74b658f808fc53e4b82393d6de935b13ffba6a4b877b08d00556dce8f979ae3fc6e03f223d756026e285b251661a9b82ff3859dbe093d7df68525d57e4c22f210eff2e08129c44137616ba1d9b2452f42f22ec86d4e97bf56c1633d5b116cf2ff3d8e32bae81727e8e5ac4c76d04954442db03f82088403853a40aa4a7cc7f98970f6b3d130b37947714bfc89c227fcd9aa5ec3109d26ce2c3b7d925ed86593b3ddf1de47e6372b73ee8a748d977661257c22ca381c0d246c4c770206d58bee6f2598cabeb6708ab66c7a892ed91e3235600762274ca7616ec96c65e2b0923db7a8f6f6e900755da7d477c94cfabd7164d97267142abe9e41cfea4769dd84f32cc1181d7a22f095c2c65f3578549d945c0b8426a84a12bbf2a4e6b458ef317f8f9111aac6d4b29f86afc8a891d3349efc263441b21cc477cf53ecaf8c785ad7ad6c00c376502147faea5ce725747420710bf2be590b6ee34f12b9d58d3135ba808721233032401d26d1c82ee71160656f71446fc056ed817f0f5ecd8b4339cdf1fe4ecb461cf72ed8dfd7a4e6734785679a8cfb7b7faf1498a87c14ec4ee057402c9ad40f81d591f1736c6c4627331adeba6502943a0f2dc5354ef4c9209489f5571b90e7d7d72bf20b9011f608e6cb29dbed229df73f8c2d376befdcb0ef85f6f7dcbfdf528724d6a579b0189520ca3f5eac3513dd30342dd2e7ee785d0f444338cd9b9ac9a72fc834925b883ad7c30810c77570d8fde576336c2c024113ca029fb65ff9894726cc2fdca07147528281d2e56feda26c4ae700077e622e597e21339e8130d5a7250a815903f37d333906885f0f2e887f5b8cebabe408adacdd4cc82c038b6bd56b421e659cd1da42470e2499086f2fa2067eaf973adc14149addb5c59e20b342ba14071c0b97818b6d7307a92d28e84f6952a32741b4cde51fe2e514963d82a722532d8a746bf02d80891f98245c4d066ee44ce9ee17ef8fdc2e4636ae2c3145192aaae10ff7bb5268aba8c0ffe1056a1dd20149124fe6fa85b4e5fd781ebaf725ba86666ab439b329ca47f7def4a6974cc1590f89ca0f8efe17f53d191e36a728c42bff55ec07c606c265a472df8d380d3a35fbc4c3c28e84fd8e197d15615729f136bafb23c8d7abbd615cff20c7cac63329aa275ccf4de02237f1bd7831172a6894d56f643cb5f66531edcbeb7bea7be5b64de95c4b84c35c8eaa7c64e144c00593775753e7d030c6c6a2dc9229e9111ac181f66c31928e5d61ab20d901546b1892115327d0a69b80a0327fa2495f15d06b15d20dd252d8f347b174f37d23e77834e83a2fa46f3fdd5abd50252e419c7e19013760cd937d93851af35583b728eca6aa66492e99a97d561f1f3372d9a2c3a3088997ec1427e84884d16b2242ae48d33f7ccbe38f861a9b5d161263cecb1b4a7e31278c5945f07ac1b9cc599728cd648ebdd5ebf0dc785ce787059772fccbe64ee1f3690f5f96a4eca1b5e2472f60a5926ee5f824bff0eecc1c51a1d1c41d21f8437f93c71df31c9a19e948872921a469749afcdcd5e4e098efbecbd43a4a8a0cb261dd6872d6d540b5855b67211653ff6a53bddd1edf0721b3628474f1e7ed17671ce3f02bff41f9df2f06572c0c2a543268cc424754801cc13ea6ae04d61bfc833305f12ee4eeb65cc542800133cc529e87dcb8aa641ea5b7c043bad813bede437782335de919a7ef6e64c1975240ae1b1682c17abe08b371cfb9a7f9aa1d000ee3c2168af22d8318879d572f0960c865882df90004e5b046e65e51d419ee79ed08ac4e9c9636d4fa05c5e47695923b1d18825ca573e232cad5c85751721c837d8662770ffe783353f2f6f72c6f4252359fca6e29d3f5c8d73b525f3b4bf723fed3f67635a7966301bae4a720abd30512e6406c6ba993bdcd8e05c8f2e27cd2ee6c0c96b65028d74a8f1b60d619b0a9e5ff3357257192b5bcae0e402f2ad43e31ac3aa40066cab7bf4905d349407010d3aff22776840a93186b646929bac6bdbc5a7d36e62d16815671cd81ba97f2596d9dedddf759c1a54a24a1feb0954df8f35db9c80510ae761df775b37c51d2758f8a42b064e760eec9a21efe39b36c81e0a611e8f60fed6682c994c6a675c97518bd15c0278ede51f383179b3dcf7925013af8cd98e813cd384522872641f225ee804aea06ee822747df564d14ec649bddd4640720efdf7b29cd5d85c1ab57976f746159cfa4fdb95cb3b40e0c60330847cc2f73b2189f81e2f9d387296eadba712ed54d8186af823d1b0127672690eeb8364aa76df593df28554f64339e28123dfa6088ec6b31c11466ea822a17182bc95e5c0b075b147d317ce2c72f12ae5d58dae37569aa278f9b651547ea682936d6156d1cf15a44dcaf954be7282b73ad8608d998806e27721f0dd5cb9bb07cf1a2acd347597ac01db16aec372c0ed4a53a7798a1aa4a971b99217fff1816b757cde8f941cb02d7393244c8172485d631dda595e7c0163eb634e404fec5d2e7df0362330fb0b60fe76c823b772181db2fa979b2970760fcc37a26c2b111f649f67ea684b33e972422a67ba857218735601941f2ed2d7f9550299614f1f1ea77e04fd673a6c4b7fab6c742795595dc25820fc697b740910044581077ecf8bc6d1e30e6f8427f92e89b98d032c728eda2376e5a7421968ea6797ec407bbe0488ef389a71f0f616462594413674295d63f38a1e1d5ed33895a2930dfd90e4a110697d691affe1aa6973a9f003d472dbedfdb647ac4dc65913e545d1d5019858c00b5a9b629a10cbd9f507b12d1a72a21a70b80bb0bfd377bbf4196f2eb0b4ace5baac1149949de20316e4eee076727e00aaf479af17bc30e3ec498f06e8852f1653a7ea265768d33f9746ff5a007203141bf511213e198c4781945f9ecd915f3aa0c64cd55becce29124f31f17d72322d54e9995198758ce5144aa8734f99411daf939899b5ced63e1ccbe2019f72910246af729b05b34068856b254332428f7fb2a2c842ec83a43d48af20d00a729e66a39a0031574b21ce669d3adaf58aa2a43d00ce157f6de7674eea48bb6d72bde0c6447b4154c593aed266f9f514d78cb09b3b1385a7a7ef1c2dc35d80db727c2ab91ef596e22fcfd0cfc34a3c9f837d614f1d5ad0f98b45275d59913ab43528df0ca884befd154053ac2ff4a94bfad8d615643f520e90bf5a987e4df26b72b302e6de08d2702530f6476858f1d06d3f84d1e9cf4f6f3da4241f8fec57ac5528ec099d4819a41d9dc07c08847a8fd37281eaecf4f971c20c249dd73c4bdc722569900b1d6dc977c826eb6fb2fdb7f16ca34899998abeda867a740f134f371a84c28ceb4e55c2ce5ba703cb43170d2ca456399744ef5a0c7d2994b3a8eb92577ffdd9372c055ee4003880e81064e1110b71c78b18088ccccd363759b8f20b0b40729611f5a15966ee7d5ce7ca4bcdbc2b426af153cb09ca11e332d53ae85e72b576904cf8449f295e5be6daed05d685de16de8bdc4edc8cb2db477b2e68dd72cabbb6bceef18482882d2fbf50970f570e9bf6da80dc57cd1f05b2627d34dd7294671ec0f4e2cdcfc5975a6f0ccfe5306493f5bff48e39e5da6b7d1ad862c572ae0b858a1bbd91031f7aaafb9b49d9e5bf2b860492f551d7de4ea80cfcfc614f165875951ef82ec3a4b04edabf14eb122c7a14c89e117a8513da476c87c16b2b158ae26732f8b96577aef7ad1570aea0f6800c161d241b24425bae5fcf924e5199c3e3e6e573fd36be388ab086d1d25e00fc4821475875524deee44fd6fe6f724bdf56c453ec23ccf728dacc80f0fa6fcd0a8ece242dcfa3286cd38f56a8f972fc473f90850f5b86f73d8aff277f8a74d76df865d9d1c85b9bff402cf1daea177fef7b905417b449536fdffe211d4ba2fc99d57a886dff13e47c24e587c8d472a5036c8db9c1105f4b9a916235f1e713ba60ae7ff445ef6f2139d688c6f93b25e7d594bccaaf9c34fedc08f007fa34bbc065dd1716341fcb51f299bcf396fb722aee51efa2e4bdb2b11f8447738f305a30bf5318b8d33ca9351f4028157285720df1c23c9c446f6d0b4c13620ee1a1489b399445c5a5d0030d8b1bb051869a72135b0fa7ec75a43213955d33a7b3fe6a9d2117314c1915e9d99f5b3bb84ab141e3fd47bfd479fe134e0c9fbe4497e7d442c31964d9ab25efe2e11dd323b9987202e69daad6142cdea51f7618076f898a33ee0911b4954948a9aa8bcc29fa90727ecae79f176f59cf49f91bd46f9dd0b3f648336ca1155233224ac6109edc23449cbeb94111da1e5117e12048d28a51bebd184ea491d5876210e9af94a2c0e25af9dfad8ca97cc312450f7e09140e064fb81b21e4c5f08ee6e348330ff76bb3679f4bcab41ee8a977bc1405d91947148dbcb56f0cfc03bd95106747b67002d872f5adb5416ff74ec6cefa6229c8310d863ac2cc6d06935b6516ff22db39729815ac0c7927a30d96d010d09d161277661c611f82102c53e323f9ed1d08fbfb195660d4916c5217181560069a2aedc16db2a1b9f695bd5e9252866cb269530e1d72b970c1e7fa00c10d5245822bfcb847221aadbe1e63629030dfb10e0f4e098417351ca4e6d5381da255dbbd15f2b8a004635f373f27d20d037380a496f40ada511a27c2b60aebae0d26f5733c23ef73df193c4c8860d94504f667100c0e955c7228916fa3faf9abb712bd505fe356e48fce6a71e763c265b322b0df324ea0c0359d60dcca78fde1ab6d8abb90a44aac5b74329afc8c38aa3dd8569b797b8bc172f870113b8b37a18900c2f34d8c22479f7ed6f7d4bdc96cfa24a88d42143165127ad9d5f0bdeab9a648829f7c70a9301dd507c43635b8ff814bc0a7da6f1a6c72c197c63536e794c05ae58212d974bf7f65cc545ee105253c7e5e86017a3feb72cf1a7fa1ff12cb4b199958d64a01455f637914b40decb412b4ac1fbbb112ba6fa5fde16cf3d3bc486dcff8724ab10af57655e1d8224637509a175f2e1c58a74ea75d9a253d105f9b9fd128c0cd66fe278874437a4aed28158ab078a9eb40c71aa545ef4d6525cb60581e13c47d0889c1d8996ee77b1da88a0bab91c139da94729be471efb9a1dc0b74852dd5dfeea115ead1638ad306ea3efd375c3d9f186b720e9dfb0cd13afb0eb2ad77199c240c75da98a92349e6ed8cf63ebfae29607372911f1d934cbfbcf939af5b81082053f4a6ece69ac7466464caab0a206659ef72330d617000f0c452ca5d918e99c73c3a72e794d15cd30f9efcdebbc2d1405e720d58bec02016d4377e3c11feeac57ad9b5eb1cb47af6b1a49b3463942576be40f4472c620340c054589aac1c4bf65fdf2100a6f4e06b26198f318d5ae5c9d672952556504dd0a37f7d37d1ede0ad24d639efe9d97ccdc3d2ba481b3b76a8875fc6dd389f191bc7394f9b1a62a2442f208a0f6240f83d031767fb72df84e1ee33e0a0c33f838e04d33bd48f0b79a0ee9ed944ccce8be7ca0f2bedfcadfa6df07208cada5ec6003b314f498808e7bd40b7486f9b3b51029d6b54720233d8238f72eea592e7f9bdb5a51a2a4f5edcc131a0ed4e009ea5c280a73d3e658d9ace7234263a0cc81c0830dd95520e5580032f6672642af9a78b04424a7793aae0b7177220574442f7ea64a8705564e13a0ba93f04ee6acf56863744accdbc6ebde93823a10ad692bdb4f191fe7a13efeea0046931b1fc528b768cfc1d37498c84dc9572c99945e66b936f7e51a1445a0087bb82e7b5803a2a7fd5d2d85a8b5d117592724827179f1058ca5c550e5712e3ce6e436b75d34000a78654afc7382d232b0b7216cedc070bed1409b61bbf04b5d5dd4d751f9c2533d8045eebb2ac340ba27c72cb163675dc7e776c56fc7afddc0171462b15eb692705a5d8d32fbae9b42e1272254ec52574d66dd8f93b6bad6065658fd9e81cd9b8682342e3d5ad4f8bbe367246c4f9a6d9b97f87f01c0313157f59ce4ff7435c296352105a7285e79fedab72cfbc04afbe0634ecad0683c0f2a96b02091496ca9c2447eeb213b24b26b7f3042036638062bb09b2c78d6c494e8f38e1a3d7db586f9149e9af9c4c852cf9181f499aa8f98bfb3dbeb8b0adc7afc6d895dc8c1828252b9d0ca03505bdc61ce1720c328839761aacd2692acdf85292ae43688f7cc398c3c540cb9880416bfacd72e97585cd46dad799e1a8ba20ccea812df372f9f44954948f8f0640ecbb4c8f29726d2d2df6a21c34042146c01349b41bf9e9fca7290a45088be099344f753c722058e5839b41d09175572575121d37a57d3b493079ddc7e2d5a6b6bbb5b57a69af7ce8b763864acb126bb6879193a3e70dd7280af457ca93a2acd9a7e6b1841bf51770fad6d45914b06c67cf72482bfd11a29c62c06c715fc1667123b249fc42274b9a47dc086784ea742b28a0f61ba89b89417f3f41cc0d91f38826bd268c6586c63f6c7922a4fb8c56d7267ffac385c807d015225421db11999b9e714dc8724d1db10c48dead066f743fcb86aff8ecc32e2fe32c3bbf74b948372016ce9a7289af5a984ff99ccbab54f7de79773d38d2bfabb902e6ddf97c89539703f14d70bbea8f9113eb01b4095854c342041a9d124e8d5231cae9fca0623858efc82e33b6bd58185f5157777c30221acf7071505db6f35dd93a158ce8dbad065c8d5b721a680a4bacfe2aa01d4514b7a9ab9a0481d1d98abfe1270675603f364bd4d3729e746d724ce038ce13e9961c656bdf2d5f107317123687c608abab2472e23225d4ab85fe2cddcea732949aa7d5d0a2e33f87ded3c4ddf4a31f704a03b2e51272654a2990bb6e5e13a1bbc73d3b1e940136f352996e61c8d49b88f12b76edf0720ef19069a35c26d1ec71458e30dab34aef691a3044bc84b684b242a44a91072a6c94aa7ed86a66dcafa7731ad808c2c3e1accfe5428b79ef62fa892049c675726b45c5d1567a4173dd05c60c1b2a44a9483a132b911d11cd999faffb412e65446f76be43a88f99211fb5e684abe17ebef7b6a144f7a8e9e87e2a9b0b4e9a02721195aa1311443d7e37d5b38a6d9e4732124a1dc40b72b67fe763706bca2aa572adc29b58549fb8098ec5b9cf650e82dbc28181dfaf8fb1db83b8ed32f6406f72024304170202231aa8e4928af7825589ea9803c17a227131e0bdfad9a1a6b472e794020e7927818499cb5c3a65630971358c3b5c4eff147d70bebdb634451572f53787141f986b164ff141b44dcf668c2f7f215e543d047ea58d0b45696854723b2a9a31c79451a006a9d1ed58ad8830bee6114302be9c949bcbfb770d77f072b8c49852d1ee1674092c64854ce4c5fc1de6afa78b7cf51c072933032dc205133f57a2854c904ad638cc6d6980d35d5439c6a347be3f5099f20138b7bbe9b241ace3286f56c13972620e0212ea6252d5fd9c1c09524fbe6ec0cacc9c0e8dd823abe4442ba54974b38d3f019cae780b35e7a7ee52c75aaf5ed3b7b08efd9b3d46f3fbf711f64bbbcfd62daeb6649699a710fc0b8229ade426f2ce8d77c743123277202ddd7fd119ce4c3ce70e2cd03ff55f7b9970279f437a9942c64e7e0d09072f663eb936313bac44fa207b218fa7712d30501ee5ed4c82610499364953251e80c79b4184c40670b7faa6a41b57bb559eebcc6a967f3f01771442b714308d1bcd36ccaa14d25c324ef269caf127aa722f7d91abc4e025336fd42b96c7e0d56107dcbc2778bfa541c5e75a01df2d853e360edd2c3e6d064c150aed22d227f2371c1b4f0ced50fc543ba97c319bbc61404081decedcbbbead58b72bb521e85c40435e389e0435c5c46589b0a1e0dc7b8c1b49f6303a033ded7f5055d7b7cc6472cec240f3781e55050224c49528e92747704645ee710fdf70cbaefdf1fd347c543b800927873c9d8bd6ff51a85cb33fadb316f31c62f8e03b91593f95d68af80750ef811ae6f65622c30cb3a1cab87b677363321aeb337b38991887c4509245726f2ee2ee2a0c9fc998d52b2039d40e35ecaf3fc3a995631de142961f308b8d63d06fdf8e6f0014ba0d92f53b8c33d28a780588a27d6b26e342900a152577aa72c07fc6e515df6dce6dce2a57e694fc73b704bf3ea5eb3b4de9ee55554f8f29726497a171f3c7258ca9d414171f02bda4d6cbb247a1d7fbb178865e9f4733f172eb91a62d3c1d33c6b37a9e41eb7063e2ffeb64395ea31d50708120bfb1432154576d80ae5a5b19d8ca48608656949cdd61ee07db7312af99a43a09be95043043a393f1b5eec69e99d67cff0fa569354c3ec88b7f6b278b92141c07837b87b672b9edbe4240a450e4ba22547614e2b5654ba208270eba1e75238f1ad84349f6722f22c5468919d92efd58dc1a2c3563e93ce9ca41d3789efbea308ef87d02f072867494dc2fc30ba606ed2973def183dea3c88791b16a29c52eb33190b1777c720e37731a7dcd6ef44fa571a946be6a977546696fed99e10606136b795d7c4537308cebcb66283f7e758263de07ae7167f64425e0ed4af77b76073d4c3a67bb723c4317255c8e09a61ac77b664da29f390492aa6e0e83d266d70725b342d96a7202bcd196511bd95c6323b55e0954341379cea738435817f9b371b494b46ef672a0b8c4e2964bf17df8500d7a3259bee31bc3a25b7f10a5e04e09219eecedfd72bd93b7585e616ae9fed59d4f2b5b0e0021df868579baabb3766f6a9b0d916672853cb48b7cf3ad53aadde3982054a6d23045272eab994b1e52463a48d8751f4138237f0443e6e59fc33cae247c9c4e855536f43109d9b188772d1d24080ef46d2c042888b0e54e6f2e04fd2b5b41d1b92b0af3fcbf86145bab3d3b469e99025ad7cc95cb9f2858fe842c39f20f6c9d3bd94d98756dc30fb1e10c848824dc9272b9c840a4f25da982c0adcacb9307118ed061348e83d165e0c3720b355ffc4572f9998c0d262162c7369af37f36b7a5ffa5f00b90f928d16c492c105388797c7256947d8b4bf29c096ce654daa2f09d53072201fd5c408398aa801f99c0cced483d89cae6d34ed2529d5e56d9962ab1d2ab0d0fe7793835aa2d04604f2ab22657f56a05f438d4f5adf0aab65588f79abb356ea286e25d4e04a128219b990ebc393332dadc72e5baba9bcde316150fbf1fd2349600d8ece22084e8d4ab9f40c52a08bc95a84f7afaf49535c2bc6236dc633880decfb2c477ad85964542e58b2572acd5208b36b390c837b46c24fa5fc346a8217bf4dda2ea0b2e5561ddc19abf72597f3d644a10c85b90101d114e2c242e11d2a5bf259b12e51504bf1ccacedd6dd427520ed6530e56ec0a66fbba5336cda602ff883c765b227484527d7e8eba3ec21eefb7f7017265bf06725ec6b811c3de6e2020450ad4e5a2b512f8e8b07872f33203b92c38d50aee863fa64bad1a6ee73fcbe3744cbab5693fa120fad0170328105798d911cfff0276a8b9418d71fcaec609a426d8fe59fc209b4691766b729e7a5d5f484ccf7411ab3c1bd04ac6472fe85734de9312c55c42ba403454e572cd82ff2d9e9d130823d6c4cb9d89de6b15d511ef574074e9c7e8a7e88dd14e196e6bb9c10de8fa2a48c5912e7dbb6deeee4150996459e56fecf47b0b544341720b610ef75b2fc223025b5de5666ffe8a7d2b73be0cd7a098423ce9900615ed72f3d1312c1398a14ed56bd9893f5e8c03b41c531af0f8a22293a012caf6c0567255439fba64feb04c6a0b68745c8e6365733ea29ff2fc5c03ca0f0d4abf8f23725426bf052fee2199655a5c2be1f67db6d10754b5c68641f7680223309c93110e88f04905f70df33c7a5a59d2e4c1b1c9d17c024a5a3e76154ba42b5fe56f8f2e0a1ab74711f7fa79d108e04a8fa61ce2467f17e24101988d643bf54d2cee41727d2fd79a4d827791f4b7826e7df6623b6369f440750d31ed45dc520688077372abd57d9c774a6169e1831618a5fefaa150ce56acf9f8f27d8305b80ed5aac045ddc32fe818a9e20a722643d3f69e9230cab4c35e22d8502426ff3cff21b2807202ec4081bc4b12f26ce266aa7968f1b4b229a839639003f6f00a653c4fa91a3dc0d61458d2d143c54e5500175a06abca7a2d81be8a88825b589ebffb2dfe386748fc0817ed5c288f73cf1e345f6de053b37494acd7886213a3adfc1f55350a726916f93d07b9ca5e0773bc24692ed34d80bf2f8163e64e08a2de220f569f2a727e8ade1e6dbadfc17a01a7e40339a240975b5a09be5e9933fc0ab8d2f4c8e336fc7d1c6091e9d9ea37c548e5347687d34400341472eb39d030ea6d363aa40a72f30e10cc406f825e8359302175495bcd6fc9f98477a71cdfff13fc0ef79c8b7221a4da21434368bd57da87018d028db7617e4a426c0b5a5c700c1ae747c2ea72024b8f6f0e9db5450f8e44b8945ef7140a7e478b2c3e514728bfba8dc17c5972bf795019e2ceffb3baff84fef88fd87bbe4125586611092c00ac4fad0ff9f1721c00dd7d05986ab7a9ef6f87c4a74a15ca21984827752f141bf5b64f268cf114dd0389d07993aeeec5c980db394b86974fc08494427b419cf20e58bd4ab72f72014fa12d16e8c9341f69561397df083574ae096d4c308c7d32ca5639062f2f711133b1dcd09e226c8740511a37dfd8d2521a2dfbbdad31fac0711b1aebaf3f70616f3ee4ec160544e529c117c7a27e30d26dc3c0957a84d790ed3e712a3723446b64dfc43b8e2ac7cf539f2bee671a03835afc8da8fb6a6c95145aa12ed94572d518b13f54cdb8d6a635c7dc74d683e6c4411b4554ab21dc9744b2e62d1fac728dfe470dc7620e5a7b528c37018964472e3b490460431b493c551d88676e64725363d6501ca07ae046b3b77a87319c4559b75f62a667304c68455eaa74f9d26c32186653b3bded04866b4d2a8beb9bd56241f44bd783869afb4a9364822c6672b051dfd748ddc3bef17d7a6747c883f65f15d9cb3175032298d476125e369a7207fde21c6b0c3d3dd68c041c38433ab4fdf573e74d8baffec7cfefc82febbd72a90283e3c860a0ba4623d2e3eb629971dc24a06f1c6d399a00f152e43c28074958b94ad037ef53aa83e507a949ca9140a9a0170ce8abf681de06289f4615f272e06a9dd3190a259bb225b6f56ff2a22025c9b2f7c5e4b43f7c345ae24a891b725f9e287a898c2d8cc1bf7b14b84e0f4dffd83d755e2e3c5ed779790389037b4ddfb22b0d995929c5e825d7862d1cb8a106d7d4a5ebdf70a87965904c44cdd2196b040610ffcf1e6101700bd76a498db0a0fda4915a1ace6461a55b39155c9305e841b675a4ca93506849ee1c6b5855d51a78900773ab98b6ca6d690cdd1fd972b6b460e260cb1267c0a8d02e114d5882c9d40e3b7e11afe41dfde45bfa26a1ba02000048a97e421546f8d4cae1cf88c51a459a8c10a88442eed63643dd263cef880c1c72f27ea9b0931ac58028537844d4ef0bccbd74183c80f04057bca445d2533d68219a0565581cd6b8afbaa125e1f93956cb38a97960232b4c2040b1ab0bc66ffb3b25b17bf7459698c12caa90535ce500ac7767238b56ed1c6769547417be648a1142cebb3596381b744c1f44cb219b67fef64f64ca9a7042d04c89615ff9f93572fd5f1f617d3e1256540da9006405446cdef0001cc1f59095a84b27915e8c6f720a6ce00b9a4fd1289fad80b201b4613eaeadc256bafa460c55f03ad0eada35559f28f09243213d89d10e442ec28b8b8c488eaf764e25e735b5179c162845196d4c6dcd93cd96cb3a9ae53feb80c8a3433569e6e2c524874c3affca753b78bb72a2e4da9fbc0042d84664f1aee190168d48e18fc9af068d1419d32ea7ee52027227f75143ca6dc85fe6122d63620d31a314ce2252ab688511cca3cb760b4f446dedf36515b8036d7815b055f756cf345bf4d1d80eb4a0b0e34e755fedebc19e722b7b530adcbc9b5e1b8f6d05cdc323205c2dc7c99e5bfd73224d0a0786889172dd91df5ab77d276530588efd73781bd8ecf182f4160f1f9d6cf8ec6275e48772e8d4e3f1af5ee7e21448318297b3dbd33bf6e7f4b6805a817aed4b949b12c1396b526d7e9af0d3b1687bf892549072deb61f7529cbf8f36debaec7364fd071276fe3cf713c0cdde496b7d1859c975e6a0f4f9ac855acb8682e88a0fa095f8572d8b247de3b94dd2adcc0856ac8ab2e164d4be554ad16acf237f0e17e556d5772ba4d525d8f66fe4785cd565660961ee5122b5967f84f58b42670939198be9272c2e708a1933d477443c26f02cf1c310edae0d9ff609d400273f5fa7cbc75c61c4ab7869673019af6ee41365f502676da51bdc302f2003ee7c3c483ca1c5ccc7205190a917c132d448324ab59eb50a5de4f65bfca1d56544bab360a271e5ee072d1f5cd8cc7b618952329cb95faa79d3e261bc981167c97e84dcd30aa8c3562244ef6fbaa8e7d0577b4c71f5b0839c191c5c7f3f9c003aad8a7baa5edcf3a0972a9c8269cbb0c561e9642ec423c3d6740a94072f7b4873683b0e45911fe54b2156cfb22366b9320ad07079b556c82a989e901f6dc3ad61366ae1b16ce8b0bca65b9cb31e62617e0f032f913a92ba322737378beaa45de460ca4764c624ac6e37231ee6151c705ce603f527937543c46863cb7e1594508a63242866757bfb2b07239f9424d6e05fe583aa4f42b1bc6452f3e9efdb12432830457a13e5271980c72b9f476c53bec407c4c9d6298ab48ac4a5c5a27df1d47f72505375ef0259bd0725f4f96817c7baaf1bb3ba84239c76d8ea515ffbf348ead819ab0fa580177b272166383b57b00277e091f7da2f2abb4213ec74897b7ef21afa364e2b172e5be5c92536435e6fe45bb1b8bcc18629b99c0b46e80c75a5aff4b90f108bc37b94272595d8008bf8be26d596c741da7d8ae3ab393fecdf22b8220febc8d7f123749722578fd9f6fb1f7d1f7df548421e62c9a1bfb20d8063b53c378f0a8c18bc23d2aff757a39d55e44301ee92f682e52eb13bdc2c55b9011b1d32e9a7775e256de3e78a1b97da2e512a1c97d096bf8e167de4a9d551b50cedfeaf8aecd1006826872a41b067e4bfb03a1b65b4e0d1e20ced64445c13daee5a851d2ed2787fafd50723e65d76195da1628ff652e3389601664c8ccbf08308831c1be3ee871621f7b72821dd5f9580b3e98b06a49175d78d2b12e54eb33f2c8d8a179b1bcd396081672bde9d0cce17a330a2cc68b069b77798bd55ffc913f9e6eba40338659e24b621b779370ec63899db257302048ab1723e90b9ac8777444d1f9735fd0688a38c56fbe2f26c41f5df0341a7b2e230db662ff3f7a7192fd0a1728b9f9a4ea3e8d2a72d45c98fd0e94996954aa8a01ba1c928b558fc8fe8e6849260ff28759b6261651a319b019ba9c2cf4c188f2e9c065abf16c4cd6fab0d9e4e01827f7fbfe5a2172c811ab4175d2ac1851c77952431acd08859389ab7d5ef6740c7d3b62c0c8ed72da2775cad107d93fae92456730d941dcbd8935dd3f5dc5c9ace80adce391ee72546d1e3a7161c2236368228c7aed08b1bd010223a77f048c4fbff782bb6be272d0335e1432f04ecb8c2defec22afc519bc29ff20059d49cbb2281dcfe5b6da72bce8ebb05b7cdc58ca32b55d4069e417f9977b9c47cb01060e1a1bb62b5f8705847a5d2da97f840eba97f95b0033cf92a19aaf737a44f0a78a7adc318c37c839eebe1fc89337c6903000b8cd85061a74d2ff27ad20b3b7400f8286b2b13b7b72f08f1ed3666c27b482e711ce469664466ef7e26c0c5c885fcd14e89fa63b40720d9b9a57bb8447f5e5c0c1a7e408c40f40a3d7c39eac8ffbfa237d4bb239a42b813a64d373002604af3ef92b4a472e1cb877bfc5b19a43cfa247e2def9218822c797fc4d5dbbe8fea14ce9d6362fdb5b9ac997eac5eefe2337e12f91297ec144bd6e1fa465db1ab4ed5951b1f0df14d93045c15580c713ed238bafdb4be977726936e3c109442b6d3b04806abf41b5801649022498f59c6e6a17bf087d0bc01709c91e0a1c7011a91f103192b7bfbd668768b726fd11eed736eb4d3b637f3f5be04b68cac261eef744e24750fb63f8fb7e1796885c7fc8be07c2bfab94c39372f1eee97522e4f8124ede987fbb0860508ba433b2f12f12c7bb18eeee1ddd84344ec1ee1c6bea6cac37c768932ca888b311b3bd1b613d7e823840488c8ee49872d5e07d2221ff92c1c50a61bc825f81120174d8ef8251dcfae6b0561224547f1ad9b02acc6e11bf147b7bf436d0339c86364f3710127a7d2b168391de8e0fb85aa23c1bfa08a79d4982ba9d369d9ac98704528688626e0b53b18ad4de655c077236311f684986fabf6bb6079fb6ef9c065949d87a3c706a61920c6dc0cb0c08727ab751c057b2b50d37cacfb7f18ead3a6737c567d3a33afeed2740d62d920e406e5aa8975584162c787a5aeb7b0b19d19e61f13771dfb12da8609cfe9c4bf334f2773a378ac4cd255ca134ef5ad3d3e06a84d67474b4fa658356934c4787747233f75699623364ff574bc11626b6e7a7dfd5ac3afe53d7602723c618276deb1d6c3adaa331b45ced7d35ea94966ab85bfdb69b49479989f2b7230abd7f946a28e2adccf255ec835242baf54167b81008ee7f847cccc994f9f1d67fe3fbe43a72cda2eacd3793f608c6e8223bed794310a936303eb65496bb9fd1edb4353b6872aa72fa8555d1f7aeff08e9c1bccc548e5a3266e298cca680ea7e70ea4fb7a8640a377b3873478bbc92ede4e3ae5381b24843cb2d63f684f0850df0eeb0eea41fff7d6499ce3adfde8408758d91c0177a9e1c0127d5681bb0c269c5c9d47a7b72bc5229c65e5dee2508b66bb80b32b2ec76a487af62b3949651973b08c7e76909e248307d7d758691744c267ae9c18d2407d430b8fd6be8e8b65333de7342617218cf9ba5a8c90fd2aa46414faea6b05ab23b06e0e87ba8e78898eb7d410b421fc9b6c1710c1da6fe8f2756e4165f12daa1018615b6a9479bc4d716dfbf4bc263b7f0e02147444953aebbb2b459bf3b4ec15fba2fc33f9442878b7e5a78e882333a29b7758d23c88aded169ecf79b53b164b982e07d2faa7c589e411590d5c37244eaef4997613b27d584dbb91ecbe7f48f4bd549fa2a4696fed3c67abc66c37286598afbe53e29b4eef69d96ac8c311a62c68bcd8a93d1ac0435bcf58938577223cf38e6cf7c1fd72293058868b7665a036eae8950ce5ed79fc8af96986e6572ba83768a42535d931b1d13f1b83111e4cbf6ea40b5f7f7463e5189da9bc40072358bd912d6da11a685e77ddea06bb1ad1278d734369e0ed8386d0bffb78ecd72e72bf521396ace3516b397f84e939022741f6f13672396eb86b0928bd021b5112e4e6febf25fcfa618d25a792d29c7c731d61f58ecd2ff69c3a64941313aca72fa5fee4959dbd769dc8b76efdd2131511b743b706955d006f19e55dadfdf967279568c0cfa603e1140f1ebf4b65230458f863d1834fc18177c62d2071f6fe072e6139f8140e56ee1e8b7791d33fbfcb7c531f330c9c5b181622a6b039459441395402bc23557106201813194385efeed7d37b008d4e11a5a478d9f4ae7b28f72fa75f10ad31c898ca62cbbf6306c90b7f90ac7784ef38c92b7059a047239e1724dc42f6591f41f22b1212d54532b94fd6c325472725d45e913ed53731898312d6ce2c4f8751a10aebf142d50b605d774aec9097ed03875c89e6a7221efece57232a25248c6ae56f7d9b6c56657cb00b4eaab95e83ed5c475da3f6a294324043f19cc65c9d7972873927136d810ae4b1fc79ba50d5744296af432349a3c41b1727ce0798dd29b5350bda6b948fdc03e925ea19b53f199572285e0cc9541320627e760aae4de12318c10c6270b54e0cac49636ce0eda55cd4fabfd7aa26d2f0c72f0689d587e604ac3bfd193d5ea71bcb868627795d247792a56e6dbeea0dd2872ddb5d986a39a4ca7a9df8f18a9eef7a29fd6e9c1e00c18f3a5393e571cfa9e7234486193a5ac1201f18b68b3262a1b1793c47c97990bf8e29ed7fc0da3fc2a21ec4bc7eec42a0ad4fd3f6736109729bc0d1906875b6d3813ccaa66207e1dd1724d481054e7f2530c1dd0f53850ae14c3e308284728d00ae801dabbe6dc64827209a24dcd30cb474b29acbb8fd54917221e48b66037a23ab690a29a29422db405ec9857c0e7372f22b3cbd50ccbacb896c379b9014fa2aced9bf411c6d960017263163d1e2500234b9f78495b28456697b2ffa48335590858ba701d2c4eae27722b037319a89d381b1a1e2880499ff6231094c664f539e7f57a59d50ecc8d62727fafc24a61e0d323b84e3d904e1b24099bd6f842d12d08abaea751d2e6ee4d72d63f7f55c89efac23ddbdb5adbebabd12f51afce4628f495f507d91524586d454cf3b17a847f68d1f88c1ed8c93e98f88696a049eb85e745c95ca81ed9689272edee3495903d5f89b9c52a8d11e4f633f5edb6d22e579be743bc0ec0264e94244b4d09c5a84f031e267341ffa34b00065e43ddeb9be7454fd79239890f68b9391cd5e0f3fd2d39fb3f3461715cceb3dec9f99d7460080bbafdc24d9402809d72c0d7d09cc5d9579e725a91f670ead29aeb6a7ce5072120823f0354f62447932c9b8daf54c85dc4cd4b56d2a8b5a2c67736f245a9eb55765d1eb4a43a906f5e7235410ae1ce01695164b34ff844f11b4ce68803c18009b41b255b94ab5a26307219db0b0af1a29be9c959c8eefce0289622d518541f9e0248f318e98f4a14e66ef0e83695eef6fce68d9f66e6896d16c58063c645dc070a256458d7c78dc71e45a8bec4380831e95e24521b39d8c017b0c836403be9035d408b039bfd45374a72ae528946d9507bad973ecfbb722eb3bd9197710b87470cb23abb1cb8a40ec46d696f52d5af2fb99f4d5e41df875fd180d17b8b539819a0f69fbac5e54b612e0dd867e435b9c48693bc3c72e6e69e66d93423dd99c78469a080a250347960996d5d989aaabd24d6f42fbc4da53d3ebe5450912bf67541aff9a749084a24bfca72b331325cb1c3a835ac2703baa4e12e8eb20578e38535f3d4a6d3b29e2f42a672138b5bf9db6b01f87f9d792d7db2f666fc76d09137cbd2d44b8350e3ebd5b57290d1d10ab92f29e764b38bd002ff874f9e814f595dca7108cb25d0027d5f59727aea23dedd6fc93fa3682839a6ffce53f8aaaecae72b4711c673f9b7784f4672c92e7baa1c4bbce70ebb40ee3361ace62c92005e3c211901b17a3f629a7f53726f1af8de9c690d06ea14c51e288adaaadd18f15700d48d148e891ebe316cbf72d2ddced9919566b8156742ca8e8eda2e6fc3061b7d75763b79e7bfd89a60fa296f6c845d4ed88f6c411d19679e29a96ca15408e6e96e7589b28b345bb1dcce72b471ccdf359d52615ac8eea90b6b72e95fce871d63c5fe9af3f3c912ea2c7e72ea2d2434ac1d52d12dd76b1ff0f1b901def91272936f73516c841026cfaef20cf2a5b1bd555cff83da82f4d2d8c7db8695613e7dc576a272188c17add03414401b0461a1e65c2627537bfbf6e8d9085b7f1d420586f05ef128a7913fa4b0437266177387f4d189f31e8e15bdfca88895868626d3e44c0039eb9067ee9391b57245293fa0f32d1d6b4424e6145d0745191c37f881cfb2a7ca96facfac1fe18644a6745954680b8fbcee54de8b4a3d00bba1bd550987e296469e0c9139560d6c3811288f1dcda49142e16abe03664190456ab77daf93ae664111150aec06110572997a4b8a72013ece8f87ca4f5d56c85d67cbdafd68312bc6e82517b108cc907205d7d05962f4c93cbda4e910f8bbbd3a9fa5d91658c191219912021e0c74da40222a60966a6d6c858b86b9b7571bc90965ad494b7497e352df45582955fa9772fa0b698cc3a5bda6a7b66215d96c66f3171ec7ce9c8bc6ef88b41c805aa17217dc0e75d9f7b3371f2ff670145bd61c040a36f204c0dd1585f7cec333be78d633688e3111a3da665fe451c4e6f379eccd4b48780de29f0e525eeb36635c35bf7282661e7a312901440833b473eeb4f20fbd7fa333663aa43ce1c196d2b3831b72608ba2bd979770c5c4a38df450e24cd8b701fb74c8d4fef2a75bb63c2d8ad4722198ae98073dd4d9f137683d8aa1f286d41607c89c1eba511d9222c0739e82111c583e5343f3927e30f089c31e60cba84a901ca75b619c5d31a0607098f6016b8e1909161464351c56397dfbc2d921588faf09f758c176e232d321a6ca3a2572adf1c1a1aca63c781b6a8a294b56d6b21eb5d3056991cbeca19d6853945ba0721c2239ae784c4c6c4136c42c43046fdffb64dfbfbbb092b1024a5842e9653a7295cd13dd6c715a45607e58a3c8d2d623bbbef60cd9b08056f3b8d42c045ef525545c8aa96130fb4fea58ce341fb6767ff217de6778fe39379f04016521628b72428530275be9b2d9bf3fe33073897b0fb526ef0b7fd0ba1c555397e44f8ebf726a785e75b700fbda093150bebcf29267a6a64246d340f34c0e4f1f7dc8b30a7228b7a460acc09de778721ddd9562a91a2bd580ee434ecac53e8c5970603fe6728a55ed296e11bf7c8ca4966222c548ffb94f39c5c9a4d509a0f6200a1d4db2727784629515c6b33453c54ac745775ecdaaa4ad15451020ebcd166ad476c50c0e6d1717ba65473349d52edd83f3ae49a7d1da37f01d4f9f53297535fa011ffa724db99a326c28657989599159e2d7acdee3bc1a2f1d230b4cefaeb989913b4b076aa229ab6ee53d6496f14419c7a9202a2cb4ebc420584c0918f09c0a8db2e372ba644dd29acc4ad23c613abc62c6a01b71c3ae447c068f5d0d8c12b10f96164a6789f18b8767ae4ba2e8a45fbf8fe1bfa0db5435c874771962301d5bfde2d2724838cca53364e18fa5607d19bf5a403f04cdafbae868843ba44758c32a306f4462502316d242fe96176f4494c5ccda2d4425b515c30134f47264535fe4387e1304a0ed69fc8b907cb73baed1153727619aacbe003331fbb2588a3e9e3130c948ee7ddfa8b4036297745ef4bfb43cd07c2e1808a8881bfdbca427138f91043814cefba6842031f552d929ef74c20a4d17381686a25d9b4ccfd3a1c2d0ac26207210ed2c5c4a520e565a837100f5b89ed0cd5f5cc16dcd433cd435020e521ae04bd7de3337cfd26d24905e169f8e55623bfb0ed6a42c7c5f5fd72a1689920af049b3888c8b3d972045b7d09ceffeace3ec1379f90b42338b595e2bdef61be2b172e08cfaca71ebde8df7914d569b9af92828c14939a8bdad3e428370affb825872d9c33378b28332d3d1d4e4fdfa6b7639794201500586fb2f42d9fb03be12b4723c0b271b5f8bf8e8712530cdfe4f6c31524d03f86fafa30edd197024997f1d2c049c2f84d318a269456a427b456ab065577c25d7622f9b77d2cbc53c6afd444115ac3964dfbe9d31c0d5ae123f4d65bc240a4dc12e8d39ca53c2d3dec9d53b630ac11b9cc65b367d0f33894dc180133bad35fb98b1a3a838e68f847b20604a72a5d6e7ae7e2c33f7d80ce998d812498318d9500d7aaadee3c4752a28068e751baf15b6615f1dc4c3f8ac7e0b24eecd57ec366699cb3eb1ff42075968e2137009ef22cc10659d8cf21cd7b8b373dbcb67d38c42de806b1f8aa839ee8b5360da722b8b267e5fbdb9934d989f991c8608890d9d50be85ea103624ab6a037bfa6f722abec9cebfd5aa6b9f1903b622272b067fee040588da143893fe76f8cd194c057032e749f60f03ff85c32d670a00bf98ae5ede4095bd47f49a70fc428cafdd72891aaa1c75913b41a4a2d415d04593ad4427ef51ec908b0c44f049a09b386172aa319249532509269044463fc0383278577d02fb4f4b157b7ca9ed62579bac0d2e4facaf924b64fd715da2500450dd218b1e67efdca06de130ba3dc3b3b0d272b7e4caf2503dbd0d8fa9aa9c1b99744507b9b4b5a75d10353312638cbe14400da7b43a2a46599414079e633059628adb82bfd6c1d92c5bd9f89d508277dc23094950f92748dc2a43940c2237bdbb259dee5361be0f61ee339409e2fb1b817321ddf270df9ab7c00937803624110eb2008b4bd596ddd0768e3bcc0e46c56f3c7251e967ed8d0c57b511c26bf0f29cc9c928f4799c112823722d8907b986ea665be7dec2ab1be77ead1415f94b8cd7fc7b6870b0d55a0ef3644982b9b437b8a6051913d05d38979de41065e4a8e0da4b7fedcd35cd94ea1b17bfbdb900d0017771704f6e2221c5c235b7ac5e3dbc3bde0c0e4f5a477acb83189344c480c92e2530f7fc88ea5478f2c3b10b7435c3db2a9cfef7daf0ca72de2fa74b38d8570beb5622569906f4d153729a1c8168eb923f774273608994d00c64e290a3ffb471d27235e361cdb36c00721d27e568cdca2d2e061065f68f839919f462559e025f52727860142539b246916397605492aeed05b1fe89d0c1f855e08e3490a2c2e44372880467fcff20cff9ebcacc9fedf2c6fa2c3179926854628fb40a3980f9330572005c3586741c1f2977b980d73f493b6801bd010f259afb910975cabda27ba772ce6bb6e45b4b263113cdcc67a2995a4f0b5f2ec66c21497e534bdf433f288c4d27b8198a28440609e21025d532914dcbc62175298798bc74c8f29f15e0fa6e114577576e8f7bee04cd1ec8968f8ea27d02bf8e22456bb041e6bf6a91d870517263bde0c55ce1621840e8ee5524fe46281bdf98cedfe7706b59b30c90262ff2248ca9d9901366461fd264536976d96b32ae3ca440595574614733e1a4fbaeba7104de4200e8a2b4e82f7fdd54ff11b7b16fe5cee9023fa3c6ba18238fc2c42172fe185880be15d6dbb3313b4cc5587d4bb380e0de33d6661e318d9d4756a0e472a589cebb546993045a06c9d36843066cc2dc52177b10a37108f9fe248fae9017671c8de90977af27d1203b533d454f0ec594f5801514013a27272e0c1b7b1e7227cc116c9921af5c8142af522200966ccd6cd1c8e6c497f5b225b0dd51a7d7139ad7de5af1e76b1a5b30b6f008c0fd95e9590adbce886261f8e421c3e3fb4a5121163cb2a746cf1310efe0857cc5c0b6bf837bfff6d1b1b3351f7f20180eda72e71414d30caa6d3d6fd60e159b9240087c39a4faeeea2779d12a6615e8f1d472df32c88ddf45bdcd8104db108e5400ab6241bb1113ef9b0ff0af5f7b2475dc11be7c5f19e1286015dd34562b4103af573dd396ca934748d5fd78453928c7b87276ebc14fe07804a355d2ed7bbbf6b3493a80e1601a54454eb76c4faa73ef5e54b19bd3a2923f44d50b417ef239d5f742d970b9910dcb04ef53e06268841e0b72388247665e35dfee26a6429af44c32ab8cb74ccd9bda17182fb151663c11da1123c1513ecfa24690b6be179e29d3a3b94abbe823e302d80744078abdc581dc72aba5f74283aab7b603b2404b2e9e577f6f97ad8daac64b92d01716f5c8efc4629259ed212014f455a087c09628ffbde0b1fd730b2a20363d46d311de587cdf47278a1490288566fa6d7a7f8488d2aa8b885d0623b3d9bc1dde89532965e1d272e94b4b92b1cb94dcbf59d62812a7ac7f5222c59bef65ced4d526d1c4c859d852a881c2bf326846a0cee36048a848ff19a46666044d7dd8fd5cfe3b5172fbcf72b2f91550c6b172a008de148fbf9dd3e6fbc48ee84fee81dfa27b3e6b575e846aefc7f8079ba5ab02898e7984b1abd6753b5f7dea346c365bd07bfd3abca40372307a0510cb6a71effaf0c34e7de8e68311b66365e86c37da42d3ae1c06c91072459812cc3df0f474e82f853231e5cb3fb452e089e5f7fde6526085d8a42f9067cceee3bc489b0a3bae23828e558a9dab858b9859127f571ff06f82d07e6987725858318454e70c1f085637b3fd6c8592b4382d5805dfb8ae30a937108ed7d761e68c5e6fdea0235c72c094e197cac91fd8f08441d42f57dee10f88f51fa3a17230276e249393ca3704daf67c34b5a6c7997e87a29295601d7eed7f3be01cfa723c0249a14d24a6c480a766222f2091fd141d10dba4c83ecf499164b3bd0c8a72f36f80280ced40d2a0dc18d89f96e30bea146f08687fe1d35befeed68677da51dfd712a40c473eb9dfd5d4ad2609ffb098b3f0bec3a9ca86a7702a017ecf0d4f2af1af4b15dc84e0e2330c4cf2641b816b2adf07ac11932e8d0a838e0cdf8c72fb6a53fb1f9e1fa781bbadb52007ca9d0c26885912eb8543db01e38c3b2236612bd5a54c09dbe6cc4dfe2cec86bb2829891701875dc2c4ca8e121144b6968472972bb68dc434bb7bd2d8387b1fd6558e4dc85054bcfa3b1b42560f1c71ca2b72511d21834c443956a9abd1248aeb66cfff598b571d304eb15d2fcf119dadf072e2ed38bdfd12e181e2fab6d34e48581d15c6db2d35f1e92bec32e93acf50eb722679d71c4998055608cf3100b41d56c8a2179645e10af39d6edbb3dcd611a6729e18732b4271bdcf8250f9a9e6e19c7a453ffcee78f24e286c62b9724df50872451ed89ed60e45194780e4599cc90841a2520aa018f2faa3fddc1691156b73195798c5cf407287623c76f998cbcd6acfc734f2bdbaca42294c4c888343ab3972f82f5f1a2980e75d6cf5fcb6a78423c4893628455039408ebd35d34a256b6872e240b4d786d59cce1486e73945bbc16ad7ed4e81f34e3b7c656611f73e128b69c1765331acc63b33286e4e4c3b448b43c399c057d5c00db717d5349dc11a9f72c20d2f1321ab98809f04bfe066623c01f58fe461b4863e80593987fa46124d7245f4354525ce04c24cbf62ea156588f802603fa1c2de8e5f84b015e527fed472e69de307128ec3707d33ceab8a3ca9becfd932e469f960bc3777e988cb6d5272869ca51f220bace2099ca67e5f792ef6109753ea3db38e9a68b1d48763f2926a243dafcba77e02dc9a2eaa667503ab90daaba4785e7d1ba877c4067ec0b11f72558dfa0fdd2a660edf99627f0971c531db1b964386a3c8d13961d1f8fa441672e8c92c6efbb7643798b5ca924f2a35afbf67fa3ce4ce65c53d1f67cb0868f6724f11cc2244bdb7b085ef8209edd23833ac6ffa44a48d9a9eb339d43b7c36292ae31b383f60066184a83d233ea51453c13e7cd12364759fcef2264097ae041c2eac00e357bc07460eb8235e71c314d2bc3170fabf42e53e09cbf18c0bbe0923617d159b3798c250936f498dc8f2e677e616322c30cc128c94a023d89dbdde21720e38e71514480ee5504143eccca381f836b60b860472567f44eddd639e8d9266bcb7a8bb8dc2809df5b819db179c61b577e3413c206afe41a8594bbe6952586f925a234700192a6e4dc4f605d2d4ab40a5f2aaa256f3289a21292ffeebc1221c153e615186c19c605e3485b3a4db76cd15a1cb0102ef78fe3415e190e80c46236123acbe0fd99866cbca6181806ad095f467f001902bc6ac1aea6bd1619177726ee6648ab950b5da2cf9774cdeb4eb8b94f878cb6cbc8bf76ff341fcdc4e0b72537f49f8606f1a4fffa5a9eb69578743601f76bb997627a04146cba8097d9472958bcd679d1c137ef7108523822b715ee5045bec7afd7428c869f9877da56e52b7ef06fb1694a57ef184d8828b1a2294b000109f316b2acf92dab74fa3b25a72a4b736eece330467b7da62597965005a217f51ebaee009ec25ce43b537998f60263a85c610cb4b975ca9ccb13f421375e7d496856cad06a7a1701b376ebd42607381799d65c453df3851f5509785f86c7f31ed6743bc635e22196dd316f37314800517d218c9e7ecca4900641d37500a879e800d7508317fc2f3919086395e72a3d9a8249750ea16ab5b258421643be540393b1904688d8fd37a8ba96c96bd72360e4b595048035bf8ef2fefd5c1ad9803ea9b3fa1c3d8212b85e8500bd57734e064a7c91a3edff885e8fa8da8d317b67c0a8003d29b830d97f0e440897b2672e7213134266923bdc64f0ef97a90c78002785c5d314a135d5ef73adb87a28e72e6a9b051f569a0a645b7c10c07edae6e44371887388a44a5c4fd72b804b04a0cf0df08b4241bfa784a3519fb5e90b1bbc9b3ad605f5d154a2798624d6462e3721efeb7f279ad3f7cd0178a69b8cd82620672e1031185b954db7dddd4318ebf0d9c0d25456a0a8d20390f07d992f91deaea122fef990a18d52b4e0748c0cc5172314e0a7bc1b29cfc0fa7122a4e909477c093c823630a26bbfcc28afc3fdccc626144a0c57d82b61c17f1900c9046a3785ce07543b896e85937e428193f819a56f24265b969ee0d771cbbb425cdabea15eb2fec6987fd931c06d16a2f4c6fac725360f9afb4dde9a555b4c88c642a08ccca2db0d2f67bc3e5f573bb58b55fd572d999abb4b5ffa281ffe1f45141eb9ab4782bbf9d5a3892990f2604516012d5727a117990e81d2050a187e189dd963d485ec7ca3fdddf5075db46d4ec109f8929dcc7bf7c71ae23fb0f5129df74bdf3d149ffb46ba6b5c71cc08202811b54e372678bbf593e7e0b476a175bcf3f03c1b4c7ef2c7ab6392a0a2945ffa4f266a0727831e454d60e811b37c4fbcd45877c5dff3bfed8a76d489b4c8c418b47de6472132f3b7b605ea5742c9b43f2e5a55661e2ba452d3f4019c5932b7a301cde8b72447c4a5dfa04215fc06b829b509999671c70020016f949f79d4daf3c074976724027e3e5dd3c49545b0c3a7a0ef7e7478495933f8f4b9b10216fd6edb17fec28f6abcc7e02aa447a322c16b7be0c20ac42b648b55e0e63707a625dd114791f727cda7876289d966bdfecf946e29db68cb65d7b3bfe7b41492277855a8aa04a72b763e67da6c20f2c2dd9fd3fbdc97607f34db38710c81f2c9ac20e573bd80b72f2d4839164dadc1c5f78c0c939f34af0aad6f02c5ad1e603a5d1faedcea800729c4ea374eec53eb90441a9eb84af2e58122657f156d15ec79226bc398fe0787290e39253fb6ccaa60f9e48c305689aa3c9b729ebfec851db9661806b23966d7123312b36dd1c28b6351f7000f2be28b2935fc64ddeaf5310443c71b7344489726d885209e2b9ea80be5da393b0f9b3eeb29a1d40d885abed9a5cd7aa07917b232dcb5a1dee98157e1995fe645e3acb58b4b424edc4c44f07654da70bad1f2a59862d28c7fdd1294014669e97bf640942856daf78f33194397144f1e6c75a3929358b59cd734173696105d5765d435f9f041dccf7566db89d18a3dbf7eb51ba7218d4d894c0be769c4ab2083d61089e6cd090d5fa73415c1ff93405bd75c46072ec2b65503843cea15482fd619ebfd31654ffdddb8c554a2264844c95d6bea422d4c7788d4ab3ae4e66178749881e06f9a17add46e6be9230204e036727b2834a23772666d1b65d27989ca39f0532bdf3096603280719c6c05833252e11d613466977510297deb6e21b618c5c61d66c3336da27fac3bde9dd5a0e5e2840b8f70356e4b6efe60fcec138eb8969cbd02c831c012c5b09d2c2fa6d8d7ed519191f722d16b7e2ee2d5bd3cd991a93ae019fbcdde721950ea0bf58bbad1fc15c244072b1f9727cca2a8bcbd4f94540cc45371b4d59eead38492a31db42bb781f2ef172f2bc57a46bb296717eabc09561ea547434b52de1ff59b799d990342f9ad38b724eef016a31c45588dd1dc017d4cce626f8356c0bbb57bb60066c13497271d3722e78c14cd19d65f5fc6a15e537710b8960981e8dec4562b4ae5cdf16c72d016ba0c6e631368dfd2a430f09670ab4631fe2a4090ca8f200a01ed852c7b92029547b6f71d619503459a84bb4fc50e0913e076c7bd16dffa2ef2525bd28ac0d014d162aafd8115090689e41cc5ff60e63eed5f86623c020549fc0e0e6b68accf13115b143f9baa6d7f19f756f39ab6509cbe697d6fdd8e3d041cf27f4fade6a36729a96153f067a782d06a7e67a8f4208cabd23fd201d0c6b955b75823d49d4f8724ec838294058090c5bdd71c4b5bf0a532f31549ce635d73a27456454a496cd71c0fc75c7dec3738eac42624d6b5d08f7d2fcb0186bb6c73222d3e5bf61fa77726a082a904ec8875f423ee64d5ac441aebee7ecc241dd09cc624beddc444c1a7265e7f2017a7bca09bf7126fb5604fe30ddc655cce383c967822425c2696f0859954145dac92b75ce6a5c386c603689d97c5c56c053e7b222705791f1365ec172f8ec0a554ff4707f6f387b75ad1cbcfc364be99470c04b4e2dd0c1821bcda572b0b2a083f78fbe77f4fd6bb6651545a5881ef20eb0e670b9efc1a894e8a1a172c33e0d54b6856f62b528fe37d8df95e6c870c04dc1eb0cac1e88149c6d8e0172a1ffd02a9a23d83ab1270fe5550bb23c035d70b649ae2e14b988ade5e0bda22be3d5c07d2eae3338bebde3a19ca1808a4d46f01f24077c9c2d38d4336aac07728282d426a1d26e1bd39d6494412a6269fdb57eeaf8212801c998f71f5a5f137259ab5594f7282ed3e951d8dcbf0ccd7c965c5d35deb0bcfc1c0c8ae6052be5726a7d482cd73dd31986ea22bfe7e387b22b91091ea52f50da75bf0e43d5629f72c7ad6d4b75f82bcf53734fda398abb34c7b8292d1e82dc4ccac04da496722772f336ac002e63990b2d4cd73aaaf0bdcd8b54e541fc18fa8c9a955f7eefe4807285f00683a2d4803c5e29b449e97376586db105aa7e2ae62f125c620c5d820972c450ed262392907817251d9e2bad9875775e8e07a8f0df55b966165e2e03725158917354fc2cd9a1184a2a91f5dcd13a255f7b623ac3d7a85b9c145f7c8e79722c450baa8e277401efcefc21e6fc5d0712b21139a5821924ef1ace15683aa2324dc256faa945eda5a7943363ae39f034c2f58e9ae0b2e8242a915cff7ab6b000a74b1d4f132c765c6b800d6058193ed7dd51ddbb913133829f50a774c811dc3648e67f409c90bf7fc11fdda7662186ea409296758ab2bbe0c77d4c6dfe2de6728cb021425c6d148c320420744c0d7aaa16682f4215f2f6ecf588402c91c5fb72ba210f9953db617b08a5c9ab26f4dae1e14bf9ce03913fca6821e9a08028a87258d8b9c5cfe572bbbf66ac8499c655390f68fdd13ca96a1b93b71070069707727d1dcc6632d8454729dd493d6b53a92b5bced56ffbdad17916a779d2a4d9760082425f85f734c3b06675bf7ec817816e8c5b4469d649820af63b002a71a67e721032992037a4bfa0c08721a40126e4a8752448b1fe2b8d6e1101507e6d81782c612440e1609c4d17ee0618881948241021cb61e9895aeb37195de612af1591496193a378952cce4698634852f2f232f2a768e9e586f3e9ab91869bc82dcda3728cd1859538fcd59ed4ace9fe732d6fdf0d305cbc3a4f2ff913d24b96f1f8d672ae9277654495b702401de360cae30aa65dc618c9695f3a25be4d956b44f1e972e8e2d25207f6ca0fe2660c5597c6a98df0939c9c7183773e176d65c0391b2a38eb7aac113fde4ee846971f4956c235ad18dcd72ed2c61cc6672e55441f50477225b2c0b3048d9a17af544ba4a43077c193583d787bdfaa1927d8e4cbf3ce542813a809b7371c2674e3687b7ff2bcce2167ddf9e9264bd5d1a1d8456c10e7817241c240eeae5f5a3d7cdb53de191b13712ad24a756ec5e23cf003850181a93a72ed088bbfe04bb8fdaeb89367a0bd687cdf63662fcef7b84599b3647ffcce883757343d27dde5148eff2c26d17e32641fd0504c6db0237ff1e057bf6bf9856372800988ae7b839e4d6d151b89872391e3e471c7f30dd1658af43067f6cf663e2c8c19968a325761de9b6d172bafdf8c85e665ecfd369225197f755c4eb03e9820a1687973c626b9fcf795415ad5a36fedd837386e8166ee567c75b0eac01d2172274a70f7b58015122b87a07cf9cd00eec0f2c7280b7e4e21831d8ae740478b3a87a95b384ca6dc2c68ae3132d0f77ae0663d394b6bf655fa235eb42016f06565394101fd309f0ac9249e0c47f2611867353c78979646a08dfac16b0aa3a5c9655837bc540af419e7dd44882aa9c4440bea29ec1e8b56a14c99c4f3a2dd30e4222bfb0c77d111947b3902245a32bd477b3cabaf2ee0b9b487fe454f9868a628240a2485ebebd6484c9a4e3ad42426da22cda827f05e798ff550eb2ec43693a172b5e13f4ebeb50a666023670760ab5935c75d27a5e24ef13170a93589a9a16d7216e8ca3cdc0eea33036444973cd5ef311c19e2a1ecf2c93ba3d0202d69d0fe720939c1a7defcad3c0a22d4e71e8c93967cc961ba98dc4255d731307eecc258727368af3859742217a85e5c2278199e8f9b083f274a5fa0448371147b8060212d84c8cbb21a2c63bd61f25d9230935f19243ac2b5ed146e0ac30e614a8400e20de2069b492bdecd8251aa13ac671a4d4d08aa8183224e1edaee3fc92169363872eeefca0188457deabd6605228c286f9fd55e507f8d60b51ce9f4e631277f8e62b3a68449710c6e943e9d9b3c264ae1893daaf6fdcba6bce2166b5a31393ee8720084ad39653f79ff205fe7b718885d6d015aa36212c2eb82bf5d11a29b547d723fd081b541a1a85eb74abbb2f126bcc2214c501aa7a2eb3abeff761da7933f3c4a835bebcd4ef898f31f4487456ac6d17429b0300d253bf9abfb41732c1f9d1ece8fff5b2fa39d93e11c6fc8d8610bb1bafe06f70d81abd1fb1b247a021e4a19e8b60e6bbed620a8c0aac57d8fc707a57e478a44d471a69ed94f4aad83a9af721d6a5b6c2dc3a3059e24dd9a836332b6fd5873701700c7e26b67e04bb6a826729958ed864283ab5f2c35a721f57fc4183c071e6e7bc58262bf9e5e80b979194dd15d869c2786bd6c744f1561d99ef80a9924581af3f88815025fbbe808fdfb726ffa84f0af681a60fdc1999852f0974030b8bad96e37bad7ae3604efd217f33622f794b19feec88938be2c8e2939cc53e7941a28782d60fb09d62ddf2668db7277739ff67f97430d64e200a03b5d81b097a12198c3e403afc6a112bb9a462d7214ef74f93a45b00d75db3a5d2e2ff98fd360e55acfbab3eb1370ad3bf4995e00c26a8a46b32aa50fdb7111eacb7635000720577fa3732805621fefb121e06c72422747cda058fef0f5fb5f6d173777799e0996864515f5197fbcdc30118e9150fc356cc42b10dff43accb2730a5956b4d69a7f4d6dbb44cc969cd1a381078e72350523e51516d627209d11871c3644c002f93f5ac34406a2c3f9e7de1b17e272f83a531849c0a05176c169143b03418c29bb7ec9775cfee76032b864c06b8301aeb2d54310c38ee391f72965d4445d48ae592a155307b7e557ce1c29bf719a7292cae967f8600934ad67d406494af4f0a6aa4c0c5f2f73984b44d817c12bbb72f3e679a4c0d6b2c7c0c1edfc37e94e26b6989add975cd85965133dd59ec41272210948c24fe5b2a9dd618f043286c7af764248eb76e520d543236edac7318c72ef5b1fc0a84463c47f90554b8c44a3b1b1643cf992172136e96c3bb5529cee7217f673de8ede51fe00f662d1eb316415e18df7f0d4eacd34eb458f283e170f7235215c7a5746eee2b0cb50bd842cfc54224f5d47dabec11028533e46d2e2eb729db1f0c5cfb31c36f682e4cdd0e3db366a0ce16474aa11839f02d7d23e41936534cdf8cc4a31b90b2f8275ed90aee412d8f8588633b6f7dcabede482bd112572006914c08f5be424d9099fcba9a727bd2618317150d2676ea3e28f93a2864772896951ce74b2c2b27859afbd75e440277efa0e560bafe6f7624c398b15d2bf41f782337cf8a99bcbfb7b5b315f36017f6142cf5af141994182c6c1fec00e674f0724871c626fb57eb892ede9018012e706af11c688a65a99c4bed206392d9a72297b95c857e5e6db67bd128f63ff57700a0016c91b8b154d1637149edd3256434fa51828218b336fa5d1e21b86b336c747d316fa0b8f97856847f42e90ab117264f1974812d9e890347d4a97a3e299ffc432f0236b1e92f5c9e3e4a6bd081e0417fae1ee371be39443576e7f059a2935d411fabb87d793fa335e5d0c3331a472b57e3d909536dda6f7af022e07b40eb78f5361885c513d0f6c3788dea6fe36726e43671bac1fa150c4b643815e1cb16a6f7baf4a2fcf4e2c00357869a3425715b9f036c6046564afccac74ea976d53c43cb9a53032f614a97dda05262cdd22727bea01a29e6dc170cdd1df67b478045c89c12f4b1207c8fd542984ccc0731372e0ad8838ebe31f8e1f0217d16787e7d854ab75432f78956226337f95706be37223efe09bf2d4ecfe447f8381b9981f0305fc26356f983d9f7170c07f9edfc6728c9863a1392e461084f1d4d1d3fd574212abb5781e3c88ae652f45d3e68a3272e6759496aa41e07d632a0c2f0f4a3bf04a4ea1ff6588988a572b7257adea1d725495d3b7f2fbe43be3d012e8ece8d09927ff9f0d29ac2f9a1ab2856d785883728bea1e5a95bb23d87cd9a5dc0956d10e9902c08c66ad352058b2e02335475e08197569d1ab6f778904fbf2b82b46cd2933b6652753925e3c003c91a38561704d6421fbaf367a6b7fd0bb53aec8e3f4bbb24e915523d3f72d30267b8fd3047c72636aefb362b551f13cc167352167b3efc42e13bcb00ba79bb36ec87ad3c9cc727a05653240da2533e9ac87584dc78c888ea02796d4196f136997182eaa34fc72fdbf0197f17b9ad84b7862ddfb740d09a5784065c569d9bfef30a514119639032b7b52e3f56294e792ee1c8b7c830c7f3a34bb4df7ffcfac0cee015fae5d8b728bfff6c2adce06a80cede577898be09c5f1088a676b81fd5bd2e4deef9b4be677941a53760520483f733fd742dedd2fbbd41006db37199b172ecce4d3982477218843a3180d913db9ee6f529a0c5aa6acdc47429a0da687961a4ac67f8361a72ceca98f0d1fb52b1b915c55072fa60f4da356da44aa4555421ab92e62814ac7221da05afd1c6aee8973e6518a13d2aad0b456fae133d047a5a456d0f4f90b74074ebb1b30ceeafd33b4e76bbccaca2f291e68a2418e56b0578ce8eeddfc1f931cced21920e189c13424670f4386d4b23baf23a9716c44d060fbab08513458272b1a0e97f3b8107e38b98134b89109d34434f9a153b1a5fd005e1333d270329727b74cce777d1cc37d619cf9d84f3c1a736ba31c248737ef94c5f594c9e206a344ff9b9a570af27018e03bd80d73b0a7de3209914dbc6fbaa5ee13e1eb74c1d27adbc38c014e50c7e61bc540a26bc344c6ec516fbed94f51807946c75f09bc47218e57f58765bcd0d735ab660ef02213c807626dd17dbcbcbf1e7211bf1eca472d459b6f18a284c54aaa17fe90c5b53e5175e06424c7d73d58ef7733cae2b967207fc13e239fd3d5aad7ed4b7411a5b091b8159cd186b5a5c7b415ef11422227221aff4c8323834cb5d91a45a7441e0624b5a2c8ec33c99ea4703a220320f1072f0a4a3220f2813b296b1a483b233084377f9dac5a925d8619286f5466cefad7205da3b3d188d3960f8e2dafa944d6b2a05c689f5d59850f7625e041da4c40161c41b98e0195edb11274b381195abcbbfe09a456b315d60257a8a6d54125dd172f4b952aee92fb9bc0ff948d5a33232fec30457f605eca2495bb5a28238678e722bb4e92e7273dbec2072661e917a1a38d75e96dfe6f71e7ac6d79c7a7f6bd0729b19a1a003b7d216db812641b47160db7a98bcba8ea9cafc58252ef2518995727b2ff0c481812fe389c4eef03f96d3be836e03fd6fa7b93da4dde3489b966a4cd50d8d71e38b01d506a206e79206eaa679e13f2133f1114a2cdf196b61632e0111f0098f5ba0b80811ac3fa5ca45b8426db950e591a8c807c4eacfeed448427279593441a8279d7ae5f856f0770edf3613d6acef8d11605365156b69b72e99726a34af0058c41534c511aed55b9fdd882d8c5ccea1d05bab48677f56f6ba8f722bedf54eb98fc4b7a5e874e49a99722d4d21fc00078c91776496b42f0736f0726ffa84327fe5b2d57638e5c484e7b5673cb026905bcd9fab12820090449e7c487f38d485e66a92522e91e58ce1b4d6c53a72d47b805e669cf7d16abcf2bc66724e99860f70178cf5099138f9a23cd61f8c250fceeaf8cd8fbf1e7f9ab619f3727c19949551976154fbff2c7e5e27ec20e2a0d29b1fc7580d00f34fb01b0ee372dd13e031849d412ab6e38edd0b3f1b5c36579040b4330ce76930838857e1ed72c9def0de5811b568b186e52ccd21a04d7e9f4db109486d09fe88dfbd8ad2953a75b602e6eb7f1e575ab74f898e655f9519d90aa07332896bb31a01b3ce1bba00398b59abe9d5b0aaaa741084b2c0106e6ac20fffdc8b429c7b8e799adcea7172a702d4a8342911d59a7658dc5d88d014c44031db2413fa6f3119c7db50f2af72d978fbd432ddaf4dd6b616c17b442c0222ba3f9841156e31e8a80b770e8888721251b0a3ed076234f3f8970416779aafaba77cecfc3bcd7035390c7a7d596d725a977eac705cfdad26b7968e97f97a49af1c824db4183b80eb811ce86279b872c205455c63ed8f50b4c1ec391f7c14082e9b87f26fb87fc5c6a4040da425a872ed2e218287bd4c4dfc03f6e18fc2173f20ab55e3887bff877cac207b08e90272466889f62deaaab1b7b73ae2c85f39fd4997d5ef12d771b7a46096d5e2245172b1f1d428a12b3d13835fb81118fa90051dbe50d12882ade963521a0d7484d8275baf92ba1b0d2a30491e239b43ded4bd1c0fdada0abde95994e9dfa0bead540d9e0ea70b60aa0ba208dd8fd4ab22cfd008363d2af8fcaa8430c7b8676b44cf00211f6184e088a019d48024ec185fd50247a08c6b41e01c255031458262630b6e95d0fdbb7e866b7a7451c478a2294545191c953b1ac5fc4acca33922fc665872a245a0ebcb45ada75a1e90581882995ad4e5281d6c095049b6c432c844fced72c695e43e6dd9fe2f3809aa93f3751f1c07bb229fce9e8d3f20d3b0002ab061720d26d709b111e225a8c2fcb21155c7378783f34845ff5d3c6c453f8fb35a3972b7c2ebdd5be9bf116db72df3b0fca7243656ec5ab3ca9cf68e7249362b72d9729a3eeebe6e08cfebc6a5e0188f0619e2e80f6e78018ccb20032832b161b0647222dafe92d127a2ba620e6399353cdefbce21464f2fdf517e5bf30964cb5b26726936b914cbf52682f3ca85bdd2579bc5a01c7c1624045b937a51fdc2214f8c7246f6fa94e1be9196fd6ba861b57a36ca67d50ec5af7d4fe0c4c5cd1fcf46ba2b3d85546785ea95f64984c75c671f1ffd8653b8d98daa68410e74cd546fb086639b0114ce3a8d882b12c3067fedcee9a5a101a703db0d008d4b8f01d91881c972a33a81aff44a9b0b1039c40a397e1705f213b03289cabcac697e5c67ee750b5f56d0266aee579214c98d3ab66decc19301fe3fc26c52e4c5293287d9d6114272b39142b732b05641a2a19172441b748d561daf6fc8c23586506937ed3860a95e1ef19fc634ca74e28b60c4c8de38c837f38ed2c42d14ee319a4147bfdcc9bd6f211e3ac8279763883ac5786c1f5428bec0b7c4b49aa9ae3bbe6e500a5875663c2c69e4072f9158b733203368a33a1f988cb1310bc0feba2bf3b9daecd7af3672cc8a37da448da33172d8b518ceee3579ac399eb0997f911d9157d8666634905a08f1d25fec59d174be0b5734a3822b86cba815f4cc7d32c344f16dd8c3556833e6fb71d191c6d419ccb60b2db5725b05be2033b6776fda51b8ae76d1104653720806d35a1bfe5264730cbac7b1285e17062b0837dca4ff95d6e05fb56b7cd672b8b7dfff26b8bd4da9b7a00b1b91787286095de9467dba3c535c5b5ac68fce72c9ebf1044410b737dc84216c9ca23a3dad2da0ba8bd7d5c7a21dcf1507aacf7249d606ac39aa6829e55adc12426b271dfd9572765b058ca664f9671430d5d151b099c661f9e736d488473c18fae3c7979498cdea00e8d5d44f83846b57f8fb0ddd5dffe1cd896c44b07baaea2540cbede0660476acafcf10e4a7260b9990a1723f80a495f6c1662671ac97731e10655cf3ac70845ce0c3f07f20d40fbc9eb872249ab91d32caaf9bcbe9052d2b2e4b7ec194d6102bf6c65e98e2ba170032a6728319f1256e4b17ce89c07ce2a881e0476340175b3dcc5daf7344904ac0d9e572fcf5060279d684f7222c523d29f22051467c34b8b96e53cd2447a1dc98ac9a38af1e763669132d16f20bb15ccbb0b90436c3f09503676c6412255d88fa2c385ef12407d284a78543382d360cdb11d1f323f50b9d782eaee10523cc9867a47272068edffc5bacf3ff97fb4615aaa6def01515d1c55ea7c41c03df30ebd0ff07480a56ef4b7da4ad381ec010271db98e39e76cdf59000744eb5842d145e1d9dc0d8e27cb41a27172ce29e0a977a4f8d4f2445d0155be4612e5c5c8a4cebac83a728fb3c8128083cf6c7053693a0873ef4ef8be163cc91b7e6fd68effd5b973c95a8a2ca349734073956a0f7664afa20947340417b8c84811c7a2494d4749354d72708bb93bb16dca260bdac7e6484121d176377322b27e229833557ab1e4fe1c3df4518257d8b33e8b442ae1035d2c4be27307354c5c359d0c6e98ce3a05376f7289c59fd244a84bcd3770d7ea79d0fc4be1a14f5d9fb35eb75f776b29cfec7a72e0704c32c95b5feb5b29c325e0511b01598cec8cc673f48f635c26ee4cb7094f44dba3d347f057df51500ed4d51b7557c0d7e9faddcfb4e8f022f2895e0fe4664ceabfc6d059a62df6e39893ed9fa60602cfb1a5a8dc1dfa16297824d95e4c720aebece62932e4162606dba85f09bf2fb87877ff6c6f6f6234ccaf5d71925d44a0c99b564315c7c898decdb894954e4fd582149b08732645bb2a74f8cce34b1197dc54e93e14356a20aec19a8a4ce0d7bc50bc13bf63e4e88f1fd774015942725c748737ee87d767c4390a98917f68427fb7bc4c7cc30154351093a1c121630f0f4c2e044739b4aa8f98f73d747eadd1d82569ef6c33ce2164fee352d403e272914285393e680bc183de3ba89a84be93bfe9aab615f01f72578f265c0cc970446ead4b26896047788d425d0d9a02efc5df7c13ce9bf9229b896eb5286bd05572bdda0d6e3b8dd99856db0862ea23774c6d5c97e75cf2dcd47c04a29bd407be20320e3896b3bea86323f737e067bf75e7ad5c8d0ac7c7369e5749079f8db28c724c7b74cefa1cfd9c5b7bf3cbd6db2f2ddaf20887536b5e0f4de8029fbac0d53a8e7c22c0026047d2d68123e02d12505120252df8ba1c645be26b82bbc1fd4c724a9fdd67194d75cafa4294e2bfc3dd342473b813b5af2f255d48573856b2f8724aaebdf18bf4ad455f7ff06864ad7ffad1adec545050ced97e4ddea148186c727f34c14477ab417dbdd4e166c7614295d435c30bd9a6b8ffbec9fe3ca878e972f2ff86226280555da15ffe35b611a77c309ba49dfe72ae072a3d03dfb31469728bd1e4463a63ee1a288378d22e545d0128766ed1fb4b6a07529b83b57a9a4c725a1d028dbeb7258cc9b1e1c2cb6489a3babed1790cf3c49e57a4f67b6b8d46724d70bdc40ff3b83dfe9159e4a316004830f5a54f55859e649af2f21040747272cb1a53878bea4922f538867041533d5f2a5d986cb18fbfc3a0f3e6a399883f729cbf999045c2f81581db49346e8eb02120fad7edce819013b3ea871e09150072777d17b1b8404cdbb83fc18f0a74a73889de7257ad6c0338b59f6045c90d7a72bb492498c515a6cb57f58282ca6fb3086b1cb334b4d31f755b9915cba7e8367237db7bd24c480981e1061fede5c14962cd1f9627b65e0a9ca5a88fcc8bc5f372c9df2e8fe941c04454e4cd4ddb2f16a8a4487d44b6301b014d4fb27bba59345ac0f73850753e8fe3f8279e639beced6b20da6632944d7010ee81adc959d70a726072dffc0234a9d0dcc7c63b21fc18698a305bee8241a5d4b4cf3175e725fa4af58672b20ad404854e5e6ead4333ab7d11d2305e568a9283e3c03314d9f82e727ab37dd30e4c38ef341e3da873ca5edba77063e0eb27ab566c33c0cd6916e072e0306b3de2c3b132ed57bd6b5458f47e4cbf6dca89010c6bacbe1b80b8a687720c01a7ffafe865fbb98f26b5b54468cdee0da4bd81b14441b57b92558f247d36a22e80f7610a0dcf70ea8e97264ffc96d89038714f2bc4b78631ff1da2c7285dcb36afea9e27f210df284f25fb9ae63dfa40b68b1fdcf8f47d0be4e10c4521724ec2dbedba5e020601d294414985202034cf2338e2118d339f4442af7e8ebe14901afadaaebb41494c0cddc4ed900de7400f63b0537664fe6196971985fa3847f4767c6c385aea26a586e683772494fc18dfff88773ac73ddf837f09f53b6a72dad1ba2ae8f7955d5bf40b2665421d441fe9826e8996d6675c23700dbd657172a3ac1d562dfda37b643618c5f7c2e019c701fd1eeb541635fda8def942576e724f525223e337e00f368072285223e423858c22c76d207bb5be2abf604da67d660af198d67620fcdb69e1927baa5fdd3a80a4cef00490d1615d150ad49b5052725d6d992b29f667c653dc06927e6f300baffd87d58bcad5b378dbc94a12b1487206271db3f4be94006a99266deba762b7e5c6b163d16e89f50bdaeaa70395941b2d9dfc393ce84147e25e712cbc6b70d72c47092013540026c3a46db51d16c472ac138349b64860ba6b0ee8eafa840f5c591c47dd336fcfbefd3eeddb004569725fa3b632f3e2558d036135768ef00a10ac082e49b34e2243f1672a02255d51728a4427316e082837ad4b7337021ca882f272e10ef65d846afecffc9b36b3c772e558ff385ece54677f2ba7f038d7f41a95015d26694457dae8d780016dcbe772c1d3865179fa0bd1ff05b874a40a156a258ab8fa09730c20ec25dc0790addd22df514fb136e2b61e091390d275fccddd49dd9250b7f8f200d920390a0dc75a02679f324a69dd282bcc9da4cfc7eb677ad4d698cdff2406f872fb32583b3d6709e74e2758917597adcb27ff78405a554031a08937e112e772861e18ab7d715d0dda6c935a9f57201d927eef25b937775adb0278fc9c167ca88defdea8586dc572deee68f8cfdfff7ea603899bc33edc251b28253def6e5636d61897184ed7965970629c6eb22156e4527e35576436d8d0a5b843adc0da640657c44629a88b1872c20a61f9784e44022c54aebdee347bf134a8bcfa6d1ee50c6f608b6200436a722c4a0fd6e3851f311b89396293015d9bd59d2b155abdb25eb376fa5332e52f3418e90ae6fa8ded52f6bcd36ef0fe1ad401c01ea27b517984f7a2f04f6f718022f4c580b4d5e79e9b885d722630591fc5a2d0fde0e71a661eb66ee87614aa7419446936784bfe100b1a28d94c75dda9349382363d9a616b0e96f23dd9858b4d72961e0f46182eae107a9924ff1e5d21f4e1f5657af032d77cf48b1d1306096c4dab2e39eebc2c921e6c33aea247bac316f5d284527d5da0a9aa9fd17ee842ea62806f61a966b2f1cd827c7863ceb9df03a9c3eb95dcf0bc7b9108cd45b2475172812a8a8eb7b7f814fed60caf65a5f1e566737e3242c9d92265cefc70c5475858c2f4f328efdafd2befc7b5d6ff67eba43a88b755c61b2adf0e862177e01d5972816b01b6bb7d8cb0ea32df35ec4d367b7a054b10e5d61644067f1e1d29fe2229f2423fcaf803a21ae93e07c63ee011a115719dbbd0813dfee42239dbaafe407290985e99c222fb69a5e922b7e4ce760b924b950779551622eaffd4a1a58ea07293208e306a4bdd6e39907edb5bf85d289ffd47da6c4a4e8ed2dcea754892572d82e0d819384671336322655820af26c7e4acfcf7350f94e1b7f4b50d5c04177268507dd696cf08f3d8615dec3243415ca9cbb75b682ca28c7dafbb86bc211d1ae5f2f39e340187d38da2dc79b88a84169dcfe6dcab7e4d0b0cbf55c0d4fc9c5c152773023c7f6af136c8957af267ccbb9769bc72eb0275bcc0c7239af73c8d7283f800f1d3199428b3e7a2596fee67edbbd62b9989a5de90577e711c32e46f3c6db64a9b15d1c68cff4bfcd3e8e46c3271cbbbd2e8bdeef5543ddab8523bd21f74f038fc515959e70f4ef07ba8a7d8de332faa2977fd09fe40d70ec54c9e3e727ca0eaaf5a1d4ec5da16019473f1c9acedaed57bccc64ade976298a3f9893c38e542f2ad4af88336d721567263b955259cc762df9763ddabc8d1d294d537f95f1bc8dbfcef005003824ac3ed38cd0d507ec125c1251cb78db184604d4378aa72a0520b6f8e426fa2b4556e0ddde525a2540b2fef322621fc30f3dd0e879a42726228e5bb3c6f673b028f7ba24a80993a85414cafef7f04e33e0ba91f775adb7234983030ef893a2ac795c6df68e25e86aa78b5a8f2a29080943a5c6300769e727458d859753fe0f6b549ea2b2fdeb9176970c8f8a9db04a3d14265788a760e7284f8554bb0cf4ae5586f63550f5f5f2d12f7d77a5a4beeeb407cc13775eb96728568be4b5739609ed73a22e81a7a32894050df3365e287ecb67a65d5bbfb0a2d8dceabac3c3a83978935f4208cb19c912293441ccfe207eb0eadeb07d9602657c985ce1a308869574d3a63eb2275e7a36e80dd5088ec985e5ae78f718532e7380be071ee8659f18a67677426fcb40baee8775161a4f7f8b1c95988fd21296d72fc5165fb03fca3d4b108fe25b6983b55e5f6f55c35cd212cfdcb683240e4c572c588e655e9b051472475c4c9ab8984b66aac19cc3a5c63726abf308d8ee6557213186ebcb5226741f336cd21338e30ff420333d364205cd526cea535a2589472b40f78dd5f206a6118e2ed547a292de1b575007c5971c09c0413901504d8f957868ab1a7385fc404b9a20fa18400db6823bdeed1178395df4d82acb2cd554d728ce40040de3c2ed935ea5243ca368a7308d1adb80e9720c34b091f572d7fdc72a17318dc10f042f3a32a746aa7933ea5ef4afb17098906994ce7c79279b10c0c385e8aafe92f4d9c8ae0e9913ddaba461211a8c7ba4ec5f300fa7c4d24e77822bf7efc4628de9e9723da4e83e99daa1894b2a866d2d87113a4f473ecf3f704638fe18ff2f3e9f289028eaec49ad3993fe2b4a5571010267e5957a64c9bbc2472ea27f0a51143f9e2ee9e182692733fde04d40c7d76ff7bcbb11995ec1e56415e163cfe22bf8b3e088adc20dc2f4437b14f65c8ee3eec23c4d3d4be40c468a548ffb07b1e61afec833afba0c5f74830380fbc3b49892ebc452bbb72d8b95dca0cfecd4e664e345ba7bf5ee1a6434c659dde780e1d2fd5e47cc488efcd0370c92b7ef09612f29ba64cbd6aafad4e78e08c04dca62e15c826d185b808abe8ba33250b6102564086c0466f5aa8752ab8046916dc17e2eda17d1aceb62bcf7153cd726ee2e887ba294a0d23b6c7b7c75695eb959884581a96665856d8023e8ea77034d689787aabf235e31e4139c7b910e024b45ff3618d4702316b7230157f59bd10af17416fc17cbdaa4fec9e616f128f342bbab8e048f4db2eac52366ef6abfb727654a7f0a5280f66cd1a3ffd1a58c5711d4491b03f54bcf85b609a171a8e127272eebaa9f1aee32e8ba651d96e5d56232d3ce7283bc612cc514c0c323f3dd072bd29c6ecb1bb3dad8cfb2eb737bc1a71c4dfea3b0c7b80d939089381521ce37284f22197fd8888b3644dcc7860ca7056b8487894d2857e5ebd026ee0cfd12b72b7bf595edc4aab42c706e20ad9160008b64b5e7feee6b6d341388e2cdcd67572e85cf77d5a91b1a0166eae4659fdbb0e8e3e1d98f70c092136e9e2ec1148c717464db20c24299ca684cf38b5cd94d3bb446bacdee2d86ec6da08d9ebd2d8ab723096bb4a71bf68fe02c9cd1c6c80cb4ff4b8ed88821d867a8e66ffb4a63ff67251c97d344c86e8b3b7676e54fc23bcf0e1f3f5baee9906e6fc60092eb05c79239a179b6453d8346d9f13ebd07c1c5962e3a1c7603d93ec549d4f232f16a50572986d2f540d324c4591e6de4b367a10a39483f7a3e6d839fae9905f0beacd687295b6fe76976ec740b7d4682204c1e0f09ee67ecb842afc942ee5e274c21999721193e87edcf27240fba548f58fbb47266dbb20cb367ac34bccbb400073076c6ba8bbd9bac57c9f6c1fb71553187592ee28eab11b8d80c691644137adef3d6f602a8f9bd3bcee1d9a881b3ccf356d78f443c6e1a7915eb95654f0fa3f40ada07226c5c92336bb376be65298255ec51b4e4e003be8992350176e2bbd7928f95072bf1ff0155777c0b4fdbebe3a631a3677d4702362b2d2e5ab948296c1c51b405a6d12e894c7ac8edce756eb7b95dc10201b2ad0b6da738f6b1d69eb1e3f534a72c97d67819d9506fcbeb9d64ad0d41c89b4e08e06d5432d1eea263621ba158d72892bc76366cd6e3ca75d91e52eec9b08aa7f86d6faeaba7ee9c3842679064d72e571cf85ea77cc0904bcfa9a24eb7c25a27c4e67596c556071b58bbcdca4083cb0c9e808db2767d17121e66d7a0296762f6c877aa8e364a14b4c7cbd64927272e016b4e5154827fb825cf21bde2c98b9395f23e2ccb0c324a8554f4de7176972630396bee7c906dcceb64f4e1cae0a1886089e37fcdc7432a59ee1168637e3399ef06b305a38386b1513af1b8404e79a57f8e36b533a5c39b413a6342833c672dfbd27fa3dc9b7988a70e6d48c69132ff8c5b109294fc63075c2a4e5c7dee3720dcc9ef248cbbdf3c4e847a189b7f1bead440d251d37eeb5e0d493fa3e53aa7286fb4ff526fcc682251492f1e3d744529fe753142c1526e05dcd7d292bc3ff361ab0c3b8c85287effb07938f58f57cfce4d3968251bb9a1e638b418a3b670f72855c857d4cae49c89d19e57f6092417b3ab27589500eec6c9f48df5a382f927258299869bb37867d76e6e2e8787ac37b5623e0e26f21c7130659022342d71e052fe9ef7131e4bc9bfa7216e0c235fe466b409bc7d5fc121530db22454ccea3729cc20d582fa1a42ecb7d19712701223af663707457ecc4eb9eacbca614945372077397a440a788723599379dc77cab0bba829201b283f60d840bd343232e4772aa6cff425da5b8e2e155a1cfbcaf04b7a0caf731533febb3020f09f48f6c61724b97474db9048cbc5428eec420659b30587c92dcee2934e4949316ddd001314bba763605fccf515ff88d5c1d31c580323e563b2f0fc932e98688ad6357061572c7bf7a593e8e3085212f59d80371ebc658d4da053d93ec889d490d5ef7113772be527ba1f70979f00f280462ee06da0c290f06789945bb6bc2f900b695a5fd7250c6602b7cf81f6a29ce49d10e1e15fe055ed496d538626a3e00bfc9fdda6a2ae68323ab781f397056192cbdfd516911d326753c8b28d9c9c5931f26cdf7aa723fac60bdc36dadf6ead3328a7914ec01f40494745eb3206967c494e90fb1a572fd5edeb051499d2338964a193f3b88f78357cb00330252acab9919cb3b1b6e7212cf84a668f0c9dcfe923efd128b9eba12a5c2b92a5dbe1c107845156ec48172035538ff36299abea17fb11e2f108e496bc12dd5788228dcd9e4f4459d2d0772c478b4a98e48951a8bb43c0fe129fe79925e8ddeb05e276a74a43bd3e8fce457ff26c979062a7604622396b4a9d1cc1b1def7baa59c907b4eaa5b1d60c189b7208ff270be32116e26b5c54917b86544607ee044001d2d559574c2fd511b8ba72586fdafae3209b17624da2f59a879bdc93e49aaf6a01829a9ea61e54e0c90772a9c44edccbb33103ce52d9b8ae810ba636bf0162fc1711c1a50e123b0536cf7212530bdeabff627603d208c2e55b5c3d2b70fcf4efc470e4adb9437aa1a0f8725428f838cc9eb3f1f1c46e450f14d11d46a44081e2a83784fbde374ba68ba972d90802216ae24740f84a57c9440d9a1a2b921be8c5e64604e9ffeec61df7e672725a6ad82efffa4dd8123ed7ab2b722bec5bccbd50fc12d12dc9485e82aa0e24bd0b6d1e6f090c8de67874bc3fb0284bb567408178b88c99e1992fb7830f801b327dc461993497981f7679fe01199d507e8b00015a4e76c54ef076e64997817252c59c02def0ecb226f43533f04c46d92b0548872b6233988169f8aa06b33f72bb79542c054a458a7bbf2742592de30366b75c8dc49db1f328fc29a805d3086ab2345c64c2c3e31fc3c4f7b30bfbf4f74efc1b3e511bbf50f1ec7484f867a672e9812a8b34cdbfd29bdd30ba2e1d617b48e56b4d542d50d480cbe5327885f846cd1cbae07ffbe845917b7352bcb2adbedd6f6e07249ac0b708540471570c5c72779e592db89b292ebca574f1e8196fce51090c3796eecd03e601c263ad41323df9a191f5b81cb720111b5401ca70f69e632d0cb860d2df4f317cc1b940a14872812fd6d3d153a8836a32bf2e1ad6bd2707fe6de9b28d8fea8b12b9e79432db72c2390500c04683f8e3c5c910ed2920a811338540479c2e5e70fdf3d2ec0e0072164fa25a3eae993b335cc9a86e39bb6e77c3736b8ec5c6b54e84eb2e9ff30e723ab91a44ffc3f69fe77580bfa4837152cc696f9cd10c16cb878cc6bcfc218b72b5315df0703ddae96c67a0ea708e17a90ca98d5ff4cd396cb8c1edf0e6e606722dde11e9e156b92edfa91f3dfad55c9ae51e9d584a3762e42d1d1732794b5760e00cca5abfb9ed6a68623a9ce92cf7266b7036c51867bd02ac7f6608263bee72315f587c085f467ded9c20aa8b5e5e78ab329aba2a6bcb119291f5fbd62c4f7240b25a35af554207f2a089f45262b861c0eafe17b1a447347b5d708a1654fc72d85e98dae84938bf077b6fd62313a5b7a9eddba82b71c3fc5bf1d7fcb08edf7268233246c8bf84f17cdd52685d945b7bd5307d01d6db98856910b5692a9412727899325760781bc6fc831c268eb6010585cf3c90be0d8e14581eb44cd663dc21eb48593b00ed51f6abbaec0f86307ccd9cd6fe316d0004195b21a94ba4b3152fc1975681d70f74d417740a349e5a6c99db739122388cd206318ff484e3487672c740bcd485275054fe64f7e93cd6f38e24dd0f724909dee6ce6fbc3f0f57727248b9109951054c017ce5f68e8ff3a28c389f4d68ffc20a220cd9129fd47b9c06fb0427059f971376c7646112196cc54f1ee3c1159dcb2a269f041fc636cb72219678f1574e7185db3dec8279492a4f8415391979f218e92cf28f752ffaa03864226590db173a6da099e02ed43896822907fff731a4991e0d5019bd177029f77218bf7a8f32f3dbf37bd4709f92ab0964f21fcf29e88ec54ba5fb51aac90e9d14a5564cc29b90cbfaa90bb4f097cdaaf0ea61110a9a1a57286b36102aaaff82143582ce634c7eb10d4bf5a3b508fc03c34cd1abd875a03fa2f71ddd0b132930729f03da1a6636e9a0bf64bf13c26a466ebe15915384456f800c1d558f0410a9725b0d34406714b01d19c863411f7d6c57669c8b9a111d596f79523edca90f387233564a6b5dacd47157866bd13216724bbb1e5fefbd6d98c1d3e6f2a8036b4866fdb93f22ad1563b1d791f82fd10207037e219e1065dd13e99831b087533c4f72d52530198d86b0fb456e49a8526565505a0023e5e707a941da65862c65cc5b6d82f91d988fd52a6a249db89c8f94542a20e1cf1b017ae8509a0956e0d882c758642e176813fe003822c7ded4b4dab5b110069ec1e62e095b078d49c18a91e972d9609aec9754c262b2cc9d843265e14d948a5a3e627be96a0cc9f1f337291872a7c044d6dc268bfe3055903650e60e4f4e54a51639a35c547af6f93c74a52a720bfd5818d7772093d1e90b88a80396327f484fa78fc910d4667970b812cb0d0cdd208da233bc1ebf74b2215a39dd07d9a802f64070c4a062af432ab02f7695640920d5abbf1926c4dc81a052f5344d44ee125ebba7f6894e15b6e75923b85735b9f79213ce4e5e0c399556dbeeef2b0bb72483e48d2ba4a870b3a788ace9132e193ea9dfb5deed9a79882ade4bf56ae02b794c314cbe3bcb0ebfcc998b52d9725c5111a11ec61b5df35431090f685415f9622001ce8a4a77a8897c07349d21724518f7937f652b7c9c50ebbd9510aea589dcaeca0511b6fc1ccccd714d331d725469ccd9310d33c91da7e40ec0dce86cc45df8c317a18d7596d4cc879a44a82823b2d871a2168d98dd978c2b55008dfe124c6c3055588e5fca3a3e86d00c5e4b07511b172119dc0a1d568d1ad4dbedec2c415b47f15230a13af357e43e7f6172b61d0bc349c748e20e09999aafa35ce66662d1324b5626713c494da1b764b8723bde500734df2ad10271b3a9bed570392632838f1e20d5fcf2637412debea5416dd2486bf4f6fa60a76e1f7c2607290d2f7627e49190b28c86bef12014c76a7213524a661568947638b422c1cb662387443a58e8460737d0cb72ff60fb44ee727bb2853b6a1b6129ab6c523dc20bf8a4f75efeed32aec501c3fcd1de287449720d1032c82037499aadc4e07d022cea2aeb0359776d92c7d1335759f20ee9de722163b8812a1db7d93e33b68061be33090696edce14473b6f9743d68a33186a72154c530c8192dfc7094badbf79e3a698d327607f94c11d1cb307d68b6700c64124bead7c05e6b3c3e2a21d7c1a91c6efdeaecff86dce22b0610ce05f467bea0d7705f965394199c9a63850b6191ba99cc6535ff3a926dc110569691afdf2e309ff744913c6c40dc26803a0c70dc32b6d68b56fa20301abc99e4916674d9e33082b7328fb2e2a33b512f3943a5b6c155f1d6a89c3220ab41a729be97a3e457f723a8ddff874a974161f50da110e1bf131a48d03eca54bd3542b97e42b45f9c97221260dd1e86e9b93c9bf9bb5077edb754a78c2d13251dc9438ee07269bf09372e9725e05d0fa1bfa3e76a1c8a9b3c5a36bfd27273d0839c478910a943a3775721e943e7a57090a13c5d940dec2f1cf836fef12a3cc2c7620b0666e99426436723d42486ac018b53e4625d07d3f6ba01446d16a087f48b2082e5b8b05f139bf7298835149eef0804926aba1e9e82281c99daa4bd41f6a7c3fab18a614bb2f2c3bc1c0355eeff5e205cdb1663d53214b33d4b69af30d0df5390f5ded7e7870e172637e61fe230b2fe92dc4a8308a6b4afcae62af8bbbf8c00f62a133b2393d153eac1245fb93b7cd137ed14907f80df807449d648b7de8ad2ba2a5634b0a3f5e72114833f5b83d93ef520a2bb8e2f9c7be440d033b7606aadfc77fb6b32f678772b1350920ba562d3807ca689425c69c701086e54f0ef09fa1af742f1cb43d75721ac75f7c1532bac04e7ca943606ef7cfce4af7a6468b6c44309a646c63a69c72ba1dc1e18755eaccd93104d38672c1229c01e3c1cdf5ef84a02da1b43351317272739d42557c43d6b22d508af22fdc2b59b1821779c9913c832f23d03977bb724d2721b5349b26ec92f8966f18cb0640053f509be59a8cc1ba9ec0e969ab217208aec2a6cb5116d1a2b52f2986dd7361a51e5875f275690cde861844cc079072e1dc644a235d812a7e02c3fca45181059521a687c5175a43829ee8cf0ba5f172fc8d9805b9932037fd22eaccd38966301eead41d2de0b1326ac07439d7e2bc15ecbc6c727f1a262865369930d7d0ef12e69abdd03da89aa2ec7796b73be68a72ed7d52f43aeaf4ab1c846b024901d3a7de35729a82e3f4634543c13a81e60e728ca4bf8d08789cd29c12277b2c461299030e9edb9397697bb3523ee44d05f24c79a809208b92ba2f7a395104bd661ef1aa9da2a7aeb903eabbdebb7682358f11d291ae87f9b2e3a28e9006f92e0ed9c2b09157f91298de757383c30f02a622720e07c545e979e5e5cf176a2ba6f588e88d72ba13f822432e6a3b4e76312a375e954967141bd9d33c2bb5d4286d291ae894807f6383652a52295a8ac493064765bc4b60e5de7e1c723b50f6427ba257d34d3d5d857a30c6eba489ac9dc4b55572f518bab675750519dc6e28c23742739f3ea9149db5c8f6dcc88e410704fab8551b26f55f0430963d4c5175ac74398121040312270df0c520a55aaab5ba98b04ad68211fb08ff94a3063aedb59ef6798ffe54b7eab75116d3da2200986bcfbd1f3f508911c9585d507945a5594124fcc5eb0038f4504206fbc218a940ae0d6a72eb9789dc53607544660bcf535dc7708a9a73d8fccc383ea8a1fc5199ad5bb06198dedfce941d513788381270d1a30fb06e746563a7bb6e37401843da64b30620d36ee8382eaa95e098372b6cf169a27196f7df9185156a45789de9ef93b334724989b9a05f3bb4eecc9ed25e3f56beae46c17136abd812aa39c0512314e17872b24f0f0aaec74c3e070d4a178936d02e7b98385b40c09d1729f1dfe9591c776973fbf7394ea6b20b7dc3529cd6aa5e8e9c6ac91459b6a61709e0da55c459620d2bd3c7d7bf1c5e6d7093431e2dbc60b7ea42f183ce12328f57d942587483e77274b25b457606688c6bafd4bebce571fdde2844fa8cf8e85905fe2da30b54ad72d19de638df454b93431d3308f8c535bc38dfd0e3938762dca3be5fa1d644dd72788c4eb1981b2950590a636bbe3e7b17ec0362ec9fa224a3ae1af1877ccf0472e3a1e0c1dc47d4f6643d7edfdc1e3e731343937d27e486c94c569da08f582c4433a1d2da15fc5a598f344672bf52b61184b01334f723227d355911853995d2727f8c92486a8113dba88c7b0248bffee52ae16d4c60b54ba18edaa8f4dff3ca70dea7e2532700c07b3db0e595a5229018635cc463c8e7613e5b5e5e062245be72b58da0d045f311e476287f5884f20e458296c4c914eb8f0bb684e92f3754a5722924527d531dc66239cbcb7c55a2d6ff8df5e87d9f53e1ed341baae717c12a5f85b57c2d4a13cd32cedc894d331b01fc5136fef182a6f119b3cd4ca43c3adc019451f43c52e86b71b8d1b9e0e4dc068a5aef958813e81e5c7bd3f1e0b523ac72e1458c0eeb880ee24e58d0b7daa8abcdb4d98cf1a15a11ba75474de7400baa4cc457f5e4c3a5902600c2cfea05c9e1b06290f2a032c8f8b5e3162d45068d4202cd0670d26bf4b1c8cad534958d9329d2e09f62de4579aa5f3cc26bbe0b830d7268b66fbdb996aed2243860f6fc2ab6f98d8541e0c8a8e55da61c1dffd2f4523d7468bcd02e85d64f66911d5c0850d0f70e290a3488cd9d9d1f2383fad4f583723e2d01a4991ba7964c4130f37f82a5c7280e6de0c606821d4998e4d91c57717202615f540f83ccc4cc43e8529020a25863285ffd33ad0f637597c1ab0e4de2042d3c614d458dc4ca201acf0510ace167d14ecb3bc95a92ff7851a2ef42835372bef1bf6e12a51cab4cb222bbc9ef0c1f0777f441fffd073fabbbf2d25605d87259dea4a43d356b860bb14920af6d815c229d32e27799531ed9b9cdd0d0f832365ea2d874eba2cb4b5b8614c978c8dfa57177d78d68ca8ae745f0041c401bf772898b45fe32c9042aeeb3c4f64dbde05b3108ea8e2447b66e8f9920aa33657d561f0a2f924be240fd3ed7b0bb9578dc2fb3583f99e9ddd4024d8a6c0c86de9a2065ac3e616b626e87eaf5889adee9dd9d160dd41c4a56dda5b6dedadf65d35a15e830fca345fdc7374f825f047d6d5e0ac75bc36f808fbe7015e398443fc36a072a4a6fdcb21c89b04d1610f37ddddd9a5008a1da20157b2c16c8fa7af63f655c297cd24159cf78ccaded8ff78668ea345eb4071602513450dd6d71e1c62740720aca88c635c6a9e76f8689f0857c6215b213edfe860f1d1184f4b7fc5918ea7288f1da0c3a887b7e0d110271396cbc86048b03d5c5bdf0febc217a8edc0be6720d56fb893ad9aecd9ee046bcf2ee06d2b97ea012911f0e9fde85a8378a2ee272b7ca4432fcd0a7abd74288bad02cbbbf60f2e539f4d45ef34b56e74c1d8b16727cdbe5c9b6ac08e840fd0c1fe1a67d2c4a265a7c986376a7d24b8d482abcab72e6732f1a8db4481d991cc138077804ab4233c9b20123b8115184d862d82083723d9215341b44a2033035f282afbe6752a1e8b8ff0070a08f4443bcc75637806f24ebfe723047554ce907e573b51aee4ad78b8a05a1c7a36920f0a8a45a51fd7247b1a2080dee5c55f0cf72f6d603f3e22cc08a097c3dedef0679ea34d0730b72b696d2222a9212eb0937546a26d4ab058d8f705ff5b0f93c074e371cfa9e2b72a6a8a0ab839d981278723b1beda4500436455006d7a1822cb10324c3663878724afa1f399dab70b5c685b2b749b91a9c280d385363fcb1b4468177b9fd491f72cad35d5d6436ac10ada50327a85e019a95db12ae017fc5a2b822e899740258723a708c381852babefe58665286bc22f115a57d82787a6faa8482d8a692b4f3727f36dee9e971a6707f26499d9d01b12c6dc15f0b2f6e38c3ff2a6f3b7424b57214aada964e48cd9a239a41464437ece911f8f512de01cf3d9934e247a6541f3ff1caea15149ba27b790ba89263042c1bd83e3d1de4d2ee2fc6a40b22b38be072015ef238761c7d75a99cda24945dd2c8a356cea52f80eaa7777342f4745079724a4cfc636add3a32de8d04dc809593e85c067d9748048da6a4693e00bfd34d72aa5edde0035844be1c618bd146cda16656356e32654967d4f54a3a6253beac72359745d6d2655fbe6f737f0e62c1e76c52e40fa9390e3d008f741930a7582d369baf31e23253c5c46fc001697707a6daa0a7ad40cfac408b4a0bf1ea2eb5f372a7e43ce5fb8bf8cdc29e54fb0810da78db6357fdb327891eb0c1b3e2e119af6a26926c4b44ef81eb8148ff067785e2de5274178e852a243d6edc4f02d9d98830530600b26fe377a7c94d9f16cf42275ab21060d80656736ab23133312becd313efd14e0104c49e4a6248a2bd4decb784835295dc4bda2cbd6f9de611d3d7d8727fa445dc1b50d37626b841010b5bb9d3cdca58cdcb141b6d7679db3b78d28472dcb2b3d60c7162bc60430eedf4dcb2c9d493a5b3da9939c9f6e2ef9fe601e42a6214b128eacc742607f323b912c713a83262e2458127c38e3c5d237cd55bb8105020f90c25b937cf57d239b683c2970b330f4b91e91acd084989c4e761adc623420cd66f3c99668e61c725d689baca6091a825c6a8d1888188eff86b46c8a93bd262d096bb31258411647cf3dee378a9301f0a3c5392e7c17a2d410433a1827229a55cf93882211ebd85134c201cade9390f9ed7384aaf7cec56ac2a500b1d5b37487d4dcc29f0b6900c9c8ec2df83c351069f5c17ddc89738b243adce60731da2cb49be8844c66ae8681274d0b4f3e4c6a30cc3e9db9a2112f8161876d9e872932a646da692f43cbd5f07cc74843cb36841c3cfd20e19e5cedbc4498eade0302a575adaef8c4ac903de7b286cc6b4c3aece8f67ada96ec7ffeb2a1db8c3a34aa50315f7e1d53d30f9648926d58665d2b8af4a60959192da9967bcdb9546ec106747a8ea79517438edad5973e4c8cc67a08beb30d5e77401766e148a0c6138410c730b4c6c659ac72e6f503d5445fd8345043417ed10014ec46dd3f02390c7166cb39658aa6643ddf318b3711e2893db0c74206d8ac19263d2ab6eab6f571a4b947a6475d9fad862aed2664b2a03eedb5d2208cfddbf04ce420435e460469a20412d0d663c68ba479b7c7cd5d1f4e410833bf16c136d3964b0cff478d473c872a9b74561b9aa144b303657245d225662c2c68333c7998e720814a050d8c5db727abf40c9569866ccbc4ef87aa3bc162ecad1adf71838e6d20123116d85aa5272238a83d79f90f9144b560c7ea048fd9d8d2cb423db6116d541bb5edfddf697727be301206965422ce5dcaa2abba1765730877e8d2a012fb41ff33584cdb49f726ee06942ad9d9de43c5526802fd529f49948468a1f3b3d2166474b3408b8b672c61721bdf1b262ab383405099581fd1ffcdde3bca4cbc90b00e7af9d3c09756879e2fa8a26773e8980faed48fb77c2e72345af1d5703421190198b03e7ba8c727e78f47434590c7053491019ae7f328b924ab7b64591a9e4f23ea30f345be469ca2602fc11a3f17ff3f5f6e15cc50e9b0d080444d26998ecd64255fbd66335163a549bef77505d13b4fa668b21d31f5148ee42489f798630ae694443686e2a48286d80e7b48d278f4b54e1449f5e31e6d2f7071f1160c0776d9a8de5dfb9673496ac00aa9ccb5f45d867ca744918b2234691a84d29d72ffcba87699fbbb9a17205071695fa240d7fa73c81c66e5f748e6471631937a1957081662be895881a72fa3663a8b6585f6272d5ebc578f5d2794f8979acb9a08e194a6dc7e6d5b76620784b60d75aa304cb11ec087220c040b0eeb17df6feb0f7ac72d46d0caf8c5272e9e594db217853f298c934b5cf00a293ec3123f3b536f794f9f0ce513a497f72bb6f72a32ef769c2eba317f8b5a84ce1cca4b6376beb1186843049e4423cd1725cfd79fea5db537faf9f2eab8b87f9ddfc91abb09e130f90165085603fb6ad1e3302702d8543a100824dfe7dca42ea4c7d1132962914f4987e5c9c23c7509e40cdefb1a41802923f1094745828b12bad0208dd9b1f055ec528b03d14bf62f8720501de4ef0a1faad7d401ea9f6c252549da24f510c06162e55fa63987303aa7266fb4dabbe51fb9c38c089464bcbd12f26dadea55bf71bfee202e96078f09472e0bff25fab45940d01eda084d92ddc12a5842d1bc02c1d5373c079d9fe4e567254eb197fd7c741c03770771616d07cf3358908fef1b5b7db8b1a83ba135c7a7230bb49d99d49bcff4c63c8d685e563ab0f2dae60533b68b0331b33bffa5b8c1802bed40b4026ae78256d803a41f14bd9d7ef1ded9adb4d921cfdc8454e5e416c058448a9cf3ca27f0c0cc7a4eb37cfd8bdfe0e3eeef7a0aa0076d40d89b77f723f38f3d60e17aab07921d0e598c40ce31855e24af48d7597c8fbdb7243c4b4720cf21e6f38fdfc788eac5b9aa1b2896aeae9ee18acf568b8b495755efeec5372d58ad0a19dfb4b781658f2f331e95b486eacf88ccbdfbe3d37a3bc3232f2f5725b65734b6f15a7d34733377ecbef4097cf37ff98150ede69100616df2f387b503708b4bcee97cd8fb65899b73c2f45e8e8498f3140d1e2002b2373dc59ba3172ce313e331eebdba0776d32eb38f7acb20776d3f82eda91b0d7e4495ab4d1a9723e8eb351a787d108e5f968872e28dc873216366ec551dab6ff211fe757ed4913e9a5c8364575c4b2128e7f17b7f1ba923ee571b79f13dd3a52ae024114fe0e724dd65bea32ea94ba1d18033ff3bd54692b7f42ae58e16615463414f182a00d722ca6ca9a2a75d83a6dfb5fcb4a19bf12391dc149e06c8e5f0161579b9dd0a25021836b7f952364485bf57b6c7e0c0eba4ab5cea5d1eee38a3c4d2454c8e9883b388b8724ddef4cefd1673ad883ae547b695406250a92ceac5430b39da82b057207efaa78248df13eba13c3d3b28f97bbb3cf935266559533a4e135fb6068b835a03afc0734db935be3e75864a7732eadf4975b29a39a4f7baecb115c5910232d63a3545d412282d60d25bd8d5101f5a68813310adf2a6d79d35b13d486f3d7722424e5941e6a7dbdc22bef7fc03882d10a15c3576f1911a8922bde323deb5a72467be05f77a484a5b98535fce66453eae720020ec2cfe5419a4ab97962609372c82e88d955c93f98adb7b6c893189a94e512a5d5bf86599971ff17e8759a5d4e7f0f88c7d7447746677a331303f821790f8de907deda589f247468733f00aa3ff87b0440d8d1fad39f8839f794e3418ec6bc9a56352cbeb974e59701ab2d45187c66f11e29b69a6d4303784cce50f9fcacc5cbd4b2bf27eede1e5224cbaea04d8e4104e9545d9256bcac75c1362248924006123f13424bc632595a9ce35b677234cda6ac769e29de1aee87027bba7d809aca445fd1a32537567f446f2ce1a102b3ffc461685338c5693adeb4ef921091de6a52aed6787708cdc9ca84ffc012722962c9ea0f51f0290f1eec2b347f33495890ebf47e82a9e94272a06e7c0d5f721b194741b0bfd318abec2afd00b4859021d41c57c2acb357428ae4038162b24c4b44fd8e80a96ec0dd6f29ce7751967c1b7cbd85a87f89e24e18d4e20d203c72f73675a9d3b188aae31afe431c2bd8b5f36daeb15c572f5dd29e0298aa6f4a70484eeb8f51d0f1487af1a7fbed14b5bec6955b3e7a2fdfba041377a5136a2509382853da324b75f0882fef9dc0823012d179a67a5d6b5ca074f7408650d71367387568df2ccb1191144e12acd5446d4885981ecb47b8e7cb15a1426e83720b72acdc66aa7584ca3a075a26bff3964073bc7efa9e831dcd678549737375eee856e574cad7cae9ffe09c7399c0aed40f6863f5f5defe959dc6e9e416b4603ba7726240e5689050ad0dd618e4ecdb0acac2558c1b3742cd051b3d586f9b4001a764df29ee98896654bbb0e720c02af0e3f0673d47f281c6c2529486aeb7010dbc0a767fecf6d29f2615744a28fd787915fb121e0210a3313d00842d9aaa21f875600664e557fcaebcccbf2e9882140e0f0c551384ff586778c87fc347e3b4097a57f3c86db151fabf4a3183df5ac800b10dba4e251a49ecfe4b55bb5777fb19d972ac116c4465d72dc82c3aead27e92fdc64bb2b4b5d3cb4777e59a5d93f14249721effbd38a74cedecdb169ae5db4828ef2da03f987770e7a97c805c6f4149a400d32826c8bc9acc80abe3f8eab50660439f6514a3dc59356c8eb071c184ef8d72e3bf346bf806bdffb0f434a87e5f9c6eccf84feaf0e5ce30a955b2aa1d48fe02619ef85e6e8052d4359bebd6adff75af0cd805542c45f762af4c046bbf46ef72daa43313c9661ab87e41595720285f4e40f1b3984d07fd012f627dc7af15f572325f94919521dc898864159bd62f8cbc96d04f4c842112e32dd7efc29f0f0d720b9d661592ff865afb08a2637f17a1006b2719d1c81b47c3d1b68dd10b8229728bcc8a9b22c6e1b46e50d7889db5643e0f4bd0544bf20a90a47ee17ef3468264647bc5800646c40cb1a0bfa0a979e03667f59965b239db444e9be205279e377219c7eb8acc1970a06049eac0439223fe50ff38b718fbfb8e53fe3472cfb31c725faaa9a2b28398f8d3501ff3cc669a5e95823c481561ff19a0f45038639a49726e779c033e899d52c43d7490aec37b53a9fda78ee42c48e035d4dd6e3e7f38726cc4dac830dac759493884faa3ddce1e8bdd3c73f3f57c32c649dad238e132720f14f6c7235c811dea9262cfe4ad168f93c5e349de74928ce59b0856971d4f7231a480c432a71c5ced1aefc07fa9900223ece7bc203ecb3943d2d7eef0da2f72f52ec98076940fa9d2e90713c983b9a0a3ea09dd151a9c572622900db5af4f72db5e21578cbf360d010dc60b12a201d8b07f3eabe062faa8736d02efd4420a6f2d70bd2db065b62cee7c60d4a649d34cafa2c40447d8c2147761f94dbdf4a57222a72e269bfbcca6b3a59bb33cd348483f806d70f5a01fdfd40023d0c7c448722c89fdaa81bbd49d57c52b177fd1b2c74e4a4524b79ea9dfc91b24b5c98ea8721c70aef5696e5caf144ae4d5f4aaba5c281cd0ba9e34da604afdc1116778e3726d9c8d7f47502c0b831324dc8765bc62f7e293cb177e2d6a4b98c2d0d4f2615200a16cd54c367c55e2f96d98abcf32f517fdfc459019badcf4c9873e98e0800f83853cc376684685171cebc3006772949f15d400361e5812ec1cc55dfb7bbd7219e6c6137257c25a06cc0913481d68e0642cc8209b79c030d006e3392eea2f33dd081c64dff9e72614a36bc5816b9d69c67b5d3c1d41d18af5d4fa0e8f55ae72795b798fd77fa6dcd674f53a6011d90f5264a47decce50406e32c92521de133f686f7276ae44a51a457bae9a69b4bad64d6af938acff9cae2142f4ebb6193a7281ec1af7b9e9bbe78a0450d33000feaa48869d2eeefeebb672021f7da7986272d1eda6a8bd73716e1274286edcfc5a419c0441eaee05d84fb10ecea7e32d29235b50c9749b5b1d1daed35f91ccfc5b14844941a83fb5169520cb94d8d0d4de504c65b1ee8d82ded717c425915145f28b31dda943132392f3506a7ced69e22637ebe9dfd6617719f54b2a9d6bfb08150b16fd8c7e5b0057438aaff27a4f0a1372d5b86e32421422fe234ccd7d8be32d8329a2a102f8f51226fe2fe5e3d742aa3ce6ab3cb25d9830aaa3fe1a21347378bb380615cc22562862355e5a17a2f6121bed852387628955769e86c3f72304d9027c3d9a08b71020ffb94817725e2d7a725ed6ae964da69324d648678f06d53e0452cd81db79d302cae3c67a6395f74972ab54852e7d2b2e3e030413408be9bc0768b693bc964e06e020f19b713ac0f33ff0da1d06cdb75daf7a1bee1e72e2ccc67ce716d9e770719961cdf4a99f81ed20764caf7a8bdf93a88b6bb5d8c5cafd22b8c087fcb62d2da0cff112613a19e8200c7eaa6c5564e35c96ac75dbeada1e6000b39a86866b2b78f6e8865613cb1b1afb929751d9f60db9df762c90e75dad8b1879f9e41f93a3fc3338f6eade46e9722d6db32fd0715ec68a3a0f0eb99c027adabbf5daf986db277fbd5aa5405e3d724530b63c7425a37701644f23482978cb1e52806da8619c8e3067a43bd026835d3267b547eea397f58576237bf709ff2ea414e6545f9939bcd12166ae72c2d172f875259b3f323475a1341f2568d1bbff2085165c0eec3622b9f23f4b9887d2724cdd8897bcd646ba5983187a15443eb1b74e0e40bfaf139d58fb0725280cc6369f779c610fc71fc6aaa35be7ed53332976d55ec64dc68c0eb41d78cd09a062727cacee023555f962267abba5ce51acb28a34923e84c8f7c38adcb79343c3d41a467657f1606b0197f323a6d5e78b5667bb4569ee5643fd8966475874a1c6b472f49b4a77b8f2cbefd0aba0c672f78e314508603314c3c35ab050f5969d6c0d723f26822e64c92d3e2e178f37bc25af563df377f6fad1259ff5437d218cf3a372b13624efd498005da26eed4b7dfb01ebd7292ec4e369fefe178eef7bb6ff46726fb06c4f62361aaed412cccbf4aecae5291b924f49f7af473fcb4a8a837e3472a11e4cc00df060ac8351ffd9a949cd6fb0cab16a1ba29acbe0551e87e413211c6498760c32bc0d382da33159dcc9d82e0de6dc19b2665d3276b387a2b5fcbe722ba9c993577d6319248c1a130aa1645286413dab8e2358ebf1631dfcb0e45e727e75a098a60b65cbcfe241f3c228f7eeb6b5f2f3bca34eeb01698fc344f15b301bd47e8b2099a6691df065433c6d223613b3a4de1439c14413d5a5db0a563410051bd14d26dd74e1ab3ebfe684c235072e53980196ff60cb78ee3d373e5c997238fe5b5646037f62f12ab83c6cb6a5c86b001a5aeba42de606276b278d915442130236cda6e597ee85ba2ba763d2c2122bb213c3eb34eb3d8be9f8a590482727d3f29dea07fafe5bad914eedcacdb560c013e0d2448b7419965be6d76dce4b72c4cad1687f1d66d5664715cda373f378648a9c7fdd532eb1a8603843d2b7493724dcc9892ee8aea25151d520da6574fd17579da73a68a00d2f54db3a001e7672b65d4c72ff454b56da765a0d3635a6946baf1a0e5b2129e0f9e2f7a033b3567290d054ab67eb3cdb5be1eb04e799e06e7b0d7ce03a1d93dde0e1046173df6947158cee97480c265ba5ec1d3e4adc752114b7b49c2e15a3ac92c6af047680d9641bde9886962d70dff556d399cdd7f0cce05b3d823a2939cb97a0c1185cb92b413da4bbfe432e3aff8ef82feca6d2a57d1ce05f66ae956c81fc64dd4b77df5272a7aaf86df22b86c29980ca0997848fbad2c5af78aa59d453f06c8851268c1172268d8255f62467033cdf98d03276e4495ddb970adf73cf2357d9cde61fe3fa40b4be076fb3e3db0421aa47833ffaba5614afab68537c44a25bfb5a1eb5dbd172c384474518d1ca0199842c5790de6c12d337a70dd61f87889a4c11e1ac2d9f72510d221d3cc297dec9497e911bad6d8c792b8572a5d4adde046b683bd55efb4c6cd2b4713c3d91513651f62f384c5337ca2800475e430db2c360f381fff6c27273ac8a29a9a9207860d85e131aa9680237e7583cb31f091ffdd9d5780bf8fe7289d0e52244c208b874605f75f26017737df5f1ebb209c93a0eb75c5072330b720e4c42d3a5a57d2ef426be64e9906f03be211084b846999dbdca7832fdcbb4725e9be659ca9f5c0550e014f592cd3a5c2708ec2090a7f3a0f1dcabbca7674b7210091801a37902f316332a5d0da97c5859c36b7f735ebb0d2b222807fcc0bc72a302bb3d2a739623ebd05ec7242e82e7bc2d72e5207f7fe18e2b60241dddef451d4ce123e55b64c243b67d1d1af5db96db009bde0267ef427e8e00d4539b43720452dad3e466f878f9087b5fb827dc387712404a23f41c65588a9223330773729540f701fe3d43e84b6119bc72eb79eaf3ae4608811d9d7ae175cb94a2e3c3223b23b5b81693b41463aa7fb6d7e950fb028ffff1091aea9e54ae1e79c060407211c81382e9988cd05da26afa00e864852979b92e25d5aacd268e1443ce4c4c2bf1920aae76c18d84d256910fa57d3b84387f0533caa8d2afaf669bfa04693572e2a77c499c6d4f5ce9abba338702e7044c8885e6ad5384d4526c39db9c29b972cb45009d4b45b554c6a453f18517a888b8e6c348d2105c18f45f0026303ffe727d27896becf7981eef8f2540c8490abc7aab8f636b736748dbd10ef7ef8dd072217b189ee5187841623c9380f8880d3f9b6bddff43a1ccacdc51718f4d63d25b04ecda9a931a5238704e7e11176bef9acdff1c0998eab1c56e76ee8127e2d01934c1c95689d62a04f023534d1c34e8c691b6b942fe67eaff53f4b8350115e9729d27e82688b93fbf45e960a715e6eb09c35f7e7e37ec9357638ad8c696fca5720c9aaf359fdd930431d9fa3ef8ebdffa343104ec7aa858f875ad447d4d36bd72a145693647039125f201f5ad7bbf419cebc6d9df7c580f4a5032933790a65d17281784c5c64e4786ef2b76d7b5301311c729a17401634efa91bb982fdb1978540500ffc28c7d5e4c43c7f4b65653a9bd163039350b1e41e4a80746dd3c8cf34d9563b575a61f70130a82c4cb190765718ce8ee1e76a92df352bc5975ea01b17288aef704d892efd62487b92a5662c4be70d2fabc10e68826c087468a15ef2a17fd5f5aee684019bc1535a4d5b0386adeee30c38cb5008f973db7472b87dde972a72a5d12a1948aff51e62013e3008db82a46a96e0d5a4680931d7f59c087ee67f427baca1ba27012e0bcecec23ac5444dc7a2a33f51fc481d95948a826fced0d861a819b356f45d151afd7689895db55e6d77d78622b69223afda14a027cce727537bb2b7d3e20dabf294beec7242e08d67bf40b6de4f9a284fb6e2a04bcf30037a0a5a71669ad2cced1f6df9d7615698b859a9338de791d8ccafdc7987b9c635cf7c3338210e460238da5756b57184c78726ba4d9e23040c65f7216c4625f1d105ca25eb0708f574c22c966202b6ee8406a8561df6d15c1ddd59bb4c860a01a647df3c053704e803ebfecc7a5e6cb379eac472709574849435835d4259b9e72f65c4f26867e4a52802cd7a08218c16345a3bada123218886ce7214e381587110a6265ef333072c88792aae9f484ccba198e0323942ef54f3766f6bf2743e67251c5fbe0cbfe1abe79fb6ccf0bcdce0f1aca1fa1f105699f87978dd3c0336b0d3d1f2405f2360f4708f05daad6d2087e3c79e450d991600f095f3d8219b34836a22218cb06e58ab6dab869fd400b47ea90ae4c6ea78a610bcfd95e43286848720a1c679e0a3e897fd26e0d7cc78eb6b53816dd5def4ffc7b77590a2b953ffe727fa94ac5ac0c1a5f6aad9c3da222fd982ca3ee72f9baa80fc86529fbcf593472a5c9279c09ed3c125d97882ff1a58dd7020675c0813ea92285104edb899ff5717a80264ab8fd397848edadb86b6931c58f23d81946755345cfaee9bf523cb67204ee43461c7773a4bd7f6ddb3c671c07db1581b8a1a6afc40e622bf4b274f8729162e5fb01336b1062d082f3339ba22b7628725d90832aef25da1b2778f480679209e1c774c3962c15d037eedf64ace9bb3f26781899fd321b982039a43cfc445108ec0fcea58d1667511e9842172548cca7a41a26034c1b0dda4e2d90d5550b5eaab2f67d1bbac7e19f5536ca86d8f0df115b69a495710e8500954db2adcf07b12a268755530d836d0f227347ea76312d6c17961d6d8ca3f3d672ae35a33a72a995341cd1a12f29177431bd75045610c9d14e3282df9b6109a97ec57344374df59c461de97951ca60b39670f4386679b854268a34f38cc0eaaacd1eae589572f509bd5cc5cd74a5104b0d33eb897f63790434167827b5a95cc96013176e8245479900911c3077b972b6d33027b90feedf8f952a65df20a8b59862c91bf1ee3e4f13b2b71bf6f4bfe0e22d86760483522ecc9a86ad834a5ffc89c61692137372e141f5cb0b337b2bdd6245e3494f6e0e4d71cea1cab0cf19b70a2e3b2d6eea728399da3a23c0015e3106607c2f1805332ce7bb2dedc13f2902e7b4edbda59d72d5bd27ca88c5e29c00e8dcd12a058436b8a9e3c98e07168518a59ab5a6e4b8720f0bb9ac687ec531376a596cb4143f92b018edc10b430f1111a6c49b5b7c5870ff7711d7ed270fde45a37b4d5dcb55a6ec2bc04f93076e75c83ef2772d9ca272e4a7159693253f624e740801e387d49e5a54bdf1f30188a215947e441c5be560afa0a699c228fbd2873f3c0509c90c5047f15bf66ff853af2a6368168a03da72f9c450f1ec4cdb945c42cdaf0aadcc0f6018828b56639a67719b46f35f8a7c72d60f7f235bcaebfa89b1c78c772a74b84ab6f13d869e036fee7bee71a9744872b4c9a3c29d36626e5a975cf23caac4f2da0480470caf60560fc720ec4d2d925fd0d04fa83f3cda53203a4e001ea2ef2b19210c6c15aafe64ead6edab19493572c4101d315b1f9508be6f31fee6b771c41a9fd0c47eedf2a2216edfd333b6a56f7c962f75c0d5d4d5f2d33b45a964b86c669207299bf93b071853d1a63a910142ad64fc1dbc76f4a4cfe8c464bb5ce7e48802b6ee58a41d48e019813983436172c2416305baff42e2131670f9fad2223b71ad4dcdead3588b28c3121bb19d757296a4f43ff399ecaad49184af1c87e05564b1d321effe7e042c79a19c8ab80172626cd53cf9e00a6ac9f5ecb59d39eefc54585f58336b628c89dc9eb4ef717319fa00d1e189831e6bff2a574481aef12af5e385ef7850c7cf9244ffd840b68e72715e85e70223f18afa6ec316838e5c7b4228781cade4f5b9fd129af9fc7d97726b622f8431d4046f45da6143f4f15de3ffcc741d7d92858a1ebec5395c990e724d7e6ddb3921dc64a0714c78b6266fd6733e841da4210841733381351b41ed6cde1b1885519054164f8fbd5e158627187e342dc159a2852a2afff7573fee0872fc177cf56608c72db7a8c64fe7f7009520324b64b2cc17e04bfc43a14fb19242f10ff0f74c86489a6335caf681eea68f3feb545e704e205df029904e72133a72ad85af03fa1bdbf7b1f5a645114d42bb691d494ac3002534c8be2f62ede1c372ea91a52b1539ab6917fa23b1216480bd328765b9b58bde9d9748893f872911720766a233fbb4f3c4d020289a8a0ba3848911e326d5431c8145e098fcfefd7c5ef0a59cdb9d34f2fabcb66f84e3b2cd93653c48729ac45401e201005f4aac6a724aa442fc40e977b57d83b211cc96faf4658c1e174d81e7d368f3a5b6b5393572e408314fd0c3733017ab450c64f8cea6daf1b70975a3af19d282369cea2a7b08baca9b3d3b2e16b88222d7942c1738c5453986adbe1bfaf94dc7baa64b92446aa0b23fdeb21d8f444f7bc77e5968a4cf416ac5009afea2c2e0540f2a63a09072bdf1ffa72fe82362c58e9b37c7ab54b8114dbd1c69a3cf4d5e9cfdd13e0a7372d2459d66e39fd14b1219397b62c199f59b87de234a43ca2d0774cce47bc4fa72b42442c9b02bb591dc0df68bf203812994f0999e434d8846323cb37150a5ab726d6d08750fce18f0006611f278b0dbd47121312e73c378aabff0e5562da9ec1764bcc05dbbecd2d037bf49bc1aebba9d0c2b933ca487dcd006ded25de44e0d723a71435e82e674ee88f2fbb9717d69ec9e0a9900210445230a309a58e98a7a27c6bf4591352d7ddee95f6bceefec0d1b167298056d1536dfbcf7560621a52a7297ce12a2af2c89ace463a1fa892cf810aacd68e51719b56233d843f7422e286ddc1724dc0ac2b9acafb33618b809480e8d1d3da50f83193c3ca65a716cab1f54af5fc761abe24ff4ade7f3ab471d92e5fd6624a00effe91f9712d8502c723f72678f7dd839f7a0c0d00b515dd160914d51fae6c8e50755d7fef9c458577b4e5756a4849533d04f29382794a3b66ad77c7c86ede516da57d2b17b6147bae5ce645cf6e5e66b7657e7216c8bc5b17dbab79796a92d351c9a0a0c812275f3867c72b85a0c5886e09520631144cc68e84b51dd0ffde840e241d434efa840f3ec0951085286cdef4a156c8e14c3ce737183abb14ac2ee3f0e196f6f37e301e2891b723dd4ef0edf6356979a31b72adafa13554f0fd54567be140178deff0c2fe9406c586267044ace4d992b1b99c473a8b184c42612f8c20b206797c472c4a8d9d772a0c674859b5e7228063f4983e1519496f34bfdc4484bad19fa1ecba5f22e682a9cd729340abdfb2f59eff8f8516759f679e2de3036b3f64b804c66bf938822726fdddbc036022c995b6150d0432ff60b90a90679f7321099255fcedf1134096e4c1930bd4324f92e545cd3cb368c412005c37cbcb39c166f4252d1f6b821b67203d9942f6305221a22914329f7f9cbb03cf07560969410179cef67fd17a6bf531ede2d6d2489ae85a1adb82ded9f726192f3d5fe55eda0c954e2d1a590963c2dd74efcc5207baa7e73a99581ab15992295e2caf4517f55c68d912af0366ba9728e5d9403f36a43d98019ca4c86d92a542609229951d93f865c0f861a143ffd25d27ff6a9a143e32bd4b0a34ceca036546e0cc0531fed70acc55f25b2d01f7e669fb4d1ff72d893ba6a392e06cd3750416e75a53160a615d36fd9f31476b1b615f55e352227c92174def906e6f5e2a65b570a4f02ab8d1f5f5d1b9b0f73fec97251c7d5f4d573a84e0230e3f9224e558dee3c3940bb403c8a24d9bb0a37eab141d5efa67902f592244cba97b5f9971dec511d2a3122bab7d464783f15795b7572c1d3d1eb5ccd17151ecd7bd6e53cbeb8dd4507a112a62dbc1c6e329c49467754edb5f3a0d96912f8f39e643fb396af948cc23994596e718ee0c98b8513097a72c86548ef6baf8634cfccb6fd9e58486f46d485817dd3cae51c7e5153ed7c5b72b8b775b2479a68a7d26e6d7d90f54d6384353d409183fc3183e23c3f77221908468fb2892f5cc9d5495eb9df706ff4da367e0d078e3e56a9b467beb34d6ffe61595b3ad1b1b509fc97104cbdb73ba57a8abc341f60fcfaed64827905eda68a21024fedc4cd1cf01c59de9b84e274b00308bbf82c490a247cd0f3ba46657f631a90320bf150a1316baa175b8280e5f30ed901685ecd7d750edf73e83cb0a1205f86b83531267002941d496688fb712c55b22854959acd376907d4f09aa6663b10e9fc48c1fd6b16551e02cfb77d84eb7e5eb2c8f5f4c10f9602dc332d8b39d07263f66d8bc5313f1bdbdf6fb308d9894fad1cbbeb607014dbeb3c8d30b1b8d372963d66ae0f879bfb426e9e432f16a4df5e2df1715c866eb7953c55e7c90220722955124dad0e3c16331c5d4ff66fbc19eaedfd238065b5efa3936630cc636172736ed52796360296e135dcbf974dfe01c698ab0964c83f444626d9082630c272d9cadd9d012514dc5b9e0f41b79be81d49916c2308b61cf88f23ac9c1189bc2a4bf39ad79be2fbcba8578825ab4366b76c843a1b6cbe4449416528ea5cdb9d728d1d39c18c99e905590e5b3fda2161d3429ccf65d82e6ed6fc79e93f21e25d00333fce488b0fedf9ce61cfb084cf3e37a88f1a250421e58e4b316ad883c54f72f46f1212e37fc2c6f9c6f3358ce9daa5ef9c5378b889d40cd31a28e12477835e61a5aa8957336387afbbe75dd91e7dbd520040d0bfad4990bb83e2ac9c043a5c8572da7fa7581fe665eb9d961fffee6d013010cabcfde4e0db2e8e257725cd724e3a77eaddc96323905fe38decfde323a0bea5db34942d1eeaa480b99267cf13884c66246b3214c3ff3bd6b12855a9bf53da0afba3a2509975c2ad05468cd572d2e0e38cc0dcf8d18158df4b77bdc6cd7241d59bb0c3fd035057b96a5e0e5354c28a56c9c9e055c6837f61faa36eefae095454cbdc5ef00cbaac197f44b8e972e230f91b48ee351de4785e6f39a7131f2aa52b8309444f4aab4d61ba7efdfc725a380454ff40e8830adf7d61b43c7c05817e78e17cb229fd293c2c103abf486157598cfa5d24ca53bdb18c500d5aa3d802f29742302d49a54d515c0e81ab4750b7f21c333c5339af3a731e2ef6c353a01dd055b236aaeb45e88c9b7f9b513c5e51af4d414991c60ada47925ecff6b99f6e31ee843c0650871266d954e8cb8a72ee80a62c5991b470c01d719874939834840c524add7bcb51d95e192a8f11f67231a774d2783b34b6bd8decfe7390a0f5d5f06ebbf5363b192f300862717e0c72f5ed6970d1d9d4d928370dc8d6b5d8bd127f7bff85b67ceda513119c4eed6c72f2451c5e5b76a31d566378fed7ef6ec4cb0c8964b975b3d19a69ed978e6e7572e66709e25d8fd97764eec45813a883dd7c6446e50cc46c00ce8a793edc21c3721ab5fb6b590b78290be5991af9d5ab26b78df5abc32382586282094b4b0eeb72c521cb699bad8650f6fb6d616fda457fb1e21839edb87d661906e902c6713972301980a379366b9ce460c23bfd6e2ec4c8ad737a3103046e49216c3c1c2faf489a214f6a12dc5c8b1f645788bedbdaa0b9e8abbfba0503a22c749960809fb6723a1e91cb4ffc57a7c6afc2084e8b2cc076e3d12751e0fbb6a1f81cd54537fd725fb73d5e87ad6bf0c670b9d5159ae7541a19fb39ae88b133cde50667ab9cf9358cd8537e64bcb6e3a4ea926d4848fb9bcbb89c1371a189d3a893fb5e2667eb65d8731d9a018bb461b683081d45c2a131f5f63412639d8469ebeb0461ca9ba52232125407aedaa4d05cbc564585170984deb2180f4ab290edb7d1a901bbc91d721adb69b10bf0343d3014121cd7976ef50a80429b12a737563572d756a4b94731ee8a076234c70c2b6ff3baef9131a875f9d67019f4121847af67cb19bf725072acccd0a0abe3b865aed943e4a070b07772d13bd9bccf4c659df897f0bf4b8e72db0fbcee372eff5223d953284854989fe4f3bce5dc8f7cff740d69c46a2f0e7275a95a7d234e7f2358ede625aac86af947cf6ebc963b755e595ff0fadf699c72b6327d53338c5362402eae937f368c27414e2278fd29a051d501570db5190455a7cbcdb588ed1b1b6ff982e5513796a4fbedd39a2b593608efa348a07a8a7d72b9dbefe9686989f77c2e41dd558decf2a375c748fd84dbbb56220ec875c4dd725d31764653e494c990d2fe98c01eb8b8579597e28432ad0849942612bab8fa72a39584063c6097e898a4ab65cef1d82d5e86a78c691f5f975c7fa210d611f568d108d1e4c90be24328c66d833d436a1a2779e0a06a30db7f3900d8dd22a5ce13aec1e962eae2c4eeaa87941895c7266d406ee9247f1dbaf61e9e79a0260de07299dd3b7d06383ca68ddb24c555cfb148b3f390bd9fe0d5fb7f8ee381baeb71722affc6602624959edda869eaadda71625c5a8e355158b0c15dd09167ba0840726df15a5242291d10ef037d61f5920dd2e6cdc66224be63b699abfaaaad0a3c213056bbd0b87702ba510b934ba89e4c4e56042ee37f377fb7e0b47474c3f785722330a4104eb6fb323bb67550eac7500f5b739992d151b030c40a884ac37d427147deaadf43483759802886297047815a7264c64bb7e9d6721b9b643f461ae15b71bef1a72982870b8836e92fcf1c2e4f5f52dc8749e2142e5922389d2ba9af3c017a9ce5c753a63fec44689945db88098b6c9d5a246b0b3f978cb73089bf6e72ec34c145f50c8fb186e6493cc9aceedf534b834b8651ba51165748e04b61293aef453390ab873894d5888722ef2da7104dea92c44595a6b8e6bbfbe1e709aa01d531ef8530571f3bb889af084096e031c1aea5cbb3b70402845f9028c6fe8d72595b84e9cfa6c766f5a20a6a10c38a2d1d312661d43cb92eb2bc9c587c1d0472d6b63706eb769b826e92fbee3365daceb9fe391731ea985e1d55e8e624b421723bcf587341318fa87fd40f9c82f8e92481c4da2d759348fb2da8e6524f0bee0f095d79497b5f69f52e51d73e2df72725b8be9c7307422b379434bcc4058fbd720260aab4b9de9a2c2a3dccf8d013c42f737aa82a7c33f1ee2f95e7a7156ad9728843b2ac3b144c4bfe22d9ad95dd3a66344f8f4bc1d63eb7cf4c58e4a25c3172a1893bcd1fa46e7b6e1f28cd82f33452898e53103f3f5dfad310bb1a7f3cb9637fb5f9ba4f5040afbada72a165516bca985a295860fa20a54dfc8863be486672c86479f84223aea0ca184160a14eeb745ca7dfff819ae3894cd63ec08b57d272df6c97bd8087bd8803a69b28fab542b8a2415ba0b6e6ee3fe893e7d783dca47045fa05e8c67d9d94e31db081826b404a5ba0649250b36b5718adb54531a429725364ca61ca27cfcc387dda679cfd73186bb083135ced2e58ef89296c055b0e72b334e9a2fbd0c12d8323bdc82cc18c745e0a8dfff7d85175ad14fb35989158722ce2c8cd40b9657ae46e9217d1cb3f3d3468a29c68af08f50e99756f31bf0a7281e22a9aa14e69bbda6cea23dba450cf61c5aec5637aca22c74ff92c26221e57059d46c20d87d327048a1a29d01fda2b516768d6fb862cfdb5b678459962547212481b2ccd0b0fc5da83a38512e1f815b646d6313169ca4711d8c6f3702c994f86f666d48ff9a15f80bfcf272cc727d56567f0d04f7914fbc606fd301998fd60804b7023649da716681d4f4b0e89813d1586df19749e5c1a10b4bbd8b52b617214321c9423a429ac23001ef0318b93291d7788443eb8df9d4859c326718f3072c66488771e7ce84af87ef473f7516f712f67f35decf7ecabd6784ea8197fa37268922f8e3021b6417a2a2244c57055f86e3a9c4e6592ef3a444f8be2fdf34224d46eeb46b58867eee68ae7bcf26dfc96aa4cd5a5e692a4f5aeff5b602913a672296c55d07096e51e81a5227a7ea404ab907643757abc77b6e6ac5fbf7b4bff087d80921fcf78aaf99b5ad9d7d83870321db8153a53c60ab39c27f5e818b77f0e2c1499f25cb98b3051d3a44db06e4fd05f1e9f9fd8200e35398fb202a0de6b722a91e87335169bb0769050018a681d741f0298e1f2592c15b9f92746b06c897251e1bbefbe74c543ca5c5c6f55487fd1835ab0da2371dc05838ff16f31a6e372eaca3bfee36be4a9ca7b244de7340c5d11d7df319d29e868d0ddb699a9b0c6609ecb11db50a2c23274604cb19f04e7e595b31e5d86aae646d3839c7572c860722d4fa7ef9818264b16393c05f50be0f4f18c5c7929ee381f286b48f84ec470477a05e418a0576f294f5e076ae7b03d0129de31462bdffe3ead23301355dc051c568606ea136b93f20785b9843d2ee3d53cb8bca109a7e6d707f9267ac382c61bdca40719b7370d290288e05da4991818296897b4814d5b317356f535dffe0f72569142b841b122f487568a5ffcc96a903bc5987f315d311800a1bed08b6bb4624974f69701f18c8d32049e06582cfe3bb33e78e47019b36dc97fa74e721a7361c26ebea7bcc038dd333df185c0347d0de1f275658962ce482d5a613455687f72cf96ee71ce9ee8296e3c8cb83119485abdde740bfec0fb5aca204501f50bd731ead966e571005957145add71adb850f3d479450eab7463ea02fb30f6807513723baf3c26e52962952cabcb55942a137534f800374331e6774eea839285d7c7220995b7af56e1b5f40e23f20f75f7141d2fa9287b8e9ad52d77d07d8f7a48c972410637f5625e51123155d235f1c0fdbbc46047a16afc7c843f257ca7fee7347257bf90a4d36d3eafd39597ae056fc5945089ea1b35410fa95a663e5d641cb9729ed7c231f38b95b810ad78e677d415a76b7c4288a1c493faee2bb3a2b8516472b55ded66320de20eaf35b5241abb3a90e68e8cebebcf4f632c1291681558c72e2ae21da7fce24d360dc4da584878f71dbb7797e86e1e9d11aaa8857f0f187472c59d9e08e0dacd2560d018b3e26646a64ac2ee05ed0acf8174227dd53c141b1a20356ccc1da09ce551c6e285f4468a7216cb271d25c2bd10fb4f3a0b16beb47245e156002e5174c20b968dbbbf04d21d0a2302470413b87f5be368e8f38306125c371d929f1627742a618340b27ec59e8aaf7d5142cd5275fad26f637fa16b72904002c8c63c11a5a22bf0fba706e9da2fa1885040ae8fa0872cfb1f5972a125072bd63e45cad999d4fe969050df2cdccbba8730572541dd95ed3f71975bf37228f905ab4ea8cb7034f94307e8820f75f7032070bc0d54f61a1177e746eb68723b71338068d9f51051993ff001d72fab613d90cab796ec107a1138875f37994477b0d3a7db1ddc152a95dbc43a66ed7353027e54d9b4ff23aaaa213a6e9e451bb83753f5c66bf5c322237a27aeb6f680c39b59893a8cfed1d48605b8cdfabb72b78befe66354e9847421cef5adbc0038db184eb907b2ee8f6895fe4b6dcfc072c4c8c0d18f5d810d29c12c2b8eea74e9352577e74545c4fb05768678efad6e72d4023ce7acdedcdf901995239b488901995188645a0afb29eec1029d0217184d18c3afbf019cfc6faf63120b8f8f19d6f6b2b43fd97cca42caa893d50f5b6d72263db2d1d5f24b95da5efaa2bb23a207791bdc190968f019efecf1773a66b8724430a26ce649efe8a960c7b225be293a61ca75dd58d70108c60094fac717cb724f96f1905dd6d6079b208ec37468eb20117a30f870a71818827c88cb8389dc7266d1e721787fcb54743452f153d7cbc9b874b580bf22804ce9c845a8bc80283261ebf4af3e57b989a8a00f24091088c486f7b360ea2f9a5985f9c364ad1d8a727d7980dd9ee62784d35a2284dd139516187420284bd9ebab32d1ad87f0b2084a8024326894440a1f7fff49b55c1d524b58477f9cb2ba383810a59ecf005b1053ac9e5f3b17996fd0bcdb791fa74f63fa76b9e6f9e14b4eaecd156daa929c47727b8cc59e2998abe1ef5060c601958840f2390d5392e1256fb94e99b55a5ef7722f21ef7f285c45520c188556b38135bb953e05a9daf035482d97be9cde9cc347ca6df228d91a31186e28250c2a28bb23e48a9dc4ed8e53b144966d47dc5a9e72605f1808c08eb00c534b72026e55a76869dfafa3996764d19c0b37eed8915d72adbf81da8c074e794777a19315399333cf5f01ea6045c588dd5b662bc6155072fc831710b31b79a66911f52e16cc43ab65163217229df837fd3c61677934c4300a1c5380982ed51dc5221115ae22ef3f7b90059f94389358e623aa52c9fdf7262a801e37ab8ec51adaf1625735fc09604b889ab9ac6d9168ac9c53bbcb836f72d1c38c7006dc61d371e3da44465e7e3e24814e9eea0f6956e70f4211dcbe74720c6bf4470370afea245044710fa3e03561042acba7b17c516872bd998dfa6d02c804f3aaaab1fe2b4aa65a14959259b04fc65c2d4c9c8166c058c21a2611861a0a1a8dbad73b6049c3faa03160b92d8e77a10f6e6d9e704f7778b5602dbcfa72142429bc3c2c67271c70f1c0df4a437a14a7476a39653c045b4dad380d785e72f096805fa73f8c985bf10444fdb44400e007bc77469bca910caa92e6fabd8d72c7bddc2bb2172945078e631974d916aa4c2fe071b1d0338c927066b8a7a80672dff0c46ae0228144e466e40166e81f5dfe929e1eb2fe40dfd9cbf56a29b5a472d1eb2b137ad5265173f320563f6c771bd6085f8af1f431241786abd3e74aba7263a3c19dc881471484b3ac9a8b687da3b1de9a42b08eed8a7723c2b0778aba727645f257876bc4e4dd4e5fc740e4ad0aa48d9ea6a33544617fce9570ee741872dfc2129d3395a27dff14d5da2b15ac73ccd978fa3263a6f009679d83a4c1db4174f8be203decb039e4f5cd5922d5145baf0934bff04f3ec7f49834572809ca52edf2616d1069843dd442489ce2e2102b8b5ef2c96b48b7d915f916ea30d7a7643e3f152b4abdc9d72adb934eeff2994c69984a11909534282ef4db5129d3ba604a669884d9a4759d67fd5a4e3c32cf926eba6110729e0c84d3ee72bf4e196f57d8b65514d2835208eea0a0ba0b1a7483515ae7ddeb36cb17125c15c8d8caec1056a7d60a483897fc0ce311f38b5122ee5755ef6410a5cd9937e6027e53ce57729878c21eec58f77c4fa708c53110d57ac8c7bacb47fb1fe52964b6589428013daa01a24bdab823c896f9b47c7610bdec45d6ab1c9ac554632df66105dd13467223a3c83e0a2e261418517330df7d0a743344f8edbe32c86ef0783087827c5851c1f237bee7ac96791303d989a28941b6d8c95813498acbf3f733458b5dd450729511df8e75ff85667bc095498b55a9a85b330c0dfcef5bf8245a9aa40226bc7229cfa186cb0e0ebb65fe32f6e6879bcc14fbd5f4cc81e6a74d7fde56c1a93627c1fc88e18da6b2793287686e2d310ced03923a0af08cb25c6a72efafe48e58725514f687ea49eb52249f97d8946cf565baf352450f06810843fe7c694e760472de1574f69d6bb4ca2e148b8727a0e725f83317848a692bc78dcad10cd972627211909942606908c0b87a59467e91a35406349850689d21f66bba70222b22592a4d6432727c8cbdf426247093203cd6cab347530af0570e95af14dca993d2cd0613916e12c856f167acff60014a5b9f01d73e09c18eb8649e6dc370a96fc74872ccb96b075e64b4ccd96ee2e71b8a2be4b648ac99a0165bb483a4128447daeb21d16aecfd1ebb176b6ccf975ddef4c92216c8adede6a788166b7616c76c98fe72e25cbf81d26026b750b534e559c317b6833d8b6a3e5becee3a6f3941c533922cfd05488f3322a997c1daa17826b133dd539e18fe46b89dfed348606d128426512bde6a20a4cb5912f8781ac50ba99a074f57fc3c8b67ac2ab49caee3fb87b472fc6c745d36b4876f92b508e2681404c01d14c476ee5a4e17f23f9a30ca37a472015da2121b4870c52070d68aa13489e97070aa18ec779ec206c2f8ffa26a0220c0042e8d31728697f8c5d8eeda157b64ab2de8e5df58290ad6dff4ac9968670fc617c1d11615ffbff7b1a72a190a1fa7330689e70ffde101f50e378e4564c272ce06f798d6387e49818bfa228f20efe520ec81cdc234d6a3cb4fe5be54d9c97211db8fae3336cb2b5a25fd1f1114ec912ed0fd049c0a077775ac67d717095b724a724c0c476a199a8f8090ef6fcef68095a79e4625da4bcddbfd4941170cce6bb7f396d5b510e6efe54f8744f9bc15b2cbc4e577fdfd740256faa733c4fbff2708b02239ac7ee26f42af5de13464e4c0f4349015672948ce2d63fdaa7ae83f2503e45d11cd2be2778455f5af295d48819a19522681c69a439aaee40c75e47172cbeab7bfa8a8c3ee2a3a03c065424c9f7ef217d10da1d06e21b63a102212832fb593f71e5fe266c4287fc0986a1e49bf9dfc1f4f8277cede0c06bb5e7675d14d180dc4f425b6d54cd560da4b12b265fc3165d375adbc60c68d71733b0e05941f677bd035ee64d1c692dde999177c64cdc65c71db1c94526589dbc94e92c8627223355b73d04b7257db509d948e6f304784e0ac297a78c86c6bbbf1537da3a372e6b2e72ddc173f720c275e0c784e87a2795ba92efdf1d27465ec730b5394bd0a16189a71a858bfb2d715dd154eec18f24d887fb4fcc565165ec0ea1e61a9e6502acf40052627013b52a3ffcbb16a6f34b7c0c409f0b1de8bb6fa8f9bb349dc720194b52cee4cd2228b6c2e30b466f8d778e437e10908401ed8bfc9d10e529872c346c0733de41fa6e4a3970156c8c2c58236b3ab2d106022514257f642444f3f081cc2f222013578405657f66381088e10ac1900fc2a99d5704a049c5178717208f069aa44cd6b3ead3e85c9eceaacbcde5abf274b50fc7dd5bcd1b64de67e28c4bba92e1411eecfe5b9b8e8f5deb4017ff47e3370db99faf0854328f04e83467f0a082a25b04d007b6817924c6581ea098df5318e31bb91bc18a834e87f6072cce735a19d3dac284edbc551434849451ea0218367e15cfa37bbca135dc1a46c7986029dcdf27d62645ad7bad906b0834062fe2475080538d1be7bde1ccdeb4cd05604fdaa0113db8e1f85fe7bf737e55d2b6a012a5a84fe5a78a238c6d9e62c0dfd6eeb750bed51cc8a76aff09c9e80e852e0a03e718195cc6f7589e243c7721223a20d33a39737c86136a4c167a87e75a14022023b3fd422f0f5e99e76fc72a411ab6b06a8168a90bd68b671b7a31b0aa6416f4496f5df9e446c9ab1e03f3701ab3a0235b8130770df9b93ab76a7cc3120c48df1a9ecbe1397a3a33e466f7274f979ae7cc149da509141398306465c0fc518052f2bf43cc1a46874938af572100656a4421b1745cf0442416874648b233faf68d1c21f825e8e2223746f9372b9808f6824e2493db3691cea2e23df300f7d2b04aa60d34fd7e40591f4148372a7dcd51639f3fbed0d2167887636837c7b5009e60847a2dae4e570b0dfdc5e721624e860a7f5b773cab0280bfc7d64efec7361093c2dbcffc5bb9ea1627b6d6b4be815cada81963cbdbfea33dff316e76422886afe027e391f03f6c6ca617d72b1827385cecb0e7f2662e7156e6733d427f5ba5d257ae563e4236ca205b5c4150e01301871689232a8c24d7d1c980b10a5157b1a1112be2abe9b0f43dd673072f0b91818ed2fd1e77eebf06cc850096e748a4187d37ca70702cea35aea07cf7227ef11ab953b06a2992fca865b69728d2e00e625c1022917dde4b01ffbbfd703c6bf9fbb541d98b64b9be79e029381343ceacd65901acaf6b5a3a63e7d9f64725a9a0cd9eeefea64b4c2a9982aaf5643fa0c094db2ab454ddc015b8a71e2ed72708414d5f18e0767f4af37b4226e1d62d3c53d9f99b5bfa695a1f51d85730172fe57a226d75b7bffaee68b428da67006468829834361706778528683fb02e10594f26cc04c5564960ebf1365b83bf760fde30e5fbdfdf10363d303fc8b3ea072bb43e1c8ec09114c8dbba87520d2efc470861da375654728cf3f516772532b2bb0eaec1400693e0fcffa54fdf16cf52c17d7e4dcd99d6748b8996cef3232e619c46acbf905975de4eefa4401d219943472bd6f046ab5661dca49d7cf4b836272db7c2409903919823dadc14fcca8eaa9c7dc33b42137a28368f54c8a89ee346a2a68b05202146f6be7231030d2da326e21f9ee4752560d487bcecbeaa75554727cde33063ee3a4956ec0ef44f845b0daae960402167f748da61ea92577c762725f6c986bd24d86870054c80bca4b802df0ac21530913e5cc4bb77566f80c007251dc1e7e9d34f18de0891e4eb792aaa2ee4b5697293923255404f423d97b17076bddf462a838007c43f9d8fbbd53e1850e088072b28c7a0edeb20454e4cfa06aa13fc6c1bfd1157cf5a1f162173d53bbacc9dcd5377a1af7bcd8682a2e8989725bc6d91f23327b41703ae2be427cef7b12a8a704948d4f15cb4ea996ec96e002bbf7817463b72794cb8724b7bd7eef49812dc01c1025ec4e37685480410bf83edd18b12e7e00eb0bed1c8d1a150be3f7aa94721466779b5ebe7226e94af9ca727d9f705bb084dd28c1eccfb14f8ae835fcdd30c17c5fcaa4ae20484f475d3b407e69d75a2f58b594fababbd4d4e2e9ae0cb2846f1fb4f517b0a22cc5bf4fb1726f9bc4ce9e75b3099ae52c65e659656a885865085f30acee0c1d75349a84737201668320221616fe297dc93bf128e206e6730efefebcac2cd018ae29d887ef6e0af1af566d37720e55c5e9d2394ec59eb794d319cf4b49b97a589637af1d8672816721845842b15e2fb1fad4521e71dd1fc76d2fc0c14383d852c7d116c954726a37f7fb14bc1fb358f65e4f2078c501f1ccf3ae572b190edb552c5334e2ea726e535a5660a3d1f3ae2b6cbe9aa9f859255a2dce5eedae40fa4cdb2811db3c72a6308299a876ada556e9dfed9501eeea1625395f25329a16f3ae4ed35ee73a61839f7d14504014dacaf5e79721a3063a6c297e038c3534663b93bcb40d620e72e473292da5fd478bf5dbe950236de7850b799faa9c74eec0c557a0a26d45c0148bf1e8e738e84472396f4a6bc194ed88a8fcd633a7d6965199f7e5221bbd3a02bd5825c243aa2a5959c6e02185d458728c481b3ef536a7554b45845481f79d52a9dffea689ad8382821c55167d9c8bced773bc1a0644a323ba5d0973592cdd343db29c198a8f5056ec8734ff80dd3e4d5eea54ace7a4bcd0627ea45409d1dd71cdcc7063e5e814d84d5dd165883c6a8b1f4f5e5252d6d6ca8ac154d259ab4272895f9632c5faa564329dcd4538edd6c893fc9112a9da41a4fbc70ff4d27d7f658ea6887650b6ed3d40c69b14d5c31d91dd7602c30e02e6f6d80c199b41a2085b6aa3101ac6905c8c2e86c608b7f6b39f47a8087fd6cce6f2a0624cc9042eca7209cec8a248a218084b3bc347f6342e1b5f7d00b4d18b41f7a404b91730f53b72fea405e7a55316b2e270e2a2fe3098fa56c5415c96a68e5f9bdf2ed06ddb3572ea5a6c1e6a7f79170418aac68e3b543503a7db9ce15d77cdda187f6d0c290554a354a6a11a9259e349ad12e84fd5446506f7d73117cffb3fee5744740a84691a4c768c090ab9cb1be152620cdae60912707d966fa18b3e3a6365c87d8c0b521f9d1fc89d34ac8e37ea09cdfabf63ca9a3ba89421f64b130ecef5986c694e8372f146070fab5cb6ea662f82efa9e1f1458598bc7b7b5b4ecae41b3d5177ac4a7221bc3416dd41690218ff6e0e836806cb44a1c19d7856667fca0af54a79888c72a875b2892adc2523ede7c316c328a9515f48e7cf7c32beeef364f4306dd0fd2ccefe6fd2d797a06248b2001a81d5cd5986ca7d98c2cacd3df1917f49bd31d3728c80985c3685eb9a306cda7bdb78d52811991daef9b9c355c60593c1c59cdc5d12f0b5d59ce65c7f2cc6267719e229693961ab35802838d394793b8ce6eacd2d2d69893d303a50d42fcb3196958d52a3fb63c309867c122f56c2a7da1fe4ae461fc1ffdce01e5e9fc0af212033d2774c0bdf2351845849d329813caf48765e726324fa5d122c0545fe503de23c0273053acfebd1c6e456dea9c60b3655b40272abfb9cf39efb5b2d9da7cafc0c385c484f0291a0641d2eea755aa4df8437fb72fead4e6b192383dd4976f4976e160879cb4d36f19d773ad71da38cc586e78a224b55a8003f9daec942943712845c6880b37392d00205fb673489dd392240ea72ad41dc2fd65ed4be69a835cf2e804b7514252aab00acd00ffb7e257190686a72fe2e0826f7b0626831c085757fca031cc0e50d0d1d79d66865ed1baa88e6b372f5b51d4c148f1abff388f21535b5e4671010b47630b62d26a5a8b03861a4823714013261680d8fc634a1cb6a56313b293155a567261dccd5be65aca4060a1f727313fdc7604eaa77f768ff382ebbb1c668769a1d239bb103a3a39dee2b3a59724bba7a7b6f4be323a5e0374ced06fd540d192b477f6a15203c58941d9cb89072e95966687c90c5ad874cec432c56719ef2ac8262a080794ef511fb1838ea575bbf5ef69e7075227833a468b26056e181467c5b31b9ffe1514dfbb5bb0e080d2d4c58dc302a801e8b7b6d9692c92564330c6bf8dbb91ae30932067fad26216f72afd2ec828232b17e27d5ad2e604c45c0af267621e762f11ac7faa3d6fbbc6a722e63bc5400707ffdc307c50b194125734259565c2b243cb7858c49c498a73822a2fb9363a87981509e7cd4efbf32012252d2c8811c763db30d0ec86ff1f43e1b04e8f0597abdd18173c2c25b4ed08786b34d78b96d50654fa793a99dbce81e1ea0cecb53647a11c1b86f0ec68f0982c0ce10e36681cc292f704df9b7eb3ee45a0f66e9e3925baea341499cba382f517fe8ddd3acaeeb7e6a125d7cc9b2e798724e9bdf9ebc01282c11b997692bdf495093e22674605c2b03c8edd86aac5bad72735c999a508855bd89b5a9f2d8cd39d97041a669d91fee2bdb546f3c50d5e01b65cd7e8545edc2cc41c468262c3e01fb5116e7d62f6fde812864552dc738ba6eb14a8b4fbb808b0f9b08c6d2e6fb4dc1f799b61c8a0a921f0289abdf520eba72afa0797bfe2e98985da349d07171bd81cb139252f6a4d71f96bbbaa0d34f8572fc1e4aeb64076c3f215bd8e34c9a8c21bddfcc56c7d35eeaa89bbf598294de729f6aad7c8f9238b5501e3096bcfd693e82369155fbe7ec3ca7b55d390251813c5abcd9dfa9b190b5915555cc996dc9afbe5c9c3c4af4f0e89bcbe7aa0014e408b3b6f4ea3d027ced2ebe9ea48adfc895b560c820238d268e4398888f7227647284527074aeca0a5d0bf2f90c1b2634e886feede065e4e3c1da86656ad51262271e5dda828c153f50f3f4e9cf05d8f5fa83656fe1d14d5ddae94b8dcd259e4a52ba4dd166c5a3cecb6f4b302487fef6526d338e06d6efc2438058bd24e67764136da38ba2c1353b2eccb2613f22f7c3e4fb5ed0d32b0b2cafce2617d93b7a06727bc2a8fb0ddb83deda33ad54d4ec169d4860211f0a0c62468666681154629e040ea67c6c938b61a25e046955d309652c475a81095c48ddfeab8fc9beac71557241e0d926e3d4681c72517a2e597396f8da7fc9b071d24540d80d61e04b336547ff51c4ebc8195c7b98a5bd7227e34cf148d802474ffd10900e0f3f5b0143d723a1a0ab63dd6226fe3f4eff7a188ce8c9f015e5e36af933d8645668391a10bd2070fd2e7460ca9888804242ee961b649153b601bb8cb301720aeba6ac9d06aa60915980470d3c138cd3a6fe5dd4c596e9c7b755c2d20a44b50ba41481bda9e938d3f4c71c359040c4a4e71ed02450e556254d3dd4f65f0d14c746049d27296d729da72013101535f8decbbf396e4292d34323a6e3646c39166f3a142f023f2f7243e971bc6b262c4ad0448fef3e5e9a28470f5915bc229ebde9a4dc1ff4cc767210a84ec974836f492d8938cb02a9df6e0f033075ed674e42331f5170d4b3e272ba1c45b274924f0aeb4dfb4aa89a7c6a367ae5b6cb7fe5beb786772d89569e6e78cdd8dd70f2b72ea09a53599eb72cef38c2f972b53d49d8807c762c069d6e2e978925a475a1f00fbc10ecea280b8e995fdc9c20071667158cd31a752f38a072632c2f28a26c3d8681e547f9602d785761575b94f6d00033653965fe04c05772dda70d3eade29124dce33f6d63d4f3df933e0f90b3fa2b04bfaeccee36af440c2c74ef2a25c65cc5b0547c08342b4ae5c9a51ffa717b04184601a2047a9a71721d4e6d2caed38c92cf684a68193467d08f9ad2324b87a42166159c196db4444815460c84be7793b8a3b2887695dd09fe09b99695a5ba37bca0e325e88e375b72fe08f74b92a6d60f3602bea39d4cb73f4a1f5488d994442e9bbc8a73400fd13895cddae0039f0bbaf729d5c0d639ed5acc10b7ef8db0133acd0e10b4f4cbcb72908b98e9840891073946c88164a5ea9864fdb0dfaeadc3e7b0344da959ad6f72b8a31b768b7f37b4601424986491e38630ac5b4ba536d4a7c9e9e99d30c091667349955c5ba73de817a271b12acbef81476f83a8324f4e5f3367270bbedc8f2fe2424c60b48ca70576ee4fa4c2902e0df65b5df1d93ac58cf5b7ab22c754d92b2caa998765801b6decbecdfba02c5b8f8e12957b5aedbdbff5a4c07c14b11d72e4b5c963a368312ade784df0e4f8b13b73092de55b802335640c8d47156b3f6cef9b288a1cad307a9cd94ef69b5108c306cca985b45e62bb4766a676515f7705148580674b68e7e7c251876acd3ccd0297a66e1ce861f7e33e84aeb8f8ec391832300b186e436511596d664bdbd8ab370080c42ba857a07dd7ae06210de3b7723203bfc0527dde48d76f2c6742cb9d8834605bc33593decc95d9b572f4154672255f6197531b8a0ce88b4e4d2201eddc328eea77a2f790c85db210df227af17232fbef7a57079753eb1dc9b94352011ed43b85de2c4698695ae42805e2b241109090f71ed1954c044b2dbdeab1487ee990fbc8e62a3cdde11c9d62ada5458b72814c75b10d2473e5c3464f02b1f34e5778b7551971b0c9662e1eb8a470209f25a2eea9f9ba61cbb28b1fb0e9e46c3e5c3e1a5bb16d36af693c4865d894985028d7b88002c4c096084cb591e6f166a3972bdced75546f059491f5ce6c59c9fa72196cfdace0fed1216b7bb8fe2dc210fdc6d9c8482b68d0bb259c1e64f0239172771ad548120bd1c1e496c0dd28964d0e724d1f94faf58d00c692b9486527a57276e50c3655bd764eb2c3332798e73f901385a818bb17a36634937d021052ac0db59c94d1b46b1e919c9eb2670e3ba27b9095602437c01dc7e435281d9a1589381c7bf63d6ad70c9c082947c22e338d9300de830ec58e5bfae5ede2438db6027244b6383f9558c1dfc39d82b8a414248359fed04acf60de3fc50d827e7460f37243a1d155b0d54dc2f3e4905adb7d02cff6e95876e80383b97cbb90ce389ea109ebcd646929f9ef0da126f2deb0a94371a5d01f130fac8535dcc8abf75d61c672499b83db07ea51814290c2fba26e7dc7c2fad01ff9267903e616f9df2edf1772d139647e0661eddaca5a8ebf7cdacdc0efd01841710573ebd8a27f3ff78fb550bf63aa70a9a876102f55f72330eb81e8bb07c74c2fe4df5ead84a1e1211f4e39a85604770be46ad072f5788fb803b847d8ba8568c229dda55a4bdacae326097299aad0f7af8a4fe697b0e65513175c4327806372bdaf6b9f5c26f9769b6d4e211b27daba5bf3c20fbaf3c34be1c1ba82c6f0d8d1aaaa8b0ca2e9597c47848b727b69dca9fcfdede707040f463f9eb8a01f3a24506921c9e9593991a7b7b2da1927e8636990980f44aea0c8045bca4d6ee7e8e34b463c43b016166ea4306c7572f92421741bec5f95091ec23d79afcfcfcef128c1579119db74bf1a833573c65822e631b47c077665d62aa184dcfaeba82b2ebaf1abb99973009173328d80a872bb64afb015942e7cf6904a210d507627b3d8e989eb48e565ad3586db973a2572735d3686aca56ff4d9cc3e47c8ea5d3ee1e095ef465616d9a38d2adcc63fbe3039dc65ecfb91b5f46dff7ceffe532f804267169f7594b948a2f1381cac23c3728275be61bbbf3d750eca288aa856eba93ce796ea138fd586711f3b258c57ef729166e24d4031539c03d082a6c74a0de55292fc00bc06f76908d1709a4d7b9c40250f16ec08c0b9c51e661ea642888ba93e4ef8a806970fcef9156f009b14660de2ee7b37326898720c6de4187cb1ff3ab9ebfe8020696cff215929ebf862d672f86a3346214d689a8b4ade068c80a9f6da9b0716015986ff12c99cb328f889729786d4805b0d9852d880ea2ff1d130a0fdc20e67db41df32ed5d7f029a0707302d5762fd84a2a71fe6f4b5dd74c1110a412c36b494ffec6cbb950b3baba62f7237bce056e1d7c13dc001fb21e0e905615cb649f40ead10a006b62189ec2bce72bb07208ed8a6b5cfcea7db997c752fa0e1fe5865726abd9f0ba33838cbaf8b728a075dccad0b42d7a496bf57ed2ac74914fe5090d71c3dc5f9e8164c106f53724719818899e0cded4dcffd217552124779f7d5e60757a94480a0735176fe4a4955a189ffdb87930be7c8131ec91e47d6793706031c05f904def5fc37e9b17f72bf7c00a574a1ca9249f4325c78c8a0e5c6a839f81c71c3398f38f69843c15f6b017482971058c023d59e78d8a8290e4e43f90cdf0df64b4a2fedee473a900472a213cceed174e134c7b492b19794897ac9cdbb9bea69d7c2cfe52e4fb23380720e4e5e22e95f7b365826e69ab161a0f44d295a568adf14879fc32fbf3281e67239ef0504ec73165e91272b525674623f94c9d32193908289533e159a1f59a972ef6a29dfa12e36dfe081d2bdaac220aa776926ee2a1d835770919b4d4d9faf7249be217fda159a80fa9b4d6ca2ee45da810d1d0100b6cc2c275c219e9e3c5461bac776135af2bcca3d1fec39e791f88792103fd6d065f6388831b235b8f3dc72e503fde9b1e5bb72f3e7c1ee724a08f07db1323f9a68cfee5f6a4b8cf4dcc200ed106c9a23ccbd8c42f2336463d96893effa841b617fb06d8a3ead958916753c73380d731ba06b784b8cfa5b12619fae6336df5f08c9f8d5b2e57ad4abfc326c62abb1e6e1840fffcc3365bf2b1995f8025020b359dd0eb7163bc0125c01bd72cacdde6ed04d67805549143b359c529f29cbf349da42501d76529d265fc8f8720dc37a50af2088066b6a88da96311b075f40b28ff53c2cb9cba4dad158d4810896bc767393acd93d4193f0826cae5b82672f0ff7b4fb02522a6bd797077d4b278f9bcdff8b3572db1e9910dddb883e518fe634042e15cf2e5326768b001cce7229650386aaecb7491940ea130226ec227cdf3c189130a9605ce2b5163979ec725ab5061ff700a34088913ad5f90fbe99db709127c56605685863b4bb72d15e726c14ccf2b356e3b9ef3045a148d75b1dae7eddec0adfd1c0f002f305d17f6c72dab1bc51a401670ac54f509794b385ac03bc1bbc158a69bc6cb80859caf0d9393b32acccb8ff559d316b0682c94dde683e8180030c934ff8722100150da9ae7221dc628959f52e7ae6418bc99a2ff2800055c12b6e4f48d42b5ad51ddd33f07269ff1202326c80051bb5e6817c9b4d0059d43aa1b39a9fde9b12bdc0421de1247a6f7d67e77865ca5808246d79dc09719c260d983f1239c6efc15cd835ab3d3b3fa88c5e9d6f23e4b79842ebd026871714894acebd087190f42eecbdea5692727dc96ea561b8ff48d319c615f0182f67aa958dd1b772c7ff66cdda0d4bbe2c72d9830b086bbb0d93489cf6c40d66691243e2338a8d70595fbb7fa7dc80816865e206b4124239d28020a35f14c984a94e5d59982eb7a4c9f1c9686292ce529572e2a145b16714be1a9a5bad0e52b08547c90ae6d141b4c9fa6c36ce33c674e26e4d8d95310ec7655f7fc7c10058d03afea8ff5e32b1c19e1425e61819e39ca5025b664c63943e71a10c6378664bd36eb07c9ed1a39dd8b0128c3e010e1b1860574f93d9b532a802dbf3483494e9b6a824426fbe348a0b6308164d5d257352b70488d981b18650e4a1638a90d4dfd6d552298f90719c526d138ff135da96d28072046ee00fe89a43ad192df9cc129383bc34acea3c97afbc7f634e2c896b148872c170195cc3e7accdb3fecedb537b3c143aa7675e95dd153394e16a8e4c78cb46c10742f3c65825f259e61d1c0281247fe6b8da812f8cbfe48be1ff1555476c72e12c264e5ac10f3283da4f60aa675c0b50e80a5bce827282e1f8820a0c733a72f64bd0c5623eeb4d44b175bda54003b451329ecb56da86642e2af98efb3366551fe46faea3d057c3ae14c452113d6c2ae89ebe360c70ee402cdd347e06ebe67204c4f234a3adeae7ac5121adfc2ff3f00a3daab3edf4cc42dbe5209656c27b4ad689994469dc91a6df791b7717eac800485b78c80b69115ef7e7bf0d1c00211b9a91a7307cd62abd035242152bda6a9283a966a62d912bc2d8975099b11f63721bb1267ef5d6333308e13a6c4ab29321375036f0a042a667e0385bcc9599d25e86df567406ba952dd3b9bd6898b0b8bf8adaca6ee3307e06331b657c10f6f433243c3a3db9d785bdc062b0eb3ff1a63c33bf10dc5448546c29f4c5472eaff7697be1c4f5e9e3f01ae3b686c817ebf7dcc979bda22e0bfb8e60c7489557b8fb72cc08aa8e361be6bcdb57089ea3bb0aba63976ccffbdafa3aa89b84f9fc010f676ed4baddd273b91f488e1c1b675b54d5daf26289bf09da8c8d77acaf90d2763101e813f2b6cbe751fea4f5ea5b12817c0113c389ee4bf3818e125dbdd1aadb72044f4fd7edf5dbfe02e2a76c671dc4c8f8b89d76738a24b3540be82e0d161072d712a689deb60e8e8e70c57fb11e75a51810fd20b3b5b67dd5c0613a2ad2792e660df21e53f4a2afdbe77535ae9ce6370be43eb8b1e02e19f20f3816290234722a24f9702f8ef765d4f554c9080abc12e18749f6b2b9061f4c8536df5e3d527291c7eedb04b2b5ac6b9d62c13bb968007d59b78958b9ef9d15ffd65c5129cf72890524bfe510c07d272ef7c86b57f85cf409ff660d09a89754ee3eb5c21ffa724cb3cebdb38d07e78069a3fdcf0c2132648c99504710022801feab3807d76959a507dec73eddc0ab30d4466104e8876e1854751aa88170c453753a258d010272ab51e7601c47c94182fb6a569d5f57afddee2d7601a001fa4bc7135dea80fd7219682fa38022aec231cf1edaefe8440ea2c9407b7895c36295f84cd61f9e9272fed1e2ccb4fa4b5895d610661bc00e37b4cad380ed64d426a44b3e065b8919725078d646eb72594418e782036429af40c4abe3c95adde5e0ca1b23884df8a8278a39ea2cc18bd6d8821f1355737d6aca8d2e50780bf1e2e08fc2edfd9cb98650eecd7f9c8ed8ac543d24f093b5b255ffc789bcaf58f9e757e093de1378d03472d34466c5c1162348b4326c61a9aa3eb010276740facc7dcfc17a0b98bf08af72e35273fdf9d2cb3bb7604581c978c5cfa4bd71ae870acd2263431ff7b41f3c7260b68ceb6708363a24d3c30b4a620377fee35208b19c9d6198cc903d1648744ff224923dfd399069d476b67331e9d913550b7d0b1a5a03de4c3b80ee49145772b215333f783d9bda481c97687e6d9efa5798214f0fbb754cab2a25ae6546d7725e2f58c6cb9bf6885191f8d6f14fd744044283378eb46bf006eb87aef6d2fd4c28ec0398cc7341bb07f2a4e9656702826d17cf0a76903118939bcbf43615007264eef15ca2c4ed5988e1316ef2a8854b1b279db87ce8d9e384fe8e1726af8047ef5764ebf4c9a53983cc130b4187804f78ae204cd5947da3a993441b631e55073776329ec28c300ca8885ca60612804e95926d9989e83b1ff3503bbe6afb0102d76aa1a913a030889636007fed1b9584fbc1990807dc3140a8fb42b14b81545a7ba2308c9d7f49ab1f1cdd9d1e153659d48a6b1a28dca36b921555ab84f9757288797576a1df67aa06d28169ba875a537449d19681d716cdcfb0dcce99ddf8727a31d47972e7f35894676b1ce8eebcba33a24d7f352819dc0406262091d07e72f967fa6434f8128c7bd60205e56608c57d496e7fd3b4f7ee0c31fa0bfd411c36811639f0b49fff4071554dc3402df2134b76d76151dde868defe304ac9b002728a10e8a15b40a397cbfd3a94ab0efe54840111e4a8cf7c8199d93a495a00aa729d64cafdbb7da0adfb47745e644f0f1996e7216cdd707069674b285fb3f035729d725c2d6af5f94c4532a01edf6df9b2c55d543c9909f1efa0ddbcbb7e625f72deee55a185f53871cf76e3d272e886ae83b733040c5007d72dc61994d00dbe660945d8e22c2c55ab82aa12cac21b4eb66a4f4456afdb37181901e0fc96cc186601d55d28723e1112219aa62b4d6b0502bf6f9c28de71736a17cfc872cf45a71aa32396c8d546a8546e384b562c13e3f0b47646a9b1a4bb395c53040643208f72e8a55fdf530db3422c85d53fb7758b03521e5c7a93df39b7ea6b7309a43544604afbde786139a85d8cdb7ad429d94766fab7ea14713ff385e57463b7e7c59172302ecc3ca519684a6d0c2727ad4b46b398ace17ebda574266233812c00c4e0726edd4517c15496f91f6e318d8ab023455ba72c35ffb1159c9781fd578fd58472acf4f5f8c7f668e93afa2557c95418cffd8b7c019181cf3349ff1acb36ead5426e5264a6b9177129e9c47451e2cb7cccf7bf71b1df1d0bb13efbcd4ae722a6724a0f8f15fae5402e774b16d02e205202c31885679e4bd7d8c81f88ab4ab098721e92df87722c8d8c98f6f9fc354aae02fc5f696adb0bfcbbd66ad11afb9b3c72447235c95cb57241592bdd23e5ec00fb0f13c386db632f8192bee5c168edab72f74b83b4ca74c795d3d23af79abdd11a93fec86e5ffc8f3bfaa0fcb6010307727961d546f12ae197d204d60b7c4035ea9f38f65800c1805744eb6ef3cd177f726253bd0d2f31d2f9fc9a8e90c3fba868f394227a13874f599ba303f4ed6ec872a6d8b20941e9ecaa720143761c77dc23f4741547d3dce586c764ac640f0a51474e65f78b800d88f37f4867d61fc0e51cb14975f84b9701ecf69b3dbe3018aa1117aee00bf0e92a814c3315c59fd07d503877a96c2d87220afd396cecf161157262149d3f5f69f4f67af2072e63b25369ab5770700dad4053f098fb0dbbd2e948aa6b4effff30860cb61507e546bffb1381a1ae42cd1797af2765428c0f5d86723ad37176d720d21cd31d549a1fbffdadcf28ce6ede18a66bb2dc1c28740aae725e4099d30f7cbd251fd40c21f4c7bee612aa29b2e3ae4a15ddbb6040a45e72723a7f2e6f45aa254689c2b988c7d072c9a2da8ca406dd92c97480a1d21f7a56727f85baa02f764ddd98df34e8b53f4ea29f8e8c0d80993a2be025c24a2f6792486ff2fce752f7dddf466653c9d27ea5736871081dedecc60d45c8dfada29dbf72abd79691133fff84eb814f5b953224af54072ba507be25ba2765e28870e88d39d64453a6ea21bad8fc6222cb7f7b1452973db999060c21c3c043ba95efac75544ed10f25d4879e1cacac39fed27edb4aa9a494a72b632ac0520e869b231e904e22d6fe709e359e7482707f406dcd92128b0f6d9b3b11cab13e2af00fca4a27720be6548137e34dcf8ce435f20410a562028e0a120cf336484f744a18994bb772c199455991ac52e49b76eba592d77b482d022d9d998522f7e5817a891512e25933a6249e49db6fc709583a37a9ada3d6a1220394168551689635062ed7c3b872ed3de271becc4590e4f11a8a29220e77edb0145552c265b33347784f97de0a72c694d5dc4df7a0067db6acb1fe8dac55a244cf57690cdc616429278bb756694c553a514b3c4c8c5b2a65ebab461439b2d3a9605e839dfa53d9edc646d2fc20077fd31e0edf24d61d6a44894827efb5f6a70922371f3eebf5c9b17e18e089d472446f707884ad675dbfcb06daeb5db840bcf3039e8d5489563f6914d6b9f7c5726ade6d48694101fec23e6962e3b0838cb8dcb9368924e09a942eb25cb02ca1725d35a1f97e7a9b3b90a757b76578d6510eddfb06428368f3079cd8b005152f7201a409f5df31e47cb5eee72d3761032afb6d980c2ceda3dbbc5ec60bfea8fc72d841b88516ff8a515e5264356b45255517a3a2c50ecd602618b95c0273fef85680c22c6ce18f2269517efe7d04de533d1f2d65039c5dd3e69a26ebd053bdf072ef8435da4c9b2e4a41588afad6eaf621587534e0608bf796db83b8d9b2f2c372a215a2c4af406d3bacde7145c28ce1c9e786348d87bf5b64f3eb5292437521539ab4f833c079a8cf7440db10eb064dc372afe0bba5d120759c5163c3c2ed9a581b8c4b5234eec412aaeda5ef955b0ccc4aeaf702a2768a9c724eee3bad0802499d0ea78cd3372a87e75b0ac7a67a9cc5eda22d89eac03482e8543c4986d9da6875b80e42a692f829e64216844178d3276031d9d74984cf5063f4b8c7d06a4b0e398f5c6714847e8e2cf3631c478cbde977a4e944e144f7509ec254e8d5ecab06bc62e88c59153c4c28f41c5c85aa32ff5dde43b6a81c8ce07a49e06b5d6204720d022316554f38daf1f704ba51fcaecfbb8ad3c9243ce160d794094d462e347205a024fffd7ab3da51d3e0fe9c5cfb4a9358fbef50ba4dc414af5c87a1c0976ad6bae7dc8ab75351f7a3c5bfec5077651758e3d6f751799eeb0f54064c2f2e58648142a884cad914cfd03173c58bb90ce29cf36e11e1b3eeec0f86c01c5c877239e77c95506985fdd848fe83ffe7464ceacb835686220f768078385c1d7f307222cfcb675cd381d764c1725f7c8d8dfcf4954f133a39d11edbf6706879191d7263fb832ceae4ce159f714ab23c331c7a8bf5cef83f505db4db93f51599f82162e1212f55a39a5ab838194b8fe1b99eb61dd08963ed5863896492d94de812f1536f6201a53a3938068a87bb6c3a45f5828d05dbee3a055fc02b18eae8c7eb6f7219dc71356dcc58f6a04b99024a23dd58f7b104752e6ff6d1de5e4368fc59dc72d48dbfb99b594780108b1f728bccf243a169e087db6e8561e4c21540993f90724af6e8d1491cc27a2dfd4ad2b22eac897ebe0245ae893a9837699bf212cc8621e0c61f84c85070ac43bece679dbb35fecb8659906120d5d07d5dece761fe3e72dc09d2f3d3b0ed415a0338939c342755bc722e82920edb6486ffc1a8991e3f7212344294ebc9ad80b98239f2afd228824387c751980d2ec6f3ca7e50d4edc359319224b0391fde558a2e8b20c1eb7a796d23366717d4f47347689984e65688720d963c096f63a2e8c93e2850f8913787389ac25675e2e049426c7487ee71af72185a47351c12f2b5e69cd9b7ff7c4859680b5fb9cbcdd265eea7f3157f47d3314dc0640af82779935fb01bee71086f0c76cda1e7167e42988b99608328f44a6482126d97337ff2e42fcee878df838f7abf9061f99b0399d77264e7515006b62f4f4a8ecf9e03c1bfbe72d4fe83cbfbe360bed0fcf9de1ad5eef048110e96196e9177102cb5049c6145ebb92159e79835cdccfd01bb40bd86e7b5223638c2f4724f3d07d5a61d423dfe815035a3bf7f8a3fa76c6390b7fce3c75a99bec365425b258f294cb11d7c758105de577e8d0e3dbe4ddc6b5247a5d8e470ecee99c7fc2813aad3328e105c5a64be039e08042c163423fccc9c304eeb8c62cadecaa1d772c9b677c5adda34045818356887aa3f2d770f33487e9558d8a3d84380b2833828a593e59f0c5466d7f8777b6189588253e6617d22aecdbc2f39a968b081493e72e69f00820abee967d56c56c367b24de9a808eca1e2706e3d45b9e35ff0710845145d7be5746468844fef38a8b25c95e52bf1a0249792c9812d72fc2caab345007d22f20019307bc5785efd0f3e0190ae7b16f60c74d27da1d8ced8a3d2dad11b792c32d95db183825580f6c40ac5fa52034d2328bdf630a7aab75572bd19fe58188ea195e9fc7c54dfaa1dc3670bf46232c5f3861948ad7cb217072bb99a3625998b120846859e12d6a2d83d42f73736dd91d7b99a2c724dd04301661dc5f9044f12931ecdaa9d34cc8d39a6bf38fbb44a8578c7a4584e5221ff8843eb088542bc99fe2ed8d3d3a9c5b999691086514d7ffceb9ab3ef48d3aad20c58937de659809aa06c7aa51c4568c064619d34b5342f4618a1a71a0824519a582e42bcd22652fc6c19b86c722134bcdb1d7fad1f83297b19b3460470710275333b4f972010eebbf31821b3d5fc80ee85ac1ffab93b2f7bf95ba43de1eafe0de6b77155b2724f66693766f42a563535e77bb58ee7119e35af0cfb7f55d77389fdefafe241724f9182484d764b4cf4b67d4e0895d0591fafce9cfbd60441f7f18ab6ecdc0b0da2be46ad4505445731778439762ad210a29a2c3a7d1fa3540df262c5630a77722e5610f466ef04e27c169648061aa7726ba076b3e54d5c539923321454e0bc0cf879f0883a0fce583e0b9f50c9cfc8e35a4e8f3a121a132aa19029acb241d37266ac741c09aeddad382ff448c0910f0cb4564a10ae5fd90c5396861d16ac581496f0a15b74d360df9e8ecae514445c39c6dce19f233359f6b249f26128721635ac219da509170ef9a322fb59c65151e1e0e67bb5b3f641d9248d08108ed63f72f5dd9bb749669632b03c7e4579538395064110a0a7ddb6a7572515a6e49e78728aba749e2f5af788691c1000b85534c5a48dcc3cfcb2c8a6334a010e2b763d6c5949298f88c3b0c56492be64c13782bfe012ddc5e1b25f00dcf2f61c9abb3b72cf3f3407401533f3d2d33a0a6d2086dd8462336481037c3c3952e5eb9bca005f0b3206b2d52bdcb634ac626872c3d1b5567270afd09de90d7cdc8bd2e1992f7252b2c5c9e0e1a964c976baaa84bca911103987020ecc27831aa3067ad931e97286cfe31ec2b07ba57de927090b1821fc6c90c336592ee9e7a43e17d925cd316566105916cec04aac6d2add6fc9ac3c4f87aed13d46f9763d6edef6918f8d9b7277b456901bcd5d84df4163773c2f29fe395ca22e2fcbf09b7597337068c49a6f4088edd5937ea026e799b347b276dda667d3d2ccb8dc2645ddcbb1587247921f72f597cac87f673baeef3393593c7a106005508a709f997ac5cfdb24c25fb60ab31434e2715fd92060d18d8e745554f3c22181fe590fa6d9c8da285197f11b722928e2184f4f510cdb5544308fcaef49a5aaf306cfd851b4d90650f91f38c872bc2a2d02bbec425aaaf616985144959c5026d68728130b178dfd4d9b73d66f72438c2c6f243253dae24b844a0ab200788c3f527e8054407b4a971964118b8b72581922206ca1089d200ad7e3313dbef96c9669b242ec7968264538c2d99dfb72bb5da80c3b694eef27743fd9e1ad6d61e2236e9f83aecc9d4dac45dd8424c72ffc82cf8994569d7f7ccb099e53294b72935b612df73dddee0cae57569c0e7a7256546a992f5829c9f160fb95b3ad7f14f852e827e763f5d81a4a573d486af672cb5a1312c8f941312ee0d6fa89cb12ecd196cd1196c5cf87beccfca0965af872062da106f5503d604fb0d1602c75afdf83fcdc52cb24f5ff1561d7b0ce5a8c7230318df353bca91e5006a9696b28a4be123aa82a03f839ce91ba4491db21fc7257820538af5d2694a829f75262c8cc68d4efa57dd3602f5c6ac4986f15e74c5d0ae8339537d4d66f8ed7ca9a43208459723f104fc4942446f9ae3b479bd05e39520ffb3cea1b9b756ce7b62bfeb1e40002bd0830fb4224400bab8ed3eaba28725624eec614be7b384e4ef86f595eb55ef5bbc526827dd2f81f8db1f0b5e80f1f9e8553b8d9e526f50c4e1f2d98d35eb1f534cf010ce152e8fa3d706df897192ecdb011737bbc2518b5b84c4f9e7ef568a7e9e176e23cb9756ef0a7d4ff2d134585aa37cbf1b4f58630d2f1ded5e53933407a6d9defd7b58a8641358efcd7e3228a6361dfc17132213cdb8b9c1d6fcd248bebdc281c661c6486f1f0751efe7172259e9614298acfc601ee8947f7f674feb200e511a550523d67b3af2c45cc385740869e8fa1fe7b456dd5d28f307c8d847313bbdea51d297f18cbb3b66cb02d09379a7eebe977c868252d9a0a9cda3b343cc922efc4c1cf710f2768c38e64662224e52177db3fdc270b9eb0363b2c52b11eda50d6be947f877f757e7f2dc2c372643db0768e038874bd7c77f0a779bd0beb966771f4409c44355330a0b906a62a7ec6f7534be6fcc501c879cd701aba1091e538b6acbd0433bf615afb5dce62728dd7997af51b03804f3c19104bc091647130d070f67034c1b065d00912ee7872e1492af6e73348827428864d6ae50f0a59bb8787d998144caf9bba405b4be80338aff8e167a07915eb62462f4b2b5d4349acddc50f44c126b5da50dce9202562e76c7554bfc3e828b8e766e998754060df3de7ca074ec25958ba138b869a417274902a7d589fb646ab40831ed35f8f33d2d518fe5f3c3eb6e40862a642ab7f02de3d70f9d25777bd694d22ccad5097545bd959532949b750176e9d3e6c987933a5918eec33018635d5d757944101f3a7104c5a2fee5c0fd0f60d3984cad4a7133639b9d7132bdc5840081157699ac4ea82f53f43cb7f5ceffba1094daef004720e6be4e23345413969bfaddea36ff32d640f4490eb42f589e3dd3412d7f95d720e5432a2f089294e43b1f0e6b48d0ff6792fea011cbb29aabf093dc5e00560685853d62879296b0f9acdedbf9ac229d68de2a7425a5144c6d7b33d10d0d5e709bc63bbf695ec9153618e3a107cc273880f18023064c45568e5e2e6f0d25d1872d067750bbcdfff2ec667fa946b36d196600707dcc059af4610af9733007ec772b9735170b3c050620dd0ce967581d69c102a34df790b8c597a4c992dc43c504efff254628b283cc60519eca22afde72bfaecbe61757e89bc1a1f57f81e593c06a02efae62d634c175628e176d80631397983107943bbfb2b1bbfbafad00b50727e2010eb35ac7d25a367768691e6c1777103199c8ca67a708a05a4733b90c6721429d95c24f2d19002e083d04b353aecd31825628f004fd3565756d444bc75364c33c4f0068dc451c7b08c24bbfb99f4d138881d83bcd2304023835f543b7b721642f2a3944cd4ecf34e88fcac0c5118e16cb9e50011350cd6ef1198df7fb205b8b4e578243e3e944c9a831168a9b56bbe096235d8f5830cd88cfc29bc4822724be062e9c750dc1c5288fab527f0ccf423af9c1325101e43751fd85f48013b72460d76161e0f83bd5fcac81a58631e8aabcbc19e48e816b20236862d69b43772a10ad5e978845ad05ca59813fe2ea33c12ee983e152b799df2eb72acd6c8a172791bdaaedffe4e2afa77d8ef9e37c5a3801fc59696d84893cf33a8e075e1d172ae6b764bed2a5a990ba2c5cf403c0ce12be1919dd5da5f775c16b31919298172af92e138edd0441f5751894ef6a9aa75cbc9f7677d2c49a2f8f16e54a9148f21e04915f015b27adeefb31366bf532b6b47ab855d78a8d6687dbc9f9942e4e7720d676a4a2a779faf1abc439b4184150084ca5b1f41146f8ed63109074dfd1372318a189a650d058b93e7697377a93c3cda4de8aaa962941fff444328e1898b724709ca0cc049eabaa78f0d1da4664d98e488d6d534981950625a70cdcb62ff72a509265c0399a3ecc9d33efeba0e945f9a1416e1f764c45e1c843137e65c7300766e3264b7be3542ca6aa335ba79d639484c5be6fd9bf8cd48d3c0f91e5e2f72e1a8c8739602ff49c2dc8adee85bcb6f86a90cc0b97828915b551a39abbcb572a16a43154ae59cf437e097eb18ea6f4ac671c1cab17f05e65fa3db14ac5b437227f790cb2c676d9036874b7ee05fff917006cea630565c87b2e456afc5fbcb52fdcb8f7b0beb82ac5575782ad0f5ed8fa54c3e5575a5f68c4031577049fc2c7224b0f31fe24502554fd6a8da88d4093169f1c068609b1569962dc0ad7fd84772af517e0bb194d988b4607e136d3bdffba1fdc1da75a7cd15bf3033032bca6d721ec1789abfa52d0f453db0f7d7e3edcc1302a02dbdfdaa130442f81d23bff0729bc32763cf3910b2d4379114dd94b253f2925b1e0f5aefd72a460ac3823fd14f70db2bc080bde59443124688a9cb2bbf27e7a66a7aa7b149e7564629af83e133c7dcc2fc12ab16fc08c127d80a42f9b48e439fbfef4e09f0004bf55dfcf29372d8da5f3d8a54b08e72eaf73229e6922ac46c376ee14ae63c33b9577aece48a72ec5ed79f6557614f421a9efc1f2303bb1503913880b2cfa99ee063d3902dd8729ed7ca45903d496e42d7424f6a7896549e0d8ee580cba92dbbd8bd249d8e8f3a66b18f6a29b4e2db9530c391b978208ebed2a401f5282d7277de18fb7f9a5a37b4813b473c625368be74a45e59440f929a28ae5dfb1ccf975e3750987eebeb0d8a173cac5213c55dafb10101e2ade85ac20428f20882a940a1d23b1834d37b342c3e6235682e443c5855a24bebe56441ec6c661503fd32c3ce209fcd7e41c864aca338dfab83bfd34a0d160274d6d6dc6a176a345d303a6d21ef9d7032d9ad4f18ab94a1706ffc185b6b3c933cf82394b2034619ca138b24e02ee1ac186df872f3cadfa1f84e550710d25bcbe009f992735eb479b0f036ae4a4b5edc57bd4572399e0522831b3673d53e76b8c8525e6e0009481d7fe3d66f467b2d0cf404b3723dd6a4c06d9169179c2c73728cb146a8c46d990428757bcb2491e711a4cded729d061cc7242c15235b63b52a26389508591bbba6b7f9fa5a52bba6e8d21d8172876e343085dac7141017ae5b0c9057dfa7b4ee806cd0d66c98ecf97b97f0f32cb6ae23877608c6fabd9bca3182bbf4cda8d50fbbc488198bc5eb026ebf24fa6997314765a7fe558a5f7f7cb65e45cb9cc4a4f0683b6a9b039503d7b5ddc4a0729c2327c03e859c6c9115590132be667468243baadd7114e56a0df6124cae42727c0d383191314ae10ca9bc50178b738371925632becce5a91b3406b3400c903dce0bc782d1f01d11bd99309c646b371ad2a2a447d7b7c0b03bd96bd1f385b1613da98701cb175f7f1a0a06ee91ac7513e6fade1016feec751d5a744d99abdd729e1d36206c34505b7366dc44eaa18836b56bfe651877a30e3317215289b0624e7ae1f920398b225df8a8329150d6dbd0dc09324b3eb2cf54b619eb79b02d604bc95a73d1ca39afde6c267ce19906fb4962d247fdc7db5010ef250460db515872fc003aa94dbb586dc100a35bfd4e32fa6f38e887be3810e75b60a47045c34c72194aced52dad71d7ec9d844dbc3cfc7e79f28ba2317021e85bb76efe88f5c70403cd7a9ab12fb60ebf2be54e5900b6c082d6d30c44a2c40fa69ae7dba20c3372985c4e0ffd2aa600fc238705a05568df6afb554f34c852fe3492fbbe49753672ba368278752e952864569bc1897eb9d115228108dae5785378e99e2e1d582c7279826c8bb663a4e73f0fbfeb03e36274c25821ad0fb625c1035197e3740d3772b11f45cedf007b608957e8cf834224c390077012e7f046d9925a3348beedc972cf19c5b71e162369a500598cab6c898b179f77e6c332f9924551e39d94343f72f319120baa67217f18445ed7abae9ff2f33ddfd13e92f58b1e9955551bceac72a59ce7f841963bb907167d682e239ea937733d4d3de82a942a3deae666157572883219fd529279f8a5db386e526391289ffabea4af53b78d99bb2c0df0dcc30cfea9ec97590945c5882ff9b8e989dc62b34fef15e428b35821443e07fbfd3772018233d27e064a08097fd9b7c76a9a738bfbef7de4530cbc00daed9db9d34b72e4add853ff411a0cd67e8add1776501d4db72084c3aa42eb62c119e9b026ba2cdadd6eb19055d0b910637e8a006ae6ac45cfb0f6e14435fea8a1f9e4e85ab456dd405b61c33e7f1078bcd59a3e40796e147583c10753edbfcdf52a6330486c72563cad748d99dae4a138a813331546f6851f6d926374ab85b8cacd71c60b5c5f9dc4dfe99a6a5670d0dd3ffef4dd3c5123233ca1f33b27698fffc7b2ea9cbb5e337760a1a9cf5a5c5f94df6a27acd3fbc6083241e073f6f8e3217013a60cf87264e35153ed8d90f3a1aa4c7cbfb6895e5447091b6ca1cd0041de8a2baa80db72dc26bacce2bb2833aa7ed6893cc6237c599a8814df30c3a8b22b8d5566bdfd72ee1b87a626ace0d7813a77c4ff08c4f2e224f45c0e011540052af1027727557226875c1257dd5c387c1dba240e22a8446cf7e754fc82ae22572674f9d08f03727facb76243a4cb180769554cc62d62cc9e5dd454a73d3d1e2c4fbf48d60ac672d5c76cc8a133d8e2a143e0c06fe9203c9c7a6ae292034c2aff6fa2065c930c2deb9ec9d590ff37d28b251b2d824c981526b0b709a33a96f56f8edb9547134a72b1604fc0d84633adff04447cfa407ccb542c886004740f603b18319197bb11413db525cbadcf5e380da098e0852b9c61bffdea37f6d303162efcd898d3d7f970fb95bd6e712d67b934b856a1635214fb7fc755aa0fecc5e46db43b4cab8ad27255c0276d98dc926e0053b90aaa41ee351853c0e42845fcb6881e6c563ed0cc32b6432c8cc9106ba44257b4a5e406f908edf103ae0474e13184feaca97027fc72982df25829f64fb27083d44c6d2af42ccea43ae23f6a5a2fd7097f45b59a09726247bb7a99cb44c0528c9ca9e59bcc8d59754460b5f4cf338042ab193f34e57299e4a5856fe449a6002def3f5d0497a7cf8b786b059ea90948509eeb4e5c9c7250f5476f4212a39fcb00058c4791a4fcbc354ff6209a9c0fe184782cb5781c51e4e879e8be10325e479d21eda93a32d038c0b3a5f2d5dde8c34d849ee459c76e367225989921de09693dd270696d0a06cc456a2a9dcb921754d183c9ff193261f09169f54041d0785e11fd94e03ae638ae89e2d746fe0fa3d2033f5b6c59a5726f2a8ab729cffab3dc28686f2a4d87a0e60637bf56d9e7f9ce8e1b791ae42772a7758a229a131114385fa46a52ea7e5ead09c74ffa9ec17fd60d59a5659e723661acea20e240fe2d266e5974139d29976f356f68de64eb17dcde6eaad68f867220c497a226747f542eede15e73ab723a0cf3e71c328dac80e398b67bcc735f72490ee69b82fe9c8de4144c543bafe4cccf6f94dac5409e8a02f9c71cc418627288bff711264809c196b4d150b78b51ce27559214672fc4a35b7a33d0958ab67238ad0c9a264581f74a205e4a71d771b5091b168bd63f9f4ae3e6944a234e8b720e50ba351ef010edb335a053d3469a94569fe0bc27f8b7d26ff5688af8f0b058c91436dacc8712925ed6404516e9b0fac079b350563a260da80ba666bf24af1b51645a2e3024abc65d1e297b2dd03ce1e9895c2a7f63967495ed90f8b78cf3729936286ba2e156cee907e59c8551a0ffc28017878df4292ae4b8d14723027272bf9d8368ff2d25c9d669948faa860815c0a5230baee45866d8e9e8851db5ca38e492eb85242929fa36c382ccc07cfd93b09c2adc337129c76d60234f04601b72bc1848c9ee060308cfc5d080d365f7a5b8acce992fc6e4c27c623c95abc9481da76aaa7447ebc292f59049db8e786fdf41435788ef3d39a07012b5e93274b612c0612598d7f3d756cabcbc3a0c1e6c03a740593bb412980e31a2fd44a816b8727e46da62b10046fd71da0699990e697cbffcd5f98d7af6d270bc47deeebd6f727d29e6ac69f03f7ac3b39faf4e3e0f7457a49f3c04247c88014da8e01c45853ecf43ab38d20dd0f2abf187a5e6f0fa4123f4befd995268feab624a9f2f85027239fb357044fedd20f84c25d74c1cd83934f6bde5a29a2c713a63a5acb8a7ea72fe8dd792074cf3a56965042b83a4c3ed0f685da197f87c1f19091b08682055450214a762baa5395fc416e9aa2b4087b7e469e4f501baa72b83aef2eae6c2ed17f1b8902ae610125599aeedbc796190c0cdf380b234e18d12b0975efe2449c672b06a5bee0d302421bd1c88cc89d1f6d260a6519b036f29f42d4105d38dbde734e7bfa9b863a3547a494aae171a438863711857f8d5c66845b6fb32d06db30072422d13a94ecf9d946ced497817d760954b8bbf6120260781afc274fd56e0077229fa1bd85d0ba8c821abeb2201a1e10c5c4bf5dbf4fe653d548cde4be6dff95a03e0b08b63486a812dc9b97e576950bb8de5b28c6c94d3e56f43f0a96b4b1972fe168d109cb4baea0f5c153e793793ee177a28062977492b22238a3594c88750146daedfc9adbdc30dde3e8191e53978d618df473e9844c2b829b1a96fb3fe406156e8678eceae32e622017f8451630fc89b885c165cef1287954df4eb77457238025ac8afe6616aba1ee3f183a6aa0d95f8ac8f32c12d872d6e9a97359639726401a65d5a6532a21934b41dc8f4f425ff0ddd19fdc056edac4f99e78a310a7263d71c0069bef2a9c57ebcfffee4187d4caa35a38b86e708d97e077d3b596472e2a89697b0fbec1050bd807ffc400162e347d136e1bd52f3c06464609f4bad31020ce2781fbb9b48b83bdbb46fd80b473ae2821ae1f5faf65e5663c1cf347f4b60ce36fbb1e767785cff7bc294c62d27474673b874ee4ae884d2e1a810f3b73be2ce41f51c24a2d2ec9c9264f98aa2aa7990a44fb8285a1d85f38e6bd901d147db0510eae1b24a3e0c2dbe80aa4f50753957d9a686d1b8ee7792f98d4a43f70d07b4fbce9172e3f743cba875ff4bb3fc3110ebe390381dad8b927bf2a571f472d17ce1cfeb2a189f5238ff75df130425381f4abecd469bd7781cf7b9d014427283cdfeea374e7c51ebac548985b27141e8d5f70caa690b1f1bc79e81b46f4611b43ae19d2e406ee10c80348f187d5be2ff91322a7fc6e0b0a6fcb95568a5552f0f849ae972c7f1ceb3a3c2376f6e20db393e4a11443f004ff49936abce285772568848907774f38ad4b18d97979e2d4ed58f053c8ea5b926b1b989c621e2c872d41f65c8be1f054c0c3285afac7fdcdc05a1540d3886f079ed32e3e034ba48722df739024dfc2251e6985ded7022420598370e57ec7c596ba61b323f15d11d4a3759d99599637264c54838ed1231f790c23a02c4a094e2ca2a6f1213f28dcd72334cb463f61e1753602dbf527611c94cab390660d1feb2105f623f6d38f0ab72c52a77878c9f15117cdf78d24e851ec300bcebacfc9fb15604596ba91ce54b725197c46aea93d583bcdd0694c152be9842567bcde94df7f18b6ea61bee3017421c4e2aaca65c614944198d9ee98ef82c86d6d5d0022e5bf8b8d8f48185183272fbd8a16da1b53077929d62650738e6d3ab471bf7349608a79cad9982cc92ec7246c4b8b8c6010c128861599fc40160659c19a3441602dd8d624f60716d945272faeb658fbfb7c260a3e5804acd78f113aada03d988439c7f8bf5d2bca4c0d71d8f4b0e8a8124e7cff2dc3de16386e8a0113dc5812263ad9c9e85dcce09e0fa167ee21518526798b715886dd4ac4ae50daa6ed6ea2c5c55ed4e8fce211ebe4b72b678720def9e3ae8189a709eb043897e073eb9bb73d68ca47d0b45e0c0324c0e13811a68d16c3cd48add5272034959788045ef93a5386a02e28764d6547cee72df5b6864d71a325aa5c6dcf9ec208797e524026109300954170224b08edab57294d4e131944df64f70094d5d3bc3e1999371938f426db2a5fbfd53c4297122649f5c2037dd0daccbb4dc43b7b2b6e745fff0b549574428c57355feca39540150672eeb0693899a81e6da1a5d103d49105e26f5a13c6c1929f810322ba429c6723fbc6bb4706d7f61b922fda681b433152f77d7b67e334de5f1ac85d0a92157726964658c84a9af66da2d292b5c11b735d8ce7ddd7e3c6eaac2f1df84e8c4c7481cd6a78fa12d9821415be1d1c353e1f2b1deacfab8ec1c4056352d8603680a65a855c405c5b0fc41283a99b4da0b04741a1da2b958e487adf98f734984f543727f5ee1045896586662c5d5bbce071182545a52ebc7abddc53d53602c39fb4472eb059a81135205c2c9fba3ab18eae6c52cc3642937dab60e00e0bd220a8fe66f5170c2035c460b323c9419aa9bad1fd79e50f5d82ceac4163e17122b208ea64ce5ccf344d122fd708389389871dd3a748ce21080e119cb41c51ea40cf060c872a4dcd0739d415dab96499a1fed4452aa667f52146a4a1845a667f23478bf787299c31d01c8e99b6ef348b2848f524527cbc05a924039ea07fd5b12d0aae81c3b3f4c24ac47dc34a420e6ca9492fad39af5b30155be2c61331fe2c05833342a72bdfe96135289f16a0ba995237104e142e5334954567e1fa255aff7b84c836b72e870e84815b7fd3b9da7b0944e5ae373772db7116b3d1d90b50db1c3f3b7d1720243ecd983f7b7d65f92422f284f1fe7303f7e89d633c2bc4ebe5213e59b1e72b7e5e061f75fe82a28b9435b8a9cca37a86ce428d1ef9ab5f76760ab571af0726de1ce079ec8e9106670a700dde2f1a9c150b5d2ac6f9ebaa61d14e13566a953f8a3eabe32ab9df5c8ddf8395a1f1a6e69dfc3d9e01fccfe0a688b8106878d0cd43797f1047e26c2aaea4e50165a46afdc2b6116f8227b0f9772db0e43411e722df08e05c2a4d88c5ab252a27da2f405cd981f6f921e1593e44778de19edbf7237760a0fdfeb8699e708d168128966a47e2f7388f6973af403f174bb0ac5b21167123e4ff2b567c4343f4a98b1265b4470979390b7d4d8e31a0222c32a7023724e37ce29ed9d51b21d798d070fc4e511844c8c4b4d01da9322c04b7bf4b60918ad895571353272e77d37110c2579d00024eba4f943d13cf596022a753d817c5cbcddca9f8d04dd164e448cc0c146b960d8860cf91b6f08b2f549ca5947dcf872ab573b93c0470a37f0f8c1d28f217b99d98d41bf68bf60bc0d0cae61677ddc72644f1822fcaa28377e707ea2f6d59c5faef7678b213aaabbf4f030fade5dd8724bceea8612c0b927b2f4e3d4b94badb2d76914e9af0689736b2b98a869db53726ba658e7e853bd0549229c8d5cc8630106ebc34c237b4c2fd0b8cd218c3f7672e23189f78f51ae57e319ceb1c509be13916b05017626f1e4cf787f440bc67772263e90d1dca22295caa35e9a3dc31a210bb9a00260b5b975f6f1c9af6c4020729a7d90a67ef6f384c063b4728e76a0135130c18958cfdad78ae7ab60a570d6529df72e7d48c28cfc91e041af0d563d1785704c1de3a5b99a1f06c2d2e4795172c6f0e875e5dec6f4f2538f2e3dfe5a70681f03c22cfd2056f8fb0a6b9c78bb72c0d82c91793d22b0928604a411eafb7289ea524cb22cc36b5e05e3990ede2972e5a84d12d649c9801f86ba9f36d2d5b8e71bc2ddf6fc40fd21eee45afad58b44bb08bc2d5ab635e70e443b41c043e7e1681039ffcf10c91887df7e934afa3972b4f192c7bfd73dde7152ee5fe779ca736a606b5946efcbb88f6a79b48bf16f6eb3b9eb058be78bb91cea39c77b32ac3eade17fb8b97c376ba2557e0c0980ad726a7ca4058e3faeba655e546778dbb53a9b0352d4d1b3ce4b49a9328f7b3cd65f9b6e0ea35cd958787ca5edc31ba475b87ae3d3ba4a405ebcaac12a758e887a51b5df9e84709f779c462c274de6d01e86332abb9076f5ba5db581b345c2479b2d24fbda0bed6a3d02eccfc9eb495e650e21e3c3c540033b34b7fd217258aa74720577c593ca5730dd89c36a8964c1f9b0968886532ce16699ea49310c2f9f554321e69dbb2c59a673cd07ccfe549c1d049ece30bcd0317fe28477abf94410e9729283269ec3071d7eab1fe294fe98e6fb6bd1dedf921eb41bf996ba918ab0597290bf89d71153a982c09acddd23ed49558b5f27e7a066aa3ef959148fe05c191f3d681e26bd0bfcb0c00215262ff2b0472b8cc2211372306b44211ee72d66b14be5dae64782ee083bb2e35fa3fb018c76b7bf5c2bd9a9594ad4a7c087eaff9c48f57c294f30c41d2d5368253b61548d91bc5d9a69c00b807a7ecb936c8e470c7210ae71dbd178db2a5eef88809ce8498628348d28e6f67055e7da3ee8c51b53379c5ee9134670ee908413eb991e72785db997e7a7b37c137c89195a76ffef9972cd24ae87bdd5160421a11d1d1dc8e8e3b1de28c02a4d943bf8e7b3aed156a872e000cd5bca03d231744ea9c862f921581fedbebec47d6f6f733f75cccfcdce6a0499455fb2e36fe5fac8495834b63bd0ad74fab80deda920fa7b20dd0cee775f7a13a91526787f854ccdfb808c34884cc3e4f9c57ce392d90c2a9658fea152359744038dd5e6974b101c8100c8241146a5b2d7bcc2a3e12ec8af7b52ae21d069ba258b716ed8247692258d676815be14681bb96abaaeca27631007aefbd59e5b686e0565edaaffa79118929dd6df89dfe891054e3032ed23207b0dd1332ece5fa7189ed396f1b6e20943f132f0538c581485b51dee5f947808f3b14cff64df2bd2ea697146174f7a5058f81df3a0fa48a0991807feeb2c177194add9af562f44268176bd58eddeba81e59773c6dbda7bb89e36b7b470d3a408a9ffae8b54ff57042b0a30460003ebcd12c8da38762622baaee093a9897b1c5b720fa4a5706557bf7dcf559baf771e2f6096034322d624cddd8c6b351c11de153f28800d042c72c23fadc8778f8bf42a78937af203ad8510ecfd1dc6231f6f82bebd2f4ccce1727e47e4277572e04959039924884cdecf28a8c58de91abc2480f4f8a6981c7772ab1d1f8a46aac974dca4b93d3706b471dc07adc34cfc1363593e206b19215e7210c8e6a13d6390d9fc3deeea0679d1f0fe03c5bdc6e935baee06a40272b8c072331220dbdf47969219ff87c11c01af120982c66cc40af3f310173e3648cfeb72883c2bff87dffe9685ae2945880a55ed92ae73680c87e20d23e2003c43a09b19464b45fbdf3982954c9e61df73d9dd2b2a6a81dfc3facc1042e761ee05121d5b77525f5de0550434283ccd44b96a224e2b6eef5e7336a68a375190ea0ee5540407ee1e6e45d42d7bcd2c992cc14507da9a386e94d4aafba215b96651dd1b3a7249b0a31438b765e2c3b47d9305fbac24cd4da2bd5ecf9db9ea3f5b50c155330f57db0f2278838ac7d436e91f1d625d9e69bea3faa70cc721223165de63074930a67e60526e79079850dea85ec906239b0a46a0f57cbbcd13861230e12b71f632f7a9e53216a4e080f4d55861a5ff08c732a39dd73826e92755b2796b9cd8ac723e9ea280941734637f1c0cfaae7b465a85b70516006df13a99a8029b52a95272610766be96f43bc95f682935fc9336be87a3a2bd4396dd0af3d2d342b2800a7251a149b0d05d40fe5592b95e34ccad045b9bf3997b0a3a1f8d4fd4e8f76107723a1391dfb13372ffd788f510ca850356c3f4dd27df247607c55fd6e05f0c837259e171f683432a417b52643ba3ed8a4a80d057a9021695d9ea82791d8904100ddd3f7039492cbba0bcccaf424b288934c8fefbfee5f9879e4abc85c813b666110e58fe88050fdbcc21d6ce1919973c1ef79c6f9948b449b9d1fd4cfa6a085b38cb71b3b870947f49fb5a96633425e958472abba7f0b411c44ac7fd074d876c52c7ffe4aa914e47a594051fc7521b637730dbf0d9ea764a812b9a1de946738072f8cec0f97524c9e19ebcfa3d30851d05a155599fef7489b86c1f291bc6fd0072e8fa0a89ae8eb6dcacb94b819973d0f7915a541cc819d59895325935dd1f453ceae37072361ab53d236288bd412eb0c247b49374eb706a72d3a68c7e2f130d720daf462c77dc837719fe9712b009ec106bd078e3718999c681d137000a7a3f267bc96faca274887cf28329d9fdd1e7504a705a971f57a10f547c1a7fd5cef0723ec1aeebbf084d5e7b7ac0cd64a3a0234f747e12052242a6b06e9c1ee85f296e0e28ef10b5b7430dd18635e2c42e4dae24d5b04fb2cee43372cb065c5d76aa723035015d5e1f15c7a221a33003bcab6a020658cb0036cd67614704a810417d72376c6b6fa14f2d3f58d075720cfd1482cc69c39c33c0ec21190ba274ca94b2727888251ab94cc2f217ccb4e0a709dcfd0f20d640db511cd9ed2ee1064498bc72e89fa0a8f34d252ecf981f0a6e8893e329b297f6979ef8bf5383cae24d1bc03b0703d92f9593bb7158de232cb26a81e39fbdce181bbebc43faf5d40fd51dd6727dd0188b66a9f767255b21e49a8a5b0b863913d84fcf9fcb7b5ac513e86d6d2ecc06cfe05051a35040a0804a628df40a854c9e8bb8a673c53906d661b315c772415adf2ebf42dad396d124660b7aabfe0d4c2705da3e501adaa025aed75d6572e7db392acb63589513b528ab5e3a5ffb61ec82b3bb23f69cebb822967a349c12126fd5a1c3f977da0d12266f2ca941d95d0227c656458a832b23facd76cc8a725da4815d7bfeec35044c3c22f95ddb7f20d213a059fa59b95a8731f3d04eb3729981b8980f0b4f05d20f9861887dbc780cdc27b74b865ff17b9e8aa9718b0972703e520ce3d22fdf594864d4a76fa8992c65b34b0962723741b8b6a32f2afa724796c0564867ce148f27499825be0a1b03779a16169147444eb39afeefd37272b5151bc0ff3bcaacf12fee9fe344223c334070c2657dbddb69d459a621ffd672be757e0c1e2bc676c61b2d9ff43c94fe50f0a0249da74688493661b1c0b2e172a6bffde2c083e484c1198049a475f6f06d0f929a0fcf2ff139f2c9ad4c6d4a729de779561f9d5dbde3ea9ddaa52de316758f45f671db898f55bd7e53fe7b56216f438efe52c6b3f4f798339a548731a5dcb5b2726ceb9e1887e5a0534dfa1c46c038bad7066b677410c5a1c50175cf1a8093c2f3d22a4c5ea835739f0502e746256ca4db658f6b9fa9f7dc67e12ae915913a7cb868aab5ef21674bf7755b1e72e1ec1905f95ae765f73e80e12edb9a9639f00147eb5a5fc24873c8fd2c8f00239e1da0584221f33b9ef26d0d053d39c2ec19f3bbcafbf1b3a33c11c928d26b72dfc634d0a0f3d6f77206566497ec200e2130cf507d6299657a2ccafadec480095c9021e90e037d3457d8598a138e0863a3ed9aa0ce543545b1ae34863183bd725841bc00c6252d0607c5c3809b4b9eb4cd5234f437b56bdd2332495b109b3b465dc2868dabcb2947c987d50885490339b9fcf8127049fe717dd97a3eaf6167696cc9826c2cbaf2657c882e2f124e7fb3f4cc051daae44261517ddeea93add4721b54a7f0e2a1b816aa10141c93650e8e9485634e9279087dacc7135b6dab9e729f2ae097e43c81c22c132c05eef4208d05f250bc31f8a81da4e4790b0d66707226509a29e58b0d99591bef262b6f84943b476b4f43dabce9e72c03f5e5e3f472a8f830755d8966173da7237b600d6dc3f0b22ca09bea1925ddabc7c19c1224021aba1a3640be3b42d9899fa6ebfad2a6f0c335f13b82ec27ebe0b564c865c453bd6aa75bd592e6ac8e219f123dc5d5303bd4f38d68398bda292e0fc01be7cd4d48f64423008ef75935d008530502f10112714628fe2713d15865594ab2cac60e4f9d94a2849c394d3f717dc58e98aa14ec581ca1ca07e93640ad39770081d40426d5ce197e427e55f348318b381ea20fbb836b996ee24b81b77a3135c4f9b52cd7004ff3890382adf9742fa6249e653117bce22ff26ac7954daa301f08707f40a6e77365c2571b3a88b3a8a069e6165b6395fd7ed79a8f8c63b1921a6a9fb3289e8a3c0691098edffa59819a00308798c280666f95b9beab8c56abbabc046b7205c1796a2433ef4e0b613cccd4f0fca383ba65766dccc48bedd5a651a3513e0ae28a17b6ef5db8b841c997e86baacc58b7dcd0c240f73ac9dffea8a817bee0411566de8d9e6aa39822268f897eb5902d7009e99c70b5e62ee11f2e0883206e725558249a54cef6216a31a38b13722ba839ec4a898d44f7dac3a25e99ed33597233e94aebdca1d532b28c24b720807f1299f49abc759b2382ec9a9463bb79b472c9eca4e05f9e7ed1250e11b95c90744fd114f848aebb80c9bbb1f0103b78d172191599fc4192a47ab20add2fd5af45f97f846174c052e53a03f116a5b937db4d64e5546ae1e98db2c59ab6d9a8ec5e18d082b7756ec086ca75947312a6e1bb0af811e0a45439d121adc10b28a878c9cf3397672dff1e248e820d6a4762b3e0724877dce94f34a1ad57d8909ae60ec4bd9dc0c191f7b5fab5acb8df52e082e2729a94d5bec664de8d65c645928a9ad9b692489bc95e3e56ffd4ad98e4379e9c723fbc2bac0082b86a2d8c0ad0be2510736eb294a9c5d7b0b675e7c3426054134bc33307e9a29066017b8cf7ea696bd4254d5bbf67c0183cdba714e9bb10db3d411d37d80b2d24fbebdd147bb26d6b1d11fe584649e24ef67d441486afe3728b727c5825e5377a20a63df05c9d9d928c89fd4839e3454b66cb1926dbfc944198203aa212e90ef65631ce762f248403be2e9a7138bec429991e70c173769617a96904515a2fc17ecf9dc2cb962ca783e1464da7554af94b7a4e9b423b28d771227225116c720dc56cc49f19f3f4295db01b90deac9a971fcc833191c782c1c4af64c84b60e7b2730d2232e29624df0f2f67629ab9a6aa5eb3390530eb034a6a53142f169029ffa9d4bb79d116fea514a17437acf53d3427189df43de96e33ae604b938f00d36d70529b42c4fc28b1377b391a6672131198acab428890a21713d81c915926d8c857e1d8bbfcd42bc1ddc3f0955c6c7f88bfbf6e56dd5e73ad8bdc7204069080e23300b86579611a01a1afefaff39e592e23494bc68ba5ea6342b972fb91b90ec6c714dd617662ccf4992af81e0969a26b985fccc66f462ddc62d1722bbfa6afbe40f9714c316d24886f69a8836fd05cafe77fafa9def2de085dbd725df81ac0d4371bae71269f28129f0317f772e045c2e87fcb82622bb5c001bd33a65cfa918540dc33e2b994750b878542f52c30c6c7c9869ce4fc35d805b74e72d79869f551524b958877fd195bc58e56ce2bc2df6dd8e94bc2205a8e02131c72e75ad2853bb966ec8355f205c80b0eef875342bdf5ef8632300428a2ed981172b4f4e0b0ba5d41885e01ee7de557399bcbe0814e8b530296fc3c0f5c83a34a72e1a686af02d2c3ce298c5c9b1c28f6990731176bdc757fbc060a5197dbba74625d4c7242ab14f0e2ad3680cda5628aa91cdd81a6e6716e12a7584b05c13be24ea7d681477a0c01f27dcaa6c05f31dce6897c5a649f901d3d04809d87dd2d4f72ec1f640f4636ff9ab72e63c6e963fbf49aaf8d6ba6315832b5cec6fd208ee37226b368dcf71c057761a3f781359b17b695ca51f0def1d0bb622308fece28f772369bc9205c6470fd719de79bd553ca56f0ca9a090b217b6583bbca70ab9bb844640b4eab93dcc72797d8a4dd56f02e2ede07a0155f8e8b99b53b40dbd172677256e4f1ef6c6c6d3148e98399fdb58a10da7c2403db5c39b9c67106ece2765e728af5db2af1eeef103dedc35aac40b325d740367ab155cac42c9a1bcee8393772cb680d2394f8f4c17ab3d8788073a2151d2b0d368026468ef1bf17ba53af7f7263f45c1385c5b5813f2df28fb99390af95276ae8a5cc7b42b42358eb27682e193a0bc194031ba47da579743e79195a37867ac873d1d77ac781114bf49d4490285fb74816a7521c1b7d9ae42d4d79a5660712e35030e0bfe391e1f7146c1880552cf76f9e5e7e3a3698c20dcbbcb686cd7c6a6562410cf90022711a6bba24b206bd8fc07686d2b800fc72cdca12965a97f10f0d157c64f923d7fd667b7ab3ed628a0dd14db46613bca77579f842485409e7338de79479d475421d0bdddae9f6711096cfba12a0215b6f218017b78d8c4442ce4b4b5aabd8282ae204e1a806bf7203852d18acf3d07985365a5ac1b541e5d977d345eeace59237f557fcf8fa8d72deb0a3b96b7eb0fb2fc544490c4347b666a6695855971515bd690ab2f1285003d68a4ee737ea52d3f8f74e02356460ae85c7c984e37a8efba806bfd40ef12c727e2c773f26fc5006eca5dd2083b4565ca780f48f8be2c4ecd0de0b5086095972530b2e29bcce2f74ebaca939d06169ead1152b430e90d4f29efe750af1360872f826de44a1c0ca72e9740a6c77261d96bb54c95f92226e8a21bc4d44b1eb8472d3c27a6539c2bb0aab555ea3ea7c959dc5bef111ea85b096153aaaf2a9f2ab7236bf501631b11bf62c118986635f7ef42f99287f07baafb2d590dd47e8fe9542d786686951f667802c9bf7e687568ffbca645aa2352e32e9c73f0abc5e43c35867004887d3b828ced77f622fae6367fa5a9ccff94dee851680067ae3170ac0721bfc0c079044767563403cf73c334b097efcc0c0094298896638dfaa7b24fe6790b57db33707c8f56878642a693ce16a388a44373f1c09d1a37cac684222c762679f43c1e4fcf5a3d46aa9cd3c166ffd9132246593782af8eb1bfbddff265a3b100e06f8368b8ffd3b6f4a5abeaff9c74cea6a95ad14fd6bada62119df1e3c41ab6d8773cbb4a027f058e95ffe9bb6f9858930aa2ad8c6fbeba15213893cfc0ff7718c6027e1ce23639f774bc7e2ee19980d7a193efced1999dcc8e5654ac90506b1d5e75342bce2d8a19ed8c9526a8aeb5903a1eca40a9f9904997f754d5145b937ae0aadd1a5e476b7b058b0e76f778eccff41b2ba650fbd23370ff2895f72d9563080b1554a7f842a2bf90875b81959f10213cbe642161b4adb1a0aeab6728e82698bea71bd6a260b20ba0f33594e24c2b0ecfb007f66e675301d69c1e10159f914cbf735b222f7dfa753efec2d94d1e7c3881c245496e0d60ff48a09584d44f84941e6e44eecb617093daf5b9b9f15a22d9f8025912ed3e809c466e3f97245de2a08bb68cd0e925a6e9da310b3a688882cdff55eefe60497a553ff07991910244b59d561e07e27fe06eb43753b64225b1cabd4f063d902cf2b2f08ae4372ae31007c3cadb8278f7964168335ede4d360577bd1c7e6e4d801a28ce8580d0c1cc2572187fb7cf04fd5cde6d66ff5b97150e2ebb7202ab94769cde35e760072917ed96e1bfa8b1ecbcb052dc536a47fd162490276e1595a4374e93be9616472f7e9498bc96750a51eeb9b68b4f3c2bc41cc26e046bf854a732020bd4c8154729598ab5eb6f2447c318f00b37ae92a7b1f8fff4be23d90e224e69dffaf81981713071a72637bc91139e998d9dabbf6a6e1c6a802ab05eac5dd7c2e6b77e27e5a311bd6a5421d7d0da2529767f29fcd1df717e1f3e3e61231bfd017944aebcf3007d531de70bee2e46c0a4f3bf7435e2ae2fb0777d7dceaebf3c4f02ee5962a72b2cb854beddabf600562b10c4b96258e835ca9779241ba87573e85e2d852666910fb74d072b88e33d9665fe5e159bf52079d28957abccca03141c6ceaf3b995f00a901f2e135512ffccdd332d7a37726674be92e8338bb39de64be0cb17367311e01b87ea4e3745e27e4f8f26c3d062804e1d5d4aceed75e942635fbbe7de1723369d0c336d114cd623c3299261bf35207f5a18bec7dccb8bc729ddd510c8e394d4e2f25ebae35ea631eb40361485c07c549f658a24ca033dd6959224f85ae728222af0b1ad730e088e6f3218cb3646145fd0b7cc4f09a77efdc25bb7c9b5c728fd83f13e6d8d0401d24b7e31ee3081a713680578babf41e17aa5abf8e3cd2720966268d927ff6f809b5be5b74fb7aa2760a520b6f8863bee63cf4dc4346271d28dabde520659afeba830d3443f2ea9b6659f9282ce41f4796f46880133a12366a554682e59300b6fdfbfe4e0e1c1479c9f48f6f0c578e70587d47992fd7c772e888b9f6cc445317d0d50df8ad5ecd46bc33e0335a3ffe452a86c62f191f8f64264e5f5f55de3957c1e6594ce71dcfb5ae7a084b3d02a9507fac9a2d0bec7972aa2ff07aca280f6e87bde2b3099f73ea843bb0a9b5767a737a03ee05aca6ac72056deed2467088c0ebde983cd8de5aef44f0ef9daebcdf86a3bcec52d273cb721146060aa0df15d478e2d58351886c15a386ffab0ae124d617c826fe6665c64eaaae0aaf86bccd88c9fb69e128a9e76b0291ea667116ac8131347e6cf1651c72bdd5935820dcdf05fa462cd256bdd1f365dc5d80f49f4528cdaf6afa8bfa6072b0729e0c218515d5078a21de70b8e5a70fbbac71ed49ee99f5c66f9d8d1c3d72d874550cb4cc802be766b8144195203412bd117adeb4f0ad182685aa1552d272e75b8f286f394aaa1a868059635aa77484c30d5c6c7fc2338f299ad62a4cf3261978df4e0fa6350ae8ab2149e49d29ee770465eef0fcd7d54e0377afeb3ce96bcdbae565310ffd6a2820be40ea990f48dfaf1e7affca4a98dec635fd01a0b1437a22a0f7a8a77f9fcb422dfab4b266e5c7d6ea22c510170d593cc1eb038a12655130f3740bb6c8acba86c436a65d458095909369c0de1afd0a80ec8d57a1260b9bff6bf419ce0148befeb4873064f2bdc9915953adfde28746cee421bcd53d72000f952ecc7fbdc94e5a7c99f8d710e2b171d9e530e22656adf1593336bddc5a8443a3c7e4641946b6c76beb70ca93fbfc0865e0d58b89cb14b35225ea949d72e571d45193d58fbf30451ec624eefa8cc2577b9be9206da6c2e439c0236a6472c2f1ca197805e914cd1a3f98cbd40398d76b0bde147ab12a00b896beb9c4fe6c5d73a3dce06444d410fb888fefacceaed1ece279ec907f347f652a89933fdb723b5111d04d2b6dea067d0034f42bb05057da4274b8a373db95d0dd5835338d725cc6f4ffe80b8d90c27bc0fd33b2a14e565418c549fcc84f6010d3f4947d187258f363b32949b6df23ae733387537940aff861d8b21c3a8a1f4807aca17d217289c1bdad9ba23d1d66b8159af4d9bc6b925b899bec5b0a6d143983678500536b815230a51c2273cecd3ab127041e1cf228a5917d5acdfaf5b5f00f34354d6c72aa38928de8224c830c6b3e5b83a5849c247f4c206120375e521199d40c5ab629f7048254fb38dabc6e1bfe4e7d08b7d17fe03a47d8fc1393921f4eec7cd9f472aa201e855f159a5061f61d1ee810c62e5d5f1f34af00c67ceb4974b12a42cd72a8e5a7a548371750ce6790c009ae5c6f1e3dd3bb54ce0e56ea4968ecf4b4db72876e9af4aff41ecce97fdf8d1900dc8b71449dae570532bd7fe723929f0c7a72e112b070abbd1885ca27efd57ee06f0a02dbb9f758c0e17bc01163d0422c6e724884aa1537bfd8888e5aff10306d553f70fd6b5b458e556f02b900265313014f70bfc2288e1edecaa9af2091490edc926fe02af5adf4c7af39c6ad739d31b6047537b182b0bd85d816d1bdac066e2781d23ad8e303e0d5d49a6aa025c02d892a4d7d91cfdfa97b885970f6f07a09e5cb226422e469b98e747e4442de042ae872a7e5e61f3f6e69e5705f398d4e38d7904375eeba75f1ccdb5a370487421e4472a219901fb4caea4101809e817eedc0e369b9e3a7e2e6b8b517632c0d62a8d810113e3ccf273dee12ee1b35cdaf0451d8610ba69ebd8e06265a08198017284e72159bd1204447f452e8adaabc1d3a2254d8ef8fb268c726076d4404d5c8133d72e450dd1bcff120494eb5290e56efba80dae6647ef9ab3d286806822a26fe8172f4c134997906e9640c3dc26117c7bf3bf444cda81298f52481b078aa273d63724dafd9c3f35b09fd1ba1f42986e653a1198e171afae51e3f6d4b0c1243821357abfd1d5666838899e02bcde01afc7ab513c459a9f8ec8303b6fc5accd5e33672fd5682b3a9dc983747ff231ca82167296c8b8eb9a001b5f6cbd189934f42247240ddcb3061c15a4955fb0ac95bf8f2427ade78bc7596cebfd5eeeec45de7ec721028e1465608c00773d2c9d38f8d7e2d3af04b41379cd91ff4113faba7e23b724d08d47227ec93ff41e151e7cc9f313f5a60da355445ed6f45a0a29de51a7a7285ddb4e5c79a5d1725d7c36e6732a8b69b90910b2e90867ee2722deb8ac5f051aa6dc346d9403edd377b53e92a2ac37f8dc10b7d654bd3ee88027184f48a03725666ca88c4b2155265d892bb55b77ef3dfe63004375ebc8553b4fbdccf48f272f23d9a64a5c806f699d8cf751e21acace6c1c4010a74156b13fecfe23f0c283b7aa9ed10d376cb3b2445dc93f907e1db5fc273ef58aa8a53e80427b73def9b72a1eb2e8e9fb3ae974b5afd0c5b235bb983cebe75651179dbf13671d323bd6772c624f6d66b4874d599aa8cd405dce2ac4b8d0a7a945156ef9d4bac3d26763f0dd967133ad8d06b309575de513cbefb9fe3206ba2d65e7795ad8d03610b126c724cdb8c1deddc53ef26097023026a9d676b337cd04be2937d48fad157f5e5a672ccabe8bb658a5a07a7d2c39bc7075965967ad0f1faada76632c33e70602cdb7298a670438d485e1346ef4fe31627e57d10ece7bfc4f3363f0dc0166351a69d72b0ec860235a9b4f70a4080cc52c9a196f684ae0dcee46aaa7a2fa734ecc0863d58e5ce9e6230db59cdb7ca975458c36fc1494a458b788e66d81af63fdfc6b47238ae48f32d04a18a13e7118ddef22e4c2bf9f2e5471342a0539ec55ccf915a729f013258a66bc85a9d1f5e63de048cb61c478f3ab509a586686f82462e6f8b7211e5751610edc6516edae0f9a94e563b38118f0bf7c710cffcf9af9ebd6b647262be4a214e317e936d774ee2ce68257fa21a5cdfea6e2b8f20adc82778bcc85e17a3b634970d7cfe3c5d3a87b936c329459664331d3de808cc0af36f06bbf472d9c1b8c68e791f3af75451d89f2045688fae7f25ab75f052cf69dd4ad8d0f172bcfc8e769d787993c95dbaf5b3898d670c83342fd91088ef6e2583b1e7f71872c894c724218e81ae371eaa79c492bc6cb7c5a91a021937faf05addd92bbf4461ca419c9cafa60cd29ad17c93c6d181db16d10c5812cf02a42e5d63603ef62c3061459251c553938755ec208e87229b9374021fb9e2b8ec4731b546aedb7da2727ebaac5b5a3aafae3b5e52e3d5b47e5d4ed25dfdd52c2384eee23d8bd155d6722d7d8671f9f1ecc1ea59eb799d00ce2404a439df8bfc435381f7b22ca3f66c72c34fde1d11b5583c70fde25412235d3e83e80d344b6fafa30957fc8ae7be1c2a5dacece21cbd4eb66775e1a3625d3798a4afd6d16d35610c9f0c1c74990391724f1e05ffac215613a7371e4c314ef00d35720ba813192688d86624458db68f066fe251d9181f653c57946b9612f1694557c82ea529e1a74b57f66c4558178a530be0a52d4c065281f951709027ce18dafc0471361844794020fdbbe79c70e77285430c36a8480085cc7e9622c0b71cf864d606f6f21738d43f2e0ad22a1d7472770fd093442c91f567adb9d40838d7e9c9cc325162f799c54bbd515b15d97672726677eb76f3fb25742002f62754962e7a1c8c3048e72430563a1d59a4c576721246882811d5dab16752afb7cb7d7c429a0fc67a84c8ff7c53da8bbd7f8fa272133ff4a582338d30c0a126749c8183511554001f1d634b9e710181a575e85d59054077eb83d63af397e391fecb20a1cf9eea56dc4dfae0ff07bcada21478864e38b00a7b28b4416b1662f6d5eb3485ce3fe842115ac986726c594ac7ffe838452bc034a57ba6c0477771132aa2345f3b1b402a9efd17cd3ca25d0f2b11b6c33a1284467286b226e70d8ca654da006c5d5e6e759891e8d5c9d62447fa2d36715fbaf834e5ef33dbaca15f8b42b837424c135e268076cd30da376d1a5e8999807201709a74cbe020ed315367dc295bf280633b7328b1877e9e9df255638adab805bb69fec0f5d1c5c9a412c5f6b9ae50253e05e338b34c37af7723e55551c566722874aeb853a19c199a24ebb1b467c7c732475b99f745f07917b169b366509972ab3a76c26fb22d3c807f5eb2f033a871095dfd87f0e0d559774a2081fe897d729047a8c472b577008de3ba69356661566879b2f255dd1530b41fea4f91668c4a7c3fbcbc0eebce4289c6ebb5b9c63427d365056fae2229e4113a5be0f207333a862c9a80ee0829dcd86f27188a17bff99e303b1eb603bb5c771a4aab5332b7728254946ca3efc0ddd52ffce35139b5f0b7ba1c4aad48a13270e52d8b4bcbd02e5c83090889cfae1a2ee947daaf54456638a3e13d3ee84c028442b4968cc4332721a7e0f4b581eef3b45b35602c42c6cef33127572de1d7f4a74a6106e399543b9386bc87f31569b4c2a4c226e69f9be1d926735ee47e2e4b85233ca32929c971f398e34fd2b5622114c3d23a58dd484afb038603b089727d2d8908550b02637276f154dbcbde09d371b88c50cf46765d6baf5c84ce8f0860bf505affcf719e7260321fb63c9def06835547d2b997578b916d2a835aadde5cd1e20a82dfe10407af8a67a786e7c76b78449ab803d42b04fec8d78e8f0ec17b66b0efe001704f720fd02d3c2a90804a95aa86b254d0721c1bfec014180228edde1841f795d0315bd796b6e03f82fdd87ce083dca20d2084c20eacda5792b5c1a9eafdeb5a8c4e7274b5d9b2052157cbbeb1af8770cf71c7ea04bdc5dead40662f17e83e14aadf01ab9681fec79eb8db6efa62974112504f8355f63e59c0cf72383634c2ea8e5b5d32de8d6ebade574e1bfe6cef87cdd83617bacb6de16668e5db12c9e3cc77fd72453d552360e3762a651de94bbe803ae56e724513e145a46cec9f13b675c4a67291742c578e03374bab92d0aa9e83b0750f282b656ba987ef35aac7d4423efa59854ea45a85cbf94ae4f13c4fe9755b8e95a2af94e3e4a684cd62d23250a2c735e66d09b60cc0819e078ecb3ec2220ca420764e4c2374bac376a54d47ad4cc93ed708765d6ed96c8b4fd0676f4c2eac03c648549207047eafa1b318023dee7131a352486314d979f01cddbf53d9bff3ccaac8b517fe6ee97998f0139a8870b47282f6a7cba401170bdb56871985a96ae647ae3f1d4396cf214dda6da27201ab1338122e0e8b97ecc875c8458d78565946f3ae4f754b0bbf37b5356e26606412720978339760c46cab6c547d5752f604cf122cd264edae9df3452fc623731a54728f2aeee66b057abef0daaabb41e6afa1fba1eed54c163280263622e8dae2016c3ac9fcabaf89554c83f2f72530725a84859e20ce5b3b4ddfe7d8d9be837f4472d478c1552200c350ecdeba1827dbb9989715bbdb42e40979bf70f3f5f294d12c1abc8c935b2916101bc2853491e768f5277ac3c4ab54039994de9d01ba60fc72642cc5cb5ca52855932ffbd0eb2d537b6931dbe3c3a78d49ff5d794c3e517f7279b04519b21cbd8e4e8c0f60d5212fa2091091365c83f8837c2d9cbb017380727338bbe6e5b1bcfb73e746ff07fe46d7d33dca1d8943a0ebc461f323227f057279835a7e1b87eb8c374f7dac60956dd4634dd1a81010c39179fa36fea9a9bc3cc8e366e1598975227516a399c297a936566307f1acc70232c8adaacac00923142c6d939fb1fd2a4627cb09278d472fefe7d0d3c2d0474f78e7ddcfaf0b6afc5f1c63ea21893b9d394dc8d6bc2d136f30ed34533c6df3fa7364a4150678376b46559ba53a91a342e39f82434cebf415dbf68c0fc8b85fbac525ccf31fc420861dab789538c54d2f3ed29942014bad918a74ca3b065bef41f789608bfddbf3ee1e7a3d10136b35500553db4f858a4e2022d2a7a63cb568c9d6e7d504408ce8d47263d7861085da7ef4ee777499fe63cafb04e94b09e8cacb3fafea517449e1f25b385f501c12810dce0e82f0e38f3c194932f98301d17d2c68e22e4a9b4fc106724961a95c71fd080206034080e49d12edc058685c761f7a2e75be3f772b60e472b5a02462703e2e4cf68edb541bfd3a1a4f72c1cd104221c5cfd5339ef9f17372c8a588a9663f9a33adf2de164a83d96451aa5aabd65dfa6c2b611d8c193c8a084acb4494b21e3610b2c9ee00dee8183ffb35926fa5f9cca3d4817ff05a9d403949535f7556d0150527f822ae4a2a66d5f74c4e0e96b7028758372c853a31cb45d89a74ce6a001891f19af4f7e92950678916801bb092d974ae9ab9fe30ec5b729064f692c8b6e6fe6720f050e75f097baec27251d6dab0d63279acfbd82d607259bcbcbcaaa1eef3e8c7deaf26ff30c695a8eca056a84f57262a93dbc816ed72560bfba072eff6e52777f422acd6d834e3743d03fabec1b786bd7c81add2ee722fe33781d1e4cd896d9f0ebe986929bc68d3bf35780eb6cc0acc08530a675d58f7cc27668958f3ad45485054707b4054a42bf3bdbe687305b5abe9f73c7d8455350003ea256ea0e4bfcecc95a8a02e8d1094e5c876c35b7cb098a9f9d2efc572fa43d6f801d824f355eb07ee87098da9924566e6a930d14fddbcd5c520c2622005e261fb91856ddfedf2e0778b931e819ac8e6db8bb83d163c9aa975df0b3d1688bf885d21e550c55e0b48557878562e289af64d647047d28ad450b764684f72281d78f7c974ff6a5ba2683d3f66e78de585a1429f8611fa06e5703ce6e11972efa9de10d14fbf210808b19df7f762a5c516aad9aab2271d6b7f1d3c85b27172e8009c7aa69f83a3ec739428d610e89c82d858daa3b95a3cd8fbc8bb3f9f6972534157f22390ad1994e68212e4cccd5da68df68ac2f95b6631f54e349105fc72119b2da8bb1c5d6704a20aabde8d29e9a0d4ab85b2ab6826eb188a82550bca72e3baef937c3e75e609e64514ae15ea480d97d121b7127b6854fc957e0b512972da0e4f5259a556eff04718aed4ff0a2673bf3b4ae01eb72a465b5c361c5d9f72bbc4a9630c6037e505004a78554761ce25e7299e525e27e27f21e9d9a8eca61fdf11a49b08283819e2a5a883cbd1d5d91a303eb076cfbbbba87873cc32d46a726419949b48477c0dfaebd0f6a63b30ddde2237b2b8e85d7d4e2547778a21bb7207c80e1a5d1140e5694029ea737e3b2d62acbfce0a5a79ad2cd34cfb0736fd4c4fb8d152656e182577a59d8b386b5ecf802efe56deeb090d266fb82aa958e572a8152c1371d19a67625df56f8c4635a6a94a5cf4ef21993e0594f023b589ee72fd69458ccd7167aa65e95149fda234fcf4b6ad1312bae353d54f6edccbd8c94ab31b15cbabc8a302205a6bf3a24c8364f3792f2edd7061872381d4f1aec07f7206bfe8ac8ee9b5234a768bc1a6aac15ff128adaa6d0039f3097be8f9fd334e727d335b723f2ccd8377703c9ff7acb2f91a74c73bf368c4e8f7a6f622c26c9f72951f5818df1f932aeffac7c2d41b3f7fe49e3154b1e89421b601eef4a1b9381d5cc09a8d607dd8c9f64d8be9f249733778c5940a1a8e07b80bebb84817e5da4cc32d30a11d711669f98bfd14d5855a0bed768f8724ffabaefdf32c367d7f61729fd0b634bbd7f0a0164010ff9b5a7354767335e481660d5403f9821875bc7a72d52eec766b575b3e392809b8f22913c003a79dd081aedf1511bef3d0a275ef728d0a47756dd2b441e2e7e0bc3a773a43d769d803e6c104e5d28c4a06821adf728cc22c786219cb513dff867acf6992a654e40639a2bf347e499a6b2eeb9cb5672a64564cac470fe4a8eefc2199e88fccba4e7e548bfddb943bd87a7def7f9c726e9c75372a35daf99edf380cde5e935106892b5499ac246b65f83c624e61083d6202ab5765eb8947d1a3a96bbad161a198f5b3ee5fa8ef923e6586f19eeab472d010f66b8d738ffff6afebc63543a383b1dc33e5c1514420223df54e30bba2620380e8a845ddf0a51f3f939143b73928032d96aeb45bf4250c31a6f906b02d725b6987beb402fb514b2fe8e8beefe7f5e2155f3796a2b13f80010fe7f9abe9728888eda6634006f868804bfe340c35541a89d31b8d67efd03f94b488b393af40e9040fcf81c0a6195e4530f2d63a1498ff24c0086cdcd49e41269ca4ca74e064cfd454e0b6851e0af85a08d1a024d76aefbb79f1d28bb91d3879316716245e400ff67c2170567d72ad2a6783b9b11c601693595c504510a753c695261c13425ecf44d426372e5c204e84dbe5ff74695a144851afb325cf7b3666b3db01315f25689fe377ba9b1a8bf16b2e97b14b8c3a70cfaa7e9e1f5073cbb44eef1ac3d472cbb7072a127d24d8e3ea36faa1a11858779675596e865d0b5e8b80d6f1fac872a96c83fd603fce570552c1363cc073f126258f3f0bac17dfd67e0c14ec698e54f4a2e786296f9128d25daefd79f80b9e38fbbb8fc6bfd59c34cfe9864553d272388961d9646e5c3b6e8aeb617cdc7e3d0821366c7eac0a8eb6142960d1cd2865c11dd92db59a97529bb3be82aa1ad98af3cafd0a34481bfde87b56da968d8272537bd6d3a27a8ae3714adf085404c1d0d5b1ddffaf8a3eb922accbb91733b60b6d720afa5d92433919243401068be023e11eaf2d44b6d7d6dcf59fb9a650d772a366a81a8c1b3edc17929014a3d9ece19cb6f2f7312b8f324d3cd91d8dc335044fa586ef79ae00535bd21186e480551fea6c6b32714038b974c9ba24eb7b7c29b9dda6d8a487d97070a348c0cb03ba6c4ebe4c8314c7e1b785f54b6a4d27d76bcb954a34fe8626f7f7bdaa962a65205c8ba5b41413ecba4ade064bb89f69f16972e2fad33d8ff82cdc8d1a2db1358f1da5c57d493594646c85763e121f93ff7200d036e6ee1a9b5ec9ce17e0851419ec5169013d7d17f34798e2f5cd94d1a47299bcb8d9eaea2189638a46cac93fe48c204396991c328c87734758d6fb2d383c5303fb3b725a7ec660dedb41ad0412ff90c15efa6e55736f6daa0f8a51f936502d8c46dc2e376aae147f0a3299b25c411e3bcb618a49f1282941f743d69f1d39a32a5aa07f3bd285cae965acbc4bf441d890dfd2ca47c3cdc9532dba8aaf3372143826d18d3ad2c384c038c129517e39965336e30ec705cc6633ed88f30d2772ef57a46a96f238d42e1e1ebca78025f6d81d48cab5f407e140e1bcd10ff1f750cecdd9f7004b9f6bb62c614960f002b9949425b44a3f190beae4bed14f47e67236a4de6d9842bcc77cdad774e01e7068fdb2c1e9c46f4b62ffc41df00cfda85bfba8979c999d04539d8d35dc3a0d85ae809ed79bb6ec996a586c973f43e2b9726cbf6799b29a304c578bc1c4312278bdf571cae02efaf7b8584c79d8a2fdee5573f83ae56c22f53d12424f713933affc31e2d1bb31915517fc015907b8d84a72dcf410c59890e083d465e9ff82a0b09ab2598e1e6d42671ae93301ee44387e7200252fb340673fdd90d0fa4cc2dfc86b755fee42909aca78a3cfb0dd2acf0d72ef7eec91fe844f5d274faeff3f7f6ba6bfe8298b061fce77bfae86fe1643ff7224d140501ea73f39801f160f1f8d0a1bf156f3e8cb9ccd0e4ab7c82ec6c3c572d36bd2c9518f70b09f96388f999ed9b6819e33cdbfbb37162fc0392c533b760f0c896c5c7a4b3f26510a49a6b2786de8dd5c45a36da8d96b1490c492bc8dcf3d40a393234c7d9780c3faf146eb83411e49ebfd9a80e0b2511457a302a9333072340dcd2218574f358bb5a270a704bf5707bd8785d016708d52ba8d9dd662837236befcce83732cf0f6ad305aed2833ad6a0f93f315be097d97f352e13dabb31264f8a4d2f2cba954aa0be14294f3e0e90441072dead5fe4df17abb319c956a0455e40a6f42d6df4d9061788a10999931789bb04987cd3d26f160a78ff513fa21a4a87d83cd2916b82992774f5cd0ca90ae0b54833c7b8706c3ff3e75bba1b17241b2e6d4bc2e2201181db15be0defc5e59db4442a6d3a41b98f96c29cdcd0a729a501573bda8a15018e2d517e59bf44d24a2bef49ac1f1953a8e3c5cad7b8b72f4bb3cfc0f9bb992587d8facc549ece53edce1eba72137256e2144b642cd8622149d1f990ba12b128f708ce23fa9fa2f4671d883ffe116282f794f022cedd872aad768d8e2ac610b94337d24f042ea0e26ca930e0d3173cf34700f5cc0d0b072a0c624770b1702bfa5059efbbf28d4a75ebd63ad6c850d3989c5e6c2cc3e205f6d61437366dc560b09ca2c8a78c1d11a76c1182c885b6f9b12d8a02bf8c1aa382db1318e129a3b49a8086d7fded1aaee0eaae51838f785b90cd4a7ce7e675345e7fee72b96888734ecf2aaa538eb0fe9350aea79b92e3225c8231d93731fc64b12a7f4082303c85bd0624fc15072ac9dcfcdfb99462281d0df331b83e1b7ad0c8efc5c43fcc056feede7477eb8dc0a163df94ccb60d10d185d8c72778d89487220106fb18f23151be3de99e26907cbd471bfa237d9c9ebca01370b5a7822ac58d36914c8ba382ec2099d6fad4c273f8d9e020a3909e6e584b16c6ab421153f33f2baf6a6dd3ca0e8293439a41aa57b96303a8e484bf7459a40f5c4f34ba42f613f066ab22c00c9f12bf8001d7ddf6f1db3806e58b5bed0696b9130d75dfc092a92e8d444282b24875f3f730e188d0e56766badea01c9760924e9a872262053251a47ddf7a6b50c2b4b5df782f63eb54be24ab0a82d7624224378b82c4065c6727f98b2d3e8d7a06d5d9d1f0fd7b36ef410760f00fccbf8c7ef44f71bfe769d0c5f316086ee19af4433e88a02a623a64efe6e02aaeadf4e0db1cf817802c8e93712afc8b792f03b5c327c5b2509b9a94093bfb96ebd48fd392e9f435e38aed1479c856d4494049b8ff43211ebacb86bb6692837a50d3d4cc03e9e57709047f11442f8dce21adf66b9c3ef61a4a62faab5d9c6759ed7be2a32956ab627009e8e13bd2024d26fb43538f1a361fb233a840e19b85302fe3505120356c76f782ef07257b13963b27613769d28f36dad470cb48d4d1486fa4994ce7871240ae333bd7203785405fb510300dcd2e44550abaacf94da21556fea39fdd0b73b8ed5d59572f485f6132d2ee35247a17da542eba216d5048707f4ac19e9d16521fa910ff2728aeeafe0c5b22addc1365719a5b6a8f19298222da8cfa305e8f5169694586572988126acf4a4117967d5fb5360b1d8b88ad37389812ba0a5196776709ac7cc7282573e42c4194fdb8d1d56c27aab482a6fa5276261d073c6186dc972c2391f5640aaae1a509d9ef0d768de3f3c1e9eccbca0cc5b6fad49baaa0c574884a42c59ad9906c6d950c2fe713c8a9041cd993f7a51e8ae2941ce6225a03c060f4ecc7224dfd4259ec99ad088be6ad99d111a95583aa93b853a3693d37a577d7a6e677242acf53dca821252f72e1c8de1a0d8715a29ad209edcfd4c6ddef87ac7671b7299bf802e6ca0c75e14fedc1464f8df872283821931e77b255421c3b4dc985672157bc892d464dfa303a5e591c454881f44b6c6056acf92bfff0563e18af60d724f26d3edec6d0c6e3f972e2fcb4ab0d254569935f81a6f5c6b4c51fad734fd72fbcfcb8a2195b8b3153751117c2b7438616126eeef676eafd5054f33f30e8b72f405e15ef3e73292a3ba3c30566d9b62a7e4a3ef7e162e0d735338c5e08621242abc667b2e2f32659b82a4fcce29ff6e04d360d1b2c28de988e3fdaf45e9a572a58988a7235c1d3e4fcb026cfb1d0f62723968b5190237fcdfd34a6c6b185b725b9a2a3afe4b02293d83a3274c2a94cc059cf9d2f5646bf48a39a757e8d3c172cd1e03c199f27acfbf03a4f77a30800915dd40aa63bb34c516ef8db1f45ca772d40d7aaa6b5620a8904f18c01534067b141b76c8c648e8ea9ee970d9e595de1d4ecf4b229f3ee5123fbe2f973fb43b4b1ce0c8fbb54445552d9f1e2a8ba9b07285fd16c105307d9e281bedcea6a0690044099bc2b6c682d1c41b30c45b608b7260e1c2de56cb4d7df0c69d3d712ad3b24e4045b315d0c24c650cb3352ce37972e17a10bab5d04db2c44ade81f7ce9f74ed2c2f6d02d06d19eb75b513cf5b907256efeacfe5b5afeba97384a98ae31fd1ff15e53254cbc55851df4dc5b1f8bf5b31c4243b74aa9fe0658395764413ddc7499655398a1f810723e6d908686fd872e4b9204714d2ecbd19cc046db0ce97e8c1ec8e89cf2f14e8f2911c9aabadea161153e54abdf52fe09b290c8f7d0f38b3deb2c29ceb94c8176ccc9664c50d3b72c7b3ac008cf2ff74e962df953b19c24d6c169497b7969dfabb801546be3ac372b6a2bdbe018403c9a08c4a23cd5f832cc93597bd27a1dc864f4035117f4f0f722785a74c12a1695ec6d7e40f817b17b0aeb4a88ab86c92ac6bd5bfebf9d162617619ea1691e3724fd47b32babec7f79804f4449e281790949734b731b9a2a508872f41177b15cc558dca234d9115ecb9c91fb49d8f73948ddf13a8771e7b07728bea61c3f7ae4620abbe90279fffdece429c117cc9e960a64f7af52e4442d3723966809eca2843aed8799afe12010ef6b1c6b7b41748eb940c64f7c28cd676725c85fba220de5be2ec50b12c93ba645a77774902c442e988a749828d0f203f724d865effb9eb65e96f7b5ea0d7c8ebd207adde4eb4fd55152979eb21a08d0f72026072c7fee602cae8b91f16a7ad0bbf1c7eee4e50e65bdeea84cc4e889be322c1791e9c2760c145244e9d80e1e9ece02c153f4e55d63e617406b96f342b4a2f6d796a304bfec2456c982af618725420300537986d10524029305da1d0bd0b0e379ca9c9b375f5e9bc5a1b0078f69338ea29a9f46f7b2d007a847eb4ae4acb56fdf91f10a1b961983c1a5113f137131187cf6a5637704bd8dbe7c14b187da858adb94625d48b77dde905565f0f1fa688e277a66914263ee918075fcc543773411775b4722bcacb1421932ec18324639376de4598ee6a127fbb22b6fac30abd4d15bdc43584d005868690f85334756180baeaec08e3106bbce7dafe0dbe0936722b7629d988cf3021d28cf83ad2101dbda6856a3d04c8d65e16550a249a91012e4cecaa6f2372c34cdb71901587d54049a7b14413165aa7f203c6cc450539c5725fe0b255cd81ae965744274a7a031d855c8e05848e31d1d7aaa252c1aea6aa7256d4da88adc31cba2c4451dc14add92ad12b5451e88b3c1c807f2190a07bcd722321eaf11d69f762e66709970958a93d50ce9cbaadd8c68c0b7a0dbc73423134dc88359ca3e2a482ecbabd2f4ac3d225df1378b311966bc3474ccc5d7e59f1625256ce4161ddd466f744c09c959e3c32db53e2ac28d99defd897d0c1cb225172438ab0497e62500511cbf6f9437749c874d520ee1411117c12e166a5396f962c604e428f7b0b3e6c62874c9c6272c8da743bcdd184935b49dc62a910d3230b358c18e49a0422884d2591cba5c02162af420163dd2b6d1e912d975baeb8d87672a65f9fc2579896eebf4ca4c762e7c96bc9011ae00cc279e41bcc3748a3772a6a8b313936d51b444c89a62f741ef37dea08d558d3f6531752be387dd2fec2e22aeb588220fa8450e2b8adab993a429d8ef3ae754fc72097a9538cfb7d29d47a722cfaa13064879bf8b6d7860c83d9af3392ecff93cd63984178679eb3659eb156d556f4cf8e9d0fb6055bd572c5f12eb249b978db955171634d147def9f122172af3209087af7bf3a79f377109c801ea38d2849ac272d2762ea40f36c219c8772c2c04e766da066c2f60371b2e675ad5e60a36975e9027490e997073e552e794f75e73dd52ca10255155ee616c8c799ccb25d9484321f048ced5fdd7bcce386436f19a7221e269ad9986cda14d02174b820b7aeea6d97398d8ed2dc4d46f9d3724de5ec2190ce7f183e019528d4a3f64bd274e394939a02c60a4396a7278e974674e69fd62ef80706f517cfb7049fb9088af9555b95333660e2049548793f537261d688f05023b757645a33efaa00e3c80d3344340698f86754342a5185c4257233da9ca1a47be11187a8ecf2364b936e1b976d389333e691aa19458537512d6503a565687a61f8f3bb83acad6fcdb9be0c88238439facea95175e113ac5824726c0f9dd6d7142a3e76fc42d9cbd3f9f22b8566127a6a0410256c2e6c5a844472b133a9c751af24a0ff771814b9f86a3553dbcc59dd726eaf29be691aa9527f60ffbb04d14e5881916ef1f0a1b7c87decc80b5f6db8166ccb70ab38aa87330e721778667eb4436f4f998193fee974e1688bab86bdb7e6c2c2388e7e25591e38516b8703665fcabe454ee0e56d79b1fa0e62f5fb17322af8cca3f2709992873f728ad74a910df853fee360371d7c7475dd52fe42469311cea5253df7cac3abe51862331a8f62bdc71c6a641f7944c35dd2b3b2a5c4cb309eedb9095a54676d67723b0812ea85ffc2fc309942197910d559dd5b56d8d059b42cb1f855091c6df37297b04861ba1e3932775128303b0a23437e55846e147f477e555b42e8437caf63a2eec9820071e22038a98fcdb8431fdd3fc5c4508a8fac578027530d2c9f597260a2a063a2d63e28f6a2d1e277798819cf6e81178d0bf103cf097a483855be72afc3012dc71c17db4e552c8f9340b398828f256f6de3a4d0b5dd4e9614d7d172d830997faeee0fc8c534ec10191d376800c2d0286264a4b0728f6305e9c719720daaf23dbece2b6c026b1e8a07546da9e02b380fbebe9470b6034be10bfa8201dd68127b48b706982543c268032162d5022b26fa030dd241b8bf3006a34dbe720f1feb9bce5a4424fbd69eee759ad7b33b0a0445897a1bfca1a316a8593ec300929d70ed0b00603b2abd9c91b9e93e772b6c048d7ce1582748d6529c2683883d0f575727d382be378e78c1b63c3c730088fd5d91f07d6e0fab0aed88d1c59e4c68ff8a398f17459c9a663735ad5b7627afdd58dac21bb0eb701bc9846fec7a72a78be94c0d76e8ce59493165ef1cc1272c873baa06c4360b4cb509f9667f2a72e2c1bd4ea573fce1dc2faa1bc91cb05bd87ade86782aa2a652b5d74e5872ac72bc142d9b7625f269bdf4aa318f4c205ee52b48c12b5fd3db851f475925f4ef7261af5f41d4a1bd68f159efca7d8fa3a214e0ab95dc2fbbd3ea3eaff04e33d1652b95a5c8c6c5b00216d625912652fec7ae7075956d873ba9fc1fe057547b9f5e86bed1633202ed997b03d16e461ed544ecb13e718fa7dc5c762e4770d59c0d4b1aa4b15e1ffd4599681a5f3ba0e831cd1bc7220e42771e3053136c03d216c372245d726b32b3e9ede180fc4515ce0485bce61830b29157439e95dac828c9d772d1f76a5058ae719f93efac0026bae18797303b036830dcf1b91c6e19f108ee729182339f4820720b8fd240d5bf6d166ab856c1a9625e7442df9c5bfabe1e795ed2f17cb815b3470e98b844de514aa344b0f9eb0d165c383c9979117aaaec307281e201fc957d768c22a6a586866ffe1dfdebe3dbf172df69a6f372141d560d3a79cd1641634e5c60ffdfee4bdb4017ee5bad45b8bee134dc6de1cb35c9f6294be76b3337685864a2fbc720817534cd126dba0d34031803be86a44f2e8c1b377241b9dcdaa40bceb600483a1efbfc54eb50e62d7271a478069371792a1cc19b72fd40e48e4095b92763b883f81312ccde0d57c35a22735d81b40801c895e76772a79e27c5baa963d0ec114c3339d34fae2ddbbbb9cfd4bbe0713b3e682295e6163ec4d1adedbe86e533b20224b057f826ce7bdb8920c0415ba65c569822625e72c6ee52dcad1d5bd75e41bc57becd0f0894285fc36cee377c306058abe9968972ccb7ad8cacdbb484e86ac47df32749045f1d2c090784fbeeacbfd4291bd0eb72e94332f1c11d73710ecf0fa1a0f7981ed9312cc249e22fe76584dc828c8110251b30b6cfa2ea44782aff371f285440f9a8fcdd87fcae59224c8fc91d65d659279ddc8540a274d4cc9abe8dac634f2b881ffcae15a3dd401288021f7ac97ecb72dfed6cb5b3f794fd5750f397bf9b264bb1976068ebbf6f7a6ab289bc87672d44e8fd7389ec2065f6ab2cbf3850330ffabd9cee5850412381e929cfc44abc74725bd323872953234c2f2229ce4a744e089705c938b0a16f0ec107727f91056664183a4b4090c86e24938caba2ce520eb9147c4a063a9f1357a93c36c99df4230de49ecf61c8dea36f13f50c02a3ddc6b94462cede12c8b6e5b870389e6a3ddb72e1f627578288e831a5d63314b1393d76988ea43a976e49eeba2e18a9e82b5311f4a0efd48381d9be24fd6ce498c88faac883748bcca8e6ed7b38f2aa03f0fe19f57f5aa80e0e092ace7a47ee2d8d177f8d014070140bea0c8baa8b5e72d45111cffb43fcc2a914f0dc463c0d38832d2e6ba38ea1c3323b3fdaecbd139fe81a1816289048f34827a2dae16ad336a1727ab09df86a8922a2e1fd00a32ed3688a0f217add30c56b8e8be2212a0adb8cbec72e0053848a79aa2b2c92dbe42a9b7b0da93b28df30afc6e3259835df3b14e700af26c78800c106b8d974fd4deb75e03a904e186fd905b3dcfd79acb4c81c19642cd86fbf4bb32ca9e14558eb608629169e355e081d0123dcabe8c49247cb1e305c9c8852b5b745f33e5361220a3a357275fbfc1f73fff3fd27066e95adedda528e8dffacaf74b51121f06451e5f3dc2ea48d6975a4948f9a5df9c68966ccf3339516345e840f2a327ce1d82426114544199332df64d7afd1d928bbb6c87e47ebb49c93bfda559751535c7f3198434b72b16b9e1c974247b918a6291ec798f14d8fc9559f9329e67e52b8a201c146bc728daf02354878090fce10f2a14b708bf873d4bf4915a885455555909a2c7b1a72ef7f3c132b47d572f083972e391e986d9301c420f6d3e784293ab22ccc66b47224143dd09eb480188448f8160a9723780f189c16ca716e25f2b9fae0d0da8672c96b9b47c63974879cef817a8cb84c092c40f07624762e8def3c925e3ebd4c726316d1b58974fdf8c995738d435bb6ebf0042f5f9ef09eef6efec4566bf28d0ba8e783382db9eff4fe0dc909e8d67225e4c9daeb98bfa2ae291b13027cc6472e69e79e6626dc61867385c1b5142a95f2e7622ef8a01ce23d82f2bfb64a23eb723c5464ef22c316ec8e07a3e43b2e07c61fd4c810a7f41e7ee7e837fb886f97726b82355e8c7cda3e2ba4653a792e75f6713d441940fa3b402d5b83a21ab4be694aa2b1b32d94f390c5e6210ca8097d4588bf492feddfcaae86bf2885d71bdc72d34382dc48fcf6849b1cc0afd1624bf274e819325684b342aed25eba3812e77228f69c076c43acd0c554f2757b8a030f30a2436ae9fbdd254700fe8a5b8f621296ac13f5bcdbae39da516b225490d6f3cbfa6cb5ddc4912dad61ca1dfc008272924e9e83e293e83b15b58269e49caab2e87fe0f778b92e4dc414c878feaaab72faeebead08c5837c8d7bff2ecd2bc17db7dc20f4704769b21cfde6597602717249035c0816c920062236561eebc4cc0fe84ac0993bfa856888eb23886a8379086baf742030b7a11561167bf889fb460b2d8a96f0457dabbcd3dc8910a4ee2a72238ceff2760a3e7b196faf29ab952aa63706c93472298bb7e6a556bc7f7de54a9168fd62961c9d598884b4c8754adbd94d3e4d5e5673ade413909fb62f7cb1570aced772a84dde2f69d7aa92f55663f8fe0ec9e531e174eb61742963ec247372f86db471ce1abd67c98615b0c47b26d1f3cadae74046422c55a3ff4971d0f672ae559506913112a99a49103531c2e9ea77994eb1f487e72db6045ae8d1a0b972d904889c60fac1e23edf5b1ebb33a4cd434ad2c734abc88796416cd282380f7252a5379fefa447aabbc845314183027a7cba7a27c97563fcf7696e280381b17260a7de9ef9293b88a2e2ba9482e4485aae2cebbfe21fc6f30ddec799200dda7212867ef65e70192a221e7ac3365cda9c71dd9beb804a185386df6b20512e9a727c2b456ab1d3c0be34eb5bc7ab1b455f2a26441bf16d5c680adaed3dcfde4c72d3648c9f49889e3dea01943480209845fcd58d906f2259c03b11ac1ba8d2da7223df912cdeffeb6905b5f56b9c65dec8c680d9ee03768a6e1ae32ee0c4c9eb19f9a0d497494f9aa319fdaa573471c52fdc778a4bbbdce896b7082b583afa9072713c56611a38aa410f3f750ccbed91d7cfde1567f747fafb70a7b8013fc5587251e1b16493fd2ec5a2d12c293dc5074ae62aa6cbe59691f8bdd932ce2f11ed720b2aebd3ccbe1b872028be3d7867bc928b4157ba7512b8bdee20b9eaa948f056fd72afd64d94a0ed0491ee4056f2698c0cf543d29df1b9a8036d1ad9105a482afd01f07dc31bb7d66b7aedf7e7068c38f48d158d62b25126402d0e48561a365e1516100252b672001b97339810457723573c9821f83548878ffc8362aa257f2889d6bd795020b085752b1f3f32c17f552438a576a80b863775ec13002344db72318a61db21c07f41aeecfa64b65b978e7f41d7cef99b6dae5e78b1bd5138a41c9cf5b58c0f784d06214c946bd25b0fabab49b4511dde5d1ebc8bc68a068c227219b931394b77b77ba103aaea9891d213fb0bce127c7ff6160214e60d9fd29b724a2e17656f39b3d2628540c27098966615c8f9c95ad4f27c07d215dfb98cd907a1f00d40b7307d4ca789b10af430f527fffb83bc1136109251d43ae8fee46172a0e331ac3fb1424d6d02179db83623237b791f7ed4bff1ce0689093263cdbb728167ed35dd0ad06e7d57503fb43f92f24297a61f107c5644e2e89f36dd1d4672390e63269e9876b0a5a36a27267c255978f39d5a15d4691ce8cfdb41cbafbe1c3977ddfdf3c890a46e0b8bf698ea18d17bc9bc8b9c924de6fa373b5e78bbc072d6fdb70303271da06c788bf737e53f9f46a2608f027018e43120bcf9cefce659746fc16a7e330cfdeb9cceaa2048126fd0fdcd5d934cdfc3dd3b68f1a47b6572317445d8aecb622a3b15e3c23d392a71be96abe309fb83564f006d086bbb94729640b0eeb1e98a403c0c43ac85de1e66901d97d74271e20a6b68480a52b8a0117a4701814df84469f01f940177771977d0f976f826251748050a367c0e86857252e31a9f0029d3de19606947efee67da758650da674b0d57e3b3a32826d2cd563045b94577232200b8c69af5f232d5407fe974879e87d4b856e88429e66ca3723d80aeeb03cdbf8f05c9cbd295bf3e02850761e82154d62ca2d01f85d5c1e17233b0ce9245234ff1ce0bfee9989b37f0b333624e7820790c775b872b2484732f76229c6c720fc4d7bebb577226819abd7c6e60e5723b94a65a2f6eaf36d41c7237804f1c436fc0ffd9f4a49745fde6c513999a94839be4619ebaf0d02c009f17c462f0127a1887628e1cdf30be27c650cb3559e415a5564917e9ef3123399c3821f93840211c59475a0a66458f60b55c321beadfa74d0b6cb1bab43614278f72d2bf6f6ba6e392efca4323f0c7280008d18fcf338f71de1d9622de77448670724287ed40e58cd570d30c55170d8c988a1f565da118294cc84c1c0299f62cf072f09bd590a9b52cc18834300cdd20006c19be2240482caf28a23fbe245090f54489ff3e27c5e8da32f49f256b5380822eafc8c916e965cb5a986274dd16ed1072b072571c6aa857661a7679edea0db7a609cee38e41b66f1d4a875d8379d08272cfc342e2b3e97050527f73fd775657ff8c55907022ae77a78c38939c83df0d72fc31b9be4bc46fce4b43e97388214af29beaf8dce99ae1388ea9793f4cd252729ec18f19af814c8d5d252046b4a53a19cf90346ddc7f935988166d53595a0f5cd6e051e090281244600c5d1953606dec71767bb0217d201a6fabb5dbbfd77072eeca2be858c0438af4f76103bdfaaa4832ba46517f6c30718155fa2b64819c728e281d4cf54b90ab0fb2b4ed343a7335ace7f511982bea03536279fb3cc032723bd308c3d7f9c7f4833fef712c4355d53028ab220a81ff08006004e0ae487c72746c9de3beb6b5f4fb7edf24a86355d2cfcea6d585803fe4b4c0b56a4f44575e8a8ffb51edd1a2c9c15b5393ba28ce6685bd9df1197deb53c894a67071b1de4daa68ff52643dcc89ac9d1710941b942e45daefb470aca18d7b351b6d3d2fd972a2c2dd180d4bddebc2d39d25aba61b6771db02e2b0dfea5eb88822019f4e2130d0b05c6c96ece64dce23fae69ef6ec253ab631108b10e320dd5c407dd20a063af59a5c7b46198f40dd112d9bf36895045efe9f5b4f4889287a7972b6f8f4fa58aaef1642015f873303e76515d2a0c628b715921cbec5e8f8d4baf38b7aaca072c94c68838af3c993c9939e5d54ab36e776c76ed27795014d5c1283db58e6695105fb985ac49cbc64f465db224478ee4be1b909f438cc8fc0eb996b24864ef9437c9fc58cce271d619f36b940ab1e20f82c155f9fa5d7b1800e016131f64b214c2fd684541d22e213b539ea4d16c5405fc9a45c81964d7b1bb0698b0f109260725079a420fa2003452159aa8eb2c96dbf44573d9946d53310ae69a06f8ccf8272a030a86dc26bbe12e9357ffe5eebd50693be8ba21f5d66569bfa30ea1926e76fe529abf91a745bbe36f8366465cf056014e487d98c49295cba874e249f76c7729008d4a9c5a37d781e50b1826d0ea3a657677af06a54f881a3864b9416b79d05c8f7bce373f794b1d265b0aecc3a7bed4db4d598cb2c0470bf44c05ca5d750475134824ecc69459c748f858af1b43c9d8754e0f2cb780b7a80fc5f434e2d915bc2a3880e8f68e2e1fdb55448bfef16e7b3f0d53e41cee2bf52d73d1988a8c71ec4afb67b5cf767c65c6c9f0c7325520e2dfe234571b0abb145a8be59f036f3729ecf891fadabda58455ebbefd262182617ca6378dd3c3fc617288b2afa78f872c648f7e59baa0e9eb8d54c86a5d4cfd3ff65b8ae7e30511404c36b555d40ff7246f9e531e3b6b426d253afb212963f6f876e0a5e194c6b8bea8fa8d5734b38722f53d6e29da794cd8e27b86b5679b422b16f9b7bda6ed700aa12452e8248ce371cfec4ad191afd03062fa457cc24689e13c8c7b58571d1b10f0266156cc03e6238a892215435dd54f2ed8dd12b9d3d88dabc2b74935a14dd1a550f74d12109725ca5b67b2f1686911e843939d7c3a7987e5b62a42492c81d86b739135ff2792b4fe57bccdbaf818b41fd79253865a047a2d0dfde3d7e7d066175c3d27b8db827b21dc2618cb1f4cfc44bfc38e7d006d42fc72f931b768973bb3fe509a2f559726fda5586b8c591b2fa91a1fcdb0711551b8a626c055a48bb376df0e8b2ddc572e86c019d864c83d0b41f759b3234fb4e981675be11bc02be338e8c65d01f224dc43f03580e787971e48bb3a8553a35572680b14a686f07d8bdab7a16ee8962726b57be7f1c2f521c6842a1dc266ede4fc7224b7aaa21cbf4f6cef054c0fbbe1decabe12a2de6d3ba9c88a5b2cdf9b817772e5858138f87a7f472f908e7cd897228c92d5e183d52e34fbcbbc16cc1595c73b5b7290dbb7d4bf88dc1c0186cca72e69afb5ef3161cbd1d203207f94cf0a623a467e88ef009aadaebe0068915086e62c8e5c54c0b8e83ab514e899a1365a59bd3b734afc626ac7a31c204953ec65c7882ec1d140516b72b0e4619cff0af2ebac556617e134f656e1a32d66b9ae67207151532d4776047349fee1b7beb081605203762bd2abdb5c068e458a5781c724d0859c549c976f5c146046370d36b6eceaa13a1c0947f296f1634855a6b78728cdf16fa577ffd2eb122eaffac9a66cc5f04369dd71f7ecaa1a800c257abf76b470270b62edaca877ad88d2791728a0a7e332215b8812d6c5681f69829997372b8ef212335fdde6d307933992d75df8b9a80d36fb072d84b2ea165970e93157202a1ff61fbb879f410a145c63bcd6c0e2fef2cc38c6006c1ee472d171d6dfb729f41cb472ce02e4bad858e49f44ea69c55c76d3004362f56e60174565cdb342bf5c3955b0017279e48c886514741dc31a3a190227615bbf19841b50cc6bcc6727f04cd5d686cc79e74e2f94f5f9460c9ddee34473224c63a6c3a04e8b7264d72df007f18dec4863b950ab6ee8905cc295a647a1f69abec6204c7b53096bde872656cf794bf2140e94c313a949a85a4aeb009bef35e0bdc4605beb60c3ad37872dcf98054a79225e29544f5378605f3d94c6adc610ffc451ec831e8bba2d8f600c1fbde80b845de65a3c11f2a66b26cac0e7c45e3be0682bcf0804e714d10fd72f2015f60fdee72cb159cc0531a493d5358e4c78dc48e255f43d0270fc15d766e65b2df63ef4578d61e27ddb052e0d38ef4a1bf863e7f8881ed3a5fabe8537e72a9638f11c0817dfeb3fa45c49645b861602fc535eae1d463111bb85ca358554dd8631156ef707d3115d12c233d032e1018e5bd9900a445c9654cd592db74e261940096a76301d19d03bb7215965db049463ab10e26e9c745d4e1ccadf2ed03726d301f72b382dbc74c1852ede1896123ebf9edae2ea1d14c0d54e103475f1272fdaafd394bb965ce904672e36afc4f04d15db5a2435f0e4c8ec27c87c211f272ece25b348c684a2df77655490eab2867e9db517b414cbc9ec3fba6c884576372be8f5006bdae1ed8d035180b8af11d2d1fbcf614dbb9864bf63c6797ef660b72b616360ee91d4894809e438dd9a0f5bc75b4722222a05d0026df48f92bd56b4f0f80538a7fd255ca6eb503ba73f6ca251c920502f5ae5b9c079561844957566464ba5dd621bf7af60a5ce30d5b9e94b06b9cbe155e338915392d96ff607cc072575e2f6579bfffd21f2a0d484e5861b15ec407e6cbce78e7dd37fb0e3a54414b020c80d1e973560f3cb85a5d710cf5eef6e7cb17a22cf78d525c434712eaaa72e1f43a094dab937df441de6fb6ab7a02f1e0a1d7863268b66b472a68f4261b720ce43f68f45f7d3b3a18fad1cc565444c08505cb5d81635fc9e5c988b1cb6772d7b4ca9a9ac9d4611bc7f224cf8967b5b12014205b44c491b3a54a7283321a7220622180cf75ec180168b212ef5e7b65eb1134d2539cb5c6887e798809b99d2ad0e3b3450d6c8b986c9eb50116f276f0f3acfee5878fd522ef785bb6dc3a827215233583b7f362956a647e7124e0414fac85321ca164975be64a6dc0fd824c72f5e3491df60497eafeb84cf72fa006a44eefffafe856434c0e0f76c65653d921277b342768b1bffb06775de2413a0696405dd775b849fc47d127da44f70e2472eced6c8e304ce0646fdc8126be1d74fa8024a76e62c5c10de2889a463fce7c72aa0188cf44e06864bad97e65c60017457a9732e07ca2795f315b1aa38c73c5725c8ff70e533e08eb173476ab19371f318ba12061018ffe54e3f3e6fb5284c329d2e8ca5632d0967400f1e9fbbcb91b49d41e5247dbcbc457a978d680b3ac2172d33434f79d5e1c3edf2fec4541a203998502cde579cba35d0f2187827333fc728ce26eb2db316198e63d347af7a1997805dbfc56615bde4a52db0986b546a9725e553c514722b3d8daea9dd3b88656e4dbfc89140d83623d8fe913736e9a427232e29cd62d6d50ead9485f79a90c3cca9e595aab7f496ad3806c532450b0317220b8bcbe6219d6d2e6fd29fbcbff25ec28d44bc091353fa657d3353abcc8fe4e660f5aea69518551d061d0155543125b22783089ecef1ddafd27575a991c8772a84d12deb88211fc79d21e2a055acaa6105f78c241e951979acb2bb2a594dc32d28234b9051395e2dbff8510dfd785b8cd633e69f6e11b90aecbf375cf492728b1119f36a599a6d31f023a4e30997773004bb5bca5e683f2a0f2299d452c1a7293ca250b4a95e1772488ea4a25fe7f0c0963531b348c95a08b8b7ff3e8962b7235c2dbb190436ff8b401eebba8bfcca083d63a9b89357833a5ab2c46f902b3708f5263d6d137a6c8e5db626e51aaa8ec9a8757b897b27c52eb565cb8a92b4972014edc71fc6b44e24c1eaad9a597a6c2dedd8e5a850a9741b7fa7fed15466c4f80c56c09cb3a236cb5a4e0bcab2e87180d86b2c00bd413ca682dcfa05082ee727b7cf1ece9f0815a6d144df5c66c9f02f6f4998964cf3b61fca780c66f1a1c6a9f2859cc6803e5401aaf211956242360f13579b5f739ee91498edd71379a3772083253e323546850984a7388a757aa4845dbc61195596b3c1b98e5c2640d2572f338011aade5f696b3c3a3ca1e478edc2e7d56bf1b65634336ce6ea970a87e12d65675b43c66ca298da10821412363a47695d8d6ceaf29a16237f4fd50a4843d313da3a8be96eb9a06d39cd83de054c05309dc449c61b8552d3292d27613045a849f1db1960e904025c3fa55abd08f49b6b37ba907e16ddc17474a41b4b5cc72c9e0912eeb536007db27674c514b2f75b2ffe82d2a028959d13c506b7da974093ef5e5d3d7595024af012fd7caf986b9dd8999716679b0dd078a9c527449d55d45dccf6946513cc982821cbff5509029d1af570fe67f00c01f92fc245f74233a58e1cd2d9c67ce1b050db3bf35fd789349abb412d0f77cb813beaf051c11a9721f0d55944da659958b40eb10541e1b832b38bd62b086f850df3616cc528d2a02c7a127d8c9b572179fbd2692bee4fd8b80d889950c53fde2ccab3b3fd3d8af72c73e587e70f5d7774aa6d84f9fd487c97bea9867a3937ffbcdf7fad42a7f3172dbdc3c47aabb4506b51693a06ac40d216942e52bdfa3c73028bf2c7195da4d72e48fbca4a4519ea7a529324b216badff21ca7e3687c8c94aa1899f0b389b657272e42bb271fef234c51570697617c14e50230cd227e713dc126d40a1ba38317272f93ce5b540b606f98ca52a3c1ff235eeca2dfdfaec31988ecee5e5e56af44463ae5569d856a678a78937d7564e546f49abcd882e43324c02937cf1bc858e1d18359a378bc6b8f160b71b528b5ec30c7cd99b9bb641d189003c0af15e362e464f761f93294362786b78c88eadc90f1b5c230e7c307168072a7515333348663cf1145fdf3c0d136851260958056f6340329c934072154f8c456d57a106f0fd725e873ec3337fa682fe8487c573857fbb82a8793a4e12ffbd5b998f39a3dd776cdcd08c086fab4620af03f33c3486d08ec02ccd4474962681c82156ddca73c472dd217b50c70304feac7fef30003e652ec9fed8c9689436a8732add44b0d3dd72d19606d4870f0e6a002d56a0a6488a698d0126278e5f854f362f789e423513726f8126730ddf6900c8a076a5654d04094f4d5fb1a717c6a66991f2b31e74c372c0f5cee2c1abcc38b856b7007b509c2b611450d3d42a1492909029a002c5a64cc1aa6397149e7019e4f70624c598ec108cd84bb7b53367a3e5f471567c63075491d7b37267456a94da84ee31e311cd0613cd0c967915c457a462078eead4c23cf1fb834ba2544f0abad85503df5a7cee9b80b7abc586a985d0c7c608ac7a713adcf75a53bea5ffedd716f5e59eba54cd220d9de84c14c9ec6988eea54eb6107257c6c953440d7cfd0ab02169646e924704f0aa2e110fef15f30e946147803b7206535054dd1f8ba638bf1f0abc6aa46676f46c8d5d2511c6259cca751887b12587cb93d9f49ff9efa302f1f0a368f9fd5039d920a3c3ede5adc825169f404d72ae2424db05d1c4065c8963a5b3ad8f37705858c88c11afcfd903ee82d11fa272fbdf4c5a965ddd8ba8cf7e93d6f6d0b684ec5919c7bb1e4d4235add7341ab544cae12f0d6d65885c7e3f6738fb84c8771a2711b6586f431719674eab5be07c72798e2f190c53152285c27e9e347be07afc3f8f0c7f5b405d38cfa65a4dc40472528365de780997d5d9b4cc5ca6264a9d2b2839e765f64153498537b19252c372590d8baa0c179b0317bd066ef673b528d47bebe61f76e4da3cb43f72b49251728a6100c768a40d3877a2b2dfc4e39183520a97958e272692c7ea4397fd6d40497af21600381d675ba31c804c20a96dde8aafc7ea417e7c5af46f111ddacb0d6112c8b66e5d391b3a6a3a40d4a84a0189091e4449b5cd980b2dc52de7075049727f185fcba261325ac4fdbbe267cef3f84fca5706f8519baed5c653a5f5f792720f99a2116f7824c92879e7f2c0fdbdc15cfa099360f7eb0f12489f859f11190dba76150cc44e3727b934580d19eac9cadcebd91d9712231d48510843f4defe72af626a506e76821b975ab477f765be36773414704030287b954b70daf273ef72e54b00d82872aad85398505a368c2da7b66e9231e45b94c91df48ff5a31e0b54eda1cd57142f62b503cc68e5d0a52f83cf690545fdeb01c582c009be3931827237d85e79a8c66101e3da515364dbc8f4987899dfa71ef58d82806ce24b5721721df6e9e578dddc94199a17ffe284942014d5a8b639a8fc77ac0251359c201012bb143aabb1569962ec70f9f6a8c8588f938251d92150b1ca99ff3e356ba24f720d7063851284beff343b3834a4437b93c242e365529793207fa047f7e4a49b72235c56d3fa67d1f709ec599e2d665cafbfc138acc76f0fd0417a829cc0c02572fd04c4aa555e99aee3161d2e03ea578887420c07b66b89ebad74209cdea1557207b7a54e45533fcdf923a7b6ac6480b6d52b788eefbf7b40a5ad7b657a0a6d3db5a8e73c09bfe16d4ff4a21b386a5c56725f6e322d0c251e5ad171156fa22d7279c61826a7a6a8a0c21cf7dec0def0bb72cefec4a249c317f00716e3165817023480bbefd8f20c629e758ed3e16d98ed6be622c8d82af0e35650139d700c393043e811c24867225af0f492771b2d0aa4f9a9a1e7a91b8e1de60a8c95e8399a6f7c74d981e55856d3867677eaa7eb0b48345c0caf3164485ff152d16b4fecd0723f4ea2739d25fae225a779b3059eaccdf385c4fc3155f9537f2541e8a18672036c15d3b22d02f5b2bcb5b68428af3d9e92b29ef9cca49258799dead40791ac72455417225b459e1c66f6182a884733eaf77eb76a5ca680b4dd709fc24f97ed0b921c817aaf5ef3b9131c442d77c4cf51934962cf824e39cf790221697d7cff2b92bfbaab339b679e9dcc2fe8726b441942b7933cd49af348058237589e1c30721dc981789a58ac65feb282325447cfdae2d05cd467288aa5ac91ea2939791772be6e77bffd0e83a364e4686723c4c64bf88aeae19c8dc315b9ed877d051c9f7299194406e99627aa99f4d1428f93276589c8dc762bdb144de3a11971f3a91b728b961a291aa48a07d35c19588262a106aab29bcf18f45961f20e4e5f665b4b727e8f22ede38e9dd5b36c72a787128596887f51a3551e8550ff09c3801d4668360199cc17f63cbb9da6bcc5dc755b3b96729b3b4f3618dd260208f8dd3ce49c72e86759667898c255e23716206c439ec2d8312429ba2cd0cb29b73131b002ef30b63d9dfc0fdc272a6cabfbe66d78a036579366555e514de4ca32e020bb7c1c72deb63eb31a8e67dcbac51e780fad9ce293f46b1c678fa15788883caad6fecb7258c86ede4b0f9be12fec5497b26564fb37dff1d6f80f4c11ad4d7f49858330727fa225b58e25c466f7a4b733c4ed74564bc43c9e51dfa5493cf095184476e131872278022633d20789d6ba424a5eeccf095e2e23c21026374ed2cf9addb99472f8e434ac5aa8168c80d3aef51ad127e00fff7f82e7c8c75bcd14bb5af80792726492d0df680ae6a7e2cb27b1c02148828cd9073d5a292c3cfe3a701a42785e088c020b79946ce7d8ffc2a9b6a4a3b86032a52304d2161d2f41c11008fda6567216a8b4dfbcc6f0d20da25568d8f85b08aac00dec9b2f775e9a5b48d3e47316727cde88321d658d0ac589824e2c08047db04cbfe1b1d7dfd0336159655471ad7245ac3475bc38cd932912cdc6d480f933542c412c012ede29e3a5aae7fd534d72fa80bd7b8f08e89c38420bd5198ddcf25a64607dbdbfdd428fba278876b2360fe0415610f47d82ea0bb03ee4a09ea2ce9f7435283b75ec441f67721205812672247a2d664740e5d1e5b6a2316003c80c936f550a17386b0fbf1f6ede4022423761a21e65484c5951d6cc1d3b0da019c4c12758faf5dfe7af7c30a6ed63d4684ad459b36c48c913f9d4dc44c7f6b76a56c04f89770fa6ba980973f574b8ba5f71402bb5db11fa74032229b18f8c427f239e56f61f2022358e7acdb803003756050b7d1e9e374f27da26f725298fd193a13600fa4415a25e7e8db1decbb0c6f0165c9233a14459abe8149c3365917ae19728bee1d63b32294b6c3533d9aaa51a4c431822c2e8822a4afe7d57e2f73f67d812c1a0b3ffea0b815e0df6abd0a4d6725d160495b908acac0e970681878a9f040ff794db2356deeca37ee54bba5d2f72ba4991f033a3520b9a8e91328e67343ae0f1b3b044a01617bfa913b6e7c7fc72320219274c08e95a82974e8bce19a45e38ed95c0fc8cae4f307dc84148d3d972211a19052e0a057f6cb684708dc3ff1f5bb57fa2a6c08d64971727a6cbdf5705423cf77d4cf94ba4018d400c14b129a83c21ca86da8a5074c49fae49256cde5a81690b80a50c5d311e5a33597e7e9f109b438a34a3316724b72168434cba176e23101bb755c2658213fbbdb9208804c442a0c948a1998bbb4b591e638981cf4da25d60082781a3a5ee8d25868db1fe4057751e1715986353194afced47c9e4555213a4752affe1f5f8948bec3f181ca4deb22610d213d6d6ad0b67f66b798372739f50ddeea35a9641528cde643f9208bf6ea310470248b73c79ee9018d5e122506f80395a6ca58ef30678c34f28cb8bb62a5f0687a623e78cf3873fd52c3272780f2b9536c43c8532b8c1cc334f92a2e957c2b297d5fe9ddd6d85aeafb429637e0139987393035e3c8ca0d3d956fc7a197bfb013dd39d6ddb7b53685dafa772382ecd152bccc9a451289cd85b175cd6a63ba6462346708bc1e64f0176eeb145a00a40e7769c5df7a079a5be4af619edeb28a2022eb2182c5d92e4d6ea6263729e03b11baa021f6a919ab6484f8ddfaaf08c394b27c6061c7171506f5cbc3a724ac902b0e0912feca9652fa5805b88f2a009222fa1d3fe3807563dce2c6052316ea553d4d26f368b481d248265301204e58fe5c083cf02ac42e208963bf65b7211e2c4c38d3f373215002836828b6684c47461fb4309d3205dcf04acbe449316f704b6e1fb0868c1114b28e6359bdf4319a4a7080012a73c19f5dd15eb143c72960d7aa8b940a033ed6db1a213454ea102efdd32c71ce6c0b6fdadd909a9dc72443b56a5f387740a7c3490dfaf0725c4e086a75bc07bd468ffb3cbe831bad072cc0cf1ae5c0ce494eb9325895c1d9a3990b427ab5da81a95211e75f743dd9272a623d6fa51c5378165065355809d7a7116b2e4a79331d79a4e3bfb31477ba3725e395998b5a71e9b913d37c939bd9664203218d45a9626157c0a404d050e747285c0429f0649565f1fa8186847b97fac2a9b841575a9fb934b1fe0178834113867dc8fdc9db53f3d9dd91c6145008400255e62cd91c8c20085022d3ee64654722f11d026f8b500dda355185c0ee78d0f1f9a95d24995ca6abd93dc3dc43b7072b72c069cd3eec16ebcde5b1f67eb7135c8cbf3246fd575d3a0dd51d19309fa72582d49b563f05ce0e57cea99a6adfc91b2d37c524d4672be419997f7ba951020ecab6f9e9894618e0adeff47337dc4dd89e0447a299e59c504b7d88fcb497972d25346b8c094e618326d324107c7d3eed3c4c2331df681455f344fe9bb814f72781d5f0a3f5bfffe012048cf9440230623ccde7b00e91b5ccb02752b8c2f4f387bb37949eabfc431758937c8435e6ab03137437693006a47e040ccb82ca39d723a1856b7b09bd01dc2c70e3c04b2e3819772267281698269729859938cb0646ad418fc8b6e20826c8d9e739ccc67f4783f15ff03237bc7771bfc387bf5e1246a654d69e8a2ae4cd8fa8bace977177be808f69bfcccbf544f1b5e37efef986272a1426390744687ab9e9b3c95a695f79130bb4a9829e67bfaaa7a4512d98553720091931b76e9b9f6051056a28ff97870335308703299fdd590653afdac7ad172808c1e09782b9bcd570981ed6cbbabe24086d78eca574700ae4597c6f7e9a24a9a38c1992520ea2241899d173eb2f3ba8157702df06d58ff2d1c8c5230ab36723781d080845133090487b73556ab2123bfe9fd42d787195c16bdb263f408f358983cbfa6624c92142283c18198101fbfc512012e7be6f61e358951e1da145a72f14b1515ff8bb53980296d5c4df873cd6089334a23c41e4d42e1fe5bfd613d72d9b75080b8fb4dbc84483fca9448ef7dfdd754b972e1640df2cfda1a5afb977227e62fd6f498e37866c32c80a90a9324df220b5449d6913981ebb1c7446c7a6f5286e99e4d74f6a6c04be539dcb7dde5b77ead4079be8f879ebfb53f0a676b72e88e95b1c4bbd0414422f2ea1ec4204f7e46de588dd2e3fccfdfe1159ed104729627d5322f2dd827389b7b089d564a8698b5c7450c5f659073c04a7919cda34d86cf7e2cf17c7af9e40300b43cf61afc2e4f21fe23056904b9894eda588c0c1db160d4b46c64f908d829609ecfb627d7cdb3908b5bd3df99dc4a5b603effb972991aa5767e293cf087ce70e31b53b2b21153b5263cbc3672a869ee6c66bae57292d9e42705147a5830d91e0d7ea102d47f0bfb480b8d7be68b69a82f655c1f722cc0ce381a2bd3d49c8b9190c5b4047da99bcd1429fe8e975ccd812bd3be78723aed9a1ac18ca75e484198fd88e53d42005eec40747a3a4c4108541b8ae496139b884a282d116cd3b6000a0f1aeebf275af46b196b419839e58d55d0a3825f72452a2d654605c0bec2310cbbf78c3fa53e99278941ef1324c028921ad9e547720429c2aeae23e04246ddab020b15a2978ecb1810b1dea58c9134db16ab0351720984121e55d996105b8e5cc654cae6aa90eb0bbcac13bee3cd04b35d85c84230c7aef64a2e8f9c280f9b983a63b11e4242064056c947e4e141afb46c885a0666c15d07ad9642d28c6636ee959a1eae6ffe12b6a001f3e650a593c685ab307c72ceab67ba788ddd7590f82bdf9dbc6350ea0bc21097a8a5163eadbcb289153f7289f7ded7bb14f2b019389a47ca3cbdb3cb121f0fca975774d4eac267ae2ebc72de47df659d445f38d6a73157b52441850ebc9661a23e9c6a5f4ea2f3afb738478001e528935d8a26338f1f79dec5a56dc0f5957e32682f627ba045a663180a729d20b35d64c564fa70f6dcc8013d193e81287c1fa0d9594b903bde363d7bab72bb5c7ec69f908fd405a3b2a5c323e4b3b09313baedd8daf449c9a57f8ee48972560193fc04b2e6736719a802291d16dd08239fe9d29416b93a0cc17381bb157269a4379b6a015d67e8c28acb7f830ea7b74c32b9c3e9dda1556560f4a0900455b965c19c2fe9759777010cc02becc94be0f17d7f7836b998c0a8f637abea27642eab1b9140f54bb7487cb9685a61bca8ac1876f851954ef9f54c4d0da2adfc7064a3054a578d8383597ee92a8255b81a8aae76c1637f3d437768b9b8f415ef723d5dd97f247be925699b89a95db9517c4b54b0b9eb8771aa8449c42dc39cd06a62c9fac6a616b3699a1958c39be6023bafb1023866f2d690106609fd285fb47232f9f4295037b6d0ab50b8a1ed04d4fe9f18ef6d7716137cc0bba4b549caee53243484f389c78f9e21a5691e92f81033cf30a170a071837fefe9b05e1e0701720ee01f24859e0634611c6be1c57e86baf9013a72a5633a1aaa1b029949dd67725523c6d0154a76fc1ab51d711700bf5aced6e96432f0516c89bdeda869be5c4111e1422ad20c40d3057bfc9cf7ab19083a34fd3f9b6d6bbef086c12026686c72df31f481748f148ec711a4e90b4c9b6f549c39ac33f47ff5c03a62d7ab2e3572730bc51af358f91a948a12ab6f26b3ec0dcae81e85408b542c89887f91a122248fffd65adc817e91aa319e2135129faf64359a132550c3c4a584b7257dd1cf721bade855aa18c075bbd644b8add58362259ab989d94e33acee8f6397fd24c24d6267cd1a7435824fe6e92a8265751ca5f73ffc6d77d7e3e1cd5c2eac45b01425c7e154c7aebee7a77ea0afb6eb8527066e9dccce448f6cb3e8d2424b2597b372233e4437efaa06d576b23352fb17b398d2754f39cc1f5caaed5c8159c2f00172153900682ab4b8681b5de4a070314e64c714bc26f543f9c7a19f14c524408e72e1662f099779adb53addd0c499677e2590692f529d74e2421bb23c744f778c72897b97eae2b7aab905deb2ce3b494590e7ec984bc978c08a06bb81604049227253b4b9341f16033ccd6419d3a69a11283685dc7e14cff2126d3dc230242a707296a36df4e2e2497941fd2fe346e6ed962f6cd73b2874e16bcf19523043b94d721793ef9ef76219deb7c9c45fee7073d6daddf539f9572132154bc04f4e7f8e49afa98edb4bc7c5b068975b9b93f7ff6c58a4e2debc35a8490065d9aa30906d72850e652af61612b725572d192794046565d1f165116d858f9272b037b59e6c723604046a9537a8754b151e41f1b230d79dc0f778bdcbc1511cfc47c43569422447bc440ed0691a30f35c3e4f993f9c15f8e916aaeefc006da8511ea0b2b5f472f5e341514c3de5888ee30637fc82a7d169362e109ca8eeff1c34f7eae1b79e72845fa2f211b14e39d4f99f1ceb18007119a5c4d5241f3f13845b1c828966d1724ad3d3edce18a6311359272dd84db597e039a7a9eeae38c2505316f31ac7a021d5b44735e5d41ee056fb8762386dc662de5cd967b75bfd90f8e27c89b8f45e72ac8ecf0b06b7fdb5cad0a522bd63aa5391d2ea1960f9adf23d64bcb9ba1e864e93c059b715a095b0969c3038d83820a53378c13bee8bc606f4dfce73e266f323730f93a2047875e246f2663639cdb40f2777e17da85c93e0bef41c989cd0354ef26a09980ebf15fbf188797c99b1b64b8aa1c803cbe3ca64d99a888723b5eb72f4b6171df8f81fca0372bf77174f2fca1701a7fc9fc082378b3036107b81573f0f9d84c6dc4e71ebc49dbbea25c489a3337af319dd3174d823bb718e86ec4d252a3a58011f07f1ec23d7bdf691150a35dcdd42e05af59fad513e79ad3d618b369e4c3573d4cae7070e58443e92a6d3f11fec45f4373a3b50b4761b1898559e7267717be3a7608e05c013285889a981c8bd2d05c174f84f3ea920bf678311a01e79f96a5a7026a6ab0e78c73f3348b2397a0403a451f1cd3b531134fc6745ac72f3cf4a97681503412d6371e4f5468867a08bef5ff884a6c6bb91fdf323917624a004a42c2b8bccb4c13d0840ebfba1a2cea9198313b9ddb6fe6d5a9eded051640af1b2681360941996d87f809d88adf820515607baa08d7bc9d169861f8ec77296e635c47f32435d8d2061918f2c1610f699eec156efcb19b6cb8bc32a85bc72c36c7dcf71ee4994203a8765e168768a5ef7377eb4577a6ebdac2c624510044003f459bf7676fd5ecd1abaa060047c47894c2261a68f8dc595312a50aa13c75e7a66f6cc26399e56c7103b6ab996433d0014751ef4e0c58b8ffde5334bf1e0201f155c14bccc7709f8b27952191164d23fb0814d8fcf4df06daed8ca047edb723ec5c23ee4cb62fb79b26873d51b0aa6f11f9013e05e6655d9e3ac365c1742722d48fe904c645556b58a3158e4723a92cd51b6c19a71e619a2e7acb00871660ffe4acd36ce8c0ece393413f55fe564e5a343d826d2d19d6b05eedaf4c56d9672ffd2afb7782cf9ca3efb4430e17638d436983f73f8e49d36e93e1207e7c8f272dd30f73eed9d72415fc8fe701991bb1ceefeac0532d1484c143712613dd1027250e31fe67b5799d50537a7546b69922afcb50735b139529e7620968447acbc72bb56bbf7d6621b770d4429207106170175ccb5c5aeae746d510f4431be151e61d323d80522d55fc6acd7a843dab4023ce8d642e91ee9425c238946b2d6eb3364d168dc4d8a5b9d3d45ec5ceda59150856d4d7e4320fd913f46acbe42ecee14048dc04f2cea6364212be527fbc45246a525867dc8b6934e77e6e1d97c98ad8b72f96363dea3074242f11c33bef7030a825a5115322e516b6f7de63da68d311b3fc031ca87cb2fab9747204650d74fc604b6d9329cb1699868a90b50374056e572b62862da3e948c15dc4205259d7e2233ba0b970dd04b582631e1612fef2221722905695c996c5c7c3fc295bb4051c65b136674e69fee858b791fd40274a4ad72236452ade024cc452831838b39c5aa7d30e764f26dc1e5e5423e53f4b3db00728ca4348cd32002df10ca22083f1bd84416139a6c9c5023614eceaf018be2925ce68c52374d4537eec4527c559610e47ad0e50f9dd8dd99278fa88730ebccf118e9103292e6427c04293a2a757d5976c44b715e5d057e8c3007567a8167231e030534261d4c2a0a96f393994374759dc80561a310266134d4c47650f669ea6b268ed6aff6bc2c533dcd951abbf642de222d99c17a646fa0a3435f13e674f32972ac02aba834dff9aad738cf31db3e95dfadc027da57498d2cbe154c5f6104655340f007e7bf8b5bd3df4a348634c65503ffe8936710750cd3fdbd7dfe3d349c72cbc70b8f6834fe8bd879a4a138d8004e8713781b13df9975aa500f4c4de6a77257f7bb4ede8230e17d22fe35ed568ee0735f48ef2b3bf797a345c1a264614b720e4a8b50fda17bb5f42561d36f57ceff2389e6dd0b37c530215fc812364fa472820cfdb28ab2895f69d04a18e42d7f6b9e5ec8f7efccb17b2b8c31cdfd1c50727cad516e105f6d5a92d46b1eb4cd5922918a912260b8dab19e97c21eb20481725c5a7c29fc824b98f69b67850571f6ddaf9c850c352f270e1bc0d7c6b118601fb46adb6fd6485a684d9e9e4201c7b17a77ee487f866a45e49453ddd2433a685803515bc2a680b8e65e7659394f17926f2981336054d879b238fe4a988967bb720669a204343a3aaad0cb6054ff5e4de26be3bbd47e5b33c44d5fdc262fdc1f7251a8e2c8d7d12d2c7386f2db9de5d4509f046a93b1190057bd49fb333334eb4798f4d917f0a9bbfa807d1db9dd79af1e7178db29a5828ffa7e8fd2e91b6df517254ce0e26b2d5f5978e7da73e550c4c50c31d38f638b2603d31a7fd2608bb372cf7df5288ceab284fb5583a3fb651c6511e60377107b876196c5b7ce6b5c034afb04078c343d27339e92a1198974bd254f01649c65a73d13d6ea548321a8c4721b521e122719abc49b265e5fe68c612d3f072ef48db7a3fd5c77faa53a0313720f2d88a9648ce9aea635190da7f71407eccf4c76788a6f9577cfc840fca35744cbf806286d1fcdee016fa2d26fe99a960647df4d4932c4a151468aa4e5cb5357fb6e34b4462a05eac48cd2587c2f37ede033330f4cbf2148e9203795a141c46512cf6adec0618b3eb321c5ae655d528ff3a192d41c92da4a63ed463bbddc8a154c64027b37c1770c5f7c76e0808f4fe05579f2b3b11519a31dee332da1c34a16cdf5b31c7b41eb5ca59a1e42a5c9eb1d0e704c8222777cd8a4e750cd42b07749208e6fed6499c4ecc2826fe8c9c489e1b9fc7d739b5cb2420eff08a4b85b427295105491813f0ec18407b68be0bb02a0fb377c64bdadc7d50314e6960889cb6fcc623a404ed08dbfd1588c3d4fba8719c2cb95e68b198b52ae0383b37c9e9572de4c3b88a97e74254cf13e7efb3ee35cc6611cb94c8342878d5e37688a27ed6690732ea866007c40657eb780134e2c4f1929380722a354cc03f11c4a82584d7270cbe03d09f81e2f857cda15b12752862bf0ca7527e06bda055d197df881797207eb6a091352558edce6943127a2602e737021f4532967b37620b7020daed7695a63fff2106f657228d76f8cb4f42424b2196f6a5989f30e92ebd0606f4bec728cfa938f95938f3207c1423c48f488497b86c1b85b28c542b7d0fb95d7e69807719c4dd7c66fba2e8567f72b1044b5a0e794732f19505b54d38af2695f35b872d5debd75b88b0dea40228b30fda4ba12f541f0daba76677614685c2501e18f721815b76f8549a6e47c5b76fd2aca406a8ad886a8f4bdb5f482b86b145182b972e8076b35b22473d2aab4dd38a79f1b155ad487296df68a91cfde2c298914ba6f5dae5fbd148e3deeb694db6bc0810884892baeaaa058cb0227ad9675be70a53d943f92526ddeefaa69b691e0f62369bcfef823fabbf10706632102c3c98b670af9764016d6b671e1bd3ffdc153af4132a29d3df0c57e30b9e67e5bf19081c1729698c4730b94a8711da5fa3737ae3aa7da15cfc67ebf277418a2036a334cf672357c8d059269047b3965299865a1cc3edbb38b67e5a707db10cceed757357b7286ce74b585e278077bc3705a0c5f00d898cb05c8dcf35e6d9edf61b49768a222c6025fcaa89c3b9ba777bc6699505ab805e7f1f398224785d8f559b7b9b85d724f90c56accacca427a7a5f3c2bd20514f9fbd31be66ed14b7a6860cf6257a3727c8832d35ef7c5c8c6254dac82854af54fb4e72fee8840199efddc7ca0e96a19d9fbacadc7266902a49ef2074709a71876425657cf3992a4689f07ab91998972f7490161945c1b3443b6523e9ffc615e60b50d362906df9c467cc5d8a2c3252a8b1af7ba761fd99814775aa8f4623c2f4f985178be0b87b0cd83c4ae0c613d7245a6ed72b91215071ba905c1b9a565009ddc12f5abe1a766041171a11113d57289ad2ee5e90e2639c76eeaf45933c52b1e9b26b38d9a6dc679a125d589524372ab4178ed7af7febe6ed7e276a3d30010a4e60fc0116972bf28cab6259a8c9b09da81a9f67bcc1770db2f99d87dfded586b8133dcfc99fc0589db5587c20b34726ab058bd0769295157d30659da4f0973c412ed2f5f00eb9b8d542b4058a0f772c2a0e920470971336051c7d7b3cee9db2356d52b941fc0542c1e67a932d945279bc3336562e8903468dcb76d242782818f2538ffef40e9d4cd037e660c25c772b7f8129b1aa2328b8f71db7cff28c458359d5670f94bcf4235e209932470bc721208f5678b6f02985c9acd45e7bcce50dcd2aaa17cc989c0209bfe848916e0618421a3a0b27d9e3ab923bd616d6b85ef54cab6f7ef5f88b1d1d8a88f0da27772e85c9f8d08bd1849f0a741a005fbb3a371eaa2d82e4af051448e968301090e72ca73cfdedc6b81609c4de8a2ba2f1ce54351b94ef4838f3035e51d18c5eded72ad37b4ebff76f469966445de7eb62e09e1dc403e2a300b5c1f0f241a9247480f82eaf74c24b0582d127159865fce7ad6926737271ed7aa4c524275bab4597772b9948a628700bbe7604bea668ca3153f888d4ea9e73906a2386eb9da41df43529da906fb010037e02b543dd8f3b93e6301433ebdd48d1170eb2997cc18490172a3895ecff0d11d8f3b6cfd384b203b18cc499795a386fb8a7144d2d073ba45331dae0fa95c4e758614d412366911b311c753b24bcc9cea12a0d4e89624a3fc396eb8f96a93c03d18d3253f5724e09979799d053605e4b535cb1b32836a6d6d4ecc8ac7072c8d11b7a5b65546210e1413f67a29fbf93272c15365fa452ce61272aa970cc36bcd0003f536b65c697fd0774f1da66862082e96101e40ea1453107269bc509ed432c78b68d375433988dbdc29fbb81ac115eff222b6c3e17e23f357555a62bf37e6881d36766126d0ff09a1ca1f9dd63030dbbb0e81a8dacf3113196e9d6369bdf203bbe18ae3153385b054ac1a009b1caa688285919aac0a383272f2574731eac05efc24cfedbb5b89ed895d4442eb9f0f2b05d4c5b244578f9232b2899f6b6efc7dc9a1e7109c05148e156f76b6595d304bcbb87bab48b4faf872d85eae286ba0be80bf42f9d19e40b7804816bdbcfbd58beceef982295af3e75f34c9cd87ce5d70d1636be0025002c5e2a6a0380149bfbb32947b934632c8234ed517600efcb5c304843d6e080f913cd347521c49ea8712303809e80fb686ba727cb863a9eb9813ed7ddd371fbf9b7dacc38ad3c848d4ee1067bca1058aa96d722fe38f3b92b64f6ed9aa404d37d4d53836c363989e2791fbbf96c98b59561a721c7c9c01c279e8b45949a947d029df3c0c6ef503d73d74a2d849d7ab25a3a423afbe1e49c3e4903e8837734bdca9c72d4280a2228740af6a0df6a6e6c09d8972f8ceeeb96b3d7028cea685ea792b5ee9e73ecd27d0e7c57acc1fd006faf0bf72db88f6c9da278056b1e8b49952d914c6223ab89734290b3517a58b662d69cc728fd05e7221e1d527c4b155ddfecec701ca9b0cba5626c1568a3524328cd345723f035197da105bfed8afc05475dadd84c62a615be587606a0339fcd42750e872cdf4ebed4b24cce7c9b726b30f43babffc9a83c645989494a7009bba7d8f4372bf974b6d94d3617a5be910d15433a3534536246f4dcdd07a2f38d8817e86290ddf2eaa3686126164fab302809bcf4257fb3087af1321aab6f3814019eb88bb4781c236d1d0e49ea6c8318f1bc3a2746af6cede52f6242d32bd17c0ec311fbc31ded3e1844a7b8e1d052d12f87fcc86193b5281f351a4bb5fecd4c3170c88d3727e7bc442946116e5ca45cec872459950135333d508f876d4d850a60eaca5b260ce0fa084e34ed229e902b8538277aa9d3ec021d6e28839225010864d9043d572fc9174f72b6311177e816fb11aea474e5693d590ebb600372315f02485eb941775b64ca6ff39e3f30032c124670f2e840a4cf1b65a6fdd1af64d70e21a345f72f406ef39928c7d73db1f170a019e126279ee0835e451cdc1ed86222f9a208c72c974b4b17348fe004593c4a1d2ada14d6515bbac664c3f246e1993b37efb6472365ba49f280a1a44747c163cd5360e8ad7af00133a60d733d0dd9d5c794efa72075d01a45533afdd5924907ad482318190da08fab0401636fa81fdf4a15c8372eae2afe92700e364b8111c3d723d63e0bc117bad10a4933e3f64b7a91915ed2f93e8c57cc0c2b64b464e99b660f5e26be3be3196e5098743be29a247e7147d1224542aff121dcae5fd99a998b59ef59c92f3713363be9381d78284e694a6eb7298e035b6214feabafb9dba9af407e18e05b8581a2bdc43d7f1a38bfadca7557206cf0357de90e3a94f9c133cbf4d4f5ef904537caf70c4fa6e3a0fdb561701729834f1341609b56c63a026e76351ad8938358192c37f743169d588bce5d11872430546f91b7df4b6cea266dfe31ba9035c3e3df20fcb3c5c7c8bf854fa1f3c2bb48f485dbda8af9da65eba07fc0a2b30338cf566aca91fbdfa4008e934c7ae06b5c70bfbe15db76989c73f6ef46fa3cc6ca6aa177e9d03cf62231ce93e14617200b503c1857862b2dae8303094acc3d1555267ac83d891811e8fceef169e0372aa2e1e221ba850228a14ae2562bea5052fcc79b3f9b177113365048b64f5bd72ce0d75cca9df21bf9a5a379deb1eda927188206d569b2892270953b0a1fbae5c7731afd651758eb430d3f46fedcbba9dca991f990c7424aec6108ef16d2d5f36fec15b0ba705d41c7fa44c8ac10dadd5921ea06a0e429dc5a88686ab1eeaaa71dea659bacf7af004c960229857a5023d3a09ef54e089aad556a05c40907412328bb268d45b86fc85316bc1462857857c866bc4215ccb7109dc9412de9f22c272f45fa4beb07fab1f413b5b2393068e47c1e47a33d80a718697612a7be46c2972ddf1db426b3d8c5e0cd70e537770e302d9780383ded8493ed4e6f2d595c9fc7247d1567961e086a842c7085f0d62bae71f04b4626e4f41d1352d1bcda160e83faf860690702f3f15fc313504ba0867bc411470db12c31ad9bf7546d688f4c712031eee5bf55decea81677bd082347cc6ddc6c7c1f7d11a56812212794dfb6f418c972a77681c950e72a5f4841188c5c91aa78ce4fb3018e42996017c79eac90479d7d4efa8e3cc669bb5b46f861c2c8779c00d90035011d73d6e8781c761bc212a57a2529ec142f3603a1d3f0e59f588e83a24f3d604d26933ffeae0e7b5bc72e6ed4d97617ee3d91691e002d84d06b09c4a402d3baf9f0d74f135afbc0f81721a91349b498da290e9a5850777e09fc444d43bb6437b5957dc14d93f775073729ad39b3a4470b13e8c2286331f6d4a96a33fb30efdb63511268effc6429f82723bb0c8f382842e089b0186db0db105bc3b5a31d5cbb28db3edda2fc2502f1a5a3f6532b18118a144857cc6eb46b5601dd8d2247a9bd8c6c0d4c43f41c9f6af72823b05c7ab1d06b62b9a387727bd6b52fa6325c34d965df23f1e0b6673814372d8b164c3b59126339e6cd65f0fdb938049c1b27321fe349d548d7a798fee0d3175999b6b9d1485fa4e8f3605037e76917badaa5aa4abc10ea0c0e011a87c9972cb61f9b0a606460de8193b6543877fdf5caf9e275fb220c6ab805af812cf663fafc31c0b9b3f960b14270c2072339f72966d6b87e42d749c219870ed85a878723dd62c56097a2e11e289380ce3e18d97c727583335f60e11e531956d19464272a9ade782f6d7e65d3fb9ccc00a5f9fdddb585690134077d35440a9a95abc51728beb6b65e3284b0db044f27a137a1987ae839efe04d1879ccf4afc912655dd191ee15065954eff781df5e1586aca6fc36dccb0bbc91970dd598ea8627666de4d5b4c527f48d6164d89c4228d7b188ce23866a6a8a37291631c8a9accee616072395db94c164b9907bf26f8c522fc32f41eeead07c5d9ae111643d9e23927aa72b5f4b700c2155b8ed64406b50cf891eafa99dea61ada2ea60b164ea69f837b083d908edc7f0ace559da389bb3d0acfe6b820288cf4c6db42d47b0cf35f49c412e2ee7df9da20aaa7ca6c24da509ccc9996182095034b6190dcd92449da33af72809911f341aa0b21df49fc48ff104840a1597360977d1ad73fa72ed437ea8f698575fc886291e61b48000769540c1452599ae13909c1689ac61d3b818a27a33d59f29c6047eeba355f03860bf67a87e57732af8f75e3526804e8740a96976e72b201adc65413bba3c67da1fcbcc894ba3847bda7e11bfcbe48d05c98ca48f121efd178fb8facaef060be0ea7cbd491de400c450fe9a1846d9f29026f2b916d721307442d617f755d4576e5ae9a57bd5f8e96c924fa5ae3e28e25274431879924b79797b7d49db92ec56f5d8760f122f97f53f7b156e7878f59270436bfc023724ded8b339fabd8d350c1828fa31fffac0b586ff13fb459f1086a5b109268d2430d04a2d5731ed34bfff4d201618736a534ad7f45416187fc049614e1b65805203719af7a01d408808eaead6dc697b466863602caf444d43339849c342983ad721eb7ce77609b33791bb63746126759b6752b801e312931f4261d4d7a4156bb6c1c121e8e1e0c7a05751d9cf6f2c116060dd3c12617e7c3e2435cf56d710e1d727a3319295109cc04b13c87f41e3ad76af1c2c5d47297f5af07b2a11e5d5d920372b4eb2d5970bc32798192096b0f6d1f9eb9338ddf4071696ed189e7476f2172850f8c606e77a89477b9f2d899a3c03346a61d823dae0403c012e265c0c019724e2dea49705e593925c156beea793139dd8ea9076c1d2768b0284760e567ba60f7428a1abe231fff059421097923608a2971050f4eb07eb665ee2e97a3c7517201f9e6bd74d3d73d5628099a59bb83079c84c445ff7a40625437b0aee45324258a218a3561af265d37feb94d5e850130d11352d1817decedceb2ecb78dbaa54ac2ba674b4f020c83162110d9d8dd84a2c06512fa59e8106d1d5c7acd01f40b7213b0ac0e7db01118092c3d650814d4c2e74bb4097d807855f2025d776d66fe10353326d854db59ac6d9d91244fbe4d435d437daaf0b868ca27fd9c053efa715cb7f36a974e9d05eeb0ef49d6bb97afd2448db0bba821d92d8f9665182ccc24723ce955970f4d8b81049c5ff753fa07f049a0df61f30e55c81bceda396fc68c723e89d9a0aa7ac994b66dc48f4f05235f350b9f172676e42f36824805790ab0722ee1457b478c56f0651de05de86fb1e4bde4930dc06a5ffdbb8b2212f81cca7281ca1a8c6d79eec1d7e5c362639145a6fb77c910196dacd15cc147dfddaa757211a3e08d83fb00dd70ca3afa90053250441f0297ada0d92f4035ee04c2bada4bb8f7d5856517a67bddc555b528147e5926a1d911870c8873e63b687da0eeb17212a6fac7b3930c35ce993a9a710266740659355f825b57fffe9240bc3faf702bb7dce895cb329ed15784a3a5eb079419032fff1e01131242ca7eda1b37db0959d9ac568dd65db0131808b7daf9fb1dc3d1001b62da376e06c55c0017928ded720deb8ace947c279774d952339086817d6eeb440cb5d60a9885dfdb8b595bee1def66cc8a008aea5864b821902c0a9bc1faabab49068163433c5c8d1f290efe711b29286d4f8df2ee4c7922c8392bb5d735090557c9a391ea80c031fe9a73745146a7b966a8a85a5f08474255432ab3f7531bdacd71976505fe4b8552ef66a972acc1cb16027d5107181d9bfa4888117f72c69e6dde05ca994a39644ae8f59b7202e25f1006123157a51a195e81c5e14b74521aab1662492dc19070859dbb126872e4b6aeece669eb769d1bb55da4fac05550050c50bfb25a5250db10ec950d72d74d8390eaa2904fffba367948d8a535117780c9a8252856caff48991d870b6d2c09119c56cd9bdc9d8de6d0f5e6523edd3733ded503b2c4db749b7b449cf15bdc0cc468edc8deb403886a9037230bd30f478453385fa3ea67b575ea4f2b1408146620190d18de646ecf673420426b470891cefb995c9038ecf6d928f6a96c72c72b122d81e586a87ac321f4b07d7bc969f06803476774b860149117aa928e503204621fb191a37e54b09e3a7214103cb0829fe00ccdd2d741d66929e7b6de5b50a1ae80512575a66f0fe217ac084261183801a05bfbab7189838fd71f5cbb567cf173a5042c178126468d132a94f12d0c8b6771a94235bffcc4ec79f04025592c40b526170b006496b90c0145ec8c860c8dff1ce3ec9dfd962b3759dd701e72f84c87f04c2a08beb8d97668d5bbb5f566396a7c1f38aeb86131ec96552517724a31abc46ba0238c58766198f54d3031261ade3968ec5b92a757d92ecfcdd072ba8ba390cbe859e7e23680060f5d88793b77fac203b4ef8017914e534082dd4740154d97e036a62a30c1595918d42a16a40a6a2b05d4e23d94d7eaeac36dd272b5e486100781c55cad0ea5e9a82ff9e6922446efbb3f9f5f1b2bbd1a8d630b72ecc29961dd5d0399dfb7aa402d2ae1075d23384ee80b07c564dad747facfd73e4bedf9183b83055e1253febdfa4e066c9a11cc48955a6cf48f0dc37754649472dd499261681b9c90470aac77c057ae3fac315e445c5a521ac84d69cd94955772aeb2ce5a890b35e4491366e7693e8717d689a062d2e5c8f2dbb3fe18d4f15008836f6e433e82c99ba37604c2c45339b0934dbfab6210c4830b9aeb99f005806596a1c78924ece6c848b4720bf0ddc9d12d5791f7e0f155b1689434c0607154722041d9e89511f37b4f8fb26380f9a34b30f96c1f52153d982f7990c09df504727e50ad557c406c1740c6504f4933975b6fb95a87a93bb40e162c4d3aede65461c433acbf7bfe9a731d4ae5554621136f8f3b59289094bcc61c7d0862dd5c9f72d88332ad7d36f6d05eda7ab1c35f34ce2d480a55dadc64259b2cb6f462891c3b18474d5c19b0411994adb954bcaa603a1a3cb6be5935d5e0f87e89bf0d42b072f899897cb0aa06714a11a962bd17031216cd5716399d554ff96aee6fd0c302728c883613cc4447c41a2073ae3cd676f2331acdca10788bd438b52e2d727c04725ced012ffe4dc6402fe30ef5d01909d9fe0a1a4e4893fe660941c70b1e620d7240548a36841110ff6a7c957817cafb47cd08f70fc164e6f33558cf2ea7edef72122fd2cbf998e7286c40691e3f3edc93483bd940ad2bef611420cbb44b279a238c6df1c430e50ea637f684929ac4552ba17fce13199c18215c5d3114f8a6f772321ea336fc8120294ece330877acac6664e1759ba2a744f80f01ab53a6b4fe4c47cf7ebc98366a06abb535cf8122f79c72f71d492fb54189a8f6997b3833464a1b173f9c103e8201137e350e4882fa2cc54b610aa486b942285ba5f0781ea7037117a062568227b5a51e077fbe56ba5cf1389a45080aba8e4117d54b5700bd2e2887fab4c8ddf9924ed35765a14b8b46ad5903b93fce41cfa8f2ff6f50a48935d992a5d281bad21210a45ee531e7fb53b8b3fa91d1e9e926768e53e108288611e5da41d3627ad3a831df70ba6fdb11561eb42627630ce6351c65b9cb6d18ef1a97953d5812f4d9fb69302e6592598f114b479a56c3321ea15b2ff1529b53755ea0db81787139aeb7f35a020a5f10fa0cd8ceb2dd412f6b3ef67ed927d35e1f72437cd85fcfa541326d08f5d645f288d4c78b6c17f57db7b9d0bc0c70a376bc7232a13f70bd3e791b929a130a7e02a4e8a4ebe745635d64419628c382d7a79a6d6733d0d3dc58937a796572ee15c61a809ccbc4cf8984befa9067c4041e343d4f146e8a6b9eff81f8624e7814e5e5a6630bb88baa170feab671dde4816287683b6f40d032cb8e055e131749095731784ed10e59c7240b42a01b8e1377f83b526e6840ec244da90b7af59fb480d957799bc3b05d278125ac14381986b566615e443e6e899832817d9c6c733a9174d76d41e8571579ad8e3ec55a7b623989148172b9c2ee6c5c164c987f19cd55e0263c6a01756aece1859575cddceb7405b51434191b96fd2e8e36337fc939509c11501394f89d4dd838d17dab57baf76c2e9f5164b260ed6dc27ecbb4c305103d605079d13874f048443a3baffcca3b29e4aa5211e5476697a80d9b6019e8c26b740d2438c47e64e1b1c52d8b19122451748e72607f74a0afc3a58416bf8b01bedb7aa051664c510e5d327e87e6dcc2182e3f727507d58d3c59dbc6e1a028bd41a871c4a55bd81d4f6242b4a54368f268a53b42a8368f12bfd372fdf4db8a0fe7cc0569f98b8aec9c7a2c7265c23d46df69b772eba613ee558729004c276787ee6544cb210cfba46f772224a79aefea963ec01c5422cad85fc5857d1472db3621783b7cfb02d3ce191028f5c6cfca903db5f0724977d0d723f783d19028a048b21bb4c0abe524d86ec0f77efeffec580d586f3edb136174acc0b25be48318877865421e4b90bf68eebc5679b05660786d39b25a7812fd60c8651361df86c27d6623c0b31714da60fa0a547137386e4e7d5b0b6c0d9999c133d2102a6234cc1fdd83af52a324fd5f2f1af07dda167a1e4e8441726824376767e6245d70855a92673e63aa59daaae0b7f53c36a13af5a8f1b22418c7f442defca452003c1d81563702e0b7d280e84d0df1ca03d04cee7b92f411593a5b235a1e0a0066183f13af468e8c2992919100a076afe49ca73fd4bfb0590cf7ebf0f69dd50cc6e55dd0148248af0f3ec4232e47488fbea0bcc2c833a60405334a47c7c1216f3b202762c7cab3add69f280f5f8320de1b613933f562764b683a8bc9a308168ec9d3109307c1a216f0befde6d7a0b7bce8e894bff50801fe722dbf11b1965b9c52705453cabe80f5d0dba380268da6b13954c9aed9646ca17208a7164c6b67e878d25a9aa55dbe9039fd3ff9c5ebfe499f1227a4937ed8b810884139664355c7f97c84f0c2d984f467e7e8d6de943dc6cfefda79f2e0c3ad727bea474db5cc70534b9a770a3d8f4ed4d0ab494f3c53ba407445d0690b61a404803ae24c735b9bb6da94d0f1ceef1911f8cbfe5026016d7a158b401582d593723df1a1cd2f67ef0b8d87bc4928c43f96e03f23473fad86d3154879756f164a0630c96f168410a28885e746ac2fc855e11bcba65260cc7451ec7a143c4d4f08727d288bc43f322ce03da4d283afbc3ed64ab6dd6857a21ec5ec8b271e706d24724169dce8cd1824a431d98ca5513fb2326c4f784fadb2c52aa9a46e30c4aef172c2a526ea3c2fb4a7bfce62f7233194b6644142317a9993cd362b84c0dbed38725798fa471f007f19e794f63f97addb1f7d5917a9e9be2db76ae897fdb84edc43bd7e430a132f7d851d48e33d6e814f2fa068b9ef5869398d8712ae96dac1db30a29b727cbb240010d7f440c9917c5deecbd5d75bdc25b5824ff91179ad1dc4721e6a0187ee12c6800a391d14ba12596c0d877d789a9bef02b7916809314c7d7296b82816b69cfb43cd3483ba9cdc9ef7af6e78498270840ed52e154e7663ab669e40e0e05c149054618d558472922a323bb06ddc25b832c192f261f7488b4c1b24f9a4cf13ee9a4ab6ca8e9427caef8908e8cdd1f1414c04d6e6aae90d29b8723432bdc08ada8c67eb6d814c43fe237047069b7d68e5d47ce20d2f4e4e64b9727df9c5455857d289af796c25e1eb39183553af654a4504b96f5b10ae6beaa4726469a4b4944178caeea7ba3626ed519239d94f09e6d18bb7ea131c2bf6bbc40086c8703dc03ca87a4059ccb26111928c7f153b9aa0fbce522c1a0029a00f48728740b977fe4e978463504a0fa3ded05c8e8a2cd88838fe3268012f4ff238f037c912581ff3884ecbc5939740b1fb63c6acb81f999427983aa6bb0edaeffb8a192ea500260a231a6fa7b4ae0461794ff0b063c32d3345ec631a2fce1680244a727ec76ac813f907b344cd529734b7017f2facc94a1845fc2951a90a0e53e275727a7f7957a691790f051648ac8d7f9a19b65f80af81a1a46d8e6dd4ce19eb9072b474043cb9cda93bd1b9d21a363ceb68f8821acb7e2005ebed2b81660ea7fa72a127b85a678ea2d3d1f77ee1d42439c4496c61485ab894071d862eea2fbbd972138f4ef70201d0cd1a462a84f49f4b66c5f5f18150ee55acc92d181b2f361f7215afc2e4d7cfb7a017491614c21c20500a681d1cf1c00ae2464570e3c36a054bb4e151220f2cbaa10a15f3079eb48bd5c35353a4daab7e1e539574cb0b309a27ad900f6565ee9191db30ac682d9cdf582dd520a7117da1272e85881f195a2872c8449b5e6ec48929f4f6f543c8cad44c5a30f2974956b513031d07613213c4548e3bb016d775dba462694a0136cde9803cc838f84fb835d547a3b947d9ca6b12a345a477c3c27c988c2ac6897142562d40d128dc65769102eff0174f4a659d72aa59fe810ef1f9f919b3c1c0cd8401800660413c1df898c9fee15c4916c1eb67da443b7f8d0e8557a62097da4782161bcb11f6abe34b0b2b58cc9809f9104c0f9e82e13e7d6c279894732ac4c413acc8fa1357dbd435d356b63276324aaece72f3988060642776a53103cdb75b3b509b7b82b0c80a41507b13deafdec6bdd872e0be46b781e5428430f82a0887f274f189787d12937f9ea74dbd18dccc6a8b0d424cd5b7e5d3c5ed2a89febf89fa1e60736ebd8740ed17620cb728eb847bfa72395cc78a86c06008d0484afd74e33b09e7b9585dfd5b56caa59afea6ab9ffa724c1a5850e4becae3c2290a4165769f9801a49cc3630fe674c2bc979754ec12723fee06ff1d8349765cb0947dc7cb5ce9fa705601a4d771ca7d7dd5f653e9de3f2e44dadab781b8b3ca8b3095fd8d5d9633c114c0100f58c7e274d5e7ff81ae72e2ca575e61a08d41c252b2f9b5567d2053a9fdf36a5ee22c27a19274d801db726b1ec9c3fff6b2cba860b25eb0d24f7540a237d9f2b79abdbd9c8285451cfe7237b47d959f8c23c854a215f9f74e01ed1c43e59b2c5b03a5a51dd65f27442618c3bc7a32dc406ae0f2caab82a24cb25cb7cb32f8af9f1a5e762a4480a9a2a70ab124c1eb9379570fa94775750730934be89ce3f18997d48d64e8311331be4d641c1e8553a663964744d836a015efbd00be411b9174d912edacc0428c5610fe0391be646303ead89679b755b15ea44de9da29c8a0e99ab12b2c072efbaab1294835699d01ffad00a64077a63aa88540e15bb7352d5bf29ab454c324ff4ed2a872fc29bb523a6fcbdb600681b76d0424eb887e20d74d310623fb0601fe9343471c613d989db1f7b845d1efe3acd1ec92b0653a00134db73773c4bedb142d823872c788f9f5f0e567dbf22c79aff179be555e1e030ed945534baa4404ecd9d08e727c4fd1ec707d6a67c287f44ae80966e4698ec9de8124948e361071981deb7972cbf58a11a5c6e5f22320abf9742268313466caa58a6ccaf4770e98c4dbcd8872d47447f3546b7ad4aa5eee5f93426a3b18723731e64d6a77d5e907c1896c6243a8bad0172e63568c58a98eaddd9201c7c841ea2f02408324fe1060169d874f72d4b1afbdea365d92e5db57d4cd6bc488d28ea43cfc5492420de4a558f1f10172c23edae5b5f1749b478130905eeb9682016fee829ca04a82165ec5f65a432c7261728864bd675e2c24474de676e6d24db216e3e08fcc2ca698ae6b28f1258822328411a3ebc00cf331ed833bf376951cf06a879cd9efdefd599558fc05ac814bda846f918b69e6f3dcd7293f7474ef1bda1451a05dc6a63a3fddf8b7fd9e196988544d42f5d4cbbc04c9dca1bb69027bd415850f2c77bb4b40bf68680b6bdf728eb8877a781968016710c08096b2b2160ef722a310897173273ef4a03c14ec72b9b7ab967f40addc9e133df47be6204a6daaea673a30dd3aedccdb6a15fad6466964c7c16e2c7049bc381497e8c182c0d9c740baa49a9b654a5172132b0505726bf1e13a2b6815f9849036f6d4d0de7078074be615bbaee9c68a7d66d6122f72f41ff7616dc6f402cc09ebb50edfc19e4265f442d40f3bdd4a32eec4a89aa21e18dab188efe04f5a2182da38f80db415dde8de35b4a2e7fa7ef5e8b51328c55d15898699da515c9064ad2a6732e0ea7f996663de634ea3037df38f17cffd2c72b47fe6485b0729a582e3c7d70ab702b13510bbfe468e85ee0b6541d34cd54a707b75daed96e04658bc1b05d0c589436eb2e84feb9f93d5cceca634367798130eb228d91acfbb3e1876709f1986ac14f4b72215bca1d93ef12a63ff51e31ef55686b7404fd0ba28b9962e97ea9bd2499715eebf52edbe23090421a3adc321e1725783a68a181a41a928b79d314073f966312e7a10cdfd862c826222daf90596578ef8e78b597a79c96a3f467e676f2bc072c52b594b3d2839391520409c667f729882f4817a4c9aef03ef2b905daa28f31c03fbcfdd79c841912f4baa3f246a5d3fa4bdcb9f1326e00dbf8459f2d54c915a5c11096a2b9cb1836cbf35f5372471d6f11414d5cccaab36e57a5b4bb0508e0d684f72e1e9e0142ec620c97f4bc872cf98616b0f46c250e04e4636b1a6c730b2546291d909ee9a55badbc5825f146c9f12a277e8dbe495ceae4d95ec5e5e2207b85a869dd3d5aa628c4bc94a3be27218a5c2784da5edf253545f0eacd5ae72ddd1fac07fc280d41cf4e3c542a2f872f50cb0e8096d1e5d9fdb8c217f5a35f3ddbb5819e1fe057ad69bd44654846f72122366c8a1a06a8803c6f2e563f1952c3063e0174867aa6994f9892bad611a5be064ef22455d223aab0d03eee2b7e6a9448365212827bc3f6e1177c498224872b0c8234a8658a00994e127b97937f15805a01fb7f80581dbe4e23c93f400711440d1982f1bda784bfe694ed283f3ce3dddcf0b6e39c4862ee90f3fd639059027eeab26a5d2767a3cf51c9374381638179615bae15969b0588ed20d35796e062a4c3f9201794bcc6e0ae91d626dff0589e6f2ed4bb7a696d175fd46e5bc86f47148126c7dd86fe11b2417d3407b8a68fc2a2c0fb9673783f179b0147da290175803a9feec4c54553c3a0d70e3ea90ae95b2a36991d229f266ac25af20eac40d290417568f2635d09fea26be9b781bf61d6c601f5f1d063ee3a09a9ef66e903a3d14d99ab68cd30f246e2c6465c2375c9c2ab506fa169dcd2c51f7590ed7c1f017098bbb4416fa4d8ea423cd009c04dd34bc8b44e8418f42c2ced4c540f1fd210d8838a293914bd5ca27e85c5c0adb94c365744f3b2e82067bd422308fb7c71f72af2276337f72650a906949cfa73c2cb1c71bc7f20df7c86f45f5d01ea04c9e72794db046094ef4c2a09211f38673bb7832b37f6ffce81b2f00c77d767af2af7251778584fc7121aa8b90799bc6c8956b5e7c911f13f2bb407c009d5ed2be2e72ff5e636288ffa39cc00f6a4848edd973e93d9037ba2c2850514a1da09815594677349df70ec42d25d7fb37ed9b7af80e577448108628f20e88066391f8a8fb721e59e5a3b3b62a2406c03509e9acefe3953c14a425a195fcc7544c2ee40c9e727f2f6b4c146180e1197439082fc27323863281d5c7fdc6a4a04695735522f87267bad4fe9fd8f08270528352085f37373de10917d1bd851dedb1e8e6add86772355ee513ad489323a91df5d59a61dd8c00cb2c6bf0051fb9d271f2a8e655ef38819df7fa569e9c010250e1434ad3d9167830f69318c4dfe83334023b35a0a2726d30a391c1f07d657f74c0a1bdadadde8f982715a2298955b028d63a196c983bc9c1a71015bff47c03d507663aebf74bb314d34e749c5ed807952b6a387ba11a76fc44857f1231626ac46f793d6eb34af3d2599f8bc5844fbdf322b754077672c5ef528f84ac2a720547e09d3a18b540eb9d0bdf54d39711103b8155553cdb72b315fc87f0b30f97c5fb45a284e635a64f3e0fe41fc4bb9f94a309c6cdedeb5cb657f8336b8df34079b92e2688d0d0a0fa068b8a7cec8fd2ee32948a375631309b73e3d6110b1d5545cf86ffc703bc57037c2897c891695ef283040660798c72daf2aa33dba82ee7b3876637aae8ecdbe73581a592022ef5a3d481ffba035672227b6d7d25816ef479643bfb97c1c38019a3510b2239223787e18c504af4ac7254b2d40bbd51263b353333fbe0896e48fcc23f780d507e899b00d49708dde772ebdba653b07676f301d0d58bad84a767881f1af4e5ebc220e9544359f44aab2970e66cfbb17fcbe7cb2f83923b57bb81073103d5e516c2c0f413a91c345fe6447556f1a71936bbe667f3b68d40818523ee5c7605b6c35f4f428f968191f2c725d584f7135be86aafdc04d028dc4b33f8c7f74b9092685af93ed298442e50fd723dbc7666cf5abc82d2e2d35f2a3eb87d6af58ee187c46741fe1f55e34b6f49727c9e14a11834ff7598af78ec3cfc57575bd44ea67ca93e452c137caf912bdc010ba4c053973870463b9c9c4de8b6641e4e4cf4374a47e3217d2309035eccae4a00c01a4f70a99d36a9fe56646f3ab71feb92a9876fc308a8fc068d7785acbd6862b871dd1c2011d4a8664ea7bbe2292281d4da7830b94555681786b62c0d4b692b404ae2e2d81798e18b2611807848a975bfccd1ca74fce28c14f554e1e17b72b13bbeae1d2c132d323128e9774941d846e85049852442b2345b60e0e9d493238b3f1d1371f22b33a7acacb6ae17da39da04dba811f9b4719200c1a62ce22672581864bdeb6b4cab6baf709fb68c582fd5799a4fc049b51021ff6b224d5d20721ca2343399a09862c4a2c570150048bf92791f3fa69602054025556b24498172c6ffec157b99633a01efe8393d03e7177ba4e3fabb34edcf245d6d53d1e000725d0e6164a9b4dee3fa8702587a7311d053b6cfb2cffe261d9c5061ce0c16f74fa77b85e9c46a59bda26f83de0a295ab5fbdc2ae899968764d728c60bab78f172267b28dcd0f87d99bfcde36cb7cb69fd8bb7d378b3f24dae6f5f4b92d865e672fd63a876b74d760b3b320a4b123729252bcc6f75602aa7110b7d6aa976670f724095fd8ec22baf5facbd2f616e79ab9b31ca5d03dc655d795969e4944bc0e230248ac442de66ab2e322d120a19707764adb043c266bb080f6efa0cc32f3dd172bb1f1c0d43b14575ce00a44625d51880925890a3a96807097289007a34f34272226ffc0dd0090041ea191a249a37bd200123813a765162784dc41b6e6f4b0a72fd4615d7eea1372c15ef3913d46900a6573837c33f6e66b0c04690867fc39e2f40f4c555756bd8c4b74d4ff459d0d85c7d54df822dd8c08e1fb153a6038fef72aa3af51bade720ed29ff2761cd65ef099dbc3e946c17f6eaa6c24ff29186fa72feaa5266e9968889b512f024e929972fe7200bb1aaa828945e7dae3834b1aa72fa33b7cec4d3e85508b5248eb547de71d737ab19ca946aaf046b3ee6ce3e4972731000c32da29de935980626ec92247d549d2303a08182bd9baf405cece36f72b553b17523456c3422595a418622284864ce2c4d18d496eff2332983a7ab847278dadc0b85aa43467197367d0c25de584039e452ecf8e9a5212de24c207a0c4172cb91db695cf4c323d2a605e1c2227372d3cfdfc63290266492c6a782660f72b2b32fbfa850a97d7107fa1e3fd65101934f955e67ca5ae65a7fb123c8acc072a4f0a1ed6227e1f110d727a861cb4e21aac4ab414d7d8287910611390850ac72f041da790b5f00a0531b95deaa56afc638cd8d7c5d05a6a672fe237f33d2740d0b717e540f831da4d19dda9644f4d87f245ba174acac4729788830cf2a267c45ab10f80c7aa8a0548f3d0d5a13c0953c78af155c89bec27559e5ba1705246a72a5f31c48cdd2b3f51e94fd28f29254368bb8aea52c893a845bcb71a88cbac86b80ea7cb61b26a58486949b57d4032690605025d21d2c0aac40e34f101b54c17295faade928777f826037729b1e849d2c82e8cd99e2561eb7c47864c31e6fa7727a9dffe083bbf675ff000ef2972bb1f174d1ac415eb6840c15fc4fb6b1adfb13c8b0d6ccf49ac0f0749a578763f6deba60ba0b7fcae442837fb6f3a03199656e82e980e6d5c8362ba60b374ca176c512eedd97ac4f9d9b124cdfd4a17f7e57223df5df4fec31f67e5c176eccb4c0998648957eb8b2da6a3d65812009a86f02299e02e94ffe19c4e561ba09ae6034a00072181549c9eae640d7e3fed09439535532994501d175d76b3fa281873fa47232c23c98d5afa482b4be57ccb704525231e4d348a0dc141ee1f01383ca6a9703e632562be4b2af081403a15d5d23e5671b1929ccb5349f31701160e7640f0ee15251eab856d3f58265c5f8d97584353d1e8fa0636fc1977587145b67eddae25757eb62f913c069149410b23310359e7a37d3526c6b5702cd0291a14d8b599b063d68aa6e1daa8f959ff6baa7dc350a543e562a0d33600676fc823f005d81727aba70cc8d1493f29b8db8adb5b7c86401723b7167ac952e946dcd5faccf01b1fa94bb8c57365a3873e664e73734083b4d27220a6cd768d4356fbfaf530f511d01af44ccbea899e463d2ef14b1cf2e36ca722c6c9971385c962a20faeeb1c601a9f4e306f594c3adc7e5052ce2cf1c261d61f3936f2b9298957fbb14a41f47424c8e2e3d32da544f7db856488ad5720fad0bec180ca8dc3c3abe4e1ccb6c664aa068378bdde824d58915b58a05c0d2407c1946d135b0483d23a6f33c7d5a9d9196bef0ba1808da02febc882bf50bd0c3a172b5fdf30c166e9b795ba308c18332621bdde284c4a078f290cb108221d8f6c772984963ed478358001f8363cdfc9ba8054a5ec1e16aeb3a3e3821dc08f5564d5eecde2941a195ade7b41145e427cf97a0ab72782aca8d77fff0e9945398f3d6690e16136aae5f80c84bf2c03b54f103f044e6e2fc4bd07ba103d20e0c229109257fa4c59c7a6a65843e27ac875a6fba052700f07908e4489ca27b735b235d1972ca74f895ec6b2d859a74006edbd3a21191b1a4181e522333345c88f2ce37a17273d8d0528d7661069704a500ec7238a77b4bd2c86f967cffd1222a69b4a5b239d37507b3a7667fea1d2f30b06638f8fb5a3bb26e7343ddec2a2c309955cad30760c73af3035be957f2633759473ce08a2260317ce4f72ac36b4393850995650b426aa6cc197f0ec1593a2998e6121f0dd99f1f26129c61306858dce92f57da72906678df0a398c3d2ea826724ae2ec0ea18be70a2496d635f4bc1e5295fa9f72956750f74878f22ba34a21d03da8bf19243e342c040702888f32a380769f31554a7adbea10e713b7018e4caf6b6178e20f07b76446b9775930dfed14d6ca08629a0f9ec37832b59a01521b7dfa2902f0fea5c5fb07bcc5f1e9789f3a0455072f87ee922dea63795ec1fc8dee7f692ed27d190f603b1098626b2d68ca9dbf417224fbdff6005091eb14310b93c767ecbc6694b4323c34a067153ac5f5689b6f72a039e9592cb125234cd43dbdcb4d5c7ce7a9f8bc14cd5b94a8edcfd627be80723b37927f380d22168c90ad8eb7c3e74fdaae498e0f34dbd31a5606f05a41cc04c0a7278f37d2c42e548ef6c19d0cf481be6010883385d787af9365ea4a453c3e8044c9bf6c4560fa8518b24f5b262264fdaa3538a955b1ba871c7cc68ba5d838dafcc40808f7b6cd168a03e91699101eaacb282591fe491a68dce18b6c4449182e6f1590ffafb9af4167969435c1363285ff92a17d33862ad475c6eacf4bcb7283e352b14ea7a1cec8a2d47949053a71e5f054d59028ceb2e29d110a36612e725ef23978f3618f0b3f69bb3236897f7fa4fc68ef505a76dae47823e832ab3c14db469141d9bb91c46cbbc24fb04e4b35cc2a2d1899956679f73c950c26d5e33e5beba461970f73435ddad6e67c0c984add06c93388f4c6747cb389446a7f3172ab6fc07eca0cd064f8e9aa0d93d83fd7bc457945d0434cddf5a8727e9114e9726e1f5af92c4c88f9e76ee4aafcc2b6dd72dcc5ac5959b364e8a846c03c70af49cfc3b5f120967d00a8b712e495f6d65a5bc56ae38320e0bf7a165c201cae97347cf0f4c691735f4286a7155bff8a0720f96b5afa7e4c307093010373c6f02072b1133236851498a1079cf26e686093cdbd070fe939880e0869faecee9e453572cbb350b08a2910a0053a6940294668442cea0ab7edf88c01d8aefcb14e2eb1594f65748bec693d707e9b4059ef07a545b53cd11dfb24b639be7301af2d8a0072230f7ef6f7b4395000bd16c7d7eb5ba93c98912433b720d6fabfa3b424a0ea1bf0828b0eaca5860065198ef2dbf7459848ea87398799841105900658d8ca877294f0df6df52837d74adbdb3c684c4a0c75a6b1fd4c9626f1e6c15837ca5b987252b6fbeccd1098583fc72730344de5df51f835d1e897131544785f50968faa01194103d7b4c22727804781b8288c41f8f109d72c759555e654990d48aa83eb722b1e501c1d66f1de6346483ff63f992ef47f93a3522edf498b4c29fcb8fb5a3e962cc55e82ac437520d8dbeb404f441c12d94312e7bb41b9629c783f8b3ff3532c3f21a4af2cc702644e83aaf26e7be32f1569ae066863bebf1644cdf9e21472771e17c416deb78dc4b9761eb98f8828bad7ce0f424ed9320b1b6874bc0773008fac1ad535445f513b8e2ce351486bedf8de3e23c3c66e7a6f11ab637a32ad60fc893b5266396df3da738b36120bb9c3065a45b94cdae1e537d6ea2ce10f36519813d38e196eef2b071b2c87245cd4af5c50c53f8037e2a9700df133e5a7f03e9e8081a262e6781f41cd12024cc2f56d9e0c61ec87968c8557711389eb27cf0af6ca94a65738ad588883bc63cada88ff52ab6e85f8acb9dee9f60df94827f52e1ef27783ee92d03f64e085618affd1616be8cdece51df7e3f859a68835a01672181106b675930bf985dc85a2b6f15b86bc64bd8ad20178425fa62e633af8f322389ccd25a7a00a33437651687ae67926e09d1c16642455baf94566da7dc3e25bf1e691faa369a64dd108b831f5e6a02ea3d298e4f8fe2ca8ef9701261a5c8e1fd5d648e26c1b86aaabefb7a9b5c56bde8ed14cc038f429be856ac07a47e55a72d1fc2e34334d8dcf2ce7dad78ad24bb2df1e9a1953706a71b368f4f1c911a272a930cb405e6b5b61354fceadb548d9d2b546cdbd04966c878fbd1a3a06fb1272ecba74a9c993b21280eeb7efabe1e13de8f3b95f7261b4fcb654a61c00ab157223cfa8fad55ac4ee4f145a9cef2d829fd978c67eefbb388eeb4c6dd5d0a33c3412a67281d44a8ffb5300fecbcc045268e5aeeacbe0e6bcc69d4f6e4764763549ac34d6172ec9b5d8c6ad8d06473ebb3c8a269344f6e23232ef891f7027d6002b52cb18a00bedb329934e4722b59ec1f07c31ad044a5e16824eb93d6ea80d4972d9318bb71f4879bb51be10a0dd65d6deb547f89dc44c87b1a54779855d04d472a1e600c0eab8e1958f9049c9ae12d74446e518c1f56e52ad0676a78afdab6356e517b88efa4db267bed03dfebbd5b9aabf1d0216aa2cfc46fe634ed06af204727d675c88088d4334dcb2ece998614ce330f84d92b742b543d4a97c7aa173777261c2983985853a6ebbd151a178f72a7830d4e40f6069de4e6f89501e0a356072dba0bd9564b73027d448108fbb0bfa84000299494987557aa0d01d59814d9072f507ec64db86855d296204bb213dc826be278ec5603599fe1f843fcf4ed56f1abd3344451652258d0406a0d8bc4283def37849101c4ff197514434e8b5b9f47215179f45dc61734d57b71d972a4f994679ab4c0e6c578ab6d69caea8b8b538334c89f1cb6f34235bb8846080581f17a21854f38e575eb7cb930d4f0cfd04fc43d9f24facdb6e047fa689e5431bdf7bef5039a0a8dcab782521321e6c004c2672a07b9292a21e5253456926cdbba838a8612023215615d728d3a31ad89899c72689ec2d4becea8f759bdf5bcf8beda64c38586ff8df4014bee5fe46223945285ad81a52c417a1dd24db627b14dd615c64bb0995ecc53246dacc3182a1edcabc2c1ca3207baf1a537cb468ef9cd9c33f9cd49a0fa4acb91c45393a2457691b5d348e738428b2d47221d318c611bcb75b4344f145fb16f7fa4a9c66882fcae9ff723cd5f91c2bd99d82e75d1fce327cd8d0bdefac7a14ad9920fa1875a5fa8b6872042bdbf19594332308cc406fefb4c8402d16bd30232ae4fccf09e2879d26d41b511da551014ca04b0bd43d32dd9f1f1d9dc0680652fe9089475fec0bc22b98722403c4eaac00678ffc8ec35af756376c2a6c92486b6f449bb1e77f3b3ecc084fe9f799c6155a71ead9a124082e6da2ee7bea69cbf5885a6ec324cda05096eb4fb71090244e072d2ce684a8fc870b61afc265e3666da9305ba2b921458b8fde721700aa94eea3948510aa277d6a84566b29e0234a5fd24de991c04b327b0aa6724cd4e6647eb98064303a294396c4dafe0d14fcfa6a10e977ccc399ae1e4f17721c63eff4a2c9461796b1d2c2383148a4032c8beac8d4ec01d7108aa5a1513272173d54a2f5cfbb662aa4c39a4b2faa8f68b1945a03ffb3a367ca65f8ac633134bc26a534349335b413adda381109502872f0315b3cb4534b325c5d2a4f932672e70748e05e08d8b3a62cc472e5404b401abdc4ea178f7609b6d09c2c9f837e723f61c551682f587570fd1472e61a0cb5965f5ecfa72cf2a8532dc369a7483372e7324c22d731d2116c1981d722c2b1bc697d1e50963ca380430e8ce21d1a37721068d8e7db2eaebf08e79d5754f89714cc884ec0b1d4fd94575d75942135eb720ade2c9c75695c49d30c214b190d974fe6e66f831f7d07e1312ceefef0a88872e9ce4601ff57b9b8db3c19be28985cd27952b457b50675a85ac74b42c4c6a3729a1b407438c33f2ef3ae9a4cd2aae0582637fbc3bf03029da93b3fceba1d5812d40e2c10db2ec5da210403bb01278c1f142f0fef2122d513047d0c491210b4726bb9d4ba3e97dbf71650170d817bc20d90acc4688fdd8a49cf41e7f164753472c2d45d65cbb619fb4812dbcba26bbb4562636045d22e49a0405f4ff8d1a9f04c90785880876daf7dfce002261508da15fee3751c7bffabec88a835cbc7c596487ae42f004fc34b4d54fc5265d110d704b280423381e4abeb3c11b6cf46b04472ab88f70bb133d5307509bb22b270bc446868012dbaebc62934948fc1c70bae72164da5fbd4dbd0635497f8a18ede52f78466274b598ca31c7e2c0e43efee0272cd3c6f6414c874dce1c76e6495e41674b273278f68e38662eaaac2e146286c72a5195a835a9fa12e424ea9cc4b0230301e0ec1e87fbec13d854680ffb5c7aa7260315fc46cf0e2fa1297386748ba7994ad0c7d570c525ec1e02869713a66ad32730e1f40882457c2fe321590878d3f8071fc22a1e59d7514684d6c96405fb61aab97292e27502037991eab65fe5b865c9f2d9947d05c79243b46b210a5149672a5cb7c5a26c6a9fc909fd327c39b29658103b83e466034623a9f97693265974e2a21d496a19b8ab98618be1a2bfd6f8fd617aa007c75275613f300116049b0728ced5fae43f49d7eaa0326f71df8e0e2bcd218440e7c58428f272a968b764f726d8c3ee43243264539bcec693b9725a2805ae78013b17d988971cf493bc900726fad590476f099351cb630f3102e9047911c6dc17859ddcce0d78e1a3316f07217f1cb1ef1d2428678367b8594fed58b151ae5fd8b25ca4db250b469e97125721de918fab9acfcd1851f2783927a1bbd88a581c3a44260031295403eb626932e68ac0167fda77bd0e36849992af29935931ab4cf3e2f7c9fd97dc8c4eea59c4822cd324da7fa3e9dcfb597cd921d2521a97ad3fda8047948f3ecc8815c866c7285c4210d2bc896e8b72248cf812b41f13ab4a54f57bf9a6b6ea6fa7bb82905715ed0c0fe04c8ff4a2baba52269f6ebad99eead6d2a820bb8df95ed3f4d714a5a4e928cb3821d5dfe5fee78bc3f7ab8fad179c4cf87bad3424e6eba5de4a164720bd73316c2451e37f8935e612655c5e4b28b08b5468c1e834dee69b983f3367255db5df6ff1f84bb4c81404267b0c3392a3cf6f229e0825b2d2f709b06caf72364290b82db2691342cdbfade209e827f6a41d6971365fadf407a04590216b072b4c3d2ab9b8a860a63478d2ff2cdec0ee50a55d42ae8f9cebacde0723b2f2e72f541e694b22856938f761316cbcc049ae48e9e048d345e58e90519b8b883a94c90c4ee02add129d541ec77581f5f02e1e184077c112e6535815f9124427ac072d4fbdad8e17c9282fe63b0904feaaf90e41ddedf01b3a71a4b1dbb3890009b72c581ac6aef6541fa1919278b1cd34759b01ddf2af40ebca39d06181afa5ff864271f3c5dba39d45858925ef72f36277a69079791bf5e5a8948f56f6a940f4372b92d3a5c37212a375f9de971839488d382d0b5291c2e7c5a212c524133f72872a630855ceefc60fd4c060dec1de5c2bc4ea053e543243ce85353deae2ce0267281c69d11cbd61cdc479417c9fc1f14892452e23b747ba357839d71bcbc374672bf92aafbd270bd5158eb8f0298039936415fb04402631b6013f8512a350f9d2c43e35d3434dad56829a89fe36a91931d79d28604319de2276f829b7d43ee7c5f3d2c671900165420db338cad16bc4edcf5effd564399c3930c3389babd81e972e50ab1298727e22c54cec28c09602375fa503b7baec6e744d9d0a7ca1b227e724a37db4b7ebed164dc64786a55459c65ea897e0a551eade4338197d859c17f722774d2ee569b345a21124ed57c9e2bbf8b8023cd94c3e17a2ba1fe9a4693ce72b447928d25e2c69c06470346c65df34f9569cf6f7b2be1ab38cb7e935055a17281445c26849fb602a8750ed7186d68cb4864b91c25d4824484b104123193727250b2fede1d95a3b163b5e9ff48356c329135479eaa788204ba96840e5a85e9654141fcaba356e400b7ff37e462eec3d54acc1bbb043a504e285bb1bf65197d720493cfb0226f44a7b6702a0e9d8584c16b2711e7b9d45c75af76fa6c60c8f4721648c35cc974f886536918290efa663d3c0dcd41932b891fe2c82cc47f259d2c4bf00939a37e234124387b9a56547d7dd732658e031e9265d7ffe140cd9ac87250e384c95d0445e0beb152a06868cf0fbb97151b270f2e0b14b07f1156c25b722da431fadd517e464df634a18d240ce9741422334f12b2c44e5beb2da1e9b922802f01fd7b0de632d0822c1a8a6643009052cb9a0c319e5a87b8877609ccae633640da6ac9d6f265964d2bb4bf4be0b28bbcc3bc71cfe833c10e7abe01e6a7170a61f2bf3458b5ea8021c858b5dec619ccecfd4864d819ea46f7281fdb996a60b425a295c9daac16bcd8f081d68c3fb7d2ebd4c158ac356c43b0bb7079844872675bbc70a157739995171c8dc8edab0725148e14edba711b0216d15c0185257287ea234db26734b3ce753cf79a26c44f557cd127d5ce75274b1ad0440b983d2f2af0218be43e9525e1a4678578a97be92aa9d61efc0e33c23399426b8cb2093dbd1651efe03b5df76221c430c1540622d35b17cd9c1abf6a3863e61c2b182c3183590d04385637729b10fee554d687ce2287ca860988e2538a8edc43fe37612d4dfec8653e539a951d08ddad42934187a29b9f1d094d12bff5077579cb4f7a5ea6e2e6c96a4b12cd09c47bc4d911d2fa83965e65d47743c2d98fa9884217040cb4456beefcf1eab9c11e2209ba0581373bd33ce0d48223657c840ec975272d726448b281e8a2c044f3f06fd9820c236c87e9065c0139c64167d2b83ccedd8d72cd3a2c9a99b76e4d58f92492053959eac6cbaf5b1bcca3be9d8f246d7e7ee54ff1ded99b3059d4fe4c11869ca5e6733b5203089a393fe657578d0df8dac12572081a65ca3185c4751514b96b42e465129d8a30299bed127a52598fb39ff4e3725feab4451b65b0c7ae4a89c968186f512e2ba3979322c242cd04029e40198b7231e433f13d534678c948dd6b9b2cf4b7f4121a7dd50f346a2745644aef1476197fcc3629ab291bb3d45e601c4a49d077a4f1eedceee3773d1183ce7728f4f4727a7af6f2da769f6a0de6a2e24a0f6f2004f92c1b9d6687bb5811146300e53172bf5cd73011fba41f7f1ac6db7907b427c0d993918d2554884b7cd4be71c9c5723325969d00a0e06a3f06cc00ac86032fd38f100a8208ccefa1c970707a43f351d2f930cb190f41babe20c182b2dee0c23fd03ab96949f1b73cee5536e2566c723c896e4b0ddc2a9fd0d7283424f491c2681d4bbae77b876ec3851b95c0c57c7239a8ad1bcbff4c1612268529cce72e7c52b727eeef5ed47306a89e118bf3be3028e4d3d9889f74080e4b6f320c347d1d9de26f094060e63d880dd4a9aa069372311172068a1eaf482eb05e6940887003a9669571a98cd374b7adc00ca9a79a2d0582bbc68a5e4cdf37845a1cd8d67548c240efb768e71c5d9b5dc1e793833772712f15af4d490f049e5fe21fd18f67704dd766a6a3092bd0aa2ef86bcc40704b11b7a10154453c1a1dd00b3c1d67f5825f8a3748fe25b5e5c609fd3d40ea3841a5988d11e9aad29ad9e5310429dd5b4c3bbbe168df11a71f6f4f62f9b2eae9724a09e3da60aabdbacf36214a184ea4a87d2daff13cb9d8211691e3dc43cf87099ba5d0e32925dd536334b2d2734b5513a9790a875bc4b5acaad1562d63486d3764a9b0ba91211888cb163924dad511786c9551e6087f3ef87f54fb53f7d7a472250be42f80bdbbf4649e9abbb9d35b5f6de35e3ee983f05c16ac2f214fd11a722cc111c8915826fbabdba134f8763aff4bfb0127bf52fbd2dcaf4718acee7e210d0e767895e732709d079383b2cda11cf263c6a250e27d12934b6c061dc81372f0feff33f492f0226bddb1de75ea58faa0b69c61b885a1d8662aaafa54c5f17233590f3cb0eb651443533ba2404c3710359d5570fa89f0252530ceed019be8252d6b530d5fa601831089df5f30db847d45930616ef5ae0c31e5a94e28c27597250da82cf1ae766ffc552b28f77ec4560debf7b66b14e891c0ddbdb4c79ee1872ead06b4fa2bc377a8149f59b4ede76597127d6a96d175702bbfddf96eb1f0d03ba46002ea52768be8602f0767e9098e73a03dd3508309397f8f2a5fa668b2507c5dba4268cc01810f92b8b7f20c5eda396a6a4ae3617fb2ef02c5f05d10ec172974c31ef4285fe4e548de3f9859729cda6986114338625bc9df2b9a2425ffc3e8e1fb9146004bcb4ac67a6d5363c12a08bf67226035f9c7747481e0cbd1e7272f5049371884abf5fadde4bec9b9b78b8b6c0f5c634b7823036d1260bd0f44e5c29c086d91e042ac50304f6868ff4f7cfee21257c403a0dcd89882a34f31c566f8eec266a9112fac85bdf97cadeaaf4b9db04861cf4dd67c22f18a07ee948063a66009908e4526aaecca0dedadf82a91173e67a373392322c57dfbe892640e07270cc3aa752e5a199cef2c7ea7e3f8ff0c8fdc1b57eaff9b4f8c196821bf0f272215416768a9f7223b28803714d3a94e0ae9861083d719343eaf6f30f8ee3b572770e267661abcadc01034434ff21e91df45fb94700684449f292461766ebe672a7c22a573ce25edc7c9cc0c84bc76bbe15e9045ac46038efbe38244f9c3f8638586fbc5d4b669afb244570dde5ed1bc1124c01bf8ab67cfa596a7da8809fa872247599b2808cab6febbfdc1257c06757804d75cb9252ee7084dad4579515985f580fb79a1836d188e2b882b38c0a7129ccf1c44ba30f7ac484562c3ab95927727fefef9dffcba6d967ff53a2de78df0bc473b9cf13a218c24d09198efbfcbb6fc1cde94af71fc14041bf78ddd20ba4ccb335516df9fc2255f3d5f3e69cb53f4ae404bdaac7693925757e4770bf3935ac03280add5541ee6fb59fdb93ec614f72b325dca6864c19487f800f00293f4ce60e9a05b4b451ea07ab0857a5be4ce872f64d95bcefacab637f343603736097aafff7a200f48d84e1d63451aa385fb87245ca2871189e171c42ede02968d2d3c8c415ed5b53b7dc9bbe8f4e9dd702e072b0144f434693ff2ae80d5c63ed924d6445d58419315724a427eb89db292ad472849d83318fd5d328105deb8f1e17b1d2308bd89f3e8d1fc49c2f62e73c26ba6892fde9a96da1f877c91e9c4e9314cdf4f33ca1bf9ae4663f84c56b2d08c9d15378e664626eb560e107c9e9d06450b268279b0970f47f9da4d3634afca2dc04729e10be96401519755a6689fb509d38e8de0fec10a00c99626867073f0256057272ca1298d3f42dbd26e1a8a5b74c8b1e58a8204ba4667395e447d88115393872192dd82d2eab19d326b9ca6620312a5782006f544b53c2e1d96bb18e63c5774a1273b7db4a125a7b2b838a854e5055926088ad805b342c15d97d9d0a12851a036b6b2fef3f43e4d80743b1a3c3922bbf7a8f2a70eef53bc98200a0c1d0927d5649941e7396238a6cdefc824e5c69b78b92dabb597314c638b318307e6021d72e7d842a7cd425db26b1301477781e269d7b4ae8b8b5d17002157d4bb48fc79f728f29b4f9ea88ff0b1c41693c68ce688fdc422c4bc51cfd366647db703b4e0a3d8ecb68fcf91f6bf81513547360d7937bb53f73f1cd5d2f2d5142e4ee32429c72d883d92d22869d42d7eed6362f8e36603d2a0be8ee298785470217c910e32572c424b3c93ac1d51b45c2c5aa273b8cdcd58c0f63cb62c90f029524fb740c8f5f7eb9287f35c77eb966929668685119d21948c0c2f3e89f15812bb16e81d9c7727ec6e64d102f72fcf924d75ef01189f3703d5668b8745e85e96807737133833f624d73be0e77ff88618cce3866edf9952684a9191cdd0271fad020fabc5f517207dfb990611b3483da38209ac9e6053aec7ef020999b190ec8510c4e451f6472f274cfcb9a7f223d38362d0729e300ce69c626939afe1dd360f0e435573c9208a2fe9c94e2557bd3652845f1d475002ee49937b110196924491ef9f29fdc4772c7ff4df3e26b65d63cd3bc62892741af4e172a2918f15fd73bed50ff13eaa55fc194037df78403ce6e7062ca0a457b3ae4fe83efa77e2356bc043419882b5e72e6be7621007caec1573175fb0a866663d232208179a35379f742d2dfc4c46772aec2575371da67baaae27860f6393a288e79fb94b9f25ad4fb976b9522c2a872dda7ca385a58908302e11a324f3b64cbbb2ce2371333e2732ace58f48223d77228452713b408a0ad2ba270b520e85d6fe27a6b65aef29962b578cdc940bcae09f811cdd7d840047fdc1a12bdd75a34941f42d6061e4f4dd0fed036c3c4317350f3aee8fa01760ffefe8029064e0090241aba5408fa61e2b1b0aeb54b2344636d80401b6d8cddf1a195881f8ad56edc50e279143d84c159c2318b86cb238156722a67276b624eb572558aa14e128cfb08a9dc567b0b1978e11eb66c087d318672419d266e8e756e800438acb160c60285ecbe4c80f8938970f04efe27e9b31772ffa2e312923907c5f3e93e40c0e86bb2330a59be4e0062e33655eb4ca01fed5dc78a4247d0ad2ce371934dd3a0a4916bed8dd818ee0e5b51b7e6e2bf32db1572568986180bfaa0f745f35b3ce9f1c6e74abaadd89a5eab428daf63679c99c672d300d0cf37999ce0b559d7eb008f11755d5c013c0d6e1d4b8bb664b9f7c6443741c5d087ab75626321d4324a979b8a9d23e80cb67cbad627126c0fe031ae98724311d2a9c84074916a12a8a81c197ffde96649080e9e951e86faf28304df5272e08d16cc1c1d84bbf5942dcb87dddbcda6c0fd3d7af1026335962b62b0889672b29adb68c8f5a3e414fea717cba97eba290d09d1ecbce4e258d221b300ed7f7209d93f309d6136de4ca2a32e6514741269359aeec49562cc0416b100401d4e22e0e205d86fb70a0a65921b9bdc11fca32b36f4bd3cd1af004d7eab5a3bc89e72b05e1e672976978bc10e334f9c9269ebf892d612ffbc28f93c512bd7b5c9063701d4347341ad4baa0b6558bc7561503e032f1cd24f5f356f67a09341e09e6e3be5bbecbe41da95a030c6a6fce3f2273444d6cf36de8550b49e6b29a3f371740c5721c53324b290d703e287be0b00de1abc06703405c5ca2688ae75fff2decb2cb5c99abc566a8d226be1ba7b981c9127bd450e22f29878a880076eef898e454d936463ae4c32f18c216eaa3b4ae09f50bdb92346cec2ccec6663a399a531cd727b02ed020ba4c6dfcc44b95e22c4ea526666cd55492b87045ed02d46e37bc006b6376088d5d5664ca93a33685183700cd6d10e869a30263d4105ffd08dd5d02a85bb7f928da63b542b5907ef8759c8759a2b2172995f983dad80b6409cc39e39c5969e374653aaa843498d6ff7890eaa6e53700767cb9c5af11857377fc49c2479bf25206a2bc840ace6816b3cf8d8e3b466178c2d3b7aa5d338dc9ef2107a47ec99e04546b8e6505faa72e74e6ae69230a23170f4f5ae9f03588e7a6b06301a1e0f177f2c0a55dd1b45b53c5c40296fd2436b0aa526a1c0af46ef2672da9c41389b40bd14929c6f4562d34d17e91c493735a93e73d9ac068108b5bb67ce9b37abd7b44297cb9a5e789f20c419e4953b11c6f2a33659f414fe88c050563a5372ed8eb32b9452ea0339e7d3af4d7422fe6887d939503167e3db7952dc3589ac68c1f3175a3478e24f75f878f2d5292865b8c8b10422bee67067e7399b38a58772ca8c57dab10d5bc27477a140ed7d1307e4aa74b9133aac35ac8b5823d9000772b4dabff0fcb32cadf48091e832e0ba9895568bd90e57f167fdbd114c23743a4cd7b779dc20f9676dd0cd2af3ce5cf4feb3561f69ac9c7fdce23db4fe321a5b639c1f81d3baa627f4a7ab68464a63c22c12fb9b1e482df509aaace5ac1daefd724e9ba69d532fdbf7cac910b664afce9481153ca067114d458b02b5e21d40fb0e494f7918322eb7dab20587ab7aa280d84b1154c575e55887635c72cb457da445dcd5c3b48dbd8d88b2e6532212044db03bbdccefd408faca956f30acf465ba72da95361becc3bcbdd21671594c9faa8b9a3a3b5aa0dada72e05e4e2ae95e1d2781497a598fd4e91086e276ad5fb93221e257f06bf2554d814ac6ca5cba8f0772f94139c3310a3185962e256f6bc76939ab55406e813171f66f2c3c3ed85710487d0b8a665e5d1a96980ef613336ba0fd9fd689401945b7897c5047d826439a72986de1323b4bd7ed062adf6953337c950c6c2786aa578d30f4c36dda48af5572289dbdf76b1c6fb6fc897c4a85df58e000c7e6dd1be84892ddc83577d1f2f167a5842a5f5e4ac614f32e6a30552e8cc1d54a35a1194fb194ec8b4e8055e6b12a54b295300defc87a18dc80dd12397875a6a8b76da91c75f28d1a78460f97cc43b84c1b8b58dbcf8a33fceabcdd23d2a361d466a1d6b3048efc5ba3ceef494c72a012862f30faceb73acf12c4b6ef89bdbef64f4aae26e4cb1f29c153907ffa724c6b3dc0e3c11ddd1fa8c227253c366a9596590672ea83570a1a566a55ebb90d98c0a5b26eb6dc27ccfabbbbe808424510b794cccf9a7d0637433379b349c20f30e20f95100e320ca9a84224c9b138284b1ada4f8bbe0afbe43124fdf772d772e425d6d735f3390182da0eed5e6f5ae234ce77949787c493833bc2e942309472cef32b54c31cf3e68354a793e27f871d792a6a02a2f6d72e89f71eca702f0f5a03f3b6a174860a942b1a143030bd9966aa6a130399aa6b79638def23fc331d69623f37040ae8cbf20678b0c960230c3a28d42f70b2e77a6633abfb7db7e5a512107e93f4cf5a06fdef63acf268b68db87a043d42094ad4972ca9304b61a02d7221b7df4a8f4e9b87a7fb99338afe0207ed37e5009dc8b937aef282ef87eede442e72be0e53a85845e6a9b505176750824a9224f809dec9392df064221b9d1d254fcedd9e658314298d91169518c34209020d6322bab2daf5f80b7ee5e19808725b0bc7447b455e4f62e27dba1a9008e24463cbb9b2a9850f7dfb13138787e605172fb4b0b5b275efaee52e6f0ba36672fd31677735084f75f5f52ab73c28ea728d8161f0f5a76fb941683fffc71f72f0e3a2529bb88e3cc22293da23a691cc0b68c9d5bb6e0fde52b28374dd6a95a7ee0723bcc109fe0e9aa1be4657458f1372e8b757ef625d0eda546eebf8244e41a3e0ecb7783658e200dbb4886564ce6772fb01a15889294029cb9f97b1d4713a2398552cf35995b8c29baa9e7d95fa8d7217ac8dd997fd62732a497b6895ac221df5b6a1d2306c216c4307a924c51310729e861e471095af7462e12a8476ad697b18fed5efb3aa2491fb997eced048f9054cbc1cc57077613d90e30a3f7dea8bf8f2deaf50a5f521309c467d5f6848b02c67b1b1b93e0cb6b3e2b8637948f40e90aa6d85c1260f5f0a162b73160d5b937274ea7fd4ad285e1d29fc047be81886e0c9a995a31f6f80489356fc3bbac9407205dc6d03cc33871aae3464c56a402c4d77f47be99495bf168cb8bf947d8e1248a8a898d7712516e555a9b57ed8f0f59110e7bd47bba4e2575af3a31acaba5e165fd95c8f524e893d398f20b6c59fa5f396bace08dba22ca31ad01ea5e7922f2b8a50245e7548389edd33dfeaa8a3b5092a1ff5069935f624052c37f54df3167289a1a630c1d217bf4a97a1ad4a7b67de7989a523f4f5df0aed802c379f31ad725c28606b4cd51102e73a0ad4169b6ba848e6c59fa95403bada1f8be811aabc494f36dbe6077a62a07d67b5ad2b198f83777c27b43c0306a58cbbe378c85e28722e761724a96552f65757b1ab862d9de929a09e683b65f32ed5819b59263c3611be03902e645d7cc9855308124264705f9bff002d19440aaa1a3bb988611b304c3cc35ec4e5a9082a9e550f7913ef3a5a7e8ae6fbfab88ad4d729cd73083f396501d5959dd4640fa6248faae77db844730d3a499ddd9a46e2fb56808715b7752fb4e15b2a397058c6339990d3049816c1f286d9d36df57dbc81b8d457a545467206af5232c28fcead293eff5d45937d2a31eb23e52131fb512dc6661eddf89502d05c4160f56704584ef5be6c13fe7fca5cfbdd8245ae5a1678aab7886a970b720ceb6a4d01d45eb4cf376a5b36af0bd54b39d27e817ac9262b97050c3da4b172ec34fa7fcc127216eba45e912379a3d953a44252a83dee6525b4cfa521da283e5afaff7dfe73bcc56be655549c64bc46be9cb854321fd9fb5c66742fb8f94968be5f76af046b3bc85dbffed9263501b4e097427aab00c9fedb442426ee701872b9fc8657bbba12253c2413b1a6f47bfa14f128ccf7c4ffa6e9d57a6864766a1cdb8db089059d7e3b75f523f2a0064872ca588a32ba9f134e4709ad41ee380c726b3988564fe7e6f4bb6b6d9ce3100e407874b3db21af30cf559b7f20d88a5572b507da123c6fd69f7e40f861e451a631d220d91d41dfef955791c688c507037290d4d3f9135de469d2290c61cbf7d338c17fc07aee4b4d8f660af523b933aa72ccbb8829875284948b62353dbcdb0794b2508059b43e97176bb68d4d9b9070476489b9e9a0c2ea1eba5888723f2c9890dc9d858d400babec0a993fc6f40c16729b19b1423f0afa8288f19f545f955e1fed5940997a3f5f601ea846496ce3f272b09aaf8c3951be693f7ea2798990bf1a56d5e11108f2240a7e08b360eb42c966d074b40ec1b31befe9e955e1104d9a76cdcdbbf0bee6759f73ea08d264224567fc08ef06047aac6f1bebe5e6a0890bb5e8e46d2edc615791a4b3bffe2e6455315952a9f8e509531256c8dda2398486765d5fb8492c8543e504b8005e70a98b7209f5bc7aa69b21560f56d7a60d3aa7539adbdf5c8610de8b2092bf84c93efe72e980fdd8df9e292dca0cb30e259edfecde6d0e9f6b51c203e867e84ae37ccc72cce2c14895227ba22565f0fa13e2cb11822eee1810dfafcb0859cf426d4acd72121437105af2f1949b3994158304c4906ba8be08ec81dd0c7f36faefbdfbda1f537b187e710a022e246caf3981e790b5a25161c60bd1cbc1ac0502b712dffd7073e1c074764052b2064616210004ae63ebea391f6d0823b0a94a8c3d64f82e72495e1c1075a27243dd9f1c88a7cf2e1646d124e492af5d016d08be3cf7b18257213f3906f93579baa3e4e92265d914e5a46ac5fe10bb820cf2e5bbe90cf5645e016c8c842f8ae84f182e5bca729925d7169572e801ee47cac54063aca04560444ebd1fffe197c7eec0783d1a08b0ebe4762c85472f792123fae5f1db25ce6872e89896be2a6ed6c00c117d03ce05ce21619ecc82ff637f8f61c40b16485b31724aa24b25870cf984c117d524904e4617360b2e7469d18564ca374cb55b86b94a61c4a56d04fbaa8a91e8926da43d4f9ee19c31dc3697425e41b5d33656519b72755f77b5657db1048352aaadf37e80f0d546125220fe00a78cf2276a9f19307233499d57ca0c8d9fb1ac6471c15cfabeac6f9186661b0105ff5016c99ce9af72b51fc6b3702e7d3c98eb04d3fcb8ccd36b7a50925f9071ae9681277a72b85e72efe1fd1c29c78f5c6632bdff6c1d8556b2df978575ab41f5f5d68365237d2d72c5a00dfedf2f3a7a7162ca282e68eebafea66cef759761ac9afc84ffa6287b72c2c469cbe91be456b45928733b38424fb71a3d50b689eba39334a4553e172d61396190df26f231a1b339d2602c81ef321171a8979e07592621d407e9de080a04eeae30b22561a91f33cb2d99c40429935174b6b0edcf970d17d8d12e938aea724af9bb4900a9d4de4760b2b747602e5628260f52d5ab00111afdf785b4599a724a3b34a347f92bc47a1657d7cfeef88509cee0a40cde7a46fa57cd47a8eb73728582c58c354288f724472de6102dfd638a9e61a272461cbd7ae9c46192d2af72b7932390db3a7f2780dbdad1a47fad3beb72c9c80bb790b9163bc38e0e8ba017c317478af0bd278631d32cbc28dfe7b163f1913130ba8f3376c3333ecdb25f72cd1a2d715c39235475df8e665b5e576bda1d3369461966ca8dc9d55a53d19c72538c299710652b16bbf84a938dbbbc31d10ad8b315dd17b2c8cd76b1f5aaf6720ba4373fc142251d277d5c3c2361c5a420ca8a38779a50a6852af5344f00944ee761254904377db1e69ac213f74e2473ee86ec9152e3c9b2204b2501d217ea727cc4f1ebea59b6cd24ac1af5f11c87a82149dc44a2e338e7dde97d8634bbf0723f137d6eb05076745f9f2883133ae7428c37a76b0bb028bc956b92f228987472c747b81a7f4f11229da9056b4d60e09c8b7f4fabd0d180a03dcf950ee0ac9e721b28ccb87ebcbe5808abd265d150ed9f4a3f71b869dbfa42d489f36864c8810d8a2dd3f395aaf33b80e1abff40c19b0485938dbc7bdef5c968ac2f943b741b6b099c80428c30db6669154b3f113de97b146865cb6b76944dbb81212a707d907272c459c4c33730b30c9808ebaada381f3918b4680414fbf7f99b37385269b5723d6aae29d9c2838318829e8c27eeb02489b0183a138bc2b5e9779b017d6f38563a6c4ee50567f9ec9fd28847c0008244f66bc5d32a2e497f53e2bae9967a787261922fa0ed21258d49a7226c22756a59fca39c5ee619a8a39bdf0f3482a1ad7209a11e9b03ba197c05d070f6b727bfc3eed5ca590d1efb4202f6d7df26a7ed72f07989febf994855827499673615e64454f9cd0667dbf69e11e230500b5ab7126911c6ed78433ede00e340e89aa82d3ea06c5e084865418a3546f5260d336a72fee0e8f3db0bb7d3c4dfb9e5091a5601e3a204e6cfd824fb08762638ae17456f550b2db2a04cd0221a42399273b294a69a47fc3765ba452e636343ecf6c6362ba8e3b398e98bedabbbe664d006bf73f83511413baf20c46aec4d842631a7f3676f3f33f0b9467e823153836c3100c657f56bc36ae80efcbd371aef669d39222f28a76cbb173a7274d8cb9ad4a57e777bdfc38ee1999fbda225711190ffade5720c0e95ed6d0809d8d25481feb50eb1e1e2deda8af48cc63b56b213f6b91e5a1079640278148b42d3c523575e50d7a27857e31cc06b69861ce784a780879685724667afcb63b4ac9ea5730fd797f376ed9f3e46911051e9433852a28c7246682d030b35f848470cd25f28a48b99c91f962b3bae11ad0674a940c7ed768a0b9c266cf51d622f4af7395ee6d32156d4f815b9d97e0ba183ec5605125465d974605a5ec9eb48ce3f722f7918924142509006b7e0fd0f70af7e473d13ad30bb7e3d003507056c87bbc76e3f40ca9b38e1e216ac887e0acf4ac34139866040074030192bf3cca50dbae8c850d8265474f603df53540d7ef2b7390d5940ecd569c14f72e66c3ea27ff2e875bf369d3b176c1c567337d504f8f2874ce9ad1f5ea31393729f07b3970bd18645e60fcb179819924e7862de8b428c5cb9a7c81cf699bc9b2746be95e9dade98da715a824b27dd6669c95113cdccb03aa8a4d28a06e23c51724569a3a209a4f46db49953d449d3df6cae6c44ac5e243d17dcb511329aec69008aa4463a48a4d77823d72a12cc469b92f7610f3a086243def1d8b61cefe418724eeda53cad871da90164f088d9831015f107814aa31d271ea4a95eca14b31372a8134aef0fbdbf5f511c2e028f30711b3b972a621702a2fd0be473027c5d0541943a067699765c4205cc35083fbecdba5eeae20c918e2a0f9c5207f9058ba50725d391957e176eab60c4e0e0bb57a1908d318e315b324495de63fa4fa8b2bb6095fe6d0e9344b4fcdda5cc1ebf4d3a33fefc41f1215a59ba1bec99c84fd30172f197ecf7cd7d81f760d99f66b0b20f5151d9a69709e169efef356eae9c28a57225a9ef38a86eafa94f20535c1f28eec21e38063227b5a3275110baa3f6a7237249d420dbf7bf3e420cbf8b9dea5cb3aeb96bb56db9aa5cb019a86e0d9e15f07297e766f366cfa6ee46af745632309e43000cc3b4dddfc0629de7fce57df60772d77b8749fc63f5c94b92db87360279a3aed17d0ae2e529ce8cb65b762d5b6b7214e4bdd04be9889b04337ab882c60a24e03f7e5613b48c4bfe569126c1b4a5729382f3c8780d15221a51305cab44856afb10b6d29abfd4ff1734664ff3b94f53ad4fa48efbbcf9efc30afeca7940c493d6778beec6c262f8f471eda5db845e725ac73ae78218cc1c3e7960f94958f074c3e19ce4cd2925fa66b59a214d83415801a4455ce3cf6269d6b6404ee1fa29f44dffa7acaafa044d2da12fda5e9b0e7242340ea9da679bacb5445db4941b42c34927bf4f0153e6a8e801679a1846311080f19912c84c16a88e91b361e1bb0d95b5c267765627fed623636e886a043e0151b10a7db66be98fc6c9d39a76ce27e76c7ae25bd74c0a2ec01959df5f626f71cccdd6ac1c6ec850f8a31a428446cc6435d7e16ef0feeb222cb37a5e65ab67726aad34a9cdf51bcb3ebe8c1b95c609b60ff9dd38b0b4125d78e820b973e3ad72384df27b6dbd5c760ae2bdbe9cfc8faddbf03638652671353f8f1b4ea40d66725c512da4ccce2cfeba4c5fb804ac0079f68e8da531c193787f3ba78005f74172c93cc11401e8dd85b061a5a05874755bca4750baf8b9a4806abbb136b5b9c27230ec10c1bd54f94e9229d1a3bcfd06506c78c6ec82877d996c73de5fcf5ea10febfd7397c0a85c4741a7d1cd774b27c5701a4242d9cce1fd58e478172d989d6ee9066928c2ea3a5d935d6a117c5c3b3ec90a913926e38f324d6407f6471a2f7285ee9c195b71741b45b2504a7a262f7af00fda102c3751b0f5a647e370f97472ed3fcda3ed27fd325ab05955b1edf09e5f3a7a5cb730b46e2670c142e4b32a20d36439ced57754b7b82a62dee6511ed48bb7cfa1f5a5d9ae4cd01fef2a9bab3e899ec7f38f13756bebb3c93184227a680cf8cbdeea205be458b33d8367403672ac32f8df3d619eef649e2859e313b6affa7fc2d71628e151bdb19019b33a127235cbb3691be508ecb76106a190f3cc22d6338b0be05b5ff82515b5c848f1a472c40b0e691cc4fe1f1fbd45e318dea3a7504e8f709b149d17560d2463c00c8e72160069ba4f291e1dc1011efa3d5dbf2ffebff20a1e46ece797331b5a1c4f1e72ad894c489425371da8b25c682787dc28fe7b620a88070483e6fd78fe77e224721c0869c8cd7e8e717483f9087a1a07c725c0cd974f7b5d6e8c259a75665e4872624cdd5ff86cc2661d16dac1402b78ae2a30fe0061241a8841d1c783e8bf203477b772d67b57450daa91409725ed9c557d261cde426ee57d96782d97b369b372ea0b58dfa796caa8a9d8521346cfd0fcf8c0718da9da9e755dd4a9a792e5577201d44ca94378ef26a6aac9f5d3667578f06ae620d6390ab13ed4d7cbed262b72d1bd4fd4e5a75bbc752dad103f61014985e7e9b970699ac9a041620a65457d3e77bf17ef7d893034f301d6cf13983a7b81edc6634f4a3eade0bf37cce0e791727ae649f6ca79e4825f0d8b8dd949c0abc265f3770351be4bef9bc4e3b5eb3472ec8d8748fce390523030989e4b95f8de475fd112836f718ff74a10f7b27b15729b2e8f79f4922e015baed1c7ebfd4b53138398728f89cdd017a037c81345ec352e44a3322828fbc406e1c54da65d822df143e0eb5d9e5916c1010e9816ca320c5a2f8c29947015f79bdd12e7648175eaa594731fcbbbd8d54584990094756b4c39a8513d3f1c7f6c446bb5d37479f04bd9a82e7a8750c8728a4e838bced144720cb9adae46b225472aeb4ae08dd48d2ec8dbaf77a005274199ef759575cd235988219a350bbea13fe98847e89ae6d45297da09c3e344ae8b4cfc3d380e24d272fe5dc7ceab3d19719db78e0c1a37509d6a39034211ad016249002a39591d22729e8df0bc5eef2007fc7a7b0f6f6f9f50543b656d1854190ff527aaa65540be72856ae93978d6b6b69e652e90ac1ed79ec93f4103f3405cbc9b7bc310ded70b47cae2aca2e235b56e2f9b0b04bdb2b046771f3152e5d6b2433b0f1a33fc0cea663f99297b9febc3577ce84737bc76a75a59a25be5d245b86ff1beabc269fc900bcee42ec9b83b89e873a495d18a794e46272c021ec8602c15c1503694d32cff7215d33ba3ebda9e5da8e505d2045958c3c8ebfef89b81d436dcb55be560d63659a06e245c3915fe6e8a3876ae8fe03e6292aef5dece13323cfa61134aabb14e72e985a92060e6fdde6bcb01812915d3c4552f71df7c0a856a300defb2bffbd672d32068ebbe883865691332f83fea224ee73f67668ff5cfd8083b4f96cf180472eff9ce22fa9a7372dc932f31479077ec57c5d55a26c025fc9f60f49bdcbda35883cf069a6065a3cefade953cf9736ce91ef51120533734aeaaa2122699ae7772a3c06dc561f99f66957e53a0afeefdd82b2619782ec0247fdebc12c0874bb87292073c14e03b4d9dde698c2ecf976eae4fae8963eabc4d9b4adce74d33cb0572d25465df8cf9a632832e6d6b23e81a153e9a15df67f762762f8d34f12c7b2169ac4b7041e56bec62605e0cf77d6097ba29313fac6c9a94845fde88f99983186107951870b0d548056526694282acb22a41fcada4b492fcde1f056d6093ebf42e66d554be6077e452a265b0603e30910980e47f6796ef6095fbf623dd2f86b9723f6b1042af805a66563a53399c4cb084307fb75dd8eb79189235314ea6af0672e096c7465fe5c5a914901edbd7d4b72091df3cf9473d9fef9cb7b566ab31a572051cbcc8546af58bb7a87118af34bf869341788d4ffacd2b8142ce7d2363c272357a8b54607bb91161779a4f1ba31c938bbd3422c6bdb81d7ec00f918322734a9aa95ce41fda7c851911f00c8692905e934ef935fce05854e95426d092cecd1918c98aa0b336d68f48376356ca551b07f5f9e232e06d8132ad843baed7075e0191d56a0efe969ce01ef14a3e2ac4aeeedb9dfed890fb4bfa926a0a65512b2f723c9e954abf1a50f2877b873fb15ae822a169c05329ec86ea55d31fcaa93c3372ae9f33fb988e8b6acdc0e20ea88b1c34a68bc69d69805c6e3062d428abd8ac72a0598cc84c97611dd2d5b191e254d26f78296681a6638113306795c7238ed64c52512ede3f0f047aa50427d9737b6c7b0fd24e6e52b1de1bb6973d839162b0039bc223f3b9954d7aa2030c2e8400a4c09d910bcc4f4c6a8ed7904deb37b8e13ec9a288ef29ddbbf8ece4ad8822274558c2bfe6dbba390f40547fb24d716936720bb32170d7dd8b46077dab497d79d3722c68595f37bc9a3bed86c53b0f2d033b9f5b41f92ccdde374b4f65803da3c178dadd409374fc1759f8e5643a147fea72d742bb52064bbfac015ac1bf4e16ced1928aedbfad4f134129df2b6f6e94b00f04096fb24347ac0035d2686513610117aa4bd7ed32fa09b9fa6365ad70443d6ceb59fb11eec81b04e62dd6650050d38894f412d5fb93363655aabde3b32c50241988f2bab684f6ca37d6e6d7a7506cdaa2cf2d11e3a22d8e2bf9e8638c581672f41ae963cb2a544509e5c330b0f413e500f21096563106c19be19ee79ee78572f06ed83c6942062a6d974f4dbeec8ec820938897a85e3af236fbaf2b872da2728aec4919922888b20c09ca1232173855c2fe07f3d9b1665b77e86e0c2fa27a72e513919545353346e608c5f9c1df04430d403f427339b11db676c4328636c02daa57724bf5f27257b9b6dfdfe3c66de0f5b0312b2655d306bbdbee7cd45ab97273aaae0ec2fe3103e14b4b5ef9cafbb4a6353ae30c8c4ac0a8832ae8cc489f61ef6103b1e87f6386852431f1dd18301f46077ee64add8af5ed809173053fdc31e30a15c1d30f42e4b606fcabcecfe2aeb9e0a95268f9b3e40d9d86246bffbf72d111db22d4cc9676df1be574b53f681d713c91f760323f39a969a89a3e21822933415cfebb55ebb20b30b5cbfcc800b51fb45fb42b8beedac2dc6f21a20e70721e0f294eac62e01b7bb5e682287a6c6312e03dec4420fc374cc44c5c4c6bb70207369113dcb8c273834b194b02d1ebf5187839b3f8f6ba8d995918522af69b2ced9edb5749d0113707c583bac18a40a5ccb11689685371f593b8f7449c13ce72cfb51b5c4608ea34c87b9fe44518b9378a0ffa85381469d1985c71a88636505375688ddb90866f746a186e5397f2eae14006522018e77a2836f35c003c11c11329fbdb65e604da8e33a3284dd49ef1d35ea59a6f4d25ba956e23f907e0b9de7203c8891d8c918c8536f9db1de29d94416148110be4929378ee8ac6c288cecd23bc38593886b14ffb4401ac4fd49ddaeb31dd5d7e6b9ac377018a9c225cedf92d5978d52cc891226def34574f0febd1c6b00b016ad74c77ad8ee1c127ce6917726048b77887ceb5d7cae4e8514eac52489fbb0ed507dfa4349d514e611b83cd7219991d32d9342130828931be15be17a8357ea792823421dc7e74698186c4ee72dc18345a88c3945bccff01844066a5fcf7375e88dc783c90e5d8aa62f64bad72ed64e5a70d953f1a946e3cdab387cb0a757e2b6edc9951881808b56c318e9958fb34e45f1980c267d9cdc3bcdb348e61559a24dd6bc0a1c7eb66561e501b39720c9f69642d79bbac92802c7562c56a32daf7c20eed9d8e01dadf4e77a668e219642515baa9e89cdad3759f089d514fdd02acc4b7c3d4ed70cd9eefa4b9f8d55c4835ad632a44cb9342090b16059f7ff5fcb7d9823bea5ffb502b92306fa2dd7230c251e4e1336b009edd71235a9298e439144037f0ad0272809ba2381aa5e2720c6b1fa89f6fa78c2bf3fe015cd1cebd4de8ad9d53ae7652b56a355bcb6ce872cf29cc876bea1142067a6da37dba8556063c60933e9887d7d1a5929a94ffdc2064c184bcc5751224d1b5d6619b955fade90e5a0f99e556218f17616c7493d772f4e15e889657eb4d057bbd8da23294c0bbb5d4bdd3af345f5cf42fd1f1e579723518c29535f168dd5a7c0b7dfe448be63ea3e164be62b9c4cc33a84f1880b5509d45b50a293ddd03456891291ec154394fa4bd7c526d0d212321ac98ddaed972509444c229bf156d954c39d025e197db66350b1b833cb2871df04fd0551eaf72fbac08b02b8560c7d0365b4f9c7437bd6973340f1afc1b6a72242d00f37f752d789c70818ca8935f22c5c09aedd55c56f09e0bf80a547cd78b55f09ff2d071720934496aa6de0a34efb05381011c5688e5eadb33a7514cbdf430af2723977a72756830e4b9eb5a36a0f3866e2c5be61dc3f5f6037745262ba93f1c5e1019520b7f181bdbc7e1e92ee3fc6a7b1b5ea429d5e98bb03295abeec529c355b5d0fa72cfb7abdc11bf07bd556479ae1acad673eb02ebc14e395837390e8e10c68aa272eaced02c371df8f58439c6af00c8fa44fb939b39c7ff83f927d34e86312b2972c6b9cdbcf5d0b0ff8c337069a05e18f76300238106b14246fc23f96d1f2ddd72033447d26f3320b7cc8804a7e65aa7593b66bbdfca02867613239161fdf900720df0aaf62fc1539dcc41c06b644d52fbf4f517f1215fd5e1ec584e9dea6f7b72a24b6c5e1724b852bc10d60dca18361f6f5f9ab08eec23df6ab9b11c31c0bc72a93599b1d394bfb96f1f4388d12744cecbf10e65c627f9de60c37823143b030ed33b8016499d74dc4df4e73e5e79aca01506dd831926ee173ec73608930db6184627edc4c56a2a148952988e0540e887c863c6a6308423b718b065172877674755b9d2c43745b081b2416174e49c126452caa263ce3bede778ed0de81de27357e6e3ae10a1c88b32bb97a0e24664f0d893a04f5be0446054cbab43525f3587726cbd7058b4c8a23919a95bfc9ef380f21a81c110857e0f0a5e30d688be57ab519baea19ce7dc1c2e1e9f33bf542143a4777ce7b8664d761e17bc76fb07ca93088fdd1667839e29da2ccb4022858139b46b797f045813e7ff016f8163a2dd047218a39e42a5b2232d0ac75bc25f01b1936110a04507c36a5b9471d0fe72a77d72820b0353e360c997763f36c4207f420ff5491db5ccf3a8fd071c8111f5350d0402ef161cdce1fa9e4af8b9f5b4cee0e64fb43a9bba15211041dd3bac562c270ed4fae8d36d5698077d7f4b8b66943001b20dbc745e8a5169d595038200c66728e15a67589b69db1c37ccac746c51a1cfa919414eef05dd68d28093d1248c7d72a9259d18d64ec6b6116cfb20ce4fef0f2583226274e3d9cb5072a1197070f90b4e106c99062b1771f9806e21818b2776a01418d2357d10f12447508b38c0c4724bc086a32fe560ebbdec09bbd135f7a55766d6113b427ff37b1e14fef8744e4fc082f8fc4117e72a60832ba37a7c6ff9bf159f66154d7cb184a59caba5842872bec4ba03f7d14ee2ebe5563b7b7b15a8de398ecf32005b4c983ae66a1be0b27203847264816940c395552e0dfd1df4da80999920f6e694ef63063977303ebb0370c9f152ad9fd1017dfffe80155464a3dc34d353127ebc691686ab5f6b1fa472da818a5071d1078570c972f97dd1e1f0c58dc1de70f51f2b7fd8cdaf63253b72644651be3370e2d4f7dfae643f5ce4ca9ef4817d2f18c39fe977789671f90572c9c55935c9133534c398c1f75b349cfb3184ba51fe6f84930b43df4339c71e72bb09a6e459bdada0476a75452c2526da9b59fbcced9257518ffd992ea2acd71e4a179e4ff4b1204b5f287c7ca8eb05cb613e218b42f668426b14de68f979c672d2d877609b70497bd597ca3c1847983aee6cc8772ef741ad1abb8058aa04377272fb390c51ce95a607b9467b2597bc1a057c1d6c8015905d90fbdfe266303b72fc2897d815418637c7556812a88dfc9e24e0f64eae186878d66a37f52866986aa348743d5f717c7abed5c02d9943be07afecdbd4f5a752c6f74aa53853c2cd72a6138ad801af8e7a31a186b84166345b98dad9161cf6e368d68eeed45f026a7258dbd863721c4d55a132dc9918e4d844651f51c77cd3161559d9556532c7ae5296cdc6b33a7f743499a1d173cfba01a5383b7c8445bd669729364244d22c7972d53e9242f3a2df77a269eec42d3831c92b32d12bf0d90e57f5a51b01a90e3c7297e434282ad78172bd71d948a9e2c9a484932a48f31c955f3f30046082585d401f127e0c4f4bffbd7781d1fcf94a8b8faadcd5db5e3b32665da3717dc3e6cc729ec1c837cc0aa055159ca16991c8db2d602ade23fc6bc283be5820e2ba4c9d720b9e139e14dd3815f52f16872931c718fcf0b034aaa7452bbb58a8e82d9bd64c788f930c01987decf7e76c76502e01b21b61e819f133d42ccc364a5ef5e38e720d0378de268350143c265faefe642bbf737f5f7adf32e29b769d1dec93e45b00242f9864f772f2577f5220a0a507cc87bfb500c98f21066faf31e080eb957e0572b446e1d3c5ada959f8d14452d8a55e08bec3548a5d79ab328edd58171fe872d38b74779e431bc99f6865de05b09f4e7cdf00ffe2d05f2e17632452d81e7721ee7136018350b3880c07bf3b7c7be91a7daae877a4edd946da2b616247fe1108dd088c44e99c9a65af45205a61801b856ba0695fce243ad60414cb0b22ea0b6ab51d91d534869d802edbf4768543b916841cb28e745f5e9ca0ad4f9dac89d072c5f23812b098adb11d33eb983620c77625a5e3e762d9ab7b1404f0ee8fabb372b0ca1e26d2087c40a1da43481a9092a566fe2102815af20f2a912d3bc7a20d669190913993e0bf37fb14fd719cc378800d82a526d585789b931753df3517145100b6babe341f09392d1d41e5ecfe9183017d77fce7de0aaf975101b4bb93ec7253e8d2e2a1821e880cc29e50dace26a4d4ee4df377ccea688119bf3b6fd12772d5f1077e9780c55d24616a62890e26b09e29e4779cd145bebec5f7d55a2f687265de9d65051b14321107c973e256370ab8acb47151d6cd5cca63f9e49de9d625783c338be069a915506a2a24c50709cfe1c70b2f696da7fc6f02e36dc6e15d7230cf1331134be458df230836800857cd5bf42bca2fdaf4cc2b9259457de377398f18e6a742bcd51eeb93f9d7f2db07fcd3d48d7c772d3ebe336bf73e465bb30b5ac9aef48cc1559dd447c32c8485ef841671dcba7502b21b54810b2da6fa312c53f6da99a470badc607b700621f6f558562eceba9086a854a15cb88dbfc972728f0e2f6f6edb8f6b74cbd803b0b1e5bb8617908d1ca95eb5653796448596ea72438bec87d8e62c6a9d3437394c14fd2ef9083260797bb695b173574011abea726f72b21b0286ae4ae4cf646abeb4fa1357855a47b0b8f539ca27c428b3199a72a866676755ae38e1723218d2db96b90694fa32e277fe1ec9aadfa165ba265872a7011b4c184ae0bbfbcdbe976bea979b24fa948472519ec83c15b9c0b51f0e5819d7cf209f0d7a1fcd3923483f9a0bcb154b84c07b3ce9ccbf48f538546670398591213ca1c268ad971a3291373ad1181b46344e819096c3ddb2e809ca183f023867bf371b7ce6a22b85811110cb7c5049f08a9701393a62878baf4a35663245302f4e2edc5ac345347e48a7da1c8fe2b5d97b5635ba7414b2e4bb7488a5c8725bdd9c255476cefd1fcb1a7a95f92566d4ef83ac22ed50a412aa474e65908672fe60571335a99fb78c70b5ad7f2354d3ad0aa8520f4642571c9c0e94b3b15f72cf21e1971fd1aaaada2c831296203cdc93d5a92757af8ef42328f4f40d03e1720b5c40de6ee080945497de28d26da7371f45e15c8bd4633058c69544c7112c62a5650271bc8053d851c59f8459ebc58dd3f3ddd9e04ed53ed2c101c40f6e9972e152a2fd946992b7b45e2f1c6d28761d9fa8ab91ff4d7b3f559f9dee92cc8e726b58ffeb794950748d76acae0a1322a90464f9a98c0f7263b8e51b646cbc472a93e339f83915b487c27852d5cbec42da78b9ba2e5c27673df4b3b8bfafc0d6051c62bf6db65ca0b64bbaa9ab030bdf67f3dc4d390dbdb4767208f48aab36fa7267f91e08be9135b415a65dc4c0315f7d3721b6eeabdbb8cd5e126f271bbe75723daef32b7e2fa06a98f459bfab9bdca9046297e5b79fa7c73812a714d94653494580f58556bb4ef59c2d0ad156142800f7b8e87429975cebb68555fc1c673d724542e3e4b350ac6302ff0e046abd68dfc7b11634c2f823932a11a247d2a9ae4431ff65f692e1e178af324d091e80c8830312b67a0ca2f097ec49bc3ad2a12b1b8abe3e572fe81ee6c5384e8e3f35ea81d8429240f6b5ce52f0dff31b356cf6381ce707a4d6ccfa7641ac7bdc6d6cee3e289a8cbdfc55e8a12323c99fad0bb95ba596d7a7c1c458ccf01cd50d92555455cd108c817b2c498516f48c2a013d69726e8746bde12f295fba9a62389997c125bcef79ed45f409f7fe8120b6dc83e54bbd8fae3edd0ac3c67d55054344218c759ca52cbba4c4fc578670111be21274341477564386306b05ed5c2f8912134f56d5353e095840cca002f9235dbe22824d8e5b4881aaf176613147d950e1306bc77603521db8d45a7a3ee8ec258c64f103a9bd3e26dfabc26705b96e8b93aac53b7e4e38e367970f047838972665c0d1045861611ff2bd3ffe802057062d14c414325c3f49fa946e1d13700725ebb48f20487888e36bfc4ce89538cd0d41cb87f966a40bb18b3b7f5b7dc059852fa2f5420be00d0f0d8a732eb8e396674adb15bae051f15075e66ecfbe96c171420a75727c43c3e1004e0b349a6fd0b1463e112e4b2343e95d7cfc9ba999af39630fdd72b53da9c9708ffaf3a9c7c29b40b5722d4e711eaaa4e54766d675b758ba3b83720a5373893ca0bba2728b72f9f95c240f15d58ab772246c2c744672fe52987972e564f4e774896cf918fdaab0899f897ffddebf5fc87a5ebbbc4f83285d77f46b60b345d97188fa726f127f0865911cef4f465000c695a93af5fc6939552891697bf29e3bb87a8d34f5823fa5ede3fd58ce1fe26141226c18e35f3ea03c0f3372ecf2ca7cecc880163644dd8b0c95d16af561eb325d87c59069ed32c433bafd319af7914b76faba5f7ef7a96cb95b5d920ab293b7bbbd66adb803a9d47250e8722ec0916f88b90fc96126ee41400cca7ecc6e784e6d4b9159016545516f5c76728fe651e49861e8491ad207477dfc8b86dc19eaad75704eedf5135ebc57ca4150c8eeb43f0299b8e76af8b3b937bde55aab84ac7078be8b0aed80f9411a4b6172f9a4d3d93cdafeb3efd8ff479967025c07d78d9fd2917435827fef915aeb8972e52524ed1c2cb2221baf350c2fb9a900166a5ce5ede44a3cc6dca1de0c927552b3b7639f4f1ad71cb8f0afa0277c01a4dfb4d0639105ef5d48c8c95dd7f72a3bdb812f4a601f5bf53cf4386d13b804d02d70961ff4a147ded154265505e1083e7a1cea805452c0a143de570dcac6b01a3f8caea72bf7495847716efe63f28472c2d25f45a079a2e52c29dbcf29700cb496426688686a27737292a3d61011ce7262a7e28e65d91f7480ff811ab79ff5040db0166dc6de72e89defe714a9b6fe72bac68f84a180f5a103a51eab947bb6a6aae1d3885c11a564f9c0b7f4bdc1f43802bd3ee3b68d2e7888e96f9ee55f9adcf6741753bd8c2e8d24f17b21c31c4672ea5bc006f38ceb1278f434932c9c15d1a4973fb745384f5b8b4a0e094c59e7235737bac5ac59aefc563ef8d49349f095201a1c6dfb27d5d108496a67fd729d05773714776e48b2d0d7d8d6685c61e655485bcaf5a9d4f666fa37e55a97e93124e575609cf02035475bd1b80cc20b9c63656d33c176d2fd5096c8ec1cef6c157242c57f0e5f95302791d9dd90b39422fbb7b718361f475a8306d26fa9ee82a1485094f95577b685f1c0f83cc94409f194ae66c279d83d5df79407ef5145848552e8f0fa9a9be2638bac7de10ad3ad4fa12403393eedaf8aff216ad45dddb4a372938377a5efc2302b1b4fa2075ba8ab3b90f6aef33a042ec46ea9c974a0f6b326efa16796fb3d309b6b05ef7376f448a9fb168023ccc8f4a58d33c00f80153272ac20dbb423b97e8eceb89a737b798f5bc5596bc6df84cea01ab1b2292e9524723aec97f5ec0d1eff8b9909ae01eab5aa415a3efbb0daee7bd9d1846ae266f172c96e10acc55487cd8191c6ed4c287c55090c84dadeb863cb37a19d55ee8d8272682c38eed6e03934368086d52e1a0e494d3496c36e0df2a31b21c89a97e67772ddfb78bdd8af7048016acdbb40fd0ddda950817dce1cf4fc21e6366654fadc7298a4cf23f7fae2aa794638e0e2564d838d85682b3c4af7a55f9e5ef56ee1fb72d48765e3ddd9978203539c558b6b6a47792de4adb01cf709d2fed3fdefe73f72c1c81efd0f00bb0f58a1b4068016b5110af8e74633767555b5786c801072a87217e9cd0aa4deb474e9ccdbc6fd1e9f06a503739a297cc4484ee6d2b9c9d0984103ea2a8f83c8ee35b5b57439e374f8b938cf0e8a4237061d627f269e57ae38727bc0c305bf615def8fc8b5504b3f713a122ebffd7590355aac3dc0f85f07b451285765cd0834dcf803ae522ad5706d43baeca457178fac6331e1d61087f658723a9963a1db0224285569e1a07c791466ca729b4e537e096f639e5a4798ff377258a02e5b746b72d48ad2e9d16d375e25efaa7ff0a84fefdc0a0a34ae6221696b3f39803eb935e1b197dd19645f41a4a60e9c0f1aa9a52500d4e884cf70e2747209944918fd27262933faa96b89e49cae1e49a3716c9babeb422beee496c47e72d25f03fec62b6883c47f017974098aa2adb9eaf6127485400888136995b6c25b02214e973ec2c483a046ca15a9e379ac19ff46b9bc334e1055028e342ff52e3230f83b6c1eae663b02f5a99a90bef66fdf15692d2a0716644e6e068395b51d69ef162489000344320a213304f90a25877fdb0584ce5a0dbbc9a818a6f6c61c72325617a2e94a881009029aa189ed651ed51aa0ade9374ef8cc1a7dd38e56596c04e47bdfd0e4ba925ecd4dec991d5b96e8a22574bee5eeb1abf428ab395d6047e3cf04e1940823a70c3f19f4c83d40c40b4c77f686cea12c5ca741f5949e733e5b60048d47f6f85c6263dc8cfeb80798d715dc87fa123bc3ad3394a6df6e01729d779b1fd6dd60af0e17c8acf2ec35304d6caf75bb1f5aa85eeb948388fc2f72b053feace2a1592577c078cc7ba2e513030ed79ae74f1f407f24cefc2e0c9c7200293f7b7d282867dce2ddf6a6c8dd9b3d70e8497f20cc9d781debe8f57a291e3dae30857581d2697105811ae616a2c8ec74deb708a35da87978ef444e9a3f7277e592265a6038dec5bad527cbe6bd7c7c865f232f74e146686f7b1cc93c8b722c1d8b6c23a7121339c4e60226792973db2300e82cfca1fd9ffdab6c7364d7728061888cd1e15488f17e2adbe7862b68992ce08a994d1b025d84a7475eccb972cfff5930cde8d5b6443cadc6ddb88681a8f540f84abcea2d9178d87eeaed547272a92ff6c73e9f0dfb591524e7a5884e053ac435df740994b456fbca3616b20849ef5ba3bcf045aad5ce43323ce176fba08e86468f64314d6335eb1abad95872e59229202d88692ddb6088eddb6b9288c1554602c15b35297f72f28c84525972a06c00567e5027deb115e2b4eb858ef7b015ebfe0bb591094748ff30ad5e864b79ca7c837729b61334b919d3d0c153649e986787345621cfdc3f8f29a5281d7274ae020ed3599f976296acc61db8e1afaa4378807e910dc3ffb8f5b5c9e2fd10ad6a623c22dadfb980c2321b787f100852d8838a04253119bf6f69ef9d318e45f8e671e88c764dba575616223ca9df154b552d013a76c4381e653d8e41426072a3af3aed90f99b2c94df583ab15811d75ceb537d6542da30689e9dbea314d43967ba52aa1e356611a5b94fd8b9cd0b00d49769bd177a7f37726f44458d4efc2386cb43ae6e476251873b7d1ae59da2fbeccc3aa2d8b0e49c93f6d9252235c7023439bee15f2aeac2c1fb6c38876ffb62a4cdcc5e25cf4e27b0d12557b17c1072ce7cf907b4d44f95476f2791a0123cc50b4e902f478bf4bd8a1d1816a92587727bfa0a31cfe116dd70f1b119d83ef396baa60481a4682a2374f1e504c3681505d798b6138c5d2337729f880a6c88a0538f4d9df7ced679ace8e6ac5547412b7285e9940429678fe72d5ace7870d3616e458bf2c154bd3a13b19c93420c8e7872cae4d912cb5643262fbfe16121e65e4b57f623a8e9ac6cf864552c6ab24b9d50ff110ba6386545be2a9b80140d8f2c608858982cede563aff60435068e3a64722eba5607e11e562969f7beaf4b54772de6d61869ed12bb8488902613b28a7d48130c3a6f447d982e169d5e34674ff47a7e808b80c6b1ea1245098ee3c6f05f72d098ddb7ea057e7046e50e508ad1564d296cf6c6ba8173eed5823cdbde20896ec33d8a68c90aaf0ff5f86da30ee236fbc1b2441b51e851a4a57b24384c317072424e16cb3d9c2906d202b560a5089adf6f2ca2ced7fb0e2d1c6fb6a4c365796dfa26fc6c6d8f78c76c29c40db42c67790256088cffc0f87071f6f42cc7bf0d648572ef40c72a1d4da538759e13d3793e250019345203b01c1143e95a3ba39972eec6aff559cdefc68e29d44ca301a5704e283f5ef4e80baa10170f32cc483f30402e78b19b30dbf05a618d06716d02af99fab4785b419c3622e4d40be81a1d722834ab3153e57455d58e1a014b1163508aecd5bb7d40a53aa1351508b44bed72a99b42bbe54af9eb268e91b60b02c41096241c35c772b12401b79de566aa456fd99f96e41c34cc60eff4ca1fa5ba250fdedef20bdb3c324eebc2a659f53725436c464066c05f8fef297b4b38e5adffed1458f26b8215b22d409e86fb90a77472ee679cbe36a76fb60e92176ca8e6ce4beaa4fb60feb0b0bcf55a30571f509e72a7459b0388d66a551266d767ab6995e0d39bf46f01721101d64e868c0f31fb72e8910f15f8234d51c465a6b0a20bf2dea13a10237580311bc7e2e5c06999517252b21dcfa952a0ff9073009b04dd933e7d49eed058ccadd1826d54a820e1c02f61b955736d1fa0dec93bb45e41dc7932cc9dc886441c3d2e358eb41b28307f727321350716f6316fb2981f2daed55425308c65b1974d4acbdbfe4350ff005972783401d5ff4caa3c2e30705a4d12a5dde1df04d961bff987e88b3e079ff3be4e2507199a5fbf2129d897972a0ac5aeb9bd226e83c6de4d93443b25d4bf16535bd771a4cde4cf2d5cff3a50b62c031210f840eb378bd55ab200adafa805cc7f14e3bc8c2d2fa5092a05ec207cf2e58041d94deff9315e4acbc59b234dd249bc0f8b41c5918ad06339a27e6a2374ac3799714a611b94a77989c9e596d3255379722c30664ca4b792cc89ec2b730fa2cb5d04379c48a6d9da6ee7523336cd91f671608f44362ab7c75f1e39fdc7cd1f1e5fbc201bba0bdd48f9d55aa74267cbea72a07b1bab0013539940bb7922b02d02e9db8acee4367a78f5c97046c6730eb3315602565f687545e4168d345ff9917ac312d4cb0123da719806dfce0097a9a447508f5ea0b9d0005137ac38cb2fcee3230698b700e959f5f97528ac1a6bbb1e728c044f39f1d1ae103c05c98dbcf4cc79a79bc9101bc4955f78096b0652dcb33f7c83f4e2b3329bbd6a78b3ffde4532656fbb43d9b9dcb6fdd39f417b2f612030d7897a6c70212b95113bd5d71e6a3848b5aa941d0168e4f68cf7d90e9c0b2b7187af30eefadbf4d9689d7665495321ae2e949409b495bd1648fbc91120a540211953ab13e6d559cd2f61fec008e1d3fc5f574c3319e5675f3318ea5b6dad2664934f9f63aaa746fd4ac0c0be46ed122c7a9036a8c4788a700edc927b0b40cf377ff25d979897ad0409b44bafb1910d8e124de11a18fc86ba81a57406100676725d38f605a942471b5de32004697fb2a08097e0627ac5e70f7ab6bb9f3e544a72ac47075a4d6b907752b0bb2cd9db2ff71ccec8b885c27038021ff0177a53a9559aac181777a5f649d7024769b7ffe54d0b4b5828c4f48eb6c1a73fe937e62244930b1869576fe4666677040f6b05e6ff9af5aef9ed54c76b70fbbd054eeacc72f5259babf93c0337cfcca91950999c77d3e3f9e71adab85c4cde6635ffcf150fe0189ad6829a8ffbbd0b0bb7832e5f0f78fb8d93f0cdfc28486530c9a6f3b2722480f4bd688380939a05d7e0ec3a95314ecee8298449ff684a91d4b0cf898f723f2c7132738dc65b1e5b47485f5e2c6ef5f3ae06d2d9419235507dd0b4f8581bbe49fedc5a6e680e7f69da7db4e276c23b1923f4ce3cbca756f32674899ae37203346ee4a28cdb0732b60435b392e17faf6c1e55e96ba94a4e1e30a4b2ebeb4ce6f28e9745e917cf4c1d4e78406473b0cfead1103d73880afc227abbe69e1f72a72f872ba20dfeed71af877d57252ced13f9dc03dfdd5a815c84d9aaa4d58a04e4913add10d568d8a22c388495e43f2c8fb30c5c8f63d13a4d86704cb6ed7a7166d95a7e21ef6b3b6fd9e1202c63ae0c94d45c6430d03a69880aa923718b9b03c9af1938111a525702d90266cfaf73550a43078139a387c6008c8759f61fe7724606824f3ebbef2ed8299f8f59518567ede1d5d6b5004faf152ad8caabcaad728d668beb93077701c812c71fa8e864951afd1706b21d127f519471ba14c8e272a6408c7cf5be0ca57b0831eb521565708e55d436b4637afe8fe7cac37658780d39036e1151d5c24ee2a4c4603a63a482cea64975ad2a7163097da8d37be0c272dc2f747db7a589825bbd32c3059814097e07c3081a715bc843c0b76f6770ad188ac364a0e1cfe80fb9a53cccb36fbb91b1aae21c95ee757d60b3133d1a76c3729e08e9cdb1f34456d3671b4455b3d5dbec44b4d76124b9e24e4e9186fc66b3727a4ab4725accfcd2cdec7ac402d88f05e7d61c348e8fe67b81fdbdfb9019b91354651935fe00bb3f7d830b1e0fdb8022029962e0911fd0ddf1e9b810d6f34b7262087e64c78e083b3eb3b0e0544d753194ad6ed5d0cbed2010a699185aec692aa785d9889188847394ccd093cb20c8408ca12613b1344d146ca23c7aa44ea3720278f812fc8cd5de7bc8b4a7b5d09122a9321bc7a4513ff5608d3528948b8c29953d2222a993f27785c7c3ca32f86aebb2b5eb9f82258dac5983892c9cfae272580b14393d32a4dc0e822610a1400fe40897528b28d1d76f57dd43baf26ce472bd3e2506ff9b6a2d8d87db4a46f3669d3279f23e970686ec4a1d46ac0ff2bf1c8eb2e16537e0b3630585575ce9501e2290e7fa34536e1361cc3b367554963b725d08b96a055d9e39954ca820ea9d72a279852933fe745a14302bc412bc83e072e8d00e900e1de00cfa1c663837bea193a4166143e71cfb67d7044a0d271289032ad9151a7b01b090be903da22484c5e84bd70c9e1e8311c2d050d97e44cd4272db6204c1e63e1f66b3e590c6241024805ecd9b4f33f0f27c30004868615f7572945403f58ba03d80246a6791ba994c9534445008e2698bad780832949ed34728771c79915851daaf7de3e2ebe0821d49c7db07eefd183ccba377c43c60bec7724c1e5367226c1335d98fc93d5bd42f171891abfa1f8000f1c1e14876b0bcf0727e832eb32f9f49366bacd998602b66a06748f2ac44f7b39bf4937291197aec72824b677ea4cdffd9154d9b5a846b3ccaca8d8833ce6555ef229b12295e6dc77209ee1234c2b84cc4d41f6f3d0679c620adf538727a18f6a9207edfd33699807232a3fb8cd018714e3fe03181420df2633423dfe36080520f16e098107b01e829460acb4e641958313bec94c6ade61d1312dd8250d04880c58ae6289b392cd47238588ef0e91ea23ae1507450b06e7c641743afb91ca2fc01be801bd0be7f8d59e67fbac3f9a5d539c6f059cfed776d77abd6388682daa913fade928636d6db2792c649684b4dfa7f802d2d258a46fac1c0b5fc561fcb221d20ec88074f3378211e6872e01ace66df8428440202a361ad67f02d650c0c9e6ff0ac9ecc0dcaed57a987171b9bc419043842538ef80f6c806c27dc846eaabaa3c6d1f3c798f48c720cc24d9ff3702eb28267eeb2eba06b6955529723bf25be42e9646facb1cca6725608211e2378f80d61b433c51882d292fe4a502614f79ac48aa0f9135defc17270ca755904217b7c86a6ea0158b3b56291fdf71fc23aa1409d24ef8cbc2b292b9cc4d2c810a4ddccb3fae6cc50c20b1db6c192c38d8921cfeda7473e77304c54d068d7d658e64bb723ffe9945cbd75eb816304dba6929a3ecd440c9b2e3aec72e5b4acd8fbcefa3b113123d4d4ef0dc1e6fc422f5b4a58f6e6c39cad0ff41f72f6c3a651d14743d745128522a33811b74bea20cde3ced2c7b10200962dc363729b86a803a62e82d234ef94bf1a3ddf138041178671e8a63fdce934a634f28b46a5956d3822fb74a9ae762fdba0ab8996d466ea6acc52a18337f0d1a5c49f2672f42d9bd009d1d437090e7c92152b3f39a66f533800fc50befce8867a87e2ff7234f2eeaa4a734fd655035958bfbea11bdac1b436a86d3a37dec7c2732504224809e7cd96d76bef31f45d9cfad4d5c2858a711a7ad3a682910904d79d196b9f722a7efd09a100173acece2c51b6d19036ecb9291c14d6dee46edc816153307d14191cb7fb35241096c3ad1f425e0741ed97ba328027ac7915161e8103da5a8f72195359e5c32109a504faa18d5d4750e8b30d7ba17c51031eb0d5073f47f7a760ec2e8224b47b201f69e000b64e8ad5d5383993405e35ea1e1a0d64805dcbc4726260d655bacc85a97f47ecb7300cc076260f976b43aac1a419ddb543b042b15eb6812b8c46e988060cd8408034435ab847880dd7c2c60ecc64523089defe3e14ba8655291aded136c888c1c5fdd14b7b059a0f4223e633ffb8036e2eb406dd65f77f20983b07d10f022e35f0a60bedb3a1e5e5712f0f7897b122103bde8e8572f302827648644df2007d98418fceed423c814740e454e860d6168d6b6eafbc393aa14b4be804cb58b199f9574041b098e776de40e74898918801fdb000b1717290c2e4b773376b8ec3207cb73e645f50347ea9c598789cae9591d5e7a5dc3f7257e4df51ed9c5346c4e5985e92e886cfe17e6d041187a5ab4fa1adb3d4ff01514163bb7abd7c337a29a4b1960b7a8d993780e1911bc7409f28257b9d759b4a72d3a4a3d3b3a45fab2c742104c01b3c54125eb0739d8df19ea4a4e0650da01d370995ff5e43b296e897ab9008bfcfebcfb4e84e1fec42a6f400ce552e42cc4872520c1145d55298820642676aaf8baaf27016bcc4cb185b04ebf0e1aacf89d4728a369c412d6efa1017dcc3f1d5441ffce9ef6cc01c4ff58cbd272961c7f98e72649296190c2b23d33422fa01b533a3bc7994a9c9be6523a5715c2b779f353972325fcaae3a6c845621b99678c6d4cc3f05155c37f77febbcd6f1815dd1e8741bfcf0d2bdf31dc3163af2eb6b4feec06692ced85de544fe7038945a4fbdadd80b8fcda9268870d65063ca36e3acac14be2d43155b33a15fa4e389300befbab072163ac8f9dba53bdb2ac04b4cf6075950933e4c57c4b5ee5ce52feb3db3015772a4f743aef31fee5c9d7cadf1719324537f9504bfeacab0684057443bb718dd729aacb0a1e6675a058467efda7a0f925d33e7bbb773d30dd243735dc4dfad91729dd641deca0f1da68bd4ecf5fdd8a7e8be76e203a80ff5fa7a7bbc546d78e37212292246a5bfe4a0b481502ab0b4dd95af815a686e0e612a883bd52f776d7b728c65aed4dc80297449d31149b553e0e7821661867f47b6308c428502aba38b72428cd7c3226940a8dfeaaf9f3c3a7232cdcd95d2675ce338af58021e8402f601ca3047335fc4dc036a0118d8a35ec81fcc6c4ec6283786b3533d1230aa9675724fc0a8a14e00b11ce0a65d7f7dfca27a3c7e79f1dc556b7b18c291105f6b64720bca78c851cb32a5f74caad9f4a925aa76529d43cd0c63c0ff34edfd49f35b18a6f01f587d02be2298d2c909fde04223afdf517155df71c5d8575e48443e425101346ac63d0639aae00483547b028657846deb455f02bb060dd0c5c5605862253f246b6a60cf64f188b004ad59ff8d008ef9214555e6f81bbcfdf6ef683600077477b871d1ed3f397b4ed6b9522bb91e1393087fa3902e90da8cb610f9a3ec7252b3b930bf6f69b8b7600aad67224af0f995ab90741d435e3c78355204d725ba0200001664a6e0ea12d234b4911d011800bb0f8c1101a0f9a49a91ee6e2493e34d8e7b2f1a58476ffef13708e123368fe2c8e03aea0f3e396ace5d845f1072edcc9ac472708c558bb32a1ae4919fd841b72a79fdd87ab3cb18c51b96bcb57c706e8c7358f8b1fa55fa7081ee2a5939ba3681ccabe93da2bb4d64edb4d2a9a23e4cc1357261a8026f9c0094a8a16f8686c85bac5943e530382f31e36f59ac35b670dbf6723ae97400eab357d72d5f2e28a4fd6c82a17bf155f554987ce484c3bd53fad8728ee9ffc1ce5fdee9e97bec1fb43664d4a27a1fa735c427f96884b8626d59b472ebc5ad8fa12738d65d07809eba6eaed1256e4afc590f006d93cee68a6f5f3c2b1470c125b99c95afa59a23b5f6280ac8272881b19b4f32a51d87c8d06cac37723f21abf78811c900a0f55adb25ff03148ca0235f12dc90ee126d0bd8d47689723d67c60c5265a1c81a0ed4e3c22bafb1e949925b24c0671e792b88ebb7149d72c8b8da46cdad5c2eddc1a7a84e10c813070f8f77ce9ff77946817a4fcb9f661cea752c710b3cd106c188f0292f589b3b1df9822580e3dbcbc7b981c1a4db80726c3c82c6078caddceb94edb5fb8ea831b926d263ff36283b3b00eb749374027255588d73bc242f29477557b95cf66eea0a380a624f4b6cc3c5722565c0a58b725370fe35fbc68caa7283af0f898745a0aa6b67705188841fc5088a79a3f2301f2f6bdcb2113e5bd95adf1032a9ec9560f40ff400c2dbf037c146345f780d4738f994daf75916671c9276937d2845f3f18254af2f196263163834188d78ae107205efe444b5b89c44666e0aa8611495b49f778253ac3d9a470408315eadb98966ce2ffe00a50b7c500e69e1c9c30d14c5721b098bea9bd698da9c198d6f15c012272cf6e7eb16bf27113905a253d981c198b5e604cd9d0d78701f39de74c7f95b11314de8b65425d66eb2977ea52257bdc4404ae089148a09ff937c7657a90f213353d7c6c181b1f8d329d4bd46a3b0ad5a17367768c295524bf0f84dfecd2c7218de94a2f419d903681b654af476a54b4feff89f1346e0fb609ff656e3eeb75a36959a5998cc73a585aad0cc0f8df0fabb3428282958999cce2826b5bdb5d7726e0bbceae13b2bfaca8489e07d4b421fc2a919972b168d794cde7761a4e90d0d6a16102d1145a41a3999b64bd58e26eb7c1694c1365eba5c403ab20f99975814d2d6c0d46fd21b567c03f037bfab8bb43cf23844246230afb4f965e998f28d6a40fc6fc6cfc6e756793690f3ffd73db5de40ddab93d482709ce3cd4e96f33f5d1a1a297e11a00c92fc452a0d92ac5fdfbbf81cbe2b6d98d895038186e6598872c9bb05ba2a4bd2624e1061b0c5592a7ecf696977f25320c819ac2a37c1c3c920cedf81998532f29eda2931a5f42af6d0386780ebb23c552e0a4b3567c245337258c32db891ebc74b612dede049b0f81cfdfd6d4047c5c2446b9342dc735bb620fda31f99e209afc62e7aa11e50261669c5560bea8a81be462b6fe981a9a74a2fa28fbb678ee03ea94aa764b5733c3696ca8d0664b52d57c72a2cefa7ed844d72e1785d17afb0f787f9937a66083e9ab568b4a4da07f4a26e6fc000acc96a7572f8188eab0c5641f366c74b18241f625332552748d18c354f415ea278b3956672766cf04d08b7e45d37e6cb05fd570164481d9eb624e5e29ca599de8db8cfa5725416467f8ae59d1b30ae205ba6c739e941e2abb0680cf4893ee18395085dff2b12d4f0be170b57c4ca26087aa562fcdf53a033567a75ec32ad1ef16bb6a30e72fcb7a9f93331d4b0df6fcac43e9d6029478a76ef95ff7493d6abc72f7f402c72802d83c892899982afbf0fe3d03c8d256e6d62f3f9aee447e5d61d1d500e833d0f112feaf396dcbdd723188afbebde4638d1eadc60f5bf829bcc396cdf3f29720d879f6fa2f5bcfd7edabb353311ae6655dd5fbb08b1672dc53d63d4392651542536ba5d064137080f26731e3db6bd69fe7eb850cb2c74c2f6a1a69b17eade1345fb9dfb82241baa441499ea8570ffc9f3ccb1ec70d024f6bd9f1051a7275e720fdb86815d1d7a109340e68c5643481a9c1509bd63e715aaceb34cfbe183494e4f2e91d03d53cbab8661de558869c21cf206cfd4e1567cada5df218499d94b2959b0d684012aeb7fa9a9dd15af27fcbc19edd9f77a12d317fba6992f376ae672f4f6f7dc36f84f8a1d29009171fe696fe50dd4031871eca24162536d27e8842d2ce7ebc75d33fa6e7765c10152dab90dd63a6745385c7059cd818f063c75e85e811dc32bd1ed29ef08e56fd022990d00d909b9ede47147fe61ecfcca2f640172c9fe2e9b79af47c3b2dcd806c4abde2cf3f39a71e9b89176718a7c74dff4855fc0570afd9f43b7c5d4d729ad8bf28d0b4792cd22065e7be118f7227b30102c723310c6131e047f8b17cd9f8c773610bd295460a238fd5cc12b9e1f2f42a6a272ae558e85f4b4c34feef4771baaa41aaa3c7ccfda3b0d7954a04db2c71074bf2d182b85a2571480ba99ae0392863d411290938e2ac3d52688c1f055420eff2670a5922c694f431e7a5b6ea647eeaf0a6ff73e3bb703b5204a5125f981e850c372df365dff0c2cb32586273ca72a2be710e38cdded4e95afec63e9ab2a08569f72d01fe659883407e6945f1eaf482805c675047415420468397c6777bf42095f3af5588ee1aa41aca0ec4e25513a76d0ead47233518f9b248fe738f0a590d77872fc378a18e43f0d8672978750a4c62520cdb965bc729885b7f00a7c0d901b4122e5614220c0f429742908eddb690bfd74354385931c1d3c0867577a0ab59bb7721dce04e6c2a0f4b5596e2f9329aea935bfbee8be6a2b4a868d237b29cff19e5efc046022f8c9a19e421b92a48b8111ff5eb0c37c7d2ec0c6f975726cd6277e7273a9c68e3e1dd9cb6d7202dbe889b020113a3c19db6e5c7988a756b88b92b4728225a4cb2b96a5660b996fdde4bb470c751e08b41e23614d84fcf22a6cc3ec723aa0b4104ad2a9929fd9be1575dfa3aeae7e7f3a1dccd22af462ae8c26f2d8315e9a971b6ec403a4e7c9744d8428a1a9fb18577124aa3930c11a59968bcfd02ecba28e4555a642ea37e6090fbcf813b5ec00a4cc9b37bb39962349fea759ca72a9f1d865b339d2359bf7f1ff47351b14d57ffe42cc14a853631d99ce538b9563679560422db96b8ae75a367d9e452a45131c65d3b24d074d889383e64cc92a2cc0e6fbaffd50ab4ed22ea6fdd9a143b6d25101eeb15d3cb1a9f7d80fcb9d4472b57ab39a6e1457c4f9101b099ac31c853759ec8da5c2d5b9be173df7b0c6e87245d725223a578891472ff34a9319ce2ab87d6805a72b05114a3e83a22b08947233e13875349f96a792bf31d91ad25fd6e0c75e8bd8d382e672f02d3e9fecdd51ffa6b00eb5295346fb5ebdc2b3ad0c92c4368df8c7a000cde9eb7b20224f0172471744e1e854394a7a26b57f377fb99c03c55f1ad1362268e19459bff3306209fe5429d0c1d1a63febe67498364614fdb9c3681b53f57881f7dfd59269787c72ead7147dd9e3cc7972637495a01dfc5d92b813660e72ebbe6d9b9d542f9fb54bdca3ce6005179efa55194f13e5c077a9dfbfa60533b7e5e3d21f329a02dd773ef2b4b8bcb5ba974ae7311929da665e7609f2369fc924fb936a4940af49b28272f655002c31815deff9ae9376321e11423769016277283f2579c6eecb154e567227481580a204576598f5a8a2ca765da76931cb8ef53a41076bad4a785269d80b24a87693ca829052742dfd895de086e142a9dde85e5aa68066a0ae8a283cb772ac84634c4bc54d96183abc0218a39e06a166ca949cf4d573f608d9af7583b8729416cf6841c1b6215f8063a6860df3a031bc7762f1a7e5264dd29391e7f6d272652e945b03a58231c96a0b075a67369d15230390746241b2e18ffcf116d8c472f5b7da38fa6584b3b8972fdda93b264858337cd6139a588fdaeaebf43fe0da1125e6f4b952ff3ccb44fa53f6bc955539188ab7d9903b58862de29415fd6187328be67c87265d91bcb73f037a5b2493178bff00362e54f93b50127945ed2bd36201f05795b4520c817b157699a52290cc7e9bdb52ddf6fca1d6d3d951669f64729d66c974c54a81534f9f0b1b83ec4de62d6145a9add1fc7e13ffef12fd6b3c2e0d40d2a4218e815a2fdee42694621d49b45996a2b9536e4f26c1db34913ff361e0c048a7cc9fadf240b63d3f3c082916af53301d555f7f0fe667f283ed660b5f8a7b07f0967fe66573098eb5fad94c0085324d8afe750235e8852dc322a49512287d86dcdc23c3bf13f4f33b918ecde6acef9379fc6d1ee1056567de8fc49472ac8d4579d5f774e1718f29023daa45aadb7b19ece46d1ab8f9bba91abedc2e6c56e7598f0badb5eca812b9057b4f77b7b7ff01d144eb13058749c2c7828a2f7232091fedd1f29a1515ead2feb27432e21cda7b672215dbf6249e87eb2b77946781bb9843139ed11d83a97cc62364177c72e3de0e468551b2ac65d1af0d5d10721fcde96aeb435b1ec0d491e482452b0653dc803a20ae9d796b47d9f3fdfe6a722247f41a26add98f2d65b9a9b33f0678f1cd43d7ea66e4a17af657a25e6cbd7250ba845db057297db0fa32f8b643a8281aa2cf08b2fde60bda0a3db9ceb63a722761ce0e41c38061b3603e54494d51527beac66fe9f5efd894b49d5991f90866b4537b70836ff610602f879e8aa1b4243f506d86f273f5fd58b28ddbcd030705473f380ac3a2bb30cebdc532b3849f4b72620324c2f86c04e27c10d137ad02679e20aa03876fd5bebec61fbcdf453da4ea4074659ff15664f36dc2c9b212a0726a51ffa03dbe433cda6df17e06f327b0e4cb2d46882e8bee88f344d7453853498bfb8a12a1cd65eb69627b15b96a740ba7991fc2b295b8bda1640f11c3151b72b4c43ac1edccab6ce6457f0d3cb51e9001aa9eb44c0b0beb9a5629ea8b73d6723c019da2e1ba41fbff172743fa8373aedbbb011dab199c2525ee0fb2817f427212b6025ea786b5429aee29cc18ded87757ca05f098e9d02de4e6f800faf6511fba80f7747e06891e73ffc54982c2cb8c11193b74ac8ed4f2e042feea59c67f29997da6c5e684fa38466bb089e9273dc0fc8ee03fb23eeb2a61abfb6b61236f025dba6db849de414de376d1320adf69fd50630047834cc5e1384b08fccd717b727fda315fa805468e1fd3fb652fc29b5f573a140ae39abc3582a36d0f35c2aa504c7d0ca416ba15ebeea8b3e25e548886b0d697ba5567b10d008c74047c969c72bc954655724db906a8c93bb419c0f1c7e7d413384925ab0385739a77f079b97291eebad094024f03fea43da289c9a35e586a88f902a20c9c1852b141f9d9a072148d266b39831454ff46d3d213d012fa205ae127b9146ba150be4cf9a7aed07275f159c76b11b5d317d1042395e12e87d9422926905770385b9a2372db27e272b93cbd144f536971590cdd0e7d48cedb59e8dc0468e49c50a0444ca8682ce43d7a01a612c281c4ab6bc1d51a2b7499a5fc00785aa4f7dec95770eb35b332c77256121b20ca68e17cb5db913e725bec3d5f21f227bb6a432006f2ae5e791ec10697fbb5a3ab677e3001fb486e6e46fc7954b5ca9cbee8a158ef3c266481a07372055351fcdcc26d547cd7786445c528a827c371e16a0a5b128195864428323100dacbb298db34ee07e178f744364995eba41d9d8e62aeb080165a504b606b241c4c16326f17fb34c2fc12cf2cecb16502816676bba15e35faf19577ac0b7dff72ee986419b00cc1aaefba3627024e98c0622f0535560741904caad4b2e9f9d05228d0a3d22ecc843f7218cd31cd508af835e2a52abddb21373bd94ca8e3c649716d81614b458df6f73ed08183ba8d5dfa6df68ba1bf8f53550ebbc1c212aad7460c5d38feb36959e6a3eb3127c8d61f4d392307ef87e882f128a378f7890bc5726a3cdd91ddd0b30347ede868239137a6955489d75d898028cb0dc88fc0eb2f72462bbe2b2267786c85be792688ad613aee9fa5dc715f45d07d43ca3106739c72dc255942867fd50a495436b554291cffca02eb08cd9c9d49aed3bcbe8b04c63989b8069c54038b6d0a38f719f57c53e6af69ec34043e2fc7c1b53f5b3baf5d3675bfa8cdfb7afff851b453596471501208c8a383b8abf2453a89dbe4ae3ab7114b1ff1c8008641ad2fb8b5c4ecf2faf375e315c759f608827af71b2dafb2ca72b44078ee6f9795232b17f9f9982f71465affc5c555e3df3c9ea36ac91df2357261b726bb2ecc0e1cd7089ac2849fc5eb04150168318f5d39ba7867d7a0c6f6722dc2348cd59f2b1c8aa9da7239653eab7b9ce334ce1b3b96924a5d8bfdc46e727dd7ae3ea848d8f5e3aa62e62107d9bac7b32b19381d89c74e64c6da8da286000600cccc22529b3a1a71754ad8fde6bb84907228dee3be996e1db75e5f21b772b9fd45b7e4bbdeea66cf9910641dfb042eb11fba0158180bb481f56fc3221f18a0b76a89dacfe998cc43a154687cc997e416986d26be2cc1cb6ab37bf8485e72c1c03ca637a972aa9e2a5b17257cb425ed6d52a50e3bbdd4d6f9d93cc620ac3d912488581e238e1c630544ba4a5ef809050ef01f9a5e69b02a790344967c365ede1ad67f6edc51d858f6dd02782e61c5c2f1c0b0653df0635c794ee44fb55872b80d0d47ea1c07d6311236c4f160c2a87cc5d78257d7484fb6c3f119841d107270625bc49ac1ee239d9abf11066f6ab0aaaef0439f869f03b5c947992fc25a0166eb82a1a5098713d0546e101b2e65abb86d397d9dfb14550de7aeb6889dd700f19f230b8b74281f4c8b95c4794276d679c58175112c2ce484c11c617c5ea6720377963d36f4991b19bcb2ca0ecbdeb1fb8a8dc32da8cdccd86acfd2995e39729af76c3d80e90b48f791a807f6daec78cb00b5b7d12fd7440fff8f03721f437246cc1697f08ee562f0d7c88b8fc37b9638aa42c300406d348c9a367f3726797295df9ea0e9da56ab36d72a165e7c822bde99a78cdc74427c16c7c44a288a3072c1b43c7ac1351ae0847962bbf6b48e5b8d0788e11deb537d765a5dbaa26e0248db946ee5e1cd49c60d61d92e091a46111f2c63bdc3402b517f0764a59810a1724074f100fb38e20351691348a0071279c910954c07558b90dea408582f1fa7720dbaafd76898087bc146b4691aba246f293bd4d254ddafb8d93b87e00b09a67217071e5026fe8b0257358e86224be751b9f116431cdfc4ee28d20ccf1d6f8e72378ba9b054763079e415403ab4f36abe95827232288d1b56aee9fa351db08c5d6fee2e84a28f15d42079241da805817cd502832af798fa4e3f82bf791f4b8f7224d828acf7f5d2733821dd049991baeb689e44091eff79311f917f7ef143624cfa922aad4118e1e85f4233deef3b389785ca4d6b855b86a88dc1c83881ab7e72fef0afaad5d19715eb525da441c794a38a6be1bd792021472c12da8fce3dbd7212ed122235767d5cfe35ecc60f966f611468519564a0501da518f5d9287ec57263bdb84c7d136e9c98abaeb858915cebda7a2fdd4a580997fe2c5a7c61b765723ce8f9b48e681aaa1689512fdff91a41120d09e5a5cb88daa9aaacde8017b672aa47bffd72d510b375c4c41b4cf7a5b2626c5d04a09717abddac8081a953c572e4ca88036197277a5e96c315e0daa15ec866c726423dad8b9e391622fe0018161847a8f14feac36846fcfd6ae88d34ab1ecee84498c92b1b335fa208eaf15772a909cd391de340a7e91adf088e09a98c4d1bdecc483ad7620fe0bbb46e58731ef86ed092a318608ee50f556f40726532079902400b9b41a3e42570fdc449c1637c78ae4a5c6a74609843556796147fc0d0962895a5c72d12b019195c60ed690edc7f609368894bce0bee444e015ed68bbf50457d369c145f479023ccc64c10728a1226484bcaa3db797a5852d3cecaddae4e49c3622240f79fdd63b1cdcc392b8490be57f28885a0756429a9934429b8b1155422bdf966e08862332c7de0622f4f406b18ac7356d22f64d6da79b8cf4dddc8bd4e5aa4fa523765582c685fdb677dfb2beae3c3cc30e5f1ba834ec19d2f751c87b96200b8c6e215dedbf0aead294500a26c17537b799e06e037eaff75555af6d743c227a9e7bb482d61f121dc0eae7f04e44170477535e0b3ed822cabc66590aa21d77c022b3685021b69780935cacb1d3d841ce018204b473f4f60acd8c355098233a7b69da6bbb8791fb93e727c20beca5968891531e49037b452aae33aecfeafb905e77339a1089769a8ab7284cd0a820477e9ca48c4162acebc90bf51f7cf806b51fcb56928b06ded7d3d72edd68c23ff7f4776857e795cfe0ae4558a110ea9acbedf0bf60e91bfc2d2b35eea87930d0c8cdf225132354bfcb3465ae526cfcdf86cf68d5e43ec65d6d548568820fe121e407d660806be3db7564c55af15008077f18b8066bbd975881679727658bb90e34b57454770d7c030b29028e4277849855cdd5616b39a3f6199d35ddbc9372be93eba361ed23aca6fec1093149c110499f9bbbce5b7e8723dda0f72f689cd1efc9b422a3fe2d4b489f11eb359c343e895b1e0b80fdb5a7c89dd1e723cd44dd05b4b32d36c212abe27c66e318f051e588965416f36707a2def6f15720654a654f19eb5836b07aa9cbc2594810cf006e6fc8f716e123ba62e62762872d68901adc71b9676feeab8d43cb29085a8d09d5e3594144e9f5f2ad9e2a343725d25623dcb068bb67523ffba05ee57f0e9935994b04303983126feeda7b364204eea307a031235284dad40e424cc7b23453ebcc3d38f2ad5e23b18e7e830356b354c972c8ddb62fb0a692a8ab3216c9b818dab4c27c49940334ba5329a9c3d3392b12b82d8da1a0d7722b0ed65eae829606d5f1c66e0d3905acb32d0c476da72fce85d460e0ded56984b1c446743cdb7258d6dbbee1709b0728df2bf49c2c94f104d8bb2f05b1ec36cc5e29694aafcdd97396c4c603b72e179f00418cd8329656e516caf0c1d61caef2b06600e5bea543b7af8383491b1d442ed38753457757274d91bd650de2394d4df982baff2c5a8c595f49dfa5cf37bb88e4e8ea4930e001801d1a9cc820ed44844a66f07113578d68a5d7354d44ddde66c2f0f546e973e32daa3f12bd8271d73bd2554b387e6466f77791fc7b7074f93b3f79d9592e509bcd83f947147c7ea6a37d637d6d0abeedd7b40c974ed97455400b49813d06f2567c5caacff266c0a7e8924c33d0e540bc71470e0a7092d876a9a98cd43c3e8439cc56588afaed284904656d767a7e68b1adfe1fd313fcbf1ffea72e034403e72a03dbb76ee842f9bc697c8e3789083e5d07b41a6cb42234e8cd74741700dc0725dd58517f588edc03f1f445da52d4ad805fa3a2776ca1ec9dfb801dd7cf4e853d5dc895f539b204ff9d7c9436fdbb675c55b25a47defaa53ddfe2912678235184fd7fcf6f5ccd8e2761bfd14af7d93a5ca7bb388966fcd9da04b875072798368b23b9c69756e354fa53e1264eadaecedcd58fa6b42c3e03e042119307f46e37217a7b6e61e2a2e29b213e2442cabe7acbf44e9339379e182b00d8e88857c765a73839d2d3483c3c9964f66e6c27d99ef1c3975e483ddb6a396ad6b894e620c080c479e79fdeef6d3b19ec6b09e5f08079747cee136f464576c5764cab36dfc72dda72001fb27a44c42f1a158a734451bf88632ae61fa817baf11d1d03b1536112e9604f218dde4e5cf185c5e6436d3696cb6eb4325da36944d6b207d53041b729812a93b8b501fae822c6843fa18b69dc8d5522883b1bfd3a6351d90fabd581ad2f905e2fc0f24b9ef0044e910283ca1f463f64aaea98840b706e0728f343c72e1d9bc0adeb113ca4e9da4e257343a43bece50e8ac3b541ecc7e78707b4a4670b2c816ad302df1d18c5a77c6cbe5bbbc253831aacbc58b5b4a3e9a77d7517b729d2c4d4c8438fd6c34803ff3b4009ce54cc57ac5837da21c4e30ec68d5bdcf0b7953c054641a2c2fb23112b28ea7a27fcab9d2d3be9d017c39a8949faab1c172d502d83ff5117d2a468eee11a3de0e97840bb867f4cbccda3706736cf9e3fc7220b15f72f76ae4be7d92acd9d4803047147da86740ee6ba609184cd6eb5a377283092358ad4ae395c33b3117b3e15be20af320ffddc105ed198ac3c3722e120aaf3121b715451705a7dadb4d2638e00ac82d435cf9a8d1d49783bea97d2ad572462c773ad4a5b665606929ec2dafcfbc3800d8c3ba1f890cfec65eac943bb872c89381b24fa6b4450304a42c2a4eee33fd615dd06e8cfb39d34d0ccb75db3372dd1d2690a68dbe92cadffa13a0fdf48ae21ff420dd4a0ea82f550de950268b72ff07d4c6887da2cca60d278c2be6846ce1276a545a56fabc643172e804a9f75ba47d056cdd939cfd238e2ff0ef83bd9b9b351fdf2959280f183c2238d9dcb2729ec6c14167f931f9052635452fd4a9cb6e4ae97637962ef19ce4e79da1c7020837f76fa61de7beaddf47e29c545529298b08f03f763fe7d0e06dd52fe46ad17293fcfed3edcc3f61f1e71d3d3bacfd1e88c267f16d68096bc097ec6d3fc9c97287582f5f27678fb454a5e814d85190dc62173f30b63fd397bb7b607642c145720cb0b3706411dca2deba2b1d68920c90015873586cd5776d799458894f9b1f36f9477c5b798d0bc0ea3dcf8a5fc7f13ec444e73203e5d78a392c79bedf157f728fd47b88a211b3357ea1a2fd7784bfa0db652f3435b1972b3a0de2d238d59d72e9aa77ee4caf311c13fedc766402c331a28f39aa3f10ebf6d2f621cdd07b6a1f5f8ee24504de6009805fc99c63a279dd46e0fe9ac5970532c0fc62c68ef96472eb75859d08e84173f7a2a49715a558c3cb1667951b30b7ff08c8ccf82e3489729986df09e4a2f2e05dd85da53c9b1290ae0775edcc1d12cbfea056b5ffa1b4725e44bb6c29a900969e6794271fec7fcdfa97a77ccf68c3ec1e590f988c96c0727bce5a7ea5d2266fbe34d4086636a82f742969af19fa2a8e4a5c0a1ea5d23d7222ac5cdade33b758282519bf322a2391c9cb7c4148fc7977ad5cc0c7f83eb807fc74e26222fcbb24da9aba905ec27e600118b2be808493e92729210b3154c25a25c204d9da161c07607735e77ac82b2e7ddd5c1b6f92915ce8c59a67ab0fa972d9b3323e494b008f142a9eca862fbbcb5dc8a5ff76c5896121d6e291ddedbf72bc4345fab943773657a7709189a41cfff4ad6fc5f3c3c5ee4ad3e56232a6ae0eea56bf0d873df6adec423fe1f2d18b342a02261a99290a8ed9f1c26144b8e83a74fcaad4ecd76b492412207eacc3be8d0ef3b18d91eddc162bfde228b125ed72945af8f8ad5ab599d17b954bf3aaf5e07945241137152c5fa382723fe3e205024488d374fe6a2f70eef54dad600788f83746092b0a2ade3468a935c5596619315e246928b8c09a588723cebe6d24470e07013797c38f647fc3fef46da2335d727e5357bb432a7ed5ee9b6baafa489202de116c214ff67dc475d739ca3aabc856afbc66a01d795063731d4737fb656a9e5887b430eff253b1c64b7e234d523c72207ed0bbed0955abce2f5083b8a5ff4942a95ad90c154dd56f6d3aa59d9879724b11f6d7cbd534d1c121f9ce3fa7f51ab8c3c9387c3ff069df1a37d3dd174f5419270176313e41abe3bd5c5374733a436ca369c233dbec4d2e150db7b85d1d72d453cc002cfe50ad54d59cc2302dab1d203943e8fa4f90db8b3c247b19e9e572bb6816243939c010e5e7f4d1ea1e1144c443ea0cebf58679a14022b45c88cc0f4a39bbda08d83dd0e4e9b9dab0d2215eb948d201cbce49519cd3a02e312d912b89af06a47f7b2cf1c0065267f5bc86443a995971aef636b5df1d1e28bfde767208ec89b96ce505e1326707ea1e481a3ae357334bac08f954207eccce587a1c65980be6a06ccc097b97e49ded1c09fb78b9266bde0e859424a88c9c0bc2f83672dba1707a427642b44ab1213c1860be49768a7ca8f61d0059613907d21e988372c5622165ceab135dbf1fbf146424ea9dcdee58af9c9b06f6bf712749003e18729d717c6bc1721da525f5fa9d05285b7d139502cf283730832ae50da926398e72d8c148fad9341b6339ba0917815c38f900854a5d58ba53d00dd68ad98b7e427206c34e52bed0b604fe7fc2717731923f7063498b395802045a11e0f7d2018572a7e50a63df9a91398db898141ac0e411d28d77a0eccc786a3e8378133526b5609ca21dab1b48012703c8c555246232499a2b7fd220a4a157a43ddfe4e1163c1e425be16aa913ae257896737464884ec91321ef5289d22518937a599937227a72684904d0be2e1d7e77bcc05cf9e650c36d66d491b30b8d361a6d4924d4ba4f7291d04da574497f27d19939af68b03e48576e3b82364842ad1eacea0cf23aa215245f6426d76605289f2bc2a54583535804d19b4adb819297be0a55ef6565110dd4aae14ad1fa6bc9f0dd7f487bfac9ed3cff87ee4545ea8839769b2006f53d5c9ccf403354f893680c7c35c8ee0fe827064c931f68542491e13dc4b246d7160b25105171a6b4211af38f9ddfbd7415857ba638d38ae0e2d63be7c9749e4a031635993828a7b30e9a388a8ac022bc2ec8e68d0a06fe6cd937644e96f9d798ea7286bbe046206f0b58f7847bb52ed964379338c48390e228c1e5afc54e5f9c1d702904a14f6758a9e4d7842f53275345a2d139ee707171831e81eee8b57106dc514e434e94d47d69de4f9fa69706ded589a65ee97df0da81c4f76c59a3df1be34c103eb91af5a3e01d69ce36e289cafefc9ef5ff09600162a3ebac7590c81725725e2ac14aea0254f13849e3d39657560d76e76be065716d2cf73ecb7e18e6037214041fc83e1382f99147c36b23705fd35cde6b5e8eb312458e5414c581ae367293be8a4d0b58c89efd12a8227048faebf92798579bbb5d6c514483f74ee5a4723dd35f76e014fff932687c49784a20545b884daa87ba06e58d03b566cc9367727a36b6db5716125ede0531275b9ce2a9f6e2bef17f1e63457935f949de71b672983fc78f1ff10c289aae1c573674f52dc02d18c44d7a3b93586fe0fdc0e3291963f63d024bbbb316e3e6c5385babbdd815dc54506669d866a7514e056bde4c1ce133fa9ce15f0a20e706b0f653c1c3598f62c3e4e00b2bcf2cdf24763a689d7207495148f82050b8c1914ac97e3d9d8841d2acbbd42712adcfe6abdde70bdb723718e68e44c75ded156fe52db1bb8896885a6e59509e27dd515b2d12cd0fde1e8b67e064ab3e75c419c668aee16818dd60c9ad936285f1c8b3cd4d077073970f0940f9e1300446fe6ea4e0578295bead34e97bc8d1d0621b482e47dc45ec517224df0bab93259c6fe5648893501aa6363961033400300a40c7ac7474582adf19538d7cbdbca5965388f4e67a335af0c44f22c7b13653ce509ecff43685a1a818f932d6e61e881b4c2b927dae9c2933beda268dc7ec2c9f7409197b274df5101b986b3a72d6af12abb99829e3f659ead815078a8cd7f45c6b0bfc4159f0c15972e0650551aa576cd58fb2835f107604fb39ae4d1ca55e166b830c8858bc54ac721a42fec033f2521c16c0deda48ebdcc2b82e14e805f646c18a94cb268556bc09fc08a5d70479dc23a1bb6a73360c31268ae493b54b61f4fc3f416d1ef584d2720993145c8d0d523daca8cbce96a4cf636c10378ac58692f7976b62b9c8104c724a13bee02ee5347178a391b8dea020ef4b9bf505af976315206bd3a20474bd302721f61d19697df0a0ce1e2199f1c0a6992407ef2c1da9522ae8fd1481604472b903b8ce72e6da9ef29293aa80cfce5da0edf1acb3d6531876992ed9342c993a5b917ac7c016268dbd6b406445bece140f7215b5131bca064cea68b347667f49e0bb90eb082d48217d460bd9da47c231db6203e2a0ac8fc2a722094cf7f641403f2c830f5b6f8f02079e6e41fe6d3fbd71877d0692a30e1a45dc91427bddd853105d2c5884f39b1f778bd6ffe6745eb0141f68b632653d8479970210e0bdc109896a5d382b9ddd87b88374448f4d07db0bd3c242c6c62f9ccf3d8da28078c172749b214da1a19d1463e38be2fdfef4e53f2aca49417eea4b28f2e1e7a92e7972b7fd68fd62694a03de0c709147fb493d820c34caea81db7b76bf2f92f1574372c8f93da9db41b404ee7059afe368abb823252026856fd638b948c603fd8444720ae264764d21febcf1ef15f935b7480832eb478b04f2d81db01321089925d3724154231365b299db5f98d11cd3af8fe63b63631646f75babc808f9595a7c0272da5b29231197986259f83e73abfd6d5b895fc8a73a688df4634af87b173d8a4a62bc970f32775f63236debbb04c115a513f160365e8a3ea1f39c9ed54fba714808754426b71f1b50d6cf21e241c4b78f342f01553081648ee7ba8ee86bd939722f9f9297fd26b639dcc8f0d11267b78990fe6c2f879eb2e471db4e0cca5f707200fa4758c549b09888b18c3b8b377a8565f9edab82575552d69d71b5d2591c72b6533474272d73cbf9c9fa25bcf6a62936c0a4936e0202cb62c6f64791c54072801c6cbeff787b40e771731fdde261a1817e6194417860dfd5e87f88e2bb8372121b25c9ec38b3ce31c5da90b71896f02a34736170eb32a18ebacc374dfe6c72c24c16450f53a29bcc1fbd6ceedd34cc8e8f4cc6b8b576615461dc77a99c2d72aa9b83041a8ae32396a775d54150d6630e23cb778b51eb1e99c592733ff4d372d2c9fc4a6083e3d1054cadcf02a0e370ddd376dbd5c63d63c3a3caab2aabd3724e17fec97ac96b8f0aad9b79abca995c3bdcedc2dbdd9a7507218d4615a8400abc3094877ed2630d1213a8c28ebd36ab674135b214f05dc075bbcad00311e744cf3027756c65817e73a0f5448a11fe04fe2d38268c3dc68a95e78c521bbfe672de6a23f670c9542372874c031ac21c416628780d710a61918bafd8a8f40ea7294753c214cc627bf1cf590c1496687905c5baaaa5610d110e5fa4607d1c9de472cecd5a2ac5a649c8150fbff3da1a0d7d2602bf5412af9d8fda1ac41b332a5372626ae76d0c000d2922bef4d163aa1f56c8b4a2ea2ccc7e6037e84f357c34be72f36e7c7690693e27cc5228d355dffd430eef9fb912d44be18fb0e9629756c272914db6c41889c3fee0872de862a39006878cdd6f571c53de13f9807a9d09c972d28d8574d06f3622cf811c132b4a4ca8274e90fd85d93780cd6567afdfe63072f01909635386d159283fa8eb0173eb0802994517d5153e974ebc3fcd13414907ef19ef1c7e5a9c36e12c545cc30076802c260b72c1f0d7372e5373f482f84272da92a771d97e786c078170f59fb591d80fc2ed54bd56f9c7da12b7c45fb21c3291642cdd4d0f0853158faa1d06dbcb67e3a371de9a4b610bba65a11bf5cf84727fbe365c1208546b4adf130fdd885f73a7bf4d0f2637671cb46e4e0a3696424faff0e2ef69da5a4b83364a4a87bfe3f03f6fec077eee4646b9cdfb7c18554050029900c80490a0a991c02fa52308c546585816b171122a20d4e5d9435189685a3e1ebcb19a1c40fe00323c851f627352183c2f8cc4473c9e59076de50891b00eaccf8f3533d6f0c7567caee6bc973b7fe9d64400b9a2c12f4766370fa7d629471f64af6fd6b506f6c0e66b93c12f2b9e2ea0d86a113a28f801cd68af512e61303d64d1442a21fb510aa20ae164782b62795c9d19157a12eacb7e2be42fbc7072e5ec849b493f6bdb1f597afb53dcf0898934ea492a1674c47b56f7343eb0f072a05e8c889a549629b6ed48ecc9e2ec0ed4b522a9100493e96f6ce3913273f631db0902902c08ef1b3fec61c389fd41c82ef019f59644506bc987e99aeea09d729b1cba4a1c60ba8ca2f73c6ae23f31e46b2bb56c6a5ac4aae001926a40dcb1321f3116c2c2c1abcfe95b791654c60f8d49f90a2b3d64db7fc1d1b0db5b7ab6432c814bac8503232a730fa7bc60f9a552df312e82837b3651e8de0af6435c0810e1b6e3bb25ec7dcc97e12c4e610fa649aa7a9f34c7521f96164126114df719260fb0dc459abf9383d5b7af36477b790362423752b8e6f81058ce90b03b03794e7ea01114bd60bc67c4f1157e199035ae11446e73ea6f845dae89bdec505c8a158d7fbf06ead18950b0af7fc4402a96d09ede0c33aa957a3d24fd963231cdfe725615d450ad0cf57c9bd98ed351e91138d15849ec0690dd48818bddc28d2fe40b064b43272954e6734ec485e1f45eb00c0c8aac7a303b33eb7060246571c66e729e48d33b5b1509655763c7069246e27e8072f67281dbc2dbf120fb2d40550e4f4b291bf95a099ee78165cb9e8fd20396ff5cbf8f7ea6e84843a977a32e4bf272b521c7b5de73763fc7130da80dcc2d62934d903a25cce710256b2619031ca572f3d0d0956cf633a2a561db8c0fa9e5e87ade252b54baa523b84d83fe4a474d712f1d1322077c8f2f267dc2fb684b0d4f40fd0785ec4660b44208d73331172e0b64b7ea592d11d9150676737358b55f78147fc22ce018b5fc5d1c2d33a14aa317b6e882d895b18207bbefebe963c33b03b13b3196d0b5041f7c4901cb31421213c3413fa2a41f3d061d7b1498f928ff035f5ce9cbf10522a9014d4c10f4a10a72eb9261a9afb329a99618511b3dbb83109f99aa2fccbdd77e276d99ca87db954f0ae5d3c46ba524adefaa3d2269920d86da5efcacf5bdcc9a921e53614ffa2c7280d2eb4eeb7d5c68e97edadaffbbe6af58d260970e0dc727299bf2f14d6c5d729f4d30d6d3349ad443fb13e2a7bc350e7568e3e9fdf3a8882d2217c925f743729d0a2f210c20a3da140e6681f0d85fe04ab83d965aa5c3da74d94b8eb7692962590ded9c8be6dde5ebc40c6d4bef1bffa298f8aa3a2660e12748adc7333791720a18e6c7efb0f596b88aa3b5619a6ca9ca04d765b87d14065975969476014a728c8603561cd5539f566c7fa530238d0af9098b77b2b526fc95db83fd3be2bd72abad77e7136e476cb293825aed0009520182b0b195dcb7432eb5971cbf537272205b24f65183198fb405574fc97341769498979df0296fbfcc8172425bf13372548aa14b421c639f612a4cff52ab5038486b69d5d458052f87664e6ad5b8d97240dc6f9a2a423d620b930c0365dc9041a7f42b0ecb84d81c71f34112a7bd0272df9da56713844bbdbf6e4bfd2d14a111d974c092c6e7d4d5ca5a804c88afc73fcd04bac7494b5b9dec7cb0abfe25300d7ecbb40b06c7a0aaed0b7e1f426fdb0f40a2f573f3b38043699dfeabd1f1162781faceee5e8be3ac29de868d01b46523a1375015675297ab98bbe9a77e4b60c82c99c88c610fcde440bf9016abe14f722fbaf5d96d3183b6a523ea9ddb6068bce73dee0e65fdd88813e7ec1a76809b72e2b0f9e91615cf849fa29da75da90a9b1038f13c8468cb8764dc35bac4e4cf644237af7a85b173debc5b6eb9aea72f07daf7fbd21ea2caa3ec81f4341c0c7f7200b1ed88eb300d869ae914b771afb1ceb0ddb1c4c1a9c5b904dbcde89c378f72080281e48a6edb3bc7d48d1fc952fa3efe3d34038ab9df9b7541da6be6b2217224c28582e2d6f3ea10b6996f1ea44021b9e903725afb55dfa53aba7bc90a41135e0424e27f52db274ca7b311b9b41529b03d2169447c7222a8bfcd42e895a4724d89b8b00a554bbdce32963e92502dfd2e50af9890d1dd3c3f9d349d1cb45b72d49b0cc6072b2bc31ca013765fe04456be12a8841cea7d3cf19e0c5849a92072f1064d0a827e0fd1f4c6a372c7ceaf92957577a2a0ce94b33f104e6097902f72cec091130ec94ec8f43cb76ec8fad137926edf51c79784b617c785657e98aa72ae9493adf6aa340caaa894e38dd1ebf428baa8d0faa385f11b466125bd5509724099dea79615a873a50ef5cc4d6b9ec968b3761882c94ebdc880e888c8e85014662d0bd2691ba04abc7abcfec4c20a5d0283aa818a7db08ade0cfb44b04438723951f88fa241e0106fba315a4099833e2e01f9c0d1faaa648b58b7b6b2b544726b94b73ed8a51fe794ad903859987a475a388b24706dac1dceb8769588e71a72d0ea3a3ad3d6453327ba613de183ff6c054b406b9222b65afa8c735724a2b76da5e71636eac5bba2c78bcf4fb883fe86950b8264f814ec585d407d6b1bbe042bb9103ae9f82801732ebd800a80c4dce811321d93f31593365cee301fe1e38c56bb47fb3168a83f2c64c210482d048dce327ed4b61e0013de7400a9545b7dc023e2591d955331dfe1ecdcdca8dab45e41a3751f516fabdca0bc38fd17768c871dc2df67ca53e2ac5065bc90adbb6a756b0797fbbd07d914a027ce8fded550a513792ad5baf92e0cfc67e31d483478e02f39afe645e24b64c8ab43acddb93a457283acfa148f195bfad79901d17eae1b8c8ae201815ed30b31106f0f4faee090011b0e94141affe91e70a43535ab1f87b887a53c61d935897bd44a926d450491722601161a5de29bface2d8f4b3de23d35e0ce7ca7281f6355febaefac501a0d40ed9855b43dc4c7ec861474ef8c5a14948a661ce9f675472418996dc8ba9fcb416c427386319b849cfaa1de2408b407a2eb38edcbbff6328916ad62f32a0bfb08a86aa7e7ce87c62ff98a880cd004eb24966bd183af3642a6e281dc1515ff054d6ef2392235e983b905a7cdc55e42dec00541e04bc09e3aebb2c26d6afe5ca9206de8ed3727a021e339f28eea76baff97cf8df4cf42a1f9b6a6ab98944d8b157236b02aff4f2e44e49e755fd06f98c651ce401b4d91ffd328b3e202d0c32b524ea2f75832916e755f96ee4b3c5330b6771c266e115585ac9bdbae73a517d3fb17c88a36fc0d328012fabaae5372a0693bc2351ff863df07b58998ef75da8f7172b0612a07822ee9e2e8f1b7ad24d8695975424eb58ebaeb4fbd41d31c2dfa453ce1de744cd045f176a105cf96586ac9436aa91d652cb0747bcbe5b2d8ce3d4f3166cc9b580c7e4d51b95c5712d4c680889b74e624ddfa8e4963b3a587062dae72045432c65d0bbc1be6d2c522094f85b07cfd0b8300eb82e74e22f5e7c744387282c586891484da1c8a846ad0695ca77892add75b6ecc6b722dde696c3d86bc72fe8f134dd972525cf4022d054621af9797062e20ed39597ca77ed744609d30697a4ddc9e43465802c8c6ae08ca4398fd0da2b7925fd5d82370102e41441531726a1592df5228c097f8d67084f335564d65e5f1c137c84c9cf14c2bf128ddbd625087df3f716cfb2028c74b7f700471d7c006d0c6afc2a3e9db69472acc4ca4102cd6c2fe97a8158d119ba40c0306de61236a28e9e728e5fee5aabcbf7c85d8606c847bba0b3b4c92ea9770a7d12e440e933c520be437a5b014f27514f85a363bbd5da8a1a1af4a85b796091bf8528cc5c5603ce69a967eb4a7e1fe39e6cbc372bf40c1cd0225806be137456d4f01b9b940524cf8e84b13154f9c34fa17f8507257f2d1d1e053f14b0e2732abd07d46865d498454fd977198619a6d1ec6c86c72b2f8ebe123bfec1da05220f98c59e42f0650994b9720aaa27d4ce9adc0311972a2775bcc25900c422feaa2b19c846bc4a754d4fde6cd6c8880192d9dffe4fc69b2dece14cece07bc9190d757fd3acb0e8915c57312c464ae19d9509d355a59166cb4ec5ba78ad4ff6ba70cd1844181fc2d6882a69e3932716d2533236887ef114b809e32602f22a4dc7c5f11bc9f9a2d8ddaf5d1f331229e834810ff411a0c7273d5615627feb048816b29766d1972f3f8c07f96413953ffdaab2c819a96a07251d02597ed3a34a6e9e9372a440e6d9f8a8046a5056469ac2671c3194d39c272e8cfef6321a7125e8df241e01306f6a852203aad475cc582b8f678e32e8e6f04fc3a700da6e512d43ca066a4e1ca08d4a0d369ed8cf9dbe36317b50be7faac31dc14f8f404d7a77ac5c0e8af8f36b8c1dd2eda8a2a52fce69500646a6c3e066ea9ab9afac9ebce08e0bcd99843dbfc104f650d5f8f0e28592b16b96a50db9c72ceeac34f04810583e2a8fe5eed7114c79669eb6d5ea04f1d006a3043c275b8722861028ec069fbdbd0cc14baaa059aa4c1499459ee1a9127c81297f3809b1572b997714d093de1cda21b3a90a7b019fdf1c397b11840a6d7113452babefb21723908f8e019eb44daa16558739f7ca3ea69b1b6d5c08c825c04f43be84a440a72ad62fcd67ec87a22628ff97636dc219985298cd14175ec29ca81a5d2c2ed8b56f1c66a10ac84ddb1827e7a1c0fc9e94a0f946b91791fe5a3f0980d62b1c5900f78384e20d62dbe550dc26c52cedecc9ae573a18ce97af4cfce0869460cb7b372c96e6470fc6797c1fc86053a06a32a411bf588f1cbf1a6560bb20544156424729aca7fbc5f3a0240414e476dc5a7b4aa44abcf5ef6bf0d34761a9f8bd030bf72314e1b84c24b9afdc183508ccf922e60d5b05c7401d264a3d895d4193b5d3027b6c3720244cc2405a98605375f0da01546f82441150713aed3cfa9aedacbbb07260a98ccac339a682db37c923e341e73cf71b443ce3c9f0eeececf6ead0e3a7243071b25da05fd52c9d3b14aee1d90d65b6677af5942136484689423754fa026f91db929189196666b5907744a16cf5b741ddb864b3b9f05f643011a4205cf6c6c728d4dda2640977fc32b688acd75177e25d78163ce9ba71de57cbca6ef5f72955aade808692ec846cf0416f6ea72311eb687a89545bb1ee73c644f174b427253220124404a20b187c644e9686d22a5e2c11920d1af090fb1c9b4c5e146cf3c3df15f5f03f75a4edf52880eeda5504abf8bc84b4e6436fb882edd7d10fd86724afd559e0dc2f18366174875e411af72310f19e05f392955a087587a06e8ca4e697ddaa6b69943f6f47859a8bff7c4caa7b020a36026969b1dd331ee8055ca0b508b9f4ab0dec2eb1907e2ccf3ea24ecee45572b0274e464ec40b01ec2480272e062d8f20b686cf10b4438d1d9f8c72ca56849232156f4302644599b3f670d42b0c3e171f568ec696e1f41790f7aac53683183757f2564efb18f488c6dc75c18cd68093880235807e361662bafa9d5cf8b0a6ee022102fcf6c68b47cac757972468a705c39ef1a9f95a578a1de8996c06d2011c7374313c6967a7dc2d8141e727005491205c60a844274890bd0b613de03ec0f224293f1ce22c9b71efb139c7268e1285d6370633883fd0d43687905f05fafc71ece65a9ef45dcc4476623cb7272f725afe61eb4dea09b6cff97f05ff09526deaa43df5f1c1bb4e836bfe9e11994c3f7d29d4c81b6f232a824a70887b65308f4cb38368cdc9f92df0ddd6dd32ae92a62467abe8f0be77bb5dc50ea5cebd2152ac61291bfe58fc014baf4f10972ca7705a2f5bb5da90b7253fb6de3ede7fc20a230f57934e3ea9cbf013a62ba38516ba748ff8f3368b175a62ea40a379b600c3fa62703d643e2632c92e9d576633ea72d353820a133c164ec4ec105e99ba0623d0a3e79be2fd9798cd0dcd3515a12f08ed61d97fa714f2990741ce2c6147b3e56ed407292cdddb8b555b651b871b96bc738bc4d63e5937468441d38206f4146f0637af04ad643a8a1554de1c97233ba620707bafca09fb9731b3093da0a7a900ee93c781ac075f41db0a8ae675da541b1f70223147f2162e6d82c0656a8a01064e84d9c083be3eeeb8867602f721d79df1643b40197c4a6167a169229a6251ea910a75eaae0f7713e2b7c18646eba525c27153c7d1a618612ead5b3c71cc81741615207b5d07dbc77bbf900d7724de62f87799453855a1d0d9711b04b2f2f9a7e139fa40bd3561a0becfcdb7472f474c6e1f09f1edba3d9af8be7c570a7a0ec3f3d184fdcae45dec2d9941a1f0fb1a69c838656bde9acccfee72d9bf30efd22145519db3dbe0da013062e3ab36dbbb799d1bd29d6b0a9d6253dece80a9ffd3370637c2964977f08440f814ffd7213a93c8a12386554b11d3103f3a8a7ecdab522a89efa746d136464fe1be8593cec0fdd03bc2c1dcf15e47f3a3fb21968842959e889254b19710b9c102e054b723ddf7705ee95f4d34c4b91854cd0390ffc4338d727cad21cf282f116b6d3652232e9ffefd4e6fbd0df556b25dc1da8314bfa079f72fa1a1e4fe93c08b3a2c240f60f843b178fbb23db20fc74ff215d1c0c7710df67a42482321a06e331037a2e5e32b6c0afb4ad313c9b9fe1ab4c9780c529278e6fd5b5b09be74a1f411cfa55f2dec3ea445d78a12708e59e23a029fce9a9d8a8df750f4344cc07a3de6a21727155f25c385f6c2221ab5ef343f24af4adf1f021104e1c74cc47e6f818dc1572f1b7530cb3aa8047c8af37690e79f5b8da5bfff7488c8120dcf26a7085cc6972fd63b813f88f30390ebda5bb1aa8f637cea8c476e6db04aff18a3de65b9e5a72952a47b983f2fb3efe00d6359e44e865f0e87a88e7f711ad85e01c5bac416072b60efdf7bff9fc5d725cdf9075bed315ae9e41a182ba61fd7a3302e4cb29cb570e32c163e722e0f3d88e2032c0f97aff9e7d36dae84f16298828f1f3d57eb972f5c4bbc31dd52a4896318259190c02038d83f7c8dd894fb60467043b9458a1720ea93d7ce0b79ef8c40e9cfd6292066e112c820bf511e532cc33397361172472211817961af5954f5998c30cbf2d707011048db01f29485a52516875f9bcec05bc7c870d356b2349d5747ee85034ac5461c0d6370eab19eb74d69322e94774329659da9aa699671bba231f1851b336f6f0a9550d9fca28c862bb6c1063793472916ce7c47bcb010675fd3821fd8bb261d88b7ec3f86ef7f968930419c556794625ed1160803a94100549cfda1f1ec988a47aaa0f57ba014a141350e0eaba7b726705b85c6c68b24e2aa9e22ae1108516c5da79134076cbebe1c0511a8a144072f191d5dbf7ffd8d13670868e966f71f8e1a2cea882b4a1385de919b01d73a371329c13860dbdd15aa20f33d5568c1e73e97cb9fb6655249ddc292e98886a7272225cf8bfb8b4598f7eda1f36953c059e26dc6913e7874d08fa20895a577c643380e81357a06ead652abe4abefe9b2357bf5324e58c08ca28b561c095e6dd3250ecd03c6520a9855288d5efb3b7b1743239e78fc96a00e2ba4c6e6baf3ac16f7227a21c901ea7653b637df831cedfdcddd03aabc11e67245e274a86f86e3c3d72dc1bad930e164b2893292f03a8be8e0b2e739f426d8d7099e717c26c3ca3272d316e3af2ebf760626b6fd9925c04839d650d9c02b3496fc2b3eb416d3b751072cde6f83c8663ca46658ff4c1f9d4ec5ba89aa524461ba1485a8fb1e1237ac7630a401c6ea2d979413385a6d941336ea710018bea2be5b5a9355bcf57003e080a1fa31bb11509741e5ed029d496745498f52fad8c9fc2d1d1b1f2132192c4aa72b415e6ab24b2c4153ab8619997d8776d163726f332bf8e74ece387841c6c6872af55c77fe121723f14abf59225e5afad977bd1d3cfce883b3771a9410e269c30651444cd64940bb99d51aad37500e43bef0402391612e6d0cb4d61ad40d50f72a722077ec4de623afb15fd2944421648cd54ddf46af7e9da0dee44349e83af72b6429b34bde3eeefa56f462f3bb6a5c3913aa12407a59468d516ae67e586f764fa9e9a4c8e178e23b67e79c0c045de9b8fc1f2258f52440979369374c7e30d723becff05edfed995113b36ea7cae32edebef1ccece486da074cd1f3dc2af2a726d633c46e0a3f0f4d4be78ea0861bedb9227b6638fad4700b6930836a86aef2033e70434b20c610a7b8c6eace8c5b0ea53f65a28bcd709e505ab19bff7325742cacee99a996ab4e200d4492e20bc93d1a3e3fcbd968fb91eba9c02d861660072a5b19d04aa6fcef8b0d0e6c35f693f1a003ee7e4e0ceabe50623b3c09f50847289487190ea8083c9c91e01bd023f463828c3faa7aafdb36e451ca8125b07b472af6485efdd610c5b3783f500c768a835cdd686530970381b81450b533f28ae72955b29543774efce3788a0ed72ed767483ecb8b076e6629a3039b48ed679fc72226b79cbcc30421e92b1006f9b97a05fd845a5d9112f1b704d84c28cee131f5c5fc87f848aa67332b0d54f144c5bec6cfdf33292bcf418d8f946d44514d718723228973427199a2d38bda49680c34f93ccf16e2f35f87c0be0d630d47149221c77fcf37636fabea4066f188d4193cb01213d7a9878924d1358dd217fa8582d72b424ef53db80f7d3bf1aa7918e119bd3092c62fd6bc6029166a972e270853714777a5d26589fbc94f17d30cf279337fc72a3cd53153b5f78fe45bc4de9081772dccfa30c26b83008395e7158e877aa549f6d12063758909a12c0b6fc66a69c72b41189e1d0420383638d84878e34e36be7a2f3ed4ab7e241ecae6a606f30106e98de4c333222d54bac65f4d931d3022d1d86c3064853e1c15923a4db1efc465b97511baa2618b4aa2ae3ae0abe9e74433a9da0206aaa4a9db8bfa34c56a9524e9237d543277a12dc0661554f7e47ad942167c9963b5dad40da36f5ae14ba41723c44c17db9bc80b661a81b2134f5cfa67a6af12a044be315bc9ac4ee842e9572460e7b2e050bd5e216036371238382222aa32321e8b7787f85e61b21804bfb72244c05078110b9694d1f2c975c29e5ae07b62045fa20ee22ffb9fc3f59bfd603f31584dfc3793c5d7ac8611a9d7281d503ffec83afabf8fa05121fe5a8ca174bf71abf3cc291e9983307c41e4a0645a06da991c124558609df0cb6aed8a21c729fc691e8c16d1422bd8f617958682492b56616a5cbe50f4433a56286d4bd7372f41912691917b6d7c0dd3313db82bc2ddfa34e0d919a8c45350af5c1a6c2ea725b95eb759b6d46a47105dffb6014ec01dea6463dd0146d212923afa4e545d27239d0f8ffb828897e8a0ecbb19e61a42d88039e0f9d9d3e457b3ec16ace82111d6fa40697200c99cc7c40c3de9a57963afb0f694ad794bf987f2440dc29edbf72a144bae5710b68ea9ddfd4851c06cf74d4ad02dfa102ec6241928a2f9d8874721b87ee488134fc3fbc5666be6224cf226254a4bba9882b90c26d4c5986297c7244a08f9e84a9b068b3685985bdbdfea2aed92ec4f600649e25b5e23551f81f4b9bd4f9102007302afad32632e62a66e43decd3be9d8e8da634253c84e069ad03937e76e9514d5f5bc114c5694dea02522bb6a53de276a35e71c4d6ef098cc272b860b7e4de922c709ea5f38824ab4107089d0c32377b36a69e0ed5a9f26278473175c3c77406fe1d23a6838f4898737fff08f30886dc6e1014d77f0c09c70772bb4499bfdf914d8e5a3c9ba8846c520adbaa0f924c175db383069c855bfc0557ad73277ce889e9284d89e0a3bf1f0b14f616fbaedea19890f4fd0d821ef68c7263540c85581b0c5c721883347543753b72fbc5e395c86a76b056aa5cc073f05405834f0dcfab852474d2f1cba5cece8f96935389e2ba743a3dc6e94a18fa115d772615e233392703818266f88901933fb4b610d7259dd1c762386245c908f97222f82bca50ea60fd70a6fc534a3b1c1d40b6d4aa7e6c50af80814f0ea1edbd720ed2862c0e30cd0855725170628f0c2e28db0f8e6a7dae2870e79292eb1a3072bb3e5a93ef6400d4ba6dae9b27fa3341532dfc96d2bda4820631c03873eadc1129440d72fbbe1ca879cce06197e5a66244ffd67a06840d7893b35dfaab283f1dd4ba5e5eb5b84280cc7314e8af184400dec81ba5b0a917a19b9323b6a7f56b72e132ea1483770c747bba2ec4918349f722bfd57580146ad4fb3300c11ffd1e442e437e5c123e2def2d92b3a9de0ed806fb057ce2ccf24bfd088080a1505a497212abe0716c547d8e9b0ac6bdc869f2d66ab505b4a53920c8beb5ad18b09b0b72a1d8a69fb6062d1c767a4b43d4922139f0b859f3a5a547b9a34b5b4838e727697aee6a41f54ad586998f635e572ef1fe9c3524d934c1a1c7712185d4caabb614b3a3407ded128cad22e85ba1b6f475fc70c183e8a90bee2818d95933bd058b729d91b0f4976fa616c04e0bea7b3e3cbb3c4e1754adf1189a02040ab43578fb6decb93922b219e15b99f2c372906644d6182c5ea7156b1ff70014b4fde7c7cc708a29832c98ed7212ab40192af6e9cec64806250025500f3250cf3d7130c163721c52cc2b80d5bd0a8a4d93e041814e6e8bfc1c042752faacd31573ec904fd6720beb8352e0038bb092921ed3a8003461b643875866d3f712358251e5aa32b772a8e7e402e3197ad61bcf2a59db7c83352794af6136c700cb873ee0e9ec7571585c92d18fe157617907c172b9f5244181173b9feb26b34b4984337d105e1ed672e94184e32ff5b0cee1b3a63a120a84fa69968c2e725205cfa080c873d7232f648dba56f547b4785622f645fb9673dc8ba8a7710dd38dcf5fbd627c25a7a26172e91b82fa125de0a0bca53433cb876ccc7c09bbb3c2f4a4230585736548fe270a923c13d815f9bc361797a8acaa5fbc23f0368a185eea58e90518bf2d26887e202edf2478cc3ae4a5b4950f8d9d72a0a1cafc4406d7c6a38c1c645f421959ee2f90e2e0b32a762bef4a6bc133e9baa92fbef4c65f092aca8d4dcbc3807a7b1572236a2a16013cc1ebbfab19b41ef21b116e2f27512f9a6c500044ae20afc0234577872e7f911713f19ab0291b715b445daf4b660d6694171e99aca3e6ea54eb2f364bf11a1d8cd47bde59639523a6a49421726420b05b4c280ae79e5137719d08ecc612437d02382891da75dad3da52aebceb43a3fbe3a4052b19a9d36302a757f21e26e8849f4422f88a40def93e01c29504f445ebfcf7bfeb2e18d9db5777723600ccc53bbeea3387564aa96c7f54dadc086a7aa2c539f38c706a28b4a26a72c0043441f034178d4809472395aff7f43998024d9f071f92efc6e61023b412466c000ed9c17de4b7afb27d11891f35da1633de21bc8613d8c741a684e263a85cf8c48d12de1322c3e2b99ce970ed2affdbc0470b229fc4e420c62a6acf40c1395782f8e71e17cb981d25dc5b472f12fa14a189d6d2b15a29deba1aa212f5e172e3da63c4e3f45fb6ae4fde36abe3df1c224fbe09e0f4eb2b7fc5b60b8a257b020c426e05c149be2f1a3397686881062a7a91e71daad068e5d1e6166cedce1f72634be3e14a3ce5d6a41fbe493ccd3a543650c89dbf53c7349c17cf7cee992e727c3bd8591619279e9ceda4244a64c50c764ff838b4da2c7a1c71f7704ca41545eab5e808a1384b1e1885907d206cade64df6d548643a4cf3a867c376597262721d9f8069a60c6562dfdc35c562be7547f8cea1cce0e0e4183867d854c25da67242b78106de04e976d3cab97b5100e6a4d51aa079b3566d9b1c8752bbed42e8724b5a390d86a197b01fefc78cec19cf61527fef377b0842add9cdbccb6ecac84fc7fd8d12307e8ea2224b78453fb526d741c0d2307f2fd84d0ee2f838a8bdeb72b98e7167d4fecc4fd157b3b7a689b71fab7049d6f2ecc17291ae06021182dc6114ccb7ccb312c5befb68c75391d3465545ff17ae4daa4b5350c0a7c6d52d6a722205fbc98159b3766e0db0bf427043b6376226ca434befb232fe037b76855e72451b92af8c40a2e2eee7d6fe9f8d18d6ad27a72b8b8432523fde4fa778b5677209c94118957efe898009909f5bbd33ea2a3fe317b79105059786507e3edea91a5bd96c32f07feffd7ba3261dab6432f35a2630e76579c54cdc508793abbc6e726b2b210e70f1ab0878c24516812d6eb4d87c9ab50fce5c1f07065c2ce0e6cf72b8377925037dbe262a6980200f5e2fd5ae0ce01780b427b9a4b0a5a35f62a972d40783b593b5ea0892f10157a0670b4fb5cd5eccfcf128b225a414e0a7c8c972675b7b86fb0c82c51d113d0edbce0ef09ff81dad270436020288d0c2cb8354723c9d79b976ebd130f0ce094fd0ece090fde27a8a3824068514e676a1988ae072cb14a8118587e9bc4f2a0628eeb2bbc7e34bd5eafc89c4c84f23cb35cc233b72b54cad67917aefff8cb3a04f11ba1751667d8a2500263b05c92a7a246327ef59f0795b0440d83fd7ddfa6af6a8cef2a0a3197ea6fcc76a27caeeb463d6310f7231cb2d1d08f6da51383d924c175c1fbc8d831f94b673b709fb7a9dea0e4fea727fd26e7a91e12110a1583c1b2f06c2ff5441397467a60a32e8654336c856a872066fae2dd61bd3acbd52435a1e7fda435f1f97aefbc8371f56ced17c63144f72b44e20476312e22bd221f40bfe62bc972f750d3ca61a37ba1e05355bc46030725c79ff4512c0ab4f8839dc8ec25ad01436c60aa5a55530fbdd7f5588e33a53531da13ecd9881592bac0bec1af4e34dddb62ed62d23101f32b5ff0d4da8a6f849189be93b2d4de56a20f97b1064028d4aff024d68a2677065e9665b351b8e3e7217015c6ac6337d10665898a84127dddbbfda04ad0ad0337352ec1954bb509b49a60b39d6e411e66d93f01e794efdeecdf72e2a6813ca1b856e8fcd76671a2672c7547f87407376d324861a2d7c8191e9bc56f8e26073e128fcf224bc9a859654a1b43b69c7f2f459eeffb9183089c4f9e28956df8f57e8cedb5eb7a33a190351650b4077ea241e022d6e4a260d968ef6d7ea45edaa0141bb3ee0028165f22446aab5cc846fe0c3cbb914724dbe9c9339704da602687c892be7c7a748ddd833064e3c68a1775c68c55163023570175eddb2503cf45463a6ad7df40d66c60d9c55e7f5cf338c48dcc85c60e79bbd25e71f3edfb1bcb136c0b4d558ccbad175d3721d169aabcb266eade2af2217059513734db6dc3dc654965fc2215da5041f374ec9e9473d61f64cd583dcd406d918b43bec94a955a79fcf36d71af3281f2d36649cfd564c5380c6c4d11bdd39260037eb683bc8e0723eb9a3bd8178ef84cc9d7275b99bf26e441501f31f648710a04a577737493e3bc947a1d0395feab7c80372d8c7800176291425b0c19454f821ea8b0d68ce8df487f5a738d227c137d5561a54bc57e9d4abaca169c582434dddd6178da0557b4d4ef4205fa69f8efb5862725bd68d3caae9acef8e17223b89cd91abbc9b64b846d52d8b012c06a9fc29433b656a3b12bb3fcb79e2e6c28cfc856d7b81ad65c27ce6e7644638dc3265374e2e0d6bacf07abf20d2796e7cf18efe7aba89559aaa52011179cd4c0a7fdcfc3772d4a7ba82709cc354ee5d3d6425da0f6403ff89b66b358a6a644744301552967220c841b7fd56ad1f56b64deb0ad1276390f235e49b62967ff4504e5888269d725e6eaf75b77f5a168d4850e73e191028a94affa39d861633bf379a4e6f3591605e269d6d04afcf35b59ba85aefecc38fa0b5925e5b019a04e68946518c066b46cff15009f729fac9201f08dc12caf851b271f7df32396ad58a1c2a877a398d664ca87a69144248aeb88c1cff9aab0fea75a5b55efeece3a360178babf5f749722b0b1cb71f9e1b615b29b1c6e17f068c0b1a9e0f6c6ceb8a88415d1479e46a7216440260d1f069f58c29a651a10318a418d5922d985a07b4c7253b9445994b14735858663f7214d2eb85d8de4a456a2a46fb094e2cd0ebdcff940fd895407c4e710765ce923871b5ecb6d126b6c59f9f49424d075a70d84f7f57d9f66e75bc723ab79de628cd586c90bfba7497d611d3ff45a85123c7602cf545a3742f2edb729e74cc57f39e613ca6ee7a53bd201c675bf0da4ec1eaf463d321f6d9a03ae772dbb214455d066d40a9a7be22e0fed57f2b452f59cdfea199ae6ae2707130ad0d37ed122075fbc8d7da873f65f87206793c28a0c43adb80fc103a3ae7a86b80720b88dc515b507bb56c52af613397785f01d6ffc8df01cce88c1dbc1031568c72a4ce10abbef4643dd62d1706fcf0e75105fd4a9ba2e0d7e340841afd024ddc7244c089f87df5dcdc737ff665eeff8a0b9b0679bcc72f8cf31638a4ca2200547275f450186c26c54c91982ee98d06a92013c29c3efb7854c1649f82e4dc22df1c673db827b734acbce1351c2fb4fc5d350ee88bca1f6dcd258eaab2524be39f72c8914ad7a73620bf9328990eddd2f96c59b987557b7aef7dbf3a2249cda0747284937543ab0b87ebc0c80a58d13b20bfaf7bac4f5ca8151ee3025a693da3741abb7f6b6ded8931dad0d5969ebcec038d00cd2215b680348c1c19f4cd83ed561bec8d52153a33c689d86346de346d1f46463d18adaf6da2148533dcdba2912a345dc4db23962517973427dafbdda7040ab67cf3bc50ae6f8c8f0803173ca30f014a7cdb346891b60fe712d1383fa542e56df7c0e20e9f78e53168b922e794da72c2b2461f7499cbaecbee31f71ad3a03eb96c6f2f7e1e0d1ff11794c5e115572cd7fc7aa3e2f2ff1da38d24196ead8c49910c35730e6f81dae50b2639c8f45572f46121201163c3e18cfa507790702f1568854f5df1fbed6b85c2e985cd7fdf2733dc62a033771418af0901a626b798f8eff7f64232ed845956c022b361767d0f7228bf509feac2b4a5965e077d7b6f6aeb8bf9078eae771640aa769dfc06327290164f6132b0745e4e52c026a03a09dd51eccc215f029ae1294b5e9117cba672a9ecb9410ccfebc0022f0bd307640f2ebf503bf6b143f7601703b8e14aa2107214aa83b10182fa6a6ab66a1fb327e98d86de0a6be01af913483263dc3d431d727dcb2e19aaa8341161e8bf716c1193030448a1d2debaec6df96648e92a17c05a2da7004ffda916e2470da6ee3837c256636108e4e961d8000818e87fb2917f72543b5bb6168cabf26e50368db48a2bbf2aa1fa6a5c1027519c74fdc97d5cbe6e44f4be73c127bd83919947f6c3cba61429b3b8350ebf9d1507c6619fd9883072797a47dd9f5d9e9a2489947332c19a18c351e0b4c5480c0ad12214d0154c8472e83c606fc41b371f242897eeedfcfeb940eda92fcb97abe6b61e237a017ad4726374aeb813da1ba815b808e19a0af41253249c64d13e685172b8ba5b543db672a17a65e6ed1422f63bc665b8e9e8e6aa0091d05e11fd890262667d3eb500166c861950b62d9b5c76e7c2c838ffdfa222f437c386bbdda5c3c545b78d370f926a3aaaeb5f1e5477ceb3b1cc04a3a78e1d94d0c7c133b4553f499c5a9bf36ebd72fa17dbff83614b77b6409b100ec0dcddc31f4868b04dadc48dee0b3d92851e7243e76a586b0eac9feb30509d4f88279b3acadac6d1e1cd231c0bc78224561d3b40d2585920ae9b621b6f66b972b3664b333aadb140c2f19477758b60304c6a417e031d7e39a303394ef014088fabc3c3a81ab555d797b83b1098edb0b32fef278ee553221d8e1813df1d00c7a77354c95c9ed3569a61bb7538731a7a8395aa6707538223943ee60540920f559f7177c7b557b884e191d2083058cdb6a1751f7294cb0277d7d4b60b8a6b917edac62c285e27e4650cdade738b4db2557ea2941252bd9b43e3cca594c272d2fe58795b1035bd1315db7c38bb89413c9aabd83a54c577a44d2ef9902f07155224cbea2f8e016ced07931f548f638e1896f1a164720db27c2da576af219c66633764c4c130d0b5639f3c3d4a5dcc15b2775e8ad55eaa6b8d58df8432b262d0fa1ee2fcf11b4f082e8c3d528ac9587293564ae3dd7291c0dd665f1b96ce7c62747b17427548089f66492c2a2ef4a8f2322a54a1b23ef0bfddcdd9c39cf237e5716f3b7d3d08694c3ef46b79ba447728d14ca32f8b72bc808c109cd2232a287ef58672e3e53e762db3f84df34ae85211ad663ad0071dbdda1159d72ac3837d01ca4ea1191a6820a69f77d29c45b14d062c9dcc6ab75dd4626593ad0a94de7f45763606a121f6827acdcf16b2e83bc754702d29e78927e1c2bb34f5ccb64447d077c3eb7a03c1dbdec53831a556d964d261b2b39801722330c706c7b8d2a35e904b235bdc8efbb255b35b298dc00ca40e16cd56db4d17fcbe308e74279f2fa145b411227fcd2e84969ce85867e0fe00c537e1f4905172c65176113babf0253415a177693a88d6cdf5bc5794ba7cc65af827b319a7d4723b0143380508e35c543164ac335bd093e43970b176de6bfc14fa8e29fb709e5e1d83b41ae43336fd0d6b31a0b99178053c38fecf126fdc718d0daa002a8d072d330639352654e294aed5544c2a8b45d0c8101f05c7aac9758041b9e2203265723d371b073d3fb281a330f6b6eed1d8a0891cc16fef6d01be6704802b0b38aa7273b3207441d8d579426c51610fa9622f0b64d387a10dc07c6b692d5242a13d71053f34298882a054e5e1c3b1b43cea0d4f8610fb7086526980ccc6551bd14f728f53739666d7321c63bb6b5474cd788b7e4c26628d48c899c912fd8569c85269802b37cc800b5ae63b5c50b0928f95ae77492d9ed0943775b7842794538a23561006b4943967fee95d02516e76d311c404293e033f28e5497d558a35745f1b723a79dcf8bc5b3374f334553942d09b51d7e7f3aa823a34230a1b3e25ac44b272039bc1f8f7bcb7c1c4a3a75f065529ee772656340321a89da1a8cef926d6f93b0b3955c6d416736051bd62205869704584d6b8ed3191dd308811b5fc08bf4e72a4d2693c098d01a9d9688a1dc9de96eebe2e63cbb397d3000bcfb568dbed01729da7515e37bf93c2a4211a7175d11884f65bb80eee92f69f3382a87880ccd356263bed6553f52f93fc21286296f5b3f61ea44878655b8b2ecd85510622cf1672225a78fe0999a1d93d14c8ab7993fa7e642d4f8ae432b27dd313b8306fa1ed3b45253e769acebb540b4eb39cf617fa45eab045f0e41012254e85af85bf92ca59b8040ca47f05759d0cc45a79b45c86ba39d9abaad880220ca429875711b4d372f9467d790e31ed4242198591872e04822f535d4df98d57ce050e43bec47fc1727a789caaffdaea3f4b45efe2aa2d3b8bba752d0c525a08f76b7bd373a9f9a9112e117447e2602bdbddadaf8a8bdddaf601f19e8d8349b6634b15f4e3963e4205b5e1f93db2be7004148088ed9931023e7a51aefe9186e60ba3022e8ba1ae5f72b23cf19c7cbda09a511424745e2bbfb9271a0b453f7d846d5db63c11c80063409330eeaa1bb0094dfa4f8a802b8a01d049dda855f7d92caab21e4911ff7b50724cc4b840e357a6d5691b92e642a8bedca02142b59f3b6ce2e1071f350908af3ff479d56ba82d3feeb187f401c7345554e9ee8296c4630ac6fef9063411e5a872e723652d5fd4d6787736856a19e5842ecdcbc56aed60bef67e2efd1f149be4727b887bb45c3cd79d320b2f79e6d3e86a0d7cf9c1e377805f219d4b1bafa4e43674e8aeda8cbb22cd92875b391c5578043b90e34ba85bd864fec107d6b0f69672cb037c324fe6238194ec79ae8ecd60252946b07c54ff9273e288114479b6c97227cac28456cd8a0b298cc5a481174bd6e905218eb353cfe6ccc0426ca8200172f5c29e3be2ddd9cebfd49f59dd4a0ecdaf0ffc36fc557ab66774e8776adc9d0ff6f50da7cece28004c4c75eacc25fdfc6b3bc3f0bde9d40ff2e39e752f7b0e72da1539501126497fd153a3fa28de301280ca5167f6b510e0e80009cbda5ae6721a315cfa8816cdfc5a94a422260272848533303565bbe53047e3834b2d2dbd38489c80ae6acc966d24fa1f25deb32b225fdc80fc7015e5c6898ed660ff36a272873b106159f279c5125b28ceaa013809ea159173d669f1247d6bad9a9c92f62a3ff3a7b2ff7d67b0946ed280db3ac7527e193dc8a51e01951a088eb3997a962e5c1b7ba9919faaa2ea473ce174ff03c2340f5396ffffe775ca540dc91806c56f2ade1b43959daa613243c5eeb0f346ad11ff48f32e551b711aa6fe35e3807548a0ebc02a1ea6655f020d3649edbb0d65eb2e2562aba67cc4516dd328fd1c731ed04fa0e38ebcbeff4301d39410db4eba67db2aaac6a5466ad4e0895372c5013819bfb3d3b6bf29307f7dd69fe7ee63233280961f1f63fb1d290cea520fbd5172647cfd702dfb53c09dd254532484c7a619ef4ca3997a2b9108711c5381731958aea80a2dd6039d3958a317d96cd746753058be922726874b492cb67d7c2f2c723f5fe94da20181f0f31935bef7754fe2db2b828ce20c2055dce6c545737b2a52791baee7e7fa27b2327fd2eba7ba347727203d1f23b8e9dc720b5bcb2436ff1756e79e0af91098a1cc62c5f44e886b4c07c9d81e31436ca3b144c5db8313007227cec3e2b125581b83fac736fe61798e9bdba84edfcae54a112335a1aef6cd7278207daf0934f5ca17682a4a432eb676df15690a029dad7b2bb496c39a4595726d618fa9878e6cb897c9fc953bf2304e28e1b4a3bc3d613b3a790c96d9901766431b3e35668160661e8df73cad21d3425ce56ca4dae8cd7f900a4bf28f1dca720c16b66b2ef07ce0f561f7962b320b3fb2d5f3de9e43713684ed130f79045172831482f8af3ea6bb8fbd2816b2c81021b70327bb6187c3d835747e6499212372a3e22c2055126d097c0b1bc9b36c028f2598827165751c3595cff7a65831af72639b7a157c2e10565235901b9c81e4d07706190b3667f6597d5f14b961ef2d726575d4a2579ec33113f2336957a566500178a96c314007e74414dc40e7e77e72383f57cf515a1a8cf62f1edf5af9fdfe877d9882509caff1fbbc0d80bc7f5e429d4fd47d962ccecf2d706fc608c798ee4384191685f7ece1b37d82102bc52372ff42b7ab97cefeeb4ff000cb41da9cf9061a14f4c8be72fb38fc34595c642b729b901aea9dd2bc01283624178ae583f629ec2ba44c45859c41b876811732b0726166d2e8ef088fda6a3fec30f45783344d1549d0a688b5e55fceb7c1dd571772fc36b1a3798f603c632b20952936f0b4b9c4efd76fe5225a236dad399beefb72bc5943cafe4f1a29e8b2b8b04077f7b5aec2b9e91388316ef02ad49e0a0bed3478fd03517592c3eabdef4e8727af70896a9a62244375a136b1c5db2d8547aa72da55865f0b1ed1b1e76e862788be3dcc88275d38543c3474668295eca854f372e189c6c3faf0ccd2f3083a64c592035d2cb8cdbb2849351eab089c11d7726372b8a3d7b182ea0ffcb5c55a2fcb3bd8ce1a5d6686ff15cace336b94f0a95dae72e54c785b40ac47d57750a5cbad0be77e390c05f0ef3313e23c150a0b78f4f47238326600e9f46fb31c097f73e73a972a300b6514fc37f87e14ce0b060dc3a77271c1d18eb67972ce02bca9c55588da0ee5d3145d872da2316f8e407105658a722fdeffa0e9d9bf5e2cb2b8165459b216087ecd507a4a093e76a1afde914d1972088340c5484361f7372a081377e333303db8b3661d888427361f26e588e4486c39e14c464582c96334314db7a0baaef24105a21eed08a4d80374aa5da40dd1093c6e27ce59f5a3d434f6f0d3ca9824b2d169dd34d64314ceafc6d3d124179e1482aea48d7c0e8ba1549f69b1a79b985438b1288b41f3792f2274d5e4675544421ca3ece37880cd4c628e7ae2cb03a176df4b559a9540f64916752a11261c9a72867613dc18ed6646128439b323ab3def1861886f17f80d34edf0320de609b172b5330769c9dab40e4327495f44b4f6415035d56884d4550be05db948b220f128f16942a4461bd632af6e22c6d5e11e43e24bc5da676b3f40ea490ddbd52e3972620201e27206466ce6cd0631375e32bfb8be71895b856867d953ed4605cdb81c5214c784fdb973f1453055044c41bcebe9da5b9b14ee89a5de12df448dfa38728676abb2910dc7e80ff5a8fd3c6c88e268a3032ea6a290574845d5090c71c4514afbfb8718b91a96689aa4e13c51a50dc6421275d7b90ebdc167250f8e159c4ec39f87083d257065a388ea9a9b9edaff15808e63715977b59bd816de7b674c4d7b020e014cb723e01a01cbedb6f193f4ed0e46f8397872aec91c22f85bb12572c4d2da4c51f1dc4b4bb5caf3725386046aaede611c5a50aa1fe57e88891f807249cc2b92d48e47c0cbb08a279eca7012781466a920965dae22a3c0aa1ee1706db97bcb570df53892337551ad15f75582cbb6f1478a27fddca503021eee19cc674d6b50f5d6593bf74d016221103f7a53596cc918212f96ab6ac2a5ace0431070d629b19300baf7b206c3403f3614580ae63410f7d38270a250a1bce40633f7568bc1bd1222ab3d451659c31a82fddd233700d5c69adeabd50588ddade4eb027252b82f4c632c6be79533e65a12b3173d7370e37dec37b2056cce1e6a8f81296659e88352b0b691f01ccc3a3a721053f2c942873b289afb8186e4f8da123de95694466c5b555abf56289ef6a335ce16df094eb5c9ba6d6446f1598e9f8413b9727076e45a049d97e9da16b79ebceaf5221cb9a266c2721ec6b4a3b0a15de0fe2252dea82a99b06c8b5bcace1850eae2b76f7cff58d1d3069f51ddaec581c3c77248ccabeb6aa3ee1b223243503a50f95134d848b9600ba079820adb6f4553aa16a8b471f7ceb00f1b719a30e13a6ca6ae257f872698951c12c508cc0506bbb4722bd9b7d25ccc8484a6f9b193452e317459469e099a13f884dbafe4f5e68b790e32e2ca2396bda748bbf4b2db2484df0e58f91128c502225d57d81631e490a9726e62e60e1e9c21697eee62b81d1ae3a603e5f14365e0e8a73a38b98206cd9a41089744e539c281f9b0e3f0ae240854cb295671562d2d9e81d65c71dc7a4cbf723eba0e170aeb133a24665a7521b425f53e3b3dc45e782a041dd5a25334897904db0c7244543081268b92922197ec29bd3a3fa14a409dc297bebea0db11aabb7292a69ad1c849c04d24882861cd31abaa9e3610601826a1852c4ec9e50d92ff72f88eb7d6369299cdceb168399c3d55a28060c543e4c67e6e5de1ce4c69810f72d3ceef173172cbd4bdd9219b1744a060be2fbfceefc7f51368203663fb49bc72e464aae4b9bc1e80d5886ccd3e1a4b0caee8654337f1c0fa3dc34f152df60672e33d5118d43af8d4a55f87de67b9c36ef63b8f4844fa10ba6d220af195c60f7230e3e40a0cd37286a2aedebbea3daf1735c5ef88a2bed4ac19ef45348109b84c5109c36e12f79675bba7b9f735b3d745c8aa5b75dffd737103a258b55771492901edfa083943c93d5b55e4279f48957773f32af87ab7e10eae1eaed8568bc572762c47efe5ebd7a45f810bc26b9298d34b3ed48784d2f8df704ba077e824b168893c00011c574441cba689addc2dbb9a4a36d560eb24d5c56619b8f9f8c7c03bd86747efe42a0171945b07f5378d5c85fc1feee31847780c446514574194f57294cdded6ba47c105d642b3d82dac0155a4c13dedb6dbf2ecb132d3f9948b7d72056d23091c8028d7dd6445e8805ef29368ecc7a3f3dff9eca51aeb3488aeed72a53f871bcfa310f65751abc6b3b0eed91bc227c9852ee9789dd3dac9c9f25b72c88dc5fe9d9890a1c3a566a7a206b3fd9c12c2c973360c69b519334e29be9a72a8d813b74a951fe88fff66abbd4d9bbe2270364dd723dc0d2e2fef10d770f6724a03495dd34331428008386daa901c8068766052484033a548edf64b7f52f021817eda21dd4d43da6a1a89db1ae3c11160aaef9be44ffa5cb476b823795de23a1b250166f18cc391d00c7b5ec0cdbad874d22fc4a8b134475ca19c7b06c94472d68eee34c86010dd1e6b98dd5ebed11193a03fca99d7d292986d81f3aa1fad729707dac4c760fb16afaa056e852d1ca253f1cd7ca8d4e0e76472334229450f358e3f530c30fee17703acf6818c3706d85434e38d32bffff9fd8add75fe0bbf72bff57cc71802cf0941cec5e3ea4250ed4edabd6d11687fe3cf62c6f44123a572526cc2fb76f6bef99e0cc106b4a511cab54afd55bac8daba9ad3670363ef9c6893f3ed8b6435d1cebfe6b955e496727619cb9be49de70070f4521443f19996573aa039276766c2498a1a46fe10efbd2e3b613e619a04c404da60ee638293ed723ba913e93c39d4b7a231ae9938e8849d1ecf9b744a066e565079b19be1fbb77263fbdb26e0a536fbf134884fc61dd52d59738faeb5ff2c7861b4d6c9b150025cb8ded3c9ac2e2d09facbf81919dba380b0f0643a6a50e54dc95e71946265cd0a728072ce304bd6801dfc49f393ea666ab801e775b2626caa3505da81cc80543c3ac9b8387637d7a943382a04ca8f05d2ba9257a111489013d5eb1a54e4272e72bb09540f7d6687d8119506c02c7c694f21ac56cda1f015f3054faeeeb693fc30933667b0234f162ec9dcfbaf795de12a382f36cc04d16a3e85ec0af7d0bdee320b355282c6b7411a4063f83ffbfe26576b1c450cd3e4cf43e1f36f1a2675de70ec458d36576dafae7a235f0d624b2cb8078457deaa78ae80ec0a34fde855a73487ff0b5cfd3245b0ffbaa635dfe74b1417d987f87dbfd7f7cee5860006f83c3739b194ca4c63afa48ea66fd12eaf5da4e74867d0341a6b761f126f769e327f72a62f1b3ff2ac8db5059f2fdb46e8424451a8dddca9ea76e3e10977dd0e454c0c21ffb28b94467eb8984b6f4692c50e165b94a3cbe54e60e4e6bc43240993074a9b40d53accd2eb5a3d0ccf4768fd93d137915a55b0145661f3ec031008e5b357a893aeec9915faf90ad07d1b5ba6575525a38fcc82dddd00fd6ddbe84a000336221edbf670dd03ef98548e123656e6d056d7114a084954ddc52e63d259c04d40ece21850a81b0e66d8c7caf649c4db5494d73cd5720d000ffe31303211617272db4dcea8f7b87b413c184c2b7d7434de27abea848f3ddc938ffc55823a384b5dca862aaaa0f352ece49f074eae891f179077d4684017be3d1a2bce480a3399725958233af29ad3d84c100dc466bf4b581dcc13aacdea4ab5e2968af9fc745a72b7ddc81c8688d47f836d9e53e29e17dd81ae2ad69738d5f2842ebcee94fe335a680e029f59a52ef4e251dee472fe4ea2ef778313c37a914b3f87880c10d0a6721d96367ef6b12fcba1e657ed3b652ad70ec5914985b711255b7436f5cad8f07250cbc4fdb89b0d3f02c170f3c71d3d5d47a6ea310cbccc461902692728087f72ce179ba561a3c4fbf3b5bac52155234eff483dbbd484ce835a5181be629b4b72b167972515f01ff862761fbd0cd9358afb68675587bbb68d00b1717a7c79507277a427cbe3d588b4108a1b18832ce0e21ea50dcbb9711f315ba9edfc6d7cda7248b393ba7738e0b4c5cae51e748f94c35daad50db7777cc56aa5f3478bf80b720bc0abf262651c0b6ffe1d24a8f2bb21a152ad6159c9b1febd7e8cbd1704e67230f19adaa75575af4bee93e8b5d37937539a02e9315e642b9048cf60032e930497a622d644d32059e68fba5cab62fefc96489817a05bb713284bf5ed7504a36cc1cc8c9eeb1ee0414740e29c099b72871c5c9acc6ced235fa5da8c59ef94a17279c8c12c54ec357e680ab73ebd2089c594db049d6e00f55c47cc5ff936dd2872aa1a12d28a793588adfbb0b64c33b4b1880a8a5540c4e0595589b619b62f53728e5847f0b717072e541f73248e77241b742abe74dc4212a47b7736ce722b2e0c29b94bfccdbe30c1e391367ba0d55b7e426de2bcf9c3e0e8b8a3183bfd1f58600faecfd05a0950a6a8375bc3fb48b9bb6f0c5c693645fdfacc35168eec6be672ad5d00ff6b1bc08845b53e27291067a0057d6131c1a2f89dc6c5824f70ad7f43489f217d0629321e8838a3e784047ee83ce5c25b982479935e2b0cf8ad3f72725e0ec6b39866c1f9618c24f89765e25ab8917ac3e3b0e777507275bc572e7d720f1c8b656882ecb3295a0c22ebb0293ba5353f30dadd303188f25734d2987672f8b28f06543eb9d255de64e7a386cb41c72e378612744b88fefb66873d2c4d13cca7a6ac8f9ecb79b1f127a48cc36374ca7269c47bb26c8b16fd6d11f61c07496d8ca9eb792b23882a6b6a3502e2470fb8aa8941b0dc0120fa295f1d72ba1172b9ef9ccb090b5253f2c6a0e263317170321e24529c84807320a0dc0c835aae5e7893eb0baea08cb187cdbb88cfbb698ab2f7a451652691c6b94be401ca1479324302e7764fa1fa05fa956b5e4ea6df9bf4bf9ae6b1665417407c029dfb51ef72c40150e4007bda022bf860297e1e6cc9260280fc931308ce70966b05effeb9720140f70e44ed6fa2130d4417c7117f1c1c2887d9fcf9672b6f53cf248da51f72431d93bcf5ce735e43828d7a4bcea7e5a0a8f09f2a140dddcf9a3068f2f94e7226eeff75f153aec4e9ae16f148ba320287d7a0c9cfd9ad477371227f5cc9cc42413721befe5bc7ff77ed84dfba2572d4ce692615c0fab3bf90f2719aef14d572fa8f39697257b6662696b9eab84bb4e949aabb2b40b98bcd7a067f0e461c2c720955d9f16fabc7a620c4f9a405e516faebc3da0eb17f52b072c94c9fe90d01726972fe68d64e5ff253eff98144ea133bcdaf477e10f87dc4cba9daba504d3d72f7a3ffa452afb36faf43da64861b7b6936822728b7f944299cab3b315470907203bc3dc17cbc654b4ee73bdaed9388dca2d7f76f4dfe77fe55cbb68cd6058054064dd6a57b2a0ad8f97b96e088165af8f8a4635bd423016e26c266a727822f72adfb72b0be99ea082a52786c2dbad8d36095ec4f37f1d02c983294e17fda0e0c6347e32f7425c68f2d63a693c32160d15307d05dc8abe28af75701116c517b7299e295501962bd9830e26677db9a6037165cec81087c09ee090b4fd5467b9b72d21c260b041923a6a1ec2c7e7677171eab6d6696e628bb2bed4f5890f3b1e972c392f8bc19b3777f87f52f8e64cec8396e86f803442e125982d70c41d01aea7230f5f2af1e38658c4fea4f9dcf73fe417ade7ed32af4d94fcc5fd2a589a7d24836496043b1d63b0713a8e46bb12edcc47315fdd6f3cf9d9fdba5f7d7db03c2723866fa451fb10bd9d1f8fbb9791b5bcf01f39d4f8fe036e310aff601ed90a9720a30f6f98a3f7a2005fc6f68aacdd55e12d64488ee13827e5b75f342375c03726f17152540fc62ce6971153d6c256701b341d9c47bfd77c6fccd28b1ef66687251c38586714c6253aa062ab2552bf35655454f266a77b9afaa070a3d747e87727a0d090f52057cb93309e0334e5bcac782ff02db7b9b0364b10da9f4bd875b626132dc01559d8bedcc98510efa5e3e5a6b2d342018a320e0cb6bca1703ad9f72099a396678a04e54f61f2b66980b07b851d9c9b3daa461f50218cc090bd80210ada6d03d91fb5decf944b9491ad6005655d628dff6cbf51a9c4770f38c74ad726d222a0b7f12a37a33fac1b6d96cb70d97c32058d6a67555697a86127916fd1aa9f1e7534263ba9b9131184208b74af4906abb390c9bf03348880f334d9cdf722740f20014510aad1438c5d1234a436304495dc212f6390c075eeb0a24a8753d2197d51004409cd686848449853b3a6c9782731769b3142910e368d94e2a627238680f87da8ab9541f85a8d8b82d73e289829a9c80af0b19758ac0283344a508e8782fbaf990b34c90ffbc9191803f6265f37745f289515a25c7bdb09bf6424f3a9f40269a0849c410b62fb1ac9a91a3e7fcc9e63994b72a2df6adfd43c9e24660aa67f1836a0eeb06980412583550410106b83853bf035368dce0dffc1683721c1740f96543e345e2879f01b7d41db3783db1a3779a2179a6adfd624373ad7278dab0fb9dcfd86f9ccdea77f9e39706270bb47d92b9acc1378476f1adb22a726b3f8dcb007333f89aeb415d4c8b94a36123b3ae7902802b527698b292fd0f6d7a88b5cbbfeac51d6379ceb35bbcafaaf6243b7f2286ed1ad10bb0e4c75822072cc27d685d932141a76ab33073d9b77ae5fc7bba810a35e7c8e9dc384d0c907281f6720fd16b0b3990fab155836c4f47acffcb72ffe4699cf235a27e1552bb722b28adbbd9617290a616977b5808ee80e7ef47b9a1c856b9716eb9bf6d2b4556b48165568673bbe8ad0ff000f5a2dc4c05704ed53ff587c799a333d115073c4639e35c9a32670e182eca620fbff01c96fe49955a7ae60deb59db211b99fd633a32bcad7371ec023fc20135c8060a93814a123a51f61dab61442b362324d24c72f1dfa7d5cb8512a7e2aa821352bc38ce80c1af8d7b07ddc8d2530dd1f9353272dd0bf23121da0065ed80b678a26e62241167ccb8948df8da3beb1588c9110615791b9daa77db1256c0e195bf997a1ffbb4575fae835f29933c3696a15fa51a3d38a51ac53a2aa3a43e56474a91f6f7fa04cdc32c2e9e423d8189a7c680becb7259efc2033ec1e4b868bfed708982d23e8f09c0df317acaf6bfba30245c87e27291c28d5a5e603acd2643ecc70b185dc5705088cb6b344a6f1a34c96a176c847210ee927e8324ffae38dbf752ce91c77b6ea72e588439e63a2f8f52a8c26601379482494e58aae967e461f9ae030ced2ef72675b481e3d893d00f62f33b86944a38826aaae2eefaee00ec980abc9ac21d7d64ca423a300d3e88b9b88cf0cfd172b2a12391e133909ddb94204e2a535cc025fabfdd375e76099e68a82505a24b7259bf34cd2a67833318f2692a5a278e1e56abcae5aba4ec86d0ea95e72b0da349d59e39810056ab459faa4eaa378461c79bd2cd9cd69541e715550f1c57d7e872c102abc1c60188c322f9474a9460d4526e29bdce3302d5ab61ce524de21bda6d44190253e2d8bd9534ab0284a74413989a001d2d86f74521ee275ebad9692e72bacb2b3fd0b48985b4caafdfad0b24885829a6a8a24e259c74a7d3b2b6cdab2ca6af9a5d0965a70b170813eb0264e7746f053e2c516a8cb2957ff9f3a49b824aa499e0d88355cd5fd81c4469f850478f5d91dc8a1bb1f2aec9ffd3754463a5725c30dd0156fca5d6810a5e4a2edc2a888ba6ce817a0c8602ec0863336fc6d472ae7172839835b379afeaab6bb4a33e69a25bb3e84e5591d80978f0795dfc0072e940c3b63f833e702d1d8b7545019919e7b1cddc83564c14adc9b79c9954ef467540ebf6c42a4e8d909c1545e5df162daa96ca1f12465bffc01eb6415ae45d6726b33eb29f1482a4f84bfae68f29261e2e1d65f8597ba1d17211d8e0cdb4a9729bd3255240dcd2d59ef8945f72c843d6802a06533c3825903a6c4b7c3f205840760ba451ace190d3d676299c96d4b17bc8bfc29c4c695e09190399f75f3f595bf3c21c5b94dd6d85717100bea64b5907fc9ab31f54f165b068d5aa0407c7f82d386f82cffcebc29ba46a5f1585bb4433a147c7e39d45552d793b07baf558b2124194c12c592b48145ade41a811c3e53ea18287e306c524c82173d82335dc1a7244986f1b2eeca31a7c65fc43206e07ff882a9096e5214b677fb188b02bf83b727521d09a27add96a580252764c753b5b228e29e269ac244bf910e54854df8c7289c6700ce40b5ac7a2376f959dfc15c6e1a1490eefde0587562098478341d03da7b28e697adf01c270449f01bab5c24876fe0f80b486a5a67bda595d0cd4f272d0c3f483aeeae702f71f04beb96b4cb1a7229d8f3c48f3590e49756d8d6150554f6f62f0e9f19726a2cdf34363b07a2e0387bd1e65a8db29845f493bb31dfc42273598c104c02915f4a4c3e5d731781c9c15997385d64c2e4f5d77f5c840ef729cdf28093eeb42223fe24ee7128eb755a8eb0e7b9fcd230466e0836067c13b42e30a98bc0c3a7f3eb9def15b3ce1ee80c688cbc86bc00dedf2947e06c4bc794b2acb64e7eb1969fe49da39d0fc25b86629276b0b96ccf2006bf4808df7191172a07d2396fed51b0b0a5e4a2000d2756948eebe92942dc08d34282d6426f65272946de9040846c9aa6438e5606b3b22320643ce8c98ae2d600df757823697c7724bbc69515bc025446edebd6ca59cae1d9f0f33e4328090060373ff9f6e845872c622463cada832911f83aade9bd9a204c281b12508beac2c6cc72db812a54a452ebbd125bf5b69b3bf0954cf63e118f90f55af7b735622f29b0674f326bab5720553278162d26c18f6077fd9353ffa740c92182db56da96e6211a1999a033e51f780acd588d8f6dd6ecff48582129daed607d7ebc27a1af522c778aae8ab6d6b43737534bc413f0c01a419b376710988542c018fd30a8ad9434c4ada7cb4eb7205d78bd111c7272971d3d8cced26ae4b94b2442da3b452f40154d3d22f592736be6cd5cceb640e9b1103e89075272f86edfc7a608e7c3716d8f4de57cd3a2d658f395f18956552d6fa097a1796bc81037be1b098b22b4e40595b78c35e072b7214ec149e1643bbc5815deffc95a2bc1595ac470c74a2863435a99fd5d17498727ed41c8316a0d27355cf673fb33a27272d68cb335502a672f4471c35fac445728611e36b4217c7273dde908699f113b3ef501a4b0a0a688f0b2c4495f4300e54e4bff27840cc6268f1b55771035b0c41466ca7c261b17210b7d0328f91674372d74dc9391a1e256c6a89ba8619e5555d73f29a94942922967b581d2b21e3de727e8f49767688eaeeed5e804d4a3ba1e08e362e2afd56b80541deb14813e0047240e705b2680c93f5d7a6bde3c8cb7861cd4aadf542e1e2b73b290e2bac300d33d6f9931dd3cd9194694bd833f84226025153f58857a04160d446d0a5eb837b5a4c0a08b60df6be80e0f6fcb105937b3c1833f43bb6ba3fb231622197bceb3067a50ea386ca1513176a4816fbddf379f180b1a696d450afc2306a1bc87f0324720fe94cfdd30e3f30afa5ab4af041d59d815c4a2dec1190f394640837a807ad08d797db641678aa3a9c7bf4e6ce82171496d3be0f024c09df4f0ca75f45c0191f4375cbd5bfbbc6a5fb06f73f3b479bace9f49af31af2f3b1e00a0dfc14b34b49cdd270bb4928f508d832c30c2ea2881c86dc8e3c64ee67191f1837125dafeb35dd7fa7531b65f9b092f4ab12471d6ac18d6a84d498d77a3ee9091f1e2c8240155d729bc2a730db5376f31da42633d45aab78ce3698c30123ed169df6f6a75c066e4e59dfc39d0d436c4fde630b618ab1a921d94a98b1d801c7d31649faa0b04747025adc9c6ba375da18d829e13a10838be720c704cace7830ec3e24073a52725cb490d02e677cf4bf5cbc86a7c98782328178fc895e607b54566f05b05e127099325d0fc196600969727305787c5a8fac1818445664cbb703bf608b9c4faf3f13c60fea949b50bd75b5269b35b54d01bca441fd6d4b4cd6817f82d085a2c82e1eb5dda46c8f6307ca2322a95048ebd772648556e4d5cb0ea470cac7a166a17257d47644e6a7a62cd0efa4af1d468a9db811650e039f34797d72f544ad5a991efe52cd48360ae5b5d79de02f5695c71be02188617176b32520ff4993a257df720fb6f788fabfaa14ab2ecd52f66983693a3473a837c905d8b3979598ce5658724cc160b058361e764ca6451478c17711d4931475a33efd74da26bea286c4fa72939487b2f6048c1659a804135cbee941de793047d1c9e2da612d06a55309297279fc4d8e5c65312f4f35a6218a5328150a76e34150c146298bda16ce194e9d724dbc3f823755de673538c59349636f784389388743305961d00c24b050785d72eba89f32d8209e425721041349d85125981279ab2b2f0ea984108c22fb51f17288d4bb9f5adf5e94009f5cea944e85278be5f085327ec1139973f024198d4742889cb306bb3e8eb9a2b6bb41fd9467f088108ca5e36bfc8e1bc8d4579ba59463b58b8126e4543013e09395b0b8c63a95648089df7fc722c126c7373bea6fb772b9233711f97d432232b52521f8fc341db0a3cb49996f38c4a78cc554b271812b8207b68aadd82d3270e47d9675719199207e956f06a0df99cb3de354bfcee772feb29033cd2fe4e47b1d1bf9a744be2ff29e8eb05a71daea9ada80779588b32759f81e0e5b9cbf872274676c9754af5c5e6bb8db811add527c25d94d63885672aab04ec7fcec4884a5fb1444731da3fd3281efc73c529196c5a336787d4f781dd85303a5c3e45e747a3cdcbc15575dc70c8345d5c017668e893500e498b12872e15836d4afe9b5ba6640b48f26ab9945c3e7be88768d7b4b68c38421a12cfd724fa6ce29d25c244a76e03c40de7045e35ab0b3f38708097dd27adf74ed61f232f3b162ca7a5f3aa6882133496a44550a2d9324d25906ed97fd2c3a1876e779517cdfd6e301b06baeb39c2b5a90e2740ddec51a8b7ab5627fe11a2edc8027aa49a5be7463d78760100d80f8882bb86c7a1c75bf795ff048287a642b8a1832ec72b4cc41ad5ff7fd782149f37f9d3571a9873be0191ba72db3a830e7b1464eb97289033386c88d7a5e883600dc24906e5c099c5dec2d05351bd6701e076f30116840d0dfcf2ca53fbb57943e956d6617544885e3362a9705ebeabc0d4176be1f72b60072141afe5563137b17a8a66842609ffe3f7796b0ee7b08a5ba0a3c01dd36e7b92e3444d14098bd4534c583824323d6eb4bc958acfb8ce4c9a7ef75ef5f723ed40d597ac786155c0d4783a83cc0982f3804617d429950a33ad160bcfe467234b2facee079ecae670d6f286b7a4de01b2f4f0e31845f867935b8e34b680272d82b95d0c280ac9a3bc9cea09d4644be058cea3cd749f0614425ca4aebbd6c726d13cf1d0fa3e4d2ae53107abb0269bb4a3d02208a9fa98235480569f2250272ba76dc0d3666050b102541a0e36ec78ac07a0acff60838a2fe20b71848475a724a0599edd01e5a85655a1b04375929d295dbe5c7a186d237455bb253fb5efc728d5381aea57204a8cec42acd92227b2e34b5c4657ad3a286837dacf04cd1a90e697b66da623ec48989f75117b5b44b69a739cac66948db7bebd57caa50f545729c0d4c9fd7dd30aed4403915081ead88d26ba18aca00c9826f6c6abac37378725e093bc7883ca42eca1c7d643caee57e040021c28b0e195bb3e32d984af7c7727571c2a9dd4ca2a536e0b23c44bc6faa17b69ad196ef149ca23095a7e2e7f272fc68de2906b3013fe89c207c82e598b08a42c1e3ddb85821619547d7b0019f72dea108dde15e84bd8f949567ac69c0394e4a942f5a160ec5a7d8087c7f213f50875b216eb79921ec9a37adef94a2019333e5e815f1811bea94eb9ce87e44e1726f7a61fdca3e3cc512427b90ef60aaa51e85e1f3a05810d8971757675fd2c1728bf1db174b2827462bb41657820747691111d2eeb0b6f2ec08e461c52bf5aa0db6089323a6d6a353108ac97d67c9d60eabb75ce189a55f543e40e5cc950d644c03de762f331943c1fe2b9e6ce53ca9cf15e6890aee27cedc3930ecda2e87144b57d484a08c919b4b1ddf6e6721798cb8b1bfba66ca238d2df9463405457802725f90fbb4dcc3c755a7d6ae937d41f4a8ea17955efe2740c38d93b923d97eec5b268998dd025804d1064e3a068bcbd3f1e69282168c30c7b71c0e6ba52ddefc726117d6d21f112bb387c3e2c81cb3f4fe7b8a4c84e156b9f4b267142fc069977273e24930d02e0a17047f573adba60889623b4d141e4081f8a3a771fc4bcd2527accbc8071cb035791431beb4183b2a8a2e72221dde5674bba795f684b619fb1f32b549e229d7f94955e23003ac34ccf0977ae355f1948b7f42030be1472a6072f21410c1df313d664434e8b3348581c72a37843ded9b85f4c73bb5c7e106d65edf5613024842566170552e958b4eb05de0c427f90bca298a6ab196c6713f2b72e1e688d51b7c01361d74f32c443f8d462d08ba56604b3f2fb7f67b329b77077206b93a06acdaeb4f539447a273f52ab77608d19715c03335cc340cf87843c3187f5e269196b8cd4ab13f4c6423c37822a2d98a99940c6ef3842749ef207d9872900d0f66ae98fd20cbe07614947fd07c0fcdaa7bb570e3dc8ac07b027adc497231fefbd0012c14d60bf59e64d916cd7eac95504b6847d95339464e13c83ce172b355690e19b6df1c4b225c9fe0dffe06a9c6581d22e63e509d0da4ab640663722771d55c42118991b6472b9c8cd4f3c4229dba87402e6f410f95cb3db3d16827835041d5562e4c1ee98da8fac658577cae6e31a4e1577b0f0e795de960c0ef72d38c0ac2610032fa29e97c352fbdb691a9dd1b25607374a2ebeb80b9250e1855d6b4db99fda9868fd7da81e3fe3645993c8ff5006e1a4f16cad7ee8c57aa49726949b9f4d74413dab7dbeac374716f600c619d4437003887a28c2e09a9917b72031b320fc9d7935ddaf6098b46e28f7cae5df696a7f785f6b8fb02f507c54072b52b7a2c306e08a7e6eb262926002ce52fc33ef2f71e9b83252eabbd806fce39b26da83700ffae62560e57f50ede3909ef4b1f64c28f050ad8d12adf6ede1d72a9df3c19d8b3711bb6f7773f6481cb1fe91351f5c7d207a41ad8ba6473dbbd721c4f87b99d12f699bbd3fc058607f348915405794cd2a5024080c71a43de37726a5d71f2e5ff97524880ce71f102428e098156757f5e0ff4c84330ffcaf84a09450872955d0a2591225182e25ff7b772d2ee4aac2317208ca7f9d38c09b6297248f1aafa064962943407ea91c9b07a48a21ec0b71aa2b47c23f2c1f6ba35ef72f261cf9a85139ab903ba07028e5470965906374eba1a62a5eceacb8fe5b21b7219070ac8b3c8529543795dfa28a1efb89ca96984893f5ef254e14c12af1d6b2fa56b0022d40755ec7cd2e79477c10fb9e7be34110c4ba3425bff1efa03fc1f721a48e62c05a7cdee5a285a767125683237c4e7a10751ef3646ad1e3f6842f1729b268fd374f40745342f240d979ac75a03cf75e04e090a9c8df169530a17b31bfc12538ce65fbd5eccd1e99ec0b90412657ef173b19dcf89792f5b11fe87142c19aceb4cbfd772aa0129553edb6b04e88c838e20ddf9e2f014c7758b2b565d72fe56c5b3d41c01980c1a90156adee3c06e99d687330035292110adef6aac8b72a4ebc319ec88e341a0430bea567f0238199e5c1f489ccd049e232ee95a70577251ec18de66b4ef204ba261999241ff6c54f2a17af76b86921e4ac05846c748727f022b331ff573e15e682fd4bd9248479a21f2373082b00b3fb91085659ae87256aeadecf60cb5b8437b792ddd976e40be9c9493f6ee07d8ebcb27daff08812de801ee3780107fbe303a39d33306d20f8d5e14b19c6424c5e8713b0de70ced13ff2904ea7abde229487fba554f963d0bcab7654c3b253bd8a38cc4374ed5ca72c48415df0f7841f743cb502110e7cf12dd89b701c4342d9fdf0b4439ff383932db534882973560d0639082faa0dc1d1768efb4c9937849413cdaa97ad7614c153de68fa965439981a0fbfe2e68a7d279efa71d7b81268574482d2e9eb3bc827292f415d54e40113b56fd2fe6413b1240c5e300b755b1ea5e694363577b48ca5c2a8061b2c0017d2c0d24acea9b61fa2f1689afe3a12d7aaf57aa4c1a472930728bd264d668452ad2c0b23d50b01d2c2f661583e731cd40064cdfa056b4c7a92fb154a366bb85b294ac2049b41e2ed636fa9e167420ac16c0ac9f3dc1ed194f72661dbc0ff03b151b2ab989e110d45f5d6818fd20ef3fc75c0fd4d0436cd3c5727edb689c4593a76e01e3cb650f3f6101c8ff4ef243c968e2d53313ea8f3eca727de1ab11546029cc28b588496eba2a4200b60fa318dff4dd176bae2865150272b52e7f2f729410f8e92bb4ca9e7ef9d9f820eab55c37f8885a18c81554bfc77260eb574b7306474e3a01af1f680bb6ebd8fa89d45ba504b83a40a8994ab24228649568b7948aff4c2ea832abcce73e6fa08283f8ea03291164b6c65f35a4e22488bafd85390145cc4c719f4cb37aeb15278cde01d6f6308c9d3ba72f06c1186252f4fbdf6427a285155cc3c34a30f9d8b901418774e387eca63ffdfb3b921f726b95696d005f211c00928f70924e23d21bd065a477c8d8c3eff1c454b1d0c92d51e725554600bb622c35b94116acaa366b77c3184a0f0f6678aa6bae6a8bc8727131a9b4f1d978573d822bd1afcd89e4d4c02d020171dc34cc96f38f2f0d0043399482d676e11549ed449af4f6f45d7a340cdb803a2b0c3073914f68531e8e721d9d4f2a62b28136b9378ffb82eb09ff3b8a108a9c704102fde5f969140b7f0ca0213f3a0d20ba44d00182b8527fec13371f0e98026420c27fe5c7c4f24a0b72967f35f7484a47abf081323f6e7469b33590af267bb5b3119ef65fb8c98d0d46f6f52659a37513b79c87d7a20d334ced3b61bef82c106aef659fe8593d20c572981ec7a8acb4d890360e5e3bfcf35ea77e1920d484bbe3f1e4b98753b151da7287d53065b8f893e36acc807efb1006a42fc6376a1c067e394346254226c27b727e9b9dc6084aa545e51aaf5b9e2251d42d9cb7bbc3660629a44c08e539e4dd726826776709030e6e02887fd9f06f2dafbbd53946208a600682f3b421e9508c72fff32fbe1c618f1e4b46b038a815a701335a9e1c6bb8af4fa7dc1d807d1f19729b1140abf071dd61f2c577d9d5e866ed65fc82042484e474660a836d152c26722dc013d083c4dcc088b0baf8b938f18bd6fe342b916d9b0e5cb3cbb4e7d00372af1ad1c041e6e71d941a3368af0349138c6d5bb680207693fc2b81d333a76a727296e1fd80aca0f31a2b6f6973b90d8a26369a6e703fb6a76f4c7b02a020c11f8f4e824da1eca8af0a4494028906f4b4bd9ec457442e53b47109e3bc67c48472afe54b847464fe67bce881800dd128c8844b80788cd95bb3ca8eb6407d77fb236ef8781c2e783ec512fee8cff23fa167ea3f57d08f8d138a27be43457335176f8587f43bbe881e52339d519c065949f25cc0db475baf5e2f6e9cced343f1490d475a402f1825de4e2d2bc7f885ccbbb4e0531df09ba6f90655cfa9bbf21ba8725558219b4ce9959753d4e132f74b68d6c4373a8c01966c6d2d39dc4531ecef1902224880c6790ef314c65f72e8fdc59eb82e6806f0d56bcaa02495c277872c72ac96505809c7dde4386e9dae71a4234629ec53adb38e9ff485b02813099ed455b2987f32f3584ba9d54d55bd067805a2cd4aa24db8f833a3154649aa08ae3a72349d7b61bd1868fc572e838c9aa799ca96666447c151988e0dc0c96acfc6bf7295f7e1882010d3345f820866c1ac822cf1c4d2612f6131d32af1a759f7236f726a18d03282c55b436bc1ea1e7bac4d536cc139fde2522dd83aca2fde3533f3721896608efd6e2ac5e9695d89a732c7d3a9f8ee1bfc34c4d5b41cd7cf9cd0b24fac4eb16f9427bbe19fe4eacb782e4f992df3db3782cb4607e60d382b8a7a6914fee79f9987f3c0b19fc553fe27212fb28f4630d2ad1339e478517eaa6934b440b618eb47e3d76b88fa8d81172db45fa218f24d987a6238eb1706b25f32a2c1729d2efe3ac28dc5d57d7715848343740a15d01cd94c52f406adb4de4976ff21595e23b7279a1336ef4bdc34e2cc9202351131806c63d816e3895d846b4488e44529401c7dcbc3b5588bc61a0fe787af4838c46c083ac711cfd9b3c27da14c0f7209f1c20659407c74b3167dcbbde0261e83d4b54b037ddd127083aa58f8ee875572809b6871618537ddcd0e6f997aef9e7178950945140247aab191f12c5a7772b8489fc352feb0281caa499c41f244ec68760d9630f36f13cab02e5752add508baf6410e5838146ad9ccf0ad7e5ce5503467c72de002e1667e0cf3771862593c5c5853b78120077eea8ab29cf1b80718080c350cb33da781704d6ac6ee1fdc7200a0346473ffc7b250e1a894d7d8725718d9ae09d326fb3922226aa512b9f372f9eef3b91a2bf0d064327736ddf87a8f8e7371d3876a889461b7138ad6cf4759d22bdcbf62516bd8f9ad4ecc0f41aa4d19afcab3102705cfa7e2d51498300a4762c00a4892ce63cff766628c69180ec54cd70c135d2f2f62d1e2a14462b226481f63948ea9c9c93760ba54d758d5d2c44e0a44183c38c6037e2652802855b564ba0f3096a2e55b250f8a39a46009ef1343eac76927c1ec403bba4aa3d35cf472383493195a3113a795df60b055f9590df3a340ed126596256ed327aaab287f069a9c4ee6041d0670e4dbeb930e0d6796d3c78c348a66f421611819910f4e30724039ea05bac344a8760fc92a87c61200b2596d973be1490404dffa952a2afb728d00170708474c02e257d09a1e2d0b08902ae0a8baca7dc56d0366944c370d72f98253f03d870df797aadea6ed68a9c3004709914c556bcf95c29241372d8b7257a7a6f882aa8c4c2f4f17db023bf4631850a3aaea6c8a1803ec7b028b6f693972bb634deb44d4d7aaf8bef6f3d373268f83e9a04d14aafe52e39e089167b671583c6a677464c37c69b15bb19ce9b495bac906c17073191e76cf574d9615785fd1819ec87936937a5d75a982d31f25ea119b51451eb7c76ca3582774f6df6f4874897ace00d2042697be40be4f5803377b80293ad9fd3adeff1c20e361aae3728d0e2fd8fbbc69248aeb16fcb663e2ed2e14d39e68caa81df36b0cbb88285a3bc41f3ae00f22bddc6555bde1f5d02534db6f15b327d4b425b8e6314206359c7231b4c7b7ac735bcd447d69bf42b3b66ef63d1951075be5caf3f3d6d791945172aaa064c3938da38f4768d23111b9177de01583df25f454076f9f66d2153c281defed37ada621b4ea4c478852e812ccca9db4895d88d1e0a21ec0c79e08ac943abde08fac2ad8780ac0ae126093db5319c8f9d71512169029aaa8e2c8cbf12372143d52fcd5f98cb0f4fd8e392e7efeaed394666323d6156ce48fc862b133d57256a75d66c3e34bd0d27ad8be4a50545ee18bc5788879de9ad29fa14fca803b6f36a7febed377a4157aada2cd88703325b3e0697b982d137b35ed229f03a0cf7262e198d19b423a2961e84bde1c83591f34f28e405111b166d566d84c128f4a720e0a1354ae80bc73752c877d2c2407d8aee9d6a5e10ce266ac4afb0740dfc972233f0af48556f1d59403b66f7395b23576e94e60b765342b266c447510b5a9723484a577165eeaff5370b0360404e471d27bafed02b1c892d8b2e43ff37f1a57693b63b3d3eb77df4935128567ddff6e2ed15df95d7ffbabfe5b3f734066032f6d26ecbc109b00d819c36390507741dc3c7a1555114751f58c3ff0e88ee5047239325eb01bd658ade49c0a99e4472bba03f450426de25f6ade4d2617beda6072bb0c641659382e8d19d64f084e71beea6c2af82993e9fa5731ac35b72db34370959b1555ed3f2982cd5c185486d6f5d36eba61b273af2f60c9eac273ea1a8572261b35ec884113241e6356d7fb7bac1db48091557eb326e86f4c8e6b53d43d64d6a77aaa77ad15bd494eea6b7e57f2ddfbf17588286731a83d3c86eb551da772bf86a6d2eab977824d5120a9aff9db0e5b48887e2ee4649e474f1fd76bc380728a4944f7872aeac1baa89d41bbee64d898ea86d6be7df1dfd1c2e877a5e408721be3113ef2f775bca55102429a06b045dd6201c4a65dab13c4822b0c0cb1c12c470ca86594b36ac83a4663c0fc69e3f1d8b5f87a3ca25b5305899d36fcc95f72e9e46140c744bf29544d5877cdc2730c1bca5790abc8f92e27472aa12316ce722ef2c3c121257d5c5e86bb03512a0eab04489bef8430e618182ede61d56e83722227305f8b347078b79ee0b9fef0ecd05e3e79259eaeaec875f5d77996fa7520a6eda1ee606ac95005109d864b15ccaaffd2c03a493ecdd87a64d133982d18383cdb7b4ba87ec0e662268976abb5b176b300d58d3f2396aa381cd07db858b7318b7ddd542cb0101d6c82de0d24971b6415d4bfe78c4570c4e33e7e9d906bd9729dcee74fb2911d917cb50c361da9f4389f10a1bb5bdd09949b6515bf7132636758460c5e982c769b741c1d83877e0badf5a5bb3ddaa312093c07fbb83dc3c672e32205af42322d1ad7f0c19096f553754f8b3b27447214651d5364f1282d1c7259881c023beb61d939b5ba588223fdaf6d97cc432ffedf3e9d7defc6b7a65c72225b9cbfe0d0f910d5b4324c3d527f3614ef4d9b8ee6b889c1289068bf877072951e29b9b194c4b16f3ec380f0a97a3989083b4aa241839c1b7d5083e7c56872e79f71790ccff7a034ce87e228c21f0b87a86e8f5fd5282be57a61b1e27d91720e39992d9bbdcbc25414836a7ea87ea2d5fbabdc7f13849a667ed787bfe34b727ba5ec2e1df950b7e1693375448f277173486b1448259ff7c088806ab5b80058298678123693cb1fde8d65f205cdc82200771aa2fb46628c4894e3499ef89b72316202e42c2464cda19e970c0619994b73c3cf361f7539bd4fd09e156fca6b7232d86b4e4a7d7c2eef76cf980415002679a11090ba5e0a2f2a488b6e9cff7c72d1bb105a3b31dfdf791125ee16574a2aa8d6fe3c24e3711e20dfcdd5722b4372deb0d4fe8b6296dcdc95f489a871b99220b8e7fab3840a2647579b76e8da7c7220ebbe13c50cedc26bbe9ed63243b97dc201671c48bbf63edab481721473477240850a89e7867df0f50c69f05fb494d10d03d2e2b5ea97db688e996185f0547225407497d37c4d68a28ca94a1a46272cfbb129c8debd26ceb966f83e547a4e086d33cb57be6eea7c475a45e63efc0c27f9bda4747f4d5387ed99f35180fcd3724c593d060a8242865d8ae1ac4f5b504d70a5a2c7d7bfe5d2fe13a2a2656467728efc5d303fae200813875f40108a3631e447d859ffd388ed4655e4b6d850d672fe10fe161429ec5e2ac0a93ec9ac8b77b3ecb8bfba9aabfa2559f92f178cf8721bce6baca6f88179710a0a0de0d434e64f187a9ccd936c06c0c8c5213c06a15d1acfe55f768059f3e9a4982a0adf8bc58f026d7d36d83f589b1974e6bb34b772d0532ff6f8c4053adb8b7d0b0f948578c0d33da271ceda50343a9a97194ce872fe17a15c96d75ad1d83d0c3de174b8cb3e119f79ddc64d3d6dc4e97823371d72d5d123b34ffaa20028e4f8d91b1a10473244d128cca7ee432647263162836272d0460307e4a1749ad1911e7fbc18c3c4811b3e2364caa2efee60d2efe8224f7274fb669ed04b3a7b1ebdfd244fb97de01df79461c6f80494bca0dfa308867a5070adfa261064fe600d5612c986357db3c62a475734149d75031e2ef7401e3c726e2ebce236002d0a019e3463774918dd86e23baf8ef591bd58dabe1e29c39a7288a813ee4a739766dd0fc5b94b9e40ddb7ce60fd7d47f1915fbdce72498ccd1ddb462e62547a49bdec08ac955735ac8ce2006d0822e21fce8549f7d3b131cd64f9aebf31e86038940996cb3675b73d12a6d6da4474f6a193aa5870ed71a56d1add6d19898e268158f5285b76b79fdeadbc02efa2d12da474c23227125161ad725a54071c12a3d2f97bb075ab0616ead9e3a8070a830b7d5592e90a9c4e251a62089368905788438c90a89833f8fdef171a2a5205039bda74930159f3f1461506a809fd2e67dae0bfe4b8c7867554c2699bd3fb16a2d6344e6c4f17c6483fac722e57970398e44ed6e92b7b40a83fa865d4f5bd7981c69c1e2730b855c33e977230386ef9033ac058a55989b855b754855ff9cfdceefeb23d5700ef6b10b38772bc5701adacb430a183e36b78922b7a63eedbeca72c799608f52a210770668454e2db3441450cf11bc2ff49ac2d4a2107115a4cb00499b6fa49a541290a67d072af55665840b44db9d8261414b8e3fee8675054cbd0d8cadd5dd6df8b3a4cdf727f998fdb4c116c2e118acb2f392448a191a909beef4e75774edb01037af52472549a4f53bd939d546f6888fb6a0a05359e5185cb005c2e60a9c615488f430c142fdb0732e3a80829b4cab170ac1d5f4675784ba5586f5b55ff00a16c9e913c725cdbeeafc75aedeea38c4cdf981421040b3fe78b278ea818f13e4df151128e6b4662e993acec066375011aa5473c52b823699870ca4ae06726f3266d70b17472cf34f3ee01b89510e6528a2e25716d6cefc0b641a55240c285fc64d7453a183bce492143299188f7e472135eb10f780f54e6f89fa34ec8431812ffaadda38272376184681af40925e521c0ac06f654dfb674b6f20b15d1c564030b5b29b972121f37e5bffca52f4e009f599f36b4cd7aa4b591b54e5ff43bf05658941886c35e51a0777e0c807a4a83809dc2c2fdbb128a950ef344d8e6e84c5050918cac70722c9692d9ec9c54010d662850cf1ddff0095abf81f3952645c8bcac165eb35072665d1451f372472f2c642d2f11c5e6f7ec075b5650e02c161a686ba700b15772a4d6e880d2eb115fe48eb3d67b099e58ed5f5172f2165aea82824b15cb736572423b43460a9bb6287db5bae9368d74cd64983d43763b9eb88c265341aa31a4036190dfeb386d0477f66a4bea652020ae1c97aaab44f7c0b795d718c0effd2872c582c2735e569d4ff7d14b3e2f79b9ff693d61305de61473c7c5bee737195d72109082226fe6c6134b98434b1d95ddafe99ffac031f8fc77e202daac4e4b926e50aae084d4a464e35c596145650ec848e0f84fb2cb5198f9667afe5f5a22de7231290334697d61f03714c4426e57d9518758f879ca5fd21039e0844defc2702b3a2c0753aec2e366aac0211b374b0095e57ab824682c9fa1bcdb77f5d6afad088845197040b5cd85e01593663b43ba956c38ec2c5de5c04c2798784e1e9cd06a045d1d59a09e867ae3188605a304fdda80198c2a23e38b9a1ffe032b03de6b7235f8ff146e0f9e2b18d2e653876d129fec496c62b466aa3d93544937c505c30a3700787269cd492eabd80c649ace32afbf2eb5af7e9642d9b817f76d0e3626729969e10be41ad27679b29a629d7374b97d2ca223e1f1b9de15389a18da0d3d72b0eb98d9c9c05378659188eece1babf7d4a07612910c77f8eda964ad86ee9c015fc7ed53601355b87fb7f35b54931c4d101b9c0a58e465df85f70622bd749e16d82ae24bf2444c9c7d70093b5e72242111229cab8bf032f7506282254951a5721deb3fed20798d9e74a0f77c644f394ab78a087fe501676f7287f46dfcf734723d6676d06c1052379f27d5e709705bd9edf3d9edf1b2f4f56d93a58f3eb448703d9c10c97e5dadf46f4bd84a9e9344a8d094a8bb2f6cbf95c513fd24088e83713348cb793ea8062cd1d2fec8335a2fe297baa8a5888e277b217328b9b887473901991a11acb6a44d515069a2b593088c48da25f06124c5f3bee50240120bb8724a0bab76efb577fc15aece5e1c503d945ee7cb1e403f7730a5b1cb978aa2d4171a5b448899381b664f839f7f1761ef67ffe1509c3fabce8858b965ccc65c1372a0ba0c1f4427d33c6215679be5495bf84e3e4890869cec1ce30eb78dcb124a72de0608593c5415a29131bb263b7f9773719d03414c7e7e3cfacd9e4f5b83d9721cce94ac8a2469b58b576bf5086df3b4f93af5304f650db90e7caef371476d72d4f45c68642a9e1883225cd01c37d2ec9134bd954043e9bfe6d275ad656a9c721db8fb72688663ad1af4437d1975cda58b209033cafae882a2a6828c8f4f664d26778fca8e90143cc0a55d7c917ba8aaa2d64a3fa856343463826f989b0ab73242e99f644177f14c2fd393f143ce6be5f35cb7a22f24d21a8d17db493efe0067bbfe9171a2e442b3a651ba56a367f845c936ab987893a3a4662cb1982da77972cd50c4af6cedcecebf3d067522479a96991cfd69e26b274a15f44dbe61a35672f3b2dd27ba401ed5a048c4e688f7877040aa8c3aed609077d0b3ff4dc42b5d63ab9d6f33224460cbeb51b4bb82da9e8db2e030346a3373487117a75b171c3172ab79c4e9bd5e41954e1870234031c88aa66fc50fb927c6ac1fe7a890d4174072b6e76d7342766aaa435c063c705a80cff14abf49e43b3e489f04bd383245b57277cfcfb88a5d2a2981430eef4474e1e181d2074feeb35e93ac5145f2e5402572dc859bddacd3bbbc33344e6393677de0e803a5f0ff9efa6f09d03a4938e05d72e1c33a24ce0732da266d44eb82d2e3e4a35ae9a00473ca94260f3cd0f03af672fb6b429530234f63b71b2196696a6db5bf4b52fbe8a195b5b44c452b15764f724134eeb90ba320725cdf384bbb7cfea8ae51710f60fa6a6c7f3f987ff1392372bf9b627545d18b068e48daae4fd2da07fe5c2f5d591065edcde5575f75b09372408aceaaf8cdbf353fb35b46aed97861f3c0974a549023e87123d70d78441c72fc1bb4dc571bc372a68a7fa7c5312ae5789322cc5b24953bf4facf148a48a772bb0856a371334980f29b4b364d097ffa7a1556f7d83eb1bc5246ac929c6b297221f7397c766772a52ae9272d8a99d21bf272ad3e777dfa120be264ea786dc172a6656cd71ec9368ea54df29a826f32d2bd2345350cb27eff771befd116651d6d79faca7b4db28e695f5e7600cedecd43212a87122d131feb9f0ba3c5e24b55723334934b2e87f90d4cb01c4dc1ca5934644c9bab13a997668f616eeab335a2721af57ddd3ccf8fabef879f536b12b1abc8b313b8e08bbac09192c9953ecb9f7214a69b485bd9d6f93da732adba295db6d056a14afdf6f86e463f3876e22415693368a32ddffd553f054e41445006f01f7d33fd9c58b5125af4b59f8c14fb6511652ed3d2a8264adea134c5b40497f7eafc69103f01da2fa7cd3126c1fb1815019cb62ffc8c4e1ba673f16ea8907ec444fb92dbd99cff5df0ef00c82b3cf2fa721a51512e61b1a0d51acda8855d029d4d96358b301cb8ef3736aed6d2afeb8f3cdcd9fb59eebbaa9c2a13c2fc37c07dacd2163b61601849e9aa08533b12d8ec2c6e569ae8bdd71b53a75981988f2af2043f4bfb17bc053a2743f63026f589fa6a41b6f5efc74326f1d1e1605b2069280d1245ba4eb470247a97bb8538c8a641402a5869f1ced7a6875eace9fb6f2c815ebf1e863c2a6e331e6874733b49b46a0aab0b3a9ba7cade54a3ba074b455b1cb2bd2033b2ffe49ef5520cebebd75bb462597592307e46cdbbe97a334fa8d1dd178b64e8a54c2ecab217797b40c9d0da72b64244bfbf873393f2dda079de52d0fd737113aeae895cc2876e593a3af12172806d8c4ce7a076abb6bccc43e1ff8f62dea742b73a6420afc18bb5d3f25f48282f0c90915818ee7d965567c68be7dea53c81fae20066ed41d35e741e1bc096411f1e0e1e0f6f15763bffdd90d29b8962546c829b25c6b022a71aaec5bf03b542a3f38dba76379a0f85dcbb4c46ebe1bc155e635c777c02e6cb03a2d25b990121894e5c98b79c23883101c37ff7a04c511d9489a061533bcf8ce89c38924d98724f4b4308c401ea32870d326fee27675e5176b5a5ee935b2cb533b40c5258f3726e9f9d4a4e059ab84e89ff59b4cda2212163101b2d5eddcbf3b5776c4cf5d072e06ce91038c976f2c406b9d740b3d68c37350c2ba11864e78aeb65b4fe3a187226cc25ff10b7274ae1287d707c4fd8ed01b9dcbfa06dea95dbcc86f443f33f723f8de9ab6fe12fea46d419b9efea5fd66c14878f4393a01df65c616c564a6b61b1d086a81f392dd6383273664ffbb623c8f6081fc755ce690b4ba1a48b6a2d2b6707f5642ad66be0e78953d76d163810ea31db97bd896b8f738a70b0e26f913e8f3ca544e8eb4cc87d8b127476498bce1d0c51472bd3bf000b9e1b79437e77722c11bddc10ff3b4cc069dc9425e1ade6acd009d24c13be3e688174febb0f4e47e2ff1823a11e5bb27c33cceb9d4c76ba4b2c5c1f04dde7d7a614aad07e49e2423f0d7d295c0f71e4abdc38079923c22670264a85718ddf62d9bb2c82c1cc5672dd43ce7bb17f35d78b88abde5fed7bbf44d1a5e5caa0f5aa1dc5fcf57a66bb72744a9b9980f77288c870debed1db2bf35a521791ea430004e68cbfe68894d272ed71faef26c9bc5dbaf907faa633bd98ef3e4adcb3d77e77756a3f6c3c0c8e72604b8b4f9d0b8ec93c51fe1b54cf05d08ae338e0b5e2134f614f7a7b7a05dd72d1fd487fc5abfd903b87b334f48066cb477cfb7f63884f554b011c4f4c24c42f28128bf706df6a9fd271e14a86e814cbc9f11e32e64e27ca0299a9af301c5b724accad0382a996128ebc7378cdc409aa86296ff9f216489b0c8a57b5314b0c721b7c91504d199da0b7f55959e6c049ae07a98601a7f6653b5eb3acc0714d26727945045499b4a93b364da6de682a8b58463b355b9623c4d3289249023e0faa3fe4ff323919e7a48dd1e22690eedb4a05b74000d49a0d2bddf104ff1e8e0aa6056fd9711ec615e9840e1c8be382edd2597d057821c27d345a89d6814e58814172584ef717d2483cf37c8401b3f35e49dd06817be845a40eb4c5c1dd58d777cc72c9f1c36cfaab7d627b277fe2ed43691e8770b7c018ef2d50d24735acc159c8727feebba5d9315a7dc43eeef4246ebaf32cea7200841eb3bf2c453be416743a629297b83b025dc16e9fa38d18c107591782598c26263222dd87b7271e520ee46bb6d114478e921c951ba463d8747cbf20de47dd8af06101d79a80df25022a937281787eff7c98252c58a502a8c7b752ccaa82611db9b73aa7679f1f171b76b47208558c26a1b2097b9254f2556dbe799cc014d9dd71e9a67c550c05041c5acf725ca6cf92c0a8102c2390a419dd4abb7dac758d71482f1a264f8cc6b5ade18759cdb6b4bf6687ca6f943dd8370a06bf181af55345949e14b96ebb114ec5d96272577d8849491174e6da0bb21fa475bee6dc532b243fdcdea51207f58c747d8653ce2d832dd19d8514f496d5b54e8f090dda6d156211e3f8c440cb25108e5b62728ebbd2716511cd9eb3fc93471e826a223cb3f7ca9bc26282445e5a4ca2b9a56081db0766bacf7e28405b8e20427f6e263d92ce635e281370af27bb621142e25e3078f136cef579652ef51c9481661104d4bf631ed3dc0c9918f5df75413bf0720682994010843799490750228d6931b777cef8749eda166c69f1a2b73c7fef72cafa131d079ec919936537c3e6de340aa3808c86f203885cc56f16b95a261d728def48be865977b08182d78c1a3fa69473d89264bad9fae8a8b59eb1c94c4860b5dbd49336dbd3289a8e13d2e76e431437cc3fd5ce8e479ca1e3f6c6f4513b02928f913bd9ac532587cc9f6b5f93e34a8592c16538f9892ca7f67077b10b6a7231c05b78fa2ae2bd1b0f39666893d2a9f26ac4115da659e3b402560f401f7e721649690587f44c00535e155f34e19ffbc0ef12dd32cbe3e3aa22a2103cd6251d0ae6b4164b6a7e8df02e4f94552149fd084f819bbe469872f725f2ba7cf549081867d2e27110363e26b62f5350585d3ac4f9fec2d35fbb6d948c9a8563593f14cec68ffc289279ac6d39b5671e0ec1c6086d11e8a1ad492a037dc4f780ff007284b752b369fd8df8e71476675b85ceefd28285419745bd83d6e171f095368c48e0c4e41d061b2c2ab0d0b4dc5b1703e86a20313de792e58ecb738e4e8f139057efbac5fb498363cfc5df579f5d1d1c6f4118eee2eab7eb30dcf5c87c9da5cc2df83a232d8e9b92ccac53cd95384137e54fccbd83e83c47491a15f3bc11108a459331ab63a4beda4d1c02a49b9869965033d751bbef4e6e79068cc68eba7a5f7226f631191a0fec73a976601761787f66149a57acc8a1bd14edaae2dfe0cfdf72951a78f6e532d8ea500130105641c36601f8e996fe63c32a70c47be1e7993202e4eecba3f4b87dd2b0417771ab2a970cf0670cb7d3f9aaad5fd878c2b1ec305d5d6f1bd776b14e0f04413a9cd264dd12d0efd3005fe12317595b3949b6611172001a78ec3d636f1107458fb160c9d0372a346a9f93f109f30de4cef74ce53772b4c8be4e7d4897edd4a255bba7b7e58480698250dfd0eb6ad51849c277ed48727299ffc6185510e4fcdfc0e9d80df7d95cf86e15cb7895de6ae613059a96d21a8d8b503dc9c569a4e14c78ac19f1caac4f28f4259f5166b51d7eb5ab08ae68050d470b410e9253550626a84c48e41d8d8322a3cb1afc180e485023de9d23a846581ac31bcd00a8db7d4844d40857f8642c7eadce60c587ec047259a8eec9397212ec9e37e6f58b9b2f7f060ea64f9e1e5ec4648a35ff2b90979267215d4fd9271a5e444062879dfd6f96d9a8ab364b2b4b809f3242d3ce29bc722d57b7b37b72c454dd94a96df13c75bb91b315b6f7d868e106a2729c7104d0262509f05f0972d211804887b3cee96b17554b4b04f84094867955677161b9e1cced9e78bac42cf2bb0b7cbb7ad692b00ed643162faf5d70936980b9b31262fa6414b75d1fde089e7003b3f3ef7d3e363b5a07f320b6e6739c6b76d44b36f6e70d856ffa77c172b56c4366d242f9536cdb2d3c7b94f972c1fc6e038c6221b84641321ba4486e2aa6dc7951b0bb7416d84930d5f8dfbf88f5a6f8d1295d048a8e5a05e642598d721082972473a0042d6d6860fea24f6bf318c96865cd6c03649f2041826dda82322ef2f173683559cf8eb7c36bf344c50c3524d1cae0bbc9741664efca23d83b72a789d4f565be7fc70c9b1424b5a7010d49de68efa288a8468e398b1a8564b466fd3b137122e8e3ba320120559980388a5c5bd9c9f2f6b109d5e58eaf4ef97f728c5c06e774b2a9516f0d138b9ce64d997355f941559673e67b4c74ded4a1bf682670a174abd1a616975a1319514956f19198895385f079c36f81af975ec8d3720c42a18b4c7370b5c828f374891a6f48a5eb2988d66b8376fbd7e1d4acba8472d8d7a3e395e0635ec067b2632aa59004c9f280e768aded0e780ceac862d7c255ac34b87edced45f554c62162b07851ea180d26eeca8e275705b20c16cd9f6b720433ce1f49bc8cc15c60026cc37069e313f4e17faacb383b71dd272d970cb272d6ffc85334bee174db7ceb37ee0b3cbcc8a717a98148e083c10dc454608dc37274352c6b58ff37a4cde4db5e795d2d8402300af0e8ab42ee088a7871c8bb2e72da1d33c8e000662e0c8309c6913f1f98c5327880e079d6f953b5ebf0e5dc762a718ccfae5cbc5ebfc28d801693df87d62caf7cb1095aca722050bd3566f692721da6af7c2be9be07362d1cd2cbdad64aabba14f7a1407023acafd3935b549d722d67d4a7023b6bc8acf918df1e890df50f9b19d73c7da970ff91f61e7c028e21678d5dacab9e3ae496011f43fc379bf1848c7f6d1e57d98350cf91d03f0d4a72f1ecdf2e297360301852d79da2279e240b986144eb11a6fad67510c290752172d325dac10dd709bcc633480855c7133e120a7b777117f7ead3bf753d8df10e483224831d9168cbb007b9c0777679831b0d967c8ad71465cedcf6c0feb22fc9726463b5ac6963d23c54c6983ce52d8924cb354f70475a709166b4e45aacb95672e8b3bfe93bdc1c54ee3af55932166102b4bbd67dfb13ad2fafacdb4698bc1b72642f0b44d5ac956d2f0d4e004d74decfe8709f67b5b1af1ce46836fdde322958f29c52d7b6e861ab97d6718861d642d59678232132afa5e214f9b092f2f97172244120b287b8d172a08da6e1ca3f2f7c90cb1f79e26f9ffe46b06e2198dc5172333c618e65e9884ac92642843999e796a7120a0c9870f943c532fe15bb1dbb515fcaa0d8bd5304575fb691459b06f9e3810a845ad54debf3a272efe09a80af723081d5b444a70e09119f783d4988339e222d284779c9751072597445b7f4d3721493db8f291c91ce4b898dab17f91dcf9610aae9c8b40f6a897061627375fe72c9dce8e1a7accb0b8720dd0a1c252d79438169d7e5fa918c5e2a4e25f3cdcf72e4ce22bdecb77b438611a29d75a224d8e5dd692517a618bcd1447c953be4e672cebc1cba61e16c33e7ee028ea4f0ac4cd4b31d4fc3eb7bb3d6cf9098d2b92d7208a8895026f3378a1d78f5f6687c1c3cc32c4c1fb413f4f0dbe3d6742021d772a0a3f57a9cd96fd1a076bc82a0d9c3cdb03dd40ec68fff4a7f3ccd43b048a372e7c93078dabe451f9b687afd8d135b05957ce4137f3754bf940b9cccabb6b01dfce4afcc3b9d89c65b2f607916454058b48cb8916ca0375af631915a663e48725a70b7746de6447693dd5187a47c0ed788da9ba2bf632f4d983ababc514a4772f8f95f2e3adecc08e6464223d8deb9e3dfc33fe2aa15e9a202e5a98aacbbe172bf665e64f3beb3360027a24dab76f05a33c960301d4b53633058865ad43017722a263c6b8ebc440774ce84e9b23b114442d755a8516303fc3f0992fab2a6a810b3b745d508b9dae31c2d2d0a454ced02f85a3da92a559aec6d33c74d448c7672f9b5340a1126623196aabe939f9fd6c16ac6c03edef5b839c189aafbeec6f072c3e596631fbc9cc69210a7941500ad9c2ba6556bded6508e48d6363f28cf6355703e30bc32db155c4e25a51faa37d51334b35eb8df981d30a0675d0c634be8725976f1bc819cd1dfa5ee6d89f4d1947e7b9e4c7210cd4f31bc91641f1ef87f33121422c4d5a679c389cf70aa405b9ab19e380dc19c10696700963a9de15d78062c035162232dba876872f6987013252a5f345b0f4852c7432cbcbf141e1ee20bfc676393d9965db03f7f10280864fab2f71aeed4dbcb1814bee0275f8738ef7285a710924ed584c2f7b96ee5064b9b451504e93638c1e9f66ad97bc9627e3b2f615f4f4e25258736fdefb7c2a3b3b1d0745fd1122ec9d5bf3bea6eb9f565b450455dc2329ae333192d538d1719c260b09dce11f3377c2c103c910dee8b2b1b72bcdc4fc939b72bc1cf52b7ab013af1ef71ee97eb53db871ab313772b0a41432616ce8bfb7a383dd211d1fed31bed94e9d7140c1852a79f7c26465bb530f5747206080dbc21ab8ce1c6cb8426fe32c3b585ba076463d0d3e56751943f035a4f449e31cc610eb111c2ca66fbb155a34d877deb9b93061f060fbb8c80cca2b5a67235bbe06da9952896b990efea9d0b9d7e706577e24198094691362e0b73756d727098e3c1bd9a20c627ab8004ab5d2351ad780434b242e96548446909d2f3a372c237b5e493f849c014b04da58c2c1d440f00d3d2ed5fa11d75ac06dddcd680127c6f33e27715a6d205bcd6545ee3fbd64fde95664b5b78ccef1b9b2603b3e13f3e636ee673b695c7250523cf5161794e985147692a762e5ce22040862a395572dc14b101f685d9e4f8fa00b028d87ad4c2a03ed02f2fa33cb10775aecb31aa72641c50b9e1078484b47a1fd4985eafcc13ded39a68e268a08df35201a9e8857110e723bcdd7c8f767e6367e5382a316812e844679f2a8494932d9ce44e9de622e984e0522e42ffbccf8de0596daea1cf8baa5ee052ba29f91d5e5f67d5df92725e8bf1fdc6447924d99f5f8cda3155fafcff020339a533dcb8776e8211f83972e946e678569a55f98006ed3425bf882159969ebe031e82e5714aaa87ea371949116f53c168f598938141db8d6cd8919ae884c74422c63b6fd8cf3bfb011266722f56d28c1d916f0df31cccf5234c5a70ada3f1011b1a16fa2bbdeccb9aab4c72b6813eb477ac23fc52e16fd74a50b9810a04c75363a4dfc5111c64735562b67213d4a1870d641912f460604a25cedebe2a686d168d7b0a89a224d33b57321b5c7f27211d32c787dd4948695e53a7b135e27fa4ff621d02be3639bc65f74ec172bbfc7162ad62a48d3a2e2796aec8653c0bbe4bb541abc63bb277af7c2934364a45a1c0bfa623bd5f353b67a0d70f34652556e4ffc3c4e28cf37feb8b88095172a6ec2e9ccefeb51e3159c4fddc5b03f1863070ee6f218df9f206f5525065ca726f4b1fd93346b2cc0863289e9a58df6ed63ec54abc3f96c2736de5dc32f0d972d0606ae83c7f3cd524f8e8e89b4b820f0af10c725da0a45a662ed58d01d7417280028a365d1e03bb2edffe065792f6d87399269b9a53b2eacc14d610c882362b93225fcde80255d48129c5a5fb101752ffff347cff06110f3f193fea7b3d152ade65f708d7e19a84362e880868806d9fde699b8921769ea560a33479b75660721481f38ca6bed7df5e65472731c939fc52f1dbc3179692df0a9c0fc50cc048336880256eec9dd956ba1759c2b3393ac67c974e0b451bb359e8e2743392ebd928e1d1c31451f61c9b6e3bee238b61267ee3d550861c461c48a88b3ffbb1a8a57260150f87c49fcde5f6f42582ca41961410a90b1906fce9ca9010c588b098e442faefe3e48335d0208bd9bf3c84c6e73e26cdf6e00eb5802275ee6472a4a2b3725ac3f3287849576165b4b2b51fd092c5f596cef661009367d95a70ddf0622f72f5730c9962cc96df618f5935c758397b36658238dd746ff4761a792d79d7c94f59ca4dcb7a0d014c2e6f68290eccf49b50efa7e6d2fc2a72f33a9a2f8ca4ef0e4e3be76b6df5a9bc8135d9260d7f382999767f6c0400918188fe84862266ba72d8e42df2750b6fd234c345c3977fc0030f952ba0722b7fb9e51604d63fec8272784413b4cede2e356cb530fc70c42df902f858a7324fe673cab7f0429e03c63f211851c9da8a1d47c8f9808823a3d619a25f4f004a771d877e86ba707ab22b7252bcbb61b26810867a0d0b9bb3c823f4d28aed9f5f34b91242d9637b8e8442721298f5cd1441a86c866e5d1145f2fa34faf8b55497c86bbf1f8b7cea6b68e37202bf2a53a89c81f0af4b069a22416eb5024489671ea354cb1a574881374b33725eaf54ec02998157303a38b7091d872eed04e8c051041e68a6642c68b0ec5072d462a0864afa0591d5caf3a643092ba9aca325821efcbfee2d9935f2697d1707b0ed80d91389e7bfdbd5b5d836c071f109c779bff943b8d57ed2de850f4d4145c95b202b80950c7ed41e2d5225793228de41175ccfcfdb5718cae7cedfa23c72695d551d3e907f57600bdc8570e3121fa908aaab540f813b73ef8c811fcc3172bc04a9be74353b7de9d288549e95d381b664de02a47aaf4f52a8190af7b4aa726cc851c76e19d36d37373c28c8013b98d85186cf0115fcf1f847f94c922ff35fcd90a20872189da1ea5b87392cae962589bbde2f9df505a729281a701802d5723f02c5728e41fa31d2a26869638edb4bb27d07e258fbcdeb93147def80669d72a4be9040448c4d77f3a0535b8ac3e3bc9b06be7ee2d089db1a0ea19d05f4cb693270bc62a1e8278a4faeef462af2848d105d19ed9bbce89be55f9f4519e595724e5d99e8ed05861394c988c6894cf27aaaab7dccd1074168b46dab4e8e8a6d72963fc87dbf2587266e7e7a99138df6a0c17087ec53f846b493e9d3f364168233dd0c6a4b36869cb5d7b3b265007bced10674270b68a9fa8709a9ea2f9212544dd55ec4bea58e35ba041ae2ebabb97449146cbecd5e2b0a2382bfbd71c7bf73723c5705b8b77c86b37de5339cc1a7db768330653a7b4347355869f6ce62d747231f3c91e3eab42ed2aa136fe392b176e1d80293bc06eff7c8a78976c72a7e825cd1d95a531ff792417b5a12f501bcdc66a7a78282e5551d99c10ccfeacab8534302001f695ee2bc74b139caeaceba65aadd509cf62862e83a40ac0069e31a9572b276fa832d451d5650f9520a0f57da601d8dbb35cd5c0b14e8078c7890c3375fe2ce728b77b36b40fac21baf7f22dca3e700f77f7d3d35674ce9cf1700a7af724fa14c4fbdcb401caa03b9e5e03311c990f67cd136ec61fc9c1ffe24622cb37284b190c20f26a6f9da6c3f09874c4643deb814e3abc7426169bd3891a93fde4fabcea86091d6c53bfbb5144fc79a1a3151a88853e3ce00bc967cadf769161a72688e402954f58a71c3bffc8e1950f70506c6ed3f4dde0f22bd33218695dc0f4ea611f9c46349aebf7398296e2f6649d7e90f9d7917756ef3a6ce40a80c818149ca79c5616ccecad27e045f9da0314bd8aae28f7be448820c26fdf69a3155106c4b8b160295ae2b34a26be18b10c600d81ca7a5f68ad25d6abd70b62567ca9772638411344c947a249ca29d15562ef6386fc88cb1cc1926f73a861dd7ad856a720b11cdee0bb130f76bed3ab26c5ba38989d5efdb6aee2a78e35e463c50ce250531a957a9d45e03af6bf932aa1d2ef0ce358c00fd07dff0ae65c95cfb3c270d03a30d210254b9b9a5eee08d55b8c34edf8628c498d044ca86d35a43fbcef4ef72e079ce5070cbad103908578745a3c2f3af4ce86e8bdfef355883978acb46ca126c927a90f7bac868d18b696b2f7a6233192a60285cb5583c8a70012705e8d57224a89862c96011c9febcd172167a2c64bdf17272de21790940c79acff086a3725fe920cf34f7b63ae23d165e4d5a246c5bb28dfa0fbff9348daf17db5fb4f8724578f5782964dbc0190131f45b54a9bde7172b50bf663b26032d95e71e08e272c246c274de72d734ca508bfb6c9fc20a6612233b500ee79303ad2d49716fef5b92ee8337429cddc6a5c6de38047831f143326fce3417eabfeb6d433c9e223f7233a632ad242709af3c449ac88c26ac42cae217eac295824f5ac282453989d572c98a392fd230c2a2092c2c238fffec8af83a9376eba63f8ee87704fb7ba5f77206c262468080d3edf6417d27482054a834640ea04ade4d0105f7bb2972b73f6fc477e2dc53e85b7ac34f823f496fc0c4ec33eece4a2254fc9667e5fe668cae727a8af752fd252e23e5aa28930769355a85e5b27a0b735622a08069da8341e8727c79fcdaa7b5d4818c44b43bd131d1b151c8ca538071ac1cba9a419de4a53c31b1911bfb6597669450d8c67e503951142e72862748355bf1d9433c42afceda7229e3246b5a53bc5842b7de2bedcc4642d0b9f7549e6c5d792e97222e92b5567295519a8a65d13136698943a863363c07e7614541d987e9543c7dd69a7697c77275743732a551cc967b1c1ec4e33aaf9f80f7da6985543e6aa5191692b2033d14e635f926c8c171a728b0cb0c3ca7997508edbc1bc0b9ef42072e568215199572dbdc8a48375724195d7586f0251199c8c0f6f9efde32b217f98bae9d72a1a0726e1e9388157cd21bfc7502e3e4bc6fddcd47099a459e75a2cc62e3387595f57286378925f492404959a7f9b65585853ce9cb566b5903c976fb76c8e122d31b72b639705984680a472de9ca0e5b2501bcd8527ce6e882d5b2ef6461bfee14182f50421c82888445d937643da306b3493b02d80e1223adfdb444651aea5a494f194c7915b698407bdfc553a4d99a28773830e02e6fd7f62b83e94dfed520babb6d56e4f81150446239f519e92f3401b42eae9d8146e08523c4bbbd5e4507a44b3099b9f38d412ded1cde758c32d197cce5e7b24132ec6aca2182dc3afb82127932f1982939157caaf51e6c0af8af859443a34c844c21aa3dc25c0f383d9fc7107263bf6c44df71c5a5685834e98815e2e2edd2e2776e24d0a3b15999dadb37bb722523102b99b80ea1995af8845c3706af73c8e00a1d0a399f1cbefeba8e89c5725df458eead27a40ec9a4754fe2733f34fa8ca708dfa569ac6bd4e752558ae64b2e09d7b043c04471b32c291e3dfc79b8d9128bc093731569fb8207ee635929551563eecca00a68171f9f38d88b36082bb1b2fe139668b66cef21f635d514556896e7683df1e12fccb9a88f4cf9df09236a84875df3d8b05efc78233391091e1a4918c722531044e4ff0b04d8f39cac3160d72f19b834c73b2c190d749ed269729cf455d921c6dc36a76bb578ad47f82b59e07772bf0e9529ea3d66893b097c460d394e54d496a4ad985f4557a5588bc3c3405a684cbdd6bde8574a44be05bf08be2e41ebf2137cd2f155f88ce4890ac3809712b8cefecc6fb9937d3cd0ddf472c90266bc4dee8be8cb260a225f941f72dd8d5bc7539fa2cde0dc9b0757cf726c57a87a9c52ccaf77622fb87953eb72172af5a9df84ec9c95e6ad32dba2b1ab72f97c0735078fe565d2bb96d1af676b1f062c86c3438c4c7caef97e5562f0db5f043210c1bcfd33125c2bdad3fb84fe37196ba840cc405ba14db5d5d50bcbb27276edb4d0e0d8c7583379e9cad2773105d8cfb5a9de6215b77ac932814a83215c5c912ab1e7b198cf4d024726f77dc20c54efa2f40ab40c9d18c7327f86b8a272760b09af24417d8005f11c87789dcc93e781fccfa4331f5b2034763500c6ef72731425cb23c32c4ea2cf872b40fb6905b94b141cd5bf8ff28f91cedda2a1f4726b05c0c110925c745b347aba0e4941f7f17a6389d11a516751ee439eca8cc52e5f0f4c5c413d5a14b18f0ae9c3e91e5f0c646821ff3a3f56ea1ba2af5ef5a172ac3f83511529a61aef1ad978246dc627257ab97fd18033e7dd8064a6f11a3c724d059d0c8d529e18c499b192a6dd285e33b354177282890351442d8a18f8c4729649d1b7f2810dd95cf58f58baeb6300bf1aa8e713dd8cd6e9353894a2e0193103627fcf3f0ebea2fe9af84e096e3968dcb11198b643b464b8b1a593f893a12abb65d3f0332acc5d4e84cd3b12259dda9d4b3fbb9a85c39e068db1c3f802e272c5cdc3a979006d141c0381ea2f79c3cd2fcab60dfa961a3c7fefc171d891787208d17bdc79df0aa684bfff05c5fc55c7d4950722f2081aee00754e1d9e71ef72a29336c71b173e01faafedc4ab569976fa2bb2f544993d4f9c36aa462df98372db475e1306bf1157e53a0645ce91f4a5349f6f049b81377d463eb68adb243372d245719c67ff36e727cc267c16ce1c65a0619993337859c13e73a6c578bcdd1cd1ca2dc8cc1e2278dd37bc73031eebc907f9f07d7da3b7d01b0f885e30dedc726ffd437646ab07a9a6eaffab5be9b1eeb48dfee68842ea18181f3154ecf4af7293662df00fec0a5e72ee7227c0073e11f87c2db56bc152de82ce1844c6dd0f72da4c8c81a5d5d9dda92a69a8445cb8a71d4fecf40a835049361d4be468db77720db03144ca71d81d99d468bbea66bb10c8373c62be5bf6a5437060d4c7355965e6e62c38a66e4c039bb17711c883ecd24fec51ed138592785d3b8fa2c8343c72a953129dd5860fb92a506ff4762ee005b24b0bcf31432fa92b74383483e59672da7153441a6f37f9f4ce4a060d611a07e6024562d1b9cc088a12db8e394c2f724b07a160e743337343780213a647fc21b934efccec6d94e173df5a9b0182a557891e7dd4e732be8e67759002ee298d54c7322b09896875dfcccf2ba30fe763726821e29597891a76856f913dc4fab133a299fabab45b1d40c9bdb5d801cfd14142a9a53cff33b98ad7c0a7175bb728fbfb641c9c79711924cea059a71850a327455e8ed9532e6c21682f3c86d3744f253c8ff3b23b3ba1f2a94c11965752d8066d0e4e475247483013f11aa3b0eeb3be19f5e51da2d0f6aaeec0f3bb15fc3f728f7afe6ac47dfd2354c8504af39fbf1642d2bc99d51e275de8f47b84e3550c722058d86702e570dc470aa328aeb99068afb984174459736fcfd976cd21aac12750a234fae00f7d7e977f3b71b1c648ef0309243eb0bd775e7720a6adc222c972b584e87082d2a5ea0da3b395a8baf413bf8a27db67cb9e23b46e3eac642e95005f5bd23cc0d5c8be3783f6d94ab0a2014e04e29aadc9b79686e12ac0c3313122bd5f1801ab8c93e6fa85a63d26df2abf5045e723807fd6cb44389ae00756627280cdd7c007a25c93291776347a5b61634893e99cc63a885fa149cdd67c295c726654df172a8ef1308467011975a2d874905794197dbb865f4bd3367708822e4d4e34f1e426f497ccbec1ee2fa88f33a028aaaf2c91a5508b70dfcacb2ed70706354d1bcdf14b499943fb96b390aadab5ee6a17279a120b65f054703caa3ee15ba64cad27da548067f956940dad0dd4d36528a0907dc656e2d7b2eb930b9235727a86af575da0311e082acfc74f7f93180b7fdc3f46965516a1ca31f84d1f0342446ca6c8365baaa63ca643a7ebca69309f1cb5735e7a319748eac620f5d4a2318a53cf392d7e5e6168517ded54192bb2708063f1354c90e3f4df8e57b426e37259a6e52254e1c5b8cd974c97e88a5475a772e68e29e73dda0c489f62b0113c7216c28482b78377a7f7d80feda0c5f634f394f9c8cb6aadbcb9bd8673d2a92b605dbd5d110bbe3de15f054c70706496d6a955dfde61e2f3cf95dfd41b79fe6572bb3c316f1e735c574a7a03ad73b1d1ffccd3d848fdc56b40fec1ea016f2d523d05edb4440a2a7b66f1cfaecb9b550a1780765664dbf328918502922eb158723770023cef8718a88c31c762542b46d417c1178a68b04f6ae14bd99a90ba6e4426b6b8199091c201921196312e31e7af500a0b65ef077b8c446913cba8d9603d7282eafda239222d7b10e60a98edf5b907193994fa79654f3da4304dae973811163604b4ae38cb560005e935167e18a63a813f2fb68910558eed05109ce1a2d46d2e25d4f11f2767c1b91fe3360e6c824c61d2751f9e16c38ed33df8d316dfe167d4155eca4035dd3772d97175577f8a6ee39248d268d91d817029820371ded4725c36f673a181a9c189e6adeaab72dbe0e9d8c8b91b006e7f6cec8c0f3010503a86bb9fd78df5799f4680a86f30beba2e576f45b1205923fd50e2938806f654724666f8c14f337a3d8678a5d7ac67dbdd9d79ab279700990aa3df88822a3e350e4028698063ff09e5194aa80636d193bcc4afc32886f74951c45b73b0750d1e7223edcf4a20f1a638fd21075ab93c7d3ce0abb9d8e83fc635a8c862fccc68b57216d381e77fda052e45c3ea180c4ea07102c9fc628e0d62c18932e7a13733b672ebc0f134e56dbb292768d007ad50457102133ca2d0f9ce0e1f9519d6831487728a59ba99c2aacab88c95fe15a94b16d42d3646b3902af0ca66b5de6b5e23af1499c6bcafdfdf70064853251aa8e145f2d26c8728023b0ed4caf60918e26b5072019069e8e7be7b800d328f913576cede8e16c468184a4bbfaa26958fefbfb27227640bf8eb3fe17fe51af0593b95a5eee154d90ebf302c25a34050ac46e8a5348d2e7a65d7f102ce3ea649bdaa8a65cedbf9054b5ec5efaa3be9a12f6aadff289532ae71c4d9b3e29d23df80e8cf0349fdb09fd0fc3265213953bb0e81dae331505778efb35d7155123b9e06181e3cdff46a876a06e83dd511365436f5424f72ae1dc1b5771706a6df1111e82f0af45dd285df95f15d24d850f8e95722cf4322e27714a7e9d489162407a74cddc2257bc1fa15f83150a3872141d8f27ce2ee72e833c31877af55c1382bef17c743b44d3d3d48b34c917eb512e81d5801377a04e7d1152e4fde356049609fdd3a2868c1abb4a6b5c3092e20a75b9b3d7705e972b4273a918f980c970f367e7ace606cf036b237d041a2c97816220e2fea528250358cc482423f988ab937b77dcd2461b9af81176144009c4dfa959fd220392d725c3a92a451698f3576e416f9627b8988fdb0be4fa57071604dc6cb18719a4766935b08294c11af9f3d3924585132ecc6073b7c5aad147e3aef06b2997f209f72878a38360f7ea2010d6e98c41706ed3ae8452da37f5913cf9e8092290dd18f0d9e0aa08c18a9ef3fd22c96e9b16517c62fb7326c0e8dfe516af15e382951d072b2aaf303f24cc476c0eaa752844b1f2fa673251e320223d05c38410e6b37e1728e8ca77a7e882159f8bb56d757fce64a1558b108074c9c77ba05156d5bf60472f4376696af67e019d17422a3f5fbc06b2eba89ec78018f8203436a1c189d59729f42fde5bd3006eeb6218d13804c787751ad3f40dcf4808ac1630d4f7cf87247b2f69ad63f44194fa523b9e197089ee0960ddfe762949bab5b69364fe45aaf3656dc0ad3326a71e8a435ea4cd7225da56061426684d2465280a2edd3c37ab772c32aa936b99fae5604149355a2be7257e13e1b8f81434704859c6b2fabcb110c61daaf3e9b958ba8a84b460548763cbe16052c0f30e9da7a6c7a5f6d8d65f772d1c9e36bb49c6ee437073f85d96c9a423139426aa1afba742012029173edad6c2b49259a5a388c30310dcac72d104395891745e16fbd8047747663c69d03fd32b25509ddcf7dd8779ec793f9fee0b86736d8548f675890b2003aea69687e452effb3f4c00180703163e51eb8531f7be4d2eda3459f7bffb4a37a8c6a04208e72241d7ed3f1027c09fce0532583d9cd68b29bbbf00b71b5c6967b3ba00afb0e6d5423ea3d5906d67414b17f3eb25f39218a8411edb368fb3e524617b0578b42533579716d108d2e662e39214abaef49f1a69214ab40aa893580c88902bc56594e613f79488b1f864fcb873de3ac9b5b00830311f02e99102a89fe210fbc9f0740348f77badf8c0e40ce8bf94984d856dee51c69293ad6244ce26a23972132d272fb3531a4ee89d959f85adeb2b0626f17605a3cf8586c338eed980c274d82360e91dfe8249ad47943facaf8f67f78d4a34355319e18be16eb98012ac48703e21770a4b9ad359fe2234cc56dfdf4905196df6a0fdb08ea891ac1e4f952ccd3c609412ca2692ae7930b6cc37aabc369cbd352bc8debe61d85f017b73230c4133d72f59448cbd0978935284d813258ea593eac54f0b9850f82a5a8b898274a9e03722ed5c85f90e120977acc8e575dfd07628f48a76848163b409d5fbd22c6ce1b724a2c0db6c8fc54296239163c515297ce3b5f1896aba28876dc100c1408e433024d748b0a832feaff3f31c85b2e13c7722981d77253b75988c993dd99bccc6872e723917f57d49224da7f7f0c128588a6626b9f2f6780f29e578a1be5b0bcc2727e7f48e4030ee790efc33da257f592a4af6c9cd8bb95f679cea9ebbb14225c722cf19f73a989ae9cfd307a2fd58e4e4c5543f426861fe03b61e5351d27ba67026c262cb6ebc0ac68a962abb686d23b6cf0dacff2222f5fb12a649c0b776faa57a715d7f4da986a58908fc76ce8d493cd93eff2c7667d3ebbac25f4610d043972a77b406450a170f499358cc039ef51d31ae59c8afa26cf8919f18403c7668e7253a89ffabba7e910e8ca0c50028e4d74fcdcd1ab40886438ea559b1141bdc872d2e8b1e56f0e601930092feaf2ee626d2827491a2bb12719304ac6670ef80b72fe4c34feeb14b4d9f75ab740da4e35d1c41742e68b501bacb98ba6876473b172f5307bf3536dcd2712ffba677a37b34ef0e71fc379a1ef90247a1590ed33775e40eeb593de7bd275000dc747f7c213cd137bb84e4f7f01085deef69fceb9cb728ff3fc92dd0d9bf99c267752e19ff8a280e706e3f4de7150746c67456c913814e540fd0bf08936822874b8bc400192eda2bca97604d613aeb5000f5291501118b53eb2110b2226277d1dee9732233f9d2aeccacebff37420e43b2dbceffb6972d02a005edf7ceee649497ad4183f6ab74b261ffb63969ecb70d4017170d91e2b237ef6e86208295279a38256e9bffd513a04f8b592cd7a4a2254df0bb55e6a6ecde88aa0b88df1b61b8fe940aaaf084d2617c81e3584b3c562ec020dceecfe7212c77e8186e06c988e1f2b05bd5fe7966d28f8f34c363be4e86883b5dde0fe720a46024be0ecd706c31f18919446cfa118bcbb978ac70e2a6de0bbbc612a104361eef83786e2080eecfb5d5fa16e387b2d67af016737392aec5c9459c2da32728697336a0eda7cac573b12966592597a6b13d1ea8ada63524ea5203cc51e10465296921696addf20d1dcac0b38326986b520629f81230f3f227a1e0b743deb7231fc7b2913ffd3408404e063cf62e0a3aaa89775a96a50d49cf0af4118dd1e24c10925cfc8e0ac622f85020640a6cba3fee083c83f887290f35e8454c6c13d728eb817de4ebf1937e830a931f2b4d3e1bc167cec9b9df1dd2a0a6aa4224104727f666b3ff82b4cfc8fe4855ada731d4451471cc848f995f84d1919c9cbdacf72144f3390868f736ee075ac108737ea6f15d927fd1538e3ee56f95ccf5aed71722805f06a0c51207160ed46c01a0999e4419b9b99750c55c5065b281b7b9df57240e9db56afc9b98b1e4ed2ade3e4c5dd769509fa15c8d698e9723ab17a53d272fe2a4de80a4cb19f1a0961cf4e5ee6f98695234832a3c9e305831d1ed3feb7725f992bde76c6efc003288c59ca8ee4f8d5ac77d9ec2a182b84fdc34717cf75726cd344f7aa3287b0a1ef791bb6c9220b7cbc25eb9296cabadf9864a8fd08da722f0cd970d8bea3ee8ab813728ac1a4db3baea72790bfcba23b79fd60210a9b721c7bb28e4e5afb726377c68f1001c5bde0b3f76febbfdc8d8c520e7fa20aff079c3292bf6e2ef306d35d96fcf32d4a3ae31df261b2963bcb5375851d32930d72d6d6b9c843b901f6dace00b6881b25bd644d67fd42afb6ca22f7799564391943e9862abf6ea6d367eb6d2968876cd17fb1bd851b72a24cc4e74e58ede8626e72d553e116e123eb1084ef1bd5694cd8ec39a5dfccfc3072c37a42c59b19cef5653264b7af570d57bf6497b69dd86904aae4f4f4f937d4584f151677009d01560d91366b0ec855882a8cd3575e2c6c58c1ffe088ccd6e7cf0e1a9cf920e5ac0c72e0407d27b9518b48e2452460fbe895724f3bf1911e8dc19b77328b5ce65483723ec9beda5f7e935dabe9d3d9d5aa58e21c11c81641e04421f0456c3107826a1323238cebb2320f32d5d3a863869921ed107f51ceb15a392fb6e0c1a34eabf072fc7a4703b9b42f42370566361b8045282155235b851a516334655886dbb860726ae8fda1573fab75ace95ebac72da1d97b69aad597625e705e0e89f198ea1b727a044ee1315d5644276669055dd9b6fc9f76e233d784d9e4a51f4f05946068728c01bcb8b04ac554c7dffe70885217d7b8e19cb4ceea1d3c5a60437ba7982672f94f97a1dfbfafa8da739d1e0f7b6cf1a32331a6b6d768c998d9c859a400a072b7be78ad79135c7eb570c25a4896e43964bcbe9094ad1f9ea0647df7fe616c7202ab1ee267e3b82a87b4331b9705fa95a38218b3edc0ed0498bba3c38e3f2a07c7c116e716fad0aa1074896e2321bdc6f5f9a97ff10dee08d86a910715326a1156bbaa815092c5b0c4fa6503201268a3222085991cb48a4f354189c3b56d3758809718e161b330751fb07a3e350e8a0692da61c6c2489c7168722dbc82199a3ac343df0351fdd1061c69c9de4b3afcae173c45cf1ba11f6cda5e37466d350e41b2f108434edf6f0d795c7a9ab2d9c5038baadacccc90a2aa7d2ad4958c271a721d72cad6e7284249ea6a9d582af0e7f08f7e8ed63bcee10a7934609bda36a77215df6f065e91404d9bdc456c36091ef1b3abcc0e804325ffd63cd57c6b6b7d727ae688d6bd686a32e0d65be8230cdb6a50621ec5db31dba8f3d706afe3813172d39c9d706815e445f6f327aaf97f1e0bc6457b2db29134e41a61c85736b39c61ad361c9d7e421a99b5bf0cdf29dae90c79f54085957d42f5185ce2096c947c72b07ac5bf837bff03c758ce5fb08e54a54b20af7bff2a599455651137899d4e723ffa0891cf6f46ac311f8338f5a1c06ee046f493baeeab37f610efb06b6e6e7257a2a7c332040e14a3956c2c40f73743e01a0fd0359fecc58b863cc7678c967296151663397199845a0e9744173117139e0d4a053720dda2bd9f9b1f60668172354d08142ec99b8038396f96533a3e358130a82f66ac6c0ea23e1cd9771991455ebec3ae1ea4341c033c63e4d8f3056ea7c2b5c5b445fddf7acdbd262f518072576b13c59eaaa22895a9dbec29a9ebb933af14095dc32eecd222fcff0bc405467114c837709a58d24512abef705b87ef4438a491b10ef92212686f1f84002e54d6a8f7774bd5d3f503c935ce2505db861cb345d4a59befd4b37134aec46ba6722390333e26615d302c8046c7f014429c79120c79d2b7192a521f68d8ee5bcb72d55ac6083009de6d10bd886f3ef86cd2b83b2aa79a15c22cd981b99ccd5936723addba528e691621f09a177752db90e304c3375893768b4ca9eb6198fc029272e321a10359a9cd55d3c73bba05072499212ebbbb63671526a4e8bb8aa0c6ce044db912392ac6045bd6aebeb53cf7ac5012686f7015cc8f425f394251cbce1c705fa3a98f38f434166af119ea1e9dff400bf345862d370b2541cdb9a371f482720f12bb151bb50d6a617ee971e06af5b9e560dee3e1e642eeab613b975605c72da74b05e9d22d5aa7958443260d69836fd1fd90ed84ff2a88cf9420da4e84225f1f33d5c0e8120fd6d3581e2b15d93ba2a47449457da30411cc75c13f81e938729107030728308197adb2997b18f0b9fdb92209c7beecf97c4188ff737d9d1e3361e31095683bf27436eff4bc34daa1cf03146966dcf9b676c9109343e96642721efe938bcd11c14cf1366470bed5f4c4b982ecce59f2a3c61b84e3e8f0d0a0720f89e40e293e72029525494d95c8fa410fec0857bcafda9e77c87405662fc372275c39e819de31b3e249ffa49ab821fae39121eb1a8fcf62ba287f8d163c3514ff3f9d93ca6546f374827c743439dfcf99f60c01139333aa4de1f56a83788f72b7926b03cc54bf4b3f83aa75aa6ca76f511c50b80199d6c4c455ce82b3c99a19627daff3a424cdae9891e302938dcdcc6d4db389a8b370406828d11d3dd954723bbd8f977abdeb3124499a323654cbe027519e0032331813feae012d26e4461c5a245df5a70b0f1adef0b4f4a1c94e0680e72c47f99e456d0cd012130367103b009bbe0be9ca76bcc03c9b15df4d1db1304e2e7f5369f8989ea8007956169a72a40eafcd947015c59e2dbdcffa2150d8cd4ae4db5699cf08da86690bb7e37b72616d9ef5313605ac027189a1e1b1b03dd6c6e1b3c5346bda363a1085ec8cb1725ea7afab4e518f1fc046cd63843fb908734cd60ae941e0fcff783604edd11b727fbfe95f7b7611454b7ff2da43aa12afa82292199a241ea976024f7d3cb0e2720b74a0b295671c26a7259431fc7acb27bd123a44818200ea1d635a79e5b4c53ac7eedec55cca01a5a7338824f85da26673cea8e982203b5dae1d8dcef232f672ab6d89c35c63513b2a41c78d94a5c3443f8a6631c1749401b0bbf73615aeee7210d91b6950fe9124d778d38a0efc8cc740f8e4ead0473d2d81ed711bff33527274ddf6373af0fe8ed50e7e73c584d4f4195457324c655afe4a181cd4fd1d7572c1f16d8d3bcf4e33651e8a29707fcac8e365d7d0a2c7c0f11b661277c33b4b2afe1b31244d5f13e9d18183370264c775b6ceba3686152d2e859f33bb850770094da67b8b645bdd325fba09810805b70b26559ce7462d79f37b8d1c29a7b36e5ea6ec442571e2cc07e129140d8b0abeb084d94fec3862389985a33480fe0cf5726c2a25d5a2ccab4a5b8bee470ce26415dfa0e15c20b513113555a9479cf9e017637d2799ddfdec144441163eab9912c388c238115a7d1719441f1c9ae8a0013f720834c000bd6f7309fa6bf250200dde76330887bc9a31d8e620e6871a122972ac11cf881b017aadca33fa6161bb372121b85c1a6615de7ea6538a9be3ff3f7280ca3c15fbe555f50551c1bc34500eee82e26ff4a0d87cc2e1ba6d86a2d4427273a785bbae6812a07a7c7549954eea11c0e558558af1fea9dcd11faa021da86ba5adabe071529c892f8ba8c4eb9778edeccaa25a2070a3b38b3c7de1b0d3b572a7e7114574433436028006efd5982dae7957411e9ba5f36ef794c628cd71c87229b823ec274d81c799eb6942008407e50dc78640f2d7dfd3411de9a13c444b72febec0a72ddfd9abc3825cc66cc8a797a4fb9dc8d3be7520476dc8366475597220156e8e8b457a1be6feba1a32821fc37d23a24ccc71c204fe983810090be472f45e4c6903aafd1178e2801980d705b70504bc4a6ef9a69f13c4f62ccbadd47286a949ba02b3f3cd2c28042369882255ee1cd5b8f0e6e323672e731ce79cf272f3dfa67eece9c5a46aa4fe52df4bb1de1db64516b506056a9ddc61f3517a217279b9722fcea423632c3907e70d9f2176a39d3ad6482fb90c6909e329f254892b9c24103b173ac7dd7b2294cb19fedba385ae186c8c038009a28ea05d1a5c960a4d9b5d671057cf023322e2ce95bc03aae1042f5b6cb304d55eb179d1846d60637c1dffd6f50c95828203bfbfc33398586d587743c4aa80325832c8750b23f93eb396047f3ebdda86be55b496cd1b8ac10c619025b8e9f1634077f934cff7eb505c6fd5809052bd4f3b50268766407c9915979b0eb0ffc1c6c36dfb6318b8b85551b70fd344892b030ef1706fa857336b2b7acc3aceb8995eb1870e6e951092280d8e5aab8381d8dfef59366bace7feaab20f2d3db3fbce4c967552e6ee3d2b7252d3cdbfcac265b6acd8100537602dd2eba72ff58495a0f20e4ac56c9637ed72659dc7197bb2b66a4cec1d950166e396059123b8075fd2407e24104028c3dd723636b190cfdbb1875943aa65250fa14136e67b8d81f0c3b9b49076e6e17c4972153e61aeff82df2c28b174dd4792cb1b4b5f60456a05acd3227eea39badf0972f2437ab5cc90bfe8653839321aac0b739d2a82e59ab47f24fc287904d0950372eb410ef7f292d67bf35928b9cfa6ac22a724d3747ee412069680ccf6bb8d9372c1c3063624b3275133e5a75832cef2b49172345530d67454fe2f182f97890c20c4e3045dba066a21e0d3a5a48354a1388f30ce41077a57ba89e5488fb1c03072fbd045d6e6601fe7a4ff23c22bf1fcc7cb70e4b90d12d931a6f0b63370807c720c23761fd2522fe9377c6c58f895f45670c25d43719ec1bf97d9f796d99cda72f8ea64d13999a2b55a393e7f01d70de7c042f94d296f493d16dae023da8f86727f700a430ab2a4a969293a6da483adbed0046c53d1e37c820524038ac55db34f595bd687c063572e1da7ef6d9fe3a439b8a931579c7dcd65c0bacf3b1d326e44a095f4e2829a52d3d41abcda7faae0bbce91c26aee4fb2e6343b7f57813df64104a7dfb4dfc329fc4bdd3b0285f1c7ccc3bc58cfb01dc5ad26ce5f16ca7b2565b6fbe308d5472f8380e5e078c9015eec31d96ef13cec21d2d7dc0e4d249dc472131ac20f6b82e3d69c87f60cad486c18de05f72c09afb136a5b9721e84ea8a59888723f52a5deb76eb3276a760d469115430149e4d9ce4b7ebc9a7dac7ca2a0fbe853f8c297b6c0a226c0dce9b894cd129e82fab5dc28389167be3a90f00e772d28484f97decc8fb107891edcfd461fc0a5b9994162aa2222aafc527880db1728c23c223e6d5eba1cb3e26346fd3f1077571153303902fd40c347eb191b5a5721b2d1867bc17d4789bcbb7ff8af20f220019247acdc00178b8f7d3f821bd0272717b8411287db97dda093b42f40b7fc59448429cb16444d1b97905e87eaba972f24368c6cefee7e42013ba4664b6f128177b871621af2ebede6a836c73625672b0ea05558493b3693a0a362d3dbaa0e9f17e50a3896049050a74b285d199f61caf3ee8b70cf4e34f4ef4b5415ff28395bf29d576e7785a81991fea616f223918f76f3121ff957b3f5541aa2fbf188a6b746605c4cfc5f23bcec535666816f072677b65d065bd7c46fc3c2f060cb197da4027f5f40ab7818c3f4d143f21aca97236b2a4165d61368b5105d5dc0f24f13d34cf475307c30c98a076b72a38adfa72f734beadd47725c6f0d132b61d0a60e515ff4038ae7f4d0b5603bd3407459b7232b4773968e4263f877170ed39ff7546088c9f27cc98583a17ba71f632da6d72722c77a2569cef961ce953ab025ffb7c901d75c7c29116aefd212e010f9b8d72f3b2d50a9a8047f9c6190902d53400c1e19adfbbe7da87266731764222cd2f725b834c9aa6580999a79340ee178bb70a0670d1ad073df3a5a27358ec3da9e172571d5d0798e52254cf6b44608d58557e4bc883f485b8b63e6ddc02a116714253e916bee284afd68da33bafba9a6a09b4dc06301a8ea6b570625e02634d6e577218039efae6d1d4b4201a90e6233a149c9a3d76b154e05793a8d19802f6850072653ea802948dc2cde48cc3a4129902efab8759e7198b9cbc1ef0470a39d9d2228acc7fbf6b3d178b6d53cb9e08d9dc57c82f00cbf47c4dbb15dc64b08a1d8b72b9ff860bc26551a82b50f51c63aa81dc8e936cf86e8c30bf8b7fc8b89a9fa57220a2a32058bb61cc4f254a81d19e927718db57aa70958489a3659adb1242606e6b173dbe3b8f33afb56830c83b100df60eff7c4754fbd52196a0cab261cdfd72e11dab960a59db30dd966a6e299f96bd3c2f94d9d33c8fa5c21eeeb6893f757227e4cd6117ce26c98ff9ede2603900eacc520338298527c359c52369c5caa072ed8df3d6bf8ca695e28d1c6500a29b7963efbde41589821bf983de6b3a6fc847fd13950623070090f8165c6d00094370fa4ff7e2faac6ed08e713dcbb9a8af295334dca034bcb43ccef7879748edc579eb5979f4f92c8a3f25d363fc6c235c489bb09f71749aff05868bb08c7d26c266112005460f0630961eefe781606657725a3368edad83f341b7df4e29d470ef5dfcef758a392e838dee3d89a12d08f13da3937dd61e8f015127fb7818c553ae3e4b24de1c93d342a71b82b53b3b74d614c98e21ac60fb40abf638deacd540d5a32d4d02aa9ee7f1f07f2d9027e3041f7252d9d3c3db61d9b42ff62d6984c1dc98e5e826742d31c00b4a2ce8170bc2183e122aae3e168701d468418cf8a9a6be1eae9d20b311d2415d2b704a414d73be563786e65522bcde419983436f5216bb030acf8db208828dd21fbc8ccf108f5072ccaf252d5798539d1d6d6db9bde69eda8e1dc7d2bb78d45ef337b0e88d7d9172a93619372473a85200ba0bf9c5ff278f8db673bc7e9b59eb355b3847983899720218c0208f38c89b39f2c7dde24a2344e39c64e4d89be2d2b199e5438f6c48322b0fbf135e22a8a0adddc02570f168043bb6164ea22edbbee53c4250e8e3af72277a649bbc36a4c0b37f6ba8e961e57732aca69b12c65d1e24d96d4a823a023f378561024a03900ad9aa80a5fcb7066dde1afd0b919f3e0e566b079a803257444e57ea1a655d563adb162fa341cf9557b6081e7d817b944ebf82397b36ab050e6b0a5c8af3100541224ca96f6788241f7a80b3977c48e9265e994d50bb9093637fe5f73ffc7ea97252287d8736ff1aa5c9e1eeb8a43777c7f5b4affdfc766872a0b3e9d41523f7689ef71b2a2b624acb0349fc9322916e8d7f02930ca3e6b072334e53037cd1c90a59c358723626403d8eb869a241243c150bb4de7830218469b2cec60f1a3eea35d60b0bd00dcce7dd3394bfb6c22e883dd89ac5ab58adef721ee74894c6c7703ba3ef7e807317890994e5816e22b9c5b30e461ee5827f6c22ce2ddf92d3252ae5a331713e8268eef86cfa669f209698f9fe5b9690e552542c41abc71927ca9dfff2d7b3adfe74b8b7f10aafd7a5ec0482cbe7edeaf0403472317b7087755dd9d7311293b23c7e9b1d3c8c42eb5080e45a01e3b8636c3fde723b4a4422def4525356ced0e27f1003d740d537346b2dc53c752f7e42c9f9cb582090a110a1e30e21335475f2ee5d053555bc53d46de36728ce1eed0fe1277f7255d335f94e64829255752b6d2a951916199cfbb6ddab461c748edf2752c6402239905c420e833a2339fe653aeade641785674a93dd44f9b53d8e5beb5aa73c311a712a186c7826cfb949f014dc03f783e11a959c4493490d642e538ea3653d721f82d4169d685e322d8f65db4be91e46f62d0ad23191a98a690a1a4c0b001901c0d28945c167d07df9e9214d313f0405c6bbac2e2621c72f2ef62c8b133d11243ba65b726870dfa47ca24e81780c5e0107d24d69f24a7de0eb5f9d25641eb4728b2f2f2cc470b1ea4f803f8fdcfe212a8873ce23b84d1b009ac3c8136bd986728850f00472c549e84d3ed825b4870e8a955840ba8ce280011103b43f63fa3272c043d95194aa765141586ba850ef2dcdc6a07f9c4a813997c2db32fd1651b32ce92aee9c00b1590f4fec7b6da57445434bf3cced0e12a0eba7f603512204762c2daf0cab99ba02e36dc343b7237628d6de8827cd9ee68563b55170a4df861f7298b3d68e2f09ca1dccd6a6c9e142f44e3dc92e8790130fc55c8dfbf7d3946e1dbce5b4752bdbb0a2635ba4d477d1c48b02c2c4bcdbe51e9b90c2069e7795ae5bee830ee1dc485ac5b2474db4deb1b3815dc8a442aca9af3aff9e7459b7549672bff17096bb00a08af1126b39b010eeae7630cd2734dfa2ee0e69059372028f0b8ac52952a4ad2b8a49d38032281b2b9470a8ea622d19ac0b97d00c2b0a98c472cec9b0cc9b62c1571ed7d0bdf7ccc7df506070c8c274d16b560caac0e6937772a46131baee6bf181d057009ffa8d25ead3c5d8ec7b7ce062c010c9ecf490af19e5e10e9a686159a127045703479eedcfff000073e37067d719e06450421651722d5eb69f26fb6266ae0e2ef2f68e0724c1f8d124a066af1f2e7b32139c80e91e60dbae42e34a394bd92d90bd99a26de179a72886f7f06983e492fbd7e4441717a4a1dda1d802e3312da86e645958d5ff4cd75d2e8ecf76dd3a68c699f430e872edd3fb0220767f1c68cde1769ded5480afcc103251488f695099c85a0e2f230cc0bb86e4a10801fc57f56a3d791577e6c2805604bc9667724d084a91b4420449671a8e48e8b00dee9b73c3636357b83d780a6a54963db0eb5b1f475d813f7872cb566e5d45116efeb2cf21e3a38ac90c5263ee95d44ab81fb2720273d9860b1af8a6f36cf537799b5d8bf571a5d297a7cd52399104bb0427838bdaf728c64b1022ca353b995dfaa99166a0b26ca540f237b0a11f711ac0b303b41a2060ee3738851ee62e7a026282b4d589e5b245e04035c225d785b5bb4b2dd3550dbdbd2e72add2ee3a45684016ddea440dca740e328df9e394aa49c20b3c65d17b43a7f14c83035c1fe7f391842270af1a9a94471407b70a6aaeca489659c6b649cdea2472454019ef5174ddcdbef74684aa6dbe78c35c76d913c4babbf9f1bb1d047aa05f50c7b7c148e0fc4e8e841e534a7369e60cd0e55bfd6ec7c1703a02042aae9c4266aa802da6cfe950db62a1476ab20b45766311c742b862e8a3ae7efc11783540ec7835bdb69db45e9c9b3e66dc6c88bebf256c5cfba9155068c37e703e3e3d72a2369dd2585da85ad577922f0aee95883759c37bb1985f231595dfc6a6ef44503d3fedc3307753f1e6ea9761f5f8403ba8ccaf5ac454d08448044ca2cd7d0072bf0e86afd76008d6334195cd1b8597a0c45b7cd4f530bd3746d8dec6102a4f727490e959a51594aba66c3a8ddbf18489636002786803e64369d6a038fc7c6c2ee2f427f5efa22dd520e42bb8f8d1313627d0c7546712b8caf9ffd863c2914072759df35aacbaca2059d73ccabc10143aede2689ba509b6c05d93a446db698e45f382bec2cbf7d280649ef06a519d747435be7c06fccb46f2cb35b4ec43a8f972234a468a54644237c2b7cdb7fa245f374bba6b0c1d9d8518272a3eb19f36bc728376a461db61d043025362f3169117bd0455786e9c970fa5f28b3a00852ef572246739e159d72fb82efb7218f86c70218273b8d9d65b94791006030769997e72b730d7c68cb203523630259619de4c07acff24435de7bb446d02dea06aa08272e8120fe6d59a0e00c433ea19737c4b2b57518867db85e572437a773cbc1262722abcae32365f99dd7f48619b00dd12c44f1f98bbc7ca2f7a530ce733dfd0b269acf357b70f3aea0113e81e7093bc1f02b4868de1129b84d6e0da251cf5e95c7244ca5db7ef45ed2488b6e5071876da4dd5c2b3c4f1e7a80efca8751314e5af723d0a6f48bc73fee804963d0ee506b1a3871ea8a63251fc76b4d3c5b0d7657454719c9ebba47258dd59a010745adc665bb25d24f06fc4f77ffcccddb842e95772b21504a405a05ee617f704b074465baf04d3a8a71593b381f6fe8a7bd188a5722609e8fd8c705e49a19cc0e212fa07d8a11c88fb35fa7184c1b4c2f97cae385e63732290d55e90f80571ad7db7c4b0dc418fccd554aaeba17e6acb4f4de02e0dba8427cdbb9d0b5d357aaa1db5283f513029b9de8e5f06a95d3502774ff6ff72ee26edcc284b742052cea188c77cfce7abeb4a38c6b39183a2fbdd78efdc9672c6f06eb5ba23e5a63c45b0c6b9adfa2d6603bc59a253ac444dbe13aa7e09bb4f0cfa25595417ff7312b4afaf87c05d4afb00d731d464b777029b37d9cd53827283e0532c461a14ac0ef11fa39a73650aa5f0278eae41e9f46a5c811dea26e650daf5619bdb67886e0f8f060813bed49bddc82a762b0d3324d4e70c2f5b1e9072320607c9ce5a00b4a8c47236d028493b11730630a0dc0f95884042e38c96674b8bb1e252fc184d39f7dcf7326ce9b5f9c74b1e28a3192a0338ae30a431c22d7225ca259941e295c41586dc7a031da7d660f3d21527cdc4553f199c25d0d1a50d511deef928e5b8913d38567aec42cb7d288c76e67dcf9ae5183b965689d116725761d0fe255cb45a15218ad4a8983c1aea288beeead913d0b51e4aee4bd23253d96a03569b4217e2cff8a33667af2c84084293a163201cf74cafd561c0b99e31a0b51725ca96d0f13f263e47a13dd3ab1b0e89ed314ac4e4328820e77f08db72e2900356ae171eec8aac55e5b6217fb8a3e92c368de9310b05c5c11ebce90b7269762c34585344f8a2db07283a3bb957f18a750f4285414a5af63d14d5c6d201c01d8974c56a6e691d1a0d1659dd8e181bb271f3164b6e97e831a763f50e32726ba4f45cf83266e81134318d4f9a4e333dfd49d8ab2812e5c3307eb977c59e5c977e410da6f50e79d0f880f33bf957ba9b69e13e8f944e9ed9a2ad72f1c9bb72a0235c58c142748384fc10b7128787a374232788d912149beffb2a5ecac3e27203466dbd15fea0cd3907a0a24fa29c7c97f17394286f63ece10fdc6a92e3a77296bf3a923f9dd8a1672e9ef96f61e41c24c754d4a61b6f87f141042439aca111fc967e6676bc4fba3fa334481cb8a15c04606b73f85c003298ef93f0c4f93f7237925382bd9dd8e45c8ba110eae52df909c4ba3954a46354b0a402f3e9a0da22ea6ba998652e9dca7d93b11077795ceae41a4e955a964b715427ab500bbd7d3d67fcf4dcdc3fe6bc9b86c9371187a35661dbd33cb03df1eec0deba9b8ea0647296e5fcc35b1815fd99f80f17bb69475c72e4654f6b3ef80051ce97c682c77772d30bc9ef1bbea6a27667a1ad8aa01c92df8f5310c51589a6a8404ad4e09011720e6fe64769863dc00be83904e798222c65fdb9b709de37ea4b786a35823c3772073f89724079ec76beb8aef489c5ccebea78584296fea82e07bc399be62c5a72668ad2844b7b6cd023e2d64d5e1d19579071dbd89eb926c4ae1d913eb761fd729d12469dc108867568213634202652afda1d962836f056c80091895fe2a5e272d6d7b13f6dd1b9fc49dc76347067e31bb1f3b117aaef408ab641bc3b3bb39d728c0780880a3545545f6421bbec74e4f21f092bb83e563b46cad0a9f649810272fcfaf4807d986e25805f10a44cbf7e1e26274fb5be81a62fdca9d7fbc4ea2901238b107446098ca9312147a589b2b23e800349da7e7e3076669b2e1d5b64dd72ce2a301cad2e4f5ec4ea7e1aacfe67ec72aa9618d375d5e7ad94bfaea294a072a402e936be023368488c518f5fdcb4a650b04296bb20645db95ee0dc5f5eb07265c92755eb8d0bade2f28802b648fe5acea48fb0cd11783c0af426674fea1c178d4b05065a9ed9e6334cbdfd074ce96051e9ae97db9f9e1cf18bfe643d847b54b3d1b782b06ad5797471fa4f740c777fe31fa56bcb354793d43b4044787a80724b218218522e6ccaafca96648fcdff6f7a5c35666283e0be21fa7ae95675fd45fa5fe70990cff9f3b9520c6f29a59f04af46ad09eb5e1744f0818de5075a907210547dfb50a7ebf071331726006594b02f726261f818d0b6ed8be56c03b1ae728815a9e8a592a69dcd31bb2cede03567e3614bb9aae56d0550c21601f4fa1672cd3071da7f940e7ddc0624f08433a73f9792e999f61fc8a04bbbe7239296fa726596f60b0e793f0b065425df1637c5f5dddac265dda12fdf5f180b2d64d39c7218ad87c9adaed4dc8017a838b62035c9b21be987f6cf3257385054307f06f943d059cbe8e8497fdb857e29f18fbe565161dffb8922b1a41422e63dea6ba16a4b6fc6e18ed7955059905f6eeb5901bbb69ddd38c79f02567fc543866b7df57972880070b109f3bd2efbfe5fd6a527413c8d21b72ec135492569abc8b4bafb66724baea457ee7b6f0bc3609d5e6e519c90e215d9a26b12b9cb60a40176310b9472fbe13ad23dbd0f3503efab30c24decb55c43e18fcb4c7ae55d462f7715cbf063fbd0bb30baead57d6774f8455ecef7e7fcc06130ba472701f6f8bc09560d657298db93e342114eab8a4ffc888d471ec1fc74ed39dc43ab5381da8c4bed624f723ba42d408e35e5d7700a6739f3d81cfa0134a8bab632b6a624bb3283cebbaa72edc48b3c7a562014f603a113a71485703f10ad360d3e0eeca14f4b719f3d2463b88cc7c29790a26fdeea4c9f27b26c96d9fdb2f66949309aa785d963a3138b7295d69f22ed8780a2ff89569103d567bbc0560de91a856d4821e6994a7b437d724b4083fc37e0768219c1e6d94d28c353b5ec77195b1767991dedfcc2a0ef0a5ddc07a2387cf6bb9b16161e4b2cc1e28ecc164bb6af43c4234cc7b1acdd1236728890151920460a405c084e524d3501ec031e9f20f8be26ea480ceca2bbe5e57208c38d2a7d6fc8220f12cda007ba31583354dcb51e726d898a3a744ea47bb82afa96077d17027a06c46a09817ed856a27a868b75931f656cf76d682464ed3461b2b06d30af1e9f6122b6914c7c97fd9a1777ba3fdd320444601dc163cfb2890578692ca2894d5a6a4918766aecdb076aa75cb48aa9102e7f69d281be1281f6720e78002fd9211654c52507f58ce4d29b562df6e7e543e0fd533a19bf7c046572d357a91708152fefb7bdcfd2602919588536f6ddeb95996d63703435fc5dc55e4a1969614e81a26e4d226efb3243213187b5744d67f7cfb2799089a1aca70e722e72e63a4d2fb918da1bb732ce27ac094113802d0b87c975f32a7a0c695b3d3f191b7c19720941b03bb9f97a42abdba4c6eec7555823a7b0b62a6be5d411d672d6947a1defc959c03204d70be9431dfeac9d49edff77c3386a28f4b922b2a40cbe3ddb7ad5da7033f7018a6534a34d708575381953535e7ed8c51e17180d9e72cbfcfeabf7068ef1e8ef5c10ee78a38506f3b4b2a902172115cc9b3c76d51372e42f47e5336a27c541b61935e2a208903d54675c7df0bc39e4f57366d059f8723e39e203590d5b7949829fc8c2323c4c71f116720d2e3c286b7b6840c1e347729a82dcf69e928b0d03ce7db6ba812b998c1f0914991ce76c241a6fd1ae8eef7217f537942885d7e7139054a8ee117eb7492db56dabe78b748d88848ac5802272536ce37e471ae8ada8b310a722cabb5604311d7aea7327076eff855d21aa6672352ce4425ce5e81e4510061872e258698704ea207da7ff98e555ca9b5d082972e50e9f5d951728c43bc8709afdc5d002aceedaaad322b332a69ca66d06805072359a1874193c3a3018c23f2f282e273ae65b4eacdb3a56428da40acedaf6d74f81e231496c8608071410718cd9e0c2323c1bf5167740744091ff1bc2798ae0274d92e67dceb8b80c17126005437a97c84adf962aaaa46c2ffbf9c064f7ed542475a6bd1a08377048d05eb9fb3cc16956717fef5dc787a77df3a280e2454adc723d4cf2859f3f3123d9cbeadfbc4e914bc0267a19e34e54380a6e3e503b707072a65e1ee26d4356f7880c4febefde6051ad956d1e5260606f622645e534fc5d72d15b0e3269253e910700e50db88d9ed36c7811287ad5e231af9556ceec40fd6433bdb6bffeb3f6ec7adac1739a5ae7cdb3d3ed28027636ebebeeaee995e21e72f1f004800ca12a0599e4a57769deddb0c7ea94a0d2dc164ee50df7606cf0ca7251c92612e31d11f8ab0d889849c9f03cd2720677492ce88569e245a55f2b3072d189211dafce7c377cdc91871abb4968869b4af58d617215cc761df8389577729a6f1b890714b41ad9b26e6361179e1113f59c464ad90a5aa2e88b90ebcaa415dbe7dd51b67fa29f7d405dad3b85b5a222f6d079afb4e0c30548543d5f672f545d450b3f212e63f603cc0a128ebbe0f013db93e6950c1ecc361ca05a7871dd24ccf4aedf5f754cc2ca115b7520ecf083e88801058dec7c5f26ae805b2621181bfef3f693702342c046c553d14e5faa28252a3bb0295488bad0e9eb233f656272d80bb7be102861494f0326713c91b09149d12041c9209b92d26154efa90fb5728aca004e3912410de6ee1f8966957e1e835cdf18eca8a307cdcc4d68797c48693975c285551cab4451a437566e36d5eb1965e4352704041d4fce882e1ee1f0291c66baefd5a051febc84f30a1264c60fd089c005baca094e4af7c07f03212b2a944d79551bd2ef329a7bd0d46675c8d7cb0ddbced6e8af213cd64eb241d0a863a1ef1a7f6b372e9d7ce3dab722f5f6af7a843e929c5498c1af122d05cfe686247395d5f6b3b9382c78cd22dc0ce0952867cf0ae5dd8e964a931bbdc9bd38bf339138270f6272e523dbb4e4ed66e07b20620797391c715567b2509119121eba728c988bbb913b4e95e716170e2e9ec18fdafc4513d7993c9201c1f1f6c36f1616147a0998857fef0e35457d0c85a344dc635cc4807c8bd6aa9f172dddd5ae961c0686c658d02f76086bf5c6075f7b7d71dbdf7d671b97b364acdc32df56f9414f7190c20c1671fc0fc5e9a086d866c5d5f47744513c81e65f2b112b6e99f3b272c11b9245861d0da82c22def01fe09f9f5e333cdcc750cb31350b1b34acc86572a7a69a9642a97b7b94907e40dc6fef3afe761faea0bd6300ab057a1c467aff72e6aed0dcb4a9421a5a2d496d506c9d27005a0f3069c91aae46fc4d00f4948972a5ca8c7e782329ac700559f9a3fa0c717147515d55961712960fd6abdbc220667bb3933d4dd86deb871768383db49d8970f0095b76514e73cbc6ca6c1b348525f3825581b3e8bbb8dac7f0203014c3b38a60d007f839289ea8d92ad1bf42c07235aebe422646609e70dd5382bf14b7f593fb25ab3ec4dd6f0c096e14eed067727cd1f25b246ad3c36ec214a925d561954b769cbf3707f282e868e2c80f61dc726d12c09e9300c6402e20df3c1372ed5d1dba55c1a3c02546fbe154bd05716a728b37182fa77a8c9a752b17faff89bf6f79a101c81310ec897d1037b4604f4e729bdfaa9aef5294671993926509a408421c211845d814fe3e0aef977cc45bda0f824363b83711f200e307f9f63bfb2d2b1ec6bd80780ed787352060f68f5ec2728465c9af1470de4efd4f13193741c10924f53d61d9366aa283b0352a85c31538567837b1d50c393d3a318207b53c947a6bf9c5ad583ab53e49977761015a4372374066ed98da8e9d899480010c2de9466810c28a308f09e1dd906575b7cc24725efb784b34c3f73f9090ae97d514b5963ba92321aad65f8dcace5d7f6bfe736822ca639c3919a1754a3e9c33c517c854a1acf653fc75a79758cfca286a89f672cd8b38fa6dcc3241238101d6c1b918eac996c2dc10afeee0ec7bd0cf39ffb072b9824a2169413612b811922b72df043d68d8394cda357e3fb789fdd6fbdda37226259bdf6cf8ca70fa446ee69e21c1da6c569306f5f40d8b1ba2d356fa302472f3933d090ab843d9c9dbc2e43d26cc245325372c8b3b37f5708cff5ee969ae2cf35cf236d654ed8c5199d3c0762d1f45416914b9547bcfd9d930b72e9f86da472dbab6064b84d72e5c09509e3d52251f0010ffadf6a222e9486afe270dd8e17263ea7673ca358f288869a1186dfc3d4eb7bcaec4e780bffb426451c3cb2d4a7250dd07dec8a36e7e51ab5f0e9721dd4ddec7fda5cb707564fc8bf991d0995627eda7fe86093fb4c9863446f4f0622d6d79332268fd3afd69220b6ed1a526e54f6cd3d1a70474c27f5f15a149fc5c26895fd21eb968f1e6832fbaba35901f48013fcbbc9732c68a9ff9a1861f0fd7d5f5547254dc36ae49c940857a0240449e7212cdcc1a8edf35dfb0dc1910f1e091b5d6bf78abbed0114ee6dd998b2b6ed30153c7c0c8655759945a50308bb641c900fa003f55ac3d76c966a2a39fc2e43e720c56d5782e80112485dac1ff8aa972e48fbf807f60715ab18baa92bc02c6ab72339402dabd83d7a6feb593330604c21510dbcf3abd3c33f1365079aee1d81472fef69902bc7381813645f36419d9d36f14ba3228fdb693a50d5785b24a0c9a6b0459dba2fe1f70474cf3a9cf72315044ee04b4e58a6940ffd3d252c61279bb6df2f942ac1bd7792b4f42a566e1eada475194f4e071f7c2b00cfbc61f92ff8f1b98d5a540f3ba169201f0ba78d1f8cbc6c05deefad019d7fb40bd5da8d1443b52fa544ea0ae8e6e8fa3b458ef02a1fcae437d8464c3ca7b9ff8cfc65146fa46723fa47317867a916fc7933cf61a51de42dab01afacd2c649f14a51e1a7c8f6d7238d218e968198f7e67516360e15737dfeab14fd69c5e2c67517638cae1ae7633ff0c0077895f82f6adf7292600ed3cdd7e43af9104ab4e59ff62be3e2a6c662147abf1c14466bebff62e2821719cb03e043e39ca4fdba562b1feb0ac055e5a4e83b14454ba8d4c8942fb903a1bcf5726b29ab4a03274097ad40041cb7656ed72388a39e600ba406bad9b78308cb570810f3d66daeb9d81a136c8b4f0880d4c72d2b8fb671319c01621fd9d31d40b6713f8bae70d152ef2e622723c51fc796b6d40c5e4b04da2695ae16824c21708712e5210608bba37c57e991af11e6a2d52455eb73b37de72f85fbd04154dc25809414ed643b047bf166ae35d21b9e45d6c04a53d040b82ad00f01f83b849342c996d5d1baf735f9d4d9d81becd3c5efc8343b593bb9d38173b0942947b009560e6fbdd799e36e8645859281a2c86f3545b7249455ee94913cd586060ff55a4ee49dde24c6db3e381b4630f90619e229b6e72164397a3a4ba4d40c3df3d1ea83a02eb1f4e09a6b65b26279b6258e0ac18b572e8fcfc58056586b8f08c1060e3d58a35d1af95cdb0f487a30a2e047e60159a5228dbe1ef8c8691104d061bfb4801fe49660f5d2c40aac288a63b79764cf58f12be78b763db6f019d0d04e4792d31f9e57f70fbb07d2ac230d07e1dcc5c382c0d5f465de7dff2adfb08ebbe8940d8fcd9f7878d9e0eaa01df2648711966c47a16a08c5ce2a44b5b15f52ddaef98edba3e1dc1ddc6abe29744964e91f22801083ce5d9dee9db87d129eab20a670d8a943db99de550873cfb152daed549ea1d3b72233db218bbdcdde3b1a246637853df3affa82b408bc7192b182760b86214a52dc4c779977243aca7b8f88ac23a798c89b9d34f5684ce9fc53e0c2b9a5f4197723c5f14f4fd50ec706724f1b9eddde9fb17d6f8aa296e5441c954b9b87447892a2be215b6bc76925c9a7d7a5ca3505762363403d0db81508e65a130771828bf720fdf5b246465887010cf760349aa2548181b1467db66342784732639a9025b58efcb16c2b45575b3dedd91e0eacb8d8523f31e0a7f4df548b99feaebb6c07a729e3a65bc66f2726be9050df6118448cad941e6878417ffccd9ed08bce29b4272f05b11ea5f4a62b7443cb456a070beb92cf219bbe40496cf07fb3f40243bd37219fd4e9233597ab2319326bd4191c2db6a7f9aa8a6f0dca22f542559ee81b97210e06ce7cec2979e82d039d9b1772923fffa35e835426a56f897c5d240a6ea72b093f1a8544c57f9b2ec3752190c49c2c11814b9da9df046faec8487ac7287725b237529806067d560e6ec1ea70334eb1909a25384dcf0f5a030a17885766172cb416b18c5a9c0a6056daa7bc0620b70cd3d89430257b75633b8169920677d7265bf26d2052ef10b11b2a8519030e642f749875d1c880f03a42b6165b5df8c72e9393d861da03443b4d1a4916104132e9015b7c9098aab8284cc29fe687dc46d952c0cdbdf3cc91dfdb7b17e5afc13d824563d34d76a62974ac0bb6b707939729003523602d359f1a04305a9cb00bbf41e08619af8a0cc46497625eee3020e65011e33136e525bdb626388115db877e1c2457d69a08718d51a0ae4c22e706c5238c1e430c9d9a581400b8cf2ab8b39f45d3e97cadd0e4859ac0c4b79470454722b58b42c04b2992877cff4e6f71fb2304f45d91d8df0d6b1cf17ed46fea68472b5a3e864ef372f600a12e4b2216b4ab16844f4ddd7f94e0ed627ea948b851b0620e7955392d9a3f0ec721d55026dabc2f1fbd17418ca98569696d0a2078b636e4ae4c2bf233148363c3031d72f09323fc4a81480e08dff9f70dffbc43b0c6c2768d82ca2090e88fe43e89c350adcafb453c2e35f979863f1e0c03232f5fca83c1f7861342bb960c310c84ab4833bf8bcc48cdf105bce431a321ec393a9116828ae081cbfdaa547535315d4d35884b9d631081132e596a5643d1b2c4f65b7d92bc62b4fe168b159e0444c9fd3946cc36ed4b7fdd41a9a45be33a0e5524043b0726d9d56ce99c8cf7bd5750f257ff322b07f0c1c2c5d4513864f99ed4d6a102a72d31581b75c75f32eb359ccb952269f3884ad843e01219e86f07e9baba5334772764f5351a147d449a4c46173a61176b7a6caf36e689b9892a3c4872b01eaf3079f6c96bc0a79300e4f0106bc69b72bab81438f51242564d6699c814f6b66ea72fb7216caf894d667a4f0a349e83a0f7f77b1c62f4c2ee731931cb48585fac65e5bac9e5cd98415f933a38fc1969421b756ca24e8e6f3e266744aa5517ea24272afdb25ee4cfadf3b18c0256a2f531f4183b4f3a8fdd17b16bb1699b02ba0507244ec14fb95971e49efad97425083f36cddb2173afa424e685fee559b510f157249ff3538076cba755157237a29f62ec2a6b497a6d1af884da585ce4eb3ede27293bb16f4956b6dbe357a05eac83fdf7b6dc5fa609182136b2b2cfab938a3c706e0a704fb8e8badf67bd40da2aaee44b8e1d95dce44c9373de4470a1d34ce8230a6b66124514c715008dc1f167c55b5aae6f1f36dd5e5fa178ef70cf5bc6ff872a65be05c88e7249ce5911db2ca4ea783a6d44837c1964a1cc1c6768a01962472e0da692e39b69e4add7e1f6eef3aa3289f5ccb1e0d64b9baa8d855fa4aade872828bac3323b2e5c28f06b4bcda40f1e4aef314993646c147580199e31e51b772bd204d87a90b6602ea0aa5769b01c630e08d8bbb43043a9b26550d1557a7b563ae0b23024a89dd1081f0ab00aac828b2e13e9447acaaf3d91828dc29d866e53394364252e0cbf4ebae4db09e790deac970806496d1203401115ca18eb5a3cc721875438dce578f0ae69a489e1d31049c377f2993773ec0e3e61b661b137fae72930799e4456097ed3f6e0e40bc7e6cf88a49476a221928e1406bea592b2ddf72f11ee653ee4736a350a0bf6e5d82cf274ee352c38164afb7dd1daa062525730fc912365e5ea5373306f95497111f42698e9e03d4616defc1382d07e4137b7e13eeb1945ce837df3fd536715ef7c558fa403425d31a9f6f1f3392f442d4055372ca330385e479d0fc4488a09855120069d67fffc5f8dd8d4146b9f1f99c0ad441e0dac8695326a34d90a43f3d64e15957f269dcadf8c66a6591c2fea909289d2604211422ea2a584adf9008447ab64ebc0229438be2ac2c46ea8d150c120a04284de7e075e181f607b8b04428abc0fde0437ce3a3b6e75f2a7caafaf70cef9d32958795263259255212e94116d123d4ced9b5f558df8dbb76326736b29f4c240db9fab2e9d975f2b74a2206f717e7b0407ce9c9835bb7c9e692983862a9763c3d2adf97dc8357d81c61336ad8c65cb9345960cda525fa1450df8706692a0d2c06671277dd359415e1b2f093f7286782550ac9a82f8ea3a2fd6e4f0357d1d5a1725cb6e6636f1174cec01d1364d53990c394633f4a1a9004cd8a28e10003af440fef11c47b1eef5533b03a69b5aecb455340a8f29aed95d74674e239a4744969724b68adb473250b5ca6939fc5a8eec260190e183b0f7f852ad27fec9d536ed6573c956e2e0e73425c830ab48bc4c3131346685a3701af983077949d428a0dfd4a0f401cf051b4ee1689d6effbde04b3b2d23259fb5c4cd08c8bbae4f67ca63472cc30454c422d8ae82271f493c46cea8ec2ef36d4c5b20883c6c6233adefe3c7244907a1fe0f76979fa06d88adae282ff7386886131be791935285e53e2ba2172835cecda34b1b7b5e0a81250cec668cba4718e7face50d4140ef378b1114a60512ebc70f39abb0e608e4b81d4bd6bd7eacfde0023ab8499bb452d90735f2da72c999c344300aecc3ed741e6861a86f966893e99eba6ea99410f2fde65aea7d721c5e4553b8c64a581687f7b53608bb9b98a62a73cad884fccc356c5faad39072483eb8fd036743236b94474e4e6cedb94d31bae59d22cb1f631530caa971554ed23364d7fda87a660cfc35bf1c041068daae32acaad0601e8f8da025a96d757258d92c666500924e76f9364e024a98886eafce1fcf6bedff9d572014257ecc72bfbb1bcd0493df44e09a0d861f15c8f46cd83df320f466cdcd499ae3c29650728eabae85c09d9cb1546c44f979456d903bd0fa41f9188cde8b6d57c7a2031f7223823890ae98ed962189668531011110c90821312d3bb21ec73ec245659a3d721212c20ce3717a7e8416da8f138c217bdfa80677f266b433b55b27a8fde148727c797d5bb8c457e1431981ad5d003614678c1b3284d6fee53a22890990103f36ee936797d2b5948a53345dad99a1725fb625bc6cc659ce027c19a2d93ff05437f1239126053e0391a7fe08c5e30350e44a1f78495ca3758dcc3c20cdef149d66f41c9b4e256c80cd670d5e1fc2d1d6b1a8ed94a40ad7e3c3a311915500d61d5fcc13fe703a1c2e3d854c9e82989fc71120b54832c480f447d19bc74ee133a841911ae024633569f6095f55f6c85fb091af076eb5a7a288455f095e31b2a82d384fe5baad2ea23aa16cbefd0468a489d1566f57fe776791b8845942bda173cf4006ca7d565d6500e3202ed755b9c5c84033887cf4d89c4cd3af10da0e1bff835a4442483d321391d03ded32976cb2ac0be2d7f7049e9703351c98524e4dc70a72678fa7d843e3b406388e1e77507ef3dc1205ea6c2e747667015487f0dc1ad77231bb8500b14524600c2c8fb6102bf1a40cdf93fa855888f51f94acfb7ac6533a363d929ac59420aef7e26e53167cd54321e7c1dac5aa89bbe4c6c5508845d67294f9d296c2591839b75dc69c8fb1c17ccab11a30575219ccae1e5d6f48afe872c1b0f438cc47e01c452866932581285660c9e8716e27ea570866416ae85202461cdb47e7b715e2ed839b07a40d2061d79d5a7588b1670e0a24c47bfc7d9ece72da2c3d9612f1a7dff5d607bda7890365c7b2f3c5d034103d27e8038e069dbc634956f21004c09e0ca0fec5e32bec7f8b724cef7685961f8f6f45d94e2b00c172099b0cb8f4561ff1d8c4eafb56b3884e6f1b504be93e4d6632ff10d86bcadd194e83def7f0d31d6ecee6706acf7af1a9d4f9a4143281a3fc0e22019c6fd4280c3ce6d80ff77d2cebbdc9d1d842232e2a64bc763947965bcccc170b229b508a72858dd1d38b65c69a7b79ec0cb17e438acf76e7bcbb93bc5ef3718a90b65d396f8ad5d2a32db431e8e3f9fff65f1e617ade86068f2a24cf0cc25234ba02535e7217080a4ab64765f55e3f3c8e3df3873fd1befeef1a2305b7efb336b69caa7672509286f3864ddeb8c5476a16400c1bcdcf40362d2641785ea6103f7fb9ffc402878731067d0363bc757d550acd95fb30cb8e2d36133269dd292cc4c5e52824084540ef59dfcea4be6da27c282a416fe991939f23f92f894cf094faeea2570572e3689dcc055cac0f94776438963bc540e06acea8c9006e4f3cf6d0382a418a727ad5123809a17232cca482c89fb941ee99a8fa1c622fbc7ec00c2bddd34563721f6b8d333a4c2282905391bd7f1cdaf96f49a7c9245ce5136fc227e69edb0614a8e6bf95efd9c54621df3396ab65e11d802d75c2eef2fac50d409d823b5fa7727166ba899d52abbb47cd50bc4b24d4352454c0c5bf78ff5918f6411bd36dd17214b9fe64efa8aeb734f5bde0b12f6292f670cc78811821c1c440b016b69b415e1d801947c1f06bc388db828b6a25cf9eee56f367581d95f78efdf240b822f96cb261485505307a07b9232054993fb538b9fc105c16684458b30a60b60986ba5bdafb0147460593f49d2baee44d78cd9fe29c77beb97f381ba988913357889940ad77454cdc00e643aed3f72b659449d9a8d7d67893c1c75588e03999f2cbba72b6dedafa8b4fc7196ec29fe1fb76b4803d3ba54ef4122584b840b2089e81bd72a07a8ae1b9cbd2a08e6dd0880006219c8f523d2fa5055275cf929a034c84521e1e394a4b0d256f5a7a5e39bfd9d59969792144ab483a43df8f64ebc070a3b30dfbefd6500440c114550b52cf5ddf3444966413d78a5323882760d73b9d90271f6b405a074d8f45ed3770355e816ce1769b7073082ea5fc1c88f4254b2ee9fe727ff103692cc35199ace9875f550104d72c30de7a7cd2ecb52fea739a4d0f5b49425a06e07afdfbbf3b4d6a638a912c7eb81788b7db29d4ee4b534e71fb9d6172f7bc0e14795c3ade8f52dc2605e68b02adfd303309272b2d5635f9a3325e0e7224832aefb4adde8819bdb7cf56de0c043e0f6c7c6235c68762d3f34c3a30a72533c4ef1c2e7e606b702d14b9fd6dbfd87b7cc2915ca06e9fc784e333b3b448727826e59fb99673fb3d24f0b62c8d681113c110479e052d3ade7db7f1be6525726cf64fb4ed7ae17d6c5c7e66cf55388e02d81df35442a2180f5f791cd0336e72c64ea6e236ef0eeafd13e680dd7a8aa3c59341c94e29e6292866c96399903e72dc95c53878a5058c73501dd067ca4682e5d09767b7ee5af4ad2216412cc38472d8118c4bd5650146df676620bbe6ca21139cf169624548cc5f1882eef98dbd72d2f6a6f8c2ffe46924cd805ac8e3fb24aee013ed657e42dba4cccea9ca12eb722cf8a2ff48e82d1de80158b46e6fe73b90913600e52ccf26fc150de91afd2f191443edecfc72b71bac530ea86ec3f0731bdf69f88ca63b14916cc3966a3abd42f5b67142af299fc3f7ae40949caa6a4a045fe7a2ce516af794895e2208c9877259244476193d94aea6c277f770d9a0a8f74a7ef102337653c560477ed050554ee260b3c2fb67dc641ec1fdd781535f2d75a47479d516daba399871d34c257f7227b0ca3e7c5a916926accc351c546a2a70611e659554ca5c8901bb5f5d500d72231724a7f0eff22b488af2f34e5912b2fe22c15fe70f0bddefd206c60be6ee00cc54d12b0ff8dd63c0bdd741d29018cedd9c7126e4d43fa7a410521e2d2dab728559fbca901541cd0542d34eddaf5edd43e23b7ebda08393fcd650aef13f89322aaed752194b970711d477b2d5fd4813a37d64eeee5b120b4eb2aa4da6bd9c72300be0ffe04d935c60dfeda80d370afb6f84fdbb21035fdffc03f611d6e1cb68dbc673d4bea6ba98c942554e3fc007a371b6ad3f77b8de1c905ee79d9c4164242a63777723ab8b6687cfe4a88f0cca66bc4a0914fd7823801d2afbe7e8e4f3236670f4c73683feeb5f4d792228141434b67cbcf2fbcbaa31f4e33e444c8c97407239991d9a4218c441101d0a128d6d914f06ec98c94eae408a3e3aaa567a8020715a22624f707d49087695c5d097dd9c53320a633e0aa686f66fdd2924528242d1bac7bf31d9f0be5eeb9f44659374344ce912760b69471d8c6d82d587c31d7269d1881da89284eb0fd71f9ad80ec47d219ad536b712f72eb41ff5f7dbc2cc725ef5c0b411288af88dbf0d512fbfc4e67983f06c3b0b2abb8acb2f9fb3fa804e9c29b744910e4c98abc703248e933ddbdfe296914e48edf1cee5341b58ffa71c3b867dab0c09f0c2f2861100fc4c4fddf32fa30232461fde9f5e5c4ab94cf92f184f8ea8f95b97c85a9b063b50aefef7b4915af4bee9c2451cc579ffaddeec724f72cdaff7c0ad933a22c32875844e587a6dd3140b6bfe703e00dbc8c585d4338732b3921af6b12369a2bf4b70ad96612b7b9da6423c72ab58210c5aaa462b7212c2a666922b07834ab41f6f0a94863dfeac2415b2480bb49a99769b980cf472ca070326d71bbb93f05073d0b6b23a4a31548f6c94f3dbfce453a43641cd48112dacb3581871bbe139a31efd5e9e9a82f00de12e4fce6dd6db064f81db98582fac255288b58a944330ff37267cd7f2c0e2e40145807e330d3aedfebed9da7d72587bc8776bebc5c89588860fc0ba378a892393a110db541224ce536aca7d1e72ddff2b2ec742cd00b2106e5efcedeaf981b7086e51a15186c21097a1fe63fd72418c26396fb693f39a192a10d01e26b3b43f3063ede58deab5a8043cc4ac0372e6f3e0d731b7e8e2a30f1219e2674288591ea9c2c56a442fb8a4c8d1f3e89a729efcf04720c7b6931d5742bdbd896baff1f1fd8c5d23a2b467d35e7f25f1ae722ae2c7ce5e3a0e05878167d3c6979508867456b67e87876acf3afd43b1b160720ac02f384f04af068159a561e4214846a4369884ace41b44f5b1caac2f46a172fa36e6a40146e4ebeb90dad766bfcb361d3b9c0d7e868b7729c62f4e70e5bd475706b634278f0b91ba8d3669ee900b7ab43ba76f751d92c368ef49993c1322720c0ea674db37278aafeabc00cda41e7d5b0688b28042f169e4cee797c86f297249a29015490a3c69e913a194ae6f734dd469c633f6166494a74bf38e4682b872585f98357cc4f6cb674d9f3ea7a26db08dc5a5c8a93b5edb77e1ae9cd941215bb0f810a1671ca00ac8df5fe9ed6c407af44aff00db38ca7fe68858d49f1dbf72b472338bd58207514cd59ee70cc690e5634c88cab5f67ba036c3aa8d6b6fdc721d7cc0efa3815de77d4fcd10c762ef3a98ff69ab6bd9ffa6f1cd6d0333a2bb34dd004a61f6c0ad2f8e25ec3b6c6dc56cad6882d741ebbc599a28ddec57b2357206849172645883c7c29b442ca2b6ceb4036604eda191bea423040731109d717265c882158aac77601bf6c048be3473018678cf24efcb5ed55a0e5525be5414413e06faddf34aa95e3f2bea1fbe4c704ab0b1a03e9223fe0bd4d218884c99c17204155f0bfc1770f34bfb0c2795ac380d4395b8b8122a881ff0c6a61780a5a872678c41b90c227313343e4ca00aad6921f9ecbbb9344e64095cbe0ae1268d5535ff0d10c61ba016649f474206e047b795c25058cd81e0657e37306bf70ec5a87237d6998d57171499e2bd4a5ef361ce7dd3e0a1d27d4d60304c931467c6597472fce8698ec20b9f6fb05f5a5b835bb6b6302935762160ae90db7fb5cda7fd692ae6c12cdd0bfe844582b6685ea2072194674eed6697ac355cf0e5a3ce9bba765f1df0ca06fa19fd14a1de7acd4b406c45e33f9cfc8d6e998143d1ef73d4f3c8725d28e0b94f04173c29b248c4d35a8f7524cdbbbfac9c196da48814d2c26c24497a95c570b9b75fc2103fff9851545c75b0e7bee6d712d3df0712534b90a8fe452427a6d7de56417727387c8074b7425c1ec17f087f981a8f98322dc3e304105b5fbb044b9525625e5040a9c73592e76af696f01578855595cc443c085250f1065a041fbf3c00eef254673cfa92e7c8c736927dde819b762e968074876ff3a536ccffc290dc10950c9af14a998c9a16ad29a55d4d5d606611aff345ae578b7f72b00d17f9c140ea79aaa705acc338ad88f717e3484b30d45a784bf4bf8e479a72926ca420fa7d021a3c6bf7bc45869cd8237921295c95734e388513f7462c751324ec1785edb8cc8288f40660e413054875fc13a70e111f1a4dc32bdc4b5482721cffe59a813a65bf4715ef47806028f3e7163a3888b52a5786257c53bd1d0972a097b97477fa8abe24beeb928135626e94f93b2c5d745310f29eff4667124772e4ac3c125bb1f71e22756ed746a508e30c5a87b80b9ae11a5cb9ab38e70e675b86fe9754c14f1add9666ba17f4310120f3c38f0e0d52997f257bc7fbd18a6072529ba9f51e4114b4583e6633ab65655352a417c8cd2493e89ce4cb3c9abd9f3a001f63890cd5c27663ffd076674b5565be7a7fae394b0dc3c781e7178572967227724a42366008fd6ce1bdbe5ffb57b20554c02db3a366760355a01673194b06450320acc1ccabcb78d4d84044ac35de8f9b6b3b9de6bf14d15ec8fe8744987277f1ebad34605e9db767f2c83d2b7814665c41399768d8e16c815e2bac59817235d0971dc27faf1b2bcf83606f9977ef028fc807ff2158319db57b1206c36c7280b84523c3e59b0cc4b141ad9b4680d6a449bc28a7925a10a784640b5e4f7a5d6618b5a7a94efd61f454548f7b4c09293a4646ff729c8066aec201a703578a049e97ed3ea3ca7b72c6a0543a8e3e1d6e0fd61cd2f5057d4de0f9a807007493723e5607ae344cfa1a92641d3d3b1d281617f28f5d04394723cede83336b0e4472fa1d03d49eb75ba31ad0829334ff88fbc97c6d9fce859173e6bc7bb184b08472b009d5e1a5888ad26e0295bf82baeaa1728a49ef2d0fc878835d0fa27612b3725a2ce122f7f0e50e5c530e69433897881be99c1ef2f2e86ff6dd341fe60de572aacbe4062d6795cc7489bec76dcb29386eac2014cc9fabcf29d3ff120a261272b0d7f683257fb4fa626c1580fa729436781aee1d0759b6102fd2d42ef305061a3f098ff41944c10f6a5ced17a06b6e9546efc811d2cf8bf345c141e7c9ee7f72e90da4b6161b4622821fb9c5b8a955ac8448b58aa454206e05f612e8582c5d72c0acdf1cacb7a5bd5dff7264d38f0168cc8e829afd81667e3eccf84246d428726ef41578316eb9b3f5cfd14b21d25b743496422e0b71df05af61f91792f23c4745be47399ebab49a687cbe0f5e308a84a51f81baf87a8a10d502701f57342572a4c62256f576f42239fd0cd1fc404ca2cafe925020b8d13fa4c369249308f210ed1ac950cbee36ed590d00e375acb292e92c0d37b746213c2217708f81d4832fa8dfa308ed6a9205a5eccac11a6045af55d00cc45e43cd83724ac2210b7fea728bd2ce8cb3d8073fd47be2f2f3bf029d2c0503058a15858b1e4a2a9b9b302c715dfbe44257afba47562c46877cf1276d7eb4185d96e7229c49dbd7f5dd143f63127fec3cccff3499486a9665cbd1e55def809b005ad2dc97627daba615a9b672673a37ad26dbf6a653b130aca0dd820268dfce7b3370bdcca438c3b44c6350366d94d68156a4a72b54992bb0d0a382ee38689c3c23e16ba670ef8358d9f73672f225aeed63a80c5c99c4ecbbd4b00e76d1627d4f0f82d15c876fb6441f6738728264b8f1f5c714b7dde3249c8f8f508c7481046c5582c01843bc5152c7b83e5ef904835337c95f9885ff1c1899785ac07ebb46d2f033185bb8b35c60971797728587517ffb066a406cf56b000b2c50f5580f032c987954f96029496baad3de708f44a00de04cea1763b59fff858a321a4ec37de56f8fd99d250e8790dccb2172cdce94d0eec6a1700a8402cdbcfbff4041fecbc95bd1d35c1177c95237b046720ea243f15640acfd3b0e6265e8bf66b2c78febd075332c235e442ea37ed0497279565b5118cca190486af2b704360271d1cd3d78ad13ebe39d44b4b5ec08637275379b5dcdf55ee5236a78bee6c75fc6b5b4e1204df7dc9ebbaf5ebae1e7116750dd5112c9bf7691dbb1af5fba03e791d78952637bb3a60e0b7012487aae3d725c7af3add0dac55ee9d28a689f85d96540db90bdd62ba2568abed13a5885de633d6e56da582f3cf85e46869df4d2d62424b8dc8e6c925851140e5e1212429e72177ee720da79579f25ca021e08d80c893a6783880d419867b5312948d3bf2a1fac0e2c6365b92b69398e25b85a22ac85b2d4352e1a56aa8e165a9943c7e714728c350fc90ea4d0a961d6dab073cfc375c926e30bbcdaee1b8eba1d579eb90b72e932711b0e6ca1bd5c857f7cdc0fdcd52b7c770fb7432c5957d2ecbc316aff443e5e80f8c726df354df8e3be0b8c97f92b16c47d8ca02665e187906479a65005e5d16b5eb05e178f68e7f5c82b7026ba07714cff8fd7a039d906837527aa3f4d92bf3c43fbc01b576204d5dc68f597d2678db26001b62450c55372337d61b172839db3cd3f99acf0471b2d3a1ab6c99a27229e98ba7fea9b9515fdc93efebb72d780446b7d8d04bc5be45f068065314fcffa9be6fbf379736787e9bd36ea7b729268a294463cbf9787b533cdd1ae7acb0902b836adb59fbd93db21096ea3f2722bf9c7399d39f73ea37f12778ebf42522b78e318cc768295732e404961b804214dea70a34dd082d63638bd3d401055f3dd9b87dbae39d17eea61316bc42560721b31b7cb8f8f429150fd8a8e4c91ba2df4b97e93d631935d4ce3be6560d96f4142af6e32e6c620b9cc006a5800a23af5e97655fde9ea032b85b0906b851c6472440cedb22985c84740ba3eef72f2947ab746f3e2f8489983aa325fb559a8847277b199b7df176ab6e3055bb90cb775db3b1eac41fcfc3a8b84ac03d367df623e1182c6d2c28aceae1f0e2b26035efd28f43d4f0d08d0e3c05f8075c3333eb13ce05c0bdb3d95dc28943607bf2177a79aaad8645023a03a39283500aaf8e2df05dcf758ddf411f3cce47852e98dfbf4adacbc814a82aa3b73ae937944625029724b66da49ae8b55fff84aef7b9987f3d0c90f5b0954ce5d8aa4d8f411565ddf0f972e2aa8e2a8e9ebf405abf3737b00b9653f8b6b0be9414e214437a328d67d72b152e52cf21842ca3ee97e787f6d423f5652cc75ecd347a90ec82532d1c46e07c658dae7bdb9caf169bfac51449e6aa12357b7fd6d2c1d728f78af7ba4a1ce729def456ec0967253608af9e86857880b47b84e4fd905322f77ed3c77489f8272e2b49843fce48f06b3c281036d193a769a58fdf6e27ec52cd51ac5363a662e09b765b31741e44a971a14fb8127174e6f2fb14fd6d393dd2060aa27d099564a72271af5158fc28f61d4d8f46338096ab7ddb26cc45e3ee8cfbf7757af32ca5272c9938afd47f3015b904e5cb6d913a36fbecef864dcef283b863e978d899cff727770a87b161618d0ef7d95f2ddcb212d82ce4cbe5e876a80ecd54c9729186c7293b6c849f9561ae76e9661a9ce60c00c14c2f8997503bda437af5edab5f8e708bc19c6593223616586f1fec3b75100ec1dca51d65b2b0baa52283055fd8f663a267cce554e44ae48f0b5e3adde816b3f5f44bd282d4ae7e23c0116f5d0f648726a496035e4548739870824c27907307a02b5e73a0c955c866b31c78a30e63a01a7ccf1a3f5b32de6140afce05c7f2e29ff89491b0cbd4c320691df67893ea114c0a16320c6b1e4f32a2753731624d89513f4234bc63bf440872610ad6d29a7070539cb255f4702a8a4e4efaf855aa17273f34c4af04ea5158bc1cd892f8dfa7233065441b04ae1faa32c5eea1b83a890b2ad2358603ce12b2dc76ccd94d6da72b57cc2156bc09302e734c5bbe4dc3dcd356e1b575754d6fa5b0b053083140e72c65d1e95801d3a1bcee4fbfa23a283304203e00771988f168c5ccfe0b4428d5b161c0a107d6474e4804d653a18dcdec55181ebd7128753f8ab6467a7d2a4fa6f94b85c8647706836ecc3c73d842c5e60443487c0939ca34b49e1018fe01f782338dc49f11580ec1e2e595dded7a7e2c4fdb54153ebdaebe3e73ef33b0a284f72c051abcd62b6d99cafd9ac85132c4329d3e9bb911538187b36084dfc4a3d21729a30b560c4e3699697433595aad5ff7cb55594fa7162531013f5fb91a2554d13c1177823b00fcfcc0d1ce0aabb48b58060efe0ac0bbd7834235286db8883d872fd45e0e3d66e9825e17b8f13ac7b624f26f6b2832a9146e96d457ac9131dd1385b24a449e074b72f7c10e0b452b1462fc6a1fb7b7b062a11cbc68db2d3e50c57dd9b1b8e232ebbffdbf5913a57166d1b0b0f64ed073b83685ec2a59f96f56a3defa72c86a14ae6f2003b5a616f1478f62a55715f9871fb857f5a1d242d5ca71ce3407a71e8cec22ca7a757a63d29db51a1f0293e5697b43d9f81d2bf346d603598054924c2c178455f1918e21e3721ada2f6c3221ddf332654501682eaa71072ab551d3a6a0ad87d5adec556e9a8233db3a50427735e1100591dca7655ddf563efbf1e70b7d89a6b90520e73f1a47bedfdff56aea2d89cb1063ec9e1f3bec543744a2b1879c6d93b2a8092d6843895750e0ca99a3c4331873b8e08d123c4d60d63cec03fa3988f4b8a25b8692657128c50aaa44449f04cbc9e8576be7b16c71e4e2b5c82c07d85e7f3d72fbc919b3e67c36354b098feafd4c6184a776311e572eb7629ac3ecbaf96fc3d448332acee3706f6a6ee62cbb4a256a3337b2262d027b4a4ec937fbc2475b12aef44824294f2cb7590db6dc6a5562db90e04fe8ab27203cc29fb7c2968fb60f816abbba319eed3df0e8da536110ce7d4e282e9b0a07224048b1559bf9d89dac3064e40d3f77d259037911cfedd688b4293cca6254633a658b4dd8fa0da0c9386108d8f3cd11dbe3b3fd7fbba2af4a0a9693ca620ac72063a1af50b663b2775deb0ff903102677e5d6293ba8eac4592b8a9bc29df29340ac113d0e3131d04d55e7b6dcfa17ad3ea9fb8e0b37b47e69b10d0f7749cac02a58c542be088e26efbeca3e0fc1195b4ee08ebdc1b5f067f517142c4e69d632d324cff96fc187be014a4a235ae57a7572902c7154bdaa3b786392b51633c7722e96af39b0f16207e97b890bbfb78ad967697174ccfd8b700e430e1cf3d774172307414b04fbb3550d4984c3829fc291c7d1cd5d9e29150e8b54a582bc6680872d8885413e7f42f9fda675cbedcbd3ba9fa58674533c8ef0b3bfb71d95bbf9756e659b6be656dbcb0950f8131277f3dcbda0552144220e05985a1db099c7fe0723c28952f9cc7fe29b34b4e9f0d026f955c95207662b0abdeb8f2eed907d4e52a33f8aaaac916764d97aae943f97943c1304e3bd9051511418a9c12e6ef487b72351628a5b9a54a20d2180f24c9acd0bf14710dfd4eb2b87c3e1031c1b37787727642b1b559c517b0095e9d08aa518c053f365ef73deeafc174510c952be93d62bdccb2120b0b8ffd5307a30ea36c3ae4a9ffa63bd1bc4209a8edb8050fdb5d3f716b5ea3455d1d4094f3b00837aa56a4a11f8f087db9d0ba13575508be9fa7725193f429b615feabd4dabd6d5a0c096c655f169220cbf792095ccbad54525572403f6ca48ea8d585184bf2ab502ecc2c21e0259f463a7ac39c48232b1c7c3d72aee5825337a4bd24dac2a746c70562ca32c638b5abdfc9426c4075da3ccca13e3d624884e6fb1002d668f267126eb6c13c1de988b8a30b4310e7fa1af86fd4161e297b59d7d1b9bf7bb704652bf340dfdea6e12d34b52e0d46bcbe97080a3939c343511c16a616691f82a84c7dca8b8e0f92ef5fd841d26029c401577513a5728836651c4fb24e5407e7078c176304abf8a52efdebb85cd13964c0212c9ca50a6ee184db35216d2e97456126e25c3543a488864340157e230cd238771987a616f9c915504bf5b13a2f0ffb4da700b8cb222bdad81b4557881f7716077f4e4072a9f79b5ffa66434e534758172c2ee0382105a5d4a51565f210038114bc53b4724e9798b8f35929563ca9db5b9a61124b7617d2cc0622ca0643c893f18919d5727c033c363202200d92a0f22a8d6d75316383b7ef0b3c95e8aac473464b294b08f4441f1eff53b22eda77faa60aeb9ca7295531f67438891060549ccb172e25729e24294693f831497a067c8144e8cab72941208e41a55767a2a772eaf674127205eeb8ba7216180e7660180490f6d3ef957b75ff81e627e40806384123c46a727172cdbfeb59796e2931526a7dfdb3492be1c6854871cc6dcb4893abbc01813057e3ebe6c6ea4d18d07e45d9119a3199e7a33d89f115bd6cfdde69f85dd45b724a0e471e1fb44ed906dd02e3e3d94ec5d4a4f90a2bee81bd3ab4f4395bc9235452ffd656b1e0bf25e979d8e2c238a48dd28a478da3ae2804208e1ad0c8b90f2c9b1461a7c27a4c8567890ec226065a2e5dcd329fde91b017c107eff71194f5724d4f81d441b465ba2f2f249d0f0f0d438767f139464db8bdb4e8070a54fac172b08db93578b564d07af4fecc7309e94d3a7e3e6e6e8d715a7fd8d2403dbb6072e86ba6e525222553c29a11658e51da45d3d17e36a0acde09b612ab1da7327b7296972869d82df4e340d650bccee5a61e408fae0cb01fcad3841bc0d29992847247f9a71dda9514cd5a1687ad81aa899623ea0928bf7f1548bb8bf4486070ac2802ea6e1fb1159baa589dcb50e01073aca98f4f2b332ae5a30d236c02aabeea722d1647beaa9702414be3221a08bc4a07a51172bd9da891c52f595cb82515c9724f8bc66fb698a795ab4d5c23439c25939b465cdd842bdacb1129ef33ea7db36ad8feb818ff03f632ef6cb2648b5055bf61aaa0c38a401cb9a1e2930eb4361172c359604e37272ea71345c866a8e695e22b0d2ad60b8bb326c8a8eac66c1c8c7285254514d48ea54a7a4ba2dacad997af44aeaee1d03299c4e0bd7a7acff91816a27b80ee26034cc8eb6021710001f1e58fa0f2c6795e3d3f4682cf22e4fe5072e9bfc0ed2786a907143e1c87fff081752ab36b2460a0b6e8d54d65173b67c856884bf875f0a384ea511b8a4eeefaedb590804bfb9026033322715898cc0a351e1962ba05f8ddcdb631b4e50cf43bcea7e3f406ff955a7ad1c9a3c152e0924b6ce14fa29ae05d714753225923570c762e345c8828407885c1eafa5fcb08277772df0705290eaa60008d252dd8659f59941e8989f07d101007567cee8d574cb36b5415fe49cd65b6938ea62892d21b186522794bb878822156acd378f108433f720faf175691c84227296dde993532b10f59aca341ef50eb0e6860db6adf3aba72edee4f14cf3b531dce9bc321ff387ba404faafe6ec95d1376c923989475b04692f31c6b0c1b45d8c7eb480ecca0bde42cb1d50e809c26476e9f8141c3c10fe72e9d351cd3e4bab5d773ba030ad0451260e716d82352360017c2c6506782d7a69b94930b530a4ee0347bf1926b0dec583d312395e84a5bd10d9124b77d12e4572d20542423310db85ae30bcb75232ae4faa38332b1f25d3b0fc92cc284aebca72ed078c14e2d19d1b149db02bdacecd719d7375837320a91b684dee47a9e7b3727745d6c4a91bcf32c9062baccae5cc48cd38f767699df1bab48a2e382136ad725f5dc863de77c61bcad48a7b1993e9aa98307134cce5883d488748161c77b1729ab7ce3e50e4955024181498ea8c7797b412742de692a039754a8d0211ee757215c7ac13b093be03f07aff97cb9252928ef0454a2edf31acf2234bd84ebfe172a1814510db2b022de7bbefaebd6a9e297cb669d42b903f2e9a388770544fd14d483d035506ab503f43f07578f30bab817e38f913295e441830c54b17f8ffb572183e9bc28ea917e06de55ffcbbcf5cbb138bbf2c1c3800662db145a565b2f336fbb0643871d146284269a6799d41bff7697e86251eb447c1cef437a71f85e572443cddf846fceb20b5c61fd70f45457f9ed1def6f8962911f52ee8a02c4db14f4acbb0e7e4746d67eafa66c570f94c9cc9795a760cb8c254124c90913a492772363c77684b299e9f0e56f4b056a8fcd5df8947ab2588e88f5af365347d9c922bb57bb2508c3145af0745a8f6351684b1bb5cd2a0e185ca4688a9af44936aa172f33e78fca6568ee48139d0f8b7be1cfae3f31bedd885e04be7723d3ff306a3391518b7fad65e2a285247afecdc59ac03d99cb65704651f9bda6178d9c587a214adc0a221ded224972a3843fa27cebf4d57721ec9a63d377bc81fd86f89726b7222b351618d0d7a55c388f6ceb6e8c5aefcab68cc236d0317e3f6bb5347220c729305d2db2f099f2347c961b060dfe31b16534334ace0dc9e886ddb0e5e3c417202bef0d5f8b41f8b17175206025a11a20a64989cb465a608aaa0e11791401e14b41882dec02faea93ddbfd8d0f93065625159118925c5e4160abd38184ca965a1641da5090edc45ce701e069a512da59cf50426b1dc4397a8ca1e33ceb20366a36dc1c38520bca93452019a64e7379e9e84094f556b60f0ffce4767de3b18872d9952801d4dbf3a0a9a87caa0261b506621b5b3929976faa20a802576dd1933095db65c4d9d1bb8d930def21d06fb1fa3ab9f3dc05557fd747573b21b0b5d462a26b47195ca6fe02990731ef14841a2182f1e49ff39a82ce754c468dd4b7c972fa15affb0867ce02204345db3cfd8d40e65f6cf439a882066e622e2353e0241493a9fbba1e291b581447a4a7be89a37d9d37809fcc50bf1b2878f435dcbd3b723b8664649c982b286a5b1e71328df2c9a66720e049f77ebb67d5d3e1ab39155b3bf35cb0b3a340fae87373db970253024159ce127d52034dacc51b768e0d8272564c268b970eb53d935439216c3a9b77d97542723d005bfee468191d1dfde172a302ba802e59eaf8a10be40e6af314e259789c4c8a1facfc5dbc55cfaa42947231ce9088c314d7307d95320b8d3661a6cd5b86546d65d5538e039dd563153d72f923cc436e2e4d4ae92791a545c8d9f89f6b3d345f3366bfc67edd48251817427feca2db46840597250fe9da239827869fa5f50413f01766c8179bd8d43ea57296dcc870b53b5f18244e6c287696f355ddce0e94a51f63759f3aab9074000c418556dea67c6433344e733fe4ec1f198486923bd81eb26532c692f4a2b723744764a03a6a8059513b4597e92d19de5608a9985808dd111d923dcacd1a123ee872d92cc72d62dd45e719aeeda0e1b3dab4ebc52bc2b32608aaaefb93f56fd59614eff0336d02754da449b45fe51150e6202bb068ad0d3c92159fe3803d2d706c72032301a274a3fae140fc133a415665639be2333900f244adbf44adb20d215d72310018d6e34735b58faf23f25a4ab26615d39ff64626c9c17e793cd15490fd72326863837fbd502129b5dd6c71527e0a93013a73efa224326146a3962b70cb297058da1f34ea4eb65745969f6caf4fedb8b1b82e7625c750ddedfe1a3e5bc772ff83c83012460dcc3c2e4e3d1261fbee9a66d0448f8dc201f9fec66b9088a62989770eaaee27607af1bd70fb4e6c989bf8d28050928b2c0ef1cf60f608d8d95cc46c7eee92a290ec8cf0a45d1db0a36b646c5b7ab5ccb8ba1d5954854a246c720e32617b727c92e4348861ae306763e7f80786edfa4d6512cda31ce958e8c5723c36ef2ed6f6c1021a70f5c5eeca91b42bab9ed623bb9039015e3986c638073ff16be17f7c7c9362b4f4a5fe60f2729f75161d49ad3500f2e4e48951865f2e723e93498bdc5a928ddc59819e33273371a3d4439d5f0d6c21421cf0112777b972c4c6aa007c02c80d4fb18e1f1cd9835aaf1df8ffb74703125a950a2fcae60072ca421c7710c45651bb52533065511d4aacf3fc6383923f78752f12f96f5ec45218bd03b606530f9d6ef8e054d04df7dab4ac56f85fd34fdc5b0ef656503b9472072ff36af659de011185111decab2ad05a9b50094b7d787086e3bb7e94191572bf99324edf356ff9878459be28020b946a47bb26201cef644eb834c60886237282ac058a8ef58f7d564b9be5ce2f7c59a125c41416ed9714baeb778c761d997289fd9f0698d82356f7304412ece881625ac2eb2075545fe488fc8d7188d18d725de81c7ee558077b9ba0a3728603eb216b2dd22d4c942d15e1be277eaf990326705c4c3beac32cf333a343d7aa850ce03e6b2c45de5d60d0a84942b0ab862e72665bb0e6681bc99178885a74beb4864d8c1d8b825b74c072ccc2806be847eb431294b53b921cf23add33f0fb607aea61bb7c905d4de327b4db0edcced1d7e372d05131f6df8ae3973e2981d5f554c6296aa304bcd711495a0c809cf3bde09a113372562822ea3e558f43fc02bbed648b77a0faa203494bc6d99310d3dfd822247e1a3158dd535dc95bebd98e0095e5c4bc9724af8c6a0606c003aecbda49a425f17bddee139c2b8d5f47f055c7ad70d0bc79c35677395f3c1e1c3262f166cb7255fec4e577bce2a88a5798ab9f5886a3102d6553626f927d4accc4dbb9502072b3c0241fd76e06e7efc5906b32f5e49a126bfcd6332bdd42de2703943fbfba72e0694a8d4c5896d16b3d9baa1e180a0c101bf171affbbcfa55cf5c8eb5731a7290d0aafcb0eefb28977df48c89b21c6cf3b4d1d898a46811cc3ffad306699f72010c55c1ce3d3a6d71fa95fec24ab3e8763cdbb507daff9fd3c6dafea5d14b36a4f8a46747bc7dcd512d4bcac9d2b52ef185b82d2a33699022ee3d1eef1d2972e5155b6b85141cf369f357ea3d6efa817500b7502c751a2bae21651dd23fe9724ca885c32a39785b84b059b7575bb7113900b7ad7c52eb025a93075de5564613ad68d85816cbc38060a50fa9949d445b6652e4b858bde0aeb1fb0c09fbde5072c329137406922868146a55e4c39ebd96579e2c1302fe6d9be418547fc7137472abe0f6706d1f42cd1ae4609e336f0832eb7b2a9b48ba775c849af86c430e677233526078190ba66846b9543ec300810a0510f97ca6fd5701de5a049eb3deef721cd582d44f92ee2669f0932b23018e2d1b117e24944a4cd75b40351b2753f44bcc2bdeda5aeb46dce0b9a0e0fe8f6ecda98785ee315eed2a99a892c9647f094181755fe36eeb5d32b2f3f146602ce776f3bc016c9fde776a1abe588ba8c17972a677c03dd5b30f01cfccd31362da0b02cfbab8d5af6186926f245fd083d08972b166aa0fef196f71becf700b2b5d1b44c7cf98f7b3164ef8d0e31503515b7772f0445345a8d29b32a7b101fa561ce31b12a3d5fe0bd32af310253e6c56634d33b61a4d7e9ac6a7a0d33fabfd5c143f71f78301968424de3f68a5bf987b2e9672cf3e98fed04f8826d9a111116ff9b7f474af5fbde796dd743fe8cc2d87bede727309770e0e007e13139e29b0e5d04c48f4cb8b07daeb35e29bd074be917280724073127b19a35f2064d674dd6922b8ae1bef15c4c7f3b872f3560d789a8f7b06f43bb46bd33150e529fd60de0e7ae2aaa71e90cd9a33135f6714d77ed4412f727d8c15285c3c8a1c3182a13fdb97c01169012dae1b8b119a8dba4c72dcf31356e690f1835e49c82e2494e79e1ff83f97920659b2bceadb2b70253afd28cbe17262bfd5ed5b23b0b3420f67ccfe6de9efe69087818891e1c5d917e068845dc972e1bd41fd1867b153952fd6b450967d8a820297d7c40c37d5fe6278a0f0affe72184fa8245b9ba076cba37c36f946c309a8525e9955777a38318016ed90950a72b2cc64f983f9a5c4cc99cbdb9bab8bad42fc84753fda49660252a77a1730cf339868a6d82424a62931e2968f9e8ab2042212ccda8ae58c0d5e6f5b3d363b9463742a443464feb8f0cdfe78e1d1628b3b47a4d6a5f76a2b04acc12b646833eb728064119f09f1c5d7ac22e9b7b154006aecf27572cacee03a4cb3f236e63b3072aae4df1c65525084533b98b4c285aec1b3590285d6dcebb3b6fffc52ac441172e1fc9143506e87b88b413a04e5394f87bd0c4e6b3f6ce150421dce6428d26b331edb308a9f8571d28fca8420f441b6f530a499387b574a32748e755532a22b72ece7297383d6aa5813454460f75ca853ea70a38cacd841d7e778e43a257f5172493df4d9277e3a96025ced53c98938157de1d689e7bff947173c71decb2ccc72e8c659ef687057b2777c549be9c13150b388ff98f081e854e2ca8f9d5e42ac72b59055b41f85a8bae1e0f557b6675979a642d97cfdc2b8a6c91cb50ba946d3632bf402c8758e89bae5188936f9f8e9466c6b05ff0af183b6b4801b88347c02722058763f5776661e1399c8e45e907818748346a4ec7ef80f2c72829088eca02e40cff3bee3e4d77e8fc03347588035d3dd6e2dff137c8d7193fecaa5c21c5628eba7e780b9a4495c524c2336857827d271e1d33095fe1f923b5e9e3cac33dd72351c35fe7be3f242550b15af1ff94af53e586cec78293fd48ccdbc6f651124720dcedcb20ff05ec56461de88aa29b33a752081957d243f96cb4fea28f9fa4b72a068644e6d38a9cfcefef4d1611f340597859e3e6d309ed37daf03157e5da8725282f4cb4ef902544928c7852f84839564ac0ccec3e2b52e1085b8bc8b6daf72779248ff1e4fe68ae5559a081b91feb807e1fed52c2908eac6bc49c96d880872a6fb47b729b60bc9f46b81f67e57bc916ca739901c548b14d6e09b7011ed37721047bdf48a85836df738d18fa6c0b7b28fd39067999522bb87eb8b38351fec72ae518277e96305067359dd21bb15603af78c235ea573d6b40b63fdb2d57cd970d90fbe04cc60c78e8188cdf4d4d05adf78eeef7d4a8f1b1f9ace1c985e71af72d6e6786116acfb6c5aec80b196b72a1dc62aa29aa0b48242c9d3da8afdd8df724e1a8597419a62bd01b6adc98a9fa6de3515584b659495e3599f665d556eb724b18cac2c11bb1b482f238608477664ba3451078d0b7b87c6e60401b645cc4e724270c63ea7d95ec519f81321ef69e718cc32af64f1decef7b8db129a6f3cd8727f35a3874b09d347a81c697fc99b36d728c82d66f9d6564addecdcc7a6ccda72e9865f35b5bc0769de2a1fe2b2ee6017d609a603a82668e9ae4cdd383affcd4c81d8c0c1f5994fb8fb8fbc38b5cce46b55c263e74634e7e0440e6a546f65d8729678053d03d542a5f38d5afad56cebedb53a375beba3b7ac9b18aa7c54cf393943c4cb6da3459f37c52cefe5764c284c8ba981e329baf31f31b6e5c6cfc76b72d174036635c967518b990f86c9578f6241a94e6a21c506ba7321e15bff727e72bfd3de83de9da7b2f2bf4e2135efc4100534225a43ec8dc9e59ed705cf1f24419b2703677abd94d5365cfbe25447be1f87a029ba0d6c93286a1f1db50cd2e272889a727b16707b3d46b16c4555d2aabdeced9f62a3cbf3aeb0ee476273e36a4652fe6f27ce2063e69abaa399dbe8196be1920ac7e5bd867fe0ad1cdbd72e44726eaff0757b1e10bfa6e3975c4510c691c30a4aafd029e32c901243abd4abd87271558611859bf1d3f095fb297de600e969ba73a8c1e8521444fd697061c0616f44319213ec431db1ea95b55f41f7e4b59608c1b288172fd78c6967ed3a29830d862fe14ec77ac4a346fd949ed645fec5a91530109deb5427c531b8671c3c9072a4101a8d749b714f9a16d4b40edef6aa290c691de7b7b28417d910458bb47c3743dff549ff3e41a2fd3a787813c5b72e63065edfec0f66384e5c73d4ee40e172e50c0100b9c3d7ed363a774eec1c9eac2101975fd7f48a343fc42e7f5c697172e34db7dbec1e4a9656843449010ca32da9f7638c835abb1944024055d8cf7b72a8a0790755243bc825795bcad74a37820446bd84dd7eb637d041ca2fa340a8727442b744638c480ab7cda1d9500c08e3dafa9002d45fc5adf0a45dd46a3f7b72761f7edd74fbf6de94af72788c3704377b45fd6f0d8ac575d4cece19afa3d572268ff3df6475ad5e2cb772f40fef06433ffe27b6a4b7fe73be6af52d880c32726d4abbd9622259e25ef451e4ac945cb1dd345ecfb0c319afcb79c25d7d337361b80b22747c6d5e951c22556af1be61146c1a717f2d3ee5d2e5bb8fcdc5869472ee71ff55635da121d42de1461d83884b8e8b5b5891188f898ce081ad5e21db72ac020ace00ff47c2fa13ddfb408bf50e1322ba9240a3e2027316e61d1cccf47295a160a7e22ebbf927b9091c9376a49431bf71cb69fba6b292be4d713f40767207b2d74bf10c643b9c177942c5c4c305a4df93c2e80370487e6ed78485511f72a97d2e0005a94672083bc6e86c89adbe98ddec5c6a1e85bcd6c35e27790c8972b979799913a6b54e8f17b4e0b0f3b6c1a1ea1d4ab004d020906420a61e4c911cfd4005322ffc0d3d01214e0892998bf8a59285e8b48e6964435e9d013a1f787246482143deaebe338df673343bf308d39c6af7b886d12b4f2f865a6b672afc7223b67f59ae00cf6c5d6a5e11500c8f40bd697c685f25e5deb54f322bada14c7247c874e9033f16be2604ed9af9b2d63042bf9cff094b540b8b0958057522f37284a39b8d65e794c6d219bc1b7b347f2114780bc5dede1a48425a7e2179973705edeb9a15b21452c4e07d2188282459e9a603a6fc02d3cbcac04c87e011553d14dd91019fd6a8ac3ce85c917465fe8adb2f69530629c33657091d7cf07f22ec72173285326476360e4b3659dc94538158835be26e385d40a897d969e7f5f7a96b5bb4e4856eb212c0e076d9edde9505db6a3fe484ec88807402e2f72cf744637207f38332e1c22b02c92a90ad5b111b8e900213a686b98269efbed937e0e00072040522f6852f2fbdde70c07d6786660e1b84b3a804784152cb5faa8ac64c6972fa1873eab3562cf7f88b5726068ee702a21acecb7612c1447208cc744561092bcfaffc8890942d561640eefdac40a60b342bf7a90bf5f5fade4ba66866b7c007822163728485d5325ec40f81a771ef038579b50a25cc7e49bddfe922296e9f722ce21009ad6d3156c66813bb0acdb91a84b2cb5b17d861b8980e1e93a48b117214f4c61f2403e12ac19715454a53fb016597c33292fbce3deeb5c8595a883a724f24f1c550d653f6d01ab413bdbaac2dc9e031abc34775076f15b313a4c9f07236192aaa3c759ede90369bb17fbadf8230aa1d676f2eed422339c03f29bed04d85babef68738dd7349f56da10131520dd6bab214336abb96ea8d6da7f910e343daad814a06fd1dca1182a441f9a665de7f9da212995bc3e63aff6fcf8fed3b72c940af09cea4916a41946d8a83e53d5c020e3813a29b32a357ada5b7d900f5725cd538fda51825ee559a9d10221d9d208b01e39c56adf65903ab5cf46a29917228878409fe227f4ffdab5f87505d5834bdcde440f022b65abae234aa641e2b0c8b8598af31d883b52540e56b79f9672f1e5c2bc65dab1f1e92de9960d78be43193561b40c84bc4cda2e611e12daf8dd866e440b4ee32fb7ebe80fb738cf568724e0e295ff4b5443cc1d55cdc336686347344de65dd70e540df44c9060dba534c7328184e05d8390dca2f899bb41498bf0b2e7fd2ea46a41e40f3db49fbfdb81f37ebbbb2ff8ea5196315b091b4f15cae6c0c3b9f1158c4fb00182603c9a35a7235abfa27afe1bb06aa7b9bf5bcb18706ba98a4bf9242392f6d749c2979c7827266b407094bf89e21052623b759bee7bfddab58927763b765d0ae0ac7b3a7a072f6b842bfd2a7f132efdaf1ab9929c763e0c1efff1cb221a167188d2f9fb72f728a423c0724b2c82e3778f72660c8888089a5f62e28b9e819a6e1e9546b7ed57204340be46d17183d4a5788866eec32c6c3f4331ec0db5267597920d5f57bd3725f4cfebd678d87cb054e1008161bd42df6947045bcb3df4c23c1ce223dd1e3727eea9209a960521fb276eb16de1e300804017ef0205b13604a50e11d8805a772b20a73f0a41ca935717daa43a5f5aa860e7ebcede9f1f2a3317795ec1124f771fa610e32832b5fc40891fc8c12a82b0d670ce5ab8c1230f1f2757ea0ee504b720aaaef02eefdefaee470f92e71a779135096916c7401193192449326c346067267eff2d76e9417a78276defc05d8913740e3b96a097c289e5aefa2c789a521725a78a8b7bae959b40e695cd867b3b2b19ebfe8ae3cfc4f420b51c23c6b84c972881d270c979a8ba3b91663972b7c542484280e3b169b9c76fd55be181b9c1772a09074d514a4835a1c8c4dca22cd3887d57f04fc89d9e2ca6784b3e928656a7236455d805bf5b314893f4323e63cbbf859c944dc01791a89456312dedec567509e4fbc58ac40c95682d45514af47a513e9b0996998b274227311936d3204a47251492d30e9ad89a5d3158d06b304075f632cec3f629c3174697f92ed23cd18648514136c70c21bac3015ce7ed747e5b0f3b1abd7269aa9d7a8fd1894eef6ac724355b7983af1fc26f70664d8668821f9a828177ea48fbe37cd7de826374399725c85be18fca54d743e045f55ddcbf2500cc2403da4650187e4aa129b0747e755ea3be9dd55bb55261f035667386c714aadeafdcae1232c4990a498dede7d1272640584ee5a4c184aba717a020bf5052dcd1724b5637ea1d7032716ca1e29143f63c178193eb46a5954a8922a84c64da59c0bfa95e82712a011f3e4e36a61eb505924bb44b1c30610f8ac230d6d8151ba50981e578108bcaa470775521e62ae7243340e8a34efdaf6b2409d12bd72fdd0e5bff5ece12fc8c6c8073257ad7db6250e746c55094cdbba414f160af666130a2a3609e71aaf76f72f7a27c706d9b4724a916acba0250d5186fe25eb9c7bc94e1166cbdcc9d01cf22650a0e9b1f9cd4182259f87e061b5d93eaef7a0a7d7b8897a676b65226cfab65222899d3471e2720dadefd38f84314acb22ac6b137dc4e675bac5d5b65840f5e2a1eb2a22e14c72dc091b7a2f5a56657caf5859f217f6f711e0ea77ae6e24380bf410027d6b4b7298fbe02c524e18dece546b579c79ca4cfddc0e00973ca18da964aeef7df6ad72ffb379d2df264ca22aa5e876d1f59968ab08a0c8b8f3aa934f62a42cfc99bd1ca1388cd2eb64c39165a249973d0d0abca3e0d92f2a5ec90c82f6a349df95697268aca34da8a0e57553fc41be266a9cedb44d8bf4c4b3369f36cb78dad8222f7264cd15c8f41a11a8c25a37e66781a0985ff1850c0d33748f4869cf19a690bc72b3a39cee87bb09763a40cc60dd2ffb436dbaa3d86f45dfc0452e628a64b943721eb6296671451fc5de534dc67305543001dd1ca0768b30e8dc6ea14b649ba0720b0180c1b64d392a94bc4560df011b9f9bd14276382081dae9038c88a2b45c72cf537a6d9e6c288e680f06f18258413189bb9343ad64ce4d726e738fedd55e6c60293eeb665f41dabc61418eec16a424629fe329d4d167af9f485599eda475185fa96add6219bbde9723dec987ae46d2ea3b3422df994f8ae515d2c2bd622472185390722d978715bf05da81211e35ed199c604dd602af292418ee47ae340d72ae5c6fcb202d330da5b00cd731c65f9d2347d3006ba53b03cf07cb6599953b72d252c5532576f707c1a6b517e21d58dbe2ed0f417ad896a30b2a2d51c53fe9728adfc6b03afc64f492c5a1b91809d11deb3165216b3008f6869b97611ee9365a2120cb1d3e3035c882ff0dba8a7b6d92c4ad8f96e1cfaa43873b4a34e4e0937269173e4bd9bfb033674283c2c82222977f288b3a3b37f4b3c6a8ffbea64760729a512c1c6f39fff52a6f212419f6dac5c62d8f4b4330a1b5b2c3583e324459480cde25338de1ece281da683eabe57e3fdcf3f2f1de4f34fbcc198c7ba3763472c0ca6dd74097e01690b70426936d7eb2669b47eb835daa33c804d66fac42ff728dd8ce7bba1d1ba2128c1a8418e10729eeca90c5a978e17cab685b6c9fd8686f42dc09aeaf5a7e575b9bc584561a5b5e5d2e082d3c7421861ebfe58f17466b7285fd2788620ec8fb317eba09b331590f0f031d2c95dfe3c4d4a6d8cf405a0d4a6048eda7a573189ac68248b0fe281112ea88e09e83448051553705dec0f2bb1d36caba0d7090a52fe041af6296be96749dd8151709fa6e8620ab17d9f113fa5af606dee1addbdc039ba027e91d8195e25c6a7740785abf4a781b0b12b3fac372d1797d7c33617191308a9d7699f016374ac97d311fee3515ec589f270e4a007222d5064d7857fe7d2398c9d5f0b32026e8197ca6bb154fff1664b0b8db51da0f47c6625bb10d1b0c30fd2644d032399c30cb419c61651acf9e9631906d5aa301d5134595b91b90ae07817b3172e916fa4f236873ceb71c07a29e6523ca8d90729c46b75eaae048d05dc5d3c9bddcae2d1bff3ce971b8213fcb25389b9cccd00fdd6542e9d7e159b9e5b31c99ba576928c0865fc81d843014e02ba893f19acd723e90d87094c2d122928ec70ac39f99bf6236ebdbf740214b9a895ffc92677072d09597391074d2f4a599bd729a015d514a34543f993b356d4709c44faf704a52f51f3d12a82f7f18463b6297591a9c88dcb57e4903b1e2299440d783f977ce727b9b69b7a1599badfe7db3f1c655542baf9294b945bccd551762a33ce38ae56308b8871a950847407b4ade71acbc4197c7d32d9dd0d89f61d2d0aa390b3b6472d27f374f29f8b01993c4fa6acdd4d569d9210ac71bf3c199e5fd62f068f2dc725d400a91c05bbce34e0afd84c9b9717dd64797a1ef48312db0fc48f7836dcd10f4fb7ca9d8f2c17b7da901f7631d41882c4cd2459463de5f47205e7100716a726c12f14e408b9dd14ee636d202afeb6a790b0bde0553132ff69ad931037ac572274b5cb75f5de311f7a1ec9db7fa815894c41a8446c33a8bf43b6a81d60fb21345e92deebb8d8d0a9496734e963f0083b85a45e6a9fc41df37da64e663ebd43983f85b89a0895ca44a6e73911d9b9b5bfefe3822398d74b3f26f2ea7602ce4444f752a37e2d44307c5ae86aced2aaee7da6765132a312d987ee04f7b9c8d476d9d16cd20880741288036e5ec5626058a23e474c06810a9f9763f53898b65a1728ff98e4e2954ea2a1ce3741b10745ec4ea79b3bc7095a359dc59474f024d872e27ac61b5e2e94e57b5011f8166b5f4c5446ce253047d016410c520cbb0478472e0b36864779c116a71735406dad4aa3491f5c050438d2e8a123fee4dfb90fb7273a60f02a69dc3fa0cb902ea6d921dc2d1a095a9d1532a2894e5eafb58db06012e59f05b39fb3af8f144b162d9346b46eae43af788db6da82ea30dfe0968c26a4d7d250c019cb968676f67f5adbf7977fe9a85635e3df3d768ad50c80430703e8c8f962179bfc8e20ddab0765146549d6377a258c32b873c724dc580a367f1727c093c06292a08e0a47586ae22cfb1f59530faab2a70a86669b8a01e90301b727f3b0cacbb605d8be91cbb5343312fb98fb08208bb507022c7de3d52932cf872aa3571ef671225142d092464dd5495a5cbd9e7260a4c4de7dff088ea2a44b000cbd1453913ec45c05faae1370a6b81de92d7c09b753635c3a88eee28be44db3f7d055c4c92242ee54584d67ba2de663d0bb1f39ea7fcc46c3a567cf32e131d72609d5fcb8812da56a8fc7a6eb013c33e0dfc5b66bd87c9e7e45cf7fe3357cd17069f13c980ad9cc3237978568838772b473d5905454bff85666a947b7a23b5720613ba583e1f0a55835d113dd518d1dc6569ba576d8803dd660fd8d8557d5153e3d2d095a4a38b80830acbf0880dd67fa460f99b4086770a0166da2d0537244c63e6d2c2736bfcca4aeed36853895568f85ce4360e199aa1ebab47a936b51a590ba0b661110cdf060552e94b83ca320e1809d4865b4553ad60ae933081a5017205fc37285220ee6972f6e89e6e4fb2319a00c22c531d61fd168baf9fa35aaa327815b1d1865b74f5d55cfd51c33bfe63735304fe349317ca40e2619188c3487274fea8e917495d7e762db00953b9655d9ad0759a881d30a6e2fdc4618a08bb724431a59e48a304ab12340c30f7c2ef795579cfefa161b4fca9c60e0c5d2d1a7240420b7f0055e3b5dd9d99dec2935cf68dfd6364c459473501092db8a9d18472634deec10fd1cc602395e436263c97083925f843113ef352b0ee707d7428b3725aab6d5738430a94e207a604b2f9718273756f3cb20bcfb8ef3e1de59f0d23280cf52d6a0f593f7827ddad848043f684e55a89474f7361e928e4182f6b4fad2702c28decae40fc0ae03d05b69d7c2ac8caacb145974a47ecf9f3c4d2ef967c36de47164d10449b83e597ae087344d32acc342104573a37363d92785572ec3d2f611e84dc274ca9b920a894be6a8d70b5e4becf041ca4f9d81cad394ddd239672016faa248ded65eaf5018c4502207c992f9bb320dd9c6332c1459be7fc49df427c72d02083b8206d0f2d83c6a84abb9d6da75ca4c8350de213b42c95dafee1720fdf2b203d37eb3cd94f08e57c05add76ec383d65f6e55cd400a6158fe50c95bdfdc5c7ada421fe82439c5138ae173486bfe8f4355385605a8aee9ec95e6a6097ba95c06b4e77a28573c5ded2933d2331768bf99cbb0568c39caf80d8fb84e26b6c43afddf93b303d033d5bbe88f3bbcebba28ab10a938b0f6f52b4ae57da772830e9cc8adbb6845594b6c06ef0c5dc16adab7ea5e67f08d0e92047365ec7372f13a6fcb4b79a21c8f8c866bd97f726c83ed5930980ce913c1a3b92c3ace6d12f92edc370d01fc769cb6900ecd3ef79418ca7adc101f6126f3e3e26828f0861dc288052951f62ed07c5b3044d41061b5775dbcbd9581adac67a518a1d301b0256e90953b56eb2d8eee0a49c2f7646df76e0f159a08bea54bba0925bfac676672d8ad6438eec6469eb3b07d28e7620ead6333c6ae2a5277be272fbd789bdd9518e253ab8ba4bf0338210109bd96b80f2338eb452fbb39cd20290f4bcccc391e1087f3919c184f8bd6a7f48c9fe019b216f01a01089a895f29e6ee3c76011b9172a7175cd5fdcdfcca8d7e654f3276d65ac91e4a5a7f03397b9cc3c28b837ddc45c382e0a3cf0b312ee70c232a70ca2731bd240c439314ea122a0df008c8fe8272ecda41c4bd2ad7042e973bd53b6c69264a250d7c3834f9181f1f9faef54a1472a2edf618d9a61d3a4de0a9f508cc0753e26a7b2f736d4dc873318b79a2989b2719f80f336690c49b6f941dece53b8b7c40aa0dcd6d3d571a87cf2bd712b41125cf00257e396afb11b2d0aad0904fa5a6dbba7e104fa41b60de985738b5f3b97272894c6d3785c14fd7a458ca38096e9c105168800f789f6675b1699437ec8772dd5adde2f9c4777642ae0dc5632267c9bdad49392e277f0ac641c4df542252450d13981100198c6b29763255b4866f1afe023fb184b958b3f510a539efc5442419b6aad529e213084831806119325f0c44c32e0c4f9f2d72448426729fe6bc7213f935e2c6d0f17e9b3a446b548ff5379ca39c1e8b933a7d9990817e69e2c472f7b0093343a7514a555950f6dd5e93b1a3a1d2b763bea2dfdf3bf0cf8a2b6e7277c0261b38baec52897a1ba3bf0a55c05674998f6c2d80611c29ec3b7b241172785b1543b7fab3091cba7cbf7219a64caa5628c11add7a256fa5118471f0622f4f74154433b50597fe5d1ec743aa36ea75f2b435cc44457c432eba7722e05a72a22a6c3053d1dc62d54092cfd179b8cce9e1aee62b0997fbfe429fab732b30237d652a050bc7ee0c7a68916c18e08d5a41d47c26c060416b5a56993f12c36272ef422ee0bed2c31fd2485064744347930085415f49c357171c22b75f8c6e477209ae887c8c13c3ebcc706a262acfd5876a0f0e9f4d19d5f4c7258844a9bdda496d0c618404609016e090ab53c48e2eae275d5b0dcb9d7e2489655591b4f20958117dc91f90dc5518078df8291d7b0f63a6ef008fc04bc945b77895acb9ccf1725fc9c08fc88a47b5b98b8598da5cac53ef934a65935239d14e54b65948ca6a11be1cc777c28b15aafde87fbeeef3da6b430d356496cffdfa0454a72f49658872f0b93252194aed8d739d5b366b892a3ec9bc1b66074d6638f235111af30d7e1b1580d5c8382a280486d8bfb16ba8cce4a3adb06d52cb35443baaa1234a495b175666c203b1e2a737cc85246b7994089637166f8f497e282dbbcc49fc19824e72ae1b76527528d205189709ea2c3577b8801311881468aeae63d7033c6177b67214ec4bd69c0012cad0838013fd2ecd21ac7cf23ad04144dad4f65024dcb5be72ea297987af4a74e176d75f8788a1d39f9e0d46b0ef3b4a3fac581cde518348723920afe8c3a75cae8be10b45861863cd52bdff6d2e239f0006d341ab3bc8986edc8516060bac09fe25406ce86c7b8b60c526c32cbb8593a1db297efd08d5c86c488f50a0151a071caec47d4e33f95690dbfdd0ce80eafed5c4c1a33bfe2ce545a7a6a8778c5cfe5b98cc1599bab48fce4e4272b6f424adaf8db796296d6b4c629b8ea42bbc48a25878606284eac05ef0a079b48f6fe60dc4be4a877370b6ea706a959950871f42db18f0f551ad248fb2cdc791f3691bd99c8799787df6bfea72b850b1533ae8fccbf97093a91fa217b205d687525583fe0456c017c68754b62d56428adb0ef2b5aeada266b2f7508ad8880862b64d41c95af3cbb588ec878a72ffd52751066a5b10260d4b76bfaaf8f27f11ca9fc24765692cdad6adc018840173b070dcdfbf8dcab3ed5b0043bbc33855c5f293a7c3330f051f72305df63872508917ba0ea0d6f0be2bdc6a79a914a9b74411058773efb9f190d546f7d5a7722c4b000f6245f4a8c5712f1a13d4ad60f57b395825630fde78803e7a233b0d16d65d88eb2c3d1eafffdee86f74f97b42a9069eed735b9fda4d401279530148724e0b1feed4acd8520148f3ce0d0192d0109acaab853251216627060566e9fc724255dd7ea3d7dea6262ff8055e8ad5828c1871a0c2814e76b57f1fab8b3c962a73fc0764fbc77d974445b3da379f621840e9522cc49e416e94f923d4376403725abf075fc3a42eef0df6e2626dd9f925d8aa4e77d87ae76c68b2fb8847ecee27471e5a926fcb674d6b8d564108e4bd34ac8a4a6d4e2ff57ba138db2cc914ae45c3f1c355b05bbc2e34fa0673dc5eb4ee63558c82a329a08816d326bc21da0e7212acc54a28ea18e86f5422b0e598228b498a9b35ee3da68cbc0dae85d6d5f50ffce9ee0a67ef42372c75e2b76d3a3b1dc4098a69551cb230673a2d2ab866ee206c9537cd8e3408aa5797255bc02660dadcf961ad130eaeb4aafbd2866bf1cf21597ec80b112272713792478768cc6a31627cc7d401c04895a5461b7d51a6ec6f4093f5b0dd390d3d75cac3b73454bf5669674ac5cec787e9f98c1d4476c9906bbdb32fece4dddb128552f057dcfce4867a9c6887998f742e01c6d74fca567803c5d6b64f535843b95991fbe58aa93b6ebafd9259f88890e726ffc2d5da2f7b725c33fc8d0d300852d67ead3652dbafcc96362db284ee6e513ca6eef305532b22af10f78c00e33a560ce7d61c2c25e801a5483fce24b5ca25bb617380d1df2c08428b2b46c77a702a94a4eee6d06716c98852681872af74512fd0e8a807c0342098357a96b4fbed5724e41c6fb354f9df169b9eb3af2377228a93bbaf5a9dcd72ec42f82f60c897d75cf4de7a4d2001def60e67fc8c070dd919b091150635b572d3b0b6f8a401b9d5e289434206b3e94eac39e511ffbbe9d47a3b8f50f9e2a5727505630a4e406f494b46dce1bedca66d3cc6e0796ae1b01f5980317429a55637ae373bc637c405c0e2267529065efd5d0aefa4c458ab087dfe10e240d7fd1926bf1ac632a878104cde61089bd6603c9c19bbc2492a49d5bbb93ca26c20b1bf727ee0a4847bc7e9cdb583ac8b7ce43d956b9d120a5e56393e140a682d72998e728dfc7c6310d3fc71ff5cf7a676f3979aafd2058f1e98dae4147e7d78fd882966a413a3d70563a392259f90a13ea54bbbafce6418bc270bc2db99530409ee685bce37a0bb3626430fe373460ba436451c69ab8a39b25ab1b4ca83849255042c72432025644b9611fa96dd9565f3b3dab4da9610cf15870841db6fbd44b2d758725b0d5b51805750f3d6b6b6fc50e05e70679cbd5930b48151c77480348f5381492f8c301942662b6bc7ce65b66e8f9eb5e119261dfef48f83f8a2ac944db03572c98b5c28a50ad2b0e484dd4e508d94f2c15292c181f382273ec346180e1c1c72aa5c440b1b10035281bc6616d6499e08c20caf8497093b606d630fac588be70e854789f7e35a9a5c1bad178b7693df16d20af04428962ce3ae6368b2774dab5ed34ef46cea3aa57272cfce53afb051a60df3ab18f881480da6c1091ee62cc94688892c3aa9d5391ddc4c6178307bbf917c3277962c811c0ff7059fddf7f7e572281620b707d8d0f795e3e485e49aa711beb03107f4e9ffa34bf18477ea2b6e72fbf133bb3f5bc5900b35575bd5afe563e7f367db30ab239fbf74193a15b9140b104171ad3b9d5001d18e4ec07df6625410731776eaead6bce20dd2200c32287211fb43305246e3d1d5b79687b60a8f1bf9b13eed685ff7403db209d9c264364076a1ef688eca0518decbb8adece1030eaade2cf31953958532156ad314d42272bc9b2dc73d5d142951e4dfc66a7626db44cf2adc7edd11908135771accff2d72d3ab345e5df05e96c93eda73d8d8d3be88bd200f028b8f2724ffea53234d6872487ec873ae7a8d097ad9dcec1757bba7db65cbe9cd872dc6bc96d9d0f5b14f11c66936d388db90054db4163acf3aac6efe22810b98f2b7ed22a36ea8a65f1172706a2116e18ab6b2b8b2750ec1065a1b2b714679ca69ecc703b22cc0f9c6ca64bb7d4cef9e67a8830a8d1f385d7728022f3b0053fc52506e2ba371a2b71edc722d43adc3b8e415a111343e62c0b28ac677451807cfef8097fffe85c955f1de5cf394dd2da4f8cf78e72571d678f61c90d512a163900dee0e484ea078fc6195256561458e44dd6188a3cc6967e849d6942e95a2293e3e478e4852731d79e58d0747af4a93eba0732a443ec19b22f1e204c0e5b53da362c7e0eca8d1b7517b4f3e4406811a6093ebfb6b3de3ed9957e629d1e9a890ed4ba546a89140ca93ec4d308b810906b5be356377f0cc4a29dc7b770a0de49646af459d951dd81b0c3c9972e8f20e9ce4db77a402666c4a5a8c20b7758cb6a761070880fe1da5141fd48672d07a4c5bd08b63b47d7edaead1aec0385e6563c7a16ae0df41ee9fdc1743cd72af99646df6cd0caf047f1ec36828507272ae559e7773ac302c423f93b2fc70725d726c138ed85f123740c4ca18e87c12bd5444b7650ed8e8e525c57d68b41e4329dea30f626ac0cdf43d43d7483dce84b6b068b3ee2790b481b055fbceefa772858a710ad80a2b17c247c3d8ff536ce2e2fcf0f6a48f9cc379ecc87987fd7d62b639fc488827caba157dd848aaecc345b44b75d38173c68b0a6938db602ca21d8102e0fc750fdde8b7b45c10d1fd399f9c3d64d3c0da9ea0e8af6dc4be64b1723f5e3c598a66ca91a19bc07344653cf55c5b3fde0d088f087ba63b2d7293852c4e7d527a2a3b2b23a794e9ed3b18e8afcfda0a7a86fd299f623c47a043b26044591af4b422c6a03775e81b65c7f284fec5ee8bbd89c010d0a715e70d0c1af5721952ecf626178cc25f6ca1f8c3774315a7428c64dedca04b0d5315b0f2ba9c45f84a4aa40f246565eb76dd6763a92a3fa393f62d68cd424026c34f0ff47ad972299d31115d2e0fb14d0d4d331dbd92a1b847478f9de8b77e039f8b2f28c27c597b0ca0055136a243fb4bd77b76fe0900824c5d0c0a06121dab1894de6ebd3c721d654f7ce45b990a620c4854fb5789ae1c3d103b8c4db8b4b491428b4e9b051ab1d03c3728eacc7a18640a3c3993b7a9d60b47f4577af15d2276299699ee6d72dcf8d18d2d9db54f5760fca207319caee642d9336e2a3da66ec424565ef4172612eb31ca95b5662985de9855be105b3b8abcafb4587ebefc3ed4098dfd8b0a2efbcd8f5f1d7d722599629890aca5cb64f0a115a2fc090a87c981d75c687d7f72135ddaa53beaa2b6e3d815978bb2111ca876a293be81db64ce3a3bb317caa9114cac5d1168a80d541d1618928b3e6feab51214379aa5ac4603ac2423724ca9334988a86cb33e7ae259f6aaf01f4bb0881e6664047598f02aac5ed03c87c9ad724aeb226b1797c953cb1cc05e8d79c476677e57795a058ed86a2d081b6fc5b15f4c27ae90e5103f1db768b126f1b731a5efbe9f408be4d3813471afcae15f1619f69fb51b799a86b0cb8010915b0a58708449f25e32bd9d398003fdad95727e726497b9648df8a04d11dfc5a20acd4d3290c0e4c050d5a0d6337d78c03389a5720a1bc356bd1aead46ace3055fed8612b2bc957087ccf21621f2dc6e635f3fd21715c732c677203263cd52e9a521911a94dd5d1feab83daee2a0944fa43eebc0851f71da7a4caf1c34c7f5a9127d8f655892d1d58e24316ac4be5a1c8a72c6e67ba9de5db37751ac4e577c268d63a723f6da8aa24be8bdc0d33d07f9b4e033d723ff35240bcda4b1bd63917b54a44e28ec10b1bca892c4d2af0a13d1dd305e272d5b00abb302b24f66614b88d45718df94303cb662fa528b02c4504aaebfba571228ccd8a5a0aa86f9e954875320753cae729e1aa13c334250c2ec1f455900272855aef5d3fc10669a26e6b8f9bebe663ecf40137c1bc905e4c2d4f5464d8ab72c910493540d49155d07a414529fc6019bc4f2f0db77f211a937aa6c13249b1729073a6aa47c07f40ab8c6bae0bb26602dc49180658bd616f0a56b3381dae6b72b98ad1f18988bf1ca0212350c5567cbe35ba87e4fcfa4110ee15cd34ca0dea723d2ea80c744e9b90e55f947c01c3ab9d830bee8ed26b7c7ca123d1197ac60172a2786fe388636240e50660c2a87a8b635379e601bb6e1050b986b8c0e3c13457d71d2bb4b32e7f16631ebd7d79539d2ee827e99b54878e99ab984e6260f4ab72d0ecb1e1e45843a25e8027f2bcea5f3947177478dfd53935ec5420d6c86c462bf497cf7d429928a672d6b474aace69a47767461d7acbba06551242cda827e47279b6861e6d5581dc5f97ce6a44eaaae522ca1386b304a92097adcb828b43dc726b1fa56bc7c521fdf637079afd1791bf6bdf26fb50322feba7d503d0df2da232de67a3408af5f713179df16e16c08961ab985bb4849b562161b64fc9c085206e176827c2a4033e73a807b6135d77742a7dd039fb8c49eadafa5e96c20796f6720ccfcef1079a668c77c874ed7b33e539efd8bb58d81f0e253e533555bf2d8372e69a141c4d5e3c55d8b46e4dd1778721ad405b1f6522c6ad697caaba8f55cb72846fe8a4db58a2f94c77a06ee3d94b01d1e250bb8c6f38c15ff3d275f5c0ec7236407d445902cfc09bbb7929249319b7693fe6c75e586550643815cfdc2af572efaa1ba98e180d1cf3598ccba48be1f615832b1148aa578b685472f21c42ad72c151e9364bad45f6743da95e3db4a5f623ff361c12df134c46b54beb8c05402b48339e1ee155be77b42e379e90386c896cce760c09f9a31ee5f3bb91cbd159725465d9a15b55d24e33b0b65d6b13ba9e0806801af195a3e18f2bbea3a3bebb3b008181c9b45813bbd8f8324161a2d7699117886821f5fb3578322ab5882d7d72f791d4e92845889fe4a5a62dd5b3565ae022175cc5a7f8a9bcfc3feb39c3593238131daa4dd9eca45ed407a3af6881497ca05461136ca20ec85d27d8ac9e557299ca6f4121e8d5140656c6e2fe327104c980511f531253f8b293205e42c6217286950beeda45a310c44c786774e930aa8597211bee21c4f80dc60b112d6caf721448f44e668e80f592631d70cdca72fa9e49d7ac322f0c46fed91952d90bce2af8502e9987caef0429d1dfc2a7f511f8b7a655db63d355a841094cbf74993172127701318fe576db30c8cb7cb72ca033e2028db881d9f8efe328eb06978cad72b10bea94408c74167b6e67157f240f1b7d5418e768d46146c2feaddd63f03b723821fb7d7681d44a555741279317538da4cb0f4b417e7fb7903ae6fa4830b472f8eb6e6d04dc50acfecd2186ac6dc417aca8bfdf8ab0acb93718f4c55daef8714d9acb38c331fdf7788f4b2bcbf8316fcd9b6561c5ae573e512fed61e24b3572fe1674cd25021450aa2f2f08b978cc1589db66ff38b4132001437e5fd5f62e7270e961355f371319d1a5feb083a479a59f1eb08c9d6c9fb3b37bc92baf79714ae0d43e4f69398b67c395c428e39ff1050895d99080ed7bf683327eab841864728489b83f895b4f80fe1b02b3a5c8451f39743aaf94d25a5f5da6473af51e6f720b84adbfe668bb152fa912922f5b8918a8dad24cba506c720dd52a757f873972ec540ff74408d19c72b00566d460cd25eda3417cb9f5dc164a6534a60162877268ba372a76462b09e20341a66fe267a76f27ad46ed23db3ab4fe6ca98aa37b72ffa50b20741a96a9d6a9b8956dc593c1fd2046acdd1bfc54fb96c18dcc628e726ba8e4fb55842b7803ec14080bbd519b291053b976c2c01ddd64fe347b136f252fe104a7e57742911bfe37fb20768a42e7e168947e8724bc7d121c4b28946653b7d3bc641c2a4a57fe0766b696b894edbe931b2d4c1e991633ba273ff322207242cccd1fb60d98851e6a734bdbdce3467e2817e91c73f4e4e80f6e7f49878b7218c7820c2a78c89c5e5fb4ff78f9dd079561bd28679524d6b433c14407b56c727c7110ee20f30fb506cea09f093abdbe8652206ee73d19c6cb1e4f7fa26ff772d2d63da636942412db465643cc05878d0aad8a4d826c1dbe6236d684f31a2d21e18b6deca99e0ec2dc837c94b36a25e3a9a85731535123250ebbee4e2c10d24a97d97660819e8d024526e20dc771440f8aad917c93a5564b65ce33218c656a7251eebc4d6fa65d42eef12e5ce705eee56723e99e52c3dd54b82788ed91f96b720637bd81e46a785c3e065ed9f708960b91411f7b89dd9ca4f99a44aeacca6e7298ea54e3f858a8c535577fcd57876d643ad106a22dcc8cc8174b080902459616a725ca82fc7e6fa19fed6eebf5415a7316589c8b64e05637ece40cf4c80ede7253b9f5383d5289d44ae07fd1219d905b2b3ea7200837126aa0af0d5b933730723eefbbd6e62d04c620e270a4f48c7d6f75fd8231a9666395b3edafc607c03b72a1529ed3673de6b3de1fce1b2770a975afa418c9e4037ef9c5ff46a7fa657f72d3bf5bf20cb2c06dab0534075a9e0247737dae13e256c4c25241edca784a15184a0549e4328213eae6399f371d6ca04b13fe83a781052ccfeb99a277c8a9ab72677cb08808fc80d7aeca590cac86f873ed7897a355500aa552c6995e93580e3b32107cb29feeb82011210b2a01f7cb72903b19f067b5d7b22fce033a2d3bd7721bbd0aedbf6823361f7764d7195675c9191d082164c946b6700c546fa36509277060c8724cf61a102e01d859092cf71181a512c2696981ede890f04f1baa4c38bd8ec8818f37b19445bc2aea62af3768cc12e935d0ae150aa44bb5c2e4dd6f720c31044a335810e880f8a15bf20dfdb91dc238e643727667a4aafb6f917ae23225f98641bca527963f3cfad008d45b7ac9f11d008894f61fe620ec8c07884c0dac76823460e46748bcf3b8d8a333bc219d2138d96ad1b09fea7615fb3502c06fa2c1b3051cfcd54f1ab109dc1a28fd1cddc6a9252bca3dcdaf9739129a5397726562d680a0d7e806977bbeae7921b008405a0024946a989e9a6b03ff745f1c72ad0aec47f2cb86c286074ebd031ff632c677d7f2aab591db5bea1a621dcefe6e89ca8b65cfb9d6913222c26a75d01e7afbdeb604929dc295a39a3b5bc1f49a72aef5ef9cff84321169101ab35f3e07377f6933395605c1cd5d993ee5330d3372fccb37be777cc3059660f8a2e0a93c9b385866dd916e4a7e2c9a4b95cf619d72bb564d30ce3e3a42444d609da9926fe5d8d9c7bde7262e8c9bbcc58282361129954346b9db7c544755a46ffdb4b2e047fa5224f97e51434337429d316a49744a811d789b0a106c2d3b4bc06462be58050dd10718c979642e09aeb3ec13525b37dcac02980f4de605d7bef44fb650c52ae416593fd7b29360866c80c33997474ec5cd06effad619a2a9c6a9cf3b097c817a880f1c3a682559a5290c1d2419364a487780fbbdd74fbc619a6612add8bae9ed286a2ac4bf6ac1778bcdb65a19a425a3500259ad358a8a22cc1312a720dcb14ad32eb747d66fa540f6d5e873998c6ceb50a0cedd687a07b5ee2d22e038b12d7e47606e873fe5f40c84eaaef81f6220966faeb53ae1bfe095cb290de5cdd943e1aa1de0fb710a1252696bdf11aa04728ea6beaf938ec6cb9cd778c159116b804f56ea5f0b26da24b778a2ddfb6287725455b4a9ba48c15dcddad21714ad924874feb97568fe097de960b1701a1ee07200b639a29827a79e95803821f1a14c950d0db792507693fbe93ce41aae4ec5727370e312a63183e5a92bc88b0cc1deb4e2913ee50ec2e37bdc3b0184bbe8d66ee637f06c7399166b50123f1e733d52f19350d08e60b995ed64651ba6547c20724e8a9f467a8b111f2fbd1596e0eb8b226dbc6855901cffacffb6392b8056d672f7f2f648baa542f656b5b4c8c8cfee652f9fcf77aea3d95a8ed094205f1b947232ba446768df32e2f04aa2e6334500586fea44d6151c6a5dd4a6551665fa1172f0d6ed7b3d9c9aab153e920de4fa5afad973986b68d4d86c124d07f24f42fa197bfcf13787e622965b2ff9138d48ba92b694c51ca8abc39a1add1c10ab071f570e1409b114d0f9666af9b7b18dc98bc6de917192dee85b475e8309951e1a9e72fca64ca7a5144e0bc887961bb68383504b36ce722c2633576aa2d5f90700af72b679d478a2e0bdc0ed28cc2f45a415cd1a140fa6e7edcfb22d3862d110f2d77281da1eb914786291f329e1a18fe095a328f6a700c4b6568d341f35f8ce68f85b087ca470cae65385d66da0d80a0840405673bf48e42946fbd96831e6b16664724f8e263aa399cc62e20dca85bc6f6bdb5be18dc1aba59171875866f3ce7f53001247c652adaeade2ff4bdd3cad65a62d6741e0bb8a5468145304fcc67efe0b723cb7a7031554935aca7efa4cd6e0e9d27eb9cc2c197052759930eac13b5c7d7243d72628e783c48e79ee326d4056df534c4b06071e84b20149fd9eaf98431e1068bc29bfe82efc6e723b18b7e7265b2dcadd416eafcb34ff344b2cde2dc6fd722466493d68ed93a9443d3d49daafec2e7ac4ab47bcb5c74e0c798ff9d7ce8b6f57049cafbf590f838bffccf974f4f1527ab6502de1a3ad1552af9524416f5a72402cb7bc68534b4ee628a962c38b2feef799f87855e64893395abfc0f729ac2aaf2ece54cb06b585b00b55de7b0e10eee997d230703837f4f0339439e1e80672603a50eddadfcb70f94499e4367ac21c7b48a826125691a320759dc508088353b193bf6e9a2c8df6a4be172107038b7e3c28779399755418edc3c8f2d6c90a7289c5f9fe2a692079d2396db0353ea04a6e6d2a5c8708191aed734a3d66f77572d31eab18df2d099032bab59db7c2264b2fd022d7f03f33ef4f738d9ede171f5c09d65e80b8299790a3455da41c088a226caab65a1fb8d000276278ab3e27c4364e09478d5bafb2cfb13cc24abba70c63d11c0601fd191396f9c5e403c2da4b72c0f4d55ce8550cb0a42fc2248a8e1bb2b92ca67972840b1b6543111a1499447286f7f87a85ac22b1ffe8ba634b2de5a076b37fd92b48778893b6aa10f53f1672d1d4fd6a7dce5327d265f04eaeda304943bbdbfe3808d571906c0c8ffd07d972e3217d7e358d44bd3d814c5842a3959b7c81bfbd802c26a0bc41c0c01c9d7534b3588da80a4b9bbf91b33e821ea32cdf2d2c89c2f0894268d70f64a47450af7220a61064d832ec38fb3563df5f0583dd1a71ce70e1614919a9abdb2bdca4004b452778c6de7f7335153aca5e180c7d681876dd7a059dfad092aac67cbef22872a3092f66ea6cfbd17c3f8573a604f7f02702fcdf210ab1bec5bd31d5f9b02c72c526bd3feb5438f471031ca20f5c4f8005295d0b062717fe413bfd3a988ae41012fdbabef930f505287b2e99d5e493ad2c92a8b2da4293965dc4eac684a48e723807e402231ff816a8f6d8297dea2245bc79023469cc7de5d5e4a8e85cc63742974e0f0e757afb3294433df68cdcc4af8d47bc8205217d45427559e438a41972ca44973778ad1f0cbf3738937e962e3f5505fb6482a2566ddd8d3a157a37b36cfa67e169ad0bd7ba8e3657be2a38a8c09f1fa177ccffe692623e19f855ac68725ca986556758784ed1f78652b8f751bb0801dd0cc1ebd687082e16c4c61b5972eefe02362c24ab256620d47eef3e420550055d254ecbdbc2be4c5a413bdd21344dbfd42525591e44366f491921bf4671432f445ea58f79ade3e8d649edbd4138f825893007455b5c8dee9903d23df49f1d1e1751430ba3242c10d7410141b1728dfcc138f6e66f3d99e0e2b896c88b6a251702d93b5b2fcc3a8d0f7e3c33673697f03e81360a6d636de2d3effaf537df95864b0d113c0e31dae7808e0937ab114fc7b712d9ca065483cc751303ea5538b7a33e9c7053194315feeb16fb00cd6abb40b43e9926b0ec4e2997e3058c44dc8e6f3dc4c7a48e53d11b1daf3d06ec726fa7c9d7b1e5f1919e072f1719c769ba35d1ecb63b70ac08670d1efeca5ce972f017ab6d220e2f20f20c65b12390f7fba12c4a23ab5a04c3f46ed2792a87fa7286c915e1db9752c1d80eb835300052d43c5cf748c9c2fa7446b6ebb04affaf72229df22f96728268b65a33f5171e061e15b2e7db1bc9434e171430df26992b013ff6222ad47ddb2a35121c5b5480757bae44f26efcba2c200fb6f21c921b3872702e9264693554bbfeadc00b7443d5e259230d2f34909da0adedaf644ba21f72d52cb03344854c89916730019d78648285f58fe3170947ca2080f01b17409672d95c65ceab1419dcb721d58c1ce98937c7d61d8224fb4e02d3a49e7dd6df2b721d0562cf6fc891add16a0ae579f49f2ed6560647f4c086d65c9f7159c8fb730a5ef4b6ad5dc38ec895e23b6ccb90807c38cfc7c52332f0f11a9db505467cb872f42a47a631895af1b6e66de388bc889f86c4b4d042529eafe08c2ad787fe107231c51b606e2a1076810124e883eb137242bdf0d2435b9dfe58325c7178fa1d72c30e1ad01afdb4eda22b573da6dd683500e791d574d335353404e9b14c106572618230f1e0f92918dd9b08b2ab9cfdd02608226c51c1d4c8c1d239c5b9a1f443399fb7c77e3e768f1bb73c19f50949e118e5d443f2a8eb3ddf5577f73d08aa7216d9e14b3e68bcaf693ffb919dfbe77e72b585db6cc30c94a32b0a63eed91b72ac0602fe070473b3cf3610c58a628b045ad497c4d6fa0b445b46411922891a5fbc3bdc52dd4c9e0d1f1e6decd72cc0dea30920730d67435e3c64a1b264a7d359fccc0e79f0d6fc50cc5664e078e9e873df1c2e3d82f9431414840e0ed74e641188186c1fe751bb8971f1b238a930bd9e118db5e0a6d8cf2bdcc9c46d5c3766729bce74568863eb538bce56ed64f8e7b020bbbb6989bc86c52adbf722e14dcf6d38badbb96201fd278b0743a7cbf9eac8b0c684e617294136a049b3d68935056c097f90bf86131a881503a4092d824573d470cd9869d5d41f7f088626918c32729a7ab635bfb9f71e0ae98ec4389d54f9bec7e95e3de4c506c60aa9e30db744502f82d59b3f288dba89f73bfd36c20ec7c85155ac6f09c303315e6d717e4e7c3e1be150d99181df13397bba354e83c28834500fc989e73d7ff38f4034cca1731e1e1d2540855fe2c0d4bfbc1e1cefa5fc3898759987ec6b459e230b5466243a3093b3c181557e81af99bffc0f61bb8518096d77de0a7ad8362f062a70b164995a1e5a9888f058d94ed7c6a074f8ffbcb572a4f37b31d456f641e31843717d7f21a8864b687e85ec286535148730aff5225568af0883ed7607716bd9d2ef563a5299fa70d8fb2219e0f5440fcb3a9503df88bdec50a8db3654c5cf96633a0aa4721d747fd109d366515b07aebb9653ec30bfffd6302e3e4167559fe30dc5c7011f034ff03191ba5d1d8094d0796895b00b66385b3e78895d02ecb5be8289a0f72f023352572fb74281e792a2d9e8c693ae170983d95599c569e74f1702b2c534658b48e75047ab686489b47a5c65472a0d1048424ab628c9b99693f79521b1d5723ad527fceeff737229d75c5ea213064ca2b845aa3fff85109ab8eca6c305481083ff7c9ad4fd815780ea3812ce11e04de280a447cf95549247434ddafcbcba72496c56f479df24adcb93b4cef94328a1068601a5c549a996c7449b8c189c2f2e7ba112428b9da8c026e09802dd04898f75e0815b38561cb147015726fc625072c9d55d686aa80d2264a05b41d75802375a1afa038845d6460320899181e47072c9903f8e51bd95a3cfeae79fef2c63e16692a0b98108733132005c65cf2a993f35c665e7791f03baf60cc2fdd36342799167bec7d38de704b036fc5504332d3864ee14f17fa2edacb229351c0cc2844f2a185050ea8ed1e18d2087cddb5f6b48094b5591a28238020aec28eb9b6692f4ff90eed3fc8481c2db126719c6e58e4cd6f1637be9c9c0891ca0c7b7029ceb4a2e94631264b570a8a8e04538ec6f7229fb8d8cf3f2a54ddb21097493c0b114ce44ad8b9f0fe8ebdbd9db0e1d87d86e19f6757b45f42af29ca5966507c019e82e5db985256378b8c99042600093ffc26314dec51b45e0c2fa3da3433f9c28a3c82092b042e7aa437a87e6cf3ff5fd9272082901aad6948fe4cf2bc4ae318104fa815d6fdb87e40d9a2e219f9497cf6c3af8b3213e22dfe93248602b0c3e6c9548df26094a08b62fc43bafeeefdaf78849f1760a3e8246fe5fd6aac718488c8dfbf51c0178f800201f6a2748b2a4d8c07211a8432e28a535c88a8eb8ce6bcfca535019158996570ca563bda4f96fcc3a6ff4d90e272fcd14e80f739aa7a6fefa54d741c6a8ac7f809fcbf54f3120e9c21549238a84bb2791fdb342197ca2f524696f6da9a05f4be2f840898a5acc9b69227451a4b7e03eb77c796a785d536db2e4c89d4a85cc3c49a1c65c55ee8e506a729c315fcee4a0c5c9aacfb5a9283b123efb646b99a84103ebd60d42b7fc2fa2378db518d87987e9cbeb83f6ec1b77dde217cc8184b21fa0fb18c4d41e2427e41f47e345d25eb0deb869d48944025a5ef9a11d3235e1946569fb58e14f2d6eb621055cb76693e67fe0a29e78202066cdcd186be1a30de05e18fb80527d18c75b72d9182f080378451bd998d8830a65b71caae31ca47305d55349d5a7c52734297254c011e8a08242450526c902243b0aa2463241fac64fa5c2f5c4b00dedd80b72a33a46aaf9df82f1a5e5c3f89acb753c25f7a0cac98d9aa6258d8aee3dc6d872c29b35c58c4730e2e997eae790c26ed87c796ff413a1e1cb9559229856c9ce726b7d8ba67a63ec6b1468c19d490783843264f4b91c3df46552812830cdf2ac7215e06de37335dbb552372d24e4a17815c6a14e0f0bd68ab0e57d6fa3c5957a05083c55cef40e1ecaad3db63ce1edd68c22e5f9ff29e75f6760c3c8e5586ec77246ae8d47227dcaec4339b44fe62464318d9da1fd09523afb1b0aea85853c6b06fd65aed675eb686e129422e08ba4d015f698a02de90763708831d605b5814672114dc726e6e5b17e4d5a0aa32097a6a021c526d6aed419ae006bf77f14f9a0722600562853962e198032b399a42921065fe88cd3a340bb8b95f54e23ad7eb7725df5836c2b8a3b8f19790c7f857c1540fc6b3f16322a785265c7989f6699fc72c54066e6941bd79c66022ec484f375f0ebad5b0515abf06f95fcbe09738af17269f976677fd463e5279763ff29a4392cfdb81c1e78be80fef48e9a37fc1fc472bedbebd120594b823e6be03866d768b51ce1d0488f88f1955a1dfb7f48c41d72af2185a249a54bab363b09ae6ddbd9785873ffc3ec5888faaec142ae349312725b9cd9298c4f5a8b1d1fe647bc6b4b41f3e0ec4d6d7d1514777e58816f7c6e632d5edadc526206db7b0f94222e89645aee667f038e5c7cf005fc579bf226cd72d9704a261b8ebafdbed65c5bde157d7cb68f05d1320fb4010caf95af803d6330f3e5cd23e59fc5c0b3691bfb707e8148dbb6d25b21bac552c41131619f02ce0cbd54e162d3519bf60fd330db1430dd44fc83e1b5e2d978936a6b5dcb4e92bf29cd200c195f68e399973e3d5ad64527d344e976817b8360a6aaef4cae9a2ae672d8d72c60ad3ba6381cdd888576d877596f4d77ee8ec27eff075e5ed1e85dda722150e674c141e17b8b3fe60e97c01c8aa35d7dd5872c4dc2556768cf884188354b52f0c1f7a7e4c387677efbd310efb3af5d33373bac252b694917a6baf0364e60b7563d29e27a4dd217fad95a8b1fa8da97332a4f1ef057e4e3480667bb8b727b8a475d4b8faedad3b4d05c15d17a61be188d83cb673f6426f62e9257338b67292b20999e10621cd5db3b7c184d9ef3e0ab758fbfa1e2d959c6d9476f3b8f0c5a0af669abb94e66998a265128303bc2bc492bd0f713b7664aabbaa3e04b5d1e6f1e71300df85f8b38001a947403083802cde816e93a6b29735a69730c94b67262b4d5ee6a84b96f7c9b3374e07192332b2513ccf86fd6a69a6f196447fb2072a1d540d92b263fdab98900ddd477c5b7826060fb93f35e7d2ef00e59a94ff072df7c1828364cdeed8d358ca2e41fcccc778101bca72dc415969617491e6f622a0cdc9f4935d6cc8172f8cee0ee6d98fc349fb549704e693a317d61cf7542a154fcb49e55ebcac5efa6189b4f89570fe8de99cbead9546a22e4a8625b9cf23c723c2c0fdfc67bc4fdf693373db1f3e9c257da5ccfc4a8154e36596b806964f048376de7390897a71c683b9bd471a5d017ac74a2b4720a83b42482f3c86828e86aa7c96aceb50482ae1dc7226ae8db0c3ca6af496cec5a1d2e9599609e0b25912e6421692bf134d9b00db988450e6da4c35bfa94f8841dff9461d4bf209ee9d272bf863b2c9852e6d9e073e0d5c6a8aa170b37ef55209c9f8ae40c0fd575bbed727a3a5b1f38a2315756dad0f5ce57bbe161f51ee1dbb0cdcbd0cacd0594f1b233fce6a592d90e99a711c6c8a021c3245b49fd0689a89daa97098e9e2bda927f72e4955c0c97ace858a6334a2cbcc81bd6ec2734881c8ab0b95cfaeb12cba98372ccb315689b6e5b4bb6f255693e8abd656d2f794be871674bf64122a02f730272daf3fdf2c9e00cac5b07655a2c0870796b1f7001d45fa284bc32a5f60a2b0817151f4155c3dab9395772f945a63b87d59175aab0c1bbde7a98cfcfc548bfcc72714574e5fd318685571b0733fc63d6fdb314584c8ef1067ef0b8054392deb86847ad1d48cd1a4a4df7d8463384413e08e3b30b97bc0e5e57a889e88fd6b9795b5e6930dfad2d7ca2b6fa70503fce6a01340ccc8e54877c418908f65d69230b318ceb8400571be620b17fcf4fc16ff184f9b0cdb8fd6517849a603c92da0ffc1890f61e3949338574e75f21efd0feb84e1966501585b4e14d696b9b5386efa1727685960cc47c192e28a7199e3c3636afcc0fc7d68d4d8ca37063822229b71e031883ac2b893bed7a77c5536bc99f7afad59aac8162064b45c976facda5e6613bb40f8911c2781f476ec23eb56e4b90aeae65b4c1c1fee6bed12d3ea3a246621a6215618902e83976e0a1fcfbf7edf4de8c30fa95a9874fdb0768d4ed02eac225fa9d0c3e4ebc71905e71523a1a97496dc1944774c13d1c70899b0b6bd08b5472f3bcfdf4f76d54a82df119e92bbc708b7c3c8608b2857531b480bbce04e3e12f8b4563ac87b6c558f8be8c66f166283682dabce234c77f61d6aaac0bde23532f6a1f10bf72ff8786bdf65134db1673880225c3b43fe470c150902392b00fd1720a42b30c7978945238cf74be38a46fe744ec7bf74c9d82ffb7bc6500ed846072bf8c65d66a52ed58150a345804cee142865bc1382e7edeb252d4fca767f7ba7272ef8074ce52c8baba5f9aa6582fab08f3ff4d4a5a402000f6957bb2f340447202fb2c9d058e24dbc56a7a6f0a34a8c7182844aae6da758b6978155ae743c72408c10dd02ee7ebf0302bb903230247da9739e070133bddd5bc33783d3dca04723e53a902aa9c51728046d15a0e09b9093c969649d63a269473dbdd2836b612100e4288c647102fc1e3f5c23cbb39e63509b4e4ce52d1bcd9abde61c7556f667211915f1b2bd88e3c045798dba2859641a682cdce8589f2abb04a98644625b430a156d9a09ba4c6a07c0092862f17094af963dcb7eeb2f3f1e80ce68ffbb63505e6758c3cbb935e3ee43b9ca4937aaaeacfcab7e3bd0d92044e8fde59b385b10c3d63a80123113095c19a676996afc3e9bd2107c170bc9b555002ffbb63aa47725484604183e126e67f08cae8c93552be85a01e8639fb8dc0fd9b28db53826c7260c1eaf2af9a8203a4d5607469af0a2380877966bd723003856ba35d9b8e7e72e86e853e9a77c48aa24f01827d357227836fc02c3a0970d98980797df466722ea5fe26ce357cd749f706d150114f364e4fe73b3e30cb5e1a87400296caaba272c97d1c9ec5718760e3483b10cf9b5a73b91cb1c6b1578e9c2bbc4ed7f246e5069aacb8fb631fec17709c7b14dd2e1ab6022cb574d4e628c46e9cc0a2103e662e69712ea54ed0809c5f8b53bb567eec5ab0075bdc2266f111a4873985f645284aee56a2f722f2e90876addf41fe8e7f04a91058a97395d0fb32db57d8bafec1726909da2913765e3b8dea1ccf9f5edf6f5e14ca40a3b7e22f32b01d56de63b3722fb64168b38bc614231f7ac2ba1a55ea36f5790365dda9e4c78415a8cbe62a72f0d5220a9c8aafefc7e13102de5def2b696a02c70dd3fb5bc24ab29dc3b68c44ea7e7c909dcf2c6c346b50e429bdb46eefa5e5143d07362d9bcdbdc753a6b7200ce70d364220c9edb892075214cfe4be5497692160c5682ae2e8fe20a7f45530e0c1c56d07c0e7684822a4c50755ce1c748b8aadb5a7d38af115bb3d60de5972957c432f5b1d43aa163e23a675c65d5ff478619a68af8b1ca6ce442585dcdb5c23e6aad2ec60eba2756c502403665337bfe5bbf8f693c2b79f48b3f490969f02ae455f3cf98111e258beb934a1ed22c414aa1e4e9461b31243a702529044a8722f736f9fd1d28d7ea5eb1f54c5d5dd45afb8b05df67a12b4c44218d92ff271726e2e99d2f49eb6c95b92bc44078a4ec5a0e17646fcca2571c885ea39584a2067ab998ec1a9853ebfcb951a76c753264553a614a1bb15a1955dd0fa62c71c2549118a7912edaebb9df3e643880e515ca8e14de83836f5beee002f823c34de7e06163abba34ba239ceb99ab340b16a66363c3f8b957d1e8e56b23c79a41d72672677a6d4ca836ca5f68641d0f2e0031eaf99f3d845774644e7cc489fcff7e6d47202bf151d9ab30bd78b82682cb2bbc68928e28780075bd8f323c1045876f180720bb12d134586ee4816c860e7cf00e6c949b4dd254ea15bc60e237e25a195044dd749796ceb9bcf37e405e6009bd94a2ca95c033485ecc725fcb562b22806c272851f3e7ed1c5dca03c7dea5f8c4b13ecaf6db86b16d0c257f798cabb8a9d4f72b546b33a3f1a8eac3583af6dc55538d73254922c1a6ea73faaafeab63a29527237b685932f22515a34e51c84e476d3527cf760e33b6370ad4a0b16b63f9da272297e241ed0941c5c87fcda4d19864eeda979622f53aea47a20a570bf06e3f260d159f665f0d3fabce697e1a687fdfcfa98796ea982d85c5c805090163cbdb72a75fdc1f1657e8ef7c898d60ea36cbaee29175f129a85d54f0018f04244adc8723775770f8bef63ec256a7c190dd1696fc916c1de8cdad8a2905145df53677d7261e35d7e4a15801c3cb4a861663af7c2d2dbe82c298d00a5049694542462af0207be2cb7a8f5678792f3fb715463760cb3a967ad19ce5914a2535d7d4348f52c996e0f1fc0c319e801bc604d2dacf9c113f49b77710491e52caa631a9f08061e1a3f283034c550aa3d29014d7c06388891789721c4799eba253c436a6109f42cd14d0c72ce9cd71c78e0a727651efd74feb986307aa0954c51cc1e3c4c591c3448bddca2a601fd0a71481abf3f5c504bbac4b3d60e594306f0b52c11e0f621720a6ed1966727bfab8f0a6d0b88bba803c4824331dddfa6e648cb08289e7fe97254c018d31257fc6b9b430b75ccb42345e59dec5880b9355fed3880dd3432ab729d080555c66b2a9fd9ce9b430a10c7dcd8141f6bf6e3423b8af244257053ed093695bdc7ad998205ca8d6817d8408dd822890e7676d1d71e9421c519eb12ed7203b748687a62e0a42978ef6780a8d0c2d27ccd2b4619f077692bef95f2c27e72a7b1177ad742a55509e7f4dc346822cb0e4593ccbf2ab2f5b51e1fbbba604972d4b901b1e28f82dcd281637ae20f76fb0ccb305ef71135794d6a308180c2e972ec2bf2621749929fcf3cd1f1d442e1cac11d2e8b81e17ac608ef67882c716716cacf4afa03fd5745dd80b246b1512505f210f74cb3eb4d7a62fbca3c3474685f95001d9a7707c17f0cec63fc7158f89e7ee2fa299481a208da2acb9547dda64201d8d8a6a41f7f7265d75766967a7dd6363567098faca4408db93789e9764a390b51b5fb32c2083ee530bef50f0ae8fdad3dfa53a86c2f5f05c5ca11ecc7c17248f19133138d5d0c55f2b6f97206f4d025ef6e6d8fb750f5f8ba9d2688c6595b7d81c4975b2ba43906f45f0d13b977dd2cde18b3c06c6a70b8548c0030f200532dd1895d5f9b15532917c242e2f5a3589cef76800e25b16f279f3aaded5043460ad2b698445f300b07fd3d1069fb5b871f7cb4f16ff77851ba39728b3fd58834522a9d65cdb93262ded16fd622e1792893de4b9643fc5dd5c844e41473ade553714888a16921fb4fe52330d6eba7d2ab43f7dba8818a9b2c98a7eab91635ce72ce3aead4b361ace56bfd4089154f87516b6d3a9841dd6076407a21cc38ea850c30022216e875dd3a5c44958a144092846f5093579c72451a2e5ede455b636d720c7b63a5394a2d16512d94e0a9c5c5c0c67d3b0e679951c0c6e7f38414f11172e4ef304715d37c2bff139910cbc1c0e61dd8fd648ba012264eb6747e2844ea42bd72c1b076cf15fe44ac68173c5e3cabb7e9bd63f24732b96bc51dbd4bdda947468f2dcfe90bf524161e4f111f162eabdc5a5877ee6f39abb2153bd958809272ff1300cd0592170b57ace855bec9166f3ef7e9839286491c1c2cc6ac3bc00f7251b2c1047d055962288ea96d5871f61e246ca049f67bf0f99feb4a37cf828600536ae1d480a242bdf283b672a473f48455f2bc5d75925ade02490e53641138725299d90798837684d8c507f8c357f2b4006dc9f3d60afcb873894ce468d02a72af80dca589002ec12d60bd44c2bd5c01bf624e2c0dede7d648a289f00c9aa4720040446e511d8be942698245cf90af971f6507518f34ed5a19226190dba380153bc964cdb2ee0bad67eedcecf9da38c8403168e9b3946c98213511acc03204369482ae0d21fa649910db9bafbe3f57035498670d3cc2cef2ea9e8f74410def11598a2ff2fd50fc4fdb14f4bf73321774c3ac1e95adcbc7d60c2d1862d815741eec1042cb7885df80413a7020512b77cd8f71df4e7cd3b5efe4cd99dcf646a14f9c6d89a5cf959c54e202b86a77b923c963ec709856fb46c174230813e4705272328724d3ea2ce394867db52c1b078258964f1e801058aa88de816a785edb8607f504b3a5123b47acbd0c5790d839a071b474aa88ee8e9a41629e1a87ba638f724cbe5e9620fb315a21a206c82c06e5b6cea60d942b07c4a0cf27c830b09adb7232be3b4fb9864be64b4efb9ed7e62ba1b995abdd1dd6f3366b1e7b4d1b7420637ddfa41be991f776694ba235a6c78b510b71b9d4173aae541cf80c82b571017200a14c7861552e6315559b051f4f87c462dd4b046c604d1879d789cab162a218a379c5e0fb084f174d1b60252314da1338917b952ce2e13ccd4b2bada5b2ae72a04852ad01bc0ddef0240addf3f39049e334ab2d40145c5083da88431f1bb84f2a69f56d27fe961f2fd8be90473a7316d144dddce060fb1a59887defe79c8c729bbac7223046b6d3a039d6d839e73d011dff3f409bc1aabcd0dff04577fe4a3520b0cf39c3c2eb865f89fcdb8c04ecd2941393d919b24af17b04234f72e01d27e5b94b5a001832b6d4e2b0f67023cf77cbde5944372502fe4caa94a87c8d657215703f9c66c3170e9526c9d4f6b6ec22b2c314de33139ebc1bec10876d1b8f72f9d738214d697518d5d87b7e4a0c147487e737ed1edfa1b81761b68448dfc57254dc641b4d96f90398d7c98878646893a103141b9292fdace485f0f9af4b4a37c6f39883d59d09c15037cfeef51df9728b5bae42dba27dfbe3d5cc146d2c1672d40aa71fdd7deb9f7f1510062e0178feedb52199a29af1cb0bc722694be81369d76883ba396eedf5c77ebd882288fefbe226e6234af0da8147b2e707bd382a72cac29b6d0e1a93de12ebc91a2400376ddeb0f3d86571c071ff1e748a62a144722d3a3d467027678f4ca53dfe200335b8e8f651ded353be4143659c886209904085f079f0efc3660eb053ff62b793b81a1ab764b2870329032a29f96c49757372b5039140b46bb7c189d4f21ea489a9ffba15341f927be69cd79559e8c9a2c372e7ee2a1df258b0c3ddcc82ca4e0d7b0423aaac50c6c31a36c280c58d1fe92872ec0466a293f48059a8e82378ca07267785fd276f000d83ded62cc0d0f663d62e869a502982597e15262582fb5a14d282a28f30f1d405070ce5a6ce9b4a67c972ba91dc91c394da7c661a7a4c3cd45088352ad63af1f0439290974104c679b668ee86f4a55b863d1c0b26c9e36573708239aa8583cad28c5fb056e24740591002693016e332f61fb071038ce9202ef394a45e280ec7fc3e511a9455440182947226fa1d67d381d6e74c643b97aa78b147a4f8f97751a50fba672827d635fa1a2d1c71bd80e715e8d7ef6937d278511bdd8bdd4e8f9ab74cef363db1826d06b1722d596499796562aadbfe583a90b29a96d97ddf8f14d894463f5a24e06577c87287f8c2e68850919441d86d6a66e6c7bad1b54db681c3a5a82928d72ab5908c72ef8df8db9429e2345c8e1f447429f7e622605b1a87e9907e36506bcdb99d6872ee958fb30cd71dea59c325cb52a7225eb0766968789e0705130297e2b478e6724447032860c704b273dba882b9be0fe764238c985a5d528e199e54fc3eeaf910ccbd85d9a19348a4ee84f72a9d43986556c77c49025b5c80a78c8db30019f354b836f1869658c0dae7ae835418f6d6b42aa3716677831c99c6505ad5e2279e72042438e2fc631763c49fa370ffd68d08ab818888c85e85cb35b8f30bd35f73729f0c83b9031cd1cddd8b891eaae6114dc091d0ea707566fc90e2e3d78402e30804a9b34e1ead8f8192b0a9db38c76c4fcb1bed5172a0c4ca48278f93d79ec34ff9c1b9d1ad104ddc07855f6f0c2245a70e813b45d283d4d71e5fe4459b54bd3ee664b3d88205623081eace3c36052f2dc8d6560d23d4028746c01cf05a84b572f9a08371e458e7fd11cb26b1f7a47a931bc6e7fec799c61af796276ea94833634d93d08332b18f49fd7ae056c710c033f73d20bab278efe1feb496694d59613077e0850920321f694864f30d6f373796b3e8868858dff88832d687d0a6c0737269b1225421479caa0450c9bfbbde125a43d359184680b5db190e2f78512cc1720bfdc1189596b2c35df12dc8f8eb333033c998e6918a9f3a9bd6bdc66995ed5eaaeb75ff409185c0c24797908a8c7979bb620b726f37314a74202a3847c696487b558d7434a1b255e1807dc284c93bf14227f98d3c26600e1e65dacd2ef966722322ee324b9c0fd5db6de886d597a4bbbd05a1d61215bcdd7a4601c716277f72e0c700a51c9f5bb811406423c79cddf2fc192467a1b6139fd89edb438963cb2223ab60a69210d14e6ee7e87af481317b5cd00d324c0f3327d423c7aabe3555725d546b94a98334f291a2060256008f9eb29e683a2609952e8e2ff47475784401636d9d39e168b7d130d75ff64c843b7874de23c5255c46975e5595a85511ec2c4997e34c7765119ef81afef94fd467c7b784eefb393a8a7009c4e7a14c893b727468452bcbc8d6049f95a3fc0919faad19bdfbfd47d007cd295a7176c3ed8972699f3e7ac45e0d6a8b1fe3135ae329225f01909f432a6c218ca3e1ccb770987293552de632deb62b15be597b1c5c7f7065f41d3414a00dc4ccc38beb817629727ec28e6506bf8554006352edf6e8b7efa75dfe70837f9cf45161d75d625ead17763ab5500832487c456b512ca4d5e6ab614516a64011886c07054289331aa87274ab581303262fcf87b7b56ba081022b57ec95c2bd6122fbb17157111a3ae7727cb00c1bc419fab4786261a24905b64979ea0ff0a7fb4cb98fbb6a15b1ca5772f8b3b6bd9a4f17904e5fe21975d821f06b3b30b7626a778a196a8fc121f70e726233c4fcbe1120cd9a45591d7bcaec96e64875604fc7901170cb4eb7d74bee0d9d7d0fe35fec935cf50d0b703c7eb225c135592ad990b9a8e5f48b9106cc5661f17ff3000f8123402ca3a3093429f33346bd4feca91939858c85d061440ad3723f7ecd523a96b9ffab2edd6e71d2a59fbf8a4f59a5c0ab4bf3ff9aed79d244722417a5f5206200da94a56336d4f2a8c4a8dad01f3e285a25f3b85beb1d874e72e204b90c35a3e6446281f1146ea55230eeffca38678c26b6d09c8133e8ca07720636e89f43884ec52997160beb55ee068827b4821af93d47125a2d653872b372e7d940f88c8fc3dd35432470fb0386cbdff925fa945d115148698ce42406df3ff3680c61beb83a4e146c3fd4da5c1487ed0e8a644ad6b9bed8b676890357dc7297a30ff445ceec3629dad068a9c1bf41a66d4956848326b437b3f21b6e6391721203efc789d845b6f5eadbf3c5fa930008a4d2ca72119210c614bdd9337f2f54bcbd0dde1e0b6412770fd20b8f0bed9d563ec07cf10c50730026bf8b9535ba72088ebac666ae12a66308ea73a9de0f935a99c0f0c1146aa73b2026b482d8af72f02c40044d2402646ef8d1f6b5813d0b282e943e944a1f761a53e9e313c4e609c04dce8e6aeb4b1ce1ea1b8e18f1907ea7677884669778adbcc59bd42209b355c39602c8b7e09fa7cb87e28eae132f21ca5be0790854317e7edc517332fbe072c9cebc050f37e97b7251164e1f067e8f36d597a1a5aa1bf97e42b8ade122f52105aca651d966a201c097a3443c49f3bb064707a2e3f83233c8692927ee9f2d72122808a67c556babe987a0fb16ad33008a75278a964e1fe2ea832893e8bbd7166afba3ef388e0caac15a36e346ce5d63b92851fd760ee0d198db05febe9f4b723c01378c2de9a18fa368a1b44056af558efd7422d3a4814ec4318c8e88a26f51611bceedee9d02657cf56500869eafd160f781363d65c044ec412d8d19276072bc8c442d6dfb324e8c9c6ed8bd6e0769b332542667f3bacf628298926d44b805081574c19e62c1bfcaeaf262b812b9ffbb2d40de4cc82e574e2c1003831bc9728df7939b2b4639c74a6d6ab7c62d18f9e60458c68686a7a2006a38b36af84172e5c4f7d6ae23e90d641a0f904952d434bd9f6ac3489065f127a2d0214a7cbe1771be43a1df2cc63ff03d17711a8a9c36a12212df4ba4eaf63413651ce30dc94df8bbe34e275839779da193024c34565f3ea21552e8b9e8495aef191b0b00a009ed3fb49e977325c7b6266d39580fae0d2d8dc09cea8a4a273e21d2781990025b646dc52b5eb6bd548e39db3c7b16910e2936c29d768acd18cc09bf2161703d72a3ad3beda3215d73db2f545a2f8366219797bb88e593f31ce3ab7c6c7a357d72758627f8813060604629a83ff3a0745e74501dbe6fe431a1ffbe5cd929e61472fcfe7b1fefae23995210fe931862d47ad719f3983e9c8979d29a074f0442cd38d978ffc5447aa1945c129eb120216e2533c876250b405c0c168f317e467a2e7299ead7b04add35bc281662d52fe6e582c9d982728d5d5520267c9450ac075b7228aa8e93d9a5a22076ad8dda003aa2ea3fdf14c13e0cf05ceb535d81a2b9b07255f537213d6abe8cf3ead2e3e3e1c3c3eab2abcc36b852ff2e74c2a6dd9f6272e3593d60c7b5e777c84d8e5e072c552d1bb1f79f129829b8d5e136eae1b94068aca913a8dd551f5479286fe071d87b307cb4e5b9a80ceae6a1c9b643ea7179721e5ec19ea467d8614b08bf846c70c758e3361be4d212b32a881f5715f7619d05774982b4a55a6f202f1af9aa019b0f8882b3d9c81a37fe54a1a4b6426b24456dbbb51bfe6a0180b1f251f60202d52787679da9ae567852d88a4f24d1628b877229b63b8fdfa9d0104d01cdc38fda930bbbef301244e3be99ba470bc56bb94272559958c28a1b24c6fdd39371bd77e4b11e1f5c1498ab4e40490d8c91ac4ff2233d530a4faadac446f9f70b43b58e137e2bf48b63a8c295d976468d3a2ff09220951530a709c1a62456c2e1b7d5b270576a35b104076195309055e13eeb601e551ebabcbd20a8239ce375cafef0eb5f135bbd060c30e1112b67a3e415871c11728728bf7ebbd9330fcc207d4d8d07b577a0066ceb1fdbc885d6e2d236ab8ead72f8c364d4329b114b31fbdfed0a35bfa8a1dfbc222eda6ef9421bd94afe09d34f29756ba672ba5d57ead9e989ff77f19a77cb07cb12b206708f9a241ecfd3591563d894948f374ad23062fdc94abc0680d3d9e858f4880850fc47b512fc9036696c7524e878b8d9aa9281054e28138668400a6e7495260da4937093dc2062fe4d42ef8a09c4a1ecde1f37932f90a6f5b161b76160eeeaba1eb62ada6634b914619c837401ed96760a4bf6c1b2a4c95fa7ca1da89d2966aba475909e3d52d07172ee606b872713265c3235f63f5dc75ac6567d5832f84e55cc677244c97e18d672b8903c17b1e3fd2628a393f0df0b4d2ddb5803eb749f58b0d0cefadd134aba258148234bd5b2899a4933e338f161565985f98591794ff1eef2659f91154bd448b1b35722507633e58ccfe3a47eceb9c0f2ff015bc0f9caf40d3a24ef100ab2726aea1b535c4ce14fdb017a81ddbe3d3e51bb9d66571711b1733c735f102cb33f81c643b445c7744b75be2f37a2cc877b6fc69a220c38c4863b49497e7c8de87197c0df6d72a0384a0470f7aa931749f930292b57e4cfcf056ca39e045935754dac4d127df78aaa4ee4cd85f238f12948f913f54056cc037a78ed67734ee8b1724e084bdb322939ea7631775ce93f2d8fb18fd8905e8e059a7f834ce004717d3d9722b91e8923b8c8d18b3f365fafb2c38ef68a8e7a05a30d162b3f2b88903107818fd8db6c424c801db87739eeddc883a6951eb893c1a92fca40b7aa3409124fe3e73d98eb865c6ebb36d72d7efcd63282d8e3f51b5dfd6050fa69c902db8768f0e422e4daa3a5e73fa924548cfb71960086bc4dd1c1adb511c456a058099c64ad9325e816bce0a611ce55c9ca6c6faddf3801f4c8b4eb3656cea6584a33a472ffb716f1cba828916f7eb44204fd87c4d4a865e593fd1070072ca83b6b517972194397f9610194055987ea16937dded26adad697708e5ee08433e9230fad8347af13fc607eb57e808c412998d7e92322b96fcab0cf99f21b5899464705363435e5cb3f7c9556fb370803d9d2635113ecbcc74cebad2a7439720d39cfd1c9f1717c45413453b3c03321b07d83c6d9b65620f536bd71ca8076cd560eb92a30e672c3ec083ce8617e5b968fde01999cd7b6250da7bb0417bfbb389a9eeaac89d0454484bba01cde001d8b46b5202b9b2b2475d1886ef464323e9583cee0788a19726fbafc2b274ca16628d0a1deedd895fbfe9da412ce1c77eff3aa91fbc52be97271dcd0e8c668da02bd40a3907d97d585dd2c5acf023bd042819acf483f5b95463384b82454d8977b13ce478a26b7304db987fdc27ac9ef0f2a02aa14588357033ed95f1ec63cfd61c1b5d485169f327de42276830b45353105d12b1feeca7372e4bf2979580f57208317954990d1516734df31725c1707f61bdce3169b731272444ca1ed93f39be94ba676264438785d1e6f775985ab83fd0733a15cbf53af34c95808c7468e114239e93103134e5b0cc09097e04fea66801363f2724fa5a37256ede2897f4494487874e82ae49d057e84717fc5217c6614ac378e9446d6c0721ed9d7fa0a9c7a5f9a4cfab212df4313c32c94daac18effa30c01419ee3401725652fab8b0e2b351c67f640e55eadec01d25e1dc3ec468157869c1e0f736810b059eeac85d73d6eab534c1fa9a4445803e87e163d130870586b5af7187cd1f5bae26fc5c5cda4929f4e0f6d0addff741d70a71c32344fa152fe538e8e0d6db69cd2eada0d95491dbb45a4d9846fe9bf45f31121220f6adf932d0efe308f5a840becbe7c3ceda0ddfeb5038f408157f4648dd16a5c769890632070cfd8965c872f5a104197c340a3736d8f240c33d24d422018e6b823bf270bdc21b37d8ba7872a95b4cc381826880c82a00f719ac7d949d50adb657049f0aa82d89f7e8d1ae3588e5412b28b40478bad2913b3e901ac8c7f3892f83f28dccf7232930a87a2872606a28f1c24c5fa1cb9c146f230a63244218bab0ffdfa5b6d7100d982c9ab3721df4cd970f0ed3ff70dd4829754a3ae6de0a91c9f263e11b8923f6024d8dc7724ad1a38d380ddcae00e3ef3408cf349ef3da7b35535f9c2aabd79b87b58c0d72ec804592da0e03ba46b8cc43e28f58c1694e1faaaa58ce04dcf6a47cc41e70728a5d39bfeb115f4a70b2071c7aa5bab6bb543a6763c3cb2a3f20c70e1f99d472e35a31419ce6e04a15cd1a0e3d5b5c162b141aa5c2dd2aa1dd70a189752089062660e5482fc36fdfbb5b21887c8dd3aae3b90a0d4236590b6b20cfc7a638263400fcd9e1070c83ccd7d5ad2dd177d71b1f92fb579095d133e903a66e179831721517f497f987ab8ab52ec2208d7a0d565184f2980b10157cb2a8affe8ce1ad72e3f608f4c2a1f1a5c1c35bf64827bf4a5ed06a9925f48f618ba2d06154f023720e227c2001289eb5dcb554d81298a3da7e89b53cd759b16f5062096d54a6d60de39e62a8dff969bf4e8a4df72b540e8c9789c068b6e59db794f050707f8344723292c9b74d640cc6ca1ae62afd9b9f334bb227ad8bb6dacf6e15fdc1b68f852aefd4120c5a80569dfd0d804a88fe50d59d2daa289fcf1384ab6ef75d53e0ea7241cbfd0bca44052f0541a626fb92c79185a4fa668e96799e239a0f35e45e9872571b8eb5aafa734c4deaffbd82cf0c0cdb996ed73ee6c47c112b270f8d9872727142d9312c14069f5a77b2c13b2756a80903e0202958fb4cabf04b3e8ea31666931582f0b61cf3446495c55c9e36603abafb00e5dc5d16b7e13b213c726f5568908616e4020eb845a55da1675e4230b10f74a3f98d199cee8a2f3529381c1272e59b2c96cd8a9f8c4ce70db5caeb2b7f167c69e74ef4c4ad2eb946a89d5c89070845d95d7fc1139d71be1027d24152943b332d2dd7dd7ae482f668bd633e83516f1d03ab69ca30eae330a7b874633062754f747e893e641ed55d56c1a62f0c15ece76427676733b460cfaed3d60dea0e8eb70507822f6ef22a2bd4d0f9f835128d8de2fd64c4709b9830974958e754e6e611e4060e8fbee921f6aadc8ee5975690af9ca1495d5ac190dee55a6eedb67dfb7df47c5d290c120c04839bb6080b7260ffdbdabaf0445151fd9d85a4e7e7cd7cba6edd000758d70d9ffb601ff89a06c446bae167a1b796f75da7da5ce04306c6c1d2ba242a8c3ff7f07c6027c97b4fc16b22a52a3cc172169ec88aa0a769fd358f9ab7d912d264b8b8726d370bc272ea057cbb4e185534a1e28e28b2744e2de3533db01b91b00ef260c51b3c5b2a7257d63b8087576f66e44bdbf684beb73cd5f85e01452dfc150ee49219cee045721bc4f852fd2eb023dd14fdd81a6c52d7eedbc5cc3793297fbcecf2a814feb2724b5cb79647b74e1db916eb230bd605c06dc3143d97f70cd72fd190cddbaf977288b9c5bbd9bdd3f6035a0eafef1d43c80725727b26784c6eb46fb963a9f24633bfd9f782b2757525fec19015bf042be3a8c5d70b8a788f6ee6e376b008b46c6ce7228bdfe535c03db615ac180d3d643772c4e49adb436476ba468a0b05162065353714f16a2b57ca8e1a832c963cd487c7f3a66d7305af6ccedfc333b61af4634a13db5c7c7ca83a77c774c38a8673b158ebdf0681ba183024db8bd14796c472e034a714573569b05a2a4514c97cc5c903b6960a2e55ba850de99d4d036bdf1b9e70478992eaaf7ffaab76eacae2ab2d9bab377b3e95b8908f7ef754df4d170edd7e8867da547be5bca5f00d9c9cc7e1cac6dcef11483fbc45501198bdb68c722a9036134e3e02d34d70855fc4e9ac62d66369fa6c84ee59ca117778229daa66fc7f997ad9203344edff1373c442c22e6ccd44948372c7e449ac2a5e5fb29a42b7fb97494b2ebedda20c11937b532003bc8fb5c8c29c489e686f19c673da875867322daac02d46c88e86903f849673e228c9e193233bd9a458df0906b7ffee72959bd1e2aca3c66257b3af6e55f225abf6313e3f509cc6c0d5456c972aaf82724ed4961838e3e76069c42e1509d4f4dc454d44ae5366d2e6e11f6f6f8cb03a638b2653a5b20e789438d8dbff4cd370bdb326e622ccde1dbdb8f826fd38b6c872a5d7e149adb03d59860b90570723bcd3c1672988c61cac47d54773f63856240718963423d998edae21b1e4104a34a96b2212c267f88afbae125959f1ebc70372bc0628a127e3e0ced82797ae3212bae8fe9ae7133669f21ac2c374b18a75b7723d7e969c34f922c76a633eff143df25ce1ed90c66cc44622a2617e81623b3a3c4c5ba428b36dfbfb2ae594f221b18da9e4556637a700448c443a620b6e160372505061af28136284f81232db86d27682d11a2ded7d8091c0d9a0681f72f2ba72fe925658f145380fdd3595a8014fd70a744daccb6430e8b42aaf7a5febcbd4720bb5697a907a4070b6ed83f39736a6da33467a36c835e0d438b60366e20d2110cf7f6893f56f9a7edb22f4f3b80f46cfcf125369981ff3819e8c3a2ef515a072a48939188b2a379984e9a54b865ee8e38ac030c5dbc08abeec7f539ce6a02972c5859528f313f8b1a17bb887f3ed907c6286474b46ab47c00d04c2de5dabe17251583a09b0ffc10ce5ca6713e6fca7e704c5b3a6cbd6cc8cd87112bec0b53d601e0d7ec7a885818d7f3893daf2af32a812c5cf00605c0c5044625311b935e272b02c02bbae2c5a922aefe17b4068fdc57e2fd18bf87ca0f678a5d38f39557b3b1f3a94ced9fed5a43f00f9a15f9cd7aace9d03e8e11d28d9c1fc40d2b1aece72989d4a103bef39729f45514b1396e258ca31514ef4fb9746238b41d08b9dcb72585f1305818c95e631ea40418480f412471464beccba0360373ec64d8cffb030c110cd2392fa34bb2efa6b1028e848876f6506904291a0b8608992eb89f094148ccedb4d34e0dadb1709c8458c8d7ea4f493196d7954248c854a62ab821c7e720e57383a0adf8d219f72d89ff0e5ae8d24d85a1a05c8e2b3f912e72903a91e1f4e3b8c2a387719beec847d4ce38f94cc3f9d10cd7a4894c234cafe97380657366d8ed4654a769601b67c5ce60ddd0f26e36a72ce05c27247309b81661bd35c72339dc4a3b50192888457aae5bc40f83e5156d9e4bae21071760fed571f40bc724be21e9714cadcbf5048a4ce990dfbd6e8b23b056faa887abc5bbb40e24a6e0bcb7fb778f26e924453c9b695b6dbc4c403153346bfc6f297773ad37c315e55208755a11e6ed2320e85d126d1c2b9a7a5402796b6a3674f91c99df22487abd9723e484426594ddba3b99d4b080d64dee75fdafc88ea49d6fbf4811e50e442f8721c9b05ec1c764d90757a9627fa3ef10f01c86105ad06c7e80978fc65977a1272ca2d3a8ed48349ca79aa466ad785482091f57ea7d8f4e6d82437e0c676882f4e23f692651faabdeffb35060dbaef1e2522f37de827d68aab81f630988993a115ce7be17fe29a80778266c2f715afd48695093c87885b57a30aec04d17439c272bb5f696f713718ffb170fffcea7b55d6fa742f415a817826583308ba5db61d72d69eceab59031a57566d87b2334915cb5cd8c32b5506bfedb193e135c81ee649be2ff9adfd3407a4acac241a44b5d47a3de5625a6ee3415d8ba14acf270b4f72fa17db996f536f7b163063807a1d9fdb46301cfab2488723dd10b7f3a96d61647983648cf4f3f3c778b20628b02aa9ceb351a0bef00d0c87b63b780eb9de6072e2192a73f8c4f3ed3195816fdc29cad015b14172977e11645b51c7a9cfa2d472a42c9417b7e908cc69ddec375865f97351f44fb694cd538bc2df8e49f4fa3672d0db914aab6ae54ab97a521c2b7400f899acc74f4b497e64d302e53789888a721244ef7f5bf13beaed917938c2a063bc479d3267914d20f46dbbb699203ff972d56da7547c78b33a477c5ad04d3754dd68e3c674bc23e505e9751da48268031e860f3459a26af750c4845721228616b09653472099f8c5fdf6d2cf9258ef0b0beb0a0e71ac135f006d7b864eea7ea6923654088e0f96a7e51ceed637b836ce6e8bd5e5cd6bfba15feec9659d4a08a37ac9ba9508bceed98b07a0f7531026dc72992f90914f2fe085c1d4b81a20ad9cec89753f1b38fc996e1828c6a2a48a12725473f5719b462f69af6603fc03595c4295fd9878632da07f7a9245661711a772abd3b6c36be146ef8f87ecac51381947f06e849e30b13304de4b7c4ea170eb729f93ccc18cf229683e7c1c0bee8dd3931ea5ae7478a5a97fae70c1f1088399724bd9eca1bb6036515cd599c95093f2ae3ee298966fe051a76945cf9072d5c00a217c38d3cfdea5f9db2a0cebb16580f059b44f050210dabda219a7797926fa5d5e0bf1054ebeae68c37aab62621366e3a42bb6bc83e3fa2db947b95bb5d09c72d99c822e33181805cf1aac87894dfc6e92d8f8851b3f43317a7a2e6d97c3d97252837ba2a31dbff81da5703d700339a5abccc0bfe337616d972be44d4a61aa3f5869d7f00d25760da4b6fc59e463c75d4b4565e1c6fb37aa1e5661b43e552a725fc60306dd83b1ffef6a848ffdc023f2b77e2cfe265f74a9dfec411715f08572656259e82a9b3fec4132a48458fee95f53133692cf083cef6c1e3f955c6a2863d18540d46897914b791f80875895074909a082c44f52cb06b5122b9b1ab00b1c856162be0144bdbff61a32cdc26d3d4b12ebf884698b5f5fe66b601040019652f29d1c0ac4293674934606e2232b74717dc308e7c7b99add236b2412f87f4766883a5f81f6d1775484b86276ae50b3ae2acecc52ae47e1787388ec32fe054f22e860597dd8837f4c7819f11eb80d07cb0122f1a9c1fe3f29008362a8ac5d3e7294d275eb67a9dc172fb89143a0cd711074e089fc89298d01a5a9fe6cc2e226578b4aba4f85288d3f899309a63010d6493ff68a1b51472eaa8441311deb8bf572f287a3ad929c5b96ae6c45693cfae6fcf051da5814299392947c51d6e2ed8e68738e870418d7a75c3802bbbcf93dadda005ff4de46530aef01406f2594437d726438adc59644cbf98e33ad29589ff22b3deaf4f4d17431747ab5a6667143a95623acb17b36ba37132b9c2ad123e8409a5c02afe2368d49e30786beb766bf96449c47bb8b2f21549f6aaa1a120ecb541eb8dfd27a921b00c07422c2b0b66a017242f989eb0475f5b9fa7ee9dd266e7e82d8a326ceb8a84e6570a745210ebeb0724eb3eb46e4dbe695067507d58e0b265caeece425e1b0af83941898934224c97232a35e588cc6caebab74bdac00046e7f920bdd4565879e88df6eef139423a072ce9f1dfa34e1d472b6e76ca2b375c7080457b04f7c5752fb73f0b786384e49526344440567a77fa895162377b7eb0ec4885807eaff424949c6cee3b96da976724445468fe73c4189294aee0811e1aca7a79822e5c24bbd23ec9af435af125b7216f758f55b9317561ea92b635cd994bfebe8fe2bba5566f202e2dac43a912b72c6e566c0e293ede9eaabe6f59363415905092857638d7b680f2ef1b71869a00648fccb4e4f77b44f145ac3e99b5c36e50668897b7febbda1a075cb9900993572f91fd75976bdcfa87f8b56605d353491454e3e0833327bb7db1ea540c9c792724a3b7fa185afa126cedb273e15b0c7763c80c3308a0d160b5a8aded2954430723fedc5a4653e96197b4817d20eeb66001bbd7485bcfcd8d51b53026ef7cc3a26b9c73177a6b4d54b3037eb17268acf2de31e9f600f386da3fcf6d6f58214f872d9d14cfbbe72e39ac67a6f827ab95ef13536140bf8c40347d501df45f78c9e72c55df249517b370479f732d978c81e0788c8160a0a158484ed330eaa4eadf348d5f52e0db00d16dc90698cd4b36069f9830974fa7ab6f18559f0593ad5d4655e48138e147031fd53c8ea361fbd64c1d1faa3f2fa0e8c45e1f0a29776a500b665a19084b763bb5bb868bcc24f18f23753c079f0c33438f2884cdea9562d522a727fc95c45a5be0558ae918aade7c9755ae9a698285b27940148d514742ef2f15d03bc182f24b5bac6fafae225153088d934a23068296fdbc916350a31e4f5d02c1b1d8c0ee196a4b4d853836748efd9dca7166de223fe12c6a33d4513f35bf97226784b0fffa0645ac0f1e37842d14049cbec24e4819b50abbfdb0727d6c269062bbb614c760947d0098f73fbcbbd9c01d1939487b7fb932aa81740218f2a7f5cd139b4563a0cec284c6d12405fdb1365d7da5b47b4ff29bac0c2bd03f243c74bfcd4e387f124a9af33f82892f0ce95c65c87904a582c98489d9f1c7b32881a72fae9049d49925d1b4fcd7888249a6062c756843241fc8df7937b25afeaf9ba1a77b36d328c9c9c43ca544f0bef5362d040797314a7da85b431af5abc371d88724d65b5e773dde05567b07f339141cbb94babea494864bc4c759688cf1aa303605ad17f512176ed6e16dcdd366c825d0fe6042d74a62d84767f97025e518d36673468bd4fba6ae1e4cb1f283c9fb27eec78b3482c741f1217bffaa920f3d0a665b66a2bb3ca1d8735f9868592530a9bce6dbd107ffb05a4115e826cb798cb37729133ac8058a343dc9c6526d2f9f52d99e3e100f5c542d2cd43978b0d068152725030baf9cbe6e4966da463c2470e36683c63e3f43f07213de858988b003b36728e7011cd95365c1c293b50a9f46fec23c4ad21cdfbf56515925e99f5f88f7372d3e9e9e0544d71b5fab1515079e35ada765af2adbf68ca962806e2033e39574d36f0f670cc149430b637cbce07bae6eef66ebebc3dd089f8702c50035ffa8e728d2dcb814efbbf4bf56fce37f9ac5bfffbffa5645a477fc59b3d7a3f372a7772e2531b911aad4f341ff37906fab5a66cba501bebdb3f227914130a6a6b2c81484b7e01846f244553204804868504dfb6b88e6b473bb54a96cf7378d3d3d37f3787cfd77ba0725eb1359b3cd6848e15d587d678e49fe59fc8795b9a337c9a5435c8a7a5af3e51440f901b8fabdfeaa8553a68713ec594c1f74050c77483591f72a0bad2bac356659c04a429ce0c8a180b4cbc3f4333eeef9b390b745e877ef32c2673f0702ab7849a251d9b22725b86c48fde46c22655a34cf67cdb0c98faf772934b4886ae322be4040c973943cc38469da65a97487704095b5dfca31709e872f3a98a47e10e8bb3b586e4102742a04d7f0ef908fea1cd60f45dabf417ea410851e2d3f0502a5711a15ab1b7fcfcdae86f7db7a4af94d9e8b82dfeaab43428725066f641c3ace9c452f230f05b589f40731936e272ca832aeb4b3b36e433e94ad9f341aecd2aa9c90e7e2ee1b79804f06ad593dc088cff7276869ff3d07a5472ee345e9a2d2f0eb961cba5c5db555b9e15081da0f0af228720ee46086e17d972365aac4fdb0bc6de12912ac27561492fa836933035ea0796d2e0b0d23dec4b3fe96dde9d5647fc86c8e9cca0242dc98dd451529d86cb57a134022a0c9b1aeb729f9b1211ce6635386dc693c8838cf58fdfd67816009f3588c82ac5b0d42c3b10c8452eab456d9e8c01b421c6efdbb854ed78d866884e137de592a805b7be8432c46ef4791bf0c416d4018a76831e2226fa7ee2e65dc40c2a9d0f3102de10d072fff1dc6675a2d607e2ac6455a5e3db15f3ab78ca31222b5a7aabe63434d31872dd0dac79ccdf0705022aa8d524e30457dac2560d2a9d35111d1b14e3052fb439ab6390259300f165f69d457d5c348aa7f06da7b965d7a137b2b3b963383b21725aeb2021922c2481eb1a0b33ae999e050980d2f16c6ebb161548625270e9b7722fe30c322a3ca180d2777ef086c4f7c50d215c964448270eee46efd5e26d0a5d8652eb76e5eeae91f059bbfe0cc43b11472c32651fabe4522e575a5640b3562cd6dc06222725703bfaff73fa2b2a3ba3f23958de958952234527fc1214ab3572e9fef1cc66c1006a387a5e0804cce6759cf4a879b3a6e8d910dacb24fa988872df6dbc6bf3783ef5b740e57a6610aefaa43333372286b4a4714143c5b7b57a725e6be2a08d8f4a71185cf9192650f6330dc6fbdd208a4ce1d9f069271c900d2d8d61e9c1126586d080170b139220e2b9a1fd77680829712c2a2ec08f2ed2d572370144cb4de83b5970d68e8b005a0e99d67f02ac129e28e1bb1abe357bac40720d9c227e4a6398a12b78107efd77a113b2074e80f11f0b7bb657348b2512e530f4ff71916827e15f49ef94e3a1df45e20845b4a1e22cd00ba5985a3a39a9382dda91d52f6e0d83fbbbc067e7b44344f2b3dd50b96255cddd9c0697787914aa625ad337858287cbaa61a90026ab129fae364f4730274ea75625b233e637e1c7068cc1a9e943b5e80994c5d31845982508f2c365d4fa0dd00c073ae70b29eb9c34ee3d48a5b6b7d319bc3e9c9f8a7b904f5759c5860ff67539b4e2fa0f1adbdf72d3be6f87247f9be06f77088d94c11c74568fe81be129c23298d4ff3904b883724477f90188b67207625689c7ba305fab7c1b0edf01cf371cff028b024a9a3c2b43e214aad67cff79b5ab267f90161bbd9ad6ac50763791c07de510f828e23c729300be65088d40482ead9e6e96f4f193e91d39e75bf89ff1040893b95bca3072cc8d7f6cd3908ae4b1a55416945a8d5b23ee5e5eea5ddcc8c71d49afa789e77254cc6db60a32695ad0de727eb7552e14f61287261f73364441ca82293ebc29722fbaf8c661338384ba1f6d76c57df0fafd56a3e9349eba752fb15073d95a5d722f51882ce961ed077323bae9eb44e7c4b03cacb0fe932e2c40df4a624fe29672c2b0bee9bde979eb7fba6ba087146b6ff5f97acccb903c38f1faba679b394369640b7a025b5cc286046aa398eb03322eba160f8bb4fbe24105720b3c9fe30b720e1e696d09b8e9efd32591aeba9883a2ab22c961834a95f495c41086c7dd27349c91fea1bfa9c5d8670dbdfe4b46f822370813f6cc76d3a4be5ef4956a31575d8a654089d76b712c67f666b2b55c8b59112bfa0daf053f5467dfb265cc5ba572c6c336a17d44a0613d53f26362194b1be375d7d1ea88da9e42bc6a3a9926f515ea9f44cfb40390ef494fc0f89739599f31b1a8ddb9f2759ab9d09a181309e072bd0d6e11e58430db83daea93672b65d5855c5b9a7c123c00267d556101a1cb2484552c5f8638a396bc75c403b3f3fb3537b946d6eb41c13507b736782cfa6056d1d2122e4e1a06d8249029093cb51a264e14058d500c896e182e266d263d0f08e1dd27ea256b27f387831218fd9c0c8d71d0075fef5e6849a82bcf12b8de022c0d5af019901abda9d391f99d2e5621f30fcf9d43026408fe610a950a47ffe27252fd2c907b260d5324caa9adf064be02e9eb633844ed8c819c02042702d50772aa6f279d5ffe2cce1916a7f4f4119b992f8bab518053fab99a6cb8b60a2d767241da82a9cc1db47c45968d34b8d7d89b22893cb622e6452d306963625be1ad72f6242490cd8d1c51961662878b3a024d566c2deeae52c1141e2ddf440d4e5172ff0b48bc3e59389097ba0e0ebe32fc692acc4394701e36c0fd3ee8ef7e1a552d269e23e6f2c5e1955a08b721448da24f1769be1bf978771e49bc4128d080f26076b8923efe09a336bc3d736fb99199d1bc7b19e364892e27c56803dc87d130455cf7a0e82c2af9fd51e92789f01ae4942301e4713ce2cdc77fceb648e767292a5f4e597030ea23becc7b8291a4547afd41d266ac75d40e91fad8652922b2343bd71669dfad09637f200ec71437d51e4764cfc2959c3b40ff19bf3defd93fe072d394b19d9a138107605fbd1fcfdc758d760dc973f4656d97546cfd3b2155332e52b43e6e24b7384570eb1146df7102602a2d20dc301ed01b8497ca2980775c559756d974fbdc9caf8dfa8df81401fbcc5f93727e1d5621c84a33b9c2427bd47299744708a354214b1a859b0b29d0d817db3904239f34f9242dfe80d04569b909122ddbbb73965275a464b3107afd45b4088c448be5da4871588a9e49ccb03d72c0dae81e56dba34346815ba711f925ac09f55eed925adaf6f5d26f63ba7f5a34f5abefe33e26877e24a96899f0364bfa96845d8face676b636eb1d6bf908865df8247db5a21576420cbafab39703141d1ba506221ee4d091ea814dc5cf4da9727ac6b75ccb39ff98bbda21501197d1826096c78cb18baf66a5de8e212acf415acc8a9d3f1949d264aaedbda3e3857b67f7a75979c2d8117ad6bb219c7cb3a82f9079707bf3ea87d39b9dd20ff973ba25caa3c91e94e9bb32ff413a3ffe1ed6723c219308df6fd3036f83b71fc424bf631d8563f818086102ae7e3dabb58c41725bb5a139ecc13506635842cec1e844daa69fd0801ad3b84ba40543a5dbbe1072d30567147a866abf604b231ec3bc4041660cd441a0357afcc8ce455812c004721052850c18305c79d57f3cf88bda246e6d9102dde094919145cb3f745547fd02618bb8a488c94d1562ba8da1a61f31bf61a56ea075d88b413898d7b9a9961672fa1d1a0b7df4f209e27f368f55ca448b0fe663c1111eebe5d27db3adae860d72c6afab3fe0889b2b9aacdca6219b300ed19eb5642d1788bfeb6e4c02d4821172279df87c91b2d089bf33ff3f67f6b3df495730d8a730a0f3fdb00e55a08623724da1db26805baa8be4c1abcf26cab4168707fa274f105cd5f5bd8b4fbea1b272d86f5125b71a8027e61d9f45bbd9daa12bf20d0ce4db7e6f8c399131259f6b5e6dfa09540a2bf68a816351c60e2acd31f882d8a636492f8e8dce7d8316e0f6320b875b8cea2b04c3d1e0a1799c9429db7404ffaf0e74228803f1af74fa48d65270b3eb50a901a72402a815e3bd2cab8a14dd9b3bbb080f4ad708a68321569937d5620b0070ee1268cdc1692131a2b046c7c737808c853d28e93c610481e6b0727300be4926e0dae35cfa2bcde59eb897209acde088ad64d7ce22fbc70ab65a7218cd0c5a9ea1963eea1609f4ca93340cba6f9dc9c3ab8bcc1e0b0347d5d7f93303bb5a63a8c2c8500dfceb0d2c847ce69f08e6740bbbd1289e79b12797f02672eb0e89b36def2bdec53d10e08b57e9fe613d6a3d98c6ddc9b0f49c83f8c44b72dec91853eea5a572f41f091d9cb94f560d7d1f274a0d903026fa7256cfe90272dca1cb8dcf4048034ece8f5e091afc5ae518f79a32714335765bb7ae8e2a3a728cc91e3c2ea73bc9e4ab96ea9b92bd13a1ba20444023d280fbb4b6ebb3907541342f851c1dde07fea0ac51f0e35e92cc58eb42c41a3b329cab14141245e2e85be763a67bf873b5b4be9030d9347df6c8e2c1730e82815b296e5f67d3f8832872825e5dedecc71af0d324e66ced7083c84f4814c248021a79601313993735a6241319f11a3f654bdcd2c4b24c8dd91911e4648a390ade5452c5e7e3308756b523136e1a88b2d130c8f8ae971d01dab4c5d70613bf73ff5b072617cea60dca8c72ce3b6d1f86b2d46021062d3354e50f3f7bc43aaa3126a092a8f911597ba9ac72cc6a32943b0563c9997c53126364dca9fc47e8c5a6378859e0b3e3b41c1a3b02eb02ccd9852d02830413f029af1fc6d930d9a3d7b6582a2dc98e8e62a59582726e6c24c85a6935df0f28d16e52111bcbfdd74e7501d120fcccc91af355d32972cceb00df9f1a6b84fa33e615565de9762fc718cea47871165e30a9fbd07d0a723db244da63fb5c8d9cbb920585616f516d072750828eae2db7c0a6eeaad2b872b55382a439b1c173ec22f640446ffc35639b3c0c8817b79cf3ba645721f8fe1106f4d9a5f289eb15b52bebc0d70f1d0e7f7920ddf7483091c068afbe41a98f726ea4f7610585eb4e1817bc69ea02a25e1c13d79156233e6c4fdfd4d9a4a127723b60cf608a60c4044e3bc40809b27c788b4b06e39f248257b8e97d06408dd8724aee9bf143b25061595fa92774349b94a775d9b45dff45f7770626c0b0ea8c724b46d52516eeab7ade77f7e0520605b0f82cd25b2b96ea2d9318394afbf31d7218e402218e3a1ffc2f6dd8bd64e8ea101f2308ef24f406eda38e5d3f1973e129fdd27cf081681521b775117fa2bbc5c14c3770132a4dc07130c493a2b9d350727edc7e87a7092f452e9c44e6795e3d9d514ed9dfa926dab04d0cc50216f89a166365c5d6e73f1f148d550956e65b7f09101b1225a5a878b3f6048d90d1d3c51d023ed6c2798358e424bad53cfca86d6459e9ff3bc313f76796d864d155b78258fb93b1c8bf5ab49bf951f6510818d62557dd1a47593fdc0329fe2a488a075a72ee177aed5becef60ca6a4d54ceaf1452e9579b9f03bb4266c647d8065008857203966b2899bec551cf8a13afe264b8b3569131295c490d36101e4076c181c67228690005d0f9212446d4d26fd94ca28a473027347a9709e6e8a11f6c5a680c720583d66c8939b136b31ca2c6d5f3a2f85590380162be0b571d40d824d04105722db00e6295be0c9e6b046cdca83f58af0838663d635a05ad27058108b96eed720a410904d7742f4bcacb826d842adfe3e86fd85cf5f7fba492d8734f43e5cb7247b8679656cbe325252584041355a828e1f5a75d4736b9c89db3847e880b8d724b8543e265ef64acc9cf6696501c42d10b080219189899e0c9b16ca93b6373290fef4450b134a22ab7d196abef13a6dfb22a896409edbb461ec52b95dee6a6722440947d3aef6e1ac37062989531cac57eeb855d7d121dc6cb6b6adc4606667290add8092e13c4d1f461d4607f75dcb796e8a7a59ba349a3ac5d08274a20ad729adf79be1d846e3cc86dffbfb6032e70853e2f07632f73c87f607f26782da2726082fc924bfa3dc5a21db97394d9c385a7b26616b9ad7bcb0e60eaaa8aff7259e9a2326e24aa7d34892f9c676cfcfd2f1bbfd840ef3fa99506cad08ab50c18598475a02359f06bd5db11e14804818cc537698ec3fccf95ed058b20f326b79c72a9c2fc1d3a2c77910f9a62f8904d5524a48a4b3dddc7cdd858eab5c75a3b0b72828d6cbd6d89cf6b0906f7c37fa539a4b1932396132b044cff072c43f74c2772122a66e60e9ee7a8216869bc71a563f60ed9a65fb681cf8be7f6afc8297f10721de3586e74b7b7e42f7b2cf4ce0a8517f5ec91e9806879124621fa336f5e274fef9a467e6d080787ee816bd8ad71661ab6b53d4f850e452837529457af4ec102b50436cfc0150c25b696a227e84464226ce4b6ba184bf540ce84742e0f99433e919c5c53b7aea29b0afb670d13aa5eae2292a93b5bc84b6ddd77eebddbd79272c5765a984497426760b531466d5bdbc49a4f02cf941db803b24bcae3825e4072e6e67bf2ada780ebe555f2007eec1f766028ff3a57efcc3365d7105b506302727c85d4cabcbf96d214f39da3fba221ba4a8d052651ae56f26669bf10da17357230ce58baf8b1d1d3d23caabe8ad88af6bb07ba29dc749059c841edd3c82e377265e3b583d94374334521a371ea19a472053f4c5560047c062e0da7c22b098a4bc972f0f238ccfe47a7cfda2954317bc61e397279e133f2c2b76cd97c77e87229e3639f84bf1b48c7b04acd146b4b85ec7ec69bf97a79ade813e6951d335eba2ea9aa9ef5b32e52c4fe101c0815011b4d0909b7f464cea376f6cb52e5c71324723c36f75c09872fa66b0b9cf4654779cd09b5eb29dcdbd935454bc0653b6c5c3b5a95e35265cb25604e46f00dc321603b0a5949602402361ae82425097589d572276ee82d67bca6963a9fd7d21c70927d30baf55efba8ca09fd1bbb77fa3fe61f9de81971a2dd1792f2debc47516fa316d190da8cb2b275f17585ec657fc61872a5e3c3cdbd61f07306f7aa65cd7abb0017d853f59e1364b1d9fe17aee3ba803f9e756df44774c428b1adc9dc47e83af37aaf41b99636b734bb08df0886d99972b7c03711031a693fdf8c234d9232eb8cdfeef2489f0efc57e323f1e3e3b1d9566a67f0e01da1fca5dd609a786468e27817f407f3d6dbc97c8da50dc5a920d572f9ff603a4da0f9d6b5897aafed9136a84dd0b57b47c5a5e6173b8d2a4e36ee722c6cbb77128427becffb76f57cd119fc71c92e609c2a27d1327d5d8bcb42857248a276b7371f2e9211ca3879d5faa68a21e781b5c776fee833eb283a7fc6a1727147ddd2dbdc29450bdac32deab49b1d0edd3428ece2353288f8d19f061f277280fb5c7baf860d3d13dddfb0228f0e29014038e34a3caa81928def5731a5737296c3ece10d255f1d05ab8ad1e78f12b578145982e9af5c2f72b198ffca70ba72d28373b1806fdd41f024b2fa29534132da5bdf730f2e3648a6cf0716ac8bea72ef8cb071b3e8a063696d73689c8a80f703202b5618c81f4bc2c6a2f608431c6cb0baf9a9327653d6142c29dd5b8bc9760d1b50c0d4755dc1c94673b51d2d8c7286ce3f197ccb290d1f36c8d65443b00c2f0c1efbc8ed1c7eb3f6599a0520b02b1da43501fed7662856a090bef756f98099fd4f7e0961acce90e8be0307cbce0cf08b10d5dc2f1841ab42e2e8beba9a99e8488f1a3b04b4d4d59d6ad9fbad7a72d7459c832b8226637b43c30eebdcbe66f951ca568a553b1be94bb36c7ec0d072411ea04dc1ac4c395034d3382e0cfcdea8a3eda816bd3858477409319d043d728b6e9a12e6414ec9fb3dcd0413575f9c8b700db9b358f3860b72fa7430454a3bed48bcbec829301e201efdf68a51a125a8cee3bbb5a1c3a2b2dfa691f79c5f72909f30b6c06ef655c267dc08abf2978b98d7d1576fd16c1239b036ffef52795717b0f2a56c56e893f141b66f01695e5d0c6c55e801af728b218da7e5a0ace701d61a1d7a85d1ca1e070859364cb7ee1dcbe1cbb886cbc50e21ef909d7b012f40942beb16bd7ddd1b4b2a2c29be27ce7c22a1047af5957dbfb112421ffde6951375f76c7928b4daa36e50802e17c53eb2b960288e66c536ab0095244576d413722686d6c5576178e4b995cb3dcb928199d02dc9e1aa74d9b10dba6e713f5c4f720eda82077c5016fc0e1c4e68348b381719daa45b61fa1358a7fe5bbdeec2b872c81b102fb51d1af6cfe9d8022980c7d6082ea5fee18f49b023ee6793641a36662ad283c9c820e8731319f53623d90aefb2ac0514fae53adb48ab2aa2bcd8c412eea2484dc617f5307aa54f4815ee6c736436630abd23fd8d98626b56d8d5a772ee99d732f67863ac74760cb71f921c6905d7cf6f541d7efff6c55977afdfe07291c752f0b06befee3a656f790dd5947c50db5117a9be969b5c407b1c979fc972ac0b51f7bb8793eba0b3ae0df045ac205178e792ab9c85980b7851344b680d720df3d7124c8b3b7614396b6d1dfd1904896e49e3dca144463b49a688bed6df728a7256258b7c6c862d7684711dac5a6fe690d9e3bedcfd14eb72f4ddbe238572395431adaae59fb1848dfd5fafdcab5b8d5aa9842e68cdd346016f281bdb2a723e87dc50e8717002ca196e0e4871a30b4a9bf17037926b0f750ba514681d6b729cd2ff465a4a6f2b1c101614e712986115e23a419c70b9ee10d62052b1dc43725b1128d6389728aaa06b0d87cdb3654fd11355eb80dfbbe5fdc677c91e09d872f89d85fc23808d215661af17bfb0675e04b03668520280e8da762a4563e4dd72b56c7bc30ccd0028739973bae8c8f787e0d60c98446ea2bf3db4bcede135211ab270fd19df923d5e4a0ed7e3c232704830b248004ab0413724f80ec21279f40345082f4b58d7211fa63c5be8eda39cdcbc5d21bd0ff0d1646db87eefedf07872334b63ddfd8682f3b89fd2095f62b364ae2f9d83c51f87b246068665e3fb531b00a893622d6a562bef9b88edca99c7aeaff868bfc268c028f7ec64896d5e04723e918794622c3b99ea665dd15a0c8d25838cf6700dd92cf013f135b0bd0f4a60380c9e7e123db59c5828a3b9a4acf495a7f8ad151ccecb896bad8af1c79f897290cad68c2923862521478b8f06c65abaa5a9a9fd48df4e0f1fd765e312343a5a72ace3f237419ce9b92490316a21d48b5280871171573294b5b9074faca357250fba9476d49db9e7a5b15b1712fdd8f3f962879a27965edc8e194770785cca2e80fa0e6671482e3b4d4ff8fc366ee199af9e958016567e5e715973d740df2a724f19be2e93699489d90cdc64e33a2c29b67f1f67c2a1459ae822201d01e7d357867e7672b00cdab0e56089694c87148fc9a4676f88e115538c8e89f67d028772aba1269cd09caecc5420c258c31915475ed8b54f8178fc8a16f63ac6af4a7720938d462da5d172eee33dce23a4a53f8bc6a517fccd6250f6bf7f49987f19ef6d936f87a18de69110da0d0e2eaf76fa9651d21377ce1b127f7e3e4b4392ae6c5ad190e6d9af96a05e36e7bdb50558ef7fedc39b3ce44441d2e7b2125959c6332806a1f062e6c180e7cae2703ad54d4551f06036d963e79cf5856a4b9fae0203728e5b673d854ccfff2c146aa73d207408d2331af7bf7f5c4de5ea3a7bb8dee31dafdcf2deb48aaa9db7f80039e8ecf06af5d1a03b99318289240376b69fe3be7293a08c1ed18d2767b77e3ec97f4971cc62cdf9f28a3a9f602f8ec2f74dd40f0645a64d29204bd7d411089d139479d0dfcf105a296206f1d869c61ded15f10b72d1e8ad831a948db15a03d787c7af6af4755a099c3761962d327efe8ef267dc704c1e7e4501d9fafd6cb498ff97a6c1d45b2c645e25239b686b3f583dc740533eb8e86f6daa2ca1d963debd0ac4c25153bbeb0f1edca461a8580effc526235e7205c6e1e334faa316fb1e95ff1e1dfb4871e9d5e3358457e44e6a44b3f1d3cb4f4dc37bfd5ac543255896d903e39132fac9ce5e04669e7733a382a91f26a35516f66fed5e1d0b12f34191ef3c4fee26411bb469172797bdbe8a240166cb43614c5d20a3fcf057175f16df96da4d9a895ad1ec3e9d88d65a61c53192aecd36d533a5d511237e43deb72ca8a402cea07dbe44e9401a0e5fb9fe0f8d8b3499edbc72128656c5594808c2527df05f5c6d4d9a6c4264413659d9ce3b5a2f33046d553e84585501227192445983326c80c4bb383ddfe76ed221059c81b1935b67d8b7726b695ec2ca14c8f7a9ff93939d6d49501d314cfb82ab8d8bc9f4cc49c634737246499c25e814e3c6a499683e4bbfcd44da3d451a01582a18ff0254b88b28a5211675e05b72e2c92521bb5f4a31802838b57462f39cc1e6f904e121d926404b4de74975ba81a064cb82baa346c2eea60df79bdae8526e5dc29a92a35799e3bc727b89bc087efd7af523dd588dabd81226e3b5e1261e3c8ad76971867fbe1de87244a288ea5826311239ab00fc9fbd26b3e060beb3e7db6fd22a25375de5ffb17200f23a79474f4fd3ddd7c349e0282f54ef6c28fce272bf61db07e4b548ea461081a016c36f382038b0d3ece3a276b12dcb410539b2e58ea4e6c515b851ce1672fd0ba32d15e06d10cb78751a1c3e0cdd53af7785cbf7394ca537ef05b3ae8a3db5cd270938bc0aab0c78b2d36ae1bf1bf327d37043dbafea5ca7e18c19005d72d3a09383f5aef2516e44e08ac923bdf302c69434f1dec5bba71785676c122f7204c6182443bc09bead3f96c525f4d311ccdd248bb057742e62bc4c2dbc74b172f20768a93cfe599260b0a1ab03b90e996d21f515b78d3af16055dcc2ed6f22235d1fd39aa78309eebe06ea5ce16f76a39a36e7c23d6ee473fe71ac09bff4c972ada822b93c8996f04325ab9b7fb9a976be650425d73fbe449010223870a80a7267c390e070f29e7d4ceae5d59dcff4ba3e238ef3525fe82b4f3e3394a9cabb72ea6a66940aa39c4dc10b7912476b6c2551625520919ab120d13394deaef2c86e486f98abf54a1c21cc7d8289f02c67c67b41515a184fceedbd0f843393b6e2721fc76d91dfb545c68d92a6ee4a55e53e79d06a86bc4315bf87b35e30ff79265a098b7513e7fc262c297e101d0de70568345bc1c118193b98f8ac9076e381c8725ff25ffd3f2791f256c64eb34f3e7431c0049cb562eaa6f6e04e66eb4a4091342fc48c87067d8c2148a04ce6360c93135fe6e0990a5fd8112a56bf793a190e0f833536a12e017eb849923a51cb4de587a48b11e09874a909adeae9511ef42e721438981b2525eec6d730475be66967d6791c8d9d2fc6ee83c8078b31597cbb42f21c4b5a1c75e3ff8d394f73dac305438b842970e244bcef807be9459ac0e91b6cf6d601746f9d31eedc199c76178f843b7d8f6a7f41c22304d9fcb86066f4557b3d5901009c5cc226dddd17cafc3cc474c3863ab24c0b217001bc6aff08b572da70d5b1c53bc9584d52653c4cf46f4cf7d9e8236ad18c01b7c06639e4db2872f33bf2179baa1df90e11ad439114f01cb70c8491e1188729d626496982f172727b2b29c9cbb33031b391d0f5af5bd36f0819d7e946a67eb7689ad90dab887b72fbfe48260d4026d04556808fc48cad543f664ddea359d5088748121cd6ea4a72ad1a4605d8793f04f3e28aace65dbfc94ffa993cb71093544bcf035c4e82cd72ec092a26a7ab39b932525f07c0f1ab6d7d10900a8f539a076284a194ad726772898a61c8698fe3236e835a9362a0c326cbd9a3497e0f12c1aaecbad4d542590bdb83a46addfb277a9c39504bc8706953b5315267a3a374a6274f12422844cc0d07c33184f295cc57f7be3e8edabcc974a150c9a4a32499bebabc495b7cc3dd7271f3060055569dc8f31f253593cbbff8adf08d4b2fe0bc6c6ee6e0ee682a0a418d011372bbbde8d70c3857617866bdac2942c1e4147236278633e5461f4609076574a4b116bc04fbbe2fede3e1758fd107e0a872c691c513139d9bc038e2ec72cf593cf047714531cb560fcb0668d62cbaf82853ab8e4a1901d51c73b9257f72ff066f79c87390d270f9b3db8a75b18d2a5964252d5dcebfdc1762420a024564de703f70d05ffa70370525d8aa3194b7f02683108c1143e2c4d431dbe503470225754bc698092d0ea7c62d6abf980420256dce2c7f748408ef12769eb27b9f724d3ce69dbfb2830046c888ed2144dcc13210af2bb5193275dbb7c2e9224f443758b551cdb44a095390784b53a5e1fbd6a370cf0e825a8f03c5536fbfb150e41ce37a7a1958e992f027001e844ee4fc3963e79cea7db61a0d4daf35cdc33eb122ec6b31e4b0a5acc682307038971f74df56c03f6fe41249a5c9b2a5c31617e4158c6d52f792833e5ec1b2db5425c6a3869d6d333a24b7605649affcd596a0e072d7eda1c8eab2f4b7f91d4d5edd95c6e675d1b741c3a54597a2305ab7ddbdca7239daa0c97a2e1d2c3ddf3dfeb96b4bbcaa909f93c95b1ed6728a68cec6583972592eeb64bb69a039da002091678193412791a6813c184f8e94945dcfb9e6d86fc9bb9dc43506b8b22fd16912f3fc8a0adb4bcda4d059a43e00b0d3c5bd323b72bb53a5aace33127177e4194012ccf55c4b72e6c2fb3e474af36045223e0f2c722cda3501af51a42da40def5bc8c61e18eeaea080ad36e342043d89cfcf081c59653858abf96855a4dcd6df01aecbac073ddab677f06b0d32b06f2ca3ec5d54310e85df0b08a07649d3ccbb9e7e139d0ec8595b63c7c526e8267f5b0efc444472d50ce0ddfd7f42ff48b5fb55afc6d3942736f09ac76ef57d8ecdb71e398b91404b89e6b56de5c774ade147063307a8addba364c510b5a84e90b55aff6d0eae726f6a558852ffa0a517a940ea0b5d8485e4b123603ef2badcb6e5d236d60095722c053e0091bd797aa6a49870eecb1691bd40600af69cb256dd308c324fd60072e60ad9b98bf05f29054cd616e9471502987ee5b07c19a8bbc884b093493fe772622c810d7fbc68a3a26275184a5290f81e73399ba51cbc9fe8a8771c223189728b11550e3ccc56ea146c47ffe9c6d1650cdbf19bd7fe9a165dd87ea19844ef72767973b8cf0e35d67d88e424d1bb4e59485e336adc6823152e84f89c56e489721e3a549e254b7ee6eb33de03e50875f5e795dcb297d72f81206e11ad88a6ea6c5f90cc3fde20b903ccd4faf819096d623236c493f821fda075a582f46cf54972ab269ab40aa070d69216762987302254b5d4e8a610596699688519f9062d1f724c5d23adb423c0bdd3cb7e1ebd689fb46c5da462bb46c96af54c9f770bd6f92b8f729ca6bec5f6f06757bff0e1b5e9c4962913fd2b5edafb1d5e96fb1c8ad27226b88280f50f3d76815d50102a16eb57efac504a04df0bed93545408bdede2722f413b617acf98acc2b12685809b5a1770bc63115162d35f4011220438b0f652e08cbb17b119f84d2b4301e60d3af78f7868b68fc8c7f09e00d9e9656769a072a1ca6b042669bd816201cc9ae6b7c95ccacd106bff8770c63a77223739103d6dae7970ff53082c6eca98a3ba0133dae7818d46ef999d567142b4f75c3bff1003709b92801e4d1800635f9d4d560c4926d7f0c2843f0b0a3151fbb129eca5b872a6655995208229c42383522f6a75886aa1437442a7a0097b68d03668ef3c0e723c4bdb3983e51f0cd661e8c4274f215a4501a5f6d86688121d742df47c94272d81262d992cafaeddf5123df3cf5c3f7e6b3ee17fb1f3024c65b7d7ff358301513803b440e2e99d2654b689d269312453e17c83d3e75bce71eae25909ed9b1e724bca9792a7df91b7ad97dde178bcb80577a38d50c09af060b9e77907b2fede2d7320bcf87b9329639856522eb7cfe50060e30b0dd156410995a57d459ebe3d2680fb685e5f9c7135aa4a7c99a05c89bcc414c1c13ec18327ee08c002a5fd7e729d8a65f4a596d7e58115dd6768b62862a2b84653e4f95084836e1466d5d92a725786a108b3a104364a1a0bc19f2650bda80ac81504d2eb40110deb73f4388f0b5c16011232b58c441e41a4828f48351595970190e911ce2a67d16a5c387cb472b186f60bd84c9ba061911111818090d23a55dbf170654bcd98f7d4c914ef1b72865c439cc8fdac55cac6c9ac82130c6f73dd695d4ffc1762394ecc05ef94be72ad970a4c1478a1590eec2243c85b68e57e39912391810a32e10c250cd3b6577240ee672230db9413fcc3c25b14efe76f5ad1efb4ed696026440de2a1b5864a48adacff3199c7358d8034e2960758b8a192c2385b0a37544a70c996f3a961b051e8e484c9db34e4e87e1d419c862ed82122220d65d023eab6647788372fe39372d74010cc02dd8954e16271c7a5fee0573c9c3410c2fe77db6478d757790baa725af56dfab9d8152ec4d4b47b66d4a7de39fcc1af726fb181fba71a466ae0b47211eb66d25c03d3c1b2d8156959c7955c1c9444b4de4b3b357b1110c56da949309d0b9809e0610059519518ee7fbbc7e3070754ae468bf1f619d4bbdd49c25172195982951c5fe2d45acc10fd4e4c2c3bd064e9fa4e62849d04bd1b6e1224927266904adf0add8fc4b6747b7d40a8d81603320f549e5060b47f73ecef84b7ce3279bb7eb450f10d760f852067cadaceeb49383d7744d1e735871f892ce700f1725d20e842d0a3f4bb7a6f03a4fd838cc4b4ddab2f95b42909f44c05bc6e1f400cc6ec8ff4583b5847ca810f3f57e54695d686c1957c9e2c1ebe3d9e10377d235a62333621f74875243873e69b3a98de2dfdbf5ba8ce2c3fdb0f89900cd8cda17243c3464302b3d5efe4901e79dda05ea1a0917ddddb2310a2e2d692a158405f122701555dc590b4337488ab927b1db6fb9d692f8252f03ff337ceb8610fc36058da0d4e3af8e0774d5527e293082c9ffeb9ecc4a4a950bea3f3f353f737ce0172fc758c7e0628695a987b3ac3acd78de831473b97d87c41eca64c2585496f21723211a45635eb2f1f4656a3b5ffa132c0436e315603fd4e7abed02e4ac324b3723922f9b7fac08512b472ac77cda5c6e765f09aea51b43d47411821db65656848b2c47bf47b2fd55806cb1069c9b6177c00daa0520718236363a73178667aa27225ff62f38aa987a0f380c1c4ec7399169b5c055b2bb62578c396efa13dfad21c0d43a28a68502626e3a70568cb0f957041f6f08397ff1a94b48208d8f9c3e47295b1bb7c694955e338c840c4f71bbcfab2bcff7d9f3506d0c94f057abafebe6145ed87e3fa9a63e49d868cdd7e326759f60c3b76648e5c9ed6a9b2354c977544056ead6385a9717b88c39b1ca3564ef608548fdc2f19e03804058c1edeae2f728d333a4625c4bb592d9a4f22694330ba45e016c5c879525783b5068ab8e5cc72a658f38376b0182cd5024dd88a01e913cf56d579dc3c85044c4a5f4ef1d0ab0cec43c1f7a6a0651f4b928912c4f54ce92f9ea89481073ad5d99d2aeaaf047f720660daff765d0572317a3319447c08f82d1c25635fe24ed33b0d322e6e1f8b723de634e7fc507ea8035056df7fcd0916634eed32705a20120d5ffa0f35c9984614ce0f638e5295c14237f8d78fc22ebebbe219e836e2552708b9f07246fdc6508a922cc46f3f3cf58c6c35c77e9674a521394eba6ad568d5aa3aa31ff0d004723b2ab147899321ec88895bd2eb0df3da4c4e73f33d18e182247049d52d21a80f97116047486a784d858149eda86d817901958858d5615791ca8a8d5328d83b28b45dea9ea1f37131f92d0a22d83f6cdf7dbb5bd93298dbd8f5efe76f7fdd2672a52768267d536505d220df04054cdabe091fa668d60b7f834e02d99ab08ff8726324d4f4ffca5e79a6a7b25d7e851e91d3940a5988db98bca19e2840d3d5ba1cb7154afddf24e6d682e9a8fa2b740baee5aea64eae60cb1af1e035b8e4714a30b0efd325196bf4dd36e1398f855d85138829dd2b09cd471e0ef11029d325317211ca925ba250b9a8362d691a161e3946a1b82d0ef0a385266a126d72514f5a13d9d59389fc6cba627e038ab669f85a8fcd5fe830172cfbd897ec6b49915368604613054295d9690048a996925d766946aec3b639a267a2b53a74a3f207a7fc72528328d79f5ec6dc4d89dd33ab861b635056b52d210d3b09e237de809332b35ac7d2aa6f0da04be4e641db98186dd0adb177535e2c367cb7937f9ba28fa9057241b0b1c9d0aedf29cd4b7b9a00dd037c80aae2d7c8483c3ec9b36dd1dbfad72ab92631c0ecfe2e7f7822a0216336d7643b01022c97de954dd83f766117b9060259e85c58fd48651e2a543ac3c329a8642f7156b3180b6abd67b792b4024ae2588afacfa2fadbd85b44c88ff119b51a580e1e44fc12c2ef682eed5c828cd0e16d3e2c1b9f25acfab9d7b2e39df85651ab50373585c64a64bc001880d23ae05148c87a3514415cd8c0334812f9c5443ba8e14da1ee436d3bd0fae3ac4b4c8685721356b5dd27eb6b25f4fac17bb558bf6ad64dfc238fd48787a8c408339bee7d05832501a7dcca704068da210bfb439a57d2c76209a0d2d8e2f76203315ca93b017e6db9e0f88e4dd5bd967ef4e81cf1b3af78606ba0a565750a479f018b47f15f22b1bde42a39334d68cd142bf46f9385bf99016c00dfa88ece6b83a5b783ec7282028dd69aae2f7924adac305ef29f7eb2d8522d661f0b97f7ebef8718ebe848b153455de0d11ecb84ddf8c129a3a1d185e5886966491b1f5818b762fb2f4e530cd1498c17469f5f531b6a0c55e6ee7398490f01a6181330ad8c5ac4c38154723e1b39f31b80f2634e8271df973e083ecf828520e51b0dd31bce5cfaa1c71b018e03ad16de51b556f21d1cce256bb5bb9438e3114e5a3343a27ef0bf0c746a720ceba5d8d21d1d14e9656ebd2da861d5c70d32015d4154fcfa2b92c5e0c0f64dc5f93c5d35837b6ec793d7305961ee10ade385cda77579e9f6e8860b3283615c84227eb2b9e512873d2b1c69bb695ad795b1f35127788282f60d77fb6ebcae72a4c8cbb6cd300dddb6ef32df2950c014ae211e38440187d0dc66e628668a8615a328f5722569b65239b704454df0058da79e48a9f7855fa42498467b93b3e20213c4d87bbfbb3623ae770cf5e861f2bb6790d62da6154a6fe8896d3dfcac1272b52c8983d04d3b081738f9f25db8f0d96c7775a15532777cd65e0d90312ffb72ef20eceac59dabd05d32cee6e7fb64ce3f620034e6b824b3396b7b56deaae172244f43d6a158c63f62839be3f93b47bad1e93fdfc520e47fdafd73ffec65f472f51104d262cf5005a031a6b9af0aeb331738af9c9186ae4cd74f8b76c0722872d58fe34bd1913aa917b59ac9ba97099b027bd0030d6abd697d7a16a9b7bfad2be0dd01c74099e1e9257a813f64543ea1c8bad1574b68d32ccdebf79ca6ca5e727e2e8e16565696e7baee7e40888649351d4891d61a7f757e0ccbacd86d7dc0088c4a24fc1d53c11c1a4f5995b068183926b3e5d3a8ad3e6dfa64517b67c8ce722185fdc01dc3f10a8768a130089cbd52a54e223fd6feb6729f54005b27c3625ae88354528b5bfb80c4d8d91ebbf296ed208efcf40ce8cf084db445c816f0a64557f75c72afc5b3e5b6aad6517379e1c1b22fd7147be8c1edab1edd3e6894ed69b11ed283e14c5b8a4209abbb7b444e9f2d441b05cc531d6e1db64912105575729b4360fd065d637467fb64ffc59016a8072bb2108c407c10eb288f08df974b728eca6c3b6c5bddd02912c1d41aeaa2385660b4ac73c2fc8d0d8d41572802515e3d1a948638770306274f952ebbd048181a5de6edd3e216c8301403c9c9424b6c882fd1b6bae83aa0f1a686a2e4ee32364fd50a6b7461cb1cdbd0d7b432d3695de6da6a22de6256ca680d6a0f36de0459be16057c3cb08a3bb92c1a118c2bed7255550321a39b4ec81d37b8d6ee8bfc897fef4db1b94c1171cb2e9f73e773b629ebeeb9499d1026ec02f328a3b3b7e624571a1c5ec89b17831aba8e2089d0181ca2fd5ea546a6a2deeec9bcd9d182e16cfc6b8ed142b00a3da995e86997f5d065ea85be12ced08973863d2ec53ad271ee7f9ab5c3a4684f2c30974884c7c73472d675c306eb862e75d88f874cc36c077023c6894d65bd37034ea1d7666341907215a33f6e5b08fa9d78861aa6b3b94ffeec6f08ca34c3b9898788dfa0b0a783415dbd1b3fbbf34916407c5d1ac5c0ce76cc33b77d4c982c64d5897a2c727d6939307ed2e1340dfd225e3c67a3d9fc70de1750314ae9dc8085cb7f9fa3ee5f5c72a5b5fc11f4bd6b0afc8ba1023cb8943c83e825ace24aa4916b1e54a36e0bc9721cc8776d231f6216e55c67eff5133fb9f2818a6b3467c2e0e8f9a0e47237d74b49c2fa0997df1a8acfcdfd4366cec933e78ac76b26e1a0dc64e4acbd30eab472fa4a148a5c66d008f093f3b494a31eca0db814952bb8132b79cf0c8ccbd8d172fb465528baa04b24498fe0067ffb789d72cd1bde503bc971813ddcb07a0369728455be8931aa9c7130737a2fa7c7b36fa46972d79a57a49cb1aa2be350f9e74ecbbd5293fcb797ef987b7f00e2e61bd258260181f1809f1311671c009fe0587280e45334e008bb7d598925c1e637e647b440c028ab27cced0a62e42513c97572d00f43f263c21f786f8b8eb9fc5e98449a97fc17aeb60d5d71e1f6a059ba9b72bc305b810b0ea7df0dd01b070288ff8136e333bc8a311aae01ea81a4b41755725b7fd10e3a88bc817e1f8d41122dab2bdc110f5de7e81ddd7b06298d2156026864a94178812fc6fa6ae6a9d423eab46a7e3ac47f719ccd5d84c1107ce0e8b5671510f3b06318c773ebb111658cd1b1f188dc1fc6c32a9f2d1106cabeed9aec7251bcb31612590d79f9a93f8106d56f2a88bfa9fb7ef51e36dfee1c043a237b72da194a9823b4c130d21657e1c20bc754d81131a37e971573d6d03ed1b9357571b42c72edc5b87ae0700d83888106c3b950b15eec6429ca04df17137b905d75197403e9a00a3a3011fe62d2f93d3304ef2515c07b23871a92230aea60422a4d72a60d91abcd3c34046f1c3ddf12d580ea732b0123e5da3e2278d25c733259171ecb7758bb68951cf5db21e4f79cfe69c56036784c0fcacf6f7d67ab866dc160271cf4f3779e398ce004e816de72a94e379f7cf74b8c383fdc3b3804289ce37a722695b44393cd1a19ba636d83f4a4353ea30aecea0dbdfcaf777de91c26e446726cb6dabf94f19da76b3d0dc14f7fce6f72a4f69103d46fd341436f7cb143df725fa26ef4acab32b2bf4e08197caaa0a2d86a275a9f1766f975d10e81da797e72b6cd3672b41dc3cdcfa6dc0662a6fdbe035bb4aa30f3420c360dca6b4d700e6d6c95462dd394d8460ad82ead84d7bfdab0be3f6614ae6fe8453b0e448f96685aebf867442f2616c4c167e1796850b068c9f7eaa61e6556b01c5bac4e0bf53872b78d8754260a18c5cfa86b309f023f7cd3327c4556a8d498b4c392c1f2b9767283105c348b7404c5872b33a0414782077fe56cf4eee696059c12b240199447720b022d6f77884462f80ea5501a06364f065c0a37a747906707cb7c21f280834cc990938467cec7069c741818cd3857e4e2209651d5f2b9c107d9856d40d7617209fa613f1f01ad10d153e51c86aed9ac066eca3ebaaabb9cce8e3663ffa0014143ed925e6dd0a18655b97f197642e7b0efd3fb71f02a871192892f868ea76d72213d3ab256ebc9f09c71a08982f9df084587f8c15bba90a9fd6d94cc8a602e72664da75b1cf06c116997985e1db52778d1b5aaed058c2c8ab6deadf070a208671ca4c5befd4f2a0a74c77a89f2e580424f68053ef901356fdd5f5b98dd7e0c72322567bc3437c96952d11cc9a94c48635b59522e2050b03066d3e7d0c65b6b72c6ea49956482f3a395d1292362f7f5b4cd1e6a1ea39aabc622a6701a1fbfc772a4d0fa7b39da087d8754f32ccb6ac806d2e22d164e0439abc7146b665ddd79618eed3b0c3f5c176f2c788f81f948b99d64ce43a165845fdca6caebe90d614c5bbb65879e9bb8a5631053ef55a235e1258e0e6d806e7ee4bfbdd46398c44c4d5cf83e1828d298efb7f45aca2ca653331e50b456cfe2a8d2c2be9faa70d0f711573367926ef42dff59a9f8fcdfeac4de6133eb6ab0f2429777ae36e1f7d6ded172cd6b361901e4e306874b777e507bb630fbf38655cc202f364828737523fff572b7fab7bd4b60ec12ccf0fd8a1132c07a340552973112ec95d57f3cba771e374bed029146a5b7383c73f69441e5c36f5fef3e8ef3068e25da0b9799e4829f5e7295665cb8e08cea01615bedb4a40c0a3d76f2991edd97f41f685e67be2a89ed7226bcf2005130fc1bc097382ce4472c837611ecd57135450bc5b4b384e12b021491d9c4eafe6206659609aaaa45b39774431a72eb26a3fd7cd0e8fead31ce16587101cf33fa59cdf63f4dd3e8272ccc91736f930373f95787213deb298083fe720c313859706d630d2a55f54ccd00e89853f9a1d785439013d508a64f3ea33423be82047e0bc3392d1c9343ea26e9050c870131d64e6ca4b32ad833907c3d3e7293d290f344a69876d96484399e66a67a728a8c805cea3186916725ef6bf907494477ef184d509b29d08f480b242732fd231ae82861dc53dedfe1f02089c57871f6cfab271cd7824759025ccf506a210be872e25d9aa44db4068651b6fe0f0672d6141097efca1166ae78e0657ced6622dd23ab6705479d14d98c6a5dcf7eee14228da2cc90c481a4e6a8c899238dabc8fd0c91a737fedcc7a9e827b20a9f0f4add2855260bf678c6e269371596bdd9b3097a23a239c44e6f3455741d90148c72c69637746f7a677d1f8904cbad6be752dbc3096ebfa608f2e1cc3595f7afb41e25d9569c093933d3156a3f022bc1a51d0a4f6d095bac2bd822e6d0de36786a720aeb35aaef17823c928608d069fc268e437c3d2a8e1e264297680a07a5e6a72419d0ae303640ee74af52beb6aeb2e89693352a6ede3e1501420c4424089d1b721e78355d274c0bf98f3c89bc742c767243056e79aac90f644b72d316d7c10972fbfdca628b06051ea81fc56544b051858ba28f97bf56af27cce72f14f670c2721ea2e1d839e387f6ccc909b106aca0f300eb0e9d48ffed0d498cf1467c38ab089eaf9d24673804ae5d5e49a97867733a1af5432653f7efdea878aa37cc60310a61795d794454a5cd1f7607646ba4cc49c488ef4a73699c2ebd78e3716bf65f7206d17bafc4d3493e45980251094339d21ca21e76ae72ba32f2d504fe39501c718e1928d978b6fb2ddce8e6d692ec65f6f635a860dd0831c4b86352ef9b586f72f35dd701af163fa5b722ad30ba22b24339a2a54a16e3f0fdeb169e06a2a678728cbc25e048af7e7985e252f66aaf3d3c3b2f60baca45a8c43420ffe1f038701b1a2931537018b82aebb3b4c2f1f3f1c9f821a696d3204fb55b13b5e9136f017295a9d1451554d3249bcfc58be1b886932be81837aa733bb6096211d33c3a6d72f6275a6d1f9aa655e05a7ca3a886e1d069e6a98cbb01f2a13c57fe754f3c190134d9aabd23bbeda61f382388ba5ecf4cf642e63e6e78c6b917ca3427a23ae372e38ffe114e23971bb2f0a947658cdd1f8d2dae2b60a1edea506a5b2630a2d41aa761bbcc1a8954215054146c24c801e6cbba279bc30e57dc282e080681799623fe0cd4df1b7be1a13f8e7713f1554540b7d33358ad6ccdd7cc3d8f4052190e72fdedb9867f57471577fd085798f294efdb1ddd2d3b9847c1669eb62f36154972917ef783123c9ab425a71995c9e9e14b98b28e17b81463e0ec00d2783ae6a0726c404a648abb2d4d85236de6eb6e08c3b559fac1bdc9c88e97be68e0d99e1072526588b3bded78173625031031720f12005b477cad19861ad9a38c8a349638727d95412692f67e3d4c8f1c7d6d350113d0b0135ce0c14dfa01956a91463ce87266fad662759002f34921372e7c8580f335246e8b65afd9ab38efd18e5a1b2b72c9f4232071d9dd41e2c57256dd37e262b36529f8aa127c0f056fc3a28e5bdb65a04ee07f8e1910c25c82d658dd9796c7cb640a30cfdf3f747f0d120757da0872fb9c2233969dfc278e5057a8fa778635a2222cb42622950daa9da39cba07677292c65db9567ee104d3124bc3c93ea30b4e1677e48c384de01f9c18751d1f24726278632ab66e9e3bd5e4664dcb55244883652ede0d801306177aa26bba45dd72aacacb133a7a9e4a465643bebd8c5cfb46073c70b6b4a421a9125f1fa1cb20723e216ca649d4b9f161465af7b00251fa3d8c02229aaf9b079a2dc9cabd736635ee343b695baacecf9d834b2a6c4bce686b58876305a3be54cb6ea7fecefe6272d80adc8a9df71efe055585f552e98ffe4c4011ba79273f7198ef6a2876878e01ccd52d5d3c9484269aaa19d3ed65baf39d24c2072eb5cf6d1ef906bbbb2aa8316e1326d361088efd0b1cff6f77362661e8a76693e2ad0afa07e01c8a9491ee72ec2282741663b75a13d7a6a709bcf8e03322840c126a9abad42acfb640a69272e163cc467309218d80b9432fb927aa14e747f1419c8bd6175518b6c101f6cc2a2727161b235a3c5fa9b970c0a602c9f4e5a9b17eb087289c17102c8287c4537256626693d9dc2e1723de1b8f4f2fd95807ebb734914b88ef93be62efd272f40c3a2f5676790b2df566dba9293820ef186500ca8f2aac1055b1e13b3ae44a9857b5e7cd129b244cec19ccc2a4d4fb0a0419c6bff371af45e04a6f632856ff3572ea7de16c41f464ded0be50afa8b4217304dc4c1e474278bed315d61a496521720dbd774f95f3743eea7c322f381cfa319fab2776bb29bae79ef4a93d4a7cde72f2d0f5e94f51e635cc2bf808e5788b5923c9a6da1263baa4d628bde3ea48fa2d9f28b276d04a21c628d9bef7aa0c3ec1a2f65dfd039002fe7a345e0b55489672dd9f6dd8483ef3412ae50e764650cd56a2f96aeb69a5878ac11d7a58bf08ec720d652b431a3785e067d6bd7896ac25234a9e46359632168cf794d5dd1c70c8728afaf21e0ce39aea8b8a25d3f5530b99b98e02b0fbfa1038794b531c6210dd72ee3bf10cfa36e9931db9c2485efb428c1e0dc434b0ff981f607ff59195de3d72955a5e170423ab57c49f5e0accfef4768c121e8fa8248fcf0e48df1c0c60de72d6c88ca313de7188977205773110759ce03aef551a3029294a44d3cdcafe447238a6dbc910db2708bb0570fd43fd5e3bd6740cfeb243a3caef6edd502160717246d7deba969036091fa6275cc9aa430efad440c8ea37101f999039a4c92f0372331daf5b6e2d915289a2c924dcc243d2beb720192cc57fbbe06a407e6ae58072ccd3baaf4659a6099cb2e5ef07879a668aac7725ba7452fd99f2f32ef1a30327bc0dcf8fb525fe6984f8430fc05a955de2b2fdb343d1860a57cd772c5711d0727a765fbfe1807752e54ed1228eead0f897b6d2a9f410df4237cb961991cf6b23b52e8cc540acf00e308dc4dba34a95868b28b793843e0fb45d8857cba8bd5e7272e954f13198d36feb89568d6cde43f4f9ebb805c51a88624b7e8f0da73ac018b77eac83f8a655bf446e910432dcf4c193dd730dbaf4885fe2584aafe6fdd372c94c8bae8d41dc257fa35317c3a50473f3a306499a59a9da9c0839becd54b372d3849145ba4f4b2596a0efaf93f51e4e4d7863c9835b3b170c94e50d1d55e772e8319e661afbf11d7c193b2634aefeb7a04dc35525952111295c7937190d0f72fee8bedbbb6f2d7c52024d11c63a814d34211e69568193d8e83380402d381f0ed24571bbf1637fba2f3a1c4728d4650f957ee41d2a4f44c79551b4745fe53d5c498e47e09f29a4cd720e6d57a78259c33886e73eebe822a8af4ed31a8fdf5772675bcb188032744a7605861bdb976434d04771829f067eb7f58810ca48491a72de231312fdac7ef1ae4d89247d8ebdc0929381f4416c3bd3fbd8c66a69e0d772cc980631a01993fbc9526815c6460494c5cc2769da0e8a9de3032acc324b4f7254d80a175a11ab8d9e914211e80aa4e772b352e8f152ad11e2ff08491ebc8d10d8bdad7722d04b3083aa6de5d9098ff17839499addf3aa30d94d3a9cc0ab466d74f60408096237393014c1026d3ae324c35897eb8de374b2e5c545ddf1c5370ab7a2128300b28fa1bfaeba6c707cf019c494d42dd7040618250d644780228e7240bf9d3a6610c6f97faedd3a5e193787941125946fb75cd0db085713adf20d18f889ef2e5ecd686d84552a2ee493cd42abcaf6856d4c4eff863a0f9c68b4cc7224761535b82c8485b8fb749cad303ed7e054fa73368c28c17926874efa88fa7282b06d620b5a903a4495e8760c7ab5a72543bd9c9f95df86058ecf37940b5f1402e0b863533cc46a06a7354f435d3dc586a641db48482098229c8c1eb68a8172eb8a6c1d1b9795c21e1a5cba77d6952c04a00f36c58528e24eb320067cb5e56baedcfec96871b10b66f2c61921dfab895cbafbf553b4ea8445e260a4009454721573ce6f7f6891dd6fbbcadfffca78187741cc4d61ad23064f68f47a333fa772b704e95f008fe613d706f246acc8ed5688b306ded114656e5bb69d05c3671172a95db2ebc64a4d2c7a75cef617340536ceb589afca354922ce854cf4c3150b7211dc160c06df25ee0c0b5131dee2ae76104b60a61e6a944d591778e0f21d341e04cdd9fcd3cd551d423bacabd7c1e0148b19c3a3497061ea5405c49eacacc62fdab0dd78352ec073e593cd3b4343ab5047568468f7507d09cf2e62fdb7de2d72978b242bebc1f4b5a429c452f9c224a3d4a84c4e8cdc8a295624ba43fa250c7278b16a03780914aa23a07ccd6bfcd1014ba7343c4b83c30a99c88ec4bd6cc33e622c520a3ab48c6a0d0bf4ac9aa0fc8f41d0bf7d97fb19ccd3aeeaf3fd20874bd3f50a217df429dc9f25fda1282003674f47240ee3856e4bfdc4caa3ffc2d3309b02c9cdac94a70635ba15786fb8950e40dc52fb250754349f8214e3293d4d729f9a3def41f6854e565a22625dd2436e09ec6a641703efc673b4aaf4de7a7672ea76d0025e5851d9febf1628ecbcf9fd11edb5bd396c739a355d84226db0436ff9947b9c84bdaeffe1f0322aa11295849f0bed37d58aac4329f38a500728a372ecfdc7229a76619aa48136705ecbe36b846c7140dfcb1f1c9bc1569f5ae61b3aa7a8490d7d69235da77099494ea376cdc2be60eae381eefd7c43490cb46d17163836940bca179260a290c96a29f8403c0043efa70dd19baa9a449ba49808200dbd6f32563f569e219e8f0409d0978f994e0c3236038f5e4eb7a763b8265e0f72a215c8de4cdf0a96ab69d9e822dfb2b11e065402ce38b40ff65de67e8cb09a72ce4bfe02a043162d2755b09e2cc0a3784c56c21bf67271fb0e15e956acb5f02ddeefc59d160dddf72495716f98469c2621b9e94f0c797ebc8eca399cd847357293cb66eca4737d7ced0702baa08c60279154653e24f4f426f07bff2496e2b67275ddac796d27fe33d6c0c947e92892e3f0c92eb8899b4083c6c7d2b96dd399047b5e2571f3c9151d4016077af34862b75f80b715d20dc3320c8cfe2270209b72a2e7805d9f453faa07c38148f992459ca643e6392c96af878b8a1735ebbbcd675892d260d9c4d74d7a0446b48d7b58e4b69fe48ebc247bec36a17eac4cd15538166d394623e65a26a7aceade35114c078b71846a1d597dd09a0b83289586a272efce2005444eb3bb76a3b1f36782a309f05e2e5fde1bc3bb12f7c9f35a907e724d25dc464b323e98ac3e5bcb59fbb985e8b85cbc177c372a3b7a7a14bbaf8272f3f4b4258fd2b53acf117fdcb45ce992028b10494a67ddffa8fe4bace0d1d41f8a6655f0a3f97abd9e3a10764119991c9941fd0ab2dd0f3ddc33999b594dc4726744136c565f99f8a7e5409a93923eedc590087a89024a9f6d594accd61c9a0d038ee99c7662b2c5dd41f29a8ebfba0cb772b08f872618b4834c8086feb962729752e59e064b635200785c56aafa1f0d0d25c5abc72a3f24b95f39a1682cd872b520fd010d4f8e133a8682dd33320dc31f1bea6e38aa696cc95668261fbe7272af74b187bdb8f78adc84ccf3187686f7091f06f731c9e3c5890e215a5e1a7572ede68148c4537d3ac67d1643fad4c4a654c1f34bf8b723c63424aec83d08e42fa87e45e244c2f83ee6b1bf23e7aa28ea0a777f376917bdf006d133bda4ae16151621b72ae5ef2e3964a2bdb2707095e69f92d27f524c8e6346adebb02d16a43b31f5ac07aeaf24f7aa55c0734a448bb2c284157164675febcd13d4cb1e704b725b162d751d8e29e9eeb75d63d6f7977b0aeaa83b3f02a414f17905e209d4425c2fb3727280279f369e8c5858416c66a937eda36e331595bc88116f1bdf802872367f22fbc5d68488ec489d8ed010a2d2823f1d179477ee6cfc06274120cd8f112814523fcd4e6d53f769a74a48ffac5f7ce763f0d0089138975a7d68301a9d7273898f96927b94a52c506b0e67f527f66afaf4ba657c064e3865f959bfa7f872ff52baab85b9d9c8735bd05e45c7dbdf1b9d927c76608c7dc21aaf662a437c72328bf4bc2ea09feb8d3280cb86b78fb4a28e2020439cc47e650cb9bab70491727e939bc2e0e1747f8a9559d0dffd87a05f60ce63800be65d3eee5f8e4355924f34ed19dca62adfe5e7e933dc509b7989f4f164ce92c24a25551e6fd4f35ded520449eb457c795750adc302dbbddaeafbd1275bfdccd81f9c1135f18d40b1ad725e30039ded67583870b9e64e17311267bfbaa8fb2fa141bfdb53054832f5c67293bcf8ffe1cbada9f85420993a1d0d7c45743da68af16033623c2e9869191e7200cc2ec62bade4289a046230bb13ff04d1994a56108304f047ec5e53143ef82891473eae3a6a9317baf7ccd0e28eaaa8f8675029a0a6e16da89f32d8e28180728c1aa85eda6e316e4a3a90dbc7272dee73e390604cea8d6c63e9aa7e648ad37210d56572425e5501c4fa8c904d325138290ef0aa0cca37ad1dbf314ed499b653c44b52a3440c618a4ccd11a71892cb970f494d78ef553e0dce7e204c88f27672b2fd16d28de40b84c337621522e414e57bb857fc706d3f5e5e0af83b7f613672f8ee69b2e75df461c8e080c423bf23657e46e66ca3087ab86a18620a5fbe797293807bebe6b820a3f2625df49c5016a910cea10e05ed044ed8848b82a2232372684cb8ff70cdb0a01fa9a1d499de4190e28e8e8aacd3eebf06af669210f2d3ba020000707d56f1f282aee234577e650bea2e7b18bb6131a499582be18876aba99d4b6072685f0073bb44a9aaffe181818112767df5631b8eede18a5494a4284897d5ee7216babd8800deaae5382b76d2a0af8cc80e7ea28f48635fe73e4c9b157ee18172937262c0fb47576ae149a89e76a03224422949efc15adbf9480dfe5cb2ed8372adccdd9a736fda2be03a4cdd134786c75593b0a7fd9b4b7c3806c73482a86a0f6df402c7e09c1bcf7b06a930cb91e8f7b383b279fea2d2f81ffabd73058209726061545f964b61f753e538b6df4f1ad5949e4a8cca276eb62924b976dfaf983d56d48bcbd51c8769fa54eb480d9780a436ef8ea89fccbdfd1798c022cce8fd51caf4107a005e2859ab6cb0fe76964c0a27e3cd3bc824b890765f054636a91072a778720ff1ad2e0cbcef5492a7bc33373f8ae310b72e2307d8fd99cd848acf63a3ac66895ce4cc9f8802d80255cb3a52e2ec73b01179bf4d0d2e0565eced7d72a181871a7f3ffc0ae9db461164bc43eec7c853fb87df4e5adc60fd65277f1072523b15283c078a18fabe15aed0a4c02a5b71040a426781ec18e6e9628cb85772b56cf03a56a3af8410bb40eef82f04101552ce2c0e4223a3d9eaf5c2f7ddc8700ff1f13b6a5662e2c88f4f01744d7921b8c084985ab4938aaca4a8ae3fc3a107211f371aa27f57a5b6428ad76c9b5a5de8b3f35b1360b3588beab67841cc426c5a73238b62b5ca8e4c8ea0d023cc521d12dbe7feb63eecf9006ff31ed91c7a6755739875b072154a7b12bafee8d8cae5ebfaddc971be92c30f82cdd8b0b0e2727554b9de93183855b88e77006d874478c74a928418669c24685b9e8a3118077295f374dac6a8d4ee5f9d0302fe75f5eaa3d37f768f6afa7551290b375dc1b172da0f43ade826a28498a041ab5cddfda0eed69ac2ebc072fcee1839e285fc7b72884e7dc5f01a85bf1197e0753897a2ecd1a99cfcceb0bf892f7732febcc853727f90ca9fe45728c147fa6286406674a42d2a3b1de335d69d7b610b37a29b085693433cad1e973e46abef6d92ace38c1b11c0355b157c3c640cc73e5518140d72cf1b017db537540189320b0930e77e6d897fc2695ed7bfa6227703d279207e7245f1fffef777b21316d48b3464ed02b7f5f2a5b70af1b01ac3168a8db0b85854f9086977443b2fcab61e9cd768a4db0088ada5ef7adb6b2ce06bf5ecd45059726d230c107a54e5c65f4c95605122cbb0a5966605578ec82a661e56933c71d7724e680a49ef0ed4584bac20bc223d4728859a2644e238f560676bb80520828e724b07a83ffd73bde3db5dc504f47469051fd83a9f32c33c2e23404d77de9bcf7246505cb8ffda6dd3804052ce8daf5c54322c3aea143de121c11c258a0589f4728c175dff5cc827b3e31d92891b0e7ec2dc880bfd547b09a1ddf2c179dae6a272202de22e6d3a9e76fb838ea515b1c287cccb9f771902c80ed5349c62aeb2be72230219ba9be04bb29c4b956fd2eb491ce08e68d84c356b242d7eca6f2637a172445bd146d2e7dd88ac42fcb307f0403db538d25fb339c260e024e66e7c7df55ef2dc136c2ef0b1f2940472e8e100b351f31200d0cd7b08590ce4f8638425270074b4a123d40952b3933220af4fe3caed60bf292df280f37a499f46a5c826ac0c585780574e2c7a356ef5099ea537f4f2ed9788ab51a5b2aa29df4558afa0ab72679a5aa4672e35b411898c11446a494c89759f74cf7b3099ff7f9dabcf456772e0007e0de4a7b97ead56c2d4ee5afe80ba2b294da2a658190c6f70816f3fed72842e7c227109c60b45cce8114c9b9228cc0e428b2c2bf6780c764ce7a789b87287d90bc87a8c09655a15ca7ccfa3ecb10280f18cbedb499c70d58dc70a54d07256fa7b541042b7e44b692a666cf9fc1a3eef6c862afd22459dfb0e4659c32772aca9144e0dc032cad6b40a2a1059541b96c4cc6499e6bc7284cee2455e3e85228ba047fa790f277ee6abac624b51f2bdb9b978c813e43bfec19a20708e5cd04128fed1cfe1cf7d6161f2389a9aa44400128dc2ad4187f056b2e8184793b1c57246c12646c20b222f114d6c699fec1ddf13a815da9743710d5fd7c21e326d91723eff2f8798ab732ffb74ad9d8ae1c0552e8d6e456216d851a5a0ac30fa135c724efc1be9e7b12d0ae3b7fb800b58e0a319f426dad4dac7c9ff4afd96d4d49e72b5abf7525b023bd52ca6e531ee07ee9967f9a88bb84abcc9bacb4c28b1e609676dbed0ef1a55981c0adf5d758129d53440fb28c2602328b1fae892d19b423472c995121656861a5896d529872cbffb66501e0070a2a4aa7858ccfe81ee56a9174b8db53a9e7f863d4557b0757277311120c34e90b2145ba66882fde40c6a3d0b4b4925a3f115b952ebc4c0d527759a4410bc258f06121232455f813a771aae7277dbc14d7403e687685efb6bfa27d5511cb83291d4d4d757652e62c1f9eaa74b404ffba363e36ee30fa176ace42c6d3597422efb94bf3e8f117aae858a31ff7217aef1785d19e691c0f82ed00cce4887c8d5d2d424a2ad6936bdedd387570d31515b0f734fa3914791d3651e30c629f28ffdb490958698121239ab3f27598f727b4ca68574e13055bb815b83a809e3becaf9b8abfc87a95a23ebb09331de24727cf4892a2b4fee89976878cac30fcbcce14de61479c554e1cef96efa8dd0b816360dfaf4bace61e5022cdf6cb92be4fd53bf5ee91519713b079841088fb2f672e9412fc2b3954128595d32689f9466bf69ef587b03f3a8e9e8b5d07e8b250000cf6201b18b0d688bcc3f3e85ec7faaf41cf7e621ad82f55a8a5955ba9dff6f28de9532398b135f297eec17c23cffb0ac569098d126b11dd07c3d0827a9bf9e5dccd0f26be459b8aaaa6c673be36f6c843fd164a4d8420d4fd9c9f69864f5436baab3d49b13fe6c64ae980e0fc6f94681c025398c52850e5b28724d315212365c222bedb309bec85734cab7ac27413e9895da1b64fb3e03c43d5c3dcda0db061fa42a7f8abf6f9122e133a9e4875ffa91a497208761c8a9c55ce973356131be72caf2b768711f7250c34f2749967c5975efef2cbf888355492785e6627e6791727e2496f184d49fd69afbf382efef4feee7a97729a4689600cf61280f4e73897291ca55773edb783ec50e851acdc9f9c1abc7ee47ee8ff0b8bb4bb1761a540e7234367be7421ab4a9e1dc31a14f1ec5dcb88fc3b56ce27bb14fc22f182eb06972cecf93d0365d6da789f727ee69f0b811b7d023d869b46f5e984267a8a68ae072df0cf5d847bda04090d29ecc8b927d82e7769c7e3b17830049902eceb62b673da331c4ab043835ead9a218c5ef1e859f3d3b97965c93ba5769a82df67260f4031099a838454170eb4e0a4f751ae606ae86e62398dfa57c7333a6e6347d7ec272d6268fbedea2f4e1a0a0973ada3931f83a6e08305a33ddfae63d34d4ec3d22720d06e69149e8957d99060098b0050a13c9963ec8f6790421a82542afd6e6015620487635c93ab7707c2d6ee880c563e812588249e2f040dd78d0b7d7397df12d8632c5597f86c91296e74abd2b7df080ed669605ba80c512b0545c43c33c013c8ee847b9bdaad8b5d9df38881eb30913713928385bad4c8eb8816f187329bf6495f317067afcd414bb59b5746d5f360f554dc98bc98cd40b964d6c807e4333715f5f182c8f5199f1da78064aee089937c867877f44bd18a647d6dc6afb404438bed2cffe9c272b593ff371622270b59aed01b007e25d33d2771cb280c605666e72288a6cf73011743d0a5f0fc56099d50f8149c986d5d185e4c5d34018803872a07637d2eb1c6fe7d002357ab38e95dce301587ecc145ec846c75bb1b3cdc548ba8ea34390eada9d5bc5e883ea91350d53e9fe857b14578dfb2ff99ffe9cf536229eed1d02d68728f0005dcb607a176d1822929a4ab1e419a7cb495cdff2df729cc9f22e272ac431b601147a647e04d04ca4e62b68ad056da765073c3dbaf01e074463c1430f1520f796073653f8c38f8f0f76889b4826d83571254b1d23245e8571f3399ce190a31711bb809b405a1e617b0b960a3ab286f7a300b18c638f7219dd017e4b1831e6a8ec7eab93dc373033412b5c39e22de41560a63bd2f3467229daa41125c7565471cf4dabacfe946862c45c980154b083491a8c0a3ca3ab72baf3fcb0a3a30d82f2e620d111014b46c50f8ee2d7f35ca4d9e76975b871ab728acf274f736e259057c3b574e389fbf88720587941c11b8da68d763b2179a97257d35520cdd46706879ca1a96d91915815693e63e6bf112a3d35707a46d26b61cdd3c1d7ee1901e4be6ad54d21f972f31a9d6fa5ba39fb454c105db300df1f6563f7d4a90e0ddc6f0bf8860580a792857c99a8ce32142d2ab5992c096aa1806d41ac804b986cf4e6210f8b543cf29caf7b1978d5413dfc308fd5de3736868972537d0a36ff68deb68b6a55351b922ea3e10cfa664f810eb428dd799f4e6f6e72a369318746d2e655d86aee3d8c36e518cbcc7a65e0adf3639d06ebbc678d217242714dc0826658d5872622039ff98f47c612bd33459b986e7810245661025b721785e1e035b618df985a256f9bf82291ef5a046931a3ee69a513e2b09e6b8f4ecac6965a7ea1770fe6acae18c4b4c72f8aebf4bbd003ad427b66c298075eb672c57b16be13b35f81ae53c6f2544155b45f2807cadc3c4be48b966d16192c3b1fe3baf1d76fee30632aa3ca2f296729ce5598b0c7a1d2ed034e271ebc346a9e72e0bdc9e510d3cd7c23cd9c5d4519d0f3b8e0b56529449f5281cd590e3e1079722120c3740729cc7d8d359d0835826de962c4b1aa0cd259e3199b756201285868687d66cd5e89bde1021b0de3e6609fae99d6c312cab14bb8b34ca1c562d8f772779d62e34598de1e2340c7256d0de324e12345f67965c9498318b06a580d927219a5fc8cf819a240cb464a6e802d5bd418f5a9976eb193507b180af0e4bc715a788dfcc2ae8da5116e89b2fc8f26ce453820d8c9cd28f112eef7ddf93a93ba2aba86353eba092e72cab670cb8c6b27824a6dccb39c70f79974a5ac05968b347201520a7a29a34f523786346aa25ae7b174681b37badf5391e0522db8d5671f722c572a297c910b5952ecd357eaa07ee1827aa3ed3505d7342804b1d3187d5272903fbb8aa5e1a081fba0381a47e3dfbb349bb3a4c3e51b8739ed5bc1ba8da872c43005c4e401fd3e12c1e59e2f479b739dfc6111edff3ea04cb70c5c4c1d7b7246148dc306d3f365395d24ca180f38f1a1e38fce964c9262f15f4498a0917b728d3e2582aed458ef6d2c4b35b491cc40827bf341ece49987a1a302605644b4723f2ebebebb29510507e7b9c6eebcbcb0a2d0028e886168bce1e9ee047af65b36326ddd5f45ab31b169d77ce57a18f4b469d0031fa843e43feb27645ca4f9c8726789f9876acea46eb8cd3572e90f0a7f8f909a24074d2fcf63dc73555922f6728d60085a88422fa065796a693ba4717d5753e8a57a83630a235c0261dc1ed97209234faf350ed869f859de44603ca798a9e603af9549f6c0000d256302f1e34ed6f47f1be442a5011e2861cc77e20212c952a127c51e4dca28c5808517119972b65ed9ec6b70b4c49c67cd3cb4365e9ca3833cb3cd81037d976883ea2051076c269edda81f601e6415ed6dac3cd91b43ab0469da63c41888f8e275563cd63b727f7039f62ccfdb7cb590ffc839861f1871be4f6071d0e3228916146e091c210cf9b2e21a87730e6c7873064a6aca9f502c9752acf6c84a970d7d8654411cfa72eafa372e790b69208f4b8f383c822b7e4941ac12d80a186361dc1f41821d776974430a950d2955c402cf42d8c2d0e3c1825980225a56aa24f2d380923285167226b0cf14f0f8089c68ffc3cb8dae6e373481884674cbe220726e6a0934035072e15e8df89715a7e56a291e9cade328db030a72a10155457f23fe2efe5e53491a3fd56911ae40557571bad16699a7c01338be617807611cac6b424d434d61aa72941e01efee580cee72117a30338d10108614ca0916593cea24e3e595f59eb84fd4a2308ffae4a93d7c874e318d220427e01fdc19244f584812976f5904aeb37246c7d9b1482cb2ada65362c63ad7b9b4ad948c561e4e8c0b2368ccc37258bb156a7dc591eea72c0da20c2a4c27ef0cae435635df04756b7d6dc619c50492211757e394bdd76e262e3943ad4fbcfc93ab98ab06044ad1b2259b0236595672137243564f29cb712868402e5f72864d94898389519cc158f4f31c33a76a650e5d728889bc5e531ea86be6138757a387efa7ecb9b7cb4e98be864f70d05172cb0f7227362cb56000f43adb8f3df0dc173f2ee3124ec8b514d8a8598d0559b1592e10e751478a142b2628238e49ba67294cba9faa6add624f41b3899451a84783e7723c761eba02b2e710d48683a8d7cce7f51be7141d3ce4f05eb07fa6ac223b837292e7adc65c2a93c6146bacd8de4d4980ea570bdcc150ad4a514250ccfe15e172128b75f25270df7d1d7b1bf29504a2f9171e4c2f39b0274d7c0d7e32767d25720cf4626df21e447950691c573a117a7d0cad3df60dc18f6f34325cd9db95d311a9fd2df62de9ab8d5d7b5537caddcf6fca33e916931e836432a539dd05adfd72c0394e34aad522286e4fd0f4ebe83437f722abb1442d79c27c66275666ea5e59016b017ba7fd3c0d1286ad4fdc388400758887dadceefe2bb3037d2af64c3e72e52e67915e4003d3c9860a8c7fa629173311d57547dcf23c831b356fee9b6a6a32285d35b2073e4d51884d8562d11391de4a8321a543ea33850522461d30710681f03a16bffae9ded906edfc1fca7e46d94c7f83cb526e66c7a5627ddc6b0a72a733f8bb5445d9695d1342efa9ccfe0fa6cad1a21c9cb8c206ab3346fac5ca72bffefbd5ecc541ce59ff1338d1dc646e6a2644c1a3e348830d88c02a266390726fc01e61739ec7fd9b3a55d8eafa1af250acd8b62daac1ff4530e49f410b3a7270e3797910258c93eeea9e403eb87f6daf893f003ebc59ba92cd58b62e6b3f396fa0f54ba996b26c22d7af2b1f0d71cd3dff6481a7b25f628175c6d00eedeb293383da7af7da2049ac5acf3d400d7919834c43b1a99184576aea450654a53602063741969448ec9978ab4590bb5dc13dbf6e62787c87fa5a71c3134d0ceb965f7e44ccaba4552296b84abe2fe9799fb98d87e3a9799f3cb25c96ddb724e839216303080ec832da3e580192325a8dc1e9e9ad84859cec32b2f2f6d880cde14b72538425e19ce81e90987a2bb2479920d620e76b3f63757d89edf93fa8235d9d72f1c3a492f29249ebfbc4b94efd6a75a8ac0cf9852ded3f360be4967090e1a85f167ab02e076ee8fc8aeeb45cc1084abf07a49edb4ba276d65ba9d7331275e129b2790bbfdc0df4d015bf26aa9cc292fc469ca50a54fc336ab1fe8613efaf3819ae05e93d9d3c52df10af26ad0a4575df4821a7395a519ae9eed7323f34963e1f4b24941e36b05c2764496db11bb653aa22ab5b5ab4f80590c27e68d82f1af54c2a30968c3204c53eb852c4357c75f138fb291fa6352a5a0e815f2de522f96a230fc6868725f7c4a14fc3dc47f8493226809debc8f27f4a17dba4aa0b9aca567237d604f2f812519a74bf8f115654c8f4fb3b501851189f8e3a93eef03946547212e82968e49bba85ff3e02553773fed322763c6e727fd4b84ba276999dcf00728de02c0559a56bfe5d82b9d950626feebb59796ad811bbf8159fba8d81c985728fb725aa218e16b8ba53d1654027c8b38cbafebc3c7f2e0f9e57b251bbd9fc72359b335797b9f8ccd202103a55d7ac9cac1c61f84f59f4f9c7479edd6545953e8213d4e24c22a49c89a5c01f1f4cda1ca7755cb2acbf6349b9c937e205f8f472a0c4653e43777400a6e5fb605e314235ee2929e3721c455ed13ce9c81d1ff86804a708edebddf8e1446ff555ea2f590043b0950b6f6093a13e4b9fc8a5295c0d2f7cbeaca35c1464e435197a3a05c98849429da43100fd2b2d65a8eaeb690042d1b682f96d05e5036fd1ed4cb706e91cb9d0f75f6a2990ae13e198f1b6fa9b720c3329c9a0c4cfe8c14937a27933f0effe4f7cfa7d2dc4052a058060ef5e7672338f8fab260ce0d86df49ad29026c3fd443c19b2b8142b76e4b47dd9be22c872d8e5691783f7158f3a6c4e693231c5acb716f0d512f8c25cc742ebfe516a4c72200af084793dbefbabb79b21b68132195895cdede2702a1167797ae0f0e7e72c466ba44a8dbf4290679199d5d74f11a231275cde02696164d402549322ff9458a0bbf518da79a99d7d08b8ffdc3f90ad05e898eeffc429e06b9520047c8d5872f93008a27cd1c0870901ebd1ca8f91a9fd0177b28031aa84443b39451c65227212d5618525f799fb06568013a1b52cb61fdc80f116fb46d7e8fe262193ec9072faa844ef7e8a4f4a8276999271eb8161b5621416f5fa3b9d9d086a8878d315724f4af110a0ec2134bd18505527909c2d117abaf05a4f60f1989e5b59fff3284a0c8f9f5f95d29794ee8f967cba780f02f8c6a8f107fab4bcb053c39e44b57b09a5ac9489430101b4b8f7e4e645052d2b641c6af6b99f97a6fbc0a19388659272c2d9707329d82621de73b71023de475cfa3125cd4837c3a62946a5bcf43c48726f9cb0ad16c8d77338a6a79bd1b29f814c2050ec2fd55bb653f22865b9ee45125e99a8111c589ab58888fa5bdb192b37d8074992bd25c5c02ca10012bd2f73434940272572e143918e01133cc5089e4e57ec52890a1d1e7fdb54f4ddb689ee645ebb880fed9f0359c114e004c4c19eab6de6dd4d2f4b704e553aecb81014b560ed38994e721377678ac5bfed4d0e709f7fccae8cb068c1252a5e36b5f2696b02a0e3b3a82d8ea3b52ec03ff41ecd42dab7f4ad6edcc21e4a289014b3e1b8407203f47ac7599c409ea920e312c8e2e3a1e1757060c16494bd5452757737ce332bca422d79d777ffb0db5376eb14975a8029288c2dd6168480beb6409060ac867253265191f208a89f5b58ce9d0be38b39c7781ff3e68bea99462f58f8c2ca64729d34035c86d2409301c03862fc60230bfbcb072d4d968c92b02723f53a20cb72b644638ddd35c8a962022f22c3a32d347a93e51c65a349329cfe371fdf147572896cc683d1741b4160d31f9351161cac78c99cd35345b436a4717476828b5d7220cde37c70865a3e6d95ed24d25469204856278dbd8757117b627d5c244eac72ebc9bfee2767fe96b7756731042c9d5b09f93fd715ffab13faf00ffb7d04277200fda2c22a3fa06e3d3ea4cd6ab68820e151ebaf6084c30f735bfce7a3edae72c83e23a8effca859fd179e28e0ebe39ecfaff1148edf28a56ed7aff3c9aa2a72ca6b0838be6a189a3eb6a1fcd58196e67e99d73e0a6801e5fafc4d90fa1441176546a1f454998421e06530b7fcca872d3eca799034f2228963dc5fb9d9b69325b8b806f3c486b6b0ef3d5a261ce5259f38b5c8bf7c07628c6008ce229f171f37f416763aa040e08b0079d036de54c94667a074ca36b7b39e8279afaff74586187b5cb24eca523cf0f5b604f8a9cbf64159104fcd66124c0984921ead2a806d6ef705458971bc6b81195130b9760a5a7aeeba5ee0f08d866dbb53c480fe55fa72a164e93cf75f82f16a186e7610a517021d28f0d63c1298dd11f6d06cc824de71b398d7042482f4186acec83150a819455f495fb22241293dca05719a185ca47285fd78b3a46707c5fe23514ce91ac96d3ab12103ed082f126ba44dfeb727a672da4c702cfc027f378aa1333a4ebfb8152778890a166db8450ada5690c49f23724818ec776b29666401712a35a3166a0b71f71b987ff2f42a14ffe584b132dc72dc13e0ddba289189a1bda5640493191ed06e05862abb83289a9b0c1bb409c520a9cc535ac20b911c23bfd1b363b36957822cd4660251628e3535aa639759597240f001c6d66537fe962dfc847635694337fe042191fe79ca7199830415f9d07266a94dfbdda28318d4b494ba7c8450a4aa2735ff4164111aafc26d9cba5d7d4baefb7c7d99ce4ee929be19a0589895e7a5007b4befb8dea8d502ba809d2a8472f25b98871c9413281cd56a9e60809e4b8645fa03f37a8a127e3b69b919fc7972f0aa3d54ae5a2f29e92c71f46b0c07c6d81f8393af41a52b40150691a5048e728b165f410f50684a140add1399a7981adb224ccb9b86196ea13546d6b3bd69722265b2437157cd64542696437d7aca27aa44ef82dd47b0b9ec9ded37b1d5c172ea2d0ecdb6dc2b20655b924af2a644055c9a9adb89475d813d868b3fe7bbbc72fe977b76701a995e7a97dfaf6ac808ecc7c2fa73c0cfcfd89ff5a8af971b4c7221c289117f808c04ad41e820dd8ef3d11ba8fb0586de6f6a76618db785afc172ee748f7e6d3f87bb767890682959c80ead11fd14d3ca6b9c49b3f4cee4d7e772cd644b1c80533d509e44ebfc9d958b3294c0a2e6f9cfba6bc9824d2a6f7f705ad40bdf0edf80076a20cd2c65303f0f6c9ebb361eb438e8cea4cbf295651bab2946b288f8b7d8a05870c1c8dbbf5e49d7dca5a996bfd635607d1dc8cb9add9d4c790de5f6d600d155b2aa2be322f53c4c8a4350a30b291e66f25f3df14c1b847281834abe21a5a1c800d00e246a89cf2a2252f7ae018f2779fcb40d07d1c05d721042babb7d0be6178d1fb34787715b986624b2aacbcbf36818376975b09f24507e6cf79c63b7b9932d4a4495a8cc39a31a28e4fa05f231a162125d0536376572ecc1de40d8db4f91cf6c622d77e9a573ccf408e7911f1732e707fb62f26f3d72fba6b6fcd47ccd78f81032b323f64288e22600115ba2132856b1d8cfbf5f22612f6ef08721e0bc34ceab4dd173d14ce824f01c378bf6d3582e916f804a0c0222c2a282428d6508275a96f5ee12d5d740787bffc77b47a10481f6a8cffcdecc72dc982bf8b0de7ab017fddaf07645b69fec4e30d7532338e2fcc684aa1318fd7206f15a742162ace388cb6f027c845b9c1718a61cf9501dc918ccb2b03a0fd6724cf286df0925640e1291991e73f3250c17f7c6c01addb4103bf4d64302b3c77225bc6bc4f4b9aa3e7a30928b3ed13a21ff3dd4660a73f89416535d9becda837225d51e9fc7b056309e77e79f8880bc10f92d8694845af2479b5b2074dd9f0a72451dd6540024c49cad55e9c8eb5a159d4f3d3fa9138b5ecaf77cc64674480772992fdcd3a43dfb80743d804802b3b8f905efb1dcf18a05137cdf612526386f4361b8e42c7a58d86ba91c38a2f8db5e573ac39a0a10ce9add24858d60547c9372126e3c655abfe6a1e1699dd68aebe227b6e5c7185963f47e33c4e9f2e675ca6d8ad7a19c707c834f698f12fd45c24802549d29b0e9b16880b8afdc87cb7bae722d4ce4dd4e06a42a33de241871f755bbe82e00cdadd5142d99871ead3734292ebdae94938784d26901eedd33556059eed50fb876f9f8754762bcccd47d17c8725230106f9e4dc5c37c3118c161dbfa71f195f06f313977374c5f6e990a6d01726bb2139a3dfbf6489b45a2d954a05e09e9442730e962745784bbad4e8db91d12c6e86155c3005e788efee81223faae96ab126a5ca3fa6dbf7ed98f76a2cfa97269ea2b7c692132ae00dd1bd2004ba1c8f172dac20cf951fa809f7b93d38e0e72c82045cb2f947ef06f371f1182987ff01803805da6786b5527f1684d58482272305e6653481f5cddd52e6b14adbad4aaa28582bd601346485e6c800a048147252f352f0e504b1454765cb5104e96e88e8f68262ec18efb3133557899d72e247211e4e787d614ada6800d0858f5fffe500fc72696ba1844fba28a50a88c91b8721701a4aa3592b22bb0051fbb452ae494e17fd5f2fcf4a1d7c672e97270622e35d89e2e15e5a7f3616a89fa494009b405dae0900afef00623747589ae8569f0724846a10d37ff1c5eed447fb29861f1e5cf049ac04703ab30a05bf8c096968d2762640ad3aade34008cc23716e389f35ae1347e18396f5cd1f6820191b00f02725e53bff5b3ae233f94368380b3beade069194a7e547bbc630f529fbe54caf7724a1c5db82a48d39b3a0b680ae511b96178c1a5f61f5a1473a27e35d212f2ec72911ad3fd1fc60535ea74023d02ba15058f9abf60b6b6a8cd15f30afc8e8c1c7266739bf1f32515e2750e7489fe4c4a97c65a0f1b3dc04b8d67e68f27e75d704cb09f914f73741a8c53877201fd19054d50dd5ad4da5971481f0db257fb26b47266a8cc5d97e0a7ea6980c774fc0c20bb0e178ead01f51896faacb32714afa27230153d5a8bc11d43ec4e81dac3fcccff75cd75fc3ee19bba2a451f34dc2fd03e8c1360708c367103c3017edb53926bbc9075342196544af781c4fa8beed162653e921789f3e3ed3e9e24bf3e876f92de63f2385eca5fc4f9dc55ca06006bc372402549eadd05f9e07d82b5c5760a8b43eb92a698ab8cf669071db9f90f98787275de491a844cee48611b3233b93ca2193d19e970b8db3938788a32e21309c432743d535142be745220a450d516fac7d7f3f0e300dc3f869968d72357115825720a1c9808a43dae5b3175c6c46733763ec4bd88929a6bde93795b42d76a77b80fb4bbbdbf7f1e93c597ed37170bec35de19e14956d24eb0f9663730d37669d1727274a3ca23a3fca110808021b95d94ca433affecebdd2e4d7c4d7a27595a694940ef9725a12d390e4d00eb386b45931f9619fcf716a9ee5a9ee50a0e6f9803722828d617f54a8ab114aecb1c93fc7a2a0829f16109385480a1657142c64ccc27c7ee4c858a870dbb4812d31a0a502fda806501a2af7c347b7e82665e8ac0eb3152f5344480293ba6f8475bb3bbc032d7e7762793578b8ba4378abcaf6ca46c1dd0920e09449c76493375d966ec8a89dda058782be2fc0670e45208817ff1f6690336cbb428e76a1fd7e01f21bcbd35996003fdbc198152445a8fd3add60d5e7295104a7f8d4df352f7ae7983fd2b58ef192c3f5ef73ae7df7bb842086dfe6772da11e60dadc0124e7234b622fc8d407eabce4bc44d47f109af16339d3a75dc72c8a0e73e65f0591f8b3b8e86f257c2a300aa383b3cc7d50c309e5f96640a45727cc481dbe2508f5e81cc7819d0182999c3109848af2dfc7a7582ef349cd4a61c90cef6d8459f2cb556bf8a75b04beb1d0b0bbe8f46145427e1d64259f3f92a4862be7715795d409025574803449d50d3c8c1d17b423f04ea00b997fd599716722256c5c48c65bcfe4ccdf91cf9c1d267c201e94d2acea2a94fa94fcb05fd1a722ff94be35f71765785078be58d972cd2198d7741b712c0150e9f88504a109d7222614503a4dc16aa0ce70ad4a5be2b19487ca6588af6d1bb733b766278cf1e5150839eca58833acad2b59f537316ab6bf76cb6f7ded3f2c0fb69ef32c1d6c841be0801e614b3fe2d264b204d4c87e6889f6b640147bcf40738e34d2f6a9af87218e0cdf6621477446dd1e75f7583187726ac5c6ed9b9f39e58224d78e341487249229737002673bc8fc8d2ea0f8aa8379f8af76eaca4af2c31b698ea1a85397239b063beee5331e09b9261a0ec02023ba7477fc3a85aa4b30f6039e5a13caf727e6cd6f64fc707a8f6fc983755f19214ef332f175a2981f47dbc46848cd88d72fd2393b5b57d895720b08f0f54e0966a4cee49d17da0ee836f75320e01ac005c1bfede0e627a0df0fe28ef6db95dcb6c110f16e3f60503536f5736b401d3581a6585890aeb26f4e191bde1ab699ad88a74ed741240a7f3bc69cb97c7f08f1c7236225b15452babf90722a473663dd11f64768ce0ecd14f4a961627f31f582a72c5d6eb8c456ed657d9cba0af621ab904c567356883765063d1ff3b0c8f49617256cc4144a0756f033ea48d4ca6870285d015fe0fd534c439a3f17fd549c6d864b0c900bd2d00ab743f58d18a13cc9e0086d8dda87c1f02773328e1f10a46d6729195258877c63d747a397a5c6249a2fb4cf05ae689b48e17c969d2fc15bb6a64638d5d5ed354a44edcc7cd43fb09d3db817c84eee06dddd8a4500280ac0471726ab83a613b796c62eddc36eeab5b90958f1a9400c94858e238dd75a99442c10d39197cc3ff7f4b0281d6ffa46b99207e9b504e53b5a9d0169f5db0c140b71d271578fe2646ad538b3576928ab9fb3484626d1d96771c691b5104cab757b43072017068f117b2bea09a7936f9e32ddc7c29bf75ca948d62a159da4bfb4dbedf7209b5e3f6b40b2bc7bc67e035dbeef26849e09ffbed6d0b1fc58dde724e993d72be854409b5fd2ecd6ef6b70c6dfad0d56ec92d124de1bf99b8d5264788b8e67297bb7cd2af6e91a53617eae58006f7d21341869907431edf10e68a5ba2c9ec19f87900a8b7a642d915aef7839ae5585f282ffd9e7a10f3104597c7a2db577509812085df3299a6d21ab5429ec485573eed58ec3ab292c429f58d65fc23481672aec2a849fc96911af93d1f08e312e9ec5c0ec8b36b8d308c7a97cba66805214e3302327e78e9aee6cd713946249c911474afae0c2ef056e3701e0aca90c83e72ee63ab1d962211b0367241d585e84b5f8a026853f7c7de9e9f08472ab5be3f4d8e50db84d4ab491d55ff0084fcf375c8c5275b4575fe4d06e9b861cd0aee314c08c4697a2fd90a43254e16a6dfa11a32fe3b554e6a6e23ba513d94a804111e4be3ad057e3dfeec25360165caaa81dba6ba914020e1d0f889227e83f8ccc56b72d9abe3fdef67240fec99cd44cfe2c4be68b22379678942ecdc973d270d2909723496baf5fe368f741d1a0fc7cc37624ffd66c2e321e58621d263f55de2a9ef7277196db07b70ab595cb1ae52e538fa39516076ac03e4b34edbb81ebb2aa1277201bd2cf6bb992694db06f68f769fafa4921c301bd6fb46560d0ed27190712d59416b66ae07eff7c3415e1685254afbe6e2cc5ad7644c5b6b9caf39439149ab49aff755f8aff869d74d0592490707268c6b96eeab19686467a0d5d08c016c3f72ca4b0ac36803fe9122c186089ce1f7bbe71284190aeea2fe99245d5765b6d6331f2b7600ad344a7172780db26390a3587d4ec5d0ca77308bbc2c415794f54c72c60ef1c37905a167024d0b3f2b854ed5eba1341ab5bb23c9f2a8971421feb71baaefcb4d19c3aced13e492e28e0d8673f08e5b5c5d7a3dff7c7ab583ee2e02725b37bfeeea73a2cac653425421db511ca2f44b4d7a8b97fe5d6d6455e9593d72192e2c5db57ad2983ae145757f07a09dbdecd7bf71eb392349915c93c13a5170154975b503498d683d5cddca06b0d2b2c17a977a8a998657826aafaf920b3f1022008610a12a6f952dac8c2a0d9d0b22c5645c3f01ce71be782641acaca9ab5e91f185d8c196169a8b526efe9e8ee9676022c61b9ef43f9fa9020d1b1aba8129bd220709be05c4103ccc7a0449e61c6e74de89b770b518595fab5c4d25351672592f19c8669e556d15d080cea8a71e13d8f1a0714717fbbbc15773f14828062343bdec713eb6b9ee715c1e1b85e8fd1fc6c7d89d7f00473067e54cb6611ab21d2e733a545117cb3e02568330033a661409c84afd5326a6a91d8b63359e9437728f026782b0a01f7d5c9bf51451e6288c6cc1aa97c9952366a2581410ddf13172203796566044519b0e2be20a12f4a90a4ca85c685bdc0f8ed0071755afe38f7209e079d97e7ae0f03a85111b111d5264b7f739a65f3065f560d5c196781b1172125a9534d86919da00b94a1990be1187f2870808f46e2327a14192e3e3f2191cdacb56585c20c905b652c744c3cf428458c235537c0191195fd545dfcccd291fbd75f442adb08473f431039d3808afcabd77bc305894f2e831d0c0b39330b4121840778a9d38a5891674dace6ef5708b9788d7516d952846b651ebd50c8e1572b67fe880e3348cf48891ac04e7c160df7ba6060cf91e7122fd826138a929451f1b3375cf0c4007d0c0d5caf3203b701da95457fe70c744b8178931986253b87271cf9a15afce0118b7d7000484d772863592ff503b4480130a332c5ad7946172f2523cd637dc0e4a0ab2642f65fab5221242b0bfe6140a44dc1fa2b75c1689722675e352f0fc0bfc4471270fd1ab18c683419c27b8e77dbba6f05008c5ee0772215028ec645528cccec8c16d2267f808f62e518bd5c18d4067f67a4b135c99723af8e5eee624635fe9249d455d3263b62464c9106b412f1e55dae89384a75672017fade6de728e40ce9548f674019cc3b8fdf9089db79da9f3ece9c37f7238725303fadb48463ef41528ebc9a6b68adcd34cd4b58364ccf946d33ff8577acb72d7df1441e0e046c6517f658f7b1c5c653707a5a595aed51b89c4f8ac609e550e7b42315a5e59c229da37a3f6550abf465eefdd2c329a9bca6b434346cbc3f672841c3c58b5e8a75686cef7d5cce395e61389b5d8560e94e216d9f32bb101ba72d5e289f313e8a2d41c77f24a45831e9b1d3d327048364f75c4249e86776f2a5bf1e7d8506abd3abe73c0d5d276d496d893e60316746386a535684429b6bd891139ec2ee8ee7d66b5925705ddbd45241a4dae0bfef3ede758d834d50030d359721dd13bd0fd4f9b880584acfe14af5df4ad2b49d3e09ae292f545163d737ec055bb78ae4388eea7534f3bb1298d570546820b7c1339bffe72da06f28ee8738a7232b3f0b1da77ca62ca7c5f5bccc759da762bf883817ac480882b6fa79dbca372468fcfebd9387bc6e1da873ff84fd2f0a2a2f27787da3d7d2f6d2e2962ce332e06b3615561cdc90b5a2044480dd59871826576bb23a04064385c62c75e967a727745a00c631fc2759a8d51e0ecfe9ae3a897ebd817e405c16bbea1ba55716010b242c5bc2fc1d2a860f45e9fbde8eee6d1949ee466a97cbc7616807e2199a272510f3110db5e7b78578399249edaa1a81455e661b862174784056c57094d761c5ce9d122719503f5bbd925490f83b07bd86fee74af159f80cea59bc779275b72e300924c8ae6232f4a136328bf19608d8968a1b3abbddd8719b879e02a8034557642b6e3be12930528316e3573c8619d314847dd3d2ac063e3ee57f100d472725c550f7ef4b82962b47ef6623b9bc6949afccace236119e4219fffe4cdfddb511f872e70ecf02d4eef91e28c856de1554b6955423bdbb99434043dea3ce569720eae8635e1983d5df04fd63f7355265bd3c557b5ffb7b938567510fa5a0b1b72779f0b14c75f48d73cc4eed59f456fe4d50425c993ec08a58fc567d8c46aa1500a733278eaccae2e958d0976cf5c0ead5042d7590a01854abfed3e2eff2086729ba96642b2869d9221a6020e5dd9e53eb4d1e55ed427d10a755b568d16754c72bc47b4cdb2e9a91e5160ceba89086e1b81701cf8a5b6f2faa7e98a2d6aacd66cff6552d9ad77a69ae82695e56bdf5299a40bf9822fc668be9d5de6eb8725c872c0745fd6146f611e072f5103b8437b932a2f0bc9b360c35e4f622cc77bba057299a72a6e292a2f6871281e99c43a1af24c2b7958eb54bff9a27c481ed292ba36ef24449972c2d941fda5c1a3758918c242f329626d2a7c6b8b5dd37c421323725421dfe7cc6190ed5c4656e6b39a937cefe6ff1ddaa11454463eee4ce8f2487204dde502dde6f124a09060ee509644788a2ea47cb0b870adcce920c5a20dd81db977bb3fdd8a5fb01c1dc1709d3a13cf692f91457dc87b25a62bd6f1acd6cc72eb5c3296ca7091aa5e138070e03074b047d2c724c6bce7ae9f75d1a453d47658556d4ec0de36450a7bc352415c8b5f022a6160d6ebc3fa3338ba639dc5f8f31dc019dc090e85b0d171d8f6975b3f5df55277e742e9a34f968443905d4c8f680e9a5c2b65903cd1798097479f0ef0853c6cc394bf41c089b8dd3fed9c4612c77279d74b0030f33e2a15de6e3dda5293e55c089f4bccffc5445f92cbc23c2631729c7b7ec77b5e66f008d294f624aca5f6bf57bd53ca4363b46d569819f3cba2724f1e45099c63e35fc40057854ce2c1d3e6195d8fd467b6e0e0bce408fe04e4725a4eb26562aadf6ad6f97609b7f62750a0af4dfa7e432fc8a76b396239e8c572087590eec505262a126e502775249152254867c20b1414a930e8774876f89972d436159ace1a919a209d40a317b376a61fc97acc3f2f2a62c2751d43bef67872f2ceb42e6a9ca7de46053b00ad4c5b8e22e55416a42bc31466f304fe80f2c272e0e96197ba8216246bb36e7b3cf3373281fd1f974ccceb3cf2eb3ac2f4816b7218b7497a7b207948c3bc13ac12ef29c18fce7a117f5ffb837e9e45d5b3999b728b87e312c8c5172d0ebac567cc6ea6a5c5b7e38c6ab9e6ca0edeeb321d4328726122c003a778695adb1c285d9adc610b5bad748b61fe973cce503de7ccce97205b23970c38dacd9607b675d43564d8c99fa276cf51afd264944518c71ef0d372882838e8fa056ecf85d31f1fbe4b244340ae62cb11138841aee9e0a7dd22e27277e98d59d3ce0f4bfc59bddf5f1e3bca11cc70fad168d1bb1e951db278548b729585363e5422397778087be01a0e5be1f110fc8f4aab75f416c2062ec1a7317220c0867ad1a19f453a702bce5ca0499bcf2db23b75bedd2a6b2a50a279fb9950379d67a5768b5b62fe25bccbdf4f7609f04274e9bb9084940b68732e7acefd5f90e2106eb8268879e2b7b08922c36eab46ed95da394efba79239dc5cf611b704a9041132e6661acd48b4f5672ab08a14c565a4ce5ebb6869b788e9cfb1b905724fca4ab874eb7db96514c282e860c96880c1430eecc66724aeafaeb8cbd99c10b8846bd4f2f6d4f7fe47cfed46448326f6676b49f0746539d6f145226588c4727cf46c5ae585e6aebc016c7d34301a3681a2f708dba650bcc2b1f90a10c76b46db3abfa3c977c9816acbbb46ccaf0f480511b5e92fa62949e5ae6c7c037b987244e9f86bdeabe7380df8de94a749deb587f30813f9ac87b6fe16b8fd5eb52f72419e57fcd30f667e993baf6a26ae992ce8b9863a90cd6670486c4d99990a192e4d669df867b3733040e6500b8d427f7a0100653304854357eeaefdf2e985637215c3d0fa27f6eb364486191beaea2e78f5fff9d81a0a34113f93730334d9505419541c861dbfb0f8970b977a52c2960231cf445c3ba53d9161ecefa7b69044727feb9813f8c0aa0aec516e2d084b555e6939c2cb5a944891d25671170ab26f7275c0b3c05ba459f19bfca429353bb47823e367dd61ff4b9ed866ddff7afea4726cec577aadc678540f5233f12cacc136ee26dae5a39cc957efaa83ad7053cd6be9b1882f43a2a60d6b39dcd32067dc05571fbef4a3b0afeaf696838cc5e8b772de789e180f95050fc1bf002a82b1605d022e069d04c74359078b3708247b16018d5bf1ad053879593d681883f007395f6d0582215a7d505049baeca1a464cd0e4849ececa1e01bf76bf7cd2fbeca6bf65083e8926965bd39593987d44b38a56944a0d66c500587f67326c2cafba43779bdfb164f85c690ccbbd92535859196726cee0c2051ca1e8aeb7e57c21ae6665e05301c4bafe1a3215ff92432bedf0b727da584741636b5bbe2b5b0471086b0c024ec99f4bbfa8b98bb15af4519c8ce729ddb40ce2451648bebec5aa62a28c0aaf0de89a305ae19920943109ffa91d7722e015eae88b0aa39e66739c1b7c1d7bdcb0747221859a506e8180ae051f2832c7476aeeb70a75fb61b9ca4322a89d5895ad70b7f53888c6f387daefc42a39372862df65aba0724177824621e914c5a7563b716b87f4134c77197fdd6ce78027248a329f55d47bd17cfc5f713eaeee9afaf8bcebef5d2430ef0a06036f131a27280182a590c575b832280d8bce6c94ae76d89e2244e81cc6bd2a7045cdecbb460a808fdab8800fd4887b67d61f2ebb7b168f154ce47a2a26211332b407f15ff72d9c6ef7b681166160f46eef2b0c3110cb6cdc7c714038bb6d1720bb78fe3d6729c5bc4a1353d34bf23b73ee4a9a223345c4223276f953afc996e6114fc9484723210190b6c3a81fb40fb4627afc368fa3d949f160b2f6fb56882aba273bc4872eb292066c0fc7a26b6e2b2c76255428923218d100d99743fe9608a6cce8ea93b6d39f3725d956cca541314a2b80d74755b77f9326216f87863389c7a7bbce072a7ece8fc39990b54a517dde2f1115e8076082bae37045081bb8bd0963802c82ad1dd3a3190aa89a0dbebb987731ffda473ff9ec3f1ee1f7f4b9a8be7f73b001ceaea0af9072953dc05d74150f0e5b2de5afc8a2a83496331f574e82c816f0772ec4e3df09e8613d09b61439016acb6de40f4d2e7475a8b17e1d94c1e34694072cbc16c5776048c18d2e3ea1a6943ca465d9198a7c5d10ef3bee0ab57cc24d6721e0f76f8e81dd0bfc09157bf06e15bb0950d10531949bca2d91522b3c8256e72b3edff15cebc8dc5df41add4801206d61011f18a39617a483c1ce9d152ff880d3a03d92ea6b67069e3357d615ff882025fce9da8a4a00afc96cec192cbefd8644f613839a4773bd6bcab137a13d4af386d07f9031a3068c8b4a78f638a8bb572e216f3aae7e3d24278a75f43406d9aa89ab52afcac9dbc92652b0515f5cb883405dcd96443629967b79f6fdc98daa4f560d0e24ee31c19ee9f023a9f7f38d172430a63c524d039f0846b145530d4c61f950b5933852236d33663c66c4a5d6b611b6cf2b6b0d053a850c4bf013d647dcf1addaf2e178f3706769e241029490a72c42eea817737abf27cd03aedbfcdde0bd9cd82c854c3dce99da3c97dd85b7d24b7b23d41b8464f9c67c35634f5db11f5f943b182c156f060759d16cb842102727bd3e3fd40e265f16872dfbb31d3dbb2b52fe174d3e728af6c4166be6a8945726d1819b2375c9661161c6ca55c5cddeb8229d6bbf5fb6cca9e6ec3a1899d1e6edb86e416336a34559fffe2733bc90769399727bc77622e0a8e29e450fdd6d7190e761e61ba20af544a25f941f910bdcf4f42e5572a74843c788818563b09ce7211496819f0f1018bbbbcb75ee9be1342228fe34cdd6b1da672ec5c5295ac0b72600445350be2c9dc34113c807ac1636acb1e73407536940dc7832269f77377149b669d012b4e308661fcbd6c2dbcad4e46aaf4df2548a8f0525a289a48f8eb7253d343db7e6382e27359ce234108438cd1776ce3a87fa9c300fca9a86cf2e1331bfd0b3ab8ab5316d9a921d42a97f12a941b59a1a9b9a0415bd3d395ff875c69fa14b3044517a10ecdf156cf38fa081e62791dfa550e41f4c816b4f71706d172c0a7b50e8d0c535c2e26452cc3082a466bcc98ebb4e266d8a841bd95b9b0d072dfca8f18250ed8a650dff3240d1ef6d3493744cde5ffad5e2f5ad69cda426d72f00099970e8002397600c5da59ce87a7908958e26479ba57abf92a8583c90d3932341d25a24d9acf6341193d8d15f7ca3b9060d5619dfc42a666176b7dd5ad3907061abf1b6538200ea735b16e6614ba868bc5afbf35b9b7a1397eba5c0b1c3c71e4f69e8762f36fb9b23654644c63c34484d3687355a4da445db517cd73aa6380686b9f267d24f62e9cf93ae6bbaa842195adeaef031057e3f1d441d816852baf59a1dcaadc223510295942b35ed1de102aeb4a0e58c55eb45c8f341f146c726f8e53291ceb616be4568ae91c4f608ad509e5215abbebcf088eac3b03df3a0d820220f42583453b16cb6144146d5beca00c25ef4bc7cc86f461751f0550f67224188fc2aab6b8d1a0cfb65d41bc5893d06a977a5b7d5dfcc4c642224928df724097bf86ad8372f5a5966bedf543f0cf4972fad9632caed9d9ca4628760b7e43a5aea470d4e3fe91489b00754e32c495eff37e6aeb51be86bf942b86e9c92540110f4fea31654b6d8e123611b8bf6c5a931eed09507c52e5b03240d82b3e386b95f39728ad53c8cf617b193e37cc3a4585eb352634bbfe6998a9bff2fd6f0f72b1b5e05d276f48fdb9a1701153f4e8f90bfbca25387e60e4dcea73e071e1d97286388793d5bb612df6cd9abc1f721e60cb0866f3ea7e5bb3d1a02cca6df846723ed758ddfeda77e8cd5ec77c8a06a4e1840287f9833e0289dd6cc5140e0d8a5224c46ea2c98fd7794c964b58524a03d88e8d8095e8c52e6f32f9db4e3070bf7296e69687e67c08015f4cc622688635071684e10bd118c33a89d3aab5a27c870f7958715c244e3b999158263d691888938674e5c3f25cc93e8d18ea9650d6451616e09b865c3b45b7e1a63d980e6d0f4a1d24432c9192357d8ee3545881ecb3720041114cdb52fae5726ff294fd530902168e784bf9c5546dc79c3cd076454a2fa7a78bc369c4e5d09857a6c306ca49bfc90f48addacd50fceaaf459842ee9c7249b55cfb2912fe32440868b9879a83a1fa31d10cd85991461224ba81234ad972880a3ef9246fb77e6e1232b6f5e0349d25e19f55f0ea645c1d9ef70427b3b51613be90dd829eb256ac40ad7603d6c3313f3b594f1cd3e119ad736fc690370872363306fdf56d0f1000bbe7f6e784acbd86691cd4835bac825712bd32c68ff848e525429ad5545c6ce89288fc32a645dfe8f3f94d14473bc01a4ddd3acf440f72cca343dd8f074f66886695576a89a94bfa977bbde7fce36c0c93c231812cdc14fb8e0091bbbd9aa54e1a452ba45a084dc8cef0a68b01b9337507083a1e7bbb72a9aa692772687062ff031d04cee2ef5dcbe80f43d807deadeda54e6b0e8df07276d3f43c2052399ab3ed490a743e9cc8e18d2f22d66f91230531bfba305c1c726d721f2b106cabbb1356593ad439bac5cc4aecab1ae5b8c98265adfbbb241a72f0ecb1c324440fa2aea12abb0178a2552922edc74b6cf79e5f52a4323055df26cd4aef67944504939b31c5c2e034d8f0f5e204ca2be2ab9337508af48fd0eb438e1b29af61d85db0a04b8c947ef14558915bf1d3936a96d7a35b1b13b0526b4deb91a79f53ac3d8b5c6b4b87006511c981afb52ccf883d23ce3677d23815b125857ee06d3541b429e1e3972ab77ea4b0a264986d8fe14477eaba9b3112e4a172e75c3814a3de3e2f4298b9efa740d1466a330800f0ced78852611acde05b72540e3d70b91b18e51be915376ea986e9e7074b6cd57ddbe4dbfe39ffeaef6c1d72cc0aef20d1fcc52e37d9bb5e4eade8bceb6f5921f23573fdf4c6b0f6e52b9b72c078e9dec73f4beb393992a6bf8973c07694c1fe4c70b1b3a736e3d2425a5b722750742ecbe049680d62b5d8870ab0d44b2917ecedd682ca80f5549a432d6372b64e15cc55a313214464d30fbe4a78920d322f5542fed5dc15d91c289dbecc72b15007700904422351a1333f5fa9e9f429711d79b1f2041afab9ce2250aa83291d8f27e3a2e145b0d4b5a7893676ab8ecb742f998bb81095dd5a40b41bd822721c63cd04163a27bf6c599cab09584846971a6bd73380d9920a49e5d94773e8722c9552735207095ba75ebc91fdcc045f2ec40cffc4a6bbd1bd530abc67f4e07208e6b2c4e22b89e6cd6c4debcb7f513295e265922c0a8e1251e81e9449b2aa72c3fd56c93e9947ea9cdd1ad157e1464949c1eae03b9e4f244a5ceb2f15cc26724ed7888802dfdc5dc521706a5ee68671b8a7675b3e823a1bc50eb42ed6a448726c431f90788f6150c4d03c542848de8898397ea47ec3c71076360e9fdfcbbf394daf214de1a1e0cd81e0ff631e1513956bb9edd846a25e1ae73b2e4f09b58f7263c243a96efb5ca70ade40b9207bcc92ac57f37b6143718622ccb28c1d394206576a0e12b405c8dae7b03733833c46966b7cfca7238e9225a1d603da3b70d6323c83c8631c3f19dbf93a99a17a382dacb84195cfb550291660e62a94e799b772ebb22ba24392850b764c2cad7dae7b110914f9d13e4c35c237da521959939c724f0a6d8d61416f305a331147269d223fa711f823bd7f3985d06708b30ff2217247ff88beb84d84b50001d1c224088839e82839e06fe715105e4ed6d26d19aa72f85a6131ebaa26a07075fa437fbd8e12bb871208ac08764dc212c7e6348fa872c2818d6bfcdebbf3604748bb097fb57b036e3f82ca4ee06214f4fd96ee8f6272882afb5d77f0e3be96d109c0d2a85dd0251481e20a02749676ecc23d8ce81f05ebae38fd861e63d743e1c6361f85b7575287e3fd76fcc1ab38405ae2e228f122f51940899dc6e7a7f6ff7be8159684375b634abba293dedd38d91bc3c1e5a11f8806a0a5fb2cc57b1b618c2de1274e1b1eb545b884a8ade57fa5b409604033726f6eafb5b7f6f8b10e910c4f2c2e83a004f1f532c49352d9b5c8bba008b22c720b38f7ded82313808c3358841b3e34dcd8704bd85f2f5d571e02511a31f6776e44afea786d5a69fe2f0c3349a900b193c9b8ebdd90a477a4ccbb8ce661fbcb5cdfe52c892aa54bb14539ea2b4cfc99691511901a84499bba08a87f45fbf89632882a414feffe2aa77537afda530b8f640751f74ad6ee7a2cd32bc5eda486ed2b924ea02f7176ddee6e3079787a0e50e1ad63ba8cc644df8559a494ffc6a2087256cdca3e5042e1316cc51360c2b6f018caed7ff2b1f964ef74a273531ccea67267854c515fa32f6a0c239ae4daac2bf3ea929bc2fa8468ceff4e4b03354abf5b83c54359ee22acab82a5cd1af042a9ae5e420a4eca4849c7fd57dc9cb793f87223a828a747ca0f070d2cb85744016721cf8d18462709ee53188420135721537238110429fa585b566a9bd6f4946978cb0bb0e200e90188e9c3623c6c2e67fa6d8a07369a11abe6442b5636d2ac268b4d6d2626fbac876949629b6577d8941311520a1fb081a75404dacd69ab1b77ec8dd10d8f152e54df26c1aa20c85e3abd723b050dbc44c403ff032dd4754122e84874d2bb78645732f334f6fa011eaa7972002ce675a783b81798bc3dad9abae7a1e6bf15f7278065cd3aa89940f88ab861fecb9d7fbd337b3c0b96c55ef67dfde8417133c96efe49e346d87cd0069cfe6200de014308b6cc1c88efec5b6cec1ee47db8ea431bc2b5a330c8350dff50757213aa4f6f19e488cf62c7ba2da1dc332f1c74f33a158c1bc66c5a231e88a4b5724d07b418e78111f9173fa5cbe8d8598b46d5524a99fa491f4c8e2b7d4e631f72954c95fbfc91b605aaf448fcb8b7381be6d3ac2c41d6ac52ec5407dc45d1357296dd9066467cc0d8b0867b6a9ee4aa9cda0c90580ba87f03b7e649d7a2a6bc722233a1fc9dd2f3fd71283c6252d0e4869f69489ffbe7c0154e2af3d3ad24fd72a5e6a8cfddcd6a64722cb0e75ca0dee8bcaaafb9802d360a104d9f4677d05672514332ae122d4dd0d61b194f8542bec33e67f7d3cdfb1b1ac6b4e5dae435c672904671a5ed045a57d71e52e5bd757a4c882ebe46c0f31c2322408636b328c52b7d7764fa0e723cf86b7a188e08d686fb99a6973794238c17ce302fd0717a901303fbb7d0a1cc8ce0d4eca5fd10b6022eb0579796edf537754623a75bdb5888726cf4fc458f40c8987662fab09951099e79e0bb74c080d2e19ca3bdbc2fe63a7158d8c7bad3ecb64be5dec42a5e7b07e87bea7305e32906f668d7ad3d5efb2372654bd192fbed20b7f729c4b36dff10c786efc6c62f108dd5840be333b8b6cb7299e5bf77a8553df3d4cd523a712646f448b060bc756af8a8270eabdf87714a7219df0a146b41948a138d09ccaf1dcbf2a58ad1fc51e241775e523baf22051e38a7db5d36c0867806bdfa9bec3209687812d6dfdd042b823a9ee0b5dcf8b7ec3db7a5d52b672e264adc8a3b13402745984bdf78125fc007db0249f8c89c9f806ccf8b7a74161d980574b7338d0b959198942ca05788621f37588f675232cd4a5419ba0b7acde1f1bae41f97ed19bbf7e91dd47adb53a536cfac36c5f3b8a7fb62bd1f37a526a3c8b21fcc76128fdd622f3313dad43e68dd26a7d45a3198250523451955504fc8d9bceb4df1e5064ee105fa7be61eefa96cbf93f09c184308a272e67ffba308e53b6ef8b397a94e89ef3c5b665edbea6627be29d02f998f5288727312d2fad7707b94a2dff8e71529eaf590fcf4c8f17abf212a5593790b99f372190a9d51160fe35b73b2250077f01b6f7e413ea144cc20d21817552598dab97262eb1cea7a6bfd3ece427670fd66da76b092df1f3725dfc395e005d98ae3b6244c1f67d379a803b51ae086776e4623bcf691e31393967b6cf1c6c9524c61462cd4a6af6519546384834e35162ac1488da4c311776ba8aa721bef8308f50f4372d0014d625b2c53be2364025767d3d0540238f131ea802faec7c440dc0e56b6418fcb68986b60e135ce68a5adcb426173bb224b5ecea96d2bd99d83e154dc89724c669054cae9bf53cf5f0ded06700dbe87db0e606d795c36b7ba9a5e6f0b0672d68444afcc2d8b0a868ee10a458a65fc2f95d80716dbea00cf18b9508a033b551ba62ecbb8ae3c5614aa83209d496ff3e16e63a8a0f53df68a9088db06e49d72204639ebeb984a0361a54a7f2a0cdb0723f4daf4ec0618601d3cdfb8d280957251c3e51ebca7e66525676df3429beaaad82f6cca06a6a48faa0b25621ee6616d16ff96b2b90b51de32dc0a68fb13c725799f5fdf51f5076caa2fcb492378e272a886f6a60c622723e17e799a96415643e5008c97a25ca2fd68e351323e157b722befc196aebd469297058e0ee20303c5fa5c26ca8df40053864e047bf521c43240643e3f05970545449888c924d7d04dd218feb52a1bdf1aef395da4cecf24381f86c9c48f6a77da2510445d1c478b96fa651f5a307ec9029ecb9e965971ac27a5e54a2a2ec42d1e858a9ef3cc4088b3a57d172bbd5c1ef26ebd45c02a228772d3db52552981c5b25a7dc336ee6b920d3b08d2b98a3b377e44bc8a08dd31c04a29f643acdf8bf698273410c3357ab76f7532a52d2452ec1ead5a50fbf30ec02d9f9bce95d9695c73351d299e0b709e4c9574e42261182ab91e1746e876e61072b4c8f0da48521fb037bdf05b3692b0940194aba49407013147e96f6bee0ae472647b9751900cdd33d31ac847309b1b12a9e680e8f435b50b09dc11020d44df35c21dad1018a682e9b1e7be74e17ce6d416b70e7cf5b78a7136f0802f107df94aed786ea7844b0c2fb51ac0863e8cf285264e0c620cc30273545e40d736cb8072cd354634795393def28d9128e48e49a3911763065e5fcffc45c44976d36d9772b24f5f80b2ae4da9caa0cc31daaeb16c7d10344829e548cc679a91374f7e8172e8c88ef7c84aef3e4915fe215d4f6c48c7296a744e0f5f43681f9432281c5272bc4d9afa7c0b9e0e8220daf1ef8058215902ea75991606231518b87c4a9602722e777bccd4b21a6b3468bb9c884af1cf8aae3dd9c22c6aeb8fd89c36efedc524aa74e13f4356ccecb4cf0e7a4936d20ecdee4bd2409d4790bd7051e91036f47215b68ef3ab292d60b0674346d0df94633e7d127448a9c4f1cae96d4f10b7e77261e6ffc9695b92b375915e64eac6bbb29990ecfac62d4454b57ea423c719db48d8ea4ebf4f83fb8da1f178d0b90a8ef5698a1fc9a5a1815946de5ad40f1e811e9cc5386cc22498ef90c8834f5903df92d95c3a754620493f027b04fc5741334b0952c92284ee6cd82dcfc00f60cba3e97e9c1c6262892eb7a5c831d68ff60672ad5bad71b5c205c7999712a243bbff508d7735c12fcb3e3e92272b758098a114f40cf71bee108b863a6f142f9de5e80ca22037e0bc1491b724127fea62937272ea1384db7ca1aefa5011223319ad8bc740ccd6ae4fc515ea0b4d5ecfe8ac012f14daabe06bc5b49653c8e8faba360cf92a665bbd66f93d0568032538858c99250e0886486b206ca332d4843b9522580302cc4f3471b6ad7c75915f0eb8caef72d3bfa602530c7a83d466da803ed7785a201084a34e034c175854c8c0e9cbef722f1386f565d9f10ba8ebe2288ffddf5a57650b11d815a27ca5500557b3ae26727f257f77d25e873d0b4f150b00a45f87f8182055bb85adb1a169932be7992254a2f7b928b0268e2a9888d2d82b0cb5224a5fa0b5136730d4e93e09bcbe7faf72c57a485179a7aaef32f1ca2c908f066de95204bb1fa4fcdb29ec54e72a893f20c1879c4d20babf0c715b5ed6f230f681aca583f386f0be7ffd228204eaffd36dd330ae1c5284ac59c654e7df4ac2601575c06ebcb624e2ec75376ac2f322ea5a71ff420045202dd3653025d0266d74aff4ef084ebcf85315abe5d888a8dc1a7264577d683d4360aedbf6e4257af22a9b7e9d3257084f212d5ac77b01569a60729ed1502e59a609d7f1d575c50eb0c43c583ff8fb75743d9ba03b9ba4d9777a726f19aa26c63e612ea7a992fdb9c0b16ad0ab06b30c2d915e244275616d185560fc1d12e5c2492a57db6fa2ef0ad263f7a8dec200deda849633cb9209196ae95bccb47a3960a7351b38812f02a4ab8ba92d55bfb8c9cd91c4e117eda926459902effa3867d8440039570f3fd9e3d6e26e8b1d04c0308cedec19ecbbd4dbec947256c662db1bed7dee5cffc0c3f9c679f38d9abfe3a36412a3b5d93f461bef42305430c3eab1d143dd88e3b18ddc796d551d483366225eab9358a87de09b2d6537a906890da9cc0e70657f4d36f27d3757074d41306aa33c41b1bc717bad866772ca08567eb96843432bbba58d0ce396081f12fe6f0384f6fa73bdd48d53b47c72c4ced18c8237e76cb2de70079c7b73cfe557553b831989377185f88e65144d170ea9b656aa1f175e343b8a9ca9fabd916978be088848203236ee7f9eb10186720f5572993bf10c606284584a02d2f4f40a5c74929bc1887dbd6228ecbd3bee5cdb7dd1046265538821da71dcdb21335c78f918e2c25307e93bad486bbba52501798a8d8891f966f837a42688d5234d39c3e9db9a02b39613178b6f0ff8e023074e6ce410fda8c48d95c51085c8f1d6858ed9e27e1e387c1d6b026ac57b578172d4a4e5277ac77f4efdc3a5302083173d8300b03f1e61ce2e219fc83a53350f722943b1ab835909ac3ae206ce4c717e496c661bebec1d7e62eae303fe7166fe72acd130d8e4fbfa189a7191797806412a11e7f6f4ad4dba9e3722ba64b349337234178ff79f33ba45d02a7aa3570ee53d8442bfca9eb447488664cedc408c34727c961f77cbb00b21cce5566c5863706276176999f7fb176b0a856dc308f5ba468dae15ef5d6e83cda44641fc3e7830c9485ddd30452d3e53980047b80616e8372ddcf402a377ab95fc9acd636c1feeabaa080e196635f70df6581ce83f9ee2641c773874672a93e746104a89896068785dfdc33e7f06304386d3f75b7a92ab72d29f1382d5bf86fd5e6426cc0e68de27ace936881555e86791038ae788ee511e5a8d812a682944323c7a1b065aa637aee0fee735339be7a508307325881c9c72eb308ba81c2b9f165aca269cc401970894a4d35e93d38cbbba77a2730f7d862f26dd3e7b1f7a38405bb0edd33c9290fefb13dc063be67d3b93cb97ae6761fd7272d8ab0bcb49122240029c573836d01293ac7ca415a5ae09bc94336ddc040d72b241934c3c4f912a7edde4b2d9591bb3fdc41a52efb44d50e4797a151f5d030769bf9ba664dd97734481de35db3868d61272877bc5538acd3b7505936e24f872babc6551583b2e1eb806c10f118c46510554648bb20860fc055f25b9007a272b26a34b501821777859b930be90ed25dcec1d5f893d55afc3617dc2482530f472c1c0bc7b9bd9659cee991e6c4c89cef499c04232d9faf36502166165de09aa7265b7fae8301baf1508a76e753c8d918aefebaf1bc23ef468944cbc0b129cb44e3e8694d656ba8a6f13af8ad70750510b41abc0affe5a31f7fb457922363bdd727105855b3548ae0fda58658df66917cb4a6a9122f6cab3774b7cf29066bb607234ad3fb1c3f24899004f15ea5b9e5ab2e74221d1e9adc80bbae5fbe6f9a3c07271394604544e766c2cbd8277f641d854d79e6d5f9306536686f96a409067bd72b312b8e7c5adfd02f1a1def77147b5faf22e5a45593cf75889f824f03b55a84be79b04f24f117814221280b9a666b488f4284e5d60c1d00c18de666d4e227f52dc4a4edce9732a25674c2502b30bac3559d6612a945edc0518dd37e20ef63a72d8547ddbe586cadbac0214b865ecc4095eacccdc58b0dc454873838ef107c9723a35dc8509e477569e2b072a03c06d7ae573c2532fda3a4ebe7746a50d759c69592c82f1d63c5f296c1e09b2f066e7ba0d15c30a9245d2da8a3919e3551741723ad432f7ed91c0f49b9236eceaaaa846b1324c6d0e80444f7e7095682d2da972a7744d261829891f0ba5e2cfa6804b75398d40c5378c753b8ce9d5dfcb0d6004e86d414f9697c2991056fa36ae44827957ad54332b4f5ceb9bccc4ec59b511145b86a23ee129bcfa16210e0bcf10445c107892ad77d74958d478131c5fdb6821a05ac7b21ad54408a879d584dd7a0a66a3829598331ac37be3716fa3f9609072d8bcaba3067b6b43cfcd8bd99de36afd60aa43bf9cd2b2abddcdf312aa8ab87210f981e3e57a1f48956076adc0dafaef4a5dff043764f42d6b39fc9c7d1a6272f2ec95691b1596f50becb2d0115bca24e84a2af923ed436345ce783f4ef7f37261fd3807ca50189aef37ab2a744cd5e41609ce52bf38bbe872f44d9e09bdb915e491e2e866acc7ef3c2905f977ab96f3eb5f974ae4ded1734ee09fd762118c025a1c03d88b37d9355592fa4ad5fb1fe5ed2b299666f33885514a26f275a6a701f59e7a37d99859b80ccd0727dfde8e4292557f107d668cb189abe1a184c0df2e8c08cb3587a8327e0945e2c5707a16f66112f98db81a6bc65873f913d3eea772a986d7ea1eb5be7e342e8bf45ed858267d40dec016e4bb7d26d1199d4d42f972c317b566158ee54af70f56989d9b41b6e966baf9a68f9a781c766f43876e98728be8c846c5260fc90268b162574be536a8404fdc92737824c2868da6a3444272e89a0831dbcb7a7b2126bc19ab256b576e7b800f56fdefbb025b8ce9e35eff5c11d972a4bb65460dc8eb16f089a58c23f2382f19c585daa19da0779f24ab5272a9f51ba7e0e85d36028bea35be2dfe6af42d6d746e5476ce946b8a2cab65a952751bf5e53d3ca08f5e3787c958024e0c1f714e40c259411fd8e79360f78f7c4b2ccf209389c96f60101ecf18ed4c5ede9e2c3f0dd94aefd278047bf05a10003623098b0ccace581faa90e9d1978728d589147436dd40fd76b05bb93b30ea1c1c79c2c781283811ee88810e2705f44058e04728ba9a1f4295aae48bf073dafa531e6ef97978538a8e18214c53e5c78e4e4c26dfb6385fd95414a01f2da9f0e47246cd73f4267e39e155f932b2108672beab1819c4ff41106d7cdbb2d745129356d507732bdd9f950178574bcd5e219900037598f052b7cb2d23ccc76761f935729a545e76a7ff71abb654e2de4838ed35a37e0d4950b17a6fef721e64474dac1adfc29778f9840403ad6c01102386d3a3fde4b1f6513bbafd6e516cf6661f7827bddf31c54f5337b6de30d24759e04c4290038a70e64ba0a5c9ce8ef7e2ac3772d772791cd4ed47d5dd2d667def5a951b966ddb0162cc58fbb103215053e2e0723881f394dbd54d9c546bf96c788357804b6c0006ded56d762b9f585060bd89727277a2a221fe8f48ed94073c24b2a4410cb39108c2306d419a153c42db5f2f7059a2a533442c9d6c73d5a6bf2e5b3f8b9473098693ca977da84bf797880e0a7226e8bcd5239076f9acb58bb4733e8a0d576148fa6964b8e57d491f0792521c3da68cc6985f1e80682296b6286da54845bddbe56896cbf1627c80ac90dc236a10f071c5e145b0454ee77a8d4ba08c362250de57fce9dd12d3202615e050202c04ad2a1bd95f94e7a79e3b93b529574934423ce1e6f561bc083dd2025b4bd51933d3bc14e2c3ecd31f2c34ede0cac8a181ee3217eb12505dcb73dc4107d5460a568b8ba9b71cd241f1e12057fd97b25fda5e19ea0b3f6b0525cccfaf207f7b0372cbe88c20c80cbc17a43e237ab91ed4a25197daa13aa457c40c1973bef11c3372e186779c99c585767249d5dcaf15d9339a23b7326c8f8e7a7a5b109fd8e49f5061ae511320fdc59dc1e6eedaf3d873b459d38abbb42b307c7d9b2dec5335ef729e09ad45988392cae7436f7cf8926a60e24346d60b7dd0212ee85c0f6d66610e123f0c455eefa6901c7d5077e58c459e9975349abf3ef8d3861aaf6187e69e729c5759253e0fd0f952bd883092d2f3ec80ed6935613a47128bda73a813424e19ebec5da422070bebd3ca945ae99398d50f76f548808b03faea9ad8771b5cdf3226f8a8b8f59361734976b3488012f8ef16d91b287f357d11c48931f4f8902f055607d888234cff11f1c3b85be6a35fbf059613e110900f938089b77e29713a7219a782ac3195d164a12acce59fba8a3e106d4271f94febfee9a718995b4a1a72d8674d26350a9a089729d29e391706c19af98fd8fca249cbd0b93d1faf2060721d47a123a4f6fb3a2abb24ec5b41be93132c2740494c46e3a306d73919e98f723d870abdbbea5a15848e11ec304b7ff81b5549a320f8c0c4fbbb4731bdd6d872d23927df6e0a991e31eb941d43cf7e5b9f66008cd075a804ea6ee82fe3ee2a226e24a943b6aa322a449aeb1077d681a0a25fa3e431f0a83054a27b727d87385117b7ae127587199f2cfd85830d2b1a2461eb569f4ffb131119c41a90631fb9721c6a8457110068dd946fb1c5e15d257312798bd1fa630b0dfdb4f2217614ba220de7ce9bddc125d0a7c14e737ec5e7dc7d26fa44bd3dea90d368633e9bee850f6536b69ba0fdfb4cdd5e7853f4fa03cecb3447638c010badda97b46d30fa8c3cdf3da8c7f6b95d92305a8a6252dc6cf718bbd226fb5b8de4e4c0150f6ba3db72115341888b91937b8c240f1792eef8b92b6e7744c6579e342f33c9fef7ba3d4a62bee2cf725e59969e04e0a447083256e12aa005f435260aa5312a22ef743f5d98152b9b88b2eb42b4232d70e2e1d66a919f9a406507f598bf16a1067bcf5b728740ad07713678e5859881a8d905770b76b627f2a4b0150f1e3f4461f7e8f872ca44481e9df9173ab2e5121c5197167f76a28571e43683766c0afc0270cb92723301c91f7a9bb7a5f057db4eb9cb080262ef51ae9a46324b44cd62f31b05a972adb06e170a9d85a046d13a971a696e00f0ec09e950ec878cf8fee9bf7a2e2172aff7836a2b16d1048d5ac7560c6391da265a8c3abc52324b782a2a2ddf221672439a06a3291ccde6ad8d096f78bf68e9dbdda6a951f3db57119814e7e1f5ae724581a8abfec2e2a64f73e371e6ab28e2e42aa5f876498978adb16f3269508b1c667786b2b9af8f052b2f71e14f5a81ae97769a7d6ac7cfdd4843d75950237a7274b05b7c2f182f98d2d3c5f3488175893aae5d135b20f5e5be4dcae240ebb27240a1537f087b1d3257113ad90bf9e57f6add43cfd6ede2532cca30caa990b41612b7c7535c86d00b21e81e0f97dc7a814953573e48ec6a22635cf7e69ef3b772613929d414110b5fd0e198f1540b2265f72d298a89f61d812c1a32879a2acb720e17db2ed7bf2241328f9ba01f0873a48f4cf50ab3744174c53962dc0cfbe772ebf0eab6d1dfb77d5b69af13fcf5f06dc6b9fc876f03c31f252402966dad7f72ff9af3ca547e3e33bc55fa61f25932eb957e9412e73fac2f074b8fb986959935e2230a167ac534e6975959fb402491659a37e2d95584482242b351ed4408be2c3344408646ffc2bdc4615ebaa342630907be393e9c493540240c8622cb6c9c7213225892276a6f4d827e01843e8b1f686f420b511b809fa9c0f3341ad1f1f944153af9650cda21ab76a00336b12f18b28e146dad6e3cace2b48f7266a29bef72ab0b7341fff8bc4a48f4006d2e46298c8677d5fd1f1a1df92cb94bce74d60a5c5a6e3d3c5cb25719d1567fe94d56234785c96a003ba55e314029495f0dc45d72ac079d0a47478b28dbfb08baa5006035a6076db7f8a729c913eefa23bba82f727a247643aae0f5f147c4e30209249775cb3753e6c86cd0a65c11fefcc6d99e24b395f798c64046b6233925be12adf91f032490a9ec58f3fd36d61fe4947b3455153991b9d21f5b62cee6dd99daf0a5fcf19e43f082152c1e2d14bab8178cee0189cb3ca6179da80660075f24c5b8c59047af291f683cc45ec30e37022639af728eb19ff253721a4072e88c7346f629b4f397f657f7c20d6122822c3c95457772cc394720baf4934841b493ca82c3c09fb924920098ab4d6e84556563e422dd72069bd485c2fc3f121f27f651abaa1d0e3a5a9526f3d26247e7096dd62d060a720d8d94abe827cd3367fbe44c8d0c0d37a73a34914693c9cd4a7d7ba24ee14c7258e2216111362469588c80a50ff03889ce927ffa35ee6ee93a836e4aa5a51772f3eaed57bf10c75bd48fc73e0f84175b294e2b5ec418739607caaf44fb87f9274fca0f7d2a16698b0676692c2d0d8103ebbdc61465dcee09ea26a0b258f8807225a0a98742b2847a9d790d4a47ee95211b7f1b098adb6437ba82c66abdbe7855976b7d744dac7b7b7de8356dbb52386bb0684a170ae54e64712ba2a0830da736fe32d9a6851b1b1d16045211fd110850e6fdc3f77785be0ae2927d321baf7b7203cb31520761d04375cefcb24027e571e565a926c38acb6f1a21604d25a1e06b4edbc2640d6b7aad6729925205e3d618f52bcf00768a6d6aa88b0d6db2a4cb3d7fc300caba008a4e6bdba15fc4dc96e4bfa4e38c93b624620eda70d7f6020c72e636f67965c0664e28ccf781f5773a00e4b28a92149e447ae5d2249f0cfaf559a5f9f6797b385369576c6067d1bf91b8384bc68bf92ee6f2f2b8a840236b537281cb28ade5b54b09c799454965f85c2187d56147795542c6a672bf188b8fbe722e43300bc283e6f5bd291e55b4d7088c555a7c472fef221573b7939c802a7d728a24cb5e75e6e82b18242d74013a029b9ea617ea461f16b664ffa74e9919f34fe8e11e4016f4d7b2e45ba2ab41ec9e63b02999da1c3bd0ec54ec6707d3754c7247f6579ae2d452a08cc05fc46c87df488f082f7f1588222f12373af3516d7072850eb0680dceb5faafb5d095a676e6316f44b2bb5ce5d56df7508050b794197213e74cc5b167ead8ee1480bc792a9ba817e8cc64cc66dbdf443a2321a1ae6d72666eb878172c3c0ef182f7cf5bdf67ee72a5b19f7e9b356f8af430c11102025a4fb7ad9ee1fa11b6a01ca006acf615c90e1b76c082b8e4c48d5af434cf85e01f2ed7d1fefd8906e13e800054676badd5bb95550567202eedf8783c074b052a189e3a7f5505a7f915ab88ec40321e6cc082c167a07dd14ca29bab0611bf201b118e35b4dcf7f35b9df3e9be3f7c82659314658a8bc202e0453e577dec823f3372537170b5ceb353a2871291f7b8c6ff1835afb79e59ae47328a5d27f5ea9f5106bfd9f2420f383bac7eef8839d3ca2cde083980921caed5075d00ebaf14ed6072b4b7a695f70bb9a8d89a49ec63890d4ee6caadc38bed1db5d1776d7a073aae37d78745fe13593f87cd9875f5d114858cdbf04e2b80bccc54be3324dbb74e5739ab73ff33c07b5ad14fa3bc5a6b6ecb27a5504542e30680fd8451cfae4a84b47270d310449e8b44570ba6a77e7cfdf4a6d58011503902bfec38a240924350ef1e886a5f49721c69551acd44a875fb546bc35ee177efa8744f8b943937fd0b6e546e57891e7ef754f66c4f4dbf3e683565c25e4242422faee23ec8ca3550b90c72ba9cd3b5ff14299d8146e3431a336c95ebfdf0fd57172c67ff25f96c8448934811521f94def51c9e384b35e326f3c10e0fbddb4e4d2e76e3dac8e5f1a997fb18beacc72fcd4a0ba4e10e55a299e33d20005983791dab795161c0e9f816c7087237b2020301c9292f346a1b2d76dbf37add0ba1bb60f54ced9d4245f6686f20713bb1c4f311d5f397dea5c53dc1de5667f4b25cf1474fea067e42473367a472691308b9370bee19ef68f8722978584476bda15affa35c130f6659cf46efc143724d142ee059bf60ab5936cfb15572544153518f8ebf69aadd375a31ad6a6d114493f2103b8c7d45f346b8586f1bac2249fa660406bfafc878a3b50bb013dfc11261bc33ea5c9b72b4a422a1f6281ae802337ece2c262a03a5c9f34119f9048d728883817c45c5be3e1a1c5df2e93e75d17c4ffdbbf276e1bad391fcd3a0f63c727876be50c87242469bd01ea599028816c77339e2d7f8ae67c17718fdaae2107218243e6443859518bd6e55e7ca9a61ecaf67c907603dfd111de1b56ebcf79d7238fc191b772cb26f5eec73053880a1efda7a6d72b58e48d30b2277fdcbcc7d7259c5c66385e94da2180edcb1f90836feece3c8b4ffe33c36847679a2bdc9d26f52a0c4e4f5f09bbb2ea22488012e89326f127db45cd734213a88626f9cd49072273f7b164b23f59453562553311154f3cbc01d3674f984ede7e89fb6e45582723c29e6bc2d79885c43eab97308f3791bd0e2cdf078e6f2082daf4c55e1e56431f80715f18dcc952f310ceeb6d1081def9546b2211171a237ec6fc82f87d2aa33af9229d0ea46e4be83ca5732ee688527439037644aeaa7a0d27c119ea4736572904e1ed850963e045d7099525fca5edb67a820848bd1d527a47341f88586ec61db5a8d33ff0374934734106effa306eee71c1dca57e1e1ed95e19bf593476b72fa8ef855715c1b2adc07cfa0307d6bc31919178e63db5ba0ca4583042b496772b8c23be5ceeb87a6e53d3778b39b20b3c2dca3006e7831db2a85b408d6fe72729a7d3774d8d735ff011a770c2522ead23f7084e60de77b6113a203e2755b281181c028b2b0d01f85018f24616e479a80ed146c22b7011251969b610ef138b5397ac4b92ac56d192fe61bf6384ec5d36c10a67b281c5724ff696f5a4f99564372602707e1fd24df2d8f55fcca4c28567021e0e595bb3454da7d8d882c7dffa93ba86ba88f36823418d7e1c3cbbc2c57cb3ce95542a6986b7472ab74749828156a0e8288cb43da42d3d6a62f722cb65076fa6257d9b9a6c32e43a1e40a3a55127267a1245b2d4e2ea3ef6bf85581a70279a06bcc82f6ef06103187550e088a1b4b072a529ba8a7071c9d4885938de22f9d15a1e0dfa90af6ceb1518a3a9a72ee7275728fdfd3a0bb8bfa44a1a76814a281b506ec1403d6c416044ccd31d9646f7211a33503d00d54885938c5d8ac0118f8b52ab5b67d1877e4e9b0fbd986e4c77214608d6896f8e86eb72d095f0f477e27bc065041b9607c8e68a8df94dc18e872fd4ac6ef1340fb7e8a6f829f46ec978fab9b0f3365d2dfc87a2fb09e4d4adf2d97d270a69ae617baf67926614bf4cd92346dd6c7f5f8cac19f0121181865324d50d97e2ff1c896f79536f6a089cda6ef1977bf29041e2cf232de7919352f3572f09f7fc08c0ea4df136b90c1a1d90f25a742756d06012787b8375058d341423da05f23410a9c6c01e1d7ef07ebf3c243f797382dc6a14ca5b1394b96bf6e9214c941e6e77d45430a948de825c70c01793dd01421c5573451cffd44d2abaad2149f7333f33967b42fd19ebd95aa31c108a6992ae98c5fef29eaf83a3fe935997240a59137084608adc548a4287521eac12400b64edcd0df41ca51a33c1f7562723926c8c7d9648f448057e6459dacb31c6606f4fbb2268137eb73ea4e778c6172f743981fd0dc2bab7b3880dae97d5d174c3251d071ca7369f978610fac1ccf0b343d7090a76421ced5308b755f99c303909dd91b382ba33d0e5b1625377316170ad2c1e1872c3b555b745fa2d959d62ed120456ebee7b444b6a2ad0cc6b71072abd8f88f41e340b7487e71aec5508871ad4b75cd3b91149504c94e496251ff316ccd0c42a8798ee3393630d37d03139c91f1ab8d38af98451f2bc8efcd0b63727d30b9c11660154906fe67d8789edc06a49f5877fc6d9a09d5d5365055f7a30f3f0a63912702d879a3d78b026e358da1a8fd6c403b7e052831b571173ce0d37231472d642656a0efbf9236fa29434aa9dcd88a842208d58748378e34adbc0172e4c75126b5ae056ebb1ea3b9d3a27f2effb1bbb081406e4b5977bdb12e1e1551aba5b153861b213d731e810a14beca79ca5fde8f1e26f10afcc25de2c53ddc72d3410e14ecf5e767dcb38b0e6f2d8455f8ede0bf0dc5865294a38043931f2213927196cbd67bc311ae2c84f8d327851860bf8545d6b3987bf588ad69262b8c4f527bf6b2b032f062f712bdc7dbc04dec5e917ef530fef21214aadfdb7cf2b1728149083f08e8b45a79c76be54e58d45ba640fd7ca5dd5b3f714f069132470062627fbe8b53e7456079bd2da668f2c87bc121b1bcb735034a3453d41ede4ffd4624726d76fc7407130e39300886a0e378dcbdae9e25da4b6a6f74dfddad04a3061781a1b844f9c2d7dc2c0a5080f10122a9d9add72bb550788d89aa99e13073544dc886a6d5a62913e9ce2e66a7e5be8426ee5486a5f861fd95bba8d18493116fd99a4922b2030be76a0e4b291264984179f8c3a911a680c3e2a17d49538c727235e364ba1ed3e3abdc47c4fe428ce28c9ef39ce6a8a4672f1df2435b9c3e4072c78152465aae9ef98c1032688d59b6bee66053a267818e000098c5e417617572b01eb057d452dabef06f308240edc06f7698be45cb3422b50fca4063e4136972642cb3460b8c2d15f92e1c8b19ec3b30d68a566057240e649e0d8edc5118d472ea08283d46e54294c05dee4ee7f15d3181de8c0a25413e28323f042c4a9f6617cf389c34449007efb66cd787a6ef717afdd76261fcd148b22ebbae85d3ce890f86cf3d567f74a73f3c9e177ef6fd2b95b6f42cadba58c5855fa22463d3ae7872c4551967ccad57d17bdee354924d09185fc4129378435470e42b1d795b70d137d834b2a4e8e27c6e466a09112cb393c8e4406f0e3c11adc275e291f16a166272f1caeeb62e1dbe5f9032d508b1d87b049080021a9971da4fe5c6b7a112c47b60ba6c20ccb51fe7d74232dca664c9924f50b5b2e20c5df1e5394ea851379f2172974efdcc081c5fc45fc566eb83320cb73a5344250578e8dfb6fd6a7598282a0dea6711992ca6a69cfe767c5776512808b02e3db7169cdd9f491f92789ff38572d27a28828ff2c2b87f947c1de2141adee428e6b740e3b391f0b7d9b481db4d7277ab5928693cd9390d2425fec7be76a762b47fcf0445b3808767037c958a05721f39c38351fd10c2489a6e6f38594c846a2a6e2aad4f8a74fcf8119aeefe5572d124a400d2875f3a9187eafdcd2dd7b42845aad0110fe0e8bd8a56b4cf408272e72335dbbcc94a4cd778fc3ea9cc18becd36470b9b701521cd17e4742e6c2a72a263fcd1af80dbc0302850974c553942e93580f5c992cbea9490edb4384e247208cf05cfe2c19bd551e663c6d8f42da8a17c2213a210f2b7d3316e7add6d7768474cc765d8eb25d5348bbe248c7918b417bb12c55c337fa73c1a74f321da0072ec9b75b302444c87172e7891d5cf924e02f4f10b8c0d66e30cbb7547157114348bc3bd1289d97eedc9a59db94f167a3a3ded95333e05ac15641a4e75142e8c4f88f0c1e06c37825545317086d3902cea6b71b3f74bf6932893ad6ebff7f45f72c235edf7d45a6dddb4d0e1a65ffac3015102fc5865618acd77e370c6e5233272df5d03490f59035447e60f3cae748e4f6b8df55a7eb107821c957982f525866272e851c8561a34482838939a94e56f7c2c75fe047170ed355801b36ac1af9772de5d6f835a2deb795643952fc2717982755da3b7f001bd20b4c174f3c129690f49d040bc46bfb5859fe6f658d17448ac3f0f0c8db04d38c6877ee6e4961ba2723c9be0293bac9588a84da9a1527ca3f4d3e70bd77b84073f01421c814cedd872f7b7f1ecaa2584da676a226c8d31f592f04a34da3df66bb631692f9e1fea1f72272eaa2a1c536c16aaf7228b218a0ed2bacfd1c1a95ed9d12d42629d460b2f7272b27eaf55c1e5c6b09f7e4b4423f0b07faf6acdb6d1a9006ced063318674a720afa14a90c1203879039979a0c8f45623cd2f8d1a7dbd90526b6e4d2dcb25f720820ab2ca95e470206e5420b28fe1a398491fd260afa04246055fa21edb86366f291630a373fcdb7e4b785785e74ab8c410d9574a9cf94db4b0021bdb536db1c46ff57189fb1005c92e585bab81a450f052b44b5dd743a017dee128c2eeb33623e507f97e82f47979a6096fc0d1fe6bd7f2bac3377a9ccfa49b326c6e4a41c6081f8e942e1e17fefceab4fb2c10f90fb7facdfa13ca51d1b31334a07d0230d357030d40a86a11da1daf4e64d0a941e2e91229c6ff86e2bb3d699f8a4e9ddc05077909757c19a6d3e9b23260d01b97e1fa92edaad8f54d6d399302f44f57cdf1c16def06d8e8cc85848bbe12cb0095d4652a32b12759d68079d1ad831a3b10e0ed8691d5da5d057f8d3dec21a92ecf56f6ec781aab337702d1fbb8b09cb174c6c3b49d4b9fa376c97f7748957ed87c583b5fb4eceaf9159e3c978f0a3b3c0a472c3222bfc041829cdb5f6e3ba17f147a5fe8780e6d0395dc781f39b5d3c79ad379c80a06efe5e9723505f5ba2e2132ea28ee5291aa593a2890f0fe2b9b4e3507203fef016127f977a101ca4e5582027a0fe3a3bfd0fee5f73e08e8ffe8b131f1be675c4da202add76e826fc3feb05835fa6c183b56c347916e09769394fd3781fd2f5da01a265886602aa7c09cc123e3f5f59651e0d89d4a53dcb99b1d36a0562011fe75491eba73a94684a26bc23f8c72d356357a319742001b970861cc392179f3c51f430f3417a412becb3ccf3c7621754210697db2510d5e017250ac32b4c9924f722684648942d2cc80290a00957b2eb0e797201d1a0deb725581f417b72348dfe819709562eb108d7ee4777884a3a052df5b11aca86afb4b820bf923572db2f6886fc3fc009915a612465dd644fbd8c8ad592aa8b63d0e76f06ae48b472ec9746d1555a62f398acf381966b8303a95408f65a8c75aee9a97c640bbaed60b91265f4d6abf605057834b105ddcc86d33938c6126c829c0dd5940e7af2047295180d5f27ba2d8b8f9096b6d394c050008b7331442bcccf4f943fc68fe5c672b0ce3c05453db8d49582aacb57ae17512f6791a3bd1190b2712044c772f0a7727c38daf2fa4d0976d1bcf750ff52edb485fd2711459c2b68daa9c94cf1f66672e446da370ab8c5630d7ee87b350d79654020090cee12e1c0eeca298d449a46724e89e4f88962bfd3a0aca24d641081b2556eac8cc7f3d1d174eae231535f42723d73044da114cbf5c098cfaaad989f0ca78180dfca41103ba87e66e39b98805dd15b7314e0a6dcbd8b2622f4c76dfcbd3174cb85bb6c179970e09219a0c2e026ba2bff4ebde8fdd08ab5e48f75928e93b4d56bb44792e2ef42d5f679446aaa4317058f4382cd9fef689b285aad69334ae6525ba6464a5138948ec096109893727d90db3b9db416ffefcf0caacb0096a476c7b547a1bad771736c0ed804bccc72eecaf9175bd100fa5bb4d5fe9139d171ab598d07814d3ccd151331a8f8abeb0898460d90e04ab1be595c28d9f66723669cf6bc72c0bb601279e4b4a5e2c810725ff45c8496c87dde94028e12fbbc0b86f8e50480ef212dea7e502c67d51de972dfad665f84ef5d6ed0c5e1d4f5b399f908d6c1a28c83a4b51f66199819e1ed48d5d2131a3b82d82c9899ca26c2fe02de0153ecb44180e534e22bf5e2a829c22acbb16563badf826a6a1404ee678999ce497ea8bce4cf1006c5a9200066a0467289af2ffa036d19b1b13b5fec26e4ee3b05ed6506c1fd22399b5287c789ad1451b89bce2beb22cbc6d9d4b5db18748e2f1054e5a4266908dbe87bdc7fc3a4957252bd6a96234bc02b6a9f3a7648e2beeb3499313b8660704d9e66d860b76a0d72c32a0269e8ef5b72ca7eb783907921e506f5ba565e4a0d437684db882413c17225806434f51e2fae2493394cd10b6f5d942db53e05c9ce2a9c0dadb7a0b045726d66dbde784e1c8d315f4f4b2a56e90e119afae293514edc21b18376ef1ca07284963658d7469969ad7f542179688e5828d7edcf7e1ea33d941d953ce8af75293f087882fdd04b03c91b105776a9c7e8102e9a9c62e6ec85a1bc5ecab5ad4b7229bb44dd1bc5fadc623fcc6428b077f344f2a63d0909805c611bc4c4335b0e72b1524aa5ded775806c6c258cacc11cd7da19ce4b0441a24f2f791c75bb0ffe5913ef7dc570c509fe48cf825430eac91612aee3725f0361cd2a5b74dc0ca1c13e38a680a0cf9717abd45cb67b9b64cf9494d50d45e413d8ee071b9781af632372069d358170ed3c66915036a8c1b859e705f09918c219cc06353d34dc9a5dac720ccb847967877458571a80ef9587028f3c36001328ca9dd66825ae57ab58023a387b282ca740d4ae6956c5ba3f79759bec85568fea64bb22c9f50048bc47447234d351d32ee27a536f32739ebad605b827a500c4637b2284c9ff582c6ad58072b0c2ddfecde08db5355f050939512b7052e12b996b965587609e58278e511b72931aeca890523890336f0f726109f8fb221a590976daac0eab4bbb3a7c8e2572df466cb1d314d8456e24da492c2b4fe3b36e089543c37cd993b01666ec370a726028a2a9a6815a080aa44a2534a5edac94431591a61862d0c51dbb03658e097213db904c0b42d299fc10ea803ed34a117e1c9ebae4e2fac677df697ae326a47294be473733d3e3e21687680d3e19165eba39fb9d51b5a95946fc6dc27c770f4582de91d4db395c75f58085282e1f3e621dd99ff26d580c07573440d0c4995772ae47d17bff09c617009d2dd958749fb5231e07317233ed93e92c82655d94fd72f1636c18b10056ce1427927205bde0ca7c2448f26cc94fdfa31c51d01f65ec72e028f75f99321f0ece0ec7e27ac81b8c1cdb8fa08e057e4322687673983b583982d2a3a041d7da642603da71230060ebcd8821146279f585e74d89a74ea9bf19505a06f4daf7895d5b22303fd37d1757afc5a87b8789f314688e8d3f53539972c04bb7f498c9243b9b4cc64538df383de618b91e7072128e167cd44be0d5594a5a28d96b2e32d50c1de84b7917a1631ba64aa52f7e0ab9f1cf39301d186e847229d7e82395ee0101e7b3898d2751bafced70b59d434c90ddb2db251d9a06e7725be85214c99bd633104ec197bc2756dab50ac9c36f711f819124b8d5987e407210ab03da8d9feb819d816fa336fe7f376dd09ce2f87cee5e798615467f48ad7162e0a4e655a438b2bdf7451f6e6737388918064628cafa8c35c888cc31cced72b2453ae14f3e2850945ccee246fff1b86e8b5d34ac0a2fa57a2a58b04de6b67204d1c3555eb68fbb52f0834e386d3b60d4a6c5930428fda6ddebab9ffcb85b238f136a6eeadc8f044f46e9eea5b5e502f6aaad9aaa0a1ece5c65d0a8bd254504a9f3b00f23edfe4c05a2ce92d4161bddcc0552d719eeb3dab7c166a44b6e36721088be7101bc1e32154ddc8f17779ec8e685f41e52eb69732ae9d7b723a51372037f52b18e78b2da570b163d8c1c1efa255423a495c5d0067c5513273f0b8572168ca45f69b3dfe5463cb99a0b1e5ba7f11559d0f93eb3c61474bc8ad58f0e7286e419fdf819740664a67e264eab1c6d748b33227ce22a8c92a2130fb40214521f5841c6b8d4cec716e4309a55b62647c27be8037e34fbdce5babc084505a7460e27d3e50af8d27d8e5522d2699eb3b7a34e04e7f348ed434bf7dccc91bdc972aa27333d6ae027523615f9e7e0f3a3c5bd680441cdf45563bf27928ad508a83c2d8c47fd23b5fd8be7a1348cf492f1c562bc25ccdb9f496e44588809cc98e97269932095d1b5cb602333c97d28958cb69cb614a452ae1e00e033c59e392fe21f7504fcb436ea60acc72ae69d32dc34ca8fee8a702279d5a3bfc0ee984db2b472e2aaf8f21e09c9b5c639be598ab9be82850d0020ff006a1cdcf697c88e457f72ad2729a8a795b021bb22a62ca07cfe44de2d50e0b204708df223db757f0a1b724861b3ea95524901eec0cf2bf7059bd5a85f2100168deb5eddd221a1f85a527207499e7b722ef4b5aa3d56ef4be3fae37fc841fbd2fbf40c3e03b0c0f5c7d3337dd5f68579c20713f684538b54b04350e5a96b106ec4459086bf689722b2af721e028137d87b38c2e780cd4484f69d6387345a405157ec14782a108dffec1572228497fbca42d6e58708c36584a1f4d7ddace3ef922c965ab1efb9df2dc14164e07c54ed0fafaad3ce81b31005a5b60c0f5d519fbb47b8a1dd065ddf5e45a4723c6d2e38109afa10b7973841f3d6688de05c037721b046e883da7098995d9e4e42bf0329d84a330c2f71b44eeb7a82f0cc162286b1abecd1afed544700b9302ba883da5142b71c3ac1c16418238ba82cc10f7d9627399f8ce9bf124ed7b57809787a60cb3bb3bfe5824970e64629fd73169e41aafa2de6454fab66774e5f6c6c10d7d12cb6e7b13bfc7e314e16709021b9ea75e740feaa0c081693cf39cd150892414efd640d5d244b81a02707a41d87fa001da7710df8e8c39217a30a49ca6dbebd58251846d1298fe7d80df4763b5e2ba9ac4f80be804ccdd0a3c28c5d27111d6732e96fab07d285552bd0350ed08610efc6c7073449eb3d1976270ff62a18268e5e4447915d2ff9ad85257124c4b64e3b2294a4661fbe895a1981d5d2e6033408e3c19b3003af901aca16cdbecfa89ab45668c7a18a695c910fabb9a75b7263f42fcc3f53f22c388d82aaeaad9ce4c202a01d4206fc53e55a990b21a9a932e600674340c7e1e1ff162be7780b54d7a4fefc4bf53e945656fe1448eedd78726570a8642f6053484f8d6558482341cce5ff02592a14bf381578086a95ccc172c76dc7ad3b8f6b4e69af5909653bd5ba517ad2415baaeea4d51c8d1388ef10567d2ee28e82aedf87a7f7576bee24e067d2c45ffb75526ad79b7911799f57125b07ed4673dd2cb1578252ee4179e47c8e96b38f1dac7f22f60ba0045af8692e72765074e561a5ebe504b13e177f3aa1fd30a8ee5730b00ab7d840d1f614e27872148e805f1e52ad13e2889fc7bbc9a468c33d965702a7ef37081808fbc0945d729282945ab83536eb73d9df0068b708730f5bde8bc633cd897ad6a6bfab1af0724f7101a70726435198ec22f6e27f7943c221fb81321b94170f36757980404672f0dde90ee78b38120a212182d9409145d301a7611056d3e1f62365e53b1385721b19127304af1256a7d3a5d817e456250e478309f08d951152ac7d149ad58849bf76438196613f6e4ff7049801356a9b3b597f273b39171826ad397a26e4301a1db13c59ae06ae96124c9afc24d38f35dfaa5b82c5549f0060ebf15248e466724ea75f354e8063f1b6057e35322f86fda447e6a2ad02e92ae9a4b532920e8e2024f59a6e71feab9bddb7ea49af5c8c812731e9b352c970769a286c7a29479472ed239a93c25383ca5720c78d46ea9dd1e1780b823fbb7da8d1716b5250411d7215ead787635eaa697c0f9b85f817fe5fc3d4bf3717b5c09a646df0fe30b6c672bcc1b501f80d2144b8e3ca90ebe0ff614919ab046a826d9ad25781fc922e50727c48503a66d27d96b3d337c511fb728056ebfee383820363fb34a1c4e6be2b721d98dd4228424e72b87de6acad1419691a67c5f2136e50415130c3e77ccaa5721e9e455c13233c60e85f25912a76fe1ccbd6535b5044177454b11710482de52be0eb11e4b8c8cc020a4a9aeda61f63d92b1a3c6e8cac988495fe40e103e5c72d560d37fc58b8b0eff127def871081be6147719d42cd2721602151ec9e46eee728972332a4986a2688a14f4f0a3ba7e9eb2ebcf224686cba6c3ec445b14b577728bff384c63e5e73eab8abe4c6ccbe0e88704cdda5a1508cfa5a6d4704797d472722c8acb6d064536e0cab5fb4e35bd34ebc04051b212871cb90652c1a1530f72ffc289e516244c3edd345443d929aea772d91ce20aaac2a3e3d78f9cbe9fb94e20ed63bcbb5595a2a2b9781b61016ff66041fedeba4185c949e15a800235f672ef4db429332a08f4cca049a47070f3d6024a20ff646b6c45031ce52a215b73727f63bbaf923ff997e77fe7e1b81e025beee9dd5cbdbcd747df703c4124987a512c46ae8818b1d060f4c791e03f3f7e6adb2f8f3739a7e9865145acbe20cea17268c7fc71036091d393f13508b1d2e40915129ab515989accb04fab9df8e6592229f2d24c8030ea8c98684acb54c799a5fa86d1effbb6d0191d3c7bc8b1603872e40d4dff33fff6efb35ce47ce94a3712e797e3f364eebfd78a4388a4f89d0f57d068b7f752e027c2aca0b43b6b4ef4053df428ee4e14be9609d786c973d39072fdb5f78b7df7bbe1c05aaea01b8bf1b8dc9273e2294291449db0da14c0aefa7263f166237458880ac8533d9aa9473f1c428e970c83269100d9e84b5d8163176ef813605e315dcaf0294a9b66d9397be642d98e36422ff445364180aa6b5a0a32b7cd72fa32439e7699c9a12e40622498f63c9df8349861de5b68dbafa067757286c69a9931ca6067b4a05450b204cb599791963b6685b38b1661cfb6608c1f6179a0d8f746df7e92eae0b3a99b8e85251ce63099900cbf8dbc70673de0dfc9019a5a69642a36b89d1c5f662795b993badbf5beb77233f82804638223b2e8e9729c6457a91dd24e5ea6ab5aa41963d0ddf1ada1461c4ce8e4735658a16799396467ce867b6b2717a3c9d6d17cfe9334e8a57a74e407c55e5313b246fb15f0ce722672d0e35a6f32c6e05f08e6d00417e98ce5758a1f23661eea7c638a6b3d9b72482b3df4d44e512841adc5f1f0332346917cb4660bafa89a359165591d82e272b15ef4fdf0cd5e879c4fd5f0ab9edc0683896780d1b79c066369cf9c6a8c37729e841db4bb61fc24792503852671b1e893c225ec13b328841eb8cb3ab721783a64ca8116641f84a3a9b62b9678d8564085e4d59b7f20d5adc11f9856ec4350681fa687293304bd0dcf03771995eb16219b7611a7d20dc591e9bf7cca22a01d727f9c6c177579c5053a0da1fbc062675da3b66c7395dc0f35c575591760e4947236769b8f225e18e6da7f00a05965b28d74a14192b2b85a8f7a3c3ccd2765a372597f10ed0c284da462c0f6c184a2d49ec44f92c1cc590242471afdab214958723c8784957151b0eae5dfe0ae0d32d1b7811dd3600684330b790ad80f573bd172798f75b4852bc9813188422068b3e1f49d8922d06d9c82a1f106cbc2d6f41672449c204feb6a9796ea73cd3b5d7f6117ae73b74fa02d666ed5461516fc85613d6576d3e197d595e341c60010969b059c1acdf4491115f8879051351099eaa92a28c31b05f754b899fc0d9905034fbb408dffe765a3e845c95dec14946986106ff292e67063e121e2f8ecb35187e5100a3d223806373bd18797be2d9c5a7447726effd82096dc945ab9ed228dcc23676b2a85ff2e54aa37726d6c8bbe7617c772b71163831ff0c0331868be0a5c47457ad68312490e8e85903c1da3958d12f02115d2a2db59fd3fd70ab648e221d98c8e7a05120c455c2004fd358a7dcf900a72c18a5b3e8df91e51d7f5b1fb47a5d2d42806351d9cf6436a14311c04db8b8c2ea10966f76a87db948d62fe6c5d34cabe9e58bd2182c124e30c557cdac9b9d31c055f18a5ff759d7e9b20ebe3e88d27942f3572d818d851b8168ad78ab07eaf71d9652b42ffccc6e52ce3ba410d0c5febbcc3abb5668ad7f64735786f721065728878dc92347b74369086bc6b1538d4715c9bc84e49eba6a81fbaf65652ba762a8764c44afd3482199d7f69db618dcfce09cbe6351f94758c5286d75fb94adf72a65a440f6d2ef4bd71849b02960c9204accdc0601409ac9fdf02bbead2b4a13edbef927d6ada006ac2762a4a6c0c438c02906d3226c8711c85c2efa511b48f723d814246b9b7736482c6b57efda686deb2bf64dcba91f56285376d6ccbe6be4d957a8be9d6e00822475dc85767c165dceb45c623ff3ce5fbe4cdd156c0e82b72441f0930cac3de50cc7cb80dcd1ab51832a5d8892677f07fa0beed4a8df2c069b7a00fe662995ca90892274f17bb83c5037d10beb71a9b22d20860d0fb3cbc7216a52ae4115d452b354678a321fcf16941df9b9b3b258145149107ef3698557260a70a62d753bcbd0058b53a7c8778a13eff3c15fe3a54fb5a2fa423fa996624479b066e73c31edf66c3c346cc31ca9f93c271cc5f6832608357d8d4c42b8272094ca7debf79152996a923e2ab827bc4c515929f16694c0e84e338c0b14f61438eb50b1193cb6b556555bc39b4d0103dbe908ecc61491738313ef49c0daacd5074eae6d7708b6c4cf3305f94a3d7167c0d1d8cc9147fe64197fbffa0eb8f97235b2e399360bc5c7303ddd5fc8176cec8228462071843dd2363d0021395359e3e885d00a23d0bf85f03dbc06bde17be9735f4a8863dd432a1b5f60b92dde6945d8c08731b75fb867a4c40a7f935acd1d20a96cfc4ec681226cf9858cdb1a1b352c11776942d33850706d3db897c9535bf4eec7b6318f13307c95a65e973851d729cbad42cd51abe4b3d7e0ca1b9992fdd700367bef6b88f2ec09af3be63b41e02a9502001e0cc7a7f2278972c76323c029097d3b38011bcdb2d8e362f54e64572f65918c79b74a17da177b22c8d22df81699276af5fea37bcc8558f832efd5b7217ae50504f46eea3eb125717e21ec25429ee52286e42deb9b796182cbde60e728bfca9435d4292dfbce2fc304bddb75ca7187d2627ead3bda25ab3d9981e3672e4bf2fdcd715a2ecc2491b1cf3130336a9486d4f07b23fc326e3dbd01c02a57251120e8994e2db8999a9b34235904a228c56f434145831d1292c0a2357242b72ecde7aac9db4800726d3ec935773f8c7267a3e3088918961ce68d904b9927e425af0fbffb35fcc3d6806ecdb204d6a514bd719634f4f50aba469c467334ce7258b0c268d1112b6e246edc191dc2d8777b575199f84af9de3435a8919ae7cf67276bce4d00c5f8342f6448ec04ed9e6d19fa1068ec74d31efa5a7ff6095ba3b72453ebec4484ed33b6f770862364bac867e7981f4eed5274bf4f5f6ca8f117772d812235093897de086525d7dd64e5a305e5e2e6d1358673e7ab44cde31055234b28c31a4d358682a3dc8485b071aff29e5d4a0ae83e3a3730082a9c8a1464e7233b9590c141e55a060679cce299e6a4097f29e301b4e64e1be10642d5ca596723543b257be72a082f4bb7458d2b5e00c4265f9e37b2e75a06d3872a18dc41657c5682d4ed5e6e78632596a625f73a3603b336598a360d48a5b5e9ac83e2c74166bce2421d8ddda0bc45a67dde41e99d8f391dbd7078fa3ba4933a34f740ae43d94fcbfdea527ef8d5476a212eedaf64ccbe0dbaafc1064c9c1f5138fde19b672453ea81bad0bf8dd1833e1a70fe79965d34bd30a60bd32a4fc270c6d9f6b86723ce0ed7ec91267fb96102cdb902d2154d5483346bf561357228508210ebf7343392b16564b8898544dfe6629fc4be4c78b013471e2665997469f71e4d60c5c05e4db5c373f784b10f78b2508d99cd523ad8717a9a5390ae051b7b28f8b8bc8722b43430767dcf557bf5c4ac7662e6e9ba3fc6c7a250f3d4506572fdb769a3b2edbaa05886269d277af00f699fc059d9940699ab85d5104f0ae5db8b0b9080572a2403d7bbd899400ac52390c94aa6ffd6f032c4b4089bee7fde8dd19d7539e46c9d81239100aa7e9a791180dcc71afc1c3201dc0045a00dc8b421f36cba1a313c93222d10b136d4e91aaa835338ed02596f26629ae667f01093b4db80841eb66f340ba1c31e56f903b9709af5a374d73b47c7acf2a3daad43f0f97ddd3d55c535147e520fdb09656beccf4ed6c694c7332e7fd0a8efd77f1bf8417166072d272d13de180883e5d0694bf9dd6fcacb1a722167517f28f6cb8af54d144bcd6165b10cb2c4ff5081a4190004233dbcb642144adde0e31f6f534ab0a8433aa8809727b8f471adca536524e012ad1a45b9107a2159259091fe2bfd78f016533ad59721a7ac880ffd644bde38abf93791167d5392286567067e01a3c7ca6da813c837276fbfeba03bd299664253f204b7cdef772dad2b6d07ebd7d51e3c85c8cc5e87011e42d1e4ddeb93abe36ed3528f9a0457305f7abf21aedc1bb2ce235f519d6729b8a10c321d287431666566fe1034e8122eabdfff1dfd627d23ed0023db1ca72d82e19057f5919ddd49a56e2e14960159935b42debce55359a27c9a2d3e5b872aa7f80e60a006eeb49298351b705260966c91db9f1781c583b6bb25b49176b67df9ee5b12a86bd716d99aa431e67e4f66a11572740197c0e50ceec02ee71fa5483a1fe2eabb9ab326f35e083f0eaa8eac0e6d588db4352b77fd1ecdac95f32257536ae305f389038b5e5ba0400138c1b2e5cf1c01a4285d19e3c37e3fd1f3e14dd93f4b51d973af4d9e0b066df59c792470b5dcf3439f48659094d25c0ae3472507d6a655b61421c57c9a8ff76266ed19c1b30231ec525602a165280ac78300ac87f329f0ac185d5ff918b3a4c51b16715330d743d5d22ee50a58f3d0bb75e26ccd7549413ba9e6412c86ffe2d63876f0fc834147c0c9e0f99399ae55d864272cd016d1c9dc2057b72c2f23d7542dcde6001ad7828a626620d2083606620b966de225f344ea61590b161c16e2a2a64d772d34855ddb98773c6cfb0dbd6994010867f79ffb212483cf18e20e1169525fd27e9a0b2161cdf1886853886a5b10a68015bca180f81e9036990934e41fb206bda3ef25cb471e4c1031d55affd629c726d7cf3fd81fb102bf46b3dee79c8434de6239883bb79d64ff2f02dda3fd4052532a21cc294018f33115b58fbdb569abf61bfaae2a83f02337899ab1f3527cd727de92f0b300cd661dd1e88a02665d5bef1811868fcea34a387dba45cd96ee372d89e7454e402612ad962a1247586650153032e6986683ba6d9ef381a366c4e7283635bf513dba52f9ec0419f6ee4de92ea56537121b06f99101cda2c3b62021aef6f4456d0a656b0e2ee0106ccfd95b15c17973498cf7451762fb5353b459c72ff5314e6c20888a394d1a317e48f6ab6359e9a70bc91e645bfc8b6136748412e9732c6e6a529a0b6a892a86fe4427ddc9c2bfcce26d175f0bf0cc417fbc69e72b4255186a0d5cd3ec9c7369da9967962a22bdd2202dddb34d9627fe5c983fc724b45a7bae21ba51dd2be24a4c1ee449115691c3273a5ab659bc8b39133b3d962fa047bf6844a193e7f5ca79ff2d46014ad1f98ef706844e796c61f76169fc77298c958b15ed9f512caa6ea95f25789a4f165a0a83d710396307c810e230c3d49747f7855f364cab3faf15ae2967b18d8c87a795fd953fb6ea5d4334225691772f1270210cc43579fb154a4d0eadccc8861e31ca04998eb5c2233acb6af2da514e98c2e4ce5570d89c25e54921177e557951c0fb9858cfa499830e8e9b90758237e6f1c8321c175d0845898966a73e291d3e41cdacb36c1c44c55b4197e03511f52c015f69d9b1b2fcdc81613cf28b33b961f7e3d934ea1964a4075440d121841a63af5fadb7eeb8fce824e1b475fae9cbef63aa84aa277049292bb843adb9f7215c2c97ade770ff91c8ccb339bd4fa37cd3efbfffb0dfed34aa67e651dcc20728c45a1bb533e559c1710f7fc42853cc82d2f3004052a27dc1cfcca9e35cb2b724f789464c0dcf49818fa18cb23f164f35308be7cb033d27dec8768894cf5c77213d75f4c496c56104f2955a558c22905bfe64adb3e77e0ec69719a3e1bc3e04276121edad8de008df4faebe9106de4df4d4c60355a740ef7bd1157afa2312b65104d69ef42f94ea86fdd8a235874ce911db59f67fa6a538f69cb40ab69425d725e99b4e3b37124cbecdd0122b0945dc1b11bd45a228f41b376b4ebf5883f7f364bd3daca7030574a483fa8065118aa9f070f983b51203b8d964712b26c8e915cb7abf2eeb56c8286f7b5082dc23439136ddcceef6bbbcbb7476a13b1162ddc4618bdfad613f3aa916867147bc70e3112162301945bc7e59eaf5552e3983a0172c7b7324c6128a64e5ea7d6817decc644d2bbf655973af8d47d963a42f62ad072f3b79e6ff77c1136ec3421f58774c07a7c27955aa069f44462c2366a34f929720eafc832646a61fe30015d5ca1ac4e18fefbdeddd21be0a9e877cb40ae828c7214b90c1a1fbf681fc23c93b71bfbc61b5054453a454516557f66a26badb360722fad7b09c09de5e2f0a20a553aeebfa82b2f27e218b1250ae011a7e6cdc90913bafd69c7ee728b7feb108a42e48799ad7391099c8356c1c8a44d0ca25f40ed5a3084feb29746e71328af6c8e32eb2e0dca47c1f806de99270ec22b4b9aecdc7206dd643f1d1a59f31cf98a4d2e7354f86d57c672ade18afe9a8fa6d7695c8072054c0ca7c4bf599e945aede598dcf6affb98c1d140158716af14ac78f2dffb5ff8a0200704c5bbc2c632acea5cf1ca7df8e7969152ad51a3a7752d20523800723d3e30897764b4271dcdbcc1f41792b5586f7fdf8f12c4a218ac0e46746cd83055dc5b134c553173ab29c22d05d06e77acf4e04d5f62d036cf7cf1b2e83cb572f7d57e8cb6bd0ccb9a65ad5df6e347172c9372827839d8886bf3479211cde14bf95644e2e0a8d5ce47dc5a9c407403d17518e0224f7e2a4a3576ce0487a4b05dae87bb01ef974f0086ad49ad514913b36abc81a160df6ea58a6893c3326ed8729908d5e1a6d96d6653b6e3d70dcf14c1e5e94739d2f1d9891a433b5c1149b250768f30598a8b150aa8464ef5e237eab32d5d72a828a13bb0aea404ca768b1b1f51c65a559ca110cfa968b2e551de62243b8f3adf3e686089ccc9c3390285a5721c3361e5216aba97ee83d02850122ad7409548c29339fee8a0f4b030f60d6f16ff68b6ecb16ee92e0919965529db138d546f1be59a85feac65a0a89f1ba39b7218bfa056336ee044a67c947c78115709fca8ab364624840f858fa73788c6a572ed7a7812c3eb4d1f0467142e508410ca4737f446a66e0dd7c88cbe2cb1a8e0722baa0b9787e3b892d9601a22f34fab3dcd97495538b33a45f8cf5eb0f54ab854b5039b2225f3c1469bc70769c947c36c0bf3c72b39f7c632e342c0e5a65c5372880e2e411967a5a31603d13e6846b5b2ed617c1eea1ae3ed753c13934e3e11246804eb14810bba1b1f2a5c918ef258b1fa7e353ceffb7a7b933e6ca07cc5d627a702c3ae720ade4543f400eb745fb752bae9b9b4570615283f1db895ee338372bc5ce2f169953e0e457ecc14b411cfc41d19d436edee22028b1dc98c6f504a37da87fc6a304549f38ec3d6e8afc2e76ec4fc69352b05c1dfe9b8800dfd071d60fa10a410eef64376528c304ac2f330c72b23456ebf57edf3522d9b31fb946c723c54ea5fa9fa9ae58aae861618b50aececc21685b31f9cf77ffa636cc1460e7243f59d1293e7f76761669e02c9ec6b8ce99f350277f0d6f8aba492008f718a7208863d9a7dd3fa0b6c92418f3e8d46aeb5af6d535fd889231e09fcd446b4807232b575c75c8cfe0da648d983de78ded8ba1c7ce8313a55059acdb6e533a75f72864a52bc2cb26a5bd5a53c64f12e786f7699a4ff1182745fe40391dac6d0f840e1bfef823f905de8cecc1b769d582ec3db43811d39362a20896bfa5719caeb2f12c8a8b3cab8dcd5651c755cbc96f50f9b629c9256214a6a59a82bf3313a5e7232cd3ec54eda6b91f27853c8722f3ac13faaa07046ba55af0a4a014ddddeea695d94cd6613110889d4f608d8c596e8c0488ad9e5b8b4a78d56d7962521830a72887d68dded938caa0862b54336dd5af115ce0a7e5e8730fb7cec708cce2d66728f089e759be6a7e21cbddf04111855486d1d315a3f94e3034478b30908c6e572c44e9e5a49ee4c0030190443fc1e86e09f3e2d9b21d75fd529dca824a79343591bef1deb520acef10dd4dd66761d9a94148ba8ff6f34173f76e404b6345bb139217fc5891295487ab6416e504c2939f89ddc55d42b3ffa2c47c867401c66e236ce711f4e7ee42b0c171294c1c6a257635bb5098545dc6890437c7f56fab2ba729120ef07b718b9388f251cf0b360d17d3080b2532d78f16283165ac66a0f3872694c020c5c2149df845ec1fd39d4b1b3ee71a29062df0f055e85a4b399f59c33cdb6f3c4cdf3c0569bf543e5278fbba1c2a83370c44f75dc44c61b7462481d7273571a09e1aefa218415fe1313e5da64697500837a5e2c27c44e075ec313a6720fe018ebb7acd9bae09d76ca87be36e4cbff389e74377b98f7374dc4e6cc09723fdd9b92840ed183d7fbc4aa29c541835cc0ebc35d5a7dd6f98b3725b282c7725b891a8f43617afc3dde2ce30d5733609715fac0989ecf9c9623c35b6dd51f23760255e013a40273261ae57a546391dfada0f58c79334c272350f99b2932ca72dc0de9751d994b802951af410832bad44a05b252e3f6e1ba295e795478a5ba72787c4013e30a31ec38bdac8fd431830cc5e14316dc0f5610ccb8fefdbf279f0b2c674a42962d552d4f66cf612ada686b879ced0a7e7d206a15be73af35fda844c9f9bddc02cfe63112b8ec38b5be53969bb9a092a2834ab943d7221c8fa1aa1021af72dff290d5869d5ccd1c433899b369c7327a16ec8e65e2e624ea451770729180a874fbf00953116c3b0ec370c6bcb493d7b3b78396a10c1014bad987ee4712a054241d7c824c47065bb04abf95d3af6204cfc27263c7da714b674d5baf722acd20b64bffddbf50299174e5c3cb6a51f1e6dfcf06a18a791d469227abac72632ae9d85c2003a08feb1ade66b27c4afb4091a0004f43e5b5ead45ceddad57222f560246688d20ff4a2eb9dafd135d9202e4d9606b533fe1596dec9eee44872d5d38343c91cbe60ba72e76c6a58b570acbed1d155630156f6fb2e6c16538a72c0f2e864d0e8b82def2ae7abc47909f12e4a97a1895786dca16b775a04c0da72c1a239c9c9798bf9762be308d5d6c74cdf936d030d564ef59e8fb0fc40ed9b64f021f1dc0df86793b537ba578c82671caa076eb41af19e812776f38f5e98ad7254a89b892959d7fd40b11f90bfea2e2821f1e3897de6a7e903827261c18512728c93cf6e089f5b8696fb61fccd605c667043ae345cfe3f6d2d519e5d21b0fe3be2465330e38c4e13f2372d26e611c672dcbd0d73c7e7392a8928ea3d347c377214f37045276b44354c518215d87da0522e33c5fc465815e0568430e635ea914579d7a2fc8a336e82fa5073b571d55e4249e4aef5e48512ec3c71f81dea691672b44d0138220fd7b094cf99f21dcf0ac32b9e3badf56c1e18a67ba5289f1d5272a89ee01389269e6d098f6b01d465dce63d89ab693f4246ae6ef04eb3e6f2375d56e4d3edf0e43323a20790a5dc434a2bd69f4efac21534c2ea418d1160724e72ff1a3bb0f816292b37e6cbe5d58305d8309d590f653c1ea6ae388c153f844d7285f06e0316c63202664e55fb23a33122182b72022824231b05c6a496fd4ea431e3d6da915251b977ac258b3a0e93d48ed03959896a4578f4ccae83ea8ef470683abc57dcac38b5f3f4b8c4ac08d103c50a3a37eb07829b429cc143efc2a8412fd67b73784f4ebf56d3c16beeda81b1ca19d7163bbb8d1f982c4beb0679ca9b5e94af1cd04219dbf6dcdb725e0db09c0c56536eed3780bcd00d90150f897dde72e9eb773102daebe6e90a8497136e7e2992e967e1135b6c065f33ee2aa4a36a7242f447c74a1b93ed072a4345bfe53169ea70d10cd0ef009445b8b6ccdeb60c479d6c6cccaa50bab4683c66d7188c060201777056a9aa7adc9b1e3e049358677247bd109fdf6ce15946000ce7cbe52d728654bbb931f58b2661abbb6f000017727d7e689392f44db22087e14671fc4076c5a05fbd52bd5096bcdaa038e891fa0bc7a84703a842359333e6e2701eaedcdb9c2f3345d295fb0aed5c5552ef7f8826e421c74f68b19c83da86adf1c4155eef78125994131ec05d34e071fefff384132a2a57af2eeab5503f3bea35d4aca067ec77135697d60502d0c55f52e4910a589fa833458a7177f63209c7b5cc1c86599a037bc64c8f416756ff3d5e7961250413a3727acf203f2fc5df9a7846b8077e281be2b1bf50712f65e72fc886fe6d72ab0954418cbdeee1bd1c92ae220c6513da780d9b8b99ae32c92713eabc591a4e12368dc9db30428bdad5d7bd35b3c67b86c52051694b1a8f7917e264e383f24f8d7016dc3820cfb425c749686564ac56a367989718e82b1ea303c9f35841b672edcd168fd41605765efe1b6d32d513ad17ccfe5822370d7ecfbed986e286d8148e2bab06753fcb1c0d996384ad82967aa98cc342273861fa82ee149a2e284c1f33e9cb50d2ffbb472826250dd8a0bc2f69505bd2a03853c0e9920f58bbe9c61626e7715fe45315c2eed0c5267a19e1a9c53fde1eeb1a2d2a2602a5f4e4307617e7613544a75271d78cfa81924bd6ff63b3d581e324ff37ea4ace85d9851df072c217f33eb4d36c5d6eefc26458d6a9cf75963cf3c6d5aa9169ca8a7e3787027254b68be97b4704f31a1ad4f7a1b7db37e746c348cd3326a8122aec0675a5e114ec7a691fa6c3add2e6bfbcd4478e22fc18af3b9537df331389d509c52afa871265db04b75866b275e00c993d2e97c231e47031520efd5a9aa9c304ba5e6ca372864c281b1d49be973ee8ff5a3c7157c7ac75f8bc88d08954e11ad7afb9e40472c2ca1d26ac3cf39c761eeac18e9e4e6c8bc8f4adb80a7dda1b57a88836945c35753cdc100cb4d7afb8463802792a61fa495429c68cd8cfe642bdde29f3ee364283b3f5542c2fcec5e6481d6abb1f859fc761c7bc77fb37866e65568ee198527226b7c6b9967a083c0bd5b1f0650aaa68d02e220eaf21f782a010d88863898572207a02045e81906c365629a1aaa45af5848d9859229d761084e13593654ad1670faa341a290cc7dbbf1e18150128efb50050d6cbafaabf339805e3708896ea5cc449564d92ffe22660199496e487229778608e8040046b4e66c2a85d4e0b8b72260640aea801958cb8474d937d4bcaa09e2114c956eb30c03a05e4c09199cc316f75d91edce169141be2922e195be0ed1584dce19bb7c9c9ac6a809cfa0cc131fe0a3bd521ca82bd29b45b8ad5afca38ffb6c880730b3988017fffd266fcf0727cb8d4f6e9354722edc0c6ec7a5fb0c65bb8fe72ed4ed5f9bfae78d56fd6562f2496524dbda0f7c8e6e401eb6b6ff9387b90f8c283da229810cf99fd11afb46450426087b1e1622c35746de28cd18c00b724e66bb0368304901d906fcff8a77206d77450cf980b278ff12a1ee10cc03ee8a82dfd54243a323c4c0470596ca272343bafecd72dc81894829796030f7a964ee1e9578c43871fdbd9fdf0c1c8ef7202bfc3c3775f9243997e51b35c6aeb71276d0140ef72db65fad02992e21e5f2140e2579019bd592bd4c1b4fbcdf9c395e7163211a2468b04b3dd88ca8432337280bf95c03174a5ea2025e67c074d04489650a0035bdc486392663882ff43855b72370f81ad5693137aba633ad2e4743890da1e4b0aa92433e89ccdaaeb67b872398b7e626e40603f2499641b1aa897f80e0e9439e45445aa444259953d515a72615d99b775e37f83fa9ec8ec0be9bb194d4fed15d24b66e64b54c3a1c0feba72292402a1bb6d39204657319ab7153c89f2460779f84d824607a5071494986f58fd21064f84cf351c860951582415cc7d1057a7f9c39e5428d003509f4bb1ac72763dc680256bf04021694ca9270d01b172c8a6f5a3aa614ece1121a92fa9827247b0245e9c7a014690b32d06f9889ecc62b63ab4356dbf7720fcaeb8a1ad6b5b5f01e8e9c2f67fb5b4184ca00024bd76641452f80ebd537994f3e27a1c922d4b95339f4a57b23c171b7e110fb17045e437b6a16a91e5c1f8d0ff9e0204cb3651ba2d7dba4f777fe1ac90ccac098e4979ad171de99216aaf1d68326cd1e4b4472b55da265bb59e3c91ad7fdc3d479e38bcb576c40ef24b76b60b47076931867723e88eb34340ddcda23fc0bbbad72abd43c0d1b982f5c0ea0569ae5fb15849f05919f93e56d896effb82c72fc44f3a04b8bd7f1fa89b81f8c17a3bd7c5577de7281c3decf39a4ed60dd7b6035031e38e479c9f9bc1a49d3b29aa79c91f45dd33d3ce4628f95a9e6ca6341220d6600f9cda3a2d058d40a2f795aa0b4ce6b02be728747edf5709cc3bbd2f13b69fc541ad5ec089bbc10a017a56b29b93d86f1626d07736d36fc2b6e3dbe0be34d5e58d83309bd1aa8801a45a2aac209b051ff0672e6e16332cbf26c48f6bdbc5da22d567b42a5e71083e552fdfc4897db35ce1e72ab103bcdd4a5d59cd4de5fb41df6ff49f312554327b700f9dc36dcfefcd7dd639027e9a963da8f49e74669d15118e91914bea55e00ddee4dce3800e682af4672417dc80c0acb168a0668a0fda3e4afbff835ec0b94837f639852423ffaf90d722393bb92af377bd10c397a8499d294f6f4e6a80c4f4a2fa0278a28af07585e34720ab34094a3c504c7da209a3ac5bbbf79f3c8491082fff7f6a127c2e30ec072f7065bf89a9cccf1693e726251d9aecda22dea035532eac9ec673d35f2f1f672fae48bb14f1a49477fb3264226599db36bc10d31bc205ea5f177727f2d733c72f383522382803a9736aed7bef6f604204f96ceecfa2e60749be2880de745465f91f2b81e4f909d96142f4e9b92dd188282508c239e53923684cb4a0db9c0eb7278df6cb6a045ad67e42ba72e0b168b0ec33df561da54717fbaa5d4f7092d4172ba183248ad5392c4cf73aa7ffe49023287efef10f5325cb01957821f7e37a943081f0214404cf9d798a86ac7f55fc47e4258d207120b858663d748ce7c247b72f6e4c8d4ff9a9de0485af5d9e1a9537986aff333ab587f2802bfb7d103781572ae835712787546b31b4d3ca64abd8707ecc8c8c9646678d2d9e446a24cea7c1c4423dc24b8e5fc70ab13b2bfbe08d59d14f145285ef2344c16954b492bda0072561c2214c7c2d8def5dc5fb375ac293d19918f5fe7db99cac0f18fe28d1efd224f53530424ca54b70d5e28e8e83cf41e5c610f147199cd1a2d4f91d6e4c79f6bfed0720f758284083ea162ff7175ce21da054f42d7586c14bbb12761b28cd54382b21ba21ae0cba2ecd73497caaad13392e7370775f05cf1fda84731129c0f181f2b73a731c9cae9ed5373eb34aac5cdb873024611c7776910b8fb7e117ce972114fe200026d46a725ff630760a450ab1ed0730cfa6232e1b8b896e4f15d24727544fbaed711e322a26120d30410ac3a7db3a9890eb835d14d9fc67166649072905446ef80b315093583e7c06fdf728747f8dd787b154d659dd014941cb2463794924ba08a3524f2992caccf041b76c22b28fff46080220861517b78140c5a03c0ae9b2afaef644701264857bd345865c317d7e177933662be9bb736077d113e6d2c7fc854b9c3920b636e2ccf1d2c33985510c237ec49b418cc3da68174f1725c2f14e245940a095aecde8d286804b844cb6c22a83eb49f5ddcd81184dc5e721e1c59b94fecc3d588c01d76ad83de44142c947790bae91cbf1e33e4d143b64281179283142d22571b196ef53d46366e3181ce446a604e97e0b07ed591e8ab725fae7482ae4978acf0b89c3d1c58610af1ac5b19651a0494c738ac671c1f7c722dc35300741f797d8598cb6ae74127c6584bc58ac781299d1dc6ee2c1b725a7279f3f4dfeb7d1cb685d3c53d5eed17466b31578875bf9f81c9a95c8158e7843f49c591b22f3647c5c61ebf1ed19aa0b9152a9731aaec404f4f8afdc9fff1fe72c10545c1dea4796a51fd9bee09a938f46a39bdcdd036e3e8413d5c777bb95a72bf45cbdd70c8039252f9595760e1012b06d6ecafa3d6318bc09a8634ab85127294192332555d5c1d3a6fbde3a7df51e71aa9c6130a8b6d29cc130a4502b87d19282d3e85270a61da14e8b9b8af25f86bbc05e2d3d128efb1a677c7031e913872efccdf42bca51b5832ff257d9eab99cc7370586487cbe062f987ddeaf4dee353380c3dd385c522599165db6ab91b2bb0dccd98198002e127a1788920f9ba613b323442301a8080794d7fc5aed9fc3f8d7465ce1dfcfb730591aabc39cd192e727e64b221a2249da5255b6263958454a4ff38816557bea3bac867c3ed49129472818a1ff6602488911a08387c40a38d14c22e71b7735455a018eb463c2d7ce37205b464e2c706a74a21c1ffb3940dfeeab6fd1b39f6c25771218334e93a640f4c794e4bd463e82146cbc67a042dc34c9089e9a3d8265363d2fb5ff2b402c8ec7286271ef8c9c511b4aca161c9632e510b8d7846a77cb4e99641ca3ab6d1af697234f5f6e511c2e01e699ac540303458f71b97ef63beab91264bb652513109a87249d099e8097dcee2a57d0ce270a02b3bcc5afc4417cc4d1b1278045258710838dbc61f5a9046249b0e1e56c15622bfa44f6dfe38bd4c49a46fdfb0b94345304019689e24c078665dab0c590b237a44c6b019ed7345360640a42c395e85380b60f7d27422dabc251fd6a5903d78cc46e751cb4b30bec80cb2904efea7f85d0c7263105bb92b6761b115b3b438266697d458d1439959474271cc0c2a558332a1672fc650b4de371565f4f755eed768de334e3c504ccecb8618f38952abf9f54372744126ffa2bce60126f0d7b5cf0644a6967d8f8a768598c99c236a4c937f2331441535eb9e20e4e222ce2eb9ab87f8bc830b7a562d4f6a824ba4e174f281b9721003c0a2415dfe52d61888cee0291a9d79a7c7d7737490aceefb38909d4d4c5ae4fc6224a3aab28b0cfefce36ad2025d8670232e58cde1f9225cb818ddcd1b16a15a087d372f61275ae3a0129bc86818d12f3591ca44b8fa6fc499e8513392729ead62cc90223aac5b9250b50c837520e86b53addf922c244b9cd4569a767d42e1ee69fdc0f61b188f50551ba55a79bc7ac8509f8b3f8dc984776bb5d618fb724af23c4c687075b42d339037a114c034bef38d7b5de999f56cc4265cdaa156420f3f5a85999d4c036726f5951133114d4ad10040fbd9afd9f5f123451d8abb1a41c8c24e152b8c7e19b89a279881e57658a424672e3ebd29b6966d4f88228e10e9960f6d007627495ad1a3556d3de5ead17deccaaa621ffd7c0f440b22487072fbebd4f26cb8cc0a9702424a32128b614ef2b812f89842a4292f6e55aca2ee3f9845d591f0db4768036231f4f400a000c771d04fe5c5fec7bd655fbd189ce60c103125ba467953fa2b73dd85495cbacb426aa72bbd408adf1dd180c69fd13557092edadc9366d7557685b756d035547bd46081173a43f7a05c83d68c063d7a672381e10a6533d042367e2291a33dc0b84b3a3ea027b431101cb8378bd653b37224ddbdac64eab92384a7b3586a71d9e8d570952364da61c397bee8e700519772fc152be286eb495b3301de007f5e8574199933f4e170bd120e4081dff0f27b5c232f642b079d0f609de484a476a0759ec644b03ab3de9e49fb4ff2d93c1442724684fe43389a54ea9dd91141c3f8b6a1b07e54f67f0c47af672bebfe6c5fe46cffcf63869ec6fa7447b701106209d91b3f4d0c4b6a290f2bfb57e3b531bb7a539d60e18ee40d799723698f33875c581680eb10e5abae7cdb3a15ac8e28d85772589461550b3aa335a1092c3676b63feef7f82b0224b319e7fdae0455656a6072a21f840570831e54c73f19ed86ac9ecd079543a03905b494d6dbb6a15ed56d3ee486f9b6efdca241769207af5fc0743346dcdd2defb62a23c28c5d5bc8396a2e4ad07c43b40db5e7c9941872a957e6f0c20c3e8ccebf60aa1ed2c4ac9e2cd027bef1f3f095e249d04ddb0379dcf9627921d2ffce1c0ce2c06cbc71e78d068372d4bf9a5c7a90a9b8303348f9b0a568760ebc776afcfe90fcf50b94ee4ea511722d9ec98276146ffa96fdbda6d7733a366884e856fbedc4934dee83412186f32a45dadf5cc88177cb1b289e3c35acf977633f2628f6d28e7ca003cb9ff4632872fc560a1885fe7571adb4fb2e9289baefe48bc9ca3d2ea9a0f84369a73825a1723ee701808ad53d7c2dc007574f226b1932d7f33b68c652712243766b4a41766dfd8079fbc12165cb01a697b270715772c104451cbcdeac72d525e299f849ff0549537e8b3597f3736696a7c7d116b86daad36d951e7ae07ca092ba3f9405c472445c9a68ef5b697fa327b5bec4ccc001cc54c67368ac77418c69b8ac6e489b7206a3f612f3cf51d0e3bbb639d8ba11427386aa827dcd87e3f9e7b0b3ca22457169fd86679b6eee8231d396ad4450648dc5da33307a79dca9832743434d089b498807d02580b62ad8c791bebe3c172d0dec5d25b906a7c5f4706e68f66d93006aaae1697d78a2ad816473487023cb57b7e29c069ced380f2c81ac3d67cc396d7223133b0455af85d1a702cfbd17889e938d8f845c1a2fe2fc00eae765083e0b4962040f127bb791a861d3589e038476761e97034996db47eaf0c0189353c14a726a5ed2d84a8d311584f577c98ac04c88080119a3558518e64f641d7eca176c3c09537896e8a58ebe74560d373565f3177c54a8b7ac4730498cc55c7129267e0a06a482714db4b584d42fa0ad00c2276f2b56db2d29b149ee5f082215bc8fc272e7be333d45a12ba42b1da3494622438e4592441509f0be96ead926a1f43d557244697160256760f3b4365fef42ecb2e9829b4630adac2bbf5404f3a715f810722e4107f45cb104423de6c3f3ebe559fb788c524e2572140347e23a0ba1aecd72027e3da2785c4ba8c4b23ceec9463ee3694a8a31520312335fff37bf625a3172deadbfd1b7b9d06db8569c1ac4f5a19952e758e9fefa279e901a2ee58e68ea72c372e0d976b7af87285b41a7758ff8cdf60b258d91b3bcb601e03079465dd072fd379676c3bd16706fdd0fe0594c0087801dbadfc5f17684b5d29ff45e447065f204c1a9c61fb02146195cc583b8dbe9d4d3cbaa9011b8ece1d666a2d176ab72a3d1b55d926571b9faafa5058c95488669e85e36f40d419f723b2a0eae42d872d79eff7b9395fef976f4193868ae02998b4ce12c7a38ed4599b2605601f32272c9cc50975475b9bb8682397208235303823f61c635697eec43393e9dc6844072be0a1bc1d3088177b5d562adde4f966acbebce94a527f44b1e4edf66e6a29a728e618b834cca6db09dd62966b0031700e24fe3aec5d4d12ab77a25fc7c5b3572af14718dcbf599e1f2aa11c7c8e5063fbb3f315b930b96640a2c99740cd6453a0d7349e4937bb353cdd461c4f8134c58d5a173ff15808419510d7fad3917a372e33c46739701fa6e0f40143b8e20dd3161f9d5197f5b6dea960d364fc00470729bf762261f04616247f72c2967cbf641cb03fc53479a0cbaa71649248a6e62485f076b3933fffad1e2f6e5ffe0e22d3fdb4b6932e5938efc82aedc0c0de38c3aabbf948da1da46544775bcf40bbff43a179655c033386ad85ce33418f5bf8572231e02093cfe1367474d0b938186960cda4b20a1decda883724dc820bb29f838021e22e26219d1beb1647006919f2387f113f284a485573e1028f9a3a5e03772da6f8ddc3b7b1d90daf221c349763dbac4327c9c8d7ea8bc2e1d400b6022040a5a142e166f8cb5b1d3f619935da9fe128026b6bf69954bc2d11518b198313a69f6e9bd5368600c73f4d940a034ce8c717b1ec72ff14015cf1d33200612c02815361d4b4f31a213cf03690f95da1f9235793c9cf0b8c7580b8f348abe074b310d8826292658aa2dd676f9a834dac20b4dc188c3a460f8bbe8087225615cd5bc1387c6b227888a25a1ba9a22a0b301e85c8d65a1b2b2563362793c14d06e1e4c72d19bb1bc3f33828abc31ea490db3d1d9d42c877fe598edc7db51b7d47406ed724b3f33f5e36a7273abb37994de93ea4227dc6428b7d29f75f9c6f02f52a9d572c3e9e047d4ecddaccd66a6cd5f96f2ef2bd982035d825e96443c00d0f4a6be563add71fca599ce180aa7e3b3fc6dc2eabf960c6f0374181997a2cffca98e7e723fb319e557bcee2a48689a40ee76b9e7e5b655676b0c2f8ac2bd9d4b500d1872b0e934d72e8fddfca266f78c3af19f60f675e221adace0bc6029932c4fcdf272f3528b750bbee5ee5b239220a1f7c95f22c9a5979a5a8bf0443108152d3e327282a8a0d80412d637da435c587557eee61fcdda414ca1d91b76ce1810aeb3450f2f6df5a108c7cfacd6ad9e1fa97dc3532035dc961ff69f701642790c0a8f5a47f61114fbeffe811e50760cffac6e95d306180c97474243a831e56a9353b4dd3fa7e74758d5067f70c747ed2ca9b0220253ffd91cd0fe12489dd1272a8e0786621491f03b1bb4628892b6a76db9899177af1ae39a74bb19b1d784cc4691c77572bebb08d271571be7607c214c314d4540d54efd15829cc75d8c76ca969c81850707d8fd95d9251394ed3056f1bae101d24c1dbdfafce6e55ccf66cc7e5e55f55d8beab3bad77858d86676b083e087d9df720daf2264fc09d26ada2775b207401455701707b815a3690e2af5ebcd110d74ac4a6b640d85c6927a1e947122e0f572fa8f9186c1394f62332dcf2baf652f5c7b80dde4ad4b5332dd579bbf0e0f5172a9de885dd5b508fd1c7df618351bb1d0a7dc95472b0264022f8b96538bd6dc136a2cba25c508d819465ce63187545ef16d1ed748f632d6d6faa3253767b40c0613e672215fb2dc27bb4b21472210750bba419dbedd04d2e867141cd638a53f2d707fc32c60ef2f286b514a0785fa4bec30bcc4f7b44fd48c0a8f98c639dd5e72bc7a27e3aeffa6d2e2f23a21d88331657c5efb67da9b4d0c57cb86c44272431561b88a8f041541bd1b25b1495134d3dda70bdd1d1c09bd8f03d1dfdb0b96ee23fe772d8bb22fb9651efeb4ed9b8b5e3294679251594a6a11f0b3eaaec4c8b372322ee46fbd3c07e5f7e2b4f6321a1cfd043555592ba4ce8671acc48ef2ed281b5d2899ac36bc202acedcafe1ebd795618694ea862fa44acb199fcbb0e0cdd172f7878bbc360c266d6da5847df5969fbbb3b8027ec77f524180501d28b362644e0d17cb6df73ebfd6a60759975c3baba3e87d8b9e61dae076c0ea0ea46cf8fd720772890a3180aa1017a48c35ae07077df189cb1c32ba017899f71fb42dac360778e8eee3598065cd45f04105256822e290a2f65d44ccf758509e636f51662872f0b7b2f2b34b5da9a225af17ef4c4a90b88f17a5dccf090daa8585964aa738253a12a40e0bcda1b374bc118cbc71fbc85d8e0b51099488cf9489455beb23f472c5be9348ddb39974266edbf9c059cbf8f9f09bcb9764ffb05c358802673ec97222c2abb1ec735dbeca195419d86060da2445df8942980b664e55811fe1ca767225fccab1d4b852ed9d96273433321f8b43bff2db0fdfc36bce2d55bc877c8f729b3d93c0f99535d96cf8d7bab49dc26ed37caf51d5c9a8af19089d175ac1be4683892f97deff1c43be74442c83fde5ccabbce7a4c77af6b6deffdb00c281564f43a4f3109de30b5b33adcb103b94bf2e3473309124f4298240fb74741d89494db49b3f381f8b98f141a4b44b5abe6cb8bbded8970e5a4f56b1676ff4886dc272deb6145d637c2395c9efe9f1bcb3e66925a1f2123d97519f0dbc62648d327e7113db748a8e48d5af2e61dcfe56162cf718cc5010258bb51e9b306221dfc2d372cef0291c15f7901bb524acf199e9a224dcfed7ff93f3ba5da97bbf708fe19572d7864d65a3e3870cc4dd400624e70a94502dc1bd78f06cda0edb538bdade46724bcc6e2c9fabb051140023a9cd2e59b8fc2eb4c41708f76e27d270ea3356e62c8028ccde3224cd94fb3074a9fbf187c87fc458f38f4e5ee587d6db1f22422072d07964aee0a28996372b84445a38c7a5e2503b23e5a1dd2a1a0f92d2b2bee472aa35077e9e8d52db60ac238812c4f4d62cd197e81adb8214ec5ed52344edc6722c67d90e6834062a248614d6bd6ceffa524adfe30225c65764f1eb074bae714af2409aaca7312979bbb883a2532b7b80b3db874acf3c95c554236ed0110e2551701b5e5635c29e4e0590bf04231af06b2859941d02f3a5b4222ca60ce88ec2724b45c3c6f6136d7731f11543dd508cc75b60d72d5190d944c6e61de51918ce72df271dda6b023a89587a15223a97599bf7be370f5b54702c035a61067c099272d9a97e8e409052eb895732fb7241f441eb1a198cab7133a67e2fb3a14060b53735e12c9767d169f719daad5e3157dc4bbe1c1ba26b0c698f328382967794dc1fb22e181de68199e657fd7ad6a75f7faa14648f3ef0f30050b4ec244d9534f52f977110dbfef85119ea70b632582adf3f711d4c892ed1d97d4669c85abfb6e133569631fe386251c4f9e9471056dce3cfdee8a0763b0d62d55b9969a67e5c5d72cd61e4fb8288632bdb8d5cdd43699beb6755c3381bafa78edb33a5a9356049185716b3d8ad923de4a12abc6a4db23ce7e99f9ed721b821519f7eb837aebb1e6d0adf714c2abea2f6a2b0d70d81313097900fc17b6ce7c275110d7d66930fb97232413a0a41056948db79dff33c6bb3bc1375f6a65d25a1bdca1dae4729799c726bcc0ab3932f82f3fe11058d8c58c752aec80a7cd15df2f933ee200d29efb14a01264b4d72c64ffd07d47c92b835ef1c3a90696e74be54925d63bb87e303381ae81ed1e3044829c91b54809945a3b4222b38fce73b88fef75dd27d78e7e7a87219d96c01c9ba7be62f5c43d5bfd8b74505fe242ef3362434b5533da75476b572e41b280ad84ca8345daaad166a4dd717608beca15edef5fd1e9fd17adaa8cc7282bc3025374b8f19b513c1ce5f4080e30816c8bd22bbfc4c1cd6b84b8a182a720986122659c7175dd4344576354ee12c0b724f129b069dd600a7dc651d790c72fa43362e1e7b46678e467b61ec6a4ca7d8c1ef5044bb1a98ca6a1b239ccdc80abf66a25b82ded983dd7cd84b25f3e283832cd9fcb063722870991e7c8c4f142151af1f534d1b42068488352419cd0575831531fb24fbb736ba089781908012728b9a0c0dc61a8e18157ae488cc0f2a5fb366b69d8489859a6f3d0eba7881e41d556910e601ba06e35ca488089095b08dbf367302c9c1c781bb18dafacb4f3d620ef8a5c32cf0afa925b7b8d8cc4dd74e483f18c0a45195e0eac0870c3e00bc72634d7e5665c46879cfef43753afa2c22ec4db77eac2a5dbf03099dc820a1b67284d2f6a56f9df06e3cfef8d0bb092156eb46dbc2a6c47d688e755efabeb6676882af3a19bc053e85a44ec555f598b07df020a5a6b270e945df9e42cbb0402c7238b8c03d17531feace09105fac3fa7026e2854551f803d60b92320e7b6549672a6c9cd31752abba0072d466ab0e468a59b450b58cff47337bacaeb0635c137667b382d5a928e98b86f9d281b6fbfb5236fedb6f6087341815bbeccc3750e836dcfa2875d188610f21c29dd2bd8fe31895dcacf93dd802e8cbb8ef394a64a2358e749e03eae556e210c485a615b8b35ddfbe88800d9dfa3730b4c4e81ddf41b27239927f24fed04a7d325a6425c0197681349068e21486c6082b376df71986c72a0e4dedfb8d06f4701dd2b2d8af09b167e880c047d44d3f4f1b6c936edb36a72fb65457afd0f13e4134696dc2600468090e96c714f5c1dce74ab2b3111b8f81589b8740bd6ad2b74e2d16729a055e56751317aa4d480a93a81c5e124c382e758c153e4a3fa75280e4e7c301eaa05f6b213ffa1823a7c56a04953b11e2935a572b1564ed27e7c809487b1a216a0b0bd9af8893268bc9725bb8522a827c15fdc725c4515f03c131d8f2e86d29de1d58ec88a2633abedeab21834ca1635ac790a2d17d63fdf8925799e3441c458ce6288eb63ecb54cbf10b1f89f4998c3b9958d72ce127ab3eb6d70ef4dbb8abfc08b5ad6daaacae3a4ed08dd58042c214c494d1a9a1aeb7785599095bdf688a7a694a8064743893a44150dc8dcd26817c8777e1bbb456b17e84af71bcf49c5fc325c0a025f257ec59936354512a48c65b0746d727ec8195c71704776e5294ae8a0aa8ad23c58d51a2b470b71090e05626ddead0ac2ea05f23ec6c782311f13a1109ad39796081d37675ffe69d783daefe3021b56e16d69725db7ec157619fe9f41d27b77169fa4e5b4c3f4d18414ac14be2e4f7205a783188c57df49f3f3dcb23af33b87ac8bc9255801d3080450a854b9f43c2a1fbcf929f69086d636300e99bc1405d4300a4b2fab560bcc04b094800fea5972e9ce3d550fb0913989fa2b52321a9f23df513200f5c421d71f4ad660da7f4272613ae808594001b370e429519c51e82dc862c31662a1a8f19c8c81276dcf1615e12e5bd575dd019daa54f363b14d9dbd138ff4e61e4fe438107c2432da786f49bad9e6ef959be53373f9ab4d12ea5415c8650820d71611ec3b3cbe24c3d89c7292bf9679e554334dbc19f2740f31ec0d05ae635f2526e1f1e36fd844ad202672bbe00c7bb0c75be1fcef7202ce3758caf91c9d2b3e8648dbf7c39f4a76ba996439739a5b4afe6b9bd83fd7c2037d29f81adc82dc469b06dd8d27a433cffa8372171cbae38008ffd2d1e7deabccc66aba63affcaa1c9477f5446ed37b2a029f00be27a38c9d6d12b66f1f97e834929c76c99a8435a9781ba29f986742a5b5435f57e3f6547d492d003c266b63627f796481b2e65e0af7ddb5607ce6e2eca69872ae437ac9135342f493e7dcbdb47c67147820331844a13b93c60ce98c4dedc713a010c22949e2e9c87d65f0a36d0b1259d1eb0b58f19e8b78d946ae8dda8f867293d3765d5be7e551083df83dee15a02dbe05dffacabcb1f205df172d24431c46547cc851585307c959c98b76a3f7097fd7939c754e27e3298f7c26597c9d1266badfc5e34f9441870f9c2d04004eb684e52ead0c5ec225686e1f7c6de35e1a7218b094e413587837be2cf9cc5cb4f4b8d8c6026f711871790690245022fe4c0ea831ec538524bcaa728ad16bf84d4b8060819815c5c0193b28474ab9c9bd1972d36ffb256fa61579ed401810c17415cbc0cd0898f5a6c65fcc8c7e695e065753c67f28712d9bd40355933cc65cdf7466c0f27303dfce438f23332203dc7f7c7280cbea423eebb426f4cb9f63dc59e2cb724215d0ea06386b965300ec757e6d3db6014e8355acb9844e1661dcf3ad8f7a66122d13f123ef3460d4178cdbd843186a2741f46c74b9401b807c4aff99918cbfe7bd29978816e1c012a5131305c472b925d7958b769366e872fa52e06f2c2e0280ac90bbee71f968fb78dd8a9eaf726f6229e4569fed52c2e19183f0ae7957cfcbdcca46e42d70e76c32968fd1467250738c9246ec9a292d8cafbfa4a1ff83ae2a65860dd5e4867c433f974b92cb4cdf4100d17e946c31b4c10c11a879ca66ecb6ef61a5fc0fa77f5235528e4fb639155d8982628325f18524cf83c91502514a9870e9658f9fa09983772bd0c304727ff49cb9ca6480dc7133b4595411c7aa85c46fe1141962b78e6a8c4332d8ce1de17d63248f54016192a059c11e5dc38da1dc1f605a3915357535e6566975107239bda6733b8cb3f14119efb7977082f1a2d0d758f34e1042520013d66168ba0e7701088812392e774af3ac6ad2095f8475e261c03eddb1d8313cb25fbc160a72d196d5fbf435d9d4aed782dd08ea60eb7072f6b94304c738dcbb18b7f36c162a945db45ac44d1b25dcd7c03e3c60ac9b3dc21271e64eff68734deb7009848b181fff8b3f93626f920fd61cf830a99b4e35bd2bd32fbd1840bdada4172678e472bdacf219c358f45f8a11393e8e28ba7cacbe2858027b9d96c9a3101600670620789ea9cd20e6d9d2c96366e5e527dd475ef060bcb3b63b7b46dcb0fbc79d3a7259c357bd67e8d6bfa188bea1df3c36b523d2760c961ecd0159071426c7c3807254eb9c395e401c6cb767cdbb4c9966875c896344184eb4d6320f47c74dcc9672d3eb7a0ba2b631d444efeb73ad31f5c33c3bffb36dd0f7d8257f0bc36b63943fa7091a817d28ce595600f5c5695706739b1bf3cfcd0e60b4492557c25377bd729e36fa90b05ea78dcf81ad4f6c12da97dc3b8f9bc9a00811ab8cf03719fe2772f96cb7375f6c586331f42e96dce5714e0d560b16d64182fec9d0d82686db497292a4e3e316aa86915f26e00576cd7af0c0ac2d8c81fc01569f8e9cfdcdd28072a5fcbe4d4ec203913a3081728704fd19d9c00a6457114b7e014096cfe802ee21886c8a4cc56ec832467b9a6f97752c9b4a718cc7f674db4f3f9fa883afadeb72c2cfca19f6ff629883e39b3ce0da011e5e6b0057327d3f365ebb5c44ade96c278f237c23bb535b4206ce3177142c2dae9c629ad67371c10b7f6332d303288d097f33217c17616e20a801b84d6c3747a9506f211beacdead7ee1985bc89cee64d63e5b06402725e5b5d4cb265d3663ea8b13080ca709c58b3bfec34ba52db991b26a2aabfd6e86aae203d508f8feffe0fbb82fe7704486150effb7ff1e08ca5034f6758e64af74c0e97230e7eab38e55217da110e60ed820e9cadd07cb538a47291314510737b8ae459595188ceae93f58101f88095e8ff63c96c5c3f62cab844e354a27ccbba01913f472f74e1aba5be89493c127d2e4f1ed1741303d5862072477c60315a0ef5047d6b74f606e46ae60f444647ef2cc42865217c3ac873ab729f8e672a82055b4f2507add9a38334f1300222decb9853accad18b7838c7cd585252759135f514cab127e9e02d69eef604eccf40e5abf744086cb8bca940ae65c9e32171b702f7076813e20477167a65ee42954369b6a685a774d59fea960572cd7d8d5df6256fd3e26fec56a99860f1fc12423ff6db227ce77f7ac8d042d172995f9b99579e9a1c8e6695f64a61bf27135cf7cc8f65c68dc4b999caf8720f0d816cc498b1877dfc39615a72b10f1862e4aa4bb4f141636ee320bcf8196c366867cc4c88a6dc199a3216ca18e3642f662f4ac0ce51b712bf1830010d76501e3156ccc925cf8c119d1aa11eb1bb708a6b967dc4ea2789127fcbfde464af006e7297c512275186b230d55857c994709eb3b1932f6a21c731b707c1a7ca42be8d72634dc2722a17fbc995fa84ae1ef056f8ff7e9c58dc0823c1de6bd661ac99c9568a4ac43b88b1599aff6946bc7df94e43bf10bd463294959e2debf43810a32372fac7c9c0c86a05d1028059c5c28b8225f38446d683f839410ba9cb4781a74f0c0f2131464490bd0ae79195e9ee910a4d0f8888ff45948ce3368f3756ea627772e6460981704f205a5b00d8963dc779fe9d20cf47f39d3e757bd00f9464947772d4b818645a2a146bbb2b11b9b7adfa29e914edff086a1145c3e39991931a234a1bc70831dcb024050b5de9a3f09fcfeb39583c66136180af498a214fe6fa6b529616d8668efc59311e019e945d4ac12087efb95da0f3e7069740c856b1a7aa72b07f07a84de37e439b9250c1a6fb90d178a762d2add052e3c9631cbef293f37244fc2e607f464c9b5aaf0f4d2275e4dcc2b952b488f07dbd1afe4382e6277b72b64a25fcf4feee7ef2fdd3aaee74b03f29faf3d591e170747614547a2021d2726b02214d3acbd27f65006919ccd54434d9f334e132ee9d53d860e8670156b212e2e1ab1b33cd77ed12f0af9373a60e7e29e271046a5766f6523d5929c3975d72ff7ff2c3fef13915167896035267bd4c31110eac3415308816f4ed04386293635316851240baa95d836caf94598d99eb17d9db8dd94ec00e6252a049eae25730945dc6027007a31fd2d20e4df98b74c2816c5377ea96e6f705067e84fb6d2a5621bcaf8eadbe20e2577e8cc398ca9c55d1006d6f761f8dfa64e3f1bd1162b01c58c0139dfdf19c3fa004023e1e68fe96e2645e1bfa7033422c0dd864f68a4872a4dd9597b7b078cbea24fa80c49a4cd6d170093dc3a81b76ca9268d521f7e908dc2064b7261126330c0e64882bca2123441d28788a3b28ab68d6df887c5ecf42a266e88ac118e53a6a85eae2af3ec7b4b3153e4a22aa522c99b3efb5944f067234d29ce9bbf6ff8b328f40c608ab7f52de2901e86689f288a099e4cf4558c12d22a14f7b7e4bd2c176798b7184a9490c5ef5ed5f41846edf06f83c3def126d72a7d994da75b2911a2a4e2ac27edbb00aa80ef69a36700f23b7836b3a3226c8031691276c3f2673b53c8d803ba6f593a3a06147130757970d7c199df46143b77268c7e3fdc5291394a7dde62bc5fcde834c5025842e0687712fa161c41d31b67204fe89b5500d1a866c1ed6ccfd22831679ba8e842ed939281a734f790b90ba70321fc47baa6719d82b73f466d06e062b02f8acd1170324afd0f2a28815814f729e683a1b46680deb9d9c6d5c9662d8e80517905102240e284b64a56feaf8c572c9909e6cd7b1872c3143f85b01961936da2ff5eaca281e8edf395a3f2c249c7276af3f2fae8c45fcbf01cc61aeb6b693db5cbed5e3dd23f90bff8df97adb99720e22cd377711bbb224bcd393a80d8848f27a1fe401253c312124d0dbaf581f725f1923bdd102cc8c0550d77ae8b2e9c5d848a868819b75125a43f0fa7c69ac729a7fa53fbcd1ad410e36af317e528202555640518b1c758001376d5d522a9114d12b59e98cd9a675239af02552c113a627b122293dcb813334465ef11327cc6b948c6e4ad5b83b247952145c3437f2af5ea1caf6990582a34028306d604cf8724f76d3b84b5751bf0f93e0cc9bb60c6bc10ff2a9023b8cf33f852fec9a75523a39881db344ec865450ac210cd111f8424ee388cf6e0483d4854e33acdf0f4c0c386da5b7b3e1d7a92175bb04676f27cae61b11234a2e69b650724edbfb3acc7225fba61df3f5890588301d5f7308d06b89bd607faafc8f3039110dd7b616b219b647f97ffa9361c7d1642ee1d28219a85fc45649256ef267ecf6f472ab3f514ec023d72cae4d65ff4bb2fb02c453add48e93e6e87654e8f2d216fc47de03a872bd6f7d6c30e3fbb25f0237f90b1ef4fe8b8f5a16f04f3b8433a149da96564a728124f176af2ea297e20534c09bd851eaf850df313087da952312864795d2177248971b68d7b6a73f42c3e2287fa14d49b484c5ff9da3d2d94efea7ec757bcf722faa259caee8175a3d2fb4bea503003112f2f45306904b1fa653f618ea795e72ee4ab28b88b5cf8c27c126586137a5da334a60b8204f1fb71e08eb94ebe3b072780d56ce0a3d3a050693f67401819437a64793d71789f1ff07e6f5122aae300851a24217db62b3a8287f5df945c12e97cd0b5a1ba8ff19620fb0dfcbf9c9b84e9b2177c1e219321268e39e3fe0cd5553ef834bf028ec7955a691094cdaa68d08761e48e223e96099a9992415e82fc0bad68867a2b1d8b7f482fd839f66abb145d2b7332425ea6925425fe74f5018e385c87ae6949949c1fff20640230379cd6f07088d6a1e12a7777509bb1f6378bc44efbd8efa3c1fe1ae424c7898d5f6087285bd1da6eb97fac64c19b3c326c10dee5df2b3daec572da4c644e57a5487797240045aa96f6294680774e344e0592e2055facab1ae252d935ca6c02579b14f72a0a03a531389a76db97affdd13a5b2807e67b96f06a1edc3902650a0bf7009724d65e519d34635e4ccde1e50bf8f774cb5791b231f31b1e52cf7df5176a7447250128a2ef0413df0d3a318fc1c9d396f77fee47686b2ea8167528913a8f427720d936e03a62d8bc53087f8edd1b8ed714bd1de60736803f584b32aadc1cf3804358431853310cf7f1da4f9e1c1941b4694ca6c6b0ebe90dedb380afa908ae5729cb29072fc1d40fcf930c6cd3cac35fa91caedf34855af74c03ba5b4bcbf08722ed3e3e7816dc595df9f63a20687978ae9b305ab5963b2b971414b6e2fe9dc7231961d6a59d5817b02db4742e3416d29c2abd7ee7f242bdd29068ce249b85172704d673df4587878e069420d2cf7f33fe5a2b82522448655d89e37ac5821f04e091c28aa95bc773cc51b9b2b3c85521aaa28590a9f50ccec673a3166b396bf6bc8358fa19b25d22b7a11577865ba87fc6b6e3c20914f380de5820990262f337257834dcd15502ea0e5423aad58493c6a4ae2ce6af48cf28a756553c39dbf00723e4992a43a4100e29f84544e0dd5e0ee53a6d85bb7e2fde34756f15f5819e3728b63384b8c7522a04e3a04b3c1f14ade127884785a2a1c7e4f0c77321bec24530837d7c511fadb338ec8f07240d62ad7020a1e3ffa27243e181fde94d4a1760001576aaad715fe3eb99b34429546c1aad2b003cd01f27552ec4b1fd5fe73fb578fc81b5049be7ede47244d586ac75fbffdc98ed66bd5d8b7e9c590b10ef3b63aa6a1679e1fb5d39d3a77ed6efa59079040e6c5219e2a2bbc224e9bb6a73c7c72adb45607b695f3e54a0f802f7f4a86770ca555700084c1c871f1bae1da0a4432ac1dd3a7b486c832a3bdbd2d9a1319f6b71cbf51f64a625ef37b0a1d470fbd5f1b3525a77d46eb4418cced8a651dd5be45a97b0e01d0b03247e7df815fe88672558a7a59008be03dafe596ed7258e52ca14c2d2a3e6e40326b76ab92dbe5dd725dfbfdfe7e6b26434265310dc2a07b89d6e9d65fc3072f7e7aba17d1eba30f72411794fecb0e78b1021f9f4856e76a28fec2dd9d66b8aede18bee682350e1e5876badda5bf12bc5c6ebe593e42f2e77c53f8707a05db4a2c12dc3526c37d106ae0b2615738331cc83dffe566abecb03754f68fccc2acd88cfcf4d291776ce24e971bf9449c44207b724a74e7500d93d6b97b3af6091a50ae1b01d05117cb87724b17781b08b7d93e963ce0b87fa17a411b2f210e8ed2981c3c91a8c439f9a3596298a7af992004c9bdf04579edb58fb875c49d6c8ab9c2058b16e834459ae272278a6ebdd42e44d06640fd0f1f790cb13ef9d9d399082b81a8a5a0550abc4651fb511aa91712f3a269b865b39091de242e7d335a16ccd31b9ae883da180267728a85004316425efb6e78a2ab94c3c9697d3eb0e362f8284f1b25596628565372f08891e0ec3741b51ef8a1ea80f45303f3068c76a47dab729c43c2f061936c724ae766faa215592adb2108b288629749184e1d9a505b7b806828a89473c9cf72fbb4c2043d544c83fece035fa9bc24e1feff5d3cfea42581f3787fadfaa5fb722d9d51228e68ed02a4ba9fa6dae8bca280aa170ff908a961a80a02b899b18c72bc86d6db6838b42294c40fd998a4464f21a27f6004288a27326657dfc8483d13c96025ddce9c5206aaf3d60246b5a933de35ce0562e9bacfb4b0cdc1dea6b152ef19ed9498078df81f2f759bf4c67d9b890ff7ea30c5c6dd33713ee65c591272df870ef746080cba80d946352fe8668b0cdab6fcf6af907d9dbec01d9e88507271a63983e092b3fe7174447194b2a6ed1d02fce11fde88875d2d2282834e5e7248952ed1bacafe66a4a5141eaf3fcddde0b021493a211ccefbf93bb0f8953872648206cf7556f3bbb4db497febbcd1c692353656fb92fb25e86d9acea50bb972ec59141f89a9b26a6cbecf1818193fb87415cfcd8fd332252bc36b440db68872fee92a06fffa77a778cc5940530a5e315ce51ed6d203eeec9f659ac6b68cb05efc23596462068b2837c00c1dfc07b744132aca477518a849746a1a7ac95cfc724355acaa3c54593a7a2d20b844cb5dffe6facc988c19d47dbadd7bdfe9601c724f5a20bccdcd9e675c99c6b74b3efea8cfe4f970083a9eb75095cf5f0e761272262ad643467c682da1d8252c174e70e1f03fc0604a42a4fe9a0507b7924d7e7273279fd45256f74e1b5c67c234a1e2e168ff71ca1d2625d2d42e7373a64b0972c012cce20b900a49ff1a814c87a124d055395c66a0646e927b06988a8c099d72270c0235417ce4c36fdf1504bb353e59355b527a276a95a8e78cedd6a25f5b2b096f38fe7b84fa5e522a9b38a357d594a2b43ebfe71896b06de16a16e6c782621e33ade5b0d292f7b7a7282c05141ffa534174d5162ed91b4a55b7ac501ed57200910aab1072631ad7ab00f18359b544524a4aa0037e5975f958b86dae997772425b4d21f6c23372a64e8e85fd778f3a616413406521ac9653222d48de3d100a815320aa44f1f04252434af7a718be420a0f5e6678aeea117e998f486b74c5721b22b615c9e3bb4124c70aeade43bb1e834e48733a10ff0173a7e6be50c28f725cbbb029a4be8939a9e1699ca44289e40b6890f4295048d3d40d122db05a7e345b1828c27e5ae63fbb074cfc1cc400d9cd9090fe4a8e4733e7f5f17e291d394e46a019200dd2ec851d9d9ecd0ebe85faeb5ef26ceed9bcbcf224d57d80240c0c1cc9463c5c3db696f8da11a65597b6d2e611a00ccf64d01f8b2e6db97555675c5a05f3079111edea30ba8811b8a7f32599bcfe2eae070273239c22865750816d66d3ab274173bebc715f8d8fcfb1587ea436b010ed0491b196c4e64bd2b2bf72794dbe35ad9847e9eee880cc3c0674bd64f0dba30c417621407563483b8bf17282f934215c6678674eb4e9b294407d4913dd6f00d04ad6c592588be5405fb403e63d95c68e4641d358c3e9f2cb3420e604250ef3d0e39f987b07554846ea7c6eba4fb0bb6cad5282f79c8d14e3838a063a4712daca5acae893d6c90888da4a723c43b0270bf476cc9b37a2318dd6441c87858f7e550a8a150f4d7043b2f5fd575e4b1744e725a1807bf4fe4a451d1535d8c73b6f6b7e6990e2d819d2b57b4f72e6d3c86f75ed2f099d9035b46a6d5b5b664bad5c479388c2611ff7b805b93639a1d0d0f76de34b7d5891c56a8b4d432dd6c935f7cf2467b5d3858916c2d7ce24b0bcf307c9691e9334c01b463f819b93bd44597e97f0dfec0d03e0ba41535420fb1dca8119bb9b1f5f1871942e87244bd56a141e86e927e67b23163680ffcf58c98e777241301acdbb9e8708b33d52b1077c91dfabf7e11180acb7578e89eb72024dc1d826c835e4ea5c4c21e94567b16630eac3abee572a97408c1b0913be5abed3723c2651c2bc7b0300d377f4492482bbc5b7c753f10cb012fc1eb09dac7211183378bc0caa97debbf327d2c36f367185a01f84ee638802a77adb139fd972fce1724b4bb1294e131018fb83c65245ebccc71d69717903c1009db6e3fcea7244b82d2152b8c0770c6a42b823eb3f491ed120e25053a153731c10761cec0672b2e6e716432df78249271f4ce664128d35f2d88e1ed0a714a50b23bed2be9e3b1094680a14142b005efbdeb463dee6256cad73102b2432c0285b9a5ee50868466a7bea9ee24a161780e6e82a14e6337d6a298e510f17c1080ef1219b07e816712b411e69378dd3484770a7bd6d79b1d832b87108ae04ea2c009a3eb28edf9372805337c44c077962da8c14f8bfdc5abb00166905a95a5f45e23144201e64487267544d72b84ac05e3bd5051db2f50c09b1c804c0b4b681599729a2bc1a087355468d00f5595fbad0f653518082ce41a994a70da628476594377f181d33837472bd66fbe8de06138f077afd157c4d2778effaf754c0ed7e56234240e59c7cba3f90596c8d39b7a2f1c23c2ee1856d62a4ec3b839524b7431d7e4220f40e7bc072018282668fc2f46fea8995f15677bc82c964de96aab7e72684fe2f4cae40d472395ea87e1ade3bcae5c5d673e3b2c05859e11d6ab041739bedc72e636879b7086ce0a9b19bd9786057cd540e148456dbf96fe083a912dc2c6fbaec8d1aa83e7263b574218d4324f54973a21e115fa8562d96efbcd05fdd43ec5e24d94ef68c72af05ad823ec580e9d9b5ef359606492d452b97eb63f8f2330a2c29fb25feb472d9108073c50228dda678564d7f81331e318d6c873b6cf19bbf37b3ab67fbd60eefb8566223d1ba63bee394bf9316492a68a7e0dbf6e81032dd43df2e9fae53085fc7822ba7ca032a0b521c3525ac2ba8aae06c9863f500c7303bd5aa0e27f949a6ed955381fd6e30512f90bcd33e3ecb4f2171c9ec5664dc15e400ab46ea5b722a2ad059774328a3c72c57ae19cb1bed6ae5cf4a2f83fc32619b3f33843ffb72ecf82e8bc6cae2c6680911986919e36abebbaf81b4c24266decd7f79439f1372e865f38afc02a8e9a55995024d9cd65d912f7b197be57a64c33f31ede8a33e7251a94cfc76cd3a760c5f7f27d6081123aa21600749b76470431012e5330d9e7222d2670a21ed06e698386c17741490209b23f702de7b4773f6259f8d7b65c272e7e1b85c5fd44f9ea3aaa7f3dd1b869fe3b67945fa65766f77459f6243d37a72643703e631c4766729fa02f626a09eaeb6c2c65a5043e34c7c413503fb2f327299839e3269b191afb1097969e801c1af536e2c5358df78582e452bf377586f65127bac1719f314390caa27e5e71d6e9fddb3eac3d8218f8492234b02022f3002b2e1dd692bced65e6e6b2d4b7ac31dea542851fd264a0df95b242e9329a7c672b8f8fd1af29913b03a4c4e8cf0fa528d9f05317b3d544b2aa7235d17ed3350726a0fcd9fc43be5b330dba9fc48ccf7d137610487cd96a1af206b96ac40084572deb8f5193d7194522aca36b68e4831275d07cee3911a3c79730501655fe8c0723d3a34d08fc2a7953c992df0583d04f585e45fdbf46ce261e530bd934cc82672e2289491b89d75062b6d3d382f8e7c8d5434c5e47950c1d8f5f1978f58e8b572c7ab3a52636e31d1e65ed8c644f78db89150e5c51078ad1f17c1e76a856bc77243de7973ab836820152e83b4f4e4a8acf70360a82089372d779ee9b4d1319572b4403f75afc81b27e2906661ef37ed3a9c4a1d6fa7f66213fbefd32dff33d0677805ec260611a75fac9b7296a2d7cb16db42c760246766827e9d6093d9958572b5cf9982d2c1f33c4871887ae95d836b8da3d7154cbe43757f8aa5e15ac84e72cea354b4968731df7929140892119109c4a778cf50d83bbc111cc83b45d080722b0c3675179263ef5bfb247b7e34907af8d87a38a6cb7e1a4e17091af13a9672a13ee86890fab92ef5620619f988be8982c5de1f53e2d0e67f14de4031edc84588efc57922cb81e0f8e760c14b7ea497d23ec97f22e8926418f83d6807e4675af0366273147f63c501062db6a414bc33e860b460fb130ab3029a04f7e902ad72d867fbafaa1fcfc3edd832383a3de8371b3a5f24c27195832f5f6bfce599e672294e9fee2ea68bb3c46083c165ed92af6a9aee239a367ab62165cde485a845728a44cab7cc9a419e8d77f304acbe64c49e13fa67758e02484f37d01d6188d77243def1c1981e87e75422cbf64c832c129b2798de7d7f054bc889a79339442572f2a3c8ceea63892e269c1fa2e83106c09e609d230f4a196f24a07ee0bbe8fa72b944c42198bdd66ef866faf5c6f2925137809ddf2f3b4ddfe28be1fd739a0d5b9488447fdd5e5ccec3cbacf2a6016687481c5ce74ad9ec558c2af019a6262131b45623d04de100175c990bbd9f04f2b8d696a1d818e5c001098468562e6e037241c16e356ce6309f7e957f30dc4adfd0b902e785b6cfe1fdf6a8ed90081b571288373f5a0a190a18cf3300792e6a8c5c784ecdc88ab53feb324423dbd437ff72a97f05ca1a3594726ad2880a0f390ea4af0fc385f8ee09f927061b2eec5abe729782c19ddaadc088114505230f720ef56b1ebad8212026888b3c987327b329723b33f291f4cd66f81423a504eb88d981da91b41b0f67ac7443fefe8835f40e7257a28530b961b163986049ab24ae9f5bff0e0526149e4a2f4d9bb02c12a8875c55cdf321a9c00c9e2adbf3a76af41ec795330cfdc394c152782769ff61edff7216159d6306e42c0f7deccbe714cdecbfe9aeba349287d19200f4bbb620491972c71f2d4614fae3227c3ec67ae5b162b7dc98920beb90e945a3bb84c6fdb7cc2f87883542d267ca7e6cb7193170524e24ce3d3d71c1f4a31ff4c54c818dfdfa7201f06249ff6d4106ff4d906653da35559bd0f484d8557d98b95a00ff2f50fb173a9d3043b0ff87e85eefd5b4629171dd5c3f0909af25b8a390625d251ab626726c194e18da44292c3ffeaf82a103e4b89343360c71a56eb7236445f9383c386b6415cc8337515857ac54071877fcde0e41a2522cf94acd4ed1b32978757e8a7268e4706961fe2eb2f2a0feefc352c24cee8524d2e928f8c7a0540f0287481d39ed90f805e4e35e1318b589fcf10850163a2904427e7dbea55a3be2f86df7f172e0d0d5c334a11763b936ff3b1d22caf2d9cfd32c1aab0a98fe724f53f77f42720fe47eddc1246fef2f28d59dc916d7922604e538ff1a8e2501d6bd0ff35cce720214746aea7c6db75f292f72f517779d7be195c1061f8872eaadd0f3f4f68c723aa0452ecb0556fe82fcfc6974ce826ac149890d668572b7c6492c77741b917206bf0d6fcc2cf4b8dd27bff11bffa6801887fa0aeeecc74aff8d08d965cb8972d338d29a7742cf7a0199a512c92f2a02bcdd225b27c117e7880d274f3a06f47241e9f6e26851acc3a4cdb54cd0f446ed8a21a3616a72d5acdfa0ed15c10e06727acd40d60a1f1b6e57217aff6dea49526ce3b85bf37f727e30d72cb6cbc1567282bb08bb3cddf22d8da96790bba603fd0aa5c15c08887a5ba395f8120138244e60921236c1fcc6b473633e97279815eab58a52735f942d4cb310bb2f964e8e720661f3764493869f46cf7ae752cd080c947088c7bbac6208b8aa61b5ef11c472d9bd0ec84a2276c46c1e76b68dcc4b1ddc06ad483cc6e255f31199be7be51272de4de9bf938eb2964263d0abb8717c049f575d218a305d3494824db39b0fea7244ce80244a6d87d0ccc2f2d98bb3b52f7728280f2ff3fc4a4a637569389499342e8d77ea70b080de7b4df9962a247019a8a20b80d4e13aad047f5e98d014424adcba06dbcc1676d1a5a529b246cb4e3c0e7f0d6520eb41f70d9beae6c7bb50722c2be87c68bfc7c097252415af8083b844fa2e979d7807ebdeaaa7d19ea5ee19fbd0aaa5c710a7e10b6cb84aaf4de812264d5f1144be046ec76e89eec8495a033d5ee90d29475ba5ab5dbf8305a293daf3c18f1f8e3fae5466bb754310741c471e85c3b2d7511c564588d41e248ad7d6d40f667d9df39aeeb62eda020bd60872b0fee5bfee39f51e3d7026da2e3ce3f5b510404c42c64446850b071234b88672a0df0a903a8e6ad2a29074ff452ae07b90bdc4a30675f8832ee484c3ebbe5e720d1fda25b4c06c4a170f89e84fc378156828d80b12c54e004ec38db35506f572d943875d315e770fd54495d371925c699d1773303852607d412f7e2b172c69723cf98b01452c6d8215284ef7fb3606c87e367de435ce796a861f1c3c56234d7218553c28d14466bd29c4701a621e37dfe12fa4116c77bd65d659a87af35fb6644c33a5719d6f6182f06a231eed89f3046862360c7d39d71bdb1e74e0c664bc7298ee355993699c397e87fe74f670d8ab1cae73366a6ed762a22b5ddd955d576bf39155a2a95d53ab3cf180301b641ecd5f2ec1d33ede32d0dff8dca40f70d03fc3240b7c6f65fe9cb80c84e69ef39940c394761570ddeba915cc22e5be06aa72c17b70ad1ce43a43e50514ede755b5959d162da25ac58851a50321c04d3b1c5d1566b5a95d8db28d531d1d50f032eae74ac61e5af053e187b76d387037594127120fb55bc99f9051c938929c705d141c17f00590648b63223d376ee53b14e072a4bdcb0fd8eb5a2666815ab75cbb1e7283238499fe94033d433c60dd2048c7511e9058a6fa91d5b9f8d5eb2d2765441b67cc229effeac93fdf23f93b2c17347220d3abc610757dcd329c2a9cf36cf674ea9086d48a30049944fead0f2a33541120a1dc0f006aaa5a63067a7e80c6289ec3358a907113fbd706c8eea0f5aad80f45b3c739a53066d07ec6f567fee8dd3847cf789e4e7519da1b1e614c87bcde725bd4eb67d57fbb76c64e559bc15a2ca83414a708cd079b2aeb6566a6bd9aaa72240ddcf1891813029c5a5533e991d47d6660414d91691d1f469fbddbc3ac01722f60b17d34fee038014f78887fb617e4a993f383217a5f039bf736bb1e0d15723bffb2777aa6b3c4e3c498a2abf392c127e50722a7a7fa26802c1354b6cf1772ef721b6dc5b92d08515868baea3c54109e867de26b94c5d2f0c6df33812028722d11f12344d4597d4e26e085b44bef675d5fbf61b494d72c08993f6eaf6bad72515e5976094b1e2ef94973346bf18bfb5eaec4239dc4d8c06755133865d4894d9db587a64702b8aeccd59ec08710b079ece1250c7fc846dfd0045859f565da310d933f9c45165d8b154886af67795ba70ef7a672ae1442533bf88be2aafed27273c89d5d89a835d95d8509b36258e44efb3afcab4265aabe2ffcc1d80c6389351d5a71819f82530d5317b7eb3292a80a2ec4e10ee0f5b5bd2479137a31299572e6e68947a70c9ab6da93095a9350c37ae65b07d89316cd34b4f1ce8d6adc9672651e3558e485fd3b00422a5128da143a3496ec30844b157bd209a420be00d5728fc24e86515655eef0a5f9c245ba087656a9ec50b387397f3444196347a1db72d0e214a33ee045c62f641407044faf63612e4c9d11eb4cb21eb3b2a6b6f293725f5cd3ce7346d23f568e18531392b2d943601e331dd2c103922bce41c1075372bcd41c5b5279fbb2197e7cf3569113130a969d093af0b29b3abafe5cf834733a50ac4946e00b825e1bda2b1016984dd6257ef307203949b36cd373888ad81640ef9d1417d49625380ef2c1dc8f378ec0c7ee4183c4b26dba31de38381c6022726ff10d67203c68c7d19d6d24f3e933e570c463df8234cd1939cdba7762a84172e9ad6326940b2b095496524ac67ac0e934f22b0150ffdd65d8cd80dd7926867255f5ed963f6de093bfc737cb8870102a49f7365c976ae0d258196fa6151b9a7215dcd4b724388835fdd9aa0fb2da4230d3be7082fd30916681e80db181f8a6489d387a631d481c7372a7fd52b02677d82748149d815f159d5bfa4454d40b1b7289dae3f5a6d44581bb824cdff1c0be7a0c505bf6b094fbe39453e5ef62b5655c90dd5248f8ccdfe5b2d74a3f0a3397b863c97441844587c0561a6ec39d753f089a4d796c40709f0700cec3daca59a73956b43c1018443fb6a017d73fa565b272d6920ceeb1adc92ee198b9481ce257fb4035dd5b004d76516ca2d67bf35ec8724ec41175549b1bc3fc7ea9d66a4c4f3a01496c3518522d37f1b10dab3ead1d305d827e3edb20d3975b30c436370e5aee8fd94582affdcb819f7dfce8b0ea674f4ca2c7ddb58e541249871508b0f685619f03426d3a0c1c53315ab795a9c85572fc5240b77a280ed0c0c1e21817967637fa94bcb773b2405e3aa73f42f150a572d31a1dee85723d01ea28a0db6c161072747564fb89943689643117bc8a167072f89c3809374613f0a330e69e45da0ded5c057de484964e1916d4381b007b900a8624c374d98fc16847e8907ffa6dc29abc93082e92760fd2356166b192959d7234b3f92bd1f254584e61a6b442bbed71a8cbccd0f8a84fd9bd768e228eef080eb234e8a55d678a6291f81880f9b675a1e1b93fd2f96e41b528a721686587ce72b027d86f5d726a54f5dfba74559672110afce6442240909cdab683319534d0728fd9d47d0678df044829127800637d505747ec3ada51e36cc239ea059d16ea722eb1af46a211a381645830020318e4b9d164b614b9f3efa29c0d2cc7d75cf672a15f616acacfb7ee465e68f072815e2c89a74505c3537c01aab5e7689ca1d07202d6a4cf0e2efe75129e9dbc52958c78e313ffd8094c23bf260a879b652177720678c84308ede286d0ba896dd2e2f6287c6926016a9a9e7cc708eb8e32c2cf72b21c1ae1f92e9427666b19d447085b0371ec06821abbd8f18f21f02bf92745721c84fb7586f7ef85469769e6f1fdc3d47aeda1ad5a10964c47751947f390ec726bd7f4196146cbbd5b8ad220efecfc08d78b19ea441885cb5a826bf7fa47c86cd1de23a996a2b5370e13e9049bfdfc9f2e505b896f2513ad8aead4d464f3cd5faeb5a0f60a1440c1bec583e2ce760d9a34194d8546af3ddada7ca798bc5627729cca1af38f48bfaf8553a42c6c0955987c92aa6fd7932d2a256fe4e50b3b0b728e98cf735255b73aa0dcbc85d15e2b7c8ded2720ea4003952c7106b4cc6ead7236587748cb86754d812170af57b5749b9061a187e59b0ecc6b8448c136c19f7267f8d354cdf037ec62e92eb4d4b253a3d915f41ac36a99aa62fb9e143af0e56e060686a87a0aec2a94dabaa35a0cd6fdf2552133f99f5b4b569a8262d4467d37c2993a07454cfeed18c13f1fb6ad11554744c6f8311893c208bfc287d81cee50c19f0cd4abd836a19a4f261251ff15b9376e525fb8ec61429b5fef8ff2c41a72b44e4a81284a2ef8a414dd81ec57f905244801778faca2cc5211a74442afc3728065560ad91a67cae751c6e745ea3bdfe5b872def1dc46064ef66d4366fa230d8e9caa0bf482ec41b6307176735ae8de776d515180c0a88dbd7b391c1781ce03386cb2127bedf5c7fda343e31623500cbc7624fd3c9cd586632317b7be6fcd3da14e8e149f0b15dbe72f746762c8075dc63c26bb8ef0415094395c59470ef572ec24dbb05baaaa7477a9e751a0817e27a962256e163f3cf221c8a841a7e40d49b03b9fa12be5cd4e63bb1898b12b7998807a0921702ca5533a6a76740ce0092fab0e2147a13196257547d6fd9b18bdabc1cd2f3e731bcae7e338efb930118a5ab2273e38753c8eab5396df0f43ce2f4a7afd00da93072d701732999e335bcf510dfbb22c641e0567814546ac1f98aba840a65b94ff927ed2043bbc95a1c2ba72a625f80ed90e9c7fa763acabaa19db00e9903d0fc0fa2db53af2da69323d7e72f5d6fa94409cf009b9f3c157a1633fadae3b8067ffe15bb7a6d4cdfe9429a066c192d6f473ee7df9d064237269c8204350934847069ad9b67307206fa1b2317276b52d2dd02cc150b089d1cb5cbd8048beeb98facd24d9b5ffef8beaff24f672aa1ec9e801386c994ea9389e8fc823e9ca858a37afd1d4d253a641b2bdbe00721bb23ad024338277c0db4cb5a553055c846d20813c34fa7a8f9206d44908171ca78c37044baba332a62f62179d2fa5268bcbb5a69564dc8192ad12304a77211dbb21c63c0413d3941aa1b6cde780a053ee798d5b5fa04bce8b09ac22579dac7232e971af9d79f3bd1b19931b23783b40e4a12d9c86b8f651ba7e1b7e6f99b637278a7f311974cf485e2cf34f640e7403a051f89138162771e344389879be9908472e5fbda6eda47beb7d157b66a50b8e46476e3d5ae96c9fcb6fb9290222c372fe9febf0d82c6082abd217623b8dd8d1efacd749c8d8eb2e5519eed523e41872dfd88d277b9c6156f0919913ec391f82b215bc2c74780dc62e0ccbf7763ebb7287a155818fe474ed42699e9805dcd09467573fb8064dd0f349d779bad66c8672bff13e5ca081653f0a3325b8d94fd8be1f37def1bc3ecc0461972c357e3dfa0fd1aec8872a3bbe5aa2ac49536b70f565a3c17d27e8a2e3353c58c84b9ccd6b72d50551260ee62dbe358d73b5da080f43ed81577e1bc5e21eb17c31958d9a8d72d75833733574d3683f4c07664fee35a555de5d2e610a0fc69bd429a744e09472ee172b428eaf173fe25e464dee03bd891834088071e6a1b32cf1fd8d55f4c948c222d005d22decd62fff231574d0efadb61f6f067fc4196948e9d8efa2d1631e4771505650372c5fed7f5f27c84ebf15e38a224c5ec98a37edca47d19979ab20ecfef4a2b0461f767e67b9eb976dd91e5e689f2deb5b20394007824403902172e7391052ddc9e0cff3d5fa9e21222ef58d8dfcf222b38f32c9a87d1307ea1972722d9bde553d72bc881c53133df7a565aff6878fcf219ef554ee85a6930c28727695f1fd4fa3e6333732786685bb118076f727c9e2f6e25988fcf40bd4972746dc8f09472e227fe687dd74101c07931c1b0b4fffb78bec0212c8c6983f5c287278c9a84af4fcfb32ead34f171a6ee885f01db5768096ce2274a4ba53ec111172aa24d29e9da96849ea2f633df90a9646e59c229610cb7a524aa2959446308f72689920a2295424eb5733a4d222d5dc1e9ebce5c62195153a72cc6f2f5688dc7266b12984617898d467733670c1136e624ebe1f185445fc2817d5711fdc2e9472fadbf8aecb87287ede99905488fed4a9233523dc4a978adad594e4ab645a1572059fe3e3090a15d055c73122dd089b673703867644780891012cc986e110c4720de93062b0cb0e70dddfef4a313b65cc61c3d2814dc20e1d6e3949f0af1feb72597e9542b9915c926472a46192c0f11c9299af938a9c5c27b032e4521c8a9b72bb8db044e8f4c8fda572edd2ce1affd2280df585a45f4fe996a226913d7df12898f4c19aea42bc14e603e8a2c17b07ba0ad20f0639309f7bd8b5e62549cc207204ee621e0400f2f93121751e68d8032d9b140c6538f48da7f27663c7eef1586b76b05e3e9f7995929e2c9792035bc07ca3e31dab23f452c681a9864af6b346723994e539212022548506ba29bc88d3b144aa127af5f38fb8b5d1352640ffa2729005c21ba3c20038edaecd03477bfb2758e62ca5158599549ca7760fca59b4729592bb849c8be8671f08c7b71cd035786a2ee55d01a6e9297c34b9d509086b726312553c391e0199dedbaef97cea054241b4869cb69bc697449d8ee5160c89700fd3164b5ffe60c9e20242a82cfb3d1384d510d360b98510bb870313ee00f772124625fed48f16f38f6eb02e8b6af8d7366842bc0354be2565c6bb1fc452fe19c8ec4266b08588357fa7d65b88fc548888fbdc773222d3c78f56785073d93e72bec78854e2a873a277db0e81acabc7cf90d989da871322c133ba8a2b1cf0f9726a7ab413c39ac19420af8103f6449ef4d46f0ec4acf734c2118682db7c2f076bc617cd61efe024c72d2b2eaf593dd424ccc3452b6bc7ec1be189e8d953efa372ee48969f30d47450e34c3e5069877000e5188eb06c7b86d59d465cac8a52e572ca38f8c8d9a07b47e76ceddc067282d52cd292f26150528b916105b20f2bc40eea0447d9e142b2a0c6f7b2acd7d85ed82b58906e1564ff6fae81674e5d1dbb5d8468079a097fca1fd328a6b53f2a5d542611af945f65008af5f6540bc5b1617215e9038b3841f1f195457b4a1f9796f5ebabe2f869d68910b83eb370b173893432f0b885284383ed403f70b92da690955b748d1236f694d5e6779d733a32535511bc24a1852f990f26a81f22ea0006a4a99c1d65743c1ec221399e267ed6f26977957cabbff289a594a29daa0e0a81be3ae08fb39267a15091bd532150e8aa64acd7fcadb0a7a2535bac3f4fcb3af1daef1bc01d910d2f6ba013a763547086725a47e924bc26b457b516a08fac37d6fc0b9a3cb911363c1506bcb063d7f43e72913014983d29d862cf7fe14fb05d1f71548478cb2f8b5f1d35c0eadf0b92bc00f001a335a1619e6e4299657e68bf8e99a80442cc1d6c9458420a2eb72ede5c72e730c98f664235881f3c57ba566570d4742bbdfd0a751d4d6dfa54b148bc2f645ede6b33db823db229061ba7d1aa06a6e1e47ab1af2b30c0ea2347f2422c4272c59771e0aa5538a766328156628d515af31328ab3542aaabeaf5ad651fe9b872315fa65cfeecc53a44449227675b09dc411e61eddb1c573d778bca81a7022f39fc45ca4fe88785403c70a98e6e2965dc141fa6be1fc8650eb94b645827e31072fa55995fff82a871d8521665eecde921e11fb6c6f506debc58267e3341ca8c5d6a48b09564e346bbceb94ebe6b481bf91032cd70971bf8018dfe4e76afaf1c082401f79182002d6a8b87ab319d3909dc79c466a52f89cded8276c0f774ed5b0bb06071083872e83767cc61a4239fbe9c9c21c360237a1e22bce88a12c4301f1a0fc9dec19a17438ccd009bace05d38d6fc3b336238dcdb80bb2bff682b517272848327672411ae4be3233efe69b7f23f44bf91e1e5c4220b69ca0d1326a8f572fb4a8a984042855fe6e0a65ef6526d49f80815642f588abbacb95a9825e83f724b8227cd92f0ffa9a64a575bd52c4bc7d9bf5f8761c707e6e89f2b4ab29f98164a0fb6db58d0f98d520bb415c17f0a24a00f9bcda10a03e85ce236e084d2397248b4b28078586c807f150d293b59294d87af436bd5e5a9f6ce271ca693a7ad721ed939884ec68f0238d42b504ac5c6c21ca8d90a6ac2d693b0312ef513532c72c97b8347e9e6edb4cf06c4d92d0f553195739943218401ed05b597d65684c272e9d4a0e9cff9d19eadcd3ffc2539e8d67ad7e7333221b4e46fafbd2ab6176a2561f0a2c8918dc60897f10358ca8b6efcd951a1efed9c6e4d6fbb3ddda3883808b38ae4e4d1e32c4eff2fbd9f62af6958e924ab9e775610dd530bc1316c3a6671ad7b27edff22e1ac672edcf769ab8e105ec1d3c88ce0f81b0e1b4201506f48247fa97bbdd18981397db8378245d558aab0eccdb02d67e134d4d179d9ba46f67281a932d6bd3f231680d3fa353eea5ed537bb77873d7b7550923967cb5ed89e72393e88e99233fab2fcd398c3e9021463a0f58fb40a126719b0acbeea568499721235b6ff816c8ddc36b3083bd1c1d82aa9e8bca4dc962afb44cb6fda6c33140134abd61040070899432e2b611721befb904a62ef10273ea440a722dd7fe57572526334b25e7e2189d63c15ced09bc124794e1455e5731a59f89ab13a167005228ac8fdf295cda0e13c6c9954d544da7071601bdf0b6d6d5bf76c29e0fe80142119c646e37e82c4561081f83e57d825c9cd2937040c096d3ae4ae8bba260a436effbee82eef5c5e4f73122f8a880016edeea8336d8e9548f5b9c04cb9f06c1572c8b5fd55f2db0c9f7f2c60f04fd534229ecb7cb7a35baac63d0251882e611901807934e0ff9c5f13612c764122fdc3ca8614b4d1eb2333a9061761a69cd5982f94b36655e823a23c044d922217c5ed032e7ae84dd92064d51733435524019f7245184490b107326fb6b9337816e25caacc302d7d2e5ce17ff803f0059955c672b21f389789a4ad94a805f82355b799f88c8bb7908e5e5e425e803d35f8831e67d729c61a83447018562039044d22e4988e78fa91e074576f0e92693686c59872016289246520f24809f13723fe50a34413b90bd3777169528adb02fa9bf8f33e536f3626e406c01b7d27c3926b734bafe93bb371bbb3056708296b2ced0b5372909eb8605a5110db76312e6dd65042ab3a253efaa8af149a8d003fbb12d6d372fc76a27ebfb4fb421356cf3fc050fc3de9454b61dbf4ee2279aa10861bd97072d580e8f1c6accc056134119bb0904bea46466db9d1e340231fdfd8fafbb35272837c6719af6897ba2cc6cff0d0ca60dd13477ff517b6e732602ea8828afcbe7221c32fc65d11c236d054b251ea130f8bcc5a830a55c90a0786ee79d716776272b752a87eb02de3781fe029cd157f2167a33df29c475143b8031a03e95e7027729760cc8241de1e1dff0ed5c28eb70fd78552d92e57960ae4d321bb1df51bf75d48004b36722cf5370a8838ea08bdcc74a4696abdfb71ca8a003b21c000612962e72134215446db8493cf020e48e8b99dae60f8d29a7ff41b51c98f59ff62b371da1335cb27e14d541323a1ba9df563be7293f5f9bd60b7d763591568106b0772006552f929253f1ae65595e42a81fbe32de6b2b1ec4985f4ca8c8192a2a3e572a3bb60601c2ffdc018472c274e8a4cd8cbb0118ab2dd8188d5423e2e73bcdf0010446578666289b9c3474cfadad21ee259cea91110089bddf9dfe6b37fc5d87243db55ed6b74e2ecff7ec115d21f0584d2adb8e9a7a4ea860e0408b24e9acf4c592380e8860a6e7416218f97d127cc41e37a627a7f39ee7479b8e6c8fcf3831588af55b5221e4c961c4dc8b1a5795d9cb0d95bd9d110818904022e58f00ffb1d5e7ca88c0389fbe315e08e08836b66004ad6c81bc18af70a269741cf479ca77204e25eea6b08738f11cccf1d492f37849fe78be282fac0118d8244f52df24f72136366d0f2ddfebba3c7b6e8fd5d7faea94de46e86ce91c07587a6cdf770877238be5c4c7806717d9c1674ac3eec88f38f59d1342c013d525ea0c1f2ae258c72679211f2168d55316e9d566622d2baa4ffbe242405e45f951f16627622c0f67219b773f9fe9a1ac8c5d0b6797fea9b9758f832f79c5bc62de76a98ed3222fa6185a687c593220c234993b4f9921b1849037f4aad62f12ff87119a6288a346072e2c085e2c650f1acc0673d3c6b1342251adab0fb5bd5331e3b8b8bddab8c0b2c5ff83a498292c74914a5dad11466b1f974bebca2474c920ab1bc08a8c869447292c67de8dc27691ef17bcd956675ff7125dd5401fa4cc67cc4e3b2b3218d64727a51bf3ac0c00e8c208a7f04ea17f162f58eb207fadd6d2a9817ce3e2eba5d1b33c2844f15d3b02491eda3c490cb21601299d616e0ee1efd9e89788d46b689114f76fe43436f8dbf0eb173756285e9793f67065530fb1c733c1e40ed15942c724686fdf93da47b8fab74c7d78953e5abae6425dca843d7e84579a1795b674472bf960b40b2204006db16c592720378188363d3988342525ff47ba67208c17972d09edffa0a79ec500b51bffb0d10b2d59fb6907b2bd0014e010f4bd46ef8d072f69caf46fd111e9c699249409ce2c4473c563e1d47f555e63f315291c86d6d720bc928da82c3bc1c673aa48fbd3b2b0736d3600daa44bbe2aed97f3243a667000669fcdf6543fb5d8862933f98f1bc229ff12b81a65cd5be1baf8ae131c1b4724d6ebc82d2b7121cca3ddec80c88813f33b451509d64624d94832361550b2c72a7b7d6cb2647e5c2af88d8782cf5540be68890fc6a97b3b42f5a6b1ee9f236334833988b8c9bcf038a3b9a4eba69e71b0d2934016f61e275d3067fcae5b6aa726077dc115b14ab2013180bace2e971f0db98f3125c4d5abfd841732120e2cb72aac148ab577692a0a95f3b709d1f1d83f86e9c5735626cae85bdd07902e3861533e1930389f70862be2a19023f24b9593f671998c0ef7a29d3322a6683bc6f72dedd36eb90de3974147efb5569b28b7d1b895ce7ad6f990dbae4455029af2744a7f4a3c1d80aeacbeef1b9513dad84deb94f603fb570f5668bd81977f44d6f6a03c50ee320d9cd3bee12e0f998797da850c12d0ec5072903ac817114430f7c43bd1eb60901ad191ef1567f7a1273de0989484646b4c7877f3e7c0d44b7ad5c5a8b96761bed59cdeab4673a0a3ff06b944d89c609a8556dd528878eba518e9f72af8503fbafb26208a716bf3b0432d54079433059d8c7c7e113d03a5c62e6c77272bea0a4d4a2c34576e7be2b01b833b3ec027f56bb7f805ac3f273564216dd72a70fdc369a4361a9ac8a03ef94ac5b90667114bc575af15c0299d5573d7aa23333583f839ab8feb595ea9e1c94fefae09a98fb3477486b097c31f4e9d4af1422b79f433a2bdf2d37df40b922700791d54f56370d35ccc8c2078fe480dba71e59986d868d419f9d6c8429c73d3eda813396b7b9f3c125bc0f13fef489edefcf72366d033aefa0688e37af276e06f38512c2cd5cbb6523487d318c1eb4af56f87295589ff5eaf526174ad1167c4f7bd23f964bc2350d07ae998479bb347ffbbc725a2e933861f7cc286b60f8b12adef50d008fd25dd0ea1052c16db0bd4922db72eee9541ffce0042a5cf1995c4b66a592045e6e48aa5cc1010b3101b440dee2721834e684a49257202aac1cfd6172648c0077f3e1457278b82c0de4c35ce0f472918c209d2d84980408f4aeca38de5591d3435cde60103dd50fb7e5e258a09072db8c41862e14868515f1d20552ca6ecdbf4d6277231352a49744fa144a3c1c4df06744d718a14ca8e3d7fa7f26f235f41538dcc77aa46adc1629228862e1b77292c3aa5e4c0cea80199dea1c3f08e512b5afc0ed2b401435b626b1e97aee8d725cc77d36b60c3e13ee0fdfecb284fe322095ad245a36d701441cc8064032fd72705645f97a34447ac4181c25508d50cb0b9700139cb3f693c2112c34294c0a72acf3cd84a3cbc0aba89d3c2aa0cc2fadde2d17a4bd78189901f22ebbe4992472a01fa90a6d01dc9cc38448c64d3a93c6d8774239f2156f13685194ac7dd76472a8775961d5ae9d01570ac56d3ae75e70259ab30147b58f561a47565c15a6df72de371970ac80847384221c2e7fff31631ea243d286c7b08fecad050e7aef2a68a8ea7522d18975c25249141a529bdefdd4201ffbbfebb26ce0924203d7f7f237d4ce7c47b9a7334b7075e6fcb4a5fdaa7fd0304c59fc72e0161e4e04f191d56103fc4d0d3af799e2f52563a06413281b79f0f4a5d059877c8cbb2b41f539277295ee6a6c10a8eea33e058c95d70b4ced4cff083324c3b1b2053f495f1293ad43b9c4fff5d44e0d2ac8889f25612cc9b780ffb9e62a7c6f4bba1359fed0163872a3656f60745211c5d9b5e14e747bbd7e1e5553a19b443133d0b9591e45799a72de68b5834d7626371869ab2d47c6da2772afac8d08d74f1b1a82347a2add8d72a5373aad061126194a99d68aff801e5bda7c2de0362de588e5d36a745fadba727b63e141614dfe3fbfafc04b5db596fee29d4388cad4477d4ba82a41ae326363686bad6671388751a6fab20f4b792fbf328459c880dfc75f89cdc44924678c72d1dae378d0780a5ffda90eff5611b81932d204f04aa40115b94be5b2e2624f729b3dc5c926090c617f05a5406455643a9c615b6ff2baf3ca67c915be57421b723a399a43bbbec082bd33d884eda5ceb5592329688173b0de8afea5d06427c12f6f877f885bae9b93621d8ff199739c9eb1ec1103b1652c11a6f9abe5740b0672c1c4e9228bf9cc3ca1795c6526f99e556f626b8d9b7c69ff464a4f90afdd075004a65e66fa38c97f50f2e8757f49dac366aefc8aa8bb147c7d99a6802fa4c634f47df0d6036a1d25bd496340abc0de400062010605e46bfd32d3a67aee653772c9772cd2581f9609a3eb964986fd5459666d9b3e9433f142d94637b649e34f39af7f2a48008bd1488a7d41a80e7b721bcf505f916bf613e34ce24c37e15a9f1d2f6b2cab45191369295b16855c6e9c3504f19a88cb6d825783c18d220bd873726e4b5abfc98122fe418a642a2826f13c4077243edd673d4435afc704fa32fc659c9e3f074a4fa52c77bfe4f994cf8b047ef3e21dcc81177d142603991dcbf807360422534b023b65966e3ff2f319026d791a1c27a9564c31d56d06b41682df725938333a9f04eed0c33e8fb214f4131cc28ac29326126a47eff837ee2d1427723c0c8c89ca567dba790e0b1dfcda4534fc7e563ac34797bcd6ab5e25ddf855725c6697cd167bd752727c19ebc855a46f8cb18a479c163ee313f3f6ee63ba521926f893e977bbf55a7e76c91135b363023ac67e44291ea566b32ec408ab47d434e096ff97bcb62512eba140145616272edaab5a4acf3dad0a4e7dcb3409765372a565e91b508a22091ed38f9f3540c691b570b9120f785794b91c8d089fad3c72ebfc5ed63d0e8cc8a68b3aa5c8e79f98f96dfeecaa4f172e9a94f43d50fe20726850376bc58bb7a594db85155447bd2be6e6879bb464ab78fa1d4e39ba927741abe81392b2487f6451c695507d74990da57b149c3cefe7fa004edb2b0eda603016715a61b21e6bfed9093c3949e812716acafbe3209982063ffadd15c3d731722b35e0aea9451d1155404a67a79c6dac8851a8ade364b2272a44a8cc27da130aa89a18be3dbd127975f3a7617fb7d6b8fa1e11a8349f3c6f3168a63bbf3ccd722320f732611977dadb2e5ca3460f47ca5a9340358f2ed251b526a1837c0de504f329ad882d4d9fb25f633c86bb828351c6f8278c4a2cfc38acc4be0fe1fd64727c730d6e9fd350548833bff616c7db46b0878a1484b24db5cb25ed9021ae1072098af226e795f3e5ef400d4a274321773bf16b9cc432e5d11444ef43bb74717290a0642ba757681cdd754f7d541379182091943a059c48e90a90e6a32d36c372c0c239290f848fb56fdda13a063a7cc647701152f54e7fae28f38081fc0176407c5299e3a1dde97e0cae49832eb458133c827b2ab9baa1f08547dd165e7d4c7276e5ba52a81c13ac2487ce5ed8bbff2222af4bcd4d4829afbafa866e2fa16472b2a0ae867509ce0b527f46d4c4054465ee08f61b3009403a2d354b425ec9ee72723843582cf360317fdd7eec1e1aa687f0b9095550eef5e95bf272a0cd4953260a934d66f17fbf3bc8447c2b5a8eb66a309c30261f2609ad59eaaa79226a3b70883e6a0027f366958e68138dd26da64c7fa6a87c3b716e9a192faaac77b40f72ae04df3d61be9af15d354458934299af528316daa8fc973156cb228fe69d777244f712d56b287bb3f5efc497f9fac29da58cd158b8232b822fb310d52698a172a6764e6dd4337cd3fefb06664e8ca868d1be48d720c2f9ced9666608c6bce4252b7bb354f7966032bb407948d4866edbbccfa3fbc7b2d720a359f362b89e31724cf6f9b6635fd187ac14cbc9f2e709cfc1ae6657205a78c068a9943b6bf92b1a1be4ca76e065597bf2ba6d843e0ce69abe3d55f930637651c0cfa54f4174497251fdb96eb21d30f4ab04438162c81fa0ed3fdc5f5f1bb1d0a83f181cf6126e72319a4f7e2e6f8cb8f53fa116712b00dae25f6aea89551c213739e1c562420372114f7e2debb7f122842296b62b9b1926303777666629252ee45523d4f9a61916140ad0977fb13f80bb540e80d6ad687655714e312ef943904be0144d6ed23972e948b0bc7bbb000a68293d56a75140c1066c31fc0c7cc01fd2fb2001915ad97238ef31190a4bb6444e91e1a569da5aae0e9c46a6db42b89d54879068ab3a1d721f183cdee10c1dedce788024a06c66bfef8dabbb06a130a18b095d036b6cec72a9e9eeede38c9f6ad5fe59782d9263f7fd925e86bd556cd6fc9d118f494b767277ae34e67fcd452d8f45bb4244cdb21e8a0c0d9a2e635b1fa33533cff4484307941442585ac8661a7aa328aa7fbe935092f20945fd8af3ff8cfff24197fb9e724fa0319dd7ae5720f11cdccc29b592e516d38c895bd63f73c4ba696efe86a172556543b6cd289f50bf89a6d1d043324c48df2149cf0bcf9a454eceafd767ac057b4b315137a9414c090ca9283d166f1f18fd5f471cf7dde291e4fe028a52991404506fb6d052f0292d9a3c8af2d10acc94678874a3436bccedc3fe7dd705e327cf3efa3ac45037cf989c81600512f36d12427298afb75f7c091000344979f9727a21d924f172a5c983cfdc6640fb1a68ad492cd0920e5f7700515ea6bf5a3d72f3a588528bb273d40f02400509b301eea4b59fce460b87b1b54f48095a7ba272616228364cbf7b15b9123df0f029db94fbe6a39684f1cd59d18f78af020bdb53fca00a680e58705e6282dffeb32867b4af33936fff7696d473b07a0f3cfbf83551040172c67a75da83dba83f1631a392346d6229b640d72fe7cfadf79e439972bd12d98d2e51f863d89fb422cf949e675d5384464bcc7c562a350243cdb1455e6e7b9b69763a77a0fe288f174823504337bf4feb3a80381657d470ee2203a46b11e4bcf33e3fddeea8d8adf9465c9cf0b60fd7299c3ec1ba998290f58d4f3972e3cc63c8f13bae57cd95a2855e3da8a32ee749d2242e67fe08038815518a674a7d9d3396fe9755cc8b695056f44aee1ce77cd51060bff4979a549c2ff8c250048adf1337c522e1a0571cafb4435e463f0b39117b5090c44e8e73e702a7380420ad4d41cbbe4ed50040bb76de20c984c6998716b84ebdc41a3008627f04c5c572af3c22d31af97ce28472aed73ddbde1cb6e50dc0d473b9d6904e7cb729baa263a7006b0d8e84c3c30794a8b02213b5ac25bb88e02aeba31fcea1b1822d550555958dda8ae6bb869d3e8f7ff6cd1fd123e465a643c14ef2e42c377b80c52ceb704d081b669f6ac35d919cd8ec68395991cc486409df3dec04c5c6767d2acd2619e29515fba9afee9322b9a6e3268990bb56df8a7d8d9f52a7480d685cbcdf0c54447d071c045f436d91cbd9d6cf9e5c23ee0ca6af9f0ffb30f6adf28a9e780b7218af68b97ee9a563d82618369a4baf0e4c32570eaf3208f21d8a904128231e1f9dc0544e0121a821a65433abf771826c68276c8001bd7909632dccf4dceaff72b8edcf22825816a1b9011412a610cb5ce37c2b2cdcb91da1027fde369c8588513c761237b919cfab8af5ee632eaabb3b8c07d6af4d691395940676aa3f0709073d854a08337106cc49629a94c7190e443b6d6d3f4d0e388ed5889740b6905b728a68920fa95b9d4007f34e67a28627d121b447e5e776c8bc5729208a772810407142f02a8aa6823a39aab26beb08146951073ba4f8d3b9525c0cdb9c57897f720556adff5cb13ec737c4db662b63cc43077ee04472de587d9eb8f5b7ee485b1a3a41686e36830f22eb7f5cd3b76f210f13e6466a92699549effe3a4de19217291562f8f19e658cb6a817277150ffd59fc8f157b313a78a0efc3d908376ef4e726e1fecc3e483310c945661aef892ddc2e16e085f05a247a867cb7316cb674b726acffd7ecfb79cb762bf8cd5595a935d8dc94498d80e611a6a8fedfdbc3a6d3f766da5fc17ab53eca557602a155fdaafeac3d52036f9c0cb571ef3cca4e8da72e06f27148e94d232f3e005bed185a957401441a65b7fbad43d4b5fee9cf96d72c316fd02fed5df80e982542d7af2b656270d2def1ce5b8c2c09a2e5b13b726725c25a736f5f6e228697c01f7a9514d6726d45480c0048405e2ab290d675ea772b76efe88c79ed2c46f745f1074e986e559078792b53f1fcebb17d134eddba272981125e15f57dd285fba273fdb572dd6382dcd84a614e5faa42abc86ec3596721d1d2afe999c4ac46e6cb983b03c6bdccbafd7f748584b7a8f9e4cd473e7f7485c1f3d36edfaffe055dc16b948664a6fe14d137d7c198ce4fe7f81581105f6729b8b834e0fcd5c1046e9746217b3e8154d29bfaf36c740913c506d5a4714fe1674901c48e7997667d570bc2bb852be7880455181d63f12903a3b5d6f36a57e37cf2f5b4637014af24be7bd1ba651a8a48c88f164508ce00be923bd11c192f642bd9082e6135f76e40f1226818f4414e434f5af7b960d3ba38fbea67da0404c2a6ecd5cb287626b4f41b30e3f16001fa59b6b83da828e66c5b95a22d939937a430d73036bb929cba7aa2f52417659065734dbfdc16227de87270b6b26f0ce0f72d50e26daf50a25a75c51addf350050f38cebebfe9b419ce714bebbd062e16f7241d50fc0be0b269c84e80540b3e215367bc2a19bde79b9c967513d2deec3137255d22c660bafd3cb67ef87c9f9214dd86cb6f9a109d8fb86c324fd4edac23719169adf3b467b05307238fc7539c9dc1104c5fa962bd6c96245d87703529988723f54ca9424166247eebf2d737acea762893b719b00213927b23e5afee7e4c6083bf5039a79f6e20d17a9b5d4b2ed55dd9765bfcfbc4da4638a0e06000b2a442999c29c0d2c5b74821c7b181d0a8c5b2a3a2bdc60a0c2f4664cee43d44fe58604e681da382463e161591fbd575f1cc3aee791e6cdeed94ae61f4647b36153e15d3288ede3dcf9aad2356b84ed3a78376cde8bebdefb5f58d8680466c9838729521f7068bed0502180885bc347f8dc7663359fc005905fd6c430de90e92f5ebf7265d97e09525099d7762870280114cf18aae4f38ec80d1da835e247bfbf345c7298ba7900b0fa94fd48e0b561adaf364826eb6254baaa30629f3409bef1de497200046b81dbca2e9f00fcde9a6c04db31cbf60757c757c26b0e3e3094f780d3185bda5a1fabe5fced45cb80178605129bcd4ca4fc0eefb337e3e187cc07f5227238271a8c2e7c7269e4e78d60485fee0e7a7290476be56303f3d4469fe9baa86013dbb551f41044bcc1cba58df6773c0005bafdb11220fcd1ddaf3a27d47244322fa2c267e5de8973c88d1c1a71848c91d3eecc2afc98846cfff65cc62dafca726f183463753ae923811d62fdae6896750df7fc9b62656f846aed2350bef9a772cf3e5ed3cfde4238f3d75bdcaf580bf7d62c71bd8ce5b81c3c9b3ba97f848f72b23604948233f8c338bc6fd35f6448c2dff094f58c55dfff8c800e6a72a9632d2bc0734950732a7ea56dd7ca76c33b475eca00f387b7d7472aa67f3ef650895f8f32ddf3abe76b0ecd9dcce2c466c7498a976fb462aa218174d14b90b475df72ef8f1d188e1a86492cc881619f878a3ef7f3518b8c00d2b71aa4fab9df754872659c5afe6c32d9fd5ab7e88fc5b0b93ac551de065239be0159a5009f7d454b72e83ed4c70f56506907bba3a9ed641a306dd948aab54c9f91b93f7bdda45dfb72b2cfbd4348d60d9da1d95fafa56ac1c7390214fe37e5dbbcc9c2eee18c475372d9e482d6ac73bd05eebef90d1372eb494e203d6dc3a68e79237147d81bd78355a4202ff40f3853f1fad9066956300481d0c40ec4f00f64f149d524b4ca4f4c72498b7d54fb7ea74f994b790ac093e79f52bbe080878aedb2762a564d22aad172e58f93e85e0463223f584b2c53167268175cfdb4ebc3b30dd74b6fb20cc7587205d2312a903ccaab1c1d1ee8493ad8662c33afc71b3579074782a9606598dd0170f1e7afc6dbfb2b2426298a1435f8769d5a6bfa4e3f1ca1f34194ca6e1db747a38e24d365ae11c980a58708d24c380db656e5f38682887917bc58adc0df7c72845c676d8aa71079439497320ffe4dcc96a90c41c32155845cd5039a0eed6f5f65c7d3cf1cf1f5f250839c78e38cd5872c62c6ffb45dbeef7dcf8c1c25c76b2e8fb6cfb468716ca0d821ce43cc55c12cbd60eeb64ba7a6ad4e58908933579c72fec9a805718e396a9f9c9bcb3438d456448d2badc56802d54547f4681c7e397238c496bab2a4987658727c577605c735a1ea3e495f074103845f5cc79f3add720c1ca421443a46219d4c21f1f87897fa8cb10d22fa886731944f53d37986272e861970f369149787ca338f74b7f563b7cf18f07c811ebcf3a6f97ba7715b05726ae79e9c596502433582720a39366f3b9e208e955f9cba690f28191201580c72964b24bea2e4c2ba2bac41fb8017d3d87de91f3be3f11e9a3a40d35c865c1a0ff2418d83ed094d279d4f6bdf0774336d2485de5cd9052126fc74b321eef4a5721b124a79d1d4320b444b47a71c2c21195fe96f34f7daa5835e9047d43665bc4d72ab475ef362757a552c7692ed17b98b54fe1d0e1db25255db3cb7cf896254723a19806c8fc98a8f2a33701c3b65418e82b1ffbed7d4ebdef20060526d61d372b7a34154edc16d9257d19ab09256a668653831819db91aaaea95e922b13093454f7199529d158227aaafd91d1f71b4f4baaf273923d3fe83e24296e0ac3f763c804ec68f9501e3e49f14a3d54f5af7a0ac6e3c7b0bffd551bda1db55daffd77270d8b5ec925f434b48f867b61487dab3cfff2044e7293431fa86f2526a12d1067154c368af1b91c67fa96cef24dd51a9233428be131b91fa80a54dba1fad941544ee80200d75588d757a2053d9e59fb9a3188dbd237a8828d7ea15b218527d725d54fd42bad81561d7bad2762996a56d3b805beb172bd16615b1243654f0424400f2785679754457773bcf6eeee6b9dee2fa0e019f358f0de4da590bc6cb47726d6d3df1463f2c0a86397a7c0185029282e6d43e2690fee5334269815a2997724088333cc9102c24b8dc4438e0bb50639bddfade730e88bd2acefac118275a72afb246b7e06debb8a10e94639bc6e4eed1e94ce3eb2d962a837a865289f44e726c7e06ce7cae59e4b2007c5cc90983cbbde672dcf0193a2d46aa0643a03850000e1fe2357f41568b02cd63ca50a214c93d4f16b19fb303ca460a9b035dfef3728da9fb3d8e8f5b3ca479a1dd01d0980706dd4bd6acab3bdef39a99240d9bbc1058fd4b448d6d347be153c64ecadd6ad867f5133c4c8ea55ee74c225c833a085b370e36d8b761ffa2c99ea0f72a00620fb88b37571c18b70c74a3c2bf56a72572b963dc3c7c1874bd797dfd5d81f3ebe5e2094bace888b0420b0d8b03b73cf63c1f6772e21d871318198f604dda30956134b6897d1b84a4de0063ab23deb09572060305cb7ab47cfce2b4f58e223782968c53493ab2f006fb3b87fc2540c41f676461f13c834c87c292222b2ccad71b3e99bb52e113f17cb97a82564c21fedf298859166b7b1ba767827ad432891010879a0b0ffcfe5c7968dfe1a4ea8076d56c39c4c4defc2f58c24f384791cb55738f2e221c51e58cb6938a81db76e144d6724bfae49c7f769992f9650eb62f54bb35ceea681d961a3e198d628b870d8e247211db5477fe15178e80974688a5e3aef245b6af0089034179628df678f69d0d726bdbb2f95aeeefddb48a62a7b9fb722e61e5cd5e5384dca53ef1eed651032534c331a2abe205c15d441f9ea9cc6e86056fcbd0869d9c5d1a8698fcf7ea083d72d15ad62b3ba5ee982ccc15a12c1110e1d6f6d6dec1fd9fa4ffbdd9a3154f2e72c010aa6520e84ffe5456c4ac242c800e74fef975ee97464fc4bb8241d7189872ce8c902b8b93ea4ceb18a1cb7d572d26b86c7846c586bc6bf5df17657024183fb166017d0ab2a6cb96e8bafdaf0646c03e667e95e64ff69f1b1893a1dc8e3359eea531e3d68ea39dd516f26502afbc572492ec81e82e59055c5e5846a13f1b40c963fcb19e02de8911ccb78457a37be3b82a3996346563933ce54cd6d10f517250016fcabde2f11dab37ff2552cf6a48155d29e18834a8df920782c94189c972d7cabb61d9d5348546a5d66b3fbd487a8f7165a3cce8224c042980b9b802053e6080dc8a0b42ae20c5f53dde8cfdeec54487e55deb160d4cc82dd051f1883431fc7cb60044005ccb531f64fa58d375028b7947606ddfd82c9a9d9680e5ae5772f2e116c5a37ca1a9c212cd353e22bbca0fad972c43fe03e360e18ea2676f1e7217e96159ce094b9019dea05078bf734a22ea4e3a55ec19864122bbed324fef18c3ab5f960886cfc13eb805d248791a21742db28802a2ea0be3d90211a7c00b240e6f9f37fd9bfcea84ac9f6357d773cc48cd419448fb6f9bfd90655ae5f35457117fa95f7c411225bb08404d29cf6adf2ec8278d3aff661869a7a5c8c93fd572eb2c3a44bb5e7bba687a1f589adfb7da4ca3a385b477c38e77dec4e0dbce96681cf32d2d359ebf64dba7f0b174921897d132de7a1ec736cbf921fae0356fbb72a02024752c0dad4038efb766e776330ae09e474826257b0e81ba1d985659725f2d334a897c646856d4ff8cbe577141ca419c8d58db339ed21e007b7a8055da7277be13b889ade2a5118fc19981a2c4b238a1ec92772352bc03cf5fc3799d3f72a029830a181e090464bd757edc2d64f8acad778fd09a93c854ebacfaa296e272d28355a0ceecebebe99e2f02d410357abf0233ea2e1508ebcd2ad84e8bb04572c5267b1ede51fe27377da874a423e52bbc97019b90d1ddfcfd7d9681b1758672ab984f3e2bf328548b1c0f9605873356b70e94dfcbd25e479d624880829a4a0179a12709a0953325df8416da9ac8064bb33403ba8ce1def0870471819a40ca2e77faf20bdbd75e60de8a1e22b2997bc55275d5e22b5da2c2701e81517561292d1441ce8183f4619dbdf1f7e7f7dfb5405cbd32c1170d79a9704a91a95afa2a2e6ac27da5f1fa63d157a61db4af2f2ebfd600d1c7db816207b3e1d07e83099b7290722cec36ee9156590f5e9131490aaca1a23258b02d40bcf0a21c43228053551a28fd425a546846140fda19c56eccca8bd268512cc5e4ccd2a21b748dd76f2a827113bf4b769ef58e5c415e3dea1723f914f505f0c78c25b9a3b553d72f30057b067b47bb344537eebbcb13c941ba9f2ef966bf4359a71dad33bcdcd71054723c2ac03b0ae507b3c57a36907a80575eee9e7c5bc247ab45ff3aa1d2d70c05279d6fc7d6c4287a862037a702dbfe5d3c5eadffbc2ec3180c40451f09d84bf3277a8a9ca9dead8a7ea727e30a9cad0c66ccc63592b8d7a6b46d32c591d6d761350fbc197034d3e67bcff860c7e79501f36c7d85a767f2fdbd9fbc27c8c76f1a728cd530205f5d3678c6b9b1d4ca4c3c4ad5deccc86550b42911323e105dc3cc722faaea7cf8c676beff44128ba71a38ffd07744855bfffe8ae86be3cc866d36727352aa2ca7fb4dfbb290f5ce35a65d450267f5384e1fe47c9031a07c50bcee35a5f7c049b7808dae63972cd2c04c7a8ec58bc7ea273216722b77bd16c2b38c72ed5a112e9c62de4eacb530217dbb280a04d8d6acee6ad0b7fbb786ec96a85c343f4bd7e85d4c55f14dec2712af7471c84e1ea9ae86c61fe59581faaff9209c728df8a009b68d322f7c02f3f08709351f82c8ee81f1e66ff4e90724e8a49f871ece34c41a3b1fc9b21054f779eddc304a4a72be576d35e4fb99d26b0f73d687392a9e6b4e883ab07bd6abfe7366e6909fe032018f876d5d3670ddbb1cf8b8f672c00e5110b0481a48f511d2a2c1095815b71be931f983ff63b753ab87d3b22872544e099f36f2a45c2cd76e77d7dc9f648d403646d656ed8aa84c1fc6933f3b616c870ab1455c077ce25bc1e0f60e81a9e5713f1ed116d88562c69836bf701a72e3764e514d3ac815b39783677c79f9fd06d5a1a2eccf289084bc415e9dd6a4370e3fc4336e593741f706b5af20af7c844245ee1e92ace6ed53a676085f426a72a742bcd234c59fe882ac01bb4c82a596663d11956f616264a2d0ddb7af9b59726db047d32281fdac5d39ba606da8cfc929b2598746e93e2f2694a2efff2ced729e468edff7de23d90c141dfb13915f7da0128747cf17e987d6c859b3ced3c472a1d1ffbe2095465e1d1e454682eb61a6a9750921724c6bbaaf41930621428372284a5a08d7b4029ac9f7fb1c40c89715d75d3ef23dcc9de5dd9f9d0bc61610722221cb5eda5b69877494cd47d58a85290639dc0e8143e55192a89c87a3f1c35ddcb5709bd565090c73133204a52fd631a6ec5dcb15f7d40b151b0b1e76ef01523e0b033d59d8396a29a39c81476d05e50a594a0557d4ba702e60f994283b9a727d84a8eb8d600083b4f8cefd900a2bf62e55453b838627ce276ff965c35ac072dcb9b7a692ede92170a0cc44bdddd8c1ce4deb2a5c610910ec7dc14641cbdf72167a878c6c963b8679b826858d240cb834789d3d57f2c3ed91941afeefae3772085746970e25a6372492728acf977ab9d5d2e114130a271f9f34311016236172c96a145ef30cc3a0f5788d424d844f0f75873c525e3fbe385cfbd1b1777f443c00196c5c3a8e0a96d8e290bb9be2eb9e5fa91d93993da6f16f954c11e6ac7e385abc08315d81048fee043654745545feb964ae620cc395dbf3f9b427e1a22972f602e65be40907ee3e4a443cfa997c8f7478df25b7f077d9830ee596e379bb725ed8b86c5a666dcf6d68106db1f0876b322d7d1c34661ffa93f5a709af5d2d7227fab5e24784cfa19d9273ce781ced54be4d934c400ad303ecebe3c5536ac972981177886f9101746032df8203cb6d53dd55f465d218b0ca796dc44aa9eee40e0646729bd27446001434003ad923f7a14e2f11ee558576aa16b921afa5246853b3cb34801e3927d94d743b59d4fe2384edd63f4d12fe03045a397cbc8fff04720f127a3878d7dfbaf5082b469226f4a21810642e61850a5fadada40063a77d5b84d3de0ebb0d874d3ac2d27dc00af79d63697e44522c2d6c076b95f85ee7267299de2f6bc3bcb3d2adb213b65442ca81c2797f28da35b4612bc92079b7f02a232d50f005c22367fcb8a0275158c8fe3a2778d22b228dae6aa37a16b5ff751b72f791d452b4c752b1645f5ada4e64457788d4811fd71889c034462cd798f976049e9803b629824e2d478130827d5877726137d6904dd124dce50be6e99a2560728f8f325fcaf5f1d2f0bf7fd4959848ac44ee6ea03c10b84ab7f637edfc616472fc4aa8026782792330c0bed411abb05fa2d4a34956b2b3fa4f694865bed09b533150ee3ccbb035b0edf7cc205842f644de48dc52be032a1ae45c4af67366e772f3bdc3c6e31d3295af1475dfe5e13bdcd44f1d7967125ceba142b88bf6828672bf749e4cd6692012935b9bb18726e09a805de6ffe741b0d09bed2a02503dbd727d251991506f5bc0454f11c11c3f14f3fc3ef23b5a08662f2a53050a65eb6f726523b921c9c0bde9a957e7c1941d89be23f4a60cc1c380975c2685ab445c0c4eb873e840c94ac4a5d91580c75d09110401c53f33308aae26c348476e1bf9ff722f668a07c5beffd8815799a0d3bf3e2e65d52a298f0cf79fc783575db7192a7294f32d8461284257d60282afce72e194162533ecf782de7024f6ee566cce6c72cd0d65f0f70e5353066cd781cffd136934355a365534d6360c179bc36843cd7224debd64dd1ffcbbcbb4ee08ac314b98e21cdeb6066b66f4e91f2a4f90e1fc343fc7fc069af2c68c5ae4620d1a40bc70a71990b4aad72b137a11620cf0777b724b0e7f285b83e943da3c1cc9dd2d39118886557c7281a6278a58f1ce6a2a5872c097c1dbb1fb5fa38242b70cabafba5a39e14bea34aebdb18c96666663350b37f0c042f15b29912129e253c7832fb03be130da0a97f93bffe9f663adcee097723c2d7ddf15aa54b6030c4515952f102019e04d72e5d619894017f124cdea80727795ab480e115bab3ad3a9f2c13b84cbd5710dec20eb5375c981595cfbd2446ff13fb9dc92aab48f89613fc3f6bb2323740b26c34096bf9f30c130c204d358728619f18fb1bacaf36e4667879b91a15b99c2f154b785298697ac5396967b704e77dec2aa9e0c545e6cc362e80a6f7d32ad554a0eada6620feec87efd90008872853a628045b793ce9e0995166c6280f49f1509a5ed4f8f27df584ed739a19c725efef16ec1cf1f4c28f452547bfc21ac44818e03bca8f629037202ea768cc64f016b41bd059f93b15482c83bbb7f49d7594694574d8bb9d8a4f5db2b1d8f917249019d03e455135bc88be6b0a23456775f2d7e4e85ea6b0b02af1b6e9081014fbebdd021dfc99d0ef10bdcd9ed9746d7c40c6cd936beb05980eddde5217d847214d37b94664ebb438c15c8a933ee9a4c7e98292b940b3642a6437ebbe5247808f9407d3e4aa624b82c52389476c4ff900d2e4d141dabe38c9c0e791d9d2078025e4d022699c4679295d99d56fe3ce3c171eacec5d75a5baf7eda82cc4b4f6b72000917c79ea18355f89575eb123ed7bb84dc292d852e595bbfd29e446bda73727dfe6059ad3ef49559e8b7c8c10ab7bb1aff80a7d86b5d42e121c1cedbbf8f72e45ff0f8eed4781db7c2b5571088526251c070b1293862465883438e80ed57721b9fd821dec99ca015497b7308f6e42e3d4ee1ff287440c851fe7cad5c8d747270d8e075d8ecf8e98f8eb3b1d9a73a0d13a4a204c4d930f113b0e0da1cce772bbc70aba604e7957ee36d275cee8d00560c835be03f523c5f0954b0d13418206a73c9f7ee56f166ce5d64e240992adb0fa81a07f4ea2a88dba8db330636f1e70230d43100f1646c8b640591c269038b56619ffb413340ddc83577727d9e9dcd38ba18688eaf1fa817c4c8f2ddc592b0890ed99c71c6dadef34e12ce72765218724193b9ccb899a470a370fa5b9feb9e133a2af61cfab1c280580b021f47236072dcad753b861ee54632aa991d70683ba45a3edcd56e027e4ffcd721926628d95412a1f2d661c647386f2943aef152b44ba3c408e3403b06f57fe4b00592a64f5eb0007df41145ac66f5894df1c568688ee62a94897ac316d1bea4b70aa0c8e4282ebfcb5b3f4f3fc632ec80ce004b8058080f57d8feb1a9449e234c811e927472cb51bee50b155f0b4b83984fba042b63153390704f50436d1d7eb532b961376ad81d71b2828a5f63dc2092ca453464f702e2fc93ee5421fe33b6334a42641a72a8afc9f32b9266562d94d1827888cf47a2f404089bb5b869df1c79f3a10927728524ecd2797a1f51996377dcb7073f925efb56abdb64b41c00eea630bf44aa72c8f9f86454258d73f7eab545431f88fcb5d5d59fb85c44c53526f64d7c0444728ae553c10c3082375b527dda0de5f1fb9f511e61e69a9f927d790898cb48dd721910bae86bb7db00ecbbe8933b4da97b8cdff21971195ed03d173c50c80e4b7285be7ee838fc0b2e496752133763bab47421f36e548a1755c2f4b536af3a76703ee8388bca5873b41c5a1c046d2f99294ac51f82ecf5107f42dad3f42c0b12720a60d98aae594eb90ee4ac16849bc652c26366f174a8cf243347c15ed6e3e3703def0973dc65044a38d69a0ff7ecaa213c107a9684514875ed5abd05a2c99372baad9b635a4da70cb03721c24b497c6fddfe0aeb2ccad089e0364553a122cc72df42c1fa81751f7ce41871bb0d00ced83c5db33873d5f00761a8e61b3c7c31662dd9300e0a77a9a1d5ffa543f58eacf21be26e9a293a1fdb55b1963378f3d672076fcc99dbe8230af841eba0e977f64d74ccdcc92e3adff3b57628decb696041ef6bdc9de6bd0f02b8b2ecddd1728a6fe83be5c302441310b7147075f194a172946d5a93058c69b2c71b4a76c066667c6257059ba1baa10f43c883ffed3dde6c77bdcd0ec166a848134304473bd4c74e3593ed3c23b64b8a3e0a3714ea42ab1c68abb1659d65ae0506dca0ab85655da1dca8bf6a52e2dcf517eb961f86729b4f04f8b446dd1a818758ac32bac3beb90b2c1192205d9daedefcc263269697237243e046f5d7b2b004957239912f1eb8be96d058ebae6a237bc1e1de74355b8e234de53693ffc230c1f3482c32392b34754a0d1bf4724c2bb5d5bffaed8e003d1faaa6c7791e87d55b55922c8100943a885e416284e77e158b369a14524ddd4e728e81cb442dc7658dae9979dc6bfe2e74baba6b4ac10b3ed9dbe53b88164a537252e1f54bdf26f2cd19c3b516bc9c67c953cb35a7556770d62b2dbb2c978a9c72397bea5c861c3904ea35ebc8e8291e2f01489191506e18859ce2f29fdcaa094705ea699f0f82d7ff06fb793af68f8d661246f28380895270fcf3d119b5e96a6f1230d24d6fecb872ab255d0d5d85c910c81add64a5d851990373f5b0070adf7204854c64de1897270fe1303b36dfe635862b80b1c33f722016853ddfbff8497291567976212bf039b49cf9e76954fadc7de805859d49b162e4ed32358ab99172759db74e501a9e4987bcc9e59d30e44b82754bf677f53d4577da2809d00ad872e5ea430a47c1b9912648cc3a8e696b1f287ebddb10af345c9b01591609b9387242f4b2dd13c4f995fdaeef37eb9abd0506e1fee806b14749f49be43379332c45ff746fa78466ad7ec57b2a17719141161be2cf47d3dc2244cf012a68108972720684c5554f3ace55081b19266dad4a114ac358e95a5ffbae74e7c6f7d1d4c0016e0bca429e9516caeae10fe19eee85c1d45c18132f11474eddcd82519419c672d14d295e73b4cf1bf393c8925bfc93def289d2ffcd041cfd03f540dd8bd4df725ac2498471bf0ce9426622e5b082389f77732577d221ff8b7404ba5129c0fb72f21421df199a4758b84d13eb4a2ac4a5bb568abef6febb0c2317c2e2cc11fd729c7ff1e9f629573ed9b99e06cac08abce7bab4f73878f0bf3be7633375651972e603e0476410f2787f561b1755e81e22db8a58d8139977546ff99d7142b42371e20afdf91ff01e53dc6a4b1e2b62b00a37de72855a54e8cc194f6c331ae51e7252f7f0e222280551d246c301beb90c078ac444b2c14fca1320e4ad77e20e43035bda6871a5adc18d99dfd8faedb52a6ec876a0df74ad872713bc06c56521961a7eb41e4bf97ecdbadc8ccaacdb313bb8551a44b99dff1fff161d17136de4be03539a48ccb08f4a758631a38b0e7ad7f6181f92b354f4864f3f96931b4b1dbb72a97e1e839b353a339dec3a044d72f8bcde7708ce1a8f80670f129cecbab0d4721788a8b2865c2c4cf28dafe89d2fe720f13f8e9c158a1ab7fed509dbc2f87d225af0847125f787f1fdab8c2900b45b64fa7094680dc2c9139594a8c411192831b3a2058c0b7bbb59a633c95891b0b820cfbd217158de967b6803e99a0e9b7b72bcd12e8bf11721abb0ffb58e45ce5880b04c00c14d41d73a65164d79b1220072f6ac3912dee62344f7cf959a18ece08f8c16920bce3eebfa9fa6111ced645004dab5fca2e3d72e91ca91a0a82156665466a75105fa6fe5b403c02763914dde722c33508d96760dc7046670fe0a15bc1354e63edefe33e63a968894c82c2969720a9860b76ba0a26019be02e95653f7f49adb2710a32a5400b39cdeefd6fca9728f94bf1a99932cc31670f60db81d4972b78ed8aef808bf6a91183f86237078720ce57531596c68b9bd0bfc8cdf8d07012101a6fa6c5dfb0c44e692bee2e1475411f65235133a8998abdfd3c1f272fb4974bcbc28ee87ab02347db7373a457e4da314d1d97bc12c21fabfefe878f5879aebff945a526ee81ae20d1d5404a10235113ed3f470fe15a11e3e5716eb013067d810b6402340132f8d6565090499d672689853290b35df3fdd83e17e2c8eb1d0d0193c8a91032f6e9c1371baf61c657227d652ae5acd92d28cf47946932959b652c18e71b7ace26d49f1c0b4f59b6c7203df0c5905adca1132ff05250b343a0a57931c52d56b9f9e2a7444be32943172a56b204fb08711efe8a4bd9027cdf6e3e7a3cd7a7552ceead59170c5dd56b1722a48964060e9f00cc36b7cb967ff28f3b5bd4939378b04f11ccda232ffb4aa7284d6f4402ba61623a8700228c698f47b7fb1e69dd33291960cba53eb42d6582695f5e138efb27becaa67cbef9045a1a3732e975c77f40f25621141fa2d7dcd72d4cb385f705eeb1d860d07ff50511f24fed951d9ac631b5f4b520873d29f2e0e7a782355842162299d7365f1fd268109d758fb576c7270910ec7a2d63ad9d60e18e4b053eaa2120a40929513fcd7b89219b02ba6494fab57b4216e01163110729a189ad0bb42ceb08e848da3213987284856f8ddcb2962d33eedf97b816dd572d3608ed30058f07f39cbbdb0c8a32833fdb75b50e868e9ffccab44dfbe1c2f0afb183be91dc6bd1394b9e20824e09fa31f3442e94c02ef78bd1fa0c7e4fd8572a5491387fbad170868bba08bc660831c421ea7a37b006fcccae3f238f9f5d74a40150862896ed4eada49ff37a6f33bbb38bc9ea192b36e0df717f8e1347f2b72a684a80a7e9f5dcc759214c309538af0d7df33a1fcb476b2c12809f6723d0572061d5bd6554fec317214ea715ca2dedca36d126d4ee90be820adb490da555f726aeb1718bad46d17950f7bf722842123121f55953f3ebb3c9e9e12dc8c99307206f40c1e9e2a21e7c9cbf275015188f53be905381ab6317b7fab73dd35c614721d191f0970412961bea1c208ff0c8df75e1e7a7061ea5f05b44c686c510bcb72562ed7b6ab5d82099d893301d7902456d408a49af963675c90bd260e2b5f4c72d0ecab02a70c716b64db868be35601c51d9241c7a32dd72748404c2f3657546d162b4bee22a5917edb3f888c0241ce76ffd8db437c2e8c47f5c531716d00957247ad514d79eb34e88c2b6465bd5cc8bc991faa0b38f66e4405860e46bb61dd18236bb7e4ba2c7a5351aa46e622a1f510dcf1b79e933bb915e9b3f6e59618fa72f05d2724c964dcce5ecceb28b12c896cdc71c2907c789c3ac9eb0bbc1d6bd8597124815c6ef8e1a26bc348aa3e382ac0cb70d4d1fbe09dd61603fc97a07003728256b29c2e45418a005371e102045d927e3de1b3fd7a6e9ac096ed7da476625db277d858c79b0592420ea756e663bba40274ffd1a4500a9fee700846ff3b757226f8bddc3c505f53ffa2dd471050c64f9128eaec0bbbd1e1e00b8ccbb23b8c461f9b692e4e6718114f98a3eefd752758d1d90cbefb4458826a07ee3918b87c726bac60d72447028f26bb4bfa80983d2c3b2ad1a2d257ce7db0400ddf3d1deb11c8d288e98c41072c9790666f8faefd0d40ace5a21111adee94e0a19e63d92a72defa560ee4e226329ebafaca0c5d858bb67087470ed411ef34a82f11db25b3210defb62cf2c9fcbd6c8fe3a90a96c1f01cacb6aaf0de5f78e827c9912c5023723550741df51a31c0badedae66e1a251ceca782f2777fafb44944fa1428de392d40dda9e36659b06c51964a27bf6e55de4254fd42c8979d8abe335e1870125772d7926d70f638cf003d0b03df1775a02ba57fc067eb63357c3fd1135e1553a172074e6a1cdb45f978fd9e1b90af5a88af7cd6887bf60f95da5ee4825320bcb2727fd96af7e069f5cd16fe995421fd40c38d251d0ace9cc19d8a8aff68893bc472eb4b762487129fbdaf756b8a7b64e2b47f8b0006b3bf7a762253f3d683bdc172e4e498959117a99285d01620acdfe4af5c39ba2fcc3ab5b65ed576112ab6c82d302417e2c11f19b2f736d75a8b6597b1ea372f8691f64133151d18f1cb8c4172ee6b4881fc84e69bd597eb1508e828ed0cb865c594e240c1a60cc91e6da5331ae3376f04c02d40f0ef9f4b370cece684aa94f98cfb9333574924554b6e7c677247333b8cdb46ab4759d7411a9c1ed8fbcfd1ea793b1ae6be80eb8c5b34340272223392a888f9b52d5dcf168fb7dc123a1f5af19ce6d2382ae52309afdd602827f674e24153e822bf9f40bca95a721d68872f8929c7b0647b0f99176e81873772bae81da27be5fdab37a2341b1e3362f09ccac8fbbcdec92a4da49364f6f51872bf92ad661ba3de2efd247d5d45cefdb078bf04111c2ba3b69adc818d7b22ce3f307532a6206830bca0376f5a6ceefb51483a5322020655b76a7be5e56d9c3d0f480c0f9894396bfd1540bf2083ce2cdb75e422c2cf7ef6d7e50f959a021abe72d7ec1e7af919c4a93bbf95473b8c62dcc89c88ba536de23d564d792821bfe472ba50eb0566d3b5cb51e09eca4596fe8c3899f840db8728a9cf2126ecc2272672c1b10083d77211e2503410e120f09ce57e9a1c55453597dc21b4a55a2f873a720a62f72d01316a20dbacdcbcce562afdf8849f76002195180f30b04869e25c4511968c76e38ce4be77b4751a54bf07870dae31dab1ff9ef99ae3a19a3e50ea3907d060a51bbc9be86a81eadaec7648650ac0132a9d9cdb76f18f34ef88477672c970c4583ac1c07caac642a3c809501257c6188a7de20783ea6e7eec41615a72a0817fb7136bbf32a1e2d47f91bf76b6392a95e96ef60434eaa1775fd560be72a93affc48ef3582cfb23192042fbb1d5be39c3b48feb2b09294a11ee401b38720a51ea7d726e6f5d71134c487bf6a22c28f8696cb4504800013cf59415b09472099ca74619cdbe1b3f13fbe7a2dfcb838989a5253ea79f239c111c2f8ca4392613b7b9de1f7e7c1c9bb229fdc1c25a92d8d7e8d51055c6765fd076ad43f8272a1c7d0ee467ae2d64b561a91134befb66dd3b265bcdaff62c22b016000bdb36725beb1b4f1876322b0061dd5bbe308f4266a928804bdaaef43cf5023da3e4524ec9377df3edfc6cbb8699804519a7733e2912737ff4988c26a3a83d7398b3c072b78cd8aa5722f9cb60e8b9b045fa5baeeddec82a1b9b14f742ee5c1b633d9e4f422e755cf495995cd1ccbc71e8a317fc5613c42f58f773bdf0d0bfc366b08a17a8b5ed1aaebdb48681f70eb6488287591ede67e1e8f909b60e8b2eedfa2798727fc39592be6ff2a5b5540806e7c28e74c67c6c9eeb84e582b36c3f0d95dd38728786ae11e3b792272dbfe6e3496f7cc060be7beaf90e89d063636396feb69b3a26a9e6b6d3a66a6533c1521b2a02424505eb5f9d097f80546dcf983c66289426f9ef2682c446926fbe0fc260a329b2c0b5f6ba2869ef12a11f2abc0dde361a727c84e1a1f3ea647b3a481eb5f0809da7ca6e74584c63fd060aca99b854354a6dcffa90246b43b8717e8480609306e7da49704ae0829542eda904c0fe3f1f5d72d0a17d9a2430bfa22e7f352160e4293365e1832a0ceb10e7a160b8d728e1ea21057a29aa0d425b59074edaa6587580bc2b7c78ca241cb0ffe6653176ec43c54efb46621f03fa7e0baa0ef4fab5c7358741dc97da3ba55d4d93216216d7b22e6161380ddf708d50c2aa451895fbebe5e2a1898b75d437a88d1a8242004d0a4b7257f906c8c05169bd4b059e671523aa0a93af617acd4d5a414d8ee12587187d72ccec8945a4d8d14b42860f6c14c13bbe54ff03c80876f75efa032e1a2d4a7072b4e743b6cc98e7d2977a4b69de04ed855e116e2a3b912e1441d9df331a6ca772adeaa2d42b26132bcb4a6468ad5d42772bb209a8ee5c27f2363088382e7fac720c9b8258fc7fb7528ee9352e40e85068fb6c8e3ef747bc0472542a5ae74c507266dba5f9974754e58f99167cc9032f383d0819d055a4441d5e16c0a3caa0a5698aa53a47cb7b701e8c6ed1f6125e13997235f35b2dd9939df7a2dd8bdc33e0227cf0ec6288a878655ecf7f35af2a4a5ef53c91be37ce9154576712474c580a118c8849c9f09cd6bb0a4c4ca634dfff2ca0de8c1c95d3ae979aab9f08cf7cd86a2463c8ea334923e38578715a109f49230c5656e885b2b032c28a457d7a343972bccca1ea302ec37dde91a82d06cecbd0d8965b20912471af5c6c580b6367450347df6c046c38c2e3afd5809c8d53d3530ad39b7f64cdaa1f55f8105e11e47e726566b6fe80c4aae4ff3e90e395af3417376e8d980f9c05a1bd4b8e60a7f1f81c5bf75edb1356495c6f4a17867a70b037c2bf7c670101a67ce1bbd17b60ba3c0a6f2d0dd8c91fdd1409a544b9ed0186ad712789b20efe980660db96bab86582187b7d342e0956c16fc4205e8aaa07669c6fb735b2b87039c532861d46b7bfa636bd9b3fbfc944d74554821536d312d5052b5e764d03200b32c69dd85eedc4b472df45356add5fd3515a398f5a5d3f881548c819f5cdfab2f3f6c262146bc8d172a6cc77074b97e625badd6619002af7abc5c8469adcd8d3720eab42d31c47cd72cd509f160c8deeca4a78ed6c46580ad5199184b2900191706539a00dc36e5b0e67fd5afa4009e0eddd502fa54e935fbb232ba99419a551ea9fffdebfed6062723e32d3d6dd48d8722bd020fbdd65b1b750788e212a569000c5a788e09d2f0972bfe3b7babb6645e77ef9c6bae5a500531e8a7ea6f3ebff67539e0ff9adb9e8722cb92a742c38262d2263828787b269508848b8b8728f4b154b126cfaeb6f7c72e844ad25804c6ba4e8d365ecccd96c85270c9bc83efc88c4c9558a7c9c34af724c7b5031e1b07dbd609ee834b92235155df12f6fbd47d8662bb76a88235e8c34165607a5d998ac3eaf75ed2eda97c3f3ff8db99bda8734b81bf10cb8c570d072c1a1a4037d0b61a5468340e54487a207ce159dfccc63b1b6736fd3fb26daa747e2af03b06e30fc2bedac7075318363bf9feb5b9b0c4f33b60c2395838cac0f5a0441689973eab8bbb25203ceaefd6767c015f8c7178e323433135675051aa446c25d1ee1cbfad29b7e63f1809884993b790ade6b09e7237b38fc2c442039304e90120b2c28c501cb39705d00feae171ca74b731ee48b7f473e136f6ca1f0284db262a4fe246b532e93ac185269cccd80cdb53835238dd3d34d17e4ee62cd754f777f74ac4d0e5ff6e08643e659581d9c78b0dca5977cf5f4fa7b489ff450a26cb2f588d9a8cdb684e52dc591be14934f164b1fd5853aa82baa68dc16c2e7b1720eb1455a2f7e9b5dd35b0ec682b980110dd5adef2d33a8075753c1a0370a7102b9300003b327a2c1386d1d3ab85f77375979325c42e497ccdd8c0142219777724d9228ea4b5b3acaf190de65fb30cd18fa7cfdf01259b5f4deeacce195b22072b6d73050cc4935770a1a41d659547bc07a8e96ae50f28c147937e32398d8307266224336bd5d8c729d00f755be7d0e0d6f1c488412f8012511c02f1d2c02e54bd43238eb9f26d56b5aeb677692d84fa5cb5b6750f734c210ae834afdbc1b0a503382f3020b09fde532a1286ea69d4eaed741dd068c1b5eba5055257a9a5968366b9496388392dffe212d9ae62b47575a005b10df6a7911fadfa5df8734ea31722fbd025d6b3d8dd4e7d083623b572fb44d35a4beb33b8541aa46594f5075b6722e534f8d265baab1d929765d609423094fe4490d95b65c3fd30fd0d7b46a00725dc205e748ad433c3f1607d4399fcb15a18ffc02fc2c4cbb19136fcd93c0607297aec5f7124a6825b2cc6fbc57e5767bc962a8bb7e24847107f88e353cfda9498a7c3b811a133011021f815d6ac58666537a683c603fc904b6f59a0b2af99072b81b8ab00be0648933724e00b10038d698491bfd27d4ea038fa7672e6499f91e24ef8feaad7fbc3b73ab7e01cd83dfffeec9550a6583058cc381b48067ed9f72048ec9069d32178ff9e66c2f0188d5bb2b9ea8ba3bda8f1b6585ea10dbd7ed7298bfdc5b62ea6426ad16233251ba2378fb4c902e158fd10d63e955459d7df446000475421b758e0fc61e844ad28e9d9461d54a132f605ad03ce1c95cfca64672bbd3027b12935b1a907420aa0f88abd4fc80d8be9c72b90d2c7a12b181eb9972ddfdcb04a5789a4f56eb20ea87816bf669201e25cbf0059c8c17ebb27883487236dad92198926816e0ad0dde8159020344918b367482be8238bc6f475055047286634f1668f582a924b421ae0d2426f30b3e93ed0768dd59c9e2dc9bcdaa3d72763c6dc4374bc361fa6c69b1be95e57f8ffb07678597c97a67900b3c2c0e6972f961682aeacba8b7be13a4276914a62fcfb55ac7cded507b33043ede67fd9730dd4b45086c5344bc33fcfa77e92bb7630217af17f6e27921fb37f1534d099108194827fba50cd4b15ac2887212c5c0be748c13bca2af2235028e139d83f894447c96c969aef450be3e6f8ee5967dceb573acee0089467c8abc4988e057da574a24dbf38de5dd0fea046537aa2ccfa9e880e68c860e13ac5b28d2941fee7ca07271bdd39c6b0dbb18176e9ce0fd4d3fcb485c8c23e9a9c403dd576f57a4eef3720f5ce8a1fc019e905a369f3616d8befb72a9e033a706e27834f9164b871a50722fcbfe525145e488df3b1a82d98c63e5d45135086439b17a3105bea47207737230eb6f6a33e6a48c66179a05786e7340f9afa0366bc4fcb314a6e811bde3e70ea7066f748b2e202e945c8c55ad9b00c7c4b53049304e884ecfa2050041e228729db037f830ba430c821068208ec9f762d8df1a65b34fdcc8a6c2ac0c3aceda2ff582f3688a9134d8f2e387604cb7ec67bf5bfb5d07503eebdb32b99fd473c57288e4b3b646e43f0082e75e0491aad827bba167b1c17b6c6f459f20313fdac916f251c3b5b3452c90f0bd1595060768b6f868314c3705f7bc41997c76da4afb72a5de2147046c6daf7c5e43604a7ee52032b8c01eac355b131a96214b6aa87d58c39573b8313cd073090006561a7ba9e2536e4ddacc183c6b0100a95cf3a043723958dc069c39bcf881f18b875e3f9b74c757d25ea19968715439281a3de6317220ccc50952e1e1afed68fbfcbf8664550a500e6c29f0878c185eb9f4d118147245f20a4fe01074537cad4494c38b2379966e6621db6ccf0f405f0d9a85868f59abf2f70d2d45b8a846cd81148814c772e1908520d0444c53fb2799f5a58bf8721980856fa1042393d261ab8079250adf936eeb278c7e9432b6f27ae82b741272c682e913cc700e19388debe275e4c30d074b7f78c34dc15efd82cb61942c32256af60186000991da759e672a09421880c0c1e46fd5123335878eb87a1c39b772fba5756dc8fafa2329647f43cc5249f7ace9853c437b3239532a0b505491607252ec53bb614480063f7940c3929d58223cdab8d63d6717ab82336d0669b4447246fc817298bfd18fde4e9d9ec2237cd59bfaab93bb36550072c4110b670761728a4e58f9fc514c3261425a5a8ba1a62ec1fe81b8030704434ee9fd9991f39e34922aa2526c983caae70a4912f6758fc49bb974537955db1aa89325c69eeefe6d83f73c7a308bc18ea63870d3b47b22372efcbee1395c3bbf07af993e956d8924b0edca64a8d68987da3909c65a8ddca26457c4334a854274fc473a4ac823b27217d34d41fab0baaed377eaf31cbef6b4c2eb7a2909a975c86c4148814c6707328799938931e838ff588557e7209f8dab11d13c7bc310be42199c272200b83f04a23706decfa5e0a601533becfa17781bc32ff4ec16cde31ae73307c27449b6723f6ce8ef5a3a5af7042afa4b37a89f3b3346c1daea51a4c7bb2430932a6736231689a515a48d09b97f7fbcc6628e194f0a4192eea465f8dc3ad088e6db88300fc29753c7aaac228436d80429aa4e6a65b585f0d586ab71b2db74d97d1db8a00046811097bfd8e56e750e41cfa7b53f483605abf5b50877a60cd5c83f1df5ce443619695fa41bae7f880cc87a57cbb62c5a60e36271633b364144dbfcf9edac7248c7aee322e126a03779232392f5d29ffba5ab5c01a67b7f1f53df4f42f8ed53bf223d330c51d83fc30b9b51ebad46487bc08927a5639d037f8e169779de16728d967a4dd40ae67215dce80595452d29560f58ee1a7faea75348ab9f301ee972caf6f59ed88a61be328ea705ebaa707d099ee8767f1f381802d946803165ec72a333f03446fc4450d8092a2ad8073c2df157e721741665d2b696bd239bcc3772f897054a1cc40234e81eb88d80bafd7bd7bca51f56298ba3f092a753ea932f147c2c7f4a8119d382b6e2de94c5b79345eb8b9d342418b51929c2f9ee117dff728fb3aefd2d19daee6784d6ce16fc20672c98e3d1ab255ecb14cec31decd334224e938e283aeb675790065bf43bd3a5657fd83fda4bc3d3a4f1a23b155ef38d2bcbb105fb2fcef8bf1fd0dbbd4d0095ae1b05caf16e262e430b4cdab7731ab972120b4dedea43494d1edf3768da8c198d93fd4767bab4c1578a41c505f8595f2793924adb1d9a765c8731c93e7aad229b18312a05445999710eee136be6d24e1b856153f6eedcdece228c9f3dbdeb48fe3a429b2a1d9ded26d99ae89a32a65e50b7a3e3cd15de4c68e3080d71eab42485b049c39e7e57dded9b8562dde4d4d825e6614ddce0cd0fd737ddcb999eece28d34389b919de10dea9ff1037e09a7453543f73628695671c6db03bc22b83c987cacecb0acfbef3f576bcd4c6aa3516072963076a22a8d308e90ae06da71d5efd4fe50c5e711bb77e8fa7bd056f5d6df4acac4b6a2d9a539493ec639050a5f14241d868de2d44c994a8105dcfe9a7bac3ce7ce49c3beba7bdcb66c5cc1c325abcc5dcafd737089c4599ef1e4920664ca204acb9d2454f8ab014db3d48c7ae076cde4510e9b16d67fc392e23b826df7727209e1a1776e0495df90aa7d33b32009355653e1f7359ec6ec948c66a0bf64c10ad7039361b92a5c18f63fe54e314eb51eef57fd5abc0d0ff5e823203a645055720a32380ddf675f17330bb87085f9f37a00a8cef5b2132c0b386f2ac4814501112c6ff25fec6db8dac52c7b20762f10615802216591b9996e8de8b8b112f39b72103bee4d00bc2588ded487a5739d9fd50c882a3f24bc401fc9f6dc55e7e2387235e089eb92e8c09884daad339aa037991074c72602294b5e1e9180f739a34f6955a62597a1ea35af4da9fda0d17f3166f02dde8055b80985582d3be61894fd7283fa2063d80e52c39d6d53f0c1639f6461ae1708ecd7746e3df04987aa672957df973b3768934b99b678f1fb58cac40c5862852506c7946b8102d36ae5d6a472ebce2a9d1184ab94a9a33a2e293a2105d6f0a2361fdb6d5780bddcb5a1adee7258401a735612c10046d7e812de6c1f28eeeb9e8b1a748078e0ca1d2d1d6624458b4ec92544be3a12798de07e343798bb946cd0b5acdfae34046a369b3863ef7252b304a956a65e20fe339751114b6a36a002a4c5b0d379d037ff57e098bdd37238b7750fbf6bfaa8e29a351ad8b40b56a176c4e0f956d85b106caa96dca7f24e8855d9209c2f5b51d057894832170ee3d37108c226694dcbde0e6cb53b17417242c64630ea2d3281940e9ab9db96f80cc44f6f975d1d1ad78b1c15367e5a4472408566deaa193a88a353ca5772c2678d836d0c0c0eadc8d2366bfc281d14e62b189e714d9064dc06dfed9617f0ad5801ad1f9587f72cc1cd46a46ad12e0ac1724410f82d6df8dada17a61343fd968391f724022770346b9c53acf8859b810e6fb0262682d643509cac3bd776a7cf6ff2b822f470384f5a9579b2258a3bc035165e66cc20870cce7819d3580e88468a94f4f900e6b9a166982926e13d778393722241c92752492e5c68f979cec42642eec88605e93b900a9244b98b4cbd82d972571742238d96c407453dd0f8b050d01494455a2f7daf53f41387933908cdcd72aa6f046600e57f6e410ae52391069751b092ed8a60833c8264597ccf008f7e5109722306a5e0ccb84711baa3827951b074844f66feb193d2446648b0cea55206963191ddef17d970284629a07d28c5cef53708681fbcd690cdae8e3b2e990a4e3521e5a19666c0c5092d97783ff731f82d894ed1c12eacb1cb0ed68e2c89fb72b0edf165a7c0377a417d5384653ce717f5c8331f53fe64d0933b0923a1b9a22272f4bbfef95f1a5f6ad820c5b89d518bcee88098ae194e522a237e83b941597255741251af5d33581e748f5a347a2f2d88594611777f3e0b0550350adecb397227cecfca11444a2d7732a88191cad55ad08e8334bd343ecac0891c533edf4e72505f2e399e4fde01d309b84fee415d1ac91e5ba7a359f3139cc9f3fa7955702c48aa826e918321715d9f5cc3d964586fe2bbc24a5a81d2b4925385116d77683c8e1eab19491947dc0d11dd8380786b019237e2c868b2ee76b548adc88120b072f9a5fd5f2b67656173e07d1358c7fb64d06609652d123536b7157bd98f9a8472d3a713c75c3793b51b5a4f17a8e7489be9a4300d77b03b51fe3d4d9edad98d7292c7297a42c87978837291abd583427ab4cc80dfc044309eccea98b8ed30fb006c4a738b0eb1b694617494f231a4895f098ca67d31e5c105b5c39d0457f8d072d0ca4035eb9419798c5e02690128a0c7de7ce42b8819dc3a1d52753f27492c72cabb8c1a9437b3abee2695489c52356f925714b8584b685b2f2a0ea911937c72ca2d042af4c8a4433fbece4d4a855aee7bb503811fae1b58c8a25c8db77dce4e3b8a8a908bb5841509d1ef5fdbc3ed328ba5493cd4d460aa308306f67d14f87240849f294d176e1b216447a1d33b4ed52c3eabac62f6257ef7787025f4e04a72ec76d1fbe3c406cce83bbafcd0ff36cc1898cee778aa4dc6292aaf3b2cb2c35717af5d6c6a2c325874be9e81ba75b46982b471ee9368114c4212c103a72df24f5f5ef06c4a5f31f8795fd0792baecbb327e25774e40831220cabb8900a1a8d671a8351189edbdf805aaf5fda58f6ba865b942603bbb6f85b679bc4ffee3cd172e05b40e92a74b32c57a7f130a9c162c3408075b5466b22e6dcfceb671ef36502e9fa9d633c4a7fe540f960ae34293bbacb2e1f7af8a08b0a1f1c2320bb349472bc9aff0ed9d67bae5fdb12a4d3a877aa2c4e742663e61c2cc2bb1be240cabf6455d826ec26d63152844d4c00d9e80a7b5d71ac2e57d9f806f9cf4021d9313972e05a9f808cb6e957dc5ab225d541d45e031f7f3e556ac572a48a9968a85052721abdaa6f626e085245e27fb90070d8afe92c14a036c954f60d93007caad4e872bccf4f5118c8d103e958f720826ea7220a89ebe226d03f9c87285dde62be9a72fbb8be230ddcf8a95a71906402adc0a3399154f7b9ef843511c5f5790b4f48029ee30c521fe04b027b94f762ae9a5b32a61c335e59f6094f7db2e6b63fbf5955a553ec52e05c1352655c9288134f4bc63a6af148ec49f4ac8567cee03d8e93726c35bcbb6fe8b42a37703aa3c2399a2f29ff16a023fef69483204ba46082fc0ec58f0861837ea5036e3165525dcfe5c328ebb4068d8ff88e5c93f2c0cacda340c4faa278b653bc7d70e4caf7433b0212d5c35fddcb5e85ee9af1452f458b0049f8797cffee84d3573f73c9083089d6d5c753ef66d76da626fb4f106f0d8b35723bd2f24486debf0e70d07e4fe757a67ce2251dffa59d4aa9688eb5c5b6b9186aa01a9b1c8b9072f6f59daeb387c687a29555c87922131557496d14328601bc72fa3e746a5c628f2f91a0bea84ab2b0b0ad0a9637689def58687d715c4b86e4609a7ec9f829ac4ba6108fbd3c10c3611889331f4a12cc4fca09b03250f212dd72e2f9c4931d5d8d45916a1fac7cfb7e82a4b6ef1d8abc81ae976d1515f928443352f119d36df645b0d473c32e9ba2ce5c129e0e17d8ec73db7e8b4830f746451f0813063244f73ed40fda3c209abc8a99e09fbfb6d285356efd186a5ef2ad882d7eb9b03a61b885ab3eaa51a3f50735f4785a1c98c5bdb5199e5956e81d319315c25e29090d23ccd9a07cea7b497021a1c284844e78977fb3667c9ef1594e3a720902068621fe5f7570ade96bab52c0b195e6d8260195ef8dc23d9ca247a9d8272e97d194537c0b373e87a6f4fbda107a0a1cb9b45c204237042f99d9b8494072e1415b4ef4e840cccc73ccc1fc80464e0bd947661259fc8de2b02f4b49277e72615e03b359ceed4a1942933358faa7b01c2566a5c849cd896bdb332e304f604eb37daf5cd01a7f585ec3ee43c693446c63138c05a9f804a567384db7b1e83c729eede83c57ed29cd125991b8ab018c18b707822ba9b400ca8ec2fc89473a097260b4b0b339a6d50995c7e6a3efc7ae89b64131f57652f82e387311cd35d9ad5cbc726398fd5d04dd9670727d359485af9e7be9ecc5a21029b176a0ed2ec7347251f2c895a82395e3b81f13d3e54e68b475f56a16d7e4664fbb44804e3d350272dd80863a24ee12c1ce309c3582ad579d822bea0b1858ba68179cc7e9dd54a972221996a9a7933277635db6167d4bf1bff1e529ff4bf4c4b6a2829c61e537a00e8efb11ee80b0370a6fbee1ad57a0b3a5b19fb1d324baf201125175246de087723d0fce04bbe30862f7d965dbd968853293f178e4018ba64252cff02c6bb4316ab33f66fa1a93a151e4401d3e5d82155f20d168dd74c9e4e8092ac79cf0182571874e381d79efe70f727f984e94096f5b138d22517351e47dfdfca9e7584e1f50da27f2d8bb6634fcbf128809a8a53e35d7fc76c9018a370bb35464c097dbbd72062211d29144f0a6bbf3372e57bbadfec2d4e947945587e3b8870a1308c01502546354b69cbc45f4df72eaf00c817d53a6758821f6530aa39782a40018c03e3b3ee21511d239483c28c899c5a8abd38057b5654d3f0bd95d2011d8bf7253e0231a529f26cac199efa9fb6015fef96da13839d1c03d5aa8e2c65e99f0eb572a72f211a4d3da79f583550f309caa8f5a0fdadbcae3023aed452cf13c20635458497a3534ff978145455cefe61fca3b92c1b0d7dd79a694a36c5aae06e280df787246226cbd6782155a1cb4e24651df3cb0ff1f2ef35ecb8a3b5b5d649484d78e72b9cb5ab21607502fc7ec2de5683a6dd8e46f109a451d371c74df671959e6cd7292dfaf9b63715922507645e28f2c520002ce884da155ef70c29d3e951a5da3039bd37038029e8949a0023f4dc38321dcaf7a690849d0277bfa23bfa960b5c972e1388823f5059c85043a14a5819e84191f6b450e448113cc07514b76cede050c6e9207daef45751fc390d37839b495af039421220ec8f35ac6c50c8d1214a572f7681d8bfc9f35497dc4dea5e9e434776171b70c80c38d54202906d156c5866ab69156830e9d13fb8f32828b1cf87acf6563f1286abd745b5f2af4daba311d72f802ad531ac24d2ed35bae84e3ca0a8dc6a17f22f410b5376f318a70a705aa725f94c1b568394ddc336232adefc2230ba2ad2a9cb4f82df0dcf0c65fb9d9fc620918993e06ceb9e47a741855ad36a7143ee281737ad9bc324a2759db7c9ed33dd965569f9ea81ccb73cc5b2ab2575bd44570932c47f78b9b9286f48d1cf0f1729f2e242f1fff9cf3485111ab8aea343d3f997d037c228827eff5a48a43012631771a82f52cb0beb1847bd36dd8c6c722c428b60a58e9686fbcd8513c2c6e2536c4f7d793da761b4fb41377a9087bd6fb2ca84f8793554c41eff616e744feb326e5647a12de7f1171a2a2c37a0230acd20223b09e3621e749b3c40b8d8b62760fde689ef60f57100306127c12881f1f5bcc4309757f0a8a1c4cd4a8d9a1f47d72226596a1f0d3cc3cd8d84868c94a12336d5048f62cb86fdef099617f1644277270fa9fbfa04324d99615f75873ec641c5cb0866e454dff754803dcc4c82dd748548b5f3650ea84a2d9e446bd9aed594a37587beec974f9d20ac6ba904ed04240554af37886c43ab7b4b3f90097f95359e8cc0bbc814df9676a6339fe7a90a91796d37290d1a46f6f22abce5d15bbf867a1dc9c5eeec64883f5bff6a7b9fb07729dd70bef8a9f4748f9e766e346b1f6b021b028928564b538b429a0b00c7e8234c1e48d0e8886352419dd570fc701e427cdc499185b7acd6145dcafd3eaf0fb726ac3329e0f2ea443068df07dbcb297e85287da02c859ff8291502155f00f0f72244ba73f6f5723e955b1a1b33347f7ea2d175844b192a2924f28699bb893f565d0d96bbe73f494cb7514b6daa0f1c86312dbd6bfe1f2cfc67381cfe584fd82727e0260e5042660811b26356112bbf7fea4e1d099f36d2989c12cf21bb88b0c721039f4d0ad6ba6546e4db26c506aaa235d80ee33820a03ba3a6e500ac75d1b1873052f71dc252cde6278e5adca7bf934dc1ad33ce22f992e5b9f098e3f5b5872ebc71043c3231dd4a0f9ceeee850c4ba33ca6d39aba81eb15e401f2042b67c72fb86e8b4f2e669b612047c0c1142b47e1e69658fc2c6f678e7e2211f394f792fa7838aca058869ca14e7748dc0053d04ae13df1e152047a909c44049e1c5d37216361749a7001e15db5cf78de98168dabee158f30a017292009bef633ee41c72633b293d86ad1c2f72e9dc71cd6fceceef7e38730109edcdf905498d4a64a972dd152c5c7eabf73a2ebc26217826566a6edadeb998db52aa890c5d2035286f72ec0c8ba864b3038a197ade9cbca935277924e102cceff0239f952a66a25bd172f68e3d8a8055523bd243093d0193cef22dfd0874fa2b8ff2b7fce8f288095e7291efcec5d264f7e0a592f8e29f2672d8e7628a803907b8afe1e19db47facaa2b732cdb0c45081dd3b8fad23c9dffc81cfb6e5fa22277651e918c94123a23f872c14708e09cb72ac9ace037ce95c062dbdc56b7330e893d6aa1dc207c9a9b94711f0f70f84b0888640c34aab225fa41eb4e30fd2159f31973ef447c7b7498c47213e04637c8eef29642da02ee9f294d0e14b34ec679c6e1c33df7a4d8ee001e724e091dc5e084909f47f71576116b0f1946e3529295057ace2f5e7dffaba0d8722ae7d2fbd3dd37d68b92ed6a37c079b944947560755bd364895194e9a110b1722ada4b7ea183f24751cb8e57b5737478c83e8f1082a578f1e179e0f1aa7d0b01a608feb838923400cd83e62497e786d336965709c5a3e0e3981ce2660aea9872d8689346c7842d8b5d9dad606eeba345e29d76fd369aee2af9c047cc8a596e72f84540fe781dc082a06aa35c9a39628c9f2e2caab59ee02a3f32f1317ce9dd7280955da29452ba207fbe7bbe935c40ea5f3a3d41b0c420e35ed5bec25ba49a26d1108368f786658e4af988a1c19ff7a3d1d2e169b37c55ca42611ebee66bbf4c08d8a09402cb8460b52ada38ded5f3bbf8e483a29fdc07e014af381ee63f3d6e079c9bcee2f6e1c2364374488d8004cbec27482120454370d58eacddd6875d721e9fa58f32a1bdcf64fdc584110aef5f0ebf6547192a41918dfedbc32561c3359b2ac7b305f84395f726abb2f2ac4d89e6f738b3fdd5c4408d1d7a5aa319fa72ab052cda25ea917c07e6c655a76023fad0c9e896d092ddb2f08ca04853d61a727e8a3e1cb892d863444899803a9e1f5131ad5a3a085810ee99f4be693dbb616036c0d295df5d359c3089837fa79274f3ccff2e6879af691dd95d675f290b307201cc345285157f54d7e884326d14d029c9ffd4ba907bbd583e3e635385a1d67184ddc72f477fb1c57d9e766e7bf05b1683422e7a985eabfd4a9074479ee0b5723bb23505385e14d13e7b3d39a7dc9fa280a263b86fc1150a3a475f025d1c0372873ebe2e5f144030ab69147d34108f114b4b70e8519081e1dd6524054eed0438c4b8572127f77c152bd65aa7e46cb80ee59287f73578c48ccb234322ccef3229f202f25f3907738f8e1f5bf2adcf314e9bc906bd9cb8f2e3df3c049bb6aa067217f644ce40bb6f9874d5ae73e0f8547dd218ee55f261c05574ee72dc2920f172491ffafb1776bef44ff787563909ac151537323ee6bfae743828dcb4b198697216be96250e504a3b3cb839b87ca479e76df083acee490f349c82c21fbda56772ccbfbe4f17e90fb2f4767eb386f114598d4911450a4706b7b5cf11bad1aed348c6559a014cd02a638ace3a3b7adeb7d388cffb8a464f58f139150a5c4c8d80046c6d218535daa6833de135201d356bce95f757261913e21dca4e5a008db58272b149be9f00c4034f96fda0896f993c8caf36921471330b4a5631e44f8f8c72707b7683d72df6369bef6800d265b1520039870d6073f6c5103b0cb09eef9d7d08cb3c1eec93ad348c13963a152a15ab69d5240dcd4829bcdd46dfab960de88f6f3ce84595b96223f572b612ed6fea2544e64020c1af52cbcf84750ce99375e720e3ae9822ccf7522b57aa52350c3b7f463c704aa2774f0c1375d16f4fc0c86a7273cfc89ca7d0efce95690bcb9d43399d78c3671449dd4874a1270359a1a4617226338e955a6286fc6e07960954569453010406c7dbfe38ec64006d41970403514ef613dbd4ae80e5738df8ffc0b7cbfde3fcd91fd7d8eddd1f2e398c68ccaa5d12829acefad8895793d86cca006689c88615e4aefa16af26fe764acd347ee9722a8b4f44d117fcd9201034c33eb7effab6ddb3c1f28819cf6cadea6eecc6b743daea354e4983cf5e806653a998378f7568b7a1f64fb702da2151a66a7de9a55506d0ea940b5afcf2731d3bd69309a0df603689dff25c20f1745c7821cf574572545d443ed4ef6cc0f1df626a90baf9c8b5e326d2fe961a5c8f174364edb8a722d85d61d5345e8d78ecd1e069d279c71efe51b85009e39d053dcb87062c2c4472dd12362adecd36aa92a0bb6ea44ce8f9cd74ce4dc7b18e4f6a706b1f14f9dc72c595ad6f456a435e92e221566a1f0b5745afe74e645916a935930c1afa9386722d07ff3d75f9b6d7a42898cda6579187983d69e15d84b3574820edc882b71c722f055a6819ff823434d2e4fe33a6cf6792405656f05edab29ad7fedac7491672d50b44ff040db38e588f61c60c1bdc70183a577df12663bb2b3fe42e7462a92a2d67b245f8865e8208a9f9ac327c6c0f878f80c0d5ae7cae99da2a04d9518e726f520dbe6014e1d2796a0cd55650af9b7529f29d32b5e08efaca33fa9dc4dd72eb1f404dbfb85c7c75c043c8eefb0f0912170d0b2092557ad59f35bbe60c790c04b3ff4250c3761eecb8326608d1179e16dec9d600261ee1b019923edad5b655fc84fe8988e07935ea9f91d9a3831a26b370f776046eb548776d563b357223723f5e175f19197d99c534a3ceb1f69f7547b3c54f8bfba9a28fbfae596a892472d78466f04c75fafb6121222dd99a5b9599fbdcda8863e0f9613cb35d23f79071502cad34b0e4cd1e3e4e475c3408ad8d838bd255a5886c85ef1a0ab893fdc816efc35d84c35c80a45b44807e8bc3024b91af51ba2092eae2e48fe5d92523fb727e9f43e3f8f9993fce4b8fe400d725d072d501c2aa34d2a2f396f8216eba662f86516cd81593c8a8df2275cc024611bc484259b366cea2ba50209ab024460405c965b64d33409145e7a35ea76bb8923e0b0f61ba81b6198374e45d44a046dd686e23b3dce275247026cf45c49fc1a7dca740a53a8030bebed9eae2845f16194c7c27a7b8d2160a348f6f30a5a1f4eeca5129c83f46d7c4fbcd4e4d6e2b8a5c726333078622905db2e345e292bc88d1bac256c45cf119f93d7ded9d62fb26e67216bf6389b8ce1a72b7d8ca7f6f0e60a32bd098444eadee6d6051d1f5eee9ed72a1263f51955ef27529af97b7b401e8c379b815ba992fcfd104e7ba3a87aca11c1bd23223be281e38547f7091cbe1e378bbd5746794de0a39f40185169ec25c117e5eed9f1d7a79dfbe9dffcc14cc8feee60da2b14546a266c4bbf4c862b396576b8e26719204099827967b62fcbc5697acf7fd301f8e3eae44b3f44d0a3fbf0f637cbf6f433850761dfaffee3a744776116536e7d0f20433137ea1ac736d4f72e4ab5fb71af204db480354d9edc48e6e54ac6bfec96d56093c1a862c5ae3d6724cec48f48e3a3ea39bbcf4f74d6bcf958efb8076022b14f2d6dbe92c7c8fa333300cb4ca62648a78e2d6caf85a32e32eadb868b9ed814439128e6afc44f8f57222d5af9f7cae353f8fde97f9655e38f51cc7e3fabe10b9c416c24403b1fefd72b5e5a57a16cc68a179b626d937726a1addb4c7e09d02892c1d0dd6106ad90a7287553c9304ab3eafd5a6740b74c89126b25af0b905346a1d2cb893fd090ed172ed902f3f836399bca9df3693cc63d7178d69220effe2d28e2c69f01812c56a72cd279aedde17c05df5993ff68fada09576fec14bb3bbef4ec129ea261672970b0f9244352b39087d7878b0c9fb6de8277805f6071bf08ab5d8fd03b253f07124d840daedf8e704d71ba9afa89e45af7005679e1c9078759ba3442d9c3652877250ebc0b02096bf9763d630d042ce7162005db1f5bc8173b0e66dcb3804e209722971bc678c05ba18c934719c2effced067230d94fec00056cc175916f8c02e4b1252860bd419650d95f9b697c085ad5406986a8d61ebf515a19936d7b4e193346c83db84b303af92fcbd963ed5d3da08545922f432a8e9fc94e0a7ed40d0b872835a8571a18280f1797ef7c7c07b06866680b6318d0a7578c9e41117a9e70f42db070fa1d0ca9ca7e3dc142afcfe3c33e87bf640af0d18271bbafaaf9de4627284068b5943096865d77953377a14a8074e81f446df81f807ed467c832b825472477fea1901496b34344819c34e4d6b0ba36775e3e212f763163a1df8c5a155727fdd4ed77f3a15eaae75dfa6d27414bc2af90f6a31500a9b257e575b870cc6725a79f07a5799b6c52a07f19badcd77db1999b4ccd953640194e19c18613c783f1e9040c87a02eb528c482306d912b97e654d530e7e7d3c5745ea5b1bdd1aa658959d37829b12e72bf72104a3a49ef84ccb314aabbeebcbfad5f4db812dc670308086b52ede8f983afdb8f9a11d6f162276f9a0d73be32d6121b1a230ca44a672d3545d2d41132e1585d653cb17d4408986b4ace3156aae2e0f4bec5f66c3047269c386bbae4999fe182fd0f44e41022a8dad61d8b6abf8f60ac6ba1ad3714956872ce4c17bbd0c03e0b7650655cd5be6beed1e5b64ffb6d788d144472cd3ee726cc95b42064a56e0cd900ea5fb22bc3100dc85e335d28101aa488bcbce595d721d6a9e864c609edebc91c78f4a5f1e8f29754d8cfd210382098c7e1a1b0beb5c7e0440f2366ba0c6b30f047132d6a0ceb429b5989e22938ccf35cedb63d289727396ce0cd09e45fc57ee55c1158c9850cd547d63ca771caadc75616abcaad420fd98341b62f1a589727c9a0ce8a99386c1bb94f64b07acf285a40a1ac5a99d0780a32e68d33194ac0ee7c07ad5c4bc9254d7d37597b895171d1c34a2b5cb3e72ca47a28757eccece20f2f9467f6983aadcedbc22274beffdc31b45eb3b3fdb7224a1ccac184b324629ed8ec2fb65809360a5b1134f24e6db0041d5bfb2f3fe7274b6bab402d18c68ec9847908346f814f0b3b3a502b49de1d2ace124c03f38338319cfa24755bf0173d17187423396b462878d9f42e0280ce5a3fa369610a3294b4b71fe249efcca40cfdcabd713a1fe109601e2863bf1dd3d92b2181c3aa572831d16150517c34e5bb0017a23a8a840488ee7c656de40dbc970899ce0b04a4f873037cfd3e9708f66f448e00edd4d7a363fb66a48aa876328054065d5c2ae301f245dc9adfb680aca19329969bd94d40f46d0b3a80215c036d11db5ab18c56154066481a50a6804c521d275f8c48b3ef6389ab5d00b17c3b43d01133166f872a6b10d5a6be8f01fbe88fff10a3d003aa70ac45a9100d4d57f71ad37bd62bb53a8ea7116f95cc868430a2acb65cabccf1be56a3e915599284e8f1bd6f5de001f9709bea1d2f42415c92cda192d8232ed24171fc68a646437b668d3b946edb672150d4c43bde352b7eecd41966daf12c205366bb213fd694b22a8bbc3273b961601c410888a40622705ac4085aa13c81874f59122ccf765668952650b43ef6d091eff1dd5cd0b0fcd8db6604c568eb6f4ff7acc19990f5cf65e001a0636febf72143255d8cc187686e7f8f2abe0a2a6bc88845f80b6715df5db8356cd1afb01728b995afaae20a2de383052832a549522abb3978759fd6b381e721720ca4af2720f3c055c0772378e3bff93701ba5ad91c7564c2935ee5e7a395cabfdcdab04725f2b8d86e55275bfca941af8cea93fa81753fd30ddb1565ee461c5b33de12a72d1b1f0500c841edb6e9059547b83b523b485bb84b0069a216a5b4e6eba83c272df094bf3ef928a8107578c237192474b5845bb1dab7259d938e7b6b0df736128a24f466c01765c3e0332c6cf831d094ac7670b3b1fcfe8aaf7410552fbb3542cd7f9d3a07542609df9b2bdda9ea52623aefdbab3a3c466a3590a059a4cd7b638b44f50fdc9d625f15cd3a45dfeed61af1515db85a0521d3ac1d9d6aecb37d3724f86d8b6ec4c683d54751f3923b1025828291289be577f0aab399865bd148572626af8e1aaa11fbabdb12a6210a989edf7a7584e72c0204644d62f21a1d818729e197519c1379e80e646f4da62c33d56c05f0b86a5ee86696c9ee1715aee8f07d995a5961bdca0f0a733e766c35ba4ec1c71a351353a27cd1826993f610769022cd97b1849ed3724e0a2e2028918e30779d81cced7f67d96fd734819998eca3c9c2bb8060d7ef7e8e18701dc2a8788d2d14844cc69e59aae87f305bc1242e272f18033d0567801fe03d8d4947e63bf254c42f27c1ab4decf7cd4c3a5e2c95172c72983f5d0082b5c54e853f5620b556746b0ec2c8532b6d60224c68f551d9f0924b19f8af7f2443f1db046ce056ae0540d2f4c8598bf8234e97d17ef9913ab727018c91b000ac2caaf088008078ffdb6a4d2349e5d2aaced8b67a9ee3ea53746044221de227a6c392ebdbe0c31118fc8c89ff4fe1da7187d4f3080879fbe3b0f7b38bd2117b0759e864e8bc07daa1eb11bc70854b3333923f8ba67a7ad711b68b19612ba6f200551e44f05bb7ea7b71a3819384da7d3ac4c23815846ac3c187234f099f114577f77172ed498ebcf05126ffb9e90601c6fcce23f3a11bd6c5a5db63581993e52c9469fb0a5b906d471e25403e3f3030a7151412a45236fe8fa72ef7bda78a6bc26982eb689fd81ab9f2586fba9dd408446d5075dbc97b500d672b878d9033b7b8d3e39762b38cd4a8db4d279c2cb8ad06c1d534715f68b514672345bd1aa541138b3b6d8fef1cb9e739e8a826d9a67c3d043f66e2b35ce85b824629acee4f2b759c40f64606046ca008d8d9c83211d597f7c78eaf253acba00010016d9bbca449277489db4a864b3bf4530e84fb4e918fe3bc1bba4def272c8334c02a55816ef855aa16778fd6258fa1aaa2e19ed9bebd3dbe4b9a1906a66b45a08252281a6da9fab2d22253113273d4d591bae29994e9ff2d6285fd17a22ea00f3e1c7d09aedefa4f7f6d37253e73c8f1237459e40f8ab63e99cb8d4fa2c4072108202fdcddf09aed331e6d0ff0fd903fb4a8471b3b29bfc9409e0d5a1ef437228a11ebfa4067e402b13b92d61870fd10b753a2fb16e504e01d912363667367227aa2a86f68c6ef4c9fc3d9317c1fa7aa76bf20827cff685bd2c1f2ed2cffa727a68323c8c4f5ac290f354deb770200d67adb2b5291b3299b78192a5c1ad290c265def399f2081dc21de88a57b063bfa917f3120424cd40f2cdc181738dd367296c3032b39f223015e9386abb19355b605638064946c09d5a7c1a87f6bd5bc7280518ae445bd3cee5e0969da35797bd14dcf6c45189a9cfa20d8efa553789f7130f8bc9e8baaca23aa82e36c34c9235e78936a1eb24dac88f46516bf67e980139359785be716b89b0939993bb1ec092d9e9a86515cac216154a871cce01f6158f93fb60a0edb9bd04c236311c06d4394d0edb98af71c3a0fed3888351ce6aa7245c396632683e8d1a768915de2fe509101e451acf49b11d22aa3255071347c1a7927b3d0cc8360796138fb75c0fc2ded84a48ba9d48c6e81797ae6467cc2f110373221a7c632244d9fd2f0efb426ceba9725330ae201695f18065de47ba9eb72dcf43910b2743048081944f2e44a05bb0148bf8b16f96f2d17eb2ad7ce2aa05df30e1f496df94930a970ebb3678acd170abae408d5fbb5ffb19c5de8cb8c5672e588edc07ab33f71cf61cb74ae06c8bdf42114c64be17d077c8566f8ed0c8d7265b6b1e2ca0d77bee4a5ce1f11013362c0a1ba9ff02a4ecd6e74fb2a7488ed7223c9da15a2a2fd41cf365d56c30ff053b712c0939bb2b986dd74b710e5ceec56b1a954677979e6b0ae590b756ff27ddd0a6664ec5a85f8868192209cbe95ea725b008abf2503f12fae24a9482c41daaaf78e616bccfa8f0ada2918ef35bc0672d69dd8885019879561e28d722893c31f8b389860421023938266769eb79a3b72f62c3ff31a8b34775fe6c61d2f35bcb51fa0888e88dcebc87dda4d479ae82f72902c365d2f56d77d03946400d23aa83cd5c67e262a2d45615bbd3ff4d6031272f64a44239536c01cdfae095b45552535326de2d5b69da5da25b538fbc4d0f4722bb44be3b25fe5b328c2fed02da8441a8ad64fd6ff779eea87084dcbc93c9f3310186074da8e6782a292892ce5fac9be3c7a05692619e1a7effd439e280c98724e174eb5f7256cc90496d43cca1d07ec630e1d4b3107e453addc990ebb27d9101028d70da0ee2957740a9e748c2cfbf4845dc3b07116a43f3c8ed90ac883087273716a99fbed603d9dcc3d04340fc280dc930ee27cd2a29eba05fb183088b440dd4c92e4b214d3fc61588815d69d0fc0cf197d747395c2677a0b4bff0bb0fb72654e794b73a71514409b3ba56a63cc7c43fea331ec04fb31265a87675a295765b8ba81f19ba4e74f97ebdd6b34f251df00d7119902f4c378a06b476d8d084e727ce813a1db636461c5e4025e4d3228ba87c4a54181c2bc406d365b5850167058401360aabea33f0509917cbb5c827b6471ac218243804abab86226f6e2986f72cac3ce1811fcd77c00c251eb0f9255aedf7ee9c482aa080d7165cda4bd0f9772e110e965ef913324274c6625fe8335092d23a48bcd9a4a3c2a04fea7b52a80724760750021b810a8e24792283d7e477dbf80ace98b2ca561e5453383a0072739f987403d5fa6d27f4dba582f0d1ebcb7d2e997d8cbb1f3f5f77effedb1ece22a02ddcf99b7543d35d03a7a356b13a7dd9c9590cc5817d7491f3aa22b0c77146b3b60361fc720abd02dbca6d6acfd69f2cbad4a80967d1071ff7772241a69e27265eae0bb6d76e58902b036459c8c01214cdda1ce486c5608dd5141d741b48f357a732433b0c7ac1498b12c5d01bf51483594d617f236623f6516aafd8e29623ef96926513d195a0534d1a1e48a8006dca075fd73017a0ab454f291f2b45ea14e891dddacafce297aac556a2e9e416175143aa3510f63a8cfc4a295aa5bcd9a722fe80d3a00c15e69662e4283efd392254904ea77ca1ce0cf6946fd40883eb550ddd377af768da1cb6f5627a3be009ec38e0a6394630e767928a878a7313c8817fc4b6aa8b656d1093e4ad2bd07b9029f80259f70512f2135072a052d441fed324387ecca0693e841f6b4e5b55633b863684b29c3e4fdfb2ed40b39480aca18725c15eef0a407a960e26829bcb09b95173e135ba307437baca594aff0f4b5d9721f89fa1c2fde67e16d677067c223ba695f22e3db83932c7d3694837b8485400679fb5981be0a6c509d2967e3756bd66e5fc06dcc3f7c13b163181f664aead520b22eb3875529833c5ba2a941a639b4dcd619a3d4aceaee0c17618b6c2fd6cc1dbb9baaa7387c7ea4454e927152fb557baf6cabc8e624e035de797d6bfb6a6d7225ff5cb97b1958a0bc6f2d4e0c0ad1a2d35743217fa37fa8a4716758ed26811c5791f22ec0ed6f16fee7c2c91b661d2c031547504d97ce920d3459f7dbd2f250823a5ffec7ea8f9ee671e71652f8d4204577a19becd2801b5e259e1cc5c1a172c329095382cf39d62f81a5a3b91724b3361992c5174ec1f4042228ac204a357222d2717456d266c1ba12099c776b5c2b54ce5b902ee0f1c5331c20464c9ac4137977357538f6371ca64fcb67c904212b1c0d8324f832bc8fcc999e0fd8c1df25fe6a766dadd26119f2e454cd3e697cac6945907b91d2991998c531010b36674c0baeac89824d99d3fce1f4badf1d7190e1fdb07aa840e515afec237581c1457228441c0d27edaccd3b69dcc2e4d3455f520f78a2aec9bcd012684f31f1be547261e70aa1d010bf2b5b1ed231eb1bdcb6e3371b499b040ddd1877f5dcd4e1bc72105775c0aec9d5255e5588250bc4f3b5770a4cbff6c359da243d1814ebbdc351556630dc76d165d3ed5158345e894af0bdb9bbe319a5b2be0321b36f746a6e2b5384cf46f8d02f1987a092170cb462d8f56e55b4c7339ed06a404914e4ca10058f9eeffa4b3d6bc2efa9ff4e80fd0055c9ffaad97b9c2f0af972bdc75950ee56ec06461af7c9296a065b0e9253a7fb4d24ec50c74ab49c4ddc1c253666371721d3cd44448f322b0ef3b77ed42ce34e34135abc35210806145c78f2940812fa2118797c11e863f0ef4cc2218c4e0020257101c9ab62bd5e75d5b5e514f34652729a15ca9e48117087a259d773a1ef1cbccbdd7355d4966862c1ac7d16d6c9105f21fb6bf2282f1b9bad2ee954b84e6c2579859ee41c5cd1881f794029f1ec7644e2937219fdbe96038c79a7c60c2af1fb2bc705b6b7b6a1c479938a850fd3ed72ebea49a1552808e0c0d6852d840976324f339f3266c2b757bf1bc3e6ac55c872cad89f25162362b0a2669f187f844a0e045e10dbf7e45de26f12bd06af7a9a72b45c43209910aea53ec594c446d57d95bfa968819b2329fcb431743b921885725b30626414ad60109d040e704f1f1633a7f5cd0e74c9a8ee03c5ddb865d91b41db83b2c760248ef14c8891aee9d9ef5e4571d73c9d9de96dff5eed80c99f8472873108002a7d05cbd6d1d79ea0dbf2a4b9da19a032869b10718e822b68c1107269de5482c78b8e381543276bb482d79b70a76b015cac94685003acde9c4ba3722566a6dea0f566563a1c8da64d8e38ddc40ca82de2f5ebd57e7f49f67f385556538ee2719a195e05629c9c689b3a13b44e3c67582a0b8751f3d3f65a2616b9728be16e8b2abc9e012c9739270d4e1ab06f959e7463db852780c4c61f2c8ff2728f30f9daa81661aba9725a3282c3379d091a048a1d1c86e32abcb75414fe52724b6e5322123a9b74e8eb4134a49ae1341a5544138a18d183ded361ffcee25772d6c865d1a0d3996895e65c75f7af9e97c2d39a788b0576c08ce6c9ef6bd21c72f86100a75f85104fe9886ea349c2a7351b70940ad08fef50d9063c56b88cb7723d0a39bfdf0293b9b1d4034cd25b4f3ff3c5fed2cbb07b590e4ff456f6885a72db46235824ab59faee8fbe00ffba02af534fb262990b2b83da77fa22ce7d5f72a27aca57263f1de9f0d28b7b9f5c148064193b35b445cb74e5271dfcba32ee72f9492c1408f44904611d05cbcc2d04c70ae9b5928a69d6496520bd204e5ff472d9d2481e5dd94ef57e038fcb735e849adb9b9674b7d272785a38deabc7af0a72ec124673eebb4232d68859bede4d915ffe52ba99e8bb6a832b3bd2d7a7ae3f10e003fdae04a0a55d2f338966f3bbda9cac103f42d488cb016fd121dff2e2b26ddb3412f68e192c165a8a183d987ce94c0b19ed645cc12fd991fe325e35b3535dc1467c0f54a8c65101bb749a790ce02f5ed3863a1e46288015e82db93f3c85723112faf4cfc68cc2f0961ca5ed4b17cf92788abeaf313cd44741252612053772999bc0c070eb92e3f0f3dec2a5de426f6738ab920d29baae78715f490514c372d72b385d54549f9452419534b16e0c991b5d72d0668de38294f90f69b8d36548bc778ff4c37bbb28f2633ae5691c2f69711a30fe783285a475366bc889a3ce35b1896741d93e40eed4e1d5f919b32f4d90198ee3c39eb56c02311dcf71e34312418a421da102800cda14d5fd2939b6d4d442d72f9e7232fd456ba3e7d3aae372c6789e2b6069646d9f0167daad6af515a559b0f9042d19bc1fba9b6f2a9511727a5a0cf99f5f0ad6d2707ef353b4e7616102bf430d85bb96e3df32542ec9b82db3ffba119a6309f682178783f485a2e39aaaa7ae446b6727bf3365965f92d917e6c307002254747d5424315ffe5ff2489d749c5c9c95e0a515a2ff047584947283c0da1e3c3f120abe68872a46e62da2fc03ef2cfcc1f8b981b33a2b30b48225757c07088353885b4962a0f1bb5d7f601d897feab6fefe8c1cc4da313c91157264466053ffb6075db8089333c3c9cac5e3cd585b74ae5b8b90cb3f6454f448726bd814afc0b46699c7809f52ec65a9b2de5347d43b893d7ed2d4ac7ea05bda728b586440d326fb53cf8912fbf477395c80790552eef127a1259593b47a2a3362d32c4ad7593ab73098ea8a975f81b9fa22605839b3cb163ef430b63d5c5b512b702e6b7997e8c4596ab352e7087f0c831245f570ed2fe1b886cfd3cfdd69be7260a3f08cf32119eb5e9e61fe3f1b4d7f3c3d0ecd2a59f933a05e1b8db6bccb726938a6b9e11cea5379af20694cf3f0ff9029342d304655469c4f18ade6f0e81fe8a7115e63fc3ecd653cce356fdaf865eae28d76bfd682f3e87b34eda66910729614b4eb13f357ce73724536bd4179366df5dc6c9f6f21a1b7c567ffa2fc107205226835cc4d99a0ae5bf8a472b790bd2636318aac2dad04b9672fe5efa0f11919fd5593b4423ac2bbed756b43f7b50533eef0e341b200961cb4ff941b97a17230a8a47800006e63ecf7269239a6eb27c8fed578c20557f3e5132bde8cdbf10679e880cd70a1f5134e390eb9a65e2196f0bea8beb218a0533d98cd67b7eb74237bb8255d33ce1b55c17ec16e695ae724103a2a7ec49291edb406568b43cb3972128190889ffc8495ab30f3ba822c37ec93fde11832b4aaed470efa115fda1311989ff2aa1d7a1dda155ec5ed33a8ee105041465c577c9aecb40f1f41796d1072acd05baa56576cc453577006e417920a89bfd66e30161609ecd1d26d49632e4bc91002505e24377a028f79d68c64dbaba8922de456ee606656c96a94639a3372f1c103d94005ac2382847f258d21b476d54b124ef031f7e71572045724d76e72789834a991507445c2787160e8bb328663e9737720bdd908270235d28fa0852d819782e7e6f73612b9d7ae95420a6098f600828acf900f38857d03c252efbb72bd7dacc5a6e3019b96a7935975bff0900029bbe4e1134ef3b6c3abcedae08a2103b52d90bd2a0f2cfba909cd314390d68bbf5bd2a79219ae81b5e69c6aa33c415e4193ab8e467cbf1f600574c8fc869e299eabde115d9032d35560cd050938724dc7f3b4e80f056ebda1078c488923d3b0274949dcc1b55cf92383d44fae3372f47f5d2ac5692e25a13346a604ce2067a42f8ad39f556106027799b1fedf6b3b297f37f89f3812e117c7742ced2260e91ea21910b66a3dcc4226ddbf7222f7350b05590d24f0b629357889825c1f4d1d1eea1251332d5388d31b5501a2810b72c9e18b486cc25584acef67ecc5dcfd9bfb8e9aa4547631714fe1e4bcb25c63430f6530acabe706246ab0a3f3b0de36b78ab29d6f1829ab4a4deee06da6d2ef7200fb82b138bc23e938455dff2c1488d6deee43e4d7970fd6186f42e274df54250f2b28aa95e611580381939f74879ac08fc7990a21401e640537302b7fc8fb7249395649734d2cd6f1f837a6187cd3921b5730bb35aefa8a1e0e5d946dd3ca5a0ec62e51447f88ada202e8771eb322b69dbdebb22b78574d1c81afbf606ebc72201b06869f02ef10a70d4b876144dfe0ec0c7db8abf384b05b4816ec474e674e9cb3890a1c1f612f0a02a263d13e68e557a296879eef47ff766d2824f0f1bb58c2b9593c269463673634e768d0b6111089c4fb9f3512ad8df43f4e758e35c5019dc0a1bbb6ce4d137154f0195d844d85d03259142963e4748cc213496155a34b0bd34c8e30b2228e7de71d00ae1ed84e3fac6c25019f9c00e56b4a3c06d40d28bc5d91ef2ff12326a8d8928be4bd6ba426dee3be574755e57aba9d3bf2a22c0256a4ba28bfb0f111b75a6507d0d31059c26c109d988ee801e8df99deb923cc72eabdd7b67606d14317182235d4fd773ba6d2ad560b070acfff3eb4f585d69a72a9314034d3d8936483a61017fea7a17157e1ace6101e0ef484fab089a753bd7269aada55b99586762c949c8c284c8a09d628cc87a3ea3c67e1e562c77449b5727a6a6c78310c70743b01da5ae1d379be342a6639e856af3ee8a938cb06ead12dfc1c0488a66026ae4610d258adb5ff5c4c1a67ad314211fae90a38d21f482d72ad675110eec230c08f3a0faaad6b5a8c2dc5ebf15807ac1e701214e9896209720def0c07bcc4d45d198714bada2aea1820125b118ebb9504cbcef49494833b727166ef0e8c32f597bfa89258d4f87a5374211bfa524dffa6d5f2fe52f0c41b728267afd3c8e60ed2089bd39db6e4e117febf56a1154059bdf69807334354707250bddea730a7531b8bc4d223688b6e4f7419adb8e10b44ab2887c72d9c22c82312f925e6d4595778d50082aca1ae683f59d6b891da1fa9299d5ce777ea486f72d25788e7d8036ac9675dd99e8f9b46d94fa50043c3dac74bd7d1e8a4b0ea82722a0c66f379fbc8ed9781742a3a75dcbce0bfc59b9ab30ec8a24f64e983e6ea048aa86a1cf77b897911744a5a7a8029f4bc39d5449bd8dd05e6ca1098cf039c72f1c2ee3ee1f6607d55ee34a95a3d6ec7f8b9a2fa5eae0c8763ae8b21c6806e726b00ae7e9b682cc3a87c6e49e589d10dafc7c8b130493fdf24daacfd9c75e572dc6d0d6fc8505b641fccca7498141449f718f2b7df6152634ad229080937b472f5c32893e5ae3c642312a469d5f777eed87b9998292fe7810d42bf64134e2a72725b6b7a5d03f00f5bfddd70fdcd1d96a2bfe743bf98e74c27c7a9a03b1d7a305550c226241ecba6a57e8a7dffb585ce3036dc8033552adf9edf0afd5b84b7722f6cafb6acbff52ad963d569b1a2f55e27ae2ba2946a37c30177036ae2741672b584800a7ee44837a501802f1a4548e107d23c8b5f71870780140684851d39721ec5b47043f3c136b162f1bd5d11058381a83ac664767d28113edabdb242972a46347348b576ea3d57b94867d3b84de01ed47716d5d359b2fdd6b9c075c62341d8931810e51807b3c91f67d53d43790b3bc5a786f7bc67c0e81cfd07521625722a62008cf208ef2e88d4ff7e22856f6ce382675cef8a30d038cd7095c04e506f34c59203344a48185ebbd7716739156698ba3a41e932cef3fdcdbe1ba2936f72b6aead5446db14dca279cc666ac0426ddafb55b2117eb3376079f48647d02b726f7f43f262ac3111b91fb602b5438d7e450e6b933dd5b577de7fcbd25693bb20f7328a35eb82fd5839f79cf5060adb57c4a56e1617ea37d9c2da1c873b11616d4fc2c6934d0a4418700b3b102c1af737ca127106645581bbdd628a8d03334322f0989b20ea4d75df1d091907cb40ff4419a5ca0eef63ed0392e42b10398c455e615dacadb1dd3ba80bc276625c47f0bd0df35075e8d3b8b5a801154ab6e0a7723e2a46d6cd5f8992a1c5cf6d10b4779426030580e4565e6ce9e753b5d8de925380e009c58238b24baa6dc201ce4f23e72919e5f871aa84e6b4663c6a23148d7276d0f99012f17f2a576fa7be702a361f2bde2f0061263601c2002a1e4487ed0c0c621476819a0b82daf4d7e0d5eb671e02d7d9e723b7407fd00f52f3209e730c7ff6e8164b55e26371060e7919adc14c2fcf9852da0939f2fac59d6532b5b272fdbbbdb98af3ff4401cc8d2885084c4137510c6fd640b55c9fc100bd2b00f35840eec2c7c59de4e3867bad21d711c187a6bdc984840e89631655a7a7fc74e372a3ca06050b09cae86c37f06cfc7fd659a1a31b464f070a69d7632abc5108f07254c96aafdff97a7b5dbcad71eb64d8b464ff831473745ca8cc02fdf23747076e44c3092685b4c347042cee1481d54d04f61b650645cd9d176576ae422c715d729338d0a80fa02d5056df935a00cb8714169cf5834d012014d0d7b3c6a64c795562bba364c09b8b5aa27464b25fc07e11afc60f7aa48bf4579e76d192296e1a550499393fd30243442e0b3b07f9e1c0c214e8a166e686d0a1f7d17b41b1f61b7216b46bf6d13716ff2c6522cd6ab577dbc07948805f329d5e15edd43d9301d472ff3053e2eda523fc861272464216b44c1211250a4d16c0958830d5edb432c172cf3018174869b23c3739da1b203e0c6f8872c1a2610ae078557af71e551394725042bf9056f33995bd9d0627484daf196a224ac32c4116fb0f4acbc02766a272d4c1b5113a969e2846f4d00d3df7c34846b044891468ed1ff33b7d9302ba9272ba130c9b00f79c7a8b9fc289b3d38e1b9a80d98fc4b64b3277ef3b6eb740b94d9de3cca764f9df8481fbe36e383ff68c0fe9e9646858e84c17c6103103eae85e70b64d56fd50375e74ebc3cab44b75d8980341f3b10015ee3f4ff6e85b084072d2fbd1caf11356922cc2e14db125ff33fd9f19996576923e6501d5a7b6a49172b3b7ca0284493b82ece7457adc2256d13d9f9372314e4b4e85b47f7741366f72f9bb43372986b1094037cb56fce62d0c3d09648e7de24a3d964db0aa85511c0d15fe4296b0217ec3438302c84df2ab79429b6f426d95f7abdeaf0dcd28a86132b75ca919713dcab03fec621d0e8ecbeb432e591b0df6d8e6cdad6658a0ea6f2a7ebd11b27b07b16de7d2da4eb41a26588aac0d3eeca4124f35a743b96bf70f72dc1958fd2c508335dba99a596e8e1ddc06e446c9fe744fbc85f88887d7add772a9dead9ccd330d0cb8066ee2f173e05a5b594c9ab323b1dc4d29d1099ca7fc38cabbfc614641008241ab1281b1c2e6589a4e38985ccfe9e9bac7572499740b72f4656f580caadb5a3c2a3d86de2dc025494a3a393bb0afa2bd8d4b8425739414f0702bdb36560a0ba870b0890316676e8eff82bd2f0cf6cc70ca87433d77a541dbfa114275f37521869a13d761f96cb2624d55d803b910f77cb66e726be3f80d411eb752fdc57701858badef326b2811bd14bab35c3ed169ad22f7eb9611c4721048dc70341d0579e4fcbee048e164401e17392314aa61e368bed21b69d344725d1d07f0ed0511abf1aacec1bc5e0e7dc66e53477e360cabccca84444bb92c258f490d860ad6a83936a71446bc5baecb5e0b1ed99bf02d2e6b2b36786104aa72642920701f3d727bc384dd3e79b96fc3893acdabd8c36d75b12158240c88e5728a4ec9a21e72919e7d9feb3e19021471b60b9602148a792440af5c1cbff5b1722f1639e9db5092a4d108874befdf569651d5356fca5ce4b7ab248b6b34bd3572e12254d4d100dc05548c6cf09e41727c3ffcd7d32077a8caac14e3954a030c72aacdd8c16257a4475445c176f39d2f31b60ce2760dcb077a0ca3f671dc0f67721af22d3f8df63ddc7b133e2be6641ff935b5d5682f29ccd7c08d7cf7b3ef9d725adf685d5e50fc9a6d2f1680484cc97eab94ebc77c3d73edef3531d5124a9572dd2775f99bbb1f3d05fa3bb9c4d2f5912be59f15281f4f35a59e5dc2bebd1d7212b9bb0463b6864e32ade4279faf4321bc95bb099c0118d5359c9c7ebb9d7147ad9f24919db36078eb1b887b5dbb287e78d0fb0ac2e5e0f18146cdf51787fa729b8387704724a280915773f8dc3e204b84d5435030a32e5e6452be5d5487997220d8ac9ed2cfc4a2cacc519b1028005b0b0204ede2a3e214e5df0359b950c8726c8d75d39372c6c2ac9c22b906f7b538ab70e73b2d32076fc2fc18290db3ce72ad16f22709407811d3f0a78c076e8ace1ccb67db5accefea08edbe5c7cbd9431a4cc73115ec785f0151bc27288b084f8618b6e51032805af08ed926c3f97dc3e027522af871648efe5acc4777429ba4f94412cd72260be61409418a2ea013672bb977a797d0a953db995e83e6d16cb66ef79bfcdd82f975e82f49b651c5dd172d998e12dda8805db3e66454b3a0b096f8b50bdca1691ce24d38a68759121590572cbd54e25e61bbebe7888a23bbef94aedd782a0e5328e165fc9c534e933af72f990e7983f6366ebbb627e9922bcb83cfd3910ef05e28ffdbb947abbcd7bf172cfb42fd79acb626bda6bda5f39c33fce0d2ce787e73f9d307b94e8b93fe23272e58a254921361d4e69dfe403e2389c337ebfaef503b1a22fd2dc7ec95fc6c44dbf7bf6feb9fd81ee01a5d21000cd775a988899cb18462cc8f8841d14b8ebd415ac5706a19e86ccc49841ea218a2c0e9205e192f0badd74e19ddbe728269bd42587fda79e8bcaaadafa929a78e1efbc06da24a5af8b56cfbddbc140aa3e9d7872e99be9ad44af309cef71db783cf5b5f7786e6bd2cb50e080faf1fead9b85e2721bbf4a231d3386149ff823bb2f740142e6f51bf404aebf4b81779291855be348b4528e77b8daf7213aa87faf848b69267316f4659075f5c4d6043d35c6535b5c788b61baa2721c25136413acae5964b02775c625a4db622db60a6c9a4ea81472b8ae64a8dddff72acdf42698bd1e93898d61a73f4badfe26f515c57e42731037350231c8ad1f80ff941f0b13245d79a7b757f2d11ad55e40b83d5661fcbe1e6b05e3e1651f9c0000f1bc91910929a14013988ad61f5094ab930bcab2c6fe42300045e00a847464ed7a0abd9489a27e9d70a70edaa1ffd527f3d7296814e53f72df948cbf146d691c5f36372b0d8526939e7bfc29dbb24d613593892173c44b722275bba7e43ed6f39f99d9d835a74eb7f95f4293fad0901799b5529ed1c6c20b5d4a272efa60a5da4f9eeb522f5a4bd8491a121ca0d560fdad91e54c434d8030db3916d7708aea622317d4af0195bfb1f12874b0dccb3cd5ed67985a2843f072643d4f1658a0b7d2f7029ae5b3b23696dc92a6027ac3217d27c5a647f6e04472a62e0e6901c6c1d47a21cf62b08828ee1dd677d9afb170aab995f24757c4f26ed0450556db302b15ff78e6572108cb16ef258549c397620b20db0e9c9981c272f09e699da2711993f37746eaab49048111b8a60fad8c133a1336ad7511af3772ff72c31622f1d12657eb376f0c897db15f5b4aacb0c3b5f94a36ea6057ed850e5877f3db66111b9a2506a5007c4a7d8516896faa4fbeb93b33387cc7d4f704723a0c6026ec65db0a562f8564b1426b941f4e53131ca8f7ec60958a90000f88726925ec82ec5afb0140a2c7a08b91d9569adbc083aeab78ca09e89f7323881808dfcaf43d50b1c6ccedb75119ee4f00305f0effa05c17e7f3dba1c55cba856672edba4a1d6463f7bedc6416cd1388dbcbe2a7832609ebb452e46a2bc813447454cd5f821f2efd08ab4ab7f6db9e96ab7fe318cb844616b0f02088f0a44cffbe5708751252d77b4ee1fb649873729ffc1b0186ddb8821989ae10b13a572c1a6372683a627089940b51e4f78d1cdcdf57bc365ac4e98063f37791db443ee83ba2727f3769c4d2039a4064b840bfc562c5431b4698c3c5394d61a315a00d08d48728168aeb2afc45cdb6f312d3c4bb76722bda649d52bb87d9a13dc28d8efc85976b947c63dddb3dfeb5e8a88a7a526efb0e7b5cc8c6ad3c7ab18b722cbab5f812721b23b0242544fb458e588f6b0bcd0e763ee27085688a69179547b9c3dd8fb072c9188c1dca2a6fb5bda30a04df4fb4358aed6a13c8468aa6de041a11bf894b554f824914530ce6d43cf3f4ed09ae30ef215e58ce43fc33d7f28af4355633b41d3dc090ad6a0e847060f9485f3c6462cc851b7063ae3936d86f995d79cfc1a072832c800e309647e292ce3e5d3d93e50ac2da5766ac2045cfe897c50d70f5b30362d2e9f8186f1c944bcbc6fd95751f7de95d7b79e06a70a3f51bef03f4936747808d3c5dec12492baa990f785064fdd564a5c911443fd08eeff2c72dc0b3895d5592834c4132df1f22e5d34b1354e80c2a2dc9d29412b32b6f0ad0cdfec8dd4f53c3aa81526222b87b156d8b824aaf8a9f41db0fd2a55c1018b74673d3ea6e72c45d834552d5d29c98eac627424394f175d40237d4a5a741272c5eea718df3723d532713cf2d9603a241a0e59dd6be97ee0f83e2240a4b485d6b0cc25e267472f596fceb302600d0f1ef12496a8c71865660fdad78d7dbf9c1f35140b7acc45cc78b7fd797947db1855438fda82ee7fe6f7e95954a9c67ceba037733bbe58072cf2a37140bbed00177a2aad1ef6578a5ba0fb95f5f99e32247d21e69e62d563cb6aef20bc1cf8b79770321adcc94398a4316be5f11a9475e239652670102783a67fdeb6dc517782f48ad778743b595132d1ec76e33f21ff65e3d653f0cb5ba72a87e4884ad105750696cd684b958de7c60d67d585a8f1a272240e3a7e296d572be3e71113b33890fc2ef7e6fe6a5d8a5f1f94b9a2d7f9d11147b6beea290db12bcea2da730d014ca2d9ae43cf4d188a56f7b9b8783b57c2024801480691d6c3f4778e526b09be9d8d62fc69a917c259c800377ac9753bce910d4bf6e8d8aa572c9b94785b2908b3c16a2e5ccfe03068b04f63075c55003f9661432f4cdf31b1b09e0b998845d691ea4f2719a4bb129cb9006f712fd8139ec577c5eea82e102724c19cd53a7d325b384470ba28da6feeb45b4e898e232fbdc29578ca10bf5d71085031fcef4eeaed51794eef93b3c9b4160ae5d95a7d69db531afad1ee74e4972cd25d0118f9e018cacd5eb01d7fbe1ab2345ff0943c1c0d8574e80a941991b72e755cf14d6a10bb2011d503d15b403b61b54d7b0ff6c61ef8f35fb24f48a7e51d8cc3fa56a91adaf4c4e75457079321cafa3480a8b847dc8cda6088fc0fde072100af1468850296b543018b43a0ba0b49428293d44de418381f5572d59ec2349108ec84517010ef3e06a2fe2fd7b4e13456d07d775142ba2d90ae96f1c710a0ea9e0a60cf293097d01d40e3792d87e1a172ed13e764dee17e103e28c01549272822aba26def9608f741493b56f685a5dd8f08c4bf5f4e06d049f139da460755c2342bf468ef3145c268033f40fe025ca962c83b92087e34c77ed1781a4e00d727c93e74cac41afaa55f1ee0e2e24d10d6853dfdf207da0f6143bbc3817936b72aa6bd7231b63e1960d61c9c318085758657af269b61ce2d22c21be2f3c5d93590b5ffc6019c6fa1fcf713e1150a83dbace70b680cf969d616c560b5eb1f7c57285edf2f0f79b9e491351ccd99b0001adcf292fcc79084a5f7050d35c1e2f730c4312b7d32a6f9aeb0d9b3a95a247df972d932b5f8947d62435886e587975262bd6338e97f31942f72fc822ed5026c8623a0d544f82f7bcf606c3808e1030da72b9fee44d4516cf516785d02b375259314bac9db2767cd79b7a613772c87bbd720ac04e48b0c74078782eef22ca3047b3af1280568205c39f93551632d79be4725153a1686666d9154b5d42489c3f8ec21f8e109f3f5e8ccc1896ff943a030e383be6e4d084309cd9cd2f47feef990a04555b43fdeba975eaecf3aa2c52011d6db003a9e599e75273675b4c91fbdc0f7b998229db23d61b09bbe1edcabbca6663ddc8e672cb79f5478ac55446ec225f046648e475af342df4d3f856c25832f53bb0ec27e1f8475e48680317e1ba4199e493e71f760554f971c566bfcb4dd9b26ada42ee6ad9806bbb269be49087c06b3ea0b6b40d1c76535366baa656778e3042f88f50512254b377555e61f1e83b1fcd5fa8edf02dd099ae0ca83ed2704d3d345c3a21559578b62f8319e2d9f13b38b5320990a7eb137a5c46d6b40384c8776cf54b1baef79ff0ace432970668e89d0f054ea3b0fa94c618b7d4ab94e1cf261ca9a5c272c4a1e16b1657b436c3ce2378cab68dfdf4c19b576ae86ead64d5377227bf7ab84578d1d396f4fa14662899e9aaa93691e320e4be906dbf2930356972641a9a0e4b3a216ad001e2b423ffc113e3fa001dd3d500c092085b5d4546356dbf65621ea13179c0e6f770726622b6af2310ef6c7ed52fc9bea360bf33c7122deae181ec1a854099d2853e1298dc3f41db8c32d50c4e67d1e6b51c22989fe95e0bb425d456214ca98c6e2482f9cb5499f3fb71c010c42986db989b09716f2972000b77e0c7c53b96ae22f6d998223ca7948e32794407fa07d53b3f625bd4ec72ca363c10546ddada2e90f01fa577d7e4cc3e2e2ca698bb42075d7208f1a8365e9ca5177f70290bec4a86e476c098d610198bf87e84e959b971dbab8fed8e0e3ce5a1b7695bdd074745ff8a22ad7ce7f071b86a9cf5c64dacfc45bb2c751ca37231cbcdfe464ea22184240f04155ae170f895bc33167fff2f945b31b7a3feb452fe469214d80c9b05987efbfb3c20567ccf6f13fd1f6f1cdecd4e41d0dc83e372ae474c859027122e891b325342aab6e882c18e2bc085b801dcd3832be74d18722b785cdf0c5e664f183fa296fba26a148c7aa554aaac6f90cf12e4f8190fcb72b0f465b3bf7b3120e88605705d8103a4ef5a7d6f16b04c1578790114349c0a720e0d1f373cec0bcc482fc2e7f93d00bd9f2f546e59e04f9a362996b732bb3a72d93ded70e90b8feb53303395094908572026b48ae1bb428566a7bfb118c2cd7200a51fa25dc89bfde8d94c7c2e5cd3251dd7cc8bfa132883770439cd6248e9722b30aea784e876b2903266e6fb19e0fd76a2f67c24747197560908b80d8d1c72b9001f0c4d7acc0766ab8a144126e59d7c67d0dae0f43f9b6e0ebaa1ca4a57725614dbc3d6a3ac74692d5287e360415aa5ad89255fb7af2daff436da69b91f72babb5a7a50fbfa30a2709115cf7d8acde48b04bf509d0c8c2af72e4a27168c72db15c6bcc1617d43328577680225583e3428b32282634ad1b094116e2fa59523790a0e0e9080f04ab46038cabe8ab4684c6afb96616a4d9ff8a0aa180640a872d70be54bb0840dff8bc5f46c9c727de3d28701c1a5a2c929141c64ccc2a2fb7220ee5ea361a45a99f554d9f09cb692728ae51fdbad009eac7945c665df4add2eeb5863ce1ff0637ce443fc10eaa3c7591157ef7a40614939d021ef122e58ec7268840226ed19c180bad3b2f4613b4a39196a22b0b6d7c0ad85abac610cde8b7217252058d81bdbb1ab326fc2f1c93a39e8ec46054430becfb238773afa9f3d72585dc383213ba9ae18ff09b19c419f7b666333e8f4fd0ce276733cc60a13627212d7aefa7a23a93c22fbed97b66b29f2326dea0bf4621cf7b90b9d2739431f5da227128ee3c0c28b9f708841d55c029cbd0c5e799feb5f13981cef9d0e6c23415ebb67e435b56fdd0f2c1b8adc14aad188fb1251486871b868ee017f06a108722be8d769c1e5c6a0da1947b4755a397e6a2ec8cfee67216d296be7ad5066257201ba790d38fa5b082eb785d64d283ee2e9634aa7a6b9be5144777ca71e6d4a725fe9da9b47ad4b96858c50abb4e60f8ad2c17575d14eb7a2f7c7648fff35fc7262fb79c538563e80c4bde53e33539ce9df7588fc750e06d978d18b7659ed5d1323192b2ad0a874c77bd868b40d43d2a1afd64245d6309b2b17d8895ea937fd7245780defe32194ee7c8d7598179d194f5c1510266af10e15b38684f2f3924372d2c668fdb34fadef0d2a7caf543a21b34edede62adf9e292ed4a940e618fc055119bbece44d5ba83ab53392ee20f9f5829e1d53f3b36f7e794e492d6dfbb43579ece769907011a92301cffb36622de2213d7390ca1532ace80468def5a9f5a7270ff24ec9a9c499be867f2bad1bdf109cdd6db20676abd38097d584040f22b72a931dc86525bdf964baa1cde3a72ef18bf17d6dbc60ea7ac9360a853881e7e72e1b7c1c67c9a04863ffea636ada49bbddbd0228a479ab7f2365d9229d74ae97215fbc9533a279acc92ba29e5b7fd1d475e6e0302160e22e80e9e6703ab168d72185e6cbeb7ae452582804fe3f2e8110fcb23f10f2054fd6a977d579daebcd665a5854960432590fe7421836bd15f513c04f1e7ac05fe3346c6303b7ea8cc932441e37b192663aff2e38151a069e85366d357ec38370dc8174894691413b9627292f106b5c15ca9bad7cbe38546508296c1cde9dfcef3c53f4e5912585930465ad019c3442c86d7a3220d3d9d53949e8d532904093a9b3284b665d10b285b8e072c753c774002d31342bda78e7de0101e114c5bbddd94531fecbfa431d5042d48d2ab25e455cdc1666d628e16f76695ccc414e9c8505704a08d6fd1bcf99ead725f0a1cf99c7dd7de1311c3e8df089548fc84298d0dbd72c713b667086407f72d5e875c5478f475285036ac385f54995557d59204a1089b74ce13ad177c5ed068cc0e99067c922e7864c84b11cce926d3d774fc1b28e166bad630552220431772fb84b70a732690ecbb64583b1f22739f5ff99ab0156f2d90a61935a036e16b13c34381b8aa99ae0e0af33f76f0d5a243d366341c9b374ed257540fb534a8fb72971b5b9b7fc7805c85dee167ac3adddbe5f418a32d86d7b8b62290b87ae496029dd6784c02b85100ef5379f6681acd4207de02fc5f61b59c385adc1cc7679372be7592b96e095efeaaa346b544bc52d017f2b91751d89ab31a59c6a46a4ba372e3be3ac9c0bde5e9a1817bc665851dee8d480ba315edfeb2a6de08723270a22da7af53a6ab2b835b11d28d62b708680f08769a8bb8215618b4567bc87497ea6dcbcf0cfdc0578dfcfb31089e0e0298f8fbef894fc3280ab3c0b477f422603372c4d836c2e079ea3485e9c76aa7c53b15e12df89562d93a0ba700737552151872e5ae4cb22f42238df7665a27a87f9ede0669d6a2051ccd6d9163a964174fe0726dd2f5a1bf20db0becbf52f8407e14355989df1cf9d7e0d3433434b9c43e3c49d4d519007e02db4e170650a805a69a85c1ad257a47c3f822faceea2519ea04727621813afca47e3e8fe9ed1172444077b63610fbe30991949fe8ffce9c4bf87233961ee149a929116483d18d1dde9126166d58b70cf71335017361073954a509802cc712f3b985924ae0d14ff8add05ae0982a321d533a3a11b48d33cba16f72526c9e537f18b1edc999f9947d59e338d57bfc9f6fafc1296d3105385d28ba7227910c33ec5e51a1456f9a1854683bd3f5b15647a80465350b7a33cf79ae1b047f5075698a0978965ba521234e2907c9bac93cc8c0ee6e97e8ee4479aaca62726b36cf52bfe1433d61c24cddde6fa967050b042382651e45c89cf2bdfc589941390ffc03d7956a63ef187a180116dcb981bc09c66e059e7c1b0e51f4acb37d724b7aced7b4159469d77c7475eff1632fffcedb20431f9d7bb1842f8d319c6462c70a2924a4ae2d8e2f78bf50ac2bf1681fa0524c0fe4d149e059afb08e244472a2048baf73c4cb3251ebfb6f5b832850690d2737023ed2cf8759c1e46607bc722972de15e7351684327dca0004ab97e10db868b158767938c895125d7ff777728bd42f295d3ef07c1a755f993e6eefef63a3d5998404776d0479d46fd645f072f1aa0c4ccad04cb80b1d19dd46a9926fed4a854058e8ef8acca1d4f0e85ae1365cce1a63e7d4da5e1cd8328fbd1853b1995c7772599e8e91a7fad1662121b572dd11fed089403be9d0a835f24d5cbf0504bc54142a9fdf227ab6b77666883e3b540b32b548e9b020d26d683525aa33308d45cf1c59f48bad85727be0373ab937098675647e0d81f6c4a24fb174b6122eec5b35a2b827727d4435e1f65416c649153ec62095bb804151369dd3c8625830422af21fe1072ba1c75891975d8eac5591369297c27490af7ca92c8a7fa765217c57a631daab9b2e1eee30c4ce742e62fbcd1e2a2df55a05b62f5a1b683d3bb4cf254b3a323d9103ac636d1b2c966472d1016e582e1d0d7d299f07597b4348e921fbd53803edb60d68b33deb243c9652a415faf518eab61ee22a771a9f9fda59bd953117803c0eb574e1e518bc6cde72b751b48b048a0b097cae9378fab625bbb064daf70c9539cc5a4dd3ca4f49467285d14efd945294caca70f88295c100cf4b750d0e626a84dbe3da6f76f707fa3fe38dde1fe61cec1351e73ce666bd5b0a76f05970ed4850bc165ab409adff6372b9c8f942f162e51efe57a8adac294c517f79faee12fe2118d1c178202d0bf7724eae13464573a0ca10c034838db267d73c0e8787745fe4c7e4651b7791ad90104a37d22c0d174c3e65996a17c893defa4ce8429b2deff0e7e5c27945b5f569720fb36b43d0343b48d26c567aea70ed41b45be0aa8a386ab4dad84e0323645d5a79f2dec40f18445eb6d127569fa9256dbf53b9dbf5f58d7cacea5457407f6772052bfd93f3e1848fa232b9d1a5b30ff20d8e2b93faa5b28d021711240200991fff76b09dc2499805849f0c30f3644f1dfce1d95f128bd0f01686a0f219c41d7238eebfeb798b58408fba35d95b982fb0a2a0697099607afaea669bb841fe5f72993c9c7d297803d0b6de07bd35fd5e47e3c334959758feeaf19d7106f3ab5b7200d4774cf07b8e155f8d120f0c95a3a9207e85f536bfa81f8c77234890773c72d5c5e915384550740bc1063741a494dd9949c34bdde6c49c368b6bc0b50d5a724a9aefcb80c6027b176edc837c6c0330a75d3f98435acdda751f457d985eb3150d9950857eef4a52617c30eee3c8e19afa9371b000bd07c6914ec84da2f9d7725352013513d30476b2e17bc1d4d75153596d11e2482285e1a8d5be8c41509772a293104a81d9e4b1470312b7c00e9053fb3624b0699afd08cbacc3a9048fd259281b73bccecc7a13adf12eedb0764f5b497833bd9db8928314a539bfc9d8cc72211eb9d59125d8ecf212e65c31853d4fcf3792770e9cd04a3f6629a8088b4601e92dd223d180eca8492ed71e16f77161409d36b38f03341fff32d2518d61cd450c418db41b8235a6d54cb1eccca71ff41c27134af982ed03725b803f798b9c68a277952dd8e6325f8a74b822d47bb7daffd0345c61da14cae12011b154d4357267551eaee5dc5a565e7036d7273b83f4e6e3b3b06f9c38e4d38f09d4a9e53772333679cbcb69de66acd52afff1ac88f91a8641fc8770d29d13f523e75a40ce0c2e1a70163fcf2b92ba9527b7d5f73378627404a0714cd80698194dea6d2fc16c41eab61c016d04019396afa85e3f90b5ca55706394c4aa62de5bc96d79a43742e68276aa129d45b162f82418ce1562156d98421f7363bbbf82eaa9e8c8882a72cac1465cc3eab4e56a02f07b2fae855fcd70eaa3a6c819f3309d6eeb626bb77033ec6b4b4db5defbb0a9ba53834e160e155a1b9827ef6cce529c7b156b0c774d5251d6d040dbe2146ee68c06d5bb6ed4eb24e6f390bd186c7618efe574035c56201f6836b498bb54cb5378ee6f1d415e993f9793c04b5591ff8713f87aa4ec723e512eaf0db05c1b46ee631039faaa06bae4e289a8be5f959de3eecbce827c721093c856f5cf2dc3fbea14da8657d338682b08534fe4cdf7363458983964c9132db422f61c8b7adaa2648f2ac72e1e5cd00c75b148aabb462360867c32ee0b5d9d47ffdb04a4312b712ae2ea011044558c760c82bc52a6f2f8f8e5a8d938bd1589ad4cad3f7d4ff68a9ab0110bd33267ff1fd9628f55d9a25c91e89982400510a5f2231418d7236d2cec195b15a0cfd32149a3f3479a9249f760ba654862545686afdc8e7e1a4fa87ec3909d049476cfe52918ad4c43bec426ed342ad5e6ee727cea9e780c90bbf849662650c826b00fb439a08f671be9274b4887f185d0ea70ea891079ea1cb0f3dae28183b651c77537aa2b5cfb53a5d92730be25d3c5240f026ff4a5a59486b6338260b98673020300e9a3bfe4ecac3308222ca69b19d41015a900e1e01b3466be86adf982e68cd684c6eca6825df8c2c61c9cdd04640372ac60f71ff809c3864336ea3dd570d80eea2e230d7814e336115ff86f6352c62f2d13424f363112e7e828bfca00f593afdbf6c8935c053bc4f8b8db95924d6272aa9b0345d1c9b70c0f5bbdd5dc15e7408b6ac9ada70e10ec8757947d86dcec5df22b6d1b373a2b63f6cff7e32335e4bf2a8f49f5d74490fae18932d86015dc0df080cf5779b7a6803315ab6d464b0d671d54ae3e56e6e6878670e937d8dfe2727094885bc10ece97c2a6660a0e691c9795c2674e9a017a461e6587f1fcb8fe3a7f3d1f6f770c5f6a73de0f7c8e274e91e1174bb893661405e739b366362b6e72d1e9a496f4328baee13972c10072856ee540867551dda1795b6879d4da80ec163c25fddc81182284a7705d89a3c8eab2d839cb2129f8303df2f854df795ade170536dfb01e2549fdaadf5268255b9a3bfb3e29f2d5adf2d93341b704ae97f9722e78c1fb6250658f2ef82b67bfcbee69babcf1cbb10557887f3fe560e8f0413ae07449afa8686c12b9345c699869be4c4299147345ef2495951de6f7b4eb5e7229431f2a819afc2e58d97a6fc1fd770b0f1b37d07699e5ac953ca45bd5457d72396b026e4e99722aea8032110fdd5f7901c34f35cca51a835a90084727ee0e4592efa16f69fc94e9a0aba3baf1c694ea254e5faa002b682cabe9c88b000c6e728fcd5a9ee7c496d68333930ab916f0ee54c2c84f0d9ce0a2a9ebc638423ebd02085f094e2bc56c8f5292c1bfc7642165febc6bc4391ed69076b254a07dd15862a1d0ca6efc3cd2c89c8010429470ac5184bbfde1aedcba6f0886ea737bd11f727a66cba672fbc3e83d7e87299934ce403d35978be6a7bf85127e983a3547d83a3d1155596de4f598da476c687445faa2b5adf70f79d70663058e175e5d560272b7b774de547de8563d4a6046f3921a6392291ecfe22b3efc02a6e82cb56c876f239e91c019a76481ec0fb26235e24805a9079e8e5e140ca28eda9d126be4856181a65aaf55273d9442e25aee9d78367f4beb453aac5a39b03f90437e62c589460f48c22029cb9638e553e77fd4656d03ee8fae4946dae9e91e21cc766b034872920fe91447bfb6a7616ec280b1ccea4ca5178f4c7f4e8614ea01123529b8cc72bd93d9c390970f932b0f3d312a572aa344f456858d3322daaa61f0b3f106620bc6a8ca94b85f1c393f02424ecd3aa5661405b4147282901e4fcd5db0e237514c4385a34651e36fccb0e466881ec0a4d8693cb4e9d7349925d86f33129354836998b55f00cf17ad58f7d8c440ef99b23553ae77a348ecc05c45ea53be4fdb04726c5324e1bd943e3cd1982cd1124909d66be3e5b20b654b28e303a269fab8a07287da9f6ca7c375d4931ba47d6ebb212213f9ca1b4c20b68427fa0bc58691f2560e47c9ab932372ebee3aaff3ab38e9aa59a8ac3b69b49671432d3a55df981d72659a14b9651fde17b60317fa547e371f6ab6ec9853b14a297e43a5e6b017c272b4b3d1b6bc516cda2aa32b538cb8eb5a96c3f0b11b61b1e6949710e7f032ca62617cc03644fb3a4dc8af015a09c857857652f650eca86c0ab3430f1ba380e972c983cd9cdd726dcabdf544034a8540bea05ace570945c3dcf2e40250ca51ee7271a733ef6d1d77d74f732ef00f8e685da8d0a9de16ce729cc40db725426d4572b2b4cfc839908abed43993210f01f92f0f65d7a717ebeaadd441976d117a3c72fd17bc0bfc8c343626505c0e80b622f31b4feefb67d8d973040b8a7ca6e926729f59d219c9a8bcc4a406e19dc5b38f616efe2c76daf3bcd4c0f491888b535a44070d7f777603b41f5fe008c7058866d4eba2daeecb2436543a247adad0f9462a67365eb94e6a0bf663b08bd335f99c7c6da3767ad71cc4dc3de506ecd8668772e0d1541d8762dcaddbbc8ec57d372ff44e76781dd7f9b025b4f6dd062ffddd58eebc341109049e038fc8103cfb2eed7a9e966ae8a8f74e5270541726f9491472e2dbf9ed5a71d63146674c970b31f494165e3856f4a589641f69c8215f560358f075ad0298534985bfa79ad8c8a7f169bf63dd656b32119a8c3ff5a57cecac729b052d8247e346d4d13c8fb7d81c748ddbb78b17ae91a0136983a56339ab4572b3e20eb2f2a20addc49fb3d9f31089a4d6ed21420c3d33726f6884a9bfe66f72058217567b8949da49b9280ed56e8e84e5e0ed828ecad0a73707580dd5c1767214f40c94ebb680315f94701e944ed7c4630ae097f181ab4dcc7e15545faa6871948fcf30b7a7be38b9f25d82d8f533de07ea9305961804978656549bf64d6e726024ea6e14b64c37187b614185bf90d1e29fa0e3732fbdde8349dea00ccc5b65c0b8337851f2cd4d6ee49268f348659f49050e0e0e5c4b25f480b773319925722ad8bd786d2184232093cab9384b3671154db548f6ec174df3cea74e8e3601085f2842eeb6fb1817067b8af6310ad1da6fcd99ba8ad6eb2892873875fefff1174cfa8a7441c5c1b7495dfa767061a491e3038099eacccf234de02ccb424408357ff28ebbcbcb6c53f5ef396aa762168be8d441d93184c083dccd04e684a92b6ff96725180ddec53d350a0d49378503c3dc5f0aeeb0e1d8d585c602122851dc72d7ff866df90e66573d914e16d998cbfdc89b8e2ae2b38e2d7ef97dea95a5873f5194ed390ae63b052b95fe726f84430d2b8022d96c2b8d05ff422025b942bb726d44d5b9f1e6676b5976a42acaac8e92a0d3cb518c12b0b26e676740bf9ad7579706db8a81d094c6c6e49bf0e38500176a2391b962d9f991e590c91e333c5f72b21618cd179884ac381b3eaef3d3e2b54eab04500f0f48936db4464142b9ad727ff31caa5539a46f9a372c1d33dcb1c2f09ef13e591780f13ef2c9d9f2ef2a07a0ce4c3ad5d5883ffd5ee5b621aee62afacda76141af5d234173820820ffb572126d1542f976fb78623b8936f2e2aba9a1efe9bfaf80b51aefdf7e6e2396ed72d47d7e7d971165a318d1120a330008db2489c5fb2ccab14aa89bf3269fe1f5724d9accc01ac91d0f8e7f74dcea26e7b1a40487c72072cea53991a54c29d5a372781cb09c4abcdaf8ac792400b7305eb43b0d8a32e749e95211911e17b69cb9720639cff5081f60fe397e8789f1a2d63200940d3951e209af64dc57413369de5ba3ea359901c603c388815353e1943cbc4fbc87e32af3e32fd4739e5b4cb51572851962ea03d5c4dc9bd00749dd3d81891ecee0568fd09b97137b86136c27830ee8c2932c015654f3332f545ba5406e6cb9768fbc1357653ccb759704bae11d726c7b50cfd83472548d4da29b2267c775c09b2533efc5f11257db676a8bb79f18468a261681baf8aae3035d7f1c8227e19e3e94ca39472f0a41a73161f2dab71d064507147ab3e022433bdd521d04997e6383d5d1afec054263af59a29b0fb54d57fc4446650c766d72d184f7c31559e941546d14138e1174f04b31fe33d06b72790a3f8d8f6952a1c989b568b6ac1a965ac716e3108263679bc8b18b48beb250c7a710ba952d4184c0d9c5d319a398b91e2bde3a3bb8cc77e812a0c91738fa72be02d4ba07b021d006dda12a7e3576d2d61586b1c324ea9157812d1985b73b3d1f2562b6e37c85a627906ff6824c056368d49fb01e01e4609552bdf12e3b5b72c3dde52be57c884bccc0a8893f6386370e6349e75703661b80443a90dc3b237257f111f517c28a936eed00a77fe51e70640a5bcd4c805d43fba3923fecf3817252390fb3dfd4709be7d968ea97552c1219815ecad3d734c05859d7b51e15f87283f0d8116604474b0bff48304cc6758ae59da8878bad96b49667152f5bd108451335cc75cdca5a4a45eab6818e05995a8c3884a043b713d55dec58b9e6206772fc7aa8705d997f59a76c0acff3126dd14abef9eba9263f86be1913d65b2057720c69e1f5f32a6752245e462cf1043b3508664b8c2e0ecf26beaedfdfbbff977244e1680c2c9f64d79c91d2d30d9ebca588e9750bd12372f9ea271be9342c6972d367bb2985a6ec7bfc35b652abd1b270c3c5416588c8b19aacf45f276fd70a0371b3adad1b6050ed17613ee7b07290b9d7a3e9393d70d9bb131d16e4c0291d72d186779dbd446a39e57fc8089af90ad6a59258906510f698efbda9c3e2c28621c3ac4d5dacdcd9b344c4315f07455cfd9557018962c5260dd8523cfc40c9b3722716112c1dca7228a525558e35a1345c3f58908ec42a34078de789fe269c19721d7239e6f3b06bd3dc06ec15e34efd63a58f54f0de314e57f2bf20c6b2a4b9728e443586e03ecbe9788c0522e4da1dea75b69844fbdbb27789605f390ee1e325b36a9b8a84538a61cd3d70ec505a22a60a5d2f5cb333794875e4ea7a2039c672dfa5e05ee08cf8596f095496f9bb71057d3bf5ea5f84df6ee686e9bdd454e772cf5ccc659e65bb822bbe47f291057f01fc3f352ee595a02ff5178f6c16c2216cc2fa163b9babf8f5bb63e7fc76b552d008b0ba0c7138ca963fb33196a00d513b07421931613ba369d46e1ccc72ad1fa58b2be9377d2e110fc45b75c74105b222623a012060c30067e68c3158ce5898ac1077edf2aa351c689cc715df7c4f7e720bb1e367d5bcb097406a7871cfb036617bafecbd4c894d59b8ae7135ead7d1727c18ecb42f0e575f59e2d401bdea87881050e34a74d5da8405d5077fcb4d92722d84015575b9e001db2a0d988b377373d8297aee7aaf5f148dc1fc23724fb772d7e884cb8febb8d12b87785d7518a15908335a60831702699ed94dbf2b9e517210ec0fc66ebc916d281e0b8d6873ceeb347b69f1ca270bda726296094b2070355625709b154165c372f89897a1c06545fbcde856e763fd947250bcb21ff0b427c92d5d0bf5131891fa308a3c0b3ea0581cd6c1142d56a5f9657a0d60271251133c42befc0e128583fc2fe187b9ec8b87119206fceaee0725dc4521e679e3fe2c1bb1b937ae89d0250f248c61de6ef3f242f1996a356cd3324aaa80d9031ec42e9c6e6358026c4da1444dee54936600cfd6465e3268c33d8186b0eb7234eb940607ea5d0c2866610827f1caf404ed9a83a0951d21d2765e3549f0b5ef721bfe723f20144bf94da023fd378cd04f74f35c3f6cedc479c0470b0d92695e60a0b246578dd1d632226031e75ca96aa246df4aaf04d978716f93b0cbd7406ec28d82440a22d4e532a0482a11d12be36aa0d15b3ef4cd6f32631f1ff1a1a28e9a1c3272dad145a9a279d39d8f60d42ca9196e31fc077a07775485dab7de6d6c3ad55f67914b67f1d4caa3e5c5554017a2784cb19f462a34825011be01a224d2636a6172f7ce4324f36db43f46900b48c24d976b87dd240a8a5165b17c94325ed667311e5080b1c2594010baf38a8e7e7400807f60d8d60fab28bebc75885e74831b8411dd68cf68fd394fce4dd0070c67e4aac786296bc4cd3d82de8ad7d0e801602772ee7f13613abd0379c8ef6ca6d005509480e3caa78f05ebb0ffb56736b4453523f50c4d4d6cc308758ed6c0d04b0ac5c1939b6c95e8a2025be88aadad05d681490205b73d93b258e9c245bd3855d942e3011e8222b3c835d12cda5a04f7d5d37277e03036555ccd0f3608e53ec536831cf9972f76b38d8fd70b0ff417add5c772a73abd7382b6aac53cde05812dc806edcbc2300f6d6b592625cd89adc9b0654a6178ad2a5297794d2730d81ca234bd524d820be722b283fd37f9bec2fef23349f2436f48a7867e1506b0169b44bd6f118e60c8c1e1559049d12994b872e81e72ec79486d7255d0f386a0acfb75ca7eecfe9f7818ad928b832621629c13381d43adde2d28f400142007b07f3ca23e5c0725aa830370ce35aa242a4fe13c228872367ada3b93dddcab1aceb8de49974baff527235a01a7732ca39665f8ac381872569233048b1d01b2117309eafc3d7f74bd15610e04a884e4cf76e52b1eebda0af15a1b5c9d6487746a4acb7b2c920d5fb2534e1885ae4ff5a29f10b2a9349b72d1f6ddc1c359c8bd1787cc254ab0dc5c3741fa47b3ddd554e19cb10e36a86e72c1b3383747e9b42ad67b9f8c89987188a4ceb465ccd55e4549661adcb96d50726ac5838fe62d4e3181b0dbf426a04ff31ae28dd0204556a503359a59a79e4f37aeb6cf9daba2422dfdaed0b52e44b45ecc8e481c32bbfdf1050c71e27432b72654cb41cbfdecf2949f8bcb911b7edb633d0e84752b6b936d736e555361a97172b3ce39d72c8eeebd88e88d35de981840d9acd3bb8b59ef585375adb8347508184d649a87629edccbb7cb1372d47b4186533494686439f101f08cee36b74668723777ff7c67230fe0c1c449172e80eac5d0222513f83b16d89aabd78ba54d6672c1ff0f02716d04bdc4beaec5f72c69051e98a08353625a75efcbc94ac917aa72239d22290ddefee5a213c6292347dc2309a6c71e504dd571a2920706085809724e2fc8d08b164b1a87ad51361ab683b035df31b0c0c287f96ed97ce7260704721a1e5177175ebb74c216680b222c1db2e3c933ce5dc37abc3c7b108861c95034618697283ba7f7787eb344257289dd4e70ab4298cc6e3c26d99f64165360ea4e86ec732a62b97b90fcf4dbad2d8fc71523614a68f5c6afa5a42c4d52348ced53e68e2946cd918ed60c2e97006d9373ffdddd02ac966e0739a5d86bf94efaa372af05d2314deb2238ac60def90add143ec1630deb28e90d7d09c71e1abf64804aa892ee0aa05983eb734390b6fd018c5638b6237c3b1222a69eee3df2fbbea27258d7c8a9c696e2b1e0f6d0c18ba70345253efc47deba78e9ec55d1ae6b242572253dffc2bfdbdc0337ec649fd8199a895c729666d75fdb223d062b7a457d8013606ec3249173101f5394561ccb2614878ec3cf94867ff272710fbcdbb770ae72da7b37752110e09ac2ecf27c349dbb02c1bd4e5e5c849303a467b628cd810e722877c388a0d2cc216dc2e71b3e7d8c1bb8f80201d5c12eab178716b2a4a5fb728359485946112d989b77ac1ef8ee0410b89b22284320164029913ab03196f634654cf3bd5227897e5ca0b895135b9b94f1addd3b65b4a15d40419d9609471d72163f5d3b825c9e8ee30f1c4d52f545a789f8ac1773ac68b688b5e5a5258a4d3178228d90b3cb25e9c11c01e6784ae02b95b8caa92bf342a4c86c225b5e4ef6725279d73d574cb562e67db0736b64a0fb6bddb1d350de7ff8acdcd910aec6307235967be40a792132949cb23ca3658a9040299ecf3b9405b06321620e5468d13a1fb43718cfa1dd9fe31687510aa2c0ce882520eabfa48384c4dc448a4b1fd27265557f7a5112d41a236bead10b568365f1eac91f486e544a42f6386e330c2961e86a141a9c4044e0952f061f45b0bb78b10b76d525f33c326cc2750770bab2721f9eeff9928f17eef9180322f49708c57d3bf120b3587189738d66d411b5b73587e49d62e17f421f76db2f252787b9d81ac820fa2da91d812adec13a5678161c9e18f7961d06d8506005bd4ce17357c044bcba2405e6f23846135c6ddbcd8e7280966f7fe4f19290800064ed59b1d979ee8850e17f25dff2f690cfb68179c472c397648bd1b9579f3a1f6c8846af176f391f9f0a5c64f85a9d8596d59dedbd72e3e4cdbe04ca9649e5f550ef5c4b8d3c63cb62e821f1c233442825fc130102092e3ffed7818fee6571e5dfa110b990bae3191bf199535d656962c9313f223b7239de906d14ed7777e87814e08c793a5c6204d87a0cb6963d1b0e5a78fcf4467278d02b738398889b41cb3d6748cf4bb8f67a4ecf7d193f905157ae5533451e38121a1128c0397cca35cad811d093458c75749ad312a18c0317b3061c3595c0263a7700867b805d527efb05e68d41b2ebc4b68c21e39155ce8535eb4344368d7252bffc263fc1e0597f53dae8a30a0ef6eb65f372dd27356fc88714377954a1160b70d6387459fe793b5fa6e63d902812765adb13eb6ef6cd6bc29fa1c0d4482686068dc0d042c6c87c086dc1345d3cd81e71f60756d6233f351cf930af3e4d276c33f114f59fd3d18df494cc372f89c632c3bae0cfc8ea6e8c9c0185874fec72aa0bc4ea512b1753c5c8da61e7899cedaaf26f7a50eb10e8540aa5b277bc7d72cf4ec66ebfdec7b4ca4a2748bb16930e7af4549a5cc0e1b25983d8384d4414720c788213ebe39369c558736e9f5d8f16f11302a44b6b9c248e05ad0d97e33972a696ea786bca5eb2efca3cc4a6a60909c4106619c0b53b81fd08fe94db400e722027c1d943d27521f34214bfe67a267cc76ca16fddacd5c07654414e0fe4d054a84677fabfb2577c61527b91a7ef7f67c4faa51ab7ed52746d53d76c647ab51500dbd989ce9ce7622421299f2762839bba674117e5573dbbb9bac214c3b79f721ab9d63861daeaae5b4086d164158ac3d30c7683ca06a883d6294f2d7f31277290ed5acb062d6adb5f9868694a55bd0d29c85d075a89ceb858c5a57c118876724bdba295a9a6ceda0d04ce3ce965b7dbb6552db3204dde5a4f82b2a065755f724808ff00a3b1a99cb75f97c741e7203c52d1d6ec803fa51fd251af551f314b5fdf7dba766cb5829c4f03d60e347a400c10267dfd9aab04282bf892c5c093f072debf149506e49013de3d47950a9e10db038c991cd6bd97348550622a59ac8272f4d29074a29df18b58dbdfb42818003aab1011f0d6efd156b6925e6999cc013d723e55dafcb574ae69ae2b2d32f5ddfe0188103c56b743a93e257a8c7ef4787277f9b9180cd7d569f5363c0f07b0f1386e4fe6151af9db92405eae2e4a7d4a0ee5380a0539e97f6e4254d6882715ca768be70212a93863998b250f98a2808c7203d6d1a84f97b2697d8848904677982b8b3023ae7ffcefd562c3e0c587f01672afa4c18755f0cec6497cc8aada616c553a54e8a795b573872cbcbc83506ff172b68ca070a0bf6eba3f81912e12ea6bb1af54c86a413423b3a781f5232d7e30721cb39f7f7c3b7cde49a39dc5e6d1cc6e179891b60465b7078bcc81a0638e5b03b08b8933e5de8d4bcb74add866445a7dbf915b73e133922d4dc595abaf5ce347b65ccaf6e2bf202bcf5080553a95a614274c15d92f3fdc379f4ba0d15b911572d987a813500ac6a14d8397f080973f72879a60d5224686ea9b352ed8cf610721f88010d89d9df92a018a23f4fb903d9061aadefd51fee034732d34d4739a0d722863faba81214bf7c9c220fa3af1b41a7a96097cb90db823294eb6420df57e722a234b36f8eb58d7cde46f9aac5e126978f50c6d9a8446cc82abb43383949b567fc107d62eae81a47b5aceb609a3ef5cbfafc224c0e2ada52748d1eb8da48c72664bfc6c357629d6e2d2c16488c430aaf388c4a6bb24447ea3a6a8802d03ab72e15cc35d3f8d8da4172f29c18c84990894432c9dd1c4c14262df60ad7b9f387261bad5c3c78e87fa439bc0c9b0b17107c53e6179c3929d23911b29bd68e70172f5f00044f1ff9ba899e3a3a70038ce4b7b1b8277586503c0b6501aa62a14e072a17891f47d904c8b6b3ece1e51884bf5f01fdc86af8e77ef063c3a3ea75911386b04cd0b596204f60f03d5e750f63d33cf0c625121e41ecfa836ad7b0a4dae72835d89ad4848a6c2c5d6e4619a1cf87e6e0340ad3cade5a01001bd6bb6726c3ab7c7cd9af728db394270c6ac915720ffc16e713b4da2666efe1c77cf1f36b872cf673d996e1da17d39ecd9cdd4f431cd059fde1c8f0fe39681c615be155ed413a21f9fed49c3c03afb2ee911c341dcb622876c641f68849e6ca978d7c2362f722a5b128f92339e543487a4c440b938400066b1073f0413fcd11ea5e15a1f77723d4c234b817f4aa7d1c499193c2e18d86882dcb1511bd1238eede8d3b8481172233eab2f0541f5afc543858faa41c2915550a437d5599cd30b6fa23abadd854134e8c262081c50cd197a8e4109728bf14f51de2a74384ebcd4a49475686496427f9a686f58ee1f66b30f0b12476f339449cd90dafd44eaa51236c542f376c37230a85fe3079d7b1adbc6f8efc8f059f44fc4589400a8a62029ce9c64cdef02729b017f893e9e57f507051832ab836c68fa0871bd4632e5689d22d7c86cccd8727bd458d6fb0d8b7492dbd5009bc1e206a89a85e5cba52ca7dab70e7a21373172109b9985660a44111fdaccfcdacce6f4cc54f0f9bf468a94db39394e2433af722ea13287b2e81a47efc91e5f9d4c87e6a4e7706a1a6b96d0fd86470f28cf6e7280ec74e34cd412ef4b891d5dd4e055d42494ade7be28c9ff14b859929f073d722b14c5c94636c65f4a8bfbc1d0cb7a16485d9c0054ce7468fe3cab50eb479d7298f5a8f3f20975711e0c3ed9383351fdfec9a6596bd5cbd221d2579a965c3b03313be349e50682f0988b0bce15b780c32fa0008d18359c1dec2f66fd6341280bd5a240300a31a2a28084ef6a52cea6e4f5eb37f76ce9de8df1e318bab34c5d398a67294230a6fa9fe7f754b4fda736e4e3e7d22ef7f69864b3967b5482968f7271381832753efcc2c2ca69341c796aa9f76700e7008c62e92a2ed62a32b60e5a106b484c21dfdacc411f79e558895ab93549050c182640656f87f530815cfe72fdc73351cc5d01a0ae32e6e1c1505f6ef03415a3a1163c5e537ff31d18775772a55e281495b4bc737494d18837ee333fc9e5f8db993cfebd711bd88276c2252fd643098b7ddf8c1a7c877ebeb78ff6e3ddfa506d0f75962839276cb088d4e772fb6ad6eec77046580fce7fcd9f6ac5093748e52940ba82b0c24d63eb2d886372304ed67b26b4aeae6e19077f3ee99b26a0d23c9ff0ec80d3191f9f4a777e3d72ec8257b3e853218d032ce1874e703c391b78e05bc0d512be65c8dcdbeceade7201c023cca29bdc93ce0a653ae1fdcf6ec0f1d38d7204863b3845803d0dd777728eab5829719b22bc77942d122c5b43bda13ca9b6f6c4b44e0ae982432aaecf05686fb29ceccd92b33f12912fb6896e499fc3c3df161a4879c9a643079df0ca5bf8fc88f0ee94122eca9ec1d6c14026c2e3cc9893f7ce0c401a970d883398e072e95a579f348fa3a02890192aad8a61b12ebef89f8ae869f64cc46407d48dfa7293a6b369d0241f189dd46f47e187886bce80f478a210aedc260263e8903be90787e00e44d0dab060741ec183fc8d53ee6487c1274e66deda62ab30422d31c03cabafef48cf8e23c350615ddd864aa3b7d9eecd4a0054977d00e4f3e10ab1b7729cbe5edacc3b67f671c4728438af1702a258431ce4add8606786dc1a43f9ba72a013e53fd631cb8551a165aa622024677ae4bc999ccce8f616c04609b6743514a99cc83f0a064774deea07cd14a2381870f08fdaec06f5968335a66bb11b1a72153da201f47570de9ee8e37460fe9e11d043a498a8c221eb59d83175f3d29272401bb2ab8b6da0ffe010af9951d7ae391ed904d92e2b0b180300678dcc2c3272d6a23ba663c1cd9a266a30509524314c1d2e48d023586671e432bf789510dc35321316b65cad5e61d61866580c9a46737cdbbfcf93635952a5cb2c3a66b18c45978fa3b8fd7956823c362bb0ed20d46ee5fa240b95577ed186fa45bb0a6ac36650d9b9071ef33792ffcd7ce826d35e80cd8b9a99fc0acc5948f70d5a898c1072728742665691392033ac98b1e95e2f74bd7435bc9587dad979d1df0323b55145a0fbe5b4fec9dedd1f6a52931c71ac40ca02d6c3e3ddba32ae400df17b0016722c6081bc9e59d5d471a94aa5293f86317a5d59f93caa2075a49e15dd390e4872c711d50e104795096064520de17c34ceafeaf0882319ab32b5259797cbacb1217000dee2caff47e58ed73c24116164ad40df56e4dec014a30e6013b71fe0471e68bdd6916239452a752f3a180c0bedb5efd45827a860364c481ae5cbd592cc722208dd9378962e0d2b1b4ff456282b7c26a67cdf6be718f389a4cee3cd5e8e403c66849f1ae28c55d9c3adc42686aa735c7331078647fb998d055d87ae13fb72311869c21bb1618813d8d372769d3b9e94c30c22c28e08178d059aaddfa29c5319cf3aca6673f8bfd71b22f98d07cfa8923a0ebeebd1121a8ebb72ed58463772a565e37b5af722d45d004bdda1a285f1e7bd13eb1bc2fbd63c203c7fbc41a1723165d59d3ee655964a5c586ebcf4939fbf96c918d27fb31d363099fdfcca84232653c6a79ec4d6af7ed165f6935867aaabf8b99623be9fbdf8cf34261b45bd7292237b78847c7b8cf3a22d890315c7aba7e1b345e68b698334ce03ff0c3e92725655949e20131aa49fb25546f1c6c2d4b14cf83f6ce14f069037541e0a347e72c3d5ee6e3b1c9f5e78edc5d0ba5dba83a2247539a8cddf92c85fd0ec79d083729741f0bc4983d312370ad7ac5305f4cfb922ba61163ba019c4a702499de2e270279e7544da9a58c123b69b7a36f53706359d48d07b1c84aee4d9a4d1b0e99225e27c48ffa3a94336245c11f71e59593dd65a4e0a1a0d07fdee83776a9b2ed77235c371ca5b107d562fe060f454ed7b49eef7022de80f0959d8b957c127ec6772fec04b17293635053202da706124d95009c89a851a75b25912fe316896e62e4bda38518cd737cdcb01e6c2d4c082831d416ae696937740e4d090455985546e205e0614046fce5f3b1f31db3f8be40e1b7713c267f7cd8aa9e71fc53db297306f2d7582a1b26b4ff107a741c82ad2a3ce4d47ecb9fdef8abf7e8fd4cf335328720eaf10f73c4a0b2cc77a1d815a637803443a0968e76bf5925f1ee76b6c15d54c108279069e59e073de835dbb04751638b5bb183b1390bb1d19750bd50957ad72b284bef52b22e243eec0cf3fa409c306000d3cdb5be8e9c8b7f0672e3147cb2e1b4f4e0553d67761d253bb94c9e38a428ddbac029771bd9129b77310614ae972c707329b3ff9216196c0349f3d89c5a5c1ab2e8bd36216105b784855d0d31e728d9e731e79cca68f5d4db66a5e77fb60574d219e421738adfe1d7eb232705372f01fdaf47a07e37596a34d6958bab6aeb286b80582fa3fcb1fecf100a52df4726ea429f2fe023502e6cf854d36944cb50663b5211a6d325d2b5a555d534dee2d95bd64df471d46d6e1754b889bbf31d2038a35d2c755f8c94e06320dbac9d072f120c9a7f7b813be67188321f1048cf1b2a132485b98209e843afe9768a45d4649ac23c0d38779c39dc153dd3885f5dda6a87abb9aa5f90196e11995242fbd097af3bce4636374d648098f4c322a26b13f06801b5fcfad88dc7e56edf6a0a872104d36f31691ae7c792f3eed56f9b9a64aea9a338deaa6f07d68afce7552406294af4d7054f659e6854bdd13aa890d5d73baa217ed16047961dc3e9d1e035b7278f743275e53c4abf08952d7b89dcccc116274eee38740d3ee35ee81ffbdf520dab603b3692b329ff45963e7a80ea9dad2ed97cd956c6b0e01456a3b2324d57205f61ead4f6bc6127dabfc9ddb3dd1fcf4592501c9dea42c8c1de4ef28f6073e14eccb349c5b13a31e8e3a1a7151dced6a69ff4d4dd0fd1bad8fc7d83ccb1345fa13767a4dc92e9b1a4158feb0bddac78cbc4d1e9f29e2cd120d3c33c14a1a726be0019d83369dcb961dc9bc5b4cdf02176ecb64541078f89af3629c63da47727a0e1259fa531512ee1407bf09edd709fee9380eff153190d55742b66553e7035e49fca25112c3cbc2ba3ba2e1d0e490bc77c78cb5e3d9fc265c29d4336b3118bde3c3bb5ccf0fed4620c01a9c301614f6f482151867d2f60efb7f9e18c6d348af594f89e9db6744ef8c97075d15d19c188c961ca1b1b0f773c76ccd4b735546322db14c79b4956ad7d33f93aa29d13f0d1a0deff3e6dbd634c5751da2034472050e56f83a3e9cfd9d6be0fa6eef48c45efdf19ae66bfe36272e062f5ba67e03ccd33c2638ae35f20d637f05b868105ae7f893824a05c2cf36a443bfd0a3395f501c22fde45b6509e8c5e307616ffba6c34e5fb6f3f6ffbf0d2fe2c4be6488721b22a9d71942370917df203dae706fdbdfc263ddffae2ea680171cd5b87932073594672353c6827f197cee155880e09b38d0040b9ae14890bac9dd3370ff7e4c6da8e503bde3392ac9168a91df56c2d439f8177efec7cddb8bcdcea5f08b5f0c017e3001a44f105e423e04c76e7e9f9825df5044ea0565be474ba2b578a2ad729b3c1ca1151760f8ed3797ef92c41e8974b7d5220bb0727c7828eb61e05fd4496880e3562d2ac093fcf6dbf4f120fd82959cdece2ebd00df3fb1759caee7c2725e4365dd99d92f4cefee67e8135e05ec70721706754e88591c6c5651b1fbfe721b81446021fb719cce114df2276b3c32c5c3a6fd2e2bfe0c91705571039d4731371f2476f9e54baef8e2d6c0d55ea5b72555097cfb0e7e9c6b1b664fd8b81b72d323bcb49d9dde80d64f6fafaec56cf297f88960374feaf289debaa457b28872b0930cff8a546511636f4a7823e9d61144c804d44e19656acd440bb365b0555dff94f95d792b35324a124d4b2d27448f579a3dba090d2d10c3d47762c63f1a72719ec01fb0a8222408dae526e34c9e895d485a64d8b778f4cddaf71fdf10fd27363dc9cde92d23d3f076f654412cb6ab1d861cc1341a9102355e72c9658d28729dadb7be1f1ca419ba7fc47b8d83bda2f3dd3cbf8a329f8370ad9c2fe5bc0372d03434fd28fabe79b7c1329efc99ceef204078c2b0115b0d56aab608fe30ce15e9e961232eff2924169997a4712a0365bab6f84b8fccbcc12cea3bbc2adc8e4999bc75c4a611af263e82756fb5b2eb2560d6d244a4d0487a551e34c8b6155672196e48b005d18c0193ac0d8e56f1332adc4c926bfe5b8544ca4f9f09d6e87b724aa16617330f09cb9d282bd5b4cfd5c823bedee6d9b5cf8b2c8d4fc49ce60272cceb5b3eb7d7c275a2b503aa21562b577c947b4df8aae00252f0697d6b6af62b1a582325354c2e212eed2d73b4d631b01b706c329779fd45de59d1986a0f8b2c5e295f01d8a7c65862696fb62b7fee23d2183a2bf2cc31e6abf77cbe91a8444691de9fc70373e98f2dc7dd7f3a96ad1910c924d6ca7ad9b9b0fc6decc610907266276f31ec5e8e7db0065c120fd048a6de131f16817664b4b7e6d9562e6d9f424e106f6bb9fa2603e992ece045efb654a4a11369993217ddab84284cfb167926c9399f106f77ab0ae7f7b2d2a39d75d18d7a8d47a22de0fffd26da38af60cd729a904a26ea727192075ee90e8fe0979f9616273708035fda643d7a70582261539f4afb5fad3bc26792e9b20757a1c29323e2ab8518af8a11aff2aad501603949fee0d9a62a00494a5af1608a7e8cbd22aec8b4fdc1b72f60bb383ae2ae44ae72b434750bd1a1ccb5fb4b88cc988913a09a846a9b6b358507680e4a8f3034107299812f279ef76efba56f976c497417058b0f7c2bbb341f7859abc26d36f58f72b53c694154b2dfc9509597fab26dc5fd2d8255a2dabec2bd4e41b59d21ab6e63f1ea5adbc995b9e02fb733d166f4b532a222e154c12be64de395ac3683af9a72c1769c1c555c6e2a53ac41634db367135b5e5fa11d3432980ad1742a9e55a062582e1a0415ae9d90044a58486f939ab234bdb510fc83bf42769dd4d21c0f7824fc72ef1f594c55782af96724943e1c82c4f5fdaf888362f282e1a9d63ae6bf7282d7facd4ead4634fe5f82363e98e49e04f3e2c01462796253bb3c8cc24327722d5cba77e4e536ae6fe196ecd5055b0522d7465485cc09c19656d8a19e47b150a76098240da83c150cc152fb0fdf256dc911bf2181eac528eeb29267565b432a4a64225eaf9e68988b27774ad69b98fc24b704e372ee970d4cd0c7e8a00fca723bb77cfca3b705ed23507f01a631b8ffb8751b021d9814a8008d52770e6ef37258d0308d267f6b0bab60c12f332a6463a605f5b5705f3f095924c2e7dfc5016dead439d27a2c4fe0822f77607a24ec6d3625bb254d88ff6270e52445e023be720b7c9bac833b83816c479720a2c63ea1d8c8c5a3432f5f521ad9d2c98e81d272ea608b59bbe3d0cb717ab1168ab0885bdee342fd9a42eb403235ffc39859de720881ac28fa7dba2225159bd0793423b8534e7b9deaf9db250bce1f3c2bacf5728323815d9ead60a09363d0c000a0cc1b1293ff8fe79ea89559d83ff82b00337272a429e15f412db769f26a71392e73260dc4f0cf0b81689c96a2876b8ca15a72371f1c28b19fa28607f7c14fb9873f8b6f7b915a80afaf00eba741d442b574668e3557c4704753d5ba97135ebf6a01fbfce3eea6d4a5a6a37cb9eff16d72ad38d1da2fd1351186f2ecd08c3b1bc98f2aeed95dee07117abf32adcd3d4c3991725fccf37b0faf5cd5bffe50d595472544f59233d5ccc17f1087483ef33ee2d072b2a1d7a55e25b5dd3743c0cfa1d61f9a8000434bc81e3a06830464b5d8145b72c5dd6b0c9a5ea9e4d8632f1b80a6541a5af5ea21d4afbf03b5552980f7360a720a86b9e58ea56657eab6dc68199fe8a23969fc2274952dd2db96767bc173907229f01777bb926cbb09acb98fe9157dbbc49379ead9233507ae2353780d85ac72ce875e7d433db1a44b0d042ae6fe710325093e8a12167f9e497ea83056aadf63c24285d001ba8ae8f297df30cfc71ee006fc90a07811c962d9bd7248f0041752f55e0feb839aa291e561bc92406e94f3607e0e89cedc3e386bafd03f517205071ded08f85958e2e916400fb0b02b81542cb3bb0f28b59b2b090a05ec8f0b5372be289e6ac33f8e2d65647a2d2b66b78bea86ca67000ad4c1fbe5dafa0784732a7fcd7fb65a0ded86eb5c6cc8589396a31641a885fdbd6b59ca5601643df487724241fcd36f253ecb9b1dc4cc86989d25e3aa5d3f5ccf61e4e6d843f6daa99872d9322539687b9651ad1ecfa3cfb152b6e76816e02be986211fc6a50d6c218667af4382bc4b474fa21d2bed0e796436040579fac2036c89c8400b48010538ce0205dbf0e29de315275c9d406c7f3d62161be1e6e1076d204ee83453b5c3c71e720e5e2a8e9036d20459c425b92c40728fb469b93cfc0dfccd2e2f2c986ad317657fe6a25ff9c6a1a775f8f8f32f0899c887c655317aaf69c4a1ccd245f079305b34b824e7fbf54c64a0caa2367066944d2d00410dd5e028f03be683cb72a201729d568e941d94045441a2ba573bf2ba1b1e1b625d7da89e325d9170d684868860f8775bb6bced9ffca75d6de2982659f9eec2a3f41a4aac9dc242dfcf6823d572734fef3177be4d2f4d9e87ea05316ff1875031ad7beb0c05afcc3e3b18555c2968017c12bdb4402e73bff0412ddb4d25de71aa28d10472e1b18f47c654147b59111076dd6e116f538c1ede8fd3ae7e908c0db7696dd33db93df402913da9b5723b1f103dec128f24dca22624c34839e51cd8ca521c61335efdd87e24588bee72e20342359b3c4b8e3925b0c7ac7a750a7e823d11de607531bbda0ced49c79572007aed05e62bdb89b560f06c611cdc7ca728e25301fcb78d2fa8bfbb1889e563eb051e4a04d5a29de8b99926b18b4c7d2190678dfdd0428f19183b13a7ca3f388afe74125cd44c712a52b2eb0219daf2aa82b595e034a4268972299bf8ad2f09992a68488f3f8b573842c4c5b9cac914ab1ced379b8a886c59c938adfff5da72292e52e1468f4b0d82416bbcb740eb949420f278661063f31633100bc4febc724e6a06ba5f615144a7e0859d8147c540a4d775b7e45325b581c26a1d33963308baf7bea35128baf82aef1c6e4468e529f857b1fd0f807bf30a3da0cd110f5c721424b8ba354aced88772d206041798459e930e20c7a068e0460dc8121b04b6092ccfc146a851406e37772a01777d49557f15c6817669dd478ac9487f59510672ce5905a15fbbc060421f88cefdc3f3afd09e8cf60d0567f3aad258c74a018035af015e222ffea808dc740ee6468302dbb041542507134d51afcd1e7a281c267209fec0a8001d856227a82d02c6ed351b7c1e93eb1f3740a287b51c5605da3972aae2d7f5e194ab2e33e1fe1e472af2eee381d28fbf350ca072e3dd776c480e050447d73345b80c81b2ae0168bb54502ecfd47330cb438de7bcd4daa3623e4b729d96e49b95071ba4134c01939307b799a76a01dab80c229e3fd690d20e613572064cc465b0f4d6206b9fca1840760fe016b8a7fd965e27aae16fd23ab0be7c04c857a9ab8ddf80286fc861b6554d3364250d21dd8e542f5bac198a72bb74480c70cd8f91a3c21f007da8ad5f56415a67b0a3d4f5cbb688324da7a5a19889aa55999f63e941e23ab80291d11eb2d95abc15f96b5043b952872313241f1aaaf072a9ab22293648853685246f7012c9b54a950931ef9954aaaa3e298d0b60e0df6168c3e7c4a6e37dfe57b526e622b55a8964db8ca4db426526a09f78a692d12872146d6894ff59720c210ae93e2c0a254276f96910766e5f4b50f9cc3b2abcc7662823ed0a8cad9b2ac2ea47de13cbf41445fb8798380cdf5f33ff1e4b9ed13f72a7585f9bcb30bb6b4a52957c555b7c517079ea26967665a5ea88ed676eaf274869f529d3310b5c10331a358b29e246169e0053d23eaf49b126984530c2bf237202e19359ad797c1287636c16d4b74e130c32e71de636d637448ed53e3c828372344a11756019625e9941754927bddbc90c1e11a5494f6ef1a2e24b84b98b0472b88d0b3090af3587de8d536ad25c53c5c8b62b365861b870a90a0dbc6f9fa63e30710b6db3892c28ce3047a1f0030434c55335dfb4a6c3efff0d17faf873a40d35693ec6f3866de3a9b433d9344ea610c9a6aac40019582246d7dd4d9470ac4bc0cb5e92fca3807db7114c8d5d741effff39b48ef22571876107f4518d91af72a2c4902bb6a8fc6f1fa14ed7c8e015e3c8aec74054d3a493e2320d723d159537b2fe7d958d015af6f6fd62703350d5f88dc1bbfbc2908a818f8b98207549e4729a511b228390b69c0dc9bfc77f5cc56b69cf94d5b3d4d09829936d1c02174972f1d889d5b8af62d2a72a621a032692f9d49bedaa7af5efd3e3ce435f20452b2e1d71e7db70f339927ba7079ef9145460c3d1fb34fbc2951bc8c8d8bf204e7e2e2804fe85b9f3309dd75779f28620d3523e5bb61c288908eb104adff1bcd25172eaa45893ed75f57bb9e0d1f96d99592393ee08c4c4309d7f517eea3601dec2725dd97149447d2c330dcd81a6f72c4bf24bcff6ad00610db1f49dff55f82bb824ff494771a2c165320078e86946e12af055eeb1165719e1cca64c51e47027534802149545f01aae017840dd4c5f9966519b134f77ad6e981cd35e199c990aed725e3cd40d616807af73346f705e86275108f70a1f099a064f74d3299d64ed15725193124b9ef788e541b05bcc3386213e64205013e08801e8fad7e07ed5eb2f7245053ecf59854102a893a88d7c61e04d23806fc2d9d967dd2f939f53cf3a0f6fc9b5d37a86ecd208397432a4959e117f0ddf8d79f4a858bc6379f3148172ec72d71e720a68520c0b89b56df32a351de5df809bb78b92b9e52915d288a561921cfc00a8210bbc8f2d94fea3219fc0c4a300fe7ebc42beea9b641a682168a84a720e82255ab133c8ecee7a140a55d2d59ba168b89aeee8e0869e77f1e7e03ea6724799d4c14de277e6d2aa0ace134c3040eb039547ccde18e2f091cbb37bdc2d72c9c55617d645c5ee5612eabc59b86486066453f4897f9d00e54c27c78d01365e375ce8f9ef25c79393541dc5aace06b9f4578e74a7ee89e222c634b8ba000e3e39e6580785a234998c76f2ed28852b14bb58b95095d2431a2003bedd925ba63788d668f5a0909d1377eb2149463264d912b44512bdb910043edbbe9ed63d793ab01e7348000e246c6402c2d3574182bcd04799a696165f89b793607790f194721e3fb8cfe7189bbd3837478c535f156e85749208bcf213a2ed982fc1678ec772b2066850920cf997ad91a9a5a8bb141ea7a88270b6c46d7ac09d70468c1e551422e6dfaa90c3a9d8f008c5e7e4c02a41ed5948d8cc86cd3c1a3a1b107071b53faaf2555c6eacf0a3e79dd1410214dda8bb97521467f1040b2cede71d9d1bc52e66b97832d549ca25e14be0e2ec431b2c94888d5b2107cc34e095d986d7727a72967a64c0b46a4eafe19714b811dd0e3864cabd8eca571573c8d3d675341c941edceaa910e5fea95790aae3b7f8d1c7f2b5f4d3014e0519f6dfc8fc7a414b9326fafaf801e25117b52ca03e7dfe75a41c1a1394db51abd043c61e462415d94372323b9d299319bb8b1f222addaa04a4194352d13fee88e06e9437880f46030c2a2ef867c687f1f0bc82da7fe2eaab665760803ac0904fcfa0fc10d025a27fb6586d899542c33d2a66e7cb1dcca5e1a64bb396b1d29ad5a4e40a897ea80c947923afdecff84df8a57f5b01f9e89673b463001f933f203680276dcf25204ae1b653dee4ef0aea60e8d847244ed3c702e09e66a51ea850478c322c35325d512a607248903177f8ec281ce6d5b4d25ff6980bffadd55ed6dfc13f0138156e182e005614246276ef9bccf95c65fdee8fa4bac9bb033c407b7a15debe8e39d8b4ca717296ff2f59217dbf8d3ad4705c363a9a5b5cdc6de31979ab172dce3d8e6ea36b72265b8c8f902125a0f758f92626c1db71b0b871e32abde334147c18b6bb9d8b68032788e59045a0f6b842af8a0ac47325c9c415d9a73cc67f33be7d50f43dac729fd4aeb9e22931f2bcfc72abd8b5c19a5d7cb14a822865ddcd7e79f06840f9720a2abf4dd07909d01ac244fa4b0684c0f2fcb151bc96e3637133aa5c0bba284ad586cd0e31daf7a7b9424b93240495fe51670ea255b4aa02ac112fa9def7200a1bc86048e4b4d26d13b54de8b6d338820ed5fa814f18020ae742edb4dddef4724969cac7f0f689109c382671311bfd2a6a14d0a8d2808618ebc08b5111317433344a9072c393d61806ac4350494c4eae799546a3c368ac82e32cedc1fee920373e7f41e6739f1917931b4ef8e95806271ff3942a35ff63cdf007c017a5f8e372d50b123fceac72c4d52bf8d695585c5d8bf6cb74840b201c8797b1441cac0572740e9cafdce741adf457010aa1a70707bb9686498055d27675689223f6d00f34bed07f47e27f41f2e64512d7dd52836cf183a08dc921086fb2114497a96a78036cd681e345af338a920059675de0db22bf6eb2086c3d2e057e84a796f49d092c2afc361ad9904da58171ba75ef60ea7ad593502358d11185282c840ad3c6d9107ecc3103ebf1b70ea5f18c52d6b505b5a445189bbb9d6ecdd755cd3977dc6a72497f49378c1892e4217f082e8c109f97263034d64f93507a214c77a6620b9c720dd70562729049e872b06ba8e4e4fc66a308677672aa46db86693003fab72711c727a71375f461178e8705b41bcd136fba2b3917dce22326dacc8de99022ae19dd4869cb925d35fdecfab94d0c9ee6968f7057efd8fa86a64bef30aef8807f523dcc563411183dfe6f2099712643ed5493001f45ba7aa4fe899728837c5c4872004d157d4adfee04f055445447218e25ffcc575456fe73a99031823aebc5b549b4dc575c0616a39754fe3bfafa210eab2e7afc7547056f2d4993c7cbab7db65d0f3d5af954cd7f68e80938e4f12f90fc206da5bb1ef966dd81e6296707c93872a943cfcd1f43368c069025cd0cc0ea1975e5ff6e56cba718f4d11d5ea3f90752bf6743aef0d065d8d71962753bdd3a5896570bd727ab6ec67006cea96da43472581e05394e4efd175c6aaebd3b3d531f72345d1d0a86d9e146bd2f34516b7172f572320847b18973ae8e58a2acec4ee0f993a94b032c245c531ab0af413d260134c962c393a228fdb363c1bfd1de4d594cea2af1fe73ad6ac2f812abf675421b762855f53a2ba773433c6a7e8a11c0e240b489b35ffc9dc15665d385decd9621758782300a5ec8fadecd87f87478cf293ccfac6f5ff564931583f68d8cad82045f3ac7c8b17d704b0e42f48b7a97ebbca3f5d35198f5127b8ff0e1ee2cfb4c727ed4cb9c7200147033e8e18aa233bafe932d72909dd65663de9efb387fd39e3d7026d077a0813fcd3b26a84993499e60c85e25b122553d5ef5e48a59e09c646ce9250e9168c6bb5cb0decec3b1ea96dc506f98d9e74c105e62bccc41f2898672368014fb4010f186eb2d6324367b01c43c72dbb7e0b801d4d9790a0f5048e84e2c9665fd79eb4ffa4a60427f351d239ae9f30d63f00aae2ecf4acbdba5cfd80c7c586f3f0787442d7d8991e2beaf76e7d1b6ae57515bed7d231c1aef8487e772f21e1210fa254a4f2dabab276be940798e87cc362a70f8be23a42668aa9ab8546e9446b99160ac7a22e0111554d990321f2c19513c0183c44cf7f9d08d95cc7248e0cce3f55ba2f77b5086333e6fe27a955faa34eb9a7c75b5498087ebe75533b4b004a75392c9c44eba505d7332f40d2cf4f16dde67a640ea140f8e163c7b46df98b99d02ae7519ab75d91fea23695e0059bbab1fd42d47df3d91bbc2541572d6f6f8c5a0eb9d4f72f43f0f6c491bd8dd8635675a2f345e293fc0290c5bd339a38fc0bcbebf79ab6a051df01251c4b1c711e56af1a95583fff6cc2bb21de2601490fa330f7fc7ef04099dd63389880dfc9c17f3b4818497e00983d8b90ade5c2c51da9fa23f495ad26fbdf1d115c2d5e6e3eddbabe2bce6663eef63a9d29c7244a417d07f3f992562100d27057382e8a1cb7735bd8196eaf6dad8fd04f57a724a8d5a99dd9a2807f6ff2fc9774289573d9d1c8f7360117ea249815989bc00721fcaa15432f475a0f30130f5f11333ee35c2580879a4e12bb07032ce8ca7ac72d43daa6e40eb13959f2a000eb91272c67a7f1e78c494dd86db254a68237d3c1736422f63687e379f3832c982234c7f491eca9b92c0c4bedc8001fba55497170ef9e5f51f6294b3cdc6c69aac13b8469f16bd18d1a9f0e3b5140480cdaa55030e9931ab70db68ba132138a89846da967c6ee61f4b126284ba1c63be478265157210269b7eda15efb86b6bfc623c5592eabe2211d9481f26bf7d76c35a982f1d72acb485d7ae77db75571c5fc74e7e95a7f3e72f5acb96eb0601441e60723aed72cb66e4534ee2020f36c748b00e51a908554357e8ab4f39c6d59dba75088b1b5531c2dfe584bb9955ce6d0a0211d10e636ba40fd687fb22edf624fc8abef27a7232cd2df65c3b07d78d599c8d257aac19d3404db163817e3fcf5a51729cbed30880d6295c4599c9eacf567f812837cd649c51a77dff485696e857ab6257a1e272aa22dfb6b9bc5472888ee6109ab161183203070d52947b7271857303031609729123969be55bde35b330128b2932f993d76ee3fe5f8a4008df7de348deb62a720d787df9ae6ec2512f3ad58a09842885eae26b56136c1ddf6213f686fc282e7209a08413c98c6716b9639b8be79160497a646bdd9042fc33d8ec8ec1422f0e72acf87a57c7896db829adf70a7cc1a90b5dffaa2f369dfba329ca9db1fc15fd5e8811972a25ea4d507b16e1837606df8743a17022390daaef6c3247f1795acd72e5ba3286935f9ac533b9228ef189743f9fbad2ffbd525a3a058173e9e1f9666b3f091172090f6e767c4c8c4750d5c5f20bfae0e0c9f1fecd38adb1d7987c12722284cd2979d936b30c4bc96c32e8075efdbb9e35fb601d810dbb9202cf98996376e1db8f8c01efdf84b76a6044c0f5d374ca2f3ca33893b4badddc3589e1db72d9af173851053e3f096fbaa54a09d2bc247dae80cd6c95260a71d1cd701e6672a6e2c8c3c308392711cbf9799b32a9d00370cb24abdae16a383ddd40c1d33272e27d475e93c6374d54a6a2f3c9e3811a360ed6e1f00d44281810e3e9340a771fecb9b51f7523a50d051439c4c793395b6af2b5a36849a53e934775bada85e5725409eff1fe3427ac400c947704879b6423b84a4ce6bd1758d41312be29e14072eeee7e822d648513f96a7a73b1af2141b6e1d8b831b1d81d679036513110c5724bd1c28c54607ac66e0ebabe7eb48a7d509b018e3d39742fb0f227c5c3e87357751c1279c99643a071c2340a103ee7dd36d2f73bb1bbd879fac0089f38d4e272e2a6178b3fdfdca5dbf818e5b3cde37e2dbe37f6f7481658eb0d18bc8dd416435fb2d27a33c7696f365adc84f36c574d65bf163c4deda7a954a93c1b1d097e6bf65e0f82f7c593dbe73f589b71badcc5957867e2ec01914f98931379454294729273873d6b77a60985aa4c6194244742e3f30954ca8ecf437307914f2f735d72b5fabe1883caeadccde52d8b502b6add0dafc6c42377c4a422ea982b6f8a05727e4eb07a2359b19611095708aab76c0168753d37a7a82fa7348c432f122e0c7295d8392e7da8cc9220bb0c643c8771c63147cac924d08fd5fa720ac33785a73d1ba5cfadf828ce8c5463ba791d4bec9fe63612ed5a5173339fa9fcb645765e72702a8c21f91ae7340a6db8d8ba5ce2d058dceffb5f0c38e1e39d251a04d3cc425eba153a6eb242ca0b431e43ce57a9e50a9828a787c7af987c9d8d794296ea72ba7ea44bc5bf9d07cfff42923f684fdfa050a2582eed5705bdbf0b58d930607207319b0532683cc271245b3c3d7f97ebecc5544c5fa585768391107eba51ad72d3cf8be70cf42893f3e4bfe36ac227a7e920196d185d94a651aa0ec9466eb472ce144b523a1e0d992a7fd6c9c7194b91285b0dfeaa94641d99782417910b3a29a9e4780ca1b72874f401227b5000051ecf572b11f861b2b691fa6eac381cbd703d6d92c5fa1aed83e94f5e0e1768ce08d1bb1150747e54261004539da874c37277146524e451de7c6a66d7540df3e1cf880a479b3fd1a686d99aaeb09ec4f772cf3eb1bd6d5a46eccbefbbfa70ea0efd684cf1c6a36e7934228623bc6606b97294fe100cd513198f8847d8d6c194968f4ee285eb378d769f7a308bca4519636e240866b5ce78ff6a3149d395309724f1be0112d67fdfcd2423e848d6b703a010830841d596d1ae7b9a4e9286d37016c48c9a0cbede6281612f53554471cf6d3316e071343d2fc77bab50f6cf24fd46a496f300dda0a77357e24811f41f8fd3727ce3eb39da1e8b6283d70a294ca968049fa635c95c31c611418356e209651935af81383bd153164d1f1ac1475e7c53a3086d3462165f4ab1ddfa88adb684f7707a15cc25875751ac1120e44eac12b9796156dd1a59510a8fc011cec0ce44ab7224ecc6e33024c0994f54010dd040f292941fce0fcd09e0cba675101cfca14c72ffd89320f87a192e36d6d69de8765191fa5fa2086fd496aba3604b1ab6834b7256742dfed1aa20147454d08099a8fadab401fab605a157e88735aef1f83f0f1c7018d2f1211504f8b505659dd007e631df1c58d1ea635c45df1a765a8ddb4472e2217482149eb5d4cef147885687ec9cde2ab18ac6bde771bc2b50c6b68a13593d5aadd263a5d9c1a1f2423575c57caf605b7bc9692776a0ee7b0b3a3fa343729ddc852af867d1697043cecb84adb5fcb11924e84d58b9cb30ead57957f120305f94541246181fb5a859b7ee50ad7957a1bb5dbd326773223ad1486a7e4ac75dea5aa851127ba70e46f0a8f7335beb54a01ac5133667d389cdc68eb06a1b607203f9ef3d12be42cb03ea0bbb2fb6ce1ffbbbaf69fb7139e1ce83c4eeff8d58729015547756e8855a440131fa782462959cc70f4b7f78e21c54592f0bcd39df2b9c3d0acbd5df9e16d7783ff43fd5b7a6056aaa33e643744b52273b11d01e5172b936275e6199127c8603a011c7af1e9ce7ee282770e64db347f0cfe5359dca72c751c02251b0dd4f7cca04c80e38f30ce832a452fdcf111e0968c85947869a4ca83fd107de8ccab753f2131072a9de8422a47f7eb575f5af968b660c12f9650aecf9eb05c96647b5ef44e64d1aee0fa21194cef3463fe46084827a7673f74472657c27a27f4ef84c2103c233fc1df7292f25bc0d3669a7c1e296e035f93b3072609807d719e1735b6f440ccc25cddc024279974a3e5f9916f8751b0ad5994f6acd57e040fe9c82c6dc9ce290d01202731f5bb46bc4c7bf27294ff06c855ebe322c760ffd84ace7525dd89e2019671da3733e0763b8fc104e933b613821984404c76eb83e2910a88b54c02e70967a9e1cfaccdfbbb8ad400f05eeca59d5263a34f285a8f847b03bc2553270b8209f904ac30bb61ca5e54d2e583d654a0e89737229749a00ccdb6728d44d7735794b5ffd5cc25641a799af1586d7b0e14ae81072645a0ae6f2f003403f1ced071df7fbe6ccfe5c1f2e4fb027531fca8c7ef5fd68ae47c2805f2bbaa5cdd8a58396e6e45df84ef7bd85d4bd8aeab42f44c86d52722f6a0124a1fb17762369f4d64177f61c1ddaa214292ee031928a682d512d85716e41b7345b2bd524481534c6ec5148767fffc067e6888658b9251fc8d28d7972c0b8e45ccd05e454b0d018e251673167a1213f95241dd4df9ec16e2a9f4a5967904c13e035ab8f06dd095ecf4f58169342cc6c483ecfc732873fb126ffb5d07203e9c5524a0f31419a20f094d4fdcd99f89472000e54c0d9b6a02d8a28cb493b17880a105000bcc96e6aade4ddc9d0ea860881e904715802aa42ee22242e44720ea9dab3154dcf2b5111ae0ff6894735ca4bbc2dd6f8ee1ffdc82fba87830138085b6fb76db02aa567497aac6a7de49c4707a509ccd8e1571592df01f0cae5507315b9fe4be18dd9aee6e7b9e602823c0f2aa169941107cc6e4aaa44d09a4a44ec141fc3f48840994af3dacdbe21b0bd7a0602d95bbb8895b7e2c26bd4d4e9149ab8d28df0394a55eaf839037428ec90b349ec4e5cf51338faf32c6b2b4be27277c1d9335f9a568fbf77486700c749b511ac0f41f8e84d49bf9ee5da8bbd202debb70e207a78bb1fc020e4ef198be0a2d7a13c6e5a1e9f8f8cb15e999996f17298b7ef19db1f3bc9c0dfc614de638caf518f054cd67c6d3c41250185ebd36f2c5d6f7e8dac0ee31a585a3d1acb8ade383f761517c7ec2febd1537997526c5e7276e8c3a294681ab4178c461438d7934b74c58bbe677cfb879ae0115c2bddb91fc48b7e8cf6baf0f51f0e40cdf8d241209e8646ee79e779b98c188342ef0acc39051ae5467d30bdcedb385d9ff75d5fd5f43106f2137a612715ad899cee600531c87d0d19d611dfd255cef1d4725042fda6dd11dfca366c06ab0ebd53ff9416723106a703a6437578084a1e6b9eda417f74bc63bc206651cb77fbc60176ae137217142638cf5ba2535e86f0ac834c72192138d180d6f82c1c165cc7bd3dde6e5ea130350e3cc1ff768e407f44b87f41aea17363de7af91ba8d418c716427815328b41a4422a7c76a8a077e3317ca82a081a4c472aac61cd31bad214808f2c884ba5033c953f5521ee9d52f12297430a67b82b08cb15c3cd08bc6a414d1f778d7258bbafac13637d9b6c0d440d9f2d939fb3358987163121799cd606d18cf0ab72f3a7a4cd9ef429055c29b34016ab6890ea21e193072b88e7596683f2b1c8ae7269e665af3a2c60a17bdd8b2f7f76b0f7c50099d32049a65d319cc8911349de7206ea006e3ad8f8ef8cbf5641bdfdca9f664009c50093908176a52eb8ee30ac72b156940a21f505bb79078c1f306e4f4997f77e51fb691b2e6c5b34b5afa49b725142915af322cbc56e902710ff03c9773f3692cb1a6b62cf763aee6e6aebc9727eb8eb580303f3edd4423ebe1f8d75b54a98d71b98ba35997d40d10901486e72e3acc76f38eb83d7dbb96664b93b991a5e1f0a81550e37fa5abe6b37c714ba7208b006022842e7cfb97735704b05b6977ddc7c3d5562db842246c38581583972182b951b02e6c6bcb3f96ce87685172e0c623b8e3e8ae05ca3af6c1cce4c8d729e6a17b013acd3d2d56dcf42184b0d9e28e6d1ece552231ea9996a758eaead72a50b4bbf28b127e69b660daab93b0e9eb5dd8b96f48b8b2daf88ae15e9733a72102e419cbbc71612e08674e3d9e448b0e709bc68b03c5ff22534e6cb79b4c972bb32db0ef7df63fcb6ea338a6b38d40d6c040ff509e908b90242cc9eea621d4b828e635ba7644f9942e99e86f4789c52a9b6fbdd470a3d06b6fb4d2d64c15a728b78c9c6a2dd6d1e4fb94986333633e886abc7a3f0297fcb2a24e58657ca467287a2cc140aea9b0d74004e5c9b6d89a2bd02e89590c0c8f5a593a5fc6a8af672fc31dabc95fa77838af65a65aff1f7147b16dde1470893e115c7d0fd8410a572215ebd702dddebc3b72960bcea82a17fc5d751162437682c68582148342685533a31522044c0652164b7c6de052ad0ec548992d16df1a4554fc0c352c501d87217ad011b30233b30b23815d51e6ee2c30e3e6446f7e31fdaaa6f7d8eba112572d3c76273ab19913672645d8978b5e7052cdf70681560a9969daed31466f71c3fe8caca97b3e9ada4bf3d85b927dc4a2dd57fe7a70af5e8cca1dcc62e9764194cc8b476b3cdb99f74759e0c91743eb8a99a51e7516634836e2df1623c888ae24c294b93d2438b756c950ab17b8857c04c19f8e445b467cbf0cbfcfc768038b47239b1827e11b03f0134d645c571f16a1b55384209dd09e76a6ba9118e6ae6ba25167c1a4b4d6b011de3bf0b7005066d5201a09b208a06f1e32bbe3004e184d6537ebab425e95eeac93e52fc9a1451096869acae0eca05f14bb1a55a79cb87222d869aed254c82d000ddae5439cfce377c6f974190be32e1cf5de9fa40470e207267894e259a80f59f773c90e7378f899a0a144c5f15fbfee31634fd58c553ba5a78f099806bebe01d821899c81f2f38d96b850f91891f58fbe197f41b91c201540aecb8387f2e00868682186348664447d30a9d38b9bc5da4ceb4b3ffeb3cbb7273f43934f9dda449286353457e15c397b516ccc6ccb30c9d42e95891c644753a55463f4d93b98f91d65a2f2671c6dbe9825f5186a7a92b5a87e2e824e404816c6db5fe40f977c834a95d2ea62198089b1ffc7fa1a90375cd0c3be20e0c28dc7276ee389fc14126de8331631de0b6c97aba0b8eabb1e884164006538dc79e4d61efba066935010b4f72caba01103fb6c533891ee905b2e927c3a63f9dc2480308b515b885ad40a63b04a901eaaf936fd21decce93fc9636b294fab79539127d4820e543102e1dfd408d0bd0ddf8dfd494b1ba3966141f0f1d8e0a58488bafce3a343f6670ffa72f44907252bd4344d257aa0a4e574afcc38fdacbc1c5b997654e58cb20c84f5c5a78d57f16360f450bca420cb021ffc7bf59e50c900e3de3e272135aa7dba59f10ae3cef9554236ae5072aef1caf08c46642541a47e9d1c2ed20f8f7c998af05d0083bdcdcdaf5bdafc96fedcaa0eefe7d4e57fc1fb759cccd721de7020889748684dad0111d40d25a8a838db87758feef200e5eda0286e32d72ab87ceb45f123f1dd609371e3a8c0100723d482afefe03a725152475dcfb9e72ea8e3d73ad34e8bae2b41e8b33113c9ae48193e491ddab7849cbb95071058772b74f41f9665f6cd30e7637d385493ab10b43fbf00cce07c10143481801f057724a1aa1ec17da8a766f417aa73c3320854d12fd0f77e07136388c5b1e552c38727bdd72e172133bb05c0c65a1029f4f73f958a635a70b003e08eb696605ca0d72669d7a25bdb655044cb0509a8aa37f3f197a60239480cc1304c7512a5446636320f4344ab77ef57550f80dd23922293d0d06219053969312c575c38bc8a973243c78fdb27db9271933f585808d5bf1990c42a040977c8c1e6912bae97e1413729553147aaf315ca46bc70d6a2a9669d0109bbd0b668bf99e0df7656a9565117207a24006595626e035fd5ef377eca125c0aa0d6d6288f5a227d9b4bc34536d3e3d2660ddb5b926900d36de5b7b0dedc2756afc0767037fb61024da119c35914f9baf853fe06af07afc84ac4b31952ad076a8f76bed31b15248d6d0af1be6456b135c2315105f970ef617b45cd4ab34d0c1eaac21265c7db4939fc3ff6d6cc772b10401118d243de2f438c649931f2ee01238021079b687325e532bc24742ed688e02a34b7d94be86fdede9c9f73a119a8c418d2f2689373928a8151283ef4e72787df6a1deceea75d8ab00a0a3228cb78792c18df620dc5eb1363ba30b27e1721a7344a057ff347a1a8701ceb01f6e24a01aa21777a6dfdadc017f01058cb5148f7be5c1f1b3a94d144cfbef483173463fa980bff85908f511d2d982fb0baf2ab297fd8c5e6fbae9ef48df9bd9f84c0aad992ff53bd37c04d26c364305290d25a7a5decd733088f403f64c2f78830f39b8825425212b9483d88ec0479b02df72362d0776dd8061a9dba26ab12c01789d235ba0c62ed651e89299ca8c2c5bee72718a2f6bc8b5182d23f55d52166876996a4f8f4f007223d508727e8c98095d7241f88fc178092c12603245b3a0e7be824c56c1664ac20a0217fd76c7e52e775dd087cb0361c1857409c647c54b9cfa8af25823d596d02f67e5a2381a9b692a44007bd2f1b724d4fcd0a891517b1682d3ac17006ef66bfc77a28baef0a879ac724ca1d8b11be3f7b8cb0ba25896e35dde4f7b84c9d49aae499fab33d18cbdd334dd05a3d1286dd2cfd3342448f5102d627f54174bbed1d40db50223841e637672864f07b9f17dc4f5471f9cc7ede2b031356a39428e1eba9b18eece247e93816181e880984dd90168e9b394f6ef135617efd0c31f1d194fe3ab19239b96843a574aaca57ebe1c8f2d41f829bd2220be3e2ba2471c3df3b5440dbe5ae07258377287b9122952dab84eb94e57418320d712c67922595082dd9518c67eb537028036b9afaa17ec138c3b8630e8c44856acb52f51b6ec5cf46663ee54ee95543dc5728d34167f6399131bb52a95e85364a4afe965c09372f11d657b2ae1c025998272081fe9dffabfce3e7e7e85543ad84dea527c765bc29f6571567b8a616c8a7a722a6464118a5dcfadb97fccf902f483f6629f50b589438e9e23f2b11e35489e72639fc5d82ef6ed9193dab0af5504c71ecd5247e305401d0915d3397326ff110f292aeabf0db0d698b4d00b21989af9de458186cefa11c4c6d8f62566c9741372d6262fc79cc68d86ac6c807547cbc650d335c371aeee454f6f2c91364f43f248f9750de66870c63d52431f53f58299c3a75537821ee82855cc50e9bc8f3b4872e9902f85852c47fa8cd74beabc0cd2f318798570a8d2f8f518201955b38ee3727ae3628efdd2d92892220b44f8ae3b4b6c72086c8730f8f8c792b07f5ea04a10d6401920ccfa6965d568972addc8a1b01956908d39445ef282be3dbc1bba1b00436a40ebd895c1fa781cac661f3435ae6e93277824cf6189712efd0be3f6ee317c6c0ee51feb92ff93dc6a5eb49b6cb7c938919f2b4a1869ea232c63332e220d2e45257197f7008019798ad03b7ceba7452e96fa0884efcb33a5d8173753642ae7172d28d436052f278410afc6b9a20fbd8f73f9f9f0b992cb6b422b471bb02738e5b91120d0b8cee3599742e7bc1503cc41716214380d85b5965362ee8ceb7249905c31f5b52c3714873821d359a006cbd6a6510712a809a205363222636a2634334235bdce3a96d43bb83d5252c1366e999ba07d2c8c58bc75350561ab38513661951da9409382ab848da14a8d33bd3085810e14af035e70ceee30162a38724ba1af59913e89320f92692f0b8fa62bcdb32d4473bc90c758780d6efc922b72f7204d6ba8cc7cb3669c9925de3ece38a812c9e175ee1d327b07392bf18bd572847032c9eac14e0867ff93c08ab475927be83b6507d8a50db378fe47d29b05606d65e73a593dcef39ea16d701ed6d06c84f7c9e95ba3601c5a2d091720dc0e72aca428382100e55daa19558c3a9d6f33827ab763d12e189dfcc1d4828ec2cd72b5d087a6b94aa69d3a4e04029e1d642dbe7d768beac5c245aaa4d8b371741572aec5715516c6d6386aea4a668397e5cfbd6348b3db5a935ebd85ad3ef08da472e912edd86ba03489d10ee2cfd7d65a355ee9a969e66b4f0c8b55510c40aeb332c0ea36a03edde362f2200fd26b1a49744f6bac9e959e39d39b62ddf5e969ce72ad13a9792e178829e11d13378dc948f0c07c75aed0b556c3c3590cb4a4d7eb7268695cdc5bec2c45a9f0cb30a4bc93c8e428f0a889503ab00efc965bc373ac344c476c5c7775fb02b532df6385428d6557b9b948af3551b94e87f0f13366fa5811a1139af458ccc6d95bb27ee236929686fde76fdb692a7ee36e01616a0eae5e7fdfe509e3bf6e939b6f6cc8bdf35c745ec15cd12cc4e1bc47f35958eca481728181c805b73ef6f7fe1125f5cdb80563e08cf659a9d9f8f6b001411afb6e5772ed3704106442ae180a12320d8dc8c8b1b565f38ea5429999520d9b888e4ca06804eca2c14336c06aef2bf829a762794f3397f57438f96fd64ecb19fb0402667267a8c1547553878d8d650048558fa9870c4d8f6d865bfb9d6274af8b6ff5660ae0378fd75fd655833878c57e2668c6405798a32b5b48b60ac4d1ed4dfaec530bb345008677206ca1e71e66fa09caf1f513e8cd37462826ba29a00cf013930772bd01ecd917793956ed317723e37cee8f5cb9065c415a994faa3aa07d84c31e723caad4f175004c7c54dcae46458b23af5da3790ce35f72490854f10ff7eb1c3f78374266394fdd12342520667e0ea5ea7a7559772e9e3f17f9e6eecdcd538d720a7beafa3f713b28c1f50d962990ade9c199fee379eb1b3f182053b0cd23da035ce6dcd6ce5f185d5981cb5798e96a5f3dd64ccde868a469707c05f6f79712720f174fd1497df5bd27810f1de6f9cf8732d2f3ced0969dea16a19396129bc372845b1412b2d0ae55e7c7e973368c67fae9a701ba374927799506231a1fd10738f57e5a2d2bc08c0aea1431bd276ef45944dc3b7cc212ab0031516b1f2a3cc4720b7a94b4b8e985f0897869506225ff7954546a6bd0824111560accbf97766a72f9da2ce9be363fff4d5244851573e7a7f31191496e790525105a8d5ce2dacc2066a3c647cda0d636fac9a1fe9a90e1c48680bf70fb13a74ec63503b5c0cfe31397f01f675859a89fc3d4bde39dddf2f98382cbe65d929428ac3ef87717ac0002c1e85bd84d9011b4eab73cf5b0f056bee8a7f7068b7108433caa818d6785b825f289adab5c60ff10409fa1ab8f5059b1aafd6a4d9ed92f228c6a33213d142f7204f2971b6304155c565040fc754220c31c5339d85e8b94d59bde4ed61ee1161830e395dee6a56f03cd2a07511bc3aae519a1380f0c8bb74ca8884b1797496813857435118d43dca432765f33552ca88b5717613772bb7a31e889c4a914d53872143df6b52b4802eb224b53f5e82cda8e4f11a3b83d1e728b0b2e5421f23ab372be56f48bae41f24b8b383f0339a1e81ceab80009690508e6a49377381f57e31ad9ec23c40eb6d63888607eb99a9c342ee41a8fd63c182219456370708eaa1c26916a8e36b95724ec2e67e08ca7bebc45ff872168bb3707a4046a40a1ff082e72275cc351cf9eed2c88d2a425a46d99d7205661a205291f87cc417d9d8370d2720432b53cef804a0c68a602dde77b976bca753d4ad595b75924f05901793b880e4af93c5ca812a842f13d7fc0126be71ec2d1377dac867991be5ef83723d70372821e1055fea5da5d69cc788c2b47a8794886332ea63662a01ecefc2a6568e7374037fc9e56fff322d9030f0b747fb10cf9824801b7a6cd425207087f7e2930f90126b0a5d664f06d6297c35f143d03b18ad768098ccc86599f146b570f3e1c138f9fbaac057fd08db6783181a91f605f51e1b3b0b535cb062ddd702cdf6fcbe64e503d690f46f25324efa4eb7eac17526f03333cd7678e4eebe2fe86c34e2cb4e652c7eeb0830ff4cf111ede71f1b33f74ff1e4271e8aec1c6437fd6dc94d7e641a1be6654418fe93d677d0c9375799c2be7e35e1ab090d02b1f857ace40670079a42a482a3577cd1d4f9060373372fc1a2cdc72c18efb500342c6604cf5a469cf2e0df3910eb0a760a84533242a43ffd70240af240595aa81549dc64ab7bd80c89288e8208d2104451871bd91eea63d9dc2c31330b4d8b083bd99895eb2d9241f460d87faf767ef47f849f590437da1e2124f46156e2265e2006087e0b34a8877e426b6fb548166f90126b0a24aec110957a69e4bf3501dedb1043a9177a14c64617808bb38df855a08c544b4a5a59854a0a517d4e95b879119cb3bb099cd45b568708c12417dbb6c01f1726ac04cc0f2742a0515f9332f2e047c0aeb6a909b6a5dd615bb6f03ab75da3d32f7b0936e2093d44c2b25b6d2cf32f0e30ab6e6498a172c812a12d6c635eb83bb69d4d0fbad6fadaeb46feb4a45aa23232e8ab0b3244b4cd04d6ddaea930e160db69d371e227e6a48e6d142814a353aa73b30fb059fd91362f5cf5e32b74b6748914329b0b80aac0b38e353a057fe93cc6c3086da2927be9d6eaa9c813a5eebcdf03470426b80377bcafcb41e8a85e074ec05652db08137bdab01ee781fe63c72f6a9aceda23a536bb496a7a03a60e00ea85a8d562dee26647b131a960b3d6dbbb07209fdee"]} -<< (9ee7c86c) {"jsonrpc":"2.0","id":38,"result":"0x1f9e96112a22e1fee7e4fd8513f12e3d3302d656e276f16e23fac811d8a10c9b"} ->> (9ee7c86c) {"jsonrpc":"2.0","id":39,"method":"eth_getTransactionByHash","params":["0x1f9e96112a22e1fee7e4fd8513f12e3d3302d656e276f16e23fac811d8a10c9b"]} -<< (9ee7c86c) {"jsonrpc":"2.0","id":39,"result":{"blockHash":null,"blockNumber":null,"from":"0xcf49fda3be353c69b41ed96333cd24302da4556f","gas":"0x186a0","gasPrice":"0x6fc23ac00","maxPriorityFeePerGas":"0x3b9aca00","maxFeePerGas":"0x6fc23ac00","maxFeePerBlobGas":"0x1","hash":"0x1f9e96112a22e1fee7e4fd8513f12e3d3302d656e276f16e23fac811d8a10c9b","input":"0x","nonce":"0x7","to":"0x0000000000000000000000000000000000000100","transactionIndex":null,"value":"0x0","v":"0x1","r":"0xb601d43215c076b233f55553d32fa2dec9bfaff9a42766dcdcdec17dfe2db7d","s":"0x374d58681d0ef37da749ec15a9fad320947145b589fddcbed6c12892738752b5","blobVersionedHashes":["0x01b0381c687bfb6a8a302b1c82e1eaa40ed61cef26789ea4381ae224873b2ae2","0x019fce4ea55033892d6e138b70ddee775dc5796dfc6bbb34a42825e8ce156300","0x01ca3bdfbd891bf00f69d06b24ee2595a7fa9ec16b33252c2826b2335d074b41","0x01549da3d2e959bee6f2bf70746e9c8485b0531eb86c80acfc4545a44c9d9e41","0x01c8ccd705a2fd73edebf7b293be6e2bcf81eebca7ef11dd64b9a5d7af975623","0x014d15e626084ab62165686e0bd08d22a612209881d2e8a25d761ad91b9ba4ce"],"accessList":[],"chainId":"0x7","publicKey":"0x95a6357daf5d9f91c85bd4e1f8b6226cb18396d772c35620d071660400a543d8b51f13cf95d7191e958a12c6109357a4e1e50eecd92db513dab323a5c1fe7ff6","raw":"0x03f901350707843b9aca008506fc23ac00830186a09400000000000000000000000000000000000001008080c001f8c6a001b0381c687bfb6a8a302b1c82e1eaa40ed61cef26789ea4381ae224873b2ae2a0019fce4ea55033892d6e138b70ddee775dc5796dfc6bbb34a42825e8ce156300a001ca3bdfbd891bf00f69d06b24ee2595a7fa9ec16b33252c2826b2335d074b41a001549da3d2e959bee6f2bf70746e9c8485b0531eb86c80acfc4545a44c9d9e41a001c8ccd705a2fd73edebf7b293be6e2bcf81eebca7ef11dd64b9a5d7af975623a0014d15e626084ab62165686e0bd08d22a612209881d2e8a25d761ad91b9ba4ce01a00b601d43215c076b233f55553d32fa2dec9bfaff9a42766dcdcdec17dfe2db7da0374d58681d0ef37da749ec15a9fad320947145b589fddcbed6c12892738752b5","type":"0x3"}} -INFO: Sent blob transaction: 0x1f9e96112a22e1fee7e4fd8513f12e3d3302d656e276f16e23fac811d8a10c9b ->> (9ee7c86c) {"jsonrpc":"2.0","id":40,"method":"eth_getBlockByNumber","params":["latest",false]} -<< (9ee7c86c) {"jsonrpc":"2.0","id":40,"result":{"number":"0x2","hash":"0x90c8b9e1e8e51584453f5516036bfe0031ebb595e47c5fd664bd56471ce618d6","mixHash":"0xf8caa5bee858bdf1581f3920c0a700cd25923f194fdafa96e47fb2198779c608","parentHash":"0xf9e9afb4b42515283ae292d4f9982cb0c87d2338c717af437fe39175fd5e5fae","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0xd51ab9181e964dcec962295995b1fff437d9504a88ab944324bc1227c0c94bc2","stateRoot":"0x724b6cd36d03f71b4c088b69f1db0a61b3d9789546c24ef32d59f1dc2007041f","receiptsRoot":"0xd50521034c860197d235df5876ea04b9bce05f69b7e89b96e597d9f6d35b1492","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","baseFeePerGas":"0x2da282a8","size":"0x408","gasLimit":"0x2ff7d8","gasUsed":"0x17a25","timestamp":"0x1236","uncles":[],"transactions":["0xd886baa4d7824402a508487d94b8efed257832170ecb5f5a85b8d2e15317728c","0x3cc701f8f4e4c7d32e1a55dacbf4175dd4a61b4b8f26be373b8f7b0bb4c430e5","0x6208c8da6ad2d0b72a65110f972a38861ab4d12a45397dc6026d07e5623f748b"],"withdrawalsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","withdrawals":[],"blobGasUsed":"0x60000","excessBlobGas":"0x0","parentBeaconBlockRoot":"0x062367f0b23e2d49ad5e770d9ad17b83c0c1c625c3f9a290cd9572b3fc6cfc9e"}} ->> (9ee7c86c) {"jsonrpc":"2.0","id":41,"method":"eth_sendRawTransaction","params":["0x03fa0c03a6f901350708843b9aca008506fc23ac00830186a09400000000000000000000000000000000000001008080c001f8c6a001a980e7fb17b09b1e2c9aab90ee0c5f1a9bd2848913b49077b6afd065622230a0011cd14dfc9e5290c5e31c2225f64f2a48bec55936f28751831f39d254ee00b0a00143403f36d4a9d3692fe87cf5726e6c063d13e43eb5b0e00032c3c1c3d61a60a0013bb19bcdb7320e9bd1ddea98dc49c74b4c3264de10b32ffd3a3bbdc9832e89a0011841c96e1f4a3fd856f561ed6c226617f2d212293f09ee7364160b81f0fd8fa00180bd20dbac08af9007e7b917abd22d1127d7120c8f2ec18e43ea84da20400c01a08896360b80c08142b823a49ad884020da37cd8abd21af74dc9cd30d550917841a06978428bce9ee1722a7b858d07d0a4fc0e55276d671d05c81e6dfe9c1226cb4dfa0c0018ba02000072676268c71eb961cae5f1a68dc8717be7d1bcb40517e72a5f910ebd76c2bc2172e354eead4a1b9b37615c075199ccbaec2f1a8f663e3b532da02a893d841b3d169a821b4ba090e56bda111e91919d249d15705ea516e8e16df5f0fbddf20a2e72b981912e7070834257626f63929515643df2d5519ea5744fc3d059ae189dec05a6adad51ee1fd9886517b2a6dd96e4526f905a0c1254bd2b62dc5b5454d246726098293277a6a577b73e55fad02c6589e2780b69526a3abf5ab94bbab054bc7203ea1e3aae7ac01d3898d43d4f3f4222574ca73fa470e4864bf7a646be6b2362f375badd9ac088fc223a34b2f93be68883c9cb72c4b376096f9df1c756ed2372c1540afcdc296ad091566dc441c2912c4eaa3c5759abddf8cf6adce44ef31b1b1f7e97248251e775a20ea18ac0c9070931da1f6ca45a1daaf23023949b3ae172f8c894baf223fe1e5e2300812a40f07b2e67c1dc0b17bba2c833259ea262c172e77ba94908cde08a75e60a0dcc93b51ac5aea3445d73ef13bb89f897f1e31e724040d224402f8bca570dff033cd6c3817e6c15fea3d25a42d54ed1f3f459c272b1b21f7e31eff1119bfdeef098ea30ee7fc1de159bade0aeff8fd2533f7476559fd19e7d5313224e4e5ecfccd0ddf96d5a404ceb85233ce8aab038a3ba6efa722839d35c4a60ffc04871b835fc8991d3962e9e0c5ff59e1c9fa324a0cfe47709257464a6f3612b77a0c8f271da11519dec34715c5b39c8a8cf8b25a8779d9d72a9f6a5096705fd2d9d2a7ec090ed1a186bfd5485f5d7b744905421859cf96c3784677444b10effe65c3f3acce478d1dd2af3e3e5ed9632337faaa56455c2da4582d74219316bbd942f8195e703cfe9f9c720857cbf32409a21e9a5c267caf2340f56dd04b05dc893b33464f61762e97102a849bad154e8486c47e1a4b4bd1f447bd7c7b3a8657d75dbb04bc4cce6d7b483d7283a301e8b6bfc817389867b2f02a4a983da73411c76caba87c25f5be7675c72df9db784942857d6bb54aac33272b6b70fdd537781aa85b03022e3e822ec7bc33893e602a88ff5eb622ea5e60172818a489597d56086cab720ed9113223dad30e203d1783e0e0123759e80b80872879ab8321011e126595f03f1998dddd55a66095607ce8aba956034dd1d66e372c7653ec5b5ae4d108e5085602d0b5db6cda7ab1ddcadc4bcfca4aa555f61dc69eb4aa2a3d2130e14073f83a84660a39ffa031ab344127d75f2deb634154cf73e76e463f3b57df5fb62634fe22e7c834d79dec563a2a9c5ae1659ead21aff4f077c51ec94be6773bb3c886846b687beb85f8511369510eeb56bf1d9997c0288729273d4813b48083f53bf026740c555f749d72474ad8448b4cfd151d9578a096cfed238c45f40776931c1e6344f3a4a2c0162a99fbdce82291a05d053effc3762e48597bd3796f113bfba634e19a5055afd59ef9865a7fc4f2b58d81cd9623b1aa5314663285085dbc712140a7c7d60dbc9e9714e0f7eeee6b3204b57a7ed63725f3304043d1bfb4b6bb16aaf1e3871f0a22db8551ab0d8023bfa454f3126907247dfa703f455d2e8cdfaafdf112ada54d84528af30aa6f7546496dba962e5417fd4555b068545e6637b6b9a3139b75faace13d60a5b50050d4d3783729c6f872dab51ac746e4f90ed4cc9f2c05e2cea90abb710e8acd5696c794f055e3dbce727a650bf1450eea65716cc7e9f9c98c3baed3d85a99d987965254b1ae7cbc2a7242d591fef576b0629f5895fd2cee3a79f68f7ed02c6e0e2afce80fc424f0006f8469d49b0df24ac82137e1b151b56b70f91dd9c9e4e61b716ef0e88621fe3f72d6fbf93015df7dde36a2ab6be1a836c0d6263c40cd1785deea6c1b36b3c17d6c30f080a47c89f41d360f85e9813eeb3b62c209aa0a056182a5fa39f98490ce72b11b0d0c590c12d786917c8870dc146508b0d7919ef51aa382759158bf468c5ac45ea47f7b812e1bf3faad32b9af2fe42fc5500855f80132ec73160924b87172acadc2f48c216b65bae250e570178b6755152e7dc6b034ae204ef2d1fad2b7322f360a59a784fee845d44f87febd2a222683b38021a9a0fa64a45006ac828e72dba57a9175a4df3116bd60ed22994a15d17f6dab121d8c15257ac33059e5227257a926a5491194581874dca9572970b94739434aa7363a243c0011e0dd4bfc72a17089b3a2a4cd78f77bc503917422b2ef168c931ab8b4ef65978240b740ca72ace5685690ac320e83bbed362e3348e5d698b5f3e740d8eea99bb136465f427214f272b4770fc06ef83ec48ddb86c24a7f4cbcdb8ab4d0f14f6dcdba3642ff7292e162fb8742547f2fb4c6398ba3a15cff12027a0c7c5aee0956acf0b69b847284c17959bddaafae1864946fd7d0a5b01a56a065872921c016c5f65c954a9e553c3e7cb0ec219861d04c2a88f60ea978f89ce524abc61572e63c583ba0688e72fe1660e9a4115121a2c82ab3647189eedfcef9e15d09f4d62f62b40dba583f2c0d4f2bdc28fbe2abdab565cde4b3c4b5624a5567d3d91b86a87e563f2c62e54218289db8981a4f5cdb43eacf58825e2988baabc774de1fe5d3319767890cd06ee6f436b997d80d52990f009be3c4de62ce185538076320294ee7969d7757e722da446cb86e63caf9ad8335d3007b2224aa7f036b461fc1facfd70bee16eced72f6bf32afb065531bfaa461d4b7499bcd268ba910447dadbc25a9098e9afde91be9d794d923dd17f1a904e2c6809a3d900724d6d7bf046e469d9efcb30225df1dad24c602d05661e10393c4a66cfa9680997072b4ab5e000a49e9eeee9bffad72dbbd30904589efa94aa0bc253dc6099ca42b7c3e3d0a8b36e46aed8c7b555b72e7685e281c8cde971975b01058326cb47851c12a901af8f6d3b1b1295b47c7728c053d7eda67e3fda086cbf156a7b0a2373a8ec8b880007cf2264e97e0bc0f04f8b4158c590b709be4ac4ccf604319160b15c6016f51535814cba111915c8272dd193216a6013276617a247b631b4ce41823eb2d5eba501f993369329067e42900446169ba493f7210f45eec637912f301eb0be6a94ef62b89022c2ca7d9d35b7bb8888dfb2a34e5dee8dac14da7b440712d27a8e16507075af1ab7a016fa272a88e75d583b6924eb5cff36435185f537cd7fcaece737c4461370b783f97ae221b54b0456ce6a70867b8d1684d5305ed28661050ae23a9124a080d54191c67728acf92cdbf151ed3177889fc350d9a9ad23bd0227c0a7ba038a1745d08a17372e45c744016e4decfd666c83e9ea1fe05b2876d9a3d5a2ae69dc89a79a236d77281e05bef7f7130fc9070657d354a79e2afdcc5f7b6c418efefc2bc5838d1050f33e8cd316994b722da27013d1bc037342f95bd811938f9b6ad98ae9d302cf1729b5ce21592c928a13b57d1d0f68f62c84d8501b44be235c6a5677065ebeb2b72860180865321847ddd83cadb814db46887c8f6149568d7dde042940bd32221727e45a6c02f152f3d1eea6ac00f06deb2ffe0cc100e23997011401b07240e466809768c71fdfb1842734bfabfdb3ca49d50666a36043be2115ebc747ea9c7662b96214efa671ef722e860c6ca3a65538a19f80e1d6ef7a1f777b177899c2d3072bb71fde45152b556ac7ce56be56b363803f2f05b0874a60fb7994c00c25acb720ebbc33a8d31d18b328a985a0f871e2e95b8089058869e7fb798a05d2495270bfd95bd58bd47edf3a0b40aec51e92aaa79fefc1c5c881893b0b8b76a29d73d275a0242f7351eb0167e551f72de31a51b7b65635d7e3f26928391a3a254c22e470cf866945dd8d6cc7103b0f3e65fba92e51d86d9c844fe6fe932f3e6a63a9e29491dbe1592d4330a787d7e2ae3d078537848abbadf7aa44e9ec7cb226ec9eb505e07b2ddac8e7ba8624fcc5a068753bbd4f599d78e47b4bee5c16a16613d2872ba072a15ef135c74b4e04ec68640109b321e5e7dc2966891a41c57699b85ec728d1e67638cb56bd5ffb7ee4c1ab96aa30067e0786806ae1dd1d32da1fc1b9172f14b0d08c4bfd27066834169fa3f0feef0285a2020a95c6b897d9c20c1af946ec5cbe2b50383ad0778f9ee91e6d5e1d1f334a0ee8f80bcde9c6e6c720fa739725361aa2de39b7ed30b29a522cae93d0345ffcb3d9ccbce8e153e3910a09d94671465fee238ed0f030f21051964b375a33bcbef8f1546bb3aa10f104ad215d772a6ba008d22f0582202a2ee6ea320a4b14420b83f1e8b9e0a51e8bc3d65e9fd72a57c83bc06147a81b8802a23c473739f89c95c5559426ffedfbd6e329841cf720460842bbc865c5d7b4f72fea11fe22cf30425ac212faddbbe27423c218b0b72a9f11dbad1eeb7f877c1e882300237f1156f657d662532b8e3de1039a439fa64220710e273f908c9409f79616d47157bbd8936bad76578fc00e0b34ede1fe972e8b68e05893307fcb1af13f99ad9d86ecfe6bb19540f4b1c87a6c629606be13a630789b43ccf8ec51e8080f84accf079cc437bd3dff653429f9ad5232d123906cec99ccddf26930551e21a32368a6c747aae279bef2b0d2c1a4c76cf12212a7267df7c7850e0626ba01c933c88219ceb3005012703166e34ff0ba286e62b0a7212fdfa5cc7cb6180bb522072eee2744a8f74aa216117502afda210d446c085725520e36fd6e52549eac0cd59058c696a684848e195366b9e57da9d1724bdec7235867a7723b67aca411e5935df1aa72c58d57f5d71eea5e1411d4b0dfd2786721f9a244c1b42568d93cb8085e65a15e09c44b276625bdac75bcf89653e782b5389f50a899200b249d6db0c0b9826dc7256c4704ea3fe3e8a1010250968f780720282ad3c30b2b3a8c9b61d430990fc72b7c86be5de93eac15814f1afff6bfe724e67045118d8fdd06a704b8e3cafef5b1ff2920d23c3de41dd17bd620ed45f72a1588a0af8fcbff0305a137d777d391eb2a50be1e0e19f3be3dde1b06acdb96a88f4377cb0a7338a602e724af4bbd5fb094049d43c083660181b4a6be6d9d12c2aa5f4ce24e5916e4da794793247f84afa72d85ede588ac1e4a8d8a4599d1672f5905a7c3a02170de47c44db39a5a046a7cdfb2be8a038dfc7fa314b5be7e572f41eef578e06f53ae4a0a9b51cff9ed0faaebca29db6987a6fb3b34a5d566272dfe570edcb2ea7d96c141aaf0d85fc44b16a9241512bfe6c26a37e4da8d5c672259dd6b77caaa2e33da93381295f9064c05017813371f7aeed6535645b0126724e7e22608d920d0135ff2babf2892ad30e7ed8d224f4ebbc063ff61dc61377726884b2797a65e72afcc7d58c4db981300dbec3042c907dbec4ce942938e8de374257dac84ef171b8297474c62fdcbba33d2f48cbf414d4b8dec97f8b9a78f0644fc7020f2ddd861e89fd57f32bf9063043486ac7889e1bdc2d38e0813c7dc65d5a0f61fc595e81d2d7422ee451e79c1724e0e13fc2b75b6776c265c24e4cba7231fcec59ab3c7e1058b46235fb47bee95070bcc2e9264989241fa911c75d7c721f0523f5674fb807f4276492c61a1d6dc5468d64a6d25607cb0a3af4d31b78721b9cbfe05f0c479660a573d616d2c3102caced2a710def2820f2adc3e60fbb723103304da1aa1750d1441fa52d3fa4911686552776f80115b8d003d323b175729d28e6ea14b0ad84b0834c774e6ee9ada68f043b935dab1ffa9f49336b3ef872fe906b31b29c78e396e05d9d268264613c1dcc6ce6390f9f62bf1a409e560b5bd6221022cb6fc95e58253e17ed49f01a46559e4d1304f26b6a3d3013cd3b3e72e85d9e818d0033ed2c5cc7af8e647a345fbba830ef8744d4c4b208686ce7d6722470b084a6aa3c69962410e97bf26ec6959c96f484e81a2a6cfdd72f5a8320729b70c01c63019980c706f3c49739f77ce4b3713cee4c3d465899e1fb47487f723b374157f6d71d5d46c60addf15614b2f210d8c4a20e2afec7f28f0b6f79c172e57db4777643fe5f1d850bd5e82398882b53cc2e38530a5d6390fc2372367c7262e17ffee83c7fa61cce8bc18bffeef59e0054d75a8b60385a03940ae0a3f0451e28fdae62627ec5ed3c49a54cdca945b700404b289ebf93f89e8319ad750472abb69e132f4998deac54d677ae7875e198594777b1339421f01ecb9807220b34948eca614e9f76611a1db613663007bc12311628f7f7bb308d17c3dd6d048e722036b2d1d8c1a12361bb6140ba5253b8982ffef3dde42054bbd2e2f2dc7d896e4b1a6e4194e6310c5f6ca08d2307171cd135f66f6c2a5a647da2bfb2b3b5a05ea25e9efe84ac472a16fec8688980ebaac46cfe9668429192106634b4b36fbf7286fc36e0c381ba18f8f90a3dfa77975205c5ff015a52786c3fd178dcb30467631a1be5a8d01a4781632de9c2113afd09bca754a93f30987b73ab5f93e43b58728a695af0af4a786a4b95ed67fc8a3f646e920c0c8245873262b68ce3550ac4727da9341d8156aefe629fb20dd0bbe1359cb5d5cd3f4f75ccdc3297ad824d5672a52eaf8cad54494ff1132c7ca392d54f333716e9f8d86fff170ed7f5f6609724322ae09cc7b2f49bbb2347ac0e577bf9695b5ca9dd5e7447a47fa27c39b74d64b19469bd642667400e2e91a5b640efc60c26e9bf8729d56a4317d94197bcc472c1ebcfe2e43422a31d7a7387ff43c10078062bae4e669e12b956a9af15bca272665fbc03bbc99b15b83f053878f899ff6e1e14e9e7d5c8cf9c7c0badeb7ab57296aaeb44297ec9240ba8ed494cfc87a8344b73b72ceec1a02898a81309836b0e3579767cc13546567b9d042c55bc48f656e4c3e69474383fe3d3f495dcbc7d5e156cfd4de4d589c1b92f03c37bd31bae2d0be9e7f10106fdaf48222d85d38422eb6a80500e0f753052f3d920d796fd94d61093adcae3cc892353002c6ed4bb728c74716d9ad9d5126f8db5ec3f2fb812cdf21f6dcbff8241532f735d84201872b4fab5446696e14ddb42c228ded8a97d0851697087587bdf460d55eb8c31b872059158d4ba07b774ab9bfdd2a315e77a5656cfcfcf5428f9ad3206464071c07234c6e5a040336d46b17379061d6a09493b2c9e3fb3af302bae3ab8aa97e45510f84c22db3a6ef252443baf9a219fa53d7857e1ec9340d550d2c074dd66d5007289f37cec3963220f2769b2ea5ce666f98c74eb36c30e2ef8f801e07bb5670e49ff81dddcafb6415ee3c53f5a99c41644cb9c358164d3b8c165dceeb1278f01722cfbc41299c79e0e580cac4b5e4c2fd1056cc67840add02ebd6a36a7cc261a72786954ff0ddd50d83cda522690de419f47c63e9d6c4a931453dc243c79fa435b396cfed31914c52521389910c6ae92f20a8776e7840d8031c15e8f3a2fddbc3b5698fb777bdf64d9885024a6cd9252f9bec6cf37ed424288c5af0ad8c137ac4ce00fd0cf9c071ddb3c1ba0e9d3611ea4d183d47e2a57216ce0c7b5e1093fb172d2cbb752afcc5cb7895f85b030a22330796f62e5acd3ee55af32c9b23846ad728a6c0a6e2d57ea159f7164849fc82a3ba210c884f632d4e53013f9a3a9c75b72b57da5c50a223f4f90f3b0cd6a67abce0542caf443965793cb76c80417f36c456d91555f8a9f6f9d7bb29c44712e2396a3e5832b5b3dd1aff02090696f5870728918bce286f3390178a5eab0244e4105dfceca8bafb23555e52c4033a7d34348e9d97f823da05ef75f24813948bd34ef0ca0ba3bff33b46a2910af875c9864725db61df13171a8043e672d46459d421a54f649110c1a7b13d5a0966dab7e8b2e0dcb28dfc67b9ba822868c570cc5ced97cec876c02ca93bd2f1dba5b48bc4c727d72e313698fd52d1ab9fcf592e264608dfded7be0ccb63b951149a221e93e7229b6b326f8df160eaa73f4d3bf24161aa7efd7a35c530750f0bfc8a4ac62b572f854610c83d08d18fea050930b68b16a1d1389dad3e0a7d03fc958db3b855657b3a1c228d65c00729096e777949a3d7d8ea56a3e0093fca74c15e75c32b397723b27d4c1135ce427a5524b643619e35c164d7b574271ca2408090f4656c70a1de04b122662af36fcd4fbf53a4e2ad45f4ea17575435a439c51a35493f0ef1b7214d7865f58695df3a8c00dc2bc6af3a1f9c15a5c736a92e722fa4a2738d9d5519ceb755f54b4a80f8271208bff4db43c3bd411ff405e2fff1e00db88ac3e2972064b42dc7f345c63264dc2d77d6b6fc8ee079f11b6bc0cfcb69bfe223ce317729025b37900d420237907e19185c4ce3a8cda583b007e86f5dedd4f48d76cf772238b980fd7aceb0b8cf9ce2a0c69b7f4e49da1e3faf98cbef8d0172be1e899433b797762f3323f2e49dd22c070f48786f5a66f5b0dc5aaf0e64368750ff11d3c5508be055fd6d1d6259668b27cd8d5d0025d7cf7d67ac16f3da12b3ffbaddf64e4c0edf9026dfbae1d0f28981f990bb49a1ad00334f979828b5f3bb04930d90254cb9d518017c19fa057ba3f6f87fbe7fe45b6a4af97eb5b324eb2e2a9f599486cffa221706dd2d7db3b19935a3a07195c6ce006515083b0055a571e188b815cfa8332f15212366652c14a2d64c09d09afd2cdd1eca1a2f6e8a37dda53ec267278ed2a551f2b714d4483bddea4f92fd42cf862cb3babc96f50880c2b7a095e72334ae89b9f19c6b22343cf12fec75a903328cea6be19255ab65b140fe2f2d7721d0fe3cbc36fb6e0168497fc8ff0e4230f167572a8e5d8be86cee323cf54880f827cc3f63b1e701e4e616289dabc3322a144a0817b2546a16dcf33ce9ac22e047e2c986882a90d1f5b656f5ca582ce2d59822168367da8cd88879be560f77b72e67fc74992f21ff40e05cdc3b33854aba27c055bc21782f403f243670a69476fa83550ddfd2c4ac90aa416ff87532da9e499da6e08dc0d2f0a6db97ffa92b7720c02321696e03fd464663cf97ff3522d9508beed659dc3327b90600e39a97e72ab901dada3cfdb14b31373329942feb56e159daf470cbe8bf3faad63cf786b7276a466fc33a79f2dc492983a8b00643a5bc0d5c297be6d1c134ebc37f9699872d5b3441f5b1cc693a01efff450ab0fa3e6471f5ed949e9db58a84eeb5ea96b0289e9663ba2ade853c5677d169fc86fb1250432918fa63b442d3a1b0b605248229c5337e176e2622a36e64fc7fbeda8cb85148a7fd123803cecc32171391cd9727f6c6cede4b71040f09a7e898459d3e6086be977c43e4909f728a2c53a6402721ee1dfadcf9ce897e234909d19520336a2fba7f5e48cef2d6effc4f9dda49f55473a3ead7c35087c42e8c7ced96c75b4dd98eb84e8b80046cb30652e792bf572c57cd534775e020e65a4231f7039e2a74dd2b26be0509efa379aab4115b973728c70a1e39a6ddb8a60b4eb5e6166cb40554e572a5c83b513124a32022cfd361eb7afd1caea0624e77edef3901184bf3db53713d0056b2c3b1d4caa712ffa2972d0e1aea1a9733f3cc872ce33ddaccf4428567dd87319a758eeac6b44be86702ef7e962fcf0d324fb6ba0c83399ed72ffc268bc68f555ca9ddb5be9ef3d66ee674e446444bc5757b9aab22e7a8969528f446f0929a0cb2423311e318a4a7afb48181a5cc7d4c97f51d02dc977b5ed3abc0ddd1a4fd99792ffb5680aa1b27fa672a458ceb0561a704ad82a8822780e98077d6ba1d456fc50736b1f833d76a89a10f80d83bc20a1907fffd1baaf8a4e91e3f9192c64bb0d2758119462a70e6eab501e4a56c20ca18decc1e5c51d9de4eff2f34aa098999f5e1cf621bcc4f7479b61e1da2e30c9802786db1033ca871c7de688cb72d81f8204ac5e5f0f0aed869265b45c4bd00abf1816dd8080d245ead5cf5370ef28c19464b6560f731501087572e0e69b7f10a7e229a61a7eefac8495f867b510cd3030622fce6335ce68e9e20c4afbec215312a427436bafa272d82601c3c14cac5ac301c88532b311fd2599728a3fa40af8085e2a09a93ca3d76b9b2d9ab0dc49f1acc6737c7442ab5992fa03d9f8ef54528a9a2683d63d2c4ac9d2582d4040ef324fe564655d1ebbfcb77a29d62edf00e50f5d3f989f710ceaa75fe0d4e54068a8aa5ebca7f3f7d6924a296c7625ca3befc2e8484a6b034ca005cf99b02c9b50fe0d66096d226735fce7be2bfbd7dce0d1cb85def23a735ae37e4e00c1eccd3f83a361d71f776efed60cf04443ff706f8cf067086c8f5a2b398836ee0318b9283156bc3a3b1b13a75c38390363aed4539e8e3e827105641447d58f9292689e5d890607cb54cb05e4c715ff724657a97beac66662eda41e41d65a398c6431bc6a64b37806597fe053d8986460c4bb912bf3583a3a503d45fb116c9c81161d4abbec3d3edb03c1814a0454c115a21dad2713b72d316f1782e9aa92f960a48c56b54a90cd090fd8718f73a1bc725fb18b32ed7663a2e04a8ba45616c1a835d0efc39f19f212d9f5356ab18d5872804c2836513d02ace60841e84d192df6884819b553aef98208c3601cd45cad7220794081514b081d0f6dbb051335615dbac761351e355c2e30e6856740388c72234117804a0af2574cb1a58794b2d117a8731fa9750358ea0dc6c453767c2672df9cde69d9b9d0073191d6ee01062aea4258bf53e0290d8e5e5ed707ab64bf71167af21e25376743681225f65099237cd1594a2aac23c1e7d738832e510172351dff3eafe174abceb55736c5fdac6d630836e846a48a681f39009fb89dce98287fa1c3d8b99663f26eef63066c18f1f3b10a042d105f6c3d9f297d730b6f7c286c8e443fd3cdf0a06601c5d6ec75172775d79c9a76cfa7fc12e43db04b5bd5721019bb40fa53fb99357db7e22189cbce4978c48961a5f0dd6d2b0606ea610735adbdd2a8d25e5eefdf84829ca43c6fc9e3453623e8a8542e58b204fe10af306363117e1c5fe3006267e1a149961a24596579b1e7294c9239aebd17fb9f717c6142e28219a2c082e41f0a878594ea28dbba483468f8052eea404a466adcbb3072b0262a1ce4c304f3e8af9e31b7f6abd021c3dfbbf63530a527158923f2208705867fc2c2def8f8ee5995f37ad7f1c7baa6a741d78ea8ae6bed4710bd5b1d0872476d662b67cafe6783f3a8d479cce3928558b98c484b93ffcdab3aa98f705c7244d390d201ac325ec6072408fc0c1d90012aa85205f8bdac325f524c8384e763923500a17f474054fba9228f45baabf633c9ff39eee8d786c720e9a3305fde72f5545cde721497a7449540c67ed861290128d032e90ebd3e7aadfbc636302769199732b0073fbaeb035347c97239f98ac48929e78b1634d9486eaf090145eb72c9909cbe79b0439c6f7b3795a1675c0ff1cba3fd98f09b8eae746435fc58e7688fc651e490d088c9a6bd725586f6b5faa49a3dfa3325bef324fdc0f407ce0f640fb46e47e3fa211f13826d72282d4a9fa8fef21f37be1ad604214e2634d05919b5a852879d59a6b825512057bfab441356b1d58683bc6d04cbff5bf9df1bba4e47311b1156d0df556e1822602f6c3722f6c42fa78e4003049e5dd434f55dd9725cfd3da27f4e02829e8ffb351aad1b786bee8a2bc5bca30a29b90b613f156a026471a38e2e886c76a41aa59b164c097ba01d2ce34a8955fc74695b603ff65b687423c8b7cb44866d33cf48b071b4b2a3917002fc2352f3554d71245149fd8365fb7f7defff63424b77e306b6def47623a257e66cddbaccc49daa244400b1ed72f2b4e4a58135ce193fb1707756719799ddce69b31acbcaf547a7f35eb6fca95fd90674dded03c525500135d86ab16186f3bbbd6bbc5048366216e9fc5a1398326295a8fe0dc2a1d622c4461450717039849211057c3e7f66cb7e46fe37d3431583a6be939763a526a09a3918c44fd1b86d00e45788bdbe61a12bbeb0f4aea62cc0a90bf6fdeb14e79d55c5cb5658f37d55e6c3f9606d96ef864d0c45f62ba739cef14cf565f70105f682a58e0b549829c9a9b4eb1d423b1d1eab3e3869b9f27293ac23815680709ccaf10c8e2c27e7d368b4875d1da1a91e4ce4d79095c1c672d0113830f5a0ec572d133ee89185610225423f26d0bbd5bed4bac9590af30172c0715e97faeba6bb95f008b18536643b0900837bd447e585de2839b4ff8f73722fce13fe52a20db2eac8ef038c8418dd7a419a6f2d6c5abdbe08b71d870ebe0259d19b6d35f999d6be862a4638d05373350c50c0fa5ec51655ea55fe04ccfd725f761ca7995a32afc936d4240201bddcf8e6cc0f265235391532c336bf1210112f1682ae43c4f97cbef6784fd8bb2bedc3b47b9c4e5530599ae2d705e3791272904896ddde3f97a525d1ae147efcf1c85ef6bc43640b2a56e19063b66ac6ef721c29a9a96213feeb9d627dd7ee188a5a9e3ed8e39d8aaa1df6304ea2b25aee72ca44a4d180ae325163a4a239b6c5a5eb55f80d3eed89463772fcc94adce3e972d6f1f5554f1de58af53b375e143d3d192f0db765d97fd3bb0098eb262c4a686f946c063d5ff16b177122cc8ae3f6d2d679c032e0e5455c686502239759d827725c3e819a718838cde4928a5a9f34780357128224830c4e5844bd68b840c0f12a141248ac64f4b2a903d788e2d1ad6211e71ee681b0109956c70d3a4e36994a72ab694ab9401214d1851aa9981dbf840747d87725beeb5301c8d725b5d1e3f5048255123ab96c4b480d1be0c1e0da2a71ecc9c009e13fa12c4cba6d470d04cc72768353b35a5257029353cb9ff24533bb8bb24655294db75f51e01e155fc3200937a8e19cfed87ed825f439504327abb8a57a04ad87202ba41ef27961af5507698f2c8c8f7d3b34a07bbe028a3fd73eed460c49b507ec20e2b51565d7146fe752ee21472f1d53d8ab5057b2453839478122246765428a379c993e626947343e726424deae7566cf862816fa3ccee9981b8540bdb1102bd957cde8f255e0f67064b4466ce6b8581a3e3856f5f6b084f4b6c0890336af64ca5dab6e3b695d2ffd06175a899fa8556f952cf46d2ee663a5c81bdae73d5824c14b45cbab3d55cb8872bd23242624e2dec08a1fbef6c27934cae56e2c49e73212c7d6c1d3aef4523372b33bd21a3d0e40c2c7ac1622dbb1249bd79ec2d4b7e84fe56d6c897bfe79cc237c8475f6a3cd34dd377c4935cf0a67d6a9770a4694d0de70a9e28e7107f6c372c5222867bb72d131426cf7885bc3f0b0cfd9f5d6516cb6fd28781836c93d680d4841a5192fcce4aa8ee46710ac4fd5bebf1fd4dd8db1f676c328f1d96d7c051e197c379029e9f226811997690c9196fd78c034a7e2687a000ed73f7134c75572130e87bb0e46c49296c9f8e28a8a8e21fb228c92e5a554fec0f091d2a7ca6d72fd0168d0fe37c7c60f4a8c24061b46e7abebb8b33d65fbb745ea2f1ca4db4e72210ff63385f0b20ff454c57acc9d33dd37f8af58a1ae6c0d5f5cc1c1945fec58e5c454ec6f979ebe2b2a7c0828f6ada454683ab61fa310be688090a04cc05d00fa8c01a41e351132c0a0093257a600c23ea76f61beb10eba08cb78fdd300512e8b65159564041da4c3200e27a9e56167281cf593f48723ca7b06e3f5d5bfb7720c9ac74a10125f05d15c5ee770ab99ffb3acd09acc7228e50315a8b6159c6772be544e15a9c0454f27047a1322ce7f25b3e3eaf062398693dc7b59c1a7c4e6100a1b7419c05fdec9b16c226283edc9aa6248672b2e0129309377a138b9d0b272865213cc09090dd7e85dce9af8184e922cba0924a04e1b1c3878aadec6ee86721a1fa54fb717fdc4d45876a47296f1faf93c20a81f9113818602192b2105cb13fc7c25a753f98a11e99ca760ca00e6d0299fc365d929e09cc23cf8a79a8da37216e2dd054492c6b980fd38a7029ff6ed1a6d83dc80755ce19fc1d0b53836af72adb79d1f8dfd204f6388d1c84ca8ed4cc78772d527e23d4a3b5557a6aea2f772305239ec43d6c00775107d2a5dd903136ca6b5b9a4c104f22066dfbc5e3987720447493f12056d5938561713539cfcae5771729ce7ae709aa3dd4146c2bd00726b5292c300ac21aabec828db3b2f4b326110525a84519b8ad522e5a1deb7b672a3390e508bf479800e04ec1911e06ba70154747e908eebdb6e18ffd6ed061e3a08d8f4c3a542332d4a54b3f2ade87b704ede1c3ad6d9b43b70410bc6fe4fa572946bbb581583fe694eb4dc65a9fa8dd0990b59990b850092f2d9237343ccf1727f683d16857282d5e52f549a92d236c91802173f40d9ac6a3e1348820ea4b572eb6aac5a326c2892b1637dbfed6bda0fd92d422e12814f5edd40200c2ea4c84535aeaa47ed34e89d9a1320ba215536d59a096a82128216125b4eb93a94dd0e1ce56e343ecfe7afd27281cb318b08050a4d572ea584babffa11e877984e6e6745acb76049426e1b0d876d4e7b17b5aa3dbd0bb5b30b194ad59c8f72d12e5910720c65d8cfd3a55aa48fef3c35135ba785fbe962187dddceeb2ce356d0c4de285ad279ecfcfce27057a86fbb6349eda3cee481122ac9fed355ab0ed4325e71d5456e0abeed154eb8cf928b0a43068039f602b6781792434214d468668364aa63344f32276a6d163f691e2dffcf4f0412a86028217c8d4d9ef5db73a038fa4a1b725de0763d85b5f234b2d863e4200d404d7f435df7b6abc816f9cf9a467dabd365c0cba737eb85aef1c84bd25c8b764c38d2c1123e56e96bc11b0c4baf9e5c940b20d18d83fc7c26b2cea797f627f952dd814d91745c3cf85e74390746210ed65e365f3fe7b38a9bdb894182ccdeb4c9225c5f10e0d323844b5b7cd0c1fc0f3572e57a271a6992794fa1d1f0d227dc32c6cec30989e82be1f0faf5409e1ab1d565a466611e0e2cf6465183042650da0672da97d203e250031ee3c6aa7042490e61facede58c96e759cd9fc8a03dc55195c68adb4a755cc2cc14967e1ec3d586721d81fb4692458a409e1ae2b24bfd50293b12c1902eadb1427d45952e793750f720f7b99e7cfb2ad2ba492a7b81971b1599f20a3da29fcb56363c5169271c1ab52288e381e9c606ee6e971286b185f9e5649040b7aa2295fed96dc431028ce4b2338923c058f6582e677d802d571f15b2b8027a691f9cfe5783c6236e8b45cc25c9eb9acbc7e4f87a929ecdf10997eed2f91b43c2e32ffeaf71f4b0297758026724d3d853eaba2261d47b27ce3b62967b34d52aed814ff9633048afbd3a2aa0b72193ffc53c9e89927262efc971c180117f7bc92e4893394e10c0d04d7c284b3728f40ad22bca6c784bf3e854f9248001176bb1d6b883234a3b81926e7e057a372b4cad3cd0876fc5683daf665dd590bb53bb992c4aebc1628db75858069da0772a668cf98e596dbc77eee91f45f50df0aad49fb76f7d1ebb7a04d878fd0b6fb72464eed187fd66e79f65460d4bad9bb20598a51049e3a18c007e34735a67c3e52dbb8bf080ef82f6bf0e22ab2bcb831c3e88fe995f89a60960a6f1683758eab2a5ab2727683824810cb9bcd0f2ad0f85c5bb0c069bfbd50faeb45cc5be6177014a4bc17920ce9249a7ef1703798593541ea71fd4a409b5b1d83716ecec2888e72e314b9f9172eb0a4a12ffc01d36682b60539713ad1656244573338474573e556b47e2b6fbf810af442869e14e5fdec4e97eb7ea382565a90b9a0f8f58348ee0f75d1761888419ab48c207536ba05b8210bb93283575f2a481939a973ab2f5972e06bbeaa9e32aa42d66a46aa172af879f32a95c32765cace4b188e39685871723c9552b7c8916ef2bee8c17bd4882cd1e1bdaf62a78202210bd9966cbc350842301af9eef0d8a4d5d7cae7d07c0eeaf913242dd5f820a20ebcd78126d451511ac1f53c7c2fe362e754f00a0cb3f81371a75c2310d7d2314dea4384cffdbef34a8d7559d21f42845a86f99d75647e344109a7399eb5ef477dddf4a69b27be24239e529df22a6ac6d66d5a9a213425d115c31c156c2ffa05638a684ebd3609a072fe98601ad3f4432274e331342fda4fe32293b1358110f636d8ddffd0597685199577d30bc84806c9d62b061ca780101f10a47543f3cae8997f25ceafbfaba805161b4a14d47103bddc7c213672941f6f9ddfb423f07881076d1526b4aef45372021f8932c9d7d1031d1258259618b5e1d41cc7bd2179ee09ac5325738ebbdc728e32369f8affcb508ab3afb34260d359cc3e0213e64b90e3c49cb8c2a04c4b67cb3dbfae3887c4fbdae6689bf7d44d2d3285f5b86a0e9d7e840d6f08b819ba72685a7c78e58575de6bafd53f457b33490ebb1c6a2cc361f995e71b677a5ef6613171b2e7480b22f31a9b2d46b88e9bb078356fca9e39449c34f313ffee8d65729fb766d3525c983decdda24e5a8fa772bdef1ace534cece83594a6c41595f205fa431ef2ebf9a6aee70dfe3634fae04f7e8c4eb772e23c05c5b297bb096871549644e2ff599a285c3a4962cd061153aa128bd7ca04d5a1de4fc9980ae454f309f532450e5791c1542f1215f98dd647585ec99c4f3d2b45d195661dc07f0418723fd529852147e853438e11d057a2b8bdd6c2778bf5e483e5d8c2c33af2b4be6944d92c33d3fe1235aee7efc6c6f4b3970827091746a543c16c5a2bb29ee8ea72573019814993d9e2185ff4a620724a92aa170c647c7d669d2cf1961832b41f40ac1bfd4c16fb34cf7ea26ed093115ec4a8b52fc6536f30d48cca9cb882ba283233f7c1b56be248e249348363a5469abc0d6e4c44cd8fb80afc77a055b3667566f61468341ce3536025bfaea0256631439d7436e3d0958868075c16dd4d26f101fb4c7b7f588727cf13abc81a8eea15ecf5fe8ded170d055683107e06864f897284a82d5a808c4ba00b4efa656100e23e21e9011e32a7a076365a01c7b280240325dcc1a5a47f358d86664c7a79ddf47e04b2663ec6d9bf5caa3c24b9e09acb530ca6316af3f533923fd186703175cf1025ae72ed5de1348eabb315b6e1c4e30a9def78e3bcb99abd8ccd6b40abcd14eacf11418b20e1b78a62c8ea55c79a53026fa6b45d46d1c6854ad2ca775a9908b38bb9521fd2c720c986da192db903e072ecd7ac3d50586a3635e05286924108d24c2850f468317776a4ba70f326580472aabcef9d7e816b0cc96601d8f6b8e2aa75f66dff9c36015290086147e19b3f378debf7d85b7274af1edc13ad079100d330e2a52ed6afcb601554731a9bcac65ceaf3aeb368ca7217d144e395228e034898a8941d703472a5288bf3d127a748720aa7d2d26f62199b069b0bf266026e68830f045ba5642bfbc2ee6dab9008d9728ba14d0aede1da60475c6f21a492b99a4b2cc51c68f9e39e28db3cbbdd337e6f464690842c35d9f2025df4be2a52e561c8fcc326891768a011b347f526e6c972df6029d34fba19879cbf6066785439c6f34357a1aed5698d406565eca5f9bb5fe662e6c9dde85d2753375ca23f5b7a6c105849d63e0fc4bb1e658bb6b104b17284c0598649c32c683dabe1ca5db73613f4e5f99488acdf01bcb860893933b469fdff144cd02114b79ccaeca7796380724ff71f39eaeba91608a71942e3d94e7283ca82c36895a62b23eed360b7f6f5b352e7b59c3d0a06c730ccc0dd317e0603148f8b9580e81e7f78a433fd7778ff822a024ffca76eba0cd88d351b25e5af2e3b68562c4a9830e3358007e2ee52ad9497650af9106a898efdff7aed92b6f335407559b0f60d5eadfdf3a06216c91eebfbd5397a05ec41c2044ca1fcba32d272ca22275c76cb525bbd96ebda6bfc5df394607a7cdd331322cbabf5f600985a5c1a01be0a85ab40c11230a0161fa4292e79f7d61f73feb61d4c33489df7af5472f8d2ffa460525bda99e0612a3d1254d0660e57bbd6798f46a24223d67b2d292733488a5b35f09788d85a95a54919800350ca003b40a5960d82240658c06dfb725ac72a992dcd2e803120ecc39638ee2dca7492dfe0234195c11e96d55a3dde03040844844d7b367b240047cfaa7c1eb39b15ebab2c52070531ed877ee8810b3135bf3a187b82e2a1851e3d091dcc856e0939780322846f8ab432f67cd4be447063a50803e0891fc76cf9cd7f1a386352e4b32e524e75ac3fcdf79adb342b1455d4d80d160b630f7d0fe50cde3b7d549ac66922762c145d62759ee3264ce33f7288eafecf1d72a11bab16fcebd3a3cd7609a9be803c0ae9fc50ebe4665801ff67b12dc9d1b433dbfc54d1e82405f8186dde5262ae208af11558abd3f0862aad292e1a75e4b648c0027d66d797c19c57b71e14b327a6c506df94fb536a2f9d2b5ed6a39e984853d5648406f068bb5ff28616d85ceb03a8a157a2ad1cfe874876726545bd1e2d32a086b303eaa3492d04c0bae1be09acfa7b53b17df27ee172df4f74c606f6a08eec967bea4e91a1653c18ca8b12f497fd376c7a5f45b07e5f7172257001bd08acc2edcca50614176cd28be3929e2b9aeb3bd0378a4cee6445c60a0cd9a454b514d1f061d3e6180ded3ca3cbadb67fa55780fc62ae4c7930a754722add5378dba7e17cf058c602d7dc8a703321bc102e8b014ad04c3514ebf1cc2cba20e5d9da5f12dfc6a068728e7485a4d32becd25df69c4344f48607a3037a72f27f749e046e70557413362b55cd198e75b6609565cba4fd86b8817aef59e12745eae228c76edb8904b1e3e24e18d3ad8c7f454a36a00da7eaa032504ba2706e2f8bf04386c14f2aa2bd62b627596a8619f4c86775c0324901658332cfa5173aae23b9341ae952e6def84c3727ebbf3b3de28ac36d0fa560abf70a74919cd8351eebbca3d116e243fd25966e9a49e6f56f42f093ee62ef44d634fe2e1278c97265ee4085a749a5aa4a097b8f48fa464c5b13ff6e24768e18032943ba78338b7241380f40fc85397a8d9c36443e67a76b02c94daedc7e05c7e76f640141e2e9722fe1a28f251ea9ddb8a3181f91a376f63a21487fb002a90abf68423e3ea5622fe70a74d77db1e53d2fc530579b8367d9b4597e717d065f5e001719b31503e67203ab179f78aebc407d6642ec74c9e4513960b19a67c0eb5d620d1a7ab815dd72dcd365d589f92f63f24b8d96bbe04a5d1e972870e4887111475e57af46ce0472f8edebde8e0723763225b297ac88e7baf54de1f290d6737d3ea8e416a1ee82722ceee7f275f3916139677bed6bb212f12b8beeb9e7877a4bc3fc349c0c556947f719bc93c85915a94aa78555a34a61b0b492cdca4348c60c243484ec943958726d7f1dc51cc27e544a1dd1c1bea0ba7ccf7d5eed3c5e9bf8c4f657f956e99909d9e9f3c8a6d1ef6d7d36230422fb7cc7fbe8789bbbd22c5c69fd5a273990f672c01239d677dab6ad618768c2ab5e06dfa67ee411f4d49be53b4f972df42999389f2f315eb91bb81cb2cb4776d3beeeaa2316f2873058704cb3c1c4e0eaf8b5361edb622049788bab80aef4d55733636d8599f02e293caa6ba4ea2309c84106487009174256abed6b03ea197eec6641776fc115df30274b444f52568bcab205726d6191ef391a015f691ddc2bd383f9a3b3d7c50aa5eaf5c5f74b863c7b615a4d9c33656447e29ea1bc1e50591a72b9295d78376c78408e3e8e29135be06c84726cef6e8ffc4825020bfb01c97889e0dd47c766ae91251509f95befc396ad534d2bfebef7dc6139cb48a217a04110a9aeda451d8c5ec9f3d842fdb9d66bf28d4c47c6ece8d5f511546579a2dd467a479e4d8f7af655fe126f31f1176117bd2d2ae89a995c34e707acec243857aaecbcc328e665da07e9687cea84fbe88d745f7294d0981038f078f3a2a086c0d5152cf6164787d12d4ed9d0e7cde8c8d302ae5a921c1c8adf4384a75c585a6a8fc5a3e9326513049567bfc791240130cb97df6201dfa5d379341b1b4f12e233539314a06de821cb553c1ebc512a3ef9c1843a7267418049e4537caed8ccbbba4fbae1a09cf6870ffe5fd2bc061bd910f84da772af204b674145a3ec66300fbf5237f8c11c26666473cc2c4378187becbf151572489a1b7165dd5f282f7b3e76ab5bb188565e90c2a238e023883fc2fee36c48729c767b5ed26cd04f370598e245d607846277dbc90252c6ce0ae58aee7f11a819ee73802308919265b2c1f33205a1b9f8ca7a0bd7c6a65b4c96f7e26046ed9d721172a776a900cc7745d061dd0e25998a196c180cfb35e00ab59da8ed4278e5717b1cb60c77630b637f327d1bd5feb203e585624cf4c425a405610e3ec44f4110878251b802b3a7245b7053b81c031e98ff80fadcabd2f8d1f689a947a9eb1b0a3facdf4a0d665d8369dd46d913fa61f0b82afc53d25b426ebbd6658fd626dd1f19e2972b00e1605093722f78f280e72a2c96fa2f29fb3cf54bb42597f177ad72eddca25fd9ad6fb0fbda4df0c94dc04f6e1684f95db6069af109c7ac290e03726bca0d0dcd94ebbec67ed0914964c588925039e042135947cd93cdfd782d8b7206f0e4dec0fdb8560b3f4111b1da9ac96f92e46291a9620795925851e3985e7267ea7d382812d81b16f45e1fdfb443fd5e218a7705b9aca31a390216a3f5064621f94d92ec4ba78836d1622077d6c03bad555cc41e1ec2b6b7327126c15df2723d9673b053c549c358eaea996155b1784a5690305e09a6cf4589696a785cef720579120f9a20ec79a0ae326baa73e91a83be66ed8e8798c80112e7904ac78b72ad6b4bd987c0f5251bfddc288b529f77d499566975df0570292bf38063419072d31c586ce437c5fa4700d36d7765d1b13b0fe1b591e3550b6098385f0239be479892016dbb8153b8cf42386a0a10bdd53989ab425fa43fb893b5ee104973c172d06f26fdbd1710ae5a7ebe8cfc9682d25d32661001ffad05e203a8a59079f072429bdd0d913abd4b56b0281dc71de75f780604dd496f7eeba63c8ab0ace8a70053039f350e460b17bfe9bbd257752303a514ba01c34120048040b0a41a40547250ede9f10883664c140e191de9e3bdaa7fbe65dffa9ed4d15d707ac4ff27d843cc7307e1c46997671a6092c6a5c3a79ed694b35c3e34724ab7e37b6aecebcc29885142ab7a74490744989075d3f814015d459467034614f9b6ab6b76d00000411f4266f83aefe452bb2736feed01ad5d8450439496669ed07316797132cd3f4ee960324b57cb9085e5d34035a0b6fd61e1187e6febd7cfd5b66217daf441cc3582ad459e9bf58a55ee2ca89c7d3b97cd6f5951ddac219ef4e49485324ace7b72c0c455584ffe9a95d6e01427cb0dc85135fde2021c76b6f13e95df44ed8fe253799802ba0bd0bcd8fe29f76167d92ab9fd344698049b2070281d63fc7fcb2f727a408f0977ac354c433077e7648fd911258bb1ea56f54c8eafb6e9269ba3c842e674ccf5b6c853bf0cbf6f11a7081ab09f69f5bbb17cbf56340be73904c065723c19c0cbb1f00d2a7adc0e75ca6b0ef9af759ad6a94cc891d89ea015e45d3a6cd33ceef2db01ae32aa91d9bb8aa732aeed5db55e7a70821b4b7fed08d81b807266f787e81f7164fd631759d68cbf1cf2e251c3a8b5de334281fad14618406f00e1d098795f04f3f247a7048e8a3ad5d29fc5ad40ae2062e1107fcfc320cf2a1afa3667a2c0022aa38a38e3c4e9ee88703a8b86a510ae60e8077ac45c1236a77231ad3b9ca37f3200c26587ead18c94209edbae8f1707e57995165a9ce65e2072ece7bfcb872ac4cf8ce47374dcdcfd2207f1c17514e43cba82aefc8bc674b3568bd917535e40741f5f3329056f94fac36e9191d4cbc81842ec02a0fd25b5694e4d22c8894f09d2781069bede6f52610337ed88acda1613d8b2326dddf71d0c726defb3fd6e6e931809cde9b05c039f80bb5db286ae26a62637c590f771147e72e282beb70bd650d600f41072eb3efa30fb17c6c5ba9c95b74d4ae91d744cbd72c826f75ad008f0dfb5ef0412a9e1319db3a97867ece33ee62e87cd39d774d333d0ab22956136394916a1bc60430880771f6c3234f570bcd7dd945c68a56203122d77925f96daffe478f4e3bdca37e0e9db29ec14aa0f81b839ad84d2223ece07f01c4b49ab7b47a9ef2c835a38804ced294b68d09a2106a607e08ac914b68e7228ffaf2aeef509545343fc09c0e3f376e077a5b3daff58f205181c7c8533087281e09b3d91c71e2d2420379fe7e400b303e3b7ed79e10104f9dfe377bcf976726f16fa217e2031566c7b9394635a13a5cf7c3a27370f7bd15a3f14b9b52b1a72a0a63a986b11b0f1b8c78ef11e593fc292f6bc95cbfd40af728d8044ae08f666832fffa42b7372c7d17183611d567363ecd1d6def2786bd2b0ca5f15d132fd2b8bf5d2074c9062f31417a44358ade654c44c4fe85b4a08a568bab40c06467c4f5970e9ebadf4298864b9a2376e8783788155388095fb3fbc777cf39601c49c72804f3342143aeb10eb18a757c0103e14e6b55e7913a12b9ea9fd6c157cfa20722140c0aedf84ed2332024faed653a31e52f0f68c16742ea0ab48298991eb5c726a88e66dc26ed537a9343fb3cf4b2667b78424fbb4d25cc183f59081846c3e72f54a60a7f2765830f198396cd5d3468b7e9743c614a426a7b089955df5f43872a7ed796446d9d5bc4ac0b540e9370d1b046be64f823670458bbd3beefa4d3f64d6413cbe9fbc8b2d2ff4c41cec263afba15f489d82f36eee430b42e8ad56e5727e4133a123079b9d013dfb9c4d74fec26a42781d1c55bdca8544d90cdc018f499e358d4463d4b9afa0d2e9165542f7cc4b81e2c1f977391bb91cdec3ded179155bccc777861c456fb200674df6d5d1d73782a9815e8331a19dcfab0ee0a0ee72660724ff7173b62014052741039f889b62b5a2e245c904d133c2375c8d0f0651d51103ad09f517db932cee0017ce39c9698e3d5bc2702608df02acdfa9cad6722f2d429efbf88b86954c35e3c22cbc2ffe718c956852f12ab070e82aa5b2c672c92d6b2ede9d01523a9b8e223865c3dbb033c3209ea4168eeac34b56d90fdd72fd6719901cdf50f56b3e6d4d3b5ceaa82232e4129cf9ee51b349325a34542072e70eb860279763377bffef66ecf20609203b092a33d3e1a4bf35049b00559a1d2645ccea0f706b448f521ce3114f31d838845398b7d5c94dc4f4527033e18c72bc3e9ed1dc51c61c24c4248cc08e24e896b1a4cf47857d6fed5f44ac2ff040726e4950c7d00205ac1d9db3401596ab6cfdcb75c69269e0d1fbbd7be6741ecd6fa8f5b1f6e2c4d4b2aea57314ab97ff8157d4633a4d4eff93f18c0e52227b7353b19f39f98b13a692f6309b6a5657ae34eef42952b1cfff2a6a93c562dbcead12c9e0a8d3ab8b603268320f4b60cb207f785f6b4cb08ba4aa37a8e2f9151bb072f916d647087c26cd2f92daa5092ad3cf9d7cce62046f88c2b628a2ac809882726f5e006892c88276e381ccc48e9be060188b76fdbfb54e61c96b93335285c572fbcec880d5c2c3a2718f3e124907f77576814d4f7117336d40e52a96df7f7d72b5391cbae0eeb33d724217b221ad7c1e1ebcb6ac3f32c1893154607aff06b3726fba69aca11c39ad384526bd980165eef3e3b414c251d10f45997fd952bf0272da1dfa81c32e4cf1cdaaeddce143513f6140ccd152f37d05f872c31a7a3fae60ecd88a76866651fe50a795d6efb8ab8dccd629208112cc3168e79135c74301721d1f18f54281dbadcb3e3f34faf00b821a5ab517ce0bef67d83435d92a7f6537f0a9030a2b77ed45a1beb7b96238f130c95ac5e14637a26bab584fd875b40c68df485b165e597bacf4fbaa151a8f4af5d4db4e61eeb07c0764092d5b8110d3722516969a5d832f83518ce8445f863c75bd70e8120eac23e17f401a011492cc72765f061adfee0d43308f2d86bbf8a6f66d787d49bd2cab951b7f5254707c0007de8731db4074e9de30ecd24c2ec31a64265838b96e27db416cdeb620d3ff78273281ad7ed3878c1bd2523012c551c483e77205ed08d08abbf11b181e91a7d329ae7d5f4fe8ad3932fe33c9a696b08fabe733bfa30ad790decd45a05685fec972dcb6bc4c67b894e497ea6ee08bd7f489bc01ef776e8146fac608569eaee845204f17781d1438d76aefa1270f38ebcdd24354d73f10b3a4eab079ea850ec92572dddac5c4067699b8c11c6d948f4c5175427b3c719268509f800d7dca30474a72d82b6a720544f14c70f61a81e3208d0e4fb64c334602fceaea5441f679c74272775e4cffc9f7c7deafe1babb4bbea9b58d24f14b997b1c39d91953dac56c9e651f8b17235c8175f9414c75c705c509fa91894c569d7f9f75f123fa4597c9fb724fcdf2c3e30e3c5a95db7db1e41f60b46ef3155e15ec1707be0ac07fbc280f7235fec9b0a1dd38644c88a832de55782b35833494085cbe25fc5e5efffdcb1152e92cdc6362d820ab319dc1c935a1c059522dfb46d7109b61cf8b4a08950ed372af340fcf9d0244b3e840d06e69f7199f885f11ee415caac72d982ab4b118a87223f06df47d28f96e171cff8ab4388d92e344cce72596ea2e11e778e736b05a5a4dfba7426ef550c06eeb620031baa2175f8e3512d0cfce8ae71f397e4793d27291e282fdd75285ddf64cae0bc396ab4ffe95f712e23a9e24f275ff1ffc9a5072c06df291c04bdf81647c1123c0e1046673b89718d9a6a3f5e3d07ff7e7a319723244689c4febbbb4ccb4f6dcd04e2603734117d52aadb1bc9770467ad67b92722c1d31a97384f6da18bed20cd55b52293f09914c70d2fde5620549b32b72d94fef040209aa9880e4d4b9d31f31f0e2ed5c96623527c3548e73a37b65a2432972a70b2688ca21c0f96fcd166cf670041497c22b623e012236a70dd8db1f7e210c66d0c1e1bb0a955920256dd2b2066dd0b216e544bbc02bdc7b0a8c33796d9572e3c5e0e41d477d40d7ed293631afc865b46434e6e8b29e419cd5811c14de5172f4a82dbd8f5eb3446a05aed9ca93be8e9c5ec7d1a640a484ff60494b4aa4af7223ec6c64a464b6191c46fa6a43c636188dd84e42c0eb0857962e03fa1a018d72398b589aaf7285f1d7fae7a022b5f4eb2665d921f5a83c305ca9000b17fe9f3cb50cef2d1578447078e3eb1498151c5b3ba2068b312a1974690e47656ff57572e22808569d5dd589941ffd3b57f7a338309cd3964a02b1025284e3d2417024723733ce1ce9f875af0bff6d62396970c24d0af344796a5a9083ba665953a0961eba0dc2069bb83d362e6c70496bfc1b8079ef66f87cc043d9ee268bbcf8f0cb72ec02dcdefaac47d3d5c1eb8c62dc4118b56c4e9aac2d106778015cf76c2d9872f70fc6b2bf7aeb9f4e59a5cf5b35af2769081bcba841cbe35279f41c1a086b1657a79f5c20ac8563554faa2e1a56d1b827ae23561d4e50fc897d90060867ef60e9b71a8063e55c69ac93033cd5f5084ab710c5d42f5336edc586abcd6c326a6ebe85185bedea1dd9d56061901a33d2db237fe2df15c2d9e2ae0e608f32bab972d66c1e0448ad0314d7f05ddfab50c51008b6ad63c4e02e0021dfe2bb9324b3727b5c9380830ec3535a002ba23a4eeb25644f9ca7b18a3b9d390893e11ea89b5bb6e88a35075dc3830eb54f62bd93086c795aa3a76259c033ab5023e5dbd2cd225f744c03399f692b3cd940e5910ff71c75a9a22293469c9b835e328311f7ff72ad145df2b2de9a430100c480fb810444d971f9f2208860f5320b8a034a628b0f1374d1a0430228ac82006459bb2d55e077e46ce91e321b8c4332474de759672a414aa93bdf01ed662b4c613650ef0effb1124497b6ae4c0937f385c8e9fea1729277b13a2c6f55be3fcf6be4954841fe22f731a13ab238f4d0e9f97b6c2781232c9e73d2699ca7a3dbc784752728b23ab2d77ae2e551b9c7a98095203e5af67253c3f86a309a8c0553b0eff91ece59dd1ccda4bec85fedb017778f9b31b07e729ca89814ec404731359fe13983cd690d48eb249af30a9b93965a1d92ee386b7201670413d32da825f023a2291ba0d574755ad0d4325e048dbb0e116a4d1712727de08b772e1ed9e6afb82c27d251b6fd5fbbc804badf3e25fe7f5f9de38502725ee67a8da05af6e93702f91144612ebb7ce867dd5948a98120f0b16e2d155b6388bd2d117e7f890cf74a3e3f87579df4cd400ea7d08b327d03e3604d89e8057286ebc5be1261f5fb44966edeb81dd5fdda4f4437b8465a18716ae1170a4bf6370dc871f5af00acd4986c9b9463f6c866556d9b78b88afa532b695dd7ccf8d972bd778fc37e89a7892b29d1d6b4281c671851355d0180622229b8582c9d16ec7212e4e0606cb99ccf3e2bd06c9cee2346dd4806a662c9be5b2bb799dac0705b729942db744ffbf24338721ca8c34452a60066e598ee41d42c436a50b262f38361b7e02f7975c3829362e4afcce21616b1bf00b4c19d9d766abd01a55a9cfd27722dd9868248f6f8091c61bae082067673282c5489cccb60fea1efd674c4533572e9f689293cb65350f9c53cb2d4ad7ead5b8320a48994701063c9ed019807e93ee3abd94462e37f30f3e9f59d6fe00d931e8a705c1f7fdcdae1596b05357e3e72941ebb63a3a61306c8bf8386e32f7dac3c6061c3ed8a273580eccdd4e1007972b485f0428008c60420af59ae3735f31b3556a762c21eb0dcc0142a6272a4817246724604b36c435ac216c937b1e6fd80b64271da71a29fa9b1868f7baeb71a729ef76c8e1e836710f6c49187172bceed95c5e7917f43ebec1d42e72b9e68eb038cdf9ea74225a97581704fdd31c67eb145af0669c6cf52f620945b531d9347004577512da3c71c38fc45a76de6f3faa25db0d362b2978bf6e55765d1453b44726a651997a678e26cd9a0f63aabd89b99fdca4528db09928b0e65d659b3101b226e22b5d7c9c5d0878d50614746c83ccb01705d10879a5b52577139f69f4f707225c1be0aa1170184ebdfdd9acbd8f8eb1e384f30244777e331fb34f442cda90f4cbca57c5ab44b95852763d536df55c1372df5de731fceef677415f35dfe9472bad4e24af5887d1fab90ad3d291c1d95696fc54a803cfd3678295de56781674b808d1ab0d63cbcf5843b211f0cb3101e10e4f269f698f1c92016c05090a171398435dea69e299b7b0b9b2ef12121ed80611fc663e88cf6b84616af2ec6fd8e72510ac1fb4c388c16e54cba1359a8a121aadfa97c417064bfce8db0584f7e9472ad5c7c5ef82c6ff0758784680732710b4f26dc39167f3e769b1c3c46a2f0303723001ac2c5e7f43019c6a1975e0d5bdef5d37b0ee4fcf33496f56fad3d3cff498deb875a56f24f0dd0eac06a486e5ad23e6592b1c5f7e3f6b6cc4fa49b367d00c3c70fc9f9eca45d0079713132c6ecc73fb062e141b288b088aa7f46224738112e1e8a8b32eda9769c64226cc1c7684173f75e44cc5c9b5c5d27005725e71931ed11e2bbc81ae43fd4ebd840a8d7e97dabdf9153c432c47212fba2fc20a02672c34a8a76e8e8af996fc2577c37c7750548aca95424225af2847f72ec2229665b099d4d9a9fc77460c93476e8d644de0c1512330de58b7c72a37f9305765163727eed021fff9a971ca4267b36e8ff42616b16d0376ae741c016fa894ffb11aa72e8d87d93b1cbdd2e7e990c405d80326c95f7225d43f6368a0950fcc50da6b772107d76da4d2af8682054b03d637d7873b58166e3c31657a691b0ce8c8d5f7972a15a729d7f7de3b0fb81474d921ed0c1bf54e38d92839d26884ea7cd13f4426e5b8ce771c62a7fe52f9abc44d6e8a68c1712ea6992d2dc717f67b640a6f66472317679a8aea0f2128ddd3399abde5048c6df69a60111feb6a9a8b5fc79be4772c691cedca1fc90dcb489f6dff6e59679cef615759684802050f88aec2cc8be0af0703f96499285165dfec441afd0a777fab10d7022d63f5900e591690ed46006b46a0eff6baaf6235b9bec0f550d4a364ef19e68c638914659b5f183fe2b76725f6b59982a23e7c3009210cd988678b8b4d38ae0f98d29ffda31ef94811616220f27f5f5871cc5dd89eb0397d052b5042b2169a6284bf85998d3b2d013033472275c55c2086382555bb87f7df4c8cc6c3a5b065d777aeaa61cb8649571b40548fe20d6d69427f4fba828d66f801c3a86049f5913448e83d5116044dc115bf972ac15718dd6f347ce3180297a705f101979d8773cfe2aa3d8d0b6166025346472ef19099afba90a40b3dfe8f0c6009cfb8a28c31bc0081f90a82c2814a66150727cbdfa1f8a18dacdc953f4484496135ddeb06dabf3e55fbea431924c6fabe84e60c887525eebb4cc0555a78ecc5d677b762736aeb1658cc05e3c6e98497ced7217ea95c72fc3b56c323b02a8dd59d89ac0f5b356976b287b90dfda5173b69772ebd5cde6a92a7b86200c9c0583e963291c21b7bad3a551e557090c6c356734721468a0dac3289625afbe441227c4a8833a8fdcbf7b7b839b25d2ec6657d840726bc020cd14f4346c0b368b6bfb00f9f8ccbc44d9bfea5a5c6c28e058b4c0dc22da54023574c9cdf1135512135e3ae954aa9ab74c959ba35b12571d5cb8b5a772084ba0fcffa0a20589ef965c1ecca8fc98f720633b689c43914ff0117ea6d5136eed5f86d47754a21a965dd63f8c4874ef22ccd7aac7f7b4d4f072c20fa02972b358c1c8de14cd5d53ae473de3f809396019ae1c73a2ec55a3b6038b6e056872184160f7e1c2312ed8890d15488565f030436356056685f7c32255df72ef9705ef6ae2bb87e3163e8547d98fbcdafa95d95babb2d28935c6489592d499901233d01a308743ba0c1d3d8e060bc4ea6478a479488d5ff5e76ba34cb0496119a025a13d6dd23cbd25385ae172506ddd4d2822cf989bc1bc06ab403cfbd539340a72acb8ed542f2ceb3eebda0dd26f0c91d66b50080b5452adfe0e83610edaa64772f9d0d094e36440cee159ea79f5b66d9a66ff644b6d0dfd3da094958e505868720ba0a56771cc7f722dcb492371f839dd0e15b836dabe5d82574e1729f54342619dad23a748a3d96adbb7f1c29d34b84f4c4e6a77c84cb377877d4db64c982a722bec2bd2891226ebd9553b132446536ee07b01082c800c2c82c5c0c0f941394a6c74fd2f59a2b2614ce171d114ccfcb5902bee689bfdcf577db0486ff166e44c872743ad2fc60b07e92401ec37b9e84fa8a8863c5657037bdd9fcbd434c0547205b6b084e5547e044b50b34e3e16596e541da6f7f93454ad7ea148d672bdd17260ff39ed87675b6bd49062a364cb19c54bbd6e25666ec3f4e4dc4b0c715e6a4c80df2fe3820d770e769e6142161360811bb31256731dea48620216f805cfd34dcefe2dc8b5a452c8e55d7162966a55325e549f007a42bfb5ecbf4f8cbe3ae772d7a451a1c4b8e418d38750a65be410926ad79650b6d407ae7de91906f295e2251e390dbf47365e274e73958c9e285725467d9c029fe75afe498a6e98b1114e03bca63deed8e7ce1105071d069d5f6732175a8c5393f914ba588ab8800123b77207f70179b6293a8c6c1b057f9029dc3dfca0a38982ad381b2576eed5e041f372a8771850a5660c6f5e99c7e08479095181c73c56cfad162221a6d0cc65f9c814d7e9b79c309c711117d177ebe06b46a7cf754422c2b90c28ef805989d8ab0072012d234709def2f300c2c4a8058a4649389cd27f87150f29179976a600d3790f5e78493683c3e3b385019f429defbfa3a4669759fe00846a70ddb81ef25a391add13a9a57ae3dd2fc9a096ee400b28c1936e9f56580547a7834904488e1a6472dcddeb31cb9489200be69b6ba61db1d009efd6441204af32d44d353b537c2c72414f7989146e3331c4af35b796bb9d7a0a84e5b362295898177967dd368655635202f2182320bca2363f23ea5307dcdcd2c8147b30ea547333d0ee8c65cfd77267603c488f9ca082af3675e1812ca47e86f188c02aaf7a4f69f436b64a63690e60169314fba34da558b73679d47dc930ed26447910a455798e04c05942837d72fbb5e91edca6da35543514c43abb348d1041b342f2cc877044ffc4f78f8498722bd790a02ba37f3b9ce448f9095f0dc2579446ef6164e5671b620ecac95f581f27e4faf5d6974dfd44958c16ff67ab82bf3549d1798edd93b6040a230e319c7233590e17c16297990f622340e0ad9fcc0ee744bd72b4ac5d5573219a5c8ec97227d97a79a59dde23b2efb397c313ddf53ab0fbec04a51bb7d51d69523f09605ec0fc1117b4451baa3f0a9b9bbaf235aec58577cccee25c570d5be9674f0c84725bf2b35639c860c82d1009d1803e70959c6df30d9a1d711874904187240a0272143fb4321b37b2bb9d3f2856b8a5d597a69935848958a69710bf2e3f448c42669eaa026e9dd978be8249ae6a7a9385e5749090be41ae12ce146b7dc90a2b062ea8a37418dcfa0fe78b1508afa349733803e9c18086a9d334e0a154e6241dfe725b660bc3556497dcc694295270ea9d307b8c044d1192fd31659e44c47d758e72348bb99f2edee4f863f53219406d920346fb23cae3f47d046b0273014a85673c7261005ce2f23e35baef2554af5c57f8b0bcddf9da246615aaf48a7a14f2c43720c0a888422d9d44a43afb4da5940c256e4bb109c0df24d70cfc6a4c8973f8649c67665f3ad36f9681f3e07f4e1f4fb7b505e7a200b10d3ccc628ccfe1f0d7662066f0a61ab1b9e17fac2989862064d08fecb2e392f64db9f891ca517ddc411ad2b0a06bfc6034cb51b24cfad58efe14b6067664ed69e9bc314c619a0e57da72573623f85480956e6d2b0d851cbdc078439e00b05ad79f358f8fef103328a0720b3fcb1cc8e7af7181437c9ce676603f2699b4bcc23c0318d5af082ae4d9cd2d1bc445126011e5113b3fc8c279f7975dcf51bf12c3c1efc6c8486578bb4ff71fd28d87f107b1c786a366ea57cba80b2484a5f2c5d806a999f479c4f0066fe2726d0f1ea659436eca51b08345ca8724d7edaa5e035aabd9d14112f5d18e67e7726005a74bd9cb445664692ead69b265725d90dfa63d3efc8812524b89d3952272b60999b14434e73aafbe09c0bdf6fb7f655173865c99abb20c53f68ba5a5d47258d27764de70b1e786f281b3d831e9166665dafc0f8ed84da1fb1a2dc2717272fb892fd9e7b9268adc6daeb668d11b87cfbaa74976b4c324249f724305c7b872534fc529f9feda11e6783dda4feaf5fb59a6129b69724a8c3366155980f39d727546ea3df1c7e564e9ebcff78c512346cc5eaad22dad4f7972ab636fdc6e4d36bee4d62205b65d379ded5d2d2c0779721dfe58ef702f1f726568ffb3ba8b011530dec0f52b37d467965c88d98b8e23eb9a88e082edaebd4604dda7d76797ce7262a4d686b7e266c45ba8983a080fedb69d17fb9ea48fcef9647971b9b249ea6d86709bb4aa57332fc1cd5461da7f0c316a823671b03eb3f8d7a200539227f3728c5b2a33df4b7d297d71b57ece983474c362f7ee9772be0d488efa0e245d28622ace3bb19df124376b8ba354a02b502dca564ea2c26e1f42c4cdec507e14a910bb14039a3b22556d6d92838cdc27e49b292221661927af143f0c4914ff02f86b88a8f5c2e0e85f61343bb6498673adc171de207d5dd0f12e3543f3922d7bdf3d3eba2d8ff20284864810bc0df52bae75ab90278c84a77b157e4a664ed5d2f6148ddf0dbdfbce954c40500aadbc5feb710c67f19cf7b973b5d2720703fbea6e09ea4b00c6924061786c860513294f120897f97d0e96b94c1f058ec4c61d3426725917aeae08f08a431cf1e0063e1a2917247e172ea3b5d9831e66f0c3e3f377722b6cecf1fa672bfb326244f31d0ccb916f8cc277de563d812b358fb584ce784ac1bd00635694e8adce0dc179adbf0a66d4dbf64cd67c49fcfe39e29962c71e58728219b62096b21a946deffc091aa1ada1b564337a8869db327970a59e426a72d7454598c96888b445101c8bb4b0b38437ac4d952d6179702d1f385cc3233d37c2d6f340eb0b71627959ea8551eb139912bd75afac4d04b77b408f350631722fb3441988bd89025a76f7e04b78b96e7df7d41c8ea544a31c4d2ea19bf029c272ebedb9bdf6e46dd72dad15ccc9f4cdc987368cf753eefb71ca2e8445e0fb482490520a291e1d8108b54806008095f84c6310218e4acfede896819ffab9b3b772f382d8300a0072201145af934a9f9e8887243fa226c30753938fffc5dfbc0872ca16e18fc1170020e2da039fceef3af62c721a19acc1a715711ad9832ba438178a5025bc46f6910678a1f62d1f0b493d48208d6f58f8536f42f6e2e365cebc7220f6384eb63cae3172d496848c72f0f1b3667b133b8a94a7505156aa4c6fc234e1a5fe82fa3641fdd8b11b3e02c1101ae96dfa2e784a4a1e94e3c8bf794d1c72e95b9f48d2c6337635b96b051d6e524f2772a6dbd70d7d67a4f14d34473ee934ecf29455fa104884e344849832e78d42f87522064fcc46980eb59f0754b70643dc87bd7c655627f0ffcbd23294854a2c1954e0ad9abea33c9de70043d10d7f321699862d252ca76288007c68a4edb873459b4be061c8a018a569b52f309aaf33484d87956731d12a26090273d8cf1eac3b63629e46d2413eb5da7be43892db72d9dd36ff4bec7c1f495cc367b4a10f5614566b3f8a90c92c6ce673aba753fe724c8903ee54021274340d0d0693e67dc95e33149c060953b9656893d3db54c472cfdd9d429fd47536ce9eaec1575e2746931ab5a1d0d2ef99395d47d4c1cb9472bf1f58e8989954f0e500dfefb9f5b0883ff69055a2c87a4ddb03103c104c3b720dc99d775e225c39c491d9b542a87768be3b8919ea859c306ec046a6febe85047c32d45ca621ef687255b2e286a090fbd3dcd4d342997f1070e15e3a3d878572daeb11ff31829ae00f871576f62cef483742be55dfffa8b9b13cbbe35a760e720be5fd4e8b65da8b07f1a084e37bfdee075657d21c1347eadad1156355a640231f8c725dd301fc71d9340fd1347c4f45b5ce5b6a4ba4b45af5085ae55fe67b72e82e1e4ceccd9940802da2df9f4c80d018352884ee5c2c24377815aeabd9ea2fa193c0792a704c7bac7bca9d2d7903ce4da23370684e672bc04f5728e690b224f69b443fc3ee21b9cb610cb95e81a6debcfe06b15154e966eda066f39b0a3f724f6b792ffd7c952de055e95f9464fa9693e4bb817afdd8e18809e1f773f25072c0775670cc7d9e4ecce6e4092d95a055fae4b0cc9b7f8891e888553a6aae8a722f97e426823ef482625c9f530ce5169cb58708d067ca83dc002f9312dc70c31beff2a5725e35b798a115ad687f219839b3115d0d612f13dc52877b5df803ba7274bb910a868aa6f1db5b360c44a37772c9fdec940c8eca7ef9c6b492a2767c7223b94df48b949026b6391ccecf9595b4a42ba03f75e544bdb5f4e8de81642f09634fe357fa444da0a36765cdd88771c049cf8902c1afa35d5687f9e740f8a86f881d10c07e6c88a4818087c1fa71abd3435256d1ab6e52cd8cb530500af8d972c5110dba81eccedc054d6a4a2c2bc3ed5577dc79b374a06a67ae44a6f4230f7247d823de7b44661f0cacf7e07eebb0a65912e4a6cb12e6331e0b2e173af97b0491eaaa8a19ecb8502003a8ea6e145828837f0eadfa41440d9761e8e8fdf45b7285cc2152a3845cf8d897629c53c8c71fe4e04c07f88429674ecafd607908b472abdc3d9c80956e1db685b76690f7708d28ddd6b5c0985e62c08f6a16ca3ed763317efa1a7adc3c734cbdb7b88c31b28031f86b76c76de6d9629a95e409b62d204c1673def32bdc88da7612cb48e7ad25141738c1de72b48dad45985397891e72af4d473c9c7bb66aed8e7c3d7991323485b1b00b11f7831f664f38225172072fa674e1ea0670081de32be0628c5dc76455e96b4d84978af6a0cbd759832cb62c15c98cbcb6fc8f8b4afefbdedfc0c15628d166a560fe6c1db7df61809b6a090b06503974386ffc7c446ae094f24ab238b638d1889d52c1c36cf5ce44c97f8c7246842000cbe5179d5b4fece62cc070c9fd33068884f5a5cef4b1a5fecacb4900531dc7569a963a811c565331f42478b05b54007b14dd678a345de916afc7b372997ecbe7c6cd17f26bb4689ea595ea84611f7109e1cc696742a1b69ee9d32e72db7054f74319d81b0dec4fd8b162adb3d56e30b9f3a4f02d9d2ba61f907b4872db9b9eeed37f88d2fdb4861142e798111f07606be7cc63ffa25989ee12c15272e119d64d1079e055fa8c71cc1bb00fa54fe9390f000177d51e209bd248c33972f6c15df63d2a531cfbb44f8573af40bd9c1a96502bcb07962cee18d8245f39729441c094ace324bbb02c1bf974ee2d9a55db8fb668465132ac2699623db1b672f6e472fd0443d0a784c70567c9fbf0b8fb266db1a5805ace7dabc66edfcc67725ad9ba32dac54a6e54a929d595e4ddd1574ec23b7f3a7190c0893bf200cd3f72a8451ede5f0e332ad5f991eb28ff7455ac8c8fa97d0eeddc052ad64f018f84722859f4f0e038d1cacdc2e087c9011ae55766cb1b984a5b7092cdf998aff00c72bcc70b1ed7ca76e3da704b41b8b1f1248904e12636eff22a824310f2ec6ec319cb8809974996a5ef3a51beb00ca2989b218d883527cd6bab3f68ac061bbade5225331976b220c9b2f8f2e698749404094c8e39c39d19ec3d26d822899d733e20f577cf69ae11de96a4e80b17142d079295f4505698689a4f0a38e6bbfa0258722703c463534d8db02c4915ebdfce1082cddaef8d0b1977962939bc63ded10872210c0edb9b0d44e08593f7e3feda76b56551c74b3e7cad3ba4e287ab16a88672a79d5026458c296f9e86b516cbc9037201ee6944a8ee08930f38b3594d34ca722ecd61388c5a0fa5ec714eebe88d0e1819ec52c96768195cea23aff54c2895644e13730752e1064f9e90f85a5a0c9d73ed940955c9fd17573faa2b6b8ee0bc72532309d1e793b5cf47c596027a5d0862a4755b4fcb9196f197216e79961d00238fd537d1db7f7f80f57d58a207c37d8ce2291579b8550f636eadae87ffa803722b64b8138f4fc1fce773d07161b9568ade84b015c8356cadedd1edd8a68c477235faf842f8e366c2c7378b350c5951b80c14680a382c6814e3f04127c4a42a4e6291234e5882e61e08663b02cf9a90b3d9827caeb34418802cd0ed6304f1610100bb39c40a2db5b3d0ebd340dbbaf4155aae3ee7059e4319384a9d743139a2722620aba1b018968109deaa56b9e80504b6de1d538ceb305980a6de70c414f671e95a086da7ebdfa7623d995e19f65cb9b380d134ca9621fe8bd4ded7dc2809724f4740686167169f7136193a5b965c22e30b7a164f5889a7e9b51525ba7998720c41b6f43a01b49c796fde2997f7c3ac3d541739cf0446c2f5bf397ad045547265ad968d765447bc1c37f2648e446bf6c13b1991fe97be9958e2dce776323872ad5d917e6860b68d3f045d1bfb275da36fddba15e8b908340d83f26c58346572b6f5cf1ed6a725eceafb3bfc5296e168c3f9a3b5a7336af2fecff9a30cc53972f38037157407991213cfdb4c6d90a3b6c04e773b40d7b70cd0c8a2383ab58272eba6393f159d65b57a7a05e920b1b56700877ccc040f2ad24499aeb3b43f12159f81718617dcb9ef0f9585b3ebcd5b11419948a7ccd5135b5ffe553bee0fdf72887adf7f4055835861114d6307bfb3b39aa2b4ebe0f94c15175db81e59e69d72f6f1311679ff1f33d236f4b75b81498eba618fee7cbc7b0c3e7dfd417eefe4729586336d331da4cefa62a923dde9de66cdf2bb5b07431a91b19e319d06fe5872cca9a86ef03076c47144c24aedee705825851228b3d1d096e3083752b9948026fd7854774aec2ca1326738df4551538a46ca8c5e0182ca29d23c5d1424fc80594254cc54bc886c59f0f61c88d07f0a71fb9bf7c2ce3f0df6b3334a45b041c136c91f77779baaea2f108ca19c353e91742b2d6c62ce4697bb0a6237b7f793db60829c7502e60a177b31cdd319f31faff3c2f380793f108f59a2cb38c48588e5724e288532cc93a106347391d6048490b3586f10a0d201c9c2433ea3db05205972c595705280484e4e7517e6107ba4631be6b788311c9f9b82edecc1ef324a2472da73cef05d396fcb128d572aa65c9533ee41d878a47a79b77ac921c2cd609172da53528febc775ee946e0f2a063e84d686b027b37a9f762061c98525ea8209724b86613d2ed6a8936a9fb9af2a0628efe256a26f22e069c999961342d72f8c30677a44a536c0e7284f0f3bae204a8c7c4135385843e8965368ab1536d5422272a49393e7e6f3c10391ee9e6f138c86175f8e6493578fb9e9c0ca67a07265685117e167f4f1f4a64446aee0d4315c672a0ff26e2ccf3289aca18fc50be3294d1862e9999556f7a06dee69fb6905e389ea8a7da5d59571ee8fbfc65d84ab31c47297ab7c763d5d5d9616c9f23f832d4ed088a349381809b204b582c8188b934272c3cdcd45f3f30957adbae5cab4e137ee0ba261568d50dafc8709cdaf9803b472abc30aed777946a6fd7594487394d425c7b9e9025cccf260278a51d6943adf72b33291eaf163f27848e3efcf58cb85c53bfafdd51f049a64d75379da55ac5b5b4da0d9052e900a1409851d478a9cbed451914dafba5ba6ffe3da474eed935b7224ab7c4bdfdaedb6db4de3ec421564fd6f75ccdaa685a0acc6f4a3f74601297269dcb65be775b965a1a93cf093d0454a33fd19d3bcc77e0885d68c0742f95b3aefda9209315b6c309a14af1bd2c2f174e55496784d7ca7ba2b2c3217798254729b16ce62e6477667c65832615f8b28d000a96cd19b26f090679abb48121da7729007b9d782218d0c77a4e0973230b54bd7c676825ab418e9a84280d1f779b372b724f02da4915c9a0a67c9aed7a4f9dcbf6c1ef350945392a7b6b8a86e14f27257df3538e9ef9f8a7cba53a7b5da00e360e2d3f1fc1947f40651eafa9b33a6727b36aa16b2890e10200b0a14ec85f0ab77cf3fc4dedc5d4e84be02680ee9af41240c743d543df0de7bb2356108d21edb85bc583c065be8cc2eb870d3c8bdf072a3c827aa5c1b4aa38f41235d3915a0c58c518f3486c231483ab7010037f77907a11f8154564acb01cf6937a9c9320d61649af55682645a1d5ea18f4705379772433827cbf40580f66d038e97ad2304c5a1b75dec16699a0dd64be0cf2eaf3d72dd13e15c49e9c65c6937caaae4dab6de783e383b360e070c036d50268e0e6a72935bccb616981a16c9f3055068688dc7d907b06310d8f1d3a700ff9aac275472f31b13592d4de0c8d52e9b09dfc4953eb28f65cc667e352ad824af19c31c3a3e988281b07b80a019ccb8170cfd3a9bf1e743bed0a71477a53a17b52b2a0999722add23ff336659e9d4306265377b7f72bbfdcb8e521181beaa6a8790c17b29375d95546994e1e60020ff0b1c3da98324b764022b615fdd23a4021379c628df1fa9001cd7999bfe305dd6f731d7b8baf74cd3a9c5a543f3ebad093239ee602e72738c956e1cb9d183e7a72da4d68cef131cc64eae67902f3d3c0b63d41111d772cd5b9b6930f91491fbaf63209b86912d37682e05118503e731eb34b7ef444c1317a0537b10c553954e5fcdf7d4e1b9a04ea5155c520eaa5253f0b34a4da2ca72dce935d8a4328198048b14635c92eeb631b4b53ff1cb60d8818db0314309c572f13798d64a3f8dce591f7af4411f1284b9c2681aacf3bba9c7488c8da0a87072cf7d592ddff8956b8a41898fe75403e616071bb4c846e4a9c79af3e5673a6b22194c56931fe9db4340b3a77cfae598eca14058bed83c82707678496510173e72086eacb6a8259974bdc04beb452d08349b78aa7bdf67f9d076094a016e9e2f726e0c54210fa1576ccc1e24265b607001b64a6884c80d9d2c3f514907727f5b72ae58fd06a5f6217dc4aaf7e5a46fb289eeb5090250b3f703b80c49d395fd5672032ef8ecd2bab220490643d5bb86b6665df5ce7bac60d3bc7d3fa39a6c69dc72be2050ba5e86456967887faf14175731bff176f52314c3e9d737841c4723e155927a3e18270ca077a78072b42b048fab7a999dc96e87cba6c24fbe0da3d62c72ed04887f837976cab412c90a959966a8a5151127c32b5924dd439b0d7a5f0a72e937e9424f22ef7123a3fa43fac9717c2b1dc4c68fba7c90405973c7dd61bb196ed58752fae89796cad7a25ea929a8746887e10dd53be5cbcb18e6a63043f5725353c1e4a1d7310575ed71b86a42c0ac0ec119b0f7eb34fec6353974c59cf644c1b12d0ea66b4e5bd8086bb1921da0f99404bea6ba840b71fc7be0d41dd60f17d84093bd0622faf1ddebe55d0a63abc044f504435dcd3569ef591cc3acd79e722580500eafe1e7490cd558886f808561a0fbd9cc81f62e5e4c8bb8f95c93a3728b504914702186ddf63550dd641cb474e074f46eb2a65fb159128a43c8494872faa1df0e58d7d1d270dafae1bda681023ed5a5605fb0bc132763beb7c8d3377260dabcabffe2c870c7d52114a167708107b26fa1e52e9a385215ff9e27e3927208332284e2ddae5b167c2f4a09f99d7c94a8edd295ff25dddee15b64e0db7d21f37f2b49c756b7b51b31c214d602de8dd9494875a8e7ad7e86966a7e3a34000161f62a14e35f010209b53d65afc2a7a651a502fd132ad6d88520015a6b22c14c155e0d8abad5da4178b0a15e510d198f3589e69a768de28aedea34e212a4c2727b3c526a3fb7eff302c40e8889a47d789ebef46fa3c528795e8056c0576aff048c3f0ac1b77acd4097172f18bad9f6f38890f56acb5e960a48666453b9d90172f017abd76896b5476f092be8a209273123e2d86990ac81e55620a488b66e915c183ef8705cec6ae1151e8ccb7e1be2ae6c069f97c6a3217fdd556b8f5d989c72870fe5514c18ee8569d883a91fd3586ce961da131bfb9d4b9eb67eb98374cf725c00985bf7ba1c1e676d6284d96bed31e480b2afc89c7edf18b6f2c783dc8472599dbca0870acafd26c7ee0dd9f9a24455c35f794b648101b9fbe9410c3afe5aa0f16cd73c95449eeaedebd6220f932f1b5139dbffba2b99143f5cbe6dab4772d3f077c9df102e7cc34a9adcf4bdd09ea483f5d637e97116f76585b866eb5f5eba8055f2ef67fae2b0326178b31a05d465e32e2a274e80e7a5585b2eb91b7201fc8633d9531607e28a02128b08bbee1db30d34808149115059b14f64fb97ed72dd91fc095d67035ccdf83f9c82ac0492395f202222ffb7c5901802d026dcee7291c2bfb7aa325b5ec786fd213ff72d8804b617619cb0bbe01a5e7ce5f57f8123c51dd5ec3cba1ca1674cfa07697f31732635edf73325f3a542739214de7ca072b8587d556a16b90aaa590c86e289df6875422d6fc9688b74249cbf821643d1727d1a082fbd94b0dbd0c94c30a2bc422c37526bcd361f8bd983a23f0b3b3ca35c32aaf85b14796d4594fa56238c1be6b592b98c5e35eb7d249de754192d8cb372c0b46d4f143872cedb0c7c5c226c50a66ebb8e6ea33b863e5a504c7ee086280058e5f99bca4bd1d0576b6fc7f15984c4d03876e8fa6333b383cad9ad96ae8772e80651a55912a911a0a763f6cf142663e8d139ab6db99b58a593dc4da3b987725cef2d9b8d87467de7979e7199b026a1bb600da89bc60d7f088068d0abd53072663b35e5471ec861f490a270ba2c998caca5d05297adff54243c7ca7b008717282248973e4535d8e21cecd206bf9f7e49bfd7d94aca9d5fbf929a3221d93bd57827771eecc88469ba218d0b6383f864654c8181f30b6af63bcd9bcafe260d87201b6b1887309d83929b64e954b4d0420102f009312c20a075d8190ee1d10f77233487bb447cd25c28f76c0bbbea1579b006bc978bf9426eadebc00067242330dcd7a569040bfa1a965d787189d1ae6b6b917df409fa732d4dcdf2d3dbf292d12f35df1c3228fe1dfcc78793ee9f633cab3b6f5ed8f8fe532b2ffcd79b94166725e764b8eec1b2d3761c7280b27cdab5d4940339d53c21d0082375f4508ea9a0d6047377c3188966d8f1945d24431e6dec017c57edfb4ed4bbd5c7a51b4900b398e6f31157903682575840a235f24175b0d95bf9d92a2c1e01f693f35ee295172aa5930ae6111a71942123b0b66631d74a676dae1106c8362137166819a259d720b6fab8dd393da68ce56cd79e3a65893c31eb0f1ba5c692cc750b65c3383a24bb3d503ebafb55d75ab5ab452756c0ff51dd430040cfc0a0a4584cb167dc1384b3224413516772b86e4fd72702b45de20bf75a429a2103ab56551df8b8c61eb7294fdb7209343b3dcf839e09d0963f7491d8df845405295e5c4fe35b84ca0c417b1b43fd7899d73ed37227cc419da17025757cae12ca35ca0934aabe64e40cf72c1f38ec1f09468467f75732a1eef8054c088791ad1b9d97a87321860922dda725f36cfec3b2defc2d6dcf6797b4ec701d0b96e44fa2e61a938c9abd9b9d3ee72108351c5df92e4d4335d8049b671aac6b4f4155b273207cea9cd9fb661965272ee9d419c3857ff1291160bb5488893b51e52cb81b6684f582f00a9a5773d7e0814e4440d5cd24de56ec5c4bf9ae77903c041b88f3fa2573ae8839b8bc3ad466af2f66b8bad27d82d25865b09ab36b0e8690dbea59628d5eb50258a4ea51e4b729ea456976d16c4051f59cddcd539ebd4e777c45f22f85ed8205dc9c74ec0b7721e7b7fadce78049cc288a8ea78b190611b7bf281153dd2a517f457553e26446c8d0c4b3505f5d8fbc0e28125321cf6010e2e2ae129a4aa1a0a4e2e8800d0880d45da6c6aafd4bf30f98403eeca7ab7770a9bf9e32e5b071520ec20e656287d72f42ce0da6068660ba4490f44fce145b0b8d06f45f3d29186d6337c9b5357741c1d52afdc744dabe1872da165c51be7d074e2a467de31da9e7de505d8bcf43d25591f9ace55b29d2cc89f7335c4a8e3e0a634b43de9621368faac02a2f7dd9972f49f5766f1e352aa0773668672ddffb0f52f43bd8377dde7834da0b384b13e08f99a60e099fde62311337d81971938e04e4a40ff259e85ca7c56f271e9938172c70de2af56d7b537ecc39b6b2a7319947469bda2f509c5629d91f39043bfcc209594830eb0e038315e651f4fc33010927d452bfd1e71e51d401e120008317c720b842489f8921597608aaa8427c4f62c45030e974a112f29cd3f373f08c51a72463caea1efab8c13bdc013dec81e4abeb2f971d3a2496615bfb7f5a7f5d1c422fd394073108b2ba6c2ac76e769998a3e71fcbf551c123a76c80a29c5ca18fe1e1220ff64df4be79619d76c13c02942cbea9078781dd77c1dc24df20bea9d6d6aef6b8e47cdd8675e3ebe67840585d96afdbb137771cb5619591b5b836ee4245d17d86cb996ab7311c2570986f56592f7945c954c6a3472fd8dd9fe4da7fe30226035c7bac8a0522a5a885fd59763024732b8f9a2df87a6b1200f4a6a4a53f5726a1e697fc07051375a5f556cbc99166a47e81766321d459bb69a777da1c46872d06ffae50a5b5b3e88da79f3345c35bb38c8d210ba7a68cd61cc8571c950d672e45419597ea5825c2015ab081521a6210553d91c03735d0cfc14e542f86adb72a89818aae24d5f8216d81616b43c09c5c42e975dedb834caadbc684f64057172c3c6b99ad7af989270b7554cc0f09eefe56db5c3fa30537708a0631cc2497b3908793983cccb638db22d9bcf65f08e274c6bb6e7ba053331833cf63690b59772c96287eac5348939396843e60529dbce1cacb6bdf041f6bc709c69855bbf58582d7dd190625ab5de464ce6ab94d780a05ec7355b123ee57f6c718be77b6e0a5daaf464b697fc1da321b3c6783f106d9bcc7275e416e510138daddb181abca44f248cc187cf42c7ab860c745ee9d10985640027cc29ee7530953aa3ae98c86b722930df81e475889cc3eecea09f2b502a23094a122c1a89fc320b030f5bf988728c4bb8e04c7244e46a6e95db8cd9d17adb719e3f9c26100a093e73ef088803377862ebd4705dce8588f1109171228b79f6838cabd8fcb6ca9f250fbcfa7fe5727eac0c18bd9faccf38978f5429ab26610afbe4e05a6cbffca6e795cbc3d7d2371965726d66047c917b6c8eae46a4391b7177d1f707188a96a20538765b00497280f50167f0efce4a1b8c1303a567eb7988cfcbaa6f707dd40dc51f4b57c360724d5b67ad1d86af63e8688a4985c997f7447677f737bd3ea515f697e8b7a74268dc1918c1ecf85ff5635e3667da3d3fdd616955e0a8917cac11261c40a02a0c72ad886cf5e079494f9d5de15e8493acc6325531e1876a627acda26de1cb2b4872677827db6b2fd115c1805e79713aa39f9e7c00816d8d6f4e0f78ace24dbdb072a4c7b3bf222789ec8449e19f8701b247f9a1b070455a271bd2125cb6de403e5c33d631202ca8a7ab3505d321e1816edbb40476fda3500910ff3edde3c2c27872e0b5559ba7e10994a2fefaa0bf5306256b9594c2af1d689f597e7110baf36e655c5acf4b2d22b7389fc38277a23b56e62930572cdf28874bbd0db1155f80ba7277262323895903d442fd4d692eaab801ab0180354f06951f58026fed332be672fe4cc6bf959894c8000a0c938adc6aac8a5a91282059d41bf84aa31992821772d6daa2233ed3dc3d3b9960cd94d519bf2fb7b121227591f797905313a07f49259986c41b5d57bba7f77761fd0fdbc02cff65dd7607c7566f949db61398afd772ebd4dbee03c55cd5b299a12cd7072bcc8a07953a6fe6793435f0ae63fbc87e3de073e2ff93b0097d17a43157ae67e5a1341432da02151a568509557ebeba4a729a7e55bcec922236729476724cc50d24986b93c4e98d4c25ec05f549c387087218900dc3b42b48402d240daeb68027d4947a529f48697d0c29e531c7812f8c40cb52358e43cf77d12e77320e2bce26d14b85b9f4b9f88fc082ea847d15e26c018e1e6ab628c96e04bd525fe13dd19cf6c6169e9541cabc937f941e5f7b5b9459068bf38511e152907ccb007800a647aaa79af776e6638a249dcf393ef7ec7e728add779303e81ae5a2a51b13170c5dbfe2217c6d40c3047e5088fe7acc45d67239b728d2c7cbda2f415744d25cd7f1a880c2edc663721d6650a006b59feadd6e7c9c6176fb1745898bf043cfa1263527599851d58eff94203c70c62db47f5a61348acccaec676da4e829057da187778892122a67e04e23ccd6ab612e30318a72a19b0ecc170debd41bdddefaa9421b0b53d693382b5a8c3dacc468dc2cbee6727bfc01d4128074784aad45ed60f24fb1e3389c38be20425c8e516ab00cfb3472a7f9e21ba9cc214dfc2b6d1f28a0c2e58416b212c2a5f728d3e7ff5b79883672a46a83f8786cbc6779401ce3e622ada534df922d2177a3c6ae459910310cd515ab1d1dd0c9acdc42b5f29e81e6b521fa1e7f2902aab66d3e13a7ba69eb4cf02f7633eba2783a91fd7f70d2e495550ca3edf741f4b8bf1bb05c1103485e70ba728ca4cdb324262fe7205b020caf0e3f79e788191cf2d5e187051118ebca420454119a8a3d39fdd6d58943a6e6e43f0dc50f0882e1cd5de68e799e0658332998724d5553b695910c144f7612f9ee28addb12c522a47124441bebd9c351bc643c72b1568557aea27c504de3f6f63da2f6daf23f4c4c6047b7207e7a17dfeea1514fa714d0a380187e678f8a9b672648138c13a3cbe1efa62b48568b66a290a81b723d9144577da4fe0cc01e7def8d2124d65f2d40b7082c35cba08f65f150d6cc72e75702fcd5172c9f6f670b589410f1d9515b5fd5692b826d1f57be5149be7b0407865de17a96f0e55cf2d2e53ef7f4c868c8a3bbb4d686b5c694edf933b9ea439c7d9387b2aa0c64720a9de3e16dcb69a8ad9df97a629ae37a23ce1685791672d156de71992a73365bfb1cf53192446fe6e716583704e1e7a64cb8834d77df72e93b89d3e9071221eb67865240b14c1e50c8718df02c7081960537df6c0ad036572ba83868adbaf590b01ff516aac03b3cda1a6b0d3450f744f1908f3981f772899090ffdc5e22f77c030d84aa52ddb41de7ad52b7b65c9170853cd782020e72acf1e8a3c44fab609fa268be9f6c63c995ac666904e9c6e1f2cdaa6624021572c419fbcfdfc20f7655d0ebc841e256f6b1e149d1d4b235d6bee8c58e9324b75fb7eff2332d772e6906f6bb3cf0bd2ca7624c3ab4567203f23326b6a9d919ab7250ac9ef4dc5219ffcc8741d236243673928c15c4587166f48fd171b4fbe9c172a8ce5260cd0a488387ed56842d5bc329ef4c55732e492cc3b00a64d049507472bc5fa9242a06e1d4b14d1d1329754fb3802dda9ccb823f4e5dce70d660c890401f1018c67744c67fbd793a9195c0ea321da00cc70fd24f91d95a7b68d5b61709543322a309ab7bb68d0e254d02c11725f929a7367d4a557155d872dc42eaf23598b8c633f23887405d0acd01f7ef56a0027d4e4dbd35c574f0eb3fb36208180380d54dfccd9417de0c9506b72923772aa3ed050d5758174df6ec132c468a4e36c9301aeca265d73600a9557b85869fd0a5c48005081aa4bddebf2ecaa3d94c5aee2a7675a33e741c23a796a413518140b8b3c018fdd9a7f490e0febbe1f9d1364fffaf6d1d6cbf10ba85f9f7fa9950b985a22e0778538c7107f687cd7ad77f4847a68b0801de79556ba2f70cfdfd466c996ddd6d234feb223a55a31d182efc721e13a965fbdcf84641f96b07b1f6e0483488dcd28ecddaabf28a3c7c798d8f72047230de5743b030296f716e0c3998f11b63bddce04eb334c99e669d54f9c672a795ec249ccc2e7ea42cec26cae68c02e7dd0f1b44e1b0c5f5080f75521eff142fae215a29dbfd2bcb293949c14c10996d4e04fd316220251dddf11448627472fd664e0c0d28f746b4aea2f60dba40754e74f905d2d217ae569ac43c78e13b72e0651181eb990b6073c30a1b3ef6ec505f56308637fab389cd370e9a76fcbe72985c043b63c8e220f9bbf24eec637beef5eea6a1915a863d3b3101da41709572d347dedc5cb6cfa97700cdf1a0161d1f43369ce1d81644e8d0d412ee1f9c7d6abd115da5742ae4028d727ef5d82e1d118a0ee4e06842b2f24fb91b23f145af6bc6f4412010b961b284bb5bc07ff9ad868e3e543261575b930bfd8ac385c7927203066cb22fb067cbbf7dc02300aedb18a0490f3d490f48b4ef5e52190d00721549400b1f68c07c6dd860a5733733be901c2ffc1d9174f9afa8124e3ce27e88647758786af581569c2eb28cbda42367c6a7dc43266227a87b7ce1107bf64ab572630b16d42116bbfca1457ea63ae9030b1578453035cdd8db7ebff86618643371267d95bce131e8c1dbab2cdca7e90fce7444929968194e86c4e9ca8968d3597289f610c3bc9a60bc96b0d18877795348977fc85ed5df4f65b5a22d45e8a90172bdaafa9a4667aedb0f2c37528698e887f4914fe9c94010408099bab4e2e0f072872cd1adfa2124d5ac310e704d4235017c36a7bc225fad0e0cbd427c9abcf53956658a2b794c0b78f0751b59efb5a50219279a97f7b3f111ecaf27841a6aa072e15f6f1fed30c1a521c9940c21df23b0cb67ef50e4323ef896b0832cadfbc472e363b66f4e260e3f2e22d6f11ba98f7d711ac6b08ae4887dab57e4ec84c1502e1b46c781473eb100b42ac5da266bf571eade182d9b8d82dc09e70a7786fd2d72797f023d7f72847ecfbb30204a7c246c721386b874f6ac41bbe36ec164564c72c5f6181a954e0c08d0c8d5c7aaaaadd545f44554d1ad28e6fbf041122891f824b79f1b65f71a758ebea87d68b20572f9250eb4853713c9832f46115e8c7c9a166c848ad09ec517487a84553eb68ee4d6baecf2c592f9ca01b90b4a1b10d17f726045e94aea5783ec55d58feb04645095b9ae09ac5d2915f0f2a11e96a792f934d73c82c6133b355f9c68aa36fed8e3d7c59b9687dcf166fed2afccd17cbe8272d04cec1680b3229aee0e3ce404fabebd1e27602c88181ea40baf6ffbc1734f314afbeed8b5c1c4ec137d5dde6b966513e6aeb20dbb1459b662222948fb1bcc663bfa7a9de42a80c208acd302a95247c22aeaea57e735a2baf72f77fe856fda6688e348d6ec39fa3e6033ad6d66c741fa8964e403ae87531cfba200660573c11564c2056992a33684f95b01b3766f409847344ba74bbdae5d3761f44d5966aa16c3cdf48e57ffc876a289f2baaad900be2d6045950c28f3a71f2a5b3edf670772b1b46ced69ff81c9e91935b8c5b3e792a36a780593312a3ab13eab1024dbaa728a54d0693cef7333ae83694cfa2597513da51246360e3042bb7271b40e45a822babfb4f2ffc192e92b2897dd777cec3cc2614930007d0a3303780a7e7f9c7472428f527df0ddbf01d46d094df64e6dc7febaae0ba9e6dc766e94e44990806a72da97e85bc0cfba4af299fac3cf9f486e2ce0ee16b2c3e9906a9c9697d34c8b72f12051b5a169a7aaad2556d016ead598c20b2270a6fdb77b06d5073cc8b80a725d8e5c7b9552fb148a68f6c128bf8ec22b4dfb48296acddbf63ac13e3f4b2572da5bc7c47f29da94ae318ee176964693d6b45ca96dd3cac9a073cf83e4eb1d0805a5011a184fbe16d12c49bc577e593561d772197f0fadeeaa9978e159eebd72f6f1068198f4218a434188787903f95a416c733722fd109377c351e8b6da1b54dcdccfe5b2a627dddf1410eac3ac2bd5d31072ae7f31a3ddc762ee16f225bf132ffd09941280e17bb17807ac9a3dc3705d57a499ec547dd0cb9887d3794306328e5d38ebbc4f290821b9a79305e937e3d6a7252a3f78098dbc3daff7cfb0b3727440bbd7bec3e00b6b2de875a317de41376e96555b89316cb7a9bbbe71156b728d2f97a1af80ddbc7732d7f2a070265371c23bf6d6bf8449a17d46fb3649c5720ef75e324bfc644d5953f65a4e318f43def386f1004a49828bf41d27a987ee17c842194739d10e8b95b3dc5c9395741c541f5cf278c5b5c642e6abca51166f7211274b4ebf373de1dd20e7ae7ca025855ae78873f64ee3a5d75d9c90d451ad6755c54dafe195b7fa3790d16bb7c095d2e3507dbad58e8caaba17aebfa98ffd72b0646bc8d4b353012359eea98386f3697d7ba3e0f83e9d93338e94f2c46d487296a7f2dd6aa03daf7bafd8909d6a01f2ef1cfd74bb0a6f8e48c5038d62431672a8b67b83840adb2c4d051c8d1ffa5e62bdc590b8464606c9d2729c2749157072f619767667312d6d5b7d7f6d0dcba6f1dc769a683fe7d7e1db0539b9072c0f4fb86fd647e7d3f52010bfa06f036aed65e4f93e357814048bf9bac8fcef88a272c1c07a34740c693e0fa79833c88a8cd6d84bfe0ee53e7181d7e8591d0a146f0edfb94de7e3bd4a5b5f111800a96c4915b56afe3f3044a6ebe1790b8842113c723c6a0d290d99ec0f5c300affd8444d1335b84f3df2b657cdfff18427a4d6e457559f3668a83e309d9800c24587802a5a59b6ba4f4d0b34174839051cfef6f0723ba30dd688c65d1beac99109c15f09d61016e2b6e3d1c8be2fe9a386ffea3072c75b868680f4c207d611c43988c4c2c2c154de5380223fc434f0ba233802bb6feff8e5c8b1937b1f4efae52aecb3eeb3e27fff1ec35307b24ebf3ed5836a187203b30ae7ffe9f6531ec5dbf48904636797b05f9e1f640c29b2735e70af00b772d5674283dd533d3443554162995fe8f38b3677383d382c9dd5b5a7d753644572496bd511cf9678db94406ba6c206896a5246bad53cbc2c66ddf0078c9ef0e872758cfc4c0676c6a29fada8e1a49388c23cd686b1f2d6974109cef5e1944f6b3e0e7cf380619c5dace549406eed2b9a77bd247608d9a47e5b5e7ce6966719cc725d1491793f3b52fcd35c5298cffce8bb632d5627801f67a862707748be211a727c2020ced495d3b899bf721692d9a1b3ad33bbba8c5a02c2880f1727c835f22bd086ca6f7467ab44e67ac1562656299eba6d8ba349c433f1362e69926c5a6211379ffd7c10d0d66f3c98939a647953d1ae8a2ee8bd9d213ae98b0a634432da622744d152a0ce7d67aefdb43634aa9f2f7980f4184867694e1bd09ecb7ff61c728928002a029ea7de1702301f6a565ea53168ab1c2d277e0e84e8a136df62b23d7d526aac1db6c766b1de3238b80e7dc965522f813eebbaeb7acba5eb1ba8f91189cb1af13357212f6e7365a3b853d845646939f827ae866431f5632c4f9961723ef9fb391c416dc4d6f00dea93005f1dbe92bd160e52b14785edcc5fac11c07276a4c8597254bd8481819ab3b0bc382eb06070fb9cdb0019d8119ca54ff6e66b89d72356cb02b17e63ad480598ba30176a423269e3c288582bd327adffcb412667d0e845b8c73f387691129c22989b4a6d8c424cf1ed9b666afdd7a846ec7d72b7f64c33610b0e3758df8756d149985b22868b35e7de294c600c9518722d3b3d7c275b80182d574b3541284c7be5207143fdbd1047312e451b2e89d75d0f2358a2738daa8a4fcb117c9c9eee9c46eb648adbbfd57140ae9303ab42b8bc3c1d72e8054a7ff28136f2f8634f689333168d7a1ed6e8194f8aa025a0b7f667aa4d7270f6c38557e1798955940bff8141a9f92bad9ec9ea1a99b294d9d0ac4766f8724adc0f8bb5c4cecb3367002977e944522867fcd3c0975f6a7fad5c88f928f04e47bbe9d0a3d488a488b91d920be219bede8fc21932ad18e46785fc6dd7b0fb7221febe2aec961fa031f1edf2c2e192ba1ef3c692910cf335a860be665b7cbf726db5cda065bb4b779d5bfe3a5501219f0c9e4ac45f71285996fa2ef20885981b3011aed27799cf64215f400ac77b453431f5ddd6857be2a94362ba365f83c46803fd21f6ccad6e2a9d15473f8dc7cd12f2954fe4378b373a0cabedf3c2315e36993f6524019440905eb1a2808db9c91b519430c1b818291d32795c1fb75ef25e237dbb610695f0ae26d60c500d391edfa0b194cb23d2d470e976058add42e47222b32f140b7a9b7b1b06619a389a3bdda61e315b2c594d9aafd7ee801922177244fa539a7eb5ab3d91579400f45e7598cd068794c82d4dcaa7786b2630a37b7071e1ac68a77f8ad51c3e56246d82e2f8fc7d8df1db1273447fa119b534f303689395c578a9a8981c750fef6998594a43d0dc4d89490855a4849926395a3fa36a6a96db1ff21015c3bf8da4aab4e444666396dab639f07034d9c25472c03eb75ef76b9564b886f76d952167264b6c79b58aa4fe6bf18067122a77cd049399b66667024edc821e1dee6c326fcf79bc086080d305cac19f1145f426d06fe6602269e2d642431a49671a90912afb4132eb74fc547a91b870003c31bd9595d13435728003e197601bb51891a482ca904b1b1dd0b142f00054bb69c9e91f007fb6e44ec0096b69c2a40d0c77502d4c1aab6f8cf0b434bd739a9a16e23e1e63bc6297725bef2baff8c2eb3a51aff5c149763464d79c819bf6832631567508a6ac3fe91dd2b03c68d23aafca882ec224672233377f51467280a0397c1e892f6aabfd7a7231c7240156e650be0992aa9278608d95879655db5b64dbea17c7c1ef9cc0f615000c1d13c478da956aa1ee497a2658f2d3b914b981b3db2366d4601cadd34f727afb221b45c4340e32dbaa546db64b92ffc119515add41d702c74d6bfb012a1ae9e3d0c5b77dd8b91d391baa3119e200c7624122398ff85088162a3bc0984709d28a0b0974d2c97f49cd58b19a96b5877c4f674b75136826508f79d49d7d1d722206d770eda7c085491fa8b1dd5c824a50029640c7494381c942d6032ecc3545205e6c6005ce5b7aa498fbbf147d0cfe50b99e79d12758faf65f8b8de9e16c229f6c8c130f7cd67d59e3929181f5c3080b172c1974b36c9472a64237f7bb797253efeb2dcec6bb12be5c3d89501b844544bc2f85628f2ceb70a48244a6c54d0f1fbe7b3d78b407e3ff031422cc791254524e404b69127bc24bf3e8261659de2bc6916846030b6dafabfd83e3e361f646d26f56b570b05bb2fecf213e2e0e1942b032acb1bf84661a7f8bcdbc7180672b8b40bfda9253df41de5d202ecb25cc727128452cef9d3b740d95cbd64c2d3a806f75163980d62bd444e4ed0cda648f40f224845c21b8360b145521aa38cdfcd4d0e6d2feb41939bdc603ca3d3c7578309b188bdf262182dee10d3bbaab95bcf6787372ba7903542482f2411027f791723e693c9f383123e4ca22fd68c0928379e99dccad1bfb5c5fdbf9e347f8502f722d767964910d556e5bcadb7dc4713bfdbac1152cbbe3940d3beb68025acf1014d1a63080bd20e50dc951e1e48d0a6fbe8f601d0e53c303f590471795cceaaa7270b7846508a741981721b90ac66c4dc6e8452bfb044d18dd13769a28c6e66b7236da8a0b3926e89385883340d9f76b367f381c81b1cebbd20e084d4c5bb89d27620b74943cd5de61a86cecd93f0fcbf343d73a7331e5e57ae0255f2060cbd17270fb755bbfc0ba6e4e59c888efa8f02f7d741e4e91f6436d53be335779b43472d29f5b464b18147f3fb13b8f90ba1a6670383f0d8280b16a626153930d36a8729c9b6327c4fd948907a465b014e0a4a6fbf7be3d0d73f1805d8a714b2edb75727663686ffc84ec1713c2e61fa31e547e563df2d74fb1070b84f96d1e7d2960721cced6d5be306ced0e570a74d20019405032dad611610f3d4c81f0e21d73050a1595d984add7aa79a5892f14764d9c7cc746f6d63282cde4f8251c0c2004db6e4992e0d8d94325fa5341384a79eecf70aeaef6dc62f2599cfab0dce0bcfbe472708a1d0f4d24b0f8339d0d9fd1bc52127b45ef06ec583538791cca6afe721772a64fc00d3b4f107788cef6ecb8238406daea00302014f0e270c771b576911328f81dccbb0de0879be550b2c3c59f3f2ebfe6b2484249f63b8987c2cd374b5f72151dfa12afc0cb6fc10f7b1a57219b6196725ebb38e77e7086c31fcd0ed04c7201bf3b4c015f0fcdada1257ade53f7e52328495872d232a6db4d641c6914fa721d56d490a8cf507da103ca760322edf7ccf87d72ea9e7a2407de6aba82b22f728f1c368c98fcb5d154819cbaa17cc4bdf6de02f44dd2c2cab89b25b9e79c9d1ef1e5e0afd10a805611e5b59e91b1bef6f0ba0b60d52de75fbb1ae2e44a7af372d068d7bdd7de999c22899e968951888ed3676e15e82370b29a8eaca5d3a09e725f31c364dfe03331b222afef7a45738e6aadcb2950f79086abdb9e675b789a72132aac0bd03740b9bf228b1859a1f12effd76e549721f79bee16817e891139612480a4c260f9f8b53cc4ca02556f99fba6a8c94b8904d042c4f95d5244816b34c259a1be6e462fc61e44bcaffd48dc14d5250cdc8e541e97216994a1c9795e72e0f2eec56fbbc8f4b981ab1daca9a5d00d9ba0de8f2c1236dd431848d04db40de03e6baef0e160f795f8ebf69f92aac452258c43602f3b8cd29e3a45312ee7725d14a448ac44e9bbb94afbcb2210e3ce3acd97a1848f322f087c1c5111076972ea384e419031b94f26e21d98594e5b91bf86c326403f7e61d4777b6f653f0c720f0a6887a4bc65de03c14a02ca1c65b16263fb3969f2c199830b9fa6adaea17272f203c9020d62a062eb02b6ca7b27cf07c5fdd5f816f42afa42f40b9844a334d516dd5f5b6760758868b4764bbf750aec1b8cc1ea466bcbe04d853d12567a6ddf4649d8f97921b9f500fbd7ecf27856752596e984526b730ca380ffd6d82972f788d2268628d2eae64d83e8d5cc834453b3b8f0c907656ebcbd1b2b35a0c4256b7b8807b785535b760e44fcd50b3ab5d3c851ad73ba8b806c22cbe6d27efd55d494a16c89b70d49b5fb438b3680844127ed34b60020301c4910c9ba206de072f2b57cdbfb2c81cf806d6125eea39c1d28ba69f57e7863a7c2a6d7388eda3b72909068c0ec5b7547b95b60300c116daaeaa009f3221cde1730535161145e314fb2a8760992e66b270e15f6dd48d86b2c67a6515f5c0792ee2d0e61974afe1372be2979a55aeaf11e8f8de12820aa9923c8d149f81a9b666e2f1eed74e652c4728562c927eb6c4528c3e90b85d3b015e1c2496f904a5c30734be55d0793a02272f84b630bea36b6d328aa97ff267549caade1c22baf08abd15d05c7dce69ddd2b3070f28a7b5b06ac76938be612e48247bf95ff2fb53fe5da72d2ed121125cd164c0f85295e59349decad8b19e79474267b94dcf4019dd843105d38881ddfdb726363f3d07f677ba387d813e33e53ff0d1fc52aac7d5b43590810e016bd7e857291ed49e9ea47050b5353c7c9dde285e3568c020d1b8a40077f995ad99af7bd2a642919efc4745a229021e991f4ec20d2fa50dbbdd0d1a2120784af88be50ca723c07abb8059d623543a963491377cc104fa6e72003c0ae1c42119547e43b904dab948a0f02d616aaedf6c8f8ea4a733b833d8dafd66efb6e35ba489ccd08ec0f63adc8d15af5f0b063ec692ba0e856610f167ea90ba87913a5fb144430046c7235427c8e81b037236daf03829eff8ebc864f21ef1ae668901c26f019ad47e5724b250baca2b119f95bd870cef12b95b4be6ebe99a7afddd10a404e4246cabf72aa27d3e7015fa420cc6b27365be967668d90f9f79dc285ebf66ba50f5bcf0e179619ddff29f1468f07408b778543f6240cb5c2bc5ba5e2d3c7646bc26ab80a7271940c4cec0a1c1cf3eef62c1e6c4dd6eda0d93fbb2a612486544b3a32e7d7726891a0f89b38e93d6100a3dceef0119894b3752c00c4507d47c97dafbd60dd7297168e41a8b5fce30157285dbed87076f487221b1f6c21094d263b2dd95f9f72480880088171c5d9ab6e61fbae4ebb6a6b2d86ed6d9ad9832b2401bfc6e58a724de16270e8ae49ec93ea35889ba46f837eb21781109e7b0735f280a963d327482e95e7587098afdaa78ff524cf0885973273b4163636f624d3a763d1ef1ae717f2ee036b74b70e2a2ee9d731f41dd12c2ca62c0e3895863feb49938d3d394d3185933a9b236879062ee971e0939dfc692f73c852d73de3e2681a553d52afae72c4b5df68c420a3bbcc48938a4ba7890550bcc791d913e8427d24ed967b99662ab48d6e03ca51811fc81169c1dfc51eff69e57054c32a01261e8da509cb8dea28f5e4f5c3f5a3869e57134789f32486266c17626cfea2602e8fea4d003457814fb02756e3b92dc34311aa6d0036d5c2a1ba690ca7991580ea95bc9c50c56ec372d0640abe814b26306f26186e41c9396984bee66e656f5b375deba2f5f90d5a7262b7a2e5cb7cce37d1d921de48d3e751e4247c94f065ac0d5af877e6afd5f9311ee848ddcf2d60d0e805bacc514bedb8ee64a4d56a13ec03e6ff221cb9ebf04bae8634d295ec4d6ee2f67b1a2d22c9fb613afe3597f47053101042e3a0dabb72f841af5ebe75f3e5e9a83810beef5a0a0fdc99bfae4f2a87c174b96c1dcea972a26820bd684e863c52d8bd4b37655681d2dd517dae3e91e24d3f5cec646ddf43a0fa3da5b330bf4732be1a5abcddd3a4d85763420927f9b139dc15902a52e4722943288c3c1af67e27dcc56387e6d6a2b82e2247a2294e291b361c2b68f74a18ef8a1cd9ab3b9ab880a15ea70ac6f0e6825b1d1be735096a4ead3430d528ee7284889fa40fa38dfdc9e0108cd3b72370c134d91e1c821d452d16ea485a68dd1d0d8756f747694cf4460f84f9ad904552e7ba64a1c9cbf1ba8e5d26f35529b172f5ca59d123045bd6fce6b7931b4f227cda12f92fd54b2318af84a3ad6d804b7227c39081de4399534effd7d18dd67e11b2148891e60b58fbf9d940cdf485d742b126ceeb7db30c6b42320ed95cddccd47c79d5da6b8ad28bc5748bb76cc1d37283a79f381048db9d39302637fea734d91b939d30f35650f61565331c3b64b972a6d358df8f3e4d0724809611a9fbc9987e47d09a16866cc823d07bb197ae10545f9b2e5ae7bd92274e88226714c39358075d9b217c36c3812eb053133575ee1a0a62d86ae9962eaa20b98fb2ef126f81744e410eb7a6b2a2a4c7f06493882b72f550d5be17813ca5df29bcdd61475c7a6ff0b934c625e089d55559d2a2ff2258f52341a5b757db51f2a00df8a40a6cd0325ff2ed9a750ee931c4c4911b65f91ab9134cfeaca5ce2916e89c6f3f7b9fb8a51872655ad11bf300baa280b8b54d1ad7b8712d81ab63709f2c8716637832194059e85d2245e70e7c79535c21562972bbd1efde859b81fe57281518bfaa693514b00d7105f6f531b2565ea07aeb746ee2a1de5c363c871a188cd9a5612eb00956a17b3d4bd73b71ed13fd6681d1387251e0eb62fcd79e5158dca75cbe4e89056beeb3fad9c38fd6d11d90b6d8f23472f7a860b8fcd628cc59ca6fb5dab4fb749f7e20c144ded59288639c36df5faf72dbfd7bb332767731a37768cb5ca6820b6beb16a732cd7721a4ecc642376d9d72331d94a6e8cbf068501b966ab2d404c0c24340a30adaa1368a488e4186c2e14a2dc89bf79edb124c0c8bf9f174da48f1e53352ff98ef575a8d07e8e0461e5c72bb7f074a540e30da6381c60f89b2a52a2302084da2caff296190af81e3e7ea4aab30550c7110f689ad1841331f5e6d8441707e40d540b003192352d6a376e23127a1b8f8207bd591b77f13fe49842ec1967457dd01a125dc9e55f1d79b240f721083ae252e0820b715e191908c651798f401a0fa4784a13b4b6609a43ed756145c1817816a0aebcb7298667d7721cafb6566a8d883eb88140d14899fad0de472f86cbfb5b6b518756539d2d984f33c5bc51055cba5aeefbafcfa2b96678e0b0af34275e822b2e0aef270713e302e3a08df4bcd50337b198db78882516cd101706991e5c3a5d4f14193d65f53ebbd2645316002d89cd21290558a4f7cf1f3c572ad3eddab6f8ce64ea396951cecf6b55a31efc9965a658ce6e5ffe48285fc0072d0537a8a07566620cc4dc11b51e9b6b558f830bed1137400b22ddfb0d622ef35e91f7ff196174e58bc0768de7b37efc2d50905ce289b78c4925574081638b04dbe432010c0e8661bc8b38874c6e4a8ca4464e5f15928536a6ad40f0532b9eb72e916f7b36280bfdb1075034e52f57f94b0a4c6b1bd9d5e794cb3483e52ee9e725a08163f4e6d5dc58562583c51d69a563610a1da10603a172104e873c05e8b21f0417cfbd9da04381e3787ab8357b9dae05854ba4b5f4153aef188be016c60720c52de54cd88a4b38caf1f6fa34029a32b8cee3dfb6bcf570aa83ed8673f2e33cfb5e570d7abf7427e114eb5bb09e19e2f14db36628c188316b7b1cd3fa79050d32c82b4eba95b3ef25d3cf5ca3991b6ca4ddaa0367a042fe209fae09a18636a064adc18c4a5dfdb3bf8b69d58ca9e2e923ab07047f8cf9aedae71cf33492b49950fd2f92a5126c032735400da343bb121405b1c3966d8b0ead950be1049c01afefa43533f9325e39cca47bf5b8d6291a8fe4881006e91a1f9eddc2ce4304d3a5a9fd6f4503ea3d706b12a311f829f71c450d82251def3001fd6e02e0672bb0e59e6a06ef217ee8e0718f5175895368f11254a174c19a8e675355b121abaaf4e16c3885f9427404149414c269235925468096c6a9c9dfa906b9c51382eb7a672678b9b95fb1221700bc345289bc91681aa386c47f0b85596a38d885972870572eb5a82a181adc591017c6324c14e19978dee13fa86831edbc02655ec7bd82d0109a836f5cf086e4d85a90e32deeac7ea24abc5f475e412d3a9bccce7c2654872669136852a564ccb034872ba8fad5f10b13126053a81ef1688de3cd5d3cd42328ce69b2fa5b98aab7d6f2c94ca7a0f82837c097a63b57cf4ed361d9b63a8f372c3067a1b1f5fdb0ea9c378aaaa70220dc5c528a47a67b2dd465b42d52cbb0b72546c1b98d9d260e2be894f586adac4c4584ec3b3dd23a4d4a59ad2e51e9da02a096a57022ae0810002f77480409e5427dd4ad3c19a6fb1df3891a36549e3cc45b1cdf78d5ef92727823589066134999252635a715ef76bccbc30e935489eb84264273e49e16dacd76c1e4d8ac3f6e6dbb45a40e026f3c80df5e623047270673f441494cdf4652f4c328c9e8877ff6a3cef89dd308de083c539d61a1f9d0ad35868d312152222938621ff52464e07aa688add173fc4a2372ca6d1127a575e780ff244bb89fb0bc681ac71164e95d4dd3d3433a2cdef27d2e223c27f02caa6ae4336d45d60ba8c80c596dba48012463c1299a53a392c647037e6b88ca8b36fa46396500fd4a51bbc7d3fa91d142fe36e7f30888cfb80a0f09cb0f55cfd9fbd97728e6dbcfec872b98a7cba8ed5e7f49b40701675072921474cb37750c1e4888527aafa47904ca0198970b1d3646b3a60840b4b0c342b045a7fe8a059e78815123a4e2e9888cf466720cf1fd0ee3057e530679fac2463a0144a83bad16f1999e54b6094a9f776e0ea1f7b39b98c43c8d6a9002cfd2c7cbf9ecd41ef17f041eb6c7212f7de503f95153733e3f460e9914d9d87277612e82494249eb0cf60948cb8067780cf8d29f8008a2fdd9766a75e27b5905bac8b8364bcf15ec019973c306447a2d61c37a3cea77cf10737fb7a11d60ef07b09286381f6dde5cf1f614b7b4072b9cc596223f28c58c8a00bea022849deae8b3c6b05f03abe05c3570ae50461365fdb121eb825e34399d3f223a54d9973f04c14099922d687b231dc28f8566972f2bbca7bf22000e41add0716b264537850729892fbe59d14ee011689ae37ee134eb35ed61c999bb62a4267e23486adbf49a365d7238338e8fce12af58eae7a69a71a83b17cd76c9f4a6a25e8d858883298da01af85157d76848cd438e0098616f4e64f042583f898452220da63eefd5fee60adc84d89d937bc40c7441755da51fe14b9b0f343d32a87b05a2d15718651163128fcba0d08adab56dcb5fb4c61500b6b3246fe892834c51a1edd979b280d7b02e03d86c4346a587dad1f4ce86a72bd45dd1ca85bef332dca41838527bc6bb7a6b3c0513f42dd1db564e2a8aa4401e460afa83b249dbb5cebe93362294c7457964eb179cdb282d02c184212956b72b3069576ce25d4793ab4141d90c4533e48dfca1c737adcd75863cfe630f85e724b03c197ce4425b40c2eb102a9f37f69daeb53cbedaf6077b8fa90b733abd0543be70dabb69ca572ea8b55cdeccd6e0d2544d182617ca73de2f277307d959a4dce2bef62a7e60569bb4370fc384791b15325ca58308b510d26907e77dd30042be41e50cbe1d5932b169f58ff80ab008d63b9bb5428b127fc34b22c947c107e72e856fb0ae817a4d6b0c51b60a0ec3656bbdd8bac5714c838c5c1b0c3454785729c70df684c29aed84e97ba291c6737a51fb1438ac2af301c733cd6e591fe2672586eeab5386f6c61d9f6a2d359d61dee0af8f0c07d0bd94321bb8fdc8e4a3c47037a4800df6d8b896b8fa5b3cdf680624473ff3fa8d6784b8332009e12baf1722090bd6b83f8868be947c89e72c72821a1ff9968faa1eabf7fb72b18f1eee072617f70e33d95e9dea13539b6532ff28d4750156267289f8a2169948c6b48a372cf19e7e445b570f6dda14ff7a827b3c3a5c95122dca0221dc3023e7864ec77721a46138149d3500f6591b29a1f9fb2f1e08ca2217fde36824844e4673f30d372763cb91c54f5174fe49f0aa0f960fee8346725a974ece18966d688b0e17201441030cbb072a63d8097ae01f09d07a76684a5c18cea234969239a7c5325471a720bad6589757d6ed77e0a15bbf8d16274210ae5bae2a168c459502fbdf4e89304855da7640d5736b878af3a251352cc3ef51b3cd7c0c3ea5348a78eae1b27805da7ee6da3b614332324db9b4c8c062ca7d80b1d9db268e034fce4e424fcb4d572acb0e82ee5ec15d81b625ec1e559247be452fe84b2e105ee36806af7bb73837293ad600cffef2ac351588db1443103302c89fb3b0ba3dd218124e119b8539251b84f47a188cb6c3c4b01b874fb651843aa045c3864db74d654ccf574824c0c72598a3ec9f73e72e965971c1cf814850931e6cd75dff15cf23dd30a1cc98cb672c11c4327aee42c2baa6af534681b9b2fd8126e826ccf97ffdaf7018b9cabf1453cfb45be1bc7e0134aeb22bda4e62f0b8d3c2ec5f43ccd1843a97d7e52cd0c723bb85942e093991071c146fc097d0fb0d69788927866475cd7d91c97d249dd72e090be20e55966b188dfd95a079b42629c2d6d8fba116413a1104fe42af90c6fdc3f73acf8e59cc27a37c6a4cbf591b66fa62b0d31512db07688dd7bbaa3b225b2a9623c213ed82b7af2266c8602a4be11b5cadac9e16f3eb9d734fe358f3755e88b43efa34fd3ae9bc1e75d225aa33d67e24d0b6fc7c496cf25aae9c9c067721bda7be7091f8abaf8b68a83070b4b7d2eecca8726b285a777727145f677b325ee605372f38277ba936d3a9a94163ca174e6ccfe777237174a22de8da6c10272fb874298001a0b83f15d56d345b86c9504827fd2b5f2741d31611994b1214e72f3fdb7669ca7a29b4eb406ed65c6b7cf5a6b0007299aa5190d799725b2171c72c4b0026b6e56f5ac71b7f6db50f444d0558ebba2ff1da47aa7040f3298b5f45d7a172d687d9b330d9a0062818eb4a29a3166ffb344602aef8a3e63fcf20666726abaf4c9636c1cb58bad3308d4c5f90b838fc2541c0755ffc56a3b3879c2284dc616d29f41b0c55b1198d1608c194942e66b592684efa6cedfa84c7f8d14d543174a75cd88319df4b34c41fd38149ecca38caddd72a2920017c0012f5bba0a7251f791de7818fd7110ea9091e26ba7c5fb464ce430dde2fb8cd3580c381af42547d4065c38d2763c054a35231e6816726c8211f1908f2e0145827a233c59b9329403f65e832ce618e887c72ed8ca08e4ec4f822661874cc4d3206e7e1654e740cc2b15d9b1fde87894f4f8c691369216de39fcfd1f08c43ced680b751b7317726e5d46acbad3eeb04288c169cd2f14ad2a52e3989754dd417649bb1186e6201e45e39bace9ee146f4288b236925fd9cbe964c4c3e08fee54e9e609b73442e672861a6eaf31c00516a24c3b2acb547977298a86a06bb733fe5fa49ae20ed3b4723573e5c00852d817ae47f1c82b2459daa934f21ce7a7645fdb3713d8a8003772a4464aaa7c6a12a26e9005c09bc01f303cd1f9034c32a0e9ac70760535424972aae5513a1af5511e09ae79c400654cb932bb734555d60d4e96249a87b4342272b92122a9e7e63e8a3eab34911084860d20fd03ecc789a5c9ff49c3279d799c08eb5f50b145838e7fb941091c40869574e16509019dc7c86feace39c0c33f5a2cc0e8cbecd01243dc4dc29662e69c66064ae5b686f91086e9ef15c838dda895722327521982a4d1ee7d9caa430ed5c1c04c9b80943c8373585fc718d29fc18b725b434b3817d4c7ecef5b6e0e8dc334abb65216cae1d604d1be5bc2316bb43f72dacf950ff1a2ce0b951a9656a48e70362a2bdde3716382d5addda02b14a06572a587b3de1524704055c0a8a2070d85eaa5afaaa4e5215ab5a80be98ee623677221e5c5aca14948919c8581b671885022b1837b23c07a5ddd22dcafbdbfee8472ed4b599f5c619e59d955e4fa9d3dc735a1780746d76d8a9c9650b1267e26044c124f254a53717d7c9bf7d7d4df296e873d80368a5dda3942d0ffc9713f72617217deebbcb080bc3d82a3d01be7db3390e39e6e4d0c50721d26868bd13547457225ce67f531a0dc57318ee587f2e983bfca9bd103fca99c2925e1b3606238117255d851279fab41f13bffb823ea236031e8629bc72f22986205971d5a538fff35ee67ba546638f2e2e7b9e50c398c0f4c24b660d2fc7e7f9356018607647b370a6926520d0f1e706950529bb0417b409bd7de8c1a459f6ccb150edba141dc78721a1b5bd71d424ea0ca62646d8507d69b6534d84aeab69396055b9e2764f54207f3eb6a1f4fdefb1f0319e47245b90240c5f96bd33dff614ee58668405581f572672f75caefdbfa0a1302d125921eb4b794b9f469e6a08c328afa439492d224725d7c9555cf4fbf71dc22e37c0b59e63012e1d56c3419b1b64911912722c75d55cc614259896895b8aefb5cb63e3f772f735d666830b95f4554f9526b7bb93d72cf3995cae0b8660b969d1dc1066e11fc86927e113b87e46ccda333cd84f4367278e5c450682d8826b674ac6e1f8ed0b889a282983b63607006c1920f3891240d26233113ee8d63cc8025a8b344bb61abe7dfc579a140aabef11eeaef19774e729b70cf44958664438ff3458fb1754090f465711bf116b54bb7abed8ab650c65a17dc0f739c1bb9d4038c179f75fe93f71aa762ff4f169b9377427c6275329a2e3ab55e94f452a0db41339822095fb23157554561c3d8013881d98081303a8f6544bd6b97a6fc44e8b9e612308fabcabb9062f6163db1e3fb1f2bb62a8adcb159ec4d0cfc4a020971a3ecc8df434b8302c4e6be517853be65452ade74432d87726296420a3185cb238a6e5c9fd43f66641ec05b35dc477f1ab00398154865f40082fab2cd908921059760c505026b3e5feaeefd0b6627f3b30b0a9069cce16a24c7dbb990819ae5b2c84418d5a898eef86588a73883db9405a407fc32dd06ae5f4a5ce7eab0bf9a8c86b574a51a4878f9dc607fc086dd2da00a025348f34559729c80effd4647d398ec55a2fa0b3b7df34e909b6e026012509ea9b4961a6b967260043dad3fe628cf517e7b9ce32fa6f66e1a7447dc5e182be7c93938d64f1836e7575dbe697326d5c6cd587834996f5172b82d6cda292440324ec91eb5c19b14d4438ca465c55a36996a7fdf95e0401a53a3bd299174d8d4fdc21caf79bb093d5b8e202dfa994331363eddb928b0337e1f9a70840bfe411422ca1c7f90716f728a91a16700b9958e3218c387856588a3c86c6b0e4d74ce0c244ef56b9a7ef82b3f32df712c50e02b0dce0e2be39acdd61186e0fb78c74a3ee912370830c8740d0636d8da1417a03a63146adf23c10a60786367a8d1462a412efa1fecdd8e390b1c052c5be8a4d238b538bf3e862bd86e7a4cbdd0ae59b1815e47c4a493e47972a8d3a140c88f0d19f235972c259a5d1e91a43a9e0cef7e144ca6bce79e121b72ec00fe449fe952138f43be4acc9f5096305c4bef79fcf698321d11f95e7927723699069b3cffc76f71e4480ee44f1f2fbf9239484e22912d8b0806de2ab91b724fa53d0ab937a60cee58143a990fe73642c7dab8fde1d7ce5bad9dcd78163409c9d49b220a7a74a37e94027e3d8b0b570b18aeb1be7b3da40c63d057ba4a1d18b42a7ca7cc84f3f33c2f9f25d676ee1f8575fb463d43d06b7a862c249c0d9472c562a5af057d071a6e2276f1eac3b8619463eac34d6ff5747befefb7687a0c72113b68223f124622cc4765d4e2cb4f6ee4078f1846be02b6fc58305535553662f82d3bf936efb1a8d2a12a7b6d8938b1a0ef462f561a589a6045345a2b10cb16c76c48124adee42496b35a44752bee9b8fc50483f144e74517f938505070f272480bb4dd7903f8713607e175893d997a762c20fb07f3ede24f1ae07ba8857e52f08a0bf06dc7ac4cfcb845fbb52605ab2515fedb63408f466148a5ba0e08ac72937797d92d4ec9f16590892056f378f6e804eadf1557c7a9c0fd97de33e3107202834f501445ef9fb1c1d02348f073c6d4b83866dfcc66e6805684ee1c61ac72aabe697a3131b0e466e64ffcefab56244bc514c677b5c5e1e3536feb1e5f6c7283ea527c8429cea75ba06facdecab20d776e9c780a82f2cd954b8f7083a49b152b7a5eeaff70735ce65f462bee702733339024eb5ebda584267d4aa52cdb0531d48ae030fd17ef0ce7ad97a50b24ca0da241a9e3f44069a35cfbb94fe7096d630ff2a267401c7e94969e1893a12d34ab15d6f70bf8fe440aeb3d97488613757262597416cc190b66d42141f5b0143c296da731c4d57dea1bfd00b9ade1ca976f1abb263220e51e699cec639b59ffc442cb1b8c0622e534d139a61850f51e5772b2f0f61c02936cccca1d719d6a5a57f584d1fd1671f274327c83a4099fb37472e89398c7a9e0bc14eee166a9dad12b692b9b09def241cedaf643793da5b7ff726ae154052f39aa526258f45cba54a89516a8d6fc3968b57a35784a7bbcc496721530cca94fcccbd161640c0c13112f1b3b38a8408f79159d252f84d41e0f4172e51abb872b4183fdc81c9f735f5efd08311ac3224a1da0c48d85fa8a49a72a7239ae29288558a4da1a7b253386a111840977208edd9c3f1eb9196ec40e834a7287bb63ea8a3a5c49c47c4d418dc2782f47829ef344cfcc423228c6cab8573d72acc02b24e75e230d36bfb1c306f9f80ac2f1a5f9740a6f588765b45af91ed00893936a1ab48b4e116dfb100d550d0803591c0ab4c472e01c95cf2e4bdf9b2628aec127b190e6e8785342fab3e206e55702022765dd2701b48a3d2d4e9a2f0f1697f87a1ed4e7478e65fb6ca7eafd4cd4520782fe1628bb03b587733fcfaddc1e816b4b9d876146640ee43c7e5d28b3c0d0419f7a72adc6ce7d1f35b4f8b9870450c1de24ed7f02df0b811a117816357ac0cfabd617b571024d353b31a5fe5f59b0d2f7ade98cc2f6952642ab02653840ca708b0cb077cb173043325f86028a725368f3c817428ac65e4397e144e59c69383d19fa434b5d1019d87fccf2f1835f443bc6165bc9b8d144ce6796ed8df04825f62ae16c3a5a5d53d0956f9d8e026c0dbc13c389e328857e92aefde3b3f22e46b19744286655aefb08ba535d0af6729c84841dc7064565547d46f02f5a5d176795d29f743afc35ddadaff03d61a072c80b9a10d0ace378630976c8dac81d8bab7606e484c8d093a0ebb8a29e012d72b7bdc64480c85636b0ce9af2257e487bfe7ad08119a0b63bcebd0f6269dcb80cd5bcfc9263b48a348ee4059cb95f424e9dc760ce7c2950ec0c654bd2d7696007e89950d4bce166cf07ad13c1f7709d337a5201bd8b84f8b3fc4dea695f818f726e9df717291102982f7e994d2c6bf1666ac380c598ad0547d69ec54e1f32a1056378edf8d3fbaa8d6b097e901d34d0e78a6d47b7f6139d10c0e2be55de72ee7231fb25d1e22d0c98c45d38d7fcaa963fade7fbf0334d23fa2ff968efaaa911721120b5bd71f429c2cb827eaf657d83ce2a7cbace4eb9ce057e915d01391b4972ddaa96062947708602e5ee5106a0316c8ea8af9756aa566c096936befc8ad872faf6458b4fa5e10b38656a022b016b0131d6848b87a4c67c825b6109bff4ba28edc30299d78298cc14e68399e3d5db3115747ea5a344d07a40827927d8eb49721f087fbe80bc68433247d3fd2e9a52dbd07c6d0db363d01c6e12836c242c100e4469a841795adaf37e516991f272e4e40bc73e0e799d7c7c3e2f5ab0050011727d38ddf6471f051901de4498ed2928dc57bf81d693232a1d90ce82ba7345a4729c288a8c26ec65d1b9fef0bc861074fac6259fc806236673e013a6f82e1ff65c49958062892075bc82f83a5ab78c20da9d0e8b5e9bc261e1252c073ba8a8a55d4a286ec4c8e7857a2bdf4ebd1dd1d21be76a6e799174bbdf2bd9785d9ffea372a3bbe476bfdb98c7135250060e90809f6af5ed7ce3fe88b976e0a2f99499ee00868c16cc8f75f6c4705a5d0394d634fe2665100937d61b05ed7aa574fed30b5dffe2b93d379c6b10c25ee44861d8cb36ac65b19b6dd5d5066d4d5ee5df5f0610bbe75949b3d9faf68beceaf3af9340d5900633229e85ee1c2662c57f6fbee572f6c8a4ecde30431529777a3f1d71364f196f2d658d1334dfb9bfa79b6f8bbb72939556c414a38a7d971cbd7241e91bab147c32bc6df575c331c5657100cea010ca596eaf2581206ec94a3b450f752e4e13e425f26b2116e6673d53add6fec872db57d30596736c316399e9158c4d1a37781750f657cf4d3b5d7fd2558d72eb722b3d1c347b9a57454183885c9351f5f8c48e9c03749bdac7180355786052497219feed53dd58d1aef8abb0eb53ce61c57a6981afd8c1e89d71fb01c09cff376466a18ad8214bbd5681a5d659b56d2abb31ab922e086907585636173f36d98e72dd378df607b84c6f76937f8b8536ab3e74cacd8607c00d8ab30d5a4295a6923a295af27e167ff9fa00d818c5837cd0d8551049452d0e868a7318c8ab3e0cf172a083aaddd54b4c4c56cf78e98dc3efe6c496ac28ed4aa82497f729583ca297125ac51cea8c81df00b065b91789fabe6805f18d00870590c2b0b73f16391bbd7221afea1b1f013d2c39c523d695ce551c2c65e255b70078509834e4529a23ee2aa203c37fe1e28fff014cd85dd6362609a867fb08cc149e14275f2c3a6820c007d1ff7811265f8e6b1fa9662ae95e92a7a1d4c61fb1455dd395da09b976918372520ed44215ad742d98cc3ff8532e62362afd93e4ced5115362aa8a04772aba5da6ed565527af3faa065d3012f72d529a4a62a7b1bd662ace213f81088d59d472148d3a8bee6c63fab64591067383a3a4a112e31a2a5c41662a02be27ac1f11726e9a84f9e2905265d3fad99fc90764c5cddc0944d8c6b263f69669426cad64722c95fa0f37268bb776381271f3befb8ef07a593b3fbbe7a475f03c4f8318c528cadaca5402705febca68a378f03057725a2f6b4fd5b13c7db5b2059f3c700629341c58f1990907bfb1e58f5b76856ac306763d582bc7b2b33f935f37c9def272ae726f934532d8a685d18be955a8d1cbd840718e7d3d53b95e98eebd90b5807281f33bce2ec4257f3437a3d85b8ed2141c8d62e467b9bb9151626178138a6902ab48e0d011478b740ec75df9e778d118e4c8ccf073a9f58bab851335710a2818066a4b5bc27e4fd9184ff3bce78359f613415ba0752ce65e6bc18b6b404bfb514df4054cf97648010f960df2590f9d8f158bb231a5637dbf8d9ad79ff7ca9d72394a60a67fc393cf1cab1e39e9c7a45f90d9feb738d2449dda5ef59562668572aa9354aa037515f8aba3f9a50d163862a809c61d9c5da539f51ac245739b426b6f6c0afc5bc572719e53830b40bc9a584526c66429412f494191b0b78d80710f923da5ea111a85deca5954318e52fb139d84baaa607ca4ac794bea47e6192372446a50cc6b5418b6d82b97063a5b4435c441b990cf8cd7153040baafee2a3d0ed4ebe73bce768aa16c10c929a105b9320826bf67ab114626842184ffde52e87208df5a7c92e310655cb54ea3accc70b28b4433cc1859ef258402a38ff7d4a972fe5e068c65f1c839be6d43010d8626a93b27785e38f7f608f5ca9dabda546672488b74bce158ce121132744b531768579bb9ef0503d46056eae01f49d9cfff72825c48d698daacb63ec01d1e37336b577d962c2e16bfa0445b3a65d656df4f724979d4cdef2eaaaf32feda55f512244718e246b15ee3ade1146ca1213407c70473ed14704a1574f9f7f9bc55ff9b43eb167d4972f0c3940b897bac62913ebf01b4f73bbca59b96b43b18a86933a4d6bf377f425afb01af5752d1ff2ccaa0aa2b72af66e0055466948ff0ab73bd123f286c6b43e8e8ee60430f13cb52eb9af372d0ecc2281669ea8f73e5b94d8570f80e3dc2d0c137375431a530b223f133ee362f78dc5e4913d0ec65937bf8dbd004a6948169b3137c7eac148be7e75897fb3fe47687a566b99c9b6d85261a3fe7fc0aaebced7c4c5877c23cfae1c2c25b00728312c04ac91a23e6902979a0d5f24345917892ca1023d490026ea37d0a25cc72197493edf1138fc596d54dfc034436991cf498a8869ac50ce486840dc33a1572ea61c54bc2583058647ccae2174f7be4fe091e83009bf0a202e508c2c61d01725df229db459efedc90485e31ca443f9cbf206894a8a1f46f5df1cc0f8cd43332b6d089d18ad209db44603d1388f7d8927f15dba990927c51f2a8552676269a7276298b8cc2610ca2674caf20c5ccd8efacb268fd1636d4751f731ace06a670720042d50b1a853303efa389d20feb991b38045f9bf92803aaf39d2cb23dc85f50c95839d438d19407e900ea907a55bd2caa4ed81955edefc203a42a7c0a63b83646186e96b64cd436bc12beced7c15a99a54eedbf48d6248b5820b72f3d8c7e72e40326090c7314c3a5012589f36d2466fb3a506b281b80b57f7d8ffb816288720356b5d2f866366bfa0070ce5934bdc967cd46f1300bb980b0a86b7119b715725a128bed3f6c1c2b5235945ec5946c34376e752a1c1a98adfb4d3b30d60125173bfe6267cc299e4ca9c39e571deb9e4d189b39654184beabdeeb2dd3b45cd47206b6d71b80c861436725f74d37308b51f19ddbeda9fde34ca9ad6d051ff264722f28ef377cc1e4e8e0643351be89be5eb579f30c9c1d858e065007f61fbd6c72a582b1a90263fdbc820728f63ba2f45e3fa6357c5fe687198d6c3c99789cfe7272ff95c9f7a4e1afcf40c0f2561171d10d64c803e1159120f663d9672a68a572746ed544faf267984dc2cff62f7f0ed466729622a43e3afa2457222066aad004b16fb442e5feb70e05ad226fb2f2640c1bed22ab6f2f10e5d13ae757640c051462305e68f8d4950a4f64ae3f838f112d122595038a53e3ac45ccce22557c451e395202cc4e321ae61aa97c423a2e2efa419812009e02fe7f060f67d3bcd41b72cd2c4ebe3079558b26f3e62ba91473ae3aba2d1ec0f84e68655c99d04b7ea90ab1ed02789e7768a2e759ab5e838f28ad812d587f61add3d28c80d0ecbd975f527ea4b019cc090ae1e89e209048e5abb3558af84f8c11b2fc1a3510a9acfa161f53b2e40549d1d420a836e9750c7e9bae136d992bde794a70383c146664d79772d83893b1c2d21f22781da60f811762d92d2a215c9e3f1e75f46102d91beaf13c3aaca6906a0bf80ab4aa4145806d529d6303e1563eb78c339c7e5d3e545faa726992a4dfc67cec3afee9a423a6462f4df1735aae2493f2b020cd3e6cb51b2172217c7b82694bea7c0dd3cbdd6657aa691752600f7f2b2cd2206db0f46cb3dc723881f014dc798f8e70cd2a1ea781da4d0f520eff907d62585c023f1391d9566f6782e128bef64c0d5d85bec8691c5a7a58531849a016dab3e766017a124502728ff6aaa659070f183438bb56dca5f8e84234234b51c862a6b718a5c9f5f0ea4065a6a51771c8fed5f5b3b997386f869929f6f46ad31f2dea1798643ecf039972ef7c7d66b7841910845be8eb2c9992dfb29ff77259c8c340e73457009d827b7273868d5bf78cb29341d13f619bcbc30a570a9edbffd0a047ceec73dcd39351720f652d9af9d43531f4359f876b1d01eb6aab1500539f6810c015b035d3d0ad72624388ae9a6e2a1bdc0de6198403f9ca05ae20c7e3c300be56eb9ff151686772a5133cdc2d65012d5eb27a30511a3bf0c9468a48029c083bb8140970bbbcfb600c0eeacb48c28e189714fe9b7f05a39c8a09c30c1eca62bbe069383e4109a72390ecbbe3334dd779aaa5fbfbd8f9ee342ac4bae4eb2e376bf91f00fb4c0eab72dc9c941714ec0c02fab2fbbdc38b1ea835a2b4228c59c1a52bc64c7831d576727b28dbad1d51068170462d5127bc2d75e8a484fc7b27a905675d4bbdb776a972c7f72842accd233fdc6b337d96ded41d2fe7443a3dfcae4933e9bff4d222f072ff09f0a9bf2ae9b7864aaacd54a70ef523b88c4fd7a18906ec792894f7069a4aa6b45aa99c265b24b59f343f6cfd575623f3e0667f94dd19ee2f7bc3eae8ac72a22ad3a171a37db06f3832f5851097833a6353e9c6ad2e83d1779d2d6987ec72bfbb490b169b3cea32b370349f1b64395b953851ba98f3e6215e650bbcf517722899a2890fe9ab7f756277e8de7349632cf556f6fd8e6950115948ae09975f7285eee40f28be2a2bd17d2cafc0326810caae178c151378073d3c17d0c799b37250d67ae87b059a27c1255e6250306e597f14339d2b1f2c2bd423050619981d521ccb496e17f8ef38cb191a9cfc84ea1bf2632accfc4b65cd33591bbe269a0e722649e8bc5714646a99f2d11178da7b4dfd3ca59cd6fad4e18cb62b7d0a6ff27265dffd29b5417e4034baa8a60695b5c146cfc944efa8739e87e16493e6b21672ff7eb62d0ebf1074b6301100f941d7c771dc4e8baeb186545e942dd6d1bbdf3432863cea8db8322098b2217262f8f8419491ce371a401375f80355ea1652504b308a46dbccbcc6204516123a18aa7ef1caf13b4a9f653c49e145c9b199cfed72edea59600d675f380c28fd989d9838ce78a1d30bc84f1600f32d060c5956ca2d23c6749f5d9c9b0ab1b251b54259d55dc563de1df8c10314588f01035ca57a7264c998f301cf03550944026b6140935d40bbb0164276e48c123e7150f6a58972da0981a05db189bf1e6424f3749b3da4a6b9071b97c07bd5bb8e49f0c8e90d1118f184a3672c4c8db7f40d3ba2cfb6f3feac41e22782030d12f5ebc5b4397c34543118699c679eec577938f525a76c463e1cb5112b324916148a3fda803fc954c0c05ebf755530af5c0ab21e3a02dfec6888e425c560fb80cb5310298b3d36725beda11dc6e01bcc1770037205847a3028d2dc935e63f22bdbe23885e3509672616546ea9ca8e2fd43a2ff4a32a8563e6a2331b3cf955e2e54652254140d4f3a5af2da15a2c758a4c2e4038496111411a87f2c5c9989f0dceea0856dff1d8e72468c110c7662573280c4d059f2fc8a04400fa3175bf7adb8ca2823dab7687770ba0eba94dcc9c32918e9cb66ee2096ff3cd0f0e86795f4f1f472cd5d7b51c83201ea9ee9c3f3a80ea4b9af44a13a1ec793ac769b70da8c56e90f38b97b5bc172f20effb7c2b1825289a9726cb80bbfad27bf264eb719c35cba4e5164cfba5c720780e634a478c9284faf8e6608186ae5cd3d5dc032d30db9e25ffef6cbb0c17295385bf84eaab0504097fef43adc7c385e43f1b3021aba015bf4c71155f5b1726c385f48115291e9dd45d55d98ae5fd0847f3baeb410aa934fafc154f9170772b3ff498dbf8ad90dcf9c2a485ac71810cbc120a18d545090b90544bc2a005072f630ea109f0116658b9f9f48f134ca9905b509734a9ae0b53ff25250bf27a572045ecf029be4b582b7ca3af639ede70c50783299f636da04dcd9e9bef9fd557285d438875820b9c5bf583193d6a6f197af4a19f042db3da76e6c407198ebb7340d4029e8f205820e8814109dc336cf7bafdc6bcf4c849fc25e80315181d3c172f4531585de6b7943fa735527addbcf78761665fc769aed270f87e4e611aa9062d6a654be34e8b077074e3fcdadd162c00d62b182e8534c27d3a9fdf05342b972f72c7e069c6b3ec96da1ace44cb0482c621be4469ca06d4ee8db1ced63bdd1725883c081182c406b2e11541ba556f82d3811d85b1ed6d7b713ff4f0f65731872a2df230b913f73444759d4fe57465d8e162ea024fd098489d0dbda9b79f4cb4112922d97dc5f2d9fcd0b35d388bcace0a564dc4b42ef66d45faa6a80edf74c1c2f795cd88d679d95ed4c17ba4ee3fec97794e37bdeb117cd93b35ecf62c7617296d6479f508074f31e41660fda0499808c698965695a511f773090b6c7ca8472bbbe07533ba9b70012f6b23928af76664c00cdddb926008edc9dd1c3beff5f72ddb7846b974e6450701614993fb88ad9546c15a46a13738cf5fbbe81133a825c4a4fda62ef447e29141f09cb406f622f1f876d4f66d49f577dfcee7e025b5d72801db2dbe0313dfb70ca328f342530716ab652cf9e7f33bdb4fa359bf8516020ccc693c3f814e71cf648b865f6cf32ec52713707a56220bcbc1a26383c62f872a267906962b15752409c7f8c377d666f7d516faa41f43507703f82a626a6c572d4e80df1049ebaf1f2251bfe134a27bb2384eb9981c4e2ad44a3965a9c781355aac408b69195bcf557b440836db887b7aacca758240c6ad20359fa1cf785267253a5bbb6909a16352722209137e4b2b7363f19a33c01a4cb80ff21e7218b904fa00cafa812e24d23ccdd57178e0e3f83925266bc684df0c7f748222b63eab32956623a3717cb00de6caba377e55523edc3c62057392ae2fceed6b323396403725943e17c179c706167eaeac059b94b873282aee0d40d564f59e33a4523cddc5b4a8efdc9f1d0ef3f4e47a33ddf455b1c35dfa0155dace9c7cb350ff5d3881e72bf6899d904ab20fd470dbdbde62a28a767725549b8bbed360d8f9c1a8537cc2550500d5f365862af1785071b263e37133ef1281d8082ec44c949fefb096c6a723f4f1df508ad5bf5541cabb8d8948e8243ce1d53ccb776485218a82245ec85723780c0f7dda74be1905b197ec280af33fdb373192d5730208e50e5a56d92617255ed59e0817e45ecebad08c18002370e0df4278de70c9de5009f09967fdfb472ab0314e501c422c0f593775152ad64e4ca1066f59068d2c47160708a8d7023722ccb21fdbfff96b2db91a5b1a1966ddf001f1edd3f073b57241b7efa92eaf172d1da0a5119acf6a2b8b328bc6c7a83689badb3091ef7ac5fa18b196b5eba9121481c44df82c529079d670911feb36def589811ef011bfeadbb951204dc40b17245b61e21b43042cb51f67053aec3941e5845e2139fdcecdad3c5de0d95326f62661e51fc187375ebdff376240df786ce970a28495d7e116bc21b182c437db6726bb986f480df910ed467f27cc51f1deabedc86153c0655d79de54e7a3486a4728f11972a534d9ca84823cde9d218fd4bc2bac522860ff7cb8bd0ae2e902c8b7255e61786425220d37afb4043501bdb4741a541dfcbdb66b30aac2321ebae452dd3c73dc12bf951a550ec010b56ec7b746d937a08b8756396afb892b863c9cf3aafe5fd1edde7ee84a8b4ad92a503ca70150ffecc0a1e7815163923a285dafe2025330d1ab7fa46c213efbd78374c101a47b6140ea102f61521fd1c30c1a58a72fff5629840dddf4d85975aaa6a0d23ae3bdbfc1705310e532a173d1643902c72b5d7dfbe0f0d7257839d625170f536e250bf42aca3dfa76f7a0643236c861f691989f18db88d3fc2d34b9d4eaf1757c2f13f19841235f2d197cadec94f22d872ae89892fb7bebadddb8a8e496f7c0a4fa39c9d2a407bec939ac9bef3258ceb729733a4695efde2b68684409bb71bfa1921597f3105aa5a779351a62e31fa6872124568fcfc9adebc5676d6bef0fcd83f47a946140188022e064c7849f03bd73090852577e659872a7a002a7de0a482a102de6755a5847bc02c92cf526525c42b39324bfaebb6747773e631955124393d604a023cef6af42437bc68e4dd0e83722e77bead1c2670214e6244340b480033fe66ba078cb39f7cb3110877596993724a7da119cf1e45845ae8ac102843484c7cf0cdb1deb5227f936220cb94db2d72b8605f55316c54e167efeebe17361773349698c8b1bd516cb6248cbe3879016d636e2292f1abac12281d980e772f24aa37e181d0b8c51af9f735d7e530a4627231dfe09b591d997c6a3ca0d3550ca8b5738974316c26467c807ee22c0d787738e5dde75a19cbdd0bd285cfb81633e5969c3d1ae6c22c524ba7271a44f421821efbb709c44a5153efab43f01e2a648338b16efd1d568e00f2c0888ad290faa372c9df08902dd75690bffccc5b49f37321a51e3782dc6febe059099c809155ae726dd456f7f016f04cd760e9d7246b08eeb4d19028ff175d945f389a5fc75bda3133bb3590c969a1bba95951e08bc74f26182038e04e6e9569f8843e1531a16072dc5fa1dd49b8ea6bf71032e77fb4599b2b16fb56a8f51ba1799d2cbc0d54a072433fa23060440896344f0da636bb72cc7332eba2cbd1bb295ee358a221867c28b063fc7b012c543208ba419703adee8ef9ecc5f5a24252d2eb2c6bf221c1450d79fbcd8525e3ee1030ea736e75a96338b89c420a7b4549ee765e6942e53d797221fe4dc4f6b5225850e9adf7f464362bbf6fe34d8773689f7e5b13deb2452f72460c7df0d837d402fee3947173454b1026cdcfa51dd0b7731b8169d98f77ee72fb69de55746cef78f20c37f4fd2a5fdb8b4c9a1cc602dbae1f9ecbcd616b047232c530840d4bb82abd4acc4305b3099261538f5c1cf887b8b4dbe513cce12b7294ea580d05d8080e2a023c9b013253710f29adf63df4a1da6691ca7c785d0672a25e14b01ee03dad4ce56c1b8138ec9c8adddc03b12bb7883f61f3970df42372bbc4a58e6d4803f8138d1e863de7dd8f0ff90b3f02184919baa663da8c2551729596372918d8ee69cc10a559e150e4df2a77b991e37fae0bbafa9304afa46c3c146c91303788ca35078c99c51c7aea61f8a4cb68bc6d0a6fb9d0fa9d9d7ca3720d30c68ea356a8dbcfdbb71bb40f94fee1cf4702222504f5a106931625a155603224c694b3d7b13434ae51e999bb7837aec3b067f1e8cccd8e241c99d5949472fb02faeca673df4d273b69f9c2e593d3f6552eb0ae5431d8664896b763d2b372d6d4a6c5e96e21f0f5359ef68b3ea77b09aa652ef5f5a63e73bf5e15b323777297af0ad939d38139b2d913b51384dd8bac6dd59e0384c101c75b5453b688f8726063a985c3c38db72ec0353464410d13d1b42800730055cbfd9ef395e30c104b229df51e7c1406edfb55792dbdacb2ab4ca1d1e937b512d535635d60824dec4fff2f904c80e0db51a17bd43252816d03d03defc8b3d087b688309392b912d2724126f0e16cf421be4185196642daf12736ed6f89bb7fdd546c85f12ee35be47285387641f3882b00139c857a583e6eef4a09c83e7f6b670bc23887d96762483001ee5f863adb8758b5a8f224249707c11385e9c036b582fb2441af85c14fbe3bf529abed5e77ecb8265c09e8a7c2ca2cf42f1017e021a1a723114030d2160872c1c324735173882cc8103edb69c1fbfb5c5a5e6b725850d7f29cc5e98bd32772ab1d9633e6356529fbf3452a6f06279bdce2f9e4a7ee2a1cfeee7bd4deebe133096fc371fac9de3d2589502b649818c089c4e3f55118f1eb0e2e32f23adc94368c05e8847eb6cb71e47d6836cc441cfdc8cfab9e16ad10d0601fe00aa04adf2b02a5726d0587449c10a757b9fbed9c8c6e2ee5f84c9f17a3e3d49b1b673e4d721622da15ffe5ca941cd92ec0012d088c48a0d319b80e2f7c1a3f7c84c3459f7271fe4a27a9d42425f934ea2129fcf8770504314b4156b76f3b63a3d07a7bd072502f6412672b0c48449c8614f14047915d9229d42bcecc95baf3643f55b542722cd67f5330f7900786dea3ded5d3756aa0131bc814cf3b72fd448f891ae75e0d8884b68d86e2c0692295a72729460c496e89f82a11dbdade6d092e8195f22172862caa9e9668c0d8f877b5746a1e79771d1ee23804f0e0358b843670a62336726446184c9e0d41988131ebe7c7d6dc77a505fa521dabd56c02d153559d3dd544cd48f1716a0c315a9c67da6c4545e0a0de47c20c86061495d76533950e01e472e68b5627a140061db7794c0d69d81c3bfe46f75d6e3b2ad6b08412f6d9df2a72e2c332fd7f0e370da45d5865a943a6039260316053806942bedecb3157884f72bf9bbeffc5d26d17b4768aa89d427a59521e15a0bda97d702e7108d88296e60a95a8e2d95a4854b814623e61fb13b6ade606601cce3e02d5845acf1d48d63672b79d9a5202e226e3dcab0975f408e0c1e90df01bcbf2095962b992b308ecd37297cbaf22cc0c3b1f0d2f9383aca36c297bdb55f910b7cb01a0dea03359679a72925cabd8f68306317d66c8308e583ece0a8db0b44dce880e0740a4f4d937f072ddd9d7e9bc5ab412d411c1485c82e3c5f20973828b578b93be0dc232cdc4fa67d8308ece389d9eae4f0b29dc262ceb463ab470cb84ecbfdf3eb23303dd6abf72108bfd6b0005292d5b84511d171caaec723f23e1dd13c19d10496dea95673e723e21272d10cbf727a3e43c0944847e10ab7db2da0d887438827cbe6c4e251e727ae2a91e2c50724223ff90eab7b25b4bdf1864d9be71d65559670e3d65e9d241d42d063991d25000fa1ca1b36b2a0f83f47c3edae1f14705cd189deb7984e469e6b3d39538218e2c3c896f4aa8eabf3a360ca92ad3a40e5d71368e949a9d3c175c46b39ebd06c05753eeaf4679bbb7c0fdd3dcbd303c3dc0691655fbd27f32728eea7a64c2960bb6e74f560f3ca85a83d2aaec07baf0900495af2c21a5397c72fadf33196625b6f22e16de942a890591fa32c7dff280f17570404d506f772e0278574a779eafa390f13807939e190b6d94327ee6e4698807e070c71af8f90c725398e51dce02016a35c74c9fde50ea3b78adf4175c9f486901a6d2ec965e1557ecd7ee3a5bf6f22220c7f9f0c6c4e1cd23ad77d34826e18236b873974f4c1e726cfd323e0a13d33df8ad8a7b57da66a1e0179d0cb32ecd78cad28e9ec37af572bc1adfe4cd9eefc8d194fa8fbd61bf5894d691bb4f4aa39019143b1bd7a49a72857b00f8970aab7d2226e4ddf3ba7b9f6ac1c5542495288cb777eb98f4311a72adb3e73bb3cda3dc6eb94b8f2e4bd0f5076c724d58312d9a8646eea9835bd972c115789745617e41066c1fbcd788c8578a60b06f05f996fdc68eb8fca704ac18fa10cfddbd44856ea2194078aafd060fb50df7b6ad376b0ae3aa362c45048d72bc56d0d7bb177d1326a8b36f71dca1841228b87d15b58ded2bdc6225f2f79972fe0b4d05a07122b89d77f71c07d0c0a28bb1d998cd3f378029bf7e32737b3e72d8470c141333d31f3ea10d283648d3229af9c74478331b2f3101be1d01e1b972c25238df1afd5ecc08a598a152bc3c8781aff5d87bf7596af0594d7543793072a284d798ca568684856afdb48046c268ab6f08775d0385608e9813686bf16457cdf30f781c67265ce0e91da0902a20b8b72fafbfa4c2f7378e875aef98005c721eedc02aab4aaca8c053a7917b5ad4870fe47d2f455ec70334bec96eef19737254f9a84c1c50b12798f96612e6c832285a0577073cb4f92c7ff849f09869d44fa5bd2fcde114f92a1b677fdde075b1ff7b6db28acb2ed6392423a18aae5fd642fc0ea1c69a94121dfa61ebdc5b4b3e06223e2544ec908e1055b6f85653569f3a9d9f5c6d8ad5f65a7481adeef4bcd0d2ccacbcfbd584a7c8ee699e7c4a542b7290ecc746134c530f1a1962b2917898adf364410979626eedfca981ea240d59279aa97d1a18a08646a74280570ede173e0dcfc0c58a7fa055b4508a42bfa357723bea528412e5fc0c02857e1dda193fe68e963746c8b12780aa2f04f7fe76da72100e8ac51f3458451c75d52ba01975d9d6cf5073e4c34c1a664304dabc871415fc04bcf595b28e8a02e82df865f6cf6cd4af2ac1388ea7cc844825e0d1064e46a4aae15d448f51e3dae5e14e72f717708ce0334a6f05bd5d8c1defda0a0dc97217d9f4bd6820011a84e46a78bc5284f7ae754f3fe81119a7e7d57cf5fdbcfb723c32e57f3cfbeb5d8837c6d628496663966a32c55c631280eafaf4973cf14f72d9c25d10722b5fbe8134c9cecec2cda2d4e8e03ce9ea810a4cf5cefb5b773c3b0dd537165d5a90986c90fcc293eb632f1488362473ab52045297c5f7f8ef9234f8fa0d835f5acb460144e62c12eac67462996649b132966dc3247694e72e44571c29bf2ad5b80159ba048c315e0a3cf41e1e3e223ce4abc6a338850698d6bd72c1f926ebfa16220861d73e62eb68d7567b791c654e8534f56092d003e8df6272e3ed02ce8498c961b339ea0b12549bb83a722d757c334457a9034063d835c9720a6c018eadd3034657f14d3db3e9cb1742af6490da1c172005d194f6101c25725626a5eef9c31f5366108129cdee785f23b18c6c9e93841f0e2ace5f96867d721a9d5247dba5fdec669d12f11740fa63c978475a254db25ebf4ca4d28dc09c72a172b39d76b75e0bbd2944ac523f492d71d7783e5d55c33db17cdfe2e660b010f86af7572283e2149196b6665e6128847ea018471b0c6e15f834a2e09016513f3c27f8e19cd2141563bf565fea20ee67139ab3489560d7d2fcaa1982d0dd4c72a80b86e276a261613d3a666ef1a63ba54bb406aa33777da850c44f053c3ef2724352ca2ed3f785c7032bd1a106d186712934378eba19cf338a2948ba3a353d72f3082f7901d6c49647ed61be1718674d70168f085ba05df7becf39f375160672f4371832c034cd108e12eb620607a28668a78a11324d0b9223bd74b240155c34ae319b0613d2e9203548ad1c807e4cbbec4ac769652747028e1b7f67853d2272940768ffbaa8e61118fb5b86d7f9292f2eaac0d246f425824a010a39153d9b7210de630b143400b79fbbc9053170b8780ca9082357f2fc18519c6222e772cb72fdfba867ec53d32c49caaac8f2060c52fbefdda90b57125b1e616dde69861d72af518d68d51d2004cce2999cff8965828476736707cc28034e392fbb591f383e31d15b1b9a8b6848b35e3120b503e4990276926eece64b172bd2fcb8bdfa61727e349593c8c71d87245ddd975f9d75bf416c8e3d4dafcd341fbbf6107949ef69a30059b826a5f1bf2ea1e7a36edac4ad0ba1f3b23f2e70196dc11515f7ee77166452b66b7fde5e08cda830b011d4e1570d8a4a02f6f857abfe9faa2d89c66f7248404d38efe6fe2532298347bbcaaf2c63ed06c953b2581fae129a860439d072aa19916dbedcfba7c6145d328b7693056a7af729cef0fef54e041428726fd37226eaa39e96cc483fd49fda2a1f386a33ef2f4c258a2c238ad8096482fda5dc72eaa6cb3c6a4aacbbe322270f808e628c153ee763d006f29da9a78ad979123e72944949b62b3e34ae476ec911fffc98708c1eefb6a738b13d8003886c59937f721ca79f1d52373739add0f47e21d39a9a1a89656361c679aeb423f0fdabe48872d1c8b09cb797489d06c1879352cc09421aecf59ffd6ce6378f36dffa5b2440728063cf69965367d254aff36253548770bef2d16a4b94f951cb9a0c978c302727324b70d7c2faff7ff6440437cef0e1c4e88bf4a6d81b185a31350939b5188a1aac06b69384405fcf540eab2ea1017108a8704204c130eba3c2ab73ffb36aeb22229b1d494d90914ae1a9633edb9a5a5510ae2e7d5d4ac3c651334dba2f71ab162cf82e2705d2e0d556b0116ded77e9c500a42a25d19ffa08238b68d1b3803f554c57ff86c196fb94ff0d104df83997602317b6967af4bd2cd18ca6d8d343257298049af821924db7da6ca04bded2fab17bab838ab7daae124c185e033cce4c72d18ff9c53d1fd48e35d9d77b3efc3a8d876a0511a6729dcb386462c5e49751725e720d7699b558f5555dccf7017dff4a6fe4df8dd07f62d686ba4dbd7c56d67294c7dea49dea476f2849c1b1b3a3e4fa9dd8cfc0117aac68fa8b8203f5883a507f6041b451d4b26e8e07e10830d49cd29e96a2b867451f4cf06f15793a73164c7879111b2a137ddaa5988fd86e19f46408f3a178bd6b1d61dc3f22a26096c072d82f20ac1ad8b8ad00a790a171bd0a328bc0316f903fcb5bb1b5e12f69d42705f8f7f57d0e86e765bb81eb5ae1da5d418d1b44d3c793f487478e945664b38e729ebcd4ca8687020a7e54cb584bf674ae48d738f7013e64d1ef3e66c71e3b0204f1a22800a7f6516af6d3a8a99f776ab292f0214da78b1b3ca38ff5eb7b6cbf729e8c6fc19e3c4de8f34113faaa0221470bd8c2bb9409a2fedbdabbb6bb00c572e1e45b76e70c7f2b8481af5e631127af33b94982a18a8de644add9248016a472f8e8ac0581fccc9e978574ac4e0802f7c76dd2c7b15f959b7a1c7180751d847222e6723545a900c8bee45958174c0ea243b0b40247e4b6173e3c015695aaf37278be149a3c49f009beabf2fe7faff90dfee525440a63292f4f1a83f772357872197773505b79cc1baaf338b4486aff07c2433d869d06f2bcd03649f4d2a0687227940696fe80e33c399f110e9362e3f8d6e79f645c5db481529c65df1970356ac8ba21365a6fbcaaa41265132ab2b1bef6494125009afadc2d8ab92222d2c15c77bd98f692770b3e069468fc2eb933ef58ab8e1c50906bc49f803e5b70a2c4543260376e9edc972d5fa663f63d3745d8190eb9ff54ffde6e03bf54ee0ec5f8722b44605ca7039f80b0af361ea37ca0e68eab2b2730adeafb7533f20ec4f0777251923cc7c80d7b7dc355fb2bcdd90459d000385b8b5f046d9bebb7b65c68683ec2ae9e6b77f5b82ab0447277ec9fa8d607b0d4707ea0e862fb2a20107b912472001897aabd3b73c8d480f4666b3de65e92768278ecbf13d55936a3776d6f160f61ac45f8c2597ed714d24265a9a687df10c8d715928568662b97f895feda433605bb6473c056c7e0ee612353a45ad85ca1fc7de2d46a76696aaf7ce655b69f46ff0097090304426275710070da7278382c81565deb2c9abf5eb1367cf3e40f7274d92907661daf7f66f5571aa74b17e0361f4c330245ed762b90185cf7355a69033c202b08aa9468f125ffc9d2c60a094267ed556028709acbf3cf8398efb143b83431d09722a020a5f0a179da0b5ed1a963a4022a19d9b9cf2f98d1ed72b247f9b807f7332aaefd760b244a9f4d3aceff1755867b7cef98add3b9851c9e057243183be5c440af7298881dcfac380f44a1d342cd513cc9d931f383d7503d557212b134316250110e96b4ac3dc7cc7690751546d3cf289c48394097c394af0172068098443232219aad75e740ad18df85845731f6e44c1efcdbc70beca07c9d724a773d688dfdab0312a961b7fddf006fc45e569a95804f28e1a41a5d10b8c02fb74d76fa3ca2710b02a73d28ac63b1ad4b89d7fd816993b7aaedc6b6f087a972f13d258d1552bd1ac9fe11d11c91a7239c40bb37ff8521324b028417a1fc3f72be94cde01b4b38b08fedd8ffacb44462ec80cd042a45c17b4f50c8e74897af7234f71c074ab93e86cb4299d5aa423d8d22385f659848946a411561840263e1490463257ac2a1560b42ab31cd94ed8253e004406051701290618a06408e85977230d0d7d226e635c5fc68c71f4d1b07fa17bc5ecd47a3304c449494e666659747d96cac69d2bbac427692e29185487eb9a234b77b07834ea3497126ff6b75aa72e63be2676ee0932c47c1b1be4c5c7bdf1b218cf1716df1da7f1d4f1a63cfa6729d4e36730b416e0cd19a84fd06be918c5c8628620963b309de127ca7a3d772725a4389a7a53c742716716f51f5fa890205b45d9916d7190efa225ce370130742e11d64f84cf15eb17eba1f26fc3abec9a8b28f0381c3fb4daf5b75bfe1b20206c7e961f0645167bb9410aa77114a89207ef22455ce3474c30cc8c7b9eb27185880c101d77a732406bf433cf88017f81aa56fb97b7985595cc97c5ffac0f5b464195f4a44d52ff35f4da831e6c83fc22bfb5222815b3ba2185e90495439585e72848fa9c732079bd19cdcf40d303e0578ad59d2b62bef767103090fa4a3e612720f8c9f90b90973ec1551787aa0a19ece89b8ef3e4c9ab8d2790154fb79021a6f1dce28366f55ce4aecd64c7e9bc4ba1bae4278927b8f16aaf7b2c017f981c200c7d5e35c751439f64145d06cdebb398db4f59a9b7bd6c6390e8fed76e6a3d57205ab9089f0226a20c94c6c877480b85513b1fd36bcb2cfa6dceaef683092d1725820f39dc5ddc716047775af1c20e8b2ba609bad7c0c974c7aa40bb9f7ca0c725d218275aaee546c3918b92c9d3a520a6bdddb2ee1d3e45c4dda82ae2e2e9f5f3384b9caf6e51f45b5563a4f54248524fd9085b192286538af8a20c9a254d9724c79cb33a22a4f54e9e3441d80a6b9a232e9efe723163cd20e17d8413f0ffc364835f925679f4aae37da61008e10b3aa6f8d6d89ada84b97331586abfa6214727721f1714df6b8aa2050a252b20d1127b770e75bbce9811f54bdf1cb1a9a8771a0e478bf62f5351e4430bfd6764c5ca9b5489b980d32fc2983927a5159cb612579aeacbe0bdc0fa59f750510ef05acfd02883181c48d0f00c705e1336c01e1721d3237186483e58f13f89eaf9e3c705c7448501012b8a976984f4568e7488f5ccd65c573fc08a4c169ca7919c1fe018511c682d9975d92b5aaad8837875a16726032d00b8b63f4543ed6235fff46d5883e342e1d1390198a5d4a0fa1e82cd97270ce968ff115919352592c8ba391236e2897cfcdb9a9d08c7cef1c48756be13c735809e8df15445d2e343ff5a08701fc8e5f942d1fdf2b6483f8e438096c7d7226cbae751aea6d181dd49eb3743b203dc464b1737e4863454aa4b7dcbc3b2501b3c343bb06c4601cb901d00733921218d59280cff7ba4117099a6133eb975812c851b4cf8641ca07deff1fb09c383790af5bcbabf82f93b1254e2d6e86c29b72e51caf998b964ff25e9108349756b30c9eb34693e71a2b177145dfd788e3b84674190f6524267ec3187e65ee227deb7d65419b9fe6caa748b12c00bbade4f072fcf3e8cef0531f9049299403c9e7ebf1e98f8202b6f2aa9b18bf376a7c8f9e1a38bc7782f0bb48aaaa701a6bebc5d3249fe8bc75a9cf81ea407ee990228f5e114c02690e6ecb4ff3080d80ec576a40f836a832b03a14e85a2dd2c1ea27581f1a3d60c943310bcaa314192ef31bc1ed1dfba6f3b415babba8ff0ab138d1c8eb72836853bad10d5198d6e96ea2adf6474fc162e6a01c9d607a2f6415490c882f7208d4acec439b1df6eb0746ac903af8c137731f22744f08ef7d4d40d2abbd2f7257a60834479fbb5516bcf789eb3f398af848af768f854a722b9a3fa0e3b33d2ca59d410a19c7a4fbd57129aa9e0bd0bff8de1ce1875c6579e9102461820179722352cb98f120482585dcfc431fa313b4f32f2594b9f5fb874ac2c2a8a7d7ca66000a3a92702dda367af99aae36abc890a38e2eb56c4362dbfe33b77ccb2dae27ffb3dabfbb4ed349493286ca860d3eafdf2d9dc01832acaa1379d0dc14a7ea6077706a1fa825a38234f348d9553f201ba38a5df5ce6cde89713742fde7984372999db69b4521e5d17c6355b32958768bad29f7a141cc6bc7530701ffeac3f81e58e6c10ad5323f93e31796f45f8905f97aab60ffb181297051a9d1ec60a55c72592ecccc95e00041d5107dc40a730f1ba5fd96eb0bc6b2c7eec1bf10cb8ffa1ed9a80e10529b2bd0e7030713f67e6524d4b8053f0f66bf41cf0a3060af5f6372936e9e81456a3b9a7ab1e7184071ae0aeaac7e597c0ce773761470ab515bfe726570bd4da86d8ac75f35fff956dae5fecb020c21e6b8056be55e3e38fb7c8b3e1279b1df84b6851848846172fff26861104a6c67574e7fa1724b7fcff8fbc42cb7e00580ce75cb7fcdacf60f60d4ba7086c791b2ef99736b57e3761ed51423229cf3a8d465a8f4bf77e00408292dd941bc20c3ee61cd212b90c7a322a11ed772e8e4fea8e537b9dffe60edca21b80c610c5d7c39ffc6979aa2550f783730b21b1715a3f2b497a7cdd68d395b7e87b5b896d234444bf5307c025b43a2e888c454bee787216c010ad8fb468d036dfeb926cb3b426f77fe43f1b5b65f44e722163e5552967e6efbec5dde663c16255ba6a3a827d89aa1130abf9aa4813e308dbd62d5d5f1eb020ffff9defa99c266aaa3cb1a8bcf7152fe8849bcb5e7d20b58d0721dc48ef7d9a46dcfa4fdd4c0fb8d402d4ff5c7bf9eb98f63c5390254693b5772b655d88562e6e07add1f81e53d44068a3dc0bffd477fdc6fe13c2ceddd47ce7274834a50c863bbf57e67e259980b0fdfee206d2d7dbe38337fbf7cf66045643eec8b5961881fd0bc95a04ca52e66a4223f4c9c9179fab5b29b46a3b755129f0d08aae45c101c291b760b689f17b3d2f1cfb65c614a88050c7a55e9e93e670f47fb6af4a6b0195c6994ecfc9bd26114a8533fba5adb1f024cc35d6920fdbb8a72269ac9eb430ce7017b750ea8ccb0cc3cb2a12ca6e4909131c785f12ebd1d6b726937622eb5897d99a779d196c80ecf8fb46a5b5af8f05f1b19c7e601ae13e24c702b3e4b6ed4acc8ce8d625c0a02f1f5ca54b20f1b29a2ffd81dc52a0f94807298b2f4d577598ecada615f0b25fa3314699c5da2d370ead0bcb9370e62d6f322be84bd108f973afb9eff5e83058f82e7b55899e32c7e7b91963462017abde072e4f6b2a0671b80a2e65c2aa2f981c4d0f4fef62aa893b7330bee639cf0102872a5fbe2384d83fa98f3c4601e8452d8e051d01db10e2de4ce1047b12f2e9dc772a4fc8b5655ea89629590ee18ee546e38ae7293fdd01418c0cc8a847bdbb2c372ee16080f199c0caa1205c4a545737623d0c78edcb460d147cd8b18226d128a72870115ce9ea1b5ce91f5eb5db315e2a6792e68ae95e898af471422d61dfb8648add65eb4d640691afbc4a63bd4875de527911fb508b6b0e3e7aeaef93ade5f72809538eaef6f6b947ecedd4c67d987665ed73e0c492407ebb1cf338341614e725ee9113884c6de24e53b23d3ea5bddc807334902c109d696156682f8c6ff007251be92d75bd3a6012e33c6177fc90cc52c985e1edeb3f5332ac1f7f0be80f47210082f3e4fc799aff3840ae11b7193ffe379f97616a3d44bb496c4b226a66a3d2fc747978c11fd24d68dc59f6bcbe94487e516720f6172aeef84c2e2abe17a2bfcaa891fc7f756b04412dd7229b4c05a30fa4dd6be5dff09c767cb5ffa72de72394e790f1cb801d3f266be4f365b0a87e2f5b67ad4a882183c43c0a427d32708d5eb3d59b38f89a3420730cf3758c7f03e185b85ee6825df72bad371f2813f72fcca0c4618f42130eb4576ccd0c49237e8efb188dbbf3fc3984dc9ceb47f7c72e791a3a0812ed702e477c160c88e9d1755543828126f84ca9660086679667846475319a4d26c3efeb7065bbf0e4046329ccb28027a6ce0c52fa5bb8c0abbb120af26dfcde76426abc32840da1f9308b271c1308a28ef82c340ab43a5d458c21195690345aed22f7835e714c9e7524c543334e6151e284d21f3a60a724b8eef30e86255421ca7164071269f81fe336a46ce7e1438103002f7468e8c46fe5fbd72184816dcdd1ceb9a28ae6a8e1d210d7a96a8867f3ea7b92577f2b42c36786f726fcfda2a95e8db1fb6ceb67c937fa027bb1a2915f0b8d8704b76b1e446641e7205128edce38ee0783359d814d1dbfcbbb4fffb9d254f7196bbb6dd17e79c107298b5b40bc22d2543f9e6bd140a7f0b988f9f4ea6b022720480a54005a7a4447201695e49a3dedc71516e1dbb4f119d8970f38bfa604219764964f5b35bec0c72d24918df2cf7e9f822ee07560fcbeaf0f11a2aa6115b79c52b0293241c7ab572188398a059f08c63074f4dad94c1c544497e851e3d0bc2523a698471e7041c2efabd6baebe46b62548696a0714f126dadd84b6d79ba3c8ceb7b4bc9bbe71622b41929da409784112eddc4fe4b369bb8261b61c36922900a30e3de9c73cec7c72aa1f6ef943cfb60f530715b00ad0db04cfc09424a640e7b0615f140e0d2d6d1d48d1cad601cde66f86f8c273d28441a101bf55087560339ba9f3c276945cb66d8be252d54235b4a5d0b4ae6f847d54e0f86c2bf68459c0783795a9ed4262bc72e9a403ab647f02aa6e6250286b1f1365f20d51d93ce0c83e3d950f6119ee5f72282eccc52d9d355151b38622cedb7ea9cf8a1d9712a1688b9cf9660fdb599b21a834a59895cc3058425c459d36729c0c92a4e5932b4b53e9245c4616cb3acc7207c3c37f9aca561fe380f3c8cee3e758601212220702a9b19347938bc02b4e6285a48cefeb0f22b225f53bc6e3002a1ba147d39b9026ab069ceaabced938570795930540a112efb2402383b665ae120bc973bf620c90fbe22620c9342f4a1d722cbc428b4daa885f2c5313b2d5f728d5045a01d38920bcd38da3912baa7ee0728214d6150f331c26cb5c0e3626946ffb89b9f10dd5387503413f0b47e04bb30961e41f8632b83731005221babfdc5b3e066b503762ae3dab054798b2112180154d7b184d900a79b7566bf4627d0e95e1a791249ab7e59464d76a69f65452a572a7d324c42f5d2d26cff768abe6a8da60569a1bfd94f4a86441c1d07f0e924872e27acf9d532bc93c3fac5d0a12b18fdf7122989dc33b6cbae95e9f3b6576fb725ed67d0c7b353dde5f830de266d3d4a8ac30b351e06a9772a059234edf18a11b63c02337f582c8c177f2d47233247d321e9db6d14b4c05d095ac151111f91e724d5f04a345ffaf742b97a6ca2564f9e5714410d7b31bd6451445fb139bdd131a345a716735cee26d393eca6229645f41099e29356fe93bcdc5f5ad407f696e72170d813eaef1af4e208d5a74558020d4d32c6b5c80ff3d031b5bf5f6e93656727d06ab58a837069c26e063650b50788a6ef4ea21222c234fe2c93aaa60141e72545c7b55ca7b8609eb40c9de416aaf32bfadfaa9f69bbe231e3b35912a6e385eeefc4643d114efd820cd88c93a5cf254de34998477a9f943c5cd197468957772a3df1ef769315f0b7664010bc88d4f4d0d65937726beb5964bcc52072fec4b720e854e0af82672a6180d5680839a4df724e1f921fdbc3f0e947c86fbd90c0372e01ce3ed1012e6c3ab3e8cfe4719082239206813457d7b067c09fc643d5da205c29f1f5a28dc035f60fca1fe861f616fe77e0b8aa9a18b59ceefff1bb6a172524b5835159212843f00b904106303f2fe37bfd84adba2cc839404c87fa5129805333caa065e61c602347eee52a2991d17afb211950b036361797ba0267639c072124b1ce665268f6e43bb2b920b8781136e811f466767d4c4e2fca959bd2309677a43e8e0808cef93be5c2b2985b36ddd74838ff55ae414ec00c970ba80c2c161a79a9c0b14972658df08168a783c6397715c6470f92283aff6ec0484ebbb297237f50c7b2c1c1f711a7f54e18393757de5cb8866eafffc201afc5077685cc16b3d2db2f6d27530dbc6b1d9001688873f0a5dbf2cdabcc40fb059cc907e7f777250ece459815da3f0afa5afe4180267b22e1a3fd9c7abdf595f35d246b57a0d4bb3ddfb6992a42fb9511b0a9d6ab6c2383bb69e7c90e746cb0379fa5cf96de972eab38034c3cabf646fbf77aa9c2342f078de5e9f362f237e01c62555adb2b07224527a2559960282026ebc201d587ed5b6ed5edd04792145cc0f50b6ae514a721dee773eed62952afdb18131e10faaf39e4210e2dbd46fe299231f83d962471f6d5707c6369c6af997702525a7fcf882ee1d75d9fe1a5bbb0aa43ed23ecbf372304799fe2da489a83d066a0637e079b547c270f5ea8d08b47a5f1a9ec16799278d6eefdc1131a1acc204a1b5d0f917f49d2c3c77bc70629239780f2c45191a72fb6fd91dc1deb34ca8d2049debc9305068c605adc95b607f1c7cce76658abb7232b8e55360368a7dfb754048c9663b38ace95f5c3ffc071f423159d0ba6d4772f1935795c68b93c1e6c3aaf38d02cc845bbd9b9adf149208e3a87ecf43936a72021a884ed564a62e20994abe988e7dc3fd772011cf12a1215ada692b317c7c720bb7fb94b6697c89cadad9d534ff2ec3d9c7eff5f2282857402f88ac7e273b7290557095bdcb93aeb7b8d3146c1389a7b204f288c6312ebb3162104cc4296f7278b64a08196bdfa4ee68f37549dd5fea3396628e26b772181fec82dbd730604d3ce3c48182d9f5fd38794acb28b1fb2c1b50b0989883a34ecc3a199157fe953aac51c1a28048b65148e84070bb0702101eb8ae44d6a65489cd1d0241b1fbda72b3a7a31e03253519c8e91da3932753a44a5c0f34a8cad9ff0423feec35bbeb72f59ecf98a031fcc52022121eae6818397fa20df762ce72fadae3271a220ace72fd65072e98c0e1d28a85105a72bf2921f0c6fc7ab279b5087c935288681dc572d8fcaa07c1100135b6cc52ea72e4ef6753f9cbc92026e8747feebf14b4a7f15adc9817737a946e7c93b67d59d60d929602cb39daa463349f13e32143b680c572e187f76f95bca0d80f1f1689a9a021e65cc757bc694815abea99e636f8cb980864121838567217335cc76b89e3d86001709d68d32160e105dc6215ac6348cb7265d488f29340d91173b4074e06c4d69347c6378dacc2f0ab5f5e870a623ed472fbd59fa3f3b29a9346e71e7d96119de22ec4fb5b65c70b85681c0e9f2f789a3bcf4d7c9dd20af5b1a10a82546cb7619059f81fcd0a86337813073010fed112725d2e2102dcd3c50d4ff761405de9c23b8e9184a9461099f368b27acdcb904c72c59c93b50ee0e29d041352c079efa4cbe9d493f95d91ab45499091c5dd895c32e0d608ebb9f681deb6fbc5a8d37744905ebab72b149ff92f5302a993fc761823bb6ed450dd5b520731b75e9c3deb6397cc70068e7a2ab6a1d1b54366549712723717ea5e99fbf32610afa71dc6ae15dbb1dc2b00b0b84a45f5b884297d17930fe25f98f5df599ec078b332186e2a32642d225cb09754c6bf1cd725cf351e0a72fb48c876b4e253ef09cbc25b70513e5f1e443256b2d10a0208f252028411e6188511539fe883e6bef7d15bcdad704ee8ff6f92d66f54d4467b49047a015a14726c0da162e1765a9701e1914ef3efe6d15e319217c3368781e6175cee0542292304a0dc0529110db3807f1899f6bd7c79fa7a5819d93b70f8b5d8a7680ecd5f720d84a4338eb139d4491cffd8bab4fb054ee547d8ad40010c467152e612bb357283145087ee226984d3fd14344fc548e05e0ef3a3f4c688fccfa3b7319c044d72dd46359d565a11ef7c480744d3d0c16d14e6b4935351c170a2d4ea300dce647270852f3f4b5d8cbb8982980ae754afb31e76bcf75ebb8ec4f3e3164ec8ee6d380604cc549346d21cae36e0137a3bc410ec0579d38a9795b3396728324c21b472205959b362853b2a9d3bb4d40d0d357a384212616be28af0b4021fd35648af72d71189ca02a18bcae4b1258f8b3789f9b188971753853aa605c7efb0cb6a66721ee66a2dda0722551e845f941261583d8c2d3683679c2b55f61381e908f9a64aa16748661a0baa6bf687a39945f4d812c69d4568594f2ffbecce6752e06cef2f7ab64f14b3db781e9c7a394217aed15ff89e2a08e5e34ad2362ab25c68def82ade6b2a98325b54602b147f9a865db13510083acc2b21c64121bc4360a451c934faf700504d86d5062ce49d93ced47fd4841a54790803dcb4832ac7e4a85cb4662af8670a093ffdf0bd55e8292e1db3e6d23855df2428cb075f6fca7336b65e723f5ba36fe7903fcef709bbd16e01b3545f904e6ed112bdd0b9d25292ef876a5f07b9857c8e2862189921f54d36c45782900a625cb50d55fdc67d25f3bfa0b70ff8bb786ca724a08350c432eacd54dbd14b46a1ecee2a243de3aff7d6e0bd556e72851a84e9fa5981407f76173beb571801d3f20b758cf014b2a1b5e036501d4f70790b8bdce84ec322d904d325024d21ec50c9634ba7fb10be957f26a7e92b72819d68a3e61eeab47a973d37354ede3d79e56355761bd0239f601c1f72f92172ac015149c7ae1f81defb58a75a8b9cd846e03331b648e759e978aa42b77d2e7274b32aaf7a17bd1f75e1fe4a2f56ec39c9cf2a2d5245f9249dca8fd456050c58ebce78412c83aa0133efc9cdae2178e9235a9f9e48801f43b1ef31b2ecb1cc7273331932035e589800a2599e6516e641688c0ee9c365e11f2fe51e85c7a34572299e3bc2e6ba8f440beba5044a113d4bda2a004066de7d7aa4932397d79d16726562923f78beb2012c26c65e9bb9efa93ab49848509c61ada670b12fe58398416476a49dc36f78b16d14a943a65c4017657f371effa69c00475ca4dbe652c3070ad0bac5de6fb6dee4446c93654cf21d236b5133a5138ff7e3f4a8d3f66ca072704b27fcdb5c255e6cff07a87db1254cf16b94644ccb062d39a3edefd0fd0e725612fff9a0efcc3efb80e0ce73943ee84c046ffb8b607a66e45df2cc290ec272389ded50c305e31a87803b67b60d95f0d4e8b39ff2c0ce1017a664247c246042cb23ebb0acefd4fd936e5e9cec5676b89b74458bdbb4e767f73735750361430124eb1f8efcd5f73aed1d124e0c141da7b70ef06ae4208a744d33a1d323c74d72b90c581e6dbd1f319f4edb52ad964529dbd0a2c49696246d79e5289eb836c8726c720d3180287c76e2ff197fee3ac120bb755d623569c06ef390ba5a1ee817721fba2ac245ef64f9a1f109d514f8aa15158536ed505d2c390206cc1d0b456972b6b2a8e8239c877bc5ef4422df374cfdf7b4eb4bfab66db326cfd841a3fb0c72ac67076884a910f716ded9e9a678bc39bd64d2d9266ee1b9e8ebc39367957016138478fd1ca5079036446ed9f013309afbf0682d577d785b4d8cb8da571b45660649c83670564c85397636f03c5533078def5717fd7d0f8ce5fe8bae3e374172ba5666a74ca74e98ae14bc86ae5273da33c87cb1c82f61dd7e0d27044ab0dd208d909abd2e2f3d166d5b1af9f59621e63bb8597ab49c1f0c5ffcb6c1a7ce99720ebe24134117417d3556d03fe88d5ce4341095d92bffdaa651a7f54792a0e57205c46b17f6fd9e31e0c2d5911fbe55617e7247254c18f0ae3f3231a329aa2640229445870cde379cb4a0aa8960f790f080d2f42e847f2888e0bf92441df6a45704ca82328238776598390d07c1633d57594d727b231f1a4afae9e7540605e77270f1e0924e18ddb2151d9015f96b89ca83ae2083b18845f1446528460aa0d172e54b539c6ce93dba996899d1c41d640926ce5f443899d5a97425886c306cd87221efe954bac712ee7224d3ea051a677f7698ade8125fd6476cf652b54c9aeb72ec4ea48a31a3a590b28e2cdda719960b8385d461d39956333c0f6028ee37ac7261ae5994d97d21c72dfe9417ea789ad6c7dfb1ab8e80ac725d7cc3cbb052a27266bcdcf53baec19c4375cc4080a1c817a85b7b5aaff38658d236146b2a70f5720402eea5112549047db618d0413a129066e5c0b91af3c7e04f4d7f1a88c02e72cfc2b8715cae6cdf6556201f8e79f7e7762475b18cdcad81eccc021e470d5b2679a091299abce42b3b0ed3a5de9cb05ea6e6b5bf520ff1bb789f71089123d2727f5fca6be4fa08173d410e8ff8ba89ff5b45969d105211f1c56796f8695ba9721088a5823fe825df7a1c93dc98afeff58ae64324629cd1524c2c4547345ce80bc7a0e62c1a4cb3f363d8ac8c06e7c84b6c8bde42205411d0c29e8e192218d872f72b482be24ca68976222a039da22b29d612ae41eee660a294f08ad4864f1e6530b7a125adaed36113a1a17bd96337a6b8e93262fb597b244082b788d283a87213226e4ff201bd376bb9729f106fc8bd2852e9cc3788bbc2218cd491b36cce72c802ecfe3807c84660fae39e87691e319e337f32f2a86984c16be246cfe763729e910f5f68a860f0cd7ffee84829c459044341e5dee550a05ba7fe16946824727b3caab57f2633736497a1f49671e720e6e0a396a658ffc97205621e747a7b7251f78be6757038d6cec7ac648a128b39ad3b1a58ef623128bd12c3b4ac679e7245323dc50348f43c98bf154917ce5bd8e3a47ef3762fc1aaf14cf3c9d2c49b729c917ac90d8a115abf2ca241903b24d7b8b94bf8baa292678749af9c9345ba59df1217fde1275e489361e44680ae144195c4b7a714c04d422a4be7fc75626149dab2ee7a2f3d88c385596d8159cbfc3cc0fb84c4b2e9251ce72bf822f46558727684e6928aa03bef37f33d376150ece63499fb71192570a7821e7a48eef7ac72c01f773c59e5e5a9f7397ef9b24b9e7d4439919582d8ae5c58d28d232d7ee0479720f89b4d43be371360d875cf4c2c1708280e76a05ab5fafdb5987683d9f208cd7d29c84e970ecdf315bc17d953da7a7f288c680b5028ee24f2e68250c84c28d212e3ce7b451e41a52cee8c00bb609ba1abeb782e331c35256127648946fc72299061e00c0e817d6d614a1d409d54f9e3193ea4f92e7afd165092cac8337c72db5deb444c792211d356efcf0d4a10eb01f0d843ecb5e4441c7fb48a9906c60b7861be8bcb5935291add86403ec4450f6dd0a390a73676843224a9c258db2d5809d6cfa5e6c7b0f2f334bc9c64435a62e7c89c872fd874a5c6411471f7a22315aeb187aa7c304e21ea66dd45a663a2d73d9782514ac84b48f09f3e6af34e8f4c3047cf43ee791859a6be301b8d7c5a0f6c88243bbe48f305a0394fc253e30223dccd34541d952daa724146451c451600e8b6bd349c6e209395d927b2573cd1725c7f5264ccee0472b3ab47ccc654c50b9f5be745c2161f7c41411167937cc14e925ff3ec10fc62a4e9283614434985937498b5a398cca2c95286174aa8ea531e985ce1bff670f9842f614b194c4c4373e62eea67a05a2e80e6392bc2c4a73f5dcf25dd98965155c5ec3ba03f86f5723c20c3c6135637329d5040393305603342d857c0eff60e2bee51075edad2646147710145e9af5d4828019e42534dc09e38ad63aeb7f3628912e1d4768735dcbe1a88d0252d307734379d35f51dd7358667f50120b8e46b8376f00379854e087de58825654b72ac13143dc7e0fd02cbe472fc507a545b44687d7854a10c28a8f2794fc293521baef58089a6b2ec1419b772d31d1cc6c8c4f7cbd0062c90ca66c069ee27941c6a572282f81e25dab76cfd725e06a813dc411ef36bb33f6a9757cece6948cbfdcfaa79e0b94f542c93fd983397e5aaa97c3c8e01ef8f82b16d18941d3ba3fcc6697442086b9948790476d672308dc6eca974fd00d1d8103a84dae9922b08db9970f2fbf2d5bff2e5ff949f0344704e3788cbac2fa7bf13b6704c68df65fda0e4c4b266e3807f24564a3ed52b353c47f950dc5966164171d5876697010a011403a3260683ba83a187a80aae72ba55df6bc413593ff5027f78403bbe6965365f69256c7b4996ab9f2dd128c205e69b817f738b98a1658415cd998ae5e2779761290d0d4fc3e895719d2c7583720dd72ba4500461ad59a99b73195b3ad9c3869d73f0b326b9187e0525214ec44b583be9ab436f6108b795b242db73dfb4c8dffe5f722c6e678f797351a0350772b71489ad530023879019fde902408b9af49ffe716dfe58190b0aeca7d2e5f072a67183494e5857bbc9ff596b77e1192dc43ad85bf195b78a82d16a2a0b9d7b012634d075d567c73319972af15c828b535c4c73cc7cfb28d601ddbf6cdd17ee72610376e1b0066582d225d17b12ff10155cc95963109218758cde0150ae6d1c729f4debd0b60d23e160bf4ff9b1f13f81a23f4c7bfd7c13d5937afe476d5165729c323b5afe19a4a1b61fd6c85c15b6a872631ff7674e1ea6c6bb435b782bf6729441863e8ce17574f45376d2c6b56d75d2b6e315d2ba92fb58aafa9bbda7327247f8a08423bb3426a84721aa100f30056146770683ccdad2cd68938caeca88078246b4858faa4cfec41a52b5b25396628e69292c28cb932a6897ece689bf6814454f4e11e1e804afb22083280e1a6d51c50ad7cadda70c356e26e2cfd8e7b0493a21c0ece0b12a2e4b4a3f3600ddc2585a60bf4964c0cd4a877b177a1e5a0072efa9ec46d216d64140854862872b0039dd41026f111054810123c4a20cbb7b7292b33e6e547469f017b7f4fe7bd02e715d586c742d2426918b32b2c0ee52f6067ebf0c2b52614a67ec9ffeeffa1dc84ef607211c7fba7099b506d1063604d672bac9911787761e4bd353be5bb90c7f59d47378b44015df9d8eb35b55752af3728ffddf52260d36a0f769bd6c5ce8f1cd1ef443ae3b0c0457298739809061b672432d97cf927112ab09f4122aaf31b90edad29aac1e67265cb014c34f4d9c87723b59dd80b21e70906ef404563cb431e8d14950b281f9953430b91524ec016b3026dabb3d53a70f33ab602fc6c946f5f8fcecedbb4040e1f2e0ac542227e3d10d60fb63d935c9d0b5f3f88e0ab15616842c5b19f36c8acf66f91ed1698c0568727c909dabe4dd5fbb2a5ad400cc08e171bba63116a0105a1bdeff718a1b62e1154e6a476c07fd26bd731ba24c77ed95d85e311fc38349af87517d4b5c11f14f72a5e66ca354bfad2a389ee71966f56bebd91667eae26b20ecdaf9da02fbe12d1f67cde37a3714b225419de40194673ac0fccf3d46857c600ada0b00022d015e7208418f46b0afe61fdd664fbb87a72b3b176205154ab8b8d3677451719695d972ce24f77ae8a55a83baf0d57719a8c44f6e03f03e845809ea7a478de30026dd72fadb7db0be3710a62375bac071e199f5077f3a2f94673651bdd5f5be84bac837746ccc72554242e420a22f02328851bf489272551f5d43d0f713f4a4ed55700020e094fa3e01079393d4e07415f7ec58689ec33a11d712d73879066f79b5b57279b2189d2a61811ce7bfddb1013e5fbeb368a4dccd9d492d172d8faf819d8072b5189dded29d0385b22609aad4fa5cc0cb75640dc1429aa6461d5ac89be4a772fe7ddde9ffb7ee6edede7a6359070242620fa6f56234a31bab4e61970dba6c72e45903f8fe486694893967243aa9366b73c2781e516099bf0b96c9d775a81472a80413e26e3105d1ec3345e7a0128613a0c9f176f7813b1d291951f0bd56b172a4a2289923f72b23a7faf7a3eead98028a0bfbc6aa10586b6672ba5f0ec8ab72156b043dc6896c675e2374f662687a25083787cc6974f8557bfd8f5d324900724ab8a751ba95125faa342542eca515ec71ba4dc3d9a0199c7ce42ec0f0aed410458365b70b9ed59ee957f714ae4c4d227a8ca42e3d9fb9da4894c99442b1e457be2d67bfdb44081e1765de802a628fbc02a46bd0f27d6615ac84a2b16b412547226210631ce65fd9b2dc7ff9aa5281642e51beb289e51c5f50089b21d4c97072ee2508d92907b09838bfaad4353b3fe774816c15cd8e88a25786d02fd365b770646dddc25aa3d8e9aa6a00a3a4ce3dd06e8e0199a82bc66027c6a3576073fa722baa1cfd4481e2aac3f647836e1d8d337b3ea5cbe1fc1c962d56fcf9a05c4f728e909502635de17cc03f2b2ee983e532c842cfb01a8721245e8b8fb70d8b9172927c31d217f7e154b4f10ecb7da4d31a59f1479ae97477efb46759869ae6fd122dec1d5056ecfeaae8f1c2dc61c4b1c2a6e145597f5fc5b15c9a3918d68cbf287ba394aba43516872e50e47a64c0a51b3cb142e20a200a03c587a260a2d3ee72f04799e09b0792c41a8fe3229ca87b9e8b619a60357c50e4a12c8cee1895444d6a5bdf2910427202e4a786ad7cb2704229700194e4057adfdf5e229d5efc8a1500e4adf839adcffe36c09dac320cb39268ded80255c10213cafd4a7e6ac6d352c669cf3a1cec8cabb47490ae0a9549ba798b6540eb47a6f1cac0346fdfc8b43c280052e0c61a93c57fdf99f765ac89ecfc042ad29ab353e9c582c5a1e0b3b672ad65747339d9cff83ab57383d2fe8705f5d8003065a0dcf9615516323ba5be7275759050c1483e3ad41257fe5aa49d0b7df9942dfeb04263a99b3ba1bcd561728163f9fb12cc85a20c0f08b1f510b127f41ad752a96dc4384330fc7e1104a9724286c2606c9a6178d3d6ce38bee6b0e1970871a7b0d573f66649a78ec2acae72a39419d3c712a5bfcdd1e00a5024845b3df2ebc630fa68ccd3be2a9569068872dcffc8ca350d048e26d7d91957006ef57fde3784dae9f7beadc1b873079ce8724c29a14776e5503c26416e4d44e8608c75626411208de4558cfd7e34ff4dcf72a5dec495027fd3159939dca2bdc34fd4420ee58199d376dad32f8e5aa40f0c720eda219fffe2203cfe73a6daa9115bb6f8239c754a2c90c29049d8b55e138d46c64506fc581f8c00ac8c4f5ad5905fa0ed1a2338911e08d3b3d2b2cc09b9841e9b0fbb6d22aa670fe58e9bce507eebc08af3a1e353d09a25c360a4dce5596f72e2ce7636250ded528c6a00ac3dd8e160ce6c426f05e28ce632f2a557fd881754f3aef5e007fe36de39527d0fc40c47209fb01957044d1b065e30580bf8787a6de5679e000f3891ccc82fb7047a1dfd6e101cdd9d0e8ebc2658f8303bbb996459bc9f4c70841fb9727ff598b394f1c37e70ce390b3fbe90769307fc8f1f91373b4efd815972c5dd23f001ecebd157fac443f256ae18df90cbef0bab200ffb9e72719a67ea9ad01d97b5e634ebac13fb950da3f51d950bb510c217cb18afd98f72ca975768be5c3c91a938e45c025936769f7b2e3f77a231b63f799722e62da572e058ca7fa22a12e656088ec3942700bea47a68e611446b2b127f48b6544c216be80948042c9a9b1aeb21a45582cbdac6b35abfc58f1b0384f817f731b9360972bb27dc291fd1e5452720506d17f05a053a87f081f7e72066d435e0a84c00fb23fc3129430dd6029811ce0cf00553ccc2f1b25139ac1de03e6076f851090cd172522d4ed7e686412b60712279187a31f4b67e4da6b9e5b5cdd1b1d74c4183be2b4914fd67c41b81348091e6fb450bf18f02339d7e46c008aeb4babaab39f34c14d3f740fd0b521ea5744bbe258e5dccf01680474d7fb065eebb8307ff3f499b7200ae293579f6ca64d707ad3475f64d4b3a76206adad44f9fbbe9a8f871d1ad044ebbc8c6b718b6af10026888490143a40c0fa3edf631febff4709a76da389f726740fd9aee4a174f757ed97fe86613630abe6bc056eb00a7cd907bc98d823772e604b9353a378f09c3d9d6130c817cc5d7767c64554f1142cedf547285f6b262803d95809d195a02460c10e004983d804e17f7259b0555cde447418dc81b5f7236bc608e481ed3deb36a03ef47f56826e667f7e8825f85e007da4ea777a27072bc7ae09d9ee382b4ec2218336b55d443b8488b0ed95a8c65a3cdc016622dd5728c62e2d1149aa08fa1ef6bfdd422261690a4777674b21e5c6d7c1c5614fa90248aa44f83bccf876f8c423647718fab0d286ac525523bf7041923a2e38ca6af72db7caf28e05128e0cbff2fb0890aa42580894b166fc26313c06cf647dd0be2446ff41e8ae891b16d194e3a66c9a9199cdd1dad3f946383da8c2e817559112372dcda16ce4b1ed37d54d8783ed75b4db9381e4547c452a80cb0775cb3d17ad517da635a69a06fbde4f8d2f0719b066280b6c61d67a44cc7b072325ac73891b42cc18e45ba1c5bf8bfaa287cdd9c342a45c50ff3c0d8ecf9a3f396c7ea19da83653877b7f3939cc9c139a74c858bfb140551eaf191b2d2df88c3dcb544a4a9f57262cc3859c08c1cbbdcab023f0cd29d8cef5a153a36b452afddec75a22f6202727b8e84bd4a2ac21e1458db92694f877c4f51585f06851ac4f97fa59dcedeb3177ef37bfa0b71dce98f599838dacd53ec0458a8af8fff20764367de508f94ea72a50e9c5750224a58dc36d864b6fb071236b97e59447865cdf21b158741ed55729f3bb0e11b0c26d49cf01d6288afef50f72e96206d8a915fc6fd61abd52994725c4f9af4ee96175c712da558de18c506a5170204464b52881c55f90735a7703a5650a42806779fc09d56a83de31e4bc389e45658a8f618acbad4757776d0d61eef426ca0555adc78e3a86dc58e2933911823d4fd1b4988e5d927c624514459724e1463097528382f3994ca93daaea940fef552d566cd72e23f289ef5e1b894729a530f52b7c71f94ee25f0ae7a462e5d0b8333fda9db50bd4ba59d453d248b30e9c5b3898db9207895a3bba90cd1aada18dcb56df6f3e52cd9e0dc890a7bce29f43a97c7ac914888d9d992ab501d6fd4665e073e6f922cffbac7575c97402172e0a1de71827352728bae3cd5c594adcbdcd75eea09ab5d444ecac2029107f172aa4bdbb5a719af38adac8ff3266146bef9f716a155243c253a31d6a28ba31372a02c1da92f907a445b7e06ca64dd188b3c7b91676d485ba650d5079f66d1c55445429f4356dc52dd28c7b62a52373ca173cf4b0ddc7776cf73882c82eba0f8725111a80ac79b24ca81ef206e07b39a532cbcc36bd9079ce8920b3768b197e555ad73c64d95d63ca6bbfc4a0c20fe07c45ce6c70acdf3c31c833890ae0eccc47271a6d833cbb89709681fdbc29927a8668a4851e369ccae35ad7f1558a65bd51de7231301af41e863ee6dace8b9fffe93a5c10ac3c4aa30576d03d56b153dc47260f393fa4d5db93a0a0e28c244961d9f5f0f671632c32f2c2c7169e809fbc3725d83f58efa1e703e28d62eebf753b9cf454443471273f942d9dfcc9f9ba4d272269adda6ef9d18ec89fa1bc1d398b557eb01d738373468bd576d25984304bd4fa6e8cdf8d2095ce76ff6f1e752f78de0400943838b389a0359c0c8f28b99ec723a097559a701b0a7789e4951775ead778b0dfac8f8882a0bf94f4e331ae54f725fb7f14614b70f33e2f9e4e1faa7cfe15337b46f4cff9b0873649d85fd4e9d5454351211a480382106c98e6db5c38600eb42d0d0b2c0fd276a43d08393c335727bfd9e8fd72609cea806dd51e2187c9d74a8a39b02d642598cb51b2869014772c9464c836bb0e492d983e5bad55933937986c44e92d32d3fe7907666f72af51c11fd678d6d099c5f5fed8593bed7f72fc59a934041f168f6a2cbf7c8f30498727c63bfef614d254b9d71b22336cf40f15c235ddf463b2601b75b42b3a46a61723945fa57f2aa853168619a794ceb5d83638ac5d960cb1c162326bafc81254f72da045ec31762165ca441507b16e4a2f4f94ae1bd55f1240b8e584369da57f9293ec0a4085ba1e6814883c5fab4ebe25a401929230ca65b555673928063d46072545d4253dfd5f49fdbd0f2163af1412676667a47613e185299f3697a725cab723032d98063b3a4a23da724731e3716e15aa4b6645c3c69722cb2376bc7ac7c33570ae6dc3a17de95fc1aa2a3ec693688b56e77a1bc76a430dcde0ae42f9f085a72961497fdddcdae6353b30886c8e904146c5cf3520e284797742590e7df2a720d547fb5b0d1c2555f9510b8dad050790dd28869fb6355d2ad2dd6142b764e72a7a665737fda4f5c00e06c06fce73ae442fb826d812980a45062f3c7524ae15cf18bc20152ad2b6d59571ba007a1369248dc0d71512ae99ea629773a876d0d726871b06c01d4809c39519b10ecc8f23cd21f7feb39ab554e8799e19ad1f9c97233e0ee3fe6fb536c6d3bd043729272c31269d41785132675d53c98e59410dc46297f0a5a409d103a48070814096743dd55547caccdd41e7079439c7aa7f004721289d0e872cfc05927646d7651888fada119b0e5f78f8707a355b97b1379985b81796be21c5a3a676d002f08b84c75b1f7deb7cc60ec396b003460c911011c72c900cafc42765de80c350a8cb505b93b211c6d55f2cf91d8ae2ca74d688d6f72a6e915066b2e81ed8078864db2684fa93d18ea520803d69d47131a8676953a2d6765d8fcc384c37c5c916059ae206162d27ee6796d04b7025521460b30043340c9b210663a8f3be50df3f9dab5f61d9de0ec688ea8a91b99a9e33969bbddf21a4711d5c5c47eae8c04af9d26f73409a6dbfc0c9f2bb66360166035acd4a28272b97c27a3ba4d2e6fd4066ea21300d62bed51a72dea6ff2f855fbc5615701ad345f60d9f3912214c15f4df4ccf56e7c33e56af1f07bbaf1b5b48c7d94893381724906fc10e824500595eb5e64351e7e854bcd4caef97f0d0e03db40115f448e0c5fb718bd9383ffc56218648e43b71b6a6418d446431fbab3722d157ac9e39c00bf7a753276e08353fc761a27746e4e1a6fb678388ee02154f9dadb0957303272eda542f62e71ffdc16093959251cb2c25929df9e288203cd591a92a0b02cf30482fc39b92a2c09375c390cb6e80030effcc90b6a9a9187b4cefa5cd2b932ba728181ec81b1b6d9a7adcaf320a7664869243304b8790e04a58ca4c6040524dc3e39d95c27af2c812d0ba6215291e82a196cceb9f5d7a39834cc72057f4cf25472c994535281d949bf70e9990a3090f064ca5367bbfe0a60dc75486c6860ae18721c577b3590efed752f8b929d7dceeb75d1393037a7b0af4a09e75bb9f9c913729370a071b5c2e53512cbd3cd92077fd614f76c91217d138c0c802acdee918a09fba616486f75ff0a865e26d8363f8cc07fdf7bfacfffcf0f1580a959133e2272f5bbc3fb683e9e9d674d62bf20b58cbc78f584759e4adf204e8b9f8a0220b4722148477bc6b48259586b8829e3de52752cf107f3e9ed22e90110d037a26475724190b25351f17a35001eb11349e7f159ed7adf1ac086d78c8d8fdae45eddd27236b85cade4e9502d784a8b2d25c592bc997bb17c61977ccb490d04647b3c0b61474e67842b902ff7487ce001b128a4418655eeec2a3ab5f73ffb9cb08971b07240febfdfbe250522227bb8e8db06bb90dbe362ba9387303fb07d298d24a06172be902ccd71d648b9c497f50d2cf6601d4716a847edd849fa4a49e6b9961e4272dd435919158625676b25a82190401f0505476e122115278623f9c7448c197f722b45b38c8e9c7bfa7048552279e756cda518bd721a732b67a0f5b41dfd44bb70455fa48884505562491ac0ccbef6521bf7689291585d489b0d724849731f9c7252035f1f9f45a37d4e86bb1ee38b958ee3d25c36a3da25970660d3c6b60dda726343f2e4a7db27dfab5c2d7952a2d42130bee7f64e9848296518ddb43320bb7233d5498bf78f026ffa26fbe07fc3d0f5d63c7c0aaeabafbbdebc75f0b626e2721393fe83be88fa88c1b57cf6f4b658ab2bfb620e28b172c89a86f0607720b938c12cd6de04ee74e2486d5f7b74e4af438dbe27d4662c4212b3f6dc922000fc72d5420a47382c9f7669c40df7357c12ec02567a778dba552cb51b89939f7aba1abd8ef4fda5d151469408a35e4a1a6a81e981d18cc8c659ab72b6b783b9e13672fe0af416369510a01376dc5a1303541ce5f8167c38bba870ce1e438e2a5997264dc3a26630688227170779aa08a023b039576ef51c78484d329ba193ca09b9720f5f29a84c7cbec65eb3790fea3f277a796a06832d93a5e61eee530e1cd955577241f76d95201c1db3470b062a3d3bc9b5458af186d1d027dee36ba707c08947fcbbc4992503158ac3f3d752b28413ca02933cd5cb1ab7312a90843f575a8772534a01b659654c22d95903da6193dadc635a2ccabdfffe4f57667fb6674cd45a199687803258ca49abe6f03600be816485df749cfbcda834f351f568c0357f72fa03f527323a64ed2c381137ed671401dd05166098554cf13242096946c93053dddafa8d27422c2d54d723487aae1778287dad60491a3637bf36f949621a13728c252d68ed63dc8e2fce263969fe45b6d4767f8d6ed73cc5dc92aa1de4f55b45ffa195e244ea59db49dd27bd9df64569e5d88cb4963c1b0a23208f175122a9432bcd6e2b615ee19dc57be912d29953a24f26d2af9bf4daf857a9ba231532e272d8c884e20810994864e84110ba6597d4f2352fc9b02e88b74cd703008c36b6724e69457e1494f91d06a4d5259b7d1dcd43bb72c48f9e0ba65301315a1a03341d33f7f5ebd75173deb84f13aadfd54e9ba021aea8baae4a07478b7d332b2a56725dd3f43952b27d58e556b2f391681a14d31c70ec7fd927a0d71855def878a57283be5237ee30df2c85d9811fec5efe0462366f09b2875183203422a57acdf17295716ae8f1aa424912a3042bc8b525b69284a791f04f427e6b66f87e00df0e720484b7b065ab898663606d811aec21d7ee84ed5c2c9d0803963d6f7b178c7f2c4ea306d4a2134c35d40a7faf673e15dff9f1741134c8efebfa0965eda1aa137261601ae81758425f099d3a6f031a8051c1d4cd4cb8aac7a2469afd087325b272cd4ce32c9b72d17fe5af1bdb2825717fac8b9c34cede9f64e2d4d9dbefee852d870c25a035fce6df8e4f964b90ed985b828db7d09b423e2264feb292ce43657206f466bc23f1b39d4ec4e2a8e96128995cff1c3c3d7a2a9b06c451670175e472f4b0a8e70fcf15b840d4df4dbc4f94f9d3820d317115cc0ca74fe09888956d720ea9326afcd926c9815fa38ff8a2629a404d8d6c14f4a1ff35b6880302516172db5eb90159a54bd7f1a1240bb3eb06abd8ed1ac6b1416351977864a1f82d0b7234948d0ad236714f2ce75d00360451425230554bda4112c699f83e2dd7eaf7728b4078bb6c15fecbaf883a861d2e9933c5fdd7787caabc9925c54d93efde7b726e425f7e98f62c66d3ad033b611cb41511e9565c5e0a7a881a08e43b8bdf2a726cb831fb29bfcbb0e18ccf39ab9e6315e1c827dcbc60fd63ca331facb40da87213aa07e1fb8a561857c5841deed51454e5f5c756ef402421822428e302409b7263dd7a15d4fe7d49ccc4368d3b276007519168afe1cef92bd1d82cc155be711c2b608df616b6b458c50ae754523cb9f939c805ae943a8d500310f3b6dd3dac72418823f333725557f04098d7cc2184b327949aabf1eac38b280aa9be1b48cd72f3e61a31fef7324f8d662d205a0263c8ea971323addf7a182cc6302978d0b072f0016def8ba2db32d8c500a30b7ca48b46e163d7ab57709eacd73ba2b60eae56e105be0045bf2679d13ad7383eb4454698fdb874d15f427a9ec66fac0b45e9171a2ed04ebf4230c325b1d4568627b3cd58429611b4b54fea563fd47577fa1472c877529105f80049d05d98c201c695774bc5d97126712539fc26810c1936b93876cf33941730af30d2bb77bdbbf47742ed3338d5fdb719df18a4639147e4f5725f6df39fbcf79beec22790104ddd3a6df39b8038494c27dc6ed0a2453316f25ab06e6bf88fedbf682f02fdc5ac34ccbe1699b0ead6cedcde2c87fda59243374ad6fae1b00e79ee9488c5d352c3a83e427c417a81b32dbd3ca3f7db9587a142203f09e28bdaad718d014d83e248419f6831b1f1da7674288219826759da351072a898604bac7073b0ac5a5170d1d6e289ee97ff8c0dd3ab341bf0864a0c37c172d8404a81c3a471c1f130bf581db869229f724cf588fbe405da243274c6734472580a8f75f2f529a123bc8a20e622b0bb3d96616bdef984ac57be4d326144035997ad87d3fa714deadd747c795bc19b98840acee851f309a76affdcb370b45972c12119478bdf22efd85b6c5e7dd23720eaec4b3a89c22f6713d6614965432372522f20ebcb69eb5f4911ef9c7c175595acbc4d6f040f435ffe09f62f3b7de40a5f9a726ccbc9900a22a67d68f995600348bbfa85ad919776bef37988c8d71f728d0fa8289b9af3170f7c5559ef23266a6bb487ef415f83058798d24c646953726ffd12f654c8d609b2d0865a80dd5e12d8798c771958a07b080225f6c45a4f72869aa8a60f96ecfe65ced6776c0811cb51fbacc9adba7a0e86d67694a7cfa9723a9b5185ac4c96a4c654943f350876f899119c956a41d31bbd5fbb21bf22d407b658e519ceb41bc4fea57680365583edffda864feaa2de423d6f5b000ee0f918b01f999aacff19e22b725916a36fc328b025a9d84850cced7d5544936a1612247c967dbc8a6c7297e3436a5441b82a20351db982a4bff3c030303ce91bad73720587ab4834b5b2a6de0196c4b4fa448326a1fcc9e9ceb70dfc160c7c51510072d518a1320fb92634eae226e52c5a366d402fa9f8e37c8ca26fcc316ed9bcc572d97ab496b54e9dfc5619fabab722d64f8bfad48072fcfc4eda1dd6c34755ba191180b106c3359f4f15499165a05b4953d7eb637ed482a38d441e9170eec0907222c083784dfc7b3d52b67573d63281fdd6aa4a2ea44307e0c6c2386104c287729cb75763d83c1a5fc3b81a2e7f41614ece6626076d4e4b075da260e604ca97721e689b3f5a2886e34f1c7aa69fe1bde3e110756a338977c2201d7c4ba4916372853520cab754b57a500aab1fc86e490d4d2c9fde3b1ef5c38b4b767a6d1bc247c7fa2d3c91c265156e862e45146155034a464be76b19b54b2d7de8f76b446c30c036c551531535765eb2d84b6110c4d7cfa349ae04e9a1472873fc88a83452727307d50b9c79f88451eefc0d6c7196e0b28ee1a7ab5f0886f7bd0b0784f72372b470b14402d336faf1497cc8434769e3d02e3306697fc90162c042296cfbd93bef0fe223dbc64163eda372d652575330b67948250b069246a583e28c3f1b2948de489a8c945d277c6ffbdbad1ba842a3e3a79ab661c46c8a8cdd6fa2814f5872ce45ee51e5aff2d7e36c0633135b80c2061997fd3072d3a881f9b98909b3d1726cc9e0840bb42d9e908560042f51c4fda65c1ea7d9ab59e505a575a7d862c6721ecabe3aa095f7b14d68bd7e6de7c3cc03d7d27f1804e680053b04e5da9e4f72ae8065335b4fe3927cd4490410a2f0e545d46dd68756c40124620309947c7c55f0b765d2e725a6c2b38254d7639d222515ddc34f28be5c5d844e3323b7b4732bebbd0ffaf0e1e614496694a1c2e264c513abbac420dd86863e1f232da2270072795fe5374fc55cee9217ebfa32032ef4cf4a3d1e3d540373292e74012d6ffb72ee567450a4cf1d4029af0078ce8d3254f49e5729eba9309bb1b4abf81d885172bc8e00734d4a4397944e62e3c16001a16244669c0522f9a1e964778fed789c3fc80cc773648122f23220b9e43f775a7e35ad463f999b28d27f8c15e4014700210641cbda27f8abd1beaac60728557105777f8f7fee24ae570c19eaeed5682471becd7fc5b4cac04b97a7854a79d7f872ea61cb82c4541125c5a0a51046a9877278217c242da43edce61338fa511b3c4e05348a5d5bc55f11ac748ba739dd27124d2cd74113873cd580a4121cdd54889b9611e747779b3dcc3c188ec151b8cc2cde2168ef53a4dfcd1a8360fbc4636fb8af6f77319d064d30a96d989964794f6ddcd2af6dfda64d86f257b139a1f4cf9929d7dd830953320bf7e75511cbe37861a9ae17c270e965f3daf37b515d1373796fe3d88c4950bcb294dec48519742e42b683810ba21686e87be52293ac2b5a1f2e2ae1e5d944916e1d88d40346c6bb518ea05657449a6b60fa724e5647957f6ecaaef1bc1e0aebd22cdfe289d7c37d0d0a23d8736df26674af60cb6d11645bc3f948c91af5d54316dc97bd7a55bd5236b7aa26d8b0585d2c0bb129d5e17e428f553d21abc51a3446a01444dea4c8506916ab247b37894478698cc32c1998c74bfb82b0d935eb624ce11bfcac04f4fd7250d71aff8b44847c25e05dfbd544f1b30dbf976b1c45cf1d522b5f467a2a9e726cd238fc85c5ff6f6b24aeb1190c5d875a12b25219fd3e3269296d7b25487772f64cda082d3bbbb0b0cc3c9748ec93859a67d34f4b51817efa13d521d19c7150713b85a5c16ef90ce5188ab1e8b8e686a8421c058de53ddf72dbbb1c5ab23b4f0793da04fa3c0270d94a33a72038d8463654d16a529240a569ae9e51e8d07372786eaed6cd2dfae349c5605d002ae659300846bbabe8e1667d8c42e49a5f1a726c8b749977192acefd47d4f7562ce016e44501d329f836f4822f32be04708572bcd3b8541181025db9db92b0cd571f4fde84255e86721a816467064d35f074726a30d4724a0ba07192e44162e1cce029490918bdf0df0981edebb21c54794a72ec1014d77a4c4f10e43352e3c765aa0b05b26a724a5a4e57cc50c32d76365a72880d6bb6feea3be531fcb1a96a00d94f3cd63ae3fa11aa9b2b121c9e34ddc4720f4b4c638febbc369236274a231e7c90f124ed16f1e4a5a06442d0363da7a842fd49e94556b071d551167644fc2d6be368d6802bef3918781651fcbda23f105d60e55b8ca908a832ac7b5271d1816bb01ced299a812fe958c8da748531b28272a57c76ded788ee38bf361e28218bb80c2f113891bb819dfd91b5f1b95be96a1f53712efac04afd3d176daef9b4436b97b621b209d6cfc08d205d3c5ba5c7cb72f5025d95ad9376f995d738c005fd663a87cb7f878214d987b65635339191df42f53d6e2680ae70c1bae14f40caea0162a512c096ab9f41732debc664b19d251d9651d216bf66682b661810fe0fd5ffddc4749756b9f089899d9166e25ebe947202a47c225e03a93fa821e3064e763d3a6b91f626220aaf23a85f527fc076c26d4ea3ebafe936b97a251dbad46acb439ecb4532cf2f75c1438de345929fa2bf49bf9b26c2768904a6ec25051d850d78620350dd8651307610245a475cbcef1d72f470643b159259c0c5c55d3cc85c5847464024624057ae2d35062d0e425bd972077dce94074c3e96e761e1e78f399fa13d30d12497a7c0293435e68a16e207722633b0ef0fea0e54ee8a80b42a028937957f9ba504d00ff61d4ec00fa372b3725a11c17e0caf87e2fabdabefbb8978afc8b64a0dea57a977f21cab34a762071f79f5cd3f1a55ed106d2f2bff9b8dbd1ec270c26590d3d7d2794ea0b4b9757530a92ae6dd3698a46340671987216db0d5447c9af297c3727de1a237106dd4bc72b1b3b935c7089c404d35be08d006e88fdb0ef6f864bc0dd4feb6b8db99847772fb762b56de5ae4d4d4ad0194ad7a2b2a983b4ad458a02758fb9d445999bed3728acf7b4ca23d334270732264969b68ad98f7ec10f3f712d14e121e0affa5c86e4c06f3948931c0e12d8ffbaa3651c3abc1af62ebc4d5e93340d7290e12ed0a10dd0fffbf34e32ae7c6062a59ebc233faa89d0fe916b4bf35af9cf1c719287c337eabd1e81cfd08baca30018b5c4bbf2d9c37f255ff7c19bcc6497ea69719f972ac989ddf317d2cd31c2da2844001ba4c636acb957ec36bfdd2483401c91dd872064cb173b0a663c02775fdacf5026cf693567c5f186f3b7893515be651305272ccdd0bf74ecfb6afc9f052094199c739f34860d96b6097be55d6dea37140ac0f8366ca0e3913e40200df969ad121907f1333541c1df6535405b03949fe2a1072d5be9f46e8c92f5c4ec6578c29b6a64c6244a4dd748341e3ccd59eb9ec7ebb72db771a17be8332930b76a7f65debaee4da2e1019847e57eb51be8f30dc621a044c52b0679479e43608fe3f5fc8cc9d4a7173068f2185c9e0c3e973ecb60074724e000348386a30c7d465e4aecdbdae0d966a79f480fe051c14bb28f252dedb0a2ddb9b559295614ef6c01f889e860755a61e639cc363bd49f08a9d41142c6d472775d8de0941524e61ed3f98ba3914a62c4e48c902bd3587b91de173d9f94172e0dfbcfd05f508161eeb0dee3724dab8ab1605a22fdd87029c1ee2e12694e4722dffaac664100e4c9ffea6e70d32d3cfb5a936dea70adfed1dd7558569c43372f7efaf71455f46deba9e22da9ec54fe85a1323546d208eac6799fbdff57dfe72dd42af31304232e51ce878026e7c701379392ec720907252b4f8434ac897b0629b79371719b311b76d8d3ee2e732c538773c2a9c9d14f85b4066fbb642f18372225739438e86deafd5a83e64e0c1f03d2cfef724b053fcdfe2d1d6c67bf57f4362f42753ab05665c969c52442f95afa92754901a781f6bf6acd4be6ab05669608f5d02d4abf2c3e2a5abaf8bfea821f5f818a67688d6af29bfedda6b45d22e722d2a934b921751b781672040b1e3ec5e4c7cfdd4e1477413da66ba7655b9c24d81501fcb29af584b1d751126cc4b382883d23143f280bb7ade877cf87417483d29eb822498845ea8ad26016e9278fccc13e96d057473ca98a3fbbd96ee9f293006645bb8b9878146591a343a5d8ef3508cf0f739ea6d45b5f58eebeab68e9372f0ebfc1bb173f9558bf93feb283a9384607f6050b8f48f4afe69a173aa3e21720f9d012aab4ecba3d0f2ad4f671bca58f7f88b234d2408bf49ac6af302074e542ae73bae0acdb02c5e358af094007c8239a0da8c326b3dec4419f221780fc172082b5648a2bfd56a14f9e6da3a2471b1640e6f3c7b9bf3ec48d2562a9e42077294d913b07287892d106960585575253e34d0db371c8f37b187c862b28f40624ca8ca862612ae6664d753e32a6dd4d9f0494f245237b3d5ba295524a0507c45726b2a4b32c84972879cb967486b276e51c580fc444a808d48888656d10e265072c6dbde32f0f19ba37230ed16bd3b2aa282de40298caa1f0f74896ca335b06f5230cf4e471a668d5f15635dbb9b535a5da781c1e84c3e96a515073e93d5647872312d5816304f659528c24f31e17fcee1a7221d95c4325239ec10cd2fa27437727e112cc88c821dfe008495f25495f63b4f3e2bb822dd17a055e2bbdbf684b8722377f42939476d1c7a691d8f9a9de3ed8db9fa4ee5d2f6a19e875f08519182356265bc898215bb220adbdc99ad9b63e9bd6512d269039e3088c2dffa8698bb72ee48a33dbfb566febace7a4a19f8fc675a726638f66e10fc3422b3d3bb584572d41e08bdc485d285e2d7417b1c18e40dfb2082c026a51c5a297463a9015a5b72186fc1858c4cc6bae9201334066889e0b2fb08f2eb2ecae30f1ff98324e498720e3280e3a12f10b102225eaa6a487949142d12815a48ce6b34931d82a7d5d07297144584dbec9025bc5ba5d73c9dd13caf5e6d32f631c9b0f9c0d6251e67087294f68b345398512d5856e2eeac809f5651ec24b52b1ac135e7b2a18deecf6a5f2236f9f3aaba6468b43a63009ec68bdaf8f42bae3ec656043967918b7e489b723c91a744bf2a473fec4984d5d0460f44b1563ec6f7d3c253eae1dd2be8c8376af6be69d54d4b35ad6e19233805dd1578a7944a97ee96c733460495561565d472ca6c6beb01ae6b097b8d7a504af1741d2ce8491ab9c512eee9cab60a6990944faea1e27dd63416aba7f4f666a4e9be45388a242a1b0f693fd413af4267e61f026beabea62ada000cdb8d246661fffb54844c5b364176192dadf537ab08493872e003d51389645bbba4131ced3b42b36fdf5c1238d790d198ed46d06c57383d101468f81b88a2879f8523c3d80a39e3b24b0b65928848e334f21d60ec0e42407221fe3c53ce0e97edee85e98274c51517e9770151608b6011d1af0fe469a49f474567cccbc444bd95c9611cbe74cf1ebbcf32d2f5fec34439c52596a83411960d0cc5522c8539a850c384efccb39eef1d3d024269cbebc087305d35f26f386572b1211b925c9fd6929d61081b34acab8311462d64a3a7fbf27adfe4378430d52331c1434c5dd079ffe41fe79d8ad4474653790364074661ec87bf2ffe8e158e4e588b0a670c671a50bd551cbc9ea77671b3cb7775912914c35dfe37153f6093729955b7f3b9bbe05b821a43c3e5aa129af8d02b0248780bd865a2545d6b7ede3fed748da5f1e510be7d66002c65f84165db078446ea0510c290a8cad37e35e22ade765d77f42187c7f836d45782f078072fb062b15834905d760e04b2f075ee7269c4d99c49371d12d109098e840f8d90c77610a681d838bf94a0774474297572a297976b451bcdde8784c8f858c6fd691bf8040e8e3000580d6091e980721542216ce9e8a61a4cc5e5cf614c372308d9d97fc7937ffecd05d381c3171b47eb21dfc133513c4e98d60aa53fab1a7f1a3eed83e78a8cacccc618ff2be318e9a27294121feed1cc1374e59e4e3e0bbf6d9addf9e87352e44bddd591edb24cf2f011b9abd86bd676bd2b4180d85a59aca035882be90003d13671408e068bcd0a662daa67a1f5d450f9b7c45f98ca31a0aa34d2ce1aad4742ff53038033b376e5493979e36c9e278b904a75f06459258d50da195f1c008ea3c92ea227bd6a1984b67275733ba70d97d2355717378c3b2cccd0326f51164a515f4b2f5fad0f899de95bb57e2a290ef39c42a2dc6bbfec3e160b1868416da6b584f80d2070e8527247613228818e15fb6c149908c0daf11ce674a020129b3af2dca0b8cd38f96445372f70bda131157c080e8fe2e610275a57706f98f216069cb34a1ee4d3b9816a8072e78472aef2ed3e62a7573173a4cd6e5f6efd12c470414faeec1b7423f0518f72370ad766f7a68c6c02a3f353cc97c66b232fb225b9c2137516ea5c451e23c272d8809312e215707722a00d9dff7ad7397f18ccae9c1a702a974839aee8124b20960c87d5582db290f413d27ea73432dc58de5eb2a9cf75410443e3d022d6ba726477bba779c39eff10235b92976fa009fd92d67e43e091f449f3309050a83361e23b8e9d57841356392e63932274a0100cdbf37e7c065ac7fd0506fee7e13b7212596747a671b6f44df4cc143b84fe08d8bd7f46791fbfd32d2c6d34f7319572a53393e35660ed6d1f36f5ce30530857422bb92b141a33df5b8f4320bd2bbb6b6a210ce32e0b98ed6e224189ef1037ae1dacb801b6d7944c8ff098380282b40d0647d4b28b28302a8bc3ef8b50a818ca7eec19731a30593642c8e3426ceb3b729eb3dea4829fbc28fe3863f95fb0a7c2d9d15bc070e6063d9ea4a111bebd764203b646e5c52ac36c3d012ddbee79ef6cccecbe2edc70eed1d7b81786f04649728655d7a6a8205c73effd3696c27deae03ac3603cf196af8c144cb5a6e6ace20f09eafddd97eef4b676ba233bd637de90a312108b75a63e33d0e976a5bf1e3c5f387b1ddbe8f528d03fbb88057c5db19f6c563d7d0e82f6622ab23e4847e32b72b2da53ddfa96995b4c888822f1a4cf3aac17faaefc9989a506a4d120773c5e7276dbf640dfcb2a435da2b5e07953fb3e885dd2eb2a7176dc7e45a7f4b94f6b725a39206b07b0aea6fdf85bb2cd24e49095331fe468d7ea0129d61e5f2649946f0e6affe3f51dc3d993e94c1481946d5b978f02eb56a4dbd1085d29b437aa1072c06e50113098c704cc5acb5b9a9efdf1ddb5c705dfae7bff74c03d03ab5d462a0a9b5db8aa4494596f11350de8af6b3ccc29afe2531c4deea0eca207fbac89724c3a54a06955e7707c4506e67ec74380dc9204b08ad6cd0f4806fcfbfa1c32720a632338a4c944848297e0362d7264ecbdc1a34bc2564ff13f64c8d9dd955f72ec6f5636197f4b99e0bf19949b9069f9add4fd8cec43f3be876a405bb8b72772729ef7128e4c9f2b7328c95ad2b5496f9868e2f728497a5a04b7489ff6633d728f84ef4e326252c8d2565dcfed044241b019423aacbf8877e64c50f4d61dae724a0c67807ca3179436cbd5eb6c63a678ee06241e1c0d5fe8b4f620d3c5d1a6720f4d5892f58b1b195a335f91a30adf372dcae295a62fa7edf34f9451ab14bd583ef74aca88108bab7075a8a806a94949c3fdcfe7059dd247fb7e2ee77566687261202b06e2bb8cbb192343409d792addbfe050542453fecd73cdd76de7513e352fabf94fbb6b217d326e88fec0afb86a3076cd83acb820c7d6ead56584b37272238ba3a9af0a94364cef9f46e16ff249644381b899c3e2c5857ead72357f9172938a74f5ec92b7481957a5ff98a3ea97f5745bb991df12a39107eb6a87a52e720d9429ea67dbd10924eeee40c46d6ebf0f5d9c3051a2536458110bef8d4fc0723454c9c1518d8b49dc6219cadbeb0b2d7a29bcc86c11c9bda9e66e536b77846a497366cb380f253d4b4a65cc6569777a142c8cadc8564ea0d07ad5ecca5d7a72212d4031fc6e5bb058ebcf2ac2020e9e1545486f4dd72b04314fb8e841680a06e14d59cf766596f6123207caccf962b658aaab3831d191a054ef5ea220e62c3fc96b5170468694a9ecce67479d81e8114f94e5081413b7040708ae8d5552df7217921d7093206b4d3bdf77afa60f7d3a38ffebe2472308599a7ba528d7cc68724731f15a370a096d5f55d9ab64dbeac8f056f253a103e87e393e677e358937364a7cdd971d7c9c9e01aae51356e88ff1019390fbb0a1bea7c93884a42f416b6fdddfbaedda8f11e1c92e7e80091791ad314c78c1ee435dcb4e77f8e0f8d6a572dc1ae0ac69ec13604711cbe57fe4971c33f948b17ffffed28287f861a24d0a697335a04a7bb998f5568306b8c85504d86a8022e9b4fec8755ebd96af1a654853dece142942381988b83031d1ba86c2f44820741514a0686588ba04f5edee8f72b61f485bfca9c25532248f77dc8ca88daab0d167242d0719653c07dfe3dfe872211c921c78a889f1a41d98e45543aa07affbf10f4a52be2fe82dca34aa1dc902778da603e18d4fbdc5209103ff7990040d72fa2db501dfcfd6e382f0e8631572f632e52d358b2aad61d5d5c3dbab19240ab55dd4c30cbeadfaa7147907fc61724a8ef0a4d058f61c0f45e26c0431aa00efc7c2de354d9b21d0b330d9acb841728dcdf2c984b8c74fe86cb8cecafa2e34a8ec48e07c0bc371669a1767d5c2cc00e1885566d7e424060db3f16834258972ed80e48d3e588824a8e7262af5bd796443a5f67c16d5075071d22d7690618128d22ca3b1c9a862c23765d2025495450696c3859c88031da82472e067435efde051a0531e3103907ac42d4b2673d2e0729429372aa6a875c762316e640e477071cbb1b61ee121d300a86c99000cd88f5e94f840683adde4bbb07f13a5795ff19a1d552740a072d2dea12fff36e6580072ac361859d9d5953629016d5f17e62b8996f7c8e424f03f664c577a9b713f6420aa55016114770affaf97ed2ffe8b2ff78bc392bfabd63b2ff20f22ab3619144e62dacce79c61e55702018b5bc147e12aa600497f46d13de1b0d5f69345c1ed7263f4d5a29dd78305c9046a082d691885b4bed23b1a418688bddb718157d5ec72fdd63c55b22d587ce0f84fdf28d81514f0a7d0add8ec92432e89ff648ab309722657308b96c0dd42f3ae23af4ba5afede05078d54cfa688cab60ebb9b3b88672675634aa3eef0cdc3795f3a41567e42093573764e341f5ed08aad8e9d5052b72170a690d9e34dbdbc8252d11289ee9a1d836c44ba3bde0226a2605fc098df372d0a62390e26f3c503af819843869a11c1d2570c4cc32f988a751dad2af133472a04cb0015dbe4b3b279a6913202fffcb772ee8347c32f3079c737ce20910e4062a002643d833cc4e5b15ab240bfdd2f1ab79f79c35a4b455c30aa942f1c2eb722b33be04fb7a6a556bb4760e87747da6ff2083cf3847a83d4577adcbb3f2b26681f268052a456658702de8f119aa7440bcccd9d17e36646646be2a9939f3d0721eb69f4ac71f7869bd8c6c4935db7262aae481a0ce743ce448e65e356050ae6aeebd692dce88c967ccbb313229e097565a855664f5e5e15d5c998780e5b5f972e4bba685e7ef26668ac07599425eb35a2ecc36453f1e4ec35a4fa6065834b172b0f2ca2e37224659df35fc430213e241381b771d391da1475da2d3f4118bed115e4d5e30abf7cb881b45b7759eb51a20badbe59f6ab317eb69f66722682bae72d393b37ea961dff66299f3746654bb3df3d95848299292160cd48019e64f0b72d99fca48e414e1e2db1aa594a6afd9d4c50a030a9ac1f9540c83fe0b891ac910dd003e89ce75aea55bb2643648388cb09106178415c811772e7e38c448f50e1e767542535ddf496dc616bf3678caea8cbb26a88ece330c99a814623d292a5a72814534a9b3ec7b5fdf6174119c9c852f5335053ee4e3746b63d6c0b306d86e72f1da79570fff694cb080c0754ea64cc979acbe1222edaba72562713d58e57e2bd9eb4e4cf0c84b661ab9e2b68d1ece21216c8ba86f3830a730fbb9707744da325a32520f336097ef3b728ba0d96200f6bf96f3409125c993e1126eb5e68a4d726fb4763beef3ea6d0fe85d6edf18e36557a1f0a470e4c9ed8786b84ae09622409e6ece761b6b89ac968af5c84e62925370c32ebdeedb74a58118a28751704472d97460372c872faa4931f0d0e2bc93abf8290a28d0efba7e3782b7a1128fa20653f1b7875ef5da0b931955af13e62b76e6a694dbf3a4f73ce4ff03114ed16572cf5d1f83dce8939cf092ea897e433f095c503981a6c66f7f91856a822ad86272d82f81a2128ba61059fb91beaa69e8ca98c849ab05c9c68d6c11b92d4163aa34f7eb559a554caa31c1f3a6d5723bee3eaa888ba78bf5cd502c6be28b3f5fe9722b8b515a998c3abead9a60a800b01348aab5ad1b38efe3a830bed1cc57a2641c6721b75c145efe07afc21642f9e4141fabe9b613787a23ba71d54d591b86a41c455aeb7fb1ff1d344e8a0542dca93ad7b959ddf84e7e132daf946ef0f9e2b5728e33e6ede3128cb6772a811e58c3ec71b06b444544f020ced80c20d47425611333c33ee86c3f7b52fc4253750cb5905ef5f0b33aa5b413825080162dbcda452ccbcf213bd9e2a45b39c9ec43d040e1bc9d960c76f247a64ddc96667664a0783473742fd71b0e5f1d69001bf759c65e08eb20cee716ca5e01c265cfcc9598467216e959daeeabffd1027417981d2d96f62ebaa88ab68b8985092fa8264c38f372aa1e344e048f55d1c1cebdc018a049c4acb9066447d12b6dc1ce600bca6159725da5b37fee25a10cbb92ae2f36a728a5a6f95efcb675756d94da1711d58a531c3c126e00dd473a089933a8725d55b450882b5681ddba5325f01d10bc5288bf726fafc6686df10b06ba46e627db3107196354ce1169e59b0c5204ef535b73412f7b87f0f376b2bc521f3dbf29b2317289d0624e58421c47891f7a9d49753b89344cb906be60c6ad58fc107137a38e143a36c2e0c872270be9b3f1ba7863cc7e199c27ba903feece64d008053f1d2455da51fc4777307c0ac869bbed149c29f76e04282148cb4cb7cfe0af0ba5e0d44c74964cf8e38d038fec6cce641e051aa9533b895e249983480d31a57ef3a6ac8dfe321471920b351fdda02e119bd73af072644e94c9fc76c8e1793eec4ce4967f680bc1bc5af8a12a0e016fc0519e8afa726abad55a165da410d3ce0657e7e8e1bf379056665ba5f03b7166ae5cc9d50e38affad2f3af9774e84f2654ae9faaf6994abecba5671971f443463b6e5ba449724b2da0178944c2fa0d2865404c4d5633cffeebb879db334f1ff8a5806afb8b3231e8865666050c31edeff1104a755fec39123d3b3b7e6a3cc6188af4fc48e3729f76e3fe09c410ecd8f7768c729d5a7678f9d7fd3a51b60a81425050bc7b3772b5aff56e5ec73ec3b84c24036cb03f1e38d8d3f82c42e248dd48259deaecc2720104f64584551ec43170326066eb72685a04db99a8e7454274b440d3e9d0df72deaa381783f5c04b67ab6da55b25f12d9c363bdadf23fdd762c5d3b2124649727fa069ad76211556aa2d46b65349a6b38c3096024979403e06177f7fd39d930fe4c2d97b1d0ed298cae0c3f2129368ab4f3e66460d88fdd7f1a042e7d658f77223f7a7a749f4d415fef5cb2b31f2f46de52fc1a2bced2216756c940eac327772643403d41562688354988731400f59cf79426799ba631dadbc9a360070456e7219010a023f7667bd350518f59efac6e09a2088444a2027c3444633ec5ab09872dfa1bb1cef774deba57f6cca61718687626821eb973b35b485f897348ab811720106d7f84577767e17b1db6cf91c4a3980870c1f2fc2fe234aba3cde6965d44f761b37ccd69fff58c78bc37803c6ac9fae9d137ee79e61136c4f8a5ade098672e67acc49a338a59253dba0f2d6cfde3f2275a2e133b4335efa69f652642f0971a695a180120285f8787a69fd196441a8c1c491dd53c61ba79f3273f6ab36a4728de65780701b77fe70cdf8ee96eb2e52fe16fa490b39dff52c657e51656e6f7220d86fedb7ac2aa20b89b751bfe33dc816577cd4eaa301f351090b89a07a4836701420eee992192c18bed24c107db2d8cd4ff5c6f8cadcc7f35025b9f77b1f4c0add6f4201a5c3633212d7e43740590d04b1cd6de06f7c4e561c5f21895b75723acecb3b4eb12c7ab4a50c30c237d62103ce1dfef3c8b581067bfe276603657257f45837ca2d9555cbc2343d93620e88cba351631eb6d92adf6065f46baa3050ac6975f46e892ed135d4ff3f7e6b84f164f03d20fa574ccb0f511e708dc07172792f63ffce93b90d5a07a21c1c174ec41a2b9bf7a62e08c18f022ad9dbf94c1bbfea6a2d00720f6a57eec262afb0352997a63355aabe3cbbb99abcd84bc71772d6595399d4627bbad2af864f4e4c1b86204a2f9a4081a3b76a05a5bae5678a720ad570bfa1c42be3d64a3cb9e52d5b2afa6acf50f3803d11fa96db4d6fbc2972e5d4d777c6485a5bd74611ec9567866b169a756dbfcd9f72d438bf21ba10af48358c3a8da0ee2feb4b698c9b132dc92d37fb466306296c1500d73d07a1ca8a7263279f953c4bd136a634a76e125eae2c2ee82af45624d443fa8c5297b2cef35046254d29fb14cb650475c53628456d82925fd9f6d49244ae1d9c4ad4327d04419692d16fdfabe76b668b46ee8915d04bee1de26ae972d19bb64c4ce4992ae0729e78c485ebf1f9bf4d0e6aa433d188bbc241b278031756199c2261d35111a723f88d0ae70e199bab7d15fc31d743b3fbfb75e76878e856ab7af249ac38d73a72fa9fcf64248abbd4a15b8c03b363ca3102df28243b732e2493c7d7dfceeee9285b2a49137fa125fc1d1bdae4ad71bbec18a48261426b30fc1f4c13082aef636fdd66b29d83d310edbc368a7030964a0636e3f522ef7d57ef78e64c44e3796061fc3621927160df56f3d85a980d8ee3cb330e653f3e6e00c99e616a36e5904d728f8f5ec7bac27fc5cbdd4c4a9e019c11d53cbdc74fabf13fe512bb4969a2fa0b2cbc7f08132ba02d4fbdd0a1c3de852b3c7a78d260bb4c2dad509ed26d104c4ac744e3d61bed8db134ef571aba3bac94f4ad0526c93fb0912eb3a733346aeb72439292b0136c4bfa16148de1b60bd98777fc095327dabe3c292895f92a548638fe8f415d2be5b9614a724f836e467c651dde0bc874a84cea7a4a39ea69b06c50962fd84100b56c8267a37d4c661cabe14e212e8304c77848b6c852e2b53adf7296108542d6f43c87fd83590741d495fde32260624d096dd3d586d91989121672eefd22505e6690d5ca2d96d6f4695d9e556fcbee629d0c0e7e0014fda92e657291516418f6fb13250cf13d0acab9904dc84974660f48bdb9cc0186ad159fc1450307554277ef3d410205141894a645994fd51b6da59dea9bb2d6b4ad97d92172f2d26db64f59ced35b8d6aec1ebe4152c5fa3b3c18a985ed980dc1d3cbe9210664bc413ad845a508fd32c7216c26d215754e81dabdf410c70fa927626d51e6721162a66c72a461fe1c3cd912047ff3fae830cd8277270e7711d091b6310c7472af705d68345f3237811e9135bcc9c7146cf7820f83eafce45b0b564ec7deb923290e358d69566eb00635568b5f70990c1895f2179e2359b5ed98e48d8159827236b6132b43f11373825bfa6b5248140bc903259851cd45a9df6af6232d49ba3fa814c986142534f521aa4746a3d889f9470f3057efc7947430c5ce7664db593fa7ac47039ed210e8cad69a718dd4fe138a7b959847f864aaa81b1de1ff1c947273f4b55d35a3087e40089cdc1368525a397ecea6272e2d47d2608791ef2f075e2b1f04187b74ba9928dc390e7ca88f7234dd5526c90a46b52e66fd0f08d25472dbc3aa40cac99d9aec110d02f1037ece1b11f7580761d8a2b0a2f68cb6f05606a76ef4d39f0a5d37b19a16f1876d6d823ab4589c2471afeccf5c5d63ec75d572fcdb74ed042626cc059fdd46ad5b24509496c18266cf1baa01a4078bd35bc76ef9463bccb552f45f90c273cc5758ecc2f323420dbb2eedaee4ecb79e6ee64d72ef59fc133a745add28ac8d8675f694a913c5b3de9968bf05e8741212d942085f9eaba360d33c700577c837bdce4f17de78bdf3deafee15c6cafb8bf4f3e5ae722dbd736aa7f179484118e4fe61d42431a9427029f33ff4050b75569aa1359672c81930d1196179ac6b1faa895631a98ab5dca38c62ccda41f80c4855a78265721b01b5d4051835f1f18ce9593ffac1c8de756e74a1b75dfb248160537ce340723fdf6b46eafedcbf3f2d5d7a8fa6e750a5629ecfae7c8976851b33ebcfbe4972e9979422e7acf0209859995c5d2a694a1552c7a8ecfb0d936b199e9a33e4af3f49fc91cb31650a5ad381dca144c4b15675bf7a7b9e6616308ad10668fdbb8a725470e9250dd7348bb09541cbdc75433b7859d7f8ceb50967345b925f1746fa63c0d52448497e7eac2bcb07137f633e76056911c8c75de815ffb334e22970965e3631d06d393e9b709070adb122b47fe16056d7abc05e6dd44dc770f88e9b3572346da8d61a32ae02b1c865dab58abc2611f06d170d3cc5c8c07aca7c673b4916c7d0514fec1f0dca2dbe635eb6071c07151f56090ce79aac16ed9508a0e58172173f947db2764fa1338dc67bb23ffcb97c246cae65a180e859ea1c1461d8f372fb5b19c58e0b5cb77a81736dbc1079f42e642872fc74cf175cf2317c715cca723c56a34bbebaa63c759d69b44652443591c9aa4c89ffa27eaed6d982702874723425ace757f0c0a39d39784a51e564ae0c71b87b0f757e53eae3e17e9933427265f2fcea2046b9950b0b3ad4b70bc6ad3243444a890a2f7600dbb2f28890ff24e37ea5e9ab10f143d29da0022e1e2a2cb2e74559cc5aabbf68ea97d3acd22672717e9f8a7fc4bb382a85666f67429f2a01b12dd1906ce71ea6fa6c06368cf672ac6ec0e2071923af4e8ba3cdbc6fa7b51caaad6c28d9ff05054ba5bb858e2158c4d7b50a9deefd46c8641252be2f3032161c8cfcb853e4ebb69d6392f498f34eea4d212c76e3ce913fc31d306cb9c3c18f9cc5ee52b53697baa4e1375302fa72cc88a2917fb7ea8e065b188af2ae09517bb85f3ed4ce87ef20f1f4d8a0e165709fd6d01dac3e348ada8edc10268aad662dd56938adb4d167415871f3ad140b15034e2e0d8f3f38b0403e30c3e184504fd94bc0fea182746d2738e4bc16fc0137bda6a466955a1725840b74d0cc4fe142dd9efff8823ca6dcc899738daed0df724c974d584c194213636d85bbbda3f36890222f884c7045bb4e9c17a174fa32721819c5e0dde70a1c231b6958155af45fc380b7902de4f644a00409dab8f7d1149890c66c388480cec4807e210ceea6a8f6d0d76394b4448f910b6eaa5af26a72a53475d637342427d7b48fcbb379c081c867112d97b311bc37cdc7364294331ceec6626df9b3ef31b48247f2650b3fe6a268ed138a26939dc55af72fa161fc5019ffa0585ac388ba3d4c2e62cd7f5cdef8f198693fe2591f8c0577b66d25e600dafbdb087a9c60f6d1f8e7d1a7deff168d661de31b592927df2f755857b600723dbec489999eedacc801feaaf72d358b55eb97e41603e3654a4a33b5f771f07263c2c8231a7c96cbb213a95177f6efc515725009ee87212fa4f757d74b53ad4f1d97e0b98b45dc4344a47e212119b98cf7b39c66eaa6058fb96dc960b79193721b675311cb7c4b9bb59d0c2302907a6997a64da9ec09407d33d18b2a66652a72e0f34f249621c00a91335ae5e30dd53b55254fb6104677f10e10823f735c2165eeb171ba691cb7dd05315206e58fdda9aea917b9f296d26098dbdb423cf3616a87050c07e15c2b0c646fd7e657914987c92e085744d972eee9eb62cf9a5727729193f310c50260eee6f44c5f9891aab757b371dd28734500a42e8a63457d3f72d465c740d9c1705df4c6293ac0bdc0c925956ee7cb5ce89cbb1281da56beda7273a7cd706325074ac2e86b7c28672a70a0b07d7fd5e9da7cda9ce0a564c0aa7255565784016906a3b1c6b91b98a971eb7e968bcae26a89521494c384c19d2372e9a1cf21ece6ea633cf8a2dd34a909afd458357b1d7af5c1c2d9d0be90823a72b0c435ad21c66ef6e45ae1a331da2018654c07731f5152e7e95d032b7860167291c5e89ae1f25f1cad5d064a6314212e74a862710bca608aabf587e922f3822b4f3bba0fd78f8e93d6f657396c7277c5b87bb6df31ee7c3dd891ea858ea51f3521c1ac29cb88b89a9a33f1074cc75490c4aaa3362fa06eb9637f5c6e8a646072f6922a18083cd98b2f893286ceb3e9a03165ed13a4403f525ca6e7bb0b114d7230e4ad3fe31f2091dbf0b49918c1406b9765efb66a208312ffd32a2c62ef6d373ddad296e09c87ee15bae3a169841b52259a1aeee3e177b75c4d5d2b56de99201b48f6fb3647e0376eed7500b7ea05bfe3d830c94d1150a30796f19fb86417592a0b9e19bb23fb626f53dc159159ac0bf5f297d826627eab15e995ea97d7041ce89be883dab1d373b891415792258a7f8a5d2ea4e220d9fe62b9c65e30cbca72e89e710128bad5d8c887bfcda3e6a4385194d99039202255d88eb2b22bd80d0ace8ab5021337a100e5701ad9e528577d01842cb8d1afd6baf6e91f54111c8c7214cb12a652741f080250986a804b3ea01d325920abd8b7ea2dd128aea40b640cec4c6a3d9fe083e2315b588d1073a9788dbfd52d2515563af076d2685e89d172f0e4ffab9ddfa6292e7e283b6127d258267382d15d099ee16e9e6903c186922e905d9094a31ae5ecc068df6b53220475586c8155446b5f0c5a6783a4535c2b0e696ed6d8760d0a2ced35001ee5a7ceb2dcedf222d464aa1eb52ccf846bfc347275c56dab7644fd53a14ba1217d16f376ffecb33e88fc264c87b1cf5ed15baf72466d4f6f991fb3d352c44f34357d2148a13a272dd74a10b744b9e369b5e6b772f3a3e20fa5ebf08475de6ebed7250a130659f20362e69c865ef58ebe61c19c55db86519e87b7c32e6377ca67e298340490d9a3ae313a0fe405cadec056dfe2543474d4ca7589853230546316f426788e0ba6ec9fd56d071f9dde4da2347f807258db7e01139dabe661d8d7474802b304520eab19c6a52777cf0786fb72f6494f2d8097b9c1720259732d64c5bff85e6eac60dd961d9bcd1a0486724af8bcf17233459aa47448a0b4ed70689ff0c6b6025e3a75ad00fa4673540f58fb0c28067291bcaf445078d2fc2137d4b84d16aefce73c7e897e9705d0f9311479b2b91d72f9dd645840254326ba26f9de1b221f503fd1e0bba90fb5072ebe2e68d91ee572951ac415dedd44643ac4e0e2310c4d309d3ea584c325218159737aaf729933050916c7ae2c7e529872cec0a6c406cab68f30c17d52dc63c0397522daad2752580a44f60dbe627d2f4d5c4cd9b292864e71021c559023c7ac908574e8ebb960724fb4bf395f92e78ceb43f9a871ede9e275c5017e7c316a5d98d06366c7f6f972ee043cd383caabc2d4dd385ca88c6c3b355277da9860dbc4663c6a3986c7d672acbf608e837138cf44c05e2ce201f57266671af2156b4863900e3a205b32247265ee5c666e71c02344a69e5273b853cdaefc12274a8084faa3324da12c18157295fff6b0fad3c69b07332391c71e14b4ce43d4bc5217382ab716579681982e60828562949511ec9bfaef70a7785212b4acc2335a426e33fa1821a8f246c0a13f659a298bbfb7eed3a69a9fa20931362510b0597cec4728dfa5686982f25edc72ae6f4be46826ab9a1930bad352ff1a623cba5a723526e1668d4afd0347f9c44d4809c83b720ede4ef73817fd220f35b1a925a06b11d6bd5fcbd8a636c7ba234b973844c6f96b2666475cfb0edb26a80e77a4d997da3c02b9c27301b84fe00521ed482cc7ff188757d6e3ed37d18ddfa0b0d73a3530c458f57fc9b5fc92e35e7213027c21c6afc11267636846a60e00f9185d9d95545e8643a1866095a8f83372c91bef9db1a6c9a2f2d468c961470d9f4a13ee7af7607dc050f79ee570b9af721b77bf4791217ebb8b0162d329a7eab4c3e122f510c813ab961c27aab4519772006a7cfb885e8d79982cb14ab1e66fc795824a8f054ce87ef275237f8259df7251113bdf86f2938717bc23d093b2424fb71c4aba0e9f4263fe1f519d85ec9672d1b89f6016f409372437ade2dc7662a203eedcef94d5bc172ffc27b96b96763a9dd954998f5c7e51ec2368e2512976b1092101f6c760019108d5b8cc9ae4bb72fd489bd59ca9f579d3cf0f3617d3f87c502cdb1d17866841bbce167ca94af16588225e1705051ca7265719648debe528eced78851bd88e1d526c6b57f220f25998a027dc352d7be641c0d0f4490d46955aa35b5a0d00bbad729c2bfa87fcf872726da62b0ad556bab3daaded5884b8100dd37e1ddf23bd7c0619dac3b4c6a77241480eb34cf6e7907ad4fbf19fbf53ab020f86d48f5f73de65e4d86e392e61727334d04cfab3dd974ac4e219bc5ba5bc8226a1c103e267b28e1951d9dcfc8d72d0787f82533c24b004063b1a964c495a387e7a8f23a8f9fa78d02fa4efdf361eca6280c9060d589a382154b5f7dbbc0dc61e574f95507393ef9eeeb795b0bd72527dd70eb3348e3235f737e4595641c085df02f7a480f6b47b4cda13ba5a8223a0903b77dc17c850d1dd361a01154c2a113c9a9ca518d5fca54471f682df0c61bcdc3a936418188ff7dbc2cf5425a72fbe41163233321e39a1f956f2ba351f72eb9074e244caf31d8ba1bc147d547a18f1379870e7e902078ecd7bcc21120a580b98a4cb8e2284798a7720e7a178bb5d1bd2a79c401eb0ed19f874be2bb5a372c4cd1e7cbfa2262866b7dbda048844daa5f8ee27e79892838a0909ce753e22726b718e06504592f01316a4ac6999fc22d5864421ee7c7714e587ce8aec0b7970ec98599efa948725af97d5510025fa49c86607766d0cdeeef405f61bf06e2072a5af2f9a8d3928e8959390439da667ce79ae23a76e080a134cd9d39e9ac8b872e23271c68a1a3ee3a5e251dc0facc8eedcc461ed1ffccdd759693aa64c0e1672d86db672f5f0d6195d1e6aafce758d080cc19f70be4452c6bca5d92c25ed872c7ae174a591eb39366359f7f6f9998a25b969e43375537d969a41d66cd8741a72b4fd6b44bb2ba34963518ea6cb340c25517ac8b3469aa54b558510750221587236ddffda102329dd92bac1c59b58a7fcd5b2a60ded8e3ea8360708756487f5724ef1ca6477f7c48c550245a16525c939e534009feb3400673119715e3397cb72ac7ac1c68906d90947417878af37cdcc52f1367a2c67f5c86a4b940648f12172947057495174584d0ee9b76de460a14378b81904c6b133b187fd3bbc617a5772c8cec57bb19754999de485d4841f48506ce4c391bdfe9ac26c8e591d691cb92003b19453f4d085bdf2f87d947aa5da27314eb72b413bb1cfcdc08419c6f63672d867a4fc33a2cd1f4569b6dcf26a325c6f55ccfc068b0ba3b2a42d09e3270f06b90341b5272eaae273bf6af977043accf7caf5c370dacfc387304b00389e615e2ebfc022c7c445faa786776c909234373806f402d5d98b7e2ef694c9dc0cc472ac2f6680c8198d013000f11c3cd3aa20e840c77cbb419a884ba79ea661328f7241dd06ba36376daa72b86ac7ed939e492733f5028b37388550b1c04b3517fc72f5545fd244daaba894465af6b481720057904aeb7870e56e32086ec5dd34741cdeca79cfa43ddcaadd22f555668222d1fcfb5e7e3c5883c40366967c446d56722490d756cd7f0907edecba37a2f13f06cba219d8fba2427966ce91829431b62070f11fcfc8cdfb762488e3c19678f0709de25efb46ce25fb3a8430ec8b31962725041c11cf9374de04d9a9a4f8f373bbe9d2b43587e7b8241798f1de4329a11320225bcc2b05a4f70aa8f41c2a0ad9400c4043a9325aaabe2b9b0fd6fe989a0839af231fc6b7d3e345cadf744d184565c45315cc664716edac11bb02efa72d4be88b43d48e5287b3ea031f81e992af38ba120f12f3f622ca71cc36e24dbaf072cb07f8f76b76fff707d00e3d0415d32e539e00ac0bcb738fdc204635103a956bf97d17de020a03aca24358fc4e3958d5d2ce5a67fa47133fce9823856932d42cf950b2d0f95fb64b5a20f6a1231fd0253ccb3a12021795503cb177e7c55086729787f3d3e353ffa8c2a90749d69d1dbb0487a06e272cf91a018a242735db52723a99327e00eabfaebba46d6b8de82bbbde8c13f9d8717ea7b15811606361666117b7497687ad325d1742da75508f194056209220f810d5aaff739d1f14960e5338da85b2cc2d9adcccb7cf6ea37c4c33f288cdb56403ba48ed398b47cd74d87291ba236330f39e667e519f89fd95420322f8cfc1876221f0fb97f815dc53727264de135d33d52efbdbacc57064b6cf432a524ddb75f727df7628ed3bbbc8132367611ff5c17631f65e069f4abb1c4e456790e5bb0fd7878b36e539f73fc1a26b9971fe9771ea0e9b37b8b85348c40c8c3631aa747cfb2e7d64ea016858ab2f1d12063e9e9c6b2c30f3ab67d3ce339688365c8a75107e03692dc832f87436ee7244b86b4186957beb9a3af00c1943497b627bc7eb216b482fe8d42bfd80e7572196fe957b824e93e26430035e4be732cc8d787e8bce15435f824da0f8879f86226289f42fb8f013b53b69d9f9d9ecdba0b69275d77175cd87390c377bb3ec9207a5c6fb1ee97008f90e5e08de2804cf6a721f865351f9216d4e63829c80d00f698be0de2bb436b849339d6184e6cfa4162da1e6f587616ecc20ad38cc112af17235286ee3ffc68639c71896ef433f31a8f23cb05640b3125ccccaf5e469a5b464649fe24f62868e43b3872f58054ae858f7595d56145114695ce4b6b02d59b0720bf0d13dc71990368efd65a0418c72694cadc5ead855eec046a6cc5985401772721671b4113c6db85c91bdf782ec2af62afe901d5d44c77b3a8d0239a7c7fb025a8cb72a9c21225ab000e535207ea719a47c229ce75d74cfd8e05943d6931572784aae41c618ab880d8831aa2a55c78957dcfb4c099f5b256e2ade05c31b4d1efb986fb38520e37cfc11896912f06d664778af8ffbb3e711fb63d451b55ebd7273671212bb45cb75a39dcb13e72ec7ef9c51db7df4442639468581e8198b85728a9e5247410564b7925e572b0a358707c44475ff8744f211e7cfc170c163a172784abbbe786491fbbab0f6a57d42e1577ef87a0f9733c119c2fbc5dbe546cc7228f08870f1ee7bbbdb40b57181bcc5549b9f8132f6fc9e65293bc578ef82a972a2a019d9c85f7a1649f4e75212ecc635c4371386640e03dd9f6b966e1ec598729bea921a86cb55144c8ef464bd84b90f46ba967365eb4cdc8b9c0084fa559a72204bd6f92fe8b0c0720d10349f234afe8072ddef8c0f75b6454c3aad6100b1721e933a4b6fb49f4ad4aa33e53015f023fc4b145d9b54ea3d7b3317b39562a4164db9e23c00d49112771b19493617b50a4cc5349d7a899a3c4a09be7844b0d47207f3b091d28203e8570abe70f51784e046b9068bf36cb3ff87068fd2b375de0413e1e0b2f69dc47b62358afd8a7699322c2d3ce7bb6565304737107f794be872727c849881bd1b67edbf735798a3e2be8c49b7ddc7576d2a19259f2f264d19577f598af559459a8891da8df48a7bcc158cffe51a41963ef018564bb8c9c53e169096f1f23ac33d695869cf39bc1466f6f06be2e191a390b5eeaeb8e65807547266bc848b7669cb87838757597346964818beaf7937ddf06c380e982da51e3a72989b96b3c8c6b136f8bbb3761d5a9f88b9866adc44144d7df63785675f8a1e3162b272c66926179281f0625c8f1583ec51a2da8fb95ba7dc21c459f7b60df4725b3c6c621940cf91fc0ea10c124517365217b65dbd6f4594b39de312c9bc9e393b2aa4ec8d9b977e443604e1f4d4d68928fcedeb0ec1f7a9a18362d94e536e726c10cb3bc246511290c9ab5927ee586ae1c51636f7628771d0e82f225f528672910a6309811aa1b98b8ef90a3cdd14c8b428e4e0da71f3162f5cca9c9a60ec72a4ad8e7da6a560d8eae65d1620883977b4f5b874cb92e2315e2f6caeabbabc4fc2de9f31623c4b7508373458df554e99b3518091daa006be1f62a4c579359238a5fdcc07029b6113ad5f77cad10720b7ad606f8f6ddc8e3ac664e105d8469116d5f78b0f730c764fd83aeb2b94854c1f0ec5c05cfb8a5ac38ea6d15d2a6a74729f75f8187810ccb87d164a6a148c8fddf524c4e50d3f7d0a8a8ae5497ebe9d726f279b3b0804dc8b7bae7f1b505e9cd2d338a0194a5edb4d18e8f7f1362760729d4a8cd69ebbd772169c8a055bbdcf79643f5a78780a18597b5e0bb03ebe6d72d14a2f0c3937e0b07e3ffd1f585c739da261b2c8980bcb2a8091cd1b39e5c87252c25622166686bf993fde70ebe7a19cc9a45ced46628cf9f21e479033997072e15cabe993a2212599f3633aa335e2e47d065b599db5753d43b4f4bf39b7ca3066e2de83234d1338b02e7942659070871fb6dd4f6f625c4a4c76f6fd58baab6dee3c87b7d232ef9911b1b1ae816fae00277b79aec39ab943e917c9be95a9755ae6656912bf29a97eb62600f411a60806adad7a0c6581800e0e1027bf24848f19115dc8a2670db8f73d0151c57ad2e4a920c322d8717b4a68c5d6aafd2fd118724fb197a24451aba702a7003190dfe89554d991c6e2640111c3aceb0780565f0407c901c8006a9714774d5c26d13708479c31be45480347e2e0b5c7987172227243075cbfc21c52d1ca935a00ca024f2f8879c688cac7270f627b46a80d51ff6666a1e8b9e62d47e355ab506f8e6ebe694c2f53a2367da9a7c0f7848df33f6972455be5678c68b559e62d1a556acf1e93cbfafbb592808ce1b36aec3c9e83b67264c3378e055f21de368cf907e6ac572fe49db5ec00ecb4ce076a197109687772f03cf210817a2cfc867541c26c53b7c9319083fa8dca409323b9e2d72b3d277224b11c62484a6fa3f3ba4adabb9fdfd7220c3a7a2ebe39dd200b68609f81f372f7ce5a0ac6f80c267e2c7df1e668ba4927af798f76fae15c68170b155318fb15261748451013e650ec8577b35b03212d55bf25a47f8a33a5b40cd632341f9a720f54cd4b420c343a12c29948030b590be9a7aedc3c66106e5fccb51764aaf0725887b472d55473cc1a8e59f0312cf9a99f22b1a9a5fe3cc09b304c0ae57b9072ad357bdd347938a86a7424ca020b655a55b8949fcc984af9dcebaefae07c3772d52a65c0db65c8d6a5941bf2037bb4d4e5aacb4baf04e4daf730719f3c1bb542831ac2724812056876877695bc01bce380d8340796ef5c3c269ec07a0059ae10d09646a69b02340a4964f5328145db8369e53bb5471bf1c764334a069df25b72a2f323f667924358ed9ef2c64159fe5932ff3b2cda46b14791826bbf99d39472e10bd6f0b4f10c09ba93dde5f3f4ef497e068bf43fdab10e04e27c044e672a7274d0a10c3c575cefff761e622df939f62a2653d90031d0ececa50407ae3d675641452b5fe3ff5e676e278be36f817a687183c7dfccb0485769bea34ff5ce9f72669949bfd7123e1c62ef3410b215d09fcf7ebe5ca74d57bb03cfb4046d1f7a72249e64b4d1ae7b503023f96f3625694f58895b2f6441a195959ec764631a6245937e54d8858a4451edbf3412cf7693ed00cd2072c82c476f5aa8e2c221aa6f72fbbf200f69b403a4c97a9130c08d3eaffe7c430634d54addd29026358c39542580190b4a8e0a4dfebf891f91a9a2fc71c9dea8ee3855087a86093ae77deebc72cec48aa74bbcc16067bc7fb775bd1c232b1804f342e55cee3f7574ace471a809725177adb327d32381f0e8c15a7bb5ebfefaf7080c52d43a69c67c6abdb8ea425dc187332c35cd1b6a5b37d814c88dac93e533081fc5865cee0d12550ce3cf726ba3075dbc5e64349a2cdd44cc98861ac35235fa2b4d6390a742e46b92f9857277d8adc11ab61aee1ac7edec7fcb9211d629dd34666d6a1b059889d3848aac72b66b40ec74112d4512a2c28433ca197277d9d7adebb003f77bf5957b1c3bbc726dc2f57ce0a7af48bc3cec66586ce2cc5bbebbd55cdabbd5a536b43be18d0a72077961138e46dcfcec87b3dab2470603cf2db6bf8e067897ef2a10844d771d3dba4b6d4e07e24ece2535d99e777a5b2cba8242ac326e9f6e4ae5fb767941e76559b49394ea89238adeaf003e70fcaf4096f7a201999283a9ff4dcf17a37af313ab0651ed330d92d5dc6feac62d0f78f4de51c6460db35975e9f18c35a6639b2c211003aec6b4378fd6d46a5abdaab1e9a85ad039c0fadc9ee5b1bca1bf0bae72f83b4d60a0248567f3bc4ff6fb4f5978dd74c77aea5520cf7442622ca760520db286b038704c0efa0c3c00620aeb70e4297403c5a48866ee25e849226f18e671c5720383df5f8635305d337a2615e926cf34836f9985ebf1baf180da597994220e15f684af003ae08f164d77d8e1e5e9a94ab82c58c5aaf98b4dd548e65aea72378612c3f0ad0254cbc813167daf36af9942f6fedb618560ad1b9766026439484a09f4eae6855c71bd3b84c8259f4dc98967546fa18e39aa787288a86668d872e30787dd90bedaf2cec9f1025a6616978a4bb0b04c48a4e3327d80b4b8659841e76efc923f8d2241640263c21351ee7d804c74a8ebcc5980add8603db25f5b72d3fc8cc4e70ba5ed326c1f64b18d4be5b8a323753d199d6cd29eaf27a60a26724f53cf4b01b28a7e7183fd52acc34f09e0430bc8a556636e29f6f5ee3421d972ed448d24f1cd73a1747f1be1844d54491b7abc1e4cf98f005ed40b5bd3301b722fe905826a08246ef4d976ec9aae7cf111a91af78af699f57bd7009652686e72eb7b55e208f2ad6db9a024a0f17aa0535e1b9396997c29ddcb01a338da90754eb34473248a71b701fda69bf1ec5ab7b003d8d4b2589fcad9aad143edc818ba6814f849eeb812da271e96bd7887650f88f4d3354e9f7d6af54abad69d5b956972f1e08cb3507f143ccaaa0a213bd3f08df47a583f7636bf6c7901e04762d54e7248d0e533faa174896e9330e0b6b715ed815a4be8662148fbd74e372d18adb172c413f7154c84273174f2be60b5363cabe6bd8ae41c012b2f474e27f5e9671e727e7139341adf3536603f282b1c498506594bcdfc1fe3313027e3f947fed9541b5146c052dd4c29508acb7104a6502b42874668a70f67a6b5a2c47acdbac45c23777aec4d33af3ecbec5d5f38042899c4156498073cb7ca092faf5e86a24c1571cff02e8dee6cc69ac9a2dde1ac623153181bb05fe3c4dbf6190282cc0f86047227b00cd684d59e0194f4130ca65577a9072eb18a15ffb698c86713b4560b7a7273351290a4966d1357f1713596b8332faad357961d9a2015b126c133248c0272967735e4d2d9fd62e110bb3ac13c1c4d288b69d341de03245ffb2ca724f9ec72ab72e828880a362330735d0197bb60d8ff04d966f1ee333322af2584587b28720f1de9254220a8bbc5884a54b6dc9442f4db0dad3997c6983551ff2b0ad97c72326f7ff772f8519f8f305759442774bcc81e078d4034736254418b79f2c3847209bb49790cf575ae87ba315c26edb30d0617d2bdda86ef22807c18fac41d6c72751a6d03e68cff24c9e3902e6e6017b4125ed20a43af8bf68746947e667d782c89317819724884014a44bf93dcda80993dac1a227b6a56c1bd358485c2951272e3adad91b3733330eb2949dc6ad74b4207118c555b16731e302ae1c14d9e195b9220738c323a730787bef6aa7305ea9dcfb8625998128a9c6795f63caf5e576b8509a023ad7512e90fcd2e089c5a87fd278bf1281ab3844e6137bf6286c6a772cd21bb3f5b6f5eb18820785463ae32ccaac71cdb23bf7774f4ec26d9b043eb51f04eb56f4b38146da138bf369864fd99c34dbf4c3112ce8b8dafbe0800f0d2654351d5d9dee7cd47ecb7455c962d66072265cd2070bdf394346024c3068ca77242129c409c8b9995f6f485a9982a053ad42939a277e2cff0531e857ec862ff724029d0d3396ce7dfcee210f3b842ee44f97e6a34a0d17de3b75ba107b879c85abef1bc71bda4698d0b0039a7f6fa61103f68e267b8809900323195a760a44a72470737e98e1a2198d9584ce44b009e591eaebf1f82e3159e97f4194a65cefc728c276dc49d7d57386071df6f582d02b08b09fd3c58237ee2a2f80848c3d758724152f94de086cc754ac400e107b77439c3d51c0a01c1d68347a5a46d9108057226197c0dcb86958ef5b463db63fcd063371c6fa0d8180bbb4f635e727035d34e4de80de5bf2a2b9fd94fbd1d1bb3996736a3383f02bebcb70fa5c35505b4f0162e757890a0a481cac72a9342ebb4549994afa0e7c067dda4ff1cdc4ab32ae87247d43468e40c56c09db2970193793919fdd2fdb76781efe636dea4ca7a4b241ccc2031e12f6342a33b50039cd4d9d6037aa2121eb8666d1baa3b972984105c25835c85914dccd5725e47bc3c0186423a5a099e8239078b79fc8f677b2df1e158e957e6fbd99d3b4fdb9f51260c3e6e98c29f4a16fd817e441a69e067fcf3a772e5a5a2b47fa51355436fae6ef06e61db95727fa910e0b830552b17e5171fd572accafdb6c0122d5b8a4f1b33c35ff66b1de9aa5c179f72437327518f0f2992481e2f68a0bc1fe56d0e6d1e34265195ae24d879a7eb3c3c519ebac7008c1fc205cc02a8387ec90df249588813ba1d478562da1f7f2859aded125541c39371dc646e68350a64394c74a4054c6a54d470a6c558695159f0b6e496835f5ba39d7d729ef20dd6bed1ba5fd738e63786692c66a24be5238aebdba9a5d0487424f661119ca2ed64ad899f11946db15e5005e7dc021e9e0e507292b7c15c3464a82a5a09b9febb668ea4ae67814f4fb1b981a9b3f9475622d44fcc5c2b4e3e53a4bfc3725df4643908e372cb0fa3b928b0772036998fbd92b14b6888600c2bf0c5035615bdbdc8947aeea2797cbfbfdc27913e82e06726aa5c702fe517d4d33e05b2a6722ea50e53412168ed90e740495a927f4ebfbd12de3744a80b5d674c94af5202721a6d8831e55336d7740ec502f064aa45f4c3876e41390981ec5821ada448ad0bc4ae40ac0017ce529343abe7b894ae4a2e7e7d6a9664f3cc801d435d6aabea727efee90d4f0b098c88981f61fa56b90d5ea02672402369628fba5ebb7ffcb2720e3eb86d36515beb6a52daba0bad4a0663e1f9aa24ff3bce6a398e682a7d18725ee44911cd9a11db9cb46f3090bb58b0a251e33894337f99faa9041543569603210242e004d26c151d7d4557a8eb3e075406f637d66317c2c2e651992c7853723d7917c7fdcef0c681bf188f57475ca3aa858f959facb1830f7ec642b4f68b72f2205d6d5ab25f566ffae53526091c5d143a5b2b3924f8d2edc979bc290377098fcdc941b04d5d673caece8c49048382a280be47ad582b54c6fe77b2d711e772191cce20a37c87f4436251efb1241d3571fddd8cebfa715ceb69bb0441dfa372d82e4f37b91e3d7b5dddcc172e9619786ffb349faccb855816eba855c1b00272c5ebb00148855dcbf3a44c76cedacc5417f3a49c15ba0df9b5c1e7babbf5a34ed8bf42c3e79a8bfc2a65e325a88c35aa5ad20e21608b2888246d25b3bdae64728895ede4e9207dfc62c3f7e63b659d961cd253a808242fcae99799deaf865d4712ff72dd221f9c8dbc68409b1257b66b1812e55d456346ba8dff76de85ca7427adf5d32db846b170ff9dc5743a9ca12fb8a893890e6b69a099951677b4aa54113616bd992341f9c2eb2be23a251b77a44089c824804f22e8567d168c79475872b5c55b0360a4bee66dc162d497828e19bcea1e6badbb4ffb03112982ac105c4838552cba6446c8b185aa9d0f40e367873efc06e1a1e5870322944c1c7e4af872fe340936ecdc92bd2d4e08ed63c8b9c0d8a4d6b24f3209437f843d8ab415236f67ec9c3f41d6f77ef434bc77fde23166e4bae09c304cb53e12ed499c139455288ccca6c4679b57222ed1dc05c958934b6e3dad3a6a6a4e8dac15b6a4d7d7d80cac00d5e98cb47151e4e82ad6ffd933daff2e833ec71fe80870ad344ef5f88a72da9370b9443b097ed44c87000112473863e3bffec74cfc491ad847d2f5282e723e43d338811148d64cc23c951a001bd28d5706f03ae59eace5422097fbeb3c4ab518ec99c01c4930c55ab73b10801c195193156ae71ec62e3a8b8c850c7298723d7f4024e852ef96306bde9c54fdf59125fdd4bbf947558e43fae3e35180572fdbd57d0161787f650d6e68090eec14f9628ca73c9fbe913c34de8c37375c947254fe453ae4758c2c4d928b5355741feebe629845d65b7ed9517252536a7edf708477ec00d699b90b3f1904b9f741e08d57b3269790740445c95c869938bb84725bbf60447e2150d9c6c6e21f50f24c98491559b3b1c66186030d491104ca977246ef2b75dccb0bd83831af69302f5d903b47d265cbee2ad06f78950e43a56348fdf672ff92b0221003d58d49531ea67cda06ce235353f19ae365b9c79337ae236d7ce0a34eed71fc42c1260d091b68f85b60688f87872b8647f415647cdcd2728ba68d34810f17ee0d5e8f584ca262783dc25903c99e3cc70391dbc3ddc8d5729a3a990c267f794f879f9ce6743dba6afe2daf7a479564c7ade6c83e7aa04d72bdae239a5f900aa78a1ed39f106640f9f65ff2ad4c78b07865b13b970d2be15f0ba8d00ccb6685a3dfa7bf774b96ce34c8111125fbc504d03e2f5720ae06c95a3da1306f3aab2c8e0cec42eff5276bfe06733c38423398b3bbdd353dc2a7442e897852c9058167729de3e411eb6f4964a2cfe50dcb666b95d384934a77ba56724231a78b718822ef261a281e97cd06f4b7746b28bea2d6089e625100d4e27972daaa8e548309e80e37172fc5c518fa31c1c561b22634d422f51a5f472b6aae724bdff9f3ef7201648805a8802d93a77678fe28b169dd9ba41aaae76b556bf7727f8b1df011110c27a2c6d141f2617e4c3540d7f3a6ae524ec81d597295d33c72bf95cc182616759f2bc2898cb64764e5d2bd23a37ad477930a024c64f5bd6540386970d93c8f292529aed815c3ac3e91a3fac98522dd3dbdc071848d7a94b92eaa1120209a45d2373623aa4ea00a98190350551acd72d3b97ede98d68c172855e697c55ea0e64f79e28d18da3fc8ced451326c6506cd7b6f28bbff2866618d72dd3100ef88c595548a5d11b673eaaa46583cb3529d0a1d449183a58f86bdb372b88582bade0bd4fa2a7d51965a189a14ae2a2fad0c38dec92dd67af8dd83953462997f56ef6d4468a07f1e6f0bae494e12609fbae86663dd5c27f51fea245f0e995ed110a2c13a8f53ac4fa9daaff835a25edd9dceaa81633ea0a0bd9b97d372622c368bb8d1aad4fb512de40b5482ff5bfe78e648447077f0514908006c7b728ad9e7eaf33ffd32ffcf23ba48b3a1a44eed616ed2757f3b2271000d4ddcb972e3f7b5a0ad0f0aa42bc39fe57f90646740383805e36ad41a8fc06d3876997272edf16e771365c83fad26518d34fa981ebb02adeb073369e7115c5c5ae28bdc7255b946cd5c1e54e469a307f3995ed79c32ac0b99d0de1313a5d28eb7daa3ac159f9b6e736a00f01ecaf18d2b7b93f19a2f352d60091e584232973d1ad6fcd84d23c75cc6f813885c71c784ded9787202c50f24a1826dfe7c57edce8e6b054250ad1e0079f75707617ce55f9747624c2ab7ec11dd24a6c68257657f6e800a6227ed5e3deb99e9cd9a979cb9bb22fcb55fce705cc39b3ff5afbb546edfc00a9572e0f7350a9649d65f481849497742beac206f3ad323fdcadaa91d4ab825800b7254960b90afa4f7e8caf44ae01d3787d90b4947a68ac041a9f4c2ba2c3f7246720876c877b2184d6bb168545b96bada818cc15c372782d3e59e9ed0375a8774229bf7e379c8c787f66cfe0343c8f3bdfb5a8ae00ac66430a219b0c3376156027267d1220d0beecfa3af1670874011bc1b5fde89c92ff4a2a26305ad24fceb093156d83e4b2e48b1b49aaaa1497db57542029e88dae0f575afa89060ddbcdd7f3721be360d1fe4d96795e428816cc8b13ed47bf7411b9fdf2388bb8cfab2e4fb722f5ef7cd4e36fe84b87aa1b185c94f0055704960f769270c874597ef93733c7202880e34f100255ffd1fe13b38260bdd699b5e83adfeeed287919b671492620880c3f1de29a7e8971049e3b6a25e5b9b1e8124dcc65e80ff18a2b336ca855872c0f230a9f59056e46ca510194b388e1f0b605eef9b8894b2a0a6b65358892f04b587e820d345b66f6ede5f949593c6ed5a735e0812a79ed421de7e70f9606c727f4a83502de3568cb64d7ed7aceb17b67cfe6d7c93787e510dff970be242d2722fb24198bb4ace48e7d23de82870c53b0b218c008efa63c802486793fbfa022bee5b425598b2922cd442347e693507e0e38f64c5ef5514476c2b2d730d9c3401eba5915f81762f9d8f5876f106538e4c594af4f494ce6ac2c68c34a1c1ab30729ac7b61f24bee73123f97328918208e887d09743defbe5c2a342590231f9fd3cb39f64e691fe4a2029def6327d02e255cc0af7414049c2ee61e412c45d37d8724427b36bc8b9bed4c99d6f38b459f3a1448ba0ec166722a59c7a3fee69218e72f4dd5355716c9f82506cf29c971c06982aefb0ad0a7347622f1e339daffdf66d06c306db1645c8fc75976336ef32ee20a49208beda765957d98c70d803801d727d8441956baf20910d5cbcb40da93999302b977053b14955a62ab3ddb14bd872eb466a836843163354946dbae26112460194df206dd04d47bc803d3af6128e72f0b2d98df9d964057ce4c408c5e99538b8178b1996c07ee6e8add852c7ca71209457ec76ef9cdedde59db88a15e7790ef56282cc2ab9c23ef419d126daadcd57188ca201550561744e61cebaac0071450fca599b6c6450747053a7973f390e723001332fc5313bb5d47f5e747b338f76cbe63158f1258ee40e4324b601d7a972021ca4cd24ab3d95790216c8ac1abc366ebd429e0b136d9fa10514b4b7f2ee726869ab504930f9e45cd71e995c5aa7716dd2649cde12e2c3adc7bf5471dd8a72fd72fdad25e4eace162304daf4aaa69a9e7396ea024d3a916990f8909512de7267ad28e061d4743783dacb553258aa5b9b9a58b789a41ff32594238f2e24760448032c8d051c28181c6df0501f332cb469ecee543ac90a907bcc6f2f86d459461d9bd2ff2d94835d6cd2c8714eda96b1ca77945f2d24557e164e02cf04afcb518f148e1e8df652163c2c43a9f09ce33bbfd8fd701b3db3ac8bd97694cef54b0ae8b9e39aa841657b20fe9be9248f247f348e50c6f39f885fa02cbde42244817228a806b44f8c62ad7700cf8e642fedd0a5e87ce9bf58181ffee4cb0d8d17b772b13fef262c0240c8e02b0258aa2c67b83718ced67f3a5a9f778df6054bb19a0637f821b730df3e3e189365e7b601dd7ed1ecade2bd025f4b1a17deebfa422e2f2b6f7fdbbdf1df26acfe7dc0f5a01ed0f21be885ea236fa1c8437b81521e5f452e3dad7e1ab0acfa1cc0a9227b8eb4d252c7afa1309f2bb02bb422fb58ae9864e27444a06464d3ec737c56b4001dfe22c8cdfdf95c9e7c275d8065ac41c1b540b9f1848023045242fdb43655ec58fc7f104281f5da86272b752ca9fac6da97043865049bad38ba9aefd38532bd4a3a163e22625be171548d00ac35a0c692f072ed9c644a888ce4e724c8b888bd3843e6d9fc0623c09d060e7c391ec59b6282221a248c6afe31ec5db60681be52a8f038918580d8ec1f46319d5889c445122f7215483101f8747a9ac3f16967570e018437ac59942d690584a29a011caa688b10f5e7d163d9822f9c29e88c6927720001c183b8ed8058d2a626de60b0de17157223d56ffcc41bce039d728bceefac4054c7e8b0c72b549a50d0071d7727a77472442c17c8eb20326c87edd0465691e5a326fc34f9189db2cc6a01f163f2a20843bf442d6fc7ebcb33de9f496fbf5b17a0ed585f342397b9405f1a49666b5032539141aa32381895875526e7e2ec515e1307f408d6d4a5a26dadc1a449c2184872b11c08e9ed27f8bba12b9962dab1382ff933a2b9680c99897755e94a079cd66c5a94811f422b9238c272d66d42fce3c4f0952fac791e99ccb33bef4f64392872cc7ae9d3321293c20176ff466a26384a00909402031468a8de1c5f78d7314c6d8a4e71703bc26a364e8ef1b0e86fdc2ea5a4a6fc4f97c5bf7e7738ef952af14a1594c43ee8c971bb8bdf9f19d93eb4a540b7485162312c3e5e49671d391620504f81e4d724372afd5ba5b11335be780b0a2f7896018e2acc8e7c89cb48c38a7242a3b2ce80dd28733d91f280f75001cbcfb59d52ef79427b2d46dc574763dd72f41d86fc2f5f265720069ac31aee196fc2e2558f4dd7703c7d69676da13a0972fbd2837fb64d44efb2369ee27adefffd2da6ace660347c6c2817bcb7a2b158725370dd03db936b58a7b7e3ecb2942dab5187c9fdffcc3aaed98e083284685535823ed3fbfb20936e65057db749c6136a6c578d7b66aa80d48af1c59895b13f49b03551e23991958ad5c5743eefb904e779135e2c221805de8df386040641290d1f3f2a20b5125008ce6d32f9fb3fcb88db0b2f4a4d2770d488a13f7a65568672c40f90a138172a5de89554f6c4b58ab8bc7f615733b1c251c581d65f265b5a18af102dcf41fb3e0242ab8bb58b8d531442838b01fedfe8637478f9df98da0b6ce51c98b0c2125a9c02797ab51a17bd2674bae03c71fe1948bed70b24da839972af73c13801f92104dc8a2c2ca92aa85c4debcdb1a3c30487d421a27b0049433205df244a4d692db532c195077b1580214ce4f41d018c32f93202b5b4b5a54c72ae84a455af8370fbb74e006ff8c5541fc28c8701c592c9f5ada1b3161a1a0a43a252e65bcb18e9f2f418e1f8fa5346c607e87666f44f79372add6ba00342230315893a0b3982978aabec7ac7d5f5c445429e2517eb944be81cb98f376a7ddb72cd694c24f6bf115f227e5cb8b5ec3a860ff2db6f242ecbf16a097affa35eb37199de8b8f47731c40423f53f751741c1ec72b450c0c8f289e686f0f91a19924720ccef0456491913ee9f48ea221f2c6215a61db57cf172b4ed13efdcce46b191fd6774597d9e005a63a91d1366424a716867dfa3f4b26aaa03dd0dade66064b72389b9e2047f2eb1040c1674d1518a1737f761be7d5172697fb9e6c343f61850ac03c86709d846ec18efdc404c03e5f53f6b5a3c729216ad810a6f451f8d4325f9ef9ced20ae3f28ac21d00f1a870bd851a013cf95c4e6cf7fc0656ef3d3aa272dfcb5e9227559b088f8e342e7203377ea1b04cb8e1c742e5dbdd5b1c0e6c8b721b692c652578aa8747bf89fe2767689c613c0d1b6f01d9f35f31cd5c1da36b50b908b487a2f28b6ac89adb5e6e1ad11c24f3509b88723e46085ff25e7338ab48a429f79552066c33a4660565e3b96fb26b182641e71c9709ba65c2e960350972eca4007cbe3da681fd1fe6d425b4254996fb17254f3f39a85d930b9e1e2c2e517fb6b1452e54e664e5e8a85bb940f8945b0b4a3b7db0da8a3d6e57eb8809b372efac61b22cbaf4d1deb7bced747359ea94eaafdd4030ff507a7859588f7faf502b5443e1af64c1b649818d4593c0a8bda890a7dd2be4ef0894706173a061d73d20976fa520be0a2228821adbed1a63384152db9f192d5c69b19c719a40ec6c44908cf9a90dc1433e94de730ee1bf94d59ceb233d39dfc61ec5c78cc0aa3f637235d0f92dc0962e45c3dae5cc6ad00da35c9441232ebadf162ab319988638831ce7014c98d62d78be29e5a703f75b0c845891d75ddd4b1280dc38d9c5b93f413540c3c445ac64ac5c5de5a2a5d1305552d0edb6c44cb1b9b4e09618bfa6b8872e1899b3933cc86328ed18b1aa63c7e89a62582bdc91359041f9c387049bd1b3179b6216126a11aed30811f3e8134f864e8eab2108d43297308c0e07da73eb643d8592ee0a3b8d3b221109757874b9e8c0e1bb7afede5c33fa2f06788520fdd0727b889dbdf0d9ae25b7ea93e7767fea7e4827700103d9850de1477e5b3145f872c23050a9f2ba67d2eba6e02773fe6ac7d5ef956b94e252fdad3d545b7fd20b72841bf70688eb7d00fcd48e333f70ad4ddeb3e3d761ce6bba06e156304d5e4072d4effbe0bfb7cd1690088b96688c7f9e0a6fc43bba0fd474d0a8ab1021336a72bda17a7fe813329f085ae3830c190029b8a9738587cfe96f727b95977f3ab172feccbef173b9daa4d2d5dfb42b0fd8cb097221426c3fd0287864a6fc6d6bdd72526d98924e52e7263dd19ee80f189a8dc712ae9dec24839a3ff7a8a433eaab2808f07e6de49b2a347cc204ebb9d0d727cbdbad3022657aa9408d20410d0ddc72697261b381961c5715e098fe6178a83900d3be067ffcfdac7c0abfc467f093720451ffde2cbd7bb27b67bd8094b35d923ebd29aafe5db9e2a66caf80eaf84972e66c6370c439d5ff8ac616c34a686e52052d9262826ecebd4567fae7f982e1727132ed34642985965cd10908958b22b967d6da2abf21f5a7cb2d3bad2d9eb572a18f7ace37edcf0202022d351bd44a5ad026df2e2a9154132dc512197797744cfdaf9f7e09e6ea03e1a7d48bb02260484e74ba631c168b80a73df4c7b002237267812a85e7a11aa4b23650ff8b0992e691f4688bb721bd83902f638f84ef3e6a12bc9eeadc511797c4f4c1469cc7ec8e5e6f7aba04ed207e114536eef0c142723145d877c066f15ff19fac0c97a651f0443ef8e84f06f9f346b3a0f403315d7227d0aa7d4322c5e6ce24c53b1ad19dcfc0621e61e29987156309c3a437b4c548fdab182ab7e0fb0192ed595ad1535e2846b91e2261ec02a129432530ef8b5a0d5783722f9cbcac059e5dd7b2bb2cddf3df3acefbcc5dc61e3d44543c0602466037da29f1fd7aab8f4c19a3004ed34d5a52c52d70b4fae74a6cfb61380a044972eb70560e12292c7597b0d010a7b820d99aafa597b14f8b17daa210311f12c4531440e46b2340a3a69c2aee3bd205d1196c0f1726e07ddfef4aad758d3787b7720ce2775aa02bfbe7114aebb87dbe31c3d92e65b567e3ad13c1df898d54043c72ffcfa7a92a4ecfe6d793125ecb74c1282da8f88bdc32f1c437d7c85f54c06972284e5d31642513182d73446d7d44d9623eaf77a42878b8dcf84b0a40b9e1e2726f83ad0cb718e8654eb714713b43f01beecd0d4fbd4626d0ca9e5c480e320623fdb9ceaef4d0de15f509fb3d84d6fc9ebe541b2b82708c770c040230de04d046846faafbc5b1468ac10273be0239d7c40797ea7fde3c570a7466adf17f410c72f1cb621232cbb18fc51013d5885c70775827eec2134994169933aaf9f6ca0772d6912152589b2bbefbe8d7968f09b990e7826c1317e82a670fabfa84ebe2fa1863a5c8c4f87c709515fabe9c00691f272370c845cf2a9cd384a2bc2d4ac25872f39bd7e451e31d3a01a1a72262d7dea1dd4e8b6b51bd00ff7965ba026abd3954ce0f59e5f99faa1d0e7878e1feaeae2d29678e57cb3350a3f7f2154671824a0c979359c803b6e240ede4ffdc53d9c0a3f39706a1232564a30b157a17d1c94f211a3e17a1395e86ee55a43e8ab66cb094ba67616c339e92daa0bd85269f5fc072319cc18c47e6c8d65063781866f7c50830c1b9319488d171f861cec51ad8d77203b9c0077f81368309ea5e5fd50c8e4f627f53c354d0aa6ba0206eb489146e57eff898f689fe4cbe0a4bc6b6ae04ea79778d2089b19ed4a11687d01f7d5e49728c74288ade0a45d4cc5dfdb0515f2aba1a01130bb71fd80e95602a509d9c757200abdda52c3fb00836a1d9a0cda168b6a73ae641a100ed7ce87c2e2b62ba4f72c7006c199ef6f67bfcc673721d737d9f6bdb161301d81aa39fdbb78e6ee1c372e195562f8421db440c0559d19aaa0af838eb07dfbc4f93d142cff29d2943601e3177b2ae8c19b2558ad91b4e5c693c4c4d88770a407295ce21eecb045c3a9967f7f16527455531d2f48e6df8eb4269cc6621e4e83e15ad8d4ca343c572cb787201074458e49e65c6723c460365fe7753d98ae0a8392921d9a6366a3e78659746ac99722475025389786a4239b0da5b9dff5021901ff4fdf05e5388d4326bb37250b07902053868474aa37d4d70f7e55d36aff313780836dc3d1a28e1f390ea720dcb2e8067340d37f1fdcb1e3166e4842d236d350974d6cf524bb29622bbfb7236ebb68de6a0b8fdc916c62f7ebfe7fc034d94ab473d4fcadc7ec01c08dcc83353be086e3c00a1687f215bdc7788b51299779ac888f7d92c85a95c9a8c7c5d72bbfc2de8ec673ce091052aa6261049b03493b89df6c08eb809f92e7511265972a552439db12891d1c98ae39095597086deb4f8ce0bf29ed6c65082fe6d27f15d7653a8bbc69f7e2f051140104bb7d9beab9a17c6c5f22e84dabca7ae4357ca72c8aa6d8be317d87f6e4adb20d2e1ed740a606209bc881708b73cea41b9ad274fe7176fd3d566cb9bcd6cf84a6d0a0aab9dff0721d76cfa4ab2d8c5ec7022dd4acd1dfb022ef6d693a3702b0ab7b3552248039ea4722954652260a1a03a7f9f72d84de2a4f61a54f6a8485d9b68159fc36c08c8f2d8b77268581080b75a85de72548ef91008a185ce4258d8a42f44d667f027bac8eb32165eecbd25eeca16fb41439d27bfdf4b7c1556fabb90dd8041626240a06accd69244245a1185d8ea3f72db8ed5d4892aa29373eec245a2085e5d590f96a2f6c62ddcf346428359d011729651feffd340b1017b3f1049b59ee99d23a944a5d9ddda4f3b5d0700b8092c102cef5f176eb95d2ce1d20edbf48371aa3c33debf415e3647f0d52f667ad4a86155a37299c96653286a2646e407183033e5d961ddebbff4e8a490fd1c02e4f172b6501594ef45a0837bd5714c3a3827c4329c907719b3666914b053d94ded4d726c3dc6c0ddc3a4ce956dade17c4991bcc751b7fcb132096b7ed0a6aeace58472e10112cd28c82848c03564c23fb7ba76254fec4594a64f03dfdfde2d6699297223dfb50a756324586dc37f2e8a45e73a4fadf73951683447504330ae18861b722c8e07a4f53c587d36bf8636722edd768f25216cd2986c58111b4a8d7a93d272ba5474a018fa4abcfda8a1db2ceebc2d7c8b10ffb49f1564c907e1490030f172d267a2012f2a23e5b1d200ee8b6da0cd4a604ea7f27d28f1c8c484d68a21270aca2ab10df5744553c70cadba989ee982d70e38f26151bc406ea0e96e3b24a86e5638a15aec3f05a91d70034fc8073a1df0c05c7da593fbed777d6c586fb47f72da66a0d09cd146d292ef7a8c4c332b04b23674744dbf6b4badd5ebe79f5fff2eaaf12285ca62d4881823991ac9ea764c3057b4676dea3793181f48cf8bbd8c727267a251955c9dd2d6357ca72091963ecd7d878fddd5436f3964227e558e7b72491017d0e6a18a9b5f70a32f2e2082f764120dcf97a5e02cb92e9fe89284a65d4f26c80a8cd03170e309b941ec8a0c3eff3979306a234cc480fac6bd7e573b7288e65066c8a5ed45090ce13d350878c8773e129772313ebce1b66965e0b82d07ad9df89b375b9555d443cc017644bb7e513e8a572d46e1f272f76d91708d5556975a1b894895d251482c83d165e63348e77e5edf2d910a2d123b3eeafac3a509b7cb99ef8911a4ad0be0b51434f6c3eb32491f1d2f7411484b1dd41256108272b3d54616d7ece91f9b609a8089393795da84b8059f2c412d2613a947e3eee172386e098a1cb13c4ee557fbaadb0bb73bc08eef981727916dceb86c73f0b94e72064fcbfd9e0f8623ce848ac118712df772d95618f791994ccaece70353654172239a5522ef52680f282ab73d60152fea09a88be7e5922fdf5a373c9c71034e7292965b9af72efb78d7d5d4c9a3eb16b6b9444e737c31ab9eebdb4c1dd53a9a72b30c043074ea6ee1874d353dfc1a776d4c444c9fbdb8e9b63de1c92687ee4903e1ac51e0dbf4be32cc4d6274dcca4ff8a6aede32f2d7dd90087b1f966f7a6e721cb83d126768f48bb7fcda3d5ce603cbe43828fc71005166cfadd2055e9aff6f5b0cf70fb1a94d5e9351c69fe58f9d319478cae28c0ca7640a54e2fc6f99c3729c17e4b24d2ecaaf8b3c744a3a988370b4c02008d8bf66a54d108710d61ea16e0d8ce7bcb39551b7b66ea734d2614dd6df7a7b8d3241dc90956e806a221e37327717bae3cc30d8aee4e8e2e23563127649e0498c9f0f3cb58221f582e77d74296c6b0d01d4a66656bab3fb4fe9745833a14e0a00e9a697acfb10cdc17a2999725a8b90b3a1397b9db4ab31f5d359b1e9f359895ba82928d0c5197b0760e591724bd1e83e5d9203e758a4e6ed4502d5676f43be3621bad6e1cd14bf9bf6f1fb72569ca86c6bb46446d1ccaffc8d62de6a80f15d9d2ef16030bd5a9082ca88865b9b9391e6829778fdd78643ab7432cdc67369d51f826fc9678c21c51c71278f2615775ca6bd2e03989dd6adce9c4d5e1c42319c3cfb232bb27821c21656e2e0085a671284aae4de6b8b0bb7a7f0298bf2f213fc10e41c11c6ce0bdcad88fd9f7280406fb88af27d269bc476fee8d20a6238cd285a1df4bfa42220c62278e4eb72a417f11d3d87a9d2a3b48ebbfcced8d68d2b5e1485441087e18a99e729350a1dc615e6564d5618e3070747442f72623fc0aefc90ea55ed4fb11657e338f14d72b212ad3eca16f9512e65da533db155947b375aadbf7657a7a77b879203bd66723ed22624e08d9ad4ad692237a1f387b640c3484eefb37ac09869b4ceb7513a72dae07413c5b0b07bc3d9abc1e4cd45e3bff71e51825801bf64aa397c504cb603d64f5501e2c3e94f09029206e3b9b2721fdf4dc5445b5bdfd2d531a985b83a72d2f8a8580cfeb69a249232aae03e7842dde7e88c46cc94ed30c8304ae0d3f01aae45d2afb90ddf904e04fc634fdc911596674580028951f9ee3226e90e161a7286ab1ee85b21bc2e01b766aad890c6790f30d317e693800c3c40bb06f32c7b6ce467f648b68b8ed5ad9e8867d562eb16f4bed8283948157561115e5cb218955b40a526d5eca198aab5dd3177291412305a3b86c607310bd524bb65cec1ed497205c3b9096e9a68fc157951b3a03f8ae7139523d0f7fc6b3975daec78496c0372c9d2e4284e29e7becc60b9319381ea92ef67772851b2df293a06508bd53cb07285f5424d04ccabe0429b3c74deb505ae104ac90d1872752eb5d9015557a950470c1478050fb1044d5f4167dcce4d091abee7e5d65ee6df6e97414c67952a6c725a5c61bc126d6b0b8e2097d83ec04c0431bbd3a2bda4d1a942a27d6924be4f316c34b92c9544dca47532f8a318f7cc748e0da126c7fc03239d7778c71ff04e09b10ce58d813ab9b108fdb32423831cba37d20c9e216b3c69c441fa18bcba84726982eaa79b343631b67569a852216c529b2dc3a9249b12f44b2590a2e4275322b15e5ebc0a7635fcc7b6a57838e9f1d8e520c27db861a54d027abaaa5388c51d1527bf579f07334e16c45b61448f110d96ecb1d3ac7a04694eb819e2bf43c837421d038659f37126b0312834c3ff32a755d20a3e58416344138330e961fdcf7247b451d42d43bef3a0bf42cf75ddab476989f380e6d46bb234c20cf2c0484364d89c1f185b01d809252a0682dc9943e29f3cfae2a90d93a484285f7a69997611a390a22cd0c8be8a86adee631bdb00dd4c5268124636a8ec1ebc357676c9dd2e838c9b6c61775b941a79bb1f4a2b63c8ae84c9c2f3e0c9fa43feca66dc0785456d4a1396422c2616a08224f5bf3c74c0967cc6a5a6b24ca777d9cf51a0280272e5f5e6b6ca438ee9f05f19d6b3c5e7da404278ae6d310aa45e5248cfe9a6711866993f9ab495e9467c12eea6c660f1ce7235ecbf6e877473cf1f4c24a38225586834a083baf981d09908850482d052b4666cade77ac490cd46489644e64534729f1361cb5b8d3d9b45822496a7b9a8eb6658eb935340338b7e41628ad9a7e17278eafedc0181bdf048beafeca112a3c35817b47bd960ab12017307625b5bc572e889fa7d4ec85cd4e261cb93c9161958087f58a590c8eb01928b3f2adcfb144b9affcf4567d24dbdcd7740d2aaa239c01ca2d8ab9d65279356fd7a3ea80d706ceaa0fd6a770557b87207861c63c7cd65d28485aeb462eeb91b26b9a601546f31bb914c8c9225a822d26b8285336ea38779b5fe297c59556807b97428508d0a0ebb3a578be839c2921628cd724b8184913027ad56bf954ac8518212c366ac68439b391679a91be20337d22d1ae2799a1e57c0e99bf8e66d63f414e4f3f5147b378f709b4bcd72b0978aa11a7713fd763bf8bd5dead6932ccba3a7b55224f80a37483b7a2e1364c03c9a0b537440145f72d3d3ad154fe89cf49c3658aa474842356347c3159a2f6c87bfac8f7b62a3efe1a7c93e6e3ea06b97f8dec71ddc15af02b4e7c86e7a506578c687af76db81b6084b1ab8cc1d0546f490b695c5d2feb172686d4d22ff84f134f556cdc36064e44d9510574392f1fa019a38f656f82fd517ecf42039f1976e6c819abee42a1e0388d8002ba0112af301b21ca11e4b2a0a278b9d480b304c2b674e7e9288ecd53b36a347498c118c51185c1a97d6ce28e134e3ce66578a85c58ac8fe14171a174fe60300488759746ba8471cb675254279263babb0f40f6f686a50552f66440b2b4cfe5b0da9678814a72b223c0b9838396cf5ec95a364b7bc35e7e581e19984870723bfb705e20ae57731bf1c27c8a271300f920402352219e1e97f68e8dc0327f047e919b4cfa84f094e67744ef69b9535732dea1e4d9c2ee4e2b9a6d1e60dc56541afd9dfeb58fb329bf3fdfbcc620662d3a4b7b44b41f9009ed3b46badcd324c0927ef78961d766b4fc8880afc5d3a587667a6ec4ef6534f7104d53890ec584597c01edf769a9a9d8cb9246a9443d57227881dbfb77f8f675c9884138ca3bcfd27242e390a1679494415d5d68209cc72b4e885d32760edb86b098b1746d2ffdfdc06065221fb54d013caa1aebdaaaa72b9356967a455360031247f1a021d8de540e1651062b71a7a503cfa75ee01981040e81d28923c5c95bdab89432b3a98023bba11dc4d766b5b6479285ed9f5c350cfe6667d8698e3f37d021fe57467b4fa6fb6db1191df76272169f83ca0ddc0726bb2c461070e787e76a539a6024470733b88423f9a067125b3d8d8822219841fdb4eb190891f4c8a84b6326ca93381eaa27407d81ce0669dfc46a5bf0312e86111d13a250f1665cd1c7ca261000ef7a7b1fe9f0a561903d8c2e21903618ca32d47b2da9415f54fe88d623cd5589190ce5f7e4174a77540f6f3c0ebb3318d884a3d973b569e11addc1f3784d63b50973a1150fbc450209165874aaeadea61ae67b653e67310f8e5bbf57a81739d83c33a3a2947ee6fd8ba7acc3239a0295c7945b0b1a1c5a3fb4b6d1a66d36e0649d11ba98d597e442a96cf6bb92b7c5b7c3072e4345aaf15a26b43656bc658aae38c5042e857e363efe0774c5b02bc12b5057256622c18235b298e7ec02b44d0643d8ec6745d7efb8d3c028e157b732ddf4c489f35c0736c1d76364ab4f7b1c13eb0f4603ff0bd4c170ba4b4c55752d29c847298b052e4472748dd01d694f995a99f1ae9646dc518da1f21dde55dd59dc2547253f25d0e477e77b9dde5432d7f009a49b82691ddfbc63b8b9df76ef617d2393c0f6690b961e0162779925803a9b06cb1cb2f95036f0e2d147e1c27f390676972da161f43e7ee79c20159426be4b22e56f85df68b7bb09d5a3dfe82af9d1145722ef0a2907c27783d4ec026c1e3d719cfc2caaf6e17c688ac346ce5716156543cb6314abcb14b0cf2b014072e7f9b58ce0791d2902acfd1380b332352a173b31f2129438feebc6f1e13e14ca0796f610f24b86dc5c503fbae82b7aec636b11872756d8d91000fff8b48f81c66de7d0ce5f632514a46f049d20198c0254e3b6f72978f9d32d1603f12d0b8182f4c4adf0b8dbb16d7900eb39992a7389fed2d6e37049f8a194ca5a8c33015a71e3f2fb4a76397326a59ac6bc24a137637d724845618b54121b56f733186536c9195c62f6cde5ae31eb69b3c08d7bf0266c6856a72bd3ba687b0b30f9a70562ae10c336ec56bfea50dfe801b0d026ebb4e8bfd3f72ea3d5041d9617351b745d2d52289b44ae0aeb6a7c70d69f9486edd94263694726aff99ea5bbf974d8c548fabf6aca57983535c0f2832f901f45db76da5bc6f72595518dcdab9ebadc3c0855d109ea4bad2ae1a3a4482795fa34a015237d608721c73709dd232d89c34091d0c0e47e7f67a7a07803dbb000e9bc3e26248cdf43b3cd9361cd7845d3568f0b73c1d091f2bc6707b5c2ad7bf399a49314e1b812e724353ecec686df1d30c312c5a1a5000b0481fdbdbe9a8cef58a8a4c40646ce972e39fd564373976022757f7fbeee40a8906fd2d95c77a76a95b4eb509d6dd9d7208d999c1c2a012143c7d2f7d8c8348e8cc4aeef194e0e5a225666df68630ef723247a70080e99c60d479eac344b6dd8942e346d0a2cdc9d9d0a350ae7b175e726d354ed4eae6ad5331a79d8c619a1fc192b69d9d216f6e1c9e58887b086d820f87e6f84057453902822dd01543a6b6adf62d2f85ae5ab9c539b288ab6aa453728f8f786511db6bafd4f71915f825ab90029109075765a294edc234d6300aa13d980db4b320d735eee3d8980aa6bb7d079c8a1755a72335a3d2f685a8f687067242e5502383b36fd20f58ab2a007da3c9d69bdc2ac621c4415f820ef0157c6672a5c7fe873ee286448c73b73f627a7150ed64fe6c35e437ff103a6972bfb272723b37cc0d824275a73d4b4b43390bc780694f80460b7b91246bca6513f4622968c653fa5b4911dc716dca5fc62b86df54bbcfb8bd7463b093581d217fbb6ceb725d38723f4ad3bba884d448539809119eb7a6f7e522677a0dd1a5d26b8cc05572d9116d6a1dd45dc17d0104fc3bee966fe84af7f901942ffd823f407c60a539089d74e0d91333235bd99abe4a84a6c15f72a2bc7dd3caaa1c92d2110c0f3d7a72c8d9d1bd6d7ad8d1e1cbfe5f21df25fe6bd6c1b2eb870e23d8900c7352ede57262e398ac525ad27bf1199f0ff99cff7c3d0da52c6ac5f73f68f9530455022672eba194c65a3232260a77d893569f4d5ca0c97b7733cf03f49da57b4cc32d2b7209c120c77cf7cae9b8fc5fd88fcc032a27d739bcb3f91c1ad614b0034bb2d372a8a5075b7b9c2c32d4e4c2c297ef68d6ca815f1620c95612e9ece61768e93d729136547137aca4d4e44557f93b691c94a84c1e482dc5caafcb89e4915f7ed76e1291c02c787f7895400abdda5312a0760c1b3e685b89e2662f6e75c4924b9d62db17ed2788802db345e3a05a171c4031c7b9402a00fa622e2fab21a9c1dd6572d3457f5bf14d3452ea843e53678d8c1bacf42a9b795d76c1faa79afff139d972679cf61a3d43d07efcf2d23878708754842807c16fd9e72ba26d78024633634fd8f3b2d62c9466a928644dcf35b7040e3b90a7a999f76af73a3eb23bd5c4be3a66883b7a6f97a36bb15588ff1721bfa0e306549154db459c64ea06a400e81372fbbb2d347edcc0b9ae57cf711ead003ca254aa553fb8abb3807bfe19c57dc1723205010701bd932394fa28343ac3b1366beed2fe88a412f4e62108d8d21c52222589f5a3a3fa1f19bb73556f8eb8f57def8e53eb6497e03d7c678ffeca2a990058442bb0b5f16347c70b519e66db805efdaf045bc8ac90dbc26b6d09fa198672440f107479fec9b33a89da31e5c8953e1955369a19a018f86407d88a5fa95572c0d2de95f2cd2f999d3ac46d25e10494c979036e561dd5ce507b453b6c42ed72e50bcae879e2d2cbabdfe4938a6382f4530fa396888b4db0eb147d7c88acd57206954d0efea86ae03384538684f6f35e2239ce8574d544242219dbc49c3368724c4871aab1145fc30d7a7329840314df96be0b4c8731b2f3d246aa7b8df91c729103597eadd811e253078a73047aa3b5679371439282a3ba4500241dafe2516236d821547b85f3cbe20ef386396df1e835f06ee40d62d9d89427086368cf2661d44ac415ceddda02ef2a453e80b3bb35a6bbfe37a829fd6db87a2618728a36545c3c84e1446daa6cc30d2b3e71a3b786d05ca757973224a2fc9576ac324afe6f2080c22f81b913ceb780b3493d72531af7f25bb04bdc4400cadaa96ee5e69d72ccaac0f8ed1597b90ef4dcae09c54628375f0978be2b9a65fcda2f235193ff72716fcd47ae409d6b368cdfbe3b2c2f36cbe9b23f50c3f020171ed49b2eff2b0e6fde9f8066257ee6d6bca0ff5f13cf88ed6c8b12a259bd836e3a7f96a1704472aa95bcfa19a0ea5667aa2afeb8dfb241bfa090dc968df96340ee19fe988dca722b7368c1bf10a44c5207b58f48cd77aa37494885508891d6f72466fce732e0728d670fa0a47ba169688650139ae6a836637fe0bcaffacaea20a406ab10121f728b2a9460030a3bae1d59a7e6147ea0f25c0b8f0cd12c0e579e1e0fdd6df1ea47e3b805d25979b813d2615b92acc9bac0b09fa31e742904c3dfb53e0918fc5172a372996bcbe72f85b2052fd66507802220d6bce610e5537365a09a9067e2c109a941e43029a6c3fe97a7808168e9aa8ddd8d10aaa0b3b0e4d2cb83624da63c72d6ff189a337a2304f8e578b4a826488ccd8953363548b54ca501805f677c2f7273f6e91c439ac95fef3367183f9913c0ab89ad269ecf6473829f98accd5c087222a5e07f3c32adb8d8c8cce4aaae173509eebe1941d10a7df374d6a91c3b7872763fcd9b60e3793a2adc75eba3f90658a65851555bc1a44c88067cf48de8be08ee3627128904536be76d5af52fc7abce2f57f721b25f64f56f04f2d7e9ac1d3e737a1a229f4ba058fb2183377ec15d27b552cc5389147418193b16fbebcda6726e018b24e7f55a581f0bedc630dfabf5813857f6d1289272e3424a173a172d729dfb171f08850efdd8daa89278bc1be392f4db5b06789bbf6976fc24eea21f72ad057d3cdc08dc7858b4e10b52432585861f4ca774bc9ce0e874711114389763fa5a639cad04bcae53cc4ae0f40144efab44afaa2d456946199e624141388f723ed75b699f190cec95bbaabc40596e4bc4b3840ba3f090f454df5e1898039e725a4a54c90daaba3f78bb181ca4c799d861f7bb72ba177a8ddb61fb909768b0156f1cf2b7df0bb36a703b9eb6ff7e8edbe0751d2665b8597bfe353fc9c448814db7a8bcd802910bbd37338f3a679404003dc8a6240d28f3f4d5afe87b03413672e2254b927bdf2decb47a0f8bc3dd53775860dfe6eee0b172e417d2767b979d11cd4fade6015ccec2098486a6cbdb62391881fd9d1911788119e437c896a4f2721249133a5fb83c41dcdaad341f7f1abcdc0ebf730410dddfdc6a80689bd761728c00be915a1d29b6777bcabc6301501c8aafb1518e02580700f114c158cdfe7238ae4856acd39a2e2284410ff353237ed8efd866ad4434902985d6c00e25d054e1c74f943155cd6ab6fcc8f91802a1252c90055270fdfb79156d05ef038b1831019478854d96515e64085c61aac7b4f779f62557be630f12ae22a65454f91c089276fc7ae84b4f0d348fa4e65b0bb07d8a841dc305268ef471b95345d484df5fbe582b12bf9e6b96918065769f985c74be16512cbc0bc1840a3afa1049e10f18a60a955aa3ca4c0ec424cec863d89845bda76c87930f3b32cce9df9515252a722079ab86f05703508338cf0d7bf600b1efdda87a27c602febc0fd3d56af4ff723a9cee725e52b9e348ad894be1e6bd4cc7274b4cdcc1151160de03f8e2544772e8089caabe79159c16b5ba931a365a9d1a3dedec94e3d18bd11f26c433bcf62b0423e5376753b23896395fb416a306c0ad9cb92aa8ede3af85f7d9514751df0f725ec4d2e5c41cbf02848215413c1c4769b190871fd8c1dc7fde7fd57f97626d0a1d832c494bd7a77e10ff60a73eb512bf433f0dc4359d6cb39d158e87579e7228f909f68e5caad13cd64cb77ce347e6ba88e82513ea42a66f56e4ea7a48ab720080b254248285c9d13f5233808ab58dd54cd65a5104a6c635e6f59e402a6772c1e96f9fe275b795f52307b1b06fe5491b9b49f30e224d761f6e91695e6cda5a285a9e7aa224466022d2415896d0e4e914e2cd22567feb16a35a56980ed6f272f5f1ba020a8da209f67f8852ad97fa1593bd1799e7eaa0bbae7e48177ebf355c09b28533e8b8b8450fe97be6cce4c367a27eaf8960b214942cb9ea0811e94d32970455154e7b4dcdb9c444883be26996801b17d6be24fd5d073608488f293672365abebd3befbaa9a3e649e180b4ef91bc2b18b1324247b9e259d0b48d2bc34a14ffd2f06bcb169ac5d771475acb8329977bb82c527d7e1247fbfc9c4333dd7273f9602f4a6d635f7c0e8c6262e8e51f7cd9039213c44955ffc66dc7f914b47285f59adf253bfb75bd8d5ff6e38f72e0e8d82c584f492d2512394e10e9644766bd87227043d1ec7f89df05d22509cbad70332a72c34b4b498740c085790db972a8331a641cd690eb2a28751651107b7ea87c15293ea92b85a5779439a74f5e47812f454bfdc0c3b2d98090fcfc76de8452b5290aec306b2b4007109ee3006172f44c49d17d861e2d7f5335539c5d5b0e4fe1574cb9524bf9fb3db47f6a8fcf72936bc42dd259ec3525bc6d4b2e7787543ebbdaf91457cdd9ac1aab57b0565f724c95fcb266abfded218714db31c72a16ea4bda8c1737064819a34f3a2726e17279536fae2d409d8dfc7c513f87954325f04a87af7f76dc3709eb99de47607f72336477a6dce223bdd5d8687b6df375c81fdf7c2ccd8ea231f961baae8a028c501918640bc191617e7d110feb22b6f3dfa8432fc9fa3f7eacebccf9e97963572c5bcbd93aec96012be0633e8b51f1179aace0f91257d038001f669a8e0879e872e2bf826b64c008c89578be2e5b5cac4a1da89d8122e38faaf8b86c11615155728aaac255399563846009166c61bba97f73c91872743b69c43d07f7ea26536f0279173a60a7319b2b64bceac5699bf14c409621f038ddd4ee1d3807335a57a872520db07c076e4671ae9cbc92e848ebc674b5bebe672632c24482af8be6bae24cd1a314845317cd4405d2bfe9b3bfdcce663db2aef00807108e74d90c0800e0382b2f246628a78b9dffffe3e91f574c235d4dc3f1c9610ef0fd5ce248991837446f6344514d26edded5d3d19c3e1479fa222857472ce452b6a6d3ac409e644d53c8e991c536e592e4a1d7f207ba981d9e9da51f92eed8649aea98b2b1bbbf247210ad3b33f5f3c793c3bbed0763e57e60eab8068103b9b17ff7854121d779877254e2592808e022a8d7e8eebca71862cf02e26bb46781fa9a0edfa3daca9a3472c9d236126cbfca3cadc8202b014e21981e79da7ac1b894d3029f4d12f3ee0672e72c2391c54c090d80facaa52394e85f58dd3419d0544bb017b033c7eb04207241d519e68643982aaef8b4b666bbb2c148f1c8d4eb8e940bf1ec831378be3272950c4c3df35eeda881d0c7c12441c53c4a6aa2773734ecf7c9e7015b1e3f42728fc79d1ce8b62fa25671dfc7156ebe5962881a738ab71b90b6aece11c1beda728239bb8914da575b13a8ff4af230d307b2fdb3e12bc1d66d147e23bbe4136072c9cbc43e3e6754fe6f07969166014cb67545b3b81ff07dffe44630e0821ddb7291e9022228989387f6b64d2cebfe89e64973e4b2ce27a3101de61491468647722ce99cecee8eda14a19f5c967ecc01ad532945b6382945f4588c5fe2a1012e726779cf0d7eaf73fc8cfdb603018cafc29622d8a1b63fc6aba824571736054472992caa9627e878947b6f01de4b224c9859f068e81357e10da50e08db0a3d1571fb971b2fb9a673e82d75e3ac2f847abb2027e50eba086c92683f6741f3996372dfb134fbba2c7b76a5f133d6a5e045509450a07396b7be08c015b6511d461b56c26dc9a7d52a6cb4c6fbdc0a02bdc3be3ce4dafadc5edeccbeb98d35abea0472d84796c0ae2e79f1472128d0fb74eb0d4b0c516dfae9758eec94ae5e1989db3d57b2ddb89809adf2588a7adcfd8bef61bf8e77180c22ede6c01a7cb2fdb2954d70e1b3e85350c60f4110eeeb1026e698368ff551a6bbd59f1fe59a86c04bf87291d425d3793de85d6077d18e9d52af825f77dad72261ccb353009ccc3301d772b9549aeecdf2b6ebe2d1d7fa6ce4b593c654646acdc3398e325129dd680bd472f7484f945014e3c9ae4574e2e49a1b0f75d5bb8c016e9ad8ebe20c8377f0027233e21839a7dc706ed6893e7ee5846c08bb6b44b12fa6914c6ea1f36fdc4a273876a8530d8320c42576110ae85af40bf6ad50dd7af8074182163a76b46d85cc72d4db60be5552f3ca46018cd05669718715835bb35e91a24113259369a567967283fd2fd327ebebe669b2096bc03020ae37ff14118c76d471ebdbc8925a6f6c72cf64261cf6e31052fa3847adcd9d3cac0060a55ba18a16eb5e3bf8ea27239c72be6c10ea52642118a1c17e6289c34152cfff0c2a64817a6d25c8f51353fad7299bd818b81e7f175771b863cb3cc4f670c288098d50fc3afc74310c852f1717721c207451820982440ef4d148bfc568b53cbd2b18452681866b5e74c89841ea7245ce7737aa8d78a3ba54184ddfe6c4ab991be55ac2f3a7422f343de5284de84d801859943fd2ee5c0b77196508712fea83d4b6772557366af89f6a1a9298b5035b652a17794da3e9d1b144b16ffcf60bfe1c3984ce6059ba47601ad6e5b53172ab30269d16bc12bc30655bcb86e9f31c36781370469b2fa31720d2cb08434f5ad7fafe080f35d6b413cb05b0c6d4570529aee16853e6730d950fc2363dabe967b6427eb0ca66632cb3194254e2c82b5102d8a3fbc86bd2f9a0e364e30ba6f27241859b1e6027dc8e3ac7ff70ddf70dcf9e54c770753e8de117da1321fe4b96728f0139150dafe821787cb25b268e8e219cca0a295672bc1e7cc73b68fa9c7072bb7710d23e89a9a6bc5f84b3e239e315e2e31a305691c1164e403c5ee148056d017919c5415c22809290a325dc49bb8949c39144667289b74eea496350bf08729ed29c410aa9cb482017fa87f2dde108e1541cad584abe7bed1b0781b448a37224d5ac7c78e7f37cbcf28bf2520c346b5d5b16dda8f4956766f4ed609c6ec872b0f2b6361ba57139193f75c57d12c968bfedec56ee460126f17a28ef5e231872e6ac5671bf7fa1d0c021affc2b7f9b42f8e3d9845b57da437a8ab5a4bbc08972bc6c6e30ba74cadf70d198f59008f11f99a2e2a7322f021a663627874714d75e66192266d20cf4614ec882011a73be84cdef359f0c94601ca8a8d3e0d4445626f2c3f5b59e574d208b1714a82aa8d362863b0886282ee05a28eb0e3fa193c84657d4e5e32bd67506d486bfc7c89feebaf753a3e5c533550cd74f0c09f7918e7283f980304d3973adbcd2bf90d5ba995328f1629cf0ec60601c870aa6a879096edfcfc96a50b63fc3c9404ba2efbc3a3f50b8c5c0a856cc6c2a40cdd1f04a7072c69d6919aeb5b8219b01381fc13e0749f3865f307047475e4c7e005251dcb45d889983731eed97763e457aec2d2d1a69888315a414c3a3f6f9b0a6602ef08c2b34d836b017fe843d37ef7d84006067cd3b8f9b2d58d970334e86b1ae29fb71200fed928033527471330c8a6ac37613034dde70c57432b30196d0fc1099822d7229353602f4cd78727b240e94e5b2b1a2015eca54b306e566cadb4e65ad74e572f8d7949884cb24863202a2140523d435c57be41a3069b10a7aa87a6380382d291edbac4b9c00534a174a894054808857974e82c5e96669fce5b771556d5e8c48c9f72f70167656fe7dfad6e7b6d24da97ce005e728ad8bb95509f9f7fa1f590beb54ca5ff3f4aa7d46ee33128dbc898a9f04260d5438cc01e9c1b1cb2191a87220e70a7b9f16cb24a5d17e9c9fcd53c255185af7c0da788aa5ac67d64444aa7295b44aa8a1840448d962cc2e98be950add14f7fe73a69bf28895ebb0b468b8500173545a13aa13a43671bb3f585e541a89688678cc81cceed96f12b5c1f27c7223304f923559d2be57cefbdfca913f3c456413ca8654e6f1aebec743c09b0172fcca9ed02b97170414fb138ec84cd0366460afcab2211ba4b0450b562abfd46dba46cbb2c606a7e6e552ae04cfbe8bca546bb1f43d7db21ce57c0bc04f6a257220225866fe56eb73ace1085f6602bc6bed3fc915cb10562f67fed443ea354b5d69b1c72bd621d1cfc404a0d4ea838e84200d29e0e578c3b30ae08811876dfb72b5c0b4e48b691574981868a79188d700158f9d85b4941c0556563fa5699c1a45b8a4412f24e419e4e5050936281ed1bc7003b69626ed1d051e86f5e714bb5672f951f8cf55c4c0621b691e9e524c5ce8556ad765b5a1c66848de1a51c8bcfe72b1b15b8a3af94f9e729d9cbd105b8abbadddcb21dc528f4e99cf99b44f41a071b2950b42e1fa24b7633f346899b9cec1d25a4e8b0cc519ae783c8a9a84b93a193a0c5cbf2c8d41fd1bf0bbf23a0d0867c0ae31246ff1f4808e03b0acbdc93a0c385a5578f3ccfd7fed4dcdfa726b34580c6499c6a0f32f48cff3c58f1d488e72c2cf0f3135be2e3b6ce11d0b2803cacc1d23d8b38e8a558100472ce1526e3f0bef3d60ff57ae616b4b09380360e021e68f7f43db5fa2e8dfc609ae70b5c70e72d00b92aaef397e6c274acebd1a90260f1b397e3cfd4e9df12281c9d86f9725030b5c551fe0b80fb415345a5fcb387c30b45ccda00aa282063dc0c4cbcfaa0772a03601a307d24397c7d5d513af622afe2f19d7224a5720ea3af57b50e66186724b4b73d35670995caf2cffdf23c887f1059af97970884f6234900cef51222039ef3f8ba03a11517837ac20cd6a1fe437c0998f9778e16c48123628fe025f0c4401fefbca34ed2fef7b554970e50b35db5ecaf63352cf6f582bba47f629ae1263295672e174f2d09148328d417c97312c118bcd9d054b80bc8f050cb6592000428e70d88f768e1e1de144943ceb6de1a1d31dc8a6c9aad6c685b1673afe87c0721037e6f396ed260c1fe77af4687c53ad694ad3a5bfce7f881a3c2ec00910b17245b4d5369a8294ea78f7ca4ba317c35f7a25d95bb9ba116d4d95af661b4c8572e20913a3ac0e78c9dd37d8d95912d7976a2d3f9b94d0d7feb7cbff63dd93fe670822abc192e4cef0f25ec8877be7bb31a9086d4d16699f4bcad81df215fd85419b1afaf52d2c9915a4c239d557d1adb25377f7c35f40271dcf72d397e55e0607ad8b97b6606f149542e9c1790158d9da8d83dc8aa0f478c9a0b8b02d9ca9584c4ffd8da73b7e8f3ecdfde2b0078092e82eb01f1270d3caa82389640d90a1c558e08d08d3ed50434b93266083906a710598b86fe39c5c2e1c085854fde6913e7278ad91aa7e8ec97e542feacfe0845344c3ccf640a24a751e25b02c5aa756723b746f83b5e68a2b7903df64fe4ee395688cfde95907fb58298ff636d425e54672e50d60fa1803d1efe66b849afc522544b27b23d927b70ef6a3f4645dbcedf37275821ffa1d3bed8afa054da9c25b4dcec865b09b40892f3c11b74c35ac8d1172cd3e42bf1934861a8ad7469d3e0a41440c0e54b6e67c0a3cfafa086f1c01c91a6b19f07652b5fe8f67ec2ba2eab98261359e9ec3f2d82901630cda7fc160412eb83708ec430a18a543b20e5ffa0c0714f57eb1cf8b92b5400168594b4b0983722840f24859f7891c220cdb9a9d6468e7e8c60a1d973251fc4ea24a1a8296231b6e26841109ee95e88fd5d26fc0d2df77ffbffd6a6f5b85c3065e2460eedd9e72e10e0bae9be79ca0f7eb9ad3144205e49f79c9222e93454cf03ed16b2c7e5d01e5ca637a40132edc2a9769b6e182b1193067636f86da15672372855c81841472e0f710c55168dc23891208c6bbaf1b499f773351dc656b9da97e0902012d31392abad6c3efeaf4e2e76e771b62c3f77352f23e9c27c6267644f6073577bada384bd523d3ac1e2ef8c8958552d778d138c39873d8fc55ebb9b5713ca9ee6478721af8b7c9464279d36c8818cbcad683d262e609e936bb75a2488cb805e5a9512fc261fc47267f368f3743f80c88528d316b0178273823d282a7addd2d139ccf6eec8dd28ef9158989828e8b964bd4fcf983a9035125237f35787177cefd929172e396daf9f433a6f82cb2854b7100ee0b74b0bbda3b71f243cd63d4af64e10e2cdcc5d50d5b6f5b22d4cb3ca754751cc9482e9b2b64b48f6bc27f3a3adc8da5722c66fcf2e4dd7b93cfa2d7fed2d270ae162a178b90fd3f2670ee3c5631147472526ebc13207303aa12f582f48488b9b71ea859f152551b495fe38f9f57611d3685be9dad1bbcaa7ccb9b162c59516ea0256017271e9af8a997240080a202bc18714aa474e7a6525d9cb8ff7baa952a8ded0ffbb23b13720bbc30ccaea607ec1f4f66d66b232e5b5bb3b567eea93f63d4794380be378185bf95d85d493ee9797295a19c92747776400385a15746ff54c4def747af120b8cacce218d5606a4667233aa5b89e981be45fca0709442991379f9eecc8de1b8168ed0e892239c9fc26e7ea910be8c44e20335fe0a6ce6694ffd9bc15557abe240db06131417818a97728ff9317c507f8db6ad019919f4a6654c4dfe2400ae26beb6898fb447b3f8945f1da3b6ec84e22ed0c555a962112031e10e1d129fcc149000e28ed424343d8e64f1e8b73cc97feae666ba334d47f553dab5f8a5f4ae056b359b7fbd0c4ef22472d28339f1a35bb137557c18aeda54a0197ec26c29f4109f328a9b34a6f09ddf729a4afa21b1d433cc65bea74dc888145cfaabf88bfa42cfb3636165adaab561729e5e529679da38427e5a224ce14d9f1d751b85add226a7ba0bd57535779949725090b10d2400f384534bcab1bc30566114fb054a7018c08d693bb7be4766a17226f6b9c7031aba687f5ebcc06e28e7e01f786e317650041694030230bdfabb18597dfa2f7f128a90d95c6d44f1a08978fedc943da5ae0cb105d3ab7022f9f2728517c93ab103fcdbc47d2e28d4f64065cf235afaf0fa5f76c10bc1818c956d22a8a47db5fb943614d614db3f658b3d37b7ea45b7971f49584a892ec7ed567b72939fd1d55da02d54b73b2fff318cc220e1e58143390deb1bc816c4574f42c1724fad30e08ce4008d25f57931023c747bbe7cd9649937c7d8c5c2aa640cab2872820c036d4f3895cd396c9d78c80ef82644e498cebf4149ec7cac531749c6a903cb5b4d61a3c4800fc8104b9b7ca82396e91984e582a87269fd19cf6e823a4872a41bdbdbe989b885e1564022a6e244faaaa9aa037720c17fb2b897d1b33ab7722ec676037966eb5e2c8caefa7a1661837108aacf9ff91776e286df284f7872726b85ded0107c3c15cb124ddb6283e1470fdad321cc4b4ed6c70afd6ec210fe725ff59b5db9301c4fe4ab2692987c412bbe93ccf7bff431d22675bd69348eb9725ebe17d443163704aaa0c581f565fb2b53a09523866010429d5d82b51cfa3345185bd6c3a974bf8cd74246d4eea328cd1e5a0c0933b957a47c7c42688cdbcd72e938565aac086ec1675ef75862fd62bb124e22feb317f95873f9b5310b9f7a1fcd237937132bc271bef615b31536b9bc23d803b441c826f22cf58758399cc67228fa6a96a3f19c347b77edada010129ace9af452d2fec6d87f69f2c2efc0ee219273851b1fc8d7a8eb24dc7d6726e520c8c346830a07d66f4bc4ee73ebc5cc05e95c74d18e3c77f3f81ac09a11f9e43cc363e3058ad84210475c5deff40a9a7275ab158936f1bb713a29135dd53af6734cc23ec9571128b7a4f9f9d522d1d53d1c8ac6dbb717deb344f16ef3d0b26d4a5ee4f3a4945ed4c72e2812e5f2502f1305a5c5cefc1463d3e87b772a7d07d5a4de994f7681deb9689b32429e182af74fdbcd0e14c6819cf3a4079b0a166952b4ec4c1267dfab9af4878f91c2aaf29872c066d31637ed592e99112fd92c32a431a4acf07348ec365c8cb54525c7314072f06fa230a6778ab238a67538a6a8361cfeaa27ef8064aa71694058277709d34c22785d4f7e549289893eb47b4dd1c09961e40097a229c5a1b07d882aacd24572164cf98079f181723fab17a1b58a86a884595fe55b5e0775d01623bcd4087972228d17d8aad47f68c7fea9fb3c22ba78d451ac918ccbf65ab193a1db3db6d17216a7e7760bda04de54f1bbd3e3a5c75c543180d010a1664631f91b2665ac2125d535c7bb1fe1be87aab6e3ab91bfccf926ac13991f281ecbb397f10ca03d60724e7079edc4c469b481c59f1b1f96bc904cad3684120af86ed27c03e60f8e847208785c78c8bbb6aa97df6896b4262dd3c6e1b77dc3053463a197ca877a886500653bf3ae59478cef1e694ccc1ea3371c1b813b491ea854ef6b5227dce982bc7250c966b1edfc081cac69d1dee8a9ab893f9325ef044c20cf14cb2c88ca783d7226cc38011007372d4ff278e2bc7dd26185297df242d8c34c83845d114f7ad7724d8e59425ed65c57b8ff88d79523f1322c44573d6826daf94da5f69459fb4c728994513e54d17487373e2161dd9627179d99b9dbd608d052a99e8013aa0e9c5a512a7701c56c9bf15c4a0af860ec64d8d5ae52b7b3678d8df00cee5bbdc35a2ac93df6ec74300d8e46bee62555e0af11a7453c96c63e030d963bf1475864b57206ee3115a85b2be161d8f8138076756c0b664d58b13d6557b063e1472fcb5153da434665261ae27362a17fed145291da017c7aa615886202624a8c3c15e53c26138be341e26323a30c1d3390a900503c19ecd92e4c13fb6c661a383f00a7514ce176054c3523bd3f0bdfd238a3f606658c46c7d8a79e591d695098272f374d5147bb10f79d817f83c48ced252e55642043e62855ca66ea72aa4fbc22b6090672256549b75fb345172b4949f6d9fa451fe323bb72f06cc8e9433449663bf7fb2c18ee62211383c888d19f3cba5ff79e80cbb21ced751e36370478d93fce59ee72dca8493872620c746cb25ac85e5574bc406886b1aa1a4de19f04f78402b05e2f57a1684d9aa4f66adeadd14b3d91e7ddc5e73faa9f336d8261bf0ab929d1754253b3019c16397658df9569adf6efa1a0438c33e68a68aa7257fdc827c1aa433be6b262f2af1594b11f0acdd9867989ff761c896175d3dbb71c6734cb8fc8ab722d3f4b0d28c0bdc7c8eee4dc71d574a6bd4b00aa67322d24c294e634da256b72d90dd2ee7d33eea85fb22bf517618ad6767c2e2e494219f8744a84922d4180722aba4e40b287ccdc3b5867909b46ca91f1c08ab36cb661eedc4eed873ab0b75ae23cbb37fbfb766de15da04c55733f6067e3686e3f7dc4bbeb6548692ed9b01f0444a2f2b23538d58bdcfb7af0d7ca6b18193b366d13dd15666d03e38ae6890b6dbe94620cdf14babca4c7367456893d74097bb43a9a2739db4c122a90360d1746d808aae1ea60ff2f52c9e93313173ca0a9a901da781e30b7572504e3082172439cd94067a0484e08bacd744a80059e6dccca582247cbc41126a28bd7e69b72f0b1112c73a6586bbb13eccdf7b3a52d2ff3f3bc7aa3568ad1408b3777598172446ea007df0431ac139f36b0296dedbe421129ee19f208e62930573fcd04fb70767c28d0af2a23fa259146f8457e600e9a585f4f39ce699fe41ff11ed0490a368b7c792a1e4e2733538ac2d18161175173406158d5dd5f5b8ba75a03dd13dd72e5a84ba8aa1f7a3e5ac85c006fb31efbac295871ddffc963ac80b0675f987872f37e30f51af84304c782b3aa74a84ccbb71f0be84cadefa05f786cc71522b272b7296868db30ffb1ab9837fe73a6e39fd034aa0808be526c9f413d8d019833498be2458c0962763047be8decd360ca02baa63498c8cba1e64547115c34d546721ca7c667be46b8af77222f3e54039b63e8d2f7a578c2d946835ea9007571d7720c70fe150b157f3ea6e05bc740d6a80ccc5f22f9e9938e8f0ab813330566bb63ec7a60ce48314cc494768f4afe8f2d8d3d61dd57bd9e9d8fc7a23fcc97d9d3725d200ec3f6df9d150c4b15d78ca3fbc503350327209d5d0e78cbcc01f6c44327da01a65d25c05fa551115a46b05fbcff36f9788af9d86322ed4cfab8c206f572ef4d65299d478afceb56455b05b7daa679852fea525fe456d7cee25debbf1272cec8239e7989039639793392541e250125f425831e64c511c3b0babb59a5266ef3d890eac3a9c29b45b81e605dc8bd60a9043d047c99942ff03cba34d2b6b137769483c85f875371780a3bf297c169b0ddaedce53b78049ee9ad89085f35cf265dc3a39b37e5a207520e770b534641ab218b4b8c2e290006799f7ff589620b728b03a580e59d0aeb755dc0b8458c757d7c7c1d5b9aebbe4215dd5cf518ebd154c872541a71852eadf8b91eecfd886c27e40e1c70a250bd2f57fdade2eaadeb72c61f0f45a39f1ec299be8278e5663116fb3247015c1cd677bc21c6917c64b172be0c795c92bf96780ad1d5b587e43de51b5c2907d31ef11fc79aa43027a65a5036f4cc5283c055c34f6e1f362e2bca7a32199c756c604eb07121f1ce4b74a672acc044c4da0ef97ac101b87fbb1fa9d57e6fdb7a680f288fadaf389fac899672809724f7ad73f4c3cf8f9514fef9f691ae87984e5f5e6c711860384313f9ae42499e8a8911b5f3e16d60e63de3fa44a4ce08d7f82fa167f1c05542fa079da67214a22ffe788586caae0390945d11ebec588012f734ada59b012b3f19152b5f06251680754c309e49279239cc5af13fd3d84580cd174826a64ebf94c113557a72f857fc16279c2e3504660f7affa96492a7efd0dc5f3a2b3cf144b5de5e3dfc4674507c952dd2695e91a9a80556e38f4ee7093d13ddb1bafe3b1f912aba4aff725c83b30294f0cf8d92ece1271c5293c0a45202c78f4c81f5e5ddb2dddc226872a97c65e669fb6a1ec0a1e3961c04482c535930a925b3c84a50ce753d364ba9426ea8c3bd5065cd821e589443a5c349cb71dc1744c4cf7032bd6fe76f254ab2061d558fe5fa7a2c9acec46f8c8784c42703bcdbff1d4aea566dea14807bed2a2222e32df8cf70e2338aed15566bb5c555d5289496fc0f8845b55d892134b79664a4d71daf0da2469c7582d15a86c6a9c1d89e8f8cf0ae0056cc3d42a90a449d7288f20878770ea7413193f2f4cee9ee1cdfd2174d5c1768835c8eb418bc5f611ba1018256f6f884e9f15781bb4c1377145e023236682c5a5003d3471e2520061b68e53bb8e44eade2cfa6e4ccacb340933f269672541a65fea188dd2d9cfb917226f6a00ba901aa25ba97bdbb35da8f9d512acc9835ff4c968b8f8732a3366d720da7cf135a3dee1b19692e27f30bc27eb5b289ca77738d8b8426e60209555272b73fe304b855ab24996d39991ddf2e1b99a17f132e67ec589ab61d6f427aa9725d72fadf811a2d4653df823932d97e6a5e30ad8f57b6a948e56eefbe5959ca72ac2563b55096753eb67388b143a0e9bc1a0fd661b5be584d417815419c73f872a74f0e5a97b19501a4df480e5a3bd407729c54d59ea8c5b917a1be249269e372873dc1bd9d12fd952ee552a5c76c83c63dba44fb5de263f24118637969b3c4720f857277f54932a9893be72a3a28dd746f6381fbad1f431482c2517f88aaf65248ff122597e21cd762955327fbf5d845758746c13933815a97fc868d1eaa6a1260a8cbf642cc11cce4f1a5260c9e85674e0312a007403175c3c6738a4ef62972254e6db55c842bdda16358490b2c5a9575a749f7ab6ab6841bbd54ee214a351934b66382f93d2bfd32452489e6eee91606235323ee45ae8545fd057df4cd530deca53e0f9ee78195506cb5965a930ecbd9ca42d61c2725ce520deff9adb2102a09db271243ea7bc64012ff3bf00aadc7918348fa0fd9ecdcd7136dd3d7232972ea797667824ae5e04e01dc802813ca2a883f64544ef85f1a4e42b3fe9c0fb572360f9c67e7a771fc0d73f55e7b9b3d9ac2ad9afc977a0fd5385eedaabd5fc472fd3302693356c392d6535bd55fb61d720cc92ef5b921e80638858a583d131f0d7816ab37404f08d65f7a6985915b1eabf6709ba9bf27ce78038cb36b16c0a172aa3454fe34b0ee121d74f3dcf3c0457925e4d47166dab63ce7e374b6aff3d84f8165a3dfb0c66990c068b26ebefdce748749f796deb276e6086587388d611b6e0eec151970adb57ea81a60a393902f250ac0721f16f7e1d865ecd43f55c0bd7237ac6b442b79764288ef255beb2ba89248d98fe61708f411ff13f9bd2eeadf61311d69d555a8819aa4e9344a32207301dead8c44f5720f3383387b9a65603b720b8c4ada39fc4cb68592bb104e84bbc581fe056a1d19e49b98da2a7d065b017231071248fceb76e89f87ed1c13d37033c9a61594ed1553cf85af5c6904ef4a72d013de9f1b0d68a37c9e57fb64c9750fb67913cb30be77339d966269fcbc867204feff2c0c3bce68d032df3567bd5ab23b4093cde9e8e7eeae13c4eb422d3252ddb3411106ad4bd8dbe43e58ad014b467500e2787a1a0c872e7c929af2e9f772b982383cb56183018d87dd56cfaeee5a68cce48506fba07c8d05849f6f3c4572fe9ecf943b7968c34cd13bc60d18b555a98cf9459f4b57682569e71336c1cc72f4cd9906b310b3661357b3224aa6e65ba03d9d30294b2ec61ab8d29849d597727682d48b6220df88b1ddc4665f76bc8afe19b0a980ff6dde6f631a18b1566772277a0e2844f56999e088a71c843b118684bd40ac57a3052a2d82fa11af6b2a6ba6643342b406e8c895c93f8bbeb3f762cadd9f051a43c4821a2aa36d0673ca724d7a34687e6776e92dd3c454597682360d75962f5f26cd3195b31a90051e2a725af4e20826af2c4d6e707d7c409ba1ba55453a5ec6a76096db7d607d9c585472d65bb041189a61ff92d5fc967fe26b7af42180815d9100d052708c0541b5327247b6ecc56f047dfcfbc3210baeca95edb5924e1bcce946bd021445f38a3e5a7229e2273eb7d0cfd3b0bccbfe36952030c9e033a308e7876108bd50e43f6ac37245caaa15150cc8b323d6f687a9e3859b1d42b5e169aeddb096d045c7b1a7362e5d3893b73662364e418bf9dcb0d0d226f43bdea8d0d1d1308ddce0dc81f3d5016af60e699b22c6d0d8cff2c0a1cf7840b5fd3659e25ff1d6dcfd2c2d66338a456941f7e006b3c4eb417ed832ce32f65e5568e204b34ebaa5b13e34ed78deeb5670e2314dd360d181175b3c9a3c7d3f926b07044c355f5a2b6c2fc1c2244fbe72788b149e800aeb03e4b051156f86df9f9986e903b1812ae3f01a6f578e0c6c2a6152cc7ed70655b0de44a7b0e153fd755cfc21eb22ce0bc5784808c815e9a44882242501540987fb0d65311f4d7531895fec595b3b8055cfce2d48c12d03f929657de55f1d619814dabf6d79ae06a1c3f3ed4a65e7854b1e78980e3c99392a72597273d8940f81e54e5baf613fb23097efe214b01c7479654d564e2aa5315d7240ad01e8a4a267309a23272c4404ac8bc2d3b702dfce2b6adc3bafbd2c72666aa26d131a6a7beb2e4d2f38e59569e2c22b9e3594cbe24b00f4f08c8d673feb4d443d491c342c64bccfaddbf09149754d3203e5b962cc67c3c54359f91298e672b81bda25d329758ce943dea86daf33b9317a53f17a159312a08afe8ef8c64872b48a1841ad8393089d590e7700c18eaa49a41399e00df8eecd77231739614607d0308f83ca94dd98ab952f0e3a5b98bb93acace4c826b30f86281250544f182bdc45f98a44792242fef3254a587e7efb81dc116661d020f817cd50c2ce9336726926badb1790d84b81ba47f1e89826ac2a3f52fdeb2479e7a59badd64ad2d7241eb3491acd61be290287e4fb5556a09711687fb772f412121055eb7a23265872167b8237b3d569d0c73a44cb6a1bc88e665f6d1210532e5cba36ecb2e5b6986b58278bc0059d51903a99dfaf6d3866ea2339b6dacfb0ead0973d22ec33213a4f0d626b342101eba4a6b3fcf73c11a61cbb8ae2b5daba1443e2fff07f04406172fbc1b99451ad62b712af36fd3143b4db4ecbf95c53b18b58809772d3a71dda72335bcc6e5f1342ba018403b5f7303c3cfecbd644485ca04226152822ca3fc97261b779f5912d99a3a68d5399d2de3dd70937bf47715608953b699c6f989fd74eb54c30401a09a8603e29e98d7aa729d9ee1081e4db86bab81cdbe23acb816b728067fdbd0e46217554878281cec73153ce583fd2f3c5a8827d89238bc348911c38e879f9cf9776c75745a685cb1df845612fcc5fa1d7a29713169ec2d292ca71c97a47882bcc3ff937795641cd50cc7268871536a49696f98a84e72f982fdb72ed0c1ded51a201991b8ddcd73e21cf997e989cd9b1847824c1835cf9cc9ed544e3810fa293185a4c8144becfd857fd359db6761a14027fefd59861c8ccdece373e69b79f1334914148acd0d5c0158a706b70e57015ddf0a4f4906aa475775272305f8216fdc3d666423e55aaaecfee8b6aedd18c1622de092452e48447f7fa722b3497a012df34b5871338799c98ad4cad519f2df78c7fc805d7ea10f505a9721eb1644d1bdadb04239f76cf01aa920bff7800609b25a7c60cbea5e9813f92005cfc2beaeb7dbda0476004eb334eb4b65a234b0cbae8fe448b873bb5a9b7156d933e475a221cdbb06626561a845676630bb15b5ab236b911039b8f63fddf8f54eb2f4e67d09454fa7ba21ab0634081907d8369cfd8f7187b680f4290db5f063d7815a5c6057e8802238f984b9e7a5a04c68628113a9c9e2507ecfc9ca3b1497264cc85572f9562f957cd2502652217db1d166fe22335772d8522b94a27bd5d724685c6ed4dd684d97d8f567add5e0a193dc15a708520ef2430171c9013f0f87226b071f6d269ff6793badd8187a86b396c7ca87e6910afde6321c6298513e472f35a079ae0757c9707e0b28564c19e396ca04519fc2da5b1ca6ad5c446a4237214685267516814bad4046af0e2cdd89d9451a832a7201faee67277ac5af2f96a8647731697552780f55d4cfb597084ed23ea5031a8716e361cc30afe4dcd5f7274eee7cb18c1c92dc95a27c1dc1ae404a4b86955b2769973f52e45368208d85c68e8c12db3b4fbdcaa5609d44ec4e39001f64626f87e26a0819dbbf2482b0b39c12f6307144a9af807cabca9ba24724cf1f7221cc8f742365a9a01d529fd8a3857092f0a5d4b36f9c83bd2bff1654fd9994c60312a4b6abf2a4855d359e51f722aca0a4ae49b30768f90b06cf70708eccca6983b8d205315a5030a1e1d77db7248ea6791c2d40778645f8b8f2c5ecb3760c691a029c6f377e585a90e5eade972025abde3c9ed74995ec83b649b8ca5c819fa5f07aa967059c55ac3cd5c8f3672ca2b7d44410930916375e092f64d8ddf3dd1c19454206a2898896c4a61ff924912e21f70dc286569391f733122b9f3be9e58524b6e6d467d55204303db815a7269177e8c7b41f05f0b6ed5f4acd278887ac2d9f045b60050e81851de116a257247776e567dc551a65d3b6f20541b8d70fc0aee6fa35f6ab7db723c1be5086d237eae3b724f55dc4a9192a4467f733613373ca63f58fac60cf0bf0c44e7893572711a081141e6f10ad2bd12bdbfe4d7be4ec40eca0cc2da2c5ad7bca0020f2c723028d3ca18eaa95111b02426d206442c23c64479a06a8f715cd931d44943a872a1da5f51a7448529fc366cdb091bc5e57bfe0dc5beb925f4269ffecd34de2672ef28fea706a39d2b6097a9620fff18fdfdb8d356c2a76f2f32e234e973798b082885a52c643ac7173aa8dac8204280e957e11577613366b9075c254cea310f2e755309993bde7fd5210208c9739da89f38b4e7112b2b0cd85ce0cf571ccc8272bba6aa92e4e319b7cc7094f665b2f3e79b056571de6f878e816ef68174931f2a69e1d6c2033312324745ac4b42173d5835dbb3fa2a1948ea81042e2ba2deea61b2957398dac4edd5a2e1b13aca69de21212c00ad9530beb4ef143cb720e3a44132a8970344ad265944b77f2bcc91dd2293cacc3e341f3cde3c349cbdf90d12721e7382723199785930527bd8ef1c4009ac6923ac51544bf049e1f5116b073d7252ed1e59f0eb33c61b42189f52ea1539b6c0e305aa6bdc9c40e78b52d9704f6230b37b2fcf0d0c3fcafed8ce5867da9082f2a175a6f9dc39325d78dc4c8e8272f69d4c1b7942aa8703c780b87cfd0686f33f55e2fbdebcb9015e8e883192c57242a0c9ac931b0a49677ded60d2be07cd0dce8f19b999bc279569bd0c99cb7272448ed9132cf3ffac3074aa9933a5be6d37e43dd5ab2910bf99e0d40a4a638072ab0e60fba8c941a57a6b36217dd89cae700e4bcbb55b89728c9f2fa15ed60b037a97b0ebc795c358de74709d3b44e01327591966eedaf871e63751fd7cc70372f0c0011d82bb9a3c557f453241e6d7a08ddf282023969e4af8dbace2e4d0722051732ba4e5fb708f295f4023a9d7ea4d8af544fff7d2f09d1a36eecbf33cb572404b882f57cd6464c7b9b43ec1fada6cb522ed210af9ead29b37d6dfdcc2d15db592f64fff9bf8ceaee851b35ee3d2ba89dcb133c55518f55f5c8febd1b5d31f091666aa09a41b78ff17ca0a03fab5a2efe8c642f215a9fffacd312250fdd522acf8442f498e9c463dbdcd5727cf45e3c64f170f28b8f0fda20ad01fc85c3672461583f2a5c0ef5ccc28dfce453c2c29682fb1857b8005fa561201ff1cb0457242fdf27687196c133e6d8a5edb585474581041308488404739dba1c70fbad0720a00dbf1e6468be1af7e29af5ff970681d20d6d170c62c33a3dab4f9c68b9765cae5334d0dfa41ca4862cb69a1f3f5ff2ee3b0231107c31b2606001ba7ea0372d3768ec143c1037618ef7c4902843366ab975e20e363ec4f074fc3361245d0723b21e7832f1ec03afbb1016bf89d2b32f5bc33f158bf9106a58da8b7554902724c356f9ec11022c3b59c13a44462761185fa6481193509bce55fbc480eba5d72c4ecfa31bedc3e55609645808dfcaa6af6a8a21fffa0747166b60ff4aa8f287233a7650aecf27edc207a353ac121750b868f848036e52580ea7f20864e9797724e0b4bd0eafc61b7b9a19140082a41ac6526775daa5e753739ff279cffd1f772eee044c8c1909119b462cc6fb732da2a27b044d8476474f4778bafc7ac80f5728e7f428d72a12971123c46d957c1fa9909a7ea8213105155ea35b72a8678ec72dc0aede9c98aa5935b19222e2902a7f71e22b412810c54b14bab72c763e49972f6440dd81e945dc5719b416d1f97544bc9c9a259ab904708f3b30d800a52a472a7ec1a02ba59ff204f616271b7de53ea56115d0292ce47b8b08e801cb2bdcb726ab1251d66fda6e415d2d1826f2a01994652de25fe9a25e3b8474fb640b8f3721a5857f64706c66238a002b6f206f66b774ee4cbf1170b93d19ee806a9b83d34bfff09922a6661bc6cbbcf768b7c2b97193ebd30ae0a6dc7710563ceaf8a4b09c7f3ed0964f369b39fd9a542ae84705fd0d6403e74dce1da0522cef5cacc25728d2f87d6fad9247103ca824cd9a3e9ec473dc928780c6311afb99222d78e91729dcacc3fff9d9506218b5e89fe8b9f9a7428f47adf05a1825ce08657dcca2113fa08a5483d0b5514662bc12a84ff9d4ce55bb012a691ee7fdaef136d4993685a5f85a3987cc2a0bfecc778743271127c9d1772b24db72345f80b6191477ef12da1e3799a8a69cb0fb68b39b6d4c6abedf96ac9861e2fd8f526333fd8276f3e2976a208c7e66f9ebcc1f8a7cc151fe9922635b9381058456b5e28151bd1b0da47874c95c93135ba13a3d7869fd2e4b4fb06b711d8964d9c759e8ab67b22d99a72025d8004309162901a21e2276b5930b04a27a6743853e807d9d202edf69493393ad38f9d5905a414537da6a0972f2a55cdf8fc67241465f092ec4a43c1f7ed0b69ea6c2fed80c15b2ee817bb25dc87c8b9a0e8a335a751e98cec4faaf6cc3b3c96247f8c8142a6aa3dd6b751feb7c54bff9d72995a49a391ed5e760b120b8872878b81c49236a234d0802a45dda38576c27a4b57a18643c4891a960bed1f7472940025447ee31c2902c0ba509efe2a3ef5d4bf6c0c8ab95b59ee3d227cc1a672bdf8ba4ddeac936a1047c64c278fcc1609026d7c1eb79a5ef531f9cf3aa94613ab88d3becfa0eed45f40f92497f4575fcf0fd29bc3ec1c850e91fe92cdbc172e433b59284e8b458b5d1853fac3253b54d400d6a87e7280de0a68223702c9311b37314ab134a9669ca56212f09bdd7a51a117f3ea13f04c1f0f443276c41f1b7235c1631c9bf52741a00c0c25753a4aac61ac90b1c25ac514d5799a03987a6772f0a175f2a73c51cd262c0bf0f43bba1487734376d7c45d8969fb50e2b58f3c68fb92aa6e87de9ac9b0ab36921722a26f83a9f147d5cb20e34a49ed348e9a6a7265bfa7c872549ff3ff828e1a4952a7afa2c4f89377e9632780b05cda04116472ecd212df1c6d1a4ed61cc8a8779655a5a06f35d253e20dc4300ca1212473957215767ed793215644aaf0d985aec2586e4018ecaca0ac80fb8ab7fb73c1988f72023266687ecde3e073b2a955302b67c23a44bb95f6c208dc66cb32f2737ad018aedc4fb4d13c26e44d35f329ad382e35a654e5dc06d34c867838683e187e0472e17e8a33242db4b7b2c80fe172c90f553cf48ff290b7d64362ed76cf8fc1a872b422aecdc71cd24d8e10265db1e6d39700d051559897bb961ccf90b24472a4046e8fca0412a587d95783bede6c38cb7aed68cc3947ef42dd80747caffeb5c472b0a176bf27e01212e141236d4a9047a61f16cf40ad5e1e31e19b5c662e802072e18f242a3a0b30f9736d55743523abdedf999b65e1c7e4fde43a4821464e6e728955a88d35ff6ee67b5681b719b6474ff7c13b8c2d899f7640796d6d372cf13ec5f145b99d5601de413414d982d1205e036b0366ac9f14be502955f42d13931e5286b6243164952e707bbd41272bd0a05d1df91edd5a74eaf575ecb1b88945261174835dc22c42190e991fa1e2e1ecb25ef0982a7bf00948a127e5291ba6425c556c871d43afeb0468a0ef90d0e6f6851a04dc1882781c4f82bcb247e314670af944872de91915f70ee180b4fee0611fe08ca847accf7adfc031e3ff59158a724dd3a7d26d1569273cd5bf37ee5e2e75bf0627aea937cbabbfa6d65891423e723aab2bd8f767b7bbeaa28b2521996c32b1e00a2316ed620057beabea0624cd720e75e7a4c9503575868678859a7042cee618c49ca8f830e7da0f3a3b923b7172ad8b713c8dc32ae4af6c91602476a6099bcd071a549eb09b5f4536c2491a695f4a7598743c99eab38ff6ac16ffd53edde4ba619f00cbf1379de532a11176b37264776873baf376c5e48e8c0d240fca634e6cadb15a39fccfa61c2c82b2bb2572bee89c36b030c219382309565238cf6a219efb9752ef34142ea5f3cf1d54ae722b7a0551072552135268085ff002ad4b651cfa3aeb27954f6d1c801394115b22ae057f728beddff9ad62c34a2daebff2c0af0f3a5feb1ed8c2aa01287b63b57286c9dca0c383487a28e2433f7cc54df03257440c81f1a2e8c6a69e8ffaddee723512a284c81d71e86fc7b043c22e7c85a67fbd0168cdbe91eacd3571c0140f72e20c8be913eb7f1063671e1127579969e642837e341284cd13fde95551c27c72138791c054d0aa6fd3ccce695299fa092430ef58ae7a49d973a0ea47f0470672b02dd8a7bd4047792c8681cd95684058186a02abbc199d8d769b91b785be276f1a5582885d2ef97f1b3534bf262411141d75cb9ec1f7164b76ff1967a675a70535139cc22c0fb83a927f97f783afd37cb8c3653b04d0b561ee1f63772d480d294a93e27ff8c97efe65a38f666247040f9137032b4192a6408d791762c8e748728f5ee86d8c9c5f7dd98c5ed9a919ec402aefea3d7a6e1b05773893a41dfdea72f0a3e2a4e303a3b2732dfdd33ce01e33e750e9b7f3265e9b0064f1479b5aaf7284aa4b2d2e309f136d78484ca61a8890d2f0c92731a2f3f3bf9a7996972c6f72a5fc681942aa63a0ef8fcd5cbf60f80c5ae66da0a476d9aa480e9a16b8b62b178d8afec1ce2c7f9e42d9f004a5177ffbad93ae517952a82a56a17db89d47ea23766e78598b8ec8ac5d46b6049e25b1ae049caf26e8c3c75f57cba538fc92f77278ad3e1dc8c873107a1795b824b1273594d780236f962319e920b5e77221e8724aba7ae146537e238806ba4dbc0ed422ff67ac042f792a86841ff54df8ccad5c6029faa6e362881d6a62126301066257a8cf143db720071678b9b9967060b5729f1cb3d053840cb058b8acedcf5999718c0fccdfad77e7e2fa344415ecf0347211a80b5fdf16ce41e641e3cfb314d832c16d8554213d9a3f3e83f9654c67d705da98783e20984663dd9aa2671322acd45354b8a0619c2c5d468c98ed7c78324c328d5272cd9b2b4232477ecd80d59f288456ac4c4069d27aa3319929282a9f492d07635d59ca50cb2d21a14e89f7f4ed5a64f3370c2b2c49f8856c642ea3250833af86abb604925e638c3c5948b69336e63393d7f5b67630951102a27dc3d76d81a8bba8550cea34235d09bb9d8849de3a26db6d6fc79942dfd3adfe683bba12384e574b7c124fa91b3bb4617bc3d95093d1769574f1891bbada273930dd7f72e14c0678e2e5dfcfbcfc79b3c24d3781e39c6dbae07772472a4db10695870a72de54c904fea51a8c7262ae8d2bc55f180802c9edd7c644e4a58618576392ae2c10e2464a4acde7e81594bdf38672f7a61dd534900bdb4b47840654ae92ba23722267dd1ebdf4559a1e2131eebcffae146f53f29ad73774e0043293c9d53b62728f4b02183aec3fe4258db519b599ec772e9967d593bdc5c47c5f4635a53e35623565256fc58d160633e7925d27f1207d106c3c6d49f58c837b3416f09363fc385a25ab52400948c31e597e3362c8e60bb25a9c6f1d05420c5b07f9555b00f472a042878fbeb4b19d9dde5211519d7cb93d042c836703fd89949010fec1f58a7286d49c8b8cd0ed17c76913190da90bd67e92dc327e8e9470b55e706f8703e672d25cd06caad0ee116d56ad7c6c0605c7d2c71badc90c241d8d746308af29a60db5a6e5417d0fdd6bb6bc1b10ad98f1760b8c77cdc53480bcc24f507f92d1fa08df4a2d511380b4b8f33f4804b4aa3c96f33b17923eea11bf83cfc59af9ea4872390d58749d9ed74ca80896991e94161b81e8ffc8bff999d3da78d87ddf3f1664721cccdece4186ee962b4645f67512f44f03171909dc923b4782e403fafe8664dabb924e65a01dd0d7c6edf62419c25861e3e5a3b7205a807369951cb8059a11f260bef3832c784546e44c6397e456b18c365d6fa5912c71424fb017276ae41589fba3df3e7f1c81bd020aa0442069f1e88812bffbe021a7747ff97774ef3f729c9a43f03241d849a122efd55803c5331581da651f1274e4fc6643151d47267239fb90621972b7a51f3248fc8a769a3bd2af6b0a0642e2647e546ae875fe5b726a46c0e4cc9b518bc82fb3cb270a45abbf5b8a04174c21a1e4a9e120ae6c3f722178da173473a2b331633f7b22333fd60e5eecc39cf03ba1c0562b4c5e0c0872d7d938484e67eca839603a2192d7dd87970f12140c3875ea13c0f930e5d36538f4d39fcf1b5e5d9a61ee54c03a4aaa59e4c41593e71ef25362524743cc2bf55906c08b5a83033dbbf76609c6dba4482ec60195dbc1242dd645448c447b55fa72127d893dc1e9c72d6129bda468ceec9b026f0347b261016fa7fabd9a30a87c6579477b0e2b7864020977186171f3aa7da79ce1e3dc5dc43a2606012629e65d4d861353a01f0f9ba6938164e5d766121a2c494c73375cfcac3682cd1138998e7208b0da42baa32dd52026a2a37f4e51c7e17677f5ffba1319d7253aab5e478f21be9c04c3c21871a76d01158edab96d551033dc15acb836814ec945d6d62eed039e0139ca43e3dee1a31288a0c8382702d5275fbcfade9e9af266b0418f78df6bcc6be843f1ab39dd1613eb8225bb6f14e2dd65f65835f58a2d2065f19ccb781db6cf502935565b96ab4f3577c18fe0b23f66fcc08e1ebeb5b2ce4ce7d7f71f3e0a5810e1eebd4aa7442668eb10435581bb24d099d469d9e856a7869bfb7b7f72ec92622b304e3de85d4944b62920502ef0b9978a2d68908a262a3fd03283ba4f2716fd819b314568f1b8958e6086e42d1426a020f5683944affa403c8d75466d20ec528184612c26000f8607bdd6889983e720a8acbdbe5c89b61c11eb675972aaf38e98e05e4669d045bb90fe1799f6dda81248e9bd0fe690b491e8f6528b72ffa714426a3885e7ce4ccd4e8b3f147dd21fa283c1ce389fb8a0fdf9bc758172df2bc1a19605768254d47a64387d993813fbfc910900ada984cdcf2c0b420a54b3079deee89ce3fbd350c0df41f69a9becee601a42a1b2bea0f000186004f2724c9f085e908f8ef0d269eee3c279b9c520ec593187ccfc4d6dcc78309517f2721e6a7727fcc45d10a70a681b5a7665eec68b9de23b78dee96f3cf41a84bdb0090390fa08623570aab1209c3bcf30f83baa6735ca2d58374a053295b789c04e37540ceea9914d04e62d2239f22813578a000cad28a33a0e2959fefe1614b1357213cd65e8c5b89542cb5a1b795cc62bc653b01ea6ac425634c437caa5ba3fe71288b5f78741cd1d62313411220a8cbfce959887f481fa98c78286d4267ea5be72b1ad0bdf549959512d298ac4e7e09e5679054caa9012cbdaaa2ca3ec0dd53f3ea3bb6d0cbc919343934e6e8a33a766239bb346638e98651c6163b388a01ff4309e8b1dc63ae08bdf91b74013ec645a6f0c6cdd5b622b9070caa03682e335f872b306d628dfdf3b1a76ef882909a5ec594623ad3bac0b2f2ccb32d56e2aa46372b012e76629c8f56eb9db9feaa87c85a3e3b046ba25706fc6dde236cb5b7b8f5e2a199fe74aa7fc65da7aa3a01368091807706fc08aa4ef39c985a3c47e59827275c8fdf4e5bf0ff163d9ac02af4db8e0079f32b72f6da2bc60c10f4c6fc59872c6f65371ba1a2e1b55d3dd6d713f217c9b20fa20c3715bc3f18c48c21ee8774c9897cb38c51218526da2793570c5b1d1f421e24edef7a16dc14c5e1ada8da572a7bdfc3a7e8ddc266193f70936d13958cec394e1995b70f088655a0bc1a968076d4d14f4d66539458565675928530526fa6238514195bf979578b2a57f5d803aa4593f65179f6db62d50da40b4be17aaf3d726fa32d5ad53198b6c9e11562972c1e98078846abef2a47935abe00988e2fb9b9768c0ade845e452a1b944cfc472dfc72c83cd68f975eb5dc66667ab01eea1a19f6f81f1bee931e07f555de4590947452dab58e1fd99a050b8c7308d294a46b3d027f22705c58cbf109c823a8472aba2768e1a7b33e50ae879924be3ed860527d8a9ef045a500d01aedf28ae751bed141524026f0e86f6df6c56008d666da6e2b2b940d15bf1bb426e74e9ba7272ad83d108edef0fed6a1b8cb883e75eebdc3ef5d7c5394751dac8a2e2d51c5b72f38306ac425091f4da2af6eca845e5f6dc39d0e230e68faa87968eb6932a28722b3c254c71490c68b91463b4d2c2b977837c7a1f0598d31115b85426e6cd417290c32dc46b6feebe73735712fd7a34676c611442c524bf7fffe23f0943c8d106588ae5438b3f1ce61e61b619e3d4336edc6ce7d9a34c0a175600b0ea78326433caec3c2fe724267935c1a1f73f3836b278eef76ee3f24a02fd737ddb119590726f42018c34149e4a81c7fde0cafbbfc839a2d8be56462519bd5080704bf8c172a64c7ebe1a3009ab33f23e8c2a1b57301c85cc6c6c624611bd61d161137e000af56ac8fe96c5b046bbbd797c51ef22018ba7b9cd2b7672dadb2df1db41600c72d095bde0d6d664f9d4673251fd4dfe21bd48af86702b5e5a27e5584928221d7267e4e65ed06d3c0ba8dd38399da9f326b65e50766666c5088e6965ace157fe72744fe16bc88133ab60640150cf6133a25150ea46a82a20d24be19a43392ff84e9ef597fe3c28a873be662cd923a3c1c86428037257a5b50c4a7f70bdd684d5722c95c7ab094a074a22b87a07b0720652cf231048c550e98374601b662e904b72a2517c58056b9d4caeeefb50347f800788a82fda35377e86a15ec5f7958fd45bb6909ee660a4bf2f27cbb4f8b0f14d1f64f6845ccb2276938cbf9bf1073368352bb9f8deaeedc34c7dfb96e0a092516eef8ee22ad761a7b200225227535c32328ae7ef6fc765865018bf9d1095d31da8baf8b2ecd9b72cb64c359660bd19f155c0c97b032aa3f33c951a6a39e696635f3372ea464a3a11b157ab544925fc19725ffdb07f57da6b9ef9caf821c34a771f4dd9c6543d379ff15c8e9debfa08b372e08d224aaa0e74b0a43bd9f0949724b49a1e14a76af69c9ec08b3dd404ce357272a51dac1b610a9c4bb9c06f7ef57c6099109a10a2e4f0994c60a4c9bc012609ff342f56cfdc0e8c7b9c7f00eb2d1084d4202d6fbcd6c917d29c249e84299b46589eac31bea88bb158a063ba4e8a6b536e0dd933b20e0089db9f6c81121f82112e3338e1fca534fe07c947efab9bf105c489f70e022257b3f53f610c42a84b726606f50b2f4c449519cab06a4c2b1e57222e8d9b3dcfc2851d34df38e28fec13c6b835fc03f87cbcf20cba7c892a073a3ffc5c7704cfd33d2f265fb1cef08372a056c4747fcd06e6a606d39e9750f4a28063a84011deab05f623358b7652d7728a2c58e7eaa831f19e040b38a81a0e984340aa9e17da62b35dca2fe75883a4101a558f3ed63d62c469f57a57054598d6d215b8dbbe41d448f0e4032850507972a34d6950e5e8c29e2d23a559b66f1c94a3306693d607bd2b976e58d80e85c37266cb970e32340595995927623aa0f6a32427b55a955eac03ab64862bd989725154534c565e3daf3aeedab7fc1572530357df44ca302eb3ba58ec4a247ef0d572894c1049ca7c14149a5e619b52d8c62b28624d77e7279ee9dd7451394ca17272e42ee411ed3ab76108b05b6c3f4d21e6c626991908649ddd5fc6a9a68a85b630dd348b3cc6429138918fb6d6c80436dffaa80d3d740572fc388334931a54c972470865c91a26e2081a396b0f199e3e8cc39c276818feb762baca04a472bfd272bb0c7b58a136f1217ebffea79d267572501bd4b617a84680fe75bcf187701718948c70f46e91ff3b4b74905cfec4f8da7256f520b689d21f90b702f0e2a898727f8f8a79fe7baf1355484bb2c46cccc26ea7105c5d2ee1501d496d5ca0cb242a9221c35b8b29af16d7d6eca38e091dd9b5f05e64f1d0cef60bcde991dd09a86435691a4cc7f674b082589a47656f7d535e2ee999b745cb113d9e9c5b62fd1f7287000dcae2611dbae2fe4de88773894c755ae320fafe790886061b9e905088224c6548690c0be05af6d5a538aa8698e2ad6d8afc09e78c6d59f18d4abd978c726d2fb80597512cfc740f0339b3f8e2f040fd8b14a47c79422c9399d37abe2372ff7f3d8d0ca2e318ad005c0a4d1da661f7a8846b45810d1ccd7b7e1b3adde9720cf1b9768957c73255262bbd446778c473ef94cc61cf285d564da39ab97c66724c5bc7d975efd246e17f84954941238584cc7e5835200d02afbeeecbecbcd85d4f6033e3261a43726daaa45e4a026a0203d6069b21ee3c3cac05c575e0255b32da8a5b00ec5cd592a34842c5e2787f59693a4a6c8a51bc40f9cca55ff0d69272a5bb4851a556d001ce1abd3dd121768a8c105ee88b78340f9db6abea7104e10d8ce2012c9270d5d7d83357b62265e5ade4b9e042fd7ec1e1ab3cc0fa0225dd0f671b4d35d8bab2ec36e9d0ead2b4a73179b4f388282991c3fd0808933c81b01aa40c9c6631f634c119e263621f9e60b195c506806b2d196bd5df9cbae6684d72e30f146f0b1c9ad937814629dcc4e6577f1fcee52babe68475832f1444b78805d53dfb2352a7014174c983cde5b659f9fdba5276fe0a31c4db446567e4e54f72cda3b0725cd55e7880d1fa5a96a31772ef715ea57bf486d3d8f7ef55164a4d729bb7268ace92e13f80b614ba40533a39c93f54edd5291214bccd9db1e3a3da722161a68e3da96ebc7b7654be8432ccc33ee9f5dd54ca7a9b42cd202ee820d35644bb3f4798d4392cce8e67155691aadba004abc8786e2d0b3c55d6ba85387d72c20b6687a98d4964a40b7f655f6cb3029a62b24df15bff94565d9b7cdbf179720441fcd02abf641ac7eb15718fb22172f2acbd0f6d4a23e1cb93f0f62177787219d7723b7e589eb8cdb8181f8eac3492c207cae0cc624efa663bc237909d507253f05a604dc02573aa3fbd124a5912a346a82b64250ae7d2f4667a508f2a503c8600bf3f0135b28613243a8acf4edd53b06d28cb59c95b46038bcb7e1a612e2885488c8a9aa66874267fcbf3e3d9cea1e7805f02f2a07016b24e552c59993c72074f59adb3cadc15fbbd2f46f3009dae6b0cc787196f5a530ef0636c6e08d4722192ac88836c69e5d102e576f9a081d7262d95fdeb4b18b60bd4de7d7e3f1c3e769aefe1f71c7d62eb2d2d8ad87e39aac8be16f3bbaeaec4a3cd8be3c7033972e59b2a32180c833282c5e53f51dc13cd8d68a09383b427828d8a75ca0d438772753f6e6800d10b1e6dec0a6ce46d6a38ba9992e5d3c8695911424b5ff9b441726eaef1852ab0a3d45353a0e614357719186bddf669d9cd2d480dcde66b82f6728a64f79a7d6639241fb394b199aa91e3672df24ec6ed6cdc78089f2621a25972ad75e9f8cfdfad43ffd870257c879e249e19df19edb641c09f2351b17ec4fb2d7522a07df1ed10d6a1d74379d28f41c13c62b2e4ddcab9f56ed01a6c95e080720b959e0ad26b9e971c757c030c67ac8258c068e1ab9112b602c5bb4f43983561d06e6d3db2ddeade17ca3498e6ca3d6af55643029655fbfe9d5131c6d95e3a72203ddde90d804744191b0e7e8fedd104cdd8918d4edd0ae522b621b301cd42157e9303b21fe0a1ee5259ca58b5d50b5a0b532dca9cb7cdf5fe7183ec3daa62721084dc7321eb9a0b735fa4b4689a4eff26022fdc98c3164969ec62967702582eb4c3a4a6ef8ba25d99b64f910e234b8650f4110bb5cd24cf091df05c15ac25043fb88753685e3b8602f1ada0cdf7eeaad6b9e8d0ff191e1a497ef2ea95a62572f0af5d5e6bbd72fa3569466d130a7ad42a2d4d01f7e62c503fb798f75da4af7286750d5e48fb307b2e7163b7bba413218194a53d37b4139ccdf2da2ffaedb353d95cab04187b49732a90cccf3f453f8754259b91b95941c2ebd46fe679a4c73fb1fbcb68a9d046649edeb963eff76ee712fbdde15cb948ad339edb7f3da52972e78ae7fd76ed5a27e0c12f2377225f79cc12ba3f05369317909e4824efe844537ea7504931475b06e1d97b951cf4790628ae3adc80609819641f8b64474f3172c650e8859d11e75b7829a6f353197399376a4a4248d77900b5912387b7356e72aec2b27828a27f4b9aaa105d38e4085c68bfa45905fc30526b4c7b1d6b18c572df8335730ac9a961cc8c1b0ab7bd7c50d1963773ce9e71484818456f2734b14da339d50244ec6ca00a8b490507ee7ed54d1d3139aaba9415af43c3d57c9a4a17039820362c731b0bf7b29f5215b72eb4bd5defdde71cf9cef8f0640579325872b5c0dd26f48ba07ba232bccdd0bfa425e19c4209ee5f32bab9f0f84d97346c4f64d3ca8a54fcff785bad222cfd4a6f45836a76969328ab7031d463be5e751f72c2309366bbdf6fe11c08befa11608718f61a683cea18a02f6f5e6e7d20a07772b54ee4d1baf4a8fd37ee9a1d06e33b7958e46d92c415dc6705b1c79998c8d57220a5224d4889b4593ce760c6b29dfcd9103edfbc851f05323f60440d8132e9721f496bf40f6f6e231eedf09605c77ce5fac19c3a13a9a1c508be3378889774604683efbe09d2e2a2e011bf72f1821bcf9352527a751c107524747c58d064cf3054ebc97753292fa2b71a1f77b12318aac037d973bf926817a05859eea7367d729b8b8248427ef7594e75a9cf6329c2aeeb882b59514508b0434c24b0875e3605400c8a8ec29d86c6028bdba01c9ed711f89c08785c49ecb04c5ca693ae2b3472ffe1816fade382a1bc45254b0511bbb43cb52218dea172419ebd4095def16a4f03aa37a7243839fc32bd831019b6b62dfddf4621a7df3aace211fa1977fd2c070a78c1109dd113fe4adce8c5f8a7b6fd91f3af9acba30d3cabfac93add08553b1ea1f9dbb16bd9de2b386d7d33b169cf17262feb0666d2329778a87424b0dd725cd4dcbf392c97f0352fc4a6b370651b54e4160ea1738283ec16a6caadec405ea9f416d3a6950bc8f1b1ffab88b510baf4a703b10e9ce9dc5236407c8c7d88720ba20016311d4d82e0f53651aa1da21f041df9114a565d044480f2a615b135689aa1c6402ac0373c600fe4c1b37eaf52ac0c2afdf6975acffa066b364aab3659d0cb173fef7717b6d38719ae6e17bd6dfad84919cfcdaaa574b9b374c595d1724f7df58d39da3a7c9667b02da2c4ef1670cb3ecaf8a8aa040c2fd3ca0b647172172ae7784bf56c3ef5425f83e80eb75f3005b264253a0c17db1655df1093f05121a4b7cceb9ec78a535e28efb3cf4b91c36bec61cbf81ccef5d2e6d7ad746372b38969b651642ba5b77e57a49a065995a996b7b292f85d4772546b7dd4fa35725a67f55de909cafa538551474c53068fd95fa381afd504301b009f4fd440cd72f95d2e73b6ea20b132fe52484d0c24add49c2c1e68384e151a48630e8fd49372f335c80e3c95534525e66254efd58e1d84f628eacf085a686e6ab9464c172f729d399e2480d8c4747be68162537514a1d33f294c05812e14a3dc0fcaa3f07e72d1266478e8b2756774730aa5618496b339d929672ab74d459963b5fbf3e1b3725d23a6320737366e3ccd4da117c5112058edb09f7f7fb7191b20f44cb0289c1de267d6c20197749dda8f9f8dcb606766d2863426828e86e0dfb29d280befe3398b0c029d446a9eecf7cd64e37c4c21af288cc18face641bebb04c982fcf82872cbc8f8665a51cfbc698fd099d73ea692876cde23845e125dc3abf33a42b2542dcffc64dc2ef396f3ecc17b0babc711ce31a4496790d69056cdc16b1a194da372dbc0e65e587fc2c15103a2f2b0ede2484c80dcc6219878fae8b57af7e49e2645988e04048a5718e89d62ce6efbb1f9c23ab180c6346fc4f4bdc76361373623195ebcdb894d93867604137c0fc228cba902679f24b085ba4ed5c6e2087c8a3c72201e5511aa59442057cea3dc2ba3cff59c27347ce1877ef0062a71e52b164e7263e4a7d451ecf66e348f0adcca40cf147610e07def4c348f42f646f562ede772d7d1a06acd25ee4255543b0eac7cd3bf9b62dc0c642e25872951e9d8ac8e75540dc0a7e0b36af9e27207ec1e274ff170b890231d0a58e156f01f29616cd6173dddc5882df551e6cd935f4d1c9a7c19f180b2f71d0c9cb89ada1b7b8275dd17728f9c9c165edc4e3df45de15184ee51cbccf822a507b8919463d96e47fa6d9158f787fa32cf343ac1219d8bfe8ee554c730df2bcbc997104bc67fec55f4621b375918d739b25c99624f52aa3e404b5d2e0d9f577633b756a9bb3c417f73602666cd3676b409e528ac17c108ec51bc6e26902cde440512df4a65ecc1ac3e32d8501f4f16f4ed8d632e5b9e7c6fac6a7a45b7083487bfeb5dccf35b9e360a204072792792129ebf9add8ede04b9b0e3faa0aa71623cd902554aaba480416d36d472c2e6a6f883f6915b3cfef63c7a54d9bb5e0051801fe7d0c819e30f484dc69b72e4299c849e4bf49a0c71e97fa542cee0c28eb5ea4295438778335b2d758dd87225c9641b4c562dd54e3a47f7391642ea8fc27aef5ff37881f40acefa7ee547724ce7581f23dca5b7bbf568da97da0b3c66aba617e9c64929e992c1a3a9897c72f2b9b44ce9b53cb2a62b2fbaa92e54e6c7bd00b5e7d9679a0ce38d04373abd72901dc7fbb3557b91a6fbb03de9764d83942f6e7c9eed3427e1bc1d50ea84bf72d0e905a6461d7d5a451235133eaa4942e79c5f649464655242c31c28a939a972545fe3a3642acc4713b1bb0cd2715edd507b9b8bb2dfd545e04edba89c3966723139d0d385a6a82abc49227401dbf109ea72f2a0a5dd3992130ca6b93125503977f8e1996bd3fc59357e07728d2c3552da4b0d420a34794d95ffec5a7f398872e61e8cdf2d8d659b3854dd21e169e5411e9d7854f4d78c5b66e2ebaff52bd3723e352ddcb82f13645526685f6c6232b2190a91e746caf2e26d53fad54d3d8a4eb082ac8055e8e72e0b52af4aff14e6df6c6b8a8ffe45790ce5932b64e95954242475de38092386afe22e8968697c7652d9f51ca94069557a9d8291d074f206303a4118e3185f7889b0a2947589c3311ccb74f96c3b58324911cc00ad1986ff130bfc96a2b38aaf7b7b0ed6c1d7d1c3d78c77c78dac545a7c9ee1ec8836ae6b0e0055b3bbd17f050b44328b94e6c0adce6d28defcf94875ffde67854fd6526b72b0f596d3762d7a23df90f484bdd6c26c6afbb028e48efb4b00f12e18c6e7db728a2d081a53139bb21e3b97c726771ae9e660c7259884e6b19170dba78dfe09723d1d73e59945142eed1b741c6049d8f63890a96cc26fe17fece97a566e8b0d729ade7c4260a118465db4df16df91a5f893948e07af6e10925979d5150c1e8a60185e9796d66d5715279321de5d477c9f6a2857d5882792cf70619bf82a2fb972686d7584a199d883d877bf733f7aa029bdbc2b4f519b502dd0dd0d7eb3a121281393cb0bde559a2c708b2d00a7c5aae532d08817f50a87880ad1d5d41af41755e31f84743b62e910ab8a6454d8879220fe8fc9421f1a102c50e32cc242768f7258fcedd20c51fda97c3b12e73e4a9d5baabe16ae0ba4ec88216fc9bb73048c7278c8697026189bc32895e221b137513201755c5758a0fa083cb1a61af1d69c725ed9f7506ca6d71e46db47050461b1fd098a58e79442bb2bf5c2c80dacf3ba7255337f4be7a9c961dcf7d90f904bbb5cd885f86a50a9157e5c277afce743e444d0be8e0fc56d20956f3ef6b397d032a78e632be33b060965c1dc0abad28940720ad6a5700d2053f4bf7bd98e791046410f807b6c9eeaf9c541ac6700e67bc6355c7f51527d7ddd8864de7f96a57c8af6d64556aba7fb0e4ac327b2af6de35a0ea65363c7d6b364c6a574e36dcee279fa645fc49eb91d19da3a78b082b4084f08f303cd9a44ee619bb7787f216831958da33cef1431517db12c4d68c55598b7021c4523703de768b96dbcee34a28eccc48abda57b787dfbe3a1927a09b4904a72402b4f362065f71e8e5bcc16c6d116caba909c342f7931a42f9daa2ed1d99a721d3cf32974cff75a2d2e6e084b07d96d0c39e42bf29550c7d28f46e4fca1f672af3fb82a887a5d342643959d05ef4219b8ba842c27dee5d5f08fc8891f6377431521602d9ad7a2e6ee1b6d06c2c408ea6d3595a18e65be76a972832b7e1c4d023a37398b2eb8d3bec0dfb26f61b68111c1ffffb759fdbcf42ad0c00c12b4227072239ecdd38972e4c6d7ffe6d9f9a5129d047636703a77ec2ff8d9debcf50568bb0d9f0dbc1fe1bc1b0b48f19b4504d74fb9c59c41c0c359246bbfa937195772bda87ed9281d539747f7ae5e24cc9c15753d2eda8cacb7ad4f6ee9ef35606572adce24a63d169504d2391903ac9fa07192096ecfdd6a66b1303789469636f2727b9aa34c1af476a836c0faf98e3127b6521cb9ad9341183a2959c0e59c553372e0588b67a938b75572b731a209e638ae72f813eab9356af337815046420d6972b9812a3b3d7a4c19e52876d3dd44a52af99d665c1b33a57c1df26e5c9376d372a26d531d5520e55f0d86494c3a7797663e9e82f6fac2a8eae7ded6487256537259e1cc5e5b7f806bbb387a37eff97b21a3ccaceed2e58e7c214cdee46029cf1bcb5e9610da0a3e378b171aeb9e5af8e3c7cbe670607aa0e139e975c6fe1b4a72a1e3612327e94fac9defe0982d6e4c94b9e1f7612c6d06671f5b30236fcc3f720aa2762b7bebde0df52ec149debeab8683dbb97f25aa8d9c5fb7f8a23e8a9772e067382305cb18bf2a8265ece5db7acf329e5b01c508d4c99525450be8e67772a544581c1dfbce0c0031d0d9b182977caabbe347be10de46e8af14460ed861724c53a0f0239a67979cda17945869f6d99df44614bb9cf4711a6c180a05a58a4c765ef48728dc56bd94e5dfaa3698eaa04b8d8680336195adeab20f36d2488f4eb61774ec955b96d0cc4eaadefa57f4f3025334b0a8e1eb36dcb54590d0249372c0b024b84d1775fc1876a5783d52b813fea860359783f53481cb78447a103472d00901670e8ad9f1e2d86ccf8a5889678847a78271a949c120fc683288118c72c87ebb15c3f5e7d6bdffc2c65a9b8f4901aa27f96c8b98ee7b75b93fd71dcb0fafa7b29e6fc8225b5b1a6a4fb6d519458d12b27ecee329cac0f4beb45487d2729931d6c103ea6a0a0baa98834173bf43fed379a87077b975165a86587b5d1e724b8133825fe7061428f5dd9597dcd7fc9c6f19d17d7f49ca2ee7e2826a3ea46642a2b8a1c93f094cb847ceac08938ab6dd87f2d8606f09c1cbeda4e37e0ec7727fcb5761da037be15b32a07b928eb734e1074e55c05297d473dbcc256fbaa6728fb37bc7b0867a77ef42bebc61a107fcc831f10687fbda4be7faa4ac241483727d79b4fec9543bbadcb20e60a8c6a2a0d7bdd5d15e26145d35fab57563078f722a027c9d5a184cee63aba703002917c18df20e14563e8b59db7a826444d1ba727da8ccca3c570d2144e238ef0aab629401df856c718dff460f074673416d46144d701fe463da9c01c7839553f329a05f80ec67b13ba7726521636906184bb510be00867f5752ad95ac2d796a7f1ad6a06684130d4cd3b0c810ae8f1a5ac49a63a93dbcc72ff42110e896dfeba3663a386a45985dc3e2fe994cdcdab3269ffb37370de4b9b96047f9f6325b90d6903525c364a88d1eaaed60d23d2cf20750867287f0906a14c473a1f1be995b96be6becaadc53a7971a7762c385f1b18625af722122511d6209e8e3403bb75d2e17413c682da7b90e134a27adc6bc81aab9897275b11c6e246724f655d8023427a8412816ab0fe578b7849818e4b82be111323dc5b9910ebe1133672d386aa8ae1e4a91a9c3e0b5091f94aa61ee2e7bba4475127ca984bc647f877063d55591ab69ee4b4e423f9a1e127a77bf01025407b3a772982bc8186751fbeda0621f15a512a9532057ebfee6f9efcbbe1a293e4c0ba172bd7c3c7d286519530925400a7716ae2be340d47bee1fb9bba89ab84019330772e7fb068fca589e9ecebc27b3a05b693bac83c8f802f8c731331ae973201a636733cb955a5a758cf5bab038c48e583748c792a1d421dfd16cb7397e575e2cba660da46f267337906ce0238764ab5e7c4996f08b311647a828caf2c318d59a963d1a00d1fe294327819f4b9da2daf9ff919228f8a336131b93297faf669db924721ba4eabbc06315c1406013808359760e0ca2e0ddcccc99522026afdcaa195072a6442d7d4f110e8e050c20842856c473867b27f419226abc64c2473283571e1915cf4bfa1aa0eb99aa91aa28b9417b9b5f8de0976c99844a2687f8c5174720410622f005b1de7eecda4dcfd6e6644ab635279877c7f0dfd2fec2fbe1d1dd5e48961f7d50fdf433eda5b35aad2a7af2f5823748dc20e514ef28d1dee8a7360b72039a901d4e44ce3f4ac6e8e0622b710da8a7edf284cb63a34b26c9b371a00d7275dc0b2fa68260e418660a46f29f9a564518208f87b24dd8e4c1852dfcb448530b000cf8b1a8ba9d71b19f89a0e82e239c9b391053d14f083fe01f369fad8c72f6308eedf30be2f71d6367c4c4bec1285a6ff6cec91a8a8e984f3a46df52ee38aa8e5bee0c9fed5d179a5ec11b7059cacce717efdc63c6b195e940761221715c03f11c392129d52919ef30fa0c9905e8648898c993196b3309cd9ea70dc26072b0946597397d0a7f38de50472ce42e7e699846d41dffcff29c9e0cc5742e545a06e068901963e011d40d5e1c891d4eb0653105ec926d0c6c66552ae9e238a21283e45cd4f05f157844943475b24b9b2a8842f4214ea2c6eb3ccbf8f96d44d772538047d8473601d1a4f93f4a53ea9652826cd7ce05364ea825c2b6f064413a41f7f0bbc2e9825d91a04cf9852142ee4416a7c3d1fec10250f1273bfbb7d232729e4330cb94a7cb7e35f136f488bb8ea09bef16c5bd05e8cd71d334f7ba4a3372feac7b86e2a6daf4583a8724d9989be2290fa6103b63c5a075a6d65a517ec372cec959d2920c1d545e1074e49f6747ef34cf732d021415d2025b6b54ccd63e72c18a6c1fca49d23f464d420b34b9d4ef7f512ce404e38e7e76792f374ba3891767a0859127001337f0b060cf4525199381f6ace7307a1448504ddca7058294725f37d852151208a654a9eaa99dca25a82f350e9a8dfaf365351381e348bd7872c9acac7d0c534e3e5d9e463d9086764ce682c99ba5044b8f16e434759b4b060b70fc9f567adc3ecfd4b2d82b764ad9c5ecb45687fedb6a3900c44af01f19c1722941d2585f8ed6eba9ad9a45969016da26304ae97b43fb9d40979b24ab534c721ef020e0ed3f7e857a2a5a2d27aadd61ecdbbf20614ae54210999d7cad5868726c7c4f201bd039c71b336c39f6e66a7218fb14d87de308c326a067a98b4ed772663450dd3ae9108f53acbafff483d4928cc8bf1da0ce9c936e0066e4945a557208154f6ebaaf6e366ee7686356346e92629fbd3e6bd21ae428d2bd99cee5db6f6cb82f275d2e876b6594e0e9442896c3c8283d45dc1d3359280d837cb80d84723cf9431db811a605386d0bb10cdcb3be548515a91d07bc380eb895a994dc7572e00b9c4246c9153c0947ac917be394a66afb3a8049e820fc64892a1a0f4a017274c508e1738ac38cb98bbc4e8e715940bda4e87dcc49e1f69b72df2af04dee72efc9c9b608c479665c9da601344af672af740974c2a008c7a88e7d4c875e1c3eb77e8bbcdd9794ebfb2a0acb4be8850320dc7d00f93320c4dc4d9cbe0f65c225cf9af428f84aaf62cd7545fc6503abf765e0ff826e546c5a6c30280a46911872b7ecf2679fa9164123a9f93aefbd3c7ae0af141525ba901d4c9b78bfae13752064fd9eb038ad38bd73fc5c5f1588c8c43f1f075eded10ae9bf3732239893107236179a38baccb4e512949adbe0c72d1f81ad4ad2232ffdad5eafee149c961f3f3fa01a3dc2e876ef47342b29832c9c15009d1b75dcb405bfcb3f9686c529141e9d9b56f4966aa440c83f03fde4309e790aea18fceb899688cd128158bbab2472052a271ae458730e89ca577bb83b0f624f64dc96e61ac03356be4d95b7af3d72bd3877a14f0817a674e005ec3418db4935009af25cef027c241327a48abaaf7260ebb3284b8b89464cd03ac75bb2e8385155eed2c5e84ab9e0347428e19c49726ade6581938b4761cd521efa8a55069c6e5f9f1167fa05dfa5708aea287289724281521eeedda2bfc193894ad45c112e6bf86428a19ec4ed0b7f495f2689614a6a00f91199cad7d3eba01b6da065e44911a07cb4a32bec9869da070f87cf0172e487cabdca5b86a785384a0985834f420ebb98748508635fe17993bc198cc41fcf338dc9dc85f075ef6b1ff674c94fa4659b652e753def8fd7d8f7220e62f32637d86db775e7414e94bea6b57106982cc904e921de7b6826a87e45ee104c9a2f58b4b8e924492ee44d843d398a1e3b836d0e6d1ada3834dcfe005a3bed9a4a3aceb12e1a54b1bb4e7b5baf3056cd23136422651f80b3097b92fcd39d19a581720d3ca0affd9e15a81348114b122637b9e37223dd4331134c3d45c57fdf93be72a50ddd82a370777dc5c788f8de4447275a29494df1ad4f652cb723af3b9638729f5a529ecb231498245f50e9fe142029178194d2390838e9196e1c7d50b1127271e8e8c38a36021c9350f65151467e98f8390fab974ad34b615092a203fb2a7288e6b0e821f80416a4efc831b92dd4f57265cc728d475a4127b44ddc1cff76725d623b1ba22f48b5cc0d3c1beb043506b7e048e49346accc61b080770497167291f76f2febac34535a96469430680fdba21320e4abbce969b48f9763f69368386823df0d73eaf6bee9f71c5de89ae85bf155ef8e17b95d38985fa3a741ea7172b48d6d13971308fcd27c158334413c5629bcaa7203055e89af2e65b0b5d2063c8e33c921e2cc5b03656c3016bf5d705534bfff33fafee5a63dc022b21ef7e661db2b9fbc2e0423c5146fcc4054782c5c3e726ec07a2324ae85c4570514d3d102efd47b8aadd96437a4db970619d99578dd5f8b02c6f4c9cde1001fcb335f1319848de9b243ddc8b6a70ef1a8587fe25958d01945b92dd43ec9eb6ea0883d4f1db4daf8cf74d8af2eb27c8c7658fa2dc75ed82aea5de3697c2b8e4631ce4cab72eca9e395f61e08e49a2735da13633149dd464e9b7a200bfe54f4c216d9163e71e88fa3b40b71516fc4323a1943293e8872026ea1fbb57df02404d6ae513f917280322bc323de8db6dc5c1be14ca7f6d75100e149049b33ff2c02abb07aeb7c0a6f82ee31f1fb189b43a91817471310165b9182bbc6c31023808b29143fd8e672095d0e3ecd0175f34a5eec59f3f3fb70056e36097c26625379b0339024850eba0200004d75f61869104baa4ccff5be73311be9bdd6cc31779301dfc699479403c8a786724423468085b669d2ff11bdc1855ff96a046b653902025e1b720e171800e6d11f00cc789c06b35c7209f1c81685f1cd5048418a679a77161e04505f3b7c033b637f24df8b429416825ffbfb1822e521c22b2ffe2fba7f0a075add6fe684b4de72394e60dd5aa2e12a928997807e1697aac44da265176294959e7df42ebd2f655c6322b228041d0591a2f6c8e5b32f964e83efb2fbf626945f0e171f32f621a572986098db57f68ad53d7e47ec3f0b14fca03f318cc9fbd6bdef4cbb7b7770b5086c9ed9796f36323d01ee66fa977e289f818a8519104e50b72d48c77e1b7dc8722ca7cf2dfbb8b488b6842ab507df9d03ff194c62cd67d176cab2fcaa32b4e57212352c1fd37f395304d3e3bde197df73d886966b8d9cfed2ed4ee55d7890b708891507f060fd049a9dbb90a2c185637c1a2b1e80ea7b64f27eb263fc5c55ab728cd57eb4791f18afd0a20bd80b9aae57ceaea1bbfdb0dc8430334fb209aed5722ae23763f515630b188dd29dbf5cb1c594872a0ec27c29b618415b9fd931a3725ac9e9b2552a8163ec31759118931e9a0154fa0458849ebb76277ea11bfe8072d45b7d8ca98954d2ba2f3689aa180e06266bff755e149b9dfcef35973780472b6c0ba20ac8a9b6ea8b9c1989a29e54e8f0991548ba029ad98d01a96416461b318ffbf723780c9efb51989f2a21124b93ad35b964339bc5d79661fc402fb4f1722d687d2560f7add21e3b3ed5d872f7e3a844a0301f59f58d6091664c8b753272e15ef7f26ff28688a98f7d5455451b8221c1ab3f44a0f99a15a67030f4c5042265fe75094adc0379bcaf463658c20cfdc2e7db4b9606227a6917c4180091f2710ace2ba3ea2c31a4ef82924b6a5301036e86ad74269b31f12fb189d2ca5c8c5e2564c35baff6a1000074e2f00e5d4dc23bc4142161a1398fae9769d9181d2972ff834f76dac79d54743c16f07038763b1e8725707faa7f268470b5c49196da492051e0992603eecb79a896c7929b566cda97565f091070744ee966551a8ff5138a48d7179830ee20271a51e597356c826c0a140b279dcc79676958f3e0e8403551020947d38dbf59db4fa930c8c64c962a2f73416ddfeb27f36702eabc55e97245fa4e5468eccd9b6e702cf20676a7d1330cb53f550c2ae7ac1a4cb4fe871905f93b60ed1c746e323fd967dfd2920c59c7126027131366df61889c8c5bec7272c28a27f6e4936d5175ad5f7038b7aad56ebac682230fbe146d2892b9ece39049ec9f825d48a0c6e19e3cc8e48bb3e098bcbd8049dc96312c2de61646b1c66c570a2119ce9422c6b792245e5a4129a00aa0ed1662f086346eef8f4d650ac99972c8dd91a4461465b38ed62daf6241c1c116a6c56340d3fa9816e855e1572a2155474dd8c0c1e183295f570b4ef3af7671d9828d4e384950e9e0ef0b2302782172555dc236f604495446c60055df0ac3c21a1c0ce316cfe22134dc00cf7f994d2e8e3ef0d218c29e5a06ff52a19a906c88cd1f20baaf548627595c2335482e46721555a02a025f135ecfa46dbb762a671a79aba59483aad096f0cfd535940e9c72a524ac6a714980ebf8780a9e7e6f62f061226a8b0c3dbac2aaf92232a3c4b8720c6bde36aefacefdfac1e5e18d65d61cc27a853cde8ad27720321b0a17caf572555f994d5fbcf423d13090031fd5abdac3005e1596c8656f0f753685326456218419b4bdc872d8390e490f41f0549aa58ecce7cdb417ad6eee41176932be5572ac5dedb4843240368c30c8c4555d513ad08671a1c465e80d523ae2e5997ddc726ee2e9c7469fbee579c4a1645ec10fa551cccef8fc237aaddcef4d7da8599772ee8fd3f3d9a39455f4cfa8d290e4b9a4261c39529aa06e66f6b083634fe3e7295218f09ddd840d4e081f924c1dc188643e8bb2fb3fae13bf2b7ca19c12645372c7c94d21cf40bdbaafd85d8740a8370b1860bf894d27d7636c9a478682b2132148b451b5b397969947443986a8af3df40ca80deae643f57c310c622d00815d3ec341cf9b68c6cab862116f16a16885963cc97328fec01cc2effc3278c0785c72449a2886838259d69524b1e88a0d8e46190c7f8f4c248367dab14b04f17c8601641e080fd1ba9acb73d7eaca72da1126df46ab73ea2c39176943e657c2232b721cbe991d994e2a1d8c5ab1b42344dfa1c87920be05f7e99c24256229d4c60972770ec9992b168291dfe60feceebc6b2aa4522b3e4b66e509f748d7745c08a3720f660680a80aad1120fc6556230d72f93d528dc0c0d762d0ce1dde68c7dc25724d73a2b10fe37ef897252e11e1481cf879801fb850128a74300e82b87152d372e465cf3047bf68a742cf60ee4a8ef925c908638bfd5d9b1f45e0790600e938723de4fd930bc83b1bd788b43afe3b77a4e98aff72574001b2e411a137abb4294ebcaecf91ee7742d9943873f56a06bf9d01f2c6b49a2ed1ff7d5498791abe71720390b8359fe6e46a6265da577df1eb41a70f319c01f2484c87601cb93917127245ce01703c38f2d10b047bf3e756e3ab146c1f75dc6cc8bc04ea6b491f936d7224ee5a09272044c694a042f2935fee53b4cc793cee802bf349bc695930fdd625236b63b50766ca46fe7ea1b33732d2a9d8605d5a29d7d6f579703b356986346e94a8c14fd7ce23dc976c0127e6deb4579697f656fd12148e6875262110340f72f4c718997c87cacf19040c41d3d03ab1955360e2cf738a7ed5d9d78395ee517270113af051f3462fd9b2f75c62dd270f0c3b3e2f3a4fb2a9ae324a31f3eae814f4939e28426a28b5734e1c11ba32da6b63159d7017215d2edb56140430ca44722d1328eed2de769f52b8a909c8a4dd559876243ea531ec3855408db36daeda7216c9d979352eb9620fab08ee0d88f9c9efc23c10a73bc20c06923c07ab650b726a2bbf1925481b0d043c632437e1111d14cec9ebb47cfc44b7730b1d85e4c272534259a79785c44fdbbd30e2cdc131fdfc729eb625be5ec35ce4fc6eddbb0c72efa166b7cb94d800fd35d59e816858941e732be5e2d5c3552daa8115e4ef9a433cb9910cd3e8df12f451234994a7dad756e5ef91e02ee82a715480e7c7baaa7228357a6e294da94e318498c3bbfce4340da997568ca8e9d96c4e3231b1dd73115e49a326ca0137c405fbc18a991b68bf4695e1e5301fa21542751a9c3aa839724ed031bb951725ff9b23d523e92aaac48a87c2aed9b81160fdf0ffc75ba33e0a0046b11f36ad26ac61ea5960e6385ad9281a85a949fa3d521a431e12089ae672e3415b73be3615aa4fef4e8a64a750bf54097442e50489189b943a0ed22e6e409bdcb1f954f8a2063a034e07036926fecb8ea93007189891ff2e166e2d28f2727c3b8022545d3eb1d7df827d721f1b1cdb6c45843060f77bbaa1d1eaac83f345ee2790b01af20fbc44a5a1d266fb4c16977f60dd916b5591df1bcfe6a643e172522556df02422476cb321899a10dfd256e35c44d9b475e5f89af0118c423e820dd8b947c94973fca14c19fa828378ad349069ea9344319d757d583ae28690946c4f137e97a33e4e5e47799475879da4ac913ffb6c8df42ed123dd857474411485c277baec31c2b89b415d78e2c26fc6e73ea59a48bfe5f12e6bb95141783772db3040976e657f7432ea77264fe443a34f800e1d5451871db27aa248172db2a1204e2c0c378fe66d564d4c063aaebfd4ba43e9eae9dfacf3a91a07d2c26a06e72dc9c7b9a5a30531a651ba26ef99494c9062eb6fa9dd16bc760559f59a0aadb449629db4edaae2aa4efb77c04c6e09fe2a5c1515577bb30a6c4ad729f14902801cee5cfbe170b381527412bf8651b012a80e5903c13558ec4f212da04e61def720e1a3545707d56daf92d4757126cd23ade5af73655ca3787737c9095cf4c6a7214a7d94507c48ac8b014cea0bf6f665ceec6a4fc9378e989f3da90a97edc832d1596880cd2aab26743eecdc6cc6398a8174b1b4294ab022b27c123c83caf0f72ba7afd59107030e8ba7560e4e0f6bd79c92561a1501f800505ac009ba9fdbb72479ac231f551160b5d564b80bcf62e7b1105804014613e614b1240cb5fadea5765d4e16686d4d2edb83aeceabc887f8e5a80e80d0046af8d5250cbda30d3087273a4c4a9cb31292aa30dbe4091c8cb29d0bc6a5b59dd5dacf8df98de1316e7729f585c14c749efac29ddf44de08c51a19dce38aedc5be3686952fcfcd40d9072b1f2bf673bfb83dff65ff72e4d0bedfa4f762aa8aff6cf873dbb2c8be6aa5c723d64cf8169a6e67ff5e29a3f33ff5bd1b84cc5197c2c2a676df5bacf6b345c7211d0b0c60d23c738c3a4b7004dd65a2943dbaaffacd71db45854e258c8a52b0b631d389d9f235956b2fcad43159fa4e061a1dbba15365c5c5b482815286c1972bd2adb0fbbe101600abb2223f0326598300bae84263c3664e2cb76f835bd1255fcd37b28e161c4caae7dbaf9373469e7d6d09ad41b87c6f9446eb7e0d7ff2f7279b657501b7027e878742b46862a89c6cbd729d4113ff56b5ebd30d7efb365720c4f33d4f7b02d5e154c9f7484252cc5c6fa20e0c178b5136d7611140d6cd03146d8a5976d5295bd59dd226bdf8865cab17a0624265e2b436844b201e5ed1872e75c4469d25b507d06e6290a9a61102a1a49708503047163b75fb717c8524d724a9773b0f32233aaae7252c298d9155c55a865e03a117d3d34afc4876843477282468ef9f79d0f38dd8d388af34318d74d5742911b76214664b4663dd25879606c6b310c12ceee754241e735da1db41036379d2451aee6f83fcedc2bd64942721691c7f4ee078218914f8f5fe3145382795a7d2e7643eea444ace209112007184b015c20c0d0fcf2bfb1fa622bee61e77095fc722f5c203c5fd9039c871e3e72e183319f56886ca75cfccec71eb11e13a9a2e93953cefd6b0179a8e51cddc04d7de95406183e273893f774a7b31e67608c9107a3fab49982b710c0ce52c64072716352e9fc172204d2cbb76f704be17f5d7dcf198dd11ed2cfdc2967787267262b85e6f8c3b92b10edcd578879cc7d2db6888ebbb3f81a648082e4e1116a1472196f2a845921b191dd4352493cba586d770eab889240c220917c03c07b8ef34f9a0db110c61924c8cbcb476075b79eccb901df0aae53a7bff2bf833191f88e72f0f403344a91e45487e8dc7b256e7235dbb95f809ed2f780da08ef3439911972b42659298d636f9f44204fdb5197f9d568f903a85b059647cb730b697851687268d0c96018d8e45289dac05738ea011e548f4f13bb30b16d8ae3ea9de2e510722cee1d7a4fb3aeb20412c52c59f5fefd8dc45584697b70888d464101d39953721b5352de9686d1b207e683e406c7b6d3af6c137b12e5b9e5af5257b6be0d9e72dcf7c6b2d1f6331b05aeb760be67d6cdbfd2cdcb916e21049d05347b77bad721db593e863efeb699740b7016281d79c95e5123f544061f62715835000477601be2e94eb0c534690a84211e26acef7a7e808e0c266ac95697f0136b190412522785d9116a3030c139798fbc0d2ea34df7570fa19212f4725fbb5513b091318d72a5fac294759db68b48280f7724682876acdf83b963c2ea71bb6de8781b731432a72c7f90b4889f7b0315feb9c12a74b6ef860dd3bcbfcec80553fa471c257e4c785b217636ba6dae7734c92a12ae72558d63b3272de787f44d8114a135532872afbe422e2e10b7648d3824d8699a1bb8efb4fa5994e81afdde33bd01909dfc7218e1cb05c59f94dc384be133316b1926fd372763f2f9f8df4717b809b73797725e03bc5a5a8b0b14a2bc09ecb200635d163060a7431fcbfb0ec1a6374890e77256c6445f18c4b24cab763915f020db4475a178fb77a4e3ac9da0fcae5c6140704a995e29407aaf94d116fba291984503c890be423f5e39c4dea92768b39cae7207753dbade87938376963b94364e27f8575f0b55afa63e38cbe4df90ba2ecc7218691811d1a00913285116581b15ced6784091d40f33b50c12e69d1e7f84f772f043f15b763cca870973c40af511fcb95dccd45f507b0f055486d9d9cb323972ba8b936781222dd2c9a91f0cc381cab6dadea3c43d8c249fdd0127a477329e22d9831d18ee039335f40fac70bd3de12128e748d026e3087121d1e9e46ebd53722788478dad37437b2af942130927d69a3ef01df1a28ad310560f33112bce981cbb607ecf6eb7b8bb39c92e1b884d76f1ea05aa9b68833c049c2f868d92e6c07201b8d0c2df5259cba86e7b8652c20dec610a12e501c5206e34442e85ff761672b79881fc3317b6b470686e37d408e401068434a560db1c082db7df7183d55b72ab2f991601a038b4d7e1c207d4242732d46fc32c26b2ba162dc94cc5ae2d9c15edd3c1aea729a3befd992bf9f323f4163a2b1f4cdb403104073951e72655ad7234cbc0aab617bd8c014e546d55d2806a3ef0b116c4bf794e57a7149c1f188301bad2f31fd890c47494459de84c49ee85eb503a3db62ebbd3fe0672933042de440640014f6a0b7fb5eda2f246992d1b9b08217fb8656c5b5967aa4d68f683680cf4459036585b888c4a97cee8a99d1096ad9b22ef6588d85078e59f4edf58720629c12d5499d31886094320dd67ac1821f81fa859d6d1748cab22bea73d139e3ce0f7db96e21c6f2c1832d0da469df1e3f4e9d4877517b353f18c31e7da649e72aff5c41d36b6708447c5c083df5c6fc44c5899c86d12baaf2a05b145de7c61727534b26436d460ad14dc6393f172b4c1f8e07e06d77d682a11d4e4034a3b11270e35da478e2425c49220a208a4b0507843bde0a1af5d1d7fb6409b1d9219477282040b6a8e8a69430ec07ab288c907845edb884b76631a59508d99535c5aa0727059c9c497b8e2cb1304291b916ef808077a5333da82a43d34615444acd363725c38767dfa4f3bbcf5b4645c2b6ec5729cdeb86f22d1f3227e477e33e8551272d992a9bf00a44150f85c3f447112c9beb38706b9a9508bd5621f947d1bef9329f064bfd46c6cc1fdda39164ea449dfdcfd4562935d8ead7b29ad2febcf70b9723810dbabbeb22586903c704166cdcc54bbbd9efffb452caf7bc95959323dc272bbc5476381041f874cf8c483d8d4c3615c970e6d1fe1a0f0006496ff1b358e711d3c6e5c6770832bb232e7c55e9eda3536263a318f222489877d79c3d457b3549874e4305ebe8ec3197fcdbb2671a29baefa98d24475e440f9ed1aa99f13334ebd021688647a8e6013a6bddf7c7b5a5b5963733253f3952baa09be1f4a59c82f8376a7cdc91ee46c417d7ea7d12772aa97ae85cf0dd682b99abcc7d4dda80e721883e3b704272642897c55265714739acad463e9d3d96679fd62356f0625a672d549cc0181e4266e15e4c5beeb69549fbef31d6185a0cf3b0e9e42d7d4b42c72fc8adb777580ef791cfbee6198185930c3fadb1353e9f4c9a81070dc357dfe22edecd394b14a240702fe1e20653371c3188f2911d44e581ae671c7b30c0584729ce4220e8ce4cb3be6e439c6a5c1e962967d25456d1c2b0d7f785b85ba6be472ce2c304bcf8e0d8dd4b21380bb85bee5d5c7f66d926068bef245ad82af2470357f9e50fff0c092fdc3ee9c6db614d9cdbd69567fe0be87c304d101402644e27297f8e0bb20c7fccce08a0ff59a6a41b11a3f75e19dfee92814e3b5c78d7cf8724f71a82942bbbc5cf98c0dea7cc8e283d4ebfbe14d9093899200ba249eae8527346b70ca2273b298632d1220a9a7e7bc304401533907c52e2ba9b72d454b5e026adec413959047bb7c1f92c67755262efae2a9c45796f0650cae7f8c48e3aa72584208b9023535499f5deb011b1f93a8a05ab76074f1d95171b5db67ec0df972b561afe28c6f43df5616e3c218c42a22120efccb967a5f07aa84ee31cd380a4409baa11983102fd50d383d50370c2366937945b9db7885c870f9e15113761f4d9c9b2ec430276ae03124629963e272d24d82ac7853ecc84227e1a587cc047b720c3ef9fb5fb26353e3ebf0b913567c32f2fd5bae2965675d804cde3493d65e14d035d6397a528507f30c4649db7e8d4e59e7bc393c0b8ba989ca1b9d8c44ca43922a68a2c64f38a518516359f6738f0e57eeb5bc2fa6ea39c10182ccfb31b13195c591a52f10516b19d32ec97f8bd94cc621deb937f36c51690139ea7f7361151c02b0d2855cc36dc353cc2c083ec5fbc6c24668a79912ac44c7b8bbce155772e6b03d7f79dbe7a977f07f51151ed7bc74203fa7c867847a3e6f63dae67d4e72293d337523033366fd09624baeeb2ea680cde6c938eb01ee74fa143e7c77a019be2068a97adbcc2b0df3ad0286ed865be07c2bc651c5ab3cbc2baa209533a4723ae522f0bbfa75b925aae7d768a95d4065d9b16bf87af10ada24c20498896c3fbb7fa12c18a868bcf43ea3bb529fcddc836921039cede8168fa45bde02197e6d4a6f5e92a064899108ddcce3658398e1093b2236797511fe9dcf743db53411728411dba9ea52378a0106ad0621924a61f55380d4e755b3d89365fdd761de8d4167d42573703d5cc194c3eccdcd377e36c6b209f84d5ad46bad99c8e4b4a2e472d8ed80244246293e9579857fb4d338e1a640176ee2c1e4fd32011aa99ca4d91d98ac78de2d87470049d6e1128fa33a6d9218bf77e69dc72a6fe2d9e671ae8d72063dda42f1a5ee79e53b8704941168a22ded64a2b2eeef000ba7f4645c636972ddcfd22b203f272dc838a2c1971c644c295bf49355e1ee8455419f4e79828f171f07ee5460afa6d688855b796ba6bbed751d655b06b938cf6dff88e6db57a033d3c82a00bb99fce8f842e99cef229cbafab52c1d4768cc22bccd76bf4aa6494732cc0daa4c58f1e81eeb27939566597a511292372c476fcdbbd0dcd76438243ed5b9c1d0b83f92d983f53a60a465f97a30f60101b139f25fa2cca2856786f67279cd1a4ff85072771ef243686c0e4e5e20cbf3925a042335a9111d81c5aaac724108bbea3f3e7401feb89bac422147ac379faccb22df299f22f0b084497217720c237bf46883e9f301a05aed797db97a0664ac954a281b2b7de1c1ad3b65ff7296d8a865355ba04fe77ad39a0081bcfb00f6c052ce176a8b7dda2f8f9cac44728547c3b0720977866cbf6c156cbac9dbf2283b418cf16dc9b50f1421cfda177277599b4e66d2e2ebc813f8476353127900a991b9f5a9abc535764e01e0431b6e790a99f07acc2f9565f98f450570f7ded4c011bdcc5a32680d3f99c8d4274372a0288d9a2c457666d6e9d4f4806f825c5b5a3881bc6b01ae28578b27c31a5b06b106d6f303be45fb1bd343da206624eb5883d6db0d14efa9055c5fe69a40ea65b5a0145f0f500edd1c808877b6b6b59f876e03e859fc4a0d26507f5c3bf51472d34c75841e995e278fc9db0fc771f54d9b7bb8b0dff0370bcbe295b43aab580bb5aca99d2d19dd0e508008553c780d67765bd30c6be01f12b8e80a9eadd96272fc5f91f6d8f7caf9f46645a6741b0bc29dbc2323ea826aa8c76affc9deb8641e7e21d8d757aaf7c12f3125befef4f51a9a9d153369977c3b219ef6bfb1dea324d07a040cb92015119b1b1af9cdc516c6fc517eac3618a55d867067d191a8d07051044db20cd7d7f81d6d6b8a30a0cd860c716da2e4f04e264ae0bef738aef3727b4417a8bdae5f2f6d39dc477ff9844abbb47084a8ab23325766c755836a9072343c6b7aa6ceae207992a8e077b7047ae5326f77af1233f15e9c8692eebe7872d66d41ce94a03e352a6771657da8378e726ee4518619cae7892ddcfa035b8267b37767c94b5a8f5c7d57a06830cc523cc998e898b9defda5a7159b17595d6a5b78c5e0e37f8c90ae064ff9135bcc92e125930944d85dc74059dbdbff3d66876a70f5316fe1fec538e2e9534d185c19d9a155a7a892271c2507262cf07ab18c72a13ef623c0d1f367c3725da9c48d36ac7a04810c41b215a64512a20c61be6f1ec65967a136ab1eb111138615f92cb2562ff961e03713e731c5b683029de4b40a5673114619ec3142759e3df4a1d6c5289c98656526d97f532f8d5d551c3b2972b6cfde7ede706293d0139e62fb0828a1ada5d4c675994604360da33537866e7298100622d5b444729c9649625a6f78f40c8d384569230c18fbb1853fbd44a2724e0d3d031bf3355bbaca32e8b2599363681481d555f5f60c57a7f445ff194002ffdfbd05f1a6b30bc8f11b76f2cbdc824ca8dadbdcfa59d56f3dbc1eb266461c43935dea357506aa50ec3a6f5297057400f3ff6c187498f8b0698c6ed56d6072f6f15958840a6d4947b9760d95d3f723824f28fa4256f918dc7cdc8bd322ae5970817fcd4046dcb7bb45806732ab01fe379c8a7af2758826dd3edc69d12cd2720bfa6aa8548bdc113facfb257445401e4dde53ebaebedff2284d40dffbfce87234881bd134ee118977e206cdfb55305a754e1c0a067925aab240c32aa328c9393e7027c4175941368956acdb4906d25f8dfaea0d069eda39d83a93339c10327284259338132e47f6e082cf11075244f13767eeb5f164e2d1a0ed789520d0626c77d930096575104062257ea5b7e32773a42e808c588cb98f1d252d7a8fbe74720f51f730c0178ecb09c8dad309f3859e9575eb1128761b6f204e1bfaf9556972f85f7040cbfc8e5e439548825ad0e8dead5fd76661c57a3e48b3aceea61c75724b323b34bb6108882b80ef3e3cf37caf793823185710eea3faf826b4efbc0d43fe6d04bb9515884222ace3c6399e428c74c3759ed3df1739fbae38c2fbedbc723f87c4c9d5561955f65996601e55b2b0462d81107264da18a44df317f398504e668a6ddf69b67f47899c592cb351576535efc5628d4120cdf3e9eadee4bc6d1039782324230d34540f2d91bfb422ac8375597c95a5dca0e1cdc149b54642543f4aa8e0e63e5c7e9263fd5c1c92c296ef9de521761a717052821c77c3fafc1572ae0ddbc7fc7cf5e986132711366fc9883372cde5ede5223d8b4ff694d9e94372beeb98d673a6cdecbd17e51cd779841679ee90a58253b891b5e60d05f8e7ea3ff2c8169c3dde2310aa4505cc731d756a2d84ae3e34c6d03ba1acd1a8648a4372371a6296824fe4fc5ab9b242c3376915aae42c822ba9c0869fef85097863f42e3f7a756dd9787eca331e6ca80f62e1cb0d724d0f7356c1e4d58dea1e806dd6723ee709319892d46c67c81b19dcec73e9870e5a18535542f43d7e8741290f1172ee00565f462f5e449c76254be57ebac7472235704403e3aae58da7ff4fe44072473f4048510cad3e37ea87694db6fc900d9fe069da09577c9039b32679328a72159bc25478489b6508c89087dd188ec01138ee06e694a7c7bff19bc7839ad8089aa9ff2fca0e85b5a6bda93a86c068d044532e1e6ac75ba42e08656a0068da32ec53d851198c5e759efd7735bd7dc375a530278c69614d0c713498e151564872c8d98151f9a07a3a65e984e5fc3706d5015c00f5cf8bd138b694f4818b16bb0375d476f653446d49c3c493cd159942e3c0e6d136c5ed64be6c0b75d7c50f1b124ebc3db3061d77cb4bf1822b8134907e0e242a8d539275bd6d7614165f10016bf3b70a487604cdb99606930f36db2e8a5840cde4ecdb598baba5f82acdf6774fe37979011964e82739b8eba00db5260d97369589459c0596e5148f9cc891c072b6f4d081b7f7a9057dd16fc68880f402365bd2ff6a6ba321ba97ef01809f01729dde1b925862986b8009482c28be2f8a6692f89d283c0b41c3dbc53ee27a12118b45afc3ebbc3fc01bb24ca77a0dba9f70ce1bfd07f7d41ec9ca8925b04b99726761d4b603506ce444dc58915133667dae276cbc2f5cad162943c9c32b3cb06afef752c6c3c761914ed69eac3bcca19b3e5d76119ce840d9decfd1efb6fe8251c781b1f0de11474cc3e0bf6282a2b3af72e965709bdbd41ef9bc31b6c6817d7212a5dd2105b7da3bff141723e9dc051fd212cf7414d3647ee3611ba73215a37269eda8536e041c31e63d30d164badb160bf24a2ee7730b801610881090a8fc2b0dfc2c78183a7f75ef14196041a33caf1ead0b4386d66da5adf77b56248fa73dec361d5e6b027bc16aab33ff9b04621c465375813a6857f45675637e0c4fb41d1b39b39363d1e8b2f6d373de1c8c2367f85c945a432fcc1cd14b451bb48350373781b7213d450eb2a9e72b09254234af890f69e876252ef1574c9f945530176d9106616febdb6d0c073acf133ee90852e6a7061446b95b075de0f9bff7c0dd35b1b6ad2e1077f5db3cdd63aba5dcec97bf62ac567dfcdc1990883991bdec7721d849ab61764bec0ba36fbf2426ccc3e2c18f3037622a16bcbdbeebebd51c39725c1583687747cf39993099b0baffc6561bce572b87334097098b63b712697b42d471536312f214bef95bd4e8c05881db5c6596b9eac06aed693a1a209725203a09d77d8d7ce16bea422a46b78254d273a4d2af20adb1041040c6f9aabc878e1fc771eef1a917c88c794cb958cd7c3750962d2412cc48a2242c4881361a0577538efe94feb57d9573c321d0b3f63bebc342100e8add1efdc9cba3c45df1efae44660357df1749701efe47d130c21324ec331daf9b54f803f45b2ca2046ddf0b0e00103c4caf2e3a2da33ec59962d9da41545a7baa42ce46b76705bbf75b411e72d2cdecd7a83d5545c087994f78034e816af0eb38e227e0ce86dbf0ea3f34ca37240b826470e64823ee4870c106db3e9502526d42e33492ae1eb65f6b8988042a5f8487a23815c512081e7f6df7f1fdcbae01fff7e1b5948d32fc83bf5d4b48061ec26723825eeaf9caec704495359e4c4a37644e81cee7adce1504619ee49272cfaf1fe19f60b650767bbba8593491b87d6e332999a6c9f0e3819f14fe032072b0e420e7f93220a1ab72a658f34147be4c324a24da920b407c2e9f75c15937423564f81b4508843c37e331a9cdbb66f516efafde381df65c1206848262d9243013648da5f248a86bdd07dcf68302dc814a7133489580c17be65aff730f7be43aeb619ebab7ee127376c26da4bf2f526792cb663b9edf8eba527d630007e7cf724a3255ded1a1474c143f4f6b3755345fb43e48a6e382f0bc48268a0ea23df1257b30f40927b344bae76efa8d38cc96047f9393ade73342b121c0b41ba29eb972aadb2d00d18c089292c3da04cca97a343915113cc84291861cd1e47ebf026f39552bb31a6ce218ccc028e4c4c3dfcd2c0472f91f41228e33acbd7a6a48223b17ed2dac6ec2ecf84530443d77f9a1fa85eb8b3ae756237baf853d3abda67e1b4e308da84756134f10ac64ab89ce7a0c114186bc7e93fe599b0eec5e18acb1d652a84d0938c34b67721ecc31f024131794eaf943e490d09d8b7d4ede207fe6b77229e974e007bc82bc3e3cf36e9c478d6c7973bf5cde164347abf55c3e8566c4434e863977835967a9f9cbe791d9f8da54cec6fa1fc33a4ee387653292d26cc7725e5f8784e6b426b44180266e942cbae13be31b2fe922a6ab60a3bee369e3d24e69d8aa01a8dbb53498dcdd468dc5328e13f73f885d6abaa596b209193e702d7226af8e76e290c8f506e36d4aa59ff4a5e093a51413cb2cfae8f56042caae7e4dbd9aef42097d989561bcfb9b9b8cd6c5a964b949ff0cd46d3711dc9799381272a03bbd30aca57bc92f60e0063ccde53b64e0ca17183a70be63e372d9c6cb11727ca65385cc84c95667ab2b6dd583c304974c47607ca82ab5aafa036b368c60726d1e3373b13ee3eef1df9601e540c7ba0951d4c37518c7e569029bb1317ada72aaf9bc01de13991f61b181dde29da66857e590d18aba43b8b5e9f075ef10f67295a8c4d8a5bd40ab8ba8d892c0d3d17a187fef7fa76f2ac2b30518a124b46909142e7579392f118c1235fc996c9e5d69c16faf9cdec72e85644393d3515ba1301e85aaa982ac8b3f5dd8886f9119374af5198ce4ead11767c697efbe7f21e9725c280c6ab0a8b65df8eee8491c96f2847705350e7a7c0e51a8fffd2110c1af7236d2f26f9e128aaed90ade17aec55e68265a50e86b2488ce2d69a70b8919f20af75a957a3b2d17c6d6adff7c1d36cd9b5fb55fdc13c6cda00d16d76cadd5b63bc65cbdb7c7dd76b1a9437e556dcbf9a1d1dc534db2b1135f85c54e986d71b031475bcadc9de84c7b0830cd8ef2d0b2ac88ed35b533cc35d2451991ba89b4830187ffcf05c3cef4c9b5fe8189f869ced72425a5f38c9e1d9d560c57149fcf852314c8c11625f5274f8336a0ee9b176bc268120d01076349df4ad7030f8781143334161393e98103a3d4811ba7c31add92bd244aa45297b87d0b145944076677726afa70cd6ce6685b451da8a7a04f08ad629003ef0efe0c240ae3bc1b2938d7727e6103a192779661495fade0d23103e615120b6ab5031680c1f6c134ef1ef8726f0249123ab9289f6952f242331506fcc1378ed24612f1220d7894245ae0aa1cdc432727952b5801e60938af3b728bd56e73ebbc3e91a9e6ddd34e115c40857267dbe0472c6bccfe5109a07556d0920e7485dfb25725b2d81e1c2433ac417061c7b707dcef064740dbbf988be31e9fac5146ed21cf460d913a9517ada7b644728ea2db36a5ba53a17a195dd305e9d066e711238499af4d32a4b84068a3ed370b211e938f21556b238b45d925a8f487367abcc07b26d5e76cf08b436a3cc5065597b3423ea8d7fe2abe884b9cea537fe18c7ad480fc381f5cac0df363ddfbcf1d9a2ce97de97652c3b05fc6109f67d1a466fb2841990542886ad7bde5790c7d72830b1ddc3f3181983b3ac613029badd2969d199bce27c6b840a4e46e046f022eb68658cd40756f29d1305a060716acf24e0d133a08c8502a927b0b4d4d654872fd0a53d46265eb5fa4e65c909ddfa5a50414b7ee4e4f247483770dc220b54b7207de4e1fef1a7b616d2f8c9d3a4e6c0074eeac44b91226569deb9ddab4b1b072f4bc76e1de37cbdf94d67b6d80c416d155f74eb9ca6da2cedb956c0b693bac72675d3ba65cdebba410010cf90a25aeb0b107fb4e39772903003baf75d501d85b7874ba1ccf4025696f2595410031f9e7d07d5ad5df6c8670e022bd4faf682c72ef359dc2e6d95b6d3bb7abaa0170a0645b3cb46a6a78c722f524c6dad7256c5178b2698baf747cdeafcfd3bd6ae6be04e69a86dc06a06d55d37e062502893e303f8a426c7c93fee73f8bc378954e60eee4d4c302113c4c755b0b4891bad13d7210743523835a028395c6385a5fe9e09b97bcbb28361b76a34a9d006aa686616dbb545729f1618d1a493b23de68c7f4db70fd0da387adb354fd4d9dfff06995721fbd5ef9cd00c2e0a2cf9bb7227c9838b113537a96a3734d42f657b7e9fd7f13776e142965e4919535f08e4365d51e1fa08e17699e21bc62fd5da57346d6f70987d44ccec999cc1cd488ba1e2d2ded3570b52a1d5447370ccf52ec343bab84023af0a0b581a36e4754a80d6d77da5ddff72ecd78db418f7a99b14140d8cdd172c05e6ff62f96016750abcb8f3142f0fe71f13741b1c3a50bddb1b6dffeaf2a54fc87d0f80fe8acaa16e0d5184178bed6db08dbd3d3e19bb3bdb997d9d656b50b8c23cb94573a27c3d1f16c73f96e313a7497251786b1e53f53143cfb9042717281dc0ceccba65ff2e43c7ecb6c92cbbff551a458c6be07e262dc26aec3aec872a0661977fca52c74eef7b171bf6bc671eb43ac94797168b4f384ca7e96be20728c57bee1927da8b1505bf8b1485625cc67030ebdd959800a177543d351d8ed72b8f6e1606045694ca55d376eb4063ab242981dc0b5f7507da456a6f4bb8514729190e92a4858bb47fb9ea3e686e038edd4643eb38525f1e4bdb5bb0089d3a70eb0897e5816c7bf0e4693868d81be147ad79330b1fe4986b6f95edaadeeac8a3c3d09fab149d3ea7b8a8ef8d68a7eb52bde3f3c50d123bcaf007abc5662d81a68d5be36077c3bde86ecf4d0ae1459e503cc10204dc9442660bf6a4cc095e93d72dfe279c0593b7b5a101b46cfbb8182ba7dbbb6884f63b47804d359b8731cc83f5aca0716010cf923d4db13f8b05244dbad4bb14c3c9fb3fbdbaaa05d0e59857233fcc690912fd148cf8ade3c6c59b0e0b6a3c2144092c49852494ab6e56465051af16c66ac10d4cd076b5775e649f8da129e7874f7810bbce85e1a0fb82ab9722a65e6198169cf8e3c52ee45ff82119b67c8775737010c1f03a697228e0f3e725eba6a5f0a90c3a9f90e7dc21a8ba5bf8181b24448e723db71ca270ae5918e72bf6ee5a2c7b121993a3c46dcabc163f8fc40bbbec49d60c642bfd89611c2a572a44bb92aeb4d1c11ebe1515ba0abada9c39588112b0392fa78f8ea5b523a9b24cdc61fe3f3293358337bfe05e844a4be752508c05f72913e64d4750d9ba760727cd0a8072763b21acd76d5d44f7fde78a3519b2c4ae527a086f5179e70cd0572b7eb983049bff364320ac1b3f0d5d80693d630d28ab9b4e93e4b85fdc1128e72f8f202fc934396a03e247b28459418e61aa93d1ea075f06fd27e9292a7b71f720a41e1b428804996b5328d9ba25bd60488c2187f8c79d6dea20ca9ee335c05722bf2a2eb8b2e3648fbd85fedc0a18ddf22582db7011d618483958936c45eb61ac469dddc89b317cd2f07659ad682bf3724ed5f9e654153c76e740cb9989fff5a96e01a0ce24e62288f93d0f26ad149e7c8f8f2d524ce3fde8f8dafc07ff8e0725d3d5ce7de65946bd46858a901469e2e837b52100ae12360c74cf2b8f9876572a751967558c0ce63f7c982befeb20e79ec3324cd8be47a57d2fa79e520e9de7221ef05dffafa3bf30d2fb233daf52ed595769b928ab522ac5e97e6770ff2584c41b49e63c644eb236cde827ba8890f8641f284361d9b79ccd34012ab7707af72823f582e13049b6fdde744a14a3fd5aa2005a3159709e6b1d902cc424a7ecd72a7382f2482122ecd719c82df4e423b0342042b2d0536fde3a9468697b9a33672c717b06766f34c734798043088f4ed511699de0a4f8f882174dd04ff54899a729969e1ae05addd182e900d895d46ad4284686c4e1824ce056668860de49c295a3f53c632779daca6eae234fd39c56c4ee9e9443dccd6d3290bba979e15de093cc21499e2b2151b9d8c11389f40b880d5ace23a21b211fdc7896a3f8d5a0a1072d7d7ebb296195af44a06c6e35ee5a0a9cbcc54420e2438c9d623f456731c6f2ac3adff1c224191f9c2787e5669d7b5e97d53d9400147670d15bc42b91f3e6572bc9d55e3fd8a8ff5be09b34c9840ad3c0fab2b7514df998c0d681fd2b8790f728d5dfdd9ea1726f11b86033f881b65bd2874bda3d52c48312040aa980914f97248b2d7fbe61fbc0d469d601d2d89abf1d554e61a3d4f35d07ece91d05d60517263845b44cc51d969c0c32fc60e4922db538cfc760c5b57430ac780830c718c377715c0ffc93b71cfd786e273057f95eeb0173702bb12a1ff7f028bcca9911f299c674ecc9ac4f313c12dbf52ff7a19e206d27e04c7a1907dff8a72c40ab5fa72ce415f4531fecaad4fbab157ae2547b5d46d9d9c7d8d0f4229995cb506843572d61c5603525f8bc21b0d3bc58316cee7396f7e1934227c1c3689a5033c02c742969f1b27389994d182d62a96519c811f0280631ef995d4f8957c9b9c0fac5b72264c621cf2629bb315550505b9db0bdf3df9037803c9f5233d624f9056567972347d433fb524216a44fd630f21b1e1109ac5071772f23afd80232ae69f656472c7825d663907ca619b56fffc2d8f23924c08101029df34bdb18953601873d41aac46918836bae510f11eeffcdbd8405653dbc4d454de34338a8a1b69623c7e6c778d2517792a2dab6b8260b5a3295bc92db9f7c7073d52691c1743a1606c2272d16c99edb2e6dbb7bc96dd158f752bd0c3bd091e8303a8bb921afb0a2cfc0c080d7348db43d6e7e048960b24c9cde5d609e90434e7fb426e2c262ca3454c6d72dc2ac0b53e6a1f8350241bf046b6247fcccb2ce2dd1b78eade5493ce3716a84b2c88e9f8bdea08a0f51c5517cd918efe8501f8b0fcc9bf2e96bf99f28b1a1e72a77c9ae6a09ca976a9d343bdfd1aa369375e3ed73fcd5a61c2f7591029b0667217dae07347008cc6d8935ef1c06fb9bc517dd64e0d7fb40554472dd5daa2ed1abb49bd2d76425cb2d0458aa815cfd3934366be8b96851973fbdc11810d29fc4d4630abce7c9aa75fe4e6f477ba7e5a2ef8b04ec9b237f6326985237d23be8d72b318c6e2a3fe256105a1d5e99376b2f448e827ea1f5a987bb826509c7e1f7a72d6a5b5d331cdc5f3cbc7097c5579b5577bec22a7a58b9b0210c3af29709a3706a54c42a1e8d9f44c62957694628842e20bdba7f0dfc303fa4a565b4317c80272c7fd2ff4c7c0423dba6cbeb68cb10def07f3b52fac80c3d8cee99f54bef6d272e2b3c66cf655251fcbb03770b5382e58a7bc815872c314bde4537436235cd372fef8ca07baa4f9ae8b3a9b95c872e7d9c6cedf034aaa15060144f29bfb48f86c82ff05a480a575a6d642b6a8d3ef1262602df89b898d5559c8d95ecb8e38bf720ffff8140e07aae87826271933a108cebbb8369c577ade9afb573e13281fff72b6ee98836d35c690c9180c41825085d35e7a2b12af7e0c834491b447c5325f72ce70629f7933b47255ee94d3dfd8a49133a17c534b1c10ff61f3c7ba81a35772eee99b13d77031559746ed7f6f39e2a1d21279b5dd160886aba45701aa443272558bad0648cc36c963aaea2ace90cd30a211da7ab5d4af9e126dc0119726da726e20efd3630ee3acdb558bba5ae370b6ea8b9aee5b6e35908462c980a298ea72c8b97a74881b6493cc8efff48870e8e02298f5e41fccca1d9372bdd4cc191672b8166508170232309fc26130c95700ffdbc0d4a45b96645abef0f59194a8a97207fbcff84225cf8359ad4c00116ec1d4fdadaaf638dc1b5cdbc59a276e793f72113d99a93a60c392d58c1654e6a283fe513a1380dec0aabc532cedb35b29b5729d8a7a2af8e177888db6461c455527cf3ac5b7dd91f98af2e737c28f03ff8e725afe06ff6882136b621bed057cd35912a3215354008d6ca9ff62dcc623f8e81fa42ef665e3044605a5787e7bda5425342fcdd5e8921b8181792bb14b5de7122b5b36b07323035077fc6939d76b56fdc2ba8166cfaa416f5924a62ef9ca9ae5604808753f38c086ba33662f4c4a6ae24a1ee54365deb9d8c92781e3f38cb2087291a4555b4d5687f73e040e4bcc0b230d0abc88541ed4de2fef5428c85ff1b94fb3312c46d56181ca6ea5e5b87a9586dd98d044fd308b9167f59618b12c1fe172e5ab51caf1114616734b6dc6984731ab57e99cd659e16421d9f190ea20ca0110d74af6b3e77bd1458e71b84412f3c963477ccbe5244fc6ecf82d9471b04a63105ee86801ffe2dc020275e8bc06cbe47de189d807adba5ccb62e519c0a78dc17039b1aa5b695667ba064401ae928a37315d392ae9c7a576cfe559de4197d61b6776236d254775f8cd2728e4f6a600af93c046b9c94421b62169f3ae805decbc1b42b606a4a62cb96e6048d9eef91e7a0a5f88626fe68984ede32437e82d54a02a94d1e24e7b55eb8d6ecc91a6c8935f9fcc230daeceb7efc1f1bca8a1c3a95b72866233970465f4a349dc9b51dbee52fc7d1c575c489e7707509754c2ae90f035c6c8f6e6c1d097d22bddd3419e4b7f061bc4d88a11de6d30a6af867ed44db17260c6e0ac8403de3e3ec5cafcb1a45351901f3426ddccc29d3e0ce9bdbe1c060eeb33aff01fd1db2de3fa8b20244e1b4b92673f0c891540bd2dcdb37ed25b7172c7fd4051cb2136b69dd263ed187933a91bbbbc504a0241031a3db7b9635996729478e650e921006d1c0b3ece9461a12bff1e6abb4aea2b1ea01e9cf91347ce728f75aff16f7b2cb5ceb0e4c5690479316f7f7b914e919897338281be51bdc772b6279259b18b3b51fa3bb5cf691f72020f99b06a4b39a32f86155c1d5be20b72c799078745890b9fc45636c9f33b616e403a7e74d282e5566eaf938c32bcc272396f8605e33b099c97d38acc0c81f41bb59500cab37b3b9de32eea603d8b6f62cd5a3d3d1d246da906e9a77af91ed9dc6c4abd08f016612286f5751e4c1cf872aaba3500cdd5c33e5679d43f241b230f8da5c8e56b4340d10f5a7db964d05f723fdb211152c12c6176cbe442a8f92e2f373b6177a5446f13f73af4728946bf5309dc7bca6274d47f89fc3a43fb38b7d2a86c86beabee73fa655f5362d2005372d7235c3c47d5130563130468569c8a9a3ed4ae53955f283a7bcfef3124680e0e986485089b4adb45d2979d3088a99f611e98b3e4fe65b2196393ec78af5b0c4510faa8624cc2422a493cbb00c3dfc72487a6150b92f732b5761d54448d96a07241dd8283cb28c0f733a9df4cd03f4809d437d4f9fa95faba4480e638d7e9c151bf5053e82f9a99633432a2db3467444b395eeacbe01124ad04ee436b52a6e523e3d603f1c7f4af6cd2630e251ce77290469636949cf86ce92ed3c992fad765218fc20e070b7031d07a4d63a66b1e49384f37b3d1a2f948e8bcb84603b6188b723f84d9965b2cfc80b0f61b9400853c9d5a53d3424fc45dfeb601a01551553972c21a0bb74c6261608131d590e4a4f0a8466e8bad876e8a287d12ee3d06047e725f2c83144e38e53cd269a3117aeaeb1632be99fc5128728a692062a7bce7b838abbc754cb62c9e4ca0a5cd0e67d9ac70ea027b8c9564ea5f27e5228dd321f26417e7c65c341760d1dcd494c026c7434f6763397fbf72f996641bdbc0820a2d72bb8da2e732de3fe25856aef433c6790e7a8728d73dfcfc20feeaa5556ea513720e9e98283a8a3014730d3a8ce62f653227618c1f2d19cd680b70d776f6c5e67274036b4582420a3549c5bc5d7f1e21ac475d929b1bf9588c635d40e44fe2c472933f4f3adf048322b5847d2dbcc78e2f0b4632615f5f858bfcadd27130d11e695e484cc611566176c5a07447e2bfb3f72df2459234a86f3b5fe3bb9a3b76387241c09bcd630a893699a526e11d5f430389bdc597d3cbf76348e603a596dfa865b65fffc70ca585d181b3bd8fec0f7623a85adf9ae4448715c86dcf93c67bd76e9b79462e82c585431ebb856dd64a3190b7ed30ed9cc8c57710d7fb6a9f5dfa72f15dd9da0033275e3343ed2ed908e114ae8b62eccb95ed7f65772886fcb10d610a84544c31a6da3142650faa6d14477aa562ae50e5600d919e3de5c9b1b71872a43f7968c20901668a84742217743a0255052bb153531f620fc357e664d820727132d864d1243f8a3fb564c5f1e712021f1d880681570ca0e06148b00a30ee728adb2bf35626fa93275c9cc4089b003b6bc917ebcdc845b5f94ec54cc9dba6193385b0f97238b10830e496d655b8715390f1a8148c5370e4dcccc988f36c7943a7d92211e177aa6cdb9585bf8d9bdf55e4fdfb05a3cdd79f7b166753732f341b9c1873d99abadd615f28dc89265d2feb436f44a4077d033d508d31420effda72cc76c55ebdbefd152e1ecc8b9db5301ff0df776023b8406ba251440d7056757210302b79bcaf76fe2c2ccd5a1aa2c790b9aca377c247e219a17da83d065d2c7222839dd9d0f99645652a50689bb365c2965665045072f830288ed77d6a690072d17f66248a56681736aec70e5af17d8e313eb17a4331a8969410702cbc247f4ae7ab65f28a9f16675145bb0dcab3e839bbe0379cdd5afa95a3dce49bb2fbb71cff14a6ac7b97ffe43adbc9c55da408d7c7b6490f40ea5e36d6f1d494ef4a8c7285230e80042c2e3544fc15c19ad686ee792bbca758a46b789e3a288f90d6f272b40313c965120019479e3ecd359cebcbdd93ac9787d4a9009f3e788ca7b6e86ae31bdc49a4867bc83edb0158d714308dc461f418bd7858f43784f0ad20c797466b6a4850fd6de368c3d71f09eb754656035c92375e35c57c9cfc93f3be2e5c72cf75e37537a02ea08de1ee5303086b9dd9c3068a27ea3d6931c5978c082d87724686644ba4275d4ccdeced8de505f964bceb6e78870244aaf01a6e6b88bec53821ec1f1c12f607c20b96449f482e9ddd2072d80efd48371b585e8c1ff5ac9009f159c972b9967cd66f4f4b4d53ceb8d3ff085c088d38dcea1adbdf2a14a16a72cd41d903c270139afeea07155286570382e50c213a3fcc76b358f302c11967729c0b591d8e80bbe78998e3fd3f0b456a6e437e1a80e41ef67b090bcc26aa4e1729608103a74b86930b2b52375e1671144697c2c2f9dfff02166e2e6c6b8489725059d3221124878ae481048703e9ee58504b907882ea59e1e505e40e5ddae30905c488480aacaf40cc05fd134cbd2cff25caaf2bd7b4c243a63c6b4a2710b87259b7473a9115abd2baab096da1b7ff349c76ad544a4e1485c56c6efd0d299a35dadc4071733a3cd266196997646b1fb909b486e26152f753ba5544681acaaa7284d6343e4eb6a1089cf3019281d6b6e441959a6ebec27e830b788f47e322cd0c7564566bea23fc164a65e8cad0e06b39eac35e3544df981e66f3964b86efe17200b43176de95c44e9dd106728b759f1aeaf235615e3116a1ade97ad2b2766f7208cb733664231250b91fee9dd0609bbb88311892c48d133b54ca48abb6154a72d1d2d686e212b47cecff13db2e4afa3cad7a7306f170aa6067b6fe21b94c220f129342197cc488184d9e79860eab23e3e026c406f6451cf88c187b625211ec72836efabce705750a3c78139444315e819b06c275338fe299bf7e0444ac64b772b6aa6132f0fbfacbfa819893c53dbc915c2651eeaf6b1ee3efe9f52891451267ff7aa75a50e41da227b9c65408255cf269f3b1002b847849d13a090a04bb1472ba5bc2e8255847cd6f54872b246a16fb020a139398fd532f7b34dbe0a9c6237230ba969fb08ff1bfb73fd24ee3d9a42c17660023a0c229808a18bcd42fac1672f270b68b49fe2c95723c135be23039c9791bd3c42100de41ca7a0347d3fc5672b7e8957f45cdc78b435a0199ef607492053be75aea35da228840cc66129b3557e43eea0ae2436b324b7dde17ad4552e48b4909de62cb7139532809637116a27247249cce6d1f4c2c8b67151ebc641b2491bbf20dbef28eef6186156ecc63610440565fe7611cee2b2ba0b8715aba858c9e0c9e6252649ca80b8c44f1e2032a2616c301e1f1150d52b2d878d46a54fb70a22e91b14f9146d8cb41a63e745fb37298fbc20c7952654c2ec9d656bb0c259006c50ddea9bf385524095c7af1dbb02cd0e9995b6479eec08bf2fa52f7619c511d1b27030e0d6fd1a00950d7fba5a372d574f8c5b8770292bf948a8085d26f9d5dcea35242a7d7a59b2cb7237c492443148d3dd1067bcac3b6a7eb1fab99e0e71da25374e8b8657f2ccf975fb0e8842fd86bee93648f3e8a3274c989cfc1c0c1f0bc2db6d854a97660217e989375d371551131b24b1e3e513b0ab7399e76a5adf0038a45067d7ce5d695f6077d6df572f6630359e228f1ca3aaf23978318881ebdc1d5ef7044b989d6a351cc9df47a7271ea2a618c30ed04256b59e2be0e2ec45dec2530597b05a6b9c7fcc669982a72acf506a91d4e6f18d09eba61b6a8ad33783fd1acb355dce4679d1cecc17ef77242081e7818fbe55acf85d038be41caf9ae18f6c205588abc23339deb17523233c3ca941e553621a0131b57e464dc662531730af30b6dc4023ab7fc7f4c6cc372d86c4569f09caf0db07a584c86c0021448dec5d7627187ce4d68ad04c61a4172f1ae2e0abae31b49f5284542d0129d2faf64de0d211b96e6ebf3dc4245a2965128db615fd932fdbac7c27038c83c7d8cb5bf32cd33322722104767c64ba38c72734111d6470a11384f26484bb503d66537b9f323bdccdd809f5b16d676200d68b5f90c7e54760cadb2d13b9726982c0fa55ca91590dbff4f08bc5903bdf24272ff591773a67b1b8670fc893b6af9df15a1bcf11ff5d72856acf43ab1460bdb7273689725e8c116ec794724ff31999b2cc8c8512230ab53787ed48beefc7bcb72ecbff43168a896e3eb289de8ee7b649752e673235a19d66811d4f9a9d624af5dbe3135e193074becb057d70229cbc627cf4eb98a8c30e3c46aeaf83c68d2a072340d8c71059452276a1be11fc927f412276ee6464bea13f6b971d2f157187b7217031d0846651fb594ac101bb7c8aa788813adba98e8ac2d9b10a9f4f013003bb2c3d46456cac876a027c7eaeede8c0efbc44f6f16cadbbb520f601081d7ee07dbd4515a33bae01147d52de21c752543b275730634f4b8d0ebeeb7bd4a0df4173d33614f0db461f72a0d50bf540da39d4ffa73ed1d7fea0dc57b5630e8af8772e0b20993351354132901594c318f913424e85c756870a2593044c6587782b672996fb128078a709156d541578490d74f4ef38fd7cc75b14d028312f20b7b5701fca6128eacae258867535eab39adff68fb917d8fb971b9bf9141925fe97a7972585c548082838039e38f2d5f4fe1616a71c1a6173353286addf4ddba4550892a5159a03c65874d6db5a0844577d771a4421a7b79384663e4ef057a872c965a08bea549f93e5206d696a82d77f27cf46521ea70a144db92f1fcb96dc4b606e772e25222819ae61d1a05c7c7d7834df42205ad6a3eb40129f89bfc0448fbcb4472693808e93d0999dc433201d66af9dfd0ca3a40a9bdee6a86ab979b2bc435ef72b12a6634ae76a9b7bd9be40298c58096048f999ad0b3b2626df4ec3339c58c5f2d4a8fa6998e7ef2593cb364e2a8f3d29232fa8040c4c62d24dde88e060d0d72b2a41fdb1b3a987cb65e9b080791d26160c70fdb205fd119e20befc0b4249258faccc0a65ea340372fbe90c55fc6be1d40729756b3083be54da5f6690bb4c60e306f3843b53a005d949bfd99150af8178d623ad5eef4eb480c8b77724aa04371defac87ec1506cdc7b4b6dc95a94ec784fed0225b02c38969759eb6284e08972d681f1590637a0d097cae9cf7911f65cc07c8e7d95d64c036a0068687800ee2763f112089ce2cab4537ba3a3e564cb56c94da43f36f2c29ef7423be2a8ee2e72acdb0a28316913013b11e69593c4e28af724b053c750845636b18e4ff302270f286fc8ffc266f607a3bced6ab26c276c060fbf5c968069449d5e3014fb8fc0729b7f23406cb4a451696d7310def130c24972e5b001f45f67a395739410f32472a60023f3451c82bb8d1865a085855bd3bf3b7ee84b5b7cff372c88aa086df3726fa9d1667c10a385f3d9de8bb5b5649ce824a06158616c2235a30b929c97ed72c8876505e6b8b177cd59b22ad21276566885e67d81f51528782839782a5b282e72d910f606675896609b1625365b7fdced747d5abaf185b5d5bae26d3797e072ca0e3c8009d17c8729fdd54978b24161be3cc465771071e88c2d8925ce1022001e38ec86778d5f9294b763c237c30e1e23c721b3ba7e6be3787e417414696a1326918598af3217b342694a687803b886657bc05a24d8bf2c78626561ac81d87209b91a808340bb51add5974146b9e66f264e7dac6e3573ecd1cc998838e16872f8a8d49e324f75e30ecc1a45c5dd3e6e68c1a8e1584e3c23bab918053439ef079d2e536fb41a2e7d49d85046ce5cfec0dc450fa9065e50fa9c9519fa77cb6a727e6486cf1e2b1372fdbd485244bb70395f02a46b233e2515bcaefbd17e7d98402ed958ff937e85f8b9444522c24afe369705ae960236aaccdf43a7bb2e870316dc234e2c507e80377e701402fc1a707c0b415f016f5f7fb170ec114425c611728e5e0e84a8c748ebde8c8106651ac9e0df4cade13870c298bfd679a3d2e91372d4d333a51da13c795874bf825358355c140c2d3135ce13c33245de0959c57b7211cbe113cca686905030e7dbc4474601531e572798b5abcfba82cff531d63372df3f3ec2c2227f5e4edaca7274e1fd7ed0fc8989e6a8db87765f0fb90b2a2972c2b0c08a465048df5a53c4b5dca12632cd2618ae245c60ff669e7256f129b16389520668351997967f486e0b39249c0a019a07979e17e5d9f13e8df3cf0e5f4376dfc8fc428006c5dc92184ed513bbe0101a8858861a160d1817419697a22572037263cc6317463fa810923e70ffc8ef07dd281a59a1b32d269de89395e05e7232141fe83803ff74ebe6fa91ae7191a5ee6239e90fab594726824748e16107722bd4dc6ddf194251d978bac1cc7ccd9fe98847cf4880f599be4f8fa2760fca5d57ac6d245327e82777df3cb77b04a475652cb769d3b282d3a6149753171f6f36b70d426453486d1e4c0ab9f99277c1fcfaa098cfbdee286bf820fd1b1f67a272e98be53e3f2efd6497177397cc3d772f6a195efb899a22fcd5684ee2de87fa72b742dca6cdc3d29c87ab0b1bb60bcec01a04fdd99b353cff877782d78f0f33723a4763bfa2349de21c250d2341fe5ca370bc87d9ee3d489860aec331913b0d727ea97f650ffa9829dbd8455f97724e42b674190716048cdc40aa7cd6c4b7e47251695fa542340bc43e2ccc199bda76da85b29e79303693335932c43e75a77150c3e3a4041cea96e6307609e42ee40307f8993caa7d42c16ad4493c39dfd646726671ff3dbab1cadd0a8b775fc1a51cb17c1043b69ec94bb364bdd5a5ab11ba7230e2783cdf49141a2c3bc333e338d52c88e9929584877a1d023be13189ad5372681fd0a9041cf0b762e32d20ed43f0e80c072adb6aaab8d930ef8139043dda2f14a4bda6432ff9aace71fe8650ca508a207a623aa5602427fbc6e6f80ebdfd72b4b81d1204895fe96d76229d8fc12ec9cf0506c3bc6c8768d0aab83b41eb967279c9f6aada3a9078a5a34ed2416dc450dae61fbc8f2d0f693bdae5817494c072c2f46d3e2204c881886567c3b23d8f3f4dfa64ca7739f8dbeec3e2a2c2b9857135e17223a0a5b89b0ba818f2ffcb77c6bbf6b7858e6fe97144ee0ce438b8604363a17c0fa7b16bbf1aaa8ec3988834514c80f79babade8e897219ad81e7d56724d67838cc085ec9dd81252e6ef77ab63104c5cf29ad82d73f5d12557cbe96d727881eb2b07284967517c81af5114c328026050948ceab96be4b8914cb062ff72c91660038d9ecb2c32aecb41bb1f571bee30a3c7f009229ca3c58cd8729c237282a53a99905b5f5b1199080862e013dfd47a4e0a5f0d92136215d7098934d83130ff5e359c819628176d7ebe357066eb30fa063a4a05aa0c931572da198737724fe7b8a08a3629c72a79e0e8fdaa7a1b48f0b2b27e843095099f7df5499a234eff50cf14e70fde37cd742fc2a10de298569a415aa1dfe50766b579bcbf1ab2409554713ad0b44e10642706445a7331818c348a284c4ea7f61e0ded9d8f7cb572f961dfcdc005c9ec3975d680fa08fb71956f10e16a1dbaa39bfc27c734205403cb42deafb3d3eb0b05976203f860a4d91f7738277d813381bab11628c768e42db254bdd01cc62faad9064c4e39858b25e85494a64a1337a656a2f226d54b33285a7018568ab358a2aac7456145a84d22fce444796672fc1d413829af4c822372274ca02c5a0e1f5a78535222a90f614e4b37401a8c4dcd59f838be040814f9669b0716cada51a854b0e31f74b9062ffe5d5f0201aed981fae60126ad1feaf2729324e07a3c385c39ea36a9f30e7240d36cb29da20e3a46e5f7781d64e058e638a3e8274583961b10680642b354b649e0bb6baee18a10421dcce8d293fafc04727477ac6ef9935b3ee51f94d8125b449a72b0943eefdde0126cb01f556a55930cacc673f11b7440d5ed11ebe30241105ab59ca2161218a210ae3de01cac0d2e72c9d1999bcb9cc86654e7951b04a2a0272733752c83f1c713cf2bc58cd87d6e0422d157adfa16e2df6802f356882bfa255bc98d00fdd6a3ea623f10dd4dd434726f3633debbc1363858d030b5da09e3153305ec91b2c4f608b5ff35fb967da1721e8a072ca54de68c8b945fbbe2bc0ac41c2b764f099bc1bbe87e7ab99d2ee77232f03e955be3062f523ef0afabf80d1215e39ed612c37e506f0734fe285fef44915e51457ddde3ded99de33b162fb0f8c09384c6dd24ea77646f67553545897286de3a3033799433375f69bb6597b870476a8d5e40b5759d94e036fa2284c872d9954ed77dffe606d7d03be6c855ce0b98e3164c704059814fa00c528f529f7274fa7f1188d427f93bc4ae6bc0bce8a31713709cb43cf1f378a4cddc9f8d6f2b5c221f69a9fb67e3cbb69546daf5165544be31b1bc34dfc6f3671aa1da0a8e23afe87b3c16d4037b15eb34ac7c47d4aa733bf55249012619e0d5de198332327294b7d6f06ec9438a6bf290325fdc810385787df7e5a65ab134d23e641c32b54684916aa4f390106778a5dda7f01d9abd91e470d9b5f537d96001e866a38c797267ed70bdcede886ad4062271179cc1b6118fe3464e34ea32a5074d1ddf61052a0fa7161df2e7f46f156be09de36d59afaba84eddccf9384f2d27abf019202c720eb7d905626b6dc589f882f4c748304868eec9741d073c796af29ca016a2dd0f80140ebec5b7d3594c31e3f828b29c939b683c0f60ba9e1d56a6590f1cfe97445d1b394e6ff07af9f8b94717942be8b09de51c53feef611549bd1dc9fdda07725706bd857f5b3bcf1a364489805a9fd255313b5053fd5ae7531acffff3215e72582e6fac1e38aee296cc2e986fea6d5a4f098e2aa2ed5ffcb90c0431fe0a6d72111ddf1702d52b9981899c93777a1d77432282b8f169672c3562d0e3341377729d3cd432c9a2dc506b18e5cc2a716f215e97b0203855ef62ffe4ddf91001bb723da8f4b212ee9161b11cec434cfc27856c2b8ab90d0f2941aaf865fa30eb18729e3c3aed016d1233906eb10faa3f40205306080364bc4a837b8b4a78432ab43236e56a3453592035fe7113e473dfc65e9b60be8c540160f4d85c54dfeb210f517feee6c6dc88eb68cb7d95f7c75ac4f0cb805ab89c0854d96e14d6dec902497297b20e991db01820146e518da6b2a7db2a9c5223275079feb0f2c1fec474eb54e175169b1c982d33a58dacaecd442d6df1fbf0a3b9dbd394c9bc9f7329bc1d692cc8f6135d009fb7b2ac245c72ea395b88ed4ea10ee529a8276b5515065ee272e5e019ab42931bb1b7f210a76cfd8aec84a96837406dcbed54da1f3a73cf5572889d6baad2d99322073733fda621afd4ac3eda57a5df2fcc902b3b8cbbc09672a01a5af6858b45c1c6d139d0d908d4ed5afa696aec0ae17b28e4d93704aec25fde22b8829f014dd7b4ecff7121e29a4aa8fe3e92d6e2553a77404bbd8531a44c9ab093ef0a4d5a71b3c173340f011fbc5ce48c562afc6870a9928ed666a9bf72ef224610f19928903776bc23650bb9b3e8a90994ac900966bb02cb4131ce6e723cc84d55e9a34e0b95d0b2e3a01f844dd4de417dc2bf259c14aad08dc521ed4215e0ed9109bad5b1c2d5c297ac35100b7624797b7ac2ea0f625ec60eb5679f6c3e4e12f7cc89bb0886bba8226203ac1d8d302ef3b08b1070c0f3a201cea9d972b8e97c03d5e4cd52c6d08ec6eaa2572f95eb33a7d9a03e988fbd31516a38e37284b4d6649190304883e73563bfdbf44865edc5393661defe628787c0700b700d8cc9dce2828cbba6d4612982a48147c0c8b09b52b9ee5f60d27379f81cc071727391135fd4671c18549fa2bd419337c3f9b4d22be2a7d4ca5d5951c517198f72c5ec8e26f3e6a4721d28c5d218dd27beae39fa9733f0bf2cd6c72d3b980fa73c4da81a768411f6f376169d7631cca42537362ffae45c58f7df60e9998fa9a772b0e7636abe6b8f89c3556287609e1de736499841bc1164c89922933c138c7f4806b92ffd8b9f6774d989ad2bcac3a3fcd73d0fa97314bbf8b19968d6872c9e41ecaa433d0d15b7d63632503f0babc4582d2fa1b5f44ca7038296f5f122967111d7e46b826590d02c4644c550043a462d82cd41554d63f5bb85d3127227d6f61b7fcd86232b42e1708cb703499b2eed58f80bd71df618d075f12f96162a720c72849109dac02f456c6d9aeed1e085aa273220a5bb03a14b3e69258a7a33879772db2153c6cb39c948549c7e8f38969771ff17b1cf218dd739b868fbfb5f1073215d392dcd702d0cd614edb8e18224ef9138435aff237aa2809ea58d1617ae0372210eaad968f33ad2cc32398cbe8949abb3512ba7c19c2273e9c0710a2c098101875766cc7149aba22bfb805a7024e7f803e9a28f6556e0d42b1252c99f609f72afbf060e5dd3d485d3e15af6ab6c1510964aeb2999ab0135b68b8816579eb638b19f40f8c699887f96816929bbb7073098aced78f1b69fd9db16a115b4d07672e263a163eaceb96404a27fc1ddc82e33ca19d9b4f78079c9df9f4ed6c56fa372e7f282f870cafde8759e48893ca5899f76de9f3f09f00a4c86de02037da63c727c107c296604e9a8c1dc557eed7bc9ae1a2f516f440ff2bbbbff865ed5d1ed729db1409285d91db0d7ceac6b170334164a5c18ea82a95153a4f0671b0ba39f083135c9c64d21360dba63cff7949406fca71438555839328830beb699a3fd96721ac3bddc6ff1450105fcb5723244ca73cb1c99878bdd6c7416243bfb214d4834802f49371e2028561082af51b1748b6058e510a7bedf5cf820f0c9576f44e916d2706d64c738a12e60ad5951385c36ab1c34c572fcfc8ff5a639576953cc370983b40c6a777ff408f4fc410038c39ce256f7b3e2d00db8240e2d6966e9908272b8e57b8cdd236841e6f10b94e80205aef76a5cf94758fe6d61ab4dc92ec46a309ed7f9e98257dd31bd947ee5bf5db734d15c52d2b67128b2a527a6aaddf1c10ce921782a979ed1ed5f16ac82261d5fcd18d5ea1617cb48ac4a3cf0bd408edf72897c6463087807c95b83f1ceedf55de85a63fe57ec054e3ed53a0e15d591a87266a505de8230a4a25f70475c4375d37fa1dd02128422c0701058466b1ea587028726863f55e97fd471f816ed194645dc863d46e5def79821991863a89962ff72750b2e51d92d5877ce680364cc1a184f64314635c0a46818f4e0f2ead0a8d472bafe9eb47be6e4f8ecfb99434004e542fa60d3d7c5af2ffbeea1abb32d4f5e728a7bb16f2fdd3e25689f944d7ec4180c7e0770bbc6b7357fcf3bbcca8ae23872ca946dfb2db143deb9841d45aa2ce89bbb577101571f88d1cc4a8b7bc0fc8034e7a470deef5832c0874fefc3a09433fb6a0aa02b7936f328130bee4ae9a4c3727178c2c8a2c1bc5ff3f550d4d825299d83c728c918f017a8f1b36815ef2dd47286412d8df90b8888f6fe00c7c56dff069bbc9d4d9cf5b5d1a7bf58d2107c4d153e6fe0eaa0e3a18ddbe355250bc00414e3858dc6606e1053b7b830a5b122fd63f91caac74624a73698c6736cce5bb8d9b6f01d30b9eff5502ad4ddb72504c15bc9abde2727df7d4690c1ec53a45673b166a40eac999ad6d3ca8282e507ce1f727c63dc5e861ca7a59348ac9c293ad095f8c3a6b39349849f244be0aa590b6958a0b5f60871f1fdcde2de6ac1edd4a8cf615cc5aa42996214e5bdd8909452b837224f6ac2e506f18a155e7e581ac30f1079d66a0025ae807a62fd5f1637a2895048db5792c9e58a8e32279b6cb8e212edc82f920b7d1bb16e96ba08cdef21d97264c496399d9383fdd345e4b5b8ca563ca412c3179733db1dd19ad8c2cf78f572ec55cb3a76577ed01916eba937e9a4724cd5f265c606deae1d5565f832718958efcf3edb62ed9d06d68572539016ef90fce855e3f68bd398b9607cb982436572413596be1f3ff566392b2c8b90cb295919b3f71ee39bc37b5d81534d2b9a0b72ee97b263935ae4980cbbcfbc10a5ca4221e397b5582e263be428b421ff245272edb455def19e0300aa38ed19fd98432a0f7e32aecd9f14e15d30373d94dfd9724c871928f28ad7b5c2a7669caa770abbe69549fdb4cb963864326ba19e6c6a2b38dc9d53f3bcfeb755300c92871e35daebcfa599b94ca1b9b090671e93f2343ea0883366bbde31c27119c81ec1e8ad9661487f2f4310eeb01f69c25168fbab721084644d1a48efad5656fa85c510866ae6bf50430878ca801f86cb4e18ffc6720bfa7fc48f7f06830c40707fb5dff121d08ad996ade78b34430ca6dbbeaef21ff07251ff9febdc40f4a218b829a8adae1343018fe2a4c63c9c99dca5a3209f11f52655c84ccc12bb7a0d7077f0a03bd7c6db0ceae762b1c505a31af5e50b21722d66476a9966064dd91c31f1f9b40ad84105291971b14472a8321c05fea59372f5fce31ed8a74da50ee3adafe43894420cc41186c0c6773bcdf2bd1d060d2572398b71e4039a0d64fc951549a53d84412a535e3355e5b880589502d557bb5472cb214907eeaebac1607964efd76e17426631d9048acce06ab9fe2fa862b9c128c3714f3a14e4956686b6ffc0c0b2f2a6e2489806b146115181a81e1e1fd266183a195c8d7f1d44409d380c2fcb823aa2b9593edaa4b8f3750b1c1dd54c44f072ba81e2cca693a5466390e4f86875c8a0a8b5987fc9b16351ae94a03a56df6172cdc96df64ed64a976d2a9408f5e6803298c96da13e845dfa768cd873738d1172fcad410da24726e3d19d2a591edd1d4e9775e29d167d09ef9a23a7484edfe80cf4c47a3e4dd94931efabc0fddeca952eb88c5f42afacada0c93873d24729ce04c1c7d79ad2642e47edd3750ab54d735543768caa9331e8e034691f867d331172685ec2ea6a7502e41d406873787b1a842477509422fb2ab07204233d93f5d172830212737da1ad34d4ad187468d532c87a1bceb37a19029945385619e6cd3b7258dbb131aedac9ce240a00bb49f6d74b8a4e48f7295fa23ae5904c9e4ef1ee72504d7bacbae31929c80939ff6693d5b80812cfd4bba5c2fdf9eb6950a86ae47213d883231fee28d12b99645fe956e91f7e555090c388da7b5e974bde51c7351c13e64eb6a4f84d100d676c9301dfead45f0d041a036f25cd05dc0b191299a8722d0120f9f27e3ac50cb61c8ec99f0ec8f93b468ac3d6cd022ca29cdb8b285272c342f884ca71bf60412772db8af0906bb98b40b2ab3536ed8616a99e4c0f3d43c0039953231bc80437c446a505677f2a001377c55e11faba4a884f15a00f2a5a3dff0b631aacfe105a5af2c363bc1ba5cdae56de54e2cf1c474dc8e68f397172196000c7b1e191dbb819244468fb340a981c2de5ccc2bac19c23399661eb900d44f438b3a406776e04aa517d2ae4e3266f1413785e5644aef9bd4a15e8261628d91b3e87ec498ec0f1219da790742209f5def18abd40036714c3ee9c5dee0d2aac3671509c1f5240254b8d93549b8dfd941c0b48ab007217113083a6039e87309ef9f1fd57010083046ea577cac93307e40d7811eb7cc51a3b7fa69c4c47f934f78ee1b5feff1922db48e7bd2081ce3c84ad2c72623f187d145c4dc326498772f1daf7294913d9b163fa4206657e6db57bd6cd107082359fd551cf9a67b0715f725d612cc189a388626547a1c40eca25d297c9d3947ce28d1df5afe35fde34727ffb1b4c6cd0872bf94d56b988a4632d4495be9f67144c2d9bac26cfaee5a7296f9aa8b76ae8f3f5006fbac9f1f341131260fb128d20fa460b0bd53d78fe267208235871ffb40310df62291f3c69c12a6c6f54d2d3fcd7a798e46cf8f68f703fa518ec12ae3b168df66e7552c8832d3b4aefd9da38b16519982f9a657ff790379353f166b3a595c6cf78fa6b78663c0b5e511c7d939b53dc616a50bf99eab172b2093c9ba8dac2312213dddfd4698a4d72e25468bd719f4a63c0e4e2b47ee5213968830f9470603e73bc5e207b233f4e1b7f48a5eb69307afc46b9de90e2ca4804c5fae5c63315d3acebd870350add74564fa73b4d84203d7e8dda11e7840d725321dbb917c1db4af2745746e37c33cecdb30794a144589cdc23cef742bd78727e2c46f66b880692ae6f13d68f63fa298bf12a3a03a2ac4811a2e27fff2c3233d62badf620c9ee9ff66d83cc2c233e1a3763ea79f4a641f6700b79cb57113e48c731aba22e23348a689eaf70bbe1f182a9c7ebab57e49b152f1560208045e217dd4882f48ab158d8346bc1ee858bae03c90a40313f78d0b4aef69a2df4442d493d3091b67886dfb1667da455778a7570bd60d02d1b633a5c7579f93a81ae317240d620c7de689b13900c24400b6cfccd1eea51be42cc58c500b7a2acd9653c13299fbc63582afddd674d3e85238d804de86d49d533d870b079bcdec73bb80e729fbdefd290177ec3f33e1b76cfb061ba1124772167118aeecc6d49182e6de818fcd7e86beeb4ca3a58ee712d576e065e5de54e330bb5bd15f03930b2166996721d3bfeaf43f8c41dcdd7f1250cbef598b4603cefd93a4c2e1c16f0fe9b366252a262ef9113fa401f812137da8f6c338fcbe837a27c86378311c82655bbecac3157a53abc7a5193f087ff70af61352a16b56b8082d8e1d50224238b9efe6594725528d0746d8da7f726b4700417e506dd6fd2647e3698a2dd793ca5534672f2321c7d58c2d03a38b59d340526c5ee7b545081c6f63bf02f097b0a20275d483751299519d4b2473e47a5021785eccc3393bdd25a5b72eb915820a7cc010503b572c5316118ca1645af7eb917df09d1602c13b8775a98c70a62949d28f5d9b576724cc4944063eb267a0e30afcedbacdf3b46482f115596ebb0d17193b59b5d6469eac2c76b8068a4eb4c7c72ffe8e13c86ece14bdccbcff3c8682123f9e7f5d872e9bf03afae53fed785f532a9d01ab534944720f5a584870ad1383b387d0bf4097df3dffb0e918517e4c2a7d0536a9aa900a72227947e5a6d50dafb3319e8f13733bbdd8689bb6ff3c2d33606772f320a350bd53cc62e78643e05a80e70440a299c910604fb3b40617fa013da19d7bde209ca4a9ef26210df8c5803253eedfb698067128415a6261f99cc73a2d9afd151939bb041b7325ffc3fe0062f455447508417fdaa78645f0122c1bb52c65ad614eb692e0ea874599404c1269f1ea55672f2973400a85b88631d9a8d39fb0dd2eec676ce3009541c94031a5b1afcb5ff70cc42101bde4f6171ea1c69bb0aad6a86f4dd967cda86b50fb8c21d2bf242247201047a9c52cdf296fcb98d41f457baa2854c6bcd6d49bb8a9e162387d8ff9f723fbb60e62e6308b0a499160456de9dc57959cdb439ee27944b563477abb3cb72d23dcc2a7de89b8f1bea12c9341400427b7cc5ef853386e6ce419e4a6c24c4621d8d2c05da80d3f1dcfb6a5675ff56703656f21539d4ed9cb4ba58c0c6b6b5726f2102e963e93db906f89530cc500d03697d093856c39c0d8cdc8aad6f7881728335c500059a49e4316ed06dcebe9f4e219904b0d7ade0d6959e04121683cb4a6b3301c529f55cbabbc96afae5163c95626e001dfabda850a27951143e499f725d49a9ea85fe86fd4935e9191c7de07e0cd1e0f8985b8f4dd2ef52ca603c79371cbe7422a4e8d97a7cdea438889d8dd83eaed4e1df7c3e60a35d4cb8af5077409797b6135431056447e17ee0924613a22a9cd01bea3a66662cfebc35fac85d726d0095d014477e2f01d35d15dca10061e204722103185895107c7818e381c572c41b7cf2cd2592f7edb29b6618a4416dacff93103465d3e21b966a8ed8e4eb677edeb7e3bf16bdab159f57cfff66db49f6a4519f75dc98476b5227c9c088c07275872c1103de860b71e2cd968a6aed9b6fa478f0c5dcb322e38d18ee75f44a729517ca6eb4400d610a1b817a492a33b45c2bdd074b9181425e2ef26171f25b72d1e61b959056bc53f04840b68876890ed72572131fcbb5b596ad5c7cc29ce70e959f2b0b69f478dcda42accc91c0aea656679dfee6d1ad1d6e2b0fd6ce07e772360f749f63f7319560f105c33e5bc11cbce49263729d27374045c19f77ad9c72846b5f0046e10abcd561bc8430e4f4010b6088804cac55448fc9b7e5defe551bc2e6c5efa3e172c881fb629fcc9ad2f906f7fa6c265ac7133ea80d621849bd5cf802564c2b058457a7149f6e76ca84b3e7f8f68ec3330ff7a1f07923912c2e721edfe17c229e05e0ce20f4cec11f7bb722aff43d5f7ff72ed271f657804b1572f7036228b9071f79dcf9cd2ae7e41f3a9c14fb1bfc6c3f8170a1ca9eb4ed5e72a635e10b2a00543454ec9c8643c9692bb8d195a69d1028304441236332584272caf3c8ba97efdf28caf3e945ae5a81d677ae9c2b7ff9935de6fbd022f8de1b1c3ef8af33dd5bbee2e9870b78012c154d18ceebf7c63f18b04177010b4c19cf1c4aff5079d9a8918f5f2639aa7d8a583e8b4afa67ad1101a1b3db43006a4e2f72310063f75648bb42deb32c0fad55e10b860e01776ef1a0f6330af8d75fe92a0092a15a676b31f49aa6a8407135f27d5623baf6dacfdbdbca839eb019a1453472b7124608c854ec7ab2c7189138a817a2a8d1807048531967ac34209200fc5572589a5ef6cc1f9a3f05217b42dd5c814967dada80e3f210d1c1a42eae38b99c726866e7b8b42cd9442fc9f3f450e3c23179ed7ad9a5fc553118d99e2c63672c7244fc4e9617f7136650f2bcf81af7a09b52cd946f75738b85251cc258137c31729ec06b5a15aea609cca2e14b4b438ea457496050431176f4e1d225f967d6e372a6d8cca6a5177fda5330cd549ff2997c2262a74f4708355d4426fef500bc5e728233c76dc51d2d81f7d06fd8c5730df17bebe51b50837d6a6966609b7a6d50724a0326a998fc113a51f3a948a8a128187024983dea8836e82f4884fec4b3aa729cd167437015ba44401fa7b3c72eea9097e85ad1e9b161f52cfbfba8c04b0e72ff790064a721472a9790e401b2459d6edddc02d17626c69d08a675adf681db72dd2bde4ec0d67acd9496ea73f05d082ee80553c24a9a547a5aecdd3207e5ae04f4cf670d4fffadde2c5e02f9a8bcfe1e40191167c7836f0261a781eb7e605372592cb67c014095957590c4dfbe465aa86bd88debbda8a2e521146f2b5163e4721cb446391de1ce6228c77bd1767ed1089a056d44f16bb59c37ca947caf61ab012affe82f21dae94317c709b32b51a4f4a0ea508f876be0b8bfc63561cf2bf82dd1ae0eedf1c490cccfe0eee579b3556e4e3490d8130d048e9dc49157ca886c721cadff669cb6d6142f92d9f64b599dbdf290409fcf61185e7cbff352f018f9729648c3c990c188892e1a1ec2c91ef929e55a560db9878023e361b1478ef5664c51fa0409267ad41f191e4e8f320741daf2ff29d922cd059f95e8fa63f5835672f3900103cb8c73ee4222740cb34bb0c1495c9798df120ba4d3102d704a43e74f2be18b2c3cb8194bf68d365ddf5a33f8e2af78ce209e28be2918029bbcf77f2220ff85ec906ede4bba6d17f5a022cbaf8ccb6d1ee2a3ac901d1d87756b615e72f292eb2988e7df0a7660dd4dfecd3e3a8fce6e8ee1a8314d0d2d625fe398267242bcd5cf9209c9741b2ce4910e0f4767f1af0b0e6c393d42b2c09bb26b525a20c9a1c909ceee92a52f12a736832f53238c2030b3bdec1735262df41a051298729b7545fe77342f88c551fb96ed01761606f572f6e2e7149abcf3da2be24fa86e684a449f7a313df449f55ee386cca5b28effda284852d50270c0b8642e0e3a728fbd2b4d6faa915d1af1aaed833bda209f4d8a3f6cba055e7bffc93d8aa0d2724acaa9c333182a20db488978793089cbd953e9ca18429f84f25bd3275f0a50724e44f9e5f6e78fc469ea1154eb946d2f5169d8056993dd00bd75a5ad2c3ebc72561570e3e189561acc7460633316314a5aa1c9080bf5f362769cc9569366cf727b4758b0ee12d02565336d690d760899a54183f46186e6967cf02c96331b31725a36132cb793bb7334642b3dcdd10f71305b99051cef4a4a5bb596bb91ba0c4c68ac7f7a9a624f32bc1fc936e08a7fcf13a93ca3ccf0f8140ab734555e63e472d1a30efd213affa36b97af25120dcc878d34358e8abfd09f9d0fe11a785d2172dfa769e149f308bfc553bffcc5ee1205f9d31385330f7bd48343ba7263917b6d799489d050211c0a5f1d8092b54cddf9df5832b6315f996d7220ee378eaa5e72cb27aebb40a379bb5af565588a7994c332261b24ff57cebaaa3bbd1a4effd047b2506fb6b28bf6bb94c319328da5b0b4c017e6ab5a94d2bdb481d57de9d6da61f72319778a9e4b4961a58728ddedfe050cf4f46df2445f05d1e94db8bbb91c620dfc5f43ff7500bdbc994ad59d3bcfc2193e07077d9593f2a54d69deb912bd67f75b28c14bcc4ea072fbbf52d49cd4c63a6c04300a3a07ff2c0633a9c34e3e28f4d22ee55a2ed35304ee472c326e11b13f78fc2f9cd8ed3182aaa3f4ddc7ac728aa556f8207798ccf0d56aa043f7b48791417d3196af142223cad14fc536284c1e7588e7fcc29e5f8b10e06f7a612ba121420f9f18351bb40c8bb3b35729b15b617f8e5289d4233f7b34747e758ad1e2573fecff8b51e9e74e6e4809560b8672064b4b5daa8dacf6ff8cea8a393566185772e1531e98b0684e0fbfef5352a949048cc2dc287f34c2cf7db136ea50c24cee6dfbbb97de15d447e643da83505272878d2b63c3c945bb5855e7c55fa80afc49112f854345a5e11e25ea2ff785393a794d5524bf8d7de623a3951bbf55a06f23568b83e07fc39ec6d307d2e7cbce723c57afeac474040654f10ee080f6fe158416cb28ff0254a2a46c428d119b1b72f70aa109b737c41291ef5e7f81bd5329a5e437632e44dae9480155c42ddbcb7234bd4860adf7d97c50285d52abbf4092bee989941c9f0193f3d25344c57c7d3b9e007394ccf680839ad88dcc2027604c95240c6a77dbe8e58f50f6b5d8e8bf720808880041c7296d7d1e10425799fb8efb4761ba59b9589d671ad104c639db1bb633700233abd07050ca3afced1cc023acbad5d4ae32211ad4fa94080ddc0a2d6238bce4ddb60b634248bd323f8c92dbeeb892ea09d39e474d3d888c6d978d4f6efc2a92456bc56839b4e1291de1f309d3e7479980910fb6ed165bb010bcb96d936afaeada7278b041979730026863dc69aa5f436eaa1144fc5de1436b97ad72024a1bd8338315c1518febd251364719a21ef1d486b88160f2bdefd4da191f7294f282c828e8b0e210e84646bf5361460290353fb1b30bd4aade8e28865bc56b5c414763ce06a90ea9b701fee381ae479baaeb7c1bbd9f80079e27c4c335c27255596a11f41a2e41096d5490287c13eef8e8e130e592373c655bccd6bfb0700ed4aefea27e8e9fefc488590f9aab572ff1c3257771a655608c987fae34d23c6119de1294d89a862b94f7eb2d2c52b02bbfb15c46691fce8d94498014bcab416ee34fa33d56f038c62f4860374c127a2c8b70888f3e41e475dbcab19d602e6603d0eb2afe341455f9698ba9033e2af97ce165d82203d6ab4ba6914babbd270b724aad35e2381b4814abaabc7435d8b55af034f464173a119a667e37e9bc574b0710b1f139a3f140ff4a89184441c97067535f53e78bec783a2a5f4603e3ac3d534375147d42444411d779549c659549948c7fc5305e18ebcddd955f118a1caa7250f24cf5a0a35990ecdeca7a050ebc36a25f93dabfa41075860a150971e7117206735a13aff47deebd11c7bf9f213c1bf295c3fe73411dbc7ae0aa8c26548d4a97e8f932dc1ca5d85c6da035a42ba24e2ee67174d4ef157edc90f144dd2a6672a14ec51f6193159deec37d767fe2217b52036c23c7928ca235ef7a236464566596df40f7b4c8c4ab11be1b2141a9dcbbe5ee21cfd1abbe86a96ac12fdf6937729b095b04c69958ad47575bf7ec435e8996d6d69100343bc259b054d996219172d85c3db81322ad9ff2082ce9b668932d2a906dfff2f3ca131500fd479265e33e8ccc0884932ee77b5f0431830220820bc4a8c37846198872c19828205aa5e27222e312d105ec8df123a273ae82ccd0459d1f2e1abd34fcbfbacf8a2253fe1e70d66189ea155b722c4add8a857dc9dc1db7975ea05f2df493e5720e0daae45172089b20c3fd9fd65e98b4fdd457737d8950499b3b83e5a09f1abf154320aadc7251cdd835063d0d0eafb3b6f3f5fb00d2547c95f81f3cc2584c06630d8e43a23043792e0b39bf4f34f6d2fe62ec039388459cc80556a4fa92a55f33dd223be972b89ec9de73cd0160dd018c01cc508be5a2f08db3e2f78afdad9134d729e21272ddbcbe2cd87dba6a7ebb3be8f0b18968bdc115cc9d1589c6e86d9266141afc7291041f45261d0522f0c37edc40db6b3f7fbea4d4d6b6e58b583ed126cf26b272a14c9a29b09efd36b906b9a70ecdf746cb94e447cbf83fc2440ba120fb67350073b5a10bbd98d909ee1697f9490c0ffe15b59fb3891246495474be67106cc972a72d7e5327416ed77d8a380a80cdca4bf75619eeab4299c8bf675ad3d90d5c1e3b3450df4ca3fe8adf7b4d1ab492a5a9ff523a83e35ffd891f69e8a06e5a3e726aabfb32cf009d07aff173c42edb1816598bb4d1bec17ce930b1ce907c136a72578eaf65a58c75f67c84c438be3d0813408df5fe004fd2c5d1621a375aa7d572000d732caa1b8e2e6a586121d878bf3f8e48764c0cd00007fcd20383eabc773d6e3661c40dce16dbd230ef6e3889402c97493182717a328625ac7600530eb22300391328ff862d2a5893e52ba5554c9caa43c8b301c1bd87bc405bf3a9466e077044e17feb9878548dca219fd5dcb357d067a9aadbe5cd8953ef54aa30d3be7215dd0c082ebf0373481eac234a4a425b79ccc164d4719ef6996861d225404572c4a62b7de08d88e118ea74c5fdab77fc1daa35216c83b32d54351adac0edb86b83a39f70db6decf8585647325a154101cd128e7adc520060f4f2cad53b099b3b27dd621660867898039f9396c5d33255990f26f67a45e59f8d2c7a150881d172e21141422ac8f654359d94c0be03bdd998746adcbc942e3b1e2e4552820119726226ce16693e97aad8b3e034f77fa03cf30f4309a0e5ac55b2e5c0dc662ba740e449b075b2f4909bb31fb7796d0deba94421950ade10b3cbf54cece2a9ff8b23d6a106b26a7e25ed40daeb4c7069e549bfc04807490413924bdf26b3597c3872934b5fab3ef180ea5f04e0497c9d0cfe5ca0265bb5aa1fd74d609a86597ecd571d946f971912ef5db34f7fc6ec663d34d88b9f461a964e0f98def18de89c39348017e9ca6bd1143ff26e5f3ddbf1e583e02b6e8417d0610b692f09a892769872746e62ce79e0de36fa268eb401908c473715c22c3bf74e190340cfb582d00461f9830bea6b1680c33d6e8d1bc607d2f999ce071f40153ea5054f6bdf86519372855a5616b19ae1062e2bfb9728ff8809c2dd06f4b29d4c774e5060b90d1c3f7251ed5b290ab3079c8bf2ceb50560a1ef5909fed2cd8adf40a8a178a1c514c043375a0f62840ba7269020ee64c706606b0cfbcf65c9cf3cd6928c552393f2a06fd6da10b4a5563d853daf16a843875ad07ee587b6323de44b9824b1cce2c13d1377afdf2c3c22086f703aafa028d00f00c408c9bbc2dd7c51d02191643f46da121267df7297cc71f4bdb39b5d1404c4c084a93f9589178f5d28085038c9d71272de85f3a2e2fb1a8075f6a4b614d1d9b39a715d75cc44264dd6d0b4cb31bd2c724eed516db8b7cbfa4d2a2d2640c44924cf6e33bba080d72c02ab5a1f137045303f6dff79c8426150f63f04254a2c7cb4ea5eccc5a0db520f35bfa52ac2175820203d1f0ec5f1da434acfdb450f389a8b24aa74c78283ac98bb118e4f3c8f2038231b3602f7b52400065a8e22d6a298aabc09798cd6f6d62d746bb7075a7859222dbba74ab5d3030be0ea6222e91dc837acbfa51c7d2d8393650634cb67e34372a09142d0aaa9a53dd6caef34f626e8ad13bf9d8fa97b84668bb988ebc09f950675cf4b8faad4a5bfac1059862f593b5901a1829b44d462cf59e834dbe51bfd69c64f7ebf6feb238f2e6e1df1c23c1792166875878ed758fabcd057bace2e9272b576067253894540cb9fa1690b177a3680cdc4de2f5bfccc1bfc80da963da672ed7e5a80413a1fd86528e35671be2038b74c74f5732e4bfc0a376e18bd7377728a2a337ba751740e9463bd5c8a91e5c2eaa15fa9f5e0f4b7d235fb9193e046514e9edf61fda19fbeadbfa4b057c1da8751c9f4847ec4b84278a44433acb0f77272e1b14fb721220903c73d74f1ab946f379a7975197d2fd364180107d5de37727706c39fc75c309fa5fda77464704b086952e580e89602c750de275b2068784bb91d8724606296fb1102cecc36a56ffe8cd75ba51a558942bcee46385e101c05b5344b4addcb3eeedaeaf945fdfdf238fc299d92e1185b4dca28a637945dfd204662abb2973e17dcae614a9883e3493ef5f6583b3f860e10562a3e2e23455e01ef6b0d360ecb090ad6f1c1b64859cd800367780146234410d0288ec836d13872d47e6962cd91185501aa1172132dccdd3cc014c0fdfd447bd102ef22c813747234d877e06717485ab82d56927e90feba347ac803850d54d4c7039f25b60bf7553355c3e3c511398a17f170f84d75b9585f733ef661dda0651a4d8f7e914e5247e979cf1e074f7b0779eb120f5ccfd3611589b5b9e1eb2b125729ff00753e2872e8422f61a1fd1d64a57dbe9b9beee311dfdef5d282c133e490810e3b78e6e42e4f9fd510abbe94050a8dc667c81729fbe8c363468386b81b67343698e8bc9a723d438c6b2558e78f09f5abb3109fc56630fd0a2c76d6f656627bb2a7ebfa3572ba0b1a5cbcdbed774ff2aa84170b0a9ad6d53e4aa15c9513c84aebc9968fb572edd5ef02924f21e82c5b32d98ab0f356cd54692d52b5d23875cb4219f3d2e072f01cc17a73dcc1ec79cca3a22058d9e857e5f0ec984cdd9f94b7c07801c152729847c4e7bba868d7f98e0dde6153d6c5386a1abbea9e62f294a273f0f58e99725acf6493e356764107983e98db7742e655a5493f5f5eafe205fde2b8b2c8c872049f09073f54e3b9bf6fbc1dc3d98b7e6a5dd43b0443f74a5c7f67fef1a44c50b91bc242a4c168a8a0b92e99923beedca6f71fed79305543091f8cba72b9aa5ca440508b0f2226c3e230dc8492b4978a0cbcf214165d655f8deeac09e5cfeb311fb207887e13346ec939b396ab690f382faaabbdc695e0df169e485ce81ad4726547f53766df9a8116b27145d3dcd4c50765dc230768c40446fa75a5f419b72ce9125aa8d2e7bf41d49c6a29e83f8b691f993fe3610c4d25f78defb7aaef1f723821a70c0cdad961eff628a84d555875fbce7a26bd617d838d0144173c1f9c72020aaba050013f25b270904ae3116914149394d26835c2da069498dc1fc0e82d184bc9cf57ef83ddf89caff20e56112e01e194d1136a67f68bbd48d380c67e7260f609a86158427e0313dd8d76997856a302593222b305c5d99cb40dde5eed7245da732334ae2b262d7214cab55bee13e8c0f4679b581502780d4f41d7f98572178c59cee4eda791b0407eedfd61316a6ba2b1b7eabd3cb90e9dd469943e2f50a7e35282f521478b9d6bd08824cd4786087c8db5640da73571d11ced3d509f7274f823d713407c1d165deddb08579c9a3fc2512ebf76443ef9d798929fbae472260cf6c3f13b84cca407dbcd3df8b75e5cde8add93978c6cfa5719bafb11a07277110881e493948b88f82419207c8d4643ff7714a4f8fec3ee3c7f1f312a017248cb61c2f452a3177cb10ca55fbf436cb6484da89d56524a7d1bb16d81e6f2729d96db30e7de74be336782917ebda0ed33a1550f16dce1416f27256e30cf387272ac82c2b8d4d511932f5c5c0585c2cf705e79d699450b949085bbf40197df7256b93217be1865880f2893cefb739bf952cc915d325becbefe8db03e9fc8da2c3b0b56ea0a7b824c4cca5076131d326ea58613e1fa330c9b528a20ffc5de6d057dc5bdc2a27506f43da1233033540d1041b5ecdf41958163705091d94bb7cb0136e175a9226e2aa4b05e1e68b6a17b3484c9748229dd3c78f08f131ae8fc317261bad9edddad8b07de86a07ac6593864662c3c292863b5e2ba631f3d9cab874536602546a10f8d918ca3572005950e3a338abf906e65c07ad793dccc98ab38724bd86c28642ade98edc0c436bd6462a880352d112b9b1d4b5601ce7b8b43327230c92bdeb20a12a479d9b5ab21d34022ff621607c208b5106da579689f7ce55882f982b551e6bd8b03b44207c3dc1fffa4fdabf7c6610a93c85438e99e2b0472227e0b6629318412dc3bba2fff884e626460bb2d0416305499d6ff53f88bed725e818895d10cc33eba8b654f19d18a6fe9b52c034387a1581d10ca8778f46272ed0e6235f36ab6f97ddc2f9db910a95402e2fbc0ca3d762df0c65e1c7b808b72452d8d3dbb260816cf4ae76fc18eee908c835973fc22cf1560ba1d05c8ff22726bdd5023b4d46eddaad6ea7113f7659eb5780eb0a6e212283862a0081bb51572c9216c48f91b471ab78311a7097627e826754eebeb07d7394d38bf3efa9ed4726c41969e4721b6f76150bb96b0de83428897ed784d9dee56a00be2ba5ccd2172dd8c7679addb96888291ac9425885ea79933a7ef2d40dbf0cbadd0fe6c734a22f821dcbe033a3ceb4c1f21d0406b5a4ce25ffb447aa704f7a369afe59b30e872708c2a92080da2f14fb9e31a6599364a939921983c0bd1c8e29240a1cb48ed72ea47341f4bcebd2e165e89d3f27e349d6d5868da4b0125596f454573de775c7255c858f43e39ab8a09ec6672dcd4d4250db2f639de78bf46079b65df4f66bd49c5839d720b3f3771384d31ab5389e53b045f56426389da7b08dfe95093204e4ad09757341ee385dfc456feb05211e7513ec2acc407b2aa42d0b9d6dc0b915f622e8547e1ec16188c5479398f61a78773ebcd1dc991ae14eae8608ac56767e0287b1f10db59477ac607ce04712dd3ac0b8dae639125c7e3ecd639329ee85c32726c7a6eacb0cdd748ea1ba7e7275ae5b11f3fa799e747c93ccc492c8a13909472a200e7fd2b184dba9c154ebb25330448aa4bd968896ba9887510d890bf869318e03d6813caacb4a13158d1c4e3dd2c3a2125244739fa5927937a249bebea037296131fa148ccedcc81664d76cbfc886e982570d224c3a4ccd17b40b658ce4972a6f291e485fc3ab55d25d82616b8c241c647b5d42b4209b8f9cfb53696f0255c6fb076198889f84c4ee5c12758c5d559f442b7249316dd8111e77b17ff846e4c24fc6fb49184b872dfef6cf969ea656a2aadf7dcf1a5163f290e5fda69f44e72ec9bbfacd73c4844ac594d3538c67eb3bed5d49f5fe3cfef773971e777eac936e100bb67b1be01e12d8f06b076ae1d6af310b0ec4f9effd9ca6593d60a0a192a39a7f102a6495ff26a6ad08228799e991de1d180c79951dd821d91135c23e37103b5feb945ab82d4b162d850629be294d54be41ad755fb5988d747dd09126e72707d65c56570c66ac676e2d6cd8b618ef1b686c2d85e70b0ff04f542eac3f072d66667f19a637b3285bcfb5f2f50387e9dbe0313c65905a363fd6ecd67b9d331c517bc40598c49bb0f0ab156b636613f7690f5897d2df864fbe14b3ee9fffe72aea6c7ca6553872214aa6d5f34b5248cc850428c26b4e2ad0a299ca55c7ee372cf0fcca6de31f344b627cd0628c07c53734c17539dacc63820507bd9073fa67299b21803a43d311219f5d49c9f14a49658596bb27537fb50e9dc6c0ff2ce5357a3e106807fdef3eacadad6ee2a56b4ecd85244f7a542dc947f7f5c72d5c288724e9af94fad517198db2b0b6be48d6eb5cd4cf0b810056641fd65c2a5157f030062252a1988e16df98c62dfa12ad14ffc2985571fd258371b63e51e92966183363622cb8f364d478c78f83226410f5c76261097cc534193d3df17716aad31ad7200a7121ce56b2d4eede83d5067ce393ac7e2fb4ec443be4cabbebde37c262f722c6c7fba2d852e5f412d8338342a009e095a9097a92a1f0e35e17ff3aff83172eb9f37c687e0204936c36098f946b93a6bf59fe8a2119fb4bd5c0574a1120672e7b4616c00569c54afe1251e718262c59ac7621089d6450ba304180d50ee216171331e07977f31b99a9a5a2f36fe557505d317b729367e526284da7e72c7a53f3ba66ce8c128a97f20cabfa77e4e41b776ac015f475435d251a3ab7e5eb57f1d82d7161741d0eda6faf43d6e38786dd475b023a88d856612c3be28940cd3c9082b42a3ec7b9181894d27f62f42e83aa7cde016ca64d1e57495015c3ec212990dd63f029ce2b05dc58a8bfc32bac658c3f1281352b08fa6435014d5ac9e65ed531af4502993f1b988a53107af88aa562f293fc3f3e7061069f040918450af097196148e87f8018207d90c7bb1b59d2570e523f495d894c4b359a86537cd950272e9b4c42a80049add92c7124e5f5f2b3f8e24ce592679138bd071baaed8cc9c7216259788040275763f369018fc3a151fad7e74681317f3c2889c83deea918072282c2c35c19ac6e155a3fe18468f0c3bacc6c35d0659fdc3a85a235515cc327299e2529a76c8768e4752bc8bd195cc249e26ad2fe3a8d57726f6667242ad360331cbafe0731b58fb057ec364e590e616aeb95a022fd8ebe1d5bd1edbc45c8872f8672606ce3828f2ac6b3fe2ecff036d1fb207216706ae20b95b5f4edf818872ca575348704bd48edc8736330d99b01a7a2374592886c60ce1d188a87e4b3d721e71acc876ec5819c2da72d29016db92ed16e7b6e26326cda3c728aa409ba318812f04e56b2376b751cab4f0b6bffcb4d8008d63ed99539d61de3be135638f72baf072f67c968924b486fcff63721cbe0ac3736ad907d99bbb740aae25db1c72d0189c50d4369cd07bbeda71e3b5c3676cbc89a84d7fc50a7412a084251c172b8e0dbf9b65221fc24febf4c650a7b6ec683caecd5d09251d85488379bfe974724a389f9f9f978f8da95c4915e99e39e0995bcaae0827c40151e18514dcac2a726532a44689ba5b09e7cfa5aa24aab938384bdc3d29ee7bdaf8d3837b360e275edbf7ca033ad5f88676dd1f7cc00c4bac2cae37ee24d119f626f41101e6bb747238c9dc233f1ee2e8cd54fe847e5cc8909c3d62c49641c5eac1084ae0ec0d4609bea52207f882980f042090b20cf7db9e3918e34e9900479a95429b4cce0b6e7247a502c3207b31024ec42a89ca03b17de06b6a995f573da2fe644976ea99fa72bf5657ef819d2c6671f055a6a1e91c98c24fbd531b8ca6e6740f5db1a223df72a061887dae6a91932c59536442a3214fa9195526b197c2c845efce31eb694a72fa4d966f814583d57eb1a78db57490ec9a788ce9d275fd48c18ae7901729797230d7fdf02bf3835638c8e3ba97df215d6da07d03ad26cc4d7e67e1d1c72e72525f7c8e2504fb63c74528028e46b84212a0d9ad66a1cdbb44ea9e3c38a46b4772a8d31f756eabfe04016c81f581e608dd690cd3c44b7e89aa882121c30507be725ae11ee49f50c3d1b783cc34efbf7c7ad94e91bb3a22f3650b59c98af5d46a18671959ebc879b1cb92b4c7fc715fcf12fdfe43a7afce4f48894ab745a728b125e6c942ca18bd41657a32a7e4e3497de4fbc1da5d0f7ec7667e5fcbe754661b72a49d9aa3eaf31392b66d02ff92dc64bc8d94c06d4d38bdd8d7393c65d70a93723dd1e763ac0ab97af58135044cacf0abd66f4732c4dedf7d6f0c581d26438d72826aeb69f2b27f26879b6e97b8c68005cea9638d25d7c4c9b84d05cb47091272584a3b545afe95f455c036d6ff40ab10ccd649c430a90ce4ec9d656d038e227286443cc98dfddd0c138b6753de54d9abb05fc3641d3c6496622d5d3cf860c37299a2a4221e06958a91ed9d6f2db8fdd0cfea3cfdfc683817f684df387fb0b4720c83720fff4faff060bb90c576dca19c6b32053f3cf43867a02ff755f8d4d372f07987dcde5632ba072c5d63480edef578d6abf6feeb77c0d7a4260fa7375372a1f35a1f7ca0013b4c13f378fabe69fd731bbab334d3175e65654e52feae89723c91b31e2b9a45872fd7fc34246f4230dab3d45d17ff3834de8f5512eafc0434eafbcaa10c4f619aceb94f2a5e3712d69f03e8b35c48aba93caf6940bbf460595cea3cb5e316cbd01cdd0723c7437b5a89b12f5fedb688a1b0c959298b0c5f720dbf69d928f0165679acdce474e23814a560643a93107067f3b62acd4301ea4ec6682e9a5f2376afe453b856cbcc159f965b5f12ba53e597e6a6f2e66558c2268953f1cc12872e842517f974c30e1a8e6f5a2a3feddcf7a158ac45cd7af4992f7861b1359be52264c5d5f994f1e674fde71878552494f453ff76792e9f976e72a1abc6250a04b48acb90710a3496579d19b92ba8393b10cb907d2998c43f2772513c36bc3963e20d1993c5ee511ada8322ff7a7f1e1613ab5523e4337943f54ec02ee6021f5053ad29ef26a94f86916e954260a9f9844ae192d26bea956bfa34417c070266547dc570dd446290edc2bb651d2d112e17ffb2a2d2701bdc90702e8772614e7afb153074aca6f8d174eac4e390e4188a64b129c613871aac4ce37210a679f6918fa52843d76d8aa7c4bd1f64b0f1822b3d6df84175d4f5176f50136288093ebf3b62e8801cd4a690f6e9875db3f0f29cdb1ed580ece1c1178ab6724b16fdd85ff645711f2447a74417370c7581edce5f418c7201771e1296bfc772a7011165fed1e2ee67bb28963dac0a963a1d3da7010cbc1735b9d1c7776d1e720325c14c81109953387a86ec2a4766a975b2fc1b30a74fb03b6c75980d716472a3eea7228ebd57877de06a42a875517d51c2109b1c0f07ec62236915cc5b4d720e7020c1fb197971b3e6227b625337bf3d7a16b386c1a8b536eb62156d69e7723f77b8dff54a2d37b6abfb892ff4db8b8771c8d07cb2032756f00002ab8efa72382ebc851d1094167ec8823855338e9c8b601c6f1ac95c05c0a62e16ba4f954d7ef5cac985815e1ce64a55c830be1f5f396caf905db398e369a8f2eee1dbe672463ffab830cc59265c5a68dfe7ba6d5f6088888ed3c94008d68e0bfd21afe9139f77b3349337da90317d182f0a1f6f07e97733c3b4317da3b7816144695de072bac53695b3cb827a37d9a28010c52b0ea3c60fc06f1aa742244157bb51022672b03da9ba18d8a83225ada643d882215cd778d5880ea854720b959b7868d4fd72f38adbb474e1913b118fc0051010202113b72f93420559afeb321517f35fd260fe8db60786e21fabb9df975fca8394a7d581a7f6da9e759b955e8d402181b2676d109cd3ad822ef734ed8100d677e0816f70633439d5da57e239677d1f638972ce2e882705e0625fd1dc31ff8a898790265afd8b3735cc5c48a08ec843e08d6f359246041b7c33d71280b61c7f577c840f6147b477a9a8b99a6c3c9df110681d461e414a359e2965d365c816f5f9e8bc28f4557d09240a3192386fddd8c529696af7e8b77466b872aca58f3a251c3961cfc8bc30c473d138ee17d5d51f9ac533e46478347a1147f4adf5df8d1e022eaa57be4026568d213cd32b4e40bb2add72e83ae1138277346d141039b8e18b5aaa7c1d706cafc99852fe9e5de03dbac472759bf338fb1e02d799d3583688ec5d53e646df224361ae99351835a360c8133dc1c09f05d3d139735498ce797bdff58e316deedf09851338467d33a0fb5fd562deb464d8b791a90ae7a56ff2820e976a8c58d607376e4650c123bf533860c2728e401486088124133c7f3faeadf88f12b1b8ce7c7ea99563a3f79d61f433bd723e19895a85f23f290b913963d508320fdb7e6bfce13f52b11bb20480fab14a369cf087b6030e1ec6aea96b6c88dcaece08ae0af1c18ba4e499b737eade4e006b749cf7e316f57dbd7bf264bbb90dcb222e45dbc5516134c5557f8196876f5b720d7f3cf00c58a993eb1878fe18d719f07de21967b2f9de77dc691462021b1c667d37c6eb6669ebb7dcb743e3075d1d25d56f97be33b1a3d79001084c131d445bf75a7e5cb881a7d50e731f37be6780229ba044519a6d95084e65cf3491bd6a72ac812f2ba1d0e5f9017d7f05be3e5da8d806085d42dab2c773c302869738a9729fd9de4f1e9c9d4c7dd6fae4cec5b838bb591fbc9e61f2229d775d2cfc43e0724dc1327a40a9868acf4d3c5f97d5c81918d7438bd973293bed8c4c560dbb304b0e68710cf2e77fbe10328edc5482905f5063c83dbe94f1b2e93bef2f46134172e9fa51560eab11dba1fff0d5cda4dfef14eb5ec2995fa1ac07a50f5e8f0a741e211478e83172a2f74046ef5f1e9cfd9845ae29d45dac1b2a55c13fbd53212672284aa8d797e18c8c7fb2b6b7282fd8e0038cbbd6a1bef160fc7aa5323037e504b36be5b073a4956144a3b3aa31680575effcec2afab6c67aa0aea91f4e464e72df083ab56c312053214afe34c85ddce7515479f47c6f4f6f700a13f885388f0a4d580bdb9a0f0ee148c97081f1a3697c736270ea1fd7467eb343173e901eb36c9d447524ac396d322d5c5cb5cd0329e14a0fb60a3db94fb5cb4347652bd8ad0128299cd35ea10b8124b7a951f3cefa5bf3920a401d2d961ade2b4f11cb214a726bd7f60ae4f28f2adfe1dcaf1db4513d8034e24aedc5a5bc756576b02603b2722aba34e63060e1dd7081f76c17e563c413dec19c60b590ebbf71fe4d175af7627e60fb0c2cd66055a6a93e94b5a2a7d994553d15e18a01cb439d8dd8e3cd5472318154546687dea41fc69af5b2fe2f273cec2bc1cf44da2e7aac3e5474938708cf91c8aec6cdffa866d0ca140d9ea5891b21cce30136c71bbe98e8ce0d0caa61fa0d1ebb3f503cafed5711b2f1646f239942e677d3d4b8d361c2e12d41a9fe229c9295eed07c0332f513a8e87699febe03569ab6a3fa5c9dfe8989331f51c02f2033de117226e5e1aaeae797120206e019b45560d6a7b2204793dcd542d4a962f46a8254ada30f640a24a2dc2dfdb9a540a94ead666258deb925077172cc383814b49ce226d67ce7c5892082304f3bb381913945fef487971d4a79097bc9b62a241078c477f17834e1a44799a7d7a4908789fe4b69c2e98499821adb051ea672fa00d7fe2ab4f479029cd686c676dd1c5e244e12e68a77245e4eb11f4d819e723c0592354f345a18edce97a4f4cc574941ad1ea4408e8729c8e7405c5df1047294ed38d73fd778a2d0e48f2503696c2d70b7e228053aada004074124873e4672aa2a6c5d8c6bb16f17d25be46a4e8616d254bf7ef37497e444f360727f7948726413af5aa3bb607239c046be0b2e10473ece0aeb7c4dc18677c1937cfa62e761f70d596c600138c035855a7b3f969a758bb9cabb8e7ba3b115bb08639f1e4d72e2e7c30dda74a40511737f67d9a4d6e6118d82cb832d912e918e54f92e6cad72e2cc55d5b2b2ed552d61fb308c1ccb5440303edb0ca0220caedba967b8d0787214db1960897db5822fbf025c84f87f7f193a091bfc91343080fe160951049572d12466acfae60fc8861aef3e5c98301e7bb263460646680def35bb0c326cc14b3ac53207868bdbc0258939c7b3c183034f28195f22c873515a08e7cd10959472d92921d79923bbdf5f82b28361f5384385eb58f45bf522290f8dd747a9e63372d24e0e16b71a9f5a8a9ef012b0f7348ec13c04ab0e4ed27fb3456e0850993372707fc65b29f96570de008bfc6cf4dc7b9397b1eb24fa2eb90e2d1aef8d6130725666077a510ff9a90e21ddbefaa53701e6b04c57e9abe2925af49b92bce27f5d9c6c84860340fc22811ff48c29a0c681e9dfd09c8a433424e9248aa6213182012e3f30bb04ca3625d61cca9f91fa0afcb500fa792c691b390091dfe7b1798772ce05539bd7c4ac6129f810fb143451b7548c20ad32fac77d3438b47d12515c69960bb5bafd81f85aa3ac9f7c3c8d93e8e6501dff0e6b3adb368439077a4d465d80b4c9d97268273942949d3e930a5493e80d5ebfef7fc2c0d146880ed6e6b3503bcebbabdfcac54e52d881cfea1268309fdd1940157444d1b19a98c25ae2ba7289f82d5e4c4448b231af74117b0d0de153b2de4efe5efaedc01d3ce38e6e503cb44cc2d941576aabed41fec865d65cc10d9f40a37e7264232bc4bcd4fa6a3b726c83d19bccb557f9381a41ba1a1c02d170944e65efd22a9cbbef34126677af05d1cd9bf94dffea12f84166f61326dc125c0c295c97f242e45455b36a053b5a2fd6bdc230ee2d8e309b27f322895de06acc5867664da78f44f9a5ffb8573a3b7011aeaefc7e696cca66155e559b242584121023bd9af42146dc08338695521d723a58bc8ce412531853fed948c9905ab9609be83944ff28c1dabda54f0bdefb1b8ba64ceac5cd5d5e6e38037ec4117259c051c420c920676a05c42886342283724f534f25c16fffcb4417d5b51951fe4fb6fdd474c985c91f31ef705ef42b9f254f4d15b2433756f3282a17a84dc59ceadb729294dfa5bdbe9d681612067f4f729eb4e2ab43fdc30817295e1f619a93449acc481d055146213e150c4135ee150a0a86fe46f2eb51b2c8bfbc62cf697c0675f3bf69ec39f0a247b21c1487f69372860cd401b6e4b606d72ed632b9f5179c1d03789d7212f617d3220aad74f6ac1368a89764050eecbd6e998cdaf0f80884f80dee2ba466cd0d9c46aed1bff6bf626b3f3bde44aad6065062069ee23ead8da071901a3d1a2e9a8902613d1f810f1c4032fac34655f68816a8c23be2b978fdbb4c88791ac60da7e41449049bdb2b0df39c11d58eea5330709c3913cc0439b253165b0fc9f321a4e47b096794a1bf7217c041d90afc557e91abe40e5fed65766635f5a9278e82e8050285ee43b86b724a492b528ff92911bf1796135f4ef64ddd0df034a9baa80e393bbb534186476244994acd4ccdd2f7a29e1c0e4d0bfec89cf13040a43535f45f35280fc9145c25280f3891c9203a6795bbe95ec1e6edf8bc5fb47e13eb55b7ae732382104a3072f734a73959f78c8c02822fb57a0080a6e9d424143de8557fa8a003d8c4924f169e69e26ae285848a00559feb252898e70c7504f8360ea0e232c516d3342e4d6047931235a4ad411022763a9db38b3271bbe8bc258e9fc2f2a32fdf88e0b5f772721ed4b0c8bafb4a6123efc2189eb4c71d499ffa586ac5b969477fb5bbf3be01e04fa95b889fd32870646edc9fc7669801e9b1f3e359810f45e4b5c76267ab2f803c9e828809f3f31d7da1a139d320df50a2bcc05d3fac5eba5661cd0f00bf726514d6e49dc205f1aac3c5a97e1ef2a1da5a4da4446ca0ac3fb7c4f46af3c60c9d1597029fb5d388293902fd81a37de1a72ad240a4f77a48233e5cf941014e20d30bfb95907d09695e29dad87581ee95f7a9a00a9459ab0d77ec61bc6f7d8c49b2f88ed6f47f36a5eeac97e6324e8554a513d9a4366c397862559cd32b955a391e236717d409a2d562353a19d7a88fa5ef4399b1d31c9d6fe9e8aaa89d735754c6d47d4da0dc3b589baec8ea0b076d8287511b20d68d4174d35784d634ef0d13c631c79ee9a78f8aa279edaacdecd1af0b60dec8c261496a55804f817f908672370ec2a11a436d32f08ac4e5199b004770fcaef4b5958be3a5b9262ed4d844720d1bdcf56e749fe36b53ae9e1fba9742ee6d3a6fa96625517d5d37a389c80d728bb7bb4abb8a9f10cc537ecb322e5e561b1b6a31b4b4622e66dbd649eb4f30728520b7538bba576d2b16bd5b490e6397551bd7d13b528c05b57aa989367d4d727d0983fa8cf042d655c8b88f3ccc98b51489a3fc16f8fda4a41705164863bd724246ca18ee861ebbe85d02e1430d3c559ea100f554344909b50b7ff43858e96c2af0b659d1324fb6fad46c95482613adee4e553bef56a32d72c838a7f09226065e083ef967caac213d4e11b6eec6111a15b12a26670ef7854abd2b1a443b611fb4bef1b051be5e91e1f5ea4af1e4457dd80d7e831d1ca1636dc9347564d2ae56ad5c97ab6724cb7e5ee81c333aa9c6e0c8bb09169c541a4815b119f040814f6064d3ddd321bafe0bf8bc318f886561f57d5f7e32e0c75420e8ab1b62bdd7a3728f040a367d16fcdf776556f82be94e2f14d8095019eb99c13a3ab62f443b116bf0fc4834e45c786ea8f38d6a261ac3ba08978a72fc26b63b122db50070cd687200c0d6620889c29f2ec6a983662a6d36ab8fa2598993c64d106a11f8c30d9a163633d3d6bb91f9a4dc5ab9c6d13b37bcf36edd7e96ac17c1ca0ec35074d30e2bdb15fa9349200d9daa40f961a96f450723af95cb03fd8a3a3afe79401acd3572d181901398ccde6b3ca1fb6515951ffbd02b8d8318db8650d8bedebca9590f465e3d429f6e7ec7cd79fdee0a004ef18209329f31885b8ae0148f102819b49c66945268755bf1005a86aab1bf0e7a266a1b8fbb9062cc7fbe47eba3ea81b5c37250c4326a7931e45734ee7110f95579e9e9cf5ace04f82aba8d4d157112db8b466c4b7d538f127c25598dc33d72013337ea9b1e4ae3fe676849eadb43fbf5b908f26572ee5ec59d2abd4fe8b0c121e1fc22a952378db87d6f37e6a0e862cabb1f2df365db0b9feaa77ff35130b76520722ffd912497aac1dd5bcbb1cdf76fb47220f8d3017e622a3a44b270a46d98a1f1bd31098e787000f27a401efae80b92249c25f67a29bc75649fa93614d171e19898a6db7ec4bee44fc4b55a41dcc19f0befe1e220aa267a6ad8a31f0b85050c2bdea737998d9d21a88de3edcb567d7572da6458e630cc35be69fd0a797e7ed22289860469fefcd5119642a54cd028ab3ccc85fc2b73555c6a190e559dd05b11b72a9b93750720d5ac032cccafa9ea28721dbbe05e7e44f87d4efe6bb9dfa747b6bb7a0afa838c7a0038ccaa938f64bd7264c15336aa56442bc98e22d521f52a6430c7f7c1e277bea2122bdf90717fc472045b162f54ea06aa115717e337eeda9f2dc84657819aeb67106e5f9a736699728255a8ac0961d1b2291803f493105bbb36193fb43b2f224c1d80bc6f7257577243a7687f9e9280ae9f559121d0d6584d7ea4caa726a9e820d807580f4351e56087ca41483ef5f8abdcbc383455a935cf558a3d4782a0759c1fb5b2802caea272b3e358212eb3822b006fa657707b1017f5eceb451ac0a913be68eba3c593a2473f0873a91696d635a8790b021d05e2a4654c50795d95738fe3f33ec3e2f68627a2c5f5483f9c02377551a5e3f18e05ac75b038ab40648154e813c1bd576c2f0f8e3bfca7efb8519c54869709cf394be9f56ba8bdaadfb9e6be2db380dd8f5972b53d1da6c261a40770e2fa1aa4b2a1e1d7cf75d9bc525831f426fdfc6ac4ed724fd610c0cac55af549c174542f125e74728f11671dfe7c5454c99beba7ec675830821f6a1fbc5a2c02987971d94ee1443eee2b374475a4d52f41cb32d7237d72a9096c6aa81008af78dc901952359d8377a0f0bebc5fa7d3982c2865805e8c081d4e5142342413aea4d8099cd9c31796168b1576f3cded3fb39b45f4476edf30a37d5d96f23850c9592d884dcbe0b4d25a2debc5d4c9fd45b34748a2efc5ff16bdbcc353937b505c6df5e68e02287bb6bfdab86e5cd10bf58eecd53d6fd91d72e323bbeab0c533fc644e576034df157bdbd6e7856b033f6aada10a279a63884ccea779a49582ada6270e75d47502cc95320451397e2d4611700c50d4926bfc72a109b2379bbb22c18cb011afd0d6e304c8ceffb0a588f1896cf01d65ec34056d11b95eaefea627d47e23d82960157f26653eccd0f1c243de417152dbcd2ed07239c248e8f552d200cbb5912ffe7bad75be3a17f2b74ae295fbb057184bc9ba3a2f9ed29100dc0a54dbe0e7aa8fb07bf6b272287360c6771702cf88d106510c729ada925e5cc3edbc5a5a315bd4cbc823150bcce024186ed3f42b0a845f91663c22b1c18c554a13e0147ddebb51454e109df19f0fcf0cb9026be0a38ad4fe4c7248b1306bc86fbce5176c344f07ab0a503f472740010f714d37f37089ca86207281d846ae472ccf4b0edd36044e903982ccb042847f3af47bf61a13d37ed6b6720788af1491c638e54be8468776e2261ee55424e8018009eb1ecee0619ccea07284a1c0f13bf834d136abb513226ae446a6bf36a20ea52ab01965ad911cab86728d251131e2b280e5f8488057f3aff757ace487850a2cd1589d0332e7f27e9a679fdf6a9555d73f5e3935fa36c8346243025785afed431a95ba17ab6dcf730811495dee304412daeed82e55b325b823c89e19c6b4cc8f8a386f69d8246ec6e57273df766b562fef843039c22caff3ee90ca910a73fcc8636bea18c59dc705311c7150b2ff8f58da10604ec3cb49a37613c6803da0d8f13b8fd60d5c3ed9620672f5ce702b0299caac772208f49fac286ae9214793d14fef6728fe0bb89d5da765a23d3f17c8254fc0731a8c21a86f50029f7039ba9a719850ec4f07c12cb8ae6e27f3e794c152d3959f7f04f763aa460c76cdf3c74158d34eef1fe23f4620db4b2198b80fb3ac5a3fb29aabbf543e017a015baf7489f68a6917553466f57c3772527203a7deef0491908054aad52169986c2a93750ef831d7d0edcb17ffb9f072fe33846afc4340227d9d6a724d5cd6758eba6e3e2c2dad8c7d0479c2f29a83723eda0d0a760a5c8f6a919572af103ec329c974198dad8a96a7c9f2c614d3ec720adf271e017357b08a43266f54e86052c14db7105641c10cc8636d52adad5d4dab17c17a954f699a6da5a536dd2169c3d6b29b8a685e82a8ef63275c3b36397210000d27aa617fbbc3a254115e403f5052cea012f2926e917d9be49cf06031726eb63206d330fc1499ddfe2f3b6b8917f8a0f3638cfe51f778ff448f44309a26a7e8c4e4f91101a749a4ba0e2cb0410ed469b572dcae8c6beb57b41c5ae91d347af13a6b148595eb792eb44bfb9b453b7687b0c178f45a84add029543b82d972b55ac769f2990670294df6069ea2d875acb95f5b07161bcff7b3c36a41cf947240410e7e92ceb8711732a520175369421ec16520c8f10f13a946ccdceec388117e5963a23a856ff9414044e5f1150bfcbe024e54402df87d6937cd8dcaac64727f62c05f4239f672f229d49d6327147d2411c1e274ae919495830fdddc924f19b8de2f1279aa8c52ce9ff7ebed8dc807ac64f5230d06fac45cc804b97f1fbf4e3b300532d79c66d82b46b17ce81f7142a1ffa86059f00c099c5d087b5770c6729ced9e4780ff081bd706ab2763cc2e0b2d388c722cbfbf8d6b79bcf873b9a572cbee6684f959c9a91f52f3c5f8167409e7009221b0256788887f2a5d5450c80e14e975e620c51683c67f8ff00ba6fe340827bd07430b8c8de321632def09500189f74a3140c600bf7234da0eacd4d84123fa532d56b1aa50ad9673e7a2689a725220f1e1eda76fc15f4746694fb77f087b3846354944b902012a879368f0634d3bfc6f00cb4d8244565db6eea171e7cfa812a5f772ff4cae53867bd12adf61725c5e63a808320402c8c1b4d94d8b7adff4d96b061604d94585a75aa3f377f92289c4fb7508fac828405ddedd612b6743e74a7b40ed5f75e3dd56491506314972c69d37c27092504cec4060c0a9a43b2bad96c1b7fabb0ba80f2007026f7a7a72c50dc660451927a119669c55ee36ca044240de552c05f87a60c5b26872281849ddda1c8166899ab6e1440694303c81d53208f8a007c337338e83a7bf7e360472befacc08a80eb031eb948ed94e4cec8e074b1fba632df198cda35747805969726ef7631af2fc227da6dc55042f311b4baca1b90fa4a409400bca617cca446b72c42080570ca0925f2845cd4c42310badd4f7026113ff3a8b7f4d28589a449c726602efb8d339a664d3a9e2f5434af950956b071e1b55a6d652b122e9cb63dc36e09a06142190dd6d54ec18ec6a83641f991c07c328ff792e98d298cec5512f72b1e2359c2a01940593ae2a1408382f057a71fdbbdb403283ccc38e85add19e726fdd15cddb4c08d0be603eeeecc990eee0ed457cb11354be95cedfbf4345c736df0781e07e91767ec9c405e9c48efea13ebbd7e760a26fe5267f53e4d18a387225ad6f6f83a54634466fce02c8fbf2175e038859dc6d8232c1af13f62d5857728673ecd5b75778f8215dbeb89567084e26c81ebb5bbf54347eef659e80b5f6724ff87a303805fea1a88a832d27119c5b93ed1c42b8a478c7d391814597bb07725c7c92813f5cd18f2d22fdf5e88ee2baaf90f9754f88cee5f3f068a2d882e6723bf8ca14e0ab4506b4516a0ef3874f9d00be0606f8a15712c54472b4f02fd1621efd8b87ef44ab29b9cd95a12d6af9b7c86528c46b7b9d2bbeb6bf1dfb9d99725f299f85cfc59b222fd1d20a97b1742516552842ef06f2f9387e9f226ef24d72bd3423524a953aee9145a1b439660bd4abf2675928c46db029b8d6c42697c272ed03f33fe610ebdeffe9c6ef97b044bba28f016bcbc5c5952a52ad5a89104e4a93ee3224d7b8d06bc612441fa8067b938525e7fbce2e2a102946daa2b84e947288e746c1565416692985c36e5318b21a2d6623fed15d74016b1072f9bad10b37e2f6c6f1f14513805af8920b2d9a74bdbd219d1bd1c73fa955aa85a1ef41b46012c8620597ac0611b6c834b1f9e6530ece7a0f7c52557eaf2a5f8d1a9242027236625b055c9fe68493aaedafc46c2ded62bb656385ab287c877a4106806c534283576bfa4bbe261d36c7d139691d8059ee40165819378b0d2a0dee77fe11247286f0a981bae9fad0f8e2e68932787aeda0133e0be60ef5175e5850e198d601675ba196662d2d4648a0dcc30f071e83fa07e055544e5de1a85856a672ba951215e3a67e064f9b0f61fe0eb49d078dd980d9fe625d05a8af8525b137efaa16e52535852eb510a8efae4a0c51363711c2e6a0c8dd75d745f93104c37ccfb74a421dd5ddfd050e77e8c73a4a98fb8d4bfe1a95fc9d84596ab0c868851a87a1648572ba51a0b9a9c0c6e8d84ca2ba5ccae6cf30b4ff549feb12cacb474ecffd6cd1729ebc3441ab63da67e1c55dce7aea642f242fbf25b693643cb54a96400ed08208d21aaaba07caffce0a2c1d35efa8259fd581a19785c9e7d02277fd0444a17c06a5716cbe68523759ddb252b1c50fb3328d32b8fc010cecc87f5dff76ac655f3fdbf1c3eae7021554be78044be4f20ad74776134f89ec7bd001c7125fc3ed8a72720e9a22b48d7dba2e3099544deeb4d77d3d22c7535107c3daea8867b758367227bc79339d799091922433371ebe7017a1a868d512adbb56e21a6a2be5e22d28edcb5ea6bec6c6d3f83ac99ebd97e96734f6d5f62751523130b52b54e5375a724cc50760c7376d5b986ed2428adf7ec2ef065eb58b279e895d578b4ac237e768e4462ba6cda6ce12ad469ba988743a49f9233dbc5508011cb0fe0788f1faa5570ba8a50446a4fb6d3f02bac0054c6b2b427fbe1066d9c8dfc852999268a4a07240efb5859d3fe5b8211cd97942be367a0203e4f4cb2edbc2fca1b4118f8c0f5cd2a911d700bdea4b88d8d215de4b73520a657cb6f0959d0300fe06ba56c5077240b811f958ec2ff1f0873778dd86d7385d25d7190f9505006aa79bcd513fa46214741aaa4e68050898a10345862a12c156b06ddbf860708d2768995936e94267d433502a8fe3422ecd4c9f9560386fa3f37ba2406fd903b9ca30267b3b257253dd11fb7430c511e054cd9ba4133d99d915cdc3b09643883b418eff7737ee370f3bd079e5329b10738bf600699c333c16145af69e9ba187bafd14eb96c9d56072b3f7ddfc9e074f14793cdb44978a12754493b8755bc0c2c25504041d7ac6e744b3fed4e2d988110040845c69ee31f9398caef8da70b39a5f22501daf34e6f172ff7a8d853d372c8bd28a506b6d80c23d9523a350fe65050a1431e0e18524f77255237c3d7d01ca77c20b61330867ac8af911fef5f795f5776dd331d0aae2e072d194928823e6beb4895e1040734f8dcf0eed274778d30b378d2738c11d2c2172131641a775e70d706885848e9eb4ef0e8e59c11adf909a29b0f2ac75f0a51d232845b95e6ca3cb3819b0b1af6286b906fb174b1d3c4213251914efc1f6227f0999109ac6c3f38122012fcbb8cfa75bdcabfc8f9101f89c9b6e0b8b6785161d50777d2d6646619e1a985e0936e4b3a028a5912439bae09a1e82bb9ab51ec0e772d32d947bf82293ea5c610a9f98fbeb1308e7a7576b55b82242c529a4c0e57a720e827de4dfc3f8ee216a5c9beff128e5a7e9d708a3818244f37bcc66ad438d0fe40d604583bcf92f4791f86628d3de762ef1d3b3e2c56bec11c94cbbe93a1a7294c92386ef29905d824160ddf6fc3065fe49c5f5fc984a489c5fa89f9d089c3e070f69243412714382f52eeba9fe69cc6a703caa5b756091fa215ef47977c872042911c70d373c7fb7cb34df1de8c9371ec135000811f42ae0f31bba24d2a7729251e90e21569f47d9b0168ccd02ac138e7abde4f86484544eb61ca37d8d587217b14a07255c1da293f5090abc2ba29cb281ebb8536ad71d67bcd6d3407bce72ae8e98a7b211ccc28c12d6c74e27c60bde840d8386f9c2ae491b0bf3fcec9103f2de2457b2868fbca4a53daf5c1da18fbb3baabfa43a7c01ffa55410e53df863bea1e92cbe9aa5f616a1ae9b200c1a9d9a2b8d1b34d5aca1f4356ad9549ac71b68343df7cca861c82d0351f187b85caecd44194e4e46e488d6dea01a866a467261731f2a45b937ed4823237e9396a0a44fa5413221e9d883b2fd968ba2cdf424e0f0cbfd25fb92e1ca83542f13c64c6afb9e866b1013b460e57e15743f582f133ebff8b353991a10dca1d03a7a53693be677918e1d490cb9729efb53faa9131c0ef1b50c2da18a3564aa4e93b3c890825732768adf668de7d521762cbe3de82536aedc1a674b30c11cd85f310723a80e93f99674e5bb48a8ca4dc748438f62336fdf9f3f579653793218c485658148ba05e6ffb8b1795c2b34b22ccd8bf53572f952147ccc95399ee3e17efe9acdaf5587454354b54067835deb86d4515fa7724ce54c6dd1500446179ed7775dd1d7a0aa18b507041a0aa02ab90a6c1818f46604a160abfabf823bbe68b6f88514a7492abc24b81e6f2f5faf2568cd58827a726b742ab0841ad1d8c068c2c7fc61906fbdd1bf4600d78f4247a57eff2afe7572219f468748cc86fe1a4f3c3d71ce047fc2883ec84db91cf6c7b376e663f561729445c7447b53f2a0fa6537c03a31fc55ed9a07676ff094df9f97a60ec443fc7143fd020ec73ca06d64ea0dd04b6692300cd605feb7aa7394f0aac87ddc284912c27e8e6b34164a194ef86aa98f41d975da26b934470cae62953fd6b0de170072d029ecb0f39eda02f313a63e947fcdb70aed1ad5dac9eeba94b3f81a6d434c72245e1f6d07dee9b6d7bb5bd3101c7d4f4be78df08fd9993a125463d8eff47170044788c17a14df8a49346106d2998a0924c7f12529867d1c50d2beef56ac5e7266e78027dac2e018b7478f8e8756a55f2a64608b793fedad87f982e537308c7233764ca9d1deaa084454258161723756159499a2b9e68bfd306b466827c69b72013070314b451a55cb4ae5c036e9515b3640c7f57567b75e033beadd474a6a3721201a39c41e57593ee07e65e8fd7f6ade8c06512a8a46f5a8d45102c4f1137259105e4b5e5613a8b095b3642c601f1962016278c573402fb5eb0f6f8aca977207f1db43b9a95831af89c27ff4485e8942c086e0a8f13c51ec2c12a080a4c072b09f15bf13704dff9285782df4d66d642b63a5ab2b11ac93b2bd852a3c027472cdbfde03e189ece70ad71f8d4e5d6faab02655a3512839ac0924b7e06e11b25881116acf6c51361ad753bde7e22a4f2941b1780f395e3d10d4f97e2bbe3c9d7232fd79f30d2767ba3c735c295843e1eb3c07789fffd3fd812cb4b72ec4bd9372ee75f2d5ca3f71b9238020cf824bf142a1ca8704e8c6ad7e9e87dc505cc7746a905c1c4e470896acdcaa43f281a2cd29a7d93bcc86a76e50c896f8fb62f51f35ea88056373f879310dcd432de6e3b2daf6eea9c9c3dfa39f4c2e8a4930859a72c5af6d5089055b122fe4778a4aa234f93c5e7cd952ddf0e05f0ee947c75b3a727d064b80ee7adf8a944adb7ffda80c6ba3244805fa2267ecdaa005e59593af7212469e9621847901446a608ac76b101e562758184f3ae02dee623a2be8881711f7e19278ccb61cc3bc022454c73b7339da0f69f2f77f4c8fbf96e22191f6c872e5f00631cdfd25cb43e8ff8893b3fe40655b33f3870b301912aad467f7943e3643412d5a0c50ac3040551240ac155e55e7ee671ebf49784bb91817e5210b2232d07f5857ce11ce686048932385360cf9695f9417e074115a83491733b0dc8d53360da7cf0e54ae87d217764a55dc289a1e2cc12727ff0cac43cd8f828ca5f972ad9071df99cbcbb24b0f7270c64f8d68bdafcb72f4643b0a7bc9b91b36cc07724fba84b3ceb5fb29ac04b7c756f2897b2121dad54800ab132cbcd471f90e1d728dd5c4b3e8435e834a4c761c6ea22abe47683160365398f8c7dc9498848c1572956564ccc4acacd413743e85243b5b165f80c7286ece588b66361ca5f666e972e469f2b07324665c424ce8338c38d3bd82edaf664670f5fa4b1caadc3628b9722d70bd76be4ec9a7eb365b6e1c967c14d627e0e06e3355f7e19fe54fd54b451b1cb2ea85d1085683aa76ec2070e55b468d0e06bc81c7dcdb6b5872e79bb87c724489e1f7cc98da9d495872eeb606dcd16c2186a416ed50984b1c61f0a7299e4be7a755f9ff3e0a4392800e18999c93592b3c12090a2d3d3ef3d763eec4c08872af3ac40ca06d48dcb042a59060b4193037323d333bb81dc787e47e32c4e9c7362f6a5f5590bf0226642f45192cabd0cbcb73ec28aa56bdb97f0854b2eb2adf72917bb7c8cacf2953677c786d7f638833bd7b4908cdcca042d8adbe1d19460a6f4d44f5fda3028ae2792d6f40ec6a42596256da6a0e56f844113f2f710defce7217a0feb1a9ec19dfdbad7b31b4f2cec4f5dbcd99acf6e670fd833ed3a190151d5a63e20df89e3c7719f0d64311be1471e7305274186bc0cf5336185e095176394db1c96a7fe5ad46664d7196ba4252798fd2b125b3558b736f4bf9345609d814cbb2f8bd65e659bd5d796f0e85ca5a1d89c5d085c666192854d401e8dd055a37e6058ed7884129925627220a0a1287a8a5adfd3463e5293ab311e17bc263fa5459ac287b31d89f50c8f67bb636b9830b540003928f51f3fbf125a9e2d661e323eace788454ed4b45dfb713fc900a67a61c4e3926c754be0ad95685145640c3343c154d5ff1ae9bed186d9dba34bd9578f6358a272ef544b5447caba3c86cc81ee09765d12030acd6ccda7f5deb1617e49c2c3b31ee6b34a64cefda6f5a88cb727dbef986afb4e4481deb7a09468178d8723509df08a9e6286bf7d573fa215372aa38a29c8b9b12115bcb3bbcee1a2acce660059ca1e75ee9dbdf3fdd4182e272b47d63a4a72700ffde2f17373e7dbb12737b7bdd8669c2e05cf33388d41c4131b82fc5f666bdf27f2c8fded84fd7944e17cb3c61807d87cc7708cef1a3550c4b1f71e2a64f01dfd8380de49b924fdbf904cef75ae5f36c0952de21bd89cc2472a88ac2ec1d8f37deee6b38c50e863e2f00695b08fc98ffc18c2825f61df4497262af9846eba8ce69817528ae059e1adbe1d690cace81f71ca07bf9b6f2c98c431672f1536a24c923c47ba7a240d9c135509ae68626a773b60402177ca788a772504dc1fca72f91595d0e7d75f5ec13177be0f83142e2be866b5ec50f7be40272f32813c5a9b30a6766b6a386239278fc47d75f64fb033709a30688f0f0977272c773bc4a8d8771e092777db9ec9b7ed549d36639c188844a7a0ab573b3e43672f8ccfd0d12ee44a164d4cec3c25141f96a35c63f3b7c8d3160e100400188c772bdfab936083d87f77c7ac0fb1935f951e7e0dee8bdab30961468ec3406c0ab3629e09f9e5e9acedb9e47d0a48c2fac578e97593ca35380fa00ebddbebb1216727fe5b9bd6496f575c05c02c0e258b1ab966fc46e3bddb7305dc6b6a077794d10b3f0ddd4c7e93dd0b431a4ff36f8fd502289d1d9332967a29209fffda0c7cc143666b7769fe541e7d0e5bc46ddb7569c585b5384942c3ff060cf69e52f254200ddab20b3378c7a9fac3a9f61ce7732c52def15a732330f4e1c32a3a412024f2a9f5e8f955a571eb82a5b2893228462f767c28eeb4edd2fd274ce67f690f95d72e8e6ff7608363a7435ce5df5ff409e1f651ce481ae89d163d3550d72894e0e70642703ef6e7ff76753b00e40594b0e00104e985190d80c0a9b4cc0511864f972b30385b3c9173dd4106640fac775ef38f95fae1838401454c82628ae6a611772216d0eb6d5e74ea36490a65624fe234b629856f9d28b2fc5fdae791c4fb16e7248e622a0a10fe563b4fdb6deeb50b9043c2f9a94033d27d7167e483a7105cb2b315243819dc7cbeb3aed34164badafc3466391fd0edb528b424d860b4667d972ad19d3101f480280d4cac998a8c2c4ad9ad12ffd7e891a1132884020e1513472c077f6034a76b7768dabeff3fd3c82198da0eb6f9f4bd2f95295ba7dda79be72d75aa2585ffa6191c0c8ec91434272287546034aec997ad7cc4051420849201c36c69979ef8578b59efe7bc2eda1050f4e492c0d9eb77f60e3d6b61255bace700ea74f9441a507e0618eea53fb7d85150b9083ec350ea05e6e322de88c0e9e72276bd56592cfc03728161465456753d0abb23b5f082de6811a58aebcda0fcb3aa6f398e1236212f269d7a9e0a5b93bcc4b1b434e14529b783fe9c89267011a7254292566a8591ca5b7a27d6219f45978235b5f9d25cbd6fb5f5750b92a70201ba11b913ccdda54925537d587b462336d29ba8c16f7de9424894bb898cc0b23727ca5f216d6cc9b623d7913691c7a68883f45852b9f63a5229f084548c45ba872a0f2e631cd84800a17125ad23aca4c1ce9cb590774647a4936ba64b1447a1e72a5419e1035f3f0739aa30075d3159a775c52e26c61ed94785d96c0d84254a41b465a14bbb9ba79ab3a36c4ad060332416b46a4cc58a95b8e9df7ad301b370b551f35a8fa8aae1a8276ba26f0b9c4d98106c6e85072f4c300830bcbc62a1f3b72c70f9ad16d8967416cf12544676aeb9f1732e8504c6584585a0953cd9d726061afe349e3feae3184cc7b539eed155c712f65d0dac4b11e3056f9642029754c3f0cdf8e6a213f047363bac0b33ee3aba7e35c68d0102c1fc0b21092ed1faa6a72d2dd3ee5c79bbb69ed84f0a489d43ae8f2e4115df30550f0068e10370397be721c7c445eebca3b1be2eeede84097af90b137f8103dcd9299e14fc41b2f99c072dd4ff9331917c5f3f344b866b911f7621d1d61a6a8c9bf1b74dc015cfb77aa721642b177f5246cfd68cc4c00461e424f254e2f0b914b8454345d93aad42ad240844064312ed36c9a081165947e70f3597c7dab7573e77f629700fe35e0dbf372e4baa9a57fb5bee951d59619031480027c14778e9234c583d007738ffef31f431617ee70920d717e19745c472f80d35e6a8bcc639066a59523d7fdd5af1101093bc2c16c64a3b99d281cbf81eee02a8a82e64eaf44aa31205525dd3da30daa72aa1350a0db90c4196bcb26622f41c1e861c5610c56c1c2ae4b6a7d9bf2187d720dd4183c6e31619c37aea8962de04e7988718284300df3c539a4dba7b98305727ed23e83ac11c7238e557ac1c347dee7d1204984d96afb90834ce4a4dffa3a727b9687652b6b20dcef9dbed411b75403732049c58aeec4c5c59cca90bca80772bce1d6c4fb4bdbad60614198d5395f226f0f578e64e00e6f8009fd88cbe7cd7242994f5ed74de979b90b3abd9064ec88bedf76f4dd16974a195f7748785f897224e92cb2979f227291e8ea850c8d9c8ecd17719a76a3722a18ee2f4612ad0e72fcb7c4599f133c493d751c93ad10be05a0010915d19f78f76b5dc9ed39e9c472a35be8b23206ea8f54da3bde20ca5a1a79212ba42f6f729e8becb5aa8329a17223f8b636c2b9b58e8b641441df46786d8ce747e1951bbb73e104b65384d82372552c65a6c7f1204645ca50a9eedf4df9b7ed51b3ef6ee6b449852e7abc6ddc72be4ef104137aa35e1282ce783eaeca6db25a026e3897df5fde85e5d8f4b2cd72894c879d35b4cfff6ee472b7488642f603ce02e2d4d23be0bee1310111abbe724354287e526a6a2c56bced3aa7d521ea8375483fb8f84dc0ef6a31fe8945db7207709835f7145121280ae47ca6e1945f41f158e144d7ccce53f6adf567d4ee72e57e06ed625d2a8954d704a40acac90479b024f793f16cf3f9992c0336e89b5d2851b9cae1ceca22b0d8628e5ce4274413c0ff24434b0b917063fc4c4ca10e63ae50d7bc3644a9a78c1cfa57235445a39c57e7ba2730c9387560b6716a64d42daaffa0fb86b7c25fafa4927e231c8f84ee03027f7ca506dba8e735268310217227403739578c9f92535b81523529d8ddde843351ba4eec8da8a92e3ed373af72edb161da463b06324c1a30d207861f3d5ee59871f42c7e51f87b53c30086be72b7a44ca24297cf3226728cdb72958e6b3d01a2c6b89ba4eb584810c69ecb1b01259851b51e2ca69f74d4c9849524f7ad1c1fd82d87f9a25726c072149e215272623ce55a168a3d9c5668679a2f3b71946e58a4f036dadba8d8374b1fbd4f755bded8dcae2f6fd5efb1d7f71c43d241edc27717a9154547488af5387d9c29be10c288d6314769b831bd6dab7594feb9946a7f17a2a6c0103205fc2145404ffb02511aa3ed3063825d50b9f1342dcc1158269c01e6694add5ff79d82f14d357962d27fd75d3e4c7cf0cecff17605c907d667b9f7b7e4679531dc71a049536643471b25c3cb54b153401e08d6d761fe7836c7189aae71160517df7214559adfb501369fda076835b9bc8f1f0c6f961a507ee6149a744682aa49429990fe805f455f3c5cc74b126bd7f4f27e70f32337444f6258c4d9bd727b58b7c07fb0a5c5f06320c59049721619ea398b7b53e49598f839252290f0774efee76458a6e696b43511255c861522c3172f647552247bb9418c3bca3a99ce8daf677623384d887a7260cc8cf4cd0f2e580bd1736d8a25ddda09bd511a6a50c84e2849bf95963ace2d79f07a90016ae48c4362fc1804237ae34217209b6dc643d5f68739c2f259fa72daadf783fba8d3ffa981b1b5f1c3fc875b4e3e141ec75b225fc77e72537d8b721c0dfb23dfdcaf5d8cb99b01269fe9f3fb0a74725231548b9b3105e1973eae494bccc8cd8746722b460551923cd1bc2b9c01bcbba93e2fd76fd7ecc6cd2c06722bff8279629cc1a6d58783fe1c1fbe10f6bc316f052334503d6b1f9a1d7a864a9e5178f58e744bcad400a75799c8b943957cfe3e99ba70138fed8039bbd90b723128050dea1229b05ede2c598f0d3df7df7038563d81dfe52ffd205a6a7c991b9ede6eb44b25731a3c6035c9d903cd5bc01a8aac1302271be2c33a13df2e02501847df340335296aa3e6edc986eef62fcf7cbf143c3da6e17de036d5354e9055dff9ee83c8ae5e8115602316d9ed3baf903144bf9ba365a18011e5bf12944072d540aa25388479af18d8ca66d1ef4e002adbae5a50715169058a9bdfde193372faa7bf4a0a19698824a393c1bbf50afd08b9eb1bd2aa1c21243f4223d82f406c6e15357023424e77772321283f500fcb2caccd2f6290e84f7acbda7f340dd0729460909092904a47dc3eb856ce686ae0d2fd2cd2823b2bb3e67b1844f4271c72b05efc0755c827b6dd5ad2a98528ed1986be09ab9049356b319c4644cbe74d721d4b7e80a0272df68681a978952412a5d5e5c68a654f27cc1ebdb8ba0b8c1b72905bd11f68526c46b75980e36c978152bb22a626cac4a5da9b463f416cb7f23aead97a327404c9211ad14857214438d8c54ee932244ce4ffa3b9a7c4fea09263c6be3afc5a555cee354281b5b541ea3efe64883294967ecb7d44c156172195666ec23cc033817d065f0af868a8f9d516441ad409748a81a752ccb3c248270272f45c724e148ead6170773604102f8d1cbde1f9112427ed9b0fcdc1b1fa822b72236ed7f8f41b0a260e725bf0f7f9d71ef06cbc8281ea97595fcf9638e96ee632b899c9a7eadba55524a94ea1ffb83fbd12d38e1c66267466c602b9bc52aaeb37a5af7dd9f78ec1ac94a04096df66e4a510716c227d91a93e1160240d7f9f8672b95ed0e9a1ec809b7611ab053296d71739e0b1207ade0e9284c4961f3deeb3721a2e61fb9c67fd0517fa2183145ecd8ea8fdff3074c19c27b0b828862c344f72bfb2515a0089929660d80963be85a0fb9b883139502be45038455cdac4021b721987f6d08535c16a2e9271d65db336a4abdeaecb010094d554630b2573457e727295bc39ab7a17a00eda71ce94a13467108a692e3b3aa133a62049dd20b93724c6d44c4c9adf51e666e3d4920991b1800df717f72a5b6c9d723922783d7430724949fd47cb8f151dcc2d4b98bd7cd43625bd1dea83ab3b7343ad4d3a1a5f9172de1925e6dcb725a91814116dd127cc06ba7ab03319504b5c053533ad7e74d658aa5d3b74bf892d679e656e69b29c3742f10244f0f52fd4747ba5ec091f3616727cc165c10031a73ab6ecbcc1e5ff1031c1ce7b00da049141d30494dec0a10c7245e2aafe2c51d44d1e60482f670d7ce1bb25e9b6aa6f5338ca94d10da64bcb5f805cdc2c4df973337ff1cedb1911ea403fa607705a64c0c8e3e8eec2c9825772e645204de6cb346458377888f92c71254426a194a1b9b2d9d849e52639c1ce72bc49d867afa6e19a9bef96fe2218050d61de8e63d9917962f41e2f50f34faa72841b6ef993032548952c5e7e5ec54924283355bd8997c804b11df6ff84198308b52532561afc1efde69d3ae0090607d398988da7a615b1cd49b100f16222fd7241d41bb64d0f743b68522ba8a1ee66d1b0491c5e3771de3f2eb23b13a1bd06012e05090e454357f98bbdf6f16bce6b08e2af955014651d43e7dd8b5ea1da1e72d97ff608c6faff2d65bddb7ae06e49d3443bec7ba799356e037027494d6161722b7b2966bf68171084c1bf2bc70169ab8c7a8af9747a9293c6da639d17fbb572ae3beaa80559a7d859df570b6de4aa375cf9cc5d77b96c95506a1fd47ea3d672a382daf204c1b7acc0a599cf43ec5f62e983b36344f88b54d9bae11390c2ba72f3af7a760219aabfa2f2ca03c914fdf8efe7ca58e1d1888357436af3370ff35aec6173659285160638d897aa1de26f5d5e257fef0f9fc4d981e6abb515474562aa4c0057c477f6a797c9185c1782258731ee6ddb0d2b9aea5a4d9a0f4e953c7281b7b5307d93944bc59fad48a34791387ba57a90119d8dadbc46c79a23357472bdba70e4ecc9d3584b494c0ec4ae0b279c868759eae74508868b66caa9b5e072bf43dadfcee4fbf31ca5a7afacfee0ce0ac0c74a29ca9241f3bbe93bf59c715a9cd8380ced696efeea192a40e6faeffa4214bca7121353ab111363cd3e473f7281df62914f66211889ad0485faeeab9dad8eaf4d1bd429c55dca83ea9f352f72f1f412f45e1855f68ca7d45fd974911eb9a841e48744a0ec64febba79bf0db729d255e266faf9894f2f0429b3a44583edb573f38d8023af334e08c372ef590727c254c41e6ea525b2ff90610aa552d07fce4b2c561ebd21a07952221d01d417262b04cfe8c7a787a9a18c3e530e45e04b03c7b1f5f077f983d66b598bc3415720e631191695f48a9b07c457321336b4ee908e8b98b07c5447487854854f96659a916e01b361f874a6db194a2678fe991995497acc7a0666c5f7adeb57b662072e2fbfbe122207a6ff5c35acff05fa6c63101fb466abb6736d194c62e74173b306f330171c558f57c09dbda62ae80f97702a5b61a5d59777282acc5dfe7254272554da9add27f097035d390b5c47fcb7555b7c8349d79ac40d7ed6bffd2864f724ac4cb4a084a525a7f30cbcf348a243fc3f25617d73fe0e186479766564c65721b43768e5b54246b62672312dfde0dfeb248f14bbfcc79740e51013842c8fe725408a2cf88341f6eb4618a91a0959781f339b210aacb6d7f776e90beba3b733a1bd64ba996a889b983f5d7f28e4be6b9f26866e0e73bb188efd100f2ba0de155a363110c9da30829789dd4993219ba26c1a2b19ed1f711fcb7dd92cbd6f1dd725387a7bcf78bc4d0381da9e116920d9798c9dfb6ca40031c40293a5e58e44823af772eb904ebe282458b7f8e9db55d06e6d4c647f1721c0ddc53f35b5132e17224d3da3e71ce471369118902eb239287324b87d390baa9fb14dde4f6a69ddf1ea9eab8236b5bb4b943bb88083a1dcb5ad2bc9e48564f1e11504640962c83122243bc248360573be50760fdf420710a7789d9161d5d4c7f08e2772e46f23d6072b91ac8fdccf8303ea703e8aac5a0b227a406ee0f48dbb559763b511d75615114a37be76bd146b5127e8cd969e97a5b37bb50428074efb99dc0bcac80055e5a726e9802ba3fdfe6307730567d4800a4ae09dd63296b0d6e2724489bce020eb152e6f2e94046da803cf87947b363c60eb25dad2c2fa68da799dac529f4cf821d7235672ae95f5e35eaf8419a8d0398cbd2536580dc0e42c07c483de93a103e7e72b73dc1308619c47cae7e4854563f1d0ba522d9366d8e989421e854173f4d677264be8aefff087b242d65bbfc78f6fed25ad344f47d53965ea06574ce1ebf295b7b6eec7ebd1503486f138676807aca8f77823d520e86f86686f50603e54c7672fe76b38c059532b7b22b7fd28c3f471db42cecb87488b9461ea003515f0cad72cdc6ec8806c23eb72f4ddc752c9a28c268ea11e76a29c7f6ef3b99fb04b4d1525b8f806e5c11b85886ac3769559d941a93ed99bd7ffc3d23b6ff7fc64160017248f2b9f0ec3f983905acbd4f1df252f45c76f7036214ecc66057e3f965e2df5f7fc9e637930f0d6c9d84106598d19abe8e3137cc0d764102e0646cab53368f6165e83234d14eba631271b935b0280fd7c83e763ccad5d57e03507226594e9372fd19f2ecead76b71042eb8e28c9e393529c804bd48e7125fad56765a6d5206410f9b89d18120d192520ebb14f767bfbce4804f0b154615bb153951d897de92723d07b8d0e2c35cd77e251fa3c9b5400d429853d493c3794986a38399ed942b72b91b69b889bde5e291989019933977b1d8dfdb7c92e7343541ec0182e61c727283eeda6d40dec652661f22210cfc418b2dbb615cd48b6e5ea9ff885038ca105a2dffe8369897218965a9d39c7a9fa59c1b860cc9ee6f2e8d08689edb0f4c1f5074f6f05811ffd53618350a20d9c361affddd1c27670eadaf7b68cccb6089921b5aec0ebbab833996b92364d23ef30ff55aa51937befbe52571f1b51ded68df72ce1b95e590c87c626caa74fdf37a7dc3532dbfca9ac15685b01f3ebdba77f9726ac8b7307277ba7c598e10ca26cdf49cf796210ed4237c011e29f2de8891ca72496a823d7661cda1072029bb0ddae516b6ef2c3c6e501dc9ff5fbcdefb65be4cfe7397f31389ba3f2c472fa29ed1e2accaddc74f412c165d9af4a7eebb37ee67b7f4f7b82064431988a9053113ecd2d90a09789e2ef851701744e72521aac365920e9f0e32db7caac150a86b0b388c0a11a8d4ce5d456e140d2dd09e5133f06a2a03197419205fe9ab626535fad09335eb1ebd2d849da89b0461888d74c9de0f8fd525e054dc5e5e01b773c2bf73aa9cf8f9be26333a6dac03d42363a0655372a113df614d0b1c52d6965f38ad43d26fe540a4450f4d66ca6b58b1186a07b520851127d8bd3708719155939882bfee272e34c06bebf72d70527e8bedeeec216969ced7aca8d0ec02a8d9e1e8bf7c7c401c0c865fe82ba2d7fc6515e0c53463081c0fbaa3a2884bfa0ab8b129e489b881bb71849d0f92c862c3a46b1cd60b9b19d80ed77867637d7231c0619020b60a738e0893e7aaad30dee3cd732ae55cf272f41c555e1c720eaebd26b78cd29f31c7e79687eb2b27769fe9b17242bc597f72873de8009de7d2c6de592c707828f88805d43f539b08d1c8a35b729c4cf318129aee41ff1e8f9a02a63100d96e0d0c028d175d7fa170578c65248b43645b062d850a73ebf7d65d8d92164d3d743b6e9dbbe87e4899470e0016440db0b553d972606cbd50ecb70e86bd81dbf6f23db1b4061f1f2cd9508838d1a4bc9715f93e32d23f403eae227a0b68c7da880114e41fb1fa50711d6b5fa279a2be30ddf4ab72907c7d4ba9ad63767d55df2882fcc4f1bec21b0cf4ad66276d442e34f6d9e05bd2fb215d434e83125230ebd8a478a71e04b282f6037f7653c768e21ba3eac94b78bc12d44dd9ad08bb74d81b2ba0c1a3693267a20400f804ace10bf3f6894b725d98dce8b2dbcd3f315b03aa7824e121a6d5ea08d9000727ca8a1af12478a7726e6b499e348dd0bfc1f2d370b8d70cbb1323392f40c6dd40f7e99d0621dd8972219b72dea8dd7b359bedbe9030574aa438900bc7fadad1e3485a2f7d763260726b17a250f6f12dfa8796babec088e5182779e258121ff10527fce047f09fb65355833b162e56ca256cb68e2861218c74d00f08618289cc93e162a7758f3a1e727ab356a609192f69ff7e0a50df0a92236c8aa2b8b46a3ce6a4cabfa7fbb5dc72d383cadbd769901c6d9ba68c267a376904aa0644b7d9e47b67201d50baa88072b35e63c1a916c68831c9ac708a9c609ff3c7dad7c005e115141e8088ad24ff04c4c740b423f84b9850f9c9eae8685d904e651c27b655d5567af83fd141b787700b85bd88c0bf12d82a34d46090b4ee8f1389c3e95fbc6fb39328762a52c4841839aa11a2eb7fa4f8eb489c3e69534b6ae995850bcb4bcdaf8bb0f887d4b0097266d1140ab312cd9180ad242baca3023cff9065137a74f83db7891a9a4125ea3c2437da3101a67021269854c6290d6cb3ef8a0724fbcb2b75a14c41d2f636b158d3aeec4da25038264c0e71880ae9b05e173e152d035cadd179181deb89e63058cfc391b0c200284289e440c9f3bb350a628b4231823f3ce5986023bf071e8b3bfbea68eae1458f1fa0b91873c489386d1520b706b1c7fee5ddb9da81fe97cc728e1ca4b297d1f20a3a51c9c85dad6912190f94048a83281ad21a444a7c391e7259b3848709e5c46c137cc30ba2faccc3fd2edd77bac5ea15137b49a665ebe4729daaf273b4bda8a1f6acedc545e89b20fa0930bbc2ff4c39801100d05a444f433386e4dcf0c03c763fff25720f7ac8ffc0f7b27ca7569ab49834c378cd780d7292d34a4ecc26e64e9f595b2c0aaa66bf303b73aa719731229d8b347705b85d72a7110c3c24238fec67b7615c89fadd75e0f7cffa86fff6c37d593306de58a35353e5d1c77e20d5fb2e1b333a98a33934e0d9d2e94874da6e67e675d9cc4e63249059b09ba2dc9a9bdd687ee19df8d34c5294a8016bde998ab9de1cc094eefe2b29ef8f959ad71c94520d078fef0f546b0de66441876e3e6a13a0452994f45d3cd81616cd8ee58b0fe064c4043c219eec0a1d3c4ed7722398d12b706afce25c10696e50810b95eb801e7b2b9fed566f8e0021938c61cbce5352611d3e914571725200f6ec5a972897c1024e940414fd05c335c97b485b3a4d3c7a1dafe2a3087296853ca1d1ba260fff7d806018fac387185ab008e4fc249baf8e68083f7d6c727de8bb7daf38e236625c38ca22b8cbefbee3adbddfe32c432dc318e663b27d729a16596edfb94f2f93947d4db73b8cc042003ba267fd192005c6fb44534a1572f80164a5392840bcfcb31d950058f6480717a5bf185f39e3fa3417953b26dd508fe612dfee11c037609f07a2b32dd6e027b0e424ddffa0797c6d345606d9555cbb7dcb74771e739eacd2fbd945d18c8ba847f9d8b57d7efb5755b62187c84e72220763f773d9adea3c6712ae67048fe11e5d8bb0db9c66b2bbd09b66b0573272366886a3542a6d8ff22778c54f56610781668efcc9481690383ebd0aea5318729435518d0bc7ca7b58b4f5dae10f1ab3725ef29cf3709801321f4cde3f9eb672a5362bf71f72906dc1cff30c730d84615e9cbc17d6a29665808f62eeda863b57771f7299b40d4be5ef75e9083b3a6c1165e4facfba0c0f6d41c15086e4a6fb72dacc48d87c66be383b6ed7a22bcb01bdc1548f63ecccc3f5f1c12a37a443677287210a4a273502730b08b3e62cb73f0b0869857a4a5db6fd5d9188886633f52ef1d0bf5fc4d8fc69f5de53ddc71bbb321609835cb2519ca350bd0997bb14ac0267a1a3625303fc39d4d2acedafccb3409af196bf119c4ddfedf87d5baa7095415cc6c3e365a1d44eed51900133c619ff24349fdad8fc607821ded032455e010f5c5e25215b3b04ff4ae7106ab76f17ce84f0be822abb1460cdd17a600fb798725b6bd9862d8150576d1508d38b021e3febfb4d24a66d2ea5f2487646d390272e1a5d1bc60f83f1c5b59a8a9e0d3433a5cc04debe3ec0eb0f286d6dd117ce7c72f3102ec89707719a6efb3c6c64b88d275df647da6de8bbb5561c061a723c3f726e53ff655c6f260885fd1a9e2301f2c523f14d29113883d4987df7237abf4552da9868e72cd09eae700eee1635712a5a17254b82289f37cb78fca3276c1ef572b7d495a24510396fb895a240ed0fb62728593ac2b94e1d6567355dade89e2a3d53d2e9ed3461e457c5fb3e692832ee484d0503f6a386d78cb7e49f6f275301720aee87dab2005c0e1955fe9ac3673d268d517ebdf8125d7cae90e89cc1377c729ebbaec4cb9dd5939ad29ad66151d3801ddb0577d0e2b16e6c24d1ff9347f872524339b87850a3290b39c7a55b28c7725ef7e2ccb45293ed93ea4a5092eeb172d471dece4124553182df8466f087081c7b09dfc6129f4f1160e4d9092e2b0072e92c5a2e9489606fd509a22db1c153d67e6b0579a286e11377f7bfa31e251272fe29127e2a3cbda1347287e6942fddee39b4d1ce33fa93340b1ad0643c05323e66ff8915638aafe28721b53ec3e95faf033585ec2d4502e40d601da69d8afd1791b158dc11b19fc480bbdf14cb41bdaddca773814ab55053eb8552a12fc6f93039260524b0cdd1d1141f2fdd15e32f779dc850977666a4c9d1842e067462ab72ddc25b18927dd56c52f01957ab800cf2cb6715d84d0022360a6cd9a67817ac42af7e95fc7e1f59daab6f7271594d31606876c30e4b739dd919d14391b5f8715a358e7d10e80338595013fbeafc938065e43c0c7fba8631e466b756efa8e8cf110a9cf2752869cad640552ebd5f3cbdd30b1d82c9c335b113a3660b6a8c9bf92f4404e204d3be0fa564a83b429fe2d38d6c71c7ed22241d9f058e221ef2c3e3728314614b4d974fbab32e4060674ce877f6514a52892958ffe963e755c6584b7289e811e1145740851fc0788cfcc1b10c993caa2ed1056659f28c8bb8288113724f2ad1a1aa58116bd0803c15fad1f0c99a695cdcc9f18fc1508166de0799ab72dbf8de349d64aba23b0eac69ba7d1fecbec78993857a61806df07265ce6fcd598a33dd2d39ddffe33f26a4f54c30ddbc5dcca945906a8d32074dac8344ad2d72d69b753474eaca149631c1087ee490259f321b31ab1bccc9f1075374cd98e258471b2cf75717cc1c98d50318fb00d3107dd4a8f0084197a7cd6602d98024664613b5541bfbdea296d454c5362135dffc786a4b4febc2bcef5395ffaa69518472f1ae023525d8ed8fa6a0b86ee123a432ebb8f4ffce3c9686b838606c07bcf472a216191fe252c521cfbaa8a33bafc0d428deb42e769925256b988fe0b4265c1b8ffa7bc0e17edc546440ec3809eddcd89cb427a23168f33103e6ef28f01dbc28e6d79e6a2ffc04f7e103ebdd0b031c55a88728bb2d3d6f7237bfd7d3d8ab54724651403c2cd59d254990345d21332c3fc3a7f23e0e556f756b64a4cf15ee3f720a26277c8cd4e9e77b3944413cbddfdd1c8300b6c0cb0ae41ab7c87e03010a729b7f36ca36178b4e8f40bf54133380d499f117860a3c0bc85b96fb4542598b725129b2e8ea7b4c5fb7d2a64f54b930d2214cd0366f71c356cb38428b39c99f2d30f71fab823cd39b55029f15e142e82346fa8ad50f32a6d6fe4ecc4fdb852a72baf38832f1a278b24eae5754dc9477773cfa88fa6d74eaccf5be437a1d5eef729c56e780c7bf0ed20e65e00b568f63f2b354232104de823bc468939aad5dbc2cd43f51a719aaf3d658d1aa970b74af0007dcfcc491a6154958a8e3ee55aeae27c8833b0b8409a74786617f807651cb7818a7b9e69753b63379fb58b11677ec72084ded9cdac1776f2ff5df26594927db59fd15c8b42ec436030863778727507231f35df99114c4622b260ccd223b91bc8cd31e9ee8931495c98b600bff3f0f452e20ab0d6ef966a789d0c17661bf32cd47cd039cd7435c28fc17c759cabcd772b4b4a385439d5d4ec582f5caeb5811b44b2cc511082d8712d01746f2232e43720ee7ea4d9a5fc0aadce8ada0e4a8336d931e893e5f93fff1f446167823f2cb0bf11fed9bff19e38b1ca2c66a6785213d08bbd54bb127a1c35ba47f2be4787b720089e34b5e10f3f8fdaff5fb62243a6035bf6b438b8bc80d7609493f22200f724c423d08fb66f0d3f1596e1891fcb05f0cb71cc8bd55bd0e234f6527ad46e44af34907ae30e5602bae90175ff6c1e198967df5f55e7e93d70720e290e711971eb8729c4f05c43b0c555eab79861336e4547d176ba2b83f3cc07ed580ed4cfe725d4d22cc4bffa6f94c4c8dc987f93ef69df79b4513305fa6aae59b9527a02e7259fa8bdba7ac2ce809b8fed7fdd4a431bcca7345d390d25ca5c6c9fac4383b729846e8faa46a616d8925b23b1737b890269515712c11b4a2c382c0f121086c72995a62c906ff5ba70b70d30846c750a903b32efa13969863e0c8b2db2470567262f6348bf5ab221c7311001a5bb63c5a351b2f48fd8592c0d916421c5f61a36b40d676fcf0f6847dcba0e8cb88ba7738c8471dae93aedc62c60d77c3bcc5c1027a2e4ba9f6f85d0947c832f64ef7670a12949d27528ce5467fea348c28f66c410ca8fa175b807da04d3b52b31770ed9722d918c31f13b876a04d1a7caa330772eb6420c8b6d02f3df402fcfbc68595ffaf915288b8a917d3f05a27dc111773515f123deb11f49bde9e38949001d780c8d7297cd1d0e07c9f98a5cad3b19d2172032985d9e516d0e5164e70860d844a32aa6c343487a0fd0a381de71e529b9372136f997cc0d28dbd4c122459e59816978ab68c169015e3a71fa3220fa99a4467915c650f1677c1f76bdc90a6b9118c03450be39b5255acba6d3052186550aa6acecab1fea330f47d543a25f9eab45e2df376a9b5aa79a75423a2a1b52e4ece72ce50dbee2426aeda43623f32ea5bded2e160d7101166a514dd51fb01b3eb7c724a81fea0281c3bea8b00b752d8d990c4d8255eb695f63bd25a81a5843d129e43fcc09f338b32661b6366b8f9152064c85ee0b5f663062ef46aa5718fed7d0d26c1c482f19d5e6edcb074f20fa59e0626ce192e63e6bb5303c73470eb1b7ea258a9c5e9be9a1499bdb401bbb334a2a59f80b3284d622dd3a852872dc31f572872ed40605cdb37ee7078259f05b5e6c7f3361c640aff2ceac0eece2952fbafcb720eafd6a7b1aac53df65aa3bc5bef3be7add253bded83f13f1af0b835b39571727eb3d82a4e2944b422f6533d3bbde60db2e84b92c20339070bbd452a88bf45378ba9057c8a023c7e3745bf0a7865eb90779e98858fe6804ab66148bf6bcd4328352db896bab0da7dcda6ca1437a73b3951520fce4dc897861b7d9d4921aaee0e44fe173469e4e03462505482d35fc3932c36af201f6ad96bea9188621f3db83786bc6e936f0d962404aa9c58cc6e247f0010bef82ab6386a8c544e8fc50e9472c1f8d84131730632dbb81b1c84d77e2702a87ebe904eb1c1d57cc45d6a446e1d2fd1a3a39430ae1aafb9c39d00203fcb9cedcdd6f2d6518388126d18adb5663be8ceaf837776501f175861a1d06ab35b1666bf48b4e2ca9363f0cb434b746e0cfaf9b2ba7ebf1290f8144ce19baf2489ea864e59a21bc489ba4ac4fdbb28ea1e054dd7240552988867bf3725c5723fd54677dc0aa36ae4a6a096f43c20701933aa8b01f6052b8e1b76a73447e794360f579c53d35afa26a5e013b607b4f025721bc1c501f94d9a977581e35c8b6d0b352497f6d4ea88eae8afebaa64fa6b4772ab6c8b1c1791b55f0fabecb20dbc3751097234362025325456346c3f87d45e3e7c77ec57af07a4cc91151c190667a908d232f098afb3e27c56542ff41960eb72e413b178bd8a257e0d8f6682ebc459ecaf51ba17f1ff78e7f0e90bed2feee072e64ab08e83483b5bb4bb56126ed71e49a933bc001513e479bc4632037f03d372ba0a998294f091ac8a1fea72e4d91fceb89e8738d0acb4cf91d90f17714d8472aa727cc0d3c0073e3ce7be48cc4af97d749134e1cf7270d6991483d277f62172710b27a46942f809aec09c38399c9617b87056b0593137b2ee27e87722e5435f4970bb765c5208277f23a2af73700bdb87ca597e6e20252d935524257cd2dd72868a6ca9b42c38a4ac80d9036798a527fcbbee6e8b2938f41119c98beca2e33cd368e6648e6df056f6d273fcb85ab36f253b47426fc1b25f9437cfeea6c72072b317fa3056b0605e50f7fa26363dda6a2f3a47081feb4f6f914129dea59e4972bcd836d627309c2087f87aa622e4eebca245da230012d38915994b8f50265e0df115fa4e65346a4edddd768a909d5392094c59d7d1cb3038919cb02fd1e01e05f60497475aac762983af710089fd70a66032eaf4e5d0578afba19c88c76ead7245e6081404e92d1a591c03c37b974db0eb0be31accefeab002b4b116e36d550a3fe347ffff586c246764057bf7cf2eaac50ff94dfdc33e5ea5f16a0a6c944d727cb563bca47d360401d1dcbeedf86cd7155d89efa288499223c4290655275b2e1046ab7b799e8d887613900428dcd3f936c4dd5552fcbc0ada34bc02dc53c072f636c89c036346e81ba5e36b8352161416379454512b9acf72b04869e0979164d65f688f308234508cb7bb9b1ce989da8dc33e6343463dff39b700e7a131cc722b9574c9f5363852dd42be02631d504fc6600d7948a5269fec37008ecc38062bb880e6b6dc38427e631dc537bf77617e8e6b36dadfc293fb03f0c2e256ca22376be23f0e48ca7e737dcfe72c2178850514735e75c9b3d5a01ff42e58ab2a07724169a73a61819bc188a7c09025fee66060dd9c7e964580b1c33b709d58fb39722daac33077981726e1f3e1ea7b72bd5fc90dab14801fc959a6c16743744b2f72cfb8d24a9a6dda61c7ebedd60ce9a5a604c0cea9e9d0bed2042ec297dd103602ef5646d1bad1d40551eca9a07155f0544e1f8eb2fca6051030fc1f35a751c3725a2f6e10d95b37fbc3b67aadab1ff3951efab6278403ecbbdb4a1c90cd8b4772f239a2dc49fc2beb55be03e411ee031f35202a811e86996d99788b116d7272720a1cca425e1befe689c167b2946aba0d881729554b97d9e1a698eccbe37f4b179fe2bba59400975df37d679d96ea820eb49267dadaa5a0515aaf0f64733e6a36be5b6fe8ffd66c60b1dc22eef34cb35e7cb14fc183a451ba0c33897de0168013683f67da90c37320f39ad1ffa6e013d45dd3e68e0298321c9b27227ff44a6b029c9b93641140ba21a36754aa8d0bbb83f70830f0b49ee91ff2db058557e0e17248dddfda9c1cd3e86aa68421f1bb5ffe5151c02eb62d213b1397776d6f5ce57224742701994dc19cbcb412c608d5b89092362e322a987fb83a52b8e27f5bc24e2895600b663332d5932587b2b51353b3ca14a37d9e5eb81145286defe417390d5d33a73fd05dda5a58e112298e817c37bcaac4751fe7c5ec964960aa42e2187242d11ac2e4e38b726c4de24448bb5edc0eab24aae354a417b83029a557b6d431c3be23326124cebd23bb5e9d95603761f634bc4895f85e6ff5c2b1dadc509872c68c4d9257bd74827cace24f5db2d65640b991c5bf8901d57d310ecba1bc8772bebdbbf8527fed8fcd94bed9c892b651e1fbd6158b11d486fe63d3491a863172f5628e021164869cc065c7cc2926c9cf784a230e7f057d42ce14e528622c234c31404ea38d0c4ee5f8fe2d498a4d05df93d8ad71d2848aa24b30a2fe13e9717218a6285d47b8a20114a3c3d7a8cb825bc6e6e2af663e7687253744938faaac7262cda3a0cbbc9f2caeae7284bee230e64b15224909e71502dd2b0689f82f2a7207f3a577bb5690b0b27c3820656e56dcc5d4818f0282a61346933cfe701389479fa7d50739744a7ffb6431d7cd96c670f85cafdf0c5ab56035a2ee2399d83472748d6d2cc351c4a26e1ff45a2839d045d1826bff086f638064a20e253d36477278fd83d6aa40b82bc1f2de983a32dd3cbffe659c67eb216d335223eb87a1f7194a75bd3f6d1b43b22194e30de5b5bfcd88726f7a07196ccfa8b08849a1efe7720847d1f87318846702ba393394afa0d91cd5e117485a488ed3242af096225d725c19544c30dcd276d9de9c2ba03cbbb57a2521d89b5d49e8ed3e9eafdc0027728ee028c1e850950217f046eebcd2d0c4b55e03520b91cfacfaec1f37479c7e678e0edfac80dad203ab75ae544b8d287ed6559ed67f6a14f486961bddd1ca79281de00e1d53f5500c292142657ae0b1bb6bb8e67ecd0a0e1c033d4f3f9947a5725e21a648364870886bc6272d09fb8d50c65379819b2dcb9274b6b683b0c10635ccadf383a384615ce64eab607afd1a125ac4cbd0eb54c73082b775adb6f07e72a2837a185e8e7055f3d5a94b21b5f5655cde11e4b3afa0a4430ca8f16a85d372c74e469f929808722a5b7ecbc61e4bd05eaa0c2d31b9deed6f1e8ecdb0127a645e01e3b881ffff1c855997a664f28374c5ce4beda5a0bfff14f76c231e72ed5f6e477d3b0cc168ee1921302ec82ec834e3399622baabcfe4c9e54e715cff48720ae045dcb3ac827c036403511af648918d7233c143562c551c48ed163e1c39578a6eee8723ed8cea930f53bd71b8d7b3eaab93d21e9d2379db9a85b51feca2729ccf3445838ccef03c6109ffa960b4af452b5deef15f2a36d395811ebd3e7b461c50dd53c41abadd777c622150fe439debc3647ed1ad6a4ca6d35c25fb2c663bc768a8978e67bf82f55625f36ac99878b76cd4b5543f8fdac93aca143196753e2b821b6d5aacce2ca0b946b18bd41497d59af6f1f39eb347adc0923a8f8c33553b5047b83be5229890497756d111918335f5a7a3bf569c89f931727adfccf2724b2658f57e58d3160b65406e35d3fcc38dbed0090723d20a89df29103249bc4c6a1df7f64e8d29ca13bf2bf0006e610ba0e48cac348bf2422bd993bc475f7e723e633af9b8fa8ee07334eda2f7fe989593066d1548cccd6319ac0cf7d3ac962dabf990c77251368fc7a2a4089f6550b18dce7ed9560af05262c3a67dacda5472144ad7a0a2cad55450f4b0a29c4bdedc1b429619a929774085ac74e529221e4d24d3c79d56b573b625127e6a38fe0a011e783f5ef7ae53b88f78138a84530872bb979851e6d0655057d4526a052d211312cb1b44e773a56a9283e30690bbde72a5a8bc872582cce601e7bcbd39cc5030dd40774134eccf6b2758fd81ec369e14edc9e965b2c7406045eca92a5aea1b2148645c7fae2b3f1b040fbf0b4c8f406c2a67b2912ab473adfff384ad48ffef8a30921a2070feafae1ce00514458726724c559808872531153505c51d907099affda647f209d714e6a0c63de22250ef0c0ce4045930f00bdea5d7bf5ded92488a98680fcf71384b0a555804cc37452b1d2b8817402527e176222cda086d8119c540324cb67463d048ec17bc7827a19d72810328144c7f4aa8dbda564ec6f22c5a9dfdbded3a95504dbfa0b0d69b1f575680ebb5c3852780a5d2446f9165d8d15da4d3067fd844308cad02f3f4b2911a23b6dbe159164f5b032a0818ef8d143f88fc68af7981bb7fc0d6b365cfbe18947212ed0db842c4cb4696ff1b06a3eaff9110fcd14665e2dc932a725c00a3b8fc72dd2ed2a730a928c663fd356aacee9214e9c2b0274eaddf4c92ea6e7b4202e543c8c891f1900e7c535629b6dacbfbf25b2d1abe489cab5a4f282669d4eab10672095b5dcf859968e7bd7c0ab5bf27773039b9302ebeca001eecc8e2306f1d7668f977fcf05d8270a872a8fc218d85384c983ca886b9f1df19ac0501ee5ee0875e4a746f1983fe33345edbba9034bfbbcc8ff2173fd9e8f0a378582cd86b817472a6b36b6cf84011f568773125fd11d92769e08ce55d04895125545144848c3172d64a4a566029bad6f16ba1cccbff53b454f8b1ac6a1ea28b04bd160ee9f35172425a8f3a4e5b7d90fc35ddce815dab09af509967405e779d2e8f0432ed27b30504b161f6f63a13f31b70cda13dd0c30ff8e832813c4ff51a9d2580111e75ab24221bdc549cbebc50e44245898f3c8d6242ee431ef6cd9158023e79ff82a7f647077b0fc67b163b41537c8c118ef4267b0dd445aff2efb21b3408bafb0d8ec572e1a6b3f16b434024c219f87528722b594cbb6e7bf989361a371ec6f6127a595ffacf2c931026fae4a40b145d3894d79a72d8f7ec5e059aae16f91a22c2c0167203ac65cc07947db31229ba8d7cdaaaa9bbaae80eebea485d7d5a2e9206fa7d726249fe1e144f66b332097396d99ac1de5312f4df20fbcf5286ba70b07370ef729e416a3421746b184adbcb5b701daa486c8f01bb455354ff53ccf0379e7c8a72b6591891d576c6974462a68cb5c9bf4ea4d15816dae2d217c8d72fe92ec65872cb2ba25eda37b112c9f6f55142bfe72896b93b204ee7eee6aaf95a9c065f26727094e008290aa1950c7f3a9831f13cbafffc49cf90d91fc71d39d2e9c04b5d460ff85e7b83515316fb745b5d79ca8b5f3ef79d32c80b58a78d66c2af4ee9f972ac5eb6be312880e58b46f0ed41d9dd6c9ec983c217f7cb83c367741a62209f729442f5f6c9b385a1e1e0fd096532174a4ebf5dd69579ef8b5a3dcbe3bc345472591921e59b5f6396e55553e87a38434eb3ff788f4a145e4af46c7b084700d4727531af6826f268302aa62c8bf0d57a4c08a2b7e2ba6419a59ed59d700e8b4872269b7dd7bf2fd3cb60a4ab229de93c56f913e4ee62bfe699c2be7977cb5950506e44c60f6feddf107a220bcd1fb16e00a30c19d414366d0798e9749c916d86720185ad7b0ddf31dc8343bc2be66d6ccf9e059fb480086a7ffca89f32f7bc246779a77d5af17517c3d3769cca12761e246426f1428a77d2f559db4c1a49339020bc1eb6a250da8b213e05ae252860c18276500cd841189de07475ba6af13e061066b6d0ab8a5c7f319f121e0be2023a86e1055e163b55133ae4f9d92d85c85623f4d0ca67feb900886b62a3cc001bd32d9a6ee9dd24f1c23789ac2201b2b9fc726c80be5e9aad8495e8595efc002bdcdcdefd478fe1948c96565b6f92da2e9172cc5bd9de9f8a568ad912b8b1d552b2dd74856e4df018cdf260908edf7b1cbb7213a7c2953a42034db66eb46bd60470d300279588bd532a46466e7cea14d7947236c3b2212c4be1349f13030ed2bfc370165dd428ad695396b3abd57f3b71db17509a86d17ddc34773adec75166db52d273b70f0fbcd7938306dd1f9586e85312c054782e960f764da369d16dc1912b6888b1dc08b0a3c248c7bbe1273f15e8728aa2debd0a8f27574d7cd8ef1db76fbfc28448fa01e014b59920cfe6ffd56b72f02b9f7b97efb2cd8fdbbe3a783b47eecaf249c3e5f788089c81b20300c51b64dadbf205ccadfc00d0397ef8057631a39f40c17625b313802b7a07df7ec4665e1601f99257ecfd7334a25363a85d506a365e327451e5724ee69e6e76383a6d6aec56a4492a219167738e36a9cf8f33fc24b2d4997902a527f16da6e4ff9b110b259621a04bf1cd682e24d661086dfca03fd10cbbbd5b21b42093f0e75b83f4727b91891da11ac7048c624b575935b30765058e0fd8200117c8e09d5ea64c902df81b975b2b1a379227264777be15c2433ab69168367c1761a4c705de396bbe72ec921cabafe79c3f0de96b1f2ad70d12c467b39a7f4594e4b45cc8e8e9ad381cf599861de5a3e1b7b632ffc94569c14d0b1bb36b8da14e1f1feefe4e4ba768722658ef8f7b74f96acca411397215fa5b60774179cd8019d6d818ced2a4669657921aa0f469bc0a79eb815041496c949854e08467d599dea3c42db3f7385ab8722ce427e86464efe705de24cbd4e93785bd02c4d7688960701f47a5912ab71c26715e57c70e18ad5168bce1ca668b22f2f601be6eb9c164690038a967e3729e729049ca4ad5bfbf539b7579389db29dfdee005afdb8c5619cd9d3380e984f3d728b2fdf4b7747513d7497782a623ccbbf0643fe052b2e5dfbf1cd1bd79defb37274c99070ec6f7809ac3f5093de237f38d230863a6d191192f013a2ff56643372e3e352cb3b8741f684429a54fc2d7e09895e2712a5a7f52724dd771010b23772017a5cf3927472fcdd7e280c984fc65bfec5c63bc80c83c0e4ffb5883907a472f799575720fdcdd514a683a198cbd3e79d559f6cbacad1cae911daee3487be72da91d954a4e0fc6bb20fbed1f76e6c23b12c27d49491d623e8e801456ca12324e44e0e2e740feca7fb2f38356544bb173f535c5fcea9b5a2acef93894056fa721aaf2b77c7ff458e091595d1b3659564fdd750db4ba44b7e4b4f72e0d6324813495e54f19bc820d077f5e6717843c2693d7770ff41a171170fc7882f684dc2265e0f82d2ff14ede4500691526c0e5fa03489f874dd1a5c0090e27b64e44d4c72087b9a586e1b679858811d5a953fd9730f7f43bb229c9b70081c554cf62fe0720144b7baa67f79e19df9183771275febeb456a217b51034538b559c2b92d3d72e04a303b0d60751c8bea738246df72a167100f8be8b886920a318e93f32f0872f8ffbd7adf128856cf5ad0efa552660ffefe540733a5fd3b6f0a7c435bb59533c4e77fcff407adfee257de16c94c6bcb0ac55331010d4d2fd3f9d5c8dd877672863148a2e860405342f542ac07ef7863c5d11fc36824954df646d198709d884bf37196145e075ada27cb51a050d53a63089e6c2f5eaaa5aef2fe62023e7c806353433fc73fbb7fff5a05bd57717a3490b967432e55de5cd9e79f31a28850db5349d083238a903c777db31373c6a6b6570a217c7701c3d003eb10291ef2b30c72d58363fed1fae31d63f90edfb24282db272f05812d8b797e90e49b2959c6ce7299a44efc5008a37faa574c400dbee16a21c5c6ca38aa379f149ce0e4c235bc72a96df7ed7c1db76c9f1a4363e77d2f964ebe2b7dff5b0d1c06fa624eaf950e3b6e3e096929a091ad296bbf3d47f4f940d673ea5d2b7966a28617aa3c3980f7270a67d1bc368a07610d317a3c70ed65aaf403f872528bfac73dcefc4a11215d724c5347f71cdd624f2acd5d6a641268ab8a26af71f446fbafdf3cf5f09602ed0e3970a8aa3d8d28d27e1d2e8006dbb917aa709320ae2cfa318edc2ab0e33f54723807e2ef051d16e487371928ee9cc770dc6561ad2885982438ba8438ff203a72c567e40f9040ece55dab8e0a8ccbc8a93f7ed90517e64872bda24641785e9449057d4df9037ed147f3ea993c7dbc52f590285be4728857b26c986eb427c7027234af1d2f5189d522bd5c7c6b4ba54fb9a84f38aa262c425ab8962cc0dd023c72f46d9abcbfb6692a5d6733940d9615a6d05290f9890a3e0c56414a5a39ebca725a19d4d90c70aa1b2f9e225822b9b9b9e95288d25cb9306c18868116d4553672826866c6bac5d44abe73a0c6c3257ce104dbde53bd6d60a84e3b1c5991ed0114a4f07186f004e81efb545311090516e40235c04a4779918d9322b568121cab72b79be9a7c82962aecaa21de0301c5c4ad66c1c75b4db4c1235858925c6ffd511c050b44f263d95d45d54d756697ff3c00a94765068c190573e32b2cf3346776e28fa1b60dff2db49eb43e330eef0b68224bab7c246ee23ce55ceb3c9279c2972c53d1935ea20edd339a32ea254c69cc2eb0b6d94a3b36dc29e779e1cf1a90c0f727749a5ca01b449ce37ac87060efe4779317e650e3bda03f933696290805036b11ad1ddbcf0b42db3bbf0536689f85c6160616e842d2ccbfe63565c96674c728473393a72903a717f4006c06743c97905c75ce88576bfed3fd353273c6c2472301ec6e19d1d8ab7dc29db11f7cf59e0e403433d569dc09e77a05271b0cd0b72a2884cbc565f3667abd6fe43aaa5a34a188cbd36cad44409527bec1effe9bb3a1d296c70a8821deb032122731631dfbabaf6b2648e3321651cf57729ccf4e829ad742b6c586dc516b0a965499120934758f36be474677e2f4d3499dd1d21eb720dc1fae437df3226f31d447cc6bfe4962d2c0fb0fd006483c7a43604dc1acd72a53ef9211aba10f16bb331cd76592695be4b809f649141f8b4da59e541b68972aa43949121482d96f7789bad2a5d06286b9ff26891d881505a1526781218ca72926760f0c379ef66f8a6dbd91c02dd449fe69e8236cdb771bd3200c0c701215b2f4a3648774e095945d04073312c90c9dd841d240d7b04f1e8b3be24297f02721d72e7f15c0786886b429b78e39e8ddd1dc211d35261c0677cd1a1f04665b8467a0b14ebdf2938a0ed730a43c13a515bfa8128fa6c2c8bf1a43ca24306d5d8720bd0e657fa9472b5e39c41b63e634497183694073002b08a67b5a15831689f725aae1d97b5de2350024a510fcd8efc3cf2e453c579e96344fa11d33701a40d515496bee8f6bf3557d1653eac943fe1774533e19e8becff8712bb8d6af867405dac690d6441a2524ffce3358eba8c6826c8da707c89f8eca71dcc7946f1d93272cac2f905e2f633e2145a94bd50f7156b95c69669bc8a2054ed9e22a7fd8ef3128021a18a14338383b172aedc685940775ec45473b46f9e5fe772e48d7519751d3990f7b541c575d9ba2eecb6ace7d0328a652443b64b043e8262c170cca5123cacef7d57fcef5ac48833604033509f7e97026c1cfa50c575fc146be09f11996ef77df8a25be63cc355d55f3cbdefed6f8f8af2665c50f7e1f69dc14df5d59f7265dcc039e5d7124f9c3a16fcaee85e0d531e51d556045b6050dcbcb27aedba7289f5084ae9f165a0ab74b685b8a1961033b05bfb03ee1185e69b644cac15fc72f799587109253753ad5e1692181e9d36ea519e173d3b8ec4c62cd6da13e74e72a3e6099446a92ac2a218b16f83f900c8af4d41df199cb723627a8f3a74037b72735b46da42a3761ec24a78dfa4255a2bb9513a022e78e3703feb0d8314bf30721c27dadecff0b95cab3256c2fbb17c33cdaf8554e08f86f22e5e399a2c875b4e5dbdf55c968183415ce11a155f8ec5a3e01a96f9cec84f04662485d6473d3750da9a5f0c23fa4d38b519fdc4d366f6623c5fb4f8cddeed1b940f1a44d2526c03d95500a379626c9bea3e7f065d53f4960ba4a10460c13077246b7030ed2a9e7295cf8c3e6d09f9ba06ce9a8886a7f42cea66684f810014bde6f28897c4435525a41ed1065f7a6bab42c25858ab54406823e60d7a83bb5dac5022e2010d30bf72b9ab375a32d1f074bd1094e9e7f17023613dcfa632d7e8869fef88d989f7d3724a6acc26505c6bec936467c8cc3a2039462e5ffad54679280da2eea0bb61e172bf35178e4a86de5dbf526338f157eecb48914be6bfd86a952f9cfdf0460f3c72f8fca0cdd7da8a46c20a4bc8786d5b1ca5d3f2049f7804f8337047b84e69d6720435428c3cf79867abef0c51a6ef134fcd7655e1c4964af3c9616c6ff860615823f48b69d76f3b553b19d098241e625367525132fdcb260efec7cd789f203f72fc14d5cdb1d6c4674cda37837607ac57a557f7dbe43a48be0001ec4d3fde873798c9932ae405577f20e02722006ad48aa8410abd232ad0203aa5ef9fbe6730727f07a6b14ec2517b79a3ee7de7bbe1efa69aa59f54cf463e8c55a01c8e633772aa965bdf0fdf63914ed6ace5b2bba82fecef455330ef92dca4c30e2c9b44770bde1f7f2d2728cf0c8449ebd86e3d908e5b22bfff366cc982fa60bd819a7c9c72d1c50738598d20fffe9aaadca11bcec86bd5fd0648fcac457473ba526c4919729018e333e6ef40e02f4c744c9fd568e950514d1cd5c57654618096b8418b98721d63a4fc4b08f5841070ea71e0c6ae59f20cf3ecc8cb722c543eb98d8b6fb072f7cfa783b732c230ff2dea32372dc4415e327bd573f9e6c61ee0276c80ecae72bbbf34b6a6e577dc268adc643a8ea8ff56b71d8a6a459f3b6089abcb01f9f84a7de4d1202d5e13f0e2d07ca4a1166fef6335bea772d73eadf495e11df48c1a4da16915b05abe67b4dd3e0822f60acf68be9fd5eebdd04cd0e52f0f297853846896e3765d689427749c4e66010353d00577e616c5428904a1ab340969606906723d24d8516ddfa82a8050db2f8c6a9d35d9a13618abea6c2b739001500f36a215612b78e52764b08c1c15ecd61396cef16954104366bbaad2dce1329303d56a72b13b927e65e6aeb35779096f8a07608232e3be86aaa5c45ead9d5fb0decf122269ec6a1a1240790482cfba6f75a8b232e051be8d259d902f742bd381295c3b679e1976b137c836d125320295a7b7195ba244fb7b13c145a1426e34e56684ea3851e7d2deca464bc06d990c02fa7e1e0d037d5df772f05312411bb5bca3da61726011c09b4b94bc53fa8ab65ac67f2fdd4e40a4b5ec75871d573103d3e0f76172ec90e92ca71609654d033132642316c38976a942d834e02cfdd4fe2beef21413706a5332f32227a9026aba14d55cbe3ba82faeda15a9934534de6e797eda9e7255a22162ecf032176f1c2005b758b266e03c72aeebaf3487694c8de42885243f4a20e4d17ac2efc46d4f9ee05bcfed3c90d4e1528a951c7915b4ecc7d29d0f19f6192fd37a897c1ac97e48c0de11c02574bae23cf59f9ee56e3b4a49554f3f728c5c0cefc7a15bf6ddccab5e931e87d6f1b430a828b98d714a48938398575072036732ac9835b564346235748f77088c24618e1b901edf33d315e6eb0a69302cde039ef3dc0447fee10de0ef775cdcfc26dac1954a38e3fe1c9b5c689f821b6413a7f9fe5c28432f7572021b59958de1fcf973e987cd1865b794af00a89a6d381c8251d786679f6f750a39e7ac291774503f6918048bb525c29cd6654c5cfa72c9f9feacd8ff9a07a1bf6c1841e0b031a2fae4fa53a1181c1b7f804df9d7f27298c972f2b20d46bff5b00f6cfe725334844609ced713f428f4e4fbd4b55c0572e76f6e12a1f941cb50255398ed1d432831352649a5cbbe20f548ee0708de25585d519d1e8860a1b7296d2943d8f9867ce4bafb09f26685bd3c07e162d0b1847279161b9ceccd81324b1bfb28f571367b75bcdccdc565a570f888a15f6bcfbb720ecaa8cb51a67ee5eb4b8d283657d15b6de6e60b46f7c6b10dfe4ddbe5b290539ec2bedb194b0b46b7ef17a86ada51db5846fad808a877575b1d438b3d17fa3c2a172df8b38fb2198583c1e3d269b6518c412cf958678fd2c545af49eee18a7272034078b616a9212a9fc96e7a8f655bde836288b2f3ee16623d30e01e77cd7241694163ba60f9ccb46ec3bbf2ea060ed84739d7f2c8e3116b60827b73f95b72bc4bd572a88735be20ea9e6892c11114509ede775043eccf3a76a9f03c9e4d728f4a51b3ba520ff31bc8e45fd00e63c95a61595f2d778d8902668d646da3c364fc8fca29ba41f66b7bb7ee422958041c7cfec2f7bb775167a5e42d5f0097b971eaceb318e792a439d5d8ebc28a37ddc0dcfe134b04891486c16d3b0a5cb8ba726699e4cab052e7d7d4cdbc1f18701b8bc21513c2a1b655d4e46772d82c3f0e3c2a3373d692ee0997588995ef1a008ecb3248371593c22a9dc9e0d2caf708b7370f4635b9a0a41117688d17688a56be52779c55797e6b67e8953c9ab53811a523419585224eb3e99738541a4d6ed91825e531f8b153ef07a6270047fd8db8bc72017bc8089e565d0ce239f64225656e7bb2ec4c0f0b360c0428a868bb1396577254537377dcee3cebd00fdb9f47ab035aaa84e5a115544eb1ad81a3b1afccdb72d606a263f1c32781056b2442354cf4c15e405f701905b6f0122605f59f2349723c6abcd1393763dc3e590435d1e3233edb116de0fcb6090df18912abb4e62c726208f4b619b8931ed64b203b40c93b8551388e05c35cd9ce5b0a7903386f3c729984de6dc9e08a3f61bb107dde09e2acca2cbfac6e39cc3fd815decef3dcca721da4b98d2778b29e34ad47fb78169493b601691636b7a23dbffa07fe18ec2a68f8f7b762ff666c135b0b40aa44914ee69522df9b48ce915b4c62764d0b33fa52f6f9ed86d35999a6b7850d4afe07f95867dad84dc5259fd9facf02fa7bcf5c4182d6c58efd7dc115e98351065ebdf4d57e719bee8db09fc94aa36a5c6185cd6ac64cd88d064e71d252cbe8238fc20fdce83fa9f4d76dcd4703b3705593f252729c45b92a575f49f503a3e6d267180cc4098572db47b9589bd9796f13a43c2c72397e3624eac155378036434f7f372b109053fcd603c329a84710c803b7d5721fd7776399f8a9e40072b156c786f3f95fe8728aec86099b2b34805b2afae1ef727e95ade9e6626f61c3c9399d9133d97fa4445c194b68c0e4eb7c59cadc6c261f6fc1cb6131e9778de904a394a37f9acb9ad553ab75f190e7ab1b5616d18e7572b9270501187044780a95d3a9546737f0462595c3b80448bb5f1f29d2e403567293169f2645692e577e23f63a09e3c3eed6b0501750804f5d842a704be66c147256f8dde8d52ec1c639800c583896cd16eda754a9099491a71c5c86e10cb74e722e860651764dcd131d905f0d451a52f26c99fc52911b14f65841f224e382cb281da9e4f2ecf789b0bdd86bbd86d8c8c666ea78279978a63c7b226f7b8c152e7299a9d93f31fd68de4f2ea4594f4f473dda776decd1ed8e300566cd5fe99e4372beec551f34ccb787a7aa71ad84fd681e1cc8f9e0d7bad522f4d34b7f5f83284eae6b7770efd4d91f9158ffc32eee5b7ccb882cde0edbdb0f7842f0b6b8f03b7224bf29eb5b93e7765585e132d1bbc3b38e474ac4047eb4ba6539f7024e71e9726473c650520568068019a7ba2c1544f843124bb9a32a6a1629977cdc718bc132bbef4a9993583b9c1c27f80e52f8fb9747129c7082250777400e72406cc41c64396c3b3f1676f7adbb0898b4059d79e93cd43f0894e1f0e046d60e7fdc0ed8373b9fe8c0f353df7c0e410208116d16411167c2a8fb3ae70faa8a80ce21b3576b9a14bd9ca8d7f2a7b64504d290524271672c78c1b3254e0991f9a2c56a2b20722acee07892ac9ff55140b32d3cd6bfed53bbc72e5e0136332f9dd78261d6e0455c0e0711ce92807ee50d0309407ed65c0c9fc82da0736c97a2d868527a74c7119c44ada10ed9430a431f8b725334054c8f56bfafb15b63211e9073bd13375d72d6e272d7f27e7f9822108d666612d9e49385e7c1a9b975617791b2319d8c6b721d359565074b8f856f6f85b996b31ec27819349b816faf333f29db46e126ff722d45a3de6688ae9f164716d890f1fce1e1a7616ad9a17236539b0b04947a6841c84149e773f973aad5158dc21bc95bc8e4ec6c415892ee7f8a2deb8e3512c653238c4b3f00e16abbc78ebe9f431170472690febaf5de9877204bc8cc3356ed680ef3349c069d49bffd4c39a7f29d837115d8c5b27ad6517fabea03d0b29bc87209b899caf0681b06f24d17b8504523a61f9f3db0f1c8bfc945eae68dd1aee6728cd743bd878a0df7cb4d196bf520b36a2844d357ac595d9d71cd4faafab1b372ebb065df91df129b3052cb71a2a7206f439e8ae49ceac18daea1e971c1ba8a72049edfb674124065e419ac80533a60843b4c7b39ce5be0772566ed4229a9053c2f9a0f14eb541ca1623ea20995509a3614455914ac2270957a83e61f43e7ce729fd2b3989b3bc6cb7de6ec2fd4bdc89ba7863940828c2dc9ade3d090062abd721f508f6301daf2b7817c065ba7b9b4ef4fa05cc0873931c5a662e5c205638570ef43959d8326cd18242ef8305a345945dbdf22fe76d75a3d51793a95969f651bce95d410800ac4481e545aa79862be752b66b63bb0259b0eb21c2f3b691e184cc205afb10e65cb6f190eb6f883a6eb0d4d1b691c9d82c647018c5f6926278c7240e50ad8fa915d735a75e2d34194a559742a1188094d9437d0ab12a9c5eb617249080c89821401405af3c43a3f41bcc84901109d0a3660d5b915b4c2801aab720f8dbd6c7b23a4259b6cf8826d6657c78d3d007e16f30d75fab89238aded8a727e475454ed5c83faa2de2ad55ba9bb3860913a0f65affe0abc88d7257c53ae7282ce459e722875b530d0843ae71151bdc95db7d7d0ad76f06a7ccc352aaac572cf1baf381d8bbbea807df837ee851d9e14421e70c9e807cb60b63ac43d8cdb72d5a122f8aa43d7c093150fabeb80d6da606136706ecaf2b03e1b791b9dfbde727cd95ab79b01cb9e4fccab01d2426a6a4e995439f7ef5c77da37d182b2b347729edc81108495bc6407aa1ad0637db1b76641b269308988e6e17a36768edbb172e144e5186a51f6f188b37c8074a3b505563a447653cd3831de9bdafb9d871372dacfed82413c75a8c2cdc5675cb19cfc348657f3be7165cda230722e2a6e1f7241ce78a3130bb595822d928ee1d45a5bd91355b9aa94d5470ab6f7c21766dc299310e574a9618a44d7b26b490b2454e426168b7ff75946647fa82d4d60d60172623c287c5b9bb963942830688c7072b5b69bc7acd8abffe34958a4dfd7dfe472cdd8443224cb9c1d390286b9c09505788630dc95efa0a2357ff0548e4576b434f47f7d17347ff31d6c753fcb3004477ca45588a53a31ceedfd6e08ad60776272a2db3a653c11236ef34889e0e34d8a9cedbd4a8c3ad387962f5a7de7b5e8886e54a328520ac569bf9134c2367f02306ee44de780e97754e179262de431df5d72de003af26bde8cfeee44878d42efdcda6a2f9cc4740b8ebda60e754d8e64b635a764843fe6f8be517a31839cadcaaf52b889ac26a28ec8f1c0730f57fff28172c383b6a117299104650b794ac6b5852f1ff588d83c6a0bb75f2d0e73c44c2d4ef77511707d7784915ea0a2f89897e19713c8f1a02c5cad2edfe0dbbdd82a95725016a63f608a6180aa7d68093233089c856409a64b7f1c677672573f77d01d1e3343093d7c080e5c9522ee1841f6472f3f265835a4d51d7f175c405dddd75172379089f0e662f67c338d3d16fb1b611eb3295c06a3cc2855e4b4e0d0bfb8ce7247814126ac24f26612da5aa52b086af7cc822a9997af4a03b6072aba19985e583f7f2055263f4c317574ed5fcd99b8729cec3050d71c0ad4b6e1c29772963a3df61acbbfe262ec1285176b3f5bbced2c7a37ca989e54993c29b8397ca0f5ad724e565f70d34c4971346cf7cdde04954dab04abba93c1979542ae9f8105ad8d0003e08bd9df57350e2c9f7147a7ee874cee853f1e50733a21f9a118c807d7991ab28b6b97659e9dbbc9f694dda63dc384aeedb09277678180893f5442f9312b72b1f4996a717f44f3e1be9e11b93d31bed04bf8c66b0b4e21986bdff06c772872e534c959f9c457df6be0a5d017fcb6aabdb482416500254e19116c8ab87b77723563ba64983dc6fe1c7d853563332a02b80d28851d51c4782a3b5f4f37ae9a72550f114f75bdda6b900eec0f5e15405c62d94f70e5cb0011646c1343f6f98272a1edee68bd9e498c37b13319c494d8b4169a19b9784e335d2d2a4fb6e785dc722b259d8e0001b7f7f49dd32195cf55c5926d96bac943502693a6d16c738d4872b8c1f4f98bda2edf0f8c700143b8963c4a4cc319515653dff484baa226e26f726f13fa36f4c03b16d6b92cb4792fae2a12c32e97dff3fad448b97e8b844271721bec9cb4d6a4f249d6680c5a64889940114b9d9c756dc0d8503c11a1c8bb871740cefa0b364b519c1ba93e3f33e9e8a16fe5fc1faab84e49a85b8023b6a4a672c36a7acaaa345e519f42b8569178b71b32cb729e62090b0bff30619d314611724c0b1a701959e18a35f62db931d40ec2f3367e646549cfadac2476929da82b72dc419ad920cab0aa7d84f7469b928e215ccb3f314c7c0d4e0f4cfad80a80d872095071e6cfab1bf9dd81c42abf3831bfdab3b2c4bd868e6eb3528683a4af2372cbd76e7b9f0840864eb7705271d75db37fd9ea10321e7fae196b87c173122d53c0f35f3f8cec90a15a3ea55c8fc2802bb96c1fda66df7eae4265a230ccdcc25c62e995a9d43a69ffca25bb8c52717efe0166be96e37eb2cadd31c0374aa462725ab7ed4bef3c699f44bd4cfe4c18f91d680d8336b43ef732f6131fa96ed84a1f907428b306dbfe5ae418ea0d932632fcebabdb54b30900d64d23e834f29e6314848d5a3354d637e75f76e8f73c237d598454ddac0fe27f71986da9219d7143721b9f6d5fc29c277adf7272675eed5002b22be482f2f8a104a4d8b157b37ced725560fbc67fc827633c4dc8ad322f99fe243953f40fe6d425c80b54232c4d167283a0fcc01009de18837e3206a638840b5ef6ede570ab449ac6c9991099527c272a375ead0e754b53d28ab0723dad332e4713b69c28f120d26aa30783c6ac7472d928e842cb4c6c1ebe37e2c0d08a6dd78c96b512508e00d3c2941b552ceb3572586835f4ec4570954d78b35dbf6b833fac822f1364c039cb1ab6231bc5f7ce72b3614a29ec780b23577533351160ee8d8605bad53641c30a3313f1ba2942c44ce52be962032fbe98bbb1b853cce9f82193d81e7921bd970198b178b6e52ec77201b868b1ef17505c8fe708de2a14051fb5f27b8931ccd82dc2d4590907f3cd7210f0003ff36db64339224a76b6adf8da046f04ee90ef8aa554b7a457ecf09c723ad107776bc5c8483163e21c9423f1740a9d4bf34d95b620341cbf0e5e870e3e9b885c2be6c85116a7b6d132322e26f4c0f10bc728d5887070d8a3f5ca8b767280bf0c86d613b776cce0634feac165971f6bf1bacbec998f62de96ac749ae97265d58fbb8543940f06a9f061f8f88a726d8e69e4eeb3adfdfe3b6dbe62ea554ed7f7ae27f19e320707eaf3beb8ddc92308b0e76c3ede707bf6d0caacba96f672a9e9174bb0917b89ace18ec41ee09c5d4748cd73b4dd051b232dcb4db54cbf36befab66c55c6b8bd49df94d8c4c48120094ecd54cac67382f319812bcd969613ad17db93bf64eee50aaa027e25c6b24007df5cf2f65bc39007536ccead4c7a723054fe5a80805873fbeac9212cae71822d9776dc1fd14d99bfe841f5b58c2604083b8737b28547c5f10365cd6a22b4da296c99fee7cf8f0d6e6c81bcfc1c4961dff46592f0646a4347ed1b2dac99f5d1f8e3887f1f60119177d24ef4daadd054125b44aa6a96ff75a4c2ba18885ad342fe3cecfaef4feb35d82d380fad1b98259ca193bebe118ca3ada0a08fa433052d55a909a86229eb5886bae12046b75172c17113f1800ae506a891a1b36fe8c5574a8e94796bf6ae73adc07fabb47f8d19a1094dca437de920cf725f8c2d84e826b33cd59cbfb01c2c13fc2a98081436728eff5cdccd3e8821e1b4958e2ca67b8289613e8240b3fee65a676c826f4a8a7208808acf26ca2dbfafbf7597af95073099a90ac8117c39cff2363052a903e672e56982b8b0591bd819fe6534a0906127c06e6e5bf4543648284062dba92b3c725cc46b31571f2e3c63f2c0d9e43716a486572edd6abe137fb2bbcb18cbe2ef728bac6978dd2c384764a8f4824c025b8c3fb78c10af929cdb305dbb10ad56c669a662e594dca15a338a5c867e257de98e59491b310c8d6359be78a438482bca72d6973f4008bca9e2076ee6a3fbf549d2e5ef907eb041f8815d1849eb88bc8b5aa6a2f7d4bcbd0c560cb0942b472ec987a417548861f69e796c72f29b12cd4d722ccde89dd077438e3c2b757c07453f68517393e411aebdba31c0d0ffa09db972fea6da57612ad206584aefc89bffad5521db261f4c5c393d66ae4abd45567d72953a04b0ac0102ed057714123ebf454109fb971d1afaf00e336f1c2050c7a259db30014aad716e7c3412f6ac72c6bbe135f5e336d77d058fcf4d30a920da507237343b68e51ba63c8b9ea46e5712b391b75eb0c33b3685a55c9fc1dade7ca02f22a091e95b94618fd124330957f49f55e42f3a0360a9fa1da922bfedc4ea207258bacec72c4b51b0050739c8aa913acd2b8c4882e8c560d6e6a7dcb858d8691ee0bb23c02fbc5cdc0560513af425b509117d839d5662e0cf31fe789221bbc97232d5423f512dccffb938433617047920451811d2058341a6fe75a3e218890d703c055873ad7315cebaba2f585cb1c9162debd5e2ec22d16852700f49d891db729c5fc7b43a9afd39251e048d88170f31771666b4c5600a2267c7c628ab2b4372fa4c7cbd86043b11e31e1d0b10b943e91d67c6ce09ed745d269d51f4e13bca07e460b712b74723be20b73cf58c36ab0e1095b807bc183429f99c025b269a8c6075f6d66b8af6532f49e0579a6e73ff3268ee85215c2035f1cdf6398867ec5d1196d630adacacd6c0217c0fa6a5943351c6a83352fb80e3c71d087f9dd6c7ae72bf068f486cea30e68c569c5252c73c0fbd0583bc46b2149d1fc88b00db4b953762b5e16c4c45c0df25610979def7c1f585ae2919490f0c849a587448d4cf7b407e691cf084bd4a4c63ba708d8ce2280478d40ad25c7ceb1c3997028fd8efa6729d4a6dd4af87a1e45d168fe0ccd3c924287beacca5f15791bdfce687d757544478a4105bfb7c3a4207de2803fc8ecf50986e14a2aebce095e8ac5d504ccdad72703f390fe28b9ac5110e58cee68c70fa4243f4c45e7e73001e5e7dcd492f0772edfd30b2322a1004f6705e129fc67e867dec25a0ee3ab33671ced359234b5e72f609fad154fb0bdd16a440ff0d8bbbf5fb1b95c4cad9e1ece618326edbafd172cb7ebef219766a8f6f87abb311d42af9cdbb3516188b272fa068bb8ec8e6c57214cc2489ae0b069721d06aec8e2f015b8b64c3788cef145cdf24c2d78d4d956cb072d36724f1e7677c5235a5465b4aee98f03e4424e5db122bc23cced09fc815534593c523016fd444a4042b33429befb19ef83737e1ccdebc1e05afd41fed72c9c626a733f10a22fcd21c89287ebe3de4f257e75d0f70eb11298adff9599872de08a1771a7066e21d4dbbbf30bce13a65e76aa17669fd9d452320bcf8f44455fbacf96109646ba567ef791571d8d20be11d29683a86765ababa57d0901c5a72860bca15a7eb1cb21393414a9614a8311d4dc14f1c15d9d350a23a8636c369716e87e006e80ad2d9dcb141f93ae6e7d0f62072a744c128db8327cebd66aa9d0b01e3be3ed3b13d67f305c151822b1a9effe9d7cceb7fa646a17e1d192d113072a98aa6e91a3ba38a6374029f21a9c946aa0ff9ed93ddf77d1fd00bf136333a724a14ed900cab862ddc2a75b206440643fcced97021e65026fdb616e0e380a9725cce615cfab36f45d8379cd6ce66cc89b857d92e747a632d7b6e487cb3cd065a3df24beffa3a02d621601dadbe05f45f966fb2608e79b023d4068723b12a7d0ecb5f03f44a08a757a01e6b862ce3e0b1d40336f9a0a16ca7fa9fa9cba1af527254321896f1abcd659e6589b228773edff6987edd18a17ebd43850f9d0ea95d72ca443222642e8ba11ba925a9cb2b3abf313456fc3abb550ae7f3a9682e7d9c720665f83c49f7aa1dc2c836484aff41d3569669e6d25f8b0bc11c9f1dc597bd724da68acb6975df3a9396b7406b992e0069a571fcd34d1cfa8fb72bae9d9a3172452bfbdbc9819be0d8983f28ba1afa30c890703abc252626baf08a03bc6a91723667091cb891f4dd4374f0564958318219d4a7b02f108c9ac8cc731d719a92727fb4d85e743c3974b8f147305980db0b3f124163ccb8f9aaab4b1d29f6868972814bb9d8bee22014e01aa2af3f9193ada3d0f052dc2eabbd1a248e879416ec5dc067fc51505c439b00cc91d8036e63e15314185b8b3a6ed9ea8f22847fca44729961143259784049f53fd8a6125b14a03bf22c2b6fab21fea4b6eb765fc2ab3cc38d9a746c352ebdd6440e1c7ff4a14e72e96f8971fb9dc76293d0685a0bc2727ffcaf20247ac43301f694aa95d888264cfade7b9b92ffa8a4ce792b5278a5280d4844dc6b78904036af66231d37aeedab42cbe2f507a0efec947d76fa30ee54c2ce2d69ecc35e269681d06195ab0c59ad85a7a9a7e068e745b8079e65ad7b72fca580038400675875531e9133e8318128c8b2edd70a7f98ab272eae9f21b76d1f762e359f17d4ea726f6170d33b4cb63388bb2c33f4b2b6681f4cd6ef77ca3f65e518feec4733bb0befe429bd4013419d267e4dd22b97c3a0282d0fdb7ea7195bbd7ccc992049055be9b58417e1b59d5c1f457ee21b971121d6327fbfc5ab72b05167bc3c25e3e74b99ee8a15e7305bd0945b1112079a963306abf19701b0723dd1c8336e30296275ff0959f75de4ba9852176c3e30cd95f8793ca1164feb256d9bcebfbda6da7c04d1a6bc4db69d01302d859ef0b71bded02c8cef4d36f672006b7878e9037f76d6e850795de140cc2a23378594b8acb8d775291b53bb8d723f304473af96bb9ada9b6406472c09e2f840639645c56dc1bb14587efc44ac726ff86d1c0408f6c3ae15d2d48d1210050e3e3f0130c76c5ed436e566df05df4ad685aa146eba7258543b3c364f81c8cc50419c5ba065bf52d662fe10a796f8720587065a8667b6833cd42937285a0fed53539acf0085a668b9593abc174eda7231d026f88c76117c47cf8468e8b2682ea4dd5cbdc1528f48dd4d9742fe853f7255fb322bb4bf11bf79dea4c7c0bbfc4a31a02500ac6e5ba4e5a39f984c2e157296d97f027903433326d3c847209cf7e6f8ba41cef2a872f80e1517ae9f373672966871d7c976544fa5dafa4879cc39b412ef440f056916bbd9d0a1d87b8c2872a7704783fd0787fbf9aef847f2eff5df386eda74311f45abbe6437c3979e3672b412d65a0661d4027cc94bd1227949fa8a9d9528184dcd2ac923859ec1d9812027e54d77cbe03614374b363d9c3197bd9e5362f4edf8ad81af35bd12588ef56fca667c271d4dfa6fa0c9e045b3ffdbe30cee9fc2b36f428d7e81f3317675d724630f440289155e3c3981e53d196a3d944fdfe4d522594abf0dac166ec81a5b2beeb060510a230bb5b940ca7ed82a62362281076e83d6bc905fb23c060d145272e758deb4e29120336e60d92688e7817a36709f3558b59f61e1e09f540fb5a45f1d23d3dea71fd3f810fdc6e7b25f03f7de0fddcccae96b59008189f80b048449836f48a87271863d445f79c0203557661c6f44e6368f73ad52f4866e57893f3890a300d5861301d406e8ba1921e7ed26b2ffd861928e34c89168a9bf7f420372cf99e3cbf88b2ff58e4c7e16d12d44ee1c524436d5cc52a360f2c1214e55c2382fbe992142508f3e96f7d11720fd37c17d1df95136ebfa7683414c9dcbb4e305864a6c85af776cc1f32c5888545023bc208c74ff8cbce551026b877f0c13c0726266175f5857cf10651a4ba1403ec2565778597bbb3ca35515c385049f0a25729f64a9fb7fdececd0aaf4a1e40b28833ae202a7b2bc08982eacfc3f12990de3d1009ba38ae269705bbdf55ffebc23dc157b45caefb45098f027c18b356312172af18a39299ae33a2347e6f76e8a93e4fa5e5f498ab0690964fa3410eb32c8120fe753acf5daa4c868b652daa230dcb388e1efadef47fff3922deb1e8d5d1d872294603ae87c69ffd0f559e3cafaf6c97b69cf12471846e61025339f49cc2631d95c957b916517bb742a8b8f079708fa1cd0e350103603afbfd48ee12a415417246e3ffa34b1f388782d2e529929905d240b6bb849ea90504543a47a773728004cbeb320b045e5f0a7b8c5f21d4346e5533b93331a92df2bde95375bcc23e897228c85b30eb17836733c1c8036002d8de651db1d430a8f68eda245d68ec385472e99e322e97c6c0656b1c55568aaf61250cb518fa6f1e0d1a58393aec1f33cf720514b77748e07c9b1bc9f2885cfbbd0204ccca200c1314007e6c25df6f88417291101ea916f87fdcc325f63c76cb5028898a492ae5880f4d7768052302f3f972c06dc58985bd4d952b04e15e5049847ef7a55eabfd8f075206ce1be89526ee723517ef4d991f71dc0124caccc3dabc89d0ac8546d1debbf05050d3ca5d160569599745f4f5f1211b38e18112bf16eb5d039e3af3d0972b82a45004180df42972d740360008110914ab9230cdee702cff1701032e3376fa6565b9807cd654d219dc80fed3b24b283dd3f5622bc66852c8d7de91139c4493e2d69a49fd447ca043921397d54acc8460447a9ff7b653c414305564f616c25dd4cdf54e2243d3b44dd338dafdc298fae2ed6db47031121540f11b1566f33cbc01df593a6af5db13722271ba2d215346a40979fbf85b31bd077bc2ef9ce5021f10d58d3c6652e26e72ed27364f2753d59249c93d155e327cdf645d46a87ec85a38a314e636b35a1208b792a4f1a6cd6a8e1dbfad2999b3a99c069cb68cc2dd44316c4ddbb34ede81497303e6e2fcf77c64b0bb19664948bac33da7d170e5af398a9d9bd1e7b30f886bbdf8fed7836fe7cc1dcf1e3ba2b98e962939b977d167195840528edae5100808862f7db15cc182f3c2d55d83c26acbc2677270474dffb5611a942f5caa9f5725d7bd8947b235546376df11a5730d99c67cf6e708952c8a47607705b694cbd572cb73604dfbcb4481ce84ecf14198fd7a076507667b599566d29eccf10e34b6728889db8d233bbba84a53f384dfa1a7ff8ab75a8254c1a21176bf05dd26c8af721048c6639217901e76e17ffa1169c5f1225fd739f39077efa3346deaf7d1284035319d5db4f250af3c54f7388d580256e0123b704e01510c12fd8476a53aeb0aaafb5236a66cbc35930e10c39a97a9d12d347624e1550bc75fb2c462ad827d6395dfbe1c13790d1c7c7a4c090f8dc4911038bc441e2b0302d351b965d5352d72b8be327a125117990235482505ad4c0da4c9af0fb5002d284517cd3a6a877b727daf50385dc9b41e0490a246dd1bec9b5fd5849f100438546bc934e563f84d23e0933147b20154294d1379a69e7f95d7280a0945815d01728d20ba812f381b728cfbe3f7a9e9ac223cc9acafd2abe9cb0f109f53306b4f673972cd24bfb41d5397d3d3f7d5a033b884b93997ad7a5f17dca54c2cb8694c23c3fe1db2b9539b72de8cbd860dded44903369b4d635b30a78445f73777a9c396aad7c056e7664372248cb81e7649387b2fd527e545a5b6ea5d2556111f0a69c56cfbb1ff41654072937181759ce9ba00604c69e5ffcf06494bebc381fe7339bf22b4ee23c6fecf7251aa0ecaae6983e98d85a8af227e69bbd225b73089ae8e1d52f92dc7a378b572a05590edb4a6ea28171ceff9dff41efaba356550de22a7aeeffeadc6c97597723785e90d81b0a56292b17a04803305cfa6c4227471dd420b8f0eb332877b27725bfe583ec1a65d6bf413942871f5927d6601d48b6067535233198e86ee2b37729ecf923c60f00d469d8d64313976ac84d29131328d599ab76f01f1cb45696e72a30b7477d2fcb92f8f86a9cdb27dc601c4064340d11774ad77e6f373ecae3f25f42f61c9bf76e8fc2e51e5a505ff461097e3404d1f5bba19c4e6755db8fd9a7241ec3f9f55575b94ec2b40a2651e1c51c68543cdaeb5146ca474239670a27b308689da783b1810c19cfb8adea5ba95b75e921b7fc201988682e8868911acdb72c0ce84695ffd250d4d0246340bbb7e8c25ff9ef393c97209bd85a9f527f5a77240c1ba3ab6de8e49158f8b6000de131537d9f40de5f21dc7ba90468b3f310e722139e3b566a10f47e95e50dcbb66d3ed882f413824ee7754a1db210aaf36e5724010505561a5848fb56aa58d3a3a594177a4f291f762176775c0f5b0d8a5dc314777a2937e83792dacc0b153c73879132121d7e8b094d2d0161ec1bc0918d71de66bc40a0bbeabe8233824ff9e753fe9ef9fa408d328c161ecec62f4c2c09c6c8dd089db6508959f52d9aa68bbf12bcfb54a844b2a56ccdca515343593061672a49721b89481c8741b509fb7a78bbdd07a22796f36b7f02f2ef4a149080d267241e4219950e572ccc5d38cf616e435f7ec6065f6959071dddebd8a45ed70ae65ec8ea3a0b270d2e51670f6d47b502f82bb6c123d73f90d8d1d5fe094d5dfb072ae9f9123083144003da759bcff2681bf2d13e33e5799c21a1801f95f4ba9ce721ed92c2750a216f94814c9f0ff691f64c18fdb4848fba3a4f3a0bae2ad8bb12154edc6c5202bdded8c203a3da8397193cdd50d41616f9fbb880e5cf51381ab7214a7be7fce4754a28c06e759362d38dbd1bfce756727ae65c05a6d93670cc87243dd76c6a0872d1a3e12fd419ade05f085bfd05218422531cc208b000be2f972c8aeb1e67ed21609611faedd06b655e13b62a0eed7ae90bc08cad2de9314d62e5fb459dfa6375aeeaef49fb309a98a95cdc63217f8316348d87927afa244e44a23af042a7065edddcfa7e5bfe6943f58f494aa2d79826badd3220fe653eb5b5028904f1f6799e27f4509df632a3b162d4b0250b6c22ae78ee2fb6fb2698b1e721ebe26a9c67ef629c796cf7906274dc0e708edad7bbeee157f92d3bebfc9cf644ad80a873e5c9cca4888b29ecdabc73c092958e093b313eabc3e184202e096728e7107c75947c09ba9379f3cd32041ff72cafef74b972ed355045a8343981472bbfd0f7842ac49f216c9a8cc50619f6b4697bc2e2296bec85c6e39b49e4cef5a3aef5c7c6235fba9e7dcc095ac92e43d097a25a73ee9cb57319b62fafa3fae7255b9eba2fa1e781b98f60e8d8785f44174ca42a305f886b8988895a053e9c272f64fa4a3e748abbdd4c7ceb9501eddd3b9c70f0ded522e9b2f8ed4883bf0131780eb120a3ddff8a11551bd2f04ade4bb1f2393d078dcf5ee966001f8552a7f72f7be5500b97426e37536adf6902bf1a74b9a02c4dbfd2ef4be6eaf92d3e24208ecf257f5239b0d45728f86d0ee2f0147140fe58393949d7ec44bfa9fe544803ea457c3fe05896e9b82de694c4f6118413187d1d0fe1cc9a235933efb3180ab726989baa60941c36ea2d168747c9d8d0d80f86fba0384605f56867784f4fbc17277a7779988d95aa7fa08285cab7f879843cd20243d71140faa639d812b11587210d555da26968b1418d660440bf72e0471f65917ad662231724574e6c6b8af6b876c7a3b25e6a18560477a35c74a7f047b5e4560a4c9e238140325701091865abfbf5a1ab2644fe0067682d98d2de9293e41a637a5fe53340063baa400c6627266012a4beabfa60e8c31ad38afeb121f90bb7ec232a49c300c756d2a19cc98722dcef4ce2bef4b0dc22900d8948ec051506d49bc41f36eb0899917ab33ee5a72821d8865e98d74ffae8d223eedbb1a720513ac82819bc75a7ecffca5fcc53672fcf37c015b609bdd161e1bcd556ab2ea89064a1f0e71e267562634d84874bd72d4e2d1595cc968db85e749e511864d82685e875780e6e3592fea92d3f0d1c472827816bebe42dfd29df23fcd1c2fb39f74c53dc9bc33bf7024033be63f933672ad17f6d754fbaae8be8ad79107a8fe69079945801ab2e318721a42e40d9bb6724b8857ebfc597c69928672b3f314cbe6ce85d18caa6fbebd7cb72454a9a3a020286c46e5d6854e74ad322f2e59c85514aac4a9fafaaf16b49c52ab9cfc2e48594db00967afff957e1cf86720767611211f3ded7043e69145cfbfea4b2536854a6db6b8a52a6a7cb5d0045824b298aa1c27dc73119a5b2687e4cefe9aa0db07076548d2d2abe07d220eb5feb4538add306b873dec6eb7e41d5eecbedff65dbf06d0ca81cd7336ecc3e7127322e21a30a92483c752c2408720305b6792d0f4dd7237e40e106f86ce143b04e947a55c319fb836f112148b2363496af54352dfcc47858b93be6ef8433f15f5bd6afdf0e1c965c7f7bd03eeda23595cd97604663b28423bb89940695efad36261c7eadf189a27526bf872974e3a40fd3efefe5e62434197e9a0649459befa8388aa04cc2f9fd2bee1f9e739ae8eeb46ca2e712e597237aba2436c590ccb04cc408eebd45f4955163c874062da83dc868ba7c4222d728f6678e916624aa11b025092fc212c099ac358a3f6612b82fcf7be7834fc28729658d0c5e514f38760b1fdbe8ebf0f40abee2f3ce6b07101fef5ef4309cd89725a04e65777bc5b636d1e05f3f97684831f3a8d0584b52133e6bc780236c440725ea3aff69a67e28160224697045aa864d56172c18823a941a48e92c9e94e66721c1bc606e6791418e4438e62880875ef470dfe29b6b0337c544e8d2f516d9b35c7c8aad90635f2ac6cc837084ac76a84387d8c14a984e45f117f07c996515c726209251cabaca362a2f550ba80a9546e32ccb7b7899787ebab6751d72eacfb72e04d7db45e794a7928951b13e1c3962ab53f5ff7682af135d52be81149777f72ae373e01ae7a96bc3a1b5e1d271995202842c9bdfc66e225e918644ab30b4b726a0f2f17918665f782e97e4e1c285fe3f8785db6279a0883e3855dce3b76fa7201ef8a79d5fc4dd95a40ba9231a78605a6375bec9d0e210b5acc728a9c1d4572e03dc431d67d1cbe1ac1e733def142793dc7d0951fc0f42aac2d0d272d539900ad487665106612717ce60b627ec88044d217e27923c7293ff4751615be6e800a9c370edf6e06d45000dab969f04fcfce1586f2bee06c0495160df1c5371f4e33f6d7c68aa986a3695da0ad9363171ccdf6668cfdb0c31a84dc7c125e574d994d206f36240302a6bd1e4a0a25579533a5125d32ce065fd4671a8303fb1805503b197ffb10f6aee52c8d779d2373e011845c092649a744cf6794156d18d7d6d3726c55a1b01f0e4a36fd4ce7c2dd9e241fbb6161a6fe2bac0ffc36353a607e8672d4a8715466d2b8af55d9543e31ee253c01512154d15ef4d185a5d77c4976c26835bcc3e973f5b10db6ff6398b3a2caa7c1bbfac709e6854e735b90d44baa4a22538969f5f964633dd731b754381f597547e57a15949504777126ea348478c872f73717fecbbd7793df09454d13fe06a5e7850e6c12ed21a4c496b54b4bfd9872c46ba98cea1dbb596e88f668aba78d185da2d70e6b24c85e0f4e0660f0ccf37220eda282262181508d27d849750801f1b093c397f4a377c9f3299af8cb176f729de813b6d45fd8353398726f407fd78d6740cb7b6e31293612686f21dd5cff727bba3aba07a74a3bf60266168a5fd184d8e672dc58717197d010e8a57119097204794621fd8f87c5034ad88d3874b6fa998ff1dc6faf1f9c17761a930a826d729e4a2cff6d361c8815ebeabfc6896cb85938c4ff362b72a99b1e36c4029e4072f5b23948668b621e8738b6e899e845cd057aee6ba887c498d5c8751136c1af72a5cd3c549594b1b35b50aa904f3a86731593cb4b01cd21826b73420958aa22120a135481827399f61886c859eb3e22df69e14f414773cbec26b927bf0d582e726600307a11afa78c7b46c2759a2384e428aa66fdb87820dc53a88b52d6c9e272f0a053d366fdc6883458741b85ca7eaf0d0d38bb17ac56ff0872801d1af6077288db2d1c4f848e4245ad84441fd2fbc015b6979918dd7f0367bde4d644ae1b72e28a59fc132e8b85378bef0b89e3e328272e892139808c571b14e6171439d567e92beddd7e50d54fd47dc36ac88f73769b817fdd6d05ac1bc785b258e1906e72394db0f2cdab631644d482de7f52cbd79ee143b0508dc54c2579b6e06c5fb62c0cd36d4bd105538ab4c60bbd53a8a962a5486246b3715aa87e19ae8b61bd71727bf4970cecbfba8ee1ce81d869ce88963be74d6c4a4c4aa8f28d6e8a25ed4f72f09b0693244b8fb1b8fd7988f9cf8ddec9c55b184a35b262ac8ec9d8c9f5606e9bc8287dfdc033437abf42c3f01bcf77c5fe22cd5c030906e0e744df42e6c572ce697d8d63d8f86b43a108fc97add47c45c6d7e3efc547100e6aad64615ff66b577d88927a035a1f636c27c837e2177e8404c7e42896d461b339efbd54155e7285259795e85fb8239d5aa0cfc0ddfe044bfbc154d955413cac500185af72590439a5529b398fd7b8253994e3805bb89303bebe5c08506bfa39d79185a04bb96fbacc38071f29274ba7594a7d630b8ed681173b3760cd8bab574e3e85e418f272c6ca3a06338bb8e619a3eff0681d2618814c44947beb144d01445a181b92f869e4e373778443806440fc36c3cda3d43560ed87369e0c2548a87202a40f75ca5563deb1ab0ea37232f73e15486425eeaeb1d04c726d018716f6936f555e66077201634ab63f4719749631a0e21c23bc511da1aba46f37febc22aba635819efd0d87138e09d0bf681a6745c7830217e2836c0e802565b7f075b41ba77d018a3472091f728e51dc2d7be9041ce9dcb93a6a6fec5e31f1e420762338971474f5ae4b7dc84797104f87101e033963253ae5d85c85aea51d5f10de954322fccfe017329cc2f17b21e0dde61b2b7463c388ca038e3ef8e7e6ed2fca2b126a6329b5a472c2d16273718454ee10a9d01d5dd961b7d396f1cc59e7e72f75ecf24b584e62725940723183ac5a4b4ececcab5b28f072360937ce2b0ea58d4e21aba800a74c44b2dd42cbfa1cb0de39085860232d2354d15d69da959ca734d775f217b6959a7262095c8bc6514f02545137a0b9a66218a977b7eb10939d7f1db713a244ab2372d318ca9e8756307d942562cc584ac68647320cef7e179a3e5ee87186cf91af6d83312d160c634126ce605531131734b7eea6021358e33c2a915b904c1440264b8de3fa0d34df2ae9c8f6d5d9d64f647da146038d91bc26c420f0100bc42a0672e2f5f7de2c96a620899b7b5f4656ba8d7cf462179c1f4facaf21ed37f0803529e7c953d820a9c6357d6bd8ddc574c2450f07b2729fc498303df6a68804e8517251961c1d603f95acf4d8ec7f59cbd7450ae22601bf76ef7ca7694cbb56e10d12b9e2cbb4a58e4b6a7c088b4fa445680c5f88a458438777d9dd5d5cddfcfee5122d4a0199deee9677a27e109b5233846ce4677c912866dee41d0a6ddbf5594d7203f07bc3b17de881b8ce71f1e344c213697efe16cf5262df0ed79363edd0001af2386e9efff09d4e3c13faf3e89dbe48465faa888a55d0e30a58e89ac14e5a4e07aaeec243b5792dc25814a0455e1379155a6bca7abb9b1dedfdf5223abc2372cb9731ff2df6b3a376ecab69f95928ce77662f7d610afa866cfbfd0b87f8a2721704ca0db4f4c7a8c1d1c3c26680dfdf35b53b09e3f3607a77bfd40541202d0085db1588ccb222a6b1f15e2aa22e487aefa0e3780a4fbb5c2a952763661b13722b89c22c3d310fbda2f9f15213ba6f8836ac0282216fd709923ab9eb83cd7c6beb243978574f5fd12a7a7440a236e57d703ee1b4bcdcc50fcad664160378d8727b7493070b565be6ffc18f247198ad0b643f9c49f9ccfa9bd7294fbbf9d47c167ed97153926a9305b411d44f69e5bf32a2788de7993ba457b2555f20d1388f17c28958a69f56ae52d65a9b30363bd2dbef7db20b0737a01736bc4f86dfe8b772c986dccb8bbc95e40ef3693e1db092e2ad5c04efdac28b48d53745c17512b5723fb7eb96b5965a119d1fb42ebaf0d17aa2d9071aa820eb810b57eb263056b6726f2d669b5c71bc186549eeb305ac7144bf2bcb3b01c953d08934969678f851721aa9c73ad647662cd594d1fb7a4f1b06d40b096571b0a955ced680f099e9f7050e1e73631d10ee770f8cc9bc625f90878e5f9fdbf4c515d467203215ccebf23c0e2c30ad0a03e9b759b85c03ae5596f8c5c687e70c7ee93b8ea4423e40a11e72e92698e5c47414d45639f1275a60ddc7523eb4f84dd8b6a964f4448ccb160c19f057b249258405b7cd423fca2a70e900d74774952315f2d57dc37c07bf6013726242c0506d6c75b9f28427d98ce19f8433788462f637dd2be7d46caeb8c3b57167734d0f64b58d4a082a8f7cf144779a901d861ab2d236cbb508cf61262f89687e22e480b321151677d1b67f3354a9271e0771a0d45d4ee2aba7f24a3b847872e112f4ea6d03d4f388e7134b7ecd852c1fad579704cccfa866596b78c35427720f002c877d26f96ee3e506c6d24bf1f1ec20c32fed482bbec69e84365c7fea08c3846a31972e03914ca7a51b645e153653f5a67108323d3cf32662c7bc4e167210a9a467359ed389034a369b4779f01cf9925d5ce1fdfcdc7c85176d9a0b7c72e18ca94d8895eed060d2ef47f266f078e26329e321f2f338c32dbd5fe34d8e72cb538f58f6c5556062edcfec8d3d0ab10b56936c8cf3bca7360f80b492d8ef72c3e6daef08d9a372863d28d349fd57eeb70465f575289efbd9fafee94b05d272c7cd50b96ba91f55e47e40f4d0a4ce999114db97778c6a3034c782779dd0567279e85642d0d8b58a6638a3144c7832b56a86dc613aef8d7b65d99b146f81a872957ebe2ae33608ee1652695997819966ee3db4c46a242f00827196322db9a40c1e721b0c83f947eebf64d8e94b0d46c2cc80a148653d6f3b187021a84228c9724f38de52437c45365dea1a51b77328bda88aa0ef8047a59078778d5715788966f49fbe69135bad9a774ec467955655975706987f62c92161f9cf69465466f6724510a2fbee8bface122cd3cac0dde9d8304002c7ebc6ef43ff2f015ea9882f72da4cb79b1e852381f876828efb4feb51fbe054ca180b836e0cb2302ca8dfaa72ed425c5bbb3eda771a8da1457223a4d02aa35423a13c29d78fa5ae4bcd912172c939557441ea58f36b2bccafc7e6610beb5e33fdab51e3251c45adf164eb6172cf15c0628dd67dff2af804b75267386c714c4bf4251d7816986482c7181a661446cabed651d1d3a9100ce18ecbbb438d1efc8f9b8bd1f61069a6c4a781792d72ba147d6b518d909a609d399b8fa0299dff56a556c7510ddf0f05b94df0b8cb723b6f0d06ac60d611c4f5718bf5214cfc0a41387f9b6d65c044d4439c991b4b0ccd6256954650352c8b7985c2f6edbd663a74355e58e41ee2753d259b961f707268525926b905b5b7e5f8004443d5f159cabadcf2403a384e927ecd43f16d0840a104564fe3502527aae390a06945e7e266bf5387dc09085199ea0a1b17a0582e77e05751765935d74695c2d63f788898aaf10e64b2c492da1fbbad446673f44394c455dad21e24a581b43d7cb63f6491be0c66af8b3652fd87cd80cc60e8b772c0dacace42aca2218c4ea40f4707761aef1e263df375477ef8a99fc5efd65c72eb6f3ed610548c70ca145d6152ea2900671146c318985e6010cd0a237e909b6ce22353a2bce3405c598464a43da8e2668fe9a2f6881eeb614d4698ef45a4ae72ad88591f2db4c8a1010ab78fe6f0d431f8a2d43919cdde235556dc32825eef682a02941499cbcc0e5a33b13e4d3fd930e83c88e28076e226ee34410735ac46729c26bf380a5f4ad531283d95cc009a20fe9b53ce020c201d59db0a0dff68f60c7916ac54bec3379914abd3e27751a91b9e82f611e28ee1d5f8998a536d102072c8d999c3fee07172984f47825e7549f70c2b6335e757702c404b169aa5663a00a0cf10c0c62abb390dee1ca29506196f9a188ef1a3040b7bea94476e5e7e0c59973d2f045050ef5d5588e7333af1e0303b5230c72bebe759d1f3c8d817ddb07255111b3c4b37d542c75a4a603fabf0ea33e67a97369f9e8c340d8a58a200d372e601f3ab95cacb3dbadec0c3a2afc405ec457302c90e6fb62a14ff95e7b41e247b1f3d50c5711b84381c802a3ae3c79388bf127751c026039e7a22735a930172f24e2e87091d88e6464f8554aa57d7d26dbe544be214b7ba5afd800f3073cc72e64f49a11e52d25e47001dd8914f0ae3d6ca674e9de7758f1048ffda71d38b72d571d47880d7cdc31d60b1027ea1cd08550f8f3b5a79b5601fd96ab67ee78c669b814c7c0a67be93e6df04ed9a6b3432c98f6db0c4b76a7d63043cc735741c724587ce46738654df3d366240e3ac68e7c4cd40780332ee2b12e5d9b33835dd38d65f37193c7f10baa8af5b1e9062a18b921599d3e4923cb90708ea1b6a2718724aa0eef43d41ea9e6a9134a917b0ae9f5d2c0196d4da6ab48d217c1c678c4b0ea991df110a1b45ab65ea1c6c341d441bdf6d9c59afad90bc6bf4855f4cd19a725ec77076dcb4ff0ed0abac211d1e41694de298970de14ac8fa73c208f4ec6e3201b6500de3f7eca63ded3cccb6e1b907b8d8e44449bade13d85f131f7a6f1e7203da00c8df00fb959959400da5d22328f15f433d42c1cbdcca2f26ebacc2b072a769628576e24bd6e110ae08222d8b159466cdfed2afbcf20d6c65bf4b6f2172839c8a1c5233afb3188feb5a68a9d1e0aaee133fa33a08860b0aa8a835ba8a72cf3f65e4677e8d34099bd4672b27bbba8eebf16a1ebcea275da2c698f3fe6e725fdc366b17f8efc2b915ebccc7ad380f3d6bf41a8f49b7dd65c0373c6c47b472e92219bfc342dd03b2008d4c0706d96d42acce7a4efd77b96eee9635b759355d58dd227dde5afe9daa44eeda2c0d7a584b0fdd2ebef20f8b8d330ca21f861b7219cd9ade85d6b3240fe7c992bd314dc0e66f44396e0aef3b11dac2f1aaf7d435981807bc37d7cf08145dff386c87ba2d08899d56e6c4ca65c2104b4eb09ae50eb306285f9d27c7a433b861d46b158b295653a015b871146d1ce245d6210a25505c564eca48a0db8b1a5abc038f8aa42dd08dacb06fe227d20da1066a2fa529692cc4882bcad05213a4a9779a35fd1277c666dead18e8e0c2976f182f9e13d7723d3584e471aef190077387cb2e03215dfb3c553b6058d334a154f8e1b3447b72710ac74dd4c0a706131e4a1e2595c59cf245b04e4abba9ef8b88560683d821727a1499a1cb5f492037bd0db8d2da08fc2f8f61e916a868c3693b52ec79500472f295a9142c5008c8f3a6db86a83b0eef475b74c221311dbb06eacb512fb07272946f012e1cea9ea0a0868e7743c4d93610d92ee3d14cab4c721207ca875cad577e6cc9957e52c3444cc253a39df53ab1b88f50f5bffbe177cd4087fbdf9e5f0fe367cdda13eef66b08a74186d44403d0f2afff667dfd2cb419276d786185b872ee5d2b6385296e0418f76707d4ce1a5cc40758b0d6215acc6c50dffbb2e0af6aa57fab0d787f6b1741d58f877cc2440b17cc4b19fe9c49344784bb54ba74bf016452ef65dbc3f6ec4efa1e69c65de4eeb160f3cfaac52779f7e998effdf7ba5a40d2383aa750e39744f3ef7efc9b12ef3b9bfd4d0474da58aedff529beea1c72eec350e5979d669bf34efb05b29144c5cef2b55cd7dc6ef656831b75b2b9a0728b81bd33fcbc1e8082c1f0c32b42938e2132eea8e7b59e73119ac2a1e8417c476de84de012128cece6eb19b7407f60bd84712ff426d44835e0b0f7d654e5765ff98352f616aa3d3ebc923bac720b6621b27f2f2cfff0de18395756627e8a8a55728aedd9ed06c0d6a8e9b7c2f2502a023661138242709ca7cb1a8eef7f81fb2e2d8ef16eceba0f5f0580c6ec52f9bd2b453657da4db41a685d427d0ded756a72ca1ddd12ba639a4fb0263e3c767fc8bc12b51b70b7f551c78139b2f0cbda1b72bbb432c35840e2290fd18c97b39429e79e062d5efe80b8be5efd33aea0a98872c1cb1231a9f4d9f2152518f2bd3d52d99625ec861258fb08e1ee34fcb0c3d5727cd7eca9feb3bcd097f0692c776743e44e9332a166ecaf98b7532628cc8cf4723c7795f3a63ddf68b283b24c124cf0b987dae5a95ea357f4270b5811a4b3c872971b49cecd219210ba61e5cec690bc1dc7be8080dab6be8aab1ad952f135b8723e57a9259444ac2bb5b45ae6f83fd4ef83e37ae4ad01324b16c18326b6d69f7247a7f20c0bba1ea75f105a5b655a3e24645486949e007f2bf0d487d22cdb2f728c5c067c6fb4efe4ce58fc4a6aecd27560d1577caf9badb1494ee9ca68610e3d1a3cd3d20afc590976c89e03f99a6004dcf6c46d7603705a45ad00868ed7c10ad58496ca6109046f09e9af30beafc7464da290ac64fa937d211d9ead431c327229bb1f0cac47decd14d0f1960192c1a7a21be8fb0325911f00c3b8a3f3dc6072cb2786df87e95df0a4eb914431530644fc8a459518e2164a2d80951eb139365878c0a70ff00d0256aa2bf44dcb05b4887a07425eddeb09cd19df3bce289e6072bb5cb9027c607e88e21b9dd2fcf7871df88b0af6af343fd89f0e06f50e7d2472c9b3303542c8ff9825d721fa08aa14938ad5c03dd615c2d020dec1c7df566472018e72cc809f152a3414dd905cea0ae7164c61440c7213054c064a39e58798722a93a69b3513e6c03bf2837399d70d5c81d483c364efd0a5c077f82cf2db1436490e2bc4a9d537af188fb99eefb16e0cc48f566d85cb256554fe1569954972725a1cd87e812b04d1ff6db867f4f88e2803073b00517b4a312be74e490e6f7f72117bd773344aa7006ee9590f926d05165707b7a4984aead162de76e0df2fd4729d3baafe3f869a12d04b073c1c0d47e1daa978d97c8db6dd4e6d20d61e195f72afa0e2d945b80cf2e9680479343b50bdf59ffd6d430d600810c0742a94b2061adbf20d6f03b37f54254d59b51c748d1bc7336d4f739c7d4cbd8cb9eda0b5343848ea76eca5d3bd3c14b6eb4104b081c223f0a62f24a5248939ffa534562e9c328bf71137583096a5447f86f9fc1245de26a5096dc5bb983cb39f52cc82a79a72208fdfafcbe0046291db285be61454610b83b84053ac3316e865d5554336a1400d7ea7c8ed7e3cbac5e3ff8f3c0d61dfcef419c8ee8809407c894035cdf55072392677db3bf3007e30422f50e4bb0446b4123d62d65ecbaab4965279cf58d116d297e2e6a02d35da85cf58dcf019b6d1a160d16294792015577b059466e3de726d8e6bb291eaa4dd8f8fefe62505fd387024bf0f87c94270bcd3655b4143e17248a48bc706cf9c7bb0922f3b79e978fcc17dd8595bebcf96a2b4caeb2b1c4272e6ea462f18d14e139843e983e0e37bebb37033f2963b4487d8e988b1cdc2367235f85b88d004ab5951e1c86af809485a299d8217ff20f6c06300797dc502be72b7a66bfd127ca350f5a6612964b7b066617a275e13a5e055db2dcde2a3cbe67219335a4b4d30bc255b2f257711469a3a754b4f82fa50e508b0f3b6e62d35365aae64042066954f1b6a001b31d9aa5a591b308ca2d2487898249c0c64bc4fd072a697867a78eef75ce985560aef9b6aec38f9b0cd8b06c382b358c23320f41e720243e147f38740be5ef761cbdc5d27d91c17bbb3309e108309eace7286cc88723fa8f4a6afc7502652b4902bb90efc171cdc021fc207da4db06fa5ae00bbbc4c4a79189458d06a5b5b3ed75919ad57b377f21bf549a58f0631dcc567f0887272384c6ab8f030d95c28c0291594af7b50301489cae2977eb00e655f5a16891f0e55ad7f5bc1d3da33b2a14e1e60a6146b867981d8f81c4727165caa9f99107024c96bcf2d158d372f01769ed3919bff57b0ee5ea5b4c1744851986c9358a715378bf8d64c2c153f0b9cfde8c0285c1325007c95ae5579978246d8955aea939119cd6a702aaa1ead8f179344da7dd8e6e13b7567ca8fe59b5d805caf39eb37cd721d790a5d601b6ed9ad720b49deaa6a8ec8b032eb048c4bf07acef1f49076af497b33d26cfc2ac8e8920431116a944c111c35a576b56767b025dcf0b6e76d4172e456166a7fa6d3343c5a68ea9d9b504db23607cbed32e9677fc3c0f3c8958a72bc5f16f34e5cf4eb275cdef5ea8106575242b3f8d24968c5c052643945d09e7263020e57bd678feb34af1bfa74120786657fce25c86b0481e060cf103e2ff572760603854ed08294157d3ece81735b3d6d83897ad42810e365c94e321a89c3558c5caf23078438f26ed3837dcb2561d534b0a11eee4fc5d66ec19ce53b05803df3e5aac4b4a81d3fd924eda822d6cacae80ac0fcfd53fdbeab03472c04519d72787a79b4f630cba87bd8337b126bad96342c3ec9f623feaa9a233cd0d00b2672a5c20beacf6c1236ae19e44de236810be490e450a2806b18405549cb5509291474ab67ad53e400b781ea16aa76dae306cd53fb9428654cbd1693a0757cca47728e762bbce05f38c474b1e09c6d4e39ac660c6ff240281f00f375f0c6f3e3437205435307a95818c62480ecac73dfe57d65d6ac6c27cf2b8503cc74f4cebab60a2c9cccc32559345ccf694a8df9beac10ab1c539ad80ecea57483ce1d46af50724a47c11eefbfb1758ca2ad4106bb646be3a7d5e56ae2e0fd9c905cc37cce7f544867cc5bfc184994589d414ed190ba3d0a053445a532716433192d25991b1b438687d912e687a3b7957883c42b6572a279c4e739652f7f9fb5588309b326303afe6fe1e8d7f531094250e557e3387bdc080c834b69715bda8eec4d1ba8c7e772a3ee7a1d35a8afc5e8bf375491d5c320cf28a83607fe0720e45b342a1705f437103c52342ab3dd142e8b990cb9cf2e3e396cadff516f026d56f3975988b8632803aa19771593dc90e302f70d44949c93860b1b82074e40ac170ee41ccd569e72d9b55671cc5431b7d3dee67e0a5215b59dcbd287d30d4a62ab5e6a807e463f238466c1d7a67d5cc3941a1cf4b7e883e795caa40acab620009d7e89777dac45157272b13f1962ba31a595a828bcb7bf618573891ee3589502cfdfde3b6e6bbd7275a6a2e89f029c00c685c3ec85f229f0b37f5daa620300bdd83285f7436d4e72c9ee6c261cbb1704a7434595e4734f65e95c2f32f3601ca2fd14ee9ce3ebed729b60c9e4fed7ede331b55faa2cff7c6d7f90d354e1560736b93a2a069f5c6272640dc79187a7d53f4483bcd60099c7f39e152268e564b04f1cf42ceb3d7446724206fe49973750f1223c9ff540a102f93719c15da86af7b338ef78f872de4f72e64df51280a0b6c3fb9f0527074aa2d78703a9ef75a9d06537a771bb6fc1b772e39ac45d783494103d8bb59a7259e73a242a08338ab815d8f111c87d0ab4ac72046f50884ff9c838ef68b0fbf47154e1face79b032290158ac3c475b1890d572c0b21585b3712e5786e69f3b416f3a568544120b43f3168a3bbd56e62df68b1fbd9f26cc134bdd087e24bed08935a30ef01980ca38fcb577e09ec5fc9f51bd617741f82079569e3d0a29598bc18b1684dba0ae3ebce79b2b3cd03324062c6b07a0ecc38a275e864ff499c1e4d054215ffa36e8e4d4152e2529ba47df1f881072f0833625eb2174d16027891543d2d19ee5174b74fa02c41de5e5544fe13d7c70c7f03658cb5a3450531f48a761430fcaa87bbe97e2894b70e7e67dbc10199c1c8834fe718fb9da40164a38168e7e85e12b7644225c015b998b0d1601f6980c4ed9ac56b1ca82f79778f458f65c32e4109dfaf5692882d2cc26820852e5031d2ceedef1e4c3bef27c32d29f216091aaffcafcad995172ddf7baf0d431f5bff802f274d3c53f76377ac7dd8e382d758bdff17e796a40d0a102c3270d5f6087fb729726dd2dd2acec45e8ffacc01a7647322c049900a59a39ae224a2c76b1b5d77207bd15292a7909c8b3262e64e20364d6abd843caea458cc97f94ac5f530306373507644a009f71831f0dab4840e9f903b8bb6ef6980a6293b1439e89efcc8b55684acc0928e7709bd6359e2e65b70e60b64faba162f4e62167334061134ad672f5f357f64dbf96f627b11e14ee3bf6ddc0b6cf0f9126523fd636ceda81198872d6488dfd838f1423116c7c0dc455c1a3770e52a016948c8f82fd26e5d45df4727bea12dd9008dc674e1dd0d866444d14b0c81afc835ed82d050b0938fa2c3233d96656c650d4aa2702fa490345d1a61e4094e549a668f1307a74e4bac6399c1b34ef7422dcdc856f3f453e0a355a2d4a9c8a28f9b6df99227fcd10e7b3801019928070e52792db364923889494ffe1f234eed5d236df507f00d46416a9bb1172124bc319150ab97adba7bfd737c8a32164f03b6dbad3284612dfda7b6d79895c3dea65fbeb53a3401baabf66773ef6c286bc71a4b7b72533d163e994595a6572a46132031053932755783c85661f87229d7534bc4c4f9ef3a76ebabb5f0f29726b95e85ff163971d23b3082b597981c3dbef26c674bf9f2baee3e48fe29a812e92e94f5cdb0c13631dd2e5ae7b1bc30dbaca26c26459a439bad7d3bf47b592723bf96fadc0df7f925211296b99bfca1c931d0933525398365d9b21cfb5fc06729a38d1fe50a2029b356aaecbd21b9244ae5ce2c3cb89ff4e4b4883b996c79f72ec0173504275d705860d22a22535b858671a52989c29fd0afc833196d9a33e10d2c84054b2a60e09040939f3c68c29603fe13981e686567416d02128a4fa247249ecedbc672262eaed7e927a13e3e01bc1952142451d926d43091399313ac307468079ff8f9b7e35805692c90337a1c2c788437d2d215092e12bd9d2ac0cb572a290fe46fe4a702a4523a86ac311b84e8d2b6ffaa8439deb48a4fc862c94c51a8c7805eeab3919e99e595939dbac1bc4551492c62d30d7b5081b830113bfd77217f499d98c9b0fa919829d1135b0bc19625f433e76e20e82d47f5ed575ef330bc2b52d39aa0f98a24407673657d5f5d140c280eb9c243319c392dea26d9792573578ba5185ab352dd2cdaf52a8783ef355699596bd4ed9ad6c40a07fb69e674e5188616774474622078b51605688ff2d622b0fde79cc68a0b19b50e78d30c372bfcf1c0277c7875ce96b22fcfb1f0d024dfe7673b268e28283a360cd7ed53c727c1238d8931d3197a692c043cb675f42f9c33cb63ec0e029bb2f57a7540d161b1bdfffa21b193e5ee096e1e10fac63e822bb77112eac02d06670346d64c7797223d5568fa3dbaa36d8f2df301b750fe21277e7ee2f2fee64a68ee4642db4084e9425349d2cf82adf3c6a37590a4fe23fb829cfeb3395c064f650b2d3d67c1b7209c03e4aa90d981a6a3ab846ada3bfa6d37edd638866c21b4f7cdf2f96397d72ed6eab5a6291e5ae14aaac8e0cfae4643a5336529f4220d579746024a3839772ca51e978f7977195e0613888ab15fdb23a224f631529b3293d9aa48bd02be872f9ef245707deebf606b307ae3d5f042a2a79ee7261c1f17d34b12d6f2a6c18148314eaf22bbb4e04ccb4e8ba763ccd995e173e886702e72d284df23ef4e78800c5ea8c182bc82248c5ba107a8d19c2a557330ad889707b49fa8608ca57de1c0073e534c52d44b03a6d1bfc54dcd4bdd250176cca03cdbdbf66db92519816662ebead720271e4d1b0bf255fea4234c0c9f776854d948e5bc72ef05d5a52fe21046ef0bc5b49e07a9c7c5455fa06c4ef40b2138ebdd5e4ed4a39fa023bda30ac7277d6721cec7aa73dad977aec2f0550bba8694a936637a494c7220cf95408c2720ac49f7abb1c1929355513880f8f5b15d652681a12fbec30017cbe7c3fd44112d8bf81e6cdb27c4f061d1b9d3c8276013444cc921b0eef91e0106ada739998722541f65a4ff5c2d9f545ccfe4c8adbc42114b96afa71d3145d26da925e810a72b0127f3ecaed793c03ec26305a7590ea56dfcc330674fd64c4b2ef82a73c5472772cf45f4e8261ca450d24d74336c87c8918fbb738f20b0aaff27c55e5650b027404ecff17a071a5cc5c6cb0044a4cf7e522ba2e772ce7719ae8a63f3e624d7277c927f75014a83456aedb1e50bf85058f5172cb8036afa90a232583593b9d72897254c5159ec7da365700df457bdd72ba07dc5f00a8b503467e4c8a53f83e379cb1dc8ebe98c5bb37c298e1fdcba1adb3042d76a3948f9c428fdf1a5d788e7213c6a195f75e3c808053046bca8ec58220f9e71b31fd3873ac8d022603317e7267ad6940a76179612191b3da451469db81341b499a22e8a52384b0dd61567854fdc5422b9aa6311e37f92eb8455603da3c0b0868bff74d4b6d24270744a28c1d9b85918b3013aa7a28c40569141eb4b1446ec04e0d7cb6db0b526608a1d0ac2e9f56806931c0dcf54dde7553971d33d83d4c051faea461138f5c7b45f990a756a2b0234406698fb96f11a07bd30522c5b2073091529dd33cf473bc7eb2343b5e072dcfe1d7ab9cf53c719c5856110be54b5390036fd67e010b8528f1e1546d72fbba17dfc21dddc0739104161c051c0d6b9dc07850c2196c9bd7bac48d3e1f724db90e90fd03187ce51ed858cce85befd11784de8be0f8269065d7c078a4875c1c36a6c90e170fe7dd90caf2979a42f7ddfd3f66fa4212c26e21fed8f7d08d72bf75f9a004212b8969911a0b1293b7390a5c49ec06b43877f6236083dda2394f618f749eb1afc57a045da1546ec2fab3fecfff80c0b8b7380470e48d5c860b728a0849d1e84e61751c7d6d9f09ecb77f47f219575560ebf3ef8143438a75f672612bfd663401b6429f2c99aeac01945a278dc71484781f0856cfb5788348e672c0304565b9e3a537150df6d6d4a1dfb83bedd778c97f4081ba9191c01ed604723e630de1d1b73cb56872d5511ae43595255ab9cfd987b3a05d9ab7936f84ff72dd3bf7f3a9f05b64f72a689be76781d62506722037568dfc43cb265304ddd5728580dc9a0bf7f0e39fc5ad4933cf7f08edfd47552d12aeb8209da9c2040204729798a6417ab0e6ad3ff6877af7ce5bdd6e3729040287d4585b6cb4779cc89a40dec773758cec228b2e9df19397dcc791869cbc2d2e91dd95479cc2b816140a1ba2794a2ab3f7aad0f6d3ffba73de93fdc41efd499a10f492241af1cf38284c21b27cd06f55c87fa4c0f8fdecf1e650399c4f28999e3ccf2e9ec75734241b1172ae7444300cb83fd2babdcb93f2995ceb993896011f8537e5cded05f12f7260720dcd0d72ad46733d43866fd093500d6a407a2b9c3219b625ded401d49a2e47720c18875fb87094f65a298bc7ba5f13477de5a463ac1264e8061e62ae3eccb43bd0f8ad144913a9347f6ed52e286372905b88974795ab1425f691ec2bf592b3726632456b833fca4261867f329541266ccfe36f244bfcdf7ae32127ad1ae28137aa5e157eca276d548aa16dd7e80b4236e6f93f0b877de5c16226280f28405819cd573340ce90c048015b91b90fc503ef7f7a71b4a1c4d4d7d9dc3848c79acb3999524f15d931c87911d813f386e1a7568b9d6a5061204623a0d37568377040725975fea6a7103a07edcf73327d6835d71ae14f06e277a87694483b17e1e3077208e271f7ee83dd57aa4eeed10001dd1cebb1a3ce329eb50c7d3f56148fe97172b6b0c3e984e5f4a4cc41e2068e4cf89585baafbe24a63b6b850c712cbf14ee724f13103113bc05859304caed2a3449d171946461e2fc0ff8b4ca3e9d1b735c054985931acaef7b4136dfb7fea43b2331529de740f664baba301f036bc703c24475ee3bd76a5c1969e3ad4550564dfad683ba762b5aae0063807f3dc6cd18e8273d2a555137bad7fd09d60ff07ff1c19b42824f1a7c83f9126089cf37c1897e7217603004f469ec5d41d485faa58af4a36cbcd8b5d1ef1c2d32c4516a9e9cbb72395fc51708c8a429597d5d773935e9489edddbbef3d71f4401cb728cc2331b724f4d0dcad27ccde492993727ebe6a6b83492914a6ef4c1040768afbc870d1672cd426fcd61dcc40760f9e31beb1c9c6edd813d183c826d4d433147dad97fee720fa48d5bd90acbcb99d9e2c650294d0beeb1489aa8470bbc9fedf567d331e3721699d973a4df359063b72aed0237da634a9cc478d858afbb8f39f63c86524d575aea3f058e36ef8dde4a14026563efb26941e4b544452abdd48a601dd2618b7299e258939f09cbd90664fd618cce19ef8574994bc74f4fc9e0877eee0511f43adaf1daa84676b4e7c21a5122e2e47d015f0baef5b664e7181d1e0a81b2f8127261fd88834061fa3c83b6ed4ce9c5eec7f252cca05152354827af509f34f64030e801b30147aa877779d03db913c73ed68257692347d2dfdbcd2073a70fad527213dd93100680a30ac1f7d90cdaf6df1a38cac9524d891b54c65742ba3bc28272a3bc08c3af152935b24f6fffe4f1e2dd40dfded2c7e9b9dea4aeed872d68bd729f2176cef7565ec1bd639ce9529036772d4b197799e2e8fb9d52bc341a0639721efae2d2bd4e7b8adf5fd592c1738afc732d86d57e1a4c66177cc508beaff453b8d2c8ca677798bc477990e591473d4089736f0e0d8c81040228ce5dcc6bfb5deab7ea408858829c66509aa900edc2c1d0d79d346a5aacbd07afee3b618dd70f48cf12bea30895cba4228c9d78c3bd3a9d29db9c6c519a61d51fd2421605e0725b5b72f0ff2eb71fe53f8ca4fc11b6921af71ff7b946dbbf5ee9ee81f09c2372eca69b8545c6ef26ba6d3b585e7e531ed454b71b6619d0529e01a2a1a16da3083febdb8a55f13cfc21399ff304e76fca054cac84585906fb9fc5b4d2cf8975722051005b173c4d927112597844cb6ccca60a32fdd8c4643249ba263d131e557269c77da9835d996c99a37c65a22ea23c40eb4a1bd1c7f300da2367cf4a553e32f2473b18bcfe70bd611e0140c756fe7b389a6c8baee98824a94793b3f1d06572ca8246a3b6868195bea2d601f61b9e0898ee27beff1adc28c4014404c960894a608d8fd5b85ea77c97e3fa14b5320e890d43102dce2d3b98e78d4052151c9d4bca8f70c9f122bfdb0dbb05ab2bc0342c41930a2ba50e1087e25ae89c1b7f41727ff8b751b2e7506e0481ff53c1a57fe23b4bc2b7d6a9ac0d0f8b729f2b88c2722c8acee8ab1b9c2ac7979441f0ac097510dfdc5a77cbf23dade81055fb5cd81c7e9c51cfd8c4497d7c82535f52c56900dea1717e442cfebaeb9371c3974e3b72c2299e3f11b1df2da3ee68f0c14bf67607edc7070841428547d821164dcc47722c3059535fa2a51730eeca191f3cf70a95f93f383d5896d19baefdd7de7e097201bba031df7c5e6dfe0506222fcdc4389ba4d9fa51afb6ad4989589765013b722418239b2c6394d400ffe7ae02ab95d0b719ff0741998e62599eb73771aa3c4b51bc13c21b519ef997263054f0f06db9e6a98acc0e06419d902ec5c769894748a0e22680269d7b6ac6643e4b091755d6ce6ee2725f2012babdaf56c76810b63274bacd9242d46e9ce39babe905d1f6a39984f20aa8d8a141900a319698bc831bf419a8a790bf6128771a17cb30597d6b82c2eb0ced3070f690893e677978b13e25ab1a8817de8f2d49d18ba13f5f24fd13e5591369d0646925d813cd0e7e3472651c11f4340df5cc43334ed7c25c668c08106dff325bab47f36f6a5ec9ddbf3126b201f67b0c38d17ad1427621d12c2f6ba136d4df5e3c4a64113766ba66b7724b070d9eaff6efdee6b4cdd73d6b3fdeaf258f4f00de2bec2fd89a8c56a4ce721b8fe9d03f17635942aee24e5079c623c17c1bf9be1d3c1398e7013dfb5cbe72afb19e88d49213cbccdd5fa2f5f4a0caec73803bff911eed2041923824477172e03fd1edf01992a4c41b6bb403608309f8a157b7859cac76d6626e19b0d1ed72c3ca09ce15575fbe3593af62b1a96db330858be966829153cfe24a5ac14f567211deb5060ee56ed5ab1c5c93e55e23b0bfc7c19c0333d9ee30c540c340e33e20bffdb1af5e36339ccf984083de769dd6988e6204de644ac21163c866c99356729bc4d2b4fcfde949edb65ad80997a6947848c3cc4f38372be2b1e1fc5984c546b3a687f5294c9986a121366ea9f44777906f4124c831ff434360b687656d3c08e085cea1a461124ae0e479b4ddf7435e0ae0325a1dc22651365e1f8c548401726023f560dc8ba1ff1ab7fd0f2e858799ed25fb648bd7696870aca1a8ded70c7211b9acea0db11ad8a2de6cb7376363afeb439ae22f0432f015567b60a76b3c0840e3b0cd73db3b2b71516953c22dfb9eb6a0f80cc764e373a3533de9e822a67261d7d61866ea966d0200aea617d33dfe8a1eed350949c030a5e5f3def669806223b5fdb063feee4cbc6be9ad7e3b424cda75aa265f7d55b69a96e47d47c947724e243c6cdc4ee11f0f342632abf2fbc31aef8d703a22060c67463ed4f98e9b72faeead58259a37b3722e5bd584588a9e6a7cf10c0ae22309e85855eaf59857466f7884d30bdbe2e190401a614e317b246bbe7130d10900c6fbc5d5c2e4413d7144aa25255fff13329653e59b260f957fb01249b2b006a3c8c080718cba6e362354b8045d6b8b2fee228d2dea665c17975d23d30ad4163db7c6ecaf7d07894f1b7af78698d4bb4f7a2cd5a31ccbc6da187103d497fe005193bf65591e3ef15f7263741b8f6c9c588b96a3b6c696a10a3534ffbe656444eac9bab841c0ba055d48ff310934e9cfd580454be135af107876fd21ce419a9f037e37288045fddb63725a381dde84d83f85f93057df6e9e3662460a65c37f962c17b11e32855635367207003872744c875b06bc87426516d2ccd24899df770c7603581b15a138e43c72526d654e3be1969b79ba1f0948989c259e866933b6ff84be674e44ba6294661d712f772e4188e8297b08a2329efc77f0bedaacbfc20f9b1d3f0dc83b3bf1347250232dcd0e2fc20d5700a2744aad6cebef773181f2f23bdf9d2beed322c8656ddeded2a4d1559357c7c0b472b565f70befcaef51ae6b5a1e527ad0cbb412ae722ddcfbd2d4cc3a64bad069576b9cb4a426947c2b3cfc52d6f277228fe57aad18ecc09ad19f3e3ee4924ac391ac7a16fa9b4aeead8a08af55aa76c636d8e88972a7a005f42d372d98e3893ab97d8533b930e38c13bb76c5a4f272d4dc41570172dc1d9d4a2e968c7f428c060e494c41ae3c067514ba769e9aab3228b0430a877228a881400dc8600d1e5706dc8a98f03038f22ce7c190ce415d0e11fb3662391dbb34deda0e1e0a1d51c4130d17b6421a635c9ef23bee2303eb956729938c5b725ff33fd3621ce333f6822851b80d7a2bb40862ee87815d3355cb375ec5c9c072c1d06fb5e6b23d257747fa85e65e2b8cb144473ed257f421893e4ba63373a67211dc7e55b88a77a6e270070a261a80bb1efb4f49f75c415222a05643255d29222c86c0498a032e829ff675484fc75fab4080ab022d502f17c172b852cca83a3e75735f35844aa0668bc7d83a068e0ec4724751e09e831986898ccde3af10dc64a3a01c63f4eb63dc07a928f0eaa4bc7faba131ee0cba2992a03c408a5b777472873d8c37e4d0d6f02d978f2e48b4183096da2e99c566147da9d50448e92b67726e597d2c9962f0475083f98df5d3a3c91127c990b41be5d7e3150bf5be1d5072a018b5ef189f11c10f86dc6a79b4e5da9bd365f8bb120da0d3700ff9253f772b287f79280f6dc399a3ce6b509ce800511c0f626e30fe2d9f8385475abef9787206586dec4733a73309054db6ae2f748d071669836d86e4ce9867b80fbfcd4513061f4beaf8059bf72cfdfbd6b29edecc51cc94b6641967fc47f1a1e39e83dc7291bdfe42fa83e3c76e62e8f1cdc9f8319deb2001fc7ac728bd55489a2b41bf725c666119e0faa09ff76771963cf8b799cdf5a6afcb505382763ffee5a54fe672937a50d3a324551a720cb0a8e6922658413f2f53651f5ef08a205d23660ec8722b48352986c255703fbd3aa062588ad52474512c6fb7313d155a70846407187205cd6cd6c27288c2a4407e37e6902cfba1c9df8e37da510e07159508b20e913eeb1fb2fb872b96287515d922de4fedf2acf7b5d37d2599b69100bcc2422bc872c0fa7cdec02e81d5df1b4fb422b4dfd3f3506f6ae788134bb13ce379abf7f948c71afefb5bac1511212435bee740b3bf27cc28d3d40c73124a83c4b24c5f50396bc13c582a75281d58bfc9f2c3527ad54c327cedd8787f7ceb8309c6849749727b3a2c06c696a640ecf26dd1dece92f88e5c3917e559e417a10be236cb3e4772b43c4e4b89ffdbdf323e207f9a11f975c6f60ab0922a0ce887bb75da1e995026970988c8694206b113533ddd665aaf5a871ba6b6fe2e59b5c175c954b90cb506c21d159f6f390a044ee800dfec6a830936f9e5e2b36a8426dd18a4ad5232b07278893114dd7ae241cdf7b17fe99d37ea7d6930c907c1b7b684e585524a50632a9eb76748fdf3195424bf2063ad24caf114af63b3dd005cd8db62eccdfde90072b62ac41a1d7f5250543ec8f6d85294d2e08b85461b8d4808780b0c58ee434272082efe903c336e8a509bc2fe915c3c15853b20e9d55bd79043b9b88208929772506be7436a085a28812877f086b5363b8a2e7412e387b398f4856cf0dda3c872da7ceaea479276f03f9f8cf9fb225ad2166185ab25b91dc2584f6747b52c6e72e35c315a250c80f8de6f909d7f1803df9d5f6b7987aea321264206be67b7955acbde7425db56abcd1d6441cac667c4dff7021a614badf7da98d002caa04c44721b78896c3fe1d957e8c95b1f24cc5bc6196f8a77c03b65ac0adaeb5ab4079672d8442a49a2e3baa28deb7aa78c754d9c263398fb6eb9191c421b67598bfd20722914f6c078d6d3c4f10e80a94f953a21f340edba904717da2a66d1405a5bc74410886c2ac916fa75ae599c8e70508266c017e8a06e407960381d00b6c7406454c0fe0aa7ad57aa2ab9fee172fdda14f1fe5c0e18a913679a16b6eb081df624722f0f0074a15a1a8faa1bf334f4a029fef8b2e4a1855443a899144a054c92616e07b74d3cb431b30072a62540ee04c135d717ef24d661845ae624f626df14216461762a510df73c0a1814339e9ea5a3de39ce2d47e1da3408142335dd306f587263904379c6d62486117bf466a4ea76aa8354e737020a84aff76aec95c57ef638e392962e82618f455147efd9329c74b2bd9b3be6d421373bc5e2903a10e50e44f77cf88051992c61e16d74d74b6404c3d3ef41cdc39c07cfa8d9416dfa2cfb72832e634ae5a672cac828bc5b00c6323fa8b5bbc919867b6e3f6a9929563db3096f8f9b8add574ddac40593aa9e8a04d0185a4cfdfb3a2a7f1156ab702252e172ef2e1dfeb883708dfebaed1efa38e82c029270f1bf83bd80b9479e84cd136021b44812469b05553e8f29061c63513c4bf29132beb113200933a66668aa8a6c72831ceecc0513d35be409867ae8578b41fbc722c40047375894366110d94f2949c45c83c34bc887b952ef1369662086c831aea524ad3b12fb69dd49b0eb1df8249aadc5281313be274be8aedbc62c5dac4f17acf4208d7afda886cc7f7edeea7281e048edade4816dcb1913dec40d29458668d5589265e19f3ea69cd2d840427201428b456f9b350e819cd7d87130cc03455bfc259dcc881c7f43bf56ebb5b634b23804c7743b04555aa42287fe55c745901bafd4841a09caf66ff24ef8642c720f96e83145b2bdedf2a1deb11cfbd06481f77a77e209c0636ab7ac46622577374a056a7c05494c0693935a1327708a5fb8303ab963507075e0e3acf05f14a6570b143fd328021ff4b12f6aefda5f10f2a6b28bfa62c844ea364c127d8294c66d097cecf1638f7eb6fe5a0dd6c8369df35a348575404ab2249993ab1b8368a072d692b09715b594c585cde6c7cda320be8d5b3912e39a4ebdcf0b30a494ff616937e6cfca2f1bce4ff9e8d2b957757798d548a8e23b6a88a63c7dbfe48e954148c4ccc90bd5425bb6cdbc13cbc316108b21f9b3b6ab4b58bea4fcb516b9cdeb44ab82f820556df0786cf058a54e558204f933d0d1ddada306bd3efc5928c3511f5f0dc68de0d55d867ce484d57f48cc0844c425c0761eac7d95806b8158cbbf72632073381abedc0de9b6ebeb3df6dc2240c39e95dce6757bb368b6a67e41632ee4e2be5956ce0700147e60510bf4b38b569b15f3d9c490d45ce5de23745bd03a3e123d4b55ffb2b2dce6cfeb21ce3cd35dfd51c450fa06f873c4e9ba0cf77972e8d42a67486daf1f1948fd9ef7c963c9f4c05a41079149719d240d3d9c631d72765f6d206c4a486e0e357a0dd057d14191abd38b424b7e94b70435d591323c0518b32e4f1169503e3b33b966d7ee462b3387a71c0c2e70ebbe1deb3aedf6a1721cf538f26e96b28092b3c4ef2488d74415aa74f9f95d26549fc948011e7ffd1afbc78238c3bdc89a17ddf39deaf55d5bfb7776c03eeff043132db8c293a4821e7ee91aecb3031036d69e58ea78b235d689d33554c5edbc41cf94ab1e516ec972b29b7a3f8112a1652eccce3b31df19d0b72946c79175c6c9854e132540b2c572bcb0d6d99df947ac6ce159dfbe958a59e0e3dac23d044bdcb825195bc0744115272b70c042c18582a8b6e268861c96b072e457ee7f185c5629e7f26b7133674c4d967cd2f1a8bd224ab143ce74b4ee639ed176b76357fab73858c5449d7edb72efbdf295f2972d8cd6f2ac97905fb54616259bd92e2b1f1d8e35bedf78fddf72141456abc00516da5d79fc361a0cbb3e65325d0b8ccb195d5cf4ff574c78682970a109f92d5910386aaaa3722a266166bb1feebb5f83b0ad2ac6bd58682cd27135c419c9c4d2e208ff1a422b6b451dbd28894b5e8aa2ded693cab1559a3ebb355a992800ffee2d998f27a31531976d84694fa7fab990170af076c427cff0804c1edce87171e1d39baf6ac776aa8dbd782368a6ba85e74f7a478c9ddf52dcfe4a65da79e367f663b975271cad44e77951b34a32247424b9d0548e5945e74010242ffccf44857dd132056f580a8915dec823f73c69ed1a316317f1c997ce97bc4020ebd8688f25dd70ecdd6acdb9055b1d8eae944a340122950c015cda98395b352759d6d79e25ad848f3358031d1d3305d43e0f51e161914042f45b3811297d72377165ebb01cd198825f8221b750eabd6058691c953902463cba9a443972327266eb11728462664688953c1e9588ee6a0d89bc37a766b9bde6bdb0beb3f77c72a7f5046c17fb83de3b5dc7ca7ab5500bc05d58efd03c729fab040b8ef26e65723fb1821d2641e0655fec62e41eb26f4e75d4d289514400701d901c00c8f7c8039150de31a2971ff887086afcd4c1f316146f5035d6924fe9c2b5922498b7db7258cddc5fd1e84fe11029fffd7dc8c16210875e33108de5f516a5d817a18dda72e26a96739a1d514ff78bda7db619934c5110eaa18a9afea41d930a7d9580c9042b30da2b1e70683eaa101c41f9a80880dd108219b50d7240af0ef5dc14d25072678779d2339a7d1ae16952bca35917ec61c1f37cefc133ce33e1eae1e1bc6372ef7703cf15a36a491b19e76ed7fa1b4f662d92d4483f2dbbeb6d833397ed34720e05e41f95057be916c98d1d73ea1780cbcc9a17daa4dfb446496ddebbc56907a4c3849488d2af42f1c45e5b2be93aab07135e93d11ed312e7011215f684f0724a7a2f25f801a724c8f5c15560166a10c58754bedcf97c01deee3696f06aef721d2ff614acc399f7d63c143fb373bca9335f4c316b0f09a5cd5b96282d43cd7219e8e6a31479a4da745809faa3366aae0193c6900d28054025b9f9f556857e72176276394f8b04a9f438b69881b3fdac586c64c857f7d03ac9300c91c07d64726fb9aaeca66a767baa8a343cbae902a34ebdc53e24f76508c3f102c7c0d95b7287f30ada4583fcbdd546a1589421f23df729dea6e5c4edff52f5216bbee33e038df93e517691218d6c4d922c334d8e7120514541ad5b63352494f5e770257e116ba2b0de052c3de3328a3abc4f0c3728fc0374c6d54b3668b52e484b05dbe804b8ce1936b37af5213176fc91f34145245bfdea8907d968c1b0bdf10847be7d5e66a8dd39839f44c5a2903f733993defc3f681894f1af6226e9d03fdb346fb615028e53f24379eee6c408a2e1d17267afd9a4ebf267a7755406724264b1d5573e35c4c3d013f2e3ba70381ba96d74b1e79008e1a3570fe6926eed34e7e87e2b53cf3207c95dc7ed6b59d4328d1f910a4605d30c21074f91f43ded4ac0277abd729b86b0dd2fb973191125ed596d9f64ef6d116901da302d1cbd72c6b057579e5f66179daf7981673189da43e8a30784c7fb8df6945e4df303a280548beafbbc720eb01e0001f1e31fabf1727017bf7f861bbba0aa1eed58ca71575b94b31d1072ba425ddddfd39926bdcf3993efeff215f6e7f533bf43cefeeaa68f666177cb720940b535d686f105767af25eb4d9e894a102b0a8e1ab8cdddca4b0fb965c7472c61d9cfcbf57908d765e937c861b1e07a13550263d8e5b05e881b5ae80f1a572d2304e29b87fe724086ff7f847ce7f6a86425f97f5dac16354f1044547c69f72d7ef0179dec0a25f463f56564f315633a6f7e7d676295316fe39509166fd16725081ee52b74b4f2be685de3cfd18087f05f8f220a65145fd5d5e91d4294af972e9a9efa74a83e1fae9550bc066fb37071b6034e11c29497aa9afd5170a486d7230583f51b818943f47fd999ff1be909f686d324fd91926a6a47e41e54df16d10cf05a0afb43ee1ef5991f38b1b9f17ce73b5c1827c119919e731d2c34f2420727867a9543d1112e1668ea2b2d2d4a77c06adbb33cf4469dbf37252b604dc961c2424aec8eaa047a844baff6c36f023f768f68ceeb394de21a257cda027a7c872c87d708f1e3c9b248a6052bcc273c07a7187cdb61b0cdb197aab9cb56138860fccb52f61b0186498b948b0e636a10fb4e22cc0ab281660b2857a0dcc9401e22a7c3e65625ea6f9f0ad9e80e6733af12bf04fd4fd0690a0a4b27dd4b0eb75a07292781aff8a33cc2d0ee00e0bee6f1c839fa2a2c3a92dd2d02fb137f8e0e18c6769ed1eef98dfce8b110de500ad1a9f523e11919fb7da9bb03796b2a2aa50c8728ba059c404de90d77e9374303e7fff63a62012c5609f9ea643b11e9bc0f074727bbe0173ea6772f9b50f9293418a53155110d74b2fa87a0d405d70e49dea2b110311990e30b60ffdeb84a72d9e969612548b1c136587d27f65aabb01c80aff4c0aaed301631eb3101d0ee0c8bfe453403f376adcac70bf0eb6772720b00356720203923d345491601cc9de8878a94bb9ae14d1377b57931575a8b66eb6bff6722c765663cf45f37ddf6d73c23c38035ee6420eb86091dd0716c6717595cb25349b67e371a8d38e829037383c95ca30c4da24f8affa8c8c836eaa14ffabaea90e2c38cd20886d737012e435633d3151ad7d22a2bc577176c210a61d36eaa2d51347ebd6c86f5c5af7059b46a44e83edb4250e24d8209ae3b8f4045b0e31de7759ef6424c991533c5a320ac1f6cbc86190171d4c3efb3d9967f4439fe8258239728aec1d749a3ac7d0c4843cbef90a6437878a52716cde5ab15fe691985036e2727013f4fae9f83ea25f0ef48eb25bd532b853a9d6ff7c4d4b24974c0af9f53372a67dddcab241dc9b1b5b502ff68f3e31e63a6993865bdea508d7a67b6d2d8b722f7d6e0435ba1d3f1dc77eeb1738ac6a014d5b92129a4d287914c9573db5ba7211ebb73688a4e1791ff5ebc06d39dee1682d0d0d0bc104ad2b21fb286967433508f4e6dda0ef8dd079c2406712f2add185c62daf6206fbbe520d7e1c8d1aae2b636eb8708820dfdbc9433b62e659806981510615b34e5c35e6fa339c87c07308c3e2d1d6f974f77100883b858168b5accf7fe24f19774d89875cb70a69be190d1767eac7f7af05388a52022361bb722b6ce5e4929dc828e1d549c65da626a11fc54c1c4e026b9057af5d98de0bd57849e117455dfe8c1e5212b21192ed157872f93ed516b3101f2e52d224d0fafb1a5786104853f86eccae3dcde484c10497729839379db95d725f6cbacbd6ca51ed1dfaab01f429174280df8277bd7c903e72cfd29d3f6cd7b713502a18c03ff462ee18781e80e047a5e3e6ae2f391a187c5ee2548859e1555d4709b9728bf887971ac67c8e44b4378eb4ad67f503d9bd010f9ee0ca08294c64076efb62062bb57388f03deaa6632bdb3df9e8d348fbc9e75065167f7ad75a7deb24fc7d22f42480ef990103a3dd18abfdff6d4a3e7adccf729dfc367391c0dde1a590b6740aa355a6862dcb96e2714ddc1dd5354c3e9eac2369107431da87a48a2b94feca323cb03a8398ea8356b408fcbb068e0426511072ba8da8a448d5eda09bbcac404cc7d39b6224b8d98cd42659dba9cf0d271b063882c405ef52aa6194250231035a6653c5777e65f1ca11dcc4a35d1d6432129134943124f59a73589f96ffbfc82a97c59a052ce1db77f9738337dd9ee0564754723d781acbe31693dae3c2a256e5a01903def8cc1eb297ce10e18547eee783c9654e285cd1541b2a4dd79fd18445a7648ee8367d3b298cbd5cdf074bd1c015472e969b7d7b837746c9de491f5d43e29223a520b97a9d10d0f4c9abd804ada2130f8dcb447b7e6b92d549489346d3d9b73f51bfb34eaecc4bf5d04f2d2030c68d322ae3adf3f8f2194b9ad3301958b1605b8eaef310192a49d02baaa2f978b743727ccae5db36eb776d4fec518a43f1c6d514176182562a524f020d23805d0d0672980d0a42d2f8123bb8840f014c090eb94f67f23387f763dd9a4d8fd91d8bc4669791fc0be3841e90a9e9def37ab0e99cd259b7c2365bced8eb337c96ec691f580534bb25866dc18f821d1afae6b3c7322d46f4a918a2993e268c8fddc8085f1a3ebf2b0d7c9f7bd70c13143085240516621d7b94dc7f319230065ed734809d1a655e1be6556e54fa6f5a3d3115fc3255fe84118dead9ba0d19a2f52ad7c1287280750b2b9cabb83c732bbf1d1a3780246aeb26b1d592be41aae4e8f09088ac02ccb133dba3e750c654089702ad39735b4c17ada85cdae8b59a4e463b0184144be9a126b6c1f7e2b914e7e33019e38f28ae25ba03677fcacca74e50da02691c701cd1ccdf0f9d95cf46597cfcf6765baf4d3c81612274e72be181cc1de27f733ec6701a53ef3b046cf55bddd6d2232ad1240d7dca575df4084be406887f03a672049a34db6578c0c818f817b0ce94617a21a9d0c0e2d7abea8f5f8f7db60cc4720c7d434f8605cdbb280c23594a093f03969de319535cda038164c9b6dc12cc725d9b789249529b7edeb4623105692e79c465d51fa6609e5c0686b4d354ce121fd87a6281d4dad94530d748249a4840024fb52b6aa36d388555d29f4952cebe57831a179d244b4c644619af1002fe0807642eabce070cd448b520ad71b4258872629e4a8a3fa3a0617602eff4555b2998538d8504e2b16a6ec039db1687a140727ffa3d0de711bef1e1a443f5faf135bc54e0648cf6743a9a2bd7890650a23e72e4bcc736fe0348661c2af329ae9a68b4996cdde6202b8600347b985aec5b167268f2b2bc73501006cf872a83e247d6bee500d01f817aba4a40a7293f4f813e03ee773c98366b8960a308bcecd66c8c9880c4ccbc5302cd7b8080404ea7c51e3c23f87f2cd775fc306f165fca529faecdefbbcae5d2d4fa323243a89b07e73572153e737e56a1c7c88624e274ec963fadac96641dca1caa425f07b27bbd5286728cb811ebe5afad72a182b7b95c750f6b6169a23c518e1a3db7244bd518f2c8721d5fca1ec4c8fd24b04430bbaf271114a5d1585bf7650bec1015f74153d7c933d6f10095c70391fc4bf6158394edf6687ef992e820fd3da677c1d7429fe54f25ed24a78e6d0dd09343fe2c351579af56baced7a4aedbd9c2bd75f19cd50f8c7230842fe23e872d6eab28a315a3cf96891a4318e4b3a0825c845a4140b6e53e67273b940af1e747e44ee321b6ea9a7341153fd976083bade02e092bc4e3f0cb72ddb366c3a41e067e44119e1e100f599ae24f3e0011f3870b8b61e283f702e672d8d6353e3ad9b54827adc8b5533090bdada36ff7ad72909ac4ec82fc392b55513bd74cdb58d6baea600679466017fae83fb703c0e67646384d2b1e2797d6717072a849aaa18f4d08b5c37b93310202e81caf7c08da19dcff0565e4a722ef1150e5f85e763f72e0ab26d6cbb5fb392c8d9271d1e102573d41af2752a3c63ac3729b91054831b9fd2a4fb031b1966f0ad1bd2149c728537719dd6dfa9d2a303372fe8bf7bc94f02be31f2eefee628373f14f5b437b997d48bea09a5ed497f99d151295428e0b81195c4f2c326d72a648c0039427d86c8ac20f16e44c8bff8d58178c800208107a1962eda43505c43e821b39024e25b9a27281a99a366d8f3c335418eb85327ec598ae03754d7cf77cbdeb46aa691a72b89dc119355c81ea9dd75a8d349da52fcc305b3bd3eb8e4489230165fdd5110fd27a3da8d4e2b57b144b5dad48f51f6285cdea416845ce17aa32ca0422f8eef917f44c0fdf74243a62fb14b6037d5fbbd24e546d0051dd01bf319a8d48c1e168a2f5f49bff4ff3d7fccc0f876f59d487c12d19f54e0ec9393a4fd16f40b4181094dc06e837a7102eeda42bc3955a8bd3bcad52596cca4bd4061a163b1fd6506fb2d8b659d223fee3ba75721432d013715fa53a696932cc70245354648a72e5bb8d93e20faad422f195ac1bd9c74c01f004217800be693c18d97d2d33926f7232dda5337a0cc9861af4bf72b7337da897f63a5836493a7ded86845adbf3777ac4c815cc42256a84998f2d14deb4300353281aa519f4c8461be61005f44b0a40b8d7282fe3f98f606f47607241bb927441289d6b7db7d54b14104c2ac07326090919fe2c7929ebc4ce2c0872880828504642fccca9be6bcd39032e7ec550600a21a0b4575e5f5b15ea3ca4729f16186ee98f2f83fa541847097b60dc2bbef8d23922195a8f89027dc04af6722ab7f1266445ad3a030c12bad571ea2c0b9029f8dc4f839494af40ba39000226bdb3bf3a9eb61d97100246c9c2277cd1b544616cc54be9bd69ea9824ecd2b5726019b854b542547744934b36714059ec8c4339bb81fe38606d2ff54011336526a1a7cbdcd721b58afa020afdb3767baf92144afd43fd31ce9902e72a94fa5372b617e15252574a1601d89d9113354f0ac61fc66f8f444db1ae29553503abed72848f510822d5f734b22c79854e5c9ca1625eff754d5c5d14d2ff464d60f1d123667da76a3719a3819ab89999a1f749f1a280173d5d97a99d9096635708ecc03b53ac6ffebc544d9c2f436cac28e8bd305f432eaea82013da1ca297a5b7223172fae164bc3857a4d55923c2bf3be142e4dc224f98a843f5ed53d12456bc89a91fa14a8aa4022313d384165a5975c6df3ddde006ef345d5690aff83b5b253b4872386ddaf6e26f9099ca2b51e7e5a6b78252de4bdbd241d82a8c009a654d03607295b6767002973a5e593660a9d9d048d487ce2515a4c2833c1172a8cc4267d7137b71a56db6618cf66f4a33ad27414cd8de73ca81986566a7d4878cbe7e4aab724c2c96b7e51b0d6ac2de0866973eb739341cd0ec7eb019aad4ca0a27e7fc42728f5c4f99e4a915f42b5a48388450424ed047cf78a096998aaa0735439c364b686b80f705ddd387a0bcd730194653d27bb6a5edaa02377e910f549b689d8f5372500368faac94b1a2a0dd67770a05944a06ac516ec7e03747d36dd74aeeaabf7240d348e3492f939a9f610cf06c7777abcded3cb54219daf0ee418f5786edbe7214b70c36ddb4e2cf06eca0cb2c570bd748634694ebae85fa15db05c63c12a8728f64b834ff6289598f32b1807f908fdf8c410a6ec6bf19a4e56daa96683b7172bff1fa3448d508c75c957471aac5efe57b5d099d71d26629ace63358b22af928161a9bd4366324bf09568a188ecc05c1c758afa1ceb49859f419c9e0222f4d4f01d02c932bf43f079fbd9cbda7633c7b35fccc16c8f2636b7e9fce836060d472a29167e81ade9bc52bc0c1f479ad81ace82d9e62eb2d0ffef919f636e6bed40e22161b174b8e4200d9870afaa25c9c34b8f466ba0760956d621e79ff5eaa8e5036b3aa7ab2233a9767e81b3e62acba5c21c391f21d55c946a5dcc3b50fc5bc7254a41fb3b3ebe612c809a6f971b2eeaa7cbf5ee3e3a0716947d73604a455107235a0e2d365851b9914bfaaae4851bc8a6730b868d15d0123e8de78dbf553c27281e59cbc37b73feba1ee7825084a2958ea62d4ece5fcbff839d123d72ca2600f044234fbbd5ba674149ea2e333a91ca5cdc8488a9ab2c47f9748409407c96f6b50f05d964878dbb063497dbf23e55da63e83e261e41f68ef6fd4a9261a49b460139551fcd54163f68f36df5e048607ed988a11ee2bc342bb2a4048a831adf11fafbcbecc1b5ee4a8c2d5ba531e35fb8008a4ac9d3572fa5411e68006d3a85772beac6c630c98985c9ceb5cf834f471edf5a3aaeb90a3fc9498552c93d84be86ef85171dabf15f51ec6493084fba7d18bc12e1caa7e2170835191411c6971d572053cddbed0c34589573dd2ff504796c4260d1748d280e66f5235f201e7c28e723f79abf4f0f1693fb48477b00383b4939f53f13b15726bca167f695ff64a967251dfe6153b9b70d49d51c3c983e8567354f5156a22914806e785372c655f450c67b2cca7f662341d4f5cc7f7d9f5a49a0d41a774117cc1a5f22a427ff35805724d81bd9b3befc0cdb0f51c6fcece63e9870dca4ccf1a3f350e9b20560b1fc21b5bc9930cf7c2f09d76ced27b7274c4cb7dca67b3386c94f5966cc55c7f4a6772c40e25dc348909ff0c81f5bb5de9695ec1f3a98c9f1b42d22b113935129ced725137b19e7dbb0580591f5fdd157084011a3f20c61a2474867afa0c6262b74e492b92f036a4af4680508477075bf3b1fe7c1297aca514372b2c1532678d6a846843b339941ceb9aa6039da8d4b4576ca4b96f42eceff59aa66ca07dd9b00b2672a134a3c4ac5ef7cce416c397c21d84d4e26737203e32c9c67205ab6641e0e7279829b9f383341e217957e395584331d588f83e28895b671736d9acee0aefb8720d83ae262c068bb8be88b43954c2e5212d0db42a99ccfa3386d0c21455c65d0ca404d93bcf0c600f681c6505a938399ffcc6293ba83ac92fc34df08240a40e63d51b7ff2cf692b2bca8ce9dc6caec6858ec6b31dc05268c270c928b25d6f2b58841955163e010aecc83c770422f2d33d401c3f4f69af621607500ee53d970f4358bc54a74df949e7606bb2ad33a7c4763392583fa8ac9fe3cc5db02416d50e720dd44a1c19ebcc36d9af0bb63d2f5c9d7e7e5c885f125fc452a9b1dba2b7ab729949c94eaa6c894921ac7bad57ecc8ebdbd1e8ad62af3c79667f00f62e2e0e722a806b8d743b002677dbf1bb399d5f2d91e22eebb240f300c9ec06276710b56880ff1395d722960f18307716caf514b5d4d4d65be1868cf921c16d1941a5f0721c799e5bb82de8cc1762d44d3485179860553e8b17065a5d3d06a6ed17e28972c49a2bb3b48a456a484ab314f5145b665824a04cb2f78a84db2ff5d6619ed1595374b65fcd2d71a3c487dfe28d4eed8c434bee5513878d893765ef81e4b410724a779fc7eab12724d07d34ee26a9263b4c4a9fdf92fc58bf3d5cb73aac772f721a1b7d6ff88b436879432cd856a0c28deaa0769284a0cae284e42c002f9f780d87f3d9e9bd249ddab4955133ca258e1cbaaae9530e7d6f8fb607283b5014d47220b4fead765a2ce36de1d53eacf3db4063712fa4fe9e8811b7b7c3918bdee9729c337d89d112e1ea263238189c811ba745a2b0930c29af7616a59c24c4572872fa165333af70cafb864ee48a2bf8bf6c320e7fba7bc808f6721115f9bfa789721d6c0a9204a8c4edad3b2143faae146cf8d6d5b951379cf81c0d24429fa7885ea7891763c1ca60103354fd535ca56445c521a41850f5b189fbf9438df4fac87258292505b1e6cf784981187d011b5f24c405a540b10d32dcfe701e027c812644a0b8e6bcbcc75506322a3103cd20da6c8a365302c53791a70dbaf238e7d0d83bac6474cc7ef827112a9c34c77fa71e9118fbb93b644f2d9050f4a0f1ca8e094d6b91f6e5773d5313f2842ff4b356ad908e703218d2182dfe0fac7f81416b63729fe00b0997035aecbd8db78d1c112c68cc5d8191622c434bc5e0888162c05c48c0874ad0b73e25cfed8b15675c71b185ee4a1184c5e00c3c23b25ff47e270672dc4720998ee97b6c609d73b53f4a0443db6434d590236fdefc21e2b19d65b17286958336ab2b4dda6054aadbcfee50d8410de3dcd37d234745b2d448dc942d48b1a8d8b0e2f746b1d69f610492b909040d4884bfec1f7c3251ec6b05bd1958725510db8f04527c164e189f72b24e847165fbfe0acdaa03898927f3282b91f772cfc221520ffafbadeb8a259ac37829239f8e167dfcf4f1afdc34e6b113313372a05187f227c125579ad25a5db8d4874877fbdc2b24f60d4b2e044bee03b62972d45c7d2e8b5f38eacd3c3eea70eb73896e9b9d752a9f810e73ea36b6150e832c43e45f51a32e2a87e25a07a737b71f100742237d74e1c014fffe9e2e39e0150b58c2470e031e3751b954cd55895339f8b09f2df6926ac88d2bad56353a189172146d1210279db3ec71bc850d1708ba5dd9c67e3a151ebcea5a56532bdf40f6374c0f17fb7ce641166657b95d02398bc652d306603f3f3d21d9cbe920ce791b72272d06fb4b38ca0d11dc768c3d8bda9ca1dcf3bbd94473965e1889d821e6777236726ab796e04392b9451c59d5bf454f86ec13b839f644e62df0f65f79a335043075199cc1ff78791a1385d1b510b8db8060ada561da04ef4594aff77334450b53b252e3d16ef7b3f70543b6db5b6533a08f813e6ad6101bb9fa2d4176018572e93f36b4e2aadeb5b9277b95911ac7f1787aefb6efe88331a7d362632ef326724d3cb6e763d3c806e33cfab0176be5f33425dfe0080254b8fd10e20125e7a1726d983319882f99f60775c0ce34207e51a3424298eaf9a73215daef070738bf72ad10b5930983d7bc46cae72bcebf7efdab20d85ad2191e60f4fd54ac1e4e14721748885143f79c6770dc7ccb28fad54c000b1a5771ebaf349dfb300225873f0cbab2f6182eef9867ce3f34066fd4e3b4a16af8240610cd0f333ec72b4be0e97298d9aa508a183787f000a473d88350c03c1f683cdd615f471dbfb2f0913fa83e43cae92ad1067eb7ec78af3425ac388e0f5528ab930a256f10ffef3684833b43a128e9d6e1e3e9faba44b0d2edbfa22e5e79f0119613320b6ee5aeae143158726d8a59e668559b2b29162ed40acaf30f1c2356d2551d4710740ac4abf71a893904ce5b459dadf9aae6536e514cd533e57e83a0f3e58f101f266e3951f823a572b02a2b2c227dcdfd33566e054ed01df816f6029d0a9a22ab05f63d8e157f096bc6c7fb7c39d8a64e56908b357aef666cf5b3342bd3376d35b9aeced57cf6e27286b303622d578cfbf5ec22db356ac86c2edfcfcd7e09382bbd27f4f75d39286fdb76b82eb1d467dd031def9f573a14743d40b34f9e166bc9ae9e03d126349a68ba92c3f42f1fc284f417e2b704e5b5dc920b7bfad1d7f982b1ce87e7488ef539a64571bd4ce8ce061352923684afb4a3de0d731108642492f140158e4e1cfe7218cf25403d5de0084b9f48452a9c6ae94125f6d92c387bb7cbb1ada15b51e90fd41c0fa1c8de15a6d52b7c96284415b689b20c5c8bf89e398d6fde068343c855acecf1ba2ba420229a257b44ea1cebeb8ec621d7bdfea05e1c0e682f87cbb3727226769b461bbd510e6196e5f9d5e40708cdf987a03aa1f1f0c0c7e3f15a884baa49398bcc689d2b20d894c4656b57f4f7ef1fff8347e96061b14b317dc0c8723cf38e845c3d0786ae2c0c1d523c33f54dc220e77ab2d518d2be080a8da15c72e089aff94f7f2bf41587f3f09db48bc56cf34ea35dcb5ad4849d249825cbd91005f9d3984370224e591e33fee748aa568bbd7aba28a5a7bdbb1d649449b886729efd8d3366b1b71022717ab9d38a517bdbd69a5fa63179ee901b4e95753fde497dd181a4df1d0b29b14b5251289745fc1a3f49f5d0892706f01ce54d9438a01b1a4c952df675a394016c3489c22f126641497bf58c6b9ac7fd1484258f7b793c3c63a70ccaaab0379beb89f171e2c48e50db6b531b48e8ae65c8787a31bee61979aaaa16c8175b9b1e77f1a0dc924e70b887e8e74c5bd088ef64382549953d1a8df88a0c51abb2fdd38f5af2b9052944d704362ad9d1e31681fb0d10c7bc3f3944b48f5c50e41a0b31ab77579c8db8584a3193679b67d4dbb6ce161362a4502711479651f9020f9f625c6b9e5ea7f74a9cf56a2f3a35d17945a98d5a00cc10726b286956ac01a9a9336e062b24da3be9140c4423db89e648e3973d9ade5e042c0c6778f351744432ac558e81d45c376da6c397610256d897e00a1939c2d150722fa7ca6bdbf04765f83b1b1c701b9b9870b47af965bde392e6f3dbd89718cb3454669d4c580e26cc88a8c49fc02382347a80a204c4d9b8b1126d0efd7527962de2f69aae0458b6fb74e89f7b4de27d644ab2495a1fb7d92538da9ccb96f1ea722cbe0991d82e6fe7997adfa791b02024d0d3aa8079a8165d00cbeeadfb8d4c72088e0a64de55ca345c72706cb9c4321900436a8fdc9801eb666e7b3ad405323abb28fbfb3749bd4e3c71e75fda7eacb16208ae2892963549d5605a9d49e42272c46015d58996f796c4e071d924b31f79872ac05e4161e22e04f1916b8e3fc77203f6a4271225dc66797e7f189259bd3cfcfe0ce4c9f70f3fa39a5bfd29ab3472bfa35a4236e9c20b238fad15c64f2577c2de471136160b3d2db8fe2c2df64072c01bed15434b037829616e18073b622fd1ee91e7d82c7506eab655bd5f1aac72d085a5f9048aff1469c8ac4caa343ae3777447e0e3524f443f319f462ac4985bdacc53a933d1846625ed610f4668167a2bd28c70983d3e0d8d69cc648ed338199d3cda39fc637201947790b3104a960bbbf530f6acbf147ead466a9b26003472d4e4fa78544fc9308f3a9a121fa572f3e184123c4d05d8a1c5444bcac607a772c438d7c911dbc3462e137470d2d5884bb2c8a57d07ee83c9deef63bc27da7072f6beb0c2befb06605e0d67c53fb5ce79b60c1154a59029f87d4d408b474811727a79e4c7495c1c9acd954e619173eec3cc828ae5dc4439d05b8392a43449f4625414ec76f98512c61ea12927d636a0fc062b748e5e5c51ca2bb002f2fdbc3b72fa852e128fdea0522ae886e387a8830c9ce277beac93813679a0cc07943c4b3eb9e2755bbd960b0c78ee20c785a0f5d6bbc88ca5d1ae5bbba692daaa152e6018c660e4092c199f3af2921f22c9055b5144a50b497d6c868402f59a50013f4072e21243fbea9691ddef95b054c83cf3abbdc64c6b75dabb2bd46934323bb04d724270637ae1e64fbfc9da765dcdc3cdeb9f1472ae309f1240724724cfeb2d0d2960674275f527b46d6b679a8155a731580b4b5b739e0934a104dd76125833bc72d322d6963ba9fb512c23f3676fff819bdb56edb9b06607d64f0a520f519a6772ddc2f59c39e0675b69230b5714fdc1f908e1a9704856ac818d875af610bccf72dd44b0d530fdb151ce18f4abce3a45c160ac43eb0324e163ae0d73487da40472752fbb8fd32e76f143223c630bf6db3abbf8096c60728c2bdb83147e52cbf0728c30e88ee1cf5abfceadf85f3b112a3b1f30057b135f088ef15ee8bc1eaaec7249a7a697db8f75bf3586b03b500d76848dc87274923819a434229987453a5d7297e1c171ec882ac9557c0788bd47e8d2f821673de50c1894d0aae891421b0b7206a8a9514b427a170b70e84aaad0ac56769368332201ad689927f43f9749e1728f30f30c127f22ebf5341f3219504a9d10ca218817ba5981aba962480ade4472bc6f348310e810eba93fa5b7bbefeccc72b0558388aa91e7912ca1e15ae7b94f1e0182bed991e73a66e2f9e0b6e0fcb542d0c2d2931d0509e07183fdb026e33a69f5eaa5c0a257bf14ee7a5502c1b9554306fabdb437b1ddaac2d096d85cef728b96667314d2e7271e0edc0961159441bdeb22cddfbf8e562950f4d035066c4a43f6a5a2b7cfbe3aa6caf27817ef5c23be09b8de00456ca8a64147a61203c0720571134383c2e7b9553145ce2abd0bad1bfffca7868536954b4e8f6dd0d07172b272ab12d6edcd01f279004954f6ed157b2ed140d4fc832ab4aa1417619a6872d2f0f5e86cd7475cdc373617d706dbda698920f9d143659e62ff65edb4832372a35378c503da2225af11cbbdf456e32d4f82a3fb87c8719de7a1f7c1a0db726242e2f6194288559448c801224e8483f111b68a6a1a3b2c9b14bb3fa4ac2b58724fac5ffacd31d7b07282c5dbf734c360bbc543c7101caae2df9777f5068faa3e51e0aea330ade14abc7d0ea11e5797059d2cd4f1d94a982e8c0e00b514a03b722ad98c2769d449371cc2873c7c48d5ea1ebf0625377a5dc705f7fbadf26b9b726377e15cf20aa5fd638b5cc8572cb29896e6ad7a91d9c563e15e5a3bb09d01726128490b9129bd442d65d1dd45717ab9851fa6fd7538b3b7951ff5af5d9cda68a8ec4e1d20aca8d24ad11440112091ca10bed106ea53815a8df7eddb61699472a9edf289ab2869b79ba987247537b30b7b8e39bf3442220d482180ac705e1907085e213127373c065810c213e67097fb5a529d2853ae2b8184a301e5535f2a7217511c8faa494f546c59467236157d68caf252bd35f3a4e726ffd528d76441723efcb74d7afcf33c5e6a834d1a8865fd2f666845976dee076d0033b1fe6d9d6ce442540fe7918621da48bb3a249be42d0692a03c2e23e3e46e40654a0de80772a9842a65dd3136d0f978b9a3b199d208134aaa2a67b7405071127d11ac91e27226d5a46e33fed4f17845e8acc6cab132608cad84024c8320aa35580085cc0472ef523a87d249df67908725c6fef544b45697d3898f9f15a0a10b783319d14772315d5d1cfb05296405fc79ec21d7cc0617fd7ebbf73772cf265fe134bf5b7c72a51685a96730615e78efc5b373f32b11a0202dcfae183613e72b9377504dd5218f97a0fe2bfb604fbcc99089389c5db15e743951bdb06aca0226ac7254f18072ecffa885380c5f5aa5ad27ab8a7617f47fba1db2b7a4df370192ccbd4bf5ef72b141703f2b7ac8a84e6846de6840e10a1140a2b37d89300ecb676183c5210c2662639d5fef68e93bd6ce30802e746ea8210f80919f77156e41cd9cee2845c2727af5994f94ad6aeb4dc653e3e6e4697e200d34c95e2185386df182ab20e205541258eb59cd21c76e9e58f2078bee3c83dd0563e5c50da93bf8a9e2639cb1fb5eff161fc09c95b132b6866e2c224c35e328278f33ceb72f04d2609fd85a1b1a72e0f0cad30f9c5727e5c66eb38d90167f7ff1cfd4f777f1ea6c3608da2b93d372f610529c789495fb812847c3ac7f8b935ba13f16a49bfe1a2d5168f77760e572a6ec62b79a405f770b63a7c48cb7f658e05d073f5d361e9590d82ec4f7cc8620143aff7f437c7f82aa650f26df81715491b70a4504893eea0b9b0aafadaaf17204a25dc4b3ff944aebd488ff5eead5699deef63c7b3a1d5040531996973cec7286da3c56fd1ddb90dfffc69d64463be7bcf186b35056594418e3c45ed40d0659a03766eb1a7c9210ca02e72b8157624ee34e91ba33bdae36d73a4d2c63f65f72e5ccc6dafd971acb96a43ff4829700d10161489159a76b41a65e6defe1d9c20b8301903a431a0009a3dad1c723a1e1528de2d09523ca8543a66153ecf53ba772fe5321530e53b0919543bf80c6f12a81e815ef83009df090aa306df291d0ee6b0643d25892197d97d0a65058538876cf4e380a00bc1676f749c15bb3168427019591b7033db73b9589fcc9023569a3296ae080f14f1a5b78e1514181cadc80722bb5cccbc91030e0eacc030ac3d54babd130ec38621e55aab8ef9dff3fcac072687f793c63ae9aaa4a7dc3c49f47001c583e073d29f292ddce9b8c059fe2cd31439f2c8d9bd0e5d1e46abd8144a83dc43157701d0bf2bea534772f25d78de50ccdd0ff1eb5e1b0c3b28fce433c6f4d87bdcc4a921fc74e91419d36a6fe879e7220e309f4ddcb5309eadc098eb83b737c0cf02424f07071cef28ee305599eef11cf6880543e05551995aca04b4775b4b36ac0307960ccf2e4713e0122ae9b2272bd1449b80f88ab4b1e1e01b8a7d07f46cbe935b447826c739a5df0ee91c81c72feffc260b61ab2ebef6e0ed2423f564f9ade66c9bfbf28ba6b6d41239a73734ae95b615f4157b0b5a47b016c6ce22bc6adfa5adc2b9e838ec43a227a206b8772c4fcee6190342c800bbb6920d54eedfdd408d61398d06c095b1db124f657c6722085984e838a2c08d400c07e27807597eb2936eeae736b4f00458170e244fb40720d6b12d5d7a61817d7ac3ee4a895163fa82de9dc95601d74215497b2a76209a0d4d261d1f1babbc7b3f9a3a363e6c9640c725083bdd7ede0ce8ba2f734e17281b09fee1b1a1708475d158a5147acea5b530ea51fe5fc387c9f591d32357672b9f94c80736981b53f36fec58c637d7bd71186a02b7e5203ff96a18ab1954b72c238bc8a79d36e9a17948bbb7dd4203a958475c2e703bd2cb887f7b003a15372a5d2126bae800bbfa6b6c439b19ec8b9da82129877fa4778a936d12c37552d72ea27627e0c8bbe42b963ca0cdf19c072dc1a364041895d60af842fff1261a21ecaf5f68e105b7ebf42daed706a9e9a4ea33948bbf08224b4cbbf7700012e5757f4aae1761b058f6435afcaaf437bf47b1a02f5f469267dad9ea6365dc319387273e02d58dc9ad476c5fc4c5a62dacc65ef762aafd63dd7a7b07d077e2e3e9a721bc45726713749ec06243ca0c614b31f4d0755d70dd0256f3a69206236671b3aa08f313f9b002593981b7ace28d6911f21306d094bf243d17bb1acf251030271d3d78420f895804fa57089f832cfe276312f01521150727f0133d95aaf289772d9eae79e88fb90acbce0802e9aad77c833dc0062ede0d4a20af0461437d7e61f96c4984389cf3d7f0ff0b9e73664f8586f47f18f512c073312afa1f27aa20820a8aca048edc567f7d59e475cd83f92f2ce267363380e9d2b0338ee67814f216011a7bea1d54b6b7de361feed7bbde29db12f3777ab6818097fa51e65967d4a72e183fb5bb80ad2f391062fe60e74be43d9523a5d625ae67296a76a64182096724ded224affae0322ef07c526eec821296c236e7dc0c191937bdf566d942b605bc89138b3745e8b8c2d938d30b51e3ea4aed4f4d35f875e5c43c25469b8b8fe342ea6f93220c91db37ad38147ca72259b4877f599ee44e0901c34e9c192b97272366bcf71b222600b709fcf51b345445d782048c2223e84b608f641c33fe3bd4d0b126356f3d954ca10b66969da2ddb7716ee8f1f7b4d90f7eac9dc070cc0f172a7452d8ed76440ec877be1bbd7c74a475660a3e4fee8fe5df5d4c01dfb15074ef95e6d26e70064836738866d40740da97c059bc5c4e693ce53be6852e4ddb767a2849191924e4c700270a9ed7a1e6caee7df9596c1da1a51c62960faed245472f1fd62a0a24f402cd4dfde9a190ae9af6dfea08c901bbd550ce81323bf7a73720c7af514cf38584073e3868baadadb6d6c4412432d7ce052e3cfa6b172004872c9e8b1fbc22042299bf3b914ed7110777e0cec41ac63e795a6ad08f5abc71272022b414f04260ec9b388dd2e1c015879b6fff04d9c16f08489fb61ded7bc7a7201009cb613e5c101e520e43127a75227e6ab481d278c2b1f8e4e454537a1f5725b8276750cc7228561e47740426584d0f278792d5addf5bb6a7a479dc9db047293546fb0f6433cbce2d51c470b46a8cf255836fd759a6efcb513364f813c2120d579706e47497fac1fdf53dc273d14281131f86a9d828c0b56bf3cd7d48e9372c98e0d98307c59492f262cd58cc7ff02cfe9ce481037178aad98f68c94556f724e29951f8a587b760b3c1d955da4b4f16c92d3ca533b79b83ae6c485bc57a14113334d73814783a4045ef52d4bb0b4bff4c8675ddad8e55f6ea29b2fb795197235172dc59479c8cf046fc809a98c2fe0a3ba656aed6cd3010863041ee7db6e19708938f73535b3e0d5b655e64ae1c6e1b6257b0d7829b41d287900c7a964a3721549aaceaaa4f29293827f486967545191e2496c99597775a497a217f05c0f72300eb38ee735efccc5fe1cd066fb22288d01f4bb92b91a5a1bfa620616627d726ef8d5d7725049104aba1afadf7f7f80ac93294b12923290eba2e41b0f32a628ea80998fd7d1d897dadfce52c87d27e99d2bfe76e64ad966db191f4378dda37229c7caf62ef7efcd65d3a86d3ad26210a74305dae907f319bdb6e48b422a7872917342f7e9aa464577bf4bf1dc7313f786151bb7a9db6e1a0ea347a67d5de2721381f5d7c36ccf80b00946db972ebb507445d0e7f6140b7dbf84cc0954bb1f72d7b0592b31b55771d078622a6070257c39614656eb8ef4edc1afa109f16d4b727a6aa4daf72af55fac07616d4e00361dbedd4769f898c0d8541b1f65f9d0371ce07e2d187fc3ac636d6966368de1419c54b07416be61845b9d6f6c52b55f8802c1db47f83ab6ad7f5f56f60f5d7bccfc6db9044a0a9e048921979415445e800845a62bb8a509866ea5321c06aee80cf11fce3c4c5b97e7d0ea9ce3e4433f96139b637c7f43887fca0b8b64b66fad291fdaaea1b8ddde07f849055e1696d91772680a731b2761c6b77dfbf9fe99f1fbfa883da59feebe5ed910ca7020c5a7a272348789c6497c93fa191fd9ab6a8c16a81b923bdd3b11752855a4049e1f7c9a1c8f434c458f8567140ac0f84b0276410ddf8e0505309065c5a2730ecac097366530b96337659ae8124369480113749250ee461d7804df9b72a87ba16a20299f026679c838fc035eccfd0d54f3af97f3eda756ccd44b9ec853e697efefa8992944f6280220bb06f124987d5e399f9ffc5cf26539e2100f8c8c500bd0c7097d94726386cec1d5af00370a34e374204418713a321a2b3d61bce28b88f0d00b68a972eeef29432e108f3469d10d4694b68a2e826528136d9455462e83c6a156da7472125605058ef28c2ecbafe944bdda38a1b4fbf94c9734649d9ab47b59cc2807544a954e1919271da494f1c99b71b3089a92659bb337de3639a5b946fe3691a7725bcabedfccf661af3f3c76305140ce05830ac458539265500b58b29f50e25972ac5145e50d059f156bd33585fbf81f8a2047674ed6a9260ab0cb8dbd3341a472ebb4a000c51d7a4cc2a16e21a9e8e2ab476e5c96417f445af710bbfc1dd5ab726aea49c1b82990fa58dfe0d4586b71601ea199baf085eda4ab006733d7fefd6a711dfc46ef45f66957f7576e89fb69daa456335839cf1344b9f520ce1ee64a72a73aaf913a9970d7b0dc08f229b44529f238fb8d60b19e0c77a5557169ebbf7249f0d739c623a5a08678a740e566e64b752d6684542bc50b8b12102d15d4f722408ea24a0a03bb1ea5aeccaad035f4af5d2649f11f01b5a79a337d092ac3e26c7ff434febf52929584030e5ec70b6129baf68697977de6185822f9df2f00f715004cfdf3399f9175402c4a6ee750d4644a1d6adcec0d52cd7ee6cc591ac7b17293a87cf07b62b00f0c9eac719897c649bb743c6c3e79a705726d77c76d7710224ba532cc85b1c036c519e8791cc2a081516916094a6ab5d7027deed5eccbe072000736b034e19de747cb9965feade33a99e4d447b24b20c43091ec595981b272fd5b30a4aeccec6cccea7cd357e0b5741c093e60953909b101470baadff27972de754b426a4b3ccb7801c9d9180085060fb56d63193d7406059812379d3fb2723ae866f9d3c687413358227c1e9973973c592a881f1dcd6f088c294146311a6b98f5e4087d38db5944bfceab11ab35cf44cc5f6b99ef510e88f3ec920a8d427205f8c5398b164139b085d7fa808287cdbe813a8edc66ec7040b3428d495ca472205596ed4d01e9e13f2607cc81e100fadfab601a19c434917ef43f213e365230a0c004fc22017ed5003b00d9ad048570a5569a0034632a97da1cd1bc48e2e35d5fac24adf1fdac7074d74511200a9772c2d47a5ddd53f291aa29277d0802ed723d4b1f004f9242ac701f75d56b8687332933bbde699118b6407bfa78e2f087397c6200cd927589e9fe18888b0ccbbecb2d253d858fb01c2daf61906384eb5c723518b6f0a85b6c26e7df3a13acd2eef4fdf332e4d524b2792410eb0928371a1e3229de2703220a67dd7a16fb66628cf0fba316ca65169d3915594d89f3bc2a2ab1fe218c54402da5ccda1865750192b9f3f8bc7923f8cd7a2c320a3016004a72f5da24d3079752627ed83f0e3bd64b9ed5df9a6ee0dbb83cfbc89b83fee8d34499876e9fb839ef6854be0dbf96b004a15415c09c46c0a08a990a6f2addfa9320c92ce550de454986e64c214f7376ae96d95f01f1e6648b44cee522e6743c2b6d7a05236a674c043d6b3cae9db87a34ce47c0604908728da802ad2c2e00055772376e2d6f034750a0e0a9fef806b05a33f4e1fa2eace1e4002447d921fbda2351b9211e7db6b9d70f2e6974a6f4cef707da2926719a80ab45a7de018f576d4c72458a4812c8ce69973f994238e525c7dc92e56e38a9ad4cf38af9be3d81e93872ce00a6641591c8cd3127a4af5cda9d284f6471b9f9b9ec1dcdbed2dfba626033c4f0601f9d41b003ceb3dfbfcaaea2f575da9d079bbdf685b33fa255c0fc3b72b85faffd83034173c9e99fd5f6e1378eaddc92ab10cff118fa073292c1c4b77296597c4b10ee447c76008a0818e4f35b2af4b7c4cc3a4e0d9a73b2e6b0aa6872b01878e06ef1dc46c0fc504f532b8aa08564dd5c44a81868c7bbca59fe953872cd8311809e164ecfc54654af64f2a58a1efd050ac6b50b1af87839a27f5e8572b75de03c36d696f020396173cbb9a0bf18e130cdad79ccc759d4611c0ea35672da7b2a1db38e67ba389b07ac2928bcd85a3e1a114d28b6f3c86715134ec33806dc4536d2c11a585fcb1ff584449f9f184e74005ae976b1dbae8f56060a6cc30f6b47d5b158a36a1e95f1a0c5860209cf8957aa7a534be89a6fda331c23fac972f21888607e10b9db8339139c26c4b12daf51a9ef0d326b0ad067d19e7c57f372c52032ee4e1613c7aabdc682ff7ff52a14bf56b3c33e9e3a184a2f76fa5298728e0939dc90828dd4198fd05147e5f290671db29812e22ccf07c84e6ee835c272a8869dab5a866515bd28712b3202a739ca5395cef9cfb6860e2b9d9c33dafe7263331f0f5ad5a10aad22fc425414089414da22657ac2f79376d10d91a9f86d722d6da9462c277380a6866ad7e94af9704f4e557159c13eca7555bad8a30ba26d66e423991bf22079c8a69c0d81424ed1504f2109fe6e0105571cdba8e0d6e50ed4a38c944eea75f4d787cbc9d438ba6bc363bece0767ff241642ba81048caf4866bd7ccfd95192e73e7b6d5c9a707d87222561804c09b05a0689721c485da97211f87fb781dc42d7c480da95ede0949b9ac75a4c170f6bcece01b658d1783572786462bfe800f32f2b6352b07521263e484918f1f5b56b5fd6a90d5c1152fc72459b474c4e66698ddbba561d4eda584a9e5062c0fdea64c17a8c69f88657a7724df8ca4af566fa257873b63024a4ab5a782dca8ee10bdc7e2469a266466bf4721d206c48c0615ba5f81aa8fa8062c02d2266d55ddc2b5bfc6bfae6df54889e72446cf480dd87d74c082ea4b9e48d4c4a8a86c7e1acbc27bff7f644681311ea72ce48029662f17bb945017afd2ce0f012932ee4af5428baabb3b195bf574bd864d45e77bf293a01f8e36033064e631abc2ecd7fa7f74919ac11e5b84d07fd36727712b25680ae73210048e02208716438010ef2256ec062c38b102787ad0c2d18f0cb7e5276fa392a6ab565a8624b6b326ef360096015be7161f2e49a369ef972233dcd4ab506aa2d0c5cc6c0596f91f6a987eff07ad5e20c5b500e8161aa6a42f91a71f7d4f81a54f8a20853a177632c4fc4ca9a78a618abfca6fc0632fc5d72b10a4657f239ee2b4354fb980c2e808cb75104c7a1e5553e917cd23dac73c972f8bf114ec23414e65decd3f05d35fa2d2eea9ff18555854c61227cb569ae2c72691b798336d69d77efa2e67f3f4e0d28169b1fdb9ec7b42f744acc3c78d78b2197ddaedcb7ad15143d029963c3be22eea99f99ba5d8b13815162ac874797f6250a339f8342d764531ff3a5ab610f75abd1ebc640c4078314f0fbc18c4751c84bb22252ad59a89042f62d8060805bc9156bfe37266fcdcaa7dc01b24c4451e072223818f5d9eab8f1c5367cf140c8f028b8c1777983a1429b76fd8b46ab03987210a50544e3354812d20f8edeab20d75b2ee255a7c5161e4040a5ebdd47fb194faf9ffbb7797715d8f747eea89341584b82784b2250ddb9778b051beae0dccd72ab96ab4c0a59675777eb12d25a4b2ce9130c3782efe869845e7bc12f0cccb972c9b9f6e9abdd1f5eab5a6d52f1c148e0348f8cf3ffb18452c5b9d5117eda547224be0230b27bfb843dc1e215ab6109661f877dbf347f88706a376321ffc13772e1513a4a3f165aa75365ace536f41dd5d63e95782e5805cf71cbf5428fe2974242b2d6aa3047787e540593edcbfce2a1fd13a85b47029b83ad4744efc59b5201556586dc5d4a885aa5f5c3ff43774f02f7b4ddef8e1818d202b56fdb3502a672132ea9c16373ca059b41fd00bdacbf97f8eac71d7d02657e1c3855a2776e5972230c9ad8dd607d43fa342bd2496adf2869983b3a0d658622cc44800d00867172683d9d71ba561f6425d884bc5d2608bc25bf62f4855634c51c89449b41c7eb727cb8619618b5dcad04983051be602da72fcba651b484074ae3036bef9702d40948ff74a00f0bbd974ec4fb26eb9db762953f19a49910f246b343a799d34dd6726131a33ca3eb467622a1b29e366d7f9b31014211a76504b90927a1840ee6dc2eca6cfbbfc45ca61ff2b15b6f1360b7467fa7933c58918e066b95881c8dd92c5ec2934da07cdec9b3c276f24db89ec269d17a962447772687b06c150aee924b0005e86ff4d3cf94d345265c7517c0bd748d2f728b50988b7c1ef6f68764a57466c07999b6a31a924a0196f82e81fd44fa3ebd17805c21f0ef8214ac0991da527284420487fba1225be30e8be5b64ca78d3a573ce842683aca7259934663dae03849040d35192a065dd3ad53a5c7d742d01303f04d2f7e201d8499110a61f8761e178e961b1babcfd4e5023c922647e69c81fbebd229280e659ff342288805bb72d4a01e424d8d7e1f318b8da3db8db4348ab82f2cfc70f31e2839591f3b07cf72027d05d1ad561cc3549ababb7c1f741b175d103eecca5505b9d2e4f04bda15722c44cb472fa883e164617f3a608cc003fc5be112e2114c7e9c7f19110efc7759c5c49961272b31df84f540ea180d4a52a79b7285a4878243969d92566f85b972ac24e974e99bbc59a239a75f53abd792df00edffa529e4f6340d87041ed087723001343dac6c4d58c3aece80b5a887fe8fb828c16b7b92275b7dc5a85d843e3a6da017390dfbba3cf6647b7ece827b0eea4dc08df51c411317488a17c0e10f72f52a230fafb8d99079f6d9c0cd2b26fb0acc343caa0a732f8e47e499ecaf3a72c266400e42246b53ef71a9350d93024a3a9a9368b5f5fb7e2e7ed656800feb727cb6967e6fa37a71bdb2a0e217903ef906b487fc934937d5ad7446a235d8e2724b5cd596c61d46a3e6a85fc978a97eafb232a757c562ffb210add9a73f68fc65555a9ce597921aada48b0a7ea584945afcd299e96cf547790c3db45228ddfc7262cdf1b52fda99f155c283ba82d671ab430a37bcdf5ba734cd8594f0b4bad51ff8b43b7d8b9cf9415fe6de6a39c5c021c6918c5b0ce0fb37e655f1a76cfe6972477d39f15a90da2f4af868d2f7983cb31b5f021dcafe7948c8a12c184c440d0339f51ea85f262be8f8ce34a308a860543892437bf232741cef509712ee51e467450877634f41628d5e0857041e793f226b28fb646f4849070993740b24c8ff33e368e2d2943237a10a6930fd9348202c17af8457f4917b3e2fb9d2ba92faa20cd3fe84d28359e7f3546b91428fd2ed1a4b9d4e7cbda6722d8dfd31d1e29185464de76b29a2dbe652b51c725b763561e728bb4bb1ed1caceb130051a3744ef4722f1916224df9cd17428d00958e7ea2dcc85b8aac1a845d3894063b87692ed872e6435a90a96979827f9ddd8d781d9b3353690e585858cc9f591a9369328040242851b9c17206d0cac505c4462ef9fbf1e75f0f59bdeca87a8aad7d51f95fe4726572886123d6d2025b07151a86494e16b27829275f656648ad7f0362c4fe6b7256960c5c2e08d8a8a662da860d7c94c607442b853658e44642980e1c049b8372c118346d08503d2ab13b1042c078213da9c9ef2abd071b9af0cc4b5c3ed1b5724d586f7cae9d9564c893887e556638d83ee500be633d8979bd629a8cb9962a72c96e72f9d261a46175dd4c79a596c3ddc4d268996b863a982f32add118b9e206a8ada9d4e1ce79a8ae4754d6ddd75450fa1dc662412deb0394b7b18023c34b72073bae71ecd9f00dbfd3d0e0bb9dc772e50c23d6fe8aa3a5ab87aab303fb31683a6de3994e39ad6295f289f83da348d361f96e6888f34bc51134e6f34237d77091e6fc170a746d62e070a29440f69bd42e69c741383f1e4c03e1f25ea41f2b58b8b326a6de10132f5bba9e7bcdbc1e8929c4065def2b43d7592a094a62f88d7238e7d9e35afd59001eb50364274538806b7e2e4fc55d181a365a767baf6aaf247849cfb95258e1f5ee3977493a800d490963b83a9397e8271b70feb909e22b7212c7c2d9fbe58f6e8e78e892772eed35c33c65b3b568feb788edd5d8eb010d72eb522fec2863cf2f403383a4fe47b45f7d2a5ad5bfb86e25db4d2b1ea2314072ba4c5932801c778f288c3b83f05934356230f5bd74ec0987529c0dfe5c47363fef12207704c7ac186b3644131e184359d815f9204ec36463b75de8ac5f18222c6b6351fbf6fa523c96f77ab53fe6d46b8afaa61630d50792d312108d30f02372ba63be2c10bf89dc22f7362d39f0ffa7a7ead2dbc35cd68556ed3a361ec9e972f20000ae5c8729bfe74ec1acc611da72b68729924d7542b061ba9de1d15ef320f4a7aaa83547aec2f70440f2569670c0f03ccc318aae74cc12bd1406165f1f7265af7ea8fd279fb6423e7afcd6960831b793a14a157aea1c13b70c495b3fd332df21b800876fd82a8476f70d17134e7125f4e31195c5f957cfd9e348d8ff1d6fdae7498634c5c9ac4cac084028b1d876d7b5b94ca4ac7f1013ebde14f73d2372df4bd4a70a62c0a70466eddc746a813c27f2c092c4e7418dacadf773ae110f7238c6410728d18db3e36ae0dbbf988bf43d7f8f9e8f1a9aaccfb27d926abfed38d04c2f275a2170094196a585f481718f190e0822d74b8a48a073c3ad90b5f17207b8541f46b941806e1772819bf397372cd92d013399da443b0974b0a3691018088a88cb900485670e537650c5cfdc6b40a2ac0ad39db4b5c4dbba0a24249b3b942fc3add034b35ac8bf240df393d890cf7a4ca5cd911cc372fd4a569ca48672a9b36ee3078e0a63094c0cead8f012420785249afb79cc15d7c1b42112d8e172c348d5683701ac0916fb328dd2ec1b9d40f6bf5f6b06a7d9f9bab6dd99190072ee112275f8b333fc65646260e74fd4399c6d70baf49da44b56a3f69b42a7a54e86df75449534430bddf7563e6aed5b0fe602b77837caafd94ad24c5009bd5d72549c487049e7cd30cd261a78a8d845aa800cb7a3b98d3fd3ae5219cb0cf86f512245f6bbfb28d919210014a8578807dbbe1d94852600cc7d9239de68a421035ebb5ae66b8b51cb8606479bec6e7df49cb0ba734cf2eebb9a9282ecc030f2cc60d4b47cf6fe1135a2caeee77349a398bb6f0a31e49dad9f765fc397867606084fecefe1e17bb738330143861350b056b4ab063365dc1e3309e53e8dbf0171f272b415f75c64b03ce7f6692e5d3d8ab752e31d918f335edcf900276a2caeb61a72c9fcdf43290897317994528ae26244e44b16794b7cf6c062cd0353f9ae7c1d728cc1b98034908767129fe4ff25c32f73c074d8ddb009068ea2f9eaeaf1fea0724bd402e8dc56b1f8fcd22f8aadfcfdfda232b685f98d67c121a92aa6d05c606e25ac18098a3b0bb5f6fc3dde66491b8d0b5e7817376bd39b261295f6937b2b72317710951a9928c851510a85e36b2dfcdd4da54893d944f0d4840385b2d5ac726563ad6164d048245a43d735273817fa2721341181eee6ea1f648bff1de45772f636f9338753fd2724130b71bc9a020fae4f12c4bba0911a7afdecbe0abf1272a32a1584e1e1395348e2c14e39e8d248f1e4290fb44f84e25246348bc0e69f720a080a54d5191e6d11e82d37958cff3c673351fa3d899d0aa2b6c198aca7b168b0b0c976289faf5f831cb611e82402820069eaaf9f6699cbcefd6b0b5741294dce3c381052bfcf3e8a283b47384dc24353dbae2275866542c6f5dff0ecd10c3bd21eec4f31aedaf39e5de59f5225d4595dba5705bdacded4fc1d536349dbc372edb5f835bf9208957327c8ea189bce9b0d8ec6a056e266840a12c80cda19d2724a1be5506bdc06fe67d3dd32b2c572f4bb5b033e6ba0d1ac3f855ffab04ef5676ac5b9078070dbed227ff5124917ee32cea57140cebc263e53638a72fa0dde72aa2805861007d4e45be0c5ef174cc10105926574c610750ec9457a04c034ed721f9a068f67cd5ed3e0f80f632f4e248570671361fb51f1c1166071cda1895433556f0169f06297b5dc8af0d69b658e85761d958ca61d03f4f262d9c78fe0a272738ee30854aad5d684012bf24683ac7d4054fe7d7cf07f6c5e95336ef17ccc1ae13f6bc0f743c5f189ed3ecd40543b62e48dd080653a6bcd06ef23325ce6e36260b4fcba1cd4af3e869c59390ac18fa7c8f69597f637e5b9280c3a5ffc5ff51622c72ab2a266eeb1643e17349f2d28f55e5e36dad9c6f9056e80d490010f753b73d1e10ca770b15188ee6f347263628e68a0c65b0328b90b866b776604bc35724a348ac9492a343b409f57a112a467171444762c89bfb82a2998d3969fb08e429fe0a5dbeb58c4955171f6fd96686d86c73623c47ecd8cbed21cdfb8e1ef49727ace9f92b81c41ad3c3903605448e2887c786e7db1198b96b85a2010cb0b0a724e01176c6ed58cd90fac340920ea7c81be70537a8673d1ed7b61bc5e113e3c723ba16b072deedd261c2e12a21bf50f2b5d77e358e1c1310cea75bfa7d2175a3b665ccc7e0fe4394a9861f9c186354ebdaeeab8dde9e3e4f46184925a9ae806525038cd0cb4cb46e7c07134b5ec686c8c1a9168a2920e281e8eba58517224cf7216d03b4cb8b419e1f2016b50b2a105b18b71a2d1e582bd135ab7b6370c4881728c507bbd58ea5c7762fe76dcc62a723ee8ab75fc4c04c9ee3cf3221fe5918a30a6e89a39632b1db570e7221c90b303053ef719994f25980561e2a8976f0278723b065fb4cbfb996395fc43aa0752b91c5732e0a062373f0a9988d18db160d172c162d64b00c078b36ebf760ed5a819650220d2981671ee89b6b0c97a86c5617216e552889a1874e425181c7a0d2044ba451ebd326e15e0218b55bb2ade78da72c9460a868c5115ac68232d0d73b141339bd73591d769d48e709ceb8e175373725287510f771f7478cbd063356bc490194def7f53f4989cccc2a038909d8325135766a4db994fee9c82711da30a42f59e082af6b5379f72c238eeb9f5a14e2d72e8fa3a5c93615009dc8f4247b9a4c4a41ea22befa146e9fc15c4aa6188ee3872d9ed0393d6928d7bdc29a3f94abd2a51d87c9b42d59980127590c3e90c6ae6515d105e555ea23795b5620c1149a4733b670d747fd79463df0d3b0112c646c2724697267840115ffd91c2f49d4a5438da38b6de36f5e7354c21ad75c945d6037284080bb976c2a847bdd72e729d68e09a0ff33017651cee472c9e70d87d9da6726fa55d8a0d8645dc5749a0e9806e103206d3fffe5bcc3a5890280077ebecd872f36777b09bae5a555fb51546b5ec285960e3b31ecab89b52618b4b1035a30b6af1c5785f2f088792ce3f73added4496c525527a53ffba8a07771ed9322b8063e26e562c23ff8bba68fe5817865dc951379b3c7672f3c35dd6f409eb9d0a44e72103066dc79ef7f6d46ff567e1ed87aa757ae6a2d3942f68679cdd9fb6f905856ed24ebcdd4f83c116b4fe1879b17330ad6cc3dfb435784323a7eede302356c726d95ed29ec135d483829a5dbef4098853e5fb6e8a1262aa2a11f8b0a4097df364f958c1037b6b493c37de4a6358f49be6d1e81ce8906dbd2c98b7491545021722d5d499c66cc7bcac33d1735f730531a69a41554ec45f097a23f3cc491e44d72718f021c94b6607f42997c8bcba0b92bd66b9ac97c94ed69c4fb3248b85d3f71f3f9c85862983343fc6438e433656e1b7377d688fe76d233c6610813d65e31726de9daf483877aebdc4fa4e335ad68d6a3acf95e1ddce087d7cedfb1a80d14656bd74a0761961e189192717e7c27d4aadefcf2ba6bb4f6022acb3a386c310272f728bf3729d8414eef834065e8b0bdd581fad5171557f3d80ffdf38461c2325faa300375f77788e2d48a2fc0af4ff2f56ad4c13231b3bf35fd23e017d133a002021464c84a3892ef42db5cbf9148bcb490ac00e8acc95cfcb3d2dfbcf691b372f0d686d82397360c32938c8249c9df586f17bb9ec54e6e70a9dd1231993ee1720be2cf1ba958656404907f8289a4da4525029063a2660478ed2331560d652c4dcd897e7819ccdb58fd508657aeddc34ea0ddd95052ff22ac4bd52cf55a937c0bed0b716bc805b0afe3b21be628cb78a95231c619905b63a3ff184119acef700dcfbb4e7f1fbd0be976d60c71b1e71f0e2b51838218d87155cb99489c146c067206a5064415fc7139614610edae224c6efd645c6bdfd4bca98d99cf9af5c59c4787c6f9c14d3383bace97977bc610df32418008f3657a9b78d58fb0fc0a34d872417a9e43b7a2b6395bba17d00c1cd17780e447c76810d2d6c5b54e7c35ea8309f150b8b98957d5ba111c545138a1b9f95f31bd712de78d86a8e32ac21b2a497258fbb1346cecaab01988268d2ff903f572af1edd60f8224150685e2adc0528722f4764b10cd5feb2ae9ddf5425f00c4796ba12341a65e80c728e4ad516bbb941b2cf6c02a98b589edca9438a09c904bfa72822092cf1174fc5066c075de3e515f1b3c2ce8c12a95a8061530b43014d164d7e274ceca4e3e47eecce0c44e7e16ed0304482c53eda1eb4f3a396f95c1c4bf65de1b8db6b995b9f285ce6a7fed372be251dab2dd692ae033ed81811814a720ab921893508a3186e93815cb7d730110b33553fff7aecbd75446375cebd27f310c1e16f8061ff84a103632588fe40729000b330ccb5a0cd9184b6df80773115b6d92855062e826faac21f9ac7038f72f86b0cc221cc2b0519dbda8c9654d4474b36455745dac8ee9fc5c3040305c716d7296637425aa85bf01e93d9964a7a3667d6bf526cfb9f7bfc3b789fbe70d81cb07da817f48ff8e815a7742a20bb983dcbc5db9c2ff72c679688106258b49016878dd28bc1945296a886364d91dc0501402421796c53a0261c3b96ec79d4f472d7f3b7ce09e4017dd5c48f92629c945bdbbbf8bd25af406cbf06169a3ee83c3a13d740402f1355cbad866e26a17e0b20a5b9bcf8a414e0643a77d31fdb77d672afc9b81de38bebbd62c9cd0757613a106787073d13c06e828bac1fc18e43c372ce1e0c6615eb6025b4cf1de92e3b3ac7313d3ecc5bb97383ce6dca18a3722a72b8b04f97a3bee95dc991c810c1f72b782a9de975b8df28baeb58ff6555b7f17262137fc8c568c5984821a873c74401382de5ea174fcfe99ed8037599fa0ec16c4af2a09b7f5f5dc65d9f7374b5e02c86dc533655fd2909aed1776c0d53557e72182ef38a850e5e707b053369ce95e0a651ee532e7c09176c42cf89cc5cee7d60d465189f5b6ebac5f7ab6d8ad566ec65d0cc305b8ade04436578ab0a5b66d43cce5cf1dda2d57491ae703e63bb7db3146229b53f06b168118ab259a406a09572c07116c9bf8f0e5b9630d298b985dca0c11852a6487efeab682a0fa8ff23691af88c13951cd94cdc81f4159e86279fc83116833c0a160dde504062e5811d971b2b162c43054e58280be6c252122924db477d179044fe5ccfad7dcc402c86a97220d22b819ab3c32c5787cc8a9d041416d81a8b8235348bb47e712899aa25df216ff0730ea98bb3db723872e0108f5cb6653bb17c4805cff982340733e5d3e902cbba5311a823bfb934b43a04ad49866a8e5e4c3c5489052e625e5d00a936e072195b4257fc663a556f34bd55763ed992f2c194d19ce726006e13e90d2b7f2b72f57b1e3bb35b65aa2002bdc23f745d75f10c24f63c9b33974f0f8180def0b072ca1c90f2dd1e9c7f674ad88ea993423bbf544bf9bc4f6d585293ee0ee604f57226a3ca180b18014f58fdacfe3933a0429565a4e3bf59742bee3ae326fb23c561b910a2031e991cd2f8ac1a4fd862f745f6253710e866480d8424fa3e5e5feb72f7859da6a9b581691fa53a450d71870f00d6af3444fd4b1fa72036177d390f25dd225fe5c4d682a927671c2a11526a95315c1430b9fab1f2eb08bf89d4228f72eefcd9fd1172d2807537ff60410c0eaf079292c3eb4d365630a4f8d5ccc4797279363da4e7a45c8fa978f294ed6f7f277cbb5e5f82fa4de96c34d00c59b45b7273f26d97ac37aafa63f82e8f65d4865c00c85616804986aae5286dbe89d73272e47440c90a969d85eab7dac8d349ae5943bd023291ee7c302fb76d4f172b8172d548072063e6862aece180bca4eaaff8c490699fdf0280b1f08ddcac0031247248f89d3a5d19d0b30b32d3ee1d0ed68f67a6ae4abbb3076f75575961876f0172469959e8de2cf88beef13268e10d13f73bd12ff40f40051d45a8c2bfb8a78972ef9905b1f97c2b9b52e3033c0d44e14bac914ec4e74bbb5ed44238e29ea4377205d96c779d76ce6a3db6065921417219aeed88f69f722e1f647e897e7a9eac72960a6537f4127403a444d5b6c7959a68dc64991cf1c6d9b48a5104cb28926472992dd74e7318f3bca9247cbf63629a81940969962922ba9adf6e90c4fd632530585653f059644d23b1acce5facac1fe2b7e0d1007fe3e8ceb07323e3f892f5726738e86ea46526a4161c8cf14150e763d88ab4b69f37f346559304d58451921e90e26d4c566ff226e25aa728f93b9dc879e4f25206e585a59fa9071711bf5c6d1e66e38cf61f70994df30276ddadd5f28ebca64ec2b4a3372c4dba9972c5ce126337a70e10d619747cccc0752939be40f85ef4d86d5ddde09828fc5cbf3fea72c8cbd97cf780dbd43a0b53f068fa8426cb1dda25d69fa153b8f29ff6242a58727524ad6405f27e57b0ae896fd85fa64df6bd525892bf9f47675fa2f28c241c2d7a021166e28d01724fb3d9ff180022d199148e00740caf0b913aafaf5ca34a72ec788a416d3be430ee5f1a19d7d32274aaeb218387c9a4e4269542c6656db746402bb77bc9834a8209e872dc2547dc6373a251a87f4109752d18e3bef6b0fb1768912d9054ce2718862ccbddea877e18c8d4ba720c582b385c78dd0d55d87f237d6189b751a63bd48094fc98235ccd6aa81f4ac96f42ce3582ebbc2377bc2f72a9189f234e98a775f610f14c5a4876a62429eb7765daf0bbbffde6806295a60f41c7d86f250356af775d24e712fb17be9cb3a172e4c8f2e4604c5c484a10981877cff785fae06dfc814ea8f693506ed93475bee096882abbf19bd91717806e7295a6ab7179c68d60a82abaf915badff5ad18608d09efaf9ac2e7555dc4635372db03dee012c4ade516077c352fe8a897fadfb940531223f09f75c6c0b22edc50a8e2b55627795d680effe0c5e3647ffd74ae3642e0559516dd2a57f52f3b60727e6be39c648944b9a0eee4f742f15908916c0fa906f979aa171cf88ee663c440db038cc4fd96495cd82e1f1e38eb8fad5d5b5fed6658f4932dc1ef6285a98b0cdee6dacaa854b82e429b976da5208f74f36efc88a205b9c50de36d6300fd3672409320a4daadcae6349a5d040cb5fc482cdf463339fc97baf34a3e727217f272d2e397e764d10acb331fa93fe5620fd4fab53d6eb33b6e53d282d132948e8572842c248ef794bdd7ca5c4cb9bf4e3d6e107e5bd52b0107741c6de7f82e363472537892cf26b1ad1a86f780681471df1a6e5edb851c3f770cbd76a3cb7c88cc5414d68f87ecf6cf5278cf0f42016129c1ce7b5e9c64d3b31099d287314a9fa972d05c34a37e637fa9631d5aafa07c2e5806c1115af677830811be8796d2a4bd726a0ad660ee5f331589647e0fb0a68e392a7b19cb665aed77bd57ae1b1b38b272fcbc277a2af370c90bccff064548adfcc59a56a4b10808d8fa4ece2d4b30dd724a2c4890a8e8a9ce24a5d7abc0db5e6b123b8f8572946db439b914c45f6a5e5522b6455bd07b87e07006004c31eb943da546c1fb84c72428365e66833681be7250fb4255b5ea050e453bcadaa40777d99059ed2fd4d2b04b00125040dcaf156a0aa1938814984f8d6144012d7e3f815ea5d36f600b1d9454d26e1ea001d9070b727314273a76e7abbd68e7dcd950e6f38a73ec227d5fc0c95ed875a69d135b225101ec1933ed64a7ccb6aec03c361b3db04923873f9431d8999f8946869d26725a9c199d6b614a5fc11ffd2cde8a0ca5e230108dc83c39b589338377de6d48722bf3aadee7cda6261b3f2b2dd7b753add4191c8fed6581b1032a077ac536dd728549e101a82f9176e84548c0012de85e6dd267074a6550dff80b2b54036ad11c059fef8929ed451f303346d8c77001175f9db9941e499ac81d772c7187e10719b3680cc350295f10d3c02c0587fbbca442b7df8edb3b7b83e462cb45ee4a0172a32554c19c6341e727e03c003d132c76d4aa702ef9d767603a71644c9f39617291c0b7029f92ffcc63e7a1ced89116b788ea1ae212401f981a9f7ed8a2ced972eb98a8182617a24c77a89bf52c65ce2451199a1656d064edb4450c19b2f3a33f34df146a637ec0e9ca96a931b7ec3d04ec91ab3177720aac019703d50edfb22ade58209bbdc12e6495230af23761a97c86c2800c838f7fa87298c479bd051a4057c09ad8b9cce9f29158d209a7773a2bc3d318192faebbb1ce2a722dc82b627236b2124b8a76212dcd5fd9b50b4bb3558ec7cdd306937fdb339e28b8814346720912b1702e7d44f24e90721275ef149faa75037426ff0df8b71ca23a4d841c6cf9f77d861fd5bbbd44c3bbef694612878a59d887a283b0054c3004561998271a07ddba88f62a66a3fbf6cdb4a47b1bf26636860f4a72451a8cdda1c6e7e205724afec596dd4694db1094b583f1901f0b80a74102215d1736d7aca6c9f13c4b4484dd50fb8bf885e1c0115dd5cb2a983770d5909e772f6f89f4c9fb596534f2685be176b0cb9794ec7698cbfd58e6c850fabf21f286df13c6158d38f3df93a331a41db73074798f3f864af21acc56bbe3a06e49ee290b68fa83e6ae4f439dab5f28e8c4cbd5e9a6158ccaeec52a6662e5822b2e2b1f833e2ba8439d9dbce4e36b3af406e16427de23d90d91bc5fb282b4f4e2177d6302a65d04f893006c672872c92691badd28b4eae963be779fcb306e582ef3dff2d144ff662f34a4aecf8c35a782047941f1568e1af0d0acf1f13937112fce24d41ec7994158ac4c074249156579d60cd38d9e78f0821795c02996b73e6d325a968fcec27eadbab803b4e3321f9baefe62493f382ea8f557b4337fc785f8a6f2b65e0afbe92b320223942516c2863649eb9e25b5a0422b5c3ba42792eb6e4e139d754add6293ebe4d743d90b28fdfc0fa91b0fd59cf022427675b5f3e9da60b626b7ba7ba7202850be9c1f723baf2a0e2fba0cb4f0a45d793b13810e4b752de204faa2b8c5e1ba6eb5d89b72b9aea8c1b942cec1750bb4278779d0cedd5ab2ceae171d4297104a12f066a549e497235c673dc4246951ca5da617d0ff1d1fca80285c3c545d99404f97983d72629ce1e0e9779a02caadf3bce008ca452f068fb14b20bd7b653ba8ed88cdd93244dac425f29c4e2d252cc054a354aa5f71bf3010a21b9a20cdb51f532bbedf3cfca9cdb07ff64ecb1806d0ba816528c2f1a2f02381ad48ef7b82acdd0e7cdf5c71e4b014fc11daf9abac171572c7a3f03fcf836e55aa27d322115cae21dfda72e18cd3bd3f7ef8f461b42cb6018f982ee86e44e46ce1f15bdca4201e1f35a37260472244384e6bd61dba16dfaa21e45fcecf47027cdfb6a373aab1fb60cf5972b6208359bbc4ce30aafa7fcac1f56fb074d84f9a6e83eb37ff0327dca0c039723f80167852bedcc4b5e107f7717eee62eeb0d5ee02ace0b8cdfde87ee91a5e7203adc155e1b465ae488d5df7867475efeea2abeb2943923578ee70ee1e905572838b6289a2e95e0ee3269b8667da6bacac85d7110c3244a76c3beed3fad5257243d5b2475300263bb5e62b0ce327f01eba6d3baa18d52d7c2a9759cb734b9772ba6fb6946aa9d162ef9686e7d2a29d829abd63336f8856aadf017fd5ff54b9726051f8d7bcd43604b6b62224256afad5e88c0edb025f9499de3e9eeb5b6eef4912ae39d484bd7af6cc5d31fc831fc0e1de53be2c5b50f23cc6bc4121eaad4e05b40d51f05a57764f1f1f58d5fe0663f3e2a7a52f21e0eafeabd7f7a37c0f3b728da682cc46e33efcb1ebe7d3d591857601d1b223bf9e6bd67f4e7f8e44050e724a8449c61ffde6204f6d6de098eb586bc99592f953409a58621a60d61a96d4728df0762251f49e0585aed6ffec043f8834820628fc2f84d50703fe46abbff372dd59a5cca70d20f99fbd06db587b891bc28028e11646962606486d045b9f3e04764d2cb5f3cc2e2670872d688776d8fe93c260d4c4ea5dce30372e85d5814872c183f512504045a468b9921fa3000e8f67ca567e580b1ace33d9b03bc69f887234ea49fd398cf482bd380291ff881bbbc893009614404bce55d35feff0c022729382bee7d00a475b9bb774ed310959713f5989e6aceb358811bcc9d509579d7298f34852dc2890375bd79c9274d9c65ed7a939a2b9f779cc8dcb0140b7c49c72bb01c8077f358fa106ccd247f28e77727d2fd9c9d83d43d3692b6898a4731e7247e3c716aeb3d61a861a33d9a54770c5dfbda3f8505c65263ece63a33c0bf172aeaad18f3c93f5ff96c04af4fdb72ea015c89703eadb0b9b7c69ccbb103a08721459623984e0b6e3e4d25e42605be70d83f01493873c6c6ac1f2fbf9f68492726c7a0091440e7e058a533bdcb0e981d5ddf3f5679edde6a0f0793e4f7ca6132c65a156c49fcd3dd932ea8e90d63e01ee9c07be75dcaaed2accdb73c1c6d0bc09d2a254e26a5a81d03ba243dfddf6309aaffaaa871efcfd281f6ac6fa62798b1af5c31ec22b99edb5e56cc51a9456a7f2a1135b81934cc95980773daeb3affa286ebe2b8b7230902a5e61d4d4608fc5a553d8641b591ff03c43af1231ee1286729d833022b7cd641609ca5edcaa78d45dc940dbeb28308471e0898ffd7f326a723cec03a12c9a47092a5ccd10064097e0107b5685ad89d155f49ef266a71bfe7292af8b845ca91050dc7db993f309c2d53fc1c9dc96c22d344d44b5d7726906724b486fa06800d22624bad8da121f8158626804f4694afe8d2442891aa9f84d726ec437634b854875ffba2bedabbb0cfd89f36033bdcf7a6693616321dda73123d7ea18f86b6621342541c03622afb1cb0459c178a0844df9434da3a9321a6e0d21105afc882f4af85463d86953c5ea4e8ff9a6630859bdffc6aa4416e8448f1794594f05a3cdc5b6aac6bc7d32325fbf55772ce553594515daa676d63b0143720f4bd67699e48ac1fd612bbbe0ae285a97fdadc02119f814643a0a29beed7d726eea0f37d2b19b8fe288487084f79a44700cacba354dfd5acfa5362e99127c624371f47f1c7edefb75b086ec088e212926e43e4eec1062692ce9f08faedb95726a288612e2327bc84f189ce8a1798331aabda4cba6d2a81cb4458a96be91be184f4f5341796cf9ad6dbf185368fd3f532cdf48270a5037df1dd8978a2c6acd72850da295496987b5035f493dbcc75a1843c2d23132a30da2ddc141400e465313f0ea02c66d4cf8a7c95612203e930b663e0b45e75fffe20d032c2ff977f299728fb1602fb37743b428ef109b5cbb637eb7ad046a952be2a58da6678c1d638872e5f1f0213af0b286c1638a7bed0aed3b0515f8d8311600eb12d42f3f5ff19b727e0c80a3e7ef03c4497147a85618d9826bf1345b6f8627fdcb04fa3d97a76e7227168a3a4cccd93348819afb7b09a20e866378855373867a75addd425361de72b91d347b6d2dbf8fa0503ad9b85566cea9a77fa3831543b9db77922db8a3df095c4fd99fc32f6cfb6163479e098d0ac47b5ab99003be32c2a08885144e037f0178e9d47a0d34829fe0a2b1405b566d3b39d55ece6785d8b6fdf7307aa8d13a72432ed6a4c0ec68b6dffb92805c4b6b3fa4a21e44cbaed661bfc8c922c23fb57286cc107f9a1d0f6fcb70282bda1e9ac2a28452ea320c80622417f870fc3625727d44c16a960f25af26eaed1fd822d5d9d10dc3ecb3f2eceeaacbac9372dc905ad767401905303ba38320dbdc5dad4618a7a1ec96ff0ba6585bbc7782856e10729a70e5d676e1021d4e1e3ef3a8ffed6241dae9563fa8f10c971179d9f0438e72cd88c2a58fe4adafa0f8a1582a3ef5c6e2441ce7e447698ca3aa106f230f42728424d0d2704ee2d6e846334d362edfcf35459ad38af05f64997a374c29379d2d44d028b880fdee7de0649f91303555a330e7bab3ce36a512a01ef86de994cb728e9f2cb469ad78c507fd975160b522f6e8cde6dc13f16e9bf142c26ce34f2248dcab7508f463b27ebbbfde73ccc812c694f2433c2c1f306c50dbe5bb8a5f9172d29af828dc6f3800d917ba07f8e4ea673feba2aa0d213772cb6501e06358a2723f6a15e616eb5c371c9eb8608d59d842fcbc95a2884d697e59120769a709886ffbef8eb7ef0e45c57569bb86cf68a0d6db15136e0773d883f457a675acc79d013a11bf9af39687f21162f44fec8deed503f5c07e8a0f780b1e1d9b15a06bc972d4ac6bf80d7a5e18a29482816b40950498b0ac300b9f32048ad01808c27b8f72972cea6d377813e8bea66c0ec4e150bde12d3210617995c633981c1c6d72ed72a9e377989443f4db8ad94492640525a6481c685cb3129fab27973a5e9d1fa02222589174868bea0292f593d6681c871bcec7dbc500abd61f251d042e13e26235e391b1214f53dde493187d0915a10273e8d8f0452692a648583c03361921ff729d663e0e7654fd5c17af49f46bb4df743ccbd71838e0374a2644110328ca9d150d6f79458572b8ead0e30f97f87f0a43ba6b78993674a8b371980a97d3fe2872aeea805e7ae812f98c1b02d8915221d84db65571b215f96bae3518349c1a4f0d6e013e9e84030083b5df88cc65bcb60aed882aadbfbddc2cfb14ecf5f6e58e72683e02086aa4272d35bc92528c5325aeb4ed425366fe9ff9895b4010e62c4430df08d7d5fbf8acebb584ac6591513482a9d2ff6843768248e3d6385e27d045397872d7459c08a4923a14238e8d245a0dcf54cbb7fa08214a29851d7b59776a2ce10166091e448318b98fcdcd5da8cace71ee29f48c57d803abf5ddc109887a5b96df02bd55e4cbd266cc0c493862b29cdc0f512b2ac0fd45b26e3ecb2db2db28426faab6fff448f32762ca361d57631f740bec4d375e64d686359d19e2ade6720a4fa2056055f951203669787976d4ad41f3a6323bf3785fc8c2f9e7423c185fb3bfc5d6098bc686d1c89981ce73174d15b61c8c2c0cea7a9d05027f34630572a8eb65ec5ca0899cedf0a35bdbc7b1ba1e3fb7cbf06c06cf9d24db9ae173855c0bb638834b6e240dda5f7218e45784e767de92664dffafa4983b5679d9f15608ebc0686284f93ee46a672cb495a95a20953e250feb07a62736479d04001adf03bdcf4ee389641e217b82205b2914716e47abea5904c2632d52664134966c08725ec0fd871e619e9b445d4a6856153f5f4061b869f4eaee9b04cceebe504d6b72158cb2e7257418405f06c6b24dbd36f73356389bf9e6b081efd4bed80c847b4b0070204c5ef9e201a2facb685d42fef00a7d809d2c0161a5a4f6921f4210047294d9fb9c7e7f8b518111dbed8648a24b6d336c6cc083de73e7c38da7c65e3b729b271ecf5955bef76a18851cea929086f58375378d0dadd4cca3d4932322a4598064929daac0f0bab7329b67ecebbb6019aa9677530b0e66b6d02e1320925072a266768dc71a44d42ab6722a5b5c0ed3898e0d0c9d089206f57f771612d3f0722adbc579223d9ae4bd17cd95485346e7c560afe701b77a10f23cda81680c5872009a29108a9cf07fc02637ba3fc22c212c23ce6b421100768edfd8470140fc0039961777793820fbbb9b85456f51218d37c6859d696d671857f818d215fca14858462c522da651b469c74bba2266a707e0574f6f08c809e9f9678a24ff793c72611b939af3426432c66d613411d1e3184dca18fedcc369cdbe882368601e60722aa6ce6b48ed038ff46fc9bf8ba50f9b0f6163de1c51879e6da41415fb08e23b2a2596945b08b694256b5b8e4b22b6e4b1b764bfba51ccb92b21e4cc25bc48606666acafab2b8d9a15fa89b6b713acfab1d7f54d6087c50ca7fd106b1a900342f526aeb7477ea1e063fcd8c3ea6945449754a310b1c5deb147df82f22dcb9b2bd123b8e6be1b2b8994b7a69cacfa5ef89e295d08f91db6b6092c56a37a91ea7292e0740cf26768a9d912a8c1d58047a5ec27d73ad13ddd3b2d566c41ea168957aab1c2e71ad082ef7f4dd58a40721fb81c98f882ded760e0ec8629da6633d34fb358d413b091af8e61984fbfe1d35ce1d522c13e4dfaa116a59f46280271390acbfb4f483d869b2f508f9950ce9c2e8abf27b1591db8210ed86c2083df201a72d3d2ab7d18aae5f83bf5d5192bba8d952f2cb0c35192e67b2563b4885deab36361505f5b7149d97762a95fa3e404f3fe9ae795ba5a5d8991022e6bf782d02d721955fb81590c92d84c0110119cca975e47c82630a81219d12c10a86daf793c728b509d1fcafeb6582d3eef5f7337a2b1a978a88f9e4fe43535543aa8f6d3aa054704e86df4ba9e43dbbd00a4001ed250b15e383e2ea2033eb6969d825f7f25723f71526d95998f58b776ad277d4fb766b1e1a450d7a66ffc3013643fa17d881be79c80c58a97fef2b33a206f4b131a6a2b5efc75b849fbfdfaa890edcc6ad172e2b0021cb829dc1ec20384fe87fffecb85d08083144a3efb5e023eec46a65d3094323c007847d7a22b1ba822fd4f5a120f68a6a43a49806252540314aa65d230935506507f647b646abbade88dbeb4634a1d6e616f92dbb7d0fc14dbb6443a39c09123fb2f1bb5c37174a894465cdf21770ce5f0173dc922bfac19f2f1eab872f6994eb51f6b1deac21e2393d453d792d0393ba0be4cd781afd647bf71c57872761736f66049014ae3388b527a4cb92be4ebb6a70c39f054dd7b46cbf2d55672fdd5a1e3dd70d08f4ec6b614b73916cd9ccca1e71959aea91cfd71d4c0622e7220461c783a44782d28c75c4a189e1ad5ed17f33b4deb2903fafa08605322c3561eb555ffb41243f93a700ea014577e6299e3806cec5290d1bc8400095527c372bf29c848ac1a234d1bf80397574e5b0a9765868b5c2f98310efeccbc674afd72a16c01c854e507b70fae152beca87396903ab76f978eab852e072ca5c1e8ab723b3e02455548eaf7a14725f154c55f6fde22d4d3f11c25b8e7944175eff9387268d0f0e066e977639b0840ad0f5ba424a6e83f7f5cc290c3822b9d1f8f9b2664551e5e6e92b55fa95188656f6f626edfff86af97747a69afe51c204584c6103f91d1bfdaa59d9b6485822832f95e8a6433f655787a7860063b5673f97ae44372f48a9301f07ccc0f770c76ba27c87e017b6a60b93c0beda7f2789a35dcfa59724f6ec049b72daf4a1f69ae2dacf5c057e732587e6feefcfa0154bab1188b9222c61d632ad76ac0b9301e0f171dbb02dc0fac00014472bfc3f39c59c790d4017219eaab6e3760acb5ccbdd218e4872beb6c00a494c7ef29243a41195bd5a2cc2b6356dfea155f3b92abe8937352de890e10c85ed24c9fb1bc6e145f9d71fa4572238519b8acd5a6c066678cd6c8a633110f339fe784a86ad8204a5bb9aa82bc729e6af6b72fe070d035e4a3a87f4cfe0c8ab5c3c816d0e64bddfe49abb278f9693c1d431ede11632b02923a73985bfd9f4f0394e6a31f12bed586c4061072ae7246e476951e7559f9f4cf9df1ca9dc6a5d89b017346c26a331580ffd7ca2c197252282fff171e89b31dd0590d404788f73a86d7c95f1e32e4fffbdbcc52edb572e9e1850bead3e699bc147c0ed64a1d84cce93c3f93aafb1d5026529ad57071725975df8445c2b197aaac35ae52c2cd588d48e930119ea7c64aacea0aa4a42e724853dbfe5572ccac937a3efa1805dd7e2760fca672ba6ffcce3b60abb3ef9b6d7b37d8cdbbfd58e57d99b34f944a1a841d5b41f205c37662aba374f13d5d1e72e8dfdd9e8e6a5f4c5c01764702455258b373365b087e25bb13f8b270db8d42727e1f103e137d09f5d060a312175453cb6c45ea93443ddb782db7d650771bcf72b30f1d24cc2a28ce67ea7d445995149747977afadeae20ccd5700cdee08a9e4dde3d80329db6460b704a4e14479c48b32ae37ac517ae16d8ea3c444abf802f5a15abbba7434650621611b55fe45538ff3b5ba91584f3cd27db63bad76ceb1872984c8e603512a2d0a8ca08b05adb96b266e484d65f5a1f26428b14f35d578a72b2b317d7ab058b52fa95d80be4372f961980f964b5d850f94061b33f9717bd647eb44a77d42f9476299e995b4b9959552f0d1d145d1ee3c30e46267bfbb20e099e703b0b425517971b8afa23e5e45d71ce4f0bc07ca565587eba4116fe7faa3d2d1d58323d82d8749ea8390075a8bdd104364baf7fa3090cea5c7b0203d78d72617086c1bba08ba731a9d80887643cc267fc1ff885573b7e4b6bb4b8e2475272a1bdf3c72c36fd699c8153329f8cfd71d1fb04f657a8c022625a846b991ad972d2f727d4011cd19df7fbcf68986b741b7b6406cec71e762387abaa0744cd8e7209e51333ba412d178aebd5ebcf2ff68dd0d59ef132720b13f0bbb3a864667872af8339dafdcc7829480a985857aed50e0f30c519b07be4491386c8b6171f1e723d371eaf71d1b1d448c72a7dadc8194e354a8b532d7adbcd275a7e60d3f167707872038e924bfb5b24f9de1c5e794a4e8186664f43eb43766a10c0e0af0f800eb143386e0cae9622d6cd501451d4f10539585bb2cb36c2ab6039fe3f6c35c059d9ca25731869aa6712bd76291c666cdad42f956d8a61017e3aed1ad8cd8d36721418e2b336a2410836088cd959647c479dd3a3ea5dd0eb3e29432305f8fabf68c386649e91128d5f326051ffd8e29dc18116c2a55914e148d7be1d9919fbc0111768e437c691aa4c480d1db50f524b8068aff007f5a02dc32a0cd657a054e7727590c6f990857562a3301b3400ec61c65d88d9fe48c12e14e7ba4a3a64643272e942031f127add61f701f17a6a198a82b81664121ef0ae56e477598010f0d672c9f4d823e53c0ed6f130270ec9b5cabffc74f2648fffe98d1623cc85ff81cf72dc5704e9f3ccae29ac1715cc6ad5721684214dac2e0e6d8cd8f148b4b104a717833ed60c3e2c14c601eb8949cc27790901da81508f639f1b9001394c7ad92f725b472a68a4dff05bdaacefea92d662986639d5b5e305bf353307194172df2a72e4963d8551f15772163c73dd19f7e274ac0cae66063f56a03c926421895c07725100e177b6b99a4d4f05c9e5217a38503e12cd00b86e28a1253c3fe02e983172eb37f9196dfde1e51a2594ce528e55e59f53eeee7d27bfcd9bf87106cbcdc8727366d2aaf2b768aa78497617a6066aacc53fcfdc90100762c47659deecbb5101aa4472b636e696c23a6686a16deccbaf1a35c1c3832f3529be07308fd62f017241bfad0e03b93b940b94913d770ac2fc3a25a050345adddaeea9fb41b26d5c726d5b37e8d059c6bba7f3f86d0335409627cdabc7600efe21aca4f6f1a109e872944cf86768bff8605aa85746cedb43f168acb00b7e6c0cdf0eb114de424b3d72a9d83120d1d8906a3ed31e0777157d8b8d986cdfad9b386485654627e66cd043738f94442be24de9b6a684f5d47e5825456120889fb825808bb6b8898bfd3e29964699307458307a8a89314554c356963f5f5f56419e138561c938457fd2257224b90347799ee7f3b043a28b009bf1a3c0658c5681593f2ee09694d8bce0a17234a2859f1dfd3bdb34ba5b25aa3b237342723ab4605547b4b130c6822cf5f172e8097a47cf0f6bf2291c54195dd8d55496667877abf96e82ba9062cf43927672d408642afc069193c2aa5c3e468ad72b28604781e45038678782813f4a682a721cd6f1052b74d44380d01e63a49ce2b8bb0654e0195ab9bb58e5a4ea25585872903c8524ea2323a7093ba08123247c2f8027360dfed0760fff38c66b2f7fbf7233aaffef70c65360af944e4ead8449dc90364fc8603f45713329f20ebdaa287207b97fc2fbcd03a5dacbd7421dbdd23ba75380b1b34809bbed0f340f8383a22a2f97c5ec531bc1b33f1605b0d261ac7efff9ea147eda219cc15c5c200c22ad55936554aea8cee17bfd088b9b3dbbcd4a81fd05eb94164c9f1bfce63981d9921f4e7c30d8b570190bbd9a5d7ef15761a234b9ddf612767acc2d68e3f699441b5563e7b90c0293246423f113b13c7e29dbf520939438262f54c94afb2ea3e53c1abaae96da443bbc6a701ad404e4c784ffc2c3292385462017a80469d597b488722e0dc3a28e1c22a38f334dd26300724d3968df42c504c4b9b5a7841ab4a4eb72b1a4503f5df4f192da977b97ec15c269d7d2205a7e9380dd946409243c2b8519490eda39787db5b79680c2f27d9689faaac04768a2f17457d01da0d87a5b9b5c704a45eca22c56dc8c0cc5d11b11e1fcee2c1afb09e645c6299c99d4360b2a72783cd60603b64cb86340cf00693af18e3c8da14a97eacf1b32bc1237caf2f372f0d84358b4e9ee4115bab88d86329b67e536f9a43127731036da508d05dcc072eb733732218aff2a1f223ee26347b1177ffae3609c82b1e4d67871b8226e3d68f5f931b5a5453804028b32c33346a19452ef6dba71b2a23d04e2f8d61d6e96728d0854341e1f639e690e819b69c6f62f720879a985702109c68e2e380618c572ccce5515d63a199ec398b265b8a93d874aa5b0d91cda6316a43289f5c5fce572df21e3c4166594a19a9c6ae4b831bd903a18efeb20f62f9b7df6bc26f17f62724d9bcec43d56056ff4314b892ac89790868e6045ec4dfc901d5326cbb37a4309dd711ce20acfb7d7a3814964100d96dde7571520ab11032c8ba3847803511a7259617b7a1798fad9be414db3e23673c1d5586e43258cd03c372482bbc72fc0723c124d555070d7adca08467439f514e592340d3a92d1f8659de46f6b021f123d284d68f03218f0883ae4e1a8feaf4e4cfd3a7299574e298b6fb7246248b5fd1578d6ccacfb1194d5e457673b986963fbb7d22eb95f412485e009eba9af6fbf0b52588291e2b71d64f0ffc90e66e9eaa127169d40ad0052b8dcb02458a0c69272835dc12d188a776f188ec744ecd9e2385196bff925be97580597f7f6cd7a4b7239eae008b7448073eaddd4fc8aeeb65525f1e956a214839d79b0941e55781572a10d1ea844595a3c31bb866683ad554c2360eba858442ee749fe487d37777822f781e71f312681ff2f7249fe309366e8fabf45d06b78ced1abb45ad34418f950878ed265f3cdc7b7d3bb450c896a19083830b90240b2ba3740c52993351176723f221b8b5411e885e51af00d24f707365adface61f3373aa7392b2fd6f8f09720517a00cf759388765dc397f03d0459db620a9f8a26f4a41d4f32e34a29da672d6e0b65c729db8250733d63634c0de619d3266f3251a6e1363008e3c5e861372d3e7dd4738a8e51747b2e09a3c92a9a43589d30effbfae8f06d9391550bb607236e1d7aa27cac1f94b7b32405fe7293e8dc6162383f5540456c8ad24d0ec915c0eba362a8f45c24cd2654ee034d51211a1a99e477fa949091b0d5c6f7d82fd727ec284ba2a6d4a2820cf4c047dacfdf3992e440a5926210e44bad98af5beaa72db87e7406bf77ee35b946dae82b77a47ee531715deeef6b5e159728cdb72830a56f4d0ae4167590058a410edbb995b980f0525ac7f3f25693d4b71d9242ecf72c59241f2108489801b0acca5919465b409b384a305bb50497a9cdcd27f026d728a811177d793f18ceda49c91d94bcf2400b873055ab340246c4df90289e0683ccf7151de90b999b736d1bb1d822c1626bdb1119c29991cc6684d5c262a1ce216bb355cd3b126ff5b34127ce6dc844ea4e22e0e6ee97bf81fce1f9a470258c672b9acc1918f946cd6f9dbfc747e9f57bdb2c4413aae2682ef0420a4150a181e72161de4d90d41c4282a70b387bcbc5e50406a9bc716a3c2467911144a995fc3729b00b0465512069393e716f342b8e3695ba1d54889afd0a19a845773313a6372f76b5e479248e63312368e1e015d181adb5383aa8253ffb6a416482a6dc62472327f57b799e2079950312ec3eff9afbb6cb65b86009dd61478b79c8db4e80472135ac5e2c414f8c27db66ff2338dc46a1df61ec766c0aeeb6b6a2325840ade55c5ab46ffb39d021d71dd8f3c5c1ab3198637b6c070d73aebb9926b0fc3e19b663c1a205e2650931995d633c3ca445426c5ff91c2a619d597a209daaf2429687247ce1bbaebb20fe070ea6bf51608b934c04a52dc96d766f576b9fb4e410105726e00eb6d7ef1b4c478292263879fa36127f3c41af44c3bc0229f62f9feb3814b8a6d1cf1d5a74d8a90d056c75e23cc98a7ef9b8841db63df8bf1b7ff9e042a72278fd3bbaf2e25bd0e08188f5acbf8572fabc7345f881fa10600452139eecf72cd4b53406cd65da2b54ec430179e056cd0f8c2e871c88918319dc8dc6af04d726a1b99a82f8e48bc2d63936eb211f882ab1c125ee65b0d2f777477fcb0a6695e0b2bc4e54406915e31ec7ed0ff03749e6ad11dfe862dc3b0eca5e21795f6e654f1f69be26a99145906975fab21f19f1b9190e943e6104d966af8ed806794f223ff7688060a50df98b78685a9285c73b3726a9bfa0f623f1ac44d96f4d35c0d72692958578b4c65874e4c2b2ef2ad21cad43a0cf481cdafe5f5e3ce263dc80b7212b22e2dee25fa9acb66e846fbecaca50917fe9b436e4e73f24fe421cc0b1072777d5ea32c8b34ea4b46c313d3b19836de270958ecf374c244a0d9e5aa1f6226306802813cc4bf0b802ddc0871e2dbc33af4e17de759ce38ec067f9ecbe74372da06340163d5e4e3d44c74e6939029d3621e01b3529bbfd546ebe212b1ded94c2ecb9a43c4ac7f10ff1d4c6e42ed1ab81691985872d81567362bda21f10c2f72953250f3aa31649f30d0a43a5748d0847b932b8e3eb417248c05b3f5589e2a16b4707d9e574da722fab2e9c088da8a63fe5cb422c67df8175a5eeb0b3382bc43fc078c80c11d1066362ef5758c70a24c1020e332b49aaec5fd4e23439c9519727eac45162e9b2cd9b4e6c555265b7c452e01e8724c7248e636b71bf7b862ab729d3085a9ca2ecf5a26e58628f7543dd34770f9078a1b14af67c970c59389884bf8d1929a598addd7e209f6b41d568c506470800282eb672d22046f5f4eb2a072dc2f7b4951613820b5b0e31f31c63dc09b881408e746120ca862acb61851c772d605d77e54618b352c7626354c0596356f8a7b376240642236595a10e2454772b25fc5abbd1216661094fb1d302bd83f87499dfb1582b4c74f3f469812176d7214f2fb363caaf8d30e21b781cf57c7f63ec85e5d09459ff0ca15d42e3fdd401103efd2eb5d3b5449f02676cd48938a3786d889e6426b60c3f5857cd08b744d68714cd6faef6eafcfacd23eb9d16a12c9dca40ac155373de552147afbfd19ea727231d3380d1f8652c78da2b908e259e0e76f3c0a1df4508ec2b5837a8335d071df48ce1ed3ed807cbbd1243d6a6f009f1956ceb924c4637af7d576ad4a70da723eb965998eaeca8e61f1d8648a61417803c375dd84540511aaa3ea97120df17265e938df930d90d14720c35a428a08969d685e829ec4f8b7f58d07bcaa79be7275cfd9ebc5ef457bbaaa743305abe4e8c1097a0d88baa51a70b0a6eac8630f72c848fd715a982857af69b45e2d4c3db21b23e18e85a776f37c7864910e36966abc31728f17f5ad1251c8bde6f13f8c9940c78ea6af2d859d4085034f48dc72166897e11fcacec839d78b3924e103b122945a50712a554c5fb6b98821177d274fa13da522a50550e3f17a567ebc98db7474dc809e7c2448fc1e50a917bb17fc72dea5ee35846d7174471b94c9a6c5aeb853ea165b8692a55e7747b55760d1aa72396515997751e912d56c773ac032edf94f5e210a7416bd722d3101305e922772b92851eaf449c60038287957ad5050d6e2dee1e53ba2a1b3aa76406060ab17724163cb8e0aa00ba470ffbbbf00229631ec3286eddd96ae3ea4481e0e2d735a7292def185933b1b34a1adb795b866b79754349ca3e88eabde32897ce9a1f4aa26204384e90739c6e536c7641bd2f0fb0dd5ebfd8c581b15cad9541ccf15aa141edcaa5325142c89dd9fc6a2e75316fc22bb7a655dcdb438f2f942799fed33f8368c39e9ddee596e67c8dfb03142305718619d277a1775ff57b243ad76f301b9727b76f837f27389d40ddf2e15d808f0979885fbce50db47a50731deed868ab07205f9a9a2220779ed7bc5dfdf7c1418d654a7296210529ab50c20afe4f33ac072902790f526dc6a6f0297903442979ca8ee868985a1ad9f202b358db5d39fb47206befd49936dc189194571a200c83c9408804fe00bdac812a2a3d3cb74207b61bd378126ee0e6a0c1252bb209329b57c087b175a4d13693bf9b9e22a50e9d266f2980eb9a49b9b18c79ba243734bd3eb6b90e44871a11d7ec4b558115c6f2e42205276fcce896bcb4f8851c090bdde8abc1ce5579339c742b568da4e3ecfca6c83ae7373700bef4b3df2f79fa3e36207d58b710da97fbe1c4bd779746fe586726d4aa6927cdfcaf72f1273858f9b420b1b383ee19c60157cc382fa7ae15a931a77ccad8d99c12836ab6ebe4d6602ce14b75fe4926cc0f96b9574f5dea567b7728efbce5726b6a9433a13dcca4d456bbf9256492ce9d74897cc7cdcb2f0f894721d62058f6a7156097b3da4f1a36c76574c4907acd7808a6374d182a5dbc8fa7230ab79b7774322f5335ed80cbd710784a6533783ea73f866770edf28ae278b7289e94ad8f7dafa2226aaaa63ea5b7db3678066f7a3323bd4beb853b2aaa1454bde3df72557654bf9006910f9aecaf7f377bb7695f19c54899c769e25f67a435d92df55bc1fb36abc624b3161be5c830afc0ef75b9e47e24dadcf3a9cacbc9c729266fa0ce2edb02723227b81fca62ba00568933d17baaefffc3a6356051c62694fc9c767ff2c12fa639c2eb607f76594ed75186dc048734d019bacb69344a772ca7d28a1dccb0dfb62bb71dd2a8a11bce1843b813a576d0c7acd88c07fe41b2b6c5e22223e9101d00d178270ce943fea0ea493c74a28621572d7725068c5db0f7f9b64cb4d8ffe85a6cb1863cd7d9342242e53fd72e6e6d924906bac4ca29f7249ea1213fc470261a85b36f633325e350640327d6283598da3911d9a8536ad72a583b5e9d904f02d249b1a4dd899f50bd3a018d5a803f1f540ca99b338c4913fdc395872ad30caaa79cdc71c34e4e51f6277e59eb8a86ce201a734cf4d815272ed679f7aea584c0b0e35adb79fd4b3d2c2ceed9117398bcbe731f105a5d0aa4b329b7e48eb1fa21a35ec71eb0b503f68b54f475327dcb9183fe7fba021340672711f000973ffc94c4328896f8df93b5dbc6aa79bfa83b3063abdf8a3cec57f3da9eaf0e427d3bd29dd8817aa3532306513b91e5ccd93c3813f245f0d34a850722530c2ec66552b7dadbe777f167df07d8407a4fd2fbf2b3b772aa96885d56672d73effd9f8745a05e50d8578e7d52916e408e71eade44261691d25a8d3806972667ca91febbe5c2db9f13094d3ba07f5277489ae9fc13692c31ba7d15263295cbe234114c75acf7289e59d3e70c9fd716b8dd6f5c7bbc5cbd477f475de35967224e2a1456cc4f24fac1c3d237edfe891698b8acc2ecb66e237a26a1b9a69237229ef85c7708176c7f3b7d1f75203892c3b0bd5e4c871f551b5acc31a93a896515d03dceaf251b79eac9b5779481904de09cae19f091e37f36c830b855c99f5725b741b91d61685e745c1b606599592065ae48e4f6166cdaadd3a1fbaca41c27284b9e49fc6a73cdb2a2be91327c2ea2ebf586d0c7416e39b7eba02c2768d52561b8a0572ed6476de46d52a65a496e0ed0f03f2c55afee0df545ce9471927e315651d51d37bcaa6dc567fc309f1821005d41fa04cfd239ce5c5a7bfab44ba2c4f3d97855e3cc708552ccd1f648d650195698b79d25036bcd9e2c1eae2816bef368ed5949f679097a2d1d3ab3569de4bcd189a7388c252814b996e18da31838772f14910fbdbf7107402365ab9ded3aff51c234a2c811fdc9c4ec34e2e7273670cc3d359d205212f54b7c2fb4decd517bd7009ca1036d944ee46ebd7d87df5e97242f232176f2aaf03c51079c359cd295dcbaf772fb04aa13ad143e74d85d5ac45691c7ec222c59e14745d9cdb8184225a0c37dd3f193c192b0ff33de177609e72904935198af44e94ac2d8314a7bd5a09c56c1022fa94c1d31891ad659207181d8c843164baa4e06a4d6ef77737cd967af5b8a59a435424a41aee0ed4a54ecd72c003388179536a176434e6beb2db979d75eb71f600e9f72eca6145d8b7a81c723e1465838aa1c834a0bc2ea10f5b37e380a7b5c98b7f2f460b0e129f98f1e756da92fbbc87ebf9d20bc13ffbae03f84d35ec18c55ef56c97fe0902e32630181e10cf1545f21addf845ac25629747539a72ba84b69c00e4cc06f6b70e39e7371ee277c639baf0020d17f3661e41d5ed61edccf8c815d9b10a65950f50d4d6af72fcc77fbbf265b628b7ad9d9fe5a2dd9e0c31fbcbf9dbc6799b217b2546247a4119fa611bbce12b2dec8835bdfcfa4042a0be780cfb29ed65773fe4900a09b172f14e1c49beefd4d417996370c91b38335ab0f69f2e7d4a16a8e15d87d1c8c512676c1b39ca2fcf614e65db8ec8b6534bfa266b49b191e586f4314bac5e839d40c33f51d74f7cf85fbc5b248201e57ec7b1a75298e826d8bb16d5cdb2e4a18734a5726ab7fe1f9dcb68ec69157dd4250ff28c035f90bbffdac797302675efe272e1deb81b5f823f4d7e21dba0a64494614f01b05626e133572291debace14a47275dfe51385b2c85d50512801b1de4b2df1d4fe4cae938036493adab47cba5e72870deb265c2f997e271d81c07a9130bdc8b4ec3db0b2e12a4641dc6156b54472dbdbbc6e1f5f7394bbde9992efa57ab17ecee18acc3392fae28062a98e8b5c72723c14f18373c6c6256e19b6fc0d8fbd1ad457434e290f3fce7a81a3344d9672dd7080987d9d3cb0210114a3ef347fe39e96536d139ba58e791bc661949a4a6cf5cd9115c88ca5b126b8cd5163bc1864a6dca25c2027078f13f87c31ccb589307c9e8d9abd03eef69ebb8d8f4426aad448dba78f2f664935e324a9f3978cc0167a1bdea032909496a305625d0e5b55f2028c88f2530915d778788a59b26fe413da5f827dbf2e68830f655dac18cacae8b3c8b099cdc70310ebb5928a1173f97225214ea25cd860103b785faf1ac1c295a6a163ac5b200be0d9fcdf3294df2b7290db3a50a3a1fa3e9c7abba4eeb9a371d003ea7421ddccae888a3aae6e2313722bdd7685ae459c7538d6a5291c298f1f036678527a1b191ae35b606b2a4afd721675b91715f0d0899b03a9d0c7e05398177d064969754c2ad2af59cd86b2f17252978acb78fe5464bf7885b0417290e58d42c561cd1754ed6dbbe432b6eac91d8e6d98059ba8d630b449916c111975cd56c8bc6314e184482d23b9530b610d073d0df596295382d54e5065f470ffbe4c31c7159c5338c8a01b1ca64e02b36e72215049b8dbb352715badb4cfd0c782d388a01b0a136f11e0fbefe12a95f8485011bcf5984fa3ededc06b0155d75cada505fe5c01a3f2dbebe0ed476870e1783ef06ef1728e28672eea305a7de0d8654c78c451ea7446092b045e186625415872b0a59ab6433dbdd0c88c549e91a5fddd7320c86349d1f5be40567d40367d5d7240363d6cd0acfddabe0e8dc8f3e6b053484d293159bad9e9a55bc497448e6c72ce7d3cd6fd07807464078888d249308b2a8e87a59348192f166779db3890415e1468501dc6b3e6f45752ec7ec6a735d48e36c5ed20819e74ee3cd58ebacd9d7253a2cdbd050c63c898d0c7f3cbb09313b29a011aff00b22abbac5bf62797587220a6da126a07c461fc7d081dc6cc4da5f05ab31497ce950603cfdcc5d715fb7202a7472087d4e72010127994c103a44b05703758bd2e07bf3719719915f052154b2704a31d0da9012e5088b73d25141468395c167913955ad72170bf352eb90b16a52ea8d9dc4fef8c60a7311baaf6dbbfd5679154e07eb14d527a70580af10eac5f46b27407b2d9948647f62f27f9c36515dd6eb1da4f5a464eb22194b92972708abaeaf646705fa2cadda851b438a4e7ffe50e6a7f2b76d1248f62c3756a721fedcf0e0853a178b47b7f4ce2e162e47cc4d18d948d0df4a47b1d403e2f1a72d0831dab3fe626206213e5824a42148ae58552bf34e94faf4c2b462ca6cfd5725b7968802484e4020a3e706ecd14fb392ae23fb3c9e1daf7257453c04fb6cd7276f707a4c6f4d778da5b9456e4105b237c74ab980a415dc9530835781b852172f03dc6adc21a731f2e34da2b0a4ce1d17e9035d73dd019c82b0e93b530a8e072538e4330d8015f4c00dfd53bedf81d98f2c992e4b6028ed8ad0671a0f051bd28825e0a48fab3761a8e3bb8a54f3da42184e9fe911910f4750cee838a3aa5c2726d04658a8a97eb799b1a39f812d0d3b3c4eb338e0adafb8ff887a10c201460400acb30d8fe350ff792af7d17f1d551e1f0434aa700a5a77ac7e1b2402af5df3fef9c27f87e67af31077e12da2a2d576dc62752d76eea32e6eac3a4a104d67b7210b881c2c6ef378d3c40fbf84ea9f679f09986e71fbc127ac19ca7c2c9f859724ba869b94cb92abbed1be7aa36d5a106196646497e45e82afa6f7b78cfe7f5383d9400870e35a6bacf5fc1e6c9353436eb2142c2c845a26a0cfdc1e8fb754d2b6e9383ea141cd0bc7c42d28bec7628a615cc6fdc25e128673b17aa67aac56809c1744e23925bfb9420a5d9b2ab0c59e142103acf63fd262f5a738c4f6b5b0172f9e85dc028a6c421745140340b557ce891eafa8c4b357263fb0750490b37f172255c4599f72c7e9d2a895da3f95463908f021ff553f2e26ae217f150f7764846a54f62ef060aaf6b811ed8b8941e842866104b50c50b18de2af36733556298277b56099d4ddc1ea4582e0f4bd8efa9503a1843e0963e2d3d3bd9684eb579156e741f17a99fe5fa8a691b2706825c6a3afa7596c8c47633529843ab8eef143f0cc8e4d83447b1ea579a2d3cebafeab76ff25c9f9863d44b06c37e14c8c0bf4b13113a67bfa778663b80a86a5da1241e55177d829e8393f8efdf07c9c474a2ec72a94901e7ae45daff029c7bae2b49be5b4032eb0c979b7f71f099f2ce934510722b7d05fd75c6ba54e058717377604e766e5f2855886f53b762d6207b7fe929720469cf5bccb830545ba72be8500af6ffe87dae596c130c7b433d8db479323a72953cc08c0cf6261bf8c2b61b195841e9b1684a0be674d98a7872c9ac9a61ad727ddd3b3c8ee21a288b374a8a3b88c9401808f558b8907e7d759cfd341b509d72abf4530bee4b2b995a6e968cb874709e266a68a5b964f8f11b23f78a86fb712e1d81a12e5c7ab9cefb7e5799fda23f0eee1a7791b4b26647a9b5885a9d1df946fa33786b4cc67030121e4379e354bf9f973a6364109726a24a08eeb6e1dd2e7279c0d16d4046a55358d8472d88d9f3bd080776c7b934c6c9a6a3df689408655f5e690597172422535a793269582ca585aae43f130bc46a309ee2110441564972f8766d0a892416f1bbdc38814caca857cbaab53be4d7e7415a6541aa62063921d737c6c0fc1849f93cc5beb25f38ae8d7ac77eb312156340c22644d22a89ab726708a06bd5f1f76b905b2e10c4b25c43edd6af541231371e36e36fd979e29d0cb4712f37b9529d7200b41d19b193c275d76cf191f11eb34e7157d2d90d8fd672e4169cdae216897e39f6ef004c7de4ed963de18425321860194fd28b7a7c9d6d61938eddc3edcef0307aa7d27d444ea448cc2b70afa7841471d3f98b4dc2b272059931e54c714164eeb69a9223d4a9c2acbdf0900839f0ec8788af844e93da6704cd20690ae4be493765bff4c7c0d0745bcc59f3c425e5d006d8b7252f767f72516fa4a55243e04651743f615edfbe911a13bf92013bd6512bb15cb2e707aa72216bb20e11d84aca084af0ed1680975e57800aba3dc1828f02179761887c3f55eaccfa8e555f252270933c4992167dfe8cd7646fa92a95206c0417e20ea3ab640067de5da8841ca645c9cc103818d6715efebc08d2b80a766a1bf0bc10a5d3727734de5893aa0f492fbbb46d5d62e40103226442d34f8144afac16bf8843436b75c4660fb0260d17b94f56304de7836d710dfd8861a72f8d1076d5e763df26722a2c7e5f453cdee0805858384af6f2cca89ec2d7745d57cce7162337ea8b3972dbf051e8fbbf969a899451cad0d9a09c277aebceaca5153b098eb4ffdf21ef541ebaab3853c085e844533ea8a5ff602819160f437cdbcc1e9ca1fe257dc85d72b3eb9a13479fcd9ce94d85868947d64fd6198d63c3ad0096dc79d1339d5e65720115bb66a7332502214e8921fbaef42619ed1a7973e68c04b7dbb12c79ee1e1914b41f2e59f77d25c55d4c17fc32a33f2d7621d7db074cc8a51d564b3c72ec51577086bb37ab12f083d34632a06adc9e68247f0e7e71baafe42d43312b1df10b8023bff1361ce1707084a093a1a8e7c286070d97afc607345ae520999b9d434d9d333774e2837dc676a19fa5e595cbde1ad7fdc8b53adf7d94d6589c65b25e68d2a5ae068015c9fe39021283032115d8d2b6f00f8cda2085679660f44175fe6a33a74789848b530ccc5b336661645473c444b19c6babd7b1ad12c981c793567230ac3cab26e02f6b507f1039dc2934bf1a7a2f0fd0ba77574a93859c095af97240e239e79868894472e10c4229f03209b7b3c2d61b72bb4a95664fe75610b050d9f1c999ab15a41198b87407d20c8d695c3b12f1aa6cf373ed421a12cd7bc460345b28f86613525fb83985225a38072dce3ec297e8cc944d7d13042a7b98ed72bbbb88661ebcdbec8f73beed789277d2260c8b5e55ef959610010e4c74e28618be6529c4676d808b1ff55fe257a189fcf3844a42949870d39302efb9663c09720639cc27761c7f83f01bcc44b4fa0aecf58d32203027d20b2fbd76693c775572e192465a13e28636720d7aa959cf7cfcba117dae6b56572191800c7cc9459e72847f5b0c4d10f59d539f8af7688c3fd3e3fbd9a5656744351f43f100354a716f8184aa8c064eb0c0c6838a34b6b7ab1c1c3350d4a641148a6079531c8fbf824da08f2c205e49cae3823e48554c2ff30bf5717f0818ddddc80ecd9cbf4d6a8372c6da5ec28d2756d62112d294942958c5367d9d28bae5e09520023878cd76db0f0ad29276e041ee98e188b49f52751cbb6fe888bd489a38df85039ba02d93a6722dd2a26d1b6aa6883dbf8c48f079beff4402cc75651cb1eb95c220f401af284bd907a4e6dec8f2db399e52c89d28464d108285ef43301429ecaec0ecdbfd6350fd5661210c5b456b72eea285d49a7c94390c48af0e32dbdf0ea832a7bd15ad6dd664fe21cc2edb90193d8526f44c62973941dfeffa0bc0e1c173beef632f927260a80d9c568e1a8a62b49e8ab963922d71754504e8841e337afa30cfcf60f0424ea28b324c0fee797ef463f2b4dbb49ad7bc3ad717d629719a0b7eff33bbfc7227f9329779a39f209c57ea4506d024a21ddbc8848695d454176e34d292c9b272d4613bc61b35b6efe263d15a57968ad39e089370e8c131323c9d2de22524ff727bd4c19b1fed1d9792e02774c49e736d9a061059e8f23fe4caa9036a91dd27185b7e2cfbe3dcfc86629830568698c06b5b9e8ab5e18a1e17d4f93ce974fa16147166391f394fd42a7596c115577bcdf4f4d8d08e9cfb1bf837cf98269ab55d72c1ea33223a6f1387e4ff76efdce25f3b7492306edb316e1200dce49366285b723afa62fccc85b27d4d3b5d76236126b4b5b1edae679237c4d259d82ad529ba72e7d023f7a1da64a5c3eebdbd8bae3cc3299f0e4ab6a7fc41d3ed881debd82839294ed1f67a39ac0965edd933dfd7e2e17740a3fc87ff67a00869b4dc637c9700389f025e60283496d1e9ddc9f71aa080c1b93fecce0787cf67ceb36e0448d612342002f66bca83f81384ae252a6176ea102b3446be03d0637e9bf84c421e5a720f1006a827c47f3f3dcaac465f88ef7c505e891788aade7a2551ae9f29f8467286452aa5306f48b6dfb7d5ef85e69c2bd20aa3cc20c6fa16be9b36a2895095727a6501b912618a2814d8f819ada45c8228ff064c81e780d7562a6422e230b6729d2051f64b74ff3eb05748605eca215c5fd88aeca803403bd0b450abce5f1f724ec925d9341d2db7eabd2f6a52b3f7fe4bcfe3f5b27c19f8feb9499a8d8a61724375cc3591d2ac240bbcb689b8eb0bc87212527bf9c73f1dfa0618859c9e867255412b2f58934ccc9a59ace7bacd0cc079102730c70c8ebe7122215a105c377251c4441efa96d703c2ba5e877613d2a21c394abe68de9f6a763e094d51cdf10cfcfed51d4127ec515f0ce61f004bae548b5b82db64543f2685b76835512f187253ff0a44485a28444d85f626df3e10717b6313d35899b10a830fa4b3673b5311dd67373d828dcd86a58301ff2b49b6233a299649ed14eb2152d0a0d7af9f25728201a6d5c7f4d58bb59fb4f4714aee3def083fb9e616c53fbb6f7f6602546672c6dc30442ba3aa13c0c531d9c01e1bddd7ede24f53b8b95e717b4a29d52eac7274d56d1413ce0bf3ffc9738270438ced8dfe02f4cef86e3f9d80ad99c0f8d013078a3bfe9cdd28a59b49ed0cae434766d02bedd817d019df0d372993a5d8e97251deb11ae6018e07b24208672e2bbac9c9ed8ffcc995e635d68e6c829466dc72ca76f080547b5ee3d877a52c49a325a31ee7f3c41792d3f2c1b14b895c803b2d008a3147b6b7b8793ff7d3413571f11609ca91ef9f14a1938df519a3d3672a722fae31290069cc6804c081944ba4f0918bfc4d0027852bef8527eaaca48fae72c9227d3acccd9ee3da5b9772ab8b1955ebae01516a4a109aacde25f0ff60c36c41c365421a31d861c641f2f8b1c5847f2394e69f36e7bcd54f63c3619bfb417252a95f9a42769019fabf0ef709d9fd6f919e288cc6d63e4268acda691de133723836781db7856cb7cd0e42b16cf013da0c7d8516817db9131fa549f983a2e27268f6442619e6d010ce64f3e7d16ed624a74352d283f14cd6769e24db28f8355ff4e648d5cdc2656216587f9c5500ed90c3257612dfa18ad7980efee4d9844772e7e18dfb34bc0cd2af36f02119e32798c3fdf5d9c7f2aa3c60acc6dce6dfd652b32cc3cc5dedd054ebde92f28c6c88ea3ce04d04e0d8761dcba7a8ec4fe68b72e39a5bad7acb24901ded800c202a944f667ad5d0ebeb5bb7768f9732e0c020724950b25aea82aa01cb9c9ac8342797464035abfa7c1b256d82bacc37c021b40e253fae00042fb7671050d1e0ee8f2977b239416827e38f3e53bcff19524460729269d42f4af6ccf649b82ceab043e3d16b7676ea0cdcdd67f2906a2c66eeb053de006aa4c4e5e66ce63a9130625247c195531977e523dd6fb60202e9ac023472e0836fb27b3ffe8cf3c6930b8e134f6cb140d0aa5cde2b3f990d9127f46aa87273021aa2a3bb82e8beb49532d47a180de637f381ee7b39866d9f828f792a934602d409e86c652591a975af81fd187ab92af60fe2b4bfc61548aeb096bb39ad72f9c827d870295c67ac6a5bcfaa215fd6e923fe5e5f49eb3907c017b2d5bcc0727cb3b279c88b8a272de689c925e745ad7e274f62bca35a12cfd89bb27e9b83504fa2da447673ab6ed2929497782ba5689acb7bf9535f53429285a6a978e4e872219ede3ab7efdd8eff42119481b1d93745f9f5e9cce4b2f70e3e96ca0a1f3272eb652f8295102c437e5ff365a755a0006109cf3b2c1829d01c1c4cd53097d57235631e529edc4e9fa67fba2426ca5a1e4ba296540ec288b0193489d2b1bfd07278e2b4c8c1edd9e4cf7542d8284725bbf3ddb8b5dc4d3388e8d30ef8f5a3a23902664cc258e0d6c9082aa8ccb819563c62b9e54189c6b6352ca42e944c9bf628d04459e9a48acd7746f8d68a6cf316e7e71e3ae2977d1221fa38288cf7acc27238a538fdab9af289dd3f9f23e51c6db741dde3729911147041f2db042b10d7213802eaa953a0651ad854b149cc292b9ec9bc1ce4a855c980c143d31b8697267229a4907d50a832d4459c237d3a7a72f025bc9ad53283ccf48b897871da07e0725431196aa9be59dbf4e91bc3a68253fafb66d9c8c1f9604f02fdf9a82152ca72ce308951e351fc879ca97887380b06c1157a8de95c84be08e8edcd230ffbf109484dce69156bdf2c7aec30de9a153635df5640263b56a53030afb4de79eb7272e0af3f715cb4ca968b64e1bc98b0c458c12b0e7a48fe10906ea5c0dbb8976472d0a092026261df69ae976af63ba059e9d8ded596dc42f4d97d3767931571740081870c0091c9d1551608276bcbbd3cc724faf92b2f15b3cb295a87a8b3b0b972134a5788d6220b1fd431ec4ac8c8297da23b9aa7f1af1b8b088c2d9c6a6dcb723691723d9a5a8264d6f44fd9b0a3bc0a4156a7b22856735f2c825e98280d167268417ced426bb9a45ad9b110132828e08bf11f8bb4f82ac3034f94b1cc60a272206368320277c1dd3bb7076b060c92a2752e3c8925c86d01f8657cfbff6e0a72425b3fba1430605aaddfbe3e3a470c00eafeab8e43317e478636a1faf14e0572efe45d6023cbc12d113a0a08e9f3fb8d7dbc54eabbdf18a9399dc91fd9aad67295cf8af8f07de1a8563fd61731da830e6a463f77b80aaf4ee8a48685f46a1372be37c4df81691cd1bd46d30b5d2636ab59ee5051f2e087ebafe5c86546787b72700428987691533c92fc56ec96b36ab03b7bdca52ad201ef7d61da2c26dd087295616da77ec7921057d9f2f94b0a488ff7b541dc2c1bd5204ca24edbe194fe6621e0d31d85ed695ebc762d51a46c95e6f83c4de77273b90600084a77d517d7725d6c25233eaf9ea3eb27d7ad87998fbff49a879adc4641fd7d38e72754f1233e9069b7b13635751df91a51471a8ada2c220f1616151f7d13dc32d86be10acd72fbb386c04662472a5c896af4aa4115e75715efdabbc53dc1c2947836b0d45cba0200000764c726a72f8e1d245f332a1d022fffdada0c4cb2a016886e4b33b66cb9a53f72c3dba937704d6d250974552e41e23f23beb935cc1fdef0577f1f0205e809ff727b8819d2978ec13b7d6e8f8a3868140775f53366b113e84f10ed9dc898d8d6703be7c5556bada2a5f5d83b188ab1cd419f2513a0013b71f61dcf715f0b69ee721fb828a5c03205fd75c53dfc211090bbc294dd8f9e3ad952575559a4f91d371265fdc8b4bc8617f6454131bfd78768bae16d6933c01844a7d1b3811b0919a372cb931d3bbc7b13787c20143f5c9191a7132fd8895a35ca6b6dad62c86f735a56f6cc03409a3d7a834f84d8e7963583d74fbd017b30ef5f6adb8ee19ee600225fe845b945e1ae45877ad01fd3ca1dc67ec8ed306ef934d1dce1820766f95ac5725f0e187cdefe892cb38182ca33589a376dbc4aead5fc2a5e1ad6b7e751089772711e1c192e225194e7758ea4e4f82e6b2b570709ba9c2d3b7a3093cf27409072546c318ccd0caeb4999501fd5e74cdc404e8107103e9403b84560cfc7d8cf672da8919f4c7fef4a7424c47572909eea577b1e80eb46c80b8185f93b1e7ee6410d72ec7173212c46fb25b8a3be6899be73565e7d3d4fd97f7674c646b1f2ab44c15bad16dcfe92a4ea1da52c3e95cee85a8e1aa337665848925dc68f82a51721c645390ba5cec7a29a988bfd44fc44cc2f471eb7a163258d8e9bb1e7673de395219764e751ac5a3d608795ad923f0b1d3fa237fc48e2eca7e86f04fdad44b5f725d3b62a092f3c9aa262a48ba484682f78d91a84697fae9a35351cd43bb59db727dceb7220fc6e7804bfae0a13379281565e99a37093ff374e3fac4ba4c1c8a6300c3e3687e1eedada3f17665371a74dbf06c28ec26b7a69599aaf43297627461225c3ba95dd408f845bc5e55b9924d2a3f7d75bcf815e254ba3b6606e4246a61d61824806bd8f511d6c67f63f14cd91030cb7e850d5f94c6256b3e09547abd721f25eadde0ee65faea6b1b8096627cc27995e9be2405adec4ff4a3597644bf0f1ed9196570b112cc2aac5e2d9a0817297d5392c28318649434d0ac3b59ae7a296539f42e91a49a785cfb0a68e157c78fc06b08ae21ee211ca9dc46dfd4a8ad20450713ed166e5cd20de7ec48d2e2a016f09231555ac1ce1be65fde036b578f28a6f8db001b124766c1a48e223b2eb5af218c70610097aaf02d15a159c2c05e72255d98cf8de891d67817eb2ddf7dc41afccf4b9adad6b95a446354537ff8132dfde24514b02ca1b16164ede58974c6f6fa83a1159a51f80f70929bc32ee779727b3655345e624bc8520380d48e07e3ce72fcf74d0c8efbbdf6a68e57119ea072cb3d8882510568ff36f0f59b48fa47c654b600d2244786301d56ba9dc212ff2c3f72593a9fbb51bdd025a5cfa512ce8fe2f5aefa2928b8aed3fd9e1794da693dbd09fedd998472b66959efe876cf206223e7f12b70003868d4b420adcfada272d9992ca85ac79b0536d9c7b7bb87fcb7dc8bdfe64289101833963003006a964bf2af8351224cfe648138a4ae5a1c2637e40675e1a03f1c61fc0505f51c136e722c57214480c5e813d6c3b1f78a37309bf224dab80f474b4da40b89c380abd7724c72dbbf15f8e7c8fa94f60bd6afc428c3bfe353aaa1d74c148bd3417803cc7222c6bba1839b18cbe42c218bd212f952851398c8f7cf792afa0827e87951c272070e7b1fe930ee7a6ddee4856744dcf3940b12c7ce4e213d33d7b4011a7f677231b882b628c5ce964b0adc52767f6cd58ac29900ba82f63aceec2c1e05840e4267baada99c9c782c6c517607b3ffded9181e42d7f439a24b18ddd5692caad772a38d80a17f867ffcae3ec66ca9d1f6d28d40fbcdabde272c909f6c14d6986a72005df7f8b538c92c9aa35f0776423095a5d103d051899da283f2e7ca3b342f42127a7f6df51e0984d1fd9189ca7e8060dcac75c8cfe602dc86d80c9aa7d1563ca70cf2671020526aa937e89abb6c9fb2d4a7eda58ea33e29e1f3212ae60d6e7226184eae16c1c0d42cecc1a1ae05e011378a69d280d58d809ec184be6735b4724274cbdcf131f0f19b9da3dd176f4516cedf1cc106d91ad22db9b210d88e9a1df5225f58cb0daae774011def5bbd8da9f794e4ce75375a720e6ce356c7f5737248a860354b7debcabaef135d077ed32fe3154693739e6c8d942b8a05e9e23f3f26faff0518f824d7ab31483210869c53d224f61d273090f5a1b7cad3c3568f72eef699ae83b87233a501790083647d02ea41d1b8900f03eb8c71a88bcac91e72d8d4aa91c3204d8b1d1f76c18c9f9ae05c697db4a0fced9e8d7218c64abda605151af248371a198b5bc9d941fae59bc0f19afed8379015456b8088720cbb39644950b1dc2c9afe7c7a4364b27579b70b791339faa4669472e2e184a6afbba272b94d6dfb2e577593f3260c410256fdd5f1bf8b8943c1e8a646966894760fbf724ead2e0ceb675ac4fda46ee4cecb4d348df184f9937b5b53847e7b4d4cd7d872b48acc67427a968bea16c994ce58f323d90335352b0f022d9a4b888dc8e241724f6fd44048c694e01c4ba22071f0b8667e878c8bb3d5ea572de88ecf29062966ccd2ee0e218cb197d679228f651075b245b03b8d7e90d769671073bd6f30de72bc679fa6f075b5e7db8d58fcfd11f27daa6097042823bd0d6cc604dc6e33ad4ae1ad7597328a549aac680b156d158bd1c861b15897c1660c3885ed6beb81b41cc16a830dda357213d42e430f175ceddaaf64b30098da6e08736c61d975848272aaf90c404ef4f81c59996463749dac32d63651604a19f20309151e9759c08c0f8ed83a12bd71e2e44470fdcc2c600bf67abc877646555e1b64b392302c231a72f23cc9e9dd3efd71952dd2b5e5e7d73f61b816d1066917176cd48aa6763b6752d3dd8869c5be58ec410b7d5e87a8fce18cbf7ca686d3242f7f51b4ebe9df4c0701cb5c71411a5e0b775038826400662a2c27b306346bf16c669f867e1c0c8372618cb9b33cb8b0a90040fb8562ee260f1d454443d6279c7a1ef37739dd768772433afd786fc0434519c7c38d7fad72cde265052b53c6c104bee68015eabf3572afdf63c365fc4bfb661670590b21895f4b0bbc5592b0991dc5e9f10b470b86728273c30fb86e69b558d6bc04d898c830a232f1a84ac516b12a53550fdf1ce772923d520810340056216cc02ef4ffaac95ad09968d204fa931363559254a2dc198b53eba7c3946b3e261345307b1d006b7d36494ce702a1e9ec3800d4fc3d5272816631bda3f3d3f8187a1d44eb8bc17e3d84202a656fe69a191b9a6fe66e1a7220f67a7e0338b8ce263295d9f56b1356c189675b22bf4dd2e30982c4e00d155bda587b29b3baea938771666046ee2b4717bdc2054eaf55035b829466388bd4061558b416faca2c72e9ba5f2c0237d05f0d5564152ac492689610416ce49d007295e4d6891a5124d3ea9c73f041e4ce4685c8bec7c04901c385d6f12e6cac05726c26e317532a44a271dd5fe1111e0856ddf3ad478ce7cdd42e412d39cf2068727783dd0b2445113dff05ccc09fb161a1d4091b9def0272e119a60cf2bb32ae2f9151d86a60addfb0c505800a924af91362cb1e7a0c493f09a67b51aa33274572158b6bc06497ae2dbee007b38562c2a6938c510d4b9489a4006abb2984f49572dcfb9e17b39f9acc7daf40fbea99767d9a867023835424ea80baffc05ce0dd72adda7e1519b448a4dda5a91e20ddbaa6e7a165fc0ef18d14c8b6cd5523009b49ceb8c4d6e7acda29f0e357693f0495ba7d70f041053b766b71e96156d846f27293a74637d1c5d8066eeab9d18f2c491c0d9812d72912227a593d9cacbff160725615d44e38c62c834add7a543d9be80bf5463f956e17cc494bce68df1f248b3c1fa7a4862de0b24e99d2cb1a6bea2aca6bbf54247a07f136cee4e2e2dc042f1c9a1d88ed9d9dd814a30a0e091dc5675526a77e5575ae221d5719e6a2ee074c6e39bc46e8dc16af1e7183e4e7f293c2c5e8dd3905231cae8b2bef3763c92acb72ea4efebdf39c1cbde99221802759d11a2a60a956683bd8292dfbf92da1b1d82bb3736ba0df6df8b69d63cb87fafecc63e79370fef48e78672b1ae1139f9e7d72be770d8a9c4f889c20c288e6838659ab5f9cc10e8848fe511afab2e545ceb20389026bc3ba343345efa04fffa435a2b35e2e57c7b7e9ac9d9c44b21f22b84e722a5a2a09188a3f2a14db493a6baebab6a3d365f72774da5d679d4727c567d372765db52edb8954e08ff829bb9d41cabf0770aef382dcb0b2e7da859e9de8bb5ec26b30bc2c406b858c2b6498a068ee0fd0fe988331e8a630f28099cfbd5e9226583a3accb68b28cd9f8a82b15ff4f4393d147ead1003502050122957358221721e8d6fb6b8fb7bbfb2e697c07071d2b501490bd40bb025a1fc68cddc2cdeeb72720305408e7e2bde5bc1c33631b0586262c192e187f9e13bc9848eb5ed09d56f7b8b8a86d2db2b8b9cc172f13ed832f46798387c7f6dfda6d2028ff06ecbdd72671e8dc9082247173e279e880021e740a773f11b5ec6c9d9fb4f7db2504cf172613afdbc05a46817f04592c08c8cb77132b9c43c14b7da09d6e8890607d9167221a0591d72dfcab0cb13cce0611b3e921d9494881a913d05a917e681084188726a7a12d4a5cae96eb13799eeb1003b53cb47d60aff34069f8ece28dace2cac2d5ac56a2d337e0334ad1918a6bf94be195363124b14b886ac85b5ef19dd253f468c5dd5c374eade8f1671c5725f9083e08417e3ea1f6c9e4c698d810915abac725ef6699d17840529d5e2278348ea9575702809a4cd3b94397152bfef023bfa41ea0a7db4ce8e3ae9d72c80f29f0b4d01110a6f33ff182a940004b6934361c87245485e245182cfb2d7ef770c692efd7c353bdf1b9e1c84ecad2abcfb2425a358006e1b996b62f277a6d9d399075ab6cda24ffa2c7f97f4e58ce870466a1f3b3b03bce0dc24204105540311ba0be7eeecb6dd4f5c4edbdc709013b9238119da724cfccb8a562967d18031eefe5b096c646b318ab308f416161731839ab2c12b675f206f56ccd27fc92d8b3611f086db2e2817e73fb77d5deacd8faa7fe0804d7207d7df0243fa0a6c7193d281b051e9696bfd2e4bbf9333d00d1aee7544f98f701bf3ee6ea2ded9953a53fbe230d75dbb34e67cd20b6c98d8db6cec2f2239147291e3549643836a3ba21726c8f0e68632cfa97b95ad46b00f844156609136d172fbaed29c42af3284d8208a2bb7ae642c116901d0eb08c0920936fddbfb3da172486ea6f3de89d48df2336d198cc51022794d195f56589bb956b2c565d4068372533f12812d95b0630f8cff0b57fb9f72c5fad93521569885823640ae4876ba720ada5adb59e14ef64853581052a5e11d0f0396de4af8c134e1502b9c290e997285ec399ae7ac97103b90e78e0afd1e50d830cdb92c44eefdf0c70f4ddf005926890e41410725c55409d933eaecefd3bbe3a87c14009aabf04afa9dab9150d604782d21cba85ebef178dd6ffb55307e7d5aea75a165a5882539b9fe9a25475d727b91997ec64106d8dc46fb748a8a363ba4d10fee95e940e204405ac740d67932c73d341f43f7e3a8491ccd852dec4cc3ba30d12dd16a9e32951a3b869ebb0d569e4af0e05c091161548bd9abdffb46d0c5459ed760f9bd46dfac33d95c1ea972a1ff90fafb7a6cc4c5bec656c54c676105a9e8ba198860e62bd2cf31135c1a5c0c036cb3995034f10a5868467ea957868452470fff071ed57425142b53ba3f1acfeb0de3b4999a513d4d395229328adc20c0d30ebf34972d3620e9bca6d0234452df06d5f7a294cb507d5f6a0b13dc35e2205199e99818c03b0c8bd8f7ef4207bd4877166e37c88540e4418fcf44f712ce71e1547d01bf8ad95da691a638aa728ec29230c208b04888ebc170da3fbe0475e392a5405e205f8901634b6028ff589ae54986a704b7ae8ea9dddb42a51a767f52db65984cedceacdf820c0e1d660275c6a66aaa1a2b5ee2e2eedbf0f1d779aade0caf02c1d9d6cd6cb013cc1e3272e131a54eea2766b417344fe8f4e6b229807372681b871d99b913c48ab8787d723ca268bb99a6e430cd74e97a6be2ef26edcfc101b61c8df48af820b2e23b9e7273521ef888bae923bdb3dafce8c4d13ab03bcaef733f8cd24be12d1d90ec2d39accd63669d7a966c56473def275e47bfae326ca71adfea28f110c81535347972a39fec6d2c54ec64f5069614a0adedc79ca8b122299cbbdbf0dcb98915d4d872342a927504720ef863ca6c5d63e2d13e8b9ffd6ea141d1b7daded985466e6711aaeb9d2da0cd74d95bd5a49968705319d0dceda36f37bc01429a7a9a22cfbc72a81187c6977d02358adc64b2189d7181fb896ea6e9b2f713a94b4ee2d41a14327f7da15313ea4e5364f8f9b3a52566b8ed889651fcc4e0f9df520de2c7b21a02148d849cd1ecc7c21e6e30f9a1fcdd39fb4662ef35b25675579d7684ccf9fa72cba9c84e3a55a9a345eae1e03c5a7e343b1a5e40bbfe71a5f1341ac7c992b4728e4bf8aa2fbf80251d98749a31f237f381a43b16adfd07aef22c61baec77b0727e284b14e2405c12eb877127ae8c270e76a1d31b29838a99cb5bffee81ccc7729642ff54d36339375ca5d1d585297adc9a69764542177123999baa39b8e5cc7258852349708002ac80cdb23d03104931530d57120a4281c1351793fd9eb01333431931b15135b9c7d8311d472468bb9e2104a3d720a7a13dd8f7cd0ac29f160ea6960057f49a4a4b150812c42a010d6971f07bc0ea7561f569054e8faf9a885bb8032955fe4844a082d481c92d1800ec1650eacddd74052a8b214b611206b27237eb282bd1471d17f58fb2f0e68a13009b910974c8a903bc8f6fcc7060138c720fd4f054e942460487fbdd7cc55b59b7ecd8bdd2ffd8b3fe86ec2412fa114856112b41ae052cd59a90d65ae180b03b1ab1a9b5a11ec6d66c80b8ec3857654b727ea0b408612a5fb129249e5d9222369b119eeb23dbd24788ebcd28c5e331fe72dffb127a1c4e5f72584cf9f9e472e56ea5cf7f7d28cbb3e833fb82310cb1111be190b041f03a30292a1476214c3089ee1a1ace9306364988977b31ae06b1d77256653b3c510f849470614611e7efe8a504acc4125378c87e96a0f88238e6f77297915bfd13e956f8da8db645ca52421f6abdb8ee962bd94e2110a7949aeda4728a690b8653f806264c41a375f3a951e572e19b05ef2ca717a9be202b54c3a172dcdd7fc83803ce6143d0869afc031c9bdfb448eea6608bb00cc1d7483e9e93325027e1dc4ef23bba8a92cdb67fc972648eedf38188b15ff15b5d9f6415c485721bece53855844985eddb8e8435f3f559552093fadd8c070c636323dfb8e1e4612b60bcf9256a4776a8a37a9b27b5cb3e0fc8e89b65acd8ae60f1a8276ce609519f3fef7bdf8f2a2486410f5c3c79191e5dae62f02e1772f433d1ccc68e165b72528d591b004043e9d422f1fb16e9866c046ee3aacbfce042c61715d7c2f360727c6acc47716e26b3b90d2643f2ee1acf5071abccad2a3466d4524e353e649372c869fb029a40f08108a204b4307c301ef288c430c9869278891fae1490ea3d3838d0ee05c8d6cfc7551d932dc938c0cced4305bf0e3dab1ae7e714615e8416729cd122d1a6495183e897e8b5b481a868a699c92a40afed28356101d22fa28205a186773be7d67624f5f152bbfd42787ecdd4e7fba21fc90c57d7aaa667bf7142b7c22d60ba84bbfe11e31d85f218b2250f72b7037f8fed2adfab3d3324b82f116b1ef8d29d0aabb8424177142e9ff8655ab7f3145299d5b9153ec4e4783333636641d089f95f55e1f423c45899663c86f5e2b592c85b6c326d0978975018d3619fa8d290386e3ef18b4f2e0f95d60ca220c2efd5997f9e360307bf9491be07587510eb27f83072bcd732dc81c8286a21ff5215d1ebdc4872e8aa3cb1054675722965545d53354cde9f12fd04f0c3767e9a5178bf0e7ded6d7a2dfe7b1479ce2873c16f3ccc8ab97d82b4e20e4e38a611507090f82a86ae62250ab02e213b4e0e8a641c0c24dd029597052c32d06bf0d970d47ead9cdccc1bea784609aa5e7140271e2210ca6c6a898932867a25b7e45545d572305132b43619218fb9177244723d171b76d932cac9ceb6d2c61bb8be1dfe81f2199afdc142a934900b535a7e726d5b6c0c84a1f4e775666e4bf7dc9f7f8363ee2adc950dd518d595c9e5c535721ea0562f0bc394fd9c1c9c8181923cb3e85ed1b58f98a6647dc5a6d9a20f187261bdbe8270c7631aac54d2637c41f905c3694218a036548bdba1d751656f0472426db4d805bd78bcc4b4fe66760a2366530baa6d11b7120737d4abd3bcfdf27214bbdae8397ecf8ca2b9cd43da2d2dd48b571b85c4b04043b524f59237803f72e378b5782c03d13de469924661b7a81af264d949eb766c4023c07c07f981f7724741070ce2875e2213241417a6211069b15125e7f60465802a7a223a8e2fbf728f019808e92bbed309b28b704f32bcea0bf22b4a4b6fffb133016d71a17e3b1f6fb8e8aee1d78b03c21ce668284373809c38382ad709a3224561b06d49889172cbfd13033baae078dcce21592ac586ba931b0974235f9a9d774486ece317b748771408c296d91da9a2844f6c977c90caeb32d3af5600a337fe2d8ab1dfea2c72465829799e10ad3e7bcfced34ac03e7a2b4c0520622618de786bbb422e2696727b4d9ed9f5af7f6ce93bd7fd603beeb688b276d64ad87c3520cb390fc369b37283e7f1622b8b8fc026ddbfbd59bf72eef104a251c8c1aaa3f204d445ff74e972caf766df06f321c5e56de5b130989c65ba012ee4a566a5578a7f26ea7e5c623d649eb56032d10130254ffd621d0784cddaeb7f78d56ba7acb41393c0da6fe54a3d0191f67086955e2e4a8f4d5ebc903f267ad3ef0c05d860657a0d47834afb72285b7845080f28bb70e1f8634904ad16afb586f3f7230b9468f42520bbda26721bcdf10535814d937b15fe7ce6ede20a9e3f3afeced6d155277b4fab26bbd70d06a7038b994a1a4f178ed4287d0cd326f6009c85e207ea84654abc8df955a77268cb06141d07f4de2e69af06a3781280776e3802982941a9cd83e62c1651a972fd70206cf6e698b9af0754d20dedb2dc4690cddd57ff3c11674bc6b69e681372d78a6155f3f3205c9cda35cb75cac88caef1a20e72458417823946003b1d9c0bdcaa9598eb559dee8450efc0b177891b2ea290335e0073acfc89905ef40dcf2a64150eb01a2c7878ae0f1982d1ada5b6f466cad536748cb30f0fb2c3ab60c41e90a3785708e3f110ee55fb2a9ccfab5537ca922f45e7ae1a47e8c4a1b4d608724ea97158e6ff5d9f2c39af7b8b3991cb65070a06c532f6bd3aecc4772e2a57728a52b95c37d5166df192dce8035441873168ebeee137c8c94f3fdb3e91b01d0abda42a8c248c12969f9d9a2f2ec5a146eba950a3fc72f858ad071124cd5a6c290bbb8721abc7baf05f424f7a5a363188a1f000ee63137dbba2add4a7525ca0722f6834578cbf35d1e1eca7d65984722bec10d2f016747135d91562bac08715725d7f86183f5be250d57181ed7f2d9d16bf5c6f1b65f3a224ea3706e34253fc7271ef24faca8089fd4e2cfa5fdd6a114c3ede96013d303759532d9b1342c213674f0904e7437e3212cccee3db98d1a718a6d10d2f02f30bf21aacdf95d5898941244ea6acae0fe3347336bad06fcd76a2d7690b393d1e96b2ad85b3f236495126bddeb907061b3cf19373c21847825f93dea4c0e98df5f5491030378ca7088f7202f20f579229c07028bbc3be1f79bd7efe6b5aa6cd87ddf6a70a203f581e301a03882039931229dd4dd37bfa5d124a4eb58ed480f7dcfc74d47bed685cc28b2486ec9d8bc9a2aed2e2a103a78107e8153ee57e392d218b8fc82eb6732c3cb2726ecbd57ac7122c6c1a97110cd75505647c667374e3b1ad21cc52d5f3072d750d545bb9cbf8ec2c5ce62920035d42e3c5cef32ecc818049485f2e713a4aee5072cbc906f990e2facde167f50d5cbfbdf59869c588b965b368a6a2a76f9d72ab6a679f35fb672a33d0c6f7253f8903be8fd0601d5334473615004f6ec1fc0ddc05206435e55cb0b129ae8ec204d603bde755d5e5b078c959aeb66d3cb6aa385872e3c8512ff860b89cf577a7c58124df9ef78e5e0beb9cf823eade1876acebac4f7528989fec084670b15807e776be6938ce8e240f541055b15c15b61d5bd5c61e3a8aa2c611f7170c4e446c7d4eb91a0a96d6069f491b2cc2c6fd9c256a83e817f34781b8cdc9d9470f24dc86c041b8df950d8a3ed6c4af9be7d55d973b4a614c764d10196b3af6f9601174a52a1f500e8df8a7fb406d559c2ade00fc17eeda723530471decf3770d61e801cc41594eab6fc747f8108b336e39835dc4b00b7e675dd6fa5c20194a7559fb51a6a101028825cf2a68d6b1b973d0067c7dfdcd3a1175a4f980201865ea4533fc77e7f74a2f963ba831d7c39ed1cc4c92bacf0b1972478c2f678d58426f409d09d5f35969636cafecdb4ac2a65fac98a2e972c32d72466c578e5c802e12fdd14974b7f4cd485e973c48052cf7bd90545af94205a57252a27e79a53aa78817d7597a865365df03b2ef506c76faf3bca18ca6fdfd877212dc40a41305599bdd8c1dd5b9d54bba7908fb6432c568b71f22fb657a7c85505d2349e7869be56f2091b43541723a47916d5cfc0cf3a5b2d29c23e140275e303e5d730e4829aa12593d5aaf262a45daf886a81497d373e2a854bacfa90d92729cf1a66ea6e90c3dedc0fabdb7c4073e6106e397c4aefc7c870e2cc682ae55464de3811c05e23faca547e712be548d6abfc93580f759f4d0415f7cc71fb35f48c564f2096e3b9cf3097669cb3bfe4eef33afa609609452fa28c9823573459f7273f111e3dec5c7a06955015663dcdea74e73dea86b0e9d251f20c74ff5f38423a77007dffd82879c062affc07c348674dbdfe40104d54d3c783c52bb8176204e9dcc68c4d17a3b724285381d32c849091f8a74b3677650ecee266832fbd53472cd89603bddee2c8e452d7efe34c2ac6c7bc6a50bfa5b3143d2c7b3e469cb0c7269a2bcc4f45c5e3c91de7ff0ccd2e4d55362d49ced491113f8601b3fd27ff356a693e10a9e59b50c541b09ed6daab78fb5783b9e8f43a14aa350546e0f9f5028daf091da33efe786f04692fd5d12477c1b7b4c274acab74362ee584117adc8725d0dcf78c37a996de601ad8fbc8151132ed648847e76ff37b901dbf3b304fc4f2bfb9204444de60fcff912cf5cca42df0cab1cfc2fc480a809bc1a84d64ffb289e7d908e6a3e2119e75bc71369ce15dd93195bba0e264a9979483ee40375e0039af84e0636b03eac5debbaea2587f6e9ae9ee476d8e49130a589ae80c1aa707268c5d03f2b9b4a99bfa9c88c71c3252e6a777e1812bff070a01c22144c009a72a4ddf86c7ac87e8aa15b289a33cfd06d7590f6a6095746f2a4c9a9ca6b557472cd973de7535b2b8975dbec2300c73adaed70fdffd351e9edb8d7fd26cab3863ee3b2db9456e17c833d8d8f93a0c09bfc35712ef11dd4455f3fd90efb6964d65f4c29f3d15ec4616d76c9fe7c69abc3c15cd2923a521ec2a23936b4206bd756635f7ac52265c3c42628ad23e7a84c3e219a47ce9a96496383a59997dfe07c1b3061258af70d9f780ae530ecac958bf9260783e1161648ec2dc1d18bcaee89e12264dc0b307495c73808d978ec94d27a6a5cf9ee58ccf8a2000636c411939f20725d3ac0c60090f7419b8036e8e0a59a1207fa6d0d33448ee7f2417e6062d3b372897363ac7cadb12501f8b5b6a63f036467073a3ba9492c9a8feb43e24b823d72ff703a0859eddf63466073d160c03c96910d6547365ff026b0523293e89d436feada211498ad1e623bf62afa8593c88d3dc7df52d3d799ec89d4de4169c687729e8c7bd09255c4bcc2e8e33e3ce91382e8cfdb546f4f034d3e48857183049b7212b9483db370836c3c8e8e875940ecaf2f60e04808c68130ae29243085c60972e9ac3d09c28fe2df6b3b993b20b3a1541d23f807c10370c8de25041f44863d67eb144f7ba607361febf1e1d123cfdf0d21e854f32ef6b0d24779cd47d1e807724bcb4e2cdf8eb82852f0ccb694315c0a9a8c0143bee11d4ec770b774e22563477359e2f87e9bd26d22fa9d129dd834092b4add08beea6101f591e8b2f3738821362e5c02cf6a431da24b200e7e622efab5f8b10a63e3921ae76c3d317fe3ff02a1e736ad1d06602f299ea8f72bc99089acd664e090ddddd8d2964fd7861fee64812a7230e264bc5aa6be77c276000c11487beccbf7255708543420041329817215c315e15227f3c0494aa2f30dec4495e4c7860c977c473268ed6dc88c101e12d6b8310aa3fec8be058a1a0cc892c54689a52d80a294a47f0e86e46329ca2c726c4db65a9483e38fafed04a0b5f615c2041ecd6041c3730a18ccd1f878128155ffbdb39bfc0c8a82358d491e9c73ace8e773101022c8524eda60fb790edd802c2f6852599ebd3a76c1730101b012fb1508b51aff55815bdbdec5ab902852307245abc22a93bd87c9042496f4a7fae28661dc36ef56aef7ecd5ce55dcafec1d72fef4bd2b2ea35c06a38ca7f5bc735dcd092bc209ce13d2f6a6edb30ffca955704664b4f5759b21d01be807b7593ed615a500d19ea3f342095c438ee2cfd49f6810686b0c1a29d9ff08268dadf0a38d41c27c92f11d961c6b467e2b86ee31fe72db621bde5186522d2231b80dcbce9a62b8efa80bda1d87a7088975e6ec5223525efb0f6fda3d45aed9da420f0a95cd17e7a9b3a87b35566f98791295f20fff726346a408a19451eddf452081ba2277bdb9128360fb72ffde22a60ead24835372f918532fdd8a0fdcff15e455556b769a988c51ea044a2e22ca7ff2a77cf41166201fa98d37bf448c05a7e046734e255e841fed700b12e073ac57c3d3aaa18e72c48122279d972a9c00536613858b60e3a5c194a600125d7d1eabb2286b4d59490e61dd94ded79383b68d4ec0b41c3951f86613638352034fc30a535b0d4dc66fef3f79020828faae3f489892716d5ad69f235c394a44de46be561d39e70e345183d31e22093e1430d9110af3163f3e9483edad167d7c0d94602f277ab33eaa72490540172dcaea180f01f5bf3b2043ff4a5abdd37dc0678413cba058522c400cc1c3ee0e04d4c18ad6e03c719969baa6b29f0e248fedb411af33f6cac44d6d725eef75c630330b20fc98c046e549d60e71bbb5d95381b40d2512f843b11cf512cae17a0fee2a705608555dfa4ec3d4ca8ab6d72d3ae205df21e0790a24b833729241c574d1427ed4b8122c9dda93174d4f26c7c85b51cbcf777d2abbc4aeb463a8762a2a0cfee742f81b4c57bf786fe8d52338f4900ac6450f83486b6fb05c1ca576b121d9069e69486ad158fd6bfce2d9c024ffc846a0dbf033d7ce37075172f52722f0031bcac82d15d9832e2cba539305d0e4121aed045b8393523e9cc07260ab4155486eb23447da9eda02608bbed2dac4327d412ddd1d5eae9a448a1944901022a1f89f11f88b905803b1eee5008237bdabf63df37928bd5d8de87b5e721882c18d369655fe0307eedcac00493ba6a08ff2971d34a90162b52b1bb0594e6e19963053b6df7ab44033db6539f489eacdbd737047ac10d78727f64daba93972da22f47791762e4f2972899ca608f89715e2551bd97e1d4ae91b1c69e56972306c0427c1b185a441a76e86c06f63769c8f7577c28b0334b8fe60fedf9a7645df1cce2886cfbefa4fe21446d4c9e0f0e9926cb4446f398ef2c429647ab55b72786a565051de6dbebcefdba1706a829f9f4a98c792eb34a07a8ab86d1bf1ab2e3a06409ad7f37e6f4e26002bd0f786eff4212afeb46a6175e612489d3258e172c62b1a20fd83f2de54234b61eab71b17949aa7f5d54fa1ba668796025601d0725f301d649522ea8f4755870a98ab03449cf85e502ef9ee04b0fd253a34a4496accace6f63001d922ae240a143ce371d97344c3179268cf54497c2b7d3284244c49e2d570820cd27da1d24c7dbc0ba30b90e0e6d63c7ee955b8a2f6e9d5cbcf7255d1cd6af6154e631f281126cfde1f151b22f91e97246cb29bc24ed1a8ac0072fc5ecb8d6098b4dbe8d64bd177d64bb9a04a707e8b2ed0428a6243198718cb7259040edcd5e8bb98b5314a743772e545e87f8f65d1d20edb951d630c49e5bf5e69f8de53fb6c429aa3ed327b025162e0cfde2bc98f3bc127b4f1ee100390944780705ccf518f3fa25ec509a84ccd230ceee8b8951c15c88f03278b56ef3d856a664e5e4c68b4cb3158c662de9172ac947d4dff06330a39df306e5712c6cffc72a27446b2af6c555979563e27023a15f6139f5b2e1d45f5b0f9b8a447d7b360727914abd8727162e49ab5cd5565790f6568458c1035d0bb171be2baeca75d90725facc03d7bbcc0a486713eb268f46bab97e494dfe4e50aa89c934ca614fc3a3644e411b2007101ac349bf10bd66958da38f5a876294011f7d5356bf4a5c9af72b732203ae8383628794515a8ead283872679f8e9dbcc22c666f9a6a499814072b313dcc56c26c29cc2e4d3d45fe351ffd8ad1095057b024f56f4149dc98e2a2c7baf2a710542a557e298908c4075384cec20aa390b6c5aa710b034585404bb7285abb3e78a87d3a6edd7b6a79f37023c1edb196b3f5c54f784e54ec8c13aef1050daeceb48cc8121f926b0cee170fe634a37d5186ffb1d5fe54575dd090610638ac01dba976e7db01ddf5fe7bb0d1b3f3dfcf8dd3c4825af123f2a9e3a8b6d72bf04b329c31cc3f3d73562e691339f79d4be0fcb5026fe42cb2a8a1cad1fcb34a09d8bc4ededf4573637ed1f6c5168ce379035b86da09d248b1623b0eeba6d24de9646c18b166ca418b7983d08cefff01b7579e682a73e64ede38db248e08a729f7c51a7d720447f8d6c90c06a1cf6a6b08e49a38bbf50ecce8a38821829de72f004e4392278bbc5190b246dc481192bf235d6904767931f4fbece7782fadf72251ac43d5365dfd71de06c7e833dfd145a0b99cdabb8f409171e730d3150c372cdd49d8a1f400e609c5034a1f04735e6b77bb01787a010fc35c4bbc8c9d3b87245d36b01f8ef48a7a6d48d0a23567b42d970030cee55ebb17a2360d7a6f5966041365fbf315cbbac3970c032d1f596f85cd0faa2c15153e29a0f9c8d628cbb600db18697f17eb5a80f08f7d1636a1e06c409cf6b73869d8fbe35ed42d4757d727b21053cca4c995045a731dd87a44965911014fcf63a1592b8af1ae04112d572a3a7b612380acae201469d552ffad25f82eacd995d6d1b925dc7a5d4bc364e7292b990e74fb19f4385118c5d0b93b0dd224f9f35d403c398af572ec67d58b6724d478236b184635b3adbeada71a5d6492fe979d765732377387e3e516f12cb728d49331314a3c3ff5f8338a8e7dca84560b998296ffeb5e66a254645be75dc7212f4651eb9ccc3a6f9cad1eb8623d5728a819977a12029d31de93d55764be40b564bfa5fad54c4ea81006aefea0de57b176c7d76b4a054ea042f8cbca2a36d72df9d281f1d8a18842f73c65e19f97d229d9205bff4178ee7deb90cc3ae7909067226fbcadfd8d37537b29115ee3136bc291fc763ded53bb7150918424d8ba37256f16d85599a36092b0957929a84a8fa819d6f9d78d77cd6f01d5f2554d3fd720d3fc7065194d4e5db13825a350f88699e2b505f5e876e7402b0715f3bf80936ca288ea6b34be050cfee15c6eb4d1f19c982ac82450cf83a665783f4521fe7724cddc4cefb3a51fbbc48e6ccbf8d258c827501baf751df9145c4670c7016111e0dee59cd51da09d0b6d05f4dc76f4410dde95aec34e5d08d5158534b81b2473f694a3ebe9daf77c622999fe5f0661118b3be5eccd8b9f580efb641d035a0df72e7a1093f576130453ea05407a275f7b0a3c114c21a4b8648c8344afc8cc45340ad1e2732b62c113495b450676de531da18c4143bce5c34ae1629611f6e42e82b02637edf995146be8f655d2bb0da0d15e62fb02bdb630e48156bffff34d26a4d6697bc617f01b4b5d1259cfa50599bed487b053bb74bd3797cb2b7f5de4b6272c307839a14e7f90dce43e69cf54c11448bb711baf9a64f983560cd32456e287215ca46b612f6410b000004a11c8a4692cec410f5d785527fcf97774bf961ca1793808ea9090b8f289dd176e6964c52522b18525abc36d0093a7bc1d22b1f1d108de1c1021d9b104b5171a84accd8533922a60889b9b47c740c99f0f2d8860b55808b9d4801ddebba1e2469d541214bbdf6745845b05a6329047be81e9744c9721586fe7be5c66d6eadee27b101f8175c5f5814c39ff3c68ad1b019a7d230ce720440e3c8f0a6318baca8922b616685c89edaffca4613afea04039c8153531f028dd5de0d8ab2dfabfd34934eb0fc7a4eda88c7a48478be1afc43373804993d193a1e15a1f5a8f9130e57f260c5c486d802139dd1a892dec50dfd5e6e00304f72d87256a3fcbf83b732492c69005858375c8a10baf12aacebf34c95f9ba572272a0dcff31cbde5ee8bbc097ec21d291bc55a3d6e2b5f4bdb4ff1a38879c1375387d52f4858956cf5ebf347760ae16cfe59bd0229386644311e9774fa1f0940072168752ec62235a376f149d6a991ea9d9c070c1d18b38e0b538736345ceda3c6ade39aebc3ee31ca162d3478de77e5fa8a4e6eafb049750bcf64d64b3a54e9f4210b3f5e27b21e2ba59f2568ae02301e202c04981fb566c61b8636b600991c17259faca459878d35229e20ec9e005647a635a31e2c008a86b81fcb451f3f8ce1d64e927a20e0ff5e976e7f32c1a71f3b96556eb6b0b3337a4473cdad8b7c9f95d9a52d7dabb09bb5b0c85077dfed458237a40ca19ab1810f3edaf8600c9329372d62955f159517aa0114b6f08c872ed9a4a37847dd9476ce5574bf5751c09fc0da0a6a02af41e0266078bd2656616b622a3540c0b959cf3f9ff1f99159a473472bc31ab8787f5a5786c3ba75f3f196777f27a88106b275afe74b2646b61078472f41096e770bd00104fb372ade46f878902e41d263e566e45c3bc3aaa46b5d77211b495807b08a4c1ccbfc610f964b6ac5e048715a26aee717653da50971519547cf139cda10218f9367e37094ea6ddcd436944aac36e7386da158a648ad663718a33e82076cc081f77142f4f4f35d4d068643b806248ce871d23aa3e8b1e21265b2fc606ce9ec7d499d4d6eab7eb8919ab26b2c959871fcc8cdbe41d0f43b0726f4db45d44dfa1178e6c4556531141e04e21a01f6f0530a9114382d6df7d236170472865ae61d988ff90e0a0fb9a0e02fdf8238ffe3ffd64ca10874092be2c721aae75b04976dc3c1533df95c918617d9b2113d2e38df0f0b29e9c5a9deccd729d952ccc74467991c01531eb6fba486c021a13fa87d03d04fd07ef8a43d0e439c6e2d3b9f7ab8679df1d2d279aea0d793b7e39224bad326b04d5d3b2dd23a52111e71c5ded5c539aefb537ffba5e6c4ab21d17185ee3829052b4ffdb668e463c71787bd121209a02fcc5df2a55a6ad80d230313bbe0b185886030b05ad0034532ad8ad5a844bd51432b01a8e0d9aedebefe45c2812dd08e24841b6bf65853772e5537e61f89110f2010746f5ec6292646b0991ac95c0afc1bb4245f413b545727f3d87fc766599c23ee236a84410307cbda65681647735e9b36ed83d24b39872880a29a5dcb798da3fe0df59af75ab5bbfdbff95e560d56d0bddc5502970a7208e3c466afbeaebd6a2b6a3b1ea6a9ee477ea873c1b02b6e5a2ed4c9da46ec869eb70521461a2d0a556d154e87d782d49c974559b440c62f4b09429d51c3f651b0265541d5a1fc876f884eb7601b4732e8dd6d090a3e2b82e98e29e8498cd7129d00f941905a5de314488479b6180e45886e7ff1cd8143dee4a8fd0d59e16bd52d26b072b3d47f11af1ee56b9090ff631b6c582e6ab851b4c56adbf3102354f471740a9141f54af1e1cbdc4534f5e2133441a5ae4ce6d0bd2697b17271c1cb950ffb013dc1652b607503d191531bb076572c647d32d01bf6b5ad8c6a6f32ec86e7d00f35c9811f99049be8f08a4dc239ebf893ecfe91239d3443b28491b223615f04bb6668b4a18ccc076d48d81fc3280a5ac19f7c6fb4cf6c262fead124e607270ea666a5932a4796f7411103600720bc9d2f10ef3afb647b9ac8dd840f40d1fdc88a3321e22785b7555f764da8e0a80c924737becd4c9ea2579b3368e89f47229604965bdd9c398d6c58c237e792c825b6a7833bb943fee4a3fa64c3e6f4172af0be22303aae658d95b52ec7173716a6e491f30a28006df4ab0ee46f730d972f4a3daa1aa77a0d8ef0e4efa8d8fb514f0fae2bc0e9061d84bdba64f0f165772a23d61930bac485cb46b0c6110480060962ffa36ee8f13665e55590d613ef37261592917a93aa9f591e9d8dd65c2ce5b35591e676370df23afaece9dd002d6088763001f0b2adccdc4bfde3c9de5717016dd55d37132744e5c52a9abb36e7a396747644c5567b430e7d7ea3fb477d431b21d80565078103a77d279053237096194d3aa0197595fc8f675a3321d3fb61ea85ae0dba2a858da39b0ac9417cbd0728652cad9259adee844340a4bc1b5d3d56dd2b15250f8ef084cd1117b158a3f4568ec90a7a249a9e582046002f9f5901879e92c3703cf3b06951f26fd3b744872c332d4806b53d0dc44e93a438b2f0246c218a5aabd8f05071a724e8a7720365d7a2266e335a8c0016464545d6dca9b832ec6c9c0aaf53a50a1858af3f3407072053fdcb45b7e470956fb83a8615bb0ce0a50d55c9561b2ebc30d74de84f36f7219a8084b95972fa70a0327fa61847ac0dff0b91f33e81adad899fa450c4dd3395b52f906c0b38a92dcec8100324389706e9f7c9f1efab4a49c5cb48c051505122be680bfad979e264cd0b8ccefb27226ac37638bbb1f5c3b660fbd3d85625c536863c5a1f0a1efcdfc169d7b1e29d93f9f67b25fa5ff3ad6bd76c84aa27a39726c7b8dc9ea169910534322ce224077130d5fa8f0178ca822e2870125ffd56e72aaf75e9eedecccdafdbf12eca39bd9a6a17b1dbb8234b5aff420d8469e8d7b720f89e9de94b5e52a6a28881b31e524017ce3f50c540e7b1855191f8dc195ba36bb279062f04618f0415b444840ffc37cff29a5162b546b45c6aee6f18c30086705fe6db1d584f2862bf6ed04f84a2886e281d6c73fc9413443352d114f638b7260956dc8aff2f2c1e19798459146f0d8122e5345b1245493bf814e75db0f6047443963fdc1e16ee6d3077bdd2796e5436f25efe2827aa9e70731b728a991bb4161dceca937c2c9ccc5a9517c398cf3fb3be9e1ba3cac1646a5c6b6e02ee16b67dfb22cbe53452e16aeeca8e1e760941ff8c4ba6e2b83a765a86e7f6c3723dc72f810e9ba80138a6ba142c2776fd587390f65eb5c9149d0de0649924aafd626722be9f6934420876989dd7babebda09693203358d317cc37f1d565b9b80f59372043186a94a0eefa86be27a502c85e20f0e8fe3b754972d4ee1ab3b75e803f73fea49c405e63c86aa2b30e3f1401e47201a5ec6a808f6f182ac4bd687c65d18671cafae16f2773c38916fae7d81274f090509a4a005d1d5e4a11f679b2e9e01728281a106a434c0b7660c57b3d040d528ba6a55e58dd158b8d1323cfd56092d721bdef47a769e174d28bcf305ce4437faf6137dd553e398eb24e5422cfc2da62f32d8d500e4774b943f4178aaac06a6c9dda3ea903d6990d2cee018f7436b67724e8bccc9003fa39dfd3b1ee570703c9f86af4849bd6ba088d2aefe61d292e5720e46c8d1ccfe7ea242b84b03d0f2f9d29d169a3efee2ad39be6fb930fe890a72f877879c4a3ffacb518801a6dc405febf5d0d54a0bdb785727ada94b4e28f8472a99ff9dd53a3fe7cfa48dd16700b7a130b8bba401b2e667c59f1dba18dec272f51e5aed4e8577445d15101944cf3237423520b92a3206b8351ae7ba5d5196445e244d169c93c847eed3c756ca6d6e0c39728e0a14b4d115f9bbccc0a28cf921e02b3a98c1d58ee4f4febbe3e6a43d00431168d11f7f890e3904c7283a81c472f2cd60146d502d4855195d9f6ffe476194027c112d10aad8608695a5be2f5540cb6876d628d605f205551cc0f303b03f43cf3905a2bb209e70761c385ed74549aaee983d5875507e1e01017efbfad9294dd787e1489440eb8e4ad79753efa8720030220421d18073404f9df54459ecbd629da7461b0b410799a30f9c22d7a458a5f690b5724fa331e6943e225d19a92df7ae5c92624cfd2ce44f0cb8920202721f0ee9db3023447be9d01fa194d5fb0d19782d44bfe8a7551e8694679c249a2d9cebffa263e031ef7d4b1f4a4be0e38c64cd062235cf2efc55b6f4a85e54fb727bf1938d8af4a485b15d87011fad1f17a12189e6e04958097588df0e19ce3772a17506dab1bc2529f97506b0b80916b363a086b131178260d706c3203879f072d95fc9e9d99dec48182cc5bbb543659da346ad8f9d3b2c7639b7b19399be7572622703f89cccb6a021f9c579ac5b3f092643fd9f39e0ee25d56c1dc8976d4b72ef74733b203d2e05db0667093cb67ab26fda7cd7751a8d4d0ccec23044027b3d732112f7d0cb7ed4ee7d985775503129490f593bde6190ce038e55afd56db3720b6bea3c634cb12ef01e6465e8c4aa9c8e7d5308cfb0c58fcfb6f6c38d379d7220c75b06578cc42ac8562930550c10e3a2653d70c3b958dab0ea4ec668d6ba4abbd0acb17a5b6b2de01a0216c97a12680b8d9dab3a23fbf8b309ade8cc1c51192d48f7c339f2da1bacefb7cf1e73ae61690a6a651307f62ee8bf91e3be15ae19f4bc9446912a9d816ec5f9b061bfabb0ffe6a104bced29eaf219a9d391f51c7299263dcd440782a1c589270ff4cf0d06aba174ff2532f3b423e9d786a4eaa359bef7e0fdc3c633ad6f317ac565dab5858b37673cebc80000fff546e549ae47725b9f4efb66ede877904bcf430f85469efef34fe419a13b78a2b0f56d5dd5e4198abd83f9cf7b26b4522c4e318e21066c06019c5765939a12d8debf1b59f88f47e2d325b3b29b6b64c31319f48b67facaf40a9b7eff59370ddf7cb9d85696a52c7c271757886dd0598fda736a3b3822f855aa9bb98a22999bab1cf1644215782200a5497d3bc20d19059b9f7ad16a75a66ebaf338ad9a340ab2f3a0d909c314724624b3d1d23bf8a2faa2c194f8268ccb1a7f068b89bdcfe36cf5c48948d29f1bb99569d9d985e09d76d26fbe5a22889daf675e5cbae2dbeb519a8d4fd4087c72771d2f67fbb4791d68a488bef03378522fba73bdc6bfaa3233b94344e72793373832fa6aafd8e0da314e32bdb1253c9b605a5f2ca4b7b5b2979c0a728a1fb3729c8db8bacc0f36f64339b4b96ffa4e04a1f52c88dc4bfb104816cd0e6e6a4b0ebd6947fd4f1fddbd665679f145bfd1a6a17e18efff1b6dd469ba04d862c6d3729903fdf268acc0835b25b2814cf685ec859d51cb2883bb90dcff147c63e58e726c3def48311c70a80f486761130a0c9eeaa7c0ad99a27e2693c9068fdfb1335e7b54df3e9a97e06d34ca8248c4ed4efe9985ebdeade4d7a37fc29fb2f0ae47721437e2e93dfbef22a242d784de592fd2ee31d7ccf4a4dff6a04fb5e8049954727661c0427fc32858fa3c76ae48864e7b64e8dd92eb12e51541ddb425c9bd1d72b4415bf29ab9fba5b8a5c844af5927ccc0c80c5fb24cbcba00514bb1fc9d3a721416b001a305c57fca6a2fc0b3112001e00949b37973293b04084140bd320129e247fb2d18fe57bde0c12d1a766751e39bd987da3757062032ce1a3a5db28a0d8661657ea27c16563ec0d8c3b0c963b3ff4491f18d0500f6de53336382fd9c03086e47d081b2ae5f0ddb3caace399c4fa377880cf1d3fbee0e401097cc7c0a7264ec9203d7f253d38293897e45f86f1a9fef1679786834805c21b30199fcce72b720b55d49d2a1a5d4f7857f6e4a5f66e676978f4ec13172242c6dc15fe06f01cdcd391355c35d3a35c8a7f2131bd6406c756ae3822bc7fe28b19bd8560b311b3987bbf7b5784348ad6e6ab59cdb128e4f2078be4dc68602bd1472d2a01d207241638e9e98b3d91012738f2ad99eccade05e8f25011c143f73c2750f5d237972f35f0f0efa1f113a833128f5857a739bb12140f215e32b803c416a5312095972e88c0d6dc1ac1b18d894140d918d50b3a439eae9c7eebf78d8624aa7b72a872d0e7b6e4eabc48d6e973bc7b4f4c6fe9fa1aded44f337a500066bb40257e0792baeccfccffcdccaebafa03debeb038567bd41b902a1078165fe182fa0e0bc7d20246282faf0f5ea71da96fedb87dedca9f70873da8677849a5e7e9f8b26614d476bdc1ee5cfea13bf75cfd19979f105dc49efe181a261d78f066c30d2fc4e7d72a982a4a54f57c0166647f508dc283320eac5e170d97cc6f48169b1447263c325daa51c8cabdfe39d0f070cb3f6fd29a4a88d232eb3adf87bff2abf1047603950741bf71f59b5e74ddc36c0f53cc0e7b121356eb5e454c37f0b8547b74789ea729551d93dc147078ad7d2f0021d6e878712b1b330c20141cd1a237e31eecabe44e8c681240f506ccde77a976963eb586da4ce02d75fb427a3caa282db4dd20172b5d4f0c4b951180af775508ce8853d65e37a0325c7b2a7a6ea28c667a5157d2b7bc0dbe553d085fca3bcf3b4de48d7ddac816d173cb53cae4ea40c23e880650af2c02aed3eb0dc0dc305f64c32ffced41e827b22139e5c1519428e1747eece72bb9777ca0455bbe06c21b3c38f6cb109ab101e2df42878dfa9148b9702f8fc481d123f819c89a487a962e83c54dfb3a4741c3c96fd8786c96168d787f960ab5686e4524822a49464171e61ae4ef82a363e8d0b65341f92eb8c1453b73f01b2729b98f03edf5f3e6b2b9c9498021ffe30d1c0abc9480253d09758ea019ae1ef3f2d41f81985b967bb9649de528f0338fa08188ec384552b08e3be0181a25b9065d90612269c5ab0250547805b8b2cec3beea55d57c666018cd7d9216fd4ea431c0eb9f8d3b51a84e4521374315209b0d1f2cb1456ab4aeb77812c590e88bfe81207f6d947feac9374de4c17e34a47dcf049d0e2f014fdb127e26ea40ed7e0d87296549ad64a08e7a1780b59a2f9fd9c90be9cab1b016da3626daea1b32865046ff75cd678ddf9ad1ea39a56f28e890ff11438a81d594f7a5b248410cfae05760e9953ed5dce3d3ef1dc21de36b79cf7f3273a6107b54492c445ab0202b88c0772ad5571b9739e28d891782758895e2b823ffb1ce74c652e64fe26c2ab75c97372a1cb96b336f6f013dadb8dc824c1621248819ccb0aee281ac1b40dfffea6d772df021d4e9abb8cf4a837f0a4009bc944500b5ba2e4c8a414dd029ba88663637265761e7d9dd8cff1dee70c954f481821ffd31471539c1825924f194c94a00842b89982a692b04309288cf07f10280dc4e562397ca266e84378ecc9d29e1c5b72e05c093593217b0b1a5ab19c26c916361f231da1e9ac6653f7f69d1dd709b97244e66c891af4b75fba8709dc6a419014e015d4a85aacf2229995f954c3d1e5725aca0080967854635975c2477fb0c384bf1a52fbccb40b0a6b5708d0b6869a415d480616231ebb3b992fb1d7ec9a60693d28f9a0833cc3c74fc48702f7f71423592dcb1b29f51a455d14068e0117e9e9b02d7b9a2b73d7a0b15e61be6cce1158968e3b483be8c85661a8c15020e5da8e1ca1693608b31a1746758bdc0daae172334524161ccf55d27d5e58203a8889d46a9e877f7103771b41ac593104d75f508ba37de34beae2ac1bd2693fd21c665c9f1dd2a2e4193f4d706231cd0bb39172e6a9daa695bf57efa1969ddfc8950c484635e3636a1600a57d5f67f1cf725d29438ea99dfa140ff00432614bab492161fe3858733c30a98edc7f9e2de768423ee000144c993da47636cbefcf019ebafbfc3cb40db3af78478f2c90a029f8d638572e232fd60e3c70a8fafc587511b1fb68b349942a324f438a0e71f4c8c05272cc8e8eec9fc9d79f24194fef3f96e55f0816cc9a31936c4f0ad4a3b45d3898725797c7215fdb98f5c2fbde663f0ba3fa6903dd066f12db8fa0a501d662d5b17242078d2cc3d8a229480e0f3542967ede209c87dfd6746856c26b80523a6a9a1bdfcd44ea6d6bfa0bae78d35384eee9d556b56c8d7a6f5a240070a1785a096068a402adcc6531845441c2e28bdbbb29a9975655faa886c0bbb11b11e43003ba721814ad7cc57bbbc0e61a186fb10ac349f6400dfe1842407ae097dc2e682e5a320ca29d1940302477f8fb0e00ba92fdcf0048e2d785a5abaa3e74fc3caf98a272c19c6f929999ec9b63477dbfab69dede427cf71245f91e6ebf9124994e726f72d98777fd6a3003ecaa34dfb43956b3486f41f0c3ed0cc7cae222e2dfa7043965c44b28dc1fbf2473d71a4e154b904b2b6d3c02c3ce39602265a278622e82475d683bab2af5ef0e9d5af3db9cf84e5613033540e42a0dd053fb6094c96417e009f986fa0b5d40ffaec527121c51bbe3c45598ab9300d4d4cee935094116057604ca74bf99f5b7bcde4c784f08ac24e0a6e8f6493132423561535f97cd0b545c52e8290e448bff83406d215c797398b82b098d805bba8186dc2645160da263d672b4c4ff148f311cef592b882bfa4f80da520db47bed7af9bfb62e11d050bbe3723d9c51617d7b3b99b232683153ad0b305a98915538e815b4f221d8b70e66d072f9cda39ccda8eb57b923dbf8caf9c1575b9a79f3657887529c62ed92704405338bb2d1f30672a15db76c2c2a87acb3f8394b963194bd23caa9d3684e701ea172646a9b8232528472b80a3a589365ff311306a043a40249bbd8ca85cc09a2d172803771c18f17e8c8daad529b7481ebf5101ce2f3bce269129867c630bdaeab28e3159fc5288a0aeba80dfdc399e032cf7fd3f78dd697574fa4088bee5b9e1b0cff54d1b8937145169212095ee024197adda6a0c02f53ea354987e43d51e2c03466111e8301fb21483bc9c22b292dfa8c8ac6839e9bdb86ac1728e3b3694679725ebedd8f2565d3c932cdeeda550f5252cbe7848393c7d7fcbae6fd1b689005724ff791a0af45b0b85babd16715a9cb5da3cff348093edca91948efed07c6d6430a73dbfb0b1e88e4abc8a7b3e38d259bb800a11ac627e61b6afdfd97a4ba71725d500d010f4e358874f12ea7cb1b749ad2a5fe58e9ef030d69930878c4b1bc72591f667e40e571cf7f3da88f5f1f1005e082f4b92eccb5b30fdedd1c82dcb93cb07433a8733a9fb992cdf0d65212de2036dea73c9ab627d6d0905602865e0072c47de6868a039cf4b31ba111dc15235d795cb2d7e1a1fa0bdf6e7f5fdbe548615f1afd1bb14817076fd0fee1faefb109012b2cda473bee56dc1272864f922f7275fd7d5cbb0d2afdc5c005ab2804bcc2d16f7d7d4463555701643ca2a7359872545a363d89d613a3f22abc7a7a08614d941099cc8afe5db4f71076864c867031d2be88bf94ed360105b1184b3000419a4c2beb7ca4afcb3cc65723fe5af3f27273d21ffccb9758fba48e8020b3e988ca509cb1c3937b8143f960095183236d7294326c529195495e2ddd122d6b9a5609f1458b2319e70037796f7b6938722340e8d6bf491bade3767b6941119698de9e4f7a419c4ee689ebd21928a33b8ec14710e5ee0b4d7d742ac5fb12296db302f0a10854662a9e84a228f89592440c46724e1bd45152df9868d7c431bc006f7f87fa265cf9f801f2803a89f9e60096b812acd097a9bfafa35c41dd22aed349e3b398e0095bbd5dca56e35d1bf4616b5e7231b480a512bbb64e2b4be9b45c8b490905c6654a038aaa5d4eb212c01651f2724e21c74c0def2b8aa8be9b54b589bc2679972eaa219c1824fa24fe0d29564672e9e333bd7209bc0b71a81b11b47caec044e28a6cc3fc161800487a93449c404f1d237aa20aca2db939f156e53a9725399b19fa01d414599a5599db2d1233ea7027a43fb5aa8fe85c34fe5b34a5c6b58cfd14432454d0404b06989a3e704ea072a5a9ff1f9a2c400740b748390c6cd99963eb927856dfecafeb1057a2893174725e6e413aa7a412e892f350717f4ac3dd6a924ed58415833b2be448973d4b467249b9770b7f087a1fd81b50f323ba02a6af62a28f9fc6e1f1c0b18ec2937f7b721ec353af1d442cb1b07d062f1a5c5be8993307328187fc67089b1f7da426df7236ba4a7d4f1b37c44e3571058437073c5e54a08859b910c6d0c787423177977270e799f4399c35254b391715f783b04b195f6acdf9b0af5d2d2fc2f959d5df29a3856a574e0cc18e49469977fa6fe518f32069c12c0c54d562c399f93d8a1e131956c37b4c23e410d27c113b83b6f12c75495988c6e8c0550725f6bd210da158792b290e474c334261d636bc9951d699abb9213029a14e611d2e72aa7506f47207f176035f026cd49def28b40962d5bebdbe463f61a69b81a0df6f872ac8cf729731422f6ec4a960e41e417c79873c3458410384a5ca7dd89bc4f5c532d54c7298db7a1703e85e07bdd80084a8523e94fea5201b35e204211d39cd1d9d3c047204fc0f31ab68c217f54202d89be3a1a75803bcdd0efa34a69a2e4e64d8bd9871c733b2dd6ab1189a3417f97414cd7c64b92fac10aff6b510284864e68e00a958dd22a57f89d71b91c8975d60ecdd802d60d5f4d903ad99900b3a4fd6c79b413b7f0e95c0ceb6a5fea66b9c9e22c373a0b73ac4bcb2aadcc0629500e423429672c30cffe3811bec17e93c3815522f9195386ae1bf7fae92774bcea6360d143472417bc9dd8a548cc2c3d5021bc19939ce474ef4209ed0c21ca963ee8209cc48726f696361c9cfc7318f13f244671833d4ce5f3ef66017a9b1acb26ee946f3cb7285d2bbcaea1f155e3628c549d14af2ddb46eccdc3a6326950628546351ef7d19c616b57b96633b895143383bf001263da30ac58d51579510d01f540e64a99f728de2eadffd5a12e88c2039af3bd28e22e6d62af27d970bb468afc51c74596b72899d9d926ed37644355767abe1c12c191a52b22f77a019a92c296be9c3310872ff4b2d40ce2f1726c225435bc83a85714ef6b1b525ee0c07dd24fa2eee4d0f72248d74b15309504ff5dadc53e5ba4c97115b69341ed00d3d1f35905e3941ec3b99cb984180b29b70c79209c5196462cb70434347368f49f697f91b89c682350f2d606495c46624782c4404eb5b9635582020b6e966a5b702d35f3472e81b1f7243037d85beae06beac250c77f6fbca015d44779741722df6876553a2319ba672d406b3e57ef657c3a518ff687983d2e9cef7d2a200e7128f0aeed9bdf35e46723a9ac5ec89c3be3e5bad1eacc6617337897c338078201ff1989cec5575a75d722bdd22165e95e6781155c9a54d98e110aa8d23c2c387f7b081c36d7b9b7bb1729219a8ace099d3a34f1a880cb5d199bb2344cf00c6d866ff8ab5a12f6e1fd46edf133705c4140f30cb50ed7b9c62897532d3ecdc357d54700baf74c9bcb623721ec9a8bff15eb6cab006740a6370db35e09e10647f4e03595624e932609ffa321fea2d5c08bfba527ecca082895128853c011143e5bbf4bc3ffefe00c59e6c72b46288b719c64a6406d5aa34fcb88ef5e3fbd6ae0f5ed63c903afae2bfdcf03b381b91f4bd0a6c9090d9a98c80b9b4c789c0f99204b015c94323caf3845593060dc413bba21c562140a2c585ff0d9a7214f219f2b598a32e28765caf89ce2e7202760e7b56df1f91c8df88aa7c65367afc94fc19a692d035cbb644b9b8fc282a47ec2e31e40ae00c3aa0f88d6295770789be748d4fbaa29f3bdcbe8e87d78b101c1e312580b1ce84517d5c348abc26a4309c4da18dd9f7f26458e676c8d5754835fdad7735d2dc7fd55d222f2b3962a7f95634513eb6b55625ec72f2ccf34f34fcd8b9341638121c4784928af73fc6688cf6391c95c18fbd29f13a694ab9fa72fa8f7715b9365222fa40a8c8f4fd4cbe299c1aeefc8ced515db06bcc0fd82165ec7030c1f05e006c2cb41abcbacba1b8d260388f3a208d2fce07326f22050b6337e6c6002f2634ac562379bafaa39857bd05e141c73ccc501e0d485791dc63727a54eec4f8b40f8b3a3a178e80bcc210d3f3f656f87dc96d785910b34fe42400d88d101b0f3c932851c37e4fa0cb4847c1c66ebbd9cc77614f24cb86169ef067ff00cb34050e273844335f53ef776a471b048bac3a0d153639b91872e4578772bee817f5c1abcc9208edfd96eec500761d08b955f7b6a9625f76b1d7a53dff1ab5f2066a7e583b0133eaf51cac7f9af87a216da4a89e8eb9503e3cdf8fd1d37252cc94841d2956c5b763c263cc9a5dedf3a8747c9be54e232887fd074b32dc024cffa45ae0208c39daf1ac391474d3f64b953678c53a5f22d6d034f912d6d57280ff2bc0991020625709ccc5892d79e877753502b0b29db28557b9dc342e22720090445ba772a816e4a80a57a433a535841ad53f0a5c55bbb7f16afe665991119412c825a863e265ee50d37ba7db6f8c022543453ec1cfd1b5c1be4dba15710146b3504ab8c1b2253aac29836148d23a4c8c9e6b1ee20cff3392eb782514ee722c6734b2a792abaa3ae59f63396345a28fce79c9aa1122a3346cd1a4881a1357ac45fee112c05b2ee3d859f74a7ef00d13a6e0ff3f9c54c40a5e6f31ced60128d675f22cff1d398ba4baa48e0d19fb1c8477736ed62c1b76cca972e54158ca723021694fa45197cfad1014593fc4ba4345caa9081430a001146f5aeba3523572e6f418b4ee9f0db43724b928ed3af3a9409a36cd7b15069713373986d46299728bed236a0c89557cd2f906bb32ff5e7d210cf69563431a6a16e3a42f6f7418725417e9e2c661565f053f6a1e4713a6a9d33a934c62a03fa96c13712a5627b872ec8ba33d7e211f3eda68f32983544c3ab68967de51c31dfb0639eebd6fafcc5890138b2ad068fceb61c6ec631a9533140b66ac835c604ab6b6d92e29b66d1b07821beeaee0fa2f37495c5da9d03e195d47a4af3adcc0974fe196fc5abebb2761d65ea13220578568e341a9c94d85f5cdcf59d6289accd4f5d108f42ae2ff697205c7250bb4c63da68bc6377547188181996009b8dcd35216cfd94e165421ab68d09a1c3f75fa6782ce7a26551c91f1df6a3b0156fa5db35faca8637f67896c29c5cda6121027509993c7a971cbe88463fa0889b828c2d2b150c735c3d4a6bc66b1952b11c9081e714eff5aa8f545ff061315a9f6da6baa7d927b9c532c2ae031ae34369be928fae59ae11474451ed876b84f13da72bb5bbcde606505c4008f5d8e04865c14a517e47124357fb445ef76cac03334193d90e43cc6af1d85e6c0729aa3195807556d214b41866004bb4bde83326c7c8f7a79e0b135ae249c30ad72c71ba8d0f128af993ee052184cc3c39a9e9960906dde71cb3120e8d22f35a272450d54f2c57b2e4759f8d6b4e7fdf072667a0ca21747f7d6841fc0cbd2af9472e1df642ccc2a691571d02bda09a1998c094887233b147cc84acc3f513322417248fa08df9f04c4d3505d6185abcdb19571df8f8ef716124aa7d5f7cb6856217238c1f23fcace311befe5a89760361c78f00c232eb2eee3b9d6c094352cb31f729ab417d8f69694b3d6047c544803b5b89bc574a068687cb4ecc5ec6335f54f720971f1db37ddadb2a51987f8c786ee9670dfac8092f7906e8c62037c4f8487724d13c99bf7a4bba829233f14a2f519bba0a8d7636f662b1066f6cac70ceb0b3a0242dfcf315239042ca5d750952daa91e2f777c96efe76e8e15013fc7e3356726d636fa27e7348c1a5cd30642606710ab616a555fef736ea23c5358f685ffd5ce5b9181d196377d595e600ff84e692f05d806c2b8d80948b9d58e0bc2cc58709d8436485c312c073f10ac6bbdd550da10f82821c3e13a134ed3b242001af0c41563ff3a499494bceab48722020b4a47f1e3e9dbcedd41abcc43c51bfd165957298e65a471f0e3548bf01ee63ee52a8a10fcd8c105b74bfa80602104ad789d972ac355c318b2ac4d133b43303656ef4d94af18f8fcd281696bb6de538b1dd0a29a3439d45358d0a0b23d48d42df350be9f39fb92faeedb3f52a75a3316ed130213234769e9ee7ac7e929ca27dd9e8548efcc58da468b285b748397d70e17adf726d6b6954b454c3495c2984d6482416babd806f4af161e0542fcdc4b9d456e872a9dc32c3f4299bf39525bd45da9bb840515ac6f48e0743b3cb8fb874180be672ae3364c8a6444e6d9bb9c869682fe7afc237c2a687f10e3e65edf7dbc40a8772b1d2734e67133d221f0921e4b90d21ec34174cc136ca9fb7493a3fa6c84530721cc2bfe06325ccaf40cebc7cf0b9c415eb1ee7d5e304f3d94c48d961420c897218a8fbc1763a7ef2441d590c76e159b56dcdde9243225df41a6e586cbd870872cb9853686c0d94df479a3fbf248c90fcb232ea46c36b64092a0f20c580d01d2973dd5486253b603c434ce643562ef90b3d5c13df0ccf710cd28c252eab4f2628b589a09ffe89f7aa057d8776d752c74bcea2fc1a37de1f4ab873e2c2fc8d0c7260083517ef294025ce8b388ad3541059b7e7cfe755b484e1a504b79172a8d172591d72a36d5de3e02a512a439ebd3874a481e46c1c0a674e33a593a6c855006866564ef81dd71fe5e21c8213258e3b9e405b7beab11a80b8cd2bfb4c66534472501b380c0a3553448621362bd658af4a6e4a54d2d39854fff5f2d319ca65a732a05a620fff57ff72088d7a3ef105cc983d7d42d6a6c0d85944d2893964830a72233651e98f6bd04553f249ce6d6e6aabec1d5849237b1fe0de8ab440884bba72fad85793a3f53542394633e996e3ede766a3230fb5315c8dc841eabb8155050b5c593e4250f682f4cebae8379026514784761f012350d7d5f926759dd8e83772534142875bb51f60c9943306878de916591ac2e4f5886864241f8ff02f8e1f26427ade59d72eccd804d3c248e9f2262789c0f188f1e4d813f7d6c16a5f560772c0bea33e1bdf8dc6a42bf7bd044730d4115117850f9ba3bcd1e38d0f4a3b2772993ceeae4b28f65ea0b77dd57e827c157baeec0e90dd9e3ba56a08f3ab8f8272485ad5c61f6eef16856925156df9b24a14b94968e74e29657313e178fe7cc8727b83f94d0f79a1def5acb3213a7bfe7f285100da26b55f543aa9d1c1f6298752e5fd98beeec4da8cf7435652b8cb0e913e50df85731a6335c7a49be5dd6aca5b2f30b2d8264cc92d0769712d9e7fc4bf05934067cc38e960b7ae8b0848ef98729d95653fa68e1197c42a9d2aab8c2df9b6f952925d24396fa314d4036309a572036fce69fc21bd32b24e880b0ad216d3f588f7cbb158a3ce06aac49a4947c62189499d5c18487bceee8c3d409b8b7176c48e1445ce464fad29fcd652edf8466bb855f5c8089f459182bf111b1a8b57b1a52fa1ff58a9fce0390175da1963980c0a29d30820fbe0ec74a732359caec79f812cff593ca050c3b99a47b2ae2faf72ba56103ae1a482f05cb9de6456316ecde2e8f256a0c16a112860969a65acff2ad49b012b333b123e02b380ee510a10c6605985176516f3cb8947f1f44c3f926b4aebebcd73211335584203821fb83f2553517c2a7b6390f8d923c6c4df3d1b72faa8a42b8f6e5be064ff7da0cdcb182b810f7eb7f6bd59ae1739ed86facf3e57378c7e3131e24c30c5f230e9385293018ee3d3c4d255fa6c618e5eb10c984c1b0da4e034934941d72107845ccd95b927ca24ee1f218d58c167fa66e4a897227262f1b9c1a8272ca6d6c9e0002d6d8430649dc0ed681fe9006a3c0b53bebbec728c2553b23461e7d6cbfcbe3c1465cd779d6b70a9aa0606db6472d57112fc8d7252a6eba572892166ac6d96d027c6c36f4d854049dae32938484ec0b777b61872da3663f3b90ce97455bad396b0367832cc0d3b7572c92b0fb48966b70d6ceb58855f32c923b5c39a96245f7f3f843b260ce8340b29f4d831d3282f08385f6746c59049455270f190ffc3910d6ead18031953eb9ea4da2a3d69467e24c82d7027cb8567741f6c8aa2c18969acb90ed8a0771aa53f72a4e7784dd6ff572d95a46668d6e42e69d295ed49a95d823b3570ce1c4f966c9f97d4f79a4cbf57d735f76f2f44bc92708a6bc1de994bf120bd2569b898653170b8e84ea8fadd85e221e672daf41e06d70750a8a91589a66d5f54c9c3a52d57bb4893585da538fb84bfa80d87e5c8a3a0446917a383af350f03823e3a556a16a1575ffda7ac6c9ca719f525f61ba7db95c057bc02b674a7c8c34e17cb45a44323af497fb2f82f23834105724728d9f975890eea3e0f6bed012c02ce6bcc671706dbe6a662e13062d3ece172786b5d91fb35ecda1f4c8da72804f1bec0d1cef694a70f59836ac526981f503fd28423f02bf15583976c22f91a32b37f289ae41b49e35425fe55de03f37aef72232c2eb8b365dc0c8087e065de021886ba45f1ff2c351dddcc11409893faa0529fc9e84f5b2e1244f99a05ffc0d84f61e073f9e58945fe6a33cbc26c9d468d725b322c937bac4b2fc6305d33eeb36b16c3e57cdbba94dc00bdab5838887a9f5a97786b9e4fce0c79bd2fd57873d1cc6ba40ae9d220243ec394f0ba7ce6a2f27213b0d6e2bb308ff506bd0491dda8307b301d229a09a6517fed2488e306863206fc319bbac10c8efde1430c3357c1c15d0efad2631b66cf0e70b34cd84bd39072560924116b91d2e431d603ec04185d276a189c059c1a1684b75365f222d84b1c247ab4c9cf057cf1ff5d6c933f3cb2e547c76f506438f373f7f990e3ea95147253f84d9f9d52b260e6392b4f3b0633508beefb91599c85e4c105b6b55a0dba72c86b325a3d46823fc894c3d9f3a64a7e05111d1cd83e4050dd3e93b04bcf6072606eeb0b554eda892700efff30d000da4ca677c4d17b3222c6c18cf3360b6e53ecd791f52b75fb72beff092058efb086d9b6f41507c128edbd63c7b2cbf0da72496625e3d735be692e3f8f47e4a4ae95608afeebaa99963429eafbcd8f554772766d49306107e918e8226f7ff9e9c60c3d9c31df73456dae23d995cf69d8a002a95736844b4297d0e0759b845618fc45478d401ffb24175cd3de9374ff4f301422dac0d266a88cba0f63b8f26e2a65dff5bf9eaa6d0e4b0a014dadc526260e72dc2b17a7b257d9252014ef08faa3aa80ad603a91ac408f234c5a07b7a821db7263ef0dfb1b9fcc3430bc28c7c47f586261e3532c687e7a545e459485b708517205a36b08129233c11b5ecf432b9de8c0d6744140f6ba3f305e39bdbb35c5e972b0469c46a26e822c87669742d41fc9808e2cf091f0fff594bde3adca398301721248918f3f082b00b6eb55a0b9d5c5b4050f972a16398c117bcbe6d3445ef6560288ffd6877c22acdd4aab55055c117d00b48a1b8186ea504b04ac44d3d532032205340e2d823756892dbfd691bbcb558032d4d97bb597be0c335e59a25eaf06c2af55cc285e3cabc56efefeee504cb046b9d1acb49c59503dec71a74a3c9c7231984a368b37cee65a4393fcb9918d7265ecd77663f8c6e838fb457e9db6ee722feff83d2d9525c295cf3cbb44d6476b17c1e6aaaae53ff26eb251a35ffcbe72d232d1c7eb72999a167e752e99edac905dafb9d2cea74277bec847291e6037592e80dc6ad928b96f063c6195a044b376bc38c6b9b9bc8455622456c078d2d738e8a388402e3cd649119346a85b8627a5adafaf0ab9203cd006879a8d4e054002ba9f5851b4ab0e7b0193120999d5657dd4a7fc6865f119d78682af3ecfe78a72bab1a890bcf2a4b1aaa930bf6804ad390e96bba213ec6df592f872f019c9af72fd755c1eeb5ce62f6c3c2cb87f1179bb33d6134a7265451ae029b9c078475012462fd0bdac1689746804931498db6f0006c2bffd6f3a692a0101ed664c57eb72c620cb91c45ce212d39653a019a1c49f591510a03adfa5d1ac0590590934d8144f8032bc44384e92f3727fccc30f5b1d537ad47b789e43a3827cbd8741c54b72517b3b326b8ab88a2c5aba7e04cb628b47962c3f13363a6f23a016380bf3fa72a23cdf74971a1ae89295e04d4fc40eef9f4112d0e8a587a58f557b92393a5d72c030dbd42c5dbdc0f0ffddef698aa56a04cb9251dc84e6759f80cfadab60e8713e1e9903bb2867e7898bf24d24d833d146c6f74db28f868d4b2ae2a50e31c10241cd140db7a36ed276c61ec0cc968245b3d0258a6b265207cf09e4b385bfbc4c37e71cf28e29ab85989c61c3c047ba11c4e4414557eec84a6eb8dde95e24f7451384cf30d0cd418531028f63fdeaa0fb92d14d5b0a61722dc31fe52f46d02530e6d7cf86f3510c6324e126ee9d28214c2cc66b884635cd6dc44c844175a53d206ca1be80eec90cef80fb1ba9bc932fc434b7e9953c7a178808f89ec0b715565fe5e2fa6ed88f199970b2ad52d91ccdaa09500cac465ac7fcd19cf87794a12c7210b11277d4d7f915b8edf3bc75aef0981d87a9a18d16579831cd9955c3323072c045b7f2bf6c6ffd80f10718ed2297ffc3ee926768e474fd693dda66b807d272beb07ee2869b6058f11d6a332b75896f26dbd8a23c5c3e580d7c2afde8c8434870a03b086145f9cddf97a0cc3a69334b6c4bf70cf3dc892736999c17582fd7727d1076d18aa655970e4c6b0d5177c1c62e5eb454af81e7bd8b11b850c25c283e1bab152e8a274e5dfa3ead44557cf69a7072365d71e7ddf6eb09a26fe0766e72ce45bbe20d4030d92dea726681853d6b6ecc5ae294f1b39dcda2bbf3fb1c0772228f18f84382115a9bb98083e886fc135c5a6bc0d651957f3e796e992397ba17c63122ff44c15be5b6713e18b7b007a63ab6b81df595cd10682f73891f610572234033f7bbfb49b32704c727b2b69dd375f88da80e0897499219caeaecf5fe3051d55238e4be6fdf76a2f3feef6c84a1f4b2b6a29cd70871274c71601e43c7728f9a0c6815f780cab381369e7568851f84a9ebf000c58be54b02dfbde4f74505dee7a54f5f74ef19d1f0a642813bd967887373e9a48c16009acc918c0a68647227607b2399ec9ad813b14a06f57e1346813043a2cb3d667a12add0d0c4034a7238990c1e83b223076c90de7260fb711d82a5e4efbe9c0bdc351095b2f7f3fb722a7d74c32ae90afb3b484a0ae0d92fa2590d548437f7067dd94c74b0a9a70872cf0ba29f174fdbc933819bacfbecd1dc1c40140b4fe20f7f34266f5e7706fa722b04dff607a2c0f1d67639acaad03a1eee9d9ab766f4d8a93d39a7648bf24372984dc3c447c1f948a9f449f300fbe17f95f2b36c916a2f4e59d995f8aa4a3c66107ee2df7e85c352fc813682d14b132cc49420955ee0915e297a10ae50441a720a7b4c6a221bb371a905ff69ef00b9e5010ec018f7620a989aaddeceb8674e52a617d56830acc55e3e9a72549aaed5f1edc6613e93740133d1213a57f3bc4572603fe654be01f4e711943a02c83e6032a3481bed02aa63aaa285165f5a67e8722fab7234a0fbf593c71d59764b660e5d9992256b8ad3db7c5b827a47aefcc2723e60f7c547d2eee546afdea45129a2da2881cf71f41435b49a6812b9f086fd7245f7d83e7a22088db37332578700c8cef7833ed9ff32ba9f9ae76962ea804a729dbeaf4ab61288cec793b60c153d1e018e47b2f5a02428b07536bf60f8b3b75a4a557a30213942fe2bfe5bc96ab53f742d4f7a534c23384a2c3a1ad39bf57257f9c7a19895f7d5635cefe6f57e1711ae34a6a10f2010ce7f239b562d1301967225d6cb918638674b60fbbdde81f422f0ac3284b705d4cc19ceb969f28136e3728ac23720220420af6c8dc7678d652604b7ea5713ced4d85d0cda64f14e87c35c02d5d6f47a8f5369cbdfb5439af2055ad315a93284427e7c8c70bac3b3c3b1723933d97bde7fc45aaeee08ca0b02ac37caa256650f1385aef1a98f5531a9c73a524a323076302ee6deecd34f4b4bbcd69adf7ea78562dd37a8b3e6a8a1c6b572dc7da72d21a9965281307b07e1af0a6c33feaf93c925d569decce4c236507800d48d8e51a970e2575a6e4bdf7c56d0fab298dfd07c45a4b502e0772f83c5f95bfeb92a3d49687a1b355d6dae1a14b08bc6e76c580436b820cb4a24c6b11c2072fc8c1ebb03fec4f949446408fe3b7f774a4f421167fea886baac8b7a1625d4727a23669637afa33836809a2fea1bb2f83cdc7bdd05bfe8d5e455f66a0c128d72c5312b3c7ce6e9f1118bd82fe1aa7ab2ad1ad11634e502d8cd6c209dd9ccbe6f4f5260383fc7f88f76fafa8f326c7858fee7d5f2d60f16800067d74bb8b3ac7244ccf5ae90db810b35911a631a011c18de13aaddf67132529a88ec9c03028357a7bf10f6a8934993fd25e5eeccdeb04754b8fb7488960253823788724cbf93724ac38be341fda9fd2655678f0a0b10425a31b64907ff800f4d33607d182f2172225ce24e3eeeb8eb1e761e797c07a7c54fdc5bf332d01ca23e6c5f0bbe59f6576b39250e909d7176d3c271b9b215df9bee22a5d2f62d2576a9672213a19c13721220feecadecc76d0babe672f02fedf705a3292475290464b79e9b258f9bae728aa8217e665f93305e49e5be2072cbc83b517388cc98e7905af5da9d7747996e4415c656edd56f3df20add322de4e97c2d52818f10e9225105e456bd2104be2d3879471f7f545bf4cb0ae78475a6a97fb93574771d3aa65d70b03d956848d1546d10237ca4066464cc4c3309a79562e41ee74464939059be8eb9fe9f843c3372ec6b6584ee247d9077d734f41178f1a09e0bfede3ae0c28067a03d28c84a0f724760f53ab2b3443c321a36fa458962b21c74dd45fdcccf6c83fb59c73b58a072f0f91ad1ebdad1729d6a52524e2bf6abcf11f6571e6b99a342f2bac37b4c7c726b5616fa7c6bdc8322773f7e9c1cc12e4f5a50e139a08991e9771332b2c785721f1fe5c493498c8f592f528f4ed8655ef6b21e63ef8d16492650d80e96619134ae200477bfdfc41c2493c8dc057e6921a9234e4b96dabafab0a975fab6dba2723d098fd2f7c13c9f1d56e88f514b1217990601f1dc65989a93cbe7354f51f972d8f1524cd4f181533326b496ba3c70d521860b9b71974b7b515c4cc6fa0dfb41f2112939a03b5651095df3ae5de3e4a798b2e8b48620d0ebd9681162145f726c56b4e180e1c095abb792332c419f468a47c2bfa75356858897b9862895066972cd3fc02d45f603aa3243b66e42003d6cb0b1f6095c2b2ab382f948658b2e80214a66705c189ace1af939b70e24e95d286418fd9d632429a343a18bca5de2a47256b88c8cbe51a10f4a23d257a44d13d9f9a8db2db4c77a81d58513e314c4945a010f9e1bc0867686ff1b31623b25861df13ce0f77ad44443a3b2213056b1e972420551fd2c89593ce5e29c16c388e99fa042dfdf45227504324fd3c5e2700a72cac05840ccfd8a52c05aa3ee518597a19147dbd437b234e9e39d4cabcd890f72c9cb071a9ba473d3001d9870ba44055535937d44fca4c429f974d3b988273472b6596fe83a4cec1c4bc6d81f8bec84552d0c8b71aef43e9be39a851d972c6e5c516f38f0efaa1caa15bf6e74f40695c78b3e4be7b1348d52545ee5659527d74a9d1eccd1c9492f2e514c22a28021c5dcef69299a8800932942d4d84d00980872966f06f0a66ebe6b3a49f9111f1b277736fe00d52fb9fee620766f6a6c9fcf32985d601fd762855042ae8b932ba73c2e3ce5eb214eb5371d9fb961330034ae72222522eeaa5df84f75f6f1e6aa8c7c91757ea25497bb568ec6cc79c8f99c41288f7b821ca5502828fff19f9b205ed6c3736b73dc0abb27d983aa21c016e6ba72571b8af570c02ac226a2a2512e7e65b25e918d95a9b8a251d646787933eb6443594e0cb9e6a169076819b8c7dd5b18b7beedd9b31264b9383b7ae54de5e15919d50b8bb335086af49015e9b2dc34621e406ddbc9e8ad5e65bb0f159d962b176c18d41ec3495c5081efb4d48210909b6e5b1487e9a56a909cd260f4050e6a7372239ff369aaf7cf89e0e6e9d601eb737070e0fde3b4b0db62157068a0591c087230220fa8bb74defd0ffb77c6a68692564acb5925577f3f1f1557ef345e4a4a72fdd2fea383f3effe7dc6ff252631b05d24cd9168f10e6b5c387c7a1a4135a721b589f05abec7900316238f7d1faf083172310d374c117ed11e776aabf939ec121d549af4b508fe5ebd996ca425aa9a6daf4a6bffc5d5995da99590a982218f72c09d23fc347cf2063409d56ac79f3154bb96518cd3118a4fd4a5fee79a316216ea87a70e3a901a1c763687f70d9cfb6a5d0c7babefce945d0854e019f9bfa7725824b5869659ae1bb4892735172e6f40cfc3e2c381ad0398f962ea5160af457236c6fb016ac0c8bd42b3c1c8becaa5966435e3d29abe4fb1921f72691b8b5072fdb9682b8f994cf97dd07604737c25dc5e77a81f4e678052931a976f560f45490efa05692f7c126e362178d2f6094b3de584324468a2ca0b4e83b92b74b6617251899b74efe56205014bb560300c10c52ab4fa1dfdebc6291af7670f618766305bf4fae13d2f500d181092d2c6a8f0743f12bd27b90c4504d232757860985e15c78a7d8f9e0a53a87f22ad22d71bcc985d8046f54a22e110591b2981e1c4fc72d68e578779ee3ba1ed9a6f26cb310413aedf08b05b39ba28e25e69b5982b5c72f127bc99844dfacf2eb5104636057cd73fadaa3cd799a9cc528c51cfbb3e815f353c99ac7a0ac564a5de39205192e23fd613cb19bf96c0d0b8c17d0445501d72f2679e252f2d6f55d6e37888199f83cbfcefe4bc72e0c3e0be09dac4ac1a63727034f130881a318c6ec3a1ed38e1acc2a4086e9426a4f786a404bf9f8fcec35ef8debf5b9f9e97c99dfa9d98aebb4bb5562a2d2e908dfcd2c644c7cfa669190edfef87d383593f2342d723f3bb87c878504bc5b9be3c23cd1bd26b0fc53c28725415816bfa576f1e4fc62d9b1802a9ec27351aa5311c02bd62321dc21cccce72949b25d4049a39c448089e4c559109c5a5028a238521ccb11d72eba917e7c757df402cd489aa43f1c6ac4591a5c6f88f2e2bc05ccf6412a0e8ec53d8d767fc72a4507e67a975204483d58464b43237be47cd87733d6f0bf119e36ee43fe27527caaa9287545127ed1a803c834015979c6d52a6340ee9a79fa4ef74a5aafb777231f24d3fc09155066b9aca56d27bd49d3ed8bb53fc16cd0918031bb4e2bf321fab5480a98295deec42fc218aefac963d2ee8d7c67278207bfba0da843078023b9c27a8b4b066961735cceae99c63dd06949be80543ca4a86a0af3e587d076f6ce7be5d503526fe32ad2a3a9914661f733d84e15dc55d2ac0c81c8d4a8d1c56442062a7dd48ac3668e4bc97eb810a2456e5b4d268a86b9c7edd70470379f18e7218d0040be8be43ec55c26cec4fd9df4a86b8b1163c95bc2eca94ed5eb613aa723a836974fdf01420ed493723283a622b5fe6badb0c73ee9ca8d4c8e67fec00127283e88afcb3cf940220238c152b0c6484c9dc6f3d9e2fd6910274e92254fc72f175b16b457b2acf7d890a5d2e9d8eaf5e87b2806f301de07fef9ae41f60645dec945e88c7edc45cee1163d5651ac6c2b654c8900176f4886dd80212eaff9a727c78aa11eaa2c7a4c84784971e90576ce65432f91d9d36c7272550a6645d2d0b49023b8b6fc822da4304a66fdb5c6aefa257569bcb963b87bb5a63275d2e9472aef272002d2cfa3ffa20ac9ebd669f65ffd37f485f87696e9f338a20a4444243c2493a53e2345bb111c015fb5e15423597f5edf383a7de78e0af042080496a013d06cf5afb99c182abc5603ae66bcbcfe57f04114a3e87a89ff06152b5afb00f5b49466847503f2f70809bbc58ffbc7f7af776e2b30f2057962bb49d2ad239728797682f1a89fffa3664d8a5992405f21a15783bbb5b7173f3de0be11b4038720d0816310ef252d9293ca4ae781f4ba32406b14c092aec95b7c2648a75fb64723aa3de16c484b77aaa76d236633e2e2b473247b121fcf8ed223e51e29985e4662324155683cc78a3a227ff7182267356373deccd81904805c30e1ae65b67d07231685546499298273e7065d3903ad3cd0011648b38593d2e2e69257613053472004a9f05496fde9215dac8af5b47e9ecd72984095be506c6cd7e1e5ab9ab7172cdcc8a165e3baf7d274ba289ff4d1aa96951e7838335d889b65f483477d0a03be0ef9967acf8fdf7c9973ff43101f113c2693831ff1b474382e48808cfcbe01ec422b470e54af9f1852b84fbf8e2bc79b3a68ae05b1ed829ee607a97c65e664ee8778ed6f91ebd073914467db2c57a949e814e02f11bc91b358967ead8b84772afdca316445563f720ddb95087be2587d41822ff48e9ac15c342fbbc1dadc172c34196079a5799d30d00b41ac4d4b1dfadb43c901c25a180d8ee96e0fa3c1026774e682749dfea6c78adefb28bf4ae0a78b9885b3391493390fb80078f3a1572efcb0b061e4d04ac87d20a7f870984d4a740e0cfa443abe2cca2b9e338a54941b88c9e90b05f740c672e5f34c7a7c56df511656ada2cf88bac912d25e976b440e2677ae167db144a80274d1b721c3974d6dee5a70bb3eff3fb11e738dbb6bf72417e28d8aa6c730ebc7d60fd1c2fe32b304544f8463ad152b47cdea8db3f9e721372e57faa1dffd98833aff1679687f355a899c2c6260529793a38436aff12721da0ff7c5139449f3d8470a765ab48b7c535c5872d6712481a5c346dea923202717d7b61b37bbee463074e10829a6a073918600eea123b4567a070bee7fc571720e149d787fe4b2c3b69bad21439691ea03ae0e7b7d1e22c9ecdd493f32992725bafa9d3b1ada13b1e36af556d6734605b6a72a298a223ef68e27c2bba7b1e5594a9b82ec0800ebed365c15d0db34bb27f7ed5b2a083e7b3db8424e705228c72c8d1bdffbdee4c2e81bc1ddee2bddd83ef96ffa4950ddc9618797b4a8ac574728d784ddea91bc75623000a1cf8b43e4cdfeb1f9e054aff4693bd5472fbfee27206afc52d51eafd4beaf783bb53683d646f0854b44acbe216ccd39611d6e311610d188f50fc602f2c8aa91981c04c86cc302e5db94364e2827304cf9247b721726518d52b2f9cf45b6a550ac39746bb31c26f56fedf90fe293fc35b628ba69472c282a9f98595111252bbf0834d42bcf5c018a0bcbae5fc8a5bc071f92bb27d03948c1e60d0e42042184e75967d1d50bd42c02419365d61ad0c32e98e4e9c857263958ee40b36b8c7706b789c899cfab82883a1867c53197486fed159f363c41198100636bab51136454113d46cfc4e32ec4d680d9e0b259eb28b19ef0a862372a79fd125670338d59033e04b074173721cd104ab8bce1de3e998f7f577793372d6cf8ea0766c4558d088a53ba06f9d74b0f06d2027073c51f21323c04bf4a472fd4618658680099458a882c977107d6194f0261d63718db4c6900480ee789172b7b538c3653d9b458201e27ffa008615be48d2f34fc1944173aa1f6f8d2a46060ceabbaa95f46c622947957098743cc174ff10205be409218c34637c6ea65761b956c558b6bd7219c879fcc06104de9dbb0ad837cc065f4904af08df798a2572fefcd96ea39aaf18cae779c37a9d7b37ba5e6cab9b4ed7bc06745816b0859c64fb1cd9560660b28ea1a1b7de32fe85836efef00c63ca097b9a1cff6eb90c404df221c73f15623f191e2a6857da377d79835d741184c1e31127f221fde0134372f8c373dcc1e2c0cbf5816d25a8dbc26e01fc365b5ebb05fe3b0dff1645075772a84279e7d5439124913560b0d0e35dee0bb33a10a3ac55fa2321d0e2b8901372da5a353d11681821992130d14e527748112d63bf4509eaa8a698bbd9e3f49072158a546f5fcca1991250ea7fcdd125461b7336703c8fc80d5cc4bdba6a5ed7637a4e5bc5cd82e1f8e28899e1eb2b7ddf6823165a06180a1e01fc261291533172dfdb1580d8c57c444b04f9fcd542424d5a8064a28d69cbbcb129bd598b61e861b67ed7db5062e323095421a7c80c062be0bd4974ba7cb2617c691fe9a21b71177b78212994278521d07053a6a122462bbb405f9391b58c54a10b5a755f89437248a0820dfb336b852b7e16a614b3ec47cbd1d36abcd6b5c45f5f5615e4e3083173517702a8ce59e86e7774c80b9518416ba7388cb8a057628d56cba15631450ebe1f57da6a3e816967d7b5cf1c17ea47596fad3f2f7954999d0ada3b1e0f353802e000b132df4d2913958dcbeb4047f8e5121f6839fe8604b3c3577883b48772f9382da9b047f66536950afbf869d48e46df7345eb914ada21c49c2462a3f6725dddd88476fdc87a275d10b13fbb1261fbc3dbe2bbc842366b0fb9508e0967723a3f330ed32faf9bd6bf65245ea016f498590248fde5a0f9d0509db69e7ae5393382610a73498bae2137fde0841259fd7a77184bb8476163bf6a114ae0ccb6726b854839345890b2976cb247b562308a96b2697690c577f420b65c6c6212b572f9296ffdc3fda6ee1cb66f71f9c2c4027b27e965a946190b8192cea0c99d3972413d2a60d8ac4827e0a632ce372737297bbbdbaea25b7fc1cbc5ce9c4ba63772aba6fbbebd4c5db5535772b92bdf7503e92afeac7d8785eb7677ccc30057fc3dbf9d9cf48ec354de01d5c2be8bbb7546d89ed2d76393f08534030d8e74951172155215217d8def24bce36acecd260b7ba25884608cf8b9da55611dc6f8813a21c3265f5d5fb78483f8fe90f04c70d9cdb228be6b106fbb36950955b7fd4d4472206cccf5629327c0919beca59c8013fa87efb42ef4499c752b442a492028ea72b3892ceaecf48f9ebba5298ec577559e4c2d5c3116f9b66e5df49cfd14ad1972b1d4df81165c5fef974f654b4aae4a4c9d8f2a1506d16cba408142d9053ddd721d401e906ff26798ea91c4d9a36a384b681b6c78d9341013d6cb05d4bc4d2f02235ce1e41576ef84bf6a897ee8282aaa1669bbb7046c782bd254c0cb5ab9e372cc829d2ba15cb5c24d07d40546936f8aa49922575121cbf9c975914a93d9fa72f1a537dee83a6003e034410646b33a18f0a4de10772557e6cb636336457b657261266b1d29b282713f8c7e9f5efd7c2a9dd0f38474f5258f316053f6a91066728045c43309b18e4ad55fe995f5850353e6c9c2ec7a8ab26c276ec5a288868472f54b1673feca48e8fec6b2797c593db752ca68a6298d9fd6e069ae41d48d6341709f7b871e715429a80b055753df0e03d48b6037da3cf74a15477714b4d625373a739b1f9e08562c5d009071b7172c03085efc085f3465900ffcaeadb617fd2645844f33f7ca6ee17b030834478de7c5730fe729048e28c3e63a2a67a53a4672334c1bda816bc098f3673eb9031bf588930849f45d1bacd11704939ea1f0c541fb992d126c93e78670d1f68384bc42989b653a9c458f4c964849b5173087535ec6e13de62a377f99ffd75fe82b23a7104e490250158c6572ed9da650b134c571de17e42c5879efe4f9ad3bdad03638f9f7af441c1026fc8b388883bb8f22581ba2700a56a35fd393891d2d87c910fa5025f51be4aa5e1a1545a46d0979830a7249e956f70b84d86f17e580f9b1064e65018eee6bc9dc4ca222b702bfa1e3a31afc8f3d9a3988cff70cba1359f5c61b2e61a8d6e2c1a7956bee98686be5a02272ea336ec836c8771dc38e1fd6a5a8b4290aa14847c6fd19d54b36d1ad507a764991a56b5e4f8f47ca2ccb99bf505f99796203cfbcf296e0d7fad247f03bb7202f1fd6aa177d1a99bc2df20d7ae6f099b27f35f1ab50da8499e766588ca68e7b7287f047027ee870afbb704a54574d8a59d187796288837dda4c0f17f9c48c08727c401b5be60e98fc9040cb19d4cc47a94f8317ff7fe632a73f1129debf12c3725d3a2a279fdd69d8492b7ba3a201bd8edfc9052668e104b4300b002b246e7a5ce607993ccc7b0e0d2ae4694c59edfba7cd6bfbd6c84e3fb52976f684c429582e98f5664c54505d308b67a82c266560cb6e2ddbf3bee3717157a1f1d845d46d7201ddc970ffd8cb8503f1ecb42028f2d413ed4901754e2dbbfe6dd2d94ab277441357ddd518a570ecdea0c6abbb015b96a94ee71b62a8382697df87db1cd67d72270b52ff8d11692d02665b70fe0092929013cb73de64fee641e3f3e08025be41d282f5372f51c74059c910a2732f816ae7b2625b4dea027e3752a3fa61e46c0a76f20dcc5f75516c98d51cfa0837919dd2572ece141c2df8fd8f70b4430e8b72057e397a6759edbd5ca2b7db4fd26fa9772cb97e20d5e419ef8ca26e2664632b01a309462ea06d36fe62410e5aeac5cd75b440a0a67f36e2c8cb37975252dc57b6fd0c5063f22ff09b4e9dc089fe703da3c49714793b3c3dd8ac0488b8f27a2f649553c8717ea9471779fbd6fff46beb30a0fe070372c528ced1accc276138036ba33d40c02230c598081556c806dfc2e16f79893f11a25c7b1f8ce89b7c6f72f6eab802250917a1f5c7a7faba18e5a97ff623c4cf8b247d1bdc33017088f1729fb812a7b11878b35ba63019bd46ccdee4e7820242a3803f1ec87ac8e50db7722e8d3fe76dbf6ca21773346f142f34c322fb94aa2d36faae4708a799ffe7e872de5b55d62a4e1b28b7b295796b0154968a76911fb3c42f7d002d054e41ca9504472fbfdc1eb0a0600d6ad5c624f69cc635273ef0f9718f00cd22b30746883e724dce0d60df820e204afcea61cb9c1ae0729a3a61a5beaa788de718f37c32dc725b46114abd85b16bfb85d7da162b239af3997dc1b50a979d0e225bcb505ec9729bed0c88dc02b2eadfc5e9a007cb9b476c5b71c0140173a7498ee735f3b04f05e4493ba6aca9e42df70311e138e65edf393175c586ee0c40d45b6358e669d5317cc9596b0d8c173b84249f86cd47632228cba569c353adb90042fb5a0125647282d3e213c61458f0d4062fa9a55afddc8de9b32cf775eee5002255b909187772c88cc98bfedc4e80b425f83d4879b4cf83608d4c6fc7c08b8315d94395f3835e29f29ba7a683bd06d0b0baae1683110f6551edd8ac66c7288092cff1705bb072d33672205f89f373407322e910715c788c2f09489cfef2f87c86debcc9f204725b88d29ef9c0f5c23c970a487dd9b0bd2435c1b9c6eec40017216afc939dd97295ea217149e7683c9e1570be5d026b56288d2d6700f01843111ffb9309879a72ceddcb9cf312f3f227e945cc480baa6baa80ce5ac473ed7de8d6c21d3ddac272090131063126281154f073a523ed62290c45a6ba4e5e2e9ead581a5c6715c83caf7f370a819b8288d253284e7bee95f32c6ba3ee46b191fa074ddd430eb19a724433517882dcf7588d3c3a6c171a07eb885fe86328718925a6fe36f381e4926ce84513adee91c7ddc93b4e573c88bf3c7be98054b8b17db951b3fb39a033a848ca212e02e7296cfe8cd676a8e294222235628b35098ed7031750ba90f12acf72cbb1cc635b4731e87ae44bfdb938eb1d2f63f3e3c4cc77be6e3e034a4a9f97724080258f62dd3080208f62ac817bc6499d73fdcd04713b77fee7cc78c739aa728b228b367e73ab0ce87e130e79df07d7f79d4ab6460cd12751bb688ccdfff137d5a55bd6fed95a1d584f8448d1b1b794eaca05381387101fedf93b09648a862fdc4ce0ca8615d4ddfa3ff5410ba78405d640ab1c493d770eacfbe109e6a2de725cfa91d4b5f638cb5e883719c15d4834e53f9a1cc8b65aa618819d098cdf9847c71e5b2e6318dd4819339dbfc955866a47c7d35c38c7718a7a8679f32c1a75727ae3fda6a951f493545168121ccfd9171626a707f8fb3cd7aaee1c01f1630541ec74fdb60f9b0291ee4c3ecd0961bd5403d39b2da6cde9a52f07f4b5405fbe1f326cd06640aab48ada56fd53280d7772686f9a21e0d4ca90a8a12cd1afe18872013052697c914c0cd745a47bf3e5d64c725cc089d1e74450ac9ecba9a1425d72f1fe04d3bcf03c737b65c52429be97a58ed44d0c56c5885931bb7a6cd6c1850ecd59fbc38236f0529aa71f1b2a8224b2b51f151bffaaa784ac7d184e4a520672267138854efe972d5bb9d32f4f4e18db9b18788134aa01c1af9bbdc21d3dee727b133603914dbca14bc366bb20e89b2ccfd5fca1968510238d0cc0f6ce53ec2811aa11cdb9c8cf7c58514ef607c2679488fdedece65a171f85b552cdcb5c9e4700485c9fb98e8bdd338cf0e27c00d6e67917dfc9da0a068861b6ae6e8a5b1f7273b9273f667863c2531e2022ab5ca166cb5b2a5f0dbf9b929e7a4d9bdbeb1b72349816bec15308b7e127dce2994ce62d89dd838b0f2fcda3915483909486b672bbc8a81835704b07153921606e80db08702aae1b1b5c6088560b94bf2a1e586736fb9088e52cc0d95b34b8d14e93bd5fccc1fe92b65f1c11369c3a06f6bc004fcc88cb63f3e6ef2b8d7971ab83d1bee5b9d26d18c149290a3a6dde01f8576872de2845117919d2d77f410cb8439597caa2adac71341aeda01d4161bcc57dbb721d4f4d85c909f7b7fe2944f74533d22a7251f8be592c3027603d34b7a1e1067262864fa1f172d88b100e033da7c2bb036f7840110170a356bc8ac44ff999397238fa5dd411e409035461ec9739dc8bc657218bd15c9dc5c6454ebbcfcaa412720de56728ee711601c728d6cb1c10434f34e28d5102cb0ab7d40acdf627af8472b793ff330050568bea2d54629c53ebf35760de69b87d42e05a3c9417ee8bf572326b6c18a974d0443992a54e6e66214709e2b8fbea973733476f7e01c7f527722a0f9c7c7d0e6ee8b7fc2a3d2cb1420575740748fba6a2d2de998dc7e07f5172bdd805399975300a2b3dd4ec0cce50f31c7a2b06aa26dc6bccfec4c5b149fb7208871188efee753889dac4053cf282aa3292bc0b193898f0ec5d528d8e0f5772c3719cd8cbb4c94c2f2c439e98e1bc972cb215824f664d8b67aa8f81aacf9e7279d8d8db46e08556962b156dc738a3a35264b19f68ab0ed18b313b0f9caed71fe100f969ea4ff1b3113c9df485f445bc95ba8b74df909cd19e908aee5f439143e498db1da6712677e2b4f512ee6457045490022bc02a6e5d1b6b3036acf8bc517fb48f2d5e5a25ba436b2765f7f118519e09f1bb3d0989ac3b12acecad8c31354ba000dcdd556008210ba86a7521f2c8c5bb5911e89cd8096866d3b0c6de5d1179c7cf590eb74953506beb5b303a1314c8abde387a32359dbc09f812c9b0d672f6d7cecc78cbc44acefc141e44e2792375ef70aaf1572040098cb6e2df70a2727bfb0131e41db205d025f74ae89a2e391f8ac457d9a74ea837c91614f2903b72c9447e294df3efeaaa2935c1c7ff790c8c12e77c4c3d8c58066deca0bf530d723f3d754f76bea2a0c94b55a39108e785cdb700a492056d91d8bb01107737dd7215579f44a97c62042242902747776eb5819af4baa1c85e0a41a951e85bbe5e7258becd18782395e27cb993050c4fe1771a09da14e6f9b9bb4da5256a0ff5547255dc5ce27d438bedf8081e417f062934e1ca2d587dd01ab9a14670027628c072b3688d3f2d7f51a0978d5fe12d4c29cbea0251f81e20d7d1b0eeb4b5317fa072cd42541e14b36d993e06852c748496ff8f4e04bcf53c5e6ad6f5fd1fcaf932224900dd09f4cbe0db501d05e05d1532adef3fb401f46ec35300d80f2df4bdde720a456b7dbf3f28a4e74f69a5f4dac270cdc90eb01d98336478060ba59a033a20376d5ee2580ba9bacbee7f1683a3c735b1a7f130506b1a81a449ea4b570988467bb9570443cfe8f4dd91e43eeb76e681d130e010f8cad1b1c758fbe1629dce72c1e34a4ac14f64753ac511925919352b67b4116322aa8351eab1c5ab30794c4621df39782399e1f6e607f2b80500a43370fa18e312999829a6f920e54f6ea272519bff736a6a370d895e5bac00c8d378bbf71a00e562db84ed24c6d2709cba582f841bb388b48f8eff6b77b98af7f77d510cc23d75c375c5ec93946afd0673276e1da793ae3879bd926db776397e5339d0ea0d70b6358727c6aa0b3bb7e5bb6333a8c67a314bf2cc966cd4b24a44f8dcba721ff64294716bf50701ba233c002dad58d3dc7903129ddab59cf0cdf8fb3253f0547dae8ce05a2708d11eba00b672cbc13b2d5aa5d76d3e02da222329aac6cd219538e44f400fbdc92946336bd4727fe4dbf7f4388b668b9c3a2ece19f1b0cd00dc3c43d1c26b2b99315ec9614654a3178cc177c57aca3346ff8f731c64b46e1f63d647964a1ca6b1aa89c76c4b2178e58af75ace9c916d5db53332799edb825d67c997b332b6d045e7637d19d57269e7a7b29824b042647c26f4db490defb2b0791633dc5806d1ad1d19ff4ccc1a92ab60aaa50c96f8ac002717cbdf1c56d8afbc7a964164f13ab00f05ace28f722eb86a873a0a00bc9ba0fc253eba9bb38781d845879cae62fa6ca0d984250372744993e73a481549d62f893469e1586017a2a7885276647e9a25081c7f20c162d838d3278e5d8aad886bc85130d30c099a546a2397c512c681913f4a03f78572aa5707e0822ec9085ade4a4ab8993a447c14600f3d3d6f8133a3c5b53a2c9025edeac513b1d1090a9e7db2c5ce09cfc71bbbe2dd1a85d2a176458375661ba372d9678630ac366a549dcc8a68282fecb42b6db28460fa090ea53742c67dd3c9728ae17804edd934c171183dfe2f2223207b42f65f96d185ec6d2adf76fb4b9b7286e5857c2ced64d207efadf5082325b677cb0a14f308b4c486c650aaad7dd87228fa1cbfb90973651be5edf1ac281543aaa81cab584712d1b586da02c94d7c72921f0f8141a7e89b2cdf582eeb0a3e68cdb311a9f204843020ddb948292fa872380d8d18d0c082b9443d5ba5c45366efee596dbdb30586235ecc005231c99672fe60788eec50f14270a20d3ad682b86c7f5a7efdd7fd85d072481ce7d408d572cf284d0d9d4d1092a40e9627693077b1c16e5ab97ea1a037643daec2fb1c4472dbbf77090111b29c9449a8400fa6e9d6d53508f33bae80962196d9e1e0c6ae3c3139e48c26c278e8b9388ba03d01a00ea0a0726f0e180a80861eda68262c9b7278c2245622495a9af0ffc6e7b7a3d74facbf9c92dca0b3adaa75d3fcb70f077219eac59251843c589eeae2c3cfb62823461df7cdb1fd3e9627300ef1ecbf6272ec6cc8034736a0963b18c1bb6aab918049b24481af5434b12a5481259b127172c130118a6987aacc9315a68330d7ca6e6eee89e8bb048f0e62746534cf542b72c40f6c2712730c716c6215177939c10a54aeee2dae61e2880fe1aa3036fe9072cc8ddf90b321d331e2507dfc36c65a166efdf7d9720fe43ec77bbec8aafc7072a9287de339778a486f31997e18bb03470114bfae566985c07ce575cb788062726baab59f2fcc5bcbb6d863f0e3b2bc64e027b4a8918f36146b9238260c0b9272411b8331ff1d4c8e045d4cdc75d628d25d61085f06b90ea4ee192054af73551e782f3a95ffc75feff693de7b7bf9b5130921d111a1dabe252838a15cbf012f725d0ddcf8778de40c7b8f24ca1ad071a0bcc32e45dadc9850caf4b37244981615de9333e7980a39d6eb4ed78564abb7bbf82a8a2b465c5f07f88dbf446177744681cc90a2f56a1a7d36ce690cf83e4c35164d24f99b253d62d21df6d2baad6c340a5e5dfab601b164074f2b4ae2a2a8436d9d8601b1dff0f88216e40636018d5818c4c191ec1c511c6fa755b17c88751b5a654086333bbda8b26c1b581a490656e06c04fb1d46f759c824fe79def4c7d8f62e953915d8fc9c45f05387c3f050586d09c12d9f11ed1bc6865da0e675ba8523969396d845e365979c3c2ecaebf10d6ac8237f88b1ca7bcb13d4d069a665b4ac14778fe5e6a9f1f98cf5e9dc03b92e787ee789070a392360beb26aa6982515d747676ee6dd8f415c2d4d1cd3a09a35655facdd13f9ec482b512b795494b8f56e6de1bb4e5b4af95cadf50c33acc540345a857ad9125f6942251e35be4422ea9317e9797b3ed3d760432946271df5729b8cdf003f8f40c4c7861c847c1787824c46f524da7cee574fafb5e6919d3f02486f5204ad3cfbae9b2930daf1364ebb8f26bcae25f3076685265fa9e77bf14cc7e7287c64dea24df9e8486af9cd56577be2e05f79bea5329c006fdfb439251c3e16b826a28a66fa5468a6c857ae02025d63c8d94a8136b309a84ea2cf583e72b5a07af290ceee70e4ef2d2336190810223f5225110ca7b77ab18abaeb2b6d728744abd87c8651151cae692ce1174765747715020328cbc6a77ae9c81fe002725d860df5a355b803bf08f7d9bf5874accedda8536ab628517cacf868b05c20722b13d6302fe53557908d9e6e43738eb9fbdf202ca7b16458562bd34f18d87672d4984eb41dacdbc19be3f0438a2d3db6656139bd3849d5e447ff6bf5730b1572ca0b281ec6a37d7d87930839c69bbb5da0056e65a1e24aaf8c7816aa548e7272d0564941b74aa30836949830d476d246ed17f339beb2274ca9a9e875d91a7372352b8246c66b06e6726573405b6b0422146a4f7c892a316bcbc7f900f45805722cbe4857945833407fd17bb43d44cdf228bc4858844c287ae3d5730a3d47014d1bcd66a7d7c04d076a7282641cb63c581ad2d865c97d17dd820f72d78abe5c725bc02356c87ad1386424061b15ecb66f3259a74c32058862cbeebee7a493870f177906622e734cd1ef12e49b9d4b071d5d04d8dc6f8572577a78aee07bef450c59134d838749b01902e332fbc006fefe3416377d903140b9116c55098e7c487288fb632c12c337f9844a1c8b25b9815eb045a1b3ad9a62644e3448de9d66f64459031b5733af8b3c3730111792f1ecb6762a61a8964a8554be2c1bb60b00fe3b9a765ac24758c2566a585d7ec4405a8dbfbe7e4044a939ccdb8dc6f2216a990bb85b130d6aed653013f08ccd786fa7513aae62b6cca1743dccc5047d1b7e7c72558ec4db12392991afa1d65ee12f717452c27b60406d1f71c4d91b9456ebef2d2f37f34bbb9ad300f0313124e573df3ff30a12f9651463d3aa4155cbbf4a525fa0cb65912f1e094ddf227015daea1267b53ae48872b7f48a91ed5d4a848e5072512d3a21aefb07d23f5493c8ef84b9d4769e8c841a7893d022c21225866fa172fd9330109e9707a54a19af1fde53e3d8de1c214d0d63184da4b946ba2b607c24bf9ee982f983ff5044e2d379138183b0d66d0d1c90ab3dc6071ca037a92866722d87f3c5fc16214ce7ca43f9d75c2280cbe4b2fe92c816683a2314f494251572b6d4ae577c140aea328d8a5c87a0573b970bcc972ef91bbe9b9f6525d922d772c6b3ef72c36eb66713e43d983bbf6dedc78b34a5e3ce3fdcab0277952b9c417204e9c256045e62a87ece2eb919f8be323a4b3b98bb1d99da57ba8c60eed01372c002d95859b4cb49c675be46e7e2c7e8ff84c162c924174309c5357839fb3248af74defdcd5379372c4496fc3effb9dca17768874d46cc0da08eac280990117212b6969b2aa1d0a4469a0f82a88805a8f9590d91554585faba27b1b45c82665c90f1acbbae57d1d8db3dfc58464e828d86bd9f270c23db8b2d4925e78a236f0c2e5bd4096cf9055d1fcc83b61ed3e2fc3bc6a060e7e5dc952dc002dfd1c374726524ebd4e0a4d736c58036ae999e924d40d21b0d45dfcf258478251b99a14a72cac95cc9a85d588d04273cd848cf05b20ddddaa552d6211fb21fbdb0d00d1a727a501645e91b424ea735cf394bc1025928196eaffc052bb88e924a0d896a7372f4b3346c46b9efd4e74a2bfa68d7c4a5803745e12375efe550a7d107dd5c87466395cf0a68a9f50e333f78fd7b042e709a9c13b7aad6c94415522ebd44f3bb5c8e98e81fdeeec8ec01b95e4873feae6b88b3bdf1d4e21cc70fe0472a9d118d7272f54fe726ba1758dc5fc2af6f4b8a9da9f0d2de8f69cf01bc320e2a3260605ff47a06077a57e5c057817a77c37ceb749751b0e42007cd39ce5bf9ce5a4f002da03e15ab74cdbbb654bf3ceb32d12807e114b1072dcc2d8bd484c760b5b4d84ba35a5bb6f06ca75ee22948b1195bb84e63ecd00282c747b0963566c4da0c41720e6430b9e92d7b1ee48ab2c1e7af64ad13431a95d2ea79e4b63f6c7fc51f616751135c64e4b53b20a2cc14bcf5af734503367f68d74bb51add88d1320ed21d7208fdab2769fa053de229debb22b4f8f4a61ad069e93562f5afe8ddb4c6226a72cd2020984bf72df6d54b18c23a3634e3afb04310e63a4b19348fd820824e0a72ce65410c50dc85b997eabeec555bba59a700245cc532fcdd6cb91590ee8df972b5bd5d3dde302443fb13c1ccb609a9946ff78dd2171481f96b2af27fe0bbd72b0623fff13264b5fd5621b779e105703de73ff7005eb5bd33e2108fd7279f713666b9bfd29ad1ce058917c46e24725fd28fc0e2626e67397354f5ce93c52d85720d76d060ef0b4b3e39dd2f7210800acf2867f7d46d6ab3d74d9ec8e42503945d1894e8961994db6ec0cecee2fb859d2f19e01fc5580ce1825f4562aa1d710872006b6ad537fec494a2ee20e2b6945201c42d5f44950f3e8629b5b6116361da67cdab8c76257ef3ab6bd7f2789bec35dd8cad9ecb5eea3dd594f5d409cbb5b372245a4369aa1846bff0634108a1c2f70b6e9910f062ec28f1a47eb92e6de65c723842f91c209f4d55a903104fb0a59ac94e9c7dc980729ab5ee3c7ff99861bd29c8ed02bf02034440e23406785dc7ecba01029a82d1e42d5b8f20a7b1383ed4677c8b2d5b027aa0a47d0802571d7bb9a132b9398653566bc44b34071b53ff6772a38fe96528ac760a61ac22f21114e219f00dbcb1395252bf313672f581cbfa72755b0b9593564941310a36a0dce9ea1ee1873f201bc2ddce3f56fb88ec310b38d92a63a16fe160f1210c419285ce21959f4f958e6c042c796674e53bd9743f7158fa7d942ccdef171351635f09e0647fc4e9c81940e3e18c5b43487b073afe727cd552ce8efdb8c2fe14ab87bd8d8f763f219bb7dd9bf5adfd21368221b05e728998a1d03d54808e10498ec9870e65e3b061a6c068df6f17540d20578658b3725f841cf3f4abed44528ae01869fd1a3c6ea15d36fb91f69763307381785b0172821d80777156bee7672575a091ef1d3df0290d93ada836aa0a321491ff2d8c72aa609d4c8abc484eb60ce94fc4cdd0c0e035f1e5d3fee128207ab37160b64113396ba146472f55f82e1c3d6d4483669696a7cf6bc23b1e3382303b1e04b9457240a3bde277476f0db443c2ea10fe96024fac64d426a29210b6bf8e13a9972b7297bffd0d4e241c7b910f67614dfe56a23baa9aa5a6d913cc2f302138c9e1fa72424e8b0f984fc37b21dcb8e464d0814c22c39aea992742078c877b17ad1a0f7238266454ff7b20494159c010f53a6b2bb3258246f0ddd8e1324eac2efc293210a06dc3a17d5695e863ef5617c17127545139b8830ee7bbf925ecb62d038dd0726e3e9bda1dc609f19c3960f7a332eb76a6cb188a365e9f71c39ce0d5a832b54677689661f568418cac831f72115e1a5b4c22b0240f9e06c0c4f93678a7e61f72a05e9c6a1e63a4460bcb7cd2fe0fae8245aa12ad6598a4b575808f17fea63820f3b2604b6c222f77aa6122250db20131f74de3ab42fbbf49f4083c5cca261b64ea52ea410e70698f1af93eac6ee1544738316c145a360fae40c1141c07ac216a5fc6e4b9d7f14ea38bbb31fde49493776879943b7a0dd51b73c5047826f27d72d0e2d0a5c885c80747d9326f002575b879fdd9ae77abcaba5720b092deac2b30d829c81470ff13a99987329b4715fe3709bf11abacf97bc711fec2b5751edb720e3af5f54a84c13e1db95756b6cc9bcc4c3d2a52611787d7f8f3897ec2432e7297d8154994314ac6bbdf166b5c33b3861867c11362735a770ce701c1f8d9d97294a1d608a4a51ac541037ddb786fb19d87f900cceac3707d92c2bfa13a5cb8687e1c2dfdf9bac5fc75b3074d9eb0c1acb2e35fdbbf09929cbcd9e34584151c72c5c49a866ec5c68a5d58bd1f0f5970f2ae30c02dd81dac1397f436abee9ab669069393512385db16be321398228fec2444c47408d23464c7220bfdecc3af88722f3982ce6adbd9cbe5c39edd0740d8b21708c7dcbb1521ab2b7008e430160d72109737d4b9a0ba42fe5a50dea07c307ef7c41b463056cb3816eaa242e8db705b2826441733a3bb0b996d5ee798631e133d3d861e9ed932864e9feaea45c4d47245dd53cb9213c47c13bbcbd0fd80467014de8cdc82ba69a24e1d8273fb000472303fc4b0a0314e23b5ed23193e03ff70417e7c553432fef0cb310fca363d7d7289914a69e32405f9697d5a8065b614adf8bb02f81be8fe5e2e564876576d9732247276a759d31966a6981446be9f1c9d8d81da79362f8f545609c64eb08bf21c29b0915f3bce2f4ac39ca6df02af8d234b787c2594b05cdf2eba58799337f93e07dad2d0f34ce3f4ad80d7deed895eee036a5ff409ff34fffec14f8d19546e30691ea99cbf02ee30febb93662363b28de1188782adcc95f0c22b636dddf67e666221dc413916fb841da70dc17573ec4ed660da98ec44159f2cf8ab94f0caa83915857c07579d0abe03b91ca66f3c983edc55cfaec7d0d2051b2f79e68fa5ab7253d00250583b24faaf27ec186ff4e2dd8aeb3e48c51735711a926d65795297728d7cad8c054c5ba270dcb86fafffa85a3556fd30000f827d2ebadf95907883728ab01d4a8310fe2daabad234475be21c5514062c30a7325bc77254c08e1cc708da0d042f0f85fd8ee6524dd0e26994e5e14a277eb859b968056e8c851a4dc545fd7e0e2d86b2433349eda3b69d400398a7d773197b5daeccb97a445cf7a7f84db661688cc0bb49795d2d7b9bd434c688bea3bf76115c5988ae6a505aac32b500b5bd62c5d0987d28d28f6c5e92ce75cc6779040075664a6e54f225c57c88ce3ad3fd02875e098bf66c660c225cf7702e94e8acd338c37a0015d1b3aa0a3184624d938192ccdb430a76eb1e601ad5eb73d7b3cce2ffc4ece65002b6eef564c3725634e1e6c0bcfdbf92eec1c8cdafff2755ac86fbbff3a5af8a22a2a1fef1f0722a2a5fc4c5b70f19de68d4542927e45f986e8d358876d03db9935d8d5975e8721361932b2805cc7629262630ecf650ee8160edde54b371c682945dff263fa472be3748dbee7e3dc039c4ca61472650837025ad51fada588c09e4ab9d3850bb72e7261dd268dabbeed881dcb42e888ff1b9d64662106becba436cfcc5d84ed61e2890c7f9a177ad471a2876eb595c6b60b4fd68a800b8a41b772ef701cadefd107a4b8f4e8195dd85618cf495bee2ed0ce17e3464ae102625dc1c24c01bb96f64405113caf51113279d4b407dc15e352e39c49dad1f9e43dfa71b56fd64679b63f0ae28ccce2e97830f7c49efda9271a5bcb75aa60ec26013c0098fc94c0f676f56364e5086b50ff283a3d38e811c73e5e9f7d4cc75894347cd4dce5c19cebc396b096f1022b101ea65898ca11138ba5b684b6229eeb3b0240f2e13e7156c1f727f87dcd91fe16f584d238132138bd725e77ce0d711e9a8c5794e579520a61f72f1c7c888c3e57ae3705c76c136ca7a0bcf271480fc2b3b288497f5f74bd9d92cfcfbec45903bea426d2d191a4f6b63c8085d2b7395d64d591e7ad327f828413067f981639013220ac4d56a85c8039680a4bab98d057c663b8cb5fc5b55b16849d118a4ceffdcdf35b15fdead72dea3b3d45899ce30b9ab36dfd50f11616fa0726151429703ca38644b3e864a80c402cd50deb1c11ba05117c5c438a1fa8ada720d681aac23929d65f2a7f02359764ca96fb77e252cf11e253832b9511b8812483afe1de33bde283ac24f17cd51118a747fcd874c30f65c6a3587495c3ebce471af4165b11c82b58eafa70e1d377a55d67e4688720e22baab691c1e724d8b230711d0bdeb5c3868cbbf3e2196ee12264bba5fe3ce740623a5b2b2617e3333603d2c2d2f8e313d1c152756c420023b74190bc58f394aa5bd9966bac1bab0f6e572c68e1f23b735b9ea0c076f3b8e0cc98e0b362dc54710dc5c43a730acf7c2eb5d4f779f052969fccf59b789b6e009c5b5464768c11817fdf93f7bc3145a023b72433083db09522b3e52a271ab425aa4ff1c84966b63147a50719ef5448969ce2b7035947c49b7cf2096e677f77b4e1516f93a7e7a77a18d3bff713b88116ff9727e3c2acbe240ffcf7928b65ee8287e7a14a047e5aea84d614318f8710361860636769a4d80561c135743fcbf3e59fc684a67bd8b6a2c16ca074a8dc152ffb751f6ba46db0e403e4127dcb4633388ab036525a0b6203adf1da5618c73c7737f7222b42b88c16aa78677a1739a8f526120f2eda5e7a597b5911893579edaf3101721f9dbe21655e1fe6d50fa5883b55c8c6d4236dca1fa0b769cc6efcbb5030c725046a35880c08acf5b8b490a8d154e7f150547f53c206529cd9076d1ccf505552642bcd3c5cf99f1b214c587456829f5d2b1dccb8d4df4bb728bd66879bfae726473badfb6432d25ae7cf5f3e5a8b981708f9b8183fb26d85a5bf8a4b37b0234793c0fb1fca6354b4a81838e298dcb0f9febe0968dc2ae4ea614bc4a73032072b44c8478707bdda69d4ac6088c37821ef6e839e88f08a55d14b5155174b670726135302e6096ef0f071feae4e69737367dee10da50471b3dede516efbef15a72abbf586e7f684e91e57177e5047b5666a7cdda253b68d6dd59b9c9a3dd70c15744f173bb0601dfb95a231286c1ac68514ecc97f3dda632f58006051b000cc151922afc5e4be869ab7a0c73d0bd19dbe3723028d4c4230100e4667a16bd47dc729c9b76347227e9b83f716d18ceffa592502a468436c8615f63a517b40318c972974f1edbb3a39f2c543df8a85c760870d4f9e1558f20f86f25c261494de5b82c272e2af5c0e34e0096bc6791b12b5cbf2434308ac33ec2a2f110253fd2f5f47237dd25f8d4a187a3712d33cb0476b2c1985ff53f7eed825d7167c76d067b6757427cf7ab26081a8b61661367144635ed7e7672039ea3546ecb94de0ad16c2972bd62720b6892119a9f69f05bfb8d99d99b7359fb550b390030f4a528a2dba159894f10eecbc7e133c923ad6a89630beab49022b0e3c09cf2ce8902b1b4b87572f86761d6c636ed852d6b3f8ba77bdc7ea14e23cc3f66932defa04b013ed5c71dc87efc13c2f4be768e58a599c6faa8548a79155c8fbc2142bca38fe2486ec272764b05e35ffbf9bfe85148de67b9175bccf7eccaf16640b21faff0e40d39a74e1646ccbb4705ed7b69cb8b0a4f4ca9d8c60884e4eaa46c4d914ff08a8ea41c72d2c215c57b7146408e8e87103c9cf96a54e5545b0de88908feae11f415a55972013e682f8025f2f1eca1e90f37a275b375e1757bcebacd06a72196d7037dae72057636b5ecf28697d295a7ee8c88735fbe372f255147b22a25a76515c28b8172b2d0ca10c29653180125918af0822ec598d77bb533a1d0cd9cd4482df09e020a7803ef0108cb228ede6227fff493fc5fd9a032a18b2d57ced048c9429ddd1d72a37c2551dc7b6a38f290fff1a730fa460536c57ebe7d6d8ac1d332991533a4721ca1a2fb03f3aaab30e1f7f4ed2f2e9326954da2449aa9847bd448a028e803727f3c51b7b3545cff603984d942e7d4196598456a81d79eaa30e5fda1a63f3523f8b9d2943363e906592c780846679388c5b96474d5556bb26fdcbe3703398b56741d46934d534a2260f1d5838190e1b5b09ec061bd415932d85bb9b2c345e33bab7257b69634af809d1a9c2a7b099ac55dca07acd2f370fd46cbe580e5412472c85f1b36e7b0abbc4d75b90b378008095014f898bc48e2e0dc686d8ab0f0944963ad3f9c63497908dca8a336e66d4b338f0fa9465e7c28c476ef5b4a0498b913c20a47644077ea0da1c25ee40106d6d55ea68860d369737ec03f5fade3e43b72c183fa5f5b9f530a0150ee40ed4b0032b117bde6f38b892ecbfb79334ada7c23a79ff68c70ee55292543064b4ebc50d39214b5b9e8df244716f46d20ca291d6656e22f65b2de40dfbbd635f1fe056960ff663dab1821d522ec78b55083b58c7255ee050bf0dc7c9bbe45e71ff669e6352adc298093f9c5a911bc3dfc1eca5522ab75b1e5d27a16946b5914195810be365439eee52885c74aecf3b5f5ec5106315fe67239ff2d68375dfc1a0da5dd4bff33c4386d58a720c8d76cae04ee27e17288067df457b54526b7d9845b06bcb51d9db1777dcbd35398fda5a0883f8046729e8dc66370ad8a900e265fb253bfc7695774a4730d9d7cbbe5fcb8100bbf1f7269f11b850b0920f8cc82a368ef25ab9e8a839bcf5714f6a21b21963dc76ce372a1b9c657aba57d754dfabcbdf75482a64fb7cce369b0bcdf8f5278aaa7e8ec13f4e3dc690a33880e05b89173d510ea11da2be21de367e92f7175b45f02fb0b7263bc3dcbc65c8bfb15c38a9d6bd97613369c2fce7e853504a86666bec68ef921c6f0bef3b0b40d1df02f023cd3c641cd24674aa8e015e2e7d2f32b865baa4e1a87cbe4a50bf9eff0d100da6c7a3c88d87005e1aa2fcf91f03585d1343f779d7210f201387d5a58e546a057fb63a2ef2bcd488a6f93f7c730b3abbadf5cf1842cce05d468bd41ef62f74896e6947ed891e3cdc9dad4b9969ade3f710a5a68305804c85c1f6a35d54de2ad4624188805679991811743d11390837cf792a03a19728a056d07ab0830e08d3f257f0f4d96aab27673c79869e8cb1a9859c24a3e2819931e26f4ba7b4de623914e7dda8f77d175aeea78a39d45c34d2d73433eda8172a0c624c0ef2ed5c43650da3a70ee1ab7cc14fd5aeb99d09caf513022772b55728b165ca4f31cfa85e2b0eab4dad8227496be38131fb9aad677856188ff1e1e721b73d4b01aca2a312bec37f39b37df6a577c25d37f9da4b63daf1823978cc672ee648ca87d3c358a88eae1fbe726109fcca45d1d3da0cb02fe9dbfd934ac87314fdfabf33b42d608a9d92d5ced56b1c15aa113e1357892ae886b7ed52ffc2e72ef5bb22be2ca91080aa7fa910ac325582bdcc413e34075a63199ce4b32592c722e7e18ea8fb4f457591fa1e4cae40593bb4c92107f2664ffb38ab384047242723f8dc4a80066dff3910ddf2ad13d07187a7e6cb888191084ea4a83dfce90cb72719a48f83e3af21ba9ec62a5451556c25fe3c65258135d4242e9778e44f03811a3f640a9c77452c5aeb1da8b10d803a7c57ed7489c05d1e80e3b5f7c860dec3d7d718145577b782d6ffc740740024005703d9558330db4814832321445bf2c72736f149f417001efbba7e8198154686966068c9f4187cae7df26b26414ebb0082424a78cd2490c8bc0412b9a3fa1339efca11e940c74a48e52a2888c22c55272169c3a9d5024e0f5073752299395f819e32eb36b44edb38cc9a1890ee23fe472f2d3d95a8387625e9a1e703cbd482c2598d64c5bdb3c50e3a2d5c56740b5a411c56e9294a00874315520c65172e98ecbe16946240d610eb8f12529343cd80972cd707b01bca9cbec0a8bc1c39d774c98145e394888f05ca8170f3e61f2199c091965a941c0dda9b2a29a292d4b5a3ce34af219446e2b8c9b80a451be7d16114c449c8bd96708892a7aa63312062378fde1f44cbc1ae8bc252279b79a98257672300616e5b64995f9d6362e8746fca7e04b6b374a0d48694b7da3821fe8f99972a50d259ffdecfddc045304544711db33faf6d957e991d43c1c0326c776819772f61e98c83de9f6f9371751b5dcf740b0adb69f9247e44642e19af609fe2c5a17d0c3521fa5921a62e7961a48192c6d7d4926868d5e1c094d610cfae4c41bc072e8f2cb40bcf9f95ef162b2140ffcdbc2157f551f1cbc75311abeca7fa870644748fb6702b9e896e3aae1f0a82161ba34e023486158e7ea4fa34bc1438cfe5b72b0146430f690ca0110b2885d50b9589581a19092518bd8167067f40b06fc60724bcd8deb6c999133b8ee57b6e040ea138e92c922e10fd749ac89f47699123a5ba138578b60ecc6799030c7f6f920622785d534fb083873ab5b83f47458b6ad72cb77423ce409f6841936009fdab9991bc5f409c412c30d9a4a6012d586a9ce1d6ed90374c2c538e231ece60c2d6667234bed89cc863afe8fdcae68f52a5fcb72262f8f71f95a3281f5419deb824b1735b6523667dffea9aaccaeaf955d033c7278891acceb9474b98bc7c813135be66afe18dd88d3b68f2949021063d4737a72819f8838e2cf4d225ef67bff05a8efab7ae84225ec558b06af55b4e281b8a372836b29500c4cc10e3045dd35d296f8de916378a967d8e37cf97f76e6ee29767229090b14907eb81e76e3ecebd50507341f9cde4c8cc97b3fc87aa852d2dc8872a7b235c5aa18c838f6cf86c7ae8305b62043031f8af28100166fb912520fda13d810ddfd6eab6e46f5c425cbe4d79daa59e1e8b4e4e5c836865879bbae591c0b56318bc5a1a96e558e5f55f423aba138e7f6f50aacaf864d0fa2b3b571906372780b15db6b1fae27d5f63c4549708dff09f9ef9c0bce894b168898df2bf9ed623f316f9c69e2c2409ac93dcdc4e8c71d0382bb613ca391616ad60315b7f7277268c071b3d86d421f4d2e5225c6b8b96e5f8d17c8d82ed3bab96fc3a28b978272f0fd52ab9506997b7cdf7725f1f65a93380f84b76478040389723d60c4ce640ebcbf6f978f6860cdf662e4c67977764b489fd9f1451eb153ff4aa150a2fdaf7290a2b8f0dcb6757d30cf17221809e2f3da569835b301b39234091440c197e87280554e56297f7afb7442dc4605af3544f29b13778ff65a53cd49e7df184bf072e607bd351d23e0430ccbfa04ae749bcdb410866d2e716f03ad713e7a847c787266dfb94f943f54b45522ce0398a670ed808da22d21da2e338a5c230c277df772c8b8f4b0c0c9defc84dfadeb076ba0ff57e225e779c3f088b28d8e9df08062724bc573cf50dd9eb2d57cb45be02ba71dba18f858634fbd41737db2b26ce007727dff84d7c635ad29ab034774820fa2af1ad5a3a681147f17354b0d54158da0397317393a87512d20295fff1d050d2808a9be1eb2e5edff6a4416d3980d2a427247b131ea044442b3db343bc9c31bf321a68c2abe588af2c164430b8348e71b71a4f5d413aa75788e0aacb9737f75f0155b174ac3f5c13f41ea351a2991f83213ac637f2112798c5aba94b2b8de2c3335b3400abd419d8bae6f0af40a7c31c41bd50f881c809258dd596a1fea1fb8cb5cc0c29095f31e1047e47aecae46807967d4db2c5a0b9d2d8e0b52007a826e96b8d08bf7428ecd652ff0d1a32a4177f17254df57980729cb0453ec461e933b5cdc407c14a807f5fd8c9f63bb8dd5557a2d576bbaf509a297136b42bf74162b0bba7932fcb770ed6d5d32f93bcfa1f30b00968422dc82156e657848af9c743f9d01498523546fad6a0398995b34f083bf4360a92547614c54f5bec5ce51a66d5de3159fe43790d4fa22aa1a45f4e3a81739ee04e91fe22f1492beef4cbb78c3529616c93b6d18929e315ca0e3d3d5cad9485fca291a0f26b85d827bbfe23039f1fa0aa719a4bef5944a1637908dc8989172c89120adfe32a1da2562e362e87d8c58dd6dd6cfaf79107005c8323070b44e7263e21a882a9a908c62c57f57a6c8937c1fd2335b5a894c78d1d863b53ae3b372da097e0e24c51faeed4b92621968156ed291e9a64b8292f834c6ea9d02262763316fdc1097c71fbcf491400a9814dbbe6b458ddb0062d69cfddffe316fea6848454e695c6e0d4c37ce50b5bd34757d9452729925d4748263e8f574137bc21b72a51056382d1d7cb194f0bfe41d411b722f0133c3bf3d6f2b5e34eab7df33c57207982087999f1cd6e97115316fec645be7c2ed49286ae6888404d629ea4b15724b2ff0469edf5c060c01cdb7df374ca928a264b8a9c2c4c2fbbb637d2bc249726011ea3259e6bb32e056cf62c4608a79412f781518be789642046c82117bed62349fa215c615e3a17a55a1efacd2ecfa7cec1f79de48dd16349fc57c84d9ca72ef74090a30e1dc88816da65c57ee3ca9d9e49392e42cc36c3d172000f22c90726deb3785c8d0d065e24ef53cae1fb4765f8cbcae5f1d98dbd8df33e492154772add17403dcc09e2635cb87e7c55c4fc02e67134e1e84b8f4e9a5cf481d34d16f9d3cc323b5be92fd6342a914c70446a6b3b66350493d5ce58746c051c50600723826e38649bd2165fea95e67efadfeb46f343498c3e6baafea715108f6f97033d2badba8b94620924ee1a93fac4efd725c2043791559f049abd0f11b2e8b246c93dcfdb10d29e0f798970acefe14da9dd6729f85ea53cd422296e890029eec72e2bdcf40b89aba0430e4877483e80348e872bce13d2037c334f0a055e4620d727a09eba2f28400727e9835b5aa648c502c4d88debc3f72bf57d4eb8dec4d872508fcc8f94ae52c40ecebfdc6f255ea3914af315f94981c49420012a8fe38eb21a5af72451a5c904766c0db5cda994efad24a185188360c53e68f59db0b4bc641b40f74bc76409fba052a0ddb1c3565aaf25181bebdf8cbc100b2e7cd691e4d7222e4db556c697a8f80354ba10e04a32a7bec4c2648b98d850fa1245336179172245fc43c86dcc354bec8d1514c88ac5bb067bfb4569db91d72ef604a9d74d2729b3b55289f714553a1e60f5125ac4db3b8603e85fc13548db89fb2228e0e4713629bd2663105ed2eaa0fec648bb9c349712b95cb55a5a1972d6e97421d7e0a5ef383b3fef5fb8336e458170d01f5f3fa6140018b646bf6b9ae6208ffab22617295ba69454b1efac9d67dfcefb48cdc7c8ec9a2cb86a3976e61c716716db1e317d101fe2d6bf4cb42abb0473db74a128be75e907ad40ed16e72614565855ab7572b7c2802ec4c5e0caf8ce9eb7b33552d41f75bb51d77115486fe9558a642c147a2276cbb7ed66cd25fe5b4628e8c8e17d337bf320add550dec249f19b7ee91624f5308813695276f4ab847220f3bc6c7c180794386fa00d38f8c11975e41d57249cd3996458cfbb42b465e12c54955e7af4c1e1ef19616a36ee41f5894653572d796e72d778a63aff08180a14fad6b085cba8291ad0b9c763a610faab7f01072fd2bfde924b841f5620e08b2362ae80e62fc805a6c9dca0a8e02dae114bd432ae183ea4e849ee14bd1764b49a078051ac31ea83029ba6ddd6e4ae6fe963b390fdf6a9052af4304c0a7dd903c3da390d23d45f2fb2d85099d809c131ed4acb10cd2f1efb908c5e073cb0591ddad499ad10610d8fdd885d3f96d7d247fbd199972e8fd197563feab16c9fb6c66512814a900797ad96f7d8e9f8a61e13467c865721d3ad058718d90b45b1bae641f3ffcce5870be552a7488f708334d609e7701723a2c85528342ac01c9415295694734e4ce53db4f992510c7fb634c6f75437172e25f661f4e8e4fa9e744a251481d8f32e2db3373a2522987f23c5335a34cd22d69a28029eea3fa0cb42d2149b93b164017af8045d85864506a93149cfa43de723f283b6a0a294c48187c5a719379ba275afdf773c32edbec623ed89fc0ee9472986e896ba5591e20d812725b4baab50a1fbc32ad3417a7c6c69c9073a7466c00832cf6305ac3c1ac0b153e775e3cb8c3f8ae63ce11b02d3958f85a5f5c530a72ebe439ddffcc1c60dd39c3f2c8eca5676df55701624d6b19ab00ec29a301fd723c7c65742c3053ca5d667ab66f12c9085ee62bdaa11ab07bcc10ef4ea76a53729366669b81a8db4f900db111e86ebaad480676a0aeac939e46a3412ce494a509cca7a94e850733661a004f132da5ca917a75bac0eebc71b909cc9670d23cb972725c9abf23d46d8536fa36612435bfa6a8cda0fc30a9dc0ea2a1e5d1f429673483cc354e9571b666f07e356d506452375c84186ea3d42f5809fa1f20b0f2a372abe98f975faed7982c9882d27c77730cfcace4d3dfc0c705e68b775a3079bf727919ca5e52ac46a8e6d873bf6dd74ef314f5ea394eae4adb5779fb5fc09ad172981d25a2bf8e505c89125dba51c9e97033183f1f63a900812a92b55212a26a0d36ab52fbf5a5e3872ee4a6079a1cb7adebd31fce7064cf7b998f272e53c1d572d283ec8269caa1e6f2a5ea70c1eb77df4bc2e0a85818dd7c9a766ec054fe6264732df1de10f68d6670c33deb1f56a7c83b3e67c2255cffa44c4c2ea11416dd3199dcdab79ab9ed98c553fc98f861fb42235950cc35718be09d0cdb1fc2db12727fa00dbc2316de53fae4dc2189e9b64082def1fdc39d953768731f7851d3dc72b7823971b67b1121bfc807724e5349d12a19c4d4c6e5a06cd0fec890a04ade72ee57433c5787478db4e4575f4791116fe6873bd45014d0ec716b47ac3c6a08724d148f8b9420f350c67afdaf82e0bc8c0a4853577ed8ad13d06b5e4b19283d0e708a76478a5e7899d2427d6203b29f7a7a39ff8e9523872beec0576cee866a727a813b197a2ce73e90bae11f721bc6faeff68be0a9726ecd0578611f2a46366dde67c7636909cf7b1ac39d280b8f2f6d3ee16271480eb21141c92e3e5820b4729efb108f07f139d4114bf7f049dd911e6661d0af0c35b8098793a571e5db4f72a8ad4a84bac834f4e4c8fe8b542069737d29024813bde642ef7dcc9aa815df1ba59aaf35343e38db6a75775f55d189c163fbc1489ad6b3b9e58de4680e7e0446d507f1ee94294286754e52365d0235fe7a0f6c190fa9c6f0e2a2ae584583fa729573d5f1d49f6cd162448b31d94e87e199c9369a58c53135760b16272ea24672292d23f69cfb6f10cafb4b304cbd3198f5202f972c0f526e63538fbee429c54cf85d0fe496228187f698961cad4aba84ba5ea771dac684de2495fbc4fb3d0912726a75ce86c4454711ec064be6849215b9ab29a5a7ef7a64f9b99812c5d32972f4c63c65fc0f3231af86e71feaea9262426e886c2813eb15c5d3dddd67addf7281f410841d5bffeaae1acbf4a4e100c44afa4f37691621130ce9a9505bf4a0727a47c55adaef7a3e280ed3c23fb87630708c53e0b5cf0d9bedfecf669fbfcd72f880a124ddac843d223562bb5ca0badab28ddab995ad67be9c671551458b766052f2c1b008bc66dc3767baa3d49cfc8df74094a1429b9b26dbf10e5d09a6ad721efc514381609019b65b09aee0e5d5b8d2e3f8294c8aa5d27f7445da14439928f4558dc47bc87f40c577d085c4f6c6df3820daf3f638e92d561b29d535e03872b6ef87420e0ee2b2228ec1f3ba3361cc426f4dad8eae9160385573a884bd90721713fa0c770e18c17facf99bc3437edf0cc490c170b13b5c566d18158116db6963ef30e8caa2b2bd48a919b94d5411e759e42ccd392ad6f3a559ebb4f07ac46a1f3e6a589145d092085d330191cb616fc9888c314ba5834b2afd0edaf1229b682b2a9f900b642f6d6e277a4fa5c188d718ccac8b7f2ec7ee73983bebd2cb4c727e2fb25d270deba4708713661022471976e134be22e60078a428b6c86ee66f72d2a1bc06247feb0e421ff35d75b6840265ce8b6e3a87d64987516510a8f64b3dcc7b2c37bc1db1d690a979dc0daa39818830cff4b0a1fe680c2735fde57ac9723065002ad1dc4f2ed90f085aa29e4d39e80bd3dc59421ebb539778aba2113f13849a1feed28ee9d87c6c8510d68f0f5e55752afd4fbcad506233f81801c1f3729562bede7ca8607e71090103d87aa523bf886070fb6ae49a4d31d0dd0c8e3c72cf49e48a02dacdb48f4ef0628f0137062fadf165a7cbbe1fce07d1eacd242b41bbd9175477dfcda2c39f072aa10b9098e006de02865daa579b18c1f26c894572738ebeee45907cd6e8a4c6f029f50e78abf2455d183f97c5b38533b19fe2b7726f6375c21bfbaa19530cf2f28d88d380119ce15295817fbc2ab43d556cc0de72bdbd5b20bafd95d25dfc513e017feedb270fb0043f1d16a10cdbb4aa3263e06ac73692e7cf8f10117b7a93c052c0d86df0b83d8b458914bb1216b8912d1b3972aee882e85f467ab59c17db97870107ed2dac43f25575d147f3a5c3398bafd0395fae39d8720f34248ceada013e22790ba82fac5c4fe0800a81ee8c71f136b572ee32755332d134132d8b435689c1763a4383d44b24bc7f94ad44c59d13ac0072686722f9bd4d56b17faed21d5c585783e940beb603392401cc35b7389b322672c95217a646b0ebf0a7377374470baa0f8f491a5c1bba19bb4a7935532d32180cfb216b5f9026743fd8a80b7293b44f279c08c350d19f02d579b8d85f93e66972006d2da7b70ec5504bfaabd13b55497603960c3071b9228fe407482b2859fb0152b5e0bedc8859f00bca662b102daecca4f88365c8cfb765e5eca3d2b08c8a156b557811486ff94ab1dac70b974b3b32a63da457679306ad757ceb9fe87ce87255241a9944f3e634228802fcf1c9415f4d782469c7afe5233ea58587b4b9967218fdaef3abd4b46546e957985ffd0cfd02c95647887def68275e153855178d72aceb49964ce3cbce5bdae5ccab11e7642675e1be6362de9b83ad28c9ccd0a230a476d7c4ec274f699692fa869b6673c5ca6b6a928a74793881136c058129f272ce567c3b9f36733a3eb220e8523b87acd264821159dd9320180b102516df03721d42f225a73cd511fc001262ceeb984bc4a1a8d394dbba2b02cb89dd7e5369052f59539f9c01993251e3846b691f56b10274634c8755bbecbc7baa1fa7aa8154a3d354918a328c321a1118fae1d301a5b84c41295fc6c3863d35138e46fb30724e77d7c76e93f2bf069688af63414c0e46d0517a142b16648533c919caf09f72888a5fe60017f35ab5e0322314f81d3c52bb01b3d0c4ad56aeec2cb6e7f43872ab09d7db6a37be5f0077ae26d6bc7ade955490b3eff5ee5fff4e339ea17c1b1ed21bb05165ba08156c907c897cd076424b6889cafd309a8cbddd5818c4b50611e18ff22a4e5370fa9ef2470a90a35772c6b7db6cd481c8086315e34aa8d1611706e0d30e0dcf213a6b72b2f7cb650086f74892d2ffbcf6ce7d3f667fdc879c72162c7ab32f2d882738fee0f1469b1a5f3271058e2e53ab239381aa8a861b5672d15679464f7f13489a2085da0ac72a28f1597efefa6c380259426942e35f331446069441a2ab62008ea801c2d740da4a85f808e0482a23fa4edcc7d858bd525e2180abedf83feffc2b55d60c6585725fb605548657723d1940f3616462087972abfd55b28aefbe696bb84b2a8fc5ebf69153b14e3c398af8946c93a7b91e844ac307946b0920472d53d8fd0169ee07c5ac16888c564f9aa44c530397e8792672b4b1249b19576e106eaeddbc047a5dcbea51f1967a84ac9011f7cff08b50c725c5bc2061d1c47d43901c543682703d90a110b306583952deb2c70f26b5f0b828cf2488918cbb9ff5e1109815386a579a55b763fb8d61c93aa8da4a8b5770b272dd0a68d7debab266fbe846ee96ddb12571f1cf115689d7682a944a0cb73e1572e87ab60ecf3e59f95e56983f865bbad0279a9d21f89b4b9aaa2cf0168d4b2972d4a94d4661c2a1d6654a5add6fed04409996629c6da6b8c61c1f2a59be7228727b6ea9722515c29d61e04ba2f36e6be2306021c1e92d80f4cbd3e1782cb580152a5595c183c1c5b7ba405b562e1ea74173e15605bb16dd7b093fa3d50bceb2594c1522c2a4936732d225b9589792fcdc2fe551a6f92e6cf018da2a91bb2dad72d6749f2c9b0bf9ec99fbb19d38cbe56bbd70e76ae0baf6a1dc9a335e5ee4a27204d7ee24bf798dc51f7b15b801b62c1bd5d3b80badefa75cbebbb6fe9e6e5c17163e8cae2080994bb233b928492188b5105d94495eac95453e39480be933c567d8712a8216da4201ab3c67b64c0377b61c10b7fe618e500d86dcdce5a8e82d7245e94520f717ce606463fb0c81084b09562f17583d9a6f27d8ae5983ed86597210ac2819d0ea359f061d5b4d215841202eb3571f1856b0dab1d253857da337728c52fdbd072735b1016f7f1307542338e8a8e27393e0b98b095037913f667572969d073cc4836eb40fa8c314a95cceb6859a82389b52cd2280fe968fb2278a0f7b65d7997b83beeb82f17b746165e22dd6d7d692feb50feb7ca9aa979fd7b629136663e86db0441802206d2a0b79b6281b4d05aa8e3966bd0208fd97185691036067dcb7c2c8ade12b93218ac40dbc1037748af9d2fae85f22afdcfc3031d54905c81f332dea6412f4af8ffd58d2b5c5c32768d34c49ee49b36d0e9736138559eb41d473d3663dd201619ae0516c6630ce6c4d435c2bb1162b1e9e90e038f614d8ba7eeef8bbcbefed95612bca6a7f8a4ee634982b4e886abde9d289d9892720daefc0fddc31f907ff2adf23dcc9752a512085b2ce2bc90aebb313ee60811272a505a89e4b3fc17ff44ccd3e4125347d1d2569b7f01b9be8c29a754dd7170150c4c3aeb6fccf0636f9baa5b5fc654cdfcf1b22a27cde967e7473639c8926ce72d72fdd4e3e42df805525b60f623ba0aa4d3dd09dace6d20c7cc437cb1628fe72d252d4b3763115e7687c4b92721a5a6f7ed2e57184a808251b7a2e7771ca375de5d685ba1815565b81942a855a5694c0f9f2d85b7d465e44e3d57b51acd10c5bf04079826df2c6ca74c71778d23a8cd9d18d49d56dbd451ab9acbe08dd3e3f6859c1c394e4f861b51e1dc904ef275bce035b6a29e9d2f72037592554096e69722e38386100e1ddef551e9a3e0ea9754441212d57c9f77e60809b23d5cb11c5726d65a615eec927a8136093a8653c74c2144b4e3ee3b6ec85cd8e4f5d3894113465e5d2bb41f9e2c2136084131199242ff2f3692cdf52a80db67dfcd2889d0a0e22e54043f0e826ee2bb39e477cbafcacb232d853b6539de79ba6630082115c310d494cca32b0fe37d67930959fa6e25c6cc2e6b2937af1ccb201de77159c7272f395e8a711736bc889774b2e59f3d3aaff83ac5327313df3afdbe998566f8e72984819427889edb84a1bc7d7d77d914fc855d3e03d8b6e2e4b4191ca7a8c4f7228d2c0a42a67cf3f5b8af8cd454bc201ed6c1d851df0dfaa707e5ff4cf6f7d28743d2100da38ff476f00ec9d1130b26c6477b4122c8c7a372495c069b07193496d2bf9097f5ecb54bdd13c36613347366b4d8265fb5683120f4c0d579b0e7f65b25e470b38f8792ef3aac749452467ebac43c5809c60c2cfc596d4fabe7357368eace7d96b771824d4742477d13c4267973e5570f296d20a7f9ff51bf3d1bb7292b93df361768dc47f4404eb223771688db07e1bce1be02b59d1970b227b9872846d7c3d8e0069cbab2997ad5fd29bcbec4aa2cb8f8affa3d6f383a70507dc55905cc7841ebb7fe2fe4d47e1089ebe63b27a9edf9719c35de78c88470a8eef725fd6c3dab03dbae450122e679bda72ffb308483a0c9b84f2aeeebec348f9627250635ece84d16e13f669655a766dbcdf496f49a611889b54fa0c5216237e36728c51c91f7c1bc1f31fd0e963b66ea8196e83f672fe77e32aa8422312a3b7070cf760172a99f488bb9ff9091f4eaa6bf08beee79de962ab6bdbe3828c8d033e4729d9e63a077a59ae5af17c6082541c49bfaf92df1c205ae2e2e4923cfec65c72a7acf15a93e764eac28aff2aae75866bddf475408319f07541e7f01ee42b087290c501bfc79cad27c388db6e2fd76d5078305e00ca2892b97fda9dc0f8700572a98b0c5b7e6dac97f73ae01a56ef1602b1f6ce4ebd99d748c43565023b457472b4720b6a6bf0be5eb51b05e395cba27695e2104bf211a38e2667dfc5ce8e12721ac6171f8932d365fe6a2b56d12eb38c16266ef8fc0775bd46890417450e5359ad7a0aa91c75de5192cdbb7c33564e5374d4c29f549658522fb53e980d140972c97373e4e2c399f2a46c50f4a5a1e9edc98588af268026dd163877ba7dcb7a72c43c02be24a9a3353e2b695cd745323598f476858185f636b9af83e15fc9ca7202af064ccd7e5292d3d80956b78b0f86c8cb8782a8b3cfab2374d1872379e917d6151ab8cf1fcfaceb23686ee7dc9ac61c5cac4d8e01bb2f5fb7f38932e6022c434c10ed037fa4dddfe09dae3e7b5bf0a5f1f372fc83290fe2b3c2281737f54504d4ecddd02cfaec95c1f208e4829f498303ea938db9f76bbb4d328771e65c1693e90bdfdb4eee1af3d3a42e4b1cb1d143d35dbc7f2a11746ef315e208270c723564ace7076db3fb75b2358e30031735ae30a48650240e1797723b61461f5534f60ddaf093b5116a49153e5d8ebfcfc462127a9e9b39386b7acdc8372b11a25769d2509a2f03cc0f07366b5d19a8cf43f21840fbd54f368bda1b66610a1442118a274f9439bfeff8aa2e46a6b29aac466cab394b01743925f7187b2819309527ca9e59a6fb725b4c6fdbc998f4a1d2605c986e48267cf5c24c6a08357f96d5729b9405d9dd30b9bc76dbd4de243bc6e87b6ce9fdd2c54864a06458ce5f9e1c72a9cc7d32b69ae0ade614c4cffce4063a40cae4a502a892e9694aa4c2e02f4372eb17e9371eb1159847f9db472d5529162a68ccec89a7765c4a13072bf1e2fd7257618a40a3efa9271d55ff7e4156ae832d06aaf749951a184eeecdfc768e086e2f42b1264509cce2684dc9b121655aa0c2aba689372bf9e6129cebcb880b77726e64d14da0bbf697dfe8a8d8cae5e264e13e2ba21703325f7ceb1ee2aeb44a72887837a60ccd844e9655eb7bca946fc581eaf858196798c669ee931533b9f37282cc8b92e7dad3a24156b59e2edb73350c69354edc6f35eb8bb45f0d289118682c8a0f9c3fec8aae68e9ee13ab165cacb1cbb0eba2f32f4d936e2f8ddb8de47266aecb47af518d7a77546350364b603a114b78ef77cff3ee727ffa81f87857712e2044ed6a1f27fd63b5b7194787e14ef56af61aaa35c34c5d84c0096b848a729cf337c5192d9f1d8c38335907697a86c823d4e82e2a9c27b83fe3a44c57a172d3a06cbe6343ed8d6e67ab27b38a1e19ed171f1a022e1a8985da81aade25854aa9867d1785ef3d7331ce467c5e2a57b56696bdcbf8b0942ad526352fc8176872d37f4cab6c95d5afa2ebe88fff77d21fc221b804ca091393af3c7e668b38b8720f22f96666c3e705d0960bd173f664734c3fd65e6e44718bcecdcafb78a9097045756973876ee9c6a4a370a6187d5c180d86f6015ad4a45bf512e1d52122a2075820eed66ed9c6e403c4e45952dff17f9e6da2af913d0756327d303b09ef3b631b11341647b67137262e6e4db222cb1ffae1a0be5c359870e33c5343e6f29972019d96b9313f0e44687449b7581087f4b66dae4d9760538957a2782ae7e9484e5cfd865de042ec15bd67f173136f7ecd9d5bfa7d840506f59c9310c7a4a01e72467bd24bbf094c2ad93725e7f166f969ac6108ed3bfedc76b9b1a98b02fd4a72ea70dc033d134284a440b8aa7645c215e5aef37ed5644e7c4aba4ac04aaeb10d5e6be870f9330dc0544a0cc94a5f161b7daadf0dc5e5dade652d9c6b6c5987725b61bc8bcac8b4e24f96556fe5f50d7a84c6308c72c5a74fffa775eca31d9e1199c6f964bbaf3068188e30144219e0b46e8c609f23f462ff721326946ab60972d09a4d72394224d6b3230af728e0fa6062e74a10782114abd6bd409738177b726f68e35b9b405ce2eba91042c81a27b749f4f8136ed5b1c635d2589354bd38485e01c35ed5534e5ec8c1983d5d208134cbbe75bd530cb9db484e954b5d547372e862dd1df86d3509d6e82d18985c4336c5c0c1647bb01e460d29dd511743f87268648137764bf977c38e69a0429631b8a4674641a27c6e8c0c7a45fa4437aa37f7b00fd7542deac1c8d43577ab4cd5c6025b1a7293b6917edce6d509d83ad913dec063bdae357cf6c1c5ed2ff8bf2f5261b4f83849b20a85a19bebec26ac5472ae058cd224fc329ea1b5873122f67fb98ba99991edca3fcb29d9c24193732c07d6330db7bd07e9a7b5fa239a847558da1ab1995262c3897af6c6d74333780172ab3c9b02681293e74544b78a8cc48aa4c31d1765f2c9eb7d08fef92d65f984726b183bbe1faf06998ac6d0c98b430a13ce5b6d3f71f628da5942be321fff9f2e5f7cf4ce6810344c592f00ab11d0fcfe52a595687e0c9e0150e94c9cc4990f723c1f399ebd6c129bcb8a001a30b23983e32873ec3b9b0ba92ad865ba9ca56972302676ddcc0b0c9713090c0510985602cfee4a84953d18950fa53180c97a4b72f710e6b1432e5bdd60e773ee74985757a257b510e2c378dfd75d375af4367c1ba3fe298c5e12277ddde5821e0aae9e45510fba1881316dc6a71381a227bd6e72f6d7ccee914d4d122194aa171c4fa5b6384e87435433db6955e15811d8082672443feadd18a8b56e920f4aa8619c47c05442a31d636d70fab91a202e59040d72eff8cef75a7f7b1e641ceb0123cd199638f751da40e73977fe11ccd2df2371722f3e4499ad266c0c0a127cc70e720b7e38722b7a02dcfeb3bfbacbdd0eca956b1b54a0ac2365b4b6365a9920f0bf4924d41ff92178fa8b4a647d86451770a105741371552d570d20da5b20f3eeda52f6c9a0c823e22dca13471ebccf1daa8c64febe527d5b7e1d7e8692882714ce6377392ff6bb3c4428a8be3e954cb8b28f0644d61d475c3cac96756f558edfa37a72e03600f41176dc8b86f8b21448659f39dfc0cbb844c5a44f89095c27d9b35f1609cab6cd814b1b34ca4ec53f03c483711d90d5907b49449b1d464296644a8277063716d5cbba20250d73c4e70b356e0a836ce27b179ef1a5d6491092d87adf7dbfbd25dd77ddbc785be0e44dee533a724f3171cf28d278c73619d8ec24f24a7987b6fff3994f92298bdb9572accd893ba2cad3071d460117113c6cfa27f9675d7f74f1236c17d8942e51c01089bbea6a88d681bfb8d5526391e0fdc81d777de7b3510024ff470175a12782059f17867279baf52d3bd61564b8acecc3417ee8876d74674954605c78326f29d6df7f1c72c75bf5f5fb3e735855a3c61d2b1550b43e35518ab4da66fa75cf7043029de47268e0fd82ac739002d81ff89895e26cbf3312ba7f403a1bb1491ccb2a0d0d0c1cd4c4609359c33ddc3ccc5bf452a9bc655ab8959ba16b49567f26e742734d2c069d22e453ec1b16c5916b19e6f0db53034071efe53668df0cab1bea8e2d4928723ca3249b34d70ef4cdb29594526a383d7631017d90341d0e88b61183e8d80c72c0bdff63e412d4ed517ef8777fd992d2868eb9d48eb808b90ce305b71ce02372ad2cbba751dea4c49fdbbd4129c48187700367288a432df6889a182b65915372af71782d7a0dca9a925d8bf61e2d51840e46e85270ac284fba987d359d65201f4f31bb7b3ba432b4f5158e479a0881e5c119c0f11874fb91e2c0ca14d35e7f428150d8930873267e019b7743984a3367d82c04b60a4f2f49f85b6e1fd9b19828a5c66de5564b085604bbbe2c697af9a8b91128fb9e1ad58bef7cffeecc569472e7acd4fbd19778c11bf0e69a0c4669139b589f7369633696c770144324b09a407047ad57a3fc5fc732229f99607c0a9955d48f1d64cb0825dd94c28c9cee734f85457e80023bbb498fe6e5b3009df908bcfad2a262cafd05c71ae6c8beb78908aa6e3c5367000fa4f0c5749a58e232146358e7941bb0bbf9b03765479ccb2e7266be5fa4950479ed7d4ba17d5bfa5d54e71ae965414e8c1db2140c23c0838934c60ad680a559be5debbc8e061fe62635b827898d60092f47ca93d1f79486b67231ff707da1dc159045b520fe9570acccea5ee3ff4d357f81342afe8ad5f40356a0d5ff998237bb79256acb10ad6853bcfb05ca46a5e41fcf8b5c4cb9c1fe13729e762f5ae8cb5f73f3fc36f71f3f272481bf1c34a7ba816aa3fc24c43c993972669722f78b7d9824ef57bcc75351d5ff8efc59cbea00a7ed3c3e56df079faa72bcfe45fa3e4c8db7f0edd290bf5b8ac3c27421f01e410ce887ef5ddfc5a9c772d603d162ca765dcbe2b63b892cea9ed377e9ffba27c816ebaecfcf889cc3af2be490c69df1979fdec626f44d04fcf128c916b098c82c3a5eb6f8e0d7a9c12072389189e43a2cdad2a3d4eba3414b11352f22e10d63a10532a2c58e617df9cf725b269096bcd8581de4d96c2312716c54924f146a63e23da9beddf1d7d0899b32b8b353c1dc39e19fca767e1ad844a976bfb7bf775d694be4fdc3a8fc09be4072a04690a425415fe10efe6639474bd6c19c54276d9bb438a99929c6aca7f7ce72fc8f84ff91cb18b2652f795cfd0e3fea0b57394d633360ccbd132203337c6172601e2bd09f93bfe42c2cee2f77794378ef50db3a3ba298a255a66079f16d42725d1826ddde87e7ec894159f22b9b6b476bb95f9f3b153eb764cfdfdbaa42c706098590e7623cd96aca9961af3eb65c049583422f112f8c71a1c7abaeb058414241b43c6205e20c9b3f5e6951d5243bc6341fccf12f8021b9c25bdd25fbb7d0625b5798bfb39bd6253e74c98436c59066587d002156f8befb4eeff2b2f4c35a7235d1192385894a9033613a8b5201b8ccbaab156f172283b6f412c720465e157234a030951b13e11d45a5dc4c94c416bd799773f0fb17c5f52de9feab5f9015726ced515e11cc6ddcd5ecec6c3a6214ef7b0c87a0efbd63c3a438393dcf1d7772c226f3adc61deb20857dde72c1cac48fb6e1e3d6060a7e22063809832f351f258ae43bb88d634c84e3519a4f9686aa77055dadc0c7337ac63d6468e1bd0ef972e5dfd59fbd8e3e6568c0f2051b3bc10a41cb47093dc4e18fe54e74dbf2061272e9925cdb09ce00da1bbd63d2da6c4fe921953b6d508d7c501612399e5f2f2f43f5bb40bf071466e1625ac1cb2da398939d29437e20206b1da708a55ad9c13e72b8fc9d03d99048d8cbdfdb6566d61596ec1d6d77bbb269c1e2840ca235f68c72025c8121ea289a41cb10fd68d006018156bf40b4278abd5399e338280a4eef01115882f4861e5fad16066cc17fa0f4172bc240e40f3e9ddffb1289d4e42eff6d79aa42a963aa83f66c05523516aa41bc83725aa011d5d1fdbc22af09f2bb3f03914c497f1765380ae32dce404316f2c87df23199204f664d80c3dd0843af3037a970356055afe6c7a36754c324992a82c50a175a3f9ce88e255a22ca3b6fca2eb7484746a11199232107e2847e69dcd010d7d174616cfa6139813d1e302ca4729fdc182af66e158e5718a1e0e019e538bc7d5b80be719e493c8cac2a1754822b0947d17d730dfc2a143650b4c4fd6d4ee11eb204a9e2825109d3fd3b396cf47225c7e9143925917f3acc009d47ddb6b364213caa2b4ce43a75ac9921d8b2f3714bcadc6cfab78b1d983a99d12f15b1e6df91bac2c1647a25d5afb588b193071d7cdbf852283379aa4e00411cb59c81cac55aa7656f3963deb4c61480d5b1ef3432bfd07f6dd49a902491033e611003d85809df4baff2907fd83b0da4bb7d15720d44aaff7d4a8b7c34d2cc6b4b2571128eb402d8724411678e1c896ec5c6b872e1b495712755dc104990e2a0fec434f47d3a46705820158287a96a7c0e3f86726941cced0d3e3a9b7816ced5475c4532cc11cb0a3b68e50ffc3e90dce419f27263721a417f3559840bc5ff8408a42f63b1fe89e788fd6062dfcb8b23f1ca2b34ecfb7cd050c2ee168c2e13acc6191339e5c5722ba6ffa1a6fc3c4668b14b7a31972d79761000975fea96eade2078ce89cbeef260c1371bbc5e9b6b41cdb8567296b8592145a711c32921387f955097ccca6b9131ab9581bde1456496e5eacc72c5a8d3b9717623de30c4dbaf78b1d46e0639cb3efc8841ac63209ca3068cb072ffa86fa718ed799f391975ae59b9816fcf8318e42e0b521f8ae2141bc6269772d7a27553fd9b9b5388c827321048c70108d2b2106c7d8fb8fb941f95afdf6a277273b53ee90807161c763448e95ff6cc7a3ca7d07163e1b06fb97ab06b63fb72a3b5be0521f9fce3d6e915561e5af006d79cf8565bd08dcb1c9fbfd09a91181213d24a0858688f1a6f7caf19c2f6b554c7a5987f28be3c3447158d1dcbeea519aab7465bcd3d080fc1a461a8b91c4efaa5df0f1ca723533d51399b46a547a37226cc01a65443f6b882251e27d118777db02624f71a74a8515cf2c4b87c1e99722bef60b7b275964b3fa5b79007d93b3ce5b8a52096fc9373a434f37737e359633c3c68689cab2271027d28fde62190f06622181f1742fa440f58a1d8225e0b72a1c6c3e951f7172302495e619f6b3933b804ed4342d9f88ceccb98a7c0cb76721b7cd0b3327ba15ada914a153b5db07e0a31e91d65d4fc4ed8e0f9cf50baf85d633d52351485eababd27ceea17012437fc4a6ff6bf64c16a47c8511d8249ea727bc5188ac513bda9f887d74a612d09fd09d03cf259c515520e1d1489cadf5c7212a0f1c9cd63aabd6875a762712bd4d1bce508aee4b4fab9ea2a98aea3ddff07737326710ebb7d33e0416e86f1d5a11833bbfd6fe5bbec6401a29b375ecf8901c88cc80c9ad93d20fd36aebe7b7cc16ed6f1ac5ab516dd4e3e3a5b79ba97684335bc1c0e0ebe814c80ffed15c45a6da5b6ee4e63c40a82154e30a0f353b0dc7206fe4517e709c3409592b284a4cee4f4b6f7514d8e74b791713f3fe259fd8d72f88fa48e90b78809d7fce3bce17f4b696b6f71ede01fed2b3e071771bea4af5b1f337e7fd4440a8a5b0ad9aeaaea30cfe5830f96089238898ac5ed1915c3905e2b3305dedd5736a21509bd968306ce248f62c1ee5bbfee7ca6549af02fcc3d72a6cb72bb612f8dee66df31d90ffa78470f2ce1d392d17c5a49893ea695e4b2205993b7054e1924c6abe8ee2d3878c987449f0e2e9ad4738704292d57b05ce272ca9304f6f3b482546facb076022ccd2ba1291b7a2f17be6498f41499a7aca007e749de2beb328caac51435ed00510856c290c9f9adf62c2ba9ff25659b1dff72973de30c12fed456a95d32e57432c23a9329e17210ae5ee6c908a22fd5295635412244f22e63c40648daa89b3487095c980405de02c927237a2549bbf979592268747b335c62e8ab0806f6c9cc5c9da00c0849e102664ad1dc8e39c08f6e9e32017327dba7f883b874f9150147c3c273b92288d44ce71987f047fef5cd83817218be40add675ee6002a926147d45cf3fc33e85175387e16b2ffef172fe417e729348d7d0714772954bd49ff7dc7854a4cca88331cf9c89bdab653ac03026b05a8e9e777d661b2f57e33eb692e0f5a7b476dadbd1b47f83085e6d87fdb7ba2a72959e11a6c49af6de417891d11009c9ea0ee49a55ee5fdec4965b780ba748a32b065903b5a5d950a41755d2b1faba00baf65bc82611326a499c31e30c6dc7c653ba0ae388ab466c076f1663ac4f0111c9e165c265fcff9ca50a4dca2a23c1a3722fe65823b7aa91547e234d2e9a71665205620df770be1a4b703fa3c726eb2c724a42e2ed247427ded08eb5b7f262b8371f8768e61bbf68c5824f443a26cb170acf29dc735827355c300ea1f0c77d71e251bafaff8fca5100951ac23302bc34723e17e0afcae0309e73d16015dcecbf03176802ae4a19631ef76a8c914d851b4502880e23e091d534d380143313acbf651ff1da35d0d057acdccf86a2f9ac4372368f682ef76f435a609e25856ae6bc2bdbd5cbed1e43beb83829e0a4148cc06c0c4dfdd9fb5946d4488236e867e208a35a72d3289018e0f0b33ed105012dae7212422496dcec233e9b90bc2b8ea5b6b1ee565187b7a977a04d9d23edd614e472a1242acaa5e35636101f37e9b2341fbce285e2383609e1442384373a88fb1472b8df42a9977ed85f5f8cd6d15bbd6f50dcc3dc7cbced0b3da84af8e455fc432066b5a28b5b9d7ea7db2e64610af7ceb0224c89a3d799fd5f8f554373c6c778502e47f9e7f094db53bb218d3fd12e01ccf5d269e59678a00f9f757f4f044574722b5af41cad6b5edde3048c99a5177861ceb481e4559956195dc5ee8822d21a72f8de89fc81b87e269868903ea5cd5a80eb54cb5a348ad8a0398631b06ebe6749871d6ba71934c91bdc8bab9790972ac1e803d9dc719af9f62677eec2a9601c72cf47398ebaf8c072f3717ba8b37e3799b7187d2354a6b439d686bd9918bc1672d117bfcbf5d6b6505c9e9e7f1ac73c8fa543377100ad994eec62ce1437bcfd39e13d2be7c1849b9de0358d9969cdedef3ae808fcb8fb28cf31dea2754f810e728234412aaafc943d0899c185b80df9b9571bf07e8591c81e25a0d303e9d47572a462f7caf1608fc5f531a2873daea3212cf209b99287319af7d46bd114bccf728fa6906b19a0e05654ae453f14bff6f2b4f4f2cce3efd018c26432834b6e5349dc05ac292dcc1c99359ca3a918e01879d5b0a45d8fdfdddb63f6383602c03772dafe42025e2570ed54fc9b90fb4c6fdf5c0f0126e78f79f0334e7f3b84a2a72fdd8f117e0e245f15edd449c05649e020a78540fef82ed15cc7a23208a19fe472b0a29d973688878f0fdfb14886fab7d3c77c58fc0791110e0aa21391b2be5c601e8d6f551b4e9c9b87e65f2bef9ba632278baeedbb6dcfc3a62cd44230a10d7293ecfbbeb8682ecab2bcd52cce932689bdd616761720b5c377d8614945c226729bb5d32e82f12ce349c4cf63314f2b5a434b69e64288350aa0b28fb482ddc85c3843d0820aaf7c932032518e1cab8841a0a0567ce02776a89be26c902c5bf827caaf8a4946b8939b84e45d260a4209cc12595e4d98d45809c487ef5e2a123c6379dd7c1c5dc4a8378e65633550bfc6e608ad14ba8214a936f78079acd7c4d372e739fe3000643c8b8c82467ecb4a2d843e58f6608d8ac76a2cbdbce2a752fb5a6326a5f6cfd793e147107202ba0d962282dc2c4023f50c5500ed576514a9e15a684f9266b2767db4fde45c2bf003c6b38d389c1238fcbfec5a12cd27d6504a1a7bc6d309c160238cdcec5630be7ea394f80f0393940bda056345c35abadafa720d3e6c38b78bbe9319cf1635e629912114c26c83f3cbc893e37bc487eb191372c3e174f7921867712e5650e6faa8eb8a6fe83c583714045052fb7a173f3d0966d7639c1171aac61a3d8d096be63a8d496bdeffb6b00ff10acf61ab05fa9f56728e54b7ad8d00c64dec66dadf515b592a79d388a2612017838fedb17168b97772f8a71540c1ce9b33232ee7dfc1720003791c8fdfa1a82610fdef43e9b3885865087d91b0a57c257d064f55a4694c8aebb20cba9ab83eb36fc89d6fa2d84cbc5f7208e8ec03b4466310b3b5f252f635741e030a2de3234b2d2b220bef021ccf7238d64ad5ab8e7f82bddddfad70c359418947bdf89cd722b6264c673a44cb417250076cf72a71073c6bf0ef88591a3ad9c15ccb787e15e178b7436420ec0fba3596ef4b623093e785c6b6f34343c371d01300cd825cecf5d5c33c77fe6097b072fe43dccf80cb06bcffcbec25ccb10a92c49e351aebcf081f8b37a2dc3098d522cbea5533d778b838b284aba936e14e2a669cea75fd2265770bbb860cd2e2502aacdb1263dbf605a1a6cc5b2d272535752333df4469b31f9a91c43732016bfc5c170e8913c2caef8c3210aad04e255044161699666dad6183d8acf6769315b77256b37ae64e4df1d3820b61a164a42340e5ff3878fee0d5085f3d37c38cf27e3731ab3633dbdf9508e8d8c4d44e6c60fb48bf6404252a2c5846cb19bd3f8b8772988cfc69d9dbdf89b2a8bea7bb78fa013e624fb97c615a4e35b0eee2cdefc87182cc84cb995c83aab859656705daa9f4cd5aa748431637aa023c2705b20b647230084390c9193f55086884e1c674cbb62634b0f60f3f2b14f1bb60874df47472b5f08d548e80a41d6c9568a54a093a755fa9d9394c09752753e08a2eb700fe72fd2589d1ce5b691ca03f79eaadd916429e8bac5cb158c77d824f91f18b677d38d17bc9b0f6e0f05d51832ff28333f1863c7e604a3620249bbe677376c7c7a62c50e6d73f66413c7a068a348ed0e1ff453adcab4f3f79fa3fa30af66e259074726ae00683471384a4d0523d3a2a8b57e41c9068a23f4bb5c4231147fe8d7e957214c48bd43675db7c398032ce15e605579da581288d5bb474a3a02510c4fac77201f788d5082ad9b85cd001c7ce5973427aa333cc1e418691ff111cd5a4217300e0a390f69b2ac171b5da6d4629d75b7c07bac320a241d4d14ef15bd19a50b2704f2e6358e6ff5e5ea12f8b71327d647cfcd32b5e4f7aca75e5e3c891a07769720b9eece7a3ec33ef271daa739597e4eace0dd69afe28b20ee087f299e72eba644c190a76ae7a71462257cd3de346207613403e02b69b76d04ebe084c0fc979725f44ceb67705d34718ae9b739e6d88a370f376ab56dca381276190d2ac9a9d43c5f6dd77a0037481158d3596262c1a17aae47310e7a81292bb8e2092562fa91b410e5a5ce9c28cf4555b43bc23de687a0d08e03011a46e0e9feeea4b28e69128b8fb89c4cceef17ada03d8bdf92b2655cb57908bcc1e67b85b235a4424aa3b3aa47e0c64daf8af2ed3e69a710df99bd758a470707e6de528804df688fc6fe906887a1726a39fe50c7408654d6304c814e51dfaeae8b790cec6dfab18a73c5872c113b91baedd800f6bbb55ab584da6fe5ced415724cc92735b69a93556168033904c669d03dfd304d818859309b9dbd924c2c0d3148e2a533b786c23b3c9cc72745b1b1f28e40abcc7f965fae90c8cc5efbc8423298c5384c97fda5a0a0615726a565ae023b3f5791912fe1676802cd264facd6e4f6f1be142deb8a7008999241bd26528adc2e584bea9cddf7d1c27d44c6d7c566e9a105e64cc8f8159027672ee238ad0cb5cf87ede2e8d0aead6c377e771f43be4b3c502b8522f0fb8e9c91ac8d8f8ddd2cb683e674664f37b6a43d0a6fbb11c8650967914ba8f8a417fd16885186391aaa59818a45a57d3c3aea2258d3c5ca2d82e611a751ad509253261394b768c70f83526358eed496cad6083d20dcd43a16eacc098357b5109d9157a728dd83941184bd3a38ba34a85c568328cbfe55362ea02969889c8cdb86dccd2728fb819e07f62d605f157d77466ffc7aa1dec007fef62295d4b4679a70c90b8725eecd94bb3e8495e4ec83a629d9573659add564d2db875de9fb4f301773d8972be2f0f7f558f020705ed9f719cfb065eff96a9126dd6496a365a6b00aed85d40b6ffe66e6f07670c458b2e69ee185d67c773f786400fdc86dd549fe386975c727e0bf1f15dcbe21f7c65bd08728c936289d7a79abc1bd92a8b752c46c76f414cde65107202b8f56d8e8726d6fd4f293d92ae97bd5b10f18f9f6b1d80d8b0fb615b7886d1fbbac576f75b70f77e4dff3fe9ab79a9949f3e985842c619f8519e72a620af9ce8693cfc32d269f2065e03fd981fd82199eac9f89b33dd3f91a5e034e3ac2015e8eac57026a1a6850dba12c19981f82a01b08a252199e59fd1dfd37255b607d8f46417b71cd3ed5f73d0214a9b387911f12da7f8a48475c21512172db04d5675e6901a6c9fef0115cf7d087c08f2ac29de669a5285f5b0629abfc529b06d6d484673ed740bb998d1a8ec720a9f5f50abf7f5ea84b1db03ede13596725f23e5479dad72d9b6f9f89a05e7e195ddadedb280f57e3df633b5ce975a78335a5da09a078b484cefd8e58f04d30db39d40aa60c9e81e184b6ef59b5831257222d655eb5601cfaa47d5a7408028597d05d66a3e285c62379b7449cf4c096a7262bb8021c35bbf1fe7ca9d22a2f621854e2623645af406b76c6d5333dfe8df72940ebcad354ed721bd7aab7e1f6da9dc4217240c233931df67891de71d7eb972f4afb5e18d6eb0752b242d81cd3c60fe273131fbe61e81508a3d88cf208914726f6df753662becb088b0e00e78d5ad80755c2f5c15f061b39636d44782493072454693fccc24cd1590a10f4491d14eefdd2d3828decfad57a69b60315242f222c5607922dc16987f16f804aa2c41d0083fb5fd266a66b6ada1656cb00753a172a7e9348a907ac02783244c52d17431797bedfea1826808dd705710b3430ce572341020093e83a302b76b3440e09f03a9710f61877f303006b9ca71063fb8403dd0e73aec1891bcaeb4f04f6b0c53d629677e0472f550ce0f7ae71c31cc2dec72f24f58a38af5049696c400b8db39b5093054be558b8a21dac39a82c40e9a5f7202d6d250633b101f4048bd802ac7aad32001109cfa53de2c5cbe0d50857b06007923650cd93bc56786f36bd5d2b38115b7543cdf48793ed6c1d7268ecdbcc872f83a001be771a86e1ee0095b756e39b46893a99db0dff754ee29ad4b9a1780200cc80ecd01fd885b459a8b2b85adbd6799ecf8b154d635086403be0cc2d01a7201e36ca48228390cca02ac287aa7e5f92804d8bc91ccfd90d4e261115f67c272c58d56313058e78873a0de84de7523e5ca413d55bd6a510bf91ffaa3a7e5b872b33c3e374a9a58d8bfbeef561106bfcf8eea80ffab36da9befaf889ec623b3725e8141c7a63b1dc7c4f01cf5a948ebabb4698d0d8a03ba536291eadd06e52f4ff8166a4ae989d820ca7e0769cfa32e57ad7aee1fae85aa4ebe0d4b5c2cb37e725ca111c8f13d3b0847d2e6e4fcc3993e4852663e8d10fd9f6a761724e298ed721d5c3044923ea9086bac7ce307edd5b02ec048a17ee43073b2125353fca1413c28dbacefe3b0fbe066f0a7ec8b7c485416f9dead0184d3ac9b08b23e732b7872687b02435a6242eb171a59abb4809d057e49cd72aad923d3d1be5c578ee0024420c7527b313bf19b746341da5c86de38cc640715632a9b84f5f413cbad97aa133258ac939e9628f8f496d328d94ae05ec70f3eb6929ba6b0a7d9addb852e2b55759a89eb916fb6e196c851660870b589cec6a4918ce335d8429804051296132dc3a37412459f63d5a4bc2bafa2d6ea8c989461025788fd752ad2d373ecf5c2061dfbc661e025fa3aec039d9cbb5753e7fc0736a25c57076fa8babb82540a2a727d0d2460c59b6126a2064af6b1d6248bad61cc43388948625b206c890851f272bcfaf2be9f655a1ede43d36d717ef35a447e8677f5c9f2149f6246b1daeea016feaae47b211b167d37cd539bcc5cd2b1d359d6a2933cd1e0adc46016636c5b078bb5147ef2ba272e5950dae493c124043b0581c237d991ad704add3ae753a17212e7e8fbd4bce0c591e24eddbcaf7ea87a986a31abe91b372dc60625477bbc7257c60ac8f70b8340aab0afbd401597a9ff0645fb4cd363b7c9dad5f92c4b5072419b0205641bbfbaaf5ea769304bccf198642d63b19a5870753824f23079781a72e86075547135050e2268bdcee77a09494c3aba9e8fc97e2b79157f2fc17f7216498b2753665cf68c0c6c9dc046b724eb4d57702bc76ada40f9cdb751fe4d72e813bc68cac8086a7e248eff9109896a83da0ee1fb523708a16f29ca2b2e86723b2b13cc2d99b82f35b251c8ed7e7f96bbc08ae7d4874155c3e3f4d1972ba50ab24a847ff94bf2ffe2a59961f92ebc76221accefdf37bc8679f7fa6c8be92502000a96e343ac16ffcdac12371efbabdfe9a1a26860a390927d8e798675567d72c241efa38771e2e61e8bc318ad5d3071914d7fd48917c08345f4cf18f255a03167b4ebb0a073c693763be0b6e48d8a5a7afc6a3d98e15e08898dd6bf675757720d71501dd81603c649c6c10cbeee265ab2ba6ccbdba48e83136ee60787fa804690764f0ce1123a59c4c0bc379d23ecc8ee72d57a0b3ad0eb2b60309759a46210f309f682dadeec8a4e2d9534628a289342a1528023176409e1870a318dc91802667acf4d62a67b23600e1375ede8f7a89b3ba70eb76d9fec7e1ee368e536db72f533451c7058ea87288b06af006952d674a94772b99234a8976053d001f001724c58ae3e4132136faf2e22e771e09fdb75abd6e71571e592d8fdc24dcc804272b33017590a9bceda8bde3dd5a25af5fe066da1084c5c319b257c9e014c23af6c7c3163fcc12d3de8f1e11cd5bec206b3338a079681f9352cae22f75270e8ff720806751a5f53a0e57e9ea175727117a433224e67d4eef55cd714a2d7396706723264e3365ee7cf50c2f12a477997cacc200d3d06de70be9e37ea4af834f74a44f300a74d0dfd0faf56df05e3f9a32c44a2561e08143fad563dec84fc7f393c72b7f0aba9cc1d022be632c0e57f0688e73be035da9cbf8c783777c7fb526ee645ffc1fccef9f7e37eda19b45a28fc021c05aaa07b1d1ec9d05756ddc1bd456b1977bfd6bc48841221a0c5a80929845e8eef03e1699522a6e424eace36ae857c72284944028853fc981eefcc344b204fa154650d6deb074015dd01a098b4022d1c1c32f8677c8ae4466f2c9a710d1b053f29f6a290bb497a5898bcb93f0837a62f13d3edd04772fbc113b3d2dfbdcb141bd334e3293856e0d12a7dc7340cbef0725210cd1d6cea923029b4072a9163b667806c94fe175f168d341beb3462e6437227726d7e7ed5cc7440274d7234b3b59786c74bbf413545ebb48e15326ec4732f4c219bb284cbdf78bfcc6a5e530c05130b892574336e288f45244519b1ebb40002015c455610dd7c87c6c754417e190bf3b12648e316e4fcdc74bb9a1d707c72efa37c70ab73f276a6cd62b46ae4c53e3f3c30b5a8c95b25d020936b2547917260dc473e498155f033036249d0933e531b0f1c517c92f7288ea02b0c1cf2be72e1f5006345ecd7cd691237b88eca071cfaf7eab2687d4e0e10d222ec42049272bb4fcfb0506b928a123daca8d15db5adc81c86a4c8aebc8fbaadc24041b13c615434da07b17ac7e5634e8db9ac7020f89748670adc13b51298a9915050acae4b7a5cd3772cd2e5028e179cb4f31bdad498387a2117be4c930e3040b799f98472d556c230b516917245c514eac5811c8e192263c324dad2e6bf311ad481fe4c4846d284821a0f45a69449d8ecdd934f5a58577b96dd8bfeeee942caff69181c72a573f986aa601c1f6b21a01a3d0a2bea2cd95859dd82c99f32cb47eaca8bac1ebb5ed3bf4125450276bd1d82a7451a3935a89b6435045b9ea5ad8a7270442572477692880174634acb4cbeb3e32f52e4460d7abcdc8715929658e59a6cbdb261662168a593c7db97d56c6f26ca6fc92b80174cb1cbfff648868dc63f3a0a4121146c6db76d02dbef8a514bd302d288bc0a41b2abe54c8dcf9df184f376d08772c2e8cd15571e0c3356a56b71b6c761d09b8cba974054d20783a7fe3aceeefa72ed6ae71780974143c07190e4252db35850fcbdd9edd77563408443d1582205067e0e18bd3f93160022d4aef0732b69b654b3461b0ebdc9321eb91c28d5d3ea72e439e70936b7b0384121b1254577c38c295367bc09268d669dfce3fd6b85645bc9891c1562e2fa2f864f033c76266c6ce0dc75a5429a9790eb32478173d9257283e6e5a005db1a69a568d976bd41bb318cef38653c4673eddadbe4770e229a7211fe199d54435abaff62a73269a78dda0e35ee80f6f332eed1baea04858976459f887e430368603732e49bd38356f9fd5c0993716119a9d48ce56fde4ca53972b5bb2f5fcdf24bde007f659aa70d0af900e00a2db3fa5d62340eee87fc4fc15442fdfb2a8f0f7a326d27d42d4a5d41bf2bf9e0d4000dea5cda56bee3a824072aa3fdc32f78d1863a97fefecd36417f6cf14ad59fd0c25b0aad164443cdb112109abe9204f4bcc0801b75c31fff8349e75ddd863bdffb1260f5d2bac00aaf187221cfdbbe7f5adf3d04838a0e9a0064a704054e47335bd471c129e9df8073bf20dcf9bbf795cbf8ca2beec5ec02929aabd4c07dd52b7815d8950abf3233049f0b7e8c6e12da2b209b8de3e1b22e1155fda63ea83bd7a0d7997ad615b545e4c55d6732ef788472e9155a38427dfd21a2663ca18a1fdd546677112e345b3dd990723e7c522604f7d53fc1049365db44a94e4a1e0d2785f6d949e0caa01b031dcd27fafee2171b6721454241a07a7594182518ea027e6a0d35150049f5f7345ea472d53b7e9acf50ee9e48359e13909b02f15d323e908feca7da45af1b9ed09e43726d63afc9a9ac10931c42cd0cea1a0b5c9a2578ebb3cf22867c454adbd9f94872ef940eb6a4747659f9c1a04c0512c66df8966619dd8d981a6a474335138d2172d9d19eabb56b4f966a23161e830c1aabb1e9eb8865ddaf529099d422545c285bbd4bb05faeb283529c2f465f8d40487a7b545d321de5775594a0c2e7feb95656c824badbbdb47d0a0ac2b35b560fdafec41857fa53092b7baf61548b38162372a55a8ab61cf0e8d7dada958b2ae93961dc0931c38aaf4dc414460848eabdbf725b9154d6e591638c3d7b756ff90b07381ed746341f8ee465e15eccc5595a516787c74a4370097ab2de909173c5270eb5a5aa95e5ac16dbce0579d00166905a3f70d7a898a6854928598c31ed6345515322fd4a01c72e002f944ae94f86756272c3e6d7dcda2a8135adf97376f723b22883744e8256492a25e29cf0c2b54a67530bbae3cd5b9b42c1b12c6c8aa2f267fb3a38ae942294966e3e696df55ba0c0729461a8c39fe056223fd548d9bb4f2fd9c349afd46443ce863fda4bf7da549c611d04918ccddc9f52cbf719060bc04df2939ed007b6bbe31d079e3c6d9e7b0a6f45e682d9fb6d40d857d0b1ee626a93de21904faecd1ee4f2e7174590626a4f72ac646045035b94a3b0f66bb2f8145120ff602fcde6176351042adf302cdec372783dfc15260e7852cfb97133ff90c5243acf71d203de3c17932b0768c5c5741e1aaee80114714a1b9396a4eee814189d3bf5bb2389368202f4d692ed5c7aa016fd213ef98e75bc7a0669136c93e168bc0aad5e3e9c9c1febc787aea114e6aa5230aa0b2f19d8390abf1dbb20b1a118a2cc7d5f464ca7a9b0dfd852762ac67d39eb6a3b943cae384359973cf797c1775bba6d8b89c1a08bad11c7c996572c94036d5b9faab488f6947c29b55cffad4fa1959f956cc4de197ba9b41d619969dd72ac22728bc595b0ed1698f4cf0070c44db9ae6bf7ec9f5c18cfe2ea3455c76e72e19b1f7d0968c5fbf47a1fb34f903c37132862cd375abe79da97abd79008cc72c09fffbe032b146129a772dfa8c353a4585b1ea6dcbbb6a503bf570397b568361e84e334b4812c28dd9c105fe5dfc11bc9ce14d7bda8f671b0bb593b6b4b4c72037bd5ec8f505f1bd1d5b073f777c02b2aa74884163e3cd32f2038eaed72a172fe116bfc7d8084b8cad9e750f772dc09765188a41b512330f835613c853d1b5858da5cd47423365617d3e62c674c564d938bc03413bd93a0177d61f0224bae0c4b9fd6a118babdc7f90219d5e70be80242b08c2b5a87b886d15610183af22272bbdebd1bf847406399a9e11bfa27ddca4bc92f103c2110144ee325da6398017144a40d009d3c43343cc0a97d681c07401eaefd30d5e19a02c78314652596792b4315eb129dd5e2789e20b04c5b91ecdea35ec1c1de07c7a5051f415422e6567247e2b505bb25b54a4ff69db9256d31bc69e6f4afe222ac050cfd18eb99766672d599b3505edb6dceb09ba312424383669608420eea0664ec9ab13dbb4cbe3b72b3e2763dcb6157c531d5dfd46adbbcd2f360eb3cc31a628aed55f8c35e7c632c22742aed21a1b66c57e7b39a240c526562096326a4949ad3c0885456d8f22c27776237f287affaae50cd88397dbe6c72063a47281e18e10b7ab7018aa4829e72d52ef8462671f6fc98e494791d3fd34d583bdff58a41735e9a755c326b9f1c72a6b7b3d8ab564f5654db886dbebe92ba68707f0c3b7b0429705ab4d0e4324172c2e6bd5f22d9e4b4177ffd9dc99ef95388a112148cf0fdc6d7dfe41171b65472e1c4ac64d51723f57966490550ef8d57a91519a4a65ad66250804ca1e3b2c7724e12ca66ec15bc3dc157537bd7b8df4ab1c4643ebdf1559dae30246a6c351d72f889b82d421d3c1474cc66db31b8a7062840ec6fb7ce9a7fcb4159cda0581d6337f97e88258ba3f383ba38632e591402d11b0e2eb8a4d0b6e5aa3276620613706c16e4999ef6986587e3c4600aad306490e66fdfd1c121faa319376783dc5f70ed8e13fd5996a0f6447518b196a6d32de9dff96c560bc93025abc6eafd925a6f87bedf099c7aa2169ed90b7d8f7f82608fe5b9adf3bde51b9e2b15c39c0bea7286af8d5aa2dc91a077981047b68c48f3cddbe7f2f71b2942b29cd0748ec7592381d9f2d17e409bc6396e148620bb60828aff708ae38c501cc5f9218fb5b0e6726fa3b8bdefb3fb1e120198f8b8ab1a6e0b6a8127617915cd8d232bb490838a72719682464d80fd965186c7cc490c07bf43b994b8a7a4acceb4aac568f96ddf723c23f777d1b55a32ba228753395158869a7a9d69526f4a06cfbd6364f3af1272ef6753ea28ce425d9ee01b057a15edfc34ce7b61822c84edec9c4786564f3a191604ea654e67e488e02ca4fda49f952227dd95c2bb2e2b9e7b9d3a491c9197729ebd752010ce7dc074ad776200eb18cee004b6721be4b6068b251eef98826c72807a3da20784b638da9e9610a1c78506932d8658c9711490494b19d310278872543bfd72e5688a1c45c7a295c2eac37518cbc019a41356b796ff87baa6efdd3846a1d7d3fe8c2877ac49b411fcfacd562bf51e272ecc82d3b0cba4948d47973eed96d84fea03fd4960fd2ae286947db8cfd00ffcaf6e126a202b90370232cd536505b6eb4fcb8f93a03aff2a20381c13b9f0a7f35746f7503075dc875767e0145270fdfbd8dc8d7541f48d897bf12e502de5fb7178a024c8f6892a90d35bc272b45ee197e96b8d63179f33cccca27aaea2ab2be4d914dfc49d46b60e8fee067234caf82aac7fc2e811368ae2fe89c2f0b1b2d3e381d390e082d1696f5aa9f40c000d1e94a00ec9515d774bb9d1ae39ce369449b0478aaab3a6248b4fbd15ed4582a288f16eb2c2049f30baa752ad80506173346e0ee4839da5508763dd4931724cae46809e8ac7e606579da5eeb6c1fe6ea8015884e4fa236b4946a59ce74f29c957c576bec63af04ca2e0776df048d21795131ab5a75afd77bbdcb40b477428e8d37a591733ba4cd92fa289a8721d91e4fcb34db6ce5f03bf390032ac1f8a185488be68176a5d3b8a7133c4a78ac4fbd7260597db238bcc031a50a971ec9e72c7d05a13f2f1e820b2843239aae2c59a5dc6c1bff9307324a45eedfd893fbf17e06b85588b626d024cd834bf69a2ec92a0b4517cb85d21c5eb8ffd90f0a19672667b650d5d7784ba5194d223f318bed36ba7aefa6f13d77195aaa853d104ad72d4ef6ee6a5ab094e50582a616c088a1242ebcf89084d9a70c4ff97773666ac72be887e9a44684dacca5d0a19c68a7d94d40d60115c54921a70931d40a4e4315d953cdbb6f3158753deaa78c29801e445a356936545940366790be0f650e4993d5246cdf9bc9b040d1416b9039d8ea59225be4164acc51d2fb49d0f0b9a8dbe726b93ce924a17ca39bff9e3673c05cd901d9dd7ca53666c0a69d6fe7ac6b0ce41c880efcfc5a5d0b289b65abbf669e176d8f1214a65216180ea442e250930e17298ca77c508a93beeeb20b9048dea98875d4473b8269ea8cd98ac496dd6c5c762299f78578586450634510557668b9001c501982b6e2c1af8093a9e6c8e48f82006a981149b6b26d6cc922e43614da8ba943ad1f03cbb65f0c6d9ec0cb410f572d0cd90deb3beb9b4cbf6b98fe586ef5efaf091904b897ef773a92d377c732a26e0df0c9bb0355b2f45ab1da0d4435ec16a5920706fcc5088c74a6ae69b7c6e3890cd4eb36cab04d44ef02aca4a7b0f4c0980a2f5145dfb7faa4dec2a7814ec34541f7cf9f1d14bd9117fc17c5e34c07ae67ee824ef872aab26bc4dc1842f955e833e163a6a1498a3526e85bbae91387a3980d98042b26ea3e918b3c226771b7225f8a2036edffff8270bd858669e348145a4f0a3a60495e8da8fe286887a1372f17acec23dd781723793a1707462b2360f2182a1f64a1b49bce69b3b75a2d772280cdb9ce8176d5053c6e26771acc1a5bdc1bac3aa014101fefe965657f1211bb6ff79155d54fe259d67e40074bc04ec9ca2cc731fe33f21b553044813d5d27216423456d55166692eb93bf59a2010302448a135dc03285ee3b1a2c7a04a7e72aeaf3b8ecc6f677d9a1ec6e715f51eeca8c4102525bc59cbd3b9fcf14bf06a1ec050740715f2f6baef85f30df04333640ba5c8794bc498048b424bbe74fa376cdc791e50930482c4bfa0c6671809ec74167a0db7d0afb6edb16cf83446e47a45c6d53ba1a169dd183eda5f3c2163cd96ff36dfc8df0edcdba54d6ac2b1f0307208ec713751831b66d13e54687923db6201a80eaf51f121435e8bedf6edc83a06595e5aeda775047207a7b07949228ce7b5892403bd8328813355168b52258e14681fba3487e51223c7375661fa45bacb930cdef3170a5e27bd7008416b7e4b722bb2574311ed36eec41d751d35deeab7780537b2a02321b1afc60a5564a3da004da15ff86ad8317c300a8b4ce6e0df01000c360a3160f466a915772507c7920dbf6ca05cfd35e7a9560ce3ea183188b72a2755f274c7f7b687f32160ac472772ac03df204ab2e294f7d532c5414c18750194dd4cf4bb3867aa18c82ad710513f5bba4f80a9505399f79471596e07f1ddc4406198479d7b68f26da34ad207f9415084b6fa5c5716d1094d0a492dfb4975e56df8cf2492ea8c48c67bb057a88a3f64f00be39ab1604b6af3b061b8e315908c11c4563e9d08998ef7b23eee1e0772231db24e601ab9bc457235c0c3f078faf2a37863e3cb6f323c540f6fcc66cd2127e382ad2d082a229fa2f9b7d6de21c05fd1c3839fb174af28325ccf2d90b4521f53234e5b123500d7e70d63f0743bb527c5e49815f57ec237fdb68d13a9f772a015fa4f7062d7dba165596b48b2393e676829c67349fe97671919dc51660802167c8be468e24866e22886c7c0d4f1f3d8c35c2d3566d7e75b242f201fb90f3cbf15a1c403b59338b317d22d455dff5a1ac21aacc4e791c48bae4293ecd65f72efd2b6d0bba65007a5eafa86ee35845d87563c058abf98584e067786282ccc5a821641b0ea3e31653d465f4ec58d43ddea92cc1ce286deddb692a73e03febd7280d2d18be49245dd2a31347d868defca87eacd6eb59893d0b528acfaf263c40a377e9ef632b8a86d3376877c1aa198caa0f8964b643ca3bc7f7aab34092f6a7253fd2e8d224b340e0adf78dd98152ceb224368cdd3efd3de0e0e9e1affa7b83c38dd7cb5f61e3eb5867de871067e37ff0437e03bc426e8dafae07d85279e862540311296198892232c5edf4c77bea7d3a6ca7af077f146007d5cd289f61ba8721338c213d560224d641f392a32fd850755b736f2fb18011674e7121a69ce54720dd7694ad3c378dcdd4a8afd846b13b30eac754912f320d9facd8559fde09672eb7588b9ac6860322c02922c02fffef7da0845fa832f3343b55961a2b1f1c35278cfd364f94357efc62a656b5e08bd1ea4340d5e8c0e6ec85e03b73de48def55851094c70c4f62efaf7d891d6bdb4c3b5f58f1e2e0bce19b5a8401bdd6b053726c531f6f0e69ca2e6627699680b5ab7eacc56c62a4eec1f5b0543462187d57720c9f5ea907a819b5a980cce7da3221bbeb8f221259fa54e6995c00d42608006f782feec4db626ed0b5f49fa6baa4cf9ccf9a91f9cb525d291ab9e11c969c6472c3fce46b29bf1afc999c7d0c6f0bc19e55e66d131556e06dfa01f45f8bceb672c3eab9458f5dae54f76334d62bb0da4b58552c48a18941dcc727a11cebed2172e0bed677ed181ee856c0c2cc09958c489a6890e213f39aabfdbff2314228e1406113240080c4ca2ea147305b884bf7542e8e6b65094474e0785d61a1626e4e18e5200f470ec18872f4f2715da53f56561d8bbe4fadd115d8bfd31c5ad974d372ae0386663d2cfe13f838515874532075ef69d95e46fa1197ae9efdea901cfd728b2c3f0701d75e177f6bab7fc02914d294b60eb9078820691780a089e318712036a5842c2b6574daad2a1d0aec2e5580c3b50197fffe2a88542d2cea0ec8a4722f90981d67913ac91f01e263f4fb2d7a3185df4ea5fb2bc04fd19122c5cdbd7284a0177313072a01313ec08e983b59cc61fef5efa8ea76915488c090c40d0f72a91a932faaefd8d324bc5e54d7adf6fb5bf2bf286566ba0511ea460df0e8c472c0fb58d780aa1ccd512de82ac595adeed2f78c64b311d92a239c78c8101660723f090dc5b4535fd619abcb0aed1c1746932b4fca60e7257a91c904fa9967d4721c57ac9e2411122f13f2131f3c17482f240592b2c41f46b983936500c64dca5f699390ff3a5e1a53d10ceafde1ca8351e2a5d12b7760fdb14181b85bf889a965f2b37a6f925a586963fb5f8e7fb28a7412f88b886b91b438a1e29ece804a106f8d8256b032146719912fb701b05cb69df9458f1e1f73d254535f90f918e227722f7a7ba3b140c0435acf6aad2fb138ad02f09186b0d8b0010f30070a1e5c384e93f484a0b420f1d3a4f7a3a60567ca5578144c9cb5f49851664aa3c22b012f7221b9b07fa60354b2e1924ea4e42881ad1caf9e501be9d8bc1b59a431c004df4d1acb52e003c962a5363a77bc81a4aacf71589ddd57cf54a1d742f2a436af8262c4868f3a618308d0f2738bc3f2446bcc33819c14d2e0c1a936d5d2b3b8bd5e06c7d9e63fa3f81603ac6270ed667bf930bbd5e01416d164c7012d64bd1bce4572095a628e3898f002a41749fe65c4209c036133e9929b6d9c6043805696343c722c8bff12b4508cef86a0895ca6dac208ba01c9f95eb55939d3c6565d83bdeb7276cc6f47f063cae147e9098aefbfe101cce6881c77bc38d064d97fbc0b0b6a408fc7abe5319eded5fa8d03df4ea3fc3602c4a956fa1bdeddb9256d6f857ef95727c581d35623d4467403ec1da1c003b6ab7756609ca2e8e651a4a942f5f5bd4b54e31a374356cfe209ff9e584384082b83382b805c5c3998bd3e6f1485d65072d295e51d4903b8e8848b38005155400e0f8414491655210659173323a9e14e72fb807e00239f77e1235b8f62a6963a4cb17e6ecfd13c0810e07c6aad711e7b0c5ead6972b7950874b2b0ddf947e8fc885fda4dc5a2af1015c6c27fe3403ed72942c7f3887b051c9d0b6179340575cbae2d8568f544d0a96666475f159658ae5b63b32380e1e9f308bc37da2c931f62a4062db00278ea0985b1f82ab8cc2d5f003dd85b0a5f3c8a84e87a83dd35c32b53cefc19a74fd04a6a6b394d3a2b36d0518ad66507891555f355060fa2dd0dc9ba793d114b2f3d68ed9ca1449c3c402b474f55b04e47f42f7ce77a943579c3376a2a5f1693cfdc26be7a40c87e66f4a8720deeb010c1c0da79c3ab193225b1bb72745d336c2b574373dbfd0632956d9672f8dd87c1da82666eafbd6985bdf63279ea680ec8ab3e0815baebb1a266fc1272acd780ba1e58bc6e6977f135e60c833a051b1cc4cad2cfb383f40c7e99c089297a8556ef60bd546fb044891c59f6a5b2f171e4741ceecbc9b93a4550e8c54f726b22d82ebe59090f58d377e5d889330beb4808141dcee8b322e12f94920a160eb12cf77fb5d3d0bab84fec6c5c1b5b70c5939f9c3d1ffdfe557463a6e3dbad3233b29fd7e78e8a7578fc5c4c27e3a8f32fc02ae5b55ba0682e3444e414eaca52a94aac11a0bec2f4cbf2ddd86c9dfbd64ad31f656b04e4d22101d32a24ccf972419162b587c0c8ea4a8654fc33307043c21b443dbb016255577be2a5ab69f163843e7dd79bd9b6bb158280eeb515e8c08b75d88627a410501ea5ee91d1973530acbc51e984bedf4595ab8dcbe5a757584cd55f98495d48f19841c218c8fc1b506c60c19d6e202f22b2ca4b0a21a2ba1cb9e1e9173dbd4d40aed459438225ed4903634015651122219696e215e2131b8cb13a9e565a12ce84f0ebab8edce6b772c7281c2c6c8ed365b1704aef4f493e51df45b3ed3e2f31a102805731bc04905b161829a41038c2ea888e39cca19cda8e8791e6982fd0865103521d2a8bfe64727eca87f7792524905198aba0240b93cc2bfe99b76d0a4d3b4ffcd07c82151830907733c6b5a4d1be09fa2f4947c7cfd3e864bb84787ed44bfcfa3126dc737d72031ee57c4c16a5ce178c86490666723671e8b09a7c32f66b6a2f32fa8f845c7252ef8556ae3e8b9a99d0650ac66f13c563b7cbd02787ea8fd19c4ea81dacc5337d930991f1b0446c61c8c3554907b87590a7c6381f3245c3f700086dcd2269544d935b3855f96501a88b11076a9ad2c8aebeb28862daed85e46797b63f570d119f3e8f7461ef23dd66f02dab3d86b80fbf513bcf3f7a7bb3270d1c8a71c4cb72d2a9c772a31620cecf88544ded2977cf4fc6357041266afd8724ee6e4f31561ff3c6f6cc3a718ee9aef59f92c8d3ae624077a8c2651e992863f1a3bf55ae635be21ac3b32604de52a1e545d0b7da67ea3bc9a1e54a9ca647a5671ca88b137472299f9d736f3189e3d823189ad772a294fe5cfe7c1cb8745a8f2f49dc2cb94a72d36a5ce03e1b64c5952cc65a19d53c06dfc891e8e3f78903b74ee91e4547301e731d32037a75d5ac74c6fc6329d7cc05cba61d2ba90cc565c1e24972159a061416d9401809d09df4adc299c2d3da4bd7000cc6619f1cab0bb3c07b6617dc1272f0536e48290d9e7b7e6d004e2c831582300067c90aa4f3b8d7efaa8c58a90c67a4e12ae22b23961e5a144c5c916493c75c473bd1007d8513a1eb0b560c8f4c62a4b7557be7dcbb39ce9c1a5eed6ddbb1668996f2f3352e920a2d220961407a72cbf64d59af833ebd9e99c3a072d104300f64199085f92bd7d68ea902047a55484b8dc0a7667e541a65a5d8b127512b1ec2da4e6c29a6a46e5246f0bbc1f43172ece9cb79d4ed863e0d0b4125cd1951dc0740234eb11aef9bc6ccf545cf9e9860589aa53bbe772098f590cfafbb71e8d5bbe00c5ba1419a07dd32bb60d5f521729becce294740d8e83ea5da5b5bb2c073cbbeeda54b5fad281d3605a6ff103a702207753a3fb5c81940e9a56e2350c8871cc2ca9e101d102d6123396a3c000d72f19b262bab56343522083f2839f8b2c5409066604067ffc8ab538dd76af7b05bd75f60b55a01c97d7151f01ed04bcb5bb7b1f92c6a73b31811fcf19cb1c97143e0fd95947e9e4b1f086c4464fa867c0f662c6226853919633ff4f38220e1f40dc422c8d8172b096c77020ae431fcae9458bedd727d8922eb64337c4c89b68d41d4cf9413efd98262c002753e974f723a86e23c1e6f83928af0397bde311927691f01d29d7baecbe83efb17d442e13ebc80955ccef342724042bde1a7792659720755e4d14109c11debab7e5d759d2769f0e0bdd7545b2d8b05d5c20b694d7672fe3f2358b3911739a53aef18ecc7913254f2b4c00608bbb2117f00b8715b1e3aa6781ca0649f42acb50603991a7230ac4fe5e856092972b07d1df94f61a4612a2224669d872b6e653fa41260f82fdfda5940edfd3a2318e9d65c0a6eb659db72f5254c6bd2aeaec4562059c5fc5d350fb5580accbb4ab4411412e8c1ec85555479ecde2c69eafd57fd5d94467817041b418c0d0884766bfda3f3f8fa0eb1147253673b57e1c2543ce70b30dc96fdfb6ecd1b6a6297081f99f447bdb48cb2d672a129d1931de620b5878d7ecafb885ffa67bf2e99115f5fc3e55e439037faa26dbb6d0ed8b809577bb4ac51b3b7d47d4b6b3658bc216145b8ce7abe1af5196472e1ae9cde831de60c60e173b23a7fbb314f6578c3e45286f9cb2ca73b45563f723bf82542830b88bc9d189a71ebe899479ccb2249de796b1cae5878b59dd93972406778c978369e397b010d836d33e98fbc57af589e3a07e08d1e4545842c5b44dd95f99b140e641c32bee6a2b8f45e9b66bfc7e91123e1a461005883ec904556f8385942b2afe6d03a2cbd1de877073c469f786abff57de8d188725268f5db723787e4b71ce5b4630c5e2934a18c4f2aef426a3e3ef307b05bab9e37e10dc572821c99138da7eb23e23addcd0cfbbe425bd43bf424a4018abaae969298832b6578e9e4dcddbb703c95eb7c66966d6364d71f2e443bc6332f33da4da12054cd72956f465474042413f4af2f3b68d04c72516299336eeaca07aac2125d385b372a687c0a8b6cbc31e089adb8cfc0162a7944ed2ace16c3fd01a9bcda7bf64fb372072e25eabf938c17c61bb178745430f4ed84a27abb395e35c8c452803966d67208e2b6b3ffd84a405cf00b511293584898af244472ba28006d6c90de969b883b8f041844d0921f8f5c0ce9dc8769e05a285ce732095beb27f07ea4fbedd268729bbfd02ca4bb55ee636908bd5465c5e2395f0c96f8758d0b7ccada9e47078561942c167e5b8532bba87ac388279bbad4cfb817e396ddbfba244718fd4fe67b6fb1f195dbc0a8a51213d82fb06753950f5f9da63250657e2410f0fad58efbd21accb4a9db5150f7387d89a33eb1d1be4528d7a1bcc366720b0f28059167f54172fc91f0ad06c771decc6197123f31c1c332033d57314fb39553b7e59d3fb8a672a1c0006d5b2e8b06da80c9eddcf2d86c1fd3aaa3aba96501d5fd090a7c6aef589a7fc283b8e30315ba4035ded16d6ce8940c4678e1e11a42c420dd1eac5bad726d5bc745dc04661726e54a564ff2d6af04dc004bf12e1aa1c28527b659848c72126fede92c8efd724354e5ef95820f2c8992f99abd165406201fde0e9edabc15f6f87c8463dcdf6be569105e327c7c4ef5ea2c6e0c08ba3698202d0993211972d76e74a346a34ab4dbe4b432e1097183fb61752cb9b1afec435777d966b17240488d78ef2455221ac7efdb367c89ae1bdcd73c38f6bad4a3bc2dc11bd4286372b82d0036bce7d758fff4b56dd2ae8824e504857f876a4052cab83a5de8f7970a96e4ba4c3073823cf72dcb82e5a25fbfd11562be328b976e8b97339d8bf19645d2cf7dc4979cb952ba53443458b8dc75487ac83d4dde7ab081046e86cb9963182fe5d0b0d82a88e5335d1d7d4e042b9c5278c6e466761932f31ce25ee3cae4343459696cc3a40db85ecd86c9dc3231be0ecd2b2b0aa7ab02ad4606f0ca83f033261a01fff499f349cf03f650e1f3920c01e4da17374ee5a47a55951ff583c172a92537a4962cb79472fc3ba4a3552a46710e312df765d9edf9bfd806e9cd7472fb3e70eb6fd3b5fb321b7449017e752e099aa195369711796dd320ab393f7b7284685ce619968495ade850e04f926aa8145cc66f084481b99db729ab72435d7292ee826446ab3079853483a59e5c4d8908b3541ce35f30793404c5e3b5b325726d6ecf6abe911cf6039250b31b4356e87b4b1da74c6f30cde1c19131be20ac724da3b3152fac787b4d8114f8fd5f8b96085639d8807faad7acdc5e82ff5f6372c8e35dd517b7af9aeec8cc8e9708e2dc640e12014ee14bbfb10e2068dede7d399a5c3b6d32ffeea22f9559ced8839bfce865310535a82382f1a53dc734f5817283da5d429957bece3e08f87ac5e53aff96b23d912b6282662cafeeb481166c72be76129ca44c193115450d295f14d944656009bef38387e4f4d33e5bd0cd6e0e1305b8eff87f1119e87943d063e079a33a493c84792cce57289d6d8f80f3c24b844843bddf08c2c5c0dc96175ae4e88cf82ed021ae7ac6da7661874b258772677d50621184dd6b3cf7671334e9d9d87341b8bebece2ee119add0adcb7cbf437213cff10fbdda599b1bf514d69db9887b69484cca859c7d21536caf63d3e3797205622d1274c91658fd900f7a636903525e950eafcd6593ca81aad3e68e16ff6ee67bade05018eb1204fe1166fc281321aa4260a04183ead9e29440784ffc2972b64a40ff5cdd9a70385537799dfb954354d811270a1b0a736895d110f435ce71dde88471df67722d66880cec268029a017765a36b0f84016f33f48b0001d6772f5eb63525bd59e74c767cc1c2a6d3f6ec2599264a590bd4ce8687fe0d997717295d63b792cc596b4e4892a1108fe7d958f974132e035c0bbce5cfbced44542729d725c9fb5656407d79d6dcbfe897dc3568e8a94e4a59f6e75c650fe341c342650863bf228e6b18a63dcebcbba320454d52fec79644681de4310db5b864f6972a1ae54a63efb64826762f0098b1be63302d2361572d66ca50ec266379c23fc72133524ec2754f17ec87f28984e46c765e6b81099c38bbb19a1828cec6f113447647676d32bd1dab149353dacc40b2f1c7d6eaa3c4f76ea3b0f0e36d99c25735f1d65d15b83d99ec5b958b21aa06a12c64b0703d5ff5aff8085c13a959ee0fb6e32bd9420b8fb80a9ca081fc9533250cf53439f2257e6446cd8d3da18f36cad64dac069d9d3520bfddee55bb0676c29ff4d60dce09e2f5e080f058963fb717972b7f691ad516f0aea57fbed61772db5c12bb0f7e03372f41768b68f2b0bcec472c681f8a939940ca725a91444c9c05ea01f3c83246196cc0c28d01557e0272f10fafbf685a05f80d400b680a686c8d6f243f5b65716a051e7c9060a8db1badb0c175dffbaae3b6faeb6f56713a8f16fcf58c84086189fab7d76a92338c564c67221348971e43c636e10561de88c572da70340d9de91b500bd943cc5ab11516e722f3c271e2d4e728d74d5ab7e71ca11282a1fd810904c857b631c6e7384b006723abdfe36105eae972eac1a764154a551ba2364edf98a7506dce75a0d309f9d2012f678f01bb0349723610f7001bd9a85a521993046d07ce31cd030e537a7d872f695cdee5536f6c8c146e3c205eb0a2df36dd52f5e2da69d5211a6a63c006502d124388c55b07bf4a5e6e187b901a50e210d7f40a5964739769ed1bcc181207284d17ce84ba1468c08679db1d27b33f157df4bc88fec0e9884e4956d365130723bb8bf7c2a4ffe376ec88ef67d6fba2c14af0dddad2b6fc00f47e1cc60d64e6b66a6a35238a62e4a6764cc3ef2146152c3dd8454c8ec6f3438bdb8252d113e51a8baa5a382be3abe7c4445d71209403726a4958a21fa74539167e78e0a1b6d72e38ac1033b4333c5641b44e728922302e4f1f30a09ddfd15ea038b6625d45f001da4687e73e01c039a44a7cb17b0574accc7dccee4220922b3e390e1d58eea0bbc4968a7d8ab8dbe7b33078ddca260e76d5647fbabbcc2fc1d2476f08232cd7244b01895bf827aa8af3aabdd9fde2e1b2dc915543082a4292737468cea093b2284952558a100410aa80bb7f0638fe5b66ce842a37351b45671a4540f4c107672c5efb1d919bddd0bb315f65d9924d3a639d0ba4575ed553167051440bdec2272cabdbc489338e7377c781fb96d25abcadcd2d9e99311468e1944b45e652d8249c3a7911c8e0c11662bc410fc9b230076dc41763e596530ba712fc6d0db1d340415c102634f835d1474bb91efe7d60dc3898ceaa74aa5fb9093b2089a6ecec5653af0ac207ffe4066ba53465a8ac1c969c85764d7d900a4b5a0e100060e07f070a9e9359ceee4f1d78420cd8377fa635587ccb268aa155c4e987dd6ea5528c672a4a02d506f77c38f1fa1acd29d6ea4e38b1d7a71431da2ef52501e7652421872bea549a034869e8d5a7ce1f8637a4eb669dd55abe1e5df5298719ceeeaffca72b3c365ba3981481352fd5d9bb40d2aec98986c6fb39b5f247e1737f290a80c7293b151a520bb4427f94567d495af4e69bae6eee21bed24be02c9802c1b296d4e8c5758abea655af66d84aafc9427a32f1a328c619c19f75a1743714755185625d0b71fe5c39e91ce919ce94db91f6d8d216f7a92a69ec27651bd93e3bbb61b72f28b8c5a2a357eeb5a9c48f970b485605d430f7748dcbd210534e57263efe572d2e3b0aa997145b935cd3f9b9a1c45738ad506e53375056bfcf6f4a35bc3b2624275a6704f28f62ef3c7662c5ff71c19fbb1282d0c50b41c1a5b361aa7d5da72ddbfb8324860147047d40baf752bf855fae972448d3711e7c257c78ede35121095751f4f349507c95eadcf62e6682f43d320758751c0c9bbb113345a6bad8b720314af19fe0498c79cb2aed7a4ff4841b29bfb8e8fd7097a7225661c00f56e7233f66117951ed57a6a6940c5138fa982ff34ec23a560accee8c82354136052726a203cb86e13c06d0b4dd507a65b1854dc2d6eb4beed5aa9d958ea60653883726a732748719fdc86717da2c6f04c10d1c8ee4ba40e35431eb70cb9153fbbb772f6988e155a5ac59ed5c53f6916f20af66e529dedb82d27554323658da6cc27457def74f2f39092fd28f22d5719b5ff932e05e8c8336de38ede14e76457b2a17246bfbb04ccbfec1476153017898ed8b1e0ed0e6f2b356cb339e9a71d5b29437204d5843da596b2d4b84cdb829613e8cb0485c241bef6b9943717cd8e91508b7216ec09bfb3ba5ab8bf64a1e8f5a5a707eadc3b4a13e020bf18792be90b680272265b229f82c6a83bb9962628dd4fd281067500a04bf2255107ad1a40e326b7726cd4985b392801eed71b7c7a8e21e5e24ad9f58c351e5244142dad86481eea33d77373c162458db88b8518aaf5497e495a64a8998791cea79227af2acf8687727293653e073cd97e73c406b7906571984b5bd66583d8d2d7f419fa9b1528ee2fdadd478fc6b28d49af1df406a3aafc0d68cd98beca9dbbccfa217459b55f3172a516223bc4c5916410c052a1d23f5fd639a24c090e389493c491aac43ff0691732c1df64673d5d285271e38c0c04f80a2a8801e7d1bed704cdca1d6128eb1c72b77e29e35b56b7c4ab609c15256120858bd872d6e3dcc39587ee8279b559ec114644d11469575dec78130e20610ece050ee1cb5b10587feb161092660bcf630af8c36ca38aedaf1c7d1a092ece3ab65de01ebb06d82aa950590b17b6b6675225267f917706e1b1156fb20b340241f9c81e7bdbf59b441c23896e96a060a19116b3271d4b47d2982d7a9288fa74d217d793b2e043fbdef4c33721af6b352c497283caf4be4d82d620b50f3e51ad8b871e73049d494fcb83c647198eec04682c47de04e42d00c6e333fe9a0ae881c4f1790500033ea1b28195280461afe01243002eef2f5d0f5eebcaa5172ec7646c9cad5e2716e3685dc417c4e34871730e8f726d47dd2e521b62504203793896a2924e9c4476fbf6ad5c23b21fed7d7f81117272b465629be1028fc7dbc03272d7bffea939b2148c6bd4e240c19b2586844f641bb660224520e17d82c46d98260ead0164d415ab71c3fd83100eab180680671c4791c7a4ade811706739e78a41fe57f0a85b76ba7b366f8165ab74e072585072ccae3d85a5b63b5f25620c6c2a2840d44f2e8696bac0d48aadd5470181394172da8c50962597a15cbaab46985cced87f3a14e3956a92a7f1c22dc93f15049f37de1ebf34adc54676d614e2b492a53a7313daf6fed86b5b51a9fa12ae78d55572be15dfea7c3c486630eb3a1c89cf6a05c2dc5a43b248c75e5f6b273d6e08b070afc5dce95c40cb90d351ccbfb8592fc60a57e20deea07144df56608ac3586f72d125983c1c84811b429b86ef08375725f3c510572641ccf26761161115808e729def4aefa3e0a84565855936e5443cad6df647d632dd8f656eb48bca981e92729b764341484875a8fb21db2f3dec01d3b463022c52454cbf16397642df81ca4f2330861189353427ba5e90a955e2dfdf970a65bbd533f32131376eac78097b68dcc96973bcf1308faa553bf9b6a58c83c0e4d757b9551bd3b31e12ddaf9beb37b725bea2736d3096a2a6e92564ad58951d1b30c8f017ec432c625a4520d82672e67e49f098b4645adef88cddf38a8fcd18636d12ee772f7c4bec09687cddc66098eb8f1f49fa5c52773cb3d9ccd455529c930c6ac6a2b07fef37314cc93d2068bef39a2a1b5c913a033b29338d4bb8c90b6bd3d3b7ed10cf179a7e3829bb7f17e88992e09c5c3f2e09fcfa95c94392e43adf5b300c50d7348a7b7335aba3597220026d6129a22ba4ada52c80bf4b1a294d7472a42259f69e3dadb45a9fc56e4983977d2426c24b33b34b825d3fce8a00c866a81298b3f0cdc8f3de8b56b3af182e66c728cd03dd184d59b0a61e4f6d02af5d8886fbdd4ce54bbe6ddd46297272e556f2e801c91a7b4e6a57906b94dc9160a49ba10b1c563728ed31cba36021720f23bf37349d11ccf5f34f9dba335ffa34732d7b2d478d3988001411b3b28e72c30cedca0f834af7fb83d403494537ea4e1660df7bb15ac54b6806cd1d89a96078225244fd33805a6c35638d78306abde3622b60be3cc119f11743d1f60b8248e442c481a445d8f6735950aa0b0ee977b88526dcbf4bb26271b6f120c930443028537f492a2c952ad6912fac85333a75d03d8b563b2efff60680968d7652353fe3a434a95d94f1b9ed8e6b697269a0a20612c21137d430ce4e0f52a68a7c286699ab62fbaf1ccb343c3f01780b7fb39e6137be55b14bc7f7333474d7b624ac374340e1eba5c9b0532885a542d61c3b36dc8b20d9651e6e0d622647c4458279726198d580790be28e86f17d4114675c0a27fe2453d0811b15f3ebb4195cecfe6d2e8958c5ce494be08262ad4fcdea533c8844a7d341ce6c72633d8979c2f19b725c6718f5b5a53aae02c80d5e8dd26d70c68cc4829346f0468f95c9b3193523607828559c61bf4d03e2a5448bfb8d82834c32fbfb17c4c83112d65938a9114472d9b91663b53aec6a756a1baa486157f746971a9db26ee89b691b8809d9a3130782da20f21f8a40ade355249e66adde93d31d324bb30f254c7444d1c4b8ef7e11fe704b0f9eeae5491714d3108890f9912e568b0770e507bf9267e45bdecfaa7293f9f43117fe40fb7828acf062b7e464a0aff935d173b4cbe463d439e37fd64e4d18259cb1d9e0b653e1de74fec9af9c0215126f2e75c0d5be4a93cf9ae45a72d74df0d95b92ebe1640721319f7905814180a6e120951618710ca4b83b3f30348b778f261c7ecaac3fd6eb627bc8cab4a3a1d2fc32960bfe3074e7856f23a93e6cd6c9b67f0f14bc58d8845f7bf9d54cbe3d49930ebe8c29eb21fd1904ee2d72970751223fd30d643570971953ec9c21c14457634ed7faef456a9f225f550672c615cec29d991ee23b0a7928f4d5fa3210a2442cff791932f3654c2a086cd83f080c2d40787b527b0b1629100c464f685cd896a20d80c658313bacfc89861872dbb7bbf1fa25e170817a7c2fce175acd0abad06df0347df1ce576ca21a8ccb5e127c09cf6b7c7670c66e09fa9b6ea130534cb943e4c78b2ea8a542e7143e1d7250194ad0175c950a14c56e21c277ba67042ae0f2693c66eee572ea55ecf50562fbbdb1caf11d18bdee1246ff3c7e564fa97c83a2fd20095720249b8a54c0d572952b9f1e008bea396f901c8f3e90a1344cf0d5a88fcdccf60ee5e196f32e3572cc0394d8f4304b1b4544aeb0e6a4286e9cead5f0d17497033d7e550cd142e772c70e8b0358ff50225c34402370d4f4e571d70da2bff4a672c881ccafa814bc3490ef461e3b999bd444da2d9d5665c1f283f533a67f0c5aee4e34f73e31a7424676e8e66dd527b1063a9b170ebbc64dec34bb82766d023e5d0fece82afc59f70e948ef86338281785cb57b9f8d6f039fc6a239536a4f10117befe408ceeee516dd5708fce1f30fab8164b9769e1ce57a077f0570da4f9788e965d057459b52b10921dd342580e4eb8442261fb89b3b9c8bdab8aaebf26a3a64dcf4e07c6c7c557836040cc88d4e063c74aee8d8bde422e541bc8a51788a006d5b7967880a12072d2e7a540b9ad2906b4e2ed4b633aa5204aa834225a3ba790c1f07dda287e3272233a34557c7978da732a9bacf26c0ba3a170fbef192f13cc73519b8eb4095d720181fd8858fd5d2380957d90ab1ab01e889dfb7b448c2a1b32d981d3c6b47572a3d5acc19e12668cffb85061b082fc3a8ee3a2234ac10b040078c55610e4b7498622eaaccda0b6d85f9038e24b738cfc62c20728099daa92fbd3eb63c0982c72d37828e192464092c2d54ee2a60879a5946fd3688c634a25ef20ed9c808e0e0646f83769314b9590487220b7aec8dba7030b193e6687e90febd2e153b48ea85d6a3c0eb1f0fca97b76f9abc7218daf20d5f3aa3cb9411ea71d31bc0b698a86727e202ea76de534a3bdf63afb813cc9e35c63258836e0fce53a9101e4e768fa72243139f08f11541ea8280f673313ad95a49658446bf268e49eb03f9538f1dc722ee2c74e10066358abcc8c5c659326232e2c2f6479e8d7da0970a0b4a3e55d375205a54bb12243e7dcab68677d899af604b80e310fa98fe940e70704f98a2d72cea11534a9bd1fc7d324131501c3bebb91d7f7f63a43672645b3bfa944f048687733049c5f95f8e99ef24504e01ad13fda0c367f68b4b3f1da0d413c4cb72e5d6d521344f27021a724c41ae844d8c4d64ad807759fa84dde8c99ec61697b77726c0059f4f314aaedfad8715f5e200632ca3e037cb85a688922bef92cf9da3e0b1cdae31b2c0ed02d02d115ef76c4df7518dea1cf7499278c8633d6857dfb1d726174006558903772ae00292155c660debaa6b5b6bfb73bc4b325f1e3d5485672d4f85bc6bf8499a6faf38d3ef566395e865c9fbcbde366e2ccd8b30460915607c4c8d37464a9b37de635da01ffe6f8825ff47015e025d9abaf9c867cc16534723ba933cfa0becb31219cf02f51709ff6adaf009042146375cf9684f343092b72cbd9e24283b528e723751302a61a459caa170cc02c7e054dc2fd616d71e77a677337b45530df4dd50a1b990f98940bab54ae452e15f6fe6f9228fb4ad4ad1f72ca04a023b2949e21511ef740a735209f14583e16410ccd394d53c0256b28e97278320b758e460e5ba432d3a232c319bb54b46efdf11e747640dc4dd64706cd727b39039201901592c05b314eaeb7a7bc9de4cd0187c3c326c91777db8b78db5fa0e9ca031c0b9de072aa3073f7191d18775ccf984ad308796d73458a0ff01341238d723e790876a4bfd59f5ab85b90119b1eff0dd0054f59def9779bc4b73872895f340f66c41767805973890b98678dd577b259f5ecc950310596560535ff56c8058aedcc1f65d6bb6e372ee5247fe80f250719356426dad78c68549ea2ce1845ee81d57be60820462a34b93ab8a2edf96689f65e80a435ef175fcdb6ac2f727d8f07fdd601637838a98063fd558e1b9a09dead6359fce2ad0e03e2331d9d725d32cc3a2a795beb337bda6fbbab1dde3f5a605fba30c71340fac9e6ecd38c1dabc63848250bbb3e6475f029fafe5501b49ede2bcded3fc500c16183338f5332df60912b99740d21198401672e5c75906c2940bb9849210b4abd1fefe8c9c3722998895c6893986dceec789bf04f410cde75eedcc41ca8c2b870cb1243830f7221e6ef3460fbda334ad2a78ca7030a6a38868b988d236143314bc28e97980f720f4207d8df4350b13451bb6017400c14defecda55e0016bc25e42ee5f62234722ff5f70c47665b72f0304f6e2db217ab288f554bfcd1e90b97e38258ff21d272144c5356f95eb129eecda52438453b27145b277cf0ab7a6321c9d3292c406d7242268bf6e42724611b56e5236394a6eb5a15f1399748a4469e9d17fa006f63722c7cd32da5fcc17fe3183fcad7ab4c17e068a9cb460f360d2f0fc5ab5320fa72c59ec8532f3b846eb48c727350bf20e8bd94b3bdd1ec14d439b9853a20367c683cd17eaa7469d833db2dc0ae2d3c5993c43cbb09a922789f0d35b32d198fed728e35dc446cb0aff81c58f9e7a49a5b614eb76ffcb2f9e716ac87768821cc3267b4b3ae9703970417ca517631cc4ba8f326364c9e53897dd00cbafea60858ac72919a511a8f93df48ba1e0e7ea17075fe6340b5b176a24e5cd9890cba81c55e4c8f15636006fd787b4bece128be1e575c03bfa5c52ebd857b9d7a5be8c7676972debfd77c6a279aaac476a87290fd36e99b15aa5e2edbf02bca70c2098ddd11722addbef300648554b9c45fbd9aea131030c9a824b1ed25fa4f3d0481c7a7e13e76f5228b53463781d5e0b68b4e76989096ff2a7eadf7dca03b39c782ab497610f5fbab1fd91688add943454568ef4532ba7b9b2ca04586d7ea7977e34602a6559d6901f091d1b421f335e2914b2cb52f4fefc4d64d24e505eeff08adb4fdc03d047ac6e8035aa4f5dba7cdec2c3b25b73d22b77a94ef7199fba3514210dd7772a3a25283954bef64fc7933095a8de6bb8c308ddd0cf0f43e952b0723a84b55722c85d2c2d7dd2428bbb9ead8caf60bcd68b2f9138bde7fbb687259d7938f8e618f1f4122a65f86c78d0af51fe305f6fc60e6fc9249e708ba299675c7581c26179e927157e8a2104146b1e810ba8a52b54308643fe681293e647047821a416a51c13063a29d3b1e2be6f99de9c5bb149440136ff1ba0fa0a4665da1402923176862e0100f6c50058233a926dc975951a824abc181ee6a9335f9e22b764d66d45c8b25656e6d237d3c2fe2ad6ff91857ac3d80a3d65133f8ea41a0edb703114b723adcb85745fc3eb8bfbc21a81be6b58bcce59765af7a08e7f30212411da6dc02ffd53ba61b4dc98af680f23e2cb704f628f2514d96ce74ced8140dbc1a42c5726bc8b39771dddf6b94cdd1cd0fabb60833388da74d0a68715aed601dc8f3822c4aaaf84735eb7b009390395ed64c0e833c29061d5fdec57d63e543cc479c46355628eb661287973dc3349aab6169d832868c03e0a46977f92eab1fa09eba6d60081b8bb126c926a7a62cb16f1f5899b78126e5b0eba0f02dea4c797712b7bd5de810ad48f263bffd307504d9b3c21d14c58a935ce086b9adbc913aa0b187bf722d14bab47fc3d18f2308bb838c7fce9b75e7d030f57086b1d6619f2e43ddd45ee788e860b61d00d51283251d14cacee2136c5f30e818bf7b0362d4882912b161b1955b85dda3cd95b2c4410f3b0d40d37f194989663004294f9f74be80e49e72d0e293fb957b2470a9e95c493d643ed0dc263ed768b6149cc78a0e1039e51372e98e58c7e3dca22753cd87156b6aa1d325bb12c6ce2a4787f327640e14df6a72e20a1ebf00048769338096f91169739d9c5fe1a0d41d842340d8d24b67ccee36603c2c76c2798f2500b41e38de3f6b68a20c5566dff441afad871b7ae4fb4e725f6e08b4f3b5195dca07de1022eedd9588bc9fe404c9ca1b90e76ebf587fd67276c9fb47eef2dadc18aad2600adc78f392a722f6ce041b1a30920891d3cffa63007435b22abc2553969917878e1cbccb654934d5341f8eae2e540123fa4a4172673ed5793fc0a44429f88598e16a813afc2f1aef71c299c5ff09d3c1b5e35472d11e22d9c80438641e316e26397f142b60bc7aa5972592733ed704d2f1d76f72bbb3b65c6e4d5bc61f09a8d58de62afa144b7f93536f2d85279841ee3b4c4272e2edf2678d1e071c54f498718bb72c23e3b4226d24d566d89f15614f3f85850de79a318c5098c2036e97d96e89ef0a12b303e57c8d714f94e205d758817a0b72b128f5360cb54bcbcadbaab817e828e21b41365a70281dad60968c22fce38f62061c3d4aa3641b3b3506e6fb1e4f0a1c13a7358004758394a1ac52b8d96ee372ed76dd23fb179211647804f30ce78535e2689f74bb8de5a44080d5cf4bb6424a961ede26b0957b5e91c499cd9bd53a2dd22d3e756d2ceb42c316835470838e658d0a16ced742e2d8e76ed9a12c8abe4e7ac179e452f8453f4dfcd08324f8a6727698416d86fdb947b83eaa0feda61013daa0698680c82015dffaf2765df5f8726d8e9f30a5ebed183d99039df5dcca8bc68bcc5197e667aea2c4ae053a9b3c72d5b55d36af49d6963cd1d0ef9ead71a3a94c0d77bc418bff22a602a3258ebe4fddd184aaf7a762cc8298f302e69d26ecfccf86bceb4ce4b126567feac821ef72b341b9bc500ce6472641804f7c59c8f58b8e9d9a5790f6d385d0eadff184ce726258a6a4a8a538da55aec9f5e05322e86e35e328051cad4d14d9a98b35eaf441100bbb9087e5eec4ea1d2d40d36d477d44d1346d1d2efe30e3b6c37fa1fbfd72b4f96b8b976030892c83b5fcc9855e603aae748efc59ea65005b5516ea297572ded966a9891fee5a62357cb378d4df4ba70e88c792eee1151f1b66752a2ec27220aa39006266442dfeb814df39b6ac2057bd2a8eeb84f40e04ab41fc6685c01d36668a7a65d0c954738b996ddf45f362f5cf5d69c24327794aac0c8c739c0a72ee344dd0b973e7b3af74c436f0429437c93b554c153766545b2a76ab25b1f472f145fa467f8575765cac2fc7418ddc16ec0b791223f822b394406f4b1acabd728927c4aad0d8627d69d25a4806c8674f61ea0df453731c93d274218f26fcc8676c8c8b5eb39933573b2ca0e1a662f02cb27c9b85606792a39819c6ed423e432fa261db191c8e5d50979f1556c884a6090cfff05edb9decb17b33b3e42443b910a1cd675ed53b9e348cd66e9fa42c4739837fcc18928931dee329ecd2089b0772febd44873b3fd8f90f333b7ab0fc2a89ff7db805ff691693648a8ac390c703722d606c1afc6a141d56a85b922abaa2d369dfb4455ca72886b687a3cdb25349110d21ee571e00ca092ad6386b558ea2ac0241b8c261ff108361592e91fd3b7622668b172d14a2b4660954e1bf44b59acb2a2063942e2f9329cf0f93cd4203c956b839fdf7e6b38875360fd9942f42a77656b5dfce08c37cef7feca36d4eec287264b285a2b56a93c44382047cbb2657d4e3829639b83baac485f95fd74853f67273e5a4b0dc7e1f804a5a45d7fd3946fbdf3f26dec21857d53cb0b26227019f7287089f9d367c222a79b3672be85e565d19483552b414332fdfd11432077756722f08f77541fc303c0db4704d4db3e41774b7740735325661d54df64090b535190365c1a3bd78eb6defe7d64efcabebe37791199806ca72d80584763a5ea80c72541d92ec1cde0ae44f776697a89ecae7304da81a5515d6529ef0780c86a58e167448d50060c65c702c12aa49e37f83997531bb6c12768533e71f9e36ccf7d172f579a8593160aa898adeb310601f8ada3fafb5a762a61e4904a2bd827d6d047286238d1eab1e80fd109344ca54cf893afe10ff0479a123b4a43a9fc71c70ae6525cba29ce61cff9599bc1b070ff85fc45851cd7087bab4c31d8adf287ff12372f4c63fc8ac9111c13ed7c5da73123f0664f317bdd49d77e6fc8f4e4ddd3a5372e9d00f3be6bd4b493af2625e656dc71ef0b2e68577e150302557f4f83379bf7201c52bd9e4426a6e547e6a8f81ee148366c8e151f579a444e741e9942dd0577250d145c6086673043f73c3f1296f067784de3b272687e73008415bfa0be530580d10fa79290e4ad89a6d62a2247da54992149dbd1796dd980d1f664c37e1922d26f17ca08ba3ed4feb7a968fd9aac8ad9b9858927ae1699a7b1bf610daf025724ffd8418cbd831b283e5b837c4a3bb6e30eee65d53d28ecb2f14417493ade95931fb3b72e240b1a4631ed3949a94c864af8433be61c4ce8539ff9a56619ceb72d8418f835d82bee310cecff8a80544c0e60ad0d8003b05197c5591939cdc8072e9e3fd4e1c79a77ae597be9d76c3b5f5938ce50afe671ee784c25cbc7ab6c072c3e11198edd7a3c8ab1fc0b8c8735b15b450e26f55934e5094ae422aedb44a7298e3c2787940fd36134f60612d32cd623dfcb12742b7b9f1c3edf2616b66f33ab06d1f9f5caccaf2cb6450d7794c36ab4fdacee256cd56fd5f356343f60c0652cfb32489390c21e328ac439df6cc316733d6ed6472f9fcc137fa6e257dfff6728c965a6e90a969c9aadfb89a872f67d869253f436f42cdc71d023a134003c57283269bb75cd2f9044018ac38865e3cda1d12e92e5500ee54f229f5022850ff3d55421efce6b2645e1eb01aa43dfee67514fdb7095fc1c0554c88962b5d186b72e7082eb63af30a1a171b609ebb7e723825785011347a68be070d91055bee410063899d195768aa042f9ac2aab85c0023a124bd53caf59d76ac63b3631edd4b722c708f93129df4093bc51725e27dfcf53a86df7f149d753eb93161f447274d729850199d75f41b980c21166039068b473c0d6dbccdb4833122917a6ff01adc728b811f49e23f1dc112a6b74c1c46bbc9657fc02f5d7c7734cc977560402e4e72e4d771bc1164919d272ae1018d470eeda629f33b1ee93f8fbca53db0f8e96a721ecfc3ceae2395e5cf047875f405485a2799fd38ce00c41df123c929b887f3725b57b65647bebb1d4dfde410ecc2ae92ee751b0a3d026a6bb0bced8ee781971c2779580c553a7233b8fd0363341fbdc0ebb095639b927b81f804aaa4b9bfb07272df8b783e3f4340ff010c4552acc2b9a9682e2613c9c2b155ea6f46f67c5e726b20c05ced6cc3c5443f69b38bcaacf239d2d8223977188aeb838e1b58ec3772be4e28f7d8ec71de6f81175fb55f49f56556d25ef59b08320ddec77a95a6e919997d098c10505b0c9012a69dfa7074d540580d0e8624e95cc6f93a4273bbe472b82a57e6949488fcb0422f7fc9bc6f08d17e9058c9208ea43d7c88b0a96ece7284db561ac73e9749db0c9a44883d80b0d646a2e8c9c8de330a369bb14d31cd46266de7a7222221865f537a6ac95b9c27e8ffacea919e14d6ea9599da288bf672b2ba64bc87d14faade6e139aa8e6d3c57c9dd99e59c31b2b9d3570ad4e708772bee91ab0163721880ee023e53e5b8b32628c5c0c61fb1a8a81f9d1d2c044b672c8a9a444187b887453696714e5f419e9a383ff9a81afa5dcc4d0543600aabe22f23753885b98818e3cdd4db81e709f68c93626d10ba98cb0019609edb9658c14c23d4de8f25048584ee134f24c8d4fa3366c72b82d59a732ef440c4b238b3e7228f7929b4d8ec2da9c9d8e30995d97957178825dc56d74e8407365e4a91f2b72e1aed767c9468d9c554341774d612f08d5c47c1fd84c5fa9759340a101557372d5ecc5a126149eb6a7ea0b9daaa558d314489d143eef8f09cb3939a3d826b67258c18dd7bc15a8b4e78f1402a5e04440d5a2e7241eb72b07481b89a14f423f729896de24748a9ea76367004a4f5214cc13179fff04eb911cba788878ad0d0772d63d35585d12427b3b2e582b28109c6c591249a7b647ec03ef97240a1cedbc115656cab53d3cf4847cc1770f6b2df127d3e120842c83a64941776270d6ce68586d19540bd3e581475add2d27c86b45152faee886f4e68f866870cfd28e2f68136fc455d63d1e30230993c8f7704e07affa4eddbef997032ca66268e71a3c557289f9087fec54b0b075d0bd167c4fa72fda88afddad993548258df5f2a427a51f0ced61ac9f73ac0a4aeefac39beba3fc83f3186109e00abff59bae4b427379720a2496bcf16dba445dbc96e7180f9498a583ef5caf20ff337ec6de81fadebd7277fca67993fe7e18262694e1cdfb1796198114d4b956d0416bd9703cd9abc922729bcb500e8d129050948853aec066c0e6c446ac6bfa599ba1530d71c94c6972f889bb5a01c364e22c4cac9c3bcfedb6cbf7441924ccfd4c667eb242adfa20728c572e974154b0b5216b7a2e7b982eaf7ded55f103cae88804085d56d56ed172b818d9625875deba5034bd3704e187a2b6e3ca60f84f09b94a5207ebba7b176a39a0186b4a132f2b5c3683e3a564638cae44d0a3ea778f979cd6b56650868372e49695a7f5e08618e7518ea83b5a556a080a7b4fe5faee3f9815f51b657bd872527e52968b7d7403688276f0410a3a3978b77cbee6952400254096b42d9ff5722a8304a974c8cd1abaac7c6a6eea5fa524a861eecde662ac68a8202736903335ef4bb3472c29b3c637f35fdfade0920bd6e62ff9a2dafdaa892339400b5da5728213c6fd3ba1ac8923aa67247ce9b60425e300e8b9a8830ec6f1b2472fbe23672d01da649e0cd5e2fced6ef76e1091890b4b8e10c0ed781096727e2eee4daa72247778083fc1a9ca99daf8c1ff9ac9342600caf378ba6e5efd4079aea86968720432eeeb2cba46cbf11abdf8fe5ad011de51862fb079415c8c07fde9155eb2720ac406ad774de8f0b776069a8cf4ac5d759b15b8a9939297c65bc35024bf2011245e8e7eb7842755ad1be07c829aaf51d1ca307c6df375fb65f73ccd6a34d3532d7eb4c69e54b8672bb040e2d9cc70692b4126d29c578d5de9075e5ee89cc3725a5c705292a87e4c2ff1381f9220d2fb15b0c7b2f6d4746d00f2c96520874f7213db63fdf4dd86ce31fc9e7f967ac9d0a283a5827cd84909a6eb686ee1a33c3ee3577484beb51a3c7de947418869079116e49116c1cc81386e5ed0d3d53dcf2e5bf69d408307affd8c712cc79e627b7767bf152578d68311558fe21af7dd02725a92ff7db13e909c9b4f3fc8c90e46736480b6a2d546c7db180e9dd749afc872fdef88fbfb696cff59f19ae7bfe0ff52fb2e8846ef7006fa587bfde9843ac47286b9f435b761f991d3efea422d02e85c9d67b6a2abaeeaa94ebe4092c525d5721c4f58f5ef661124d9170f73a022d11ee1a8131019448b632a9a4b98267a8172b1a2f87c6f23daecbfb6de66a8985fee64f61f779d3820bc53b87d768ccf8c7270d24c9935b23ad413c5de410afb4c850fb0bd53110391556780d0c1e408c1417b9d80f65844ae663cc552a4d09f3f242817e3dd823a17ca5d6034eb8aca28130b08e0d2eddee3f65f4de39725200e1bbc2967d3491bfb6751de5e6ea06fcf0800b8a55be3e7330840ab25a272e91b36bacf3a213f6fd525a9673c1b2982c802759f97f2d75cd4728fed540e1c0561483802a44632c2055e85ce67826d39062a30fe29979a6391344bf2632001558d46c4b6e8755d641c87770475c6eccf2f72e40226ddd978dcd1b33a3d8e5a4105cf22f81cfd989b3882a44410cdd6a5757240f16fb938eb3ab06104d6608e514de248abecee7f4ec2dcf50d06461713aa0b4fa3d4f7ac351aa8e0a6b73630aa7bdff0d575b4ca260aef55cd4ad17767e172a1a0d7b228a221c3b349dd354a72a3187bc9ccbc394bff4021ab4acdfc18027274ce3260520a9eec2a51072fe502d124dfbc1f64fa03339c54d9ca0f9fa25872c5375c57b27a60c894042e840110c738f5eea49d9de71214b8c5c73e940d5b634af36c7e2a350ef8793f4f610ed9c708751b21bb355005608ff791d51c387672ee87152f1fb9a78de9657b76b0ad99897bf4a41c3f8eab1655e78aa7b09d9b727ea329551432710467706f338815d709c369be2c65f080e46349f82ab4ea797268095443c462c9c50d2b4badaa5102a2641721f4821820cb579edf59f440a24d4d9c5523a013a63925808524040e74089cfea052d2779ecf835b22fc56dc79721faf516530cfd5e8e26aa9114cfeab958de68456418c98c0f60cb1ed7c60cd72e095eabf777e3d17986ffb51d274f81059e5f0c584587e04bc6257189a7fd9727e29e82a3ecdf71cd4e710ff40be28a7dafad9c3e38fc056c46822cee773e072f03f9481298b351f0aef62e9641be501e3960b7dce7ea2c6013cdb95ffb5d97234613749df05b0aab2ff2047fd5314ea8da10e3868d6113991c2348f97fdf70b8f79aa5c4397b4e1b0a1d4a27f75c08a230856b9686de0d4c42a054eb9658d72512e6a79a79f0dd751e698f38b487f1b592507f52e0d6b5711a555389ca8e3629dde8108e9a15205ac707663e1f2868981b6ae360625419ccd21b61c866f8072124ee1b9528d5432460c786344ae0845c2e44bf468748cee220e2244a547547240b195628121bf083a957a8a18fd29c882d60f8da0e3c55558b1c70690813272506da0d0417865b5ce41ed55558a47ce902f0312a1c172ff0343f2819d3a5a5d3ee073d5db9e725a287e75c0802a15f9e70c6943b2604d98258f672d083fcc44fd41d610b3a5ef0a4d2133f9886ccdf7030b7a325c0b58a24431fd490e321d092c1c1c47ea2369d1e9cf43cc65032cc7a3552315ed5d1b003d4e2a9dc567ae72c198fe7588fbe581609f9cd92fdee17289873db8eec029ddd22c2283ca92381475ccff63de1a9179d2402dd817e7169d84034480dfaf76069839bfc5684dad2b06035f621abaab5cc61e30092c6c4a479cbb06a35db70ee17f239c4b07a7a17242dfaa977a805463e5864c00451992db98b9b873e9bb47ab48b9ed497851b57218705f31ab3ba3a417f7123b047ff7e87a5aea309d72bbd2f6dde8d455abb2728afd47b6048f7b51230aae4f889613d98e7d600d1952b2811fc93a205fc2d72beb38560b2b5933fb33a57cd244236a50ab9fc49e1867eb95ac9d688fd915a87269e43890ed1de420a5a51751424623c0d83d55bf37391426e72b8c65c557bf721e405b21187eeda3dbed005b3eca4d6b75b45a4349bcb3be01e7c0f3f2068731ca06a6f88823cdbb26ee4ca18ca8a01b4c999453fd47ac2f4238bd25e6f32872b6ccd5365b91a07b559e6aa8865b5df77c4d1ff57d64343471bedef38dc81c72251fdff1af41fc1f9f42bd1f0aa8df7a7aeef3ac91178862f1458658c6f78e726e31be272a0f36088b9f8aae52711fba4667eecc92d529bd8cf9234a38f5980436e67f18b3b84593a67fac9ae5a99326bb8dce59e36027814cf4cfdcabe14b45a26fe3190da7f9637f55f9629c9d0b087efe7e40ba1c333054426dff83cc337260e66e3e0e86e710923b3167cddbdfb1ecec08e743e548a11b4942fbfe681e46f658bc36d70e1e6b099e67f4c19398b5fcefedfa188eeda6ce0ad877ce837c72615ad9f3e55ca85873e0712f5109efa36478dddeb42d421dbbd9fba0d04af12d2aeae651b730af0900329769570f423163c736d7aec415f6b385360acf2c9b72853a311f222f6d89da57b0bd639d2bfc0aff37573950fd91f87d1bed10ed2a39975d690141abc037f0eb352b683f92048534d5a2ca5aff5fed2ae6cd9e072372a045a06a23d7a543f5cc0bdff79bbf9fe1c82b44d4f8c97cd0d7667f1265ba62eaa7b5c9158a40ba7983f0f82c8a990205d557579735327a99e470c06e9e49725d6cc4fdcdbf6d5a20096b5e260fa44ec65d563d99fa7a083dd9df817668e17223d7602616615f5727fb0f5766db62fe7b50c89010f6c2b1748b29b2741582720ffe45f0c69ab70ee9a512eaf620555eb9ea2e258c9b5a9885675b1dca37d653dca7e1a254877c6b0c9228cd443b62a32422a0ca59b26d0f7345e99f112cdb72f2e5980fed21c34f2f16a254e50db2e1fd6de8ede6f37c827b1ac9c92cf08c1ae3af9a3ec40ea48b5aaf9fc346bfff502c9f266a600ad906581fd57d73720604830880f35538345639a672083b45084dda4dff7bbb6b8569754f5b6ff7a14e728eeb4a6aa6108e29932bc337367a23fa5ba100f04a0c3d5969f12379ff0d2c72ac5695b3a36e6d164e98cc0703a5307e84c87183202aa5711909530bad5ee51f4df109dd3d4b5bee64922b3f90e5f92caf3bb46957887cc4596e635bd5de985308e8df7097cb499c9212961d4c9121c15fa769883dc121a10ead40c3bc0e33723d6abdeddfc2d0d2c65ddf45eef7254e7de58635567250fd2e155fd3bfd5607209b5fddaf4e8c13bb6b80ae1e3bb88579b6f593255dd9c8f46d224e3be99ef7221b74ee2a7056d723ffb4d4dfd05d7defec2b0aad563ec0ce7d11e7c00daac0c84b1b631d4e6f6433aec84f58d306067338f9411080dade62bae33f70f22627208bc6d8802954c2de3e307d12ccef4681bbd49f50e2ac01092c28a9b73b18e6d026f9b65aec66e9eb7f6e1a7d73002049ffb0429eebc47fb7e33993d7dd6d9293dc8c55caed612521e19973ad2a706880562c9813d6535d85fe40a30b6009a7289c97fb91e12936be69452e4941576014de45abab721b2a2db945067de4a54725e44682a6696ba09ed4653038192de540d671b4c2005b3aa97782b9cff49b52d1305b3d94cab17c8f24d85bec8f2f6782483e673263d3d49a23b8da9d0f0ec7247f18139c198be219bc80086f0313942ad7d59592c791a0a25f486cd2f9cff1e810f93cbc49eb318007f43b9ad0cdd2e102d0b91ff77bfc02352c961e48093729fffc37f69a74311ddd5db33f121aa6e020e11690de59ab083fddebd4a6653346adf0c0f68e932955f36e05cb9116658bb4507c3531c5e91877e713b366f1e05ceee9cf91ace96c9c3481aa3430b30c937bee9fd58d2bdf4d7391a06a62fc9720403b4573ea4d4285472b73537b833be89fa51532cb42d309392404577f999722f1fd8c1bb7bd3668499565fa779e5a549f266089c43eda9ff8dd854704d58649b5215d4dccdd3c4b8e88b2936abcd91b49e664ada0ade4c991df248ace17868dcf8aa0806acb37ce607a3be8f147b369d7935f360bafa95e7b7b28182f058722dc2ffb4498a1ee870dd7f3a23653da47a2b0052543b6710b5bf872d68975c6fab16aedbbc278890457b47e0afbaa1bfa84312a7fa5ea536c254a496e7156651eaf4b14adb57eb6a823bdfc43025ec78810332844e730d92efef08047179f921d0252c2a3631daad5f8c507653889f6ca879b8b728e745608960c55412028c40e3ad544ec30e5c90e7dbafcbd0706f0e81788ae61731dad1f7650aed69a8f760afce75306879901ab59be4923cfef3a03377519fc1832ab341226147b4bef31f0848e1c2dab1ffb871fcceea14c84abb87bad90ceabae81d53a6888c8d0c9972ca92d12cf0f48313cde64c0cab291d4031747db5d34de1e305d927ef14b70c7232f641bcff8b900cc14c117d5474958301266348dfc24092409a53a6e9b1477298d995c9908c4e12002329414eb165a9513a8a55d822d49811a7d01e759ae872b62921a0345c004cdab966e99e2d5cb998907410955f84d5abd6cf0a4eb71e0a21a3d3ad5f8889529ec7fda7506cf7370940d2ce08c0accc06fac2f00d7c4e724d8be0bcaaa61a52b09bc0174979ce580f9fec0905ce332062ba63049a4fb20e68e8cc174dbede4070cd30b51ea8407c3df64b9600a2cf5d996e5854aff3f572144f685686a50bbae467cac2db95c5f7426460d26215bba079ab21e08ef1264e8b26ed1ed2a6cec93c05f765667a6c0a0f50226ca626d3252e6fb7af9e2c5a72986d8c2941118d60c770cb5597e46dee433a018ddb2ff51a003b7dfefcd0af3b1f658f603abc4b558fff79eaacc0cfeb6309d6ab10bdd1a85c5b3c359bca3972e9ecae0d276f43ccb2c316828f5588d53aa91a197f824f7d088740be2d06a70ce8fe50dae23c20f639276b29807f19450d581b3deac4ca0f03d34830f4ec707240c70ce55a8f899290eb0d82df4d2336cc97107b97b5f95a33e1ee403d040072f6ccddf057503a705b990c453e452c4019a497cfc6522ed14edcaba29128ea721f37569ee8a4e4b43f2452d7f3dae3c6ff00b85dca2a0048b473bed89b01a572f1db1a9cf3ea3e7e898c9ac9c1872245371796843d23d33092778d92b66ce672e9c91f2f7af951941a44e54d4bbc797645ed6e0002ecc1bfc295130ca1698e238696221221a473ee6f7909c174023beed27bd94a02cf7ed10c191102891302726026af73890726efa45952785019b0f75370db52ab97014c89a0422529fb6a72ad4b203c9a4666c14b895ba876a44fbecd74ef276a547bd52a445e070bdd9d611e7ffd73911d6b8f48e7934e7a9c347512df840c58b5c89b2e68f14da24df272fbeab30780b079487c546816b7be6730c4b7dfaccb1a90fabc99e6d975cfb37222b105293d2835c91e8c00fbfbbb9a6b18b6fc24ff0d15f9582994c795d1ac725be94763f1359039d367b2be4158a4b7c4c01674c17c6ef2a9fc184a80ddfc0eb81c6d9e86701c347b308f5ba939b3f1cd14fd41d66dd5f6c6a94fe1bc812472dce62d44e0016944b37458e32f4bd09819f9976e2e4614fde74794d268569f6c4e34ddcd07e20d40aade37aef60940204ea408a867af88f36572d8d543ddcd52c4740791b2e4cba5e276d233229a8baf92366f1fa41db0d6d24e8b07969de77282c8cb8365e0ea1575e542eeb40f65ace072f3a29925a3b475fd4192c1460045bd5436b433e5fec96e1f1703bf9f74f3a4c8308fab04de308806a8a5b43b865579c778002031a5e88db01af82c043b283ecf56914bd20d95a4e02640a3b7d83ab43f108c0036500aea0e0a5db731d6707fa82b27e7396351d5d5018326ab307203974251c616aba158a537133bde16a0bde751145c7ccb3f783eaae4dbd1857215ed6f268f2cf447ac1ca483b138c89847b492bb83c64d2002a552f85f71ac72ca7407e0a552e85cda7c23beea7e9ff39f6a5fd0ff437fd8199e9fb73069df4f2c14dd821a136d5670a63e32a1160e44260656531aa2363e86c4cd45efa83072bb36aa3b4342fe78e801687eacb06d9f03792eb94d9d154887f2088693fc26725dbac602913cdac349ac9ffdf6285e90bfe1883294f33331790a5c1b1c627731fa49cf018d1e5ceca02ea1c95562d422f6497d8cf1843a72320adcf5997d8272c1558cd6a4b72b919dbfb620a5d3f4e6eb78d7eeb7b961f88aeb2d6cd44b7272ab1700206942cf804111cb3ec01e2b4d596c8ae1d251adce8ddb943df531f072396bcccc5ff53f010210061626540df85866c3aecaa430567f86ff7c174b1f72a3c67ed18ccc66686d319b4eac8b63a478da437825dd1d11f97085de0195f90d62d5ae5907216933772f891b94fe4685f75d8cccebef2be9892f16bada822a723a3ad35b2f350bf3918249e1003aeced14ab84fcbd6070cefd3efcdf9b449b697d6943859eb0929d5e074b41952a994214642944ea3a4271e5dd9a0d55d7a87281cdf22bdb5d9547921c6ddf4f340dd003a66b420862b295522cac55f4d4820dcf5d156fb7115a8df4604bc7f42a2d2c24a62f1552964bcff840fbc92f9375074fc9b9f05bc34acf753e30b37821c74f37cd257b33a5cd521eea32e74faa564966c3ebb90b7ccbcf0d6af10e46a253e0f1e560f1f47cca661f7857bde231f9721b00bda016ec544df2bd9365ccfa1ce942241176ccb01e6ef07bc8445b379672a00234339d606ac324ded86b766ade2267d87bb5ed1c9faaabf04b53b76a2772d5211966a524fcf1337948a4b4aa475cfa0d421cb49ca3488c14939618e94a724627a64d89614e591165d333d63142dc27c7ca02f4d41871ece6857aa2ac1b3ad853c865d1b29267fd3a3075e69657ffc03ffdf3fae4e64b469947eb8bb26d33679febbac144c7cdacd614383069fec37810ce057b5c11b89aec9a37a288b172bc117de3920dc352a186fd61e0e361095d4e2f97bbf3dc43703d39e21b4725105651e71f837edc6d6cbc9380360eab22de3dce31059a40e9ec74135ff8e77727b043cff852640b03038d8f6acc5987572b6c4d717c585037a110c6c825c7e871712b571149e67c84d18c265fc01cf28824d2a02a9e1c304444dbed9877b71b7259104673d99bc737c71add45fdaa5b84d39d3387df527eab1e3f3aef62f1db72ea787a4d19d9b1c10e90cbde3a40ac5647de7e52f1632cb044e7b1ccb0155f374f60b18c89b608cad12f71b83dcd54d8ea088cfbf7875c02bd9991cfdfb8e872a24448175ab16ac814935bc5ebbc93613eb9062582c14ff9de7eb2b0b7775472e1b25b53bdbb532ad8a582956c397ea1593921a32ab302c9c1cc584e539d4b4d6eea1bc341696602cb1bc882422bae2c3dc08f079a1778333268f898df710d6bfaca93674f7b928996f0cddd76a92f36624af365eae81e5676200c9901b57d72e1cf28cc90e045835f859ec5f9e8832ae51285079a645904e211c7b15ab7fe72f60ddc01c40e62b9d4c67ce39c45d8ebb7adfc3ad7974d3441d7ecb63490884036276ad2128cbf6311835fd2f5fb721f3be21998233528d3f6a94d236ca2dc05310f84fd8dce8b1029583973fa814486e3110276ff1b6ea20f6a40bc7ae92072cf0e1b1ab31b653485fa36d9b31388c6a061e269ea6be609cc46f2583f4f2c72951167a8ab76be908d9fc064049747d1b3f908809c3ca8f70617f904ab34743b5271d53eb6bf96597e849f74bbff6f8f350170b8dcda3cf7108566038daa1f7241ee0d4ad958503e66f9c464d8cc7b31077d1b79f4fca090c3fa0d4fb588b6203e8be3dbb4952aab64b3a70bb7281382de40f242c1cf810bd73b631e45a8b8729c2364bf62ea84dd1bc6c80694ffaa642792d32bf108425c1b6dbdd0012ac45bfff1050f192a6bc110af65cdf69574feaf71ce0ed32871c977c16b015b0b5d3f00c365d2a77121294ddb7c5f67a57af7da6097f5110d4c86cabb8a603d0a4c721d127620e1c290667048f4785184559d6ed2d4f12405daaa49bac4f98831af72f6c2d0eba66b3cc509f07b52960d104be61fa63e57b7b7e73b9fa4de6169b059f061096ea6cc70c00108a85def3eed3c964495b64c20f2f9a3651204824561721a32ce4f82c8bd18062f58a75aca36a2fbd798a951e819e66ec2ba1ed7f76072b96bbc241006b8aab78d21bae7a89c61b09d153746ae6622deb7252604cef268de15eb8fc0c181a8dfdb1de665777c062f60a164b96682e8b5af35752d42b772b97fd7f54d4f3dcf8753dbd124bc3dc1bbfb067d4aa049140dd53934dbc4f83cba23b05a44ff66399a7dc6788d5f52f2366ab81fb10ac1b6f4acb5062b14d3579fa4faba7391f0954a4e3a8d8726495caaf2bdadfc82f840f5ec10b85d96a2728ab20dad145813d0e37d91da3237d8dca1eef85743d786ea48efb9b7808170726ecbec8705705bedad71a248523e23706cb44258e7c5e96774e4937fb00b8772df981873e633247ae92277e8f5e57f179e9546ef4653950e1eb2ca2c94b2424c91b62fea372cee92566d1d2d9d0bd12a9bd22c92f59840dadb2760a3f4aad072bea2fe68136d91967f2c3fb29a9cf8fc2fabe8c664665b51a7059b575e937a72db463dfa47db090ae13a5d19a1f3ded8d7857c47edc3ac38765e104703e34b720ba05efb89d4209313311dda237329eb5518957c6fcb58b15c534d5bf210f96adec702a8f2abfd70b11b568c8760c1949984b1fa8486e9d8750e9b6ca9cb2b258d10ad9d211c65f8acbcfadc64ec521f6c4b5862abe3ba713965a9afb628bd72f91400db56be89409637b70fa90e039aef7957a13c78b8ec85a37581d9ba507292b4b4733c040a37940e729f73c8d9abfaff39018f880acdd1ca4778a879a34e97c53fbb2e36cb375329fb227cad88d263de2d574b230cbd9b962a7b7c0a87723a34b18cbe15b76d5c7d0ba0f751bc4641eb931a7e34f89552dac99b3a4b8e729341063621d3df5fa07598a951b0821857b86ecb3b7b2e3d211ec0943b06ba72348855f9abb23038a62699320ae1216cf61fa40dbfac4f2c6d91e565ddffb2312095f65b08ab1ef991c93a716bd8840f078b3c9e4a4ea8ca81715fb54af75a7287a9db51947d236bf925cdab26dd307203fef02c08c76207434737e2d3a3af13160589fb258a9295241637493b7f5dd9de762075d0309d426966ff93199db0727e78e054d46ededb323932219bbb647246665bfd758816babe709de020485c72c68daa6094ca967c633a46cc39908450f45ed31d3fd3815e4de3e0025ba90c15cf210f006bbbb00e26dc1b68c631c93360341fa9db894d22275fdc1260f3b07291ea262b464751d5d7383253a19751cec85522101e11bc81f4bab7d2bf6e507231874c20056d4f635a6e7661e871f35fc9c46e3f5aedff4ad57588866d834872f7685b6c5a3e6bac2683abb8caa34aa5c5ad845fb4417a1a2430056a64ee6672cddecec49e9c3210027728fc0a93775827e2617372ed9fa8a2a6f714d51bd360850252485031464b12ce29172ce9dde4e5604571285072fd6e4f0f30f032ce18570a1bb8fc4a67e43897620fc32d409339b47e75315aa7ad8ab113629819357294de70e5d336b18e4775eb4b5cb9da25204afbb76f33eb26e32d873899cb18728e8f3ee711c4b00581db4e8c0020e8efead31f932d0e9f2d72a6081999fd087265581ee2bc4bf79c1905392e1983c16b8d081065bd1d57bdd706a897e18cbf72c6b91933485e82f9a94bd46df5b885f844ddf60d621122bca4f33772e29d467262b7b8d666586681bb068aa5e2bbeae1f7988a985dc7a25030184251a7831972b541e74b11691d93a1f6c788032dee42029abe6b8d7cf975a104b9f2b548dc727dd90ca20b73cec8d152d7b78be89e95b28cc21f1082abbdbd33d93e3e339d014add62cc92a58a2176b3f2551b2342de864d368cb51b6f379c61237b578f9c2c57636bbeed0d68baa575229ba7b7add0b18ac10dd951c3c17fc0f425448df8721ef4a0936a00c2115a03022c83ae905d0d0c373fc4c9243fb98a57695ba024354a7c07fc69249d0dec3bccee2afe10713d5e621ed7f6efa14fa8e91324685472be9c46ff1e850c88cad73701d83867857ce60bd0868e419aa4819a90591ae072201dd4fae400831d61cca9b4413fe721782c2154150cd04c5bad0526c3e0167236d25d2340674704cdf171d2f26236d4080e4715a978f1d5d24e90ee3ec2d5728637eaadfbf3b8c6d50a9c759607fa35df612e0b9f33132c0c27a0eb2e77d04936f439f36256c60cca8645084a95ea00d96a55c489ea189da62b87c15e64b072ea4ae52efa9e4c1e59a72871e505c49a8b18f5356b3cb3d15f321321d17328729e761daf77e25b8f1089a12b5d89a1bec7109cd6a6c48c469e28585a4ad82e72f34cc0755f1d8350f6f353a1af73dd9d8c1644917575c32ef33dff82cd6d1124d9ef61e78c4c3a38f8b82c6e2e6bbb5df01f313aaa3165f402ab989740e23b38fd1c79a401caf3a52e60b39f525a24336fe0078ca7dc939ea65c53988e10dd2aecb80e14158af45dfd5ddedefccf3e6d01b5fcee2dd915d7cac19f67c12fd764e117b1f997da8dcee508d40d5201f5bd2121859a5c18472559953a0bb6c23c72742b275f0b5094bdd0bc7cdbebc9a21044590cef6941aabc831abce3d1376f72e84dbd71592547b04ef0940d2d8c27ad75dcb815eeaa63d7dd8a7eee05aee1724b00dd4d7c41727c1a4684fca14d4d1aec4104f713f1dc8f581a54b2645c193e47e8df44d82dd09d885de105ead0f257a14beaa19fb09475c8f43dec678953725d5576ffdc83182fbb68ef3a7ff1f8542eea787c94ab73ea9cbe79be1b96bd72324044f10668097970419cde3e6a9a9b9298370a0edbe0e517ce06595d4fae726a6fd2850406f0f0376b43b81e462b7912921dfcb78e7f52b695552d3d4c8f72996021c459e9503db4f4c5b5f32615c50d4b3eaceb72e84dcbc7ea53d2234b578b7881544e48a425693d251dca4e607195c8760243e1fe8ed5ee0bd2d7264002c85067ac89f8a40918e7ed62fdf178a04dd04031ef19b34475061fe91a22bb72e349e9fe0a0962171db3683e1568af1da4b93adabb5ca0ef602813487a053a354c63da1d64faec49c9549c04382d233ab3458fa8f2277f3c7b5d6626490a176f214afcf4bb4482be7b70f096b77ad9abdff0853cdc4ef39d10fc655b2ea03a72f28293bc9b4ddaa7af246c300d0bcc809833442d5d729f43e8e8663c51943a728620dedaae1d7100f976d340eee26607ca6b35dd75be8aa676661e5d242c255b68e0c38de140ff17905cd7859d2ad43fd8012738cef844e5fa1b7a4a5a54d7722d9e6ffe5e173eccaa5124f79ead8b65df149e908776fe617dc0e7cde007c5727b28b134e160c9cf6a8d9529311b0abef942a6b8100b9d1809502b39f3dd4d72bd3b9230cf60de75695a89f7b6bd4f020065fa565d8732408750912c5492eb7228a415147c430450b9d6df2c3e395ee29843c4f23678b283f04ccb73cd43c349ae088aa9033dc05a899f0cd0284d4113a53afea352ea95596437288e65a38b1d3a99c7bb0163514075b25b866d62ac5fd7fa6193f69d87b19920e9ccb4ed4207a2c19ad91c79dc43e624f3a8beb77701971eac1b44d2a6327b3f072199eff672ee103eee146ce761b5bb378b0e480ca4a369ee2d71eb849e9932d7c5789699729f2e29fb8986d1e1b46b89747e0ab8e955510ba5f2049d85a8545397e6573d34ea7fc7e4fbc24246d1ea96fdf87720453d08fa0e12502e3d7e1a024b72a2a45f9518e058414ba5e39c740c1ea2eed5bf60c71d8ec7d52af531967ae37f26002d94c7c5021f7fc88f0017fc7f17fbd82111401f9053e0b767043f5eb78a53ce724689ecc8bf7c72673033f0a552c113cc009288380d311edf5c04601a5d1f8872751479ad8796fd4a19b02fac894dac60411a22125c57c639f0d056852739ce63a80912844c4b4b9cc5b6085da04a2284be253de58e3a458aa6f415c964338f72143109232ae8b704531a9f6549afdbbf4a3f4236a80ecd07e6a1eade8e55f5664c0914621df65beb7f7e207573b99f93b932eeb2c65ecff3cfc74ef1f3962472f3bbc111c0c82833a8671f6379005dc0e547284bb17b00f667cbb61614f02872ffb1be0728df5cabb248ccbc4bc7f2207d54e26d2e10301e3ee463749b382b720b23811f50a573d7a00f91bf13cbef2e2237c25532717baac095070d26d22f2fe51f6b8d289db8adc51b365a08dd96df6e2a1e7d6d54b5bdccdb06357c931372edea1bf65f40f8965023a0626659e55bfcfbbd62503101a7e2737cf0ee9acf5b05056509a71c9b916c6e71b4847d35fbf54399d591909d6a2eeb3f0cfe7b48726597ebab398d090c660bff0d131bf9208bc5c11bc09cbd88a1c92035f1feeb7278a7986d50e4bf821dff84c505ecfcfa277286922ad46219053ce2de6979c038e7c0b41778a355ba2011f89e40dd0af9922a8669d57b43721fd79b867fd3e672c96b204eaeef5fb899f27e99a1978edddfe7f4458055e71d2de71abbd0ef5c217bdc73a3c8f0a723135e798eec61b0a2cb099e8d6af9f540d1cef6b6aff52b7207c6c04bb3021eea3c5d31720a9b318f82bc9dd3d757516abafe5f0c7e4596702ed00a396014d4e3106c8d87f979dad0b580f6c4609b4e0a995a1130be3a3f00e2b79ee84acc6aa5dab109f5813380bfe881c1174e40d1e169b9461b7621ad4047be59f694ef621d5aac0568ebacd013a742193991bd4176f3b51b7c495c31720b8402d7f837bae36d69b6da21bd3df2dc7a5fe99839bf4727981b0570312468fafe963a5a678815621ef2de83d444d3bc38675ef3dfc7127bd4899c22127472a50059af978e2aa79fd83dab34db3e99b7561c514f64f104978b4e5b8c2a21720b02eec28aefede266596a6d267d7ffd454fcc976aa9f02de196157d430aa954b5f784b89c710ea46f696dbf7f55ccb93eb8cd9c552e437516fbc1f782aa0272f7c205640a476a3c4c2b40136407356cda03f3ca546ecd00cde9bab6709797142510fd4ad3ab720d740bc04c9243e0429fd41fb0a98a728a3b6c3085704e3d4107b92505a895c5bb7985c0ec163a8dd8421c78869dc48245ebdbcaf41ac701728046d6656c5e82c0ff74c20951fc4e90b427205d527556ecfbb58886059c167292324c1d02b63e5fa2e6b3f281597d13be327ed8fbd2eac6c596d7fdd7f3307259e7005cc7000ec66fd6f8e7e82887e404edf90ff4e82e5e58258074772fef2b6c59973665df5a1f8b29e215a52d9b82c029708d02c6ff5f4605e31aff6715729160978dba9a9a3346821fb91e009c112f68b085283a795e00eb76f3e557a508ba227e96e8bf3fd204d0023d3d822c2c7a6381bdb519e416dfc95dbffdb1d228afb5c44c848f59e335c548398366072ac402e2de9b2dab97c4ed02eeac78b713b1f14191c41fc59dc4f95938d368c60b4d72dff15c5053de86bf385d9dd5b23bd22736f77dddb0e3240d380ef0ae3f0dc1970cbb04d51d3f84ccddc0f7b66411798b22cd5e89dfdb2581baffd7536e9e3d37f32368ed28e5dec9673e774c3450e538365f15193070e62b7fbb742110637d3f91f40d505d956dd17a324b0d367263c4f0049c96e7d145103f14382f722887b3b1f9319888a8bbabc5895eb7ea5f36ff767cf0ae15dc18f57399a8578fb64223e89b2c1f4d76ad87f7440819354ac20ddaf133a9e818c39e6c2af5c81ddeeff524009ba8eeb9aaf23e32c0faef14b91811b6465d7810fd924cd0f1b32ee0f72102820e9879507f0ca7bdd6a0d138c8905cc69239437b03bd76c4088125a7c2152d77343db998c566bae0f27f005778d50a1b10eab9de5c3d713ace016b602b00bb474642415f865ec4b65431157250b8564a1c701bef3c66be96ee2e5fa835fb6f3d6e74f9296286718b9a335c7285248d2536393331e3c9317932e6fab8841d25f8f0bb9549e6493ce63d8a865686528b63f223eaf276a2fd7e6799658db74f970ddf3a657baf9f899c87550572287a5f4ac1798f6fce33d261af94214b6d344db9b1fb0334853267f9720f667289faff2a30eeb0bc7211ff170120df4a1a0dacc77d2856187869f907e4822972d9c0166c7a8f6b26bbfc2186969618a1157f5f8a10be7cef99c4cdaae82aa021fb8cb39815380ffe59b874a4872ced3e26de94f539bfbea451d7a7e864e2b172a5428b4ae5da57a80031c35d239a4a648442b88598bee63510bac804c4c484724226ccbe9894a5c31d1e0a90e603d9e0c96e47b1c8976a24dee2a0bdb24ef27279a6aee090cc3ad53807c1b1035ba7eed5b9f21536421af8dbb20600c6e6d7720e87e6a94a4588855aaa97cb7ecb42187fe6a66827f50c460147f3ef8966b45d44407fcc9adfc29f82f20b7efed7e50cb96b3d5c4e7ec575b29873fc1a23e56c0ec2226edfb784b1463eea17b5f92a88f9e42dd51fe7ceac246427ffec24b472a6d634964176536b2f9aaf36a33edbe43e52d3cf3d25b8e9254533ce9669fd72a8034ac5808b9d1bfd5659272a05988e1bd8a6475276a888d8244b693743c372ca7b39241082fce7bece255f8bcf728753a9fa3e7cfb82a5cf951b6014317072e71264c6aae494c11d50eb459de01744542c4560887c11b2213174512d0724728a75eec60e40d3d7120d4be1fec57f9facc7743c6175907b66067461e3fbc30579b8adf0111b05fc3670fba373a6666d2130c2e76be8738553b706af86eed918279f255f0c99cf90e2521a5a7979148f7045a9f3a9980a9517013a705e343c7251567cfce05e44518d0f52d83db233a8bac76f590af3fb89de0a78bd6f6f6c3ff43c1ed04516e6932e192a0f1a0f8ae0282ee08f1069763acf8e3f25dffdbf72e3058899a9621892cbb5920979e496603b2ef8dbb756f3274bfd7a755d1f5472b8cca8c5bed24140487b79671e0d56b9a3447db2b10d497adcf27e5d6d51556859a01b6dabd88870baed0437132ca63958144e50831ea26e9590160c1864d64d3cb9118fcb751c3e529d6f9c2a77688f7e1fdaa6e25018cb2cc582ca5801615bf1ebdb8cc763475a1cb3881205a56e6981e09e9c15a4766076594575c4884067b22e0179c475ad561d25a74bb82a0ea452215e2f1ca6c0f4548796293a2b5c72a384cdf4c7bc0bd373e03336f74f917e21a075f8bf15f5848966fa466ef96f66d3a43567be10d0edd289d4dbfc20e652ac0547e0b6c9d35d9625e9666a5a5537eaf5d74208297604f102793969418976e60ad4b623d4d3f1d83fda260ea19e7210d3b643ffc6b2823f67c558fa83ad6e799fdccd73dbfcc42bcd2d4d309378706504931d00334543f6a5779877325ea6aacb23cc2983885e11688fc61cf42772f5923153345f1f0a25a81c934673d38566ac01ef404a0a399e4964418394d7725ee08b1eeeb86c84cf0343d5ea6e1d049acbae4eba7fff225ddcc43ab187a172cbb6baed6a7092bbc1ad6e5979f38d69e91897e39847f3769f1a991838303b7267f8be37caa4252bef0b7dc5330f6593744d5a7171fe8a7a104973b524cf99661dab53256ef570c3ab323c4b2bc5391e0e235af9c121b38f97de0e564ec0fc72e59219349e61b55250d11a4f2c8c495c713a3022cedc1c5645c4ae1c066a1c2f5746c75b88f8bf0192b64e9b58e7e4976f397191d3decf90400b9dc0bffcf413133de635b121fd2429681f2699f0bb2be5efe2af8ec39d30d0177c7c5cc50c3952dd0413498fcecdcc7fb374671e91c480ad1c4fe7e59f30e7298e97399e3e7217362d2ea3ca838ab3c323fc336b48837fd580ff793b1a54c9af5769aea6f072fba67445f153fd4a2a849c23d598f8e94ab4dc730fb32048b9446d80112d32728b33558641321e15f5e5c70ac8b8ba4017b6f1310965275aef80aed484e69e72f921b20698bff24e2435d7c77b1bc00e9d825a64ecabb242615f579bcc412272ac1e909cdd5d17a3b0fa213465ac66f916dd98d7994b8dd6cd307203f790dc72099ef28cd5d6f7a771c00a2d33beb119177d9e573e939fb46df9c397c711b12957e8b41d277604a18e2c590690f344129a5c9c5d7153022e5a2deece3d49837217c3d3e3f2b04eb36d7b2d164563dd4ae265875b3c91ed25f210d24c773f3872fb565ab220af1724e2ced64e5babf09e6f4d879526ec404bf1a34dfd715ae05a400ce19d8d42b9f6265a929ef311d357850b819367d45cd037fbb038b62e8d72d7b766c6f01c8127cb1d59681291e3f56ec105a51db9e30354dacadeba70f5725585241e74deb19294909c0a6baaaccf5ef7e02fbb38e562f7dc143ca35630723c194dac6390a9b4a38a0f90ded6eeb82cfc604dc97e33899b4273eefcc165722a0c5f95e7613f78fcfddf4611ec511984bb6c933841977e3e9d4aa53309a331fff9bdcb7b3ab3218524c53a81e5f5b3333e762c69c052e74ebf8f475c1b432a7aa83836749071fcd4a89af9bf8e88c5d8d057f2d1e55bceecdcb9746ac22b577cf2c248564fb12b9175a2b4e302e60437993915ae8d33305b0751d081c56b722b0659b07d24b7a610a263798e3396c38bad11ba65b489a173249865acac0472597e60e4deff5e4bc6f09a2a78e1a53d480aa5d92d6d5cefdde7eeb510cec90da03274efa22378431081306f9b27b9e8f6aa8ae86c09004d2c5b625ce097444a35e94fd061cfed2c9309bc9abd15b857f219471e42cb6e6b086bbc8ce41318726d8b04bdb197327242416fe24960ee4202953804598617d534039c4faf497372fedecba99f8c8fab8230dff05ca6f14d182a1d2cd8fcf474e361ea1fb490f845ba5a4cdcce164ed652fff851b7b24b9c5601972e50397e31093698af970b4a1931a48ad206d4b5cac53d3169a2a56020d4e60b45bf740aa1a1cea1dfeda5bc1a670265720c8e505c9d9c9b56ad232aa75a2a03a2a860a08999d8565bca7ac225e75ae49eb0ea29f6b43fe21deb452184d71b91f42429aeb968b92660a1ab1772798182979ec71e4d5611ad531c2a6c2fa232ad60faa841db568327c74dfa8e72c47323f50dc1213d5d8dc29dd9bac003662f4a60a387e99e0fa907c3cc24066cf980254e6e8bc2b5e8b5b595cf399611571bd626f155a53d775d74f58a91ce72487b8da3d8f310506041194731f58556e677372054997cfc34ecda45dbd5496224bf0f7d58bd910569dd5a4cbb37d19f1754a731db1fbca9ef532209facb05437f6dae4d875abbf742a1a990c65815eabe3a2656222e8f2ecccd98f1d1b7bb722641faaa88f4ed7b4a75e0a0fdc06ba9119330a8a531f23d3400bf252732fe72fdbd6c8870def0eaaea068d8ede5a036a4852bc722268ee07b6c776a4e76ea72c84e878cbd971b21974786442e3ab620c065d7e896395a8721b732636d7927165af8c83f190506e55136383108945ced4b573ae870747ecb2769cb0c444729723c67e210d326654a519410b84eebd1a0325517d46710e00499183cdcafb97672ec574ddd65fcb9e24106fdef783dd130474f18cec956103cc87a9d4d737a24729ed4db3d9e8134cebf34592d1bf8692120696dc2218c1087fb0f93f2bf18a072d7376c70eba140b1c5ba9bbd54b96f352d762514913a404fa74aeb6d0f4d181916ad9bd47bc67c01a9666e6b33be1d762837b7c9e221c0a7dddbb700647c1420c1848702ea9e1726ff4359e460b81c9e47581f13fa3ec4d4cb896b89c0ae7a72afb2ec247c8ff98fb76b7a6b025a6596517bb6a5d8291a7d3aff2c0cdbec341cdeb5035d0db98b3347a588bbfeacf29a862f794a9319c65d15137b3ce0d12d72af69d9fa31f743fae7bad713b80b51625ceeacab57be6091c52d59a7f256657260bf1fa7cf6571cfa54288454be408db24dc4637179f7fe5a2160fa126640d7219a0fde81ab74c9831082be78db855582874cf21e62c855275dc85f775f335725cece8bb18148b89768414d405be96772c7601a17343a2e74ad149878d67700190b52ad9fa2f7d4b71ae74966f10ad979e988b76137650c0477a49219039f33332c381b8c1de20e4dc01c137ae274ef5eb4f6e9d7223230d040aba3c5af1db7273b0df8a452bd78c49d895d6e8ef38235fabe6a281d4789be08a7b81149342462176de45b363ed921ae94c4ba6629dd00efe006375248d3c4e0650d1e4979572fbc7811abf152362e2175c305c32aba7b5cf201934e06d9ce3f77fd3cac6410b7f375d28ce96c8d2f390a892f16998d1c265c319d4b23ae1cfd10ca7ca7fd672f6d95a317056e0b5353a075972e8dca4cbf3cf12af2ab825c722e25ef8f45d725c619a650e73e5c49862ae3a755e6366319b1e65e34d2fdec46b3db7a3bf827223bed1c6cd036832de5a2c4aa31914bbcf2b52aae05cc3f44a5a0f4a3a5ad11a00e59847390d76eeb4183a77b34fda8f6368f971d1f42b6771c766399318e00c4df8a14ebdbfaf6c7c142aa932daf2659d7946f18453eb783919a273ac59a417356d642208645a43875cdce46e370f529bc9224e0a6ea0b09b5eb6dcbe4b541908cd8829878146abea5c511fe699c3ba260db9cdfcb5f652109000126abfcb53c94fc3b09d9587e1db669e5526bbf3872c5ddffb2aefb1dd2a414f03b44db872abb2fc38d5bfad2b09938d91a0222144be37b729c9d7f0d91e5e7f8ac2ab8a14d84977e9974fd03c3f854292dbaf50ae846d0a7846612d15313bcfc04cfedd12f752277584eafd5299b2935521a2934e76f98153fb4edf53f74bf9dbb68c1b722be4e56582bf82db85c861197914bfb9fcaf5b22e1b709c86329ffcc828116727e8bb0f2e31690266d4c141c03da7889a22e3d956259dbe8ed2b8c9a19012305c40afb6f19514cd78b557c504af0977020826e9226aec808ebcb0355a6b3ab00379bbd07cbb151e62d59a415362ecdae99d348088b852a0473c71b83be35627246574777c502bb0d910a17a6095b7359b1b13dda0288bc47c9fa8be8e61063726a58d9f72c239f845e4020f0d90ca92637d4571d0de325534805f24d874fcd0c117b19668b1bee0a5cf0abf2b402103166bd9ec9034685d60df3e803e3dbf8255bf6ac706478c15b378f4efb73f266a8e53add5f33e344e8b8dbf1089af2d072c53ffca75a7f9113c1024c61522da7365fb175161bda041db41ef06b2d9d72727cbcaede69dad44040f2805368d7bdaa7a4f4a0472b5f1197ff86c4f14991b119b488bcd50c0663e6c01c66e32369aca08de9f0aa96f7e26c956f83a08a4e97237dc3c7c3148b5a49b880c9dd8d7b7e2bb4f7665805bb7c3f42eb1cef13ac4635b7378b5689379a4f43123de7bf34d12bedd7f8aad972702e934cd86d837d772a844cbab93f64e35d67f5c8e6058ff80b7565a4066935842078cad1a5d80a1725405ac82fa52e4dbf16e0d1ce22c1074372e8ec24406876994bbc2146f78b22d2b72a637f77d7d1d2738cf19c25e65954eac110157e87bed7021c8a62a2ec972d0494f4f8c2b0a37943327e83b67c8e75b4bd78a49180bcde0f792ec5e889272407b697a41ce9768ffef7b5daa28190555b4d76e392419985f1301f89922ca726a166eb5b089a849bb7b4e58b0dd3a4b8900842b4ab897aa57d2904fa26d1572b338b7c50b2654bd6158b4ec45acf6d8007da0bffb54f0c1cb9a4fd52b5f81729d65b9803842be1a55fc19ebaa4d6a5c950a5def5fe1658632bc10e44c15ea72d0f1d3eb945e6124f1cc4b910115a2da3a263c33dbcb49148f7da15459432a728e95820e875747cb46a16c65f44b46b900ce3d5291bd53baad16e9152c9096726051112574d3e87006ffc58e951e4b76ebc1e5dc8d70e22ded131729d6fa6e721330c17877c6d84b72030d532cbf799434b75766b6ea8977731ae051fb63c6722a2e75584810984be77c0e1e92ad05c5818e7358a075f5a4f070f485f18d2472ddcc596d05a26a4c688e24698c5a0c34e67310a56386c308beed0465daa8aa729dcb958efaf2108a19a1187e2ba81afe9d723a3c506c6af97624b9d4f7850d722a9549f3f7b48fe3693e641e1dd04690d5702810068346ab18b6b3b4a2f8c71ade7373132dd6d38f9e31a3dca043cfc134fa0d3d696ebe8053d5150db0afda03f8fc7790bbd5dfb7967fdc882f9328fab5b5dd7700dc7e5a0ab179c9f6a80d72595d82a474d3082bf34afb18df76f994a6d44f070e31363f1a11cb503ce6dd246998f5b521378b1754bbcb4bf924a693dd435d026305f5970d7140f654c81372478f98b4540bddb6fe2a1ce1adc31022cb7dae8b634d04cf1ba640265225a1699e983b8144a7be938ca629ec9ccb9118e65e5b4c30d2d8b6adf7072808cfb67227cc3de6c5420530d621676824e20b8cb6ff6adad96e2061af82d822b245ff72ffc07b3b3e842dbddd8eeea71c850457d3daaca8b51e260bf890e61349b6711e27c164f7eee28e07e74cd8f2fab6756d502a1ee304aed3aa208e6e49ba18b56cebbdfbae8dbfcf571a69eb3f5041dc29227352b0b847c97c31b82dd79a0b90722e23a9b9751981f9a0dcdaf305a99dc0b76b3a5e02c6de59f198a22d73d6d4139c7f6b49ca0a683bf043567d4b13199e534906410790f1ecff82e1b363f14072cdc893270e6e26c51d304bbd8d491e35e788bcf6fed105279bd25bff7f8b4c720633fd82f255b37e5941d7d1ec2fa862cd7de07620778804fa1d8903cea38464d44571d4282a8a0d6aac718c8a61655124805db72af1f42a641f526b140ff33b96256f67a392e7ee70afc4fc8eee5d8db96ba7490304726c7be200506c7ebd729ac50119ced5698bdad987291a65ca6685e5c261c91ec07082350a56722e07729cba39f16ac549d3942cbb42529abe15adfe7716d7eecf2e2e4b0d72cdae454638fb9e0e871ab38fc0d48a05e1849c56e4816a0c6c76f8da712dd6849540fa727bf9646e79b113b03eb7d6ffca12cb39f3f96b519a138cf4985ac80b6e6f1472365f9bfe7942e951f773e8b1a2dfea74db73f73eb437128b180966a2714a96726e1c3be758cb5ee2e12bbc2fc9755447601bc78b62ef5574160e09bce9166a72ef138804475fef2dedcba8ca1884c27cc92d58bedab09096dfbf135d7bf24f24313ca5a2b9d71cdef4c947441464e73fd9e3d290f2890cf4519b4d98905b3a5193f77d4c6ffe5c03383ca851d96f0bd8325856659f835ea77b20cee9db8d08723a6df040daa40c9943455f42a261b17166cfe0d54db638cbefd3c854f8567c72c7d811388954a7371a288d736d9954d96c85315974fabab66c0260ab8bd970722a95caec45192f677d11ee955b9961970fb505463762313101d7f147c8397810b6be0b1ec74181ec6e3502ee128dc4cf97a18251158ed5814fe39dec955f905bea8fc15b01e11556ec76baebbf61ab2b7e32c0b2fd11b7a6e162ae9e7ff3401c08ceadcc73c62a5f8135147ae29a85a1b447945901bbb55114196f7db127787202bd20c2697e1474b8f275f438a62e4a22996574678259d3be3f258110327f14d09be26dd929ba12e11f8b111765ba6dbeadfb8a5fa169bc8b858c43582ae05c14327abef29f9cfeb68b854cdd30c1f10d593f6f2a443599d1b49c0d242c5872ad198c4916fa3f207472ba29f09a3dec92574ef4de9966f499f8701c7f2f7f72a2f1a6c8e77bd1389f8e5321e99ad83191d58f9c5f86fd012b74b1757b5d0152989cdd98047d94ab231267e1407bcdc5b1b7aa439276783f2ff93fc909ae8f727e23c2091e9f4f0142b2835d502fa8baccf2680b0f9f155fbc12d7fc576560388ac9bf10d9f5b04b79bacc17778d75001b750809eb0d9ae9cdff4e74aebd0f297060090d0ada4e882175b1e2fb1551f410f8a18a83f57bffc3e47ea1bdb96272c7e5c78793e915b0524493d921d8cef02d134ff6c8b4d15e47ef2c26346e9e72a6b8c579f08e273cf14ba5c5f79381d834d5e346d20ea601098d13e2dc265d0d2a84dd4c316389bfba3cb455c1510ed57bc26ce9e9a090fd2824f5ff48d5cf5aa14397a4536882f22c3c43193fa15543d8ee02b86984b61f83a2d6d4c2e22c72a4c3accdf393cd02eae48619171961a13b54f636faefad33248d7c703dd27e2b2d80704829ed645d32adacdb16d9ba14d58d2466e8d3c1d36fb201b042a61f361c92fa4401dd809163d832ef2b92eea392296e72c3a05a3b2110d664b3b0fe124e0dd453efdd359246a2098aa822c22743f4e5b8a0d40c3f28c1d947776ca9722d0fdbe3b14e8047cd91cc6f3721943983c8697b725d4b41e4d7ec4bca42624436c4d779303e0f9ff38e20bc11563bfff793f8a5c9ffb60c2b2b06da4717287205ec03bd6a15980e712ae5b56bc9095d0d16559feddcd09350832f7c4d91de727a8abbc3e1b1812ff36842b90cbc2f6cf7cd5099f832b45bb12be3fde7ed7d546a86ed3c3d50b321127ca8b5b402916e3c98d7c9358e2498268b8bdb1da6f73a547ea2ea0f3ca12500531c75258b60a005718dc92922591f787bd1d5d6137b72e8139e1d5f2841e42ed43b0ef219ffd38ef8b131ed3d3035004832da122b017217818f2e00dead644e9168ada70333ad30fd133cb786b46d7e2f772569f40662aaf80830bb933d3d44ea8c6abd7387e0739652b57b2e02b5897f6a79d8fde91a70c50a35d92c2366419790c6673534f772a4dbbee68cee2099cef69df9516e722e1d2cb75c37b47eb9e02c84bfc501403504d243b0e1a6af464c1cf9725c0572685f085ea2c14b3e264a2f62459b6593d7b1a44227c5970721ffeca9f176ee499cbaab9aacb4ba7823d10ed9197e040f0e5ae93dbf7df618dc921b86819c1772eb7ccd7da8e2d259ebd00a918de9bf1dce382f89eb65de49bea4b3f66faad172923be2267528dbb6816a8db2f729b2c8aa53424c258a79fe55d3d4e952533b72acc0597e1a6e985063adcbe07eb7cc269ceec92b9b403aa8739d0f7420e46e723ab3a16bd94c3d6cb81ab28690dac724bf9c01e98f8d17898a53aa5a894f9a72c767c43183c9f17c5e232cc554b234573125e220f6994112a4f09a6c81fa7d2e2f2e0f6f37759f08ff6b3fd4d947cbcca08dd3cf41da63f554265403d3406e42d8b19ef2f18523a489379d07c9925eeb439f59c3abead33114aae32d6763b0725db5a3659f863f4346bb491b20d6e9146417e37f9dee30517b19c0b57f05f413d85b63edb78eb413d5599313ad91707d569f969eb529996d3c22a3f334a127347109926a869e108613a4a62d1a31fdec8bc7bce418476fddcb3b037ae3557a729f9b0e7641265d4e4328002a07f9b4f33df4957b8fbb772a990415dca4a91172c6fc3ffac6438e1a3318c7780be2864da9c229d54cc373446c7d0a1c4de1fe4f7ee89e1f432d5a717cb3c1a07841047c99d98b3c34b685e0ef331a00a77f8010e76c6f1e8d866f494da0058b31322e4fdc3153ad9494ed83273d1e4ba65108157e6532568b16a070122cbb38b20972a3f0c670cfffc14c3d14556aad1ea67272d3ecab679d5d64b5ce9454303d137de54cc418b012a0ab8283f09df58fee7463ae2b985c4c375a61e7c434f6d1d419d82ef6d6868bc10d48dbfaa54e0a93bd2ad2e41de89c11bb5690d0fb9af38e0e30ad1441b9ea9bc902f1de94bc54b56f72f132817a82b5cf5a4900c66ba0e33b0755a2fd8db89a0dec3d483a3c50af991e63d29e1b7e457e18813568eacb3990bee70f9c91f3215e0e3c6e77025f0ad8728cbe9a5dff83a74afa4ac6e7d540f70a2b538c495b8a1f213f45fd8497c9430e671d1f4a46b2a5ffe867468ce31555c34a83dc1fb242744318d2b16c9b3867726e250a429650d84379ddf78d6f7a0d80a184414ef7b99ba2eeeb11ec56941d5f5740591a62f79d5b03352ea1c806cfb9e2b6cc9f32a79badea2462feb443fa47d287cad30dac8ffe63c37ba7c961eb5df4ace96c5ae6ed6f138e1d6fe05d88725d94dcc475331f0f38dc4ae766424387070dcfd155462477a9e5847ec081902585b4bd5a1ae7945862644f25fb9b4fdf53fff40c2b82f958a0c35f82e9a8565dde9def076e290fe57915c078d05ce42fdc759829b5fe222ac6d9e008b94fe67285b25f4d6238d2802bc9d503300f3ca1bfd5e3dca1634d90d7d1f6c54f38e7720deaaf6bbdd9c62a5d94fd4937407f571363854cb5e46693f95344e7cf98c30b125bc96c557f8472c4b25c2395bf2fc1369fbeeec71c5ff84bf60ade138ed472c3e7f22165a58fb26f5bb4e507ba92e65e4d3babf6b22f3165cfff4857a93a728060f36191f9ecde0a75fd7d3b22e8774eaedc313ff56c062947ccf23da6d3729cdc2cf8f1756f7b4c1e76c55ec1784a438ea7e8d1470b2f30da07c0811574720947d3e10868efcfd2b3dce512055d20da92065d2d277cec59b5bed00e650a72858caddf330b38b44b87d5ba659e28df85bfdc75f86f3848b159fabdb2e7cc3fb48ae623f57e58d402e04cde6eaea775ac4625bfffd58604e320de895288fc723b6e09d322549d1f2e53c8a03bbf97ea280b4df7c93292c25f9c0ab9480d7f6806a7792a5bca02744fbf5075df2a0675db66b5fe0c85fbad01a410bf487d041be8c4283fa9b522c767266e9c0e1d14d0edbb0c977a8b992e1b9f9942084e6c727ae71686d4778a4ec60f1fb22dbd1451411d057ff4dc86b8205bb0511dc5d52fe4dc7ae993b984d5bef82965872d7c52a19ca9bd21c4ffef3f4c60dedd8b88430b35dd292750b4a56498e9669e82628f8f8d82127f5d1d3ada98a55cf22b4f72afd6964ea2f8593fb8f1c2059178be61f144b274f0692d4b09d1c67dfd987b72d3bed892ac57749af288f15365d0f1cbc361783131a1a44c0943d013ac8a690a556270791d44e158a548c01b13e7b7cada42c6d60bcd00643432172d5230337267a0ff8b9ec2b05d2d25ae63749d721f44200280313530b764bd388b468cae31d7ad413e381dc8ebec5bc951463a90f1cd8bb835824a80ee2aa0f0f3d5320b728ded2831347d423baf2365dbc28cef08a829396f5d2839fdc766f3a0df53fe72978f702fec8cf21e9617fdded00e19b2fa7df622749484d77ec723d9e2419a726809c5f16eb6eae8abab0ae94ad56dccaa774620ecbf38170b5f123800c19b721b0fa933b59639c6039c984d950012fe093ff19e97c2a818e42c1b63f4dffd438256cd21341e0c250249195fabd45ae45ed3b48f617ef26377fff914b524b7722cc2a93cee62ebf624f22e120ebf2abe17b5786470675ced0c8bca03057b2472bed73715032de4163f0f9db2c7bd90176b3c6fb3d949968fccef666c8c44aa47e23b44935a804d51983137cca07f96f150b15dd94f3412738c0a3d52d9867972988555fe2336eb68fbb8d1c11fc4c69eb4cbf4348cda77868a5cd9678ad5c372b91f9ceb79109950821e4450ef9ddcfd65309f89ae999626cf5202ce646b725cba188eb8f6bb9cfa3f887f322951fa577ea1e1555647e68aa0e49bfae446d166e71a88cbf3b0c0d300fd454b235e99e03cf1a2c683c48508dd4e0e723124fa7227e3d4e8db19f2242a778ae738f6021d5700443f7fbe361f1a5805970f086272dd3da1bf565ac12f2227574b31ba0866508724c5382cc0c8ec5d8ae2aac92c2d954897e69ca0eb5fc5fdf8898d22eae1beba66d6ee9d977462640907d300383d809d3e7ef289fdf04f583910b126ee5b8d5713dc2a69ce3d1224b3d54104c0696b5e237b6cc8bf7b72fa5198b09a38fed9d02bf4086db1d045320c8590575072d7e49fde12052b3b89cc8cedf153a0802f0599bc08b193d3710cb89b9d847e0bb56f4092e57072e1f771fc5e6027c5494fa96539577486c776c81c1c8a00013df543da95248ffeb10f774b4869fe963f469be318e9672856f2acbc413e32ee3fbc6d951fc0165af9dd1c45a7ab3d00a17dd84b0d231976b555f6ec6952f8db54c77ac1c76735babf838a4272a1fa1e5efb42363b962f566e493c56b8362ece654c1f7affbc1ff70d400b8af22d0978b39664c97c845e7bf900c689d4945a616e069a4d4abb214a3eecad1a32b78481d2840e5716718ba48a9701af63288d7d6301a0207170f13cf50eafee6c080ac698d8710946a3a10ce2f2c098c658b4a000d4554668a952ddfed67a89ee8ffd4887cf4ebb809bde4d5869b3cd9624f13e72f3c54178aff84c0bee073742564614a4308dfc17f90ebde2f402174717721d4c4a14ecdb381051984a3c7dfb0cfe28231cca370399340aecd587538c36f9fd72e52110925467e5eed707d71aebc726b11ec58889bf7c938630b9887b77dc1111f4a1784463c56aabb1d2492bec28fc4e35a013172d42863be0bb1ebe08ab5e720fe5d86f974fd11bb731537a262ad9009f86aeeda1d90f3bb309b7b1f48abd5cc29234fa7dc0245e1f3067d104eaf913775a79910c3b32ccbf2604e68eb6861d3dd11edecf2c50ddfc953c070fcfebf47bc73d6c3c8995a4f0d4832226632c149d90e6c4faf669b2b384eea82f5c04baa7c50815ab0bea4759e6d438668b935987b7c1e1d953995d0e63e8c4a6458971e8b2cbed0ba182636ae1cfd6e5a89772c757c30a6818c8c2110c45e216c0855a6d26c5e0903522bc728ad687a72377720c8c57a2a0a46260eaa0c2ea3b340dd7eda79b20018a094b6b737a9beafb3c23580ba3b38efc959f043d9423cd24c2e47cc5aa057759d679f506c32d29e3a2727d68abbc769f6e032c63d8cf8a3975cd4707310016c1769d85663c3d3a984a72fcb8dc09442d76c7dd31d05c5cea422615200ecc6f2dfe6544c45d534ae97c36f48d9a3e96893e286e3d8412f052cab7428e9696519d8590dffe32ac91b45a726af3853523445fda346ad743fbf9c0527b72f18acf69e619a1c961730206c557959d8fa33d1da92cef2fb296a0ee2fdad13c4f3947f51dfe12271940c4161c3087f63cdd1a064d995db61eaeb4c01dbb23817022c80736730a389dc85235997235ecf5d78ddf8a05d9b1462f6ee49e5fea89507082bf808458fa43e09994f432131b0477d725e710f9964cb6bb135c8f800bf8175c6edb52daa2b6c44c849c728559827c184253d486b962a8af9c65bb174d355ff374d13907310a35a9ed3b308c527bd4283be3cb97af0b2fb28ed0609dc4cad242947ba36e18042d12af893f8bd693757f456deea4bea2a2e6ffa04fa0e55b7cd8932cdbaed0a89ff64e5f347c79529bbea82428daf700d0928a43232ead280f2ecdc95a93594e6c1030f972c8ce14cd5b13a7b7fb0ea62b269e74aac96e929de99657a675a17c8e9811c672a7f9a4886954f46fac55662f33b037e25c1de2f3dcbb0623e5226617821b7724a78e0269940f6a54721d58928f86b10cb0403702ad46393f1cf272bb4cfc614aae3bad7439899a433a248078cd8b876019f3239bed9059752ed8ae7240d6d4728bd6bf5c1c5d7f0315be23ae208bc0756010305a2772b586aebbf9d26555ba72c3666ee5a829f0d2f6bf6b7be2ed78c83a050a5a23d44705ef67b681a0095d72f0a40a301ecf8f5fe0688ec9d333581ee3ebfb45af0342ad1ac0885037384f3f247ef956af596e1d881ed563e56b61be063b1f85bf6704fe1f03e8ff55cac217df4e57d4c6385837c0d1e628b0c64359a5203419894f4bb28bfdd365f99c7b72b67bc551d5c9f67c2a1af086a6126821ec25b1f4bc72dbd5d2e59b2bf94d9e51d5ccd1408bed47debcb1f4cdca7479898c8df7b348c55d948aecdd0fe22e80723071f74987d084b6b6b9a2d09647f194161cbbd16bee365ae4fe108778a4c40daf5aa54dada8de20661d0d13afc614a17aebbf9afdd9b490ad3777e870593472a5e1d1faec78f33a1c6bced1e8972d9125e687ca4336703d65e1b2c52bf79239f8c2f86006b64ef0ec83ca2e070a9389fd2b0b7ca8d890c2108be10ab050d961846677b529699f30aea36013043752a513a5e41ec7aa69126a916d2a744e9d72b7759ddd58d06d8e03d130c15a07f0e9bf030eb7967d48f2b83a2327f07ad80a25f3271418c5e40f2db391f87418f43e4517d86d24355b29f4eb7f6011ec93721e113dfa073f57eb781faf0f8f9bc8b8ce2a4050f9dd3ee9a4ab48a51f33862ce88729848ff257d08494a4c7fa936c0146c76f8361b4a03795132a76c9af0e72acb87906c52a9958a5b41f6999d68615ea18f7ce8ec4b5fa5241ca19d6306120de56c8bad36bb7691f0b973ba0cd4bf1357d57e062cf3fdd7b5c3d918a7f98727ac10252cb9dd711c8a899ae8f1b0f59aea1193b5980449d3bd07c0eafc9d472540a7691ffb37cdce721b2ba68c16087a92d1b1dc41bd086e40bb15ba47c1872b2e5e68ae295f84c221123ee93873cadacf05acb7495923579f30742971d924ed8f343378b641816461d1ed94304b5060aa7c26527229013e86d2f479cf4b7527cdf3f31f0d42b42d265515043a32f093e0b7c225b4ebe917372e6a12a74557265348624e44382c0a96c981b17bee598fcad40d963305d7ca0897aa5fe4b1132a278c1ce7459aad653c8c9ed3009ed775f47fcfa0729651b0091c232b4877f72d3e761d1081665f0cae0c2bce66c58ef91103d2abf0e5acdc3f4b3a43885c072eba97e7eae4780935b84dc2a781c9083c4c16d1081d2b51138c59763ad8e9c7267d60774b4f4ba8b9ca1ee4d6de80dd580f0ab48b1c233af43cc3795d8bb8b7286f6692de01d5b3448ee49ec6f59e857692fae3086148d4f3e26447aad3aaa2f34dfffcae5e62e8160b2b0bf3307b8273bd40686c53959efad91538ef9e5bc61aaea1008285b6dde9a8b1d295790cd964b64126b1cbba66e64b334a4767c6e729218887d62bdcabd62da503fd8c00e41ec2e4e97ffd53489a27461f80b7cd272330e6929e99880555d680d23c35ba0dcf6e3ae43122cb58f80eb02c3554836725c00ee3e098db5ad1b6796aaa7371866d8fd018e66edbd86b6fc5ebaff315b726c387170c63c3f0875d28c788b62e533a748f451695b76362fc822f00654ca05178340e2a0c8394d2a2e99d67fff634f263c7a05ae6b714a21824aba98652772b84b72fb0a765903cd6d8c62e09ec1375c02cec7d43b27c30f7800299a6af72227f231ab7e47e743c7fc3276a8d40f5f21f8fa1b5d3f5c2b49340ee104de2867907638d5202923518b42daa05d58d1732f27498a02da9446e5b69406f3a0a04e10bbb743c473d4758230fe13c56dae7d2c85f8aace1d5e63f003a6c4d82e0e729889075dc291b8327dafff12910676705f89264f51ced329dbddad803021d112e156128d1db0fd6c0bd1bc8014007ffd1bbba10907731b553a26fc7754fa9b5a9e871e5d35c1e81fd253ef346427892db3a5925c6cd10ea2988213de5903e272bbe32a7fbe6a08e6213ffdd1fb18fa82ffc98b93ffc2a76e515be8058ec0be4cd002ba0c9163a73bf3dec83783c6680646f88c31ea170af7cba539fa0eb9c272a5b5572cc971bda56a654d450b141706ca5f57dd0853cd3b2ef771015f02317257c63a2e394a57b0ec4eb12acf7269be0e498506967eb041b6156aa13564dd720d511b24e455e63d88a2e770770fca0d4ac205a54cb3ebad753e9ee3e720b2724c413548f9c5180b5908df5b509cc767b387985a8fe650b2621258582f47f902316fcfda5fd0808a2a80e8421cadac1969fb75a9a0303ec48a5c966e16d05d11a85c895b7ed4c77d90fdc6ca1d71137ff1771f985f097a13b73f0e1743a209725b8a4e351fb73d1ac77d0d04f98de5403eac0ec7259caef0f0b4ef5ecd54d253f30111485ac9266a95fd5775014e3aa566bdea4653fb500df35ad3e51a0cdf51576046fe672e55c9d8f10226155d2ab9e2948f56ce9584636916aba11ad57e72182606158ae44ebda060c3787ec38f94e29722b843d867dc92d3cbd4663a2a0d75a82b4ca0f5f52fa4fcdcff9e94b71b7c69f2c3ccc2571ce9c270fd8a10c772cc581017efd4a27ce11176653de63da5e35dd6c2849988f94853e66e279b177227af9bcb98d63c57ec26d2a29d8cca3545c19cd67316c5b327eecf229d6b8172df6231cc361ba730cc19f7096d8cba2419779e90bbff7ae579caee285a548b720c78a2d186edfb326a8ac8feddd1282198ae126b170e89f73a60c20e3ccdd072937efd1e2bd2c16d183b60e251f7f25ff86cd55aeaf378c36173019964f1d24e0db7bc5ec8b654465086dd098558ed3cb2d078f79d0d42ac3bcc9adc69acc737a58b86ce3af580af7b615db6028852f2d7b33adfa51d8c5aa1ae9088ed278f72833b085bcdbd366e14c1bcf6f7def8d7f82ff85c324a1849251fd994ecb79039b140f6e0c0197b290c4ed965843e2647b48092d5203745e7610e098f9466b77235bf95d3b3441bb58dae3cf25cc3e34a2fbda3fbb7e29a64b35f5227e02f0d720290b19e5a65a3bed9e8d2427db357b9fe56d764e9837e5bb9deedcefade69728cf84cdf7ef13870e88aadd3161a292d24b10a8e8af778e85268209641a3324b6a9bbe936f764a209df374ac1dde81313d8e6863311af26d9c857200cb7a855e6de6824e4d96da39798e1fdef860e83ac1394d44e1c6335adb5d00a9fbe0dc57da68bfa95fded143520a31a959c9961e9071ea534e7feefa996a838b1fd3d772c05ca87be84969cb413d3b4825dccbb8b445a8119b65e76ad18117108fa233728b76371c599f765782b84a1c9aa3a2097188992bb4dd8a38294857cf0fa4c45f03aebbbea06e464932de932b2f0a7a192ae29c1730d46d3fc3f6cf314f6a3a72a5f98badb0c27f193d92b20f9f74466083084ae5bc41a78e7b5c20c2bead1c72ac8dfcb833ee88c95f68edd0d5786b374cc244581d122dcc10fbd335160877726c7717de35e9de67055ceb9b0310b64cf881b5f95bc0cef052ae2abd16f63c720cb19e8c33b578d159b18e64152bbedbe9aee52efc07cb884eec55b1731401729bee6fa32a63152cdc27b54d0a820a1e88b3eb2160f157811baf672863995b2373e556b669553e032a27f64189abe0260d55ff0a7fa671a4a5337121d933a072a4e8aadf5af81007c2d966a3edd5dbe669ae7ad27b4ca30740a9ec6326d93c729eb9ab42ab71ad1ce79e5248bbb5c28fc41dd6b6003ea2a6e735143a5c6eb0727500eb0fb23d956468822da21245cc7b01f1ff0c30d119868d780710be01a956f7e09cb42d1d5f0bf106c816904007d9e45d8b2ff37624cdaea0fa3da641ab725ad74d069a310cbfd2639407917530541943ec97b3f03671d9133ac1765ee2102f1835c6865db003c1a27f33b0849d4b785fb7b3b8cc66a035dfa74f292df1726af944196e99acf46cb722639ad7a74f0916f6275cb417d992be431a851dbb12ba20b752606cb8b68c17d7c3970a14a585f945e19afdee9dbd0f6c6ed0957e2e8dc63eb859923b718ac13bb18d1c2abdef28b7432591220c5aa92f9762e99372a677dcb7fff692c666eba509dbc73bef421678334e7737409c50251279b36062351adfb238aec383d39e73d97220f52841c9bc661d329a3e95647975975b2f2784e302fb73ade186d1e9596220defe7616052125b8e43d943f8644dcd8f2aa720428497a815bce798c6d6073568cc3dd2f70094c069f754334f8f579e4aef4725a31b9d91977d89f1c4b3a3f984befdbfac9a5669ae0b93ccdf86e432242a972483b93e0495465e0facf8163d3ea7c88e6213ef6aa8572017ccc065e13132e724e27352cc6652a5eabaae1aba0f4f662bfff94c825ee9c909174cf4b49be8572ebb63e90576d58f1861516799a6eea09260d336d91c0d85a1b4b0c8ff676067259762cf6735360fa7786d0b558d0f0afd5cb8b166b4578c7bd99f8caa6b55872aecb4f3fd9be1402c56c660a42038faa8b712adab60413b9b3e1743721454b4ec6fbf0c48bb42d2c9f91fa62219fba0ec1b0f3cf9c819b14128f56e802bcc651f9ea32121863da028c1dc2d7733764cb6acde3b6884c40947d71721fbd4a1b6ccefd8812c1a01078163271ddc55043e61bc004936f37ea0e289defd79ddb4f1d4295cbd91c99d86dc9ff7b20c658963c37aaa6984bf806f4f00056b23cc25772060a76ab049dd10b7b92f8bc6aebe7e3f4c447189e869dccc6e877cb882f4a5b476db3e43f55ed92feb051acdc51285d0c88c6b9f705054a336748bc444d0e337e929e38c783f10db8b449a3d2126f4d66298523346628d14b14ab4a009c386ce4120d05e6140d1535c18c2c8341ee437a2f5323d3a13286ddabb738d4274b7275532fe866d1b4bd647517faae60158a90a9aea14b2c087e0d543e1db1388c2699e2e7328c0333db107da3e6655aeb0b343d2f979551cbb0cdeb5b30a876e106c4fe067deed806ac7ed92a16df7f8521609992d9ee009381f7eb02270774eb72a3debb4f6921e20920f73bd2291c88925dc9a21f61b2a5d033888b6d25308e026fe4eeba3122a9499a754d682b2962cac47effa1bbe2da6122abff0e95094546dc8cd065a8d8ff4da3fe390fdb50f927c94bf15453719798fb1481b389ddc46c1c197de8ee083752e54ed9b9eedf82d1b497d1bc8925336594ad7d1ac6b97572e94f07b82d23f5aa5782055953c9664cffe9103e212450cebbdbb6f5616cc572d79036f3f00f3db5c9304c2b19a570d84716b166f4226477128cdc2853cfd57253ccbcb6fba6871599063721d476431eed4ffb482c0ece93836c41420fdd5f6bd5c998aabd517a15be1bb8229c2ec502c459723ecd444f22463ece5511b75c58a9e5347c07a094793dde591f152f3eed458b1f46d0e51b09f772c018cd8b6372478ad30dd950299a94de30c904090bc7ea64736f9365474f2d8bc2163304e67293e6c981e395c82bbbba86edabddbad086dc02501ff3dd4e8c3c7696e4faef724ec9f6c527ae1a1c642aff862ae1dcdd1d2497eab8ceb56eab9e32d2c88c227269357297663fda510147b0a3f2405287920c98e42c45aa14e97e68a60d766f2edea1966f27bbc954e296368a09ecb5966b6df4bf792f5f71173267fee0ab572eb7c94b1133baf56a9e6bc57a81f0b2f9db3a41b0922d78abf38ab1c1b1db126c51a8325d53b1a63993a20a776dd9c003958c17fb7984ab0d24afb1f50ed3fb72448321172c4b91279c13ff3b7e922ff3e7c69c4973198a262c2ac20bef1b4f371b07f955cf4905c24f688829d56e62462601163bda3be1a102b67ae96df5ff72b81ea7fe47609c93e06eb91e0c31f1201f8b153385a749277860d59ab0715d727f6ea6999a5b05edff378e72864d568c6829fe16eed510bb9ae351e303b002099ca0be157a44eff50080824a7f409d17dbf540ef9ac454ea579411bf18ab16660721eaa1ff0a62e0df98714a9da7ef79cd279acab49fcc13db8a002a0575eb56f35f413a108691f6225040e8ea5816a77f53f8ec5e89b3d7c8b34ade872aef5073c582fa3c87444c1072ddb14a6a865cbfaceb371033222dd048bdc424f20f6038c2941e00c8c52156db137ffdc5d1548c87aeb52c316fb5729572519ab7e072a886052510e2f12db47c937f3e21d59d9f68ec954c42e8603377251765c00772189ada0c92ceff240c275baaa0488cc49ac6cb9028316e9e0ef7cb9e31cdd32065c01906ba9fbe7c83ab2d4ac68a5dc1c58ddb84cf9f1c997ac6081efb0c67264ec4599ba2aa2beddf35df18e82faa1e36276493e755c52dfb7c3dfcd297b57213b3c62e50b22e7da532b3c366120a141ef6d3befdc53ad1564548676f1b3e4651d53c7b72a527312b3468da4151632ec048e018621be5415c39dd433145cf2c9c230baf29a636de79dbe4b8a275e1ebd08ccdd74e542bbab7a08450030510722807c0743d3ceeb4c485f679214f883a4ab2c47ca1f5e84078494eb31a2b007232d3fc1d5a4d986daa78e49fefa957c56e98d9662f5ad5a11befd8a8cd6497197e811ccc8b8dde3afaa1e0eae09413aaa98e4d41c3db37957f5359476d75fb7216a4cee431f80bda656581333251d0707495432a0c55972d1bdda482a9d13d704421f787391e7cfb7c3252251a3cb58adc92191a78999769d2e30aeb1bab817277336bd89f19a36e1697fdb15c77529673e4c54a858b232f7acb511d837bfe4c4cb306621a02abe366bd85627be353d5b5f44fd9ac350c5056f0c801ca85c815a4ab51f1774ed51bfcaaf5913cdb08fe764e15e04285bc2b76fd55af9253c772fb62bdd5f6a4468b34e8285b98cc6ef4432c89aa40da382e3cf34b497dd9ca25c29679cd147ddcffa3785f6eb606c440c904a0324cb5da96be6095454bef4e04cbe4502add50d9e06da7d5981497db1d41fdf1b1e288880caa07a10a446c7811ec0e61bf389c1b42c8241dc5032227cc69c46f618a8a84fa96cceb53f1d92d7284a5caab6b9852215ccc4d2dd7225e95bc404f63d53c872519e6250e1e4eed723e66e488946b270108b2a69285bb8d93c9532ee6ef34087b76a7bdd81779081b75b1d4332b0f98542470b886f844c74f3be8204a319b1f82c3f3fcc871482d72d7b0abcedff3dc962d39c29c1838d8849ef733621ece53d3fdff4ffa801f4972438068e720d9281d5b0bff9631ab9d27c5f6444834748f8603b8762f95595c7225fa4ff08f331b767ec27f18b8f8c04b3ae3e44cb7fa350badc871c8da748d721c50b595f1f9aa4a5dd261f8bfe3ee17636970f79826da0cb41e3d37c605814b1bf6f3feff6391ecaed37f81c47235a7737001607aeaaf30abdab422075b667268d33dba7488672a7993c09523a5d1f2d374e9bc58f7779aafb34a105184bf72ee79b4f8456a52c5e1fe729dd59b66a8a4c54aa4c78fd4b2a320e4f9a2ed9a0bfb52b7e8e5a41dd046d458ea4cf8708f66c9cd5fe5cb5b350f43806f9a727672ab7a3df2e9f515275978ed77ec1594148b27795d823878a623034d71455273195de8f9da25e66d3c9d86c20be46e0dcc52c54d9c4b29515ffb25e99937a16a1ed9761fddcb7c1e2fce0fb91fb2dfc0c6bc5473bfa6466d00d1bbc61ba4c3e24c17f0b482b82c3e17478729e9263245b50c8d066f97714af2b008cf583ccd503fbbcc3de880c897923d965ee575c41af4f87701af050b70de202fc51c74133372e2264a2d0dfe88b1d4ed6d1afff46d9f3f40634f2ad8a57da296d256810eae724855a0482f1b7f09e8ec693b5bf8dc8ff6c31a49dba0231a1a504a55e4c30672ce1fafe7449db2547814949ab0b87ea7aee899f9edc3be01f9e9d1b4f7887a72d1e263c5bb791017961400bfff43d73c7f3e6ff7e4f7a5c21617327ec935b572cb12d82b16faf0d3ee689f835fb7a9c892318b0a9d63ff5cf69632a1895a11169db080905603245bc1355cf060270fab197e7abbabb7dedc11201c5061ae22720fc173d0f0ba4b75991ecb869326bbd72493eb5058b94227e64baf4749e59072b885826f364a6f8564c0ff824e885bfc813376e127017f4ad8fa3303c56bda00e7dccdb56d3b3fdae5012bb1f7013761750c5b4f9301e7c21ef2dba1ee94c26bb90b4154c43c3001c26ae752bf5cc6640be27b9e7421864bc87f5b186b50e572ffe2a61c6ef6fd724994736814fa84cfcbea849efdba166629cc232f11a8a972e70a37b7c15dad575810b3667578672413e1f229c13caa8e0c60c01b58cce6729c315245bcd4d11913e226d033d4febd2d1d8da9fa1300e5c88215b2ba035b586ed57bc4ad0f62f17e1b2ceaa7167280d2a959ba1f96122fe75878741b9f8b72d63481cbe0b6b12cabaea03b85dd83b0e68e4a48da65111bebbe0be36983ad72de6d64db9848ba76550daf94f866863c8d1f7263e2ad2fa361d9d47e1fba9072a144a05a29dae23902c318f9c13c5b8544145a6872b8e64c494c4cbe56d56c72ec417b463bf616d1c31a2cb0fedc98730db6e688ac683b780ac33e4c15557c71a2f3cba50e276c984e667567eda10fe244fe6034a845099e4d78fe5b0cbae9720b780a7b0c72ec3a69cc82263b36141b0cbba5577068b1d6544f70bc610e9b728e26c5b82da5368097b96ebf9972010bbf4a7790ec27353fded68ff358a9b625645a8373f8ab84ed5ec0f1de7bd17fcade9f947c85d3cac976e337016f87266eb24abaaa8a484da2e29b901f9b3d1752949b8d371c2b2124e3ec5c34cb90fb117ebb1a6215faa537729305e14895fbf130f174fd4c1b729c702866bba65f2d72cd042192c64038920b559e58dfec70cf02bbe23278f544f5b1427753e9e0f072bdfa485b31a81fd4db51a19c8c256cba2b115b0e58fb33d59654ab2527aa5c728663010498424556a4f26fb75f624ff534c0bdd13a39b3d1781c768ef48ade07368aa7a5dd58d255e2cc1beb6ea39bc62b03074a8a58af1d2fa5355e4ef4fa63106f70f0a0f667312873b57edba626d0029bda5ee06f93bf28d6a80ba0e99c72cdbdec3b6fb5e26ef1b45c80e92715b69ba186a3f522b851dab55239517a7e275728e4e00a62226350cd64da855a6a4557a4989e856c991673b1eae1b6b7413de01162b6ba002e9a886420b922c52dc4fd1e4873c3d67d47af501f151c06e45272a09c892702f82ebebe707afcc8fc5882c9fc5565b09d98ae0c8650ef7f1c7240eb837ae14a1d41d0e6a1e51bb11a717fe1e8ffc84057c48f83477a8b310e729d439d99f75b927cf6712845e8efbe6a33a9552fb8f73aa177b1c4de82990b6d3d91420dd16e532ce0072f4375444b6883ef2caa6442935d271bbb6930c1567232b35d4f58d605d5927936520bdd0a6d680b8519226b107dc4186b47f7b8db5a5232e72dcaaf59f901172afcb3aed72b0826f236721819135cdbb32b3469db7204ae797b621a4433b06567ee3be2fadf2a8fce550230944360c68e96271d9972d9ccf0fa225857cfc66d67c6f583fee4f1f5c4bf8bfecc01f43f83e56239f12a1bb9ac715a8652eefb4abbb0a18a8a7cb7a5fe21d96305a4924e59da0a338c036b3ac8a187676e37d594c0cbbc11444b30ec033aca16f5fdf0e0d658ec1ed8721f0847a1cb1a1ddfb11aa731fd0340fb53072d7f9aa21d3a7a885e28f5a4fd70955992abb573606e47c9f967d14cc78da66055c111d36900bf7e5a81daad0f41e2a512e2f50b9ba4d79e27302cc0913b013198d278c2584a7a3b1d18c8c8a572e7ccbdc23c7aa9d83a611168a9e4ea36aed5055760cc41983f903e9edb997e725762cf4685ee50b2f0c5e3a3861b122c032ed99416c152c45e53fd90f1b8a97273148058d11b6ee14279920ddc943cb29083761b11552c654705749c1d66f672a70afff9fbc33da272685e2a7dc90e6c11339a645982b20085a21a2ef75e8613e9d0533e972bef83dcf840b78970f0d0bcc19394e5f90fa60b3aec75482a787281f7921b93c70534abca5226571c7cf569728f145b853e76d09da0754c275619d07e282123a37addd9585f381e058019bd44b88bd0e0493a339c1afdb8894572fd98d3b77ce563d49aa94c4776ae021c2259a4593464aec33461beed4d0c83373c37a2318f02de10a5da29fd94ac77a0e3018ac1bcae0464d221ed72353237720fe3c116ba385890c181c716522fdd4842d3115f3729ce86ed0034654a22812795e9bb2bac5c521aa5d69ceb58d8b773488bac20517e290f0398253b57e5af5a6d02b3ffa9149d49ea0d6419cf3b93032fd9160aae43afa18f20581b66f5c0729b168ec20a9a0c72382bf07b2a8ec541bbf818faabbaf5c0d757b9faaadcc972bb05b316981109e65965f0eac83a8af7271620fa24f7db72850a0120445a597285cdeb379b211708825246be81a22dd029565d270184913c645c14d66bedf67220ede3d8eedb70bff955f0a44b5dcb43b0297e9ca26905c121eb84e5768d6c725fc93753083817d0542cc6b58df54d3a5cdb10f89f0cd2806cb392816dfdea7258c7529b17faf1b3b9ae71297ba68944af5f722724a495cba3de00969ee3c83bd9e4c4510a7ed8c6f061cbcbe827cbea31c33aff42d51da65419f37f3f48c85a5e9604b94e75366c54d122bcfca3e1c8e0e37a8ac4a73e46dab6ee767a95d07214239a4e6ac8934676e59bab6b58bb5803b3d9828e097ce2b493a9f181bfb77211e6033ec0900e063e0e5faf2569a6d9e6560b1481f590621e80241358bb51211af24c510e37c7007978178b4ee4cc1b14c9c325d29bc27ed9b41458c57c4872d453232c2c809f436a62bd4a1d1797d3900b6599e1f6b8f273523b8fb5e98472e2074ee4ccbfddb93914bf04d21f8a4a2a95ed42a29f7a54d92ffbc3d2a1f96df67f05e6ceb658b69aae50214927b573c5dbc2f5ffd2b9e6c07086213dcddb57cb0c0563a90ee4438f900f12da7ce684bdacde7f46b156d3c68aeba3c8cfa91420719adfff7bd7f343c14ecc266eff81e2d2c8dfc34b319285bc6074ab08c3620d7bfd5e45368a0771bf8e515a8b15d1d89aeeb0fc89a91fae2c57834b04c704b1f2ef89ef61200615c6beb38595b47d97f2c03198cdc0de1339c3a6fc82b35948b4fda56a6bd15fc6ed14d3287be7634a078ab98cd6201ce79d938a0ebcb33b497c0bd1a549487a541bc4c3d70f8c64b87a0ba943846ec11ef8067d6dee8f726d3fb48e7eb50606271c97c327c88ca8112e4b1c59fa127cf1ca29be98e86b43e4f56b6b5c7004c5c9ddfec3821708e88288c65cbc93f0a445f9ae8bd9044172adade79a0c02a6683ccb62fbd8a74b10549abacf160dfd672f7566117da978722029990ae9cdad5e8167b8cc684ba606d940bbe692e4306aaa50096cdd79d872ae0cffe1f722b9019219ce7177a226a312d241071d2722290c4b52b5fe973672acd67a210484b123e08a037d32653bd4c0fdcba8d6419e2f24fa607d874b85431517b173112602022ff5cef3542d1a2b37bfcc6613b976dbe5d3dd5b572322206f805b89788d1736265a828c48a2db9ac889a4b4b525aae86a578d507ec50472edf22ec22804f7aa06f5b4b07148c44289667460b94bcfeefb463fc66a32406bb4191a020510496a5313c695b4e0867a8997bc32744ab6a53241c661b967091273554b4ab764257dbfaaced122c30eeb1868a4cdacc8f2c4e82b7e642ab9662179ce722c2ccecb9c0ff5296e4c4fd1bcfdf2f1dded2916dafc1d00a3fa9284372b499fdd733e2d9b96955029ff3b4ce365bc4e2a5fdef7af00003a0de7fd787286193734798dbd7303e873a0a8d1079e9adc60c25f07bc11ec1f5b82eded9972ba8b703c89cfa6a95f88c84c3dca4f75513385222873abe405940bfee5024357413ca2f883b5e7a0a91f0ed4e739d643294c857a2b8ad8fc502d249749363472e39f78630ae2de98a8974481a647050efd09faaa5eaac8121332801626652172ebd74d1d904a28bf060587d4c549f4e009021d96d271db09328fec3a9ac16d6c86af6e70113d0608b78dc28be9766dfabe0970efb1936c695d3f8e2b16bca758a53a2c19f9cce590a28f3e87745fc95894f7d7f8f12c5181781b56fafe4472720dd13cc5cf353bcd4c6c8e03fdea02ea4f4e3b33d0d79d3e8699b16ce38ff538fe29ca9f3f0e5b9be8ea2e036beeb9e571e9851496a25ba9d33d555b06f19b70ea07913337f67e5c547ec3b734bb55bfbfe7de62722c1b63c201e1572585d35e0093fce58cf0595245001fd7e23b5cd482491f233721d487f6c1a8fa6f538a376ab1c0790d2373be0c3fc2c96d02f53f37bda7ffbc32f9fa4c6706df80f12c4368d47812c36bb7f44d212c8d9d0b5c6a35d708791dea521487fc6c8b615d757234bc3ea15a555ff2e16f06e3e58d9ec3354ee43fb0d417dd57f97deacb57e272737add27f13f506db68ad788ce2ed18818c7d44b15723839b4e52fb023e7ed58ce7d50e8945effc0cfe1b8e36758db9af618217711ffc37ae38c92fd9f9692376ee243b6e4b3b39655c9ad67ca5c3f3dc58fc65493a6bc9f14fe3021fc441d720635348b6be4e0707706323d8b81d83b20af0594de9d809b0f39ba986b1870729f84dd77a5e86b16dc21ab327fbf219673393a625e2cdb1c81f60dd82546fd72b863ade4ce8cce3d6b17575cac1b32be64e8f691830f9a3f73ea636453b5560966561839ee204ffd804737d445849c7cb10cd015357c6dfebb8a079f17346b7261fd830a0dfbf891d5c6ef635e8fc5f8fb109035334ae5ac81711f2747f501728b41b37f4d7f3a70952897eb1378c7d95a3cb0d891b65822a1d35f7bc5265772449573016eba72a9bdd1aa1a8cd011fc5b5dc53181552620d604b00f2cdfdf7268ddd59d91d568058b61b87b061731c4e34e1519430141d0330912f74c1026728d97b826e6d0ba7984dcbd18ee37f447fb053aeb9d640654e3cfbbb6a2eca9729aaab05aab677182c48abddb15c9a916e0c03ee262d45740741db50589ad6772205ee5b04536020b7dd07d8b07c41db3f44ef0c8d1635e319709c3c1fac2cf72bcc23e12cce3029108cb717cb0f8f23f6dd0c67dfec056c87a119fbe157eca7248106f7ff6009a4353abb478f8fa6a61a62e58569385edd1d95dfaa9cdad2c7222fd80a59857159ccbb40caab452e5e9d5be846751cf7d55f68a36c5125d8b724693e9219e4eeb696bf6769cd1b8e781e9d9c882e4cd3e261329144947641a5273ba7c9c4193c8543fd6ed9a814a11ccda0ffb2595daaa45f464cd9a2f9b1f567afa9095a37892aae1eae22bec213f0bbee31b6cab893f1fe56115199cff406b27488655074b5ec3a59b5c75c1ad0767dcb5b17cc57406ab22c28a3f19ecd572f2bef2c21a0c19baa5fd9fd7679f4edaf349636e770c283d9e24fc1aba3404173ae493c5c0db7a2c9b990c7e43e1ea53507cd9ae42a4d975b6ee0c95327efc72f56415863de92843114754807526c775e5d3f43136e3190830c75648ac435948f5538f53868cbe05d7ce9e30326fbc2f3e89266c4e2238e9bc8dd6134630a6183a7acd0142f34f9b653401a8ed681ff485b47363a940cf374c468186e4ee2972b7fc0a53c3daedcbd9f6b23d6ae14d2ec5556a3912e40b9f8543445483fe9a7259fb47cee9c0f841bbec688f00d7266c022046004f25e37bce1b0a52de2e5c1aecb1460747d32b17516f2947464a557fdb213b8ab06f8f908a1d0749c12b377255020e0344fdc5641f63c9f8f29253054c20ef9e629d7607a5a7462d6c87ec2f235e071193c0a943c190510e6a8c25f4436606521d23ed71d4d3a6b8ea4dcc72ddc67979d9d96616e3007ff1d2766fd1a1547ab60565deca41b859ced3e34d72f3a4d459a28ef991d2c11d71ce687ca4e83ad1b774c4ff8aa1e3bf882fdce1725b981d83a38c4757892a09ac365a8e12df6b87eb25c753b849e6319a39ca457238b6e6fc2eb578b196394f33a8b40e293cf97cb2859c0e92de809617afb12f2d524f652b3fc88914b95c00d874f6a4dd037c20197f9d72596c9ae215ec504c72dddd7ecbbe004934d3dc804038c6d12f4e43fedbbc5519a6cd94f9ea7e4b20728c638bc6272b0cd0622d017a266185864139bc8f654515926c3a4f3c5f8a4872ca163ede7f0b7ee383ca4e2b7b2860269904959b458b9dc9acc39d47960a7c72ae8d1196ff6db66f44dbd9d6a1f149e956cb21162727fbc80ef06473ed591318ec2aea41da2b2dec4f5bb4c430302db7a407e983caedfc4fb496ddea58a2f4598870441d4005ded001817c301caa4a17969020aa7b630b4a440d160fd92619724822b4c59d05465dc7d7e2c4a5c8a89073a59f422ab08685e7105f30c7256f7244a7f7e5c0a1b9a8ef3198d01d0f10cc891c35aecadd89a6a72318dd96459a0161a6071c4403f6dd1d600967f92b497a6bd030a769bd9ad98a216f1316e149725b31295b1ee2ae966ec695b56ea592cee68807e765c696161e2a0fdb3acad963a2e6532f4753e12bb36f6896e8a83c5414ba1d06db206d9a40f7e4c8cd754d729a7bc8946846d843a6109a03b8cbbd7ca49516f1d28eb5c859c8a0b37b0af969db54e91103c62f8b3ba8add59a902d445c2d89cf625316d81c9d3b7bb370e6728e637061aa3b6f26c66aff0d122256d7e49bcf81c49d74396ba2e7d0d32627376a8baa712494c045dfff2419f7b802548afac335bfd956b31a9734621eedef15e8dbfa58fa8f5f97437f4061f5cc3c9ef11c9962a7cf7564eb954d4b88c0eb0ca98d39e2ee5d80e9604c9b9ddb92e8be3ca429b2cbb4c40114800d0f16f5f41fa0697b5dfa224d4a8df1c28589db3d56a48828d70ed52ee1660c60ce21e5d41e261094a63f459356691449c73284775f7ab314956e1976b887abe55e4c659b1093cebe82f95bd82e67ef97acb780c924a72c807f68d74564da136091bc2f91729e587a55382cca609634e1d5403c0194161d8c97dd554e62129adedb2ab9ce5201dac885e4055e4460af088a84bd08ed2ca14df752ed8b4fc2d438edaa051a70d24363ec2034c3cd49a5cdc41766785140a37dbe141f99480f6fef45707b4472826a35bead459e0b37883bdc1b9a3dbba07b958baa6aa44e8632bbeabc02d07258cfaf19acdb3329fb99014ce28291eb4a5c4e6279f81c5ccc96c92c2dd4b7728e8c63b50378e2386368b6c1a337ee33c6892e6d2c86bc5d25fbdb239a207c3f3f868ade385dfbc984234b1beb27e733905f69c8394f1f90d8944d0f731f0e7219835c75551805c1f6ca26e70b34857e70072b860ba465b2de369db87375bc722ed2a4499e6940472bd4ea3d53c77ad67cb4abef1493007b2b9fe57486be8427e840a31784876ed93a885c9d706b3c4121849dd1195d772cd0a2962045dd2c72e4ffff2636d9b5facf9451d46d71390896a9141f353b3737e276bb18cd7cbe725ec1d35f7123cab5746199a69b3dec21abae8ac719187f743b3b6fdd109bdc44db670de59bdf2ef92e64fcf3db0e4e3a9f3220e218dcab914d13ae313aaad4724e77f0a90acc2fbe16a4e3edb07a897909c7110fa85a765b32dbcc317eae60722aa97447a76cc1894ead81338f82f375a3c36a15df81922a1b645d3eefda3c20faaae8cf0b02e17f98af5f6b8b1093b866e0811b24af4cd4b38d38e9482b5f72a8d2d210a1bb4289a1d65a641e8f5a6216a972e22e627e0ee04306dbaada4572c21ab5dc7fb7139a77bf20ea104e0e6c7c3d6feadfbfaae696dbaf61a9e22f722b14002e38b0b392cda8d3145b086d425f9086a98505905474aeda56f02fdb72564d8234e4fe58dfec12ab0f2fcb93ff15685fd75b31459e01a4fd6644c3fb15c4a60e6958141b633b73d44b0da8c60506bbd1417aff2b13826f9dd7d6efd1727b1fa5be30f693320e201a7b3f029c0f278aff05db220b5d4994072c77ccc872b2ab825fbce9541797d8c3cbe1ba3a9eaede2ee807b22038bd5f946f0aa7d1725f31f71367230ee74d208641f9d9500bdd8a32f003c137a6e57252c0b8038356290b0c999f1581945f370cd68d5776c93cf620547acd88947c168622de5f6f727b4dc1446e65c6355bee25d8d751cd81fc174ab950088212919e918466908c0b7a1033c718a1aa0b5939b460efbbd5d104faa4547953329747dd4e421c76327215bc3bfb511f246271b6fabcca8f5c07623af15308bd276421d3a3a30b57a272229720dd8e90b8fa805739a84d3caf4c1213fb5a72fe67b0e163e43a751459724f13709dff75aa5129936b3c6c723fee846dabde907b1959c31203e2e944ef729d21e7b3de0a041d449e554a769211a0aaa352efa68e869d1793c4fb60f7de722730849a7224f11facb15c9fff77a6eed986821937125f0399e0d84be38c0c727af00a2b91fe8768a66afdac2117de9eb99ccd5dc69d8ba55ed6160d8ec33b72caeaacec96947b34b838c8edd7e22a44b0c8695d56d0fd08c33d6dd04f3d9372af2290f471260c2a2ffd7f8b6438927faa1c2b13eed82a8f38fd9e7f1731bb7268354e1e463aa6f51cd6e0285fb1e559102eb3f9d40d27461d18d0d76b111f7219538918d25bbc161100a2f71c8bc3f7e591139e017d5380700645d43d004f7299eacf02679ba06c3e5133472dbb947608c4598ffaf5a0702026682ccaca2016bb6f43ae8d67bd13a881a5a742e8a0725cbfdf4ff90561aa13687c7ed33e0072c76b157819eec70da7aae9f8ddf6bb039d6d27bc12959bc0479787b85fc6c5729a3436b8dfba2764f6ace701eb40d9a457d1a87125eb773235ce442b192fd10ff8b222c6cb01afe8f1349d5722aacf6314a2abcc98f6d44d7e0492cfbcdbd672a239cdc891c853bcd8127b22cefbcd24db1138fa81f89d29d66d3e8c8e243e723e030724854b171c96542413909ce153d939eaad0984f776a97e6474f035126ac7b4731b82ef7d0f09b2294ad6ef216abda565e41aad5955e6c2a8f8255df00dc30d90e5e8b48590a0b26c5f31e5d25bc4234dc8611b0653d081b69aef2de0720ae5133032fe1a941fccd4a9dd3c7cc78d04ae81fc4e3c8ee071dbc1240f113dc007b93c7c544c0c62b80d15ce4127d2208687dc3234a4f7988a55e32c93d27250f477542596e9bc449be0d86a89dbf4dcc243bdb0a2dc66661dacc3f313ec3dafcf11906e791463cc297a2af35fd04220390430493b858d15a7ae0fe5f81a729047a8d30b33ee7b76e3b9ff2dd491219906ff00f246c2020f555a87d169607268514dea8c4953617508f09335250d6d512ab88c20879ab2a005f4f91fbc4f7293ddf16b7b83bc3badf14bf4ad4709921cd7ede3b2c53848d7bc1135607cd8729abb50eb7d5f451af4d8f5040d0d5b72e6a69e38c867a28d114f009ac44a0172ce21f150e74faacf3a702918c92ffc2ffa69a6fe1287b7ed9e8391464b9e537236b703fc7a2687b036de919144586f60b42efd8b2208872eb59e6b6ef107e91fb38c2c5193af7e45c76d7c4564880af3ffcd1f68411da95a71ee6ca0fc12f772e1724c7bd3447d6ee9c3d89bf6b8ef5a1da34f6527cfdc7f0ed08f2b9b1e685b795b933d098e1eaea2c14e8fcc85334c85d89c10a9390f32916c31b679526912a21690aee567e15a1d6147ef6e46fb63efee80da14f444a39818415b90871872611f2344dfbab7fcdd79f1624e697a190ef63b200fe2dfc9f1b49ae4778f2572f076c3df4a47f9d366967bb5ff3013e4f2cd6856a69d568c3350c56570b8f472826f4302e835f583656bddd1c817401eaf277bef3dab055473d58309befb315c645f9b3fa67ebfc316f621e60417be26a5ad3cad2bf8436bc04d38f47fbde72cbc38cb8fc0120e22726592454a70602b5aaea1f81bc24defd2dd77a6749e4303ad6e3d4b5e3ca5e03166e1fcc6b59cb3d357fa1d237f5393f32ea47ea8bf7872d4a740d9741a97d20db0a0a55f9d3ed15aed5df5d2f73c2729c0934a4d65e46d27de88db689a0ea51ba5567d5adec22226b4fc21b21b7cec8ac476f08c556f025fdbfbea720b4196f4339cf4e47714d71e9ad3ee782db43388b02cd2b6e07072b7234393dff9cd15b2243963542e624657cf90258e5c1dac047424ebecfbe729cce2d9649bd9fbf58b378f6cdd05878b166a532625df37ed314ead55ab807d72316914c4d5de6d6bca8d355e35251542d00c04640cef53bbcd8bdeca66c871724e641f19612e7d60a40070415d64edc60bf1a298b36c044f854effa7b07ce97247f6095cc9ce8012b39484f7f67005d8c45c8503342473beb457f84788e75872db0d8806ab92de1d63c0f7a1a6738f6f88d5958a166c79318b0ca9bef0e628015525182960aee8f099f6fe5727f3d5b60adef5732b3db1f761e01df1b43551722c243ed7cc139c0b0056cc9a733484556896b29c5cb7e1c8294ecf03608c7a724ffb3500d23f202b4a3b7b83c6a12e2420efb8ceec5a6629ed890470f82e66728b446cdc91a7f387cd5962c2d9d5bcd947bf2b0f643f959dfb877bb621381b2c9155daea86be34eefa944fdba5797e1680bc48c9bfa1228a83546c65f953371ff93ef2e3ac96a39653be554a8dfe54139870ec2b3a631823412378a798f39872fc4d4ff460e845128f4110dfb3062d2b55103697bcc5d5ff88e46d95dc6abe36ac7f6b195ed286d452c93b9a223012378d0079b1652fce839203403d27b2b9726b1813dde44f92c2bb278de93221114cc41af7575c69158296c705db94937a72c5a3bb73b48ece3ccca0159440a2764c9c212e27a666e2d6a065b4afcacea82ea673b15c2e72f800fb236f697b7f9aa420921d588314d5b03f2eb62c7fa3ef40b3d1c5a9d55431130a51219aa3de0bc0dc5bdc00085cbb51efa756e148b2eb720db5ec93e4188ae2676c6c2b2d6f8d1207476233a49a74161c483e96450609004d6acc50331befb9c34bdf7fdccc53874f9a7ba9da8b6238a9e4194324a00a72463b3f6d14c44086113ab0535325c6781d0d2d17f52bd7c8efb25e113e737072b670e8f51167f3be2395888af70ca1473c74bda3037ebc3321ddc756c4bff40d077d983dd8acb3edffc25b021a3c600385c9d2698b74ceda4f97ea751fd7927241e603617c601de851cc83762eef3f11369bae4bc4974ebe75cd152febc3c937b57a04cc21d9947530c6cab083c4092f81960364843d2118c6ecf1135f436b1dd1f64ef45dd5ee6b1f687227b85ead77dcd2010a4462b1cd6d85334cfe93b72d4a4546286d14c4f76431071f25a88fa93a25e26a95522c5f91a99fb1c8269b721c7d1ecbfd7bb0b3dff52f8839706b83c697e2e5cc684a5fd31b35ca157b6c72327448495c227f0ecabbc99afbf70036470cbe1a1d2fc02a96253bb8cbbcf37247fad50caaa73da1d6041aa50b729d229b5cde446209cea93c5e359b7a301f729d1bea8b759b8b92e65ebcac61e343d133d09ee951817620dace14a50009d172051863401698fcddb343e746754a170149952e0d2ef4c3b2c3f4f7b5a1092e721349372803838a0839e1d145e227763e6d4ac6f55d2357239a2338879381682a75a5f4cc8a7761f576f2cbe847fc5533626aeef925f5ca7cac408c47931e3625ba688b8b45a43048766bb6004e5612a542bb1a957df5bfa36dd81d0b6114195db89e0e3063499127f728b338afa1f082c8a7bd9a305a6dafaab1f02033a49972174ba27d4c37dd7f01cbe5344980dbd75ed67df4e37b6413c3a07a0293a4e94b30bcede92910e1e9361ea4ca717faeec0a7b8395e6433f0dfa4eae85dd349504234ef5e1d132af890737a449654d9c02ed4f3cc9f74b79bec930b9d7b352cc72c96259594a54050b671ddd1b4404de7d031aa869ce926f0f41dfb62f95c6eb729a7ccdaede0528bb7f2d2f9f0dae1012990138cac7ddcbefe3c00a7e24525b72949155373bf4abbaa3d62969e2c6e54ac8ab2410aa955b90dba00135e5681d728130ff512f812953e919570dee153bc25a4c815c8f360dd22074b06b68e5c772d923053036729d2307175ecee23bd30e246d88d09755655ae371180e8090440cbd0dde471cd6146b35517923e47e31da7d2e17d69d9b83b517557fabca5cc772a9df92f6518395ad974763a1d088bcd4cf250ad8bbb1205b9b6c142bd7f210720c3304496aaa64ccc92fe6fddb3e331421b520fa6755b7cc0e44418caa0f7b720fd691f24782e68d3967afae0b0f5f5baef2011a788c606155638a360ae6b97226c798fea6bd43516a91518a01473b120b04c4fb13641ab9575b4dcb7d191a727533efc71c1da4f9581ead0829ef613d0aed53039c644df13a8e40eea4296d72220a711f54870897955e49a0c16dce61f2626ac1163d9313019cc8fd76efb02d702e3af83ba353f3a315856959d4b454fcb681c7267dad9fa19bf4ce8f37d4729a497c73d83b884179f2c4e4d89ad5a2116e4ed19c683f0b7a985c99aed7ae333608c490d59b0518347edc46c95ffd9a9db949203f663e53cc76e23199d07a724f36f46a6db20d6ed2764478d03949fb925ac7a58a97153361e0a5bad0c398680dedac87aa24db5b73eff86e28fa1904f4bd14936aa4a0ea2c5842c4a71b891cd2bd4404eb617c9e344ebac4bdf054758e3f353bc142466989dceb9aa05ac54bef1d1425a7ba2e73bcf659cee41be9cb5b0a69fd7a00d351e9196433be4d1e72ee3560ed3f4e48130057382bd8e4367b4a38ab1c18231de22dd2319fe397a1601ecfed956624ee632ef903da2b5d51ddcd220cc19901287f47776eb7d79d33728581a01d18e75aba5c2ac3ddf00a6b24fb6d3d75a82dcc2dcb29dcccb67e0a36e2421c6dca1c4323e017340d570647328364b2e91092f7bb519788eb54a5c6728f82ad283776d88ebf833366db04ab8fc8847ba4f532dfc21643eff3f5791172719a15ed7a8544cf725b15a86da45b7ce14daab82c404de3c930d50909cd7c723499c43d6159f425cd74e1397af46d7a646447b9555ccb75ac0498dfa63ecb722e051c70d1b4142a7397c548020bca3100eb40a07129744ea37e42cb44435f45ae29a7f041179065796ad06307e07c685d31905844cda9f8d223af345695497216c27932814d9d1f2618b76eac624a5dff51930d98e3675c0c714281e55d94305b4216b92958106226c6c01528ac86a545f18e7fe667ca94a8201fdebc075d72162ca35d722a9e842155ec62a70a9f486235239aebdd910b4ef4f3561df38a307bb391f80fdac9ba6d0ed7dc45959b11a687f9dc44868d132d2e94c43829950a3068df7cad40304dbb09a264763b1f1710564473bdcc3b6a6ff7453265ed9337d8a36199d905d87f88ba6f49a262e7b7a344667aa8ef03fa74c8a248136f187294b997af9bc189403866d794a5b8943e71c5e58ee789a1353955ce3ee616a0728ab415e7ea961c40c4929e73e75e4ba9de455521e8de6da273f72e4b0be4d3726e413d8369fb4a61a160fd8ac89f54a440228df9a40346fd82ab1573341961674fab8a10dc4f63103fc985059273536693e0db5c5d35984497db73438b107872948efc5eaaf98a8219b9bb3a0acbe3b2fe4683bde1235187e52016663aa52055f3a9dcdbfd02e7f75d16dfe0c744632bcecbc2a601b4cc4b9c53744f261361721be2d03161e1e24198f3df2522b5c8d66e3de57d44399195d20ab49c6ebb7c7250e612a404b4a590d3ab1db8279ab0224fbe96871fb3058f29635db623546e72dc7f15fc11d3e0354a86efdf2217168d8359e740e487ea12f7f0fbf443e29c72bb7b4b68a58a1aad6ba83a532509b96cdc92118574be6e9aa77ba2586af1f166cee2b8136fb6bd1092970a0cea14d39c298cc7168db74c6297f7985a94fd07726168d6f0f8631db9a02c1bb769580e8fca1a44ce4e15077bcb279b1ac9e15005442def0bd363d0e1ad042b00d4da02a74c0854b94e83398f56f9c353430e1d69cbce8a3750bad3bf8392b4448e79b3765bb662182cb37efd132ed22cdc748b2014a14e110f0a1513efb0d6863cc80ff29948a74b8dfde3bd51922b126273f5727b3f2cb3f3d78b9e662151d96712b69f9317574b3a0a84645da08be06342f072ee69589176c9a8da9a3266933dc34da41722f2b1700fd83886254dcbdb047131626dffd3c7340a13c2fc328711a5374f0a07db6dc17fbb73b0870fa4a5c01c720c39ad6729c6bf571528e044da0f24bbb06bf9aaa1d47e1ff34ff953df3e7b2c56c5dbbd83f871dfcc04a6ab7f8f453e9270568b3165b4a725774dba06acd8721423f8ffe9cc0cc23eb5ed9dd71fbb14606f297294f55d7b24da13e22623ef725e51b7a3524455d5311211cd9147647918c1e3401f73ea591ad4221dbf397f7291cabd0febf195ee869c9d17f52274ca7f4b5c89c2a3877d346735f60f989372b26f5bc82fd02848e7e74ba26bf51b0ca076dbcb6f696633f5f6d6d6226bb0724bf1ba781863f5ea29f93ca5889f79a2d2ffc2acd966bab49ab42559e494b772b3b227327066110dc112b92be8246949d6e7007ce8d1141c71a66d2194c508724e871e342bf16a97faa740f1725bbe1bb2f294cff1363cd6f4b8c42315d2d272c86ebaef48a35f3480acbac01de17d8fed997e40b1a73a38ebb7fb393a98b07293cf2838c4c2ae334ebc8ff0ded29ec5a00cd4aa77560170ba5482f7f1cbe072e9b658e5c699c71cdf71a549754481c82531b5a36d223c48edf38ffc67549b5172b7798b4362b4f909e3976bd70a47516e1564fd7c6551a4e4f5c51512495472906cdd0db6425758f88a95c3b4527764c36fcb39635d6bb68c667bebdf85931ee59e890c38a9d065a4416d1c87e06df2406d7c3d2ec6aa1a5e3293c76b80cc72d4873433e2debf65eb7aaf2c8be96532abcbd1c2db55843dc0fb14b0da9f1627b4d352c8784d7fd4d97d875740ffbfa31fedcee82fcd6bdd7b18f8cab60bd91a4c5faf4380e9059607c943a2cf8eb6dc60107fecf6746aacb3e7d07a99d077561a183b270242ca7dc7c54ff0a1f8e1cb77795c8a7042e98937cb3bebf555827253fba4dbaefc92e428c5bbd284e5a4ecd654d78d5009b0d745630e2ed00ddd0c984a8989e634a6cd5a00e858af34a9f6edf1afcfbca24e67c796ae3ab4faf4186dc49f76ac90cc3b4d2718023ee5ce3acc4cea78cda139a812e2eef2a7c98372d237bdf068263623d123be87baf34eb6b5813665b28ea1e4e0cf979ce02c43724972132f92ad166f2036d0199a3afa32ced394d7a553299c2b5262f76d5275724eeb27efdf26a63afce4ff91627a66580262eb37e39dbf08986a0cdfb53c02729ac3de5f18fb059686096e85e85950918813f136aca7cba21e45d810a9201b7275b7829b6e62c584f604eca821c7834098fcda6fa1d51e82371eaed2293ac316a1323c1abbe1a01e542377d3ded92202a8b328981103c26b722f652047bb59727ee574f6f32c376710eae3f30390da661caa5ef7c36d50186592d6b878532872d78197f62bcb5b6fdbacef7fc4e58836dbbaabb3015f1b625c2e11071f660105ae01d471ffd11f668e9a9d1f9dcb0c928113cbe1f01bc2044b5c9ec9000a5e7208c14908d57e87f1f50388b3ba52b33c603afc79873ec893b854d4da58a3a9727bc1daa51af0337fe67ee92a71f38335c24f891d85e3471be508ff4e4c01c05f37b5c384fcfc3825fed5f9bfdcc57e579c0c6768bf7b8e31324d0bc115d448726548207591e52a74f248af6368a130c3d999d21c5936470e707e035b9f4a0872f1d3b488f675806ac8dc1ad64884a4167becfa1de669422325c12ebdbfc997720aabe089a49453693b0701e87c754bbc3d660c762f2d89515208faf26075a132148c7c21089e25793c5f081cbe2ee487e8387d521b57795f3ce9a33353ca4a64ceb9845c4a45acb020187121a87adfca912071d86f6a8fe9b486973e25066472115accc402c1af50d3b5ca1b075fa785f997d027da1101898c2bd604deb22b727cb218d46d0f9253e46879e96618ea545f356e2303157a5c74cab0a7545731721f36b97e6d34c748868dd6db4ba00f1f48f5aec6a89e71b4ec8bd177c60e0a7259197c46fda15c416e2f14b679b1f026f2eaa4916cddcbdcb399d66661da117254c09c1a34bcffacc09a2622520331d70d89e23fbf595a7f88aa10c895380a6d1092e1c5dc2e77a059bdf4a4dfbd753763baa70abfc4265e9aa0361515d48d727de8432dc70423a87d7f02b74e78904154b05af2c6965a92662e82c17802bb72dab0b474751924ee0857674372bf8e041e37407c80c29f35c8e1ac779bbec5258ff85d37145987d377303180e3e5ede15bd8ad0bbaf540959e897282c162567282c7155af07746c121f99d3a74f939dc0e5450ff2549b7ad8cdd80fc2695e701401914264a93d04f68025ca21da6959fd4e431a99a0296095562eab841dcda61b200a2dbebc68e0ba07926447deaee1ac900d3c74eece1f59f07fa0dc9c33e722eb7247fecf36c3d865be08eba6c0bb9e42ddce7712ab9fe0294022f8cc65672b25678043c692fb4c0d88628608c8abc83c54575abb45bdfaed81483ae12ed0a77bfd967c5be2f5a0fcb057f306ffbf6d071d088b799bf5be68aa60683dc5b3e45c11f544a9088d8255536889086a9222083ef6e61b22fc42328ff23426d882c336f65f65e26ab7de2aa4f00e39b1e0192b156867a074a1f5cb512972b8a1972c4b4e8f3c4990ce4673ae27f78cbf864b74bee41d8116c4a500a5e018e6ce0300b936e7b77a45472aabe3a5056162b47b577b2c2f2711e943d4ee6869a4b37729f6f2f62983aac3ca73bbfa80f2f16b935d6a407dae1ed326d4b14d8466b8f198aa659e8bcce667caa303348f4d7a959b5945755f273c20af4193a83c0179b728afb67dd46bea1b13a58009fcfcf0d3c54a51ef6579d007d6367afc968051672ba91b0d3ad0397382baba489f00b153b9851579eeb3c25c6e93a2e39d187f472facf13a8d01e90c9b85183633c05274cf8452a7800135c1655491dc46f166b72f380992fcf92637f72f781b05f3f3e67f633a4a5557252f8e860348a67ed042456dcfeb99a51d2972a09b2ff5c33755134c444416f34545ca765fbcada53e65ecdfaf6ada8b97a234c4f399fcb07e1b5654ffee7b6b9f6c618689cba698bc80018c72b27c2c84e11ae4cb2ed5bff48602bf74bd56885e2ad42d2f2510d99f42de93b8d292ca0b94bf6e4ba8eb44cfef605d3b84a3cba292e18c6558b11f4917201a650744938d0eaa2c168964a301ad68274d2d9a17c0d33ccfdab63ab759e723e0a69c871f46e9e7ebb3d8f65f05186ef7321f8fb2fd2f023afa5301a77db722ce9ca03d940615df92b892c261483b04429857a4fe88ea5c00d1529c3f9575d0a44a838536bf87c2b346fbce92b000aeb32025282b3fc1d2d76b1f33b96196700fed2784f9f25970fabfe351639a810c390d731eab616d4d853fc13fe75e0723ec5c57af2cda1fec1a71a5a35bc763b45aae93e4bc7ee1b70027fd234fcf24b3ec690d1d49f528c85dfb530e2d26119f2c8f74303b24ae1eaf43e87f558eb67d15d77e0da0ff951e087bb5bd732cf40e5ebd8fbfa88ab37ea5633114011d572abdfd54827c16f15c435ba2f7427f8f3226b37c8c98387db8f0905c1401d543673465b3230b42925ade4922d21733d9e00f5517bbe8058d44400f5c774b45750987d3a2385036198013f4c4ceddc9003ed1e5ae030713325367d530d46ba3644a8c6e3f4b73eb07971ccca032f95d2326a9d2bbfa08fb9575dba8baa07ea0b4ae629a9551fc5d433ef19aec3bea99c1e2cdd4702e4d4c97a63ff64eef49f7d7203758dbe30ba340eb11b9b812d2c95d899f697eea6b7615a847f6b8eb6da210fb13f57a2a3f5d6759e27cac63e614afb6159be7202d2b4e0fc070ef25bf067027171b661b6d4210c48f3f363a471fc6c1b84b7aa132cbc1a9db503ff44d16a7233da31b50a580e9c47a926233e575143bd14a731d9d3d2a2592d2d5d2a024a5cee938e6ae39279d7659510802019045f914d581d8a5cbef329f07720724083723beb204e2dac4059bdf0c9f330851aaa59750a3f65f0047029eae4065649892bdb82bc3538ceb2b463258a64581c588dad432efe45a31ae8e366e110c3af7e72e0b86c6f1ac6426fe58e35d19448c95880dbc7169b1147307b41983ee29c68722c114f879ce38d8dcd62a82527bfdff7d01c32098d36fff0e21c0b74d99365148a82b83a8d5ce5447e0717971f27d31543bd1e36c0759d1a431638543e152c72769269fd85958751ae1f6808d3f6e03ea20898f54785c1cdb7be1969319bec72e53713ed86e4bedab7a7a6c54fc51413cae7ccce9f2cae22dbb738b9a035d97246814a7bceaa9407893f3ac44d999b8165c62131af6c5ec18ad6042bd8f26b7222d811c8ae24bae70e1a2acd504702e335db3765652e779d9015a61fb269ce3019e00654849c6f117593eae669a0ba0decf179b83bde1395d8559bd7ab75ec1c1493fd1b53cf7060fa6e8fcf08288d5d28faa80563a7a15bbd94072e8508e17248e9113bbe4f02d89e6c2b1b3580e8739e57afe9b87eb001c1de6cfe15de1b728ce1986b54b0844ddcb0a5bb84ee0ee2797f391949e2ddf7fa685e1f5cf23e197716b979fb330dcbebb3b7bce93e0d1cb665febb669d313aeb274397a53e8872c809415630bc60a04de67ae671d1547bd8daf8f0f1972fed6b2b278ebd884a72ce032e86d490b50fb17995d978e9ad19491b1b769a934f10227b2182d767f70984de86c7c706b9b3d345e82762482297051a26480fa59adc7a362b60926807109cd96497dd722c5d36548031bec6c47b21ef1760fccfe4634783df86640bce72a35453f631ab95236496facf91c9055251600796a7bca2135f96bf2d288d6902e4a2dbb08367c57c94cbbd1c3d83aa950eb941eb65d83e90bc3b497b4d102c7270b8dd1ff19a7ff74c96e4a446d28e250920f5928761ef2609be977b6c1464728911ad885734b362ff67a19b14ada6811bf8d812905a9d55730ff6a387af9350a9c3a04d5da9acee7ccf7244a4832e0054ef1e36726c131a7f58a287152f045c8bfcec6fe582f6d842d9c6967f1184603052a76dee174d48d71c89276ec58672e5ba3886c07e991064f605f0d0bb1f9135d136d358800bd0447651a9607a4172337ad83929f95c2ee28ce101371662ec57de1b38c6724a165bec4dd16819fe406c526902f763fe75f4b69d04b13611e2c54231bf2f1d66c5ff04b689ce8d5841ecf3227f77700be04f79998850f705d0f2f986fd05de084d2f1f09ac8acffa30f2373569d90e4a2f30eba9b62e8488b12905c7e17432462db7edf178f4d33971a9ef4584f200dce868153ec7ee51a507dc45aa449c5da0e064b010c090528472a62f518dfd17686539a88d3983a18550829a0728f2741c9ac6e2f0178a4fe672496d1a1032b867df146814ecaddbebdf956cef03a1489777c54c9a6e0067e04553afca51451a543a6c1ad5dd1e7e392a7831f44a391f1fb4234a46bfd4727f7249cbfb1ff95ad24092f36bb28bcf1cc02fa8b021b4be6f8aac15bde013a543460d84f35899aa29375b40539a19f11977a798ad8b898549da325c0bcb83bacd1a96ac04d9a396dc59e5999cd8a9056af05fe3035d0b6f7eaf49f2df955ac312726d5bbde330c54e2dae148e591bce186db0fbce78a3d354dd79bc90b2078e2c72e3a5629d9dd3382368f985f828f149b8fe386a57264f328c909df6e9fcb48a7263df2980df94b69bcfe1ccf265e0d2a854cfd8565779864a123cde354a532172ccf13b8b2464d8bf5d45b668cc69864e611c4a38242fa2fb3f85b1a48e69df7296ea3b6605be5ce2205c5ec3d9fa9f2b1f335943514d4f505f512117e7288f1c409d834403bf848409c96b1ab1e9855d700f50d6f143dba562e690554e185a72520bff9fb30e91fbeb880010e966c912283a8e3f8793dc4596a8efd7fa894f72fcd154c932cf50778b6dd8c52417ca04d4674c8bbaf9b430c1554ed8b1acd82817e26dc6659bf853ed7c2eac24704ca17b21c36a8119e9fc8586e4cf4318624c59ee8ac800e33b08fad04826db0d7a3f6e24ab8552177f4f174b44efd579d03e2f53f0f6c993f9b501f02e0e5daf1d72f312cd86392b19bb7a44fd38af7c983613c10bf461488209c2fee2ed628e0893a13d7abdc0418f4217b660714877b6308f78968a6241bc3d6c9c2cbca373ea3ec10a4b96e6934f4e21bf53c6f09e7372098c2d925b06bead69f5ec3d1a00fff94b57965fa11d6fc49b2d31c678a1eb72ed78564e8454bddfd8c7cafec8ba10ee4827a7511208aae9078fa5218909723d88aee03ef46b8603d8e60d603e1eb817f41c8197e1262e4717b63ed2bf5499720ff01784af092984092472b808befbacb0f1a31063517414e42c44fd39ad6572ac7b79de11c3ed1ec19fe2f40f106bb05ee30ffcc54b28ae2cd022fdd39bee03e433064c5ca4b7bb95a3607a9dd554abbe9f4382761412a816bc58ecb5af771af19dd6baa1b9b251e48193a450c677eef995c5c6bfac23b1f52aeeddd2ff7172c4fa85eb85dfe100788145b997f4e5786a1acc283e226e7f15d4771816c13372576afbe50c4bfe60508cca85e79eb80bde78dffe4ab2ebdd7b259d5f39dde77243845fd117fcdcef3959213d0b1cb77c22e358d31c065054b45e14096006d272d59f2353eff61789208d4e3278696b04c2f5be43ff0299c700fbfa1f74a67c72bee73eb345afae46bc931207db72d7ce6f9cdb5d544fb66a77045a8f45f1130f3d3c39d85458a1eec78be7fb12a1bd14065b3511842d3548ecab45cfe2147a72d777f17794cf84739824bb84da685a0fad0de5416481adf843ee715fc42e581360ebacf06efaf1edc9eff59447ce970bd94c0ba0aab74ca9c268bfe5be5e277286211816fef6430575d36f42f809a13099bc6bc7fc5ac339f5cd6ac21d7a7546ba210e8778693703abb0d84ad5528332d43098f898476f4b20d742dc33024c72522dea8772ee3042e87df60648b89dd135d9f4c1c3617ad9442a855b7f61147224ed73afff9104c49bb1d06ab7621997744f13c021c33d550de22ab129612823995cc5f6fdd19b3a7cc3bdf687b8b34756ffeb8a39a91dd3b1fd24a0bf6da63a3358f138dc73664f36e0a48703d6949e8a92f475359706fac10bbfdfaecc9372018bf352061ac49a82963ddb7b67c776ae344fbcaa61e7c0421c131cf45f6e1f2f1b72cba04d31ccb80925ca185fd0510b99057deee03165fdee921b21b3bd23c3d5ae9460e4cf88ca004633054e0fbaeb6723433da728f67aa66708d9a3ef72de0a9f2e6d9fa109e2d00960926eea9fb6b9968595f230a2e28e75e025f6905381eb99676306d5bcb8ad71cb6b198003ded640b70a6dedbef226d93e4e55517290260c95447a3085bff6f81eb2649f3ff880906ff79fc4623b6440ab7ff93372bc5f6d35ac8b97b8b4f9de0d67294510ebb6a9c0a76bab7cb1e1bebfb675780b38e0cb0eb94525cac49d7afda20cd92809ce2c89c9f65cd09577cc54f42fad7249ca023d61de33506bd3fb50d9a7640907c1c2b650b31fcca6a46bfd1b2f7c7277e1ffa730063ecffc8ec268a9e45e4231b0f54f070c04d58f8b30bd00f9223346b95afd31e1b417d3595f4d11d48feb33ca120ee562275237a3c955b7404e38b34c61aefe5102c3b60c31f2e22953fa4db5ead7007a531cc00a7e6f8cd5ce69ea25b8d959f75b667c135d8b2a5e11b0d7481c20bb916ed5a50ee122a0532829fda82396547ac32f653293ed3b86a5e0b2dda6425b3bfd2e7ca3b1f3265df3564061fe3604ec23fb90a00d791ec1e5f5a4fa2ab7b9c478678350ac2fd4a4dd14627109b1f05d9031d5eda6ebcbb6af1edb03a35d3287bcfb4de8e89909a5f4028e17bbce1a3b8433d8e1b4667aa62afadac319fad6f4fc1b7cbbda0018a19172f086e46d0419166a7c5f10671762f04035acfcbed48564f62c6d30501004c572b88fbe6cdb63244e100af344e68bc4a52848cefce2ddee50c30433f597fb5a72c0f650f116f2bd0502e97445f2edccaed4a5840d6ec56a2be9b8e393fd85ae42b8f6ffaa362db37c861fba19f6f419c79285ecbe3fe1f4aed3c1f2cc814ce1722bc534078a106b6513f2993df5e4fdb4b32b93923516c78b23f94167df243b2a28f98b5a47d001ad9298b80235013ebfad27f4dfd551ec11499cdd9d540fd714908a32c1c2b0ce1942037efbb17590640fb749a6b7f969377d15a6aa98a62672a212830aff65c2d5743f4c165fe521511de120aa1a3171da790f291f1ebcd226281599c0a38533c507a4821229c3a30c632b5212eda1b801f96b03e7c6c0d4727e980e1ed5ada5a1a569b4640ec256a1ce6b06653a1b5168d336d5bf3b8d6527aceb811da90fa4c85e11df9f2ee8538eddd24f941b4ee9e35567d154d90c6a1390ff32ba5f289ab88747725412d763f49b1ae4e81680d7f2b7aa5eaf4192b072297fa30cbbfee6bc7ed6db9b5edcb079d841836faed3e97178c60e2a2d8140644fbb659278e8ed09fea05e948565c8adb221d65e85253234088ac45f909d073edd9bcc79571b322146612a5473a9d47d91058d677074e0b3453e91d42356435697c6a44eb276f5817ebc81017e2d468be672bba1554afb922c0aa612ff657872094299fa54ad2ff2da595dd17c835b769b9c9c54c13982a145c0408875f6987216a6f0a642a4fae76e29108ab59a5dfdf28b002a499d5247bd9cbd96d144cf297ba96c694df37e9f7bc339472e6fd46218c36792ca1363728d1e07701a638f72afc5e2e7dec34dc576446bd585cf56c64d4112b72cbac28fbfd6e8d6c0b70172334bfdeba2d641862fcbb3be14cd8e05cbe4a9c72e42c68e15e497a19757ea6461815ee36fd11c03cfff47eff154ef756720064ac435490b3636b84ed3835b1e76631bcbd1c2cf17a9f178d3ef2c9e59284626d457a7aaa49a264bd0a6279172fbbc778a24d6b280b72e670d81569599f1dd013f3f0d4a6859667dd519eed372f847af0c2dae03a4b5c58d6671eecf3fdef0f8a5c03063588cdfc8b3a8a5ef6f1f2088001af3c2d650cca0d9ed403b76bbd79344b9e453735be1e90fc9406072987af2f7db213ee32f9142f43764667f54f761372d9b03a0147a4d4fe7f045722d400172f02c6cd683328f766341ab0163b80977d02dbe7da68fe07be6c03272a6172efb0613e85c32f6d393c01b299088dfa61c4f21de3925c03c98116f68630e1bcfaf11508a6303b002419d21d5f6ca4b10a9bcbc3a697e7ef6ada0cc6b454ee557af844ce54303035d35e901ffb07f5451ad8f68a2905ebadaec53eebc72dd1cdb713fc6f0dac6890a37e7cf66565086c0e65dfc4a164de1cc013433e9708ad55a349521a0c239aa7c64a0dcdb3df0dc4fbc1d99a7bd53f637aadd476737121be82d66f7fc67c63eb1ee051d70021e80d59f00169b4d9765f57a0ee8f1553860255caea1f7eaf066a176550051e90289e406972d6d70c43bfd99d6bfd97284f38f53b92121ce693572d52f90cedfbdfb118bf2fb6c46487d93386d60c87251e027447dc1b794a96cdf66feabf5c3a3d43f0351f718fd5b665b4af1337d4cae0e7861f6d3d98934cfb08340fc3d95efda3f8f91df609567a72be2b6522e72eb6d9bef84f05abd2d130ac1841ab403647d6e030b5019bffeefff153df09172492ab804feb1d037c1cdeec7f8cfa3cb4555fe5dbf499f733d50e04f2780cb72bd2ed8ed50536cd21d9f28a525283b0143f547a72dd0c5cea10a5551f4276c72c8bf97d11d866fd505ebd6ed346f2b528e0c537551e15462ec1bfdb10c7ec950b0ffcc97a37a2ee2a348c03d1ed439107650af6944c0bf91ae6b85357afc4572d5f1731e4b9b8d3172a794d34be40d11cc6fbfa4c471e7e3a6379084dc2826726c60660a36b78e14d3497867cf1bbd7e22f555ee271480be767ad6f9510447722193813df80a59833ee3040968b8640424911205fba65f29d9d3395457a2095b0095414629969c52d57bf41138efeb3ad75e9b8038ae40690c6f882608e45172bd19fd235bcb3aa0b80d4596e755d951a0b77165715ca1203b120da68319070fb828a6f1a7a6928c1463de1a207044c206b51731d70e633c8a8afc85addaaa72e07a7c27dc455d34f400245588f3a101f8ebd51f46b101222f8190fee4ab5372551c14283f1c87a4ac5e02850eccbee5cd3da5884d81a7c3c95b2bb50be2d669c560195de2efed051b9b76ad026054390b0f62dbbb59ccf74c56087c78455e72e2f502db92dd37d382c47d31a1007fe878c1bea82855744a7712c731540bc52a2b25119a5c0adfd3cb2afc0bfd109f34c5801b72dfcca4605d4b92cec108ce59d061ca4183cb6684a2a5c00ffc8cae74c0e014c9fcb30ffa623d476ccb23ba72cbc86960bff103865a3f334467fcbe642abe1df82a8a1108c7dc2e72981504724050dee6b5175dc781232fad3bd1c2520402767f05dbe2c9a224df7c5940c37280d905e3646f04ae43896738767d2699e855f460c3a78f7b202ed608f177bf720807047be1c7261d5dd757b975753b564440cec0baa732b8ed66729d14171e33c13641488818cbeec75ca0c386a94ba05fd760e6c86380bb04b30872a98b862dcb28a72f0d3b66641afe80fde738c43a28cba86600a38e200d198a16901b8172d1563334756bc9a7e2e1d607baffb70532bb2da74912f7c5c7b9fe62c0f4857247ce31fddf6ec185d10c8b2765c8cad76f20fdd9c00cffac3d0863ea739e50720170bc5c7e108821e1230f953e27c979a7b30ce2c1983abd3bc549d095714872c8ecae01228dcd83f6853273609e806053817150884196974ce7c7381e300e72ea089d275c1080fc01702270821b7b91db211d6e6ed8c2f9f71bf638327d01728888c10bd6f9869dd9388be13aba584785981e3d2da2023f6c9395c48d08e10501ce6052e69adda0fc1be1010c26eaa0406150a225f6272ddcc2dc79d9741472a65ff14b11f06854e67203f0460b350ff9105e800e03d69f2ea9aa925f71f172acc57747c8911582d78a601701b5ab09d5b31ac262af8d30e4af17a3fcbbec725c3d046cef2385b28d6bc42aa279f0ee3ac4fad829b95de692fd976341e916349e1c27c8a19a57f0bfef8e8de7a30f82c9d36175293b496e8248df8da679b96e3b27354739823fe29a1dcb4b69b03bc957fda2a307086f914c57b138bc6f2872bd53db99138603f6301592de060231b05aa3ce4eb1c84cd91e72f098f70f1272e97cc60e004700557fde38496abebfdd872999a55422fd4d6dbad7195d56484dba979830b2392202a3288eda90b432462dba378cc243894b2c11edce43277d687117f8a83550368fb019ea450e8fa17eeb472c8b06b28ec9f436eb6891b48a7298a7ac752bd67b17c1cf3dbaa59deea3df0e5e9bdc58764b5b2eaa525493aa728ad6fc4b4fcf19c68ea13a7b3c51c5dc30bdbc5d793cf72e00fec80f914e761dcecee80d0ce440c5a7723dd5acdeff9c9fd633b43105cec5dafa749764c15725a45963cb8f3b3cd7548024a62d12e22ade62e42eaa8fdfb1c05ea0e30a9337722f8d42899fa25b0d8d7b3158e8c39ed64844a1c4bac7dde23f763be3fe3e4d68c85d5e7ae80bf6b33e1bdeda64d2d4a325185b803cfdf34f582803c37ec80a72bd3494d28e83ef0fd35a6300c9f239e1668873a9573a32a182100827f234da3b5f327cfd7cef73def76ea93be44ca694db3e9b1ce789e9bb4f2e690ca631c7724d4f33212393740229316a2997e99a1f71df00a42ddfd348e34f63882b9b2172b0fec80835da8410ec9aaed8a63fe068e0736db682d78ad04c6c8715d5612d4d3adc5adb1ed83b93ba4f0adbfbc6c209a46c395130d93f27ab7cfe9d571c840eb5e58cea60bbf846909b2be079b02eea53261b9cfe482382f708759e2b03c8724db655a1698f08f3b59d784576e5a308c9305097ca6c5a4e3e81564cf68f2244acda5a2293f1574926f2bfb7409443ffac2d69eec39e18393165701d0ba0de1e2721623588b8c0ad84ee98bb6468b2a2c76a3162fb3476544dfbec1f5366a9721071d4e4322c316746d5903182683d85978a3f55cd30ddf2f636f8988af4eb373953cd50d7cb49e3196a7e2a4128658e7e6948a96d90032c62f5b04078ccf446dc23432b6fa13591a1494ef88ed2ae904f7f74d006ae18d92c0e22981ed91a72ba63a6ff1693d8eed6e77246dbf18d51b12e1b7fc630c55508db8ecaa484b801e0f5b474a7e3fd833dbec6c203954106c4a8c62fa07613ed6993bfed8fa8cf5612efdb4cf980d61ea095b03ab1d36287ee7b0ea0b9df625536e789cd29a44572a11fd45bbac3f85ced3e7042653e7b2e3c1939ff4bd5c5965bfb697a5380d9185fea4d637f2757c1e4ac737da95a2f9cb2fd7040b39c2132c41775768dda1b65d90584dfc2b744c1dd80389693d6ef79b10a50428c6ff4f8b2e1cfec9d00f572147414d4655eb3cb87a1a581b6bbaf5ffbe74f07b4e8a5a2fe00ed35d70f6a72806d87cacd9bd59cbd8abd3306a818d1a445a95f8743b0faed2b03af4fd4a472fefff4ed3bb933ebfefc5d976958564e89f59505e7a3183b620163516443c672c1db70af8874a0214b37f1591562dc4ef4ebedffe629fa9f79908a896271e2729f75c737b2e8b6a6bd3c36ad6805bc820dd350bf3e635bfbec2a29712ee75c1f2e8988ca5419b8e3b23adb7b58fe00fbd4783d25f36cc6849a2a2cef39b052727ee327ba379245f9ba8321ceea36e6dc61a7cc6fed0875b1852de86bf978d5186b5a13874c43001b5b380aca71a26a269ba070766125bb93c75f5c25eeafb272caf262b5401858ab61ecf90b952ecbf6f3cf29b0276faf322cd4672e17a6b65a1349b7df5604b2133dbd22262dd2e4b5c72d2c5dc9c77931d2ffb320809e1d723885bdb96d4258437d7ad01ad677794e6a20631ca0e2c5b99961fbd790cbda51d141d00853375885d1949d83f3d44ed89b14946a352d23171bcabd714dcf1b72789501e68663b4c6cd1359b8cb6272e7f67243a79c38705fe3322fc77c0733729b4b3bf2c66f860b99993a08fafe25ec2492bbc030673d63aeecf8c2394fb3729fd8afbbf1db94c46e8d76b18a5ad772000e7cffb39548c3bee822c2854ab672530b205cf44c805046f3b1ae68685b0d957d98801436b69b0aa2725dcc1c4907c75b1f76f0285cc12cc6ef8bc8f6f176dc97aaf51a8f91bcf4d711f19cb4a072c19f522787f7f512e9772345352922d776db5b0c3ff94cfa1920c9bf7edd4d70e1569cf30fc700e697ce9db84bc032e19ea0b3f7e9bf8105ce24e64061e233727c25c57663090e8db575bfb8999517b01b21f09787d7e311dc649922c971fb72c32ba982da27abd76702bafbfdb0b6a37f711aa27d61d1ae50147a8ee4799e72696ba30d1d20abe89a532daa34270f1e0d09d293b295250285c37a0c423d370fd17ea7ce73dd898ffef88dae2fb3b277455ac671550c509b36b960d1553ef6722058e18d97866bc609b92a9188efcfddf77d4c26483f25cfadf2259cdb671e55c45918ded7cebb6e68a426e8b40e3f8e175c55a7b5f89e6fb119762b43a5d472a28e87809f202a1f580bf9120480ea7b91dc3dae60d399f76c6d45a669d8ab727c84320ca999b02dedabaf39247e745f2d10ff2110833460ec21fd3690c0cf1d5ccafa105b1ae48ae7300906e8660490f639876989407905a6496c98013c8a723aa032e24f2ffcc322a97df86206c2f3773f15f6007c7a6715a5a0c830f78d7257a144fc1e09e7d8e1de8afe1c886a60c0fdcda217958ac992a15a57a7dffe72d902eee73be32ee92df1e75d8cb07bcafcbbaabc75fa6283f188c146a9383772f1fc0124cfd7300cf2c385de2ce69eb6d343d93ff5941689c9fe615632a90603c226ece24289d8378c05bc2aaa7371481b4d1b04cde0afd699c4a3df09a6d0486f9e519cc3b05423df73175a6118cca56e57a5e39f71f6b394cdb7550b6f2e58888cc59da581f49210e6bb9acc8ec5e824063afa694eafaa40c2092f0f8dde72e8a7d3302fc082873b372d341277576d081834f65cbe1648cab49b5798057372501d105a373d1aafe2cc5bd1bf4f2211fae9faa61a68bf5e4c12b3ffdbeaa4720ae8fba889339131ac360c644f25c3974fc17ac51d002c5c0c476e2aef59e244900de7247303289b090b99002ebed583e376ca74c57c117a25d8e039fd5ff0490da0a4a9fa2af8e0c11510f075225581237c87602fa87faaac64abaca635db31a7bf41704e5976a30e55010cf9382023e37523198b2a3ae5d0420f153f059c72e77d1da242c1b37499a8f35be2956e0f63801fa2291c5d4d9ce7e694abdbe8339279d4532c84e3837b4d033dd6ee9d6832f295bf30b739ae5844f10215718172fcbefbbeba2420e30de1a77dc3c5d67fa5ec5443df2171ae160a32b0820d9f72d34fb570b3d8cb71f2592c5149ef75343fc394a23a2257ddf55bc28a8d36693dec86b9c3ede9a6bf92b57e888d51dc70d3ad589b4acdf14817d38fa47a38a5723168809a89cfa2a587f89b64559eaca589f272c2452067954fa3f0420df9d4726c870d7ff4bc60918e7e8eaf1d9935257551031a669a2c87e9d1cd71c8f9f5030a19201f72e679445915b30e87985c5ee4632003bb3f4978cd47454adeec7d72a0cce3974ae736d72c9a17f3e366854a9a9fd72e4f4cc385b862af84343bf96a7ca3d203675f6f52d3a68212f32f24ba7469db85a4dec25cad43cd4f8789d272d217abd5922f011775db11ac10ccda4e09c8089072d1b72b5385d7f39010c86fa542e3c946337b20c41d0bc439a2f4ef1e7dfa0fcd3b85b8e877ccabfad23a72a77544ea03589231c0d716ae684c3e40a25673b2174d84661ca13819d1a95a72f37da2710057e3b85dd64adfab931de93809604666e736217c8293c67db9ca7270bff174bcec3ec26d58a08651eaa3bbff300d93ca2568d54885b4a9e28bf96f6f955bfae44731705c2d8155a8e086cecfb08ad74fc7c6b247d0027715cfe81ab9bc2c2704be3ad439aaf44b094873b186268f0720b17a20b53456beda7ee072f334aa927e937174b2887688c1841969316e4b0a213fd3ad331b8ed000321d69ecd3bc375716b31be5095e7f2c9e06d157f7fa84501a0edc90de93a70ce99f722b6bfbf27ad721c95eb8adbcf75145f9d3ebe64113e3a567834105b115e4f8729c4f9edf003d575ff88e5e23ec79e9f0048593a4f7745b0951a53312b1e1d5724526c0e608a78a94037849befdd815380a6c02170138f7a833acea3f1af17f724b8d41c89d4cc0024b94f5d51ccd47c9389c5ffdad225676222070031912ac72f20397eb164322e804eb24b14ea016376d99d07860fcd134badd9c09f910bc72aae2b2d9fd387ce0540e1ed1411413454733fcd092e1c365b6907bb501b004722a532fe6f11bf5bec205b7fbb27ef294e0ad0e38afa78861d61ec51d288b050fb327e47cc274bdc62944cb5109f1c0e600957c0c6db4c2b981f3a5ff1a373872f0e7277c8541accd11f0529af6e2703f870bac30b9f0a6ad6c2db512a5db4072d10ecc7adc188f002711eb55091854d8d5e8fb55274b939ca0792e9b0dc49872bb374bbd8d0a113844395b27dcf00b6461bf8db1d989336fea7ecf39a045a61d2e8d3aa3f975d445833eb359e5e287a2e789fe1b0b356d6975b23e2209b888724c9bb81b37f9b0a85968e8c2ea3e8b2878bf11b00255d787cdc41d6c3646c372f3635bf0a17a43e1f8cf2b7140685d03ff3a7ae35b3cffd31c21be57aea780720f2b7a73d95031bc40358eed3fab840672e731eed64d6dd6f36a12ea2cb16972e36bbf459349e7c938b30ce0417f3e717593e868c043205f8a5f3c3b5ff3422c74575cf4778927d4ec501baf04dedbb196d7a6163cb1b12f2566b9e938b6e772d2a01614ce8fe3f129f8843031a3431eeafb4a1b28137d90acad1a6e08006036ef0da502076d071bfdc7596d96c82f1874566e0549b67cf7800b37c223f2d07282ccb09712c09d2b69d83f3cdf1d84f8187b00715cdce0b426ac0d6a01027072be493b9c9f00c5a080b6fcbb37ed4a41a21c33dc981b480278dedb1cb7d82f72707e6025db497ea5098f3ee82c77d38148b20ad8d7e83a6cf524e33e2d6a006ccd3176f88d783b1b6af79f33f8c9209b4afe6416b22c48e22957d5dc08d59f11f235cf6b7e330ca74df6ac634266718e336d5d3b171a20d662c50efd9148dd29c09c76f32a0805bed9d5c73248a950b402cac86fa15636995792ea3a4a9d14696dbe98a8fd8aeabae7c66a36ddbda3c066dd13b85ba851032ff6ebe3c11e087216eb8082e938b1003ff151c8648464174cddae19a9e0f514bdeb4b3bb495037207c0c4a34522e00a15c932bec7b1da98186d8eb6661ec2b2373059c7640a6472d13ff3465d9155c4de80341b3a1638b447eab3fcc749de2043c85d0f9b2c2b543f70331f3cbea4062ef1ca2afc193f1a7ecd9bc0ae297e78b2a40879fdd845724f78d2d325469bab4eb7269a4e2430c87392d557328d62500ddee931f9f70472acb528f0f14d73dce7d93334d9b7b72544038d1d5b5ff49f3fa31b68f0d75b33a16ea1cfdc1e2beac796c6ae7ad2dd2f757cc0568ce27c2f79c7ca8bc36e597263ce1230a9658cf2ade9db15e7fce2890a9234e3d4494771913f95bdeb8d744e75ee319a6104e2fc3f5a36ad68583b57c2e41f8f526d012d2cdd57db79078d5aed9a9dce8f974026c34bb9e794d387ef6fc23f8e779b6bd0d1b3af9e5cc0ef6692a6797f56e736701884152da0f5890dc781851273a60c7b454fda6c776de0722a5eb1d68033959a5a54bd9df256a370a7c656dafc442e69269ee60050319972aab7db38a6547118ef95849e2273c120a9b2a8c01780003fef8eaeb6622af672ef69606e1d7012f2866b6f7b3e4a16778c78a7c07fea2035e07898caa1f0c57249b15c8f39cf76ce488584b234851dd99272c82c347ab8a1fbfed8e3d40176334f9845ad27bae1743b82f1abc87fad827645b35aeb2fdbc95e9a4655f848d5720358f6f12baddd8b3a1b7f12f3732ab66f1c9f013101939b0247749152474a7261dc3c51167a7aac1effdc17401fd3e3251a48d0f2fd05f069fb8935ab12a0725ec279873dfefd0f967c2d899143eac5526fe1e783cd0f7951a1ef81efda3b72093c755ae7e11b78a8f028bfb6daeb6dce3f6d89d159c056192f8ba27f7c594494e7fe3a45b11e285bc165c6a587d1130843fdb4f6417a4e4d8d479fb9e82361937ce400e576c5a6844d8704cb7cc3cbd3d4215989caae7d1ef4361fad5961729ce920c9b6e2fbe0df7f5e922e45be618177410fe80f7906134728ebcfb793173022cc614cd4be209f1ef310d91098d1bf273658c5391e621f0c203a8aba5b133edd1516e7f90d7f65324ecd8f0b60857af115c0f3f892e84a10232f5088567257693f29bfb0e9c58a82f99f6cc0c5b046b803504d2e4304340547f20c19ef32513d0beb75ed8a11f78cd0d0bfeb367c217c4d3ae5328fa8a87274feb6f26d3554d3afd697c96bf1e3f9d0e255b6a143d664d19a4a10e6ce3d3ce2be8871e10b938861579118c18733b4756ff552e23be2b2f0af054d3c1efb98426ec41fad19d73d959b055cb2a32ee048b86c148074b41266bbcba1bc5a63553a1a83f16772150a217cad2c771deae610ddbd7891a83b38aa9616551b314dc3cd489989c07201be2ef03e06a4ee0cb7537dd2af4ece0d48e015be7f7c8432b3c69114462c1af475f1bb88ad6b35c91dd8c955d7a3d43698f955b755635d18f397575c6b4e72a0069904130c3c2d7397cc67ecce80b848bc563c69bd0d09f7ac8fb86576c472ba7f4a4addd0a1ef7970a94d3efa2aa1f80f95aeb9ce093c0a649fa97e66ef42b40627427614ddf59dadef38832bdbf00b88e7d274416b0c7c9721b7c54d0272915c399b488107782f8d6be52705516c3fcc23967d11a0fdc9d099d9b804f4722cdfacad0fecb458a378a8694e97d40322416dc3039414cc73df8128247b2228845298a3acc44f0286b2454bb6001f2655f04561f9b654f96466a632cea69872ed54f716770b31c75369d87223954ac5d8e59330c8a1a682bb47463e81831e729a05c27d0130d6ef6ca28655f766d29654abdd14599b89698d2c47107c39df728f403708f892165ef97932246837a1f50bacf15266e38d3a65ece05cbe2dbb3cc1231ad6637df38b9a876163794fac72114f313ec0733272fc31aec1bfef3d72d4f6eeb133839232e5eb1dceb2ca0fc44aa5fac175b7158c309c2fa60409ae72a645167deca8bb28541a31b048ebb5cf186f13acf769f4269484feb16b58251449a496a12399ae60d8534107a1d2020b0f2e097421b96a82a2fdf89cb9263f729c615fe1f64cbf42096ab3106c64b73f481da4e3dc46f3eb9d259352b7ead851e850b46053a038bb68020a71ca752f45b5925a7430d61bb5875b461e610b8072b4a0d35e4b0df1d71a1d404da833d5211efbc9aa51164e30f8dda1e81d5957505a3b2a16ea33276975774708edf973af2d3f739b6e8568dfc5388fb9e81dde23bead83e84ccf912119a6d32648934c6f3e0ed0f99f57d18446e4bde0e491ba7283124d8871f8eb53f699d6affbcd30820120baa338df02630cd0603e77e64b7211e37df15919ac6de50c7dae373289c742758c69e03cdf50e49b4e0125f761397f3fcef2b61563b7f310a82ad9c7c04c7459c4b8b2e48fd6ef48c53092cd6647b2e3656bc31c0ed828b97030fd90977306138ed1ced92aa9e92d30d03ee73372389c2853828298be25e1ef632473a2499fb115086feb4d75e056160784363e18431ec4c6c8f385f7243791efd9a7af70cf504eba399fade81d1783a14b23882d66d9a4df46f46dd20db028e6c777f8058832a82e46fd93a435b264d449508b723156af18cf06f4a670f699b9b9ad84a2f2ebf0aeab2ec5b0ca88365b242f3872a6ed489fc2b96d7d5ef0290aec624bb4e48c9c2fdac81e641073bd72e05bed72662b600cef94478da1cad3c578ae720234c5e4493d1cfb80af5d35c601aa800c96ec52aa6e9001391f4a9b907179dc36ab1b6e38bd712eb1d6c967923a662d1a9b50f86d45d9a3594426c2e3e629aa6ecf9e10794ddb87150bc23526aa0c9b26ac7e6d77a29be1010e294c4f65890260e9be5380faa09689cf07f5cf43d0dc728b4bc934d0a1012b0b0f5e51aac8c3e48d75e9e2ecc6c525300a4ea6bd6e710fb7353051e7445a5c8155526e665773be8297f8af5cd23a9dcdfcc81f2641e8726632e8f948fd91cb5ad113ca5f7725586e0751c6822e07c9464c24a6776997726660acd6bacd4184f5e78b286763a814fff559b1a2bcbbf0280ad8e2e07dc87205a39c1f03b411d5f9603494e24566dad9de09d946652f399514a4f17b2dd87250f66b0a6ab7504206735a31454b9923b87402666c95fa05c94f18a2877acd5cf6c2a317852107905223b9d94f52bafc96db62ffcd498c05a6df3b1fcc6167720d2666c538c3d5060b64b5f58074677a219edb565e97c9da20104f8bd8fe5772e6857b076b3e40b00ec1673b6126bbb73e91531447a4a7b130c111865c13007220e2390c44911d6873a5ae2f334c6f858fce65906e07dc4a708fa27abfc068725449aa07241812d326d6823aaff578b7d34dacf927670e51c8351fddf452fc724b359ddab0b93e749553c7c11704e3dab0714f7af7e4763685e6ad9236a038724e5d3bc8a430643a99a41ceb37f1017fe2d589dbc8a21b6692568221c0cba672fb6cd00a84aa0f5b22a37233392dfab7b032d3b3ff7987f34ac2d174b752c072fc9279f856367562bca76de30fa9c88f852b1e44b03b581f372ac7355dd9a472df554cb21a536f0b5136ad6b9cb142240d2495b48c9aa14798f1e4d55f37277206ec4b56c4d82420e10f3b76f8f6c7f90456088e535c158f0d89c89976fd0b42c36ef3a024480dbc6a2ef1130c09b0f3cdb878d5907c6c6faee9d87d60c7eb724f7bea3131506d388f1b5531ded1dfdee67997564cfa4894847e83b20dd52972945b1a786ee1e5a2cc83fd2c7527ab4dc9cfbd9a309dc54cbdecc5c6e7590772d0619b6d851a071be44f37f893cd6517c18844e7c6b61fd55c863258cbcd8b72e91de21f2b14e538ab5c92328a61342142a76d066f14cfcb2ecaf1da653afa6b3290d3725ccfe205cbb089ac13826e592f511091de1797df61a2b1bf6e941a1f745f7310f6c0122b35f673576bec535611d7d1287cc34d6705ede222940eef4ec55d3686aab5205d5a5c8c7bf60e05bec7682d47cfcef1e14d10857a4a844113593c99ffde110d5cf05d4bd36fe85303c32a3a19609f4af342f9d7179ead5772bcf5660ee225584af55e3bb2f913e1ef9562166ad07c2dbe6ac8b7115f4c241c0b401bb4a3d754b44ae2064d307501be6a26717a5c22d6cafcc71d015f1626413c2f83523dde3fa189a67412e64136f51a38bb7b9fb9e83ab1e2958b18cc8a72a7eafce847f35ba16468b61b52423eb5431a05f995b48a5bf90a65e1e05d7d6969fb777542a9635c7ee9f21c227bc8701b368f523e34dd1f98723c534c280f728108145f9e96fd108c1f465581fa46c9d0b44a22938170b583fe581d0062db3c835fc3e10ecae64614cf1da5e003748aa7a397aa9626c976e9eb96ec9be554616d945b4be689ab3df943f84919efa9f17a1350314e982c5b2744e986598be3215aef60240ef0e2d438fd0c4566889599ac95dc7825a203f9ee378aaee5e35172634b506a7aa81e3c7693ff74dfe2fc641a81b7dfc959e4d5ea5252818c0bef06dfd43253b165cafd0fa0735c3a19e80ddb4c080da5bfb67601edbd21f39e777036aa616e72d00b3acfca9eb015bad403f8fe615c00c82db13b4f23c759324a72350101bdbe2e57eef0bdedf5a386da970f3f696c09531cc35b3800fd7cb57f41dde6581da24b97c4e7702ae10db86503d5e436ccd00776b37c765f5a2119bd1d7d56f8c19438f171680f778847ab49c311820425c0fd68ad039bd4b3d3a3e80d2e23b2f0f6d0604c253e9b9ebbbebdcc16bebf2c1b632d08d48756a5c0b8816e68e1a29b8bbcebe04c806349ffe8cae3b1439a26507ab0a055f9f636052440720924329c36a5d20d58900937d0d95ea83a156196e0e3b6fddcbe4e2ca3a72f547059b821a216dd5a4c9a4cbd8943470d4ba35c7f5a55b86c270f0e19d0ff366dd73b3ae74e705aaa49a2724e648693ca87e0b21fd14fdcb029987f878884cd656266f68d0d65413f6403b322f649b1908a038966a94c810b92deaa2e234f49558a8bf27d6cfd6ff1b4b835fc224a236bcc6f4c63d760546152f6df40471c067265923bfd46a4e81476409427290ea96b11828511879d83c0616e9d39b7aaa844a6b2d2d6d40668dd5e11ef5bb20d1608a7174513d8fdffb51a2075e8d3e7bb72f05276f93123230b4cc1ece2a2938d302e58b628fd8d14cdf8df3ad60777607232a56f6c86050fb48122f1dc7273411da43d6c1c8da75a9b36207cb46bfa2f6700c74d42c8b0a1669623da3932a015104472d7ed2988813ffa28f2bd89c60d72426445f3cbbcf0bcb5fe64e52d72c5901ca1d481990eee3b09365c45d59ffb3c7d75fad2052a99932da5eee653cf25fcbb58f58b1d0d1dcf6cdbda9a7fdc697291fcfb07d24d455e9a0230a79bb7174d59d34ea039370e2ec8d4cb2b7e945472aba291c20561b352caf374a80fcade995df90ccbcf88188049a0dfa20eadfa7269b30407ed93f29506c5d945360adcc3455f8d0b6b271affd72c2efbae637672894e5afe608371db4d5cf5a7fc7d1d8209b0c1275057126d0a1b5a2a70a7a772b9a3d6268425d9e6a4efd1a66d24de0c05808e3b7eda1ed4e65464c80c491d72cee14db79d43dc63466dfc5bd80701b81930009d238d92bb33ad96e57e03cd72a4510ea0013e3b6b876a1ffa2b4754970ceb8b1bea70f67cb229118248340f4b3c334fe9cc3de4288f3349dc9c75038b1487ff0e9388a1cb8295e0b67bfb777232a92324b68f2de4c5f6beae70a6999e152bfe2dbf5a9fe977dbaffa2aea691a737cde3b899ebeee1bbe34b279def2c7d6821f40c33dcf7d3cf6bd8f1c933072562a18ce3860fececa7dcfb5a4816bd9994e3d7c2945f4dc05fc1c6c1ccce54e7ec44809be8de8813ba06a004ac695ae464c243f2872a87e4afc8b5c16b31d72ca27640cde93efc31883743cee120dd70016f039ffaf28375cbffcd042a760320db2ad3472d87c28a1cc2a3b7023288e50d323cf81faf271803d0530c69d4d72a8c6511dae8475df478d1d15c6d5b4ce450dd3cb829d0ed5a83023f9b909bf72b4bdc14da0440a1e3c8c559524e6d86a681dfd918a5e47ac16983e500e044172389c97c5942d864b8edff993e71191e555bd6c70791460f0be21fea1163edcba020000722c9c9d7bebc4b1665647aaaf6b028742ba8fabfcd5636643ec69849dac30543056fa33b6abe96e93b9f5a9553bd894976847fcccc2b53a0e30aa59d03c849b72f2e967dc2e731c4e895a784d4ea3b755cd75be922b32d29301c875bac743a872b7665ec7fcc3b048167424398c10dfb262504fd9eaf90e8ca70c762eaf4a657282557200dd1bd79dd8dac1c539dfab8d84553cd48e9ccd26dd312f33c991fe3a74fed85f20ebb66e8e14a42cdc893828e38ec8bec9db9c8dd4f0cbe314137472660ec62a33abcfdc1d7bf6c4f6176c26ef44f7c97eaa2e35d5b5a34da90cb0727461e702482aca01cfd8776f868df212c3681511497ab2724fdf86afd62c6e0b29693cfb0727d94e89a34149d90f19cc7619e3eed8e8bac3d9ea02d4159bd762b72ab1302061e69acafb7795562e98f2616071adda5af1b5d3041ea9305041726569bc01ef3e483b99ef7555eeb4e4e0cad387f8ab2b07f398e5e27cf69b185ab8a34eb338cccaac2e9f2f32373339b714e262ad2fb806f4016e8a7c2be48272a1f61d78c9e71ef813800c4ac73f291c764a43c4ae6be106ca642b67bd2c4229048c197c01ee940a9e11baaa715c1f2ec1e22ee012fcc49f856b489b5a321d6f2aa2ebe2d67b20d249304e29eb694c87f3e4ce1f43e70a7a92dc3269a4dbbb01a8b4e81f5bc1bc52f2429967206163bbcbbc16145be52b1d2aed2b605d769b462944f70900c904223aede3a792b02a6010dc875aa77a05ba2229b6c7bede37726c10ba99df397b1c36be98882e2b55b6cbba6b4e810aafb992038d90f91dc819eb2c2c4d96c2d41c802eff48d167953b5cdb82b01d64443931df6115e0e9397251a6d00ea0efe400d52e2bce26e67e872888955ede8df906e317859d232e9f72e3fcf34fb0403c8a1789acd2f0f4b63eee08676ae25802edaae1a675ecddfb1ec0764401c5577f84dcd11e0b44d1ccd87cf157ac5ffed883693f5865bef8a73198aa6ba3c5fc7977a8dc1f0e55c4c2d5080074669aade0651dfc0f8b439ee572cdbf4525baa18466b4da4d40907a7f9b2836647b6d95e40cbb83ff15a533d57261e4932ec95d85820bf5f53c88c60e0c66e9f6e544a99529dac0371a344ddd0c6029d05c72c951d1303b84c7b9d9aec5ebd01dc210b0d1157b13ede7904f4672af1e21cf8d616d0181898e374d6f77ddaf42974e15438181198a31674c7c9372225db58e08762b9a8e8a9adcf7ca631b5ca63cc75127d685e3ec7e2ccee3eb723571a57d0e008b1d8399ff59ab6a51a3df63c30ea51773545ec4c85b09a6ff72db8f91ca0d10b024e1b18669f817b326d1d231a1117bde7b6433da9fefedcd47184c8da592a1a2cdb97de49677e61c67527e2481f76eb9f7efe752986ebca534f821df94af56b0a40cbf8183d0ba79cfe609fca96ee23111e855915f371ae872f6d99f1f77c4d0abc683a1a1cb550cb6ceda16ed2653a5b67e28bbfd67fe0e72c583e3d370c1ba41937add6d36d102d1c74869d721f9724fed5ce57b78965b72a69e53b480c80e21fc64fcc0f90ee367ad64ce4d4822e2414d32babfd205eb3c38c5f06736baca6eb8b30f9996657b273d4e3296c968f3db80e395d05930b31c99422471083ed752b62cd5af9b96262e7184d85bba9077981002fab6c324214da4b96a545f19d332e537b5b481b6a6f15480be472e27573e59c57994419fc754e0131b0f160aad8bf18a8f9777cffb8f5ee33c6c4c75b43857c4b8f93bc54272e1601220a345da5daa75afc37ee921f5d50816f48f38ff557347e0c9bfb8eb2505a2539507b3bb2ace5ef927884e9c1feca6cc93126c397b568e759609467f71abfdf9946db5b7d00a2de8a5990cd2ba556b71fece355e590a16d283dbe7b25cda6e2a2508321a02eb89c40f6e4634fe7c054595b57fa5b33eb18e7e445f1c729e53ae6db215e5dfd450ae156e55195fefac2617376d8f573dc5691f38406c40b0ae74f542de3a5d931829353e39d234756939032c2b9d9e3711b2c737294f447cfecb39c140d666acba7580cd4c15935c289a2f002cf91f566fb3c3312e777263f72a457993756ce21e90b105980e18bca6266dc6edcde9eef0851cafbc80720127096591f549062e05552ec40dadddd619db0c92da6c3289918d47ab444162d795774d4683cc40aca0cb1677625ff4508c300cf989d23b013bb33b1f435e5444cca077ad5417da77f44fd6507e0f14ac13f6a8999a1f282cb5984b67152445fe5a49448d4aba304c58b6e1b6aab46ffd58c4e7978e3a903806fb3d51699723083ff51e6845157a457281e8a31cba17558a8ca112163587142c6b9e43644d72649c4bea78f9774c6bf4c8f33d9154fb8b464e019b1f7a5d0403a7d0c3d37672d714fbb585dcda71e272435436e5f0ec5b64cf30183c55db57d8f66074785b72252fbb704026a2623c41c9b74ce9b1cd7c8d18dd63e8941c5a344e557a3c0c1cb67c6a087fa9de6a5bc71f060e4f19406d80ae6729cb646d8b534f455906fc03bd76fa028a3f5d08b7e88f3a1275998f02d9fe085bd9aea5839cdd28b3767872da258fcf8a66be8682806e04714ef5ec2389bf28829577ce7968cab78631cb72a510a9d60e3034b91d767aa357bec0f65e1b1f2600ffb3348c2b1fb76efd3072667caaa69c255e358f6ca29ae8c5a9181a891c02da6deaa5290f0260cdf6e772456803752d84a164e6474bcd7b8448aa0683d9398e63b511ed1a46165e7d54725ec440e659ccde70742c458a23e9f271e3157652cb8cfb730599144bd1333772ecdff8d35a06aecebee54cad1f07aaef5f427e612ad99e7d84ded32c5aacec728f5083dbb7138fc12f938476e1e24452c3e9c1341f6daba27f909fcdb0315572e9eae92ec259292dee6031f4d1a6758447c0b60114bada5089f42ef2ce2e213c2373a30ed16cefb3ab9b8a4488184cae146cedb1a65def1a0ca11c4d8c4cc97279331ef5b7739a4762b7d3a90e5ead1f9e080aaa4e8b5187537ef3421b957744fb8b90449720e55d022e0efcb309defb3cd2db25c81c12de325158b787f93931d91ebb284b236f04aa41d1d3db4fa7ad2a45d8086b4b81f4ebc31f4826336b726de588dd8ca929a48173edf3c05746272e0ded42bc0a2250c8bf474f779cec4fc3dd65903ae7aa8d07158241ec8106bb75fc7ffdc8c432ce7122ddd7840a843abfb31e230db2582711dd97a93de00381c29135c7d722c0b4bb09df11af7bd33d985d82b78252c588555d59e43f3450439ca16773bbabf8dc8b6a2e4b91b224722a0c2487076081b1e650813180b2d44c6d062c0726957e6e806d706bec13c972a49deb9c5e06262597c5a9294ccd1bbb82d4c6b098355c8091e9b3e5685a73720f58017600159f91e1819a01de4649fd871f726ee9a84cd62dd0f1c4552f851848bc43be00384f56a8c98c3ab0fc721ab70ab46f313a6a13a2f9f5274b93a472799e637631a25b51ea3b5b2056e6d3239458133d5ed5588d18a3c216f282f672dcc747784f7bd0b5d5feb2d03f04553abe2dc5570ace2a5062655808fb3d6a72fa68587892ea42d4e364b4a4712aeeb60d3d451edbde2509bfa181a9c18a7c7297ac4f8d1bc607294351c2a5e9c16bdf70fa90b95e0c42d33eba86ead9d6462ed801da1b43825a1d0d1c31ddc3005026ea1ab8a32444e43bf2d7996b4a08dc7269a6095581d7f74dee2953f36b6f9bca45853a02f719020d971d6c435268ef724bae3a3daf26134f08deab07f3ad11c4d46bd2b03ecad3c72e94a3d98bc944700c0852a4ed6a790656f8491c70ea6b9d361a21c90c732129c1a17382d8efee7288dfeff604c36e8bdf61cf4d2102ae1715a942871e1ab50e49bdf1fd2e643e2daa4c453e58ad1735c50add477bcf8810d6e2ca274864236c90128acb99a27072e93b70257e8e39a42c70e73a433b0a442c1da3cf6e35232449b187d65f277f5567442ec0089c5d6dcaf3fbfe7f6d50c2d18b7bf40f67ac320adbae30683eae721f6145a054e2062f4f9781b7d925a4f87ce224cbdb168193f4e84c611322157249c3a9b6c95652c5d40d03658aeb33c16b3f789a96c5c51a3bec6c06585f03720b5cb3e077e84031c4623da951b32108ac1b29de810edfd74d706e82ff7e8930d5ee96583364e1e8767474c9fb56ea4919b2638df0a1cfda1647f0feb4e22f1011a942806f9ae0a1aba456a29223899ecf4144a1ac18aa6c01ac473b2ac11f728edecee6333ebacfb398235ece251b4e7dc33bf6a808284ecae6352f02f3af687cf00ef843d527eb6aecb45e40acf12e5595d03d3362fe5490d477dabcf901728989c3f8329367f4c0f4b982866fd0be9c9f1f846d12892daa1db9e9e3e80b3cc8fda620af6e01909a3ebefea086ee761b66407988ddde308e35cfae677f3055e88701bd5efa32c3f16e8e4fcf7e1aeb7085380d89a786f68ee2e46cc268ab72b9445967159ff6165e2c903b304360058118976d2c3d394147483c010b1d7d725fc523e9267b02b0b62a9f2b90213f5e19997eda33d05c59060bc6f8565a8c725d519db512d3459cc986c7ba902f5fdce266b22d61cb77224d5942b551a185720b85690ec3cd721e620b389fbc32c1df39869293695b145f565575ae812c2507858a71a0608c588d9bfc5eba6e73e780dbbfc8f029b71f1c580e3778b4ddbf72012442b7638497419debfdbf5a21be15c616bca18cb85ff39d58432c0bf8b972042c151710afa37bcb4ad8ad924b4d10b19a29929c416f127e740b6c80c3d57283b9661db230705ab46cf3dc5bfacbe0e311b3a3e3f02a06a4bca1d630853272cadc21a5ab66f9cc7982b3b300ec1e095d88a15f6eef792152a79386b65ae272460db2edcd6757b6413d9cd7d8d429ea152a80137016019307b50400ab7d3872d45d40b64e3dcd4db25202644a18ffc71328a19ae40e40fb507a3423a00e7f72257d25dd7d17e13ccf71164554b88e5e2fcd569ca10b70eed8d654117ff8a2720feee126eacf026bbb58cd7bee44ee3cc923389e378bf381e7c2ae68454b5808efd759a746affb3afa4167da5d0f17c9e926b4ddfa0e722971c416d4c3a04a724b54662b1375239f5d77187ea9da907b93027618a51df7f66d813401f853597211797c17976fefee6ebb9842d9bfb5c11324de359cb3c40ce4f84963bd227272a1408878d76e79503d9e0e7dbd26660c253a9b95ca0ad484582d3d513bc70c063eb54d6d157126e7358937a8b148762c488c450bbf92150746c2715d736d8c72d1d6e19d3e15e927b7bd3188c0f712b7acb2b7ceb118e31460b8a72a45b88f148e99d0ddb9d5006cfb928bb79c806aef9500d84066ae0e4d40698fbfb2f07a5c2023b28373a4b9bf7279302c63fe16f6a26e64e128edf005175326675ef2a8488b39f02db5d3bdf7d354e7783b30fff361cdcb85c1ab68d55f5c96795bdd132bef37914cbe1a7bc3a55354a6cf3ed458891898505d6f1f6eedd5adad6b01625af0a947f5e1586090b239c50785e1c5cccdf5804881bf17d1c8524d747d4a5c72856468922f84c14f10b9fff58c8893d93da0617609e802cbfb1969ba8a83a4313f9ddddca634b240ac14430308c98c5e1e437651bfd0e0b658388f675f83772ab10127c069e432e6ac9121dfcdcf75579a6466d58eb09f0e070f3903be433c72b745b476aa6554c1c656c7a4620af59be0bc3081512e0f40c4e9463ffa83d92323ce816436a6c8c13989dd849c65ac4fce7fd98918c4ac4acfab156efa449972c385e53688a4117f4d17fc85b9e5207049adbcdc01bbb02cf8314886600f735323fe8bd1941a7bba5a8beca69ae06b6fa28bd072f2e4ca6615701790d42ce572d6b645ce87d18d229ea04c6bbe3b760fd5883377c8a49b43bf02bcc9c4ea7f726416afc4fa84e075d231ee4535c17075245481bacd0736d0ed8fc5769961c81d802feaa340ad9b91e16dca97e6b60b7adee2dcb92a6cd5690f15ccf874cadb122468cb77ebb5d6c976364fcfcb2a7d60eebc4561cfef82c21dec68f29db640724a9f6da3e8c277c62fcd5051201fcb5323cbb12bd1c05e62d29cb6676aeb3033ce686733b5bed231a9e9c3c9f155a1f77f41cc77c84399a14c8a1cd1142efd725d4bb7a4dd268028a39b47fa9f0b43beaae0a9de5a794041e8bfc9d5f931457268ebde21067e1954a14acfa3bac326c7f84677b4970c587d33822a08a1c9c97232d76264ecc8236fbb91376ca935cfb6f9e87e220092389416bf6ab7956a7c72f94efc8e0148d6a3fe2e67e628adbd783bf975faf7f50f0d466dfe82e703fe0eeb6c8f16d3bfe16be07acff7bbcb12ed53ebd2d6d36816b3de367fe12d07321bd8bfea9bd68e1abc984275813d2be76035749d13fcf7abbd2c169c0187a40072caaba23ffc6951d6f1725c3fa4174c941ed21a14caf261a52612c57ecfbe325a0031839f8f296624c61b2ea8e74d8721bab6c0e54ea85b264769fc6230c4763dae63df4e2315e16f3df789b491b32bdaea31a05dbf3a5bc52e24f72385892472e1e1bdbb731a16ba03a655904764e214c697d2d68a358818559e602526a0d3124a8beb6eb3df8c777706f122cb1cdfea0f8c68a637ef56f40dca538ff8b070725d748ad83b1190a936532427ecb6f59f2d0208f98202ba5f673f0f732b55b672a3b0e312a14c20153952d3ba627daa5c9806e98a44838fa2a9ce79aa222db0729cbfb835567008d838b69c9c03e83e1b8b91168d3018a24b7d696bd8a1cda767c8114f9c113f0095a3e6e8aa4d7547d409176e42222618585a716690d00f846dfbbd96af5aaffaa64f9ec7d3230d67b978609c4ab3168a7fb1795249a8033072fdc70d4613bbc79d48d4c55416787a15b35f32738f356836dd2c9668262c7a29c98e4460625bd11dc98db7575007c68c0cb45fbfd339a49972bd3dd325547f247624ac6db79e69530561aae213ae688fc66ee94b0f4df4ebffd15cfd5d024072ce1e87cff3630b4395bc43f4be6bfe0ddc7e51ef0f8063cbf53e6f421338d23fe1f3cb2d4456677b9edc065048ffcbe59cb1b0f34d2ee57c559043851460dd72bfc6d8aa4d2e47e7af642d2affd0caf3deab0fa9335ece8a4daf85035c6fb3672e3c9a64e0f89a63e5a482cd00c1722dde32c44cc6d862a287c0bae3e9e377722ae211810e06c8e9eef6d3a730c1c83ada17bf21e7852300438866ed49251c28d87fc920a8ce0999b6ef5c8fc4ff0dbe6936a7d0ed8b104382bf596f7e82475b17b807722dad1338bb7421ae8af626a399d6d8a9aa610e063b7a190da7ca9e72565863796e53e9acc4985deb85a3cffe9d7cf1e2c9a2ef63f289b0f9ee01c666f3de6fddbc7c1911fd95fc94670e36088d61d2ae66b7addf1c8d4e9216ca0d72c58c466aaaa2e9feb0bbe32792c56c57349e756118f7c6195b77365aaf546f729a4dec23956ac84bfe4c5c89d3de1a0996da2b61d10e52e35044d82cc90457725158c5b69a071efa3408723dc0bd2d9b6a8090f94f1f9d5ff25d744630ae1d725225ac0a51fc2bd701347b52ecd2977ffc37c98d95c5cb8469a54b57d189b072e1926c034fd5c7b576dd0d86b519491173838891bdf51e3a33e575a91b25c963b5e1bee3955895517c00727b12500d5d514eb044df942c8d61068ac5587e7972396de431740f84874524082285c3159d3dd03198fafffad01e85e0a09ca3a51c8706b2d17bb9fcd57219fbc87eed5e717d9164aea74a213cc43de4018e07a001ce9904dfe04d2e44a4525539d7bd29269b143a859131e88d4a8d6a28c4013a60f52757991e4dbd58d88851336d83ab1aa1197a66fc018c20a1bd17f194e0281ad0369b1888f83dde9affa921430d1daec74895a1b9fe898fdd73a9f40fd7b97281163cf0e09de7df953cbc485406125373080e6d36c36ec8c3a33406e3f147725a9ca101fcc93c95afb93d8f6293a6043866482154aa4672ee73f9749b673572d4ca2c538de4faa642cc48720392a43a2d9ed3b643a912f8b87bdf416a70801607adde14ec72b705457475b55cfe96b4c1864d913fc3de9d7767213c201f531899b067bcd105ca634e30e31014d7d3852dbffdd6e6221eb456e904c24227027224e3a5899a04b44d64f6b666195bf7777798f94b7b5ea66b19c1523dc76c9f7222fe6e48e9cf42336d0f53b3ec69afb8b2ad2bbe11d70620c5e06e387e794f72bf049e981d327bb3997d2c14eef934756fddfdcc19426a917c01b08c7d1c057234ed9faee5f2a78262a59f933258d75abc736b533d42d5f8b5dbfb81f8a49f4fbc8489e8dcb7b43b14ab00ce0f2c222635c3f1d8d2965bfcfff3724510cf6c030734bde0d046c6eace88eabd0d0e3436e52a931cd675e9e802e72ec8541614721340d25ee717e5a677b83e6033e49009688b16c1234875b2027c41681fb78b7277761c231e9c2d7288c8381fb68ba307417c1e7f2b5e03f3bdf37a0efa507d72fe2f782ddf3992310df192fa418a8b00a4078bdc0bed11f5c16ea71d0ab0fd30422ad11558fa7f44ca57938de16fc8a073a4e14ab35f7bcd69d1df9109389d72be44fc8fca009694da29abdb9c605ab104967c03d24c8336b2bbf727d8e7c3722889513749d9722566e18313252dc6c18d094a41e7e9bfa3224753935134ea72b5e4a2b232ae9647e91fe44211326feab2ebf574835a464aaa9de80b25161d23fc0fd51b81767a1671d308bbe90c15c96f75c9a62572dcb71f2eca177478bb48ae20e98d4e3b4fd2e2d8fbbb480ad97f2824cce3d87fc85ca93ef2689b5b4d24b0b20c5051c42c0b7c0909a28a25f7bbd6937b74f21b39226801d96d61c1fe6ca487c4f72e3959fa302c0e960119531a76a0d931ad6584fea334f001c658462d9f13c93dc7d7f8de21f053fb149140a5296812939b6b55406b38825b20a35172b1fd185e9d8f8dd0d55abd4a378b6d57129efa3a95663876d51a9ba795657909744206b085df1624ec97b06e7c1b4a4c8caebaa85a36871d245e477c186afc6938834b393a95a944e4f060434dffd22f8a169e9a03caa09a4491412ff7fbb865d8b3fd32dea537955d9273da791aec73701d32605d68a9fc0f8b0ab8fbaba0724beae16fea78218b6ea6546dafa61cded121c21e578e3921e774ef2da045ff72b37b4427057ca4fd2987920439007403dd2978c6115f7c275b98345d1f87e30c096b2c18bcb5c6069cc2be2378443bc8310f1be8dd8a19fd155547b3f1fafd3967d055e6c2ff4eb0996e691b4c1473e1c032588611b22cbf67bc83928b23cd7271ceddded0bdcf64e5912cbfe5cb9f31902ef099e22d40aa5597b9c4f0387972f282c81a23b09d5ec46ac0afb4185141367c438a1ab24035bd98dc20332cd1725171d1632bb643bb3a707500c4996171b7a9bff54aa94cdc50ae5e2814e220433691ae1026196c9c5a9037bb3ef57a3efc5384d3ed474735f623f80ca3fb30131d893446700a37f2f3ece2a5ecefb45203961d9d0633e46220820e7078d65272188ff6884d24d151153a3c2fc1842c775d065689784b4fb44201fedca05b9326233def8e87208e32ffa3f3e5baaf5030f93661fc99a9b8a737effb72baa8e97232051e49a1f8100007314284ae8998d1e678bf5d536589c253aa5e8de97bf14b3872d30dc9137ae5d97e9fa2b1ec28afb0f5927298b44d0a28304d76773dab63085fd1de67f32cbd7347ea8e8f8722168c17068887ec080ee8e6837d3688fb72c9174bdebd4a6a7a16336b80f7f041692733ec4b2d919d04991c3ca88b61890ad2842875c51c1717116700c0a9035d83bdada3ce0297a2272c550534756d92126a996eff6acb222c5a6b8d56d37ff2d9a417edd75549b824c35dd44ced001f47f517af75f1df54b1451c5627d97037021fa04091d4c9ed4abda52f941d6e007241fbd70950db229c46236ea7673f9a9e8c656f301968c5cf188111067e15f172c817a5972a88c2c0e3af6c119e2f92df48c12e172c50143f5595b7f67a0a0e4aed2746d735ef453204193ff415c11aa012c693efe6b993ab54a300d14fbd857297eca4a55d2c8fabaaf0ae8fc27986a86d332218251ee81187622012b5793d7219264f7bf28c288d248f99910fb2924a2e1238f94a51f8e255cec568263e4439286b976fd863997803fe754a01d55047c2674d9e4e6507028c102ce55d3dfa2d02263b4c60c17574a251d9e677b5dce38d1c4e6747ace127f07b4a27fd0cf81ca667c131af6abd049ad9ab6a6286dca8e9eb66050f6b21a907ecbecfd73f140c545a767e9660585d80618bfd66f66a69103c6bd715ad6faf88e8995d147cdc4a251a4e6f42b83a8df26b872f23adaa6e49d653a2445dd1bef2dd5532f0c4464edf765a35044f595c5f1236f1786980dda66d16efc504c1d4ea60d0149b2b251255446815d932e4a8948db4ddecdaf9571a93df164525bdd0e06d26e415801d7247cf12a0bf9fcdb3211d4b656963572076ab5b0edfb56cad4de4e4c816e5205efaf8de8eee2d41d88dac3ccc79c7fbd0044ffa861c38d8ba3c6e5f126fc32e5a8b0bdf0f3e738c4f4b7df5c71e258ad0962cb5ce6176e7001c5e8cea4a38e743be7b04bf665974ce9cb7e8befa785acd05b965a73789fe2142667891844767727d262e7d4386f978eaf8373355db88eb6370ad1d7ed0d6a34041770fd4792b64773711bb8e9f0afd4a2cd6df859d89378fcacb653c5e6ab7c967e108ede9d92e5777711f3c9e572e9a1711a5cb71f096e671edd5ae3a31fd5c0b34c6045e257223c66345f743e263e1520b7909e2ac4979c6e5db98cd9fd00700b578e2e1e4534b7a0f5ac510d0ecfbdcbeca4a2b9b9f34c49a867070747a843cd164bc43e172f128974268cef5570bb0bf71528545ec050036a52bd5d987dc6cad33e241de722148a65fbdc91d0b78fdc2443c1b1d0d5d7cfd923e59ed6aa6ce4aa7d1266a72db7842e6bf9ac34450f86c055698664b7beca8e87549bc2f5361713eb7c4b42943923e07a382815e6f45a9e1fbcafe2be2ca00df7ff3aef2db35e4bb5aaede72a4aebd0420e41570934fcf7fb974412671f30cc4a3649220ab2f7f7edc6f852d77a048028ec913b58ad2a5a74ed5790265bc1202f87c403f9059b3699d019b70ac85a009ce76c280bf2472dbed0ae618f527d5dde91f580cb4958d4bcc76e62ce5ca2bd4423ac306c8b3697ba014ab3546b83e17eac93b825b5398b063d50c72e4a04968e511ee245d7ad00c7d4274573e7bcd097b18c14bcbe50ead2ce54672ad833281cdd4a3d6b7f25cfbec7b71ba6c808a1c21c991b93a1b229d418f6e7294068a25d33f16f540c9049fca06d5cf32f3f07ce9b4926d0745b3a852628e2ef70d20e9b260ef0e546991091146e2ee55b114f7c3cd8b969519c0f128082870f3a891cbbd630cd82384c4386d9ac54ba8ad87cc07cc72709d0399d845770d726e2ce410ce1b61871b2ccab78cc3e7545a61ab4901e2bb92ceadbd04a54bee7250cce6137c373dfda56f638c396e950f2c4c7a2a1efc78fbcdeb2912f972317230b439cbae91b00ae6d95e3c94a0443dc3f480623e8873b362f81a2181990372e4083571546d4ed8654c09916197a15a0ffb73f799cd2a1385ad11d09efbdb727871a9310b61369dbf868cf0d927c761862f711f9a679f181adb1b954e4ffa479fd1d0839189b012dbd4263e9394cc17b715832db02b24197d0935225847d6722a8988a6a45b7634499ad681b1524d3b3bc0f4ca3760c7d2b70ee742b8a06972a02321fb14345a2a1a29a35358f51573449a9c2e9896c1e53d05c100d7bf0d3e4e4f827ab6be4fb5b8e1af2dba5379a73bcf2e76efe6b5394957d0cb1b5282721df9a2ea91c31f8d8b0a44a628c3e2b6b5c87a9cd73df2b3badcd6d23abe4019ba464593ace83608b3d953baed8289713b7107d1ae0d3e89231e1bc1849b48725bdbc5cd325dd2dd0acc9c81bc9e95828d7fd498f5743d807a3d061e345fd872b6c94f67b97bd1c4ffa4e032cdd88e8f0aa06d5d828ce11c91022979fff7f372e5aebe1925901248bae7ab3665c71540163541e9809311813c9a9481007761723e4587477f1a5b4b1147513ac873b224dac6749df517239b9b74167aa5a14b4bca3094b2c2736bd582f846a8becd91f2f7cdf476e876398d8f9b5aa99de0824c49878f12072a3a66bc606af43aff05291639d6cdf46920643f11ada60984e8722c90b8b54864aa649f3bf4454977314f61fc2ccc8facc028d041e5bb66316c7262144a7d7dd1c7f1513d82ad38872067f3eed73de33f41841dab199c557ffe726ff3407df5bb81ff9e6991a068f84fb6d98d9f83c34557fb89bf86187661863ea8b5400d0136f1d1f579794d5f26804ee6dc438414c5d6503173f0ad7215d8584296d10d9d1f3ad82fe24d3dfce248c864866575ed08289e3da97b6a9e44c872c021f07a9b76df8dc66dcb10d747a0ac9c1fd5f60c89340bcbc61e3f7b0fb972b4424cf9954bb0c1cc69ba1b7a852a4d3b8552d69253748a17fd94a72210c92b88e0ccab4764653c30c1f85781b8cb32cdc3c4e92edcfe172e9d209a913b1d7207d8a9a1770769c74a6da27dac1cc0a9200afecb4b888decc6caf38b87f5691b6efc47d88246b6a6590222d9ab828584dbe5bd0c5312fab2b4c41ea3a387c63bb6e3b3adb16bf067ea40d98ed479105e2f69f6d33aff756d7a76e553dd6bd07265c7d619fb6840c04f6c71b6da4ea9976feb17d387b5578fcce1ea18b734de66314c5cc3e36134fa13fcaa6f80bdd7bc568cb41574d7edc995a6b0f4c8eff2729d98c97c970f1a3170906a1d11210dd0fbc0919adb5151fbe2c09f0a258181729c06ed5a6ac6d3af854657cfa5ec1ca0675a8ffa56b35d160cd7e0540236020203e557a18d18eff1196a46a4ba543632532f8e238b15cbf2a9b863a54cb0bd72fd2d19a51e0ee678424ea840abfe4c76cebb4a1b4907a653657a17486c29367294e007b1157e73bcb9c5c734931eaeedbe04ce51768d862f5696f5298ea4f61645fe53edc34aa9066947795b2b93be40b644aa1f31814acb4d32a22133812972ccde352a45dbcff14e616bc03c75f4266015b361a2763c416ff4a4446454b87233ccbf74e51665e2ef958f255c1308fd27a8c3e04989ece98eccab5d31eb3618fab559ccd108bee4bf9cefd1c819ead018e6ee3dfaaf0cd001f28aba10296a610db98adfe2139a7c8f9ccc18dd1abe22ef0b0fbc310306a5264aa78754d2c5729ffead193ed3a89fabaf6b2d3771ff188394118c28541a8045d8efcd9c9e0d553f387a9439fb4dea596781f3adf733c96881bc51660f9e64678b61e6e281c0729475141770e132194c1f70972025fa402d7cf30e91f69c05e2222380aa18fa61a2dfa5334bcfb2d65f1748f2c527bf319bc7ef3769e738118fc3ab66038930725d671e619cb756db42dade89da56a59de598bc9ed084fb47cf068b538e4a651953b6c4e574fa42f651775f7011c8c0e350be4ac9ac9b5f55927c9a4b81101372dbbd15639ef097ffdfe00a991ea0e06db9e3749017fac7222349be039791247253042815c59c892d7ff9e13e1f104db9d0359137f274e986d192d9ae8d9ac36ec333470afba3c4ea5f1508ca2ac5076be3992e9afebe87befcebe8c7b4e02e72c2b1fb51fb1ed1569a7ea5b8511e63d61fe48d61ad3a2a0944b760c92100f372c02bc94038ba0f4d338add1f19722b49595c67c25deb517d3586dc33a4a1d4723293c227bef17dbd1e2807d2486c5df7261935cfa2098265ee47d3d66b3a23722628d71f0f07dfd151b34103b477e8c0c75e038e7f0b637ea8b753120471c03b077c17cbdb72220f02484489b247e921adb5ff35b3661651c2566c1af679ba728cd61487925912803ba444aa6634f96e6edb155915652af0214c91d71f5b45728ef7c5650901833bc6ba868a37d0923ad05749f7381980eccf26faa6b80d237257c66362fb46ddc939e95c7084f68631bd050343d361ef0d2f93d1dea4c94f0f7c62ba576f771acb6e9994ac17af15e2c3272cf50a3a011ca017f48248d14672a49a404e9b8c5a1fba53f368f256d4ab51d700bc3ca3d508f635310a982be6723a3a812e63807a37fa0f0fcfc73f69bf19976d5a6e4c6669a692fb79ad838e72ce515b903aa747827a023e88f7e720916d6903cdfdee64ffebe3ca220fa9c672b19eeebbd5f4ed4d5a33ef3743e332b2b740d1c10556335566f933ff410595722ee5d2f9218d172fdf96c9b3a1431accadab7e3b1fc8ce792dfcbebf1931e372b4ab3ba44948eee01b7bc9bef1e7c3dda7e1dee353eef7e026d9832a4a1bef72904838dc3d1dd7d9e0a545dd31e8e7be9609e63f3e61fa199a09478d3db62e72874ec2e6b81e199c350e7cfff8531ee07ac6b0d78ab8d0d64cdddcef7456c672dfce88db35b79713033051f5872c817779f979c0b8eaa5ef0c6177d68a031b1dd22ec645c3e43a1bdb4b113d5a4de85a0163e5be527e2781922f5cc22e7f57723f83da6a764aa3d17253f2667505d17c79b86a06ea996c20d53572acd89aae726382d041971a8ef75546c456b8286b487c4c0f463981230b84c6190ebf201630f50e71cdf159e4afd9bc3f79a5273c52b23b81cb9c965f0b9c0573ac65b5ea722bc0b17c1cc933ab1b938d7c87cea577c2f52a811cf94e62298adc8ba384f047f73fa11dd1f1ac2fc434667c0a1c8e71de7bfcbd24e13c587a7f2a6c3c1ad872edbc6f50e20822b3f35f50fad3b560b28bae9950fcc963d2d0f7f798dc59c7671bbb1b00d5d50a9046b0f1afd2841dd6299a75cb36c48e5c402b31d9c1621672e6e3818644f4ff51bc82ce419e967f029264e10f257998ad0339d7c7ff194d58d829698ba1b5f08b743ea8635ed59785dd2b267fe28d38ee638289f2d4d0b772c013898f9e48e0933163654671f20750dfb92cb76cefec639c798e22610d2a35fa49f40cfda9448a6f2d92651b58c3eaf35096a3cc15e8964145f156749e307243381abb8594558a6c753505aef586f70ad06772c429cc9e417ece3746f7b7720b3cd08667d59b0134c07f569a991a36ad3fc7a66ba08a63a562773f810a4b72a3c4442fef5bbd938f243ed0d82f47cac3789ef1050f50b602c8a94a3d06e45596a907467387fc99ddb71ab0625e68aa26a07512654bf67e1924a5102a3c4a72786eca5cf95296bb2bbbeb734f0880554d0d49bb5cb3abc17208a2e76426de72b8a846a3d6bad700e0e5fe968a1ec29ccaad5bc3eb2c50d6a431831530934537fcdfd0328065ffa21cb6b3406413cb0ccbab3bf99a3d7d8702dc922975c1c8723a09d9332531480a3246d7f0853dc411a5e209e0fea764c6aea181017a9f580b01a1ee9b0eb081aa15a2f204b2fb8ec9cdaed6a0c2a5a58da934dc0826447d726fc6abc3f63d252cf516d284b2fb00ba434644e884ab68708ed9e347ec0275308e18246959eefe2c3538a60420262def397143ac1f979c2898a9d6641cfb7272ea1dc1379d7ddba2bb81c68516524ee88880abb189fbd21e04243693c2b11672218407601622ac2d821a4ed4cb485ea38cf4dc3cec5b1cb31db7b33099e11072413966977a93eee055fd2d5b29c203e553b31f07e9c76f3a0aa8b8ffa4c52c1f38e270310a96708b0b5b5673f7d4a14106b2d04df05033d889dd2c7b36e62c72a4b3bb502014bc4c88649a753a2b68770fb4b4e2f1504ff36b8cca2354bbb71be8a82bf16ef26248b2035e10e0fb6831c92ab5134ca70efb8f8af0aebebecc72d3f7978285aaf29b00ca3160e941372135c100b3d112bf1b60779566b8b99c0d8b31b1e18cf1a76b495ebd0a4dfdd2927fa8b6c602d0e66950cb21a8eb9ee233f50b3264801f90a840a6dabb14d87c4afee3abe94e94e2d48c1d8cb9cf6bd172bacc2997fd2ae7bd9fd8ef613e6367759bc51b331763c91c7dfaf2fb52e84906493031944cce858daab9f9e81a2f425019eeb93c1712070cdfa77748143787467f8cce9b187dec0fe725ab4f0b82edd804e84b57a5dbad775cc4f008b3deef72689b21cf4764f8f720e5c7f6700ff29b2de1ea41b6f880475d82a48ec001366fb0c75f3c572cfdb02f7d402cf9fa65be61ad7de6b9e35faabcce3fb37f892c413682e1f04dde354f082f7892e080569a50315c5d614a07898889756f56450f3f1b35a393b729d2150d826ed71fe3e88fa77b98a0ac4941961aaed6d1ce3a9e71c52b80a5f0fdb1fef0c72c4e9b5ddcec74a0f264b667e3780643fc4ce1bbb900ed68d1e6045f9b36e2a0de3bb03571023d4a7a14b3c7a49327313c8562bcc772fa1ec7b7499dae1021b8e07a89cfea34985546317f0e25c19bc2a3a198be3500c20c03f53d2de4009148f47f3a66e0260bcf94588c0cddefcd7b863acdb20772f5aea721c66e5d02e46ddf358f797553900385e712cd93a719545058619bf3294af00da5adda8bad2c543d73cbb4ece6083c11238f905e12f7e3d4951e67e91281fe5a25c2563641818d32099fd93ed4635f12e0eece1e85dc346f853793cb4875ec36de5d840115223d715c0e18ff80c3f4cd43988d2d75cc203fd43d594a721f9dfa6516c6a40b44e9b6a4376d6108556cf3a6a4f62c45ec5732176788f772eac9b74ec28b7c9dde79da1bf21951e43fa6bcd2250394aa3ab21499a3b4ef39bcc35fb3e1a6114f894ad84b7fcaef189524bf2365f391fd0204ff3ad771ec5e09fc5091e7f7f96fb968c5efe65000270e7892b99d503d6b331240e5cab3587211cea60e09bea4cdc289b2f779c42fa8d3a90edf638deb6ebacf9778bf090672181a6dbd994e15934035895fa415820728bc137347729c4572982ccac6221604eda9d21b07a34b33d64010dec36f764ccd17110de868169b3bf26ca33285527210b59f74572fa92f2baddf78a9e52705b8d6a298e1b9020ebb04a7fe1de94868e8a162f0fd9e5ad4063c6ab4e3454717abe01785ca16b02c9a0b0bea8284a472b07d4b716844f9338d9fceb90f4863321bf6e87ce36fe7eb89d1d16128b9c272cebb29479887aa64b87bbe61eb744555285c6130f971e2d8f49127f27adf0472d6c2742b85faf2294a193d04154f0252c5d949e663d4902e38807971eca1225bb2378ddfa295ede37c72cac590c8c2f2397597d76a9a9bd3b46bbb198d8bc8728e31a2598ea74f19e326ee68edf69a2dd2ab37a0e7ef5b70e4cf26caa0b82d21152150834c2f8822bfd4cbe6fdf6d37c6b081882d00f194027250b99df3a6f722c509b10c18e8304d7502529c4d862259d5e0eed16f1a9de6e6b94e91dcb2e72eb24a26549ae11c5d7e1f2a0b66106d5c66a2ba4a4206d465ebbd8e137c9d71fdaf4227d09cc0ee25dae0b3bf6223452bdfbc2af9e084ec062dac47d0946f606fce97ddaebb3de3a3a1f351125a9255d4b7e981da181196dc31e16955f695e725942e6b5b5f8f0e1edefb0224f8d56d761d2c25f20d6a0f3d97d4d27b690326afb7dc7f5a214c294971aae3b6f0be2a8e3ba733a3b8efedec66bdb7e464076225e9d04e7f81b7a31c46119841c2bb4b1cd94bcf1d3b24010d2d1ae3503b1b871b34992f08e0227a92ae893101c5e938eb5a866c556467019b5112402991602726bbbba4b5e8e80f850f2d75344d7817a81535deef76e45b0ed1822a4247458720957b38037db6324bb37e51d46e1e75db28608838e8e3f887a3732a59cdd9a12f96c3a92747033e7d6cf5e6f38c500757f43993fbb199d508135601116316a717ff0ecb48406af80fad861f14aaaaff4453bd999456f74e6061b5134bd92145f22ec06c564073d20dd4cde6b859bd9c9fd05c6cd110b24b31663b480665dd672dba81e109ed230f32a2cdf6b50f8af44059fbdf021f373be0e49e129a609f572fe61cd9717f2f8bbd33d38203850535e3bdedb95bfc98a4bd8fdb8874788d2724710a7e1c284368de61f871bad04258d9aa954c4b2773976123ca445fbf2387220e49f6acfe61c9c3178bb77d9d4a38e9c72f874ea9987d9fe01d4431eb8f325960dafb98aca91b914cb528e57cac2aee9a8d61235d7cb4edb6e35084a9a880a0687c123adc750fa2b9b43dd819e0a87246c9f72b844817022c93b0187cb677254a965297ca29d0f5294eaaf64f510d1d27148473669dd9f2e5b311e5beb9f2832cfd598f7d4cad3a5493ab0137bf3d48588b94149b04f1aee064af2ff670304b86789928f7db028529533f8f5ea9ff2ce0b7d9dc44182e90dd354492c312f72cf29c8239ddf605db36b3bd735575f979765caecf99b5c9df8c7edc623c128295fe5e958c19ce408d1583fccfc488fc414bf5e85bac1d3e283116694b11a377215c1223d81634d2e8efdc9eff6f8fb73d0ca2dbb3878d51e8786f390b62d3172fec31e442b087d5327b29f5783031a698c3c41fb66433feb324ec5c603c84c4a777ab2f5a2ac8aa5dc6fa0a41487c5b80d3e75b87098fa0cc6f76cc7168d6f1f25f9e0cd1c0a0eae28a581e929ddcafd49ea43168b7b7f4c5f4636c79b4b4a7209ef2e9b2499062773d8b45324ab6ee435f7437a978690b1fad6058e3eb31549b84708112210d0d810f7412c9cef8f2fdf7134bafacc7887aa7590620230ea72bbbdbc45cb0edc5b406275a6b7d2e3d8162b13bd7e0eb03d0cc029842298dc64ea233e2fb1ed1659343b4386f4efcfea7bc0dfaa93b12a25cf7f6e2917cca172428263b388c40c0b3425bdeb4a5af68de5e53ccd54c2a008622e4e93f2ddeb722fa77d4182a6f67fcd71504b12088c4e48a97c15d4ca4e4ab59a83b71412cf72b8e62ed29d29b08f8dea0cf60340aef24a38889aaa7f6c707172c44c789a320c1be85cd8cd4ded05702834a637f31c60736f4bb4677b4eb127135c8aa7ddb1726b560f7bce8d07e9c7963a768002ffc68813f85993ddbd21e53c01d0699bcd72c7a2e1c708d72e81cabef7a7959bfb022c231cec63c2d485d22c9128d0879e7272047647455e0d7f5c3d23574bb04a5affc40ad3bd987beac24a10b20e1af41d36155805d9df54e3754136a85d48b694b9e67b14f8b340ad84b221a93c6604189887410afe2fc01c20b03c4f669cd1f2754626a3772e0c35dcf05384bb1d4348dfcba1da0db7b1233624407f273090331f49f77c4dc2a14761d80b5d1ed23272743c108a193ac756bc05b210093bedde0ccb176fb18d4c42938092fbaa9a3172c4a35190148412719cce4d71a91c65d57af553dac471a3323d957a0af3582469bdd5045f4fe0eb3b64a735c9a6e1e82b20d15f99e3bc0a3af575759ed0498130551fca6076fddcdb846582418747f636734f70ef9b7f2728ef292b217366932139a900a02191f9838e25881d3f912e3c7a0957db3fe711c18d4c64c644ac62388248c0d97a9d96cd71b438be68aa0336644de018cce2514b5f82281be9f4d9729678d054f310b6289d16dcc49465a8b4f746fcfe8fea2471a0b5422d9a6cee728cf53c2b1d54f8479137c2fa236ba676d5d3e24f4ad3501cbd0fcbd92b16ed72142521c72a2c940a287de04cdb89ba512282422d5114e943789ab1a2abf8cb7298bcbcacd3bc1b5a047f9a65e55c216a52c75b1cd851358ea7fbffd821e5a46c8943601a620672c9b8bf67d4f32154c639aac17908dab52ab500b3b4372fe872dfa9e99f8f188f3e88b2adbfc91929bcf57182a2b6a671a4f98ecaeecd20e17280ac4d8219be1956323be8b1269e06e16bbe68fd6eb5bf3564bf2d724d557c72f4e98847cd1f0500f9ced27ce7c3f65fb3e9776f46b7c19111e085c09eb82f72b4f6503e0b331edd045d06a841c1538a17654252ca623a50e375d7d7b0381572c00557e90cb4bde7dbcc3fff67ddbb3c1f5d8ffef70f36449c18229d8e9ca4584e7d6615789c79259425d5e4c00a95a3f0f4015b063c10c303218f98f0f7da72ffc5e238151670629e50b6aab8e70df0ef668f68e05280def4f783c02499b27245b6fa0aa306df26744a9653843aea0e102eacbefd49cb08915fd7cd7d6f7b72186112e47048fa56643ecd01a360000a1db8a8cea663b28e2d90d6f89cfccb2c5855456979cdadf8e2b8210987d94296aa788e198812ad26113e923b96aa3f60704d3dd0084d71c0de41adfff82ebdf9e17740d05a65c080c43d027b576894252158b9f848fe3a88120ef9cbb974abaa6c50c3df098bb4e7096d448bcc311231bbe779e57d9a43d0af8813915a586bf26be5f2bcaae1b53136cf340d9281867265d82dc8e66a023ea35df4020e7a97d6ed6cecb29e8014cbf8ae3462b2535472368f8cb778ea373e8df6c8824226bded6cd77a730c8b1c11ec6a412a16d1e8721a69eed96635048a5763a2e5f432de082959994cfb8330b043bf1b26498100722bd167add32f22929e1c994ad5ceb5cbdba1430450b3c8baa69503d78f0475725660bd6cfa7bb80099274614c65eb04c0bfa73320ec868a330a8b1c23c615b6f8dcd26e175c529660183f4baf240a7c857c09cf40fa3302709f4c97c48f4f17275b2928af8027c6135cde362438f511e95d0fb360ef9dfade5845865d90cb5727dde1febf390dc72b884a0f3d568ff8cb0ff44db8fe83d2ec7f248a714824d72081385c813956c3a49803ad763a3d29c496ba694cccce2de52e6a7faebb7464ebb6a439ad3a003a02b1aee005fef834edeb0031137cc2b2d4e08268e7aac98725e441c4d2bd702f758644f79f9ebc9eb1d5120ebb90e4fd473a12f15fdf8a26a4e5929aa4d14f7c99570b6a6805a0ecbaf74cbe30d16c4648e19a3c4bcd89671c09c3863981c025dcc200e0a646dd06de0d8549a4bb2393f89e7a8d12600527232646f1069176393575a93a145cb5afdbbe9eb2e317fc44d4f3bbb5ea065c7065ebdf0b5a32c209904592a99e8151f73cf16da0b8f83fc0a03408e62c83c4a3e05255b102a978e34cb310a60bef2d9d5f94fd448fd4895fc99100f2dda991f39ed292cd2b7e6af30eb115d116c14ff6ac3bdc9081faa60ae1bd2d876c412e5726d4cf721eded12a1f6fb00e5867c9fb4aa6e47d081eae066326af126081fd15bdd4b4e791ac793acdd4520b2b167893e67d38f02a28f5457cfb1f4fae2d5dc729c459492e4cfab17128a64e4ce04445713458721c25378daef8c11e93ae77660eb6c366e90488735b66e09665a89314c2ae7ea58ab23c0e46d3f104414e9270cc3083895d517ae4912bd5de2cecff1e29798f82b91bf386c037a224fdee5d77297ebc50a35d579fa25aab3150c8675a5f6caf27101983e6889dce047e817fa72442b9852b7e6f0e0c284cfb53d3b3150cee518982fb96bdd5b85ef4e76aa26486143378f9bc7cedc481e994679f5d946717f7f86e70032787fb5641cec88f128eb9831aa552fc55a7fe36a8e3da829a4c193713c0369926edc28fb9fd3b9ff7200acdadac60de15303b396dbe28723c81b5726a70d28df875a768dfbcb54477262122eb624bc1ee27ae582d39324afd7709c6defe9beeb0ba23e4e3286f83a721716397304aae8ec0c4269365f3f369bf3b6bde05401a175f1f5f60281029f3c0cb4f5ab1b6974619411ec45e1bdbea66a52b5f918d63fb5306ad34ead54b1721223d9948e7a2978ec7ec81c686e8cbc27d66848c07ad08a98c3dcb9bdfd540a7a8710954bc5064dbdc81f5ebb91697bd2c99299d69bb0baa64017f47e81a32ec216fdc10358b7b334703ac21bb0ecdf0f9255b4d0796232098e9b14a87be3726e2037b74a679d507436e155db353dc454181843ed81b66ca3d4c205b13fc35a8baa9920a4962c156a5edf3102d1d9e92880a88b2239cf30579a606959c2e072d192ca8a55b59d96bf5e4b17da392aa3b353fba749f8343d0ba842ea2722561e004e2f7ed3293e3f5906915b14e699e1bf220641b16a348cca7aab5aab6f4032ede982e3f812090bc49f2cbbde357093aa98aef43c14829384fa98deb6c98808f9cd45dacc6efe53c8021ec84c43c2ae0b28b975bfb10b289a424503f0385a72b329b7abb3a457073d573ae100c2685821e3e10a8b8264f6b8611c4fd3b372720ba9a09e87b17cc5d9fc5333656963b5ea18ebe5110a9738412a4d8472887e2dfc0a235640261e99aa0a6360d507df2a57d091908345a2afa572519bff940b3e99a92aad293c8cb9b2f7d4f2493d750374e0af700afd6ebb01cf843fa1c35a72e4606a64acd61ca8ac2abc33ca86ceddcb4c4acaee9a242c5630a986bb3c8272ba631ea96b758e2e29fdc94e4cb1369dd2c00e2039bc2bf05a9c60c9f7fcfa16f46a5b447b9709a9e71184555bde93a8b2bf5146d930e7ee28db87dcc6671f720ceb10a08367d419d542ada1bdd979a045b577819264e1b4f150a9c6ea20756c9c41f71c75e986467fa3a9f66921b4d3bf975782c7ae5d3cb53ece562f0f52720ec47448b13ae33caed4c87ff586c6d77dad83c8c17f25c9d65ea102dea67022ffdd414e9241ae58c1b928de9d4d289bc4dc28842d84a4250e5b81717c870672499520b1a64704a3adb2fac61d04e204a2557302dc491f1ed9ef11941279f672124025c0040c169ee2928885b8444ec897539829125ea902937a81793bb9d90f970ce46b96261862f67f2e5e1e1dc95d1efd561ade2222dcdc22b21ee0247f72c4b123239a1c61e5189a6ca406e61002710bf447a76e6ede12db597f0c272a1c4bbae1d6e3f624d33f187ad35107e98eedbec75468da330c80b5cc883ec18b7298d4e6bdb514b1154d983c256fedd6c5cd37ed5e066cff4380567b50b199637290675ca6893bf09a98dbf9d897e5fd54aace8dbebe81485af1ce95a926caf349b42866e0daf51a56cf977b4113453726782978a7c4c2c85db935bb743987f65de9b10c92e52ace8e29ef2cc89aca4455e3298015193ab5049aaf8e269f4a9d729ea50d020f4d5e02233d556106db58ffa056b64ffc8cb3a1cf23a44a24ea31724823cb94fb9892b28d1185466b96cae7d52170d80d78e55c42e8743ff546c972123941630568d1a8374f6bf9ff15db077ba935446a34c5c7863dc99557451072f3e0e7af6858c6f78232eebd21a7660ff6dc25b9cfc090a33a7fea2bca54b750080e2426d2df2ee14920d45727f732d2ffe23faa8379dfeabe29359b912324558d840e3170f1af752f3eeb09ec25ca2c97075c99c5372180e1a24cd69aca612e7cd8901dd48493a73b5ac3222858cfd23f50146bb2b6fb7786dd02c877d3fe722b5b282f4174eaefcb2aca07769431c8b6ffa95a4ea40e031d1c0cfc86c98f72d61d6dd3819e09db34431288112ad18ae3be377813e7606321b4226c39a856725e90aacf12545576e13d966a7d40292b8d64d18eb33329c716bd0abc14fabb72f5cbdf8be9b162683f30ba29ad44ce84ee802ffd48b5f9f35f4ac27926cb932298f14083b71fa4e6e11665bbb83093f606c5bdaf922acb81595cf30371ed032eaa783ece81e8fdad6fdc5486e7ee816330c8e4474a4b4761550295a946b9ca723d4420a73e7f90c4d0c86c93b0c72efd54774465f16ed83ecfd20cdc88d4817244b774a778d4e39e1348bc4f8b04746eb1e637ddee8195e178ea14fc72cb317219939411f98e0408b3bb5424ac79038f2dcc7faa3919b900b2675fa8cdb465027246017781f80f15d82a3d593ad787f8037141284125e53b5cd0b481ff21b63a97a92d59b47482f1025102c2dfa67e3ce1c5278492250a29885531e43f2f3a72cb8dda01d00260826c28f5eff1b2afc0adc45565c9e1cf09017fcdb8694ff372b98a6daf1c8d8b9d6f1ee4284a15b434e12bc83d479fe6efcfd67abf1cb17e3b557a326a62d0d960953359f91114490d4ed7795de4d4ffabacf11b3ebd721c1c88fc82b81750599509d9e10d2b873ea17443a1a55c98c2cdf469f5410d17865e5eb0b9f521cc806a6b7357a6f1bc3bdbcab94fbb0bca6d45a39fb9b81e2f1272e1acd3503c7e10d874391f35d64c9294c1649be7424235b67892af366852ed723a778f9acb512ee7e19bb9dfbad37f0bb6d39c818b40defc38811d896631767286482d61565e027bcdd36c59dc135cbaae3d506c6fe4aeab1e339a8ed4c0ca728743be774be09cd285f53a95f13e66cfd0bd62db494faa5f64a5420542428d08ffadc5bbe9773378aba673031e3b8c0a737beab40ea24baed54f86b2a6be0971a5553d4da927c19566d0ddb363db510f1b89b6d208a11d739bd8544eccb6e672a0eb1fd9a7c8cb3e7a905672d8d2647c62601c7844659e024bac541e2fde0538b85aac6e71eebfdb67a5c87f8760cc69ea42ccde79e4a01b5360dd16b40c84729e71509768c50b6a7789d52f890d8015e51b26754c536464dfb87506e42894202593bf3b2c4b7a6a9b6ef80aea2e5085d0e5e28bc46d923ccb9bb8adfd6a977204f67c1876cbc54a7de65f0a090e0de4b272de45023828a07e143ac18aee2772a8a037b693367fa7ba3719785aa6ba882625cdd0f5479fca905f77a77de3373be90e9f9aa3d0083b1db90f0fd706023012bcdad086f027668e5214fe5f0e83620ce2b3daa08aaa4a8f133bd430678f9aed8e11d876936e8e7646e8426400e472e830c2dae926aa8a07f928090e80c7b464b9c3c62375e61993eeea2520cf5c4e221b57f46f55d84ff56caafc07a573158d635babf6417d63d68705df7d2eb870fee4f715987c01eaefc5440ef844725959711e30da571e49be9d8d7b503de1306929898fbb2b838e5ead45ac6461d9eed2756d0403bd061854b7a4566acd64729810aebbb391864573a380e2053230f8d75fb60db1b061a3cb57c1ca8aaddc2544b78769d9f329602a68a5624e9decde425ce1948d5326f0fadc9765c1bbdd57320885e8927a66b427732047164bceee36bc03fb0c44371a532b5552a2655354138ce1428a3f9c4a7c7ba6e7473c6ae715916d348cc5dbdcc8fb4b110fb29872434de470763010f7cc1f243213cc29311100f75b58e462006a137067ef494872e1b507313383a6483e7f964cd252d7cf9eaaa4b418487e72631c87666860390862a404885cfe527d3eb2bc91a08ff0f942df1037b036967e3fbbc956bbd98337f779d36578e7616ff71abdee433988315f14333560cfa8aa6605822debe9b54349950c4d27dff73028fcc7c1c577d177e64d1e58f7e87138e2886bfaca013310fa33cc55ed3824dacc19c6cc2e6fa2459ca791a65346313b05f19cad3338d572ed58e40ba3fda7fc6c1f00e7bf746322378ced951971de792759294d8f1cfc7271353c5d4581b3eb22ad3357fadcd174deec788d26da9847e39f5d42e9c103533b14e715ff211c85fc8b55d591e8bffb123e504eadc3c6b75cea3c81afa4e17218bb9e1c39eb1a29e2532a34cfd803d29a71ba45b9e569f2b80cf6119e485e72b67b25097ee4f0ffdee9d84bd3ddf18b4ceeaf28817119c6f1a66874a20c420754aac61300cfbb9a4099b8995a87969c18754d93b0191db28ad794839bc08d72794133059cdf502009a5b088769898d596f2f9a623ae5ace1baf6967434a8c3b95a2b8d39f7c383e083deac287e69cc383c6bf6e073179eec36692c4d2eac00e59e49b938be9f9a4897d4198a0ea6ab0edf9366977643cf2c04ac3166908ac5d7f802e48878afbea9c327b9c3c58423d7b81a57227f2ee4b5add3eed9046306cf6f2092353589e4fa95beffebcf1b4af90fcec34b7f7706a15a5ff88fa3fb023212344e2b9f132f41fb7dbfa451725c95a54741652b4da12d3de893287c220420d9a680c84f71de7b91731f6d4ca6ab3cee41b6f6d11b67793bd83043546be72c95b10a57d3925a81d6aec4e2ca94acdebc2c81aa2ea856d9435c290affde617782b0048fe93c39820fd1e242547ac21f8edb5c74aa44fb7ea81ddc994a9fc7258e39614e2f557b960ef8bda8b887f8e696cb31732e2813ff8aed006c7d58c7207f7ae1c48bfc33542101589b2a328223689aff9d939e60e7f5d48e38f8c7e608d475615d34dfbdef5c00b31c2ce6fbf388577bfe9b257e962d004ce2a434c5b070c975063b72f09597df4c134a8453ccaf6e28e32c56ffe6d844a5b83037e10d8724d0adea97513a10b8509bcd68267c3d9d6f4912a771f29ccfebbc4ada223e84a469c25eddfa208ba2f1314187709ac58f422cc2463949d0e59978b5a8629e40499ea4b5b35d05c9ede9d273024fee9d57800026241210cc4b9d150664f72c447c75a0454be5b60ccfbbc81c23cd54c86f07c052a7b08b93e51d0d6b0bf07c8a709f6840f4b1a09c54da2f8ada3658c837f346563aac4d7bd02e7b724c30f06a1ff08880f35a00ba32685a045ed2be4b4df8e2232a419554bbfe84e8d8472978fb98b5f951fd2377c9c3f5f3476f896752b8f8adb299ba7c97f8c2d600772d76a20451f76a58be395c32d9f3195bc7042c3e4f80c3404305006d5e639647207b27573f20cd16d82cc925cca23477fa1339ff381bac55d21b38b60e8dd8f726bced13787f0aeebc52a1b33e4e2042257427dbf865ffd93395a0eb2855bba72d31e803a2540ac75aa366eaceaac48b15ceec1ded339fc02c96232904b72b5725b0d824fb581f09f54fecebcbf717eadfa19aee06a7048fb16cf20149c195064b61365e177cac8405fa3e5cca5b4ab7d3e4a3df42e98cca2a2718db10b0f4572327a3f8e2cddf87d8e434410902eb2ba85130824e5dc99965e726fbc0f59cf5987572f706406a940a25f687e76aa58d897aa6c88468e4175067e5a40821e9572095b220ce4d2c933893290960534fc00e877b7d0fd4bcf3e5ce7081e22c45672dfa2eb7565db282e2601ea2c92c962d7b2c49db06c51fc4a270597cd8935cd7280cc640ca5e193911584c296d9d954f32eb614605d8acf50bad9084d09faee722a4a35425ff96f65c47864d9308366eb02fc57f69110a4fded05c1f60196a1726afc5604d530460f9b5606c095feec1e6092962555d12f49c31421d961974e72736599057c637e00ccb778140f9c8d8566d562c8d6b4a8f5a085aadbaca9d91f9acaeae70b43f63ae9e24c2d3d655be8412b527397423e988cc13270943f48720419495af67c5a1cecfedd854114e6ea1df2ae016546a7394852d1df8ede88405e90087dba7e3eaa94cb524d713016b7e52f911b5ac9e4372d7abdfc38bac848442e908b3ea18bab7d5b1a76cfad6938c4e4e328cb849963e5acca76c44ba567cc29ebe1c4481c9541247213c427e71750ea75e3b18ef253161e59a33592b6724ed1556c3858a971b507732deefcb06dc0af070efa0bb1f2aaca617bdc98a672502a6a133d030bc0287da71f5253addf3dd171fa7c9625fb07aaaf228261046566280681b9102da1ac34fef5ac5e6aab42c48a75dbe33d69e8fac77feb53a172fb615b8cca4a1f51cd4c46cfc5d33e8baecb46beba6c46fe4e63138c6c296b726358641adb19d94f1ae6d6c4acb4d7d871047be8b0ba0fd57a5c61fadd13de6d7a2dfe8376978df81a4375f9ba4403844fd50b4f44b9327999efaf5378a4276fc2bf7359dfb4bec287cc10e27cdc7b063b2e70a15dfc68733f1c99623cc41472cf778f9a54a4bd76c56340c0bcd6bb2f4cf8e48141cbdb39b7411a6f0422081802b12c46fe15d46e122d091f19437a50d8358b1fb7d7461979f0e7e38df9fd72c7b1abf2ca483b86306a52c0dd4dafefe4e722101697eabaa5b80ce4f133fe72728c7e5f68591c21e9f46bf30796ffe45a6eaf0398461e2adb9a8cd32df1a97206b26a3239f8948ded65bc92e278d0754dc5a09d0547d356ab8a09855cde3b7276ab730c437ddabc58dcd24b2189d6b265691acf7f53f20c162af25588df6f6462f29f9e6a178d6c11214aaeb0f19ac5fa080fbfd7fb0d82390ef1ab57e9f009fd5dd511b8cd56e816403178b75d6a908257e710bfdddef714fc1b7117812c369c8b71e7350392b2256e8c2700f4fe714be5e3358dc7266283d471f2a7c68459749563bca6acd454b3fa0e5b89773f2a3efb75a81303d45a78568681c4844937bb5bb5f2fc706b02d63a4ffeca66bd3a66fcc738aeeb815d754a1ec1602df372740762b84ae6d806ce68ad14f0ce02dd9f6ed5e870e89ca2dd9a49b96995bc5655e5868050c73713d79a7952381a1f4cad5f09ce4070e0f1cf49c9bcb90a2c722a89255d178c58ff18dbe801ae3befe7535807d5edf849c0ecaf4a8bd9a4dd72522b1bc9a82215a9e37f938c21dcee7c4294ce2a7b97590fd9e587255a7150721fd8eb38e0997299c609e296e9e1d8468b09bad75684721c87b55323447f72725f108e25ccb3e809e8c1229f214beef4ac7b1af2af78bba07313eaba3ea81f308ebc7ae7a41644d0f5f4bd6aaec8f60410f3935736ad8b6bc38099c5137eda7267da1072c8e30828358cbc31578809093ebf6a4e74203e9bedd4c4738eb50e723fb3cf413856ab66248103b785a95bcc54d90f3cdec8a3ce3a58d0e577bde472c232fcf6f96b133788114523e5d4a60db4dbe3817a8bc4e448be80d516b4d372597f08630d8bf667d3e20ae35370c671c8370930819aa019369383d9a4d45072f8d94e95d7ebc2ccd53bc3796c61399ca471151987eb2f5eaa5e711dee20d2729d0e37a6198352f2daaf3f03a2522307d8a8542316dcfb4f62591562e5e9db1564c87a2e7d9a68116bbb8f9b70185f117e010ce598bf1a4aedd9d554efdbc07286123c25df1d0e8889ec1c380757cad0ba7df2c4485edd1beaa929dae1b81c72600947bf10578a5eb7e59ebd7fa74b8ab6ffc366e6d231a011b813963c70137277614272c5ef96ef6b16f68dbf768f7fec26aa47b3ac47066b7df92b9dc4c362c1f84122a29bdcabb0d34011a3b6376eb81d3c168d0d63bf4b732123bed58a6da980179e83fa0ac13e61c102cff31aff6775efc94430bf4dac1bf6716d599e3d7aec354d14e909745ee9e3bb86be2d2015dfc5d37f61ddeb722c6e7304c30e65d757e712ab7de48d2cd15694722e27ef427b5d47d018ff4219d69090e4c60e3c2997264814e297539379e897cd2404b469000c1c825729aa6ba6b3ad8501a429b0535ba5f5ea2528e80247631861b6705aaec1674553d451a1ecabb626dada72a249266612a48fe6f455f7e14b39468b247e057438d3fc6e95175119b944e635e2ef224cfa29040ccf0ce769037e0779487d33dcb6d25f8be724bd177abfc87245ac5d05c647cb898be909f0e1ba8bd2879e5da1433b6b5939152d676afd9072e6ff194c55a41aa7d5c232ac4254db6bb09dd6406ee330d42f7a984cbbe1f972457c1fed6013f2d20d809a610599b664276e7fd3f19aca0023191810822e9d720c1883b4190a422484ec10c4e868dbe71900f4e388d5e16347ffbc7743703d72d2f3645c064cf27d805c0bf8d214ae0abbc6134355316a2395524696bae76372892a00281c3e227bd814d936d378a2f49722e3a531f0de9f493bce6a2993687220d328c027bb91e1e69f73a65f192191f221890ace22a28e9c0eaf9b75baee4f48a2daf76dd9b6085225b6ba8597b35b7ecaa47e039145feb8b5b365a03b957299cba18d51dd1ffc34b080830c333c6e6a3fd08739f2144e6c5b17dce41da365802967ae3a54d3d2894a214c9fe72439cc48939003f8ff836a9aa25283cceb72826716a4cab9cf012cf71716029d4fa2a65adba09a0bcf166a9a127416c2c4464ccbb0887381b6f28b203ff4c4cbebf912ae934a21eb371d08390878725cf27256b1840ffc092ef1ca71ceb3ff61ec6f429147697e3a06ef02c0bcb07a9fd57283b3556b7919d15338c75e94ad045b1ad1650f6db4d584070fce0a6f891308173039722dcf3498958baaf8652113d984e7f403c4073c4bfcd2637e77882f58726aca9f7d6547ed7f9cf9b729aa6fb5bd2334877fe47f165e0c9a4ce91d24b5721b580f4a9517a0d771cf2a9c3205b57e12294db40a5438ca4e262e994851de72bccfbbaad4dccd812f0fb4994f10aebb2ed3c2b5a9d1458ba3aa9ab68db957346302ade84bde19085dfeb3205d2d4f4e48f37ccaa3580ff3e23b3f2ae1ec70728c07f1678235ee4cbf07123a94116870f6e047ca0f703510d6ed616f939dba3188d1c98ec76ed28c5a5bf0d4925b811386382c4d10f58f7451a6357188572a1bf27282ea7b257aee76e3d961936fc0cc1477f0f77f15d6059ae9f3a7da1afa72b652674f1399b8d2336adeb1c4a17e524d8f5fbfd28d73eb8efdf21c3c59477224071b6805f6dcfa1fb51bcbb6a02bb3b2d6c224ebf028100341780f773a8c72f285b7cd5029063df5fcbc38797210263b6a4cd3163a400e8416d7c99bc10c727819a5d9e71ebb017c9557cbaa48599014e2996e45f18fc3f1ab45a30bd0633feee13cbd7a777e0f780f8abccd5e1cf7a596d4c11dd651fb71e3f15ab8e56c72477e872dcb5ec8721b2054de4edbf84cf8e76950f53dc3a2d667b92aa1d04f727a91a76d0d6be6ccfe0cdc7773337817debce8ecdec01f54c828476718f8aa58474518ec709f5810c9bf5fef579b472b4f934a80a0b562c6bdaca524018bf272b044cbb4bae294bbbf28bbf8603ce693d2707d27b3b3cfbedd96c6077d7fab72b70c78738f6ef5c3d2a3a4caf44dda095116c01efa212c19e0d59fd1871aa4729736358de0bdbf43da746676eda3fbd04f897c95ede869d1cdb6305fe2a29960f172b3fd3bc70a4469623d195840858b241efb4edd8d424b38f4dbf0f48c8025d6d342160bc656565077ed908c1d4cae2d34ac0f38815c4bac695fcafe5e227240bc11201b55e7bfb1377d4c0d3a4d8996643748fec8b76aa98f7c80fa2f117290ebb5b3f188f3b9d5b54e79ddf3f8d431460613428fb69ff746473711de7572dfbb5cffcff8b5bb8df175a8cae91e7bd41c9369e1b32adbca967eea184a363810fd8fc7ad8db8ded9bc19558ad98d34987c0692d2f496842bc5a2bccc68fd0747f228c67e4f61f58f263ece22c303617852c9143f75e98a72baf3d194e32069066bbb67ebcb243d628dcd25b03d8a399732502232b4dce4b6edc237afe641465e5521d6b225ec38328690a428d201115de95d8a5855b7019168ec8fa99204729c0ac4f52bf28e6ee8b65337a1dd962bc0b06478f753b96534103d772e26373b3404e3e82def4fd6e52846b705a500c884a25df9f397c4004bacdfacc8b48419a843a346d96c17559c160069e681219769a2762d11721fb43204d5a5ade0437206014595caca333147570fd02f6b20e03c1c84b2f43a2a70d46357a45affd372a51a8c2127fe9a1f2d86b30f24e4b3707d8afbfee11ed79580d78af545bba5723b9099ca5bb718669b53cbfe2d52cb6218db30b1f05e25033480d17127b6d072a293f0a5e971d3bba5ad42b98efacf0d995fb7546c1eea80285a9cc15dcf177250b808dec47db5896c710b6ec64669cbae55140a8dfc9cf4a0f320061b855b721fb49dde5cb902f61439914ddca389534b60a4eb3130f1a6b1d6211d3d21a472c7fe722e14fccdc80fa6f05f3dbdb564374bc33802d056d0775cae1301091a3ab2b85c8018f8a33eb2a1908d56918bdd1049bc3c446a77050819dac7ea9fcb72084b28e939051e9e243f12b99ad1f71b952fb17c0206641f40af632c942d2572127ebe35dd7759c93fcae082e3cc424e6ca56bc4ed8e20c027959784e44d8772d7f7f8f29d129ebea9ea317be246d212493501da1725fd34f160597b106efc72216a8805a0c69949eddc736bd012cc16ffac4673fd724fc576ca2fe85b3a2772761c6df105c011872db0e98bc4076eb1f7018c22dc92bbe709e949195cb89c4cbadb83a43997fd26aca935d788d369a071619c336f628c64720501fb7f9705503dd6ca7632afe69cc8abf2c3c7c3ec73f420ca902935a215f2993a5df786047213917ce6f43949ceca8202c49062e23ed79f90742198f4e23a57386181eefe46300d6043c743132210367e25abca871cf6e801c27910e0d2dbf5966d6984846f133aad1c51e8fd338b606eb112595fd057edc7caab9332acad8f1677c01fa172680ab037f1080fcdd553f54d1a4c93e10f5e5d5670ce74588feaf8515b8dce7223c8fd55dd8df25783fd6cd646cac707edddad727b982f4f486a13c406e7db529cb40c0d18cafab4472bba216b6f997f0def0fd520dd7ed9b0c2ea7e42951272c1d9829218ae30511bc9d3956e8cc24dc9c94e4e1d163440c0a25ed7fe4eb472ec3fe678dc63cc251135d9e4fe34c81fb2c8471403596a2f44b6f3a0b3b73372c955398a875ace63a5146022cbf1db374e3e6ee2d3e6541b25ed79fc5048d209f1fb7d163a548c27beb2da1407529e052004dc3e51b9eb4d9932ccd3a91b30724eece5ace63e2eec1eab547ff069b9f02a380611270df164a58dfe52e8eee12a48c85b89afc668c01afb563543786da66416194795bb7e62d1afd6deef9e5072263f591778f1eed3e98497659336bae7a94d9ef21d9de02fcc47bf3975742572dddc5df70c50b84e115591290ab8c56c9e0a0932777105844218898f61c4c772a65734b6d7d76ffce0bb382b80c326a87dab3b0e5e849a89fc7904481f38fe7235ffd49a6df5a68188c15405a1b52699ceff74ffda9128f0f83297f62be8c772f30bfcddf7c89e7adecc6753a1faf1aaae2faa433508982cd4348c7ac7b96b728b92331b199559a690d844633500d7f8eb62bb45f0f70a8dc3be1dca09e13372c91f263de16b24572842c0fdcb9d4ae2dbb0a10c3d1150a5581ff829dd746a720246eb0360eb4560adbf2be5c7762407dc24727907b95152eab204a0e42f81334bca8126c137fc19050eb8b0b454750ff211cc9bc8e965ddca4867fe95f61462d2489d73676f84d59091ea1e2b970ffd504f4acb906bd82d748f848987ebff7204b83c9f3fc86a37bf8b10d5e42a5a641b2243c2d847dcd364f6d71da96708725a2ce1ba81a60340d5bf395e2e31705590782161f4fe101dbcf3196bf466bf7253603565960ff8ff61c2abada80fe4d716d1a84cd4b076ac3e5645dc15c24672a8898663ba1f0f59460d0ed6966818980b6da3d6806c96325a72b30d06e012726267b3bd6bb4fe7d11bd81e1303c8e0b075514544424da29ce23d6850c6f9f102068c611f0d41ef7cf005dfc5335d4f70a6b2b71b70fb84aff2af9e56cb5e172752bb9b559a029cfb137ea5abc683b0b93fe705adc2be2ec64b7f115c0530772f5eff3d2be64ff931acbd34ce2fd725dc1a2874ba6cbdde9d77f502cf7102818b5c8491a36418d7611e77f9f4a89f77ec36c414d5f70c7b1df8ee56220479246311b1251e2821b9838cf95c7a3e7487557b2dbf90d8ad1baf82585e614ea973cd9815ee855917eef4954d522f54109b539ee30cca68707d2f3f1486fd35fbd66f1758ff0b114bfbb7d536fd8a3fdd3927b1f6bec5a7bb7903d7e59161e0e940918677b7d496ee1a3011431a56920b6bb542d1d6b3b3503390eb931f911c23172428a7c40e5816830b616c06b44c0e83a0ec6b2a6a509a15dc3c511e2e717577210c0d267bbab0e89ef8c7b87358cabdec56f92a2f4ae2ec5ccd0462e1503f3577da9a413c9be3011c6a0c9766aea8193fab1231ee0e9a81e349da43ede22fa729cea796b3c5e7817d6ec43fd1a4078d184361cf239a54fcab1952c9c4e621652c1eeb0a8ce10c6c9aa401eb1159a2aba4c6958cbe1272887fa66f07314f643721a95ff099fae05085cb78115cdc34820b95e83534f49146ddd773fa52308927211a9c2b2f4c4022169b5eeca9122e4e82bd8aea69ba0b783168a42c26b391d280a556eddf9362e4900adb934c63ade6ae3df4e658ca4f974cdaa4c49197eaf7229bc9f561d9970d459a9cf8bac6068259b1d2026b72aaa4a0f8f08dd47f88472e42e8597156c8c5bf02a32c420d43aae5ada99b2737a2169e568a9bbce9ea872d1e7a56503f548aa83b57dd6def662fc4d75ecbc2c6ec1f8874a43e51b2b5f72290686db4d113ded40096bf976f20dd2c3a2891f1ef25427f647b18b0a796c69da1898bd97e1c5270a62f0737a7bd6ef1a38a5260d0d3e1a566335874630bb72e235709368febf92947fcafea2a965c03161b7789730ecac69dc5c36fd95e0729e5dbdf7252c8a83faab9b82d49a00516452b29da20dc4670016d7db73aec841e81de3de0024e385b50ddacde22df7222745c408a78837f993e2ddda3d3af64d43976122f0c51f84e0d3bd86e97761187b5b0fce6f3a21ebd3182f443e4c2f726c4ca1694591182d4e100484ba5c2b08f788d9a1ee6cfcc5e37850a35965423055cf1ec6c107a89ce7dc4eaff9f1b21b1da97217c92176441713c1969ab3d97273a4afd088d6d0e53876f88731478f90a268a93ff77c6fd7e6da7b2323dcae72a49d2ce25dac415fe0f223d0291c86326c13d16737cdb5da565885ee1f21a56f163dbbb8770cc29aa25ce812e8e742ab82754e4d5b81b454af10262bc831505a734994edabd7f69ba5733f5610486e0c1d7bfdfd98cc86ced0c656434259df7245f7d19fa573dd8cdc81259201069f9f4ff9dc2008820ea7be2447213a92ce7264d4802e3985b74786013b7703c2846fff40a56e540a3eb0dc2f256ecbe04838eb5dd0fd40b1664e15171fbfff364cb5207fdaeeb119c1e3583f6caa72e91d366849b1ca7ea8f5cccc0e721a131df625d258fcec931357f73cf1e4cf0d0ba272f7ef65e16c43d36b36d55ccdfddc5e1c52db23304aefbe19e66b9f464b2eaf3349f9a4dc8be639c9259562222011c8c7418cbf20a43183215b1d1dddf137ee54a3737af2a7cdd4313cf4c6f182899b5bf4bd249c0108bb94e90a8445c6b6106cfca7387ab6eec6e9281b15da357a7b4bb89e9d3d97c74fa1079d57413c8be4720678a0bca17475feebad04cad18d622ace6d0737c2c37595d702661f1724dd7258a6c399c1b37b30484fc1167ecaba4d34a52a614d8b62e44bae600aeead840dc33a403318e08e90242bcccf5c3add1b7fdd4ac2570ca4365f02fcbf599b3d1a68e251f96fe5900bc3d53f6eed26b72b1dd264fc20d55e4c34a40ff1a1859f2c8cfb3937c442666597cd1e0023429d690915eda08f60ce47518cc14509bc673321f513bbf9a5fd9af2345a860d09f7a1aa40e8967a3fcdcdbabd0e176b8c7672db7d67018780163b6f00a884253c4726e86753d84f95c15435edfe87c0e20c720cbd50cea1b34987e933db604aeeabb8772b4f4699268d122e10376aee3a26725174b2bfa8f60470cf1f56d693179ec0fca120d0f473eaf69971627187d112728d0fb66b0a45c5ce5516ff0bf8bda5b9d00812b88243e97a1a8f26ecab5e9d724a0d6b5d6ff739be833bc8fe4d085236343d9273824e79ebd258e9df5c668072de0cd91826b1be7554d41571c4bda98e0e0eb85781765af36e219b838970f472876ff61614f6edba040a82ddf293ffba0babfab277628ec79ab299ee31721f3204921c64abcc19a9ec6f8079b578f57bc560933290e595d240bf46498979772e96313542cb4adb60fc79b4d456396d5ab6d59572a5a2e9b95e3e70dba65f3b0ebaf996a21f4cdae01a6b6855c0c15c2e1bef4ffd8dd6fc59956ac9bc5a9a042987845fe52501ea31b0029e8a12261c4f4220815768654ddbfcc8f67dcce806723d7b3be6cb23272268c97e6dce723b39af9a4c4609dd7d2812f0a7e8220d7472618569345c3f892c15f5d30751af21478c5bc0fe8793748a050a2a1cf660a472377c78151c56cda9e5c392fd4034303409ffbb3788d97a7f8ab19ef2f9be76727aa4891deb71b2ac27fff1913842687548337c0e2322c51730b03202ac94a631455420f3700079d65e35dec18e1f628c3efa984147a39d921696a6a06cf15b72a693bf9c9cc231a4919378fa217081386f9ee1093feecfa4a5a56d1030e18072a2ed95f284a71b6e85cf2d8a9dbaba39e8c06dafc9398ef0c041fe4d3c75ff7219f114245ef6f4572394ec28f2c10175d9ca003e8d9b100e5b3250e39c668e72139491adeb8bb2a299b91c4e6ff63328c32e3903c263ab6b79f52fbaf897e34f892d07cfc5c01555f97d5dd9ec976e869b0993a25de6651b5240bcf2e9b5647285b5aa1f8280b731c2b77f8ff3709465429bcbd7d8cced20ca22a479c93c6a1e62cc261588ed569fe3c01f07f46dc52ddac9090aeebdb22f586c3ff31ae6b972ee25117ee0cdf8c0702d723e1bd1682ce91d515eaa7236a67bfa47ea0fc44b7279926a18912dba30d4db07e38651c1ad6fef125a77ee8c3755501ba9d373cc72a8c44bd85b4f0b7ddec6faca60cb667cf9f1d15529496a0945fdc257f8720607e5e9df72de892dabb46f4656b3830ada3265741aae883e52f00a79f3cdaaac066a749d43f9f7d2ba2f94f09bf32d955cc2579dba2cf4cd17ff0024937182327217e72ef942ae3b5bc4bf6893ce9709c3ed27ca138b8cf1447b7f746f9592eb722da1b3f94c30e5f1692cddd3d86fb38fe30a722aac3fc79cb2c8488b6010aa61087a2c8579203ffde9d71ce0b47c346286f36fc251e321dd712658bd8b3fad72d725a2049dacb58f51c28f6748eb41f11a43d94224a94cb3c3f3abf200b5982e6de9fd42951052185058a444785065d4ae0ab00cf0ed73e7a33f3244d95a6c72e0c7cdef8a917d0ae5d5a9c2811d6608b5d796c096d6165f48eb6ba48d07cb7269f22328ff2c5126472f559bd192de5710b54c0a0dae4f1ab8e63f9674323a7226d343e62ea2ff11bc0cc5b8dddc8a761dbd59221157c2b195a30348f0b60b72591bc499a777b92bb301c7643e2eee45761e93a7d4226240355efdadd1692a340479bbdc9a30287a15bbe8c1a7091030295142935a8b534720df5a1a2f73af720f9ba0281628c5a3b8bcf585c0d09af08ee04f71869592aa2e22610ac572b872db3ae2589eef349822ad2e699ad3824a1687e9c3cf9126713d73e9840c854072a0a55560ba64a056261ba55f4b8ee2a435081b8dccabd6c1f7966ae7919085721a396bccc95885fba98e376b4123bdacb660ea3536f5a99b8b59d361b2d1e77222f5ab7c41787c3fc9b31a1779357b25fc5eb31137700cfd521f9051e18ba372caa3260eb1e0bb186c32c437e910b7543294a44dccb9d0e7b6cad62285e275725105a9bf6e47f27ab3cedb590fa40d9dd478e44fe96352dfaf71a76761765572af6165124362c5e601106ad2e3aabcabba0b638774fe517e48a62b7fdf18f4729fd2eae288265d6ed8000ddaa6ec08372b4b38dd12cab2e463198e10f3f86272b1f8d56ebb04f1e5cdefa8497510c38c3c6a6fdb83fe2a24e5d538e71d17a07264367713be6c424e4e68083137c1d5c5c9d0fb5c4d81d4e8a3a31363d59ae1725527af0eeae4f6608b601a6e27a316b4b0a3efc6cf7f0d10beb8d8613109ec55081c8e7fe42d01d84d13d4399e9b75f1dd427f2c8f02b0db9d9da5fe1e644972e2e6f8a4bb3ecaca08633ef96d9c65c50295e47240a151416ff9d6398aa965283c5e301e59fb0725628f2f699b10c1d6ddadeb2d254119e2ebb3cdf8bdd6ad72bc9146775b9852ad32f3330ba072062ba97d3154d4641e32d588bc668fd33c3a6bc979460b3dc89870868a0c3175682373ad85c26ab7a6c06d028645e046e27252c02338989a5fb8964f53efc0ceea8b70531508d55f977fa4779ccfda407b1a312c24e27da09684b79d439acf8bb28d65c29d00907c54046956c64e566ca672b31cb718602de02f18f1f95f4ad9c46f6ccd0f9d4c77ddbb999f3e01aadaf172bd24f85cb1b6186de3773460d445c8a342643713da1a70b98b5ccbe91d7df972c389e910618b4b5ce00812752e93f3356c0dd356b6eab1ddf21a53ef09a9066ee545d28dbc214fbdaf3c3e6971597558614d172cc8240fe5de3091bd370d6008fd4fb81fdf6ea466c00626b56427ef8e9eae8c89059cb75c23f43766c8c04618358005626d538ac5250f2dc54228d02e9f132700fd0bf03caeef1885ca001f2b2c61e5aff574a171f1ac920a18c5f2d61da5c6398b2c0402d31e80190dbc002db5434877e477ce3c70966806b7b815ba9fa44f04fe769e9093442a6e63c57010492176b72b8a8475987077df4677385883ab9d7f878c5bc56157407aeab548191efef974499eac182cf9495e2cbf53ac71a5fa1ce34c5041dbf402b2d28e58725e1009e922c4ea27e1c4cfaaa948e7d899c4369e686c7f0cc9151356c3f878720b32b20af3d58e5e88d6609a0340e9880ab6068f7f974a1fd5e639bd3fe16a1868ea1a119f544ebc37bfe7b821c53d387a8121b1fe1a4501b3c88809e96be35735c6f77b05c4c5174d1dd2fbc0801aeb58d9bd3be2763e8c6add22f3ebf18072d5246f53cf753b8a5aeb2d7d0f32230fd29630eaa07dddcb3197c7489b5c5e2aa406f8d8a10165ed13a09d21e4fb5ab72860226cef65841d783d4466260e94125db180eeff704ec39911f2c0b355c76c61003f0714065af6376999c95960f7726796057e22cf62029208e32282a7924119339de2db192b483d2913469ecc4a0cbb68d44eee1006e0442032545f102493876c0ebb6024d80da7a4853aa4140972e50a8859b0b5fcfcb41e39f23132e2a0ea13da9fcf25839edf6a7b5436ba563fee6b4bf7a1de1ccb2c4a157c4e6bbfd81494cc342aab8f24d7151ad1b86d30232bff6be142f2c50db237267a240248d3821b71f79b828336bb8fd0e7813340728d26fc187e9277b66a46fdf919d3ab2abd688043332d24cbf6627342880f7f72aeeb2251807583a0cdf8340fd111bd1c9ddb61dd961bc053d56298fed46a3f11fa624583c76bb679d2677fd934423a37047604141a74e8dd5bd524c90ee00421ac9a5a975a7372c7816a374deeacfe4273ad88811cfad859ff911ffa74ea5d71750ba8d433072d2a66a4c6e61e71044d7c46dcf744cb5554656220b9dfc7ef72f6419e1fffda68b1bd3c31325328e599fcce85ca187928d49b0d61a56ab1794309fdc8370a176d14e4ceb6dfe2cbbfcddeb272147c8356f2618202564d30281aba61444d67ae1ac2b6875e733cc5799674f5a41ec411836bd675995e369c6b729c47be652eb4ebf34d3b9ddf06ef7e20f505ec9ac7f50b9625fe85c321457840541d04f1ed01753db0f319702ebcc4c59a155842804498c5384ae9ce564b86724e8aa0eb675c62f1a7aa849014c050d011caf2e7746c271ee7e066997d90fa424bf16b30873264d8a9958b5e4674a04fd01efbee9476722c04a4f8fa526d965d16e659826d8529c2b1de4e3157f91374199a4e15f5d6b7ecb881a80c02936e404694bce98b97725e36c26bddb28233553557edc3f5d02cc365f49a7d99aba5727bcb0e93e64a66767186d12f0f7c08f139fd94adaa64e04a6de9e4572c2364111d3dabb3e26f5d4b2e0b806e4c5662d7d9c2e543f3cfac19de6f135b78797b72d7b008f28357bee2ba928edcbb2d2f9e44ba8aea7dfa2a06f82e2fd892841a724a2e7dd86dc5b7cef0db7e51be5a875a16ba6c86c2d16f78e121be57a4e6b60275a24cfe8f08b4d7ede8772d959c0e6a284775b08ee88e95d0dbfbed6d70b572172f0fc11976afc531abe2df82b22b7dc95bfed18badfc86ccae699601833d720fffe09747eacb029c72ea06f5bfc3b0bafc55757bed858d26e97ec23313ef08895b2910273bef2cc9410dab0c08fffda407520258d02e36d058c1afc9b5bf1ccb2bf6bfecf4958e2b01f052a6919f96fa73310bb193040d6d994858d332e11dfae6cf027933ce6f9e1852fc3e42d19553c4c611a1b51ae25cce63de24fca40f2c8f2f7833cf08a3291100dfb8b08c06c20bd5e2f7a8cfe25d997076d3129f1ae4f4d53df028f580cf1e8e776c3b3968d26fd534e009fa79d9a23bcd258f7c30102ec319741dde0f444aa2bf82d87bbb679737c864e711c59c6819efe2e56f7280309da2a724a4d7ef4c01888da5cea9a2bafaf05b918eaa866e89047793db0688b9cc8bd5cccc6ff107f8ae3bd086642d8922dd0200f1a2e87ed78804e8e944087ec948313fad50501f9cae61c9107611e5dd8b07f76a606ae1f19e0c6fb72a27fb9df534bda385e304748b7669482ad4af30ea7d5f348ae5b61ead58ffc166454a046a08d47200a2f4dc4c8925bd184c5b4c0fb99433bd2954fb34a01db7723bbc5ac38c72e00f130cd3049dfa7e455f006fa443b7e6d1242a6184128c432231f07cf0ca0187b8d1f81d82edb54b9d369c9ad3e75262c0fcc1b5fae801f372b5e8305115294cac3ef5d9306eddd30bc4211d73f1b046e262233baedf07fa72b22fe6ea76a2c2c7b9d472cc01c2106b3fe5cbe715210bbef2425903bd92f972de5e686a4b3d31f8267aa37c7774912fe1d3b88af8cf07f9086f25f503f27c723161a58be93f38cc65ec69b3d78b2339b1c13e9cde539e5f6a8c4598e1722672c21cf5838f735eea8f0c8702377cb6f9f772609c5c4ab8c356ef09b478ea987215028c6bd87c427973c15132489d68455a920450dd7e044a8fa353643a9aee3619c2f03b9398b9ee1b0bda1cc33caca3b866b0d19a505b298796f5fd2d424f5a7b266083593549aad32e733cbcf4541bc8b44e5fbb376d7d6fc2a3e568f313720fab4f962434e668cc6d9fc07df8ddf285d3614fa8335572128af0c90bc3446bde3adca0cc7ca6200bef575f774aee85d4c6d1a2d0ab3a59389d43ac55f3a3726190bbae6312049dc4ce7ffeadd50ebc47d1abcdc38c481ffca0065c297ef06e6a4dfc6dc82d6382f406934a0916c233132982fe95f8783d9676f305db9a6c0f9574579b13c8045be3897a79ce5dd8d19b2beda2fec4cc9bbc2ad923dcdb6a72a0b27c3fbeacc7a6bce360b8e8dc4dcef3be62d08b2d8d430c2fbc6eada9b6239946890023146ed5c113ec43756f385b15202bc127bdf5d13b0b545a31104072779f8856db5f6cd800136d57e258c5332f2b91270f41fe66f7e5344f6a5be272dde2278e46b0518b7d0e3c5f97ff49ba74abe526c6f67b784be1fa904022367256ed69da7cfa76e5be15eaf6926d053586f2bc759fd12a54bbcb5f2c7723eb72ed77194b807968bc06d3e72f39ff57221bc01e3352e4e2a7df0205b114faed56ef9ad92322ffdadf494d1941e300523400864ea59e13295fab04c7d852c91072016192a00ee8dec9508a24258d2efdfa5ff4b33d084491d0267c4bbfb189837209ac378bff1043ca4ee765c1cac22daa3a20d90409bb4255033c137da979c33552bf18d860df18f96b6d4424214f70ceb14d81779b1acb4ddcfd61ff6ad8f1699663660bc562e432f5b346fb24959a1bf64b03c67696089aef0cb4312138062981f7b02b0e44e99811b7880516d6a97941dc59fbb7af415795bebc9f8b077a2d92d6d749922f7279dbbcb03a7eaaae4b294159e9d44391873494b26696d2da728ac1337f41e828833586d7d1b10bbe2a124f7737b6bc9db072ede31111d4d272a1f6afebd86044031f0f77d948a77e200534a877d78e5b442457910cdcb06d72afbce42a12600dbe6529beeb9955508560a08f387076607cb9c5c22bf0181b72f09e4bc2cc23189c44226ecb43a6c0bf576b8df39b12cb05448fb1b16278950243db80f8414e5a52e8168b56a388e2d97d0379271d1a552c1952665405f1c772ea168af753ad45a8b08f5c94f6b943e37b4afb4ab8d6ae9240a3f51f1da3481cc8b19e98ac9b951fa12896768faf7129f7c4d1b4a91d5f75dcea5d21ac0658728fe20239a853d30d94b9723d9cf8f4a10807868c70d82bea72d45670a652977262fb69ef71caa5c74390f36e0803092bd90f6ac7cc9725e38ffb847b28845f7240e97224952234b94b76ee05f40daefd1f00f69664229f73782fcd7abbe99b72ad52d6cf309490d6ad0575fba3c27d93ce83811985e53287f3044c7b0664e9075cf9909d2fcbfd742341394dda5159fd09bd4fe1f789594b93443ed10942984f7158a95a585855ebd51820cde403ed4b29cdf94e5e2aa616446c733693d783593b7a5c88fd8823894bf21e2ead8d47c9fc5102a70d3bf42487440fa6c93110728f6c2db3b4f88c07616bc8b17f5a034bc24cc9e356968d8b23e9813ee4672325bd133e335e40ed307028a052921436a441c84e0421787be4aa29b6e168973a7228b0fa161c49116dd958a20a58ac35dfa1237a69c0dba8ed79aa48ca96ebcb15fc89a6070d8710bc55ce909214c999ed4740ce44903104d24cb333c309c9757213b791e2b06ae858cffc2d738390027aac953444b2e64528f47aea668f6ee82310bd93eac9c7a02e9e81a41e31858ad021507e870167042eaecbabe356a255720ddf2355ce82379a3908967e3def9930d7c9cd1917273564ea65d636a086b6179e37e541e4105c1fade774be1ad578cbeaf7b82b2bae83e983dc293312b918233dc1d3ceddbbd900fac8edb6339c6208ae52d40e21bcbae7d308b9b527b91b2a3f6f9347ed339f97b1c4c669f89b2d31b3b3391588b7c4b1c3e8b31d55601e72d4851f3a97ac07201823d3e3e0e1a14d8396e94e8875264ca4e42bcc6f8ba10b4eebfd1b8f45ed335241a655ee9bdbef127d96e404ffa06b711d27a746452825574c8a85db242c618bfd0b95a7d58a5e9968ac826e2943d9cb6a2ef5fd4630723bfd87160fbd2fd48bb73dc9e5980ee7b2b0df89fcfac9586b6b462b6b2ad7728e19acc674304f3b887328822d5e2552ff68c87337aa0e110c453918d09210233b1360d9f67245f5dcdaaee5a23eeaf6852fc40d9db7edd7aa6b2414b53f7272fed339dd61ab7cd3e07cd2e28ccb94f606fc95115c4281b6b5bb80edbf9bbf43a386db057120957b6da69c8217dc5a1d0f0bd2904bb0c2fed97687eae21f1962f494ffb845a84add0f99211c92b8df5b61ec35180872914eae2378f03b574a72c5246e3b173d16319d509a4c2169e3d92eef574ee6af9870d284a10cf6a40d675bae14beca760b5e70e2361b09b50dff1bab090190f3fc3ee11c925d19599d72a4ea575b051d8e30e63809326dbdd41320f2b2c4ff769cf186312aaf5664ce725c339625a1c0b46f81944275c4d2f3fd1207960e396dcdf538dd7a5d883f9804a311bb6fd08e86e173075eeb3566e38701fbba79e4381cabaca0b803b2c33740a6f4656333f26c2b10fe8d2351f4f42fd72f426c45c593fff933eeee0b2efe72f392a3a24972c4019e6e8e653ce14b6f98c1bc808f1fb2c0c547094971c3c1725cad23b3886fe1570a590efac0c19a2a5864674470f1745af335ebf016e9b9724451dffcc9fe6716109f8aa7d6d4a0d14f09107a11ce5bf304614eb22ff11c7217b61b4d834561351e15f5bf93f270e990d58eddf9b06c40782fbadab42ed57204dfdde8d11e2a656edd8fd2a09845cb09ec964c6d7fbc5ebf9dc97331a457217724fdddbc411a3783bd6fd6c02fd0e83ad5c95504323e943a8011b88add5522e74afea3678e962a202cb8e8f7b19e3c89f7530cdc80ce58a273dff63bf5af380cb86ecc5a81e2adba964fd21f04aeb1346c497105a9b4ee7c88aa117bc1be7206c28b2dea26df82ec8d714115e4c1e4ed04a10e5f036f44834e269427a99b724354fd42955417272cddfe1d1a72341dc3339b9df71343d6ee738bc4dc71e43adeddb2cf42bef95eb7c8f293ee1686a52e38b9f1d3c4cb7cd432ca1e227228722f8f3df135be3d05f60c88914fb3185761e97fb4fc5e06926c609dd2a36bad4479a7a28829c2ac4a64673674b2279696b71f87bbc4c39f92e3be34cbcfb0894270450084a58af634b27638a4beea704c54d6085bdf88daa25b8cf16949f80872f49db287cefb6fc7ff2a83c342fdf3df842c499db0035f63a54fcd6efa1e571222afac318977d7de6c7e7bd75459853d7d0f9fed75c893adee2ef5d2a8f8901b11cd61c3b3247743ee5b6a5fc0bce48f8be949687cf61a57297b89505ede575544207f83776fd961e84e6b3b01acf40f3a99594b77b61757d856883d532da472fe6a9c57a6dc64ad1ac565b15f69e77f171b3902f0fffc4aee74324ff763b1729a3ded20ef0d312e197f682693c00333a9a180e05dea92beb3428d9022c3fa72cee132b0a9d13b49ae6a3c8c15d783b97d5a52cbd07e47a1777c3556184a327284acf909c5e41fbee5b9f23b93f4b3f43c71cdab261b120b2f258448860d3072409ff57cb02066074af57460bc1117a7ef11ed5ab659b1ab8a5a89fc77e51072ab71a31f16408eb8eefad175a7cf1cbe038c21cba44f0e09bbae804cb3fd1f72f5ab7b89757e29b7045284c16bb2ad36399d55cc9694674e67c2489c60a2a77291b2f0920b0ade5c9338188ba305a15a59c6871dc62659af5b267e09cef0bf0b7b7c2eaacb87832971c01e802cad9f775c8629646849bd538e91ab822d24ce720c0b6eedc47bb0ccc17a83cdaec37dcaa9eaa1dd75b3d134873ba8be188d8972bc345b1124f638b175af1f930e3e716a7ca77b818dff23180bc9ea0f52cc462101fd4e37c7d7299b51032a4d1886ec3ba5ab93d9e30dbf72dfd413814e382072a87b11ade8f7cd4e0e713354ae2cb93e0eecf551d41ee4c6343520fe877ebf075d545fbf9adaa0189006107a75eaee789c986d2c152743fd86196f6e8855094ecd4876f17bc3657e5dcffa8a54c59ad2c92ac8a7e6c4464dff3eed029d81cc726578f108a1d34b496abb1f561634b2c6c87764f97e4b4233c72970d65bf7a71605821a2b0fd31c405137ae0adafff24b66f87f02d7c8e627c261d55fb0985e7241cfddf81116a594e947e5f0bad7ce8e6a0548428b7f833c9b2de1348fac1272d9dca7588e2e71422ef7d3a474f108ed486cd77f683e4edf4f61ff974b55b7334eb156514ba29a7aef23e90da652561f087cfb2e7d5aac57137b61e7a0275672a86d0b9c0dff03bd82bd485c5bb11f7295e924d289ae6b4798ea1a4b7f5afb562991c1d38547f5006b91ae80ada0ff2faa9c1dab0bc0f50a2d591d64af20573b47e1a2adad24021b2ff9c0c3208099a524a97cb5dd991c74e21a1a73e5b75b46c8b2ad444fef3749c66656296ffe7bb7b8f089e36f29fc1a88cdae81887380725ffff9b361363e8c9cadfc7b9cde8a1dc14c29034247ab3faa5eea6b16880072033e254072dfd5ee8838088c84ca8c0e24e8f22dc672b773fcd80682f9223972d29cd511f680975de08d0652e0f75026e3d255447eaf6efdcb6852e24e682f726d19322898e9e32478631e49626927d37537abcd445eb6280f42b67bacc69f722a3f5fabc97cc23467543df807db232a544befd2fa8b5b2fd5f890719fca9372c029a081c217013584497ec8a6dd2ae9f8d5d616ecb692c9d97b643eafd0da39f47280ff48bb9ccdc6835f0a8cd9422c92b5401f6b5ea1c40aa1452a6df0d322221438e4b7bb4178009d9b8ea89f1d7706f15b0af8cd51aa2ba9e3ef35789c724c11d9b6bb94a0a12c00acdd360a50970cdc971e36c9b9c8050eab6ec1d1c372e0e48a46720b937149a0613c4a88d18c2df321b7ce00eb2bb1548856ce642672b7e12bf5b3bd16426ad54c1f9a1ce29f753b44382bd95c69f0be5cea728dd94436323b2519eef6a8ad47201ae9db374213bd99840a160f0199c232bb1f4f7072c69d1970d9d491b9434a856de2738fc862d4337025d216717b5ed5ac8c827672c7f1bad15ce1f891939d33b01aa2ca90077a5b2da2ed9b6df1efe0eb2761907201efa2da3d2299a8b013975af664af955836f622975ecaaa8985f149e5a827729e9ba6c58c6dc48380f1461c8ba1e4a62a0e17403f23599f009893334b7dbe4a25965860744c6b23126331b41fa3479fec23f01d793899058b57e385d56e3b72af07dba37ccb05e0346d15bbe210b49daa09d5fc38b77c3beadd59624f7ae772c8606f699cf60db2cd81824659cee0043fc319b0a8499204c32998a46fffd9722adbcee7b140bd2299a8f49cb6c47faaf92582fefc8923e8ce3c241c2d1ea772c2e76cb4dc7daeb42b3ddcf7c7ac73620e422ae37a3a39f384c56b6d4ecac072f46a1368572b99069c4952d73a03d7fa479cf27404f5a3af077909c0ca361a4be897ddfc10dface3b884ef30f0eee829e74e0a063533ae6563f562572b2f2d1096ef6f6160d8da5d70763269e336c28d9dfd753ca1036b8263d0c1ee2687a5593c58b422871d0944d29583b9ad278ca8afed9755ce9e716b5ee59aaf184a135a1fa4b51963eacec2b3f0743d979e855562ec963e2adfe98723fb95bb70ec3472f2867f21998e95d0e4e5c0a8867c03629e2da379130f2b50dc4f5c6563a53b437943fd3b26ea8755a9c387f4bfb955b9bd8c308d4643ab4d2c7e4505fd636b4f602016aa67877a8877a0ad250b50ad9381135daea86e2a69843730e8c9a9ec722899e4f14f434317f5646c67e122ba9877b723dc34a6a1633dda6e906eb194231a6623f8c3d88f590600f697a4b4222a06c1c55eda8b9129fff17c5d3ee8b2235442537061c0a87a3d1de89ec477a143045da69c06aab291fc120270db3818726e7d90918df80a075fb163aed456ab0491a0fbead2a1241604155e97b87f40236a6d2a6f33ec9fea4db692ce0633e1be850d24608530c4e9f07cc753b417810b0203434051fbe4135e359b0467a3fbc6fb21bc85aa24123dd611ebc07b476e2ba8b1fcef017ca0d8647b432789aa8a10883743e19691e9dba2d9c19a8cc1442d7b5e28b1a958f1e27184e877c5890f44b7ea07d0ea716eb3cf8320bb684205633ea7f1dd0b5882c1bc98a3e7112e5159d64b3b9ada488afbf995272cde26ed72b0a814e0016dc910c899c1f564779fa519c422191270cf56e0debfe4ddff2272eb649abae22df5bd7fcc5ca4b1eee4c9a9e0bb7bbfa1688a92aac9b59ce07a7242dbe7c05d6baac1b96da71803074a5479d86d9f54d8b9598073e4d3d8625a56cf574491fb94209139604a0536db0757e959da1242819e0cfe38af84c4706872d4c8fecabb26d4085d15fc05a47f6564b32e9c7b345926e3964ae21af7ac882bbac2560aad5fd1af1bcb986119a604339656996d87a8add55bec70d5e9049a72591afd11bda1713380958cdb4527a503a0e1f377fbafd673074d60094cd825722dde58c6cdcb2263a7ffd9d16747cdc7c3d146c5f711a9fb1255a545e858333c96b1b20a69ebaf30d2a816981022e2f609ce3b65847e95fe2053cafb33d73b72c3cf78053547fffb901d15987a69821d179de036336ec833befab0e3e3d21d7287dd645ff074e9e23347272aefd866284069d00fa80fc4287d10b74d7be5e13b691ac603d713103a8b265fa4936a1d5157ca155da0253fc6406b8728a4c2ad172823728a0d4cae2b8f0cb8850361c3ea3ca0296113a03617697c30d9a1f7077281ae0f7d769b4eb248e973cd81f72913d55e43ad7fb4d96b2d9c2a149723db5d217870d9d284871a90a3423911800f9d6c43ff79bad2bed0d979d51ddf8b2672e2214cc1a5ffd370c2df903732265aa395edef119422621b6b923ba93a8b3972fa1c6fa35dc5f86dfd838ab78d9e260713216aef55bce716c887339ad813174f20102592ac17dc676e9242389abc36fba1d15a57fdf1f4fdd3e4cde81e797a72aae4ef2c2dbaeadd43f33c3c8675615a7ca64ab50bb27694357f2306b0b6a37292c21d75f842703852259b3d06ce9ece630eb6025eedbd27a28839367966b7244ad35dfa60a9defa8213bd114c670230a167b30664ebac7326ed7936d4f12a2f13f6a51b4ea754d1f3ec086e4492ded107f7e27a26fd5233fbdcb40309e584722bd794eee184d19d9246e166ed37a0948c1c628c7831d5e7ec7ef876e9638a7283aab547c9f74118acba1192cc6c0d926dd2844d372b8762dfecf1a8cfccb8483a7d089b140aae3ea15ec2b2be04efffd3a57389e30583e6544eeed581e6536eb2805b8a35ef48ababf6c09f92023aebe3d14f45cb85c0293a55112455620a72e4fb8aeef89b8eb88ea281ced9984ac874d821e060b73a050773aa98782e5c720c169f95cfd21c27b08e0cec8503b25a4a55be043fb85895429f1413552ca50d78d70b42671a098e4e04c31ab79a5f1de979e3566aa335460aae3c69b9ecb772905d426069e2d8489bf2f0402b63b93739e4be2a201a348e20e46aff1129463984e0940bbc58bd8a646180a84dbe33d03a97f5eea244b56d3385f0f1096383223a39ca458e473943b13caa6833a13ff75daffe25bf8f26777d239b69153b2a0256f6a4582f8a7e6cfb033d4c44559e168e35eeb120810ad61ae5576e0b83dd72e5e2a564f4f130411a4d6a0208e874491bdb562ed1a23446ed2d343cfd0b19728e660d72baa17d278cd0feae697a97d1a4fe571ab9e3ca5be9a5dcead7a240727f5d35463efcc9ac0ee5c95d9c39109a6e45be46c05793f6d7a538cedd44df0e0c1c8c131b89720710094067f7db929d54644ebbf21f152e21af719e4dbc367261f34fe8846dee3a427fc38c5199deaf3e631cc28e0f1732956684df819a53729fbf9dde6f0aa635cb59280bc87aa7286dc87e0121463af80c4567fd988d9f7235e4816f08e452a5ecc44f8bdb0bb9d74535c1e5d3779985c00dd869e52392728ce6c45103a2c9794c7d7cd360e058e2da703f677115a2ce3cfbfa43ee4cb3723c1f45b513eeaaf254297509924c5c5525e8ab507b4c6462c6111a5577ce557249783aaa05d2654fd49a322ec9a4820e5a151114579374dc79521be9e4f6687257a0e5f05aaebda386384964f0913f5e6573fef53681d7476adfab6fcb7c5672c627257b2ccd5bdf082460137d288fe5cac33d9d7f2f849e267109bc449d33726096d26706c85030fd501f82fe502016068c1be6ef51d28157f20512106cbd51b28b2a5f9c99bef94d226cf02730bba151fb4838fec0ed478deacbeb3092d3006587acce1a3d5d29561cb560c2c8eec98909c7c850dddc2e812b72cf4b37f935271fcf8cd97e69e4331f442ec3b3fd1233d5661c71832d777fec2ae8af44c860714c122a6dcde6ae1f8c086da63dfcd213a5fdee5ad6dad7424d906d822bfd24769de70a9f893739e8ca0d404eb9203c332048d75ce02dce4d028ef5ce90a22fb00e67cb80e5e0b2a64e65104aed771dee00fb8168714034bfd6e7d38c195e42a9ee04db6363ac00ca2921d6b1091350fc5f85c9c4f4183622f223177463743f55828811cd013f462ea5b6bfab8ba823c76d6584950d4487eac2bc3619cbf2723f74f81968d1af46dd69fabae693f8ba02c4a90d567c1879ad2ca96af649e772d58d38c42195434b167963dc97d97fa829d1ee16a11756ee4660e7782776af7265772ca9764f7e777f2fdd41660e3d6651950bfc0c0313f7bc097500105c3a729847c19be4d2338807f51f84536efdc48202140d4f0b4cc610c9161e03f44d6315274fd3d627aecffb997e64781f8e2bf0a8ab722c326ef3b90dbc6fc322291fcf3f60120215df0820cabfb60d1b9ed177d3b16a23ba3946d2db68da3d356a72f5ed310c69a3d54ecc2bf0ef65131182d11c510a77f01e0ea82af2bed24057722de01b64803eecb248cff681bea6e7a6b116f5b3f565c0e2bc7792e8bcb0df3203d23cd1ca83e18de87d4932ff14f15f857417bb02e767d82c776e82440456728f33d3000906e8aefaf76c1f0c6c8df02810bfe84e0b9b00d7d58345f67d71726ab5bed05ceb00b567d86ba195bd02e04dc5854b22f0bd569d22050972799372651f58c646df16a82b18d3a7e4966f3216d4c36aab5a33946d30d2f9e068e11a5c83047d2e0572cf747253a570c856f493b81ad5b8d0dce40f38c1d898a856028df6311b772d268b9ffd7e8165ab543fcb309e757cacbb7dbb2eb665a9bd21727bd9b72a40de1d8fc927f7a11d5e19489d8b21439faec81d21ca14be4f14db2db25a3b6bde2adc35932c3ac765383dfed7dea609e64da88baf1b52cf05600a6b24a780b0f3fa8d0bcdc0786dc400e88eb6f372df21407b75c4732463f1a85e72c6981d5c2e4a5f3973b6967e7f5e522f962b96bfa9f15eecbc3096606bec7672a0f8d3faa4926fc9d6b70ba179fe47e78cf3987f4d89e08e297ce9f016f40647e8187ee50d706cf808aa7dac787b7443db34dab73e151761de31578b2fe36051dfae7c72625bf1a9fce9bed43327c643ee124f3f2718ea964d6b35a36d0e3d72960acd04cd45c3d35678f650af0624d9c69fed0a40582931005d6f4e96c84572216083d266c5520631eb191c083a957a88f92e87e2ead93320e040461c1369723967678f9f937f44abf41e09be506d0d10170448626323161090e560e8d64b72f8b8cd2f820fbb8442babf26f7bc4e020d1867b99e9a51d9523dd5db0edcec72874e876ba84c91e46f16d015b0c1986b9974b1fe9607d8618b15c77d74ea222c0fd8f7f1895df39a62b5fe312afd70fec459e5c44ba012c82b0660e45820232f4f78db42d9c73caf63e5cee856a652fcafbcb6c711e2ab57c2eb67bcbb87213f6b7cf3b35f72aa9c8da2a49bf0443eccba30525bd0f277a9d08dd5dde613b102f7b437c8426f451040a6790c332aaba42dd211d0256f1e78c8fe4127ec3a0c721b3587340bbce9fee9877323e3dcf72f733f19f3f79365275507675b9c30a4722aea1269e2315e276697dddc2e36c0412fca5fa8e35b683e44d453af114aad7266029e8b4462333ced01e80411d4ca97137cf5ed5e672dbd77160d112b6b83726384e48711681218cdcc9efa779a8251a8f4efcdea0be0b9df3cbc898cdfc07264769523f3829c8a63e5e4cde60b1d24167c97eab604a1a955e1bf852c3ef358df5630d3c162f9e82312de639b8190a62106158db7ad55c626c6e725eff2f53fcfd3cc31c518e7e3a8ea22c9570e0dd3bad4e0a67b25907625d82cefe60c0b13265d3e43b1487df24dbaccb87ceca6315cdd65cd5a2a70c3f5e8e607432da572a98ab0a0d2f2a01395faf46953c356f17fbe0293954a153a075b58bde0edf33e57039eb712ef9b8e0cd53efad84a03d70bc11e3c5f72bc3e43f963273381cf722b16d3fae1ce25041ceb204d26a06cadf50e7d61091ebf71eb611198cecfd77271adbd914d993efcfd32b68882484726d0d6976cd6b88869695eafdf9f9e4f72256c1894bcaa4a30f339c89b15d3555196eefa5954fd61a8223bb838dd6b445e0ebc925c761eca7c8326803e46790626126cab6a040767b0f1b242fc59128572152310b8bb17b31ba2696c786be731b46ec0df58fb97236af63e96b4d60fe372ae9b722257d199d260c8840e7afd4f4e05c00e976bbacab55d157fc7b69099726b461b2c0a535f18c4a92fbeac6b3449399f1ffc0003e771084370e688c5b6723898d08d546a16a601b2e55cbdc08525dc9144c36189516b4b01ec6d6df1c4720c4d7638b64bf4521d326a064c28b953fad01f8f7cf3931b8d0e1eab4e1aee72cc5cc9efd63ea057d880785d7a36ed2d695fc8e7017508acd759a9a7d7f8fc0d73a342c9fa73718fab56a15888b354169d3b783d0b925e0d805fa4faffbf47720843b3c5cf05c62ab738991758d3588e486314e531e8ff65544f8a4874599d72477e3e3441242dba4b9e13dc9e4b709c2e6a210ee3a5224321c51f928f437b4fea9b2189ac3d6b07e4ceca22b0bb8fec87f80529b86bb4e2774a381d355df6003361e5ea8c4cb3355828a3e38de6234622ecafe8833f0b1373034dec8413a3362cf58969e71a9eae0d62351ffaff449d6a36d7a10cce2c6a10e0b7967ff2f1729643c541d210a788f05e5bb98355886bcddf2291eb14ba13a056ada7d1639105922ccaabceb396bc1933e1559cc557dc751fb0b20634daec985a98b1fed14f42bda4dcc39546e516f0a483df3b2ad8aef80bd923948a40bbea98c4056451c97296be789bf2c8638349b8d643cf3b0126ae16fd2e4f683761e2cbfeb413e8de72249dc3dfee4326281ad16a09b6387068b5e2f0c63a0466e84ecf97e77c54cc72a1e0963c95eeac0e90003702fd05434a89ffaef46541ab07a7beac002ac8dd723bf83536e5e2636ea5579720f41caf1498cd4470081ea0c2b006a9c15c3032725e4d46718e9025d1dd59b0a8a79a38fb52df55d442faeb1f128f67959e803c720863e89c1067dc16ecef28287d1d9ab1764e065bfa4f876b2963ed1570f15238cb463536124537a4f8fffeb8d27ecaf825ffa0f935f9f2fe5d763efd7624d37275e58eac23f9613db7b81adcabaa02657c05f683e2121e2fa370232f46445772db194c79e4ec5821cc6252c6c9b4c016bb1a33927c9fdd6865f710b6246bb1725d88419214ccad796017fb9b79160ce48cfd77f33d65b3966a5e21c18b133c727010e326d7e63466fb024bb63c74c3a72cd0a3f61807001b3e3358c5c66e1a29016347ccab2316d7f3934f9ebcf58327655f48c8f60830932888bafe19447772c6730b1fd7af380ec0c874c9ee340ea8e8d890fca4255b33a1ea080d165bbe3be09a0f515d7c33712dc5c2bf69ba0d5cf62f189d4221ed7010c03ecf8af73a7243583e981d39059df1ec0e9f3d0e00abc939466a3282bf15cc1136211a23001afeccf2b2d7ffec8757b16200d7f369edd019a9b4ffc26083ae5b30116b1792726011301a96a3d3dc35112fa4ae1610c65578a1d4da65f9059f430fdb38bc8a7209d4d4ef6b7fa6be0bf4586dddd642873a05d06f8364058b6044169351b41401812216d59b120124129a31cda1702828a9c6151843f070ee58cfc4fe277f590e5e22bbe98dd025ed2a62fa8eaac85cb18c401deff4b13ca7ac6d390241e4da72da444fc56b92de0ae8940992ba92d9448037ae2ecddc713650d6e9bc4ca6ad7266e02a7b8cb430279ff16004e25bf3b9170a1bbd4ad78ac0214b060e5ae6064083a84d82b88efe80e4dbadd4fc27ff4e7a4d94d8596b5b904b51e33428c3fd72e042912f96931435f2b3f3776a7d0671b4b5345ed7b0c05fdf3ff1bbc51d6572eade2942bea251a04356238fa02c9f450e0a7ea5d429ec83412f3e7e79383f02cc8e3bc7242ab3abea996d413466e8a74b0abb3b7c5d24bc837275f5d049c1631885e51a6c51f6d7361cd5f121a7cda78a9c520d5aa1c7833461c37bd6e34872ee2d6274f8c8369706daa6e44803440189d54470e1cb1dbaa0d245ba3a073245a2a4946456b41c3435d8e9058b16d19ed1f1ca84c3629335563982ee5d2bda7259d8a5da4e8a65ff69ef5ab84559fa010b736e21356d1a3d5c05e90fe5a9ac55d0ff012dd7c5cd5e305193af02a74442a2c60addbec2a3f837aee2d06d785c169d4b97b77546b5fb1f488f0bc3445e8bfc6d1d19a21ba2189969aaeaeed1953f34aeacb0a2802d6947ef744799bbfd97e2cab9e5a94639b8d92debb13ad71c72875065b2812cf2f26a9664c3e90351fae76857af1f9efff29ca872796b69a02abc83ea160e974df3816723ca7bbdf90c8b9650dc6970ed34cf0a8fe928533b72f5426a27f6bd4f1a81a5206aaddae220c0d32ac9ca96a0a3ed5d33ae80fc7226e741c0a363c406d076d962698746e255701c0ae30752882aac32dfca683108311dcad5fe20e867a4b8af5aadbdac601da17b9d1f1df10d5e5ed567496bd52f16dedcf57c5e4ad13011ce7c59cb3b2e18fe73c43d963f1365a2057799b426d06c18f367316bade57b8c30813c06b60651c1931bec9101834821c4ba3fc61bae50c6f882e0ade106f602d77db310a729021b4632437db8943d64420b05a03c2a4c0806972ec43f7dfad843a1041d41d2a11f712da826845f70a2b90a7d703f90504a78c6cf6470ecf58e94676b253d7244924db21ab102f938b125fc7c1165f67232e641ac0395505372b31a37309788d0e46cf44aaa0e90b253e85f702781e233771668fb4f875050cbab768e725401b80634380e869de91cecfe0866a4e0a65b69e32e8e5f409983a467d52d82aa635db3722a6dcb8638fc660f86e90ac873188195e74ec3e754488363d97438a3e577ccb5dbddbe21d86d4be3a00ca7aef42a9a090f6c939770a942bd9b2220001e527e91f45978fdffc4f5a4ba64ab0a2a7246a47cd3024dd7db824832c3ea72957e7835b257aa8919b01991d888449664515696e77d279199ede52a8272b01eafed34c2d24bc4f5c0658a486f32b19e59728288bf9d54d77b9746d601fde0b7e8c5a0bf39375603dbcacf90aae1124de872d0c7532cd76c318396649b6f536d8ebb978779792093de05f84d963b6ff58b2f7c1b48e8c844ea49a8b32f59a2a2de149fd7d20d2bfb7d6d69d153ec4b1be52dcdb5a82c9ec8a58d530b9bdc524ba49d9f676d4cc99e04f527c8b80e4bb0511967f3ab6f89aec51a7af84d2e182dc36fed9430d0859d1692f88ef2b72195fc5e000d4da4aa8b730537c40f548374fb7a5e3558f950d86fa1ac01f91d4ace7c148c21a976a755c4f8d9e71958529582147a48191f9258a3b79beba21996e5f0728fda8e34c60a4f0b6cba76d49c0669a0fd75224b23fd73cd206d42fb31eb4372fd647a6158877f5ded73cf38ce90b2b6dc730b275d548ce997aa3c45948b0e2354bf5583d3bf7897c70906fb298ad39c8cc4718daf8154a8eed7d781b2928c517fb195e68f4a434924405cce94df28ac6ff56203d68b1684ef9c79fa72e3fa72605f2c184344a469c961ed8671019e9ac083b74ef8f1143a7fe6b9642af6b672cd4d09f692381bdf58a23481022e873f3b13286fbc96dcf75ff486cc387cbe1018229970ccad6f25ed432a9622a368f8c74aa383491311c10f48542a9a3b14728b6e4465c1881c79f4e604031fdb04c6f3c04ae45ca38d146d46cd010289912ec8994ca88935689643af1367840bcd43652ef9271f01a8310d46ec65e9997e223296b674004be0f1f8eb2f5d78f14c9edc35b6d3780a9dd847ca833bb12c753468fa27b3c155c2eddac7d5767573c3e08a0454dd72b7c0eee512ea76efcd20728c4f98ed7d12e7acb2ac8064aeac99bac846f8dd0c1ef6efa139c5fa726dab72d5d742770e4f6fa65ee48ef01884b38f8f8f4fa276982ad0ae86804ec9e050726ad738aff8966c1498984b6789f32a8d4811892beadbc03517ee8bd3fe317772149115f490ebd462bd6f167b07b76491ff4f59504ab1d1a3a74f7c013069472389050f826d9663ec08dee1ed4504bc59dff524c399f5a2d771fc28ef9b45bb4a5f76a95768d00c53057d9407975e71c0f5774a1f45780875345672ba3b2d8b71a2a039b2021173dac762e1a94002e5904c40806d983cb30d4248c48553fa9f72f14ca8b79915be416edd334c916231c1d4eba4614a1e8e067025bb52663f2f70a3741f200d9401931bde8716b17579e7eb8cb3b7edd745808158ca78af3f5672744526aa92e1c9cdf214595b24c76969f80f61c0c1deb45b9e83a0957acac37276691c9ef88975637eb4f99ba1731f898ff548b539d47ca87e4385ef748e6a7242f7af24ec95f9087415a06e096a763816bd9e8aa1226b0275beeeb76101253df50bc816e2548a2a6ef3b3d0ccd5f391271408bd4948c4d45996c75a1a7aed720fcea1a78d0c7ea919cc0830cdff06f69989ede321cbcf02acae38c5507eb433d2908a678fa1067e44625595cd72b20fbc3aabe6fc1bcc48f79009e3d741cc68320f9e968033f30310126e94b101635e38e58a7b32fbaa9561314fd4ce895a7200c1f0fea2cc91b95f8a4de720c7cdc86b2fa988520a5e2274c6979096a981648dd6012bd8b88ef35d99f08a2325c1d9b5d56bce864e88189181ce4046db48722cc223389540580f80637053f0ffa4125eba549540c6909bb9fde782648e675bbfd3b2271c7a98a6ae9c32cdea0774ce6f0f82e534a93aa967c6e3836a8fe6725df51063972a38bffbbd9343909e09f8bc9390741329413148dd24e4ac48e072e1eae955a6c4cef909ec0760caf135d6291d98fda9c78f520a39e140cab1ce726fc686ed1e533a823b4978b46b159f1d1b9b02d170a570d97258d8059b1f8433955ce6ba043e13c358a2839dd0dc5a85485794913fafa1863e1b44ce6c0a5963ad137f1f4b61544aeab12d23718272dc9cf32c95450de05db53530bcd59ae2728db17aaab12e9e95c8a69cf78a56958eccee4499ac94456f0bf23bd2eb2c410c0aa51927e1d89edf784cf4f033b3b46ddc8f14b02e7f85a1fb1cf825c86f9572292f7526a0d57aa3aff7b6f72c5debde5b90745518742cf66df54284fbc4c872015c0042ccd4a227472ef43c3984c6db1e8cf4b2a8cb1ea64f02dd55498c5272ce6d18ed81c3f5a3c8351e530ef26dd020fc03b6646efe690d830555a093be72dfec0afbe493f311fadf1994989af73fc8f275edc219cf937e0497868611fb72930bd0e18d768feb9cebd11af6744c2f0b9ae60a998cd85be03fb18f0e0b7b7221311f6194745e381473d5f6a9ff53fa4b9d6d507783cdf3f79fa2da261ccd727aff5382f1875acf28736509e64134ac9522835b187ee4b6fd2fc63b118659722e5bdc0b306b507fdd1d3be8042b0f36c67b2bac9120b79a4a0baba4397f0e72764fc88f693538c98550d54d7f70912c13fec1c3e56e93f0762d3ec84b0c00728a45102b4962b4ec393b5cb9f7fb2505687d6d1fa3ada3d63dfe2fd6b01ea503e6c5d0226142d5c0ea84eef2fb84ec29719f21c3d8315bbe4fc137bd3b3b8e3c2498535549500124658dcf48a7d8697199bc8c0013400542603cb8b058ac1f14f5d44c0002ec9c7c18cd71e9c04f5a450bef85007a1664517880d90d9a13f872e4d4e201934f1388601323503a4d7ada7a810611f146c409c90a22ff0cbca0727461dff6822af0ea73dbcf88fc69095562fc8aadec25d975d35e8e21bc6daa72301c4f7553d9a3c734bc7cbf105bd2cfac219149bc764747ec6f9fa30b13453857682378d0e083b01bf75262e3a4127e4d3b1663da21f99cd06280c78b0add7245c753a398a442b1787e26bbd3974af439f0b78988f286c5b331bcd79860e75f91465c07eb1197f90f7f023dbf35f7d3461887de72b6e9fb62567d3db1d84e001b36b9b444c24583f9d7bae9e6daa502f08a9d15e95376e3eb5e1a7344ac4372b9a42336b6b193e615dafe5581c46c7f66c41806589e113c424a1cc5d781667202d08277d45d1894b52b3600842a6137d4937bf98ed16eb9591263a04e23372703e0d2b625417084a1e919bdb62a22e60c1dd290b453c28c8c56d7b22bac2a723410f54c1168d8e3be7f58bdb880ab51823f839fe61cfa3edfa3466d14cd462ca552b6e3afc5c33daca3fa9548fd2884b4cf69c1ffc167875b4a9877460f6e0f44aedd3de59bd2a83b18b0cffd4781125cbae5577dfd3e94f5cddc65d1a3de2f59efaa6696cfbaeecacd89a68c03c93938430d8e580b6a9ad4a61814f76cf96d28e453c7e77a682906bef8561230b057cf57b49d92ecc25a652421981682e472f1d55b6b6986f53e1c7ae1e4685c26ac63ae944633c9a7515d360719016852629f3c436984547848429116c3a9320b4239141dfc1e264c53260e6eaa2dd1353fdf32820b184e8da605dfcc91f6722da18664a9b1806ab9669c306ed76ba1437237dd2498547feb272b4fc93eb34d9ca5101e682f09c15781077652a5d39fe43ed207fb7650f3cc7dd38b0867125c0eb635fa313b4abb05343bd62dffe72f58723161976e2bc800bc4aa03e25b5bd0c5a4530d1161dc2cc3e5cd283f62c20347263c59fb3ea1832f151840675e210a722141f6d1d189d7178eccf70b6e5521c0aa2d2d3b37577a8ad8349e6eca10686c86016f51061a1606c5dfba9a0c0ee387277b42302dd030d00700a2cca0cd2ba5ed3612338fe0b3c025a852704579b777232d0ed210410ac9a852ca33449899c324973f10fb190d82df354058046c5ab721365c4b6fb881bd02a98ea1b45d003c268b5c64ecc28b0fb8ea0f15125f11e72b1750b4590299611019de84f7095b7037411f53e1c4c0c9caf418df35f5085725320d7c7399460d9ec59691f851981ec7c2bae9e397cc61152203dca4fec9a1b2d314896d68466fd8c39c93dbafcf8004c7b0c330e895ced0d47e6a4b6e39155c1865e75e088be1b9a40ae2b9d5749ef4d371e464c98f876f35467eb23ae5d00d3469a2c88a742f5d8569d9f9c53684a0a3383f9e06b753dab433426b8419d72de41c6d09f37057165ed03120198078b198877b6213a96db3e8c74a990957735f7d497e764bb9c318e33a0a6afb4d7d075a61c63cfc56edc97118f75de71c56126f96abc96f2e420b65bfaaa8160a0067ede7ca2a332e3c5af75489250146572ca825e43d4ae51d0aaeac9197299cf629716ab69e9e1ac6809c17b68b1cdb06a302a23be8d34e711b361873f4cbe655e749ba4f4bc1edabbd7f120dfdae71b723c1fc372ec7879c81fa4d5eec69adee92920e8ffc170ce849c79909ea96e6d2cbc7c58953682a6f1700a47b4508641df5b3c67230f4854e7344a1c5e1277c0001cdd0a4543f5969b8f874a3a1ced4a5bb76f829f12f4216711855f93db055031ea97f7809cefb26e13826dc14c2c54523e022be8fd887de1c762bbfccc7e98569887f5aaac9ac80e7dc462e2f92dec57f91d3221cca64d07a87cc2bbaa4acd3ecb0b49aedf5ab50420d860efd28c59fda2c7206a40a43779502f52a2e7fb6472fc62ca49ba43f8012ca83ebd506016541a64b8acc4c22985247e8648a2900f09967ad865ea2f2a43d3905e41da7031e90e80afd3f39889f8e8b15a1f8d7c6b1fbbb73d9ce9570a53606e5b3a2fe0d4739a10a0e7c4bf23b4397872421529ca727256077e39a30161c448ca5fd7ada7ab0aeb942354204f0feb90d752afecb70fee7f83a5687235f46061fa812ea8d4af4bda2763b22fa97f61a8d87a4ce4856e15c3bac2f0d14edda1731e0b8b28f585dd50b2f6d4992de2931e2537f29a89399d0068f129338e726619e3cd7dce92fb28bbfb5f7202cb76349c2b646df080130774106823e5c9a1339f45bfc749f9f17d5cc168ca8409d55bfd4407c4e3f471959a554afafc64ab1f5f7d4ce55c5a5d609b41f4263527f51a29892d6df642726bb8923fbefe3690161bbe7f93b80d769d2a40d65a8aa4b32553c3f4290f31725db2df1c4ad46a7a1052b488bbaeab273bab531562b7a42008391f74958f3072e04b198103a26f6480baca33b8c5ae6caf95d8df672ac3caf2a597d108aa8c7233ce577c7dc687c14d541614b6e517e8da104d0a1e143c542e6fdaa35cd9896b18b1b773a50e921edd9b4eb76d8743b80349d3ecfc80249fe716fb2d711fff723149f5cc4f2c2f2367634e295ceaa6caba520538a4b6d5f65d49a4550edd6a382485d58fd90c0b1a71bdd56e6791ad2d989922a1e072c20e785cb69e5ed79b48d7557bdb36f6d9507b7c0e23d67ef78d2861ba775d99df90ee9110435f4b0172f402f4829ec8309c6c77861813903ad8f75c6750001b94fbbb0ac387e096430c8bc84c7b02b45b655abfc5c780892899f3d028e70d0ce6994026c810d9657172e2d971965cae4a255dd4e2f83f8059738ffb861d253f418d308cd4edf3b8a472ff41cbc12911fde11e78e9915442a5763b0c944154556a94fe7672f4c6ae417247b4c1930977f4aa5cd537e546aac5a8cfea319974ea69ad7a0580ed06a80d1588092d4a168a757842678482ae7321015a0686515bf76881bac72e52fb3a20721fd627f19f8a9e7d0cedaf61f7163e7d36ae089d144e4482117753407c7fca72ce2a55579d3b897f3a335081d9c5553744c306f3092ec179b4bc1c2e53b2557292cc3fbc765a65b2d7436a93c43c57f5b2f2908f0a008ce9de582290c5d3971d98d434d3145b88436b3c8a19ffc2ce8952847ec5b479b951db412dad46e54672e226af9c0df1374dea55292b6601a9a7d3fdb53c47aeb0f2611da85b6754b44e2bd6e9f324caa8c2f918616889165a00c63029555147ae1103d1ec267796096ca3d53f2c445e7fddfad3275007252be1fcf304eb883b08a8a71f8d8e6b0bba722643dc9ed679ed0861d99d23337ccc433615eea13f86d30bb49afe0dd772cc6d38648d6694e2ecf72d8582b1014ecce06fd407298ff40e2cd5ff40a306af1b7247fdae3a741be1932cd6f340286e8d4802908874378c137008ad53e3f51327444109e970dfe206f6d4166281913b8fe289fd40ae891bd7396ce744809b808b17cf8e7f78cbf30d4b2b85f90edb6d4c287f18f91c14d66878a36e74b432bd4272c5d4eb53d00fc10f420bab1712fe667cc4aec45a22b747182fbca432f45d3803781118816e07e68fc0da881882fff418ae933f549788f43681401954bcc7de720780e9871f88b360c5ee7af50865a2bf66d1907e082691730dcc0dd5a6e03472122700876fef1d915861c63f001bdb5aac9ac1d699b2008b77b49efef873f77296937ba55abfec1cd4f6f0b53dc88f24b3f32afdd10f0834a5365ff36a732f7250bca2dc09e044adbf63ea2fae150cb1ec22b555b8a1451290cf3d4a8630dc4d0c9cf4a9de80c3d54a0ee6b779956503d1f24506c1f9431728819c5995c20366addf7d6bc7aeed81db50144210bfeb7da60cced3cc1d33eeb86b46315e03547202c93e7c9628e2e8526f7a5aabc50e6e9550d2741cabcccf75c2cb76027df838193bf03f4ee8be72a98948751a2a2d5ceb40b282f17e6f9da58a2a7e6a93dd4da9fac6c5216799f4e7a8dbdb0a49c2e3c366f1beb4eb9e210cb6a80dcc6d597263f93d786cb7f0dd617530ab53557df0bc660c2327e0ec87335119f6b871e30341c2b4a615a8d4ea4a54040df989aa01f7d37ce20074d12768475995f3b2557247d2efe62798002bd5a086c9bc45b9b38e541961dd1388fbb1ba388d3aec5a72be1c7e0b519689ccac4c981516c3ae7800107c2b3c121cbd1f6a610650808b2c47bd4659e2064c8b0b5b1ae1b1cc8d69277d4c9e0160aea14a43cff2c3451c728a8cd03c0bb593ff4ea0da7eb0671ba46d4476bec8a51ddc5ee9b9c7a7e8d872e8ada3ae354117a589174cd39d379eaac545cacb5e5f7590c6bb9cccdadd26053b0f7f76ee907322199d38ba7590c70a659b7a090938826ab95c1f69cdb55a311775c5f0401b26ccffa8e8b065d4c35a6f35d0c75a390fece37a365bf6c40d726786fc789a919dcd2bb4f13257c7152a872e7be4051fbe4fe3bd62687acac94a23a932558b6282ce97bce6f271e6deb1324d712930614e361d4464e24fa49072af58ddb1a9d8b0b7ed27161bc06cdaaa2fbeee7a84ecfd65c76a4c23e3f042045300fe704e83f7a4eb76c3648f37663a8e942829f1721aca42be0c7dedef5a1627df7094822bfd45e1dbb824faf8df32cebbb5ca4bbd3a55f149181743cc09728a3969939254e4e4f17d67ac44f0af2366a1148afb1c7a06673bb124b8f5257243f98fbfbc07f18f97655f4a983c46f671dc9283bebab46358af30aa52771b56cfdde9c22866f2fd5de32280f5bcbf89a116fd64137759170c6e1f1ead10756fb37e0d57606bdc386e71ea084df2155af28e0cb7be0cdeda4aa61f13997a997216e38b7bfe3cf8462c1e347bf131bf0e8801faac934d7071f49490713912127236fa330810a62f4fcb3c84e7b6749a21d0943ae41859ea390a8e23093629a658de958ba3048348dbba8035daf601bae440cafe64e0368187a527dc3a18273572286d04cf941ab4faf1069a905c587bfc63b893f32769ff47fa653e17fc6b463cf434247c664d34e79b0cd25ed191b67047915e00443b696399cc0be191666272a4821b39ff17e966d4ef0273b442a2395df438912530db58f1c71b782c91af20e4835b405effeb49cf1c91e9eb52b09e693d3a2d9f65412b498a4c1920fcf9729125a7bb34babbaea3a7fe3cd62c5118803d61c77f676e337d175965cad149726f95247fc4b39cb0fe65f2bf572d707d1f13d939bf539459347e4639120111628031e704d9b1261e9ef8f9f4dd1cca6ffecb59b23f8833a5e8445ba723d6d5096b8e30e0d69f00cad4cde585e2d1ad8d16b5663bbee7b22bfcc6059af213624a0a2f9154bcb449771d35c73a435be1f2df9a30bb9783580796b6128e74c9f535b1c937f3183a21fa537c114f93ec654cc043fd056b64587de74f248c387ca972cdd94ee5f3bcc64527da0ed698ccee7d9b5022e1de1092e48190a3dfa59c10109fbe0e40adecc29a5d7c499313edfe603fffbbf37fd288465e9ccfc0f401ac72799a69e0fcce89195270ce19b57d5d32663192c78c1155a5aa06dde504f4b5728fef43b6ba8c88f9b66ccc1d9e7a2b68fbd5d4581cf8a49e837f074d08cc7072412a462eb170b77217affaaa716b90a58eb00f325840ea406fa054685ff5331ee2f7bdbd66c759743b5d447d1d41b5172035db99e95967cb9539df934ca60f72ac174542f74cf8532a589b2fe93747d9c4d7aa567d1e1f7999f7ed95b9762a72bd8919293bfeb9feeedcc1c0fd1f721fb3c494702e899691de341417a6e32472e02f4fa936173ccd1d9196795c2618028251c7641651840edaf1456fcaee157213cc3df5f8b68bce3e42b70695100371b2bf555ee8e3d162532dab82f86ec47257a00ab89d84a1ecabb7cd586f3a7b6a026da70ae46e18b75fe64b082bdd2372ca49de586f02ce004cd3ac118f89dc7714427dbba0ae83c389b29635b7b23372949fcf9df0d43a805f96cd94f59a19332da3a1cb6bf10dda985596f560c2f7721e353cb555aed71862ae6cd5af03a8268f49483bee436c076e8a4cf38d3c81729a23c4247499bbc8f17bbe9657ea4d45d45a1138ebfcbb5799a9a524d881714075ee4925a157137dd6a4e688342b254664af3a7f7328195f70e76f5238687663a7e6c8f4f67ce4974fc9907b1acfb790602658ae474747b35c926f845f6bf772561ae604343ae849d7d48920d75a55bbfd98095f178bebb56b661c15470b5972b83db9dc95f2aab4dc128dbeff6a1452ec85bb829a8a907bc451f01cb2308d26031cc87d66364155fcf9ccc714e99b28d63aa37e9455fcba208ce7116d34237294d1fa576bdbaffa04386875a32d2937efca79f5f42f6ed23ecb7f0d345253723819e4c0f94f95706d5eeb0aac1c11d5510092e89095b1d5d7f94ecdf89da4477fba8fa6371634dac12b74a680925790d6df4e4cbd471976c2dcb41761b50d72874b20d6271c954bbb093e13b3b2ca0724a990472a7e3ac46f59930235e469233c49bcc757c5b0260779abb8fde1442a9da6a581b4d2871e3245dcb4d4ae9472ffb7aa8466cf88d0d8d4386a529294fe9b61babb049b5f228268cd0da0e60b02099ccd12cd39b9576efbaed1db2f1753ba54014d43240a18aea4b9c371e1be17be3b1154c343702ce2da0da302fba62d1b8612742add4e390fc28abfbd5c9747a5f964e1fffd6a2989a73f0e44994a4b9ebaba20ba8f56aae5f7ee77be2eb656662da1a97780090636da8072f2c2df33b92f17d7ae08bc0b2e85b3488f971f724541b6272526d95051ef2dc1689a8f2d64c5a53f294d51b9e9bd3e110cda100329dafd5f081b71328580fbfb92aa1fb8f60b5a70a23aa1bac1a4050c8bb9c2720759017e9a6f297e511e3b18f1dcb3aefbfff8f1b3f0cc3d718b04ecd834041d42ab750bf4d2ec5bda5aac546614c70c7aa5bc2f6a982fd6f6b291eba6516472dab7d96f5ab779cfd371d5368d78d8a68ceafe005e6e0d03ddb370fbee14a50ead7e3dcddf30bfe26eb4a56567ec41d607c830dcf3fd61ba24fa010b9a0d8972afa7f157624558cd2ff4d7dcbff6dda0338e5fff1981abf849e00cccdd5e32724d70fe18de2cba4831d21e1b3f39afb13a0c6a39a6f64d1303f60a2e6b4c9e4d93f368ad5fd55048991b0e2ae8fc134008456854a996380717505e331d9f7c1b18d0566bc2e3542e2019e877c045521e7fbf7c7564fb14b794a011dba9acdd72282085efe17a221b53a0458e3f9a8057ba485503da57c20dd2aabe1dc317f672fb67f75432077214bc18b5373442bcec9721a928390e01853cfdfa2be16608349b9b2ad900cbdfcabff2e918a5d5265e14f624c47da240e193533364049942721ba5af42d9ec998bc1a3dc4e2ae90cd33c2043649eea8e574612c63f523e67360fc9a10c940486d78d01d068b9946a4d1e0384f6292bcae86282c6610a5da14a2e84e4bd22a722d25dfce4dc69d0f3ebffe1d6e40ebc23cf380ab2d925a8db46015c654e1f031d59e6bcc7ef1aca4e36dbf186111f3ec8cad43946a5ca5a2c7221d34aa4771e6e8e165dcbaca72f012f387f6012202291da1dc2da82eab37e3006d713b44028c3256639cdcbe3fe8e4811b2f716b782016b6ab856dbcfe135381654882bbdc02381ae2ecb782e086be6a3f6197c8a5863a38532aebcb988be727f4c5a546eff397c06b71d2d571b30f708f4fc58b32db90f74a358edb886d611618ae4225898233bc264c5517d7b0e8fab3ff9855c77da6f640f3789ed8bb520641171ca24858dc6f102d6bf78c99a39ddcea4c9adb29dd9195c83efc5573f72e93f18eee96d8965677678c0e7c50e2eaa54c0dfad2538b938c5b7064cce2a72741e7a18b6bd5010171239df68f43f97646a791b9c95b37a7cc2bba3e0d8a472e1a69a74fea117ba7ac2aff62f2dac74d134cfaa2f55b883bc283f5aa0455a6db683b1d1b4c4ccf22447bfb59e1e4e073f62a12afb88ede7cd536000c03a215e1f6a0e5ba70fd237d457aebe048c9d03fe301f5ad404f1df83420a020892d872062ce36c81534c7506edafaa08baace6e23c9d93436d6a0ee35b719c679748721b6fd34e860c782d81ae1e24f4bcd752880207d2c8608376c653c9cc28e60c6482c5096b7177fe063f4f3502c50ad5cbcf24c7f5d124c02488ffc9416a2e2772bd42526d3fad9efcff5019cd90da251070fad09acbcd29479034cc7081d6357287e4e1c5796e1885141c9fbe23923916b521e7815603d9c95af3c8a784086d0b619f694578983aef1a684164f564092344a22d2fce08615d8fe25bcb33c43472227558823d50c3ac77d0ec314c08f13091ceaae5ad44ea09aaa306194d2bd32bee40289aa57c180aa5dce0da9c24457ac76c502e9ad97343b5f4dca5d7a391129ae990fb7ce01d8b8b4c047079646999e113c98c1eaf175a21f7f4f9e58dc3723f17a6324b1848d3308012500b743fda155e0b4efa6d40ddbafee63461edbe72499ea16685b5cae901cfe4b09b4af6f845e02817601c79e6fd3c8a7a284f6472520d5c4f1cbe2489a666a31ed5b759577f2ee84dc9e66a015c5e417877c98372197404c1308765fcc2b16dd994f763d224c9cae8388cc9c656b2b3e36be0745e04e8641f71b5d030fb2a14deab2fa9feb1f91a1eee14c83621a4435a91f204725bfbae0bf6bc26a73e6f06c55ac60605edb1f3dae95beef97ab9c074ddea717218adeeac6a2e84296bfd6e185cdf630dccde2bb3f2f2ea235e7fbe49ead3aa7231087d833927a4c6f93a06fba963eb0c8bed56ee4f45c4d22ab21bfe3008e272244e5c88a01c8cae137b882ebec1f44545501495e640410779dc32cd712c7f6b2a2fc65ad36981942186673d55d2deb8f3872675850d974552f168837338c72787f216a8cd101ab2b117f3e5c97bbe057c695f151670f28ad70824096eed6257096a0def73e86a5b946aec7ac081e79da144a4b8d912df3b83fd3b3215ca5472ce2f4648d2d58f23ddabd1ee769646e5fdc555f0aaf8de230a8bfa6af3a9c772904399f20278d8009e8097340fb2611ae8b3aa16e7c5f283c5e30a206ebc1172b3c2be2ad76ad0d480fe1284a9118a98933baf451158f0e31a659d2b5cf91831660430ea8adb2d210bfd5c6e2576f4df5ea902cc4b1e3cc21bcb5d1e762e2072761c9951ab7b6e37f21856e8b6f17e5ab04cacf1b0cdb0a88dd146e5303f5b4623be9a3f0151abe9487b08803b66da7ab16d96ce196f3a80b9cc7995c13d75201ff2d96c0530dad5427bdca1233499ac72ec9800cc7e7deb61b26d473d91cd4b4d3cf60e23196f431f48e7f39923288c0f322ec7d184f1282e6387df14575b721565cd8ffb82bd720096ccaaf4129c361595dda8b4aa2379af08951fce406d56d6149a62f4355c60f064a8266796be2b44a31899738dd8f02f32439eb2f7da0f68da0eebe89bcb8194daaba1d1194e6eae6ca259ea7a089ae2f9d711f3e00612a37bd574a428d167229573021599a87c01a2328b8dbab4e8215c07c62bb366721e00b393c3ed20a52bf3fb2c96f9b865ca446742819b198958dfc6c8e6bff772647d936fa26392e6d613329b39bf360e667bc249e2a38ad54a9a3a88f0a70607c8504a36d26071a9d16b02a324714f4447e60a5268a20a4f8c6cc93dacafa972d4a7288a52e23c03f5aae0deefb9733e4557616baed4b40db26df7d08cff477245638820d5fe68a1ad1df3da580e3c620c55dacc7f7033f31057d5ffffd8b86c985aa8076c4322c43e9e1db0b067096e40e6709710da738c0eaa175ed4133f35bf46b9835e335020e3024e41cff2886dda87d88757b77b0324a64a255947b029e0decb88a2b2520ed63c7d981307ab590276ce1914f09e5251d6d0f25a482172c2697e036e20b9a148d5c6c18db390a5f2a8af4367c2eb6b61bdb64f0f908272bc21462e62b4a3726d8220d823685ff59dc3ea4853e647a632b47cc24a359834ae1f8822e785b0f000bc25113872fbb0568845c801ed7eeb3ea3a5582f740b374a4c9593ceb1d20c5b5e73f9927ef05e14f9e17868d067bc1b094deb295be87250b3b98d55235cf22db3839cad441b6612dd23bbbebfcaed43af43ca8a33d8201ad26bc67eef68c9a9bf74fe3485220728bf1b8adc94fa18a08327e60142211be28cef5cd0ced7ad6d73368e98e389d5ea6614363d20578b648d02bd93580b72ec7cc0e6d0789a846f31f62759fe880b31cb7a733412dec832a6b018c8f97572fae7e7986a8478458c0749c279a0142e511eaed09656ec29868b7881c2eb9730c76d18eec47d9841003f890ceaef284fe52c4821da78c2242fa436d7e0e7ff7081ae66983214477d5a6503907f497d8c28c182c38f2acb70c60a2ff027e1820abcd12436a56b7d20555f204723a517f9dd1b5a63ef49752d57108f48eb77a972f5689fbf8a6776039e00372484b893b172d730e6e4aa11bbd816fa41c6cf3326dbfcadee1c962f86dbf5b9566e4bff934f763933f4df07135d32de44f17f415259f0a28666a8616e194f847b0e4165f398141be8a78ba754a4835974933a1f6d9c2381b9c4f69ec87284dd61e8691e268aaf49d1b86abd0630cd1e1388e64f577933839abf9cce7397c63451c233121e1cf4af4fb671e03e76b0f9e69c2874725c22a6f48605bdcb4e98047f67c2afa8719c20db7a7572a681dddaacfa443972b2b2268018b78a6c427f9ad5afb23af90b9c56bbe9d4060633221631f9a91468dc3f96546b6365d071939b7e129cce04fe470606c1535aaece31aae9b8ff794da73103ded40230f2003be1ec648f7aae5b7ba366d8d2f5b45246ff9eaa12ff601ac4147ce15e1958e68ca47cb928c96bb4acb6b3e9a747408881da9826669172f979c89e6665cac545373a71b73c452b9c8ec642a8e1614822a2d3d567f8df150b1d06146c72e576a36a37389c6ceda7c38b6329d31a94e2bf1533f05e7d75513257477b22c6c1d79f3cd9ca567f28f73e489647ccf937e40f0b99b52e3ba7725e3180d7bbc2868f57d8b7d49786b8dd15d091a5965eced7c438602b95e3f77222a34761a46c263f3742456edbd8fd6412762e8cb5231725df3292567139b372da6b7f01e9d2d96e33d35d9c73e4f60cbf178f6c67a48d59e10e7e67351d0b0a23a24400ed2feb62cd1fdf741ee8dd591d17978c1b0739166cbee0cf7ca5c2729b31e9382126b3ef5bd7cc735ce0aa4c38f9f22afeea3e7d01ef62ba19b57f6ec3bf31fa95f296851aea4306ef34a946bc10b225a9efaf36f386ff8c9e65ed72a36a32a8de26a643c631fe2ba7b5ce4f59fb274182b253e7a62599018a0bda7053ed114de55505db0c9925dfb93bc1c7c2fb2b69bdbbfc30842d8b4af037e572b435be0b856a7077bb6dc2547c980de2e2d954fd7684bfc74f58ee69150d7334ffa3b7c494cda56398a207b705da24c44db105a01e6ceb280decffd74e88ec720f90961130a5a62b1b5f601e4a063ba65dcfbf1274adc144a57525e2d2167072c6f615c5873559143a6f86116a2471da232e9b18dd7f5a68efa372dbf1e1e072d5ec14c7961e97a5cdcb856c0c3e982cab15f11b38b60e97190dc24364666c7201c97bf751f05c50466a456de02801ca49d6985b26476bf1e8dee7aae248297220a5778641cc94649b96481612c7831359d1470a96eac3ad65311083b15b197280051a6d230d5eec9c42347ba200873283a51b8358d4560b5536e6b4b0408e02ddb590ccddcde61a0cef13a3acfa964293ddd3bfad22550c57d3093d78715202fdf7d8a3d1a9592719985cfd5bbb319193c4fba5f81f63166c00061e22cc6d722abed45bfca85c25e668796886d113f4a1f9e7fdb885754292b988eb474b0f720311d2b409f4691712501a2abb7e1f45102954d215979c2977f6e2ec69cb933b0d7829afc26c0c454fb86d61e92a88acfe58a9ed3929be347e52c55db816273db95fb02795de068ddfcb23b3138942d98c01da6ce71c09ab89de2dc00e5acb0a8f18f2d0870248c319472695797ecad584c7083342eac0c032c1fd05a3e094697ec7cd13d66c0e67ec712122bdd400e8333a0665e705b7ac142655239c761d0d96cb5b1c92095af5a8a1b738e1f227a3c5a7d364648162f4af3116c9ffd71f02b9c9c15cea2b4042edd283c9c0a30315ded9a82aecfb6eb828a693c0b0a5fa72c285d497938316c7223d922cda0fc59a684624a926f47794347851dfd6383c04cd57140cb17d7b4fa0dc6203cc18a3826f339cfaae72e3ea1f5f88d4fe13057286c7adfadc8ea1551a43afa2ca0bc874875fa3c0184689982b54a3d422ba72725189bb5e10ecd9dd2e72c2a477195a81394e3073db1bdc7e90751f08303cc5729f9cb04a3ad778efb5792557c5092c22cc49888eb5b8b3b6e363b2a5bfd4cd1bfd1fcd701629e2661ace540b848ad998fbd3f17ae99b198ccb83cd4ad1be8e03ca1c5d53537e6cd425ffee13513924ce98e22f1b084598a014cd45532ab57306bf8af40d53ed42a157c65f549bfef8844af32431d82e8eb2a565762e44f2ed72ad1946926ba0b82d16c90fae6f6c0ab7bb614ac8ddd0fd45c92bbadf9a49c072c03146e8e023755ed7ef38aa748a156ed493b5751eb9720e60749afa726adf72031f83c7530e6e68caf68d2920222ec529c25a73483fbe5e3ccfe84368c497721add5e1577839e6584fe51a0ae228f9cd9215b29d8234b2a7465fa6bdebd3b72f896df5ac82635b9f331ee60ff1f2481881a81d1c6fb48400e700ca8a44cc55f3124585a797b7dbfe6986449caa6e84d4b941b6964f25ba1bde3735fd0395b72f57eb8767a393d1be1587de5eb1d1bbf030b40383ded0142c8cbea327dff1f729c27cf51da8731b18a95f9d60bb224994e2ccf047f9f1b2ba51fa53adc9cad7284c185591ec78214e4e7d3ddc3654aa9fe1daef3f2faef352a8c7761829471721a530deb1ab144f36d9177869d124d70b2c91951861b7dfa2a6edc54825786724972bc4117d5dde86f8c92fa5cdf3b49f30d0e67ef20a6ee0081a7a1c8d9667269dffc159eddcc4096896c2c265f743e81ee9465f00dfe23bc1cecc89e6944729bf1a88e8ffceefcb8a38861c66c1302c919bf689397289578caad786afbe2728f5e87a4dc8fdeb1c393f9b28e02e4d83a29bcea8dcd8037954e025270cc134705049d15f619e213c2c6327ecde26db52791871525da12ee36a05bfa253b4e72114c04f16ed9e07bf2eff158fb8af8469e94d48a683997e6c24be02cde33a051adb9243abdad53a9081cd03474cc35245fc51610e5c7c142ca8aee4ea321a1724bdae866660af3c9f6a1b3a140c539974faac276227defeebb30ed9ea778063f2a664d4c3e519df77646411c869f68e399a6473e579d20392c33a3a2b6482f725e944d1451a4b6e7a2b672a35731f4c6749ba71ec173eec9171904a07a729d72af9dd9e8f26bf93c50c49e0dd7bdc485c74371c3d8b8d8951d444557020ee92001a95f69692e704c41f832092d3742b3677c76d222d7cbc81f33d1e5c336b07220b3cef6b475e921f5136f0b606ff86482558506e0c3a874ee21620831af1216a862d84fbf67de23805e5d761e5415d20ec406a49436516aacdd6064c6bf8332ed7004fb25b66f2f8576b522ab11a98f1f733e71657fa377df8f27b973162651750af5d1fc43fc0dcc48521b13519df7971b3f56c327eb30fbeb4b6793930572f73f8aefc1e87ec5e07aba212aaf99d2d3cf676ca525b28b315a29e2f7c02672cd0ef9dceb57266eed7d0e78865d1e9a2c58857ba4acff3c77b223dab64ede1f5a5ed965321d30b10a00f84787d69fed526e66340e478d5098c593b6951eb072dacd852aac6aa545d874117f092030b363c6c6cc705cbb76e558697a4441127214098edd43dd01ea7cda73a2443cb2c115977620cf36c0ebdf3ce510526b066b7342be93b838d8878e0f8ee1179d26a8f98fb3f7241b3dfe4a0faa5f0dadbc72a198d4d481a9a7421f2a37435d7eb6d2857604ceacb711ae2572d4c9f1e04a1be354d77c080cdaeea4f24cd551a82738e1a9dcbf593461ddb73d359636c02072833b7f72dc87c4865c9d355bbaaa485e74961dfd4a7bfe421f19f849c1d55d6330acbb6f658a05ea8fd05dfe91bfd9e3983cbc10f68dfbbc80c6e64c24437b72a98edbccc0f4a6de6946c7f101229b0d878f0c539ae338526fbacf1e8b4b2772816038ab89d8faecd6ca2983952e83d33d4c01e5dec93a34380e7b9415f329392de494a007fb14ddd1fd430cf00be7feb39b5781d2b6159469b3e8af7f1ef5729de8a7b5ad49db652c9218173c5148acc6dcf3d7318afeb76aa3e7be4431c572ddf0e0956de13652cf1766394a836125ee042f75ee34f93025ba1914d6308772db7db2a7bfa089cb9787133440667b4f299ec88a109d874831c9700d71d6211fcf6a3fad218bdfc037ac134b1a0764ec0c8b83b84637874a50daff930d6b44724b69f2b98c86221548fc4ecb43f4b536ec79f36203a84502c08af48129dbca38745b96693157f5a6a2d9a83d0fadcb397bd1777adda330a97b7e4383ff0aff5ce646ee2bb3919b4a430e9af482ca1f149541e933d291a2e66a8eeaa5e92aa55a0e88770c617f3a2dc645f80c80ae6b749f9f1f316cce431a3c7b94ab3206537276178da80179a549b62a0e9884f746168e19f821f332c6439dcce2d4a137697213740451deafe114bc868897e6ec2bc86ede7a6774909b2fa6f7d4f88af6ad72ed9c64ee91c25a9be63ade53fb3886f820eec51705c50389632a151ee156150fbac0414201ed9fb9bef32c1e540a4bdbc8e631895c3baf2213008699e9e7fa72d012d5f9c47bd3ede9693724e9753653a7165939ada6b1bf30d0daecc7994d6fb6b1596c5c2022f3b90577daeff600d634e15b05b86e2d75d1c42450c9778d72239f2583b2e11f776a5b8e78956d2bb531db085954b8426d198ea5b18e95ca2bfa6166554f664473c730ca956b658606f081322fb6e22185c7df87aa0cde903e2e7ba3904a6f21a59fb40bae2d581195c1308819517961c64deae46a6bac026c55d30b3ae118ccfaac2bb3f39d04e08ba0adb93274de2bd23bad024f47a2b272c6c23f0011ebcc55e634cc107cd512a363a3a04b9e7b16a471e4dab32ba99d48fa74a2807c80bc96e3f5ba5cd253ac581b259e3cf39d20c018a76268dc16667230625710d4dae71c935ccf5add089db6e65fc77bbb381fe6e768da5b6902ee72e54f885c6a36a04d8382e0ba96d5a88252f72dc3e9be5c5bd6c6231ae2754d36574eb411cfa5eae966d5e264e0d09598cbf0f6a9fed4eae666656b6e61b35901663e6d02a6ca79e6d7a2d9757cec87f5a67b18b58f454a174b35effb0362e85b3bd6e3f6d33755c06615405bed0d700a79b37826a27409792f54b3e6c7d95d00216825e3b9de5a79dd578f5fdcd6edab9e50eaec80c4398f5aab22eda9c72a72619b0185ce8cbc946225229e6ed448dcb87dbae8c35cc75800c655ce9f41fc724be90da38720361222ffe5a208842b0e1735633abdd15b33a722bec8cba9ed4e155f40c00c95b42e221e82b5755fa2212e9cc54c8de04787655dcc1dbdbe7e72b20bf978d8db86fdaa686899156d396b0ecd83d6c22bdf89010c31e79ef78e58c737571100376c21b6db51c0db6e79565637913d921a33de16ffa3a2e15d5c72c247d615e51d2260be357bada2b0cab2e93ac7c109b68f7c2148e3429958c064d8617fd0bbb36c4236bf0e6c31e14c9a152a341c296da875645048ba21ea9072b81533612cacbef50723b1315c1fe1b5bbfd4deef7d6146b58f7c945b522ea0ed54690024a96ba52978d3b0d60f5bd0abef9efca095681d0f17a8a8595012e721f357e6c2eac8613ba1d4b96225d51d437cafcf7f324057b4ef5b25b4e7106724fb696acbabbc6ab49714daa40a11078c4a44f952dd798326361f56f61eb3c707ee2a65b62663709775e4df5c5d2e65cde4f871274e9ab142f298e4a7506c872cc18479626a2031017c0ad9e0e429f8f09098cdb3dd8cf7d3b770fe57ebfe4720998998e40e9dc807957ce07ee89b8c00fdb0c51b1bfb98cf695b2c26313dd44f5371d880cc86136bfba096d5f56d5bb1bd7493552e7cc7f653a8895921c6b1a455c7b97edf61a1e3e31ca1ba8b43e3f4eb440e1998ae08cd4bc908c5637694ff2264b660b041d0dda7762a893b59ad659e8d0c2416e8c6dd0566a78794c9b10fcc24d34148b670db10f06dffc8edb66ad0358aec8afe48bf8b115f53bd3e772ab14dbc6c2692a029cbb50c7e00ceee1254922072f73af48cc1a68967f9f95720b409b1ce69f05def9d9f61d2479ff63edcca40c887572cd3822540e75ee44726e3bc387528665bbe353cc4f0ee009f777dc1513435d25d5a49e8c3ce9f6345d63eca1534fa3b4b3f674600dbfc3e4b498d69bb9389b9872dcb3a0d03efbfa591d75752a90c71137f08203aa4f8730942766fe2b9f22508266e7a6a0736b90723a151c411dc4a1d73520d6b39653ae9f14c7774fe022724a5d9d69fc377e941f6b217bfb627facb1fd5723f08f0f1f2225f5c0948d1f2d8fe70d24d6b481e67257b063fa9f7925c599159829008e008423c0ac7e87643b6382f5ad0c210eda72d1bb34d7cdd31e4b4c4af602671f92a1e9198e40cd6c19cb40d70680388ee172afbcc899f6e6d188f9ad23018cfb34e58757073311a6b9579186e19aa5984772616db5b93dabf84eb46eee7061ff606764d5c64cf245305ecf72901f371fbe05ba28681a5ea59a750f23ed648898df414789d08428ff70ab3a5a31ab73a4ac2630c7dc677488415946cfdb4c3627d6b28e5575d8480e5c253fd3f51786bd72724a390f0c3ae27e5edf35a116500f5cdf73ed9b315870dd62a6373eb821dc9672a3daba780e8847a59c55efd94f8b8dd5f62105bfc9ba9ef2b1b57c0935458e4df429c09c0351facaf4fec25ac49ef74ff232921ae07f09dcdd3ca39067388272b0bf0130689e8b6ec995fb03e6f4a48e9bcee309828af5be4438343792ff1372d588c73753eb6360f0d78482adaa7f3dbcf7a9a91253ae0ca306e04405fd0f0399a1c98ae6a40da044869513857af19c37fecf2cff8869f303f9db6be8296a72f4e0cc9b901013edf62ea6c14713b64140722247c4c18b6fe3be14bcdc855f729d7adb64497b91ca4a1f6ff74ca712794c704d4f5ce863cf28cdb71d59ec6a68360b6ac6b6a36788def34b99aedf7f1a4ba0f2394899de8387b6c7a8fdbb104d75d2da7ccebba23448be2053924bdad26a5ee2c04f9e25529b9b9fee06c6d96dcb500d89d5e2678b351575a5d70874a37103e777536f423be23f3d6a97cdf372a79c3d833abbb98b4cb0dcd267509b7eb79d344502aa3b240659850455aec3312184be80980669169e2b5e0901b26b48a9567d48020d3311162dcb5f60bc54496e37f965a83947b54a0d2f3b9dc0c48b54551d62b57d002845e14958eb4623723c73f6fda489a8cfaa42bf07e2239980b5048b6d7401e3237b323476a2ad3172a4d53929b32f8a5cc186d489faeea1ea6d6514fd16d6a4fc5574b431de5b7626c54a21a6d71d99c4f8126acc9066aefa9e132be8f5310cf3a5233af5a14e226b00e91a95aa901a9dbc4209aa8fe69cbd3614c14fde2aea4c89d23fbfc694c172303ade67ba54d79ce76f84761c72de5662e7479ec3b9412c6ff34a3888b1aa727088502f7045a24d6f6e101ea00bb544cf745cdecbc53d0d0481dea9fa735c607b2949c040bceebbd6295ff72e2ed0251680a6e95df656f9b758255016a8fb502afff3dcb456dd3c8d7b0ba0626ed4c38d41c3e10b84850692a8ca1326af8304afd3144260e100be14895ce473e14c3e948708ec9cf689af340c7f05acca20722d27d23810778c27100a8709b4c2a4863bf428f1db32f1410a1e09b60f8d6239e8b250ccf479a4bebe18d94dcdb1bb402a96888443d32a4b6baa7d744d07c372e944e9bfd7c13c8dc5284106ea0828626a333494c478336fff4a59624638c77238e3e0b41c2c165b3177d606fec4b03af1039a1999f710d5b81e7f350e49dd7206911abc6353155ad6b7caeceb2fad6f77dcee553d76b7dcdbe52c0d4b9710720657f18e2a65f097fc38711474efca9045f27c5b3ec8bd0474ec1ed7547467638e82be2046545d8f13aa7824027a078b376e4413b710c65a52a0dc4a44b965724e3a6a55552b7e44985241f4c18234a56f489d95916464a7fa6a667ff2717566c11cef595e83433c15c7b5ba7a748bc1435552f0251a8ffa9ffa7acc0e8e3072a061e396466f446616ac265ac4d3b41d934f84e36ff1440d84e5a9bcc073117215c62ec0848ff7238ade6eaaaffae1b7bef672b87b80450b68ed3de6b69b533165dd4cd2f4edb19e399b8829d79854740ed72834ec2750a75743e179626b2e72ab28564bcd3d1e10497682785269ee49fa8273a3922eb53f535332d4ed2a9e42e859ef2c1c2e871a93a1798499638a91cf6b8e3bd06ff60f7ecd0bcec77d8372adb671b5684fc574dce2e09933c39bea79cec13cc40d7c827701903d861e977281532f8ac025443d4c8dbad0c1b81614d91e4eca32474b891294771666481872f89bdfdcf24fccc025fe3afbc0dc6836cc461445fd214a2f1c80179964a9f467f36cab1da7ab3b33c36a4a16bbe42e3c12321302b5672cd9c6d5ee8ff19b7354fb3ee53d66ff97fecb260021e7809223c88ac5c83e5ac543ab475e6139ff7932a6e82f0837977da1a0889971c588692fe3869a8ae53b20320805d5aa77337e156e32315800a03f20cbc06a47efc58c0484bc8262fa558f7abf02859285984172fbced438999e6a80a28d07772d0d1cc32c228352ae18d33194ac7ed7567d201f0587cc4d2ba2a897b8f9a97e83a338e34cba990dbb416eef011c2f634db29d72313aca9b6d97831851248c6ff29f670bbf97b567da8317c592b1a8eb49161d721bdbb9a8f88d705966add06a0abc8ed11a8da49b8288c219094f5492e7883472bc6f5592afdc228644bd6f91f049f2fe2076e6f757c6312a386f6cff5157577277afa39537ce4c13dfe4c874f336462ab85fa758ad2b541a00db27439c24fb7293ddc5c00f3e571798711976420c95e224b2002c6337ea08e26c62ef449fc82dd95713249534a57ed833bcb336ff02232e4c0f13a393123b1784ad147cb7d272b2f511f48d6f90357bc2bf5f3f8302e1138320c90e508e6993f1d7280c41777091b089a6dfe368e4c4515907882381fdb653e7234493a3145c6714697d37d972936fa236f38309994442c3d9431f7c8b7fd2e7b51f149330a4d2226eb90bd50e9f8b7241b48034bfc00192d39edd753116ee47e5c558f5b1d1360f96dec81c7277425490c2cb683ea9929abb6193bcb20d28071a95005fdb423eb24481ea7f727b8dc9b7801d3f28eef2538969b154f3af61f9158d409e69770bb1f5b1d3a503c846ea636f3bc5f4a25bef891703f61ae140c9f5d24e4562dd4dbe3c08d31228d8b00fbaeeac0bf5d6cf8576c69398c49d1248250ef4ee29f38970a3a7df4a72cb7adb51c5a704c822aac380924c457baebf11bba51c5ada909ced8b639e6c34d2a77e04943469e0c96ec97ae1f7b6787bb1560cc0710926c72c76f37f13bb38e442008833205fcc01633ba3653170e13a3549cef954e1d1a54abef47cd6537240a8acf79752809fe976bacf3c33e8eaa23832651bcbf2301862a37d34bdff72faed4a0533b1ded85496783ed4b083ff640043d617371f07116dff8fc03d8972d77776f225f09cee91e9d2d9a4271541e748d191ebac6b69d3d8bc644aa3d0723c2d2fb62ea7ed570410d80784fc026c77df4807240b3b43967d02c642ae706640c49291fdccda98165dfa1b330cab6f816fb7d7a365bd35b35d24add68d7b72547cfa6bb162f63442f1ad8b810b8992ceab49cdacf581d0cac0d727edb4832b5786b6409e52e2a51a8609d55dd976e9690d0de4bcc42e32888196e7214411727eb0275ab6004372e4dd1385ffeb6cf1c5d21f25017fa8d991dac7293bcf437243c58be6839e73108aa8a81074dfa053bb58d44b80d449d4afa1b3c8bc4fe372333020e8b44a059e9e10d297e6930e4f7ba446496162dd856ddf632a3ac42a6de24fe723c3409c2d4677b8426a63ca2de73df210d90ffc50b1641c299e2f1a72dbdb657995e96372d7b01627881f2a68354f6f45f0a6e728264768761e1048016662bf333711dff536ce78f132b12443ce024c1d3e9fcf3475916c055fa08c72016388b5f004af771fc4f15edc458b852102cec618528c524af5f221eefdbf4ae213b4a349641f2a464957a0cceeb9ffa721ca46e30f6ad8895818a685754944c123bd595f8c6c1b95e54b3b98effef0e1ebee41fe769dd2c04dec656f3eef7296ace0045951d61104c60113fff3399d3b8e612727891f5cc3961e690c89e5721fad8a6854463b76c183193d9555ed433961d507fbab626842ea8b620bc0bc72fd5de5d4416c99535d024dc93e1c741f3afde4343a51169329e1481abdc27872d9207ed0cdcbd828f10f1fe5ecff1083e854a316b3d51fb1de731b7878b99c72b46c19f6c140dafd82edbfdb3daf9a8008c834dce48f0c34c43c846766126472cf427c27ad1536bf8ed6aa87f762c6e879545b2b36e05d14521eb6ebd61fcd08b6c5c9901b23f079d6199109b24b0e72b37707d94e4b512155eee0849022ac2712c3d63e3ad53842f1dc8829beb8876c9a0f71ce38d5ea5c66ed4d3f855bb0723d7305eb7292681b996bad56a7780d3e5a973b6d47dc24e860c305995aa5507285e0a48fa041fd63e3e536fe0b1bf94b0abaa7384b901b4706030f1214003b72f046ddb625d39462abdb2297bd1b5a1bbab3343b745a6c28e86e8c3fcc3c5e4e881e880f576ecef5bc3030e1600962ac769e284d948fcdc63e8790a22ec0d27249432b6b4e69e7db87bed636bd4d041a131fc3f70818945f67bfa2cb0751fa72034241c6d682f710643c3c69b7dd7740f4438470ed0dfdca4c24fd3e4de71072eae5cbd204502455c9326707bbb1047725bf2972e0c3cd286ab04f413e9c39720ed766c244509769085514e108fdb535d80e69d08a91df4560599b6690845c434f8a0e61424a482f570f1a4ec2844a5aba7c5027a29402d6907f1902ca90f672e69616a9b92686dec9583ff0a05fb4e2192c7df95ff5c2ceb2af2ead4d9b42721b6695ac75a6d3353e815b5704324a577cd36cf6f3828200523c6611a05713728640598c7a3b9cc4673a7917696680785a89de96c54a64bbd6e3aae8875af1318fac87357e01b9c454fef6f70617a7c25990e9af0effd47a352d1a2289e75872254156d0526b808dcb56f3ae2575367529d225dae7b664a1417532a65ed7204f77b08e47819677ccfde372dd046fe6809b238cc9df30b35b3508d7d315e6f74b4f1d58da8cd882432c739ba009c6487a2c6c3b2b75870042a9d4e9d522de1d6ec983e4b55ec0b4a275416e0839065e3cc0419e9ee14784e07aa5f4b4f82a64727ec2b051aeb2610c1e16e91782e68ab7934a1d9aba9ef306c4d324bd3941ec720f47eba7c34f33ed9f6c9984942da8c32b842e6d9ce80013fca680f9ab8f1f723b0b8a0bc2ce13422de18cadc3aec67089529979be0f26b93852ac00aebad40a49295a9e23860983783fc1c18c51e27a3886e3f20cf0f6a69a1757b3c1ab4d59f0b15e0887ff92c5f3227456a560476d082bd50b048621fa3cd6ed5a2c4206222e0e825f18420ccefcd0ac04f8a360ec49e0c1f051259cf5c8b8b08ccd59fb6cb4f33442f393b87cbd18c93bfc518691ef399674d9467371489ad68f5ab6dd72677b019ceeae64e2a696f27a41645c927a3ab4b977d7bab46472ba2bd1f82450c9d8527cbc1320dd4712ea38ab56485c6ed0b44bc123a12bd1f309b8d2878f7225d9fe710c625e480b8bf519c8a9f4148d474908a38826dab310bf77211433729c5295c72c9a375a5264dbad0fe120c03d01b87a07a6040aff333c6e83e309119ee1f161e0043cefe407f1dbf33956c5184fa59447c01661acb147605989b2729a4f870eb2908b0754435ac74358897b0348b3fc707dd002d21a34361b0c4b11d96b710c87875a053c97407f2e6f6eb99799df2323ac8cc27c3d88ddd9a6f013b355dbb436448b7a3a0798638e75d6f5fab89fc059e904ea6e663ed483b48464b35a66b169a68c2f12b025c28bbe83e80f78b5e0c627ed7a9df3ec1ca1f09f7232fa37c15de7234f426bc9e02c2af96bd7f354621c3eeaa95ed43ff8f5677472498247b36cc3a6eb4672acdf0b230e85548cbf3002229512e62d5a4e317fc27289a9ce53cbf2a4ca9afc4df06d4c0ea5c694a1b521160ed355f5ead6f9b5237256e44ad4cf09efa5f3d8aa368abc2db8456463a9c27cf1376f6238af74db5a43c163699da9bd39ef90a75aa0ca1dfd0704da065f8cf01d48a80fa0841ae03649437a85fb09ba671c6855464da7504ac2b2d1ade116e6e18248804d212901970cc2ea12d230af256b37a192df77671d590166de01e0f85a63073064464980ed723ac39a8a75a944c5f981223a73cf7a856fe8ef46dec6e778a3618b3232904a726ca6434a2e8d62fe8f8b114198ec55302fb047aba1f1492ef82f96a28fbaad1460756bf06f75f4dc01988706fac0dd00c617a08653bb6b90dd4ee63627014c3b7e67df34cbd929e11f1b83c1a5e9b268337c1a76b4e068bb22caac8c8b6970726c9ce1381224eb5fd95aa5ee54a8717a711f021a94d1f2be1654c6e722e980728589ecd1a1dcad6151ccc64b4f2e2729ebe4442a72abaf2c1d942a74deeaa1727c8ad2fc96d40c4ce7bc0dde4c2b78e32803c0f4283a24fc724b467f9333271bc6b218eaf063cf2dcdd53f9cb8019e12787b6ce22f7832bef0a43591967f425112ec0898f1c0137e6593e01efd2fa01f16c8c6f53533adfae745458718fcf9721eaaa13911216ab2e2524b791707b549d5cc4c4333dae2481f60a8981840d60a0a6c24a4819c362c9a03c9bf8f5f134c8dec8dc19edcbd5f38bc6b6f4aeb7972b22bd6f6a50f9da27aad91db2ec0c32108664b2e4872966b6161ce445914d7726a62abb819ba74713caa7c3edda9e6644cdd97321d2be2eac58fcb706fe479367eb6833b882a0dff644395b90f10398619ad0908a12c3561f53f4a21c02b005ba717035eefac3eb5c7cad3583afff11428ecbb1e1bd66dc186ab5c933858824b0b76f4fbb92239418c75b445692281c5b3342c58ab9a68ffbd994c96613a84688e2ac603ab7374db89157ed160cc20d3aaddbb90422944fdbf8cf1d8dcec905e7be1582fe60991d9abd09583d90c4eb34a0d541765254aa30bc368da178d22728eca842f736cae2d9c6fab0b6385e405a454892b5f204279a7f17e171ffe56728b1eb9d555d69281a9ab4aa774f371e820a106a61842258a0a9d7c5740f876545ba996380361fb1897cb0a00bc351aaf3ae10dc2a7bc1be8979f1fcfedda7a722001a5cb53b6b47c610b5b143e8cb8e91cada2c8833610de0ad38b6330f14539b2aa5f00685852947d01b6fedfdcd459a5fdc7f97efb84e698c303458937de72cbdf429af45e0d9600c365fb3ecf41d896c9c026f4e60854c8528ba67eaa2a721a78b7d24663f56315c04de556d1f0d525434fbc33159302721b3274348ece72048b4fbc1a7ccf5023ecaa17830629a3a3f7c7530a1561175fbf541f1eb4ad7276da4b68d32e1b7afa251fcc36eea643483e67ba0616d409c7ca732ff9b4e75d53c9ee510f434de5789e69831d741966d87ff6f95eae6966d9fbfec949b1ff72f3c2dfbe9f543241d6c9a9a3c3a467f38ee4006da4d1ad56a4f09aa900bbde05c516d5f891bc01f0d4bde506ed575101f9805f44b873a5ce9d7b88e84277c12ee1de0c9ad85ce8860f7a9da9eacaf14d3cf2d710a911e645e050319cd0a6f472c3cf2d27156dac824d757b4ed1c0ca2cc4481a0120d5fb7a809aec0832d36c255c635d540c7766a7a0020356b2182e4857a0ada672f58452d22035694b6c9c7239c75132f243c11a2a370b9e4033ca4fb9619c4e632fa3f7b4e46daf319a8872ab3cdf70cb61a441dd0a68f90a71191cf517364c7e9edac65cb35a099420d87288047c665c1f67ff0ac1e97fb53701043944da3337f17df6edf5415b1b02ab7243600a268450ba62159103278db91f5949677df78b3162d91634b023088bf63c13402d1307d8ff0d24a99dec87bef90cb72853249fe3d0cc4b95938e048fe3507ca938f45f860763de95e71257f1e62c45d676e012481afca7063729b071497251fe6010d1f4682d3501f3d47b71a0f5a2090369bb18e1aacdb8bde59937d1721dc209463bb19acb0c2b26922188cbb3ff4d1e8f87267c762c7749b38d362c72014712ab383a8fa3d7464dc8669139bb1e6e4f72cf66cdf450e5f3fe748f01348afdcbe45d7717673f59cba0a4e5877aae3ab49291608d147431cf9c0278006b85bfab3eb672d723abd801dba03740fd54f60b4a81691985b43f7c4034023d727819dda16b714317175c949d6397e502915301d3255fdfe9b87f29cc75ff997287501f07c621139a84f03f7a12c42645250bba0ed6d89c14fdd0f8786b9d7b2875755b28eb4481bc47387c90bc31c2f315a18db4dcd1bd7040341ee3f5e09f6cb334fc05ff66dc005578c3d7e4b5ce19edaf8d4a0f9e50699fca869fc78d136c4bb3b083c3daba133cf37e337edf62b089af55e1840d264c739585cc233b5a5ebb725fcf37de1f787919b72308e66605496b6b74dff9a5fa0aabbfdfe409937224e4a4bc22c6d9ded2ccfdf6ad17b1e4ec617908e0921e1d58f09aa491a8b35d30d4a61254d434c51ac90f7603f6a958fa74314c44df30d2c1c204711327bc57b7a48fa4af1749471d79257bcfb47301cd4af5b4bdeb2ffe0b86a608a69ca972f621e879353763832609999b212e3cb4aa26a43b34d0688037461c57e1ff6c726dbb772c77d04524ae13d2b8a03a5c9fe7226cebada1266e0a5651b8dc7bc2727cf8bd931b5c79c7349d92db0a783c440ba5258eafec3a1f7e14674991ec53720c57fdea8479c2d7d25b95a033cac38357829f12ec856c191d677e0e529d825f0174e624aa8b5e2a8281b47918ec4bd96fbc8feef7ecca84c5694ccf4c4702724df8f712527af6b6b149014e0b7a8eaa82229ed20b70fcb4ec170bd083897d7226bdcd50c4dfdc8fb369a486714574e8113b14f8d1546596ccead0bb337f0b61578a70b0c69c60fafb6f5cba45a8cfa78b06d8f2a6c555854d243051baf8aa724ec321df4643ad2300de46eee2c952afa68287092907773d1bef68deb3531c729c0d3417e9efa0959cc6a6b1db6250e7b3e850a1162d6cbc9ecf7cc53984d76ee37072eab24b1533a342287c38ae13e6d73861f77be68da756779a3dcb30717228ec542d17e2b500aa39ad699470915b0fd4ba270f41a08e6806057bc13ade0f23c0dd84743f21f40ed7906137935ce47a20566b9801e09374fb12f137f6a6722b9ec7aaf5995defa4db6b81db5a616d22d03ee66d586b959e3a8f980e443372315ad740c3563bb78dcd61be59f14f2c691c499dd04e4738ffba14044e360772b3e68dbc337d15335c08b643b918b5e1cfe5550022983a4da4872990d1b7a2722109584d54aa45b3cf08aaba22bcbd35503d17bcfc638af96ea9539a1bcb1372fbab0ad10928cd1c8b2ea01a258975b88a64a12e5f5d1e0b02b8a4317ad3bb728e348feee825e64afd7d4956a749341c3f426edb169606c856457c185526b4727cdb7feeae7c32a5ea572b60bb85466a26796c2170a505367459b53abe39f3726b7b3e0d556a0c6cd08338fc1fc2035b4bf16069c82270fe43de1b58dedd0a5c02b3b8c25da842af59102d1b57d9bfef81be322c1a9504818a35bc0d813483721fcdc4b181bb47856286aee922c652900e2a7213efba5dc0baa45abe812fb57236e738adb886370e7fd2aed66bb1a6e1d11017f73ba6fc1fd93598c8f18d08723833641889d01c69bec21706bd614b56b5cf190bd717d709a30a2fa48e71ee5a8d55e6c1e4b07b3c7c0d69b85eb1cbfc652ec0086920d48ff88e640d5f06a667569a76dd6a43cc3d42e7c06cc38910ad62f693abc09a3983c268d7f9d401417268609ea3f6637abfa38dfa1e653a0644d102941f4b1c85e77b124b74db715572c1b47182e9b9de7231d801c7065870706cf53c249d699aa2c8a773f3ad4f0251245e75e9080457e300a940cafb48514a6c9546cc99be70350a273ae13ce623724823441b7243ebd5e5c0cdf426db1b64e8fbc2ea69a0be03904bc4aaecc40d727db5e69255a2a680a1399c22be7606ce9b7d81e4552eabb4e1b6013e1820926a98d0c3c9aa7acdc457bb91819747f2b533c21b3a24bd06efbd7f902e7446a626ff30f91e5bb07dc3f3b74821b76e5ee233cc9dac2d1ecd00c2180af52e539372a3d9d76741ff98ec1089aca3ca6595143fe393439f9368994fe68c1fb7f837721df75846f29fb29235987acdcdbddb4812764cbf219dac4899c174f3b7c340729fdb83faca06045597d13776752dbfba7c7581abbc03997753c597a79740db4593af2ea56a9083f52ae3c107c28f59246ff2c7f05862c36c563406a45465227239c102706617044df2927f80488c2fc19ae0e11b2ad77f8584a834e4fd6c0364d73987f25ad92b27737dbc776c16ed85cba820d741f7a33ba03c79ed6634a67211d73e2254a64786c412d572a2434484036f154e1eadd7d83a45a72e1b121072042af3a2048b0b3d4a4ec43c8f2caef19868749872ea25a9bc362dd5723f5372f0fd9772fe9ddea8bad12c2e50a7bcf6b9ed94a22ff6c160e83ae3a437511249fedefb8a5a650437ed1625ab674141d385c50b5edaa24ab011f3375b15a34a72247ddf3f286ffc6c320c65de684683dfd54dfef937ead0900aed69ca16afcc5b2e10d5939e25dc429b83c6aa50e2dd8af53270c7bc21c72d16ada29333c3fc2bb7abe90a5c1f57efaa98c0ab5db7a5007b5b3a798a0f66b93b7362f03b701472d8cbf856d39c28d68ba6110fe0e6f56060d6b42db6a261d42c66bee2d365e7722a26cc08460dbd1c6e51dcf0b2731905f789e2890a36d73dfd6dcca785b59772e98820939b960b92e07207fd52c31d50cc986e58645eee616802a943c6323b7243ce5e0fc000a0aa46920ce38a67ad05b0d5c8886241d9c424e1e8c1c8619c727d1c50729f0480ff93c9caa0618f16ae2bc4124da2bffa154d5bb18830b21913c827de30ecae41f4091b453113f348e1f338b2dbddeea6650574578d39bddd7251eb6cf1e23d2aec02bebe04c11b2565f49643dae8ed8b43f964f252d50efe72a39b491cee584cd0aa58ba2babcb60e448fe532517b9d6c27e847b9530740e2d1a4c56f647647994bc3789fc4e21b0209d897d9e394d43481cc88cc49d899906d5b2c54d3e62c5ded4fd8efb633e2bcea97a3c65c6a8cf5f2e7e510ac9880b7299de576763e32af88124d625abe7b108605ca1b133c3c8982727d4838e3fa872e3ed1d7e5e49ff9c8be46e06e9714fadc89340a57b45b5a04a617fc7cabb70727e32e09bd4cb3b7214c0fc0520373ba1ef3c475298be35b131ac4322cc068e59f8871e80178495eb12ab7a0664ba13e07d5a0919bf739706dfa53f1a871fdb4d174cf23e2d3655884fab35fa91e41015739b28fa4762d60789f1cfc1666924727e773b9740f810bab50071c0020f9aad0a56bf4cb3dd5a82a1337d30cba9b953b4d3c931d48ef4e33c2793c6445092a4927a39b08733847a3ca69597ca0fbb72700a922aabf4af5d65675b4c38988ea7e1bf7ab06f75f577ca6e614334ca6c44198763667ac7864061a87582cb27f399a09253b88ead40df531b174b075b8f13f9e26dbf5b60e9b2f29905a78dd0a20cf21e0d351865b8741e32335249f1ae156f1efefdc3e8e0f15b5919b422c86dc54a727e9daf33be7cf44dd34643d75552cbbbdd47de36702ebbe09fbcac5ea11e9cdbb336f37eadf067d3aef33239c80f48d276ae9f4cf1fec1c5fa692f290d6e958062405357a9e03c29583b65d8e31088e5d6f91897a4e381012a10876d844249d83860e68cadc540f0edfdfe174e72d0c7d1d397b29b66245de923523dd542dfef75abcda7df26a0ae19f29502aa7210fd0658331a9c22999657ea84697b638f72bb558e63f5476b39c755c5c86672f67b7759b317296f1a60e4a32f7637914ae1c7b694fb5cb9ec3d704d938ad8729cf1338c63c58aa67a0872f92f3d0eb885272bb4a35d6e3bb083943f837be3723a0ef8cb8eae9f65c671e71e8ddaf5e56be89e1dcd9449a949ad303d823cb972e4fc915c17ec4dcaa27c08febade078c51398288eadfffa8654a7954a3c5813537c5d3c5c8d8f5cc6118d021a6551ca8174b1827e887a2ad18d02b98dff215723e5208ddb4c8eccf5ff4ffa38355e9602fb5d4f45e719a4887b9692870195372c2b002988c15fef5abbfbde626d6d4eec13809a462d07ffa107e2f1f3a066c725129f7252f2c2619b8a4813c5f2bf578d5442a11bbd2286ac7a8ea471d827d4346c21a8a6ceec1ab78954b54d119227880bbccff7b202d89b6b6216b59eb5d721c60cad945c150982cf5c32df457fd24e46198454c03ac08f139477f7088e172723c23f8bcaca29b10213bfaa135f2185657619d3e46b9deb1b27422b0327013ff0f6607161cd17a545a93a6b580ba7a55cd3ca24dea8c76dba6120af1fde76a5546ccdc1f3dfec010c0dc977c17bd57f6983acea17514d7868302a8e605795f50413ff6d5d7a162979d9bee1b82143935ae804a2f086d0bb08e4b2c2cc40b1dfb38226ed8503b0f78028fed9d08b3255b0458148f9bf5a57179b096c66856721333ddea799d0b7068672ca63f4b5aed9c22e41c577fabea2142165b51f7b837b90c315a7557e3b3f5d28135830f0c31d31f867ad8c8143a2596e9ba499b027206c357a9512e00238d32a1d867e92f08b3b06d5ef53f3628a0591d788aecc672f337e8c295c3d5a7e01700177331ae706698fc14e7ca9f070b50a3091a206d72dcb7b803cea42759c9abcd1fd6841ca81c7035c634351fb4d23b944b3db87904fb7249b4941516521bc851901c8fc3d8c24fe4936e7fad888a5aaa6a5ae747725428dd70add7e2668d511c6bf2cd210a0b9297877b4e69e032d4cde1470e3272764c0ca7d4f65265705976dacf1b565556890c1bf0d15d3055d9c753d4a557402ff3dc90ee52b5d42f7d10eab286c61f5585254946c49ead9d70b6313668161c9a10c1b70a921fc16282068e333b9dfdebfd1641939fc937d824b0319bc86e72b112d58b9168805ba8dcebc9721dfd54be940fe661fd1b0473e749596c2fe3728ada679395fab14029949466ef3bf33f2620fc2574c79d60ccbaabe0204e015bd68b0be69a03eb80958c36d236391892e1b191c3cedd3b44a350a8b92b489772f7ff8737bc4b7a45d26b1c6c73bf42186cf29c16e6c7a1fce3898c876be4cf72c1cf9bcd7e9fc06e4fba168c191aee7bf67dc62247fe37854c6c02da10181f72f1dbcce512a6698f7374a7008134435f8684b327a319ccbc4ce1a2302217957275e082cef417d1860de2d2b1f1e9b53e709d416ff98b825bd8ee04cfb47b5e72523b9294b1b8787164efea3ad7ee532df0aceb4cd5389edc6e879e3e2488367243a7c38a0a0b31c04c5926b0a54d5112861a86869b4174acd55ce75dba23af72d1dad01878e3fa2343ac83b518c5af4b826d60f70514bb6b73f6a5c885daec4b025d956670799c62984c87e5427473488b130b0a631f6c9bb32be053ece88865eff5126a431dd1277bc6ff4eeccd626a9147af105ab1092f2306e95742a77772f9219d8d6727c57b02e4b9710facc130e367b5bd501727480a03212df6c4ee72482d1b551ff8a8f23bf672252ba648ef4370a5f334e42fe9901dbb65a82b34728ff7ad53c5549a83834c79c244106c62df740f9c4fef864e14f2da4dea3a8a72b7e1c348381f0271285a48f6ab5e0d0f41176d8acf7fdfbe55f6a2b3562d46720191076dcd125f07b3dc601a975ebeffb4b71f801c92f20642d078e59361820080f55d704d7665b97618c7b103da8902c63463bc9924d17968e6e9bc237ff211304edabf053ca163e439f479bf070474bb348a1cfcf0c1716762475743abc772945cf4cbdde7b9fbd6ef266eb7612f6a4fe7d5623e4ee6254fbb92564b14e972e344f0529aa59b6473ce44ebb50f9099346b685a5ba7af31b40e7439c4ffe71a84e76430be62edc51bfc720b03cdb60c98552a392698a4323b6d2e72cf18d965f26c768189220f089864878944ba0924e8830bbf173d253c615088e0788abe5aa90d601082b48687e74cf621360cbdaa5f16815a611cd0c65855bdcd58eaba6781e20a595a62b8d5d5f1f409dff6f989b7f8fa19de89a9827af78f8b47ea82652fd8e2ba4d77d04a03dc5d314c3e9021e263083b3894946290cf7d74e7305a4c4fd492da7ef7385809ee1584e25c19eca9a2af10eff6fe46270bd237985b0e7232bf2c80c8fd021ee6952f82f30da4bad9a9ac43e6ae94e8be1106fd2f2e290607fde5d54297d5408d5d2f7084454bb72edb33249fca8828365e278776fd92727cd627c41a269af5bb6e4eb9bf540cdcefe21d9a0bf266f539c4ed19f53a0d723e090e47db136c2fb21d9ddfd4b93d6b6bb6bda232206be79132b377ca286f5d4e4e729ada30230185325b99be922ce743f8b76c586550c7a1945839775f7233cdd9ba19d13250ab3890f4c35dff01ab092a6c52c9736a3f9c49ef72f9c3d634d1fb2e317eda5f57a69f8af8f32aa9c37c8070d8654637995bf16a9ae0daec163babd6e81c26c123d3b63fc619fc66b6af543da84cc35896e734043c22bf1b72d4c18949524f456e2d167634579e8631ec56be1720cb90578739c8060d850b462adac8aaaab671d29dfb9f3a67bf7232e9dbdbafe4daa899f439145d20692929a471320b788ff001da915233804f037dc1bcb37704d8c456490c6f23942a1a48ed2114015def04cfcf9d449086af81151d88887c891df507c445a4427bc8d92d81dbbb2d01aec434fdb82c6f3dfc91d2f39c6fd2a3f750061f3ef295cd4d99063e346f6dc87f56703fc5fc8bd9423eededfbcbdb7ae993f3bab32ce49d626172cccf1e3d837683080dd4bc481e3fd3ca3a0fe5ba9c97bd48ee3e88cb81e40872468fa14de7b8e718032ce47a04dd34b69c7162695fe516fc28d5b9174b78db5ed8ff731f0eba7552028aa07d097e647f8fdc384d86a624f1bda384c39da31072ae363711ea895847080b3c9a5d88fd7ca554fa32f469582df8f8c4b08fe0df72a85d0ba08a023ff4f166592749d769e340a0ac65ca282dca5b29c4e397c41467ce0452cae73d022ce80d3cdff8032fbd9d15242a354681c025c0468d0e7284721b035b79bbe147936ebd09f99d94aeebe8baefc21aa847e1c8f9dcba13699c7257f453303bb493bc5572b4dbae6bc7a36b4120624511823bac0983d019e06e7289d6d22cac9a4572521f2aa538ec5e5df9ea66bf891547e759c152ffcdfb133dd19893ba38ad613860e210226e9e9cb063069459811e42b3823a188d9d24404600038d3c52c5a62a9309f154d744ed136136b46480cc8248674106c0e42403729bc9f932a2fb8bd5c567b6da582c66773b8f7991be2dbe80cf11a06925ec9672d393c7a6da3b060e133dcc7c705bd958902053396118c5e3f7c6f4a8868b9472209dcf7afcaad26c5778fe2d4616d72265c4df4677031556d38b94bc01d57972c3616be7fba745c928990e755690010f2865360cf23f0074a347b204717fd472110abdf7c1e31c6e2d11040ce20635db02b27261d5159edde6f8820d8e7ab0727674cb03f96826f716b36a131b18ab2b242da8842c020336e552fecf3b325b2f4627a8e5a44d0136d12e11515fabaf3a1b74bd8979accf6ea1f247211a0cfc72c1d5eba3c8b91567eec5677b1bcb1cd6904ae3cb9ed007540445804a3918a10c782b86d38dc342e3a0e621035df454c05401a2937ba69bb5c69271f59aef91682994aa517a0cf5d28cc5df463fed7c7c2b80f771a5553b872e8394b4eb5ed1729df460c703013fbbc4ed00caa7240b69732d7ad9c79625b8fde2587b9c38eb4f8c1556cd0c163dec5c3878103c10e4a40a8b77841a6662f2cdfb79a2d5b34772d6f01028b98deaeb5912169734b28ec62ad5363a257960466811ca8b0276de1f628af9e995c29e615e56acba2816e941baf88ea8646879df2e8b3153088eb6728f465b24edee76b4ad95866ef7ba7b9d657ccfea8c711405f4ffce60bdfbc172eb33a993407bd32e901e4635b92db0c33abe2d5adf3fef17cf53f1e7e27f4272969bd19accf9933b2a3cc54d7fe11c1fde015f655e36c94ca8107bdef7e1b73c8ca0835aee694dbde784b9057450394a9c85825bb0a854626d37af2fd262ab7206d9a0c6e27de7109e957cb1acb7887701b4c4831ca2d6a0677b6a4c949ae172f2b4a393c01b68fe7d29ed0bacbb7fdd242c86d5b1a82c5bced7b7f2b21455722089cea2e4eaa9a06e14a0ff9f60dfd49fdd9e466972b70753ff644d36170a0209cef234985837df33446e204a2da6b8b4afe1e682c7113b068fb0d9a77068724a6b569c9a94355ac63e0d231a2859973bab528bb54cd395bbef84e4e3429e729a32ff7499fffec4669806768b7b52392b626594cba327ddd913f47ab9c86905d66bac6621cfc99475273b7925c8acda9fe705f67f01c25dc37be6e8d0ba787286ddd8ea071d52ec3047bc0ff3e386ac1f7347e0a4d15b46265a38a73a2de831e72163865e5a196f1dfe8bbd2d83cf1cae3b92f6042e5cdd2ec4f160aaddb572c5f95ecba6b4586d9f8bcd24755bfc78f0f7bfafddf58937c3e4953a15553d47a9a5458e0dfcf7e09b41a8b8650c6e9451eaf2d36f50fc371d7bef0adf0f1c680079e921f3ac102c58dcd126c455f1fa20f64d77edfe70930cb131b3b1246372ff5ab1c91739b0fef6023cef1c80747eed29c06ea1b043c5316b7045e1f015257969fa7e0325559eaa15bd651865c574e8ece64952917f883591a6e4a9e1087206860c8a7ccde19f1a673fbb41fb75d10ff339800f1ecf206e0ef056d7d5f172e81412417dcdddd8bd49d6694f9bccb9787bee3aa342df9046dd969352469e7294b5cbdd0c229d2e35f98ead234fb92e185af43f70fd9146510528787fb76148cfae3412639c93aeea2859dee5c16155a06319cdf1196992e22bc42f2f87cf545743c72e8496949ceab9b946267436a0c501158897bac1706be79b91f50f9f3e4d17e73382dfd4562570701795b8dcda17ee23a88ce6534dba5c5e1809a45f3059093d9d89fd29fa86d77441a9f0eef2b1991696088665ebf19d39f51b844c720eab3c6bda4994fad80e6a2b3441c33ecd9d5a28b86a613333fce9ad3e2e61727ec0ddb34414b46b1229a5948b304cde26d43e6ebc4f677b56acaea7f1da4813479574d2563fb203e5e4672355e9c331e48fcaf1585b70928eabaab8f0215872409d1148a07234da0efa4810ce4c893dfbc54888f6b7479231488b0cb5684972201636ec8ccf21bc0cb2bf393ec8fa8f684ee3b0c8d5595d51524874196e36210927dea328df715e7fe1c154df791586329d82095d4ccd1c9e62586f84231b553efd8265a82879f2f71bc5aca81d3c2932882e856f61c51a671a23e564f0ff727804cc9e6eb5a4e44e0f7a3a7c2ef1ddde241be652eb367b2488dc9ad58d894e4014f8dbf0f9b8bd4488fadce1cf070661251512683f6dc1720c2e3ae601947202ce7e89b445bfd5c492b3e95681c9617cddfc905f2ab781c42cb8f3f0c74137e6ee6197be6feabaf6d366b251cb91c4345f6de901e6fbbe9aa5b0683b8fdb72549837a15907153177eb0f5cd324eddc8ea6a3e7da844cb24cea918381856b720b3aa8db67fd7fe92385b95de779017ad9ca1fe85b4fff51c3f7f0fd98413572b8aa7699aaf6e4f70e3120be3b102cd1e2fc2d4a26355145fcfdb9b3154bb0722c77bf5bdc5495b3d8d881deb9a8428dd752c7fb4185e4a3e6834bd9c5158d72c46199d000b4b13c42c8ffd32d9d4c5e23ae2b4d55fe66a1aa839fdd0c3404727d33ca98efcb05c382a56b8c325b8fc26f76a74b7216d72c55042e63a00e4016d63a2af947a620e42cf39a1b685b56033b3a1f993be3d82cd69ea8851a40165ce86b650f20f82eb42ddae69694b6a3406522e9b801568fde1400fa32cdcfb404d5b05867fe76d4b79d4c33331e479fbce49b4971d74110e453836d1788bb8572dff570623786c8ff8c7fb07fb0c37da5613dc16d8cc64f6a04c17b3532c9ec72227ae13ff8166d5ddc5cc7813206060e75b06d98ba4a4c8859a91d491255f97237737ffea2d34b0b04032e05faa145391a493258e8a29155e61722928e96410ba5b3c21d7a6c28499a827b6c58866cc1676295353297f5d81a5e46028fd1c165865d5d66606061d6833c4dcde64268aebba4370071f4ce2e1a6b14307863ad71d78b642a6cb4ea6690fcc478ef8c0f6aa1a31cdd8d038d61f6da2760c1140272b6e83e1e764c83c0a4ae2aeb938780a87577233c290779d26ce86ffb9d00c2431bc7dbf33839cfac28c2e3096d0a2abfaeb2790a3ae74ae17097623b5f3bfe72ecb52159546480d200fa9b486c1e75109629dd6d05840024e0b73c496ffa602ecfeed6455507a5917ca3b1f92aef7c250ad90dc9a5d7e4ceca96155a55aa9551df1256a434c13f916816ef994b4bdfffa3860a0b3872c458814a1b60806091724119ec025f011f8a7805db704f9b12765a013f27358740d2be1b4c6f80941d729547c40e4115e540861199bcd61e852522f889085f5bb674ba7cc47c8264eb0d33b15301a45c38e9f15ecad2ee8b334c2781078c43a285ec9a6a1c2208074d72a4f05a26204a3cf9342bd27cbb6fc1bc46966a43fe97cd704066f603b8203c72683e4c673ca77ccc82b60a22dcadfcf618309ed588763863e1efebd4555dca727822ccb18f351e60c410dc67a2ad6d6c329955e3c54bf6372b709842f10d6772fc032b714c502ce3d00ce22215c7a375267a0ac35a2db6cb130dd3a1087d031fba3d3030c66ef7120b143868415dd3fc480ae56c54e9bfce6d3728ef2a679b723f16982cb010722e84df2e632b7aed8781b14900fdb9a105a8a74b7d88226222989728f9f1747dd5519ba6407d864b6feab771704a9e4795431fee3f15e407721c0380edfae3116420efa19268d5debde805df91d140628d88d89191fa630531284bc910d992313303fedc39b6801337c101d13bacab4dbfe1855b0a41d7ba721fb96b1a0e7c6b0aad222a027257a90b7dd8a8824752a468cca9abf626c0d8725c39e5ca2cd6feda498aa43558d1106c5626feac0597422b18222cef732e2f5fcf3a36eb0dba7d88db95d32b1986bc537ce8c2fde0cfc2483c5d56b54e9a267280e09b1ea6f4e197a4cd8a011ba80d60797aa8cf2a68265a63f8daa068e1fa72c166a01b93a88237f919bfd0ad6f459a19b82aabaa0fc1da9f3777a4458f0910dbd2b02f8b79f1465b8fda30032bda3de56b3cfcbf0f50ebd576a7ed1cf40a72e90e25577fd1926758a087f9e3a13043e4e98ff4bcb83bf989d221a5caa449726cd96b44c364bfa659dce81677e0aa66b378b4538ea148883e0516bb55c6e5397c6afa468d49ac62e94fbaca1a94a69a606a9c7183d77db4ed33396a027a27726bb0f1ab813eb664adcca9e94b464864801e3237c811c83007e13fecb954e972771880f7b5f9374d788dd516a20c87c3f5e62a77266fa0024dc5639bc8bcfb35d9d4f3fc16a2c26e3c080ceaa2ca94bf32376d38971ac412d5d455b82348b25a987e4e84c7e1d640d8949af68fe5a8906901a1415f9b42bbc30329ed61b25a72c544dc47b31bd90b77c4d6aebc6f383698f5c624950761c70abe630ff42778265c564e234e0825a52803e2e0f9491b48910acaa9ffdaa3b7187d0e6643bb3532e73eae75d7a301f42c33319bc4775ef9c3cf53fd479d12b1d5d63a9ee6110c72b8991a1710311948fd9bb7dcd4611f5ad6f02b589cddf79398b6ebfab227ab721276e126b3f54857db12a0444761ca561078fd20fe3d7f8195cb05c99043447287748fbcf0bf0a5c62ae8f8bbcc3efde651f8ac3556b638013d8df2cf07bd64f8c4c791c288a35f8a26171ad933249a1d85888e4a1a686a4f267556ac21c32720f4613836529f1b386c497a57aae01918662b41858d0ecf72efefcb12766053537b5d7686295ebb905f76f14324aa322ba0d4f3f150e8fb6e10584fdec3fac721e5a033b9da6ca7b8029eff30cda6e955a02f42f16a27c0b36cd08497618ab72395a501163061655ba0e5f44c30650c2db1e1aca6d2a0c3028f25db3254d61727aa4fc45482a9ae53e8f90b3db55fe4365562b1ffacae11d1546184dbee0047248390528a62b774e0d605a90599c66d7cc5c7108cbb7873ae9eb0555bc8ff72b5d6ddfc2fd085b36f5957fad7a5da7c3cbe20940c356ec76328cbfcecbf57b7228620d868a0e9576d11192c8c820c791dbec4d91d00ba701e2f2f80787f10140699ab74b99a42148aa8526ad1a9c1930b6a6f733d3ce347ca712591a8f68fc49fb04b5c8c5fe9f6b2344ad61ca7d8c2bca4057d147bdcc6670698e504ab03f7242a1528a4eeee7b71460ae9eccad4552cc696bd6db9063e0ce2d40fa059a1672573bde521247998020f2e738db11a16f6da5e9ff5588160d5e1aa2ce5fd3f958b441f0fb174a66b873a65261ecca0f67059e93e98d4be3e5e3f9c8e3e1794372899a6f884cf5c50c4c3598d9c193d286e6d84989a969f0d517267304cd30f137380f5f039c2c22797e324f1e2e6f231546cc7746438c79f2ee1fe460d3604472c599f1095a4791d1dfe51ebd08ac1e32bf8ef74faa85efdbb4fcd908d3f93772590e722b72826b819e851d3caea08122be57e70f2004619db6fa5ba7be5e6472948d1119e634cb8a439e483ea414ab5ef4d9238321bcedcd643e70aa42648b72c4654aa64e4017a42e0ad75588d7405182ef68e033e5fcdfd33eaa36f91c8d124fd82dac40a789fc49ba408bc3f6367ba9f6b3376b6af456f2b9a62310e4267219c5454554f32fd044a4c257f71dd12be513f81324fca93e743d8f1af7e3cf0e97c4ed27a69b4d3fc1bc7165fcf45655ee471b730d57f9819f1413ebdb4ab57260c9eabd0deae3f3f5fb894b7772d607402492a5fa8ac09ff910d9d8a84618724934422599f3f841eab0919dc87a837035e5b2f7ad2819ec5bd2186d13af76280d6706c931d387e67f7902e2c9f0a8bd48c002f37418823ca8156de55fdabf27dee363168cc5eeed3e40ceec4750b448e9596a1b8a0da067a3f4598833bb917215e29baf463b3ee58e478dcc0469f3f2a9ab50c2d9a3eabe560e3d5f860fbc7297f392709a3ef2a7672945e69d3e44811298f52ddb7add29ad1075b4564f496f8b69b2d7b4d4da277e84efb32bd4be79a7e796daaf8f5e2ba16eeb2e80c5d5724c92952244f11592281e4790567475b2b374008bb9e6977778be3c2ed89c4272fcc5dae73d634c58397d5562067e986e4b3d5aff51807ad694848c4b54b4f572a697dc121af0f288b3b8819ae922cf1822fc653f37654d15273a13d5a8778f72767c9453a384901d10c6ec326d4e30e8ca2ddfc17a103841cc9091c6e9873c0b54057c0648b301386ef607caaca6659e0765ea72dbf5e816e9df3a0df494db72173a0f93ed7a40b152381723e3fd72afbd5ad53719bc3d7119f3ceaa0f63703fefbd6663522a1892c8293dd3af57e23806001653cbbd9f79de396bb507277672d4d91518ec6627aa1dac2130ec3d7a7dfe6f2643563a6512d9afd6162a01ca7267af126d4ea7e1e51cb0abb4f03c451d76fd7bde1300bd501d32dc1408254c5d9f399e06f5c2c8677768131297a812f6798e78a600cb86e777753ab48eda6f7278fbd5fadce2f441d266d0790725c6c002dc715630fb30c05692638d37258b7264e40ca93d4cdb36dda678cf47e1baacf092275cbeac93d902d0110786e28472f44ecabe072f7a7d4d017cffc9efabf5f7fb4bd199ed99473af5e705b79d9147779b242af5c1f5c9405ffb6a9c289c581c94ce0a08d518c5194a9539c32929729bf93ceec1f68f23c6303dadd88d74e04e16142b1df0a0029074fe667d003c721bf060409d87ed154b771465c2a08e562c4bc480e336a326cd423ebac3d15a725e732df32e3190fbea7073480af73b63cd3d53edb203599d75e8eb5bb171ac72ad77236baf1e73097c80aa0e6cc61e3f8774ae7938f1b41819f3af721096ae331c4d4a03eaf699b56e3d80e43370ee02d6392649b0a9e187fbe9f77f317a9272914193c1475ef68b1fcdf55a21f454cff1b33a0d4440aff49cd4a2de5d49f672c6321f77ccd210a99db7a0f51271d31e89bf75428b72c0cc09d2c4692f9c9572513dde25dd3e56f397463316159c3950d84935c1f80c725fc889a8e042016632e461f50415bf1a183b07d3437c015919dc14795c810c3b844b7da62f0da15e72c98411996c454685e36e2353bfaa6d5f801f1b59e7568092304c197366e751203a248f441005cd703ac696425610ffabb863984b9453a2290388e976ecc95c50dee79695139fa3a05473f9f79bdfdcde8a2539adf0e9de4c5048e620922e09722e76af24aff918f459bb4fb0139a11f33974c3b011b7153fff9767eb48cd0672e3f783086ecc841a061884bf536d94531986d24698b69259a8e75cd0b4b3ae727ea019a372332f8cc62a5dd2a57d49ca3a09a673ece6e07c014faf774e58fa7259c73008cfcf7e232e7828f1ff17a648ad37486f8d01c19236bf62beb042a8295b885f1e0a31e756679858919e03cef2de6dcebf1e30d3784f31cf7bbf95d8728c8d3955ad28ea538508a7df61ab42c5e2f73cc6c444209954a334de32e5b6336e3401502d015495097be79a28db133138b25da917d07354bf3bd72079524d720bdb7cc2f4e7428209a5fad7c3111966d7b7b3d07a3f5bd3a91d700e7a8495723a3dff24714763ab8845e1fbc28a08aa4430b6552693d076ccbddc693f249c72337a281b0ce6aa8f3fc1e54097f4c9d5aa31b15b8060afa431507272ecac1f4858fe0ca94d6ed47e72b073c13f830008e8d65a56d91789635f1f9534359ebf72f02c988d1be7b26d406f9370b82e035d0fc30ed629a8ad8427c21e0961b74303c1ad627e089aa8792987c7c500aad41d462375dafb30af6cf62edb0980d50c72279263214ae9d0a5d67dddba1aeee3c41c0cdafe5e9996697b7064392843503d598c7959d8d2c254597f004b9abf469bd46b060ab0375f4fc0b2a52df0ade558728c8bae4c714ea90d66749e4187bc07dbc915186852ac4acff0b5b76bcb3172f10d6b1e2f67591b4442cb40752da8b4b4ede7e1d057d7a630e5e6c35ffc0e72be577e82d7724fa4e9e4fea3b3dda296747fdf143f1d3c87c3ce2446dedbde4b3c7401a219197e4acb62728e6138a192b9aabe05c6dd66f01c3f83b2dcd49f146984b70d53427500b99885319a0aacbcb74a3b8c3866c2ad42e344f505c892724aab1b3839b0502b4daa49717ae63ffc475fe0c897df0ae1397185ee00999526483f205941de30807a329e3f48d72359ae9e7a2dcfa7f081b0c9e110fd286d417d899170cfbd7fcbf16f25afc9f371be588c5a94724ff33d22981affa5efd372443cbfc1587e4f8f00a1ec64207e9a9f00104923094d509acbe06b572b8b5c3379e284c0b3492ca0a4b867e148c8b0cdb64977ea49e342645a3b3477fc5071597757fef4a94ef184dbfd6f56d2f507dbf24c2f233fe00a553201e142f4ea3c72c5ae3b1474dfb8f2ac631822d2c4feb34516a823da2449861f84de4ae17e24727ec9c956feb00132ee5a52f452c4bab625e193ce422c99a7789dda2a9aee587293ed37349e597e279d8295b510472c0ead9068b6be220e6577aa3b925297d472d644f7842b33363d17dc0c661f68eab96116a0576f0d512a946cdada13e7db54b79885e1b9670a552470026666c11887aa40c3a2c17daeb65a221772c3db77727f06e18378723c68701173a5c0c4c3d23f20cd0922d119c31a459dcb9c9c2c619bb4b96ae5b46474550cd57e70ac920e8de82eaa52dd5b0d62a6fcf07a819e72469ce976c552e09b13fecd7800cb9c0da4ae1bad554a50daa8fd79ca32fdb2725ddd55ae0dbc373edb886e04ad9e4dc5d0be1f3db7930a05eed228a9cca56372880fd971c94ebe8d1d2c8488f0a35c46966028191fb796d56a841d691e3be05c3d4adc301c9c4bd9ba30fd6b7c9755d792de29c3765e8c814c3e89a81e33ee30193e4d945dbcb7be474908a2e844d695bbc0e18ba12082c3fd847f5553914a72b9c495ef76fc7f7b7faef09a576811c4a87767742dc243c3ee82b80e09de437235cb2a6fe8c82feae6e9d3f38525877814deef224494c26be7ec60a98a47ea7218bd7b9f6cc9a090b6507899ed1759f3d07b9e245abab2c0a68685395c51ee72639f226159cb5156dde4f4aee027599829e052c12e70dc9a7aa6af5843be0772c6b27f91ba7f4ea062dba59f615a1f92d594d776710ad9c11f90d538cdd1cc72d1c91e6342c983342336cca8fd47fd419250b4ba940977b929b6caa3e87e3b72ae8b199a4f7b8a674bf24a7db1f163b43a39174a0f89c520a29f07532bfb663d450a49c2e5b3676a0995db7ac651bd2df93639a7c437b1faf622870ffe30c63eee50bd31957a6cd80630be3c6678e5d78a349a86bf95821e20a9455771d9b923e1b6466aad68e2e103306ca69be5497152cd57c696ce2819f6dcbffb56a3e77220740081bc4148ebc180962fabebd154b71c7999b4959d305b716b08bcb23e72b708bef0661a4ef1a733b5c506f0026bee9c328fa53f6f00fc625e842b6d281d7e67202f24da43176ae6dc12476cc182cc579aa85ba8d7af156c91f4b8cc4400ae20217f39f3cb6d5681158a67e21c3952342b35d9c70954aa6ed640459f01178de6a260870159127c202297c0cdcffb9cf8c1330e59fb6778b7d2b4569ee521d83767e1e22d3b0a653c6c1af199422e17a8b685be91ff6ac16962439fb922725a3d3554c2cfe885e7109306b02e3937199ff0cb28257cd6ee7f45c96fc2f7006cae8bafd07eda77c20d5fad52c58664effbf16a4825269aa1d80fc30708c272a8a99f4feec15191ceb8eefb17197c885132c9103e28030c9993b6ca7c5512720388b6c22b9465216da8c43443d6ff4e6584a575af80ad322c5fbf1865121a72234856b52a6e53c878d23a6e160ac25f5172ba973a674efc75db60d007cb9e5f3fccbd1013140fa9d03f3f9a91f516bf7c8bd1fdb90a214223c3ecc45ca3ca72eca5fd2aee71504aafed4b0823588759fb51532c76bb75331e73ecc578155972a28aa2b0c4c15b0bf82792f4c712919afd1e0a4490dcd25005d31714ac93c5141e1a1c00f679183b557bbfaf38654064d0fb1f62f72920d3b9e9a8bba36cf0721048f32a15bd599525524d5fa5f38fc82f825e7d85e4197e23c321657f5944721bdbfa51f3fae6d81af1e078ec0b715db89c9edf7b54cc8b615e96fbbb17f42854e448f7a71e0f162275ec7f31e3d91cea8771ad900a589b9b1689b13fb4f972ba4fc66e4e1d1551a81ab6fee49f5dbb7a00ef93bc77e6acec79b0dc1f7a9e7224cf2445e47ec7f8cc18566cf4388c545b099fa92b19c38609a063b20e5ff172dd8b853fa5d1c2806adcb44f22199bfed4aa0d4af1edff22ccad509024687a72a8f809c376c3ee94e9ee3a422d5c361819eb2f86bcda1bda74d3ff23af4d7f724c964b31233f46ec4b25495b8212b2312a78e923673340c36cb9a23b3e5fa62abd1760e6154a7a48c60369bfa2869026861dc9e408d9e50c283c9b74f3c7ed72f1c88cea6e9f1520dfeb89fc1b051283117e2b67b5c56110d4ce9d234a31b27226dbc749e2d22e70147836487a7fe70fabbdf626c3512a3ccf61b6535c07bf0010334d31bd5bc528e61b43da05f548744bf1bafe9bf3ca247ed3e994e318d3729dc8ddf119a39aad82e910c6b8df41638cdb0e99be433c13c874ca769f53c9726b8752bcb080ee9540d4b8f0f9060cbadbcd0230b1a92af922995ae515514a72d95f2119bf0148effe9c7609da9260803ec90a54079f1e04bf8ea4dd020c355d5840e896b8ae2dc35a871eed530a4f6dcb9b57e6afe3d43558edd1fb453cdb7217950c9145a5812f1d8e56a308995e2c13d0e5d2b1738b84a55f73ca6140dc363c4138c7f58f1108d31f10fc7a2f51c77b74eb6c28f5dc981c6494d6366ad772591582c25bda5f4c45ea6e18e22476e5d8a613faf6c054ad705805c5c9150b72ed421da07a7071e539eba9b789b974632f8e76675003b445addef453db1fd072f3869b9354cff2347ecf02698e09d98f9185b97cb64818c84b361f85550b4d728cc740c0610945e23fa9afcdd01388aa4240cb73e539360a8e1c42602fcd9b0acf104610a9c7180e7bfd595d3d8077d5fd82e220412bdd4f66e9cf53862123726cf6214aef73ad1a36673688e7284c3ecc07599fd03ae55e38c25977513bce66507d379777378240db534d1a3051587e1737714a57b2ac4fb604a1861a889a72136fffe0929a49fab0defc869278f2d3cdb04d2efca2fe2c1ce6b6c829bb4f723880dbbffc1ed82a072c6a8604658797d9d7a2ecc0977322012227d8e8f10972208a87c27b334b52a920c09ee73a322b5612d7536eb12c48abc38d1759c4a57231ab8313ace7503460d62c407a59c3566e0f37075366ffbf45bbb828561c3a57476a4033ffbbf80a7a924bd9f0adadf2fa4a74eda64729932cc65d79ed55a5722a925d57cd2e8bad89815a81a2a8499c10fc3b6a91d0a2843dd12d3f77abe572f44ac515518a7ef97e15383127442510b782957ef9302ef36141b656c5424472aa05f290078675ef3e42cd626a1a8b86772489612ead8287a8844281e715f07239c769be3ee4e769ad01a62dccd32cef99e16ece0273df520292fbca249d5072e22e55e8763fbaf0844222b23cd149cb26fbc4ed0c0a81244b9080c0fe057a7270189c17393f0f238696de73f12d982092a47829803707f9cf4667dbda226c16bc24c4bd4fd23664aee7c70a5ac25011c1e69a8fd2b4c3e982d89bd5b6234672bad60b13b4d233e587b4c0c47b062a21b0f4ac31e040d629d4760135c4fe8c399c7c055c6c5d428e77a20b8b32a401de32009da152b98a3e45c884a28289d6724589459e2ae7bae8951f1212cbbe0417ee9401f1aeba51c61e2d991c2204f972243c4801b5b27643862c1b837e136324091bbf99bac0747162aff512119134729be7135defef49fa8bada19b02afd9df440bf18b526ec8cc4d6cb5a43a4aab6076ac4c5ba4e6a02ac0fc5037155e5d13e334763d0b1fe394f551cc9c6cf9a72886ee5069660c22f91ef26ec6589d1ea049811c68d0eceecf39af37a5cefb147208270e532d2cf5f2fa113caa2f0bd65c729f0be7ff672eba9996c952d1dbb94f44371c7d8c685d1f2f1f7cae6f06366beaed2d31559bd9039938ce86b916866522f6e29fc8747097af3b9778aaba922858c75debb70b8373a2a7ce0b7614bb721f046e8c1955bcb949bb8910d5d76f208898320c1f59e9f6018e3e705bd0df0a7c5262ceef63bdc5636c2743d6d3307149bf03f3766ede1729f4931baf1f807258e2d9239fea79a8ef22b483a836aea8b18567e0704a449bda8b03b1be86d30fb7e2487588bebb513300b88a0fbfad9291d659352b2477e0fd09125bd3640211669b8bc7cc7d1081f52ed9a74389cd46588e1e6959ff346ec41b3c7394a4bc72d86ae09c97a288a9c0d3d079e9480683e3a80a8498d6c9be9f96e101054e2f7201b0ba88b352291942424bc84f167a37e53857ebc98a517c20fdd0a9c61dfc72ec78bf9e9157ca7e877e9bb9b3e5335b6d1e1bbc7fe5bcc2cacbf3d0e0e03e0a88230a663d5d7d62582716e52892a125e32f23b93b7333c70b300d563ac08772d710c1e0b710a27902651b0f4fcb4716be237e9dfb5f6355ccc348a96bd6012f4ca3aa56021cbfc7d559fcbb364ea28f949e45e4de8303f7a936a3dc0b7bcb724a6b066750c5d14181afd380ec8384dd93d2acb8620d664ac3d03d19839f2b170d9e8a6fb7e0cf1415a611b73eac7d02f30c78541e47d087dcee7bac2f39a02a99b5bb0cafc3c9b2774e67d73cc4d8a26016d685793a2e947b4870020f699172032d437b92219433e9bd5d629f7a536af5f0e35427db98db49c227ea4d330c72213f82e5577bafac3b0472ae7f995da5a47222a63a0087a91b478f313c55d27263a996ddd7d37e91972e36437be2b039652a12b0e7b33e528415c5eef27ce4449d87d489e61f47074383ba776a0f324c048e5d25dcb871d1c083b1edb89d187202cf4b455cbf73c9d37e05a589dfdca9f569003cfa98ef6943aae8fa1bf020720a25d9c677687cb4247b1dbeea32f9fc4bf03a75c27ece43dae916f0b63a3564ef42453e546cd20c416523b04e49dd293472cac6557469777b251ebb68123e32a636e44b1ddef2b038c0677bed354c8edab5af0fbb15a62298e374405836477233ee06375be96cff358389b9b3eac418e7d94bb5e044d9ba00ac462e0e573e72ab9b59403db9a5a6ce7d0da25dd27239e041c71e31fd35d78326c079258bb809ac8e7d53f60813e6038b1bf4a941165d5669c7634539d5f28698aabe154bf7729bec5fe0a1c06d97107787c2f256b0c36895469d08beb107f237d93265a3a5726591d02a4776538ef444e13bfa52ee8aceca9a120634654049f7a9dea4f7e70da3b7221173c245bb4aad80129feeb9f68cc7a5ca6b001a89ed17cbf9309df37225d256a44afa73d2ca3f8d72a5057060ff1767064701552734a4b58f8ad7c0725abb81772b599c0b9368ed34496d78335dc4fadc444d05a836460b4c9e7465723ad5f929c676325fbe7589de43f57b9faf58f4100772b569bd51438da8139f1e4e5cd20e55832ced73a004042fc69d51065ae525dca24cbf80e6ed42172c983fb36d0d0fd7a03e5d24e25aa6d0ab970126d21b582314178a6f3822e1ee6a0b726ee5eb99106e2a9203518046a5682e1851efc7a7a0c8aba671648af073b73472510f9c47bf8cf334305e7dad02b9bcbde5da5b753632d6bcf3eef1828161cb4a7dc1490bc9c08c5147a0ea0e38c794471a4561f74e6db97c1363dbb0ddabd8546376e3f6d726a7e8a0677d6a8c0027f61555fc20968aacd548d04639ba92c22909b1185d357b31dae3c6f0fb4d95f84913be709cfb0f41c7b09f1dabc0172b726b392603d604b712c02684c3f0b1f3ce6918ce8a38a4b07f6c8d3b95ffb67272aa078b9a4dffec5d3f1bac4db4a71c947ee70754908de9a645f60045462eb6728b04140f417a374d717348826496c20937f54ba469da2f235a421f5ba1e9bd7291f02e2ab8d8bd5316960a4c68be0cbda13190a2aa4ab8814c85ec0981bf6172260f5dcb4c52c702e327928c34b7c84ad30e58b15b8ed150761aa1c97caf0b724a3c406006eb8516fd38220d42638d29bbd9cfdc4d61d19424061931898b9972778ac235a455b67cf3e2a35a10055576631b2ee336e2e81dad6e6a441ca2641d5aea5e0623a2df27162095c73ff148eeca3f88a52d7280754cd521b227316a6b6acfd349e9183f9f7ba92b20446b3e10ae7cf734970b20f8c1143bf1390f3d5e73bd1834fa5d2f67d3e5e25604f4ecca879f896e3b4e4a45291e95fba5289a06386e451e8897282e251fd34b3be77f84434b6e74fa4084990f203efd54691457eae240dbdaad89e8b43ec2934b54691169fa4cf9d8a05c9958439445697a19722afb7a3e3f0abc409143f132d40ea0fafef98cb849747e1f8a77bcc69c438272bea4a7f1170437a8cc02d7f09c0551cd41610fa27e061ed0597fc47fcf846d72a420c6d3bfe5656c1759b0057011edc875470c393c8cbb8d9ae65fbc9be0405c3983452f72a3c72012d91358c2651bbcac4ea58403c57af93deb241a7b902810b4372cb7c3b05f1dcf946574806a8df9b6908a236529f0f95489164a12fe240c8f6fc752af5623778f16100c4370f9addef192c4c0b4c7ad10847a6dc86fa4727183d94bb5dd2c51e29db6bc200f771f2bb88e08f4d1ccb9ef6bd16888cf7c7201f4ffae466869390a3e273bebcfcf2d0fd991078f6e2e537ed6800663756b72a33e44084a98d342b56b340d1abc6a7a74f8006239eb20d11b753b8ab2ad5372dfa1808e90b0ff953231eecd96f5ff632e07adb6ab1f6f6916e3d74fe3d9407223e81a02fce1feff00a3775bf36d3cc5deff4abea0eccca530bd6b151a8ec4502075871173a0eea4da9929bdb4684bf4c547273a90aaff42b9aa8e5f0aab50720da0c83ef30d48038615f1789bde845e13aee53d445a770503ca2318bbc4ff0beb422cb15811b64d57f6398e54ec9f23154448de796ae84fa3307daab8e525721a0f312eda609aef7108d94af3387dd33b5b949e112e4cf4a80223c50a4f6a72a702e1396acb4d86f7e0e204609679f5797a1818331197b12a1f8d9cf68cdc402749030901302e2b31023777b1a9f39ddadaf7586c2d88bab0cb13a1a54c2572d451595afddc257e95ca081c157845c3ec0bbbaf2ae91a8c02620ab022258172972062347222e5aa8ad6ca3a235969ff3d73046b69eed5dfd682ae7d6124280ec9b545d1115f85a939aee88233fa190f759032666e6736a6841f7a6fc593777281f7999d1b1a504bbd95733100830309c79d9dd7589707d9ad971e364e94531c45f0f7e21099f76edc44eae33013a89f7b744ed716c6695b8d6997c1fc0fed06cbb5abc9befdaba51ae872d60f2b1efc5aa99a2c1b921f34f7fbfe1a2fdf1b729406eadf4cede0f0743ddadec62ef89faf89afb9eb3fba9372844140b5e03f7290d7185fb933820c21139adda45cf675edba46bf52e43cbb0c46e8e44531bf723b467e18709259159ab04454bea6cac3e09d20d8d46748662252868596017472196d133d7561de07392bf1d43cbf9e1bb0a2a53707368cdc8d8687ff79bb3b7204d6a5184ff385888ea9cc70e1158ec269d8cb9cc04efb939079a940db9a076512b8f2bbf0ff5d4ab16413a9147b3e96eeb2caaf239c850ae8db8b9371437f725647cc9ebe23b25b669f18fb6f91180bae88239a5a40c1f686c32983b2700b7279357525ddc6b34fbd9ef19c84e63d1a75c566c77e81fc8385fa244f867cf84e7a77b66deccec55ddd763f72b5ee154ceffd030ecb33468ea1fa82aff817807295aed3bc19db5b9a7ec0e9f2a72a526705fc06b455fdab1d4a77fd6be6798b1e9eddbcc61f9fbcc329fb3c16b6690779968686033ce73bcd68cf10ddb507d072221ffc1ea4fe2f8586e29d108747b5f25d746767f0cdb5e9731f018cc00e8459f5300669b63ce3f492e8f369ec45575212d1aa300fecedc5375f3150e9760d66cfbf677c196105899213ab991ce1c112b037e6a44fa0721ed590e15e6db59910a7a9fe6e0763bf11da931d77c3aac2406fa8f1bece20f071d52f4ffeddba71721b4fdb14b6c6fe6ecfe878721d72e0e1e46be40b289704eeb600b653c64e215c629799e3f36491563f67be8f8d98a542988847df4e52349a45b13330d95ccf7282a2d0f53c79f6f0776b8e5eadafb7110fd5a6d658f90e76033f72b896fed972ada105729cced8a121d3e0303f3d4877f213d8275a046a142199f0b7250886725c8253776b91fde4cc372e1b437b74880da13da91147231451fc18fbeffaaf729d7518aeb4721f242c0c04194786b6eba252b132143daef5b41d3cbee6efad72b8701328e554a3e48d2be47a90d997c6c73570a31984e481b794307e4e4b0972c88cb15160ceebc396b48176f3f3616177f91d8c775a35555e20cc6847d6b40ed2de191351decafa8db196fbfcd3f2125fdfb2b4145dbbde29f021cf8b0ddd21e04c293f0b3847cecb6b20dce8cc685a501e187d14503e7610fce650a052f872c7d5e50d5d3dd11fda9a231d114ab20608a4308888aca7bcf099264692a8427288b2cbb97f5ebc9255a35cbb95d8d4f96e2224cbe3ceb371311173d1bc2f8324a68001754dee40c3efacb6fa2402c562ef7144933c6cb287fd803b82da272143b9696e02c62c1cd3a2a447ec6423c526b0f6e53800f7fe034ab2e789c31e9d724918cef2391c43f781920dfe50b573d45507fb71560e481b3b096f565cc84f51f51565328f9b125188b8cea5b0fbdf1f6c1648c667938c2b0fcaa1d8d41c22723c5212b9a4c1caf65f68462f058b07081242231d1815ee0d1d6bf1223429087218a06c5b92b341ca51e571bc1014f6528d77cb9d95025cf792da0d6f0a245f728b0d2da7a65fd3e8a75917b96224137a04646101c02b142b57f9b9e46724a8723dab8ded3517ca066e2e825224b35acbd1d8b825e50198fbc44f8ce21aca7872e5c04fc9cbfbab368bb6844ef92a750640246d8391f399aae2219d324941937269e8739c4b28b4867d6548193258b8710ade34d31f713d7ab80dae66d7554423db6ee27811c8c58d14cd1879452004369fdb4dc2468154dbc7b25ce369df8c7207e4ace7873f77527297bae9c75d6c9feeb2e9f533048c2620602eb279d6442dc7e5e21f3cf1fd1b10041e77af7875047a269c7c3590676e616d37efa5fc116fb6bf93d43df57e3d1f06a096b1ba0cd5f51bd36c5d36dced5b4ccaa6e371ed309560263a707367ae0413ed7c0e191eb3ada07ec207af987e8a22989ac2ae0b721e6c795181003eab9a1055c616fa1bde058a0149f37977ba1a2c9969d6fdf70e885bff7c0c85d1961772383ccbad0cd7ee34c0fb872f11662ede37cc0505aa722eb2712a4d2f2d5d64dd5bb74fa898a6f6d9f2856de8593ba2f75427bbc89072434e7ee0c78686f417141892a94cdb65792dd00200dfae89f5daa0d259be28053e55c56c24c09c3ee67f32312896cd28d7c0bc39b4daff0a2a123283d4c3ae27f3f7b720c5416a83d8eac5ad89ba377a1f61ee27871a947ab93bd04b3990af7210080e6b356deb717946b9d263569e6939550f71942e78e375e37b3e659f3f5262ab0a54466f585555dfb8a0f4c27448f35da281f47ceae574af0f006fc2a654ef487f3b88dda365f85839df5f468eaa76adda2cd228d3f27f99211bfdc9a872f57432982c7e39c77cab497a8a3691fd4716dcd8c52fcf675117be7603e95072c771168837c993ad9cf12ed9e12f9881b02300cae1d8a9a636272c12a899b56eba43cbc02eafd68fd304073d7cc145279e8745e38e9a2ada5704a1a575d06965a0886a30699d126cd2c706748f58da8c16f78acb95056ee8fbb7c9dcc036d172c224f83823ce2413f6521d52f2f41bbffb42b7e2161b24edf40c084e6a187e72b50f57ac51ccd447d93440e411e4dc3ebc956af5ac4d219565a5e63c36c61d5c3ebbcc0fedfaa6186811f0b202fea61b3c1a7405dd02ac6d252d434ab901e1724acea7bf8b84585be947ac1227208b9595f2a252fd23a86e53bfe5d55c5237725be78c2afeeb5826618fbf60b20ca49a9c3b82e7365babdd8b94e0f1fe885f41b5e3076aecddb65327b23842d2f209b0afcaae8db20d551ef30355a7681077726cb73a6f237f794059f0f06029d15af8ec1adfaa823441fffb028e3c5f54a522b1711147169d2e101d9eacafa7295ea1fc540bb85e0bc742872bfa1ae04ab072df287feb45519599f472c36bd06666d3ec8d77a5d2f31b2ce1c0ceac0500ba7213cda8aedd9cf427e700fee7a05173c0164210890e4f416a554aec567d12f507c108d3381070a93165a75d45ab3a09babad3737c9aef610285a79ee295171372e15e603425c9e9c6ccf69a419372af5c1af31295053375f49d65f9a2cb17d267ced1b3984635c86af7cf1988b907dc83d379f1bfdaffae6400c424e93dad420839a0815606f9ddbd558f157cb0cb50fc5695b2b14081d7b12dde013c42a8fc725cc0cf9aad7747770a5d6ca0136cebaf695e4b388dd89fb1871e7ddf316442362a63237710c25e08943f8c121f0935b299ba90b38e933d4792c7230681c81503f5ccf2dc0f7b12246cb5acf6c209e1ff80f0bd122ac735498c2139d074c3da725807a1305b39447ceb10996dd5c767865ae77d49388b1d46c2f5e28db2c3486df671cb2170bfce14c3f40cf53c7d0cd7eb1cac5d86b71da1a8319a04b4333c72383840fa3f0b4d893a49c220e07abddac6c7dbb126bc6a424941a658b17b051b996ff8a4e32197dd39ab1c853c3752e38d546e56509d49442b8557b45005a572fb1880b9b483fc4088f17c5683a39d05a1463de7591faeb7fc143a762e7f4672638857430829560e6bf21bcd2d6f8006c823ebdfaaa5395d7fc851b79dfa007274a5fae0e20fa25c75c2e8c3f6a01dc4e2ede141f5a0579610020e04160c31390a5149738e62c5d93b1ea22db052e665ebcc9c5585f1915e29bfe6fbcd7142723bc0cfb5e60804721576238e785cd9a5deac95b3fa58f2f5cee2bdbdf336fb434f07e5c9aa538927a6b66ab3509aea8c7b10a3a3491fd46fe8ac1a70e7f10572fc3d58d7de5e96683a1bfc6eb2a590612a042349a541ca2fae2f85a86a45527267d6d3082e6777a882e7b6ea497e56aadaa2fc865dd366a6d972ee271272957264aec222a92aa5c6ae84d5a6a50f424d4d7fb91f727a3163bdd22a8b8a537051004395f9dd94afdaf23484281380191a974b7e5259b2840f6edcbeb81620a522af5b820e51df83ac3874eba9d2c8d942c7fe6f7cac9ec428a29158a9213f5c72d13ec476671f4981cc88f8d7fbaf19de512a5a1009df9850ee794874e51d9872cdccc998deee0f166d1e29483d612a091898a77a23cb8252ce6b9f99bb0acd728babef2a858093e5c038e551039c9e4f71025c2f39045bfbd8e08587e3130b72ed45bb2904a57fd73c9273e1a92388a3046fcd288b7724fd6d52f6d050994037dcef2772d50330faf5cf0c6ec7efeac5893d1d33d9ce42c56d2cad9a558699722324ce01829f6a5f44eb553f3f9e7323cc41d77cfb4526c971066736bf42e1677e4eada8e5e73312626b0ab95e33922e5b4230e6735c822237814d14d8b13e72163fb580d0c138caa4391213d60162c9761630a48c85ddf37166a9921c430304127d059cdf1e7d67e67ff35849183566f6abd7d3d2e1418c3d47be661a11b47218837c9d29b910cd1a58d722f998b8d79b74feb3f0c309165eb29e7f45e8575854ac3a203be1cf519cd0d252ede9f2bebb59afcde9a34f56d39d1c3153feac720e41d87112e498483f4a0bdf77287b24c50eedbc292e711da7b2913f057e4565b99b906673a7cb562484f7ae9320b357204cc3e9de471d0b2d05b36ccb8cce58ff9d5f0fa58580f47e0029ee175c75f8da25a4a9e31b8ef9913118f06648a05369eb95912596303919bbf22888dab1e433a10ae4d39390d7f9ba558ba63d60260eaf276831990ff8c3f8673bf8163f6b99e36e0b69b788c543b94283fef3f56fef7a6a3bf92e123ac765d2bf17ae6ae605d0401f3e708115e645d2fbc131367205bc2a1cdfde834a3bad4ab8a5204bdcfb7ed4850e19bee0bacdb1fcb7664e726e962d30593615fc464a8c24d935180edc8a70351ba9ed4449221d5a78c1624a246c44f08090c0afe1004182531a7f8ae712937252e98b6dbb2e4b314dd7d425edbe95d2bb6c23bcea51651cac9616321bcd4cc0b96ad8a51de2550d4238ea3812f87e0b8af31c588e8d0c33da7dd90ec6792159a118387e5f7eb6b4a9476e72e5f1024b0e517f4312d5399219f101ad9a0722c1c363db6d1fdb1bf0b167937259f10e1c5cae71e7d26c020406f36ea334fb7f4a08ea33b288d5ff4d5711a111270d6017f95bcca4ae2069dbdb8dbeeaa0332ea16a4e8f55f97122676f07fb723672db101856c05531647dfecd60cf30ba34db8788c4c70374bc16da27b6cc5451863464fec17e1243f38db319a0e727555f290f3c0524c44e828078ae0d4772f64fe1e0b0ca14fd6b885680c843987d026c63fd7fb459f2cdbc5ba49e380970696e139262eb170aca3d0e5a9b6e280babaf73d77543edf4e5cf857f1d8f6f72b3be8cf1153d019658bd8440ff7e96af9b1d08f3a50c419df20025bf32a85f7286fb5cd2f8be85f39e23da3f7ef15f7c458c0157dee13858af4682520b8bfc72b3b0536ed0f54d0ba5cd4d511b14effe0586ec551698487a13071609dcd66f2eefb0cb97ce3aa3630615bd0846119622ccba3c78858803b2e4ec975f1a3631725e13af3fdc60a36f9cc8ebf6f178b60044bfbfd7206a0faeb88b5727a950287209a9c24b2a11f46ebf841d8686d103cbc595e5c31e72047ed7302077f0fad37243d4087fd39e4793729a8deb1e2f4788e804808f45c586c40e368a0ccecb14014f6a3687f3eb7c75b6d487132d2b69f05d2a1947e8be269135b72747ace6bd0ff80e3d7735349e73d3a5564248d5f965af78b913126e65fc473365ec4e78a136447bfcb22aef44deb39f92a6ba9906ceb6ba926f7565db1f2c6fe4c8543be272aff05ce45d9b6441fd85edecfc48115a3b6e74714b53d137cfdbddf493f8f40821d762cfda0752a41f7d83ed1d1314d2c64b9c65f05c05bb82e35ca28fb4fe7212dfbc468f660221e217d79a79705617411cc244f35842eb48fd85ada0a1e972c6ca9828ca312748a7180a688710b5e4607b2bd11edd6651bd5c9448e9f2b364f1075920e31ef72593682cb7415e0fb87791101605e57ae4b88e450eb1c8dd72c9c5ac9cba31ed8158d87594110bb731606335219d63b9fbe6d9258f92f7fe72c86c8c19638ab79a03a9e3562db76afb252607d2d834cc752b914bd89b6d9049dd170036273deb74ba64db63ad87ea9ec0b89134f81daba220a33ed54adb775f6ba6e1f2846d3b8872d477c94de49d11e46ec48248ac1358bd60fd376fb5966f9779e0555a1d1e7d1080d36370d2f243c176e987aad17d6a196044dbeb6fe71aeed3fc7c0ef4615d4fc355e92e71a1e2c4823d840ea283e16eb872f9f218c12a8e8a2b3c22393bf2a72f698c1c8e16162f0b9249308c7046e2998dd0faea697208c9acfe0bace44aef2cf257cfbe6039758280ee08511c3a71825d8fe12b4d6f86b8829ae5c2527a5a2829021115ad145904a1e49584992cf17743079fffe840ae68768d69fa6c2d427084f4d91be5b5c75f9b0805e7d4528725e170b6399122e2caff270d1296352cf1987e77fd6201bb63af57c2062c9a07b9b68620ebc172c53afa5dfc50e41fa2660a39ea531849db9783ff8abf8e49253fe68b20104872716250bd12256cec79318fce1a8cfe0341055302a67b6d89700456fb33cd4372f10a99fdd7ce0dc5f648d29270ba4b9e04ee702ee6434cf3f457383680d061720c9e5a2381f059802625fac201dd137d74daaaf3418b369858bb45fd8a54d4722c331ca0f117da9a7da9a5d6b69452b7bb183a727e5b61ec82bbf56414ef896d2058e57611fd58d26ca96b3ae9476c9629ef0838af5dd5c3d33fdccc6988fe72d2bc1e6c2d829b844ffaf23379f66c8763a973a415f42cfd208de699bca99272aa772f8a974a7634b117b1dfdf7e1bacffbc5360209e903971d9e5edf7504772d4bfefe2cdd9f6abae55c70ec5f20d7bc1201cf971457113cfb86f49a0b4c07261f6cc24b6204af494eec28d920e89a182e0277d420ef646048572ed20953a72a9b7ab6b23880c05c367c8719a4c6af1248ecccd0d59fc37f73538d34b419e61be5d248ba10b9f76df5795a62e2522afa2b10f5d53c1a957621d7a47c639fc72a3db3d5067c938cb637ee96f1b597428025103224c165006b0a933fa8725077253fa1f06739badfa10220da7c8cef70ab0890d04703a217b9dc746664ea2401fe2c8a5f67221b5e43d10abc0d73c7144d1934c11c6127852ff3b231c3b40e555ef773e484e7a124d9c24b0e8320bda403cfce7c7b2f32c96da6ee442487f88729f3db8c4e451a7fa1addc9830d021d0c15a1f91d045d1fb03189d9a128c990724ddeba48a516a7bb33026ab917fbc4ffdf800d53379c2de9f67c0bb77e9c11323abe813033fd7ec3768d82deb5a0f6ab6762a58e2beab589971e4725ea523f402cfd18873c951b72b65d45ad1d5615adb0907257e81d77ca7cc790785cd36e72a277e8ff47b46cafd69cbef04edc76ce11902719c12e4101c35b5d9a38bbe800730650302b6a1ac061bb7383b71c269b13ed91da3270ce02a7f3ba83fb233a729f8aeeaa80ba7334579d73dddb9c0a55a9d236d73c2605f967a5e52c63dc2b33bec41966028b5cc1a97e94c237e5d6bb86eef7893e68a4be6a7767c45d20170e1c8b27444b3d5c3e892d82e618118366ba8e655b5184b2e0c5e0757073760972b30d5393a423513369fe6025c84e93793b9eb436f5d43d5d1e03f0a4dcfe1172b74610fbde117a4b6300256e846e0003f3aaadf8184082d939d9c658e07b1472b6c1c3be3e5d8685969b4cd2d838347d387e3e50f5e0097d7cd26220f64f061e67868ccc9076b8a3f71c76f1cd6db01e92cbbd262dc92300b2898911a063b85f63003100165ca42e891a1bef36f6ca9c3bcca4a8e6a23bf928535c2491770372a8cb2348eb21105a4fb06749c1724b2f8aeaf7715f9515c8a0bdd063e4937f72b9973c073804cb872f2b0c73ba2d0f9a96aa73e3197dc186ad5d84d55be8da68ac19f9b9c79c179edddb7919b97ff47ecc0d330c8468a9e1d03295acecbefd72048f7e2d54d919fdaaa37f4ce23584e52bf5b86e059f9a2cae690478aa6a6272ed661fcbb0cb86f8f85ab722432d0663689cad3fa782fd745382247ff2c57972ba38dcf635e3df65be3f0fef067bf030db570f1de6ce1e9eb5aad60d1c191d722432761cccab1a74361d3dfd4f0185fecf241b3acb19429f017682556e650812bcfe8b15f431fa2e1303f11102823045551b07ecf33e3ff9e002e2b450c0881b87b6e6e05bf7607c233f41c3d306c4e65357075b4f7f70209519e7976be0b03be986e30233fe47909debe6cf34cbc9e3b9acffd341d552b013573e2ca7044743e6246944168ad8cfef417a10a4c8538377c37c00899b641ccc0b7d83e522c16be17673c444cff8b7623701b46f264d3c4acc2af63f39e4c4fa8f69400c07d471b23b88df905eef33ea789eca0280526916fb2a2ec25e2bfe4e075d4bf1bf39640fd5467b319c9f3043867afcf3c4071c700bb7bf353a7bcc3719c7f30f4ddf72aabfaaacd7ae7359c99670df7c545f7bc3b189c76bde68bc5da6b9f8d77b2b21fa3c6de583e1b63e76c1dc91e9b9c1eec5ef5a0191ed697a6f85691786b70272b61d4859af2eb6ecf7d96dd7eded563783fb40c78500579e8e359fe1594896725c8dc26c071e4287b5f87b6f4dd670cd790a85186f4fdc27b978c5c762b19a5373f82a44692373355ea71269b5c88e404315d07f01ce35abbb0c90705b3cee7235b6d074b8ecce176cd94a880897353b53224ad7e18a39942fbafef10c0a6e554afa5b0fef56db6745135bd293e0e4f779d29ad768791f288898fd8bc291c772898edd11b8b8f3180fc006bd7bce956b709723888a1e3df970bb5ebfb504204033e26f8b44526a5a6a98a2d67fd71cc22b292eebceab87c6aadbce42b95ee07218fc737db288669b54bf88b715d81a2524c76361a0995dfa159b349c18d6b8411f582a2ba0b814adfd9817c005f9989db40af62cc3a52f6b292f7192b6995807c37d6107d5a79023b67b0fcc69d1a41cda0ead9435555fbc0a7b1467b1138a720df6e4fc37a4a3f8f1fbb9662926fccbab219f843f0a6fdb784a14aa358e88724ac2167cf094db9f4d7af84bdfa999dd4f3ecc1affcbf7b9b2b66b36fe220267a641a7cf9b6f84665fdbc5d40e5937dae94645bc388d9cf22c010cb97beec77264f47fe23c36fda9ddff8d235bc650f12939fcd3273e5eb616604c48c64d183b17015b25be0872fc3b05415b819e6d144bd989ba7cc5594e47b078f430d297722e768cbf02e11e65c6af41dde42b537e95f628129da13096f9f50645da90937277eaa2d8597f965ac858932918bcc969d2ac7526233aa97ba5ea92a6f0a9755a76610ce98fada8ca5f8900e3ea99f3f20142f0736617b89daf91f163f048815750b69375193b6799da6d0279f7882cfa17c5044b4d3f56d6d88e1a08aa42a105c6b5e945bd3e8eff108b67ca7e219423b38dd94a1816b81bb05dd30852982e72f9526df64ae557b6d7533dd2c852daacc334a38652cc19ad82f965bf06b2f6723395de067a88250c8aad3de5d0e52b6e4f0582f7dcd15ada36c3a3fd2d287949bf89a47fd38e764fc4d61f384913884714755d6ebc584758e439c84488b8965be2e6132c44a962203e0058c511d4e938ffcb1b5db7b75d8960733ea860879f72572dcb64fa053f55d56aa1ae43089a611666401cd3dc4e02a2c42fd7a730b328c36774fa9751e8a33d997ea78620d2ed56022993c6c252c751127d1b12152d5a4e61f33549ac6bec5b5ece2aaf2211fa4b17ecce3849cc239aa40d9b47a0eb726163597e9a0d0c7ece657cf5a996c487ac4689d9b6bc642767f0f08b39aa0123e33b0be5b2c91d5aa5722820b0e73ef9a65dddebbbdd3797f0270b4d218f0650798c64d4dfa1a2109402dc9ae1be012f251dc9a1f432d0322fac8e3695c244725ace2f835aad29f2dba0d2f33f9687e1ca7eed7b21e59fcc5686ea8be3996c1f47f7c17c67b3043c48e8bf4e4c4aaa798f7feef1f066baa001d63d447051727262b1772c656b6ba0c63034a851a0ef7fdb2dcefe0418b61c1739894db69506729368b21d89c346fe9b0489204e63b62204368fb3b282a25569eee1a3466f55725ad6f8748b8c6bafa7ad66fc41b0b0396aaadecdc162040464d353825cb0b9146e0a5ec939748499dcd76328abf2d71c50b1cb9470a7211a227177521d635457e3b2ffd0e22b835cdf43b927ca2357fe548c994d82ed6a34f08898782dff45727873e59841c2a880f4ef2a97db5f920fc3518548d456799312a62b77fb611d725be5dcae9edf1b935d3f7b5528ac425e354cc2974a3f980314ac55158dfe6447429085a437940b9ab7c2fb3148f6718e45105bd25c817496498119b019778c4fe8a7d3d892c14b4fe37fa9c15940a9e50c9acc51527a8fe4efbe884b561d5039ada701629a33edae0b6a7ffa377bb8273ec6edc9c74f88cbb7d38f9e95fd63400adad211d8017e50589042c0e71298423cf1c367ff89c10d7d9206e4049ee4725ebb01b4123eea6b7e6c43e5bfb7418f0b09c1b1b255278e085a0a99b52faa726c039ea14e6cb6dfcc5d462d3dd313338132105c61370f3d0a5f41327a1a2805cfc40e6d2e75e148f1d8372bfa0fe0202efd6b9a063a7fb3745101b20b557c722df94e127573f510c6b426c7cf8a7a4f98c8d6f36608e8706c70049179090a723b5d21517f463df4520135e40bfd3bbd8c446118d2060421c12b5cfb8b89b5727b43e79e272674c818ae0782d040b8167a664df7b45834edd8bf782dc0a302727746579a4ca8c044d30515eb204f4e4f53f8c6ee3450ba45f4d2a6190d9d57720aa3556b577336668555ab99edd225f7fb42ce092ad663c3487e34fa7afef6106af7e933d0ba9d6d1249d9d542948e25a9d52e9e09f64e5252e09628e582677298574199b7d4a0443d54d1b808a55326a89fd5c943f9dee511d982eca3ef4e3c5ec5669ab19572eb8bfc313ea4b7c6d5b30fd8e46158c1720684d5d82e5b8d7212e8fa370c928001f5ea3b23e107f8ff923cc2491af1303f7f334cc7037d976dada8629e1048cb258c90c828a25e26ed5c4a3141ea8ba2aa8bf468c8a81faf17d870f2fc78c41a389db0f5f12318161baf33839d547eba70986d89bef17ba8729ae5d06dfd174009c44a5db0cc8cd28bf7e314823a2bc3871fe1447fa706653d31b31281a21f4f837c24783ecb9e97c02565e59394f40dffbda97bf700ee6572d8e020d0b845bfad6fd7feb24a25eec72fddc4c336536b0e22bba4844854045afb028a0fe44e8cdefb52ce37608b445c3cdb55484a31305288594f958e2d52721458cd095605732976b5c4b43fad349b945894546045bf2aec8032e49fb0c23c9f8c465230b3f50d097f16a931a41b7926867a64bd7ea07cad989a8df692331f162ece5cd51de54f99682b298bebca5cab5b00f2473689ce66fc4dd9f4643e72ccf7c5dcd46b54fd8f8910b875f4de3652721ef6490a6f1e59c6976d2cfea47266a7ff0ca5fb84bfaf77c552220232b6452b5bbf72064e1106a876eaea9dfe72c34c2813f2253d67454f9c05b52f4fd49985fc3e662af717a9891bc4f4e16272b1116b0f2afed9ef06eebf9ba3f293f596250d3235881eb83d1afcfec733d81d64c29dc6b2e95002a23a56605b2164e8d3ef5bd3a1c81c9b15f2057e5bb203355bf0b76ea2bdf3dfead16c162d5f1586c4e26c884fd4b6b3ee1b372ffbf9bd385224336a80cf51748398ab23cdedbc293f6b067f28f1d03cd8d193da7ef809723d3630456c42daa92a41610ad109914e7cdab1190f744980069341c9b0faba726161dc2c3114145f5c4c41b74cdee873e02a01f6a3ccba5e676a92a9fe0c242c0f18f5d2a6624329d1ac8094c4512b4483f2e69b780460cad00399e799d4f37221543abd9acf6c6b68d685ac6c78972ceded57023981d36fc7976ca89e69474bea1c976603448a94e4a6cd7780b7bdb6b0621833095a29151567eb5dc12312154a5d25428118839a865db48b65ae136e6ad1dd561685e03308d48ad649de4772611a7ae4c754b184377dff4b1622f0fc47cfe86a6156f6d81a9a7727b168a938adbf189f508af3e54bbf3184d6acfa7fc82cbd4ec2e812809ff92bc2e1664e3c1cd52570b5bfe02d63aa1c4b6e57655174bda95d532d44ab47260932ed362e72c52f2b188776263348a627fdbfcdaf81c100f0bd1ac15bf6e33a51b20aae2072106ed58b731295fbb502df2acc02bd0a30fdf0e3db2862c2beb5367c0e565d72b48a3ff376c4ff731979bfe335a59c0d9511e49eb1e36af7a4cba48930ba6e72da4282fb4a57900778305de8314f4423948d979d97bac9c30764d3beeb5ad12b8fda3faa480b80e1e231d5720d06e3337124aa27c750f8a2cfed5260aab23172999ed9a3ce0b6bf5813619573b21437e1b7aff2d0bacade6cb0667a481db1e7227ae245d876fe0b07d4fd536a0ddf395c65ce739a6dd9a758c089aca31f9e572ab19ad607883114cbc543aa7aaefb9027186289d60491ed44f996e95c8f5d955afb017edac6e1a7e6e73b074a703cdc8b3b7a350da703d3d208608f58a8e8672cc6638263eed2a4d974dab0c90906e42da1f4f4d44dd50db5f5de315f10e1172a3c1a27174999a20e3d01f06413aa87a41b1d8690ba0d60847205336ce166361bf5d12f704ff32862ac4f2f24c7dc1bc788f96b5a6d4433741e8e91352361a0633fee29d6e33481989b080c85bd4dce892a03c631d98049907965fcab43948727305b6f89893fa43f6493a53ebd2782d8a29f0f837a982df463b618b44754172e4d4dc60573d9f9590dc3422831a3f00ee5d0df5fad48c84dfde8b05aa653c723fe6c6714ea20b54b664dfc19d6b96f460971f1c38f143b02257d0629d218d7294ee4fe70157f138611d063e8c17386e101da016d3c5a6811cf18c08cf3f69728fabca27b741c014045eab44b926eec043e88b94309f9813b6d16b1dc67d365943b0c58b26aaaa4cd9d7eb904daecc3e6744a03556f496f113bc6a11ed443572cf41e922784e66182232f2c439641abd55d3bdc80070fc1e42a7a740802114375f78e867d104972daa5912f2c68717fc09287fcb7d7cf7ad6216f04d545cbe720d1b1b76d3fcf7aa9b1abbac763f1e8f7e0836e26cd10310e3bd0e801fa907019232ce03ea0cc47338d2188a9a99b8566f50e4199ae1cd487df4419214e86f5b6f16cd1fc8e25d9e3c2661b3f8b3de53226049b6b759d324fc0dd696e539992afd569d91a74432cc53cb1b92274cfa4dc635485726883b37a2585930fcc4ce2fa400c6abbb85fb4004d61fde83c78e558d037117c8a25cc981da9e981db0d772bdcb1d274c9b19dacff049190c138900e65993af2338beaafb78cd8cfcd07d721a90d46ec61d16a11ed69cdfd5875dd10f1b83c50f11b1cb6fc846936e162a265cf3f9944508ed2fc14f8e5e92af8fdb6bf8f71e5dbf5e768df9494ce8a85872dab2dccda3410d094b65180167db4f85240fe015d74a67f7429f46bd0ef7ec725f8dc99a6a75b0ea98478da20e7562794ecc70b95e70c8ad787e46525169465bb8a74f9b3b0365614897be20c9fbe518c02aebe830ee344c18abdd06f7aa29720a9225670dafab9f328a7f78895cbd131c4dd571b29ca0da766a2f67e8af7272306596b95382fb960f69c4f95279b8f2dc4717406363bc3c8ef94fe296a90002a1dab45031b7f7ffcd6c91efbd3273a4294950a7366383103d94b524b8f8cf72b69a843ee3557c42dafeb69ca0ef95c9280e67ccd963683dc6eb6af6bd02295176f2611c947a4dc1d6895515a37bf94dae5e9061939a0b2754bf8c37b2758372fd36e863e3956a2c294c2c029742a77159043fb949e2e8447824d6633248485210c5a699e46c8db685c3ea841fd2b81fac9523a762a2af4a7ffa5b034e8dcc72bd7eefa97632db58d9a9aa9bab3df094f7e9759bae6e5f15841c6eb770204d679b14e5cea7890c60514669ab051d1cd67a537e3889c876f23f5b3296f8abb305d55445040e0aba548130800ccc75af1eaa26bd1caf3372cc5ebb004ba6eee5720216f43e8997cec7d2a9f1cd5d8aad4d01bdfa7b82cf90549b7ef8de29cea6728297f7511e5ab9f91e0495fa089b8ff5e415edcb4ba2d220bc76e2b2b7ca7d72b7fb5fe5957100ce599307130112c75e1f098341dcba04e5191dd48b63283e72dab7e21b87e63f4d287c13bef79b24bcc742b67b84e0fe092a57e17ef4be08723daabbd0e892e9c11ea7658475c79992b334ad07cfc4124674f4501187c4e73d2ddf7ca928196bc998a797688b6452aba0688d9bb182a9dfba351987767fdd358d4eec23042e0f77d827be4007ac9afe5205fa567a7d5ebbace800fd3e13ea53309a11b13728d2f3bc498439c241f66a5d27d1f577c54836bfe9cefdacdab167a92b87f846dac0eca1262a1fb8674501a3f39d555511a6a6755757a18248f52fcfe2f9793ddb024f0af4f0c4356cd065097da85e019b959055d422efc8d4aa073b93b5da4a78f582ae2f56797154d1c8670bce9cca0e0330de77b5e0033bf172b43581a1719e6f0ecc872c073da992d81454432d76a649cb3bd00c47c1976c72c5861752d121c6585bbeef8dff2b4df2586aca47e2d93005c9310282fb4fda72ec7b70800ed14ec8b03a06822b5755725128dc623f1ab0b9c75f635221e908724667ac0d294f35ed70fca350ac16a2c5b07cf7f7e4e4ed14f6422e6c9e71d172dfea9156484efd3e2a49773adc4d4da2990b06a7908fc55e30b84fd7cffbba29badb28c8db1a83f9b1ff473654b7a59ec4c72f77186459e2cbcc0e5027a8f02fc5e16da7bbefa773fc185ed7b72aac21d655a4fbe4c14ceebeba55f9c0d8e472abf152ae762902f812efb8aed6b0ac9d83eead565bb91b1258490454dae5fa0dc7d47807e477d02b0d9eee36b44b74086a2e7eefe16b994e2d89ce336d687f4781b60c5721f5c214abc1392ba5b417576a7bb0710f6cab2c066d183d9bd01f721655028edd9a15f53f82835d1cf010bbc31815c37ca81a8b0af46bc226dbef72953f72a0086a253eb442e99e245ad38726674f1ff222be3b7539ecbf1adb1972a044fa5b97a2efe88a329a4540442dd6c372c9f230bc0082773dac6307b5bf72fce104cd3d4901eabb108cc37807ae0db7a59b8a47ecba037d500a8ba3362172c01d753134d9d60eec7ce4d786683439851393d3a148b7180a9bc34988576972031b38846f7d69c87700ed007f559e3cff4fed4699ce345dbdadbbc1d687de56e07853f7e7051c5c68cdf64e33ab37612a907d1db1a2c763f42f5760997a34728348bd4a9f7069a88a149d76fbe0b1001ee1099ff6cd1c160f7a191dcbbd9972d28b5de922ef2ec8ae8e3b3edebb968f452bc5a7c6782dffd52d7a928132597294eec3b15254600b7961890cc7b074219cb85f008d693126ecbb016b46273c48bebbceb4e80d8018b95469990df56220af6dcdc08a54fee4a00c0bf41d3150728541576c1bb94b1c2d4563b94d7a2b0fde879ea633f2359bc4d60f7a0d228c7257a46e1e629082c4369a3d89a1fbc5e6666d53d622033cb4f060017ff31ff172d878a5432f5a8902c09d4a028580aab19ac81ef42e6bb4e67d1e60734be1910ac80e9bf7171364067a06c9b5b038a38219740a11c713a109d3c810ce084dae7285e94aaaae608d235b0192a5e0666c45e7e165d52e37410ef1cac5889ecafe72db5a55a2074c94660938f8e322112f47a103b6681e815ef75a67bc2a3386b6724b501ef828699a7cad056ff8fd5f62e8d206f4bc46ce2e62d9f5f212701bda7277861a7c22731f9295291e4e61f66ad60a7b102af3b1b0ab7de110e8e3b594725337dfc15044030a2229b0aeef254414f1f20ad375ef873a3db9c74a81601772945d5a1f81684012d5cc019401259f42961130f97f2f32764b9615b8743f620f3c6045549137e51058fe6f5662e34e903b31e54ab71339e94140303c1fd51072051c1d9da49a8827d51c948b34d76146d6f4ab7bda1e5f235f3fb49edaaece567148953e681861bc3f31d3f6687e4963cdfb4b703f509e937a38fc5382b4b4725b11054d6272cb053ae6ce85e5d8a596980a328a3067c05a4ad5e02e721c3f7296b6fe8e38ee44e64ababa4b273605ebb3003a705f4bb4c9bf3a4f4728b745728af2033b2c18f573f2c51569743cfe9ee7de7c89aee684c31f486e4fdfe687725b8af2ba3a47dda65a0f0d9a5636fc68bbf510b045316755bf2225b589d700347ed2513dcdf9d2b1b82f948e6fce4d0a7316987c22026a6c183c3b986bdfea72bf5f2ab1446d29580ab92f04b9896c2f2b128e961d8aa14ac2a8ffec2198222823b01f2d0bdbc588ef0d6a5c5b0f9ac51e6e69b7efabeba668a15f835103fe72c83f3294d2e78b6aa313c878005db3a5c857ddbf7fbdcaa9c44e84713a495249aa070447b911f14fa8cf9199d2824cb94e25e7804922598decc6ed7e28cc4d04772e9acd94182c0fe53db7228af6fb02cd6fec83808490824cfc7cf6e0b8180c61eb991b2bda0b7ff00c199aba9270db6ca23373f222a7d7888e247b9425c5728a9c85638ca7fae0e68188f40c05ad8c5f6b4363666c443a517f9a56cedac872dd7c84ec61e3ec849aa8b8fd6007653ad122037550b526d3e896d269355de7728a5318c252b2188782bee9679aaba9d31f9410c74739281c37b1978f0074901498dc27d2b4412f21fc29ea2c03c94afcc2fadafd61becb27f3218a56b1a836728b010de5c5771974de1b61a9624bdd093f4e8f8e6f321349905b76a73dd9c972427808b837f68b9705791c1f75927ba74adfcdc8fba188248d03b8f47c1e0825ad59f66fdabd718655ec3c758010d4f389a67f50ea64322ab3b7935b68fc5872eb4a4c8d6beb348d39b1ae0ea682a4c37957a1a6dc1e4a72c1643e930441a34785664a4bfc4d93661ff238b9d79d1ad96ebbfea60a21a913a9cf642d22c26c51e853dfe43a434a097a3e5ff09ef8e0d1cf237c64d363d0d048053abf98c3f2728e62a0ac151f38d129b8e196c22dacd31103908596aa0d9d87d9c45abf41cc72725d10e0205338916e47c6e39e82651d84c4f34a938d98a7021f7648e96e804093963e64269c7cfdcf95926fcdd1e01f14eb6fa32eef6b104e8197a947e0967202ce12cf7ef5ef4d38376690fea23fb65c0fc3ed5e00e2a4426ff1181287586599d83088453e8ce057869ea8b6b6fb65fff008f1c5ccc6e28e460a9c38f3647216dbd730f96df6c61d7294b2b9241de7cdc7ac9b76fe286b8683ec2f4fe71908ad34aa0435a0a2a1767c3ff942b7088e4140ea6ad0fb1907278e60b6532380370e38a790c9a5de42256a43f6d079d5cacfc0b0476701be57a1a6e706d9c608726c5564b9ea6f5e6322ff53e518b0af8754e4a6c47763d09f5ace9d9297df4d7264b586170f7d5608ab7a7c43bf089680484dc5e45fab80ef83f1ba494cd3dd72a1d00c300bb45e3e134b4a3f80968870b7fc5b5d21e8de8cf42574fd429e0472eac4b55d3d8bc1a2a982c519549843035b0aff63ea1966c76a608de0ba638f72123d1fc954b79854c5e96ef8230360c7afd43630921067593fcc67be88ae981c024006c6e1a2e0bff771d246de2e49219e67f181c5c6b5c99f6c4fad4c177272161e80b31856549bd4c4eb5c1be6b108fa844c0b694b5dfa1c33951e50039d4d43bd682a91b0c48f1a2bd50d3346b01988e282e9d91edc8f57a205a3edabb06b10682922e2e3275c715b0f3cb088ea1e22e830d169858e173602b093e80f4f7273596916d6b9ae13da1b156572f8549818cee54cda9894e4c0a132673fffea722115faf8e49bd0358095255c3ce64ad1d3304c6d2464587a837410cb199cbf53ca1a27602451b48c89d44ac13e1d40076106bd70e2072926a7101ac1939cec72ee63e69d732735ff9d7520946b5c81875fbb8cce2265c7b3204637ca2476c623ceec6e64d8158eb3e73d7b863c92109a10d5c4dfba15e24a4a08dca91de4a572534222cca84e712116430dfbca8618656a878b01cfaea32c446587ac0d992719fd66bf12afc25b800951fe16bd008cc38d198f68180379f06023ee9f91d0cd2f17916c559ce0c99ed70c03052a59f14b74cdddd54d5a0182af84549da7488212888ccdfc60457422a9e9f1a7287a767c422791b50a07c706b4564a7a944291722908b488cf91355ab138ee89b0d81f46615acc4452698b6ed883c5244013600bb95181d90615f888e699c3d74525542f126d452424e402845c0adc341db3d7721fe6344ac6b962fde1d275e49debd8d646178a81b5f4714e5e31e08be4e87972de18268ffed5a52b2da644353c891ec6641de4453868ea9eda037e8f398e247274f8c6c7baa9f1310fe08d5714f6ad4b4fd8a9b108cc52e49f21e7010d71364590c5a7f20e5fd815d42e3c2166b070bd2fc4948c222820993da630ccd2cd6b722c56ab55c85091af67713452361fd489971d94733bb7016c127b8da4dc1b2f72884a8c6529cea5618621acaca4e1b8059f4902e41cecd1d28106c158a287d80cda8c303e810c335e5372d04f86e7b6ba986996407fd984db6e7865b4c250aa3c1cc2e80c52050cfff7f104a4383479289531c1d0811b103ad9816d5f67ec1a722002a9aba9e10fc288bdaf4a085d234e5849e30885731186c99565f96971e5586fd32695a4d4d0e29fabbc8706f58c010c67d0495c5c52ede39257dea272b77211f811277b925e6f2bf80c370b243d75b8a979a7af5146e9156d06e942d0e372e5f9c763a41279341e6a6378a56fe751ee38734b0a145ef63a0a38943aed4572c925e77cbe7479fa474cc379be24ecdebe6fc917bc92383af7842429b49254409096b421c5a3baabfbc0f8d1aa7f175fb098469e9cd8639be30311e14ff8667234ed0e0d210648b082d0eae7ab8998916f01f32ec24c0121941a8e78418e5172c7135cabef043dd889626cce82eb3009feaf0d806f2d3a2056daef5964496f728e764831e65dc333cc3a3b76f3e145e214a011c7af011cd34e302e3fdea61572f1262c84c13d0d3f7de2dcbe6481aa2868ee7458bad4a0055ca7ae9d5b38db552db52d8fa81f8e7d60f75ad8006831a8a490e023f9021ada11496258427bf172f0595d3f164ec20523edd3344baf6c9b526f62a78375cb30212079187ae6a87209a41a52c92160e7e87eb75ea33ceb26dcaad8d48c076ebff27f7fd215f6952b919fd52e13cb9d69ace678a0281e55238b715008b34d4c83f655191501a13572cdb37cded4e0d28eae797666e5370872df7b03274b1982f3d83efdfa189983728bd8e62e4c13d0baf91284421c5d937347f121c5fd25125b5f23fa6038356f3dd1d388ba3a1fd3d8118c6ff5d5e26a15bd7256800d22d2276ff746ff39adc272804053733c0960eca730bba18bb3fe1227bfca4ed61f124abf6576c45f306f26c56d54a79aef98c5cf83eee1048737885cce0f57965878cc0fa43929164c1d40e5bb156c6db977880c969b7a40d29b4db33de471171d2559b8fd0466d9bde30f62dae36621d45d791ba43a152bdc4aa60645804832a393e4d828eb90d89991724736cbbc473a487dfdc5fd3cc9a6245d250ec51c343b2616807326f0df5cd3723f479c3dae921d6004ce303d0bedf5d167d2cae8a6d41dca96d5d16ab1f6ec728024d75a5dc0ab63d657652c9605de8445c1de05edd5e3fd4856c4a255dc65067cdf8a6d146584fe8ae363b37802557f4c8cedb98dbbfd305f61bc2aa8c6a87242695daae6437308ad5fb3039d1aea384b478ded3a36c4514445cf56461c85723dd28b6bb033d95284222d27f24d9e3716e30fd6f428729614711011bf39ed725dfb8ba143743533b76139f984a9e1a228ffade19e4f238062536552f538aa648e73a621e4e058c34b8be16dbb1106357ac8d1a017932540ff92be98c393864381c6eca7c6c63c21b51b35819b9e8dd26f05caf461029266958f486d5b84957264d91478d0ceea9a49a9804d345279d8c93b7c13e0a4b1d1068e729e4143601d57ec8861902d3a72d64220c6d0be29d9654ce36947628a2d34dd0479a93cce5f454ed6dfcd299c3c7ea0e6ec674f92e516c4f9f03aa6c4c32843f36e2400050c3384c7f08115e1607a5af4ec5fa35a76c2744224c8495e086300a6c20859185c1e4784e852fd4aaacd0ebff04f2850eea34d9e9d50ed552f6291da4015b1b67213a2ea32ed7336a422e87a3dad03e5c6271ec94c884b25b7f46c659a446dba72ade1168d7cc605c46f6527c51b1e56333a29d1cc4daed92e3625c6b1ced50b72bf5d15a304b6d001c0763a33b71b306a4a42ef34d862a1093c75b942f642c47265be77d146981febb068cfcb1333bec841e9ceb3406bc7367e9c3b1d2579780d9acaeed389f972dd08789fd6352e8f7974092f17cde5c14a78401d2adad5f36365d7deb60eb45db4643796e08e1eae6e440e849ec3289104010f1a34145c6872b5de6ddc0a3d6b884d47a790f8449380954f2e2172f278fe35771f25cdec6d0fa0fbf95b7a0f68cc3a9e803d4369d69363e3312b2669c7f949e783a8f43cc6722dc646baccfce34c8d711fe72c70752d994bf3c5d878035dc221585557392a144616d427dd481294bdcfafbc6e855e8a3330724e6ce9fb8a3995a7398eef9572a62838d8a7554ac5827439d883bf57f6a6a0955597d23edb0c8cea7f13026372a78b0d11efc2a86f63cb9918fa599c12f18b24b4809aa49dce3e3ac72284407268f40cec8aec298c34afada33905269d56f03096f9b6922cc5af1ce8050e5d589dcf07eb14ca6277dc72b72cf3de5a6109995ee4ca5f44a1ecf6df4188ad3c726f9aedd46ec510629a07bb4a45e657ac359983344cd6573627086501f3c96972890d1d5fe8158d26a3d9c9cc43febd7976333652405b6f9e6a891dacaae9f848559e09a1f788e56a618a7d459c107542284b72d4d4cff0e7540bd227d1acd52decba14e2a90045309b86ec2d78180fc1222a6df413edbac60fc2b7caafb9a4631fb90227d0ef7d23b29416d280b83ed2bd6e9941174210c1f88b68a6a2553f726cc199a2ff395d09613f5bf2a41a94ea906a44cb63af80aaff154de2fb61ff72c91414753bdb601ed90b9c1c87f1050680dd49294b8604361f0dc1a9ff334972bccd17b04afe1d8693939324e5cf40b724a33122ade4bc70d69f9d0f1d5cca5b00e77334439cbbb55c5f4e2b016f184d20ebf403c283d98269a1d6cdc655cc725c4d57396a0726e6282b664bc8234b723d4965ffacc1f708007ec9b495454d72297a236caab9ed4d03dc8df75ea61e6cf7933ad87de108b1a1365f55a158b941f286f378a1b3202071d96a68a98cdc687e06002f273a6813532968542170d93a7db21fa8862510ce0fb9dff8e20024a74f326ad8902980ecf03f62582436fd7238e75028f89e361e314b35e7281bfe4326a7c7c6406c93c2208b19884e4e1b72f52118af2b04e7f80ac27bf50558fdf7b3fa230f541282b03a0551eb72053572b8ea4eb0562b16a53f12098d53544368b5e7740e99b9a8b60d834aa43c3dc62a8cdf121cb716b8d19b4cac72a063aaa1dbc9c0725f31eed778be8b4a7253dd1341081c495611d104a849203324625b3763cc0abda949892273e8cb0bdb8787725ec647d1db54149de7546c9213f5da0581e2f01d638b95a7e0eb4eff98d7b35cd86e7f6cf8cdb86e6ab59521eaec7e36f6f0b956237b91670623b0e417d64360a66a429e7fab6940a7b1d9758f7d39a59805a104aeeb3334c6a32e1b6d9d5e6f9102474b2ff37e2f1a1a0a3d625f9e2a5ef8e283198866bae232c802abef820009e81f5bdecc94632358f437b9131d4a1bd335067bbe41dc5ecb42dfbb4e2272af7a83c15f48cd359a6d7c3699917875ddde6f7e31f6a19076ff6bf3a90f1567664ef1f277d6feec108ae09631b5a7a6db83c2470051a2096a82c15880a73c7269e21763f4ece5346d7131426b375ee5a4b44faa83f1460a6d33b6defb66775347f6152500f8cdacfec46d4f692cd46c544b0b98d56d542d0fe070acdfcb5f05a9123c96fef622e2bb74c72c1c4997b830454af6ade86716d91573938a98fa2b15f361f960b144d8a577cd7dd3c65351657ee2bc104f52c7afb6ef9ac9d09572ea604fad3135d7a3c1a0691326de6599fe00f97b2b89c5da5e9c7248009fef72600ea81af0784d46bae2d8c95d78758a65bf7712afc33a0b850cb0a84309012e8c3451b5d53260fabd16cd32f347ac87174526e9f8b54c73c90f3f4c25705528ade9945ff39e5e2a9e8d27d97ec0c7d52355be0d399aa33865bcd159408194724f02abba993b191f1aa65c14c2c04284b0c82360d331c0505175c3e66c71ef72e01c40c20f97b00c956fb388214beb1acc7168715f63e4cbdc9ff27c48536520226cfc5ffca475390662f081af91f560b9665cac8da1163e1cb85c509331a472aeb096736056ea6cd2e5414ee77184c82ea15795547359645f97fb76704d827284caba6e844ac71c92e7b78098062469d2a55f6e97a14b565e64713b4bd8d85021b9b1655b2dc5e8adeb27de5e4c6cdc276734f92b09ff37b5244ef240b47f66789386d9ec3bfc745382da024115ffca7de065842080d79b7fcf9d8efd3b8d72a52d08ecc7c0ff5bd8793fc24426c65473de942c3a3f62f9863a09b6ebfde1726354e3103623fe8dddb907fa4bfe0d1b7bc54b86c5ed88cf70ff20879ef7c57218e65dd863658ce297dfc903ef82f19c9892cd9d944d62826474bb40a41ff0726ab51e81717f29ae3d4c001eaa081c4a8c270b60cb85e1899bb88cb4ebcdb36838d1e852c325a9448cf9a543356696c50f776167946cc60780f9a5aecefd5272f849b6bdc51e3a505d543711b07568f7f9b4f96778c5fd23a48335c5ef86aa7293119478441ac6501f2a8bde6fb4308fa7f3d6246ef28604f3818f223e758072b814c9a8f8ad1e241d70ff3096b731aa974a74b6ed38a66ebc06d568bfea85727e632e8e2905063cd8c5ed5890182b5e78dc556ca67ce2443fe0027977040c21fda7a68402f19ae73e9a365d7328c91452501ff93d533a4341668e2b8d766672dd742a729c796204b58bfacfcc85553adf674dc29804f9a4a0ea1b74a48dd372f873527b8b2d7867038c94c270edcf5003e35c9d7a3a438b5b325539d3fafc72b9640f2c0778fe06f0cc2161f40ca20170df51dfe577176ba37e2ad2485e176e16364fff674bfdc6bc329ada4feeb0b4a207cfbca05f896c5337cb7355b2b14649c164eb34b9af751eedef3bbb9cbb527c894f8d73d178715503445e216c39723d4183cdc2457770c6681c72a0f318bdbae8ef156bb3d1f44c9894d9a91277724a2e6bfd7e411b34b7a8c8fde5555b7f25ddccee3e43bfb3f37efa76d6fbdd11c39db8479450a0f045346f48b1a9715ff17e60e6f9130fecaf04cb4ee9a5b472b851a697f4b7200f88979280b903e5596de42f736da7573e2fa76759a2d43c4402333023a16eb668d4f82843e7f003221e4d120f7ecb75362c4d8aabe1b26d176025d6ca7851be6c13cb29754e3fbeb83b6818b4a33066e68b8cdfb1b3a9bb3ac00654abbb194839577005fd55a8801396c22ac4524eacfe31e936f09dca7c7255c36c3a238b4665eec1bf5bc4e83ce2fd5d8b97f384a01a01bed99856be9658c7a68252605b99dea485be6fefeee12e070aa6be365302372b2345b924a05f72c578982c949961f603f55d1996736e592ce1184163b30afe9b0115bc33261616bbfc9e43bf30b954aed89f87d9d56bb73c5a165e25bfdf2e5c54d3162592fb05e0f8d6ec7779ebb9638b2377e067b274c5b0019fff906c32e7c3c8a3b927f9725a7779488bd2266ce93bf412f6fa64096c4f159421ecb54c0ddb11cd0aaa2c72b8da08061066d075367bd5a7a36ae9eb5521ce5fe81e0262460fbc40290b5f5026c90b657cc7ad1df018966cea3d221ff38345acdf609802268a658a12747655922590606e41cecdc3bc6a2480355130b95a00e8379dd3c5304bb6ac485f537258bbe50bb3efd81b705fc2641be5965156a455f692dc90018633e237827ce672f269fddef6a50f77b88079e817a7553d3aaec81366182343659a945229a4684e0916c2697252aa78a120afcbe58cde170f4c9854d9392eecfe51e115ed96ee7297beab18064122938eac136d71e60515e2d62d83bc837b6bf4ed8e17e7cfb172424a83fb96b43f28be4ec537baa63ac68a00e916294e36f4b531d2f164eff472258d2b3b170bdec855b780105555b8edee866a284391c45c75f9e7e405afbd72b3d1cbdfdf7e083a11ca82c227c6c6488b086a824c64eb29a45d617fa163f772f27aad847eded52eba44785970f58c83bc6158b6494a77bce9c7c24f654cfb067c1085c8e587ae17d28d66eac283f5a662c4052afa9f410d451d1eac3da4c018d87a7f144dc32a55767bf6517a3519b3757aa6430ebaa928aa2547ec504fe86a06f197ce4141c88d06050b67febc925a1f5f57f80dcd35f873f44c19af8c03259bf38e8aff3c6b6baf9a86e04c7feb29c913738f0f5c96123a77927253759d72740e3470481f2dbc7d54d40406ef32f6eacec5412bf7c5f8cd864e8742833947b9981c15696b96a70f65cec1b977f330b71370bf303d1e5faee017f809a9f70399799616fd8fb3a704ff3306cc524f225a353ab19c2a5f9d0281c64d92177e0f3ef23856e2b1a906f095747a787c93ea7faab098037d11c59f40679e59eba072c52feeaf0d433a69c32152065e32acc203d215f01a368e7d8945e5ee8191cd72087e1bc1d08dec6be5767fa4c988d328b5b4e966c9e1f67bdd562650b2a12b72110c41564dca18d4b34802a3c3b1c575fda107aef217594fc8e59b896dc95151fb17918124989cd4a0b351ae62e9e24ca0c988336e58340bc7733614c8bbef3478770038615d33608aad65474b487685f91e6e65f7c0b9c4d33e0639b89124721772db0a6167d7db9700e4d61c64ba4448a0d14080aa81268bda6e3e878e8215422bbb5303625f88c43284333de04d5570318aaf0499d4d26913fc64d8dfeb239a24bd45175c3b3a6fdfe436238d1c908b2e4bc3d9eb2b7fe52aa216378a657253817b57a279805a0358add38a5da4084c94945bc7e81accc670a8acadc9364869583ade04e087be3513b0951a59414767b9fd1a8f9c505662f461d6b718f072d0369c6a3f244cfbad349e044e24afca9a27ee01b19ff317bc5c22f2d88b0172d71fbbbe68d5e54a3687feebb2dc8c06f17cdbbbe2f5fb3567558530a9ac86496960ded766b14e8c6a127b51493f5826b3b939f5de594a8fd0fa36a927ee47725dfa5d797b5b32cafafa670ac10a0b5ba5e1492db260fec69c2bb8f93bed996e522f0fd6affb3a3c6efb3436e2e03633c14d21a7899315866aa0f2297d510072a393c105b743c24dafd7a85974fb281e2c5e1557c1cd9b9643c0c3a7c41ff272eedadd6361657854576336e722ee68e82c6e9143554d4dfd7d2270443852a21ad26e2538c98ab9f70b275b9605244052fb12c5e6f376a4aea656eed172e44f72497c6f1cde3e63de315ab7a685aed5215f22e91f7357ab1def6be56cd19b51648e1b3bcc4509f2e6a66ab71e0ec73ab5902768f971874aed437777d20d1c0e1a2a75de29d93d82eb9da374cfeff4549cb254ef84239ea7ece1126a3eab55c27235e28465a11676b3b7dd22203848f48e89fb55b0267fc1d6d6579f9fb6c10e725847561ca73a7a53be06487f6de4591f0be11e1a97d64b717e8b63726ecfa10585cd63c5a691292d75098afe04e35823db1e8dc8d94c74d8cb8b486a631b6a61d49a81a1d5810bbd1b32f024ba2f7e2088ec4deed1e27f8d69c1177cbfe544723b004de21401f443de828a545d01526e573ae1b7652a04d024925ed09abfcb72a50fed6a83a02d3c5034f1241cb37396c16a82b23cd0998914f7e0f4a5c0983b4361a8ee70ba7d2e652c0d228f12fb2fcc4a7889094d290d635438dd6bd02e725fe845afec31e9d6da114feddc8b8fdbe4b5bfe4d8d46d2ffb39b4191058b372e7bf705ad8359027b88f75d912ae4a5834f15573861e5c5306b5af7715f849606c6f7a2cbb2d1a3ae87918363faf03b8dd0dd5d2f576a6a418657767731daf709a920c7b306456e5b93c5fb56f06d793e5da4026424866454f0ee01dae81aa729cbf3ddd82af8e79a024874de31767e387ac67f6c4b8ff7a79ccb0906c2c4f726251af5ca647f432fa2afbc652027ecd49da6328649cd3d77a79a3f3c69a697246ec344b11b6415c68f14beaffa267059855376c8a1ffe79361dbdd482bf796ca28d7ebbdea80e0545a41d1b16de4e15f0b1ad2a33842d700755ebb014aedf7265d4bf48064aac94a15e0ba9502e12c7fad330e7ccf4dbb1d3647de2eb786144b2e5f3c1bf85e7b1648efd7de9d708f40149fc872f2967dba6367b6b9cd4d743798bf77a79ff38e8bd47992ed4a5ff65914a36d422fb8bebc197d2b10bc5f9726966eaad17c221b80c5d348447727bf657f5e642c1c86f3d2cfdb8e331eca87289cec61ff5ed10161c8e0141917fbf31ae452216a0adb87eaf80ebd75df9137216f59c068c3413d1e4aeb1c3a46c6281d81244a2bb824424347c88c8d17d8a7219192fcac5c797fcf521d23a18c15f2485825593f6aa6998e0d2a13f13e74672fc081e449ecb171cfd2ad55601bb8805198fb8bf360d412b1b858983f1791e2fe8d62d2b3b51f3cd33ae73b639149e0028b615d5fd75cf01996af5d1423db172af16b1e5d7f99bd8f46e12cd3ce5d5ae86dfbfa01ef3db2e654430f3a4263a728d99550899c90ebe04f571260d47d17cb845913d507ed617dd3e1184211a0a5c7e5b38ffa1a6b1fa6058c4a640cd4520e3d187d4666a060675832c6f1c4c3913d37a1238f2f5de117442562096f0cd5e62b7ccd784517abd4d2db36226147172abf8f35fd6a62fba6c1d819995ccad08bcd8031183f20ca89c785414a0abea720331250790dcee0cabd353d0d2d4fbff025bfab6ef48828eccc4109f2a815551a5375a1fdd907bd899878bf1a81207e1b186a01839215d9e08677650759c957274afedca16d799b6043e6b8762d58cc1867d23d8559df8b69e45bf96789e3a727c1503047f9006f9dfea6acc34f3157e11790d50411ee42d4536ff2b0a34b244817ae622d47c5e9d287a4bad2d8e27be50b76193127dda134229ac225e1ed9723175d16eced02748adb8dfc8bfa276506d691abca4de204c7e3060bad080d619a2c30028cffbabb16b1dea7ac9a137063eabc2f1970b827f2c912cdf527fa372aa9fb370226836e7fb63bbadcccf4db35ccff6727bbde4119bbb4b15a2cbd572d4b5b69e22b732013079840e146c31f93bf9a199691be4f66381f39505e794729c40d212b134e16c761032ddd16068b9a0767c2302ff08164af6850551243a4293fb635a64e5301df4a42b795e33d365d3b63c130d08440522ac257698b15d46d52dda639108c188b6bba53a45536d109d84eb91a6de50f244903a9ff47ae4722870b2ddb96510992400218cab3a7c504688dc8692248c5f1e88ce5905d86737368cc2ae828a7e48eda754d61b426f6ecd139bc92efd88b495e933975596c93e6556d16689510c20135aab008ebbd404be6ac7b20277b1cd0e6dcad0749bd14c3100b289bdac3511ca62e40a54b09e1c73efbeefefc6189a3b30f957d2914b727e99df1fb5ac52ee3e0c01baaa628e437b2d5bb737bb8618c38ec7a2bf4b600e91040cf5e947af7ec2d1af0d4415349dc6d6ec155962799c8af5098ee5646d45130bc2ad2940aad5b5561ef55647b2736e8f76bef2a5684d3be4d78005870a72bbe36a16d31f19862d6c5f0cc30257146354410941b2402e6907c3bcd6863c72811811fb226bde285a20a32c9a7f890c63e1a2d3022d6f3c2bf43c2cfbfc387216b4610dcf4ffaf6d7039de3d51c77e7d28121fb087da45eca0900ac33b60016d82acd4fe3b6009ceb0579fff274d939873bff121126b88d22122dd8195139571e7a79a6b109b26fd3adca5e51679f14464314ebeb98429487c0e94eed7f07727722f37cd2a5c9bc186446c31e934f6d71296b92a19e198b865ee39fe986827246b980b213a8bafee1f1adf545bf45a5622249f67b5da90b9c165238af746f2efd1b42b9596f5f5dd354b2293e72bc185b9e8d5bda9bf9976143a62543f9d854199dacc2c7701b5d2d0523236164ce9507dd490e8eddac54f2b23d125e48ee56818f08202fa72229598812013142702be3e00e39ac6557a2ebd517e27fcc2630151d64e4921bc8af79f87df8f4935f1754a2c2ac46f8ce68b532a05d2975c351698cbd135e85e558580e194f75c0c89c964d046f40627501a3446501c161577211c3c9b97bbfbdc4ca893b944d247229bb09d27c6a5b09b396b3119bba8cfc72b6dac9074266d02bdef5c4d0d9af21b5043f3364dbf2cbb06cf6e3f316a64c588bb098e5cc64d8007c8dc7d6740b2d6ae24346a10437951c55ee58f732a9bf4ac838b473a79fc4cab3ba6bd2c5ae9cba2b7b659b0ac7cdf3bd1a5729f78805729c94db8c54e765c45d4f226ba9a5bba41f3123935ee7909ffa1b3c438405530ef634268fb4a422891f36be148e57f79a3d01fe0039a40b293cfc2dd66e676b57cf7825adb674750ffe59e470536171b162564b03ec2caeb753b8e81e2edcde613e9f1f89ec0c4a461fab733bbb3a15116141cf4b37c6f4efb9793fc03d8f0130ec0ce0fd9c9055a60e242ab5f18ae47675c31774b7f8b93490e3a39ea662423dd5ad57977a28e36246a40c92a698cb22f6e7d623769fee51910c159a5c738d72b2d06a3a4fe6f4a56bb56f15121988665e791cc097d505f5527e46eb31c2ff301461fa4a141e595aa56db2f0b747db61d10d249bf36ac8f8e4919e04627cc0729e9ecd637acd8a144fb4d28365e5d27aa0f37b6cb6fc13a3bb8fd08fe58e3b6e0c9b4b834077608d51f73b459a19aeb15c4b643d8b112d852c6cbbc2f31d8038636abeed652bbf5f3193af561c250b72a8b0c42d7e2f3f8806ecfcb957d7e51b57a515bd01dd3adfb6653e22699f0f744e7691029ba97d7480207b72b21f6a72465da18ab258b76a436fc97f6e39a3d4e43cb02b5ed68d75bdf17c24b38b5a0dc3b4a75d80d3c86d3210a70d5b12fda441ffbf89f6be9ddb706369442077797227b030e7229338cbdbd780429ca40ed5acaa1035b4f8a467767d3aa6f434a07236bf40b636f64a9d5b6f7cc86724827d4abe0080aacd72b7af31d4b0aa04a272a15a6ff696891f51f1e67aeaa74fa703ad2f883a70601890cd69f5a221fc6c727f5bbf019e82d4a449194b012a9c1c8b9c4c1b61e3474fe676fbc32303ca28726e50d6bf36dccd2aa71d7146cffe5f27612ba72a97a911d53434d89a7ba8310db02dffe0236170776305683267e0e79e317b2cb5971dba6379363faf8c14df6d28dcbb9ce0882e4016f8afe4aa1c181b92d0ae8b9a064f2c07243a09efdb3e72e1f64724ade253bab45301cf13e12f02b79cbf85da230277c2cf71b64fd96b478b0fc1109596e9cacb1678e11ca2e89029a4552c438beac4c8c3d8407f100a72e61ff4c4ae9e1017d8ea573832ca73a4d573dc9303c9664a027bd6811aede072fce0c3ee6e9176fcc4cc574eb1373d68f19e31ab923a58995269bb1ee884345f1f4e820dfe268116a994d68d5762d254d37ee1aba5eb45654cf1acaa15faf61d33f16dea7fa1f37c93655a985e0f1a3f1283b1ff75ec7a63c0aec79550bfac729e3aabcd8ed670864423356c7b5388b0362ef2df8a253626ebb9c69577ebe0726a7e0fdd122b60727f5a9e3b8417d4b83ee3b0f0ff9176ccd6a4d59ab3401e72ceddbe92ea97ced7614333f146d1fdc158ea2f1f6244fc103aa59e74125a8472be62f90360cc6c8993b08a229e179b87c91b386de1c9611b9760e1e5e171893ea23259531530f2464704f49dd7bc847598805a65cb4529f95e79f1e41fd0b572357369140dcb90517a2789801eb78d1d3530a4591e92eb211452136ce7e622105bffbfed4ebc3b38fa526cb690a772f43ef8c455e5bc5919c732192f0caa73682bb4fc1a32aea379c3a5206b5f58cec3e2bef78c068ef911325383c0fb1e73722d47f4de69d474e00f55518bb53e93eb4cc1529c735dea16ca836d2c0289c972bbd5c8acd06a93086e4f490cdbe31ac32e5ea4923e8b5e5b0f49b5d0156ad3720d8ff60ed26ddab295dd938450032b716eed35319bcec95817c1305121c587596abfabebf2a026e7f66e9e9d0ae07c0f51733aa48c2e97695c5913b1fec64172098e37dda6105808f124cbb3ef1c9d7589d1878e433fbf27cdfa57b3cdaf23726849e7ac339e12cfb76f85c9fc1665b7fecc4a666f2fda48fb8221bf1dca43618ba626f00bfcb6a45756e93f3be6173012fe7ad7462292200facedaad1e6bd72d6f9e34ef90edd6d50012ce8b6c7377804a6d5d1e87b65770f3767c901f048729e4641274f8efd329a83e05ef898d1bb90a0dbe71491873fda66799947451d252c3352f99d48f9e3b829f0e75cfd79cafc25935b8a7957300f8d43d855dc0c72219389a84d12c7ddb4ad08fa7e538e6b6493ea992c2ec5a047f117a7992d9a72f4deb1509a5bc8bde390cf20bd8e1114add92dd1f215658730794c3a7b7d4e723f3a0a93d72aa18025711f07f5233cc8375615213c73c6ff885bc39dbb79c9728c6d84085d80e391176e4498f8d3b725795ea076751be543f24472ba876c614b9d405fb0f4c71e3367ca481b288e81534c12a92c2d5c50f3ac85fb756c9e5d72c5c8a5df1a118bb6c9db54d1c8849850d4f5dd98e622520f91c5d818f930a272b8461dd8495ddf563082048fb3f584b1d19ebae14748c03a3bdc7e371aebe172fe95b907ba05c41d26a5f6d10f1543c5b49acfec0808e4c399bfcf92bf9f7272f85dcefdc0c205ad92033c7706d83d8d82f678ba21531e019019b903987cb672674a0fb0c6468b54cfb7d89890ce712bc4989b254c94115cfaa956e112ed6c72a7125dc123ee892b89ff47e042f74ea153f20c019e3d992b4cb221da541fd57240cfcf5cdc6e781a78c0c1813a9e93c40f48a239647e551ca930a9438b4a0f111ce3c265b5bb372a7193e1ca42b70b73d661986642a1975d5f0430d82d963718a906e6c9c38ea337a5cc54fd9749e16f896f6692744dea593c55f89dcb27944110be1d0b06489e53b223ff6a662e0393e7dee4e32570420ce5f6fbc8de1a6c725713bf0e4d7569dc1bb9efef3dcd55ff72b65752e9cf5054f5ad2ae8b5a70c4a3ee50401d836eeb7b85d297b263208fc024915d56dc86a85da86f4f3acc54872ba2824cda069d88df6ee04de480a169c00336cb23528401bc01f4bcdaa5555558d7c75b08f6548b52d4a1f376d4bb50feb405b1154f89f28d8ead69e4296a172014948eee2732ce8373546767059a22f540c1cea95121febbc64ae562a7e017210abd48b53c8adcb870ddb78266420708836d964b21a81885dd8c9d06b64ef7296caee5d3b8d47971c7fe5d59788e8f905f5da2b31a75d2e60c1fcd81830e7729a6907e48a480accb7c8f107c33f556b5ae6ba8c439697d28c7ddb352d118b27c1412df6d26db54f5b14f719ae94cb8cc6df7fd0fb8cea18797f97aa2259ee0a21c938281e4235bedaa46ea1c88302c6362e01bee83f848460eb18e615978d280bd2cf751c1ef627516ab7b364738660555720cb85f5cd25dbc2562128c26d52d818d1bb3a7fd97d488fbe9c20e4b8a08616b825e5a0cfd5a1ead4cf6c216a056e98371ec88df6ae7044b113f7704c12c53cdddc9c4d4fe0ba986b539351de72ebfec074933b4c0b9a791595fa96f0060afffffaa778139d20cbbf4357fe2872c9ab6739da45850e4e630ee4ca1fdbfd80b9447f113a5a877dd4f37e70523272a579d9f9b6995a7339a8817a9f3f2b497d055106984d4fe4317fcb63fbdaba72612cf5874fba1138d6116e239e62d6b6f0ef3134267080178246316e178fe27248bff229cc31c854f7da64ed459c8e51095b91171af8a87f4d860a9b0bbd287201ffb9e5714633f41cc4b78a9a9d07c0e73aafa06e86db54ad887f442df13c7221578f44ace48ca2bfbbe857cdea5a2a664e7b6d86328920caa3ba4ff0d3506db94062c07188c1202b593437040ad0cc89ae6a1c352db2e70420a54309ef3b41aea5b3a29ea29e463af2ac2da84ccc414884ee9a17135b4d7564a9999b5598586d8f505f790171aa406f55535abeecc50f2d778f9bcdab06f0e23976a739241b2b77432618e4f1059e28e81d51f3eddf9e930e4723800a1ef8e478130236257298747bab2db2bf163211a847d6989f162a1d45c57290ef1ce5e32682a3688e3bc994627599fc9714b08fc53011d3f3bf14e6898d0871fc4a2a71824905e0d93b8caf39907a5919a49e33043291428bb9e4df6b897020adc4ac21054e11cdfb23001f5c34d964bab05c24b21bb3e35ce700a651013a5eaa13c1b11a8c5d4a16724271a83b5728d6983ff860bef27338f8e4fc8fc780b1d80eea65aee397100e5fd136ae3be80cf452d73edb1548469a6793290e4c32151541b85b5da474cdef723569b595236308b79f7192b3472ad4d9029e4c960839a01ba336bae55ebfde4d3098f3faaed57b638927eea87106e3a7959331826d52927262356e6eb952de7277bca1fe5b10340ea74c2b9d9c4066b79122d124388dd112260f7eadfcb3ea72d3e5d9fd7c9279f23c5c98555e6249fd1d9fc869f56f9c0bf4c1c2c4caf2e013a15abf12ff2181e4cef1e9fec0b2a388d729bbd44639bd1446ebd4ed5383121af4da68ff0e5c51f409185863701e6e1a1a5e80df0d9057d3a08a7387ff73ce72f2b73600c7625793a83449707d01b5e416a8fc0004a5c68142089faa36e347723337498386a8b4cbcbd8980a0b9116ca082293c3824aabf0cb9e7f74ef56dc72aaa53ba33a8672d3067b4c666d375c8a506fd5c8355f034efd82bbb030b0f872e97e10994b29ef25250e113df2665634af08af9511b703e895a39dcbb966237274560e4adf0cd7a21b58d5afc753f8d6e429d91ddfae2f8ba00b062419fea82ef966f3b511c972cfdc54fc709ac9201d416edb39535c1b0c3570a8ca987adb6365891b6610b4f3b401a129afb4b8b72fe105abeb3f6b719411e4bafe4f75550d56eb08db4243c777266828d0e247e0734535203014f6a110f233d78ee465f172b9b86a467401a17af38a4852dde675f7d4431939e4a8710c871593665de492728181ee8d03e6f2b8f93fd54d50504460fd581d5445d7b39dbf66840b5ff4e132f2c8d64d293d0096fcce03eea2da1fb67f0b7789a57529a06d71bdc20069c3727ab7a2af54a65151b9031da896d8767456c0d43f570e427c8310007d7fca3572c931545daa2838b44fc3586e6966cbd8fc4fde5282a78f400ff51f8b757b9572b77deeb081a39ed2f2b2eb674845db59cc192c14fee8255c3a2886934e398c72a8a4d8fab2ed753785e23fa05c062ec61fc10678e0513d808e0f504ca638dc6311252d55c32908ea92c56bec59a425eb2b16ea44bf9d8dc6ed40143a5f03ab591d7b95fe1d69bbd8fb6e54499b7c7c516bff0520b597d38309c4a73d19d80a72e345991b46bbe4a2b0ef741ef39a9e8402b51f6cd357e1c372c369d3c4b12219d371c249e3817d64a49d08f47d96c572155fcffcb47f6e7b4930956d9edb4072c200843394a6bf3d46c72a9a8a57af455464368984e3e807bec1019d95f3a22a64c0e56e1bcc05558f836d7554421b3575aa3bf5ed31b1a4ba45d8386c8651721619989ed1d8dc2985fddecbc5dbddee98404c5c408a27cb8f14d8e8c7f53a723a12a823b6da3bbd9ee1f506681ae874dc3c26e2ea7917654b1357b321537e72478a84c0df8e9e53ff3e7464d59dc92d19050ba1ac5700b6a1a6171e5cedc97205c64509189ce85ae67f74477806fa7e6c0ab82c9873627266c7ad026fd676721553e1c1863bad685c48913827b733b6cb4917a003977f894a432ce6b17a7672f5f036339b52b194dc299a1ed9ce089e252d44d299fd2577fdc18c8de0b0b172e92199f900b26ebede7759dda64f00768b5b72d1e173076d31106068594b76729657e95f58c46664c7f6baacbb47b11ec58dda01a29c754696e0c2069a3b871973867aebc1fd01e70ef5b5b8363119200f91a35439b327f9902dee765be9f772a542d9a0986d8ed7610582f46f79af1ff1b397d1d24ad60f1440e110a7d3eb1d2fbb2f63aae7cb98d16740361c7fe5aaacb354b325960cbcdd63daf76dfb1a72b783db9902ff8a9b5e74b12d82f898778925f3fbd8d18a4d504eb5d490bbef729c474f268ce96e0805a26c9f447e5f33f6570baf3e785531ec7fcd6e3ab3ee0d63a4c2e3e2947278df7bd5eaffd4e28c24d6442c61468aafccb8478d515ab772ea8c85505a98109e6a1f8c1284667c71c4f8c751112dadb4b41b3c94628c312a8cc43a56c08816fc2e987cb3ea259884bf469cd4f119201254c20ed2eaedf2485a96fee0990f5791ae396f806b759a59cfb21d168762aeca634b63b087cec55f87ec0d8b2a9f9822cc85292ddbb8459ec2ed6a49feacc49f965e26d6f79b0e72c6226b5312d064c02d3f1504654fd0bc3f18dfe92e3cf6f64ddaa3b921398f7231829c56c571ca3ffba0ae72c0de55ca36a68c831f07cc6dceaf9e30fd9b9072cd1a848a73bfb95cc89ac98042dca30841bf7c39561b581a8b849864f7845e18fabb1f3de31686208e7889105c7c0ec96af5b94358b95479cfe49c142d602a0c6aaff93c4cbe24d95ec6597f3d5cc98343e5061e02c947260d4762093c4ef36f09ccee85dccd5db072cbe1265dcd6fd8e0607dc7b6aab702ca3745574378fb344f79af3150469e1e5efb40f598ac73f165a3402ce094298c8310f150f0d92d62e2527a8ce7671963759c43ef8be8747e8c1a7b11fdd32ac0aaca2725b0db4072fc4ba018d33bf65a80feddb28fda2a6f16f4f9c9a2afb5280e9cf8a8aed69d72507f50d44d48de1c57401ace55f29617e884a6bd34e9a83f1a73edb45960ba72b7b4f0e101cd462145942a9be5f13d6678a602f44922a596f39944b4da144b287830559e6dded34b27929492e593db4a74b1ce96548d3ecbfee49c5dccf97a72d8647bde1d3853e7108ffa769a58e3b5b71e89265947ea70627527a13bff5472e8178f54077e71c1fe7c95482c43ab28574c0e1df076db2a7deca03e3c7b11174faaef469add3db59e7f33f1d4a7cfc34e0bc2357ebf130acf4267ecc7845e7226b8d91ba361beae143c2f3cf0a5dd16e4077e908f0049514ebf98c61c8fd3729f02fb053c8d6d299837201797824f3e7f8d20e6c93cb2f8e2412735791b1472fe8e30bcc2939c476f4c2a4843ad09f240d4e977958fa68918e3aa1702d62172b8ecdc2a873ce2b3152afce5beba1c5bced6bf01bfbaac9c11acd9ae31745f7204d08a0ed64783ceb6f7c98be6240a08272af42769ca828e4e7b33dc7018e63bf90179035760d3b84a12fc4a9c14ad402d55adb9095d9ff1cdc31ba3d2e7ba72c0327a22a413cb3f7c7177018df3524dc85c5137400f22c7a64899aba1f4b272d7a897e88b0494e40d20bb80d4aea1b5452bd8bf993b42849586044b081ba5720f5505a79a990afcc27e8ecf3b4ad1a8977925bd248a3619fc72148811c94c72239b9618829167aeb204fdab6189d25aec3f6c93e48ed09e234da73febd0bf72b6a88f2d1415d1ca318ceb615485bddc4d35bdbdc60ef6216416dd21dc888e729e79c969f54e596c1770a29d4d30ad15592097dcbe1ec7b937007c30cde620085776e9120f1b4be9cb2bf5ed2f7e9f7a5b1af2cdec2b5e751a003759df57bd72ca65c7c6f477319b35694d3a71176892efb21fe546130f575eb3b5e742be0c563885ffee06222e36d42f441e2c2cb8a7b14069ffbf64bc13910feceb12f4c072fb2110723a0571da9607e42deeb07b77ac41047b0605a29196cb3b5702ad747296abb0b0bbe27c97de67a9259f6243215143752cd9ddd3d9e76c2f5cf090cb72103ecc084de53b501f0df2d6b3a136d3863d8f8fc589a1dfdafa170868383772ee37899016e881e3f730c886d214a215fc5136dbcaf109ba8c895caa0356ee720a4490b7f2281fcef6baf7ab3c3fc28b2f405ed8022a6f28d2d3d1bd202ea06a3ad8481c84abbb7dafc3973943ae1b2a7e8f97fa418283ad6a49da5da4e7c43dcd231a31ef34ae2ae72190a8b30038407f3edc7a907e09f8609dbd1d28f01f3a9c4eeb3218686b4f84c7cbbe1bcf5d8f088c0c59a08a20f5d22a8fb0418d0f72fa3d7f64150170f2328ff5904023ec3593272f1e6db3e0375c8ceab85634ee2027fbce7cfd6031bc9fe57e2f3b90979a49d59119033faa318cadfcc05a2e8772205eb62c6571226a46ceeb24ce4b2ee274a65229261149252bc364e7fccd1972ec0e30eb058cbd9249faf75b8bf0625a00583de6603fb7be49a8a8442350ef724487567b86348a651700001f06f695326c590329993d0bce4a4f97d5b60aa838f2216982a790fa16a6ffc8500e46ae87aba1ca933c11960af2a99b346b9bdf59f433019be45ec8364bfb087d60ec2a428443ef7c4f926d5c87362ef5b32e3872cf2a4ea1f66ac00750d9927e1d59e7770d630dd73fa3dba50d7a691dbff49f25d7cb46cc407a974895e5629aa2a68d6c974d23315975edbb213f3e07652231624cfae9e448966c45f02f237f0c8e079ea5ab2dc4cff39015b26391c2bb5a327285dc24eed544a554662e70eba3c19a7874be5105f777fb5b0eb35c0480476f6ea6fd30d6b096c76e4f6165bd2073862a6e1590ef7673bf570bcc17b339d8fb13c893ce1e6796a8b0de8cc26f6064937f130e52fe7dad82c67d3c730fe268315d0b2a384957cebc684f2b7caf24f4fe52ab29e74e9d398e599efd28d2e16d4972e69b0c76b787b37d354482a8277ab4a46e894d9aa5a71b848b4b9bec344c415771ad5531b4a45f5a4a319333fabfc4adede5ef18847aa68da3ad8d8de1325572fd0b61edb4f8360557d0ba9a2d13ca27ef5a2f6a1fdfcd3c9f12e5861b3f84723b0d4a92470ae4b24221a7ba28faccd0bf6dd5fe899b04e46ea67339098b0772e51ba0b256c0f14d7ea3bfa948846f51e617d19c75bd20bbe3b81df4403e0f57f9f785e7dfc7e29ba1b561ca22f87b4b9870a32577c1130e6b50eb6fd6fad272a15b6730769f0010d0be6616f897e8fe7165674ae2512dc26295b149540e5b3605847a4f11404f01abfa4edef40b6b2a0946930b65239e282cf2e6c566ffa841e617ddd7e5cc5bdd81b1c094b8fd5f84a5337be911f5994048f9ea4df2ff35511ab2233dca8d29be90cc0510310a1d0bd474c378971e5d24b74c1b5591676b72b45fbcf8f8752eb9addde744d26bc3206db05a977ea71cab92824a84da1e17196172eda0df47d6edd53076c8014b9047193d07baa2e3a9e7c50cbf45458a5d7221df9c30a49c13bed6444083a6b4ceaa9364e5e4d3c243e87363ac81371a9b7267c74c6159ca5589d66e34ce287406ac910c29899b897ceacb320e69cb0dfd72f7429f62689e9e0350d35becd7662e102824685e3f507ea3a55008379aee36720eaa99ab67b37459be78e3f46e03bfe7ec5579393226fb10995e1272f7b45d561e36ac601760e7c41e572a336bb184f52ec74a62a539e99d86f4201f5726501beb6fe3140d010c2209aa4473619257ac69d05e5bc4b01e20e657fe43aca6f6215954bcdd2a24789b3f51eb4e654df2aae216c2cec23e036063ca0bb887c47a728b38adde7f6f7291074a7a969bdd04cf93d6021ef256959f576dcdbb402e92723a7e60009c98e0da68c634e2f06c267bbf99b22fa984a378b17125e441338a58fbd57cba29c00a828293245407d4d5ba90d950681e13073c0fe986dcb97a6872395a0fbbdaa856cb64bbf6db0fc179747783ce564763aa9282db1d5a878710133f38c64834382bfd26de2c0f44f0c32ce6e31c312ed61fd06adc0b3fe2cbb0357a1435c97c584ca0381ef98b1cb2c39c85c103b02f1395660dcb0e90daaa932d50958c5af73cc2df20e9a437e79ef749a81b2a87bcba265f1a33ce28208f22727adb38e818b79e5998bfd6dc96f98b28ece8fd41a1105dc2eb6690ec50e64c0c03c7863d3f9461af564cb619770a61c5e0a9c6861e4fef72cfd3b0da9ca8767268c8f25c9557cdd6ee50ff752bd5da469bc2c37fb44ff742138b703fd6fc7772d8e38305561a2c461c6a0e5ff22ac5d5f0e0644710de2c86494b1c3f4b52b47283bddf4ce0fc865c1d55c823d157d550dc6c31cc63f5cd161b72522da4b113721a20a61038fe1131a5300608eb0db64fc240e82eaa787df3ec96e30cfc936c72056da1c46947308e40c38efda6c2247147ef5f018e80af4af5cb2d78c735087272829dfac366cf6ffc189017f3b2f108a545d79fe66b9617949624b72e4585721ee75ac629b59cc292e29119989d9c9a8bbfaff506779e62cb8240607b00dd539c6749c6d5aab3043b8af7f63a626ad8e34e0288985ec46b95b6925571d0b15e3ea57cafa284c0c7d2992e44cb7e6850bfb2bebf088f33ac8232741cf28d6c7253d2a8f59a4daa8f5e020b90f5a528a04467b97630cb14e8c44892aef0a45672d6842af5f0f517729d3bab8aaa795fb2f758a3674a1d89a434505c9c5f741c72529eba761c1836f282e99cf51f501ea0fac8b384b61430631dc30dcd0178dc4e989a828410a457345c4ef7cb11ff58191fe0bd8be92f9b2ca82708cbe8167572113d0e9ce9971b06c67bf0c0298b5767328be0f975efb94628e85d9ff0559c72d56a01eb6c2244d6d572ad40eb4a6a165456bdec7f7517ef0d9a98d755b91a724a97c6b1ee6defc844cddba6a5bc57640854818216775fc25e78b0ecbe6152727c9c7abfdd9d65d626a23f7fcffc2d0154f8080002746acdaa8ee1376cdb2203f74ab930e65c15873e46f504ccda95dc0a9188444896d390e79710f6a0a7a572f13a8f840972b1ae7474fe132083f3c91b6f2e68a31910a2f0efb762df4daf38c33116f86db516f0c7c243180d8b80d74431fb3f0d3adf432c848e21b24ef33669e259ac04649942816c5d729543999b1dcc596131198fc278551ced6dfbf97241f5bb84b6ed3308846f0b7482946eba14607ffe51cc967b4d8d7b516b90a135ec6f52b2b5900ac640f17185a65dd815a3f07c4e412d20bc63e24c97ea167a71889a53fcf4855fb7d57f0c5932d3538871e8bdb81e5e1f368b2ce940ace0af33760ade43475ab1557ba3bd573151327a53e50f77c3a97c41cba26b28ad8ba372c0a10ca1e0dde4ff015e6d16d97e19ce8b15e97f0c12a28b755dc741b8b41f7295781fbc44fc46d74b490822e64b214c6098e301c24a9fdc5bb5b79ae35de472f33f8fd1194be3b1f53f4196d64f53fe3d984b2fe9c173f639db94aa378f6705e80655c6aee1f346390f08bbf26053b4168261dcf66d8598e85fdfd902969449a6375411963ace8809ff52c59a04d15fad4ccf1d08c8390e7276561a7b96ca47ff8b56ba541bb5db339984ce8fdc47a9fee08ae7e87a9db22b15340de9c7aa726b1699b8c56aaa809bb5e1d47b58d5c688023f623afa5bdc222d4959fd67da721150f2961a91c9317632df9f131d2f3fc60c649c3af6b1c55c88c6fc71545172c8d18acb15ab723e153482c039722d366e011402fcc79eb8807d684a80f8d068d9f646f59b6bef38a818298583e8f16a581aa562239c594b422758ff73f3f272d05daf90ce22b9dedbe81a4cc3407f302a251dca1911deb154d3aa2e1c1fab3603dd615055ec37c4395ab005ab5a8b28ef063f22a9b4dbd196bb2f21b66ed41338eff912a9f3435403bef904242e75df6b9f7d76af65f8a83aa1555d918ad05c0152eae8984d5d421454f1a131c19032dad54564855f10e6557de7e6ec057763eae71674163328660b8b7e9d5d9fca4c7db932fc6425c94c62cc2e5fdcc576728eaa07d26dca38f3685a27c31e1f6958af3054a01f07b531d11b684a5d27d27221a151b56f5aae20024b270e78405c5a6eeb5dd68683b23c59e064e8eb2b9572c27291e666374cad6fabc72bdd929daa5ea0a9517452e20cba263d2c14813872b17ddb04faac49c870e19b1ca52e309b8b5eaac051cd49ab1f7a26ce86aa672be00c0cb5e9221c2733c5ae795139d9ccdedd4f3fe7b202dc6ac5502128ab5b729a2d8c713288c14515984b9be16ae316c115501d454d878cf5fcf2baabf4a744a74bbd1233311589fabc0981176fc2b3e147d8ee2149722ed817baf683dc7f720a81187188cdf7f28d3a887206c41d7653b7393b269d6fa64ed69d1fd817ac72fa4f431ee5c55f846e344eea7b9dca36f143352d619c2e3515080ed22cddf113bb40708156e1ef4e16ff9e9726b00923be1140c27a20d619b651710541314922d2ebd71a422c664600410d07f554fd60e1662f96313ca0b4b647ea6ca7e3e872013d8420d9beff51c7721d492c5389cfadf8e29bf44e6badc18f430e6f226a10bb8787b88a1782455963242d8e9e6419e718d335a466d1750b53ec3ddd260663c2646bd1a7ed11edca3ec3f6eca7e3981602bfe612f79f8b2c6072643cc71b729d018f22f9cee50738c50803137bba52ac1ab8fb6a714d3d81eaf03b65dfc64326827802fed68a46e936c0b05b4e85848dd3ca2fe52b06743cec74b5cfe32a729aab36ad195ad975b9ba52580e9d5db8872d7648696b4f72bc4fd8fc4a2f01725b4fbd2e6b35d52570cd1059fc74a7b2e42f032b3481d7de0794d79302cd8c4e1093e591ba3cfd573ee7f27bfa19d70637fd09232b65cedd19d4bf0b9354f51863d24e1a0d350903cde100eeaa7bd739b472b8947af735577aa16b9bcb38057246c64899d9bb2dd6c5baf3903865bdc8d73709e4a9db62cd12c168f31c7fa146180baef47b067fb6306a4403463bfe618b287397a07117f0066e0775ccd7967292ee39de6b03936a11e3840093d120ea556c35189146ea46fb35ebb7bcf0ef057cfd9675cd89db3c1a7c0e69a4be6eb28f22b9ac2625df11668eb793d6b0123cf892c71d234e8487a8f538a52297b5a2b03173a83cc1d62a104e5ee9fa1abf7207b44bb6f2ec4f7763f4d9edf10b9d32b5dfb0231dc1d4ec08d6a596f3725816c79d66ab238d7831add0ee05def9ed9aa0d4cb1d325805489d30c39d0c9183179347fbfadd227167ce6fcfa54d1b0e6f08d7e9f55a80bac2cab3429d6bf9510fd94992186f9246e73d8177f4740afdb4de2b8b445c6c195c24b8524d2558d90a70329254e106e83bf55fbc1618fb4ca07b944825966fdf62ff9c68e367fdc97287718c52b0f6ff5a0ab15ba20da6329f59cd8fe822510441935dbdbfd9950555f754d278bdd809cf54f9fa3bd3c6a99be2ec2001166646f8f6119a6500675a5d5bf2c266efe4a18a8afcc031f9ab06e2695d4ce9d0b75dde733c33e9325ffa72ef0873b6334ce03bd99ecf961f700168e92b587654ee09d7ab70d52892737e727ed993a40409b508ca03f359c1d88447b0762c71bf2a45627f7bc90fcf9201729219c8f75ab372f72324090337860e98f7178cd0ed99b13415b7ca3685385872ea868467df8748a5085e09024f04f10f41093d21845147fc015842bb30f3a061949664a725afd483eb14a25028c8efc71bb12766fbcc0c0d361d41bbef17e76e49c0dd67f29fee07c9bf96d06769c8c9b2c7b5d74b7ca808133adbd17c89ca596e1a627d92a7c6f4e418420422729dd2b99849c74191b214eb1a4d6face4dc2573f2443af0ba213178dfcda2823f684d127486f702675d89472525e8c4bf1472aea613f662460312b529c9c8db2a4595cfa95307bedf59926b8febf0116b5572d3fd2cd247cc3e92127cb6e3ad07b615dc655d3953fcd2ad500b4861a0b834724db194b78e30ad5171a87457cc06fed752c903594807b6a5aeb8d1dc76ddf9720cff2f4404180df97719b963ff4d6ee5133ba53c82118168ed8297bcc5d75e647446bb01b1891febcce765e9dc331e3fd04003b8ffcfee1ee882d1089552b83ad3781510603223817b520131c006cd24b8914f0248c8cbdf3a83da95cddc22644a63838975808d043ec647b3342e33ced6c63b99b79941c59de54c583973997221911f2d03bf38f531a14861c3ded2e3e2e2497252f62f491029456e3d687f7242f322ffd08dbbe2033a5cfe8473a42121ecc07015b0f3ceac43e8482fdfa6472d8ffd7ead47b175ca8479890ebc20653660d85958619f62288b0b43c56ae7721ccba4c92cca32afd3dcb3253e6a9516625f91b2df9388d399ec5b4f62651910a2a95920a5d84a3cfdcd08b0fa5aa04fdfea88ba61089de6dcc3065c79c6d972a278452a91f87e2d3a3688234a2a061b59d17dd63c66f1b6e7d169cbe17cde72a701d1feb976bb2adbd14f533e48959a81073ffe1adca4d693aff0f43be21a4dde3129cb24933cc440c3533ebafbf16d3e67eaf7863de6ee7a525d0a155f9d2dffe344d4c65848ad64927b90e9d25add12f1df4087590355e6f104d6a4c0ec08ba85709ab87cc7bba1211bb8c1aa8b640594b30b3b9a8c6d1189620f4bca5c729d2bbcad362a3f00227670469432a93f89f3ee2af4871193081947e0ee73bd72e84d2d150c83a4c2b11ad7dacec6fe71f1573c49f561ac1a278705e495d6a972fe34daf93632bd825928d9fb604015166f4713a5177a4a32ac299eb37ab41c72cb5a5b658d9d9bc2c3a310e844549a871afebe463e30255ae16610b9d0378d56aef8e977062b53313d7341e5ff24672dcc19a4f3833c76f6f4f050bde0cd5a56005aeb01fb3d7f3985dd3ea1ec6e86e58f4e743684480c997a50a9e78a99e83a84c93b62973ed7ccf736d5bcafccb0650a64a4d023920d5198beb4e7591ff82043e6a9d62959a5871dd0f67805b92b6c1dafb44228dcfaecafcb17f33537ac727f367108153313bc2775d5ef63e2dd91304c35e8267c6a8d3df27d17ba8b7d32008d1ff94674710d6315314d8aaae09dff02d9388febc082aefad51bc003e6724343ffbff931d179d8f86e494065132086492e5d5b26416b83d937475c4dab67a7fbee35cb2d8ca6a6e8245f8d8d8f3960ebb1d37e2041ebbd19486ce8a6b0720c441b1459b46015e7e3189bb40c60dbe9c8ace86bb2ae9efc18c30386d40c1ce2102a9d8e51bdaf98f48e36d5022d158e2750d19e9570cc0734899f270135721b4042858f39537c36370ae49efc8c584d52ea7dfd918433aad1c12a19cc0c0add59d9872ba6f840495d472141f30092a9e2e8f9c3612677c00a566cc1c1fb657ed61adee7c84d95abb8568f0a4a91d4116aba6852abcd6edf5062432bae1172903734a954dad8e78400f1b1b3029c44e0218a07d87e643a4408583441d1c5669a77e2a74c9f0e30f9d9d62d6f7908ba456ccb6ff0a5916b3c871e40f495bf0e1a11a94a58be438ae52a3e9dc13be2839e5f3ee905d92f6b7fc99300a4e20b48a5358599e41714de6bbbbb741e2981d8b497d42bc97bae460a9bafaab4bae3372499ef7151de28dfa9083dec4ccb0d9b949163c5e461a3e2e29475f93b0a43202be02fd7d92ba9277ffb2d6c62642f540e2d7f2ca4b512f6e3e17c50a492587266684c7e2ecc58e1966007016294b51c2344f8c6d8c3ce9a443f531fe6f97b6952f4a1ee806a5c25baaf7a9be8fd712c35745af51aa288e607c8a9e54c7e1366b98f5f40b5c0ab21678f8181f14e0a9abe751c7ee71315e3ddcce5b400f9cc72c3a1044b5451ce28448e369109fa147850c1a457b911592b16a7bafa7c412f20fdf469b75409a929d8ba17ed4c80794afb46de7a640afff3313260529f5819726bff35d5113cae7c83ec1cf316280f5c9d49f769f00af973fa69c024a67dcd72a1eb5274fc1e82730d155d598fd5b4fc4ec7be10b21f6aeef9ce4b649ef8b77216cf5332cffefc42182c7fd06c2e4a89706fe4bb590049c5bae81e1d5167b67225d654b81487a3e1e541ef3fdcbd8dde35dea606ba5561dca3474ffd9cf9b272d85a8bade99b5d584b5ab3cae7d3419688ace4ecc08e4f9f0cbd30d281912972a60cc858de31f3f498d92aa5bf88b0cf897adb74aa73262be37a31df6b7cea4dcaef75720cf7ef818ebb8f0861c8480d96b2d955737dd7fde8fbf5fd88c7d072927fb8945f324ab45c1a9d7d5c5145df5e83384a6f372c3eb7d969e673eb8a1ddd84d9f7b88108aaecc236a95bf96cf0ea5c56e68dee04a8dbcf11508fa6f570df8b15f7f2d221efc3842a42ba25c68fdf3e4cb372561abd7786d2054936677223a9e40516c569084994c6c57a119d0f5cd026f1462fa837a35189d4e490ee70d55bc631329f543353cf7b81dee75ee98f76aaaf86f40ae18a916b9be073747264c1597d0cdb8e21e89de27cb4073072a30de5e5ac5d0e79ef54aab32606977220b7da0de6eab1f1eabd57f4e09d49c1c4b481f9978d2cc608f8652e78f19172b9a0375cffad23b18a07b9d6e02c2b14cc23c8a81cd0bf2aefa86e3afacad672d3953e1bb185755b9f8fb310c1cc0775004e6b4b02d69f34d279a5fcc175d72915b3d90d12dbc924abb79714306190e82241b8d59f24338009f2e26e08560326bd13cdbfd3c21d64b473b9bbfb3db84cc36f9ae4ae15a32c1e97d366617d3b72ab7db109ba2391f54cceeac5e960b3543e2859a9c6d12cf5429c9f1b57e96b72113f301e6392cd4892ad3947d374139acdc55262f3f9a9f71fe2d2ad4c4107720635c6cb24232cf9fad21ad700152ce139b49b6c2518b7f182475a8ffc7fea72ce1bdbfe1fd6b6b27816718f74d0486d0d5505176ca00fea0947f7b4dc47f9725f058c35596f339bdc804bf590b1a2e7ea42404973c5124ee93e17ffe1c30515719089a32de82651030f3cc90a7100031d438cad7cd307917628f081af2e8849434da68b5f981324ffea22a81eb105df80230ef601e7ab3e9a83317cf09e3b4109d4ea55fe90c662e1521e669a0f0106d775924d141148ce06342ace793732675ef68aeedc760f60ce9ec1a41e960efcc37352108cf8d043ee3a6e8a748c0b0c789f2609ab70d5c0a78a4266ed1d55360dcb33ce9b37ead0e88819b3d69c0339788981a31f4cad666810062e699642fbc73a23319ef0555410599cb1dabb2972c26bb0d0e815f7a7cbc460523640738af282a2b56990fcdd885d0b832f20b95b29c6403aa4787b76d7db378b3fadb39313437f89f71f22c07dd7a48989a52f727150b251dbe55c500c954df1399c5a0086d7d0f6bfa764e5586442a2fc6b7162442dcc6c787a86df7c6edadd0fd09655711793c5ecb9bc25e0c54d0d503896595909951624ada386f8b6f2799a2f71eb183403321a73be0f6167a049c3b07b723ab664aeb90408141aeda9da592079f012a36023164aa8b2cb9f21e355628f72574c9460fc1392578eeec9d5240a11bf34dac46a1100a786eda921b6491170721f7be7391a685016ec1dee152a6888ab074751f1d327a12a542e0c5e7671b072f1314915deee525679fe6a96dfa3655d1de54b9ac1574693e4d0909468050f720d3fe8fba47db9c24ce2e2c7d818bd33af8d17a54cdd00599bbe78b511bdba2e88d1a0a65dba54b474b8f29467341ce2236a7527de322888d91071079190ac725c4786495309672d12e66d1318e3d48dd2fbe8c9c5dd3e6e8d27a3076c48b9729c90e6c6a263ade73b059dc3eee2ca3001d5fb5459c69771040468f62f1da1725dc02e6560427f50cb44a3342effe5b113fcb7ccce2b650016b26e19735a930807dcfb1036c18a32da8177c0d0daf9e366b716f0f432818209dcc1e1b802483e2a4582abf0eb463d8b96ca1ac63a06d4b83667f34718b566eb376c5a5a3477725a7fb5337690a107d6f6748105eb49ae8799841b2f4eb567b510c0142ccd167223bef62d4b54ee5e33eea6a7a771830c2b9dc4073ebaecd5ed0d775427f15572abbcc3355ac490d053db1574f6cc5b1c5b3e04ca5fd48383f5e76cec8f00f30d2dfb0f23fc2ec844e96bb0e8b73a9c8f33bf47b88f77eab57f9481addeee0e725e4004a7e744f71060113b3aa8f7e27f390925691b2ec205a51607eb69b01d0004e1eeb2126b232beb01252453797606cf57c333e7de36f84c02a4c3f6dc1072fa086f53cd621d94a16eb28adda406a34eaf1413745e3b147bc7f3bcdef062721c053152639aafb46d1ee4dcfeb3a330abd09ed28710d226a3de26441c58027236a9977662b654af6ae6bfcd4903b92da0656baffd9e7165a29c939f7ea08b1f30cb1f551ec81e2b3618cae77b184fd8d052a9f47330ab667e7aded486839855959b68a9ef15f26a335566d4a10a14fef8f00167228b14117055b6e96a9026725bcc811d43c64f62d8329535b9ed6cc079e6509d59100b6f25b252f89947572c4b866c10ab71656b453d0996ff7bf909383c873ae5688b64e65cb867db600872190df407bc0a4e5b22a0a3f82dfd8e2dc3b73e6be3bcb83b6d244542516afd723611d8a35749d31e051a01bb3c7ece0cc1bc620e54f09f55fcbc8bf0350a2d09634cce8b315295e15234fce3311f703949f1052baa1ae08dd153c888ecad9772e62fa34e0980d51c7b33e809add53f795fe42290d80d0ca9f80742b3c1a0eb727a5d85c645e8b3ec99b291591a4ffb6c46d8be3072a2fbe1e47dc0c8ba089b729fe5f88db3a1affb4b708c2a8ae5fff9dec97478acf2cd9fdf73e718be587472b9a93794cbde16552e7daa3a790137be5f455310e003648d288f6fbdf7815f5adb06d70376feedac7e1b4092e8d6a914e49d4250cf1c63ba3c6215301fb15638aa2e46813a92adfa19855d45e8990f18c2235addc6b1e0eb840e602e392d04724c89b6a1070e74aecde034ef082c1a1a6df6a5ac04803677d5e71b6a08afd672bde9208eb9d815c4e250c0306db20ecad06534d71e09db4eebf7235ff1d40172c7ce7aec81c760ca6ff90b7bb952e2a4ce7bf215391d3ec6a786828baaba9972238f5310f7283e77fcdf45ebd03c54d2bc4ea5445245441fad17a10c99246d72a27a9e33b62eadb76e5adda4584e5c7523e5d16505a4d95cdb6b370cd5602310344b09089f596b129abe2dffdf017758593bd0039ec87425dc3b5286a20df0454f0070ee43b08a165a5aa39aa6ec28ca14357cbc507502babc1165e70b3cee430de3e0420e8ad0395a21fc68a483606a4fc6911c96b9d0a9f086861d97c97503c095e0cba16b4e4f847b8bc11a3325adce22b713d2c39f62dbf066ac14061472814c32b6d1722dd492b7257532f5325d6a0ebd858bdeb4fd9783ea6f6dfad4145432c06de82289c7428f749a6c72447e9044e84ee7b2bfef7abce065871e2843bff02a6ea7a6bf6360789cfe1db46672fb8912965de79d814cecd51f512bae729b0b5d27bb8dc8bd33c36e6ea75dc928dd7898c039f86cd5e8d7965e10941600648a0a18169f5530507515bf165b701b9090631e3fbef1b0b4946889d60b10723eac007871201e879dc6a7e8a78b41ad36fb0b9794044aafa950ff980fb00472be445ecc7c639b3d3555835e5e433ef744a4f1ceb0203a30872e92a261ad27723245a404b19c30c3666271c2945016250184e0180fa678b1e79cf814b8a9d372e2b8f522c0e66826007705a9b953857623c544ec1e5f7b9f1a8c3773c379ef7274cb5960490820ae87a2853e493033f6105c3993f0e37f7753ad7e23f2ce4e7247a564bd5b2f693f6c4a19de89e58202007ff315909027cef2e8575d5af4fb7240dab08a23f0f7688dd42aeeb7a4add2874c4b015507bef4a609779cb1df1867cbd7b79a4a033700824f3c5c922a26e95fd059d6602c0a2b9d2d842802f50e726723f20a8d81ab029a796a3ff20c4916c649d70b36181fb850b34c91b2c685162c10b642e6f9d1aaa0f5a957e8551b45b8f69be6ba5b10204bbc9972edbc8d526525e39e1f3b84b7a35f39c4adc417e177a8425d0d07dc929f08c2204195fb2b80fac0aad683b285f23b6ff5f7ff26724da827d91d28a698c50186abbe8c3672f4bf85ae51395d08246b88a8286bef5e0f986f10ef9316124fdd40227b077d4da516eb44c454e4c33a2b91368d8368dbf39559236ad08e31b3c8baaebed0fe4488e113d24ca5cf0727207a5f4e7d80b38f39b6c2083ddbc02964aad63d5cc2729238b6321e4ea92f22df5527d42f20816322f43c415eca2cd0bf6c83bd7c9509d8ebc7ab74a988a3824c502a86ffd0bbdda4742e090a56e53989807f20769e15231ca1c78754016f5dd89c1e93006da611c7b29f12635bbd1b4ef5bc116d2872b8f09a35af10edeaa45e2606b20aa941ec92b189344265c18a07e8b9a0ac33722e04b325fd5346bb22930d5804a259141c23d8e6a0043a06050f03d680eaf972bc572eb91b61baed4e126c245a215e0ab088e7f485d8bd21ad91783a4ef2510a936356d0a46ec316e03c2a8aea4110493f2e8ce4bd8b740d817466e09abdde20ef0d77dff819f9bb196aa7a702b98e7879f504139873ccbbe46e8502e5dd16726c98c624b46bc623cff45c5b52e5c0ce7279203cff326f9b0e2fcb2165be937219ac44420d6da2d0cfae81015ae0df6de75e4458efa2093b200e5e79d9bfa9724ae951011ab3dc386a228acbb55916dcc4a929e51b29cbe14dc89445d3439772a022094e4ec390b9ac385a03f9d2a3871f432422b0da90cac33001f23a0d6972cfe52246b57fd046c81c98954273f553b783652623fafa5280e9fe3ddf5cad72924789ad191a174fed1ff13877d73662c1b9b4e64f9ca12a3977c6635bcd8772fc09ff91851e9a5af03ee4834d02599b2e14feb9a5e66cc03aaf0154deb24045d088c69db94689fcad1c59c81b08ff0c5e1c33d59b5698788e87d04660f73607a2d0b0dc3a9c7a6f3795f67fb65bfe245fcea40126a7ebe44ed552205f99be6e14e27fb37d5e275940af3f66813fabd68b5d9ffdde55c1b8bf90f566ca26477249b4a210c47e47572e912568fe360a12ee6ab226c87ee340469c64c87038fb724409adf517d1bf30b2016b2e6c4d92c51a5a591ca64044ac6f699e9cf6c38e72cf92cb602a6bae1b53d671fbd90baf87b4c9db58c9c12ca915a1b2c6fc1a4271f3a838d62851829bf399d0e4aa41eb467ddb3dcba8364773483fddcfbafacf6449dbface4a0f70cc7db2cf0fa61cf1bf3567b8992775595875ea84e7dbf19b2467468406c347151b6e277ca029cbf3ab9aea6ab1a07ae074c8d0b7dbc64d6066415cbeef0d988920ad8af949c35c1f805ae60a23e1c53797521b9651d306f8726b59cb4a265eb3a222d8bdd3d94eaf14828b113ae746ae0fe1fba5fb0def2b0041142d75278dd26a7bbfdc1d39e1e5865c169d91d5e9a0933d04bf1872d176723a1aa00a19f9ec982f820b8b4fb73cbd8f58f8204709e27adfea0fb3c29bf772e9e371b975842636d2c0e55000fb48bdd5211e06a430db898e0c916101d1b372833de43a0e9c6214044ea9ddc911c5107a805b9747c2826e49086ae49dbdda72ca77826d539306e8e1d79b44867ee3dfbb9eaaae048f543c6c1975235ae38d42c2855fac43bc11764a9fb826cd0549310ef196218daa960c9f2e6b95921e227278655b52c87f16b44fed126643381e9c6fe2b20c22570b9632e2321aea3eae720f54f7ae4cc128c70eb4d0947189d949384006fdc394ca264d896449916fac2ed377b7b150e3b40c34da3278ea989949d83a1616d1a16c33aef890f421eafd7256615da2904758a2f12795da72e5a9283f8d24267b138d6d361393f5edd7b44ec7cc1780173c15df41d1c41cd81b231d260f5da2d654b921042e4fc2286c0b7256011dcc86806987590d59b2c70250d34dbe90a1b447e9186c237ebe9aac2b728b186f95a4ea1467e797d26d843bfae6668129fc5693f6241b33596639479872fcfbba621bdf57c91ef738ed65f4e2c9d00914d53a3318c2c8bc9d2f944271726b9f456ebd4cd093fff2317498b26db835980c64963c1a0d7ee63e397df8fa7280a5068642ba41635e17ca1cde7aca1dae5afca2eda4076c10afd68a5d93465d10ad226dc63416788c8f864880c0d534c27a77c31d8cfa25d745f8211154ce1eff4916a6cd0bfa9338f73b31f6edf64b852b251534dc3db290f6c826a2f2c1729fb6baf72c2a6e0c39a6e42ddf1c2d57edfe60f9f3269b65818e6bb9e5de7a72dbc6bbdfe503fb86759c7b4c74db72e80cfef66bdd5bba39342aa6ea571e70727ef34d4de8ac757597ba38bb6010960a710a0f0bbcddad51129ab44de171d06313388358a2e82253c5feef6a20bd52edd62c358d5f99cf0b75a0d2b073dc6c72721841895bf60a88c6240306ac511fde048360939b19326e6f99cb21fa6ee972d1553b57f2ea933ca8dfc6f5649dcd6530f62a53cdcae5455785b8d354ce9b1cfac89335e5a77c467159a84da4a98b5874ca11fc886def3b8fe41b717bb9245c8477b2601cb308acfcfe26029e0971abbda0792285b15d347369b78240cb3f71d552c8a24057744e866863d9a53a178cc0f6e1113ab99f8f21239764be761628ea718076127bda450df407d658c9afefdd95aa15587aa28b9392d6df17755f4bfca39d1b50a14124815f5152ff097454e6ab7fd5f577a75a7056f03e1e5f2960e251364a62a7348046b25c1b6f3abba4f420df4fe4bddf8e10811b242809a2722d80d94d74e9fb9246b4caf2cdcbefbb43fa6d707ad4c1f550283e9896e411547d8efc32c4e052af744134ec57e19aff25dd11126feb7c036db60777e394ce724e9a85b2432f19906f083a81e910f2b6b12765a8c0935e7b53818a08ddcc37454122e7da6673d8d0c3acde7dda43d7d7532c7059d69bce147a52f233210af07282f7562e4558ce2c29b536fce45be226726352044841cb60de793573ef0641722b988217b58e1ba5cb3d597156be8bf187f5192ab25ea8ad8afc3ebeb0e55f1c0b61688ba033652ad3cf2169216a5fbd92d90d5c99d7db4d3ad2714e9a8c0a72ed2b8ae32a976ef7c2aa9a7bfcb8c0b286ab8555aa9d5ee2845f30f56e122672d54d755e7d061c5c39d6f64bf181158d69e7ec0376a87c9958be70f2200b6572f634a1d4568228b9f078a2044a43e46dc361579038e9db6880b85ec79030c0521cf18d36da25fd8c8c69a3fbf949a9ec3d56caa03d95353e6707049118a89372a43ce75f94bd6d665a38fb3a1be3f78bc03795ca5ead534ee6a1d7255db0de56d3a5a6aec462c1bed20258417ba974161bcaee72c65f336c65f2a7eec186ee72536bdada6d5a3dc8385e3a515a9e2bd81fc96112f655c932aad8b5cf28710b72c8860339307a2e71334fcf988474bfc78a78ab7e17249608cf890102a1e4c9237cd7249ba32d958e1076f4a2f8b739a028caed41d18edc8be3c5bd1ee192d572a7530a509cb7f406d22e6aee7cfcd119256af10591e46c676d2b6893998e341ec8fe34359e750032fc0b9b50aa6256c6649e985a14b6627a91a5b7ae5d58b672757d342ab6d65732a181353ebf5c0658dcb36c08876b4a7c9747cfc22d3afd72aa38df5847bf410204cb760032ea7ca9e76a6daadfb9bfdfce5a26b6985541726cba6311006601f2ed21a336c0ccf6339ccda5f1bf23755f7c816de64a9f0d0655062fd98d3d276e4f415386cb531a401230f5e7ccf3d9a50d5acd39b871b6722f27657eae698ede7f1b187dd16afc091207564cb3ff73c3ba31a220eece2272fba7c05eca369e4cc092e986e6347e4043adfdb0122988807d2ff988aae95c2eb1ff800bfca7d2fff45d1ca9aba86803c4249280a3b9527dbcaea5e001bcd802de9aabeab56677765f551bdc626d11a623d322567fab9d693414094c01004342ae8cd1888f9eec2c93838f0fff1f43dfb3267d31b7b1ffbcb4726c77a1f28872a215b32204b92e8f52e828e892a7fbef8d8e5ef7ce0bba1963d0dc1a812ddf72c6892f6e6794184ca0259ce294fa4527809f15f758ac8e8fe339403013214517b72efe65dce87acf3912a467a5014d15cb55ec38fc065783b753811ced7a781f882e853eddef31de59f04f841d4867934c815ec3b555c73bfef0c04e5fcfe472a21b06275e96eedd4b515790c45793f70270101ee88efca538579872bc511b453e80375b4787c0b42c3819bb9fbcfefaab02f247585ece1bc8dbd91aa6f5ef72f838bc9dcaad4515c1b25515f3702bfac40f78bbcf931cda6be3410b53a96b720c56ef43f4eaabba3ccaa3841edaccc97a1890a5eb66dfe5554d8f59d375015ade093dc86d46053c689844cb3dd59b776894f47dc32f30333d27074c17665a72079e53bff30ca7d851c96f02a42ef09b3904d05e1b0d77d79a9e4ab254f434723e023a6701418516cc5b41f956174cdac96ea99ea03826d16087e7e8afa1197286e95350ccdf9774a43d4b9884677ba1fb3f31e4807700c7aefcc27c3bb0747280d4d713fcbb5e4539fbb7211413d1ab09a2968d137697cc759f1d75afe16c72ca78500236a135f7ca6d4502636e55af0082196c58139972c380f497604d905f75e5ce5b1296534e18093feab04173da0bb817b663858d02145521fd91a6c87293c310f50fefe8d59049156779ce2cc65904d8a5cb3c461d1b855b89b63ea6391acfb95469ff3b23e00bd2d9c1c0fb2d356d7488004d7fccbcea6d647036761099f06d0b9074e2ce3b1cdf835fbe8f6ceb5a573e06262d9e72c9d4fe798d297275a928d83de3fb70f2ce3a02192b7a1417a51fe12ecdf2e65e29248db0192372195f1a11d909307f6b61858a9bc40d9682e65504ad8448a97ba33fb89099c53c6a4d72b7031c649c0220e75208ceafa3b71091d4171efca8812d15ec45526429e4cc27979f037d5fa7316683f206df0713294865d51bb3fe65786057f53e8f7202bfe30d15bc9ec0d00a8345299a92846186165b79d866f2dced03e7b4e28872a24da7bb0ceeb3cfcb8b89c5583f671dfc1d3470b63ddd40ef452d77b419b101ccb66de911dfc5a4bde6c1040b7d680d483ccc9ad479e3185a5bdefdf1582b7213e50a4291fa58207ba1b884d868c6ef4c790d75b82d5294bc04a5c5e9e5157261bfc9ee2c6c3859640699e06b5bb129dfc808123e2160cc7711ac77719dc228c7b396d012d312ccc0222d42c4b12183a69cb0a63482a391e4d1e76862fc967212cfee0a35e7169ef2234a290ba72d1d2673c6a40d8d4390b28bfda6cd7fed7255272da79e5d75b19b6155d7b70093472b76eff2b4ef080abe13ef9e61d3b272be4aaa06ad6efc7628ace5754348acc26caa331baa41560926d1f03e1edfe5727a7657499c883bf1f44b7eddb50e859b48995ba2882b23ddd6d8470a97606d72400d9271ddb0e3c3e78d556db80fffb3bb06704244b4026073312c60957d9d204f0b71afc5b3594f5ca197bd0cc904a119c59d3ec640aaf71bc7e2adea656c5a74bef3607d2875fc33252e639ac1c171dda868d7e8ea3a41d1beb42716bc2b727c0ce4e7734d97af51b7cad7ec51167744a64d6175a896e9a0eb059ffc3f1d3df328d6328af0bc8ceaf23d6f3641c48462644e2ebf571e84c597943f124fcb16be400e55982c6b5eb25d1a060a6dcad96d0c6f106c93e476134a888f52eccd6b29fe9d880c0b9c47775d82a3abf0b87fbadd8e166d1130d1a07f6cb25b752572160868c7c945d25387de56551e5e662a2959ed64d4377d4b6ccb0a45e145bd72b076191bc6dccdcded12ebd07b36a4ac2f949982fffcc10937d07129383aa2725254c1fa8a19907e3800b116f3a0105d9d3e0572e650000f846c22823298867240ca7a6cacacd53458fa161bb049b2cee5e923a2c17ef12afd536fe8d72d9d7269c63c9a65cbc6f3c1f4d5ae3ec940f53ea72efa9bb6f559e17f3e210487e572ea7a1347cfcbd8ef21ace562b8cf848e7ba58cec6de55d45226258c16b5abc729326546f6aa5a2a29b6eaf48ada4e172d08475e8d3761ef94359c6d2224f427298dfb18de107f72d359ca78762df24ce3799bbe1ae6248514b54570cf3ca6a72e395a92c10022667073dfdf01eb543129ca4c41e80f73b70a5510119eb7967727550b40a1c5aa835c043f7bc0b138625a6cbc0505175938b1042f021614d14726d9ad2a574599ab26138866761bf45eef5c2f48ab10ec8ac54735c668427d66aede9d96e93c43364779b512281e258ef2080f55cdfb303fd8bc397a5c4c305723d711e6edba0068faf009384e31181980eb526f6b7a8f93b600ce941274d97724aad88fbc4f8eb21b7d7de4ac31c9b263584dbe2d25e64f925c9fb6f878acd72ec89f656f049db8439a0405987e1fde4ed4d2a1fcd42e2d17db64e0cd076495ad7623c9023ebde8434531930f99a289fb0fee1aec23989b73a6c1012e9d30d72af913db83795b7cda0d0422c7c3f1c90d4b7c2016f264d85935a6340fef49516a4186b0d1bffe50ad59aa2ff4589a1a8146c8e5a39af41d003614adb716ae13bc14c8d454330f8522d2dbb08e983ac6621e4522595ad42fd4e003b4a7a904143bc884781732046170f7c67ebe556c3b712898e8047260d9100bc86b3d75161724c2b08e8cbb9ff31b93c4c3ae28cceb948660415dbb59dad005786d2f193095425b14264243171d3a3f8993c7e8586d14741790925c18551eda9dc94b3a5cf728eb617c47ef8657aac17b45da046f28259046c143c2adff4446e494897b984729e8e114836d0c3323ac1fac7b094091551b9cdcc84204ae5ab3f5e1550a3e172948a4ee84a46ea2140d8d77c9561acc640524216dbb5249f1c607de0e48ce97217867b962c875b03fc0caa151e9bd86130fd8d184c44c83973ed39c5b144c972997b708735a5ea779ddc2203e17107e414151e2ecc602c81bd1c25a0492bae2ab46be704c9255a8d8384665672adf0803daec6af9b5e97d5ad4a880a30b1a65939bb2ef9f5c9bc203484bbf5f8e6b87c3cb59e6260ee79b119227816bec26772789ccdecdb221ee60a4ad7468fb22fdad56054d51a42bf6035ab4cc2329c200b804f81404fca6698fe2593b78afbcee1dffec158905ab48cabee66713f3a4a0a392a6bf52fce96e09ddcfda1d1d5cabb601fa1d862347363c8dc0e6cc25fa972548e0de66c74ac148bc4d6c0593de681a91a49c48169f9289aa7bf287b309111316c3756539e825abd14da50bbc7e83288a95cacb1d77d3b21f02ac9d6430772732bb211e23b1d6d342c225a6712947525a914fba1713558895bdb9f244cd072c00de23640490fcb5b23b75ffd37c7fb3b4e9ac5618659e153ea124605eaec722c12baacdf00960c279b22d2bcdac70ea063c4b1efd8960a33a90afb09f877722632c2c91d3442616fd8e2e03462827ef840d7dd4b89e2044cc2831b5e25a6720f5871c0ac092241926ad258af522eaab2f90144216caea903b240b190f1cc66b2589465470647cc7cf027e6a4bcf673d7b8940f220c790d542425cf9f79c23d87be9bd17901ab536976f066dbd1108d85228cc5c4ef9fec093065b394bdff63056342ba28ea9cc59cfeb13ffdfba38b183341ff7c00796244efd073d5e8466e88a0cd4c0c18606b0f9f64609d831bbc5999ae261fc78425d2e64c8dfd5efd723f1d2fb353a7d02d92f4fb74cfdbc0d72e1ddb0edc8bad561527371acb5c3f728b08a1713ac3240333db53afacfbccd13c407bc5b6acf8ebc8ce9fae6ffc9d72cc9df51363011570ddd25ff0d9dfd8b15a0426b46aea17d2e8ada3580fbafb72b39ac6306038147a0ecd2afc4e83def8e6c3910bfa107901bccec51f68917104879f5ac46bff6df65c9308c9c274ec02fc471d6f87ad7137e27be2580f8ce934532fcff787e808212e3e712d4add4233c47a725f4cfb4c20f5381ae3c0d50172ce93c81a4c7906ff91772ff84b3690bc2c5872e836f8cbd7347bc44a7526ec724c7c8cd0493daa44b569c4e121985b01c276d3bde73313598f52aa8cf06cc2223c15b73e8b597a5204c43bdf4d08b9fc457cd9a7c477c010a48262e19efbeb7277a767db60a66ace35f4c3037ae0af10cbac3fe97db60ee34d76e69588f5bd72ab23122d907838a1654cb3f48e78ed50011adc976a191150efdc90df105d557298d07fb718018dcbad25d15612dbf67138040afce7cf9dca17602f534da59472bf42cec024118078059009bd992d28447dfb1cd8d3db62b19170ee49af2c16703d3a93fd5af385210fbc47913500eb9f54e8d58cad233cfab852e87c554986728a65ae4c410ed3cd4b5fbceef096fc4a9441548b99954ea17097498389594d07593c03aa04e52a051d20e818d2b1d71590f4cf1fd2aaab83a6dd9ac007a5e672c4a7e1204e7d579b3489f691116709aa0fbbf4d43b7a138104e8a4f2bb0b737218a8b4b80365de44850930e5ac3ee66e01a20af594be0e2adfac1633c9f9ef728e52700d9bd6e49e6c2836dc23e4dd54a6bf890aa245880a531ec56bead30b540b02a5ff1725edc801edb7e930a6dab457659c6a840fb96854f7267449cfe1725d231b3db35626bb3041269cfd0130a3d961b37acbd0d95b679fb5ab1986896f1d05d1e386b4a3d82873a15de40431d9d86a186758ba66ab71f3640c35d887149dcdf7c1cad643f4d1ac73dd8571f4fb8d7ae6d3e58a278ee5b7fa1f48188072bd36719f0329d0d512c6c44b0367dc5bb197f8e376f717ad270c34e65f29fb02003c88094871fdc9b84cdf2e3c6927a2cae44cb79712108fd5cb58395aaf331e90b44500d8e000504369a3e44ddc22322d170c663552b2dfc9a06397767e6b18993e210ac04b45acc66d3e6fb66d22a8099f99b4842af48e275a6af7c5f4ac725cbd73c41bd2226a8defb5f81f97ce45d74cbefd87390fb1f3d903aef6094726e134d978e928a4e57971ec831b671a0acbee305ee4e96b3d8d4b1ad96e22291d49269d4a0a492a20ee97b9ede3694d68be45626f28989a86ecb73b2c649ee330a7717301ad60947edfbd7f929257ff649de1472969233ce676b7619dd341bb01d1461309ad5fb2fd35f3f1151c25f13e68c7ba94eb8171ed69252070a7ab712236e68d03001308dbbd82fe56841eaf09251e938701bd9e077d8893196579f07202f4b09038a5d8beb6756681ede0ffc9116c73a685f91bea51f0b381ae35e80ae4c0202f5c33d2cd76241043d2dace4c84e177bd2afd00056ba6091574bddb42bb4265307df80025963be0f54d92ead4b94e06df861d9d7240b06bff4e873172889446c909d38a43c4ab5727d24e3cdfe3532ed8bc2863baae93c88f02dddb72dbc6f9a7f15c9c0d8a9b9b78600cdd3c7f2bc9750eb711e17378f28dcdf94310f1e5ad7d1c4ead2b345d871f873876d45ca58a38f59a530188c1e6da02614b6a6d9101caf4a12eb489f7e1e423a81f1571aa37a12439175904c8b1aab2608e49a8d2f6bff03cb557a6dd5a7e8c3cb92c5e26ee7286fb4276c9c9a6180dd3ad40adf0ca26732e5501696831139783370053afbd69f116ec9b7c8735fe664c9d72ecb468e7e532bd5337c5319335aa51654b0820732311bcf1be0e4ba19d5c007292c36322f6d4f44cbdd5b12dfb987e74663272fdfd90e534b84251d8f21c4d724a95ce0c46552f30ecbf14830cee23fde259f74aa552828f08308490b7794d5a23b110318c29e53421c9a4c2d200c50bd5ff411e5a2b4a65d3f2f288b302d5725157a16c02b4369529c793156c63fd37fb4caecab13fe773683ca09959ffc133a2acbd0b9c4acc316d09279b0b2357879e1699336b3368b13e6481d4b31d4936c18afe5afefa1a5b1c09f0fd97a50dfc4a8007cfa2e026f1fa09ad68e4b37872b2bd06c0d7a71583a690f552e8c730d9819b0a8455688450ea12ee18c621e9727c8b0db2ada508ae04587dd393972bb6a0a5d6bc03e27c065229f244993bb272b8837c36d46cfb7d601b30bb16e0e8ede49f8b4d71df5fcf699263ba40be92720aa3bbd54520df89dfad14283003936dee4d7c397bf01f30f2e7c4d33a98c303801af66dd95a3532648cee9b4e25f1e42f73305e786c936f175e2a8a616ce15280b9e0ae5d616e33b5c8e4505775270360783e5c06e3450998cf2ece23762a726d20cf2620c3e0659f0cc4ce65844b90015edf4d4ed82a56f3b37e8641f52d356f014360beee51bd4fd72d0208ae19b027dcf7978b29bb1008e8806a9a9607550060e8de41c8c56f16cb3fdbedba10a5c964b174f28597fb246c22f8b100f472753df587ef08e1848972309290aaf4a0a9a2e039c5199455ac54cd946e72651c5d9e552018a48d0ffe88a017d0285c0b76839a3510267aad847fbaf1f6ce1d72889b24f5d9fc8f730ed8f4e958f2213293d0619322d577a6041ef884b1eb5a72a2503cecba4644a42f1bb108bc190bcd647c760f488d07affe73241cdcd2db72e9db9e22979c747e837da2758f9693b13fe61f18a4c63af0fbdab93e5bd1455ec73f119b4f36134fa983ccb11aae6740a594d18e7501948cf60f3bb6ceb8415a28817c8742c69a12a1c921ac5b01405af696bd7b96e09fa2c7e487c808d00218113a23dace4d2c0c456a1cf2a4459693dcbcbe3afa81a9b32e36743868deb960953700891243703a7a25345c09268f8d8335b1a43709c78763dcbc23d1996d7272b23f9f6073a24fdb627fa8d5106cd896825b6891bc260a8c01849b6322747269b14e9ddb451971fb469a4fdb4e0be500bfaed84d01846b1d9a0e16a257cc729cb9f7ae5b03e4da78ad8fb617329aaba955b26a5575c1f962edf5712b2bdd7221d7a658b6223a404ea1658636e6a187f1bdcb1cf7519abb2787513cbcf2d572284df4afffa809c431935e24d20cce8ab4b907b1c70ed8817085a8c88ae8ec72d2b92745ed2b84aba1e4c6055d141f9f16d3886a2491d8030e1cac926e2fcf66b701a2b6f5b3efd093edda49723b9d8cc7fd8d9f362158fd1499c51b542c552dd3fd545b7fe2e4ec62abe7754ad97030b9e3f65e513a6f31af0924d9ec5cd67249c5c63cd9832583343ffad21813995aa7e4794b8db44158dd637bca5abbf97294f315009b23e3c32da86d7e6d87562a2d85234942f97e12c3a293eb95945872ac83ad13538c7ad26b975f5dd5e9bdf68cdd1e4bdb3cf49c7b849f8f2225ae1a1c6a0b5d9776dca52a68fbf752ef308c3a15b893e6a2cfb5f6f4eaa570d3b37227003045544f70ab26dad157093da76decd311a1184c7bca793e9e20038a0272171d216fe36e2a335994c80f0b2b92d8058a5460aa4e97ecdd0bcfd5f0961f1a9042dd8ba533ce98d3d1c601470abaf41296a143e14bf53e1b9125c022689b70d960363d9fa70f36fc3abc66514994d1613ed4d147bba21e89c0676253f0997222d315698c220a8e6ef798e93fcb427f509ee63eea189df41e24a6baa944a972995b546587a80966031a497217ce5c19ad0e8b7c2ea72616ed6d9b53b46f0f6b640c7c4af5b857078cb8e4d6015f837ee7db4e4d4e94ced5591216627eef45724805e1c83837438666d2e0b14734232e1d2f6d8206995edba0fa4b9d22c20572b1ed19a5b6b44ae2a6f52164f77b81aafed8189e6b742d951c08860d046d7a72a1363eb60e759b6a0aaf740ef5fd5e32d6b67f267fc1284c9e8f3564dbaa11592ae56875315e590fc3a72c73832a504da0f072abeeadc7362bfd13a45088e026958e69f8163698116523654f468ae853c4c651e822a87b14140adeec26c7d3728049e4f91513dae9be146f8a4d4e1f5a8aee44972eef25636b5698a26c41f62be2202000e39918d879ac4b564fdfca6b77eb4dc73fd262fbbb5c1d9ad5cf5072ea0562ecd46bb3bb3e0156225cc03aa7ee1168a2ef37130a4e2ebef0b23f3d0db6c7cd46a659fb6dcbe1276847f22920733c3a25d44ce5bee941576d7b6b0e36777cf8b2b2dfc55845fdd11955766dc605f5ada216c974a1d598e6d323eda9723d772a2c1ba9334df19bfb9396bd05aca87493d6d4c8bd1e5c4f685f92231a2c43b349c1bdef475952f6815c8501422d0490f039ae5a91553551a1b105f8eb6093fe4a306ed7138bf49222697a9ba29d0b72adbbd04a5ded847ab8a01bdc691b33f9d136323e85f15d5382057ece077f97de3678ad05e3a0bf84e001fd49c0593876d566253e5f7eb9664d1e9d9468b2788fe3837801bdef6465cb731aec6e72bbd65a0f9023fcdfbec3f9730936c4731489a87e788cb9974430a582e546180b5527c02e08b4b0ef14d1d8125a2bcb14e705b646e82d6748cd8bedf4250cb9729bc7206769ab100f120b37b141d43fc121f76fd7ad54e86a2fcdd1c5eec10872d5fc3eea429d1b6cfe027471b4c90e5769e56a9221beecf10cdbbad51fd71772d8dd8b7fbd292eb0f84538188fb1625b31f69036e7dd9766d666df72c892482714f794eec21994e91138bc4dc232b2715f5bf6963e054920c0b14a883c6fb67279ee580d07aeba20fb36bd4d31a7b60f5daf096e437d6b3a669935eca77c0672fa0b3d28ba65768649cd7c54c56e432b997724c83ddfd05f88b6312ec7ac166327cb25725d41a198c8932c34418af7f7733393af4288e49ec00775f15325947263c3e60ee6bc4a7a339978586d6b81e81ab90d66d88e27157552ae8e73026b72ad23aa4279f38cd2d08baec0af39c748295739b5610d0ff1d3845ec631c3be7246e683c925013ec895f652ce845a1faf4cc4222260aa3c73788faf9be1d7986a1aae2290b3db22eeae570fbf6f1d2e272424f3df073ccf128018d1e2ac479752111424e450945ba67f1dd4d449cf3f9598b502b825d8760ec9ca1927800f96182e702c5b9e93a49b0174b5996ac5114e2019b2171a1b14f14892f4285e896972f699f5ef3f20ea5c52e297b54187d95d52266e055d14d0b979a4fff37f5d0b483282fa97623ae89048efd0173f81993c8349d4c2ca315dba0882576e83bf8172ebdb1aacf6c517386cab3d30d29ae0b9d8995715cf39d67045f5186ec1448732d210eab8ab3d3c97dc715beb0ce3c71c9c79198ea67a16163310781fc3122a727cb68313099ba1009bc05f3c3eff02a03e72b96be84428b66ca1fd95d54516720a20ec943156a6a532d2afffde820942debd57ab5ab6091f17072e129162b402cfb2372fc9db112a73d0c46afb448be65597120d1da14b24a63b43c02d715772744ba7a6be3743d090205d6992d63074d27c3e53bd6f3d1d4e4b7a084c6fec51ccd3610e9f04fd4a57cf74a68f6090e3fa54c8b9420d9d902715619078c09e05e4d55bbac02c9eb33208fb6c263c4b4f2aa8317e812f07757d6012ee16654c72d9d96f049ae5f7adee2a77698e0998e89745cda042e8b7ead75f8daeee9b927235b46f50904113779fe5a5f37e57c5eea4dd3bffe03c9f68de24b36f66f5d2728a120ab85597374507aa1c294791f7f3f231ba2c71fdc371dd53f83a105c54729fcb92659f3af7ed9280c7246a77c1a446dc7a3349a71a4161abfdcab27be372ad73cf386f45438ca32fd3ee1c33b748e9cc7670ddfe0ce9b383b9f5380d0072651719440d09cd509a089823d733550dbf3ca59cbc263a5abef82567b633c372a40bb8571c468b09eb31a47237be91e3fb7207f9edb6d4b70ef976a5075fbe5cf45897db37eec78fb4175fe5c3fd9cf9b03f74431c1261dfb793a21f2ace2672b67cbbfe12c538bc1b0f6ca542c005eddbcb4a91c1521e12348cdccea649b572f8e3fcbd9d620f3759fcdb9bbb9426c3513c947049d99663aea1951a2bb245721bb568960a5ebab447f0eade40f9ac041b34532b52526e3820a7a785b8e16a726000ee515f34d4a391dab14e344ce3ec2b723dcf0f0503d6f1b6126761de4c64d1ff720e7dcd7e9369cd03b773218ff1bbefa387b0f70b4c89748bb9dd0ea606fb594ef8a07c3dfd0779a24ffc367f5dc6b43b3f698a111b70669f7c067e1c72dc4046216ccaafcd71d2ae2d0cf993b4aec6176f27690bc64a43aac897cf110067f43f731231c58ce62e6afe6bc23bc3a9446d863913d3bbba7428a4f040fa72bf974e9e65e4b3097da87200d92bc65540246712fe3fef15c1d733acb1a3bc61e3497bef21ec0f364c6c499a54dd42b300ed8a019d9b23a86d0106825e5aa66a427ce0e9e707858ae61b88ef4cb05e680a954e760b6153a49828533e6744441adb5add918a6555c1aff0f45da72bac37e716bcef859b22d3dc2a4899594e67725667ef2db232cae59fcf6c7bf38cb8cabffea4d5ee939c4381956441ce55f87262e6c198e1fca3e48add34cad1d1b1365396316e6a114ffb99a5dcb0f1f70e031e3785423c5dc57ba60d94daffad2fa09e6cffe0f36db9f02439544aefbd96272f896b36a362c36689a23c4854e8b61b88f512846f631d738ca4e202e6689d6b10eb94c017731a620ca8a4749ef6a9f7c2aa5be20b72d777aa0f933a941ffe723dc525ae2bd886090ec98df659be6a5dbf9a9a6e94c91a35f22c8dd25c68dc07ea1ecd42403111bb1d0b965deea729fda425b879016b15c8f662e021904c5a07580a371980216b4cf9bda42e95b109da02b6d5249aaac7f03870b959639edc3ac46f61349675f0f65091098871beb13d1bb29bea4a6f3022b496b278960c6c19124b3387821fe5a6a256981408f71b9eaadaaa8de088a455da5ee7847cd09172975024ce7677ba94fca30c44ed2cfcd2a52c59f9770f9637e5a31c92151352725bafc801a33802dafc687fdec8fc64a9f414fd022db3ff71d5070975a3039f72c845bd59531aaf7f3d7b3ae4562128e4eeac1c41c102a7bf9dc30087c5d2e6729df846da06f5a9b0636031c8928f76b9a0887cca578983d90acb7113aa33a61e7a66d876dc00810692f8a9cbcde095856c0bd1d0d8fed3c57a3836e6cfb97d11163753175e4b31e0e6a8efb3e8a1076323647cae0fe8f653b400516041c8b16bc192b84aa5d8db418a12c1de849eedc1ad79da3222b3e0cafa0b922040302f72adc4ed7bf758e0c9c264b37dfb2bae32da2dd4515acf60754083c0bb17f9f867cfe8f1527817582b292ef0b48a83fdd6302fd7a40b88c741a751c1abdd936a72c45e679c55a85d97d8675218f84219436c0903c7d1d5ffb7dd079fb978b13e7250681451bb7eecff654eac640685e2517fa15e1550177b1acc5ea1ababc644728be625a2a44a7a3fee952f5f1370432458e7121d77eddf27a4466294e31bfe722347c5884abe6bc71ba60cbc2b0914b48aef7fad0d9884a6bc69cb40d466dd600e9a51a4e67ee3a87cee68085ba87353315b190dbd86018519806db1b4c25c72c88292a36305a50100ccf6599521e06db0ad7916c311c314170892a3939d4707aa7f27cace7a0144a1ab4fdc01abcf1a86e442c8319d3aa200ddcddbe2737972d9d8d7edc3f78167b61c6996e4497e394c07863fcc1ed86875f9ca99fea02772f775810cb161ec4547290fcbd62669dc1727f68675d7af6829ed28a03484c8726b880c3f003d17b9867117f2f012c976f3b6698f8fe2ae15baf67d6423dab972e562a562a8192c04f55ce958e9f78bcf5c757862ddfcecabfa838263b18c6b00861499e38954915882d2285913ecb2d1da5a240b8aa4dfb1f291d3d839588b66398692dd767441833d5219a67532a4035e60aac0cce1b8f7856620e687204c7219f1e2ac5ff2c5039588a2a394f0614db270339f1340158cc77c9ddee011ad72ccf1d03192478b889822d754dede006f2b61081afd6ba70b69f2496bb7f84d72e31bcf85a355936c8f3c2362a1f98eb423d737963511405a6345642c98dce3725331e91f105cf135ba09505b6a66248ed92d795ff11e18663cb1ad9ba52f89386001865a2dfc986db1399e2818e549afcf3831b035e3fec50b06dcee563bb572f85d5ebc903b7817a54e09b99fe2b51e193abe9328942a69bed7dfc685a95372a3c04b98eadd31dea5c4671523453858beefa5e1f09e8a98df084821d7570a3d5666899210f98af8e9ced447d92173f779f372b185dc7d8d557882a9e9c7357162540d41a2525678ca3ba2e6e13a13ca775fe5feeee540a46309dc0ed9df263c5a00f4f4799bcd3cab6075ee11fd04b6bcb748943900403d56f9ec7bba890b228c998b4b47164fc13ca4c4287e895a4ca550364184ed7a579d9ecc483e1ac4729660334e318006b7b5ee7f5f372c03231abd54be006c4282d09e48983518a672fc559a4d3aca04d62ce460da44840dd286fd00e4a1d7984761a52e62fd8b6d37d00663b346fb27fc0e9371b2329bc557a62eccc1f9e22ece4b8d61b3647bfe5353788623071168dd84a9544a1e3ea7e697d7ca3025939f693be73213b6a850722e90249825258fcf3686feca3fed79192444b8fa22660abdb1db68e7187d1272381231fc76d173282f031d1445785faa986aa3daa66538ef32ff257de3af1001cd8afc0b115af93586dcb64064669244f8f10cdbd88d07f8788946d85fba1172151cd4aa00b6c60083a732e315ac359d55a9eb274729ebb9ceedce2f9e89250b2fe5e1c7ba4ac475b1b3a2e14f1b610176e0d034f11e834aacb92110d02c2d070d68c0add3aefa37c2eb756b4324bd1ca9e872de81d3bb3901a56294de329e72be054fca0220c0e61eae4e84f2df08e9af9406ee8d773e4bfbe3cd26270af764083b59dadf306b6d82c24302bddc5103d5d0cbe3040ab7c05ba57175c1daa22a93a4103b1840f1df19dc5ebe290161955a1a630f394999fecf0f2dcb5ad1390fbe884d0dd41ce0bab2b6dfad079bf03269423dddf2df19118c670ffae62d37728f3807a77f1b07322d81eed9799702ef07554857b297cc4dbafb967fd4673372bbe91e75620305032b8b8e096e3d6bc8269f022e85de0a35de207f6a9c56a1585552f6469d4b69607b2ea9e023aad2884dbd3d5fd519acea0fba425e29bd56566f04d481cf304dca8c44f5256f8973bae764a6f0d872f447f7a2c762464cb34d26bb14f325f4656b339cda54313e1338386c69bfd0a96d37d1d880b5a86286721b9dac81f05ac31f42412c8557bcc67482bcaeac3a57fc62a15a228309505f7265aecfc7a9a8cdf25cc84ac23cde818d51fafba28bc79ad20107f2941e2cd7720461767de57a78fcfd7e837d00fbaf3c58ac4d6f9a1a31873531eac36e67b226abe4dd847064181502df6f84e527907f6df9beb841880223b02998703402e772b8396a0d24d2c8a2ec6e7314006d0eb9bff187b4f81fc3994d3d30356721656f790684213b27f46137a27bda15c9508fcc2860873a13ad54a396c8ef9db19972a0db80e240bf4a421c783257be48b3e3bf96f446c4d6c7feb748c58fb974b5727927e37559f117a4349e1a959034b530f1991c8e67c3fa6482b4e744c7b2c918e907c70d8d434a0062a54d391a9e4182c28cfa3a3468f6a1634d057ea5660572b58b65d51b874d15e44d8e446e17e5f25cac745ac2365b8d756da63fb3184d07694a0811103a8b0df21b44a5038726c7bb090120330607142c3e38a4eb909e328c578bde466aea3c3d606fbe8161c84f61ff8eb5ed8f08ca31e52f11b25c6f401a60d11927b723bc546477c328aacde6ad19b37ef00020388dbbc30609eb1f7292e20f48179da37668ccd54a316f215e391d9576e2d8b225ccaa9d8d1f10ac49830c3e8a59c4c78bd8749b41494b837f964386ee281ece9ef6b596c5e02a2d3b636f3d367e0c76640c252cf4ebc6fe33fa4439954823ffb1a027b56a80d74e185eda3689f1dfc613273be8e5229d9ab55564665039d66b7573e6cbace8d5e672ef0813b849c67a9411925f4433170b66c4696783a03a0e240c4c9fac51d5686198a45cc58eeaf4abeb707ae1d47375f3d1be1be6c0818538abad04e01437fa721c29355f9069f5321a4c43ce74ef8a5b275b305698a475d00a4ad956a3c73a72b24048a6b5baac1cb74bc7786e065b708c16c310e580ce4952103c452c0c930880359dccf50639f87be5132139f863eb3f2c46d4eb51f10c7a64af2e17391f34fac8e328ec4c60f8af7f4ba5b1d823ca50fd30a767321a5a70c83c90ba122e72d998e6f4c920a9236cc39a427fb65897ae19a7946b16edea1c03f7b511b5de72bbf86d388ebfdc44b39586b080f157122967f10065e3d15f48a31a0821708972ebb8d8c75fdf04f87cfade1da1be92cb203aa93a92cba546f79693632026731dcc9d6e670e5863ee07086e1507907ab59f0655d0edce791d107cf30cd9651f72f4834e2fb6384b75158188f36fce87488b034aa8a29f2962356a1296b18a6e724a7e3f1ddd27bda9c757df4300e39e2c84e6171ad34bb3f9245b8b80533fa070a74a385fe712283ac1ad27754080f49166ab9988c8a0b6ec7cd51768ab005e72db1227e608c618979d882ce2d8cb593b95f835084abbe616cee3fd8ca3cd080af94ee9a19d5edd88c3e91188a5cc74cc5be363b4322b2d657fabfb4b30646b723bc401cae0e573a387f09460b42d27d6eb24c63dd7df4d35f197e6ff1f358d6a63527f867f5e61512329cfccef187f41df1bb6f93d2137efcf345147c0d8ed233d80880832f0a670a71ded61692b94a8fbd208bc0ed5fe65a8b3dcd23ab03c7243af510a76e550f36ba5619dc47bcd1ee5de044873cf98cac2e28c5490a0d50a5b2932231b2274ed1524c07ca829f0fb5410c3cdb80f715f54fdbc775f77d120d4434b166f7a04b0f712bbce75b4d34cf52a5c31801890ea7f828f15d9481472235ecb54ae72a7ba2f0ad332c82403140f6b9ddc37c4de6ad0e18df2c5215d72069d8b4cf9940f7082875fca0aad3f25a6adcce5211b9ab56260f5b646c17938d3228ae24118b763766c8c384f483cf557392f04db2457a49c681ca58656a953c80a23c61de8f69322bc4f7567877a4e31b785bcd5cfff8ae01b4097629e3872c56e331b1e6fc994e536e75e16fe6adab86b1a8dbc15cc98098b80e6c977bd72a61e903adb98b7d7b87d93fbd1acb1bbcf9691803ae8b080afaf09222425ad729b0f8debe8477a9b255169a7ab8960cdc4eebd0734565c40fc29e831dadffe5c0de378ba8e4391b8092f82be98a663c1ee58e5b31ca6818d71c54ceb4b91e0727f29c9d8a05e99b96f59ac368b11d24a7aa12ac83e7aed1f87c80a250cbb1f723dd77846e39b758e0ac7f4bce4a75aed4096d0e6981084247f0b645e33b0217227f3f21dfe2853de7cd97604d80c1fbface7aed14585e2619ffc080dd14c8572ac98e9b7ebe097fecd4e0911391146f9d7e574267a3607ccc6e80b85e0c6ad727aec491b3d0b887fa14dab5559fc29873b79f87ab6a852b5139d618b9085f0246537aa9da01b148a92922e608d4e0aeae3f8a27b10d3e063fd7429ba2f97743bb739dc7ccd8f2b6aec734b2deaa203318322f7466b3823eb43d28da9fe50be40f7ea579303302078ff215d0d494246831dd646ffcc5a1af34e9843bba05734728560f1f60f0291ecc722ba6da321653a588c25cc8b83d5ec819e86d2b91efc72e34cc64c9e8d1a291e2fd94716cbaa631ff82310c9d2943b13c79a82447a1672999b4a4e6f12db999f4419587df75faaa5088cf842f48b778ce192e92f60985149d9bb61b023faec8a6583a2319a7336b7eea9ea783e3ace27d9a374adef8a723129a5c1995528af58f2dc9ca3c101649d8147bb8c52fb7aa9d2f3a074434b42184c7602fb01571675f5bf598392237b7ce181c95666f58c69e20bb263df1372b3fd6af0cf4a2dfdd17f09f17d293a4ea1f133d54b0837e610640acfda96f2727c1d17330433a3dce1568575b0dfd815fc28660fa25595da2b742393691dca361bcbf7f8aaccef407a5d9797acc7bb725714d43db2a5da90f776f7b1d15203720cfb5725c0ca55fa66dd285c083067d785fb891d21aa6df934c8e5469b67e472ee6b18598a8e95dde8a3c2f132328d6edf89c8da4e50ee6ef21a01e02acb137234af39628d7c670b85cfbd993b958314ffabaae5b35956fe765e3b2772dc5e723be31b8ec3d97a2de9d3b812ee1fae44889288a481e69210fa06b38359176d72019013749983f528544608cda480533911b86bc4e3e9e5430abc9b9a03b19272178bb99999191a3d2664cbe1efaebacfc95ed1f20f7ed188d33570e03e7fbf728d04f745f685d35b6b197c5b716062c828c8768b4a6e84b04d294deb3c7ae772b1b1bdbc3c14aebc893688b9ec23249cc9d20894eace7f0b6efa9b778e087172aa83e6027618f115aa3c61efcbf452cba0f0e858828489935b8cc7833ad39f18d5832f86a735526815cf6448ae1135809dc24b48ba825560ac4600912fff35725146cac6cc38b76e8972688a19a8758c5146d7af0f7cdf5e3bb5581949a412191fb40875fa2130e611fd422c166ef313023de7d06fff16d2b349e6a19b9507728af27a2c682369dddff93e70cc21f8167984f49520c17b751dc76c9a4ed8bb56cecaa11ed7e1de374ac96723890d8d18ccbc946e788de7bc50bfa0e23b5e3b72a8e77dad46659bd393525b3df0eeb15cf9aaa377c242034ed9e65fdb590c4c2a5f418e797d16ac5e717cc9e210060f6a2c77df584a0459c1435771cb8eb7ba72fe0ca7b68398d6821b9054d022f782757531f18163f4b99ef6a1e1e2deb41372bbe2639e66cec4ba97d3d6d9a31c77f01973fd51ee350284de5646f747a22272f14c47062bbdb9c3ede364161127155017b92eb2d06bb0f4fb78fd7ad232d7723fa02fb5430251058f8f3744537aee6a4abfa079652b28ef8139a545b4f0a7108f75c8207886019e3f35d25752df9403554cd07c8e74fe73e9eca5229fffc8724b46912db0834ac9f0cd3ff832f5492a856e4c0d2a6b9e4ebd0188ffd42539721bb4f66978f94c7d88b76b5d2eb8bd70b95df746b7a0c14d1a97beaed27ec7726c6fad8a51ba72f42328eaa28a2cff8f5ea26791b89a678baeed221adb975a72f634275fb785ad62cc2a909e54559862594d8f7d8589760d16019f8fe0e3ba72cc5b2044c0ab300b488978267718fd00efa9a07f2d55e2acf7d16a35e3eb126fcda322377cc355495cc755266bc251ab3ee51d12269699d83c8bd3cb9c98367283326e1fdf561be02c069309d50efcff931198d2080ed765667025f44b8f89724a2bfa432f2e0b8babc4a4ff007b53e1637fa8ca0bcc32aca639d2bdc49a7a726f5ea9551ccf76bbaeba44812c103da62c1fb2be28b50f0e2667a32c586ad2726822b6b3ecb89618771bcc69b1e74e3d28f341402c8766ee6a9dad17e52b5d53f442607eec2afe34a8afd0e5b53d0904f2f238958a942392889e65ce7ddaf162f1b760e7f741e8fdc3f5ec848a0b2b863978faa002ab0b38f9fd056311f0f272741cc7dde076fb39ec40229765602c4e8826433dc693f647ccc0ef19d784a93136fa2209d170080dfd287cfb4bf887edcefa7406e1c64d9ab5db2dfbf2a49506ab6a8e895e2569260582a2c249a01e9bd7fcea1693bf3469ebd37f1197d89c2aee025af1603ee0089fc64b6e1b31125a54a138c2eba0414b975ef6878a7c3772c627d025dd31f574d63c19b32303dc2c50e83050bd45d91878db639d1af455605d8ad8339f8f43b5ccfac29ba10d61889813b9d8193d76caedb86eeaf354d072ddf89bf498f22d763765ba21f58a9ea19d3637a9bf600c7aaaab4d999c3c8066aaef9f36281cbcd4b174f09ce7343634398aff0508f813368fcab682306456723fa85954836c95bcc30e16815cfedda544ab5468c46bc95638c6028c5ff887194b1b1c9483fb7ab9a2e71b77b8220b8a97978571531cd6894ef9dfb6fb4baf50c8d1bae43d157119f65fc3223c09a632eb46254d6ab1ec20d648ef93b444fc389405bec7567c2a1ae12ff19fb70b17302aea09ad8e4b3313a098a29f04228d64a04f701ed78da4b69ac5de09a671d0c74488f6e5a41274ff446dffc1ae87b272be34465c7a6bc57553c4a54183afba84eede63b965c7636d709ba218743aa0723b2c25241f1d112fcf01e6ddb99df013502675ee592ad0d3c8d410deb9493245ea2d770e5466e2c7fdac0f11b556b8a4e4e71758fd2fd36d03577fa12d290972f06163028aa7b1b75940407bfd5914a291b0d065a6e15ff0c41312b8c57dfc5607aad950cb5d8b60d214c7528fabeda103c488952b53fe28ad2b72a5c8b04372f58e9806034f4fb57425e7554242b4f9cce306ff4d2ba6fa3ba99aee672a8d725e2bec14cb53a627150d1d4ef646e198cbd9829b031b9ef094e2fc232c6e8172c5b96d92a9e2bca82dc59d4b1f4c734bef69bc04b9eaea4269e51fb5e074fa30b8bba7b8e1c9408ab440d91a39fa7285415a8d936d04ae6a6a3f09ad38d6ee701d8e97553352fd24a0c54b462175df5fc443b7dc1ef1cdb3a1b2d48d93227172c284a8404b380feb1659d4018fcb20f378264a914e207491a448cd6cf480603d8c84f79964b181c2b4cbbee941af34bf75c7183983d0e665df0d95f2e07767003456c17b130f1165420078aa20a8a7a299be11c3141e574247608e5c176b5a1c92fa26a43245876798dab2a4fa43a1347562e02d04f8dbc896b5f7fee5b52672a6d4c2c1f5ee1c0b63ff8219610638fe61d24385780fbf45c7b17e184e993128b6ea34f11a5e98532870bfcdd0ade63597e7ac35f83ccfbeee650dbc981cc84fa561a528d6777b0e64f784b151e82993096e79ddb76dd148413980539c7b89721364033d9510dbde6b6ef72f79b13a1f82b674c6a70d81e06a167a0c9426c872c38e7d223cfa5fdf2e76011544de32d9249bb7bcc78b84f8f7619dae100d0872d41f1c9f9cfde4d0954f1fca4a548b7a5b7426f292947bf8ce162b8f2f0179726e2baf715f23b931090ed857461478929bab0ec7193b0a136dd67e3962c2531c88b4fb274bd8cd8ed07722a133fcabb537d8c7361410fa8ff9b360829ff8841b4bf2c73543e88369a1398a8c9bc8b39dbe1dc8ac9e71c12a0a0b649a21a77572800056d995f79641f83e41ac83e8e5f37cde4bc748123eaf3be6cd68e0740472e2f814d2f0ed5e9e89c02aca687efe22d8cf251a9bcc13f04b3e4b4fbf2c86411f9ddf8d89468ba55ca9ed629e519f2ff068f6678cc6c93286ee5f0e5f7100428849d3463855baae8a499939865190cbb0256ab1892bade5e5fc3c5710a6a172bfd5c3b4f742576245497801a2040b527ee21450b520383b3b3e6ee48ea9407292bad6760180cb9276e6b782af48fbda0421689bf2d1b762ddc22f9daf4f7e72e11d97651661071757e97fea546038b3899530e08524c7ee87309b0f0dee9c042a3426cfcd001d28997e9dbfe5902903865a825c3125a0d44f4a7704a3d25f15a507e2efdccd78b2ec3256292a854fa8f053f397a039d4624a9ae6470b1c7272eb7374adff3b7c4c4c2d5eec548f30e98c15408ee184ff9f2f065400c5bb0372eb6302b70ae66b1286f6ebe9e42650657c4482e9f9afacf83ec178f0507d4c5d8e44b70c46a46c92ac1b1c31de8b778d041da83bfee719e2433f601a955bd67261e3a0b598325a74030b259df87925db2f5d15466f915d30c4d0e6f5bdab4272fd8fd630d58a7c9f19112ddd3956428748827f445807af2e9412766d14642072e891369add0a665a42119c7fb5bbc0e0e02d1399a517494c682f66c7a00b85727828985f80e29e819e1c729f8f4d0961091c438ed49cd196e150e9b69491011092925c34de5f8afd9ffcc4eb228a45041f15e28fe2fa22dce54b610eead281727a5ab417f314fb36590f663e14fab5b432002cfba286b7d5810db17f9cd7f872773653b26d2426a2c51f2410c329bfef9438366e7e02b81c8576f3cf945a1d449153fc05c6db6290fab5182801600b4167cdf720e8d51b052b1df6563aeb6672d1444ed00fcc7a6af45e5b37cf53fac3559a4512778f0975ab5888e4ac875531eaa9965e097a5156211f84bf76ad8aebe53b8b5dafc138ca6060a1e090a1c05b0ed275ededf05a65d4dff0b7f669f33a4666737f3df21de3d21bebc975b86e72698349d961e25390ef336aa6321a2aefbdcd1a24efdfcaf72d3521f8715d015eb07c4ee27d04865008b3f13996d8e50bca314b4c979efb2f3506df02310a0b50c97ed502eeca4d9768c96c00d7df8a4bac972d2ad976a4041088cea9f07c2c726567d9c74b3c02622ecfe3d6919489af814f15b4d35f505b22b186607f0c4535a4ef275437d9dfee7b3e758f786837b54133af93f6f8ff812dc7471bb5e125660c60403f7cb519e7c0a44cd290b3a76398d994c61dde710f9540a143ffef31725e2c19ab5dbe0abf25f492bb7adedd81e472e6edf9511ee1c64e9a4845ed70723087ab1a85d4f23a9346079ad4a141465845c2f466244d439e0e0fc179dbf07213d36d8e8e0d768df34bdf03848e69be35ec9fe6bce0d0e6feee1158d15c3572b62fcdb30dc5863624802d34466802365b9b87bd66471f87c3dbe90110012b7253f7d05c0cf9d5b42f7a5e2c1e23f0b4f7b182d9d0caa327626301459997a972af8e0f8a0f3d12ba5ea74d6b4ddb048543fa289061ee5224ae16c5f7996ff4612202aabc937ba8c6425700358bbba73e577797782bb413094db6f4406d50627276cce8f7211dca4092bc1d29060715b7fbba122748e6c4653ce79c54e759820cf044bf21d8548af8d1332b53cadf9442f691f56d1c893ed772192fae3e6771723145ba2ec6bfb9ab77ba99180401b11f36b83ab27da6354c32a93ead3f2cb772491a4f80f813aacd8e8e1736911ba6d7c278e86befe70fc08265bad4e376b0728a02ef75035b914ce260d06a3141e2a3195e555932e9eeea4ebe350947fed3729d596ba4452b4dda480ca068d5965cce9bf593c71b680c62e7a73da37348f853bb9fb6971d0d67d4f44750ee007baf49023cc00510b0f8e932d6bc4c3a6f0272369cac68ed92cdab4221a7d3ffb7d22dac585e3372faaced91cf56faaeefa9724da2912e2dce8fb44d03457e37685636e2a5846b2b01fc21e80ebd5e0a75f228b352b7bea4a8151ccdf5e7d3a3dcf279bcad1fc073e0d57cb3f9136cd03ea5725494c7cc3bf034291acd04faca7ff6929846b101a2d1b7bb31346906aa22fa72d4a199022104d2a0669e9e435b3b63b6dde323233fb7c6a19a44045574a61e4125dc495c03f97eeb1f6d276365f18925df0f29aa41a9d559dafb725ff39eee308ce2a02daaaefe566304cc07ee60a3522627cbfc82af3bdbeaa2477c0a1bf872f22fa651d2466aad5f736bfc85104c9831c34c43d8a4f0bcb104754e7d711127667a60f180c31209363ce8afaf81c461632a955efdc637aa6624c969a7176072f771a46e90a45bc091889ea03c63b93d5f845b6d6293f2e1fa86a10fc92f3a401c94ee0e3762eba657dc458b7d5f7f34f040304d3df9890cf5d1d806094a297255ba7efe0c6a9961ec82762e0fdd634dc9c64afffb66351b941c723def4710641cb52d5810b7b256248a5e20f95ad18fa57155bd0b15ea007e9342b0b3711b72330dafab5ea5eb65e94b1f51b5f6d02957a8213e2033cf4d3ca056eeaf57d50b4b5afea141e0ccd1403908368c16fae3906f0b3937bcec122d2cc5919f008a66d8d6edc3c2657e15707a949c5465aabf6e4adcf0b642de7c917cb26f626b2a6ef111173b8ab83823bea29e36f56dbbf9f95a8e3763da7476f0ed7f4619f6c3722536b5d726ed2c29a753a1620816907f26055e694a7254c7a673985ab5d0233478130eb4330353a24e1b9d427a7857fd378df27798e9725d0ed8ae68977fa364b609d1009ed7c5e8637bb601ec3015eb4211fff1ff49248b967a6902b7da215550ec1709de3a1b0afb434695c63727d8d6f516a8911bbedb31c6e74d9c214b1e0881a2ca1c58a4d44a33a640fb009fe3a38149e1dfad49af74e77674001716712292f89dd643ac1cc8af48b2dc078d7b00008cab17936fd39605bf5ba0b0a3432fadc049c63a39309414eb966ecacc34c30484eda424577b85265d1958f0617248d6424a0b00accb8089200cec1c5c0ccfe323835cac36a3ee38baf01e1c6c1aad4b34de04096c5edd50c937f1083a83197cb119663dda00820d28692e20ac082dc5fe9f84600e3f56ea003a39c6b2ba84dbf462046f7e724b553cb71e4510728a2bf9964a05dcb469a805bc4f942fc1f16ff5a28fbde81917358e6a5743733e208951cc742deeeb524dfc1ce671b87d59e3307ad7d0221ef232b8a3e03cdb720169a6b9789a62a8d20d33c1911402d6c730e98d78b502460f8968b0f7bff8720241f3046320d017f78f562ce4a8381b7e5d598cc3d7d7667cf8758cd3c1807243ab85cc2deebe784666d20378275453465ca22d2ee1ae573b20d000fa29cb63dec92196276c519dcb81222fec7be36dd8df35cb8fb71f9bddc7587a1a7dc2721025365710eb22767690de8f15a3d404c1638ee6b65ac6475887f39cd3991772f35b776ad15716eb37ab43f2d2bdd80a0393416b4e1e64650900b1355207d072e956cc918141372c0222b752010cb2da7e768abba2b69d20a34878eae3ad541df00dd2d4048b3623d9d050c1c7bb3f88e76fd06eca534308f37f39dda75f1837440c3231326d478bb6df206f5263a9f91b3db45f471cb2be96297533bbac2372c645ba8d804dbb685bc04ca2868970bdfd120fb066f00c509038cb6408748072b3ea694e70a6679c50293f53d7a9f217278f158226a8fa18b32fec3759e56672ea2d4e665f89f52bf27ce19b527a13fc6388ebef8250f584666b6085bbb60672b69af1349bebf7b6bcb6baa1b4239bd6fd91ca637531f476f2bf4409708346728aa3423abd3f1a9aa778eaedacbe9efffe05164245b70728e504ed12e14a64718da25bb4d34ab0119c0f5a5731a1a6b5bbd5f20af78bf578240c25aa413e7f725972f70f7787ed1cc9e5e9a5f1df3414a9f67dc8727a7187354e9826388d7e5eb5c068942e65a83d3a59adfe629e3f72f89f2df4f49a6c5738cdcbc90fa3ad24fe6e0b1ac3f359e1b7bfff452aedd56b607d26a53c033a73deb982ad3569f472e9feffcce6939bf4de8927e641e27e0eed9e7a70517ae39f9b4f67e1bf531c599376b07dcaad4f9f5e1dac52f426f63a90bc392f73ad5243ae1601d86d7fa4729651e514ba3fce02cacaa8a4db96a207eb7f3cc115e250ef0e28bbb6aad6eb72048366fcedfcb457f6fb9d82f1e8bac18374b010798cc5ebee054cf4a90fd61947889f0df01aee8117d4f22c4fb4d592319ce0c0fa316ee1929556c52081ae72cc0592df7a27b94f9a7f09edea5c27524bd51a4dfc5b94fff97d5b78e7e88408e2ad80170d8d8adcde86c4ebfb04e36ef871380ec4ffc81eb9d89df34d90213b9a138de230904a04d1313bea3e5c4f6597cfbb80fe92134ff2626fb91e776a725e05399ac2cd2499f468b2d51decbe6ffdddf33b614a7a6ec3a9fa295fe67b721248584ef676b96c78e940710b0e437714742e9b447ab860ba97ee95c53b42584ea4b448dc3dfc5236eb1cbb02ca700d32bb2790b7a5ca668797582e8a10ca3e2b02f96827d7daf412e51e828251355274a54810e1c50f5819daee94af2f571039424fd4154c2214e65fa3ae33e7e9a231e83e43101eb97faff6e8a92434193cdd18c275285dcce82265dc4085189815b7f3e945416ff7a9d3947099bfde70723047464a85b96e171c4838cc5f7b1f4daa50b8c722023b6c3e17af840e017e725a44b451b2c7b7a3759c917df4ae5ad8c68c1f7ba334ff8bcba58c4993f1493484e1769ca2b1a452641d5b96f60052e540e702463c436a3f79eb200e2285221b96ccabd72d11a0d0fa59353026294e46f011e725888eb14c38f345716a148272a0b88f196fec49209f54a716e0821c57c9084003655b85faca378b949ad99c72747a30834ea81e15772e2d7572f54439996a10ea01515e78379660ef037e8172a907af10d6ac33980d4c8699aa6a81ccc42b0b6d64fe263d0e688a2e0baec7724bcd999f0955c0a73497d42d5fae5af493e79809e2ed9a851ab5a5a6ace5c272981cc68193dcca3ba7fea9a4ae655e56f7543c931d388c23e01cec981d791d7056cea8a97901fe3f46199253a64d6f0de5c28b5168c321e9c34a6ecc86356c72a36d39003887215dabc9bb06a0c62ce267e2396f8191bd145ef3e2aca609c11486e7b210212bc377d64c1f66e30beb2b8ef9c3b8a726855906250ddf88424b7272ae1a2738835272c28233f7eaf35aadd77a7b14e903eab23b606a82ea62d672065bb434bb69caa00e9a16609ee1748a7c83256221417bc7f6be48524ab2b4088af328f01917395b0c450d94714057e7fae73d29b56dc63dcde0d724462dfb72d747727c5f10a04f8cfdbbd6fef2e8e72392fe1ae737f7fab56fa8c19ace197296695aba67d06f13c9d3672e61236f569c8c7fe64f5aac52bfdcb47f8d9e8e72198037dce52e6c917d9b4e958434717463c56f9bce6333a8309015c894cffc72d8ac3745057ef7ce1b3233a70b6068c763efdbf59df019e0e08bb0a182a18136efaef7a302adb2a7fd48e86b0c2c2d6e229cfae56a5df41780f9ee8f5634e572cb0818a7d7227b378e1541b3a86d3fc3397392ca9ffbea808c8875f05181e6350fb03c9204f5ea4a87d8fc76b4ef63b7e93775955b2be1ff2f03d201e3e50e445f226a47b132b19cf8e28324465a20d24a2a44828ae801c00040675c4dbcee1931cf78ee96499085bd26afc5d7fb1fdf2b602a611789dbc505c42e48f3ed163b5842e74a1d0abf2b4d3cdb8f73bd9d09522528800ed0fb4c4dd564d008df0100d3f83687bff4e40d36bdebae643a7ea5647423135f88fd95800f297e627fea30f9927eef0142265a3b6396b43fde843ba02533ccb0aa6c0d45289b98fd7af972be5f0d8e20ea28859fe3ffd9194aabcece237919f78ef4353f468a63b743235b57f6c1affe7d27a7c1eadd9e71e374493c699e4e3f99230b02e89575bc3f7372351946094c03ea0f5a5f4ca1b29bf26156949a242cf481c3ad6a2abcc41b9872abecedc37e5c96146eae997319c22359aa2dbb3ecc89618be35914925540e772f3f07b901a741f93b0d8c38996e980eaa085dea60bc86f7349e209f4d569196b942ae47b5c429d56cffd30657aca1419d9d84ec88d7d44001cb868e5559af872407d1baefda6c9d303de8b1e9372a78207cf94dcffbe8d6e93b059a0e174a9729c9f7a43b79dc690e429a2bda06e8330e6a470be1b19300aa03ba20f23a374594fbc40d59a57248ab201941c0625d56afcee6cef13744fe46548580ce5849908870ebbca787f2a7e0342008a6f8fa1772ee63433c67074e3f6689fd11f031f5eca5a4a7427cf6db547146b3e24b0bc91490d47680549823ab143d74d5d1594621f9623be8f1777eb42af4021665ff4a4a564c788ec624e3cffe2e4700683d17286543f74f57b740235f0c5e832f7d55108393876d66e4ac4e6f9303abef89c389b85f5b7844d6116c2560a83084470ec3788e47d8afb06a78d65bef1b9031b59436567e24fa444f74da0f987f8a78de28a08cb1da3c8d387fed1f40786f69621b5570dff7649a07a5ab88636b172b9bd26cdc129e9d2e43d0f605276465c534b8e06c7867c6b935bc007fa799cf3bfe234f06e7cf6e640d24bb0a34298f6044681b363234d80d15b8a63ead8a6bcdb1e292de9ddcd3cd226300a5e74fbc85472854586512a19c81d59f712eead8439bf6716eba6428a63ac9418c6d41313e372b4a7b3efee65110229436e75b792c4370bf63a5a4a8955fbba739221f6e9f572f49c72127c2247e5c88cbbff5cae30715937f6a4d69d7480d3dd730e4d9862725ece2c1ed57a1e54ee841fab0f069ae92bab5efbec1aed83a318763a2bafc804f12671a4a277e62f6b973f204ea6c10155674aace10da377e228e779df08c85ab438cbc282fd7ccc7bbe3727689dac51ca0237faf1e3b396e72aeda740d44872b1132581afc481dd1603b4a50b916c46ac09bb8ea905296feb126c9f2fc63f7295a46a6c12df8a6bc3f143fc1e9a0fc1bf3b01d61c89fdd32fbbe6b3805b3472ee24956e5b8514528978c16d561afae852876b7651b3b5f74d52f91aa6574872aca3bd4a737929894a81fd0a17a3b875d0d19f0d4f21c0c11676cd29f7e34972eb741ce5542eff6ae6efa917687797cd08a4a96cdf32380131ce541b233c232a52748ecc6f463435300fd889f80712eede01335a174d7f0e4e24e5cc68e39d72fa7da98bb5974babf387cc4cbac162e14165e79c8c873ee9a72ec830cb759b67868d66dd65bb366e14f364d5abeefad707f84aee6e704d75d29963754b0ddc7253883105d5a1e22623e0f61eab5ecc5389c55f8dc6e72b56c363a6f68d6f4872df2e87f0de126e52e73d4307c46a047b627570def46d6d22ede72e371b87dd72b19023c6b4474f087b3394cd3b15f685337b54dcd269fae907157f6ba50be9103a569e9464a08c1e76c7ab1f6b487b9a2d8b31ba67286fc161b5278bb93a9b0d6af473fb5d48cd88d960b6105987e17cc7d8d01995a7e11d2f437cfb4a47a572bfb22553b25066ed6193a89c99c15127fd34ed60217ba700b6d11242d6b46c701a9335132199eb514f49faf6ccc372ca495c7aa0a41fac56bf55133cb93ed47269afb31b829b368a7a9cc7c9a374bdf46ea68955e5da5533d01edf028a0ce25c1cc389e8162cc13000e61441f8a602dfbe4ae9eca9e3882cf8bd6e6c82d6e93cad6dafcc5bfd18b389a475884cb5cf0d48521458d0d0a90078f0b01ee51d207257c1c5222263ef6b402122801b061a03e7a59c0c7de15612f65d41499514122faa196aa93531833d49c4c1b06613d609c95a8e2e920588129a568b2a8e1eab66b703aad7ddc7dad581c334804650396e5a7619ce463862f8c208dc89c48e3f4e80354e0af5939ca9e6784b4f579f0f071c62a5f84d41c28b4eb789fd2ba11c512276c459031b610851b4ea6828ad511b0005652f0d04283c18269c0ad73fad45f033e484b3b05eff0f3f788ba8bb2e2790cf4de58b6d0c090f774c0b3431b534bd3cf4a8b1e391021382959909a6120c7209188ae72c303e2a127adb6117853864fca975ba211819a63b879592486210377b75c1b89decef8c26673e24c4875138208b5d16437d4abd187828b3e86b057068980db349b450a001f4ac5b005125edd5554e5b8fc786d5260138d3abd7142049d60329fbc29717e667df857b3d59949708a0198534de6c2fc853e28d215ebd8a773af901e101a71e8b046cabfc723bbb912bef2499e8b7b12f37b0517f90cd0aa978884a934a1df9b87479f89372faadd17bcd0f6efb99ab54c1006e5a004ad9c853b55bdb5fd0f3efb1cedde972edb97fb598aac7ab7b26592652085212d9f9484416bac47ff612c9c7902ef628c1d77311326ab468a8fcb47377af64a3dda243a8cc514e774f3b46c4193455721e26fba0f3199499fc0c4a10086220fb30a1bfc6f494846042f0e741a001e4723d6a2d8ff8f59e9baacd4e6c10e13318cd543e961eac76b79f29ebc68f4ebd72caaa6867eaf979eeabc25b53af68f34ec4e82e51579deedd68bcf3c473743972dbe819da3134abd2f2616dee5731d146f9971ff2a6f5efa3475a5bb46e50851d77d203605ae4ce35dac44467435c061981e507a1bae57731f0f70a183547852443283c195f57feeb4b9ec717f87853ca6edb7ed41926c868bf15f1519c4ccc725dd6bee4dad18da9a2887ff6916ac52c121dbfe8ce534d4df572ea7677ec27111c86153b82aba99ccb8c7d7453473ebd47c4729d42b76ee71f0dbaeb2aebef72fd03f5adf7643f8cd271c97df0981f1bb79ccac189202f8d4cd8c5cad48c86638dbb9f4b72a2c956771d98dd1810867c4dcfc0ef8570e7608dc1c147861a5e72b3b1cf0df80fa54873fe68ecd0f695a419a173d7c650dd08fd09ea5e85a6e57298b389d4de5c0bef32e5cd76cc78bdcfad4b2ee0ec06c44715a4986da2de7772bc24d4fde25fcd7f55d3c3f6ca7003c8799b1d404e8f14922b9d6e4efd6ebe7285cacbb2601ecfe5c1fa57f702d411546b96c611b4e0b0878a3793ce89634e31f82fde870af9827df90f9f6e1f14298043a42a15d2ecfc4a3f30ee616e6ab85cc46cd089c15cab479c03ab2d2f0c5ca52aeadaa514d8df1ce9d0159462b36e7237233ae9a8d5ac8aa5843f1ed673aa39c3adff6199072594f38685a144336709f1b7653af0e308b6a0f56fef2ba10cf29dc7f7272e0658da663608dd3c233472bb9f86dacc6442d8324b664bce37352522e86d4fd08e246aab1dc2f3ff58241e8d5fc4dd0d6dc7ce87fed80a9ee8d7f25c83eda59c1f684c20f00709ec60b0726f827f8ca6d5b82cc261fa27cd3764ee1ff7efed742077154ec21c9f7cecfd722e41ddb13dca9bb235517451cc67044ac215cadbffba26900e63b8e86216fe4c718b191a6411b2b0c532f3a8868f1ab4043adc21525e31b1ca4efa5eff7096726d3a6d1883b90cf818bc0637d727edbb000b8d659ab7dfe033fb0f5f7d773a72a39cd0ea7091aa669164c433cc92fc27ce0490ec1616a5e7d048add117bf3172f36b182b9dce720f8c54c9113a587f3a0af616d2848aa70d97b2bfbea0d65172f9c7cadddc4f8e3038fcdcbedc9165cb5487993f7f6688cc0efacdff11c5bf727ec4d17bf0c41ed4c954ca5764a966899a8b6b89a90ddff59a12216c3ce5027296a37412afc9b0f38e5d99592ac53659282e5078e83c88c3ee6ff35f27fd8e72efefc34c54ce94cf62ff25365dfe5ca80748009edd3117d94d7bea484a99075aa5b70356694dcda8af7d52a7800bf4301d105a3b08257c9d2387f5378c28345787807f7307129a101d3f0e5fdf3d1d13040ffe0f84bb946d88eac7b8a9176eba020000309a9481e459e8ea01c7a08ac3fe34bac4438189cd70104663963c4f42c6957f12f26ec480dd11c40e5a259e4295b8ae4e8f68bda329b334861d68159e684f1a7240b89e13cbc837929f2113611245e194950eb64e51410c7501d9db7db207aa40888ecb303572ffd271a4b00dc444ea5572fa93c3a16b0d7e8cf29980a03a6d7219b697dfa20e4e22add78acb270708e85be2ffa2f9dbda4a18e016866fb3c722147101877296389eb1c386c60fc2d040ee22f397945160d5806916884bfef172e6f682324473c9af2a4562096fbd4d6385b7094f53e1b472f783bbe2b2ef5013d5957981621c6f67f945f85ae60180c37b5d0213031464a2c11d6d6ed817ed72c19d7e9832ded94b805bcb86cc91d4d22a87a8598a3174e4f8da2cd9715cff722c40e904b2b7bdc51f6d93318b2fa65dc0741b17d8cb0a971f599a5eece0e15c63ba39626fb28ec7fd2a16b6e26352974d6762d4642aa1d177c1d60b62b37172019589494cebb1b6861b48d4e7bca40a251a36cbcaf122fb5d32c94cd61ccf72f2d1578be9a058b3dfe26e742c1849e9f3cbed78fe600d3760f5602af8e64772422f8dbf35edf37724f95e5b8b0300b3407fde915762b2bbc9ec256be3a45d7240bd654b4745fb17c7d6336011fda8065523b0c919ade597ad7b07487753f8721a9158b9625bfaaac31dd0f9ef0e7e62eb320be9edad8df74957abf169f797620bec01cdd1b1a92ed446a767336c5645387be96ac65a5b5532507633a9a3405c5e83b86427cae418b6835a1c8d6f7f08929da277be26319a27c70930e5393135b227910b252b3a1833c75cb348d0fccd09bb41429208ebe04550c0967f76f91371851ac6cb75d40b72f49a4552d18268a97fa3aaa9ee337d3e31c3263c8ea27277c2ca33a053e9f176dce385c70cbbe3883d6ab26dba285bf780461d6f954d72ac22ac6f06c7200731f9b1e3dd0dde057f49e552b69d3d27b7028b82ab0dd6298dfd85c8021b4f65123ccfb58fb08623bd2fd11972ff76e0e1349486b51e824d6293db1e4392526133a280855b9684ffdc434fb557b8a1f4c59a47082bfb71094896d044d32cc1acb3a9eb401e7768e50ef7675481fe83046bbaf8f4e30c066d9dd01892294e0aa6a1de093314ddc602bc1d2ee05d71bce2282afc01a5bc082352de9591abbc1b8e7ae730f62c637c423bbfeaf1771b2e290c72a8d2f2e2d437ed09fa5bff9eca3156bbdb2d28525ff8fb98789e64b4452f96b82a4d64a04648dcb5c9b1823d3bf08c937787ba0ac45a8b735f0b1d32185979a4c87216e3be1e717037fd21a8d74d987488e49eb7195a80abdd47365ccdd64da53326774cbb1986c0e5c546139edbcdd7c7e0b49f8b4f196b1263de6d7592227f86a06ca0e72f8dec220f7ea84e25bed9441209927c8592ded64f4bfa7f4c1b3d87b37ed88b7230461e26580acd15ad7adc394e106c4f351d0f7a308ac04aeb52c967684f1f4a1d8d46e7c081629445dd68e210e65d0c0e1f71aa65f9a230a7b0fc82102850722059e6222a38c3d1989441cf4a3b29e999278496dd0bcc5a957ceb8cd520cd727cc9d7b0eb238c8c32ba583c49b3e5106c2bffa11f681e8030fec392f43d8535c280db8deaa4a5060d246a910b84f09a5094a96b91f129d4399f4eb3940adc72504c92a559c8ea04e78a3567a4aa2882148034aee02eb288cf7bab0ead4ee1513687a7484fd512e25dd8f4a479bee91eb57a6f2d21f23cd39309da1d5f1d06724801557eba6aa88c8dc27c8c746838c01c1285c6327b735f89ef8f0f0f54f97207f1a9b62fdb48004b6724c27376195b71161b3fc2395f46a4f0401c55760d72f86c1b63469bf456c55853ade12f23c9bb4d61cf9365a6405f016c3a52688818ec65b6a2e9bf376ba8a9b1bc0f281578ceb73e7ec4442c6cb51f47f8b65a8e72864a78589ceafc577c348b55d8c84ab9be10946fb58b7445684d8259460a8e72a9818f211839c1de23eec3204a798d85ad4020c1b940aec5c69576fd5bec3e3a1d0d42b42313d3ffc7237a60ab5358d4b4108dfb03f1454c8545709d742c796649f6ec420b09764f89992694e1f12b3e64daf2fc45f53d93c853b55675dbb55df4676f90f8c5d8be0999e803070bbf5840c734259fdb4cdc9b3fa220f6c573724d8e4a75d8b8d5de36a66fde1da7f17b8de87aab5ed9923f8f2e5a23153abe7296487258b6061a0da6d0068effcb29e8d06c181b2fd2684eb29ace9d5939125d58c473d32209711ccdc42ab15c15e853fb0a1d993640ab9472795f7e91202b6ae272847f3f5a6f85c5f4d35a0febfe9b2b8407f601f8564263982d0135e8df4cfea3b3fc76d684ea3f80546310ed7d1b9bf1ed6d649d0b1af2f61fe2a375877278a7aad5e345e9ccd3be78e059fb369cea3b3bd89d4f1d968be53d874cf304403a841cb61daaa7723df137d5d0ef059b26ec55ca09856655846eba1689cfdb61438de1bdae6ae309487341b9ed24fd4e8b112663d7aa7a0738c4d1fc3452893197ee981971210595cafbeff92e5b94ef55ab11c045de59a3206c444fe0309036fa6907f5e883b213b55a44fcc204209af8048510189e59bede74f4af219cf9720a62cc01b61e0d330bc4ebc7770f4995c0a29a2de1d877825c9822052d7a1872222b58de35b77089fd85cf4f18baed1ac11580c6a7ca09749c5a62316abdaf72306d4dfe381364752000a92179da8b3e0cf4abc10f9b0095b21d208475cb0472e95810e6e6565dfb99bb5c6c822778d80ef8a989f825e7efbc176a314fbfd472dfd2adf4aaceb0eddf9ffccec5fe94b34a6caf0a51d51361fd4cde52ba599872201e2f1de9173d16fcd37aad9a8743cd2782116d0833d1ba59ddbdb344d16626cca5638044c6289916f6819bc0f413fe896b338bf88e4fb94127ba7ea3c0bf72dc09d7d906ec15eafd9d6e0acd57b4a4129c6783208e2c6bce506ee164fcca72497371e6cabe2cf69af1ce8644778884a53e9f41111d886a6d11f05640967c5966660330f426f0f877fd4e0147c68415df0cd148c2f2bb9fce728dbbd92ce2724bd95778ba160f0bd358d8292085b5f0a6b60419bd1dfb3db303553bfa90ba720b867c553629b3a8db64b8469d199a0f0638eeed69670b36351b5055e88ed9726b081e08b742a86209d99e0d352a5ce3cf06f79ed172e5a052329f45f97af309828d689fec019caa448962c01d06e4e35d5ca4c0d4bb0bc81047a84c59f1ba421bd7525f95bb234737b823dc8bd55d7509924f07ce8f126b7f2ecd456bf94372d44593e929fcaf15200f65ac603575d8a83296ad87274d146100d45ef1734572852d78adfd8e557257ad896d49b21054803bf4a48762b135c40c133c3153a97228ee74393d48b2184af2e6356c5ce393640e656f9a6de93a12f2b1568a96e5729afc0bf6379b278f9eb3a8cd091b3717626e90be7ac60a3dd4cca30a3fa3270bcf75c756e084aa6836536d80d8a2662b71207efbf98e55b158456ac45e60b82f81b133dc2c5c3d13e826f92a6274a3fae211f0949fb48fedb650ad427722f06f178162a058a6a1aff1a7e1e98fe8271a299489d2b1db3510fe3d28d0bc697d0a4a92814f9c5e6213464282834730dc0297581d7de155c21ada81acb61b2fbf72c24f6ad936f37c294a7ea020d68e6de12c90701f0c6e56139892ad177d41e072627eac3ca86ff452cc302546e17069a326ca9d8906f15d556689b58735377a58421815c3b17043b5f724491cd4908a6194106fcfb54a738e3527b68643c2c95b6e81fbbf2f5c3a825c606c5ee346ad4747e086b7c9c154eeb0f2d32335d01172d3bdab7dad7099732e10ca29f709da668b2593bac46906fc1183dccf13fdfc65fb8f112c7d9bf559b94caea87c8f529719194c0562047a71b8b65077a50d871f6b13f2e3bcf6d7745cd82b0d0ec8d3f44a006985a2fe7bbb61e72fac6392f8633c7c4f98dae665744ddfde8aac635a1f1e73b56b8d1209d8ffef7a9b36a5fc724a5da8f9f680575def0f501bae01b41127498ecbd39dd3bbf8ffbbb4afa3853dfd563af1497638f0eab99128afa3470a0b70c109378f53bbfeb3b02b0babb97207534c22a6091736c1afbc2873ac1297b53740a2c756d66f4f551a2257c51a72df7fc76c341a3d2f31ae7d0baaf937ca009632b9027caadfd7b4798b20c4fa72cd9de0b66c01d048ec8da4db75a125019527e7287d5c72f22d04f04e263ea226d3e6ca2b3afbf5a2d48e741e62e2599838375760e28d004c74a73743e8d4011fda2465ae909922b042fe2476ad87af9641aaa7b6ee6b6bbb07dfaa248737a164ebfdde5cafb564c323a921f0af0b61fca19ab2214ad7d59a26988f779427514656da252f330e0aad820cf4c038d7da99322e12a402ecd3b523b9b98b5a869872705a8a968ccce85d40d3252caa1137ee65e67f10833e4f34215b794f502a1772a6452399b57aaeeff788a770fe34c4df9c337d26579b5ae65cc975ea9a344a38f03c81a56af2ac983393a43ee0a2c13e23bb02ba99eba1a37b04533eab81f272dd225738e36ebc2534c9fadf335b7ee48bb4cd3ae097041e1390bae08a0fa051d9bda189a75303b15cb62efe6564247e7be68ddbe84f65c80b9c85b82c5a0e72cf552b528375ae532425bb11fd36086e453643d78ae8229ff8405ea86db19411bc89b0d0e6b5655f8ec34d1503268d30cc74c1e34848528b6b653b8b2a7d504c53003184da45bbdecea1e535de14d679c25a2740e8319fa6412c5e7dbad06c239a35357eac04dba107bc3f3e79bd59986f619fcdedc164c6e51a4b95b9983772f912abb52e07c0f6a607ed19cb30ba9c4fd3f0265b74dad4f41f14e41ea4b568ffac66025da0355fcf76ce8df4d88f68d42d008d119ac1bccd4ac9f2d687227244c38c00224161ce22805a8fb0c4d31dd2b4b7de8caeaada4f497a77e3243c7135cedf828a593e552d17e8dc71511f24d7b0cc8dcd485955e53009b57abe2372e365d40cf74b515162f74cafa8a2270f18f1320ff2a4959933065a95a2fad6540db8eb9c41a227d9283f5f75dc5f0a4815d2adcb5e46b24a9c632ba2f129e0723203c47213ddb5d2314cca69b81ac00f29cad37bc6829e8409b8bf877510715a55e99b517e1c0262e86860916c6c08ee7aab0df25f0c11474b0ae56ad6136b31afffae224c28321a9575b8f1546ce4ab18871dc34c446b46206b6e02fe6218087c391298b2ec593a072a0c4dcdbebe6adf652025b1fc4231847ec83686134a7267c9d250a6d2b8f0c9a8f833e2daa4db4dc8679d9c871c58997d66c3e2bf177210b025e83ddd626f8a467c6fcc36fad38d7b504999be3c54e8fc96bac034872f823509d81e0dc2016f88f6b7b1c3eabf5d3eccaad2bbd895279cb02eff5de172e14bfd1bb062579e6626e0e988c34ea99b9624f2941e5e2ac2a7e0a34ca90f72e7df23c3cd0d418773f91645324f7f5d743a40fc91b5bf804712ecb065df6f729e9d4b15eb43ea8e79eeebc36938d695173e88cb561b46ff1143b16e6913616afe773c89ad6ed7575346a9c57a6e612eb23e6f099eed6bf3f565c0ccfa13571733d3eaade9d78e614641e397a43c91143fd31f92607b0eacd065ea6a9f71f50577f381ace9d2a0ee8eee47c1ab316df5f4967419a051e0605f451f6351f60c720c0c4806aac4aa5480c57be227d437f25839ba28546a2e2d7505e6ec046ec77232ba6f419260bb935638b340c472afbdfe1ce2185879ce2b2e353e65230e7672cef7b8b736d1bd4f901ec51a9cd0ccc36870365500be93abe31eb0c135bcba72f598c7f8880de9981e9dbc23190e75472b101d425507598f0747c0fb8b97e55ab44a2bedb13ee019f40b592e1fda9d5eea6c638aa2b669ccfe6d1a1c2762505a532f343d952b5ea0cf383478a5dd15a5e857278ed58c29f42801df7e40288872bd113d338ad86de10f3c3c5f34351e80900bbd9955e2f43e2e6de5db0594820cc8eec233f52a32916fa5df565751677196b5c6edb49425a4dd8e2c2c7a489f72a3aa56a9daded09ade0beede4560816e2348afb5d9da6807ac7afea9b3f27472a11f8c18d4e8033b6c88b3b8d82055c59fc1fea4528c14a9cafe88781e3de44245d47ad92fa1f3e1f5dfa714d107d38b36de5d148c27359be3f9e65c7c57d9724d9c4ad132db1ae9fd11997454b5846fa03f41265c68691d433c2c83b4bbcf72f0a00c441951aac6d72d1ce5d12b6300515506da2bc4dbcb75e471de8f258172f3d87cb39e7259d34e4b24a6868fdd41ec040c64b29e2dbf3ba13f007fd00f724f7438061f58cbd684c6fcd3074ef50e1902ef0be61b776e4d17ae8b99d75972ef2ef07222a578ea129aede5635d1b7524675b38abc2f537a9489293a3143c10962f23f7aea26a15ddc1768bcb27ee19fa65dd16082118e465630164334e7672b2d91cd84a3d049b4429a63cf117e2de35cb013c6fff6a6aa9d0764f0006e04fb9557a863388b16679020692be2b13b43673fb095df29a15b273931afac1357224fe2eea95f90b96797468cb0afe370b06bfda1f26832deceda397b3cde48d590c02084822fd94315237b6511efe5e41b5101c0ee855376fd4c0d8082cd38c7216d995b8b1da956636d251e26571024ad8a08b2e09e4d9882e0e7ef779923512a6498d441e4469b19826cb7242428f4133bb07b7278130b7bfb390127a2df3724c7d0afd428b423b0cab8c73629e4a0734cdbab3bcd7b0e4f9956b4282c5351fa698716a3ca6a75b4d0b6e5b2a736f12cd49f6e3d8f0555636082a9fa3d6950b772a8fc86373313a798c9af198eb62d5db5b5eee26bf7a2c2d25cd6400c84472213a3f204b9411dbe44389c11172e80d6d5a874ff5cae5f0eeeb2ea783f60024d4c90864873b0bd05efb347e0d2d6d00e27fac129dc3708d3746781a4799807236b4e8792794b28fe9410f1a45a309bbd2eba124e48f62ecb43a339360b79f25d086c1f1c7de07dbf80220db00313b0aa5be466ac2ee72a3843f29f99c0804728632889c2f7ffb6ede11c6fbf07d050cf8152dc16c859031c974b8461471231c29c2bccd970896bd30092d251ee66dab725d8a0cd34b2a16f9798d78fdd72f0372db43f32a996192997c4728b98380972fa2a633e11f57ab3370ac94286c00677b33aea7686f476094c1d84c790f0aaed9d382f2283a1e4aa8de38ac872c647200cbbe47ce85fbe40e91ff573a30cc32d7f2210d2e8991c182f1d7d6df69107217eb717d32c7edb39cb71b4a9ea4af2044440ba6780a9ea79669933486d7822704c03d0a9fcb81ce8a8b736c60e8d5b853f8a6f3e7fe8d45ce9e669d68c00c72d333fa6a9ec6cbb7f8a402e1a4bc93f72601b9a5be0b0b4e8771ac2fd4699b4b70c94917b62b218e686b7fca977cc36e7c87d50c09a7b2832ac2dc2ddb9c6f727a39a2ea41467f48ee423dc3f8a690980e281c24e472feaa04a25fa2b3525b72dbdfe29dec210efc84ef04a84a444865e7023e10056ae7a500bd7af760d4db7274cbc972b2e86431c3979bb1acf85f9f5b85af7e21b20db3429289ff7a5d23725e626593f1d7aaeb29b8c24e556e0a1855b83d528dc496f6295b088c3aa0041f38203c786e388f8b4ab98ce09b3147b71abbad001622df4b5282036624339245a3d57b18ff8c0f14f36c1b2a67f6f291290220eae32df7fc9c1837f4b54a2d72b71c1f28d37c2ee1b0599770e4f6bb4b7308f2dd9dd8bcfc412a54462cf740725b66beb312737516ba9689cf5e58d51f25c77303ff2a2a52c4a71be8681ecd685bbcf707a39ab5927f278133cfed186fdb1bc040c24d7efe55e3c4983c4fec2588d893dd244f4ccda26f1325537f49cf19e2665eb8495cacb65f119c216c8445222fdf10530d2bae66e8a9ed36eb8ed02305caa828f0e96da1a040029a7f6372af66a12199758e1cd8c5bbc08362e0d60783f3cb4ac45ce8cd4fdf418071453ab37538d0024483d8e377b0abcafe30ebe40afa3ee7854a481e1c4b42d17b721af89e2dbeff3cb99b3536469527faf93205e1b098a5ec437833cb8f026c41fa20e89835035da84006a0db11330468b6e9eae9da87d56f348d769e3a191410015ccf33eb69afa08877c19ebea72c6ade93ae94db4e33b2c56b70153e8649758472f0841853423ff8c918b091d7ba151f7251bc3809860832223e0ce3fd6e1b6c72988b68225c7c9222bf51f4f5fdc60f409fc4c9d8a742767734e5a243d103447230ca522e853af4763cfedc20524645a26dba6dba1bd43cfb8b949a38a866af72e565d8537810412c6ac06e5bf4937a29a4ca935976010c67bde2b72f27887372d85318d2d555678365cda3eac48fa7d1f3f71c3b1a4dfcaaaa77640e65ed4272f2d533d0d10e3ad2d7bc70cb3773a26772a7413c8f6cf47c4fd077c51083617290bf788fb38f909849510d4d21ad95b87155df513d3d4dd4e67c863209c1ea3fbadbc9f4235766925bfcf268d81eafaf889d3837e65684d801e334f212eb380cc2b78987e3273c01e51310829e1e22bb75e5d1e3552fadb629e2237740b03b726afe418caeb9312fbc8547959124c06d9ac34f8c44d680ce2c0c02f880ce8972eebafc6d80555a8929e300641ab4fda952794da3846cd9ce98b9b0af84e67972d52829ea1f584ddf8927bdfc9c8e88d131c09e1e4043abca9cbab359f768c0725903d1a12583ccacc13a703a6f8862129b8a779aa2f64bfc9491d77390fc0b35bb1ee6ea03c57d60d080e2e253dae41b4933fead1f15e17634988958f1bb5134d752524c91793defff9aed95f8ad3990156d22475682e20d379e8bfeb5aa84443d7898be50ac352d04c69b2d469203b91bdf4b709bdc2c8937bfd4e65bc63042660e41d36eaf39c4cb3ead4e1dfab8fed1647fe6c368a9847dff0207ca43b272d1f314c57ebf7d1ecf6bf8535a47712e5e3769b680e52a74b335e3c5b64c1872991d20be2e464503dec59c28ec2577029c8a7fc2cab134c4be00e6bb84b29b726a8243b1b76077619543a9db6f68e742dc32799b87ee8784b38344676dfec226fc57a680cef5391dcacb5cfdaf06ae832f3040b2916266b754ded2acd1b7f630d69c000e65174d2f2e3f1b81657ee55da5f2a8ed7d3b83f93d52ee4ec293972a6115a71230513e7cf40be18c3081edf149d07d1d3801550ee166864154cfc9187b31957b2ed515da247cd19846e6cacdac131b8c2b252227ea6fd8e0a1499b640b7d640c6515c2b2d5ca7821db74cbba3a7abd4cf4694685a287184c518eb14bc1715fe028e52a9548426bee0f78d272750560bd655b203d9843af48845f2272abd15519e0f8a5d55e9a3cab6173370696a2fe87b17c4a65b97c0a5f563a88729021e375bd3d15e59a86530754d1302f3bd47b1931dbb37cff85ebc150b7082e451640f36045b040553ff5162c39e12dd11d46781c7e9fa922ff15b095421f725fe911d358084510917672752716c7f8c10949eacfd4fa0f60676166a97546724b2e7cb5d0cda97a36ae3b2feed78885922d2127e5e349e1ce3ea88815083072e243e5a35641d8d8d9510c105b2f8ea7f73a6721bdc7ff83e086de7ca9f5151ccc15b673bf4580c62d6248a7a30db91ca34425f1bba6e9969deb1e48cd28f27257597493978ebd9c344e22d33c4ddbb82614b7977d19608a52ec50786f87a57212215bac7c80dcf4522417a9f7b09043b0e1b4acc586c1134463f76ac760ad2136b53f82b264f84579fd0ca8863da088c07c7021b090eb0bfcefbca54503c33f5094d103bd173f2da99d3174384ce54d7462e7d08641300d4c8e7f453ae9496a4456c6e9e7e0431ec1d9f6fe838f7b7494248257335e5602223625528d831f6bbb4d6f033d96e084cdd786296558914c08dd0486ce9a31352a2172d1c2bc2e607d2deabf8d53ccc30a932e26ab653b4d904680487c37b234f5a4127ba02bb772801a353bd85441207018e549dc5f35481d4febe2b603eb30ac774ee5c7d37e4d45b740c9a465d2ec1841b47352b136520db5b85ffe6739405dd3d9a8dfd8e62427561e46a0adb6f1fbd1da97ccb4750ac922d067d83a98d7f3b6d3fcf12fa272c70b31d778ccf1c02797122e42b0bd7d02f16c68d15836624d0216ac5ae4bd724d57133e7e084657db078553d98c2f3325dc81ed0d4cc5beda00bba112419c5e9f5cd2b8e8c85e896bb1ae1cc7600651e1baa75eb230ee220f8d2385f2423e7299f1b9f06e1eac91815e7cbb2cb154a354fbb055581fb3b5f2a0df65eebe32281bbca7ed12b0620d6144a0158a58c3ecfcf1b89ee5c7f4a30344fd08c48c816c789f17314b5306b52789c67decbca9fce2bc6b7b79333017dcd249baa0806e726c77b2e875e5253f7692a0f68303525bf5e1c49c6f2f2c3d56ee81199a3ee472c028fb759dd875b38f953870f24d0399fbf8030ea1ecc298340e4af2a60f537245f0604740142f7bcd749357b5a8942aa831e0fc9e1ccc8674fa5ff6e7b8181e1acc65425c4e9abd3eeb2ea1d963ffad13060997f2dc9758eea9e2a1dd751772367599f3c4f146e459dd37aba7038346169d92dddbb9bcac8aaf4026907889197eb783c32051f46f0b2598d2a440ac8064956b495d6b1d662766f2011d3f32725b3c7ac3d21341d6a00a58310045578dd74291318ef8e2645a1aa56e67376b7206fefebca56b3345cb8f6d2fe233cecb23ad39120747ffb2ac0a4d58b46a487257ebebc90a7b940cce4657067bccec24fb9aac7316c85408ee9b34c991c6997265a71bf8d71f62cd0e1cf463d4b0b804102c4330ff8e50f9e64bd8cc6a09e4724f577053b7dc5cef216baeb9495b3ffbf3809deaac7084c2c60910ff079cd114ba180aa219a470279ecba5b477d6378d6a75da47ea0d3080251577a9fea9a072b12828dc2eb1af6bd60cbe8baf6012effe3145382542ce9c47e341f2d4769a720f43ab4ac7d251dd71ea7952a74ec1b21c9f2152f52e44b9ab10d39d3e51597267049d55bab20d3e36d49b8f14d7a5c7cafe58c775abcb46f9f7ae53238487007563e6e66da5d73667c0b4e74e1c382a30f47707a51295e3bcc9079196ccdd72e012db8aa7a043a245171e56f4e7ab2bca8bd9b88d3367622b354aa4289f50720f1bedc98658d40d5c3d3a7b0d6403106888ab441d40c9e08c1bce12c4a25372a5c94fc5feb5273462fa023d58a88cc65671a76b14ac752a169ebfb9e59af96fcc75e10dc2065f7f35ef5921d715096db9449ccd64d9189c82a39f4683da16723e1243908334cccc81ee41ae80e411d564a195b383af207018100c9408272e72783df9febe22dbfbac1bd376bbe93af2700426a97d83e8f9766fc28d33e3fa67d6ad43c058a50faf2fd06c2fd42cdc7ca9839c08fb4859223715c1760b2c787298105119e9a031c49a6c65a8bf4a62b6c9bb1d2b2501c37d3def9372e9035072493fd83828a73731f8d3f4506e69937cfd70d04e512953a494476e222a381572dc7f727655e9adc9c9b4731c97bc73898be8ddd968f5f0913a63ff583305e672342e466234f5de21a9ab77bf9cc44007eed97b385175ac6a22b7e026be1f4b5d5ca88041a987111b2c0a54d62db2dca0b8c2d38d9ce2fa74419cacb63bd099723d781fa1533611aea150ec1183995557709494500a9c363b868eab2f41316f3dde47735676cf287a285c190f00ead6fe6ee851343ac5106f3bca1dea90838a132aa97124686ca76fe191a1b04f0d0bb8f440421aba808a2d762a0719c5073772fc45046a3451f78e64cf6eb085853ed854ced554aaa37eb1dd3892465c9ea81c1deb4c5d8b91a79ef19bf4d6eae41b487d555bf0ce18695d94b38666adef0f723b7d6bd82e21b84deaecd6a2d84efca62ad469460dff0c4d11872e65b32e3f2da224d9e589321d2ecaeccad4a5a2263c333365a4d034a027860dd257ba91d1729228cdb2a867781e07c50dc55294d9a567fe5d4f9aa7dc8fa869346ddbb7b6729eae54b5c14fca4535d1fbd4f534d9fecce1ba8c71c4ead1bbf6f2a5a236357260f421747542c728ac0d29fc5207b1aebe104910a43562e0f8e095a0fdbae758a41920933b4dcda2028570cf310f3456084183800fe6c16c210c69b04aa9dd72cc7c850e02959cc652d65fc8a7a380c47acd011e46f798f2bcd7517417e09c72b254adfaacb010680df0457c5dc5bbb57ed398009cc76b8152d6340dc644e05f9c97a5dad1562af127a9ccbc3bfbb8c8aac5ba91d20d976d89134ecea174887220e672eafe7277bb43a2e1fdc37102cda91c87acaac2a45222771c32b701490e3dc3bb2fdb7478748bcd000e3343bc3519c490e309432f04e208d10ee2078c72019aa3c36b806efc5662fd0e57a2ac1ded5fac3bc7a7c85c3c1bd9be41aac97207f43ad67e4fc396edace0f8f4501af88c793d94ea2ebfa63076f16b44ca69727daeb330d7ae12707704a298f22154977ee2b880234a5d9f21214508d722a765a8605ecd19756d52e59896b9e09c974c396eff70b367b463abc47d67277f7f72a0cd53af0b2d94f80ec5b495ea6f47a94b6595c1154256a099165366f8c47e7279e01a41807b13ff69f56d00a8b12447b0eace73f9599551d3b329ace8e20a66d375de2a5c1cfc2ed5d2b98e145482c6fa929d2730e7e17fbe17cbef1fcc860a595634e89c118fe320b9eacadba7ba7dd48d5c723ac25aa69e0abe7db6b28f1d949ac20ea75fe91ab2d50b2f9f6c9c6a1557bb320d4b2b3b1f7c13b7d1dc1b725e5e693d5866b176f1d9838d7b2070d32e025eaeec761a28383842fd90f45741fc5db5012f71b5d70b49dd12b7f36529a7d043bcfb6a71ae9d7676b8c6a89d26d0137204c6b739b28903f25b9f2b7589859b350d5bca078a66b9bf8fda71997288d3a4bdd0cbc44b4af83a2600c6fdb6ef648d3cb67fb832dd21df73171fa35184d2a68434b08896f9a3b43eb70abe335daed4d97e995d09667605c042145e724277523b94567e7c11db1593e3fbcce163455a65ac01e697ff0b5050ad6ad7722254628e526ade922ead9c5a2681e0c7c26a6b2ac735928da3cc8261576be74a713fd421e2181fd7e51e9e1c7a98d501f9f9706a3bfd6b9722f8fcb1d0f6d972099d5698d17f15a920414ebe7af7934bf9da7e650bb880535eed7edc4b40003b849bb46037de9e5fcfbaee369054a376fd4526dc50583c25812e085bd3ac1c221e79d8f14807164f51ab4479caf907e47a2e653ecd10433494f93cc091c4e631507217ecf153f7d5a87a245773d45f75e4816135dff34165ec5dcf9297a91272f00d5524a221e33b684ea3b87cbe0581f9ef55e72b5958b4a3870cb47687a2728f0f82a90cab2e658f0dac7d0e2b80d5b6cf9bd6263df623f86fdb5201d64972782c397e87b84b1dc376790100ee411c8f0c4c9f3fb5b8ca9c8706707814f64f2dacaba01e05694b56d0ee78f5f952a409ba339a58cb9af998006226574ba872a17e2c4d22858d58647be811411d8c432c46492dcd2e847c7b9cc02a0f598472c13bee540543eda6d702d74ecc6afe66a37c66758e035c52ac41c525f2b9637220208eacdb61a2fbcfc1565b4fa7b07c1b010d04aeeee852e15ce2f25bd89972139326747e9ce466f64a0be76dfab3727156de8dc1008538fc066fa2ea89c441e5c4e06630837db6522d54b2d79d7111b2083fc085fb8d98997d821cb02bbe068550cb0bd27d368a0ce3964007256679e22ecbb234db9f807a9604513fed0320b9ea7ab78c963a15f46fec747c12c9cdfaa9e7bf6df0383c070ee92e9f2d437218ed863389b19780e8cea55536645d80012f0d7b7e247e63813e85a252d07572d73264df9ec8e098138ce116c1f11db07591a40a34e7ec06243c32ecad4771723ba0c8813b8842f6a58b094bc750327d25f0272c392dc96be43c898ce0f1d009255188c33be439e5082e458bff2d454dd63790366d9bab6b147b5b73ea84c17220272ac83222129612bab54fcf6e8281aca700dc67cbf139e9ff056c0de6f70caccee7bd653e1588a7be03bb98c2110c9d8d7693d2fc9567033b80cb13737872a7b5526305b64f1bc4c3233195070c7e4a6182cf8076f5cf51e5a5b64f25555fa510a84f1e5f1c77c0e8519936889b041ab757853df5d8d76ce51fae215d1439c29b8814641170369f2941283fe166d4bab8044217d604c489418b5973791972367baca34172c61412a433a219e7990e8db4b871ece089a9ceeadd218f3530147f128d19b85074ea2e55835bd8e11d767ded2a53eba3492e5943c04ec8e14c724e3cc43d6f73eab7fe37cd400a2b29de964442a507c354fd3916e8ab33f1993641451c822ec6b2444aae1c982c76597ae18fe89bcf34ad049f91ac3bcf88b772849a9f7e86f5b42a9668cc3bd2321c3f5b4858e2109dfa3b432174c888872d0c8f66cdcb473fbd1aecfbe4ce0a9b5e67f5b8f601b2e816f483d8b38f4539f259eb049e3fdea1e3507733c21003581589258dbc9738658e87116a1b5653c10272af8884051b4ca082f834703d17bb3c2ee4d254e23f20ef1058a315143df45c69b1178c6079635609cfe3ad4d90340daf9c30558a2e8a82814082d77608f05e2399777421376fe2110135afe6c145e0b9ccc2bf4afc6a65017378dce3cfa4a9615659a9a9ed40d09542d0942652c3f8da3a52f0fe352671ec9537d01dceea32725ab5d27216055f7a44fc4fcab1a76c834ca5cb7210fb5b00bb2c5f9814b224728ba3754c82dbab4cdd43a2f9c2668d5c58e36007820183403a0019ec71407d5ab7fc832e2f8e334b6e476b944dc707461083877a50cae90a2094e6dbb10d6e72f50811e3e86ad02039a1346b915689ff59c6b7075dee264bfb7bf325983676723e8afc4a76139b461feb99941c867d77636085ecb72eabd29b87f538248be5728ab81dace9641d6f07b08b3d910530202cc44a89b00a0fcf297c4e700bc44218f064a7258118dde7b74012275c0403d0b04949f4484efc5bdda82e6a2f87c46850be19781c30d34784d1261dee3bea5bbd321dea1ef45f8146df339c7165d2615abd24596620127d630ef43fc2ce1cb88179d81791e0cdc419839b8aa1269f134a6e5365292f34af5080cadad35355facce89928a7a1c6e6e2f26d4f969cfe72cd57880517d91be20d5fc1c56d9054355ce7854454984a32c287c2b4129f5972b3e48263cc1c69007349195c1c99d545461edde699b361a965dad3d13022d672d278e056c5b97d5542699ad3ccad4d1aa0cb385741a6fe40b20786ef9c4d797282956052367f449102a44d14eba868c204cd9b927bead028dbf42f0133a29f4bf896c545baf7be2752a9623c7d029b51d823b020ce989dad3e472de44d6027722b31968ed3a5d3c59c3d19de25640683e4224e21a14617c84b18b9c47f44e472c284814af257cbe2bb623cdb1a39cb26f4f0f376c60c27a16bcbc2c2a8e66172d04c434097b783cb02a7a2eeb6b8ac97982069bfdb095ed072c425a7dc58c772f1e4f05655f0f5486727f1e40cae2fc3c35c089c3e861d2a17030de9d02e797224f9c215122f9d9e7676023656136f0dfb290cee82460128cf24020340b99d72610465cc03da4fa4cccebfd6ff0acc8b80b80d5f05c29b417f59f2eb0080244cc5af0b72c4dd856c2c3ad24024981f3bc5cfc4f14007e73b3838f24899bae63bf722963446db6cec9de45ac966bc2100e073d41098c0108ecc35373865ccea72d41b80da3f685fc60c29aaf4204abd0129a850858ffca1fbd7a97833fe4a8b5dc4d4b7a1481da6bcd881611f7877e795c9a778e9d09dda01f5ff447177d2dd72b2e44266b8ff3e5bb369c3ca91e18517e80d3bdc1a0f635256598bf8ce714c42410f3935719ee96e9ef226f8019c1a000e2c91f086b3c87276d82c1933af4672d379ecf97ca1b3c6f66994f947cfeb010a192965dc4882a416fbad6901786d728a26e8979a9a91f5fafefe1d73daf7caab750d529eff4fa5880c5c160c6d9d6dca98580a6dc0a93dfa2cb3f3b2facd5978ee4502066adb272a891c420be02d4c77e944193ceb28285077f9ad5c19c6dcbbd68bf8fb25fe284357c42d5dc664237bc01a926c78605a9bc944a5e6cea4d71516e67e4952dc8f9cefa6dc670d3c7209c59041012476db8588e225924891d6ba43c8ee767e9e32722677e7774b880fcc593ac50956248ea10a72e3d6a8470abe8fc3f8fe078205edf7660c2f93d172661cc4006faba93edb497b9359ed48f071174339ce6cd35a61d9ea4333e67e727460e04dc8d265bf7f96cddb72b593d088ee53a15a57c6f926377c2c934c20722da4ad94701d74082ce5bdcba05967d3d98bc8b7fc0bce73916089673e908572265162d694142c26029d1dbc5f82461423761dedddf4053f55193eb31cbb581583c99c36967adeaa7bba0723980355361ff49a56b2bcc9d0e232c209077a11211de9f0ee52b0256599d1c026e13b929692b1e4b0170936e452f57f5b71a9645d4afdb8b920e899b179e951453c0f00ebaab7325cc57463cc7078ed3474a8f972f52be7b382a6d8c1ff270d80345c586fb9a7006cdc771cb1661bfa913cc9f372edf3a4074a1db2b03364d6194331a4a8be2fdef5130b6421077939fcb642034f0322184c99a889a56542b3ec278d0f8790049dc4b54c5a5e3c5a25586747a67278b7ed125356c8a33b9f2207e5e14b1a5af2ae7bb2969da9c5d48e2860fe3203cfe16eb57fa3f97d61de24ceafed78bf826038de09372a096a7d99260529a5456461108c809c7349bb1f16af830fb762ecb821e451551d5d4277bf45e969ac209b8764c5ed4f84605f909a09d99b809fd82f7b2aee6996e56757e912eeb02072837edda1a1d1fc948d45c001f5f62188997e6053b332de0f3d043df28ea9c472164e8c55553c85820bb0347770129cc73b4341737d726180b89985bd1180787202180e17c90fa81921b92710ab57e786004fa6982963683d78e8243b484bd811d2aec02d46f42ceeb5a4ad8675d133989ce2b89e2f834c4d8b73cfaa9eef0c72acf80021c48c86aca21e1820fa7a3bedb6f57fb13dc312b5d1e1267b20b3fc722cc695155b6e4c8260803aa9a471b488fd8c68bf14c7ee70886ec1898922da72cefc2dfcdef2600e7f2ef32dae9443af067b8c063c2ff1627da7d7c61bc50b71d23441f0c194f5ee0e99ee0a16d76a472f6704b199da65d3e0fed21fc969437230c23cb8a495a19e154a7c79bcd7b9031214886cfa6c18c01ae58b2b75794a2ca5ff7ed61e6193475f34ec993e02c7cf89e5a5dacc2a7a2a411a97a60e6b2533533faf59687276b049ab35ce0aaf431b5d6ce3c01256bf01f781b76d123a95132f81ec7b5f88e08f492df16d7274f6b011fb2436c46e1edfe01afcc5fd86e872ea21b265bb7b68f189a5a2fde5df71d4af2a9eed4d7cf7f9cb643b14151ce12c7c22d659e86dabb3ea002a9092842bbf95c9490bebc5efb6b856141b5ab06e72c5fbb36224e6540b82445ad88f6cdeff11c9adf64049829a7127927e3cc0407226b1387ce11ae36bd53bd03006e8a3cdbdc1431d1ccc609e51aec64e02404e72464013c291ececc06e45b1489301df6d3d86b87e979a7b82863b0fd210dbf272ef2ba2d2d98b74180096451d7ac1c88f4f238a884a270780dc932d11f6d2e86e47d3a70078b0804818640bd3300f03fc10fab08a6644c2549c9408a15b17e772e5cdcb7e54ff0a3a25e8d49dd1c74ccdba3e58dca30a9471eb711ca8de350972fd7cbd75d38eb113540223d32f9fa0b4c21b07f669b314f05a9cd700dd4c1c12f3f4b238c8860db62a7466fc0d2de7d7f902dfb71f8378dd85f1598327b63d7226ece97d7aacd04dfa2ad37a5567f465d907ce58b6ea5ade53b6994a9d614872044041773245119da539ec795c9ed3f1eab2cdc0e9568fe0762a650fe5fd322ed111314f1d6603fb309a78845ab10c70ea9bbbe4929b89832c30696e5303e872ca9db1d05f27221984dcce8920a5796a7106b9bc04f5677c2b5601ce6833fa2d2b90433c257a2645c1b2033b107540449437ec25f0a0b07559131eb96a1c804e870e5d42b5ff1507683eecd2a1cf25ed8d2e36e8fe2d582b1698d59311e66c049cd3719194adde450222716b7c1da07e802ccb67be242071bf4f51f80e791536082b6958637d0bd8edf8e969040948f3d17dc96b845057fd6c6a695442782c72d7f7d9ec611b41117008783ef0e24016d17143867db65da2fb24e97cc791113ab2da828e2b3f8807d9cb46099ebe09dfefbe97c629f3bbe928409959fc0b6872a15e71a14d483abe045d9015745e019ac8ed7ecfc05b43f03c11282bb085213404c5a645034ddd5f842ff8240156392b6607107db2d7bf46fba92fafc97b952fd7ef9afe9569cb2688c2b94fb848987f55f1bd084f3a41c7c2ee8c1dc905b8729b1143632de1e8790ab482ffad0b621242c2d953286bd3acaffc91b18bda04644747d36e115963a7c871b3ec6ca7ec7f3895816fb556a403bbae17088cd0d972edfebef7b956d2007ca17e5e22577415bd38fec6aadd255f341a2fbdccc11872e1eaf9f7a306a86a29b047357390c6554700d8b1c248ddde860d1e922a1a5f721a01fda47b83878f1812f78a61699e8d86365bc60bd30dbb8ac583a95289e072f40267c7997e57c2081a4a9cec1d4f78a1b619c80cc5ed786a450d41508f434501d8ac333823704407413ff76b830943a855079b40620462177fb6f7e6671a7214a5fcf9c4d0fbd0edcd46bd9f41b120499b9212e202c79cb0a6ee5ce5a77f258d6f872e16f69b3ff4187b2444223e4d95637a3b5d2170b97bb081c1ccd6ab72d58718ee770c888b52f25eecb586e3709a961653b402fce695a4db2f0e901272df2a1e5e7443a4f1c9ad5329f1bdd7c65e474de32ba901806c3cf743b6253a72cdccb49fbc9ca87f87baf637e7aa83ed69bb0c7487206a6fd5da40c0e759f4729ea732e353d3abd223732e29eb3e37a9dff2bb75367f81648315118807a1cf7229f456455981aa0cd015a11e4e2340f9a30b752f13410c431ac7022acd59821120ef614094bb773b4b0f64f08cce35edc75ec92ae440f19e0ddf19f110a3fe72c6cd52f10c60ec8fcfcc49e3af3f62d120c4eb9aa49ac19d502e3b3b5e2f037218a61b084c2522fcb4ecc7a0e22cdd7cb96016f63df17c058b3b9e3df5aece726a4225d01efe4e13b235e08969cc23d0a9a72ac7bd6cf63c778c98205d54e172fd4d2ba83afb0f6a3fa9cd472bd5018f8d04465c84a1d08b5f44b5a522226672aeaf65aee755ac83ba7b3605e5b7a0edb13fc1b64dc79f589cd52798009063723b39339300b97c1c63abfb294502a77247f8450f810442d1e909fc235a319b72c11b2e970684b5da068c38978a287f96a7730d80b4de75678cdba9a4d6033a567cc4cf4bd229b6c5d5950fbc9fa993b7d0ca326eab00456eae928daf6256702ba98f1d72d8b2600beb26fd6c4d81196cb0d33eb2e899c3ced7e44d8900964d38ee6e77a170dd4cd91ac112bcef0e4229359af69b0aee20862b1b62a2e7be5e725bf79c6984c6083f6cad0602a742a99a721471d8a889047aa4993c177105c772b381ee4e41f1cb887343ec2a72132203f2ec5963a1b40061f736f64217ad1c724e4bba250d1fbb378c2d793cfc3b79a7df170d62bad39bd15bfa0c4e8c06dc72d72c8a43e64434d17380e123127c755d204374f98a8a842994081150d2a35e72338360acb68610274ff3e859f39cef8e7d257e99705f6152da96e35240070b723c69bf7b6239fac1ebb28b19423cba5351aab35476490fb6c093ac0b0e7ef7574b74cf5008f9e66bd6510f8b9c9e769d06acff59036f831f4b628d472f06b17261b4bbedbd1fd1bd320ee3d0b5db86f1ebc5f1f9fe8b34c522fa7f678d14e16fd7e0b8a43b6351797d1d9d99f4f4b4171a09955c532b5818c2462d1ebe1ab172d1ede9f6db596ee2b15301fe24e84f527db907bbe786bcfb0c12a5e79c6eaa39d59281b71d6c19b67a7a15318baeb9bb8d651abad4be32a7bdeaaec59e981b726e7c2fd2e1db571a1b8f572804f45ef099e2ab499236973c16bfeb2f87a42f452018fb2f45c634c0083ea87c5d27b7bc7065e0ae4e24fa5242624db410ec3d726141c7f2fabf4e466376e14e1b256380aa2c4e50ba93e621e293f0eb9329a81d06d8c97ee3f53832b6e21370497fe455efc0fbb44bfa27eac6c2df4e26894c7218823d0d284dde0c13da8b1ca9779c1045f484ac148f0fbc3a52cdc200c7c1720959459e59ccbfe2398eb08e4e1155bd36f7a3709ef6ad3eaa07c7cdb10697349fe7d868cf1254386dced2763ce32bb1e3a50abadf6680481153970b7da623415d54e82d1e5025ce6c69c7e9ab53391257de3ed3981c9324ab4ebc1b5205f572cb02e87c0423da30a691353bc193cedc2f13a7269439aacf718661b2ed7f59727eb332a2d3b2ae0eee922b755af297bb8ff04198e3228514ac8c9a80980025041cf22a80a9b3e0d986acb4d98917e5f3d2222dbeb2337a980731fffd578ef343d1f2a7152043f3a72256ed984696bff0677de138ff73da0f3d28dd9c80cf196f5ed7005d65116785bb39e4f672ff65a8485dac37377e5bdb38afe635844fe147edd732ac4179f859c6ee9e183c9f8c619585ecc01a78eb593c7548ec3998b516501bcad991914a00c28290722ae0762d3336e1dcec020eef3e84689d20e9d87208b467d36ee49340bee8f5658e6e63a61fa1b798c92013761981519084d39072666e35ea9583738ddf2e42f198c799be8be8cc13349ef0d0f0f411df7d90860847184e5c8c8bae746017fad3e7c9d2ba135e6d2d72dea3593840329ef63d5213738fff7c45c743380768aba4dcc0d6a33d744d66b5b07a2e1d04ac1d3a547772e78f598bff8a76dc6b68ba5c4eb66eb33ff319777ef217d74cc4d700d6a87732f9342f90e6adf0c73724c7b93fd5e9c7df3deb3a7175491974c3ee2257a5861698039814af1bc0230cade2fb0122cb3629079fb3ed6a700e3657cce4ffc0f772c7e64389db431159e2d6b61421505aaccdf81fb987e38703232726a12d2dcb173d6977e36ed3477d2885960b11a5592f1453333adfb2edf6f26ef8595d5524725bdd25ae1a5ac76f54924a7492af39cb70b6787c96a8d43018730b0980c34372721cd77a36583445260eb3c7acb8a80af1305dc931d18e06b89c1f50a57e74727fb32b8ed084067cad8aa9e46258189bdf290a9b20c19d65d1c85e20e554b84be80a438e80775e670199eafdf2d16c0ec860afa924fd266e5b697b6d83013c6e87839dc83d0d992cb3a50158a4baa01620ffeb089c7b48349f62f9aed6677019cfc8160906b4c5dabce51455bf5bef88fece72a7f05428637283dd183ab7f7728a5dbc89c75cc14f35742d5292fb61d8df147048cb33ec1e90de0bc1cf7da07218c39f2360e8285798b786a0f7c5003f73d9268d004096fdc8cd9a94187bc57260c4a1215b2b9a42dfe8bb0a654bcccb494369c5928423161ed7ae581ae72c26ba2e7dc41d40373a29fbc98a17318e77e962ba3c0769a6c0e88472e287c5237247d562456c37cb3daedf996c743b1c645a13d44c80383ac043073e07403f563b516b66222861d5c989cd8df8288ed7f231a99629413ca3f89dcdd2ea89a2f531b8c0dd99f4706430e3b0d9086cd50b4906396ab1736c43770a07dc79d924d6679c4db9e0a2752388cd26d15c0ab3b2799eccef9ffd37ddc4c49dde7d56731472485205893bd0eae4fa7653213d21c7ca4011cfef9c6b06fbfae54fbbac14f63fdb3e3bea0f21280b2e5b70d1a8d30b34fc9d916e51d6d8899aedad8ab2056b0112cb3012499afe501515d5533442a70310383ca481e2ac4507d27e3406a1387274b6588af7d7212caf4c854a81703246ef9f39bbb4e194e01979cfd0ea53e707755b5ac3984735834d4951ab16cd72469566e6e297eb5dd8844fa0c073b55e546c98e29f62aea6d812e238db70deac111dc2c961539348472f5fbd8eceba0e7251ffa78aaf5eebbaf6cebaa1c80651dd96315b7a61a3c41c0f7ca53767b6ec720620074ea9706672bb0c0ac1d8d6014101cae2e30ff75e3599ca6958553f801984035a94d42ebc998bc8bc3b45b28d8c611ce0cfb3d95b7ad5d62cc1deb84440c9bb689a396f8b7b33f46ed6afaf49e375062621f8fd9d64878ee808379d5d3fb362b33b387e972cf76a3718af2cb296ed683dc7225b655425c996a34b284472bd7b89e4260c6080a128351bda3e4101f65cf10d169781600419eb5d5dc6a57269affc587b43295fc562588adb048b1162ce771ee520d83b85e696ad88648a722070f9a99ede610e715fbe23b095b18518035435561c3c2148621000f6827543a5df2d9350793dbc376ad48c6486d56dde0f3f4ba895cc2c3d5a5f807db89e720959e930c8ffb2bc2b93448fb3721f350a34c98665888c84ad002434747cdb721482cfc7f2d9fa052911aecd3a1ed064d952783c18dff64a54b0c193d72f2672a09769b05fdc7373f2715f2df8ac20b7d3e05033249f7d5846fdad906461653398649831a1713ca7943453d4b31b706ee0287d70ec19b257f019b60fac862957f6609ce3016bcdd5d032241d54849f8d3c1c15e5cfa611e04ff257c968cc1f72d76a403b805abf03b0b8224c0b0e350220a5734c076849320ba2d44c6e00c2391b4698a5cc228b71736f4e29c73d0f9e9fcd50252255831867a3593861529e7231027fbeb730ba9425dbfa69e0af1862fa791233609d14cbc246d73075dcfb7227747a8ddebafe481f830066d1c8b17d4ed93a0424daa40ccdda7870129f346b58dc0b349da0f9a2d7906740ec4696a671f27b03768816a07376c4a2b9682d7273e2d678cd1a4be620a689614b66f97ad05eb84c9a5596d22c1a00d018f36e253b2d87bd2d31dc5e50a71afb7ace0035cb541b915fca412ea68cc998e2aab4720d9fc249fb5e20284ba99d6bb888de933fce5e76e9e0511a9999f5de3d28cc7208c4b8d5dc2d514ce9418b3df5e0351002d054e7a4a44018712f83045ee4bf41bb477f6fd71f8092b1badaebc25d8d5cb7689b4194986299e60c83ef562a7b200602301de7fb849dff6bea23fe46d4a117327da38db8cba1c52116306effd021d6c9fd22ea504420a41f1fd98493ea5dc6b0945d29adece055049835c557896e0e4b9010b916236e5f636ab1f23656aef2dcc5b3d4d08623e1d374ee6f7ad172876d307bef68e28b6ff71bec386d7fe6b5800a639b60b5dc14db214b33ec6f7296760ce76af66e6307645f593b8a5d8acf06e41cee48adcb856853f17e1cc37208b807d7b153b956fc848ebed477f3a422665ac6ca4f67789075e7e027e4fc684385da276aa478cfa9bfb97d61c7f4517a0fd778b9678140c32327a6f94e7b72c9d487b5c5444cef08e2c889ad12c90ab0343b7cdc6b813a157e7124b826af3047d27783493dac983b2a6a1ffd79d60fcc18a9bc0fc2a18889fe8745114c8072fab260bf5a5beb7a79a0bf00f4b03e798d52ad7bdf04b6b57b8abb01d75cfe725b2adde4aca278f1971e8e39c29eece17a36e212a1b8ec30de6e6e8b26202b72fbc22aabd951480d35908144a2548da248e269b71364681184845febf15d1172f9b7dcd3f4c663e69e5b752cbba9de00dac7a5736d0fad609b1b58ee3ff5a972d30ae26d009b445474c08023f9cf0e22008e3daf514d22fb659798f169e6b36d302b48eb0b8e89ccff03f3033c8ab1732bb6ecefbefd7ce1a4bc0eb658f38730c1f9a6d78f89deb3b9b1da7ff5617346428304ed6ccf504aa771a26ec4af1441907951af3dd2234043c6cba6f0b4131478b6b449076fc2669625290531136258933abfc5dbd55fa3388231b10dd97295e651e68014e908cf26a20eb049268766738d39990db061d534e3a9c6f8b69678a438cf88d76268c580fdf07a8ae1787296b978febc88b438b30e1e6286c1513f8b15efe0556fdeb64fd3ddf2aabb933abfa62ba5f21db56c13e89fedc67649d690cce3a288bb1efeb76269eead04b87291bcab839c1dca924f1ec48b903bb30d2b167bc5d0b97074e3bd3ed34240ac72a6c2ea9811ed1d367fa27a8fdbf3e18355b4d881d5f9303bc726c0a21f14d9724b3ce73e25e5c646769067e05e50dd12af848078483f0341d9645cf206c5bf49897722e1db7fa1193b3d16794adc6e5e82ff9150e6ef5dada92d1528e150ac50dcd3f2479823067976158bface18185cdb1fecf4b0c274e7f9dff0344bebcd23cc9cb5c05fd7c37ca84bec22686204339a5416963e2a51211cc8cb9320e9b3728405b672c3aa26978c4db592168a266d282d566e430f97155e1a01dc4f1d0972c03588f1da183a1903b7b1f2d9600ddf887f721b4bae407ff9e680a03278be2bef510427fb8ffc713585e03124b01d013f88f6307db46dd880f51098d9386772e5f82cbf609070c5794a2e7b322e4668f164a23081b9296ac1f6b1fd9008953a359016cf67b864d2d0486393c2a5977acf2e3c86a6d9202e903ac605fa5c5b729f2302bb19b5a2fcd0b72e96b4f1f4a04a6957d0cfc6d27932cdc91c0e8aae72bb4a51e395877b6654a8c41a3632dff44fddd59d9d125e4ac617cfa61e78fd7246b86ad10cda3774e15db8493f494b6973fa85edde1a5a1caf55ea533d23c672e950b0496e5991f2878387be852f6f9914ef75e3032e046cb872f75b50d1524990236c2a525277452e167df03911fd0d0c5ef850cbe9bb713b3e0354cb08d772be0d39400c3052c11858f7852a59a8db41e8be4b2600495cae05dbce6201a672344e7c8f553281ac0fa516e767800fcd7fe2a5dcdb736601f536409a0f594f72c5b381625fea7bb1c36c086cbda851cebd2471f4606c431911bb02e3b60a8171876df04a1d7f02899ace3441b88f48eeb9b1850e5a9a79938783fee0c7185b722f7d231554b4b8cc6d8f5ffc4614d8f87671d434f07d0ab4953f4d43d797b37240274f555ca8dad6fca5462837c4f5f04136fb2f3f7f6aeca1f76ed4e0ad7b723316ea14803fafb62556b496fcff397cff3f6b88a45b177d585d2d53752ec908759293341cc07b878622d2d0efa910a2f9a6258f428ec67d1de611c35e7a7c33d363ce8999f583e9c5f8d2086f5f12f75e8aefa7d546da0457e52992b3ca2b729f2ebcd7af7694b0247047a0bfd63b0ef450bfceff14d29e3e7f7e88cea8a5724d07d9a35afc5ab0c7f1b54a55982151724a2cca2f1b650d44df735fdb9a3803d4d8e057991be290d60d4ccf2ec279be8d84150a1e9dbe6b0c84984d8c53245ed76e5856191dd0ac228f939b034aff088dac5b4824e64560e971a8530234e9529672fa05ecc528cbb1f5baf6345e4df99b9200bc39693fdc42f1432a8cb9726f7e8942af6d86240562d961d140631d41183571973c268cfa5caa38d78cc7807295465f88fbf23a20763d33fb72fac1aea3a84c7462ad70a16cf95951cf2a787231cc56eec4d9b6f7784c41358df081fab65c73b96bf20b6d207e38dcba0c5b7251cc82f9084945ec0f76f715f1aaa8cb70372472064895cf20b6405fa10035720e71b239707ec4e9d8f403914fce06d8f7569f47a40cbf100e62cb74bc5f58728a32870ebef585c032f8aafa0351323eb0fae19277b5a3aab8124f8016294150449b19763cce3fbc45f2977a338cdb7ffbf88c36b13b659b60cd5707332ceb7280fcd586ac22a9dc35b3959bcc277ea3bf022af10d791c2c80b49d9f33231b0bf612be2df1a869a7221dd1314099ce6593356ef83ee46bc979bce75804c9ea64f6dd2ee5adfef2e1efc45cb2dd321175edbbe3b15671b8d72a23f742a8140f720dd1fc954631d3c2261652a418063384e765a8a8bd48a864db32e0f921e5327221b7046c13b718fd5e6b382732dea8fd520eb9f574607146b4ef84617e3613721dabecd8b312c1c5a5f788c5c8a1d976a23c1f5a04439f2ce83ec2c0c22d1272bf8e65a52dc18e2990c5559f121691b76ca9df8f1962cfe65ad4f4f8b258f660af9ca2d5baa8903d2f999b6af313dc8522f3f422f3a206ad279011c244c19572b8077847b03d139d7d512f1c71b5454407e603b0e4d64e6ac96444efb881d24768760aa4974795ee9045272cc829045b9ac559addf7bd16fbc52d9dc751eb7314c81c3b1c302f4405ece008b465782b36af12bb686f7a894a3d98ec00598cf2e4e77a4e72e28c4a882484aa65b7662e97d92a41ad86d3a406f2609d09dde987259e8af8ab0ba784b24bb2319e366b9e1b972d5d25afc222239eb3fbdfbfc39065075ef285fb9d59f15de0dd05ef5d41c3595c0d6c4d110a173a580a609c04635d124ada0e68117674567a07add1f59ef9510f844da3efd42a048aad6f6225c728997d13e86095e3580ec1ef9bb52f01e7f8bfff01248a9f38f5008ab6532547284d1236c40f40d0ca778a48132a305cb95dea689fb2dcfb268263ad8e0c58a72e6c5c9e7c7c44640590501e96a2383d2b35a6addd25eb674dd11c637341aed46d9aeaaed940b89c98e8ea286d70a4b6406807c6d3ae128425b76976b64ddf44d45437f5c46bb4285dc470820f838dadc1434e94eca28473e8dab4bcb6be6b166cc8ffc99da1f80b08971f36f2e743ecfc0c887068200093a3860209c50c6f652b5d309cfb73e6f2b94cb0bbf4ef2c0300c6f7dc3d313443595006edaf6ebd35cbf946ba15a9ba652597117852dd0174daa7306a1fb021ee83385b17b46bcfb37bd08daa0b5281a7b6da7134bdcc6a6f0d6cbaec191975f1023231109c794a124fe448fbf42a8ec4e8712e962b94270cac3526dad400e4f58382dcb2f8b500e72e08726df6a589b5e293dd830b21b3c9cc4dc1aecfeba08f95139be8a1417bd72425de0b23b33f37ead2a3bf722a99196a5d2cab233a68504375f844bb3efc172a41ece907c9577953d8d4cd4b22a274219e60bd307c14360ae9eb7d92b96947277f248d9a702b12fb54224c5eb83e8a0fa9b48f20bbeffd62ffd7031667d1e72dc61d70894cada1ac9b22dd0ad4985ae6306195b0aedb79e15174a65bc188d725c15120ed39d0848d87cbd49ed3cc07288f76d96bda36c2bed26c1a8502b9c4a15865af68bd766d6d9dd9ab1dcbb8a933a754b7ab8504d60fa4584f9f84df272232f896074a82dcff05c3c1e3fde257bf17cf88df38279a71f857c2348b55f5a4d32fad192ed6950f96c5941aa7506333102e1f6d5081e1e5193978f0bf612729ddeefefec6d2eacaf110933b184488d773356a7063d81a58c6454c02eb0f47286dda9e9dba603e35042ee9823ae22e264056ce2cd5b14cde47a7115df29864a6e20219f79836a627f7c93b31236a6cfb61a1ad906ac19f601d484789c0d4f72b369816c56e5922ba57a3c7875abf6920ebd802982f3a2bc03ad3dab35244a4417aca8126c409da91427c4f7ee428dfaa52e0f8710780c456b55be90557d0372dbe8e0ca351adba7735f3a72b5c87547024d551ffb12fb2e70414d5b7452804056c4295af45b0c2942488a8716a1ab312d37eeffe1f997389c23ace9d1126b72647299ee3265d86ac27e909c7e4e8cc60d727f099bdf44fcce35fb4829f13c0093c5a9784a1a1ea810becd7458892c220d8f325c6a332ec78496574929d03b3c837f0b54c910ff075aa8a24af808bf3b1fc745aac6fd5024ee5d4ace1f1f7872edf2663d59464aee70fe49de3d1fd39592f1ac368f4eb21e3d3401465deabd3432a75786af01bd259ee0850c372e336bf22adcec860786186c391af300ea550601a6319f91c56997b6011f938b28cb47604a22f391dc83398c1c9e39f4caee28301ae80591c6643cd70073d30e771106a5d1b0fa9d845091368e568e525ffc72ccfc97a0e066bebaf5142ae7f1f943e1e61135bc0151b3edb0390069b24aa872fcd7cf8b36d3414004118aeb7df1b51715115a86210dbd08e46cb2061f80dd72ac3f3d1facea4bdef359181693ca14059f96d0333aa8a2b90001c078799ae329ea16fe8a1ba6b9b1e1544ab3d71e956f64977d8c19aeead5dcd5be4cb96f46601905b21a33a108d0ea8de685c1be172bd2211c70d5a2f3b4eb150c93c6031372f58b746cad38f6ba3e2b89e550dc6f35a4c771cf472b8d7346e37c985743b57243febc9dc0f025f85b7bdc38a0e1667bd5a07dcfc63a84097584ebe038746f72302c7f9846e222579262ab6bf2b7039307e96d9af3dd518d0b79c9ca63780c720ece5f65277d92cb86cecb43e9d552dd98a6dd2653e38c50654adc22acb0fb495cefdbbe81ba5c34e335c1b2fa0fc17474aed021cb600db921b64bb5506a767275c1bfb211bb02817543814ae307af6c607411c818ceb7b048233a4a8b521872a3a94d5bf52b0a9618305d8d89d7743a15c66f8989b85339a9d10ad239347f4043122a74835671ae7d1f4dc0a3bacd4bdc9f8bd99be9cf72f2c0c61544fbeb7230a58b062463a6be008fdd7f8af0575304376fbf20f5fd406bb23f15495cfa6dc83e6904c3f2335ad788e81fb8bc35760ef5e13b3707adf35f326f90f29ea66b4a61964581a8e59c0a8e233714924b7d791edf0e547be76a1eaead5bea76f60522755f092964f93ef4ebe14773e32bf517457ff4a47d79d643be0a96cd00545c2cdbc413f165cc65bb0344e7405ed7a749834875784cce3a637ab7f17b7536435af3300dbf7e27da6c686054b4b302a7c940878acc1813c4f8940246da39da725bbc52a658c36f52dc64000504fc859ea373686b0baed82d1032fedf5cdb9208f332b2b4ff7b8faf45f54a3c1249d189efc6f02aea111eda4572073ea460f37270502f5a6943012b7ed00f71dae5b105074c384bb20aec973033d635ab48f27243319949f6fb358b98aa779db88b24ff13456ae8ef71e8582b23074be8549e4dc427e5a0a82c9e8a3a6bbc54efdc5606eb2dbcbd8218d0274a6eb8377dccd472711d66870d8753a58bee69f1dcd2f1261b852294ceb08af42184ea58a3444c6b428461cc1b21d528883e826fd7b40e1c5818a708ba5bd950ba4b9ffe5e802e1ffe8c5f92011be00f64a96af77a8b7a1c9ef0c7ff362548e0d250d24b6456fe7255e64ddfa31981499ce787cd8f964d0f5bc917fd541f4865cb932c13ede3ea2c5f5eff27e964028953fcabead63b17ea6b3920b52decaf36c51e3b655743ed6888ed5b5382a123fa56b35e29614c4c9b0fc9a1f6af3834fd818b667cd07f30728ae33da4fc657c1a7cce74293a4289e1c36f1f1b7072ee4940bcca6e04bd7f72f35abb5a15b285beee44cde1d24fb151c5cc723d06bf88603b5105ae34f6d95bb108ef2b62bd70f2b461716f85717fd275b70ba42447878f9e9d18bac2284372a8e7ae19deafccbb73fba79300d950129fff7cc00e5eda7be9e01ba1b5441408a3fef4b1d89169a31cec925621509b1e562a8d9cdf517ce78f0e1882503a28722d873c2d96d57fcd6349ebc667e402c1e8628de22ad4d1db2b228cd6719e463532dcf97303e2f78af10462e88de230450824f6cf10de727b093c1c97c3c8e372d3c0e7d1e010997bdd90629bdfcfd4c5561fee1dc0f2311ac3a4f187fb0c1a727cf370a1cb819a47c70322d5c91724a91b0f39b86dbc3ca17107f48aeb2cdb5d211e01fc4dedac1fd3dbde70e6a0c79a15b24f844dba74ac9dbed82a62ba6272458c7fa6aa2052edfc6447fc7149c96fd5464eaa47e377df39ed67360f576272360b902d48b47da2ca4729d2d99ce54db21ac954e581d6162f9a35b6120cc672b88cb533da947e3bbe5d05317081d74016343963334d7b72474a327f9f442e72030f4f7ec1e4eaa3b70cd7f1d49ee75c0867f519593f2ad06fd5b1933e533f727c8ef362850a04c356ce1fff0388dcab468b125e13a9179629740b819a11ed39254f6c83daf125ce703d2d57aba94f6dcdc2ced77371fe6687f3ccd62b8de67246e8c38706b774753ba99af34723a66c3d73e008ed920e465b1fc95887e31772fabe9bb026ac754791b48a3f91cde4fbe3facc9f4f0e41ccd726959936b33772bf57cb4a1993647ad67a8a52158aa2507ba44da6418a908c5e70dadef5d13372048802e04303164ff7a1c6fe2eea82e17c9662e74e4381054199da9e61140c0cdfc8f797c4e815618e1aa0fb4ca77e29053d6fefdfba02c534e06533c368c466821d50866a15fb2e3c444f8e0bb82aee606d8d9c7a7e3fe4172aa47d2fb8aa65831461e9433a752cdabc49426a31ce6c36fc40f8ff37ec0dd8867dbd493d911a43f064b8ebb1b4e69f7851d8575c2fee766130b8da2cf94330de6824cac20f725a4649499e8140bc802751b776c32ef7ec29fa4344e925de4b6e944ca0b21672b3f965bc0c3cce4328550b90210708c86120ade0354e679538d0d7eb999e39723f50bb855b496fafdd5b8efe273fed8269ebadb885983eb1a850584d1cbe2c72636f124d9f4bf41d6a6732ee5edf4173f152ec1d479fca14826e0a3461fbef72f3a785910b9199c01df0dab127d218a80965a293db2dd5602598024a30cef772c41be2bd875b262961b622e607628644dbb84139527e292f45755c8a7ffab272c7753b8e5ba81fcab5db5cdc0b911276ff384eeb5dfa94d9ad9bd82cf0a86b72bec64cf04e07cee3a4361b14a51f404782b0f2b43b3f5a0544311630ff151912874c063a18fd17623fffeb19e29f0c1ffc9b6d86bd76dc44f4d497dcf344a3314ee63d5ca52f15c597bee25de774e2cbbdcf55bd98e6425b78e27d0332a28872d09979c1e3cca7c508f05dca5bfe372ef9203e918838684f4862513cc760f12afbb3d5beb864c5e92d69c965d7f9ea6a2968306fb455176823d2caa4ec33251250109b40dee558b98f22a157b544fe8be7c95e27c03435565aa3da3c7afa6d72beb1ef23fc5795934dcfcaa768daa879a756bc6a050fc692bec794be1f8782720190f46ded5d887be107e3177403ea01d77be8ef3fa7d9c5092f950aa88e34725cefa37fb755ab410aca0246c5af3ea7053b1115194fdd816dd51350e1992c7294f74417793e551d5082df8958acfed1e2ac32c25ccaf1ce7a5a3c8927446167c81e78e6db8d492f99e1293a78625b191b82f9652f9af6e1ab161916ba96db6940b8c50047f517e9afc562de563f495dbec134ef0873193006d3190baf734d5f8c1c4c9f35fdc1017125ef8f7d646ddd99bb0b9296f93338822dd3a5fed4dc11e13b6fa070c0f4cd71cfe4889510b5a3a3c940254c631b2dfd3289ad12ae9152cfa4eb2c8daacd1cd07347ca67a51aeea74f33c80388ee9d7c182c0b41a1c6721ac5869ca9d48e44fd32fc55d246ba381f3937ba7fb9a2928de83ceb5fd194729ecc25742816ff648944d8c2d363a437da204d812824fad63419ff76816fde72e5ac024c31bdac0bc90fe82db519bc1fa50388e8679bb80bfbfd81c8f26b1828987621beb26c0a68ad5353856e69ad8fa3402858fcf4f5d3653c1427e877c750100983150f830e1697f116ffcdf52274db8504a2136de4177b9c2369f5c357721a589a54c3a31260c88b42c1576f88041ff902bf438d835d340d157ddc38e372a9a2fa81ec2c9f3b68f125a6210813b7987e188a270edf952a1b8475de0d93554b182a96f8d7be6620844ec4216904683028a3ba3e48188e707207afa63b5430b1e6a6dc8ef434076d1fcb6269e5ad347bf8d3094d6e0f9c89689c01dbb04872bb02720fe25bae1d352ddd68662f64b7647459880e5fccd4cb565081539c91720fc764b2760a4f68fb5b42d23ad653841175d1927619915d7481ea8b658b4372b4bed9b232c71397eaf04b92ab4d374aec72bad86f101bc243c51ce049d2a87238c372559bf96b04c8c7c04a5946354b7f015dcd850316d27844088239fb4472d33e89b81d01d7091b41e5073e105299f902c52aa7ceace3bcc7637ea0700572365bd7fb26f6dd1ec99b167531b67fc242259e4ff3d44893f57d688459ca2c3815fb10ed3b507e5a0a6c1388acd6b7bd8db22e6d74ba195efea92aa32a697b5efa5e77fb945100f63ee54dfd6caa18a36c62d10ce3909d783c3b10f7e854e872f173395131b3328415736bf369fc1f0a5af470b6327b4d58b188581f415a8a72249f2afb4c1004dd68425736ef187f08e7b20d3b4177e009c0b31d66906bac6b82959e206f7759f483b124c64bf842f86386049d3fc2b48c31d5a3084e8de861bd90bdf05fa4e6e85f167e8972a77d9780a3242a0e156d1c406e7ad4c9bf1b7232b2004608498f539089bfdd2c931bcdc6e82b3acc5e4285e5dd3fd3264d7f29957bc258c7bd7992123dcbc6635d2d563878d85f9791bf53177147e73d59d12beb40090319fd0ec3f58754c1fdeb1629b7693665a33dc939c5514de964cef572da1e581156dce8b123517cb2a0092b3fc84d2779e0c7bbbb46202e353ee0ae2718e0047c4359405aee987d62848c66caa221ec86eac1bc5ed41462ec41a86472a301d9a1836e01bb5fd37bb14b609c93a9f05ac173287534bfb1ca1446b516093093ff386b1f2031aa86b277d0f6e9a87765f29f4e6b7f87cd3d4c76d7d457728071ed83ef2838ef245f2b87ae55a2d589edfc153243dcbd223e02b97adee7722c55b8838e5da803979d571916c2f5c24fafc9d3b5a74f9510dddafc4fdd1a727fc8060a0ef2960e981141b96ba3de56d263e9cf41aae50e98949aacf5defa720befa7b5b221450d2f1cff320b7e69c2b86514d8ed7949c4739dcc1f1c62f811e23eb8f8a3646ea619a9b625608f20c9f0e4467333a7a0ad64c331ab4cff1b06a770d7b1585ffa7b50249f75caf73aa00966c25cd6906668fdf03b8c47302d42af26e8b3b2590d84377f4b9ffb4a4b4ad5021d673bf71c4293b75d2e85b1a01cf37ca088efbac171750317e857bdd72f03fb66adf75506d930424237b59e365eb7a11e6d6bd3b2f175a8db47a999f8e7a3eff00cc4e85b5483565ccc450ec9720297aafd16a9b130d752afb1ff4d87b540360ea342e3dc2bf7c1d92dbd31d56dafd692f492fe64e38e1852900f793fa00129935d0b72a3b777d33ff3f731e12b5ce7c7cd178ab540233e6a601b1e1123b3b6ff2ba2b83afaf2d9f6ee6dc09a72e8f7af329da420bc99ace25c556e05f7b4b8bf3e4b0471d287d76aff81faab7250349498f84ca7b61c67fa409267330c0d7bcb01dcf8c08b9b7718aae3a26372b1fbbd4850858047b079dd2af90af3594d4dba17afc8d6a3bde450625fd4ee72ccc213a16f173dd8e1f6bb9f482a03afe2dca5a4c4b12d0120310a7eb19e46720a31e97b72da408be0e72be89a452501cb39d82a38e03c43e505b4093ed6f914848018470154446753ab6ba8f7fb889749a71c692d6caf21ef2132bfcc8ba372437f05971e6623be846317c2156e08064a9feb1a9bc873440f0e37d4b4a9ae7248e9462b47f05895df0f032f906f750f711a15c2676fee5f89354d43291de85ea3f767e7166bc840fd4da60deadec401ea1e4fffdc502c6f2ea7aba17cbae672ea45b9b18048723227d7d0e2a27869968593279ff431ee826a061ea1fad0a22839a546721fdda39108d843a16faa1820781cc511f7b558f00eebf1cb413dac71dab09d96e6aaeb362e44421ccf1ecbf137641823326da4ddf4409e94c916c4399a0005f3d6eed850736a72910d5712bdf678928b430edd4441196a498025b04383859166a300c8c5865158e09ad6f2bc0a526ae4794a3db7a697346c05b46e7281c74913baf58c315000a61b1267c8e5dfedb50a32d50ba12350c6c654ee981fab1d12d4c6fcdb316fb8319498a95bcf7ae3dc50d878bba8164ba1c32b0af0402c363c9adffb7939bfe5dce08e2f14be1492c1d53eb1815299f517a918f269721ceac1f67d26b1e2b9ddc240646642a1c4363491cb6cbb627e9db26bf39bac724ebc58e222e3a41b87a179fc77315de2f09e63149655d8ea748dfd0f7401e572d14871d6821996a503b5fd9e61d3121f45c6f892fe8f78be476d46e4a5affe62cbc62e010b4dcf1fbb03fd37f800d181b0fc56261113aa7d8f639a5d01147972a72cf1efdfb18c288cbcdfde5ff3207edc09a38ccd90f1379c6eb74c4189277248158ddbad5d1b87fbbd0454e66f805bd01eb1deaf4a73f17ca957f392fe34688de76da8fb0114feb89fe1e241cca6be067078aaf75095c57a6c415b07410972c66fb3e2a91f37920681587d913185a83a79f375d1b7de573311ca80f238617272d182912477f1d22b23f8c72178c5b795f88db5c28e93887dc1192dcf0f3372aee2fb5d13719830dab97c116250bd900a7c0fabbe46c3fdae694458761c4e72a4460ff8ce8749b61b42115dc0c32432463a51e6679081f2ef92b8b7ac16cb72baff12d73505d083341cf859865b300dfda3dc3a55f2e002a7bd1f28054db6723d81e7cb6b498cab8264d0560a1bfdc4ea6a0f1b5b663b81ffe3eaebbfc7e872e68d094f0822c0656d5bb238d67a51d24a59af445b526fadc0c0c630b7319272258cc548f75999c944aef04ff328742e20be99f1a264f3e10d7a7fa498bfbd3c313447ee21fd5895ce00c3a121e62d814e48fb1e899f5b3048a2b7f12c151f7271708ffb6ded1a7b9b6fe315af662168aef8566a52dc3d7f6efb91abeb10ee72fca21bd26c7f5ecffbe735898a1006021ae3ee4e75ea7dd19e7c3ce4e443e93bb9b24e0463fd5707593d754bbfdd56eacda286f8ed710977578d816e00491a72d50171192794750005731e2a870eb21f73d1119fc5c312bc3d001663c96b137238eef56d9768993f83b749a1779b7327c40e46e90c7c3701ad4f77ae2a32d055660b8a2ba5686a0c9fe9c37cf61e9697760865a94027b48c6de9d5cd8a0054726ae23532dad1a767e60e0568e5435c699445e79c14a00821babe3650c54c5472cceea4b183bd589a31d7e68b15f01174af3ce842db1d74029a7aacd8cc5a7472d414607cba197967553f78f9c7d412ce2e11e588d79161978a2d829335b01c724de0bfe17b38298b5ccf5fbb708fdaf4dff18b61fb425c46498e9303e916a672ac40e42a4db5f0be85ee0e14e85c5a7cb9ebe9e1eb613183287d5aaa308b2561b29b64c05ac946acfd2b0690aa20d7572d656f56c807ced207b3e582a0c93b72b605e3a7cd266e1d3127d8d750671f073a72595f79d90dedb5bb92e83d3b5f72c30548a28e63930d09fa61b63cb2f457db5b03ec70131ee3840c1c9d1dc430720b0c9df290c4b33a9661ffaef2e1cb6e2191ebdce0c9adcbcd635c691b35b3726779e231e2d1c8d041db74cbd6a518d41ace83d079313eaaca7c55f0cc148072d7b44ea0fc419dc95320338679c3be984ef98e2406dc3a6f1fde3ed77999f01a10a6b340d4014ef9d4904ee892bf7f2dc70ef652697a90d65f82ab718b6d02722bd397ec96f238a47304f01871d4887b65bf44725f393932402ca8a4a2460172da7a68c399db3decbdf222c2bcef6375d4f0f528a1e095b67ea831040479cc23f297b3dd5e0ca9bafd1e486110a6e5f30ee8756a8380df70a8af09e724a99408976a7b073f60cce34ba4dcdadea006f53bbe43f818ac51df5e1dc5b969186134d79c0956c38528d0da0a7a9ee391f2d0d20c6d8de1d3b734d65c77fb620abe2fe1e519e59b2c3a13de33d36548b25b8ada54316a7028125311ad91112b0a7d72fee8caa98b57afb5b3c7856900496299fb8faa5888608719a28c5d7b2dfa1072be649ef984439df70bab016bad02f3925249317ccccff1b22b713cf08699213c77e54b694e626ddc933204cfa14a83b3119f99f81693a850b3ba9fc48baeeb72efd89a0e73c8feb220791985443e3bffba0f69c15b369477dbba180c5bc768720103f030a9cb7a26ba96559999919bd7c15c4458ccf03c01df0ef0efdfa33c0765e3e02a0551c7fda48fe69624113783b9c6f8977a865107eceba2cd7267d95cf02aa93d57098195f44ebfbd51e16a9ea98cfa5073bbdff0192fa7e01a67fc58536cfb75d0fb536e955f745a9c6deae7b326861e12e91f7747f32b2513825017f377f8b479055dfdb1bae5573dcdf41ef6f3ba5f2e24805d058da7c22488f172a5a969f9bc3da04fb96c8c1d0d38d8f4d0e4fa4a5d6f332f350f9edff95e8b61c010cbb91614cf98398ceec7f34d4fb8f7324164d4bd5b6046eee84199a9b2190fcbb936eba401edf7794a72be61b22e07dcfe01f543c08a3eebf5a1df668072b768663586ad3f34a17a01f3e23b2195d48551ccedf6e05e31f65a09091fe253d009fbf4d431f5e58deefcbac08fa76e54c580777106baf205c91001bc8bda7267ef0fb1a5fbfdbf2d616e2324fe113ab468b5dc7fc29cc7a3effc4115d6ad7281f513b19229fb8b6f914e1ff82d0a062570f4dfa79509b83803b7ed3654a9722c12fc00ea509a5d1d9cd557f79285b1343bad360c78d4d9b080e25466c65b723aa72116979b7db04f16ab3bc0a2a52952fa067c6d4f7dc52157c68e49dd371e8d929c9ec4e340ecc8ac9b8de2729b61fbf1b1284b34abbcd5b2d21be5a2393edf3893079ded3d4ca8a27bcd825d9866070238232186c514782a9128a22a7f1aa2cf6d2a3d72180cd04a7c105c19a8411d4ba828e76fe19d013519814779e549892a0c436d8fd73b38cc8cf8f7338aaee150c7bfc3ce6f2d1c6d70ad19bd7472c8a1621254a998d05c4d5dcfe20172a41c4455cabe6fabe40638250dfac63172b9e45efb53ea373b95c1414afdea9eef72d86f31f1f6847cfa99566b04a749607df708288e425ed3154404b5086881df801d0ca0bacc850d04724c45270183575615a41ee2a390f697790b2c2f6b03aa59905aaaf206acf87e6e3390b85693724e06309474acb7145b51a049e07b77c245fe395cba627369d8d087e2d19ca671e4256e8d6b2e15d9f60f59b85670e182e3eb4f641b9a3b82c78b4c0e7280341ebf41c2a9a428e3b4778c284ac0e125f6b4db786959e04a5d668160d515e291308eddd60c580f255809290fe3849d1582cf2e0b97495bd4c6c1e97aeea3f6e5727199793e7110e68a164b6fd71ee0b67fa97b04459b3761cfed1377719005be5b4088d699d8df239a5457a4490df4298482acc797a92a0ee506fbd6034cc3874e5cef174e35b840887aacb4cc56ef7231fcb0859ac01f2282371e2b745d345272022923090390e923eddea0cfd707d2f04811d3783cf4407cd040510cab5f8e728ed5f53c73c3b2fcbfb86a42849ae0763c76b3e27fff01493a8d3f9409f1d230b999cfeee4ecf17bf06a9569b8cee1211bafeb38b679e64acb7e8966d94c515dd04c1bffa8c1df30073778d527dc61cc32b86684dbfd6ba8b5fdcddef8d4db22e158d88381c10d21c89b9c70a6b54c04f216df83051b151a5cddfa59d2d9602b39cb01ae46b63dafbd3e1be5e1556689727e01bfd49e8f40c7267190106f8868c11573981b2614e82270bf09f8f024fe8e904984f57115abd3063af4d4d604475a4cecae4e68644a1a85ed1202d94694233a3e652b9c9b3346fc2557dd219e72f786e0ae1ba84bc0a8f10ea9d545f8261acf492917f15dde5c660e54b78b89514f2bee0547cdd568597d6303aa74e7a8d966ad74b2345ecd104e9428a4ada4720b5576c5803cf75a779503b2f982a635a4666c9cc6112d023f2e9c5a82427572bb2620da1119934108588566c70b7886337398f8d0559f43236b5fe8df24da72acb0e9345b8039157ea0eb5c5b4a4f28b0b0f39fb71aad4fbecc59f27690d24c6edb2e574a7586762fa1b973ee4bcdb5fd362bfded3df0f97c85887b30c09572e28a48867ecdd21dd11750677f631eb5c411f0618695102877f0e7a7022f8d4cc6632426a3e4aa632acf079e4b68f21ff28a8f02e3ab88feb0707713bbb14c5e740f58c518a7cfd2d1b454db89151b99e78127cf279ba48c3b5872aaf3eaeb5fb716728acce5e5ddcb99e63ccf25344e7373af29dca814a60e89121c8b668d38ef636a41082ee78b48666529769e03889668bce68efea28a035fb315d542f772ecacdb5c8ef043edf3ef61f16c7639d8e85bfc0969b19d66f110a6cd5f67f7437288bcf1129244cce7b0098ea10dfd2e07a41a53741e389d1191018847d0a9566161fc76cd304f667738d7633862cdf0b36116839d30a2bd5361847154d3702ce549f7b5b48bca1984827de01f301f396cfc1340d25d4b6f20671307aaa2bb2c9de3d1814fe78302db720e617c4d74d6af37b0777d9e6518f3f41aa9ff19327284378892e8315c0d32d2e401ea37b52ea669eab77b695a313d83d9a0935fce23fc51d31319910af5a2c6d5ce217b0a3c5cd9c0fc5fcaa10408bb8f89016e23539a1496bcda7f2f122a2995443d8bfa6fa4cfc9dd3a727430b8cb1f26cdc0ee7264ab4ade5d005b62046c6a2323db7869f241c2c77175eed03b493994f5f866726ef58788da4f75db5a9f7c29cff0ff4aff970c641fdc36cdccad7110920dd331fa3c0adcb0675c3682bb0c32a23ed1249d726cec1827efcaf21fc83c7167f4189fa6ae72338215007a0172b69364cff7d011eb38473b457408b6afaab3a1c9729358b37682959dc2c845a4ad07c3c8362b1beb8e69878695cd84422afd3abd5112f0cfe9c7888523f38cf9a48204f92b50627ae853b9977a5cf43459ca6bd572151bba37ee454a1fde0e963bd1ddab35d26d1f7ff3edbf5dd7b6a7485c11bc725a0f427a0b97223e77a507724069dae5dc257613d225a5ffdd5ede302ea9be46719ad45d2cf53c5fb3de2c98a700d4d96ec86638cce8f5d970dff6bf5bbbbe1a3e9b6b50bc39a3d61945f01eef6fdba75710b30589861fa58f4f9ffd6bdcab72baa21e211be50d1f826081c075065e57657109c2af96102ec11a2ac52e768a12061edce642b4006baa4ad355fcfe3f82cba4df76fa9d63bb5fe6469d714c9700f6e5097b29b1da8d530ddcd56ac1677d9ef028126f23bbc14463f6b080d7ec38c77bd3f193796c1eaafef6dff82309812af7fbde8a28bba9c26694f00d1a1872239c1ede7df503e59dabc66b12f489e14b835c458e1d3342fcd1c6d151f8f272eb6c4ae6416094069b4a61f9b6eda5ae78942cbbe2d6fb9852920b50b2dda172588c583f180f200865f25126698a563c0f2d1ad5768cbec1b6e85e19903d4d36e03066259e38c571de8d19886cfe41de0518943fa731e0306082ba4c939e8872497091ac85da9368807a362811104fc807ecbda022a22565d5b08e3f61da626dd9fcc5066d592ab454868621a7bcf485c3b707b6ca61258fae3d07557a713672273ad02d2e742e093ebb7d8e705906c1c57ec8be85c744da5e6b19fc29daab72c79d0232960110b7e75984d898521364c8f7310f40d7df083952223c523c16727f2d0e53fa7d6de081f556ca53df6e24eadf4f45567c7a1556620e24cbbfeb72a04fe28a89a68b2e510936b8b812777aac64b221915bd08cbb3221bb92147f1b7878c64d7ef73edddde1840645773a0bc521792469f03f9aeed7e93aef9253187fb10a92804512a23a54668fb2fefd5cdc4fb9f460e51d504b68114428a4ac6b2f27ca3e787532e1f5768df86494318fe1ff14bb20d0c20310d5c964e28a926d97e92c7a0d469e2620524e8e2c9b043e4de72511b497944188949202e8addb7297779ea7c7bba40d63e2a55bd0d814d12e0cb44009bd270cece254959f5630727165b3333467aa5e9583b0fd8e705bac27aa61320ce2e8c2f9ec522bf065b072810161b4925fa7d695ae95ca871e775982781c98d45771d4e1898c3b91b2bf72a6fe425a0677bfe889050e0a875b7fad9aea29371c1637ea8c3c48f8d6cc10220024cec1913bc325cb4fa39ce667d5d4b300417c9a5b298ed8d784ed1ec668722b4e11d42d76fe142e4707d02f663d59d06a02557bdb5c4ce1a81098eab5f8720c19f9368895481b2d88ac98da23f8592c1dc84d64429208343c59ef09144c3ec8a03a635e2350f42ac5c8d33806b749c7a71ecc13ef6755fdb229ddabcec6517895fa0e19bfa7f34ad482dda448fee5d04409a9c0ae966db4968357471e6a1f998db0336e17b24f7c451ecb776b97dc80a62ecfba64cfe02443fdb4d52acd6cd1cb2c028c9eb89ab13955239b7799bf1a8aaccdb1e8f0ee63336b4db5ebc52fe48ebed1d570d8ac8ace025cba2e4d4a18f446c5287a20aebc024a147a160b7249297a81fd100b585f8ae30e36ee25aa05a727543988f29b0b81e030ac48387232f7d9c8274504515a7c83c45965e3ce35764c0246f15b18cf103724b3eb9b2ed580f63cf146705146fa2e37b7b6c5e5c6c67f22378380d8b7b0e1150ac579724ad88c568a885b28e7d5a157abee8bf97f684e14dd3a5aee85511824387343450b22d8c05d3204ee1ccef631447bd738be3cf12d5865459085ccb86170f95d7294f03999ced5a6f9ffbf03c91f5a9a92cf38b41d704e2fb99d8f8cfe4db4f572da6b9858cd9416f28578a73333171a53aacfffbea96150eb9b591239f5b014702f0d95071701b52c7be28e7ac9e637dae9037a9b42d51d5226eaffc54a0596725011e2709ec2198a97d5cad1aeb6e23d73493fa863724b61ca18cf66832eaf47f92c32581616cd3228bcd17b3269d09aa6f8ff5fc2c59d6fefb72b1a18799772aacd431ea78d32afcacb92e5270d8146178e98bb931515dfb6becbf4ba471f727574c42fc7f32fef2954fa732e95dda8cfeebce605548182c5baecdff97d7972463db29ceb21e1dffc764e36c7e70774f02dc951c128e0bd88ba48109e30391c006ac31858f48d2c398e3639720784ceb43f2ff9f86c4670acc48c6d6b2eac7226198019debd34cde2611b23788dc74a3835e5688f244838e94d155dc3a1344c615349b5c13350e004e58f6b3ab9ca758ef70aabcfd536ce65f1f25c191d11728a1421d53681aed4b90db267b9d49c2ef5ef5ffa0cec0185ad3b144c7538d97269df926b50860b2db1ab6bbea49270370bf6b998f52c1974133858eb2a44a172cdc24a51e2cb5eb0fb63ab4ee31e24d71a97efb0ff4cac640eef71b1c884d116ce944994b2068eb6d0987a60a9ccbf71585036e1acbe7d7becae55bdc5cc61724c004b5c6ec89ac698f7d98581ebe182d185b50707d1054fe99b378b6d544623f7047aa9bcfce4348651fd0ae642db33f217a33202e71926305b1f64d90e9972a2ffd22455560e7d82fe601dac75543d2c5cb9605e82aef7132537578a0d2e726d3a89e9ba6b341f7c106f34d4c2af4bf0028c038f4ab6fc02b927391b0c0a72a9fa86aa6b83d8e76dcc317a0869b3cdd7c37eac287fa7ce9603a0993ec23f725b4cf79d9793dece129d142f9b8897fd97a6701e0e37e67b196addde8a6ec1725e94557784457a05eaef332828146ebfd47afa82f9b75dae57d1ca5b20965e31b482fc98ab8c1cd5bc54082041cec90f39852a95d4c200c8532deecd1cb37172a8802c9fedabd376a041b4b4052cec10f5bb5476d8099f7d383dd4f22e93d372fd216579eed90e6ea0c4d630c1b878251ec652d63295287086abd278042e5e3fb07f0018ae537693ed9a3c644e2a5ea77363205f59d1a7f80272a748c89c8b72de9aeb5812305937a5576cc7d80e051e24ae6435524ae0abfd8bd351975573723df6177b2f3a944f4cd93e6ffec30f15d83b46a8c42ad87f966d0f7e082d3a723bdd43d3abd8cf68331ce60ebcd699c513469a71053b5b1aa6138465e80a7960911ba0082db5486ed2c40497b330bbb2beab58fa0b31f7e23578168db7d5517239d03b44a68aeb35e88a0f9e9d043faf17010c710fc4826cee955f389701334916da5a454ffeed8eaff11d429930200dc2cb2e038c62e0e2c80edb6b6b782948f2fc9c14d1ffe00045bdfa58caccb257e0d5ddb7e28fa24b9d5feca814b08171ae74936813938ff7ab905277d07356dca94248ee7ec47dcaab26223b2b813c465e0425719a08120fca222658e0835fe42d88ae6703e08702044340c8ce7c575d9b2fd495b814a9cfea0d4a3c3df649107ed26ba56e08fbdcef2853cd7413ac72b3b719f9117f96dbba46774bd773ef37434655879b8fb356cccde06a3cb250723829b0cc3c5b0f3e72cbd5463c4e7f2e1d1d5528a5ef3186ad21fc5ca1125e72ecfdc11beed0c0fd65372cecdf928ed509988bb28029573f710caf9ebbb22551b8fb2544d38383ca3478bd801c488dc0fdf3e6a097516a5a338bbb8a7d60720df582ea1587266ca78a065a87c0ac049ba1b0e33fe85b1c45076715fc1d1c5b721ebf00de91d6b2ea2a14b6bdb97c628e958f6c27d6efd50943418c736a93be725ac95574e7de81ac74aeb142858ecb6076c81f1850412db84ef3a0417358b7653160d45356bc10f8b753e658c31922de816982395c919257b470ef02c95f9172c47e8e1f14aa728d3f434c278d59fd31d62f1a63a90ede27c5b230cdf70dad463286c79d26a739e1aec9c8773033b333d30e589bf4df04e27274e1e1b732604d065fbe5c01ad7c67af1aac7a0c24ec594956143a5978e13aa4b470cbc0d4ba728f9a52c82bdd97066eeb5329f435859259fb396d7ed454537622ccba9808e30daacb55c74305538a5b4da1100dcb3908f792eaf80e7c78e3fb198b9a0749f3727ca197d4425871defef5ac2dbee4ba6b1400effa6dac08115e5cffceb7d3105ea9ae7aacf02d0fdecb5ccae054dc7a5a24f7a518a56af3e9b1998f932411c719ce0b77ec6c86d7e92e3fd5fe45d735bd82f0f7c6d23a434357e7464ed06c56506497940d26288ad66497cfaa09d4f3fc05e1773465b51577b724accc3903cc06e85b45a6eab4a755e2ef6da78e5892e2ce008ec4233756310aaeec361942e272e211c1642fa27e423ce37dab6d11dbaa3bebfcab0687489beb07f220efa7f5729070f6cd3ac3c8bec14caa8f3f9f60f7438ed0c2c53c91d9125bb898e6993c72e46ec3870cebd42aa8f0ed1e315d25692cc2ab0f0decef3cd20da7de4bf7934b6812ef1eb5e8127b8a7f42b3f56b080f20dc95953eacda826f9e0251d4b7fd723653df41eb7eace85650a0142e32e446e7c7c272c3357b135d5753ca076a6b19a72dd647cf3244ef3f88195c4d106a4992cf188833d2ead265ef8f6582a33772e3bbf5733a06eb8ba9fd6ce9ba20dd442a5184fdb2106008a00c9c94b0548d649d2112ee2eda5fb95dcf57752973c61cf989d33df14d2c003ebdbec117d8ba729209432088992470cd202446b1accc90604b8c98a9c04d446abe2b0cde57db27f685972bc56a4d4f67ddf0aa24f8f063936da074e86048ab5ce4e96f381305091bb8c3472b8fca1e8daac998c4158ff772a44bd7e43440ec7c47b91acf0bce727d7af4b620666829d559a5266aa0cee4369b8ccbb19cbc05de3574cb04e01938ce04c22a6f9318c78d8b7f7032948db3c5fa0fd63b7350b6efaec033807af97225de0b83b2d307f7ef2295ebc8453eae38cef680da8123a76597d41621124f2c0cf63802de9f500fd508f4bcefb883d5246140c97eaac7be87df08b00fbb633a49e310f25e35326cdaa8ce837f053206f3118521e8161444f6ce38550435de1bcf944cfde27af1583f6888d8c63ff9bb2ee6808fec84ee5a44e6ae125f3483443d781249da6a285ff8bdfa1644c4f8650c1bf1a0e975946653dde5fb5a8fb872284be50630a1ddff75432fa9360e082b8bf316bd00ea7ed45ec6aa4120f46272567c46dffead00562ff36c2128560200721c7f1f9a42e77473fb0e74a7a4c772355ec4755a6fb88f22d518ab003d78a52ef276d4b8d506e6852910f74155d820a7f16974a83d346d024721f553c099cf55c42796d73e5fc716439436d113e217fc0129547c5bd9670b48f7163a61352b403868d4718574ffa34b97a51c066d720ab9a3ebbe748f26dfa8853be13216455d87cdd19e7cf0a3f80ee31a676cb87229f7250e36930cb0d829db715a5e5f371f7549b0dba101a536dd6b3c46159c72f7acfc02a097954e96bd9178af740b695e751962855173f5204f861e1fa8e572bcebba954590d3a14d48ab05527b47706f9a0edd4428286d18c27a1387903472d5f89894adbcb4671fd65b836a8620203ca054662fd3f750076df89fe01ac372fa465765e7fe538b1748d3d5ff333322605125ea84ca624e9dd59390c2f16b72d48e970b0f05f65835530d21f7bf2de825d594f066e04decd116b5c81cc01522bf9dde04546b69aa62c69271c2af0113ba42c474ee3e176ff8fed47526ec5c72f91ec59ace701c073f8c292870a338ceb37c17a035ed8074fe68cc7a4ff18f46464e4828a6caa9a3b3581c4a0bb8e2a1e95920889f038cee55a0187460754838a8ebb79923116f39ad3902eb7acfe40a9af80534d56626917279d3cf5579f94337a1403c08d8dd5f55475e36b7514768b53f218bead3605e6ea4c7c41869c51379cd1bafe0334f2104bdde8d32ee6a23706819448975d3fb361b1549162f26727317fb0c9237093a29e948ab904d480b9764b544081e46bc7a62e08453e69b56080f44514abbc338367f0f16d59a111200e6f20977016a798b22a5cb40bde172aed7a3f7f1bbc9cb5d4b6df21be6fba191be8b6c5b3b2c3f5c4d13d049087d6e16b1e70ed9cd3527699671d01816626c2e147b3643c54ef8c08032be49ea3f7227189a6b516bbc090de47606cfe62af1f04aa28557b20eaefb549cfba7c44336fd3b3308f4fe4b80322d16cb09fd470730b09e65fba2fd96622b28ac9510fa7290b62cc0b6bb33bda2d9795f423a3989fabbefa38510b7e64bec3dea65155b387bce6ef3451a17230bdf5dfbe295a4416eaa81c7169f73810b50b9316e3f950274415e8ba5e888854eb083a3a424a89fdddbabe8c4a540b1369f24b7d3715e72b6b40b2f98ad0ca127b460a52c93fe6016bd8a1a8e73e06249c492ffcf4f2364db40adcb87847e95e135d2c6ec4b40a6b223be7efe148b8eb68fe7a3e6239c72c6356ef464519215867c8bf6d46ca4f407166540390b73b04f18e4d987ea9f11b3dd863496d0489c139a35654516df87a9fb4de25ff05d4ed4e8e51c895d5c0a9cfe1dfcdd3a69248d3125ef6b8666aaced8eca250e7dab3122f647516f4fc724ebeaade66c48993ef21f6d61bbeed545f68f20bc528304f50ba2b6b1fc02272cb27a69d5145ce5398ec7cc4deb6435413e86a4c2c5820eb546a6013e18c0d729a3eee412c08533fae14a6b963894889cfce14f6951ee4a2c7a51e512b55e202dea309f23f6fe57bc79d074cde169c22fd94beb834dc5f3fce3b267af730537239ff9c1d25f7fa179a561b3029753782f0e3631077b4a223eb42e9317e76e072ffb44dd1fc8412b30fbcff00785327780e67a44bdde2ac1209a5a7eefc52ad7292622b409a1d0d39ea1a81f675c4d308c9cfe0f77fe01804cc2468fd3e59dd72762d8a74acb6cd865a0c5de96364f62640adec7043fa09603b45c8fab82fc244a3f262b1339d9092a74fafd460340f061c4a4869e0bd96e38a4334c7e9d6f7704cdc3aed2dc9d506dedd3c60af384ac917118c1bd9d81936eea4ccb6183c717228e706d8c58a0519dccba58a179e77f73b46f4985d0e5b8c9b996acdfc3e5755c98887854b47082421534b6e709f644b284d18c13aef78d9e8a5ee626817e200e9d52048334fb3c3beada326c2f4746332e3506babfda9800fab22afd2cd7d72700fce32bb8d6fa478cfb113f107f800a46f0a9c36a0e2bb350c6b46f503227212175f88b201ece7da1b2d588245970a90001c3416083025bcdc21a02b2c803382699007018573f45d7b948db58678c9945c402852e6e6ec8c1edba396e48072057e3e0a4da5557b729aabfacf73782c945c09ec44cd90b1047de747856b6472dbfec0e7153d6b3d95d77de96eb424c5a241d0cbb7d72e6a7aca4035989a8e1832c89bea7cff15371bec9101efd265fa4b1bdccd43e88126aa6abce57ae5e972d5852ad67328704561f435ca5f621636187419f499f8b1c19f194762efd845729b67183e83ed507a5caf0b9b168fb4051492ff2bee42eb72a0e819e30dfc8b671a36f9576bddad810606fbc1f0d023077d7bb56f20ddffeda9aedda5273d3266b888cf27c065e665049206f36c76640fe468392dcfb5ed1ed944cd86e0788472ed75a94294758b7e52a2c3adb8c7c307349c372816adbc0129c1f85f50bc6a6a8b6f632536b1ef5651ec1e1c1256b581da6f10c5bb3441d14d7c165ea206371444a65540a32a0fa9f8402a235a1c10a937e0a6e85d9bb2764e41020e47ca8e72a11e23eb82c91e94249b94328e70ccf781dab27e781d7c9f210dc0a8fae61072938ed142d44ddcc8b15ee357fd37574bbd6a070495f3bff95d53627b5e4e1c299360945a20af77562245281e816ea5a1e7bbc4022e0fcf7bd5138ed8e59dae721395af9b118219b959ff1043e8299bb8e5498fbfe732a0f302601748d6100572fcb13e06a94d4c81a16aa6bfd63547e89855b90ec26ce958782d5f5dd177a072bd008337402b141b67dc10ca3e0efbb860b2c7748097e22142e818dbb01f1f72d83ebc372ca9fc482fe9246f655be351b2132df36374a68abd55988d5f49a924b82286be74d1522ad24787f0b4a3387d0802fc6991259ab5b54116ec0891d505c3d4d3941c28b9bfeb61e9a9a0d1409fcad3a3350732896a43e454185cc0f71af1e1582fdaa5cc55bcdc0307103b6c42550a4e791eb0d8ee20d22bfd379d8f727ec2179ee2280c7f888ac3f730f99b85ba7c628f486d156fb9f37a963c5c14727cb0976c012f8344a396443c54f082f71730b5a7971c0adcbf4090cb54ca291ce87e36258d80d9f3b7564fbb75378b749880fff753a739f1a7faf1a5a769b672a421befa36cf7b1488aab2a8711ef526d879a41df2399829761eb2c253a76d25c928cd83d0a70ca5dbb893080d637ee3767a634d0ae54b1ae33d5db7a62b0c728f78e665398069258c8f8b7ca981223c3ef4ef692d86bf232af26d8e41930440bc600e77ca36f1f1278dcc702c6cff127aa5cb2865f6fb12fbde0c6286c16d15a021abb4044105908cb5af0fef72c52ca9504b771d9579e9e3d1c96c9e769c0d28f066254d8b6ec6fb79f8703c1d74ca8dc9725e7624334056261850b487e955b4502ab8eb8c24ac577514a0e3071d11ede06876616d415f19da7a56d649226611a18c879e4b47d1c031ff42eb4414851bf39f209f47870bbac67b0d2279487267cdc96d0aa1290988c34cdadd9262fe072cbe647f68ecaa70d311fd3c677c592629449ad9496f76541a86a2e75d13947a74e8ea2de4bf9a71a6a74e6f14ac2d526328e000b60e81a99f19a9336c74a28f682fe63d131215a802864d979ef53db7fb4c09a9900e44860c920030ab3588db879af45b82dd73eebd51935b4fd17242908f58accdea9a5e8f25d2bef873b2196f82cc24cf93a4807d286bab3042727292610ef2fb1e926499f1d9f8d936712f1138f55e6bd5626ad52b037640a772dad925839fddd376759147ea3ff76bf9846687bbdaf06744e29bd6b92655c772e2cc6bd18758934b0783cf1afb58106a892c2e07909abe259f09c2a3b03bfb5ca60bc9007abf69c08e03265d1dd3cd7d2518333e939cb5321803790dc2fe9672d329eca45f6754c4e1a4965e5f0ea2f092e5d714677d3161cc810b7d9cc6ef1020a247ac1964be7be610d62c4acdd5a679e5fdcde5120eeb93035ec64d25a472f1fc28fc6d8d5df1bb284d23d53d761de34a13c73ade97cec917db1588fbcb39ae1403bfb0e442ea591bd4621afa3d4d66451d0b7f965f6dc5c81ca64013d872481bf81f1a81d9353f717fe12da099f54b30d57c6d9008cd4380b7b1534a157271b1bf5b1852041bb5c6b41760f4abb7e11b8dab338814f7e8313ba5d76d1072d7439aee10f87ad6a28235262088f9bdee15953f76e7fae424297325711dff72aab3487ef38938419a27cbaad3fd04ccfffc4f233c6774be477ddd977bd08830329f1f4ca276aed0f47467d5fd49edd39f4f0b902132a548ea69d01647a1a872b6d55f37adc0a91c1403bdd89b71bd6589b90650fcc1e860d23276056f6a9a378e37bbfd25f27e1b8fcc9685bb4e309f0db5d345007b3a293f3f56131f9f93433c3d3d65b9354963d8932d13e6cc4276f27526c2405f4bfc603c27b0bf5fcd54aec11ec56c1ea3b540b8104153426ea203899ba6b785dd4aeae3d8bdae55d10ab648c19d254b44dd560c720bd6b7352f7c98065192338db0807703a090158b48e9ab922eb8660c626564dcd1dc85792da9e87bba2dcd3b1c76d6e1da6be838729e2038f401a46ac3d0135312f62ac4f268d0d75e64f58bd9bb92b9ad7a079572562a8d4aa10c633a1ee6785917f5155035ebd1cb05a8dc4f08b1cb7d570f79534203aa67b849828c7b9c088246694429436145a6041c0924f3f051706cb937334ce4d0cea5db23b1cce2c10209d69d3d6ab7a032a13a9b7e80a12ff87c5b1d72f459978ef31913ebe0edfaadca25f6341037590b2055b70cb1033c1104f15172f3b75d4e6c193b3af51fd57774e22f2dd9e275910517183bdf40adde12692a72c7b6fec234809bf43e5980f462f0b949c408cd3e4e4e17d0478d5ba34f72b87213499572912dcdc82b5cd6a2d273efb1d85e0698555607f2ba2cf7f943e93470b85742fd19bd7199277c85894d930e4cfc1d5f31012d5bf2b5ad4d343877cc291e5d97785ed46426ea7785f2c0afe7a622c36e444961216c5878d44c25ee0f1c03c897856132f49b3dd493785c2dad6196078626fa162c4969f76d141c15c0729108a17fc885aabdd47abc9f0e93273d9a0b3817c4f370a8368f5d43d1468d4820b55018c2fde9f0d1ee4b515f667e1bb419559035207cbb873a5decc8c26230c5c6abfbc363a8a82e3315e2776eb161bfb5cadff6a8e061c409f9b67522ce06cbbce53cd82a044e3dec890e33a83f9860d8331bdab683a2f6c09e267bda361c415bc67b16b88f3ca5435a4314f6db74c0d1b8ad171a5aa9a4fb0bd53122ec2fea8af5e88af8e23c297890a4b85ca129eb6ff31f0a65add25205ed21a363514d8ca4787f0e02cdf52dd6ae27a466e55f1689a5571971b3e02575f69f064e8609b2643af57cd035ff457d94b2403961a2878f29eb050c395127fb26b7f1f9f23dd8f24a4fe9317c80c9fb5586a3355a2456dc334a3c2dfcfdfa075b49779932582aab02651955287d1b239d1f1f20f80f85d11fe0c0107108f47e77c1fb3f067268db97d74d0538da15ccb5c4e613911f52c11e8f3629c696bcb389a5fb6b1f41c0db8b647ce2452854c0c92a3697ce06e0c401b538c876d32e0d958c883ae672471e8b57c4eda5e1b78ba15768100307304e9ab42a4259d8515c026fb7337b729de3d168ad866df3d2935ba3fc92c0ac88e877ea22401cc354250430dd32e872fb0892c53116484e766b842e0f8d5b71ab941ea5c2bef222679c2b057b09f642ad5fb44feeba595f11516ef64306239b7413924a982320377edf9a5f3f6bc2729e125e569916e5915d0f8e475a4ea1c2ca8b654c98bc6cba1c7f50494d10fc72b0c187d2dd32ddbccc060c85f1c2ed784b668c858ced965a997f4a70bc1a1d32497fc54cbcabe5dd0c56cc6120edcb679f5a946c1efb591f951a8c2e090e767200e96abd1ce756bd8db98b797fd87e72352ceac7facf7160d454e49449884372b2cddc8d6912f21ef647751a0253dec75d7e85b8f8424e65e3e2e81e694e5814cbedc57eb8755fadd714f926e3e27ee3e51f691cfd2061ab0044d1f711508401974857df8ddd0eec9777fba576357e7101c4a2f83904358c4af9af38409f1f720795f31b188e98be4107127927f81b21e5cf7002be16287332896578cf504772b8a7ff4116a715829278edc3248899e02370a72aad0d0fc3e27cd58db0154138665155093fd642d4701163002683f253418b43b66df13145bf0af419c1dd7d7253cf62fa33b64919689097d47f96217153433127d604ba44c251e4aecae5fb60e387d6d1bca1e7c639c16b0c727ce6a1b3c0ce2ddc24380a21c4026ef729a63703550d1112a3e3703e33607ca4c8f1532e4af3a47a10f78d698d03f837b6dc727b8343b587e9a4aafd8e27bb68fe77138486865a53d6b1c3f95c25c783b999725879af14aad6eedcc29674c6c03170812ba078b938769e40725d5a35f9dc3872382cb9afe274d594b274fee9196b8ecb0315f9109e294de0d489d59a70f854727ac69cf7bbfee2e20e76163d6d392ad7ed4e3a72d293df40fc8717d4a1bc2672ece8dadbf976abb3347988d15e264e675b4f0f4a82b2a77591b277dbf942f672584c82ff0117b35b6c27784ddbbe02b5770289f9773fca1cdd7683e338f3de400a6e4266dbf5e749f58c4bcbbc6db1e6429f24c0837b8e388865b8a943d57872532cd2268dc8127396c608da5e11350ae58d1e99365a12efd8dc92ad58eba5136b4a889182c1db39ba1e1f07d8632dc4f4f8f7b3173bf3dd9809eb9620e247729d1a7a09a6da7b27c0cc50a09717138e4e47ed3a27082e3ef293f536e8ed6172efa2c11c172065ffd36584c952b4a8ec8eb44d4840c3e13b51ec6e5c25071e085721ac8bb74e38bf1673bf26a9631f0d71074b65ebf3f376bf0bbf0f9815ca7211e4e77b6531a5e769eabbeaf18f08a666898ea7208f8fc3019f3a8fecb09e11da6ae97b91a9f68a6aac682cb2fae57f1c3772358fbdb2fc2e11645bc9f56b724349c1e36b0cc7c8d52614b0782b4806905bfc329dc283f40015a98dd30517403c9e945f80cfac0c033ab389f32d8e79b12ca684dc5d0388f3be4fe19ad6816b9edd575408ad55a8b7c68d5cc092670882decf6b7076a05f637beabd5fff997204625d0813fcfd6f3f978929ca7fe414e48e53b102309102249361963fceb3726e419f6e38d0520107431255c61db97473ea8ac2bd5085c21f62824a1a2b072a4412d43c53aededea500d140624cbe0bb7fd0645d4188bbddab422efc96e2072f56dc6b7328beaf7ab6b02ee6e04c97ab59c61e7ffbca6455fa1e28311a63b7269a8ceaab5e9ad60b4829506153b38abe187a2a2eda2581303bb961ab8b9ba72679e2c69ab40a51194784ba51b218a538bc39596052c60767865bb1c935b1d72915d739f7cb40b0033976dd7a519e441f29fb67cf19aa736e3c3ac442a0d3f728933d7a53f924e73f8ad32ce5e0aee474cb0596b8bd84abf746cb4159656984e1481812d9ae0dc9db628a86d183847c3229e985e5a26c3e5e4db57dc8ddc910a90c96e7da796c1f0d7933b2cdb17ba3cd892400a13c8f35c7a14c8ebe8f4dc72b739355d6767407efb13e88123f613d6518ee48bebe5050fb7cdbf784815627246a89270ba0718f7a118c1a9e11418e8b5bac52b49a6711b4070c666e027cd728b77919559d6bc43f4e20cc60eb99bed905ed0cb0ca81b9d1db557b768f38236e75dcf934913b1001a1f6d5c7d8fae14acb548032ff874cb12ebc05731f83e721f287066f1020628f36bb9fa2a5070cef1f548930e9a2824b4403e285e7322725030e34e4fbb6978eea2015bf3b2bc2e8214052b9857c043175d36dfce8b7f2fcae7098a97dd4f3f085673e76e4ab3633572bd8da8f34dfa17c31fc8b873740ab9e274548b5e277aa301f76a7db7cf53e903d002103f570a98e199295d2cf67288813878150d821192fc51b845a69d0bbdb408c42f376e3e4f4b0eb36ae8b372218155d82988f9881db9f1e4ed80c0969fdd6f02ea9bbfd99bdba54cb454516d2ab111670f86040db88d9cfed40a271d2197235d9ea7236ffb9dc4ff65c7ed727cbcfca2895367097cdc6c8bf6cc8176108ba0f317446838071a722ce3ce9549358ec41326f1012e1f3a88cadeb146f1258f8587abdbe7174806be39e1155747f43bb03ebf84494d17951a2f91f9f3db46f75822ffb14e05e4c802aebd7aaa2111852445d7d18c2fa0cad2a586ea70aeed6ab071c892bf0548c249cae804e572f88d6dab5bb25a528a0e193a8e5e16c031891e6e0a895251b119d1765bad1a721fd7f358c2e0444f4dcd4f2c38e10c24f1d6c91a6611033ecc7f19e76ebd541c501feec60cb3b9929834d7eb7767090c4cf18fa86ff5e3964d64ffb180c591105715eb08a2ebdd4663c570f74486e8ede24748ba4b06e93a1c5b30433b79a57200bac04f100087a9675ab953860f8717046e676f630ed3168a513d248e058c72b438e8a314789540de15e93b6a05f7dc0abc5d559845320cc2bd6c1394b7335355994334ec078b498979df48d46c78ef821d70b49f4ed06304bdd2720d0f63722a7807a621ac5009bd53b0c7b480c2d17bf646c9e07742d98747276d05d11472abdad854f56e11f0a646e8848332909196ef6383f805aa6c8efa5428b5677b3b9ce373aa9b4ecb12110b24b2a124973963483ca1db56e21f86a0b7808bed2843060384de5c4483c57c3a2bd41cbf3d6f12b8c2201783f066de6b72b65facf3729bf160c29a3add99726ff39c73b68ebf0bf841b6c8a306007b1182f6b07e42720edd1451a7c92a95c37c4b6717afec9e55c62378358d27d225d7c1a24956767282ee661ac03044349ed7c33ec9584de5ba2e816ef88f5ddde3805b9e331386721e411ac653e657271ffddb71a155a32a82e40760abb1ce0f0219cde2df02864ecf61567392b6ad8e2313b00d840abc420955d45f8a31aa161d20f85ab6fdc40230a26fa6d88178c6c35f96429359397526978185d856b69cdcc1f0c82319ba727bdef3b064664a86bd629b9024cf86b72e32b5995d8ee56fa8c06c3ec9cd733231b491f8b985dc320a7ab5742e3bf84a140aa6bc399e12b623d0b68d6aa76772c7d20dce705781cde20a46f5f0c102ac09a18bed63a0366a3ff6b897d4e7f94c203005ce1b85f58a61be0409010bf33c7140a842b2abcba9c93ce8f6f0b147725c2fe499c6c8315dd5ff01ac000657c3b800e5fc1cfad5acb2414464d52cb4722852e2bcc2b20ab28fc1864e16b16a6bd4b627a8d136374a3a28daaefa9a4d72b3f47b23ba0f2c534e68519193454101cd9d6c7c4472fede56890ae59f445272369a634c74595b4eb91f5a2a582e5f70a61bcec40a63298daeaaf2f56a0aca729899da52b4ea08a3f4fba6d170806b17459d17eb6717ec05c11ddc1fde0f5f0bd9c0ea15482d858e22eda41da95a240afe90231afe18b0fb9ac54fe92dc14c4270c02d4cc9c910c6d90bea62490a4e3e501e4b2f4cf14541d5df7bada74552726eebc1f7a3a012a9e3b8233ab9a4dd17dab118a4fe8686d506b3299fc64c8f72b575880c75437a26b27544a367b8a1505220717b19a77453833f9cedbcf10b724bcb56a3867e74aa40ba36b555c9cccdcb29462f4795daf22cd68d6f7224d32bb4f046c1a20aecb5b834131ed1cb8c6e9aa78fd9ad13d4f9e3583a375f98dd0df8ab005c7eab2ccbcc938b5a4a3984a066c8af5ac98e7aa96f2258b63950e072615551714a0c88086c9a2c4588c8ea0ac94dc202c5535ea36215b4650b634305aabb6bd80e83e01e5066163a559d59059edfb80ea443c6328103dd1a507ba1728b0296a47bbeafc9ea9bdd4577761730b19fa817c5b02b91eaf973ec787da26d001ff2c7493003e5663a7033d19ef04e14416c378c05e18b27939e7fd832646bc6aeffc135db0db84dc3caf440a59678710d351aad48aaa992b8f4ee8ad28c72ac270f455e2d7c90719ed4b0c1da87f8d6ba412c9139b0515a0ad2129d14fe40bd0993174e93eee92955dee1f3748df7af2c2f959072f46fa827c6179702b47261cb76fb089f45416cf94401554725f61918b97fab1b56b6f993d34bb353a572d7a9f59ee6fe8b61ffc648d83a44e870fe89ec23d02cd51585c8d42cd579d463509c1a9372c11178b3753108ab21b501492b9473870cad298bd43760435edb54d3898f60161d9443f3952f9e0a5bab518d32c7fff86290406470c730b505ef36d036b2eac1abdb58af06b65e8d322be8f3d198c404201d8bf3a80f50f9a415723c4ef90c47b074523df649ea4578c5df399a6d17c2be0513c5e30894ce148b729f935e26e50fd55c92d799f5fd2566680b0af6a50ed2d9d3dbe9a1d597a5e97210c48ac3ed0f3224f663ce7001ea06614980dbddf7e4ee7697407754fd3b697276c5b900fed69ed4f8e1f5810738510442c1b41a3eb37dbcdab2b6ba83359b5546fc7e3a8e776d79411ca96b993f0837a6beeab4710a4d25f038e4e404366f322b8d2679973c126b0e44fa46aafd90b99aa553b875a35a8260b013282e8ae11485dbea72989cd11c4c03c940202f15cf022cc97dd4a78f590e23a0512016d7720b465e324fe94fd45f6b582a965c36cef3247e62ca173e2db0681510bb8a6710a09aeb8b0485d8ffcb0c0598a847a3a674f121e1a71e4c180f0b1732de859572c80cf5c869ffdd6b0518c91685448aa79a68df8ad3075f32e5723320ac592b72e190083efeafa67bd35aaad1d8dcb2d883137a793a557111abe868b7ec936e720d309c8c20838e77ec2b35e0fad722e2d1b064cb686b9a2e46aec44f47a98a4849c1478d2d27cc8acc9be54ccca0d4ac33beec66c04608727b91e770dd6f7d6aa410e65157b758b2bf468def6d950da13117211706a2d2d8c855e6110c064b0ba5f1f188e63e2e95b5173000e86f85f9ffa42bd6498c93276cd35a8777a9844eafe0080ae9185eeac51add3a0a4501217bcfa69b874e5cc43efdf8398d187f729529848296a97b1e66d84a7873fdabb316f814e9e9a323afa7e55f8b1a6a267219db481cae4da420ad6d1d60a4d7d7677d6dbeb01d953b95dfc8dde718e5db722e151e84c38c93a6b122ec376f601d10f85dadf87089daebf13c97204849d27241df21d28d2acf643b869e6d191007378abe3c106b64a86a62ffb8fac39257722823911d2d755d40540115ab1d60b0577167cac3874d53e0bb6685d63ebf2972b8d052cff9315ef58438dea7f12bae0c77e90cc677d54ac06d979ca9b27fee5cb92f06b628a56d0cd8d645b8ae13c67765cd0a5a6ccb523bd499c66cdd9d9872c6a5b7903e44798930a77860d6f99537d5de4734274d6e16c591ec447a10f472c05ebac6a29dc30d30071b7de76dc2f552fbddbe2a0546fe416d3c8dbc0ffa72fc4991bbde54e99c5e6a28210fcf9f5c6a86c6c722a4fa5712fd76909ef50972fa7b04a91883f01ca03bb7764deb5c5ddd6ff4df3954b9afe0b8e3f112b05f729fc6bf0419ee0f498235c96e690803f86d706be2f02c7b62360c81e11d2c172479f4771f4b2511ee553c8523c0465dba5f3dd961d00db4f45467c281af74516e11ec6e97556cf8d554d17593f2d50e6e107676cdb7ce12e893a473c2fcd61372620217e39f315dc28c3a32f2670bcd13fa1841909f1dfeefb666753a2a00b32f04bbd5b8bef583e784edc574de03d3e6b35fefc8176c24c83ed94766e78b647255272978614775efa5f38239c720108de885f7ab13d8c78817d0e33884d60d72230022731ad84e2868eae8a85be4042fa4b32075a965b7ab9f40ead9ecdf767224144d9e475384cd89486930d5d859b31576683cea22b1cddb2a5c83119c2e0671b90a86f2f385be7615d215a56c0c90dbcf3465f59be1062fc5f816a667dc2c851d5327f6df2134dfa8c7d22f189e7b4c70530ed2706ad64697fac24d13ed1067c2e7dc75e1a73ee11c0e40a52d046559f7b98fd80b11d686ebf60f873370724bc47f88ee42dfc8489c527d2c28fd97915135789f067ddba89fb1b2eb18d472ec2e1b36fd80df2d47b641b0d472ca9db0a6538632f4d6ecd4bb0fff404ad87296c4456c88f8824feb9978e27a4665b420c7b91b607723bc5f96730955294c0a534d34b758ddb363ce52e5ffd2d9d4bcfa6efd80d9f4d7897e0d7508766b8632a4f65ea53045fc3205d199f270c33d9bd9f6592db1e212a7a33b0909facdea462edd85da08e47f7b2685ac52bbfb54049ce30bdce6b0aad61187d07d93e545723cea242df92df48993f56082dcc2977314d19e6fee4e6c63d0a02de6ab3fda70939459a741a2efc2f4b17a6fa42a820937e9228bf060b0ef9f8d0a53c06f4172d9ad1abfb247944dc51670e250372d86bd6525b8fe319af036e3f281e7226c11f7766b233d26f1627f01a409a74d378ed15e05dd87b9caadebfe7619fea8ac72aa3f9d4e2254cdea70a9fa3147de9a2ff70ca8064b8d45d8b1afe08652a0b872b7aa6ccc380c06da5fe5ccecf60d07c66ee8e83bc6e81c6c71d9c3b60180ee725fbda9a75db5afed6ac3e9ddf067e4211bbe26d1056da1f7b7e7f582c48e5c720f03fca37f4c35ab657fa410dd4dc59368a4073f35a5b555a91793cc1a1aff4f4411da52400fda545d12d4d32a3d447ceab78a518a784948faab8fe2df7deb11f7ad7f547f506acf99db42709d1784900c7f14f4d8b2f072b2f2a510aa71ca72a1552abade9d267d89224d65b4230f0e7841873ca71564a93e110c83fd6aa27239ac2625c0c356a0aab444fe94e448919db0a4214f775cb0c19da34ad980b4672aef9d570544793b01de4de94882a08a9f24439c38fb87d5f2b6206d54671a72de0244909c6213706c17c6cd228a012347d497a53696adca1f24cc427dc3d0240ce4774620be3c6cc52deac73acba200adcac382a6583acae05a4aae020da472eb07e99625dc174e0d0a19ba479d0f23c44b6f1c28eca6d95cf513a1398daa7233e50507e1e1e985aa039935a448707220fbfb92be6b7081258f645c93950e5b0f035767ab0d38933f399902b5bda07d09f17389d2b8ccb402d220ec87fe9119da4358f35576eb9bcf0e53406ef4e28e8839f779ce8593ce7af6d9e20c3d0d69fc030dbe10e8e83851cdaef0f740f9e686d6e45862dc389339d838a5bf61a27210f1a65a836a7963851db87a6435c8e6ba4d7d2d72ad0a8ecea1b9bdd08cbd72dc966d0e31a903baab601ea045e12e291b7cfdcdbc7a7b18ad90cd396f424f72efb92ff1362962d7339da97759b8a2a9e187dfbc42007bd38f9290583292f0720c96c2637f952eaa1dafd7755f6ef095f1120dc9ecb9504af0809b3bee854b1b3257b19df7558eb9f6bbf325dd431897bbe55ac1b85d1bbf736239806bcb3e08ff2ac016c345c6827f0b9047b0e0c0cb27de17a4a045fc46fbdce4cdf2f3dc7241e0fabd97c5c8936781c45dd6451d8da290f929aa2ab1381e2907556be02963ef7f2e773e0a8be8846a114da84c8ab28ac0bcad1424dca80d6e83245a004a39adb5294e0a89fe5e43fe08163b8cd334941f4ba58bd8f3dd0d93bf795515ca724c4719ec1300ab009fedbe833a737503d7c0d758709be03ded41f7c77ffb2172c9130c6852a08deed73ac4cb79317c2bca5dcf0b4e1de6e186563c6ef20e1d722afd6980ba4eb5ff598236cd3c1491cd1f242c74fd1b766a541cdf034ed75e2a713f86b62b5707c2e0d3b99883ca06bc0ea095249fafd8f62c1bbe587e5bb8720b7535487599475fe20e5de1d07add5b25d330b4f9c405c1df3533368fcebd722b4f00058a8859ae5cf22885a87d9c0ac88310b181f76b6e90756a5a672b9805121374837c2faee75306b647cf99e001c173fd56a3aaa0df86a08abb7821c306dc99a730a3a1bcbb5b7196f62c253199e73a3ddf10fd0878b8ec54e18855c407dff9468a258083f314f355f24b1a5ba4b2d40fcf05492126091cb5df24947f6db4060d758eebf592bfdb570422c2078c43dbdb2c2e0aec03cd49fd361b2423694e5734b3cf67ccef847a3c253bdd7512ace56f7e131a10cf87c3d5e2fb0f945e8cf2e81f0e5868be1465b0e341f6ea178868f14db767988291b8af8da154b76404fa944a3240f91573260a1ffa002d2dc862bea084e807d218244140bc50ba724e69271f911369ae8cdb21a64a7f28309541eed8c9d937ab59a708ac783f9572a9c66c9e2e63234a6a4fb70cfb5ea4624d18359d8a994098e9176865b8bde772a5fdbd4df36b63d2f3dc4f5281d27058c0aaccbf52cb52f999a28d2d009a933068545fd9d43b00bcef79d4d1687389953ad7849590e0ae675959ede0c1e4e772efdb09750bac6535b7865cd7bf918de15dab8e460b84891fa5aa1bdb27f5ae7248e4f0c78ef277cc446f16126be379f145fda7f1306c21ff4c03e8128cfa6f72ccb4a7e7dd30f0e24af2afc915864f653fce19aaedc15f9e1dca37d07de50d72ed764166924a2958d7df97c3965c8695f191a14426ada4abb861a2f20e157372206c62a7b38a2471044247397f09669e1d3f17591812accdfb11ea3e2467ce72a14a593d81cebc3107d1f669d6c696cd53826c3bd77ea10671b3f08edd60875bdb3ed457875c95b7672b1b1f204da70369d9fc8339681ecec30026917528120a28d19043c67d528767e01e8365b50e4c1ebaf8540c45f65e9665ef7936db147269bb6b74376bd75b96fa564691209ef7433cd4387e5e42e62c2b7e7f59e68a631fe5cf4311d7226c5c91575da59616a7cd2a0a148f31890aff5da67a84b9c272028955b3c313f1e02c25bfa374fffdc3b0786d94499e1027c47bcea7b045317216d2daa6fb357693edbc39218cb1f5f7a109445f33df2e51d341a609dc2db629c81729154886082202fc66173d113737388556d734e183993fabed153a9b81724a5c75be765d4ca20b15f30bf7c5926093ef2a20b97bdd73666222855bb0145e72bb09289659c467b64a1fb636d87f0454619e232ae682febba158e3ca65910d89406076eeaa513de929dd8206809a66a9696fb83a4aedb427cf98470f51c55ff59e0adf302161229d860f7480816416eebe033957eec9b27268559c5994b6727b5aaffe1e8d4a59baa56efd078059f6c781200b1b7b4ea681e1a59233087e16cdc9f9f0a3dfc9350a214d73f18add0df66194b7b8a655d25a080c40fc22d664276b5eceeb6263221ea20b873e6aa2fbdc042d42ba4a44a4b34bd54ef655fb7259b8cd01bd2de9651f5eee3d11e52f72d4e2e8c61de2f4a53cca5b2f12901115316ae141afa0f3b89da709fea61cb4f229d51e84304921291d9e6eacde76492352e9699232188c926f09b2d05a282a80f9a8b470d667e5fbaf63a0874e151d7212cd35672372f13e248e2092745152ffd44b6515744ae4fc1cc480ba26ef4126269c566c3b54d6829366413181c43d0a5279bcfed6795c6e6646bb0af0be74728d62c0a78c3a7ec973160ff193b4d1ac594b3c98e6d131591e0d7973bb2c636eb6cf3827f1a737c85bac4d61e1d3160b95646c418d8ddcaa864c78f63f68167275a8fd941ed98b002550005854488412b2a606f46483c8acfcc2812dd25f837256f00fda8134d3563c0fce7145c776212d7276439918be2ad7fffc0c6af543727f91f348b4a91f7c33dff2b4a0ad2dc89070304d0ca2af75edb4896e3caf9164e25a451ad96f2044cdf4f1dcdb3a4a61c1f988faa8ed68577d95c9ef028d2c729225c7a52d52ba6011071e6507642258eb687ef42d43c23d6f88abbdcde97f729cb3e201011f63b81128e17da51aa204bec47fc49d86a7e0d9750582782d7921038474591c5adbbda747cc7924525b4abd2ca560d456d4749a30245c60851c4d94d661e4f20b97ae97bba360d96f12f80deb60b8c6f11964a5068a248f269172379fb9359b37ae403e0b588854e3f6581f2c72e3eda0db060e6653da18761e7270aae217dc85c0571fc7ad621dab111dfb3e0d2e56e74f4a413e11d3c6ad267209b05134d88b377f00aa3fbf73f0450216bf5529de7c6447928a244f10ae4f723f86d42670d735b5cfc1db7c194ddc2783e5b9cc725942c30f4969631b272e25e222f0cae29bd86decc7a43c3dcd82655a820f4ac0584e69c9a6cd08d4a1a272ede83d3f61a2af8d2b01487e2d742e0798f9988c121a24c3ef321011c1c6167268b50c081d3ae674ba9c30bc988a4a8a584be2f26d48b99f3b78166c9c6cc772367d3bd8d2a923c3bc20adbdf4737387df4237310957518c436e720979dabe72483295f0135f26fff3f640fc0bd6cef02a19f1d35bfdd65ab7abed7e4e96742c2b63e580caa1e72c7b1f304722966efd1a2888560bb16e2ad57e225efda3c772c3a7ffd737f2bd707dea099fc1343f56aefa97e2a44526d2ab522cc756264a72a8ef5966aca1894da93dec7a9c39c86f7a8b7d626e4096817ec064330b3a9024514886e6920481029bf4ae79b748333bc8ee9142ba8978bb7ba4c03705f3817280415015e670977dc4ba180bb5674ee1dc3090c9b10af91bd71c8b16ad6a24726a8c8b087269a6ab280eba6ab76b28a7d8c8960d2074dd88ac4917b7626abb1ea9325721c830da28af481e28aa91c2ace9b50ba6ddf1bf2f7fee9520bf4b5d726768dcaa57b212a984cc33f85d09550f98b751d4cf0bbd477a7f6e9a5529582bd7ae34ed8cdc761db50c4bae78338f0418dff42c2acce42f5c1b2f4611019472e4941abeefd8725d627fc6cf0fa982ac52990ecfdd3623807fe463c0d1ed61351decab52b5a6023aed615c34fdd216dd99655cd8c938b94dab8036d53a7d4f7297d2ab0e8a53c923b4f1d072581edcc06fa05c56a3ba9a4781afa4595bb8c872c9f57647a1faa4a56fd1a150f7299e4bcd54aa883daccaee51f2e1898ac4f872920e6be892f87f5166dadb3703792945d9bb1b7b723782e7e419d5671efd67729b1e77b0f139b456c66e671a2315a601f1dfb0feefb16297fd26e456f5cf9072e1d143bd567bb0755805a74ff79cf7a2b60a3eacb2300612f7df4b6945564704e903141630ceb901cae6b64c98e2b8c40e78b05d1a887a89ef9cd5417387cd7292648579f2c5f97614767b166f28f672876a91e8880b77b07d503bc5fedb7f723e83e079c0a2ef29327339552362f91e23dde4200367391d53deeff8a8002272a6b0971dc161d7a9229f935f721cd9baa4c4cfa158389f775b010573d6a3d772a5ae96d2a63e38608c5bae2acdca1066b84e72bf59f59a1d734c89723f757b72aa554d2bb904be0a25517953b4dbde83b685a71461d6d1049af4dbfe6abaf9629b8dbc3304f34315906ba6557246c14fb7c80d28dd5cc0ed416556c7ef9ea47276222e96e55f5a7704090bf12f2e430d923c0ab30548d8e37c4d47b18b18b520f5c771e64d42f4650793372062973f8768ac96fd8afbe387457e308b49e4b62ca717b1915f9fc437d845a74b31a3d6b837bdd83726020a4132787d6dc4a6c9265859a43ca8a3d556223d822d557dd69fa7118f5a1f81259338610a5df14447196ff823959e84382ed7b7f67f84f1f28bf881af6b3902d10b48606ffb800e80725dea950bb99ab40e72d4e7b3b7ba20315c35734429f4403b649402a2a538e3596c3fd18e04d5e1c9f66a13a9e793c35d8e512e176f75722979702895e6949e41e83250784492c8241dc1df72ea8da4e284d36a58f76fef62ff82db0a9e181423cde1da2db842bd64739926915f36980469e92457b2381135e788b42982e063727bd363356e2890bf44dd5755f9736ed2641e28519e7f60e2885ab1ed5f6a2c357bb8c0b72fff8ea04c470d62fdc4232aa7cbfa27906805495182155fc717e5001d515f4227b3a4227a9a88d3f2db39b72857672667c2f5e7355aa35d54eb997230eff6f3e8a986ab9eabeb6d52eb9fb08140ef4e4bdd35af4ff500e2035cbd720655bd2f710bee705c3acc4d2ef825789b8c58ec9d7c3b0ffb46cbc30b54dd7266561ef8239d372a7366b744e1462248092687c899cd4d6f0e102d3683cd0a1938c8ed613cc24637272013ece8c6e66274469bbd132fbc5ac542697cc519f8430e422aaa843ef4b9e365dee9d2a2b0f87ae2975c19d24a88ddbec4d02ed87e72fd290d68560dad0e496612985922166ea3841296fabab9502ba069007eab0472899cb7bee21051114d1caecf7b79546bf045847ef208a976c1a2d84cdddf497255edc21ec9b7a1ea0bf195ac358939d7386f61ece9e6b00962148f70e874f0720c9b7d117b1e37a3ff977973759ae6839c894165d6a6ca0074f1fc195fcdb1725a41c0f26a40590e7baaf31e3bbd64d11471c89d4f82c1605076835c4cbc6c669fd180b5383dcb050c7841da85c95364b24a67bce6b5e298dbd03bfbf563cd721ead8d0f6344181547bbbd2d7422a5478c7a98f85bc5f8b8adbf21d921973a729f0fe25a13540c5f7a28fb205d7f8ad87be585f636b148a96c99dd92ebd0d47233157e257cd08e78f3668960c54910d04346fa25c080335a014720b079420372efdf39a79fab6af9228af261c6857cf687a6b2d51d4cfbc8d390309f87e4cf72e129b28967526d4017f0e0a707e1e3bb89f700a1d38932d9d2a4419d4235896fc8362d6cbc1dec4396892fdfa7d74501b20adf5317210e833cc115a9dfe977010e99454f7a29b86251c7d42f039eaa4b1a0dbb8a2cd2df5f0e1faff16eb22272886a5425e660d16e47ab66caf184290d3f93ec26973c1b3001a8daf6e3c72f721ac507440d00027fc40a2d98ec2a5a4554b74da72b4aefb43fa6e13536f0241ce40bbbe11fb52a1223da0485cefd8f50635679bc609a5cd4bec247f71d3036721a2ce1f618470d74d392232ff3369d1511c765cce6cbf4543b05683a9f12695ea0b04deeb7822813da2f098051e5ea1107e42c149698d4b3f2f035893f301127be3d618237c90ddcf23f81f437fe5c7a8ef5df6daaed786f4d3300220db51c46a1dc7aee52c334df80321d71f14d719e0c3491c798fce1d78643d419d04e9c723f56aa021772f140b314fe6e808b8fbc6a34bca65941f78cf9a6c5e44f7cb27201dff7a21dc9d09bafbd2e69d0ed729a49b90fdc6546cad60ac9535c01139b72ad307b9e6a0d006d593c18d6a9ff0b40c5bdad3bc6858c33bb6256c5dc07f872ea50081e0099c39d2fee4aafddeee6a8d1c7a212231af07bb6e703c505ad1e72557ab826867c2c004d7189c863e364986b03dee5859cf498ad56f26cca258f08692721f71063f0d7d50624c076f28ae05cc51a5c961d6970eafc3d7ad84fe11ede497a39aac995839f8f4350862151a32ea3c29a9ce4f09be461e8f5fbcfe0726b2078e56881320a79928f0d7a3c888773499eaf4291661238fdf495b098ff72c4a9668f8ac24fdd5d899080149d951ff06f202be348e5d5d23e6101eda89472bcf62f50e50eda347873b86301a23815ea7d59a64942eea476091614845201638a2c37d2f277e904d58fad79d5c4dffd3c3eabd0579a58e57e41e6763ce33b72cd2c93150b2aac76f3794a2f406b61b9d55da76a7ffc2a7df7b364337153587203fc9493598a6b001e1796123e74491062395debe86a91de2cdaf0b320a6f2702dfcd34f01b2d7beeaa2898d4d297e868a6f969d7333bf73bddc9872f1f28772c65ea6bf9534a2fe0ef305be4773b641f5cde1d1993c003665a5a8cd04e9507279ba853c96b916c28b57be4c828c9111bede31fda013ed2e64b5f7078f289672c3867c09605b2c015a04c3d33f2374dd8eb2b132b09c676faaba4afff2993172512d759a2aa11cf230fb8314e866cb196bd26edc54e66ddb882335f21ccc40727d24b406a079edf3b201cce654b1ece3991668a45704b625ca0117834d4591614ed7bb765d39c59588838b6f1efe2bbfe1f083488658e011dd5a1978049e8b2d3ea05cd15cdccbdf32e091335842188e2ae2b7ba6a4c44b41156ba3525bbac72b324c2ced011d30e860aabc5979f4babbe7b24ddf8bfbbeb5d5163a274369372688267afc9276414db1bc0e368695ab3be7ce3c874cd119533ebe9c159f1467223dd038ae1b8c14cf33455920be543b041bb7db2cad7cffb8003a89f5fdd9d72bbe2f59768346225b4f3f885ea04130236a15a900848aa16e994f97874ce421069db80dcf26412cc0204b6422353ddfd9301f856c795191e89cd24ebba9eda21d610dc96985c76733dc677057f759ed5c034a14af70de5f9c9f02a18c52b1a3fe1c5abfd6ce757286f7a65b0ae943b0aeb9db9d4e8cd5753295f5d57145c297250d9adbb3eb27aa6a1ea906ad0aeeed3707c4161caf4beac5a123341500b634c05a3cb0feb72bce02a4a7eb49e60aec3e1beee5ec2b4ad3ee21c788686e3db5c6cc9b1e465860ccce548339574bdbf29d989fdc374ebaf18c48cdde388fce5721f2779a57b046b9a8a546037b0c447b979c7768bc64a2b73ef36c5ddc553e0722717015bd232ab930ee251d952c992a768119dcf01d5c34f04f3f9231d66ed15cb4a6ccc879db7f207fbe12bb904fdf0ad856f8e5c11850b8b3f362f25cab052ec3bc366550ba50c418616470bdc7c3c97d8c0176e32b53e45fea97caea1e3725a8c471b3c615864aa9e8f275fc7b5e65944dae48ed5951116198ebec5d87672663ed2df1d0995c2fa341df2fc91ce4ac1fa8cf1bef8e50295e26f32bdf79d3df83b715a1ca254d0b8a07c8073bd569c277a8b05ff7a9678d058bbf650826572d46ce32d5ca0a8d25055c9c905e9f7738adc722bf5f157fd04b3407b5ceefc349500dfd8160a55b953d400056f1830fd7f5431b204c6882f58295d432091fb72384f7578a7a4de3b88c64cebd0e9de82a63e43d4acd9a597cfb23cd8c7d07d72f71256a6657fb3868824aac0a54b1cdb9870bb6ac23965367e2740619a8b14729eb5cb4c316541ad3793c48b3f1b99155d2995e8f78f98bc99437362044b0172bb4cdcacfa583aff5d27c53bcbff2313702c1c772b0bda72e3ab5267c9ad2e6e6a46cee87ad841201e41cfc073d5df3b5dd5e5ac9aaee92b4b791b47a80aae7256cd9a421e59f6ddd702014704f2d36bf4fc709360b8a96cde5c2336998c4b2f23a0015f1881b2854b25be3a7f787942e015c088a7dc3e0c526366758a9222729d19424d22a99cfed3dc24edc68cce33f34fa702fda88213b1d92576ee085b334440bdfe93477646dc39c6207aff65f80f2279d38658de37cb1815ce4f77a872de93d7dbe6fa04f79bf237f2925b5136efc7342c68ec193e2becd21e20fb6d3b32bdba2cd3e8551b2f39f3edbab6e1cb521bdbb16c30e33e13525a6cdcb3c70c9e347f7788c2d9cba99ea2d4c8d2ac7adbfd448e5579df64e12b90dcacf8f96d6fc72c8c765c5168efacc6d6874cd8840729c78be6de13d09b56fe70f3b84c72f578175dfa3916f1f4d1de7de56ba6bc47dbc46a8e3776d3c69695caaea1a87278a9b8cdb6c341a746c526addf2dc84266b238b35b9d07dd23e0778564b6f13ca583a94d96f4b8085e8b18ee827f4d372b75764c5ed4a3dbaa0cf5e8e6bf982e20b496ff3be0c40d125c15b8748411e4b35d87e43f5409ba001c1497d98ddc2239949d861f93a1048db1426d5828c6d1abb48cfbb6837082676641469dc21872407a8bef86968dafe83f21ef57f4fdf8c965455ff07c2f529eb59aec5ebbe6726e7a50f11698180a62c5848023ec726769dd5b95353520d1df8b49d7a3995772855633a1d8f7479226874f41f969cad7e49e1810815459d41b9c5cab24641272b2d3c2fa324659247db1fa3de031325296820df49486f9a16ed251d6c8dac7304b51ed649a6f229abcee68c80e64500a2aab71a14139daa6d80ab3366f219d562ca71ed299d094a1142e459454cec962b55496ec465cf632f8653bdf3c556c07a5948cf12d78caa877e76d769a20f9d975a6cf1305146df2c775148feb062709727e6f85d8397564b19f1d0ee612c66c7e75e92796b0b2332b25e14c4b0c93729e651b58e10e475012b9d98d341c707ad8df1fef7e529d26bb60bdf52e483172bc553e328b959ae5af30c81d07d713ea665e8fd7a615b722a498ea5bbcdc3d2f1995386870cc15656806b91f3be3e0a0d2df7fc6bb783cc71e31be4a7fed5a413653814a21ba73c04991756d5fff71b0526224628925e7b83c5ad192461dcb3d494d45c483e5ffe4954f9bdc14f8763bd62c88122f68a712e0374dca324f860d9de4368d8d2a23c29fb0a9d0f4805890d7cb0974701a5380f9fd6220bfcb85724845acef56012751aa75844d2bf8adf4f2b4303624b626e2b140ec1ddacac8720d5da17013a10c80cbfbcd1d798fecc9662ea7a6bdabc2a42fddac0b7e029d293f3ed77557cc667fbb8c8ca8ef57710e0d697ee8a8af21660ea7db1858b127729844fc7c707ecb5f3f444dee43307e64d7756da0eb4c92d148a524404b623e7252604decc5d9890422fe6e2dca36672338ce6b0f1d24044773b1a88d343430724205d19f414894397ce8b3632d0148f6c3779fdfb871748e51a43c7e5cd4b672771608789088133c92bfb6625c3fe307ae5913c78189a4ba508d71e72835d329a81a3c84a9720492991f60f1bf85f1fb91cf3d1bdde224df1115ea35fdb28f72e887de1a86b3e3b475490d231e11af7e7d63ebeb066fda6f09a7ba8f01e347674565615cca3ebaf98ccdc5871d080b0086a0330b3c200a6b11055d7678b17053098261d23d85bd6d1d5e52179c9804080c97c0754750485ed93e673e5845c04b55c47abfcf9735b42063592a2e9e6af0cf5bf9f84c78d183e71bb17ed820ce7228a6f69915248c26e2d42973a2e68fb1cc4ec6188945f9798d01d04e084036722682dba213e46570a1f8ff5a688887c3c1b466b2f0ffa7a4b28f17207cfc7a1fb12d23fdfca81409c5130c3d23cb6a5692fb199aa8f170b4dfeaceefc4c3b204e86641013c306ae253136ac96a55f98ae7c29b83b8ebb2cdd1eb94de10ac9f727326680ca042aede6a1d0ff388bb3264a5160e009c7a175286872f843d40db72d433484198c0efc1ed0b5eb0074055a483ce90a947d3b140f49a2a4c9e580354984f6850f47c024307c2040bfe1a808e5b50ca664af54d5987dd147d9f0731354dc868af7aaf6501d240e7ebc466f7cb1321671edd852b7c60402200f0aa1e72fb318b9627e0eb072cba2b3c0cc950ae997cb5c60e46e7a5d7b62054a5b3492a26fa40b32b905bebd9add877ae3175be53c0bd04223bf2320c0b9b312cd462726622774aad25f086d6b68595c14beae44764890969e917544edbfc9a89d17456a23df37d8183be15c29bc12ccb3d9ad5418c44512a75d3818ebb1cbdd552d95d7914966c5259c407d61d263be2a9bcb2665ec6dfe8edee298990399800c2b121bcdb10978d233791fde20cd5ba230771ce4cded4257542fd26f8deffa9b880720d1c67a04f819d7e72233c60afbc4e72b688f4ba16c5c0f4722784be93c51e729ff4e508b38a2a6ac3540758e57b9df206e633a3af3edab5705aec613d8e8572825bad4011be6a059439e0c00b6fc55efb121b574d15c9057f33dc8ca6602372d88c57ff3fb7f45b0feb38cc4b3798b868b15b016170d994ced4660b50c7b872a3b65192c80f0d6caaeaee880f4651980e4e0a399e29e8f282c9fb60d841cf7205854bba085e4e4eded48f47c57f7c356981c29ef0cb0b3898adcac59020955f4fc12dd6c1bbe20357955020885d0365f3f4bd74e46d94ce15d6707b369ea05641d7e69347dd800ab2f623dcd1bd94f8fd92eeee665d2376b6365954d392a1723baa61863a038eeed6455c23278072001ec79ddafb91456237b0b652ef24277246ae684747b73ffa233c02a4432a408d56c6c2a738ffba2d27345ca39a353326e9c85c435927a9d8bf24c21a9db260e4f6ed3fb6602aa0e8f8ba0295301b4e72642d9a34af63b7a2ad1a780cf24d33a1641e1000ee7ad4450e05b2fd3e073372b3d73d0ff6e87285980fa74006acb78d9fd69e2b2718a04b3ce2ad1fe1d302729fbe1331f9514e8ee05efe2c75de52f37dacfc2d47374f0cd71a272a03f0494a428011d3ed71a4a067b4e7c47bfc5d9a71a8cb673ad56fa87c7180b17d00a93782a9eb1290e32e697d5b0bbd2c5aab487f900536295cb862bb4997313e96cd2f31a6107ac8d2906023aed54699342a771ac2001944466ce06ff7c381f07e8865cc8b6f483ccb09adbff391e00f461aa6e06556d13d74b9fa5d5842b13c74f93417bb304faef25e304e6462f04d1cde038f08263040afaee5e5a2a7831ba34364bf13bf49560ddaedb59aa15623f0ea9de270a6a7e5b2403527d4a6454791207243096324a0b5267ed10df2d39ba9611bf0df5b3e6af0707ce06e53ccfe9e3872f9b9d76aeffd373cb2a864add9775c2aa7d50b98fff5325f15dc86f6d30dff5b2e8b922c3e5ce5463445ced9f9a442c5bf14b8a1c41fd473c521eb934bbbf97285b2d84ef1f5fe4a96a8ad6f19122639a843627092d934d804a8545f463a5339e1a20d97496cde2474dfe5afbeab699e29fbd979c29767cd0c4c986090647972ad8db4c8a25e03079d9a6258b0a92fa37d3fb072db4a06a747b3e6623623c53ecf0b0da79e79e31b1d2061774a265517f7706891fbee3196ea73db70a110810abd8d0673a7ae30ed629d8fd7f9580a78415bf197bc993cd7c89450a55c31087257cf89ddd5da8fb84da19e5236dc6880d422489adb778123ba420325535e73123bdb105290c8663810100a3eac3a62ba33509f82b4a0688848e8a0c2e1d709728b99d53121515a1e3ddbdec71c3b4c0d8e3e65630756c2dc8b7183365826165d7c3a0f329597934653ecf8483f56bab09aca7d05c272e6490041093c7b08722839ad5e37857da8cf83ee8b5a03a33a21a4f2cdaf4fae7007c37e15f57b542072474563ad63358b1e230af6d244efe9d197333371133733b3da372ab27bf5191da1bee42f256f174f59924a4d699058a40b022246d593951af3ffa733ae7edd72be81ba6865fd807723f7444844bb9d331619af07a319e3e55d8595112d59297217ad1a9c81e4baa0e0a24d700b6a2b48803b754cacfbb24ffe610936dcb1245917d0678a4c46e20217172fb31060f1f5d71d0899fa73b8b8e54baba47d05241982d6d6838b7dcd7dd1d63436cadfe1d261f03222d47ccb29e5c7b4ff4f2131544ff1ba14544a01639b938f836987c01be21c6606819420e0cda3e4e93875cd1977bfc3bc3325019678ec94343b4454f353c8ede46f6c89d12d5bf491a9127372683161b9fe5708a0540b7e379f9149416e480995fb521f49917d27dc078a582f6d46ad70c740572eb9f1446e54bdd1aa11e17411b585c9a83925681e68da9b72d0bee6e53081a246321e39502f1dcd58dc8ae3679aa78c9bddbafba35ac617588fa44d1f05831ee71217c4b4211ea905f9b54ea05fdfda55b2fad44b5e11251ae2026bac97ba525a2ae5898a0e3034e54963c6c9e4253d86b5096a6c3559353ce37a4a314529b7219bebe5d7096208639d69b86628f5065d8f31a3854883fe2d4aecca8288e00fd034425bc2036c22d28b7cf442b538549e1aae614b17358c1da56f5ebf056564df1038cba61fb7c77ba4bdf4c8d7d2e623ccddbc9dbc69c172d103f92b13fc2fccba90c852371f9858b3872cee7534008db8389774512cbb727f8331c0a3aec5c323af5c91554fb399fa86f48bbeb9f9ae02097fd3454488723e7b7fff4a77f5b5de6693bacbc3837f6c409f5c8c995a5d89bd5a0b692e816179e4cda135a5be509e69f0c6d587e9e788b08f88da56bd6d275d1a9b2e77d3729d577bf72ad520bfefcfef0e6c896d49c1f915bdf40e84c543d678fea3596e724bff8bc5d0f9bd44cbb99391442021d28e9271308f4d78822b4028c06b642a64d89bb3d9240448a034de0b6a70c7b0e2c109e80cf600f315c757bd397e194a72d78aaebd57ed6e6186446bb3b59d0f7872e57e151a33ade13be7a566e2d4cc72536da30d9458b95bf12c4753dd9bed9b614f75123c1334a59b17f36d428cfc72fb90cd285e9aeae800df1f6d8024b13d5c3bae64e2ae7993f8d1c1e1f1f41a143d133caf38d06e7465b377c1c128ef8ea9f197efae28244f4f211a3574937b7277e2941dc337354500180460fbac7558d88b3789d72cf8d563746e6352a89907aef7fca13d8c7bcac061bba7f7cae23095e425e0c3d113f77f0ea275080ee22dcf142791b83f87ad52913bad90059e3cc0c3c11c639cede6d6d7c8c2da315972e331de8e2918b82efcc551d95309d4146655e8fc2dcb7cc6c1dd1609cffd30726e5c0ce920cbd9b962893cf4c8f20da6e9ec1884487930ba1141bf02dc02ee0e29f014dbea643a072491ad77c7c435039862350615840f84e007d14b23210b72204c1f789eb3112fec48e07da7abc44f918a93a991198813209fba6a93df0c7202d788e29441b4ee2f8b94d9a2700d4f45760e76343a278aa542b9f9c16ea6047e2eddb2a94720c99809ec5f744b3972ec1606d0f5a368c2a537c2e1175f8172444140c48be6618631dd47fd55be49a47760b5fe38bd45f3b6050712413ef37244754984b8a7ecf8207923d64564a2879dcba189b99c4a30426b1621274f1772ca89f23a92cc762509aa537039f28a11af82b9e12319266a4d4e7e5447c8060250990d0f04c43fd3c9dd4ee979cd3fc8e37db085666a608e605d4c992dfb4972ec5c3e2f9e8b9b7a2fc29ecbe2e4b1420389c9db861b3c55b9ab5cc52a9f0b7279f7dbb37ad58583a0ec180dd45859dbff7ff2ca95776e530664a7265c3b6d56af65bf42b13833b2423fc431dc1cbe9864f67129be98e8b9c61f54c9926ea37263f6137517694b865dd80c9edc825d6d79770c6e7eef35d83b31f6c2100b8a728c770901f6d6347d6e0f088c6092164651fd50a3e9d42fd405172d133eeca6721ebf49f8dc9dcc142e4c1ccde8d587345fa05420fcbc0b2145dc635cee945b72ed3988d841942bab0f1a9522b844aa95a8e30dc39edb3a403908f6c8b694cf72b0d90be93ac42b7926d8191a22dd1154c8be4fcbe53f5f38b54676c66b2fde1fbcc1e50c17bd5ae429227c27db65755dff3d53873d11a2bd994142e0ae2bf733616057af1659be5c11fd56aec48e2c91ac988e186fd4c5d30c2bb641ded5cf0f9e04cef189b98ef74730c84de707bb3f2cc166348f21e03bd1f676620b79ad728864f9a066dc9f452805770904626df02921adb1a01752e30e29f5ec7efb54175e58dab9ece35286a4e949c814dd94e98a6468ca21a175478b170e5fe9ad7f6aefb118fa13476a812dc2b148b69f2449bcd514de0588e949697926b4e69df0408a69c266a1f133dd8c1e7539a42da8973a345aba7a81c2c1e8c920fb82f18072065ab14708220c5cef487ab257b88021eb58e6edab6f570dcd871a504394f142a39ae81fa738ddd48f97d84d0a5026fe4356a98f38467c4c54832851478f8272173721fe3078d1373a6f77f314314614d8f4ad25757b968242cc282bc9a4c27288c11814a688485c5bf07c3ca2e92266cb466deee7cf31b1ec07a66d8744c672a82a4fc7e5436e111879648d7a70691e79fd19eed9f964d3747b52fc8252b672ccda235f99c245b5cfe61bcbb3910f74ea646d967462d5bbcf5eb93daefc6537c8709bf9fd4981b9f74782617950e6440a29aa44c4752fd3655e303a8726c24738f6399e33572e091a5aedb32982795ad401dfaa1bca453ee17e1152a801e939fffb6e12fdd5868b29d0bede1c3b1ec70f6edf255c6e32534d04f01fbb6e8e7206bd982e3227449cf82dbd5dbff73c3d43935f749b970a3b64b6c2dad81d2a72dd31adbdcb5833bd5f84002e61beff6da04d883241a2a89d44accdfbdb02ca72bf37bdd05f16a40edc1ffa111d73eefcf360eb7b3814dd14c41fde8ec729a23b10015f58f81b51924d4b5d5a516632e0dfe5ff0712767b929ccf7b2d2d36b17232764611d5019d062c31d00dbe28477af7f9985ac7ecc6b0a733146b5acfb872f0f8528ed0fd3a94931cf9e62a9e22a49d2d9bb7526bd52d724a3fa72a3086721756bc87b84f47011c5fa75fe875b5af311ce71ac646c1e4b52ffaed9c2f9172b19caa8cb103b31a94e06b3d09f88aa3d2e61906b2e4c58884506a876a44fb72af0dc89ff629292f33ab9fd3a345837c244395e0914498d1786c315b0370a47204ceeb3a5ee6c9e846c09ba9864c7b192048f77dfbd9db455dc026d7f26697561c425f053f393cc501a94fdf703f5b0a641e77c9fcaca9a259b3b4a40ada1b72733582dc3d79a2b8a9e5df7ea1937d0fc31b9ab2356fe7256d5c60bef985e67239b1845c032fa7ff593c0d41e81fade8bf6b41f6510b6094c2493b1cd54d4a7204f8a2fc01f4f41df224f35c62f7afdf21f184dd4aa70934047f3a71ad20aa72a5b621ff9f570970758febdb5f92854e573b643c8221adb03215accda194393769969f5f4c3deeff3b9b5d09806ac050444788895fba5d0069e2c09b93496d346da6ae9d7f6029f7023666e384d97d42edf624daa2af3a535920c5b3e6079e66f4a834e8f67cdc9ab7c0f2a7a93b29d9c107783faeeb6bfd5717238a105ef872cf788679e34b00d9bd2a86a80b0fc2616923663b1d498bf0940397bfa98e9472a9b56d4a6af738a2541ae26f9f3b9cb7049cacf5b67cf2b01f51bfc0616e577208fb31e0b551bd7b044c044348c22dd7fdbba93ebf9b3fd53a4d8c5b7207b76f2b4ef2956ace06e98f44fea6ac5103bb6e50ba8e90cee999a86a48af13831c7212fdd788de75c1bc7711204c3b5a64a0ed4f5623b82ce6bcba554fbc032bf85e63433ce26d020794f3a7a7dbdc03be824905b085239ca831a20b6d88a4a1c8728830abafc005f99d2b4cd28bb94e1f14bbea8a4903398ae675e63c709517287210136d3106f538739ed23394052d9b09a8050c74d1a67df0f33d9235d55cce72330ddb675c300d43017b4cd98279f88314f8b9f29de06e126447e386c5b96b4c2aabf666e8157bc1755cfd68de79ce8ac9a5ffca76c6971ddb939877d1d78a7242547a539f5027d2f09eb3978c065d8e3b72d768f0a4181f4afd688e876a5172948be17e787ce163474a4ecfa9973b3867d5b1f8aaec16e7ebc99481ca8c623dfabdd5cd0cbe89513b0371c9478b5ffa6f9437aa77ab6d7821669f203526fc7283469a67f0e3358cabba0c8cc56822282b9df60b64e6be872a80752191a9752d793ace6d1ac3976d9917006ecb42011a3fab4679b125236875645373cfefba727cbc2e56372ae2dd322a7278f0f66ac1ae346548c4a77c1a2ac5a50c3ee1aa0130f8df37cc35c6938cbd516c0dfcb4aae7b9b0894ee2f231e8dbeb1ec83fcf72db58c8710e3a6193f1d95c2af3800a0ad1690f03fb5d3ea4b25b2b8ca33f71724d7a85a9d4fbb88543f7812f0cfaaf8022cafb4326b3494c36e4b8f1211b6372aab006d49daa7bb892dd488a2b03ecd330f6e4670d665cb3de316203e8345772a27f66818a6f163a11c24c2cc951cfe9e61cb88cd4fbfa6f2c8bf771d3aa14722918089b9fa05a205dfb6f16422ffc658a1968df5083d717d7f762996dda7d727ff6174676678c8b809aab3c3d21a971fc6234ba17983bd7e2b1e095c31a004b7ff593de792f0f8acfff9551e032a9b09bf422a220dbd6c3d24ba317a1de10727e69e7b577b464030d38ec65845cd53c16e7414a9f0f83f84dd6cbaaf649d7727f67c9436c8422b62f6c8b5f183fed976039986043c8668bba2309fa1fec8a72aec3e18afdaafb8a5adf8aa8dcf0654576ed616df71391e1448b6148bc710b4bd62897527274373f48ddab9ef1222d1651461c290e607be42b8ab1fd8bbbc438b1a1879faf1eb2baf51e9a85398f3ea819efbf72d8bd93e044d74a93de0efc47b4afa8e8c3d4e3849346cf53fdd7a7a13e122ba08b58da2003a233f3daca0e7225bd6e67793a0516309a6d86725b9dcc72b007e7851b1f5b59b885fc0d77aa726f9c1a40326b188f0b8dd640a03560de32dd75d5f19be14255294043cb45e472e90eef62ff1751bb158c8a4e45227a5155016f22295403b04f4b48b611880d7256e3763d080a8a03f40163978508fefc90158677f0760247e3f6a64a328e4a7234813fba5d35e0eff3db696d7577fd30bce7aa2b674ddc780b36b56391660e729ba9ec37c5dfebd7a2aa45fdf25238c8a36da8bdc94e08260a565b0c13949b72c10db9cd69fdc8f9c6309b3ef1e6a3ac8421cf969718a54da3114e692e433e5025a644f47e99065b13332bbb0652a41a81d14de3ad22326e4f955e69399b7f72ae1b099b86383f6277d07ca059cadccd736b85eac036176324e1005a201b1243fc8e1e0431cc5ffd41dda7eb42de334d3b8d7580f2dfcd9788f7c18ef6951d22cd12876968ef58392c33c53b84f5d6e7e9ce5d393f9f0759bec6ee901c187542f37cb666523d2cc248ea89fcf0ce40cb5736c99c1018662b6390a546f9b077720f5228c8aaa030331fe702bc1bdf0b4cc247236f5bdcbcfab692b9078e0b3572ee0a92b522b5ef5c7dd7eee2b0c2bbdac76dcd5e9f78b4bc4b769c358406365001bab21b2f71b7dd1f1182a91836640ddde18759137ebf0d0d701c80d14dd3725d14a1c019f0f8b7cc15480278d70e2f09b5baaf8e2fc7d4085ff66269a705726d095088aaadd093ce68ebf0ee0407603a3a39d8caff86a5ce990dffde6b79728f1f42829ac7772270a2162169c868bd3ed19c15582726fc4ddca94730403c49a118c6b8aaddcdfb75eaed9d0f70d2ce5497f4248057d830ad0367fbfc9e0c72cb20c6545af6fc0de96a01da3581001a96743cedf41eb76252358bc1ef0491727bdfea3a36c0dd5b728f566b7eefe1c028d0e64a2aa66fe46d273e7b165aa2459f263b0cdcc4a94d5eebc902e611812013ba07fb42e5e94076dbf74fc4349772b4ccf619cd380282c90a9c96f7c3a99c7ba543aec83321cb8340b705d00e2d72ad827e6f392f3b9f1c24b1b79ad058401d70e067b04c1c856383c0811092061b970166c65cd48a52882b4bee592fa42faf8f1ed42e0a0f53f3fc4de37d0e3b383952859785872e3c973e566e3e286a6dc1b0c60f79f46fc7783c14ddc788e972c35ca136d92b97a81c1bbcfbaca1b6ed4f8ae1168aae087edbb8c44381ff9f71b2e90f3c93f8441c4d76036a2038c56fd7c840d0fcbac2e185aaa3614bfd0f30ddfd5441f31f7d191981d180ed7a5d393fa747edb7fca669265e5cf40fd10f72ab57e89ba0983007b0e6abe0077abebfe6cd05524877ec21b1a0ea73a31be9729c4f86df19c8a3a48b6122b95046d0cc205e936a3e9d51e8e4bfa22c7c2c68335e8efd735e889275c617089e147e93be07cca720f931a2e0366e6604d8d94672ad0836ad4eff2f17e448068d0c2f6d713c8cfac54aa7233760a488d080ff2272be9b7eb83224e5fd45a4dd18e3b5f4c9cafb64ff099b14756ffb3ce5d867215fcbd10b9e53aa25cdf0dcab5e3a137b206479957c27d896d5c44b08f6c1fc944cb5eb265109b15887deebb6d1d0328d707ffa0e2c6e7316a19bfc95726f2e7e7279bdde7f70074eb50842a7ed6f6b0dd46a67a70474df269c45be305bf5b2824f9a378dc6ec65b01b2d7bc2b287e3eda1189a0515989c1e408cf50a1be095a753c58bb7c3491087af5d16ab3be2ec90a87657c4433e19658cd6a202c72bbbe772821e782ad6732f510027dad1e346f8c3b047b09252d7f52b6231951d706684727c3c9d4a1b76658532c681fda95165388b2504b186ddcdffea501f206460924ad11a7446300b2b8ae58a2af08b388fa49b2ab6ded60d27183c4fde87ada1315c6ae9167f442063e8332781236d5a8629e6c544452466a65725db4a5e5259fc1fc056a89f9a40bfce8159c94e34a091aa52af62ae64a4913076e0d989f654e7722c1b847101d4dbaf29c0065c59dc8f3cefae5bb06089887f88ab3b837eefc87155ce698652d7593e221f4fbaaf77898daa3e9b051464959a0a9eaa966fcd301737e5eae011ffa5c64bc2ef3a5fc06961d35053e761e9b41caf23d050f452517280c93d41fad59aa6d61fe6770c583bc5023b7a7270fffad6a49b9027bad72326801d4846b00e1bfb78a020e2e9212adfb48b13dca52d64778dc38f03a92d3d4a0ba857523f6ad37895a1801f28e70f4a6a522fbece723e2f0dc5ab3ddd4769208897fa134f8d126d0ef22150b889dd83f4b83c06195f7572458dee1daba58a7289942505b111884ed7168f6f8f0d6aefc880fc7611f93fcdf27fc508297ca77251e04b93d3000c21c8a5860a32cd5c4c357553396cf5e7802c956ce90cc4f520cfb2fa9533316406b718049769697f4c3c0740d075b62e08fda125156d4ac0721ff50b0d6fb302a5c1ecf5f100c8ca9368b3dc921bab158e12d8eabdb39492724460086d09e78b228149658dbabcab48ab8140f7ab37d1488a3e184a1b8e392128a5f8daabd6fb5cda31666a87394c4611a4af2e691cc674c1e642ae51e456722c204edaff952611b177d6f25c1c3a300c39a2aedcca491d4b5a3a0ab1f78272e724493b1728389f72bc313dd749e7f4ae0601966113ab3f5521e9d3b22fb255862b3674552272f809f7fa179e5bef0d23849a796cdbe1f4d44e83579b0af64507c53ee7979ba558b2e0e5263132f7f90d78f4eb8613f4cd26c507d20f14b56e76d04957b453a4abae0067bcbbda41dc9a8a2289a0e68cc4a8ae24e6c6ed59725e770fc3bf7e6ff3db39003f245f5c1b39008407ea797b490d011eee97ba2316e2ced0e09a65545fb051bcf38ee39600bb5e1cc03baa8c0a0d7a37ab14f3af28ae6bded8218b84b4e01e014547fcb53b017605e2717a2f131cb8b4e5449d7f1f04d3ddc8a761fe53f143c5120b3b46c0e67b3eba1119b3d025f736cdd9e074721a2609fb1ee9c3de274e49d6b774488bcfc17bc30e90844bf30908b8d1233f726def3c0cd500f3ddab22c0adaa0ac8ae6dfc494fbb149bc9a5ad629e310a39720655a5f5fe06fc49d4b03a93956ba03f9bb8347ca2f065dc154319b3aa7ad440cb4ed00f276dfa359144f3af7e14e29d91ac3d776f19d49dae042dc1ef2dfe7254b62f84a60d4a78ad9a0ce8b3d6f83c6dd08ad02768fce7e506b1fa7c655a724581c059faf1bad3c5d618cfee3c54a2c019b5c6d41fe066ad3ab7ce788012723968f3afe51ea8165cb6ac1dec7ae41c45ad29d22408324675e1255e716d5972907d45dc6419a3c32d5cb95bf34bb69fa89404eee1bcbe0287af0abd5a82cd726086ea8e1b10ed19a214068a8ca5b1bd913c969133d10dc91a3b7cde62ef707295223275112124d3216babcb200bbdacc64119552cf310365fb11bf25a0658722b2f391ec543475ca5cf4e1ccdd6bc9acdc587f867e531606fac70b2fd2163720c2f6babd92492733948b93b4a4405f98c56fe3b0ccf3c05dd96742a42f969655ffd6cc8db61b9fc66210ba5e3b3e7947b4f486ff02cd03e890ef932c6cc1871d441191615f191a751dbd9a1ef3c08efb1cb706a7874205b7d93d97547516872b27c7514ad6f1c76162e06dd86abd53ea0d74f16b1f25ab798bd050ceb7e867261c9a5bbaf59486978875ab95120af9cd2e2187cf00e82148d9d8b96e0ff64727ad4f8536e4fb19dba379a34186d85a09ae82e8285687568a5e1831332a1557226fd28e4c55d20fda119a25cb06c931e6495331eaed43a7081f63c7244843172e83d5a9591ca3ccbac9d7fd5d6f47f8056c6ad602352d58284c3824a4c09c37218e0c924542aa95e9353ca77fd26d32c47eca7aa316aae8e6cdd8fa19c7cb950626a1fe5784a889b747a4efa2a3778996080aad0c42163f91f707c4f309d536867dd19ecc0605b796207e902693a35e4fad103d231429d88e9e5d7ad73494816712730d8c1427067e86a295d052f470a359087754c32162e5c9f96b354d2f9727ce9c12a2779356024115541f25639a691ed953e227f7a03a26caf958e3370725a5a13c8fdc84225e337bc9a2bf73ff362dd1f127c7a672eedf29ec0c256cc05091e9eaced2130fd1b779d37c5081e20dd07df3b809420de3f5d68af34722d7274884f4e8b9cb570fb0dc23b290fca8ce44a489b8dd426ffe26ffc13a9924a72124084e7f99535c4aeee1c896fad68c70278b23f39a5c5af4ac208284b91ca262287ca26ed295ff49330903022174bbfb2cbfe09622f2b28eea188c835271f5724a7cd773934b45cf4098938c4ed29615f8e29ac7fa724997f30971992ec9e0cecdd60387d4b3bcc4ceaa349f0010b4fe04908d416b408d09133c48f80a2347239a31cbb908d02848aaa7f4be162a1d942f89115a08e96182863a114cb9b9872b4307abbd39c3e9f90f7835e4c3d14c73e50f7669bd6cea036c5b5eab874326ceb8ff2059ce09a62e7b6c3125d20ac2b70e9f1e3d8724fc228ad65be13c85a1049eca6cef71e3e3420404c7905fcaacfe8a312c83dbdb79bb9a4573a18b01218006510644809d353c42bb6198e5c1a7e33fe7c1745c697ecb7ee752a0d131d40f7f61a54952296f812689750b8dd60024c2af996a069787288923206ecbc32129240a185e48da763795b2b31201e219b22c6fad3064b41e0b2de7758501c5825628c8f39fc98f5c3b08000395644d6e236795f1e42ea7c698306994db0416327dbb5f92f9373f15ee44fb1da2e1bb0bb7e3cfcea3f41738b6c11821a14d111721823afdeef3550be1f0c8b1d992db8d5be4b99a3c78825632be82cb0c3c7bf72c26345baa1cde082a4638ca181c2896ad3a00afc0e4297adffaa2f397f865f72893d93945b5129347fca19bc2a15c735e800bd28063040bf5e39f3855c846a7276ac5a7aa25d7ae8fb4a77f9300b24280e0a912b3a3bfbf9b4da7db7a373fe3b7695c5480753759aabe56c3561798e3b6cb25c27a93c47de00e99f1810ea61722948a84fef971f10c65c3d7ad7374e05f80ef782f5668bdc7c4543609cdedb72de1b0f9c112ba8dce858aa3c6439d7dbe171d5a29bf9f3970334373a94d6c33fe8069903cc62547aed0e51fbe15e180f8c6ff77d01283110ff9e57f49970c5714af7fedaf531d06fc92fd481e829a8c937507240b44ef25eeec81493b15ca716a20acac995db30161e5023aaf750495d0deb7ae5c3737b21c4e7c9d4521839724c698f8d4edac4158f84fe2af8170b44c04161254fbc25fc7868dc21d47e5372fafc40d1afa7f902809cfade4d4a815a8257a45671d7404153f59a581d9fcd53c9f89e446649eca08bde77543039f9e09e638ab1a7f05a39413be3a30969d9720548cbcafd197a955a7665b12385d13cac9a72648a84211998b3f36372c12b02524f47d631f3f648f6b666b83672082996519a9dd8151a2dd42972e80573b30aa74b49975d42efef545876c2b43b7ad4a8d93443132545bfc7d1a57cccee98729d6ec3a2fc6470c95ed60c3988ec4df7003b2d9c8ea0f25a72beabf05d2d9a4cbfae169c25142dae672e7c81d83fa550f4c715babf9d666b9e91a5c86ce9a154007ced488b3c1487d5f7dc0e5766c555cdbd7e9f78662645d1628aac5bfce3726a9eaed9dec1759ca17f34dcbb4977f0bae720fdef16de983504f134dab2dd7264ebfe16b3a074659822ce2ef72d29ce7bf3c580cdbd85637485b36ecf851366ececf75987f9a1ac1fdf58224a15a8bfbcdd70c34a95e8b02d8f357cdd8368723fb8f38a6b887370de80d300dccf05ac47a8486379cde23bbbc5aad01bfbd47230b496ad22998f3ab544a52abc6b8b7339fe721e3bdf0f5fa28b3fd88c9193709ba7f383a7feda80daf944d2c6547b5efb06d120480ab2f6fb990b5be1c5ca723643985fe8a951dfd708ab2e63bf8e77ff72eff59e4571ee9b2a21b7cda1a76327b441a35a65a17231076228b68d5d8c322d01587db79e6fbd624bf74eb731725021bfb5182ba38df47bd5df601f6bac35d764f0e97850d8ba055d29d798a907a5044d5248408f696a464d6f30867e90842e5e77d72b9f70875a7e30da3dc472a3e22fcf40ba659e95c9257fd79a1ef1cc275debf871e35e28af9647a0ecc304ff342a59c8742538495e7badfd6ac3282e4327b205a57310fbc4565473428157be08d703bb786dd7e095fa6cf7f6cd97eae99ca44f3db88fb5f41ce1379a4f44ff70ba0be778d668002c2a505124d5bcea47686a5abb81d737a3a36d1f342972d6aecb405d5c35e4fcac150201ab9f3578367ec83e48f3eb28d81e0a37a05472b9ef7c1029791b404bc68a1c5bbe8888c115e2a2024ab7b67c2aa81f7d029772c5ad8fc153f4da32872d1fcb5d0aafaa7e852e0fbeadb702383d7cf215319734d25647115a91315b0204ad99cb4f7434bb208c2d3638e286d31b600b781f414919818a3bf57fdc320083cc0d945b4467bdb62cc37e8a11f9d41a301a1395441c847bad4a53af399f8e47b487b5dff33d366ba196fb807f4e5c1394bb8c99bc5b62578dd091ed1525187d74b34346243f4c0bdaf2e2bac03849db326794999f72e6a1d140ef0b5a7645ddd75913284c11654a0e4b3072190cb50e4ce6acc435722d7c6562e618b75c13c853659cf933c1de25ee66aa89a3a314a60a2bc89fd57275aad676e7ff8554f622fd97fb5181696994dce2e4183f5214d5eab7109688251e1dda036e8700e4f015a806679b37c3505f0209a2ada57777d7cb2497345c536e64eb9d11154f2b13260359ed3e59a1e939ac989a1253ebd2909a9ced1eee6e2a5a7c53f5f7183f4c8fa123f8a9f169eb25a986ecfa875e5bf18295adbc1a0e8356f1028f211764db4e9c86164f62c81524e55c0bffd77857a83f3cf6517a4520aecf04b9f0ae4ec914a6c52da0ca791fcd9a1c986c119630223dc3fe3e780e4aab47d715befeb8903353ccc985c87e0fa5ee6b453c599894795b8d60fb99728a8456f347dde960f1d10e2674e9cdb004c9c257cafb6813b16e0212a485f572952bcd4a65d841046b6a4ebd863bbabaa0eeae1fa8be1d17b575a1054bffb27295f434012fadd43e25cdee16b203107e643455f791085d9cffeecdfda4fb4f2304b0d50f21a903968f04aff7f3c852fd97ef879735c1a54a1833f86e99f8e272bffe82e009b1da18a1798ac246e6100dcde46ee4ededb5f197327be824824d7220252ae797e25004eee3cdd4972ecc30377565f5bd1d74eae5a025cd736c60728ce5d66488ab1ba3b9195e3448b6fa845778177dffe16f16491e70831fdee96e54efdee011322c2f5fd57cb5e75b329dab43e74fa18ea7c51dc8fa07912390720a393bd9e779b93891a29c2f0df87c69c71cecdb43364708298913d45ffd67726b31f8726f2ebf7889ab41b2eef369d189c651046ce0c540dbc585f4316aba2616ca47bc18c9fd7a3ad6e1a0d4dfb6725d1715fca2a9df1223ba56011c58114061dc3152d9171ac238959654df76b3352b49e193a077cb9698b18992a9553d7281bef851f99e7c8f380ec335afe02492256066e2a1cd3daedc23cbba5c8a0972fbc300db1594d59c1396e743d1efa732958d85d9e72b3b2e31758523bc8f3c72cbcf5f7dbaf5e0eb580b581eb161c22c356d80ddc7648cedb5855c81646dc802984dd74228bb9705c03e56cc1dc28b8c90b1916674b28711b8aa19bd39b43a725bf0beb91df56b1f2cfb293fd69bf9f6640b2145d6c169cb18f1d524e29fb872b7082af4149c9057fabb0199aeb0c792d9a79da61f84ab86eccdb57595ebf728dd069e43cd612a5d0d7eb540da6be8b0e56b328a7ff2b2229fae6f9303229272c7e4c4140ac1ab7a645e8b27320e43a755e648232c1639aae76474650f2d957250c64dc5bbfc672f243fc2c7b72ab3d450376f37d9a2808a973968b423936e27540daa82817b6a50274ef0b4999ee1c371a8b219fd9d4ac868557ec30c56746b42e9a1384d43d600a30189b8f82f2f33853f78343fff819e7213a8a385c9af728793b506affd34dda7fffec1a049d76c7a831fe614c943a3c035727ba5602572c330032a044712629cc4e4ca442f5d4720f1e6cbcb4b605090d3b55b9cce0c7242d4c12499176c6a18a8d90d0614bfaa7a02cb10053c8246499e36c0fee4876a599308addaa955c081b3c9162887bd8ce28b0a94996c7349965e485d9a44c7460372124ed85ff146f05fd221b7a4f96719221ff0dbaf76d3832c57f5c2ce8463e81504608d7b4c643de1be67196c7dd57617ea9e8905b6199be90aa19ac8950474b9a3547c7d34f90e7c58097d8eb0a92b05e8d348d06e2ee58b990a609d791ef522d7401ab951945469da0eb842bdd14306d710de2d54e8e7bd5eb524b4c77217d75bdc068f4e879763b483e491a7f43cb195fb07202ed533cbf10b209f45726e4ec98495bc8aa0ccdff2134c1adb749d8c724173db75940750bf9cb2ef753ade1811d7aef91ad5de589bec9cf2c748de5c337d5ee6670a4e0f5c13aa1ac07297822646cf885db41db1ae893b6791236a136d8a64b5ac675b7548f868cd7372603d418f71866d0b9c894501f4bbe025b0d583173c77033f1d3824ac721a787263be46135c4732c9365368cabdc300d7ba531c69fb07c254e23d72566e861472a11d9ed31b0337f9db6bcca530669687955fc526fbb1235f7c155a39abb262002fa96419120ec239cdaaa616ca98b6aaf3a6026dd96cc55fe558f61da22a7a0ed47649a3e435a0303f4147db226fe7ad1945ab094544e05d77cddc16f6e329725b879ce3561bddd2e913339f6335ae9591ba60228a96f3c75333b4167a36494399cad58a987494850918191258ea0b156f5998e1bfbd142ec2c43424718ca8725f86e05804f8ebd53b7c0bbb056eb233c692508e01eebb7267131f8f9b97bf72ceac15362aa4de9201265fcb6481dc3c480c79f42ba7703e1ed3c9c1dd0353461756de9bfbf90111039ed198415b6a7d7434f0a281e3751aedfdf81156bbd771a9bf7e271a7f01be1a0452fbe393ca1f5c3d4ae6108b271ec55c2a02d161d60d239ca8eeeb7380ac315a84aa2a3950de9b56241e0ca9ac4f4068d0094c70e3724bce71097484e5941b9b4e658eafcc249f455a88cb1c34ea2925aae191cd5d1b0d3a7efe01dd74c3bd444a1ac55591bb06bb31b4f61f5517bef8e997d9087b724bf4bc3487bae530994e445263fb9537c593ba545eace9b184c151307c475f727911c94697c99a0fc97679723ce14596ebb0f5cd5de126d7e8c5749da8bb3272338d2eac7dbe02a06f853f658e118f8a907a70c96a1d800e716b9b59198b847210db0e2af59df2217d34386b8dfaa4592b8caab562f066b639b6a26685ecb7440360ba32f4fe2db2d22ac8398dd1de5b3eacb7cc8e1072df8670052cbe404b7280d953106d4a5906847e3c228a8b9ba3251d8a5a7ce0b27467ba94307a0bef72eb2bb94a09bdd8155c52d4a6c54bc257e6f086822aadc059b5943763f2940b4c478ddb86df1646db2a81acaf6c02dcf02fb7b6fd53eb4d36e07c8487dc22c972c8348381c07f621d237392b505b414588d11973402c2ad9b5a129a6bb747a2726829b5c7e4998963c196a60cf943ca1ebeb0168cd581e76c671a3655111dcf724eb692234801ae2d0f3b5474273587df5df4923fa11192a8d48afcc70d28d82cc42bb04fbc5b2418f0b5a6d23010299d60b88373f80f375582c72cc9d9c79f7216d43638c32ac2ad1ef651550b347b996aa92aa1a8fdf0dc3e4a82321746171bea7ede900a26f1727ba0086f1ab84f324662910d5f9327583fabebe2aa45c27266e9a6446fd3001d02eaa4adf830b06f865f57abf618ff67eec10cb30a607c72540449178737a206046c0547f478ae28336ad025d77dad65d636838310413b67d56859a15effbec4df448d6c095c28a4c24386572bf255cc0e52416c2ce7c67203abd2ed9054e42d91296ec88f643e9dbd1fbb305d1cca56a6498a673e017a312abe3132143fa67dcd70efec9ec4e7132cf07e1ce79320d6faa9f9919d35295c1db9f9c2050c56af453e935d31ea001d44427e552cf06cb1e5df1313bdf98246778553ae319fdb8d74319726b4dff8ca75a36bef07ca4561e16a9f2890e8b6687a24489ba7bd9348af906fa6d03b1f96657ca7a74af0f7c8c69980962ab85c719fa49601b0efac189b075088ad71ef544206e4552c3126392e7471989dbdd372cf44e5af049f2ac5a6f26adc6cc90ff09dca154bb03c7bf2271a0210453e0f5b45ff85ef8195ffd4c8eb9197accf668b80e85ff5611fb5cb6248e566139aea65d0abe2989c39c1eb38902809872d3be7812bf85137e89924d8ddc61afe79a8727249398b59b1a32eba42e600f30bfdcd78b0eafb7e83469642a0b84aec94f20f87ddb409f1ba657582ef2fafa67f8d688f1f1d98fea93b02967bc369d9b90c12143c7c0da5de343dd7f1ca0e8f338eaa2d9e5e2173570f76f724d1dac054c050ff50ba1dc7cdbe7d378ae8fa78da494c79e447f810dfba4245c592354e91d7720c359cd4f9f25ca926ce1352325bb2683cfb4fe480bc3deb73fddf27c591d63b48c5460402cceedcc50a7ee0cba9e7ea2413cda687477860b1e1a327f625630228616c74766e90a211fc6e21952181ea07eec3b174501065b1eb37acad08eb727a8d25d3fc2069f3380ee29a646379a2a0e2b2c9af3da8f8ff8f1da2d13fa8726d8141b8f1a51c348f628562d9bc1f9a316341d923ccf895b7a221ffb63a4e722a981c7531a90f3910c388af1e194d72b36e23e18a68f11e054673c1f3b3a46b15dfb6512921927d3efe1303f0872eca66da0b8dea298ccee7507166be22e67295695ec8f5d9bc7e621b14790d58230494ca680dddbb13e284766a8fa2265f0536e16a7da7a5ce1f070681a8d936a51730b19fec3b403f0fe870e7696eccce722549bfb0652b664b25d10fd7b85f9d401c81e029083520475ad6ab2509cf6f6bcbe86fd4bb5527ce6b1690b2eb32758e10efb7d0cab51f56fdcf62e68b9cd572ccc333c839bdbbd5ee7f59f9a08fe898247e36892d849e8d21fda04a08fd020f1f3c07d0c3d9c0d2e046571440e128af3489ad7a755c0e35d6297715ffe40872880f9d6210e046a5f194b97b63fa6a57c4f9eeea401219f2f8487cf19b4be01db5e2e7ebbf21c3a9d35f1ca7b27f55505dce5f92041b234e709ab229cf1fec44ae5de99e9a4d26cb81c9c578367ea6e72a07f60fb3fa6a6a9d9b3da58947de72bf6cde1733fe23547f540438f038983e9f77aeb5ac18cf65370a381665bbad72c92b84afeccac2bb448401bd79ed305f833b1f8c752da14531753be9f5701f3f3558ad0db50ee0fe03e67fe2e6b57c041ae29de9757b9f45c0e2c583ae5a945a5ba2d9a6506815fac25b937918c02e004cb2b7dd071706541a5e1d12f870a95754d88e5d86e4e3972f6d9794b763d92f2aebc8bdc3967a368e4892737f2f5f71b465efaf58b6bd7a7a590cf1965cc131a4271e5b645531302dda34478de0b5723bc11076414c3779ea9985fb1a711ddc14c39fe7c03ed00948b7cb0687842f729414e73b607df07616a943b0675f4bb4d9590fd22c36dcbd91d2a7ca46789972625a0257f206f5bfedfbd6cd938ab609b44f578444a973fdccac090bf2d7c404df6bc3e85bc25d80a7c2dc3b8fb7ba8edd2693540a67e1a3e8b193ad6b17fc20d487eda73889e3e2723f335b37edb1d27cc2fb713caad8146e7de5199477e07258af750c5957d621a2b20988b5c89dbcf569bffa607337382e83ddaefc1ac972ea8cf58818855047c88649e24edbb9ef149b9da38bbc6ca41b876f8be3015d60aad1c8df499b28e765aa338f4a40b3869eb7324b21989d3d54a7ed4bba3af172c64719761fa6c5a9f1d62b74cc90e8a16a7cece6992430dbe96064767eaabc57b1829fe88cbe60b0d39de48174d36c00e610282872482638cf9f6120e8960a72f4080edaad572e48bc85a22dbcae1a2690f21cf7042af0cb7bf521e0a811741d7d14a8051aa5c7d95402c6be660cfd050384484bcc6aecd552032994b0b44072bf45ad9d53edf1c41605a4493b10fc222207513e2704221bae7cd02f5edb0352f58df93740df48399106698a5f9ed99d988d6d93970746c2736fe6630d410b72b1cd75470b2a34bc4ed73c42ee8b3c518ffbec98920bad84c3bb0e057d7045720f353ad0136eddfc947630a882252e0f65046d5d9ea7de2dd7f7e8af97a60b72206b7371e75b55bf8883c6776001c34ccc464e40e523bf59b5511bfdc414a772a381497374f9b84892b31a47e4d73b4e1099db4798f9a1997742f51b6b1ab672370d855c71963e91fd8cb4f5c6a3bf81a5214dc8c48aaa914299c2c97abd422e22510d3338a23e273e614b24cbe2c91be7ec70f0ae62af10834f47630bf2cf566fc2824da5b4626464bec483a014b39dc0b06d0aebd83590bb3e76450c24ee72af0af04400b0fcc473eb609d73c6b5d9b51880db3c19876da65bc8d075a7d10a5bcdfc68ff37689d7cf4281ff5a4ba8d5b6feb87d2d1b914f96280edff1410726039dda51036a36ad9254c5df736eccd85dc1f3c031c4d5d938c61cadeee4f5b05b451bda563ee33796030b7b7b4c66d13458d3f5d3c168fe925ea70774ddf722920ef9309ee574499ce88dedafb06002862a5b53a52d0fca3711d3195b1b66515f88a1a82399a907dc0f716c0d3b119f3dd004815a6b92c0cf502ea3049157281eb4fa7e7d141d86719a866a2b86f7a9a68e59d3cac883878c74c63d4b8277207711e16587005fb27b89995fe4efba51ef1d1ff269176f78817e9d7bcc01c13af3d7f31fb231b7368947b77822cbf40c164775e1cf862bf5b0057f473573672d3af267ce5bbc4eccdf246bf4242f3fe3a3e9d69557a00badcbf6f069dc17167dccba78f03c3565aaf5cfbc71580d86b8932b9cdb7267af8bd33b4fdd969c872c99ea3777284257f314cc522605da0013180bb237df24481a1ac7d9be600bd721e0d71f34b90d2145aac72defcbd44750de1b7fb8ed4eb2cde52d57e2201c77215509562612f95241f42a8050db3c91b1a5e916f4d493ab648cf5191d7e6d472090612e698eb6619c8dc88a926b813f6ce961dd5ec9ba21385b7099cb61d7772aab52979f4f3a80bedec901706087c39d6cfe13e05e857fd5c18e318d8c76e72cbe537baec080d4e809eb787d4669b2cab2514fee37ee6452cb1dbc6ccbe9472fe6acf3b0c2fa7716cbbac025cf5b17d72ba26347f6e4057a70c317c1b215f366315a9822f37c727dcad68dd0ee9073a2384a74456f8c2d59bc64b7a2c861e7207085b03985c5cc91d7fc6c1912108ef4a778042133d7378f306e96b931c1e72445fddeff189f0b54ed80320fe15e1f69573339b55ef39266e491a2df7a41e650887a663520de4026ca5f57429a4e06a0ad40ac92c45c1d683ce57a8dcf9192a4a3a85dde70f76f6be772d6e0af10d613d3f5369c8d767f372c9251f616fb672c7d0c2c3d28c8fb6265ae54ff89461287d454594ae70cce5e97a612772c3c17281d2179ac5fe1307f8e885973858c95945c3d19962c251d38a0e3bfb7aa230729d8f77dd35aa499efead069c36c7fac8826e730f5724f9d47d561e732254e06064dea091d77a466a291d5150144be2351829af957e07fb8de85eddc1f91f66723290ce70749aaa3734cedd057059378fcb4ab755f60864b703a40c83ee6e1934bb1767f9fac67cdfb9f6bb77fc28b27ed2468686c59e6b77438b81ec3cb63958e4853c7a41844be3b32e2949a3cda82b46dc7168aef191903bb5cb71e933b77244f43a715c2d9e4f7b9e1a83ee5d7b6a9b57c570c268ee3b04755c9d6221a3261fc490665aaed6b7a457a4ce58298f16d50ef686a97db1ffe9cfd9a7c576450855c47bc1bd4d110b1e9df6aebbaf2091a7d9c22fbbcf5bbc2104181e5b4e00623e97ed83a36e9e01d29408fe22178060570887fe6a5249da52abd269e216b2138082ce28b22b2300b12e1ce8d99d8c2b34effe367910fc88672ce1a1e8c880701fe46ba7dfc5890e661d040a20c115965cbce58c5c9fd727139cc47f449cf872b1ad6b874b5890f8c880c2523707d429c993ab887f3beee8befc3e7c4cab5a475a8156d04b43e571ac7aba678beb711d528bada714a0e664a572dc8fbec54e720b3883a9df3480bf1f90bdedd5fb0425326b62af30d6d87bd9814c3c6bf0bc7247cbfcdf3823bb8541692cf29f1740af913e07cc7a6c1ee72e4448a4a869fd72ee1868c6405414cd42309f0b46a56791626ebc87340f0736e7143cd449c82372b6f22e810980f5c772a14769c0a94b6856661e5fdf400ec96e53eaf096a1194ba3f062d9db08c19a6b7a67c9eca053ee15c54fd60cfde2b33c5a99dea54759724bcff6f6817397eacc550e2d8ee830a14e0d22e49a8260967fd8d373e55d17720cef1832e829a46ee5eaa6e47f92db1422e1167aae0ba0f727ba8e4c717f68720c76f18b82368c432ea1d3f9ace76ea722ed9c7f3969153a4d434fa5c58b4b5f9a72592a30aaf09dd46bd545927acf439e285d7367cecc656e2761a7d7adea70674a73fcd6e893e3e0c120a7ceed56c0bf5dad2499590bd9424f7942f8d7d87233b9967ad932690427ea702aeafe70926b7a31cdd0f09924db2753be8758d672dafbb9dbd6359957d4c2984282302e7f1ee1d973f9fc6910b10b32de15ecba0469141ef0194b143c5e0caec158fc779eaaa69cf9b95f1c969aeeb995272608729d41bc3c009b8767a247bbabe1409ebb25b12b2f19b6ba801ef4aa3015738b7261afd07a1feea672099e09251dc656e006e5ec361259073a35ef54844e42470ec1e3c85b053361fc87b3b4af64b3d221b8c01d15c128b0359d34d3a905f71515d378d16839fcb921e373863e92f81404578b5214cc5aafb1c463ebf9c4527872487181edff266a8855dccffa8abd69d93a19c086fe95b41cd770f2a0bc5ca372dff1e0a8a3e01411471905bb83a49cc12d6c158cd155518f504f1e2121990e0b2386607c287808de6c333972035afac5550ce43169de6d41061c3d56ee06257208388b087b70d7efb374cec42b55a5051bd79a057c1c8faa4b63ecc9413b0772ad7cb4ef44d6530107a5aa7bb972af1d8d0b06e79dc56dd3fbd74bd72dca7b72b3cf7d1599ef9f58b51e1f6a0afdda82e57259e987a8d8547efe3c7e7d6c3472643afc36b4f743a345d12c989058e9206f4c7cba552feb29b6d0956f3045bf72174693e38dc3a5703e56fa167f39ec703abe3e9e3555f0f1622dbcdc8164446f4bbf23770ec25899562a13ee9201aba28c2e6c4aa35d6f779467a56eca9b857282b3bddd4d3204e0c7d6c00cc2ec931ba0c340d0feb38ea51457ff7d1a3aee72d4d6906b5f0ff1929be7760fa45a169e5863123e63b4e49ae704f485faa30c72485d0c878d519fe2a4d8bd0383a9b5f4cf3c348f5095b264470978dd07bbc6073623d1ad4cba7f7407ab39403d7ee68c0434825739ab95211edaca611a9214726b0e25c905aa694b1712f0bb2bd6f5fc3fe50a491070903b266302fd21e01e72bbb96d70d6cab1740fdd6aca10f4730b3a92358ff83612c51fcbef08097db26d057075d989972e5432d2c045b921c6f3a22252b4384a8908863834bfe3e89172c85e535bfb522f644e0d0739952e0bd90a16ecf643c52996c3ccdd1467b84b727fbc39f78cb03e99b6a4af17a8adc93d70790f1a2fa486356a736d14c6af1f72be29d9f537def43536b3417b80a6e4aa8fe83b711d724ccf2fd11174eada8a7210926d719849bcee4f7d94c6a344d764bab6d37dbe1fa666599026ff00cc825157db9c3d35a8d79e4e2c07835c0c292a646d041fee245b7db12e165b38e4fd6123d73094601c5de890d23de15138af054a7a7f537e2450abbfe115c97dd66970c49d441fd6ebdf0d1fdb732728e0d95d02e210b7e8df6909681b60938204e2358806c41f3efe33fe085e5c6c955fbf536c2f406272e47f38880cc0ae023d38706f38e79271f377527497632d6a1b2b430bd901aa3d1a69a8496f90a21a2cbd72e5ea050a1857cc784d36076a02d1be84a757cfe45f1bf513e82091dd00d0207226b9efb5e6da12d79c56ca67d843ed457bdd67aeab5b0f7fd50bef3f6f6c7e7286f5c4e5901a09b302d118298a9bfd3d7c0b5dc8dc32178065c550e4bfee9272467439a24c9f1c0370d782a24fcab420856ac782987e749088933e804b8a31722c477464dd0f8720e2e9d219a0a94db96efe55f4309d47cdae9df688930bf5728b8380ef175bdeb2068365bb55bc06f7acf26c5608b8f16c9f86f9e730add0722b1c359878e57f5293fc10078641ca858f5a84e76ba23f819f375bec9c8260722ece8b749c5620189583cb632aa186af9386cbbd4c5feedf7f0a1069ed461a7256703039014e9bec4c36406f9bd17c9cc4b1590dac2c5bd2e86bef7d52e352728e4d068ee0424a7b785940add454098609e0fb790045f2d297d7984a356bc2433e14e6603708548128b4c2992e9620e668d4a74d680097d6808cdf14286dda7217d80a0dd6457b08e13bd7ac23e2e7c1aa43a652f336c5537f4beff57f260f72ac29cacc24ad7b67e69b9e3a9f87c4dce60af943e08fea32247834d42c0439725d55228e78ecca568ceeed680ee6001c2a7240bbfcc53b991e3f243c8e5f8c439be0613bdd75da135711a3820bd0d36f31ce3d41811b6072b5fd5ac78126013bf591bc055e1294577f728ace4f0704dc33a7c52d3918af23ab146240b668d90c155c1007717f9cc583b774e5dd76f4c3dcc8b3f671514086e045216de14f86392d136bb91a199751f4e45f28dd27e7697fa5cb8e48e27b2bebe5c9740068e072a9431afff6fddda7d349c4c58b7684558c16a3102ea7977ca7e0bbd7f99b0f7271dd5dc9c536d26a5a7522b237cfcbf844cd2d6377030d67d8c88ae6ef486f72c3ace0c15f0c63bab694b6a8cf755027803db4b9ad850cd9355d036193ba5572b4b0f3db8dc0ed74daebd75934f50d3de89a71dbce23d9c01b38067ad5e991238bf7603a6d328b6ea126d4a8631112e40e0bfb13eba39e3adccd43e02ac60672830f89a19bb8d55fc458c690e1098fbbc3a67fdec83c62f524b42fa4d1a286724a1a2d49b7c4266be665f7ea489b9b3cb65920434dd571d2f82421b9ffd87472520e29d1dc1f060a95900b5d962a2716fd038892f5400d2a89a35502c53d357288a1707e26be29e77e9825e07a01f9a647c64a581e985b509ce4e3474d667f31e019cf9a4898299e63fd0a1b5a2268a22ca294a6b27e651b0c2dee0a3cbafc722b26e3c656c36e620cf83a802471fdd322ac3b28609af585e13bd1d161e39872dc49752c2816ba926955668f1f82093358982ef94796ed9aa9c41643e3c3bb7250de6bc741ff25bcf28bd053d6709ec98aea18588c6e8b5436ca02f3bb700d72856eb78661e77ed6c538ff014369c8f697f99cee1e3a714c7c5dec0227fe71727605950bfd545f6533d29e67d35c86600fe5bc7509c8d89ecf06857cf6aea75f18473bc99e2bcb2e0b503b7e5daf8c876a62f7cf0312ad102e2112f439700e72820f0465f76d4cf1110f0cdb10723da46033626ce71d6ea0f22708146215292aafb31de4b7af66c6702d3105fa9375a573e8639ae4f05631eb0e3a2f9e64b372989a72830bdd746f94694657a3745b87c96d3d6e26bedf680bad43ef17073972d513b8c9456284397da91aba5d85f97f08061ad3b4413af97c457d81faf24872c4ed9125d9293659292af30c1af035369cd5c57fb24305e7037753943438ec72f340aa22879b610f6fcb111d212c1a650fec8eb50bdb99261130391adc4f3d727faf7d8d7b1b01c15cc7a04f3207c750fee9fb1d7d240928f527291e6fd51e72da282872f453d1da9b8ba7cdb62193ac3e8e5944c1dca9a6e53a472879d1ee72fd74774a5662b146df9496ccf4744856ed18abb0ae36ab4355b66e9f74edd372898158aaa090c54dfef944160a2ebe02ad6ed0e9dd8e38eb5e1404419d2eb2546e325b8927a07d9e11fd58dbe109b77558a577dd8fc61bbe476bad7a8e883a7239845ba0f5a3f7861445bbf97f58acda0b201d0821c546b05431235cec0aa3086c59a82e91e0d32fde50cad9ec8a8e8faa9e69a697eb089cee709f6a5706f7721c0bfb35abcd9f338eaeb0defeb4b18997b154ae81afff5c4e72d2287f22c87274bbc9a54eb11d25c41df8e014eb1a424952b1fbd40c4899a83e10707dd6b47234da4639435cd4538573e3dc9801d23942919fcbc145f80a4fc8f79f02c4f77209da45816436cbabd2a377b629b1187d95fa87513ef7a9cb0f7125eb54ab3e72e7fdf1661345606c26b1fd669bad328d6e208c7b78e9c6c9e5de895cd9c3cc09821c61299913fc1d620f2b1055f45b1d8221016ff2588e72474d914e1bf538728ded24dd386800eb6fc0bf945cc89b373d7e2b44b9104607097fc25830acdf72a6602d5334dd1a713f965fefcf723e92fb9c16e8609d4baee9c134c016eed1724a75fae386008424387783751630e9694a7fa623f7abef5b14bc224a2cabdc72ae640d472cb771b51f07dd070918fe313d2bdb5982c86e39ef252b7b37d40572597e7dbcdbb2da0a126582cb10aebde307cb27365479ea7332abf211b43588722b8174c777e6556e03d81b03ff58c80ed1416f296079d032f40989b95c852672223ed3173bed1489bfc98132f4ec7860270ffbf6e3c2598141a0e51bb77ebd1706ceac3f4699c5e55d7811c29c558f61193b04ab9d8db00f47df3b361fd48852c64806c750a1e040de6e69287218e3833eea33d593198045bf71ab2060bc5b726a0631b73847065b66241d66fbdddcb7165e20a0e986b82107ab08d61bf46e72936540e9d72b9af5a2ec2bc54f03cd41505ab9f198ef46378d8d5cd5d3ef51597cd825a5d8e5c9c493cde090a7605606a5ca94867e37dfffcc38e3aedd5b630d5b5d6bb4f9b2dc4e46f776bc93d70d6548a3ec7b9b812d8c850d9d60706d8131502083f37e9c66dfd4072919fdf5b1acc688d6d064e38afde58a7589a3dfd372d750b66d8f3568d992cf22f283e3b59752e017a69d3535ea17fa1374c06c937259a1aaef8b0923ef2e395abea6e04b3797b16048dc6fbb39c2104651873c2015eef6b31e3fcf741d1b6d5702494dc48f60c7cc0fd0c194c3e81be90e94b9ef7277f933676a7cfd63ba9e11bf71af5867ba260f0c67725e9cf34f0135b1154d72f4a352c708df924feef914d6302577c36c03e57c7737b41290117202d64fbb726f809f57f8864a8ca1f3e23f0ec52888f999bc146799cb495966e912584e1972814621ff3d494daba9987593c1cd9c96f4ffa998d3997f7098c48377bee89844586a995e7d8ac34056d68582cd97d40021ba5db4bdd46789f7851f274b06c7290b1d050d9030a77e3cfd645f0b4261f8bb6d11594e032d00791bde00371a7d7290d1d335eb629a020c3cbb0c1a241405238b6ad3abf872192524d111dcab5024a7070baf851e61ce6ee27cf06571b1bb4a2de9cd31eedd0e6dee457e70be11729f6a1966354eea674863d754abe161e19f09ccbdc6c87c8ed090be900e426542491f55cff1bfcef5e989e6253b4f46c39f5f826ae177ce97126d09911a5cce3a943f640b5428e3847d30d293d382c409b48e626c9d18d92c9aacf1fd0638a827383bb7a3f5221fd9d0b27f58008ed21c585789736ae725257e0f96189ef5ed713dca3fcd41fa86c0a4bb83742ccda7b86d3ad85d60de81955e21f9534a82f3075ad51f31328fb033b17290956ccb1d675b8294aab7cad5578259debe8e96f07204000c396d410e649880aca476adf4037c674f059da4e1b313154d2403530772e5fa2fd0fea55e09dae73e26b602dcb81806e48b1bc68ca25a94f462405b8565b66129b6f727313f76d79765c7b413c9cb08942a80bb0cc86622aa0d7cd7140483207e7f8adbb4478cd8b35d14fbd554595471de885dda78ba26d1ca88514f1e76f04aec8a466cfab81b7d27297d70c51ac5d9b0f88f04109388b0e28e9c4222b299d66a2d67f34db3caaace02075df5a3b5a46e703099521269219d8ff6fa727e337ce389d2e1373eca584d98c4495671a0549c52019bde7dd77c1257b61167d2a4587e548f06d35c736ee7853291aed2adcf54e2878d1e47df1ee2a0584a72f121eee5a6e051677943125ea681881d2a674eb0f308e4bce60409d54b082b72e129b747092387ffb63cc272c8c01059b5f78222ac0eb83d1670d41f2454c264e5f2f0f581c15bd702e5c8a9f93ee6d98f9b7379776e4caa1f89e16fe6dcd85d95490c4c0f006d4a336329eed6dc58536e6712c08d3189891740ee6cf02c025d1f05c8e8756fbb6cabdfc51fc865014445fda9cc921a89f3ee45ab62dc89a9726f03b8122cdb4dc774d77099eb9e370e68e7c03d28fdf27cb53b317f030c6a187b1a0cdfaec9c41434bbddfb1dbf571d86b6b739a9e27947070d93f0c75dad72e11e3fbc0eca0eb5202320b3a592b48ce14befbd08f56f134c088ba8d56d8a7269f86ce577e705e36579ce76ee30b269a6fdfcfa7e6565a664294822ab9b1761e0e8bd412a343721b40d5bfa154bcefb56f997bbff3e71c7ea3a364e446d2272a547e5c202f6aa9d40519d029898c14db8ecdad7e1213190e6127424f6587e5a97f4f0224dcd0fb627b23bdf8e73d7d9b0d5c35b3c54720253a50a2b6d68e472f8e047d3b778f7ad9e724af75447190b8febac0175c3806ce54a9f428919fc72136ec065ec8efba8bb18dbd34c038b1eab4cc13031bbb075f201c61c96dbaa72e980ad8f37f8f071d5dddacc71ce24f1b66859b5d2258d1208d94b24c9e8827231f8b8ebecd4bfd851e48b4b76ca772c2d667d84a87c7d0ec1b329715748de72c4f717cb71faa0833cd35350c359145eb9a3b0623a9722808ea2556a14ef04727eb01ea7b19068d35bc24857a8daa75feefbbdf33790d2d25534a0d8f7e1fa72efb0f08503fb26a6a415e469be1044ec43588471cc6885909429feb911e96e0eb05ecbab76bd36152b7955e35b5eb3b2d15c08614ae86644b3eb1701a9cba5729b1ef51c525649d8857f553d4615e90faf5fc2c2f04f854a190002f0797ec072f6eb6dd901ba3438b7cd8b92170d10514de07cb97044eb34bf51385edc21110043cc75b4255a609364c530bea373f70d0dbc7fd74d05982570718274a3884272088f6f4b3bf52ebc4170e5184b3f1bb01038a0ac85738ccea3600b9ca46c1472092771bd4131d0d85103f2770f24f5abf6d97d68a0a7810869c72a08a48f2634f5c1b730e17b650fe7722f1b2de9ba7b6a84ec5acd9d191463a54440d5884672a2939cfdc74e8fac6348fc19c729a43abe887730e8acb205d0fd664e33ecaf7293e5dbe3dc86a11cfbbfda5b7854e8f53d205b84f5e776de2e25273d955f364f4d43aed142af5d2ef8adfa48773a0834be94fa5c8c75812b6a85b11d1d1c92218e6a597a7a1619df4d7ec5144dfff9d286581fd043c1d61d4fc37fab8d11894e2e7f73afd6fc9875bafecf0daa70355a10845eea84863db5ad4c23cb9d235972a93f6e494802d88972e2c0035db376d2eb74dfa91eaf14f664d9b82c7c45f23accaaeb0e7996b548e01840cb50e906fbf6ea5e011f5d1f523f3fa58880930372c6ddf0f144591979b5513ea505b3da5b299017b6e66bb0305056163b5ad5367262e23c97fc1d72335f8c8ae1b609e41443ad236672cd51b3c4183ae6d9fc2915dc359d009e88a17e3f271a6cf8e40b4a236d92a6c33afd553a23406a34e859729c583d143530c4c26bac09815251d6777b9e6c900e77d650d77d58d7079b727256d3d5a4dc374ea8d78cc53d628f6baa20ad0a1e822b2d851e7f7c470c69f7120063ebae1f0738c5f6a6a2049a86c72ba093ee78a063c28d8a477f515c82ec3230e11e0a129362b4ed2e8ad8ba6fa9ce7ad659fd91d7ccb49ce8e37241797a7226f033dda8c134690070a8514124b9731ac22ac1322027df27fef07b8ee9ee545cc3aaec5c27535dd845a5f25d81c2aee892c2dcff9293c22ef94eb0ce3d14725a4c27d81954b3dc863a81232bc74ce20fd7765a15e952d19a4c468aaffd832fa565de6295cdbff4e4f4d8f28362eef3838a5ffd66ea7155206bd434e17e617272dc281c65bc4bf98abeb521db8bc07eaa4e4302d3afc39eabe3732347eadb723657a064be27ce1dc26ceedbab07ce04bc5ba1294d1ee89f302fb093df27bb54a7d93c992effa9111011303718a6bc4676199d0d4f568429ef690ca432ae51725403160241072034ed62a53d9fcb7aa35a2dea40aabe60eff00348a6b50480721440364714b5d689f6c9d3be15b0da97c3a573f8104589bfd7a3f3412fba9b729f8a38b785fd5b5a00e71e14c150d30c7a078488dca6f3eb1064d5d2b549a26a73915edfee1925cf4548b0010abe60500ee9e4e7faa77cc9550558a09a83e1720061e74d0a533f4bf2cc2587c9568ccd66bb9cb422fa2c35a93a92d65655945a9188b7ba0f771cebf947a469bf0c094626a58253f303f933838b732bb09492723d77f643b2d2245012fdc12a66c0ed2085b033a4453d99c99c52eb0f3f3b97726c77a6744f15fdadfb2b4c0be60daa641d4743cf29c099f819595e0b646c387234eb51c39cad68865c4649cdf8878f1c280155d49853e47f7b614c7c665d2e30c41871278b53dcbf1d978e1a89c653054fb2aa84317c62e56740d5644d07d272ee28136894c1c745975e61f8c693e36b66c2c374d6de80f37c5c6d23d186220999142cd4fe30913aa9d7b6c7e4609650be782327843bf5a7e043eed7fc15261739199ea5fb6ae72e133219ab92a2c2ce158691be285b41c85e8f269130d17572e2cf118733f29801849f5355388417e0439a80b9532121dee924d741f325de0a033113d7d018ae8d3472e17db506d7f9101a827f072ead51bcb13639c3b682724b89516e1c20ee42980b36d5df6dc2fe115e3c62178bcef136a5d8d039775d721f383a0b368ddff552a4612aad958bebd9807d6bd13bc2ba5b58ac48f4812b7245762e73dfcc2d61bb0e2798fe9949faab004d9d697321984c46d59d88995972f2002b7bf3b1aef2aba0f25680361b7ece0374468cd9fc4e3d7fe578df26f2725e2e18955d055397bea0d3964635cc55e6b3c9a8a04df7fd027f3afd0d8409726f79907a92c5508654130a94be3a49c9c8e9a517ae5bb7f332cd2a6fe8194b231e270ac2993aa03dccd9ee3a3872c8d159da2c9fc060bb90e51b8eae34a5ce7251db1a1028c7e9a765350a8aa1f9040ea507b3ebaeb44ec885b1693778504e4acfe24639f5642d18454d6d754f43ba61bea652606baf983e7102785348df20372e8bc66815b3d2d9deb7bf83aefc4cd546209e9bb27e575270706dab6823be72b931c7a2f62980bcd9e33708eaa2c9b92415b56d206056efd94d267dcd263129c57c15162e1fbbe0406f130ab2fb666fbd486025af44b1f451bc1d8e00fb67413a2ccfa4875925d28db89e4746814670430d1cda8173f8a8bac48b94f4279072cebdfc3a949d7ec65581de2eb166a917de2244ac23928b326a15a1ab6b9b2e4ca1e888e096b3430c2333e9deafb8705c95ae17a6d0330e73d8b8c2cc3e148915e72e8cd96ba9c8dc9b68f8b911de074e77912fd36d66e57056dbccfac9386555e7929116bd2a47203ad3ba0d7c2699c3b9bae14e39864c17df5aff7f5dc88600d66f8fde74eeacb88877c9511728484e89beccc3090ef8aea9765fc3696886723933fd358c8af76f4e3741a7d69f1b47b5435283a0ab3eeee0e05be379d8812f9dbc29003469a2e5da4a1a9b69bd99cd47e800c85a93a166f4c7950deee18b72c9d8f815ad7312e661f1f2044a3be947fd54da419274c5868d4444609715b41a690ae1246515807af98825def6ff3c0e8eb55d6278c12437a08f98e9e04c024b83446f7dd677f71911ce1192e519b8d142c25e3b5fe35e2ce2c1935d706cbc304a2f8260397f1aa5965b1850070a3b2634f4a1c841b5533a11ef1174e0117d72e63f15834c5ae688fa189079acb9c26b7783265cf694975069db3579bf3bc12e002da71f6345c6ebadaac244994c7752a4a0ef8484dbe025de6b85f67fbd7234a9efda89af99eb3be7d2758748b2da3b77b63915b66a66399c4c8c9222b152721977cfd464b7bfd100aff32eccc70e0cbceb69850c3d682bcd93ecc22e4935486b614d63599a001f0ca5bc3db6843167260096f3041da16ef1e6225fc842214e344e2c46e98721651e33b6d7f0a6abb670fd64ceb41667ae338e638a97b63e72ee7472db5caba7530acd8cbef6443c547bc2b156ff6d0c18f35e2abce00c765bfff9d5c286b833336ced4179ae582d1e6d90c3832de6a325a04dc841b300b972a6d2d3b87ef807aa9ffaeb218c1e89ac89533bd010acb066ce4bfb3806afe844c91cd591e96f1c62d412e4d39b0fef52da7886638e39715ad7750bb77aece24b79f16f271729c1b324fb490b2cb731561949239e00e2f190f83caa62dba01672489840d23a6c42a1c75cd330561737d5fc0a1193e219e8aab58af1a2e42b0a3416968d746472ea0b83d2a8edf0304448178dc43ea156e999aa8d1c1711a7b472d5c0d2c30dcceeaa83b413a8f3971250024ddb8b064340aaa04438222cf660725c15c4c38e44418b450826e1e3a8f99e269811344f2829a24b92d49754ff5d727b68ad6014c28fba3b6c89510a0244accbae69b1bb285b539ed29ed65785970f6247725d440a14bb942d951998849503e8cd93ed16f6f432e8180dbb99ced072dec39ee50cc6225b5e222e5ff2235d73890808e09dd956aa937aefbced430b723236796f30f2e99564a0a604dae0d577f723c2336fe23eb99852d96c1204be381a2548b74ea95a4de1f03abe185c2dfdb5328ede98b531c6e9b2f7182bff7372943a73691cd0465287543c0a34532841b65e4ae82b4fc73fff31c0e85973894dbbbeb2e8edd456074c6b7fef555e824ac5fe239215d17dc6586370d54c29e1722bbb53547e6be5f657fb76876eeb83210970383235f96cdcaa804bb771320072e5f5348390d4eb7a725c9b5de73396777c314184399c32239d047c9f8f46681ef679bfa39fd7dab17bda033d0031250f0aa7d54a274c6168610e9a5900f541721644b09fee5a5773ea75c154c9f4515280778c43f5ea7f16ecd88e894a9eb64e785cd13140337dd07d6b382c6ba85958a58cbf75fa443b419316bc97353feb72abd123a0e09451d05a6b768a7253da5a9e69730677782373ef31f9111046cd141a5eab0fb957ee0edf9f21593f7de1c551c91d8ffb4a25907a4b0a4ac43fcd19846e4ce738ad5d1a375706ff0638d9cddc7fb5ad6c9669eb10b33b63ef3c787235cbe94431893085efa45da7e20bb4577b6e1e22eb81a79b335dbc0ab2c57972d0902133b77f555f46421570ec4b38dddc89e13cf00a9bf3775265ccbef6901ce45592574c7e5f00bdbb577e8825fbfd89936adc979d06c9b8708b93356c1572946a1ce2acc4651be0d5621331ba6663b847375fb7013a4d2afc087b6a58d8726b78bb84dde3d80a485507627ff45c4266b2e44515b2de06406c63b30f46ff3ce1a3a9e054521807df724317e4326160d7e174ce40c7fc190738118fc3fed37241349fde5210396d677082e53ffcb71708ef04f17da6168db3adf01124427c4b41d436132c2f433d2e8fe59691f4cecd92ea2127c786360c3fc418ff4ad5b07267c17c87ea2707d055dc10e9b987ba031221cdaee9f3c0f8275f93b24fb39d72a62bb98d9c04f6f1eb4a070395c5dfd737d277cf3c64a32a0509010e6e775e721dbee93cbe720942a2ac91a1c872dd3dc7d4ef32eb0cfce9c16a59f223bcd2720268e70659d7cd86aaaf11e1aea23a45363371866a32d290255c8177a5035507c8d714da3d3618c156cfad58fcb295c5f5c7cbc8a2cc6206f97be6777954442b27d17108b29c0a08cd56b6b01b4bcc79dc76c1b982573d828c50f380201f537233f35e3d901264da71b7bbdc2ccca4ae6efa9e4e4882e8eb8bddca709a585b6a178bf6f6b7f2dd32bbadf74dbb738da10712410743a6a393f13a5a73c3f35f500ba4aec8a71bbfce9cd429fbc52cef9d1ab4ee3c0a0d319de739f3aae8e2a672ce7dc85d35576e7f8603ba5181a6d9d4e99a9f34c314566b76deaf9b3204f20a119d01a6e3f38858e032469a01781d0adbe0f27a9d17d82232fdae97b41dc272158b817392b9e33371c8142149ab1bcf41e4187b31c1d5b130f4a255f332094391967287d202e7ddd20443740681aa9695e92a1128c3bf4e3819571e0b429f514c63fa06ac91b19706ba0f0c60f30ce4eb3029cfb67fe5bb943af078f505c9127a3b7126019e5cc476f8a4d3980de4358bb02c96b5e932e764e33db91f8a467264d8063a04de8a65716e1511baf9b4685e611a132b99de5978368c1f9074a7727c6c84b05a45624351404ee8c313507dcc9c0b0d4c415d798deaad856e08d83a4aeb41bd3adac34d91e55b8373499a58c821817fe01d09bc780218810759ca72abcac3695b22b321613413e2d0b81bb4f863960aa453c530f845b2a364ae4c7220fd7820614e6d7ee10e4a60328914574e627da3842ef2aeda54f21ddaf301724b777e81e08dcc14efe4bfe4fc2c8752a9bbc3d7015b81abe46ec9944f83755fd492b43db4cf3374d84ab7a764cd3efc3d7ac59a4bbca4188e0a0edff9e4605c21cc446a64e673bf865b16ede1b8e751bfa8e4790224dd741afdc1b50d0ee272fb46a7e8e1af65da185035f503ad9c7fe7575c8871e5fa18eb261f8d8f0e7f230e23f3d26e704c7e1d56ac15db7e40a1fe3f581d0fe957c0f7a26442d206fe729f6703ad1e2bc9697a15e25571fdb441837807f70778d68e387914c3124e9c7231a93d979583bb28486185dc3d80db45d189130192c1253dd1ccde84a0a26872942bf519f9e232cfae31829b4aa8e20fd0ca52f53fcffbd460b50fbb43115f72ff92f97bdcf7ab45da47a81180309cc6740d47c09ef93c75aa4a815001cc2d48468c98bb5c36e9a5c9e3ccdbf4bb3796b7782b7b6fbf054891957672e602077248b29460570936365cca16585004af7c4f07cc6add1b613f16b5141c2734c572e44414e8f96b636bccd0dd240c21b08b889cfebf087f5670eea73c603effe672292246ef19e7fb699b34e893f575b7e9b8d48dbaadb32d4d1e6f87ed2bd35425d9d1da4d8c156a4a9248907a4c033085f4347ec873064e2ab9af9e1e004077219bb22195820fa0019021d601f19968516e8dd35203998525ac51f70267587809368c241ace0cf4fbfdc73120d1ab99834365b747f345c59ca6088d2a14b6697246e27fe8eb1c7d95f61f6b1bbab5ea7c7483d44ac524941154211792c94adf62d00efa8850ff884cfde87121c717af8120fcff646c7a02f03d38e9e9432e9c41f97bd29e2ef19df502a3117ef53077c1c8a036a312ec2cd5e2d3229dfd3c727271436604fd2006a3866bb3a7d824537f781d9513712f6c2fba9fbb7326157e72f881fe882a79500f8ded698956a2920e144249661fb816d0a37cf4b2cfb41a7211cd5d92a82510c1247ecc28172ccac97dee09acb2fd250a96e6f96ec6dd3832a92b4e8592c2570979c5b39f86bc565fc65078edcf7c0df582f833c7db37f17285aacc0824274f0b135930b2e4d5213a56ba48f25a0d89147f4f96ec6db8d3126c6d816ce48e8e1fdbe56ee15f242830c09290147e2f45240bba08697bf8867254fa863e1acac533be1105a577149673e12f41d730a15bcd35a7da79d1b51a72b3c5084664f23644432d1bf3048cdb761c263e91fa63142669f1c949c3d67a26e6d86697b219fa71b87435a90990fa0d3b3b3df4740372e52005d5b45eed2e260c28b39151444005e217f3f31baaa569893ca6835f58c9c56223cfee3ed5eb72d9017fdbf7d9aed9f9ac771c66a4286bc5757bd5f782d85f660aa51d9a05807273393255edd3f9a2ce7c3743abb85b1239ebbf07a6fa046141128b2f439b9f729dee66504adc9713d26a63f05ead0dc69a3a35416798537fce203d10c8e8fe72635b9b09d5765cd01fe916f84b8d3872f531d4191e802773d4c96202c1d3477273a01b4924d9c6f8c72f1e8420381c6b56e8398a2f222dac110e5a7fd592815919391f19a1bb75dcc7473c2130b98aaf14e620e78947de470f418e22332bea3f50467743cf6fbd4986b311b50f0257dfda7e03ad2b0e40834a8b4bc7c2b12e726eae1467ce971f62874b0541652b31d1706127b0a27fbb81796f1102783b20725a7deafc608cbe1bc77d2c552ef2d408119edf7c176d39385636df4c027657723acc8150eac902ef348ac14e4c2f5bed6d5f3116cf72b5db2f8f2b09de919e6520a1c688ceb9294736e15e05a8eb93f0cd00688d217ffd75ae530201e34fbd72e85b0d73a3c69e91947e2427dc86bc53ff739dc71094f97e478b433e74de4d43ba71e82779400b11c29f03ef282fac1fff73a3e1fc2e3c281258f58929282d3eb10766ad2581db45edd33ceab96b6dc8a49c5e2c74d5ac37ea22c398aa449672342500d876935214b19a85fefc58b1b3e6a156235f4acef60d3e6966bc18067264b9a61ed6e56d3c7ef1084a8b06c9bb62d30f2ab3c0b8e014cebaab818ec772c4d86a7e1489f587922b4f2e64e79167faffa6c7704781e497871b139ba5ea527a6d5a756147663c6010b36226224502404738e2c74adfc65bb7648e77d4c0727ab9815b3e1ca29e6d3632e80d8106abb1102fcbc2257553073725a7abf6212f5b87b897dae05b9d9c7dbc9c890dc2370cc217744c8b52f830b33ebc87097245fe099caca6a47c0c9385aecedfdb0b68d7dcb9d04d94491c57a24c7a4ddb59689969a90c8abc9512b02ab5e0bcc84baa6735858c0f4924cfc7c510361c6d51725025322a9c22ee30685309a931d2c1bd93fdd00746d080c283e0d3196194e60cb76ad3a81b0f04e0ec8f02eef5ecf83c6488fa8e1613c08b290b5d65c95ebc728a2ea2b93bf8bc6144d834702eb349b2e44d43d47dfc406714a15acf1a028272a74dae23ef75c94c252f5d23995a63976b38fb43105c092f137cdde406dc8f7226d1521dae7ee853fb4f971d1d50d4585cbdbcadee05601eeba59f308728700539ad118cb3261a507ff7afe7d9512abf99fc03b03b744f131c34209404c7f672224954ea6e31c60ac7220b643017cba40843590a7bf0c638dc003b8707505406740abd19d26040e601d7f92c2ec6d99d0dfa37b4106212dd1178b717a11211704bde07d7988d0bbd60ff449bbb9bc4e39e2346a57dd15edf0009d350bc10413824006ee3350a2615e311f8f1f3a94358b67783fbb483ff36d834855818e2002e23b73d4ab6abde26aef3cc24f2335d21ca75c1690c141e5ed91f2829bea7852504a76fb3ffe82e81939c90884c7a8a461f05de8685ceca847c788e3e510faa2a5d9bc34acf4567be5f39fda5cac40357a3e8c42828ee8ac508bb2ede5e78335f1597f0ed24b0a79f6a49602a85ce6e2d4ef6a0ac367dcf052a9506f79b02105e903787ae14cb1885ed3756eb73a35807138aa64c00d280381f7121ca31b35264cc50832dedb5300aa266d6adaf6acd6be4bde39ca6613ae3073e713bb494f67258994c4952c9bd5f6050be0ef3b7ad2e80adb03dbf8ea2d8d97bad257a8d6b5890fdc1c5aa46fa8df4c6efc136cbb500dc1696363a0d773b936e1bcbc1707c5f691549211f494c88160229575ca72e803f71b37d54fb30e08eacb7ac99672c728fad95293e9aad2cadce2c1c11d8f3b05c8d4168827bdd1e8ffd797b9fa64c72ddade1a532da8ea0b5241c98b92035c56804993b97e2afefd9b3797c941b3f32e0f965e9d674e0f84106fe6b603cf50a41345da9dcb2747786341c77aa48a5258a81523f69bf3dff5ef84a906552eca27c79a7f75aa7865cef54f8837022af7245e903fad5b3c71827a35b97468137fadd6fabaa523242292f64b2bc82fe3b54d74dd62d5fc91745220641c5b2a90833f6d44a9ab4a5b7542bcfb74b80c3a60c3a0775f5a3590c53c64f95a45bd9bc28578e0124d8bf585db7e20a7f2e89f81ecc73ffb36a15df7a87c0f3f19c3ed6d7b5cdca4f443de02217ac32d3027e7772af745580ac6742cd5f835627f4dc7043e73e95e41259c571ffc3a87dbf146672774787fdbf8819f33174be9b5cefd6f439511ace0b815220e57a5a31f0b02309b4c780e6937879b0c0fb2c79a59a9206b07b03daef1f396678129d7d8facc7723b44c068b04d2645f13fa0161ddbcf485d6d92927efe5ed90e9cf4714e87cc4089736f673a7a687c294f83da7bfbb04e6ee09eab1597231dafaeb95e8cf1ee144ede60b80f062d3e59d686cef6e618c90435cb2a80badfbf1fb59bf166248a7084f9396b33dd50025aef2f10d6f07f43ee0b8065e6ac075bcbca6c74721c7d727a8c4fcdc424c5a78a1d43770bd45efed9a214bd8602e6b7f781dc46399d0a4b1e7b8992ec427d0b0c3880de9476202919d5226f7b6b306558fdc0f617900a51e6073ede6aee88eaa0b83d2724e1057a4de49fd016dc649936a4a34486dbae58d184189525dd5a4714cf230f3b199da5e9d2a2a69f46d6f6b2a643875b440b72c3f814e5f6683e3f37955d979c413af71e7f8d3540efacfe4cd21778ab28a803e2a86330e9cf5ee3ea7664a1ea46d3337e78cb1ab8f95666e7a4b061817f63725287db16a2b9cd685587d3a16a5ba0c3348563ac25127586e01a2f3cc430c47276e1ef7afed5aef3a903c5cc65887078f9ecd51c95f3aea0ce5c2cbeb4ce5a513f272f3df448158e7e695e9de7fde4a433fcd536c652043fe1caad0b33b3c8722416fbc9597980a8eda2e5b01d60d38049ca6655439323d3fd756ffb10b60f72baf68090f338656615155508cfa1b2dc1c3a964fafa8434446d0bb8ebcda5551138b443fc84186661b9cdb579ee08337ebf895bf24df3de03b9c3dd4124f0e72d2e8883343f17854db597f612dc51162d45aecc781b223a2591c599d67e393724411af3cdc4cdb348a7661652c9ef19e810fbf0d23efdee2a1952d88ed2d1372c09bc5ca2721792fdbc3ffd1fecbeab4609722718e82f05242e0d42624db9f72a6ba00202eb50c333e8b2c0cb8c4a86e69dcc6434ba606f2a1ed7f613529397275e73e9ff24871b98b50466d77ebb85a428c1d313c1005d877c43675eaae8272ac2ac73403347a573057533f9b6eb24eebc10bfc37e0833e672caa2f51c5b672b6a06af6ec9071eb63dc53844591f6e39487adb1590f82d112405c0b9419dd720c3648898bc39c0fa5887bd34e036b02bba7331fe8a797d6b0ccee4d3c864e175fea6abd6bf11ed656de558e98937aec4dda0b1af81b334e03c4a5e8b192e07296139327d93e8a80710c85353152004304eb7eb25087479cabde8abd3fa14372cb69c3d3d44d1c08df7a57dfec8608bd430c6ffe6e9e2bcec564e65dfd089d35ffef2ca0b79a7268c3fa641624828db2f8ea40a08fb9847329b7f3b6ae807b6e88d6333b485f48d57257d06aa87d337aeffee463d7ff4d89bade0c7e7d1ce8724e87bb41cf0fc4f2ef5174829314e243b11f59ebf3453d88d85761d17c10127256b462a8aebd4c0d2a8c961f8469c35635d774edc6e0768ddb5721f417a27866f0934eaabc4b5066e9470ef31b97948782009f0526b835df80b6110bfbab4c72a2a30d33f7a59fc6b1ae6dc1ffeed289c2d956b64f38990c3e59da1569b3673d9f826efa262da625c406ed0735ed0a4a36346242349ff166ce1c30f79479ce722e17794ffded22c9e9e9a4c642698c5bef4d97b56cceafaa8e683d1476fd2a7272c08cd0484780621c55481ea1129267ac4e46ddeb23aa7ea6949a6740ae25648139c2e06dc8ea313dbe6377f6ee9feddc49be9b6e519a14d7f32e5fbaaff933393658d143a8e8f3ba4e9dec6ad826c23381929bee31b2aca445986b7c74ce722f72b53a678768186add13d202162fa58ef3c74642b546324df88d168e06e972ca351278b0ee2f4e72c2015208dfc1fece52d93243d5cac71427468fad4244725c1dc33d84f8fb72532368e429788ea5edd226de1d3cd4a85b6fd1afecb74f728f45f10d546182d9d8b79b1fcc414db937321406618a31d78332c677c2bc027235943133e228fba71988ccc1d4a7b3901f418247e83dc66d8bed2e462a41884205c2ef3f983dc00f4a540bf9610c0a53bbb3322e81b9f81fc87d8aa9e91fb472d59572e3cde0d9ef6e6cb854689a4249044afed34619ba58429444dde4fb2f725d19c41e5169c0aec36dc62cec608864671dc4498a743fe39071f297bb71d91edf917b937d92ba1f18f9f71908b2b8d685f677c9a2096c256808dc5315886f34e282423b8d852e275f4a1a25b6dfdb3bdfda7f2b42ae3e28bcc9fa59e3b1b829faff8dd7173c40b2c92af998647632899213776d2df6087702b6016b658e9f727f574c83eea6dc4b433bb9972c217e0977e2f3ae82744b369bc3d8d073ddfd724c0ae68dd91a2d0c3aa6cedeaec563ebc792c24f66a1a11f24562e8e3eebad7278b1d89d1f97466ecc188008b267e4f1b9cad52b180e477d1a4ff6ab6583431b9a019d06483db63d1a1dde96354b8d60fe089df12e02f0b536201818dc2cca72acee14d86751b8eb75db8a4603d76e120f072a3aa8a674ec7f2ce885abbbd648abddb518ed6f737a674e8978fc33b0581de5b6257cce1fe7caa01a7a3305be72fbb4f7edc63e4f80e259ec28bdaef786864b11c4ba5c6f07fe3dcc53bd4ca3722c41a1db4e124fe1979bbaeaa8c4f6a00aa2bf6fa427cd78cacd265e5fd99b72d0a7340f79e790f7b44e1eda10ce0a9ffbcf9e97a0245715e4b5159abd84e0109cc9d317ae65afa031afccd999961f8bad787f8b7c69560d42512b31af3ae672297c3367c02019afc864ece447f114d3c8aabd0cd1b3a498c726997e83fc27720e8c598eaa6a6e5a6c7f2daf7ca1e9f12c0bcadc0d06b45b53272b6f1ff8d91eb6af74806c657402002e9ba079fc500dd90f09417c13d82c463a238b5dceef725b32640f127601af0d80f00c52ec18066accd93fad0b2629527c8fd41493c40970f397ed7ee28998d77519e59097512ff6d94a52094b2e14d07ef5ad26a027721b9ccf54e55419ecf0e0879e8c8e33db78ef7d14f4777c27f005faaf21c20d7250eaf99a6beffa93374ecb2c08d45cc2fc43a2165d3c951a17f99bca37f2807214381ca9b280eb3ee6010dccc5b7ef6fb38ead689a2855c68ab0c3aff155377251dd2614726542bee0f85eef0eb685e7dae5da1029f9500aaa8c7659a1b83e5316a7d340c925fc787bb8e8345a6919104839f925bafd36d012b24ea24cb9610ef0804c5fab8695036289f5e76734a0a4b821ac4ba5ec09bd4d4b26c73e0d0369fc96149ee2fba130db7607d220b30e0f1dec9130f2625617d5369fccd7cb2048118d4d1cac1cb3ff5d50e8ee3b820f71a5554db1716c562397c143c59d0ab142db93f13aecfaf6abc609f6a1695489fd77fa3f6b04875ca9427793371f70377240d480cf95d7c80c9dbcf475dc95239f8f35668d6c9d1369cf9b0fd9d66b5872f4f5b439319194389f7a75fe651dbd1337a2e3321671630b640304f6e24c5b7228a854b4765cf37b3fab91fd1712dd5351d69497c151ea4217c21f43bc50575ee307163688cd584f77a12e341f768e8b011a58e2668bf0e957168bdd02470d72aff9ebdeb86357a69a8f31efb0daa1d0c248ee8c7e3f9c0d9a72ecfc85e4a8401fe33caaaac7172cffaca334d7046e563d3b63d86872112f83adf875c5825f5b807d6da4b42b3f08661bd43048863b589cd7418598364449f2d390d3fa1c057264b56bd0fdbeb1ff3856ea40a6a6f383ebdb4a355e218c026750988d1257e7048131b83bfd92497a2d4c7bf5e1eab9615ebfd24f7be8acf340b7d64c3fa0f11485aa31d1dd0bcf785192e1ce7a1bb5b994f779e35ecd08b2528d75c4c73e3a6a9e2a685b06e590717a4c0b7363623ab673747321412a8443d00d01fd1d76cd5301ef1a469d5f9029b8316e2b983fb535597c42854c3b6eb53acc03d6d80ecb4db10501818bda84a579c371c5185c4de39057c9438aeb7b232989d6262a68a56ca5bde9aa41e26ebdd88f17c13b057610eb60fb3f97d7154c3156d02c4313f07222ff292995d8972b1221ac8e0e58e9c7a94d29ca971146d68781550e84dba9725c67f4c6f8f84d553d2839e6eab08d798776b4a64c35f182f5ea3e3378f7d6727cf43cf578538a0e3dc534a6829e498c2634aa966f3ca5b60ea8f4cc4627993b1567df39d24d84532190f5f36214a41fd81f620ed1e52d05e36e8ce0ab68025a0bfa0e9d778c903aef76895338a36df2bdc26ad284bab55d208d5eb5e565856fa0ec0303b7072f474161e3c74683526d49b4a06ed2e6c7571d870ccb20a7ad72b3c3a964794118b5678091e73b5685e3d1b3a4c3f162816034bc78d79b833851c567d8ea226400f16d64fd9e36e05ec953fbb87dcc8fbee21b4ef6d93ce3385ce4f3a6526a225ecf500ad959b35dd89b1d6b89b4270799dd7c7814cb2fc47b5078d17c263c1f51b6b9bb66a9ff7ab89a59287a7a014f0007f4ea0a24167706139602410e40545c089ca5a5d1486b748e4aa7a1bb22520a6c3f0ab36198128a725b851198c1b86d2f5d78d7ff26bdfd22bdb76bc457188dace8e2bc403d5ed57222b50e0684d2de913e84f9d1603456cb304cfcddd9d03afe4a60b63c83b5ef72d9b25d36bbf176dcbdbdd4768b7609b3c1a7da7b9e05a95d2c7a642d0b5169722fba9b4923e8ee0ed81dddb8cd185d2dbdf5150f3778e94ffbbfb5cb9695b3550522533cac018950053312ebe309f03befa1a509ac07ef4a870eae4ce7298b4956166bc50716d3cc641300cdd13b9ef07c0fd73da9007647aa05cfd562e3c421c7c7ed0ec36abb547ca3e71773fbf59120f17840024e5004f17dca0adefa9b7268255c97343f368edee36d8a2d1b5c4c5d04e2621d4d8ecd09ba1fd7117d0472690cfbb210906e1cd3e83f2600ca5fecac56adfa6838cadef36ed574a661877207611637b5c39c91633ff7e68e63dd4796e771709770fc66dc681ff12a6e2a2feed24e6076ad120f81a3228fda27944effd2fd676c3cde7ae78067ce500a5b3d27987e3c9b3b96fa73c777453f711628fbbb5c58a89f4b56c1e9e2c916e425727a7cf1d8ecdebdf779ad24ba85954742cd8644e65a90a310313a7f7336bd9b72d46635ff1cda2078c74eaa4dd2e4b26a6548c3d3550aa3268a398ded3a8fee6ee072489af91ead3b8796642816a9c0e2b11f8d32717df9b09c76c701f842a872b1da6054e7915a8a3b7e4f832a97cdcc8caeb6dd355a9c34ba0e66671068e5462f3f61da7f39d6b9ef9f2c078b62886cb1c81e68b25cf92615d78c8a31588025b5ecaa083aeaca994bcbf7991b093d33ae29289beee49443eb561675409768726033d42c7d8afe135f02076451c737d2aed12041540a384b61a8a931dc9bd072f8b08c4c6d9d050ba77086220d12f08293ed96e1b89e18fa4cf3a8c2d9cb4c72806a9593f00be04686e49087cca9d6a4ea446d31067463b96010e668e85afb728bc9c1eddc439885fb016c22153cffafa58c0aed3aafa600632241a117525d6cfa4c4a635808ef1234d5520ba9eea4320e3364fa8f715afe9782897a9671dd2c4811c2b2646b892c7ed3698287b376933298076c8c07bf7fdb734f29c0923472a377cfb9f97983b462d93df1345b4ca908638487c94a5b48b72b894a9bf32b725edd837fbd987ad5732dca1461f1fca71c896ba1fe7a1cdbddd34c83859c0e72395ee5826e9674041898e5785452e0bc2c719e900dcb1a346613aaeb30ccab0a23ea83b4f055c1ef2ed11d605bd77e255fb64d218e5e41306099d540d81f237262126af845456a265c94bff5c3c67d939af232ec4e60fa9d74692efd66690109464e3701764e56dcd6a5921833331c5528a577cd16545e62d03a56a2c961d47249187be56161f13fb49d9ebbbe179919d2d924ce05e1c92430d223858233f05f1c87486e4eaa4c4050d76557d6904f3c41e673c463aae2551e718c49cbb82972bc7732d23bc1c24f893ecf6db4f7138bc533e13133902662699c2c4d80116e4be4392caa4711435c904f2e2634db5b78fbd9998dd015af18032732ba2768623824eb934f440b3ff048e7f48d6138859f723db6499b30c1288b1aee24f597b731d1b0bbcf1e28c63b2931fed70975310d12efb0d2d597bb55fe23e172dd2ae9726da1f6d7ce99d61202de12c8774c59aa04abf2a891d2e95a41631eb90f17e07223ab49afbe04611a049edcbbf06d89804d3c1604436f91ee1dd51f105717eb2677adac722886e33549ad764ee7d499e3d8de0e6075e3039e9edb491f740843722bb9fc60a606d6d4828cac0eb002ad14aec9c90a93eeb9ca9d44b255121f1b72fc2709848d17ad5931b7a0ebfb72b0432ef1152f62cbfecee239b8b39ba8b472638cfcbffaa277b0eecc4610d6907e0208b727f04ad8cd27fee09e42efa58e50fe0e3aab709fd4a2cc5570008a1053f0a01921be1fd1c8f29b645225746c4072cb974e7d774e94e02a3b0dff151164c6a744400cfa5a60d31155dbb8971a603fb2b535517e07cae7e030aa78aaa26208edd436b6cfe5f2d3150239e7519e55722b49496eb72d1a2f8e5c09969634e030f50a7c971e8c8581c013670906b365001f81d1cf6410d9b36573ec557c6d8d5a13ba0437e50cef83115db8d999136a32aa2bb4ef077a6971aaf6a667b8a1da2df565a679d455b0634bc9c6bb6da3cd72ddc8f3411714965e7954562fa547768f21e89a793801641dd2417a3bcef66b28b4d14f879a8d4eddea7538cab8898a0e21d8807e6da8f623782c53199b7b9d72f17ac6f210bca69e43eb0a30fd915099452fd1eae232fc2dd88be098780333729f2d525b236b9bcb8a7038c67ff4a1fb107b23212665ca29e61b8330e905e772ab8d4897509030af90e67863d89d605f90286c945bd91d7e432a72ea99c8007221f98c605d81d07ffff0eba0c52693783938a238068cc0824c1d1894d245974dd4168d82d0a3acb0baa78af7fdc5a58b5f5081031578cf5f5ea7c8e9e1b745722f3b2ac31d82bf8c5da0f05190418dc6d7120472240ebba5e0bf0aac9801c4342afdb8e7532c80cc7b5c1f323eead428ad4805dc879b6f18b239add3b21e5a1271c567270c7179efef77aa8712dcbe267f50de8dc79caed8e07624f1e6dec630bab211cc4d6eb7b2cf6a02de6a53536baa4bb278dda666906edec1aff4fac17224f4399a19b67bc3142f113a325cc04304c560442cb1273f4adeb242ddadf05c385c81e3ae2b26901e0cd6c9429e8b9affbdf1db111408638037e3d7a2847662e001a4fbabcd9e395879d40acd2032a53f089fe853e7b40758d2ebf97f2f947271680f9e209591b3f5abf203ba06d03c06a4d5c2bd7fe1a12b6c515768d9c229f5406f18a2af466dc6095ad16283df045a967fbe82e4acbab2ec576c0be1767227f96ac883fb8a1026a07ebf0cf254162911f6c7002047e614dc70b8ca5d5d72196ea58d35cc13807060fc65a7d82f4de2e8a0340850cad4ce812abcb2c42a724a035b7f25330056774ca10756163bcb713c2089ba2928ede171c9b78664654b01878ee8c7fdb80db58150a20341185ebf369b2aec74f616656e95f7ac15a772298b731e7931fc1ea91639e39d6694f611349c3ed520bef134be9e3dbc166c72ae214ff51f8e3a2fd779722cce0129acdb573e318dacdf55d0b8c3eaee7a6168ff91ac516acd13f1285fc31c53d05fa0cbbedf1a8787ab233d2c225027fb9b72daaa7dd8041718c5be1a4832a541c70becedbf360a78aa77e7fab7faa9c7e372ab6b05240620cf024158f5a5761e228215ef1fddaeed74c1ca3af0020d5ed405e74b18efb7b69e96a865b7b888495bc7eddbffc514d43c6be9b4fddcf7fd3b72d26eeac013eaa596de4ad36961477783c9e0277ea06405b1cda531c03d998b729de70e88b2afa1d31408147613b6cdd3017a7e607de02110dbd337eb9a0d89729f814843a3c98594783677587dbdb53b2105253943c14f49206072ba292cf94c011c4373d0d1ba59b1f6b516b6694d964230c1fd7764cc84684554523d745972922fa15baf545ab3bbc878ae1ad1fc15915028408eb024e6ae2d4bc8495436725c0bbcf5a606cd6952baa5da440cead1b41cc3876c1e87cc17b76bd8d29cc1602647bb10ec54a722669eddda1e3c0e9f23ddf78fc9aa4b9d0c6e9b79b0b10672c0944d51848e4f3519bd76d415c8d257b368a3c15e7e607adf90c188b7d80d723a4f7c8d6690d40043b60c7879490d16501e021b69f23530835251057adb571e83be7c5ddcaebba13ef016fe77818a70b024b70fb9d17a9944ad6062a0f6a7726e80558bc276410793143342001e4e0b47148e931481c7669de8636ec3a2b4727fb48e3fcc3bdf8250336bf8cb148010e9a7e97e8f3d43e6453a6d86cfaa69722eec297d3e1bc6fd6cda95df5a0e018a4d670e89559d3439b6271090fa5daf72cb67750e893d278cbbc5a51330ebe9acbce7bb7ffdaa75d79dd04402999e4572c56a9255c92bd3914a3803e10cb2cfc156354a05338c8adf1dd4a5e246dfe627a606a63c50ee2004017bd641972302587388bc2068ac96634e9bce16527aa73baae526d37be0426502f136e5d0a4157fd0cffaa34c917e22a66ddf1455153a7245ba96365270e2eb820cc92b7caa03c717ab499cfa21ef6ba4c1f4968987775481af10a9d09e4eee300ade9ebb6a08ee33df84b56b2e8fb92c331fb201c09062088f3c7d826cf49b0668dfe9d9c4e5be6cd5095b9d9ba45d44b4aba7e68cc43f692eabe5089e761f481486a052cfdd3c77745c818d3362a385e29b831ddaf5729d4342007c89ebcc3d9800e594790b07d42f46c307bbad92abfe52898d7baa72f005c89431b95343566437b51914afd216fedd657739b5185ee5bd5fe0c82872ec98e06406af95cece4e112735d048a3cc49a3e7e97ac03561161231a319a5645885bfb3c966887a5f974fadebbf599cd7dd633a7c8b3a3a78639723c1710e105413048708c568feb3943d4885405276cf42133b49a6d0d96dac50fad62b18722681d7d5be13d24ad1120734a647d3e60157110a46c3ec1654fa0d7606fc7034b1c1030a8fc8e5be77afabb93df87f5d98cd1888473d7abc4f0eb4dd887aec45edcc2cd4de0a4589ccad058a53de656e67be2da59bc6a72dfcdadf78bf33627253400b165dbb90a776f7adecc76e1b983e13ec4eb42e083a3c436c56e95c337216cb0b48eaf10b62cc168b77856dfe90d668a5b69df288b3e1fccc7bb7e1d47231786ace0e8b838c47407542aacb87fd784a13a13fbe27331cd5a56279ba0f69b489dfd92b79dfcc16ef256faa72b547f9c52bca96dc1b15912efc61e0ba5208a60bc8870ccdcfeac9ee021e160d35f55c25d350d2083324fd25dd0b8b54cb72179069722a89e53c5fb71fcba2d8fddffd870e165ab01d2042732b8ea8d53372053359d11ba48ca6465cc4efab67acb8df98bafade6c012b28ae135ca2088f724541435544d0414daa7dd21ae3f62e626ef0488fa80424c7e6b500f8464b4e62b322f8853cb6096cf83da48dbc0920dcd20b471ecc5afb1e606c48684b796f3525c740b53e00b96eb6782a4c82d28457aca1a246f0b6b2675bb63901f7ee4924198cecfd8ce79a28657d100aaed71dda01515880d1405a2dd403ed123ccccb2a1d665d121f61ee45acf0d3c68880ab09c3c1649f71e0d984cffe3f357af5096e0773da93966ed91d63936e29370658558bbcf8421b4ce6d7e1c088521767c454d26afb4a365d048a1c9d4a6fe3f0b69ed1f56e8f330ac1826e11ee1be3cd4f72d355e25d30caff472da8a600ebd75799f18f176fdcd66b3d6110812f0cad1f729999b7220189b6625811556b3638d0e259f8788ac69be7c3b60c7a490a374f72394f3f554f21fa072b49eb3e59503c38dd3cdc21b90c56d5a18d76e74d142664a42e70ecfec0339980657735c6f84b07601b1ba4cfbe165cd79aa62353b9a734422417a94ff24004e42f9bc9663757fd3384b75de5d90dc1742558d03f505572b3a39afde20d729b6424b6daf6c9352896703f24f851a9767da9d23ee86aeb506c4c7d1cd619dae99e742841d4dd0bbd07ca8714956ea83d391f6113736471546ddf1b0b34e05809d74fb69a1794e6ceb83c6a36a9db7c1b221eb379e585c97211de10b4773606b71c8895d0b678f7b4fe50abeeda8f71a72ad7f869ba6ed2727df3bc2e3c8514d1ff7665a7e11a389a0a3b2dec8f4b31bcad7780b156f08418bcefdca4d21e3d237f9c7ec9f5476e0a7140a33bb5a2945c23ff4bc2676bce4547cd02d48ba94963bde3241f712902f653466c424ac8e6e428349c4d9d0bac2dfe85cf81b57e2fbc5e74aa084d6b8d63e827ba3e9960dd546774c8a7f00d1f1866b084004866e583ab43f37052a352ea1f735d628cd544df6bd12e8a466d3e72c9cfe08a44a403dd70841b79a7ac5b5e7df7ebbb36bdef0ce8a84dc021083357bc084db1b0d42c72bd2fcb7cbffd00482c54e80d2a2e0174e6ee1ce9cda29f727fff8c4af7914aa5af9cd8cbf0e128e1e810247c4c798afe87763f23e2ef5972fe28bfbf908568e41a99c1d1b12b2c5260b6558f24e83f49718167665653ee319df20b46e37a882d926a121a07775c8c96742dc7e5f987d16673578d994fb742e0905ff99dff58c3baf8a2f6136172d181bd7a0431b31fc386a2f62092d98446576b9d38e7c0376b0711944c3fa7f6336c51946e5e10496c6adb36b3b1d3de729bfaef838685478528cd55e2840576b64b99c0d0f202c5943c9219f451c147304ac9eb5e6b67b9ed157fdec6ce22c1c63863967013be6a4ce92e91419213d672ab21fd4cf517f561237a46fc6000dfccfc003cb7c11355c132cd9c3d4f1dcb727b5ea72e6c4286fce664b88f5cb4d5c3606eca60019a3cf0e2d92d3cc521cd417c8cd89265832a5fbbcb3e0d614587f988d0b83799a375ceb6cf4d8cce25aa72a9c096a2f126c3b34d7771dd245d3a6c037ad47736e85be072db9fa57f9c9a11c73485b3a8fe9f47977483952351f873c3652ec3f456b209bb8a5dc7db3ca072dba94eaeb3d5b75a47f4cbeb3e3396508fe786dda7142b0e9ad8d5a0e1c939059dce82b915be030476298de255a51861ce03ef42ed4c4cb424065db67e75b7626438b8a870d6db9f9f5ab9d55dc0e6172662135f6685418fae4988f9a93ef2729cc3390fba7258b7b4bb9052a1830935eb2218d8a0a5509086e3fb91ce82541ad3356ad5adecc2d1faadb868b74c579c65dbc54d446ed3a081d0ad4f407d0b7266261131174aedc9ff8300787b0c112f1ecca1c241d790c66fdc1c684181f23d478faf568ebb396bbcc8afb3d0858753faa4eed1e9611ac09bc82db3f18f0e721cce668a4ed16dc84ff9a648076e3c5753a91de0937b1514ad77bdbdae0af472cce29853beabca58313f9b264fa17d051d1e93bab6133cfe0dc16384b247566c3a885e0d813f013500d3ee0ad654224df7268f41638bcf5fd4df0fa678ca97245928bf8716b9eabd3fe943af70f4a86fddabd540689ac280577078e2ec30d76361865a2ab37751fa82ae2891efb328d692303902f04e2a054809958273ad4d7292283ec31d22b7565b2eeb471b6bb031f23519ac1c78d811dbf9254ca8cc5f72011ff66bed07880a64e3642115b696abb6f61cc6f9468d77faad5b03bfd7d910be3868a69dda1d2e3fcd790ad5d3cf77288ae8497eac3dad8632fdf8374e543331f6bde25dfe1f45e9e4b87e0af8df90857e82b624662740e327000a298daa3a320665f88086906896e2c49f3c0a00c7677accd6246744e9c1af59fe21cd130820a5f059016fdbaa37aa7af6671cdb74c3e4c1f1228f84aad43580ecb5eaeb723cc5c5e7226f9067d96c9dcdebb6359d87d79761480476207682d11e035c64724bf0681cad454f80f4a6bdab66aa70f25396bb5918ab3f058695a86cec64f27265def11423e074c5e77895d1413b3dd9494abec63003b3b9675542a28a20732a13fdf787ddc31c29dfae3ffa06c178761ff3addf0ac29956a20dc8ec2338fc72eac416468434bede02a93b4b4cb959081643959c041080b22d4188518104751c8f0e91f40a3710f1ac90aa7969e34c08b98585565df455612339222eb2fe727291024053162abadfb674d211610e51d598b870e74e1f1d4ddfd8994d8d8ee0253d189d45fdb93cbb4f58d12e18a04778e0bd244eabc898e78391a9666daafb729dfb123d9bd8207b4f083c25399b6c331bd9ea7b5f549916311ecbde4c5b21726da203b057366745dcd6df79754fd89c3cf89a2a0d0dc286e4103bb488451e5d60ee16041694f6ff2c266201bf82020ee2c1e22d6f3f4cc79747e06bb6bc335274f9a84358dccc3c727939f3463d44ff0af9c48817680af9c3990cf9d374b172d1b8a950a3cb7f8bf37838f54f69f748994de4ed12b7e74eff6c3c982e14e437996db9b561db98096a46b78fcb18e1e11a494532e1a1f1c055d578c4a0623463107d615bba90819b99a21a3fe33d07cea8c7592720b516ecbc5f8463fa45d2724faa94247784cdf91acd7ed511fdff55a3bee7fd24db45a1b22458181b40f77217aecbc28aecbb5e84e578241f9259f4506853b86fe8de562be04ae5ef75a872c988dfd523d126e6fc5a22c2d174752e4182cca79dc8fc010f3e1b8fbbfa49729307599b4db23f03c5ecb5296e73acdff5a385d2bf43897bfa83d1dee2702f72207dffc7c41fd441662239b71b29a8e018461790eaa908c06fe8afca66552172f7fb31a3497e187fc3eb4686ae00009ff22ae5fd492bce057b676c54bb98ba212aed3eea96bba6ae1c93b3e00a8718e3c0b3020c221f33e3c7c8a1267485c5305015bbb497966598061e417f2c99c5259b6ba285568d4e004655d1535989a65f998ac4f865984bb18b36c66341ba15f10163a25c549a6a6f932aa02c51005f0c3db6bf34d6892e5afad4e5ee09cfd68f2d3cc6dc60e282c53f1983dc1cfd395b8e424aa904332e5f6e96ec131f9998f464c197ad28b0b6fad8bb27dc76999e727a4079c7b3e76904d7b72250257a8d397deb5642c55f7736b0863a679103c2727fbcd78481ce11a748889f270d0bb0d4d607fd33073beb3deebc75392a0b440a4f4c0a77e7dc7622551ed4e8a7758f7f6d8894cd8994f709fe6e69be65ebba726555abd6df72df42218c9113b0ed252581af75d3cae6d2cf891dc130224dd20f709c03720e378a228f0e77d2b8cf577ea69ee140a1ab53cfbaa8a6bf4c541a1c3414fcd6c2e8be6e7daf86487f845240939b96c87f07802a100a747d82208d72ac65604586126afd69a5ba75b4be292edf3eb1132c06e6b913b111d52e96a40806076c05aaacc6b4dfe0999ac2e95048e8d3ac5d4317b5329b8cbdeac48a3c7267455b7e5aad2f2d17516cc669f759d051928364fce30edebd3e760308793b72567e827cfd1a93de42eef5fc327ad9fe8605668585a5436fd34c6e423cb9ce72711a4eb83036a2bc2bffcf9afe6fc931ca42f0aba5ff122dd5f0bc5af941a859c4bd49276eb8d3784d062d4140986d8d97b9f394046c4d931ca1d1218e66736379861ea7f99c17c820cc22487bbc6c41f87273c10bf9066aac87bca372bce8318ff8d860c25babff4f11082066f035b5edf3cd9541471ba50696da5629c60672f64bb74169d14bbeb55b8e1bae14bd90bfdc43de7f2b6a967f16aea68dd18072e140ec69da8a4ca89a1a4abb16c166c2d496f816a48306b23bbd03ef191f4772150d90e6f77041f01b1bed9269dba2f929f30a777268f9ff3e2a7255ca767c724cb7ec0ee6fe424e318dc2c930d5663abe68575837e3866aad1768447a22ce30a138a66fa605c70aed7a76d4f6b4566df122cde2b92b3a6296435508fb931f0c905b0b3ec5113f457eebdcce172d5e2fc6c8f59b2dbda0128897e4dc8d44eb3ad935004ac0202199f0aea75f436210dcabb7d21d268fecb4ff804a13e7d94e358369697946fd13f3bea98795abeaec82f9d97c8073c2e857873cd3988588203c832315eb754d924670cad3fa082f6c78c4c52bfd4578903e8cd66cc7f381ce03af1c56036723dbd2e7f648d71081d4b2b62b4c35553823268f28ee292a7b7c380f7601031d080b0bec196b93e906b6ed23cd769f3900dcd40acf08bbcbc1cb724c5b4048a36342bb186f3e90b51d81baa0c971a5120c981b91c9b71bb044003afb9ad4fa6af49b8c7b1a9d7a0c5d10b35be85f30019c8be1eb94d42772fa8b29d2fa4d2ff647222bfda507469ef30f3de6ec91143781cf7598092b8def090572ead7694a259b8ae5317e726eca0a13c73674f640b003df79beb53961df733c310276c6586623cbd8c7dc0c3c12ec27f09fd50cf25884c3540d811b69ac53866593886abe8654d8b09938dd886350322c4a5c763d684a7ee7eaf82e7cec92976bf6e0829cd65445a4a224289a55c83e0e060121194870b21a33ced21aa4f89a1b944aec9df12c351c2b599b1ba1571dad1456fc3034c4425f91b72dfb2199b5724765c6a22f14090c6b25b79bdb927bf3f9a308f3423732cd073c523997da2d72b746d6856eced0057a786674edaf17697f039ed4e4a16789760a0d589e896d1c958010fdb8309c65669dc8c27ad98574c27ccd877be4b8e7a75e597f340b055cbb37a4c2fd5ff2d1c89001e30559fa998e4181f45d00786d0c5fae09fcd4520dd55978cf37a2338b7ffb55c813a05007b9c23ad4c90fee8c7c71c9f5713da033ca3515892c79b67d218108c5894055f658ccee05d7a24ac82938fce9cef918728add22d4b4eb15f9ece7f171e5941364e2d6604cf9a015581c0ae7a4cf4e57728dee5e05d1fc1d9509471568dcc72794f7521fe80f2714854f03d6d553061b72a1b2136f3b96986071e68cbaaf1c42f41bec974f9716518d30b41081d63ca1640e81594aa91a7623db494911306fb5b160bb0b638a52c7b67a7e258bf2c6e1722a28e53877f4716b60f9b4ae902a36f6d19b6af89732658b02318c09a06156476738ff30b076c2a9ec9a6ece6e0b052bcb08f27b74dff560933dfb639d0a6a728d9bf15d574f226f2da230aa76a2c85abde8fa0fc81885ec460eede0f39b393b93aecb30fa13c6b313a108315066045d7697e0b5dd2563af968bd5a7d3b78c0997e8e445f8004682d4114b418e51985bf8693c625ce6cca5b6c7df5baccca465f5b16dc59482af96a974f7a945e1bfc6a3a83f9fb2ed363d4eebbf10aa7acb323f6c3caa24edfe214dcb9b4f2f64452b8f59f15fcb8dc689211733d778d1df3c8c443e6487df274ec912920c80776fb7b9386a531dc13a33e783fd8ddc30933b83519aef8375860fcdbac49162ac5045f6c0478d2dc80a85ed19dd4e2b54b872b2f0b9387c32838cd23f3c85f10f6f4668cf8161f017c92489552eed649d055d300347a510d14ac5c81df9e56abbdf92bb86417f3c9b6f60b1f2dd9b3a88fc4cbacf159d87ebac9317b198d001685a41ad470c504a8dd49d61c5939948b03272c9d5df1a8b8706e54501c95228f4a6619713edb6c7a0d7621053486e474dd752681a25b602733715218b0d36f710e08c98789a95a27ffb80b79bf58caff1ab24cb0aa786ce1e32223ec3dc425e5705f2dbb4c2f79a3b2ed101fade24eb5ebe728087e2eb375374d8b1df0de29027f3ea97a45edb036337b316835f203334c74341a5198bf3459432ad6de7665afd70a11284a70b31b0de1c910150e0acf59703c86d491aa80c2298801dd2e5bcbcb061fd899e19db59b2cdf62e3233e2e9b854caa73813bf208428ee6a39286545859c4980e9dd003b5dc9f3bade55b1d2db1f90bf16f31ea994220dc3dec311f1ffed61e71ceb4a859a61d86556fc4827b972de31011c8a38de2ae28a3301766d1655edb61c3518700fafbe264bc750120d6f387d308403bc77eadeee7738daba50909b7bcba53d4b990ea8c7771c93f2836bf9ce0c3188992fddcc524a023099b47e91c07f5e48b3d6e4617d73d837c2f9721ca2920a24705eff4ad18361a55a244f408df8eab8d95e78d8a5c7b754c344727407d0644adf6f0dd3803334d3f70b5872e96a09ac3d5235d7213394a343631780add45d7b1cb71b6d93979dd1a24788f92c92fbcdb23154939f63f1c26d9372e245990f3258571527e127ad430e8dd6bceecac840f68caaf7861fbd557e4b726b98cd0eb96ef860cba7bbb5a72a1ecb51723806eaee225ccce9748083e7f01c3e99a32d63952368da941d3f5cf595b407d0fa98cf134aa08be81a52e712910db74dfee7bcfed4aee3897ea7cb77cff98f6ef61427ddd56bd0a973c852d34172b9563c9c76ab9920eee2f8791b03116ffa0189adfa11226dc5215034c6d35221b1fd53426423e745feefb3cbaa3c3507f029b056dba5447725a7349fb813bd7207af6d5fa596f217feb68542b5a6b85502dd3e7a3840272242d406f971c0ac60972edcb05c18fed819664629a70988607487fd811806c10b8596d940798f55725f3e5652cbe8549420b04c04bd5ffc9aa45f89938f20d447681c19cd27f76f1bacf92eee1b3b56f56bf5515774fcb56680902b4cacf49eb14cf5c91f35ffb27294b8fe67a789ab7d0c891c115186a89cb8062cc1c750fe126d4c62fe0ef25c72a0c2057c1cf51e7d9442fd6d57302131e3f382fbd28b875c1a156c624e592a72dd39ce1be51f4fc12559eab66e29ac9adb8db04e4d9ebcfa2c7cf79099dbee64c14a951a18c573e52552d28b36c5b7d82253055e2acb978e2f77f473b78b810c3dca80dfeb0207db73fa5bb252f3862f57b97c634ab8a20a8dc653d9e5e7b45b8341fae5c3a14c4a0e96dc99c4fe9317970d66c4f0b01d139dca14c65d020a726cfbedc270ec4ae08d070807287e2c4725d5025deeb32914a842c7386be0a51613d2cd6bea8364c464d2fb60a2d58c3c47960ed15e4454cec2eed5cb4f7ab472cb248e417e55164b91000a7e594985647642eeedc480fbd9d75bcee3f63bbe408506997a11d88f6e9f733349ceb261fd181d480d64efa4d82927875ce3719e48bd64f9e9bf516893e9bf19cdfcf372fbea5ba552dd1979efc07e9e2b5aeb07572efcd5a8d16232d9c029ef8ea65bcaee2262ec30acaac48ff98d78d75f4cd13a400e26d779b3bc6b72fb85d224250bff008a96fe864e695f940d0e6820d11b662b721e78b25c86f4b3a63a26e0f7754dbb59b29c7b5c42e30d2ea3a56a3ab4203a93a32ed2aa96bd08b9a2a637981b8f46bfde00ce0d82048abc14e7c0365572e06a6bcc1075597595e2bd9d4531092e5f825209bcbc310bb9e9d88f7e834e125be052de20db8ef95aea8ef690bf8c1e08be30a3790c531fee812151b5bb6921e2c80ca6c23087bae3676b37073833138f3a76a6ba84794359d7229233b63872f3f173d299a18d8539ec0e17bff93a61ce12f780bcf51fbd2005ca03ff93a57204e33bb8a6f08558e918f0c3fe129e56211ae5c475161c56c7fc5c93c54b1072e4ffe31919fd149b793d17893a8d9f216b5c837140c7fce9f32f00e2cbd69f1b225cda7b9bac6053fb787090cdb6644818f4fc145317fd400e37d9786d8582726e8db6127adaf10948723939fe53d6d5f509aa1be9011fbd1fb2bf7954050a0b075a403c0a4481bdb420fd701fc988d50470d842f2b0e143030d810a690d37726b4941b739fca88b3407613518632fdc058e68f17d32545ac7d09c6954c0ea7254812826e0fd9b990937a5fd6c5e8b9d4c6302c6f143ce5021c1b77899af5672655e25ed542764a851c0b223549b265ee4f4e1e3408feb192f41c52a4fe2127285c63b2ba92470e1feffa32fd13ea165f05c8a725b2c6c4ee6f6c4758598a772f9b5bc3098c64da04940cdf953dfa88ac9a0b274e8b515cf6140c7ce72ef11453d71490f9801428ffebc9dd7aae447f7df5362c8ef3aa5dc4e1bcc9707c5e5720a9f93364006e25e4322e262825b91c80c60e060453628d5e41e2161fb46db726250596ae9f12498a93062fcbfe10549d7acec2c025126186c84e6ad7a539872febc6e5ef63f9b8a733bdcec6dfc7b981cf60346d53b83e02dfa220dc2a1ce271b48ce169387f6730a4d35d836fc8342b8ccba36b835125548836b1751805e65b2af7ed0e21b902184f6f9f54c32767a05c187e6a4d80cf835f07519b19ed0724efae0de682726cf5280de58a0d1f06a7af65729288fc487e9dad4326de3a57215bec6f7d613e2215985bc77b07baf762b011052fbcfc48194bf4e38952db02220baf88895ec570db6bc80d8d0056ce3ee2f9fbcd4893205bdb8fce877e67162297428038543e8f008b50c1d4b7dde9399ae4a9ff5cffe54d411a645c5101772d52b8ecb7212763e60c62495adc685d14c6ada30f9dd0750e2ab9813420e1830b2c4c896c560779ef59c2e386c4d1bcff7da1d8ad9a4534835d8acfefd57554335483ded88ae0e82f9958c93f81bd79a58033a44ef57178cb75e3400205441720e26178826400d3663026a42c1f03475a00aca350d7be469a966907111f3136c3308444a30bb7db860bf42f358a1581cf06a4fc44d94d709c3b6370c24c39a0328e09c6de9e3650b896640ae3fd76a018834ba8e3c905884fb2112d4ee355672f747188e986553637633a52ff4ff248eeb7aa0588c8e1d1422cbf22a8a9ad311ba575bd2befd5248392660b6f23e388bf321070bbbf31cd2ae4319f165861c722f15c149cfedaaf56ee54338fcd2b80f0a2a030e200b44ebc25f1b7960b39c72dec467139bcddb63cc0f36956e6b3e4fad871a713ccb29d853aec19066a70d61a8a206fa5d65758aa4e45ee183c83ca097aee4b7d2d7c1d321ac30973432a172d4111b69ca5ad1f03087f633a5b7292b89464188eb64877d49aa28ebe55e8a7231df9f7e185c8b303332b9b74d43fa5dbcfd5ea25a4ae4bb30f4df79c0ced30de6454160fb238c839ccb3f90c18b3b76367e07ffe9323993a83db3104cd58a1f9a82eb348ed38d14904b8eecb19167e139ed72c6a47411c7574ebef98ff6e072e08a9d545e7e769ae72cd0f06b26592f097e3b5e8a255cca33580b373fc00f32ca173fa2a6cf3c39e1b81904793c8aa262fda4a73a82921062a88c9477ef521d78993e13a03e40dd729f8311cbc17b525880fee4eb82e3e3ea2d9e31de84122301a09e404394f5538265ff7d62b7c1cf6a2a269817f6b7585a2a4b02ff70233b82f88ad1a7e44b34bbc98286c6a683da9bbdb6a305a44c763fec55929ede64723740a48ce4d2e49b91c54e542b54ac175b4693e880e364b7bb18facaa3d1037248196c014e37381e471e5b405f09bf60a72ad841184f2973512f9fef9ef1c063c2e15f5c2ad5626e308a4222cc5c5c83e650cd63c1231363c4fef99e560ac23c71b45ab2db53bbb83a07906df163fb9e875a2f6e4cb8a2d219ff9d8d8883c3725f516daf1c55aae0ee78b2bae0c1224c5469e1b13fcb6754f1b14f92268a4172ea8e0d7babbccade7d20ba7744aada12714624cd164bb6d0851345075ec9804ad004c59315ef9837767c6bcf1da271e0078bc725a23afe1e62d151abd0d1cd7299f4d00ec32671e97bc3049412f6fd47664e3e4c126bf8975a16f694f3b7f872c56b2f431ac8456d8744099d73f4991cf14b4b920fdad819b476ceee8162387221cfeef0f79ddae29b3ae92a029b6842cf663475f633586ea6783822232321120d797d95afd2c1502f54e3d55a38b2599084ec6b93a15e97fa5a7bdcf567d672bd97991f2b9e380f07e6cf1a2b42a481c8f8813c7f5aba5d33704a3362e2507291b5e92029f49cec265b108282a96b2e3385c4c441f8dba43903397296091c3b0ff71d4870292bc99273f41996a47164fcb8cc7add16470e19e4279c2c1c0d15aeeacbb058ae2e2a063f1a7d5e2b5fc1ffd6c3cb3b190fdae6d3af940d2f8a2c8299a72379f1a6263214ce90671f9b301a1d6fcedd591125f26c323573970f72c5bbc8cea552e022881fd8b0419ead1e3ceecf35e4d059d5c88863a666382c233fc633162fce649534b7ce084bbba7c1c5ffb9c82caa52908d5f10d95843d518c0c0f68c3ac82561303dff49ff19c0be4b9b5473aa11f3ce771383d4772d23721c660b2454913949bbd6b3e5edaea21603240d92613a41a9555a94239ab49a1f17c4007d1871fac3ea810551cf78e7cd38420a8ea43167975a34e07b8c660b212012c441e130da80e49f441a01e50125ea2ad526a63ca554113c7067618d19728b826d1826db05cb9853566dc7151e8ba4ca3a4aba38cd6f55da3f45540c4c28813a5ff4293df41d287e2c5e5815b3de2637e5fb8e630fd6362bb49f826a783976b480f2c0ea5d1b2f88b27fbfd5138866e345251643da651e9b104685f96d4abe9ceeb9180b2741c9ccd5228dfcf68d0b1fe2bd77ba4a6a5386f8c9f85dbe5fa56cbc145c4e8af2b53d7e018e1fbb0c3dd47633c8a41494238ec796072c841b3a75f5128efeb2580a7735b616a4e959081f74f3bb07e46493942948ba15c8726fc830dd63c26492e52f3b74c49edc8b8f89d473a695a0701826b86c0f46b67231835ef5d4fbfb652eaf55d4270488a6a27f13b7f73f33fb472b665113f0837216868e61490b0b7be3db859651f154be1ec974a7a30623c5b69c8cd973be957226482bfcbc2ac430fb47fa3dc9b7ae21cd6c9d5fe3116ea3adcd6c7f896fce72ebfb7eb504c83c45c0c12447fb448a59b7676b9c9d049306460481907b074936dac01d0de8819751b935f40199a6348d4c4c260723ee3492d03c95b3c6c98721ccb4b7500a25af6de8d403723b064c0769ee65aa33c9a689708d10b3beed147203517a9f8993a0fdf668740f750cae2c02be634e898d5e0cf078d0b3c532770c7b6fc20f9a3f30172acd72ec7f5a5da9dea77353dca501d288376ca368837f3b7e5f7c59308a5e02dfb73b026017404b1f1e3d49323bb369deb5ed3765ff0f2a43afb42c66f96149212605f229cf9b82168efa21ad861847f66d8ca65156ba72b533f05e62bab782c9f52ef82e7532ca2c0a4415acc31bc372154e1a0bcadb721abefd18129dd8ab213062a85906b39c1ca2920ed25994e4250cdf8b5ace910e8c18841b08afdc33ff6034e78d147f4886ff53424f146a23762e913d9321ad72adda4cb30b1e2980000f7f25b42dc4f430e7aab2124cb758ba8595ed4c06e572f476893f1e95a4ed109365eaad9f3e7b6fa00199cf73a8a8b1a74a91c6c4cb7270876936a1210f87691773ebe4b7a944fb4ed3ed2dc6f136a74b7201629604727317ee98e4abe9584edaf94a8c837fc3ca63bbbd6c56339dc4e1a224d6d7f572e574155a3bf694aff4195445f2a9d85657371356bc5b37d459b8ae5a27dbf00c8ab503eddebbd67822b06ae45848ab941d8497c0195ae2c04ba23cc428e63f72a75675e1def99718f450d32597b2206355aa3b81b75c8bb0b4a16b6c0806bf722c1e4d000866e14d42a3b17c86ffddcb3bd19a5166ea1d9c3872888248014f72b199948a6142c487d5c98cee80ac851d2ae8bcf0383d93c13828d6060abe97723dfe5a7fec0d432529c220dc1647b336ae080439270ed263f02d02e94d43934053cb0de3a40d383523126f4e5d30fc4f004ffd90501f6ca68bdd52547eaa46725a58148d03f348979ba908456b953dd354fc06b040d2329e447627b6df11346fb51f8b34c35e921e5eeb5374c90d113dd3a9cbbd74287d6ee84f98bdc399e1728784a26eac293bedbbada8aec13ede44958fc2d676b721ce5eb319d717d8075a3fc66292156a82db93905c6a8be3924008aaf273389e467ab86663d1fb6a394a630cda7a8a6cc53d18d9fad78c36e1b38f10105eda45ba0b8bc191494a93382e6ef167e58410541fc88a0608356c7237c960e1ca3d83a304fc1f3caf84535a7214b84f10d028465d2c8a1113779cb333b887e0267e3ace0ae8aeea71b6dcb0722d30d6cc320d5d42488be93c121cddcf048e8c7453f6c4b99572362a83916f3d33530edb9742dd660274bc1983eede5a4686ff26680eb5e6ad44c31490efa772a6a7447d95e5547e0f7cdfaaf445bd54034cb750078873dc14c5cc5e78733a723f8562c3414306cc9475bbeff96f1eeaf9e95206d0e047e3ebf0f19864c59006607b269e28d52b9d6dd030bf45f5b66b5dc32ae72fd3e128a026d6c7cd22d66aa105783ffc025262727366a1ed03478f28ce14ce8cdf3cb13b63469a396e0172cdb6fc351f2cb7b35c46a49525186c83fb8eb83d069ea2079f94e02e1260ad720887e1bfcd3ba558c3cbab1c0d506460681e413c378ec89e96ddc302c66b572f0d4f5570bf55baa7a159c1f7bb457b8298c6882c3b4e088d793973e8988e0a729caa92d9283e9b28d296d663240d178755096d4f35593e320d01d9997c6be347ca6907e90d57bec75054b9a234fc737ce11d20e095f79019eebf12828c4ea43dcb105a675b5d24ce858f45d6a142b2c7a80eda353e5e96e3860838e91357f256daaea4c00cd86457eb5127080809d5f9c5828ab92513d7b4ac95d4982ab5301ecc2786659093fb8c016749208cfe2985b7993ed690012758bed88229469d9172527a272f1eb7b4497b0a533ab014fc7f2e39de3025cc32d3dd3ce0258f712803c8236744034f21eeb52df46952c58e8df8d4f8d02d8d56cddb187aef7aed7672bb99d2c73590b5a4d24b8cef60403f8c7b2be4543e5da5b66f900b44ed4c5e124f816977dad38aba5032fa06017caa30055d4de713606b53dc9c84e0e6a22d72dded0008cc0a5c4e724da754fbfd7ea34cbea6ff72b8cce99485ea64233aef25f12c110ddeeef910a6dff2bd9045b91b644e23eb2fda0cae5444889353731e0f195578ab26ccc2b854758532dbf91567677ba8a17249b88cbee99592aa37fb5fc55d242b0d9f9577e70776bc6c6b36c8a939fc323bf7c9000ea79d727248f15bc97e8b50c1eb1d902f921a419159f02a4a0c815a3949bb1adabc91713811260927a362908ce7538a05cc156d1ac4c66d5ffaa8b382d574b23a1e6ef4ed1d5f72155a336fd70a65f6efd9d6f3e0a82dd0d5cf578a1384bd83fa630af24fdbde505f1507a598284186b4f04ff5c534064c04ec43200b4163b9c74d5d2629edb67048afa5103711eb620606aef0fa70cb08762649a7b3046fba1fe67cb11fc846148cda52a3bf752174c62a0660485eb778f7ce62d44b6901403286a8844ffeaf46fbbb68e775a1a0e6b6b632fa7f6e3910fd651e51dc55282162817be009a8f26c6ae7f251d7bccd4dba84841c0d527974309bb3d61e87de2fecc6beafe260f3305da541f7fe22732842eea92f43749c1cebc752de1361ce1be6eeac277b575c724db57acc9be2117427b7f4863137461ac13567513c920100d851688d25a98672a54b438c013480800d3cb897c70846f766b09845e11e99d8d1a32a0d672efa729518510d110c853ca3193ddda5d3087a2a5d8f765410b379864ce3c5d3aa90236ad5254b07f998c26e3e5be74cbd22ff82c963cdf6749adaa18fda1f0370501561eef439353ede3094b32ce8f5604736edc130c6317689a8a76c435e7ce8856283352bb33fa07bde861b963dfa659bc178247e8d0e100e7752b556d74ea3c0729c8653098fa261b435aaa568128308e40f0dc392013cb853ed277718170764486cb74691f56a2c2fdc642fd3fd8421fd8280af6a7c158c4052aa7cd7fc88e254fd0d3ab5742e5a663484923b7d1dd674cc7069c9b697dc07b8ff793294cb0d0abf8382db383b6077842df52e2b98877ed57059cae00c4836b63ce294e3841872f51b05f3b16e0d58b057626e31aa6a713561dfb8ba9a5c07bef7947bef229f721e53a69ba722da03b820ce06197c41630acea6ae1c929d6aeeb0dc68f16ec772cdd6b2aae5341f1103fbdaee96991cc6aaf966ea2a5367f59be5611fdc5b063fadf0e69bc76c27e445eefc1654bb61e03469001d7ca17e7d480317add9d32c2dc0670623167fd6c1bcc4d7f182c558639d9866ebee027bf33fa8437c6e5edf72b269b23c5bb3c9baece56deaf14f61e6192e25d285681001b9673275553fb9725db99047a0d2c613d876e5a98cb07318aa86c18b839497d05faacc574b5a6809e9f33d6e16c68eec9d0cd6d3215c6c59e96ffdc3874f571c34cdf68951d95133f11f4fa28851613c98b714de3eb38dbe42082586320b89142e19ff70aacc5d7273bd7185023c17713922e3e17e0be8f51b9163030072c08c4298a99ffb72a37287f4dfecc573f1d1b737233495ca6e21907f95ed0d0058a84fb34bc7ba2d717276965cfa05cf1cd524d32031cc45662b473d4e39b5d3d206daf162b3f859933431fd4d5611cd3efef59aab5c8c8df0490831c74d288fac8f16d1b668bc43e472c9493b69e477dc002a13a1345ca97bcc1c3e5ec74c88d0c2395093a8165f9c72dbc9ac0f1192338cfee452cfb0ed3963aa0b68b331192bf87c5695d706d74a03374b0cd064f01d776c65958166b803360dab1dab6ec3d1e2ef99cb2a15956b72d6f8622ac84c8ffa1ad774d2c25452f82332b89f19c45987ef4b679b29293772c2583f6509d6096b50de0fe9b46d886b47f2aa043fb6893693ffd4dd76ac2b1780b22403e48904b5f22d2997a6253394576a55c1e079d269958cb0ece04bdc06b4e1d7a1c09e4fdd79f8a3fad85ec79042f202954bb3fb4ba27603104351e9725f47e200fd0eb8eaecc4a65fa64c8a0eaa8b6436915f539b0e814a9fe5e8e972f1be34211e9539cdc813d214125200cb93126e0c748fe8eefeb033e479e03372f5e19ce9c33123a744b0f2db28e18f081831d39ade1370435a64eca2f03f607242d98e70995fcd56f594350318d2bf6f03cbed72e537dcdf00fdc4ede8ca61726737bb398fbbe332165131946575acaa3f7ae14bbfa1988a639cbc35e144eb6bf4bf92d3f111ba744ac13df5d667275eb3ce503c2a2a283495ec8310db8e16725ba20053fa90a3452fb216d94dcb2029de9954bec65076af5986b6a740bdec028b1641fd971e4272e37e77ed2a60bce2ae2a8b3294babb633b59464fd35d5b250075dd48718cf377890374c6ed70f7862fefea22629b576aca21fbc8ce6d7c148028527a45eadc50d1fe6f051de5c0158ec96aee22fb33b0d397678dc7f4a272f6be185bb03f751b45978724b41c54eabfb4feee86a37192a2cdd3ea709de93447333e04f14c2a79f7b073a45e08af5093248beb4c69478aa96724506f502f675bb2dc10092182a741983d13867eb2e99bc0eed8eb558837aad668e390233a7205759552f06f59c2e870cc1e5a4a2604bc6aba24ecb061c51a55e77dd45c7848ee8297e1668ffdbe63e804ba5ca4fafa9d16872d69873334644dede13d424e28c2ea004487447375e692fa03b7d374f2099a1ef440a7d700758f4297eaa739724bd05b4d61326395e9889ad698113875ea751a24fedd65c86735c710b53eb3722ff2aa2e04e22d506e428381286cc4eef9e2deedef17d088e71a2bf73ed73c470cca6146e141179118491dfc44eedad9166a0038fa330ec4bbcec14486f0e572d907c54ac4df01434815ec6f9901cd11171ef3c0e2ec2664da1e909372816872a81c88d29eb9f7971b010b87649c7fc47a04ab3afb2ce5d7612ac43c9c742572eac873b376fa1ec57c88d35ec854efe073e76ce3ab3056c8a011312f9008c832242f000aadcd41c2e3e081f139b0b42a59275649d5993cc448cfb94781847c721e3a2014098261205c1dc9e281138add33be2ef9a12c95db164e4de22940175f7afc1e3be3b07054731964221161fa515a13ab02625534c5ee79d6a20e907d60f76b033d5524de376c98e2fbc568fdabf4b05af35bfe6aec17c9b1b207f509519abcd74505ff2abbeea01b69724f12dafc42cd5daccd1e8d19e0b2e003c4717298df51424cb8376be30093fe3490feaef4eb3cf4635bf93112b97b21deb2b37209a1159a90a75c71915f40b8479aebe6423cca55c3f8f74b64679172117a3372efe740c905fb2d97c35641c13ee5366f742a9b67e38e4617f12cb50c0df50e72d4bac4d1cf73b92a2dad65fa04a12b742aac57a2a76e7c0f9f083a0ba505597222357580643c2aa840daf92fb20ccf3bb22f550de17b45e164c466d93dd0a809d9c9ac11584c498477a17ff460954aa7d8617ae725d1b6ce52689d2fbbbaeb721510698e579117c9bad1e854436642e9956613a18f156d0f482021a4dcbb1b72fa11d29d4f95e129c4c0a726010c15850958721daf03b0321cfbe7aaa6bfda729d08bc7ff5652ba02e18e137d27d3df41f88230a86a773acea635b45f460f2722e8cfad97076f8a665555bc8d30477957bd38cb1099f9fbb97a3196747f4c1726d6a354ca74f8c2b5a5b0a8161ecb49e0b3a770b8f35bc4f498a3769e4f5253e725ecc43efad0150aa6267e602f9858914788b800312109221b65f2c2147f272cb77707eae87c1e16f68a257656c4509e5dc0a0df3be88488d86ee6ed321db721bca5e5a0aeb9a1e02282c02ba3932753d5caed61a3f4d00e43cbd62b749ae72b7a4b6868ea6d37f8eef7a8f34cc04f4a0aaa722c2025091780d0f4cd7c01472256a78d9a02bb993d260c07172b98722e799c359a62d726296bd6f1b91da2a2e9e916537f0a2cd2a1d93b8bafca7762a9ae38d27e9a0f307bba52d6e0d89b2500e65ebb73adff58368f236903977fb42a6c753124ec80d1fab450720a3407724d824cdd960998e69fc34636bfac6a1b1e3ec2d6205b8476ae004ab85cfb1497231ff02a1445678ccee2023671042929ea2f7076a7cd81ee7ef68cbac4fa50f7295ad17bdaa972c3fcb75acfcec4d29684f73dadbe9426bb101a7be5d208a8b72717af1a2dbef0d6a07e40d4bec2717a1b13ee7846e3e39509cc1c968feea9e0898803b299bae4e90707e5c2dc50ece36b5ba4cd8b0152ef1881e8c5c88d6a9615461cf518514b1d8f99494efca11744aa5ffa21b8ca20512fb471f40a68a4872e94cf09378a58ba19511ce969d033717bf5c84db1b0b23995a1e144f7cdf4c7275251136db4d51300e59679309b75b1a313715d881e55f6c8510276d2840de1dc6fa9d549c657c4423aba668570aa1558c5afca368ad7adb6e6e10b8a188e972f2dabe95e00d1bc90a5edbc183467b6059c653856ca3781e30e8ae175f008f72358db443b014d621aba379f13eba9174bcc23d1cc88560fb5edab44108b1b3556e8dbe245a4ff7debd1072a9c6b9290f07ab8ac27d8aa2a34cbee0f81022955cc66470b1415621b6af19f83304085b5ac98e82d6a173568c8d7cfbf8c0d79772d0af9d42f009c78552700c19451cfb18566b10543ccc7c99f96e4c327ce6e715d09eec55786a560eb0c9a38903b17c802a13a90d0080dd120b1a88ee14cc28729feb40c6b0184cfdf80d63f6d6566c5f00de3735812da0cad185289cc4b63568ff6d34f194655e144d368d47d7e3a33066a7d6ceb097574a9d1118af1a0578721ffead60a2b6f174f051c0dc5d8a5b56b5064781c02c230dbc59e74d04755d72601806be6a0f78235d8606eec030e00b21534de442b1085f2224c90ec509cf7206e99a536f0af1d4a0088d02d28b7b24d0f9d31b3088f0ed2163be1b7b662c140c60a5ffaba730bf5b2893f6a50c1288c3cc50a903600e4587f3bd18e90f94235365c5d687d80baac4583b87c3a2335999065236716786b26fd9cbf28cac8a3d58da5fe2413b7e06f9e1625752deeb04ba88908f35f3a1734ee00cae75a72a72176d7caf616f16498da63b2f75387a1f98e7c657d7d570800aa78dfdd221e6722383445e1070790e9e097c6dbcb5f7f7c792c919c2ac03cb0b1a4cfec958d52db1b22dd6ef5096de1c337a67cec0809f9207a416b301a700b0fadd4cd0a25b72eb0595453bd1a04c69f2d0253e2be0ab951b127bc63a3020a6e2fedd20a1f44ef2a6377e20925d05330f98a741000ef1c140be126b52d96319e1031a21237d72d599e681a5d922222e9567bb782de6c5ec1f50496bd9e4e9cff40812962b6672c4d01346634c40fba570ff7a12773cb5867f75e9cd178e245938d8c1ff805b7267c82af53c0ef468e4462ee2a52bd1d130cb009a812fca3e65169282fd2ff82e0c0a56a13b15a9531bbb2605b1fc89f17be98d1f751616a1b81dd211687bf57229522fde7284c9fd5f06608c6a68cb51c77b08edc21654ff1fb1d3bd5d3ec672194f66fa3a983b7bf6d711c80505a5e8843d85f4a9a6a011cc8c822f498cf572e5f3bc13260fb8a2c10dcad126e6bb9acaa686454595e22542511e070010f3001b0152d4be7159180f4e59ee16351d910bae319c70f3c9b3f7996ee666e96a7257e30b49160faa8107212df24adc565bdf22a1bcfe344cfaf8e3855e4118b6726f60c78b71ba154bb0b4ec01a74ad0be709d4b24bf00532c34e5509c5f8b2e205796b3dc71c4bdbc155437d6f7ca85bd445dfd9d2aaf19dfdf002cde12260d72026eb9e250fbb15c8871d5133fcdf6fa32d2279d6f789155884ba7ae76a901727c21a4e526288d1c016224546b9efb26de6c4313a53cc3c878756677802bf272e7ca4ebc4b00dbfd46e2f2e6f495d4975836b841fba0cc5f01b85a026b44087253e6cef6e078637358044cd0a037e9f5e4b30f9c6f06e52ed5665eaa995b93724c42764052c114176963651766f54d367b916d30dc5b75c6ed27cdf11fd46829b7ce76e8571af87d148780bbe5b9544f92ab4fe7461ba84e340a33928f6ba572eb1ab91431d55280543567188762b8f5b8ea286434f596ef9a792cf40f73ca079380ef88ba094f88dc029ec0cbe35bc2a7cbaefee59710e3427322357f91d2082bd4571ad27338b851aa870f2b7928f49a63c5c817756382f506db91cf48a372e2ff97886fe19646ffa731e9fc52c0d389e44a4432c9cdbde9479fe24ab14b721c9f1e735c5468609a6ccb10d78c17f8bb8c3b4aac343e9b945e30d272a85c72617bac3118260fa6786cbad73862c057fedfafe805d6f18a91dd29d0199db772bb8f6050c3dfd6738f306c3e96d56e52a48ca9dd3fe435b9724a1f7f1fb95072e76dad633801c9beb711ed4b17d4978b22dd5eba1e0272f7042395a634b158723fc9355d36c97359ffa704791277f7a49658a524b1371c8918a0fbb8c2cafd72863b297ad5ad6c8abec85073afd73ddf5f97823d315598217f7b7d4efae6cc722abcfc4d5cd2b26a7aedbd4093a6762490cfaa9fdc400b4035ab02f5b938b772f909fd9e7b0874ef2b677c665011fbf4c2b412ca1b99bc21ec36fa57d436df72361430699cea2012bb441741664d280426f007fb1d29b70fad0adf4e37048d7281a1a9a3761b8144ab3fb488da855575dd7a018545c600e088c034abaf00773e85818994a5c896b74be38b2f801087579eda63fadc201756d3d33fe5b9857872f1a51a0fe40d7baa6ac7961ec0213f0a5d7eb76088c4bbe2cf2e8da4cdd28e72da1270e269d4988e3ff8f64b89df5adf127e41362ea2ae3736f5bb02aa352e56b8889132e1fc1fe79e731d7a031eac9a3d95094d146b60665b9fad799d0168723a8ad2ab684461cf080aa59b2d3a6eba49f4f0043ef37ab1f70e6113aac08c52b40b1e5c2e36a3c99f0988b0a747fa6557db3c2c5460f1b10bf96806f6036272040485e981ff9aa4f1b901be4f728f3e087211301c73f0d1b87273edd570cb05c74f1259f8f16cd5da147e647c9a2ed109da8dc262eda0cd84ebc9c24a731614fa0111da5a3b56359b82c1e0a9d041bfc292133c2a478895c1ec2254d6466e68c69b8d52ceacf23af4030cc6330f565499b3b5cf536073a33d92d2bd28608c4f773f651ebd4ca39ecdafae5e58d0566a487a25ebd71609eed301abb407b2110989c25d6f51ea1c789be59ed5ef012ab5611af8e4e4532545b9011a3e365aea72e105530cba9b046da7463a46eff0b890230681b5cfe90c42d556b98ddb66bf2daafd1f6cfad9e6fdd54c00f70e4e9043e6f80266e695b4defaed27981dbac972dfa35f3ff426db21cbc980edc9fb9b64c094a71e503a94f9bcc610744673e0720e010f26513bafaf5234d31c36ef98d1a2d9f521190810ad4e12ff7c0eb4a90d08c5d44eab7d92bf405d8689b119ba570ee9c1c1e48537c9a12fbaddd322e372e52948ad00b557b08fcd82a74856947adc1f9b5f28ae92652818ef8529889b72a007044ca424c47a45db9046a2d8e7164a33a76054ba5b2b2a101441e572cc726648b815544f5f53011f73570308f92870cc3de9c21e90c43759609037122e72481bf08fae3cba8aa37971389c5d74354c8f9fb5fe78e68b57e52090fd10940b84847222b8a7a16ee540b63d39af3d1690239e2f972c64bdabdbf4649f3cb572bbaa1cf463474985f4710604e06a0be6e2a6d41df2b26882011054386f2b270089bb995be95ac8c06dba099568d614d0f7422b1d7c01992555dfc844b69a77439dd59bac455929cb61b1d7c13de6a3b20ad7d682d0d7d425ab211225effc0c72396cc0443bfd76ae40950fe950082aa09427fc99f3c3f5ded4e79448e85faf1337a4cb8a48dba10303dfc9c5a6380daf388e1c64223d05438fad6f08776bc05d2048639c3ee5eb1d827f1fde3ef370949d7a55e7f93056c6dbd5551f59af7d53757286c0df3727da1bbd2869f9e179671939ca7784b9fc31de704705f10f2272ef0131c22456da73d004d8f4d59c5b52bfc0b2cf0fdad8bd0cc883c46cf2d27238b39f7e01ef62764f10f3073e862f6d5e5082c60fb42ea188720c0f14f83105b0dbe284814500b3d2542a51d64ecc3e3490b6aa0c35ed046cda1506836d345c139b0cf844d593ea95dfc3239f3c75b4664da3f5d95ba3b39f3c0dbcaea4070b83fd424e2e3b0f4a716b935975c8c7eb7584c69e56b3c5c6c17ce6f5d1bc3b721d0ad9eb14b7d1d0088819424d29d0a58710f8cc66bf62b83a67540b9335f872d83b0ac6131bc4799791c8cf4eb8bd7d0476cab767391354ce4d4506c71ad57254bd2b9c8bd10d9832f7ff7b5829e083a043f2d3a914053d91690ae8d163ae72294b53af4421de5d2090226a04400269ed36ef419e2b56cc918c92abdd3c7c7250d2de7788d33889b1297a8636d685d4079a8d0e7aafb2836a193003317e45721130a3d86e96b97c5ca004a978889e9e0894323c6472e3331ed12d08bef63d72b816f855b2e2f6e26e25c2ce5f0c9cfcfb4b6e4df21b9f29b3b863714c53057246092d47e0fa2b35cd2e451ebd1cfc24529a296112cdf5e7b9abeeb00f1209728d6f44380799c3efabce56b47f838e54e23ccf86759fbf3981d385ba6ec3e272851e5836297bf3fdd0b93c31725574dd1eb7397e344c8ed39035098cb5b4822ad0f09db38a3d8692266bd555e4b40371202f407fbe4ac475992013a0523ddb678342c8428d77eac493a45ba7393b59c364ee73d66d3c40c74ddefc6f063750087fa3904fa073f3fa0fa4c4e03e5d686100a585d848515164c0b7d9713c23767208e65c563f665de27e1cbba5ed1590cc0e63052a320eee79b684a5febb1e7c72ddb9b262815088f8067f434c644d10efa42a6171ba259c87cc479e8feb640e72be35f661932282c339ec5ae76475ddc8aded966d42f67fbd02348d687b2dcf626b92580d6b7e4b1bf037bc0db0345ae55abcd7868bac1c8607d4a54f49511627c9c628f65d34c361920c45371303dc0a5a99d7b760d6aff32026fa46cf580072d4c7405d295b5b0d41d0dde16730576a4d52940ca5c6ace3496ccc354d2c1b72d707207c48b3dfa3c3dc790f4712d10d4091f98055d7447276a6229896714f242db8500b545142216e9d38777a68409353ed4d764faeb6373ed2190f9e8f8b72a6987095a674db65247ccfac3e21aa2c76a143378eb4df84f88a4b5c81ea67729b42bfd126cb8787fadadb15d8a9285ef1f3cf2136e1f3ee6b4a18519d4e6672a22303a172a36363a4e2b7a15e912f775c8a1c18e4f95f8306c624c8577ba872945b40cae185c4fb3865f9353ed4b76b0ca1af69b0d18122a48c0d4fa91ab672ed8dff252b78fb421cede5d6a85d6f292135f01ca698d340e0c5df2b96ef793747fb0d5d1aed94e6ddc8c4714d7b7d691970fb1e6205feed706a2d989fdb1724cc731f0bd1589d4cedffb263013b1d57a7461fd9a790e0d2e85865bc23c11e722b50dddb7c908ad3a4201b18c7a66a844a55eebf33eb1c77de66dc2ba07cf572ec8864148d813ea9de7ed3902d83b6b095d1b83cb4a34031180efa32c7dd24421b63a083130fc5e3140b9823820f2a3b14c8bfc70a213be8080296695d975e241953bb6743555b6086ee738b2230383253627fa75e559e1274c557e76825144f9aacdd9be91d1ea111a0e6cf02997d6ebd8aea494d3f78fd454224d036a64172125b9e7a18235a3955a9fd5c02f86dccf51a637ae1c9eae9a68610434bf72072c0470c7a475ef2c661284a77f65316eb5350bf80b9fdaf588f0e08bd89c9e526017dc4d70b75d6b9417d1c1458387b9d5cdcfbe9d2fde1f714bd6687ec555852531ad5af63fbf5cb725cd62e27290948a76fc1e2b4932c71f7b1357eade3357267382ec1dae41e32483a2f8dd515e1795c431170f95787a956c09618f1bbb972e7e656d0fc0414ebfde5c48714c32b1673e63a54e1d084082f6429a2947cbd3efca3ab2157942e8bc3c124b0b18b830865ded5ec7f754d4feee838c2bf2c8172417324535e8312e5909632dd011c93813d6534efda5a6c7892f21d66640af257a95aad42029f208d4cbb12e29b59b9d43afe52a96b560064b12d2a030e375a72d194e739c045cebc1e63cb64b62be0c9bb6192e0d67ae855076f99d3e3955c72feefa690625ddd026bc0a1bbce65a771e56299bd38d40b0a6fe4e4cf9f483331f1152a3fc5a4bdb8cbf70644800bf92a9cbcc85444932578246041c4d0dcd7722c3bc3fb06e6f345a8975591f76037c815b2ca9b9406cec5197d526eb23be472a9f80b0ed5534135a4133a9fa1f2142422812b763f04088a91d714e2dcfc303e9a349f78898a7a16d19c53f0fca9218b82ad99c51ec15e93fb3685f7d7c11d03e42fde33ad70d9b90e38b2367bd2332a7b6993a299694b77a76b506a5b962c0f314e597e19c776b1f24c10978fa230202b2de02c7685a755ffbe6a3b55c835722b81c0744b111618e2180d48e27c3cce83e3b5ad9223c3d6a38f55c90b1d7b29bb70977267ea981baa27f88b063f3b3c1b08d9bdbe9a307aadb3d3c9add1af728ac49b42fb276007ffbc26c157a3a2fc113f011f73bb0d8f0d93aa0bfa681d2ab6158d68a24ab57e73658a7ca00b689954f2877ed60970c6482f9ca3e7dd38538cd79a0008111ddfa9021674ab23452d9112e459359ffa03c062d1cfc94042721148aa9955a399c96c333da943b53faba67d4a5ec75be33424fb8df78e59517289d00f60a5276cd0e1f679d34f1a9593b88b351c3bf35c31f78ca6c1315fb472503e49f3dede63a7f3ac0cc7be359934e5f8f6ce51fcdde4b4b08f13029b9b727c119ba4bc09368c9d9471f520f8862bbf3f6a18964d742311634748511093722c9c88550d89c48829ae952efd232609ea6c350777847376465a348274936d727504316c21ca1f1ad37ce564f4ae741079bda97ba95e3c3c4bb726d8eb05603d910fd1e38e2abf81c5ddd6bfc8f4af985fbe922dcc7c3b47221e9a4c71098f722561b483550b4f4cb066032674db9a1cfe32d9fb27c1e10c2ad58c6e45f74f3d20c6c6729f25c335e9c77c77f19263c7a3418461a363579d27018fe7c4273f728018d3946f7ab4f1017889e6544f727b06e88281846cb4ad0c5205656dbc0f72c4bff6151c9c460cb0c4ccb8f91e27da2c395a4f1e92768ffe7a9380858c020d31590b130459a145da7bd8c2c885efe67859de0427d9ac03036c5b1c1094a53282bb28a06a43fd7d809ba8be13edd16d123f3e5887bd87d213786bef73111408ec47a585d2e258cf7471fcb37a23d866352e60fa3af5c1580ee7119709590d72cd2010b06f6b70cc017ec5eb5e7fa1a312c7c105c23498c82447c493a5122d72a54f535e536da05b0481e89333629f196035b2b1b0fb9881d8e2840629f6d75eae5d1a21b0dad7f9afc31188f97a26f88b0bbea2c372db48426cf11e25382665459060b44ddafb0d50c09c3b7264bb16606e7c9d9a40fad1cdf81d3b7350e7727c067c65a87d25a0e5b3e4bb41a1ae9a41d9ee99f9a822195abae7ce3bc08e72c90b69e92bd31cf7cd426aab4b86e8ebcaa85a99cb55098927a42f698b1e0208ee64f35ced8a132bd85f3e9a24c70471cd91711e726bdf4750f4abfae52cfa72bc2291e9d4355caa5f98dd5fa87c65d351add7e2b9f075984bfa10acf3e78924dcb9aae30dbbb93fbc6a4f45bcf773172eb9426e4a004ff238365d6dec4e8a722b1e58e88a7a3856153d6fac0bc8eca8355e7078a0b173117df8e794fb502e369bb731362e871f499031f4b75f841ed86d55e93e7f18e2e69e8a4e344fb53172747590eea4ab148000cc9a48af5fbdeda8331db224bc0ae15eca949a356f0472aff38c8a8b6fe0953025aaa965dfc5a73a5001640e8dc1ec1fff49dca2c6347228a59e5b3b1d550b94cc61ebb361a8bb24a2e881c75d649c975574e92f235b722ffb1fa235cf12bf5672dd74edd8356e4b5bb725d1a9ae34d1fa0be2581ffc72af97a5a61d2781609678a678942775a2e439031d9e0f6467e8475dd706859772caedc2b6a32f33f15ed583c37bd90b9b68a42ba781fb8b1f083530bbcb229f1af9dd7202158cee7f4c5846e38c562f1ee72d8341c1c238afe9ddca57b3813072656f6a64c0fa5c827e3478cb2c170a5f792b9fb69ec80876f7b7744eddbb2f48a80d0f3d484b4292e616dbcc304b146fb46c0131171bbb50436d4a0a88994b5b5875027291e996f51dcb482b827ed0cf116e9c1d88d8838c71f36d76a8b183725eba44505c2638684be52cf95175ca4b0ebec5f4f9d4cc6da460d2f6b08b0e72ba28ca801a5410dbdcf3068739feb1c1d2dd070e6d4590d3b18f13b7a9f6bd70dee93350e155ec48d8a81721cff26c19e8eea56e00e34847cb6008d2c4f49d0e6b46d0c2f962a4fbe01cca8f8e5551c0ecd7a75831b67e44e1cdb2acebcfe61fecf5aba4b13a22c6886fb941f3740242e67012975de1bd3e6056c2dc7bcef37268752dadea4bd082c8388602c27f22577632d4465c55ee926ea21d8f7304a728a1c96ac1485a6338f7b263fa5d45a74c058e0107b182232cbf729ac71bedbc159caa59d26f746e576baf956e1f3907a3124495355b2ccdaa197b7342c7b7b6725a90e2614677bff93eaa2d8529427231570a71cf45bf51f1cb001d28648de96721850ba9fa6dd9f7311294f2a3065bc04ca1e414776714d54896f1141620de72738ebc3dbcc8891a7c6fd97de4f34ea9341033645b86ce1b0c1b96bea9152d1b82e82661252f75b210895d3f9a8b4e817b6a580e0b15f98b7eac25db70d5c458cb268ebab0bb6f01e7729fac5ca9e939b60a47de89d9b45c231d65aaf48ed7431e6b8b4ed177c28f88b448787afc39dfa523630e56dd8deb6df4ebff81e6af72b8736f7bcbcd83a5e71174b6c14fe3508eb12d92c8055cb09124afca6119bf72a5d3decbfff7b78527e7e53f451387221a8f6c36a044132e8537401625fe6b23e8605c9278255cf86df0afe11a580112090861a434dbeef2040851faedbff172f17a21d75bc37b931cd8b5f6d60450f6b2cf93027bfb0a513ab07b6cf5f04472a81f5d50d80dfb7f0b109526fa64e354af5587c2f20c8f13a01fa5c9add7ac72633c120220c13799b9140b76b9534c5342a579b0859653b5cb60bc246f83271938c8bb46985a38a6de107f7476a34268ffc817e1130bd52e12c38a9980fd0b721845586eb86fe9e6ad2472023d6b482e0a0d99a3dd0160e0c95fc2740eaac1720d8f36fe5640b73b360ef98592060c583c60c2aec0e658a5951519392a82ea6905c37850afd399c727f168be11f6bbab51c708ba4f0b5c212a2f8ebe25dc7a727c0a843632a1d999598e75e46cd70340a43e6d5cc093fe305aa9f3abcb282b72327c31e3422533c51bc1816fd894525ce4e4f012033754afe7f9d2a57af71613da1a84ebd458a7dee90bb032e13ed7ca4e0eea986c33e06b2c390c0d759c2772869293cb59fdfd1e97a0f342b6e0090f70258a0c4c02748357d7ff43ab321d72b3e6b47e579a88aa30de85f51c696e88242952fdca93e87ed52df0a2291b5972dc02cc8cc3dba2abe8cb56a60d4095fe34664df72e39bc2f57d6a21e47de0572ace965d70c83212d4154838e88b6606ff167cf9e9ba8c1b14130b2ff375fe47261619cd88d9a44f4bd92debb0f7216deaefbde3db0e2a4616abe51042ccc297262c72fc39ad3b9b23185c7fcb3287ea4e94a0384de2ce44496d8e9a33a947b7258f04356a9f743373cbbe7bc3f0053bd055d446c78585b53e2c166e441472f72ed94e46b505cd41493c7b1ee12f643d58c0c214f7dc525af2ce4cba00b24ff72b05bc7db9aa66196299c0260b63d318513630c14e0d22655356389931ea1d472d65309dd24f28ab93ce96f7b4699f7878d8e069c756457deb4fe33f5483a327272014608196e26f4dc5ddd1f0c08cf34f8b94528e2a5912a5934d5018189a1721abc56b4fae99a87117f4fe485b1ddb5c5acac3a11eeeefbefc272306297745f8c3cc8bf91998677fb725d4f67ee77cbbce39eb1075a3656c752feafd2c8ab72e98bbd4931b20eaa8e7fc32d8bd715e7c097201ad7b485974a2fe5741c051d723c4801dbf5810f97026daf8523ca7fc87f114afb5aac7dd83297daae1257fb721afd50463c92c8d52630e0a7c84f0678b0424683d8e101e42a36099ff1af14725b4901289497505a5d036de20a1139d1f719b7cf8ae17af68100db5cb6f99623000a81a1270945815eca5d4f39c74466e8b537093706f8c855a11f32debf5224ff06c087475db616f9dbde74d323521c606b62f13bf6d8c39da6ed6fd840fe341708ec7cd655aeb6d343774c47119e54c071c5224dbc6b4dad6a05418a3ed0723efa78f9bb3c53ae25f5bfd792633e54e03666acc8e45705f1b2fca9e37ca872cf5155cf778506b7a75c91eee45167e637f6d31fbc429c42bba6f3b2e035104cbbf2594331fc9f99917f7667ad4e1ea8c5f1f1356ceb2bb6f46176b59753ba7266e80b6787019565c882735024877b6365ab9eb0f6dff27ebede5b6280b1b472443c2da7ae0f4ebecc6376753bdc192dbc88e22715992a006bb9ac13b1d61c3127ab1748eaa4b7e97b353b23f81f6c1cbc7adc53c7df2c6e37331692774b6c19c2a11e0d6f33f25001a24ee971141c2b9916e89da8688016a0eae5ad9b0266726d99c3ab210d39403483d62e9f8a6b36a87f195553fee4dffc1ba1b3e5594d5712a601a22016108c7ae52c7f99904b35f0cd801ee4f118dff50de303f3ffb8724c4b5e84f9d139708a331e2869cc3703362d2631267671b34eb762c52af4df25cb5be8185921571fa1cd2797cb70f582d7f8e6a54896c52dfcdd363595f7ee6861da89a1110692bea53124724718daefb2cf211ee39e8b4b09d09189edde0d722b3a460d4ddd69303410924df02ba6cee250d6d5adc10888861f1fb81fe5fd2f90ed3fb9422df3bf92447a65064dd401250efeaba3f5f0b6bd79d53e9f72293c2c635e235583b3adc82534ba900c1737491a5cfd09b7348b1a319f5f23d64d7276c84e5f9979a2f69ae5fbbf54db4964bd44f2df2fcf3d3ce9ba434be5ca3f56d20258a5c0470d81179bdc4e5a98c38a2f3847b1e83af45fb20be5ffbb4f7e726b1b5c70cba4719ce1c086c3bb44a04668e232f479223951836d963a369ec5729ded39071016537e1d293d7af36bafea5ab82272eaed0bbe20423b83af79f772b59da1ef93aa8e643dcaef9eab70551f1cfe3ad254e3aedd668ce77dca1ac972ff0e4f56df68c2021df03d33aab24b1b8a5eda01436777f22f132d8ab243530451d5397c934de26f29ec846e320ce412729dc90130c6ec45b9ed8dc7636f4572c1f6e49e8a16374ad07337fe2f10919f867206fcef7725fe6bb8af805acd4b5b48efa977d9ef1cdf96905c39ef6c76cd3eb3cc2e987a31187dcb9ee802c168603e5ff76f21e5faa7e8e23377fd704e1fa8d44e17ca17928fb789e5997c7014725afb95b4dfc2a1836085863973e87517d1d9c3af56775ce993941ad7477e9215f9f4c72bf84335d698d26e9f25732e683a77bb49312cc3276335ddf306075d6a4658220aa053d12d7d3c467e221d4dc1c5e1da30cfdcd66e3f329749b37b0f349ab04ec4c220eb67e5812d1eb3bd7436057a0dd4a1a920815aa95b2a2a4eed72899f8fa1fe9eb5297f2ab234492ee0dfd30d2470a82bb882af6b40dd6e871f722bff60c7569f3aeb953f3f13cfa9fd39a57d33e4ef0fe5a12a3c1a86e2b0c056e592ab03c946c022a1b3e5662d1b47f1e1d6cf99c5b5180871cc629be351c518dc400805818fd2226fd27df5675fdf61bc9c2733f271d3c26e2db56c9c25304a3e5e594087cf9e33f12bd4eda33ed29ae755fe23e67f3edb0847a7bfc840db72774536531862da401f346ee3388426bc5231563856801b99b473b8c3a835cc036fa6567eb2b54486863e5f1628c8af4922c1b370c19e50635a9adb49b2e1da72aab913d8b71ce27b0b356277a5222322ff4fb7ea4a4d1af4f11d2a0a60c7b7725e03183993bd64bf11cc804e4d91bc68406e584c6d7e8c4ac07bf8afea831272e4d9be04dc89f4ba7616029edeb9ad54d39bbea39d2dda5f291a925fb82c1572eb62aa2157e285d9c7160768f14c913a79483226e28917695fd948f8ecaa4b2ea313bd1af2419ca4563763ada85130a90cec9e4ff970c207583c0634d3073f72ba79e9530b7487ec69cf5909a2f1508c5b8b92c655cf8a2c58f4efaac4fa5017cca7b823f52409f492aa799f7e2136a08071606ec29b511e24a9fd36af13aa72f0fdae9feab757f28367e8b93098e3034d804d3fe9bbfffe33f3d917da476f72f84954fd2e287c74ae0c2be8031833c643d980677106606d30769a783a78562285620bae36cc8df4004c3f860094b386ff5512e478879236e416a16583aab51cdaa1dcd9c91d5da08a2f3be208bd7f9d341d564894c02863ea1693d9ef2d10720bb7ee9d9baf236f50e2c84142a4e2ca777bd74849bf95e73c02c99ab9f2033f025802dd081a68ee1f2345273c68ae6f676317f90aff64228cbc840402ae2e613952b89e286885d9a9f1cf37a75f48ef57d8612a21c8bb10a8ab767751515d72f897fd2bbfcf24748bed3069785938335e3ab0f57f5441284acb0fb5d06e987223b70ec34f70228d23e8b50a861e7565ee73e0af1deeefc94d627713dbd82329c16bd63b8b4e95776f2b98728c3f711d9f410e6ac266b05282401e62daac0872c34c547d0ddb503dfceec0b2a57ca220b527bc0995a400693d94e30e25e24372cf88f51e413adcbf501636b34bec4c6dd5df1afdaddb24fa37342a562a9d40724f9d04a8d5d2777bf92a94de673fa8020efb496ecfd0015d660a96d636ca5765d99857012d1bf84c2cdd7627e51b578e510d0de57da48fdd1b2934e95b649f48101d6cc5ea4417dc36daf4f4ca02c4206824e3536df12281fdb84dc0f0869a45641e262df47f9179e5908126adfd906f01294a1395442278563969e47d9acb542c51c53eac7bfddb8aaecb8ffddbab8e6348e57d1423ea00cb6dd5f3e815cb5acadb58a209cb819d7beb0f6fb234a70785086af301487ba96f3898e61f3f6572838ef712605caeb194e9f46abfb8c308912576348a60dfd72dd30c334db2f97221b2e837bd4c6fb7a2aaf042035080dde2dd3675d95f5d91e933a99161c8be72814fd84b5debd3fe6cf95438580cddbd8405d4591720864f271aaf726df7fe7244c8fe66cc465bf5eb2f8a7017878b7dfb3ff352e7987fbe7fcb5230909d5672e1a5b7949b2d1d92bef547d15cbf60dfcbaacd4c104f21204bcb2a6ff64f3d72447ffd463d47bb6969a3cfe6d30ad95e861949e3a8cab6c86574d564bc6c57695a959e96ef246659fdd7db45cdc42c5f20fddd3f0442afbec91385493b9acc6ae8bf003035cf5cb40e5d9d0f26e6745d06cd57eeca16259f8d50b9f5ef5fe33034e2f2203b0c443dd652dd2dc32e2f863fd46568b9f2297bdf8123a00569a2729a123fab4f68ca552ae493a2c5f4d3187a3a6bd1b8d4d836c96839a7ed280872df0c605f2dbbfa963bcbdb8d61d426d05c12cbca87f7e90456031f2adbef31729957289057f9c614835c57aceb61fcb0c5955b56a504b62b45b095757093a772ce346241c92cc154855735ca75337647886fc85009e236c110ba65036f13ae72d6d2accf1fc939d2c52dc51852a6c2d670f7273700758286fc64f8c7a5373a72cc07bf9aa454561a3c0cbe3ae60fe939d95a98a421f8652d3caa78837e03b9482d1f4a1fca09f1f859b262e939546c5eed8d75acc021a0a552ba38da9e314072dde7ca3fa1f27b1bbd80318dc1048791e0c1dfab87b9d1a6f67ac97f8542c472100f9b0dcfba5b1db12ed9c52e496e2e28101739fa61d9c574aeb01f511986110dba21b418d131209e165bac3e0c14d42e5c3fe43fbbc21ade690f0f03b808332a855a0e0668c6310f2ba20c1ea3b4cade6a030567347c9fa2060f76028fd94ea8c18477e132c79c196a9ee72f324a52d07fa2e62d01ef27d1bfa392afa3fd72055b3961530ebd868c0f4bdbc25a5ef01167736c3209ec2ab87e89bed93ec1727b1be4615d925a1f0eec5c51b40ca6ab9206b9d87f059fe5b7ec6436d54a0b0bb50aae481fd8083e95f286d614d47b37780dc9ca07688386394df20462a98472ffe93364671b80118c4faf8468ac80dcadcea7f8615153d7dcf2c5e199992672958bfdfd46b93a609e0f53e04d6b75f26783b55a7220d158e7d4316613149d19685a26efada3d0eb6ead0bbf9f9c9e7769399f721789455da22626c70ccb34725b4a3698fc48b93cc8f48a418cd7a46ddd766f80fab7f389e6a519b266d4c4523dff30861d970f89defddce08c2815bb26783a23d85ac42c7ff1cffb82c9c33e2194519efbbc03258073a75a4432aa6cda594a9c6628e6b79b6efeaac0a8067292fd3b0b0a97bf226ebcd214b39ecdae1f9d41f8f7e0c3c0a8eee38569620a726e1b73048748f5bbefd77b6bea9ba2f5bfbb5f1e6adfc3e5a3cefc90a9a2e2724c7aad11f81796c1958222de1040022022645cdcb7d079fc66a3500d6b430f72df1283ff5e30abaa56637b0a1d28c6e87e0a829948ad60d6ea501666890f95724b57716dbe635064d54ecb681d4b4c9b7870deca21bf40f13253b2b132b1be7237f977d59ce053f39d74f2e0592b8f8e193aa7e3563a46aee05984da2d2f5a726ff0ba7776c48d632fbb723f81b860213fbf16a0db535d9fbde389eb7f8fac725a9225661d8e1080ea20333a7c8a6c809c242e1d4c9200fec8abe6a11d41247287334cd48703a62e40fff9b38f9a761884d9e0da9cf3ec7e80c8a9459d371a513f6f69da9b552003a5a342fc8a4824f474f61f2b6413dca2fcc7ee322bb1a37273c14677cfc1b73270c8eb747da0fba87d412392ef21d4a232274ef15a301572c6230cc7f6ca0c678f156f14092f83f7dffd62c991b7d68ce19ff4352d4782371230125086aba50f6e4725596dbb46557a7afb7dbfb95b4652b32457733f0972a705e70167088430fbedb578532e60cb68674ceb4aca24fef40874301f401f721ab6419f40cf46486babb628820f6420b600dd4cbb9f9a1449d310dc687178727374cf54c7b83c6d257524a19e2683b80cc950bd764cb1453f157a2051f30a08ec18bf2e6c1e4ef106a91d030ab81baa15961d8b599a0f0bf645fb8ecd541172a62be2e2babdf67c7547c36a37be3ce44e77df6711e9b341edb3573e077aa65a4413448c00e8417633f44279d065ee0b08fa25342dffed449d180a43ccaa170a66dda2e63d756f971016ed7f3dc9f301cd475df50c4cebdfe96b5dc994f5115130b89d4369d4bc5aa58ce4bf251e835d28bcb1c6c01281bc8d6aaf1f72c3db721b6910562ead49c33e355916ff87a514480430c36d95990264850eee5b49f86566c311ade7a0bb83de622b8b3fd2a7753bc6881a9ca60c71306769a5ae931f724d4f792b774e28e0a863831bf249e51503d5b0e3315d3ec61a59362edb5e597227cebc5849a9004379b3b4b1b7af51f0bba5a8e325edc4b025ad69da1be473725ccf9fc0cb144f3f6933b5dccf9f022bdc998dbd3728505722ed2e6a590042721ae272549de8d9283aa73fa26c74037e38122dde1f09b8d0245a87b12d2dbf5325cd8b69827ae13a383c2eb2731a29e7758beba371d3f4e71af3f1c40049bc720b084070869cb9b5cd5f9dbb10cef568a549685b2df490e203e5588449221e5bdbcb85bd4563c0b1fa1dccc9ebed81e70566def1d5f0fee93f6d1a553ab50272fb18aa93f7597ef7d24becc782e49c6112bd0a7a7640e573fa36fd6ab61e9672fa94f0bf3dd2be8aea682d2c2b7762368d2da94584261e4b760f87fc7056d52371bee901e3edbe804411649fe62631fad9d207bd31e48a7fb33c469487339325b64fc55f82e6e1e2383f30ecc8dfd4d20691d4720fc4492c30f5d6b67c348e393a89e3703c50c15af3a3df4f2cb69c2bf2e1fe062a12255485504d78740dd668570e4f86a428c58456d0d8c0368c5c0873e323dedce00ecd9b91fbe3dd2689724124567e54c9cedf235027500b7e6a2d93f47dcc9da08d460f815aff6b33f57210e60df611d61bc5adc14288baf6fafc3c73ae83fdf5e0ff2650a8542c4f7c248f2dab95fcbf4ccad852325eafe6a2cce3d549f30ce2218e2b6961a9e5606c3fd4bd50613867b3137e0a3d0e0818f26c55c9420d58cee9141d5ba47791a2b4727033714ce2b8f37a605b14fa50179ab2585a2ab667dbf3321668705f2e8c087270c182cdbc95a4d9a476229b6fc19219fcc9254dd91034a439f8bc30d1635572cdd9ff01926aca2232db4cf9e05e4bb4364e6c4bbd9586ccfe1df06944bb605ce920fdb64934010f7d60b133c408bcf5e07532d58b4233deeaf2f53189309018af068d3571b131946a817fad07128e32be8d50d55e39f230accffbe13b3196727a72fd58eee04f3997ba5951c94672c86cca065560e5810d0ba6f64bd397e8391ddee1c0ac2fc91e72f9f5aa06c52343fbf69d874a224cc91658fb084c966f3b74539f136812d04a2a791623c880a6a142dde42d4a515a021f5020d421766772c376f1be8ec36fd5a5b394b63d71cb1aabeb5050a6f19d5be9529f5afb687c552a657712437ee09424c2eab7485f10ffe2c21d79f0bfe05d0aceade13fb53714c98b1ef66bc6adcbfe47025781a6bc447e2302caf886cb3d5317f565c2eca25781f8a53002cbe6390e9e882f677316218db2bff7669f4b03805ffce9c400ea111e6795c24e52af3c0435ea7e53caa0e02f4f0bf75e40ef12c52f73c13a2a6d560c9eb116c7207123c8652ed4c388d3484a4156a1ca15914157d0ac9b500c6f13b9d92e15ac010519dc85ab5903f74d65bc286d0bbcb41df19ac2c686078c9d03f4aacb56e96186262c80509bd02257e16fcdd3666b60add7bf510f0b1f081c728f1f95417900a28fdc8f3db705173ed40218904c72c7038efb4bf69c812ba8054d397b1e5a6d2b5f39d94cafaa87e9492bbba6a8380e0da806337a47b5580572af0af717b774f4e9c891230d1472cb4907b1a227a68e2b778c7de8891a4dd672d15ba85db45c76042d1c0a94ebaaa51583a8f3e50286bffbec93c106ce6f2f0a9ed363d0fab89378ab01db8a755d926e9ccee6332bc9fafe3bbf057e0f20c272cdb06f5382056432ffa7ce56ee2567769311d154e0f9c89f486c1ecf280680677581467d1fb836bd80618afb6479f929eaded2f725351cc5b5f4cb07c9fbeb32f9f972d10fb3583ee5213bb1aa7ecba6942612b1a358e1748ffd3517f3f2b844be23d897932c06f1fd0fb7be405481a5dab03f327bae24dc65fa64ed8f270a1c5006639662cc1a76ce777cd8b0664b5e50a914e748c18918d77901012344666b43d98a58265019505516902268ffeef78c29e8aebc36e94aee37b4e2ade4ea2cbe8cc5331cabd3a6208ad717713d207764c7123357f4ab342cb32a7a6948930c68e190adddbde404f5917ba9a03fde32917fef4b94c6f42ea048a7aeba295a722b696b2454b6a1c7d1cf409b0c8cd6a6d61a02f4aa2eb78a1e05ac258a2a0272e2ee5337ec0952f23b2fecff8232027cc8e8bbd9917ebd8ebac0d116dcf0e27259bcd76e2320b85a909c80aa9a8f6c28359841427bba1a4b6027b07683222472616f97eeb3cc25c805896579cb0e334436ea5db4695689685c602599d8d5c6369da99e58b515673aa1f1e1088e1352947188829db9ae12511755bb15303ce3002874e983f13de17c6a657f41d7c311755fadaf38dfc169d137e0991a25f78772507f65355e8757f4f6585cf34496fd20ed846074a5715a948f78454c209e1e720f857c849b9cdea1969a2e6cca4e7accdef70602ba54830ee4c9269ac161c108a0a16f43031043c7a2ff11b218863a8bab16ec9f08e2fa07bfb58e3d6648f8728a026ed4020a95f7ab6c140c2606e0b986a1bbd4cf6d5e74d536644df07b55630176dae3ea1d0d1d0a3a602aecd9ecf7b200351037b9cf6a7f1eeb355b59b072394528ca265ce2fef93e9cca47932bf0b7a6dd0c3cbb4efbcaaab67ffd596f72be53d930cd76b32a73c92330c5c65340668852c71cdc820fecadf5c9f2650a108a19cb72d4fb526abb087c3ff48e9e7119270208f3416f168a5a76d004a4214bcd071c0dd47fd65b7343fde9c01010827dc18c7643485972319c912417a42872cef6808e25bd326bde26e357577e5b5d68dea58e418b6f89891e5e964393d1722a63477bb1868961ab892828ecc1d2b565fb25bf9dd72beac3d57b052f4e157293071c7a779b601454f23b84b1ae310bd84f1e255b313c41a2710267d5f6db727d3900a329213072a8bd9f876ff8753a75b69ed52fa2a3d015238251a90fae248c31a275af7f797848ea0acad363c934d2331ed68e9d0348f42a0d0278656f72342be8aa9449b5a0228bd6f828b9c0e1b41a571e60c7f7b3018daeccd1be5d721d72ba05334aeccec07a3acd2c7e292dd9010cbe3bdcf46da2e855929da4ab697e572f896c0a9df8fe4974d50d3eac70ebeba510d815c5ccb5ea8dcb2470557261ec4f578b0e1efed3438b55f3c94d465cfd5cd00c04e2369bfbf234bd96cc72b41dd2f5f2ec9e1dcf27b0316ba4a9b37da401b98b6f513b25fd4a70e239a872954f6df4a961977c241d6975cc53304267a5313e4675389b188611a5e37465674870dc8155dd8ded8cc8fab839d4d3b1418e466abe687d79b975d44a2956c77235b941e496f51a08007dac1b20cad1d69417baa0bbc0f4235daccc236354372ff5e0b03c9b4193d0db65d598c71edef301ee62dc20c97b2c0d45b3442f7f85720b1859765661c9387fa9c154462869d468567e852ff65ea97bddfa47830cf072206fe7a0f34bcc3353fd5111ae8faa77575efdef24fbbed3f3ced51583e4ef729c1acdfbef358f2614a7db5e09e8c8c3cbbbd0301e7b9fc5b8784f34bee503723f4a0cd821b153ec507c56f497770669188327f339adcf3029426e0fcddf44723048d6bede0645eef317d3cbf2cc19412426c51918738bb6971745906b7b8d49abfe3ff80f948ea553ca06d5874faa66113ac7503cd4404664b447bdc2635d72eadac05527dde565d90ee503b169b24e6dfb51bb6d2cfd1efe2f7e8315f4cb3066b21449727c06dd0be13f35906cd06f36162cbd33290ae32752bf80cabb451322d1b257f5dafd94ed611ff01391db29b8572561e81360dd6b1d76c7b02c80721d038f096f2a6c97c63e454d7f8bdb02303be9b66d96ca253b6df806a6b35d728c002a30c7c489b66eb4e0b0281ce592ce173897728152a9d5a235af91872172b146a3ffcf07b01f26a724ac18372cadc7ea3531a17d0aa783c226a51d2a9972065959e90b34f51c921fd38d558f7ceeadcd9b130896aa5eb90d20b21d7b4e720d72e1d76cbd66e2fd3686806c0280b0bb7b90a2752cda9a20d8b5301606623ec447b2fe0faefd043a307f86435c09d61aa6cf177c7d5b729019ae0be416ef6554388537add9cd3111b5c7edea2e4baf08699e61b80979d9e1606f628e2c9a5995ebde777d6e0fc132e8e62829ab6db96be09ea8c8a341ec26d42626c65cf77213ee1b6b05e68ae426d59700a1a4d9242e32daf7302ab3e2ee80773c58d390721f5558e39c8a6ad648009e3194c7be13e40ca9d2d04597be7392de2e201bb672c58ee535e3e10121c9f34b1b1688837f3f613099e88b6359e1e05b508fd5c0557cfcf0ed27cfceba1b1e358ea85cc618bb897ed566e9410d98f2c67e1a5fa272e34e24a3829d5d5cd5f71e6163a1f8035db43fd43e1a15c5eda4cdbdda1efc6f9cf1878b9f981f78ae97020829f737338e432d699b4e2fd4685285ce6897ed727a3d161bf96f94be8c4057cebfe1384c8a952e62feba180d6c29bd8377863454e4d10f03af3e02d5638b8ea9e0365d66907c7e6bfbf672639f8e55b4f329463065062132118b6e10dd8df47c79d48e751dc461f64c5036d593dcd21677c70b72e81140fa74fdd6ef64df14e827baceeabe59745539bfcfa2170b7d0f4e2a79729deb9972a8837269197891926afb96e0035923a4201f1920381d0050381cd272e772bd354b7091f016d2e6a66b3cfb2be81a0582852a0efc0e127374dc6ba4727104209d55402f74afffbb06c71526cc5dd79e3d609056f7c6a6a85e24b5ce72d2a1d6603d59d90887ce2ce801d798a68b73396f94d6e60813841b28e5ed347251c15cff4e1bef88f73af32ebbee43c41c02f1a7d1c9159c0551300118bd632020b12dd22ca91b5b1c82f083692af558dce9a4b7bb3b4c46487ac536f7b9ec498110e8dcde3219ad470b8bfb61d4d94acfe8902e18565e325beb501494b83c724dfc4cb3b4af95629ebff1a937f2dcd18eb76bc65873f7bddabc6be91af973427ae50ed2b9a257768bd700695db0b681dfbeb7aa8322297698206b9f602a5b690ab3115569542b82f2527e20b4ac3ff571718a2b7dd3755bdc052444c142eb7293853eece83048ebcae28609998241d72aade1a5550a4ff8a6ccf56643ef40726382daed649f51c358be588b70662b962577a144b512b3646bb5aacd64fcf072c50f17dc8b462abc3d5108f6cee6a4db05d4b4dc4b3a5379ca93da57d021eb7294f1a8c15fceb96f14363ecd269348f5b4ba3094b1eceb6552479f9bdbaedb1c20685f76fddab6c27010524cace7afa08a385c1ed3acd7d0f81d32a1051d017231ead4a24707bc0b134c52d98f036405e0554c3af478deaff532c049b8f9415c920f3c41660a8630b372adc8ccc76c9e48b3f2a6ea462fd18d5eccd6647c58345dff44679b87dd4feb18cbd1540f96cbc653fb98eeeffeace6cefc3453821102a60ef562d0cff3c258827e5fe9bb0a96fac14b8b12258932c64342fde5d14c720c4b2568baa53556ee1d5bffbce2f51cf43064129158ae75949fe786f1bbf372e153a461506d08a4060932589e3aabf99930d83b63ff10275c857a6b2cc813720269cb580201014b9938566987e745bcd4341915939db7e1d51640adea8faa46674d7412699a229d45d508c2615156d9c4fd7702e479884dddd401a1a3bf0c7243bf4b916ff4cbf531f6397ad58b7bb4613944a21bee3aeab0d2f12d46f8427263073bfe15cee6618defb2f7606c265c4aad425f3382be7268ca0bff38b8b17262c4874c6b6c323fbf742d2ddb62072be30af6659962eda7bbddeeff7ce2e5674bc9859df2dc5b6439c70c24312714aca076cd68525ec8e5ceeb6c4c2ad4ef57305c3c372e70032a89ed99cf95a3bf0c3137a40fb6119f4b5e41024b3c1cc6721a9fb8dd7c3e2daee95a21e1a5ce6d95bcd8b2270e35a259dcd11aada626545e20e1ae4203a5c725810dcec7f25e248e36231a255004c7e4599932028d4cd2529ce596e9068060626c18984f1efb4969d672ce14df5534e8b655c05f3b3a8472fdc5477e604cfa22d7e0c956525647a5a582da7c7f4fe7b8d299c1b9e0ee877275e1f0a1bada7ff6478b100a3eed451ce7d23dd01c50db6d879269e6a1dce4726558d307d176bbd7db3573dfc3971c31eb61b979e049501be438d47bd59bfc53af1e1a3cfa09cbf3d6f7c91fa95fec68522caec2c79df89133077b1a1575597264af0d23774d0ac874bcc9d0597c2e8f8158f4c20082caaf94f8f4cb2751dd723345f264970e0666682172fbdcfd90a16f98694d2b80b80515ab213e7e47227240015bab6829dbe6bbcbe5e286fc5d4ac8654b4f985f916347098d71277c226a3e5262987196382219e8ecbb22e6df2a68f03b70c69a6737ad5d985910032f72793a80f5fb4a2da789bfbb0b78ae875675f7fdb887d36517ce31ddedbd69482616719f7f66fb6214e9fb2f7db12adf49afba5fd51274daa3775c35411c01547297f2644606e7b278dee4863016b2cd86abfaf7adf96c17b0e63e58e85596c7720f4d22d43e21058abe02f741ceb8ff62a5e8dd6fdb4632bda260e1a8f76251385222b4850c034af245889f53fcd096c3361940903c57565cc588774582920208fd18e7a809774da45486995efa7c42dd7ca5e668f34a169b03aa7115bcd80862c822f900a3a81edc64fbb7a745e1d039325deb0ba066c2d7db5aa76608f41272e5b38d2c24e469d97f5fe4f45dfa7228f711290dd11c7f7360c26bf69a3d260cb1600a3d46e22359456cf222a22bc88a8b02bfde88b4b12dd9f6fcfdb4278472aad79e34159536f26912a7addaf610cd25e698843cc4030e104c4605da21a67261faaaaac43edcd7884dbcd5e10f8f0646541c316f8eebff652614ba2ddc1e727c352bb98e90b5945baccbddecd9eeab393e8ae477eb5b21d212b3d9c4c73872a6a5763909fda8bab58d578beb0705efb70d46596290696c7f6dd3e59f1bcb72823b9865064f03e87a7e3b408ea1d553183b9171768655009b8a5fac6c666e72ec4094cf590dcf0b48b6f3c710c74bb8c3679ef24704af858ae7e561dd567e72a909ab21fb970fd03c68a96492092de8e8bb8844cfc083746267c2cb71290b2fdf14d03ff6d484274df297a47d9d532a3fa2bcddf19f8eda26cd4a00df111a6d58d97d23d9ff60b405551f5d2f1cf428fb60b559bdec5b6b26f0e8778d3fad7211e762cc8f0b0bb9f8e9b33714b141784adc772514539a362b3e087d955abe72d30370f312f195afbdc91f1f6ea60d9231c61a9d82d0b8f35910a6dea0edd6723357f5b1f15ef2f9acc88fd8984dc81441027b6bfd1199eb6c18cec401c62428ff90ee33a17c10b9c41a6a248019d2ce50f161d0739bfe4d4a88b7c527d3eb5751e385417fea3f6e4985ece6d9152aa8d3680c7bdecc04e7aa0c83d63d8d9672e5d20023caac14975e193695b8955ad8c72144c23faec814981bb3ef66ec9d349b1766f7c60ac05fd1ba32cbe0b301f9f4fff26e94a7d753cd5def61bb718b723e9e4e2dea96c766c91a119a47e418a2fdeb97324fc5ffa119a15c5515826b72bc008dafd81769b79a5bc5995ffcd0f9467d19cacc26951ddf7571b4e766db729a9c3a44f8b5e45fa1c2af4ed7e11398c2ae54ffd8292f21e0ab5ea6da18491dcb999b232bfe110adb7e0151c30d3b59e4570dd4d299d286735063c1ce7e7e53ab2f2555daca97facaf13ccbc33a0abca038a4ede14b6d9699e234671a3ac572931479c0d38cebdd5caa982f7808ec19843e7db9db242bb7fa961694cea5c64ccba95cef674e8bc1b5dc99714e54b66ad7666ab49ac24a5b1e761d9dc2266e72c6de8c40ba04362d3c2ed077a9f13295825d21bbe9da6a8c2857942fb3fa6a67f652f5023da4746d8f397ba673b9eddcf2c12ea2ba1d6d0a8d0998a4c4cc440e32cf40a9241f2e91abdecd5d32d928a32cc4a7974f0f92360210c7ffc7452d72250d4f5bbfd55e66a478dac4286043aae01d230cd4baa093d827772956fa52138553520102ef312095baae42efa2937ec7483a0904259849a5f4007e5133c572d0c645170b98d3f7c03f8eedc5ca649d525a820192d887af26dc2a2bf7c24d24e4b9c1c9a58cd8ad2393b692d3de39eae53ab2abc40276f8e3c42bdca2d7d8609498914c892a1b19d0e34518859718fe997ee82151863bcd4259d2cb8a074f0c67252765545463697a31d70aec2d2aa1733338114f5faa732b0611b223ffce3c8cd7c81c46ca50a4a6d0386ccd70b53473acca7bd2856d0d13921c1d78f99b4b1096dc7b7b1d63cce2487e862b97e87b186ff8b212f245dbea71bd70cd5f2f3ed47209d06bffc7f35bd6971cdaba0c6e70a2343e1c045336df240839ff638022788365feb8318cb818fd13f2d159908898f0670184ba8b4190128bde04370f72a418cb4cac313661ffa486efd18781433c14b00fafe68e47671182fe79cbc772fde3228071b20fc705d2f265da670995b3e26b05df6848506d3ac84fba4273725a7449fa082704e99772543bc113b3a0ba0c8d01f3bfc4e68154b4fd592f507216a3c23b20bd4d0a69fb2aa0350f188da6e226368812dffd2cdcb459ce44e941b71d74855259275745156dda3c20531064823774f7048f5ae158142ae0eaa83d5c6435b57b440001adb9ee48f6964be14cffef3316ce5e3706ac63d883a47872aa716c7d3fde04b753386344bb9dc3dc0c0ab0e88974135abbd1e21f6afa1672cf038a160fc0a7598125ea6dcea8f42b0dccc04470ea869442927f932219877247bf9d39c50a89ad1df8ccd0b72e0bd305be15265846fa76b93fc47671a3d872eb5fba0f44f9f5fef34cdb06cc67555b4960c663413b25c85ba1a7a14d74743cd2546f5b62ccfbfedc78485afac0cfc7fbbccc146fb9c7780a2dd231d575a55a770253e808852b8897ee7b01fff1b1f28338cf064a77f90101607b35a82fe572a4066a35af0adcd9a86bfaf1d7266da7fb8547d936a012a9fd9b9010e235a4721c2dc4d7b72405e1f0b157889ba07f876710d1708e4b2f31e30677e6c7363e729a449868a2372ac06a1cd77e60f7af2a5e4fa2573c76b5f662ef2ca068cf8672f147d9ec5b7c75b901e267d72c4fecc8ed3de9339da51a5d19cb8bab508ca172a083e3e448cd8ae465b8f583dbad3dbb1914490945c54c89caae8ad8384cf33945df573e168d4b30b163bc1359c389421e8b81b865b00299d3ffd40db76836722c1e7fd21b1e550f8dd880f80cef3c7196a57c3929f29971c3da0458e20cef7267b10eef368a3343f9ec3f92772eb6dea575735e7a79df9ed5a666158efb9b6269f6e75c7b33ab36d9f80588f7390793fe5387c63c9b42644e2960f1648cc372519f76059f43d9289b35cecf15f5c5f78d28837399fba6c4c113bbba38971b19e0311690a17077b669e174a20661db3cd3757a07f047d670930c80919556b61a14fe349090f22d5bf422b41110356798c6b655b8fd299aa35930e643761f1d57f490cc21a5b35a3a1b0ec7f16e22d1ccb7fcf6477a79fba2d86ea903e33fd3726d9d0e35e4913ba87649e4d94eccc7e0a461ceca860258c5171b996df2a1f17200c10d3d160d78ebe8ded3cbc825bda39a0f71f999a63cb926c112c7f9135572d2d72817740b52391549614ea8f7d1bb57ad57ce6259a57a49f4cb1fe853d84bdb8901cac694f9279030baa921241bf516422c9f8a1e49b75a41aacbeb591d197e1fe5aecc4a3b5e33ec613cd6d3a6fced042cd67fa386091df81ba53bbe85722ce8f8118e9da1c481a5465e817dcc8d4b3c241ad616d54f74877cd8253840222582657b6cfc1216f6217a3bb593e21d10964411b00a13a36789f6c9191dc42adbcf0fa61cb67e0ddab056c14c060814ca71c81a3c375d18bdb6fca1380dd972f9f6b81c1e2420967330ec79bbf418b95d568a9ce65280c913c1975fcc15be720eb78b1232b5a58a68c362e159b8889decf31d22b245c94c24a93383a8c3c272860f7f18b93c219af442766f44bcd3799ec1a19da136d1f604e7a140cb8f2a724a33409689c3ea4293cecf440f660c0b4481a7b56ebfab70c8bf890523159472342ad7ae0bf06756b0e80947dc7aa79cf4b393149bcd44ae305cec9490a21d1c5ed87a0212f6e673a5801e2daf6fe310e708becf03ef4c4589f38d804d99a137e2b2355963ad9569c4e750d162b8ec368d24d82c121d6bf2f02504ac7db9e251a1419b3e0c0adfe2b4482340b68e84de1165703690376a69eb04e2499d42167291b317b280fba8455c355129bb7507537f116be6b7f86c9e68897e3f89a5ea72a424dfe50c055ee1fd7059cbc61f00bfa1c84fd85a7472ec89bbb40e60100272d40174fc9791c1653764083299f434632f083c5d19f50ecf6cfb86f48eeebd28b748e249e7fc8bec1b90d829b0e6725a77c3bea2e35a9ed3f8c2e02979a8de72505cdb0abdbc8ed0de6e0f8b946c23bba279d81986516f792a7c7eaef5db4d0cc041bf34afc8717e509a44c4aac38beca0aa102a845d1794f86fc0ead762ff5a1db5ce5b272c83f87b5c3708708884d78202a71b848c1941de0985ba6d9cb172107eb40b22c12ca465391b132fe5e00376b38cb338badcd10f0cc80adee91e285dc719b6f992f5c11f62003f6b9c845268afa0339342b53195a139d4e9064d72722998a402ec314dd4b212a9aaddf2d2b45bb3db35e5bf57449de27249f58f5b94c821144723aa0403918b401fc39d6d510ea263080d25b7d0ea86fa73e5837260872fa800a8f4417c5c2a233a900e1d7f6fc99cbb44328ba123a972abe02172b1c4195a95f272b245452f23fb2bef8b3bd18059d75a45fedb8549208e0bc272a41799adf6846af1c7734c7815e84fcd9f8ca3f9ca17dfe2545695d2918694408cd3d79ff754b1d60722878b5d27c47f0167f0c2f96c7bfa4b9e1bb23102345b76ead070bd4f6430141ebe7e2e10e3cbfe3f86482bee77daf5add5d43cc0405ee651cffef66e56b35c57b803b209c7c0ffff7a319229025ae2cc7ab32b055972faec77b74129a982f17d68ba473ddb07d024c4cbd9ef095185592d1a1d080572203114217133a176bd5179faface938db8b4be2d14696a0fb1a3156199ab2315f6d0e2a0db811afad701ef952e89162f22998c3b714c548de2767ee2ef0879409de55302dc8ec4cfd696ed07000b0198994b0aa6cce3f892f52e2013b69af0615fa531e88476f6d501154f938a4172b2738a825fa776c949cc967bf7bc41842596c1ef33ad000fb5a59de9b1ff2a9d1011651657c4be2d9a5165e18594df21727c48149b0c0a155c5d22309154a354934a7b709243c731978b9d99e8d2e5df727d4b95814666321f9ce0966721686b228e447f0c035134756e5979365a978b5e61f42a6bfc17e6e0392e4b547b647383ac5ad7ae4a27097b9d33eb1674179a72dfc4e34fc75a7262c24ee21402111581e3ef1049673ee2d2233f17b57fd229728d324d27782e2eaffd189512fa8c77f6e5c4a6bffda0a4c316c0f9612405cd725caa8dac44dd1c2cf0414569d464fbe76d21a89136b43f2e4955495b83f0b5724c6cc80042018f1304e359c9809f8f28a0415bbbcae3f5fe0fdfc75754f0ee0cb35f23b87c99033f826fc25c3fc439d4de21cf632e4cedd9661b5b99da6ab31c2281fa2b07072b92447460463eb1e28602bf6995a11263293cdaa67e8931ae5ced72c34f81e1fc3d0b123251942b53e5124bf290c9eab7afbff00197f401c10aeaf0eecded17bbddbdc700c358e3df8eaf65cfa807c3efed846d555536057e727646c3589ffbde1f788fde8ebfbf009233a712e15d5e47fba27016cdee88c57281487b1f1a620d3c81e6dcbcea67482010d524c6f7ca27731d8a892310a40472d0867a01773dac8db6cd0da361fb98fbc4a8b4b90a0fb44a57773af819ca5b72d955a24f7aec5287cff3802788ec9d583103384e61cd8e20a049a9c5cb767f3b2ed946854cba5a9bd63352cd552b9b09377f0ac93921f0627833364c3567117263d95be4e650d6cef2d7be1d3f9984491a782d8f4a9e62a86a0747bcb10b3e72f33f1682a4c89b0d9d43d637eb97fc24f16a74067d090fa0ea130fbd6adfca72a0f7f51a479e43bf181f499955398d812b97c77d9b6df1db4a525043583a4827f1056b8f3da74e07f088164c12e0d7761e3418e2491a4e099a8216d52efcfd72cd8f869599fe506399dfa0c8b43299fb7d833ac12c246b8475009d5f25e14472ff0dc4fbcd59764e51574452d544a6272b331a4eb47093f7cf6fb0636eb03e5ed57ad2341e3cc9724b176c54b68f86b1461a10f04336e43f320f8d011fc62772a5c1518ae75250156b13618c4c96ecacf8ebcfaf682001debdebee602da02d722ca6233d7f35b9c0174e52279f32079032755c3e37fc939a6146e523e383d859ab2c418015242bde4bfaaa2c2ee087399cd503b5422073d08bae47ddc19b53727589d2a09c384ee7f07a065b3dd2ffd8878dad5ad0a5274ba4c7f8e4e71b8572fa9f6fe2291f0fbd884994f13fffd3db61c6a47e4514c2d9e90682761cdb7572c9dc434d39baf9dd6b3e8e96b4b6ae0d224f68d42be202d15b98aa70d826b3721cad9d6b5047e40fe03be0dac9dfbf1773715673a83809698b537d655c49123d58318c2457b20c9915e89f20ac75a700be4f62fd67557ee64b84695a62e1570d3f32180ceca06bc3129104e3382f571db9a2a4d769e86077f4755134ed414572b2b4c8ef73bec65ce4e4ebb4ddc604672627b439ac0c69c3a2403b3d16815a4ab79ddb3462992d22c7513b11656658cf26fbc4afe4b3beef9e0b4919535bc972b39051b1f7602bab464ec2e7cc437a5884d9e7fa185ba2c5413557639ebf1e72720386094ea05e015bd4f190c4644ed086b2b23574769b965a549cb82c929233b3eafdf6f4323625a5ec2d455f7a7a7fb66d1d11796aac8d6dd2cc9ed0786972b13862e8e5b4812868a408ed7f8e2a0b5cae17a6951409c94b78975015741372040b7f8490a2c7820a6422af3ea80a752de35a5e57c71cefdc8a50cb17eef8615235c419461e22f86bee61e1b02a89feb73224b1dd12e076200509836975c5724ab05a847998c060214d3d2cedfdb98b6f1a212379fc498d4c14006eb0249d3c4051f6fcf1e22d91ef4d7681b52492fa45ca50a4beca6bec9212de05bc54587253b4ebd302e3a747d8a4d13a153ff304d84361799c68cabc6fc776d8a043cc72dcf4d74e1df53526f6b41f46d48e60f1b91c00008f4f0d59acbfdae92032994463ce2027401a5f2b3a8c14015343025bb129f4645e5ac9dbf422fdd8fc8e7111273bc397875a3843bc56ecde5a5eea8c9e622fd888b615d22c7d7a853d3fc753b74f03d5023e0597aca3033964770a80a1d668ee9eeecaf36767eeabbf515a723efe746b3616da02bf99eb92636cd586ab16998fd2a1a9a093a84f1131218972a79a37442eabfedd76ea748ce23ae7a62da97d09b5076f4caf120c2268c8a15258020d9f6d658f89a36afcd9e403bf82186e54037a99a39fd346433923626f7282a05fa0d3c0da2fab97e3ff6f5ae59900b502da1bc503a763a3df0488f31972c772d75b62a3b9060616d2117482cc1006e1eb2e937583e10dcdbb3402991e5fbd11b481980e23a85f8c027dd78d94b07317338307e8a5d8bc0a2a90c030d0721ff589bd4fd1f0cfe365d8b5db08c74992aeba59b373ec126eaf875b867d2f7220e6bde6e24d09d209ea230c92b23624ac0ed2483aa3e293beac3ab130ba3c05a99288383664481b3497808d9b394a2e48e14874b7629e776b1175eb3369092d35034a7d65bd2f5835757cb9009a8ea9a022ee564e47a7f151e3578264fc360168cc6370b29d6d9d88e1bd03f9f5e9bf60d4fef2a589b0ffc5731bef2900bb580fcd0912a1712f80680a83459e917ea4e663328e720ab19ff6bc2f589b28e836a5b8af100793b16970b79f7df2647ff68b93e3d54520597165b3ffe51dd290725f79e4028042ae017d93d9c798891484fcc8e6834364bd6a8e5d52f69d79d332f1b34d1d52137943a662f41c6f8ec00af964a4187722be878569eaab569b66726f54bfbd8d4214533e0c2a03d6257d2cc7859401b460e102bd93d6bc7ce50c0beccb6acd9044658b04441178d778b415944c21ddf99ea82095306250986cf172a7bb329274d33352006ac045bbb9ee2cb7c37be000d59bc3f9881a848b3ea26c3b24b390d9acb9c00090a164e3273bcdacf5ada54a92ade7d924c1d5cba6b57205a22c061ebfa4b1b020157f40801a21e088ab7ee5f4419d30cceb15e00ac072a252ddd0e5a5e0ee8bd9120e240c8ad46d556c632f713a5fc08e1221655d6636483d9b87a4a9c31054d4a10203f9bafc4eb06fe34b1dc1ecf55793c48ecc3f6098da101ad3f06177d7a01de6e9a7e9b00c60e96b740885b1911d994d18ab7372f61e8a9d5b9e7e0d82e15646b3cd69970551851a55e09195aa5de31fd08ca8723a97d03d90da47077912eba9c36a58cd70d316521254e64e625ff7e8ccedec3a45ed9622a4c701df3bf3e1e5333fba11e6c845c12dabdb7a5d07266293beb07280b5f28e4fc2232c7c58c6026239e093ec7d5d9b2c616b20233937e07cfe4d122ebbb57776916c2b8ca30695a61d31947b021759501d825dd14d67a11d541d723a3f2ed2c08fb3f17d8b74b7d1bce4dd6b705c84bb53fbfdae7a4b4f1dd89e728040df99cbadb128c929bfdc96d6d414bcbb4ea0a714fcbc388fefaca15d49724e80e4ff7d238f5d0d78b6ab5dabc7cb488a5b1acb32cf00bf80ff6e44ba26727fe5d0b6831690cc2e8611a564a36e060785e572696913db019c823055e8425dad23119b18296dd54664211b25ce54b939ab77dfbb6599fc5627b3c1ca2cc372be6540f9603bb7efbf77604351ff6a46c231d7b5304c299651d5934a9c87ff72b9db6f5b8a0f3952c85638493d0fe5d6e05bc1f5a03c447aa59e560bac5c5e4e4857161ae7186fceaba7efb318c6d9ac649c40ef0c22fdf0ceaa0f297a19b972b9ad32b09f725c6464ad66e3baeb24b060336641b7f10e1d02ced61eae799a72bf47e8e0b015165c450f8b5bccfd2146c6430aeccfc17f966279339df9123c723ad598949b10400cc062a84c9b85bb29044c6c16131e56fba93c19ce9b50a959c8491055a0c3507ad78cbaf650eadc89b973992ec46b3cc12969149f4e660930c23242cb165c928adcee7058522119a45fd3849b5dc4e66a7f8f8d81eb6b117260f2ec02e200a4ef0d40896b6b8de113ea6aeadd5e197a4979e24ab5ec2eaf41fb6cc33b53cf96243bbc3ffbd35a0febeadcbb42fb4ad2cbb222bd0ba95dcc726237d6b652baad0d83fa3fa5208ead83302a180ee842ed9187997fb5b00c2a357fe018de8e0a3510e60a1f7969608be82fea9520a0b0c75797b0c6d80a281422c766cbcdcd5c2a163420d823f912c95f4051f5c2857a205fda98aa487fdf5d72991c30458ccb626592c04d761fda51ec37ca456577f61b549fddb552fbe41e7223628b6b018b845ed2c8b100d4b1de63274b657d724df57df63e2791942fa872365db19da4be0adaf3388e6d55bdc17222aa026331bb7267d65eb62a86faf3437af4c7f3c33af402f8b663def697250237d403bcda62ab1998af8f5d7b6eee28c12678dbdcc30e543ca10b6b9045ceae8963eea0006b1ca6684265cfa9b6e9060f68c87d5e64bd36ca98c388bb1f0044f27f91cb54b23db7a7a1bc9bc88beb721765cba486abf07c7a85e8ace61e5d028a7d36e4e24ef7d6908a97ca5696641592447102a15b1e6033cbfdaf63757250fcf64d61d89c4e7210f91170704c1636e67feae4f95ea7987cb85f2d6e30965d1c86856e1568540006b6224acb035c7245c6cf49906d8f83217351677eb7af0dc8bb3058ebe1c4a7c01eb9ecc46a680e93927b11e03f22674d82fa57ae588825aa3289efc6f3b2db5b59449fc6e5481be074d1eb57f1fb4e99015d0cc15d141da7d1705a4432c9d04bacb56ba52cc2150087441932008b64c762c5b2c763151dc109032ae6024229497ff8c248a84172423406b8304edb07bdf9a34341fd8a5c99bf8b80bd8fe2bfac1d5050e1ee970963dcb053d62502f84f30738239ecea7568ec7d31a72599239c063bce59519c0657a7ad2ea5b39a6b3fc17c37bd676a9d6b3eebea234971fcb3e622dfe23f1172409882e6a72cb9ad80a3f7a371bb9e5f605d974fbb0c60f40b88576bc22edd7227debdb1caf93b77c727c4101e30d294b74e8efdb0627955cdad47034b15a472c51640ca0b9362885927728f7ee98f2d13ca5491f7fa1081cd88f911024bc172c6e3acd2d3c13e5b87df1cea230358173cb355312b118f4ab4ae90b113946172927175febb91b80ef3c6c9b84983c3823e16fa55b857af41a03307737bbd4c726181050d2453684a31f38b8eca1a376c8b90853687813b9dcaf1ff5e64d9a306aea28e9dd39d7964ec37625b7cddc75d68aee163790a7b813d37410f8f8d207297b60bc49544d932cd35a3e783fe1f8d77ba473d0daec2d5678e0ceed1b0ec726a6418441c19684abf5f7275b1aff30451d86827c7dc1d1b7693c6f4eaa90772e698bb5f3c2c126f4ed81f28c3f2da9bbffe54dee9629191440ac3601d6b0f72028ffe4641cddc27d38c1ccc30035f25f99c74f6b97ef6d60d22bb2c16e45472bf31f9f657eea6cf737cf82f3480cd898f70e65e8eccf4e13ab408009dc34272d1764d620bad00c8a47c4eb89b9a7ba889e5d6250c20d83aa691b794730c16724b38853006c1d9e7af3b2a3912d326c95c6df92004943f9749b2dc6c64984d723b3f3877317e1d025e6d86bad2ab466c097ef9a402c0f5e90aa71028972c8f5eb5dc6b82f3cabbc0dde1547a835eebb99202e5d0998a2ab2a0a9024ee2c9e8185ff39016bb210a6afa15b07a3d963a77edf5298d1713d7622938cf6fecb126722ac7437269bb974e19d170e95f15d95db23cb22ea0330800f9dcd96506046e491101d64a92da6e9829bcc85e5b73d255fbd5ac42f159872c57bd2912cf5ce6728284e9917aa9e54377bdcac1d8f09f1174286f3c20b2d6fea80ebdb6cffa2d2f9f9bc1536f0ae9857ffdb3402100686bc0fadae420cd0d23f620c2ad5c1730725e21f42c96e852963bd0a7d387a81af0d4ca86b5515fbe12fc7c569f2f509072745ebd0e329e3545e767e9c3b5f8dc234f1afec98b159b61870bf14f625a8247c1ad737708bb9eef051d91d84b53eb97ebbbad91e2351c8420f8723bebebda72767d96d3bbfb6db17c864024757a60c5a424940210cdcad96e7c408c017ad84f356da2efb6f41a6aca1fe073ae63897d52b89403d62f56f06d495acccf89774cab0eaeb72303d9ae4f60fe2477d414017b2cdc609e8a68dbc71ef472c296de7263eddc84067681c83a78ff740417deea387052f5fa690ae215f0c0d65080b3431eb400c7d526525bc43d90459b1286096764ee22cd22df9a4b6cccff1bfc40729e1926d34afc5c7fcb9363bf0dcf0318132275728a59794a819cf670f34d542bd495a7fcd68b57502b4f15633a28021bfdb74d57027a5a79f13bbd453bfce97233ed73d4d4f53aa477c21aa5b734614597569e8c0cb3fd4e3d2079fb5b3da9725ab4d31fb98d5454ab597a029fbecdf5c13c46b4437ecd95b8d6cd90c108dc72d4a89faa5fde843197998078c7d3ec502bb13dd1b39da092d767d7eec1a64243dc2e46ea504cead3d8138f16026140c83b1f1d6e366d32a03e605caa9183aa72930735a8fcf4d90020e26a7044daff9638eeaa8fb8b31417cb3e0e01fc5af372bc2dee7ba5886de7044dd36bfa9e5d9533f1cf2c5a936d257bc2a11a6b8c6a72ee0014cd5d121a986fbe267b58af31499b7307335dad0dcb2f357f85c48f2472c64eda53998ac633c0450221b6529d316d9553beb121e181990f94516210e30d2c50e20d8798c1f74240082b7c33777881918a37950b87f219f37cc7018d4308b35d33093c3c7508983bd1b357ef38671adbbca481ec4b65c4488c00c6079a72c0f637e90bdec05250dda624efb4aa4c961554877fb242c49cf921b9d112c45ada3dc2b8be9f9ea9d2fd8a8e56a25d34573ac70b5643560834238a6649d88f72482665b307c31ee7a707cde298e407ce5abb4770079cadb884d3a9d08da1dc725c98800f8431891720594caeb8205715f084e263199a3943b296f7578c13cc725b285afc864a3fd43edb59e8ff3cec9536e4f73d5921d5dfa859bbe3cc3f926fef8f6b5ce05fe992d5ce2575e33da2b6ef174d9486272875bf88916225a7a772096741e7981fd5a5cdad9053cb2e4f263917fc4b4fec0aa0af5629d1bff0d11a55562f9592de4922f9434d3fc55fa89c54553f6f9c1389f2e3b33f2cc6f20872019d0713b56cd794c9acdb013c0f2b8e2af5c266f2c7d5de3f15d3e9115e2a72b0a6afd23cb826a2041b68beee2346b1c92e13fbd3287575454199a0a1462372ba13283783271d1dd1c93de9efdf98e166c1c6a3953a96b66a278d0a942c2d25782f781b130df7846175eeb1934adb103f6dbe66ed934331cfa555151de32a4d5eb6373f14943beb97a68fb37c02e7ab26642d4a63aed44751fdbda2737c5f72f4bdd4246de150567e80ba79c12f5ef1a087d06b18984a36bf2f175f648f3a722ea609f04a8d56a2eeb9fcb18736a0e2c5026250771b7e1ba728ab0ed0e77372b4b121c56f807d3c3b7ce40f9547c5e1e5c442dbe817311c3bfee2eb7227f372a845d56c645d9c06094b6746c362f62e5654c305dcd4f853ebfe387be694926701acfd3d069b9cff8a04d72ee62cc6ff497edde769242d32b8f8d314ebd7f23504a1335fd4a9e8f1d5511cc27f609cea672782c51ad0e5522a0bdfa28540e672ebec7527fd6b9338a1b1cee38a8ef3470664ede53d1ff409bbc7421b7527fe72363588243da8b39e03e3f9824bce96c617cdc0bec458279966872c2b7fb2a97297b7fc9006980af93bdf7ad4273fbbad839e66fb23f659a0d21aae50a6baf3721574788a3b5e8272050f17f38c2dd9f6b6f86ad374215360b1faeec26010e972d7e3874cc509a2aef360c71b45abf104b5b4e184de77f6577d774cf995b781720eae7f9f06ec78791a840bd2be25f30a8ea3115d4b22e83ad7245bbca467ae72108decfa40b71782d8c2d52700403f207ee71c318b08f1817fac6abd89075e72278108771a98378083e2b49dadfe8ddf4cdc62178ec5623c6b378ed1a6088b09ca0d8576caa8694d1b4c53806536b19a11b3c811b4f0b20ddc392708a12b0c72b021a660defdf099f807a78622a93bd7e7ac204b500f09c4e2e070e1a6f2e972116ac996bf3d93cdf9820eb8940bc60760339a076d7d889b91b54e82022b36728ba3a445949e3909187dfcb6703b4cf9bf5508f7744c5602b36f358aac74e92d73e2e89c69d77b1286a56dabbcfc6e6c58d1a05c1b37e5804fd88e1193be2766c11d3f0dd06f1574777039f5531d2ab7a33448fcae8facfd27f91192ed4740725c5e2ec8a7e2a317c1b62ac17437af7e402d5beda10343caf8fd96272e8c124e49fde3ade7b662d9fcef989db1201fa6487a74764b8e23c28b90b466a367a67246bb143492b2c506e1c8f3310193eb411ac7cbca74594577fe34325cb09e6f72e2a93f7853ce55e461f1cce154bbc82e50f2d940f19c25d48cdefa44bd009372bb0f0f1d57bce2209a153c05510b09da503bb8bef9496156f5f003210b378129405994423de4794e4c0129ce90ae95868e346d350a733f487881c12eecf81472506488a53bbbb8b8c302cf0fff13e552ea56a3ce4ea265c08e8e27c2b8fef47261a1211ac9b3bfd1141721060d4c1d048ea8a86e44bf02b8f55326ad8c4ace722ce1681c6673e9ee68eb774345cb964ac255c730b4e2f4d45a4641ed443dc872a59f5b86517b30710592ef323d3bcc5f6ee0f794076f11596d182d1b37ed3672fad827dc3e6cf5adf10c629743126d5fac4cce3e6fa8acff1262e203c19421729b3fe01cdc750bfdc8cca7773d39d0216bb9795fd6a5369b7319c2d4ba07ed308b202cc692fd4151fdf5859fc3d1358ab30833e8dff8567c2d06665bcb27582f9baed6a638ecc2386943952f538a3847ef160307ad3fae78ad46b34332af5e298ec3481ba1151f6481ed6794015fda7648a9270b38e42583dab53f9358b536729739513aca2087ea98176c47319696ba2f242891f6be8fe2f49b171ca10bfc55dad7980bcc3473d748d68b13f89c01e40d37c9deeb967ad016dfacca1f42b67296109857a5e2632fca903c4b5072e69e50b8437f10532c421e6e634ab6b69c06b53dc671be924be2968cf7ddda6e063e9b28e1233edc6742c5b5c008e879a3721f811aa1750cdd13eeeeb8e820077f2be9f4e857d366bae375dc22c5acd21b1c0916d3071cce921f74ae84f4a7b18ac3396c37aaa57107c21909007886d8db72b9d5de59bdbae144db45f6631ff0d03da893a64f34c93db93642cc05adebb67216395be54dbdd6f7793acdc5af686cd86d186f45af40e9f8cf20b29ceb33e972c965d4bb39e95b3da672f3efdf4230526b4ceed030b34a28b15ae92d5af7e6526aea83a65a73aedf1ea82d5a77fc017b479c8f227d77f40c45e81b3c45b95672e4350bd008131691ab552b402799716b750117d07295774f14606aa08b0ac27297135fa6400c2dd19e0f4c23f54dea091a61a460ff59b5c4991dbdf3df74f972188790008d1a74bf6428ca9dbda2ffda25956a60b0f3bb10f92f830dca650f72f2a38933ed8db2655210d81264a606e49da0eaf404c013632348be03cb3182729bdf2c9ea7db82c2cf1d50c9ef7cee34d89a3853f45ed5b5a3d274fd9aabb16c1221574c01e23f575d40ab9b670aa1c8c099e2bc2ae169225b3e352b1f439a691f688239eec519c598a306619022c6c7035d4e180648d3ae51df4819d562b872a6631543f6f7e80b110cce8916ccf99936cbd071de1163c0a8534950a600e472f1ea13dda2351d46a22a60575be48e1716a1e286d3d9f9065ae8729264b22272da46c69a53986ffbf1c5b2916e5802bc88071704f374f014622e0943850e0c62c3c7ff1df2c3aedb432eedabc9b026186ced8531802fa385cdae4679b898af72d85841bd5a0c55e65858600cb6c806d3057afb5c2ecc2c5c1ab1f2ae80d8ae72b66983457fdfe2b70493c8755a753b7e7af6e32e94278a7a206a02a9c2ab67726b85c6de426993a4ad1f356a67f7fc8ef40a1b545257db52c6338491d084f672926b595f638a7ba4829d4e40c0e86612a9cd53b3d7471f4f2660b56224ccbf31d0f79bd9877f853a3ff7fbae7c1137ffb3963eec2ad31176cfa031535bc45a72a4a4a1d024f28e4221070a7a9cf9e30b7eeb7468a592cdb591765e42d4e1333925f160f207034bff3fc5dff1392c98389b805c2c435690bcb3706f5f57e61a729328a90eeefd3d5b8b5fb23108e394a9e031ea8e3c786e8403afb2f513d89a033f6b6a7c9a228e9e2d3d2a98706aab91ff83aa8f064b2a5cf97a38c18bde407297900e90b8ceccb8910090f09bedf329c242e97e7d1fa4c69166fd8001edf97242225c4251e4d01b29e7231cf6cc2fbdf5877da12f07f0e8dbeff2f5f8456572e89158310b76d4c1f4f44a6eda983fca2b60014d634b4dc2fdb1c54630e61d159e3a6faa7179008433dd147f4b6e5ac82c327d33df749d54389d8d3127ef11725f6455c707d1cf1565787aff19072e934cb37733e7960bdeeb85336ec728427285eca7451fc5d3568a2d1427b0e419ad20f356fd3a1dd15ddf2ec4afd4fe11615ddd168aa522d979a135923ebc53b2257d85c434b17c3b671b0d95b9b3cf0e5823f5bf7c23ed76b5ce3a5ea8c5586aad02059d74069766c3be92ac56e905c172af4b5ad0741b040aa314afe460d524d754ae6df39ad7758b71b3035f9fcc143392814667d6dbd140f3c5ebf79d39bd92c559cc8dddf57dae8f7669a18b843c7259155ee694cddf62a0f88b0f6fb69718c9b472f9a9fffa3cf2aea3c48bf017724e5be5068f74b495cd1d8dafeaebf7a7e9abef367bf7c57e6cd46d1a7b9b740cf7b740f3e32bb6daa7aa709f17bac2ba7216de4caeade4db5ec7b33c7412bb7239ac86c74a175aabe140bcdd0c7989c000be1ca7866e5b5ba112878544501b28dea2d8dd9eef37fe7474f0ecc5a6ce6f1d201627351e2ffcaf185b358266e172e6f62eed4e6bffc00dcd6c4be8bf3a26b940be977deeb2f77e99f3908e8767722b5a239e4c4a3e4fdbf0e839d554c710dd0dc68b5af97aed90f5ea3ad3bb1f5fc4b75d07c868c28692f10c1873b51017efe2378aa45e417372315e6de3f62b72e258916bc4daabb1760f6b839c1db2e8db629f236748f97afed284c3dde4647258b5dacfb61f9a624f8b88757007bc78a8371eb98a6a59c994ff654808f3297221612ef6e4fb220ca9a1d5a1c6c742a9639b1b00adb6d71fe4c45072f15f445799dcb624933535707028be4b3a90bf2c2d4cd625517c525eadc8bd755a91e2074bb4fa2a727d122f9e31d0484ea2c6f52b192afad33bac9712e7bfbb43aacd725bea713e5b5ec4cad49b927ba9ed354eec1bc095a35c60644fa654aa05fb95265bcd73595b13f38f5fd62f727826cd65729c16c1decc2d4b9d218b311e79807255057abaca71f9cddc3b12a864e43137c870feb8552d9d2490ff55055c8b0072c2828021ae676058f61b1767fca5019e7c4ca709d4de2caf57c17565e5a3cf2e20bde17219ffc742e213b27caf28a2a7bbec43b2d5d69bfed638ed332107ec16ae88a263a525bdd31d6e9ac9e056413e3a2c506430918ac35baffa3f043976721b0647f9bf8ae50c3cb0c1fbdf1977fabcf7be28cb334ea24cf1c85792166c72cc10c0a1ba6818e321c0a8f8c014f8aa1ba32ea80add168680ee2eb36deb0006fe450c867dcb95110a98da3128688e69be75d96412a70015811c66c3e38d1b7231765ede4b175b16e363ad0b7afbab48a6a88958fee2f9349aea472f97e30372179acc7984a1048586cd12c924d33e3712e3f8c9249f9288d8f660195c07b772081da55dd8e05a242293b9b7dbaf75d76bae1315291ae75474d258fe27e8f57248ce2071006dabdc6e6a6377883484aab1f6a279cbf94d1f066502aa70e8e51707b090b6089b8a192a889c5d1e4a2f8ef71ffc5eb8d30b3dad0c466f207ade726ac2df4fe9cbbc948611249adcc8330548e1cc90561b79851f7927fde21bd647626a12d201e77613afb190e06fd48d498c21f656f34b9184afecae7f14ddb6723ad3804e25a3f7b729938e4ade44fe453998ec3d5d2e67bc279a3763fe746372f7b53b8ee482930d36207a51b07fde4dabfe22dcb6b34ce2f8d5537e936df77282baf5df403f88fe3d132d48900bf24226d96fa61620a6e29bf1397842b29e5eed1125a90663c597066011e5ec9aeaea7527d7fef6175a8de76b369ff3fdf672e19a24e31d5d717e3ad67847c9fa757fb5acbebff531845724c3ecac727b9772f312b042ce04d5d19d264387f77638e22591722bde7c5cb59ce077b74cdb7e72a108771fb4331aa86cefd3ce85c5efd2fc0d3b13b5a9f5310981f56d11eb4972b66d88370f570f769397e78bec9cadd8e057023711edb971060bc0664df60f480cc1ca74c1c6dd72b7d821e5bffdb5ecb52681ef6cd52da5c2852b809c67ec727ad89654508922de0c5b2cb7a04c0a6a8f5aa70423e20835cbfae48ac3f8a20e8567e42a461d7c3d928e2842e4719f52a91147a92d72b92ebdc20071d417e533bc0784c16a214ce650ac3aad2ad60706cbf6dfb05b77d78f971d3a2585719b1b9ab8cd4b834648611a8fcf0fcad86a3af8c9c3602b850ff5d3c845b4afa3310012d08b8e0f6d8d78dd4c445aaaf0ce415f7f67117a60c64762f9566baada301a202a2061752726d8d23809e34971fdef9922f64626ef670760c1d1ad8f912719e0500117c1edd62d6e9ec0dcb436d9e68a3ffb876a458ca612f245f8ad598972458ed010fbce041ad11fcb66143e9b7e86dc9710e8c8d7415cccd317023157055d44da9f4738d562288ed3afb49c2807d51e6cfa17037dc16a5ff37007ac411876565fa9b1ca77479c6d5a8fbce6d416b8f1aadf2f87351b2bd815ce7ab74372f4c42e08b5d791921312f15b34cedcbf09593d4444183d92958c203105e8a172bf919b9c769b7f24d526170a2cd70b148e8e22a6f3acec0cf32125eb27b22c72a8cf9720311ae5f1ca822d07f07b4ad908f83b924ebb1af2b7714af64fb6ae3e00935decc1c4a1240e44633412a76b0330b38a8be2fc662b678ac72875b54b724aa8b85edc35179c809b5696c20319b00859ec719eba2ade6cb6bec3b203cf145b8413914c6ee464fdfe3c16a5e9fa79b453296183a246a958c8c58eac49e0638c24439e70a802b6737ae23d3c540bbd1bc834e955a0777e2d2750a0f760b77280b17b41ad9bdeeb14ce9905e96a79e27898a01ffff8cdc98ac30e779ca0a02359d2b954300f1825a69ed3c89023ded2109858be7d6ada59ba1b00d99780e436d7b81f162b89bd644cf4447d9d647b7f13bf88f39a15db30f5a073396d477c58853bdbcc41dd586bc5b26d248a021996dfc82847a4519ff24767a1d9e17c055fdfd84cc5dd9d341ac11f0b16797257fac775b93e0e6fbc9c6bc02a56aae86c72e0cd743e724d68e72b228bb3418c0143c5cca293411673709c3dc44229f6e603781f7765c3ef0e24d620224b8c8ebd99ca0fc0e2725ebbedd078ff0876150d720b2013017a4ec7693a317067ff7159eacc9f096ffdfb60cb25425bbb6e58e172b5f876b5f631ababfbeaaac614f4cb818d6aff79ef4f703077f507fe6625ba1babe0f143cd7a0b503578d7c86185c1f5bd10f958956b38da00084daddc147f7224029642813e44d3787944836f1223a786a2fbb38e5ea9956dd1290c32e04054ae24a0754e3e3b9bf00b512c3d5271fb5552d77782d440ee624da53f677c0b066664fe240d5be49c73af4583aeeb7ce8e8c3fa618f403782d2fc600cdbaa9b2d22ff3780b655964f40652f6920d7c0237aa18918a5af3b5caa7074cc5f62ee726179b6feddd0f0b0308f118a7965394c58d0c67836bd10363ba5e5cf8a533b724ac34c1565031156716274512c10659435f2bbdb57fe35b0b062f9a8ae90fd567f9fa0cf3f2681eeb67e0b26709f11a8dca43ea2833e290f46458678575aaf1f6ee2569c02842912add4240a749bd2f4bf9b08c71d452239b8f0c04dfa60bf7259d9e10cf9c1202781e5f4cf09dcfba9199bcf146fb322f58bec6b7a412bb672e9dcd84024d41ab66a879c76a9bef1b546794025beea9eddf75a6cabc03e887272600a3a44c7b3a2d13677ab9546e2e817c0e0579035f4f3509ad49435cbd35be47af3707f3aa5dd2353b417316d014e48262b68ade2cf4e3fc0cb21dd82ca728192168d79e6f03653cdf184b26e785ca87a11015606c27e1653a225d207ff72d9bd8ab7a777f6f5fa201c8e652d8f5f02b4c95020c6c36b672167555853f425fe7bf78a9d6fe13990668c29f502efebf1974b32cd85f17f93630bd3eb22ac641a61f32f5a2ed7f33eb1ac629cc62e24e7e3005422feac72b984ba76c9ad8f1e7c690215b21cb1943da50525a1f99de85e65e396f281bcf3ff4489daa11623720d6712059dd3e0e62bb3e9c016f930c81d440a9522c8a346d55396301487325628e510d4a54d0312a9cc450321a15164a2c4207b3b2ab2497851e0bc79c9917209059677b5eeed9c630a8def1fed1fd22cc080c019b84457796358cd8f4f6172275d068dcc85d3494cc548afdef7f7b0ba3e9b97b4abf58ca9b63fc4f8f50147331f5d963b9ad63c8413a070bf32440e14bddb171f697901e2e8d8c3e77b2072774dd6bcb36ad872402e99555d984d6a4a0be7355b25558969be1d7190270a72e3d74ecc379674706d60da4f7d3f144ed672a16aa82dadca3c1b2163ccdcb0729ec49e0cebae81b76f28f946d81e27b505f0fc585c1b556c7239fde04fa04529cb94e41102c81e597f26295b5825ba25a0e3a6f18fea755d56e979cb95d1553f1270f493551058376db6d60ebb7733cb4c02a2a0980836f3eb660f0ac3721115c3d5e7564cbf68952e98ac11025df46a9d4ab8ebeb4695858dd7d88dd487c058eb4e8755b93e87f32da30bf23ee44b47164b8790014531de72c3a677f96be50dae2b421b28f54be207760e4646592c69f63da16c34b47f9df7002e0426893262809af921bae9f52ac35aa8290d3ade2c12e7e7c4f69c1e780841bf08e271fc72aa31bf85df74ba8ab4dc039d6ba3f02543047b772adb2d3a9cddd9c567c9223c8101db71a434bdcc9aec6038afa9dce0b13978f0c59024f4f6e431629841ac72b942d6359e128248c6e16619b5516643c300bdb67b3a3cbb0c50a8aede144c3d92b9956e583aec298258fa04187468ac70493c56983ec7b3526c211732e8db72e3becc564f53fdd829a87fbb5c73ad8dd3c776e5fa6a478e0f3c2db66373d61c8673127b9128b010ab8ac2c57b0ed2010bef25ee48cac76bafffaf38cd0c9f72d13f6200ef4cf6f775124b8ba52bd2775dc763cec547e347405b1d8cf1ace057e4c02472bbcc0386ca56ecdda9b618b32858d853c17009e16c6bd48d23c513724e50a7d8b5703bb4e7ee7c7a1c305c5c37e5366ca8397cd94363115636f82b72451fa1bbc1a3be21671e9f8e02419845df7f7f2c2206b654968d780c20fb8d728c302084d8c259c1251386e12208c651fca8c5482c63a74f5c24940c3c28e81acd04543622653de16d45ac9384ce1668b3574000fa4b8ff0edf0f1da8e850e724b4dbade5a66bd9cb8740b429e6223c17319fcd542d42c04eaf589ad50ba0c720a59bae1907dce0985e9b8abe7a95130831396086dba3ba5fcf59a33bc69af72e9d9197a2a5bc33e46b404e436de74a97a223674ff73d2991a6c0ba67042307252efde08c22d3456d92ffe74f95f223995f494aa6bbae7f49b2836410b92507281d16f797fed60a2263b5a38b3f3e31ac0e6846e53bf7e676cbd7b9ab6856f506917d2df0f8ba2b884696ecbf8aa120718d0ed450274b9f2aeaed32e187ca228b96ef47b9eec3ab8aea6f1edc49edf856b568872b3404ba3dd59a30e43e0394b8d7dab47ce376158c208e293e9b1f397fcd0945f9b1df503b56c51ed85fbb1183eb36d8191bfff7fc071c5616ed5bb06d78d9e09d6f25713c9497d0588c6e572a53cef32a0cd2c3ece168863093aefb595a017757b474f68432960991052ca23ef19aebf17e062631c1cc63a8643ea4996b59e7bf927e235e70f71f3b6f9c472ca32dafad9bb2d946e80568651c545d8547ba5cc7575c59a3e2bea04b4d6fc72355cdc9f884acefa4ef12bcbce1bffff16bfa62cb97642b5eac41d0f90c82172e4cc5834aee4bed9481fa6f688372755b56a040c751c01f2f04ec993c0e4c409d567ab8ef76a4b7842b5a2428d6fbfd1b4da468968cf7056c975d425657f3472b03bda96818b8677a0523f3c0388355524be5fb0db18eadfa9f806dd1763de4376e0ef0fa35cc7699f3c15e300ad1a7fd48e35f8fb18c265c86e221e4986097280791207a67ffa3813411a821d70479e5a1fd634f0bd2f62933302fc5f0801471e5df8c0ab2906c4f413eedf17a9ff676e755530d68f823d212108b99099c340923adb8a7c020abb0dd6988aaad876430b0e679894674844b7964406c107ea423785a1f7b07c466b72818bf491d795b1684f1bc5144481e86473903548dd5c72633eed4a3313be3847df56496d6eb83a5958a1097b6e4cf84b09876c82ba5972691fe9828a734ca41c368122e247b66989e1ceaba85ad4aebc8c2578f4003811c325db22b89a4bd77191b3b6e13a9be70788f53f44493b076c4b04b658fe0254e26c7bf81327ca1102a856bdd588785933bcb6169e86bf7147349be2c5e07d728760a36fcc652e2bfa6e5a52ee1d8e30c40716c8ce4543db9cd98112c6a5924e432d2e116951a85a332762de9f19024ba6c985c91eba32cb9d98c0dcfe4c4710af05cfcecae5d28f3991f207ac61573f687f05d4a80a9d967e704579b49cca729ba73c21de42173d9770ec1907c3c4eab1021e40b71b0f97ec9500ad2e1d93516fb1cf8bad128ea99993bd2a38a3fafb2e975adbfb4dc4efe279735a20ad1b72a0377392ae73bc658c87402b576dacae09744d28d4d241ab6259fe3a0848734e4833e5a7d633fece28b7e82eebcdfefb9656b0dc6358c9260a4cfd3e09abdf71fc8ab732108d63dd0f1608fd25bf6ddb8631071dbd5db7b45e30febc3b47c213b7491f6b12ac0631447951b1f2d10ab2dc27d7139b5e15a69b7ef18bf908610218dbc21ecb0a2899180f3093570a91a54d732eac3b341cfe4f12efbb0d246002f376b062506c426b93f8769873db4fbcbb2d9903d033e0977fc6be664df79f72fb4134388e68e1364a984462806fb66bcdb9c5dd907ef12496d87ea48dd4d2722f94fe425d2148cda7c46fb8b2162d263c9bc0332aa3b7f5ad609c1f90ee4d7289d7d8ff8c11191d28e04b729b90b457a0383f208d6d6172e850366adb910f1f9773f6b44f0d78f024a8227d043a460b4b5a30cb6922d5ca9b8c01bd7a9707726c2df2842d6b12c71b8389d0336bd4aa96c4b17e4fc948440a0533fbf02f2b6099f6dc5402c2a7a1ce3755007c26b542867b7c98af3810f7a05f585e0660756ce29b03f03975dc1666dfce22c8dbca796affc653a6b91bbd686424a1641da2724322b03c2ce8f4ee0b081e90b83d296b1ce0734eeda751d191923f2a932b1e52cf6620e8465e1c78615ea6c317b68b12e34207d36d052d8f014eb366b9a72716c35947b403a323be2e3580407a99c9fea8f44c4e4ae265eeddb1297d0aa0d1722de083ad176a36df3cf4b196d685b0d59ea5fee8605fea995ae2cf4554790b229bd5768d84516ca7d7248f511d0a4908319d7cd4e8f9327c4131a80fee04a2720ea658edd8dde353c6b3ee99029e2407951835d605b9a4d1287eccec2ba6326c0592a96576036a1af8db9daa6c73b415d96e2f806c71dd5ed14f9d4a6498cb46266a7a910c1e090a9d58f6bca7ad91a8554c36384f1d1771b1d0ca687142df72df633d34d626fa94595be6ea6c99c4ff91d0f45a3823b221fc07567e822274728195166516d397de7401dd32c96802769f80bf0868ab6418366de2bc91e61c72d1ac499904636ed5899334a43959b0c365e6698d17e1c551250e0c685b33fa72a6d7fe5d1ca46c99adf937f011c25abc3608b093235b42f675831dcb12163b721752c5190eff8b4696fa30f8fdf997fb2cfc2d0e308d350ffea5831405747f371d76a883af668880216e310882245da20d42ca42cf9655dee013b8603990fa726016f5fa0589ad741959f27eebe47194c790bd3be340252a8229d1c7f4fcc67255abc6d6dfe113f3ef7fe4da7c86418fdd2358fd3e92bf155ecc07180b5942722103d23bde7ef732635a9a066e13723cca16d6f4ace2e213c5d52a6d795d2b17b83609171fb454446e9044236c4a08d702727bfbd7427e79ec72466d2f8b7872cd112ec44d2077a13d4f3b061efac6be24868c78c42512d397ba412ffd94dd5120eeb618612e2c32850627519bd2c06ebf2cc8c0b32369ee491a7a2d2896fa72f3ce138f83241eac5b846c79983fb85f40b77917bf787deb8e4b9718664a353e7413fab52c658b82cb0dde9df42affdb0653dccb34f65fd563b7ca8a61a1e820c2d37d72108c575bf0f7660090ee18843822a675b510444cd3231f5627301472b7d4d44221f418e7c4439091460c369cfdedd53365b92d99c77bc295646ba372258b660c22d9b85830f1c6fae78ec7b056e61a1318de200c1a333391f2ee997246c4ec829c14f46c94aa7bb15b0643b8c9a95f2af5006ab94f0c3dff5b9a5672eca32795d60c8f68e980c7c55cc095db2b4950085f204133fcb362b107feb87239068f7f3e90ce17a543a03caa24625a7ffcf6572af875f1cb7aa7169f57aa72785a4acff812faad212dae24812e3204bc8031ad0e8d164fe72232dc5e68cf729419fd3c2a867926566e39f1540a51f12096dd222cdbd6b52edfb6ede1155103e1a36b562b2fce2841b49db6174c3ab77b18b4694f6d0f6f2ea4b5cb3d7dc33dce8967d9f909d4a00d5a76658d08bb60d5363f2b11aaaf98a7e41832f2be9f0f7a547aaeb7c21fbbc7b84210be9f55249a452bd4f68a5acc687e586197993772e908695fbee9941953a1610de0257bd32103f75c692d94ad6e1b75f65af66857abe9383842de28673ab9b70b2c877d577fe44a41275cfff4633362189b3aa27220abf2119c67de404e3d1b8c7bf79152384d3ea6f933189f9cb5eea3c6ff97441db4c3fd1a3a18e238c7602f7d16ac031ffa1f6c7f7224122ec458c1484d1e2572b393ea0655e1d562e23adc0778ee9dffb069b005417fa6bccfbbee1d9eba721097d8b9ae01811b8cca4e79380c8ff7ffec92d4bc7b5de8c0dd62a231033a0823c8c04dacaec4415c63d688c3c5f1bcde322bcc8717ee57b1cfc928ab28c472894b021e6f77654252d9c30cf83dc69111411bd3aefe6626c38560847b5ad37247c9fcc0c27cd6708d57b81f9a9c81140f748a11ada5de4726e6bf3484aae538efc0c8c53a99c03b12aaeef9d0a975cd769d427b0a604b9fd791142b7916fa4fd7e6e4b59f249694cad0e2056a83920105ce597fc9b6de6d2dfdf3778012177226f827e68cc667de72eb3fbffa5ab92f2f6cf0b05d04fd6779cbd1cb059efd722a3dec742e27aebf3542a81baba3cbfd30bc424ad2c8c59230b58882ff0f9f72bbf0341923fa1acffbb501de677196af3f957af7d9b1d3d0f8458012b36297688441a4ed6e4684b836ab8a3ed1f5b76b3beef0477e0a87353a3dc26c259e5e6fb9663e789b971bb64e92cd172b251f736fab7f47337e12c0750bc10687c2ba72d20a4861e3eda5fd2c4007929a4042e9a5e67b032a8d53afed06034cb441fc3eb148f9c67c1e39e670120d8b56502e590fbe9e0478d59a8d5e3d4a96c07b640849f826d91a3f37f92b41f31c24556ec8aa228df3f3b22776763a3fa408d77102c973859d8d18796cdc1ffeb77c8e6752fe893ddc721da166d5ff02c8413a2d72e956cfb8d388f9f830a3a1fd8f62c48c5f3364acafb291c3725c2ae73df25b1a354a51f9c199e52addfd643e9445d7b68625eb69c6f25796ccd09526747c1b5c205793147a9faee1ed34a513f07f0ef91560c8fcf70a070b182b31964ef0eb72d7576cbc6209e59d717257c7ac6f5d298d78623d20c85a4e7214fcabf2f3b972464ddc0809f116cb8e2a700d12d135b6833715602d4ba449e1a1a8d6ef983e7235537d79a6afec6f96a58ad8e36646202a912db05840163d590f3ee20cccb8205fa639eb75e674571099bf41a1634f3c81e6782b601ea9b5c1a7fe6494d29572812dd73639e8da70af055d9331c0c5f3ca3a6f3dfccab6542aea0b62ca249a72a55fd8f3dcff762fd27db3f74dc47f34df85857823b55e28b0d328b86f0ba0725dfd2a701978d2d190702b8b36d172d4db17a4cb7509ce388de472b29f3e6d3b7ca237dffd8c24f6b31621823a468fa5510c9d1d7243bb28e269fdea44965b1c459e69415df59b7a862db15cd966774c951df9dc2a153572c9f5e8ccafc6b8726f0d07c473a0ebeb82315f2b34df600c3b382aa875b02161d3749fa86caeb57280d1f77bcc8f470139431ef1211d77e1a7010a06435c5a588db098ae735cdc02282f1fdf3b1767effcb488e2a441f1c00ceaf86f8ba12f45389eaac9d2c65e72309883eefb045197bca06f0179df5a3f3236f0a7be57a14f76244632a6df9b72501e2f8753bcfa36131850d230da5a43c0c3e3184c77e359f5f082b4625a4d72f6e12b88a3b7572acd2428e1606185b23c45f7dcb982dc10c886d66da74d5d7220efdb0b96bbfc2c291bcc5bce36ac9fd049396227816f6fe6379e482d7e7613a3c4daf94ddf969b8191a8383543f8cddad6117d44741390f9ac56c8a464c67283f29457358fa4a5df64233886d232393b0b454052b8ce61556694a3dafc3972640286d20c32c224f03524f6e87f036590d2089e4dafc6f27f32c17afcf0c272959bab2d9fccc8171d213a159536b5f098fdad731a18697a2d2231eb4911ce0ba2bddcaadb0328fe4f43421e828490f8d3b7c612cc76a696d3dd0b383e5dfa721616c6a8b1e26f7a5492827f67db4b1bb76159efc2b4f289d888ea3c728ebb6cacba5c248bb4d4bd40abe446b6ac7cf189068dbe7f9b3390c6d024676417735ca4ffb517b5607f0007a2008ab6ea84fcde9a44c3f64731e48712d229604f407254e57e11da50b11e910ec83d788df37ddc3f4f9387ef05ca8ec976eb164b7872cc4a305b4ca2e5b93f7d946fcf42cb6d47b7e9ce85be1b9d89937726de48ef483316a09676b8eaf84e4243758c8d64d2828185c8d36862a0f6c858150a0c996bf0a4aec4f7a49473d10797cda201fa649ecc3c838dfc38a71a4fadb95bdfdb1c2293a0325ba6b0a0d7177cec25c1bd557af74b8533d30fd75850aca6b89b0a729b49da0ed1536e851465873be8605563f2f5f63a3e972f66b176f721d6b21326072943d7949b0d6b777dbaef72096ab9ccf30385c3c3188bc898dee6f4a5a372dbb7370234c03895528616a8012dde3103169acfe97eff85632a950224846072e2fd072fbb6196af4acb71b002d1e1bf27bf1f8f3f4049d96b90ab06c5bd8d72be5891194434b17abf8f9538373ea53c50651d06415e1ccbf3d4124d144ef472169ce29707164458f630f4c277337c50b64f08c9dac1cfdfd7a9165aa6aa3e726973b280545d0a2a962c9c5271bc7687b69c3cbe1899faf7339e7270dd41f025984aac96a979c08eceaee68d1119972ae2d7c038b940d9cadc536a08a183374a4ccd1766d907659110d477e6c7398640e65dfb9bb410031edf8e314c811c057268e247f84435366f16accbdaa4dfb5960891c3c089718388f46ab8f463d4347240d66307f2e02508a75b547b4ee8878241e74174da0629e7348df56121ca48446f851216ab140599a558e194473c8d6e58e399a8e74caf8e1c9dec24161f09720d804d92218c9db3c1cc731adb8c33f3cd1249c97d0df936e1941470c841f55a6c662554f4830cc912631cc8398c518a8b834f089dd1622960a9b51be9619172d5d4c112e8859c44469765bb104703e31623cb121786376594212a12e39449722c030e367a66790435936cd0f620af6da441f96e76996cb95df5542783190c7257258134654ad9536537488647b539d4dfafeb8fa06c6d64bfbb3b7b095fd81a78d9aa75a17e6fbff397d605ddf0d30f8b32eb046596e9fc1c5e235822c51b72d4283d239bae27b78428b10dae17e6e8110c4d268ea4fde29d3133a592e336725b868eb24b2e5139ab4a7660b706ac2815c0dd28208d83441bc25fc7874f6b723fb434ea11cc73bd92cdbcef52734c6c90eec4e932a3667897ef545123e82c72722551a3acab0ea4a638287c3c0a44bcffca8da1dd399b3d8f5306cb2363c21f8b8f29264709652f643b413910388ec25ad706e98f33c42cb0552be05a54e54ff2e2fa526e8c680056f54c70862a6a99dc566f3f17b4ec0d0270a3143a97477290d140e0f404072cf7ef0090fa2ca5d038135e89b4cc5aa9d1e023a78185e96559e32d61d13b84ec074931dc482d2ced10b479c9cc3b8a577084753822bea86e686442e2d4c91e35c68f98d25537ab1d945fee161385433bab7f24725fa1f2724b6e884edee6dacbf809b5777f9c7560c17c463296426f3a0ac6d51b1c01c172a88ce9f70eadc4c98141c10eab00e016c0e4ef3022a5ce10ef4de68554cd5372099fc059599987e0334607195b5a80baa86d0d7d690848eef6721c46f68e9272e6da92956a6d8874bc96ca9680b960746dbdb701ff01fcb6489081b9397f06362546f0795afbc9ada5d13fa6464782df212367d30991d30fda650f12d7c7d8725007b9b7f8ef7b71947c4237413cfb89bd4c880bb820328a8487c06246e9f11208c2dc0150067a7539882fc796074cd464dafa46da528f94d809f9c4631ddf16f95341249173cfadd42781317438332b035a267c869120e4cdbec54ce8237272260b14663c7fdb855e0722d235dc29e4c404086d26a884537520dbe6fa0c9e72d76f51b9988b084155091357bf42896c622685d335ec098fcadd5d21a4b3e332d5f3e7947d7c506e21d0f2c36f42ff8c3f2b92713005b0d7a0b1b881b989a451fa0b78f0a9ab5e3ce67ef8dd0cb63599dd2fe3fa712844dd98225388bedbd172cedbcdeab4b44703d16e60c9a61189aedf2a40e9dd8eaa2408315e0c395036724d4bebaab6d023b934b9aa3e570f63b5089b8384b2c17b2f5184a3287bd26d726406c91be18b41464136f474964fd968d1d2c40aaf0a87ebbb65f925e2679372a86bd5f742fba5bb3d1a9e193c86d0b326a169fa772ad63e9bf497936d428e724d355a48c72149ef629ce3520836adc329fa7e7a8a7ab43ee40ec7fad0cba67262cc7c334dbd6b5bc249d17bcca77f22559780eb4124e9a02b0c51f866072572fb542c978575df9d79384f5129042d3777716acc4fe7f86cdb5dc255b5e8416790cfd95e893c99cd48a48bb2b4439cbe715d925d0daadd3b5f5cc6b971ccc172c2d3ac9a57ca9695aff63f74a06d7155a8287474b499b0dc88a56669152fd5727ba69ecacd95045667ca62fa5b378b9a3a390a8cba7d70289c315ee6755d6b720973e982a0225575db8e84bc591c25909219e28286cf66382d67db1e3550ce7206735d22b4b8e85da652dd3aa057d292b7b9745f1a501a8a017e6a957413912b2b027b420fb817956a9082f37f4156f9f00417d4ce7a5584e4819307209d612c0d1b613f051e146d5682651065e0e491df932b94768173c9c1a144e2a804a172078a6ee40526138d8d02143adf4b96dcd686a1132bccfb0a8e311eb57a63e970458fc6f4f8132009eff79e2fc9d4d25fffedc675aca6cf73a67746a625f57c54a8080d6dab792e1f80be68647a1ca38280de160f3e7429ad3ab2df63d917697255648fb22a0ce6b59f16e57e9712476bed76fb7bda72735019bdb0bea7db9472f738e38289aacef359a63b9f9a94752171669edb01a7ef2c8cc5b045c9739472bd9066b41e421c2de80fdb1f7e0ec8dc649854c9bb52aadee91e856b2bb58a61ab7c9b9b89e76346c499a704a9d91e35fbc59c90718db0b0f09bde5019202a3040a81cc8a526a7e749d4ea4b898a89188092dbca0dead5ce9eb8c498c90543455b6c32f9a3c5c9745ac7b047486b4a74f862328ab8acb0f85f66c5670f98ec725b8a1b602e500cc7cebd3aaf0df03f57d70ac5f638d7ef51e30014f90ca3d521a4f4073429bfa56c9f3264938fe3d2916307066d22254dc0b3de51c2f5096d72594e11e96fb8ff963197e0fd6e282bb6b43d67f55fa5c9730c7549c2ac40526fe3bb46a598fae95e3c185af0d8e9fdb5aca67f69749f0264796ba3a1c965240654b3e760d9bc0db990b728605a4c6ee23b369afc845e850b58b6ef50f9f6390f40ae4558a51ca91306d5a31ac628bb009be9e3526b2686b8b4e7775891822d7261fdb4dd6c939337b83640d3a771be43d29f8e9b2bd6b2eab0486db36790b372ac5029d699c692da22e4cb1007ff247efb0afdc77662d459d2a190cd66f0cc72da7a2a830771b1e4a93a42a8280f9c6249746d440391633b61fc06de1b938520a86f9da657aa93b94321f3742db73ef441c8604da79cff48008858cfdf905a72a70dfa5bccf90936b80dc59aa162e19ef905c268c6760dd52dc56db8d40de472b782cdb2e0434b34456bd052a9173b052a3a67d6d7d0516301e2eaed166da75c78bea2b4f2a27214ed619b38bad1867f718abc8920c1e17076fc8766cc17e20b38158683ddcace1ceb44befc296f6dbfc001a3e385a1b9ecad9a861aeaa3e426f2d0d2953759275077ab1d98ef92ad505534b08d79de285768d67e0821a92b086ec2d51cbb6d2a9cf1ca3e06f5a46bb0919152da39b363cb5be821d192076572768efe71ca545506277ddcde41991a39a188444ca89f2f15972fabae071bde72ecff1b06ca3165fb8a2ad499761835428f2ef90bcd8e8cd1fa46a263efc89108915810093eed92d13b9141749ee25783db43e9cf8f9cec459b003f4158e48872a34a491ee7eb6d6fe5b09c0d90d90d6d08db118b36b0c9d37e45026a1da46a59a57caff78b1c4244b3806a2dc52dbe44c1a6e4582bee5a4d76a526880dae567236034403515950f5dbf9c350b7c0ff98a3f776753669ae003f512b7592013972298ae9e5ebc5559531fa7aa087e7e4974c6feb8852d314902fdd9b1a18f0c42e7282898ced1cdc4b89491a81e0d6440917f19098e1a8c9488b28203ecd345a0a57205433445694c2f0e12eca320d3ab82e90bad687d8c224f683e3d10777d27270605ba9146e33bd1f351d63963cbac1678f76bb5983793c4993d77e669aa172b3a776821f16741af447f6414a5cad60081a740e49065cd2e25002a83e93d4727f0a445173022d76a2f51987f30cfabfdebf6b813f223a5eecf467aff9d93d72d0955d2533071ab50abab307e04ddc47b852fe4c2b881053ca12facd7ec36972e65c6cb8b7d0ea51aad4b3dec0a5c8512a1905e19f146e6226216b44642502597f32cb8eeab78126776dafd194a0b656cdbc0be0100b33f2b887c9e47b6a0e72aee8053a23f65601a88553d230de417ff42ac4b14de2ecdade29970143ed5b723619b34c32919c98eb8363e8549a9a54dfdace7d50e4dc79290140c9de03e562b2853bafbfe5d08f2e563851127ad410a1b09d866baf0cae9694c560b5a7111e16007ee2d72885559cfb80c4ef297d596f44c9a1d98bb5cae237a7a5d76410507d42758914ae650d0f9bfb93def48aee880e22731055b3c2fff667056a893c72e3c9ae4c168acdafb42185ee3d1eec514175febd8615d0d26fd7d0460511c372e877966c26aa3caf40af0c8b35e7b2849dd89b3f88f64e5c180b2dd8a8b1b57291e486295605dea25e9d576bd2299962ce743578bf17c16e054741cb0293a146d409c322950c540c5c80ff65f7367f121284a18f64b10bd84728faa2ae605048e568890489f3d7902028b14d397dcd58b44446fc2723d6709281b0dafd62384a195193936d82d577370287c53d293e8206c37ce93a6f8a864e1785e1d622455e8a95de43b69cc146969073ce00fda73d4271618ec30bae6e9a894f852770070dba9978e915dcf92a40e667a16d1c03beff1ac539881306eb60df39db6662a76b5500581169939db75a524b0c314c541b0d5379d181db254e2ad8525febc9357290643a4459c0b887805a39dc027d31b76f02477b394fbda5d3512992c0e85461196a459a536258034909c53233d73e0e67f1fb0b3dac03fc1ff910d734552d72243b81054e541256a6bb9570990b4982e03852da8a9886c9bbba8fbbe010127200e1abb9cc5a97d5d04060e9c13ecc17663543cc6a65d3c2868d3a6216dec572a9f996c8002327f570ca192caaf7885f79fe8aee144b37ff5e41ea1eaf6d0835777597194e3d0b58391f8986a71742a840b966fcea4495a15aac12dba378e272f591f5d91ab0f0a4ac8e5231942536a6bf537a017a4ec5350108d2c7dd30a372b1ccb95f0b1d108792905adcfea977e74e2d86965fae592fd08c2a0d06d34448f9d8381750b93c9e9e014c174805563c8ac1350802a6694a41a47ce53d44fd7228eee65e887da333d72ca41fb02e3799f258433f4862fecf4447d5463945bb531e641517adde04745e85f8d16a01954be314529e8e86393be14a33ab88473472e2c4ad73fbc9e981f222f5b65d7bd6a77266fb3c4221c08f68b4d7577f237c7299f6a6d1a53c1c02b05a9a765286ca0a464ead5857b25305abd0a955cf5b4566571562b5bd34b1ca56f53f35232c466f5438c9938694043f2cd462ad8ee8ca6c3f6352d823fd7249f43a4007cf2a98c091134d6a8335e6b12b5ca0d7dc92277237b79b19807731fe677f45c036cee9214f81864ff2c106e5559c33d9d089cb72ddaa5c490ce6e40cf40d78a23fb28c58413499c3b8e770e3011efc3eb4627e315b7074382bfabfc1ca92d1fbf059aea19174ed348c178fffe5beb577c4bb3772ae6d913749febd6e4d607c9cde254a455cbbf7ba1704a65026c2b6a2eda59372fc8dd9f58257289dba0339002412e78eeca5c49964db8462c41893d02e8ffcba02000072a5f5201eb3c3c856e0a224527af5ac7eb1767fb1aff9bd53ba41a60cde978572d0f9769d9b306934cf776aa6fb5d447330b8bc39553750030286a08498d4587229fb691590761d9d2b750a2cade9f24ccae94183944efd75d903cce94066ee72239ba18695e626d05d154cc16d2a531257b11ec9429b55a70bf24fada61dd1127968513d39431969df3ffeb46ef30889a9d91e70bdf4ce8471ad89f96730ff381cc2e5bb8baad3a6830da914f1b2990e0063434f35ea91898bd91140ef8a6b72b1107106291daf1d3a778dff1c0686b566d9e3491e71c2cc1c1681e50f99581d65ac1502b2f3525d9ffd3eb23f7243698a76629d2f2ae076f2d111c98387557260699066e327c8df64f55c5c7e1f816762705f4618ee783745ea463df8e31a53cbaf0768e90611b3b52446f8e05cb5efcb816bcd866e04196992a52dda013635292c115b915b79542e85ec86d16a5d47ae0d65c95117659f6e7560488fb58b0feef61cfbd0b762f15defc0dbdb993e0171aaa8402d88157f4f52ed1f263e757206780b0a861b0d1d571de1ddf6ebe4f821b8041fa0e016f6c113037695f1cf724af110f4364737d8e2372780855eaeb3e63b301cf5463f885c456e623d3c1f134211cc0ce95c9c76d80f5c279713f59b80e55feae84047727e1bac01ba3c6172fa2be3a7f9c5de536f26553b4097c03ac32263d6ec2ff01a03fb01d7694959728798cfbed318d72340e249e500c4b9dc5a594fbbce8f0b4720801250d0aadd72e02573c0eb6cd86719945c85636eba5dc5c14e7cd1db97bbbc6c26d89bb29772aecd650e1378a64dbf64d54407f73e48cc751fd2db7959c54d408c2c9f2c9810e06f662ade103c8458dc82e8a5b7512be9b880d92d9db2075195c3acb2b9f372c46ddc38b505fde5818dc54acd1ab6b37013d59f8f68874a135893310d63646eaec8c28a6c5cd8d1c4c45f83b561f1aaa10acff007409c5c1a1347c4f274c6728ab421644973f30998949ee6c04aa088216c4c60cc3657943be55817102ad90dea73a00b7b41039089fe298ae33a232f5b55bd48dc48de8570e95e8aab04d372e19c6c4cfb704b96957004413f445edcd09b43d716d29c192f27c678f6e8527297e2e7d52211d0921448055851f6bae81fd91038161b741b3fd4a72017681572f9500808a89dd77029b6a7c7413d7a4038149b58898cb58fb42730f9916ab5055646449f28d810052d2e13829d03881e8a7ddc476ec5f077e0f595f1cfd40c154bf4fec05c76b5f89b5ebbb320a2d61fe7541b4ec463ef81d4b914ee1d26c97212b4cfb14bb06154ae2507e2752e327b528ca95331e85a5f8a3ac1dfde21b472590a7b1e160e92f15f3123fcbaad866d747397767a439542550436f19fda51728310041836d976afd49fcd9d355d1f36f84c3071addc4b1a528d81f4c6ecec696129aa85c676054b899ea332081756a57911ebd644f607664e2da5b83aa688097ffaf1b0ca9b0b1b4bdc46e9834c78dbd9f3d47f4966eb9cd54dd2bd26071650a439aba9f715785e422e0f6888ed9097648080a27fdf754cbd44192d5b7dfe7190e1b9e41dd4c8e408e736594a4978667dd821f92f7088df2080d96872fb03720b5030741e349f620a06a159afa2360e100392878d4e81eecb308b9879e8147270fec32907232ab01a171b1c4d7db8bb99f44855da49ec506330749684b46b69ef3380f45cb38b4388f751f4b5640baf9c571991b3b46cc7a02defe1284df272a5e60c1005dd4febf54abf83d6305b08258bc46be874a882bdac03a665bbb170a7399f184eafc688df9f1146c2565cce94f3fc0202fe6c5367a2aaced03a2b721c1e787dfa23107f8da0f164cc309f2026ef61ef5e2c5c22793ccb58f19043471ed9497e6afe098100cfad36733352cda27075caa84d38eba45b9a45dc36084d03fb81b0e501f3262ebd72ef47c28e9430b74a1057ec8046763211767b66bf7252029636d91dddfad796a61b385f230fa5ee585d76cd16077cfd859959d74727431531d4f3f4fa7acc11945ce45f83e20d09ba0bf1b8fb4fb4d78ca91036cd078b497600fc163c2cfb15a61098d6ab3aa8a3c9c540770be3825686fe7f6a80725a75a7205ace48748fdd9bc118263b6f20c4b06156134fd9488b5fc4d72a6772986ba6d98bbad2cac5420fcf7eee643f030ff4903d0cd51611b6efda3bb9f07249ef252ec97440eb980338b939fcdabc2ffc48a55a2456b0c8c5103a2cf4d0666b590a0f0f6a31baed9b5cf7fbbd241a5771d0c1c01d5ceb39f4726e561201721ffedace599c84bb8fb76d0db52dcf1776e16cf2a4c498608aa336078cb14372b27696d7cc0f89291c8e465cc9a460673a59bc241b5f198d19814be8ed398c72df81e9716f1872303d120ed0c0234f8ad141f1b29432a599a9aa523c061fa772560a307e8d9ad58c9785348c5dbc70e253c424263e9b2dd2d31a3ef1440571727c719b8280959a4a7feb2b3dc0fe7397e8ffef046631a1df4ff4620286b755728b4b896b81e2f61992de1d094378f7093854d0fa4454bf321ba91af3f2c2de7258402cb1274543f349eeff7bb9d12abd1c20bd0eb031bc2a192be09fac58313d1300ede3d8dd3cd82b0fc2e55d4fa5d2a43a33506588ccc59c99960dab17b172c9fda5dc53f344be91c9e2bb5ec8a3b1f444c96cef6f04acf2b4201c64f990720134d67a8cc96756b26702614f10a97b64452b68494fb9f3c793f5b4e75a6272c5af8dd5099e4384ea3993439082c0de268eca9101e84eeaaeb49632cf7b6e7298d08a7d1f067145374a68c5dd3023b04b678c44d8198855f3f621f99cfaf772cc4561193d4a94ce6f71d25c71629473f5355e49c77425b454c685ec03583220ba239580b0489277e39b9ee53bf6c6a8acab4d4a32ae73bf78061872aca0cf724b618098e1ee5025deb79584b311699774695dc61c42ca5154c21d1e12a817109df05e5de43bf4d6c15ee39df883fad2f7bb29e2d671309bde0d0afaddab697220b047b8c22d58c718a6b3334584475250843b3ea03e69c4626284d498f2a472f5bce63002a5ba587cd5b17959cf29ba8e857496261ded6e188bcbf071f4917216826b040d7f5d046b2d6c104d96a8c624bec7ab4ecdd7d37bee6e0bfc889972246b657d15afcd2f502ac8540bf28e1ce42b662c5a778ab00f628987ec5eb172639a38ffd8b43787328f82fb7a149fb41fead7dbc701eba737526547e2c79666ae2e4fa84f4a58cca46b6a005da94038062cb884dad78778f3b877f5cc838a315d973396361adb5f56c035495f93fcc7bf63487802846b977858349180add27262941fdb842a2f859b2690a7e4a778707f0b384a419baea37d2626f5f95f8972805d2539585a7dc1e3c71ccc965beecc7d67a41941fdea4826476e9ba86a9a0f67983b53689fc461f4f64ed462786366d9d016a0e834291a31a613b83fea6f7248e6f8e72c435b5b1994bf6d917059bf5043f937725d7574dd1c775690dd667211ac0822c400897660614aa23bdb67b76895b1fde376dc66091a5c74c24f2a728470a278f3ee4f6ebbb52f33b6db42c168270d8fe30178f658f35331c75e55728db3f384e6f2fb80ddad67b896ed8c4464c65c4405e4aab6a4f44a14d0481272006ef8465babdabef111565cc01817d6c0481530603fb9528bcb48a6e0246b72610c0fe281e00779358850cf70b13dc680107174824f0dd9b913bda1030e014bfb3e49eb6ec4fec2e7564f198190cfac2de29f22c6367a20f80c4ae0cf4d2967fa898c3927e3614cd014f25921184e410c9a5598a3a11a4d42e21aa1f746705704dc0101949b5e801533ebe59d6000a034881e765c97f96c26bc7c7fe8535f727050395c3dddb08d48aab8f08d19bcd8c1979d50ad3c632d0290094fd50ea5647f38ac307ce38bb8923448439724f510f3b0f6cb2b86e3f29166666560f45b0d669967833a9e24f1d44ba43d8ae227be6a2c4ebe3221bfe6ed4eb39ee6e33461a3c01fb2db947f5a9ac2529dd41bf9653972153e5cc245dff63567e4d6bd37723618aab19112a55bc6a051459990deb2759b9a1e972c0f91e1f3a5c5ee2e9162c27426f6596f539f3352b2d7482b40418e08709d93552a21bdd982a7f744a12d98f1bc3135377bbeafde7936aad8340ac8e428c3c8cd4658e04c73a58014f772362657b1186e1c6ac77747b45c5d51c896af9af87442939768b22e898c854d72802954e36972e46d537dfae65c294d8072d2ceb42c79938eda066c02b5e59972c5abd2abb1ea1721d7faca05d5f94130c5aa13fbc26087fb68d957a985c04d72a2d03af17d2846b86a1726985dd73884e8d22fafe14073e9fca3c9b970129b7236ea2802aec5c1c317d953b836a0303f2e13640dc13ae5f203712e619c87d0729d7f1bafacdbedd7e50822ad2e3d0d96b127e008499af3fcad99667ffd46ec723660d8dc55978005c899c0e043e8cae3c20e7bca29b8a7f8ca943d51dcd6b87256c84109780af9204f6b0cef0fddabb85f526f534430b5d4255ed7cdcfc0cd720012b6a2bcf24e4551b139e730b191ad14a76d70e4f176de0f7dca3374c51471341f58c5296c3d7d6e04c1072a805128e9fe7724db2629cf1924aeb14677e872c1ac51b45d71c63afbfb519c383d4e88dd9a8aadd8c342594611a21c3b2e2a295a67e9eaf17d73bf62f8fca69bd0ec5cf6c2583da1f6ad8ab588ec46c654fb72ca6b21b0dfa7c6182de48fb2cd1c2a313d6543aa50f898e5dd99cb58396194724126502740088b78f338ccbbf8b57ffaabf706386ec9f8bfaa1922567aa1437206ee88097a37f1633088ac4dc3c7d9781145cc79c55c2687af1a861774f40e723c14c14304e86dc1eb51e39a59d08fb28c095ca69dd6437b7f313305fd4b57729166b3ff0d496cee82c231c2833f8ef45d055667fe81f7ebdbafa6a899a232065fbdaeccdbed21e377f2e22120adc426d5b39affddc63f51f9150cad2a051c449c04200b9db5031da7c57584e939b624f0bf49b90d4cb4788e06dd5fe258cc7210ce6eaac4af4f7e0eddbcbf422c613e7d5fdba6fe234f8b9e2e80a4d9a5f9724bd815eb0689de97d204135457f977ef092cae3145725019e3d75d3e4ed1b92f31719393eb658ffc390ac1a7f8c54ffff3eb79023820db503ba3c908dd2a73720bf57bb15f2a7a9118a8ca594e556566e3cc58dbd72c5c0afdd63db5181fba729dc48a64346c3c89ba1f53c89581c3342f553efd6647a0a31c38faa7f3a1c019404cedc56513f567d68a4958e3fffdb0423e6523c92c904082c9f9a5111dec47140ff71f9d8d9db7ddba01ed5d1ecc86a6ce2ccafcfcc41153b0607fd7f8ae322a470cb0a5ac36c59b659c70dfb2642887c66649a8685c438221c1d588c61872102bf72d7944eff62f06f3a2f1cb9253b48e455064e643ed916ad7e90a3aa64e4fc31a713b833aa9283ac9cbc498f1b25a1840edee3f43871f0f127e8c7430727b1c7b517827d70a26671b40a65453f5b5823b112a64bce9a5f86b985dc89c725c045d8df2a8ddd5844f6ae7bdda00ca940cd55d8c3df370d328d16027fc7e65bb05b1d57e3215ae2f3e2f3df68a4fdc23895f6ffce6b84040d3aa4b0be2d24950ab7ea299e3f1166c4ff67c7388afe9b0bf8c611298e2bd169075d311fe7801ae3f27fbc066ac25996639cacf64fc2e94c8674a78fa2cb43f8c604304811b6fd6551cefa903159bf78ea0ed7d3431ac8bada2361665a35fa956bb05e28da272672d917e1f732f92ca8f8d296d58fe0491510dee20b228f9f0188d1f3551ba72d81dcc672ae93ee445d1cf1d70896e6253c562b43fd030db8883781069967100d32b6f0a058089b8df9540d9b1d214da4eec7e08e8b83f6fac32d32a4ac5f6725893d0303ccc94e32d9a0281c075a293e479b7048f19c8da7a1728a7906f1e1ef77f2b1db5a6fae76fa17d2dab2dd4155c90c7dbdc17066ef8018998e3cc440c0936f71ced70a523fc6282709b49587e6adfdc133a36627e48fa10a5b3b5b6412ee02f0ffb035088ec0eaa8d5da1e4e2ccdebb69d5bdfe37aa51bcc981f47172bcbc9628c406d290cc9e8335111bb5e1db533c6bbc12b45d4618b96ec794661706faa5745308a39b73ab0b813ab0d2db535d439caf8aaddff36c49080d651372b2dad84809e24205a56d0654ef4d4a0a8bf76053f0a6b7ec7784797063438172e06e69058de27f6c2aa3f72a33dfebb2b18a18722e071f56b09001b429f6c61adb0f14ac2952ca8f3b61e37f181861d7449fba38b972b80d2f1f5176c3b23e28bba04ce67911c2ee8d3479c03a252c39e6f730b09cf28436e3d565ffab79a2720026a26ae8ad78810b389c7b5ed336e84ee3c0acf949d9a7d50feceb864f387238768849d6caa6de76c98b6022f2e1796f0c3b30536c012cdf64a8da5564eb1d551576ea9b8689b0929caeedae3e3e82ce7b1557e27c74974a9b3595e7fab84b2ce712552d211d0b2b59af0363fefc54c2be90db90f17f0b5d73cd822e41291da7e32bdf2e68de439b13d8990a072629f43a83f84e98fde4b73495f35e5a7e72fcaeffb6525a4dece25e8ce48bb00b57008ff26d293418d8e01df928df4da4722be94d895ae87421ae059f519951fb1fd222004b11dee86746ed9f4afc5c2b721aa36253d5eb160d2c2f8d363d3c89553582681b31d84186f89c7bbc014b880373a2dd15d565c82e4e0024a1eb4162a0569903875114398c3104b37e06fb8172c0f8233344664f763491d004956710abc9e877fd7213e61de95e790c71573c4eb3bc61cc3b5e94adedd37e6cc035363878b2609f0780e990d5f7d67264bd2b47de8af67271ebb89aa571b9b0e71d7765dcc85137fab5c8c579102e07134d58579367e3c82ba72bcfa33102465c2c7b1ee7a7818a0bf19ffd2379de2971474b7240f7c21caa626653eab5d5f8b75732e8b98a16692e76e2848b1e84e8797e70727f7f5df29f95b3418f61bf8ffd21fb4dda527863d6dffa817ace29372622035c13adc2dd7543aa4f6ef35c47623bf0fa25d158bf1d85301d38fc9b06f7b88653591dc7e709fa55592e3a0a7b1755ea3756d9ea379ca23df8f85011f58ee4b972719d653aafd07ed84201d28e25eafb24b25435a1e99789bc83c40a44833f4b72a5c12a9691ae1d21e912407fd8effe9577982b953de59d28f916102baf831b72ca40308a913003e8a8aac1fef2d45b083657e02c08ca319309f8c546ba88244de99406e221fd38516fd17ce5a4a94696c048179f53f8355d5cc9d111c0348272e6f0c7bf536fa2f3245dd0c6884cb2f3add3ff999b0aaa1a87384de59d4db90654622d3d4773b4cf6f532de241fb1b173393442ede78b5307f64ef9d7ae13d72bfa6e8f5916eb52d7ad30c082fc81a44e37a9f72e54e753f8978dbef24b72672b73f79fe9162e1637c4a576d2cf9104e7f4355067a81152b8dccc60a5cde7b724a4753e2ad3ff0f7cf03d38f92ea38aaba8e954d8e17634f1911846dc6df007207b3cb4c81072c01bb54d815ae5741a91a684e544e6a33fc8a4acf1ab94cd672012477a4525efa7ca0734a66a818110a5e5bfc4dcce717b4554747a43de52e72b1b276d726382ca090ae62e6a9dc3e26c2cc8474e5a18424cf2dc7040c7c0b63528545eaff57ce46415d6ad45dbaa82086b44aff4c47c05c6a1f0e3ccf5d3272f528a8ba0ccd0e48d85099928abca6a46c8a034123994a9bdc4d31740b97c8075493503e555edc898f56918e622adb1e4a05ac655f4a92d427a30dae15d4bd721acc9038cdd996c5479791eb339b70f60d0592349c0174a2884870b234e60c729cd54de77a776ba620b1e32f83ecc6f9c9832e1a371f8e2d183bc93a20ce84620341a429492e0b28c9430e0931c15610bb27cd9d24fd2c28460a04b5305b9f72ee22e8bfd4bbc9ca05987c54c4836cbb930cec2a918f50bd5079558568f32a115a2ca44a98aec69411fc0058054bd89abb8999215125db09bdfbd4cce8af1872be132ae4168dfecd5e6f891e8dec0859936b36f2348081b0bd7052729d587631066b562f0b60b04e3bc04d0d37fb15660be093b37c0d99afd7a32ea96d8f2272d65779ee7a795ef4fcb787e35f0d0a2c75bb817921d4862d51ae1b6ceb59fc72314a7a286830701953c422f06b97ceae2d883910ab7303c89462adce2cb939726cdec1aecdc895a7a9b258e9e5c139e602a099f86a821ebd24da202668e9f602928a7407d6535674faf4470ec639964bd01e020dc80a6554e7af493ef5f7e97251e125afac56e672f716265532c6b1fcaecb8a67636f7bb5ab341b292715e672b02bbe080fd904bc25f8d58bfe840416d060c1f4232372281bd5ce7dd2268a6f82470afd9944fe49959758cd0b7531723bbdb80c0a8204bf83fe474f875aad72c48371d6e1e26255c3d18a28e36bc82fa551a86bf1b14d11026b4a1e1850a3724dd52caffec53667b276a59a82adfd010670cbd7e3e1c31f5f1a4d01d36dfa62d6efd8bbedf0306055006fa3e6431f802d83b18ac6cd8668518658dd3bd2a01201d09cf7e43a79cb8b4d15414390d3b646520a706b0b16f8f55cbc77a20ea672a692d5f78b3ec304dc52fad5566bd53bbe78f10114d0cc21194608f15a4e1f72eb03f6ad0b6cfbd7117a72e1661148629f73eab69a0fdbba60b8ebe59e51367289f9c466ba63d747decf58ad30d3551de30e13a3b0efd1d2b00a329c47b2505d7be0ca9404016f583432b1992094a3413cfcbc09d1baca2a76c4f6d0c5f149722dac876645bde2fe223987f0cc32c405c8c14caa3f11b1da68545af166902c4921d6e8f2dbbb3f3aae6ef7c119cedbb813cf9578eefb94a5553c5dfeb5af1672946858301a136251d33abc05e5b9aef4df48f8b87a5fd899704e893c20bd76728201a56e1f44fda4e1a6e19d3e1a7369094723e4c280937166f39e38e750bb2991436ae8e90180d4996bb5e5b53db68d65fe50f61bd6d792e6776f36432267128c2559c96965e99ba9f224f0dedbdb7143a2fdf5c5c47a3f056dc6a596eabf1f53d4618ed565028f14fbdbeaaf911103a58136ad1d616dc2d11471c8ba6f7534298c6c1a376eba1daba3d2258fc8f80a8388b4bb134a5571096064b69a291d4f59666a9a0989db3ce91109416f32b0401039be6e23cbefb99b1ec4b6726d1b729360115ceaab67a9fc63d676e08dba86883675b4c4e5ef1c4a183325609a3872511bcb70aafefa0495ca018433651c1c673e96f7fe25b62373d3841e47f29b4028b75762d3a4c3ef897638ab816e139c09a16ead16851c0fb25e2fc643418e72c237aa533bc5ced78a4a29bf41be5fef1e897748c35441ced68f1de69f943f720f2fa70e6c6195f2a959f78dc2a41b7686cfba5a5592d48ca6acf1e8f7594d7244d6138a575019a2b9acf68175d882ba844f4d2fbcbb63c063467544f370b3726d234897249a201c46d4a9eb916ed95914cbc1b7bfddb42be302f2569109ad72c3baef6675e73c6764c565d39781985f5bb77b855ce8e01b218f9b902fdbd872e26565f5abf37cd01bf62b501133c2627afa293a181dc5d2635ce7e9845bbd72a33909c441a2e705ffea215bcdcc7ea32de66509eb5af479b7817179d3651a72ce2b7d41731b863e9ec10ba07b13eefbafd21510a5b0ddc9e1d7b39980355209fe03b7d38cbe2c7e7427aea40e1befe0794b7ce58156a1e8e552ca097164f523a045991f00d81b8f0dc847cab3f4d01c2e2ed3a15616a0e81cb05f60355b5f09444e6bedb2d7c1f444e87462dc5fe9c5712be89844532fb24bf530169656837269daef5b70e61bc8833b6c4276354bfac91ab1b44dc09c2ea42510352215c5554750a673525ef3630fc9101958f78e0f13d506112185a75b9af05a3f10e8c872838ce7c45602ed927863d1591c48157e5d0497abb7ab4fb04cbdddf9c1ca8d60d09558eed86530e48413d3ca84f0181a1ac1b10a00ffe34fba0e5e90164a30226beb7cc092ab2d3d87e4be906718db5369e6dd949fccc128f4a84bb8e031ba2b7c0013b09345f40f5c07b7058065813e2345398d2f169da78ac88e3f910885725c085d222d08f703f108544e7a6767f5c80ebd15085238ee020a1299812f4170aa12c409c42a205506f5ffeb9080277c3d19db24a74220a9d49e02238787b65f570efa0dedffc87d95219ea770e2cb887260a98659ba20660a4baa4150ce980599e418ca04c5f04ee3f6bdc15c1667bb28253cd7425fa725ea3688d09bb4072a981c1a9dcc3a1f7d5dde6f28499023f031e6ab1f11442337a63c42e237429472997ff1f9a0b4fcf0e0657b77eeeb6712d845ca4f23a3e9be59b9c38a96213f6bfd0990945c48bcb0d01be4b2710783587a4214b66ac039c831c60ce64d729e72be92b325a3c0eef6558584ebac1d232ffff2f58365a8fd61421ad46e686ebd7249f057ff98976ffb3ad8e76606147eed2f6041e868cf44bdab45dd3effcfaf72fc2fb924a2ad5fcd8c2805109e7691d3d066dc32bbfd63d817651a2aff2aa272fabb4a364a7e1b70aa32f6c9d5f5efaa83509e687a9268d54fa6a8627dd6fe725c2768e579e3c0874c8b520fa2da3601c5b26b79c9291f0e648c4a0e5926ae45510c1dabe9acb2a29254c57a897c262b0474562c4a0b85ea42a319df85165272abc37faaa79f018be8cba08a9d5e6d52a4525f7aa1f7004908208beb2968367217d425ae4f8e40081d24f55d02b4378e04596283e050a1178095b566a27e5672d45756b3da83eb83a44b9dabcae58ee8f5d1ad82d8f1b318a2f7b0fddf1d4948dee549e0caae159302ca07fc72e6332667f3348fd6191ca6589e0a4921433372a46975b8263a2b48528dbb910fd773493e5b4a9761f5dccae37ae77908898b72d3b4dedf247eddee47967f3b493efe7c47f2dac46444287b69c8a19801aa8e7269f6a2185c3ec4dde83fc51dff0cde334e60feb1a92edd5b54846bb3c4ef617226eac6e05030002dd3528b1e99a76e49e725f3e4f52ed34407c497b2d41cca7291dbd10de51893ec50877d8a721c4a3a8d468b85dfd02fce206e7a6b60413e2485c0d84cd1d06818d08870d360133e4821d12a7367d97e4c8f4b80fded51837201f1474165193674e8b4d37decabf52377d33be1786d8bcb4f3ba8bf2fe36619868865dae5726e5f3c353f62d7809847124ad8190fa024c65225f253a5cb05729d7950c9127d0c5a121943e862656ebe71ae274738495e77e6f94db8271bfd02da88e5f1178720932ceb598a961343c4c233b4ec7661dfe149776e60b2bc122061d0e7126c5e0383a734b6fb6a244b92825f18794c1a5fca561bc7e29bb55a7277fe9d2a3bbd4b9266ef90bb625269e1264b4a0f06001d2f34892be4f297e972895f86d95d3af900b7d309aa3ce02ac42b91d70719ddd10cf5e3ad878523b53b4c0d04aeb4fb384e8c26caedc756d405cc3519d6a96dd88d8ebe169bdcb8df2703c56d7658b57841fe5c409ca500683abb0aec6686a328f28ef9b08d25ef47721b23f5e75d62426aeca694df6de52620bc81954204f11e9df8a4469c6518347276eea866ebd484d4d4c1a465d1d46b7d5f5973f8276a27d1a2cfbf241d64927287409ab2a3b2b64302157804880c373bf3df9f0d5c47279df4563b97d9d7717265d2328bcf35520a40e22441c8b19b58b3c2a457ba005b856ce67af93067217255074683b8e75678a9e51d5717b32624209a36e31b2412bea166377ec8e6113c78e94fbc4e96afd78df70cdba5a4cbb11faf270e6d25966336b457fac9e2a66af4a2f7d808794b241f1ca0034ed54f14a019069ca41f4f03afa76ddfd978e572e9e8dad010cfba462234d28eea8cfe55f61293ea7a5267af23cf91777baaae21151f6a7cb3660acc05182e1a1453939eaf3e7f674f5bea3e1d97ddee72937a72baee39f63f6de7efc1b243a2572badeb06b1885dc74f5842fe760a0aa5563872585c4e7c31f638d9a5dbf2da4a59a3aebb080278ada03d2d6f8565343edb6c18b6d17947eaa8d6f0365ffd0d347d5ab68e0fc6dc609802febe3c1a42c43fc372a702190cda2c28dfe07f6653108f9a63555eecc86edfbd226baca86e832d845a73a1e05e0670337d30b67b0572f4e3fcd170578551223cfbbbd13a6809337172312f31ea86f813836d48f2d6274519d067e3f697223c63d5afadd3087f558e24e91e1d0eea6925b7b684362d538b788ba4123da4d44ec2ccf87f4b41d2028e72e732ae280f82d0a9fdfacf444a6b4e179dca7286997047ed2e769e5dee658b5cc8f0684cfb8efea37d85d961101bccff1fc35541db8e0350c4cba15389da2272c6c4b0fae89aa521f5048af67578cd13aca79e4a1c5dde1fa941884f957bb672536aca9f3628c462b88a768166fb0b80ea470e55b1a41db11284362757afb87206c1199adcba5b997c1636497a0fa0835b95deb0288884f79d18e23ecee7a87285eb6f3fa4ea2377d7a7a74604ef6049d65b8a715153518af6dfcb3d9ff49325eebc2a09a52cdba115501c9303f3dede4c39bcadf158c86dd9caccb266e7802fd94f36d8dbe7234183667a441d05c30fda16e62f9e7c73068feb2099576ba172685fa97b71394e4f69599de4ec61e619a4fa37cedd1f5b4db07b7a5f6489ec19c03177494293912ae630c7b4dc116cbca838cc99d1bebae58a43af24bc2cd35ca1d2f4ff4c313916b0c905016db6d1ac2c1c8d746f7351abbcdb482a7c3c144cd9698004617dc0e0edb0409e0e0f234921cfcbc6f2f94ebcb34ca350ab1c3672c5803f86e805cc0ae2e06c6e0410cb49569bf9c44fb641b1f0572419cdf8bc660ba903e5749c0ac1a5ef5f9d538dccae5c048456df96a19a26c9107d729b126c9dea7a492897cc22bd7f58d3a321639869dc0a3273125f2ba6e26634c0be6b72cd45bb730dbb369fd97c6d57e2ba992b37f8bc868c850d7b2b6b57879e533a4b269026304478e72b009ff59e4216e3c680226938264cc359eee26e2c1fe320729674675f4a543d79c2305144e5d8c2738e0d93263a31e6ecaaffffc5d029c73a070a0d25b01d1e2a0f55e9283fb17e366d78df72cd613f5a4b3f96f46f231c721796201d3a4ae7a7e437066673155b90b455b5a8ca345ef130d3007418aad1176b7a213b857854194d059800a9a80088cbbd65251c44d6bc466bea7c70137972d63992f3a3925db392fc3db28223e9bba73c97a50b8acf96c373ba3943a5d172a413ef0b75141aca1b6f6cf0a207a9abd90e53bd0a66851cb3981585af818372df3e17a45be49bf3ba7d7f93c74277ad84cd007d5621b7e1a963688c22e84972041b24285ef5cde010c2fa3b3280601054ff833963b302e966611f0e4a37a3211b5aae8a117f0139c678ac7eb41aeb2e9921a6e6372d206ecbf67ed79624d36f091cbc2c76d6682993ecde912e158aace5efeb025ac8e3c547145c1271d8940685314753cdd497b67b177bc2c18e1ceade1bef2fcf93ab4ff11d5640cf364d15ececdd872c5a5d4149e8299fe0b1550b29b7a7d4d6092cc3f02bd6b314643372d15aec893aa88c4e54378f3347ac0f7c2704a0541da9f09222c96e2cbd5f1d3739993da85cd61fb3e5c17228c0d4b9e3e4dea76c6907772e8b21bcef4a835a725c6fe1aed00e1eee2397742856ec1f0395adc4fbb9ded871080de49745802572cfd7d65e42cedb247850a018197b652fdb8728a1178dbddfd1dce12c8bd2556177a770619efff78974480e813647dcec943b8064c892819887d947e1a28fbf723624f6d5d2ef9b1d4168f30a834664bd8a7bae8c92214771ca338e6113a806720b559d163a70bd60df3cf830c81fe51395893b6dce41a32a933504043309741e355a4c57f0305a65217617624cd1206c500f09d63f33409e9a69644361bc9b1fd15861cc23387258eaaaca34bd4221969d93d02b1e4e234f6e0fb169331da35adcb586559f1d4e19391af688e6abe3598bb38ec51a92ab465d3924947075a1426034f335fc3cc12cc7385020c09e7d329c266e2a998b1d6320fe9ea21905da64c17000c21cd45ccb59982c3ed0c37308f60c5e3b1bc020c67c2dc2c1c7d1f47290d82ce1f5a1908daae85f6125efdfc4211c43f637b4c5ea9f9b536f9568d172eb8dfc96bd05f210b9d502bde8f7024e3a3b5dba415854316c644fd164beca3efafc88874c078119cf8ab81822765a62ea15899ee8315dc5711b4d4ceb487b72f87591290bc02b767335e84fc890f3cfa328047e687cade2c890b4f86d20347258c8a231e1df483954f48069f7c02b8ed1d3f95b65586df2876b1747d769675e2f47ebf81d09bf461c296e380e4a73a2d35a78530d8edb6d4a09d0cff2445f471448a02c7cd92833fb59083ae76ba1b1c68aff884bf20af639cc095930a7634767fd1aa4a37a9b32618cba7d72b1b230cf7b562c2f6afe7b22b76f97d0e8327288230129614ccb43b52b73cefce7c5b9f266717db5135b9314f1f81462cd3423b37909263e790edb8db0a0e4b0e361f02d5b6e4657a5d6c9295a059eadb275722acc6e972bf5bafd7dcfdf45a77fdb510de6c167648b0cb5dfb07d0ab2bf6b4137a9423a69778ece98c16dced90066ae049ffbeb38f086bc4e796769546ba6688a3f03c83a1e10e6a2e84875c581b48fda54dcf16445bf5047e6e9682cd25b672c681a9374fb5f59f4e49bf33cb5d5ebab772f0385ed9a32c99e3fd9bbcaf172b24934dc792b6aacbd3708ae45e1cd76d2bca9b4740dfd0e36d32bceebd0a572461df9bebd67947205141cdf4c9cb9fbebfa133fb9fd2c7f7fb844bf7b3a9f72ec61bc60e0ed41fa289d8828950371b17f7529524d35f70b849d88218a97ca72585aa5d5337a72362f074a1fe2752136a8b07d0e4e8c4e7f4ebd9151f776930298f793b13cf0fcab16935810059dadddd31bbe01cbced490a4d56f3aa845e130e773d52b4533e1ef28dd369ff1c90c8cbb2725ac458f3b81d378a4f7bb49c072b6d3e7ec61bfd991d7bb269e839dc623d8d5afe8b566f98df4738cb7d8481972129292c15d75d27651029d0f96d5b31c781d64021082b45482dd818cceb5b672fa466ee73a9037c91b964e2bd6660b526a49a5ca883f3e8185b1dd3072ace54a2e6f452e95122260df6f266ea56647d9e736d69bd5b9aecc92a2f51758507e72b323e812e44849f2d50771da012dedb3ffeb6b79542a7931c253ff7c1487b37254a88fcad342b7538f8c05043223a53a88e3d5e31850e5ed82fa10a56a58d653393530c148429f68210fcc5d15aa5e417c14f20a5063767c8dcf75a98bdaaf4b9a92ece88ca5432a6d49e2853c22814fe6106ffe4691522181c4adec23c8b1722408c7800571dd25cd807346459eb15488f57c3ba32ce84b1b38e9bf630686652607b9bdd4f80e93e100447ae7048ff06f01f5fb49aeca1d4fb61a739f60c572134ea92bb6ad02606423d5a57550a28c1f5d1e90911d4aba194f40fb597c4312aa4d5b9dac78519491a121e7cf018136fafa861f09327ab26d77d4a02163e40531e677d4e564612a07bcc2b6c9e0523a8cf68675af6c679eee2c2c3bb4b84a6b047a0290fd5197c4f8fadd7af62ed2b50a20424e7d899aab435a795cf75c714fcace0287cc78ae2f65b3d26d3dc831347b9e548a24dfcaf3f66e8878ffdc017242139f1cf4882bf439de6168e53dfd9f7ef94d7cd23706699ba7adf844d0a7722ba6a9ad5f5afb86f1d6cc8af233dc25bf62203e360e5bee645293da810d173289875b14499b631e957ec28c990e77867e5932a480a76a1febc8f3d21e3c3572bc1428eb7996a1dd66af6bcebdfb2333101ec6f1d6a7d8a3eb2a38a4e67e9872dbfc23192ba6dbf6b918ca050d212ace14242304d4d229916bf703b9b7400f487e6bc402503e6f107afe1617b456e51c4c7704bed6b9cacbf9b6f376cf5fc221a1686085bbfb1c58477171050a3793c6db10e03346fbcd3c8d8b869a5fb2c472111d7e54481a2236d1f2101ed40fd6a5a2723564efb85890cf91178c0975737254dcedbad54be977c4ddcaa060e73b3d654f69a9eb5dec4e27853f1c7cc8a7432592e7310f64293b4c03f3c9eea55b42e0807b454120010e08d7b4bb10fd496c884c370d075ce8669439fac4b9ff13f902cdb3d9875c687e4cecf12e40955a72765bae7afb225bff39994abc558ce93e31be7979c060f98f6ec48634f3f33472774cfbd552b4f539a71b41542a0f809e7ac9273d421f8c77a2f9b181a1079d72e696b03c1bc4d0ae5711aa8da6375b609b987fb400e9877ef5012e19b2357e061a7755caf35c718458cc8f69acf4f3a0450f885aa537467eeb15e150de5dbc2592db01ceecbebbdc5798f25b7faf7b33a72df743ce63933d0414468850e23d0b53eb6583b383db0ec711846340347256280147c4f633e463549cccbfc9462f72d9f6e5e432765f3409ca1907e32c0fb11d5e3e3fc3f84651c385e41c4fffbc4aa8d1345adf55f3386f1228ae99910b5200bbde965624256f65ad10ff33ec8b72f3b4806dda53c87f0e73272a4bc942e8df46fc3db1471d61a61c4cbe90483e728e1a72ab70166e1155c5db6176d9c3784b9fb436e6aa92705e38a4c042b2fc72da580c0ed5b363d23ca236fc3a976329d8f169a3f812f547922850edd85b7672722d84c884b099c5ed688d264873c67852f31d67d3850f3c9d6684c29aa7bb0ebfb45177503556a4531f266b0cf715ccfbad0edf18ca13ba09a852e4916e1972ccfe04a27931b5a9e619c5e5420b431c3b907bfe2cac007fec4683374640b855f8b53bf26770923e6183b22715e0c5aaec4d18ab9c404b9cfb979cecea69cf729aecdfe73b976bb6b7ee6c5785f31e4535be03a156c1be1109ba8105f5a4f67245716e948a7792ef999af42dbeb348736aee4841cb84874ea6745bfcec0e42130afdef873d87678bcee08e97d64cd30e95388a7051da80cdc6ca1446b7259e00f5b7e7d61297dc411c6284bf61b78ca6d8beaab2c83b4b808ddd7c1403320b72e7eba724287eab5bf1e4965880e5715eeb96157dd2162e7c585de96152d723039786c5f1b87c46829c6a1ca1626514d786e203dccc00fc04b510659b10e19672c8986a5bc2f78fb994ee9b814d4f083be3769e889d295515e8981fa43552ad72a2228b192e8cd1153b9848f33930df59bab760839aa0bf764884e073313401721e67d85d7fa424e3622d3a5b8192017f8d0d32204cfc5c896e474f7270effa72414e07499ce9e79f33cdd06dff90a1e4a85c94ae29928957b4342ba275696f72b2527a8868cee0bae54a43150a3133ec9d2a26e6512ad7b0dc5a359324ba9872abc0f751aa8b1e84e09cba3648a5f002865a1fddbfd0d5160b09b81da9ae1c72669ead6a6f2435e873828a7d5588b5c679714bdb0a221d1c65d196f697539872d4583573d23f1027bd290d5a1fd951a75503e797f0b9c7d1d2a061e07f5b3872e8434450203a7ac49590cada4daa5c740ba2d3efc1a3d09eafdb3faa944fd372077e09d2bfb8cd9c2ab8ed8082a0a6e1310a049e6b9b1b7164373f6a79081c720f3951a27c72dac9e24d889530e080a3120220d9326021aa5fe26772d6f93072a75711fb5fb46aa708a2cc7029aef9a1404efbd561a5de5cccd81fca5b780372fb9d54494f8f926b7f05b911fbe416a2bd1c85607b183b261924781b5b1d8571b81bf15f1fa16bb455b3ecd94aef4c75b090fd0136368233fb15ff9190295f72d5e974cae48255ec55936c66cd94796ad5d1aa0820aad59bed766999b1802972cbaec14d47cacfdf405c8b7b8b49569308a658279cebcf9522e64f495931ed7233d769625995bc417d1f87821acba1193cfe2220f3404e9ca60a2c835697d972fc012d89a15954bdd44feac7220942efd1117abf4ef0ed58e06e5403b0cf787248638b3da3fa3f963912c483ab01190e272d6a4f3cabf73b80eedbc30b2d7f3a10dcd17eaff1f390d8851640db4921954a4a4922627bf03a90190dc0d5007372caeb706b1b68ad3d02321013a2a94159837d24eb134f7d868b414413fd0ab92cbde1009f501581c75ada9b13c316ef6b61884369a835114d933f1485f2827303c894812ab0c20d2bdbf463e6541caf9a455e14ec370e954bd3b7bf261248f0727784ab3baf9b9bf6614cdba95ce15d6171ce90c32251416392099672f8d4e272e43c78e71df30da726bb4a3b81fe6fa8ba36fe19c9a410b345f3c8585eed5f02560b7fa6f50ea5a2cce0034339c57d648a147e0478a2adabf152bf0069108b3ad96e541499f82525a899051f45fc728408773e727185f860b374eff8d8d95e5a3230d3cd4181693dd7e7b0e2f416c5103ae11691517c8d1caca8bce3b70bf5562fc1cfb54dec4521bd0f415181ab5af0ba40ef4e66ba27550cfcf25a897609393e36f12ac4551ec02b400ca6c805147edab5e6b8034d8ac83d5154ad9d0e9e720d68e7519f5e58133c99ba77cefaa39603513103e860b5514b7b439312c92c72c9853e9b08508050939850cd59d4fb5f3bb720aff702bce4d86cb162dfe56972bb856bba08f8efce2d44d8b3601dfe19a18149e35e005bc8bc0e5dddd859be7212a7719136192eb742babc60f3bcf7522568e57c413e9714127f480123dda12d4ce27108a7fb949b0f2ae0a80a2744607c53fe14dc1e89d646aced6e7b4ae3725bede5d772660634ccaf06b0c2b16b66fb200e83db1699c5005f922ee1812b72363d38c5b63d52a12e8d4fa071c11513a2009e70f7f77b48bf592f8a2ce89472e32a2720d274b37016787f909752c677b5157f94c32a4ab91e381e30ae20c741c622538956c33bd8a9f0c1e31f56b3183f4cd69eb270c9406ab8a75d027d47723eaf46aa6e5ae5e052f0662ba408b12262d539700fcbb433205c0d1a3d64e46e0f95405b9f7291dc377dda86c14e9e3c684457a48a8b480b12eb2cd4fd1ecc72d4b0cc9dd7993c7193b4791a03b4859b27510d4986120c1fb9576fc023a142727efa98b3f2da8539a7d4065cc38d09a2d12c141b113ac845e688635a7c3046727834d23a1cbf8942ccb97c11cd8dd178ce094753f68c8db7bc972ea393fa601333b1fd19401a32239b76b464002bbfb89529878e544a2c8e71d6e916c7eeb7163ef58254ceecb3b229cee96c606003b59f260b575029904227e4cbe3d791ef1adc4edc590a4d5bc9bf415ecfc7205a282ca043e088f51cde0a735d03f608085cf14b3b66df60c9d7c24393d056a9f136261f4665b58e08832bd55649ea37b872f37d36b279e6ddaacf257265c182718c1d32a17e03e2be76aad7bdc91d2cae3a68d54c1b141cbdcc9152bc88c9183479bc6e2c6b081d2024155a18443101ed07bd68546af998a467c37af308452def76dd810d98b326f595fa9d299a3eb13619ac8845a94c23aa344d0906dd0f2a275dcc2a04ae64ed9d1ac74be6558d860d723163beff490f90818cf7e62c0ab9450e143c5e9820423118d73cf5913d17c93c07002e7c5153fc3a27f3ca67906a321d175cd1c92ed41ca9c2ac749e1b35751c06238b0b8784c38bf2b8aa05ebded9988dfc2ef742a004de8eb6b685e717f572e80c83e625a41a0d0e717e0f19c683c9c415239396ee2bac9716c6f862e85a1801d210b456914d1f6c4464016268bac82cd541ad689a7fb436e333de38a93a5237aa7971d76536f678259fd0a7ba59d1ec3536deb9b312f3d6f7fcc6a186cc72b5f6702a5594b19f72961c76bcc370ff943cfc5c23b0b95957807bf427b8da72978d51be471c21de8f98006618833f28ae83b425e1bafa7f5466a6ddbdb70f72a7256d99ac72d6ecc3dc9528d1b9ea570d7fe7c45a7493ffaf723f9646093872ab770a4c2a4a75d410ba5dc5da06615e988f0ff38a0634ab9b9f6fa21a25c67233e89fac5eb6ea09f53198084fe60673d225a115f3f1746eb9713be3d775c172a5696b8607017f6ad2c6c9c27091f602965a5424c1789cab95b0b9b47da35272fcee78a12042e53130588ef1fae5faecff7a6034d24e6a95001291b4cb0417180f19709a3d8b4b525fa79cbbae6cd8a7419949f0de33fbf141def46f54019b4895875ea092fe8ca57debfb8e75263ec48c1c509c76280e9dcc361812b6392572ec0dcb8ec516bcc5e2b8ec68895c1b2e5a213b0989929b5e008a51adfa30f46f201e9c673b39c141a6c9e50231e7069087fae6ccad23a6cbc231fd20d25b5036cb2330ab5d384939fb52ee27f36bed573162f669bd80861548f3f61f5f2d2f7253a5eff3bd1a9097a1bd30378dde1652b5ffd8e7cf3069e441d6ae47fc64fb5f350e852f7fab7d4b0c61d056988932f2eaf6c025666300a3c32c9410f0eb7e727b4f6f64eb92d8f22963f8eda06241bd4b800a3e87dde0cbc941837887b8393f476c9e578120350f4c17297221c6aba40e301a083d67f865e862272063517e72f44d815f8817438a34628e79a18252cf561d42847c3a7de18990920e4b7b12723756a3d635f0f0f6fa098a22a1876ed37c8401a8d18802ab984ce5c7b4a6a172512fa38affc05bd8162581e261e1a5d2186ee4cba5283b349483b4c7dc2ff17267b918241752c8e9d8699bd2d9c493fb6db276bcf41df6bf33c2ebcf59daec72cc07bcc5e3254b00a93b92935dae6a650d591d058c1fb9e474e36bd5ee2ad7723bcc18c93b47b928774cbce1cf5a532372753e2e82eebdbdfc511f105e7f0472ec5e5663b5209e6943a34c4d73af14973fd472f1b30fdc54d79260ffc26c3721d1f8df37e2f414085020814342e27a58c2403d45c5126d30569c4ad127e88772b63bed55e82f3e108588c3967a163456321542a2d303402cdd9ced6d915e1d56e0d7b177e8b23f5cb664bd69bbbe67cb31dab0efdfcd2110216df2487a758419a8b63e4dd58e24ea361fb2ff3d5ad0080b4c514a067804b7460e5ea010ec4572dde21a67a35f951c18a2bc6b52e5157e3abdc1701597559a5bd500d71c8c14729d9cb9dc7bb1f310d7e320556050e8e6f66ded8b89c84e8ecdd968f7f881ba3dd59afc324fd195d1033d055fb848a40b6daab0b01b6fec651768a61369f20636f800f3c7cdb371607220389ceee983999d78633622ed22f9c8b991a67e2cbd5399a8dac27dda3ac34f27e4e229eff7c793c09ababceeaa8b870fc00107471d7207e69be0eaaca3f6d1963fb8b87d032296c23b02a7ed576bd97a8c0149cd6b3b2a06745382cb3482674f101c11fd658fea215d4c3664bd70bcf6d95291735472290f4ec6e2ba25b6791467e71f4d4317e408068fc3a4a4c64ba19225a6d566724c75a42375d5db06f43be698201d8fdaa4bd636d9134e2d937f60924acf82072a7d3120214875a778f97a197c3227f01f4c8bae5b9768ddf250319ff5e08fb65dc4fa0ead6db7d5e27ab07f09c12be147e7b9f30930419df106d6a1bdc87e92e30fe4c5ba472935fee32dc13cc8df8db58f8caeccad5d074b56c504075188172f349a1a5299972519e0b7a20780ed79884acbd625e896d0fe8cca96adca301583acf9b08be5f5ff0fe68bcfc2a3cb04ad9394dbc28fdf56f171f2ec50d61595efc389d6cfc1c4cb764f8c9af701482ec8832b2f4010d27a0b770f23555a9e82bf395c0906cc630d02724dceb99253953b407aeb08e0d7cba22a570d890fd44720979f523a272cb66e35de65902a183c8320f7c05023918daa5b3609f1eb3656a96860ae9ffb81ffb03f7503fb589da68836e2c5c6d214567f9e85b86bd38257262a4315d2df229753236334c9b6891afdda31597dd54627d2f3c681a41d14c0a084063e86beb8ca42a2237a09bc3e7061499ced5fca31fafb350a5f04c413172f00107bb7f69678ab14625ae786208a8894d8d40d4b7f4483ee5c95aae59b072aba25becd77f71276ed03a5253d7413ff1ca9ec4acc5b7a36a98510b4fa76869e56250f9edc093a029f2d92a447a33f6f0ba2131bb91c8d4c6ff935eab70e5278f15350c3924b0d5e5743522a805c3d7437c59c54578f992fba8db940f56893077809779a269a23c1d8b910571dd4eebab84accd5d806b4d15aa6919a71a4d72f108f9ffded3970667406e7345233141a1952f90b01f224aa07bcd1e9ff12247733175d2751d32125fcb70469014df798cb653021a8c9a9dfdeeda593d29231826df96be5f00e0158ee01002e9556ea6a458c1de34b2d7f2765fe4907c8f7372172e7fd6ab9147e65e4fa3d9f33cb0d08fd80cb1a6e4c607f20c704cfd03da615911cd46c6079d4cc570c127d09c70bde9e0034efc436e4f1ee843baee707a72c123cbab847323425d993120970c94f7d906edee09be7ef91e6de11ec04a0872e4ad66c4c515d3f4a6cc80cf56af2c78ac65a3fc54593f02a7c41103a9268c19377a569109ff836818def2fc62f2e89fe65b7865577b316d36abf1b215f980151d6e7ea64a1e9bfc02d5176e1c93dc55907a6e30e588196b0ff96b38955d041daa1ec35446e01fe1d993b11a3d86e9abe17964de764fe873291614ac641a9c557cca663b73aae03a274cc7bebaf55dcbde76eb2fac9f4afd5ccef92a5e00a3338579049d1fe058db14c5b2eb6b2501e3fc44fe91dd00766c88ee226ebccba272e97227cae341fcf86a7ca5135a0d88883e81fd2be2e758c240fea48848407b72a9b1038d3a1a212bb7c285292ccd52eb805f4151ea26da1d3d0817628fbf6c72d44de4cb34b91b61d23015214482ce79f940e361113545c83ebc9858474b1e7228b1699171fc63f3a13d7a916ce2c78b32c8f4f215e758bcc2525ac4ab8fae6cffb87207e4563fd0446aa60b6ffbe3f516630109478bf7483367965baff46072d8e7c05a7444a2b152f4f5920e42625c95c534afa3685a6c7f3b61bf58488d72d298671797b2e12b26b2c8892fc8e4ccf38358f404efdd42fdbdd500f08e5269e50a3ff1c67438685cc58e363ec7fb377b4e600894af4ba72649d8854a636d725ffbe5873651fc926469419abcf48ea9c98de2b2bd66bea54a8b7468cfcd75727e6adf6028d4d496ac4f0131d1c9dc889127aaea09fd42ba066025438471f211601ae07cfa64a4102426f76ed3d7433a2f8b7c23f33cdd2d8f0d6b18aa15ea486902b8fef7748a99f34a5403db668334b56b060f82564c956191f7e27e20e172e8edd440132be9518e069d8a14d09540d13b52efcfaf339b4cb0c39cb0ea22556c45a842ed970ba9300b90548f72b4bd8448f0e658676a91a3289d1d4213fb5a148a2396f07db28a1decfc2dcbab935d6f9e9566625c360a7faa931733b20e0d51bb75e4a7f8ce00fbdad4e4b94cbc2c95c688f753dd4fcffffdac57adc32b154f4e9b2033e5fed1d1a643d61b200e42e5e1e40a7055f58ff620dfd1043949720db645e9a3d1ea407d5e52c17418f300da1765fa387c149edd0feaf0ec8bd1723711fcb1f8792636a4c8bff7705aab631f107d5a6e9b8a78c54f951cc81b8915f5e6d70e460c4a2e478dd78e48d249a175b5c0da5fa8cbca5963b2287d335d0161396072f8390c66a8ed4641d3bd1955fc4bf6c4bc0729ae63bf7afa2277b324582b20be3d525fb3de244058b896c90948d8de4dec3ea7710c805b2974299c69dc26140c466b5b876c254869a2b963f224840d308560ac3d791cc7d0170ba872975a1394b06e1c73b8b77c857378c2250a7f5fe76bc8b9d8f84f737f9d265a729d77645ac0a887167f78adc20cbd8d8359ca729b792214277df11962d7b717431218a9feb8a3d404917aa4ccb97a6faebbd984ecf7f0a54e5051c97f7f2e7b72921569775f8ca0d42f95d194284622e33a11486929cce0ef4013e96a632ef96ad2b263f44ba2e80a87be1479906d06d36c7291c63bfe7cb9c075e22fc5f08b44f2518f74fc15a9c8e73a7c3e50b2172621fe64e8289e340dd8b66444b7cf037245a4689a576f84fb086bddf728b17ef0d1c7a9e919ca877cb246bc9e3ef321052e2690d1b503905af94400994e89ae13acfa45dc8cc34d52a76ff35f27e70572d04f906532e37be987e24bbfe4e1f2960e2fc85861c030398110da36b11bfa721b6c34c98493d034132460f37167f88784ab57894b8e7a61e41c55c80ea3ee728b26d7d28facdb1d63efdb77fba5c0b6e2f315e882522454ab977513e4ba8e724b641b9b4fa7521753edc4d818e582ba0b0fa3d3b59944fb6efafa6280690c72421325b68dc11ac7c420a7ca3a5eb31b4823439cbc850fd429458e2f617ea44a5019f105b3cc894326885c875d90d6b5f2e39f7ce52f350c5630a73e1c288b72ed23c1f757135159fc001f8b29faac14b76308c110c97093786f89bc839d4523b97bd5cf7b3cb9f939ad7838a8fb88e15a64050d9944c6a6fed93fde91cc8e7287c158c7348b0d59a6c4823dea5ad86b986369888e993ff173f2c50451569f728f52a5c487330200d13604cc49959bc92fba7500bacf9c88b3d567561da0cd72e0d38df038f973a4cfc17c5e3462929d17d534ba66b4ba18840b6a05c1a1cc728f80da3f40d3a0d7e8ad76a4243459a52a36cc617f634838fb145b2bfca54972dc0c17af3060cb036ec128011eeb2738e765056971ee638ed0f54e3045201e72940b2c39fa9f46c63e635b66f20cac57cd3fb376bf22003ea15938825bb1eb72f8ddcf6eb4475ddb64cac414365772595d4e1b368c669e4702772f875a72b62c02f39a6c0a84dbf619664236465fc70bc9f1f519d89bc86e5b62a204be3272723d684d67ab7034dc6d47fa0254deec84c17c12472869c9bdb97c9850deddd372d7214bfa7a1161593425cb3a4549acfdf5d381f50f46765f95f378a8f3118972f2f2417e82ad6e307efcd45f823642b794bd506960c6dc7fcf56d96a139f7947fe97dae6f4315865d152d0fb51e4637a419727d8eb287ca5f458a11ba849aa60325d7081f063e8905721e2ca7f0f2493b5697154c836fa5d463e6630fbc2ee72039fbc39ce3ccb082a0c445d127f69eae135dcf605e735494108cef68ff11f7245b5f4234c11ffaad2ffa4d2f496a9a733bb49450a1dc99c9663367eb8372c2e74b0053fcbb77e0ab0f1ca5799cb14164b26720b2deacc25e811b9f59218b972d637cac0b35797520a6e3db8f51a09298a63d505260dde5a607255e67025cc453ac9dfd32036f13694725a2598c620e440a5b7aea25dcfb6ca6d6aea9594bf6af80c919b707d1cf4b822ca092ec5f104f058ebcf9960c29170493335788a0b7207a49174fa091e446c3be6b47d7a08143f0618cb8caf2b09fb9307f1515dd872be042e74b9ecde4b3b57f42fd9eef1b90649c32a1a2f0f569527419f55b3ec31f67b7b2b9c3f6cd3a1a37ca4a30dac7d6f6750c64718d861668302e2076f203a11c222437733a81a51ce170b4492f9128510910a2684b1d2fb5bd6b7e5d944728bf92928a754b1ec4fd71221e241f48d4c4dd311eadb80b498b1fd9566fa2911056071a986bee3cfaa69a791e33ad5070feb94ddc643b01c47c2ed8c4c67aa6694c35d737e180d48bc00f728a4a505fb18dd69e6d6ea410b22ee4f5df5ce6b72e9a12a9b282f43e2c4d8553f22c15d1eb59e3e76ae62bbceba5eb2988456b3725358727c08c04f05a53a227c027719771dd36e136884bae791b66a5d8952e9631a822aaed51c9637091636fd280e67bece8a32acc10516a964b1d93adb214c72d8ea1beef9616463958a67e635e63d50dbcd7574e5703196135072e21d7f8348d01182e6445c351c7af085f5c0a87e31b707c75b669dae9b3af92da376b7e072fa0f5725076f2b8513b245c5539c3469a0ec1b769c5a527d71cec56693751b72b92c353b52b0e746bf8e55777464381ec5925206d70a63cbe38507323537f77267b3f3500c7029f04970765d6d116a3dbca6bdf087cb5b7256b7651ea49de513e532e87fb490084a638c60b2dbeed78086021ea3b187abc858f4e0db746d33720361a6de8829adb6047592c8fe99306b00c1eb9712eaaac9dbb3e857280545725f01cbb2778b6d593b2940d2c10c2b80b9d0873d2d23d4412b2c9b4687b88a72b66787e055eab762355a38dd0aab21c9b68079874a6ac8a324f9c3bf6901b272809f646a362a8763ad2c2326e1d23e2c5cd6948fe68e9daf2189d6c98d836c14c14692eb2dead07e86422afc74264c690d8c44c00bae567a6184acbec1383772fbcc6dc873c3e0e0028bb81ab26a0f4304eb9d7c891dad005bca108cebd85872ee148183518fb38d68363375ce8c2b7d0682b715650b253fa84bd7a56d611162f2a08d86d92c2633fdec091d4b5139fb473ded11cf20722a9a8734091e8fe07203af6ad31ee4777af6b5430a63b6b1f20f1f6e0abea41fd431a2930cf0fac972a38ea49047d0934ee9b6c50b7a8e5a565225ee347869d46b2d4d8c05fd7be107b5c03fadbaeaefa8d74fe0595197759efb20a26b499d0642395f11a392a6257258de810a1ac7b1101c5527d3d22ecd1afd580e2a3f3b6faa96577901e0994337b4ad03d8ae0cbd959f2121a3390c25c6649a3fbbd2efaa764a4d8c641e046a71fa4bd734de8c89c74f275ff97b52bede1b22462a782dad81d40ad5ffee1da5720334788129069ea6b240470de7863c6d6615d08e5714c3e34edd3e3f79825772b8d20cbc3d144ba082f6dbed201389ae05e06a3b6083fcf273e33af3303faf726ab20a3b5576479053ade5001f213223991717ca1e16439fc5967569a7a58c7264b124cc9a38c16b18877917a826b0548e5b97019d9f4d014af3a1b7756d7f724891ba54851c13e4b9d8cf857bd04d8fc8a306602a6133f6ee8bee8d37718572be836c66d8e3293d306c30061828d9c531b26808bc3568fbf9c944e67d3592345d91ba3012a2140570eb837e373019ad3a82b14d2144d0836496edd41690eb7201f60faa47ce375d0cbe4747dfc112439e85b4650cbc12f4310fe3128589f051a218b90abd4555bca2662d4dc6bcad21090147deefaf4fb4e829d5ae5e2877103abb0219b379e5e5e75c5c7f34835d32e9ab72e6ec126af040daed4059d26672bdf931918f456e915bdf1801c1d243e7b645fb2bae0b4f1daca0b31f7e314c0745f21c32022d39322b55c42d08857abc62b5a680345decdff119c624c8e62b54e10ec67b4973555a38efacb2dd66d7b81fcda88c5df0a51a08251cfed315f072e7fe77e697371d00ba1948d8b170c3d765521754754ccd15a744ae9e5c2d1233a5cdf0e610b5a4b3f8a452f32a8e17aeef8288a05c4d7e429ab4b9f9131ca4270388785f45fc182ce1f32baa2df8c9e6c1dfce65884017db354543306837536fc7d4b8794a533c7de068c353edc0c0b8ab5f01c1b708d8be412cad0bb0a8c34b3eeee61c7b23152eb5cf3f62caf931b106f6b472956a1a118e80398318dea472e8d2b071e9dbb6260128def0f3592f8419330641fd4e46def47a42766888a672869d3fdfde285d68011324d805fb401ab50a97286516c6a26ed1d14276b800722ea0fb80f476b69b534ed37960101f9d0acca49c866135767bfa79e31a494240e0bf80a44a505d5f5b7a068709fcbbcf26324a6aa3722b051d2326def9380072351bc65c2bd931edb7ae143696ac9464e28664c3b26ea26c809f27b99c3d8c720efe7770f7a94c7b81201c426bf13f3c63df17504fa12ea7ee3932ca398cd57247c627e1022994f5c480da54cb8afb65a4355da22266d63166c6a42060636772e28ae9e48ac9d1e39434d50447d027e2512013b506f752c3a8fbbeb5a226e87273e1d710be7c1da7c53f3d1edc17db5293c764dc880342dd90ba93d202ac85286f438078bc9857a8b742b1ac0c253eaf4f80e843491502b7c7b4252b34253972b8df9bf75e78e602a9ce69a7c03b9a183da0f6ec52cbf0e39f98ed14ba0d312de8cf4f9c982779c96eb97ac0e3a59e284151d49fdc171c3d585959e899ee08721031990759209c23a6e2730c23b5d9ed537f6f733242dc3cd6784dc6dfb81906b90be8673d81acdd723f7b272cd184498330806b5b0b3568eda2dd65232b7972a6421141bfd38bbd22ad3f98ca6bfea1d04f7a14158a3ea98672bd95664ebb514f4b273ba2a8a287f00641d45204746abf1ec488842649f44640e1d0a65c4a7237c45aa0a952a98a6fc56d99dbc0a51a8eb4075054892ea6f795c3d0d5e66a7259955a0bf4c6b4010a86303240c3dbdf06417cce2b479bb893e6f9e4aa0dca54ee22a5e9c8036764ea14e4fa1b2b67d0b3ef03e3dfc279273cf4a0d000f53772d773f07d87e9771c8966ff9cf97f99fed82c78bde7288aa5bfc935c8e12f0e10728725b451ee6a9b47063fbadca28203cc12dd8313f907dc4810313173d88b72f637eed89c4fd9fde8b1512a97b316ccd117ae6e6230851765ea6b535a12a572dffd2a3b321b599aa1796a9d11aac84b5dcc3e67647de69376d46acb97baab27371996cf6603078b5a59175e0be184bc4ff8d7c1da8438b1546ac3934b98be7258ad326f932afb5f8e92cb820e1fdb7d9d362ea7a96633c7f8f915d14b2f0e72a0c42bcb64c307b83e5d0550cd667754751c17d47191d90a4c795f4eee1f860d3bcd2f786dcf5840161ac99ce5bcc06d657f3b9102f51ed6eed28a9fbcc12b729fb4d37792c1437e41f124aa945d06ba56252500153b1b2d256459a184fa361c8b485756214fe469d32bb396a0260c8a24da4e3f7d5f5c272d26f729b9c7d572be73ec9f412a1a3c0ef3d2b0a530df401d7e92470b5c09d6372c2725509c4a1afa8041cb38e6063ecd195b47c777e12fef144a938d9ccb090aa8af12941c8d6e3fe2dfb7036ba08fd35c82c48039c6bab5ced4d8e7e1b6c689f49da58dc1713804c74d942850149537de20bbf9b150b8d6f9dd296b0ca60743a317b58ada587242b790b850539cd5660b255ba92c6c547b3b988756d56c0c7405a7716cab0f7239d3fe3e9aef39ef07366dbb38dbf08064ed142f4918d10a094fb989570d7f72785c7907dfc31b08cb3f091df1be3f55ae647e96c8628e431ad4c7af386e656a5a1acb1f83ace4ec504a2a637214f2e2d478b201cf19bb3e1bb002c262593d72475a46ad50096e97fcf76a3b4ab3f620a52402b78af07832e8b0fc4c6f23e71dc3220f7070acdf71594d921fd9931bd89b7f6c99a69412ef60663e38a11e9e72e9f979e214df6062a14682507f8d41c996fc73917ffe1e1fcac8d5fceb4f047224afc7d520d33aa226aff05eb31c1f15a01e9e4d9581967cff96b9af7016c272b024402a4204297e63389e25d9b8a4828ac6e3382839b7f530726ebffcb0d1727ea014d8c7f814a22016e9d30324ae5c682fcd0a6c0730f961a165e4ea661d72d6ba512bb1e307b938f4690e0dbd6b8e46ecdc7aef47eaa163d09257350d15726d0874e451ab56c0958b31cc5810644142d5822f939754d8e4e4bedf4bb15e7229965f9c0fca6afe925a06721cc61cce04206e3e9c8c35d158d55a76128df62ee0e465b0af8d82a20a7570d8e2f14664676126fd7d734e4bf55a1aa60837981a63d8e98e6aa36634e02550786761391847f07de64eccfa6af758fefa9432b072372caeaf263e13610759d5c1a21b32a2b45934c9ca6d614f449d72d4ff18db7272365b8814be4a8f036ec07d53e310c09141f43e1c3dd9d5cded389c1106c92641884379b96938b4645c91a11a83bea11fa916ac26a5e463283c7b03aca24372ee30971d398a6e04e3373c3cd4b8ce43f8221f325ed94585fc49168473851472228f23680e5424e5247eecacb0107a1887c62b1fb960a07fd90f8ce7b2f89f72c3094507860991d2efd4da78475894a4cf8f0dbb7076db51e2bd474370f89e6b0207a01dc891c8bf1ac92090b0f0e1f73be790dc57280ed640fe188a23c3b87246c58cde3df741f678699da4a56c7e2a4bfec42c713aeca0f732050e5f8a50729f0d69f99d883da1afa08344e8a972acdb72fdd51639e2a7b1cd7357fedf1372eb0a28f6bf4feb56d6d11050803db4f661f8807bfcfcec7a54dba4a6bb865e72e938cf97df81addc94f7edadb7b05e75d8a2aea81ac09f0f997905d835b84d28f900c8c11a19ee0ce4513d564ad2141b112cf45f3c35ab06ab9735f61061a472063b5bdc522bce5aec101a47b56c0a1c4d178ba5909aa6350f5857f616433072a6e09c85d41fe569af6d5eecd97176d8089490508468e59baf3fa20d398082729993fcb225177ba392e0535b8a426101d36999d235b6d0203dde943cd0616d7215542955a4a27405bf4619b5ac3e45538789ea30b2a5f574c44480f131d92f727a205170641c85594818a625090f09014154e8eccad020026ef4329a18db5272d5c6269d15dd3901da98f408e0a1d1ecb7a94c75a3699ac166f2472dfb641472368d36198e4af9681dc404de35aecbc944c4f7c6ec648aa8afec7f1cb492a672dbebeae4c0dffe6cbd81d331083401a59afbd195c7187cf0940ee6034d1de8727c7a7fdfac28861cd03880653e523df6bd3fddf6048dfcc6935008a2903dc36eb0998dec6fd21f40859f74698f89c4b0e764e62be9ef1ac174974f81f381d372cd4dd6d37b3252a24baf152e3b5e3f1da7fe5b442427e80c7a36f2bfe1a0ec5cac851506c57b01af34ae098ffcd184917eeb99b33b8a31922e6b98ed2e820a721881746cae50ffe0afd9641931d13df59b41b6222083f220cda6d44bd7e5757217d85644b3e28f7e653a347d57568e0cb83a55f5edf7e1c7b8afcb1996c6350345051872a7f77c2858bf211ee2478b8354fbaa24b9cea6a094305777318e0c72d93eeb0d8482f131b5893cfa45067a0d0d52a2740f2e75b83d9032b93fd6a972c906aeb65a7db30490fa1dcda78b747daa3bdcdd47e44aa50abd95abf54ecc723a484fc2ec110b38e0d3a66d898fa4c188004699f587b4672b507aa46e5a2a72939d1d72a0a09d2845f9240a500256540e905cafc39f0167e826966299b3b9727a5908fd9a3543cf41d805cd51ad97dec5dd5b9e3cc8f89d883cb9eb9ff5223c0d8eab0f6d4fb941f286908c50bdce7997194c4ffefff3e6c860c62162475d116807644f2c09676f482412c934cf6ebaa71a2b17fddd6a0a73f1202d2033a33fa6c7808346245dacc8377624d2ec524fffaf2317b90131d79d7e784ec147d27217f8e20065af93380539e7e5df4b3b2bb3dd73a81eee1eb44d4655d645d3d7429cca2b42e3c2606ac43a64e6d8b35b16a9384b2d9de4fa54162aeb16c86d5a72271579e19657c11006fd8204302ac051a642e45d7cbe5ff0b3abed7215a9fa4498e297a36fc26ca5f9299b603080f0bdc27f70a8e4f700e6ff31a7b6c2b2333da604a8bcab0661bd0471024d99de909f75c2bce0f8d33f1da08d5252c6d79f7282cdcdacbf68bb5d14b5057cf3d36a69214fc3c76c4e926e44badc7926d77d72cf0c2901c6fd39a4f72de4157ac89cd9e89a46e7906eeddccd1ad774d4861972436841af71ca37577c7d6d4562f3509a8aedddea66ffbb0022ba38a99ad3af725e5e17318bc6f5aa9100f87ef4d6634322c17b9451ba206b0942b7978a122f26e2ae8b2cc570e16789b34490b6753c1c564bd9fb310f897f38c8aa7f5b321c7274a1cbaddbd99b9db566d76a9cc931bef41c9576c5a3f8f6a67a8f68d0a56372c3fbd506cd4e8039a01305b528b44504f0b236206e383a49197dafc191b0485558e44adcf2086dabe40ef97276f69cb456d60fcbc5ca66b3679ab75417fba2386a8db0fae55de27635df69aee920d963342c112973cd44c2e2d35e294245f82d09bd14ceb3b432785328859c4c52486c551379d44e9130a0d237b75b45ed4013128b599cd907d6bb6cc4b7c3c555fd2680eaceaf81f8815d84ebb0a3ab797a727a37c34205cff69728cc2d7b8580003f80203480d8f87dce13a2ce411e315572e372dace7ca4e87efd10f0bcb048b0f413f28482befd9aefec33ab19fcaa7a72dc5332844b476e9ffc3ef247f24cc546b08ce3aed48ea62936c7e6c85ed467006e3d249038800350127517b3d3129fbe8cabc3a6a0793303707892bf1fe12f726872185da848478eda80a65b2b8b4cd7a0f5791cd4d31df135d19d93bce0ca721adc23487be2d42bc62f4bff8e312dc06cc902e2056781233cb2be4e56d2771e738ed9c15a3c1df42d00cd61da452f735781db9b35f9d92209e842872f61f301cc262072eb0496f36dc3caf2b0e48488ad84cf86c7da068a89eea0d11388cb725c6ce8b3200de3a6d66100efe195fdb050484d5a832fc0eb5897900344adb4722b38b42ffad5f9e454eae8fe97f307c85be3e241611e06050d5c2d99f9482f722eb022bdc340038cf83389369d8d81f26e582cb2054ca95832cd6053c8513f72ef300e4a75048bc3a661538d1a212c4a7c858eb21cd17477e47f4eca34ca87722991000b3b27ff5cf531a2b239c220f7b9be357a9408e93dd49b04d60b929f1bd2e0e8907e57b1c3b857d8f9da324df7eda64817aa2742b832ee07a31e83b37235345fe2eb786c4d8003e08f1ebc5e69cb3640dabf826c1ae9165e468e2b69725c6a4edf294fbf91de28dd9b400bf777bab0602f79544bb676d1102b8543ab7239720ea675ca763799c841a0d7f6c78278faeab2801334b64952a53ee9ecd16ac76e74c21e51061ed4e81cc470f18d198d9f75b0f3db1aae08a23dd6710d0b518f68a13cc3ebcb3c6666abecb0cf65e7f13417ad635e6c70baa151481ec05524095bdf5dd18ac5580be5e6cc31996e027492cbb55a7082b74d30fac63f092d0e9787a365e2cd889800ecff711f844a3954a9543c278561ec4558f24906f54c35be05ffc6b793444f57873ac9c7f8033395a7f6e6b276d3ad9ed0dd50720d183e5f697d764e91c72c7ff18b0689f679344af5bf1fd89e09b0a35a0a86c9aa677206bec9b8c31f62986394c6965c10ab8b5bcc97c5ced9cc90be58b8549ce1ef72eb8fd9757af8603f691ef80309580ef79c610d3ad3c62e0edd6110da291e663a81d1a17955c18ecf9ce3a0e7819d1764db1fbb9784c1cb638846f44960e98272f5e3b4b2493ab57edb9606fea8c7c56d8639516d8623ac30d0fd41d1a26f4d722e195f15bf251fa144b2a9d6ceaf861318a4e8994a28705d386902e553aa74724f430331a1771eb026c0df171605a728c52b9228bb42062532a87b96c3ad2c72114778a356aa1343158aa321ee16ee6a9b98f4a0dc65d229cc84cc41c31a3b72ed380941ab3fa2125dff39605ef8904e1731849cbc6755a10b48b013ae2e577205d379c11efbd13643976550ab6db898ab61737432bede373f686d7fdbbbfa547fcf512c30b73222dd3294d3db660754829cf0a2e9efb7bb9b71dc6c06afb27234cfa9f79c071d089f3db3c4aadbe51aad44a7bd1cd698770be4cfac977487699461a5a62a4dd306431dd245efba01456d3cbaecc579621985aa38ab7957e07209678322e7177a42f6812fd5580228eff65663b3b6bd95786b07d1729aa579722e875e506a091b53d829deebe200c592c2a1137fce039ab925dee4d7825583727302f18ee3a4549830d37d578005974ed9ae35916a8eab9792a31632ee97ad725b143efeb6f6911dcebcdc5f0ff9738d2fbdcd8dc1f5186ceb759e6b8a91a8295129954b8744a9022475832888a979730e94fc1459a67721e4c11e6abbd7bc7229531c5f00b759148b0f4e48ed3af5511deac7ca4f8de41d22444537a1620a1e5d4848f31d32586f30bb044a62a3ce5cd7929c333c05c2ae11a8aa0a19e0cc72d4c27b4f2a15d5abb448842d32d2efa64964758d8c8d6b364eb0afd8e40a697249f0cb8a2be43ae89df734e81a646833fbb3de858945ae482a65a5176b0dae3834bbe269ed787466512ee4fece970873570249ed9cdaa7f45a11cec2a2b94436c1eb39bc774fb17de5bf57b1468778e7519bfb46f1bd152effaf71fa1f488972dfbfdc2cff7ca8d4e1aeaef5acb16adbd70b1e4b4c70ec0f17c65751c6b70a6ec46a72e38f3f1b43fcf75a448139e177e81ed67120188a041340f1b9b25a3d72ebf13a0bf97409b1762d532d9d113ba03e50a6f2083b4fc971a3a11d9cf7e12a243e85330a748d2b6edbb973ff3c82065ead22b009e0ad698c24b7f7f0cfbe7284a947beb3a335ced60e50d28631c5d63815f4ae23b836a0cecd7c6d1bedb916ad827fc4343372495c6754b017b76eb16eb0bf96b5d064fc1eae61ae6b6eef72150f9695564dec7b566cbd16a7ec5d0edca96fbfb130775124c99fd0e1237972afda5861ff797310456c6caaa7f2e21740d744119c5a693c23489eb552ad45608eb10a47e36a3385c922bae43b87105ba6afe383c1c6392606f096752e65e4723b9f03ee21bc545028262bdb95299aefdc7bba96a5737d98c8e277c46e918857419d09879d2550944da5699cb260ae6fb86fab2a982e15d42cc3e0922a26be72a8358e5efd086b30847e86dcac97db66415f4ff38d28f4956e08206a0b2005237a970320d142f070c238371ac06e3f8933e0a3207d717ea53197f1fbe8a10f36ce3c7c827984ae05710c1721bd7ee3f1ef81bebd874bf6bad7e444aec9394072d0bcc9ecae069455f6ffbffb8eccb58ea3b09334932e73f1af96b025fda72d151ee7a9265af2da454c26f788e3867502c242423dcafa9daafb5aaaa7730d4c723fea55f27edfd36223978312e4bcf19829059bad3712d06befb9d8847e86c272e888970c0f78e16eec6e28f759de2aa41fade8c864583080cbe385a4406a3c020acc51cf98500bb47eceef5ec3441385e88211b160b025460bc2bda5e268fd727d55703c167543e852d6172ff906e45764204aea9a7be57d0b6dd6543ce9b3726812d46696233119a49612bf56841557c2a5d4a69f4017a4d8ba283199549372ac75f02d49c58f5bd713a23f43d4414e53120222b65d63a5b3bed027cd45b4728bfdda6da790c5dd539d726ab5d7445c3cfb8777b19bdc49871e4372a2360f722ad3a4ff8326e1c0ace30f9ed8bbc95ad7d8bed2ea6bc8b8f6d963aa5a9d1872e29839dbaed7175e101fc52e6669e8468cc1da31c36ad7385f3a41f40414bb728bc503165f9e410f09bc0d896c734e1dbd2faa669cfe8701c857d9eb4774675f5f6557f79288e272206f4364540377fdc88522eda6a095610587c6fe77db7b4dc611951d998499f737bc3445f577746cc2d0816dd95990a034b35db05e0e5f342421c4f6946f6c82ab975f0c26a5ae2d22a665db6a5c45195d9d75e9083cdf72cd49f1cd18117c795b086a37d808d867d24fed5d8b37ba15f72a62db7f88512896a9ab2b9e7e194f1f839eaaec21d43e37e39ccfd7563a94b7108f0036246061c2524fbcc4092ece389d270cc259f45e44d82a7b3779e1cd49190ef66deeed72dec33d9b423168434cfa7631f9ccddeea2865764440c009a7ea48bc0070b706ec7adb083a04c7fced61ce8b6637ea529d3159f28d9b8c1fbb24f5033cd1ca07247b6c7924c2c62f2c4e2b226909e0082126b29960a4fe66a9063da8f530ee96b8104fee1004264d5371012f5daf62ba211bca1b8a89354450b3c87ea7e70de42f09f96730b8652d88835339dd6f4169c78f3f932b71bbf56e035e4d89640d860076db4c5841a8403f97e3e41fced92895b0c3a0280b59e4c7c6069c2ae9816174adaeedc2162fc8608dbd9af1cf994609ba23988857dfdfb12c6ba1b56bc5172ec9b644fd76cfbbfdfbee30577c724af564b8c7396d99222c21d2eaf99cfe520b91d3e134bf3bf5467ca4798c19e4372b5a61e88d14d449b2f6c5c2b2b179404e1ab5619d6ff393d24f4fef19ae0a183f6cee3f79e22a66278a8c33f0d4e0f483391dfa95ddc87262340a1cd5c0abf6dac75bdad0a4a568149b525e057f63972be5c7b543f177eebe790c4350da3f20a937f68a85e9d53fd64b79a2b245c39690c20f1838ae8376539c624c255607fff3050bb78fcc96a3e5e35bf57a6da3d5c7f76f454931c0890beded4e65c3913a47c1f78aaddc88bb6122642a3d909060eb66ca152bfdedade131cfd05f2173be136ad36e772896d7e41e8ffe1348f467243c833d9cf2c3ecb29464378ef608a50b556e958e1b615201669dd3e463b416dbf3b97ca32837a0e4ae31b9b245785cd4d6566c3e82bb688a136251513f55072315ee87b620ab95430a3a434b5f08287e4f27558488a41a472b0c3c0a775dd7245bd0ad5f0b434365340da90f026e065be2d74aaadf4980b1cbb520c273b6d11b8bc1f5208995cf53a5a70f779cfc8569f302aca969e96ddde584c66165bb94a6daa47beafc182f22fec6ef6afc01455f8a5fd19d5dea2f18294aebabcf3c1626370daccfe45a73a9dd3f48c79f874b549d1e7d42303cc83bb1605a41932a672a6f40cd56b5e97c964d7af4dd677fd3147cc16b4c9f4744c3cbdd70a463ccd462d7e60055b8a49a52f6e7d85d127186181ffff5886a73e7ed96afbb92473d372d70caa35425085a327fb6eb5abb02562ebcec80ebf7520e924654d715fc6ca729d28725e826f54674c37c233bc04e89f31a2afcbb8418ee4248ce709bd863c7288964c0206db710550ba9279920e2953bd0282b1815fcad82d911cdcde93f072f2d177af518c005fcae72059409bcb5be83c5ab1bd0e5a4f5ec22504029683724554144b4d2488e0496fc9b82ec3437c8751ff61309e4916d597cb476a551372ae35dde835b67135f0bd664dff7ac0901112244a2bc6c6c3d94096de25e06772fec1ff25be121d5d0f08475678b7802dafd191b7c68594a6205d98b108a899723095e9691171d297b956088d6ac989b04fc77e646303c2bbc4187dd9b7c94772b2016954efcf588ccdaef411525ee061a96cf6e639c3c8916578736b5b54801aebf117f3bc7b6e7015da5bb09a35e6a913de186bbdd3459b22a8a078f93d8c7295b8d57bf002721328d3c3ef34797ecef45b3ca377f3ab1455b1ee1019870d2a369d5315e34e8f0f37b537b6a61a92dfefb3f4e29b381d97294d4aea91d6e972577c1d3024810fb1c4e4551bc0522bc0a562ac12f6c1a4e7fc234fbf3a7e946c4f61e21a0ced22cc431e4a08393758acb1eb6bb7e52498240b35382b88caef72a372c12d734de7a3c7a61a54660b08ffe7d3cca1905460b82af222b73b6d567246143901569411b4d4ba755e05e229ba2e3fec1a04466b47a8b71052201b7254fe9ace52eefd9756179ae06bea251e15c83e834e1d153059b91fb8df5cf8a052b055cb657906f1246ed6ec2462c3b9e635023a5eb59e93c0e722ffcc15d3607251c85ccbeef14abc7cbda3a12ef71910be1d5e23e843b2e7ad8ab42a3c26e449511ae2a3d5954431a0c5bb473cd768b9dc4e74f84a8518ed18f92db3bdbabd728bc55b6ad9d5e51af9546612a240a1da68e8cd5d9ea9673979cc5ea8a9980a68d2c0c60d25d2284c3bea9dc1be3fe4c2d04cd581fb40cccd205aa02b1dcee66f3756cf1dda7009e50969899642d0e287c691277439dd2bb3f8f2c1946a6649055f0c03f75abd1531b90b2c9cfcf0119783cf444c70bc9ca98f8cf1f390fbb2640cc071c28c313a399c9026c05be03c428b4a3d1eeff8ca37a855fb3a58a30b724e55b20e37c7a229af68c139d09448cc3df8431e3fdeaf9ab4a421efb5b9b76a104c1774e53586781c4c716f127f530cbb61487e83f2f7284dbf31c38405f5724e6d7343c78015c00bdc30e249a3b596668bfdb83965796fed405c83029aeb72769f6c7ec9857f3b73244e907386aeb9415d7e5af4121ea3742a4ea1e78cc80dcc1988b862caedc559e3bc2dc9bca041ccecb82a553d8fb38cf915d6ae98181c6852f56084feb4ab6ca4cd435e3bf831f041743570c8f7e9aa24ec26d501c47249db6354c4a8b4a476d66e8a9d9bb79b56e52382d28576178deaff85f65cad72e503dd9f537d16db25768d36fac2a287b7ec0b56e35c38ec94ac8be081c4da5c863fa7ef2e4ee3eb336fad61035050c4abd4d745f22e11d2341acd3f98b80616667fe0d12a98f1f44d215da720ea26ca441540d2fe50220313db28286970e272a6cd3e879604f95d0d7024e879f860027f7385fdc6544eef89279e8e47314d20d07b4fe62ff08eb0377b60c3643c3c87b8bc01293385be2f92db95318bc25972b22ce78224a5671a35094732c24b34de1e93996f37715503e342ab2d7caeb753ebae3d4b33676024d4954dba12e24206d6d70a36cf76f246fbab6e8431bdb1090007f86f0c48ca9bdd702b5229df355c12195827296c911f9ca5f42d70338772931c1d43b5b4a589434e85d337e31caf6c22cb71d7cf3f98d82430b85d7eae723161acff5182e45389afb11e7adbdbe1b9489e6c63946c62ccdf7ced631ebb4e023075fc7bb3b28ec68fd43ff5b81e149f9a9172c52adf694db58fe94e8ad0467391bc1a45641e70a5ed0303a8f568b9f094f324ee6e58d031699fdcbdf32d72ae1b2df656d1ce8ef5b5282e63b0c4434c78670863e1a54fde78a0ff3c1ada31bdf507bc3a5954db829c3a4a320074ca7d09329e00b83d08fe28b778fe71057219d6d6b70a6fde28b80b3259aa37c9ebf1dbe40c65fb705346310fd125fed6722c9dcf16dbf9325af7e151ca8ffe4fe4e3a32849b6a35a32c676e21088cbb372cf725a02c6ba88f3494bf24b6a68accc71a1d431c83f30c47b215540ce18935b8f6d508c44526179234a75aa0130bc260096890e41f458249dbdf7621e1e58723178ec75822c623e6e813179336ea3b048c0c118bb234dc546013061b21f0c72c05b952fad59cc43917e46fc18a6047019139efd418835360b3c5e96cae170729265415018956b9ddf9d03420b8cbf10ac6dde5d7ed96fcbd64ef0fa2a67580b97d5de9855842d1fc6eb6e985bdb321541f3e77a336d82b12ee63232803e401b24d856841aa47e4861e2a897b97d41b88b86ab41347e157392874f24255ad24b1224da4c57e7c2d2eb50ea2649d55b044236a381f287a5bbd0a0d53246b0e5727a4fa2437bdc4a9121f21a717e95c87400998f3ee56cbbbef834d34576075e72bc80b90343d0617314d8d87f5606a6501551887cab582c7895bff6a150d0a77214b25f422c9d358c520f82deb2e1f5510365287fd5e9997db3987b5c27e2d672cec47f678213f3318520e5242630857d291aa6d28dd89cfa7a8b781586c786653e46827bc548ac682d7f3e26c2823a693acbebc403d61e1bfa9e3dbbc82a3b7213c4853ffcf6c6147f3e7f40a04ab6d159e446c07975fb58be281c8912ca6e724559a8307f535dfc7d60d92323e1e06f36018cf8740271647648d735d9551372a5bab0321c7c8aba5c310cd989231d8dce68488232d010f0b6c2bbf53de446723eec4b7913360f3cc8a60b9d587576fe8a1e3bfff6447697fccab4bff9b1795aa295fc6e6400d8d3610e35ed6a01051ad7c0a488c2db07a487f47f9171045828246b39b7245f81e172dcadd64541357fe53f1a6b321cf3fce98926d9a362bd6d564289521df1f1463a408d66117d84f689cc3f13bff9d24725b93b2b677241727f31eaae547cd6a3ac193444b767d098c3f620f47bbff26a03e0d412fb667372d2742c50973d04fb8f9ec9d12e576d996cc938bebbe3a809a12fa30e47277d65c62699da70654c02690348b5fad4e11348df18ed9d6c9d65f7658aeb5360df728b81f74864233aa9d77b56f9e8bf0d69f3eefc825e66746996ec78bda4be697294d39f9457ba09ab4e5efe2270fc27bd8994048997efbe2033ccd2f4f6cb7572b12f0a140c9a1f4e111e387c9bdee047f0ff9bf80c75da187903bf6f2b820a005eeac8e421558e73c2b77eb4041089bd39a8c6733fc6492c67dc6f2e073ae472be08300c71b1074b61e46136b2962904a44b5d136642da89581bd1c50a703772dfa4d5fc0ed1ae1eecdb1a9099078354faef61a9e6a55eaa7d32a8f2538f227219fcbc4c08699abd5fd37ddf33dee25b3391b3a762968f677b9d7cf12bfb737214f12aa156e42945e567b8a4dfe931827ac653006a7d79c9532dd0c50b45f718f8f86b6af6755600f89fa5cad3a3b5ecafd94a16c3a86f3e1912ec48f41435728bc27b8268006114214e436f8e857c1ec925708970adc234cb3b87e7d6f0eb34d30f1766bcf83fca8b6021994087b92c6b4cc29fa05f053006f06243f8e2070d5c3f8a697e7c3f31edc08d4916d0d92e0050648445907e10aeb689e2663ce5722e5807589122fd07ef7bd7454efdb985345e3d353c28fa490b23362ddeaeb227b8bccce1865923caf88fa4ecf40535afa037dad0e2335e739e3c23825af48f0ae06350b6cbe98770a960a30f980b39a83369e196c0ed5e0a6ae270a992a08f5683e48bda612b14f841ff44a4c49e0f66abeb2d0a64973e0704eef99ad94fb46b69da3f14f806b0b72bb96ae63f98b5042a0adb2fe622e03b26e2a3445fbbbc723f313b53c51f2e78b0fc52770634d4cef0e9b1033a93bfe096c9377916d903723c25a7d6d95799deb57e87f65eb05ed62fdcf4a5899acccca60faa57e01c56720565f08266fbf2f1c8ca2bffbd41183ad9c86face9e0d2c07dd0f86fa7cfea721d2d89e76ea8302f1fa2d1cbe1d7cb82b3a9d61cce9457583109d5eda96679225d55ec9ffa44745b3e9a21fe4699b3167eba6d4448d9550517c2362621e19218d9359196460d8772426a2774e7f6f1f2754e485c4476aeb5af8ecb47efb8f21b78f6dd3eb253405814f268c2ce6752684f89ab5ce2625dfac2e189c1297136723bde0417b78bcc114b6152e576a44b9e8766e8ae9edfc3d739625e37d67dcc39d60f29a423575b9ef270ab5aa47e37296487e8c184eec691b82c38b4da7fec54492ed5ceab6e5e5380b5a41c0807dd4c012c5ac20739cb807608d22dfc8aaa72bc48d8846deff8fc68ea2d897e546fb5c22abdc7869d826f7f7e09488197093794eacc41ea1d85a3ff84dbe9190868ef274131422f681233662f5baf35a1127242c44a73fc8e0cd5c5b4ae8ff87b91855cdfe7a358e4e8f22dcbdf54da2bf472ddf25de5c6480169ade5a76b0922b2fde109b8be140f17478d33309c4842e872d4f499fe16bbe3b54221e15b6fdfbf60526147f7ac308331c5892b123f13be722f1fcf3868b1197b3f2d6b2fa55ea741f439b44c770aa24765c43b0b6e574d7219b97ac6b895fdc88fc80411fbe86c8e6fdeb56ac109dd99c2528acdf714c672179b4a428980a01abd439565099b89fed1ab9b8380129e522c487768afcacf60531285df7f19bda76ea440fbdb9e26f24dc651d281db47d43147c493a4dd35636bce059c6f3c1a3a0b1361c8d23a73b8e3de4624d09bef294b5e0a841e173c24039fe75cc5a866b9957a99f11ab0d084ad03271aa4d7079cd604d99357e048728c960a1a1d8bbba0315f1ab34d0335006b1ed4e51313f9b8463d3eb793aa55727efe19c0ac627679aa8a1791c50fe2e3de4bb217011022c95050b427cbaaa972f272df7697b172f9488ed763eb2dda301c60662e73e308754e5b76e71a083e703334bfb9ec1b14eec4b3ab951c1b189e9e73d679cfe37b370ce070e371866851e397f52c32d869c2657405374f131b23e27b18b1cc1938319ef7aa276d26a272467cfd1342a9c08c81be84ca0270baa3efd5b6e509f5ba55a57669be8b5c1f4bb212e871d638d44b17394727287aee461d09b25b63a392511a5b04a01eff86723828f6ffa4b785bf2eff925fb6697997f75bde2bcc7f414e19748235a877fd726be8244f7f505a85463e4f33d77de6a0bf447f0b2a827801a104e60515e7b772d15e0ca8e73ae4c1071bb065be5611f6903eec2c98c8f7f3611d9a990f2ff963d6a81aaee4a968e60098274e2be5bca1d242c1befa3dda1ac376a36d906f0972ff655511abf73a7365ccfbd92dc68d71c4a434d5b6acd518a42afba3dcd48b3e19651b54ec752939207ec6afa5b48cda11a7c33a78cb13e61f1202bd239ea60cdd5c921a4ace24e5966f478b46ca717b40e90a4b214e7e8bffbba285c741fd725bfb9fd3d52ae1ae478d6e415f41e602de37af629b6cd2e7ff71e78930598d7279bd06df0723edca225bb15c9019b443c0178591365d756755413de0b30f4d64126f02e2cc47fee437df054c823b4084222a1b5a29c5a00bf31ed5045f97bd72f2a89f6249e294b2e8739b9c46805ab2e3c41f2d2f272e800f3da632a913f0723e01c8f9da42b21ba254394de1963ad124feaf5d783e532158fe1de704614c724bbf2506645d070e24f1de7dfb80561d9c86b28e6e35629aec2fdb2549152957d9ac3d7cfab482057cbf35f36c2670d6d69fad9c0b5466c79f74d6295f1584549209e8b53df3a007605ef9fcf2edbbe9aced38ee2b2fd43743b7a63b9d4eb072ec6341febb1e373bb02696fc373dba8b46347f2053dd2976844c8d4f79e9c272513505ac5b6910a8443f6361876babb5941ffe592c2cb0a1def72495f90355724130f5135e8ec648e49ea2f95e51d1c4b0c5d1d1a74ebffcee34c635967293720c8441c344fba786c3c48820a5d3a6076e483b582ec6d6653a00035c478d61599c110e1594fd6aef31ba74e06f529c6a2736752486abd0b143b13876d92ec372da0667cebf37a39fb169322ac2f79520d6e560b3637d050f24682a971d4d015c887dc53b92309b629632bb054a91f2adc64ea7f9a8db613b625dc43a9d00327209f707bca40169b67b222373b165052d6483bade014e803d19797942dee8f1723d3cd56aea54fe122223c972151507b8c618a044192f74fbb8da192ff389e8149d5f200c3d6785323b05dc0d419433efdd735356371b9c0785f8b4b4097b290c0f4beec01d87f3e2fbcd498214d710de9aa42b68d45a9a7c1081921755848e3bae90d8c520082e431aaf16dc9c3ed9338aed5741f84a80bdfd77dfd2a282c87282d11c0ebaaf4237a44c793378d7bc78446670f137ac0ea6164d9ecc21e15372776853200bc755849d78aa6e9742d5554c8798552705cbdeb36d53f7f39bb57273206e9604cc14d1a5b38266643e9700f65783dd828877f72994f06923f7ed6ebf5dc0b0e7876d1ef8d2c5cc36d973cb8edb734503b60dcb9459d3311a867a39f0ac00c5d9a030f235b4121c77eb360095c47582cef1e999b12b366e80ef2a72aef59295e8d6be011027b58f231b9b6d17a55e2cac611a36af7cadaafb7557314668c1a35117d360dade33fc1a8f2b479c10adf1844d2451003f25f3c89d31729acc8f829dd6b28c370f323ad311a2e2cf85896e1d8882e5db61a997f48a1472de25e405d6c187a4d46f1ebac51f41fee8d067ee80384989fc49b6c068fe845f0f2784bcdf7d9cd39c1f402c999af1289b52a069b269cbfbe0c8e3bc8b49bf72d5c9b23c682ee9e2df4856ddca57df841fde83fde65a52eb2d0cb7fd5239123111e562da1b150df2ab883cf348f98c5f5e9a699b45f8d0a9cdd1a382d182e27248032fa0acd1dc42d1bf6b78ec03fe32436158281edbdb6deec87d262447af72444a9051ba8d78430fa42cfdb4fbd5f826bcd63c2edecf3fd0e908901bdb6b724d62110259085f0535d7c942472fc39854ab45e64db6efaf5ae7f1f318b992131d8fb6dd3013180499258eab4c040be265a3d66ad451f0f69c0a77d9a55ae70443ab76c8a46cdd62bced20678e23e517a8955226b9108c26729a343c3c28ef72b92960c0e02b173a6f1b1c934780b6f5e0b2bef6e4af4d005c281469ade9d7720d06055488662f0948b4a2b939306e5e1991895e4fac88f0338f19d31b7527353e4c6be11a7ffb7ff6233351e92142176e1387721afe24d6ea5bbf03a686e872f82aec80dacdbc41c23d470ac40bc8fcfc0c55b39a4e5a71035aa39c30c68e3579eccb7cc567d959da920955d488a63197402b282a7c77673e36713b417e337291191baa5826456d863584456123f5f4bae4388289c9a98bcfa028c4c411ac028367e7dca23fb1857c4005250f1bf776c1c9962cf65b69448b8d4f419ac75a7275f8d3c60970a11bb486cd6be7022e21a4d2d6936c49dd20a8a27de42f164601c49ea683575c70e7d8b7ec9c65566e942ab638a525f3603d70b938a21ab3a417583b6ebddabbda48a6ec4cfc4828c3de26af33c6d84bc7d507e2d9eafc9b2e7287810825670099b094698e7bdefd1e85e23ef2f9578206a5fe8ea0e8c86ea06ad4c59c7c38d52a4a2cf25388c7c864ebac8a8f2d8e1796f608fffa4745156d725c5b289fa053a20d9777bac63474f533423f90db8b6fa5158a3c5a3edbd85272961b25ee5dea22b4647a39b6899e3c8545ad1f4391332c650b417dab175527725d6c9fca05296a5d99888c38285963f273772f441fe60421c494e2f649ce48723da02ba542b76c94f1e0b1723d850b340eb704e969bfb105e9a7931106b6dd6fb8789884cebceeb2bdf8d7b0dea474fa24721d579d42716fb945c3de69a2037291bb8968835fe02213ae92f0654730fa483292334c0dd9b609d7ed76adf36c2253c8616a02ac429081b8cf940e02c3b3fec8ef1e54bdb0c6026b27697a733a549bb3a874750d52681b5684af14f1c14e1ac3664b77d7d72ee86905b56eb86b7294662a4ef79924a0b6b1ba41d3c05191e244691998a1823e9c9bb9a7a8e477728bd28357c1630bedc80ef95e7216a8d0c5523018d36bd28c467e1631fcef7a525b0183ed03ea3f590dbbf372f442011625ce7c71917c77a72745af6ea82a555921267e3418de3ce91663ac65c0bc4b077a875438d87f68d488786d232ec90c1d0ce3c9995107ffcda5ac235bb01b4a970e89229b82e6e040897632e8c35961729a9b68cf278d35a28e8d64949061d438600d2e5475b14b53f30da3d4792bbf63ba63692a3f01856bfdc271bd86185e26a82d99a67c06e72724b8480611b9af5c97325d435e425943cec90a4c1d1ee8d64dc7052e1ef1752c1aaa2d5877b3f6722fa3b78dac87f0bc6cfd58b52aaaee1e7058ab1efb6be01d50e9e58f301f0937b29011cc68d26504eefc1ff89481b209f9da883c22bcc1bdb69fc21f753daa5a490b535a2ac462627d13e716c43ab40cca95c73acbd4a0976469e250290638161df74f1771eb1b5357fb825444bbd4dbc8200102ffd84303500a24b460b5c372c41f65372940d10d7e0b3627f20ccd65a34c895ca6b7c8be6eaafb77c946e42aa395e1efa237d4c6bfe8bc0aa0286f0f624694d4f826a630a24d7ef440c28a724f7640a4a181049100dd939ffac437786818392b8b32db13cba4dd5aa1619945c606831b883624ad2a3a02a13b12cf3947f945d8727f40dd08f7aa2f0886ba72f3766a3b3204824000fb021d5688092c894cc1ed02d65168292afd1ad430b572c578b6eb083351a7e7d6726ba0fd6ab44dc5a2ad364c4f98871601d3c62e1902c613fed08c89215def46c3767853b36f96e381a49fa18db8c4e3b984eda9584cbe693d8acbd2dc7446da6735ec96cda671a1b844f204d59a1c19a0ef1f744a6afa8ba003cf298806017c715888329ddd0c649e2d76f19b8a8a7c188c15c36f6352eee72ada8df7c78dc97a25a8f3425fdd0e03881d047358138acacaf3950f72d24c4c77f4bbfe34b0204d0089c32349e0c1d714f177d7e67b611a20443dc121649efc0a8ca511326647fed08f346678391324c654f918c455b0255b44d5f67283b58a07ea3c5437cfba2660971bd8a6a89c8647e95fb395de76dfd44da0fc68d539db54579028a8f837e07c9b7d60e8f28117354f8453672712c00c5c47cd30deaff06ead0b6c94c7591742f231e3d79b486564ce05ed2dfdabe711139249729052463fa53cc7ff45c28c8e82ee7b82c65c85b892b33bb3e7358cb69bdd464ac27a6aaf365e9f965b4ceaddbdb5b74eb14e53b7dbe26b487dc6972d09d23c6ad67f82be263f05b00ddcc9fb80c5d90492902aa40b503a150cb9fe5c4b852372531cb84402ba9b75a72efcf2c5f2f71b6fa76076e6343ec7a99d9466351e561c9516c36468f7ddafd45617983fcc809c2d5aaf94f37419fb4accd463f1358b72be870e282e527e9a5666a231dca673bac5c3e8e50bc9501c572ae09700e3dd72b3cfef1344bac290e7929428f9d61dde672a4726ab2c9506b35c8e0541aa9172be719a738a8ca3827982cb7b709a606e74548fad80a9d5ea686112fed4eaa772faed0b51e5b8802a53667c3a26fbade3bccb287ee3b083d0409c22c4b4d228721ef75b867c38b2fd5092cd7697f23955760ce17e0074cbe2a705bd747d0015690778f1e88227a23dde4b54cea780b33b3d943e8d8451ef631ebcd3a3a7200772db308e8cefc2b242fc4f77c393d1ae27d56e737574336a8eb7e83d54227ba57241f5a6299bc7b6df33c2842358a155c25723c9faf02f6eff33e84bf1af5e847254fcd87a13488b9cdb12a94f7176103633de7b203fef1e5d7a13b52abec4f77268b38c8850e0136d033f83a793beb836907103088c9ca1398347aa865bc754729f408cf756544bf79fc23c628392db0e27c0e0dd79a518712ad8cbb1f28fc36f550d0ebcc0ef556bbf70b29061af9b714301fe74ae73c2a0e1f41e2373ecc5723a1cfec38cfa7462c673b45c1902b36a178bd3958a896c32784e706fd0e610721d344704e3d6c0842dac1e996c0b95b96ae99c8b8fa59b0ba290cccf8932ae0aef05ad172d0686512874286dd456c753631573bd23915c81c09bc1eabbe6f95713ab8cc7427e41d93500ab9351ab078b82476dc4b31347c7287285a5872bbb7263e5ff4035ceffed4e82eaaba43bc0fcef9dfcd06c765a90f2921cdaedc1c872154cfd1e91621f6656204380a6cfd4ab187b730e15d01b31cac472e858ede672dcf61f5bf415970afb8dd76c8e3caf95e3bd2e2f53900e887a59eb154813b84bd84081bb35964097284e637f61769c35da8bb0a79dd0b07638c998195e467072503135429308d93210bbd3816da93c9bb066ea649dda660296a9aec707c92e720dcd9db8bec7ed84a50e06dbc2f1c95eb22861c225384e30327a7c23b852481ca1e818ccf993e3a392d890cbb2431cbebda0541161b6c65bd8d667c663a7be0eaf6b4603fc2b27858aa0fdef7b4105098319677195bc49626ee586214770971ac47733be224f571f060d4a5a1f49e11c3d4e754db35bbaa1691386fe52407f72038ecf10e022081d834aa79f64b0ef385b8d6e448f027ef4c3c4df43adcdf672becbea13affb3ba7a9d9ebd22c43a6712efbd5eafddffa2b9612aec1d519ec72b291949e3b5e2f555409097dbe2174bc87e705a2d67867731e1742ac24627c72dc80b78d5bee8c69f5b29f619095055673d774b34bba1a4aa5a97adc6c51d32cf99a513c007c856fab12439f3f1f8cb2c6e30005f4ebf63a7c6a37e18a85bf6c43821bbdcd934960bcef5e01e2346d99626dffa2aa01a20cfa29adf307c9ce061691d0fa7854919358a9c2239938802ca295a06286feedee3a3c367345284b3ac5089c8b14abe1a0b49f4ef0082d67ca6eaa7127a044029b22452410e1973f72b85bf463e0ececcf3d9461ef21200da7fc4453590f3d1f933b059da0118e6d729f154ccf218a9861133099068f8627bedd1c1460c463d05e07dad92f96d9543433ff530a1c33e5dfa307795d95e45415e301c7f965fd80884e441f9c60466c72848772136bed9b93f64d66a12c4608f6680ec0b31b4f02ea2d16d05734a60a08b372d01a940221009684705af148dee93e5d12e32bc0ade23148c3fc9d21074bfcd81f09a66fceb7b8fa2deb51224d5502dde848509229fe6a46069a9c6b243993edc075fa4b3fa2e2e480530634ff3e962cf56143b22a3149e073f1a784c872976eb6de5a2e338aac29ef3b17e6f09eed26b9888c71cbff32e31cbfe944e8726a67bff9af3f639463da6f97cd970857af087a15272f6afc456a740309cca672a00caeac19958f70129c37a69f346afe7fef3bb638fe53043bdab1343a40dd72349d42d2ddb2c4a067ef175d676ab65ddf9137a623667796fef684ad971bf172e2bd20bf63b56c1df231f3945bd9dea206582ccd8d2fc15b97304c60197f874b39781abfb70f340930b04050d6de8222255042b723f0f73d4a950e150ce28f1dd0f300eaa930cba496e4e0741b4abd6b732b8423c642c610626e72e65951db6fc09f5a8d29cc76ffa5dc1e78d0ef0e7cc67981040e01e21918803ee67448fe7260dfd4ba93ef02fadafcadeab58beea15b944e6525879657857018ed3866e95b2b3bb92bb57a93ab5723abf299c783c8be28d5dd2f0ade2dc19258ecac31b4723fbef86294b7f2284bbe73354fe63930cb77afefd2aa9e2b85cc0b08fa9cc5727b26b4b50a00eeff9f3f743e408a33d0f4f01ca14c8560d98b11f61667de84727f1ba33b59521ff391dba07a56fcb110b25b86154f9750fda5d33fb2853e08727e371d876acf239c441b4aa981bbf2e59378a3d983d30b8c10d58f7007de12190df1200f0797fa695ea32bb39407115e3a72416ed3cc89da9245ea55d4328772189816d3a8ec3e9037e62087089099add29c892b3906cdc06070c3cbcc32af33b840f8f96d1f08842c74e486811e98576ab44c7530feef6c09e0fd7e0ccd51423260db54b531d99f2797f70e3379b8429edde43ad0fb062da6463a3f7a914b7241d97e188f906bb41dd6cbfffd7bc62b937c6223350c3bdd0d8d7b4be8dd6a58fb2bd9e4e2ab8b683306039d3f2c95d6fd12eed1537c276a588ec812794ed3724deb3d5bc90f3ca033ae771c97d54d86d79823c6661b591ab3673aee18fb0c133d56655b409bd4d63b99ac3d11970f5a04894967c8159e4f297e73b9b1cc4e5c2e35582ca5552ec497723a8d8414513227b0bc09fa9d1e50b240e5b421fb4a7297b9b09aba31361a14e0aaa4cdb7e43d87962f54b4f6470fd493f3641245e372faae5ca411247c3fb80591fdf40428cc051b62704f1eed35e5fba1d77835c572b4355f17b4a338bb43313696f440f1c1c5106fde721f2bd8299d19207070c872cf18be4c313dea20030b2a91b8aebbd13276d51bc0fdd581945e1340ee38c304f7e110550f80792df14eb0761c6dc80a5b13b58aca649fde10dcbdf5d4277172d02ff18c6a17486e72f26950bb2b48479b0b4b81f84262d26d2f6cdef9e4297211eb8788be448049ef9b1569c0694d1f1640b5f52a9d6861110b10b1ff4cdd72ff74066e21d6d1e1116116514c1ce439414a4cf92e2e1ff94571ac6d3a3b8a726391034dcae29adbbddb85aa46314a81da51c860ed4d26df22c7341015bdc872dbced3e3091670ebef4a6655073c1fc55bc243c66f44a80c39b2eab436cf52723354433983d3272eda856fc19dad44255c034d7281de349dcf6bdcb0ce020172b170b2553b1141ee16dc43df9d0dec228c22dc33b625e52eb7053fb9f86d5b64397fbbe46f41a121611bdec1283bd9145a89010fb60151a3a51920a68f9f747209d8cc3770de787282d1d83b622caab44e9691a35021c6d08a47f95db02c8a72379c857f6e5e83287c096229919cfb24726f51db73bbec38f4adb4a3624d7372154c8d510ffff80d373cf22586e8fdfbeb6ada8d85c2fd5b9db86e7003eacc72a6e86e584c76a037f6ad1f69ad03592c35e3f96e9015233dd95c58f55877b97257de0b6caf6215bc2b410bccb505544f4dd4fdc312a5f210048e0659bf755d1acf658705e3adaeae0e9cc02510fc589f2b83596c164fdcd424295c76d6019c721943abcab1da9c03ecfaf8ff1aa4c83067b45a72c5598f13a8bf3401811193727d801eb50db3c5055d6e4c5f23cbad1c7d045198de699f46c328d38506f4c05450c0ce1f6abda5797944b1d37aa5bcbb6c41a868d0a8159f109d272a12995772f6b86e404f9d34dc3681b5d3d9941919801645809c131f3c705ea3f476d21f72d146ce227e63432c679385e2d89bad80bc36f0b8dedbc1c666625489d9c7fd6a93a5ace05e4279c756481be65f2392afe8ae5d3ee826cc5bed2799831597b116d4968f1eb9d9cb4fb9775a0600cfec0eee89c1e9bb2bdebd7f673abbd3a93e7264eac642dbf5ce6ba4d61dacb0176b3efa7ea895cd1d3546be4cbf58637987728e05be130e0a4c04ee708b3817ea25e305c0d211cb0a4816bd9f02e768073972bb902093b43c18078d987e93ba480faec87fdfaed03f53c497e74b7e8618a70e679fb540b557fad0a24a047d0a7f60c68f3e7d10825157be41ac9660516cc672472a708a4fab410c25b2c045347ff7f09c741b799d297e08dab9ac9fb3f5fe5bf56e11fb15d9080970bdcd4e6a3f6913d4fa102b80742f0e26bf1cc3b4e7e84a2d79be21cca7d53fd7dae17d16fc526a92c0ac3dd179a123adc7c808eb592d72cc543e0d9fe0deb808600bfbdec64239edec63794bb45aa632d269221ece5c72be6129769176d42a31032d4ecbb0a5da148c1b57f0425efca12d8619d3324572e43ce08353b02bef2aae3cb61ed3b96efe3796e5d32edb41145b62d9a619922446063859f5b803dc10c67053c99a56de271cfb29820453b61118748dffaa0a170d47d477fdc4567c0cf489cbbc6cc042c57a3c69e4bdc71907d571ec4feacc72629c50de257b95f65757f11315ef622b3c1e37c22a2c60d4228abc5d17a2b83d0d527ff313cdd06b46180378d265993bb2067ece62579f587ea6378961b95001a247db6a1a8b998d58a9d5b84e66c4fb87c49193642942e78aa4b9351782c772d8aac31c956d71be6f25ce3c4e406378fc9a36ea6f14e49e58a8010652240b7273c3ef11a38324541731c524daddcdcd7f74fdd29a362a4a0c0190a31d46f04d5cf7000d4a30285c1c906853873e9dc1afd74e209ea9af765fa0987d1e014172db942ebf8a944728e2b003ad34dd758292fd4edbbe4ded135cf644ad6c038972afc92d926fedeca60b5443ca494411f72af0ba590cea34317927d1cb004b6c5eb6ee7e45ec6d2c6b90c376dd99c38e1607379cb48b89ccfa773eeafb48d57f72ead9d80ce2140b5e69991d503025d5ac589b2afafeceb2030988f61b7937ad723aafd0a68f478d18dc7d67c3b38735604ab46188db1cd9e3af8fa610edd0f114fba42fe8120d27f7982fe7fd0f61d49895b9ab2b790fed85290b2679c267e947c7c45787d5f229741d00314a6fc8cb83242d4a35a4eac2bbbdefde3da3d84b3089310b4a5217d8aa1cc2d2c2a013d036d4cf3e6976c11fe227c64122a98925725d9494ed33ab39ca4768ead2f53c2aa2d146545e4e5e91e5a71a84691c948b72f1089a2ce8519a64bd3ff1b217d1cca9808a72c2e1273bab95d8c478a9f2237244324f07d40538aa8c39c7959137671795c8a0c16bef347c2a9b5a7f1be76872cbdaed90321d6df47a4d94c50f2fac712e66431a6eb7a94406d74abfcf9f123368535a76cbb18f18c380ad14b88958e587b80e816cd3e808ea4d5d984311a01fde2ece075b1f585064858987798b025a3b37ccdd718d92760641416e6a160d3b6ace04b9635aab1462cb5623c7fa6d75f2ea72053433fae5d5811498e8892a1a77a7958e92ac16888a7cf43be8a6358f2b82e6088781293a4197a6c95408d272e0b3574dec51d673ec53150499c1ee3eb4979ee742b8e393a019d284884df2720e0887eb31c566542b3ea380e5bb42019e9749122735bd901b714057db3f410093df8c8f9351f239fd4adc7e60adb84867156bdc353dab8f5f4f764e24954e322848567890e8b0e47953e450784ccc6bfa0d865802dd7982d85aaf8f50c85572508d87c24c223dca547b9930ae747274de7a1eb6e5eae746555481bad5a098728fd2372bf82a3b681f6e4b0875b9d6586de1352ae6a1d100ac4abc9f60b6ea257cc6b9b65fbdb37f4700ff3c6cb253f86f8468228bbf0fceede15cdafd674872b31287bc935fdd6600231343e70fd4aa9ddbc3740f51a05640d8f9338958fe4c1c9d1b20ab37cd1f283e86c6d19713e6e31c9640da4b5187ae73a5a9c5024872ce2bc19f54afc30c9d776afb8e8bb9edf7e42f6887574fdabc2b741f1e224c72c2e00f739b86e3aabd4d92e60ccd5a3c4d5cc8bd257ad8b224472c77a61690241f7a473aca7b2de52348b7e8d0b4d9720161b2c8e1b1425e1fdd5383aad8044fe7a792e144910db6cba8c9d4e74ce0ba089f440cd44f6764d5846155a130124c31b20b10a94e313c217c17fa55d89b5f9aaa7e3faeb3910e42d9f77623c76e7296ff6dd304297d611ac262116e828c9c2301f92d783a032fda19ce8330f57a7065d48ee2b9781367e9d6fc52b1d2cc658f246a670da736c7d3864ec0c7d92b559c9947b3cad948bfffd8759bb3b4e3a093002ca7a1f51ecdf1976e7e925ffa7222877ec88c3f3188d814a9ba84da90c5d40130963c91ef65eb1f7df7c2a86b723287fc28ba1b18ba12fd95c619d5b084f362978c947c8828fb3fb9d78210e972505532893e21d0fbf34bb880a33b75bad735c711ad6d61e12dd34dbe8f904172e11c75681e7ef00549064d8ea712fe6d50574305d732f1c9f0bfd1070d3db772122119933152931b967694ff995daaa014822b8b3362160fcf5504cdbaafee013a1913fde1fbb0a027e9968befc7475e94d6e583a9d6c10325f5cdfc7e3bf57210366a9c2de1a25f06aace384449d4e10b4dbe4c55683ab59775efa54b366f72e8d12d066aaaa65f72f956e58194ed9d2648375e88f7fb3b55dcdd0afacf0007ddfc8400aaad978852e9c30cd98e69bb3cb07f8c6a3919692f167fad3fed0472ddb3c9f2d0c50407b4e20517bf86601c7d294ddd974e214e8463a30209fddd72eebd54de3e17f3814fcf612841f93caffd785d47616f967367e051c333ce2b59781b6eea8a88f8556bb90e56a5a6953a47f9ae4e860ea2a92f2acb48d5ef7462542ad6025331f6c7ce1057210bcc89300d3fbfb2cd7bd0c406cdb6f391df6643cfdf654e413d1a44ba40dc295724b72f8228f3b148d2f82d2ab2d5da32b0301d7c52c959538ee5093d2f9ca423497578901090392e1ac75d11b48c30f8853e26b4f4724f9e8b2d17732ec2db2f86e2d1a4d824a4d3288ca0424d91e3be361d72eb145bde39bf250e02292ea0aaaec8d66e27b7406036b2a3c502c0ca9060f9469635bf31cbe44065cbd77d03699510c6a94a64880faee1f675cd858c4efb845383f688735b50ff0aa979a8191663da5422c35d7a04d05fb5c036726b42e4457222e8a8f31f96497be6ed4f8c15e7b6dbb25a91f72eef4bcce2e7fef0e5f46872e0122f792489d184be05c969d9cacbb81c3c1e0922ef58df3e467b5fb6c053474d3cd21402a7ca32774051a12f89f71477c1c9fa3ca1fa20624f42259a29e772f04200dc6516aa878441f0b5430a2a3596ff3e87ad3aa5f9dfbf01b07eccf87247ffabb1f4df947b52b0e54056443121ad029c3e151f93874118aac7a9ef802d4539c0dbe3d31170cc3d6ffbf53e7745fd0140e8de088828bcb3b81e11d5bc72e8633e7993b326809e37894c0cbf319aeb976e6a421d9a99ba1c5c116051ce061ce293b6141761f23957b524b4c6fb3281de26464113cd610818dffcd7520a6f3bdf0d4b357b9ddd13db8f63700780d98d316a08fc47be157f2daf6ff2c713367d8499fa3d826caba1641d7be08d50f5194904cfbbc7e953eca036e14bfd8972325f29f012eb31cfffc203185fb70a14996811a52b6df691b9cb6de2c7c5e3725a3a1629db8cc93001d47cef0a67d520787880282138f477a7d19b6fa86d51526aa0fa636c95a0fa6fca4c13eb8307d6d4f4c075b811d4138466ab4cf0d68b181e7e4e9e9789e263c0d35546935cd5142df1164fea78f8ea5a0ed773a318cd724ecc75471676ce2c5751eb142e4dfda06ad8d1e139e48a2119be0bec253d2672ef294b5972b4e2234f5ae1685e8eeca24a41da632a0a2bbc116581ec77578a0b88dad31b7074341cd05e2b0e1b1fe55401249c84fd3cfc405debf58b6536547247501eb366d4320a53e34e542636268bae9ddbf39187461a5101f7ee774c00729ca7ed7b88c5a4340238a52dbd118aee1f6bf677e5f51702ebc858f002fd8e72673d65891bea2ebaf08f57eca7543a8b67f0b5af473d18e3c1e3b65e77c8ee230b573df3278367c2e307909a2651c73a13ea8cfb48012838d439cd9bed95c429c3b34b6384bcbfde3f7a8d25953ad954a5d7c9e07cb75731b63a1e6405f04f72160f1aa184cba884e9491855bc5f60dbf4eaeae5c7307e8a3f517667675fbb7291cedbcadaa3ea2482cc0939f69b1ed4b39c0be16344e92ec8218af1c9d5ca72dc0d05690f578a3be55a418822680c5f5e2e30d7c52694125922c27f9729e872e6a5cb266ebe907ce8b8385de1dacbcfa68f91cde4f5f1eb6ed1ba5576615772c5f7b54592b68a5cf92ce842e29ae1007add9bf4807d1248de34a01284e4ed72f88c2157e6cbae8641e407df39585e9eedd25df56bcdfb227636f321a3f453727b3c15d5a3ed6b1941d4a99e0ec035c27deeeab1ac379e0cd15f9a9ade919d7240f3b60a5d20949d739652d1391eea2c1cf90757224b42816b859d230e54524c0b155834b1e597508b658816405d493893f1faa2a432bc4244a148adbecefc2a96bb0fc5e04989052ed99f31a944d0b7b857f0be58ac9c0f9b62e017979b7372e521dd6fa631db72fbd362dc84ded008a4bc04bca92bb4e44bb7bd8b84d46205ddd8e06f4fd23723590cc576b73554a100d70c61b567a8d88dfefea1c63fb2723b64a9e659d6dac1fcefa3878dc5fc979b27dc83487a1802355d8e00250b70722d20f23b8ce240e15d05d4d0f407c46b0a0e8e73143a8c38d2396332aacc3c6847746bdaa911daaaac01d1c20657c12d183cd93eedb6ea1ad0ead960cc3f04015ad04aa880a7d55f89a9448865c93df6e2baaad06a4db3046a6e78e6787a617238666604ec02d828651eca7f7d6e367c4c25ba56558cdc9b6615a8da0a53f47202c4112ed23c008bc114163428de0f762f244b38a7f8653c3a7dcbe4b30a857259306301f6723512c02137356db03fd00709cb28fd84cac540d7b36242fd2c25d1e8d9098309e2c337bea23ba8753227cdeaa68fb681cc8656212919b38242725580cd22bb6ce014834cf163402c064f5760cef095c62ca53f6cf6bdd11408727fb00306006f44c6614e9c92bc333087f02e85fecd47b927b436d55e27781306bc3150341d3d681987f3b3dd246f25a6464c8c9c23c542455970a59f9fda9872b1b6dbd170b930daba0b94c54e232a025bb64c8409b50a633c0ee8602e4a7b7231cb21c929bbedde7d077a777d28bd06efab4ddc8cb9ed58da63aa7b8fdcae368f96b8dd1aef2d0bd8a29fc93956379b6783d8802d54bc8ec4061d9253727f727514875c5517b5646cd8d427ea6b291a8c6779ab30c7938c5a1d04cded512572fee69e518c6eacef037b803528f183029fa6e04e23ed04182c5d444982ea0d72b60bc1d01eb9a50bcb40789f31819c528fc6e2d6ad859bda465e54cc31678c725a41c5069b5c291d04c182d3ea76a40c52c8bccae4fadb6943c80ef2629d63408fcdba0c338583b7d1f448d6c364ccb5093e0c6a94677fe73d0fe17a8be16060148cf3807767d8666c484dd17d70af400391c61065907cde21a2663941f0f049ec0c42b23370d96d744e20604faa5e459afc1abb0537764af4a360128bab947235d314d3b53167ecf9a5c2fc7804222494a64c3498272a7ce83c4ea2484e257285b22acde67098aa3885a6125abfbe1e24effe02d679f9ad0f8c784c01b3e8722282e0c5186a104e0e1295e35c6b25205410188db4da49b49869516e9d8dc272c419e5196c0b46ab86a11308cb861085a0edd046e8a1628b831259022aad770f14f39bd4a1f18b6588c4fa2ba771d439f8f3def804f239075464007021753e411309afd48bf352049ebb9435d8f459a8e295ffea9770fc0bd409d320a7810165d0d0886d65441c80aaf5204ab4f7a7431d37cea7abcd5e94a303f417796df80a3429b963ec32c0bdbc20ffeb9c93ba2bdadda0c463b4588068176bec3678dd72f179f1810077e9f713f434e18c4cf7f7c7fc2c927b3ec1ee592d11ed34970372514164d970e9f223f3d31ce8f1a55ca10139b1ce77259e51bbb478f5132fe74d0c55e8e20a1a12334afeed0db9bc5a2da3bdaca775c452b2d52b7ca6f42a89521c81de9b3c916b0e2fb67926880e52b40303cf52ccb8d2de8f299c24020fb866c865cced32ffe03e8ff60d3c0950aaa7827b025bc678318121add82686ce447281ca06ca2837fe342a9395d737465eb09eed3ca84e9759ef2ea1a035fe94151263922377c2ec27577c7e7938a4e3d015eb23aadd477688a0c739effb43c4da725b0c6ac4b894c884bf794c334a3b71fc3fafe3012217847ba1b20da4364e43319687e34ac1ba968746fbcf99a522bda7fb9a7abd35df1470ad41b7c9773a8f72640c54d33d5f8c878a34a4631477f539cb4c0f0d8b0d832c4daa3f7830e715721d305c8bf119f776f2577176c14863328f61f9a709037354edcffe2951e5fa72cfbef0fc3f23cbc84797f4aabf541309c73e322dabb1c92f063678a17338aa7265fc3888a039ed37c7c7ea9eefc23f194a053139eaeb7df29dd1a378251a4847dae9a488b6c840d99bb26e81d8720d76aca1e4629e0c63d3b3335718cbbff8540fafbda151f49705d18fd4a13a92f2f453b507642720538cfec383581f500672ad316e0b7e7b4393d79f6cb352f600d999bc068da4a11b2d5dbca68d7852fd709feeea4efd40b6314f7067ee1f01b223af45238def6c4b0480f75c07bac8a4729799e92a0e142b0701f0367f6040aa7761077ce4ffe33cdd71f63512114f2c7279e498db4bfe26f25c3ef301b9fb1ef40f3cfe5b95ff8ff49352fe9e68e88f72a9ce60a0aabbb7b8512ce5fa947c84fc83dd7ddaf7998ca7d532637c3d047f22fc6c327aad47335daa2e600fa1f78c9036703431331db9ddb255035b04eb0f722e6783e446de11a57aa71cff00e9295aac927893632da831a1b4f3869b383106b92de1bde3f0318e50d1cf1fc350788c7d41c2aa7e7206ea7770772172b6c5723f08cd183eb1215dfc24a9c7bf1184bc026a486962a9117d35d6b0575aed4d4fb6b77770d8cd8de9e5abb84e2b0beb2905f2679517bd0744f4aac5b73ae5af7201e77dfcdba85d2026b914c8c14f5ab49808fa78ffc2a8ca4c9736a205933772b2005c3a9eb91c31446d442d764ec2cbd857bd1d40e4efb28104b39802164c72258aa6578647423606ebf5e3ea4757c5c85f2f25a211d4cbe493a4650e6a365f836b4af9e6a9f8f27f470975e2258c95deffdcb22561e968e31cccd425ddbd7231c4e9119e7c98a44c05243a33ffe96666ee9557b7f1dcb9586ec9c3b2c4cf72844b6ce5797a2bf4c205edb005a12c31f1e14ca70fdface129c76ff61abe561895b7147bb68a9ba72271eed7421e7af3e47a89d09f9c06f16ad105eb6d6f4f72e6c53561789a56f6278b3315d6e028ef346988c2bb0e892866bf2c64c3f50c6e14b3b8bfb8542ab5210cb59fcdd1f09a37bd0600d7eae9f943988f9502722a308e3734315031776556bbb704252401426b42b44a5c92a79ebec0691e3c2f9a722c4ae3a0aeb265f2312d59151d17b7c7f6b335f78a01d07c4f85aedc08db035ed3a50b01bcdd5ff3eb3e781d2705d22078c85cf2249605c6b7a803d16c72a304325f79580d4c2ceabb63d6801da9e916f827aaf69bb043d71740dc3f4cd99e729133e383756328cf3d6d3caad6a9dabb716620291ea7eab1367185fe251456728383c1ffccfa8b24bd8b6ee326346be57fba78e872603dc7d1af83f37ee100725e6a9be9d125a9d3d7a0e055c5a5647c9d8d38a40096eddf3769ffb4be36f00e1342a59115141537e0d379aa7492f0a01a350eb39472817d800ca7b30660e75136c1e309f2d339912f54cb761e884a2f2d5a86c59aa7ecd4ca843bbb8df89c2f953ba359b1cdb90ab500ddcd4564d6f2bb26ed92077fa242c1718026bbf4c272c8e8cafa4f29d2f166b10f5fca3947443cb79dfb8a8711485b0dd2e262b06972157fc9a4417ff57f7b89d6c9d2aa5d606f90ded31a7951d5b0a3d68668c6e93f2e0611e495e28edd917184166c054345dc40af9c07ce3ea9340920816c9b207240cd0b49e40a8238f3a62e875fc675d7333b3c3088e45227b74d7c3c426b4c72369c51d5a42bfd377b1a1bfb47866fe5ecb5e42c1c0b84070fd561763b6dd70473c7e9e61519c9b8c0a931c8e8a00aa3196ac1e6bbc2d8486887d1400d91ea72b8e503718ae81651017a11b47d09ca1e5825e357d42da4166b85f86a6012b8727a48c9da1487793efd375b62e9077bc34e403ac1264e5f8ec7c05936d2cf295c6e798ea8789758f0d9d08cc7512add8f67aefbe9f78d716000eb03a9c4d5c57274a033847a46dc8366741fd8f6c0798db32f87d2e5dd83274abfd3517f74307239d93dce920f623c6d7233aefb3857445b87e2085965a1f4e728ceb8760ef603c235b314768ade95e4ea93dbe78c23aeadba50e5da385d1862d2771897b93101408f2e9b9548baf04b2a551fe356e138909c65ed334cce85a69efbd1a2ba4172f6361abf3d58d20d8f7bb06d326b02471df42128c677e74c80d2ffbe13cd1a724026b59d7a02d872b030648c963102904ef6f4835895f6ceb26580be46cc4172e75fb5827998f115dcb058a0a6dee1eb78832b22556d77ef8bb589bae2451272ca6af06eb1eced16b88cab21b0f2157196e61abcf0857bac6b242c0b5d78913881718259afc70c6931da43c7daaff63ef495ed597dae17332dc1f979ab0eac72825b7e803981681355ba4b828e7a4ec98683cad3fffce57a29fbfc8a77dc1225edc5ea2618fd441ed124eb48995365ffa3596d2ae753c4de2fe5d402eb44df7258901b8556c2fb0f7ef3b8008533f159b0055234367c858e773fcd43661ac44dcd31f916364fa7832eb423c2adaaa2bfc0e00922e076ed64622d5d4c7d65365943ac097b29f190b4cfa5d6e5bd3be67e23a9f3c461e551339aa3ce6f2ef0b3726d1a0368ba066824cb4ff002e0689023422064aa240235f859507e88e0dd5a6d46b94681c6e4f895a8263811849d257e01479ca241b07203f9468fd0259bb66f9ce7ae9f015f61f003b649885a49a6d83968d03f85861bb36ec77523db77047233b2f90a67f535b05db9899bea3f056c7bac79e249f34e8588c3183878ca121e212b1092e9852a1e581c6391267f69d01c8239a07358f98d66d165b5c8413813dac0f7e936e1d435f3ec1302b16c8371c6887d7d4f8518dd07178d910af5f4720c02ef55e232d1a32ae0d977fc9c4d0b195e652cf798b7250f95e3a058bc0e6d575ca273396c7d63ad68356560b400b0aacf18882b67d4c2f32b31980e3a6272238c792db4e6a882230a50eb08b2f3a16974ae9baf7b3c8b14b489b549f08f5f9eaa12a86da882134a870f8cbeb68259829dd4417b02b443924c389195c95f723d339019d4878d70df6003ff8e08aebdb12ea29844f5fae59518056e123361726628fc1d8e35093a12eb4abb183deb6c536d3c830a75c34d3f3cc67c358d19729531a0ece1cbbc62b59c470926192a583bbdcd035b78d67ac6555bceb28c687264eb65df6a91c6b4ab86825874335c553bb4dbb54037675b751643bd241c5172d0daa262353da536f5fc6b18282b7f1d5ef8126652eb5cc09fc77440c3f7dc72454b955afa5b9417412f1f4f05993fa41ed313a66511008a68a8d5e87e381972fbcc490e8790600d16d8a73fc889058f9f53eb25fbdf8922b81323c6f804f5104b06213aada9bee6d070ca7677653ac49d62d8c60d9fb46b5e04d43c8ade8932a37e9c6182a9f450cc9c4c8800dc965d2414c0d7f9238d7365f34d4816bd24723d903f82af5194e163cac2194867a122c12a27852d0929880e4bc6a023a18c72fdd2b5068f9eb9a504672333276d26383837d837e65c288ca7b40a291920c9728f8848dc874b33efebbcdf259834f866374e4ccf1208bebadd2fa21085ed8c72e36ca59465798ae4e23cc31f4265c9a38a83de9c4e85bb495af786b20016c15121a981a2d60376a07c8d34bfc837dc70d99084db4c91e8cf74f6979042364e72c03336d0bd0ad3454af07719282a89151e3b8b78ea3a002b6d98b35608ac7772dfa7ff063fe08180460481a44574206205f763df999bdc04ef158c84ebdacf721eae3536ad457a6a577299df500333042ffd65ad805d93e123ce29a41d124d65a494c377e1956a5bfaccee808a965c822b02dcff433830e3a8cb46fc55e0f614eff238fccb9be1f8241f0616486675f40baff44aca2b39dd9cc0a074fb248172ae202061d392a85fd298d202bcf23f71d1c401642529f1dc5104cc992a163329c3af400fe326e0e7ca69eb3be2802fa7543a13711698b8e7289c8a193954e4727a227b66612f2b7387d0904bee871847ed250dfd77d6deb6040ef81428c9ff4698be000185a285d7847bfba723028999d768070b6e37aed362a069d72866216e7474ec38ef665526fdd04e4be48c6e5ffa1e8e38caf9c04c15e1ccb08486ee72fafbc8ed3d7cdd0f0ff10c3c2c243fa4f082bfc0de51fa49aa440d23d2f33e72615c17f20d8724b5c13bac7758f18e3f7f7693e9e2024af32973b316fd4dfe4e98b9b28e1789af7ef68d68cd420dba420b46fb3c228457bb2af36103593eec7256f8ee905008d38004015c8a9d84645684056ed04f235c3c74f7fd548711dd72e5119e9b3872384ae5ffe9c8fe5156a1b39b9075dbdffd8c7d20d0de98d492729171479caa65fa0ab9188b9c1d0c9f5907554ebbfb9ea9f8fbdab99b20640b720d73ce163c4e203d04add777db47c65d9a548d7979a8b56f99d9894c663571327940908f21302210a92ae44e0554807fb2e9ea5fbf1eb8964334510f03424e72aeb56477ffcee1643e8b6195003d02f7c48fbe2eba2e024341be4cbedee9e5516ca59c3f876ba6927aa8e4cd9ecaf4e33715d28f682f585e9830b2114fa4e17294964d7daea582eee3de28a97741d53ce626efb4a1589aa99e2d8797aa65ba724eb181622629f06f9d220c273d0d82807f002be31d49f441631626408f641872168bed59ca72ad121a86277197043b424695e5807ec0c914c65fd124f2da1d72ed5bcaeaf79025d3cb2d77cee6bacf538f3513aa22945494c8ccf4d3b3f43e305ec8bad0ee2be77b660ea89f6b32b4b981a5d1f0b0b5b99e005adbcdb3dde272855a918bd1924c9d0709b1601a46c13dd2fe5e8f9d5ba71053867babe80f754a4a3a2707a97bbd09c077c1897516837f9cf3d3ec9ae1776f14107fcef7b95f6e4a9e160b4109e4650925899dc419617ee203208f8341aeb5a2780c41b92baa7275bc38a5c5118ba55a72dca7dce6ab51b4ce340945777fa52dd007fd32b50a72f3c0b76c60c6a12e3e0bccdfb241ee882a0b8f7bf1379e22f5768923c1471b72a9fd0daee98b4c4bdc30035ef915689c7e5be93e542fb0e8818718e33c68e32a2d06d79cafb051e64ea3a90ff3c5ce3579a4dd92f60ad921ebfbfed726441239e7dc66f78050725d84c73097cf4194ade1c702961cc20d681d12f1f985b37872e2bc97e93ca1ef07ac872fe07fab123dee1608dd0101a622366fe94532504872f4797ef6c915d25add5d251898169ad244762cd5d41da1aec834cddba989b772c64e52e377fa69dbaf6f5ad0cd3f72fd7bf8567372e76d7eb168caba6e59df2b35dd162037ce165d9839fb98d1b2a2654dc8afb10091f0cb3b6dc3231c442872b322ae6361547b60875e9c8d319eb7921f7468139f0b23baf2232c652b88156f89d79a66aa2dae8f835f07fe096e4374d0f39f8466b65b8d0264f374c5214d2d3b883f79da4a3847352a6d45369fb4268718d1e38daae85bd5353dcc51fa6220c6f84c39e9531aabfa08292507a22a18e930cb6cba5c5f210d87129a49395d5fe4e61a73c499f967dc9b179a4bbe8b7c92c2183f051cdbc34885541c29dd616f3f0476b336d4fb0789484d3bbe8633715c356b5dc9457e6f53851f4c060cbe72cf4a23f08c1a1f05e058f657af46ffcc6ad3882090850a6152c8d691466a8272a3b32b79ffd4be10b90daa0b6bacb3e6bbb8ec4c5decdc6f70b481f63f99df080b58c2e8e13466faa088ad078ee809f322535078687e590748e24b480e5f6a1ed9dc6976d93569060ab4810f846db4fca68677977720146d27b55e14368d4e32fad03e06c928cee6ea1d812982a717be21f0aa97fb8192892fe036de042dbc72ffbde7e28a6dc5817662ecc49c5c0d71025434e001ca1c37c1289f48a93fdf72a42c398225e8f19cd60b18a81139727fd809b7b8683c4c6866c299f27ee5021b704c7bbdc42b830c57621022ae1a6f5440db53f020d33b6d4982e117f3ace5720c6b84c9b575a155eff88c9768b9f3357e95b8195e9b3f888ca952dc931d76727b6d0a863c0b8138ca81e095ee8f337fc60d1f0ce05bc1e1e22212993c9fe77295415d9d024f4ca91cb0f0c360917d9606ef7fb1e3c4e67431e4e89a7ed2c56a05eb29e2265a94058ec993123e40fb3873a0e7be0d2f62b913a964a53ed0c7727e00380322ba42225e3eb48a6c8859280d2c5751f0ea888db8db75847856e35dad527ff93ee2acd68ce080a263ad0f2f7af9e268d3b186a4f5bf04aadb0a3a727ccc1f2b4ebeb9d0cfc07ce82a4e703b0fa8d32b3b2aa0127d4baa1e90831c72a74235d9c0c501992b9d8d63810b1fa5cb9e5f2c260d5117702fb82f4017200f04bbea70b8793f4acb4f41b1572251039065834e37d88659d22834d7e2324b729417ee54f9bcb13fead27f05b326a754089970ea3b68ea4eddc3430c1ca3357209cf44dd5dbc5eee6641db7996c902b35aea7749a9c56c7c943285eea80dcc72a23161a12cc6e4c915e2bfd6deebd13963a57f355558cdab69d1f8b98fbf4e49fe677402562f85b293c1ded0a8397bf9b7a259c18de9c9c8e7eece971aef5072ad1c46b9616b32f75860ba8e1584119fc2913e5e670bb7334358e56e9b586672f695fe7b8e6041bfcae8d20da66d0277d1d1c02857e15868d271d8d023fbd5726c68052357dc83d7493cc8ab59d5bb4f9db26ac51958b079c7607f75571dd63a93848f90dfc1350add01c1db4f8a0d7197926724de280a4d4d33ca59fad6ae72b6e3ddf80f7bcc6477d36b430ed9862dc1c34a9058f29f943204f31817fe4a72d519443987a4cae9a74e056d9016e1cfa296d0d5992acda489afb4d61df136242e3c8c5199ca149da64bdcbd032951e9276f3986720202a08129d04a48b9ae65a5024a9ae71729ca7dbd43a0c51af76894c141442e6607ca80c69998a59f577276b147efd140f1cd637ca70052a045c5eb8d5a34854dfc100e00f4d529576c7243c7c6ee24b8fddbf1420fc9365c930ddced1ba25cb8d535a9c6f769ae2cd75bce9eead3e32236d1171210801026cafc74df80edcbcb6c70bb3d11d098b18835aedf32fac68a53256e34a38338112f6e5fdd8d08ece40e78728f4a305007f058db238da8a3b37a549d5ff9f372fff42e56bd92b24538b1df039c0d5ee943dc72f93820ffeb1d73c2c3e71d14cb5e1b3f057b7702b8c5758bf5cc2d1ede7125727eb6352c475d60216bedbfe53b9b25290acf817b3c714097e1b8bb47fb18805eaf1d189df470d0b04a85aa9881df536fa6157dbe90aa0d7ff3c70e8c847d4037c8f3ee43987abaa743e2bb8d03362da6407fbf3202b52ae6302219ffde563e7270582c6284ea54bfbb97f80e738b9c11331ef8b263ef4f6483e956cd28059e72cbfb4b132e042f2cd85bfc2c9b911de9918a7e77178861dd3c53c9f3bb4e6b7231348590b4c871e1d3b5d9a56ff9cf36f7ae09c8ff363409d70e160c55425c3029544c74e447382dcc8090daa914bddba663ae1bc26f66949a4992707bf94572ae65fe74a738c0df6c40ae5d7dfd1f4283ca92014101c340a1c4740ac8db0f6ae2691f0692b6a89d1e0642d488559e36e16b4c160f1c5fbf4897bc97d8146072025c2c8cb575a267cc4adebe8399ea2439e8d0c3ac28e9491fcaa14b7b0bfb726f50204bec8cbbf03225ba86db3ead8c9d7a908f2669c0f1f09379ffadd3c00e53851e8be1e7296d8a5246e2b7911585b3d7a6483c1b1c370473aaa4204baf520036ec39618c1bea451aad0d9a5562778a18219bebaa82b0930ecda5bee18568550760003b3679fbe53125b66bacbeeb42a67f31af0b009bc9bd5ddf63fc977280fe3027304628994abfb34482e4a9eb3d92d67dd5b03a87eddeb207b549874a30ae41b3ccd82e5f3c316f247b353deffbf16731b465190ddc2bfd5e9f4e1e72856d4797359bec116f3475839001db59f9a8de63d1565765c538cd0e72764972d7110d58c67f5b33f3b18d9f3fbad2161df853b23cd22eb438a39a7b07e09a721831469ca2f9d031f1794334a0096d987a0fad1ee3c50446df2cbf60ee08cf381382f9a9c8e7030527a56b8c36b0e8c4459ae53e2446becddec4b99de2843972f83e3016fd7bcf25a682c90330fce1ad626727cfa2346861641d6a909cc47c72dc934501995411675ff337826ca518e9cd6fd0c7c893a5f35b7bf3fdaf979c727b336729f787e2e1a671f5a4e74d4d65fe536320272320b7abd0c8367299ea17e3c9b949dae1b3cd9d3f5df5873caad3e35b0042809f26a5faf0339edc0ef672c7c02b75406d8c643c734e66452a074edc8a7147dc8d7ca28e4a8968bb9d6c72de7c395937c2fd2f7df6e931b8b1d16ca315139e0a0a2d7632470d1a6b95cb72629a9810a57af9dcde0a64eda8a747b7a9965f32dddc214cce64c2508df65911f68a25b107443fe26f7b21d0620558102295c2daba7119ad3db2f077f3910172152adfb7cdd39599da2514438663ca22b9bb54e7a3bd91c57df15b4a80e22d67f5823db2e974a2adb18fbec057b82bdb1d7fd5cc4b734c2da369fc90e4d7160296812545dd184912b54667b52315743416ba061399d466b54b6d1adbf353f672459b696af54cd094428fe8b86504759a14ca5a9d3a1e1396ad349f1eba73383c2c75da9e6c765980de41adc639d1f9a475cc0a4c1b6a25e0a23d67311b302d0e8b0f1271daeba635a8ca30be9b011366b2b977cd1411bda7f6322720ca816f3b95f45843c7873b3627a641fe7c261d2bc825fbbe4bb46946c32aaf2d17571a72cf19251bb8b93d59f64a92965edcdea0393a834513410fa5bbc1b5c76c244572a479fa6f9591ffbecd7d1da90b300b240f2c636a93d2465ec455e2e6b81be47268d4fca14c75eac197dbd28e6d7fee7aaef86520b650a945d2d19f9aa62c1c04d9936d66acee668a7deaec03b50fab24a17356f414dbe30c4bc5767bc72387728a73560aec822560e4e408f92bef40df091e2471912044facd3278edd5acc47278a7e74eb6903f670847df5a30c9ed2d89f92ffb011f5882726a7bbaf86c8072264384a6bf6bbfcb87abc857404f2b2077d33d39c377676d3466d382bed9012d77c0aa94390685d5654d83dc01fb64d54869f2ff37333e531efae91a55b8eb067ceb303bb209b06bdaad490e5396789a64321ecd67485cc2b10f044ccd3e5c72faa81774476a46eb8af0494a8e90e27a4161f0a89e6eb802f2edda4790436f2733e838294c8460d1c7849be37050a331941e4d1ddb4fbedfd2048b043c4fd172204a8f6ad156c94dde20439d0612c614f7dd787fcd6ef2175c58b50e58425672b49748ca668207b9369095d2059203a192378409f9a5412f8895bef18efb1b72333c3678171125df90547448422bc284fd5390b02eb01fbd35dd0373bfa06072417f151883366922ca83522269e988aca888b740ea0335d45c7fe11e40737e72dbdf166a4087fefce705c7ab948a62b6de65695664c8f154919b5faf8cc0fe7273063622b2dc38e4eb88d397cf991fc990d5865ce039b238e662e5f626081124d9b3db794fec0bab77851ddf82edbd9c33d6dada5d2a5160190482d3a0efd372e596974e58680e3954997ef5e73c4d2ba6270b68fbf46a94bb38699b4aa5f572116868c1a941d245ea529be2efd3d964259581a807eba8bdc206f0f0b73c1f72a8d65c6ba4c575b8438f0777dd6b72e35dec2dff0880b54acc1666d27c9da8004096548a140b056f626c5d13fd32b0162380e8da577acc3c8bbc22b686142c727b5aba7729818d423c1ccf91be1e186a837db4c94c45f6f4f647907b54f4ac72489caa1a353e6917271aefcec4ae3b7023432ec3c6acbc6ef403898e69db8872c5126dbc018b17f49ae4e04f003607ebf81535a3128ff9a0559b7ddfe2d13f4a9e9a9f7418665cd21d7a8d52b65d3490a452f320b060fee6e046bdbbf1cf724ccfd96a3d0f6f95c31fb34510cc867002a345fe5531101a0d6b910d69825a17466a6a297050ba03748121899a5c051b35a787a4ac534859a888179e7e2e93f3461c1850c39676be9b2656a9bf31c3c22ed04d9748a49dbadbba0b7b4cb571e272d46c0d4b7fb6c51b6ed1de756d0df3d62124207e03235455932722e3ab12fc728618c2c9be13e3d58e678f2246bf8054f8b693c8e0529e0174f621845468ee02b4937b1e6e99fe7011387257066076fa9516f06b30f397b32cc8f4b42919dd722f3e5026bd51caca13182802b07d329ce87ca9068bdafa2f8975fe70ad0165724ebc342ab0c2618dfa9be7223ec20fde85068c27b68084ff9ac809fbbcc76f47909361c55e1bb68de023a9dd1eb1deb1e0f3c53faf9f34c0f643e35c24b0c67213c01b89856d92eb6ba95c09cae7f906cdc650cf9802706a7824f0a20092bb23b3eabb0faa20d62f5bf718cadf263ae325c18f624feb60b10e79533e1975eb6e195d949604ebaa4ef76921ed4ce2ed1d4b9bc53df191f10cedff2d81dfffcc5225ac64c84118e09864b294cdf14b33f2ba607aca940eb8acff4c26b6b862623cdc6831b311f8d720c0b01c74219af06f5e1bd34f5a9d754b9dcd25e23d49ec39dde644bc296730917711e14756be14db778f416dc8a1d2f937472d4060469d72c49e3ccfc6d4b2a1179543eb69d0b89b7c87153c0ed676ae40c2c4998b29c272273eb893049de8c6e23b79efdfab73299085f39de7b1f95fb8c957aca7830372c6dc710901d2037a95c788cf4682d0e7e5bf845ab34e1886117bb721945ce172d9c062f990cf87dfb6b7f571791230dc11d55dbed0650ea7d150840fa789437283d5f8756e387f329886f41ae317135b9ae985930e1b193a783d8b582fef7207ecadd2626cde316a47581996629dc85a6eb6d5f8278042fe662e2897ddd0b572e65f50b7ef44b18faa5521051a8bd84aa4c3b27845b55ca72f61f03cffa22a72b0343b72ac5d0492b16009affc66052b12776838387a4e36249083a1e48ea117d9927c49e2431afe21f270329cd461432080f6a0e8dbc3a548b48df9d94c3244e969f60d0f40d49476e5b89a9e5d3cf2e7aec989158360d6389da12653d436725bce5b10bc4cd37dd40dc6dbf4ef37d6d6bc96966a58f73a42b1dfbfeb33c54bc9906a64e7bc5b4bd9ecb923e9b4d278ad01fcfca5a3a25465eed7bda0b47b4768d27a712cbb05d71e2ae1b4f12dfc9d16750ce30c2fb38b2d608a5683bab272382e4fcc90ccc73cd15496cc7371d8f1bdd6c0242c551932b3af78aa94b65772eef827d16578a2988e391ff08e2f309a40973fb95267e1c5b3022d2b510a3d72b574999fc3ca92d8384a5ad84de9a30819181414f2d85e3f4cdd87580074c972da81341746a0b664dd51f5319386ef659815715c00238000827fd19c72037072e49ac235c4c6d805ee605a329d1c45c58e0329062716b013fa2964a7a63dc372320972445ba285a807ff6b54622022071c9957e06b6a8f6bdfb8c777e7346c72cc907bd08bdb7a89038b8f8815c720843ea13b0e6389e35d199e57fed46d12426873bea0aad9f3c21a08d6d0e139f432e914eb32acaf2ccb9dbf7061423cad49faf0c614800c1f2d24e30101f42c9d03b594dd41769fb24e3baa6286fe7b797222ecfa5642832f8b0d77f8d9328cfe52aefd865ced6c82002b4426db5a9de8383f7ec0e334af9c265836e4f7c128650dafe4831287e1dc06a287cd003a148172555bd94c1de069cbf64fcc864936ca197e701691d6175d706c3141f63dca7311283f8835e3db9a88c1d7d630d45dba57212b9b1029c1e8f2867ce4136f5e387240b5a173541ab9af7ce1916b5c7ee6dd4f614a50ed956b96c0152b09a31cb872e15d7f5b76de52d160f143ef0689607b48ea97ee75dddf3161f1812fbb4c1472c6ade3edd7e7cca92901d5d3a4466117b24bf6330ce552c1d3e0ef76833a9a72a9f4da2885605396749242675e9d9d1796708a32348d982bafa2ab22df9f5c729dcc7ba1835ff1f2ba8818dcadf414f3e6f6b4f3becd32e6ece5635ec3b8e95f82be208851451054e49558a886b23713126a8e634b08d34830e5a41665055672995f1ae71d2c8a77664030cb23487b782464fbc730b68ae1bf08f3543dd453727aa08f1aacf2c427f0789cd7d52eb3a7bb196f0a655d271bdc0d0b36fa9d8372566db819d6789784847b2b2729be4dea40226c62a3cd8d01170d38627e82f321e0b60fd81371a2d6e491e79022aac92d8d97b647d3debaa8df124ce839748a72ae312bb39317e1a282da7b29be24579a72f70a5e31b5041a47db84a459a4897161ab2c0a7779e351c3cda4eb0e6cab805e75ad2cd59b84178a9192c93ad830727b569308cec1428447fb444ed988651621100235d167ad6375f8c92c1cb7ec30e35f0adc9382eb3ef143dd1e999ecc16af54e85570a02986a3c45edb45d1757285b22af96916805dc30ed51887525aa9d0d1e46bdcccccb5356bd42135cc9b72866df948dcfbd230f49f1bfe31391f5f3ad18f2a5bac275e8360c711449a7672b4e28d20c2a06a3689459d91efa25333af07727663cbb05551f662cda46fc85635da3436563ef952e31dd2030cd9e733cf20bc51c9f1512baac539b63704493bab937f8f36d48cc019d3e9e1ccdf4d7e4d43a70b19ef9599d357e158ed94e872613cfb1fea1e381296e25eaa70aae2a41929d72ce3bcecbbf225656fc4c3b072ccf591bcb1ddccd332ebb1191ff8c8010e93a7d6e4465bd137a2838264b95f3ba6ed77e17f048e7d20c530b83ed0ae9a796b6de6b83b5e79ef52768a65505b72b10c9db02579e5486c71377730594866702a62c46fa5b98903708d07367ecb2b60f85d85d46f206f470dd665e87ea27b2422038da842f501245edfa3af2d48722e0de51016d87f76e27390dfdf2855640f682000c32877e7a8efbb760d744572d2186df7c3b08b3a251b11a8beb84a8e6d334f2334243490c451754293c9c9630118e8cf5bd7b302fa12f9c0f2627dc562e79ec4b7e5835a177c4d1b39409a7287f1d6a288984979382a1c1141aefd945129b48a69d0cc3971859c18b968a33dd3277b2d12fd33aec9656d9b1afe2fc9512bd91d25bb20976abc8835e4f2a372f1ae516262c26c571abaa37e42191592edba36999f06ec980146a744f7bcd172e6b1d70f6d2d2347fe7d1e480dd3376c664cea37d4f243039274994dccc363020f92cd31a820e2619c14742b973d9c5fa4c58013374d4c879340620af4c769720af98787f6b5e4c07cab9b411742d2df80a3ee15c5cb83ad49a383e80739f572287deccb7493717f5c4d544b5b878d34a54cb922b1c342e28a44a425ee929c7282e9ba0a97a2959a95c0c45a221a3aa2073d0c42d160a1bbea7d3cffe7eef96433af985874937d7ef5ad706538c6ea1c6d85c6df1442521165721a2437c1a772ccfc7440a4c017e8292d9d410b795931a34d6e55fb52f2908fa7cc48d3d74f2a86666c94799bff829681726bd8cb213351610124c9f8c2329ff82b7d0cceba72c3efcb1560abd49c357f4752dc6f16b0bd99e533e4ad5492e4b2601ab2320531a91edba8f7fde66b12aa0b24a8ddd3ccada8174d5b46d9603c00af7cf882f172e6c7cf777207ae5f56f601c5958bb23e5c73ebf40db89e71104701d700378d7227de59cc2298cb55ea309e8f4c9b509974419d68be141e44e9b3dcf7d3fa1872679e86caab4360bab3772d6183f599cd9d19610add0b17ba505742a29b884872367dd82822f0b0197bc5932cbcb350ee8c3f74d1b27a8030dc5ffb08491dda599f3b6f516de69bf7d34d6983852b4f9169e52377cdb2dcdcdb1a5395aeb92710d4895a523ff5871d61c67ce2955c113b6f5302bd3c5a9e866a92cc6dc8a2cc094a317f24cf6af6e4a71b8c7401ab906f1bd7bcae64cec1deb4aedce7c2e99f6bd6ebe752fa237a4d349af141293f11a538db3c549b531a05748bc189d265e224e9e08d9ec9a167ce2c1c08438aa1fa0b449b3931eb1884a6c32a8e21b7dd1d1c4a266f39d5c1191ad0b82c225e21680e7901bf93c8c213d21956095b07178d35bdbdece4d82939cc9149c589389b9cbf11a1a0515f8af167cad812b3e7496d606292f75708b44c64fae6af0689b145ea55c5d0bfd7d477bce7bc2959f4536c7291974d17a971dc2b4e1e9fdf58875c945dbbb299ead8b07d9a59c9086c3ce5724c30f9df4ed593b9cf623929f77eb5ac1146f54b0334b119de3d6c16d6db5e66709c5c4f6347357cd1ecc24b4a9aa452a80181b4fb09e99b27dba48c28285054fe80934c7ece324256460a23e715a444fbb1b7d17f6b3011a8dfadb070ac7b0f27ca30df306119b8423ed859fa7baddad65ea73da35e4ead350ae4e9360ece728ba55a620485de340a17e1141e059b8257559c5d4a52b131f684208119b7ba7295bd33d54637209f54e9d0e324dc70d3913bd522bea556a431fc771d0c827f5ded777888c9f6d76284e22014794c34386b05357c497be704e6d7a0c88a61887205d5531b5349c0fd213e7dbe1bc0226ef2855247971176e69d425b8a4895f36f2ae702b15e5b2e61ac3abe8ba1e348c23aaa8867815fa5e6cc7d37d7bf8c19723fe69b205fdaf6a21bde1fc518971a3035029aef71fb8faa8c7dc1510fed7e3913ca16879ce72408087137dd5cdf87d877dfc4f3601dcf01e50440efe233b07262203a3ea46f710528094fe0697974302562435eb116764c914727f90e7a2672ce9750850540c971671079cf81c935237f60d3151e3771a12ea938e5aafd8f72a645d74d5aa7b339869c0d75b1e9b9ef23de665f0a2e9d64979b64264f05cf729997a3bd5b7949208efe99c3d4a8b16c5a58362847a263c3684c094e28d9ff72a79b48072ca2655ff66573d5063b61e5f84b8a8d33c0f2a395cfb43e8a2b1e43a8b7485482834b465d70f7a4a0196f1199758aef9c07f8b19f1efd7b02f4bb720552c1fd18ff62493b3f64ed71b848a14e3c3f64996625913fbb28258cddc47211327233d57a76a9dbf333022d82dac7b3c9f5e2af08ad2597dc808a69d3196826294bdc20180156de4152e0a8f78ff9e59a499f8957304c9efd51bafe5a6172f08b977e5a85306cb01b46228f343e00ae93828cd799e47d4ad1945363280163b70a9105c22c3b33f4c0747c26525f36ae8444115318b8588b8579f8a1e15c084d1d6aaa3c4bf7315ad6eddda1ba99f2b8bd930edb559907001ab8e4f9df3c59b03cda82ca91ff30497808057c8c9f1d99418e9e0fc8a6179b391bf77953a472d7ab77c18fa4f8855bd594cf9fd0e147288c99a6ee40a2ae367973d7cce5d0728dbcf58045dee1d9e7006cc3dbbdb26a8d2a07e78a1e352a76bd0ee114db6c4e5753c8a25602922f41f4991f073cd12bbb2d201c9cb666478f0c720759c235725ed76b83a0288970dbb93f35b4477dfb893b6c62660a8d8207fec45c4b80396d7fac2c52d060bd20cff1f005d94b5390b162bff858865a8964a21acfed262872c5f11015d5407ed0b323c519f0fc526b416bc578e89816c2966a0a8d85b90172ea1f0af4ae928bb81ec280c5e2315eecf19ee865aba05114d0eaac7f9cd5df663f5f5fa6029ea96dc17520a7ec4bc4d4e6541f21e1cfac71cdcd074a63152d721fe22ee45369ca98285768250a65b4e0b78136ded5ab2a762f6e622766577660b3695eec2184ea5eb78102ebdf9b1ba2f194d1ec1960fa80efd65c2cfec63d4507fe29395a5b21e072045422f7842e030ea9446a1e72b425195a311134cac6729a6f7a691dcda7ce0835c9c2df0565b48d21b1671c377fec331633cc94a62d72727d903ae6918f2590a84be33931410adb46264cd8142f6d3ee2d21548eb4e72261a11fcde32b36b436311054cdd7220ba51c0d93dc1307b86fc188ddc65aa7291958a377a42f5626c0deb7449c2fccef1fb93d4b50d3a28cb552812f92e661e23f73bbdb4b1982d5241158f1fd16b9b1cbdd676665e0418b8b4ca61254a8c65b3b37924ad1c99bc1ccb91dcd9f8e433edf12145cbdf11542bb2bb5ed85d1e662aa5b51e1df5cfb50e80bac0a3dcdd6aacd96e7adc5cc2681b31cb8adf8d10725a563843b535bb82fefd4b144b35a3ea82f6e20b3b3b74f965ffd36de20420121ca8e842ae9c7ee716e7ebd5f9b62304a19d34cbfa5808dbdfbea4fbf9e2ce30d14a49db196fd2ddf949e7c24fb7a7762cf20df595e40e96b98dd826eb200b5ee422a020f6059fa4590ba40370e4e6ee2b0f4c3f9612f5708bb8d0775d2d0a72097690c49612eefe85ba37594714ba3e792f4def2f838086fba81bd126d8112a0eab9868e9282065532b404b84dbbe4ab48b5040ea43a0d7db281488538e3f72f1a533bf80036294801f1a14e89a342bab90a67026f944ee2b7cc6c1728e964ebffd8708fc590eb139b717ed983d9d8b88587187a06f0b8cfd0046f0c5439860e9c282589f947675a69798ca338743e08ef35e4c2f7fbbd4ee132999910ca3143ce521d2cc7a359a99fdde1c8cdd40185ad667f56f63527c9dd1e2a07bf2e37269c632ae00dc2828588d5871b117b4de89777ed2a15173d85dec7fbb98499a72580fa51fc0e622e57b5364d82bd3fe2c7bf34b4969b205afb43405d1442295723107baed8188a7d13c3150e68f5ca8ff6ed55a213d5651850e8140587a7e0872a6da8dd9e5404a7ebd7f8d6bc2b87781822b4556dc53344c3e37d1a0f4746c72b3ebe4b87b00ee97120eb420951fe3c1128f846020b306366285cc9b78dd4169d2abcb751b8e8f590276c7d1760e3202ee58c9da0e41946e9adaed3a4b734d722b842ca81e34addf3d99422d48c6fa1ab5d15a60aefcdb0be9c9721816f236133066a57badf26af2ebfa65641da0de3e890a06af5eac07346a16bf26ab2d11720612e0e2ab75fa1a456f747d687cde757719dee0b4e5220efb04ce48b977dd7209fb158ddb91df7b6fbe6a67e91e9893d9d23dc05919273757204e3edfae5e728d3a489c1a7e748001b0fca398be511d43d0bdce7a3a70167477b06e6b4b4d722ef51fec4d6495b29f0f4f800879a96df8c437f3935f49aa6948cae4668f99722b5e5ee87674cb0918467bf38b46ad744af6e821654f18dbdfeb0ddc33717b663995e159d78ec479217dd4183618a04dc2e0f2dc548a4d6f0d1d70e8c51df572be34b245e783cd1da0ec6069fe8dad588abf37c5f6e05cedb03a49d024dce10ecfdda425ada4c9bfbb49bf7859b0ae227a3876061e06aef0716b9f60c657d16fa957982b5814bb51d64888053bd43c1c48754dcbfe3643fb8323af63c29336726d1254da269d463e8d82f599ec054a3015a394d0ff3dfa6020343543779db5723df0167e805cf9babd2d79ff0afec05eba11ee1c2c16bd6ae1c74556d75f3172717c2af999f61e912925e5af337f8563ac6eff1656a25aa8d8762f0510c1157275cf51536823f2b413df1669875cb48f036eeefe758c37b48595cb2e8311d700888f9cf819ad3e074906bafe7e42e2188ee8ccf05ff1d5e111a8e500e21d4f3072c5f039cc649fabdcab217c9cc334c14033f4167593f85871b6b6c2b8488c30a33203a71a63a280b563e2c09455f6b782589d2f543be8a29969d70f6b4a45724888adf323e6dccfcd76d7404e16ea27f077e77a9ae48d772ac5e8518db76a397f0031d4d14ca7e514e226ffd2a3359a4e92fe15392738d1dd6c65c363347972bce82bcc0943f846df8a59ed83894eaa1a81812745a7a140729029e5ced8f372887e950415ce0938c360bc82adc207bfd518661e3f06fd84e87bf18911fa1d72183fc0074484efcbddb660591f4fabc63c70515a149959299e59efec5db6e66b22c65cf009ea398d9e1c34d757fa5ae4a31cbc736ca524a8fbcabeed644bc72bb17f73a1c8bbed5271ff9b3b98707540f0c58d56b5702c5ce46f096d9bc0ba72ac4340b9640a71a0ec1841bf0242989ceb0b420937c17e2f1da9ee7573edcf724726776fbff106d8c9040d7b73a4dfe80b78b0deb8f0fc53fe931824cc2e0c726264407bda8a5c0600b5db04606cad8e3abb68e22728ebec725e983d7f1857280cadccbe1ba8b53c99cf8f99c2bb621f3e271e2308909820cfdd94dc29662b1886101936cb98b3973cbcf29a40ef765f39c466848319a2b5ee9d6cec355a45728dbeb24057e2d2c4b2ffcc4de13141f908c533c6c9dd2766eeaba39d14c3670fc34ff144722a53727f4ea50daf11d237cd98b0e1ddf9fc76989bbc3cd2915e58bd1f9cfad17abf0ec219c48f76caa3978dcc36a000af0aeefd1e8b2958c023690b710207e7be0e5ef0c42ef6fc12d96a5201cacf25aa786f3e8d8af83396e41c1a5c81ca3aa20f37fd4f200adc67a46df1d14e2065b8f49be0332551568365542f3be04ee9fcc1bad17b4a5ae2af5be83f0e0cf5471331df1e21aceff60afa72ef2d7962a01bb9f395b92cfa785713463169280d9daf1f3deedd27076795194ec7a5fb1b65052a920da783a7e9c87648aa817cd78790ccee6b88641aadd0ba7242a3713d3651ce3db70902bd492464c16b8b325f5c9b4231998e99de9d3496722d692413b76ee94d5157a034b6b249fc637c67e9eee9a65fbaca024edadb833a72166201fd56535c57da48c49637f9b03046793b86ae6519f3c36b07694efe72c08d5e48d0971700252596514d209cbe11c46eeb57ef1b3ac474d8d58e6c531d684c6ea2be87094b7e8aa6cea01a5ce5d275ae80cd99d3b2e3156f477d7fe9173c24d4e3294088595b168343027b181e46462330e864523bd30ff41013fcb7722944e803a6bb41ffcee873fdba8a7997eeefbfb004ec46a1610074c2384c30230966cfa11d6ff028723831420356f0b83ce857d642db8d39e9a045dc76f36a72581de73f2ba92bb83cecab29f8b791ac21380f6e1d7a9cf29b157586aba90f060afaf068ed69150643244a46c5f0990334035e6bae3ca5e4a6d8676679b6bd72e7b4d2a73bd69a2e5275ff77b5eedc7f947d5685dd72f0ba5f757bed73ad8961fd89dd8636f6b59ce1ff932fb18a2cdb23811c918b6882c61e9dddf71611ea28f059e2bda50d92588de9a7f3b4e8dfd12062868fa3b9d10d02c8bcd91556a77280272a73b06fb98827a2f072f125c5e1873c407df9601a106d5940af02b4b872666f3e4fc5f88bc12410eb111f711c3fffdd567b8b5964b3c1f74d4edd68f172542fab4372f450c2b343c6c5f4f27528e0c4ab4ec27ec2bc671ec27275498a72d459e77e57baff32eb5219cf5fc9ee76f9e582a87af113509787666b76fb6572a07cdb9bd8f57a8b66dc92bca43c5721a7f3df108a67d5f4ee2b0331ddc5897221dbdbb7736a3847eb3e6c01e1a9285a0260c87ceeb6d4899ea1682f4ee3fd7205111c89d56fed266e4ce9f79404dfff239cf8b2bf696363501ff6b4046a143462f92fd2e44968e58d9aad36b690c41ebd868df2a54d9c1d4668bdad440f8b721bdbeabf41908d7e48eb42fa54f44337957f99f4890490241d6e99955a42b672bc1e2da5f92c6257008fd4330579bcf848ada150aab1b1f826c3f45e78dea651c0d82dde0621c1c88614b19c16ac2faca736bafd7a9b15d32ee224b1a062d57212ac40c8cd65a0d51be464bd59f2a0e0443e82e2c5e865227f98927d46b8a37243c91bc25855c6652c0f155428dc2ad2b3754a43dc93a82f521950da0e1c2272192a615d96d8d99f7015b37a7d425b7b3d7e15589ad0c661ca716ee1a608e75b7f1f408e44fef0870397cd5aa564666fbdadd0129d77beff6a855b66abda63725d077a371e56e7b12232d3d4529d2d9be44b87871cdb624f64067f7ab559ce72ea09655b6649ea25fd1c5ea236dc438ca51e5847f83d6241b6ffd911e8548872adf962b2ad27c8d538580580a8a9c33a5986d2041e11db55081b7f7977e35c0b7335c8e9d3821c43ac0378a40b829eca486157bc544565686c761a823d2932723cbecc33d2cdb101656e2ca8fbb249bbbf1a1b7b770050d7f852392bf9a778729551f4a43a72a3bddbd4f8e61bac816f2d7b42bc4d864b4ebe4c9ede771fef4d99c5fa681dda85c2f709e43e03654914399d900eb7a59deb20a857a0a2658a729d2077c6b83aa83026c7cfe8a3d71084cf1a1e3c14e6a3e9196c61f3ed644e207db516c9d6f0d06228ec42d94952a8d89b0c785830a977c2007d4836b4f6ef7233756d76cea0a720bbde6902e99158b51b9a13fd9e405d406d70d62b69727672e0cd750e5d1d295269fc4b729fffcee1b273e0aa8f6677f81eb64220fbe52a72185a27301f3586bee3e7ec731e4a612e6443d5219afa05651a82ca80781c1e72651b01a9630f7f46f74ad66cda03792f9377d62c11fd9d57787abaf1bc056a70eb858320ee677b8d9af256297f4e5d8bd3f40c165f58e64cc40869a7a9df9b721b77bc7b15fb4263a36e1415bd6caf0e5d447728b46046b3c408726e64e85b72a2c173948329700bc5bc1d6ddb5a59e8be6d0baa1b0a8059f5ae8e086b4ff7721077033dbdcc801c533955ec6232d11d4c9599fac4030fd8f5ab7d462a2876725011d160492b2e4e66b38b516c0bb87364b11494dbd21df23b6efa3351f51f2c61802e0125ef6e9875c837f55719b2c3e2fefdeea55e4bbe286c97057780b843b8c99acbce25a9093819c75104440cf8792309cadfe06476eec4a1b39a6a5f72a8f01f26679492a6eb6b40083f8617aa74773296479231f08ca08c9d5d13eb5a9d456ad397fe3b3d963f2a95fe1db146d436215a1ed101cafc0fd0a2dd4e3922b1dfd8f02f39729111800cca872db19ea08bac01310ad92ed40d2aa3d274bf72eb42e4e8d6ba7003d6270a88109542e15148bba2edf77f00bd24f12987251e5723edbd252f731a6ed2bff51fb8d99d27894a585ff2e84cef6d7c8f3edee11e4f6bda3adf0a0fe575bc94d559beb21cdf5566eec987963d25b9e3461af8cc7b7238c7932481f9a0c304cfe3c2a4c24e3d09ec678a33575ef878c48a274e056272638d146c90f902521b9f9142cb5aad5dda158327b1de5af173dcd13b0d177272248da241514d4ca800f2e9aaf7ba82ea8fa57a180f4a7036aeba8e2362978e7201a1cc8d9b9320c09bdf50ad4adda1a6df874289e0ac7d4602e17c20a735437223559b644f77091270ead84c5dc45c4a786f3f9bff336151de53dcc00d3ef036c531506b61b91380868a2070b524b072d19010c832e2cdf718699535f40cfc728b6da2cfa9d111b1582d8016fcc042df9d8e3e610caf600db56343fdcfd20b7292d1f9364c4cae15abc3a7df26bce2e5702c9351abfccdc8d6a9ed3ff796822feec415fda4e76446cbae61cc792735ae1cda47e1477f3155499533cf48fdd4722c91b03f44b149ff4532ee0434662abe9e6c94e135bd8b07d913a50f4ea9327254ef42ba912df6e16ddfa91d7cdd312c4f87d8301c9e70da93798318a6dd6d72ce21a17a2de23fe90b2e62ce14b0ecbd799a071eec1744c0fefde99bad186272d49c95fb1657e1b2893bb204b7c0e17a75ff50a40f6b65aed3738dbb33c0f272e044b540e48abc9e1dfbd2ff94cb8abd4c9b0148c7d5a2c3c57d2df4484f812534ac38a0b1081401644906650ed6fdc09f3cbc5f2a128992067cc24f42a48a72f9f0d1a833c59a227e503c50d97e63ece4b4db2ef31f6a720ce17ef239b02f728c35269179214f52000eccedd652bad246b02d9fa76f458e8eb0550e52538412439767e20957e65e151f17dea33ab255f263457628de7de42b87e00e66c2617240fa8251c0eb3ae58c529247df009a8956830e05d1cfbf11322777853ddf820aae98c47e7bf33a589b60d2bd18f79838214fd8abcc2282f97ff4fc04eb8c42722235d35e232b649e35f34dd56d9b8c7f298570a848a367f689dcf96e5c711065284e8629304efe19ac41e9bfc4992d2ad5ce92a7f760bfd3e09a0963ee611e72b1a78f8ebadd9bf912994106e5a451fabe55c3d79dca8a3f5a3f079a46bee77222c26fbc18f62c8da0ae9b519f41aa4ed3e53756a7a943a8f109d8926f308772ca037ab8283f5546451a6e1a402d3a0cdfba4828334698b3cb120f460b4f33066f6b244bc99a97100807a2512208ba05c9d744d06bb60234d44306155a668451aedf530fedbb1cf5c340dcbc080c59077d7ec027eb994ad20ba6d9c01d72547200fe6fae88d415abb84dbe337ba7e712300c6c21640c0fabc7dcc8e4bbd42204b82c9d3864e54b5f60ceb65f8820529287cf9c585fcd5ed53bdfd29c6e814c488f1c5376897704fd5e554e1b748b9e2e9d788814989bdba198e1c57347a5850cf5dae7be7c9209e0672ea1410a69a1aa7b905b693c8477590b73e4a015622e5bc62f7530b183b522738a7c3d97b95882be5a10f5f0e3abb72f0cf12b0b854628a114e21faa9d0ea8f74b56ecf68fdb5ec258c3a0d5e3a9fd704746504100ab72bc8bccaecfcfb8f97cee6509bae1657473a4652413fa0c80056c2150957ecc5397025d6691803212c932c801824e4c7e86211a17d8da30350cd2e02402657472fae3b88fd3d9e8c25ad88333008cee76fab96fb6a6f6bc7c9c8ca5bae15bc80665bd690f626f614461386046c73d4cc3d88e7881e8582fd883b467066ddd264a61c9a46a709d60b9e3beb14d52751ae6414a3aa2d2bf741f80fe136a3b59b6727adc55a94767d47b4406520de6951c89625cf633d34e63e94c0a237dc932676512231fb313fea92fd4619c773e00de188d91a025afc764bad881deb34d96977276baf24a9cadeaa32d0f771ac2f5627137d7d523460248a6dced48f2b8f16672613fe2e1e5459a3dca8c48c4bf4633643f83386ddcd9f2183c60dd37d46c205ab76e58cad31925d5ef657079c43aa001cf869d88ce540a85ec5f1b1c4d80d372a54ebc12a86fcbf872bccdb1a8188433e2572057948ea152da1fa6544ac548660065da68ce242083aa644733cdd7aaea8a693e179e39384adb6c129e39cce344cab84385846678f19a9f9298b7106cbdf6836a35df9e501879f1b6de80ef9112210b51d49bf4174e43272ee62bdf499661ab13f0bea5157059c01f6cd519fe085ad6d28a1921640a2d48307975b0847622214cb65f1bf428b723e21bffcec672db2f390669d8db5f4ad2835484fb78ebde2a1d5b9a70b79965abec948dda4b72a72754ecfcbe8c93665421560e05a963b1a9c5c6df31a5cd1165558f8dd7aa720b2a5469d61b2fdf769b3d3f6eaf03c616289b29b64c03cc0c28377df0d4b7722479db55a729b07c59753371d9e0755970deeb52dc8c363549e30c29b19a0572efaaa6a7bb6c91ba4c6f9fbda1ca8747026ef3140b239026b399c1de0125c30b710766f0073e77fccd5500e43987c49d6ea361b212a077c0a3905198af3b1b5489906dfb00b29bd2816982409c5df9a651c24d328d76f62ca7f949712cf4df72331d58978b56e9b658773697948f67bd401c948adf4ee1093e05d45690ec30285a9ba785e1af3d280bc9276cfe4ec3dd10bee9cb767c8ef8e9b124ac9b1a89729aa4a162cc75f60feca32a6d2bd113d146aa8da17e10dcd61e54f71922032a102c5e0c698e8891d501b57f12ffd3803482b5b51b7283009d2c6704310373ee7240d9d6c2cc8eff82883df8fd308921af0683fb63cdc1100082d7cd159a969a724a62f83d8f75823bd5b7889594d7a6436c3605a7b4241642d1b602367d3db0722d1cee9c7a5c86dfe43cc4847b719c5ee8dbfef3d7875cb257400a7cbc9956184b6d70c1249708802f8876b6b55b77b33d5ea415924509818be639aa69e3d7725f731f2a3797aca9fc8e2b2fb34d6fcf20d1846752fef9633a4f5ab40638ee724e970a3c71b48277ba0f6d516d5298abc56a457ff44384a48a27deca8b5dda72274d8bac2fcb7e4adccd01ff25b985bfdae643d899c57b416ebddd7377b7165c6be706b8c283c52a434a92bfc5582e14e497b8f6fda08b9adb66a89c93dfdf3a89485cfef9c3c61a49c36b615cd65b78956a1447377567c1a84448af893d747244393104ce945f591394c39ce4aff49b9a084a6af28e09d12e3086e5ab400a7208b582139af34e62eabcbde6729c791b910579d7bb20da792c7e6a3b1b90ca10ade2aa20354e720e4233de4815891f1a3d2dade10c3b49e54930b42308189d72e46d53d966ea4ee7b1d300bf1d45cd00cf2f2438c435875e59d9636e81c60160d46138725cd82831ec5471f695e01146f761968cb882045653c1d3006239082578869cde8643658e1d0eed03bd7fb465f2a8532ce2225e8db9f8d88d966ec572ae9ab9b147c5f22322b30dee9e71b525903c50cee928287ed98ba2e5478b53117537915b1cef06d83ffe24ec6adbd832a0779b7f4be851f3de0e6c3863a485727b8a01ac52ce1457698540e762cc802fe7619eea9140e86cfdb6187623f9824afd3bd175638157102cfbad6a10ae42e8a4ce66eb00baf4f56e51c23793ece51b7c67690f61b2354191b7df760554d1782ecb1137d639bb69316a28bfcf85fd7264f2f1e80c9e63100ec633d6b7b53e1701be645ad97e8f194a5c492c5b1b3172c18b1e4741ce4f05a49b3ed218ab404125ad3503af633ba0ccb8cf300ad68a72481dd5333a1711dc5c624e502f845a815466125274c068fd7362ce27aeb0e4723b12fcac555b8859ee6340862a128519d5fe7305542aae0cffe935aa31bd2e2d5c95294f15a4a6165308166be1d5d7ee76a36ed81b1ea6adccffd4c72513267255789aade74357f0dadedd6549690dd18ee7a188707f93032292f132911c1a72bb60e74833ca94ca772d04dc95cca8010abba0407219baf96108a1cebb0633726b890f30c3d5b9545719d265bb60d3ef5c8e8ea1888d614c6a041334ffc89f72398d37340800c12c014efbd28977ab0925a08f1de6ef9819c8dc649584d1f572f6b3aebd6f4bc716279ca1d5e53799225877b5ee7f4cc811334e193fb57f34723bd4f13320a09cc71a7a16b41d559ce49e1b2557a1eeda4be7dfde04a371bb18293862d6a25f39502012e4f12667b8afc9c2d335ee3d5797021d7badba03303bd2e2eeca20591a705b5750a883a0076d8a3b6de5bfbf3fca826c9b1ea677ad0d3e10c7c82fee2d1b5befb3d5c44f53224b8c783368d753912d235794a93d721d4634002fd4a9a070a81e4e7d78f36023c780cce44ff045ab599845e1b3f2c6723a255fb0eec234ad4f9c51b718b6ef48c5022a1c3d9d90acee1475fcb7dbaa7218e5a40854f0e586f93fd539eadafaf994fa585f39c391db0d9e2a98578c54722ed346fc543e09ca98dcb32d95bf7d35fb205662d695deadc52d64ccfd35fe724bb4541cc967b25eceed2cc92beeec18fd7b71d96a07232bebf3522fb088e124273132e09ca7ee27d7f77b6c3f03fad0b05d2246d38b79cfbc281fd54ebc2172aed865bfabca840f3e2d4db62e981e0b276d53e3910aca8d774010f7f94e3f209394e61bb5c1edf491ef69109cb7b7b70f69f3331414a01c5e9afebbcdafd8728d830740c2e8814be298db440aa422d5fb0b54bbde80bb6d641e9de2457c07455001158bb262393c2292fbea9ecf105c0d44f57fff6ad68a890c40f32a51b97251167afe331a8edd29dfe76ba0cf6425ee3fecad12ee4a6ee97d0337d27d6302cf8aa2a6de9e8a4cd9c3894358853b11a76380a0d831ee61348c9aab2772b4727c9435443a3913ea52ae0da2ed75effc94e7944847b2662fb7ec6599942931679415ef3e106b94adefba9971b3181d818477d1704d8d7ccc9b8066c1b9b71c4272a7448947fc039a0ea2bd88064e7f331cb7da40cafc522c84e5f273f85d377204c2e636e6331ae74a1dd10e622139f0be59d8ef97c74c731be7e913e0293172c58db342f2c84c8f4bd964677929579850d5df1ad5c06dc6958b96e7254c2f72e17c20da6709ab3fc234e4477bc36af05a4f10c9d1a7774a31a19772d9f6070cae51b1dc0cf996e3e22b2b9c8831f58c6df13dc9ba68bc99468685cf603f0272211cd62c4eee39d2578dc698a62226e9a7b9868378dc335ee890595c4d62bc7290a86277102bfa327bfac00942586b65453045a7758c787747ed3d46ed6cf272d238d25271b34a9b610337cece2142bd6440e9aa12b98b4f1aca66d775635260a73ef8b540bf731d00c7353bd84664466e8c44f114cad600591b97a92e2a0449de6d8a54257bb23c975db4f0203c1c4d793b3b46ba7491ec43c11140b6637872eb30a2281c8da52f86bc230f4bb2d373a4889478fc5e5a3b989ad4a1640245727c5abc7eea0fd0260ee9f6c321f231d47dc8e51322cb4e4e6e169b16a4645072c60621a11a57f9c4f9f21741273d9dfbd6a3ba811cdb392ccbe3645352eb8e72a1e5741a966cb963e248ec62fcbefb631b28f009041ea5c96abe59990bf1b5018540a1539071f4bc00ad29bada1edc7c25f176d1806e96508e50085526c9c761322fb633c1ad1c377d4b11960537b036ffbeeace26ce744b32f49088e9e3b21e3f9a47548004f5a0b3add7532e0f77f0861477b1c5fe667728f27e5a3da4fb1cfc44d4ec6861a7f9c5fb1dcb23ea3286f6bc10af72fbe4d8b5f03ee97c11967237fa71855481b0795b5e94cf7849062859d256dcb1ef017f6039e2359a4cfb2855683187554150e39f6078a4abd0ed4fb87821672d928f3aea04f1bb9a2c21722352a6dc0c0bf1478e2255a3ee057de4ceb546180f9d6fe3daf4787d20649672c17123454b67589fdfaa8cd4c527c718b97f53660f8411020f216f7f0aada6723638fbc4dc07c6819b49c759b2108748a3f98b347a42c273814dce5899935b72d5a82e2e8e8ff4fd3d09e00d1d8aa5d70d7daf6f4fe047eaf61c91622d34f07251924f34857b555f472d7b95c09a2f64ba5a8765d2b1490e69ef6363f15af17265ff48edf53fcb72d8626c84e1a9242a36b46d4356739827aadcbf2c7cd55872040d4b7d3f731cbe2950db26967fb054e63f40ee205236a89e3c5821b6d1d6238cee7ad14c31022c8d5148f5ca1f5e301d0dfda4786e9c78a6cc210cb089f97240d425e44fc50f24f86a51c064b77a9dac4b1baec89ef44e2428dedd5a652e72d53bdd6f5fd1a2016d1b678e15015619f05df591a2a22f28b8d885a36863db1d673dd342f3ac6898f11ab839a5ed7e0a20c8e15b9f1143401b38637907c11e72a28beb345c440259c07a0ae93ead18898923f08185333e253bdebfe0b96acd72be8a51d2fe70b65c7bcbd09f5a93dbabf85aa555e3c14d8a9d245a022f972d729a039215d1dfe127e34f64cb3553aaef76949dd01cb8c9a259930a2d06a76b332f2ba832195131c95ea74906e7f273bf17a32bd90ad3f64f8f0d6c135f97ef2238bdc4602680571d435fa6d4ba660ca480fef413d35c366e3b00be4e4866567269e3ef27e6169a160309cb4ed8407bd8a67b40dd6beefbdb90f795e0ad60a872abe8976b898b941a07d067f7ccccd3b015c01206c416507bdbb48230f5d66e029ade2116492d708ad313fe9cc599c08abe49f4544cfbf621bb3853854101c40d5ba56b368f54290db9705e126893ef18704843ab1295b4d38d747f712f7dde7255f756a524b69238e2f09ea809e8fd852d0518a403f7b47860501d16cfdeaf72aaca3752e7d52140b19bd98ad145fbe6bfa62e80fc058c49900c376bd31ea572b37d4269fb296803ed25eafa64c628facca48fed3d720097b4795ed7273d9972408c91964f262e9dc0579d77a5c856fa285c282cf653ab8a6d28845d4a3976575bdf0e0e6cdf8b5707284e77a2b68b77eff65c01241dee216aa83fcb82385c72efc2e094cf7a3174c9b7ae683a89f656d36124ef43dd874ccf1b9c6a0313b2723327338d7b3264e46a86b4ad2c08d15d9f756cf956953ae4694119770db5735ca7570034c86cf7e8aaf5f53033e3494fb607ff3b1ba7065f9a07b7cf6848ce4858ef9af195835f3bb2b5ceae95cf1e752c14906baa70f7b24283ec55ee9c9b72384ff10d37ff611b9b5519a645720de9cd9a67bc3ac3d37e1e4d14f1734dde7228945209bdfaf1f120d89c0db35cc321402f24660c15f4f38b0d798018506b6585fa3b1a9f1af4a09d062fce084948b6da66e65bfcf7008de00c90118bf2487270d3cdfedbc15995acc272f49bf7e5563ca8fb363ae6ddcc4d9f58a5d130e772fcfcab678d99f3766c79e5afecf9d98e551008e697e05719bd367c3f4945417272dc6b83f36f7dfb19fdebe4429f7844466836036eb5910fd779b1db0256fc727d599e05b9c08c3d3e004c2dcfced29ff2598617190b3dee2e6478ccb6b0d972960ad7d99017d893e794d33f647c54f65cd74a36c7b8019484b4810ef4a51e6f708840863ceb67391649b3a866fc9f6d87f5afa1057b8b66de53cd619368d272b9fa1e522497e53c1f43a440c4a88560827c427bb551909b9935dc597499c772eec9b87fb7d271ca2b37f39690a5ea378c06726ad79ea486b30e0a83d9ae7172b1e8ca66a1cb0d3b20b6a0cb55b3d2657a38dd1af3d2c7b644882ab352f0d96d90480fecb9f28b074eaebb57ff39b2c56f006c52f8dd0f91e28315d06337d072063d2ee11dd48f6718f5d4d8debdef8e8ccbbb1118a01354ab5dfaf706f4be4bf537636b82b49398ef3d33750d03f76b5476a61b6d8ab95db7af1effa2338b72f6c9444a1884146f46337cc7d121c9190f4c7b82b8a8a4873623a2f6c73b6546efe86e49da63d8feea3c2da9b3f183197d1a5f5b5030ab2e9d84296927be0872a5a0f754f62e9e3aca35875726902063d4de91b46cc724efd28deec6dfc5177246adb57f3d632e6778a051653cd8ff4e18d3842ee3010c900ce5621bd13b23729be1fb5093d26f9daca252f04af7a99ffa14b92035f6ebcce76b793fc0b2f27279dbe178feb4d6ebaa1a13f2bc0e293a48a8faf21062e039e84ea87575d98b66801c861c165c05780c8d7ecb941a32d460a978765d44311de34a413dd2563d648ceeeb60fb2de813e72e156c05cc0d26cffd7380acf696dc153b8dd482f3ce722f38999b93bf5ccb635669ccdd5aa8657102fa3afcfe911ffc3fe9a2699e0c7262b1de34f0b3e197598fa2de58f6027d92f670c344a1c0587897e7fedb981c006afe6264d009cf708c5b4d880a3b0c2d6173df234c36db35848b8164725bb4566ab476d7d41319b929db51aafd76d99e8472a50e161f5928f0dd0324459303720b5e49ea28bf4c23feb38ca36621bb8f8858ff2f00b0c437e229c7a2d27fe27283f64f862d389c1697dbfd5f604d370ba348b58c92d7c7faad11633a636f9772aa886cded6588f1030e9fccf30b8a5519bbb8306cbd3197be4c8775d5c9829725c7998917e39f42e1842945cf127e00f41793aedf747efa9c2f129e68f9a6a0b58f46f3179faffcdad55dd20f2b8defd99bcd7343fe45f93cafbffa53f8188725bc78c300c9794975075f61471fbac67983536442fb424425e3007f36f7f867259d4dde984b9f809f897e2cf9dd19752e8b8150fea58799eb06d143ef109db724ea941b957c23deae5de64c97ff553286a063889a4f2de6d354477d5fdc14272c5e802a6ea367e0da4f3bc1c481a341051aadd3df2b8b404c2b5d299c5cd4c726cba4b75ce24d1b106dfb548aa1c2bc5c7545f5383b192f91a823089cc5d6f3d18b703f2a2366f76d0bf14fbd6e10827a1296bf860cb26a355b3fa578466b2728dd0a974252fdda9666fca333c2948a62dc610dd2cb1ffd1e10cc8e8b1e6c5075acffe29957da54e566bae512f61a96e80860de13e71ccb955a7ac395b527757ebeecad252d2b147ee06533e148d25bae80ffd35e520ea534daef3970794ee24bf8cb4c1c7494ec9cedd7fde1fd702ee282d7af25b94027d90533de399b3624adb294daad616208260e6a902d696fb9778bc63ac4b5e504090660584d1dc83680a68122f0917ff63a0000a768d7fe84120c33ac775619d27cd0158a2d9c3ad726de6a292201da6c886b1eda025dccdb8e36646e0c6d32d671c1bb18195c83a6762c56e020b47b2d25f379b332fef1c1fa74e1f323744a406dd4ed519b7907a729c5bbe439b25e42981f2dcc300c7c7d58318bee2eed5aa21b4667f200081ee7270b43a07dae0725b011345f593ffab33bc85b504693a7c1f1ac31e80b37fcf722baae2e2ed906eb43ab1da06a9f7c00432517a6593ae0c44b9f9148b9feb4a3facbee6af514b9500ef262cca06b09e3301250ebb9b17260d2777fcd8ceec537294099505d9936930d01c8111f21110c39b2a2b1f2764e3bf403d4fdf0abb9a72d349bc54409679ae471df1f28180654e1a45a60b9fbe93d3a6968133b5701a726fb84ecb220a39bd9bb59fbe24012217506c11e8b76b157dfbf423c9947243721d1dad6c989cb7eb8b58d59b7c04f31d8c0a9f3a38f4335d466a9a702fe0a145434a699c4f20a831ce4dad125e2164b805b3aaf7edc7b323907200c356782772a343180b1828f494d6f9a4ae3176084fb9793e9078221539d038f228ff0f2c72c4b9511e03f719e6abf8d8924d3fb07fa187d684235c0620b201719333c3c432ca9b18896e4c669d4f2a90b502335acf9aaf5e441bd0dce7f1c48539c448062ddbb007a199d9c0b113b84fb63811dc063e252d8e3e8452b3d807e422d5d08b4562ef11cda2bbabb1b00c40633828ab2441d9416d0e48143bb81877f515aaf2723da5b656439edf634dbac22b1be645aed9ed0d90f99df47a9c165b1308ad64601b557e2303e2429b03e2dd973f09cb0aa5f15c41343e2f97406c159f88d6f072c494a4f8de586f2a06aa16da728afa614d1d25c60907c4863f7cf9e0595021728ff4215262ce698a63b4f6215ca10657dd35208633f5c823fffc081666043b72117c06af11322c2471895234af3ca8660cad6f5054f8c1d417c9355484fb076b95f6518b817f0fb6385aa4cdd456b67c97b971b584d56ea8af01264b7cba7a728b47b07c9060c449b426d45a67c9dc74e6366bc88df2b41fe1c081c1308f90299d29a994c07ba3145ba34abfa060e64e31a322cac46b694d5796aa5f3b54b872cd51b35efb85308e3c8e8d4b100db77848b02479a5f1606e9cd6ceb48177f5727cc617766f8c79e4ba8000075b7a345be31cefbd9af853e9ad58f2498aa29172caf64cda7c4ffc83a6865c2fb627ce663ebc255b0d3015554f183bb093b8467246a35849cca4a3210533109a5370e97c2f957a69365a9203568f2c8c8f5bbe7250c10643443169191314dc104628b44b85b96b8a71834b3b0d86d3a6a3bf0840c015212919505be161639f836ad559c704814282676a884d5bf8c2f0e66c4e72789d242c80784fb9b524c6e442256074165dc6fde896b1f782be68427695c66fb9ba79a978f4259cc558e4a3454e13043ba4f4bf0e7140468f159fb87a1dc6722191ac43a9c4f352e70f6fd2cf3d1a9e76eb1670749c09abfd851e4d0a8a3d72e80cfff2456132def5ef24b52288fc8e5024143cc3f92f95a5ca8bb971444f729d1c6a9a7d7d1c76c62b4ea65edc81f8c58d3c3b2eab8339eaf7f248b08191721a473af8c6da9afd2b96fc0d861933982ad21b9c2ab03ce68d6daf37019ad4727dc83f240644d1714b36a46f5b0f99941b84f78b040f82a9226d567b04100b4604087b2cebc5d5f72c489822283adb5e63170c64036e562127144e5346bcd472a47adc3a794711109c88adf818f75e7a725ed36dd0a1393ec38c7f887e067f724ed5badeed91198ae30d671aca8d1af916ab724393bf4562da4cad75efbfaa41dac152da5ca017f0fa132d5f9526f2e915182e6f041751975455b292a5a74d7229a50e8aff958f996f439e2ba2ef390d8c179674f48c8fc61aef6fa6729b6372b1862d89ea15c20d465c1e340fe819406a91e9b13d4cc81a1ede257eebdf8f726ae2ab0764fde43d4a131b07eee4c6144ea5ecedf9550512bd86336799b81c64c8b49c4c25eebcff331c6f2bee43b95cf0529d1ae573837d1632e49c8cb79c09396e96e4a6a3087629a2a8ab5f2ac120d7751f1c874e04885b912a75c99a317237edbca761c21d6c3b260afd9ebd8d87dead1bdfa3f194c7563014d5d720d37255d453ed27ee11dd4fd973125f9b4403d2a557a6b937e1618cf4d16f0b31f072dec1fda5890fbc12de7c3ccf76a7291c60466c6889716ecfda5c95d648839810b4763456b6e253b10672d29c7ad0435b419b6926db51c584f75a0eae5cd0f30797b01e06cbb2a746ccbd9f09f720461274512afb783675041aca84954f39c539ed531ea91e36fbba1c3ca8bcbda0bd9d985bd83f95ed4147c425b09e091c3b72038c3a57ea4c67ec813da71d7543b5115c2a5b1e9f3b06c8d5276af5b12cb232a1a794b08f046c273d14866ceb5fd923900ca699a91a9bde1c68e4f43934d772e9efd566697f24f8819366cb2a88ebd0a5cda101f6140d68653e233779d87f0116713b01f3ab94774942a62657b76704d80aff9a00d077d85f2fa1fcc2f6a8722bad9d7f2ea4c8bac87f1c7d26b4eed011597f843c6b72ffdab1c85846611f07bf8f4c4c99fb35fac1990f13654664da3d688888adc5fd67da30eb815cf1d3725215e178f342ad36f567ed5d1c1af4059ee0ffe49be280d73687ce0dc8807435a2e5d82174bd0b0646e15839be77c571a3201362b836daf7777009c47edbb2685941d7506f40a48b53df9eb9c00883b25203fe9a25bcb0d8ffd1c7078498f5723a4c48f0a51df46671ce89b746302d2a8ed70d029d0cce9d7cc7f33e312ec567d659b9b0a460bdda03fe419ad0b4d7de7113f23d7a32c713edf78750312f3372cf18cba1373426057dbdd797995df4e31a304f7a4109ca7806e86545a57f0772e67cc84d3dbf3753058cec7fd90872ee88333065443a69001971e4fcbf36145bf7ed13c378c39d3c1ab345d32c76b5195b994d9a5466e27f4df853e951e2787226f8eb984fcc46dfd17724933f41a37977022c150f77057b773d85c91a44ca7249246f0ebadf8804d21df38af3575fe5eb7fe2e056a9a0af443ecb06a9d15059c96291afb6eba16ac0b880e75276aab83af3ff9cb4ccd37cd45bf9827e8b3a7219c239110954e8f38b9a149dfbbc315fe0a8f522beb57c6b76255753d41efd6516d642a2efa40b531935ff9231ad56981b23925e7d3a969c8c76c77ffbe5b7720cccdfb46b3a76b32eeefb195a183a9f622b86d89fac27ae325d54a2d301072b72b872f876aca7d99ce9757aeeaeb555b6574724345a60d3f8370f47608f81253d2683240b10da7bf575d2746cfafde689870b8aa270979b0afa768cda600f71d623bf0435d74188bc2e353ce9d429e8aa0a4bd09c7dd578c135489136e2a27080e72647d39a399d9a7521b70bb647bb1a7a0806eb198ef4d5b7d03b95a46672d2ad423f21946b57a8656f7e8f76cb4c3968861b54353f0633271c8a90688e72bf90cfc06fc29e458b4186dc8323623f3dc9d543e31bac361cdf852b925d5c726560b3c9fd7d333968701251c6f416b4f8081a5b32706a806517c4f8c70d8d45f76ec3f3ed9c247430c967cc7ffd3b76f8652e3c3c1dd268679825c1c2b74f1bb1219fa4a931713c8fb3aa88b63e6b151652db9ad174a30b41fbfd8b829c6972d70799e4d1bc3c6c558fee4541011faf315c16e70ec7eb90c2b8a9ec157d2e72486f42ec13fd02366590c502b0df487f9c2c1c3a92e113aff2cad50e6518f2041a05e4eb2010833d84fd9c6d240dc157d34a1ae6475598fc377a2398ea7fc972b15ed91c853143341e9b002a1998e841403a1cc72123b33ec7a97bf125b445720b1fe4e128af3160724c684655c31f14d8e3c0985b6cb1c024f5c27229c2a42b3983d711102cc5d0994dc9ddd322a0076e7e56fd367a3a0cfae8fde35965ed7212c2dd6e91d7831ec5ce5df8c16bf19c81998741f504fa316b7d64a8af20ba022655626a4bb734fd8b4dc783afbac150b72c55dae6d6d54c34b82041963b8672093062e303ae7ad622152ab78062076340863697c1ada018e0d8b5bf8689842a47180f0cf6437f88730099fcc46b761e17da75be635206df371c2ea2851caf72e7e30e1d0989653fa35bc3b9f687040e47afc13bd2d4db98914e16f5607114002974496b3072cd3c210d7402d8fdbd718c3c8d70b25c14a9064dfd6a1664d37218d8974a9c22441ed87c5323a9b84d80b0b23cf6523c902ca8fbffb73dfb4872eb1f4d3e576d081d1402c47df872d1c4b55f9b15c0423abc5d939d0e0285836ea95dc245b35852404d14a51c4c9840ed06bfb3b8887b7ed62755886d21abd572eab6cb4d4ea3a44b87b4cb232097d87106d08a4f090980e9ec4ebbd8040c2c72802f68158d6565298341cda75dd499c03bef02df6aa5fd748e60d2fd20f57e155edc46f492149d14965a1274a21be1ffc1a57c9ef542e71f4017c79c436e8372f70c2a7da8f36148135e4cab2cccc03f871ddd3ce96f0d8184dc8c6acc7d30722156fc83601d4cbfe9865cf73ae825e0468f7562dc8884810d6d1cafdc75435d2b4348ab8f4a9b6e27f1a1b05df934c44726a8773c4520c807faf642a066223c5f45bbb57449b7adbfef10d6c8cf59ea49e3238d55d8acba57390d3f2580642b7b15aff4914c5c72708e8bba688cb77b92341242aa68fff1eadd276463b9c8724f7304910fb40a7154c92ad33b9972ccebfbfa0e93f7756ae8c74b476a0d9b72961be4ba82252b6de4920010fb4fed5734be20b06d8dc9cdc23197eb441d9346666518fa98a96d50a7d5f06afe1d312a44953f908ddcf4df7471239c7c781372cf262acd356c74f755465a24994aa165a45671b2860cb084283f3b03e9f02c7220e7b40cbb23e7881a61e84fe1ab0b7f4384fe5232b2b39bdd545aaf1e137f720bd5d7cf1e14c59fae004a13bb34e6a87a7aac660f92a8884851aba3a6b8977289d8081754ba24233eec84a4c5ba937b73df0e4697671e7e669ab0775cbfc9011d55e1fadf0b1a609e93b4d41b43c6733d65b9cfb8e0bd1646af5f9ee3656b7288ee000f95a82f9ad78420a8bf078df61c7020d67254393dc7f5c992fe94af728e370be160d597559553e23a741ca05223e20e6a9d71a05850d165eb124eeb725ec6e5fc308eac4dffabda1dc3080e2bcc5cb580d5c4defc70f9a3ff1eb60e5bfde0c07bb74d4518ea62a414da4c85a990331e22ff7405e265f58692fcab420033d2bb7af5cf451a3d6b220068132fbf449f2d427485d164811056e647b14472c7115220db41eaa2299cdcb9dc2feb198b6a10c599fde670838ac7d585ae263ac22dd7f3b2b39390d81ce12221ace9811df413986dd98fbf41b0befc3c9ca9725c4886224f94233fcb12309d6bf1c29207e155e534c4cf3e376424d6a3ac123f574bdb81c145356126d4cd6088575422a8aa42cfe785cc1f46aa442fd11d78724bee1b969a9b8c2c0bb2343695df9f8ae941ca660096c4b7a9b44823d2d455721d17316519475920ccd7f643ec1639b6d594fbccfd0f0524b47d2ce9648a17729d9232ca44ab2204337294286781dd19990aa2d393090d1f5da3f9279a6f277243222928f15f2c9d6438336152543c349d1874de33eac60cd57e7ca7460a37726b659877bac25ad9990e1119179133c8401b82ed78b33ac9c51571d85db92e3d96b3a81c33068b545ff1fe8c3142e1c245f03a16eeba0d45d249eb9cee545531c8f9da2a36352d2e0f42d7790a0690e5221fbd718a1b0607ac9df1ed0d2bcd49ae09fc2d8d97e7b64c32c31b165691a67632ec12a403a1d121df34f031c5be124b5892eecccfa5433ac56a5c63121628a0129465c640580fb41d25d194e19c728078577dfb68f8a4f14e790649fc7ba03f9eb941c7e4c2e38ce489fc861b6872739d1915ea83c7fd3dc2edd3a9fe0969ce99ee97c9cc14b60b96a87982207c7243ed8198d33fbf858937f5a9d50091a9a15e5a82af50d21edf0501262ef35530bdc2ce8746185b3684af68107a18edcb3b42eafcba40685132ecf95456693772cb79cc338121627d9c6a584eb7e12c1ae024e5b7e2702d788540a0ea9880be723b36a4101e8a6d526dd2e6e34d2bd7e66be1d214a525816ca7410a3cad036d72240315e4bda63202283086ee7482ba9f8dabe5cb3e9f13efd82bada3cc481c728c709139e1eed85ac425031c532cfd03f14d11ee621b5eebb7638f4cdfdde928c38ad5675ba74fcbe4e0ebb22f8d0845b771c6902ad6855cad785b9691d2cf7292321b82c15fbadfc76961ab11ab38a773ee9dc51dd6d278670b5388ac80d972b852b1772e4f815a3e7047103d74f0f7f5ba7e40e775f251ee6ed4ba191b5f727412abf2d80d5758e79505b8a945d4b4c6fab46f1a054da745869fbb7fbbfd72fb76904696006b8629162735a451b9fad1a6a41483c151eb43f829238852671e6c4490c12e147bb956dd9e54883357baddd06bc219b29bf9d8c741fd4ad9147253dd5f86486999390ceb8c1fc8a7a58394f3eeb6f3faa18f5524bb9822229a72b312da56a3253b52fc94576a9dc10e2c21ace9d530b48ed34cd3fde867266d174f4cbe42f2e451a99bdfc1435f07a16823fe2b92b3725f0b6757e4fd4fea8072f7ff23a191a2b8c27ef19af6971c118cce2665d8be371c4073c735f0f801e6723cd283f10a0f26b65fafa093e3566a5e9017d998c0711a957ce6940e296c8272e9ed10a1ee96a63f472c22f9da8d74eaf7ecc797042bc23d0ce18fbba84b0e7245fa6c9a6fba2afb3838a0f9cd3b192367a682e9846f45f664b3627d05d8a072d276fa6c5bb65689985be860483121b3edb6553776688b30ba168447eafc067222e07f7d98078794152b06bb754ba2ade5016a13f2337aac97bee1699bfa93114d1b621a4fc30268abda647c15dde49441ba71d46584f2c2042c0f73e3ad7d72546021c9065a805197d4c83406295526d4645f018ab45991334009317de32f72c01377d45d8909c31dc1edcffd6b679d4e18db9ba267ae1a20189b8ca3c95510f296095e628935791494b375d6abb7974746a8c29ba716e8907bbb90eb88c51300c3059e53892a70abe480690a003c0560a091cdb9b7226d614339d810f2a972cc990de81405cc11bd1b3b36560c9579ca19b9df8522d32fbdaab373ced53d726714858c9ec991990f22b8c48ed7080809e058461c17844868e15f1c4a24f1192ae36b434cecd7d713a81b67e7f91fd98ccbf4f412cec52c66aa5a854246bf725badeac8c74677b45e4a41608608c5fdf12eb00ebafbba4be0d0ef28590caa72585ce92962453343353e751c210c2bf87510a5bd7409febdc0a7e167c70ac272bf1d56096e9c9ad394a1f10f600484ab97b229de378a7da723f37b1783ab5423669ad1eefb38036b5dba6e8af76b26ded35622571b7f8b23d6ad9911d6403972235524e1aad62540c4b32eaeff089734cc6257a0dedc132ad25b6275c9985a72c33b8269e4e1b7d7b364a80b93d852af6b609833fc4ca926aee8451cdc417872b3180d2ad2579da357987d7e3fd79b25b315a25ccb362d83a5b88554f20688723a73a6ec806fd4224d962b9319e0bb32dd70fa9d0d7f756a33a970a859fbcb501f0a6b34b9376c30fbfc09730567ce48623e8a9aa0a82d40a9cb68646fe0ed72886da09fd3d098c47813459e2ab82f305b5e52d432321cd2512b7246079f1e1bf7c2fd0c4fd124c10d27f5462a09a7ec0a930e9a79b8759669a2d811272a4d724cda6ea54ad1ddaadbb6afdf4e82ece95b1de6bb2ed5ce380a1437e33c83df72324ae72d37d4caafbf7e889a5ad308aeeec7e8180bd83d010fadd4797ce554728434cb5c96f0ba9e8b6a9a16a3ea8a78c60357ffb96f821084c95660f6f93c72954d11c61b85831ade8e24c511f7b57ae9bfc49999545b483c4eef058f8e7427333ea964842c5a6cf6b6d93f0832bc82d58d3139b81c3b0c90cb3f40669c26729b280c50ab617e8ce8de8750051d2e71db81523abf1b545c1b68ad01e146b272e62b403e872dfc6dd0d6164713d588ffe830ee977911a51368f8b284d134bd00aff8b5cf4de6005e5208a2ddc24ea77629000f29f558af75ca7f0fe55b534351b53fe15df13115ca1cfa3c83d80ab493d2402b992927739d1fae18b2d1f59b33179b4e6e2557e593ec49ac24d7693f77fc462fb22f69b50f349bed3b49913709f3f295377097e98883e88cc108c509b05efa7d4c409ce84cb97ce29de7158128f1cf00830b19ec3404e0625f9bc722a5d48afb673428cd8a34d58abf518e0272fa7f25c2c25966d456d92d22ce2f88cb67eb15ce403a2a9b0bbc67f09b3d1b6e08fb5b11d2b1c683682a306ca6d339b4cc546deea00f3f9ca2958b824b64812e9a8c2c3350ecd81ac3bdc5204ab9fddee1969312e9477c12d038ac3b0ed11a720121f8db4b6ee4916bd25933be6bd08cc492bedefe77f31d985fe26be116bb24cc35e7d120413ec6f6aa7a81062745a66a05db633680920f9cd86be34fa83a720e38d1c1ea4111187b0c890c7a5a9767340e32787e1939959b16964e494ec91de8afe1d1f2009f261bf86b74efb030bfd3a56084f0745be461a1de03302059724676887b868166630541d42505159dda0dc6e649c63b56cd39e4fb02f1bb2b724204a2e475ca04d8bbdee6b5c7c597f4d349c97b7a7dd0a916389cc7e6f6ad307517dd75e6c119377440df29c9b05256ce861c483d7072b4fd985e045e99b17231e1eb78a513f01aee8f92c249bb552a8b8497955e6512291e5af44d7719a618ced060dfd57c8eba72eac8bc29bd3d75c96b2811e81a02983320594f1d5e816695ba623b82f66c5e8ab9e049b3836d3e8012db8dbb8048e4f45c5ebb7eaa86442e7986d89d190c9d2fffe352b72df744ef70ae1f05675822a67c5fb5d65fbd724fadf8c800596790c5cb79ed91b04411c8901595e661a38eafc8e4bddf5206722535820d538b6bc469cccff7e984a1a70762c5f9c9d704ae09579b9da76aed720f8c25d3af069b27cc0a6d569ef8218fa4760d4111019e4a0ce9a8c6a89afb722a385329d456d088a71ab61f4a38d1aafd9eddb3ea8a14930541448d27df9d06c287bfa6273c449803dd1b6acfd78bac9c6d9806ebbdb0ce29fdb5c4dcbf0c724490dfdcd16bf0f9af0ebdf8dd0d6c1d491a21913c275101cf33247ba1112272c29a273d9517d18df5ac90a8c4e75783ea02ff4465240009d6e0fde1fdc63a72e4c151597570c5fb637cff2448b195ec4e7dcaacd277ed1d296e0b1dc00b5a72a78046b533d3e0985bb0b12b209813cda15b5dbc6858b5bd0b2bdb59aff0f9728f38c8883c704b276628d024e8f3c6449a623026d324841cdc97acac99e0f17228d348e76a173f641eeaf55ea34024b8f9ccdb7404e5e25fe7e49040fbefca5dee9a846b0c0acd6a5b35380574bb9e15c3a97625c2e02dc1993490bff1c3ed7212c1ec4ea65a6176e4ca3efc6cee8fba5333cdd9ace8e0a2a4cc71f8791ecc72422e90a4ab294415ed09b614eb133f344ace044147b1b6c9b3f0edd2a8aee57251694901d5df25feaa8e1bd8f715fd0029dfe5d9d76a68c029a95eeef416e64ba3616dd891b61959bb341ce41c0e8e691c52ce4340e8cde4ecdb8e0ac1df7c448273532f9dc92a3a3194e23afa1658de3f58292e7667b26edea84d67fe49a372a5d5c0a04a8308ddfad1d8d3a0bb09be479084aa862f27a26fc7740072bb4a705b39614782f4af41bbb914d95c173f9a5a06183a6abeceea3c48a7701edffc72296a23e1611c63ae9b83e0ad20a5eee34e3f1d3dba9be04b2193361e72aa62380d595e275bfeec50fce7e7ecf37a9c882293eac7e304e21fd807104da1529d72de20caad40dd84e22a893a18cce2ddd5c90e45de250cf99052943551d8694572e58964c117a5df42291e360bc280a2dc049803d9b57e1fdf7163778a1adab164eb572090c839d97a99d587f2d1a324330cbaf50cacca40d630d40963d891fb0609e0dbc69be2372a81401aba352bad68344974a8624dad8ffc0bcd7a3667cd72399e2d4c58bf59b508c5568a4e6c0524c54d163ea91c0a76796e1d0198c29534ef59cc5b151c22c77fd29268ce130a683438294deca9ced5afa584479c625f72e6046b3ff00e8ec308de6fd7effe2d424f5fa2eadf16a8f3e2dcd023707bb936b6482e598b2636b19e715b092dd1ed8fb08aaaa5e29dd7e89120c523fe544271fb20b62de1aeb797ad99cae517b10a4aabc58e022555ea5b66bf54ed7df3814432ab5a6d2b39d9f1b0284beb7ee8b9c8b542bff384ac1bc1e3265ed5d1e86620bba1dea0eeb732799f6e43995e128b4fd0f0a0c19e49c08765f4736b3f3af202116bd0a547a4ff2c14b179794ee1ab22fa6ac8e0ca2182a92185bb3d9635107249b265e4dbd77d3137986bfd87ff325ab642450e005523c4f92f086fb9c05402c19c039adf6fd49b2e71afe86200ba706bff35ba6207596593112b43ce121a72f1d717b39eb09b818aec1c0f4340007bcf9ea15be449e99c2f23606d2320c15437b4a72dd4ea66d4f084fc85fc7fa29d85b9f7e8c574c7bfbc302e05a4b6b872b697871651b08c8e699ca112530282439d45ff0a3b494164e3fc8ef1ddfc8d3216317275b2b2bb69cceeaebffeccb03f62e584a449920207154a888f1bc2d94c07f907f3b0cb086cfb56a76156cf54b9adc04f00c606d8aba96fe24233afca726a394d0072ccf14eb6931616b16ef37470375768c64e097e10305c5aec86db72ea0191b8ab446414f1db9ea038ec83f17a6f2c9d70e0b6b269e50cf489be5f72f565f57633d0b43334e52f3945ffd2ce7838d3d684d9968410917357908ca8725ea2cd5efaad6b941798f2d10c795c0fa2810ac7b34f031b94b39f4144fef572fc05a409e53a7812541c3ff896e7327bfdf5e7e5fc213911e5f01c259a26b16194ad039729d79809faefa9f6bd8d9e7551e070e575ad9f80105dc338c6aeb84b2bd7e92a1fc1e0a84c3e9ee32ee11736c0409715cb6310a2ad9ef38d49b8527261d5eae3b8be0f103f021357e405dd3a606ed87d2519abb9a0ec139fc25fd15a3e67c44767a650628d9dafe851873e09a6440e3f21127da84e55a787142e1c72809a880f7f43057c66f4d8a3be8da940a978bcd8f510b0893b35b28b85cb4e68a9c15ac96d79dc447cf66ea9c71e20fac514cbf088ed48ba72e43e6e8c150a6a7eab0e9f66a4ae7986973040fa4dfe0aa5a3e988afc90661554106d103c37f729e5967f4b7555cd6d8fba99a94bc20add64c0839042e77c623e2915a16fe975da922e8033fed04e9536d03825eabfead6a212f634fb715cfaf4d1fc954b3e67235f9365467dd6f4d9d98e1e8a70233c809567869a0c4940d7757ca6c3db8557277599874f2db7892af049d5a7cf51394770e3696409caba0f74882979d3a2b72db2806cdb73fb66d58548ff71cc43d9f103ef7f40e99e6e20e5b22d64795a2729574706f8ddfd61260ef1c183e2f2226b4bdb70bae580cb9ec08e3fb5cf2776d26728fb517a978f0d38eca537107015473d150af060d16a55b40e3fdeb90652408e56cd8641b79ac25afbdc31c94c9d17d9486308d0a99a9c2eb5216eee3b330b2fb55d82ec62674b448b8ce2deceac5634f9adfb8adb47ac4afd588dbe68d725a3921bdb0a19078f8e62d1574d5428b558f3b8d011a5507fde6e8981d9baa724ff336db261d51649a5c1b7adeb867675b7d62191d6a4fc4b8228692551c6e6706224909a984a044cddfa033175e97ac96ee8841a5acff8b1b65d3c3f44dfc1501a9bc88d381c7b92cf63e242fb499bf49200be58d1f6441719674701262ca728ee812c15cbf0832c70b9a7f3bf7c0303ea0425558027352427a187ea5c1ea433674c1658bfefb1caaee78b732e2ceaed270e1b16ff8b9268fbabe5db891db25546341d4841697a6c144fe227694481369060a761a0a03ab5982ab1b3b3b6e72ff2a9b28d5da2aee88a0817545e5227a5b441ad8c4e75566bf590933f7c2cf729e00eb1eaa622a7f2ef8a0ba68d998280cd23886d1187835a8872d5844c16f01b5bca8698fee27c465b29dc9251fd039a4303c3a469a74a94bd368fddc142f0683d31d7c220718d0667b9408e447ea2ed9d7c8cc0115f66b0e5fcb14b85a4434997494bb8366b295ad023026a9bb9681a93dad6fda25750f6aba6f2d93592f722a8d5ca22d7f7ebf3def7988de0f8071fc9c979b61a4629e345a97e5af3df372fe562ba38be1adeb3041be0f69cc1d11a241e057ddb039c3253f6d9926729f7259a39ae8ef357e9d2fab704d67f88915dcc1f195cdc4eb9b331460b11c6f93724799b5ff07d9222b99812bb31f2dae1f21a3f086816a1e45b43c16681b1a327253c53c0c2d4a9fde45a4ad9dca067b5c14f4202e9cb7b767c81f16bd3dbbad727843d868f7d22ab014d6d526eac2bfc5b2b4cbc2281c0ae2b5701368013c9a7285624c50776331e8cc88187a53e92c3f591895d14568fbaff139583f9a25f87215e50d739131290147efd1f1bad4f94b6201fd51bcefdd45e89050dec9b5997288e295c7bd3dcf3badecfaa42a46a20a590809c94e5f1b44f5bf63691f793472c1ca61879f1d0dc500bbff3a670518f89d79189e69afe7929f23e0623497b01e2678d6cce488cbf48b6dd7f10ab1ede305fdcea6a5176079c5f3a8476c563f7281e3c538c74aa76a9e14319225caf513fdb96b339409587148b24a0701bf07720866562266608c3983a1e8a68472e80b81b04df09a09e1a8dc97ec5ac1bb9c12fc062d78845c0fa0410dcf8dbabb4f24892b82b5e25adc4c3811a7788febf7147a8578e9efa8a52c4b5dd37da4bfac5f0fcd1c01b40337d7d74a3670898cd555b3f6597960745e0988ccadfe1b018aeb7ad1d1c04e85ea8896c4e463aba0e648e16784dda5e73415d14879785c38f0174489e18fc6889da9b89cffce3ea8347274e0b60c378f0f984b77980b64db3148470a617fcfa2cecdb3dcb45ea9f4cd726dd2599567a13964fe152158cd656656f78a9899b47fb4961d665cafc8d2812b8a4aa072f7592d9586473f9ecb747c89b7eb6fb488dfe48ba5c4fb024c64df72f3e0b1ce24a95c651937650355e37e8efcf2567e696dcb4a18701e5f7e704b5f8fd123952eec8cbb6341c11a24d6769b912ffc908c6ad166b885dd65de86757208c80f1ae7e3f2d491b99fbc2871e23dc681eef3fd1dea7e07b86ff13d5b8872e3926f764f5abb17b2063f30e31c727b1333c2d3862bab3664ef3e4730c25d21268794f15e5bb82512412019ae25b40760e746a18cdd41d3a3d737cbe79785727fd5600d2cb6cf7401338422450b8e6bc8607c0e68daa6d39fa7986e0ef4fe72161fcda697ad0fb97da1a3cdcfd368b27ae92c31e4a60816233a50f26b5a86473bf4502a3fce5d641e1377ef9f6524159b5ecfde5d4a367174721b6d774c6d303dec3c2337ef32b1ea49db61aaeef0ccb41fe45cf2d7288f448e52069522335dfaefeeb0b47ead730ec8569ae6c2e9f32610a9085a3e0d718e96e4fa19ea8c57957bec76b722f0136d45a1df0dd3fbca25c1cc75b6f43cb59c1043c3274d04727a32c560a261b449e6b9fc92b7b419bb8418edd564b0b0383453bc20f851f472eac7576d85f7f2482b26b88e07d6b5322289f299e455c664a84998db89f7592e7499ebcd82562d29298ad12e58db1f69e62b39992d236af2c0d460604a9e4d56ab0e38871c977b80d2e00b6942a195211fb896f47e3ec9cd859f17b95f1f14724c8215e93a59f8316c7c27931eed0fb9a593300904ee386e85decf414a25a54b3886a7023e0c6bbc7efdeed619b94a2d16b2ce563681aa45a83255db0b31a16b152beadf4d3c2d92dddfadb4e883395bfa50f1f6bd2cfb832b7502cdbc1f1e72d1d76cee1a23a1ef16e5efa6908f4eab0cc2fd30a354e870545bb4a310847f3d2efa439e6ee56f7980cb8cea734e87f18396e7c4dc72ead090c60c0e9e94d8124f47fd03fe86ed79a1d12004477774adb27d0f037b8a2651a22b4fa0a5f16b728269aaac19dc484899c6cfd3bc81f37e448fd2907b4fde2e7c31521eb262d0212503b2bde4a88fe1da1cb3a9def347d22e02a799c050cf51075aa2c469d54772e1987b4c5a872f6fea90aa1f6ba2188a5286bef69925eeb9c376a1f016529015a0f282eae404808e9911d369845b56698701c71844a8b0554e245bca8c84be722ad670421d9db3ed1ddc2f96f198e29e72aa1dca829bb84d9caf8de1dab46f5550f7d7be6c408c2ba88ff35d9b362b7edf7c094ea4fb763c153e3b9043a4c82ca038be54ea556b2997af1c89877e4e920af4bf3c3e21ebecd0a3c4661a256a480ac17c0f8e341cbb065cc52eccc898efc1e98d830330809fd836dc39ad9a8872e2b661030c0b72697364630cbb80c5806ac4b04e5771c5aebe51cc93e4619272660ab9b406de60cc54b6522812d4783a6317398e41263cf5009f1c1daeeceb7251d338825c7348855caa134601a26c73cd1af03db5dc65be867be2449a03d1729cff5d5311b279913727772668906d3b54e0c573bf8887c0f2b18b30d1721a724bb503add469c1a813a6a0ce5cc0898cb8576193e256fd279e49f7eb27095872da55860f929305f4a5459bb78f3d9e42ce8951b9eeceab8df5079f936f8d3872d19641b4788caf10846c92a62f188a74493be4556fc0addd2b717ff2a9d82411f28e69a76e628dcdd1a88c54351c68f41f11f5b911f5240b9edd9010fdf6fa72a8a8206919c9c7c20030bc36575ffb71d47a5deee70fbbb8ceb07c66a436b872ffb1dfe99c9d2b59962dd215c681338c0228884105921c35626cf92f15d6be72d16e9c3c30996dbe93bbe39ee5e4a9b7a1aa74d0892ab392863d1782777fa07277b78a454008770d43209435799ce22090f57ac084de0ce0545d75b0144aa472ff25b5fb79adebb2ddfa952e5c5c75bcd66f4b7331c5add4af1da9a23d52b0250dfa4e901d04e0c32b35d9b468dcf3ac0013e9bb61462787be63b88ac28dab0b32282082959ec2bf2dfdb3435214c9149a92c9113037e6b4121cce3a45a1a5570b68ff0b87def6beeb100bd4cc29c6ab2c3cd6330b526313d8fbe2798958c30c88f6d1707fdd0f6ebd0ec9d0b18b6066e55f5969cb7b01035388d9dd7626a37204f02b4ab892f3b369349b946293b9b6d7072f1457d23dc80b56b007ba645972eb31b96029488a51ba2aa40ed3fbec351e0ea10c38a3fbd6b54a7ebf982e0f330158a4385116ab018c25edc4115db1ec685c6e2f0c4516e67d21ac9f00194572847a8ba7c23d9bcf17a3d5a6736062ba05e07410743e66c8c8285191e8dca47275b8717d0c86319e5ecb358aa6c9d8f408387c5254d983eb8d07f5b6148aeb724a03cdd9acaf6d5be39b8a35e5b543f5d57d74ee46f213023c52beb4437e8f72da7ff4f28f4e605876e823ba441803d72bd630e7ec00866f3ed0dd3e809dfd721830a4ca266144d97581faee97d3f11b1b3da0931ee6ffb033f3a28e62c569469f4e94961082db9da469bdd056d18c3076678a4a5cdfdc0936808fcefa989f122292c61353838cc7fa5d67389b9d20d15d5f6bf3f796cd953238b2ba36b47c069f3bc0170b367db6b250122436e287dd48b4b714aac7f7e33ca765755e9f66722b2f77c93152d906e0e34cde500ecc1e67561c02e49b346b3f79a67f5365b3728be67d2a2e944f807c96ee3651783ddfff8eeb7ce28a42fb6809fc9ef18c31385684a179f73486351aeaff0b78138a851962f6384ce9c5ff05ffe84a674b15727853990036c3fa0d6c4f7dd4adba1e4850bce12b7448f602bf15d8eef63f814804a2a5f9f29fde845d263109d622c046af50604b5a5690c52d3d2aacfb67d372fdcd541d952ed65d008c9f724684b5c50e4cb9c30ace2300c157d85357751372b0fa7b9148b1b0c7d0c5684b6198be419b9608aa900e2686b98938e9417a60721dac5ecc3c4a309e31be00890bc0acbc4ffdf5c75166662ca91d4fa680dd297245c8bb1888b2bf08cc4a23464148a63d96f9d37f79a89e9d9976f67386abc0723c0ac071ba05d25b3cb47b7e4e28ae3bf252e95e54c1ebfedaaee78765ed99616602b709c30857b717f41bb5e68803ef70faf2c2f263af20d81c203c1ae98f699d290be293c3f710d9a7da5ab211beeb06e76625c1af4f229a2261a48deb6004140caa412ece35ae479aa6ee86db77dbbb7f7a77dbae26be84164567568e657209796e456142dbbf13e3f5a387448b3873bc5a378764bb725783c3aeb87a057277e3deb892051df44bb221e81e4f8f502ffb55769cae5ef9a763f769f311c34fc95d37d069bdd9303089f5d52636ad03e0b6a8710adf44cae60caaa37ac6f306b13a3800e34c70d70d0920b5541278d1e009d5e461e57e3daffd083153c2ef7281acd0874f21af0217b08fd3599c621c1b76e3bec317d1ce3ff161cf2a78d46ed1a4d3a8641ea8dcecece9243c091cb40fdf8aa786d2efe1db75b7a7294b3e2a25add6f3f1fc9187ef739cfade2a6394b3c5d014f2a01d36d1d987a031ef7002e5e12f00d33980b1a4f19eff7778d1cce6d8c7654d582052a45ea6b168132b6068f2976727a20a5fbc7b82e7984defb2671fc70036223f05c935e4a750ed4e235e6f86bdec033a5350731dabe87cfe1f6dd1cdf1356a58c111ba27d1a8d0d97241a6a7417411b0cef1eca502fd4720e8c4e54a6ba1c6287541fbc34327480c53c2312e0d8eaf13b7b8ba68f801f212569e188ae0c9f2944f469cf58e0782d2725d699d677941c93800964e5c56aa83fd020ab9d00462edb2e332c2be4852817259026c17e0ada2a8d86539a1d3400bf2fed5e9e074269555448c996958fb1172100406a6f0944209fac744de3b078bc46e4d373cecdbe140676628fbd3f22072ecb361a16ce27af9d385647f41d37b8f5f87990dde242a8a8fa824b010c9d438f2ea21082b5dd72506001d0b015081dc67f8a551548972e6c774e0aa66e344720ae139f89047a35b13ae5530e29659b3fd00c0eb52283d04866c3138dced91726e513cb4212dd33c0b4d95413f682cf79d0a0790ab3563227dbe8ca48f7d7f54ea43cc05d7fca607874ef365b7fac369d73417ae132c17073e19011ff935cf720901f791a9a3203aaca30e1edab5ac145b1d60ea592d9043ea0e37e0a5ea9c45fa9290be2da49f1a77a1b9a722f1d55ae4fda3a659eaecf59582ddb0342d907248140c82e8e2106a86aeaf15382c5e77f91a2fbea6cda1a7185e41203a5fe372018cfc91341f32cd821c11401152243aa89429b419b56c643586077f082f610aabd14b7fc73cc46cf1d6383f751bafc33a7b7f7ead32c01f3e1c7a91dbe2e5722dbda8ea6d9f1c429fa70ffb439a74934fa3c2dfbc0ce07d1dc479fb06b2ff6244da3fd77c7b271504bada81ed80a64901f2a7e074f7e0aace090a86d38b87721ca7aed2d26e02066c2ae256459d7b1adbcbe155d57420328725f8727a332a7267184863c5023f19488609364daf6de92115ced388fec70822bb03acdc43487249a9622b31ef80923492278feca17558666afe993f6ae2e83e8eef30de94d472a15a98988cfb0c315159100230bd82b4d16c9b4ffef3064eed583d2f74ffc049be7ec97cf19b3fb23114608580c10b4aa3f0b6ec68106f64c0d9ad7b7e66bc06faec9e0a54f9e7eafebdbddffd92a84933a89e4d19e6d550774b5ccd46057b0e87f33b3e78c53f0ebb4071a9d4e83b39319c33c6da694a64190e87d70edfe172cd34899496db4751708602a7d894cef982a30ee9c7b624202f95bdd497839e2e1ae93a097e192e250fc23ad54003986747009a868b215e5c69477ee5b8017f722e736ff1102dea63fae1c3180462a0a738158f83c532fdb32e4555675764322ef5531b93484d65f8d126e9a1dc776b8fc70c7ecf42e60bc2c17febc99d5551378981cb7627124aba2729fc9e75d58579076033f0932e358775a76b6bc66abe1c69b032a094db9ab78db53d4f48eab822eb8344b34f489067da7cdad8101e2072fb72ba9126fc4bf28f9a5bed5a1d99fd444b552b9510b2ad5202654ff2fa2b725c344bba8a88e3fbb3bda18c1fbb74e0e53fcdab92bd0ae420fe812d94ed4072ebea2fb3703cd08e021914db3980d5cace68c914c7f22bc14959da265ac22f40256cd1b651030d5623be9c60a8bdf895e286fe91b496064bc5a9206e3d409064a03dc51e4857f442bd8af5619e1ee93106a8ff75a3cc59416fc90868450cba726cae118ecbd7f31a1400db9efe91443402d6e83336a47154af614f4e2a5a1510ae28cd7389645cf16ae4bea8b3dc31a692fdd38963dc96652e401e37f064ca72778bd0d95f0bd96dba15e2cfce98a56d34da22ee0a09b778f991d3ada5c024720bfbb7fbf60862bd2473801c506627a52bcf06f08179d9a746c9ec39c491e772d80a8ba6d6f734bc5b7c6d74a8a026eccbc7cc336277bea9381b9ad1e4565631cbeaaf9331df5c90adca296de14458f5e22a158ebe938c2b4e3ab120ce9e6f72e35c4eacc34cca3d8f37c75f1af204cc9c9545ed61dfa0c3fb5839ff912ecc72e7133e88c8691f11b051ef2a774e64bb015dbc772b37a43904b567740c5f6f728a95acc8b61947d5645e49f586f17e3c7a779d36c1d70944ddde6f05952b035645c28789508e1e7e136d2243cda772e13d115e931e23033874feb00980e3df36c061a47794a62bd9b018af10f25e7c475d01eba354adbaccd5505753348d9e31e0d5007c8e62458df0f45bf6fc0ac15d1f05758b80d4346e296b0570a148c97282ab5ef67ffc609dd534406e1010c77fdb83f0e3f99fdb910fa2206597d6ce72240817fb3dee64f7e1849b626365de9271d6c4e508584355922934c338f0f72a1d3d1c8691b12795498964cc69a80ef8bf47289ae7ea7bad44271ba099632f726c58f75c739aee1ba82ce97420d62cbbd2cf82851876eece65edf2ac2a9e0f4e748105742598671820036db66c4bd55770e7a9a03e87a354f5c2cd94b197825069df9120c53a4ce196efc748afde85f68edbf2b8541505ecee933c2addc40f213d68154737b2b0ffe0ddbe0a6fb54a2352ee3887f4ad38302f7cd603f339774e16d6df1140aac44a194b381264bff9eed7aaa232a6ea52f519f50080448f7d48a07dab4f934d3c550e0da4788aad22938cc77302eecf26bd872da01ade1104722a8562a49ef9b86becd6016536ff01a31ec80f308f33a8214f62f70bdab59f490127c0b4d9156c00bbdbdd728c6b03aeb5a11041e62b7df0f4f239c819189072ff3c5b89cc5dc53c3cd96052a521c0796d6433500ae81ad6e6875887e43a7e0d91b45855f7a8da852d9fba24ddd39ef1a3c1c250c1cb0cc742f695ba3ac21f2553eb5973de2cc8d2ee4e3cd9fa4cca337c59a72bf0032223eded7b49999c2e46944aae9e87a1f30cadb6bbbff58a231d5851d43c0d5fac1678c5324b76abe5532f167cd5aa11888b9d8246f02cadcd19115c03d63d1df346d5b2e1b718075d042fb101067a76023182065cccee290fcd5071be79ff0f20d89659b26675f7b272f4f614092ecad7d7f6488a1f01a8887d6dbfe613772a2fbf2d5f04dac0367e1ba124f434edb86319d6ce449b3b78543dd5fc081048f01d15ecadcc6662fd9913f082a3b8147dbab50bd17ca5978ff642b82eb578a00d7bdc87b9946c53c0ff07f8aad3ccc9fd9cf352f2d50982c7d04821cd403ea85e0d538f686884a3888f2861c21ad2ea39265b3d4bfafebbeae96f10b65e4ddd10d7e34409dbe017ae7d728fc1ca78b4d63b09130ee4e65cbfc653cb883495bb0eb3ff7233fb7cd9c35f72dbe292476c84f9e7b1036fd87e20a1a6a5966617aaf27b49582fc7490346bd72dece31bc0283773ac36597855facf2871e1dfd439375a6f3e3b3b4132bd2bd2a48364c71a7033faf210efbaeabc738a38877fb3737ed9c0a5f4dc7b8b79de04cb57c2b9fa226981db71189d00a64a9b34fc3570dccc5ca78dc2c4698ce962b51a8b5d1896c9a5e5ca359d6211e7612a776d284d8f8f05685ec8ab61ba841d61e8b1a502017ba8c8bc4e861b4a88fcf43dbd1a28259108d69347debb5086226723ca9af5098577e701aa5392032a50658ac89fd51f7e3095e654b1cae766328723338e32a546d1f09ccb8007c15b288198b887716380904971cfcb4c32a7afc72a25f9db3a0cbd336ded0b66fcc0531de2074400c14e8a74d4809625f1d6020728c4412fe15258f97ca21bb456fef117d1453c22dc3e2a3d80fb3eefa960a38728684ebf9eb8fc1788136c547203b47b5ea029708b24c8188a90a9e1c827a8172d0b02e679961f676dea5ece8ef09f0147e46219064a2a93796d727ae6c46c84a9dcd40025101acc48a53663364e625e941de208ef631e2f73daf65cc28b89e72cd41ff62534dc3fcb91dcbbbecf3194058f8a4fab1a277d05725c5ccea99d12a868c6b243a6c68d2dece4f1504cb89374ededbb573b9937950460848c867de13034bbd800ed3bbae35f0f416dcf7b2c920048f6cc2c9c20a7a8e2aafbba93772e0f5cab2d01c9f0e9b8e6b79423f1f60d7ed3060c8624a53dfed98c1ea393272421c7f90d523cc155c51304a20358d0a0a7ee20996100ff7830c5695fed5eb22200bdbacbf9a3d2a5d425c81ac5c6eb852b0d449ed11b1346cd965acd32c015ba9b4c37955afb49f3b7187a6285c60c0966047f5f9f3dc84cd1c62852d1ef272ea47c7b60580fbb92469a19acf32267e6e373c504b746ff298965f22c40f0e131a2676ba51b5038030a47f9b34e004f4f12f9458f5c2c4e764211b17643e37284301c57b76ca16f7576b3d74cf74471a45e0dc06ec877ea6044085fe50d2bf725a5a5c494a0d810cc70cd7b4ef4cce6f21deca741c45a2856d257ff8faa02272c35dd541e2ff85facd1efc6de023085e4cad6a54e43bb6ad4deed615945d980f9f79539c268cfe0847cc6bf7115ac7279c8fe30d7846447c36d3286067033572ad42864db3cf663d355d2331fd74fa4ba34a19c46aba37f5bf55197ff44643324ecf648adeb039deb42f2ab1c17b076ccd4ee989eb90aa2de7f45fed142ca472bf5772b85f699259498c32073a5185855deecabc91a5883d4279a05d9a01517262ccd1d9396484afc4e8032954e40ff08d54a0ba7eb747d801f9e5aa496d177250a6a960d106ae8f9aa7687c0254dcd7fe5ef5062a74580aea582eafd9920f26f44fc8264ed739b0354fa2f09f36d59719192c34e4f73472028aa483de509c7248c8a10872c2673daf7e6562f7e1d6c733b55d411b3f9f6565bc0576348f0b467ce2ad559a90533e337f0452536ca3a84c8af34204046ca8a43badd4895e2572a4fe56644c3cdc8f1777acfc4d876cbc53bbf9540b2f993b1569a4ea30780b7266e261c6bdc4f3db9c3c9fbb02c3f89101168a9868c97ffd104360ec0cc6ff720202d389e7fec7e6f1dc79238104a381e039e58261d6fe1b3b80a6c054dd9572dd204e205d1809000d5cf93ad5132ba2a521492548cdee081965baba6191fc1e0a13c2b0c07e9c59f33f8251ffd71135696cd9fe2ac500349d56e1eab9da0a5254a616b261fdf331f146a5763e2ceac4774b97f7f459f6b9b726673add864972d7255e28e682cc51c67c391b67255c4757411e5a7263eb1eaf356ffd72f1c5564b9ecd4a02a1c6038600af379355d40273eb99b0fd729ad0d739897863728412517185503f2895952ccb9cc46c513f3cd1e039f4c69f385b20f214e4242f7a723c65f1c37c303ba98357ffb8942e4e4720133359239e6e47e806cc374ce4b77214223cda6b2f75aa59439f742856a5c6b36c620005958aa5694af8a54833b972cfbaa23bc31794013060bdca5dde89d884dae8fc3c0cfb55ec6ffac7596a277278b110aec6e9d9a67bb7c1b066d253e7b2bbe3bc1b338cea4fcd5a19e52b9230f115a547aa2cb3cb7ff584229e5e00f3d7b3ce94eeb191b2e10fa22a5f96d272a75fe39a0674bd5eb66014fdcb602e5cce258ad7f5ecfe669046edf3eff24172168a89aa9d655263b67488284325ed4053fea848ab25982d571bc1a1312ec972856dd1dfdc7a7c8de5607a9ddddf29eb93d8ebca6cc023b68d1ae969a2aba3725467a23c62fc0f927b7a468c63852cfa042c7de6d6e212b3d17f65b2fd695b0311a8ba5ad8a28679ea8fbfd16d7773e17e8396f60d6989fc0bb49e2b0026ab002a2f6dbc85e3a34a8d068b68b3ec0dbd82259c2086125c8211f74c7376aee6339cc90a58ab819681a2005b453342425728f5cff263c4f01d3ae3b7b9e4a75417b1f7b9a1f6a54e9b05665b5d268a56d44404434b2d65a411246612c2322cd472f7bd2ed024048dfa16f2522ab55277a39887b62c007caf36a2cf6214183c5e725b54d0a545536bb9f30f0f730e1aa5d07f388a6223778ae1cbff9f72c57adc72bbb62c8568b05d5ec727dceb5ec8fdaa4279f26ef54d1affef23e3bc23effd055e77cd94447e8ada4767e25d3ae7ac6d9f13834dfd9115c201c2edef1aac8a728740829f6e0df17643700d72488d2ae491407bf43c24f4ebf53cd19956f72f0eb319edf8e9edee2b22754d9141cd6377c99770babf6aae639905b703aba02c1404b280bc57567ded1b24e1c5874477696702624262c55fd7489737eca324c05bb11e42410e6d189e4a469d27818164fb11a316712d483a70f16484cb18f751729234a3ade02844d9ad57bef55a9b4c7522335f3e5a239a2ead1232cade742a37a1736aad1ed184a498a56a697b46b81fcf6650e8f88c19a4e18fd5179de35232bc781557fef76db59aac63bbcc7fc8e55c3b443bc693260ca67957e43f3b33729612bea356240659d92b8a838c89541e271eb7f3160e7d6b645bc7c2f6e26a72dd80ae98419270dd451ca2f0f779f757b179cb82ad0cd41d959bd342f41a1725998047723fda32b6fa1cdef1546e90932c0b4d369e1d22a2ade1ed31584e712c75ccc1b2162d0ff381b0a0a4e76d36ee1efb277db1a1ba5822454e8508d1332ea0e4d81e6dde3c8dc91306912009d987d8b5deaa52ee8aade060fe169509f472d0b118dde7ff4a870f64b9483ca368d91bfff9e3304e86a1ec474020e5ed3837409c292c49f168d4211ae4b73e1df13bb68c046e67720b0dc422e960bc637472122d9d221401c3bf1c267ed68d24ddef66f98ea24e87207155a491be6b94e2548dfa2888b6cb013a9b94103da379f5b0d67a39669bb3309a62abfd9413a6df721f17d2c9df1466c28dcdc46a9b2b3ea35cded2883fb5b6787906bac5b45b567203ecfd7e0304f898fbf2ce24b484de05bbd8c5086f52bed6145b61d605dfd115f3c6f424c1b7550fbe428fcca5b72194fef27f8ff3268261e4058c1b0f1e2307aa6b68aa3e39e68907a47705e1230c71334872aa4f98558cdbe89427f787da72ca0197be8e59a8b55b0f806e922f8c666efd68cfef82df249ecbd57c011c2272368fe1955151b75a2657aaa1df3113e070f029ba999df71d4049f5c0968cfd7229b51eb4897930696df6a08f3cdbf40474dd99ffaa60b54298882d8b17f8ef57b8dd5596da1b21851c88fab892286e726458cd788e9c15c8a6bd774080a80572eb227772459ff96bd173631a4902433f41c1c84e5f32c3f23bdf4c29cc90ad7263d16b81f0a25d67f436caded737e490dec4999bdfde78d5c8887f1a3b14ad7236dc479a8ac55d7df2681a769b9750bdbbab0bf4ec29676ddf4925f8958fdf729b45d21a2703e42750a26e5a54220f2f6019dede3409eae60bf759e4185f39728d4dd9e7e636c84075f4cc188fdd602bff916d8ee22a5abae609635ee7ece072bc618e4172e71e625eaf9c50393804c9a5f29e394cde2895e84a0ab8fe1ca77245b35db2b27966540a3e3ff386a793575c44d8de027b95a006a07c7a6c7e1a72ab371c2ef1b2a04b71e20ea79d2159dba812fd01aaaeb6652cc3bda25ee8a172296d536313333c58a780e97b1f673f9b2450f11f4ca0e3f64ddeab27dcc704384fa84fa6486532c4db68a45914af2d0dc5a83895b3643ef3a372277a6dd5bc72d038f22b1b70050809c3d2df929fd631d2a646c52cdf723fff2c38b5c2fff1729e51384fdfd86e350d65e0c939430e50d0b04ce8dcab44bed4ccbd0c63fa1b726b3abe8652c4b545e3243ea660a267ce1ba8e74d33574022faacf62f9eafd455cb0254f74ba59f572584e1394561bac5ac7b98023b51aa0125cf8f586187146dcb9a18c56adcca6b18dd23273dd22207f277f804798527690200e851ad3c880f8e33cfb8cd320f21119acf765d154e46d2f3229dcedf7f9df6ea71135bee7b58840f24a73471fe3eb0a67424fac200594f74f55ec747107755f567e953fc1c2491f1388d23c31fddd50a9bced5bfac534c7f3301e33154f3eb8b90d563d68f71fdddcc55c382346e94a70f3141bb7d2b02b9158c166fb2dd755a5e50e111e372ce3384d6153f606f8c241f15cfed7c26adde5ca03834bfae135bf1fb21d1f972754574e77f8e4d50b60cd1bf1330e5a3846583a60517186efb1e3959019dac72e4f548104037cb320dfc72a5993c57109d53fd9f5a93f84586bf60a969ea11721a91aeb2597331d384ebeb04259d2de27e9e80926411b7edec2043d825cd355a49e98725982d8a13b8fb5e7b7c12f6f957964aa125821acb4510d3fb2e5aac1c3b28e745089a345d734dc576ae6834d7e34fe375feb172d30e5c677b6b98e9723206f4eb3b076895090b4d9bd8baa4a2f98e9735dd02aef2a836c8a4bae40e3fd5b3b86b7f97b1e40aa39b5178728fdefc382fd0cfe350636f5ab24dd065bc24189e2db2a6155de1e4c3a80c91b7146d89abaac9bf84eb0843e583158f10663b823ca0254f5125231995d54793eab9546987f26ab596160b24078a7d55bc20353eb468a0285320dcd242a64f323bb535c7a07fae80ab1a01e55c32063742da07db01efebb33a7bcada7d18cfc3def4599277ae234fdea749fbbe2f4749838c72a0b05850158b145b870d4cfcb57cb61142a2b23b409c728ee379ae17972176689398a075975da654bc54f0400f04d8851883b5051634e0ea1cd26a8b060d7172701406e6300c52435fd1489dc8a1c217d2221085e8b00536c82728e9fa02f1721402014b340745a20e1d31cbd8c6b933e1aca2ff5acfc8342d24adf1d90df2722e6b2975726c22602a77e7246541f024e3eab7ee18178f61f484e36aa3bf7b7222cdfd89a7c2175e196108df18f31db8b9a11211d14c1101485a559faf972872ef62673b57ea3039308af788f6ea787f4ddb99766d16fbe2038dc4f894a5a01db63210784bb449041c1e6ebd794b0a5f45928d68c0263c36a5370a426d4b7a16135e079f0f1d896cbb0bbfc8df14aad6769f8605e8df521cb918adabb7fd3972a3798833a429975c3dacd3404b354fe7d4138c0a1ded52a95e61e8c34422f35367fe3326855f44d4d62566e5d7afdefc7417baca149e02582e6b603079a6545bfc98c7c1e870a82352e9ac1cba37554983ea329961bb726360e09b18c44daa72aa12ae4e871f55306b761d8374f99ed8acbcd2d510b9320b322c7d87a5936972645a22fcd8cdf74dcdce815690c11cff158977e252a286c0f071b4315ed4bf18f8b1d14abac99cce058f53b197500e0960bb8070ba27a2fe35baf8e5c2e03145f87bab82028abc0b47f5becace02d1b730e15e89d328d731bf6a82742fc73d1ad70f005a61c3db9cbc64be970c903a03be879e769bc710d8a14cda99cf525b519c7824cffdc56ee2ceadd54603b6e2f1b8fe785b4f15d5c45c85360a5466ba72484c1c010289c7536af5fc1ff316427be508614e94c82f0cadfd39e1fbecb372e4b643f767b962494594cf6db68c514388069138ca8b51ddbf14e206d7cb891b4bdf023dd5ef570903ee5a5fa78bca45e25a8542a6c80f62881357c0fb7f1a6a2616325a9a69ea80e0a05faabb236e871522883ea543295c3c154b56f011d872502e6e0eabacd56bb8cf91ca37d4a316d57b8e13374a21134a5803b50467e9716415b427b1c2407490f5c92ce66375cd999b316ed2b300f82c9e9056e407cf50edb0d9283b705538a4d729211c10df73c6f2aca91edb16ea6b68b8765f987d7262661edad155ea0945bbded3ca28c3a044f88b5d240f72456d3bca1e2a86964bd3049cfb009d9b683d4968b65586c2f744627d9bfa904aac17d122f0096411723c881464f222077e8ea18899c981215b299785b69129c63b29d681b4ca673c3456ad99b7b0da7bb0a49b0a19a3f75723599aa3d995a92fc01e61f6e02679d607f076e7f82fdc881ad55b2f82b2e831aa9841a065fd316f2dd4e385c71647b8728dc51282fc673a6d3ff3577e97af311205396b2c8b116f798b4056a2c0919e7251f4564af0382ee0c0cab02d35b5208c49e234c5c94c4540dd14a26155a1aa727a41f8c52a885fbc94becea05f98373699c7f09cdcc38a4073657d63114cab728e9e2c9ed6bd5031f4ab383cb4c20fc7ed7d6feaaed763d4b35970117f3db972c47ce2e4a90125e726fcd3654aca6dc76039bf4a4207afc20f340f65c8cd8f0020480bf1babb04e64a799b48ba0cc2188704d49db5aa01128e907cff105a31021f1586b310e4ca6fdc63672807422d5c353cc36df809e409f266cc88e15b923f18593663d10d18b37c5555a4f280d08d404365e43e4b96883a1d60ae7ebbcd2f317f971d915d5198566805632579bbfb28a1b25f41eadd6ff8fb6ddcbe8bf02a51e158d270bbd291330b22714af08af0a7fe9af03ac507c24bf7773bfc7dfd506ed9f99cfd1abdb4d342fd7350f11cb63e72b0ba416204794bc730d509741272ca1eed2e368bd170c3e5fb34940b570d2c70dd8aad8720067b36df60dcb46d72b02f60822dbd163bb35e448c3ea7e7eaedb6da16f2fdbc72b80677110d6bc91fd3da72858240970ecae07517d563a3106842bfacf2302d332eec5c02b5894307c7d0402abe3570b29a90f22a977924d4b5e38268a1550b8545cc14b2ca17ec7253a4e8282b4d477b37992c4bcc6d12d01cca8eec197f25b5d3f4d1f0b2a4b672dc994edbff9ed02db1979c865ef340f64de48e3b96ae6070d87b19e318f1fb72c0abb2d34f77210259e4a46e11f99cc88cccf5e12965df93914a9a615b1a8472157d022de47fb037bf733b82071df23755a22943443783ec1fd2b5422f59aa1898fdd7cffac3e2e0702213fd470941abb19f898c74766a869585e9c7d5397872e24ac43df96197c08efe2730b14ab20226a99c22056017a835aca9841bf4a069c3d8218a59d58814691e3f5a6fe1fa6613813c6745f6e66317e0972b02058b15ebc680e6d09b4687cef1eedb6c4b10f3ce4b2a755e7220570e33c7970bbdc272ec2233b34ba2c2065bc5507b5b58dcf486abdb417bb958c1da29b7adc8b9e07224b5ab2fedc887391a53bce89ac45b93b187ff4985f2aa1acfb42713e2079f67d723b67f37f49e944ab58f2685021938828da0e65469eb7b9ebcec824b3a5172558ff3949d28b8a217df2c7d58ee8df62bbb01d57e19cc0b5e2b45611f7caf72f6e481955b05453b7c4c268b87a9f01a721868f306538a228bcca1861195987211350edc28f306356dc564b7799a66234b2e9a08383f44e7f134545a44ce7f722645e02c3f738d58fd2f9687fb88fce218635830f3f49be4bdcae6a8288f4972f48c648d53aebc49ecddfae220f39f26098d41c3f3b22080abfb2b1b65fdb7722bf1848cce6c253373f43d324b662a201873fb9c02876f0a3478cfbf4698992baf46a20f5ccd24b7f7f851a705c2c40d5eb29a7ab9768508e1f44feb4528fd7291993cde2d3ce7b37e267ed1d7309a2827f09eed34070949b79abee197252f0d09e544484c6b9c3ccc189c2139e91beefca9dbd44142d6c48b69d706ad4f48661421c08ac5a7657438ad6c85cfa96e82a80bdc7e2e07a882d61d0b2147625c72170c24e66f980e9815ec8024fc08037f63f8167426077e4d42b4a820d93b880f6aeaeaed0d8fe87c399e38b869912bd349d554013baf0ce25af48c250a1eb67275177bb97700eae82b52555bdd4a911d19ca6b248d82e85d9c8abd9057743a7269fa91e915685bd24b0c687ec2c416b04538739d0f521cdb2c12395b7bc68d728c06d587115d18db1edae606a367a41a977dc468ac336fe373bace8ba3734972164e13545ccf632c66e8697f8d2ece7114b69560c1dd999267cf1ab837686172ff1954ac49525c2545172264996c2c9a79e5ba62a1a3cd93e5d9440b5719a9722912bb260ad3b418b8cfc5958a4cfb00385133a4cb4119091de918112d69a972fc678de5f7d983f7ed1582a9395c5442dbc9b2220caef142f89d34c80ba32972239bc186dea7d791d88bbb355ff748758d85514e85093031f6635e9dd831d71f752b8b57612f321cecc1267ca5a508a0e44750a196431dded8473a9d19f4e2726396d21971ae5e93b63a5e9a587f81717977ec4df2c7c2938f6932ce5af55f566fff8bd29ed23ce794b5bc94b853108ad032635fbb08e7c7bd3500f9ef8c8072fba955427fc3a081a28bc034d8e72fe6955cfcd5758012040ccc8589f0e482382b48c7b63966abb5769eea161872835eb590eacd9f927aceda14e5c0e3d14b726e28cfb98c2e3f3edd6e7683ccb3de509f2a08c5669f37b1e622fbfd35c5df726680af597c8022cc10e0289cfcee5def2fde70b892c1fb5c7aedec50f4280d72338fb81c728363dc64707e7e845dc9b585c94f9ff669bf3142edc03498b1b272d2146c5fab06902f30f9885bf81c3f64b6a6ce4a674962d28366d6fe3c7e15549bf694672bd0a5456390e33c1826ee6f25c85f0d9a547cd9a0cb4477b7753f72f3ffc829143d689da517b3bb7d6b8f15ad32a7cc3f96ba2d21b610cee891ee211e7f968816b6e6dbca2e43223dfb859c1243d110eb361713afa9d25b742691723be4cc2488430516c47e6435e8cd3bed3a1b9acba7406809c71d80b72ef5da02c0b6ee5abf00e448eef9a2906fc213285f3f5d80b7b69b4c95d5456b8904d023e79a020af74000bcda64d76d7d9360a1c20ac8e5d7aece85088d0efd828cdd728b399321319c74da6c65b941530f6427da7b0a6c5c0685019224fda3c9a926721194d62dd2d5833dd85dcd87740397bff1daadcf94c864a1e5eb3f22dece10508e92acac1b59b415d1c19ec04e5022c900830bead398fece7ed06921e6bac47297f86618ec150bd847fa0330c53ca60fa33f9995f028029be4c351763648627292939c97712412f0cfa0a46a566193867b53f6c1525d890fabba206bb829ee721b5bc3cb5730cc55ab7800723b815c3ce2ed3cf717f5678a91256acd8d0d4605d289a08b238f17b28966bacac5e684f02523d65a321ec1ece19a8e6319aa9972086ebedcf16d6463f352fcfd4dc1a2e5af4181f8c6e812042223edb879cc9d3b4a42b7daa30e98f54949ac84314dc4d5b24eba157e52102ab96de27b447c097246700024ea461a9109da89d78a590194cba33896db359f84814650ca5d1ffb13303c5259514cbc6f3bdd899d09f4cc54bf81d23293dd4c9c8a1438bee2603f720e26a94fc7fddc0bb6e63593024c1739ace166721e5a260fb7c4dfd6ed51061c7f811d089ef0a1f55219a930515521e8e8ea533580a618913677fa6c188df0662bde8832e32e61c0263f7893dac34ca60a7a0eb3b21785146f513b99f55df9524a7f8498ec9aac4ced29c713efe26c0ca1d64155d102b28afb268bfc17f9367264cffceaaa172373f2cc1d52a1f3027ba44a750849e41f394d9f2124f3e527722a7db63577f7bddda7b74b313112ac14a2c7109d7cac71cb773fdb00e86b21727bca41e77f67caff06f9f283f0dd3bde9f84d0f992262276b0b8b4a317170372bfad6ff224e720dbbbdf0640a6a6466fd99aed6cd4a269b0fd1b1830af2b1e727c36e72a312e255bde42d92bdc5176c124a80f70f3ce407bbe46fe6992109672845c5cbb64b08b6903640a79858ce960a9608329e4a5ae357c63de7cda6f7d52f9a16131d47fe65f79929bc46ea0a590a1ba01f19588db1dffe47691b1a8ac720ad120fc4172209c4d57cdb777cec0d9c32e1c0b39fc604e07d6309945548372b7bcf2e0948eea8cd6fbbcdc9559f1b964fe67f73dcb43ad853b9dcab62b861e58e6da2c94c2842774cb6f2b209f401844bb118f23122cbe80c0aba1308c6772f9fd4979846c2f1ba078d92fb9d53de70612a97449417b6340d35868b708d572049ed0d8c741b33fe52ad5c680b4d06e88fe2319e1cfb4fab1d656ab33d847727ba57f5c3bfec41244755c24b76e410459d8fd51e1d8a430e00f22ac5c0ff030b5c03c9ecc64c90ad9ae9865d8f4d39896786f68de352ebe304292fb3c5db27237bf2a692b44604fd91229e5c26c71d457ed98795c96f0b6ac51d7a59deea1527141dc9056e4de234d127c2e32e2c0ffa001764e80aac567b562e7a7bd964072ea3129230de95c320bfaf4bddc3f70be76ce42c2ee6cdceb6f38218a4e31f4393b6efeed662b13fe74cd6f01cccb59a9d6c499220c7c7935e9142900683ae520d9c0da532b62b8db12239ed05e74a92114cf4a218255e6f2223395bc03272501f7538ed5a38833328072a2a62911db7a8cf408f4c5c8c2dfb6ebf82ae5fb885d251ceae0bd700ef668bbf85076b759d4b75de8564c632e9689bd7f288bba6672dcac37b6af483a43e2269cca9b126a47399db2b9cb946752142228d4deaa7e040729ed4dd3f282c9c1b386adab2ce54c97b514d136f41f0c5a30255fe63ee6145e00ad2a72abd79cd92fb15ae583c66eaafea5f90790872c4166687fa042a272e6830e44fbac2518c83eccfa1502f448d98fca0cfb1a0fe184fde38950c19c725496d00bf7341598ac7086dcdb56976079b7989188e387b026cc411765eaaf72a726d4188e54dd5594d0d395d97de25d134d801b8aed7f217138a8bbc5c84a728e85c58a9eeaacc892cce0bc64ac5d1eae1f4a1e54b65ef7eadd36775a7999727bfb8b8d64f584a3deeda4dbb19afc23adc8a46fb65d45ea452d7303d3cdbd090efecfeaf2b3e52a62f4134da44f84a8e411b92ca5cfb7d9f3c29de6759359684ec6cef28bd5f73373060989d8aeb291b147911a5b30f74107be39a64412ea043fd9a810e2fbc0d78ef447c62e1ba26c6a115089fbddbde01cd8e9f2425d5a10d8ecaf27b3bf7fe0f00395d1c6688ace1d046a0e48d987843f74d10efca5577286aadbbe1e0528116833e7c01d58886c2e663e3d24e06143764e6dd9799525723cdce6a0f1688e389cad65f45b8fd7bf142ce83d0dfa7c5634dee0c68571fe724965d95a5d9b8f280183ac40f2ebff8749e8a8a2102063124446f719ec0bb55ac7a321d1b658598cd3c8713064a1ff1b4c1abb3f2654ac0949ecec2fee4cda7274d3694e6abeaaf41a1ccd5d0294231a67dd178a9de68ef144f040ab6cb25372acd089e5a64e5a22753b2d711c56381d24dcc87b28b310699e220e664e341622de2d30a86152793cdd7e8d2ad9a1dbd5aef5123f905bdae17571c10918459f7212701c85bddb1d10a47a22e89b9e9614a69032964943ceac6b0e5353a3cae0061ee99b84f8141be7c1fd369ff5ff302bbe615ca33cac8f31a485e011dbf8790cfc82848b6d9f7f1822d6abfe9f1700d3ed47580d1cf27aa7192bd91635222572bfe0904821f621231776b1dc5f0fc8dd7474dba7807eff0dc58d9a64a2edf772b016bd6bd97823df5080462b77c429c5b42459bdf84aa26806be0e6350cdaa4b01dbd5210e1132d837588bbea0ec9d9e05bc7985cebcf6e070dbb2cef96c627252c44690198ac424aa4d1bf803f2cd01e59e2141ad531dc56418ed0c407ed072e0163a3ea17765d2f2e7efac3ad6ea1a9bd156f9b8d45bfb96f58a233d5eae391fd8d1ebb1127394b9a1fc095f6568edf4fdeaa7a02456738ff9c926cdce0f72fcb12464ef0175675d5ded3a22d97a7d7dcb872c383e97b7bc9a6f8676856672f9f3f68d0c435e9a6fdcfdf84a5fc48eada6e168979a7ef0cabd1bd3b2d1ea7226a6d7b5cb13b9482e5cdc7b88143c93951763a549c85df2444ab4208db1f6061dac3fc4240e1397146bcd036a12dcc6805986bcd1e7a67616a0b73aec30ea727f2e5ce997164da00eb29e2bb57ee20bc1944d210a928251b6dfb487892f7b722ff73ec91813bd14dd58c79a60c99a98c408ac0235396a73d7b351332a2505721bdad9d94c8fc1a0bd8ac0b4ca1aa8b96268ca2c7242ecbcad3c09e383e0742e467bccdaa706313056a6db4745de5ba9df80b63a2ad348f490dfe2046eea48721a26d117251e49e24673d62ff5c5396f34a04d236c5b8f6ba687810ed9384172ce02990e346b0c40a03c7bb8578e248b8d746012e4119db45745713b094134728ae979edccc5fb08f789d432fe01203ff20b9aee875d6dd5fdf6fec30698357287af5fbdb0972cf937f0628e25f0e058cbc52236a78ee99563caf42331090a728d9791a8dfadb46f5ea75ebc56505a830c4cacda7a586cb2bdc5f63eb5d200727e3666ce84797c4236579565fd260193c3b733f1e8d064ef65e0004ee0d0646a22aabda412c42a930293edb41749713076bac5c7fe4d1d6d6aa74445ff593d70154c51442b293995d013904fabebe9221bdfe1dd38f19406611336ac4ac6af724a70c9041732b7c7c9699dfe040777ee59eeeffd242d7fd0fb1126d56eefac212ac192b9678fcfc0ced21d46341ffb8bd521c1094f3aef0e93d1f7199afbab729e81b84a74208855c304fa42ada89c8e5f21169cadb65babf6cef81d2ac1e172ceaaa8d8f4425629ff0afac44a095b80f1aa0e258f7c8b5dd49eb5f8bb55b5721a4cec0a3d31dca4d2d3e1797dca52f9ea11722de450a9c107185f75f90eab23de1f686463b8b51c9776b784a61b97e9c9d5057c6c281a0be3a5a69775b98c4e1ad21ed07c33d51244da02eb3a366826ae83faed8eb9d81eb3fd8385ea53e572a5283c80c7e3f59ea43f46eb510ef2cb99256a8cd91795d4cb24876cbdaa582dd84ca73ee16821490338be4fb297f7c5938c4384eec3b153f6fdc3faaa125d728f1719725d96fb7bd3ef21a755da4e883d6f4ea45b073c2a8e906b869c903a7261a6756e8b1904022e41b774cbf55d8a891cd58d9887bc6c1fc4df4e92c642725102346da4eef1919ff6afacf801040c64b95654badbea2b3ee0e6e452afbf725781e9178ba0b78a29c239dfbc81bd1599ad6d442cca04974fd31317ee97fc644b93b8d466e85ae28a20f841f3b394179b801f7be0e51d0ba1c2f4f892e9d609d25952a9567a6df448bf9cda1354107bfdbd09413f9cb17823f3fe02ab3d0756f27eeb834743227e0c008f5a753c534925f99ab4bab5fa0a27f7baa2662ced679a5f8d72a34cdfd81bedd6a7aa3e9b31338047f6343f0b158425429330c75a40181ab7fdee07142aae613efaa1f4024687e2916e0722dc7f731a247620d9a172d5763f73cf6f1691a4fc78a701d1437727309b20cf9a0f894980b49e0de166725adef2b9a2c6158978459a3db8368784327ec6c19dbede28adab0062856e195be18a22982e215c7e54f070d584fca36d2dfbbd57d199e883bedf5ccd9e6cac72b278e59009d3eabd8277f827fa178cc363c7ca253174382061d4e97466797b72e7df56dfd2b6c87f99b4e2221eaf525c5204ba5b3ae45f3be40af1638f6a4d720992afbf56df3fc0114dbd063e344dfd237db070d486ad0ffb9343072567417295e0165e92ad99533f120767e4d857a0474d61a306eb02fc48169be7fcc9fc5e46fdc6088c804df3e20a6a9a2472e48c92c5c933e6be3f64cdbcafcd83aeba7230b1f93e85f591ff7a770e8b83dec93d3a5684850252e9829d7181cc00d59872201a1e51d994777e4ac26dec8c22e2a88d05b31bed5a8721e6fa2959e51fb835608f9cccad609b5d488b00282cd09eeca69d8d04ad0b632d8bb5f58479fa552cdec1d8a6009a2acb14ecce9fb200143f29fbd4aa909625c800476442d67a9272d1997096fb237e17c4f362b3240012aac6bdb0b881a64bd1347480df60e2f946fad4e558b64c2aa05ead3418af639caaede73069d0533cecf901595715f5f328e9e4cb9ae3991580008b7d84ebf3200db89b089d635d4ef0990adcd710a4c6722e8b86d01d753f5e3206f8dc884194688d23d781553878046efbff955c3de84822f95d18e5aa9a66e0efda8252f16a0455d983fdff582a416926a231e87ecf364fc6e37a8f2fbc6c6f710e987aed094dcdeff38a6d2daf5763b51f8666f9b472c5e1c8dd951c46d622d9104cc7e2f6f172e68a1489f63ada25ccc261442cb2728022b0c2a832f048f43b52e7d9a95c3e83ebaae4b765b5513b8944f01c1b9a10cc9fcc593bf13cd550cb3652cd78f8ec8ec895024ca0527c44bf72136592e65d06034ba1919d3967890e207169a28bbc97f468c91d07121815c74f37d2f4a8722e05b40fb94fa869350065bb4c790d90873c2cbfddcd98cd513abf8a1d092672e7f483a7d4d99d15a7e39b23f58a5470f091846ff864103d6b08d1bcccadeb72234165acd7d8ae238f43d378ab19ebbfc1822c6e8dbc529c82db8c20126f6d7237fed565a67337b7645dee4a761400d93b68ac2c2cf4417efbabe8c16467921887e111cd569e35324a6a6f8b90935e673dd193078e81efcac9516d6ad77cc772ef69179baa773a04bd0a942125c60f6ca0017e279a60196c4d18940138c3ff20f3c6c7d9498fb9c21b3684312771bb29029a026d723376f109ff3a2d3b312d5850fde023b242acbee143c3543e3c699b748e635d3b59247fcf0ec465be30db7210e7e2d0832f45fe15e212a3225870027d5af470c65559ae5f213825b5769772a1587c2a0bfb7e0d85ac476e42ee002e5136e00c8108da9a516daf0154bfe85db27927f39232570445417c2354859367931f82411b8864fd0703e0549a40ba2e8a24e4d51bf4f6dc76a3c1a723818c2f67c2513a7b7b0c1d68536c823b9df113f45f3422326db5c1adf293e21121469026562bcd8dfdee2560eb8efa68973c574638c0b2d7eca65f938ab75cd94bbed2f6d29de8cf15decb52c78e9a7724c972aeea03d8116811cafa8674a944e0b026f4d68ef27ab449fa81f0fe75aade05723377147465e251de7fda62fcc3ca3626e4ad6c916fab227e4d607d7371e69f0ac2a08befd1879f2b99f644a72a576ad7ff957ac401bb61578570b60abe71fa72f406661a0e244b87474fa141f62b4a8770727d598466b0b4c9a73b6f661a1d72cc49d4643a5526a652afba66aa72cf7df8abfef8b8c38166920b1a42b9555358df3a02ebaeb81d30c5d930a0736f95429b4fc37a3a3ef3f614a60c3c90f9867208d7fd5878ff5028f2aa60d562552671ee28e5d469ff83666ca7d7af544bcb723f676fa385804dc9c0f5d098727642afc380feddbe9d48ffe61961e7f993a97251135e169a4c57edb495e25a345bb51564757de24d66144b4925a4a25351df7227de5c4c0c08186a155dcb8ead2d462b35e3286811995465c787516a432b6642c4a624d2bea3fccd98ee914c265fc74c1c80983f3fe465c90e4acfafd02b19721d5ef450987751380a40cdcea80e715d77df39bbfac0ae033e8a79df0a12d148230b0510f311440880f20ff44ca011757783b91fa41dd6e6c6f0cdf46b36c77227167fb0f67bca4ce3e5586d02de272a4cc2d64b232b7a7fbe20ff078bba8b4aae2d8b1c596a96ac9f521445b35a125b48cc2e1ed359796a9107e0715e2a8850574a832448804675d1e80cd1f2ac1dfa3fa4b673b021d427638049e12ff87772eac564957023b061804f3bf314537208d452459d8256abec6f9553e106319072d48885f43ede3e6cedc38dcd1a812e88864e54b98573d8e2dcadd8490a6d9e72b6572e87c123dca59efeaef1c41ffd1b221b43223468c56c76c7b130037a530c4533b8e4258d344c184d8fc3d144a49a65ea42be952b02bab2dc3c1f16eda6726ddf14b00fa0b0c5c63fbc1163e7930d5b5b258323037013a74ccb9d74f779728eab5e613cd22a77ee04dc78311f782ece83cff49b0ad7943a08661e40bb4420e3bacf912fd23db9989c2da28daaee5b342e5576a9efee9f6709a1d1071388502792d06ed3a35aacb28637e378b96722292bdb10faeef4ce4355beae66f667721ff3b0375f79c569d4707456bbac85ba9639b31cf54652d31dad14441488b472b73769fa2cdef130910888a069597c713645b44fa7921dad09c630bc4449e90cd8781e734d8f157ca021762d0b128e69f8d32516f4f7eb250a39fee6302ebd72a37a44cb37057aa26e6e24d2bbd77032e17256fe4a18475e6e5b600df5aac7727d158aa76554044e7c273c8c2a51cc1c08b21273278ffb56cbb4963c65da0f0e22fe97d5a281ffe3c306721a5f61d182cd70e1c2716d11779f36ae67b1262c3e8bba23d0ab12ad36714142c46b8b352129c4432ce74cd0870877fd89b215394c0d869195eba0c79b3a244af61791f72039aa9bd43da4f01372876143e54edb48f297223648abe578ff44810d6fa288d9e4b9dd5adea1a75c21ae48d53740f272740bb5fc63beec1ad9fe5df81977fa4bf899983fc72ce2bce0af03f53fbf8b72b249781daf0f617c936c2e78054ea21511b597f286ba943ca8161e918995ee72af7da2efb2e9957643af1ab1afa1feaff972497dd45a389a816c9e209400d372cf53189b513bebc080a25077bbeb622d485f17dc70b5ca9e41d06c59e5bf302235a411a4a869cc4b999e5fe002e33a72dd0b2eea965703dcd4ec91d5d194b1493d823218a4d3bacc4795b71b12bbfd6c361df30245e2bac15d72c9a7e2e4747202e9761bc7040822b9cf19aae56cfcefe77de77158462f2b9149e4afe30b571ec1cf060815f0c211e31268dc6a71e462d430f56718104d8c850444247edeb213698f28ccbb3c928ff879c495c03cfabc49c54f05cf899cc85d8ff84a38ec35446b58b8428bdcb0151d638af49924b66b27443f0a99d56bc57b6a9529893570721ece3805641d1d1397f23cc3fd6cf9b39a70c5627c023c77b967088709371471365128233dc68498205d843ec2a6be416d64764cd0e9a6350afe1cecbd5270727c2b026d68061edfe8b9908a55e0b5bc9a3e4ec25eb702fbd0a63f61fb0d5d064bcb118114f28adc419cf629fd9ac1e3cfa0896261137eda219617af464a20729d20415801e5e7c1adf8a86167fa99654017ae8e8dec36e650bfded74557ef7258a0e670cb7847c61324981f1e13f8ca1c6c4c57a312e2735ffb2a1f26935a54ac22f12a607d6c49940b99e45c9349159fd6ff979bc11c95bd486ad98459ad7244ae626f186a062225d26d5c89f7480bec2fa3df0d2308381605088d25b0f74aae711e7ec1beb793cab89b6a810424ce83e0db83b0956dc5f6d7329bda2c720f9ba29ae554f02b3af70968c7ebaa369044fab187d8c9f50090cd55fcf2f6c646dd14dac003025130a8707517d8f15ba674e862ab415094e64374968399ce0c7201c54e81df5d40dfea48ce678457a361caef13f325a1031ddc7d0fc23c509872189ba7aaa49b3deba4c466da7148bf2dafcda8fe7f1d72cab243645cb7ff5f725cc7408f4e9b07f57ef93f13372dc7a56788f2c25a39aabd4ba247d36cb8f8728b1c2bd231d988560d76741de1acf28e0b2729495439df3dc5af5e9554ee997299e784f755c8e26cff46040a4d9adff977e36f2b32dad293ef9809f43ceca3721f54663b2b95802969f5d06b038282b678400a1e8479b741ec5e25194e4cd3443e09bbe3eee3d9bee6c5028442e57d8bfa334e5f26dd370cb6be0f5b28eccf72bafbc6e40a95569093cc2ba62573417c74c3bf4a3c36f6253cc5aa183a897a5e13841b1b1e9d2141d66ee86b7850bbe9ab1b3f435300fb75b4c5ba821366d57220d73870d35e3e439aaa59c638a6733c592632daafff655a64a2d0993e288c2d5ba5914441898658f18aa40846b5a0159ee468bc5af43e35003e11da1d40e76abd863ca5e249d794c9ca99ca1aeeb390dd0fcf7e42a6999ff5277e79e2a4d372bc99bd1c7b063924734ba9b9e220454d53c6f2d643253fa59473c0940ee6c23f95f812b857d07f0f7b77e1192ca56f44e8e91123c33e53d05315d9a1b3524a7280a8d62d579c0a988d4b9ef5cc85985d664fbbe8450f3af0e6e440cbf1f92872bd64c17b71dca8a961154c5e543d48b90a14fa5806cb86a8fc5b3818302637728cd1db170d306e11a3e36c5686d05960b0ac711b707d2f2d40893431b967ad72df41859b9fdbf625c1c76bda1dbf8f661a585a0a83510bda56172658fa99e972a6ede4bc2f8f62daca92285fdc86f10af015ee8657c6d4ccdeb2e4a8ec23217221d5f278720e39376a557ab470ba8ddc3a7ecf3eb97f76832a5aa82932e5cb72beb17e34ef155db550e20251fb73f2249009949b191ffb5f683afc0ee6ddeb7267bedbb17929d1594c34858c08827de61abdfa11181111cbfa9d01698b44561168d64ac1a868df30e02740c007f2e73b26361409c0c8f061bc7b8339b571d4723efefa04e0f990772fd4f4b9edb602eb4b0498711d82b6b8700915cfe7884132dc5a1ee0622221b1ce2bb3dd84961f5aa5296e48d76e31d35a0d8fb8121ffe724b61c55e2c6f3f93d90dadf35524147ede8f64e8ed2202793b475db111f301726f7f984ea3738aef8da6ba707b76773329dd69f5a7de5207abccf2313dfad70905e586e69190868c5c44cbdb8ad9c0249cea94667820ba02a25d83367136b772b9d25c19eb35b1a1ed1ffdbbd15e010c8d168e1e8a95f2c3684db62617dd40726b378fc44e3f09648391ad7d3a9c9d03bc7ae84c72c95b94013299110ff93865dc17cf26936649e36e78d4c341ce1b2f7250aeff9a24f3b3b0a0a27e19bfe7727a2c8c743a1885c4fcdecd4aff5f17e961943074f2916a8916d1b390d39c2872f755b1bacc29c8ee122d576c6d51769ef6e39776357609b2fba272554fe9f77282dafd643a1aa077fe6dfaf2325b75ad56982902d76eeab177eeb45932d14b72772eca30c43891f6e60552e9b2d4ce30128b779c75d20dd2d5379b17e1526846b6b9cf97bd58eed8103565c432b0b6b0f21a799d586aa83ab2e54574428e3472599d1a240fd72d541357b970e067d555f568e3f0f06a7aa2fc3a14909d0146726206e92932e94bc05e725e4e26e7aa453454159dfc6bec1aac89c25eced4b629766191b7ae325d8f4233a905d6d5aeaa42b5aac3d5a1353c3a1073221327fd148e960a5528d0e7c06259a05db3e2154665446812ab3fcc1be12a099d9422697297480e6214bac72418ddc39b983c7272537816b4f16b47276414bd0b92254172a642ca405ffcb4ec2fc29253b4e889b4ec17c68387948e87ef9c0b89612c687295b5eb831d998c17065f79345ea442098f26706d5bede080680e04483a6da6631d110e0cefffe5ae3f30df2bbd0ed363977f73d6824c9a876a1662804d05f872c3d13c7aae376980c88344cae8e1f2c460f8583ec85a749f3f41b74a3fb29321640150de5fc004fb3f3426f181113460a902346fb9710e0f05c78badbe31296494f8ca34d41f28d714f944019caa74714473c8a770f45300bccb45c9bc95157211fd87150213eae51cd42ebd7cef3a07475b70d84cdabb2d2877604270498c72cf30c822f5932075d0b496d34f34a892dccde0024df9440656e9e80f71d54272d5d697fd7663308f1b1f1389ddf2de8865130e6205c47e51824502ec7e33cc728c8f8662e36af62ab274836b80e363da7532318d8a3334711a716fa745a84528962cd7f2144a26dc7374420d3145cec4ba23c2dc74870bd9f460d23500832d7209305f790ff9ffb4049d49d94b34e112fa3133fa1650523743fc80940163ec03878d53ddbd0f09c2e60ba80a74fbd9ac6a07660ed6fcf232ea0f4c4d4d6c2e3151970cfa49e8328e98d2602799941d39d2ef79e2597e570b24e8e905fa54ad393261cc249cebfac384662db5e54616e41a9a30e8224148ffeb03dc2319a0443411cfb94bf5008e432ffc2621e29ab4f1cf43889c14a7f678a9f590ef731c0f72875fa0769acdf11b609a183f76e8326a620caeeccf4fcf9330f5bd5931e95e6201b24907a9e1f0dc7a94b723c89500b06e8a2f95e7fcc2a6c75855c37cbaad3c925c76ba13921468b3dbdfb82130c044294fdc0366f8d11c3a13c189410a1472ef4c10b95d9fb014f35f7fc17c02d612a58d0075ff0a41bce932d5d7de966972508fccd26f6744131bb96e709c24bc15950896d6de605c40e312104989bd0972749faad464009f759409028ca694db5304f2ffded496ad04da90504288ed2e7290a6af7db4768c95fb0a5cbf1de0527fe073812df037a0749cdb2ed1c6fddf72df71b3c7802265fcf402d1e4a360c090696c8b0c80eb143493b9e6e9b85518573a099aa279ef3759bc812099c35f749545ebe779109f7547a6d72e1f1c5a7b72a3128033f27a1a2e16e7886522df18e7d4ba0a7c71fc9fa280ddfae6e70f8c726d61546e74453f1d2cc19799fe9d6b4d8bba3088ca92d43a9ecb527faad89a2ee201ea2ba5c181b2a42abb75115f4da9eea0a4a7c0c61e32e62efa19b0556f57009639dc8ac44612b7bccb457912ca9bce0e1e3a3db93608044f6972ad15865e8a484da2430d4ec989c6cbff44c50bf18eb5df51e2da9ef9f276903cfed0951bf09210b3e3985c3c18b1d5fa7acb8888c0709a1c27194244ed1edd8ab572a2721369f4e319976f1dc36478523ee450c4ff0314fad7381d48fc81010161829e72c945cbca9e140301c0040880d2d02eed1f3939fb0593898310c35fac9c7af4723625204f7711f38a0215039d8441d749dd0fe96494cabc43811b477b2bd43e72cede82de4323c8651665be02e0c73d004163298f7a88b5ee6a77f8024e9e3372b90b761f9ecb82de208ef9a0dfc29ccf7e57ebd2043a0ef9b18a4a0e8d439672934da63c9cedd21502af6fe150855012c9362e9b5fafb7b1a401c15bbdeaaf2017f9f2fd53bccd03a89872f1fc59dd296d29ac70a44718778885d26d62fd472511c3d2a3f15a8fc58249d8a560608f14e48cf3afdf0994ad1a40afba16db66179cc6bee2057b36679f83067dec509bb0a15e609b2331025c9935c2399f844072c82defdb936eeb7bc5375628d6a612f4520f05786c18bf6cad414ca00e45435c2cbcf899cff43dfb8e08789853167e474c85d6e079c39746ebb27ec92ab53d72403637a0292a44f81a1d47f1b9d7f31d4d09d7a7dcaf0f39c0172b412bd7b772cd0fc00c86f942b83cca15a46f55649d12d145409f3e598064ee75002cc8a472ca15d49d3541bb72085d044e3668e08c5ec55ce1f54b49c06efa6191ec1ab5721daa0aa1fee10e0264b6c28cfd12db51d73e1226243bdffe0faa7d9c9dc7f76919f05f19316ef7a8105e0589713891d30d4b92075132d7aba57cb289e2514a72caa52e6e15a52cb1be26d85c5a7da76a2f160ad030578b3871da93857f61f772da6a2bd95ac98d6c0e0506e71e7384c5f1dc997e2e756f2d00aa54093c8dde7293e82cf8897534a716a2a44acf117c3ec4db34212f2d3b4628d394a92a927572bd1c9c920f67c8333c20a8c47306000331a9eda16ec2eb9d3328ade92a10a9720e02044b81e09441ed142055dfa4a828b6aefee305028951b90be912e4a2a37258f16620ca0f378198df2096ca2f9b954c2265c16bac025670b89f915d47da728c8f3015c9b198f3bbfc8b16df41e5e02c6b8387ed7b836bb82b6fc8baf5d372257909042be8ba47ab5108b356791173df8a50f1337fc9225d20f1694474e02904f8f7752c16fdd65936e5493cfac9d57d8838e46788205f6a659a9ca8e80425dd86b6546bbfaef7c6324b20b98288421af177c1db5b8aedd5f076356f1790726af55b340f372aa4186cf4585a02f63e01c677501511b5f82ef2b7815fa68972c7995c07c8f518c3b0c59853e6804e1777bcf6a49f25645b9e54cdec971a90724ff8d0e7d07f3a7e93031119e30fbc902b7459f498216169e392789e91f7292a3492a615fd9e35709bbc1d230a6f5c5fa782dc5348dfb7c2b6033ecb70cd72543cada6f94caccd08fcf9303e6c2824e9687fc9c72cc953c100e3207a10068372beac84bf72a3dd66266caf8434a848b349dcc08dbc5f83872e1776feb28169725e839f1271b3130f5a0eb209b2412cc656facb241ee8ccd21cdd3958f6f597727b2e877baa0f921edd651ad5e8e6ad600dbb97ea1fa3d8217ba2956b51c0e672cfa10fe772785122b96a2c718eafa1c61a0741cbd743b2c4e0d2e46e2b5a7f1301b8a7b3b8f9db26e0b69554115237d8db283af97e4a8ea86b1e71771ca0994bf9429d6c0f1c02803667a36cef56a2de6518ad345669350ad4b0939f3242b9727d94a5935e8a656f9fcdf853583bddded3f2cea76cf7474eb3bdaa1f71ae13229fdf33bc06a865725b87f9b9f93d2369b39e15a2b13da2275742f97c3f894b72d828717a79123942a90c8ee302040a84d9aadf7a35c53990abb0b48e9c7ec472341c6ce15d5009502da52d5b39252e753169f7d049582c2a8fb7a80a02b4dd62f7652645c8b3905e20f7adab42ea558c7f3e1c6b2cc0d704bf25dd0c3db20c1f843da7ee311f2a1f2400962967125bbf2700615725ea73a697a82a6bbff44872c1bb4f8ddf09f8703bca697254e22c661533992538b69096bca70f341f51b9724b96380dda15f8302d5f66b8db603ae86504002d67111e15721879a89c05bd72c00434f6d75d200cd9d3f1ab7e5b2d304586a21943d3a68f393c9b0a69cf3672e3ff0e7044f526ddf79d4f7da315bac87954fb23d61361e91415b817df206e725dd42f976b19459f05ee729a3d26a8374c8999c41aafb8ce3f779467838bc8727719c64ea7e4fb2f2c0341452432162df04d0ed58504057876da12d5208e5d20db53109e854e4850e95e63a97c883227b448629e4b759c05455602328cdfba722c1e5fab4d41251a01de31a3cb4aa2568d3416f3028800189505e7515021417295676e8404c94bb666f1f3e07019a2d4c227deb6334c5928caca850f5fafcd6588fe76eb48bfb958b837dac285fbe28a0ca363a3bdcb943ddd0760bb8258b2670aa637121baa31376a2ab4581db113f68e9079f9dfb755e81eb4be8f251e4d72b95918ae7f6159cebb39ae169448d71ccfba4472a2535432fe4a48767ef6ab7215421b909fb856b773aa9192f0f0545ee036a549646127eafdec6abf110b2172360d2930d21a6ff495a5dc98e21da4b6e514acfdc587dc2e7d61e20f7241164cd02733ead78a9c0dc3abb9f030be90fd94bec151591a1cc14a81f6cc36af5b724122afba1b6762f5a9ad3616811000ac926eb7fff595d5056e1bd8b9a0e1ad115bab0ad3db8137deceae1d006cad5067a34a82955a7f5a8b7bb392d4f572575f6f8afe7b24d002d21422a0aed11598f89ad6413b2a5ff83bcab072dea0876110ef88befc48e05ef229455ac2865e053bb1e33135610a92a1077276603f3fc00f7b93e0896cc3f1eb3dd6d16198585e4d5e549788dfe53eba2ced3ce76f683572c12d0e9d39fef2906c2da6e10bde3ea80de22c246a654cb2d3d33c577be1b4725c29d91bcfe50c1292a70fb3a562af0293e6af766fafb7a66e954b8a9e584472e712b2e6efc3b458ea3fdae2c0e3b908eeeec423d328bd2babe41b57e6a7c824e0300c4ff6ed5e0585fec002af3cfb5a6087023f03bc65a66b48d930558a3172c2e964587997f63b011b4c86496b8f15334e64517dc9aa7be9c3973e5a42550c57c2080681dd9aec5c67d548fb96f4d60b59df31b1db1f6eb7c61675cc36884896ff22c5f05c08370cd4fd13ab8b12a1429064cb5e67c0f46e85b4740e25fb314be026af12bcbe847c8ff03ab03b5ec0f997344a2d9b92c5e73be22076728f634282851ff1b95bcdc9a3abd90d7fbe7edcb211ccc8c6e20898d14838b4b363727f00949ae77a5abc8abcfa5641d21d061d7b2fc621c035ddbc8fde5fccf6b772e3b0334ddc620a4b429ee1a72ef7ec10bd52fdd6d6da1e1c7036c8fade6e0172e389a3b26b14119519f6aef0e8db4b458f2164ad14922f00217d81ec017581727c118309135d148056533a4c35357dd08a661a06ea2e441198ea0b526528d767ee853a12bbcb74083ec960219dae831a27d01f032770e2eb933e8145c6202c0b76529ba5243ccdf8df70ab6ccf4ee934506562d01cbf265231e4c67cf351b346ea248d8d2ae7815c5aa490843b90aeda40d6f1872d525852e579c54dd8bf24729a25aca738d0ea0f0f4f65fdc466ade7aa43d6cad0abc31e11b86036517525725e768672bb29257fd572f15e260b8ed667dd9a0b9569148210ad9956342310720523b2418214624616cf3e8ec9d5d9ec7f0686c78b12057fc304c7d7c27da56c2185224d6af8fed6a6cd9825db939cc12ff34f04d6c7a97a9c363de43138ef72ed347ef0987bb3542d59f44a09f6d110357f052e051d5f5c4882883b696b1935e045c7765208d4d118370b38666e9923af847ce0d30d09369d1e6b56a9330472348b0773d5dcfa22106a6096844f90069ca887ab596d3914f10608815071fa43262f117d4354edff9108c4a2bcc84ea55ba6ffb359e9dfb4d63847a02053801812f92f89c6a5af46b687ac3ca28e37530bdc212a3615f548641fcb477a285b33f256857e373e0685a60df838daf24fbd7a62f9eed2d6c260cd1239ec65ef1e18bef6b75a638e1343733785553ea8aa711b5fad952b0b8adae9e1be7129fd1172c8b9314e3155dc10cb970f35e3d47cddb5256589369ac2b52792cc78944fa316a684bd2f3b16debf3ef4cbae7fcdba5e85cce6bf138cb6dc51c82cafa0de257206b3a58cf7278fa376fe48d87b6c115f6d105924fd961a946617908896eba352a646b50e325d3c31f2e23d5bed3a2c5e5319b0ee165782e543d34759222b8872951e5775cfcd7418c476ae7054af9a1950332d6f3a803fdff975aa24fe3a42352d7a2401ea79f42aca9dedcfadc06123d4597e909e57aa11a650559d43d7df25a41c20ec8fbd9a40b13ad80a4d27e183d7fee3c5720f21b8e88a9fc52a9fa060759915cb4ac428c562e17d0c4a424207704864a4f069487d2a2b54aee9c8e67212e6cb4cf809ae74f17130a5dad6d92259bea538e2395d6417cc7a22257f6b5dd09bccf3c3cd211efd4a577985181b33f2f02267d6ea474bdfae7d7ec35b5d727463d9bff8f9973c1566344bf3413bb0ef7974f6d663cf9e795a5006837c9b08923fe97545649d4b263a4d07246953c0dd1ca9b6635e39cc6904f64036225f2b2142b23b68ac67f9c1c074c11d8f823e69fa65d2473b2a5a0b15908f053a7d2a375d132771eaab23356f4790049afdea2c1658786d10a795a10d4cc90f0f13667c2df0e2ed698adb6d55a605f8a02c88d08995f98032614b62cf152da3a44d00a1948902b7765f168c45e2efb637b1ce0279d412d487561774f260d6e53dbb72a0c443758c94727118d02e0391e3ff8866b0a04faf4c09cad4263552d3c7c472bdc9f17401a29c5255d51e39e9475c7cdd894341843bd85a2ec183044a0383723ce16a2a747aa20c332342aaabc8f0ff94771f89ce51996ad4ac30d190619c72581f7c8b70d7e808449d32a0ad1cb33a550a49be2c8027cf06d483d7ab886b722aa23932b76466f27991caf9929e30f804262764d004d85e3d12f196090dc77276e61735d44ae02ed25139ece4720ec7de2384a0f7275913e1fac3b1fca92d109df1c42759c5622158f37154f48da543cc5e6051af95f1aa4d7faba529192b11a29727b9ab468aba03cc2a66f297b0d36893d666abd394a61a97f1752db899720bebbcecf8c449bc8351dadfce433ca519ec6490dcad75e2a77f1de9c4598019acf4b9611755025d64a12c4fda95ef7ad1aec64f821767238c529f5614f43246ef74dad53f50c86bc7b86e8a8967c8dc1f95b556b802c5ff4a569d628967347211cfd98e3b3484579f678dcf5edd27f07ac1511a7d081e42c0965faf44ea5a72df86231444dfd38abfd4d013ce0a567979e32fdbd1d8e9de65e1fcdc6077f77218f0d8e891d5cd3afcd5c029020d1667cbdcd98d71340396c7ed1a253b012e721727f4e8dd5c325839ee480373846b7d97699f180629c6f49b12164e3def04729c92f6108dea62a146a634f001e18a681345b60d9899943ac92ca0fa92ddec7276bfe029729b4e2b27685ba8ff3548d6a192266292ca4e6fc2ee1c6578b17d362434803fc6b8a3d263b94995ee2e8eb0575865652e1d624b42115956c16b31726f7b462c6c41622b472541f7bf3b9f9a1da92273934cb2ddb734e6098c737728b4398383ee803d7bbc76f600bae9d91488088bc0968d456b827afad40e2bd3180d3aab1a50bdfe641d1f6bfa74301ec03295f53a9b5cec06257934bf01e83272126d633814903a6618227a3ee01a3310193ebe31069cf64426762e0c0bd812725ab95b9c2666896d2ee2a9eda29bdfd77f467f70ff31a961c7ac099000f35527034d159c355bfe15df4e69cfcc1c3c09b0fd9c2a9fef89d652970345c2bdd22ce2000cf7dcd1b632a91bf1eff617fbfeb712119784ab8e8b26e2c33bc9c83e2b938c244e15a6e0b2bf1fa3e0ccec689aaec128d7d08e140c2681b7402d58803864667894736f344a5cca07b1a8636d7ae7411e447f222f290d5478148eb01e68947d6aa11847477a5f4e65220f93db65767fbe8dfc98da3a8cf13640150d4771e964a36ffeba530af39d24b61e550f38e9d7a56a72623bdf463c29babfcb52720cd693fbdb96e1014cabd679b4049d3c965af4b4ce2cf3670c0add79fd9b6372738535de5e12c724fe932473de5f74dc303b8af20fc0f718cbd9cc0dbff9807264e129623ca34f41f8ba3da221e322924ebc3ad474f321a4bd172fced7aef2724071a38c32335d5c86c55f731bcfdc5d762fa2933fdc7a212d6fabea6e21ee7294839112342fe52a7431e4b9dcfa35daa8095a635ac37bd3bf238f61b23bab3a0b6f190e6fcf0cc8c204797c6219faad540ab3db63821c5eaf75be0cea8ae972d34c5384753fd3fa13fc46d7a2e682d488e737d9ff3b9b520269dc556069d33d30c853ba827404072d7689492f7ed97bfdc9105561ff6bb74a58f59a654b967231592c7cb82f515fc00510b989793aeace5b693e313e0eb1b7e648676f9cc37229819dc56051ccd254e0fab8c52d158f1d500e314d97f6a8ed5d3b2669136b0a6006f3b570b20155c52d842871bc39e83acd67226be2fb66fd10f308d4c65a230e5000341887a2dcada819e8f397a7cc66d7ba651dd2813e47002a39187c357226cfa7e682abb2842c2ff34417d4f528d19349b45043cd08e339c89cf9d4267265720e840aab7ed4b1ecc1dca3ccd03bb837e4a3d82365bda9e6f60ecbd5d232b8edc413defe0cd89daccc7530d615125cc30f9901503740ca6367386d53d37276130f68ce7fd12a9cf7fa3288c502d994c357b927ac59285b302bf3c0aef272e184c13238251590ffb09f15b92ca27d1c8fd3870b403274ca6f73921ca1a472e1cd6e8be36b47042641273dac474219d1db09ad355bf44ead7250217e2d6d3ffa121e1738175e3c91e87836756f56dde98ea817c34cacac7e8d40f82a97fa7253b377aeb036b1f4e6630735bc493dbb80a4e0f02203210e90395b130700631e7702b465bc626bb35513e0c920a486deed90be93b5287f8f076e1473df46497206f43f2b8a75ad7e6e98f465890ccd6c3add2cdc530157c6be1b74d4c42424721e2122825f312f3cf086142550a06a52377e5b8d949a328bc958257988cb3b72823a8c6f14fccde5c7f78c301d53ade86ab79003537a448aeb5f43521eb8f022de6f7569b0640b27a3c7f4e389a01da05f1fc2608b85d1ecc5cc4e986a2b847259754a3f29c6b4fcaa94fb669c7e0187c6b1a5ef221da1ab7a4ba72ba9931a72eb03bfbe670673c12a53289bc18d5f9d892d9efe0969edb3e437a2c706ade672950fea2d13e4a18289ad32bc9cd81cb0c5b0888b8535b32c96dfc4d760b43a72b27f1fab03ede7f9f6f18c7f2e0e823882e036fc5d9bb285837c0888ffeba3726d8031d568871d9a980e052c6bb381408cd48bdc3214c61f62263c2d686e7872e530be7f72e190c7d7f0f164e654e20699c0aac79bc2164e7157155599f37c62a212ecc98b12b5e90eb17e0d8fb12722d205e08c47a8ca32ce2e3665caa0b57244aff27cf9bb879d82904cad2cdb5ac4c54166148c93a9ff61677ca87929cd66b9441fb2dad309c359ee1e1d771ff6bc4174ec1c5e7a6c9b35f8adcdebdb2924b090cdfe96b03088fe3c216c93eefd4dec8743d8b8827b914bf5ce06fe52437217b50e4917e8f28da31723fc803c59d559d1623a628b920e0857144f0582206a1a93305491d8b536b448dca6fa895e4fc3fee4b683f04b40637b4e2b5b783772526aeec81c444572a1e61129efd5310add47e835b56c9f040fac7439ef4e710a563450e59670aeede989a0c17d24b24a31a4d5a32a7b116397b4f2a8096c9772536dfe1c91b49e46c8feea74d5ebaffcf4984962f70d6f2f3ecb153465199a530b20382a71b65dd72f58f8a84e12670c89530a64e2866c6391c958439d9fc672e69c0fb3335232b1921b5a7d1285b996a85c01a637b6ea9844f615beb29fef373e64ee3de26c41320f7870a4d5f864610b0681ee64f3334c070a9a76f04910097fd4ea9ff00d569a23a2df72c228a87180322465c5d6fb2c4ff90d27cc88071216b69786bcf67f2b444cac8ceb22a3075a272323bfdb0b9c0d0f15473e504d4dc42c4ad68854bfa68777a6182f06cda924184347c7f7d80180eda50d81ab62551b72a1e36f83f1ad23f34d1d040f23eaeaafaab1d5c699b759688d05566b5447e4e118a817f3a139169c605946bd95f5040bc2ebb2c137bb0c8626a568248572354b59143341e3c257db001a8fa626347dc171956b00f6648873cd55c2e84d725d8bf5582a1a8381a6f124d7cd0ea47c5a0d9659e42e97478fe090602ef4ba7279bada2ae4502b525cc634e8f378a75761366aa3a61ca6acc743677337f3f37201bddd085171632b026008dc7f506a9d0548684c32123aee3a7b143093b3ed50bcccbccd48520150d6a0d1e08ee26df16d9b6f963562c1578c201cbab7ab0d3a14c881dea2032d96d65d90d60b82bf72344071aa70cd1e85f8800bb9e91e8572132134cd49388ef710a1c151ab5f2126051492ff61602915798f0b353612284729ef04cfb18778b41ca9f9c7c9d21262613f8bc5fdde0ad21c3acc4ccbc34e178b3c3bccad2c115eba78764c905454c3abccd79ad07377d87f235c2ad16e2a7258cb26d8867a3319468d393e3a4678383e861300fc85f1fba521d6979bef6a7218d7f2f0329c69f4264d3d89876fe9eb909803cbb4ca92023ba983e143955d7207bbbd7c14c897652e812970a0e7406b3b1b70aa5ef9a7e2d8561ccf3ad83872c5d895459367c55b71177d942f6d31701873f0d4eb985d3ceafca17b2fd1cc459e0e705b4bc01166b8d90a453adf9495582b809970d7c21b49d022f61a061d724eb246580625a8e6a7910ad8b726cca4c9dce7a0e91f690a9419be07f9b6977253573b5a7f8250159e30df0f56c2127b147fe19ccb16ce616b2c08113150884c7405eb546b2c18b49c43cce37c68815ff381aa3f39687262f616fc9eee4f9a72d1318094376b9e0f0d43f1b5714c196bfa864c60d3eaa3a02e8cc471f0dbca72ee2560085817ec4604f7bc47c7a66eaf558bebad9cad79ea9bfe5ded7943aa720a83f91ae29afd66fc4833a5ee95e25b68225d31226845159011d1d1da48ef5db72e64ae048fd2855ce3a54170fe40b4697e23548ed9641942df969dceb8ac721f0930446c4690a4cd0db21d5ca83eaad09ac25f6d411f79c01920bc34dd8072d85647f0358a24dd0cca2b60025112d893c3c9cfe052707ff14ce3ce68523c72116a4022ed2e96fcf34c40f57ce49bd010d2e95f52bb0185bb88bd43d77ce019ac88327c93d9960975df9b34674fc6d7f222b4c53f54fb2920adbb194d8ce67247d333500a85767b94702060bfaf10ba115b38889253a44558ec10b19a6523228a916b5e6d4f385b3c3ab199a7dfb4a88078270ff4a487a1383d2b7713801b5738a817c6dd1c00b6cbcac297e4565b5bae5ac6979c7a42a36e95a9eae9118972de4715445268dead080a8b2121396681fec6d1654e9ed4ed155e0d05b9bbfb72d175f6b52e5916e515c1c2ad72b61c0f17f3ad3d5427b1233224d1825f50737276087bb910eca9ea55a7eefbbfacd1f819bb58432cc92f4f6b06bb725cfffe725a9ce314e51e281f25f704c4af7844d9218b8fd532713baf0c48c3c5dae0a372cf0db2b5f1c6a0e80ecc1a7001bea69e24b6aff643c4aebbf658b2d1213b6f72c2eed5bc1c999d2f240e6f0de6700a3d445ca3a37573e17c36c9665b71b1ea7230541b956077f85e0880625cf9ea2c6dc6d6eb68c6c2c481f690a6fb0252bd12f273c825c42a8162bbec7c898a33e29eefa15de0df4e0a38792d488ab9935352689070c7a58366a58481180e59db63975ee5338e13be0fd772ef4566871afb725bf10430cb57983e815dbe29c1fadd74010f7604363104504e8726cecb45461b7becb160e30de12a32e575a7eb33443dbd48b4530005aeb9fce69a2fc15e7e726c7f573b8b5cdd5ee222590bc3a518dc7fabd2ee70d3480ec0e93e540f9cda2515dad76d912a800bb02a563be71139c860ca92f32fe680187282979e1ecc62727fc4a0d61d8827423e56a2c07585563e56ec808b87f95adde3b4af5b324070729c5de9d923474dc017c1ad9631e5ca922ca12e67c3620db7c25dd512dfc814724569e767fdec0935c90010ab23faedcacb3167b498402bdffb952c9f83a92572145cd5733bc264574414554df29258375c959522dee217da663179f49a82f26f390645a662ca08fa32616194af72ea0190a9717a9f36f4134836f4c90cd9c272ad8d3b23d88fc544fb53a6b97cf84cd4aa1a5f5d974cdb298195b2cde1b54072fbf86a10ea9b59a09e6fdbee480aef4a1d24f88cbf8e074410627b312a96fa7242005d9eea3811e4241d642268b09af84ae5370dd7c7c48b7a272fd09d268672dfcb41316b6ed55e8d9b6011269c54d8687b2eeecf99f3ee6a2e92608e5f7f0ad437f384f0bb5aa4027179b9412e61fcba68db50090a50e0facf64392f05fd1a74ec57424691883fcb828da50165936c288caddb23ce8b92b7a924af5960187207808f00d306e3a9035321b235868d27f3f211a330a1bdcac7b147b52902475001418ff04f59c3fc559041cfb3f4873e86ddad9ab37acf759e69e00e3d82534e3630292f21e67df844108fe50f6b45245e7a8d9f135b0b1d479cd8156dd22f43ebe52eba52eb4a3207654db3f1c659c6d1e14f2ee462e77fff73b578dd6400725cc701526630e4b2357726965a2974dc8f4835e243e3ecb89e80374e9e58237230e7427caf8fce4b505308e2cc4a8c3cc95fa8790b51bc2694294028682dd4624b1fdb5408e1038ea4823a25419127faef3562501cec387389e5b2938a29df260d78762925de43c4ba48103f8303499696c5973d0419129edbaa0bca47f1cc723a4e84968d0af799d4f8691f64b50f3a14c2841da6edd2c88e835321a177496a980f3c847434c1fae2145e2f5819a832b3dad3b0dffcbf238ddba7e433094d729399c1bbe522a466e743c5d4e934a64f30f97826bcf30c65213ff92ed2db55727bf3eeb2b3d36303d0aad1170b47c47c7830555d4d0b085e549e9ce56f82b4720dded9490ef5e9857342c5f4783c3df98e715987b9b69f26baa499ea9ee05f4164f7b7cf1abf2d35c1a538f7703ae9bc566692d6f0d603d6e25165138544f1623f3f7f94bdff31bd807fa556109a855c0ca39af83e098da0c60e147e2e2a3c72ce88eecc52acc7387ae34dc1302b102534d40b6f7477858b5293523e1e0f76670bcfd3416e10913f3cc000a2322be24b2381c0fb1b595bbca0ae4945d99b601eb0c4af52c9d17bc5e17e655e0d9288990eab4dcfd59eab4b2f0dcb6afde61f726ad8bbac4d58ae6a676eaccc5622f2f1212e934c59c25b71a977494a1b85b2710447e086742b5fbd5332fd9368ec3b33e8db35cfb422945d32153066980f6e5317c0ed5ef746c5c940e41e0791178c176c1c6df9e7f6ab143429f06b3d3194130ce0508c1e75a3560964430ea89f88fd7fe4679fe1473843f6adb0a2b7010b7257cbbeaf7bdb0a1d326ff03f125e328bbadeac26a38a147d13f7472310149d72793949607bc7144f4b280891f5b234e0c8fc5d092807808357e26bfa02f13d72f9df0b07f93b45edb6f57e5d884e5b32c027bacbf7cfee72ba458a07411efb72748840eb47733cee85abf354e3a70d97f70d94257c29c301d230099434c3b37287e51e95e800ff5daddca971734265b6a397802154913995dcb774109ba18d41c3d73d07b91aa439ba2cf3ba9c6a566afda025fa82fd337ddd0d5e4311764d1e6271de0c2fa88de0ad2c615c83defa963823c06a67ffaceef02a1ed48c80bb27966b1d03fd39dcafa38ac5c95a792028626ae735a68568c748dbe432aa39f26b6844177b9ab18d27fe8f6c6f20364f355ac19558ced82d3f38a0fea743fd5872997a4fce062ebe574f1cab1b4f7ab5e3f3d1b3aed23ddd8359610cc47d34d861bacd90262bbaf27b3cbe311ed2c9ea5f2a28f90030959840bcc3fbb26466b40692e8bf947406bb1ab0c086a10706ff37798dd63593e5ef15f6eaf185d11c2072a1352a5c5eec8661eb5fb934f2c8e22a2cfe6412c15f17a6b51c29684f4e9672471122b29e6bd82565daacd738275c1315333aeabb86458bf1f7af44e586da42a795420af94c49087ec6719b9821ab41d20b259df41fb22bb0a31655e4d56b587586b646a51742311a97e66b7883bd5c0ffff1e03a5b5a653b1ab079984ca4728d559859cb4cb531720793a9c6101588451141e131c348d75db373e2a2750f72ddeadbfd6a5c496410ef12a4801e36c2e6bfebe9ec8f4df520841ed1eea05062e10edcb7eb5d097418d50a4a688516435fd033242e72439dc4a7e3fbd2f02972182b01606e31abbb656e1d601b618f3ceea78bc86e37ca27abed6835ee8161332da7d79feea71fe5b82e569c7bc060966d8edec43500ce5150e9775fd0b4ed579965005e0dbc4c5bcbf47de7f57353bad9d8c786bb5dc60e4fcdc374e60e2415fc6d2050548c9834c38fcc2ec80bdb7fc310a2e7e31339f39f270a5a21589b72391a32cb9508a4fdffe0abe178b317241be6a448ca9e038670fab38c42c3e73bd678bb8d2d489853535118c6023ba01559140d8cb51737c7416e249f9df6f2728295ec77117be426bd6196f6f576c784d57d2fe1514184039acf86010534a472b6716b5c04abe1cc436c9365c82c8da28b69888258f081d7692ceeaf11188d72fb285c92a349b25838191c063765540b2fb4a5e05b0ecfe4cce2a86317fa0e2681ed7b8e27ae9b2da86734a3eb18578d75d2a19eaf67666d6315c98ecf3c8813233485ab00dd380878e020b31a6162cbef5380cfa6b4e17c6c4eb4df6d6a3c39347af5901727ac006ec4eba2a2c1b3da56ac173fff25d812a68304bd49ebbb35d2e3fffb09c4f9364f7323b19160eb614c0001dc90166e3aac58c8491b070739f27c990a6de8aa3c831210d09a688845d8b5fcffe7b8038430045e563eae07725d455d9ef3b25d4f0fd83100486d4beab620e013887bee57c85e72f363135472cf669dda4dcb00a8701663f582ddc0cab64e8c128fa64aa7df9cde6061f973724111020b5f1631e59d349ee7c5e040c523a626b651b61768b383885904ac6d72b97d6a62cf010a70506b1de479e619cac05755ae30be882d2108ad228a12522f4ee4f630f51a861cd01cda53c23eaf666b60cd89babc7d24baebd28a1690dc721f242fecf7ed673eefb58a786d39b6652e88065b50432016782d96e65ff8827217f9f832123f25a75795e9d0cc81f569d6f72545b0dac34e044c97d24d238a72e09da7aea4bc3114059bae44eba17e207805dcda77c1400db5f57a7a552cc872a75c187dc7192e16cd5f293afe2482aad3dbf23b5353ff6e77e0d2a578f92d443e633572cddd48289ac807182a0972bd310567c0d156b18b98ede5e37c0e1f7225f6552ce7e90817c6434f451a2401e7a48d9841d05b37434712ca2e652c8b27f4da6f6714e3908c09e1fcebe77b220f447bda03a98d4890abc5669cbd78df158c90ca2e31551dc1c545a6e1b448932628632b7a82bf5632440546ed3a58f0097cce9d0d8445a164d08896b14fa2d582626af7b40a33f518e5d0d7bd4eadd212d64d8263bff2f9524b7fa9d8677bae1ca8dde57797da12dbc168ff995bc75a72fc5103a9c736458996d6b94cbb8ff3859b10f6c8aae1527c8a3a593d8d7784727a9c53894d67c15e24cf087f74bc24b4c3c157e36d3dd3b01f5d9c57ba6fc51d2b316fc5803dbeb54863f259ff87b662ef0d7dddd56a8ea9072fd5712bb49d3dc88325f5f09439aff1ebb6fca6b834075907000a957ac2cf7e5a0d2119e4ab72bf455dc5ce513291980669c2883de4cce28327fa934b0a4adedeef68002f784fbbf9c553bbfee838d0897f347fad1ca22e411e1f6493fd8096105c7f98e34572dfdbb9e544af65d7a9d40df757992d853502d1d932d7a0cf69886826f9de2572a637b7643eb6dde2af4b0b29ea651ba95ab3f5f5b6b4d657309845fd1ae10745481e6b4fc73fe5fecc8d21ec0f345ecb4e70fdb27f3651e809760d8ce71a237238346d44fb388cb53df7ad0d50eaf2247b7946976c1192e129baf3a6337398721f69a3e903f5645398a8274308f657981acef72de7c27ddb7fe887bda8fcd730cb9011f52e28b31eb9437bfe990cc9efd49023003bb07b08060f73e2f1341c72d6e5157d505cf8465f6fda18254a15e5516e51fc54ee7eb0c8fd83cb3a47ba0b262569c63510bac53e035539a1f6c8a9c2ba603ecb7586ed83ddbd79244eec7238f29090774d09193d04f983a3902f28bfa1c0d3e00e162b962147cead451a72e8f4531a3e58dabbb05ecd268d2b8a0dd30aacc1d2b613a58d31aff74e86680736340770bd030d03c5a7039b7c72cc134ec100966ae927af37c979440f663f72362593c566358525c36d50c57275feb16942adb948a2e91066da50f029c1e87233eb8c0954e1ba18c83c7acfde8d7292b7de717c7c148634064386b665f19c720d2bf8fba1584f0b2bd610b6bee3c0c4e73fd8e6f66d9952cbe70cf10e15227220ff99af56b7c689621614825a0b9d4a19be1a5fc8b6b0e56f70b6086c56ec72ebad7b755412a769eb6745cf063068655321da9e8c1a3edec5713ef553ab3f724289089782db7525a0ef632d304eaae29e873deb0bdd90d2883d5eeb2b4a987211856b3d6d9a07619b5ce9489631ff2cc59261a54b25dd4672ecafe9238681721ea68b7ab3e3ff273157bf35bb1ea1341376610a687af024078edcc8149a5d71ef06e531ac52ee3c2829769fb914564b2e3facbdf5a288a6bd3d7332c8317a729a0e231f07d845131c85039258671b9a9266dc36765f54066411a59f471d5060c6522bba95f72113963424b5f00c3151ca8d8e243a80bb895e9a07ddc8d59d17c46d553744ab0e5fbeb307bc5b009f917973f37144707644312a293abedd41447732d550b088796c5f47789b263f09e4346fe7320862ddcc56f51f2abb1bbc72f5ccd4bd1aed42083f9cfe9f354eb13ab9b702d056a5162dd316697d6b6fed2b701126cd241c55e26317e2f7ad09f813ec1b9cbecb2af70d08a83bf34fd9a4728d4bd3fc18110cbf3653edb1628b02e4a27ef87eff009dab22fcbc0df3a86d7260489b8c2a31356c51a48aa9f173bcaa40177275e2bafd676688744173737e148be992ca8a210b930f3fe508461c92cd12bd24cf0d3324f7db01c2d87492fe72674b9b7bc7c1707ef805c1017890ebe0c87afcb297f6dcc9b652df136b6fcb0ddac441cd36adb16a5433c8c0be0ac3c4e3137cc07665e79168e0021163ec3572bb290b1c8d50c3ee6ef5bcf686c9ce8434c5ec850b5571b00816a023c3779f7284bb447fec8daa90ce43ceda93fc40a647de4271b74361398cd2df39be7f5c1769141443f4eb749c62b3da392fa859af42c0b10494019cb90e1d0ff757eee4724bd8ffc39e55950318a8c0923a17dd8ab9d5e80959f5753c54bb9cb8598be872a76f63ddc6b6b25bb9cf9a420627161ae79e8872e3d30eec564f63f04df41772add5664b9c8ceae059991271a2d230b17e87bcf0d072d8d321342bad814cee724150d8913734445396343819aa9cf68c0cfabe9f3cdbee599c4572bca4f11572417ebae38e10053e39b94ae1b07d602bc75bedc9e8404f6a7a11e13a8ba8b96747fa15e32b99b11075868cc51e8e0735729b9dbcd9245f27461dda7a2b11782849acec6f855d135a075a22ec7618bd5b264a62a1518f9b06e34602b6a8936272e8377eb8d97cf4f035619cc5428af067e038ee2c59426326336b4726f82b5e0bfbcb96be056b3779e642e40b51e735c9cd67a47802b5f35c9d044c6860b86d2b67ebd1c883192965e1478737f2e182733de7e8578ff7ea800a30f1eed285b2729f36566ac651af388f2b33e409a9e763a8b46c1216170040cdbb755b35572754c9a6032220dbbdd52ddec436c26323e76efdb2675177a5d81f94496e0381d4723d59aaec9f7bc5712c8b7dbe1e8cb0ba06fe428fbf5f3ad1b54f5cf4a2c42b34c9872543da89a9eb82658d870ae3e610935c308429df585b4b7999a78558d8723b88f65550ec4a7402509b2c226c78d1ed2fafd77274a1351fe0594e5028944447472a6b73adfb8d7c13f75aaf058f64cedc7718dd269ff5c684761f5056fb7228c0cb762233edd6b4754b7d8b24014a63a5658eaffbe1c02437f124f5d36572886d31b98bcd0d9ce8fe385b8fa93c95317183af897d874204b359efcecaf772bb617656e5b5afee614c4ba72feeb4172f54e6629d15254c9f8821e85aaff672a4e12b6936fa8eddedb055146e9869a42fe01cba043d5e5717edbc5c973fd609122c6eaa8bf93cff6c34c7677b8a176441e06e846d89c131a1ca15b59552c96ed35d8c55a570372aaf5695c02775a45fa9bb01721e4c37ca6a363fe33c185c4ab7ebd2f4fc1691bee1903240c52980c57fde0a6f7848f97f16a712d9bb32e57291057bbfb1a2b441a31d66604bd797c9b1060267d0abc6fe28eb4b07a573402b5c80a3632cf06d7e6bd600ec72fc5d2aac68cff89d4474db3c819bd68bfb547232ec1aa53e0cb6cd7d3a67ac45c852488662aa8333e1a55d0c2b6234687a5b0324fded6ec354db6591bfb06680c381edc99404cd602467727a7386ff48810d727d91630dfed0134855c13ee57833cbc0ec1b34c70f24337cff227d12971d1e118d000f6d8a31452aaeb10266cfa7b31a0a1ae8c645143ee9f8ae5f3bb5f39072bc3e2f2a6400de6a3f5799c367710426b32b96ac67ba2848dd163e1fcd33c8727c589db3e7a71492fe1a1daa0ebefd873574f0ef2291e3c945261138d370c00781517532e645b4615c481b9aac568417e97d795a3243d4ceeace6aed4ab4b4695747e35c8c84ae0d4afe12db8670fcb238c0780d49e57a685fef5d72353bc1727313fe0223b36554ab9e078fc1da3f82d2a6a58184cd1c4d4b6824c37adebc223732073638b0675c307abba2d2e7d52dc03d565a78bb7ec836e020fb0c0895578ec3730631d06a4117023feb78613c9cc885e8790a833c77053384252ed70a72800515759fbffb96b497e5fa2bd48972088781f3c38013d82eea60f948fea372b5cd229f93f333c4097c92cb29a01e09ceb921af5e622309cca6cb92eb782772f7a739cca1e101a010157714945c7457e926398abec38d82648f8cc83e0d3d7216a34bbd27f764aacf75168353da6dcd2a72b44ef0b1dafb58d4adcc8d1b9417c408841ce07651c21b46ecbe055b802448e60bcd4c37801186321c3fc93d682fc92279aacea4f97c89ac0ade2cc736b2d05fc5239d2680346950746722cc702f3a7faa561affa5eb1d96512f0765156b9e41f7b760b2050b533f0e184ff7be72fa763225ed56f8d3172c4f870d6651e48ffd84ef00562e85eb4ab4d3763d8c722255912cfa6c0a46975de16a931f3ee00cad38735f92fd9241a097a2e2c58472b0f4d94255154b54177c5249b0b48c39f57b5c7476b44a2516aa406e7ef426070fa4d3845a86488a214f4a2a0fea30fec9c8cfc335c5975134a4a6f4594e047206b023931bc6b7b82f881971da09b53998f6ccf098ba62431236580c657bbc49308e9767e9c5a75454e90f02c81231678e2366870ca0b46afc566a164cbf321b1809a066870d8f2147086e047a5fc9c319dce7f2ab55332100083f48cd356972faa42826350cad636c8d607c2d6295ff881cab1b1591d3c0a2d9dc7b8cd68c27cffc8fd96371a26de18074e897057bedfc36c0f47844215a2459b3bff38d2472b50a8fadadb382f7085a0ddd2055f382d63062a39d94a9c8d47605a734f6417264954c22b5d18b0e4b27d8447d6bfae91f7369eca5b3ed6cbd6750f1a9802d72ba4ae07ec28b83a2cfa9b0c08bc54e792009a5a742a845298de07e5810065a72650f0a5fbfa0cd2fe8e6cdb9305238f5983ece838b69cb73f5ed4bbb6bd4d54a56d80f1723dffe055363c253b89e37ce7ee8643ec18c531f4a150c5ca69912726c119851921da8e74aaa8155d8afc8aaca4e63bdffaf7404bd743ebdc214d538117f0565bd5532c4df6603a1255bcd7693145a9a9ee4fb15c3978f2ed579147282223088e112be38cf98601cfe65536dc29189db4e67aa73f3f31c2f2f673d72dece971d50209efe49dcce22ca7b64c7ba450c814eade0cf9de74b85a64cd0307b38424397bbfe67f39644c43003265ef28275b43b297c40faec89be62565d72642b44b713d7ee439a928ab532290485e4252f53c94b7486abc2e68119c26972532dbab3ea4cb2e85b4e287404a273807e9bac8a7a39363b386db2a8c6595f1146afca5a82b4fe725509050cd10cec4f0a0a74a65658737fe05e885e0a482c72f70c5012a650216a1db346d2fce7afb2b259fab4cb78f9c214d48826122ee961ad80e3d67238874400cccfba447bc3cdf2546d0b826129c5f3d97b8cd4e5296cf8f79d7fad40de69b20b837f50d0001d0ebda76d326a99d360cb2013b4adaa58d0d610cac02791fe25432f2d98b80fc031c27d4ed53f7def4a66b173fa4c48051efa89248ad9d63ad67ce531575198b4f6d995bef885b49cf8f223d945bd517290fa1829007b8fde27871edd8f8798c778b8aa09ff37a1f2242e1d082b2d8f72e75e0f6e6983e672f47097c71aa4507d27e88269c71a560e80be61b2bbdde032c450711c76be6c08c3a519a5f3b96cf1381943a7a9fc4e8fb86ec6a47399c8227ec9f98e5f4426764f5d5e703334961504d345c11ed174127ad320dbb2552240cb344ba8623e950099e78d04332290faf4f56cc2ee908dd17a7d7f01b72331721a503d0f6b4bd1b2a98e52f6ed61164a573d29f0f381e1e06fee7b301d797e72ff5cae56c3362b4389e3b65b41eb30d110e1dc51530a071301ec24bdb2d3ff72a15e73849ad252a57d042ee4ff38625409aaa827d3c7ebd0ec10ff5e9963ba6ce1f3050a687c8fe7086646d2e1d3a0cd5969c77467fff62600f1b54522d200729273517d8f49f4a0bd8b60334d27e6afc5a75de9e5f7519bfbd97ed2f07f44567f797d788cbdfcd87b3a69b2cb90e40f6226198a8b6d717890c154f54938c355aad118ba988afb27f3c50d860088d6f347a24d338a336257a3a0fa52f118f85f026d28eff4643a6131fbd49d9ba8d056bbcfc1a052bb57a6da3f7ceb06df923f7be19373fc5580314159cc68d1ea288ff180af2e379828f2850d0831ce9382729fc7fcc0d89c5db347241295070f703140ca84fbb3ad44b895232db362199a512f9513a5c4d154fd01beff498ad2090ed690bf78f73c2a592420cd7620859b72d18395a7bdf706a9b7fe01517c28f267392acdef85667a2dbde3b41f7f67b46f32f1beaf879a2b0b4d3979c8166cacf2336b6d985ca0a0e88a0ab97c85b3ed72a38c71c765dab31385f719c381db47cb5cac6708750efc197a93fc27c1f6ce722af11e5f0e9a32f358018dd1b6078637be25533b86ce01e6d04b5fe27ccc847274b32ac79bc27f6ddb2709dd0511909256a40d995df6b44148a7da7c0969d1212008683f63e17a53a48f6e647adf1da7bcb7222fbfe57d17db93b71f41bd7b729c07ecd13c46c7089bd662b0557edd41163e91be80e66d4d4ed48bd9811efc101f68b447e506c9397587b220c4c02e5d76c688b7e192734386dbd92310b3dd728192d0c7dc480269920799dc273afe424722695f1a07ae69450acc5c03c75353158ce0b481aa2a3107c392e1a6dca647276f7505b87f8a9cb8ea820374fca766dbd55540621bd42d95ee28eeb29f224271fba8b9511932979d00c0d2584500725820de4b74814c13850d582d0ea6e0904c50d6d2914e2f50380684ef827e7747bcadd1025d3a23f092be0efcd86410b4858f31a8bede6a658dd23b9e6ba3377256220d78f29452caeea64690826c5838ba28f71b3f8b2c40e37ad26ef83e4072f3a9ebb368c455b4df65bff685657ede09706aca46ddc3223479925ae95a0c7213cfb8f1e36d2a016705f8fb710a7680eb5d01731c9dcf7d1bcbaa3521a91672de78e192e85c856650f886b28bc2001943beaacc8558ead472bee3161d308a72a58c9ed8d06b2323b20e1a01db3d2b4c2ddbeea390717dac0383d1dced8ebb33702ab9d4438eef3637acc6b77d08b2bb20913b4b7896a06bf193065160410e2f7535b803898dfcbeb124452ab8d3994451907de134b0c9a9923646397f81697229f6f36e410b0988658097ef1b2aa9712f0da86459db6a102035dbf449aa1d7240e94bff05ec2a18f9adf4d112f025cdd559b0f2a0ead496dc2df8d430f022345dad1b7bb10d8a0b9978266ca842a79cc692434b7a00b66ef04f3a82e9b0bb7275c553ff02b3b2332b2ef9bde4256d645b2325763e51feae8b933aa8392a9b72d4ab8922682b9f0670c0e66fc2dbcdb341ea1e1d28d1c3fc980213e89fb1fc72684d8dfdfe3c47a10068708f0a23a658d0d1a419bd635db3042bfa491e02894f7caca6fc689da874f5a465ad342283b20efe8c7d99935dd5845c34b89eac5572b93dabc0679850902d82c47812cb86566b094f3daf702a9e63bfec26df158037321d5d3b9f3263a8460ccd41fc8e6a59f9bbd55c592c40e8c55f992e17d1031ca099d6fc4d1cc4756ea540b5cb08f9c6076708840ce47f424e9c0063bf347c0794a1a72987232f0d4f76e3fdf6110b5bda04188faa3514d7ec31603fb5a8dd72b95c85576a89a03b30f1749d9ea2b52015e99894017994b765c6f44d853df738e89534b96304faa3ffde1417b0d473fb2b493fb4f08f7a3d70deccfc0d9e9b72b8be77f1520722b577b198320ba6be6eb89a98fee7a59427ae2a1ef65849580baee5876a71ba209d7c0512d826f6a42613095c545061a37c1567795cad714372f068018ff955bdc99c1bebff92605355f18569b8e0ff7ff3c1b8873d167ee0720709da12f3bf6a1826ca2b45ec9645428b05b534023fa4faa135648e52781472d2f0063afb2a319ff89ce24db11bf8ad1525f7b8dcbc9899dc1106f302790e722d768c0c4edc9677251ae9fa679839949bb5773d21e57e8693561fa0701a256e498c3056da52670be0f7a255bb2a3ac1afcc8b5209777c13ba67a8ea3caac94d9765a8c40cbbb9ab57ebd656a27dd9077a22a1c6171e495d6a14b01a8b3ed949f2c5289e1b67ecd108ad72259a79ff5d4458caa54bf58b3a6c14f4882cf42d72cb1727472248eb54d2a9eed85b73c7959a557e4c38766d7d9e906e63b9ee7c72ad6b9a815754a5592b0b511a6c0f89e8a5f789b25802b67e1fae7bd1ec801472993918d10abef4e8f428fad8677d1c68b14d9ad15bc59c5b95946fea708527727d6e0d438d04ffb07b73f95ddb87725ddafda7e0bf9dabb1f32d3355183da2668d5d7b8239c136b9dc503f84242df49f515275a3355deb5dda5c97eafaddfc69af7338f42f8f96dd45145be10c41f23660a0181d320c5343a6935361b48d56725520c170a39eb0baddd67b251e0f9235f172e4506fccf4c2d2660ecfc1d8d87262ac693a21234abfc0a7d8541ea429dea4569f874cb7be63c4dbdbbce6c651424a4cc1836de4789f5d73c2ee4a8f6f4c7c0f442cdb6d5e6aff5f39eb75bc94724e7c6065a2ae924e6d046bfadc211fa2b34b6dc6cf862297d3cd1c1dfa90767231c2dade91765744d75280a736b151a1178710786d925f56e3d57b02398c6c72b1a8c4d531a5a7b93e3ce765ea750265becbced8382ffb52d8db733c2847a63dca7287102966036f78f06027a5dd1b7e806ca659e8b553888a606732e9204172007c90e044c7332d6304ca53b2a741b526c9d4f91da58024e9280904232d5572908f9f46c865ef77e2464bfdc4026cf10c835369d0053243fcbec193cfa01f6f15b1b77d6f2f6b4359e15263f487fa0c6f373926536e4d53099c0460bba09519d0e9b45792c80780c4accb4a2ef3964de6ae6d521b150d6dcb2d47f7f39a091d6ef2fd9239cfbd3af7707d175380438f93f204fc2b35cce9e209f001fc0c21727734da96c4b13a4cff8455965fe5e698a5fc865fb1c5a20f491a1cdb1fdb7c575e5152a18a3b588f91c6854811a8af20d30afa54c3399a5dbc4dc4ec98892572e63832b07f2bc252cdc6048e241a07d8120a6bf6aa1f7717934e9e79439ac972fc6d51104ad2b933022c7581251561d2af6e9d23795dc2e7889ad68b3e18af721b63bd2d356021f8967b50d81a1853a7d228969ffe436dc89a530fd1e5f04d7279b5a89e904d66392847a46c96d0da7ad8755a6f100016e88db07565e2137a5b57d26a15f36398a70a3e0345a76efe1f0288967e9feb1dba80d65982084aa0620c5aacf3b8528b5930ed4164e1642fb2b2cb5417be54e3e3b67931ee76314d5d0b0b0f5cf35314c3d69b5f559d04590b07bfea6262329c16e00c7a4837143512322ce1d04454634658cf39d124eecbcbac6f81186a92afcca0f5f390a021703f46985882cd397427f55cd24369f2ae9a56be30726f4a6d6bc0985989ad3e5e7215db9a502e9c61376b55bf3382becdfbf1efc6fcc88bbb26a9feee37881cd072930882b53bea841636639e38eaf0e563af86ff0fdf833203c43a0cca7f2c8172aaea483e7cb9afb0da36c2dfafac4d36bc6814ffc0e2224bd910dec073e9207228e74324bcc27c9e23cfbd59f23dae693c99730da71fa0567b522f7b6badf772b283dbb7c03c5ef9450b6437a145f94cf0c036273824e78dba630154129b59729fabc6b93fd0959431bba57afc3b9c1f1970347277538da7a78f7886cda6e872f7e0950b27219e17c7650e87ec42172f0a267cd5ea02f5abab5192bc7800057279dd51fcfa64e9414063be9e440606e73d38766b5cc18c563108914c3095680d4144c6ac7bdf1c90278ac3dad1d1d59e7b5a7210657c45d211079fc29f6a145f5588f48cb86f8ff33fac54d9d4d72f63722f164f0258cbc79234ba1d39e06e1196abe4def164c8c00e238be42584491368d6ec1c00422af8fc78699d881a9667c5fd33c1cd41c84b16565a6bf75c10c40cee3d7dd3a054f103aa320c13de917241f489af14b86297a71793c0e4f15a7f5c9813c92a9ed3a861f45410a800987143561f72a37fb17315328fece06aa56c560cdf43aa2c174cb1534e166701ba17a7e558b5e67196f13e64316eb237e5ee166c74dc5f6394bdb0cc9a1501f41b727068c4c3e6f1a8ac2d9662e285b95aca186e59f48825e9a4754b36b826483b72394d54883220451a71e438c3bec0924fcc011d51464a1c6aefeadc7df7b9d923d5aa81686e8ae7a98928d1c2d55471816196f2069ea0eb21c17ff3b3a2e5257282ad65de590562dca899adddcce0b55b27179c1a1ee663c5bd196a3c466c0052c9657af31c37873a629ca2a5262766d1213a17cde8b6c94e35390a4ed0ec717210cd6b1cdd1b9007a1baf3e603f78ecad7737ac0ec1a2385c74f3950903a943a4acafcb3df72730a0a41b7e99c23f4e2b115b6d716d048bd1e2e472a984dd0726bc0f751b1edabdab5b8217bc84d5c3b9981e638428cabcd4c66e98578e5c84e72bc9f5a16fb728bc3bc70972a6ace4c3d849b6ec75168210e898ca1fd3e384c978d06e016750bd326c5fe54896ffddf6a9e01b5f43cf8769a7ef86c5583fe28d2cc5953cb8804e3619edcaa1442d1b983a773293c3fe474a52f492202c21c3e341a5e93144ca672368437927ac85c677fdbe57b1f1a7c75aebb6eedf6d090723d2ee9d94d8c3d80f49dd833d628d6494334a19a2afbe62f05d2683f45b17972576596de97895295d06d46a9e58e4750cecf28966ed999feafac28a3c1e692728bc1e97f39a4361c4f68f32f99e55ae454164e84ed789a3d2f975c2e50ea4d054bfc066b08d8621081c69d5f23604e509fb04a999b83e0df5bd62d4da33c7737758f6d5c23bc63f92e41123f2a6830ac0e6a2eb34e587de8f9ed026c792348726b3fba46c5816479d23155632da5e5421bd6b48948fe12f4075ca78790ed3912cd9680e0dc36bce3ad9979cfa93090c788773f0eda26c220f5c34bc3cc223f689fdc65e03db30d0576d35a28284eea7d9173240f9608174e6cf2181f2b7d8b10bbad0e1e9815eaecd6274182ed32305ae137582bcd9f5b108fe1156b4fece3723a56f2a5ce65be034e95cd183452989ad6894d709a1e71c31e83f7762a87947237358e94056b4a5f50e3fd0859d7f789551b4d25c9608c660a2437a32bbfb2506a93c2583dabb8d4329598bdbea5b3557ac815c216244483b46eb428bf5f730381e0095be05765c542acb2dfa37f2dbc1f0dc3a400928434fdd331ade77345720e34e2af85ce919a20c958a8695d3c87a8a4cc55e5730d2062e98de145a1595fbd7e76d64af5f17a2c5ce854a9fa87b0eb5fbf83cb6de80b67945647e879fb011e8d13da0d8012aac23bcd369fec67615760923bee4b47d0bc3f04f28da86233d773a99b9df7b228ce943c9a5e9c5645f66a07ff645bb542d618c1b1013329722d237284feebca55707101bfe1de6f7a8113f89dd1a42527b5ce05b5464e23729402d5b8a36ea7d8d05b481e08c945ae6eee0729b1736b0cdc4fd10d869bac30dc443e76781f8ef8ef293f5252b067851bcbc43d0f380882246b49d788d8af727e990dcf01ea05d033742ec90743e68c661840e08a21a9a1a24c838c0747782f13de9bd6bf2aaf73b2a3c4f691147972a28c90fdb37ef5e04c6adbfe17f79572bf615dcd0884e587610c04e93feea46374cd9ed738cd516737d35c0949a72c2e0f56b3886ab1fb0ee139d248e7188015b8b44dd1af9b74c684e674a180b250725693f66b1f1d7af7f5ab46c913cac16f635ca8368fe7958cb67735b808d600723021f9b94f46f7ef950f3e37e88fd2fc0c4cd3ae0abfdfd4dbd846a52cdbec7296bee19653a8e58ab43a68961b28436da1ef42fd1141ef369f4ea3a98d426e0aac5f602506b129597aa9027dd6d631da7cf854bb12feabc915c1296a225f9d7262436180acd672056b6114cc57bfa418c16f1dfe964c746c54aa74ff55ecaa7280a239dcee6123018d93349ae22dbf06afacf38735fa7716151fce597ae63e2d046876ce292bfb0b71b9457a75e0043af30c4938f63ec6c1e8b731f0a06cd17201e2cf556cd169ec87df64a0dc2068caadf96154f180efc091665a7de53b36722236eefe678d76a2ee3a96911a3e48d38f89996eae30f229cbba727da9f24472c47f7a2e186750934d6adbb4785e6364c2e7e50b3899590030a5a91f832332721a84a7da35ff8ddf6f0417e3002d2d8c25e069c3e27d1c0f8863ba8d95db3a65485a5fed7101ccc43056e795c5b130e890254a86f2cf07749aa3a969074b426770165855998f9fc397cf8eea3b7d2d6d0325fa30281f9dbceb8a40b28dacb508622862fa0eceed46d756d969f033d32385c2d448f546991d9b2f75d8edaa2872f20502c5b574c9629b44f55d95ed002db9dbd1aaa44b595318627dd61391e953dcee0db2f34ff8f0fc9cc0cabe4be2f24992600fa032269cdbea8ed4211ea772c247b2616aa866c66e49467bb8edeabd00a77b7d1bc95fee2d40d231eac611725f8b8207c1550abf0aa04983d0bfbdec800b45b97ffcdc1d53ecd3296fed41724fd2c0390a07f28804e9ef2b4f98ef9b6b2168225d46e3c42470fcb40f4dab72c79089fe4ebdb90c53657cd40a8d239fee263e0a8ffa6d34b9912a8da80b4272519c209a36956b3f9bc6f6e5c8dac4573efc6a1f9cd66e0ae1514bdfe4dcc96d94dba0d72ca1431339be16e58a5e37f03acd5b9291ac2091b3188a0cc5e3b1729f04bf10db23c8ec34c8f7dd598ebe7baa48a7d22d08d2870fec491200c1577214e2b9699e7011a4d49a6c4b3825a44f271bfb314fdcd0c4bc2bf6d4f6aa3054b6338612582cdebd917afcc2b35ef839500f19ba6d39abd4ba92a3b2233217723f32399b12782abc8ba150c1331d032ebdaf259adac56b1c3655e25f13f0e372af4c664a9a8912317648f94e7e91b6f81caf775835fcd4b83df8450447e1e37212a4eb10dd60ae19b502643e36141a0d99c6e65d4d7f8375a4fd2ffef2d49f72cf2e3131af3647e897b29c839eed6459fa937f63ca1c4e66bd92d71eb516b6727c7825cab0ac1deeed6c562f5e8fd854c5fc3dcb771f2b1c31014e5b250c97723b87e1781daa3a7fa5d8470a260a1e65f66934b486d8e7348500b2bc4b65171c898b75e01b64e8230c401f9fc51e148bb92cb4f34fdfd7684f3deede1159b672427983f09a70aeac3b70a3bb8d10eb80c6c3ef54fb8e6c90aa29d1772280d1726af097f73ca1f0916d9ff2c5d39b3e042e3355aa4c5f92417bda45b70eaa35548eebc101b66e3ece7eec290470bbf1a4646430d1f9c3ada2deddaa62d5fbeb600349033ae951e8a01fa45828ebab92dd01f8ccc146f7e120a6b46d910ecf2e59dde66631307c2a4216692506d50ef7b358e29c73a098b9b407e895d1903acc72600f636da21af41cebe5654fdfd5677873ee818aeb01463ba2d99f2cb12ee6725a8635c091ce8e576d7e65bc104444a8c139885dcd5a5146119c36926186f84a8660d0e8d7cb7ba46b32349abdae478fdeac0ef560868e940d29a6b7013faa5b4c7b3ffbbe1f79d5ed45dd7e7818b3e8061483b7670798056addba4006bbd6336e31b66e2b6e26a11eb8f33076b66bb1d195ff2158c28811011f8a989c537f7231d7964cf76f3e441a90099adf1d8a8583c285eaec1c8c8d34238fee620ed7720dc32948b12f0c56beac4ea8be46026305262462145d9fc63e38173b1c268172f2bc447e03e044c8bb866465212278cfdebe126bca44ef023199b67001ebaf408678b9911c2f00062ba241e5ba94f81578d331945ebd43bfafc7c2c775f137729995aeda4e5670abc7c6d363ad6a0044e3964206e443f41057494cffeea8546c5057d8c8d1f65864b2cf3ab66dc07f08c10307889beba8f34e4fae9a8be32572c3763f56f4d1cdd6e2e6669ed6b06d200b0ed2268bfe61968da43e1484d11272d080193bcbca61ab86afad7a3e52843b7c3c8168da165b19615ca009eef92f66cc4a2959a68016e1d1cfd1bb1caa012eb626f16da26b6de83b9abe0a95d20a21cda6cfb116e7039e191ca000da61d2f8d05fa252fa51a862491560b031ca4272fc016b4e116823c6c0eebe5f1b5b2997f5acde63d0f3e638e4d2d5b9a69c9d7253fe4e54876c21ee036904ab2d44ccb242eea0c768a8c7dfb43157451646544325c99490aae5be0761f09972dae6bf3309fce5a9ec77a1f229b244df2d99b96a9d0d2488d96b3889f3ff37d167f108ae8a5fc3a6303eabb677a0b0be214412147e7e66849587dc078d97fd74c03a3665dd0ae911727c4b075fe740b5b088d8723951df092083720b2e80d0d01c9cf0ac9adadae0cbf90d502d690efc04b1ed72c35ad2bb6c38969ce439f819229c244199ba9b505b672b9bc481793ef966d2454acb040e484886e15b01195bd6b5d220e1675af2010f3d054e18eab90129f472f2ba2cd533bbea7e46a224ce1f0e2db7b0c5599b6384d963cd1ebf855269d93616f0089047e2bad70636e910db098dd22bad0fa408a155a8adf472cf7923a6725cfc9d9b5481b1314da6e257abe8db34070b51203594a75a6eff9f3766318a60ef543379bbedd3ea468d2e7f1f0928ad81547bf5faeb30a7aabe0a007c6b267276d4a8de3f81b50cc6c1feb409c112f29eef461d1ddb59d9a7cfb498fefddd5134b3b927bdeb901a88a856964f58cd0ab15e5d2d004f34776e6ce75770de8072a9aae31482ebe6ded0cefba3ba399db9291348ed86a372442d67c91fbf03a863c92b39c91b84bcd4426659b44a929f833ef537b116a3b48a2f3b9224672b8a725a2bdb483bbdb9c69180659877e435e14d2f1b0b910f529eda61486cc299d217785b02124d17994e84399def687aa69dc785b47a1cc1d70a729c50dcb2b2337201ca3b9833215a9b9601edddca866eb655e00801b950a4161e216c583d8d2372d6ce56435c5c36f765270dba9b8a3d47d9826777e4a610582766319070a630502e6340f93b337ea146892d7a7f2e044cb54b4dbc4449501914d31559d040f17220196678695264445995bd41a6f4622c42aa6fecba172172921c66728dec6f16c33b6cb6e59fc2b68b13eb7f3736434d1bd67378a617f9fc007fe3b5f20fb2729c5aa929972cb2a4676ad727882fec61053d37c47f728465c0623e4556a88772560381d8b85a76928d1b3118acd3bf2764e56b330fc8e83028dcb87f7ac6887228a8499165556dc2f0faeddbaaed26c19f783408a005c2899fe8870d22cf6f54dcc8cc3aae5770932057e9317b199cc07a5c7b76319eda9f64fb4bbd2d557472efd133b41959853c8d53e8f5baca66716a9c90e94ee9ff907e8c3e7f360f15684c4da8671f38c706e2329862a64a18ed5541f9927f079bec51aeddcebdf2fc72a2e0cf9c4070f7daa310ae5ab8ea6ad0056c1932fe4d4d72a1f471600317c5726da5cf3f385162887e87bbb759f59e990feaf39027fe586e7ffda26ea5665a729d345ca13db1ac8b45bc0a070452825e7995dde15753cc74fa19fc173397724c137bd6924e1de1a3149db4f9def1e4e1b3342232a6cffc748b22a9c457c3be2b13a323206c24322da1b44fd1a36452b5f76af87930f5f2c2c0bb10e0ab4f967238e892b63fb74fe42d2d69d87889b8fd6db034fcec4395c2d2f86257211dd27273970ddf1852db1a01fc6ec9aeaeb92bc02e76b04bf266af5170988fe6cbd472885de302277616eb559a7f85647c5b6b1dad766d03fad2d91bfa7b4ac757f4725166c035ccd3a8454184fb7d5f76145ea5465924f9b10ad26e6edb68a89a5772f37db0d91cb228818c9ce8e7bc7a2861c2b2ce87951a76543ec19340775500725bc388134dcbece45838c40bafd8fa5dd8cdafe1b751dc9a6b6564906996160e7d4bc7471430a8f8cec3f65673daa1eebff21efcc4e76024cf17e301a2a7e41e55cda1f4fcf438bec1f9791c3ec8ce07185e72e5ff7af130ab377679f3ce5b6a88122b0d21e13bc77b593c40c1abbc7c40c9a8f997fb9dd51c394627fc79b9728c36a364b6a64a9d2351fdc48ddf75570bccd1da15875d5f60726401237fac724baac751f614e77c27ea769d3a6d89c20abf3ba867b0f10c2b91fde2b3e2b43697ce245e7e6a47a945406865a5bf2908d3f087bb98e6c94bb78ec1c4faa83872b55bbc2a082362a554869450191f1c7b32d3098175a54f85f6d300e13151db52d2cbbca6ce9b46500fa865cd01995a89c1264364399a8a3a730f0539283b5f2a34887aaad74f0d4c3148192e4096780d1f8f2516c235b3b11ab2cc3f32145072fc28cfdc9ebd36631504c9c4bd6c946ed1eed82b11e7ce8ed973a529803d5956090e5d9167d8644f2f39c4abecc2c9b7256f60ceab70f746d2adcacbc1e2c2726475fa97f0d624fca4cb5f12469f3514cfe79429b16b0ffd5d913d06ac026172d7ce362c6286145d1a3bf8c138802b2b9b92fced4406a626b017ebc486534b7241448d77f804b48b062fb6ad029ad5deb6fd5ab9528d9b89ec4f6b71df94c27243e2d7b8db6785f76ddfa80074790e5d41230173d63fce76708f18564827fb721bc919a7692b1281c24106b2d19696a3a0374ca2daa7464b3b0c0d6e92228472a2ef9a3845ca35dd4e1266fca71615140ce9e8fecc0256ce8abdede50d9d245ae4ceaf40016bfee4f1eb141bc7dc46fec8dcae8d5f36df2e1e42b648eebe647280be96739fec2172f0b9ebe2282d8fb37b1ff8bb39753fc959bf02247eeb0f72e2961f7235b3fb672592f1716dea43bc3d7682f05b1d7c46f74ea5bfbaab9e72d622e04e53f2ba60569f38d0c287ecb71caa34d7c5bdf8a3b8365e9f77ceba72eeb1f3d12d58cc9738dead8600227bafc8cc9f5326419fdbd986cfbcc866843c4e1d852e43c62d6731c6ddfadfab7398e086fb6b5284795ffd6212bbee0b75267d39819fc1f1dab17bacc5ad2d8eb19ab78fd64bbee31d0ffde90d13cf3918722ecc3e09cc4eaaac02b7a199e7f48b04cea64394c7a0a0bdb90fc0f53df83b7298edefb84b5bbac0ee501c1ca39d1bd62953bca022b2edeadfbb37561fdf3b728c0521f556b61b3b8d6f22f29a01687b2374e2d6f207024ea306fc0c1b4f51066141d083a03f44fa92556a1ae80a1cdbe8b8ebab5a4e02e91ea82dee020a0472e17d9fded62170b2ff7a373e80492d4704e0594c438bd04e4486987afc133c72df003d3ddfe04808ce18d0ae66682f9450e742f5603a185db601a359aa65f853efe8e5ed5bd58d2e30790660cb69eb40854856cbb8bf758c37a7b416687e4972ac0a6421020b50ed2075fdb01408d7e3c23189d6fe99ecda57d600f3bf8969721a3221628b120f1be6cb61f7a3fe1022c28c76fe1678c3c66a42157da5c0db723383efb367b242453e737591008f5a197ef9531bafc0242b79230b301e310904737197d4c400c8893b04a98a0e4acce9518a3f33370ac8905bc1a3cf0fd9867287e508728570ad11cc385948b386c6051b6a2f16a5f1ca24c0394204b6f5e35b09fd4b1e484dd274be9277c7eb13b7723c82f9564fb705637cd1620fa189d7189c018b1653e0b3d92b7b580206034500730a322bc80d8c099ccadce88537bd6595becbdd03300140e3d4b01630050826ee7d720f352e4e52ac137a2bf8d66a726ffa61cea0e4887c80318278a69a52af81582dba5ba5236be8b27db719445c727cba336018951ca40e681def963b8e17c79724b97e4399893afe79895903f6727d295f96b4afd023da8ab57e7a5d38e0d7eaa6f712d37009c98979d74ef41255ca8d84afaf178c8418effbb98a56e1eda1b2686655ed3f19369dbcee4d52897254619be701945f9715cd49bdf9b7c006ed388425fd5a954af25c6665bb93c072c47e4c543236fd8167bfabda735b68c78ec4de5f613df9ee16633544ffd66515d8b974c95feb78f46fd8eee7b100b14bc2bbd682f621dbbddf58a251857f3a00f3c7ef360bbfb9c011c7abbeda1c6a6f294e3ab7b9aa8eff337a7c169c647b72da84810b5c7cf3f6832ab4297c4d8c36f2df132bcb77b0dc8f6a359c6cd7a85e5714f327240f4875f1f2ae72b3da13da050765766fbe28dc17dfebf4aa9be25bb4580ec9efcd029586bceb2c758036f18e6ade529201ba039279861218beec5b3b67a2613d165677295e352bf3856806a217328b22f5c2d1272f452d21f9ab72267cf15761e37854a3113cee716632f45ebbea4fb7d19241ce50453b5ec467728f0864006ebfda0b00801adc8a7afa85b8bda9845caebeb253a2f5d70ac430726863963d2056ee1c5a02df4166385747b20c4ce5418822acf548a2f20b8f0907510d2bf2ba0e830990a066916e998a35957f9d455ae1d100599afeff0b4a467260fca4e3dd81d3ad50f365cda165336b9f4c48157bb10a76473959bc0055a0721895faa60cf764ffb004cd8338e045ae18350aaec529a3f22b537a6bbf600854d9ce9dedb8d61a8f7912dc99e4975136279adc295d049f9a32fb468b5a656e72725e90a1288e04ea23d0aae1cb76f5feb2ec31e465daa16d374a08c62dad6072e49ede94b0d542ad7b983d025ffa888182d60c5b08fd1197fbf5fb43db42e9722665dd18813284b9f819fd35c663fcfbaeb53aadf26bb4eb0368aa1c6fab2672597a4dd0650ee377c889cb2a40067a24e098cceaaec6e7d10ec44eff255483720a2e8c8d6c564af5e8593f77987e48c043efa14fc87e28c14e1c5c4151a3a7012c575eaaf20aa60c5558448bc9b164bfd28d071fcf219b674dbb363e350b7372005ac86b943d872e7d5f5e93b0cb5c6ba52830b35220bd2d002234d740634503d488428b4a342f0569adea4a566cb5d27a066538f8e3b91cfe92bf96f1d34b724034da65028d24eab34d229a7455ddbb7ad352ccecbfffb736c1a569736faf025bc25227872e52cedac82724e855ac8089b57a1df3ba9a459e819a0504f419723fb444952c106c10c3a3d31e7357cf1a179123c215d3fb9316882bb1aec2021cf467a274fcf25e773b81c0820c5f3ff1186c2b52e7d295b5024efcae88d34325758533a690b6c35ced5190436da5456da650122e4a4fd180d28dfb614a2ddd728565df82786fffd3465377c7ec644c54deb0356b96db0d8a4512bf6fed561472c54135245372ca2efadd4f3997cf646e2be4bf9758ec55580176bb9b82d0b01a6a3f03343b546812e4d25f609cd6ed0e4d682a5b407f6dcea2b3dcf916449172818694088fdc947d5ab17e492d18edeeb0af0c637bdc147255e351f417c4dd0bdcc94665707d2c740a44368710806dbd34294e84b68ee7af58ce8b9a686ee17210dff8c46c6c3bcb57f0c5f56d9c87e0aeef024a298c7b00eb9381cda4ea3372218d438b67405a101f8a2ea48e8c54f2a1444cce4e932fea81b2dc7fd720477243b3cb3933d2bd97c1acdb8c98b676fd915a4cfa8d2fa7fe95d2a27003458b72d11220238fde2e729695685f0c24f0eb86d07a92cb16ba19702324d9ecdb8172ce2b8a82530b4021bca8e2b439625b7b73cfc14fe4958f5f932ea009ec56d663fe8809d6e50a80e6bebc0efcf3024e00ff4d76aae0a96efcac8bc5cf359d5f4087bd8fad06876882286dc97a42c8d91e2552bef9a0481a390b8bc0e7cb62bc5048f1fe4b85f00e836d49c3aef273dbc947b7b80eae261ae30401a2d33ef90b1fd0f04374be0625b541185082ecd6c12100c33ebda792bcf9ac401eade7ec5138eb533bea0604a80d3fc85531d91e596a4d2af35e69ab2d39766e7246143cc4722c41af1aee6b4be9ff2eb72e1857966f067b4a37cd167ae28ee7157bd0ad81726d1ce1282565dd29aa27788e4866626ab44f21e35e437a39899c49111f6c957242df50f15a5d9931a8d08462024718c52987182e4d945e216bb3b68f5a0389729624501149f95ce2b87720560576ae16491cbb36eee7ab146f4c2b9d5333d272800356f9a3f871512b31c6509bf0a59559302f7394e4949cbd9e38e8b01f815a502cd802d12521661ed2ed8db4dfd8c568daded45f298eb8f4f53cda7e725b0b4c012dfc61ecb93f77b4d3da22fe90feeecdd5e6c41719d75f5158dd71add54b570882716a0aceb759b33c12db85710237d9daf9eca77b358fe629870591143390aef26d3f39273fe61878b4daa1bf0d5bb0d368098626eba3242515ae985272f7bcd10e238933f5b85d8d8cdede944eccf25f2e0312cddb44e83f2757ccac72f15579735a200ed24a951557cb9159fe69ef41830f78dbc63b49a6da17913e72629a74698aee99439e0b3b9104ac50dcdad2514e75b9426842f73848e265e616dec4bf961fd8866134b887ea84e3f77b9793702b7fc53cda74b54666f729d8727783d2408c2348a810970d5ee9ae5eb6981cfe78e066f6dcbcc68fd4490eec72ad9f7cb917e98937c23d438e6496e2d2e1314aea2fa53b24295356775640856d59ba0403940b86aaee4491805850d9065a9bca6be17b10723686321bc9d9146d4b5bb206cfad9cebd0c8f228531ab9214e50651dea69a3e848b03cb6a0828472c4deaf8988874474368f8df613cbb2fd284fd666fcc7c66633dc69550d92a672cf0f5183836425c0238fed653816fc24d1c98a1a353739f328802195eb927972522f1d4a82597920b53a172a1e0d8170dc01c84356b35424bed249b0f5e8555c88a9c49cceb8d68464b9e0eb3835e6afd120e12b7752abc45a8e587ba634636f10a5b61e395bb3e78928c1c1495ef43e3bb7cc8262bd728b9e2a884b6beb09724d5f81ee3a7fbd25ea655d3b950bd00b38bd66994ac90af5b17750f4d256392c6bbb3269461f43bfbeb8dd6554446d85d34967207926df078e7d8f2ca0c37808990b4adae6acfc35a24ab5dae3eafa348186edb247bdc8dba49f146cd26eec72173a217c18e87542a2dd8c00c869d2a324d19bc0d7a935f6a450652a5dd184725274f4dd246491de4485b2d2d74e42d2db0a579802a288dd319d15be4ef33d56d802e409c308e9ca99dfb973956f00aaef93765b9f81d07c4b9331c62e004772c42bacf2b43fbd28fe969e2f88a35ae6aed1dbaab9dfe73d9de2aa93baf6c0462c168a86229ecc58fa08cdd7910de75337ca0cfc72c1ca1115561bb935caa53a70009f391e1efbec03d1c6d7ae7882281c3f87428d14dba4eb77b22fa9682c72abd119ccf483d4598d69aec1c466ed6d53b312ae613b1d61d99f60b4772c9d707e33038299a1c8d4599030d05a9a65608c7b87c5377f86726f69123bd0fbf67246eb89a01ccea6b084e0c346659e5fc15854d6fa5e106db9fcffda91e8956a40ed8c62d435aeb3b3c3f822f4128ebad5efc07a2cfe6a45980be20c399b15e56fa97d2fe23094fd2460c3d438484635b42bb6a32006be0a303240ab0cbdecea07078fc1914e175019457e37e639ad484469e2d67d4be844e181c3d0d337ea123483cbf711a6f17ab5499702070a5bb69fddb8e0b6f95dad8a70206c7f20e35a0446ece6290ab4df61e98846170ef2d5a3e7e3f05405a081f500e51e18a5194e497c2c77ea002dce5d2ecc3dec0433492cb235173524cc17f6f3427aee65e9a5726d8340051bf29171cabb7860039456a2c609e8f6b82f218504d8f99edfe5d74b12a454a93d70ddab7a80814dabc08980103353eb5bf02b5a6051a08824bc6b722d9a956933cb1df95b5336e99a2dafd931d2fba94cab0ef0d2cf28857751a072832ac54c2491d18cd9a9cb16ee02b7f3b3d96ae60b7d0f09db245bcaac5b827222002df3b82b115103cfa8a1fb5eb9a36ba143beb3549d0bda6ed907578e380ccb521398410a6b2593bd03410b03842da5eabcfd781949e1af9f388dac6740727b2d0c67e8c887f91c7e52c91342c2f8f097f5e56174a7ba7b387513b27951720f3fb15b1a0a04a7964740dd9b8bb76e40b088a9214463261664a7556dcc3e34cdc0768d8bface31135409f39064c620016d47179990f99fc36fa9c195145710d5b08bd772f73c2cae727e7b84746daab68d2b0483e86ada3c380c933414ed729767ffeb1db82341675f2d7935a4a0abf7e10fa7cb654a80481b2918a114d3152a27ebfd9e7e5537f8be5e9dfbc8dc9dcf4456fc584dd074dedef948307b56722cedcf77e4932965e5d4db03b5a3148637535ff5f74254cc6df24e0e8bbcad1fa44328a00978ac6a14633b42fdaa0b7efa6afb59e133dc5560f7e52a27d5ff4bf7c9f4517abd6b259e2d2099d5cc5f818eb9462713215efbeb01385ed1339172d70fb8ef30cade6f1f26a75b618ced15db951d105f55f704bf78c5635015fe723e5f8d23dca4c0afc94613d90d2ec9b8904a71a8f4f4640cbede7b612d8bb6720f934b3b0acd2cc7573627da457a91356eafa64dfb8f47443c33b3bf3b189d50fb690606258f1eef30b9d9f5da7135ba5ccbd7d553af62cb4fc12a6d7ef2915a51f68bd33c971d2a5bcd6e74c82e52ef75ffadc0b95a7ca7a227a13d8d248272a6b8e2e95e197dbca43bd67fd42182636e6f17a70f78a88dff93651bc3a41c5fd07ad9cfb3ad4056469c808273b283ae9fed71dcce5d097d88799ccce1487172b2828782eee1d4bf30596121f98031f26263b108218b582974ddc19ea378d57209e612579789043d6946045d30e6516e711318dff15c51e6a7dac632b598e70111e1f0fdfa5c46bcc85b1f69d65c3bac9bfff680fd4a0ded40f70032e80ecd22d0084eab7f1e76616bec5eef261946de06e32943bee1b787d0afec7362f32372a48919db6628de2f1fafe119574623dd78c5140ff9720e2861aad29d35c0e8535a1c6085c239e1858a6a6f3e3e184b4a96d6c2fae182d40076fe6ed313f97d33d0a864e7331684ba712a3526aef5218d5380ee77170b704041fae3a22cb31d720b19ebf8a4d2df313ceba174f6ed9629e9657a8ae8e95cbe1bfaeaadbbee6572da6694731dfecbaba2148f05e7ace759ec28aec56e0a6f2dd38876750657dc00a2ddac039da060cb49ccc4c22949590c69b24607717499feec2b305b376af8726a74ba4dd01f24c97f22f978758cbc897d1bc85cbf4a5515b62e848bedeba97204bf7027eb29d547eb1964e54f49fb5a8bbdde7db22625b663125b65baa0fc72ab13256c6dd3295938037ffcb52aee42f694ddc5a57d0c5cc4f739e3a6ea4544564d44421be4e02449a05fffad7210cf4b627f7fb61fc608bcbbcf2e5a1ac072e2bfd4ffb2c049629b536b122dc1fc11c86d82d2e88290225249b48ea4844e727c1df70201b1b32acb051849534ff8ab2859ac6484c317655ed0e18241670d3f2f91cab2faf5636b787b1bba6d78420d0220ccde0bc5d39ed0cef63a48ae061f79318adfacbd02078ed9307b8bfd94d1c6339da56e512a106a5e308f3ae511727673940d35edd071883e2ba377b2323267f57bbc6818caba9ab9d4e95b03af72dc613bf1255706c586c4f080c8488526b61676c7db3bd520755bf5e2d1daf1721fd1a63fa0322cbd1faf0a4574d61463f126a99e0c715d38e57934e1c6af2872ded226c4135b8d4df545da48be778977fcc00938b6ca5f4fa5db22d118a0cd72bcb03ac29430508e2005b2759daf8621f2d451cafe48bade4c2574b42451397211dedc76ffefc2e41aad13b6c9db25df8d27f69360b4a7a5839eea23b38b8f5ccdbb5d7c6f1011e49ff4b9effba266718b0a137b98308ca72162d7d2e453bf24cce23d87c08a59104f61651f4f5b30c92479097ecee3f65affa2ac0df9af5c2f8d2227517076ed0206db65338fb12897d5d102b746bf340ec9ae669df3d73b72d7a69fb995c032063c7267238faab17b05a0d381ed026cfbf4da5e79e263297278f8ef741306663fcb5196bb44672349ab5cf6b8bd95613ea9aa8f2b1ba79272ced2f8b19974aa87f17d5e751564bd4133d08afe045fb01448f09377c4fe67320dce2d6c83e92cc85f843bc702f23b7232173086bc70e15bf9e2fa12bdbd103bf94f96bf524fe66c5fd483b921a81022ade3e0bfb182f93c33f03fa08fcb5972a96ffab464de3de973e2d6bd6ff729f223bc1fb7692de1b200d6f533dea4934ad2a080790c777049c58e181837e1fb5973ed2df5ebeb56eb320807e91b8d6572cb0cd21714e80f5f5ea99890ca672db00accebf7c842744f36900f8b0535e37206160ab737526f87f2e3995fa45c78e463090e4cdff8c6d6f9d2bcd7e7afe472e739b754fc80b7747beaa4843a2856b4b4b0eb0f12f3be3d88fd5d4a22ffb97255c55302146770ce5b67b925c1eba1a907768d391e827950da45841da2d9997291374e1e2482a06299a7b5165d308641b80276551a936e9e3f16475d1980b9722c150fdb722ff40824231fb5c3620b312692510c8ac37a1ca9cbde3528b9ae587cd4ae8ac77e5d68d0945bc14d13033136bff0d832076bd384cfc7333767747244d297ad21f703ee0e13b2f9cd531b9112aa1dbb8df21f8e5ac0c6a356ba3072a32168a4432c97571207163613d8ff6f9137e07681012b53958a117dac3180723ed02297899f5a476f85c369292daa5d6ab0ba92935b6b039e1e0ca61a72bb2b53b048ae80f4b8cd992bd2cdaf51cafb4d6217c45200d9a83730adf84320f672d45e62948f0ab62c10e00dc6e873ffcbe317f9c8feb1fa068340777b727b9d72c9e8cfd6eaa52dc47bc2c3fbf22d273f79a00e278f0767e473d57c80fdd4a57218b008cf86e96e437ef1971aadaa7ea8df922081c82544f688267ea1514d52726364c675d3e401a0e2a679b5f9e4d78dd53c7dbaa948d0c14bc06d76a9b85a1f90de50de8a189cc65b9ba3ce4fad7084e9c26ad5be7c488f6d2bf1bd67f134720b6d46cfa99aabe4c7c4238f31e1a68ce027b4b63d5b7cc6ae93e727b706e272065a64bb36258f25bd3bb4f694132f43ba77b5d0c9284189c742383a6a9646411d331bbc27642e309bc8e353d6336c0db05d793e53a9ce7f8ee8645fbd388b2aac11643dc85a68f71e89188384bbc5affbff7f89ed035d5fd908b6a1e8614501beee0dc2b44a8150d7a86a7bef59a4451a292252542767d681bf4c0572c74872d65a5aed250ec9fd121830861013aef6110e1674f2058b948b9982d2826ee6727fcbf0c8a431423ad09680a64254a98e25aeaac49336ff6c0aa38abc855b7e24aef35038fe0f2441dd29401763d38160fd3301217a10f814379a8a284b0b44728248ac1fdf12f1ba7901938c1acbdccc15c4f9a07ea5073c2729cad1933c555fb7b14d0cccede734394e5c9669613f3fdd7d571f27e552425fad5e59913a3572978e7709663775a1ac1284050565fa5b7d1b3d883e1777bcd31e647a8e07c062bfb0433d152222568a0ce55cdfccee457683ee68f68b007f12b284761bcce10bd81bcb7e1403c9a377db9629cc8b7ae5cffe1c5cedd7f61deb06796c2b2ce172187365ab931985e94f17daceffaf7d9a6ba489ebae2fecfbdfe5c745fcc54f72ffb01ba64db6475fa879deb0717a4e9447cdd72ecd146400c9e8d3acb177fc725daf77c70dddb1cfb2b16e59b96d74b78fe4d50638f248d557ce4542a765c66d8589dd2f00138ed2f78e1df492997b4cc17f01edeb5efce6a11dae151136a3683802457df862018886a084554137153bdee8daf66d48471073f1bcc68fda24724fb417d99f66b2945c4ba898517a07e9500f0ac7c2cca10d8fc3c29175bf4524d3c057381458baebc4a572208cebdb25b1f231db519219b2c4e8a50c0dad157294d4416fe0f19da024edea0567786712a6202fa9131c5dc9fc05ea8dea98fc241995416c262ddfd1fa57b73a9763809785e6e2997a558930b94d7dc798a8c1720337e39660d9313a7f2355310d022ab740ef7d3b8894f6103705ccd8a180e5725b3ad59defea91afcdfb938978fb7aa301a1d068b4b16420582282b4d4c3754fbf1823b02dda55e376e59fb65c1d5106f3fd64e90e1df526afb7abcd2c498972a2d9292e4bfa0f5ddbed77edea0d976639c913f26be680c94e974a7c260d9e6ab103349bc6d879516d7dd38427ad0828707737962cdc27b5340aefc242ae2c558a65b4849997c4ee11f6e12f23e0b7634a76f741a9ef617a1c4d8e58b029c3729f309662b2759f7c805d81179fbdb899cb21e04659139d712c6a6b4c4e5aeb7216acc7523bf959e41a1b3f39f68fd4b161b1918c613290d42892dc25083c3e4a183c762fe0ed7491f456488f46591fd83ace7d7b3a2d6837d393d45d635c497226cb04eff8d67affb12de294e751bd0d9f3d3e0d3da9a2f59c1b73fad6f05c243c96ac99445ff5ba4d6bae39a58decf6b87724593dc6a9660e0424443d4ab3724f6f74560f181b8177e95dddec9f1ed92e96a783f517dbd4dd95fc5408651e7284dd06ebe3cb82183994ae42521d5b395e42d81b96b90d349756bfa7c92c011b688265647d30fc270d12f2079534b6416efb35a71b5f9b8b4caefb47a840ea51f224231d4832884a5750657057120ca9603fa8b4063eaf7fb409d67d26c3f972224609428d13829dc624b4b592ca5acd1b2c6ec56abfbcb27ba681d60235b672e843cccdf1d5a1a9fec571455e47fcd3736764664cfeb725d13247930f937072132d64cf3b775c18b333f26b063657e3d2ffb1d284f2b785f4f408f6ff3bd025a914de36a1803ec1438677234cb2c1e3000eec11c1469f7f79e04b56127db067dbd4cc31627098837df91c7e3c342c8ee0fff3aa3884398143e46641f821057222ff7cc6660420bf63833a7fea78441aad6524d76c330236414036085e5a57724691f3687e65d2000192b5966b0de2802708a322f63647039f632a3227fb8e47d3a1bdb234d5045867cdf8cb9707a0cd2bcbf731545b1d55f1e3a93af831736233a4701f15b03c1a88ea3af930db899aed503e6907db09d6ec58e39cc93d196503298796aa0a7a407c9333e9c77790fe7ab88d88ae9725eb05d980753aef6172dddf5655f6aacd0e055496ad049c0cc44d136f088956ac3202557132f8384c4255bd1f02c26e11b9518ec110ca18453eec8ceddbfe3b4ecc2574c8f28df8be720a71d5c811fa9fd0b469f088c6f64f2bfe79bb7530bb1cfcd74997d3be9bcd72e0e2f8914f9fe081ad787ff3aa613ab9d579fefb1f68a461b789481bab1e9c7212b46e921bd2cd4d38c4b1da28767c10da0e66b33a844861dfb2bfb67994691271d9a8f9deb4a7abbd224b50a79b0155d700583a406be7e855c7c806e61aca726d283e50e4b1d305bf33b6491c6492d607c7e4e6f11b20649bcaa534503db07261b33d93d2f39ef2531d5e3756c6f709014ceea0c76dc2cb6397667ca2a40272d2beaa8a8aae47912b52dfd1a8dce609767b7b20615fef6e708aaa30cf013e31ea77a2230604f48a3455c701beb5cb3e1ab66c2d3304749a8f768d5c1276bb1d519fd9976dd8215e925da892e5f25a04db15839e9d734da6ba18903f4d1c6632fc7835d3184a9feb1986a253a8e2453031bf1a79f8a73043ac3dc5c5084fc23668c48e71e0b4566dd57e45e0f765c350948f72402c2e32ab0942ffedb42c4a72e6c2d064ad4d0046898a3a2a3ddea53f2c0957fef10ee5852143adcf53a23e72d99a2e5b34b55c37c3546be4a7dc067b4a0b43a1c909df65f6c00c94a6b98572508c2cda3e4385069510c06b1e724b278ca722c367f37314c6fd9e19d227f27271987b8ca85efa8300eb3b36d50a29e11836bc61d4d53cf6f43c1927a7bd4e725218ee1223a83c326d8a6bc80a23894328e358b7fffd19eab72b35e89d4ed216e96574a497f5685c78335895ec866298a9a3e0c340bd98c6cd84a699ae6d3f7251f5bd140c2fe0a3fc87c0d73c7453145c4db013759e5492ddd7352894daae72c1c585717be2cd6f02b25056bd674a2ac385d563cbc272da2836a32ca205a972a77f38e178ddaa381db4661bea4d19fee67e7b45472a855888aef8fed8822f720428333831990dd28a4b056b15bab08a5ea721c4f36347559d283c7f358e2c424650e290485a13f7ca4f98b663a1b8dfe9197370e5b87b3cc06f5a166f0db972d36a45dfda283e421bc03cfb5ac7530c72fbca5e7981f0c77e9004609b5c5d72f76dc802b2885d7331d3e45d92e1f2df6ed4ae8bb19e8ac4f0bf2800e7f606724a3c3340a741f488d176ae151bb397a73f90d4bfd335344c30b997c7c7258f72996cf840a4c4238188746b39e01fb014ee390e4b145d883a123fe60408fe2f722de8a3f9d28122d985d4d8b828160aa8a37d0ae146c49dbe27a821160a1cd7729517ecd36e5f618aeebdf370c69ec2f75cda6a4d613e162fbd8941f0e9c1a16e1c7dbe3df8cca081219a0198fe535ff47abe3059d5e5701ce0157e14661c4c640875f60e8fccb37fadd4b951d30bb8de3099dcc87f05192723827e1341df82300fccc0662a214003c0e39ef29e46c9a9712c31c1edd161ccec7bb8730ec5a4722e35d93d27f1a36c53cba7a685eef7bf78734c27ec0e806c953c0adcd7ce407277bd8615441cfa191acb440dcff29ee0664516f197b2d2850c23786d89c0fc1a6c5a4acc70006a9d17f5fcfc06bfd217fb75fda4178dedc0c5586dd607e0b02d8e075fdf32f92b2b9285a6cc61379f4caa9edc17da6e50a20e40f804f274245b8af02d0ebe62a94baf24660ad58511066df5a6b3536955a2d6821e9e91ce334f6d6d19f3ea49127c9f528196a0396e37679f59b0003f0d8dd4a8bc61718f2b4130b3ed947065eac056f89ffd32a6821a615c122e4657b426b0d2042ce3071632aa85c3043a8de96c4821ef513169388004c86d8b83e4334caf8fccd3909293728a9bd797091b7c72e2401030ca980f9a18cbab7be9198deb77470a2445add6724db007317440862ca7ff2e9e1acec9b455cde3ee79145e939cd2f9a79067fd6460f9104b38702f6a1d66ac7f61df2bae741940f064196932d39e1d7b1c98fb72d3737990f16181eb32e8923bd4124900bd7257de86ac346d917e2a4b09fd07724f2e5abd209daca9e53b6c9da6cfcdbf21a76dae74d4e25bf07886d695eb26474e753958ab6972fe0f2ba4dead16a346dcf8e347fa2e964b61444ce32fc86272e39228600fd5cdf920df396122b1f2541b1dea74260c8936447820664d4e6112f836726b5e05b4727871df645de71bfbb05681d018f58d369a86be6656b9b601b2a67028b5e2804e059e4f8850e641650e16658a051325c3938b09b4e30fd072b1bf431014d45e39d54ad95a14507ecbcbfaaa9d4a0536174ec53e58fab0c90e6963906a3e5002e6ab960d770285b49ad8792f8e89b51fe94e3996bbc11b4572f7820e1c5e2d6aabfdd788cae259044e0238b87dc64df415c160793bc18aa7158b91a050d32fdd39e448172f2d517eeb03b17d47e5ed8a8f2e1a6eeebee0c17221f2e0dbc672ad91de3d8943b48e6911dc5cc31c98bbc92e92257bd9b32f977266b5e22efc41d768c7616f3481bca4c7dd0614ae268f81d38da25c540deee672cbd772f4f75827bc0b006bc38b12e5b18111c7b122fff4f2a6356bf9980784669e34383f0acb2e485c45bd775ae1c3e5b05e92eda135dce8ba79bc853756a2262c45797a43d7d6c477432b55ee5e549be7c95d245e595373a6a2ed84b47e48262bd2478d1bb16640c6478d234c494880e1c47656d494c7c5e3dad3972481af72a3ebf99404b4504145a44d14b32f1e68ed87452b329e7f701783482f89d82202424f237a096237b7a5d81bcbc0862c2b18e1c0fdba881d2fbbed68380f598b727dee8c2d39b9787a8895f1804fbcbf936868fe8984d77c28394f34a12efb737243bb23eadd0a79b68d3eb3a3008cd06f544ecb035525bf5fe0d10549e2065172f63cf482f8b411fffce4a948fac8bcd2606c7b2a703ab7048bef170a4f6e6e725d9f17ec71f2c428ff8b4d9289cbd7d77d372e6a7a9d77ee0a37f756593e7972ad762fe34394586c0e82d5317b3b3b19598aac980efdcc1d2ed1daf205c6862f7b4be231fbbdc274e2568e8a0a6893b64a326cdca6d5595840d70bfa6607e74145e138afa6c611077ea0974bdab9cbbebde7e68bcf2e9bc92909fe3a7dce7b5440ed851fa0dfca239dd4303290bbd45cbf3c141c3d1a3970b1c25c666007ea72100d0664799c120aa471a5c8632724dbe70f35a85ad93c00cc01cec4810c3f317d4ac9029318d8f521f474dddd7a6e2130f507c20f1bf1ac8a099829fbe1c27286639e6834d7d7e613732b7403a96beadb395ce580bceaef7dfc6334769ca2725b15c67e114bd17346c61bd8a8413020f3bf3cde9bd5672552d28fadebc848546eb4f12ceafede98c791f3835935b66ebcf196604b96d70db57443a9be5010720c943e07b340f9f756ac9aa5ed4d0951c254bd172865c498f3acaba600e1811233f6a644428943058dbca85e19e8fe013cacf15324a54be3bed3342323536972ec39cc2229b53d8bc0f5c127cc60376e16edc4abb67336b662b71e7f34ecc372cd7426a6177546b572f84b58ad9af63d3fcee3be1c370fc03028feb5e5788a72a7bf45669c251eb11a5a3eeb7dd7c46e129e3ca19a08ea57e9041e5d09fdca7244dd8dd71e53359ef23121e671daa4b61460f476bfa9e9b21569da0e46c00d1b3d7ff94a2d95336f7e312f997b8f3bc044849bfdb709a4dd33f26388b52b0d051cd80d28799398ea9edd7308de07b1509adc63552d858398a80b6b2f3cdf8919934fc65f50cad9d3afc482a1b5bce9de47ffcae312081333b839b6f457a26537ab60897439670968f4e975401116da5bc573d62a28aacf23968a90b2259d8b6476a6e5520f7395e103c2b77c1d4cd967ae9c3ceac1172b6abf61f55be38b8e3996f91191eefe2affa42a4721e5444ec2576ada91583f4d9405df9dfa69544f191b4a0489c44bf967f37931ade8513a75349502e03d2eedc95b5dcde80c0e3f728551c186ac4c297fba1949e08fed140227d83b40c512044d911d2de26fc3c272a156eba25c85f2811db0b009e88909d34ecbf67fb153d743a6ce23407f921d723f34150878766f3519d5f8955c90f68bb4252f8980c1439f1dcbbc6fd96405726c6440b59f6fab7c757f52f37d5fd9c0b5e71308f9206c8348b93cfb8245a57273be3def689f77b21ecb2d41ead0605f4d6c8ac1a8f6ed313734b49617ff1031de377a5e9308243d1cd8c52c585e20eee77ead4dc7e31b8770fca0f1ad91ef723147910ceb13e5e5dc668293d007ab9df4264db1b4be7cfe70fc288124fddc7269fa9b679f3cc7b7ff10e285f1585bb49cfbc8f1b1de0f4acea75ba005548d21437cdf78cb98a8fc9d2786580e41f6a207fb33fa53ec046055954ffbdcceb7726e8b208d5aee093f2a8efe6044dbb6825e7679cc72587b85a0a57a6510d46d72cb842a34c36e7a500a28d6da3dc644a15d82c5d63337e2bb68009c2cd3720e7289cede2296f351ceffa01e8590acfed7ee56e9869a28fab1bd127f9541f391721bcda4578be753bc02369efdf32286905670e92011e05eb2257fa689ed66ad72f24ccc0b9bd95fc9e41ec0f89ef2206a43bef38d3a09589f5ceda73d5a42ab72f4a3bed06ec06854df88e74f09d9dd02c20fe5e2db3a60e19be59fecb54f471b56599948f7763c2d4eead34645d2ab0459cd75ef29b09dd7b49ee76b8909635650f5bb465587dd21176acf23f8c1600e09796442678ef01d1b63c37b2c9f1e720b85fa54484f0a0d1844ca3bd0bee1c63d8ffd0f830167876ac3c7ea5b34be31850cda5cac75358cf67ddc3621d6f20526df0c1e9f2b0dccecec608aa05db57293779260106e3806516bf43ce959811482a4d2097722b2100c044d497e869d727cc36a84edd2a0edc890c734685956b2b8f4e344dcf58670db281004c8a4a8727c741fdefdc97041988ed14e20739e73ccd0f871ea096ada3c2479e7f4047e7250be7bbe10cf673d6495cfb7a42734235a75683ce191966879ed29af491eb839adde07d79893ac03e779b953a346c997af0dd2c4d4b679526543a041dad76e720fdf0c344f878d381c112bfec492d3f15690c17de14a052ad8c1d84a367f337260f2ffc4a107d6f9ba4a2d7cc2a1cf3f30d049fc07c858e3b93f2335c4eedc72a8bb92faae53901a20924a99173ddbc428eb7a9092277bd66f721bf8c348de72da46bf4bd329bc68c59ec58ecebc666e119a44236ba13296cac47b1345139c14a0ab78dff29aac60893ff9d417b7efdec0bbe7c69a74824fce1b91f5114768722ad2a8723b98e053902218e67ec8ff3ffce988f62c8c29fb772f658e4bb79869da1f207a21a4fc468662f5169f3e6f208492ad8d2eb44b181713b62b5656b44292be695144104b6d8dc3d7797f31019da2ba18d7c73ba6f87f1a79671ff0ea72f37bb0667e99deb10e3171ce2ed58d4df3b371214a621010a6676dcc468c2a72d10022ffa46213c2ad6d1d9e8b16757275aa8d1d214146d5d075ff892df43f726a75dcf5f59d61993c587240a9a66023c14e44e2ea6d995a924f4c56cfd442724782eb25ad75dcca8a7e07de5e5bea9a19c21356d2afe85f4164e2c63a442b72607eca5414802ba4fd63890e5593f679c2be6fad88253b2d889947b3d628446beb3dcabfb233f6434388e0f717285c73fe6d8fab656c4f3dcc128ecd7efaba7240d695ddcf8a909714976deec63086371f8641878927e1d0350bb981c2f54b040629e73b38d7e5342aaf202c7466cb0d1850dc0ac8acb727bfa3e2e4d4ad5d1b37895ff7519adeab2e36a4716281bd4204c4d4c1d617a8d7620f0971a784db72bffbb5d05e8449b63646d50e2e0a987bac0aae98a29cf3a7dac7289610f541035eaf1909fb5bf7347515953806dc6da5be8928a73ef2ee84d2d88e1cdd04527290716f9684be9a5ef30d2ab892cccc96606a41824b432c02bad224f274d73072fb1e60107d6dc161375abca81a8eabea9f164b7c1b7a98266b061b8c5d64ff04f051130cffc67e2d7750059bd4cc748f886da5582f838d2c29750abf45d5dd723d2e4499fc76e42fdd7a35b49c8facd779b4c9da47182126a8a11bf5a187c01b619ad776ce7eed7e969ce1a4ac772389baaa94109ee2e6fd870c1dfd4d26ac4f99fd42dfed3a05d2c7b9d1855d1d55bea4e6b5d68ff357549c36af6cba4cd5720b27e2a1a48d6558cdf7f75c75f5eff8629b76ebf68ff9824ecc1557f697373f01d213b621b7f7686a2cd4290e32bb798fcd8b236d116c3f7bb7b2eabb33ce7250da0ba6bd55f112b4a902c164b3033b59390c50b6f8f7c2d6e20f9817789d35177d466dcbfcaed07c10eb0f59fdd49fe5e070b3e7c37458224f0bdbf8b978727f08929655b875869c2fd9d334fadee05f131310792ff59bd437abcbb317bd72c4f3f373068350d3757cc507a180aeb3c1a865e6997d02034ec53a42e1a17872978281a60b6997c026d769f15768d2b6a8efcd1795987a04beb97e1555d7da7272ae7c40e83389164a267896ae19110e08c8786995d5170b78a8dcb1cb72df0843621e2c27ecbf891f60495f78ae43a7868519bc51b847f2d65aea6cb7bad5721cfe69eb89438c7ca5fadf199e79b025221e5cbe3ef819c34dce481ef2c9c21df4c1a49471b23894eac2190df953b7e0ae5fe78708de3ae1d93d566b91b13e72c921a0437b5a7c098e0ae1a3e7be573a0acfb841e38782008fb87ea7713ded490402677898bc9f2d1b8e9597efb31a3339149f16c77e750d444d7f0c807d787227b79dafc8727aa7347ef4961aef3aa902e1565af3818844a8d0c365ad75a07241fdced16f17c8e887498a33e26273be4eec9462bf02e2bb3f0639b50b928772b55d8f02631ff9046d7138997779fe3b3f9ba5b61b80d8ad4d7b4e8d60786972f3b71f6783e58cdb3ba4bfd807844e494ed191d412f1afb6f4e99013c9368c726581af903cf89b3d3954f9b4a00b81c7af10d3b5023ce06b40351e4f57aec67226e9d757d74a179173df6e246f4889b251564b8c6f205ec54ee4b2fba930c672afa5c9180a190d2bf366be6d9f5e99f9c99f7984d18cff68a21b4e4ea533a9537d60325652e5f3f8a7ee653201e20a7e553b62ead00b396850286799fd5f6a72cd1e614b83139cc4810fa4b11df50c76b2014f69f7bc2cfe1e568cfa2398ee729370362806d8af9576f9ba053501701d65834f4a48ecd282038b769a63cfbd14d3b3dc69bdadd72f97ea62c39e18ab22b1517a7a2dd0774fe085666ddc8d0e7208ed9f8f5e675c00b7d6d3cc8564bfc1894f3216cce341ca75f2b48919df9f722d053386d29c747fde67876b08c28d6161aab9cd02a3e993be3c3414097f961abf7079121b7c8110b618331d383c2ef9edee4f954a1fe21a6b10597867814a2f0f2e777ea2d4c33824dbf3da9ffc9cb98afd4d04f184a3f927dc0670b7f2db721870a97164f48ac3210d39ea5fee7df3cf1d406d28a4b35dd750856c01bb1f72182413fc110c2d257cbfcd430d481433d7c26e435050c0481beff81268a8236f4c42ad1ec7bfb1628aeacf7f2291065da11e7eda26fd00e2bbc16953b90242723c88db2c62ffbd2e0bd3247159f8d7498858ead5d88d8898ab67efc357720f7291e2184e78d7a6420c8b33e65d52efddd79b3d3710fe96a85bcf8d6a12c84217fc3f4d483b066f3eae552154413651bc659e9690c636a1d667e045bb5c5c1b6f96ba6bf37e232e76796df013a1590922887f20d94587f30f119d5b62aa5747728425db30ba8a42dbc69c6c2ffe0a41ff4d0017e0b9db928ff45c84423528ba413d414140baef2bc93c84711bf656aae4caf18dca434a15af430f113ff8e8c80459a907f8649829919be9133e988c0a1866e2bc192864018696d762cc5888f56292a25289f86bc981a5a30c1202c6dee518245accc2c4dcf7d3013a9e789c697224d9889df6c9af6418d9c04e0d7a01b7ed84c49dd19d18df24a660665e532646009dfca1f2f1a0b3c04dd05683793f58435c39960d7fd17e13f537023328b068d6d2c837e48623283d8e62ed6f56688d611a7ab689e1045e66c6948092d40b01700c120298673fe2344b5286128746645143f038ba86aa98471d234bbd8ae072e9446f7f06bc89a83f0cab3a866890d851b57a065c10478f53452e2e6bcbfe728014a3f984b839893e4053622fb9b05034ca5aa333a0bc85b43e93b05d121c1ca5eecc64de3c1be416be68fd16d0570ca91079dbe821f86f074617550ada4658dad75edf7c8a8254f863a1cb376c41cdd8bda5ba19625c8b31e6689f44aa5072a5c1d945a8f8c513dcec4f9b86a5d839e728e573072456732d49347d61aae072eecc039a392a214a128f2332f33ca22ac895dadf2f717c45f1c72039d7079a72bfcef62c3172adce9e037b279d6010498e31408d901094fe03df1fb083d7a47253797afc59d0828a9b357b4056c40952e22bc0dec6d286bc1304a9907eacf76a23b93c7cd7cc303e1c4de6b9f19fd0347d8d9f84f22d96ef988cd28780bf3a728da21d5b8912b41552836c075504a35a1c61b2cf0b98dc6ed3922a85edee5c72310889da7525347c2058df0765f70f64b7180e1a6ea4102ffeb02848f76b71725f82082e2f8199c21ad831862c600100e8aa00b35c662dab090f3999ec637472cca347005375c7abd94fdd6d5a2967e58e1d37095420bb3506db6f1e97f0f77246271a7db8473e20d6090f3c6b1d5721189bcaa328afdac65370f319ca2f482bfd260708b2bcfa4e9f813b235db1ef468a52103c58ede47de6ed63d56841cf05a49bc0d90c1b26bc5f30a99644ef1be752f03ab938616299e6ab307942c6697201e6b9775938cb551f118c47eb86faf5c2bf0bb2be31e9113a0fa6f8d46bf37223f2307d984d739a99bd8ad5e0376a79563f400d6cc582c8cafb0a7069d8455f4fc62078a407a6dc0a49347da9561aaee26fb208fd7db2b051faa70ae87e1b72d17524946b0c07233dc8805b244cbf0732df194754bb03998e0ed51bd6d9bd47b47e9398da498e5e835ffdcd021f372278d87bb3dc4e4bb775384223c6c59d458d9a52f3398fe39190d475b3aff4bf9e91d9cd3b17498db8c7812ebee004fb727957a2d8cf2b7122d773ce8e2c0c4d8b4117e1a54068d92bad3db0aa70d0a60f4e4fad2f5f5faad85fcd0cfd42852735f4abbe9efedf19cb101f93bcdac572725c30dd3a710ab19aef4faa5c82bd2aadff3c405ad435679cab96eb961fec3572fe1e7d4fd215135c85fde141d53e0e0d1a0b9ef861fed252219f6cdbcd9e29462405d67852b511a4140461dedaa995870833314a1ea2b77a85194a5b24c9276fa067b90e1533bd176ba2cec4d149487fd11de4bd800ad9ac6de49f66a7fde872dc46e082074dba82c0b441a404a729fec45e4a3ca229289d384d813bc1348f72169dc1bafd6a8b647f6fecd6854bcb5a9e72f2b2ce1235a4b23ec94f51eefa4d588d155f05c68ae8cee60019a4258e5fde02c63425ef552d094b2862fa42ac1e7412761e6d8a1e6fed7857e28798775d70555f1a0f1077a8ed2db92c84d0f872e77e7d490c31c6690c1dc38e15c3579d76a510fae3b24092d58672213f246d7222092711842f5c4733184ba8a267291b60d81cf896961288f222d3c5c4bc5b5593601568392d2b292f82f8fe1dac5629d5c4d2c88f1e2f7422b380bea0f2cd72f3c2efe54151aa4aea1b2f73ae0958535e32bafd4929e9e591549653dae6031335b358193b4db8a0a4b9019b41828b326c7de701304b5d09b5c60141e5e5ca72adfd4fda3f5c8e5f8dd34819eef9435e04b33151d48b0acaf5eda3f2f1d3a372273f52e85e7700236f708602cb9e256283688335cd2d3ce69a143020b1c07c0029772ebf686477b1c91e9295e61a13bfd0d3224524cbea755b626c9b1c237872fd832e6fbb08dff981a67eb39903205e6c122464289ac750cdd0547f7ca4d52d8f4b1d32b2c39fd01020ea79e460eaef65d44857bef93f8a21ce7d7dd941904e43d1240ab6361ea8830f04bb4d2df4658e89338e1812a82840a8d44c2dab3b4048442994aa8b7bd2a8cab8e10294f80d4bec92b293b4639466c850627ccb465517fb78074d613f48a079d8f79e472ffce4c82d8b076029d641e84b4fb6eca77289c4b93866a8b9344f823496fc404d31866030f7dc2bf9bbdf12609891ae2972c9bdc763a4090d0936810365798cfa67312ac66db830373dfdfef5b23df06c3c0872f37778eb09272d5c6757e50f6ccf58120a1bb1a0eb2197532f74bf9447721e7379d19cb18fc20507d675114ed9529e61c14f76c5861507801101852f5b72ecf55516917a3cae68ff80b0a183a54c200b34049f81a3f6a4ff19797a503a72d31df18cc52bcbb8fd7ca3f365dde24cfb8d4210cfea7a10e2a6de75e3339231011a6b78267ba67ff7a9aa9f3782062fa55871dd3c478f68d4342399ff92625b9a78eab27ebc9b8b473b6bdf10f867e613b1f843690fcbffa84db2677de2314116d8a664685c2df960d38848b7d4ee27044ba3d15b87d34b96611880030caa23febb89f2dd1975a6b23449c955e3e1d01b2c5119aed0df0e7729665ac997455933f81d6c753f9ad6753ea1c950f8c07b5df2cf18aa9273d09b05aed5d3034572e1da340d4e16c6e90e70641d827f7b34a558f0b98605b187f282ecf3bb3a151c69494a327301dc4addec86607e3b34471df8089d8c3d595f75e6aed05a331b72ade7287e98a8115b8587d01a11d7c9f80f09b689db375e8fe274bd66ab32f4724cf107630ad1905bf8e5ede560e3ce0f3b3dcd861f218f81a10b1cdcfa1a0c7210eff2516761e2f3dfcdfe65efbc542438ef7c3abcdd3c9e97ef5d800718bb41d74ad9767759d72060abd33f670b3212a074e0fa585af1eb9fe54b033dfe1d721290d9c255df933c934fca9b5a62921dae6c911b4b65d3c2fbcb454561229f64fda9932ed7dedc1488b0444822c5a94d2e6257d94cd124453ce35f0be9f8ee723454f2008d6408812c75c4cdd3736dc050bf07938c690c145d5fa85724012e728dc8d9e8523d0bdeb86d5087dfe6da7cb707e772fc68a17350645c7e603f3d63295f38f6b63163ebcc1bd9ec0f46d9e6816859a24d46f01eda2dfc213c3b81245ee5843a8370e1dbe2354ed918bcf769ad997c8b77b4a05ff980e980039699488fc780a642197dbe7ba9cc5325b607f6d5145a2e24bab6ac5621e424fbcd6d7294e675ebc47a100381e8b2ebc0e1ffc22f6950bee514a7315f2484211ef8274e8e30c878d754cc543395663c979e010899b97c48526ca0e7077f3cb1d69d6f72c3c611f1bd33f431a4051553fd990a55eebde2342114cfc7fab409bdf3aff872884dc8e01f3d825064776af7675986b280d83a4cb61fb1ce1d317b656ecb981003f885e43d66059dbffdaa278dc84c765e858fbbb9f9def89bc534880c72a159925c5b3ab7f81f7350e139f3e16a472cfb075e58fee509bf7b32d4bd1a9b7472e8344145ea7fe728860a40614c9e2d5fd4a8de13e2832edd6af6512e984a3572dc167928b4eeff9ee64b587ebd16c044c5819d63323543fd4bb08c3218768b4d582a28e76cbdc007901e080aa3e9408916f2d027d5024f7f165a651b1474c7729899bec5fa9f373c7f451b4073f65da51c2b358ed804787008c4dc7fd0b7130bd19e1b293753143232b60bd5fc81a6a291ead8ae479761fb94c5c6e2742e0d726558d7a6a976f9cacd85783700d01080db7240e08c33601986f46f08ba7ad572bb9437b003a48a4291d6a5ad6131bdedb42004680f0f843e1b03427436110713c3d9d6264d197b7edd42df5732a0734d83fd050ba2199d6639e3f46ecd868807ae511b05914c235a80cf808973291d44019045d748aa1d4c0818c30f5c24b0720292de86ea6901806112c0ed51afa7ae9e6935dbd2d5d49dfdd1f05e653db74bded26d0043a3f0faabf64f9327c657626d544eb54078db03d03768749f39da72b8f341defec7a55a5621439a8de233d05f724b807afc160eb11cda5e158fee72837571fb4f13a91f296b51fef687391c1427534d8b74a93d1bb30beb160b3372dd7714052efd7d24d8677277f024d2cb993cbe6638434633493dd3a249cf803a65e268d58edb67407e5abf53cd2bafa5aa478c153930090b4ba6ff00d61b6a7289d46a0272969ab779bf533d8f7bccb0c57a62e2d62c519a335a727cb6945b72674f85e0338afe20ea3cd3f0df5f43054320f2f0b4611b2438865544801270475152abdc09c168aab96602c9a6e2e34c68f68312f07e6d33bf0f874d0dd3d272016fdade583409b71d6b8c155f29919f620737f47aa7f452d583a8e004fe1572cd929b36320827a3f7f0dae7777393be9b41459010b2da300d04a67f82e25a72a273d4af80221fd3b9c010c9f11507612120a83fc194f20cf0f78512b99c2972c2ebfd57a24d8d1a62062d89ea23f201fec566305367ddc1d9d84caea969b072ff397daff3bd36aaf86a587abc339e35755d3ba561fa25bec366b7020e31ec72eead524e0af7ba2e8da2b77781c2eafa98bd3914fcfbe0f4d81b6f539cf12e1ade0c76567143f4f6641249114fcfc1e3375e841a8cd0afac7df32c8d25cdbc5809c4bf2a058c864665cd08caadd9bdd092ed223dfd5d349ca9363eb089c87272ca095ddb54da6e348aab65ce0b9c917accc4402b3db87525ee61d11580c95e72586f0be579ba380d6afdc7aa0d5b46cd8ba7c0b3220fb3dd765d43c834394037a23309be43f3f59bcb74f3791eecf8663de9774bf30b7fe6de8a5d96dbc0b5722eb20dd1b33517b16fd2ccce64f37ae957c55a7f81fb9215d2b2ec6cfcd654400b0beb3e1d4eecbe0d3883915be0504a358d29941c3897636099edb25f748372f03b7fd673b9ab9ccb491abe0bbd1dfff71ac984a15cc7af5189891155bf9472363211716b0f27041da18e8f588de17a3c27f68387aa516d8e6b00db58e97872c33076d11bebea47ec697863dce40ce63c993adbd4823bba355c25a47533fa72b9a0950f1abe97cfa3b8eedd4f1b3419dd670a18809a32b42eb8a6d3dbf06b3c0922c382b51332b6d910136cf523cc6336354ce386f47ca1335fb0bde9bfb662eebf5c0e99719fcd7a8e57de34eeff8963b7379a844233a90ca89849b39273712e3863151c2d0c2fdd6c11f678d809478f3287ec861de780b2cc1c5abb200672de679a6e35e2db389227e2dfac9222503ac5367d84a8be2bb1db3ada14bb3872af0107e1d104a04bc82eff47f3f9e1caf108d54c33c3c1c62a219ee7e7a3af3bdd7eb8dc59a7320d5b6d9c36f486bb7bf8c0fc4e9abbf1e19dabc267ed1ef272f8f80748be4f3a9337d7f862e6e3e4073b054732b8bc746267bd71b7869616697954fe9672470758ccf35d72055be2eb85b003d5eb034f04fde187355094aa3de2453fd5fb469d325c40d4d0de261335025e467925d11e56ca5d15e6910dc372f190d6498b8dd92142728b78fe082ad67f8d51832d2d69c9609d842938654d1f229efd225ac68067be5a323a4189e50857d1e118880e0e238b6b9bf679df9f72d4ae2109b7f9ca76d2f276fb55b1b28559ffdd60ad792cd3369a0cb0501f9572f409532cb1ca4f363d27964583ac9a4e923123a95eef394f8a8083eba026307278166b148a4b20570a33819fe468aaa2fa2fe57dffc48f91d9e06705ce97f3728b291088d003c90d5b7236689a09cef3d34c71712a3c5ed5e807aef14b3c0072ac9eb0eea39dcf2ea8e3628aa9633f8d9a7fd7a982b1d1de510071b731b5ec4c1a2db7dc3b4bec57a127c0dfa19d2090080490d8c7f189d77e7e6a4d267f7531aa14bdc79206f623dc1781adfeff2ccde2fe3cb87084d691058260c35439a02a7b1c5b0c19aa5a14ccbe544f094f0ef6d2f54c861a92e94926f2f6431e81b568a47c3803c4dce72486789bfeebd5b79852df088f0ca2e003a9dc9cf9e6a5922b25ac47f7fee0376c93fcfa440844e943c9bf35db351f054cf4bc11cf8abd9d37f54491e464ad63ff08cc757e9d030e20d797ae7a24b07ceee5f503a994e4a8726ed316ae065412d7b7c7be25b62b982ca6ead5de23713537bb8dc0c2b9360872b66452474e4d95978a8226c85003991f6747cba945e4ede6f07a45ad6af57872658da063f544e84891739241e8c1c97bb0ea3ad4c9555c8a04e4e0228e469172bb49d43dab8b1ae0f77b303e9c13d82981e75fd53a00269b6f05d1f8337fe472513a5754b5d2793fec052fbfda6bfa3538e7abb35c762a89985fecdb70a71329f6d962bce01fbd90859e4de372a915351f927146b9128f6574d142bf352b1d720fa58a3814cff33511c1221d7adb763e337ec7a8ac821cd8c510bdd9efd60b721a9887908ebf12719fcc295573bb9592c6d29580a3ce4729e3e4e9e93c95f542d7ac43da95979043817e995cc9dba6733382818f0f0682c958e85be6fa219672519cca74c0fb989ef6fa0017c063057fefbd512ca86b52a4630c1f0587957529c1d11d6e0e199bb5a69276cc5200e3b4fd058d3197c3a0e4acfbc4cd8bde43727863fc4b3ddaa52a521da7dd3a02bb5595ccd099e4f1f114c5e099f1bdcdee5e5483778f16ac815a92be37cac033c068c907985271530997c72f63295b9a32723eacadc1610449a3568bbe2b271bbddcf89db21ea86f0106b43560c2f0dd9b06f4748f57485e9bd21cd8f759b15354e85c7d2487ace54a42ea139a7295f22c6859e49051b81cb08c6b61c273458d7b84c91a9194a113d656ecc75659e9207372e10df0365a3b134f5527fc0b74cf4f86c9ea287ac8a697438a367ad44fb76c721301f934475e796c74e01e2fc8290ec638c06e12612c93965532d39bd4a224725d9c88458830e48f17e88198ce6595bbdb979f9d687a7c3777224923a74603729bf72e4543b53902ff0f5ef26ed83b56e673b304c70a7acf594b81e73683ed72c444f8d806e017a6413ea47e573e5eee4958d53a25ab655f2b8ed68bb5fc5858b196d461e79e09b81eb06c16eefddf02284a2d7d0fffeb7ba8f72c208e52b5729a4d08b823c89bd67d5b13c9c8dafbd408e4d08e34a83466adbc9c716e1bef721edd96e330c667eff80763ac4bfeabd21dc79de94ef5ada8a69a6ad194f5dd729ac6405fc67805cdc1e600b00aed6e363e990d8902f2c26509d2731868554372178fdfabc8d59c6d5d1a1275b5dc732975f1a3dbb9b55899d7a5088dde57f8727b7a8e57989330327a196be8a52010259030f4ed79f19c9efd65546ae04ad87261c9cfeeea8cfcb18c3153ddf7be00ff4be95a0cd93d3226ba2756cded97b172440ec0e90ec31760f330d0bef48015fa92554e9ac7d97af554b81efb592bd62e78fdc1d0320dd8df57484e22a5b834857a1567fe7aa128e856870e09133c70018b1c7ea8b351dee3a7497172734354aaa21439c05d77f66358ed264a04e008728c675f18fe9edc71db49bdd62babd9cd3eb4fb5de82ac8ef13e869e38617f772de3462297833095e961b7c54bc335fab012b919ea64823386ce3505175b50d72948974020421150cc56a9fcdc970d9f67a9c0b6337771de4446c575e1e724872a8c35abb7c6f33d246d99d382178b2c1e9028c5b4cdaa29c91d6251d6e59087233932270d67f8c163ed466faae2114f68a78d3a5f766ee0bd16a09263419910a839420aff5789c0a24d52f713c7bdbb64d8e02f10698e5194fad87f902cac46d9c777e7da163ac0fbd1867315468621e8175d40372d7231af65ae5ba83177472d0b6c532e55b9b1f3489b52544db07e56fb64d628bf5d64bee66fe61b74a8e723a4dbdb5ea2ef7a5e84f67428b78fffc9ec720c4ba9347854f12467991a38372d8f4c16ad5c4a413762e4158e8c2ca5daedef08f03de68073d2fddec195bc00f29d690e72a337bb87b565453b87587f65d69f1facc43d4ba83d8d11a65e99d2086ffa9abef2db75962dbff9925154f4887224bcfe90c5029919b0ca4ac43f77257918a57b67f0b96bd70733049364f7026a4a89acccf80dda1694705d55799723da08f3e0fac118078c9b529ea266bfe0f3caadf14b451049ecad0e8561faf11828b7b2d91b1c2f12a78ec57eb0636df92d51cd45a90fd65e71515fe4537a4251071f6594b221aa783acd5d0617de6ad917cde9ef08a4f61487274cb217a89722d5671b77d5e7b9f21a29a0c65033e0b93fc35d07686cccad1d15bcfe4be9872a6a70c4e844853faf69936f5f1c2bd4dec45dc5bce1fc6d11c5f66c057e29472be8d5201791da253ff9f6f1fc48cf0e82a0b3adc4766b7501ebcda1af237c77222226855d18d784ac005b4db5d60db31d4b79ad7fa4e20ebe5aad96ee2145572a97e5338f3d7bc5ad3ea1dce06b5961b092d0b6ba053500ee50549432bbefa59125103bf3c08e553b2b4a301fcabe73d9f78017ff67638a6824d2a479762f7727409120ae5e61f11ec0b34ec7d736320288e66dcefcec6bba7f09df740f9a572c6f867a32591bf8bb0a162bfbcb295dd7c506bf190ff962b522bf888c0db1472bef91161ebd519bb1013af744d0cb849cbcc7dca006d68509aeed4cac389c2724c4bf62dbb7b9b2ad9465ddbbb7cd39263f6717e3d609c1e7392c36ac0df58724c3b4c297e83f22228ea13a415b40637975965137c4436791199cb0fdb37f972fbaa4f61cce6977dc6eeca0f4b66c5478fd5816a66de6b7ec6ae10ba0a99807267a1b871da325f948fc03e1661f38a5efab47a5a3dbc19dc20c2f4d3b830e6726e6df7f3cf5b3e1884607e0f25480ddb46544c12b57042e9296f59c48b61564053a065c4523dd70074b109291d2a4bf183e53fd5d7d537d30f0cd8ee10063e4412b3d64c7b5b1cd07ebfd9d4c2e0c5f1158b7867513359d0c8c2c9ab9e4af61cb2fc293a86f8326ad2ca6e06ab48b2f0e63dff8c185a2488b6f93d44a6af0672e8b65bcab95a8c1fedd064ff1bb01c4b95cd8352ab092c2cbe13aa0aa90ad072f8bb90440610d715ed52f40de7957677733c9c29bed293baa9611bcdbf11270c9fa0e9bcae998e0f4867c7c465c3f6e4298e6da2c037629d7118cac1fcb536722afc312856582e3af31913d03cb30d8f0bf5540af8f4dfdc29b512b3ca2f9c72a0987947715a4c48cc4f24af3dc41e9c7e360c39ba06a6c3d6c4e5fcacac25502948302b35b5ef43f2475087189e61a4aa91f41d6c1a0ea67c5fbcc2e3f2d5f90126b0b59b3d0fe992e572ce44efff96916d45026777430bfb618c1f4cf853b57f55ed8c2c83d2e490eb3db13294d5044005e7b0b4f32c174f7001a08c6e7d2ae53ff85ed58fdd680e089c11d8044796a971618511298ee802677b7fb57cc3d5b43c7ca9b08318abed7cc2d49d5e6aedbbb62bc189951c81a97349d8da8d74ae2af6f3e031085e6bcf35b3ac583c538d0c40b9ebb4b0a157b290acecee8f53a12da31a9504f28efd1168c402245d5b096bb8fb345692a6270e5a32c322e2d2f663b36e13360ab081a0f64be1e00079e079e2d90531a86590eda006afa5667245c8be4fd156c6a466098c5d00a1a803ed0047ab9e2e6c32b0b3c3d9f7d36faa6ec83c7076049aa5ea766b766229e4513584d12cbe8f1838faa4584057c43084b6c8f53f459479b0dbf90126b0a24da283ae1aa529dcb5d43f4ae24837c5a4acdfec94aadf9195a178d4a6e2ae4c07d0852edb9930e9d5745311b25a01b0b9996a8e7ebe37950097a109628c142bb4cc0cbbb60776da04a7a85ac53abc08d18a0b036bacbbb88f44220621f1aa3fb0a35d70ee5cb1c14a28f88398757174b78b32e81a5887f33b37078b65ce299d618913941d3a429fcd3b870e48c6c58449b0916860dde13f7211d3504357da969333c353ff3784ef86a5e75bc53303523eaf7cdfbb0c20a9d2b77723e50b5e0412c1b089a3a3654f11cdaba40fca5b488b4039ef3a7e11d0453a278fe38537b86efc8d833ff3b3482ba1dbb1b5339c2a337006b0b914482fb24d5373c85dc9ae8dd79b14ba2e3e37167686d54a8ad7bc6fc8e3817973859d92c8442fbbc985f00f3dc7de"]} -<< (9ee7c86c) {"jsonrpc":"2.0","id":41,"result":"0x8c322e313a2b3285f94d81bcc90fc0a887ac2901dc0512fbfef1a8eb18dd8833"} ->> (9ee7c86c) {"jsonrpc":"2.0","id":42,"method":"eth_getTransactionByHash","params":["0x8c322e313a2b3285f94d81bcc90fc0a887ac2901dc0512fbfef1a8eb18dd8833"]} -<< (9ee7c86c) {"jsonrpc":"2.0","id":42,"result":{"blockHash":null,"blockNumber":null,"from":"0xcf49fda3be353c69b41ed96333cd24302da4556f","gas":"0x186a0","gasPrice":"0x6fc23ac00","maxPriorityFeePerGas":"0x3b9aca00","maxFeePerGas":"0x6fc23ac00","maxFeePerBlobGas":"0x1","hash":"0x8c322e313a2b3285f94d81bcc90fc0a887ac2901dc0512fbfef1a8eb18dd8833","input":"0x","nonce":"0x8","to":"0x0000000000000000000000000000000000000100","transactionIndex":null,"value":"0x0","v":"0x1","r":"0x8896360b80c08142b823a49ad884020da37cd8abd21af74dc9cd30d550917841","s":"0x6978428bce9ee1722a7b858d07d0a4fc0e55276d671d05c81e6dfe9c1226cb4d","blobVersionedHashes":["0x01a980e7fb17b09b1e2c9aab90ee0c5f1a9bd2848913b49077b6afd065622230","0x011cd14dfc9e5290c5e31c2225f64f2a48bec55936f28751831f39d254ee00b0","0x0143403f36d4a9d3692fe87cf5726e6c063d13e43eb5b0e00032c3c1c3d61a60","0x013bb19bcdb7320e9bd1ddea98dc49c74b4c3264de10b32ffd3a3bbdc9832e89","0x011841c96e1f4a3fd856f561ed6c226617f2d212293f09ee7364160b81f0fd8f","0x0180bd20dbac08af9007e7b917abd22d1127d7120c8f2ec18e43ea84da20400c"],"accessList":[],"chainId":"0x7","publicKey":"0x95a6357daf5d9f91c85bd4e1f8b6226cb18396d772c35620d071660400a543d8b51f13cf95d7191e958a12c6109357a4e1e50eecd92db513dab323a5c1fe7ff6","raw":"0x03f901350708843b9aca008506fc23ac00830186a09400000000000000000000000000000000000001008080c001f8c6a001a980e7fb17b09b1e2c9aab90ee0c5f1a9bd2848913b49077b6afd065622230a0011cd14dfc9e5290c5e31c2225f64f2a48bec55936f28751831f39d254ee00b0a00143403f36d4a9d3692fe87cf5726e6c063d13e43eb5b0e00032c3c1c3d61a60a0013bb19bcdb7320e9bd1ddea98dc49c74b4c3264de10b32ffd3a3bbdc9832e89a0011841c96e1f4a3fd856f561ed6c226617f2d212293f09ee7364160b81f0fd8fa00180bd20dbac08af9007e7b917abd22d1127d7120c8f2ec18e43ea84da20400c01a08896360b80c08142b823a49ad884020da37cd8abd21af74dc9cd30d550917841a06978428bce9ee1722a7b858d07d0a4fc0e55276d671d05c81e6dfe9c1226cb4d","type":"0x3"}} -INFO: Sent blob transaction: 0x8c322e313a2b3285f94d81bcc90fc0a887ac2901dc0512fbfef1a8eb18dd8833 ->> (9ee7c86c) {"jsonrpc":"2.0","id":43,"method":"eth_getBlockByNumber","params":["latest",false]} -<< (9ee7c86c) {"jsonrpc":"2.0","id":43,"result":{"number":"0x2","hash":"0x90c8b9e1e8e51584453f5516036bfe0031ebb595e47c5fd664bd56471ce618d6","mixHash":"0xf8caa5bee858bdf1581f3920c0a700cd25923f194fdafa96e47fb2198779c608","parentHash":"0xf9e9afb4b42515283ae292d4f9982cb0c87d2338c717af437fe39175fd5e5fae","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0xd51ab9181e964dcec962295995b1fff437d9504a88ab944324bc1227c0c94bc2","stateRoot":"0x724b6cd36d03f71b4c088b69f1db0a61b3d9789546c24ef32d59f1dc2007041f","receiptsRoot":"0xd50521034c860197d235df5876ea04b9bce05f69b7e89b96e597d9f6d35b1492","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","baseFeePerGas":"0x2da282a8","size":"0x408","gasLimit":"0x2ff7d8","gasUsed":"0x17a25","timestamp":"0x1236","uncles":[],"transactions":["0xd886baa4d7824402a508487d94b8efed257832170ecb5f5a85b8d2e15317728c","0x3cc701f8f4e4c7d32e1a55dacbf4175dd4a61b4b8f26be373b8f7b0bb4c430e5","0x6208c8da6ad2d0b72a65110f972a38861ab4d12a45397dc6026d07e5623f748b"],"withdrawalsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","withdrawals":[],"blobGasUsed":"0x60000","excessBlobGas":"0x0","parentBeaconBlockRoot":"0x062367f0b23e2d49ad5e770d9ad17b83c0c1c625c3f9a290cd9572b3fc6cfc9e"}} ->> (9ee7c86c) {"jsonrpc":"2.0","id":44,"method":"eth_sendRawTransaction","params":["0x03fa0c03a6f901350709843b9aca008506fc23ac00830186a09400000000000000000000000000000000000001008080c001f8c6a00141250b4dd123013070c315e8967bc1e21d184ef0bfced01fdc7a14e8d1b446a001c391c5ef61e947f664acb7e263f2e5c696dc28cd8f214c0c69224a988d5457a001f3bab5d1ddbb50f2fb44c19d216b0d4e323fb3492100ba8b2c69a965d4c495a0014dea33181152c38f5d9c3924833d12f5372e1740d2c8bae1dfcc165b511db5a001c44b79055d9ac017a485a29f90d0a4f1f60c3f713a89b0d0978ee67f341fc4a001cf352375037a809a60df2e6aedf20d41df94f015e071ead5bf395821002b9d01a0cf8ad6e70d36e4ffa4d40ae236cf9c573f80f807722bbd12880b60e88b4ffe20a00369aa6d06cca08cf6b5e39f46255c32a914e686c82326717097a223a0584e1efa0c0018ba02000072f367490dc152cfb61a4e64005fbab621425de4c265530ebcd4b2f3ce635b65728d40573ad57c7e23627161a8bb2b81097d6e36234a1d058eea8acf0c9cf6eb2d13c8394cca4b71e0db56bf502778fcfe33a9fb46be76d614e93d71a0cbea2047774726ed5465ca209345e3e47a6fd726281eb5862210e18606d765c61e95fb2d480e512a10d7b71eb362b9f81dafc8ec5810d9f6bf734f51ebe281cf3661bb6f6842bb54bb8640ff7405543f3fb9ee795358a2cd837066c36fbae443376f4a72c3b5206a123d91d43d4627e562af5a68eaac5cb4ef0d8a246ca887f182d3a172899352560e86b88ac6b4dd665f8fd67a0395d51dca3576397327f75500c4df729e28998df28e7909daadff55c5f361cd2de657ae3c8dc43cd23c32a8bf173d72c0c8e943df69c3ac40a3749666c425293e7eb4e978ed140bf8b4fbe3de18f3178191d960a1f194990d43f44c4d5275c95c2a2eba116421411282094ebba32321387cd67a9bd20778ed836e8409e9e7eac29b2161a0de46c34a8a45a5679aa35cf279cdda74f28bb86dc51449f907e590001cde76d61fb9e93da6504e71f8ea499edba6e2321f135f28dd76395c50d8a89445eec44c848a7891bf144709f57c7216e98a466e319092c112834c1c2e6e612bf8b7a61a48e94190034b8cc0c599720c7faeb5913cccf7938b81d1b623f069f3a256f5dabda246b52a87747e0067724f2020a662800e82b669649d2ca68ba841cd7c36b1ad258ebcf7ac0e828fa13c3e41bd61f848449db7284400981962928fe1016ddb52f3bbe65001379ba99f34ef96e9972f39c4b015183083d2c89f34dc3582ef7c54b1fe4a384a233f87de725bd7f2668156090f3b086afccc63ab4222cd4929250cfb6a7e7387b4e4499e556b2a018e227adb79674336b8d69fae9910bada3086216756a7ab99bf2ca623551333856385be786b6e909352038fe9e95fbe18c1a089abf540b9729ac4a6ad72c70441e2a51566d1d28b0e99e050fb5c07527f5780eb07424c4622720702c072c45e515b2a92d3c18ce9fd3270f697de52bafbfde9b127bc9992486fc7825267e3cd5023ec989b6c4898e2224243fe8aa2a710ad9dc59883d9d0bafad97d971257d5fa1da55d0aed76ff0635d0d1bdb8dab027ae319fe14102564071a241461ecdc71a51e05e7584debaca6e8efe7a22ce285ba0730b888ff76f3aefd4d6f13ac33b48b7fb2da466bf95d44ffe0b7bd0034bee92d83f0197a58c171af492836ee56196c9baf4d3d6e8b4ada869c68ed87d96b7b65cf7b0c79ae08589051300725e3264366706395a6bd4ef1e511a33bbf0b028da61179aeb89f86ecc6bb8e7727c4caad5eec0639d0f63b0747c0c50aa7ea65d61645042d48dd73248c4876f6832c2813dd254c0cacc0db9343747286e9b9ba7d79baa4ec7fe389500faa7695116cc6b331fea448fd0753004e193717fa620c99c7a07c6f779bb417779d01b721fbcfaa90aed56b0d5cf7aedd18a575995dc0f7fcf4534fa03172ff9b138022dc0ae13afb074f1413b935627fc5bab63d0c1498b9a4905e85799eb785aaead6a918194d4e9b8de85bd54e265a9e14b1be4a9d582e4583df5d31e073e789b4d72b2274d0f4676979d18fe4c5c3ef9bbf43691320203e039e8c186db1c00f7ca4e14172f235d54b5f21573636f0b910e715a293eadc57aeb480999e7bb42e89b7230790aa2cd942922d9188bd7c21b773288776c8a59da6d91d48b9e83dd034f720d322d80763188192be1fec511dc2417deff52451fe57f22081290542899ca04cb5a5f1ce91049fd1d3d93e4d802d9d2bc4b4e25f7e8e2bd064fbbc5f4b7db281311fc781f3fbedb0e4e2e59e4a3f534541a96ae1a76ae9c0e89396989c0c072403c8a9ae414db793e4cbc0304cda317135397edcd33cf2f787ab067e9d5ae72dc0569ee435cbe2a7d61832f5758c5e1294fc75c2f630f9a1183a9c465703e1597df7956f8c31704a5afea9bdd1d57542d22f166be3059c8bb83152709ef29721e53d65dec35365e5d2c295a7e57db913e65c22e4302057ca048f4f43aee95681c5a144473c267fe71c2affc18750cdbf4ea37778587c979a4e2e87fa1b8e968d32a12ccb70742aa4987402ddaffac7766fc1543ad3e187fc1fd3943118271721f48c6856f12ea43cbdbdb70ec2ae2da47c71a115bcc234bda2b7bfd7c028172e1c95b5646b4980f59a10bc0226d3e191fcfc9d61fa72b067fb77d585973fd07c30105bbb579814de5fa1d7842f4947e46180da2082c16cf670378586de7842183d38642d5d17500a0de250428517623f5cc05467393357a839e977d2fc0f9725554529445867f8e19519587186d37d6e2876ee90e653f90807641e2b8ad3c72f2797a8b6c8cf31d824421a5c5d09438ac5ad8093d145b4543223e25bc209b727a2f049a7e9b4fd6172d19bf771bd0170cb996774176d279c797a1cdc45b9e3fc8a9e273acf6e253b3d4f3d2cbc35664cdd2a9a37e9ab5421958bc8af6fdcc28ac4304df38c3bd5889806b67f0d5fd3aed62177abf4785bc6e4f00827b76bf72baccc5d4cdcdb4c64e5860d571a11f5a9af7d942c71474009d8191f4b8c73013f320622bbd801e0a96b3713d4b5261a7be1f165af58dfb54663257f277322c72a58ea6247413a7c25b467fb73d1a006ec303d4ff92160696df36e4b83250906687432f1f877c95ed80bcd8724c9e200c3ecc65f4ef1ecb896bb96b886ac21535d24c6b1c0798ec4aa8a0fed9321899ea0477a382d3fb982b93f5b4589173bf7204a98ab2d5104e36abe7c5145738f6e73bc93b4bccfb1fa23ba2ad33a3aa9112eb56f7bc04a4db0ce7696e9316964e3a6aa6d6553baee860e8049af76f4cf2725124f1c2070a1db780c3373c5e9f2cd461a9335973078b0f4837f28eb8b4cb7206eb813eefd9e213009382ff8222a936f9005591aa79e52457ea255f55196153bf293225215c9f9ed9847338cd1bf39d12f064f3b645992746e03bc20eeed272ce49632fc52ce9209476230c7cb0c0143d8ad7cec34247c1e2827f7ef9ba8a723885a735b0056cbc89ee9be49d6b5ce2340b9bfa698096e84909279643854e72301b4b6649a6bd8a48aca437b1e838e879f61986abec2033436acbf0d8657d72eacb478d136da8c4a6840939c3edf4d37c7e21866bc0cefa8a1e8d66b95f79701c22ef4b566367da90314ddffedd20948f9a9e552f6c2cd835106a4169482b1af8d6eff736b7fd848e4374818668305c5e7bc967ab483b3b7bb3a0ca0bbe7e432ad7b326a39f6058f4e00357e6292bb12c1c91156b37d70faf70384fb6691930fbf8f54e8928f9514fceb764e8dc40e43ba50b1843465bde54094e4d5de74072f0b43e4d6816cbfc3159d6c872a90c42cebe587f513df953beca19ea0fce473f7a4feb85ca2c2768b94223e07783006f60b4dd6df7d5c068760b138cc76bfe720fa5e22d4ffff26fedb62db7412afc0f7f8badb7bf3fbc396fa3ae7b0b84a26414b251329b88ce0778cd2d64d338fa057bb3ed1f19b019bf0a3a5637c4eb307204ff2c81d16baa6c189466745a2b8c2c1284a3b5a658b813db3b7bde6905ec1f0979a15fef197bce5b66dceaeea3501aa2c7ca6f9f6e2e15fcd1d3c24f806572ccc569336359dadf9d79c32594f918ad59d70d790fa05b920888268c1f589a7202be56471d7820082b3f83eee44b437882fb8f54208d6f64bcc068c112521f729759d856f44152d19d7b45974689051ef978a9a21ba43702251481a26cc5db720c6b4d5973dd0c84e00c4b7014089b35ccf3ed8d6bf2d29d2be9819481fa0e254bc32a4e1860aeff1882db0ca4a13e7b1cac163b74946d0838b9b0f917f01972b791c1d29f70ba596b043495c979290e862e6a4e0ec547c4556ab3135e69b272b705114b668f64a110af7f5d06849a1179659c4a5f1818b5c078e0ab54315e7267c6e5e23886c973389df09c8c736b01536b5a3aa27fc2a60b54409691475e726a28c9e37c608b12a2b595bb41d939150722df33288e8ee26f8fdf31f42c8502c5970dd864c9e61f0f3c12e559401f6bc8f3bb77326e8c7c4866dd8448db9f72a146ca28f4682d1c79b921f21c5550fcef06e73d89bf58dced17de063a3b6a50f7c0c99a187471f7bff5bd822c6b0577788886aa19d222dfa7db18e06f9a3f17c94deb3dceeb101d2c4153a0be9d0fe25eeee016a210fd94adc1c642a86aaa72041668a2d2793e9b36c33ef23de58ef9085b9f86e5983b4075f1ce20cf7c08727404c4f88de5385f3f1414012230c085ba8083ef74f625b9dce0050f86a3837288c35fe8cb3c5e3bd40cb50e8f4f43ded021eee786e6ea3a873b237948c54b7291ab197ece62aa998108900e98fa5772993808ce4ac05e0eb6fc6ddc872c3754680da330a4100a2ebfa6c2f7369c9da471dfa1c9bc8775e424fa9f0f990cdb692f20ec4ab857f79090a85608a312703200fdcd60d54dec955b5e428fdee22a52d5715e54fc102189efd5d16c2a378e7a600be8479daf08998114511b22d36072a85539b98ab3c4e324860241603bee9abef9911cce8a54ca6278bf063702a472c000ca27b988536b5f4696ce1e2ee6cf27995c6d65598572eeb77ac5b56adc72916c08ca514d2da17c1a684f2e5122dd9f1e057b2365920a9ebc8d9ae1dbfc3ac3d30834ea8eaf75093fa6bdb124f353531fc80853138bc1c37ee4d6b7ef4130ac6e917698449fd7b65cd3e5dabe52f8704bdc78ecb9ab132fdfc8956d2ffa53716605f732ed293b3dac4441cb12014d85d03a55c0515c558767dc300007c87298b002fd731542618fb5fc66c209d0bc122bdc5f2022b547f87b79ade4714972a8f1d0cd861b53a9c553efd3f10b68a068073f11128ec90ec15d2a1048144d72a5fc2aca9c4c6f27398cd11b9af8a8b6e598a744903e47d14bcf0c09634d3a727478a441f83146dd3d8e23f9494ca09d806a0140db8cc14bd5afefbf879e64727a64a35a776c7bd963579ae94e4b6989b06d902f53b8a0e6124c9aa834e25e72953b4ca3729ff28193d2ca606852db98a659e8403f64af8da3f99f83357e78155376ec0559ff674aab3ff8927c694916e687b14819fa2faace2e95b25851e272e753057a0723d38071a7cca6bf9b54f4805ca39050f197d4e75786cf1a4f3d7258333d3cf3f9e3d804e1b6b1ba5b8b44da880d52b136d7cdc192e2b7853a3d5b1d580a144a4d669450aee41b5f7e4e9ccd272cc6159d5c6cd5ac56b79cec43728d1daab07f23f0ca569efef7ab21e53416dff445a8bf1bd84a063fff9f367672ca73687bd7e0836fba3fce1e26384c1484a8b1586db5a3e487bfdbf8e9757f726994afcdf9f18292ceff0d765665a1b99049d2712866c3b464306ec6befb7f60d53b8cce7f551bd0a4a44badf1bd5f8b10648698233eb073701dc8a737340a72b8bd7ac38548ddb64cfa1af7db172016fa2c0583b96a73aedc10a6e4820022267f0c9d75dcff8bde66307d08868a36a02d77a20c3b94271f9b689cad9a356a729487620eea15260a398f92a5b3ffef4f27e79265acb595ba8e9e85ce3a7bfb72a250055a9a81b51f86d0637bf6aebfba433113cdd74fd26752f1373f2854020ae9b9df3a2f85ab7df544cfd68bb9592fefa495751346c3339db3c47131e951475be5cf96b2037e7d0e2806277879fe8c7e04f2d8a2242a68886f5d660322cc304107907bd4348b51909accc4bd038c61cf3ad9dcc1260a6bf79c3ae8cb18c95fc8597c7435ca07dc408e467f1de12e68126dee52b0c34b58be6c8066fedb5234e43c9a31c3dc384cd1c8cc2712b6d05455955a98bee1eb6185eb35e80b8da566d7f4acaba2411ec14c8a7fb1088a65467c39998845a9b3e99cee7eeee9c2e272d899ebd1077716123b8be2d95e336c542fce36296d18e660cebdbd1bd7cb0372ecd7649d571fb50e74b4b778632590f33761cc7abfbb1cf69ae019377ef23112598835bc51b1653e5fa7e7b84a232ba77df7943167acb722f18dd64ddf512972e483c0ae8ca75cf7bbdc049e06a0773ad2efb5bd6ea5431031529c8b75912f4cbd76e08e7fc82511a097e156b1b50d00ea0517d771059f8c11659b6e68e9b409303514b7b05d68a986b8786355145d949eeaa381cc181b549d1de22c7d1a6772b833cc4ac8755b1dc9e48569893aa2ee8daab0d6334f48adef3e1029e890c172f576b312b42bea8ebb1ccc71f568d4ac09380d9da88cb7a2a49e979f061e547252b857db3223209325cec563f28ae2d1271b0d9df6a1580849a73a51987455723b9cc5a81c4c03c9ec35fb1f2627d19d20b6594ff461a625369b2c80aadc3172c2825efac0eb0792027bcd8171a2ce75b5abfe033fdcfb5b46528bbbf421d2729669054fbc063fabb2c65bf011edfa64b80056b94e37de13068a758d914f0a5533a601e2b143f1fd424d23a1e2f035da6c4039d79601845cc7ef0fcf32443f0a101c3b3c03e8b12dcb367ee73d7c65c0ffd6e34cb9dc7380746b6800f0cd5c036c5157d0d3ce058c7de81a44d1ab32e06ba31387338ccbde6d0d0b9ea9d56f5a80bc0422424d5d97301ad1af94f53c75697420e78a6833d5051db0371988be722c0208881ac09947c7ede45365f0f604ebb326795f1429573150a86403cbbd6805f55234ab8d414198f5438bc24c251a2ac6b947e0d951103d36da6b804d9a7263decfbe4be8c8961e46b38d58b22523e4fa4e268a51203cb0263946f8caf263424722983258e54b60f800d3cd8f7a5c2d66d52f528d3129aa9e87da5e356472f767c7b446a4bfcc23d3e70af791251cf2b8fd864e4f0d82eabb4e2fce6e3272207f32eef3cb0fb2de44f2808c0a14b963de6353c6a133832a2f4111face3972793b631d1ac860142e52c9eb7c9acf7b6bfd1ccdac9fd313e62c450ad8f06b72282b378bf77037dacfc58e3c452340c85c23c2f9bb17d4e125ef10ee21af082ba989c46ce35613e76efecb65f1cabc10b3777672b7b7625baf994865703c9d72617d715f6ce9791d164c021f3499a6278a60a7a707e6b213a6ef4bc5fd6610729612a40595e203d09a4814cee3abf2d3e88623a1e337607257ef961eb4e19e7291bbc1c8fb967de7ce3c2d22eb39dcf9286bced7f8d9be49032b9d082fddfa2c0b575ffc5f76711622273a2b147f0bf2c2e2aaaaac2d1576b850a799a086920d0af0f34e3f007bd9cbed41b6fede1d2a3c0a29d9d3acc22747d3bb01bda254562787d89d5b40035d70aabf7d69009b9996c9e9323729f87a9d5ffd186d716f727be91d3b200e0db5ffe10119cfb44e48ce0a67f094174ad47495381cb6c64f7252c78732bf20339174a4d6d1a72d91886ead9b919345031752d46341d82acb72081bbf4a5f892ea56c9882fe685edda0f0d7d788d387a9d56d89d9574abdbe411b67b5911f53b71f0a85f246d24d83389b7e3ab11ad075cbab3cda4bfb267372ea6470c03942225e441e9109be92ce998e67e09a3431c3a5bebd57f40a5ce07210bccb09ecb76e1db1abc8b89ddac55cc0191d18976ac808b2a29bc0b1be1255435061e0a7ed868bde3d6a49b3c73898212d8314e32b3e6a8c4f057e53878d7290f693f2037f76e2e1642c587803596266f284d6ebd1b582f36600a28250c1722c3b18f8f698074ec5ebaee87e0fd91712fc048effc599b25e666f36c84dee72177802cbe897aeff12c60214f30d911af229072fda788c02c5ad235a01581472b34f8c88d6b9f5675da712bc13a775a43ff3a01df0dcdaaefd461d56e59c3672194788eadff6e929247045ceebfc6f9df5cfc863a46b9efa853d41f3b3955e35dc491dd46426e2b55733b0e0cfe86e6f91be05b5df5afc05451254ce499b00724359d69b89e4370b15f48cb20bd96108ec0d085ea36a3cbf7c1e3002b13c1f5ea6c05ed35ce9fbffaccb676aadb3ac70ae77ea572081cc79a8ce8f82589d4a72845bbd663b57752f1d95259f8f6f84f2d991e95fc6a97544622fe726381ec234677173f0a8005c30f88e43d1c8fdd8e93edc31e1c25751955108aaad55125872e9636c199b5c219910488552055f2d53b44d97b9627fb29e98c6ab0762d26972b58e500914d18931823fa1bfe284ee98357e1d787c668a7506e727f421790e7238fedcfda28aa348595e1fb14efae232b164c579792fe480c3ec897dbd8f0b72be23a2df5b36deb94b2fb7da0d2620a3b2660f2a4975e1693651f127f7bb82728006d0f9fc43104b7f87407974fce31433eb1f1e999601831f689ff048715372f5f964d30a8a9339b12793791fff261ed0eea3745951f76dcc78b095fba871721fa340bf47b0d49130efd7086a008bfb5faff2ddba4cdbf76b2eb679f08fe059c21b23365de15bf8731725ef21ce5897e36150869b54b121df661d9325470472ed0898c7045fffe66996c7ca56b954b97d2785505459c9050e74bcba2eee154f42c899fc161f937ce8aa7b545050dd44e4bc73be03e7ea01f3fd038734d2432808a15a9da899f8ea9e78e45b206535d12781c342d78df1eb21332b61b836d6382f743cdda30afe296baa8ba415a20bdce27c5eae96b6c4ec9d5413b4c84f5e42fb5795b0d5adcfa17e3674689e360516c729db76b1f4b0d35a289b43220b6c72932cebb877dac46087889b3ec68866ec13fa4a60741cd251da4e05aecb8e2a7246a74aed0025a85225d90c22984eade3836dc509a7f21bc4c87030e9f1d9b972e7d29d6272903b0bda2e7bc984723ff0b3b88ae5e90bad9e0272665ae44c237263098c72f98067aea06a6178469d9e2fd1dd47b67b80b64f8522289b54dce8725a7eea28db0146585ebc569d981d1da7e754de4f5c7b809cba869681a8cba965f92535c1e6a351690ae2adfb9b2a01ca018f81ed38892b588cd8cea6e35d784ec724015777e33df13bfe07bde0b4e0849666fec6c77ead0c78dc9d70f26991721104138355ee031650615462a7ccec0d0ea7559f70fdecb66cb6f8f41788d3726e17a04740bb2e2d4110aa4f2f47d377f06bb42f86b788201ba57ec2e903fa72ba1e6c0ae4d1472e3ea1bb56487bde8de4dc9055fef1d87a03b822e4b2d6751bde7458130a114614a7012fbe8a6e057f22e92ff1948b0774d3be571d4abd9872f90084cd55c851c2a37b8aed8e137cfb6ef8db07b7303614cde6589d1558f6721b63924cb7d1f2a798dfc2b348374c2559660e3e71fc384346fa0e8239aa913cf2aa8fa344776f32c3b6f711a423d25d3b609a4cdf011f9288fe6e630ce27d344ddcd4c5a79986486930ad92dd159d95652d76428658041db3c4ca12fe09c563eaa28abebacd9563b57a10ddfa899f55e7a663683044c3cdab1499057eada228cc6005e7855546f7c8a73f9b91f8cd8eb9a12a8717c434f36b32cf7a5cdadc4528ec2133990e68e131886058dbf41d765b4055e85e3934726d5b6dd740d94172aaddad3274319f272a4ef54316bf10e8f35f5c2c9595c75047765dfe14da7072a9dc1c2e7c3b3bc39ce9b45e1b047be6c4c40391870f6f988a0543c78d02a272452679a3db224d9bac528e081c36b1fce6d54fce1d67527c6fe5ae6f515866699fa56189bff1703afee7f7bda15f675a6580e7f4e11f1972f38bd19069c2ce72d18e11845cb29b48a7e5b18e5a722e36c4ad4f95d4795bea8f2ac0f6d7a7604e65e4a6172bdef093675b366e4227af3aae7a73848c99ddf3d1a4f500a91b06190489b4a15e0d0bd2a714b96c02c0d878115c2dc1dfc60de824f9613f6489937220ab5d6711338d3d831a88f106617a36fd2ca33f94a5686b8b976d8bc34664727b810c5f69af54b76025fa14def7797110317cacb9532dc6b0371f09bd2dc07261cc75748426243f3adbc9a03c556548f45e7f9cae69df27bf52a04eb6921913379c37510d825a8146661cf0ea077048c1b0ef3293d03beb93324ae7e5da55476fad44176b3d48a415bc04ab4e9684c5c5081f79882d4c226f80af328b41e172c1b6babf92d94fba6ed574bc001af905c200efefe235d776f432fb330389d770fb54f6a25ee0dd1b2e4e7484bc8978545eb51a6920f3e1cff6caa9343c67f36f78806d3d200dad0c42d56eb7b40fae3459db8de2cea19f8325bf2ea2c9789e7230c5d4d0dcb853385bccd285240cc537981bd7fd48fb364e24aea8f29963495286e495a335949a6b4c223a8bbf28d21d511f372a03c0e566d99166491d7f1a24362703a8dc6d4512b14f08bedf794f96ab5907e6e51784b7818aa0e5e1f92372021d2fd1ba492c239a5c1a3226d4e0a703b5311cc54355beabfe67c1ccaa1372787688b88e5e830664619dbb697a6d946f570e87c2d2de2feb6576ac83686272c7998dbc95cdb74bdbfa6c65062f09d49979673b86c7a0346e3e3fa3bfe36413a852ddb168870c884685430829c452060c5ed654d59c51045cabf9aec644d734f8f38e0bb213bd6d5c3d93d11c1d1abb66280446e9a808d1ce5da56601af1c72653b7b90d9ff3d7e507cb8947e9034595f26e2d9c5dd34e888a7780755e43f724eea27c9d74e0a473a94cf1451b8a166d9f8f30c665ac1d372e0093820264c7215c563918f47340b5304bf91ede887249828be949443c714ce163dc0f8895b727a4ea25cc0428c7f50c7ffac858e21dca642187a8850c42b6c7a3fcba94d4602b602e748755049d83c7c6a797e2b3c4daa62a47d0c981524d6f8c03f6a605472178245d5122bf973f1f62c3d7df1a55daa49a07ff666894a497a1fc51077ee0f5350d0d1f85b60d2557f3afd6c7d2a723cab98f3419dbd5a7841628f314faa0baaa88a58220ec9e2459c3f65db0f40f7d6e42e961f3dcbd4c4263fb3a16afb728aaa2d3149d3ce50c10afeb40c14a88b867e1f492f81b11cf710183cbd0701726ba575eef1a734c6cc5543bb44d137c647a2c80effd436c62a3cd259ff99f108bfb127663094c88d24c9887a63ea6e5de4a6463a81580b6b4e385f8dd2c7df7285fa7ae28bd247c4244a44504155933111f451ae5f6a783f376e2583fbb6c41a107752e5999f27bec7cf2e8e78dd30bae3df24ebd2f77b7ccc3f47a071479c6c8236e4b9db574d301b38c7593b3ef9adcb03b45ab9b71e16465aac9c835c764f371616889c9f58cc200288af11966ad6588c463ca56fa6435e99f8bd58d1f4726ab49d51303e5d8852742032b68ce9dd594a88b1537892d212213bcbb417c472acdc4f50ca2d95db44a492710f7a67c61a36ad1d75d96f4589fb50f7d215a626fd3ac22c8656b93bd71e7521371ac460a0ba48ff85c7d69df247639eeab4b41edbb1f3739edf87080eba0d54d18a2b5a4918c89b378e689a51a5b12664bafa7270a9141536928c0d1d4f13a668373612d8888961d714cd7f6cd6c7a319061d72b6b474e4b70869e2c0c7a6a9114a44062f8f4f7dc9fc6ce7832ffa617e062b095f98b18569bfbf2bf9af33a90227c25426b465418d19c546f3143c7f0424512da3af003189213d7b8837081de35c1b2efdc9c0da2944c96500e5ed543ce641727c9faf22f00e39205b5d05d2c26b4109424da5ae85860ef3a0504a0181ebd472176bc4ed5b84ed4ae045a4558c40b1aaf8c2e7d6416be108e9de139a9007c741b5da11639747184117b4f21c6a0dd86176d9661beecf98bfedecf5b1d4f39d72a4f899b1477784ea409783368a7a358eab1cf824f7921cc07059747e8cd12808704cc531a81be415146bb79b674265f86721fd3e8e952e09a16644d215ef837260857cd8b8d7f5fa9730d7af21b10dac681c3f0031017b54c023c121ba10c472ce1ec6eaf59a92b0153e2e551845edf4dfda2a950cdc5a1cf8d43650b733bb6f500e3dfefc9f5cec90ff76963122fec1e6d1f1d7c029b6608dfa50146575ce725e2adaf28cd093065a4f1270313c117f0d7cb1946816c298608ca1b655109d72daa6607d8f6eba368109b8e776c4355bb8a8203ba0cedd4a32682e4f34931d72cdbad6d2309f7e06ff393b7d4df50ee2fdeff1263f2654f2ea948a7823039d72b0af170fae3cb002c545128bd44c1d66c4188cc386b6fb2136fe460fc848a372dd6ec64f2ab15ca3f6bbc8a46c034dd51b9065658ec9ef3ac738d6bf5e9fee725836e36c0024b246a20e6a94d08bc78fc503d9914c251952ff48eca632e62c72480b764ec774509442455d3af1d26c2d37f9926ca99545b7dd03675972589a560c87e120b98819f1d6a17641cf8c348c70950ee01b9489b7f05de30525764227ffcd7d43ab0aea2b1e1094162a0698a69e16d9bed7cc1b90fd21b4c0cd80067223fb6f7dec84d2844288b3c5ab246238a99d519e9b4bd3e29eeea5b78a902e2a223913f1783af2b0ac1d6e4f217f706e1437d63963638e3a85294638e6ef7772e1f806820a7c5213b355b0d1d638001417e2d5cb89f83460f9a22b71edc9f072ccb79cd50243f1724d57d31a01daf00b68437ff7829ed00a80430e9bc8dc803b789bc5c8938c799bc5b4e3de2df8f49713b2e7d525334603daf2bb8708c6806eb1b1ba392e7d6f83ce89d678ddc3444f4cc05d343079ed3ddebe298e56cb5e72eef987a6854da51859d781579fb31f72d1b60e4b4021e63488ba42d10f177972264f37f4826000a679aab4fb5e6e592183612683821dba48ebd2f95df1f6b14272dc105eedd38b0c9c2de2cd84311b7bc65a2e170a7130391be2bb91dd5d7c72bda875d27adfcc6ca1863dc7f6c2ea0c0f8a93de6dac943bdc5965c5929b747271e2fe44d366fe44ba66bcbea6020ec6082beaea2807c4661a4b719fcc534609adfa61aa3c3bf89d3288d83bb91726fb65410dfc8142d67e60e3c112b9923d3946b30bf50d302dbaefeac0a8df9499eab4b63f56d328d42ddbba6152f0434172ddb3f19d00e0b884a07871902a58aeee1728fda4ca000a3fb11cbd8fc7c12862692160daa48a5d02e14d1bf5d2143a3731064918d14ea1ecb7d82adb65c8332902e71b822e60fcbd69aad45c158120868d0d714db15842316e3f4aa171ef4e29cc709b527f1c264c3cf02aa88e6c335f5832d247e425a6d9f3e278fdf79350079eeef14a3df792a67d26de661144847fa7fc67cffeec153e84baa6bc7704b267926209eed14ef05a12388a0bb39321433cb297adfc65eda34e995fd8999c7c5aecd57de79af75d2ceae3b7e4805c2d3a993734a7467a9bc990433719fe6eba72f03a0c2686cd9cf97bba4d41a581f205766eec43841b10586a21408e33825672999f91c9424ffd12c55974ef342fbc94a1b73b3b1397baad30de0a72134ab37252619d537be9b18dc389f1d85954170f24104545487ec503394b701ff39cfd72d5e4c5c8f1793348895c56c5d17d49285d4b83ebd36c7da337d8528b9db5f27262ae2fd2b3643ecbbece5bce3e414aede069e77cc02db008aeb1e995ce74591e618e6c3893184382315bc41e425b79013459dd3a80d44d8b094645066d0d5072d37465fedcf58864a39ae8f9c59707ed43b2da6f4d1c0b01328cc7164983a364ddde719edd095e797b14af4937d0df94a564a302c31b1bc6093dff0a3c1edf725e95a147c49cb3bf63976ba4e08ffd17525e6727ed495c1e8d6dbb899295d87077654d42dfe77b9b69b7aeab109ea9a3097f3283b8ea90b1831fbc8f24f8c71148c967d6243b24ef397c417758e020d195c85859507fedd37200f1a711242e72824779535976cacad2f3c7722e6bd4aab86f97091f4ce8c5b057599e0b59363c227550eec3df2e83ab92af4e32cfbbd1d1dbf8635ab2c5d1bc862bc38fe84c7250d01300d1317dd746c4a1832118869ae261d122282ad6d320b1b9a6bbfce52ead6d11ee0f4c5d64d9b02fcf74790090bbc9d8489487c6902c8398f2dacf5372fdc76ab648875b5ec8bf0d4f95b84dc399db6ea193e4296569d6919be798ff72cc1c61256449c103ca5df6369a59461251489bf2788f2dff99e4c47a9a249272120c05d8ec590fcba96c83a45af82472fcae959957e0a7ac801f6802c5ce0d1b64eb7cbd7b414ecac92c115a19ee623057acd152c48927c9d7f5839b20856a1dcb7ba390422f7233ef8ee63d1589b32f706b4151b4a73bd553e948843891b172fc3531412f32bc3019f19c1d3cda0fcf70c603823776fa58b6a678430339e572b8a19738448af176824278215984d46f384196a0d93af4983d412988bc39fa72c4c1d8294831b2d55227a3ba9fad37517057a286639fb0bcbef76c2359d402705f0ef2b61f1f0c2d7671c3dc5cfcc36c8e1a442ccb1dc823a5428ae98522d772585733210654c023c3d8499a6ed6dc62935b005dd170b57b3baecbdc319f87724b308524e9c1f9aa21b6c1a2c64b12137c8fb4b788f2ac4359aa3f8e67d8527225e2239168f0fbed4d3c6271208a330fb959ddecc4225097305dfb7811cbe71118bb5502f1e085481e5d8c084cb8dfea449b10733b2b3cd0ff24ecfca8ec3a173815c03c673faba821290f68344993327a8baead738d0556ea9b12589b5b3a7278f83632085a9cf3801343e21932457f62543bdaf6352b680b42816b2207b83bf49447092e118c211e979cbb82212b08f353bfe30a282f8916081e6453ac3372f20bd21ad23c604290f4938b3475764f34e8697d89f6520f37831f66f0e3e1723cce2198e39146b75d909e32f2073e7a227d4fc1557839bc5905a62149d135179360a52281c5447f82622e26a513a9fd8164364e8cfe8fef1cdc6c2488b8e31fd9d77cfb242b6e8291e98cadd213e6d9b2859f4c4b1f9e5baa3d1d265033ff17b25c9622670d489974ca79cce3066c8b44cf6839f9ab6a1f8b2b773b54b828724feec91bd59847e5c89bbf29e8adb0a405e7b15f1dce76ef28d2f495e8e65861458632623efbbfbcd321eee910bf29567b6c27183df5e8117177e4a8abde873f00911176ec4b570c277b84bddd125c1858179989e839787bc8fbf33f54dcdf721ffa51885c7064c7caac959df14540dbac113372e12fc8d900be03eca97ece3d03c730a4f9b7fb35fe81f88ca434d2b08e92b82f90fc1ca89e5fe83b1aceb47272350a3b7b38c7f71e1d76610e659997b731ec5a8ae7693f5d6e2833aa43ca728ba8f9f8aad6f2824839dda8b87edfa1df84f7a72eb561fd641f9a8fd3162068529931ec8da62a1ecdffab03b41dded66c1cd90ade75da1e564c2ab6955ea26aee0634324bad568e856a3cf1bb284b33a29759cd00dfa618fbb2a7ff07d68f2feb4db187b7f7a53b2175631b4f0c670e5e97b4d777f2b219215ae1e0e51f84721abbe75c514b41bcb799ed06a222971cfe9b6a45ef5ace52eb4f6472c7e0f1532eef78761f8e0cd025ece21d80772d15bf4db92dcf5f4b18348dccbd0b12f272b3f7604478d801b1cbbd0fb67f9a4b53220b7084031d92debfbee1448362577265fddc9553dc6653f9507eb80794369a38d2e1c358c85844d749800fc84bcc72cd0b2d753fe71fd93ad282ca5426f1beb7b2ee3fffba1aece085e2a48035160098805f52f6f6364ef9c21e41e612fcd8db4f3560fffc3237ce6a3c9c043e7d72b6b1c0befc4cfc8f38062eb3f7c94466fee5bbbc2c1b06960b20db4ac3993f723369bce86c74891efaa1acd438080a74ce5d9ea15ffb7d31f1e15c71012298513ca663ab451c6ba49b81fc2f2c55692875af9647347e489d55f1ec1328f9e002475aa7307283b4a29d8d5e6e937da18a13524aa8f4dd44cbd0546d81a0c01c7217a5150b9a875fa5b58ec79fdda0cf39ca3e41a918beb9dac7cdee787a5b9b7252d9407bf0b4dea250cf9eb944c0036bdd2fa9b1c06de87c11ec3494e0cb92722fc75489a2eb4aa6b6e22f2a16c5430946e4a4f2a866c75f536fd994f2b70f725137453df72a905f9b2abc79b44a39708de3f8da62746ccc13557964e9321172b62ec3ca2a738dce24d44a35d3c806259e3bc431a3918b7fc09f64e0d6a1db3665aa33a6b061ae5f1602094663240f0ee59257cb07e875e168f07f2f434d22406b69b45c6f3e6a4071dd927972cedbd5087eb613d87d5773fb817cf62ce6ce72e69d6b3c0d94e37ffff157142c2e6b28c55c9fe5aebc58d5c689e6a50b220d5f70fb990ff0d9d07de089982f6b8803ac638c15fd8355e1d798aaac644f0c011e76007a051bc10fb3fb097aa501a76df16f3528aa17fe40828dc5c020806493462b6af80769d0aa6927ac3adda6ae8f20d6a53c7f0f526ba434b3a50db09a1d59c297007cb3bb2cd00e4a6457a1deb2154edad8847389c09397483fa2390d22722568009fed1f0e556287a8c36a44f53975c54382cc79205cb9c00bae69935e728a22bddcb704a876584a192ad93c0ef22b540fc0d1c0d7ef32c299c945fb981323384de5ce3e3c64848f7c1f020e1e94e0bf7e96438d395ac2ab686776d8bf719ba6adfb64d6b71b8e1d34c7e98f5b07cafc63740367b2fa2bd496e88eab083b12e38b88213ec1901a142f0a35b239c6f15ab5b6a91db0152f903393c2ceb67223f56f4e1322b497f7bc2e7e49e33126a5586aae3a6661c33fb1aee632c3b0568a2d59e6489791bed5aa88e72cc60d85208d732dd46ebc63f73aaf89c342a972ff1c893d740299504c23213b0d736a21ecd55284fab872790c2da9eb647aac723841c5050d483ae3337feb0b1eaeeb0b8c4295adc4f68685da83d8ff6436cd26d39a37b0b4c900fddf2b2b3aac119ea1c18c7f74c375d0fe4e010c35233f25726a7ce91c75f94875526bd8174cc823e43122955299c7dfa4412e7439524820102bf7d19d4f2e8907af96b33fb0db794800a71135bd41bfc059ae8be18b9676656422c55f4ea8e7dc869c0c430ac83b7d5338500aeb2236f7356c33bd6912e646bd159ee735cef0b2344356f97c3eee43bd48dbf212f34b5b47ae4f6168be14727ba8266d5741a5e0dc537ebc3e72377d91e4bbba20c33f811a08770d8499d15d37c6b10c107ad7032cc79718493da5a7ed3b011df8f01ce67b66c8ce2e781472195bb9f1127c5e571d928f754cf465e9a79920048cd1c62f3784c6acfb866d2ea463d6a638b1b7454eda0aa870cac89e6cd4c215f8fd6be113e3e446fd1c2026b33c2e8ac265193fd85be1ca6fa9d4bad8029a28d2ef0e232afa28e06e6eb1723fc4c1be3a81b0b994c3e209a3d3ad4baf2652900f39bb49a4185d4d0773f872d565b991837032e8c5fd4b77507b25a7f4fd7fa0bdc0e6026349c0395714b672fc566a5f8ca6d604716d09618028d10266b53c6071c19c634e449257eda1034b8f57af68589311a64c7de3f00aa9a2c146e47e65bc450e97cf24f8e4d17ed07236653238289d5976e5af1ed973fcb76832c9ac231a6da2944c575d35c50e13722128b7c74576f03e14d3ae781a6cf86f1f945b158825f6230ecb4ee548ddde64d02c3cd3eaa919aefb8069340a298365460fd4447a75db129746af3e541ec63154c17f9cfdd794f2b1d1361278489754bedc46a4226f6e8bed132943c0f59660c1bc49ae5fc015132c586361a1eb6a19c6b3722a78e7a2c95a8a65340e009d7277f699722b26d9b819bddd68114508671f75e8ff3c64dbe58c45fbfb31b195725ea3bd7c381379d3c4a75db1a5ac5f71ce63c72eeeeede1206dc66128f654872839951874a407a0baa7b7652a0bfeddc32afd7175d8517ef295cce6b8980497241983b326b3dc30dd27371e7dc33c95011cb783df6d26a631efe8155917a8d22cb8aca4a448b42795130526170d025ae34118aba5d513e981b82545e43d0a9722d7a6b7eccc1e5f1711443385e0eda3151bc6417a56a8a08362f70ad8efae072a037d973fb3c52fd4dcb8a498ef974f2881699856dc2567d5f23747108f38c72c7f6255a6bedd46652f2edb3feb35269a34618ab1ca609b36766c97af458217249c21ae2346d03373a1211024984ff1937468c7670a017cc460756818858225ccee4cc29c40cfc62cb0cdec3498a3b8edaf28e7d07564a9f853ff9852bb6301ed38443bf28f58a14476f50bb7ecf14407ffaa82350871cd5a5a807936e5930720b26003a6940d23d4acbbacae840364df728eee0c54410e5310efec04fa82c72d9d1bd933d846cc911fcd2ca8d87bf48e6179d158732d72f6c36f5cf60248b71d1b08cdf1084a357a34d779c44c1c4d2b41e8c395688864f7584f7acdd2ac272543a55a5adda57563c517032b43e3ebeb970bd53ccc2b0145c3abcfd8ec559720051ea8b163ad36bcbfbdb6ef5b1aa7d4527e183ac8032da5e19d352bfb5216a2bfa5fb6e4d52d4072d7c9539de57a08778c0be4defbc84a7f1e8c055c2e86724080b6c814cd16a8ff33a8e39c2c269f49942557fe9eb1935e2ad583f6e3fa72bf4794fc716030dea1a345872e00ba8569ac50a16d5e9f118b749f63972dc8728ed040226b58088ecf39de93e8a49d024146d0aa54cf81298ed1ad646cab47645db6c08372ff664b6023fd445a75352b8c548f51d6421fee26324d2603367872a92e419f949fe5c33a77c6d7d531d2cd4dca1988423456d35f9f11a4fbc53e72f605de68ba935e738776f54c96bc8de1bc71957233e83dced4c28b7dcf3a597208b3e6b435b54acf6d811f89838a94ae13543dbe6c2297313e2160cfddb74c7213f9bba928893e52f41b37784fae9141eaac1029ce98d3a5998d64e222a024187c75ca30c57cd6d600786d64729a7e9253d8df6619f98425e19e5f7b1ad0d12ded52ba4152aac0df2bf512edd6a6de5b4bc6f7366985645ec9ab216fd497c6050346173a2563de87e8e2f761634f9fac3863397bb171d7467212a34ba9bb9b72b0c140f3eb7affb46db895620d4699b476dcd4629720810032339228511fb602be33383a494efd09b20807c9d59cdc7999a742e16b31c7a9ca52d72f687c431719a22862e957276ce60505550359f35a509ebe277bc26ec47dd7bfe032193f72fe1d4b4d32500f52d946d0f72e8e719ee1fbcf39b4649070d12f53b62f9a8972b25bd468c144e571998e835ed3e108a29a005f3560ee337877cfa26f227da2727869d6d4965f50b62e35541093c63aeb1a1b8471a3df72d55c8f60f01040ff7257946bf5fffb0641fd55f2678b4ae259532477e6b6b854cc754826cb71c0e3722a7a6fd106c41b239d8106abfdff15ddf462afc56a533c5291231acaa4212a2d80c2f3bd56902007f25cbb50ab77045adfadd1532a09de3271cf562977242f60746d4a3fee3c825681458b522072eded8dd88d21ba22546c764e5eb6d5e29172ff330e0c977a209f3261279ff0489bfff2a055de323e1bd0610939a06f581a72ae28210c953e325b4865dd632f972a9a05b7b2d03ad3b97b477e6ae5b87f237237eb57c218ba5bb265b48336b3557a2025919a1116f2207243609be3bfa6163f6389fb83e9a4436f76f898309850f70385e58ec4461f95c43546fb465c3ed872f078993a852523561668db62b6ea8c5dc1179b8368eac4615a3d14bd92fff37293c6ca3cdea1411b0099eb1ce3926325156a743e8d2cce951bcf1645217518725353b373d7b0d725b875a8af21c112ebf0d543497dd311bfd5d642d3949d6f65f0a147c1c056fc9845ee3b5a0332f274fa9f8503c254bdb3023809f28576c85abee9aa4c10371775104e6eac605d42837bb9c3ebc9f5cb2b0f9cb881792ab872550b36fbee348061b2244d30cd837305fc1fd6ace94b766f8cda26deb32dcb72faa1cd66ac533c1b4000d1286559e938750e997fe8b5f0f57152cbf4c89e47471fd8907f079c4183085eeba1c6711c6bf99abd09f26eaa7c0b17f4d529a00672b2f4e2a8419d829610480e0390d3045a2621792f6c886dc96ba35ebe3e7bc272fb218ca3af72efcd8ff8edf607709b9af8ce38a9c01dc9fb2ae9c0a0c6ee34728fc08cd78f12e93b4aa5b967ba6384c2051b45eb5f7ca6c910fdde5cd8206b6a54e62f2d69927546ca889b9b1538ad34729eae19327af0d27dec7de26b0b03722ade0ad96ec644b7bf86a2dbd86dd00d3d29e35029351e4782de3075948f94724ea90774ec2a657387addb2e53f529aca5aeecefb0c6beeb12bd05b34b33a34f61054075088bee1e52edf6e8ad92ec5458615f38c04bf2ca935c4fdc7b4696351f85e57d911d36b793f26402d3b9dd415a342bb72a6795538674fc8e1a856f7253fd522fa5074df007290c2117a38a72203c433fd10edb743e6ebe2f15faec2968f41dd6c1a7d8dda9d8a4010c12d5ddd9c50431134c513efd380ff8290ccd72316fb1d3c777e7e7532265c4a117ae3745cbe983697b6100f0c1cc9b42f711725f4c970c4b1a903a5912175de5a0b620ac868df8235c69e2e4c4fefa948539720df4582c45caf92d6a344be54da073d2ff858875f36d065a3e5fcd4d38d0f356289bd8b31ea7d1634225734bc737cfbd605e8862404fcab0f04c3922320a3e30a1613bdcc550fb0b3b0ea421439a99ff579b96475fe088c89fecf33061405e726e9446f5af27c015193cc7cf5afd71aca87d62c485712f5f8caacbd749c7987237d1a0623a43b1fcb698a01680b0b5a7093af95528e32b054bab2c98885a437289360beb031ebd6214874da46bdd1f660c0ef704969a87b1c27bec46e98c5a4791918c7f8672154a9e320838d4832a5a6679a887b17dee3869c8d5febba48772463864a2f3412bd6fd6638dc8fce81c717eca408fbdb8578da0ec36c5293fe724d950056a67ea1a9f4f12688cc6bfce03b6cadcac0efefd6ac8ef306e08d8b7223f13b9e12da0f272757e66a91b9debb270df1f496fc78cf02c3fb5ccd816f72baf520f4a93ba3bcdccc5bc47c55f45f7ffa4fe9d8878aaf04d986be27ecfd72aed3b3616a349ba8e163eccce6c35f6bc4a5a371446c5aad292e30e8f2b926722204e057b43195a34dbec7bfebcadd634b65be3f44be2f56862d3873f8e56e4ec3c4381abafa55ee1d93d425ad6d5c87a47612beb5ed9c6484e8d9fc4fceab314c04a2f14a30b493e8855c040e3c045c4978061f47a6e3404bb7eb52a1e6565605955101aec135060dd11d5e1b2a84f005b6c5027c9e9af46c43f060b4b0b3728bf145bdd4adbdae13d51949b8686ac3c056f2540d2c2120ed56cb0d734c28725e5fdca90e862be9709e3fd6170d9818d0aa00ef3690449e31da2f82a6db7472ccfb1540a1a2e41df089a66b16ab63ca8764df65fde4df0da8f9d712964dd572712e71236171584d1d06b3c940d7f1fe4d2d262cbd089e4926e21ab83b04b372ba9ed13b37edd83b244580041fbdd2b63181a555f76193e757022359838e4c7218173572a5bdddb686edd91d2e032d681987234cfded54b9d48e57b6eb975b72f417d6ec05711842e69db67882c94e03d6e30ebc5091cc41dc086876e4c6005e9f49bc6512e70077154e8851a5ef2847571adb8a70a3ef7ccaa2192e63b66b722cea1075b81d3809a9d37d2c9a8ad2e380adcdc6f4255b528eec4835d459761b0d6cbdefc1f9af1b052278fb21bd81a750b8007e0fa2b6cf92163b1898de66219435f08ac425410f4cedea93c9ec2a756f4eab5fefcbd3b8894b6215b881e735d177d6a18eb13f1661777f7bfc6b05c46aec61bd452e2986183e120fba487372289101cb4bfb3ada9fcc8578d61b97f9620d235ab2095ec3e6f27407e7d6da729de7f07a7d83ede7637ca79bbfaa3619bbaeb7393b99c099c90e6b15aa6a2872fed94f9fba4447970e558be8a8c67976896b289cd2cb5f6a476068d9dda0150c29b35bd9ecc07d77fa27ef22efd6733fc8dd5bf01cb45838fa4c514afc752a1d5170f8cff55ca3ead38db57f2f3d9cd4604f1daeb4052a9a0008caf7d215667257d3f63cb4a6d3340619b154ced3606b8623e1afa5dd8683db3c21bdda66412f620d0afc3bb7710811666e5b01d703b670db330088e219305c055cfd76c08b72c3d278fc6e099e804f77b448d7134ee41722ac34986a63e07ae496d731b21c1a5fe57d65921666800045c03439a1282a1c33ca34530bdf0775af7637ff053f725452e0c4a8bef24b82f382a70076ca6a70a634c0b26ce336ecf97ebd246f601c1d9902f2dd03ad2de67f10d61b83e92d43d5be333cf8efc5d7e0e05521a2cd72dfb0d3bdbb01807560600e59c83887018a3b0eb4794127af828cc5d8faede672220dd6e6f965a05ffe528218c48eb4d4657bb9db29721730905cbdb964336d7242e81421eb6801d1f0e9c183c107abc96da06653be7bf629f1372cdf406d5f7266f6c93db4895bdcef9b8fe5a8ab8d9210387f0059c67556fc25076509da9072945bbb8e2ad88eb71b4bf986a1169640814be33b8afbcd75dd9d8810739a63726accc5166f75054e976bccbc931a2f207307e04e824f29f0a11611c9a062a972de9de4a83a20ddddf653feec07eec4f246a48918ef43dd4a6862af42afe78c521592e5c8722341680b2aab8a66570601a8b6ea387dee9fb1cc02ff6ea08d7c0994244d73a3c8dcc544b61b31574cb40fe5ad1777b8d8f90c4b5464b16d602772e934153f28b625aedae77ebf39c5536d06f1e5ab410d309bcd65a76a67cf2b727a8464f640ffb507636ed9b34c4aa685548033c456726b79def9c6b72a17c0723ffd55b8d5ba4c20965ce866ab64166c33a784c74c80e61412ad60450e297472313b69d9ec7727d8e8b7e141482aa20d7510df01966930a8d330b577abc1af72fe18caf2767ef510b68f463194b224d290af07e9fe61e424ba5769f273506900695fbdcfb00f42ddfd49b2248d30f79e53900c3dd8d282a1dd0ca0aa80b84c453e9e96e39cc5cf249aa616c3a4364341f3e7c5c8cbb70f5a5f8fa571e55f65721605f35a2a261d48b4a4bbbef881d857651a93cf6f28294d3b901b8d2e005e65a9cf49b0f85e0d27cc9ea62ec6b5d13e3eae25ceaee9755a5297364532717b61f5f25ef5bffa16097cf9c8372c27bb53197ead299c51a66fba202286fce3ee72a5d3ad551d1206fdadd9dd013bd08ee65e5a658a14b9836f1b8585711add1651d89cd366fead19d4d138ad01c0c40089682d784fa5a1491bedfd0e4f8dda5e2333a8939ae3ba2a60186f37ea3d908220c0e59763b5e61b2c9907cfa91d2bd926a1aab2297c60cf72efd5e0abb19e44228c9387f0b515b0fa26a57fe5b94e8c728e9b361ce20771a12d4b6030551bdd17aaaf9f8b89a8bce51b9f31d784ebb472ceecab65f0052f5b1c0d82b78949b4d56879f8a38dd4ae74a51211fb6e0464117ea3a0f7295c002eaa0bdff8bd1572ca72527202dfe481d2be41798b21d96f72665dd4fd3b5de63153a7485e061b7528cec4cbf3b12a7f7731e5750130a87272a763705df4276563f22809a1f2b69b2614c905dc516d935fd8afe9db8541da580132c906f0bfb97add9db6a8d2fb57b3129aa7e85236aebaf5372242fc63862702cb6555a77d481fe22764a1c7130bf9ee50a2318f0694d3fcc25f45afb9df72faa356c56e2d944b63366c090288db3166786b8d9fd0902440ccc5fb4eda717271f990eaa54f76116c3618d1ece847f8c06bbf1d49231519a2d3b5298f5ec866d078b30b2a260de3735b82b9ce8f60c6b812871302a9993fcbf0d09e9e6dd67171cf5209b82bcf4bf97c15fa0dfb8acab4389c8ea3ce6c4a53fe9b6ff33e867250324367361e32eb1994e3d4072521e13268c75d8c43c00a5dec1202c94fcf7268af2fe02b049676d484a5315c616faef74bf387036bbea73ab9be1f9d3ed77227f4b070aba26d5d50903a7a209331811ee1a7a50c9640ba6ee93bec8093df726728f6dc061d547bc19377d8c94e9bb00f454aa0a4575707c95764cee0fa8972d18da97ebc282839f8fd617becb44bb1bb20820de03f0149d15f4d1b2be8a772a5daf7bbe98acd0adb510f782789bd6fa374582d092d7149e9ec5293c502803752aa570cfc914f78ebc0cb8a6b4d20039af31cd4931eee7cae0171fa6a1b796201a488528e7ff1798ef90ce5b68b3832c610a6df16320be4b201262847ca306f6a6164c679ae109f9fd7932ce09431eb3b228e84b3aece2ccbcbbc8e7f9a5a724192e2af5c2e0ee498287138453449951ecb4cc18238565714560fed28b13e627fae4e25a5b580b58eef012e520a14f87e21ca1abf3e07ea05459f768b8ff672d0db7997f097215c12e4ccd2cc78ae55e16bf8bf43ebda6704a0b001dde213259f74da9c7672c1a1a6cc50c7a86b0d8bc89f91373a04cb33ee2df9ade4f34c727949db774075430c957b7e2408128cfb2d591213eb323bf89666d4b7e14755722a37b2ec839150de4cf67b128e0acff0b8e42aaa9489e73cd15d5db377d2c84baa8f74bb86536a2772bcb71ad4bca206b5cca7afe3f3bc758aa3e086ad4d36729948ef09e23863d5ce57257023fc6a1dd668c08e174dc5438f5592ac662c5804d4da64caf9e00b4ed9f76c6b749f3c61aa9b5ab771caf74a5ad0e72bf79eac7211e42eca78210404bd599d9d489739fb87a8e71fcc0b7a914a1daf0be0dac372d2a2cbbe9e361c8e15890d7e6ce222c3fff9109c2ccf9f1bb2854601a8c5bb7226ff98e4ead27af89d907d4492fe811260ff4bbb1598cd56da00b3b697a5884227afd156d1ca15bcc60c3a6e7cc28a076f48a8aecbb0da914a9f2ee037555472fdd3804a0bf81e312941957681ada3c4b40016e540d2fefd5a938fe06e7de472624c4cdb3dfcdd5667cdf7594db1f37f79f61942c0cbaf4102d75fde7d2141200c9be1b848a56c75b225aab38dcb747ea76c426d197b6ecb3372fcf4ca5432232c9cbda8508c8b1f5c267411b2191d23d59c368770fafbefbe0680b1add25c537ddadb676bb9526fa728e93f609646b5f7d5935c0c97201ae63bfb752cfaea200cf89d69418202bd6e39a36a38b1b2e62490bc42c9088f8d001aa01251c5c83fdb76402eafa62378d0abf4e50370bbd7877bc9a1cfee8f426126ac39bf879f4396f4ce7336a2d1d8648bbb2155f937f8c04b832d1d03c0d5a9a3d467a407ce724485c3080ea702ada123458f2a6c185ab2eb993707cee62e87d280393ce38c721b48ab6ab12de396a15f2e8b374ce91c4e3eb59e83421144a2b12a3b73051a218fb3cfd62db59d27b82528f18695c8955f8e70fb7d1a5440d649183ccb6f5672d52ceb9919035415e68801768281fff520ea8e9fdca15c8cc6a1988abab129729b7cf2c5138443382732e82a0acd913d1a476f34a3109b435ece09d886df1672ad4265d9f0853a2ba66ffecacf0820e520487f3a38c451e0193668e7b96b547223960db5819b81762b6ce446056d70d24c93d8bf517eaab215f4fbb955f38f08aea943d59574f529327ff769ccc428958f13a40fcb85bbfeaa70ad9d1f0d827286be27b57ff489baaa7abccedcdf57e4653c90282e5240baca5d7c128d038672a60b09390f06d9621b71fdf5bce50f1d6d22766f81776b6f9eddbc4a2ef09644a72cf5f2785cda5d0748d56c4fbeb28a9e945daa8f8c1e4dfecec193f01cce06b3355514595756fa21b5002b0576e44ea1877044c3198fe22cd3b52aa0d54a72d95ceec61ae063b02837fdefcd68a190ccb20d99cbfa0955c1e30955b734c6728f37676de814b4a2e199273084236f06ec5382fb1f2c30b0cf707132563d256d672a192ea8a6e1c2e66d80d2410cf5b75d5f5b42e6e524fb99ce1cdc973dc272e6d567f973fa25f6165e487503c571310a3ccb34c9c254300f03e15c8f1ea40049a48fc3641654df94c2ee424691da86b2cc8104cfae7065f73ef6a1ec0d166962767d9cf3032008469b2c1cbeb71f5f26d134cfd7b98e3028f9e622a6461c72cfd6e549877f837a2e5d03d37946156681363d3317b8f8e7b2386ee569d34b156e6d5641309c29fa2dd9bba78fdb098ac36f3522bd154a84b05dfbce8ec7b172b203d7c74d86e0a6a8ef457a948de39ad210bc281983ba0ba401c68a1b41c072455a833c7136f7973ecc068a5a201b3af7974a7d1d0bebb4992494a83604f972897f0e44ccc4cc51c417ffad94466cd23de1e5303594234381dba73571b87f724d4aa98064a76eec1d6235f01d9464c5bf82cdb1dbf63f94e87dd49f5d4c8b442fc866282d8ae9bbe132211192666542801814d60fac3347288196891979365e24b4da374744edd5c77b0f27e94438a3f299091561d6181629e8ea44313bc27256a3899ac87ea2ae678234fd9c315ce87d3739e666ce9dcd91bbfb20f7464b2f4b4f27040190b3e5c4b39f41d48e7511f3a884c89ad02d18111b703a2726bb720bcc49218f747e9e8264b8d9a5133fe435cd1efe2e15daeb728a95b0b0a0eb57e04f0bea1495c75e6332cfcd4945f256c2a05ceeda0cddf4edc6e455ca7d677233407cb5f90edd8fce35dec9ec9ffc77c39b7d9a5d7b276fc6258fd3331f4d7051fde29df2ea76f5dad31fd00d3c8489e9d35b65e38fcb14df0841103f7a607199c4f543ab23ea070c5d6c62cabc11765af89829f3493fc89838236a914170724f452a93f9f0b1e5912ac8ada7a0198e1d0c2dce5f8c92e008f2b5e52339da1c3bd6c82124cab289a23a53ecc8ec8e3b2e928f4f81f60d8dcfb8913a5c3cf772e131e333f1dbe14df5f33eda8a0f97a3411596f7449886a91e71413f4da8895f28344a6e8c6634535a78051ac85d7cefadde06cb8b8d4bd1998afca143d57172bbee0f60697e4e90dc79df6a54dcd08630adf27580fb5c01ec3291fe5d95ba17f8a03f415a052c0e86bc59329ce0993f8955bcb6fade3083af579cf0ac6ea47205d95e0c53ee0a2d871647aeadc8b92f8b17ddfa87d5bbb82920c5d27eb31f32ad42f5bce9ea4c2dc212fd02eaa1638e4f2a638f55c640c413e7daefaeb9b37206ab36184559874e4d3d23dfaa4ac7c85cbccbe5e500f9085aa6650acc54c85a5fc8adee2c5c52f9de3f98153f44159424447e21ebe105fd9a22791cfb657272fb3afcbd43dfae3b1f82e5655805da7df3a47f69413d7c33903dd43704af2e7260d5e31118b43e1fd0b0a41da98213ab912af12bb66ef3d85f84f813155da332055b22f6f44babc72ca2aaf059c381bed8d348dcb6fd5f7626a02b0f8e9441726078829cdbfa8859361b6c8529970a795e45f68b18bd2797415879bc0ade945fc01532365621b8e657872ba3bf2986b11b09d8261cde2404778443f95e51434196204bdac0111c455bbbd5e0688fba723a31e200be88be3736528a8d9e497e72ef1a371ff799bbd34826e20214e2a911fa4be38a6082c0773e0a496047461e67c767ab7693c10df57c5e8b9203bdd4c5ab67029fd78229563b3989ec0ae4f472a3b4c6dae9e7bed978d8060997a23eadfcd2b72c68a4c68c81fe4782ab94dc722948e9b97fa714a0ad7dec9905d8337ad4baa64505458e2e86268a8f4766317246ccb0be3239a52c46b4f6cfe9ddaf50876db0af5bfbfe30a788a13d7549f472a150421858854825befe4b84ebe59f90d551c0bf98cab90ee56ee3f47ee83c7259f5b350b1dccee8e7c7fb45f81f1a95a2012234e459d882c00db8524f069a72867fd3c17dd427278548792cdc8edc5ea86a76a240f88b1e174b42593dde2b0a9ec6d44c7501b141c2cea3e24da49a19cae18ce04c7e5a570c1db91aa17d736ef91b672e3afdc9aecff088a8d8f5cc13184cc5979566c44525b2731c76d20972a67d3e5e203dbbc2746408b47d274596a25b60484f0804a11f69c0c708ec8d729eafa541c9e8979553eaa782fd9ba6deedb4a080245cb2e6f9862363b5c63c00103781e1e77588629ec92d84fd6eb3614138dc2cc38dc40d8786eda99d56a019a2c32039686affec46c745ead5033b4d2984a9f6969e4b0cacfe9a2356f7d323c4e24ae8d16dea215eaa3f73d5af553610be606b7b0a04a2025f8550f2f43372c1d978179840dfa81c84c86317d75512ae3a3db372edab38be50f0b9b50198721148de4adce817b94d2905d45d417207a4de8d637e744dc2f6ffb67b3eb4b20ecc80d5eea5ffbd165c841eb449d2e8d37b25f97a459569c79e9f7229b6807172e4195d1c7a7d0e457c1eaac5d5e59cfc7c8c700819538917b95862bfcc976b725f60251804c716b5537fedd33d86719fb235e41bf4d010dbbb8bc16df305665ceb4a643584fefca0d4574135cd99ef0613e00dd00de2326661c9484aa6ddcf72f5f6f1ea8de42020afd258217f8af3a96f9f3dd7c1b84597d323938f07ae346372fd7cd78ac71773f4c1ce9b9595c375e712062f74bbb73f02f073264b155c69a73ac83c2a5fdf1a4ba17844b1dc9e8ba1abc70fe243610177f764318fbf0d72b5935e6c2f39457a819ab66032eef061c5bdfe4da47aeb87531ff8c0df0b8a72b2b3eb23a4372273a12cbd8b741c716063af1dae963f42b41207b8e58cb3ee14fd42f77e58542b989c427c1983ba3d9ae6156dbea8e5fe0ea94932a6f4f292721c12a9d77e15e006c660ec023d851798e8030bb11921a6440aec83180a3fe3494d77e356d9bf0646cc09b9533e43a6379b4ad2e83bf2b38ea5d50741da846a231a045dba1d5a7352e6b28d1dacf37341f7a26ae43c1332ea47bb4625596ddf10c6491b3494ca352290cdfb5ca1809caf191cad680c15193d71d4ae220ccdad4f6755b0ac1b205e9f4141b7ffa1387d7e403ebdab0cae97037ccfd1d4291f69720226e263f4d0eb7d55133f730b6f83043894c6b039215e7406bac1871169b4727cc01bc702c78843d16703fc754977e8b2711521d95d655476206e989994c33b398e9fee9947c1e3c48d6fbe0a0e6343dddbccd5fa64e2c79e38f6e7712b1c7224c8c739f70540a6bb031018baa14b577245aa7e18aac8327eba52f2eaa8c172eedf17cd0ffa9fdbe8134bd486c5ed9db9fa8d5a9a7be358044bfd7e8be44c72c7c534a898c5bf482aab0250e1125ca6b3f25af0821ed315d91976453bbda95279ca077d6e4d62fe3cd6daf047e0323a71d446321f50c7622eb3977010a2c872d57daea64ca415fde973812689c694acfac68b26500d53d095e94522acbb6e25444b7bcb12666cf32480fa630730d9b2e7bf63bde6d2c7d1cf85abfcd7f08472c95ad95b49dd8a54b1f12a13ed4a950387f27724a13044ec8638b0a2aa1ec57237045fbee7ea600bf2dd4593062d272b3bc314baa86bc8ed2c34ff1350ff947206918f008c31c98d886284a166f437699f30c77ada43e03209b2a1d4687daa726ed31f815fa3ecf6db3f2401dee03e41fd094c49edd638ae9a5b2439338a2872fb68e0556a632fe1ca671615c8d9969e2e7a9e74d37926a20285672bb5ee8b721f8160af60eccdb3db86fe40690e1332f1c3ce3951647380d423e524b0e4d5729a03ba1ca4cae5e27929926e209b999e3ae54e6550960e7214f95cbadd39e8288053a38797fabae198daee830dbbf5f2325d10b2b4216cf09cb4344255993a722e075f9784aa58c14d36361a2e13d3a6a7144881b6f6fb989f87d49fb09b1426748191bbbb8a0a28d55cc61c23f2e4830fcbb516bdcb7bf6dd9098ee5802a972f697b7289210c623c1d8de31de8c9c6bfa6849d66045804297889520f480933e2518d093a7ed8dc42cb76a81d7960cb86f4d9a958bd0fd96516c69e373629b48133d3c8e19b0176353f245aec58a90e47fe62303d8ae116caadcf1d29e0ade4e2bb09fbfde787a6d89de83180634d9d0c796615e12a79485459afe3e1b3fb6726a3f119daa7ead196459bce47ead5935598d22317b669a35e629d60866347c52f9c997138193d1eb3a9b1513304816929e2fc2afdcea243247e4e56e3b560372e2cbd9dd4ad36ada8da1e540eee4b9e8e69e704c29a9f00184cf86f7aaa5c8389bb5faf82966ce4cb03076bec23d4bfb02615bbd1d64f129ec3bd6eae6773142edb3adc4216de58cfe05c428d60e4549876363e9dfd73493f7fb50483dce027257add365aa5d19d41f31eab846afe5c5aff9ff6d916e5a6a73d8e684a977a41c18e13586f924e5c13ba306549de6fef4f8dee34d1211a315f0cc3ad02742993c6b6103ae8f4f755c1b3574b42061e72ee3a28c5fc85ce7ed0454a15f6b8140725cf7febd9ff9c5e11a8eaa80afbcb2283bf125951b392607093ee3cce5986d28c9609b4d435b3cbe95d790598a2132ab436d838157bc2d5b55e6daae11e27b668d84d0b13f457f852bc4f877846f282395226c2efadeee29a06df4c585025b726a36f4356b529cda2f877c1efe53749395a48f1f3d589386753b13dcc9d2b372e5784e6d8710e789d862add19bccb634439b14ffed52da7bfe18898c1e85f772bf50127e7b69cb084c57f4e37e6a61c5c16beb66f0438ca75f7725e0ecdafb72369cfda1c14f61d02e426bca94730e83dd145b2b1357647c58d9be4436726572c55bf50dfe4675e070c9730e001b26939eeface45b12bb9c4196a59dadcb29725b5563147cb0e6165a5d5076fdb67265ab66240a270dfa4686d1fff382b1c532c3ea6ee7fcb32f3543cc6fc1f3b710675178cb9fbe5683893b374f3a1ff13d072488e7f27f663737d1c3599ec5bb84f0620847461fb85dfa779be0f8e9429a7244b974617214cc647a1c628998091aeacccc1701ef6e9d9d6c82c8222161a46d73ad2a1ad18c5af15e032c1d03eae3fb1bcf9ae9d0bbfd0f3a3764eaa8c70f68683f9e5edb2fe71d4efc343880e31b5fc8c1279ebb746a7cd2e14d8b11fbda727f533687b26687db05f35650b07a48a13eeba2da1e58a68ba3dc8a40b1f0066d05f172977d41295eaef157b050935ab16142d68deb035e2e63787d1e75958e417dd5695042fbb0564d960ac088374a293f5e5a4165aa01d1e8b568866aee74109f9b3c08f152266e0e06937d0efe2897c08fa600c5fd69c0d064ccc4b56c8d3178ccb747dcc12252e6b0fa581e38d6087a135b0fafc0210bea7473972fd0f672592b3ede11ff1847bd741004012145926f1aac386eb88c4786989cf6c068f77205b5e1f238295a5f826539131f62f038f283fcfb94af3a1f37f6bd8c6d4f173c8e35b97ef1a04289d30383f136cce1bcea015259b8fd35f27c5a3310b8713a4bc60dc096bc0bb0567b197cd987d833f74d638d74ca637e3ffdf97e5a57ff927220a3a775ab4a06c087273fb1f16e81b6d1dc28925e808c6cb9e96d7eb6173f72e276b11bab940840a4fa73efc163b203ff0d81b99a4aa493e8228d092fd4e972f9a7c7f7a0e716f5ea53647a8125aa7c248ff7174400cbf21843915e1a185b72479ea2a7e50e0b7df8f153f78f399cdeba126e1f7b9681901038361585f62572304cecf362ac62674be866eceae0cca2d3c249e186a8426f27ce218cc50ec85f6d3a33ccaba22a91a24246a00e838de149000e4555ea0c9dab53664c341f151ce65c9754c0f7b75331266e942071f2887c12d98e51047aae4e3cf7051d2b0072531fc67e7be10bb6b45c8c63a70913c30d108f334eca9c432cfcac5489e139723f1b618a16def95fab8138fb63cde46ab5f758a89ffd57ba4f34f2cbd2c1c272aa84ca8af2b81b63eab07173afe11aec20981d53fcbd574588225be1898312723d0bc59a663fd47ce5717e6b9b2458bb0ae173ae2f0360748b84b67ab5e2e972f08b2afdcc57a50e9cd431b41fe6f44eadeecb90a7a99c575c410b351329295ae8a9459b096b88a2d9960ca71fdcd94cbfea16c8d90cc80a2b6c9d3d7a3a046c6690599acc9936342e7dd8d2e11b273f7b6b8b2fbeb417b3539c0ca0b1c39d7243faed68bc1f272c29fa610d0093be9d6dc0aa057c292563b2e0276db6ad9d72b67205aeaa63b887b6e2f9030ea36acd2479c83b94735b4c2c743e0882e87a72e2d038828ace36957a93916ccd985f5e0c4e922636e000250e967e8dd6fd7a721004b6a42a4399ded44bec426ee7b6463d5e4bf85b624820c2b574da58849572527214690b4abf0bb9c9363cbb1a4a286693233d4a52aae80b49856b3f7c1672d2575cef07911d8680e33fce7a6f839704ddc6436e61ffbe95faf9fdabf1ce0b6ea5f6f46643b07480a90b6e4bfc0776e11ecd63f1d416e5794fc0dc35136672c11ffd617bff83720c63e6a6ef416bffac23917a600109137a3fee1b49e8ff72054ac295060aab66b1e428cdefd8233176785f2a717e2c090eaa63c73d246272f2874a5ea1c4211c314e6cb7a5ce479d5dd02807b3ee3a6bd8f69aac3cec1551c514f98cd209c0620fae10f2f7a14c9cb9c006ec4e142d099a73e85017936c72791837011c535eaf5efe2dc5cc4d59c0f42babe3b91bd01429577ede151dab022a1292118285c4c8f42e2541d3066fa8da73442f186ed293b326fd81e5261972b41ab5501095f665b4fb9f25897e8f5311699d51f22c7b10c3d82522e5daa4723bdfb4e4aacb33841909ecd003cf0b0ee93a1f3712d640756f856c93cd2e8872aec088f2e02fba5a025a043be06bd5976f74de643973dcc4db2344b6c671b972b399650d4ed6b47f4648ba0e4a75f2d47d82b34b08981eaf09c21a7be2e1c50b7cc41cf1cf13ec4f0939ef3b0ba25cd17814cd4cb85e97fd5deb14c043f572723e36a897de806b7d427659b70a3548982fb04589bc986b3ae1d747874bde0072f5da36b5f50903ff989ce39da560a7ae0e4668d54350e2fec8dcc4348c2cd972a107b9b7695085b34e0e44a82545befabf9fecd8936fde32001c16b45ccb523f32c1aba1f2e38020cf0551289801c67aeebb245ebb0bf9c35a80e4642bc3ec540ada78fffa4ffd180d8bae6a8d17a8248979e320a0d1824407c62732d02db934e3018c9ae6538d9c97e7db8504f80729e93702cf8c3c46b1b4658c6e69e39172609dfc31346a2ccba2e523d17958ed0fbbbcdcc1f988ec3dbd6155bb99dd5872dfdb5f486f73feedcc607f8f7fbb500e35c59156153a4492a673617a0025470b9cd55b79958b6006c2133c23ede08d47a02c95a15f321c31257caaa01a7f7472bc17de16001284849e38ce0ff5d711734563ade9cfc1340fbab5e4ee96be7a721fa3bc93a56f14084a318a6790276233347ae6eb8e3a29585574a0f1cc6d917206d9bfc1d59bac0343d714cbbc7dc1b7e34fe9e67fd991f36ae021d12062e0723f9538c685a22438a3b81e30732f0af43660a5543ce42861191c4417065b93092d87cdc4d4f448a9c88546d35cdd3145958504b531b9748fa208088349351b31694078aa30321b2554c0e3ab854f313f122e924d58b13af6a0f95b2ce066f7726c70bdad1151956036e81b67e915d8fe0d450d368f2a92604c59619803a4ac1c1c69d30030bb2a83ba0c91952893932a1edbe7b246c320d51ffbc26e1c80f372728d8acae617823f4a9cb12004335ef0010f8a8bbaacfb71c7ddcb14ff95637258eabc7bba24ed8372a1edcdd4e3caa0fb9af70367b7fec102e5c06ad2ed5550db77f7e17f25371f41e63241a837a6f051968a1428bb7b7d2178ca8e9db5923d802b3c14f6d7e6969813a683f867ee1e780f22feaa58657d86a924e81ffe6532b35bf91522be8cfa41fbc631c7d0fa408da475cfeec5ac2a5b2b34e9366a6a725a97bfab54763382232057b796d671b38c324905d66180df149c3b2ec903797297227ea53fa4c7dd052a107fb76d394d06638ad541855bdf9a3273568d69f742e547793086b93aeec08b0bf802781bcacab2e926f5635fb355e5c010843e6f72319249031409962a14a728590e5ac5c0817baff671dbcf7393de45f23429f372c38d3d7b190af9140bf502d700ba2e319b30fd0930a8040aede3833f9946df7282fc67a2eec0a625cfd1095fe379e21a9b5f96a6ea01cd5b315e775ae3f3f272242d2cc7e031d391216af1e0833c2c18e631a5493c9ae9cd037b3b7032169672fffd9f7675642debf82f7e0ab637287a0c220b989a597500eea6f7e33710f5720d8ce8dbdc47d8eec7a618515a7a82c2dcad70ddbe27c99974efdd8f237ad172670db755c98e0a6f446c6ba9707e33683e7ba1444f8301fca8ebca5a7220352fcec8fded55276f652c57b39afa3e0a2b2f196b2b62aa4db63ee595e2abc11672b8d7ef09e8f9de1adc0ad8b1aca0367e083f83d2b09fbb85de09cfd2a8b39d4ced20de045f476f437745506c62e8eee3c012cf9d3c3d7782154e7398a15ab2554f4050b67ea2e7ca8bbc0bb4d2aaf5d41b871c7d1bfc5d2c7353650e3fba714bdf9ad056168dff6e319bc9c6fd888f5d24ea5211dd5f7ee76d270702f6f79e72f28f02313806a918d54ab551f146ee37a261897e1d24adf4a080cf41c25a4972f15ffe47f33bea33f53f3cec4d44187fcc3682a4b26302939e2a4e22676c23729511742fdfc4d7c1857653d3015de168e11e102d7e4f2d79d49460b3ac365072022d786f10e0c70015203dc89936beb52de2a892fed6f50c098e63605c7265720a1907c8886a9d2dbafad9fb9e1a16c95059af8635cc303dfafde14a57a3df724ef6350dd259838516ac697375773c26d1638c880568fff419778ecd742aec72204ea8f546b6dd3052a12fadc505056b10babb151b0fa561e7376f88b193d37264aa2d5ef52f7aae95483ae9ce73106b448b1dc09205a62ede1ae4fa5552541f2bef299f785942c65b35053b3d501faf183513584ac4c9cecc7d9a5d9d4406728e4b2c289c137fbcd4cffc41e9e0b271a28bac25bf1269d7016915a66c249706e69bd54736d17ba18bf068b7c722682018cd5b144a7717e29d51844681eb2e724613af102e51469136d7732f4212ff33887fc12072cbf9d10a9a770ffdfbea72d4825c8a5d178ff741e2d29c3ab24e69ff5e4b2031adf6ec9547c9dafe1e57728332894afa807e5fcb6185d355ba52aae7dfc9497d260be9deba81b2e4a896149e420addcbd463dc0f36bdc5ac0a56e48d8a20fbb801883729da90c65f36e02480a834dd1cf6dac1f7f60b8d3b9314a0a8875ee3f440aeedef8016db6d7ea27231d9bd4eda6c74ea49610d376031ab13cb5e3bcfa9f5794b3a746b775508657253e6b5beba3a9d58097ae6b5c23b023198814430f1d857635cd69b55f0f44b3b5eb22bb335ff4dad327ccb74f4ce7f868ebc85ab9b1d62cf870c71bb6d56100d5a7ea738a37e42966abcc261fe740e79e54271dcda347823bb32cfa8b1e8de722f9234ce838321ff888b4414eaa09b2c17d3038b5fd980f9521e6f87da28e17243195051d1eee6c5df556b20083f66227ed8f90a5a2a617a158e28935b216c72720e1a9f2658678de470b5343606ee7506188833110e438e7015c0f3767ddc727438da8e653581158b1236372ea7f3bfe6b689b7753bc529769dbf94ab60ec72c2a15b34556a1c4c2a45283ed36eb01d9858e62006beb83798f516a3515a615107915a986b2dfcdc35647718f87f7c655ec3e4f41c4f41c5385d7d3affb6936c758f3e1471a1649976649b5ab8e4f9088a42d16da0b05e9c017f918d3134b272d8d04b813bbc8a65eef1697d25bdd58f117e1bfc5260a50f246028bbb680dc728d178277234da3573100f36f9bf3aef4781c342c4fd185332ff500e4093066728bf5029b55e68d83e991bd937d57d456351ff6da512c01f78ca81d518600d872dbe92a27968e17eb81f7a76dc6423482880e6206e5d18d82410e5b65fd019f7225a7ee7ae8438b7bc0c870e47056ade446e4e4ab6c19c069c1493d3ed1da97728fe2473ac10f713e71d075be671eafa954488a50c179fc3746bb37acdc1fca29d225cbb2cbca3d1f23d56cf058065e853298f53b4b80620565b2d0bc2e73dd7261a9e24ea9ddb3483ec34b0cbc5b0c71b345ddbd0bc041fe878a8ce8924bf763b5a65a1c5182666fdd5a094c77b4906eae77ff221ed967204393e28087adc75da54d895a41968e096742b30f1849830199443eab3c66f44f89420509d89ff66d0d980d6db707018ee35a1940cb04b82b0a4fc9d3961354c8bf3925af0c1ffc72cd83d71dd6c9a14916014e971c180f7bc359474df48399402f25a06932b3bb3af0f3e975abcf2bc876a987fea39629a6ca4e96818801017e7e0559023c220e72660682857d5f6b56b5832eea4b0dabe3f6044952612f403cf8802efc4d59215bbb79a5e4950aa66bb304f668707f55f1db5c48b70d53c537ced9587ac56d4e72d8e8366f75eae8167399d529b73f31c7c7e74d667e91ab65eadeff5a9168f4729317ba826cdf3faa4a144a9bdaac3a9839dd07379293fe9b5379a5e8f1b1d368571ac3f3e392126ec980d66ff2c95afd98c51f2f034743bb3c73f7c70415fe44cbc0ffe4707856d11ba849b11f2d7b15843b0f2d68ee8346d855e2b06d4942723751196f9d86a792d2059efd2d43cdb13ab556476dada7089936b5c8ac298b72bc2ca0ac29b398dd564689ad81bddcf8da79ab4054e95f27b33c028552d11972074e2de37783f77eb2d89b1d84a7d5060636dc9311484fe5787bb5c4e5cdc17299b6fcb45a318557472e46cdb26929f0fee08bf56661bb85cf9d406c343f642575477e2b0292aa8be5e37e14c2b4266b47a5bbd74e003e4b43822b540da20d72a748b457e7b415bd37ffa533267830dd3d5e05ede6a6f1834e1bb5689e75bf72a09dd1a4f6e5d92d61f849fb2cf1b59c05c1c636788091985e6450f577f6a672ee4bfdc806a6176e7a82bd5085a517bb2c98a60c7245922f83529e802a02017261351d6d121e68436309e582a390d61b28fbc73b9110e2c276b7b0af93b2e47285aba57f30cb0ae60cf8fc1df6c153653c6cce23ef7ed1474ecff82915f0372afdfc6ca24f80cc4a0af94bd82272e95080c7a520048fe49f2d7e6220a8e5f372939d98d5c09c65fe2f4aea82a10ee0e9bf57d04f3bddd9db637478c1511b750ea1fc6a32d1a8a79151e0900bedadde91d1782be9228b8b31d87eb5a58ad0ec72b7bb1ae44edf12d8275d82c6f5246ccadb40cfcb4746c707a832878a882709721052cde054d9cab5e01c1f28d60cd036aab8e93c775c08d04bc652c3bd5dba6560192f89dbdacf0d7f3c09741138ce197efab8e10a34a1e9c4ef478beca45534e876ecc710f001ad77665528a07ce7b033a9786acea589b59813cd499f64a4248e70afdb2708479a0face6a17221d63460e9b0ca92ce75c0c9c5a83e530905729d0ecbbf93cab3051b070a26a7be42688a6e34ea43c52c1dd286aab736477a72d539ee5b584051c579f3bb3b847a40df796f8a90f52b013ae81e0c7d734eac6673cbb3a94bce99c4c376575a40627924a71d26adfc94a3818eaa2881e1358d0810d7e5c24d20f8962090e2493166bf201ffe2c692d93fd853019c146d88239727b7a64e1d2cfc22c5d9b21ea8a1daea9d4b0917f0e49af1386b7f196c8ae2e0eaba915350d3261b2f409f898633e3768aa0c77c1072c28d97b22072d4067544c66c9a5b65c913d39546d88c5346ca45e8d983027a30393f9d4b93d43da6f0d726f289864466de6fa41b8b5b12e89f8ddaf57a0797a1338e36659e4cc6d42294977808ec59eeb2052d084f5a42b55d45a48b10a0831ed0487481e9e80c2651833853bdc8dfcdc304d37e42342565d3930c795a424e56c68f2090273e5dfb03b5b25ccfb30b4410deecbe472b338aedd91341ee470e7df5e542d3dbe18280ae564461efeb22eb970b4e496fa517c5faa01e3a1196a84a09f8853bea2a9930a4e72f1ca2d7e1268128765c47f1769c2d23de68a572a2f6dd5a5db04e4577e43c572c026c063507a163384081f94b9075b4eec35f8f9913f12adb94dccc5c0964e7234e59a79c01fef762c1b4eaf7a63a58b482d9b84f1a1ac69a8ed4d05eb515772cffc84300910161e223a2d9f40e12bae0e729b7af86f2a1133dce0c56e9b9e077294df7055e2038e76836777c56dfb9545bcabe16b45430ea9843d141cd1792e0035a74d7a35a682ab5840a2ee2b47350b03815e78a64665523656c942e29272ad47dafe8b32d8c375e82c018c0db1238cde1505a2586874d2c65fb307b1ac72ae851cccc7a5b14373002c287ddd106324e32d9f97e741db65304dfd42063772ea6e71ecfbf2b207e09437ecab66aba3545adf3c6a87b5784667282c57ac444499f2846cdc8aa6c83f7446ddbaa09decb371ef222f375e10da746427bca5cf0a3fdad06dc934af870e3a95e22af67c032d676c1fa9938e9f416b78898940ac7287b7e850642b1baee621620be781af22eeb4a81e8f7f798b8174dc852353d36e3cda622c827d6da298a074b7d5d9268237a0ca3ed9a4990e30bbb2a400344125fdbc56a9e01a4b11afdc0518898e482738a994e780296727ce3af42bed2dca7217324ba9913606a27e20e5f7cf65e6e22a5ca4f3ac1e6fccdc1b60957777d8659e1df2a37bb2c7b2112e471af9848ce417c2835c901e915d0c905e88079bdd203a82e72af890de9df5485234674ef74158643a7adb36407c0d3f1d015045fb255b21bf0b93cc13c7a79047bdcf1eede69f4f3e0011f6c58e10dddd25b755b87264c87af97e32d1bdf3e0108e98fb27fd69a3a0a3cab5be1bfcf30a86cb5c7472f57065ce0683480c5c0ca4c6853858850dbc4fb4dd6d062c111c6d7fcf75bc2a8e83417aef4b10a71f35bbff1c2e2f68df9fc2f3dcf04acb7a80456cb00a8227efa087d9105d167fa415742b03395164a4710b8826cb99cf3939c0aab138453e0495ff2fb3f220fb576b25f0f1cfd8b2c6e816712d0cc6c7b1494a6147b35b2dd732a85394b3932d87ad197463524a57eef0f77a93d886fcb54d54c84f5b8d72730e42a2a5c95d2969d9652d9a883310a8d083f56657d25a8b212a4749375f09d1494286c31684b2f9781e76c5810df10a362bec9da6d13cdf236b7467b038728dec361b7fb0555fbc769f021db1341a0c53e3d69635383a62a336e24131727219d9769a18c391e36f2f030433ffe37f1a26dbc0b15d4440b6c08df5be6b587248c55666167242409d5f5a00fe41a806e5ffb2c7c57fb2d9f52eeab79701c7315890336378020c48c1e13edb4c4d8396592d46c177e56a01f6e655636bdf99723c0eb1b8d3fbdab93b28a342081b4889419d1df0e03797250b7847bdb8832072dbcb282d2dd4e096b411ee433b7bafb958e13a02a84aba87a31235c4f1f06912155cdf19849efccd6d36bd2310181e384b5c653b08b3d5b4ade3dbd8c505b1041ca7cf0b7e6808132c41b094470959c103a78244bbbdaf526a6a63c7c72fe772cc3105ef8492ac3b837101d7d176fc5a39041298ba473eed550a2089024fb072b8bc56e686ebe18b538c386f7e1c9088cb0e7986fc242dee345063a64242c8725014c2a3a0df852d17e39efa3abe95afa34e6ea4a69b9d80cc127742385d18408425d8d6fea59b6bf4653a54e533779e03cfdf396bb2aa16a07371af9912f1008cdeb4c95148e28fd2f64989a6df943a3f0d96f8406ef7652d1ba4111d95a813ada976cbc1416383e2d6e1130bb3d2e69de5627cb008a97360ac9203957fb472ca5a407e7eaa24471ace5dd48682bea50df3be5a0096093ebc5a4c17860cc7720101200313566b55ca37f247ffc2c92c9ab1de3e2432d6aa74f66d274ca056660a3d7276748c33373bd14dca28249ea057532ade5956b8354ba86b3548dc6264255db177e25b2fa5da7954a028a4ea7cd95ecd24e83049fb26d305717f92507210cf55302792b3e4bb804e53657e54f0460fa3ef640d65b92d40ebcc56975871c44726c70b71937cdb1c9efa204fdaa91340cc88a6ca27928db15af4a53c5b242e476bdc540dfc722e87e40a685b2ee54676d619412c0b7bc3e4df697de67406e8bcd4a43f2c2a6f7cf158c91bc7d4fd2a0d060f579f5a3dc5f1866728b3b31f717c908f613eb276a61119e50f216143b2cc7b17125e2cfdd52ff1aa061d5c7274facbd629220788b117a3a770df46cac6b6b71e11d27df487250407d5b1d5247f92659c3b6ea9e524c9f97ab201679f41e43e0fcc64e7e6cf9da48d031ff1177f8527045eb3b254a7a651b30987fe2b32e4149857a6f67069d22d9350e9f43fc1b967254d8dec6a851ce81a129e5407a902c812cc5a83b85e97ab5780c7ab2aad9ccc3cc022f681380c363f1007eabc7f0f2c3c9a6c032dda55f06c27eaf172911212985f6bf451986d72b86ab342055ec8316b81833da5a43511d039a1ab7290c7b16f3390678923f93302271a15b584ba26b9679aef7f47a5fb0a3e0b8072bafbd7ccc0cf58f1cc3cf660a5690a8d08806e1300522107242a4d5b5f7615368f4e912b400c5b7be96e0c87b2d26775bd8f7d750868b9ccd10c4a173f6d1b49081d720e09acbdc77e42f8c1344cdb41c37163fcebee3bda765c3a0b35a24d39536933c9391d1d1c0d382ab35a854902238f2694674be41f4a7faa74ae055336192a4fe7ffd9d2ad2c6e4bd3559cc12a005fc22c4dee7f3ad26fa3fd0662be06ed2da8c9e1fc57d8ffdb60a49f61446ad9eabd8a134a23d9681e5d3d629b2b72646283f302b94f4156ecaab286f2b8b6b632a7fdc683dca40257cce841aba672c1d811d4b41be5102a058f1a121bf35ab9e4accb7e1edabe5b4f01237fbea532f18f34a681bef6e6e77aedfc6b31685a0ce7c42affad009b7e8de4574c9767520d5b4b5e9bf4da1f8d5b1b192af7d842a1d164a0376346660b27cffce0dba67231db9d5d84786af2705a676943d3528e78b14c0e10379edda643c4897dc4a12132253bd7779dd5c0c67b3f7b11ab7c8cd59585d49d13aa5cd1b7162f4fa625008f78a76800f7e13b0f842cd64159db50f4570d366b2a0e4fe92a07d2cf12fa4ae067da216c7a1704f9308c877284f95b09441feca645707046a470a4110c4d7253d61f4468a7d96ffe41fbf6b9c39fa39d7c15b2fa309894fc181cd7ec781735d2913b7285efca95c91db2ef860b05798ce9c3ad5a90c1b940b59b28baa58d1717ab838eac030eaa374e61798740298d091ba25c1820340b1eda81ff42789b4f772117abe4a8b3aa93fe5d6a9bbb691c1c2b7ff4bb2d4a151edafeec2d93ef523263bfbe4a7a80a20f90ea951e59d3ddaf44e3843e32d4790b5b66f835ea1f0ee47604728aa7e7b5a8062bcb0c4d9441fd89e710c9edb3490a3d405a450c9a560f9104084bfad25cb963910d972a227e95a62c6ace76d12f92265f9df34fae7268531693feb2f59b9506ef93f2a3c4d4de0e0600573c8f98e748d43942865e7213005d91e4b0b4628b8b5448beef1f14fba14a9c0bcbfadc19856e762f3d85724756ade795264c64a9a0137908c3beb63d360122df2ceef6d003f036172d5b7256aea64e3d848de3e7c9b052b5c6fe38e8843457e26ad7094d2c3f70afa153721be1b6075e695b7a9d39f6bb8e37735fd8d4dfd2bbf4655f3ff548497a4e5a2a485ca2eed802f8e1636c09b167d53a8a3da43cc15d3c12868c4a91ff12c40c72cc70384bb7270a4365161a9309fc17704e114a6b8272bd1b7b117d280368e472bc3e576d3418e0d002679af96c2eacd33aa3786d3d78ec196358685e3ba8f77291a7cae7e843e873d05322a8a14195716d94f61f29a99decffb9a81d88102c7206eb6b74221b62554df4a87a81daafdb271cc39d26f14138e8e8e4675a34a172fd5a5f1d6ff66f1b2ffb066e6cfe06de74d9d8a8e43eadc8ca24c7d613e18d1168546d055d5dba0bb42e7d8e11e47c31407b4e2787be506f0cdb07cacb54c672ee24ca21af92dfb0992c298f2712ba78d01c64712e487b8d3154ea9d0f056a72fd5a01aa1449c24f0654aee4acd3167f6206c7a9323d641661695406d4c34338d9acb1cb370e4c33879622d97b83246b915ecf79cfdcd3370908afd23c03fc5118bd94133a25429bec148fb06edb797a4476a1a0cdd5ebec5a50191d6dbc3172a7f355b8478434d2a81de2babccb2de3753dc00e578b6eeef43388455a1357240fbade37ea9185572645c3dafd6b09f345e8a45c61ddc92adaf5ecf61b1deb72578735cf83bf072bf8dc9c6f6e8d40c1f830bd8a92b5d97c11399366b0a352722b53e587b474872c7de964d88b60947e9dc778f2a4162c457aedf42555d91d16d5e1e55acb28fb5f5b618ac92f909ca32706cee203dbf55915f7e787187bd05d4fe8cf3af2987ad7855e7cf15c7ccf7d68aa3a1d7787603caea6d5a23c355e72c980527948a462773dc762a5ea6a127c5cbcde53891f9a78415c08b0c66ed96058d0a3986696f79a107e148ce285f8331bcfeaf6c713e6db2921243d556c6b726a5ad52240351efec7587f44fbd5fdf5132cf425beb240e2f67667e33ebc8167d67d34f7fbf96881bf10d38b11e3390ef19de8ea38975663e03750ef2be48472e949ff7fa51e6d1ba334009427d2b19aa829b342a38df9208f91c49f48b35c6178655f2884499ada5caba8b47358bcbd4625649732ef226795e4752fecca6e34333f66dc138be57dd3a5bd51b17cf11153ee84de7c18c5e33946fdd6c879047282ad105ea0ffb195cc7d7dcc5bdd74db73853f8ebefd73041bf640bdd7755c230827522441a650906fa53142ad75122c68415413c0191cca1d54714e3e5a980289d233daaceccd2d3ad9e0951a33f87be1d55bd16d3370a1862bd86a30d2e17257a6622d0018befadca2cfde4524be5810342db3f6d93fd87a8c4b1deba51a648ae0b2670ebf3b25f8ff1f29f50525991844f4d78e559e534f1d8616a2ff79659862efae7256192d6da7beba28db2b42ce6f7ea7b6f949a82e7db065f386af72adf7c44c15557a47d4b4781891dcc36cef3aa8928a1ba6efad3d9fc7b2527d335c29f5c08c3ab0fe4e0b63bcdc8a92a9921923116c0341fff6c7556d213c8d7154d2cc0d4325da7d7b87f619fdaf78d864d25640945993e15268df00ad665c72dad43c8c8256ed3fd216716b61b860775cbfbfb3734a65219ba127d0259533723b913dc034fe6adb219513b65b69707ef2639c42e808f3528d84ec9fb9388272f87d821acd71d982f81335cfc45a48f7fa2772a6b2dbf0fc4050810a16bae2416e9518fd19c1d03b522594af012d6d723954f24bc744e2e7c76aa455c33d9b725715c243cdca76ecb744b66f932d36435daf01fdf0dfe976903949be90858b720376d3c8225914ea276fc85890da13bf9cc3cb24073b7dc728852480a3804a72b7c0ac4f5570786f10fb1c81f136f4afdc0a405dc7cf94614f3b1293053d4a7237a8c7c9908126e959dd4453e979b1d88097118422a89d9bda8a324328482b2c53838e20f0ee95bd8ca0a242a0a44c8fa201a25fb10cc842b8761248147b75724632923627b0e953fe32f3713329dab56a3918c95ef649d42c2be31f2ed7d503620b2079f7c89cace920cd96ed52d12bb44fe83c7cda832d8b18dda8fdf15a30079db00c54b7c99a6c25638dda2da06b279c736107ff6113c5052b3e88289c06ded7aec1fb5ec03c96b62bac7504a04709fac2970ad992076f6d07fe9764e872edbc2c9e8a9af6a2dff0d3a90c1b1632bb9005fcaabb305e2c4088c3c7587572442ccfb766b066c5560389c255aac92951689cdf5786e38b846e60da049945724fb2bdf4a4b5501aa99a9a756d21f8cfcd72f2553ad81d8b04a7efac833f0f3e59994d7d9b5035f5434ae2c4d8d766193add9a786a0959c68a7d93022e9f0b7228b77304417e105894dee6d5990e67fef6cf5873e6cf9c510475e24db468557236f2e3293bc422449be00568aa2025af114863062b206efd5898e23783b91e723f80b9d12dad061121ada0937cd07bdf4c327e3b21590bbc3f1ca39413891536bcd399c1cc9614469c83c45bfdf2a3397d0c561ee79e12b247634cfe551935725b3390ab7f7b6ef4be6c6af2703dd258eab095ff3d1dce8166d8d2e6676dfd72c97ae52d91505cea1313dc20b41af69e9b6dd3d6f2cc4ad859f0ed0a292c6472288a3c78c46d9cd0591be5d21166ab4280f88e4a4b8d0a183102b95d2fa4b872d0f05558ee8aa20cec66baf99ffd32f93799fa9278babc7391384aa3e47b0030d4041f73ad34702fd869b890c2bedfb3714e65261c2f50b24258ba15a9979606f8c107750d8233cfddbd9981a08b5e64ef6ad9c6fcad95534c12961d80a53172378fc740e2e400fb447089bd061363f9199f888d45f24b486f63c85b0a4b2c36173668d51c5fe9c5804c9d7f6ab2b733bd18c5e638f1ef8d4d6f256af368b272138016001eec3d74394eeb01604d96878f24f82212d39e08c06220160c61ea725506ef3917c34bff9c4e47f53ec3e24073f8ae7b96d43cffa97606cf46ebf97243e075be84e9c6c8fb10c788d5828f60447a15d339d78b7e06b98b0009cc101832437fca2be2e7b863471a0ecd687f5b3baaed88e81c61bf4b719f983ac07572dfd60c8b7c70bd00c3a055f4c25d4307e58e88b9bb0a192d2bea29983e28425f46fcd8d17774cc03cc2156cb8e680a6959d47135ac3b2b1cddb9f2be51480a72ade25b98b5161191b2271e670ee0ddf1fbb8b3e4a6ef575117bea93da3e0bc729ec227af56e1b3aecebe9997949ffdf0ed35874fc1ce46885c39e38d0147da7233662b616b50ce5e634ff6c7c9c8f22fc5cd5684b830804cbb5b2d50cdc38072cd631547c508ecc71719668d74daabd3fdd01f9589caa8b8c47077ddc383b2728a39c807a9c847d4ce104bc116d38a5e8b372d2178c760628440ee8e85c0856eeb1c625cb14075931caa3c9dd0f89284695492119f2bfc05e7982c10a204d8721c17d94137efc04de24780c41410918cc63e5f940f9bd14a4a4bb067dd1f401faace0ff17389f801b7e4ef652b76c5ae62febd6401580c769df5c963c674bc2b4563a87d28c4f49f4ff602ed557064515e5b58f6f5f1739c27164e39f5dc0672ea552cacfdd3d86f53db9dad16d0d752ad8de7ded50bfdb236a28fe7dd917772a42b3caf38e4001322507e69288ce39d5f390a256823fe3eed00f7243ad1974a40c7817297a4fb91f2e3ba59ea0ecbd7d1dd562d94d725de7d396d8d60364972dd2aadcecf9b204a9066caf2305724379e77c1dcf7bc261199c677df97a83b72e4dad3e22160a215013643b0d64e42f6f79a3795f6e2fce0cee3ab8e0e630d72fafe33773e3339055cf94646b7b27dff80097b9da878ca1aabe157d981b5ef72f7a8c26a12ba72abf4fddc489635fb7d3ee461594987884ebed00695dc3f2672fb70894ad317f784d3713a44d7c2ad1e4267661fbd147bf7ad9c9648f574f9729f99bf1cf54531749d04fbe47dd4f485500ed54f22b3ff5ff0f812ad8c7f3372bfc8c6ce69f1ccfdc2275ce24eb2424f558ededc8a8996a87dfa5427fa336c726faa5898385a48bab08663a17bb77a025e830787b926a8dac52f4e1a8bb00b7234cab50f2d2966b368ab0a7e2928fa9363a640d9640ca573d4e116dd99a977705aeedd8dfb4e97eea65da4f75817c6566da1556af085ba5de6ac5185912cfe1b0f63fe2644660ee5210eced78c2f7ea063936a20e67fc978de48abb9613ac37269a6a566175db9094d946e414901cd516aa85e6816a8c582aee3e6a96e97af0e770d53ad6d329911701f2e6ab6bc4b299379c1018cf1a9376d240f212cb2037264edda46161f58a6cac32c1ef1c9a79eeaa710e32cee17b51b49a1db82ce8972dc5705fefabac6d906e91ca78ba6480cff2180b1bdf8767e84b21530f377a0011a80e751184fded9876851bffddfd1067799b66cbe550668f76aa382539ad5541676b7d62aca5e07b0b8fc4fbd5ba0d5848c5e065a1d693b3550f2e15b651172ee268458debea4373239c5e64dbe230d7f4931f834bf50cc5945981e41647259bd1a051b959411c6fc79604eca0866b45f203bfdea357d26d1761b8a878a0614d9b832b6df1e01ea4a4b6e135a8188b033112645a54fda2c2c19be224017e872110890119028518a155702d8b005cae286011bd700e0d8f76d14095b247970728ce16830f6aaed757ead2581df029977e8ac377e01721270bb689ba5248c72403fcf0284a71135d576944dd55236b44dd0f98ff181be8ffbc52fdea772ee607233189fcd60a657b32de3c73eec3be782217181d3e0bfc1676f7fabecde241f720785cda94bc1da8a7fe1be97c3133b63d1e04d6be4110140d1d03be524bd9127a219a28bae35aff029743c30e6a268046355802ae584d7e6626fcab74e8388728832ddefa3ba412c095fead2226082e57e435976197a3d430a29e52108d2727285e45525143121166097e2fffcce6992a7e8f7e08a5bb949bfccccd927cc267227bbdacd25f0d6490ab5c320d5e1efc0370776914445f8360e467810996bda72a67106a702f4198b371f93546be02d747872f7111d0ea2b9e8f2402d0a2da6724ae7442b399ba6c84e91cab129a6e16fe382ea9b100eb7bcafdf544b1d173d72c1a7f1b718578b43712202868c0808a70f200e0322486ae5f15b17a1b1aa8e72be33805ae8198507f902f0ecd54d23136ba69211b1c7414a4f68246a9b42f0726a71c09e859baddde3d0387231a6e11b263eaa6ffec56851f66f16536f1fbb7220465b5b762dc6b657502c0cfb5106eb7625f2fed1198bc382e3df260c356672d82e1fdb414d8252aec3c20a65bc3c2881353b73f5b6b66f5e1d4dcf9e1f47721b96e83531fed824e11064712787c3096b7fd864f00cc14a3043815ce634901c59ad661f996cc8fb1e6b95a76f5fa76a67afd44704e96ed64c55614c7e69e21bea4409d88c37cf54ffb7205ccea035bd4b295dc6f71a925981eb665b240b592d10f1b7d68bf0fcd40a2dd712eb651c0ea0959beb757e352b940e84bd2bf4ac5ebae6b6bd8ec49f16178e51b01981392836397ab0da8803e45601d4a2f50ecb0d3cedee19f29eec459be0072e096ba7ba47100addd3be00e2968095069ad4162e4d4b33e3823dd3791521214a4ed438e25b86d3e11baa2d8fad6847915bd83551029dfdc5cfd3ef87d09ccd783a898312c5a3e8968f0b645ff9b4dabdfe6d4172f03e36112a3f316626437bf0c0ca700dbaa622823562e407b89c3f89b38c587250272a3641871ffe5dfebad7056346742f98de1f5938961807a5ae29e6c55772791ca0e5959a8330ec5edb5156e2c676cd90bfba41d2902f91eb17f9b9fd4f5e1010fd7dab8f0f043154dfef5de3e67aa83e6f9e16f1cba79275931e414e0425681ea4953f863fd89bfa14cd74530a95616ff520d4d07f5ef11cd975d154ca7234038b95155666613b227dede2cf81adac8da38b260333fa39b0e1d1542afb406c9bee06a416a68a7657d0e0f78ff1254e2605709c4863360912d43d042ad64d0d4b75dddd8c79a70f08bbcd395fc7070c5e6889c32e918e89ef915273f0aa66818b8f62c17f51d6e652cb2d4048447a5c9edae2bcf92a74b825a23741f52151b46b38e9207a810eb0dc9996a2b90574665a2aeaeb15fc4f1dbd7e05b3f568060e21d2898e2dba9db14058c84733127c288c647ed3f7ecc4e3ec036e682cd848f85ef2bbde4c9006ede5271954bbe6050bd84d3f60d21d07ef49e5a3ff5d0f48cb24406fefc66d2eec03d3b791c63ead1d061c6b91e4f068f2635a3717ef2472eef88db71537456ff3465ccbdb4b530d7e82870654f7cb5fabb20726650dee72a0226c4f0b18955313e10193359a7c7a839f4d9f0a9bc438fa8940f69eea96720d0d87cd0e8a0ded09fb9a5df1ccd6fdcee635eb563dce0d44d36096f06a9972c4e6486ac23d84cfdd93112e4bf5d78d4efbeb31c2e5612607dcc70bbd3bc47228b97dd0ea4f344e3087766a9a62773862e4ef396ed419c424f2d2adaf23b262a0c2f324e3c6eae9181f9aa9a209a3e82ee80f87ef42936b70a42a6a1fc57f114ce4df80f09754f32fc73d539a7685f32cbe5bbdf20703d3c181e0496647147298020a1eca06af6ab04c221b8c8afd60c9cc43f66c76cc7a9e7fe4bdd0b3145d8e5920e2b8dc01781f66693c108888fdc2d329cb51c443c581ad506aba9bcd561ce0a01f517eb31966a9657274f443e821c94fcd5df8c6163ff2c57153a42554e2ab3c04a5f82d483a4655658cab758e6f954acd68961467e4be8070e041e0728832e1ff80c4de18ca1707ee81cf9a29070536fd3ea2d5c08a64f390f4606872d46fae43e6a9981fe7ec185fa27b38bb24bf814a9e9af1417666ec88397e7672a576354b22b9f6d5ee78daba80d48b52724ec78c84a7cd0b5630ac8cd953fd72cfa66db65cb18b76a01d7b28aeb54e323303035eaff0c9552c692f46d10d2872d6edeb56eb1d733d845a0ef9dc2b7c81c1923f3294ba7a75c18a08d7ac26ef727d69da864be23afa45dfc8785f521fbbefb0dba2e79ed67572330c7a0e20b472414b687a5ce2ab06db465fb693842e8c83c0aa27ada63c8078eae98b8fe5f172fc349894e08169342f60ef9e76d7174b899230000793eadb27712a88878fb41f83633dfe1d7930b8441e9a88e394283718ab7778fd32488b8b79550579a659720d6e95ebc167b2e9bc98c96454af1d0ac12961180b2135cb76af12351f87ac72acce5564eefc58eaa4e2f9bb0bded6b0f0361e4e52fc3c16a73f0b8f298fff2c03511efa4fc312f147ef150ff6348a9d2fcf73976e5e80a15754c46307c82b125fe236ae0f12095ce8495b95fe5b81ca1ec6208f1f36771d8b9e29613ebff15f2a106972bb0729314d8d760c7abdf370ee96a400efe03e35887ae1523370de721187052add05595ce7c05a512b3b907775fc6320a80fbfcc0ec5cd1f9ce4ec72949f9d56a8f815a16900032a2d6bec2ca0b50f3f016c571a5367ab08ce57a011a421b39cb0b8e0a517d59dbb374a2f9494eebc33860e839d39124d47cd48f61daba5b46798979ce90a364c72ad653dc0c9d92b523d78a9d66f3429c932263f5096a51f87810794e8cc4f4f3878c276bd43e41569ec6bfae9865dcbd689f265162c80d9ead7948c988406cbe01719e6481039b9b89170ed17c49e799e15e786017b699711b2e27aa3dd5d7ed76e6d14235525eb864ee4d271b51a91f9cdcb78191b5c58a4edc85890da3794e37e2280b1b40e26a7a2dccf28a3b471acbc502b72e128b1eb87a94a151352e27e006addfd09fe6298936613804bfaa2a7c7eb0b72485b49d4469d741fc2f2010bd9b3c8ab7558a9baf619ae7f71fd7d62df72211b631f0fc1cc4ffdfb02cbef12963a8c681a5d62f9c1e9c7a394b4ddcc5dca5d7251e2910b5a6e6c12aafeb81f9dbf08826ac50847df5f22cf9af250755eef774128b8b8101b90947433ca9ca783354e635f72f9f0f7bb81598dd460fb95fa3a7235b5ba761a8549584fcae4c7089167172b816ad79a79365102458efc4b0cfe35a0ca4d38ea5e9d6703336891975e989fb5ae16e91f82114f33ff811678c18372425f094e7ab89b3bf0f78bbb3f727980ecb98c3617e9f925f9eceb62f510f331cd829780f06bb6e0b5df73289e008d9f3cab680027bd8a098d9679919b59bf7208ba61da43836f37a22fc7ac0776db80b7e439cf63b8b6c04b08efe111139e72ea4427987b243beb09fd4194c385feb0e475fee99d00d76ff36518047f99fb7219a91e66cb3bbc235c149e26374290086e20a91e3a0f57a5fc65e5502f77a60fcffd9c2e1a74daa0f179459ebfe8601c3845d2295bcbf2bd49567911c3dc3b28a56cd10d6c474a62d020118d0f8730d6abb07bf4266103d081e03b8907c678729de701afade78a73242f7ad0a2f29ad71cb505a2481159fcaafaa77cf685d10a57579f76eff89605582129c5e865319620fa0202dc4bd7f31184f230c0a83472c7ce7455527cbfa8ae88d2f5727d1a7cd380cba51d673f602463dae6bb3c402115af6824d42dd6599802b97ea19cb6fd28c69c8f99e9c68600de6c06c8f27272e12a22e5466342e384af28f416d68bf65b10e56b9d778025029f217bb947be725531c55dc2aeabd8a9ea3fd0676a5fab61ec6f55fe84367fcd23efad3811ea729ab7ce2f80d9bc0705cda9c433f13b0aa9612849ed9fe4d1c00b8b90c77d8a72ef729bfe1c52b70bec25ac87bdf179436f071a4912ea4c066e269c0648530a5908dbcec51ab6716ce73403a1ce9d2c0c8225f267d51d1ca5be2291246f4de61683a65060484688992c298cc056ad9bca67f874ad4059d9dd0e5075831c76d972bbd2b06c63cfa2f5546a5242a9c825a01ec13dfbfddcb0c9fbd58844dd4cac72035ed2e8c161c63dae8b0406eaa0324765990f8591694b8dc218b529bb93673f5f0adeb578a184399888f4d18facfc224f83969ef5519fb71d61bc56a46568152cf91681127fb6b098dd52d9bcf42555ca00f660558895fed6bdce0647162c4c3f1c3fe3806afc3a87475073108bceaa129979dcd5dd3532c0c76bbf9e8d7972cde46c6ac4a76798f9576097bf774e0d9a79ded0619938a86289b8ca83a7bf018628a06620bd895cd7578673a45362c7f9a025a185caa3491200089e95dcde3d677ff9f2c59a3065715daa5b93f1cf7a39c8272bd2f9ebda264c706d8db6e67250239ba3d37c530366455b830de1f4d1c369c0e29779bf6dda8d5fe6bf87e555ca4e21507a3aca765605bb6a9955c8b9f774f81b09fb0e15111c89f69aa0895c046314ae3cb2ba767b0b90008554a37bbc454f1f3367825daf4819464f193e72e5e5dc483da8fc937ef220f60693564cf6af946d33877d302c61dbbd30865a72c1e4667ee5512ebdb20dea2ff18002740366f14f3c0dd89245b4f70c882000720a02ee3a294d4b613458f31bad5681f2db39e57c64b03eb18b9d82eae3c5ef55c02f1d442155ce2ab1a11a69f7b976cfb12b78614259564cbdf5e808738fe572fa8661d2de04392f5df8ec579954a8d4c2ee007f110251026961ebaca141c9726d51f3dd0b62149fe1624a07a7e5645e14a720204108244f27b0c81d0089b93617e12045895d932ff2cd0ef933c4abb4e538a7b5776da2910b89a3a46d397072a0e0f919bb9491437a526688d29d34e538f1c5118e8afe265131e311ce5cbc72093405e41155b48bb4726259fea8dfa6533f239381fc671b514f9a661dea1e72bb00cb46e6b3e1c77ab1639d845aacdc6ec6682366761b388f4549919457e272ef67857ffcfa1cea8a9b460eb3bf25acb1774c670826e6550e97d538ce6a80723878c3eab7f69b2863f05665d03e057de173bfcd4efe3bd6d6d03227ccd7c62f116ee50b9c3181820785a307dc68f69c47f51d051c855a59b772d89f9d5c3918a1f00fd8c99552ea1b0b07487a2ab6c86596f9814032877d6090156ec5a38f7256fb31ef8439ffea02fc3075ae46190ec50166494f4644b182bb50b18ef6eb723e8ac9bffde77ef6f5b27b01f02783234df50df68f120e573eadf7e998472e1c52d6b7702b8bb929e2dc746f0886fc6cd15656a24320b286572d462cacf26a72dbdb6cd79d64e18cdc038f1c5c9cf2c198f97b1f7b555f9c92d193a4b88d6c5c30478ebbfd288af29f3b23eb4e467586d01d9396002d427cab0a66b1e8880d09084d291fd4a726ec4ffdb216d682d28db8d9eb5038046f28ea64a6f21253bb5a79a43bf2f6674b95231de484d9f706bfb15cdc5cdfd5c47ae65ef1af83479172eda19b123c68d2800a58d5a5339a1c6ede54ac8bbbc38c1161333f9f16d43172d3b494ec3850b250d14824543094faa4d7c882520e15d835bcec9a60a3d10b7141f51cd817d4be8dacf423855b4b1a00b10a5ec5fa9f83830f5a7a2f22af2435133472c50f40e4a220175853aced5fb7373ebd6c967b53a5962ba8be920c5572120085554bae457dede915a469c3f88568e2199d954134a68996f766a53713727dfe84f44192ce699adbed9900e8efef65f1694fdec499c1ee48ed8c49289649aca123837b249020c99aad0ac3ece416ecb1f752ee7ea65faa87ec0fe9b9733a7b6d5edfb5177599a645b7718565bd77ad865cee2fb35a88cf3aeb599b905b7202d290cd72ea154bb903ba19676baaec3329c337924e1d751b2733cda2883e721084923a11bd866acfaeeb551d6c6257099cb17377c3ac2404cc0ca5e3fef37210845b7e27a5f0603f516ffa9609d1f3c5a71851f306bc24552e8fbf1629163e71643dc67d0974aac6fcadb343ddddd54adbbed54a6a8f067a33058ae2dbf5092376a8c222e7284db9b205ec45cdc3cb5f4f2aadd0482003608a65924d478137697e0084202698f43789391ec90c35330253dd11a3e16572ffd0df3249d0d35702dd34c818ec4d9c243156630de69280df4868f606b5861c6ee8d7d7b022197218812c6e56fc404719c832d9401261ca7144c2d62dac247ac4205d4225fdd663c620cb9456e03891bd475ab063fa140c9d6ba04a2be4ecaa9a15c2e6996188707068a3d35f80691778d6978e0a4e149ec1217d7c4500c3ae9c59d18531154a72fdee7593da3fb84b9590d1d82ddabc220e892c46b7681d38223f464d7a8cf41760ff52f2ceba11257426a64fd2a97fac989caca01f50b6efff180d41a94ac872a849f95c21e3998a6524e5f43968276ec74dc5e3350a3741bd5eb8348e9c44557e9c588ad9f9719dc5cbb680c46765dba6cbb5933d1d98214add4408303c51723fd7819ba5a03cc3e205ca58a323d25bc54d35d1eb19b5dfbdaa91263d6d95720669f206cf0246f98288cf59475cf543ed85f02ccc2346bd957c1bbb11420f72cd0d348f8682d10e51eb597585e19d2406c4ee0df7013f4a0db5bb9c254453725fbbfb97e43a86cf9b7377b70f789dfec5824598f23b2c25e3aec61ede2625726cb4165ed956e70e55cfc67ab4b401d2794af4f1a53d51ff06945956ddfd3b721a3ba8f23d70c5afd7af18e5be18deaa04b05f33d628bb368ae22a68f366474e59c7fc6b61622e700b1f11eb447ca78ca46887cd15167eb1ff292b2b2d1005725b1c9a2761e75a860b3bb87e667063c8d99e2e343faf2ca219de83310a4f4472d1ebe859b70029362addd422e13786b97da56e7181e603a383cf1db0cd4a64725d0eb71152a579d111ca0240afd4abc145ed6cafe7e5e5bda3058a4886434b72d8f47a12590cf7bb43c5c474800969a2c202500f1662247c7c257b17924f6672871e4c1d6945b3aec505d04dfed84b66a8499b15078efd373f5495e5bfec5a72a2ef5e650bccad58ab4b3ffc18311094080811f7dd67d0193ffdf1800d11b172d9af0f62b0d019ea6354ebaec8bd11164ff5b0475e359bcda936b35c28adc1665a813a5ff216c2ac1965b6d9e2b91e629d052c0e628e538b83d69d38f4c0aa10f66c3918f9f0b0dfed3b964fe71393685cbcd79f2fe812652ccf36123154e76d325032d925cad983b4abb44417745ff9a60901402e6bd6493d8909540c77bd67ed76a1bec9b473ccbd99db5497fdeffe107163374fde1150aa77dee857b7a602f31e48dc23c46f71d4f2b5dd383e387785f93683d60ff905c221e3b345e197728661143b342ef84096c30a9e81260eeeb8adb47a6003681a69b82caeb0be2f130dd961e271089b9884d42e293c3cc93097acb8505243a17f8d9ce311ce0a141a3480894115eb50ee8c15537a7d62fc9aba1fb206f75033cd060a3f20d2a4b172c8709129e2554d7d999bc201f523d2811c16650bcbe6ec806a1924af2655a8327c3a6c0292ee0ba6394c7c5021427f92e689eebf7e99ee250dac57b315fb160d9c80efdb0274eeb0b53eed51b1405d287b1f7c2c898a001a7d3b59012d062566b4cc4172748e997d1779593647767ad2b0904413d9c75d5c02ff8c522fba330bec9f67170b7070a0edd9817134d65c47a1027d55d4ed3b20e9d7294cd355407257263105ddf05d82951fff7decbc05f543e7fc59a2f2df71d6fb6aeea30efe72f00654fe49a4bc229e351265ea7ae5d7c965e82a09540d1de6a323dac1270572fb73cc4727acb5c84b977648313dae13cff49296cda95017c8033fcee073ea69339c73680474836a2946a8caf7cc4e0cdc185cf70ffe155ac88c911a63dcfc7249c774f15099e7ee4bf7c50ee13f21e88a808e2297b9eb71168bb28480c50972031f721bc7445b1868e897a2f7524c09f5fa22fd44c9c8abd646496ecf11311e56086f598f603536b68df77bbf1ad2e7cd54089074e05f7d947365fee5addd07f3e1b5e3681673829280a6fd1f016474d06c5b7d775c31239b2ec32218e9f6702e0372d82608a872e3170eeddc4b2ed9ba851e0fbd99b1dd9e843bc2df5a7c0796258899ce7236c5c14e23237cb9e1d761e0f81bd30a72ade5a3539d60ca970d69ab62930e7e36c722382ed9b18b49a1b90df7b2f6bfb251e2841a0486d2c9723ad463b0418437dd0809b526cfea568a4ee453feae2477aabb3a62a27a015e7249652dfdad83adec50457d4918d5527f59b037308d21b56a7ad64a2e7514a5722c1c73e29e826eea6883d87936df8726fa8e66955a27455ab0bcae9898ca7042ad3b4d62a08786aa8af572a97dc079cedc7b9229aa73c53dfd6b3c72406f2212f8650ea15a98fa1ce6124203719df265366cee72894c7c59924f76b9c2b48f72f7427c0d6d1da87b9b02ca684f79a3ca264617d8c24086b6a4661a755f45db7239b835d8bc5a3ea2ff68f45d2f750ddfd2390cc2c4324b0136da0dc8358a1e578d6623e7030c6473ded0ee273cddce59f55251316c6aafbb32ee9bc5b1f59d72369823fe8d1c7c88245340e59cdc3b75bffcf2d0266fe7323a023c4184b94672d2f8312e4d47a6efa4e216023e1dcdc5d07fbdcb6293f85bb86d7f63d8dc0224f472a8ac783973f8a821bbc0a376a20207285e269ef0e74b3313bc5c80053c72340398462ecff9f1d7f85ea4e70847687909c0005c6079cdda64563a4accd5236182c138f2c2e5264bd57ce4a1c99c71cb7498c52e47452487c0d6aae8b7ca7231db0cbbd257a2d50eb8eb10471119e7097f6dcc17564bff621ea0c21a870f3d8dbe37abc8eb199f9331a584baed9f62e966ee5aed23f31a3303c7ffba7593727fafc8ea917aeaa0e6b556a741bb4d3538d82001002b41c5eb735bc3f7a6c772f90c4f60ea9567b7d138cd5c938d4c1af2f1110bb2441cf19e661f09f71aa872afccbdde1a1dcdab242e7cc742940bebfceb39e2ca9e35f0bf3a36f78ff1420ce7cb46d67c62a7f63a2ccfc69da4087d5f3efe334bcc70e79f5e6b43a146176fca527ff8677143b9efb293221c2481ea674154e384f20624547045e0874d1872669cdd26f1984aa9b791c3657a2ea82fb2749aa3c20f4810c396f79564aed9725be1ee0d58792539e7e72aabc547a838207903e4586f6d218171e7a419d50172c69d96aab783b35f86fd188ef14ddcb05240bd4d18bca19ff44a615274356372f8b40ffdd1038ffee1edabe6c8330ac55261551d4df4d9081871530ed25dc07218b00fcd66255732183efa757709691db1374980ca344979439021f473a7a872ce111ed97f8459a44f55223b9a08e60cded10759f169add8c02306d7ceb1870f85c24a56a46b5145dd718e3fb5f03382f2c8e4efdf79c7c2a8aa7ecb1789bb351f6aaf67bea6b81a675367b4629050bb2367871aeb6ebdb36cfc7160b139cd15b4cab704578576c62486eb7baef27b33d0c6dc67fdb2021c224764cc5a668270c151bb1e774e5cae84ebc08d98022d552524447d9cb38e6aa9f1f253ad68f672c4fe944b47f6a49b01941e878dfdb6a6553183bf2e97443117f39724cdad6b7283581727ca9864410daab926ea6ee10d055e2e936c98d33a6c2a2f7a0c931b72a7ebb1214a3534018d2d62879f7af5e25a6de08b2fc58f7bf8eb383cbecc895f60d9b4115ea174b0d3a9b78fcccd67a99ec10ec71011bc4e44cd122b05c5dd502352bba1b59b05885974d864c4da1ef7e6609f0c1cfc21f5337bb7788dce3114b7a28694c68de86ff3e25c406efc0792e4df08468336e4b987bd924025eb23176b6cb1435d28b6b59aa2536533b78394006e0adb16abc8f5f40efa6c8f961e722edd96ea4cc002f3fe5b4fae10a744fefff476d5aceb58f5dfaed22f06e81172f89451e692822fa7e7ff9be2617e669c96bc8fab906f8563dbeafe4db591f239a70b56fd16145338a9874bac22fee941dc76b94d6d98d8a1ab49326f8d2e43721a931fb83627cf5efee446d98696dfdff52864bd4a66d50236b0533592879965d6aa3a7e200c1ec53cb7319d8a41a3059122ce5e6bfffb781eb4308c0d0d8372299cca3d1f81588e6a809794767165fe2932964e33c34d845c8c77cdf2615003ff3b2539cc485dd3a79fa25089d8619ac9bf842a60ad406a1f35c37978c24372a5378f4bf769cba109b03849bf99868f01c190d94bba1502a73e960bb5fd9b72e05b1d1e59c53ef39bf74fc1e4cf85eb58fcbbf26fdafc20f1001244233a34724f5be315c885994ca98a0ea665362ccf6a51e04b62f809ea5b59fdbb6422f872f55942bc0407b24da01e61fc05f886b9aed74ff62a97bd1be6bff6820133c2167652e1fd071f797ff93ad76770230dd821bd668c6617496cc77047e14f00a1386a540314097432b5236a387084b3f8372042c42218f2ce16f90f738044333237a344215084024c798961d1ada79ef049793a2872542c02ed9d353a75916f15721d11fd28fd5dbad7539683caa5c8cb1b5320e9d70f93fb580afcfbfe2cf23a72fdb63ec41133a7f435984e535f5898aeaa47a9f5cad9aaf7cd450379e2fbed4ace2563b01afd992434c08c4d8ce3962a91ac9bc663740efb8e29bc65ce04035bf5f86e352d561d32e6af0bf988267025cf96d8675242996a91066a971ba92972f851f158c3c73ed303898f5fb9b42f85fd03875884dee9fe71f7a0f7f9e25f727159ae7fd66c681acb3f67a25f806ac39c9dd7c39e909560983fdde3df9b734ad5c1abc24121f2d87c70fc1b69af0b76596c4751c922389b03d9e666100623390609f3da1cc6b7241ae6b3b5ac32e8ebb80918764d5e433936de3503b3c724441f490e5a4b63ecaf6c677e46e81ac2c7f1b306974d6558ab7d21219d39b64972a6e3411b94ecf746e70a42ce195038972c4e9f1ba895b76498d2484c8f430d7240133d662635a1f534e4f216a5dcb7c6bd95947d58637f1ff6f028c9ef25ea72f7876c79db3c32a4be06b750c7b1f87ada9bc57138562927065520308b758b7274406e26992c3047b32f8a40a2d9d2395c7e5893305136a95de6a413855f533dedd88e5108d59db2f6f180da0f5c5d9985975ab7fc00842835a7537017993c1e6d34966cca13006ae79d5d3007880fc274109d207067a13e962ea582d55f47716f1bfd3b05247cf325d1b9b9ecb1ba38c5ad9c8919c59322788e59777e85182eb9e208638b05381296d5a6d231e5dfaf6b343ad18ad5a6e6b7ddccd505493472a5acba95707910ecdc5ca594febf80c3134c205a75087340d32a5e53effbb72befde573c12178f20f49bb7db7584059eaab6db4b3f1194d8d9f996ada3bcc772ddc4001db28ced92ef82f6ee336650e227439786c7cb99828a30f6c0200ef0650db5f1f8437d1d459b1d237fe1a210991d4dc0e6727b3ac895aff4162b86a31e1dad0abb76930e60316f9fcc45fa09af4ac870ea45b13432f44ec856e5a5c931eb45632c2f04b62585dbda98e69655d1def63c6db7049044c7a321b1aa6b1972cc644f82cb34043b2052a0ba827601cf9d1ee08799d976340679132a41abdd72a9167f161aa74500ee351dff0db55d5376a2b16716e0792150ef22cfacad930f69a2d63888ec2884805b34305a1c41d027ede664c1cd34ac57478056d46caf72b3a53d2cb7431b7bae424b926849efd993b900c6ff83a5f658ca7bffed1c25225cb1618e57f135322e1ff2cccd9ac52bd5d01b43a725ba364a0fa0a216479a1c8fae13fbafe313bddadb7724a0b9b05ff76fdba04353a1b22b7cb90290560672ad767e45e6eccb94c95172405fa1df21a70aa64cd18c3e70dbce6955ab16ce1b56cec643cd7c4f4c1b59f63f04d9c0bacf934eb5866598eabba843bc1de94f727285aa15c62225546caa383af10da0e14da2dffc355974756c101fd0b2f42d72a1105cc33b1a39d2a503f143cde01b8dcb5dc7225facd1eb7d088bea275af472c27ea0929cc22b3d7489339ab23b74ca2703d83225614a7477809fab7778976d96917c649782c2d0df309f975bd8d9b080df36c610db0b5d9a9a4f969ce2e872af0d115a0cc8af5bafaab381ebfb7276ad003985e9767af3fb9f6a4ccb7c9b0471b82e63eec2b9bc1edcbc3df4d504fac8d650e56a01f67c3ee19c64784f907249311544906e3ca09ab130315bf84862ebd24f53557691920d7f86e2991c757203f3186a4e2b03df58c67d61f0bee8ce498a0e7c0be140a59f0588c922f78272af5128951bcbefb45caab45228eb750bbfe6a6479dd2e19daf8c77dcd942a703648fa6a9ce43087244c0317b7c954e0b328f6b05185ab2aae2abee32e8842d72ba9be6ad86e2d748d79fbd9aab5936c21f8366c15836d91d2046ded75c0867723c1116270c417e5b3efe4510f91ad7e0257f495146df4054573ab1fe67954172488996a8f9b9bf286ede29642911bf9694dcb9a7946709027fff7fc26412a872ceffc21a1ac31785bf9685a8a0a1f348363673b0abd329d812800681ba20cd72ebc2ad1c397c23e06114d5a3759c9c938a227b3cdd74513313e2ac4b3dc0ef7279ab2576d2a6af02f702574f5fc44130acf9fedbf00e9f7d111aa9259b2f7872c27a27f18f2b27cb65e2710faf2f19cb1a9413ddbfa669121942ca457418fa7284b6e949c50ed0b40cdacbea5becff0d208b526c8d5c9a98c12d556034a33e07b10ee78d10084600b267e56465fb5d5bce552b35f029ac00521fc1c7f3116a30591035f336ad59e0e196cc5da18eea5683b6c04c06072250d4e7f53315b9bf72c49d0019274fc773682068f293d9e5d2a59e2bc9347640e8e598675ae2e5ad72f228f12519a1e01a6f1314548635b2d787b4cd616bfb6252b7cf3809f9fb9f72f47284179663ec438e2bdd2d9d0237b3239d43153191518242c46e4a216650728926b87e2f30414c387a690c37531d4f7f3be49cfc980f79ea34be2abfb7c9019fb18d75fb56369f2940e164b489283edc27bb762fcb800f33c80a302b521872ebce20245691ebe5bb612accdd3f3a31a7fa5e4653f0bb4ded256ce9e5d7146a5ca4685fc288a8b1cdb648f73e01438853b06ddbaba1676a9aab3ed37c79c172dd5e44afe3ea6b82349ce3e5354473c4ba9ae281793ce1ba9c84a8dbd143bd0ec953a4505d5cf9d411d5e9cb4794ef6492b238b8e3c721f246aa3371bea4f505b885a932c4f7bfb840d96534c4a5775ceb4e4a5fdad610200a9274b3e03a7672f2efbfb17b9ae0310e1508dede158748b4cdcd422c8d90758bcdd5a7a864df72cd569cc009d5a05963ddcde6225ca485d63e6a7bb3aa73d1391ff81c9b2b2f14bf1c5c2b8acc3cbe3effa2a4aa5ff2c46d0690a0690bd9c5ec88ecb7c729d8725270fc3c1791671a9bed498cafdb5a63753afb8fcb25271e7f876c5e621917568da54487c48e970edda2bd2136d7aa0f97bf5b58b25ff897993efcd72b296443f1dfbfffcc57b3487ad7d2218bf2725a61742f6250edd0ae456c72e6a0ef5871f60657547d1b634973bf63e3a28a22c5f9c2e120a63c26707b269e7835f3c772d6536a63992f893e59fecfed0dbbfd80b8705ebd228086ca278167f26192167217893f6b8d62a4ec58379fe256730e6d6a115661bb7ebee691679bc79b5c77551c6d8791f166b19962465ec2700ea4a27919d6eca2ab84fee7bf73405296e126863e7684938add9339daf6dd6c55e6883be7dd4ca56257480758e69fd52108727dcadba350d40310b3917b163be5aead5eb4a95a316350a2c49434766277967269eecceb89b777ca5499da5b45b0dca9308ac690d2ad140b00c0a8fe6c9c257224aaba5bd733abefb7faad928f876b78b75c1dde7fefca779c37b711fb706669426a98ef2afae165405c57c003a40e81d4215ba432aae8fb36edc12a3f016d72e1a651c8f4f3af0ffd90a4a7396f28c0b40ed966f13383664a30998ffd46476e07f06bdcea3b407b08c5b8322301037a7b32a09467b00aa9bba18da72a2bd05ac75f2a966cc65cddddd18363e9e4f9bcf8a32ea7433ae9b1c4dd3ed57854c312b619024e6e50e158b69bdb48d4f0118d5f7e620cb3423a336b6522a0741d59542e445c64d6737060ee2f5616cd8a761fbde31591e37732d4fc51e133d4b9aa3c6097044aa519506b364dbd2a6aa4fc1279ab2156a913196ca2cb2b9fee381e725227c501d31051dfb90726ad0c9a23e6af12c28f5b26bebd7b5e63a5558e820f4afe4322461f792a917669eb2e559f1a36951fee7f502ee42bb2d977159fa872d1dae5fae0e100f6f7408497e00cf605a9fbc0bae5801b29927d4b1410a13604543dea2612d16f2e64c18b8f3a4d1db67ec8d1897c4082f9c1be39222d525364ec402f806e51f3d290f9053b2f7eadc7af5629a0d45b6a3b9d47b14c5bb9f372b7e239cf19121d5143572960526abb97816a1f309b46c84a0ecb4b133bb7b33453f1e0b51a06e9e4eec9e26d7632b2b01e37026ceaf26a2fe21ef59c0f02bf721821854ada6fc74be914e81e6281865d915bf5dc06f819d27b3e297d1e7ace727fb23b67317f89fcee12aabfcfe93a4d724f22bae2e1fbc454f1c4e82492cb72e855f0010bfb739da70dd3f87b06e935bd71804ef90177595056cea8dcaf6b722e59c0e0a3ac16f50a87167a7cfdcb7f62fe85f3834fd15054a094f7db86067147d28f642cd8e4608e88d5df40bc72ab358457c8a7de245aa87f69c2d3d9b772c176c21b4d7b3479aa4bce3287d44dc10dcdf1b63852527227e9930d58eef2723d89838f2069172ac7119561bd56b25ea099b3ba6c8e953cac9eaec0e0380b728d7179a0f80e56eabca06a5e12ed0eb6bfab08aefc5c2fb828c928765b6e2e25e1ff5a68fd64e79de64b948b139ace819b9fbf5dd60e0be569e5b6bdd20bcf72c926bf978bf00170764d7fa2e2ba346257396f93dc31347d84138c3b061624009f518cf5e0b155fa086c3e40b53f8611238c6505fa16e12a2d87ff01e0975b3bfef44b3cb2e186ea6b5b4b3dee24599c1490a4674aa87ade2c0223e18355473da861c1764a02ae5f592334508e5405d4277f64240d2a59f3a8a2fdbb9d32395d95031024e51730acab36f9dd8097e3cb9a9b78b5023872f61969b29a9bd3861c971422a7792e3d02493a2955cb195bbb5ae634b342d38e83d1472051ce10e95a8df431793b1a5c78d38909448dcec78940dac914feed18bb50904a9f12e43f3a520fee9b242423a13bada247b1868d7de235b038c2b40e2f1c11cedb94df4d72b9c01439384d94800cc619b5ce007cd5ef5c17d85b9d769b1803a70938ae76534a5afae54c84d51ce27c65df0d2826090c3154c8f877bd94c70ffc92d01d3f1b63849fcd214be54d60a05c078eda86ed82fbbe447aad4e600e7bfc6c450b794666e70be7fd9c1e3626227995d3d9386ab9268f273bb47cb4f9cdc166dc6f0172859804947487690f991572422e1fc7d7d1829c7c67c9891747cdc9e97fb3953527d499b9d6fadd550899ff4556e4c8153e76c4f612fde45664bd8798b24cb0729c9457c9d473e6a273f29fafd70c37436e36f42ef6c2c9d56ab49577d711cc72e18abc1f25ce5888b58faacdc4dd744caeaf9dceafad006eb100d2dbe116c972f6a121d90d1008a886fef648efdb36589340f6e0cfea40372e167509f0fb8c3c2a0c8da298064149e26c7012d9bd6cfd7ee10696feacede95bac6b871b2b0472a1c139cd40a82d0da8825cbf9af383085774a1a13b26cfd38e7e04456f70825418aef6231544c044ee79089b450e3fed3c83ccdb8788b9127b64e7e789430a72da197711a76958290263ea90f64f1b3bc319dc60f8275f97b86a1339fc62fb72d4ee4da7beb566c2f18d9627ef3bb94f37e29b16ca983111df01b03a0e9bb9297d6119c31a92094df8c08dc6df190083961d321d9d8974c94cd05eecd607c672cdc47fc0660a289be3b1647473e83eda8944d43dc515bb89549018570079c272d193cc55a175ef37059e98f6377300012f40848136c6298a06208f2eb7491f4a498036219fbafd767e2c8242ba82286440cd9992cec5bb9b3a751b5b7e41996d3f511bef656ad67c3816ee9f422a3f1c1f39e5f15326fe4892c2fa459f935539ddffc391a343cb752590b35cadc518fd93606b8fb55c376df9a0a5466c7f0a7200471dd844f647eb6c3febb7fdf70376bc4792457f82d68163765b9bdf0ded7274743f871f71f538d4cab7f8fb4e7dec7d66f98888ebc6141ceed9958d8ba0725cb59aa33ba9bdee4d41f9aab8395d197260ddd88110925ad0b4308977af851975316cc65b1612e671d68d8a4d57549f8de2e2531db867c531453feea5eee572c0b9aaf3b6c414156815ed0ba11e56e4f7a2306229708d5ee37cc23aeb886e56361730e22a2c61c506de4f967649b50e9bba2e851caa0f97b60ed619d82a013867fa0f518db96f7c28998763abff3e45d4efb433b914ee9742585194e6dacb725eeef56cd132ea19cd98de65e9b08c7a5cb394a8f2825eacf73ff951eec65b723f385d16095192c665008ad80c13656dfe71de233876fdb93a1954394ed5a772d90e7da9815fde03621085ee36cc24206c5d1478287cbaf1602eaeeaa82657724d566767e438f3555dec6c04d24a618b75c2783e0aab8493cdfc9f96d6c7b0726df5a3c44b917c6763454661022135ea5dcddb8d3cc66c895fa074fe63f8f66225b86e37ec0db2a7c4c3428e4f4023e12cceb875397186e6c2e08280ff596f16850bb598120fb333a6084eff9d48019a6754f66ca7a94c8a31c81afa3a16c03cb65517bef8579a936e5587bc29a30016bf80a2766ec32d250f3112ac4406bf7201675503ea7d40ddd1a8d9009ccb97d4a1a5bfb3bdf85d202b306a0a09658323cd3d3d59e4552a6fa93d734220140ce613ecfb3b39092c4ec5178d2633960a72a4d13a6021591c442451dc4216da3001e3fd9f7288aab9430f1fda4a0e9fd4720606d86cc6800b1d802b66b4c3df0d8a9a792cb9c3b26b14ae7b1d6bc5093872970e4c5a6b575a7c946e1101bf217984d4cf2ba58e25246a7604dc1c9a685572c989776d9b750968b7b68ff73c1e5da54f6071486d17b28103168a812340a9727ba2953f5074a7e1e82a48a3286a54530f350b6310ccfb809ef3058fb8b6690e71f4ce3985bc49ae937bd3cb95b04761948d1759f1196c3018fe6f67728d6422fe57890681a64e760360219623920590da39fefb40c91df142f20ad4cb06246f20c0c0fa2de8fa9ecb77201829421e9e5ea8e6a81e7f53d5053f55d96fd3f372e4ce224d6d91786376c0820af57e80f5031101176de4a413cd694a0c8a97f23c5245ba216ab05e5ac2f5046128b1ea733a31465a79a9d1b90a81c7236cfed3305c4b10ebd25a07259cd96b927610a21e846d5a3b8660774e3bf6a1598d9a3d7277ba41054ef97b08660377917177e6e542e990610a18ab706b1d6aa622390b67333335ba1362f1a6099a29074aac9f0506204a0c3652f279b1214cb1396b3972cf6475411ad160d691e59c0b4282b2df4e65171b14e938515fee39bf01c15e724a5af4075324fda7471bc324f875f5192109db8f6d99fd2dc60d93337367e2729b91308e7d5f93cf6a4d1311c9c13fee61f98e618e98528aeb030d76f410c5729c014bb870491627a18f40e1b0e2ab1f0962438ef3f7049bb579d87e338c1d72e189c30602e31df95c10e0d98eab898c60cb238bdd9e96d32b2d4ba6a600927254d63c460c00de4d216859ccc5cf80a4e93bc7e5f5723367f8fe5a3ff8bccb288edae39d2648efdf3271d1a4121c81b6d4f202108ae5a8025cae37e56780583c857496a5a5aad482762de6e7433070a346fb42edc74fc71e57bdd780dbc4a53fdde8cfc423448ffb4b81efdbc3bdf881b2aaef62b4fd1148a3da27118d7aa772b253ab77f28d4272d804d640db49119690b00d2f7f0f285f484c634267fc57729ee79ef384d83329318ce17bb9d91e5ec315bf5936d1447d8822bb6f9d456772a9f435c9c566d5b5d37da8e2d66dbc5bdb9ab07dba2f87cf74e6060c7ce05f72bfef166fbd37192f76334f9d2f88ae51e4f98010cbd23980a62d4ca6fa6736242872f93a11f99908b1a41498103ac10afab8a729326e859dfd840aa7f18e327214c97021ec987c3015e106e23a9e66fa4290201defe466de4e43edacffd5037250ade8eb6ad4638cfaf16fafec680c762d57e6dd390cbdb475a0242583b7180e37e7449ce3c73492d98f2d6cd6a9983a0e80f65cdcbbc7d0edb5bc0081af90721d4a13ce0f4b940a41349073bc3aa869dbd18c5cf84f0c7f923980483dac78361b0d9e633f5c02fff68df552a1e04830d5b0302f31e5490dd76b3cf2ab9be57223e53521100efda8ccfce61c22340b428a0f6c907720bc7fe22b188e95feab7204c87281b860b90cb7a38951985ecd91050e036af267745d78f6fe80368a3c1e374b20a1c0b8ec25679ba408efb28f00bba6c0fd20e34ad0e201f014df227b425da7bb4ff0577d4996786f599536b6e21fa34a13180b482a21f34a7e4132ce6d637af295df946f4ec3d91da8315ae14c2e5009a3ba2c7caff82e03a24ba60272ae13c0dd2cd639eb20972fec7711d21113f90ab0358d74255411bc22bec469048b6d97d4b3cada101fbef082449c3e5461a0ef18cc46a5e4a592310432daeb56d22f5af2bf4e550e047e9f28e13f35cae278341c9fef60a5d2123d4959ffa572ce3120f145b13560f719abfad8ea7936988ccbd8c477164e14ae78c0510a0c7266e00ce7906dc722e4bc442fda19c7b020ab15501dedd1bdcb5a9da34b4bff3a2e2cb8ed33f4269b7a62a5c0588206105a3ecedd50d7e3ab25462724e785d3724542150644272279a35c8b86b7a4b1b2460cc387da8ba4597a7444ef286207721719163950ad4d85a80884e9bdc335ab7d579f5f1ab3eba299b406a7642f86054435948913966444255d78b532f8b6711a9d8e29e20f37d5b053553b06d5d272380cb358abf6c6ab36b141353f433bd05634fb2a03d9e62790687e035865a47239dfdbcc8f7c1ed12200d3c6e65677e386fb807682bd250e4cd1c246d1e16e71d36ad7d9a443d2e733234c9c39a132f59351093a2bfc44bd1d5ee8e1ceed0e7251e4fd35ae02c8faf7b8d495983c3da0627f5bd5fab3c708e8f4d776ec0c8a4df2d2fcc79efa80d7e32778b0afe732e7a4c406a4a0e249fd9f867cc38d0faa72dc488012062835f903186c8805173b6b95f6255a882858f13c4d35f260cbba6057a2579039c60a37c26bfd01c6b9a810f6244174b18ec9891825966bddfdfa72a1e505f9477464cfc522770649d06027e64909cdba0a63ab7043fca427af4e07228eb0bf758dc1d286bf741632cb196bf8e92d9656b8c14d8ad9ad3293fad71cfb2ecbaa88e6cfd59e86f6d2f29c1c039de88955fbc8bedad6d2c3ceb08a7e10287c0295c73be219152978b78bcf3738535751b43fb80c9c7e8e1d164dc58f31698ec199a82540becf395613f97e69a52d62c72f90dac5009f368562d7604a72a249aec9ab4209b3f7294c956d2e74aa8172cbcc4669baac17b96d34fea287721be9b08e128ba7bd67c7b9150673e087f4cc35d4d9da13dd319eaaabe42f6913fc59df0553098c212be74f663ffc8f9b01872ec53693975f920588f7db0caa72e33f17b6430425a55d587a2d72e1590c52e43ba7ba72a4ec021f479bcdf599661e3047f6c71f292b2982aa3e94daabea818cfed9cea54de73fc40dbfe53ad37224ede3df7bb60f86f8ed0e6816f96573b42f82346275e95c928590e16f51927269dc422a64329a2500a25279b495979b41fab5b27a8253fa8a75edbd76fa5b72e7a2c14317edd7fc4f0fe7a5c9f016e94eb41632e2f54b891af3b1bfbc494a10a8d6eb6f8b6a8ed81458a45f1efd72d1daa6381fee4f58352787f05f4947c702619ea40a0bcb3e5bfb3b1b8a9cdd69d01b270c661f409148ad6843d759aec37287b502c0c7362544e6d05f103a551fc6f90ec4f8dcbd887eb656f8d90ec0a856b26a4849a918242a12619d50137e70d7c60e08674dfdaf90490c36461d205c61e217e064386cca1ad630ce298f81d812e25ad8758dbe32f8a2214a4a7179d2723acf61156a8d6a5c8b3294dfa79f367b23df5a6c538cb7116be91a8b83c56925c5ffbfb35c94e4ecb674331ce2474e5535318aa313b7298bb8f7443f63c91859e746716537a9738283242ff12cb660a4cbcbc08d149972f498658009943eda725947ac1db41924c905f3f8aacf9130c8efaac8140ed61c40b8285a3eec2e331b24e7b5d7426dd1b10949957c8ca079f019cd6cbc419bb95daf8ab2396c826972385ff0abd449673990d6a06c0515d1d3943dc177ce3274a08bdaddb5f42f025192a3938d770a44750e9f4318097d1fbad072161ef15cc66681a514a7f8820f72571076e1ad10ba9fdc5317c80e298670105607f2459f6cc2e2df1f7f4696647203923f77f0bc9b170c5e2a84e0db9ee97f9bd131a364fd96fe3d733bfa0e451b84bd4537d00cced77f3cfc027344da5cee6e0bbfb11a38b1b5f5db5a4bacc77272c86bf0022b6acf2bfe523ddca8fd83e58d17589fab78658b4baaeb058aaa7216bf619dfe1d5afa26b52d79998f6429eb560b7c3f2d43b5d8924d2d4406e13b722af81c41fbc104a267403f8c4b8750f2d48b85fb4c9174a4aa50305cd5b11b8b49a477913fb9303cd340e4a14b769de7729f47d5f02584ed44aa09b81a4e72d1b7bdc9d7320806f0d121dce3c99b0973c837dac3afbef90bf5e97fc3143b7268ccf8b20cc72724f1a15d56e8fbc4afcafa51ff2aa2d800d9ce7bfdf73a65162f62d1a988ff89da6e3c422818886e90627c33f1d292fbc5eb807917baa03272b3637415c624e486cad581c495e6ffd84a674354f075d5d7fed71ad1d1a4a372a932d8be90a13dc43b61926561b9d759e26b52a1d3ea13ca176bb1c95af86a69a7cd9978f95154bffaf6f015fef80d61a2b06b6456727d7f2ae15dc9c2dd151ce45ff7d199a0d9e56ed15dbf5df87b15db8c14f63ef060244957ccbd8e4e7c5e6ddeb2d2530d9eb059bb058606c539283a1cc9d64becfc7cd2ef86ad17f3777270c2f771bfdbee135ef58bca8d0101e6975e154d89383abf811f55c2ac7b717222ddeb3892bc983d7996bd00cfc92cbcc91f70c57a4cbea578f6037d3655bf72457bdc8f544f3fc50ff4ee32d5adb2a72e4bc754a3b41ce44792a771db35016852e8b12fffa8a08783155a7c8306cf1392eeb73f0b469cfb8d02962f4788a203c5eacd6fde47e2d67043dc693db425fbb94c2d054f027a967223ec6bd4f1bb3a5864ea4030203843eebaf21a9ca64e16866a815fb3e60d02479bfa81beb66b423629f546c6b63e7b1f8c9a00706f68ba782f650646c213dc01c500674f9d0372a696308835c6f94cdf99d837d4a673fccf2165957adcbcc651006605a8e58260584b5f7bc5520f8aa534208259186bf29bb7eadfb5daaed8c241b11416e8ed5e57ee605567dbba31a774d62b0bd8be164d359ee2c272b74361f5f5c2ecb7300c1ea4d35dcae26187fb1cab45fc8e6edaa4d876387d24114d505d02f64904ba59f605c5aba71b312ae038b200bdfd0b13c92d9bc1fab66e8b3d01736310d6e52ea6d71e1f29b3f43e10a13c23eff7a043793085c411d69cb07d56aedac39952119e23dac3ee1ef140d972a2c6865dda3135f74ccdb8f02520c56422a76817b572d3f179ba6e789041f898d23a9e98460ce5fef17a985f23ef1360f128f9efc03baa4c8ccb447438a0e27d2e341584056c92a6358f039e5aad9d1ab656c423e0729e65dc0eae7a11c1904a95d0b818b22c99dcb33d3e130453560e0cb1f984d97241d71781f837bd36d06125a1ccc5b391abcf5b37d802588403a6b7777feb1b72ea0e442e4f7fe0c92390142ace88dc78ffaf7a03804e8323deebd7d021fb122e809dcbbc9cfbaf810d72c875659b001fbee13f0d5a588cdd09e401c33245c07249671aa3f78c0bc0eda3fd253d649cb82cb110dd95bda78c1fd674c39f0e1c72aa3c181f0b129363e902a816f0b9400a90dae02ebd4d977def907edceb42cc720a45118576aa816c95d75d3f8cee6f1657c6a43caac4ff2be9c4f0830824c272814b322266a488c6c079cd2c6ae963de077bab973c61a95b9da9d836d1f2d772ec482e21eab124092034a354ac15ca3dfc230815c0ca2648f4049114645d70159a66c81cd2d1f430b32fff7e00109f7c9bd193367733b244d056f63d921ec37271bad335d88e220484d8ec9397a5d1016fa1f1d00ed726e1655232c334dd7572c931d9d703355a0c63d48f5f0ee62161a1373560945d6d61fed42924ae07c623f8c5a184478f7dc08602760ef780516cb36a5be23a519ad687af7939df39d6597062b9d5828f3e46499eaf2a51f6b009367a3fa6dba79983373dc88b87f63318c602b55d041137408baec583eafe118f65b0e9bbc5aa3a614668aefa1daff91360e21f7c7a46bf927d4819c2c6643f1a65c91f196364979d27e511967742a7725f119e39041f2b948afbcdba498db6f13cb9d05a2d3c44b23e21bb99f1a4151e01a32aeab8153ae6b108595cc96538fd6a8f2316728cda9187c192e450e000498d577f60af528ea7c452bb27dfc6af47cfe33220567a16d702b4b252eb87f033330ecec1d9bbfbacb0121563c85ab7e800b3d35c294a79f9975e6b9d31ddfa2378f1c4a582a2e4a432e7fd40513c6ff547bbcb362e6b9ea2e528784666673a72b696b38f5e1eba38595400a05fd5fb1eb87efdcd5e45ce2b0c446929b8575c72649758791e7a540c38f9d6eeb55f768728d4b16696202d7aa1391546f4722f72ff4aeb5c31f84bbcff09181248cf4745b4eac8bbeb82c515778ccb3bbc76bb72a01d8010253624e26888df6f5bb3896102e2fcea38c1a1e57beaa48e6b7a4372f657ed608bb6f4169a9a0efcc208368db8c73c782a7331e1f81ac0e5f9a7cb72954ccc499650211a47dacd42db0dc2e8edece312fd6cfe20bfed5e348fafc832e5efa1aead08aba179237da7adaa72aa3d9a1ff732005864f8666524bc681d6457734b526572ac7675792324b0652d9fc450746a092fa0529fc4c60377e26272c7ef88bd45a6b1c3db77f43af4f9638e2073bc27a79247666f10eac972a582000ae6fcd88c8db6d5cd2487b94a254617143437df2119ea6e0ed35185e5e4b772858713d59526646f2eb918d6c4b1928d6e4b80e6c423103e7030b5f815cf356ac28332e53766725c09576135cd0c3ba7c398d9e1685a61522956ebe748c003426df81f9415a11e7ed94219e7512bb8fd7f6e3d0071aa01ed4e2b77a319fab90c494b36c0114e369474dbb815c92f0ee03416a8d051648c2cc14aee43df2158723658e900c1f5a02d2d3c4d9c5171525108c5c51093d7638ea59ac373e9dd877263a8b1e003f68421942043e721dc70339952d49f0d91984ad35c63b9c7b820726b9c0e4b221a00cc4c4057a05e300863364f89100344e294a475f56540e08d72ca9bb60dc635771d659ef433dd34eccb02369367e5c3131122da5fe4ac4101726b621cd23b64198dd5a467ac5670f5f48a8832896862c2bd69dd251cccaeb572186642b281190bd6046f9c5095bdc270d40920cce285afbb7743aff9fa83c7729a16acc2f72c23eb7c0f2720581f23d5c6afbe850391c5d68d0f3db16790005c9070a31b5e1761ef9cf51b2594f2b326efdace9d97d9294a262e29603a49a67283a280adce7a8a2015cece92dae2617f2caf2cde9f52accbe375f7a828550a42f86c3be16f7a705ba1db062fc416b8255bc1c8335eed5fde2721f75e94f94c72f51d9e3cf1fb85ccdf7f2dab1acd0e0e0a48ce699823598e160b73ac700eed358fe71815788d28f09283ec8c16db406018038e7b2e8c5eff07569fe1d0766c72dd962f3407b93b22ebf8f1aaf320d39334db925c610e08a9b77a868c88aec617d857210ff4a404027b16b586ed8aea47e57cfa8f60a31490aa2e689a64386672274124d469d9ae20a075560bc465f957854b792c961557deab2cbeee5d62aa72987b4f9e2374190861e7057d7a8a7b78f3f866083ea89e9614925236a88c2672b317591a2a692f83873871ef33d3f9c8ef64d62b7bf8ffda3b544d2f0f02cc3e429b5c1f22fb658ab11b889a8569a6579e368cc99b40d40f6a184f09201e4f7226f4a0c2e7545fb2aafc31e7888b539604afe9fbb3b5d842f9770c7b8b91f772eada23c96fd4108496e92abf8df8f066eff30dc0827230fbd20fd81c9a0636726f64726018399f9e9bf41a7dee96039c5c50a98039126ab5c7a194e778e8f343488010466bb7418cc91316705bb3d096a52d4760304fbee720c7f5d8cd5ea909249c29f39edcfaa7ab261398e0de8841cec46275af08fdfe9a9aa664913bcb72b1c7113f8609ad797e0715d6ed54dda308695ca8a29de853f6c9a057bb21d306c190920c6a92ecc6a8284d6a6a125624b4b489de8270232612dc38cce0137453828e2a31c70b0378bba73ffa9a00aa949a752eafd4fd4625e91c3fbd2b249543ebbb5a843fa1e2a984ae5a5c07246b0b2b154103ae66499f918c019237589a72a835b4e147de6eb53960b0ea6967b5eda1316ecd84ebba2235d277733c44d63971a5eee807cdbed727da0e1a00b86c15c13fbace9607ca5e9d818d732250890f309fb707ceb0a19ffed2e9ae8488e1c6a33c4684032789e658169a13acad942e8842dc855b0f503fdd738b5dae0dbcf755695d0d9750dc5bdc5d0c298780c4725795d66fdf49386ab5dd288b8b95079ec600c1a64af292add1934edceecee772dbefd2ada4c37c57fa28986195518dc89300457a933c3357814f955f6a35c32062f2134bab34165d01cf3704682e0c788e8effdaf7198c30da29231670773a72b40f0bd2fa446f9b2a31f48f4702d6934d5727eda9247b215acff31a9c5ff772a9394f701c1a24a89573616f49870f057b224467340495ffc28ffc2da59698125a35583aeb61c369898d7dce0351d3172e04dfa9a96c0ccc6e0c47167a9b0d725add9c5a4b6f497bfa1e1a82286a4abf5874cd830317c9939585636cc5d65f72f2df1e022110a0ee484f83279d2fcb194ac0df9d57cf33dff99182a1656dd1721dcc976c8175884ed2f1f185bf4619f0df2a4d2dc859488db0acbcbd3545cc228d3afa89381c058df4f93137d3eb610fe2936f7d8d66648453adbd7dd970741517c0571a9bf2d79814a165631e30ffef767db5f8bb06d2f74b75ca9321e87f02584b1bc455882867e9fdec1c4b25a8723437b63a33447e53d3e28804e3180d25add047a7a0b3ccfcec8db63bbeef899879511d4c4fbd6c4ed6eb543c7d329d4de4bf27efb512aeef07eeced6bf4beed0fdad6c0cb437c7ab2f141c11900c88723a0f5cef8e1accc41624d8f59f656f338055826a74a47d0947e3e45f815b453664bf53afa176e6138d8536fb05abf36459705f59f7079bb24f4a207e9d5db10a657cb629f53a3bf7b33407e665af2c013e77ab54e12e9b8051ed8943297ba672e7a510ab9bef50fc5aeb5160f6827bc80e24362e20b3618b08d9176394b6fb729cf9857a6e561d57961623adf15a43e06b22738e9e7789689f3256d49bffa8720a44c8b72fbc2c4a640c6f90da1f9124f16a23943dd997393da8bffeb536785d9d1adca3deef74f1d81b2f86f044efcd0f41761b721c703ecbf4e421cc086772549981211f769bf98ade6b6d5a7ab3052adcb6c9d83f99448d1b0a15118be713e5855d14e85d89db8bd15aca645878f83f1748348253f5c107aa3b880e02d972fda9e272080023ab9e2e1269cd052cbfe03de96e46180543a6ea360aa766c172778909db4ba9127d92513b6473017703f63347a930c3391c19616befe509b272334e3a1ae8ccf4a2d20296ecdb8225e7c01f0f3061ae8fad2033f541084b6d6527ed254550afe680d90fcdfe12f13fef904e3156b913267873a0c2bef5d69e55603c8d6d1ffa6d74c14cd966dc193d023cff8a716f171c4d8dac20f7371d37721ac5f3ebbf4ed184b03e0e8b0fe7d786a0cc651c0aaebeb0254a66c918452d57dc261955d76c05b2528ab2b375fe2ad091d4697474e8defe171a59b1c77b8472b8fc7b1a9a5fa94e145b97ff3c7f8e07d1d95e177921206048ad2cb7aea540725822bf6271b1b6e4d699eced821b307e55402947f72ed344a072953c770816727fabff589853b7b3ed88a638c3806d201fe9311c8b1fe6c95176dc4d3ac37d6b339ee131e7921300f156212097bfa47c9a9c3b237c8a943df8d272f77d6ae872f0fc241f28b9ceb06720b7e856e9a74cc0a1c16fb3916323f5c9c3b7bd5bf745ec0eedf9310bd61034f41f8ed4e1d4fced70c279915cc4b23583b61719fcbb728b42a1befdf02d5112a88f2daa587a7d338b67e604e937963dae88ceec307d7214a69398fb0a62be4bacb47348e76cbcbc315ec5697d3686efab25cad7835b7254c4671bf7c6ece72049c3ac137aa201cc1b7e55957ca89d7ba690ba7c85b6173122d7e22b1f880b9c0b6ca7356592d2b55d1f34f0011c1aeb92bbb08ecd92725368a4072e159ca2f7d0837688e6d7a819b2cc33a8c9f3d92f9545145a0dd832b935645ce269daa4d74cf0e360a7214d3964b527f44b82c89b423fcf569cb572aff5fb70cea6b9e792b14eae1fb81799f0d5bc918fac1f27bed2fa182c682072cdcbec3d5d0dee855104874dd29e6cb7d0e9a9b83986e3c5f0823f6a44c1a972fd49e4a6ac8c4e6ac750f6446dd3a442eb5cbb7bf94594b75cc84c9d86d2f472204ae06b63234855d82d16ec42a3fda19f41b81109e9867ef5f02f787fd29d729b49306375b600b2726f54245a8ec2880a72fff8a2c3289f5d85c4fc52fa5272d232b8779809b4945744bddc7555000c578c6e596de2afe73dea28aacd0ad87279197d793707284ae3c1552ab0f08a5a5be2948d44c6c598a0db45bf38ded40fe3447ad8e9e41ef70583d0c30fbdc700aeccf2e24258d275c60fc39cd135583e2caf17128685a234c17689c9b1be905f1409de04e8adc259b4e221c23a1ffa4815de80350fd82ccdce19ac0981269270817513cccad3f0d7ca2866da8c430b0fe8f4d9fd76f963062dfb06e393a6e61fefd5eb69fd13b51234102c0ea9808872ded5dc5afe2005f1283755c53f9badc7c37401c430db7c9943290b46525dab725dfe22b63aa1356ea27150585d0a6ff39b4c5d9fb79efbbe6baeb3eec694a0722928bfdb5b17409e6ee5c7ffe0fc07e8c7658755f316dcf551a0838c775d03722d7c644af196b89d83fcc505b288eda8138de899456e460c564821e25c518972c0f447e6da08ca07f0a00c2d850eb3c1d8b9384b12040646973babc89559c472fce803831f9f3eb12c717dbda2764a52333ed9ad1a4b41a2e9a362072426ab72e9cf6268bdec7b3f0443d6c873b2349f6c2b71c35561889a1d3efe58e211ba72b3407176f3c7dc17572ce1e477ff9933e882912800c8e8674a7a05365bc0307225ef53afd4a2e7896700b84a3261519d73274145ece285829d5ac99fb6b42e7245ad94b0b330be516619be6dfcc35f364ee8e176f545e13a899d428c9290ed4bfe4be97cc442d96d0a0332f6a567c9ab6aeec1726162a7bcfa04d537794696727121c96a807fb07780792d9e078fc16b2b99142be10bf76ef5fbc6ec8ceada6f305c72751233d115898c194274ce40c83e98431b7224e245857e04d09e348a72ba397f7f83ea28930372dc9ba6f424334d7092e42193b0dec5a7d78143c03003f9e65ff30c6207b6e56fe94d0550c0da592eb551aaa9b05f9ed02fb056d27272779e3ccb64baea6c3d8324dc38dc63dd51ff28a9513da39d5fda152f5a3f04727d79ac3898fa4191d5cd071b6b330ca5ed78a7dd9a0346c2a9e933d7f119364535508a40a359c171e003a36eb335ec4f995e5d0a8ba851710445c68060ac3a722b4a37e1d8a851f3bf502173546d866d102ba6ef03ee2e2183231b6b151ebd72e203c947f98f836deb22818b2773dac4c7de257352a560370478a667830ff472933f15e69a097f4fbfeb10752316409026dea6fd8315fed759141256ef3e4772bf3966a1b875321d61fdd42581d5736f1e0923c62ee9bf2ff647d7bceb2b2a7270cca10b73e88a435be76a50412f626fe55a38f6f4ec0a1924fe040b105a2a72a4cfa05629379ff8ee271a048505d7e042c7a0e8b08b879d83f9f1ca412b6772d395ef3d31f3f888f1738483757ea6893f70c8760fb57b6c32259e67b73e641e97609a51f1176640b88fd35020f45e9b897188826bf1ca3ff1d2cf58b3c8c9721d83b47223fac32bd1c09191fafd87e2e4cae4c07eb837aa3a04418799ae7672a32fc7e30413f70d9b9eab0a1dceb2db0692a481028215d92c192eab51af316e695a22a6eaa54c54c0ee3f8635773a07867241add236221b4f326df6a45d2672e9f303231f400289ec707b10b2d2c686331f4d4fdb08ca5fd016aafb05332972edb2d62a4ae04c5c30819b76e9fb84a0125850082e97c1d7a54b951085a1a6170326ad8c20132b3cc81a5ae851f4f8f4f0cd0888fb6f1061dad1f151508e105fcb4ec6c46aab7ea8fa3c010e915cb4a24f9b2b8b221634340161eed0ceff8872833cc57ab5690c59e28e9e65cf8b9f5d447a26c354bdd789c110b962e34fa06c676c05ac74167a119ea7fb753dafc706ab4b02d96c8d9bed49abbaec0dfd9d0516f6f29aa238d10ca3a47322bb74df7076e79183b3a835129d758590da6e3f307b06ff217a945c28cdee98533933f607df523ba43e2908b490259421d721c64d6575acee39b00b5679df6b26bb72b939df3d73de81862dc1606b4d3508544e61ab0fcfd467eb922ae33877186a13ec4fcc21608e8c1aca232d0c25211bc323724c22ca7fc9915357fce1697182e3f587659b3adeb0c5835062533c1f9e3e615e7ee1a1c4d62e7ab51429efa3f0298049a3237c8d4f36bc63b1057d4cefc5297265a66fd5988174f5f378f09727120e90f6e9e1ca52d34042be5f3cccda617e6821129b45181a33d203369f6554cfe1fc3d380f4c697880d9a861ed5c37da7672bfb5b45ecbaa7cc700b744247931fc15ff2577e7ba167cecff4f242050877972354bc77a8da19c475836beb8c800efdb19aaefc8d444772a60f2452d40ec6c72c446c7132f94b4b5e65de35b687b9159c9d396e6c58846293f67de54f5add6723f9efda3b80ddd8f8220bc572317450188eddb095686bbad08d38243303b6272f5f1b1d56c5eb87054c9b2285c1c0957d20d224248e58ceeedad722914e77e4d7e25c230f3759cdd1e58a291eebbdf2571d887bfc5bb5050ace1229b06cdd972aaead00ffa2d3af01b271b8fd60817b278c62d28e3f140c663a328137475bb72d4b9f61d7d3885f7c187e2e341ce9363d69df34ecbf19fa698bd9002a6f7217229d8db568455a5d79cb77cdba8932870f79ee452b986453c58eedf982939bc72fdaf9a22664a2446ebdf9c21686a4675fad43d0e36dadbbf5e9b3caf57cc5d02e9e8c1c3691a7dc438ec56a0fbcd82e11ad27373439ffa082119e0b0117dea72a25df4eb70ea9c35e58878c7165d58bce05d4205f9b761f5945117de469ea27219961a2779d651b993a286e4b9460065d3b8fd91bf68bbdd0a28787eaf829e724ed9e4b089bbb336d7052f64929b6379492f86a797060f165f0d2e9b3322a51b9083d89c4c20b515761562224a2a084760558090e3950125b00b4e8b7c5bbe72c2cc6798c9b1e14531cde3936d93c35dec67dfafdc82df92fd28accdc52b9c72979d9cfd291e333928bdd2906d7663a89f6f8b5f7de8d69def1e6277b8efec485641257c15f46bf181418c611ebcf767a8a30c428c5bca8734b0390f8850c772f9fe1a5d3d5ad4831d90e1487748762f5876f7e2df3ef9005ae34f9ce24cee1746231c1c246f3bc1eb6e0469520da6ae6931e1f986a27f66613c2271610fbc7275863329f417f5030f9eac35e39206eecf72e58de9f057d6e7d54726328f9b56e3dd63729e0bfd5b18db19c61105f22cae92e8e927184ecf571c764fd142007230d477aa888904bf1ccd3910e2374288acac1bf08964343cf352ab8279668e3592938effe8f0b56e8792db356bf4c14b9a399b781758cc8f86833445428ec52d425611f17a20f1329994767f3523e7b087f977fb069d43231ed62e47a66db772f455500a1bf27ec4933f1028f555a4f68f99820cf4a0d60a0af321166701946ea4bc2b89c75b0d484831fe8df105a2b67ec844cc6a83c320416a793ee9cab439f86b3db8bd6ba1d6545157c16301ff4f42e583f0e55b256bfd07c2971d6f623d333c7ed5aa9844b331ad40f91da56375ef8c62f45ea13ff842274e148ef56872fb62ea8cb8ce3f6860bb9a035b5149fba61e73f0a8cf9131b2f5c233ecc39272ca4803450d572838a802d891c68cd40732b17f7785e0802c61be617cb9dbe6651bf4d4f7f0da599a65547d88497e627d575570ac4866fff2bea0932d5b763c2f81f45c9508c5350250399181612d5cc1ff55fa357093ec3a4dcd4a77f4cb6a72c0f77cf22c50d02f14ed1ebd38f6cc004f3f53d036880cc2ce749554402ade722004665dee4025267109d03cdb78a23408117cc1e5fbaf81783460dce4e8c66bf703a2ea916416ef47082ffbb1154b8d679c3d1454cf0e77a1f52a34f8142e1728062fbf19c1531efbde4070b75272cea7eff5304f96c226223e03fb05559a35cbf8a7fc61f39ec5746a9e15fc4af6c5516c41aab4758f92d820980795b54e4a6b85864543b5d4b5712f826c20cb15cd4b1e13042a91c1583fbf773723222b7277637b8039e681ce5cc7869f1404ef1f620b3ed278904cac690ca9a9c588a672baf713067a4fb5ea3d1383cc4e42373e30c458db432bedc9f8b6259b011fd72d96f2aae4d5542387ea7e869516f609f6543e136df839b79f6d3181086ff7ec57d76d756e09f73aed73aaf7d66a9f51f3403cb61fd37e1aeaaa4935ac5debe37246a74bd68619efd5e34bac315944e6f982e41cfc5865b5c4d1d58b9a36ffe3723797940fa15df005c0d96d18e3ffe2c9c9215aa42178b23e2386ec5fddef8372c69fb69c2c1a5acf1768d8d6689af5b75dfb00ddcd3ddce8b65d269241a776060199376801c1087a1e98b91af28973f6182de0029b398360aefcfbbcf350e039a9cdeb1a64f7327b83ad6cf576c5da02ab58ecd972a5211d6e1da31270e791725ec75a58f4c0cd95e0da37fbae1f0961a119996820ac229d83d2697baf20ca72fa6c8d22000dd7e57e39696899e8ae4cdcc2e21506034930bbb16b7367c5bd0fb1906a20d4c63f6c356e11dee15429662441b92f09a5cec1b7700a659e54d23c3df658de237c4342d6fa9fad4f0c6160194a2dd2757f5a4f45e43560e8857772cad5fc53ec6651b756712892f6aefdb0dedb6b472a681d335bafe7da167f4e72cfa69d738f98074fedd6e9f35104fb2289a9d540262b18046526e693e47cf463faba1865dd855059a726ad8481d186ffc71f2cbe2990823e7a8d45f9ab0558721f87b51970b88de9d86c1c394754f2bbcc8485284a7ca08752873e5a598193723b4a4529217e84374479e92e80245024bb26a1603bf72738542134e10bc9697206e58f6fc8aca226843add11830aec8075b6e923ff40b2118738cc43bfa5c472b946e631ab8454999cad944eb65fa7a75e2d0a94ecd5db49b1324fc44e846613b0c783c259c1f19a83d793076a65c7c8ff439bcfe82204745c4e1ff048c37672eb4d64267ee8bbd6ce17ec93708b85f2638197aceef7fa51c051daa7b937ab72fe9424b4eaa76e9dd354e81c926d6c0bf2cc05a12d8c16660b9b5da1f3f3ff7247dc10fea657637910a83c8e655e65aa1f4ad5068b0045c643223e9aef6f8772ac98e96b247a6401a48a9c6249911e4daeaf4efb5ce98706a87c0f32260d9644db9ed19eb7e2ea9d0bf4b629110e4aefd0bf4d1cdc931545df1d853bc7e3964e9f586fa38d983315142850ee50c961aab644f117d82641aaffa0eaf2b2448812d431281fd04d6c9775042651b5b861e6a4346bfb0fc205f9ccf04d287a5c046116dbd86ee52a09d0d6233c0c60eca7902ba61ad517cb6203a2ef5c5d4ae62b1e53e891f7ca6a6b35ee0601916bff805fc4ff093471674dcae2ef0670e11143720cef991213753e7839e754d9f8fee06e9c7923105535b372f66456b264ff5d679460746f4d0f4620425f0ad1f2f2bfa2cee5de27a73a0636ddb92137cc3660103729c8ee7cc9abd4088c50b4c8d797573a19a50f4b773b7d0f7abdfd58dfd172f5f0c3473738b4241ece717e4654194e1e8dd81a8d6ef24ee12afd83fb1ad27274b6fe4c842386cea99bcb98f1e5e36e3809c6e551dcc3e867f38d14f3240972d8abd600ecd476ef7c156be12aeef9d7cc952945ca44daa2dfa1c3d78a4fc672f8f4fc99654ebcde2da009ccdf178b2dc70d0947537f1293a3346ae7c25c61725d7ce6a1dae8d77853772dcec8f9152828b3967d093484ca2dcd1680baaff17224f23665ff15dcb5f080a4949c146aeac8f42a19a94d25e34bb1bd9a322d6f72fcf84b0a5f6b1372f4df02dcd4b3d0d2d303995bff95479e7371d2b66dbd9c72d82332b6863388416319d8232f29e36c603b53cd69240f55f285d21eea5389723cbb8ca110e666bc8e707b2c0816eb6ec87f9fe720cbe104b70a69fbc64b6a72bcec3499b1f017ff9a1100616f8a3cbab900d051e6390ae860f174afbef2367245f2afec3d1c726c070478129c9a2e3d03e74a8ebcfe7d0679faff2da5db4c277312e8dd3d66df38e9b22f0d67d3c5b5ed9fe734823e1a2131ffa363529abb47a30fc7b10683cc1326f9db9571a50a506b325e93d1ff875aa0cb637af30d5c7286a35124833d7a087f98ce55805ba65886eda86e059fa982408b0cd279f69d72715cbc42436204e954f86ed0aff1cbc87f07ff4b7e01b1730117aadbc170f936d06122824cab718f31fa0cb9d6dc6872af7660fd39059d2635281b4211f95513a17b261b959e20589e8c76be2ce1ba7516b33296983d708d878ec44c3d1a801f38a144d646a40711b9ba1a870b31d763a7eaf129f129499e0a9f10640379ea38d229b7fa97b9474084bad7053f50a4901a141380815391c7a11b62eaf4e8927266b106cb62ddd8e125e7fb23d9630e8d029030dfb36a0e2956c81e36124be72cd28097e179ac5209ea1be93edc27c11a32f8c30b64d1bd96d2d48ca558e4bd720f4e901e60ea2643d426e5a0ea48c70685c2990f9b7fafb63ba190a60fb57c6ccbaee1afa43101d61313b00e2552b0b9c32dc3a4b6f1d2baa30575dfa42eb712c9cc44f872eda45c4b2288b51acbfa4643c858370c044d9b8ddc2b762b1e4c726369a518db3e0e59e2d893e1acb782de3fe971316ec58e12cecea1a5017f35727ea873deaa96fd2dff370b67796cdbc6ad709d9cdeae8e7ece4996446f53773c0963de8ca56e821e6e8b9c3755d2ca6877e6182345e2b31bb25659c087193371c6f921226a04001aaecb3a3ee156e1ce174528cfbd12616ed0db08fa4e2fab7272d577bc28a6574e21d628c84f0ee633187be727aad07c247f2a7c4981162b724235e6c2053a917562030466e77bcf69f1b0cb5453f46bf693eb28a44abe66186ebd9b07c611695e82238271ae44a3ab5b51ba3342fe6fa727f1b170cc24f072b9f6d35bfe4f1d32d02027f190458b62ee2e51c869de8e83265d95a1982c4d48e6086117f2d3e082213b94402e9237510c8dd76360479c71aa8289e234289d72f0862d7c02845dc28246a642be7c1f192da758292affe16d8ff544c2a3206c310f05dbbbcd1f2b9734327f85e954ed75290c54880c6a0a67bd312439850aae729e9919e1a2ff4c08be995f627f6225d6fa2e1f89fadc3ed061c6da354d22651c81550d33dec797d97f5f641b82cc82c6596b51543eb1be3ae67292eeb4dc5c7236a9bbd9f2881d555d6200b4bbab4b6e457c509fd1015473ac2f0a190260bf684e165549e252f501b7623fe4f4a152b9e75ff89d3c88b2bfec815b0e3efcff1d3a5cd42ce6630b3fcbcf7b24b49b250b1b88a5eabfa2e2e988c5266540b06172bc1b55f9d61163489a19612d6e5cee87edd5aefd5f71bf15b88bc2d7f9101f7202517884b949167f49830d1f90270892cfc46fbdc97aed9954a353bfec073d72747c4795bdcbaed4e366e73a74c942a1b8485ccc5fb51330e0701720e928da721618216a6308841ce8a5719c4016b0d15e6c1d94767fe9dd717cebc3078adf72b937ed298540861928e690046f595ef9445acc8f9b4300029d5157e1a22bbf1306e5a14f9d33168c07acf21fa05b274913272a19b890a2b9511222655a300d6b2246714413df208d45f0b8b4a3dcb70b5b354b44507ab5c622bdd79690ac08721944246036889979dfca305fbc8e51443da9658cb02b5af577b9bc084ef5e74bb077bc310c6855f9feb3ee96ccce8546c5091da437a5121a7cf42172ccd1d4726cd226e16ce7cfd0b7cf6b2a8aa224700df175097673c09c9f765091baf7da72815ef8134b4d58cfa3e2ad6812cbf50047207469124cf4cf878c469c4e6d25726d301a3f5acc5aa29bd639bf89fbe0762053dfdcc3790530443ee91ccc32355b0a5585f6102aa923d13d0bb28750967be3c90907745fcba7d361f089d6f41672c13a6258d6eedb211865abee24e8bf5da584e20c0d30cf434700a50c7c5f9604a0e7f6e85ae3f8491344df28bfee3b726ec6bf9ee9d331c95f718d820c0e6272de1ce5ea0b83f013a0dfdd038907d9183ae62f6380eaeeb68fc60e0bf65b8b07b2abbdbf39e77024eb1c65c4a454ee8b6c060b56fe68b747da10ec92dd2a4c72359c2fe48e6849887e1484a0e44e5e1d6dd75061a02c1390e3b0a47e37790542fea665b0d97caa1ca8d6380dab5a39713e8af3471405099af4373218504e22721800fce737db03a028f6425263749ecaaf62046a3f3461c6b1763da33121f51c74ca14ae4f00611bbbc3801d967b004a01160511caa58e8f7958bd1fd8a2d27280dcff3c79259d34b8527a02e0ad13fe8f707e9d29816d35d84323ad33a5ba6fc5929f37a7784271fdae9667eac54f076bce3cdccef04c9aaebde85b1037f772efd4a1c2535b9dd99583097ae8fe9bbccb922a47f879df3f7fb4773a64aebd65396237e71f4989528a552ad7cbe8faed16d686232bbd57a793cc91d691086b5942a5c02cd5942f7175dd213704626d6efe53b04aa313a6738916b77fc0fac9722d666f516d4de6419fb7b79cef117749d043e2e84be3f410646447bf03c8dc66b7cb430763c4906f8a1d7dedd39805539f148d3507f910a297d970620443c06ccc0985cc733bb33727596eb088428cd04f3d3faeb83d0a014c329948ec82f072d1e49baab268e747e056da91794b3864ec29367bab6dcc0e373578131664ef728ef22c9b7bd36cb3b0962a06b819872ff097fd7ea2304d3b9db1e8e9ac5d237250247e86e005c0053bb91527e24f66dfd6ad6e91eb971969f32bc19b15393c331054a8be28f016c1491a608b307c0071d6dc65cd02c3a7ebc11f910926d2616946fab522e15d64a62ffcaeb56ea46565644a7be942a4bf0e0cd71533c2f425725c63ec428afadb52e568435f02721a33bd9be76f6a78b1e307c9df42a2b3e06cab6c1ae963684441ce23b4cda77f576d84dfa8a2e4bcf09f2e764ecc6c05da72e460beebb8fd3e2355a71c494b52818f85dcff1a034f8f2ddb3734750cdd97726a817b580baa0e22fb25cef5ecbe316594f6d8a0795489ff0b0c5d4b919a04723506c4ff5475aa86e12c249757dc199cccde2aa29f3e4a1c6bce293f40c14d72869fd4f970efcae3b2f491e54acef6d53511cbff791cd795465245a2d3abf072e4e56de132b0b540f7c4f7a045bc2869fabda900707b24d4346f35017b2e8f703d04bc17ed4a897362e478861fc34b6d7a314bed2a7189de3ecd833f2135ef72698d0d7e5d2642156d9195a34950bd58b8f5c082b422047098295ee6a33361729a20f41512139c67af2c7728cbbaa546ae5160f2a6c03c17ed4aa0a0824a1a72fa9a2633802fabc596e7cdf478382f027a13ffb62651572bec61759f20d34a7236efe644855acadef7a73780109198d993aceaa1747c882642ee9aaaee87f5621aa2af193805512d4d8e8e56511f3bdf99d70fd27a1aa4854e1bb2e502e28a5b58e68b81716cc09ddb241859f5c97345fe8c3ae521ce003b23d8384bd399ab3764eb45c1cbc810542b6bdad7b8b8a3ab96587934c09e44d9e685fd8809866e403394b7871986bc25e5c7b8147910ddef0253ffbb3dbb4d5484c04aa3207e0c128c46d0c75c32e3e207358d70f9f6afeeebf271d2f7bfa7f18b165d6fd87d2d727674f0dfa216ca4b300315dbcf3f7bd8ec0f4e17f932a1d8cf4a522a4b224855169b9786a201e8c27984aaba075de48b2ec939fc1479d62887508a719c2c824cd0aae9e5f1b4f4c19896092c74772c0a459067dfa61867e8fff27c2e2f9aa304e4b4fd100af0dd3610b72299c7ea1bc0a9a50f20151ef96a16e7be57514e1d72518015698821d4e29b290573f0f7dd0db778a6e15428029534309ef34d980f72ea71e63aeba5a0fc86f65f111f5d0f586ecd184dfe7a0864458ada35333fb648e9a3e1c2aa62538a8c68043f7cb971a154edecda7a3665ea4ae179d5fb9df25f060df53ebb8d81277eed1af07e17ccd750c6d1a702a38d085abc3b8c371d412e05c80b80d2a6271f7b1734f9af5eeb2c62fbe197f2953dbe2addc1c1e34e067240a7e179604857b4fb47326bbefa65eb12c33d9a8a1e8078c3704e2dddad4d6fa617ad1b9b3025a965d2050fe8e012d54d3d57866668da363b3b2a0247126572269e955abe8bcd15fd3e0f0f710a6f5454cc84cc590ff0875107876a0097623f3d1dcd8e781a8d289216889bf54f74286eb33aba4c60adc4bc5127681370814895584773d3b7333f1c32d2625e12e47f31886460611274629955ffeec2d9ca228c8c7cd16da3c97a5a12c77849af245717f9fe101384b4bf11e3eafe04ddc81088af948e61e410a7d9bf9ee9913379f8d8e5eaa3ac18a003d03294cedc19e14aadc4489c0eaebc026fbdf1246e8ae383220a42d394cae01c59cc153952fff67236923b7887263bd4be8026fb372c5b12f26634dc028d950f0c2dacbf9f66dd72f66271acfb1ee292a8b969979774cc6fc7c6b43e4dd71bba49ef6ee9c9b5f472c6e24c92d74af6c8840cad8e55b016700b26833a6245d4875b9a7e499d3cb472680ccc759513a08b6ada7ba81946845be92647e52a7e3dca5a9656be0502751b55221f17e8635acce0d01d178ecb94f993f58d38fe073d2942380152cd4d7072f5783b30833af7498ddef4249d11391887e16bfefc85f5b5ca67f3f590118e725e02d5290b6051d71bfbb8bc373f63ab634bbe72fd59dfc23205cb772f31546856b7fdb89fcf1c5633749aea27c8d3f27917e2d702e337d56f0a7afcbe91f227ddc0a1f240882a1111de333fa24c32e2cf77f80cf8dfbf43f299f40ebb0e69176f90c957632efd345dd7ec1307e6858f77671792b8fbd3812946266272d54072b1189d1e8e0529c22979c7d650b6bf4163fbb7556c8d785794b236ec95be1a15022720631de908ef6eeeb0d3f9e390efd4ad36f82b5300cd183540ed8a31ae720451597e52bd8fea0b18df29478d50ac5bb9aac86069bb8f70774da6c28cbd252d816ff937510c53b8efb605296fb3b880bf4e4025f7fe875983d50db6e09b72f7e197b79efcb4b0a306ff29baa48b827beeb95d7cf7e500e8cc03c6d21b4b727e8c651a041036b77364b425734f47f1342e344a6de25732d3541d3c2b98717207026429dea43626c4465b7bfbe95bd5af651910601c0815a2df00fdf76f1072ebb8c2fa8ec151104d5aed3c4f10ca6d4d23c1423e6e8620ca510af2a629fc72dc48757564d412670567c851798a78afd0b1b34529c377e555d12285c1b7df635b4b3d06831c2f726f4369a17e3793fff2a8ca969b5fddc6fadbf5f4e73ceb31235958a1239bf0af74041b85c988089b6a6e19b05bee06cc3e095bc4d256bb2bc5d63be12d42be74f3442726660f7d81427a4991abb3a0bf069d9999c916de282cb757e07f52d3f820e693455a9d62f0325f94df24e1953f079697deeae4fc7216f0f641716c808168cca843e3a26beb7862def703824a9e23c6edd29521037236fba5c981c461abec1d27b2d8bb4057de409ea9b23f73a202c2ffd029823972499bf0cc989e745e954bb7ec575b2dd1a2ee6d6d3b25a4f51a612409e6f63e72a31926917b4948b26a2d83e96a91b08f65837ead90dec35281ba21e52febf072b5d7e97c075788ec34546c4ca9b3be16a25d172af5e84133e2c172ed3fa7eb72a3cc929cdc8a18b5052fa8d6d378b84e0da85c1bb78508e80ee71b2b424f1e64e315541bf54fe4b64f53d53d154ac4af4e709015cca7b3aed18176597cbda8726cc8848e750410eccc71643979f9548d3d8709df3b9640b7932cd6aabaced2043b7c30ff05c0d05f1f23604d4b80bd7a944e80d9755e73da053a6c9b08526e72f0b483a2e974a3ec2ac7436f43e8e43be3b3387ec5b3cf1b51f5a9b6380a25721241d68ea34b72ad72359ff5fdd14019585d17707ab3a9621bac3511e39f3d25036cf549bb18187781b541787be09fbad8220147124f1fc1efa3a87b7b7062724bba1886694903fdfe5a999f1f70bdd15107ed877099fd3859b7b815dd8e64468dd5993064850f53176130e28243b4f0e5c6316046c388249246fe7e3603f872775e3f4d041e29113e8635790eca39ba8250aa397795b95fefc600b90b366e39f4fdbfa2cc85b043b675eb99d3563907678ac7bc504a859fa4d2204b90e26f29ad74efa8dcedc9ac2c608f2e4b29c03c56fc7a80ad875041e3d446b81ca34830b0a69ba0fb1bd7ad5cfcfc77da5f48a1544d756f04f11083e53281aa83eede72b703034eb84272d013552e43522442a95e2a7dc45dac15ebc463247ee1a80472e23837a986359dba145c9f8d9e4059792aae3172211a6dc0c2a3f919436f583b7423fd7f04747842cbe620dbca320c6f1357f604aa76988f6a47f0ab6547ec035a22b60e21ca76385771ccf1b7fa8c770a96bd3eb6dd5b79b8be7391a11db6725b62a1ab25ef3da6678eff2e00279e152d9a0ce8db876301774b439f7b02a072fa920bd60faccb538d059f9d0840c7b1f76f36c9769a5547d35b56cf84597b725407cb55ecd58bbbb7c5d111b527c85b843930cf119eb3cef8884d6ac2b25a72ca5b8e2ebd076bd8aeb43770044687140353169f2e4cdbb8d7aae0a0146fa71e9a4357c53e79158327d18566c6d0de0cddc7f418ce3bddb271a1dc33c77654580c61c6800e4cdcd7f9c3a6f6a3e150360b9e8306022bf998860e7c295f4df8401aedc57ebe6e9f598fe914b78380ce4353b3dec0ca22f906238c9ea41f9d187246f7d25dc1730f19a9dca0f4367d28202e3f05777102dfee8f24193824418e26fbdbe6c7cd6e03f472b3813a1cdb3f5eba29b450390f8fcb825136134141c572df31e1329f1c201beffdfeaa1155b6016ef83ebed936ac0daa1e08c2161c7d72885ccce7fac203e6f77ea051db7cbae38748c064aa42b36524f78d7538359e720558d7a57567d4513f824e60694a7dc9fee3f1c711f13536f8abd0694c553d3c8164f518ef6c2e0d5dafb0976bb0193d1bdc90f13f22caaa9620c105755340724ec77fa5fe05046a6d6905b9c8147918b0c978ad8df2df8143a02518133e5172f2076be44e891cf116d66333e8ea74e1623f3dae2b10f1388fb828d934799e72e92f0fcfd946d45740c25aaad0ae8d1c023b6dc513533cc2db3a612f9d46de722b0a95aa60562ed1628b2fd38324607f88476729c2de3305cdbb73a1a8f5f67269815550cb4e58bcf67db8a489d9794df92e300f774363e94671ba516b59c472c3ccedebfe0ba0994a5ba7c97dfcb8874d10e006f345bac30a6e3bb566680e1c67732af68203f7e3ac92d324b06d0b65a74de16a548530b03455f9415ded4472bf9e6db5fd2e45994b662749c5d1d2bf284f9c9883d910a49a08a60271ed3772e3ca5887cb7db0d426367264084a089f8bae2c94c3e9a0fca22836bc12f77872b4855cb2446cb8b68a5d750d6c3a907f92016c0afa4910d744f36090655f3d7266ab46bdbc79ca30b1477e54b04d65b012ee2809155b8d7e85553ead726c7129bc296d25994a5e63ed4dc996cddc1418be41c2582f94a212ff1eb44eadaf6e72f419382eed72a8804be26a95ddcd901d4b3d90690b35b8ba206898f7759c0f614c812730605a28e8b2b2e6b1b9581c76713fd5ce5864dbdcfad060b68b42b54e9c9d0eb4bdc74366012cd86cd6bfd92526ab43053f6635c49335fd54abb3bc723bbdf0133ba476a8f78097d8704b1fdb127cbc283c7f8ca042f03b059463b272f88a1e8523a3e1444e7d9822c30799a2117662a9b3dfdd353ddcd5b7706312726935af015b2e6b1b6863b8d2cde083b9a930cc2be5562dfa386ad49b34d85a69f626fdf50073c1ecbba1d0c1502fd4d7f557eb0ea3f0d143e290b521f994a672ce1a28efbfc554fc7f9516e952c6a44921e2b7a621e27de997ddb5f47456e972a6ae4d8d99338d34573169c6cc9888c2ef953a4f7a0ac3edb27fc04b5564c92ce10489aed1312cc00fea77164d1751067d952c25ad3fdc01b4888a91647d41575164707ee7392e76814b089c610ae8f530d63237ca847bad966137c1cc14a2721f448bb36e67a8762a526d93414eb29fcac8a5e611b2603388335aa47845a772890e2a62f8d996344d31ee9bd5a55dbe48637ad17ac604c245ae2b221fb6cb4957e85ede3edec71ad5d64f9148e8c7821ac77f1295a8008780ea32554116114600b14a9496a1ecaf8068d4ada9dc8b4a37885c2eac3618a08135a1200e24b2724c6d4ec4921460e75a06f33c6a34bd652c2ee10556bc7dd74f2d3acb4c85a72eb84f5b5fad31bb350736dfb632dfab4b0b179f6bd3f9062f4c1b4ae20de232723163cf771dfb2edc427661f7f7a93571dade0dc0806e44c9dc73bce5e145be72f6867c56f50411bc5c8e114e636929c6576e95d6ccb3fdbb25b14a4dc2760f72231139122259ad7b2d076ee52a05a850741b8baffbd310199640efbb0d447b631abe55a2dcbbd072a2c8d3ed25ccd5e6efd6795dafe1548d97e9f16f6d025e72848da9c9dc24c20fb85bb2bce5d50218f1c9a93bab66d3034efd9c7c7bb39149dfd2a58622ffc689c0ca83e4ab4b1ffd9a001fd40e46815d8a75356eaafffb6e4131ab91980bb1be035697df6bd9e55d4f7de4c90843239fca5ddb2d751173270b467e1cc71d44366f2d3ef2b066527d9c58a46ef5785b76b074b2f3b676d772f0ca774cc6c6097e6aa08f1d4d4326ad76d79dfa9a228917e85215bbe0e81972de7ad9e90d9bf5a7f7381378b5a4faf24989fbc58205dee0ab755a9b25a75572ac9a0445ce2c8b0ed9f229bf14f7959b334604b586d4f1f8ff19e81490aa1072896595ec0dc7f3a6917288b0c143626d1b253e84291ee47aa78c000b83bde33006b32cd5bddf0160a2d4b17f6f6d033f35075a14895facb165f7b546d8620b72c8424ad293fc6fabeeb5843c3a5aed01a6a55d0321c8965b0feed49875b3332c74129f0dec5b2f56ee1bcdf6673e4b7f619bf842967715b78e588abc2eceb96f6fa289bd9e36c8b376a3e51f553b11069a934eccc2cb118620dda4dbe17a1418e74155d6f4f4d7885919ce02a7385591453052270531dab47ee42e914576e94a6bd461f641947177e2b41b0c41501d6bc95ed832ceecb0bb75df3ee64e8a59729f6857676fe4f5600d55c1b11e60acc85a78c1b12a97ad757fc4f97994313d502f860e0b86c101c7757db84345c3dd4306ac99a3ee0ded9c602d556ed0bd261c398ad0dac2b11e42a997b52733a5d58a3b022e684c0609f60e886b662040df5a72e0dac851c12df607adfae52d4ab3247f4bb24b092ee8e468b425ef37c39e549ab06e83e26146388c98522b23f1e17ae5d89253f98f28d2708d583961a53f553ae833741d013f6fc2237eeacb36ccbb8aef50224c198e7b2bcc4b333474e35497e97e8638c06454bc7caddccf9325849da641c75dd884a1809a909b4c24ed12f5033941737d858ea14366655eaea8d793ad774bce7c385f88567c7cadacdd6ac6457e8d90a3dfdba5efb8aa5fc3cbd0e0b1b0ecef7e56cc7002ea2fb07de672b45b1f1b00bde5bc6ac880acc837e29d853ef2ed1e73875c7d89d0463d1664721e0df14b1c5e2de621e76d1d45c3024187c02f86efcdf6d70012375465d3f6728255044654f6a980ef9ae27e40842f5320e8b02a2553fb18039e6800f0dcaa3eea3dc00044eebf2c20cd09d0d763b6dc688c7313f0c3da6f660f79d2675e4c728dc8404c3677e6bd3328455cd5268c70404145e6bf84a247c60912ad9474115efbe7ec5479ff48420a2c508986d469aa0d5788411f70067300b696d037791f53a79b8c35981593c5550b43d6435313fbbba5e0a43835cbb698c655358cc3cd30a6171243ee36bf07d5bb542653a986f3cd4c3be095b12c46c841f3319f3510724c6c14304d3097e706ab0c6b4481c47d1c31f6e3814070defb550cd3aa672472f80dbebd38666a243f649a8013765be05894bbc9388225c077b9a3db065bfa224ebf02b74a6ab627006975ec1c278a6c1e6c6f74e5d37d2373f60f92e2d5e019f57262823fc839c64c38069a4f24883df5eda30404372d4d3bb9a7ba91a0ce10b9e995beb8116c1fb3a1262c05d78d66c9ba75a2651dffac7392e63d4ab1f572f3a663e46256788c07eccc5074ec15976ef5c90fe401f5925df353a8474b900873c0e447771e4c4c1ff7b03f95194b8ec7253f91efc794710fd3433877fde024a00d7f1132bd0d92f2ba29c7cd08a71ded2becf31e4f534474d9fd6aaea7e4723e846027b2a3441fc1f8208c5af61e23c20e93d648e1740d483e5af63e09f015b2d9059d43e5725a65bd897d18ba9399b636e35e190fb2c04d8af6818d62a3725c073fc07d599b3cf01bc5c5a339f0fe497ac56f8738fbd9c4806f3f43863672136ef80f0e60edf8cffb317fd3830065f6878f47763941c1f3279b3cfce071078ba6b037ab62b7a0399cbbdb44066bdc79e14e4e3d5497527e8745d1c4b355728ff09955a0135459a0e6492e9af0ead1c048ddf3a3a471cf5d7bf0ff8851f71e5a2fb2d9bead79f4e941a826e0d41cedd4c6a564cbe3c8a2573a85e03638557200c4fbcc9226bd6dd8e0196d157f01d3f627a26f8710b1b80cbae28958221472b332571a84a10022ea21825e28bf019af3788973827b35319f297d30719c5f72454e3e4d01aadd38690e6a63129d3794e79ec758a7f6663359a28fe385bd4572e0ade8c02d186a9b8981d0e347d9a2dd323c82f5b1083dcfe05675527a79eb5ea1305da025ad91dff24f5444138cea1260e6675217dbb30fbe127d8c2cc98472b6f2452e6de1b61261b409862d20193da737e2ef47779546f290e4b3cc805772b842766bb010e2d87a6364f41cbb55f5f866f0553bc26e49c70ff1123e015759bd4624cd7dd36336c46325447933713997a904073f3114a3adf902650b036a725ee64eafdd61d20ed6a91bf50283d9d49b857392c7e53cdb2331f1675f332567a5abcd048f0f34004a50d05125996e26a8d9ea04d45886ef893fca5be7ecd4722bf5af3e3480b1c87621a32873da9bae1a33296990bf41928151269dd5773772613e063af8379aa715df75b71dbc1c732a8c88f600ea1dac39ee4d1df88d18720044b69620ce55b8c753df3b2b920142cfe2c233e121d3afa729d2a233ddc872f859ee93187860a4c306bbfae531f334d71fc1a7b88c8e14d7ae48c0d3d3d81ab9549a461f3b90e7073064c99366ead185008fc11d1fc7ba7f18aec7b2e9745453c881ba794442bef505a1a9bae1582a1a01cdc7234f291754d691637c8a0a4911a64d13c7c144d9dae4170b8d58e98207944a025f11428bb71e7beb38649472c0b3fba57895612e0aac6d79e40b1b1ec2218cca2311cad2677f615c067cc3292c8b9f50abd91d4c0e32c3bedb5932a4457ce72ae067a12c2dd49125fdb1877277965daa1d2f2eb064283e373f3678ca219f73c875f165d5702858c1397f3172179a5c8a7398237ae32d1323732e56d298ae65c42962403ab5fc396f4ee01f72d823a714732dd5de8bac227f3d95ea60c5215a24d7f07bac2a18dc9c6a084f28229a139e7fcc91a0cb6c811cdf26b23439f26d7065b9ab273278261b09653572ab17cc00d5b828c3f1f19aba82a111b5e0e57395926e9c9d240e7aa3f5de2e72d2651acdbce2a708e5ced39cc1717f27c0a036ae21e8bdb1c1f29764d6d95d72a84e7d5a0497f6e376a7a342f39774f4c713062115be194390cf681b6cfc1972daa413d95d5e4061467a245e5924d9b482bb986e3c324264c778e4033b0d4f0d5adc787d7f1f97f2c86c2d7475de45b4dda4bf094f0df02aa9ab6b18e5f94e6d7e643b98542f710d9f43f9efc1ceec4dbd7d23f2055d97441bc30211056c7c729ab478a7df1b2710c4bf6f0dcefb8e3d875a2815dc029076e835a314e7be1b614220ca856e6a935f6b1dd839ffdfe65b51ce1853ee9481386e491d267048f87299255a1e04600c62557f27d6ef835bb9ee5ede37c9d9ce25cca77f6fcd308c0d704bba5f2fe4b5bd701b20e45138ed56d0d2214f6acf8b6b47a28013a5f5215852da0e172a71981895fd5fa9c2255c7377846568a9e218ab0d5647296d4135500ce6da7f6d372e6ea93b7dc11c06d7792a92c1a4a99555f9351a4162299ef823dc3c0df2dca264239b00630d483be5146ae397266c331141bbc8a58a69a91b724aa9867cfca4c38b1e3de77fa0843f938cc0b7c59e06ac92acf288f39fa790727e45f3a0c56801a0e2346da592c363c320b9f82013f37f021ed78e65f131c154d14da6b4ca9e38dac2db5af902a036f7c69a7f038c7cc03c285768464a6ac6720ebe309bd530b202c5221f4bd035327e05c9c69427443f69c65aef1081a38d1a998540f3b449dc0687d9d64cc754d31e1f3fd053be68ad2e15e7e21dbfc2633c3ac73ea0c615be67d7db5463ec510e4a155b638a2d436ee11a29219d40568c72e9bd439e0e8712e00ddb75e4f1676c6f5bf0df53a0435a33818a70d36cecbf72230a894a2b69be18f89f80ab65a28ebe0459790aea8d0b5fd54a5c0946372259127521a189d71dbbbc4bf5a8f8305cdb334e4fa1f8e4808ea388213660ab46725f63df0f753f2e33e6f7a2c9b92cf2e81ffd490bc54fc2bfa98c90bca56442334b0402d02d7a3fd5b9be5fc5521ad54257a50cf89e4198f1ac96144ee7859362aca5cae87289c72e8ed096bedd71d1089cf49f654d5480d78a6ab20cbef79472cedc1fb7f0193f8ff51b6571f59fd43ba15ab364c6de557079acf006ec7763723485d7542bef86887c9e9743669db213f3990c03d77294b3bb5c01e4e1b1d812ca133ff27f2cedeb3861c225c41b1d8f4df0cdc41d1d52031406a434a5223c72003aad841510df8907e0a4de4386f4c148d8491e31c407f98c6c9f4e6d37a1721ab6f34397e0e0ad6852fa867b0e5f97b7319164a6293a5470118bdb5e166f725d4784682e5808978a38ba3551462e928de38af268584ba75afd514d208d180799097d189eed26c8f33aef490dbbc8ff6655cf9fe1ff832ce0dcf46da33b443aa97cf468ff1ef73d175273815c9809dc42de6b2d5cfbc7dd57d59fed8005e572ac58ad930a682abb35e8d686dfc228b38f58f48fb387b53122d554cbf732c372e240bcf1e1e95d024fdda85e78e4e3aebe0c1766c8ea3af3bf9041a7c8d227523618460e9de71e3a4d3b64b5077b33833377db765bed8b52e4cb6b67f97514394ca40c7eb665d428546b707ee15e876f90e5631fb4d6c2d95b692c61f69f04723f568ccee07faa41a0c92625abe917f760d56c9983a5e23076e48df34e1fee2004f56fb11491de02fb1c45572481d60ef586cf4dcad644a08e039f9f0a61914d1a44636082fb64d645d9df432f3e33a861e6bfaf49b0d208b80e26448c8ae12db38187b2e0e9095e353fbe18f2b5e5e80b5e6d50b09e97d71681647f7c198c556ea8d66153741af7fe8a2acab45f68fa27ffcabb7af92515972c10de64877872b096a045850f59faf0b1fb5fa93fa8370de0f69d7c8e7030a1efe8e08985b2030e3e01ce10e40676e638eae3c9319b81f8195d21ff582fbdb183dc265cc8c5424efcf03c8f1d8ec6f656f9a92a695d70d3d020a7ae0a56f65c58527f2456b44c16abb92c725d76b223bb04e27a0902fa862ee66f39aef23cda5b657d87d22a4db9666d7c0b0e5c2dc18a5ef3ec914f34131e071b1aeefa1af85133b64d3afe216e74fa1b7c474ade0b6772cb894c0f7c509dc602aa3b677de97a728745a0174c456b03412b28452944c1ec9fe355e10b173a10aa9ccea1ac97892c7d1192f86d888936ac5349600199f83862ceb5778bd687560f50584615603baeaedea53d72ec976f4c5ce85fe31e88e35a5f17a0bf5bbeb8b3c1faeacdce915ebd4964705f55d6f931e5dec371fe1fc4d0430ce3981253647a0e2d56c820bd817d321bd172366dbc2a360beea3228ca13ee7215c0161276057133d64625c0182c8b457af1737868440e45d92af9870ba33fc3ac03e57f6dee839879eab71e3e32c5b635c72e402ed32c3fdd50723d5219e7622f2376aeb05ba5372ec844f25dcb44e0c8d728ff1a445444f33e809d58cb2ffe87b3416ba9d0e11fc36d42c20ddb9b304be480738e41e4b6c7d9f3e4b3628c873cee8e6037c5fc65dea09b7c12427b6eee03ea5d3be28147afac22fd244cbaab011391c21535f4da9aad51110b8436bcec6717aafde9f38beb558172df7fce904eba94422a8471ae3f9c52de9d623c6b05d1e3a1f83ca539240107f06adf4c3df299b7e23e4ecf90194078121ca162573ef7214835357681d2b2594576bb25fe1f17225c4c4154c288f16e50b4631afe67c61f2a4efaa16d8eeeccafc7600003a1c9d4f02310663bb6b296b4d812ffeb496729bae0ae1e882849538d60f85138b8d42c24911caee4faf30d2e1c6bf34a21e5b58141ebf0359112ff675570de7cd20eee31f0621d289bb614ca781975f0d060e5a2b75ab15d016b1f2567261b1c5028f411cf3c1bbdcbbbbd5b2b53b5911cd7276fc7b3896605d5d7aea41e1447238e01ab9007ff7e32c96da16a894126f8b727a1a625a06a4cade7cff85ce566398e59a476b591161e8a220299382e4118520c6e31969bb6570074ff39e7f44bbdf27798d41f43f27cafb54a151656c676c03003662f410bf87467d935f17471f248eff84151f115b5b43f5fb8f79edf3aa557500f8d230bb2cf455e977d6b246bb2febe4a772535bce7d8e6a6f44c4d5d772dc2ed510658c59bdc2868ec10ab0854318055eab285509e9e072c053b8eb3472f61d64dd212c529d399ef208327b585c5772ccd7873fb1defe8ee893a72cee6e145d947a828060736a08d5c341f0d83d305bcc707644eed57e94af4daa6388725efa740d8cce821321fddb8aeb3da5d6eb19a0932ae3ef975735938e96371130b8966b5630b58434b67b8fe85a364f62225e38fb78c6ac5ce7716d6c26e5af5290209eb9d1949b517e9fe7a3a19dc9d1b26cd4ae5403b6600af617ba1dc15d721d581d71409ada4333fddf0f824436cc30a485487d68ce210ab20fb0ddd5943ff2cf30c16d9a73c73bbefaa8522cbdaa3aed86d54f59c9bc63dbc87eff85802d7af439215ca28aff096f310090a70c29a6539f7f04eed9188ed141d3891ccb72ae5d4e99dd6fc78e34f988320c7d061483485e8d9605682881771179d91a3a724210c655ab5b54057b557979f92f48f27d69871502e8773d595b6d140391bf7243a45bc568d0307745a317651b3588b00480baa7b5fa07c04a3c806ae85dec6be0d1e23b82042fd0b22029de0e24f80ddf0e19302d8312387879443fd4a6c03641450b09d6d57ccaa49956a0f671f37d080119e10f02743554f4795651ffd924741148a57d66931781dd5d1231d5f1c493ece824a5cc7b2759a306e737a266723bbcebbe0cbc95ac7fbab2eebb37926b2b116758e3927aacba2b05d6ae3edd67396de107f8a2b891ec55e8d6d19e2bec3c6407bdb08d0ad1fa0f2f5fba9b3c72f89f0776ba8b65f582a1710c0e03127c47ada4d82b27f0cb291a8923b6e66572f0332c8eba2e3026f098d8fc12e1fb397dd7d30c7d27f03dd81edea6c54ddc72d1572b5804fb0466cfa49d3b0367e7a4d381f09a3f5db4cd49b2811fb9ecab72185479db0b4992668a06299fb6af3bb8a845fcd12bbb4a4198430fc38e8c3219fa5bad7ebc94db3e42edf1b167d73c0b149ad85a04adf46744e0638614f987004cd26717cdd4c2ac268b618fc4d75cd0f3b70185b19c183df245dae753a9132a88ef5c51cd5409f02dd191edfd267731027747f09d758f17d9e9df45d637c07271cde20f4f83184ddbb3d417a87bfeeff202f7169d9c73bf368911f69f98eb383de3a30d231d5133a81175b37f8b871497fa4d1b486f6269f1dc72f7b587c472f458552f71572aa930d797b9886ee245ea46087df077194eec5d1096ad212672fb9f66d356d9aa494a24d8ffdcd93538af929d422289eb9ce1201d3889a83616d21a2d8d1bf13c2304f0dfbde963b81938f3c653edb1830f41dd54e52e449c6e9616d0df5ee31f27cd45f409fe2a1ca1be0c2170fb1238f56437f8efb1d31e4b4d2943cdc970e08d92feb93efa8c5319b02f7a0fe85bc6115c4ecd2f0171d643310ebc5431569cb81cd9c18d2c9df70a68bdab3c8be12e7eeacc66bebf4f82100f34fbfc3d6db4c1da6e3f53daf78c03b5747f0a759b34f54b65e111461d58722ab195ee4adb352cd820d125c553b5b11a278478f806c7c7aa5c4a75314d4207b55b3b293dbe742698037a5f970c72aaaac0d3ca7a2a50f8838c8c04d2db295728c11703e5fab8576d3853b07d880ceb34d7bb216a7ecf0f642984272abbe0122d8e8ed0b92015bd4d8a9de7b7db1d0fa70a096dbb3ce8e004eeb61c050ca54c299dcec9f4ccfffb43569b0999bf68aa8f518fb413dc9d1314708a67d5a0e7721819092ddb011f7925b43f5ca5015266ceb08163388031975668cbc6fe8ec1728c4128999b58ffb289f3f9f794a0d742b5e8dd56ad587c06f92582a101643f723352a46cb9fdca67dfe87363a0ff09d1aaec9e36311c4f5ee786ec3098d7a872362026330389a32864785fb12133492a160ca6e174982403d111f397b3adb4724bd11920343a12a9c7d3300f760368959003649449e0eaaf3b7335c2a93e36723a4d2a7df4052c6484d990c902655781236cc8198ab5754686d600352476eb72af96869db182a61df7c5a7eff067521853f7d211f8d4ec451e1e04068a37b8724f2888a1833c1cb61fbf6a590a1314d8f37587ee726faed08c94fdadf6ceef72622588858315bd969100fd5236aae16c47fba571cc3093d60bc9630eed1c2667287beca4422ebf6791f5c94847f9fb241522ed14ef8563fcab456e98fe5b66188b31d642b1286210c8ade7858190d2434c04c8279f4777becce3b971fae379444e65abcbd71e33f39d7b8b41f5b4d4aaa7881a50a2373225895830a3aa4f7b72b2e5ddb14551da188667035b780b28df731cda9bcfbbd0b36766b7a421443a46aae70cedb9a7ced8e8ed66cde818f52cc07380535b9f22adf914473243e94f648c55d98811cf15acc2065572261b78e338b1889a46a9b5fcec76394e6ab3a75dc1b5d48bb3c1ab6b9b197635e504d76de277615fbe6fd8fc3888ea394c9d8f72806eecd3ed081933e7c425f33a4f08e2df749c2c7eb5080236c6753ca3f2e04cc542f43a6ecf2e81ebc2ad3810e9dfb856aea625b91cfe00f6b4d8eb37fb4e5bc94293be25a7f785f8e69626f29b9a0a21b521913ecae326e4a7dc4a1e1fb05f433423f18aaec31555b2cf832b7472569121004fe64a4f33c2bd4bc88a61f26118f19a3439029e3919be64b0c245e373133ea205d09722acd2c23c081d45ea72dc89ee9a1054164380fa98a2b6e0cfee3cd6a5b36b1ac4c42c2503cb2390f54a56f735aa8cba409ed59681ce32982c174b9e4e748940c8076eae767b47acc80f553d992fe57b7e0642ddcba1845c45f157f2d9ce3d768cbba7723bb7a80740250ae60810b9ae418a559f60b7a6d8a08ce4ba26604095ba39f88e454742d28f7223ddf89fb5157d8bc567183d23e52938c47ba21072379886b2fe68f54473d71488310a8d6bc6f60fb99766b6c045dae7aebda5a5fef4b90eeae25d2d6164d92da52cecc42a97053aace31541237462229895f6f790cae4ead2e552e54321ff6f30dd3ca31bb344aec072d2af6059ffbc56d25ed1e632c192d4678ddad13e155a150f65fe5b0c73106c04722982b635ccbfb7456e6131203056469f6c9feb6a72dc8f90fb5a2a7660f2650694797095409c600cdfeaddf8d758263776b528947276aca15f1be937fbac93354d48faf2c1dfb7c157569ae6aeb520b15b1ad6426f29e925a5862a8706013350041dcf24a217c56c9120e5c9c4bf23272310419572c6c8853624084d938e2c9ccdd5799abed9711d9886dd728feddc232fb4bc6672af58a899ee5d1589d54051248801911d33b9c068fc41856dc6e147641069cd72303c31a3e19cf9b86f0cd179354c30b0036a58e7ce70475bf69a23a4127aed268966e30dbfda60155687d65b31bac1501f05aac2c39020da58ebea3fb63f3f72e8828d87172909f4401e0e739958c4771a6d9e88c4d020a481d82c4c34360846c2883e69c5087270cb06fc0869588dcae651f59a806ed796e867ec16abaaa072887e712fe06a8d1948c23ac095dc08d1075d4dac1264be9057efbbf8c25140724ffe7bab59e44624d87c1500b9991c11c848f0265633cb412e7742aeea6eba72a2655694f7f49b8f62bdd4128e9c63d769fe737764eeb133244c4afe0fc2831850de82b8eaed44d1c63e16fb55928bdc7dc93ffbb978acf9409c55a55ec40422a991bb58a39fa5aaf1bbe70c54e9045c20fd5c8e6417ee63e0aa22845b80c172f13da59ec075f1761a578fbc049a69b6cf22326be7af7168b98e9aae79c16f724c53c902c59c479a87e4d77251380e1cc2bf20692e8a2256e134fb98dfff162b5e118bf5c49a51b83b0c2333a7296bbea23724eadba62525f9c594312bc5ec72b8b7701bba7a6fd253ddb0f778f630ac1d04385c6dd93c8870f07b3e0c63f372ddca7dc6f7970619baa329ac8dbdf5c49a95f4e9b186665c1ff87e9643cdec72410aec64b335d1098af03f40eefb5c2886a24e947cdedafa3b19de39bfd5847211c2f319f8a8617ca5fd2597681f2ce7496321c119d181ff469e17f66a3ed95503dcc1733a8bdd3bacc30ade7017ee43755232fe9c2eef14f4f728397aef2a722b51fc000e761f5797552bd3f4a3ceffc33fc9ca05758b5b5d6ef21ddb22bb2d97b00a2ce66bde2799d9d5de193eceaf17bc0f430ef9f245ca0851279b6dc972574d5825497fd5d9482d4cde6510ee8038ac39f5bd512ad8d7f223e32344ee092ea3e395a99c7484888e2e879135a5eccbd960403eebd7125f9248fec475e329d81f72b9a8069622c3c0cce1123165cdcf7d21f5c45e26c6c9a39c50e5fe3b721dd68856511ca166b14318876b7f2f6cddf6997a6d80a9ed55d2a3d3e13fa30be6be4e3fe7ea09997cecc1d8846124f5e6143dc9066eed1e0dda9320437a0255acacecee98ceec8616df0d94d7251f1dabd5873a3403c06446a03f71bab518729f6eed3979ccdb2141d9ef7967d94c1514d2a15a1f0fd54a48b4c2b18f1f0814e379b1b5f0a920657a3017f8b37af7f965133f4e04dd7cfeb0d721305b5e78724ee5bd0257712c84746eb9a27ac517c02098fcf241baa572cd5d5eca9650ce295bf4915014d80874fdb634aa2f0a217190c99f68639f82865b73010ed1de283318cee41d651d62a7927f1e9f1140ea3c34c63d675363db3645c4c2e3bee99903d2a4fbb6b4b50e91cc919e58496721bf4e4ca6a605d31eb1dc5ed491fa1d09724caa1719bc18bc20ab87a89c2fc93006bae4e28fbc1d9d2a11e070f79e3982726acab71c70122d325ca998616cfdaf15bb06e2ba25f817322e4b7e8d5116b50756582a1b750f2b9712b2e4ae0fa8e98c30cff1c05d59b21997d5ca301d805d72c43c910a7e55bf9dd5f170cd5f0cd1981164fceab0c50906c61319ad472c19724bda6f37d7869176670a8a5d9cbe0b0aa544b424fbc9bf780217cfe8e491860051ad8c52533c5d3ccb6ec54ed89638aec6f5d0b7031d36c3d7e08003727877725760bcbdb664dd628d48a831c06eb8d662193fd651e54987d071637167a7bf72c5fbbcb612afeeccb43bb5baa198957cd69b36525216ec71e3dc7c3197ac7b72e7aa4cf7c919dc6fe00c802c4194ed1719cfac1c9122fe5ecab582f952f6227286b6b3fbb175ac69f70507b95ff8dcac74f1c248788a3161eeca10cfa4541572257079b415ce3e36dbf42b3b6b36b039e12369a2fb33a43de3d02493c989924b250bf20eb58fa82c0cf7aaa5e97b3962d4733c7b5348d6d045a124b6608f8372214df13330282431a682fb44f30666d9ac0e87c04eed77d43be3deaf8ad0754f79499ae2cdf3f25dcb2d5836a648b0bacf36db42cee1a46835ea49bb7134457253b4a8dc14e648e3179f8e9c3043981907c43e36acdc6110fcd5bb8ae26f9139e18c5847b9da512c574c0935772001aaee8d762a01b27c8ade44d9f585c5a06f0d6f4a0f7955766753d75838643bbc2c94eeab0f556ea4acdb1808f3c1edde10981e2743bca30cfc8662465c1e46d76fcbb0e465f1647eaa7379f19c6cdb907261dc5d0762d197a94dacea3c1dfbad8ec374add62c63df40ad7a6d90057060723b803a7a8daa3b3a9909c3cf1a81badeead451745a66b31b93af622eb2633d722a27936335395b4a19eded03ef6d1f828d1835b420f24ce692eec766f7fd363055e960813d9cb56412a7108f3ac8c12819b55adc6df3d1b12475855760402766bbc6f43f07490e320028772b1f3691c486df3a8e3b2a1171aea163f429bb397052442b598783fb336d7d786ba542f5981cc9a8d14feb4205d88483ca80c7a072a6af266d4a862bd81915d9cde22c8a688ee8fe64618240f574d892cbf2382c2906b81358da563a9cd173e3b2ad5bb69bbdaaf0b108c1cda30f1ce36d90ddeb72bfeb0159a7dfac4c41fa220594cdd2ac20615ca86504633a315c4a513aacb96e787489a49fc59c4e7139767f9585a1508f2d5671f271a220b87d13176613d5722bccd1ea4acdad33282c73565199614176b94d9c9b04c5570397caea6072e950441d915473c25b8bdcd6db2160342a31c3b1df117571750e35b6ddbedba2df5009dcb3e5a66afdeead333ee0855965d3397f0d973c003c7ba64379a6ab1b8a723fb21d3648741da375da5e0128195368351435c626214f2994ccb8e1b581677210f6548f20ec9e635e83436a4bbef8cf100fc5b964e6d066bd3ca6c0d791936a469fcb1a6a2b605abdd9a60ae12571d89c0d258a6c66893589810514bdffa372b2324fadee81962b74209930923bc88c94bc281a1f38759303f6013c0af92c72b34e9275c4afc82c205711c2ab4818e6195baad8050ac0bb5a2d93e06b28cf5d58e27225d19613cf01a5864e5193729b3122e0041b7937c3dd24af4a53c3c672b2b6e0d168e44a14e5d7fec333237a22df0f122cdbd3dec8e7df8d4911bbfb7232b84507e64e77cfb03465cc0d68deb96061ee1d6a73a5039e537f54a0465b39cdc6eb7e15bc2e731f84fcf678e4f9739bab3be3661563ea15d2ac02e8ae7c721a418ebe789348722430b5216d7514751784230b21f49da0ba16315ac1cc6d67313cc81dd9b9d0602405d492e08ed22d12434d03c2ef732350f6d2c239c0542f06361eaedeabbe9344758088ff1a68ee78a526e1568006ef643bd10f0ead8072106862efeb32dbb363414ee292fd48545355fde54f4b1491ff59b7741e07ec72b5f7d90e12f6a96d143364c4b317bb0b1a5cef8b6b3c2d439ada6939e0bc2172f7c9fc9727147eadbd829be200f795fcb3aa44b099d00c7c39fd2f91e21c4a5c30cdff9742ff7e13ae4b9bba546d8954edb2d2813318201917d729af77fe2072c28bd270eea14bf2572397945aad46e0c1bea45f41bb4dd2bffa90daf7965e136c2b98f0f501a72e5cc339c621d948ce06a30cd55e6e3a1318e13b5d187b7310c9a1e6ae32f0f0af911c37f9ffb3c6aadd6c409a3d3e6ce30b13c2ec17480322a3a972c85466f95e4fd8a95fca46653e70e4a3973f9ee8efe7168d375720ff219b6bafe61714d3670d4fbd42e661f9d886c50c9aa0a806e4283d1e30c58e904f499cdd52a6a902014ea3f109ec0df8b9aaa9d916380cb8f181850990649981727a491c9fe3ce4cf4c5d8f07f1c5dea4f27388fc52d8af12af3f9b4c4d85a037294e7f0b4c32e73168c1760e2b21c96f2d744ff9766eaf9b0dcc2146e4e324a726c2303520ad8b167e34b056b3fd1d0098cfce79dd96ec489e29b6c6ac91bf4252c1b9f0f47aba6a1a6092538008d72ed22171568f1029b3f27a5409d149cfd72b7f274c389e7f312158c79ad351134589a1e18d6ae833af7a36929eb5917b8616e32b53f01da7b32e1259c2c63392a034272f89edeeac9a98f4f6f2119f52672613a40bb8c6be9449c5f24732e1ca0d39430a18dbe88e9c54dac9d8753cafc5fc025bcc107533d31b8116eae9e2bfd1172082162d83de8eb1a3298f6508ba557dac17b14a633bfab38e941925c5867105243079a7269ca18ec07919230d1b772d1c544cf2e13e069951cc8549137e920e09b492afaa706f3ec2baf467c7fe4147d2aca412ef4f5749a2d7a4e03757f596afb7e4011be9558690d132e49c279725e0b3fbedc8c449b7de77e2f0a22076226380dfa10c3e1313ac29c97c6e07b161a9a0b35cf214d9ac8957b009772d85c504468e315c35dc8f7364b8b93c260724982b982060ef7dc3b93039144c0311b2cb17e4be049f8092d4620fc91544c1eff387066d170699cb4919bf2d064edf6300a5d1872a7115f5c7a5f0ca8585a72644dffb158d8eac5467a0229494d06c5c551ad893d8d7fe8aee67e20eb465772c1f32d16024b95d40c69953ff7f0432d430bbeba7600705659e6a9d4b03921726cdc4298f014eba2c83bb34d569e150835d0672babc3340410e10f5e89f9d07216a559bef36cef1778039ac48a60e9c641629c2be5085285eff495a4ca54e42920ae2e7e146904332f58eb7d53c7061c0cf11ff29539049e71521c7f7d60732cc759311111761daccd01b9794d15616c96dec992ed3e09381a3de3013ebf4a72dd1d0f07be58b666b6bd36ff78abc90be347af1e788f8a73a1d458c19fdca472bcd429bc1a40d54034537bb68faa0132dbf94d11c5e21c5957cfbe6acb80b7726cb0835ed7952995001112bac831fd570179dd24d9f1ec1cd6aeece853c29f45adf52222e9c1cb499a3cd15f94e3773049bc8d6d55db62ceb595a29ccc03ed7210084c9b0d1cbeed5a14c5edf2ca804bfdee10d73d977fc893d1998565b41d7207ce4bb8d5481d65416cdb0d9580c4765ef54ee1fa3d407ff1e61ad277c389725794761f07e8c8c5b00023c31ff3af4ddf0def473e58b8045e0f7f0c8970fe0e699c667586302224f0733f7a5869927f23389d0ea5c80b91cdb0ed0da449cf72905df4d374ad164c8fced0b566bc3b94942c3865dfd3ae094e7b767f2eb933723e1ecb83642ef386537086bbf5596f5a8d881d411649fd8f63211a7c3f712d48693a514c7f058620d0c5253194af420c374d40183e68669eb646bf8fc314b134ab47179d96d9153b70a5063073a1339c1f97d15316d2ec4831193d67dc3b1c5c10064f734b8ae0c848adc4cc144365724b695b31924f57338056a62d04ccff721ed5ea1d7a70dcf93f9cf426349704cd9992066c10a6e82b8f27f34c80617b7250e3b455393eccf79b8d27f8a0fce71bd03adb44b3bf5e5d0365356f6c96f172ee819e99834d1c3b387e9f80face9affb49ac6e01d477250b6f65149fb444d725ccf09796cab66bbc1a4a23f9a082bf3ba0845b2ca9f5b886aee58f43fdf25729aa3389146fcbc2a632617aecdccc4cfe64a2121a55d9455401f7f1fdcec8965f6ec0b34c7167b47515cb4a000aabd1bbd5e064e3eda6829df4fc71ada1793017724947aa980a38d6297e88216993002e0e3056e649c093c7cff8dad5b31185ad6f877ea247c132a0e2375f27d4c5032ffe5106ed318debf814e92f4d6b2746baf426401a0bd9c62dcb94fd7a7edb507d02c0d0dde3d1cd8eb037ad87ac25e13b645e94c0885a0026e9203916d7b4256e3243831c1e5a4345317365e93421d722dd71ba32a22c3d21011becab6b39e9ad127775b53dc5136128526c064686c0ac9bbd73e06939adf42c9b90dcc3edc23e74ade00205af4b2cfecd79f0a553242d20c19b04b7c5e72ac2bbfda2a5ed4a650b250278f346b077b19ddf0abb57f1a819389055dfb67e39508c989a1720e9bf19016772558fb2a0f74e816be102472b580744adc01f5f34c9fe2a8a67722ab5643f7d962a86bada14cd3442c693f72fac65a72508d715c79fbcca5d2e4d797df411927324a2ed1a21d6b2211813772ae2d2fbf628873216baa126ea744181c2306f9c0e4d562902ea2243ab11acb50707ae7297d175051baea6c20501e61d2ff473ea9d6770bf9e8b8c39bf8fabe72e08762d3cee54deb591c3807d8217ac5d1f3d623d5d97c448f80134f86458069db54d7156e6e8f2fd003579a59992243b4f0d0c6020d5f0e65b1a93492724a729383021aef3e43399d4e25d13d4f8c64d9e2823a4ed3979984b5b00b3c28d6314fc733591de4b9031c14c0a4a2b20ca6134d07c8c564fa02d3f8c6173370354af094595fc9ad73a2840f17266ccae21b63fc0a6613c17b96a0bd9e40a14cbc720c4cb87949fe8332a31da96b9994f3999b796863e9685dc9c38a66660b2cba044d4301e66a0b8806fbac2f19cac189bdebc77d850998aa725ecfadb36760264a6886b1cd031e02c3de12917b4d99a697ba52ce61210849f84d9bc0f6d849ab2170cba07953f45b278236d7b195439023e955c4b43aee86d28065b1d97a3cac71e45955a4a95b921658a59c745c13095953d018a733ed3f85ca8348d8aea83d60ece6883424a6ccb08bcb5a56c71c62b4c6d4eff94664eaff8e34d9a673987d7240759448cac72402e6ceaee9791e6d549dbe3216152d144c4d4a0ba936673843227809b1d66ae32a2ec0cef13486cff100c14d06926feb263acc684b2e02e27241b8a3a0a57f607cf3f333e8f265c971c5c0a11a8e7ac3bb5d70f3142fd7f6723138d7d3717971696bf59c9f70229af672f31e9ff4e2e3db7a80e1f29f0cc806b4964648f2db4969171bd1c22cc22e7be98930464258cd532369793f4ff9667245c86d799c6c089edbb44cc8f985343dce550860404086c197f8bab52bf697654b1844abf120f6540be92305e6ae349a9aa1294ccaa63bfdbcfc58c7bdaba521e73705bc8e5393c6811fa7c663efd36475f5e30666adc36ca694444a2c23f36dcb93f114df3e071baedab6fd3a1161253c199f5804bd5855995aff5b121def46b63a75f7fb711d7df5f1cf25e338cf23912677a19f19d1e974e94e2ec5d0bf0e8471cca03f19d704e411daccdaca3067071a9c81f354286239b4114f53a932275f643a7bd6bf319de1152deea27c901ee4a8ea5450f181c77c6413740380905e23291e51ce40a41db70920931e58f2d838761c673e4490cce7c1e5e5ec3aff725fad2841acbbceec2f54b02a0303b2e819157f17518e5c46e49000b54c6b57295098094b25d47cb48eaa455c8704647578da33c0f3ba728be1ce4aad25a12e72ef939534a7a572d42bc2b6d96b05f8a3968e7f4ce20ba46628dd31e66f47cc721cdced9e73f5deb8766cdc96bd01a85eaf773eda7eb997eb9799a610b3708b7215fcea1cabc81405909e8463a26f6a446d4c077bd6994cc7eab13791784e1249a1eae3421405e9dcf5bb6a584e4e0850b549d06d9b278f08b8b9da80fce7ec4b79cd34ae03a652b4e6d9953a609d679664ae77d05ec64452250b6a69d9eafb3fcccd7ba359053491de77ecb59797bd3fa11d9b6c1a1773e3e8fe928ca2c42f50f0b8add955af98e5fea3bd1698bb74183aec8201bc9781e04a43033573e1ed723380586398727759b3461fb0562f9408776f50986a925f7546e9a6cf739ff3720de2a899bd39168805fbf7040d9a8b9fc4612fffd772be07733334ec5da021018fcaa4af5553c1446071e3803b76eb6e37c399a9e6ffdc7a4841a001dcd02a126768223ace533b7cbf642eaafa26e2b0645123bab82c51ce102fda24c86d6a72d2fb2af85ac4d75217f0e3bcb35a18a9b20c1cb376541576d4e92818a33e7b2a394c42e88c76f3b46a3188f8c3effd41fe66d7c581f569ccc9571d9ad8009872283808f834e21f4ab282cf7caae256edaad36833a540b73f3e98c564a82f0f724679e1e2737881ea71251384e400e929cd11f2e65a62f68ec81b4f6442ae7e438f0a2a1a86ec8c54db3448f02d4c1176955abf23fa45ce07e672e635027b9772e635099bf24f653b5c9d3c5fa35f685176d6d7855a2fb933edacaf9670c91c5a7190603835e4fa67ea2c053736ba030030a5ea28a1cddde08615037fa4ed9522f6dee8e3eea327769282491885630b1d67bff53797f6be42046ad9bdd8dc84720a18ad82974135d5de3dba7d1857fb9338d5e7c9ef27f13fb04c33a86979c572faa99c5bb2f43a4c0a94f019093ca94095273f755975ffa4896dfe6b3fd9112a465395dfb233f134516b5d732863d4eccb9adb66c6d42606a0ef83eab90dbe7224ec50f013bbb6d114af24fae8620b122a052080e9a295de72931badc94f4d438f061ac505cc0499a624e04c60abf5db4fd8b0ae4c8348410b49bf8d29ff4d07c64bd1f83ebc097e96bfd2567bf05963f2747cd30267c07616434454e674011119abc3b5d4567df6373f99179b041b1140c5df81b5da5e59183db86a603dbd72b8fd7602504c3c82fb236824f8247ad255b06a16802312685b8826958ed66a722cfbf80bb2f8d7f7359a6f1cac440e1446320678e431aaa2a9ebf907794b61729aad39310e99e0e7d58e22d77dc3af43cac27bce87ba2a18f49d57a0d8464226fde2c15ba6b6ac969c7fea7c4e3af20759996799669ae8febd6be30ca2a2527232fa58c2e1e7a759878c4222daaf63e9fce5660d01ac94fa212014e247b90072c26abc500909e77eba0f1f41c4563d9728a53802f48a0e2c3e28fc3ffff941725172a5b29cff47a3c6951f407bbb979c8b3a8286c7d4e91f7bc0a46003fe4772219b374ab50167c5c6c51c7cd8dcd34c1e7d7c9368e223c07fb04bbd54ff7742eead8642c51ab09ce34d17267d226e1bd5eb2b41e1a1186406dddfcb60b28172ed21c400582cac1302f9af6aef7e2048a28c1bca393df6e3290ca4054a69a372d50818dd0ed70d2755ad3dd658f916aff94361380aa0c2192293573497a68e166a210ab3861732b5e70c8659297270db990ff06a8a59203eda8f5dc03f244b7245a2cb33c66ef4638d924ffb5b77a1354794594dec78df40f1486eafd4f20a721e2af97901b6a0d064a9518d1f6be96aa4aa00a097f443bdc3debd5fb766f172b651fdd8092cabdc24af4684065d3069a2ea4c165c7cbf22dd1adc39313e667207410720d9f88c61c9c98c0d66c8ee1125f00d758553bc686dc5bd7e996f2c72fa92216aa75a10e2c47d499c8f8acc2fca0ac049072bbfc0d65e409981c41a72ba1b74ca04534b61de86b6ea71db102b8bc34149e68ffde55e415676182f5172bc45b89ba7fcd262a431d023b42e28140e979058fda726680e53c3f360eae772f8fe452b282186ae433fd30e200fbce335407333abe5907c4886d5a33c78d0720482661fbf46a254703ee2518a0398bb2581aa07a375d12fa82f9ebc045b307201d192ca7008fb64e29308dc8b270fcaa4327224c9701dcd7d232e31acffc5652ad10416978903990be73cede916036eed5d8f361ae6988b4a833095826f80727f4170523a6381817851d88c5fd1a19b8a3a42a67aef854b053322ae33362072b125b65398ef95dd6ae06bf4b53f6508c8b51d435bd16bf4aa75bd4bfc75d072773b2f865bcc31eab8851f276a7013c7bae765b8d32a38890c9ef4f9a7dfd472bc4576782d7e6c889de896f29c616e09ec99b37e5dcad939755e3d4a61559072daf7b9f7817df3eb8bb55f2840d0aaa3bbf0dd8d84dfb60a5d64ffe0a654d072d8102a4dc14f28258aceae927c518a14eb0c457144411db1bc5fdafe541f1472d2742e267e7e2a630c1cba3d6464d64d0ac81abede31cb663dd66bf9d1a60f72a1c0631b0e9010e3a6505bcbd3181c01aef62f88c3041efe734679f8697d57727d6658a8f2082ba5b88b475a142f74bbb617eefc667ddfb888dd96a72ff4d972934e1e431bb483e94f37fb0b4b20898970f05c6719b25c84576e10a5b1218b1a23f664939e890eb2518bf142aa32288d97f8e06098a8428ab0f60467c4480c24f95ad972be50849d92eca7acbbeafdcfa6ec124a80cbdaaca46077bbfe2e9a722ff6c8c8ad6eb108348be0a6621b8303dc632795feace18e2b06780c55259c724b0030e9bd50e1a38d9d646e1fa74ba49a61db13fe0b34aabc30216f46302f4321ee81d3b68946bb739f9d7d6f515257eda944aa333a7d85e58ee9437dd7de55f5eb9881e27a1829d5e96004da1d2ecad53097b68eb5dcd50a2d42f7ef2e59728d095a809613f794d1e8590d9d6482b36dc1fb2299b6bc2b2290173f879a9a55f0546d51ff90862fd6aa57bea76692cf6077cc986d46cb0ed165ee7067a8df69e4fad06604452f4d06e52f84eb61b684a9c3b40549417087b4058c989c09833d4121fa3067518c2f9ecbaa7e3237b476214418b7e4dd2e376e3126723ff2f672b89420a945260de9e4f4a583d7d50730591be504bcc9c3fa11a3c86dd68de47276c0b0005d527daf0b79aef11a05c48643f6ee6b1558de97e11f0061f81b074d3fa83a3916b8e92c999179e211ca7f752958a40dafeeecdc0c89dd82e010977265f855af2fb3c12855e11ed8947b97a7795134fe4065625995b660923c09ff721fac741a89e6a2ae21e379b10719d79236e18dd5cf4e6edc2c0bc2e9b8eafa7237fd841ddc7f61e41a12c408d9b27e5ac5f9c077e57b4070841911eff6e88313b447fd64a035e439cb05b02440ebc738c36315e0aabeecc27bf9826078cfd82b4fac4e5b33ff999fbffc82793e4bf6dbff258f15f014c83b1401d9b83fd2d02c53fff93b3f6c30e856d159a5f139c76397edc83821e933c134682249aceb863f06e39ec9c1efbc381f536a42d57ed7820d7803acf43b485acec228e5b0183f72b7ae47df1718b5a20bc9d7daff359410abdeee0cdbb4f014633d6064e03245724fe0e28fbc3933f0e7cccc37bdcd7685cb01078f6c4cdecb803b5accedd0423bb1fc4ed15a12ad652585d818e431190a7e3b2a67e14432c5c0dcb6299f70234b0197a2824dbbd685aaa181117f11dc0918f4df81a3d6c9b4fd0acd4504571251b0017b84dfb43994e53b27427da4fa1d50a63fda06cdccf984bfde14cce59a725a5b7368b9e60c03309e1371f03c46ed8aebf29515b992d220a94bcb438c8024b41e4e0b35547322ef983df7861de3322a61683bf7dd5769ffdbbd9113f6e407a4a32f4ea4fef1f5495ad2d83f3d843a086d4b4ee0de902f558feee3790b1e5f101d4ec4e5f3925c774aa4c5f51717ee6dd1a7c755cd415232b2b0fae450852ebeccecc74552f4fc4c9dc6ea05b7dd0e8293b93f06386c65f4fd5f4e62666a727d3c3102f659dcd280603672ac3539113811aff5cab140f77d8313f44be39a729527ac0dbe8a9a61a6247e4c100abf3a99038663ad22342a909caaa9d87730722b3d55451c75c1a1dc2244377cd689f6aebe44ebd03f9c2ea14d46746472627298e47a5942b122a9d907b81c357169e09336ec462b8b342048648976b55dac318330a7a11e8f3b537158769758ab99a8217dd55bb5ee456c46ffaf613d72aa727b50783b3c124546a8aa28809ca37b20b7e9e54bb0128be87553f114d596be03a4e412b1563e15d3846db447d5e7ad37344242ca2071ef7cfa98cda054689069e564ab984df8b233b2326e099b810ba54213685727d8f5fb3d1a13b4204ace726445fd4d712033b6496972d11044b6e12e8d4824ac48fdd46142b95e420dfe722cccff9ddae47cd026f72339161c2120abcd0d0ef3a4d4e179a1827beb9560727104177161618f796c05a79c0385db6215d1986c0b20f6e63f8b85c2910f1d2a4d53c48a13512b11e96e23b6625df2d914757470eb64d0c66a675f083d259249c07c02550c80a2d96c35891eb8b58efcee29d57861ea0186fdf8163e2fd52a324e068ffee59d324fb62c18e6bff29c57f31a766c600bf29e207c2f5a3fbc5b72303c47d3712d3661e4b8fee463ad5d9ccf2a0fbec98a9b56d71076d9a701bf7287a0800fb59e645ee1d73d0b97bdcb0cecec97c2ac1bc72feeb90e141722fe727072a2d7f8ef8b9730dd526892198151bf360d0e38d00f660f8fefb873b78b724e2bd817a12d6b7525e5411dae3a5e0ab513290f2f866194fb66dbf7e44e7a72248db8372db5cae6d041cf710ad0ba4a4414911b402cbc50f94429d869e3ab72045c7d63e0e39547f7a363dbe1f805458ddcc3adbe139db5288a0f843b02ef454848577239dbd8432b0664940aafbd69529bca4170e8463f764f29faf5fa927268693eb55ec1d776b526d98ca047c9bba100b32c2c6ce4ae5f1b2ab66b410f72f91c7f0b8a05c10654e89eebc1dca17a1e7171aed796bea0452ac2a17b7da67290ccd3ed53c1787ab409935f6571c7df1b6db24ac09ff3eba978d4a8757f3365cf5afb8ed014c9b7adf48e2c485ad8d6a20213b8e70ae49ec1f15decb37f750418e4e1d5cdba827b1e880af3f3a14e0ed7649e9c5a638dd9e7952a3c61af9d6e884265fd48030d14974e53ad8af3b6c01468f6abbe67e4b4a938cd1b27c8c3727df8d0a142b2ca7e5f13b82e09fa75df5c34ee7bb53f8f14b1abbf1b9ce64124c4cdf18cf12f0a235d39ab61b7d4ac1e2648815b08ea01717f547cff7c56332194ab53df174e226449c1397a59f74418ab555bb068f45ab4699db40f07a8d771d40cf619d98b0706eb886e1185508df25cb1bb1aaa9f651a238c374de241b8726c7d16e94b25b9b2105eba55de9471a696decd4572e9f90ac1fea2e6217a7c72f9724935581378ca75427b2f53ec93cd65033a7e8ff3249b569ccb063ce8aa72e593eac121896547cc2a5fb0ec94725e15d29974994a39e5a292551f8398cb0c922d9faa0c969a0b8a0afe8eb32091eed71fb8f59b3afa1634f6f3ab8ae7e7728be1045a1b13ec64307a6f6cfb0aa348b4ba12ab19f8dd838ca3bb5f9aeb2b7280ed5c99b458c8c12a8e18fd48c56065138b7de65c5d7bc139b0c20936849f720311c38d03f11ee618c611d39744971bc5b465b7d5e6ca99d399bfc367607e0b001083159ec2fc36e3700c34cce8faf6cff3b077951c44c114e3dd261b2b17728b6068f59df5217e45dfa87ccdc1ee3fc5d0ac78f311628cc7ef0579ae1e97723db96c8dbeaa312ca4ebedd18519f36637172e587dfbb4b11a73ee8c73026772bf045c714e8a4d00c1c7279290dde2b3748c33707cbcdcce7ebc43f79c0b873fa23a6b9b409f07c59a0915060bb0f7ddc0d3263ec7fa8a3952cce5c414fe2e6fd1958377e33a810723b9f64366424c0c2ff3be2855c47bd179d370912ef9503c5a7665c36a258bed126922e00e9cb0f1a4e2d4c16bc141bc03b7e19bf81d0a728ce160ebe63e1712cea8a339e5c38bb4795eb2a9323c9de17eb86abc94649e7222c7e527b3cda009e7fb2e5e5d626e67faa5137b107003d2f7c39f8186dc0f72d7e00f21ec9e729c5ebdda01088655ad745a6bf67f27519d19e763ab5eea3172cb41dbbe2034fdf89f7cfe5cc1c1d609afd2ff0065016b7af4ba80bd4536c172f798e8ba8c9f4f46f2848a327bb0846904ead4b05140da3251e5419698296b72aa6911a84299e1d1ebf9cafc305f5dd30dbc67d01a158169ffe165c90088c648559625d9f7e2c77f438841f0c616c8dc57f60f2d97580b4dd91fa340fd1d497233f8d3db546da429135713c4de334605ac312a113f5d6b70f53489ae5d21f359c12e61687219393b8d8b767b2cff234131d14051f0d250b2cd7ae6a19560d45b999556efcc24b12e8d6c6fcb6a4f55912d8298eeb8ff73a8010bd8ac4f63eb7277b08d5885d340575f2945630d61a88dbcb271bf2969d0b2d4fc91888a667272190672205b84c7c930afb8b9df3e90d645d8d449b4d57271020d7c9e01463672d29ff2058ebca2d4c17b40d8344c228cad607fd9be7867ab431e782d1961ab72b1e5d43dcce8c2a1a171722adf91afa8ef85c2fdb976c6875cc919facf4276722f8a25e11c66769ff9b4150f9e3f9c331ed8fa6d84235f6b8552c96b7ce29c007e0aed7ef4c4bd2bd0aa5900ad3020bb87fe7fe958a9d6f033809b85753c52219504a5f61be726466d5b51961d0f4b9410847f7d7a49fe11ba95c5461a7df672a2690429d057478364c86c3c49f1ffec6df16251d9f3a2fa3b2ca17d11eafd72d4f32d6929f61a13f6cbe17a43d2190c0bbef9d263b4c715bfd1c074dd046a642d4255297c9a1531ef91dd75edddd5d661a2a5f3a0fe2d643d2d6cf855351a70d7e7ff47a2bc174501db2af7a355c51effd04dc947e632d46b9cfd305623a6721b6af283d87876ecaa16cdd6f75ee25b395493159f444b2cb55ca2754c5ced285cef1f7ba1b99a7cc10c21688cfe1769732698fd85809f48f4159bf120fc774a5d0f06e6b977b3efef942cc4753cc917a705c8a39e0ddc593376e3372472e272f60e7bfbe19c96d2f6745b995d47ee767d461ff6d458c756b88b9220d6352e0bfe4c1f8fc69bb8bc8cf7a2fe019556c6ea0338c4dfbf6826736c1086e9f06854a8fb43f1b608b11a6e61c420ce59f502f0657187cc40197febddd3476818e872c72540b1f719b8267e553a3f73b9bac59b056248d8915ad1b33a81a1c7ce922ae5b9f881a0334f363f1e6e9024de97b8d605b805c03cfe1e47930708c3e23231f20d6468b88d781e171310f5c52fe8a7b430470a456170dfae8996a814b4496c5d610c6652e3ff6d91692f7ec1e549961e8e2106797da6509c09e140459b59727ce0fb54ae75baebc0e847303a350c9e022c6657c185ed70c3664b95ec833e727029112f840901a2e68fb127cc07bd086512a1edf01cda6f99772e8e1e21434d69bba7468a7f3b38c000b69e4f0e645254f85670217625898b9426db8b73a9722d98b7db07bd1903aafffa3851e2302875ef70cc36b7b93cf830b15a9db2ad6e106047a19f2116f18841e3fda422069452753d90e9ccc6543bf31f1f97cb4e720d91eb19efb0e5f33e7ec3784a12364e9e1edfbcc160abc18082d1ba6a9047607d266e971d6387a36fa60d2b0fb5399fc73f35022e7209285f5ea75d2d250c72e2567fd443447faab11438eca90612280331e489ea02645aad9005ea0c795e726e647853aebe80152611275e5a3f062bd0546fe04226793daa8ac6b11f068872224d952a3a488674bcea70fecfbd5ef40da45a886facf8621b97cda305382e5ae1d41945b6ea783c661029e759bbe7ccb0bf10d12ef5ee2bf7f6520f9e209072fe443b0c673cc6b444c29a58c8271c734c3a0126b4b21cdd4d384e4ccc84b072049eb39362bc8760d8024b5b9c2d08ca69b62e4509da89f8a7d7b8fb335a9672669958b0804587e25216da6f38f3457acd9bc92e321fe86879a18e4db5e00a72914c40743bd2da3f22bd880840143533b48206776c720bac6dfceb75d8b598723310b33b61bc0a83e36d36e47ae1c1f300e8ca6d8a90cf55c76524d2f610e339258a6bf76595f6e2ab5d6ee5288d0e8d5863054f5c6a2a8ed2c8e54d67882572ff1c8310eee16f9b0273d78c18e2f46328de2c03d578fc615287e741468b0372d10d9b2352deb3883f41af3678fc60a3d8dc8151f8bd9ef7f2091a2e38bd3f2f377e83e26e217eb40861f02e3bb28a6fcb1bdae28f9da5478d2798cbf1e6270e81fc4b86e2bcc162944793c981d14c3adb7a59ae49f8ba63c2e6b8fc227a937207709775a1a0925f9bfdad584bfbea53f67b32ba1bd28767dd273811c7db3f72b7890ce24279c67d06f1c1411575a387d3f4149cdbf67b6d786e44233119a172c728851c5541deab20020c65d5c7200c0b0acd44d952a262cb6ed7e10c38eb7208ec01cf245d3bebc2fd77e8c663e0b77634c472d3117265a56bd437a7f78c3f1f4a5ee1738bfc43859304232a62b46f0864950bbf04bcebe495835ef2205972e3c95736e8f99585f7e0bbb62c550a196dad703e2dd9571022d10d3b44fe054458ceaa1d4c87c4aa8e5f93735c77451d333afa6b56b6643c98307a3145c6f872c8e6cec52a7c4c86f3adb0e4636d6f03039c96fc9f9a6a832a6923c2bc91676afad6c4c1e313457ecd27967a78b4f7378c0d365e7a488fc2660850743bee6372f30e4a155687ec04bebeda89c79f4951a16713c3b04df8fac080e4bdf81c947297367a86253a9e105009995f24a27521d2f507abc21645e2cfcd4cc68bd0a472ae5e56b248df18d7d2e3626e4cb70e215475cb9c91b16d47b0528c7a061e490740a2a5c55bcbd243a44eb7bb590ba7097d6dcbf4f675ce2ac3642d80771ea6720a1972c691d5a701cf9c011e9b32e2794bc39bedfb39832a027ec65b1a4a227221732fbfbe4c0d2abe53034c30be9c3221e1eec9ddafd4a6a9defcb22fef746fe3404628e417084618f317679f52403a3a841c7db6cd2df373df669127052572e20da590865c3e8a97822fd1e1ae00a83188cd23d58e5fc801ad7c21a4860e5c6038ae6fc1db9aa8d5506bb4c397e17453d3c1d48f7aecb835a16632caddb4729ac2aba7b672475272cea854c273bd90af1a0d4d986d6586a8a1960b00067728e97db9a517d5545554b70182e176c363bef1954432b75b5e689077acea795472b845a189a1f7c96730313deed60de23bee090a353384f86be8cf5cd7af5e7729202bfc54d1a8947c7e6d7894c9ccb6ee6844f61c11ee21369e73bad270f2af72cbb71d233c642a0d74363ab27f4b24715cad8a90e2a9a4fca76b36065644d6164dd81c3bbf16a766131d804c80b549bb8f610b4bbcea82f226b46527fdd0f772e72e36030572c3844741ec7979ab16e0e65e66771a7e5323feeb33821464e353643628817e7336e73e9981a53f21d73adf3b8508b4bdc63863e9d52aca38726e824626842705b4895020dc02f99291c24e87a275bf8ceab5b07d2479be87cb72265e89e271c52b1003d7b33bdffbeafd80752c5c5343f5ad5fd4667c1785542bf9d638cb628cb47c75cd0cfe60f5d6cf527a59e2ae1b2a6c0c1d30c9432f141fcb773122b2628ec1b790d152609d16bad1989835367830b4927849b1a297f845e22a8321b0afa6f6ed65d1ee219e92ad3604b3f63c7b1c8941bfd1c2d58fa3724042a781e6c4eb1d1cb145ccd6b936adb18e883e25104cec4556b8302c0ba4726ade4fbad9b6c33d8dba4b241181d4afaf747c44d5b8b4e6c430a2fa95db114239324caf010908736a334759d427eba9e6881337771e68fece985c48597eaa7293008c0469e3dcd07dead1d14053788d153064eac5bdd327f933f77b18713748d5ca74269f1aa9b2027215f0c1c654bcca23b262a72fb1c9e13cd2e6cfc3c172f76b142636ddaac1a93923f5481d69af0888cb70bdffd8a0fd120be8715aa07207e12a9b0d868cd6fd6d044127d6421cd4b8f50356969564ef2f839e77178f1bf801dba73f955126eeecaf5369ce9db57db8890c882988190c9906d26ae1c272ef9a1a4032811400cfe685f2f9214641fd0df02268aae2b7743d0858cd8c4d722cfad12a88e50cfcd6356f622931b6c5ee919d10e08720fe9f60e25dd1c58e45d75dae6927950deff59175ed4bae570facd70dc5237cae0170e237264ea1072de30b3995f3de2502467402a847ad8bf14f35d964ff960a4fd0d8025f240c297247a81d2fe889df48a99f1a80405fcadd603a8274eca7eab746739a156f12c672566aa20330a8a248b18804809e27f8ea1fe296a9307546f0f3124fda1ac788725d769cd65a5d1d41d53568824fa07042020e1bd513de5f6d01f36a667f41567291799a356fab173932bb9a3ab30e0e48aea234488a0ea661edd04b369d256472114a4ef92e32627aa7071a3c70630aa5b22a61e99afcc3fbb3e75794f627986ea277978c7f32395346ee0ba17c4e24ed281632944445aba209b9f15ccd0b823c9249a0233e6f78ff21ed34716f1c3fa7f219725d7e125ebf6abd5b2b3f01e872661b139c999b83f1a3578c64c0ea80959b2088c8dc84871c5e121ea3090da328150b025824565f6f6905d37a872c3df1b4eb1424dd94bb0ee5641fbd92d8aa729dd3c2a97685d86e189d8feb078e7f7fc572449af147c925129e4fe3df313a0e664a7f86fb75fedf17f4359fd8a3c27681d9e93453b04a84051067095a7d9b72e521767b4d22050fdc0813060599b95e56000740325b6b161c97e4d6878d6c72bdab959e162797f18cbdd7be0b365360e5e1724e6733609169d83020e668c6725f7c01b7bbe0429fc6bab1ce3bd36b3dc811c10da1066e49310efdaa8d6fa872d3a568a92ca6553bf27f3a84a6a9e1f682e447777444149e14d5567da1ecc972286870211f8706c334fec57be0f03ac1cec6094432e69fd826f5ee2c47e9c109f8d3843414cf8363c9da4afa741ad53cdb35a410d39320658e755944a0f209724bab15af7887e5ba1120fee72fe201a14e6f6690e6cb8b32119e2257780d977237883f446c9f7633d56f2bb9f159f0f1fb418c6e7048d26d92dedd12908d0f72e78686202c92f993c0e5114039d26cec4647b532136ae2d0e7ad5657af349c72143caf384367c6b056f83b2f3be51461b8380452398da85fa0092b6cc1b06272c7bdeceb314b8e3f7fbff5bcee2960daefdfb55b586dfbc57eba2d2a6d9f5f52979693e6b824d2f84b2be3939566331c64927e7a0916756c7dc4a27280418172e4c120b9228c3123232fd2a66431ace88b86ad69c23ccde85f3806f667cfb2726bf824169f1c0a55fdd06643e6d963b15ccd5cc665f6552a7294c5ab9b6f765599d4f9d7d5b889aa220b6b6bfc6099bd830e3133f9dec729c7223a84aa0d3e72ef30365cb43a5ddad0699164fd1323c057f783e764b783f35885c6a2b074ff7201b8ca2a1e30a63f6cfc8c4f9a7b54c1798e561da291c20824b297be7122c83ee6d5eb1f504a416c1f146caccf647c3dbdba99f2cbc2b171259c5c602e19fe62986100d7c5f22b87bb99d4bf367d59d50d85335a0baf7689f60a8f2bbe4d0e72ff43e47cbc2b39e38b9ffc29e9a57a3eac69dbfdbf2366902d44b0021896214435802ee217bc9dc450b0712ed928c399271bd06e18a500c5f0e20578f9226256d81126344e5cd19445cde5f2af339bdc6d0e35cdc25334bce01d209245f8137257d472e638e92c40e42d05b31404d0fae1ef9bb7889cad75a361d76c0b0df3684b164d417014f6447b4910057d81b9717536bf5fe6bb35d71953b71335a5537251eb7fc91c5064a85227835c9d90de8924f209208862dd41556f859d3160496818ce21bbc3062c9d46f3fc56d81d26fcb3319f3a448ea65aef2fd5b63ee76872887a2b5f3f76e4d97ad19493e3a9877babd8ccffaced21d8a9f11a5066d13a357c90b616737a45cf772b36241b9d45e5a44f723e5183ae011aa62dd0a9473462de899094d12aa4d3252948b7d6b7c2c2c54ec16032ba3f80c8ead4dfa842e172d34948eb671f9c085378af647e4fedea892abcf4c33a42a62ed8705a0aa30034be0fe00a58a519d217dcf6ce73e92dce5342dd341aba33fed4ee96e3959d1e72392fe8e0869a8cdc2fab698cce08b151452498ed6505a8c11b1d60972f0ee42c791a19a113360cb1b5ec81e936226b4cf44c5ef2247340031c278adca0f99272e00d121020b7cbdeeac93c1cde43be4d95827bf794c8a873b6b1606565a9395dd6bc6ddae5b35d908bdca0031ff66ab6136ded3aa006233f66010f7f189dda31f0e11d26ed7237f057d72f69db2b8f5d070797f8b2ac19487201b655c9bae407c4c876c93a0023fe36c882f9def6a0ea34fe0b261b234a0d9a8b33ff22f306378df8e0866d6329a7d332572d0c4be36bff051a5e7a9f218a1936259c4e1d7f54bf591da0e45b3d47f419da5d277c93439b5261ab4c114d33a32dd66bb15d1572388af97d584a0afb552f852df0d557be90f52f08aa44daa4eaedc8defa28cc72783e34663170a44bf8a9e5da1892a8a31a0f5952ea2fc5c1ee579eaf8b63ea727570d74d9fa0fa298d14296cadcba257f5aa5ce46ca64578cf968a4d8fc93a0897fcb86046a4fcc481f464731fe04db6c3d177ec6ddab51ae496e05ba3203f7214f596a8c61859110ea420e863dac3eb7057a1e40fa4a53fe491fa873c36c15b7384eed57f5e3fc0ab81c763b8734c77ded29ae6c9f0b34345731381b29408721a4af3690f85c0927ea13f2c4baaeb098499379b8c25aeb69099ecb39dc21c4a14ea216784676c28027fdfd78cfffc2d157928b9cd7ae1d530c8f64fbda270006eca4d00c226e59c91d727de48db40d13ded3f14a5202a3bfbce7656021a292ff8e4bc935315a1fc6ed5dcf791975c4ac20ada6f6d8d242a74e88c0bc0fe887216a13a39cf6eca62c264a50687c94867332b5105d1e968f2ec78be680e57496e75dcdae5a25229b3d2c38dccf0e2938a2bd1b33ece6c4959571058b336dd90727cc9182a274cf06ac14867baa4e7932817b0e1176c8069c72785e1c124fc610acfb3116d4b57b6395a77c9bf0a62cb9ebfdc937b0d53982d9d6e96b3d2c04c2d1bc62087c6088fc173834c9964f5fbf8f31fbccf3cf127d5f54367b83674e6180ded42041172b8205bb9e1137c1f591716361a5197ef3c13185c5eea52061023c974bf5a4d77b6a414dcc49ed967a5c357a21da64a4ed3c994dc0a70e1466472f5dd22cf0929c6d01d83971ec4a0c8ca2c58694b833d42b922e3f1035748013d7badae04424f695ab1e09f02c7ea767397c8d433aadedf2f72d86be0a55cf129213b047222881641db28a508e85cc7ed9b0cc363a6387020503ee2e43e0fef722da3d44469ceac32ff8979ca1b5b3f4f871aaa3c3b3221a0d96c16af19936f7270f38883679934adb34d30ed151261619ef061c820a951fd0010b2a46778e22abb6573edeefeb3db168611977824d3eb6f3b5e5d11e56bc3bc2f2242b1c42b720f5417f5ab89b08a6a8f8e826df72e2c6bb793ab12ddb4b119393fa79b35327217e4f226aae524875aa94029cbd0c901e4dad21eb48b4f90e2935db6c43ab50abb7ab12406ce89412067d262e173735be02f174ceaa35c915fd7960a29d91f280db210e6fd4875233630d27baf60d0f0628b2ce1dddd85b3cef4027f460c397225b87e9b1ea12ea03a80515f037f93bad55dd6898ea9cee92e0ebb9cc8f9b872839c42cbba8fe28ddfd8cf101b399ddd4aae0f639f5b6031f940417a053f2f1d91d046a0d926c23eb1bcbbf0ccfeb6a3027330757b57e831a20e3ec5eaf8e07283f3e01528c104315b1254c350cb639e83ee7d96e124071357d3a4c85f331772781d96f311ae095a76da2d3df1978d825fdb9e77b0fced00fabb1a5320c94723b22e12d13ebc70a5927cb5050e9de7eb6c0577ae95a4f15f0e9fc82925ffb672baf2f277f576c0814f628ac5c024ee0aee210090f0864553446dcdebc2fb2972b1ed611fa8db759988e94dd9ef4c59d2016dd83b76074bcecbe1b3cdd36320725ae91b4b78699f86d1099ffd3b35b4d20839c7a3258c0ec8d7abe439beff9c72352f02ea780d195567e041ad6d816e5be93c057cb87ad3e552a988e4f18c4a1eb6818d2478403a1ae43780221f800442d36134c7dea6ac35b2a0626cbaf1425c7e35b83e6c145b9d20df6a88136a572436d18e2fc4db00f708c883a32f39ea72e68baf986a60065722e422e4c408b52051718093abd5a8b2d4407fb406e5aa7218fd0a4175a4ded8e0491547cc08be05dfa3fc8116d2f6e50f2ca4ec1f860772da61cd3a5cca0b130f09c653fbbfeb3c573740fd8af3d51971df1d0303c95132c26c4935f58a9b1643c8e410f2a56af3c51dcdda9eb857d9004e26002ef67c554e4057055f38c6ea8e8658cc0e04297dcd0a83941b30bb0cfee0a97877145072e1b02e05e3af6e189071d9322acda6c818a9adf593bd87eb22f64028051b53174e2c9059f77f4672927afa2ac73715ae93ec6e34823ee830b33c023f3075e9729ea473e0be42413df619b3baa583f153b1d686aa3f66b2a16f851b613f9e1d67a16fd5cf17dd7f72a68a9bf33d61760b02bf9d5931d7d2f1f136f6783746c572a8d6aec6dcd6d67d78388ed05d97b74761055db0dbd96401ae50f268819679724b2153a394655556f5a3ee6623a8966086c9bfce8cdb3c833f01147cd322a3724f7d146656deb8957d7a1bd1018a0986c2d245989c1373f842f3e98d8e377572d51e4d6b62ea7bb4635845342877db8106260d8358893fb5dc5b2c5185ed7d7223b35ab8bf55b02e69171407f5e4e7fa65dac789f1272c54fb1b4a08003e557241a0c02be703e8229dbbfadd16a1780cae61487f8f58c66d8b36a29bb3a66c373fe3353c5069d76ddd146b5b57b763c516d1c47cd18bb8f11bf6e488ee1c7930f1d58eeaaff7053f5d97e02699242d493327443cfe3fcefeb7ac6f63353e291e03b418ad84a369c0db8149b345ce266780f0881f1a7bb63effc7102ac3ca9a727d5ad692cb2d970a49d8e305868dce64c47daa2f04c5c47b1848ed760e300372f438c107a539d158c769ed1c84e7041919019634ecd2a6d04c6d0f09705fce4a268cc3c1caacd8f91093af239d8199648373a582c3c0e067fdd2a68e85b0b57281f19b176c076b59e36fea26d2887cc6fa69086f3d5390ded0f948313fb5196712b992fc0ba125bab044df9a3e70b849546380bdb904a35996164769af1ccd722e8fe926e1dfafa4f54c1bb6172bf2dd7009f00fdfd474313a578e4b6bfffc7220b1ff16f0cde2b1110f3141c29a637f6ccd6d4912a0b3b58b82a471324ce1720da7a3e06778a49025fe4ca477b658615420ca231a3e5af06b8d186e4d5b9a3fc5d4855aebfcb753a02f8aced3b36e7e3b7f153a0127221d4a6d8e6d9ff2251e09d612ed789fa0ce34b3fbd06432abb1e456b681520e583199d1e6e83b678b72c583d351efee99860c7fc946d385cc045cab8c6a078efc0065a040158737dc72acc3a2c9d3b164e96665d9d71b7d18bfcc4db3a007ad8bfe5aa2a635f8d3cc725426b3ec66aa1b10fb25a6a4ebd77eba7957ae9e2d64da401806d66430e54072fa970e81a70cfa4663f0adfcb21e5b5da2cc84a9dee45ffa32021290d5b68472c7eccc31e4664d746c24bcc976a54b4ca3c6e3468f366bab281b2bbbc9a868410c6420c3ffdb81e0d711684a318adb6c3e7a2417a495e2d2a4de1fa1d06fc4530a6b9a7e92b63d799fb771dd3747f76ce96540529e5dccfeebb1f4ada8eea9214eb9dfacfd7c5e2e1a549c33f41798d50fbfac57db32b68e90e6d191197caa721e792e8b7f7257ccbf196ee3eb0efa3020bc0379dc0972084d2983cebac34c726ec86d5aa7f00e81b4574f8c538ad3380b5770a44dfa1e7f69825f32faae7972ae5ea8b0352e8bd0c852ec61b16ad0236282d3edd5bd42922656a6fa8af602728b50b637ca0bdadc8d1711629946151e9a556bdc14e64e56f9e796d3c4fbb5725c3260a8d9e48addef3bcee6d2c09430db0d84ff5240d219410bab09e85a3d7235ae751fd086fc4c933f6c053304d95fca956e9541b1162d7245907cb66feb0c7da86b904dd2c90ded3fb40b0f205dcc4cbc45231f8369627ca2d38d371e855b8ee5e33e55e5b1d765944abd3146f18096345e20ef9d29130d3225a071d88f7226db3892ffd2545d3a14c49cc226dcacb0b3c1223f1834f538508be848a8a111f0eb627d4939e1ca1302e9fa7af38770274b558067b7f99cf1dff2d2de3bff4ebdb2fac1cc6f5f43947d25742c1434e5be4d15f2636fe92cf2d0a1827b38d33470574d9ed484b604e047772ddf6ab526fd067e6ccfb0903fb0bb75fd3d9be8720d9ae8318191d48bbfcb09d2fc1d31c513cadfb4da959485e53b3e339c6c3d72c754a0c6222c8d94e396a4eb8e9beb047ced7abdc26fce3d19a89e4005cbf472d4303f00a64d078f740a9d9b0d4624943db59ceef8fc3720a3ca41623033e37210f5bb1d9dae0202f4aa69f759795b9ca2e56cd8499ede9b89c77d8c48f5f1720e9ccde1ec782a3c92ae3b4e23dd3aeaf1a46c2e301fc1594e464596c9696140f60fee78ed7608d5cf4999e1c53cd9ef8f4f6c838333c2df7d6ed56a01468372c7b24624e49a2bca34a232608aabaefe57313078d02468bdc79ce98c9c71c072a4e43161ffa67d839927e3c83876feca56d423e051c4f3045c1a220ad223fe1565578ebd0e0ec9cef7ac1dfe71cccb6c5012ee13b66957b89aac639b352faf36f7fef9dd7eaf80b427149ad0f4c7cd1126fd5e18bcc8e0a1637d000c2265e172966d6cb531c7e00aae816d94246ea6cda64a4e505ff25fc982a2bad5dd393072b292bc9c0537a91f251461ebbe5e6b6b7c71bbe72a1a2b776a0410ad5576075668b8ac381bb75cf5cff7028be89c7389d2cdd5d0eefc18615626bcf286bfa51134e20060f46ee5f1241a9e4cca2192c985a8ef12a0625ea55f9572487274b172a78ed7cd7c38084a907292c7d88df4711ad1623ae8a34dd46413c1159269d572e744904150cbe48f7e1c5a3282992adb8d21e7d4538bbc4684d5228fb8916124546fac6299d989d32e2ecf9487bb26f47e898343d68cba1628b084f559080621f596eb827b66b1fc4ca1868336191e8b418a0eed6f06f453d3d13767c1c68b7222cec53f40b40018d38ccbdceda4abc2dfc383cb64e256d91a588fbbf771c072d045048fd6cc99a802f944189bff62e7c4754123afbf5eb4dd127723f49493013c5dfd974de7bc99a43c6b86b0660abd48958527431a84ef766eb28238b0ce10b8a4635901578751d2241445a1f3b439f1b1973f19213c30bab035e3b24b7272b88cba137c0c6bb38308c1ea821b3933d652115eea921161953b99b13f8b5172f651c62749f362d087bea2b3e636d051c2e44868e215732601c395510cd19472f1a6616131eb44a21b794da6847af9a001773c84c4473e5640bb58c42e214572d1d0601ef983916b7f65e30c0da007711acecf95b75a1397c2c1cf56e64cea18ed365b14e02693b8516a940367cab927b26b593dc5034bcd0084b475ab38f60393657c2c54b975db60c359c0bf42916eb4a9ef5bc6c115e74b229ad184f0db7204cc591050abab055506bf412b92985e00196ec5471aab4fc94841e98efab472445cd55b96a7b486c7ab488830b727b4f041d5106484e340a785acb6ac0f2072cc749b180b61cd57b337848dde756c887f14b251606ea17bc80909921b07726096ab9826d9ae083ac5b53617ad5437cb013a29e9eb621ce028ba0a3a3b6465021b6a06d21c5979b3962883a4ce7731e01f1b525ece47c4e8b12655d179ec2a72026862522911685869f4e23b96f75116652f3083036b2d4e16f4e011dceeb8726c55b6e1149bf0dfbf68a86d1a776cbdb783339ad8da92a69b6cb0f676b8627280b8f46a9c31533e66362ed1ab1cc2ad07770b95386bfd6e0f7969608ca8ca49bc49d7866f6da875f93737264d634536258498a52738f6254976baf6b84ee672ccedeb9cb8aea8c64e86e83a15d3b6e31ac53162fc7271f2b0165eb9e39c7f12e556ab91258d6eef3649051e5d1c176e9d723df5e71bfed9e6e088c72e682472109635da8334e2e30a7498b67d7eb88b23944f0864233017f8b6f39b92035e722f70f308b434d69edacdb11a4504f4a7d1903d7c11759d1afed9f36338c5fa3fbbfd7789e82d0db937dc28e62675aeadfa48ea8c3cc72cfbb35732b7fa88b82a6e2e4a9278c88c7fa3264e7ce3b6dc76274fad5a1216a04e80ae9f28d272214630a0056487d2a5aec9b5c546fec4bc2c0b12a8fc2b3ceea124ffe7f55bed7328d66852d0987521f8c1192247e0ccbf769d24a005841e5175a9b9767490961172482db96ff5e819d13a76a8dec09aa1246f1ae3651841c0f5d6152d58bcb82872e8fad51a4c5e4c0e9de67f7ab52a66f6953107750c69e5819260bc26152b9d72fbcec9bae2ad090cb33b5f64a00a59c7873915f7956446eb1b391435a331f17286cf1f7d5d1b78aaec90148fa58526a13dd727f0edf1188f4a47628963b157700345ac285d3e92f0c63f159096285cf9982defb991ca0c3ee0b65060862316727978e1885ecfda8e4787e3f2fee87707a955419b80565d4470999c17b211fb72f478c4402206b9b02c461b1289105896907a4a137cf5288cf193a620e47b7b01f9b441e0fca35e59f6a93a689ab126c761ebc90078a7bf3924cfd4a19d084772527eb4d50691ea0b5ffd692fe28b2366e17c2039fc30dda73bfb47ac463a595f80d3d9d9112d9dc14fce5fbbaae3b3c4e34907e83e91b38b114c161f0fd9bb07c4b24340c6b2b2908be90f1fbd1c89ba0febdd4dece5f7544b00877d3d6a5c40a99bb3d0410cc8c96ac2d86d6828b2254861a3518aa201d15c05142352b20c72caec7b88867d732b31da953bc2bcdbbdf1f907af87220866ca7b9ec128b0b23b8cf2b570dd20749d376796696eaaf31392271fc6926a10dd6645257bf4d47e72aa898ba18da5ac9b58f21feb9dc6da4387d04ebbdf68ba4c8dfe73bfe6da9672fdb764907fa5d789af9ea270d96144000fba05dd2321307654bcfdbcfd8b3872db535819219f9cb681eb712745d3608c3c12262e09fcd0c89728fbbed796d77275e5df303a036acd6721ef328a4b2df3e6353887b82022af17c9ba7a003fbd721b0a80a34f8a65d03efab41b335c41146d87a70cfc16e5f74a400dcf10826b72b335308cfd5cda604c462998aaf5733d612f4b12a53a316bde488deec1402e555a9224fae8d6514935eaed8d59385237a4d214656bec2b90937ea29f092535725d7c9e866e1f6e0a90b32d95620694871452ef39efc9c8def12539f744defe36009394cc347f6da86366acd6857825c5c63b7cd6400f58e3a39cfafb7801b572bec2c9e37bcbea2c2e902f607f7d136d1e6f998bfff04af88fc51b8acf8e134e3a7ae310dd266b25eff6619f9a79bb044e9e899375cdc4c885ee0c092837671d6dedfc884614e17d19073bdf5bad2f1dba1b45008e545d692ee0c5b6d99d6568fa35cc000b45ab26feb31f171563bf5e9a4148ed0ed3b7f146c96880902f580f8fccd5165e249ef4fe460c99b285c11059d182b89ced5f275715a4b07bc857243b40f4a9c5f5df46c28d11ee7f8649be8f6f8abcb3ede3174acc91a132e2dc72ebe1090d585ca4361b5fe35baddad004bb8b3b34e3232c843962313e860f080cb309a1613c13e242a15ca44698d722999af53fb0820ed1e0365ee693ff5a3f726ac65f1ea25d485db973c22763c38042a317e60bd95af58055a171097f7dd9728cc98249be32208484317ba1cc33800b0bb930aa1d0f74a3ead2bcfa4581f3725baefd752f821bf7b0cd40a2415811821a07d25c2238435e2ebcf06a2e0a3b22af103cb66519db3f01e875f80a75735b861794fa6cb514f7989ad6ac11a5a272482f20931e7992d6dacfb13b18635f819e261bbd77558b7401496766a075a272ad67fabed62a74479ef48083bfa92ad23dab1baec0e89a659f7b3142471b84727f37fdbbf86eb717067e5ff4a0ebdcffb374dd65202782f38f773d040954ec3ecaaa452758cb8c5049cdd26da30e7b304ea411d6331af0e134bef031620cc9215c8f9bbf403d851965e6e1b90bfba0b7ced957fcfa6b2796ced413a59dcdcb72a9fd842f70c3b42ef3dac79f7e6a36320a3445650a04e9c709ae7f0e0e463c729eef44b9d9b501c2c5e75251ff17fc93bba64334487ee5c0f4faf4c059fda772d8ccc54d6424986b0e390563f4cfe7fab9c1988149c93e149ac906596aa8fa72ffb85735bf40bafbc301a5b9e32270a949051afcd9af172b5e65502f0089bf68737e1513c12f4ad9879b8417f461b7f2720c01583c984e686d728be02cc2636b52faf2652e7a8d0b85916a37a68f23cf1e4e967ce68cd2595f65bceefb566126f1f513eec25d7111338193be01a5b77dbc13f044d608c4af9e922f0ac0d97e071edadaa8a806df01d0d17743cdddb0cd9d88e5834f4795f1f478fcf08c0bbd248273fe85bde2a8bfbd22047d2ce06bce7582204808946b26f6b5a81cce31b3722062c1dfa51d6c68f9b183ae03e19e4aa016621820ccd066c9945140528f0c43d65cb1575e2652e8006ceb77193cb11dd9e851699faeeb37784107a808069872d9a16d8b24328f3af90e69324fe459d9d904ee7a79411212028a12d76665de72f11c06c2123fdb94dafe83422da46f32f6dae8d0dabf29968b2e054a5741ba7223b4cc0301c99032e965ecbb447754c7a71a10e0a6b8aba609e1a275fdc67b16050d27c9ed692f4a870b01e57a41481315f2f666876dc48a3b1984025c5d0a45a69bcb97afb5dd8bc23a3c5b87c1bf1c9e2dc3dad9adf1c13a7373ffb47c16728e80ae16408c7cd9509796644605f1134b27c8d408f5e8c4250c0cf68de9c81db8825c8311a7a6786d5e600d27934f603594714446ceee65e0d35e12fe1a68729dba44dd00681ce53137f138a10cb5fda0959036e8fe2a2a95c3e2ce2efe5472a2abdea9317e7792bb82e9ca14b48897c034b2b98734a470b5416cfe59a7b40e70fc2a7574f94344b45ce2027c964e5091c585b85595fe8884eadf47fad58c7236887de5f8b8d3b148096a555db7d103a06012d94160f2dce49461190e474072024a0545ee12bee2ba6766fd8b127841fad3693e4107d4b1b62e4c4e0a492b42a16da49891b574a76327e1d03a76042af72b123a98b2691b24c4fee09b5a52724d4aa51913a8d5ce7e7bbe6b40d95d2ea17cceb6fb91b5cf5789f3b23bf48c621946ca23d74dcb1b91fcd735677c3519e0a1b1a524ac13d173597c14690e7f72a45e11855413542cc4761935e3c88d0a99f6babbafbacad122be1093fdc78f727d97a5015b4c1b8913c810e7e1454089913b98e9eb087168389f017181a4ef727e0d55ae8b14344c7fbbed2bc7aad1dd3eb0c51f76f16a73a9563be84e89287255986947f8c938200bdf992b25fc3fcbb199cf1042c26ccc70bfb1e94950734b3e61997b7780d096e807b7f3d274976845004e83b47111fdcd3e3b4f754f6672ae9867e699170278ea3170af73426e695bc4ba4e7245c04c705a55d94bfd2b65e37de508ef7ae605634ac3acb109df63a19e17c3c8e0a88c776e997a9046662d99523271f4900fb42c92b5dacc491b43efb51597e2ba4950f0cc6ba5d8b4d072f33e6e523c691e6ee744e48d5f5f8309cfc4873dad568da52b80f69d3860b7033bf2c6c3ef111c74b574280488ba521da880ca44cce41333243a9c216657ad53a05bdfb0fa2bd4d13e547eaf372e6d97c71a9318646eaba3b1b57e9e7298c072f46d051d33eb271b6595bad053474eadf7347560d641b761149cdb621c41d472793be7ce3010062fbbbacb841339835bc873d6308c9f9e71d5ddfad9fe6ec772847458f3410c21c204214b2772d9ce3c49d7d07465a80fec371f131b518ba46a510cc64e2754ce34f33ea5bc4a3183a94bb02fda0be51f54c02180740177d072f182966b30d19ea4411c737f2718deddc0864587ba7e9284863e18fd04b0b27231b108cbe126bbe51e36a2c9aae35ce75b5d16defd826c0fa8e6471be09f2a2966b88475c4a17b4aedd06fa5884bffb23db9988c2044c4a5d6061b08f5c0b32c2a0a445155edf6df354e96fead4cd63bc3c468e10c7b8732e359178d35ab4742e16d35a8d371a183d08fcb450a461ec087d51731bd1a51397f53222cb021a27256bd0c62c3efec840391cc13ae09579c2e3c66ebd897ad3f81456415e95157722bf13e727c9b34941ffbdb2032bfe53706f9b66702bb085b15ee12e80213d9724a109b63c8785c9e1919f66d679b636868241056407670ddaa2334202b36120faef8a6665d48a8034f3eaf1109b3cbd441c50acf2449716c980f273a8428c472c23a1b89c7e504d7484977883e2d96f4801834a1711985c6d47b8b0c2661c272a04b47fd893363fe58e6aa666b7182265b750853469c26f803a091170ab5233edeb989c5df13dfa74bf5e293e57eb9f25cc5fb44fc71fc18bc75abb6b16e725159c348a35daa756b78cc6d6cc951f060ff336061d3c79ff2a20f5ebebb0281723dbea13fcd323d52e7becde54496f5663573b503fe7dfa49fc03157eab56447277fed505d2e2371a28fc0734d30c86b8e4753fa6eb68de0afc4d438c77bcb25d5b3bdcb3de41f2c687946d4233d74eb06f5a7481b3d7b493fdf3a64c145d14720457b37d4012c318ea3edfd3845f6210981e141b3f8f2d40204364a5a0abd04fe1e76da188d66b5ed4326b5a04fe824c8e1ad842a6a64ebd43fdc56596ebb336e39470338568e516c8332f22842376b2f1f4030b0ca5a338291dac29d3fd2f721fad362a81917277c921aaea50e04d0c7cd4367bd4bc0e6c3bec08f384cfdc63518eced2f47e75c45bdc8bd9354c8ca4b583fdc4a0ebc861219fb556018686728597afdabc6379fa091885c3f23675e3109ccb0ea6ab9c6992ef364b823daf33f321f3780cf04b30f2a014de994d1d23415c03eaf55c2b7bf4d227122cfed27218e4c4075f41e7d67ceec3083617a16c37e057f591465da730ab9a0ef3d9f47282bcffd1d9d7bc42f3bf9eba122622aa47585634228753fefc9c8147b97238725cb4b0182423a623bf4fc659d90915a1c247e1963a7817a13e45498662c402721740eae543a4480950969af94b61dbb88052e601fe12984d288e6261af801672613419346324fc70a11a0ef6477d4240e0487679ff67331a263237fcf74129355876f3ae9e4761025a07bbb240435b56d1e59ed8044ea2af856d5c5e4caac026133f2b48207b1ce105afe85096f2a8b18c9978ed4babc61fde7849bb88e59d72274257a7e0fb394add2d588bdcdf9d8d6244531fbed907e4ff6a362b01c26372ea98525ded02befeb4ddc6615fdba76985d646376ec272ddf40cf38903441d72441ff3254cbf56287aa1b49a8002ad4aa6bff4b1c55ff7e17e0851230d4985725c3d7c2a9bebc327422f203de45d95cffac6955eb95377191fc91af6fed8e372872b1dfd2cc3631804d0815426c79eb03a39c89f79ecf958a444895aa5b0e072461bd2ac1b56d4757f89b9a0879377d35f27524f9bd5783d8e366788e29562053c2797a84f1029709be1318e0ee15fefbdf9f801c3ab8ab1b8d5341f9bca2d72e2f4e49dd027bd5b96a9280c2af8bff70db4b3f7d75d54343765baab2ecb836c0d328f432501dc9592ebbe055b0853812653153fb143eb594cbb681095407f72c396d01bb66cde6d390ea2ece3d5f2e9f9c02ddc1c8d4f0fbee7f56d2ca50733cc588cbfe53feda7c830fe89b9a738422ec54a2f2e5a5b06413fcb720e6cf32440494958d488421272d69e154285395d246f1df421225bde243ff9e11ced0e727da7f2827abe4b4c309fa919d29a414a2963d0e00188b7352456cc135e9e6b725cc81ea4ee4367f7fe56dd87cc41a8e2ec3e1cf7ac2e3c8cbd2fd2576faa8772c81956a836fb820af6b488a6021f624c4a5d114375364e2f510792f8c3fa45720391495b03071e7b403284f93d70701ec6ad4900f5e7118b1a5919831d1d3072c2e682efe56d7da0b487ffb153204648733f4150aa5ce90bcccd43a28bc49209729228f1b114c7d14725ebababf9360f22fdee01a4b0ef57d211734697333e1b0fb3fd33e4571d7a773389b6aef46ed51a82efa482a249fb52df506ebdb8a46bbdf6847ff3ac6abcc5ef216283c9de6038104aaa7c0e63aee929946a186b34729b6152a218471077eb14dff07ba317397f1861e7fdeacbc9c782512f85d8b372176a82af49319ab08f4624536256d039ee85af584083e0607b9abc2439573032d94910f642e19a17dbbd5fcc43a096c6115a623a8fd46f7f4447e9ab545fa1721fa8e779d771dadbcc985a8f5e1f8cf01ecf30801392cf7accbdc6f348069072a4a4ba0c5283026bd5fc82aa54f2130284e4946c08cfd88da0f949daa703d525b1fd2611d13786adf915bdba51532f7a7631ecba2fe495254383688c11d5a9116d46b70c79a56c0cf3452151023750beac14415dcbe88bfa362bc634eceb641aa15f89ae281c98701e9ae7592e654b27d2143d2e8c0d7b073e4ecedd830cd35b33828da550d31c698b8d7f6d5e96e6262e9f0e38d62982f7f0fee9682d881672d491572fe915c1f21b8539210108c4b83ecbdafa2f9f6234b3fdbc9389cdc6720f48e01bd4c7e8130b0a9ee7fb393315272235fb39457e08fc9ab7659d15354273af0161667658e830797540f5a1d3b41473de89ffb8a6109483ba135bf44d72f4f048e7fa1899c81cc63e5632975f6829bbc2bf615b4893604c6c46625c7d72fd3d78dcdff7862924c599298e74bcf70ee59a7541b1a5489d65e085ddfb1628ac02f641ed1c74ac35d174a924be91b5076af5a9a3172f839c3c7583e5148a72cf3dc2a70401987c6066675dca0679bde5340fb90b439735f00f89b5e2148f724d42985319f50534d459d4a1b8cef335ad359364666c3fd5d67845c498818935438c526825786247ede6c423a1a32ff0aa3bc4a3c700c7297ae2283c23a8564e0d06167eb8fe6b9808e17a0bc29110db2156a44d9a6e99b56e3ad0b3a25967729bd5f9afefb883c63d4853020cf4b0ce31b04df1dbbed7e2543557f5c98be951e4c191542abeaf8e1e3bbf7c333688c1dd219e4b0e855b33f6edfd2089b6777242777d9f1be865442c8ed577cb7c0a83d0f7407f0bedcc2302300aaeee2118524a6bc7f9ed2e3351db157a52b21b94b5bf5387d4e2e50343a0e0010e8e17805b78f51b40df01341e53d27917333f29c21b0a85a592900faa6717049616ef087286a55bf0e6f1d406e7b9d22507d6e9af5d65edcd35d8af7132c8d7dac5a5674ac6bc5e102f9ff8cb64138337d5b867ee11f8fbff73633388ff39c10fa447b672adccf8169155456ba2678d0eb02d32ea316badcf885a821be54b0600d3af5072f28fe3a4fcca550b7055affa2c0355ef92f117d2b8c3363d131ad0dc7a96d770db956dfa42dfea0cfd11d5c99f4e0b4d8aea6aa5edc333ae423ec98beadc691956ff0680e9fec782a87e5de010ccd8952581ad03b903ebba5b63ed5925437b63081496a42cff9cb428a752ec3aa3ec6ea35562f5d90cbebbecb4442d8c62d15570176dfbcf4d59c1df8decccb186752818d72634f044b0f0c62f0d9c996e2a34b0addb4d10ce98ca89b0ffd8001774713949c380b305116c6efd85a751afeb62e29ab0b6c50c905156259d09e25dc771177f83572ff649fa754130c60a322772b3e76aaad177d8945da3d02cccb3d56f0eb58d41e49439c888efc1552885c0090edf04ba6efe4b834a24b4806fd1fde2e69c757c2d8573405235cc1393576f2c8b5df420dc87d69d64faacd4d9cfa4e9e1dec6b18c49a64b8cf746ee6900ef31a7802e4b9210a094cade47a1e1aab2c663857806f14a611f7c6c131f300b4272b31fe8c432d6119623e3a389aaf38cec1f7a8de310a5ae7855f4afc64ea47f729fc309eef403ab4f3e04a6262f1485155e51610012d9e0341885ecbbbf512f06a44e51bd61d772fd7b8f8aa3d62a5a9dae72919f383f00ca35456dfca0c8a872740fcfb1058efd0f900cdbb953ef3990b597ab31bf50f23ec10c35ee5dff2072a6b4fc23e8c087f77f43d9724d0ee9ab01bd5676619e387e8319d93100128b1182cc388c57858b5183033f62d6c3f0204b58d123917403c9f8361f9847ddeb6ac5a83e770d5a294a57d82f99070bdb94f1dd48745f372c2e54d063ec73937006b589cf28b78fa0b42d1ffa3cebb5db6d40417776a58dfe83502f40441d68e572b8a736160ced536b3098d0869263f48409b1631f0cd6510c53a749934a8dec1712d6b7323c0c080a00ac419252c96c546dd10727ef5fc97ed2a7f7f0d9ecb114e3c3d7c9db92d6cdb155cfbaa5ae990c390b0bbe29bcad40baa72e584129ef4542ec7d55a8cc4de897beab2e2954aa4b1aff6aeea0f57cdaa682b4c23b1e9003c055222c8f9d381698aa57fe8595a1df40d6008eb332bc275ea94e06663de3727dbeba1f544944fe6a9b32f8304416154ed3055f32135fb69ca351a769d78072e63b17fc7ea0fbc853d40a83bca80f30b6f8471d14cdac4e094e1e4d7c9968721f91d27d0ed1e26fcc27d0fbcedf0799be2191e0ab1fd5db08857f7ddffeb97266c03f3e1a00bef4de7ba0a8201a318c158288ce1b357d8bbd788039e6838c72c06687bf49c3a8c29b85c4e4d10cea1d84eebc38bfc4027b3fe824960a029600bab1af2ade99578af3a8df20ffa6672528d94bc3c036e6b4afc399b385eb687290a044b6eaacbe38673fd58bff97d9e2cf972c8f8bab987253f79e7d25470b1c9958de0d2ca160aa7881ccc6a89fc495ce99c35bf2f6b46cbbf7a4c77357147279f45d3c596a2ee5a576f42e3dfebe7ba0f31b84b38a6c4604bc2bd74acce36188eb91981fca300976d03380167053d1c02e2e092ec0d4149b17bc0e15d37272d4583e5da463590fe4341469e47d517b74e635347efc3c1499d891996b4c6c72853962289c5a0c798e4714c060c9a68fc318cc6c5d7a612ea95806eb4d02c072f60f999c28eeb4ac2567ee004c0571629893a87a074f476ece5d290661d29443ece762c09b54813ae34c20a447bbe6da68d76743438735960ed47778ec39d072e96906a5bedee7c7f3b26384b64249fb0b2db5d677fc668b7d4900d871bfaf4bde7afeeafa1d4372d6d694f157819c400c1043e5a3c2df1f6d2357a089badc5e39f9e89d44bbbf726c2444ecb574f22d118c9e9350ff0f23c852074faf95bf728b60ec903f2d9b4fc48e427fd6f80979126cde9771887f4e21c5ac7e0790e27281fd92fc927619c2926d4c9113a3636cc00d6f0de4e32b3428968f23c48f6f686a0bdd1533cb0c6e648d599c7761b7c178148ee3f33a4e9c116b587b677944728dc345175e6bed48c3f38148b878cf7817325d20ab26ab514d4cd77cb6a9975daedad98320931108d7ad8d9271fd8f37b894fb95e34c02e2600f84cc063a4f7234cf841c178ad06ca5809a32cb894baf129c402c104ea775ec9280ebcb732c7087fbcded2e6d686417589d0b1e36faeb16f4a5e1c71126cc9a03dd4607c45441863ec6517bec7c9c502a1bf4c532f5da1277839e50d06dab35e4c9fec62dbe7297d48054d93af528300665f360d403044f59661e80997c6a8e13319254a4f61bd5999873986635938c9c39699889f81cbd4c2d7ee34e1d0dc63156b333fac2428f9d674de5df50842115e75eec9d51da139b56ef9dbe6b1500355b8243519b72478d4f906c4eff44b7ef858a067da00d904319b265048ec2394b1348832e9b72651d18276bfac8ef5af39a436e359d2b5ba8db874438c2272f51633c5622fe57c5ab1f2729cea29b287caaca422de8efbf60bbc4502beec38a331536fdc0c97266c6a59a4ef95e997e82e74716a74178257b58852aea4e2a5dd7938364b96570184b042212b1d270fa2a175a0702d3dd8b715ab02c10af29f1bd4e2dccb06c023e3cc1f098b218eaa6317a318eaba39cca5588d9f3b024be978c1e55c8f8d57269efc5b281d0b8bdde9a89133b19e55f65bf336be8c8f89f257b3d31745d3272e538799d8e13ba085466006e33b229e8af61b84883d292fba6c73f7916c06e32df534320204eb28e6fbff4cef8bf4f6553c439c20421cf406af06e94910adb7258f51bbfca3a1c047fa4c11b938c5f5f29173d2f7b0b232a0b3cd22ab9e1b920e1a9ba9f0e40bf46055dcf1c507c425d564614a39dfd6191e3065056f04488723e8ca7971a6603bc26ee94c19f063a2d151ccb48227a8c39b98e8e213d2f2d72108a5a4abd5b9ce06969bd671fb62667e59450be35f3994fb8c0d8df68c6f411613deee49e4bf5a139a297473845beca8d56561eefd7c131239138f54dec8166e6e1eeddf502957c007a65848e8fadbaa8bc109fbe27389fd3759fb357af197209ea58bb186339222ad70be391dcadc9c4f744fecaf491867af0c223090941160a502eb086b9766bb1ca07314cb5c1408ba86ba2f3d36e4b705430d116d4417239bea62a179d15ceb5018d3dcb7b5bf5dca97dadea001eab667799e325d7ce35a54194a1768890209b4ab051a6dcb5f9603971750b7e6dd38682a71b3f4a62724d6525c4f8c76abc1350a7d4f53e81ba279a3a0f5ff5755e3649db21cf837e0a0cb84cdce80160d18ac9d000f4c0d87390fcd8c51fc8209a551f4d19484a9e72248cab7d33755ca22b575c3cc77f1fd79fedcf4d88cc1e2e0b0cfc2562e38672efb4e8d34324eb4ffa9238892b7184fc88b5d592b68e054c11e044838b821772d2810997df32c78ead77589250875a84ee8af8adbe5102708d23303197bbfa7272322892362ea93ce358bc21372a63718bd80331b605cf5bae47583edecdff72293eaa153fcfb2bab52e5d503f4d4e90d0ea012e5a762066573e57b1e2ec3124d3713f25fe34a039dd0ae76074442ed19ddafcc9741ad4d3e9c754ffc92a75728fb2de9ce38e6913c09d2d75b45cfb12e8b713fe43f3333f1be4ec61b6fccd29cbe76a7df62271ab7a9a4c742ce1b92e6b7859953d2db90a7ba056147692bc5117e15fda325597b09fa2cd1676cdcce3cf037936aee244692b153bf8dd272f7284b15deae002e26d01b7dfa4b38a755a09c1c764d60ac9a949a5ba9e7d3fb2042a97dd9c46dddafe5f6c588ca45f64221b93ea1375e7ab892d02819562712272aae4c7d264e19c7f01a077f95b26ece621cb4b2076694714b3f97062c8aed1685b6679374e76ae9c3f85286277723236012190b8203dbb2bb3ec07d3e2e95d1c0c20df7b7ba236bc6b8682e00dbc63ff21167687a5d3cba640b426263f6ca1724c5edfd2a709a8f7f7366a7e562ad59eb1d197490cbfb07553e94327d411250d96d3d7d1da14fa7ab591f616dc5f3de6461dedfc2293a38cb38fb9fdcc37d81ba095cd666487fefcc22d600fac69bf3cfbd9d67b24fbf1d8d09a8b0caacb4d7223b4a8f9552d6c222ed50331997283a52ec166b1a4ebd8170c1053ca8558f272234edfd3bdf2554f72953eb12115bc3407c13927ec5172a6355189b0f2236f01c18ee06aaf475bcd8bb7fde57160c65b72244735025463fb2261d466ec4545720bbd937b1e17ae87de67b7a675b2e9ce7d249cb39f199ae7daf5e883c6bcb27269d2c0526882691a8ebd8a7b6b3f4c4d33a2309e2c23972f19b56e1497a52d722f107192bcdb314f4467d5580041b978f3040ffa4a0a7e8f6913e8d938eb45291b9eb00b173f72d514907f696e292f0a87b363880ad636d4d42ec218b22cf0722dbd64bc70c1ca6c5e33f5990be382c80d047dd75e81ef0992db692e117f9450866420803a6ddd5017a942f5efe56de0d198e8f9c6466bb67230868cb3fd11721755b8dcbae13e81592729b1b2dea9347d113c845de4538c810ded2c061b786078f4ccacb35d5ef2d93a0ed12d9efca2743c345c5159898206a1c94729699372c08efb02e1026ede172bc980e4e96701e4290ee94d1b1d6a5c37ae26be6be672f8723499e3ddcbae370137b00046e9514db311682ab565511d12a12cd0ea4672d3344aa6bad0619d918e84a3902b5af21e7d394a538b504015e4a40735f28872363736cceba58dad61429ef6797633c738181a9f77a41eab8f58822366f835720c118f886fc5300dbb6205ad6398b5bcd1a390ec82aefedf5f94ac3a525ad1720ca6bade70d0d61bce7968b8127f233f0a9cfc12f4e41e513d7184e664cd5b7218a6bccd49a095d5875ac68cec35c96babb0c3e33b13f16585e125c7421f616116a25d900257c497d046bdb3ac339b18194ba4775120d9c4f5550eca19116b726ab8775c7e625e7d397e5c1c86e9efcea54896464707e7051a44874a10b33d28653ddba42c03b04f71a6790e1b8da6b9e58729e28492f12e35a580cc85831272fe0f1e65a7494e0e65724f545ea79746ad6a6d584f8f8465e36cb74a7f987427fbce6ecd02ea933749cddd13342baafdc5ab22aabd62d3db8d981efe713ad7729977f20e1d774779add339ba8bd886b3f7c021496cc85609ce511bb38f9e270e7ed7c1933bd3494bdee78827aee870378ae944fb7e3dca919e543ae320a7f516cc8166e0b2d6d6be58f0bd2e752202d57ee4505ad5b8d24bb778671d47066835ae0f995718dc8534d1fe7594701f7bd349fc4e1ea43db28b894c86f69873fd72e2ad0cc98a67f3b697edfc7808d7751be413fdfedc85216f6cfae4248cb917726ce8c22a70292f85ab024afa3b6571fa4e2795b48ea0bef88bc118349518eb72e191e04bc21209d2a7edf142e921da05834b52de7db175801cdd7452c75e5e720277b1f805da8cdec1711e78a2fcb1c5aae7e3b7b15ecff91d4b8efc3bf2e57295aee39ba20c6798ad4eefd993b6d4004283f73f736f5347b73f2e6d195cd357db6ae2e7d3e4d5bb69f40fe3225f0d18ddc3534adbc4c5fd47dacd4bbd36a60e273bda7c1df3a47e43f416ab107cb4113ef5389d95bec7e29f87296c5f4144723a564ca9e68342393480188f82e3a85a9a1f0773d75b57ac85245e13c1681260e7ad02cbfb371175d5600739d75f974512311787f57f531c6f58f17489ef39725f90286c07ba3f686cb8b209bee322eb94f982a82b120a8900775bae49e90172e4cc7512a59aa1381377e52ecb9024eda7e550bc85b4739bf4f7e2810d55e7727246b4f4818f5de5969eda1e1176da58ed623585c352d71e4403740a68599a09539143312fe1fec699a7867d49bc83c8823275eb877ff0eb1e14c130c005177262d9b627557abff0b23d2b3d529640ee00f09d7ceea32dfecb9d7876bd91ce3a8d6cbce064fd0dc6e0b3e32c6d6e1a8247129be2b9a35883ee72de4ca4d6ca72238657ac5971982385539bbe91a9bc2491c633d345b7191d17e60e552c7ad872e007829ca7bf28f98b6dec578cb13fcae54c2ad79c39bdabb33346ff0750487260b212b7abac3a32f6676eb202730ea84d979ce30e85accdef58432ed4152535799dc7edaf4d1c7f168e69a8eed210a1ea67fee0d7c50eb17ba257daba1a395d78930cd169440faec6073205d6f88db1f0de66618790a15e063a5b0665d4de72b05cb99f53ee7f9a77309f52606adc72e222961c4c7a72a2c68992bbe73d1134d253830949e5ac170aca9c4587a9f3f64141fd553d5a73f722f93a1babff9d7260ae8cd3371880f1773a99a8bd1398c59a40024e8457d718ea8e614944868a72fd2c2e4331c1a12de889ca247bf87fdacc9ba6152b28a97b5b2a8ad3ebee04726509ec201913c5b921f38f1f3c4ec6c60565a6ce83c009c4ee7784f761c0ae25a5eb8b8fd489acffebb84c122f418fb316a2072b1914c235686ce460d2827c347e27099e018c5bee2c3ddab2545a15bf0c6651b1ddf89c44108423180fb49f727a19adeb830b8c85b570f70c69b6ed8ba14f4b8c220ca3d546c8734cba3d0b12a5c6dea3ddf406296e0a3ef470ef15770a4caeeb0bcf2c1b5e9a0c0c470d0d7267eff6a72fdc1494d1baa8477b4cb28734c2586b7e92ae23877e9d8eb1375372b822c3a809b391c8b263c8183df995de4c22acf6a340788a67b0b92fd4d52b725dc1b79a4fb867671bc3fce7724b6cffef1325fd1541aa35dfb63847fed5797272fe7be3ac4e6290913e590a385e8ddfe051d3fdd5ade9706d3245617f0fa4723e626dfbbeb0dbc30bef7e6871dfae473b65c4c3c2c2384af327439e663e1b4846427a20ab4f625b02deb205221f79dfff64817a7c3970d8bb3e632858c995725b82c53217c750a07b2c1e8b85af232c838dd9180b32b7a261f35a69d677f3727c078655e8853a681baa4a4312a00e3e09b4e6cd6a50ade6dd4ee9229dea6672ced7c2a6b11222157b76b5cea2311813faf973a55fda1e7ab115f563d1b1a34fa003cd3745641cb02e2ed717800b16689b0ae8824f9d418b8ba27693e9b435265fb28554886a4892afe48cde9ff67877dc71c5f0af9e06377ecc975b0ee6997253504c746b8e0482237d3b79ef701a712efa2c0090715b90decadb8353becf729e358e23919a520d6010a3ecdd1443a0d41856657ba40be81e3daa3bc9ab492d44c044eee0829812f9d1c644f9e0fb3d1b278bb31074084887ebaa55f76ffd7217323d575a4321891bafac2202b2f70d8f96ceaa29d6fa75256e0721e9f34c728a510b1288b60ec13df07e35ca83aaa9930a8cf02f60dc0965a325194c3f1f72b949c93ba30f30813f577c9cc61b428cfd26aa609d9058b38d6403a712820b7275c755510a93704affc24c8e62a84865a90f7cdce68ec5d66f0af8363783fc5f31af992a7b6af3a15245646fc3e6fef51d8f52213255aa513d9603fc0be6b772e4d5a48caf5e5199f18a1e003c05b3060ac6317813a5714960fd217a7361ad1d622eaaa59f5eb7dc80252fc850612aa99e599699abf24761b428214f514f8c72a0c5477c1b942da96a3d41e2118186932ace70e30d3b5151b8abf7b33f05a41b2d9c7a186bc4a6e4a36e565ea9db3e9582e80869efffe8ec5eb997445b5dbb72572bb3ac887602cca0eb7a46672f25cc4624375cfc99592ca5a778fbdbeb9e722be03cb3f20feedc67673c1257fc746d274911569e42afd3cc9449771fd78836108a64fffccb0c917be6413cbfeb61e661cb1b81f2c50383cb9240b43240462b17d1311287a33f30122fa1bfcb2fad98430d107fa87c2e435bceb190ec5ccd725782ab4c1ab041f24b961916e5fb4ad8b58b33483c478429361e51522ff5607297f0d623f75a73bdf9db340c9708609d2d85d165f339cdd192da8c9ace64f559e09e07cc959fee6fe9b1bb4158a45f8b72587bf9a49732beda742b7545af7c45e14e2c54b2587fb1e3c82abbd46c25ae6747edf173ae7db8f8b1d30e04e0a4724f2cd1a1cb06a729a6d2b605fdde22dcc5ce7a0030b9df8c5520f8ad2d20765371da2777610247d3bab336a50ff6f2351705dd454d6fc7ab41445a4620b8b7729987982e97f069f690fb52fc7809a859f10576c00d247c043685e3e56c080672dfc26fdc4d5c7b7bad99bbcdccd444898e5d1e9fd71546b7ac0dd679dee5343294fd6f8a35f400c4d2f18fabe3c6af742c7bcd81878b4d6586a78c2bb7079572a8891dfe96e06a0c0edadbed6c648ac0c77471b444c5ef255940eb7c7449a07230e6924fd23f5433916cea528829d8191b64a3e6f38d3190ad4f5b67ac4c7265cf09d654ec1449f2df14dde8497e0d20f0c201d61c411975792060bae6611f72ef140b69033213faca59aaa5fc03b98892ee4660a962f003b65b351c589bcf723847e51f6da2b621db2171eb09f8200519ad13458b6e24626bf87c07ba55e87297e5df6f3f961eb166cb51ee3f88625f463bbc0419c907b2abfb4b8793d36172074351a1bf67896471138e8080f19d71653113fa4bedd5bd0992f159f667da1f12ca615460ebb56f67c4a07e98869171486610d156ba2972dcaaf5a5066962726e6923265ca7c7581476316cef082f2ffc12026938a97c07bb8c02b1f67e86158fece8e41cf67164f93c607a87e506eb8b97851217aa455fb284dc9201f681729de6630da0030b29905e8d17664d466bf83d7ec5a758a0b7378a7fd87e9ba636f8c3d8f54a60fcd4d4af567ea90b16323457834b0914204b946ae527e97a2d72ce8b9cfd919ed2ec472656404c00762811afbd68838d5c606dc91f694599e215d055bc265a44337c7cc456fa357f8efe3b6f242d82f5b35e55d13a3f633dbc72aff8080388139bf8f991c70c1da736bc89f6020e097c234c64b4444dd8b8be72ab38fba19569684f7df0c223333a3ac7ca49bc358d100800ffc5919893e7f9143888f20641313ebd9febb9ef3bdb6f258719e1f53bdfc04b3fbbcd114801520201076035a98205455791b5159e92ca4e8ba16d112478e7bfdc186282ed449961a50ea9993bb89ee981093c8fce5d1a882b1f1eba20e9e6f3e37b821277537468b8b93a4b2eb8db8c547b06717f22aceeba64f17510e26e182b0a4f5e9426e17204284c6b984cf81edc60a84793153bf3d0f290fd8029222061efea9d0e404a723fa4e6b40d18dd67a964039e3a66e3054cf4860b01af3c95357b4b5bc0eeb97202c640bc80b3c920cc78a9050ae310777cf4c4376d2c701557dca94644e18202ef4780b26a42f402017c09a9579a1e8d65ed83012b6c4730944552beb80e1e72b2ae439d3151b955216e20779a3c1f507b4619b6e1f8260b2d9b6495534a505fe087c6de114830eb7ea7b09e268b0c7dc46e523b94b531ee461b2279938fca3ad63cdd64a3aa06f9fd16ac688558da345a7ce40edd25ac32b21615c7ff88cd7291409d8761c047e4cfd0310a39f17623fa9a6960c438b4d4ea42c99255e75672531ec76a556122ec7c208f5bcba0758ddc025dbf077c1449e804127597f8937224efe5b9dc0993995cd05a4ba984644f6367648ef2420f7acda558ce90848a72aabc5f029b43006d4b390d998f96fa37bec651a2664c71bbc6c0b89e562d2c2fb6403f3c3ca968170eba4b54d0e9c6c7bd747f8a316509ee6e9c1892759d14724db9d90e14d18665f5bc3938ebaed0e7ad26ea99df3474509183d2502670f161e64d846f6260710df65687e47a88df1539f6d84d827e68f4b44e56b067d2217277bcbe6ba17707a5997264242b96f3ecb0c27665bc3527946906888dbf119739c050cfc12b7135885de90267fa38d979490760ff930a535427bf25155a38c9722e4c7614c04f66221ba1cf199fa43d8e1aa43462759af82fdf1460da8415a42fc70eaf3e1cd552f7f72740175bf223842660d878334cfbc460594cf0f40e6672bb675b883db5a8ebd880b6a9ec5d77000a3cd21bb42da329297298d08eb100136f4feee3835231a18c6606db9be08bf8fdfdcc6bc6fcd75fe46fbe37c667030aa6c5b71e938b6358d874f8a69142919918ae1c5de56eebc0dfa44c6778c5271409cc45dc97e56386aecb401e3764047d2edcc21ca17de4a3ee19931f73c66a720e745604435f1b5259abd555c79c114f8f4b55c17980c1332bfa19dc092a29682ef82994c4bc79b20c12c730de9a4caf0b14725c2f54e24e52bc37ee824e5172729036f933f69b63eff810281578ad4d95296331832baac46e1c2cef113291647574d3de0edb80532b53217b60670f17659164e41974d98f7676d3bf82f4577213e738800ed98c1a0a5c3ab09633a8d17d5a4a600de89f62d38de7f0a62393275173a03f52b83c085d1ec54921e6629a66904776bb01191fc6d91d1ed7c17872962923febbedffc07fc9b0ef61ba05fcc120dce366cda8674afc3b1afd484e7283a5e8f6e61c16acb3c9151940194521f2e7951c7b527923be4d6dfc4a5d47722c593d5fa7a8e48c1fe8b11708d1e7a17d26b94e55fc8199c3e328cd1280ea566987d56f03f55c09f0e39b41341ba28d53a240afd2559840740751fb4fd31066c920ad2eaa6e3c4f31b0eadeb0b9ee56704a6018638dd7d76cf66db3f0bc8203acf5b2ece2da88f92874a6dba0adbeee447b9f236c571d0a47efa6ed1d6446340b87f6d9548a8dd3354e1299ba5eec8b78fbf6c647604de1b22c8b6af7092c7262eec851c0022670053266a9acb8cd5833039d13d1fa00fab924a5b89693b33e0621f19ba29d3d2614adf7bba248d1da61a8054beb0e55eb7b4c6d8fe883e57217f69f9c1916fc129dd459bc84b5c57283f88b75a607841c9d28dda3ae673b1728cad282ce3748ac73b6a46732d613d32a4c536900807805bb0b7929ea088b45e9176d4b4d3cc2e4d9747a8170153b6069fe8e706c4f76aee0c0ed795dd33206495a0a8a7ba69a17d5cedd8c2744e8733428c431c78f4522d4931b74c9508c7246cd3f5f2450866abb7a025fa9f1da1b45a7c47f0822d8a1c8721e8a6fb31d728bc095ffc90c17fc323b8df7b249d47614ca779e6966fe481a3638a0485023725e25bf8e8213be604941d07a3d0df72fd53845c95148419aaec1b11466a6017257569b822bde3ac4db3934e84e1932ee199e07a60a87d9843f53b477e24207189ce7f0e031900a20028d5592dc2cfff4b58742332fb58143b0bce2c0e7a046723410787985d5aa6d9b3db696fe4f4765a2e5be50500849c32e23a2e826a4c6725d5fa125ad4b97c054cc49360099d91d169dc92a40796e40eb4b9ad23f1d697276ab2abe1d963b3085bbd43dce001224baa5abea1aeb1db9fde8ad9f498fc972981b112301bc353229f54464571dd6911a7e848395dde274f44c25768ffc70721bf0d8ec272518a8feec18578e114cbcfc4c0903ef01ba13139aec7dd61aff72c202477e58d3b059b643e2876a6138cb492d322782c1d746f254990468d129727d9c47914b09bc1d2abe67fdd3fba48be261c44d3e6284e4e41f4e25d454c15cea328916cca48f6dcd0dfa676d055578ef3767ee874bcdaf0f918222dc4f81720fbabc505500846f2d592062e371c9cddb5e6217cdc2eb6146a07c72eb9b7672676579efec7738855a7e95753ebe42f7b2ea062256153ff6bc142af76b622872b43a43a50e327f4b552517895b6e04b7809ad946408acfd404653f0740fc191e1071b4a44af7be31c00a2f7168aaf514ffc51c9b97a6c691e3ea6a5245484572236d085a46d4b7ab5803253a5afb49fb42ba448f425ff6706be7afedeaf06c726f1c0163570ea525a273d7220e7275de71166a921b11ed01f7dd0a397a25c72c25e05a9e8a7d3563e924152a26c8dc6636b6e40b06df6e75d1e15d7af9a5da48510957fb49f3ac77f4ffd19710766abcc100cacb7ac86653becc74b5a3e37f721a5c74c1ba7eab71723e9d60e6f5658c4f6a026857e0c7f071b92dff78a83147d91b4808bdbdd3d04fdc9770a580119fcd1b58872dd8da524adf41a9506ec527a76e1761c11cfcd1a2ebb5aec904a64d093b3fb9c60d91388e8497d4c70e1772396c78d20fe8e8038e7a742398011f8daeaac5f474ff0ab67de8740cbd20e5721e7e275df2968c6d63ebcb4d69c6c1ceb6d822c5784cde0061a2d71a5ea8e072171f51c7da6ac0b246cf0437dca532dfcf483dec20726776f55afe5ed0d3a6728eb8ede2e95428741e95bdf92151e3c27df47aba412aa31598a97279b86e84719531c05d28439a06506dd7f662417824dfe5c10d0920eaa06219157e89d3df6b77de5de3ce4d8c9654e61b134c8736e9ba72c829fa6e679203ce110cdbd8730f80f92455c0dac82e5a1739cd6d3191fae2ed3baf96adc78d123fbed7193e46084a14db652b92a0db493be31f267c9ce1febca7fd96f375ed798ce950f72764081321558504f42ea62e4723baad2509f6fa6718f86758a558f0062a7b43f73d0568effc956e242fab47bb66bc56feae3d81958c11d46f7deef0854745947cce606833d605e96314936f691462f69bbdc068b13ac56fbd04cc41949443aa6b3113ffe01f2b0e5fe8ab10ac1bb3769c2e8b6c9e64cd5fb5bfd4f8e3a5101cd04d2c92d4741b351fe635749d70b29d24f830a84829c77e339cdd4b08242054a64572c9013a12162e20713fddd9fce36c3dc21c1bcc32336e192a02791c721401815c304f893629a57cfbd74739987e631b721e1b60706f750697f550b9890d46af72d31d20d16aa6002a73faab22ec567b344589aae51552c34a52711fd1fb126372bae7ed7b7d59fb5445b4cfbac0a506f91eda2d376077b65d1d932d0c8965a372a99836949779f1fb4132500afdef5ae2b9919e79849abf6099bb0032678f4902433d618a71d21a672ba8b66e0946883dcd43fb092ca0540dfb8b36f985fb5a52dc8e385dfc13b5131f22b74fcbba40a9b7f5141aeb9433a3c027939419f2664ffdc71cd733b9ed7fd7aaa68fa87b627e1137f5ff7721bef29f54ef085554ba72e2855c86b4395583b5112eba4937bd2fedf1e58fd92329b42b86946d9258ae72d62e6f57fc878bf0f7e6b81795514ad8f3d0b40d50adf1b8fac9217d637d4c727572dae335c57a1a23029bbe1658a8e3ec0cf78740de2334ae76e0380e8201390ce6e29eb63367e17572ac2f170b5b711bf9c5c0da73a3ed74c00629a583c87270e25ff2b215f3e0481d25699542a56f8d169743c8e1badcb50adb39dda9af720ea229e42db665eae4bf4ffa7d44ccfe6756530a191187291fdc81019895f830a918c36f57cf64c3599f0fa238689634043e64bf2c1e741eb71518b519588172a3447acf4c59f374ab01100de1ca0a8440cd792ca3fa75782efee609e1f64d648965600a72f42298c6fbb967e13b486a881b3e03f5f5bbae9d7398d95f6e893fc19733a6934437b98e38e2f2fe6f2111e83392b279f20a9fd419fe1d0b2d38419994325e9bef28995cccdec70e0d04101b0705ae960c8fcf35cd86007f84de728e6a2ac3a1645e999f450b6879be856b776bedccbeede67bedc51f8c4fa80972a2eb73c947c0095e4c6bbe1b29386652c729f851e54cd5e77847445e39dd8d72c659c22a708242a59e737769309b6e28788061b08bc3a16188b157e3e4563e72e513361d75060861f14c7423ffeca9c63519f19caeb90e9e909a4d54d409490d14e3efad8cb265d175603f0af74796ae1eb95b82fb5986e6806eb0e93e176e720b83518e140c85c1237c244c30a826997255d81a2f58eef4ca64c00d92974f2deab2f7fd0ba61cacdf17dd512f153beef4a8756fb617d77545cb2460644e3a340b3db262f5bcb5c761f4401090817f7d4b9d4b0a65990d3412d25244474482726b142dced8bfdd4a9b33c80396ba5f8e51549b68bc1cd78dbb4433e4791a777234fda8585ee5cbf5275a579eb84240864a2bc5a41d436a1ad9b792fa318b5263c1c5ce6c3ead4de006f2e5eddcb4c137d859947fd3ed858c6b5a1bd11989b172fdd130ca9c2d13743db800925a1079dea057f34fb69da69b39d2a436d1b12269c261f62a8940def7f9aa701d2f44bf73f258352e7e8862b4a0a7148bf1f6ae1112a79e936dbe4dafe336e491758616cbbabeb4f029f0b478cd482736fa402972af698cce0f9590ca8dce45d39da35923407a80cda0371f702bb8c6751aaede729d5ea0098493f607cf84fb21cd44aa098a4cb5be2c561d0b30acb21fac8c21728460feafd6c48174b1854dcdc62d138e3a4fb5be7cdd0cadebb484f02015727234231f08d2c05cdc3b07ada6ef296095a6bba27861808fe1564df7b97d802872852bb0f280db8e4de8a77ecd5a73816b76dbe46aba0d6c501cfa06bca936fd72d7b848a3cee80a138718a0b229b02aec25a3c66dbb60609ef1e508ac3ff0727261e5cba028e64c5f02b5e37408465bcb213ab18e824abb98c83b30ea9936bb43749201b7fa533aab10a90b84881b52075ef0e1666728d88dd23745176ab9fe335a93a72d984a2835f73240d4012b5ed03b53fed3b18e3e14552e8974b60d0f2688669a3bee1ab800436261122b4083bdb8de43f640c3e56e9b389c808776475b492eeefc7742adf651407a0b2e4af27c78a41d7c788489dc1916c8dcf4f1777247569074c12d058928fc27735dfc0ea7fa24cab5a3d3f8c577ba76ed79091c7201819abd228dd50bec0c203ab1a1d0d780bc3f1d4e3ed216fa78127855de9d7238302f8fbe1140a1c50ef8e5a09a149d589150441bab2f565c509fa252638b44d517ab6711fc0e55f43d3ff32d474be783a8447a2bc087bf970d3f28ea27c7565829acfd7ad4fccd3cbc74397d285e7e91704972043623a2c8361a5ea820c845a37be3ba4a73a19b9618e4d0df4018f411c1dea693e6678a04ed80126e3e57726b2b861955321ee74054a6932f1a54bbbbf9f1fb6f40028a9f88493f32604564386f703a18b878dbea7ef1e37a5dcdcf620c544a407e0b0b4dabd95e4bec9e60ab02b9efe65c36c12ee28d646c1ff0566b3e6903b7ed4ab596103240d58132728cb716f7ba5716d4470d86a14fff3d0860751a84b21294bb22207fd659fb7972dee33f35f7c35666965c7a76cda92b15975920fa95cb19be5f31262c3b088572e4a3b6d13f2c75a7535beed0842d14b1d095a7285083bdc81111295564830672d0167e72510b71655a769558c67b78b1412e320d0c072115fa85e0e20a3f18512860801ad5766941552dc779df5f8d6b3da69a616b05b3628ff502224ab40a721a488aea7064e6a65494bc6515d4e0749dd7026c472e968420015cd414c8c472c200f8eb744e9b2bd425c514d4e608249dda0d0a0de67c3b7f349b8821ea6b605b0d5e2b6585e83c502ada5484554195cb1951d9c3ccad1a41b0ef5be24db77252c1d15ce4391bbb907c332de04669eb5515d499c421a206d41b9a86235c2f39f34f8b299713adc7735ada3b93a5da2cd155b12ef43fbd77925d072d5090527233cc1e86221b595a02cf5f336ea46227af783625ef96d00aff1d4480406e08725dc0371491bd4f4889bd6c4053a318d7097984a0e6222e76c5250905b33d6c035c0243171f86b3df1b9c3dc5dc7538071268e0c9a0732da7dc050375baa4be72af76f1a5a07f92b6b943320b68dc2c5a7660b213b0a0a73f60614f402f6fe072f90220efb99d31c812180b2d78a3368ff39d2383ea24ea123b685a8c48127d107271584fd10dd3133a621c51b0202f7263a047a746ca5c6ffaf02fd438d39d1b6b3d3b740026bcafc56b6dfb939e8577f510ac9cdaa2de6294b5dd8c81d9a072d9aa50195598303d2352cf2e6dc33c8becacf2a9d4ce11e60cf4a289fe527b720076587940f6ebcaa454b04d963fb6848ebd4a947ab648e496511598a8807d6cfcab2894b2b9e13120e32c9cfd6c7f89f4d6ba403ccd98cb8d1222d182d2942b7a3dbb121a1fd975bc8ea8d69cc29faf20f348ea657e29402ee1935292b8bc7248dd28245049a9919ac5350df73aac8173b7a16a13679d409bd61af06d874039f27c7cb77908d218a1982fd0e81475bf7df2f1d6dc615fdbcf81ffdc7dfde0728ee4f5954c83d1428414a07a2f2b6b0cbbf9a3aaea9f35f2408867b69f0335723530d1a908482a0bcc187739f95010b084244defe9b1514bdf37f8e808d867724a2a60f96f7edaa7f459bf08b32fd6760506ccbb1f55d326d0501dd9c96bec095c13bbb0ff97a19df5b9f8bf29a5788cb7b7b392d447a8106421f15dbf45d02d839c62297f63ee76b39da176aa9c7a9b307e44ee8d5b3d9aa98dd91f0cb37b729bd526dbcb1cf2dc757a10060096c1cd727c59525844895d509c4332eabc640c69da3ce6196dd0365cb9fbcefa410ee2b26fd7344ce58b42854c50907afa7c722a2067414e8b5a3a2056b621e055e74bf8a742159cc244b5a58c948aa0b5a94c78d9fe222c938898c0bbe11c0a159454a5daf2c1b68b1bff83b9e8009014f212234a8ba38b2a4b4a851a17580f435e1b7dcec103f6a3f6d999beea52ff983c407fb9181180cb5bb17f33f663453d072867c696d93432227b79c500bf53d4436050b02c56bc358fc1704abcaba5e9a33c523ddb58a605515d28927810866b096f3b84e29803d9e3548fd39803869a79ba2902439a7ee5f3fc2ad515892458735eeea180b6a63ffbe3cbe61fd703b26685653666299685b29aa56c74acebf4d633ff44558230dcd886bb79d0f092080ad0f517695f540b0fae532b61fb26bc8b500400747e0d4883ba78dcc7621b5d106c5fcef85e6c21b850552414ef612b547238ff7a05d3cea752c97cd5c96a7df30411ba769c7c844d601b7cef3742ceac1058003b2be9afc61897163370825d0c4c47eb835232f5a545979c6690acfd03720a77a86eb8007bad914ecb9ccbede79811755605fb30f2b2ab8e4ff10bb9bd72efb93ec50aa10b0bef94554751a257c49c8201fd3a53af307ee69b86c717e772e80a1b512cd424cf2399478541ff10efd1216a0b7246b3f1d4c26d7ed310245f5b371f7714af7c391d1d5f7a2da2faf6d9aad6f7bf90c3ec098703cc0e388918804c5765fea9e49fe5192300fc9c1464d496c93f79089055333139284b34c35f1843671a5e1f7f1321b26f340cf29eda32f887fc4b32ae5156340b90ada2066074929b36f63b02a71a84043c8d2d7b30d13f04a954b10d00c1271424b010e572bb72c34f1fa902ed42bfebdb994df2950bbb6d66536f1ca4a78644687f95f972d882cbccf56cc1597325c572293294c5050159cab1dcc7e8c132750d597b44022144fb4147d4dbd703fd23f2c1a3d50cd874764564c3d3e230d1048cd0a5a37248caa96ac159215e055f0be438063e52d73043c8c157d7e32fd40213151ecb43d5aa87d3af8ce7934d67b1434101f7c5246cdcfa7611538dcb8ca7503673d765e158d7002f6b6f0b26bad3ea2a2e1a9f504e538b0abd9824e2044cceb9d86f7203391089fa780ab1973e6ab36e72217a30f03fb67a764039ec3268f3b1bf487217c7e9daf0076220765907f6062dd177e9abaf6e6be1715236d148c541b54d72a1907b0c4557c3fca4e1b4f17a9e895ccf63a10cee8f7179252b7f0dba273420d60f55a07d0f76119c7f797ce31ca58458014c1083c5e3496c1d5d3c1f7ce610839707760f1be662c21c7d1a3d523a1998aea2892e32919c6332a0302a3e4357ae73405a73c1c96ceac56a4d4e8838631dfd79cee24fd742743860ebe95d01272411a76fd2f3468abda5d000fd36ab102fd446484f3043a48bcc88f5d38a8238df4369114a67ac8b425e0b1f12c8696b3e264368dcf8c8d6d66ef76950e9097259c70beeba98aadfb5e912cd8bb933182123e844ef9ad8be08b9863cc4585572030f01a2fa77dd1e9bcb3a57bea142a75a28427e1d70b2887fff360d27bf9931dbd2d75825b5c197816eb958b98b633661aae1e375314f514bc0c40a5084ad729f04ecc7e377f2312bcb312d025639c07c476d5e46f57d609be6b0c16ba36b72c097c9162702167f90b1d44e1847fcc5fd1929e11875f0425ab6992e56c9bb50401a305dbf588331089d2677c08c8d5005c1af717543336e50ae1a62303c50320ee42aa18130603cf5b66fbaae60a197a13765d575080c16d623b7078aad2972b20e6592c22bb847709367a6d7afed347dc6101d3cb87faf008bc0db9fc24472e5113fc22191e5016fd4803da3aadda4b21b1d8a94d2277b6d4b8bcaf421837232057991af4626262a4031ac741004b0327ab55f4b901c9c259d9dd4954397721dc5bb69bf39db24ca9aabe650b1d16cd810e691a1d8aaa77ec7178879e9fc722a816513079ccb07bce0e7614fb948c184811a24443022639ca8920eb3bbcd720a1a3000c93e15bae18b83440a8b8e0e237e61f7836f148e3a1692a8a1ac466599342d60b90205588f78354657299818a2388129dbb7a4dbde77e6717c338772038995a68c5f437605a41ddab8b4f7f769e2680b10b8d78664f63cd8f924ec55846e4a9f68aaf09ff4f555279ffc66e992d2ab6649cb2c1907de26ad02ef0f3254b1e6eee42f9883c9d936fa51dd39cd1735c78e0291cf684c98eb88090e147275e565c3a77359a0b56a0fe9535b806db22e22128c0c0404c0a4d4252a7fe3727125b802a7d99d13ce3c826011fb8a7dcdc75cafbf3ff1fbfdd50fc18d9cb0723c7d974172c3696a6404f174ecec8d45d1dff82aa916e41d79f06e99eb3abc654a5ed3250cc3c5013c616ce7748507e680f3090f4f4d8164506396a1afe38d2cba91e75730e845633f05da88e6cfa78cef8109eb41cc3013033c08dfa4d0d572f5397e4d59d5bdc05d5ab94083049898c679b703ac1d03254e66c59d9920715b63a5112ec4393aa7465d93e16a8676a4c2e4d4db1fe64d42b89e5ba0be1b0172bca5d777d0cff2b1bde1a672e1a01c9d5b76e17477166d93d7dd9c0f6d05654b6790420bba66a9a9b0f83fbe746f676bc6daa7c7e102fdb7f289d68ec80090728e977f0a1fc84d5509d708e42d218f3b9fe67ffc8b97f02aeac93847d7d9cc444a2d5e435ca852cc877eb626a43c0f63aa6bcf533b33089154c4e812d9c9027216b380ccbfd045ab80f3ab6ff43196fed506087294c231ca2cb8009afc1e4e72d575dc8b37a1dbbf2794402db0a08a9a44668c001bde48d20acf6b56ef6ba57227b22ba4b49766e30941cb7613b04b803d4ef6fa3f58a4ea4eca3263ceff62727e43f112db09b2f0a22fced3686c3dde2369b3a525257570d5f7c273545e307268aae8330c612bc6f9c3241d19b2a14d310ddb0578a32a9654a2b7c8b64b016f0d3fca373f4a7e32e651ad89c8547f25bafe2aa3bfca8b8adddb0db68c256b72c2b89afaf4daffccf4b27edb6cd6e4339f22142f573fe6cc87bd04c4562ada06623dd3a9a24ae5a4c50957aa44007af1db813d8bf205cc63c4693312597eaa2ff3ec6d42e7949194a61f31b625e8a47ea9914aeaa7d1da35e2d2f53f7940c1723bc7780eb10a7abeeab3e9cd216a543de81c378342528ec6ebd065ad60f5722e001cd9c994ee1858a58eeff390379fe5879ac8d4224b6a20cee6d18fed1313676928d1c05219cfbab930285fe6b24b1bf1159b12eab90bf1951443098b4597722dfc5c2e4f9cb38d78d59ce5654f999b697bc66ccd6fac86fe949e9e46e23f32853a0b1e4cac3f9e057fdca3a3f046719f888f12e5f3bcdcde26b33945557272256e214c42ac3d43c06ff1a42cfa1911d57a58f0e299a34963c6a7097f85277204b81dcd5cbf29447d49d952a23a6d9a5308e19e8df4514f6731a6b23f34ef72aa18aa8b473d5341fc9a8e554b0369645dee8b9ceb91bce2f55a8d0aec75e266dff134e56b4c4be634d3a9ba061e6735d95dfd934c1ba0a87b3fec8ddef27f72ac471064fc0007bb8ad26d45ce63cce032def7cc5b3d01753822736141c93572c8c955484ce179e9db2cc408d9aacdcc71b2b5b592568d765bd2970102a2df626bb7c74496ca5aeb04c4fe9999cd99332e119263813ccbd2647c2426a35afb5939d30a63d99e05c359f333a847d14f5529ced367b00086cf2b4c6eaecedbfa7217db13ea9e06d747dc537263a97608dfd7d9b231da1f2625b380fa228482973579fbd87b012fe390c8f7f758c44859e59f6770bbc1a914659069a2ed7d6c8d7204276228560f99de7262a36f12b8f475b883a023a3795636797c941e7399044ea91fda91bd2da7fc23a4308a3be890efc68c1ebdd16bf582dbc70da68cc1fb72fe7866a8e77d89e1891c81bb9c8c25acb905a733c84f91140ce50e926d5c084200f9d2cd43538b901f71ad62d3db32b4109c1855f9469909963167bd1b510710558c19371630419acbf00d827ac5bd17ac42c061d9175ec495cc67be13a3556fe3c25179222548be43d5e2bc37e6e070258020158b66a216e4102afc6e4be272bc8bad91be91add917ada21e65ac2f823ad1c707e7162cc3e34cf23a2ae0850c98e4204493dba58e0f5c089f1dd5828027ad7dfe00a9796336d99052d47c5627fbc07ca3644f8d8bbde60599c980999e28a6b52a0c863b861c604419fde6e5090d17842b36c5ff327eb65177fb9f67a8f1ff70e63782cb2512b9e0b4c0502f7265622cf92ceb173eaf5435dab021c7963845cb10af2972a9b17a7e402be4ed0a1f0f9a72931435a79fa6ec656098162c3e4eeb050a6fe2455b77c8b9e33cb7721d87fc5cde1dc6fac843bec7249986761a62028b5711928784b245c8cdadbe3a6bffe0c6dd7959bd3011132c52d067a7bd35de32c8bf763ab0dc4d491e2c74729bfbd0b047c97b721cb29183c24af4b9678d1ad463ae39af76cd816cbf5c08728ff2232e659aa33d46f02d1e5568a7b0b48b944e51b82fd7b26b1b3b6a7b3150a8052c7f8029cff716ff44057ee4604c535d2e86d5077760edb27f8e5925c472a7fd11b762470a972ecf85e2b2c8e41c48484b162904743d2744f639fe9676720e5b3b1ce905a635c4d8a275fda1bfc7f4c3357e3016c688080646fa4269fd7256a1f4c110abe3ac4d98773f72363691b64add0ffdce4e92458ece21489d553bdfd9e6447c186399760f3a370141a9d6271a7d4dcdfb79dffdad753569b99b7230a6017b96788faccc0e510472720c64dbbf96317b5e9e7beef545020ed0c9521ce732b57217bf8be8d20b6d7ef0fd18a887ac93f4d940f021dd418b11854561e15481e501ce8d5611f7d7d5290579d970533acab6ae6b83f6285abe162ddb728248fa9f199304c8beee9e491df30ef714c14fa71a72e11c5d87e5b52ff8f7723f10b56d9f78744bbfc11525c40159a466f12a2ab83b8318508707f602a85772ae36cc07a11786b14f9773e4928c7baba89a7ff33109a365b28af4f0df9819322b0c6ca381ba4e0a72f5ff5d30075aa1d6e9e6527ed12b5d1734d6b61933453b552297336e3d10486174a1c2a53eabe2ba0c6f44cb345386d59e41c9ce1dac72f4b9373c559a51b5891194b1f45e0543fffa02315cd8645d5521403b6de23672c1efcada37ab5373c1137e46ec93a680c9804054f663bdf8accf1db9180c03724db3f82ec72ad4888281868f981fe0fa8f73b2b9b883bcff41ac24d1fe7ffb2fe664c108ddde5f888475cfe7d8f7e508781b07edaf2da396da3a27c9ab9425720569529724e720984b615147dd279165912d76f023962e7ba435e967cf452c7214227659e0df8787acb23fe1f0ff7cd4f906db3502ff5c8be8ef59026f952c72ed0bd79664730c235378bf1e24197ad44adccebe959e1a1d7073a2cd0e5892659ef3f19538f21f08314ee567a3a9a0f61103e371359a6ea03eb0a4dc42ea984ab389b9eb12ba362f6dd8abdf1d8d7c33a3148e0e48513a50d25ac5f736bdd62cfe4a280b2fd8ad52093bfa824dbf8e7ecd05392649107b964fb35534adda8a723ab2e433ba702bf6b98e3a534b0ce48bdd1ded9a6c034b4db4b57ae12159f67229a51a96eeb229d75b2b36059aa0fcd01dc1dbe37a949850a9a01d402b32ba72e8e53208c122ec1280f349d1460bd7e56497c6fe4c6978dc7255d0fda0d3b172169819e6103eaf9042e1b98af9302629a6c30c1ae26e43958f0b199aeaee52723b7899e20e75d0e0699f46085e11f75a1fac6606b7a49a7685586dfe2adc7d7275a32351c5dbdc0acbd06639bee4127344220aca4938d31978de1193e84f180e00c7708417486c46f711aca186ea4511c2fa12f446f3ef7c0af7fa1142894672badf809ef1921cbebe81e04ebf1fbc9012681eee8a67935f73e121f3e8aed17297ce44b89ea3d69a28c3f99a1a1ceb37e9f59a13e61d32d375a75644a732c35c9b599d3f006896aab47989fcb61341445e312ad41a2061fe176e36eddf140772bba7b972a739c3f3153d9db93a5d80c386705fc02e508f75565045ddf3aeb53d2e2babe6c4d405139762a10f33d1fea7c48724ce7347dc73e66a1c97c2bef726d59b49e27ac8ae56e61d15ddc99be55191832b5f8908aa24bb00ec9faa7d3d16436caa0bd93752303dd6a2f8fbe5eb23af5d042660a20088149bdd93434df6727984192042082b2eae568e1f83036cb39b70fd7fa338c2507f64152f1c2b481679128ce1b6841f242db6b1a5b164486560135c89bfc5f69519dd4c06a68512321982adcc56c962ab8544c0e3076a92bfbcb060f45a1843c6e5c041c12ef47f72f4be78b64ab9947b0158e35bb46da30dd1ffcbf3f2cf953b6c5c86c57734c44847e4f8e732c9e51f070d10065400e9a4d8af8f1631f1bd1d1d152a66d21d507265c6c7d5d8c322797a5b48e2eb49c744203ab13506bcbaf4b27c56bc764e3c2204d4ee50a8590e1de8a0952d69cbb3c58bf94cbbc682b7ae45b222f0fa7297725a33e82bb3a378d1777dc688eb3e128733d410cda7ea9b36f7c8618bf7484172c41b3cd5e23246d88f9f19c1b245c369857c8afac043eb7d4b5a67901007ee4eedeb05f7d8503458dcff9e42334a429895add22ee41296600e533d473d946172a60ff9de6e8489bc4c78ce58bccda3c126e5e338d4b5c1fe6e891567639a3b72e5de9b615a40d782c55abaa27cdc97251368877bd0122af7a21627cb23cfe259edaaf7ff5f417615cb67bce34dd549ed682cfcd46824c26d6b4f9a841954e672a2474bbcf91e93b71e43828f8f1c44754acb108aa4fe069ae980818b4cff550d5b12779769a5e7ed79d18efea90e2c617810c047099d2f6b229720d6103ef226ed386aeefd7a291616b3c15fe8ce38351ef52f846d822e566d7d41249b0c8e72a349f51c6cf3d70be97829439c6919e1c9861586326a500ed72a1ed755ab4a72712355db16f69e815a3f6074e9cae671ee4c5de4aa1fd4d976ae9ed080f38d727877001e0d68a2b0ba98a7882a5fc7b3977371b507c87ab80558806a336d557245863c8447cfc0974bf5783679dbe1b4008aa3045018b18186dd569d3ece5d30514c0081f62bab8335d0fd1d696827ce018bd1f608d36aa186e270819ffadd722371703f5fb328e4d14e54e2bfa120ec54e5cfffe49184a63c1a7530463a4272d2446809fd3050f186d523621d8b705ca59ab077c00b72fbc894fd08a587de72b5eb4d0d5f315cddf74d45652ac10fddf1c4640031528e72b5f0a73aa1ec6672d2a32f7df022c926eb55d20a2af1e9bf1eca9e9d18a4b88b4645da78c8ae1f725c9602ca2e5175f66062d396db4ceb12d02837a3d9cbba7bd5127dab6857db72f390d445aa17120369cba55ca8a68b62250af3dfeb1d2cdaf7eeed17718a9c723b4e39f230e8b796704f7a370da8c0c9026f2634432267eebe8435fdcf97f872d4c28003042de7cb075018528dfb156aa17e1f83683ce044cea51763e663c523b46a5f7d182590bc4c78fa67d5a605955d60716a9361c062d7fa10e544bd3772f1fdf52eaf618b7680950906c4af4af77dd178ea4bacbe7bb7ba66c6ea5886133aa65e3cd57c436509558cb73052db2404b0c4b3024532c74d61920ce8818817792f16f97ea5fb2ce213da2bb00e6715e19e171b834559bc128cbef5c0e5c20c1804e33d8b045142c3e7403aa308ab62f8aaed0a700c057e7125b93fb03fb65e6b6e60a1b3eef5d8309ef45fa2ac81b237644605e83ffd5f1440c021e1e2467277bf80c8db2efc98480c0c7b4e1d9fcc8eb1d072f3761c5541644121ec84c26a606f242bc9534ed6ab594385954bdfe9d12b0ab1198364b90decbad5360c2e06d88d6ac653a1ca06880ab450753eaba70bc8a77ccf0839d36e204db18b39ae72fa4f42d8760eada3f7a52e6c8bd2b326b4864d9b664a3dc9dd32e2ff429632726f4eff5eedaa3105a0f29057e28c29c60d8cbb93c972c654e0a469ace00dd27232632ee102b6e28656083fd014accf52c5914004c9389a6a544b492702a0f172c1db4f28c22d9e99acab9780cddc4d7e2c121d8be4a3c3a8b03aea530b9e3951dd270a80a311f990edee262a118b4400e368fed295a9449aafeb45f3db90b428592500e48983c43dc6294e3433eec3d934ccc9c3e5212b44dd9f40ee12bbee0b80a5fc1e8306f3fe35f907eb559bb579aa96e562755ff794e551953a99b11172a0b7c6c02f4b6760305f12f75df68a396447aca4daac984d52413123efb35251544632ceebc90f95c3a07643f7f87739ed01d1dafa3bdf564bdc6b88736e3d5efb2e43ae491a3885d62b38fba0f57635777ef36d14b510fd9c228c0040ac8d7233ec79be8de4320cd276f9774f2013d9e14bdd7e2022d3163263bd1e40f6d029b3eff7b179e81a43830264d026edf8cb2f21bac601e481c00cbd8f5fd708f572fe35a4836ebb68a2fb0e89d1d9b688f7dd30f69010963fdcefd734bce0a41a72528705ea62d45cb95ceec6812327b89c6f5a0fa4e596d5e3e6cd99800fa32a72c9b01a77c893eefdb3c22e59cc45db7e090a808a4a633c0d991d3cefff36b172041856e2e992e902f74d6d81fe0957e705416f1d0d2551f864304ae3a13f1d0a38fa2931b39f6656751eca7bcee0c2ea3902be1c1a0e4ac6b8d9f4e6361bc54316e5b7f909c44b6a7fb4e0c5d3a3240f1dcdcc762195578efb7da17c94bdc272eb0082fb44ee4ccc90d9687674092597b0d44dbf2db3c39afbe29e5b15d50d72c51fc86c6f1d1f0745ba2ac89b1e9e002811f0df9d10debd85359c3670cb2e72bad711837d7a1e6d9fdc94fd80508ca8143b3427b2f0196ea33da7bc8ccf3b72a87cb07d66ad62f3bc5453223ed64dd9cf312d4b280145079aa1b0a6edc6df72949edda43d8e1ec1972fb6bdbada022c6ded72aa098f84e93545032f204aed72b283ed41c8067cc5ee5dc1d10c41033d3020e40568a2765e39f5496afaec8529202f607697463a223870cc05f4836b36a85f59e8a5f04754879dc54950e7184488b0d45856967cbd7f120fc0afbefea718ed1727c79532c919cd8243d6a1f209fc0eb8fcb7eaae71f0a2988f043e2936ceaab5230e0a26c1385f82167f3ea92834706a8080ca8720d79e01fbeb671ab442b6dcb13b7ac5808c73874741589c72cda5e9839ddea5f00e37148882dde090f27e9c1e20e7dffdea4f513618b88472763afcc1a1094d6b6afc612db3e18c92aca4af9787c7021a66d16291fc9d067277eec8fe1309b2da881b7bd194254de26991b63c48ae9790f6d13db27dac677233757f34970c81c7f7e99112d3e24c49242850d2c0253674b1ff029ed5427e2b41b8dfcd7e99e8beddbc7381d6c4793fa86951360ccf6b86a8aaddfef484e9728300299483629e12766d22378aacb5ee4009abd8a5c2396fc058ab1dcbdab46c54d030dc587b6448ade55e833530509e51941ac1d664c3741cde050e491039224fdce57c87cc8a5f8ca4876a0b4c2e4fb7a261bf5e04ab2b9b2180bf54477c163c30c9f6205d6d785e6545800f95c08a39e53b8ab656484fb01925e34367f17215f9f989de595c043f72f4b2b8e3d11393ce60de6a9ba1f4664bee6d538b6d72261c4110bceeffbcd94d2254d2b1c6d47e1bc6dd262f43bb2b16733d45b32972ab82d93c63a2fb6f40612a827e8dcafdd6674b9e24f187f8c40c11e5c978072d9e781ecbb7f0023645ca99220843f16a4aed53952024d846cf1bd09e6783897257ad1824de0d3fb5595f263e2068c63048b3df940f959b1ff4e7066578d28272e03877bf07b99ecf77a34d2d6f196ec4eb0e0f6e74e96ab92526c4fdc026a747d8399fc22be687b6839e0bda1621e92d1fb3a7a25d576b2e793b10d63066aa7208e8bb927f539935224238da9380deb32ba75425db76ce59706a2de32f4b564900972c6ce750181eb49ed4bb5f53c627d3321aec67ca7fa0b8b3bafa8537a272ce600d39798d373e1f9175cf6525696274504f58778922e712f8c1476c6ea459edad3733a5ae299caebc56368cea0178e216920935e02068aa83eb2ea4b1210aa6854512a238a09f123dc94f27e518ef53a2d3cbbce0bd6fe9d2daf254730d72bdcd359426e33b3a541e3a0fd011796f14a03663923a77f022c3d757234fe672266605297919a4dcc717d7e6fe0e74a80860880dc532481d128c6e92a4aa0572ea4623a74fb4de4613e9ac112cef82d0a60e1598fc46d153d6869faa736217725ca5e9534a2a6ac76f3780bbd4180469c6f6cc658e71897b4a86517fc60aa8728e8e90388137e912dbacb7922ba001dea481b07226101cbb5c9397dfe52b0472a737c46b86b46154bd33977b60448b0a3b4826611b8a16405b616c8e2ef6fd0f271e2eecba76487f7ac96e5dffa8ffd9f1f2a00b4d7b8911b62cb1f448676572a40a095a45d9b8a07277695dea404b5b1abbcff55a9434732152d11d9ebd9572f4fcde58d91c3e2f08f8163e906596aa62da5cce81830b8a4c670cf1833fdb729f7ad38b91bf04f58e3e39504865564bbd2736290a8cf39aa8bdfe36e7ae5034c3b5fe6de1baf3ab90466a2e175a1e29f6b9ade602cfa5fff0bb8aa4b108131aed96b294b3694caf7d48e35df69aaaa24533833e88f6fa850280488f4092d017b737bee80f6209d7aaa22de00fe2922515e81043ee47f0fd5ed006c49c025e4bf3c565c4241f5bdea25305bdf155ed6537d287ae4fdd49302c54927beee224722a6250e066323ec1c6bd259454f9b97546f267cf8b9ccbb787b1673e6ee3d34819e674e11b6d92fb86a1c1f945d02c82b5b66eeb1063f680e1ee066f65d80072f9bd11fd5d29fe188b5317a76e57ee2e727fe9f7ebdf836f48972402e5241072a9e53390a5efb3631b5e0557a16a3c4dea6bba240f3f090f2ec3b70792d314151aeac6e1a1162860f51057c97a84ff9869a3e4e8631b9cf5c081bf8e4fb7e272122fac587ea148c027353a5b14e9866a2c1610400d1bf43dc34c316f5bbee972af8c3781042d78026a92cc06f6ea23181b5549f8eaf297fbdb7eb9242b11ce72be238d72d3fdf609bd5df60514d07f1250305946b1a4584afaf2aaad9d2f59722a57742f2bcbd0b2686a25a8394455c2529a02a6beb681518cce867b07fce37209e42d31cbe660c9331f91b431cefce1ac237b642f86ad94d7e7b0c6bb9d7372ed92a1f0db5c341d92a9355164ea598005cccdd101d57a1a9d2e551832cb59620081d822e94d264ca24d61aa2f586faa43bb3e900698653cee024bea3ad66239a7c0f7e404cdc786f056f0dfd4725734507518e9d4011e6079e3afd1b6d40d2a65afb077128d4e262ad8b22d31a624ef46643ca521f2418ccf826b0280373554d2a62603ae2bddf5b98dd4129aef19d63df54e99ca1f2cf1e65af1569b592d729500dfd2d46f0570238fe6a4dbb37180ca8c2d11bf78c53be417b7487e02464ae827c40231443485f1bc51c4e638b12684b0fe3c85d5dca6a889f944a7bbcb72fe587f6787f26936b5e987a35548f76b5ec78abe760544823b59b0f43d1371190f8e3138fde406b3f02e0de65d40759107c60df6c52d37f614603887def59672a69fc56544952f0dddbcfc9ffe15bc547d1746de2f5b692b0ab4fd8c2b06d161893601a0f28b44d0710bc901ffa96e21be6f6d059b56128c2a9b23b134182219b9442bd929c9a5bc04ab4ab4704ade222a18413ae6d4ddebf65ec51077668e271750026bdba4a63f69c3ea6a301c53079c0887ec72a43582ebd9b6d4bc024972fbaf4744ec004d774c72e627143d06a9000987151037f07236545d1605e896724770d7262d44664b142125101d457660f52def3ee73bc0b0e8ccbd1f9f43d02ae43a8851b755941481606ac8de3a19dcfa4dc14d480e76078688cd3e13a22933a4966db594cddd92e79345e2c93ec8e8f98b34c69498346294b61a1084a1f54c505c19fe0d9d3600384615d53a507551a9d733642e52713a2af3ca847c5c5272f31ba7e0aebbcd856e84fc73ba88b228cf8f6018f08b81185d309e4db26ba472291fe2b02f1b666bce11c40ebe91858974c8ddc7facc6b7fc973590e24826653ed28cb672b35126df21e57c45b0d577e8ab4476f33e052a9a32d4724081244728339f6044cb6b21023e87b98cdb98d56af28d2b9e190c32ad141aa082aebaf72f88f433d42789f89b526b6c6f3713ead7e55e7e58411258d4d9cbc641992ea0e5bf649dd9d402df4b17bae5fba3cb0fda20125569b28281b238265ef9fbd7d72c330ef317c47158820502aae1f819af0e994674e5279b5ca01e0716553b05c72ac7023f2c7ae856a04886b3b6ba1e868f4c5ad72aa4ce276eb980a39af51157239f2791f1d82dcd593c6cf2fb4ec196d0e1be7fb784c9a4363d4d3d7475e7072d325038ea9b7c0c7c8e39a3260ff47707275c88950ef4f5bca0d1e9d89828163e70d4d429280490d285e4337187243be6e9e5a0e73a2e474f083d8f318b60c363d3d86c1fcb333ff401a26aecd7a2d33fc4ddaadbfabe09f0bfb3e18a1158d0cec726217c96010c667fc53ca3341cdd36519689e49bc73371cead7091200fb19ea1ab01278a8eeb91644fb2ecb447795346c6bff27ab7ab190e240531786d372743883950aba5bc556f0b12780ff976f5e03bdc7fa43e72e13dc43c33cfcfc72753bd90d6b9c66776b48cf2cc1df53b8e1eb1de6933b064376f0e250008e4248d00a5802ee34b079f5d55ed68559e31a1492a0c02e2774be56f9761637b6b372e5b6245812c1855bb5961b671993c7525b5aab8ae112249e1dcbb100204ba2724e05f7663da00d5c4c32ffcfd8a4b7c901eb4957d9d0c659075203a0da170f13beb64e97e6e85821f432cc8c7e4c1ee883719a1cbe30a520be67c6e82407677233989ea9b316efe31113a5c786e205a1585687ca26f640fbedef0fdb6c660c660c9fe60ce71174fc6e81eece52a766a4ca26229c21c21a5c7ae99a5b02b0b84618720cfdf208c451902bc40bbd71566af2c9b1dbd7ca866a456d19e205e5e96aa8ae431b76de8ac79e3d230aced88382d8c7a245cb4195cc06d1306f20c6520c054fcbd1bb9fb45ff2723416643a232c6d5ce9b7b9868ab1f4f4ca99d42f321b7d827c0b74e5c425f97e8f461f16a0a4662c4256cde621e79c01e6585c14c672c9aef7a8f87a73688e9f76addc75fad32b497306afdb1fa38e6d275b1eab19314658cca89ff4927b17ae6a3e6e3b75d18c5ab1385a269802e4506e99440b3b72599cba5c146b03ff3842dac9fe418d051dcc278dcb477bddebda71fe265ca025891b43f9cc803b2c8730ea8efb434928cd10f31b5150d76232bea4ec8481cb72082792105961a588324e405d1abc4783095ce3ab494230906d2c0b3b3183a77215b929b25e07fb4bbae089acfc5548a7d6b990fdcc78b4f5cd986fff81ba8b7252d8cb633fa3a8b5e5c47a2f8f729a6d43117b7babdf9695f89003bee9516b7222c64df4066c129d991cb39bfa6dd6af6a98b15d5c730aac79e77a371113b26b44454eaccafc6208060ee362f734a7c61629ed54d638ff5ba1ee93704e34097274d6698329ccf407d74fab8da33873bbaf79ffdd68c0907b692a4743b30a7472490e68f9051eeabd8a765e402474b9c099da0e42f391b387a560fce9d3dd0c6bdb7f952560197dd089e7fb24399a9e983c51468f29be5c1075263e6ff57f2e72a133b0d2043ab8a61bc478f2f7b23397deb1115d92dd04bdb116f1e51e349e608034748a341f9897ca445db67d073d20ca082cfe0eea92649b3deb298aaae6725797954828a27dee381c0c436a0d32b975fed59c285b05e70b8ab504addc162f15569913f40ecc2bf16fc219ede7a4d27ba112208949ccad4e840851c40bb0724554b461f0371634e656b597a923b7e44aa8d58984afbfa200c3a02f2367fb1330a1019e820030e8033e1aae65bb754c6eed8cb4d79a3413b2caecaa2a8da07244533151c5fe1622b4508d4ffee8e90c984c51b1d17b397d7b414636a052c54780896b8510c7852a035ac7da6f5bacced06bcf79675bc2a937247de5fad8cd6d122034f22ad351eeb700cd4e9698f733580253d7c124a76bf856c314c3250f726c149abf772aa03f535dc73a21a6e7c043f3a4e72e709fd04074b14e4c0512041f9375e5135a3336b1ac6fbb65720accc18a02d2d5b69f20e3bb4e9564441272cfc3153bdbd8755c1da67c04369c64515f3be2fdcb0e16086a0b75105d23e12098ff3afc7eaafb3c63de28883f8be9e105764a60f5ccc1af617e42a8f653fc72ff876b0ba50b1f56adef9cf281ab0f708cda8fe6ca125bf900e8d06dae0920720bdfd3bf075eff78159a2a6221334bfc7f6dda19edcb6f25df0c553cd46c177237ef5500b14fc00de7d2ab6c1295b9065c65662a8246c675edabcc784550ff720685e537c5de72abf97c6ba8f69970225e5352a02b55afcaebf4eff27ef0bc72236eb1b06e7e207d918d2ada94a91728195a84c5e3121deb6c82e379bd8cfb53aa684b1a7e735c65af3c161a7d6fefc664099e3e10ce0c302685f423a75a9972d2d4833fecebfdb39c962f1640cf476e105b91426b8a315a5ee38a142b66601d5d5fd4687bc4164c4a71c9ffbbdc8a660aeaa472d1fa10196dfea9fcd1d85b723abbdd241cd9610fb19816abf4f10fbcd01e8ab29109863587ef319377db907263e1cdf4891fc7ff9b62bc90854667e8302a3a08835d2a5c3a73ac4f5f94da0a1584d519023f1a7c9648f00bd76f065f4f655876ac04590c0f50904826518c72506a15334756bb1f820efa1b086a9b729d3d266ae44e59bcfa92bbe60dd78d1e01be50a2eeb1e4b4d6a6e4c99c70a4b3431650bee9ac62f36585ee67130cb572acac7034aee93773d9207f6a64d864582327ca58750ac58a592d9d5cedbf8c6d85327ca4855be92a691e6909dd6cd77253037830bce2051670eef7e8d0cf9c24851282803c58575b04c63389b0106f41ad171d978f7fa067ea5dfd4dfb9f867280215220a40719f9e1d7d2fab6a5d72cdc4b5edc10b74be3c1dedb3669567c72d158954b55d207add0c789873145bdbee1d62dfc216d3d3c3516885c4f0b37646010a85a24f03cd1bc8c831c0d874dd4aedaf461eaa69a72437073fa5254df729edf62cb5d7c9140af0ea33d43461d64c7a0e0adbedc9b326e99f626d244db72c98ee4bcd505ff3f1ae68fefed85ab4216a944bb6debe0e85e8be4198a32667241de81e33c3b598796362045635f4fa85c208f840b16e16c6e202038c11bfe7253e868539f98e0dad946eb0baff2bc69d2161748df1663baa751936f5a08d8071bebb598387979e4cc3a85a3e3470ca7d5ec4f8c069ad7261ad0b3f583eaae483fe662fc0ae4a67a87eae1496c08b2d55c17c061bc65f28c1598f7436705467284780d396f180df6f15c16d860d6c502a51f0935d2fd327f5320ec2e456f6d72118be0f8fe9acc93a8c2157d24c97f6c5d72ab46f08c6f217803e33fc658e2727d9c1853e8a683568a4791a7799ae63ad3a63a46c50ff56311fbd1fe75ec5a1401b2ac879f3c385bda83afd43e907bcd427655c7beffba6105b7211a5fb8e57257bec00ab1325c4853a293f1059a39d42b5a051401fa3e0911722e1600cc2d462715e240fedf4a8c34b32d88fb69a251b7c5ff55498f29e7530c049f55e6ae41d56d25bf032d7417c2a76fd43930e7ff9022fc5f1a107b724722e19206612d1046e044a060cba1bd6c117c60dafe1c23ab1690dbf8bf8669d8bd23f7b317c672ef4580d982089d871502bee56484b803b9245df37155ee1a5b293a6b86181f72256b1c352034c2fe6c61302204889fce98a13a5ae77bcf01468677206a82ab72bb9252f29ea07ade0d739ce4e7fcb21aea0f7317426fa41ff1016caeb24bac72116baa50019737c6f8b1172ef21f4fc199ea64b4b89eda02c35538fa30b105692c5e58774beacd79948d054a9bbf8d4316bedf8ea21031179c4c0739926ba66bf010452f7407bcc8e35e6204d410b63fed2e8691b3512f8ead9442b57c252b72bd38b42e089a5172395ec300c22f9e6f946ca2ee56566f145423cb4a7e28d233fe4d7d98066ac3589ba46adabb9d96accb3d81422c83f55b6620bfd3a11d00723e935bb4611ed3a8b2c86bc3764ca3bf2afef547dffe80852bfbe42d7bf49e0103b9b709c4e15df3356a675faf3c4c71c8458070cf8025578ddb5bce2de23572eca3fdb1d5bc063d391c9798368abee84b163172b20cbbeed2c1f751e79130720842c6a0ebdd5e4f55e01468f74e1c7eac8375419071cf2ac8f16d1ecfce9c72eaba8c58e3f25dcd6397079a0cb2ba6cca7c0a064b074b7caf6540298383a6381754d949007e30e9109e0bbd164a300de487ad7bf4334d791a20a7d1f0018f1cbde54ac9d9b3ca14e036e28e60664cbdebf95c57a89e77c3590785fe2cc8eb3ca2065fe93bfc400eef0fd6118624500e8eeb6169eafc786f98f3b7ef9c69335d9772001080c36a5d733a5e8389f1f199b76f482607dd57ccf8fe5b0de7c15172f9f1bc2bcfb1cc3b6a4d22000f7c4eb777ab9f8a95f197c492432783cf84b872028447a4b84715f29afc11de221a368c451d9320700e74b91d1d3d858345ba71d1068f535730f360f433262e0059783f1046440710128582a3aab7116759fd212e7d811f2a3e6c1d106dc8cbd8b549721dac4713666b04888ebf702e39cbf172ab9c19ccb1c9b3cc14ec3a7c5dab9ebb215665f49b14da3f4138939c9e1e947274052ff9fb43e70174a5583561b9eebe0177fceeff417dd78682d527af6eb61bf6049a7a17038e1ca75e3f034f02a1938d89283988f92db490aa0a6efa13b66cbc1d8300cdda080034435a66a77517d2bc7f023b45c0f77bacf30f2b6b5b2560857b4c6ac14b21fd26f4297eb9d76dfd6f932bfa62f8ac1d009927b7cacd357264ffb29f20c55b29e37b5abe3375432945e885360a94ab227c5d276e77a0657246493f27d33b9d27e72a8ae6ee9c840e33a508a4a14b81b6ff1644488fd66f32a912868cebd30fb6829511958fd6bacd361d165fb5dd2233becb9e983a0b567270fb09b4f511aa3840c8c86d8aa12f58e30338a40bac646528aa13362566ab72622b254aaf14b0406678e65c4adf182aecd589e53e69a5d25ec040cbd7696a724f481871ee0080cd6e2c9aa89bc3d95e4ea63fd973e81b054de332420119fa34914e7086098bfc66fa88e03ba8980aadd8825b3741062f6a65a627c33feefc2788e542a0312636f1315e52fe0c1b9d35f45383d2cf9f3b0907683d14e08aaa7255eaa2d0961ed2efff0d4c8ab2d24cd4211c6566d5bbf451ee168a108d547d722d33218760a30ebe065ad153d1e4f85ec15b469d488423396078d61a5fcdca677029a2c8aeec1b51e90916a63df1d22ddd979f05487a8f3107ec6bc789792e3da1554390a1aa700f864748facdccf43977142f208c09ac1755a720b3b05eda723b67cbc1b1a0cc8ac3cf2b97383ebff55bce71ee753d846f82ee2ed51f5dca729957104331cd0ba86364db3c6adea89d0b630260a304d3d3c4d326004dc71b72cc2acf2eed406343eb69be5ee13ee66efc67ca80c5b6a457421b94dcbb5c17249dff30e59394d95553c17db380e39b3e77cad8fbf3b1e2e247ac3263c6ea702b2e15d32fa3ca2f210ba3eeaeec91319037f16946cde886a1a2addbe8804dd4102e39a11fbe0d84d3ac5edc73f59c3f765cf56a8174bee21d04e1035b980699724d582574311ca31e14e1cf0f3fdf60489de8f3f0b4d6d5fbd4e9ebee32729272de2d7edc6e786a88570947b2eff66a6a5c7be269e1cc34bb597e983553ed3d36512f85f8fabbf883a20e9a0de5f5a93c5b69e86920b158831b324d4db208b25356a104beeb68c0c27a5eb183c4aa8b5b8f2d48cd7c6b3dae6191e834afa27f17013f3d0b77756e0ce9de7b3c0bd034fe9cebb849afb93525e6f0e8cb8c3ba072325f1a015f06ca45c46834fb8dbcb65d3f9d82a62b98990e6add10cd93fb2572c191cd5dc76d30777c815e98b7b30893615cacc287771e8878ada4648e0c8472221d366510630eeb1a8ce2a9f09e66dc88a431aaa72d2951a297e6e485182a5126177768dda43273fbea62b1a9712af3cdcf4e2bc0f74edc27322c28520d7572961c5dafce878d62b1303311c1766e92d7d1b91a129dd59f12dd85b794cda3689d0fedf914c94affb2036e1fd31ef4286590c84a7c60de17de6de0d4d1daa572193c639db1f1b72d02f7fb8f3fe318e82870a6665cc8f4b7a7c608226746ca115cebe928e3ff537db8564fcc74c065c8b99377cb4302014c966bfef4451a2f72dec70eb2adaf65862e5a4cc4c6139fe637ce2ae05b93c85d7ad0f13e1109df72c30bffb4e3e45a0cb83bf23aaa8ec4c82dd50b5bea07f3329e5dfaca00e38d7299ba30aea8c370378aae5e7378968f1cbf84c0f0ad60db8431266a98890f41720ecf31283023c43f7ac3cceba7a9505c1ac29ad7a0773b088999c1eb46bb9b6f44c3441c0d1bd9e68ed14188a9db38c7275f14fd2e371f0692efba21204c1c260d65ba49377a94b9d5a18250572d79c8185f7ce77cba2d52858489a7881ce4720fc09d77dc63767e593ffd5f7caca8f490c98d17fcc558a206f121d7a398d7721f14868fa281ef53bb0792bc19a90a93acd002515016ccf6a364a6cee246e16e9992b0e61e8b8e1517df7f52814e3e900690a86c830bd07661ace99575e9e772a644f8fefa48bf7ad6ee0636e52df44ed154d8b4e923bf32a7b67eda6dd2cf727cf95b7ee1719db16723972b191b8c8ce92c727120b5f4b006c4b80ea2784c72fc6e10272ec40960f6f3c2906db6d01c5b5879396dc350c7c63a7d6eaf563a727186128ba13155b7ef7abf4ced95f2ec3d423dc8754757b37ff92c4d4ddcc4510c6f6c31e0194d613598b9838bd60656d6b14058ed1c0672ec6d51a1d463397247c926b9a9203aaf3b090e54cd30e2fc9bd93fc7d3a6cc4ae6ecc27ad19a5a30f19418dbb50205c68773293a0bc71930e7d082ca92fbdb9636d344f01e0ab852c7c93fab584168910d391cfb6af9bcdc35587419b482251a29c95d4e888a33728732040b527220c7b827bba922b64a53b5e6a776ab0ca206f6f310fb824b9c444c76702aa68038a6391927ab7534a80d1e58c2711132252ec81afd1e5b257d7279dd4efa7fe294b0cc286effccf9802a05961598df0bdd214b33e100db1d5b1cbe188d4c4f4d64478215f2d954a7f7e504b90d63b9d4cd35e4d8915ec6d63f7261f4bb76a9788f55f965e339fb8cf312f880eb275ab2f53fd312797a21aff3408bc7cad8df5f78477bba7d99a5925850aa6b109750e9567dd82886c5e05d6472db6acd362b7437fb4cce3ebc112ab7d1e4abb834b37b37bb1dca2040cb3f3c726c12bbea54856d94c1d657cf92f4b442b946f1c7c881a4a367640e7777c14472bb1e3d123e8d737ebf5ca985e3bf9eafeb5bd5d57a38335cf836b0ad0cc9dc72c06c8fd05736a04d4a4384599694cc55bce9ae9bf42f1884397b1ae1a9c0647256ee5031da96c8740b2f214a0260470dadc778650f064baf83362b3faae7696a66b1ce700bf821cf33ac7976e8d45b9abb4d92e79935c06d27ebfeb02580f4721bef8d9065ed10e068eddac70715754096b81a5d32b1ed009c07bb1e16fff1723fdcce3a21e111a1d52a166e3a7643cf22bdacf087067d9fdc9571d33fb24072183f9a43e0d3dd540e5140b2ff5ca6ab2e0f5f1313a1854a930d903d88c798727641f9e25f40602b39bcea0d04b88b8a8449cd12a812ddf051fc0f9acc868d243d89098fb5555ed62f32c50d72109d8b3eb1b898af340de4eb9309b1f196bb721cd3be3c98c0e4478ff1449f5464b5a02c1852a921c0a84826e2e9956235d872dd23c1416f6151979ab90022b219dcc2c85c7d6c73137f2301775f8ffd678a6cef596ff1f9cdd93121fa2c932ce35c5dd9bf47fee96d1e47fc7699ad2f73ed724b1b0bb6cfc8cf440e099ab4d9096f3031507af666b077b329893cf278727156de3ac7196a60d3c8591bcdecd90c5238263bbb80c148bfdfcdac7f1a2870537287f33d4177ff56a46cd690684be5872ed1260c49f006d9fe86468fecd3f2cf5321807d51d219d260e2fc8e8046e6d1515af2be384743c237796002209115487225146647840422166aa6409f15fd1021edd8e0553266ec59d21cd2a1b922fb4237e086bf3f78f2a2d401c71bfb67d0acb5f0976551cd569fa2f6bd236ee0ad5c208c0501c8c5f9ade0de31b41b2807b86f1487462946cd841e2db4d36f6da02f5a593d77d1d046a3002e1d218943e0859d569bdce44a670c15981ad57f477272ba4d1b01380e8d85a9d14f7dd79c941498ed445c657cbb7dfe3588ca02fd621aeaa341f79d713bea01e6042f87490b28698169b9c5ad14d7318f540c9140345774ec2e6067dd32820b8cfa486b3759e00f91bbdc7940a00cfb3690ef52eeb73012412fe616479d35caabd14ede2388ef59c8e415d4df1bba116bded54efec118c08a86364aac50006e665aa039e6b5c6ef1d2a30e54318cc25c1ad551c4dfd72ab4ea2504793f50aa63be7bb09f16437572ac2079d0d79eedfaba8e50d4bb672288800ed01b5aa0ea6d0c1420308222fda6bf30dfc5d639990f90a8fcaaf486650365b1d71e87da1dd7e7f5953ea3ff04986d950827d1ea5e1f1cecaa5db8245ce03e20739c9de4ccd75cca9dbf0d1131194bab3ba02023f77e010691e24e0724682aca806f589e1e8fe6db2def5de0e925ede605288695388095b3461e49d72fe166db21cbf271501dbbd7919f557043695262dbe8ccdf2c92171c7eedce35e901e1cea9a566399a5760a9edd2939ac9150368bf390e7058d369aaed4a7a9724caaabc7aae3953815be7990ee7ce4ee74d4121566e51b5a744a3ac067a88e72f16c8a1d4c4a16322c2ebb1963412220529e6a1054fc7eeffdbb9bac5a46ae72455154ffac7abf75e6e50739f11c2e0b7330bc4ebb887255056e0bb898c5402999d8c3c9da7189890f47481829a6c34fe269fd6461bdcfc9a4700444225948726d955d63ec39c9e493cf81f52396bbc7f6ac845c92498a27e1d2e9487868c03ce8a6b187d72d3ccb940a2fcf5bc39c888d6ca7f3f6fb61f4250df468a1ecbc15322efb2c33c3739822937eea17cda21e69c2fc0e739d6c308be3e8346beea6455a2550af8d0bc852f97459f58ad208988da6bf0b7856247f0294107d79307e521fd4dc395672676b825915c283ee52d6e4602d4a5ab968fb8990997c69f9fd72000b88b961b9c0d63627bee2611e007ede05b64164628e37f7b4b12f4d1ed8721f6ca64e02a2753115f541b9f08a0859c1ddd8a8eb80d107f361615dfecffc72b89cc60245f94f6aa66dec9d23141e560bcb53bf0a4fcfb601b78ebc590dbb72c4431ed45290e9c3d8ff43725579b9d083eecf0611dc59fd519c9acfcc887f13f133a69a27f0b37702d72dbf4e25fd91cfcf3828de4423d53aee8e6fbd8ab502da65ae97c32d49c836a896163be7145b624f97877e857bd9f8afddda3cd0e63c959dfcf892deb7abf2d848f8759b1b955f001e8923c23573909d12dbce2b4172f9f68d4f87ed34d1739d2401a0215800feee65804117c7e84161253bff340372eccb31061f467a97fdb1969624a6c81211e3d4989ec2bd9701c09f026471855bfa9cd0b486c3692bcd386663bbc12641a5cb013de44b09f1b320a2c7a91e635f1b1211d7003f326bcd11a95fc493f85a2eac8fd9fc01bf7a9e482051c370ae5e37adbfd8c6fc2c1e2dd042e582305894bfbffd50c1e277b2d7bed0a82ffe613a59e75e1e9baa5460b5f45eaae795401934e41419a4ce6c70369702c13e5aec720a4c4bae13716c53af3a34aa81e8cc0b21746537904f13379bf506dc7d69a072d573d46af1c6ae1d63f26075d0da1589043ff245a0bf89cef670e9a7a998d3133e64b876ba9273bb667e0c6d7ee9c21d56c49e5c0f0e30541a451279b3d46372ee198201773c191ba12d21e4025538bd999fab1a90e4ebcf3bd383726567d17225ec9d79dd33673018dd98c6b2a97603440d5f61d528ba0a57b6bf14ab4e2726d47bfbd0f3215c47695d761ec9b1311451e467c826e46c9ce644ca89f6de575f9d8b4106cbde5d30783df2cdcfb16c910c55f874744e97bad4550b13e7c02e721485524f08ff8f4f5628dbc695e858f8180c968421e949ec0579b89c44e9e972bf23cfdbb25509fc5dbb54b3675c1b1c80eed44d6d20620a7a0b95664c2336724bbd32daeb5535f4eee8c02ed3bf40161e41dc900c94e1997e35c85024d4c172d334ac44846dd1a08e395ca92a997bca4ac12fbc6a20abb2e630f2b8c5c4a809435ef3b94ef09de5db48d3bd55380afd8d46dcd15f2452b98426a48d52d8b87286d8938e455f0916009ef11e65da0f357682424bfd379f82f7f5cd126169341b128a8c95f2e7e365c89cc728906d4314156f193f46daa40c7f735d7a5d590a445b9de3bce28cafccf2ce4fd13b934c0967d791adedd12ef7512d046d74e322228a141f289ceadae9341eb2da7bfdf1f693f1464a5d6afd0b48fb7009592d144ea00ca501ddeca4915ef367b18fec3289095295355726d252ae2686eff8171634933f33c460cc4c17cbe4d66600e1e0514ad912ae7ce9552cf5a1a63e6d0821724cca44d2d2465c27a4b152d25dd08aca74a30948870ea71853f92476dd9f7b3b51c0513dfefa5175d8359c98fa8d3deed2c821659bdd9725ec1b000677bc9572903ca46afe663bbfd97080d8384cd90d415b9ba0b9c513ed8c8793caee7eb542ace2f4e04b47da558fa8c91a33035e7f3313732ec8564ad187de4de35053c6724a286e065b7be3a0d4151468c703271f8a05f1f333dde6f16272e3dda6c83f72fda3a1ab1e6b4c7abd1beed937da143a13e4bd2de37d9d119f329e4d77a58372484603cef01b6bf25258de5c3d52955494e40b26583417763effe716143f6d72bee5cd35dff8cd40cfa89de0e72cbd3e6aa0a6fa0064436ce5d54c451eb51e7247b2a778b484634ef492f7abf3ebc7be1f40f52351bef9a8fe61ddbdbf130d727a5c8c0795e8159ee98890cb5db45e5f1721132c856c1541478be4b20f050272387a3566918f64b725b015672c3a8e435f9b21026b9302f53b4dfd5daf46d272533ed4efe7914422cb188735f27c452055ea82a9fc022f4d2f099b5e8d32891333cc71db85d19da0476a5d8c6d2b7d5e5cf3cd5b19e11bae1d806bb90d9667528b54b0285385344a3fb43f865639444bd8caa67c2b230891530c83862514727242de5efdae4f63650c7914d1907c4227357324bf66a262587d6658ae28234d7232db4bc69ddf19a0503e57c9388e0d8ec27782eef032f0c4aeffc2116bcc5472583a4b016e54049eab535e43a7f2b563b86adf0acda8476d55dff98fc886815778138bf20793472c4287b9973e5786d9dacaa0f58c3d2b318e241eecda4f5372f56242ae4b05f676cf9c33fe9a450a989896f44bbadc0efecc392407c039a472b3b16ca9c524e283df96f49ffc2ca38efbd763bb239feb96e43adb0951b8a072e92613c660c743199ae1a6d7945415a8ee471106938acf062291dd4132a7c20719491217296d1bc3cf84f16488e6e43f2d2e4833ec35062e0748a6594b907072ba9dbe0042085cabc6bdd4258847c502858be19448cc28119bd9915d67299f72d8fd64abb4e54a8e95330dd600f01b398bb08d4ed63ec600be8e466039b5e5726bab45a3bfdafbba25615ceb9c4b8cccb9f47ea27595a667bad3e249a2a84672ce04360345f7c7c289dd3f652df249f947667702b399855d0f0dcd433d1db372cef2373838473e35d90df1b5bd1ffe8ac3529bd846505d654642c55259b96672569979b3a984fb66b8a7d675f840227a0dba60c8e527e57e41c656cdf0a66c7244cbfbea40424941aa5b33941cde98775146ac02d3c471bdbe3230ea4beb37724850851267b9dcf043980663d5a60c7351eb3de4829087ab12eebe1906440772d20cd385edfa995d02807ed17ef9cabf58b56a5d0af76b3b8c72f84c2cc86f7277cc62bf7a8fd762b8b0ebb21ce484c212c9ff545eccc08e5a3c44417111722b5198aa81122bacd324ab3944f3ada04991cca0d69eb45da9b609cc28580daa487a84537ebfb7f28733b6c6c5c3347a058c24a9ae9a4d0ba1203b96a9585d2e7258316c31d7055f6c006a43d92e34b80eaace02e39a620f14d99167d0e607a722590476f7dbfaf046a84ce71a0a5159012c0ee25ba6a67bfca28bb09116a5571ec966e3e937a7dcdbc07fa24c8f3ed22a0f90804d2475258455b98c72be7aa766c94482cdbbe6c254116219675dd48ab3b15c6aabd449712b0d8e0033d9b81b24d892534e3e117efc9c20cf92cc527826c6284b7b494bc526f789085caed87572ee3f0e06c5d1b58eb3572576c69bc1fea08baba8720cc9b98ac7ed204d76c572d8fac99e77e3c0880e23145ad3b57a5913d48195c7493de8f2fd8bb6c090a8728a97bbff6caf3bb94b9799715c65d70a78e5531c75d88a778ffd95377927ca72ad7e9a5ddae0c56f3d40a5dc2449ab001d2f500d203bc9adee5a9a3b6e9c65728d08ad03ee7626401106b5a4cf5e994f23dcae0f7389afbe344984fb5f097b518a9ffbfd1eff8950f3edb75f05727625fa274ebdd95cf88961a7f7454239cd72923b2f69449b20f2a5ce234f10530615ce8392c9289b96e0e184095b2fe61c72302e1f810d6202883047c4b7466993552524fd698f7d3ebe5940f71482c98e72fc2b90d14e6915c640742ec2ce8e36e1fe198d7d1f015ddc4b748aed872a6268fa094ecdbce96482600259ec0febca082d93a4c9efcb9e6d3f06ab7207722072c0921c20775e2ca3a8d6344e888931fd8d33ead84287b97086c6889b100a5f72ef653ccbf8994b47f0dbd4b9942ae169bc090dffcde6661b5e7ed589c26f5114d68d70ec3462c8a40d9bf26fa4abc618260ef2232dfeb762d67f68526e052e2270decb3aed49d4b7df1533434c19c8c133723e5d71bf35e21e3059205d3e1d1e2711bb3f2b2100e94cf4d65a578aaf6044a0789451a150d7dd467ca6db6cb972c136f7859ed3d8cc754394a9e3dd4d68b1d502d8f547c862baeca170a2762e3833cece1da40ebbef5e7be36dbaf2ddb2cd325b72fc39013a116aa5c6a8b11e72b11097427e8ccd3f8aaee13c0f940f322e58ed7968908f935ff7d2d5310dc872b7cc6ed05255ebcfeea525a4fde34fb8146ea213602667d6c1eb394499c956329d434d47f3b1d87ba5152ce99b0bdb8b621e9c41b69f596b2db35fd4c077bf72a58f4cb4a9496c65e661a976bf36b6f4d5844b77d752e117b19b107a1472cb5bf48de3b4ccb3ea4f48f5dc621d59da48bbf7f554bfed38ae0c5fbc19e1ff105c94f6a092a3f164ff37f4e96d3851f65de7a4ee52a15a8f099fd2d3737b800972b4b4a22ccce07e47db3f2d24ac37516e827fae8c2952a1ab7afc85fea368c27263820b5751c002e8bc977fc21090bff71808a510102729bc36c7067d404a1572843ed78c3f8b74183208f5dc832eaff908a588a6b1edd6c91245ef0ac763b87285ed239f71d9cb9d74b74f4bc1148ead22725cc073e7a3e97c259ebe329dcc68307ecdd8075178e20d495c0c209065540b02b783966b23cd27ca79fdca33435a9b752c467d1354f12f2f19a13e792307033c485d32390f91eacdfd0e29521003f8185d85990a13535151dd02a024b34dd537a61d351d3a55dd640601cb255a6192634fc2b6bc8c71f5597a2e1f2b50cf6d11fc4c5e07c1e015aa02bdb6184172c90c7b4b24ea2d44fe6853eed6747ab5224e835d214dbc19d692a2783af97860d6257233cd538e789c6a95ebf90816b5f4bce7b9b67ddf0a1cbf65c6bbfbfb6be5cb948b83de0afd5913d4cdf0c984a3af8b8b9263043ccedce02bdceab50772fba2599fea35659ab438505bb522fd462961654adc4cc65dcdf98d12b07e5855d53c39511b60877e6b749963a3cd5d12aadaadb4c8fc19985e78eb8ced326913d4eea3e0c37617f3cc84730b8a7b468cd4385af47872534cf305069aaf3bd26b05f294c69931b1121c56dac3d8df12ef3c486b76cc579c85bd55f0a3f88ca1721c5e27ac827ccc3a67e7829297b56786ea6d5a383319508b582bd34d5bd03a722988f9db45ce9836d464bd9797f31e1bea05a584fad279d66bede9a965c57446e3de65315973fc561421c4156d0bf7fa2e0e4a627caaa2c0b02d1c89fffae5726e309a7c15afbcb49e9c144f83fb6ddb859b17ac13aad1c4e30ac5c77a868172ba3a4ec93a312b6b7d1fd7613b601464d364e1bfbc58ddd7b556bf0e474a56128f8c4d0b3650fd2f6c057f3029cc27ae419e4f5acb3083cfe581185e4d5a87514edec40bf4ca642304bca20ff09a70207cf9ed39831e459af48298b24c102472084890943f9ef7e5c6500f39bb7182272500706a706c85ad8afda30a2a21066e96225351c663c61984edd768664dc946fc2f76014fbb9e668c860d72785eae72ce74e760ee556701bff75218e951d6c1c6474edb06082257d4b5c9b89c341872ed23fbf6f5b526572698fa19fa718bfec2f11f4a38699535ab63fcf3ec677b72d52224d56d66f034594592f34976a25df088dbec61feede82c08e5e7f6d06f7222347c39d3af5a0c55dfe9f727648f33e9f13cf30e8f38ea055f969983a5004a592c94d02e3ec76c31cae30ba8f45567d04a9ec435a2dcb342f80776bf65de724af20ef0e3f20e475c9fdab6fc49129117fb88ce738532ef5ca62a32bf9adf72772eb96b44d0e91db5bc8f5b5b5946f94abb2afcd3191fe900e4d20098feae721072b257bc151f4423ba06fcb50308f1a97c3d0d4291e79514e0ed6bad4f3d0f37b2aa44b22a6296c931c2688733df162aeebb82dec203e2c06da7755c6ac21d4a093990db8422e6d1ea7a8553b74432dd447868d7e29ba6dd95902cab41a70ad2b0b3e0bf41a74021f8ea8e26cdb31ef89acdb8eda81c36e07db9ece2c6ba72850698d7361de1fe720ed8388e104564e1c5d22e9bad5588f703bbd25453b97221d0dac1f3f4db38fdd0eb23b838b7e5e35e1aaf0482128e6309c64b023d467279398c1083d619f987209604b8a24f784906d7584773b51ea61c85e4573b9c4e9a8eb1420d14b8b3983c5e74badaef1836055ef20ba4c337cc4475e69cdbcc3a72506e7a5fcfa033029d28e91af757de7b870e266bfaa87aace90420c805ae6931048c20db8f0482375be0d398c03261f7b7e9e7e9747f4466e195583f7d790bec3ef6521d9833b00e526ef8675e45dbc858dafbbb294fc57df3f4294c303672bcb0cb94463dbd53e745a7749f7b675b4f311261d65bf1b0231fdcb7b564a00905a4cb7d5369897cb3e8cae04d6b5342bfb40d524b55eb182a116abe1fe639261392eaff96d056937dde0e9dd9cc504f146f9fb5b32baf61515f1fb8ac211e1b578722935204ee315a2434c3cb8135b3c30e70a296e12b32b6abed64be04367211ae5c6d7a08b5c2c366f20c3dda1bd70cab8fa3cbf9fb18249de5e15845e8728ae6511d5c71fd82b13eaeb4cadac116e6eae71f4f9ae707b92c80a88c09ab720458d1f6f6e27473a8f19737e1ec639bdb6ea34fb1e6b3c414c84fd3df262c7227b7fbcbe9e3061b404869dd2df9b0a8e26d997d864d1876aa44448d3f89d112b91cd33f07b073f54d7d20b1ffb34315e0e987f760e27378b0aa170f7b3d440fa2e1bbbb2c3a5e731d71e479c49e4ef1c205fc576c139652d133d88f534de2729dc19f9a61d7736b5ff7cb27b077bf576803736e6e547341e7c39a20d47151591ec723248913b2e67644fd64ae54b8b205cb9444154ff424dbcb0505acceb3726b752cc5b60ee93cb828f54a2c9c96d75fdfdc0b723bf72252cdc27235a16e09fc07f5dc30634210ce3d27cd894b0036102c96e31e59bd40d1c99a09eba5a84af71ae8798f5afd4a0446737e9acad8fdfc7347c8508e013b8a67edb7e0d91172a566242d888d01f2ddb4f2ea6a43763e5f2e6e188f105964da8d3a0b93fc7972e7fd82d2be917bea9aa5f33cf5dd5490e6535b913007b8c358808afe3996d7408cb5eb957fb79baad07a49bdeed8c02ceeb4424ca9d785ebd0ef8e0f0b697f72b265fb13c26a527d083b2455b9461614270659ea98c005b65c8a7acf727b2a72c823679f90f56efd34811618048609b56b9f0a9ead99b83f220bd081f5e79b727f6dbb03e02993a31262f5bd2988d39408851d26a8ed30a4d15b7eda229d9e07462b226d91a9a648f32e0590927c6c0442cb3f0a073612c5aacfc21948813172644ae0c71d6d8e9cec5212733bce6d3709a63831632a002b81c6f55a5627867283365142d1a2a14b597c52eca4ec88d6ef16735de1c2ab5c9a930d690c7ca872b410b7754f64de57a1e31fc3d1a8a12a21150686d37b71b87adc06a6ddc25772e7c8d47203f4c8dae7ac00b390aa2da1d2fe53bb0d59f71ae07088298847675100df455625428d73ab107dc7fc03b21b42de99abe27fffaa053610e6c7217a72c279ceb16b32379cbf1ed9cdacdefc7b4a68061b5cf13f2eaed6d11c8988af0a582dc7ec33267f38adabc107129c02e42ce1811b155ffde80b06d205c1dd733fa480767c0007cabed1a3e3bf2eafd04ab2b59816b534c3f7e13c7d133f392d720691fad19c46565434c7de957b4e2dffd943f68206bca1e5bdd73071c1c37e062bb4dde23b95fc4925ba1bdb2d428d78dba1bdaf4782c8c8c7e16af38700b655980727464c0b156261fdfacbee4f136a92fba7e7a8a5946445453e1b17d2ed136c3086a5a68cc64d012d0fc64d2c0eb8ec09cd57877460c99eacd1ae494e15727d7c7b77d87e32cfa66ddf9366e7f6f6f278224a577c7cb54c510e7402c89172016a588c871e14f3faa9276d31851da7fddae58d40e4a7ea17aa0ede8e855e1d7c36408de65f303b0481b9b0d82c95b0f945d77d1b7ceb3df9aaf18ac7e453635752624ee531e85bf51980648c796198f3d5b7e802bf565e1826622ad7eba54a312ca628f7114055ff1a29d8300625fe0d7a285e847cd6a85699dc9df268f17292edb907cc429d590094c6695af843b6671e9688172600de5809e30fb447b5400d8abafb0a360e9c65051f3c33781903d268d333e54c2295d54c678ed6c2d533649728d1a747f3a1d16e3e081516a231fefa37a1134bde501fcde73030b2ee7299ab906b322e2b6d595d056319c6f6a3cca189fcbd7d9ea3c9a8a616b5ef5f04f66d228f5f72cca3a697cf814eec8aadc6ca3c6967bcb29f9f72c73fbab760724e7b37f4d2f8c41059de4a43a1e230aaefb14447e6f2a5dc4ded0aa3fb54fe32de1f18a12339d36bb562df71245ebc9a8443985d34348d401da4b3bc53d574723a4f75d73754cc34ce66fc79bc62407d7f23cf9f0f59d1f606c8c2674ad5a2720d8b17d715105273532a7f195cf4797200e9244ce7ee5acecdd3ac0542abd4643455f51f3e6a86dcb04d9cedf28d2be645577926b2cd2a9c48db31f99994437220198a27523427dc254fd3f67d73e83877359939ffdb28b981f5d5b56a8978721626c12c87ba66a2eab8c4c62bceadcc5124c0a3512f5ec66442cd27faa52572d381858b49f9c9531beacb26b61d4e6c78076b3c767e78d1a23ec44090d224721e40314a12855c9c98b4670a3365a7f7d98ce3481a8be155536b32a0e33a8d721eccc98e8f35d8dbab14d7643be98ddefbc3cea82637993a14734e2e2d31dd22df0c41cfc52fb8ee64dd0ee7405c9d429151a52033f93f320409f46c38471c72cd8d31074520c19fb9699e834ac6b53993369d28199bb10c66567effd2eee519c3656c3502fe8ada7d63f7a9711c71fa942bcc5af8bbed62dde1e9eb6fc3e272a2082a654fde49fedda6ac7948d9cba593559ec07f1166af5fa90bc32e8d9b162fc2dcf94f543845ae11b3bc3e2582cf6edcdec6740f32d69a3eafb43d63296fa4de4d5d7e118e1ecf23e063357aba5bd9f597a326b11e627eb1bbcdd55b4b72003a8abd86fb00d44a96e32b8df284ba25b7c1c86328471aeec7b628be69b772f83b026f87b8c52975ab355baae7dbe5ffe8ab20a44414b56fdda19b01822f72e1e96f833ae1c8b8b3cabd6ae4497583b237d82d1a35e9e03d030465de1c1724693a52f913a4f15c1a7d282e4ee30a9ebba53fc00b18eccf47db70c4328d75208e5b1274eb7c512ff15b01f8befc9423f035d276d2c9696d3257788e93947b0a50d1605b89b08b01b1f38778afe510fb42e58660e214c9280e2671ef8abcdc72810ba12fa790b1d6cf1f8736a2065da2c5265f6e07e88f25a479017e8550747286d5e418fc1754fcb98e6639ebd67c78299a3a3b4821bec7edd5b30551a3de729547a9117f3444edf32fc55f2b97a208f66e1a9957f9da294e2bb1158906a9728d5b24d1611586ba17db65ebf9235f0eaf6aca5d7907a5562c7145290a056c00cd40c954643d050ee59551ccbe9613aa1d784cb86ebfb33e24e9f07a6943266d902a2103491da79b8c6d3524d4e6f917af2b61d48a85a9e5d5b1a964aeaad91ce0a0acf549fcc3ddad5887c2a0b51c3f5e08e2ce774fb1eabb41fad0ae0fdc72c3dc5259af33564ed1243b36a7e6662e19200856002dc8300a026398f03e487264159c9f83811bf3c59f262ba31a325136b8e099279d9f2b04ad9841e6f84372159a60bcdb5740a719f447f3fdb016ef61962ebe2ef4cbbf39244e85b018854c6da00b7b2c7a57a6b529317ab01adf4efde5963d9d6a9c059ed968f08d3d6709c8ecc0a929173d9afa2077ec37d06cc6e65e7cf914acc452b7a838e0eeb4003772120cd8e429bff0b167cc36f98ccd25efecfa9a3904380f9d688594406da85c0c10503948f0f76d14013a8f33feccfeecd60ef511534f5be94e3ad03da82d6f9ab0a51f4ea44ccbbb53652c00ccd4cce6809b77095d556960fe2d8437135b727d27c89e59e2c5c3b65aa7a919750e13347f1b316df88051f0b5fdcd6e2e047247bc9fef1826cc3b76afe1a175122c63f26f0f70d642d12660aa76e2699a8344e08a42e377aabc43d4510e10bb7c1d01968e36192724e1d27ef10525472175726693a25a385bb07942cb753df9fc10aff82d1a631e0867c56f334dcd17add0373d37f6581c6c026b8eeed31e8b0d4ea6ed1e5d62f09cdb9964f8d86d8255c515aa1cea4dd30d89daf9d7fd4764c7e888ec7832738e5dd797d1a57d4361fbdf725754ea39e6301c97952b94d27958f0a90ee2dd3552d30fed725ffe24d58eab72781e3de6ea820c3cd5b5324d6e5d21ad5797cfb122561afa69e965295f814f72c844e2b4a7b790d8b79d713950888c50d0bb4b2ff71e22a3e5e67d58a2881472e03152974ec4ef62368db5f8c813a943f4654d94aabf2a7058d2116622e4bd216c254ade13a8ba2f88ccf3dd8f0c2b6b52a50db4b738a73deba97d36f8433345e20c6768f4f238f4037b28887eed5917b1d7fa318caefd639b72adba8338b072ce558d34c914944d5cf05de497049f3f83a5c15f265dbb9376538004e690d17209cd4170d7ff3fee42e54490390d2264f4214ce6668ff2b227c030429b649863cb6c76fb7974f26191bc25365ff07aea573ab69fdc6951b1d14ed43b9604ad721da1a125e714d246e1e5bebb0d92f9f63993d6b71c8c9b38a227d4f5353b9263905230b0e36bdc50f9cc89efcf7fba9b4b0309753fa5825c4686fce0ecef793bf9871764dccc5695132ed5cdc556d5a670025363c613c44d0274d1e07b40c84003c42df9599feafb3d08acd0989eec585925c210e75d1693e8a35ea1aa71873ced667b91271b1071808d17a56389017a5525048d04cc8312568e92f08b4f11727bf5eb102f8343ef8eb97add84266d4a288e9e2a27c3b0243fbb78d208a24e07c02b1d91f50cecd9f3bc809dab16f07d38da1c426a56a932bf6622c54ae559511c99dce6ddc923896787ac1281ef8b4993ac21cb6dd67707a0bd670b0a77232a5dc5aa0260f4bcf468ef30f60ed6ab474f87c6b9153621392e2414d9d813837204c079430cd350484a9e48db7a86982f5f41eb134e425359327dcc74aaf4f8280f9cfcf45041703c4817e48e3e9360832e418450aacbf5ded3d7846243fa50728d8f5b9d956fb6d356fac49b216e12d56eab0ea2a11fc006505f60f6e57f2856db7fbf8cde121c8421c1024b815f99f4c92370a256035911022be236c0de8f724844b36967e8aae0da4f183a778493fa99ccdae977a2351286f37579bb326e729e67978e8c593be5035686d1cc6ce131fa772e0e537d5e13950222afccee5272354bd246a6d8695e48d87ed37eb164a74af33c9d77919edf27bcb144a029de3a9840305f846f30c9a34bef14bd4916484be11c75ea0478c65e55c7aab1c3e872eb4e040ed4ae52002326de5c240947ee0f06ef73635f1f59b54bed4f2180e403767112260e37acd4ee6cf05d98b58d0f6c1ed36e72e45d590fc50269a721fc7272cc9e64ea0c90256f414a8386e1f22fb7e235579404a332cd30408e02300f7212d26d95bf86d40990e7ecfb4fbcbf2cebe7e23a1016751366da47b821533d1ca88af6245285df488908331e9060f000230aa8f14518e3101fe4410c6294503852d0818b3c874a117e2bfc39a2ed5e531a44b835bcaefd0c744a88386d12087271559e6b7da9b3deabb9aee71ba10a5bc6324d43850cbae5f24162dfaf05d94832744638f51e056ed8ae201a83e29a93da8fe70a440b7e51665ee169303b16381e98f725a0f55ec43d233dde0d20e9076b94f4cf3b5037f0357dbe97679d094b916ab49208c78dfa9973c8315b91d67fd8a6d5b9bba09a259fa7ee18107a1d17d3b14676ed9093302612e30ee54a1a9e7751669eae34903d7e6f5a7e35f1f1659a7ebb16e9981c3cc36348861aae0f32e0594cf9daea622bf43cb5d6864a2172668b341c6951f7d42f3c397edcb5a87bf02a37fb7051a8ffa0275d67056f2821ef113f75f5eef624062e91a9f77b45cc6ff6589a090f51e05d5c07c08936b023af3a7933434f5179f4346fd7f7e3d625d085b4faf3bace05b414fed50f39fe451e2d7bbee82b976affa8bff1f14233c8a90f007bab954edb67ec11e4076cc601f6c055a0c77ad6d01f5dedb7e03ad8ddd308ba83b3dfd08df11704a59ef47244761d99ca1c56e20eb4a5d9aa2b0ff8c9ae8bef614bcec9645525cb3e7504c172d19c4ba017238662d61687c7dff6fbef5630916027bd6b837fc638630157c77253a49324ce71edfd94a8c9c22ba264c0064df7555d2c3ed638e52b87c98024666e2928e2cc7080077d25b411d0cde8aac14af8f78e2b9570f12cc74420ef79727952f414f6f54c652deebcbf4929e5a22e09905d9ceb8f8bb04cee1c289957683229117770d550e65135b0262187f57f3340952fb92b29d79fb0f89cdcf49a72469697d30dbd84af6feb9420f0e687cd9abcd94f7258c5b2b15e1eb50c092472c6d112d9f35c2251957861e56a6d90cf3b0f78d925e658f9ee667f8cc5b29e72c580bc514b159dcdbc9e3396f14324f66bd6acc3a42348cf73dad6de583d874ce07acd1e12332f2a5ceaac51b88dfe0b147d0a44de3baa8977a7be5b27c11012c2c71612347db3d1ef73e43cb256b426a88dc0c5cf2a87a62c5605c880cc8972b0f16dd40e5dc5e8545449bdea3a5c8d7f72b597aff84d791772459b47619c728bce9b9381d664d37c39d5e6c02e4450ae9a68609b386a0ec9a10c06b4d11372a87882660419e68172b2e190b42a90da1731edf3d49c7d68cc828fbe5a738c72066986551c8525888b8f427569880532ab261833934d6eb831295b5656caae1b77e764dd5c466a802b7230b6870f364bd7fc2ccdd5081d26e9c8946ade48ee6e6ffe26512bf5f2fcec4aa729e98d732d9afc5877b49a80008a992a08b565907289e73f130c491bc7e45e0b5f65c87c3c64beef0ee7f66420db3854b1f64e84725797da86fc484a2e0d4ccf663c9c05196367e7b04980bdbdfaa4f2345be98972931637dfb873ac31e07a4bb8a7b85b1c76e06ec91e057ed42da4db50da8e1872f20fcc4da44977dcdef43fb92d5fd028aa4f68849e4ff953eee03a56e0cf532246d2558c1823bfdadb7d202898584dc3e8ff619bb7cf78e4f7bfe078a8da7418d96adbc07457ae050d2fe0e5a862063d383931ba0844fef07bfd5d3c29a773727e19298a4f7374ff0c3a6daf732bfcee4ac4fde233e4264eb68da9ad11e0eb727255d8fa6fdc7a17a2595487a7af917c0d2282c69f02d345566df05f86d39d047555b9ca5b99db469e1261bf867e03afa21a4a8994698064f29276e1606854727969bd3fc3bae21c840a0a74f57b923cb49b4d4455e0dfdf3254b2cb75ffec72b6c4cdec39579c4f4138b1dd4be8e13ef1ad0aa44d9a46209b8efabc3ce39b046f7d94244671944770ce7ae01394bf33bb6f1d0c3f27b09db43d13b1adfa117207b71dcd3f94024b6f35feb4be582ef06924702d69636f93b10e52d79ddfc40a22f8464f3b8cb7b066015fb02b49631185d19c9cbd46f32e3a6cc0fa53097372487002c077e72f169f485f285cd7330b6b8d80c7bf958ad2106db72dc4fbd872261246feada126a1d7c7c3af26d18abcc7baa0c027b96e6e19f1f853531bae0e276b7e850bd80eda9283fed62ad980b4beb5e6c43c63a61eb8b8fbac5f500d6151e411dc6ec4cc358fb9a66d38e36ab8db91a49c5b14973f369e97017c4a3d52fbf6691ff9550491d8764436d7fcdc538378f8179212a25d519f9956a933742da20843b22cf7be50ea6120c701631856142dc3779c5d651d5db3ce7141cd5e22291f6893f601fbbff3522f8b1fed7d30a5ae82fc7f0ced870ef871790ced1172417371a6b33e3c0c57ff3f0acddb6a6742aa72effdad6ebf8eb666ab8769c530d61bab206da5f60f4a6f478afd8a9ff3c1e70715f6cd6de840f4a679d96fa171250d7c315bbe99c639eb916942a8548fc6bf1aa5ed26c5a2549027f4e7e39006fcd966f5c48cd57b022349f7b4124aa519698864ada99abca6b727451c279d041c5b342d998c87639d672bafebdff87c52052cce66f406e33fdbc36aace1aa6ea5e0a59ca7729a635b4feb909b2c2edbf3b8cce5b544d5f24cb28bb313ad887260d164f82f932d7bfa79f248ce1e377abb822e9725e43417127eabef5ba4b143bbde66f40dcaff65ff095bed889e970f465e9aa81b78caba020045df716fba72ed1a2f6298389d75c95ae7497908038d2a0f186b14447b1d0eedb830bfc21412692d6b5ecd0d90de7c77bedac36a6f5e896c83402380d45a386b7760812c0656ec7129590e6878e307337c20592f8b8e895a7be6a6352389e644973db1367a5497f0b304800c5224f2e377fff9cb65cf58fc82c8b1c8407ddf34b07d4112550a3a476ac0de50011182178a36a1b1e498c41fc410b3cce6e6162d3242ce2d187292f7e64d4f5efb0a19ce601319d67960b45ad969d5a14cc91bc52301fa773c72057aa144e79f015250b33eb17f3512e1f7bfe33a283e4469aa75223d34044d3132b8257d9cc189a6bce6fe30dfcdf8393bf2d8f3bc0f1a21f256260cc8ce526d21dc69d11fb43fabf8cfe7ed6b4fd809127e14d1bdae4a2baa124decbbd03f6b511ef8142355b5eb1cd3b2a604aada6933ca1694e4f022a152ed8231d052311b8e8b34735b48e51a093f0b2b0e512d5519f5cdb62e1720b42a4ca49d6c317c385dd103265fb082c53410c13b6337653a2fda5882288a5e8760bd0082b12e2c72798d9eecf858d85454f2e4cc104b1f740a62944be06762f2b8bcedfa9950476c3c4131fef032d6ca2e93f350f84b55712fd99265c5c468565cc7dd71c8f241423e6d0e947805a7e09812ecc380bd9d38624fd8d34131e88e751c4a3a567c5172f15195af1b29bc62dedeed45f593be34af737f4df7f98b8ba0ca3cd5eab0977221f57ba777e9030c92ef5ea8209c6cb64e48b8b464fc364bc03ef5f710441a72122cdc2293de882da275666dff4827095c9d7c39d42bd47e694a250006357972bb35a9a253cff2c9faf6b8d9fc31edde7476b243981f51f1ffdaa3959da28d21c9bc73420a4930415b0f740c2954515f81fd84b9d7700e9c131b9d50b105ae72e83de9bb033a0928ae2afc5ae90ba898bb2658e1c8f4e92af91ae7921fe7603814138373805bceda90bd01995a20cd331a3cc62443e5ed9a8b4929bae0d4d52d985ccd58c88efaf7bd724a487cece87e84be2dfd44e38c8cb56c6b65d6ca0b4e9d523696d039acb9ff44eb630d2fe74feda79d25cb9bb6af72f5038225202e72fab7b42742e8bb3b6af1f33ccd7607b72a65056b43a897ae472ec11d59dbb97229d06039af8bdb85b5637c646f944ef8c82f0935c2342e38c609ee2d8eaa927255e0ee9a7bb40ee46801afbb5b67dc63d5e25d88b287525eeba3afc5d4cfc2726664ed52bb17c5fac84142fd3fc6c4e5537bf29c0246f5b124106b3339b7e6721f39a4affd727fcf6617bd9d899960518d443fd122b3d480bad09738a42f7d00ef58cef3bfb16e22654bbae67cbfe6c2fb7fa59bdf3e9eb32bdbed446962da720114fb80c6a0df304d470d675282241790a70ee8d8c9471a0b9f1afde2a910726ae17f5861ede4e5a1e4119a1cbef7cbc668ed8738e58ec3f23e4f5b4618fc720f5da83f13c5725e5dbd8f47a18590bd62a05edbef0ecf2e9223b182be90106d264566e577f8208e7f5f827e3b1f82286feec25ce96a3616180c454c844c842124c63be0518acacb1b7435e8371f9c23e3ef390fd3ca78b6b6163ed104813f72fdbc2ffa5147d6b4e508535974228fc38a5705b6fa95245740f25786715e7a7279d30e01bf749d319fe23906c36708af4ed52b98f153908980fd7c9dafdf9811af7515f944ac2131b2f32be52c730ccba39f11f12027e0e3665cdc4148380d0705f24bd31655337caf68485da67a13b1a5b8af5d37e36b027618f69f22c86a1d2512a0d35d1842ef10993575527a7877f751a94e1df89e2388a4d32e5a91d771b2aa09cb48f0a724c6770c05c8a89457bf700d66afab74e7e03fba2c336901720b5bcc8b8820db1709f5d20771f59d670287858a3252ab4becd4af3861dadf72b02b186899fc8cda3aaf5ceaaf8348c7c4bfa21169f803862ae16b4dca6602726fe3d87f6f797fa71f34ce63968af1ea259878b870c580f4638040e92a857072b04f4ac0fba7283fbb25c20e337a34ee21f6972f85f19659ce72213d3c4f63723e1dae51cc37ba1762c31362548cf0a504e0683a68d8c3af658ac15d02fd0b7229f9f55511b4d171ab111dc56f016bff53c9057dceaa5ce881ffeb491c3b0572097c0ca5fdafdcbc6a33253462360b5fb69c303ea2157cb6b43ea911a3b2e872259275daf201141bba03065d9a09f0e913e3abee7b246ea2a8ab672abbb8304a03ba56438e01bd405e0b66b065c24baa3ad9e4e4a7e67f06695983124b615e1768e2e45c104bd441d1a04ed77f8793af4da70c9cf3d341ab1fe892d0ca916370c4c74a5def4ed7d7817a1d4850c5c6b6d063e96f016c8f6293034295f8c99472e09340e34d8c01c0697e2d3c83c757736e1505cd3b663f9137b8c4e1e426a5729e1f3130222a796bbfa3b8c6a6d6c813f0a5183afcae646a9287c057a4b61c6e1976a042899ab02b99193f95300c7a746008f7b9c39678d4d3100fcdc979dc72659b840cf1381c60f0cc35fa40bb9d99d6bad22b3f9d1039a025587fc393f818c5440dc48165e533ba97bef46141f6b7a3ebc5f78780e57a7fc28f4242408a24cf8623c62a3be3c14a21f5b67b299cb494395e44ca7245b3c29e43aca001f572b5b342f12ae0790f7b00354727d494c2d593322cb7e94d39133244310a7eaf72e036547d554b1f3abd940fd3295344059b85ed2469104e9a6ba5bb6341620a72b4d0018e2939a9f838897f908bf1235fa520fbaca1b0d3831f2bcfddfa8bad1c24f49c20dd629b75f60893fc90a6a27ae8d3daedd1fd280670ef22f5177a52682d1fd6c750b9ee1ab26222cce40f50d98b7f65e6f88ecf4183dc641d7cc0577241140211693e82cf0f661337ceb9eea25623e31bbd130824c736bbef4394265271f65cbf71e7febbe8537e068f90f8443ec73de1f6ec37cf04705622babc147263db675486e49d06c5c175009ef86fba401bd45f6fb3022e28f3e063869aeb72aa401ebe4d48586dc9592487094621854e04213d18ba476030e460421117b172cb5b68ea4f3781e07b470891832fa0962aaf5767432e8fd4b9ac827eadc61dba0200006a339ff6defc6e73cf17dea3ea81f41e8bec092b4ced841a6c10732c0402a72772ff3ce9ba76137e3fc0f8e8973960de31951791c7f53a60da89c4c218013bb372292d18538cfd6d65b845bb74dab49eeebec5796cfe74025aa331eb83591f3072774a7a49b754cc7b8f0f6bf7863feca31d15b5881f61b82b83a1931fc4891662af589c30afe26f3318573c2fbda9f18e5c15b5fdae548e90bb1f440961dc1b556fa58d5ca8ba2f18ae1eaa2a01b3f2f7f1e6a30ba638791c57aa9ba095086a316bc25fa04430cf5fe64156ec8b6b27a21aa67e4e9c732b0b716e4921d7f893725ab1c0a487069cb302a0b618ea6f6ab9bd55165c57be6e679fccfb726c18ff5952f833429a51e30df463c87e4ebf97fd31e55561efc75e79e34620ec1f884b670b592679c474c2906f3254bf64ce8e13530007cae4e63abcc75a41d4a4777555aace3aeb834f11b0f8afd4851a0d23dce8e61bed90c9ea233bef78d7a8e213729c85024d8fbf88c8be73518ea7ba41e0d8f98effc456b2f70fd1506e01870f47733634d5aa3eb8c2dae900e4a44a4b5114ec52fd9b325160ca0b336dc3f4ef4e16a54c7e24f63f8755a761c7189ffbd9bb6c0a61c4b0d0fe15183dad7c80bd5578b13df62e7825f95b0c0f50922c2fe87a91e5b7a7013246fbf28b26df15ce72de83624a6b7a0e419ae7b81be875ddc62e0bab4f5d8ef701de9ab46c7fe0ff72f198855b23b5b639190c46b29ff1b4cded4b56c4f72efe92ee9d15f0a15ad6525cf4a6ed56429ee0f8c38e29fbb410488e552e1cfb0acee1311af9a4eb1f0468c7c6077f93748f8722edabc9ac7e9a421cfd747e5ccb9c67e1e98d7b5369ad720dc6b03747654c08d5fec535a995e6bc58beca71bc50bed297a7d15289287c1678999fc0749f583ab1be88cfbbe73ad9d80907c81cea907eaf1c5aad440b5b3185ef8cbd6ab0fbd0f0056e54034be9b9671f9f5b0d27bdae784e262932a6da729daf3f94cb90e69cbc492dfe07b7c2b17fd8fb978971114e71df360fc119b6720d087b2bab0ee2b9112a19223ce1bd683a6f29e51e0c4efea5a1217fe397016d812c4319fefbad0ced9d8b353498abd2e649f6a5557e6040695a68be9b5efe72d1f1d9fe3b1e23e5b789362fa9ce29c1bb51d3b36a5cc1c936196d4ffc67ee7247250ad0110bfbdd6d657424968cb2817609793121e5e0b0380fab91e465d75c9deaf49de5c4e07d25fc37b150a106026f0b6f37cfcffe190a681dfa96792572fbeb10def0b1c4d01a97458849e6201a91c7ce639bffe313fa69a786d7097b63c19560e7f02517b9c5450f89cf40c6fe5296b5456de4b2439e75baf78060c27201a071a77b68e15848c667f6d5d5c27d54b92f055803edc1db17007529cc3a722429195e182f6346cdd5eaa287820b2e5c2eab7ecf93b6f759eaae6774576f720c3513c34506a4e6f08f6da96026e146b74a96cf0dce7b4124506196d31898721c1fe2b4573946f65391fbd6d87e3e9d458903f1f93f264bd782aed57617e927ca15da37e4fca2d06a94fcb491cba01435a0b193c3b55d58226a783a738870721e4786a90ad0a92475b049700a125c0e648cc71742559b998e19b6bc16dced726323c0a279e8751d9712b9709780e9822554209cedda743db6824ed4ec52a05e1003fd874ab4d8a0ab8fa5e28ebac007ed1546f993db66546773b02c1a930b08f55b015c2c74b09f63efcf81b111abd07036c6a46d1341823685e48434136a72a0432ce0a166f1ddd0bc7d6a8407bd64815d82d7c41a61cc04433dccdeb1b6528a2eb3f57ca9c8f98e599c211c820cf3705088ffb92b9f4b3039714ab192457245538c85fb59b815598762872ea510a92987f0d0bdf4506ba5090b62fc6840729d3adc0b580db1858a2976eddce6eeb51b979acbbce468b24c8f56131bd4a95523c331cacb0b0d2497ce5736a60d2be6819f4521cfc28c960e43b771a146df7291319326eee388f10a8f7966fc3c963a1854769786e0839b17c030c664d8127251edf9fbb1878b4bf6f5ffafc6bf8f89328ab0b86f19d46ceccea679bd40447246e6ee602bd5f776d15323a508b980d12449722efe5a4b86902f688d04ee0e16002648f7f737db50eb4595c55116d5924fea4eb7300e6c15ddd535c872c246721665ab41946d4b156db6e39cc00576e929a9431168bbb059e32bbd792251bc7252fc0f03652b49e0d732103df088bb76f6be799697884e776dc9024dbb5e8072d0392b3da23b2d4cc641168860b0f66d9143ffc9a3c661e4dc6086b06b4a9072a471b961e13ddb45ca0ee45ab89d55f8a8fa8032da8488d1a3b4584cd83f1272584a8a4eedea16935fbd3595cbd8c8c45a28e9ce8abc4b1c5b8dcc514741b82bd5e8c8bb1eae2f27820625ae12f7524d069263079c675d10b0e674aa3a85160525a958131887fd51099295e0b81832f7ff01ca5548ea164cc77d70c1661fba6bc0101a5ee8378ee90ea41d6898640b5061a02004921d06de61bec233e85cc07289be88b91e97e19403a6c9e23965f0d89b100c762b311a51f42398919c17827224c6ebe114056b81bbb24efde0d4fc592a920b62eee2cf49e01b844e7fa2f46dac81da0c21ead5898a254c965923806960440be8fcfb9cf77ef977bd2679ff37b295a299c8287f1e80ee30cc0cda2e1bdecc989644a73d6ad7b5c082e2dc3d5f9ead49eb53d0abfec440896f0f1af73672357f4bb3c86f3f41ae90e7f61e4c6a0bdf35d90b0a3fe560de63fe5f01048d2f15b83d1f2cd84a5c522653f7252e727c00a4be3c704ab0d3c4d1fdd0c39e4aa58f054bcc4c1299e7399dea46dd9572ec02e7d00f669bc0da89dac73247f11fd537eef2371cf81b71a182fb70b6a172101ffbbbb8b14ed7500873cecaa6424760f75c7306c7207eb2b7a12041b39e721d8379944b091b98debf788a75a251fde9bc6c0bd084a956b66719bf4fa9d1721d35ac6f53c85739e056c41c5a6d0df681c1d9a5b2ab8049812ac44301c6d94184eb7e939a55a2b209c85010ce709a0d61bedde00ed439691e757dc494baa07257d107c7051d94f06eaa4a54137523a9cb622eb92bf29e7dbdcdd888b958e872d2f0ea1bb5bae4b942aca9c52b27b831c018c1f2e1bf093aaccadf7b0f546f72577d41d35342134a5d7eec575ba35fd3e777fb86fac6019cc35909ead8d667726dd3670edf1f04b5f47b8beb1f71831a4f86014ab992c6c92f45c10c4b18cb728ecc4f983718ca0f8627f4b13f7d4173c32419ec343961ff644ab971bd365472c3ab2fd1d5bdac7520170f6d53d376e9d2a5d1daf1f5667c432db007dd96a372033af2f50ab7883cd898fd1a736cf8e15ce3dfc8a88546ca10fb1cf0925935723d7bf27682184bfc9a2779b622bbfe28bb651c8dcc8af0f12bc7405159d7fa102998b179f89e66e48716e826de918eba67cdfca20771b517e152690c697aca72855b58a64012560016721df1c7d9bec3430e7dc49299a82a6f17697a5b6c69725e2a65d9985e8bbc9d25fe52942f6d039b835b371def41d947e8e11c92265569f6663fcff58c1f33d5e44f87d30d532429735e3dea1cbf237d6c6a5f2097126990992d800e38df39ef9669f408306cb86e19b9d9c5a6743af30d3e4c3a922f72a199c8ab7d7d1a2b0d66f99d4b310bf4d6e27c6e0060c9ad1912f34b8965771c92b4e83a9d1cb849ce1d7567b08ba617710e3839a624a45f9977022a72d8a92b3058a8f52105ac9bea101b48bf7d53c6fc5185efeecb1558bad742daa0cab92a6b6a039f159991258a3c564f475fd676d8f196c681df6052276ff5b30bde86720d051c9b94874d9c5ea2ac294f02980f87b5090266419ae1d89ca2b516695e722ba83d96ba90ec69d172f47148b34430b4b517d45852ce32c07af5d632d5f5723d6e9056670c727906e73de680f7929a0c7dce244489df5a894ebe7a8f46c472eee162ba13e6171eb5deb30c312efa0867e1f0b771e8f21b49ff76d9fa9a0c72126a2e02576ab75542e1d414cb2883963a65412a58c3d0b34b7bfa10097dfb7233ce33f1e5da48926922f0af93c0ed80ed1a619659d1961877bb46adac188e72555a6cdb2bc612e0a83656e8d6fe00bc0978a28f7e8364d4643f444079e61c72904323eba79db7657bdd24e4c77a65756cbe51cea4db0ff8351439061d6a4b6195dcbf395aa6fabbd14f6ddbd63ead41f7c863997846fc5afcb5800acea8b272880f1912b2a9039ccf08996e0cbf6b66fe23921689b502f63662bf4bac5f91729ca94c5235dfda5c13e17019725b546a53688f7ac387ff62a876f1b20c7d6572bf8aaedc230f66ef8021231b9c3c83121c75b94a1f6f840a1abc52e004bc621a1f51f84faa6d0fa427eae8c2aba9af63eac693ede84e5d546fd4b8e692fb8172b9e2c67766109efaef1b6d01f2db803e81f1a2282934a6667d981ee1d7194e720cb214c884919437ec7d2ab67419e062e46bbe842755ceb8114b68e28582391b7d7d7b29f2a5b7d520d0c3507a4c8f61d750f0bfd28bfd953d288ab38d1c7772a7e0d8e9063849c285799c0039891a82eb0380871feb1aa0250608648381bb72342ddd53454a8c5ea85836d884b7b0fca1f67590dba7a28948d2c27ca6229972bab4c9900319131a53c6308e1adc16646f313d4e262d3f322ed15c3f632b1326ca44e16db93892d62b4e1d2e8c2c02342a685b1641a45fe6480d25b1980b82728325afee0a05d61c90efe2e7d0d503d1d744359748d39d59feb6d0ed49d46e729fc36d89cf3b9d42788d3930ef96433574526239df727bc6719e4d1585440072f55f4efefd0b9296a2a4570891c29c520ee4f41e8c1395e45e5852870d9e7172b632caa02735c094a28f576a0f16090c7be0d0cdfe1c1bb35f6e992fad6f1872db3263c684ad4c19391a2f44072a16a0c3c536a154311ad81b1350f2224afb722f95a47f8b71bac421bde2d45177a4f56d6026778c0e13bb6cd7ac58229f6352d7e2026df50aca2f44a57a92eec99d01d89dc4b0a1a7c1820edad5cc0664c9437cafbdc10e8cc3d8d12c35d4aa54bbaeaf202e796c1d9106fcb346580357d77294f4c415f2ef5c1a8d18bb550acae3eef74f01e8f3e96c2c07dc7264713a585a1cb5dc4ed9ce39d761d4ba183d231502ab7ea623abf80198e952f6e4d539f272158b0a2327e3687943c54af5fb7a9c158b4330366569649314f3310ac6c1ec68e18a25f59ad51302373600871b5bc4f48a4e93e8f83f70fe265c649a61bb373ca97bc7fa14e8d9aa6f93a73333a2534d7ec279377dcdb1200640da0f407e05575b763272ff9843feb1b1b6d873e89cb9f657e0189327a67b3606388579d1ed57d7e84bdef8129890d37296250351a7e5d3f882e9001ac166138d909591e2ef5759415a66d8bc734d2ef3a565a916677da32549394f5a36f3edf6555dda6ce2724a4ad6eaf5cc67013b7b031b806ccfc16bf2e03a483a6ae08b3d25615558ad11f697db2aa8f91c86be11af9606174767a5977ca1bd09dc7dd125b324224b03729112cf65fa641b0971c4704d27bad463ea2478a7c180bd81fe0e5ca10a26837260153edfc507f23063204b8170b2b8677c132c4ff82e668696f0ca1511c3f21a0cfbb8f9a518b006d72d818ce3baa0a0c9b6c05cb1ea2436377e1466cdd5f644f53581948db50630f96f3b4fa22fa1a5c1d51f3d3837b24b81c5c6a91655fa72fab70a126959eba5cfafa82c05403c0a1180c4549596ab8a353c8a872b92f600c747b7fd13995ca511bb25f8cbe34815ae1a14bb5da6fc37015e544a883586147632fd3a4e47b539b725a4a1da8e51f6dbc9d847dda62065c0a50005a2230c6b14cc1668fb11db8ccee9483dda07706b1759848d9046ce0e8b062cddebab8046680a4b1f469e0065b9ae921335e38777d8526c911279199c80a597b2011ca2728b780dc8724410b9e9712e5435174e1a1dffed172196fad5a3bee30d22ff8572dd67f017ea38c39d55a8c62eb3ccc6f6a09c34b12aa8ed425b9586d82c6ff572e115466cece033433cd0e55622ce1cc6c1aae4dce395d02964599b7b922ef7628a2087a8fb9e95bdbdb1c3c0300bacf0a08179a26e50beb32a1de7e6c5aec372f0bfebc79a8c6c6e2878bf0271fca1399238e3c9718e59d0fca267cd508bc772bf37e88ead15fa2a45fb8fdc66c9baaee76c8c3d746544443291ade63a4c9c0c8c842e95b9bfde85887cce2763c58cb08d1a6a3b48b4a30b3b9fc1d04ddd4672e3e6e3eb50bb08d9956ccacfaa05bb3d2b71c92bd1252874fb7f1008ab98ba157ffd1cb1a060c51cadaaa74ed0d0e61f877c19a585af3e3d18beee2f1df46072e39e28d47bb9943e44d3665dcfb3b093a58aa86ff10c7ff4489efee3e9103172145611a6f836cb62a0106dcfadd0d62872cef17909c9b18e480083833e4f3472f0b5bbb677e77d3097137164f1dbcf27a279153b1dd807533596e8652515fe370fb58a9d535f0fb7fd112b13dc9642b73de365a62fdbf97f9c39ef631b0d9a3d95aba34bfad14ed8ca77b84d652f69b5d88d7ca869d0881516c51f287f91ba4dea422d5dcf3137364be245fd8cb5c40ba571757708f1e354240a940a2cff020c043de5fbf1a292c54ee5e3f96ab3857c08e79ea3384d18dea14b704c61fcf815bfb3c217956a7b775ac65c51e30cd7f1536374b466a2a29059047b2eb8466951369093f05c102bedb0d499f94867e663f1f40e00498ce4adf47724fb4923e45613262420db464b73440b47e7d271cd437f2f4d21d51e00bdc7b536b9bf21956322d0dcaa11918f2fdbb5135a8c2b9e01ab6d8518fd9e55040be974c23f06de726531838144bb92ad923d19cd8cb3c3311bc281552d01742d05f6850f390c5515035a205d8c1c830008c41d1549788b3b32d1cde0325a30aefbd4bb45941f41632e1743f1cfbcb41e089780a9da287e9431c759cda0613bb43a0d6bcc4e804c7214ebf5ae4838ec43fbfc917d2fc4cea8e7ea46ddf2f89a1f49153738d8bb324176208da282ccf9a232481d2aac8b54c90f4228307e02d4615250e1e0132e8072f81e5af1c00e1c7362bcb4c7a12c304e8e1b1d839d2dcec1a33d8e8106f7247280f441fa7f551815cde92d1d078b5da7d43512b01bdb55013f72730b9468c42d86e4520fb2188f7808462de30125fe3289ba9207b97e45ead73edc6d02f269233dd4d72b16887dca39949c7ef49b9465fe32ad6a7ed23ced20e5be291ce2d352cef7b1f6b232452b5ac67d22e9cdee8768be0f873d69383fe5d1609df747d0160b1b919f21ecd6bd554f6c9959db19694d230364684a0f9b6aac9716f6ecf95923aeb40dbab0395bab551ff543f529455a7c06511573ea1519a7444f3f5b9e7215de1ec92adfb74bbf6b7cdd668fe6128bfd3e389d0db4d0af0e36c54c7d6072fe5f8d31dec7e5aa83a38902c8ab21503cda1539f4757adbad55a56b8d54d67259638c52b3fc286ee236969aab154942771819cc524db7a26af15405c8b5007288dcab61770e5e75bdefa22005154a9322dfc8c81a34b2f2f7337fc0efaea372d4ee6e7ec4a331d47fa8e10aed128397e2ba38915fcb9c2b2f4417bc31990272dbad47545dcc89c6a865d6c43267e6a511f9f373f6ee5150e02567b6b893af71aa43540369e12059ef46162024c09a19d87d905a1e4a5a9588e01cf298a66972daa4639e38be2f39901127e2f0b0c0f485e42aa0c69053f6f20452a8b3249a42bbfd81d47be5ff59b592d547b2312cea9b9d4f53ad11b454bfc51de8e6f460720074d2bb8204b3d3e92c7fbcd079c75bb410079a5561b82534f7f03b35928472587b39f6bfbf8ad0fc4d983a508c1ae962fe0b536c982316a5e077226d605572370352011140d80ff07f666e898b6812609f6ce5b5342f0e3da2f6cb127de472f1814d42917cf95a4c7478315043d60814771935329585bba1e4a84569cb1351a68c4f18c92b07a7f0551c84a7cb51b2c6483357434a478f31ca5040cb113347a2c5886e4759324fc6c4b09b28511e6051f22baf1153c34f5aecf027f25d9565519a3e4d57c525162ad695b036abf4064bc66d492e43057aa87e7e26648385040dd44383a4218ada4fbb48a96aecda57e32b098bf7d6185b3dca3a04009d4b2586bc996d1f88b5ae3d5602144afa51bdfbff7da52063659842442ffea7353455fab0d42bc14c5cdff01445708a65f4ed2adf21403e792e9ed7a066de1dc85c722418dc952a93fda0a79e6476db362823e7e237b3527fdea0a87460adb8019272fd940a8aa11786559654d1c51576ebea04fc8b2cf3a69d951f2dabfb8da46955bb533a38df6276e925f09684589de5df811d8e6356794973c2f0935876fb83721271032d44e858ac36165ffd908dd8a94db0482475e1f57d836631a2a7cb0a0e9a780624327e9712ae774b46e8394ffc88976814dcd1aaa7bf8abe3c7826b7725fcf265d6ad887f27935a9f72bf499de8e0376372b04139297f20dd186fdb84cbfefe69bc1bf0c6fca40af340d38654306b4c0fcf68b77ceca6b7b636e2cd24d9ae3f7f1833c9bb5e3d15888aee9cbafc001a9853eeba7fb399f7dd6a97ef8727a0f1ec89f024d531d8d4b37e486f119137b4223633e621315c82f400ac42472eadf3597504c77ef72cfc929f187f79b34fc8899520eba0fa64308a26c969372533a7c0e913707de3e0be73cc7cf44dc1adeccac8f34d6f74e6f8b0f143dd24fb522ea84698cff62db84ca04d59d56c52f51f3285f6c11c8e1aee2f4c6504715e46a1b0e02b354d62b63e186630358646b72bdbc6f961ef93834cd9b82665b7219cd907c37aa3b0af1c3beac0084612c7936a7fff2d43e41c97939af7465bb0608978b8940ef6c4aa238e35d6b7a62bf95a70b11964aaaed8a9ade5165fb9572be318ade657a253f3bfa04b34a0b16a8e835e18375da300bb59f895564171f296eda75218f042265a648119635dbc7c19821d429af09c8bec76a4ab17351bd1f90699820daaf04fd801e5d86b876653129b3f56d20c4306cbfff25ebf911f172034c35833d755f07c4b991f8a0f306c4717aeb0a897de5908699255d9722ea242bcb7300582d0aa20275c981777c4853fdc92fdd31010a9b9d6883a04f09e672126b759e919e0f7e2d4ad72a7beff3d0e09a2f8892445a8a35708e6d33b1c4727d9ff78222d1398d475d59ddca32ee234a54046130b4b4bc2fac324e452fa24ce169cc109402b6a7111fb87d7f6b0ed5f7ddda7ff9a398e880b800ba46d3db72b19722720a82636bc45536a76c8932fa98031c7558542aa6252c4673d1e6484a11489fb22a5452ac71150bfa082af39541776236eb0e9a00828a1c03fa9e15729150c30e2dd10952408bf11140348d74617a89183452c5a7ff7f9ddda7868c3ffb7cfbcc783ffbe80c71917e6f937e2f211676313346a1a0e56fec454574d412de650a7b0c814b0477f45714d632aceedfe4c40dde3f44e939e00320807be272dd798703db8e524cec38ea1d79bc3e53e0d486e272df7ff0ff522e7dffee2e7298d5d1f286e6ab64d873449c00dc72c6163936d34a3b3e89fd7ef2005c0b2a37107aac9e0570be10e9847a5838722531e0e6b1b98e8e9eb87fb609c5d8d9a340d57f5b70e014caaa431f69de88c8f217437a190e6610280c13324132fb233b72127a75644043da59372d4ad67560da8b7e99cd354f31d297cd98d7c018cc39100e1e918b2a024bff6cb6913530e69a4a0e55c25c3a8c83e9e04e983ebf0fa009d4b5a8a04f45a43809e1b740cfc7140f5a409a9bfaf58ac8b38723a7341a5472a26f6d57b44633fed0a2a69f74c1176a8521eb00d2428f4970ff1d73662ab372e5a7c8d825c76e1a34640ca770116959341187873195d63945ceb075fe73605a91eccd6071c782b8fc98ff52bdd4089566d3d1f7781a84e5438c9154c4d4e972314b14be1e0491b6bd5f1044e6116c994d2ca487c63c018860a589fa3ee66935b3a1976ef64df40c2f0f3892b94c212884e2d9655fe4801690c96d238ef1807248dce07c3bfe5446d04f5cd6d2d55e6a2e6c150bebbae2a9af43e0b3947bbe03bbecce006d8a4de1aef5e98a949a3314a80e5ab483aa61596920d4a42403b77230dd4aad3a6b70530d53b0ceb5406718afbfd2d6e46795221ac7f1fc28b83623740aa4687e176d71c605dcf273cdb6cfca4b2bf85140e848ca1d4a6eccd4d072a3cf87512bb2ac942f99f6d42bd2751686c58424c5629e8ce2228eaf2cd9c47270fcf755a90fc1cce4246de20cb336dc43f4feb2b364f9604733cc0e5e6e2272da9ae28e450195f1f1798bb66fdc36c3c79cde4912bbd0d21b4293add91ce91993991e19ba7193eb87edf01867712494d6f6e1dd06565eb821756346e14af0363bf328853a9cbfb8d56180c10c1aa98d4c273704d251fe7bdcb892440c85165f1363f6b5dd4bc121ce6aae5e5c3b7c4c2323a50b02cb6eceeeaab66ea52cd200d0f82f95302260672ffacb31561c65077d85d011ab575e3cc203f9bc2194743a59be075d14646db7b77426887a24b7d5fd4f3e9ee5c561f59967a14d33ab7f729beec507029a01e65931f83f4894c4d76482c80405b74b958caf73f69651514fc8b19b649c09dbae0168e29198dd03d6d4e6b0e46e695a80a1f2c69db50d09557cca6d56ac8493bcb3b3011d1aa7defbbdad487b6bb395c21806d8ed22ea8a72988dc83f8c44702cec1892c1a25bd5eeb556f4f4da1ee14548829966e89c97721670fd7d19926c725c1fb6436ea3a1870ffa05e9c31ffc43a601cdbeb34a66726c3e6e7626527d5c42bb84b81a0fa9c47dc5e2aff28afcb79711615ee62ade721de053778efb3e76d5928491358f721a252c20537150ab1d2cb7b7c4d8a07a725f8640c80c3830d36a9417e05d810f6930f265e3035c92587bc15c2091eb26725af8c8213e5bc2f3d63aa40d9a7fdcdee45b7571367c9c5d4026c5520b0960277d0ccc0126437af08e5e77a360ae64874043e48a79f449c99d2331075ed55e72150b56f800c78a9886d2b968cc613ed8b62686b5c230bad16d565ffe953fe92305f3f5b480ef3d0132ad623bb9d41672bac2c032600d66d0cbfec47dfd5155525f9ace35b8d271ee59972f2086098ebbe191765de3bfa245fbb7467a8c66e772fac1cb305ce8d46d4585a6b762a2705c5260b54af1b043ab3f05a023afeb8b65d6921b24e1ce47b11f6b4492ea440f99f21fc2842cdfcb4940d22e071bb39f452f215a7f39b7b363aa808b358764520db4a28c9deeae8928177005ed339fe47258f55fd1cd8daa829ac7b8a0c8303e3a30bf53df9084dbe680f253d9c3212b0a1cd1584a2de5e0cc3f4436eb6371053225c5bf6f58105a25dcd4801af9bc7709a0c3193a558f307a03477b0fe4054d6dbd0ac5a2256fa50362299529b887104b2f728c08cae8f3a393500960e34142a4e65dc68a3d530a257466c6e3c063b87247f1be7856708cec0fe5565c61c94eaaf156ffa2b606020d2af9a95b186a8b0fac862f792f90f1d33dd22bcde48a3a4a42f2c93ad2df76a74d7e61d95b82fc72cbc6e006ed6dcafe5bf2c3cc7d0f94c2038745feac304c9fa278dcebe992fa721e28951031ad683f22813e85ba0778333c10d74f9b0d673eca14590599f67e555294395a16847ea53b3ec680f0b2d3298c1f35bea054b6d242f0d870e5826472251b996dc996fc0db99cc0d13812a4a0a38587250eb98771ad9823dc16323d7227a1cc600b74304d47efd2bcd0e19fc3931cdf4c7a1d469d6cd8e2e780242e72f11d0a9dbd9c92153c078b0662e5f6e13fdb08ff942a0c11f86286e6b40ba86da2af2a9eeb78c704be9324d68be76582d7e61bec0dba13fdd5109015e6ed593ba845cb918d614415cbb99d816467d00631fb1af4d0341b6b18e635e2b310f77285361b3d4039ef60081775e4952225fdda801af588c75bdcd721e8f740da7071972720b1ae81d71ab01779f7be498ab97708ed4fc1f74c33e145b2279e0e0207a1244d57f9a15ff556f13e50a0b5ed092eccb74cc4fe5ace23d69330d6741e723c8640df2952e84ba33a11b270ca6e4771c6ddc62d2d2d5115225bb3693cb62440ae231422c9851ebcd33d1360e907e866221b0d068e0e67772c8fe5fed2e27268105718c9431f1edfcb85517e436b4c55b04267ab2cb3f62de32685ab411210bfdd7be7965742f69e47fd93ecca5d1f6770035adbd8b47244dc845ccadbbc71bb430e1c26db9a7edb827dd098a58187afba61501583bfd7500711f72cd6f872a42e0dae30415917092c8cca23d7a119b1c72afab4a050abe71037d508c86872eb2e01a5e24d2269c16a408b5bd83c35b1ca22925f92abc297d89ee2032a3a0e25decd9884b87625de069b0332359ebd836db7bbc50e3a6fda233fa22e2f8729aba70eabb49183751b3b2da80e92359fc8bb841863a767837a9734fa27e46e72fb354d413b81e334becfcd2248d18cf2a455f70760deb64c5a623a5ad91cd84866cbdb05e916c406b38136009f6ea7c8b45aeb255b208b0ca134194cab65f7724b97e35752197ba7a1dd65ef25cdd1c488e3c9582663f4a5cf8baa9c01429072af41231aebeb0f98534cd1463b74aa974072601d1cb700daf2b21fe9738c7d20e1e6a0e586beb94f35fdcb900576038f4ada68feb88d900ede52be58e6998d7245ee1d2e48910034a28ee6bb71fa35c47b412536274224db624d0eb72ad65e29dd7c9ebca62c517fb58e168508589b05c33effc02a2ddf63cbdbb69fd1bfbb0f5d1b9a6952ad8db0b6d759be0ef0720e23d2172e1af2a4688ac7f4f37a08a5725dd0d7b9f931d7c0da638f4a88dc26278460269c58ba75da65b167f8c2723c72ba35f797665a71a5c4f067983a595d53e3c20766e8db8c858513b094acdeae728ac1af88d201294d9bff31c90522e19c686ccd8ed2a4ce4838055e9ff632fb21dd6abe209482b3dbf7f752b76c8df9a9aaa98c2b559adb8208f153a41992cf72c6ce95120004b9beaca74df679574988d94d012b9cbd21070bc32ea0fff37b42d9fa08f57fff95ea99cc1ca11d99f672d87ac3a4ca56a466dc693e74db5d035a09342fef11503cd41763fdd45fd5f2879754ecacba0cdb8eaeaaf2b469859872e2c82682a243cbea0fc7342a84ffabb38df1353cb6432e2175064b9f528bd472759826a8e735c5c9f78c9d50c421f029f11ed595a2bcc276302421594cb2e372be85593cdb368ec9c495f7ae1130c2aaad07f634d31e9c43bbc08623b8d63d56e3082634f0df24c213131489b89bf39cb020f46d06d855dfc0ea181810591a70830dbf0b7a1cdc475d0f957542512e02627740bc71c688dfa6775743fe94864470ea6e4e67ba1c0c45f5e1cad505135bac8db3d418e95e2e89f88f86bd52bd729ab0299498701f6e6b19940a3d6ed2c96d18bd19774f95b5912f70b09f62741bf284fb97b8c04ed4cb0c4bcb00c3ecf7d42d6a77c0d36b2a329e2f82d10ec172009e68075f95916ec677cc6fbd6b3dfc83d0bb34db59b80f0213998cf77bde72d485c4af6ccfbc15396aebe3c5b0b5263a6b1ae5522416ff8d89d5a319f1620639b564813c9262cd1f1c49e7796b05c7f7c49b8bc8176d1c71f9fffed8b13072b56385b48822b58b6e36990ed324e4f88d0d3aa53dbe0ef2479be2ffe5de9e01f89e930bd43f5b9269fe2abbfe62835dedac0e4d05adacfe32b477a1245233724c82abb3f49a62f9d5b05833d01b0fed8d8ace46753ae9880de7512b49e69b1c36d63306c4b40d24a8a7edc354124face55bdbd5e39d2406da78a36f8cd46172baae61a1885e3a3df5f0cdd12d54bbab634375f53f0b179789ca42cd192ff00e674390f90867bc8c7af77e5815d9cfb76fb13c946eb3b8f5a3025f4993cb6f720e1a4e37053b3ccd252ce5dd82ff99c71620626a0c0634e492b068f25cafad72b4056f7bd021fee67e0dea0fc4c6e9a71e96d39c282b3cd4a2ca12630e774a1b367e24dcf8cde0c00a11f51425104d1c9c00f0443bd9ec21aff8ce1e3597d210f24227e9ec24565b17ab7f06e1be7fe4ed36e7217f538df60b96b16be64aa1721f8b3e65c8782ab8021c98ad621951089863019f6c54cbaa078328086ef72d72018451dde6a0647ec32cffd5ad27d9489150012d1eaf738e9331a07f5507854bb23d1955a9825f24bec3af2ca3ef920b71a17710ee66c5dc4f8fbd87a2ae9172f8146a715e8f04ffb3fb55edeed488beea068d2276d6605a8fd664fbd9b91972ada6688343b8120387e7259c397716b3d4c78cc39323e3f972f406a07c420a0aa04bad0fb72357d239cc5cb62dc3227e7db5961cdb4818d4e30e69e7221ddc296ee76a5076af594677106cb61b79feb99baf6190194a71a79d8be4b480347472c26e4e4ef396269e4b94f9694efa9b7830519be5d391d143ed3e93f97dd41f72de84e840b6798310d6c25dcb81ab4cd82dad9d4e1b80e4b5253b11013669ad721f24f85662349cd1b56fab9981e402fb80d770752989d2534a285f2626af1d634a0ff224b50a194483563460fbcf40e6c55332bfb8370f6715dac13f1761fd723c2c248594ba4c037fc732633a32fe8b44fc140be25d820dd67d00379a155a7287c60a23751017b5191cfcce83ac1b2becb6fdf522cfeea4756a87df47f5a12c44635f77b3de49fb618a4ec66a7db48eef682fcb00982ec664a7e9ad30568a7275455b9f67803309ae6559c3ceeb4391ceda8cf6ea2193afc3e0650a71e55c72c3b3a04984ccea148ce496d16d7098077f9f133765330c1bb3c98d943e9ead3d398961015ee9b668a4aa8a44349f8f9bcb29c181cf15138171ea7866125eb1722a59afc72b7c96b560815d156752c32bb0b5ffcb058243f333f4845eaa44657253d42cc31dd53f43f30bd18c97796a3e954ce8ba10fe09be20af448bb40ea345366937a029265bc1b5ee7f07ea2d32e7438d87ba43f54c3df82e17d7055d342a030df889e88c0bd3173f2195fd2680dbf0d2b10148f530bd2f5faea55a73281550cb5147175998d24abde7fd2ab44737fa0650520e6192865e9ed0df357ede50e40ee8812ffd54ba8538d45ca90d54a86ecea4df31554cf30f32397ae686207214f3530e85dd670b49d4a62a0f2303d10c9c239cc469e765625171452f8950724758f1d42ae744ebf0f49892c1ae6974c3a7920c71a94d7a99e48ab00edddc728cc4707721a5abee03c8fa55136ce6c5f2ebd442238b12cc2182f9804a9e4e053e55ca1af8ff4f23252656221780a6cbd8a1755d707e8f6dfdea0955416d44404612657ca10428d56ef31c872f4b07c5e114650a6585ee1365150a1a9d2efc72ae33fb7db3993990f04d9c5715ab498f025ee8eba8e01b39b2fdb3863427565a671ffa2c1446bffc6f7dfeb3e1bf3363ce6313327608035e06bfaa4929373a36b06a8505fe22a2458450a0d989548fc6cf24c35b334e4118602c3317abaa28722493a1d5b5d011cb62ee3d7722132c724984665a8cc2ffeeac947703ba7f477217b4340af80ff5cf6cdbcd026d5cee13834770555fe8585867fd56d8949be272a46b5d163459c5d9ea18b4433e8dcd3d1d47648840e877788323586e8e60e672943b6305a69f9eaa11bb9368c80b40dc74359fd0df41e4f0f1dd6e615dc92372d9a9bbe70f861fb09ff1e70049492211a2dd5e3ded6d041b94a0fa3328b9f5184f12e03316af88acd6007e5ad1f39a3dc2d854347cd04b639ba5660272283a5064c34191f3d6599faa4d952320d012de4870a563778bb5cbddc2116bacd2ed2215f12a53ca7caa3a76f57c8ebadab31a4754d2e07d1ad20d767b90ab7cb5c605bf63865f28f12880da051402e089614235c9a6389bedd01f53153fc861ee1c44f4a631aa270e44ad6d68ded31cca318a231e269dd2bead23a779acb6da85a4566641a750f03cfad2c773342b522b45343c29d97f3522f43789b504f6bf55146c5aaee1c063be94de91094f5981282c30c68ba5eda57cdee07212479a0b259a722d404b446970c0ed13e8b65396f81b11709939fb2fc00fcce71eef485b544b729e53c3be92ae496780a652d7d9198c3ef3c4efd72703255bec266891b91fca728e4fdeca6915b5fccfc9c69b3c1538ea3d180b270cc19c4e62be90d3fcc3b4720c5891123483555061bbcb6410e08d9f6a56c13c124d357570b16da43a692b72f6fbaa70eb2985ba670263e4a0d386018c5628b85ab4416dafe14c98796882724e1143366900b06f3ae395de1fa5eeed45cb672140da7d156027c1f46e6e490f10ce0c924adfd485f1dd566ff8430dfa305210ad4310c13add3ec2061a60ce5e3dd56440d056ad186f749795316b3d13f8598de5764d25839927903bdf0162271d205e823a46ce552d73cdccc9abf2b5e3a2a770bcd4b6aa4ef09e94b17ada7226267e55bb10c7e36f6c8374788ac26ddf962606c8636166ee3cdfb13db38a727c9e66b31c22714cbd374abf247300ef80ab6a7b2d25c228d63e4b0750580a72ddbb2614e589f0977ace0b02741abd409d63a644b4b027f10e4b4f11842b59177ed1d7667e8b5fe29b6018749b09cfae76e4270ca03d0a72737ee3067466763ed0ae878794f515f590210cc1f9134b9bc753165718c1dac7fe06b5b34fab8150c2f79081f2dc3b48f1cd99324d65f0de7d10dbe0876672d6d0e555a5cf88b3727a4863b47322479ced6bb37eb35b72bcb864c22646514bd2b64ba82e0a24ed7202824a08d7fabd26b0be3b4a9499d935761ba1e5b6e44dd81f6516242857c75a74196077440d86f5babf3587ac11ae9ba10c82da7c1837377281bbce9e5b5b72afd55867c1f7fdeb0de1fffd5c268bb6e11371d9afac5b56d26c0dedcb5b2536957ab46b99d336bd7d11f7c33d71871a0b5555cd5e58a7c4f7b99b9343bf7d72860df6b52cd788a5c88332e165f10dc1c923acbaa5a1c209d96984cf583bb8721d853b9abe716ff3c21dba9a6e3a48402b06850a1a464e24dc6c2355c7a5534998b416e89c554d81bbe1addc7c42b5ea24906cb9cfbe4069f6ae72e540501a7278623c60b235ecb140abb235d02f08aeaec9ad70d52deefff7d26e32637abd729573a21dec47694da52752e317014f8a7b9fb12d38246bc9e3ac08380a875f6895a24ec6cedb55a19db7ed9304115c3b384cf667d954ae15f053cbc597d91f724cf5f13eda90eab0e664deec4b0833f9ce761cc4da50d57a719cc98c0968e6222edb607987f622eaf734af28d690cf20b6ad5c291722b903274f104fe590b6503d3098a56a7dd9eb0c7e76fdbc98a0e5067da44e6df13ea502cfbbba9abee3720b1f280a310708101305af27534be1d1b836c206217cd0712e105c9ebc3204728eff5215c493d90ed143fcc45f3a55dc5c19e716c39f978f2635e2f66f184d2bea43a41dece6c9f82b6aabe1bfcc91f64a8b92aecf91acb33fb6de0dca4b10723b420f02ae2fe0ab615b8a03f07b545c7a0f78c42caae37ec47e96eb423a427231e2c6f7616dcc6b4d19f61ea8f09985fd9c238ea13252b41758e7615b728c727f0f9f9691dede7b0a9f3d1dd15a4ba90067f5f12b491e6196ba84d1f6b8b5729f7a6d88e4eee3ede8c56574490d45eb2e85d1538a4c9e0fbd87b926a136101c25cc143b5c170faf76c14b5b943c550559788d55c5ff4b4500effca652b201724ec3971d5e358be28697b48ce8d07097d2682b39eae49c2bd313fba353302e726f71df3512dc9cefd9bd7d885c824475d76e077ffb20355646064293d73f7072b86102aa5effb527fed10783e9470e2412269ee41737b537387026774a8e2924856848c4e69460131a99c8111276f320696a81fe06390ad65e92a1bb7442b04f00cfa7e3a6b1e82df1604397e837d38be03fafd799019cb7ac564f6b8495bf2b7e02ec27f95dded0c476561753de985ac73d26828fe76f79086174b8af2f63406b2b2decae9ea48e7b8a659261571a0981f1307f626eaf003c830a8bc1d2c5324026c5adf78332fedd6cd823e81c26037f95f3b65838c4cc02bcfc6c18865572a5af105cfae5c339e0a2c938100e18561a2e3c5a1e81160d940a8d0878ac5a532e3bf31ec053dbc8e6342bf0111008f8b1c532864dca4fa195ebbc39a98c7b09755dac89c29dd442b7ccfda198b7b846920804f24c8e950b2a3e49a1cbda7572c6be21c8cebad00c20a7d267a7e1c01f44b617a6ad1293730986a19b6c696a72ed829ee7dec8e11d41992d975d6c9c97aaae922e0792b5e706a00c1309b0f424525564590a69d9a771c8a7cbfa1d57f6cf64d4a40867ecf3553fb52e983b833123e67baa1c282f38619fd16cc69722b2c2a8b657a74b4eefc1a2f3b9e183127298f6e0c18a7373948e093e8c6947ba4c4e1a0a52c9c5a4a3d162dcfd4141de16e1c7688acd3a166a86b33ae3f2919823e57afd54541ac7de5e918330a9f26372c1378233b8578b4af343cb48efa7b80570eba24af06ba24e3c972d0bc4a2850ecea29da2f2ffa9320560742198dfa108b2e23e03bb668c2ad7c98e68b895bd56bf455fdc0efdf55cf0562a293a88f618edde7877b8b1205fc7c2af296fedd1722b9d9c43cd7a5532dafb1e9ffb6323aade4c903c2e3461ad3dfd1d9d6add1916a0406ebf49d36d31e5b7edcbd578239339170a5a316a7a854bc6ab016e5b6172bf5eb97ad1fa0b641c9e319162a9a6d25727465862d4af3ae7df9b3f5462ed72585402efde21ff77786d9dcb17555e877025e3b9ce021694eef594449fc6af7281a6eb0f29f08a47479e8f0e1872688bcc263513c56ca2b3f928d1712f1268722b7a0aa59e252fc089fb90facbe8f0cacec434c7bc9d8f15dd0e7d87f7b88b549013f35d50591bce77b956daad37b2237d75e114d2200fcc08932c7c5db6cf6f8acdcd2c083d6e108e6e254d15154c88826c604e861a3981e4f759037437327293f0553b58c51fab449a1d5226f21d911234223df1d99f33aec93c44c52919726fe9e331059610c79f24c92aa3d0265a5246ed4d8c1df9369e375db13a16427275d2bd4d47b51be24510de027b993c055a7b953ec79e72a0eef78de86f74e65c51ac66c54e242efe70c5606c2f5cd9e9e466e3ee9906b4d69785a18254eaad72a7f610e9b1c73569a12f3e2495e0db5115cc16b9aa5b8851da159b3630f17772d2a30cc79044f0b431a4353b23e56eeff95c36e1df4d5584ad7edd45202ed572153ac05833320ad915e2f28087cd07a45033785d9cb4050aeb37438ebb9c064b5d5e0c030759cb7f4a59011c4cda3060d6ba868da431bcc57d0e679e19213872282840f1bdaa44f71ad35a424f9e337fe32b8ec4d1eff3fe54cf06495ff3bd726263b7580cb5a8622a8dfe5b170fad84e489f083a18288528e4cc19eafcb1b0f505bda310a905a53348c0ec28cebf0607f9d25b3a9ba92399621bdef89bfdc5dbd059d8085dc449fd507884b3bd604c35e79d683ad969b8acf0267530b541f6c8da5ef48b922937aaa342edd4b74b288ab7e22e853b43244180c88daa59d271c2a9e59c76afe0669047b9cbc9adf9f458692708799a5089b443149f46106a57204084dbc010251a13bacaa2c4e10bf8537c59a1e2905126b0a698cd63c1c1138a6e899730c03fd70d12386b99e4890df968fe483cb388f6f1834fc617160a0721cbbe74c346365dfa0c9b11488d5a068f0be5728c5c40a6c95ea689a75fd391bed4551dffd7aec6439aac70694e52d83920befe3d123d2fa55aeda3696a48c722be10e0cbc189d9dc362ba0712863ce26ba11a3ec3dc60ff70d611b6ccfaf51735adfeae38a1534aea9f0cde2df61a2f0d01c089d33a34689a2169e068e73b72e1fe0562d29fdca130acd7e384881e7c25b5ca70b39e5afad2ee9d42aef92b72de880a42ce6c4ff9f1f879d851fc6365923681a72ce736a8b8f8019f8e51b3724b5ab57ed6584a0e1acaa2d925614eb190d5f81d16f7c749e71747140f49551d9a7a70bbc8ddd2965269411ab5ca510daf2e643fcd74dbd143dfaae6c5a96a729bdd728478ff133b94fe42d932bc62dfd78d181daab88c830b7795fa66163f72a8dc24c98c7ad51be0c919fa88653a70b9e5b6d0e43146fe6fd8382394d6c14001ba08fd978c9fcc21497545f7ef7c0243c1709d05ec00d281fc354af873f272f811a72a26c26f1c0b583c99b75c98a6c37f9271cd878e2afaf85b975b6ab5722488cc3443a0b518ad540535774d80e68a4e763c4dab4194f464140727d44c0e137353e4bdf9405e02644c2063fca51a1b5b8e31c87604fce7e5ae27f06a517294fc1730ae35c376e09eafd765a924c9c483e31dcdaa210c905e0adbd9c3d619ebf2a03fb6e66ab928227761274389936b2c7aef062a159bd61a741d9a7de672091e7a38bc1f6e5ddb49290c3aa61b95f2dba44c4839fe54c6f209583e422c72e50ad6a689e6ad69e94d7fa242d8ca4b37b4f7f0405953f402f5db872d04b3722789948b411af688bf974ab5fb949a521474e10a1be5e485c37c9af27d8cf572bcc401ab180ad61c0e1f74622e59cf9a4c9b2506146b457f64806b618d82cb72c78720170aa1cea7abead1a589ff7875a1a86d64eccd3e9bf1b9a1b225c8f73dc8daa2b10924b070c76c53582dd705de728c6e8810c36f793f117129bcf17c7218bde76a8e32bb59fe15ecfb0b700cfe5d4df3f4611b0bea18b56f71485dab2c7a099eee25f9b6f622b5750f754e183cdb76af0fd34539921e03c5db41193e720425e09bdb882caf0a9482c040881b216ecd6b8e9ddaff2d3f8fcf34152ab172d00df5cdfa59db847d6bef1cfcf2f917e4164dd94c169a606b4552b9bcfb842e9738210a2e862832d19019654fc1192a9aa9959ea5aac4f1b52c42957161013e96868803b453a0c4d6ac881635f8fff7d65197df0e73856fae339a9904f01c72fdb992b724b95419c7095977fb8cbbb59561ec23405f29d972bc87632f4c3e1e637fee59e23d47c0d70527bdcaa14360611ef136f92c5a338dbb6e9549c4471532f15e15a6919fbeaba65022ba983e76c1e92023989b8be7ab98bb9eaf733449239d6faf926d87ad9ae6dd55d824eeea03fcd6cbc13db247e82b25d33fc3f6723f2316228b7425f7706f128c6c5db5de2ee8988787c72fa917b44e6b747a6b72a19425d5be6fbc5657a11a04e5beb4e1460606742475e41d02c022ebcb1f405473d288e51f04122b22c80489376845a7e08ede7e74bbd9c5bbabff07b8e860726828e7eb8e79349fa9bad79fb9cb9b43d82c8dd842b8673a7fee4bfd4094fe25f2b824bec2e0ffe9c759ce6eaff688491f0134234e4a8c6cd18a8fabb028a9727c29ba9959383ed3b88eb3d96144e845bfb38c78c5f664c61dcaf0477765b4677bc809dfbe451b39d871e61fc11ce2711a64e3f65a405a13d37b5424278b6a2a8fe6a8467378bdc158f292f8485e29ec60532bd7825b8098c0933b41edee1172ed53b47e3cc9fd5cc342e2b52f4fd1e259412930bace72555ed838d7debd7d492a0315979692f05849847a848c2de11514336e3f1424d625cc015cbefefcc37261a8472a3333c6ab1a68b04cf23e593f735e1c251e8e34e84a351c32067a001689fe670bd1a19978f264df0b7e6df50cc1aa7d04e35734ac0089bcf494dd1407ac625c527d37da2e8b663b7a8c1ae712a8ba3df18bd915f332b04e24922d8172f2200b61b3afe06c9a13be23c27d105d0f1d5b1c7893e9fdeb4828df732d5a7280b8e032fd000673dd03ffdfc9ab8665d93a66720115ef0ce99bf3916fea727273be2d503d5739f55fe9e8e30201e4b778f5f4f7f955dc6db2fb511b743c5b4dec56726efe81e237b8e2fd7fd0a63ef7f86a3e2df9aa2724e2e44f9b5121412db4bf915c3e3c0c0f7f89286b1e00d90a44ad9ebe95eff3d0cbb8721e962699727f1ef2583064d6e1550853491c65304d08fc44c8aef649101232dc0cb7e29539804a5125783b4f9b0647f825d51294dcffd5c1101663e69a3c8c9eb55be4557250d3648db1b27970405acfec1fb4fa06160b59baf2515eb360704b3284675d068680347099cfc4c3216982197898b50fd4d5f214bd32869cfef46e0cf4f6b5691a7cf0bba2498850a7d3fb1a1d44fd9410068817b9f03dbd1d79f49ea3c0be3bc4e4825ffe7da5880f61188111e36133dc776abd0809ba83cb086eeb0eece172c13f59fcfa36596e1b25c6a2a49d5849cd44b61c4419558c0045bfb4dbbbba1c67c0357f36729dcc3a75f82a23424bfa315f5ee416120f0d1f0a7e9844153f0ec43d65319d97aec1824e0fb0d9641510b76c46dbee3babae061e2e233e0fc6722c772a090d521586713ba9c981c5931f944445a17b6f62679d7263cff9243272d76b1ab1274c67885f70ed602b31e3ea3af6e5293b1046ee52179c68cc3d532872800ce364bad02a49cee8be7644627be257f5264139f616dae0c1614165b672964409f077a491bb62d7f5681f290599c8af7896613337c1cdfa187e08cf7772e6887a43249a328a074efaa405d9af02b93ee25a58c8a1fb9f5f46784dc131721cb50c7fe6895638dc3fad356ad9bd054ccab9908bb4670b897e58e6d0c4526409aa54090e771b3469651083557a18b307aff24fbe48a369f6fd3a5ffa0afd249ff436b151a1f9ce60ed1b204b20ed2786afcad96248224a124921d822e3277283cd920aa49b0f6d3a4d45253c4fd2915c1b5f26f57c7a5ffc0cbee01d49bb4b13736eaec61fda17c58bc6d98fbfe7576b1ed6a212b00edb34840f14d450b23e7f92351eeb50a783f57d7592a47b01778ea0c6ed1e28fdd55570438816f0a465a9be87ae5d1f713913e2bc1c5b56685214249f8734a77a2c4f12c468095f9f573de1ccd7777ffc893209030039a82ab47a88c0215cfc16ab42532dd8fa01a763f1edcf9f621b9395288e223972de706d36fb9bf0686057c8bd361276e1f49b72a7fb983b7476459303befc4a628992b2aabc1493c5960c0a01f5b934d02ea04f7a73a2e4fe3a85bd893b4e4bc0344789042f1b7750161e856a3511710e3fd930ad332514927d84f7335add8f37d0901a896b6a1a9ed36ebc8bae6ad9bc6fa572892078d7da41ce4432c368469b749a9b1c8bcb893a7e093f0900988cc8192d15e512c3ca4112f939a132d6ad5e5b8ad47c35bf51e6bb59e4cd7ff0d4c3eeaa074f5ab174ac802234be6dee520c7fb85f15328769a9934c0fe2f1f80826c262720de98bd8c22ec2118d726d0441750c19dffb4d6f981cc8c13532dd64a506e734ba89ad99cdd6b779f7d5e0dac6dfc03733e809ffabba0592897c8e7f8c6fb8178088909b2832433816325374927f58f206b9fbf5fccab3e0e9e6a9b6c8639672ecfc7f666cd6fd65b6a943b6786f090688602eed3e3417801956af15d1a1de723499574735eaf18a32a8c32cdaab059fbb33f6469df7ae24154500e8f6ef7272a6d4938e275f590771349c4d833e95b27c7fddc6cf6abd597de5d1b2bf8b03426414c984864d9edd151d4e7a525f11d1576c235ec009a0a78a0f2d90d286887261f2ae51b10f78d7be2cc96bb0fd185c4c5933e2b27e79860ac48c2b407c2201db4fb53648618098ed7d70da2c103aa8ca6a2ee4f80c615967a0eab34b1a0c5bd3ff5cc209c61fc0f022f1d0d2b5f6fa82f4a74e8ff42b72d3775c09aaf1c1720b026b43e12db7b629599397e549d905acff5b6783942d99eb26b475d28473263d80df48474101ad84200a27d5eb10852bcaa5d5b79ffab9a5f7e3383f935a39dae788d7a489778c164bf7c63caf0b2b883e1d85c1bb851eb592d48781d1a714cc5ae8ad6af070b85d270198195e2995f7adc19f885199d2d5aac35e2cb4f1725dff66504b5d62e2e5497de444a599cc6618d6c0508a519cf0a85176863b9f34ebc814206620e2b8920847ca5e1d35abef087fc9a745d0cac228d69681cd5c72ceaf7fd120099037e3eb31fdb737e07cd8113bd286032cd2961233c2b47638723e54fb453c698a194e1a93973ce7dd19ecfea0a943823f3a0aaadb0d231d167268c8d3c54d524448a2d206453f39cf4aa3a5d2a4915e851290aa4da4637fd072c5d5c49d5fea288fe7d6db709257a92c73fc6ed7958b36761379ee24bacc9b166de09556bf99942a2e6862a5ca64ca78eb42dfb1f84d342cbfe4c8f4d967f072303fc5a86124bdad2b1b16ff5c0ff2c6e23bd4ad11586b9d03f4b4eca0dfdd0b5851685cc6ada423f7382bfe0bda81fbe2fd203340900b1f2c297348c7d160723c0d9e46753273b2db964161f1d092fd7df54e864160302e2061dead46103b519fd773dc98813b600bf90af44159fedc673b73980810c203a5943db88973f4722676ee49d9f2c2bf369f83bd6686b872f164c666af3babb339be0d2b6955015e225cb0c402582a94b6b4e05409cb0b1b0f953fe9e773ea0221b24bb74c65b9725727b5b52720fe5e9179d88973c19cd29453b6f4e61ab203657548896463d072c88e04b6400ee4a8b5a4c67f6192053e2626c8063aaf5d7624d36512c752bf722e0a5808c8906076e72cce60d493fed3d0a7e6d43683e00c8562931b866d6602a08f754645e0f42ff5deb235c3fce0231033de3590785da2bda67aff69d8c272cdc777bf30448fe104c56ce86106f132a5c3e02146adceeb3072a03012ab9658bd9b7e2b11bcefdb23466eaff0ea9498cff8af7474a760fd0a2e56a02da4496cc8e6154509fc334a8c986772012bf3d8261b2e1cd2477a0ceaf68f7972ca1232cc289208a4e2c54d4c1cff5cb85bb186cb267517bd42781623d83576be72500ca345f17828d7b3cf83760484d947842f66a01c5855531ad63dfae807dcf5b87264649935dac5216c17fbc374bfa0b0604bed6021f3b41d867a69ae7a78624027751f1ae60ae92d45ad3adcdc7dfe65b30cb6b1858d8d8fd967f925e22a4d1572fe6c72ea2c4d8c3b777edf2850907ceeb4cbc17035dc7145c0772a0bb5cda416add63c3473a8a333021f6a1bb0cca9dc38ab3266a62cfbb271e2bac83b67ad0a0d81b6f1a413dbfb779cdbad4f9e9d9abda3d865f98c3abd716e98d22a06ce72cc97c495e1f7fba9e9a3bcc6d61577df8c6924c1b8379394a76f4339798cdc725a21990d061cfca49f5495747b361b0396721aa6a9d6e61ea721d64e7b27d227894f64d3c940609db9e2842bebcc2bc1aa209ece6de5fac857a586a60139ae72b83c5054af758c2f0398f826240577e88c03eadcfc16be6fef4375aeb85f9c7217d5a612442545414f40a4cf68867c9dac599468a4b22935a7339e88754dfe72cf7ee6c46b9e68b03ab0a4e5471bd5b2b6641aefe23d0957b0d192303f206518ab78538af43eaa5465ca33f08ca037c2f6c53c4a887ddcc76d2dbeea521e756093405cb50f03561c1ea073f758da143acb036a9daa411de83e96cf14b485eb3c4c036e10baac00f2611645243de3e492af7ef162639dc21b8738cc5898a26f5098e136f69afb81d664174548ad36f6c515f08b873438109a41262cb4ff8c64724c6fe9cbc1f36496e4b60070a0d920746151805b887092df74bdc55e4ecc1572f9e35046ad2f490465a508368c27b3c77115cf0715b0bdf65f888367f4b8b12c04abd2f55e64e497337025c093893e1c4899ff8c1974fffbe609e6bdfe860b2a6915f333824c889f67f49ac76681c746eef7c28ca25fe5886a8f1a8dd5dba27225a6102d7aa780991d35f53921ed0a6ce7502fe74ac907fde25f660e7c4c5b7284db3dbe6a9eeb20d4a6ffabb80c9983c05c61faaf044caa2edd4b6d1a30da72163896d30bdcbe829de6c697cc78262f8121a9bfeca814324fd0f72efaa062291a7fc897376281e128dab3456ed1574a0a0b984782bcd6183ec1ac844ceb087264fbb261418c3375bfbddb2dba987017e7f4b3d75e06675ac0a8e71571ebfb7286c725e4f9ce4854edeafb144763dffd0734c50dd22fbbe5e75582f5eaeeab72b3d11bcc6eac9bc5d0f275c1f82ebd3e504bd8248759866e1e1863a12c33ba72b2dea55eea5b70a0098d15ccebc234cca3d961cce9936d28ccd04eb870bc0b720af25149e25007ca5c419bfc2b0c743cd97fc6049181e11b76cd2118a815772274d06d118c5aca14b7ad7b1a0c27e2d1b150a7a97fd5c3f21c9930a40d571e72a9a57c51f5f4eed61541fd05a536c6e61e2524c6896f4ca46a12f374ab9327302a3ca81dc78af2d9a95e5d18ca3a68813008020b7e21bd12ec4ac7a07159ca729e4e24dbfe47dbc8bfb1070d1781b81e8be5d2c5fdcc7f39245c8c929b62235ab0184da76f97f279ecfb6f1a01c7084e9ab215ea9b3bf995015056bf0f9c9f3287c9b2a29eac01a4069f1b35e500c5ac3b900eaa2ffa61e439b91a2069682c066575d8dae4456968e95a5ff1bedbbd8d45311e3824ad8cfc8928de931958a4720f6b2148c33960b1a6ff4856c789253595aaef9879f1842b873fc1286af0352e9ab74c3fd70bfa7db73c2eec984482a72b0d7f13d519f6f57ee23de8e091c77255d9387a55dab1ba82e9c8cb6becb8c3c73b9f923bd2fbf40035ad4ff308f245f7c987576452e1213d6b92e85c3b19fbb2222b47801e4be19833bc24fd39477230dff90c2f948dc84883bc30ee97e4e40371986b059158ed9bd06165e42ad072692d6c4b8e98e8cb2c6b4315df82674872078fae4d9240752d5afb3d0bcc65198e47d740f90603b0f683d348758612a85d4a755984b9836a5ec29db6e2e459104d43f02abb00ee5fb3cf58d964c17258f318ad09c2cfd0f27e3f1f653e7ed072fd12bd35451e816eeee9f8559b094916b436df04a8c0ad18fc317224a228e0728002a690a144b483a9de2c1a5282097cccfcd7bb9ba4f2b3f6e1a232e78a6272c3ba6003e2f53dbc053a9ff5debe7df6319a51314c7bdb47df94e0b8534067724f2f0f882bd138fa2643ed788075cc79b20507b1ac2829b6811fd092cda16272723f61414de27311b56f283893e8451c69f484eca0e64c59538f93c01aebbd72960c4a385d4f16e29a701cda075dc51f08ad54fe5668433f1cbca698396b0441a0942813b2bfb5098cac7effa6f00989acc80710e077d207bca7d7d14bfe10193f8712b507a8b498006ef8d57cfd8daf56bfce6345d26282f6b0194b38672a72495284ee5d19ec5fb9de5dcf6c1309b8585c41d1006fd8b55eb52cb386db6b72d2c00b196ba5e8cef33f455ef48b15aea4b14d2f61d8cb516256feb9cff77372e9e3f594644e3e39a2624b5f0ee2a762c0548df09e918a6f271bba011738785473d2de993e0b53988b03866d7835c227415b083a7d32c0b5c142070c535c2f72b4741ee898e9ff76512a82186fe33a5f9ea75464fbc29e929da4d8e6c7c280723f18254cb8d98d1dfc5c424fe028457502bb756302703845fc9a5b05c6f33c72bda8001e6dbf9c3d5de7ba26c0a6e2458e3c9cf6fcb96e9958f84950be376a72577fce219fd4dacdbda0ad6f52611c0c3c4d289523635ff40c019e8a0060ff726b04e4a8a0d8d9563200842003f969854fdcf12efea433372f7c9d2b2a1bd77233199c70f165a07377a28b70d4fb835f3d5a575fca97bd2ca371c5b277a28b1e615e74d9767f40397a3aee2f501ea2c44de51cdc784f7f88c2b478c58c670d720a6160b19f6334e4f6d999073494d253860215260d94e5c773969680589ce36d80284b4deaa95b6b6190fa9517e7a6de2517e936acb10c1fba205feb9396557293de016f3b24a28583e139c0f2459eaa12836b38d7ac7809dca884c2a16f065278cf94e107abe93fff1709682ebb4467f260ea17273955b8318c0b949abf457260a83753b3bab0dfa8e82c8b6c397cfa09bc7b19023702d4aa53e9a152018872454aae3ac999dbe79d87b20f12a7a2bc600a8410b9287fa7f9bdad9326b7ff722f56567ae9e72a9ea907740412b3ac256138e48b289a47f2de5335931aa808723bc93be5ad533c2baf60924852a0d4808559f7bd3f5d9371ccd66564d00a35544bc4247038e289253f2247e4c8f535397b58055e9c614f53979acfaba0e0bc722daf7c519e1769009df3cc89a82aa20391eb37f2d29b1392b8c1a42a1176a006e38cf33aa0619fc19d6ad6baa0f1b6a0e7ae3a962ee482d64cc2eaffebe26551b499bc003076a9a4020d6ea1a4734823e0c67ad17f2871ebb31e4773310f98723cbb69846a819054afe08591d552697df92c475c964e46567d81b3f325a8ff72b62edda05ea8fa39f7dd9f6ef35d612402365876cf38380a02f55e210ac3161c114d7a7815df074b50abfd657710b4ccdee8391ad2b1cbaad112ba0f016b08724e0d31bdd55cdc6544e84b86b0b4c423304cb89f92aeb2d6bd572db1b4402107cfe976db4314c0f71676231e36dee1d35a1bac5d042cacf49614ea8d7bd424010cdd328266173a13df5952c85f62e1eb4166a9f4b27fa7d262de34c75fd4c6725e87df53608b84c48fe80ff0e92a237c3454ed321f1f490db5f21df64d4c0a079fa148a1465692bcdc42bb741aeaf4ed979b7fee83bbfe0cc50317b933aa7c7263277da1a494c9e79615f3001b86d7a59148eb80dfb1e9bb1f81872e6cc7467201bcaae4a1fa581d0a27d8b35c007130946ffbb8fb915c221aa3bd681560de12c28e1e9932b6d69def33a4bb5c6fd8a4b9e9044d3bced0943bbe0b04130b8f4603fca8dac37f78a8213b889a3155e5ac7cce21b2ab4bd1a7bdab05347ce89272d853829ca3258690ea214f2f5ca897be97ea1d77cd6e7da0bfe5e9f442770771aac1342428dc7190a24b422936d597930fc4f61032d8c847bbc3d5af3eb0a26c0833f3bbfd998d935a1fe54eebf6aff5c14988775b7a6e712e972b494775ba72dba42cda62d44152878b53493469b613c68b5d7ffd7d41ed731e4330e3decb729bd68568d545f4a582d3083f41317337114d81d7792a2372e5f2e28ac89d2c44480a7487031226938c1d35b00810f6efbadad13aec26c14c811465b44ed7bb0e73f7f4d3db3c5433c2e6f725496deef0b5c5ba152762fe130e1b4b6904806172d6ce054d9176fb22d41d5ab61e526f95b6eae73438f769b11f32db065dbc0e72c3c42183ff605db77291dbbc54116b24f9882be04712ba62c41ac0f485b8f872454a2a3116799968e7797934efcf740f31267a6dff732bd928bcbbc2e934885d2f9e3732988e716373e94b5dea37991690fdf93609940a28ac26247822ee8d5cd3e580e60f152d4a8858d36f72a9f0b77f646b2fce700f9fcd951ae06b9c0558b0d16242d8f8e5f7afeb3a94c505c8305a2a839f9c64fe3a2c24306af3a1db500adfa1a3a05bbeb608d449e3201e371ae499c6fee44b09e4cd8b36633911727261ab86c5a7fa27520dac15179689586d0bbd0be97fcd0fcf1bdf31ea4e1cfd72986b690097f0823ad546867bb13cfcfd0b061fabbacc3a4045568d6c419b2e721e79767eb6c0a266e2414de47b56019f58d1fb53a4056a1a542a10ae4b622f42cd927163c3e6ef30f05a8a3fe31b0923cdbf96bb2da7ab2278acc80ba33d0b0dad405d6e0154e5e81a9170114e77c4829f1554850d5d73dacef26e9777bf14727f480be6dade61ec666c6a99c954f392f331146a494c4c6a9002d43fb77cf03549f61d3a7eea2dff56c00d17e2133179c985ed58be11347ba79432c723ed6072fafde4fe09b66a48b9819631ca0c66afe77d2eba02ff7dc03968d81dd739287262d2d052afe3cdbe89dd4d44c7308de0b3796426b976c34292650b91ae51b9724ccd0a741d337311328b780b3d85533f806140601b74d789792ba942a6bcde72a22f65d3dce815151a4e5392b978616b61cc39ff911bfaab491cdb7ea57c0672fd9a2077c78a810828c2f78e1f87cb5ae695d26ebaa1699952a6c54dc6d32f7243c389f9f9c07a3787500dbab283b85770329b8abadd1d96b50219070fa85c7285c17d37b8917926288a7f729c3ce2f58e8bd1875b1a9f856644b95d6ab2b572f15f481fda66217445b77ec780045dcbaab1525fcceab8790f4f58228c47687232b0fc5e9aa1cc6474bb20e60ec27397757b29c522817dba02a788dfb9f3c96d9b3374743ab7ba46719312df18d2a742ad53c64b4a61cdbbc2be4895d7bac627286094f704f41ce97d046d07e18caa5abf3e5f0f17686e2f29026a5a415cdc72997b5fd1e896c10cb9a5a7297db006c2da9e7c3d0a343cc9c1794f118e934572adb19e4ff1641f49f7e5bdd2d5d00fc09c71bdf58afdcffc5f270a848c21db727ec71c4ca38d6181487da26375049c2223d05be66ca596259ca900fe892e3f727ceab54cbdc409cfc939b0789122e0020473056074a3ab4a65bf140766936672057b9398cf2282a51089971eea1425bd6e3ff0e2ca0f32c8dfa89ad5f304d65065cf476498d436155102c5776acea6ec1a3668460e701aa839f32def31c0f872385f37ce3d6492d38b8602a8ef5de005eed0b1ada9c31773c23d20aeff1de772467cc6e99e03de382c70bb4cd8bbd54c87b8693b9860bb86ab3cd0008d705b245ab4cd64d449e04345a2550e405c0c25c7b39eee3b549a637702a1a607158f37b12931b3325bfd041d8ae7b2517badfd77e4657ed2ae227b30c51f6806feda3d384f943103f44f0329e94d8e433e1d70099ea29b621f60f9980297ebb254b972e60c1c807b47db26afdac65b3f1ba425f50cff28b42d38b734571c59e6b9a072b323f661e94b00b5ef00f6dd5e32ed92db1472723734e9a58a12bb7c872f4272b5c48f971246798a4ad5c9a10ca71f007adfccb7259ff6fed1410daf47cf0e72123acb3ed9a6adb2f52453b20617a6c86ffa99bfa833c173a03c12212b067261ff2b078e125ef39d0e5b72f808c16cdd09b49f92d71f16b28b468fa106ce3972e2fce33bb71a92bd9a295186ce312309a67afce278da56a54a8e0ade7c1d2272b554215412ecc3c4b23be0920bac437f08c2985b76d94c97a5f30f9f4d935772e3434b16a37962590a7fb2649e4be3baf895e0eff841c35a7d81a93c3495982e69af13d32ce520eedc285d5e0fb1fc61f996a468704835202f051a81daab937204a508c77e8b322985c18a399270f9b98360058a9e71f2bb26c59863d86d8c72ec4de52adc95df1bfa99b729dc687e5e0a5d9225aa9541677226c9a1dba1fd7248576bee1511dad60e828ff02817ada6c5d4401b4c557ff75dc1577e3c7bc371bc53bc4d1be84be1732e9a26cec3e2d8e8add4180334a10438d6f62e0ae58e72c8c26e26c9add2340edf24b2d9c40431741c69f5c83ce89fa2b92336bc55ce72587ac9e37289b49936aa3820c098d92047a7fccfbaf60889e321908727c4d46fc943dd155c66c4c9f92103fcc25a522592da3e61a02d6ec22499d66879b68d729b80a8ebb7e42dbd7a14fa3c32ce0731ddbaf77f0e0e03cad72515daf48f78725cd58bb3b7d6775ac3fec956c703d83fb06a2ef2b57b5f045364a6f574a4da724e4afe2319cb0468c4ffb08f073e4aa0ed47836cf73d16bcbdf053b077dc9e25aa972937498b326b24c9613d16b88ed05cc0eb0247c0161dd8ac78a459ef41502968d3d27bf44d6a4c323bf0a68ffe307f2517708ce7fdc3265791719f512e72267274af084481725c9577395f6d0edd553da98815ecfa7f7be526bec9dcfe72a7e1b3adce7ee0f0fd35d5c0ee09a5f1aa4418b694ffd4f49a446859409ed74773786c89f4ecef1b28d430f6f919f58f5ca1b3298d47ea7adbd4723ab08aac72e10d338fda75350173a4216418b27620672365843cb9e1bc79778ade789e9a72079c9bb4661c223619b834230a8e3330a2770da866a46a0cfe5b1a6c10a51c7235376ce7c80745ecca2b173a163fcdcc3ae1d767763243dc013c464ea3289f7246816759c3353d1a69639d02f4c1fb4ece9564077d20a33aa2ad900cb1dfd8726fb838951fae399b5302aeddb499ab0d81b06754b7607e708a8ae1b55112f563afc79830848f73d2d2e23abfea2d327e2181b1e70242af150a86abb29d7a6819ada0dfb896d70c2a81fc24cafba872f34a942fb81b20dff75f09987a32cd855564556bec64b9419966e51deeb51f6a34241740ba6904853449ce9f83191b0b613aa4b2531f57d834fdae27b5299e8416b10610045ca76fbf4907b0b5cf92b41be3c7be2a91f7f130814901952563b867691dfac3e07a93a493106300cdbc36729eab75bb2711bbecf49c9d5a23e78a8f9eb68009d1a7c776c94b49c7197b3172911971203cb4b5c25e77a62879a758be9bbc9b6829ac7b3dafdf9e1f7976e2723f10119649fd41e2f85375c85f9081ede59b8c8d257d0e1ff68d6dd8444acf2799edb8e7dec11e24fb7361346c2b8f06731ccffebb30f47b6d45f01fde7a65304406cae7c18ff20c537a0a59c3b8df15b2d94dc7f1c89d2235c9fdfd132b6b72574f4ce0cf697449c112184cbf1c2d70ccac7b3a7f011f135e497e8ebbc2b558dc4ba037e94b1d7ff950935f0623601a2180b89107fe1d4cf7e31ca1312e65027d39749a24ffdb5262e3c1dba36c66bc4e487037fcb8f3ad2addd898d8021645f51d65dfe36ac4be851fca94f91d180a2ad92dbe1e4b0bfcd2924ff1cc844b0efcbb576c0b7a5412368d6f824934473973e1738036581f03a76bbd02899f7a7255c1be92a45c34b4c3144d6ec28c7ecb8976f0a0724464954ea7affdb19a3772b4f6ab8321cce1e3b6a1c5cd903cdbd2f6ebae93ca7cbe03b54e77f8db4ef83c4e8502defcf8a3cbed98d8fc149ad9d229847bab888bd999cf935f61f7b1037210b0ca0caa89ca7ba3f1be3b3382ebd074c77cbf554f7703370d3e611ca05872ac61366d69a9d68ecd1dcb8c0193a980cbfd4bae6dc1b9e9ce0edde657cb6248f01d6d877a727e1940b4a90e586b5208eea825f607fbf3969f097a5ebea48772b4174522ffe3168596a269dca2e118bd5c65e15a520f26e040191388c03dbc722fed2f0ee288f5fbe1207b549a28a42c8e83971f7ea28f8f0d9cb5a233739e720ef74030a9bd45e8016371305adc4d207c04b2ab0bc013793c892352239ed95f98913eeb193ea4f242307c495ef0be4a585108cbcc1202574637943e69a1b472d41309e58d602500e85a3d9031b9bc23e71e761963e980468f5df17c67a89872ca61100ec6c22c168e11c632542cf8187fd1cea3a7e33abcf511655dba4a602734d940ed11a01ee2b53c16ef77784efee9964a45b6bb071821e49f7560840b6dfc08f839cc78fff24af3466095be608c106fdc3e49eec04d3c2854ee8f53f172381c03c318cf6f4c38f7d38f6887f1c4315ab55100955e089f4dfe429114b572c4dbffd31823034c3f96d9aa73c361d7e6f9c5058c4bacb3aa700ecb1ebd383c09ff797e732a4bc72096bbdeaffe9a389bf4d3b12fb6b937f8f5e9db0e8b442713240887f238a00bdc3cad8da59d9a98c650f379d4cc3cce7e621626f7017c72d39812985d1b1877fc87d3698afa97e2a9c870778c005266228094356e11341f4a2e14a5239219d16dd0f3fa1d9414c66a3ab4a15ab072f59d01f7ced29305723315af63ff8b13347a5880aeff98f4afa441f68979d2a2b3eb17903252525761ce80f8cea7e9967c6c2c4342d1e07b7bd75398bc466fb7c5f234a067a5d54418a7264b1aac61a7b90340ce64b0045f8562f46ea33bccf716274dae1dfd8cce460870f8f58845983f6bbcb8434744b088ca1ccf24ab5cd87a8e0603f951bb7d7260b7c8c3cf7a927e4a7f71cf4169051508c2e05ba14265798d18f9ba04d8d97299192f63dcc31f18d61a5741d8abc5a7952db81cf43d5f7739298a616daa3472e99be50c337892032275acbf41d4311f1430b51d3eb1343c35d159b0ea57cf7268c50e3d2c9e708c57c676896df6987bff09a45acb6f1ca9562539ba8c95c0504a4347b0d91222a1dafaf7bab6e62b2aa4b588fe626d7db2b10f53015be9e21281a2f728672ae179e3c655ebae9e2723c6a28e0649400142ef2b6ba8cdaef013d901473e9c25037620d2389ab31ccb1f8f8eb91ff277204d4f1a067da02af2729d67a5acc764afdc349bb8242250a124adacaf8afda7e80fe6c1591b6172dd72bd88a86bc94f3d065afaa5c6c7fefd9ddd4c1a140a32ab5f2f462355c749fd721783166520bd3647b3451d22966cd24ea1a7ac67096dcc59c2707dd44fb4261864dae67539cd27bd6d700bef50e9b84bb4020ee8692d69818fbcd3da99b9857265232c3dbe69e2568de138b05e4b827493770ae1255dee1b00a186735cf0f372c48bbc798ed3e29d453448de739d9a009c07ce102b37e6cd5134ccf666ea7372501e030559d13e36749c597701bd0b07a65962934ec69ff3611db447744fb5729e47101e576e53f8bec92de8302eefa8547ecc49a940a763c12d277ade62a972b873c63a1e6262079094b6c635bb7ef29d94d4589a4acd0f58b8f87e830826179fba0b525d3979f9e0e305adf1cab7a898ec234bd785429c2f6a95ff35e35e7264dca2bb856df7801686cb1fd5ad9362d7b4c61e6c46c56a1a2d63880240ea72cd6bf6927ac8444843e3f05adaef19cb3e255fdd1b87767cc3cf54dd650786611eb96c569625541256db0d82fb597192106465e08ffe92e34c60751783ec0b72f48f3f94663ea1cc99008b67709ce48f94604493b109ccaae9228bf9513a4c727d0caa7359f200d304be9f5265adbd31e6f9905dacccb12ad3425b118c599872dbd1c06bea231ded8550d67a210919d2d225ef03c1b1b60e9a11945eb2cad9727e05d6a171e77f153b8c34ceeceed31a21c5417e1a0c21464c6d5801174df37249de99302666cc7f2aa68cefd7c1eb87a338db15de9b2e2c3dabe9aa49f5796dfc1260ec0db3db083752e8d9299c0ccd82b831f1e5091dfd5cc12f39d74c0472a627c82d8a2b49cf0442ca9df26c7107302531f09b1c08e86e595ee07db2a4720250b17e7f6d6ae12a512dff34474790c1c25e90d8d5dfbf780e8890d6a58f723ee51feeba38fb5701c03574684dc72c69a12172b18b7eb57963629e4422cc7256179980c5a40ba2236de93af562de72e5d73ff86392dd7592359782d89e7a15f4311be48cf84cd8a6ba9b54fb0652edf9821ff7abe74bf8ff9568a1f2d63a4d9cf336d452855be5cd570f86901c145e3d74b1f92865accaa3d5b0b37cdfa27258cf901fda3cae8d0ff66a51dd8643385edd38060f85deaa851adb96157c086d6dd68b393ea8c9dd45419e01ffe1db5b27a0309188186656125a38c4bc58e0723e9df7c9f12c7f13f08cc17014bc226b7e1f2bda91a353e30b76e52f5c028672b8539ece0e566dc2eb0330ed491b18ec61445616ecc1527cfabb20569c9b3a2caed3c7cd9be0b67f1c0f6dfcfa0d0a821ea8eb3cf530d2255e9c9643d97aad725c193dc81eaa7156454fc4b2ec520c5a266d5dc1ec0739d04b7c09d37e521e3baf6f46663a20b614391efb11b7f0cdeeff969fab106aa788459788d642cfbc6ac523ce530fa790b6d8cfd0d66496331c92f636e03a2cc33270679bd5ae3e32722b8f6c4321d811bb7bb60bed06a234a656b2f29c8ba176e8530311be57c21172ddb1e00c828f2450fdd767484b1c2e0f484fd22470711315ff9590678814ac72647bf0fb2ddb339f2f8b5030ff059a61b5b49cc6e96d17b0324a01982752d550a1c052efd8a3e7e8385202d610059cf2fde3669c002e962e16393f488376692b94c0a715da2a7cbaa22db440fdd4050b68bc8f0d04d824af80a532c095478972fab62810640bbd5c5073903fd807fbbd8fded3b49961299f4eedda94d9bbf372d806b208278d9bebdb2d4bff1ac7e505f408ff3522bef04eae4244e0e94de62852bbc3416ac39eb0ae1a2c3734d735ddf3aa9a551292b8428386f8309e4556720b73932b7b09611504f38f5b21486032ae04676433f7874afb54f1f6c94a190cc9841bafa3882ac9742926259b38ab8ec3f0829a269903c9efafd706adf5bf722e6188d4e8e9d9d0bb974b3de802221cfbb5d4e4f77604a4d513dd72840c5372f632aee6e6adf1c7943cca0a042bc2b0a71348ee83df322f17ab21c71e045572da3faf06a9a29574406b73c5c03413e0a078229ceb4dd693063608d155cae872e271107b267f81505e6b6835b402e1afa65897466d87f7c45780396bc37991620a959670c1ea4d72146490b607af320928d0d14aad3a81c935cebef30896fd1be667ca92aba8fbbb02180b89f642df46c1bca03d0fa2564d40be4a49d9cbc472ab90e42d243b71f459386281b962ecc1d28f04c71713a6ef2c1b5fcfe1b5774858b8905cd141ee63850ece8f89a6248e2cd1530c29c376eb763c957c9d415f72c6e9ef71def8782abd9204a8ae0d19dc91506b8ceca68b152ec5d1f10ddbf86ec73ab5b039e323c599f6719315ec000ce1f16e7628a927817f245a8690d41272202e88d527448c39405343c938d007927782d2b4fe9e844d41f5d7b311400d729250e03e667d8ad63d64c77d4dc40ab0e7ec0aebba75904e64941f3e7f7e2a09e713f6a1f11d3ed705894875d4e441b37ca99ddc4076c76200b581ffa2f1a764644b6a42f8031c8e29aaa1767c3b5198082f122d2a2300b75c313a21aa3607724aa02cd0df07982f4c46958548fe80e96c79a9fae0e5b393c46056b73b838a5ea8972346cb274ce3ca4d0ef651741ee9a33a5997b59aa9effcc083cc22aa1072fb9f432b58d4c8ae2b967313f06512a2270d7d2a1ef5d31597a541f4edd0e6189c0dbd873719f4205d1cb1c6567bd2992b2dd3a7a8a7f15e79999d73c4c2dd3f4160c7db64beb18bda59f0158c4eee2692e005eb932c82a153886c499bcff37210eb21e1ffc8d5a980f208ce5adc684f9bbd1554e003656b2b5b0db35f97e32e6cdfe2bb05b2180d634c22ead4025ac5a7e774ab155aca79a46648ed85fd3951ee0ce2049df3504ecc9890a204c8e0b948502d33b91dab87ef8f1ace63f50158ca2ec1f9a6769057b2113e9a9a5e4a50a6e5d2bd22f5c046a1991796cfad40585f565bad8a92a87878340488cb6fb99839390ee9028b87bf0fe17fbba90fbc728c80a44c97444108826d0d61c635e6bd67a686d73fe2cff5dc8bfe5564298a720f69c83667c5175399945ce2697c5a3f11ce351929ab0d3a64cff7a536c85302f55a17fd2f5e2dd0e8d3a832aebb9f067cd36ca233b18ea204ecc777d44ede72452292b7b237f067e36045f0512a597f11847fae7d97a3a768ebb4847561ec726592f47eb696c01efb43ac35e72d9997d26c456403b3b8f63b31f859e4e31e0f05962053340f53b3fcdda77b0a2e95a487341308a274da6ca9a3d9ccde805a723c14ac97b468556033a40af1bbdf718e50a6f4d9c81f3f0e6a637a71de27707258b1e190f7148c73a785d170c0df56f87b3c9fbe5809d813b7bb2bbbb238f65174e85643eca1c261f9a371b643e45ad0d2b9e904d166e8b1d3d09fdccd03dc457f216976dce3e7a5b5fd3bd0a839bb598b3ac8867adc61a6b1c202bd2d2c6e729fcea2682f21e131305815a519f1b170a6504d088ede5c5b14a60dbbe6a5727256d81e0d43608ed2db0920c9c06d6d76a5142f6f075a3fa18d7ba0f46ba945727a85c44e75e98292989a3cc2abeaf0dbcb3738e0485b508cce0f4dbf6ebd6472b0059fa514af6d93ad88938c2dcb2637422d857960129fb23585d2cea275691302770960ec744f5e7e494074d1c12a311f0a8773743ad2e8b4b49dbf6b355e725138eeeed365da90ee29e1f097d9348532c2bd6aa87826e9e2da88c94c3c2511f273639581ac64cc4dadf7ef06c6907374e322e74826be6d33e99d8b509d7f728d7f2677665fffd83f58e97ae41081fc52beef09c2a32b9d9885a27c25d8e16b6b42d33decfdd52c9cde762ad98233a7db1032eeac73e95e5bd8bff74d016872727651cdab7bba737c87d9729bef604e67de668d0c315518ae69ac53ffc980111b749f3464c5b80595edf4605177c407bf177b12e04e9d871e0d660f37df23134db9604658ce3c6e826eede8078058c5dafc5e9f9f01f59e705241e10e5f8472ae716f6cc984f8ad8a2ad6f92b5ae3b72ed37b70f5853375ab746cf6c8a1257202529c477b0b65c4a06732d0ee8c0fbe3a3dc4ab9471bc65292073321123d41a94e48468c36c045c532b93c653fa52163a0f5ffc9966e21fc91220e8d504a5723d1da67113a095c35cc2c1299eadaffc748d348f6e13b560a5f3d24a5935aa2d1681daf12ad3533a7cd1ae02f51f670367538bff135aadcf80fd405e486bc726c40191d8659ef5aff634e6a66d2c9d070c3aec63a18c3964da9779a5783a3f72e1e39dadddd0231732d52e584d2734cf41e0d8f4b3bc27e362e06daf1edb5272def1def93259116a1c4203d78e65bfb124f2a2757449a79cd3ff492fc5803672b50a089c1be147b8da87e2c7c63aea1fc1d915fd8b0563f8a4fd72a0d25e2052bdfaa61aa75ea87cc327b95ac835eed29c05476757d66933e017cc3b8c9d3a3e475f20b3db67d39900afe7f8f0d16057d6bb9b2f14668a1cfb2d0890d1e127623ba43bacccee43b1bbeb6955edd267a9537f62d205476f5352901e47e8b9b072d47594df742721da1a1df1a5c139052d8a936c382b36c08fb9ead299c7512662791aca4a62ad3ed7909a769b715bc388ae1a91532ac028805738f9350b862d7260c335b9974c3abc25ceeb786e99d9b1b4fa0237d9b9c2c11fd19a7540a1c537fdc207155d6af566d3633253cec0c2ceb1ae3c736fe2f8395f69468afe235c5ff5a3cfc7279e4b7ef49df0074c889a2afac4e8c5b08354b2a0d5394c6acb7172a22a65471e3b44126fd58e8830ad7c22df66e3ef8947ac8dbeaad5c847de487230a1aff42918772395ba79f65bbda1c133f6b55dc6d7c7ec8e058e00c608d15113dc0d915a594008561b8dd73930c3959d01ba34b83e9ed0f5690ebb3b901214dfd089f917e8a2b7f412f1cd263190d4235082b75682ccfdebb0c1dd1d02d17297bb171f90e788d49f494295cdba02c9e548c944e6d834dbd03b8e6a502fef2c03edb23f8ad95d15d42089351d3c23376599dadb8bf55a0a683e57e0210d6472b02ca2a76c8baf74c5421370e10c4d719cf6cdc7e785af1ce376f57c82a10272575d459cb14f831aef6869ece824f7c948fa63df05e5421911ceff12f9f1131a4edec7a4f14f456d46b1cf2349883ea3137829a1be2ae96e9f4edbb513e1aa0abe17f22b930c01490ab8b7b0cef33d76c777bfbe3b0f7520936e052a49a43d37b222e2859701a10eff6d07207c8347ad8f1462ebfae6129042ddb59109bb94514c3b601db5acdb5bf2edc4ba036e9faa14e71c8f816629738bbb5fc1bf67d672b04d2ced3a7e2612023925fc0e6760a75f1a065ce301d7fc0cb674ac9aa54a7262c76fb0a8e0dd44afc2e8b17426759a15c276326acc318df3027e2f19fa3b72b4d052e41e95a7d5c4eba1b695cf06f1bdb4c0ff36b74ac3e285f29bb000124345d0266a49990f295a9b2facb05b5f0586eb857251147dbd05fa9ba6125f3d72e8c3896423ec0dbd31ad4861937dbb0df77e86a84b6d15c171920e5d8b768015112719d449a558c59eb2f4c861e27d187f7b6b6458cfdc80e11fef793f346a729d18981ccbd02cc9358edda0cb7f56dc65d94caef884de85e15b53d8b2ec8e68125331da75770ff0319c542a6dfc5cba95afadd8f69e42dc03d1a43b73f28c34a6b6c3c652ab64a2ab9a6c8713d36aead4662c4309295ac22e9c8c712efe574b91591d38d0e8380041e9428ce526b1a325b6d62c690899158a51ed47635c4f72f1fd16746a473a6343298885b1aa567d97cc29b67e33d2ff189b6a06c5838972464caf764a4d8ac90751c2548992e062b878b1065d21af133cd8df4ad59e2c2aba7186be0858a9382fb838e6e391714d17935319337b06a2073df9c0b9314f7215a6f2d3ba362a185e837f5f147d69aeea74c920b78d1d1a0b7ec2c1a48e7172f7bb9421a1325f1cfe397f7de9156c3c8c9a9debc5723900a4ebc75d16dd8f671b538856766265fb8d1dfa2eddd2cb973eb692193405a2ba53c64927ceb2f83b125cbcf2b0b2e02a5df35d70a7a0ee22c19cee18341fac43b07c6c88986faa72a1e61f51836df7c46fbbef122ef9917e4204e7cad09a551ab687cb95f4c9564a6150f107432841304ed393d7cbdb4f6c8f262d0d663bc584d23ad23d5cf1f90d30abd6f021ea3fbab95db67167004763651af7d224b9f11f45825a193a50df60b4292b11521e6b89291b59bdeec2c5f2dba5d101974335e508ce456a0ce76a72e3b885a28dd8e45b73c990182bff85f144b183306e9270ef816b8658618ac10817e6ebc7b204a42f7f5d9232ae759516016c8183a429609bb0d32ee6ad241a72f457c3509bc471bc5733f80f405ae0b846f9da55398aa84cb5bbb166ddbfcd72ddb2d81bc759fecb0c9a82e44f9cb31b6a59e0323995de7a7e21948e69df60727787a1a583093571a095e89bccfa8f9c4dcee24efc22f5b970e742c8876e4a723dcf88c4b99021da6d9948af9ce79501c12d050aa01ef3a1068eb7b4843b8872e00a4ed240bdd3720884aa74084c00108cbbdd9f3f29450b6f29f393e6ad6172888556dba2ca72c3eb3f763500022f1a2ff0490c29c3fa5b26ea16a8f6d1fc72f76ae946b91b496a74aef1ed402a0260634474d8437e119a1a4862110f912c7227a06ecff0fd617bfe1f5620a1778278d931de4e610dbd58cc2b37f83e28437201c371347e4db55eaf3d56925506d40c4ee243eb20129f78ef7aa209e2752501b9ac85bada6a8555bcacb9eaa8ab3282c8f43345800077fa17daa41019a37472127300f9802f0f3a2852a936a398ba4e013d6b8d524a73586678522eca94b37272f5ca6816fee3cde8370e44b901bf280fe0642aa8f60ab3b261bd00ee51c847b40d4995a52f9132c5c53940dafdb1d1364f37db9f63233687d35543a130893032473bcf9ce8991d7869c5b3c5c998a18a865b755daf659f0f250e07b68b3b724f789122df2eebd8996e174abf113525c04d98c4de3ebd5c0add74c51bfa06236df94887887502344f9ec0bf1ffd1327b1783cee6a53fb9e2333909cf04107721044d6a3606333bf8677e66fc1ee986cd584d168a0e16baf28e5dbbb1fed3f72f4a2d1e9694c563ac17f0d36fc74f26b0cbf346a257a7f0e6443536481f79d721ebb96b64b5d1171126a9b295f4610635914f7010eb92f1f30d59790afee4d727e992b4a395842d7ebe6f4e5677904c76efce3f01932b8975fdc1fc3b4ca3c7282a46a3eb639f6bd1db0eaacc0bbc03d999cf59b168ed0c43d4a2fe85fe8100fc6d81a31699eb8a16476abf025bfff1160b7066d595e59afcb3017f03145531ad20e470b19a4334b718dbccb25566cf1f51ea4e72edde7e3d78b3e21d1c89e727da1baf06a1ea862b37f9ce62c47437b210f8018021d22678572f868bd6b09721ce89b951fba6b7189deeaa70f47cd809ce163df6f9ef97fea325721a406d872aca0d4324e14c1a7d1042783dd087454b7ca4467dcdb0f1c6d7ab9c51a451872d4de0ca0c0b9952f8c956f9deae2d3b05901bc1c96911c44d0c843d11206937272ee9417ac1947ea5076942e9a78ffd4236feedae655c1cba031c4685a972b30e5d283d1c111d60c2754859260d254500ef29ca36d180d31c2d702eeddfbf572ef0f080445896337e7e4494c333ba6497c2d09e7644f07c2516fcf461762fa1d4e65e930a55425a94bfa58aea60452cde8ae408e990724fdede5b149da8270107e09656b6b761d7e7aeac93ca6304fa7f8f706798812c272aa6ec7d43492cb015fd446a31ab0f6750558f1c3da0b6ff0659db1703c63a1c895e2f6514eadfa7254427351311bfaab2ed37977c6de0f13e0ec24f3b583ad737e6ffc60d1f3ab722cab6faff308006e2810a1a110da048041c093bdadc935c2a296f0b68af00c7261a83ce4a534c7694419721f120596af7f130734814768b78f64573dc5fd1405fc64ddb1020214d1bfd6917da53c681d35922139c9ac7139ebab12c12ff82c72bdba393fee6fb482e6494e19334454b668f92b794b922033861d5d6377eb8d72428e028387cbb49638f2c4f0fffca9301ca328747f530cf84614a2794ffb7f72b9b2eacbe95ff61a9c33a7fcad947ee134bdca67b52c4a4a2ec1ea272b376354e080631fbbcb92ae722dfcb6e38efc7b2c411145ec5d793b4af1708a798878726423712b5cf7fedfaddd78dbeeab6a9ebee7d50bca9ae719b75c6f9d8a04635f0f1aff6f6600390084a1c7dcc978bd04dee0855ebcae1d0c23e46a9d5792630ab95ca9eb1d4ef5ac1fdaabf947a46e78ecb00013bb5efe55e69458a7e9bfaf483b2f990f6711cb15d79fdd7c7ba5d20ec5f616e4199fb201ea6f16a3767cdd72e535b1617e4c6923127a5c7effbb04c7c1198e6706173785bdc74f89d52a625ab7c4ed22d9bdf9180f8ca568a97d35696ba1a6f2191243ccd4f11704ef113f72953b3a4e0fff91fd31cf7eebe1b19dcb2771bbf55381f4a0605cc7f59f20c072141e4027ee4a2cbee1474a2a2a593450846d349426eea8ee8c2fbc4db85c67724446e0908a3784439b4c9bb77e0042f7963e6472b759126b3299a191180c33360afd83542cfae9c5c66f75b838134a90cec49cd265af4ac97325aeb159d8dd5c6a6a8d8570ed3296daf47319159c58d6f3c43113b941b91dc60414011a1660723f3548b6d3139136057824ca29308c6db709c2cf1262d29d95bcdbe8dd04a8722e0868dc291ec699d7e8b6cdc9ca50274d5fea503947631f0e315a2b71325672dc6a9c7435f5a708bbb48deb9c02859fdeff3e9ebec07f02af611d1055cc7b723e5333f3a878096dff4ef5d6b1c2223846215bca74c1d01e0e35e2514b089002440e1b54c60977dfa881d0b1427eeebecdb94d80e913182ea128424c42a7cf728ac923eeae5bfd7969750ed0dbe621035fe1bb01c047ab39066848b5ad1aeb709cf3abf6d93f088d0b8c16697c61501e6501b96d6ac4d0b25ac40abc055b5672ab4f3f794fbe3913e48ac97ab9fff5d5337df2b0df2defdfc947c479c5aa92353a1baee69bfe9f09fa8d971f1bec2cd4a5656d9e60d00fdd2f94f9a266e21c72a427908313791e898dd0509de169a6c5c6641e1601976a1ffefc95975a25f703323ab35efb7bbc5585edf5e529bd032416cdca381391d9d0936cdf3a1c9bbf43e3e77f41e26616f97c8bf837355d4dda89b8f0edaba23a3443bb422bacf47f17fa3bb0b24ac702bf769d8d2df1f2207d661ca1d488951788e73228e71bb7b20696337ad6c6446f170d3b79d9027ae1f1b04c5d25bed00e66db90cd403b535c72fe9627a6188d5d36950fd2e1723c1f7665528a94b996ac1b4cbc03211a382e72a7233679af1209f23cf6117a9633f4e94c3a4ed78f6f3c821e7295cb14d65c5db716fbba57edfe7405da49818c99c402b29df22f9bea65a7c48c160e27d9ef4e69e664d7b178c67d76d6f28adaf1cd2c4abca6886fd81b87f9d272227061ee720218adbc49e676fe0b32900e3bacac0dd11325c67b3278174066822747af1772acf02e57c13feb14d1b18e79e341a381f12653a2620dc2ceedd414c72cedad075e0d85fec31df737a95400ab601718d18daafc1454e6d37ab896dda370599e5598fb50e001345c132e08325c8e248b1cb2ab5f841675805ccbbfaf256aafd2726edd5231076f812b8e5f63919660896f644046c974a72cba5247f76e5c28b550e117fd8b60ed3e7475de5914fc26da4672c271e8453030cf5d89759e22f635722c71d4f57c55c11b67bb60e0c4ee8109caa8b66efcfb39ab5bbf96087c323e724e1ab8c7f5cccb12b2b321605dee22b3b9b6219bd333158666f981c8c8be7c7285b0273eabb5940369970bf749c17dc2c2182b183df51842a0cabf26e912bd22ffa4857ac19eeadce32dc771ea715ce9d435a3a174546747edfc19d479d86f726485e251ec3e575442d528caab2976003d277c2bffe8af50b7ed21950c8d906ff0ee68d750b13c0b93297e608280da23568b78eec0cbdaeef13fd76cd5d74f72f973fdc3918054829d84987db46ceef4c8ef7bcfed5fae2585cc62953b0d7972c63b664d3451908aae56a5e0ff88c27c1864efe16189d629279b13b3c0f6bd5ed2b03e8af93ff6efedc89b6eafb77b91b2ca99c365c38ec40f735829610887726cce419a42657c69cd3e7de945d60823039b600a28ab0dd1eaf4b749bc1e2d728c1ac1a148f49ab2fbe365d9c096d00d1ca3c3c1603ea04e35d40cc1d271346974dea4346cba835005a93231085f34eccaa06b4e791591daa209c3cca7053072b5e8d8bc65e2d380be5aea1d4d902d055dd98438fbf06412a5950883ac9a9147b79cbc7d333313749e80bdb62d61d6d0268929620d459feea92f0ace66a79807016a112f7d0fb0169809d84cc9e9ed381fe1b0e9f3bbd39b2524a5c13e74e7675d6bb5cee9b3ac3e293c1d5659556f5fd39fa9c4f95c47832712496811ffb901a646b0d102059dfeadf826221317706282ae4945a12a249bf74b116a90c11b72aa440d8acd53d108d7796e76ca30ab22781f7a3db64a7ddf91e3daa200c99f7283194f58952877f1bc72f8c998b5521d62723a081dcc751fc36abb9ae476de6de6273e0110a4f921c7ac92e1e5d66741d62f9df154350f6036995cf23365e77238ff696d5db36af74af37a8ce9c008f85557830a0071e1ec3e571966d1d1e072bf6085f03d709896133b14455fe97676d9e28eed348c51e84f28b317d1165e722d21dde0bc6018bb0ff5236075691e121bd34a2b9696a023c6728ab4dcfc25185360ae77367339798ff06c66677cf127e220a3a20e35c058723c3ed8be4d4872d9df803acb8ed7a7b4c1ec3cd1c22013fdbb459aab366e6a7663340a8b0e674ad9ce184733a8a3bce67a384c6c429e73b1186b4c06b60ff9aa22744e506fbc09738020dca651113e092a0feb3c14de3364bcc90028b9c5a4247aa5be3c0a55725904c418aaa3f53db4cc27f68c78b71c82273cdb4ab7555d0bf66f9821b1874594c15dc3b6876231f6ad828d3927b519b4e4513c9d444de8d29dfa4dc48f3172baf57fbfef2b96b34557f688cc79264f9988d2bf9a547e2ff9e7108afab84f72c39c2832c718c3a6d432baed8aaf71e252b5a5872b92a4957c6de1cf797600456de7f97b3e5f54e9bdd55c6861167ef6ad28c8eff83b180bcd19fde40750967263e9080886b56c7dcc67d71d745d47de494a0cbda5ab856ed01c9589af73984abdf2ff5d3bf8d0c59146059d0685d26f366ba598adec2da23eb4c2f326a5b372a818d1244fb9f27fa66e66ee2e0591ad699adc2ad2278869710e20e05ef25d17e9dba85765b2a3edbaadf3a725ddfba7905eeacd505e8ea8bb9ee2fd38ae9e07c6dfac5decf9806713f2f64c944c1c082f58eb051fa158101d81bea066214a32ff6354ed8bc11b4fd638d17f80b9d06bceb63b82c3d463896d52cde61d03c339525f31542af05c21e378f110dfb890c43a1a712116417b3dd12d56ced301623c7e6a915b0f5b01050179471eff2e0e6e20a228e8c1fb075d73cb1c162ff2525d227e82ceb8150feb04993889465331b0352e2de8b34c31022a921f7cbedb9f72cc000c044da75fac4774e8e2d8e33ee3c3f646a8d45544273d3e74426cee2119dee74d2fa54b3e45437be26f33550f32145b22773cadfb8d45c5a95e600934723342dfef036ba980a550459481bf79671e4b4417f14720e029d26ab874e9fb72a3f5bd8c3c04bc342bbe13e25ecafe9a7b5af78404455c07729a94713edeb0727be292c4195dd1d526ef947b78d14035280ad4d986c1ae0cefd6b049c3bb2e7298a1d2cd381a449a8800ad1b05f7bbca224ba283f6e6ce74fc7d0ca9c9af127299ef6fe1482a6c057aa83e5abb6b3ad592e63a7ae83c9f4e8de0e9c3a5d63872bea552e4509a861d95b8623d5b18fd143606eec239a579358d30bea8f0ddb8622d8b99870da628ae6598274eec4495f137e93d817b1fc5da7769065f36ed200265c561e7a07a2f8206e021d4e263a2db5c9f560a92522640e2281b2bc6f83b72fb120800ebd0751cf8d403989e5badb61bd5becd6867185cca1ab607405f3b7225be745df5aa660a59b14513c794a758c9c575fca0f9c98c26f4a9b9fd462372667330e5169134a44e2df95968599dfd1b01549159b7182e89a88643fd3b46729dee3c700ae8d3eef16a1cf8d12fcdebb72aae05e42c815c2aecfd479f26697232784fa4fcd71c56633ccd1dfc951cdeb40752abdedd644339352e871446744a8d983680024dd5e79b1e3a290aa9f41862bdd64ab8432ad1dfa576cbb169490620508b76acdb5e40a6325ee965ccf56c53eec693894a5ef4eaeac09dd210ca093a9ecbae62c969471debda286587bd00faa544bce56b1831dde4a95f08a6fc1b494f4efaa42d2eb7710077205090a4eb57c2826e5226ca566ab8bea343f36f103f07571364395ca692ecc9b805399fbadf6ccfa45e245611ceda2e597f7939618dce99f77ccae05337eedfb8cb9887dc7b4026f9a09027e588a3233cdda34c721ed87336ee71b4cea7e2b5573c1de4d67b583153092d0c665f41458cd767b8164adc1f541f12b077996ee10f3f03ebcda9b3689b24b55b70d90067f33f27bc6f130477871ce48cae285840855490ac21edc664727d42dc4feac1880cefa20c721e28186d644c1901cc1b39c6e81597db7eaa1f0fedd21a060b919c967c1d637270127a46a29a32ed2f13dc61da55fc88a5207d48516cc7bdc2fc28357ff42772b24f1828431c8cc6003e7b03be6d46956420cf004632f30bfde5ea81b2629e7261b60b1529f3f26ba8b9c3f4a0058b325192e0f8171191dc93cf4d0fc313b412a2a7daa85320b2d221ba69ba32562f0b49d9d10c1a73fff9a9ee07a764ddea53e17f9720d6a094e8a92ddf9c06fd5f57b1f0970948f86f36ed9585ac0d732357eb3816ea81e53d7e0e6b132014bd6ebafc828c17fbeb25a99492113aea715a72af9ebffbdae4ce9562f32a6d71d2815f958b6974fe1d5859a3a63530dfbeba7063e62d3aa3baa7c7cef08e6de1be8ee6d6e180270eaa94f133a31a3fbe2cc637316b512fc61dec5721675f7351c1c3b0ddc7687af2af108cab83c261da812e72f8a9c39ad9e703e294630cae1e1f3360b8702251ab48f718e6e9ae52b756bf2eef380fb771e6512594e34220b46a4a1142e193d1987a8e7fec7ccb8f07892f0d1ec4df6f9a3431d6deca43e8c106901b26828423c25c67b23f901ba045772772af42b194aa9e3fc276bca4b1dd44a447f4812613e1579d88be4bb2bb47e5de72dc228c89434a362fb3cd32ecdb519278d872b0ca3ff5d5bebd44fd96c3eac0727b9f1bcc14841ea6ffe18be88feef6f6250626cf6f8cca0bf9951281f5fc8c5d025043449c7bfb853775e3e2ce4147c91df4170fb4b7042b23f1708d18f0023ca65ae52165c210bf3e3c18bf35b74c53133fec1c4d9790c7eed25513f9725e6dc5f69c6b336b6221dc1fe9a26dbb9d1828fdbbde80fc74eab9730443e3f4ea7298be5b6b3a425215eefd0d62768ee37ec8c05b605126a94d9c0e6020bbe27b28a23c438f1359155a722b16833d396c012d9e23d98484c5fd7ac144556cbd587291c098f1e0717b6e9a623ccdb36d83432bf122525599f86079a08c5b86defb72c13a8818732482113b21fb3273f1913c6f1b11234e6fb68b925ac3ef27942372bd08377a40ecf7e808dc5319393348cc7ea55b5e306b78c73b7dd4860f4a321224172724c51ab7ba605cb1a97fa1fb392e4da31c39b013ddd8661509ffaedd4286d4574f3b0e139d9451a94694471c06662ad2bfd226cb688b6b447efd8f4872ffa9f829adbd6ad60c5c461cc36bde20d8fb76d077b9a1fdc5f6009d961097298778427e4fa3b82342bf2edbab75222af5ff329b3026445a0dba55f3b3934072b683bfaff391725cab3a8f8a9dde737859d0cf63a29b7e332434867d17bfe837d09b8181f82fe29180de441d7014acfae4eb32cc599093d58731d95cb88e401197f1f7d3fd28b23b750307aeeda89dd43ceaee6b29c45028c96f9e9d15de0f4c2da645bcbdc3171fe9ec2693e52726cbafcda06180cad95bde3aae6e8b1f614e068bd4a276493413018ce8375fa69360950b5f9cdcd2f448db1b977c4134d94ad7278bedeb511ff280df737b75ddb3d3d1c5eb60d59d9f0b8501acb6e374f97246f39dff556db03683d821b6d5dcf92fab6a57df13e225b8338e2ff03c4d75729f276400df65ea71c7477f3ff498dbe7cbddd7512abefd472b53d63b271e19723a92a6abc0ea63574a780d3c16ac6fe1ea5819cd7293f1b98fcb3107f5d8cc72cc3cf191a6599835276341bd057e460b7b704edc96e233c00f17ac42a1981e7270c68eed1eb5a286e7406078a5ac380b7a7d92562fdd0751e22625944e01d4720b7924cd1bbecc7eef5f444468df860ee42623ebe01a53f95e93c344e26c381aec796a8514ae741ccc1bd0eb92ecdae2c84dac423eed5a3cc5db8f097d509772e3e882a74758ff9b1393b4ae7a11adba41b49762c75890daf0f83165e4ebe06d2ddc76ea2f3c5d5fda73ab3ac42299632903189d1db9fa6effc522bd091aa24773761b3a7b0fa49fd24d7f83e6a0b6102f3d4bc4c83fb615dc8ef212e8076c6222843c3608ccfe3d13fad215974e079da32f78e68e9919a55f24fe9f2986a172d1754af381cfa2daff908173b15331c4a86a813d63341aa28950300e66ab983b680d1d3746088aee6ea6d9bd29dbbb065dc2dd14af92bf676db84e157e398433b7fa36e3cd564d719ac8b13835810f26323c866e76611c5c1f3fe47d3aaf21720b4f5f864cae3fd5e173bb73cb66c8d50b219a1d9a49406d3103117f1d7270488599188b2a375c4707cdd28b4fb9777596f2e469ab3c4c765ec431aeeb1b2872611e330b6a5aaba72628815c1c2fc8be73ab2d66f38ef93b14c3f4782262a750868d1a48df4a6e85cac4b6f4816a98a43b46c4fed02dc64a4cb29b425e5a805f677e2924a7ba7eb1f9371ca0c7681d103ecc9ae206af2d2a91a027f976b5fd668e87089f8cf1f61404118976d930c4625e576cc5cd120f3134b8a6fb61d7c760cb5c5d28e654d25692c6420177d8290198ffd303d200fb733f476348328cb072ce34152518c72f4c09a793991ebcc780c0fb890a33c02f2a7181464a620676721c9b99bc68c855a3243f472b5a9696530e74d49cf634605af0a4bab14b853972e631517b9a652ab0b872a8dcd2a8a9e443e89147b3e3e9b31073f3ccb09e88149a7210bc7fd425568af375aff09f5fc192693a019dce12909a8ffc86dac0d4726823fae8c96141943c4482c3b25ac7863e0f4f4d908053dce2f9888669f24c436899211f85f4b83d6621b8332cba21b381660307867febba4f8220c34c23a172f28f7d3683809ddf2b99d3e8fd7cbac0cab31e6ab1dff9e6adf08cdf704c2872599cb1722a3e7f7c39f299d950fcb6525b6156cedf6cb27d7fafbddf14ff8c72524063fe2ca14b580f865b47b773c8bfd0af4a14f15cfa3cd00a8683a7035e414873036a46c6aae7ce7b4c662bbe5cae429591c1a16938295b97a5e25fa3e772bc7f6ec1babce80b7fc08a3203beef12debf8e34534c2a5ff9e9d4a83d9a164739118b870562b93d9bb52981a938375c003cdd15de268f3f6e88b6a6196b85724b09ec49cf15eeec2570dbebd049c32c0f30bc688dca3d634bc4a9741e9b34416367bcea92a817010edecb75d85ec3d486e91b8f287795885a52e521a5a93d5a0af88aa871f82bb20f3f4c1808f06032401c1d4931461b0a4b2975bc405ca846a5db87cfc3d25986ae9ecdad1514bc65c8288f2eedcc71ac8f0518dfb1f40d72faaa51398fd8a1cb57721f127f46e0a972fdfa42ebf671a9488ff77ab37f42721cd598af9cfd05ac81ea42b66ba5c03a50841c2d8ca5f2f85bab858d8b2e5005bd3026805c44213262084627243be83782b35674073c3344c53ce19c0337d6723545f1500aeb4bb38d55df62817eabe90693fe6cdf3b68779b0e368ffb501f60e4a9b9abb29dca003e6f06f09b3df6d1c03aaaf53815b4b440e6b69207e219721b0ad4d6e468debf00d68cc9d2b409293b8a13d0789cae35784f7429424a4972e5593d3f689d9f8cfb32090ce02a3ae39200f67de39ab238d006404381554172201e2d96d0f25e8cbf9507b8d54a38133dd35d6ca56ac0455f7b3ab96f2da23185d3d77a817097ff4ff79f63f6b9d0cf466044de633ebbaabc4d9685d507a232ad657930ace9bcce2ccad32e6c32f445ffd44309283864572846413a3336a172fc778a8cd91da3a9340f5a5b8360a6008629e695fd39180386dd9a4b55f00a379361f3d8abcd4a4ad7e27ce951e792e9bd24c5cfda6a0e0cc6c1763174ab5f0888fb831ed66f77865cbffdb17abbb6dc5fcab1c31f03446b942c2753513327723367e0e1d1633f76614f0aa50b47121e04baab6d3ad67e7b87d48c13bc188672fb8052734763acfc19a6ccf564845e8f8d2545ad198dbc184dfff55201471c4fa5aeced81ea157ecb38045b1f965a8175c0cdde1efc7ebe38fb9d1bc447ae1589b46ebd80fd9e3ffa6731c844f06041e7b43225e4bfe9d527b45e35c29e299610b159e5705a9d26ae5ce0a306c0f1847a8b2ba98d60a2dbf8d5d1a718553cb648a2283c9352c7da43548092e68478709e80efbd355d64b546c8b9bf85ec5ee72349a68b5eadd063236ece6c7dc297786e0cbf8e569a8b061442a47a607fa117271e2c9f55ded7e9c69f76cb0db6624f3167b99522bc4fb2d8df19fbbc7bee272d7859c057f0f9423b9638da28e155d1a682269df9fb08a200069b687033666138493dffcf02f431bb68501025b7db4d6f2f8c6c0173e99160249e2d468ac7f60f16f9cdbd484c91784822d1a380dc348b0ea0df4760f61ee035e879910f85d59b638124c6131260dd9adcff7053bf115e3f8909b2ab83dc22222a1d5b6d14c724e9e1fbf3ec680537e54758d63264b6ee14f45bcf71b3ca17705723a3764f16d7e8e6fc8033ac693cf79dadeb58aa1244a4e73a053b7951ec9b2d1d0bda099721c68c173cb4cba05001faf029e30356f4416a28a25dabe6e2aeca16918b6f81878e10d2e83008a6f087c0b14dbaeb6edd229f8c4ab801157c2cd00384f2e1c4b53c059aea82b09b455a5be644fb1502219c5205cc938ed3b11223be78ef3917204b89a33785b4e526c7a39321906ad44c05de826af78aa7487876ec8187eb672adf3e98e8795367be01f30155fdca173ecdb06896f4e3fa0d5fadad8556ae1720bb22e68357ddb03ce5755a63d5c738c04782c87b2c8012c64ac76522c07c97233ab306dc3745f1f50173ff3506d5a7c95a6edaf01402f8e6913044c355c415ff4ed17e5fa649a31a7f923ef59841c370da53a72e2433d83b598ed8d87b6827279e1d66f1bbb5e9f17370fff01a189930efd3272b94e96f90c1c26cf367d1a72f656afa60cce23249d70de7aaa821b5f11fc57b950ff9fa4b4ffa949e2edf4581a1e58fd6d43c2da326130e31d49caec141ff39cb5e309c2d53d59f426b48f72c5310d2646bc12515e95cd8094c4794f576c9184ed0bcaaecf4d35c0e16dd7317a8df923ad2e46a5836a1d1edb250119264e161e114f24e618fa430efae9c272ff7f42f9211c5e79ff4464426a8a749f9c3a2670ce9b46cd8ffd182e5fba5c726523f660a5b1985d55c648b2ce88313cac3914f60db490bd0f2007dffd307872088e5f4b345a66172e03cf61840804b05720175aeb4088d7e6ed463451759272492c0b9ba77b8c8ccdfc8f46aeecd01de7704df8b984c9e1e1af3acce8785a72e6b4aed33776f55f0129cbdc3e855e48c6b7d41dd521de45cb7173ff5ecbe93a89af3b36ae73bc0d7c65050715d13eb26aac728488f1b94e9891837a4b8496727487a8ee694dc29f679a5f95a350357149ef2ffcbe507b8cf51a716510e2ac72a07018e91c4e6283577cb01a010da9495e3ccbe47f8fa8e85716c9f97c17776f18e2a7b466856441a14fa98604395cd452cd877df09eab59f0ca065dffe3ea723c652c7212d45e8e55648bb6594c10edaf649dd2ccf9d6c82acc2d1eedc97472354c71c08db5a51b2070a1ea3e6b5b57b6e58b1cc9a9567fbacd38b85d001c0af36db90c33dbf705f991a9aea73943a3b5f7a35e95ade3f3398855e8b5089841632ae008260ede01e331f5285ce928cc84ebc79b53342137459aae350123d17202852631b2234009a85a8cc164f2eb78a99e81fb50e66686c0554643d5e33f72295fa3c767da7d8dc5296d45a9b585e69c698203efd350fd98875521203d01728692218fc7304f94b68de11cd87ad26081300862feb9cb97de9215356c44fe36da70d442f5e3df572371aabea15bef9de323ae6f8771fe5915a4deefc1e4901bfe033f424760c91103003206d1b17a8fa4a0f87842936a0791878d6ea9454472027902ddc286a77989e483d4f2bcdfee38813e077e3d35b9206a7a69b581f3689c559bb371c76356fd246bda466e82e8105f698a349924e34899d90236a95772db27c2d625218b73132494368c328fb1b6830350444e335d0ae2da1074348b2deab441b141b2877305d682039ee250e9f67d6fcaa8e67aee5aafa30f39de5072faeb3ce9943f41d3933d799d808eb7331458114bc6a590d278b1fc5931ecb514c1165770adbdae6f2477cec0e472297ac730f52ac6c5a22b2f3e9c890abd7f72d96d2ce564766b32371490c7ac2417c346c3f78fc6ddecac5508d90e0cff2e72252a58da77314de93458c20ff85105157e34698d8f7cdb2fdeacc1ea29b90e15da0b94c78690a4a388aa72dd80e22a8473656c47eed1839f20734a2018678d605aabbcbcc63ab34e9aae844efe7a8d75c16331bdc8eb94a5695fbc0e32d9385146740b13078cd6d1c1e7378e44f7f6c81e01f8c7532c1257af2286c6d7376972ec18329e0ab2a749be05c8443a1ae6e953e836c8c252df34c524b0cffbb2737221c0a9efc1fb27c92deb1132ff87deddd879a0a0e95133c3ab83c0af6154ad72a68991b8cc48536335a2f46211d1bb61874aefaaac407edb639834ebd21a337254d394dad208a69a70f6d3c7944e4075aa4e715eeea9aec62e3ae94c7601be72ed08b661fedb466a40d4c9b693151a2da6d63e104b49d69cb0ab4f8f83023164274fa71dd7bcd111e3848c3a3db80e4abb5a7961211693ab8583b4d72068d9141efea575d4b91b96b37f3ab986a95020bf329853b6b3c133cb7caccc8127aa06f22a2b784df239caff43d6fd0ab6fe473fd6af4412001be0c1a76a808fb088728a29df3265b2df9dac4fb2e9415c8da0bea87b53c7ab2aa14717dd05b471b572ef501925a991b6d9ada215ecd2e2223b2e038dcc624074e65e824daf5aebd172315e44e8ab5f1bd4c3eb5f4158fa6ea0cad4b28dad0eee82306f4b5d7552cd689d8ca0b7dc321a81114ca485455116eeb8e2ae519b49cb9f6d4202bf61360e00b49d3cb86ec054eef8263cae386be6e2773ec6dd629310f7e60f84c9f932c6558ab40737c04b8b6e8ce8235beca9cb30289baeef8d111618909300c54358f117953a9f363ecc724cdf9c9618b9cb9886a8a8fa1b009bd85239dc1e2db068b8255c7fea3ba98722a07acdeab90e4ba20957951be154aa5c3367437093edf587729824c80835511c8caa96fe6b9f6fa328877cdb27d7d09862d5d5617c4120327241758f98d4c4f17a9a4dd6ba79e434a43dc3fbee46e8928c196c1155ddef65141e0b9b0aeaeb94da550c8e31027623ce164b23070da8a57a6f44f51dfa8e9f002a1483de7c1ca6661677351a59a0ab4d0c97ac95dfbb5f66254cdd28cf161272cd1d756f77e68190f76a0d2f04810012ed1cd7daa20c511acc98dc9f0f0edf7293c59e67681894686da76541213350462ce581d41e5893f1bcb352e9c8b28072dfc3f343af831d0f627af90c9f2f55fbbf07f9c3d754704c9d449936b7fadb724100b2bca620eef38ebb97bc23a0f43d0f3a81cfd20c25d6657d2b5d42525b72c705d190e3498c54e4d36870b68de9a108cae3053a31fd6dcdb747387565d972ed319f49ffb3f338c0f959a19d4e7741a0478614ac2d2c1cd7a40a86eb3c9672774ce5ed53248bfe910e8665eacf512bd761909417d55871eb472a1b1f5ce77263e2b7dd3e2b1d4041a8314e8577edde3be61b4c1be749aa1c6f2ae797ad1d50cb12d7bb43d1496343fd1641ef297f1f75ff5432b20522f8a3f98c9b8474c772e11ef031e5b34a4dd8eac0fbc5f681f397295b2047ee9925e5bc270ee863064f46fcbcb7af9e2dd0530a86182fc3eb26a04798e8e60a9d3b7c358fe18f302a720325a3a22633f9d3b71a04ca9a8e2f2d4d0ca26e6d48dacda575a9bfa59722724c7082a19944edaf4f3257936802442c1711cc20611df9eb7420675145241a72174412988cd02e38cbb84be581ae1a45e809633a536ed61b5753b5a1c69a7147b8c781434ac787a5bfd3e1cfb21d605b29c96387bd367d4eb8025fc41c13b372a6debc152180a74a5b57bde01c154127119e80d7f10ed932e3105b382a3d0a72a9d64760e89029da152dc29375808eb98f551d6a764d3d5c70f8a289c9c56872f1ef0b969360fb61dafc1f3299c8fcee8c36220a69ffb38919deede03cda2172bc2c6d50d1b8f184f3876aeef753601c1d22a3fbcf542d656c6c7d67ca9f3d721363570af1c0e6044e3e61ef1b7fcbb3c6e4831c647e0adfac7a5e19125f36723830c491f83c342f29671d4670fba21911b57003cf614d6b2b81dbeb24b0c372b09ce0d0f56ee900e967c3141a04d37e70116a75ae9e5c9b55cae4abc29ff3398dd2eb803f8524bb755782d56a0360cc25fa027ee8d430ccc220efff2fcf0e39f567e14875f27d9e6946eb7258e94f94e10eba3c3fb726dd448f0a1c6b7e027263cba6c8ff315b22a491d94312d97a9a543503674ab0a59760fd5ee7126b43435086f6b8b3d9303e33e9217c108db65b916b5a0127c1c14cb0c3af0122200b242421a7b2f8a6ea6b0895bb19f5b38f5b2860498ae323c90034d1b0d325b89172257f8f5492924d34eb5f15c3f9335dee3fedea92be9742133957200d9cc20b721c88c8170d9ff2b4d65725fdaecc37e458141a2ec7aeaebe667a3c59f28915725757f78ef8ea2bb9e29f2a432f9ac3b839c06d53f739e2ae1f361818dfa9c972701909a33951f145f6c05e585f81301caec57d55626e1157f2afae2533dcb7726785f460d3cd17e4ecf38fb9e0baae224d1c2bc8e04e30347058ab31063ec41213f747aa61ef9ef9b5bc493859500b55cdeb971976ef569e6efdd5119b6c5a726fc29646b74c63a283c08f1f03aeee6fce9aed3ce9c0bdf7f5b2e7b9ba77ce27de9d59be32d2b44b5ab9fbe004da1b8098ffd0847a4fb39b79287205aa131b7236d9da2b7eadd1d80ec1ccaf4093daa74927e9c52f815d4d47df17de590f910dffeab3dc8a22970304c270489834389843878e562f86a8b090d3edff54ad986c7c34afd307739d24fac4e0aca4aa36ffaeb5fa4c699db12bc14afe8c174f484db44db5d437df6cec1b51c65ed3bb0948565efb91281b6778835160e2791e7a31ff9962b511b0afc5e4ac5ff705c6e7186dead1dbd4f724d506d18ae49d1fa800cf38a58b8f77abed8a281d57cd3068d46210cdc1f798e6c3342bd22c1ad14a0d47374a768319be62c54cdf2d20e6659d9d3df0a51bdf94a127aa8e2c19125272533aaf83bac0b8462df8b09d1c78d501a6f0e9de9aa4b08ac88927634d4a05722d94e20215b89aaa03b11aa7bcf4e01b61d6750ea221fd3814ec9740140fd4726fa5c0a6a3705ef0a7d901e9e009e37c5f99e01caa87206f887d330649ab8f2533b7a04c1bcffaa23070719d9a782841b055f2131a3137ddaf41793e9dc44d38220579a44f738ab207cc574bc18d81a397b8e9031ab07f110ead9e6d97c0c672ab2a52bce15dd3417c945937576a3ea8310e9714a39b8047a4e6632d2e91c8722e9e4be3c8105edc21dd7a0cd016d0931e27745ac926c6327be5147bad951372993080235b3365e70a6d4a46ec988dc80ed5b334cdbdc1575084ec1a16f222723f8d73f07c13a02287e0aecaa846155ddf03c5e19d233f0262e7160be6289a7236ad3cb87bcc7d14f6482c68f9e1315e38da7b59df7bab76def8f046b4bcf272b28b3330bf4a8d2c65f547778160e996eba788657ed9312fcef0ec9b7373b532c814890a27f318644e1d5748a82c9ffa7de7f4347f4256a763e90271c8fb70725b5dca2c16a76f78b1cc5d89db013f7f07a1acf5e2880a554810ee962724174adee7a27f55fac2157ffd4f163a1b3d4b38fc6c438067c17c5ec5845de7b4af72d6a5f0fa4fc91191ba5bcfe1f1bb103cb769465654b7439fd2e2efe79028fe722ffaa416460c6e3ee47edec09d2947b5e04e4548625e4d2ae43c33fe55f6ad727c67f67bd335d7dac631968cd5ed7939d7fcd38e95abac22ea748bc379516b72d904500e9bbccd42a515430858b7da1a12d7ed10e864c54b8536971d571089725028047cf26ff6310357414cac4792db1bc70be75ee3b68e9a06062408cba9720e4fd5db660ad39d4e1a5bda040ed0b4ac2986e80dcaa0bbf3a6efe12413090411be00fefc956254c8830b435c6008f0a8761b61140162d3dc8a2daf70f47072a84ca9873386fcb05ae576cf3f18109647f302edf48c238b5a48851a01ba9b07ae72001ffe70248cd098c30153e66811ae5ec7804db57258d955f4aaba6f4f72b9cb7a9ef535cf3d636b61bf4db28701948a8f84c796ae77d382df1aa533665b711b014e3ea96e67bbc4d485c59723ba0f897e5316111d8c1c3cd6525926ab72eaeddff154d08a0a37d5cb5fbb01a1cb986f2a66fb1dbbcabf594d05aabcd272484e834f95d3b862eb0ee4bda677baffb6e35ad5fa37a48a9069774188d7cd722241162c891b99027f7f73bc39aed695edaa389cf66f72c148ec405856690e72163723811cf545253ad690b41bc7f18f834ad50a04d0d07e31e00571d293bb09e1e5d94830bc3af2c170b7c99034e826607111c5b2d39b50dcf9248e07580c00aa748b8a4a25bb345b698d261c871235e0416c11f1e929a1ee872362fd563670aa7ff03afcc1e8502e582772e44ba28fc5adf2734c8b08612444b8711282017291932eddcac0384f878f2bc8f69da5b02060f8a1aca6fca3e5a1798a0b5edf6cdd0f6beeb8020a0023e2f1a19d2cf77a3e31ea897c08f6b037b8a26310244e72b8fe83d03b96728979426c80258526c21d6c9ec95af6095dceee04cb04745e727161fc43d0bf9df829af35d5e8f042ce3f85be2badf476c2ce8ebef0e9b12b2c809b3e88ba65a05ce4191b7130bc4e13585bd4862b9ceaa68104fc0be0ff217202109afb94f1641b55a17db62ef76665baf01b675614f5a8db052a229d84cc72913ff73353a4a30586bef7661ae308b370bb3d6964643575759d10a465465a657cd21161e550dade917907d376407bdf06935f07116ee7f974d48b7724d6934eb4782f843bedf8106c824de9e24c17fc112ebdb679de2c1f7887969d49390f1ecd820e11a50a0cc2cf638afc8db58adcb8823011813a26efd527f489730bb33a4fd1f68c94748005d56ce013e7d1a109e91bd2928bb60258e40b469a222cf531fec91116036f2a6798a4e710e11c29edb81db0c19f9a924811875f7a96006b5f0bf9abe04a4b3f2cc295f21c6d3e84ffad099f2bcc5305ca81e44d2216161a726a80aa0f4ac797f5e3de49ba7774dbac2cf16445acf66ce789302a5fdb6aeb72acdd754f9f9b44e1d75846fe2c114a1b41fcd26b595ecc2e485b495551dab431a48b2698bb949639469f7945a636a3c5229f467967a180b2c558d6acc6b52533bc90166d00a024c8018850360323f3d74e487b9d2f0ae69cff6c702afc2579482104d99c09d3e727fffa40ea4b4a3ecf91f5f473e2334e1042765163282cca09aef2668e6348f030795ee7eea7382c370f42dd2499020dd84dd8c47571e70f5d55c49799183b2629170c0bd83fdb4904f65961b8edc16c6ea232a2f0b55cd572dbf9540f021113c404f94fdbb085a3d30364bdd8317b2270cf348a5ea5713647a2a19bb0edb4a8f151c755eb1df57ec68850a158a3a425efd1ff4ae7db869a316871486c99450bb67203a9b75fe8e3f25a450f0d00ea458b849594dec0c9d872bf78839a6f12a18ec680e186278a81245ff94eb89d9e6e491f1c23b5fe167c7227f9e2155b300fc176fc56dec68fc32e5bb6d01c4aa2fec45d03e7496d6f0272b7f650ed48560da4781ff73afb327147e59373997c471fc72e4720eceea58672e3bf44b1dadf426a3b2f1801189625bd2777b0f8ec1021df36f7e0982e2fd372f1f7e8b5975f34879634e270787d150431234273c3624a369464f600fb2337726147e1e408b15908197dcca0915da170db9d95e517190703cd41d6af2d71f1727a89daaa88185204cc8aa9662667962112faf6922743eb43aa1047a1cbf26b093dac83c9880783638428827ea72860431c3cad2af4d6f07cabfa118c4952796a4d6b063e8a9e12cc715a097b73855d54deac7ddc5b495427988dc750a8f03e4d1c66beef102f2c1020415e54e747a7decbc674d4cb8e355f1f2ba370273c0e726e1281b37471e5898cd115cf7f96afcb86c564f3968842eafeefa4234df8ec723477d9eba40f98e9e349bb36d3174441f79ead437a5274d5d42eb29280dccf6beed2f16f077b30ca48b615f6fa8d5858c38241c181a8414aa8e22b6b89d9582f22525114ff121dab59c076104b3e44a0eb94cf43338acf1df87720146b29af650762e74529cd7ec63aaf3b02314606cb4824b5fed04dedcc1b38189ab14e9e72d575e6b79dc4ef43761535a0954e97fb14760c02c7d8c31cb691211893e8443b78f3b22c8260636c01cb825cfc7b252313e9a56806df6e6fe62329bcb9a8496f5b6c75b9bdcff66ca246ea95949cf7f8359c9d0ee58fdaac7bee223c23d02d72c365e638b6da487dd6d4525beab7eb41cba277bf846292c9b8291103454eec48a708737639af484442126abb201140dcbad27ba511def1bf8e8b1e3f736cc50cf7c0af925c16f96a29d27b4da00251b1c30581247f7a4ff3a56f2c9f55cda2722c018f1972e94059c8177ace678fa09854e1abf4af97ca8e7132c49332b7b372f475e39c1ea908cba3892dffb4c5850f23f72e2e275b9170bc68a6df3da5f972c17ea56cc3038e950c54f88474139a961f5d2a6ded4a83e98db1180854ba7b72ab96789bdf5b43b84691d831c7c4875eca53b4a95488d2a0968a4b0291c381723a34a60b8b8646351f0f2a93ee323ef572ec06eca5736796f285a14d9b9851723ff7c6d3da44c215d4d5b1e0079fb34c6bbfa445bcc4013087fcf53617e84b72f83549d270cf4b33bdb2cd060f81aba6073596b4b45641bf865057c45e570a726e5f1aee4de18dc16d2338ea72e4fa4254508b3a5ccd694e637600e2dd53567214e050a37b0a3430d71e702c285e1f7446c2ba23c0d5821b91b12d84685778723cf618c360c5c6c3cfcff86ae17c56ac8c530e96410f19314cca403146b3ec726d1adac76af0af3bedc36646c4931f81c7800fd0f4bcff0de166d5c8a0feaa394a086c420ff72d536afa3fce10c46a7b34b3eed6f63ca8441dc9ef1259d4fd7284d38d09ec537466f5cc01dc5a23ca6ee7eda94374f54e4a35ac9b51482b0415ed00cc31a91af98aaea3031eef1451cdc8cf3f5b71dc7e8a58ed2ff475b01872c9c38703544a48cba68fa2285cc6e00548a9904269d3306e48228aa9dd70405c9d04c25f5216130dec92faab78e813b552ed5a32266fc78c0b1c29dc65958825e1cda47351c4d12a31d32d11d119263c700eb923b39d46676ddefa55acd894614c4015d918f71f0af618c3604dbe2431d6a3b22e97c43d8a08e9ca56fbfc36728db1a10bddf5959e443762d3e45219e75204c48f0ccd1e977deff12ebb71387219bab656678d9fa8ef783e153948d7554fb56338e64d9b67fd0260b6194db17231a4235cb0eaebd6f998349d9de27aba907329dde3f31ff3be5cc96fe0c64372f6d911e8e5e06519d1ffa7279c64a6a2c476d9a4619a313850815e77e7501e72883eaefa38cd00baa517852c26c08af0e055e72ca9b74dcf44d2efa622687872b60e6c40ecc0522bfd7a486b568404a4c29e2f39241fee7c49b8836ce09b54726a2eb3d0554670bf21426b61b6a46fecc52a1f1fa965dbd1c8c1d54227861472f60e8489842104dfe23c4dcfee0f03fa2ce5dcd5f3b647e3aefaceb9621a1c72462a132cd3c6e1d564468bf10ad7595c1acdae35034de21a7b6df0a3d4667e5adba5445e93027dbda649914ae9d0d2da818d0d6f345d34164d7aef619c2907677086b2d8d37a335ed347f1419051a889470f8e2faa1ddf9abf10e563c5266c72f5e05be8c0ae24e812666d886c6407a9bdd87bfb194243f55545dcc89373aa0a4b7d9978ba104d3fab008e9b7ca12615f3c8e1b9985ec28e3ca52ec44cd266726f21b8bb2d2231234131dddfbf8f30c4357d5ce1d4acf50b5fe2c7734e5baf72f0a5fc30687d746eb16ff63cb874c715b1328ab22322feead2f1e224d6c23a05d383ad3c60e1d05f5812645a43e732273b580a6076da05ee45fd0053ea50d872a97d1bb8720da67026b93c499fc0b2c46dddef80f443543ad37782c0d0a16967b0771a1d0c780e6800ba4dcc2e6bceca16c4e7f8715bb34446ff06ed132b0a0782256a665411973c235c634f5d04609935ad088f7508ed07cd09b77e5862a8722920f5b651d6da9f04afb917f4779dd354cf818e1df2ab8e331e10e26053de4f2ed90925e868cf9d4fab85243ba0401d6299b7d476a24d1e7be31dda7f3dd61d5a9617fa6b34024bfcccacb58d3dce285a0f84cea2a279566fc7a691a532377218654eaaea0c61a0b992e8b69db0d8b994cd8510434ba625c13f59e2c0acc27040c2c8725db4a2725d9711a88026d95891542898e392a5fd15289de7fcdd3b721449c968a806d957d6d583721dc0dcc43a2d3555e1a79c228b70cbac06e3905f9b99b3010768e8de3cb7fc096379ae6aca614d77a4239980a275b77757702147ef73298d4d9c9a1f6b17936fa2cdb396edcb64a50681d6c8fe766277cd517518e25e6174b6d9cc3389326f3382f948e5b4633636c906fb392ab4434a93e61a7255dc1b58b8230e2dfc2c1ff23a18ac21ba9a2d4e5feccaf2c3be22a86c25244d0cf0cf1c2301c5ccbbf941e133583017935b140082b28a704e4b630f908134720b76c37070622209ca14570cd6b33375752d6fb89da939d6d8a542dafaced4725a5d9a2a5e1172cd42ef32e59b70f05f83aa9d07369b16355243a6a28007642a20e0acd9dcd4279e82c50187e05c61a524ae0eec2861b021596797f74cfbe5269b3e270cbbea25d367d245ae599a04fec878a271344f290a7c7ec82eac0dbf10437426da08cdb76aa14697e674677acd0c78b2b4da68ac50b7aef997586da84725c2fc965ac894632a6d63f57a75610e0752195b241e3aa4459a17edf7a8337243325e619c2f5ffd7274ed1300aa9fe9b53a56780195c7a5f2ed4eaa5763593a293d3510908472c1d95b64cf578651ba13d5c1b1a174584573179c3730089d725126d272812f2d9d8d53ccf8ce9e9b357779b2a0e1b7d418a8faf7eaf4abd32f989072d42f6b6c1d03b5c9bc3efd2cfc88c641dc17ca4495dc6a65a64135af7205e57ae554b2233363d11b59173071c67c80da6919388e91cfc291e51f3a371d88b98958b911a0dfe6ede3d76b5e9c935234e1adf469288653d67eef8bf1496bf7917dc9e0ddc0990359ff39f0de26a8e4eae7aea6f8be3b35be08edd8100c0dd29799e9bb03573d32b99e436bd3b9762d7e494011197599ddcedd088526f62359de554dbc754662b7898237f253b1056cb36bd4f2c7e041e6774982aa461a7241d80b3186ef1c5f58ff58fbcfda6799feea247b69de21f03e9b65de78e1594c3758b21c1e6794980df46e61d25786f8e7e2fc68305b45e6b3505ffe30eece18b962575c7978b3a5840fae623043a80ddaf2eeb22af4883826275fc2e0ad9672383573348fd6ee7e545311e5c1559e1eddda736764e794e2cff149cfafc8f06b711c9385316d642205d537a464af01ba896e5561bec5a989aaf907badb5ba972d6cbefe3b9013cdc5461a57ae8cf7273089eb961405adb3054f54ec1528c2a72f7f1a3a5fbb633d5e922099a6ae17c211d4c61676e0d7d5d8c9cdaf0b905d0231507f96da1d5269d5c13f9cf346b353ff3d061a600af0dd5969d1ca30ddee272e970a0014c4309907cbcee767a9c031d08fad0e3b4b797b8edc390f068bddb1e640725e9f4e6a85c4655e1a585f76189396f193083f04136c847121f13281d72cf361cd2f80da3d87e81f6c32e2d2c316a7204b673c090a482b2e2c9e598fa72b648cb0de8896e310ebb599bc8f956221ee7efecb5e472f1d5883d0d1a5d53722cef5a2ed372d62701734c8b96b6fd96ce3c7cacd024453917118a34a8de51083cc2911c0fe8f5777078e60454fea3e44a6797370c5afd97577228d1f761de7215cb12644429a8459347df78875684b911a173af9dd02e5c4200ebb3a12b1472e0ef13a776a7f45be74f0b6f0e72ec3138d8c9c954459a2533eb00930ba4334f40583ea7685f0036e60c97b011d0a1d94c5d8f2a18559b5882c4bef6cdd676729024c48c560eebf91b419932f4fb27f386db982c5cf6e095554d5357040199722a732c8042a16b45be12027fadac7a9f2b05a843928698b10528c2b856b98714aaa34a24127bae2bf781a4ef2a6a64008d15fb1b46077df33a7dbb905ddbd572608adea1a790560e9cb48a863a44251916fc667706ae226e8d2f791b373626101f328db782666adcc7831b48d7c676486c1b124858f7c1b2df97c0c7c2018e30db23b804cc2e1029c19a6b710617d53b2a2f48e52e23f7fe6fea21b5b5795c72b3279c7bd03ac19fd7e7618288862ab078cc80081ac7854986751063aaa7d972ffdad8c9c816cfcd90ad53bd9b4842e33b29562fea18efc8beb3e680c731841966780ba0acbe3f03ef2cb8157d5c4aabaa44646988b8d55f94b094520ba3a1451a2d6a0f6815b6daa109b334d2bbf751fdf60ec5fff550bbf94098ee78eddb727bc4be35a5d189444268d82772c44e243113ce43e263607dd5080643d17daa726d19547c04988048c192ece358b9a11dc80578eef35fe5d5ad43eb4bc6a0775e59c5c1e776f0824ae1c88a10d1f4a4e1589078a10e6700250946b7e13fd7b904685b9cae84905ec7b6ce1f81b28be3cc1822e0c14ac38b72b07f5b739bf253585f6d2b94431140645ef56f46d32d2a49b3363abfc8119d7ef65d7820a2ce925a9bce761a0e110e293d2a27a0535fc1343d913817fb6a30786cdb8a392150987235394d05bd4b99a93b3f0561e5e5552f499f9c12637ea7d5746a39e684982655b39948d2df7102a741515f194e329b2b8d47fc2027b894aa289c92a223b6de728a125984973e6512c11c6e018ed8cb15cc2b375f86d7ca630614d5ac7bf7c65c13079a9adc2540c8106517cfa3ac56aa60d8b3b8164818555c8d9ab3d6f9257264339b8d47a1126e184b51585b4c4fd2e7cda672c9a3c3ec2507e52e786a5972d14d509e31d762756428f88c8b28fc4300ffbbcbd1823fe314d5a07c8e71a77223188696f8752597c52ad7cb77f333a3cc8b0e12e60aa65f01e1a570990126728395d78586f88fb056d385696180a55a8c47a3dcf1550dc599ecb04594ca6b72e7e28a09c3bd524396634113cab33af9b5f78b9f7804e865f86dd80bf6ca9728757bd7590ed63312334e32fabca7a1039351ba5e14e303fd7c0130414b321f5d29540d74dba375683d78895d68420b9d83e5a1d6fcdaa3a0fe25a8ef0d35587235efd16a5bc998838be2002159bfe3b24c6b8b51d038ad2c648aaf7bbc166215ce750afd54dfb2bf4c7a1072ef54dca32ae7b3519003fc5696b68a2fd3f0770e6714b049b40c7b2ad77e5c3afd3a024ae5846e178b3e6d93f45f1f2f4ce7f91162d338ba6342fee86c8a7b88584ad5b9fa311b8156cbb29912fd7826a5d7e72eab79f1a33a3711e73006e5aed6b1488d9065734ad3e07666d4c79187f97ffd72aa6b3a4d1b49aa83ae3c451b9c27d120bb8afeee5a92f48802ac75c3a703ab0f76988596cd049af6e5291c26f6fd8df92ed1e6096a75fbef853b6e33c3023f5c4d4ae7aa0e9646c0eac6a48e253c7f080ac3ad1bbae3c56169cc5c228ce6b651fd66ab01718ce1ce0150c4303e7f34bbbca0d5952ebf32fb66a9469fb0cb487210c876878a0c6a0d779817552f369a347eb47856e4c522d2268b179c19f7f4704f469c13b914492c202cb3ad2ac0f4ae818c9979398544ea9bab3fc140ae65721fb303e749532a529b1b07d77dee92a71ad1d97741adbce811c5ef85759fc372bb77f1440b8d1a67b042ee23c957cc526afc5e95bf56c3ec7909d8b843eb045f7d828540132932b3c1d467480f745683f323cd2deef575e157eca36eb4723e72217326de32a0c48bb3dad18fb46470d7d5df1eba310255bd5e08adb239825327cadd74c57730940a5721ea26412e60ec20f42a1a3ac839159dc8c84cb7309f4339d374bc6a46c0370ca3aae334aa36c2bf57f13a484a3227d46880509c846b7295284f720b2d5c73ad9de58aa80ea594bdc9f10833af235daecf929817fca572148e93c429300a88f953d767f8f215f332787847055160d53cc570086b74ea0aac9c26e13e8b27163b90f3f84d5d60e2d958d087cff64ff1fa95a548b9b61672e2bde67fff8447c8052422b62aad8a216600973f96f84900d6eb25aa6abbfc551172802775c5ab295610845265951f2c850bf8640ec2317d059a95e73a4c232489bd755b08bc00d87196384681ddfff2887af2242568c78c12e812b4de344a72b94c2951fd1dab9b8ae6b35382fb859d1bf52e9bd8521c60fc6a65b28d256f6fb5531b4fd8ff514ad457f045087efe1cdb3930108f3bb19aea001fc90aae357292a760668ea9b640d20b468d60581bd983b78c6b284d47010143a15a063c1f16c32303330dba7eb78a63f7e0ad5d5541fb0ac849fa112dabca9c03891670c27286ac84d826ad50f366bfcd2e1d7f66af22600e08fdf384ee4a59efd89950de726dc9130db5e3bd91da41d7e48d637aa961078a8832745c49afbc8970c6afac72499c21623b97bed6617300e5cb8a4fc3a6ba5db2b5af8a444b4cfed9298e870f275a1fa523451c7c85dc474755b5acd3abc3a51f117818afa04a4b3ad4c3fb37290e9540fb0dae44c96d32006725b05349d940432cda2c482b35bb3dc3377d17d1fb2a2dd84cd8c7232eea98dd0e731f686a66371b871f56d0cd7d59b0bb7067c3f906efb0dc002fcaf38fe66613c694d13182ff6aea66b63cc7d272f69ee2340b227df7eaec26cc87e04903a460ce254b3616cdc377426d77bd76681dd2277261a9600aa8b00f6bc36e49ac100b4491510183344f64805547b6ecd8d4301d1f3fc8f856fc56838aeceb40c1ccd739cb225ffb67b8cc3cf307d8e6c8993a927212111b8887d718684e493e1842c34ba4368173040f2de1346aadc4dd0dc686729cb163475e9f48fb8dc958ce2b28b93f7b149a0032b214802fab1c0354f9f572e4234cff7a90d6b1217873d602d51677693e4d1069069c25929c159949e79f720906b82d4bef0d61f92d7c3417891d4dd630a40a0197a8a41a726d908d118c725f2c724db8997601d854ca792a4294597b5842390cf6c06b2d95c8f8563d8a5c77086dfbd820a762dd347c21baccd8d51f5fe1244d67a540421ead2afa714751c80993dec95e44790f6b097f5cc7d0b703cb8efc3181a2d5acc99e4cb00a1872afeca10de5184348fd2d87c19edbc1ab3f518f662771f172e5ccd9a1a67c4d55f9ca356363a9650a14d56615f7ca990f9f42f7b68dfb34fdaa437a03c69c9f72e55943fcfc22ab024ad28f81d5fb2688b431299ef4fe15b785a7d108b4e96072dc7a1b908b2aeaf035d3a934e83d39b80ac1aae23f1173ddcf3be9023f42be670c63182ea532d31f9d79052fe8d05012a914cff9d534a00fc1c79084a812f972951874a4e68cd258c32de323dce0e5ddbdc74cfc2f350bc3a930255cb0914d7255385a5440031e6e127124ba80bcbb3ae6faf3135eff515c864d2316d186765014c27a4dc01618ee82f0fd4d66f12fb278cdaa5a09dd898e890c613e840de41720ea281334137ae7d52c77c01fa7516a2caea655f372e299dbbc57fabbc8f072dc8246b9821d01e736c414d01386e5419aff3992b91b457f8204b2fadb60d91ca84c0cbbaa3d34947860100d6c606e08d3e78a0ec5abba6631ebaa3199cdaf17fddf4fb2b4d76143c7af98e6e79162a5ada3c989ee95f125b37b9bd6114eaa4208c61675436341dd2e6ed8b58621fe1cc994f7d5de778b6363df46cb53aa522eeb8b197980bca605e67d5ee77779e9e884f6614064637099fc156988c7a7fc72c6efce921aaa1c912e50c160e33c7ff443f8e379cff767c7102dbb63bc8dfa720c1b58df2a017e1a9bf0adf296495a69932179b4e9b74e84cd8d46fba760c23878153fdab3c3926bdfc47fd7e81965c1efb2de47d08acf7a45745b87024c40720449f3ee2d51dae6380c6cedd7d39d700323f16dc16ac38f17fb9a74d4181c7264fa2e1661d465a7c2022ffdbf7803618fa7e6af8988b2b975595806efe47d1ba2212bb7c2e5d9537c8a4dcef09b285a8b9d84815856b188780ce2e7a678a972d5c788c993a68ba6ee00a3730444ed203bf700270cbdbde6379025624a3e6666d74fb2d1695d7d0ab137bbc643e3107def2d624238a5975de006cce21b5d32720258390d885745ce27273917107d8fae261dcb56351c479e3076b8dd58ab8f5607b27bb02acebb7e325256e251ba9b3c04d8f8a4148c659cca07440b1a49cb11af35fb935ac20c99c49faabc5af3592fb333bcd12aa875f29f9677217799ae7292f63f2f220d8c7204f73dbabefd5ca5a3e374e68fb23f3a0d6e799cb3aca3722f100841275476677a4969fa3825140a8ac42d836651ddc9162f8c80e8c50672e3db569a4d5da10b2adb7429a3bff9e05ccb97de322eb79ee82b6f07badd2c72e1896beab82364bf90f2511e347f2038ea13208de9946816bdcbebbbf596cf727d68c5f2405232b17c72a14c7aa7da4182f3a76028105496b4ac334197f25c599a1b411a06778a5df18ece6174b526356c29454fd4f20f4baf7d8578c0693b72c8c7d70afe266c013a8ca2b41778ec9d75f86898157c364639815818dbb7670814f8c4ff6b66e38a53d8e16511c60c6aecd95b2dfc8b7ec44da0f0eb26805a72852ea0cad1b76d802f8449e1817ae3a713957f7eaac8c6fffbac170536d5884fa6ce89b87ceea399a6e289ff22704119a5fd4c66d8b235eedc1528f8aa860e54e6cc7e074c67f4fa9405d22a49af2b0c52f18b4b72341d1d0155a248e19c22722531750b120652e5e49fa3a267f8b43217b11719a7cd4b598d3783a1a459a6475cc00d936798766cae46099716e79acdc7fbf78ba11e28c0b0bc0891c93799723660721bc3fb5354ec371b7475f2f6b19927684cd323b3215c28ce7cf95d4d3640b39cc71e64b8c91fc2a9333fd2c534e9cebb4db23523018f1a81553b995346ecffcbceb4ad5ae761c84f5fbebec324462610e21e4f4f1cbbbe7ad2491b297275437826bffb3d26597e158890685b093a889874fe218b6b202fe6a3d727e07241e0ccf3e0151450e93d4b1c1c555ae5deab49141b9bcee90851c56417dafa7246e8295c843280eb1148de2469e9df24b8e5dd89f1e53becd4cce2b34f33515348ff7ceeb9a4d0d223c186ef2c37e73e828a2dfeb10284158d4af36a324a0945fce029bda997d841c85ef60465c458a3e833237992aa52450f29099333a60f4b0cc6323b644d1119986fc2d87e295327e1127fe57cb29fc76693c58cef7109020cd02719d6054d680e8933b46a70e92a933a68762b1f14eb501dd6dea5f3577261d8aeaa12df50f6210bfdf0d7ede32b496ad57998b95123044feab290de723d71a60c199819126931406e406b576cccb7c598625c21a4897460bd009e3deb729dbb6de0f7da5ada08876b26b4d0c5efcd3b1e7e23427079714742fdcfc8a3728d993a6dbb54763e4bd4da7c10e14c515b6eacd0e92133150064ad602af07972340b454f07da1359aa0f5f8f91094c55509d65d720b2b7935243d534f9c3927235f47d0337d7b24b8367428482da803e8d1bf65bd02a4b7a9fd27654c795150b1cbbd187f5164f87ef4b8c52ca3968e2de7a073e45d0b9ef6372797f5b387f3a233318b7c4fb9300b2949f6933361e63d217cacb817acf5599ffdece383ea272fc99af0d781b23d9855627fa2ccf53c0db4987a9e411fa694032dcbac04990137000eeb5317190c6e07fb994a596d2fe283ed83e4d042595b54068e52bdc2b72b9883781e23392a90970f6f33e17965d0b33c1ed391089f5a797d06fa8570772febf3b8ab35ff9ded435bddb57288aa84fe72bd20ca031e1bcc8dfc6ad09a7387a52390bf8b67bbf96d3817541074782107fc32fcdb955d04b115cf795d38c7207023f2a9cc38adf34d32bf53a7a1b853dfcb15bebdddd5ca550db52a295ac72a29aa18709fba9c63ae4c25627501754dcafe4a49591adc72fe4a845eb1f0e11ac0eb197660a335eb72e4a03fb96e7c1692ba5e4dd34b96e403feb5f28ef2e5eb82d40e8a9d95539bb5488e1dc9f861052a9c79f1e1ce22bafa1d06636ff297256cf110fef099bb0f2b3a8e706e0e696df895e077b1698ce560cb362f604ea721897d4e016dab1dab21cbebfeebc989fef2388e350804cfea2c2ce2dceac262d9ae366d5c3b0edc892e339521c956f103328d1943351435427f2b2938d9e367214bd78d31a59f06b531d68bb7f7470a61ca2ccb25aaa85bc151fcc732211547281765d9d3dad65b22bf59b054f3c89a4ededfeaf9bcf604df26569910af7a41c365d07aa317b6cd23398c1afb554e7ee1341394f0810563f8c0b74c9968e8572095c633f0d3541df8bb8f5e597b2b73d072c20c5936baaa9598ad454e7b63a34e4b28228eb392de927626a2d43e6044e87ade17bfbcba004c21ee03860148272335fa136a64b3c8397986c40870a4e2cd622f7ef1c3f550e9296c483d20213596fd5ea941603e17552115d761d17fd10394d8b69efb2a5fdf19846387470586121ad20a2c4907fc1375bdfa7010941e13e9ce561086d82ff07e2ea84834f28718da4e4d8bf97304c86604a31bd26d6e6f58d4e32247d08617e5cf716e29260720ff55c061e3d25ea1532c462b182596831fa84848bbcf0a17d00668a16167d728b01c3ec7421bf07c99df39164899c99951ba0a449688f9f028422bd8fa30572819a4064cbcbf774bb656df7c7893f4225b02a084af406fc43c397725ac85b080a7b2d8b39a7f0c9455079464029b3bfb4ddefb53d75d5f79720856f58b36a725805fb4fc72b63ea72f8fbc28848bc768338a9cb262fe47a6501598311637c722d14e2301f5ee99668e678a007b868989dc11372bc71808b1d76e6d9e7fc3d7236f1aabe28a8c135e33c83c504bda00d84f5fde2035065b130d8308f28821272448d6c05232a5a66e1262950a9710216cee154c712b34f8709c6d479b589be7293c0910ad748d117b5ee60239abe179dd06503995e9341fe5c725d8e60ff7d72fe75d732b3b5c561a513c785733e12223fbeed877f065c1998d1c49642a83872fa6284320ecd5b19c531e53519c15a8c54a9d06e7a9d8b45c345df78922fdf723d5e0d7fa203ec562b63a2e70f700f7b0c40a5980fb7a691a4a08fec02d6231f05384e5bd1ac41a8ee3928b1feb7d312ce98ac425084f7433a711c344d298a72182ff9f6e0243ad7931d30b8f2b691dd87ad120ea29d9c883d41c89f56df3c72cb241ed3c57a66ee2fb2434b2f5798b08743c77ebff238edcdffbc4d5cd5976a0678d1c74b28370808ec15c3a2b555db6c2c95cf49ec8766bd6c8e5f150fc97231d7f02cbf0dd44002b0b15edca125b6aa7d2ed8318a5824bf77a0e2a809d77220c6091dd326b7eb78115ea8653d1c3748c28617907d2efb975e685126d93144c37f3a21dd9ee29631f82010d4d5b554bcf9a999836da2f2524fdacbcb22941b255cc52a15c710e712fba4959591f4d808192921c57725ea60aa4f00e95e09456294a9012085b15cdf25bd1612ce0ec9cc8adbcbe371d73bf739b8e5db4ad0364f8eb60bd9f2d7298654e8a6f75526f0459232ee587d2a47b06c6e59c3a5f8722381157d6336cbc226c5352a44e29036bdcf1adf5beca1dcf6b40e0952f25372d2a71f5f76333e8b8a7e75738131fd818302418b7d59f6737058b1c4d21ab77284b41c32bd57cfb6ddd8d9673e9c82c0a4c5da1a5cdcd3a501ea5b9f42aa2e72f36eab98b6775337ea21837ee0247426c6bbb89fe3ca7d58ca8c600efffee072cb9f789dd809c75cb9959369b83ac751aefd9510cbf7008ff55ea73d44c754725e2c11d9ba6e055b78542b28161b8310c75ad48da0ce921b2c6db74858d847007682e5a3a98c713384579391f7e2d3a83af7977d1ccc0e88757bd1632a5dab72414a37645569df12a061d08d6e85436883c38e1693eafa0a2f3ca4efa0195760a758c5bfa029ea13cbcf5b4ceb9ac52b6077ee42a90700acc3cd47ae8f8b61721b4d9934f6d30eb6fe3c2c05a3f6b394147c7e36d4b631e8b4c39433d41abf72023266aa8a7364c38fb3df606e42fca6c51f8639f875e3841e3943552bbe187234a4d62e98bb61d625e4b0405fc001e546f421a9d6e1d8dd8548830d3010ff17c171fe5d8504d81f1a9483ad1c2f0932dd2c694d024f3c56674283cb088e713cb7a9fbc8313a6fe277246ec4def41e740fe2a86572ad5039d6fbcbfbaf5423724caedbfd775e2cf8c9951f76acfa242932bec9cb7b20664e5f87aa4a56327872bf50216ed83b4c8990d1177f090ab1a03f8bc50f5a83b4e3a1fb1c3842d9ce04ebb53ac0d49ebad7e0ac3530c969457b7b699b46f8206dc37be4542f3d60cf710750f5eacc5558c1f3526438b826d04cde3866ca9e6398ee706da755a1d0d846af1dd3dc4fd1105166a95b0f312e3bfdd606cfcf3290e1508fdce662d70f2072789b8dbfaa80108db239b52c41e8f95934d191585d6e642315b12555ebcdd46e2c35aa5b16278f4fee9d8e8a0d09779d544c756a5009be4621d90b37fca2d04de37c6d58ec92e09c23e037acbb3b37ec252839b4823059a50ffe99620231017269a3be209c94ecbf81ef57f9a3d43c7dc0ae0e2e7293c5279b981a099f3284472d82c8a1f7cab302709afe9751a97f581ce371ac3e28715076cd66a5eca2ee3fca9739a42dbaaa24f2c6e56743a4c22da3af0ad10f1f10aa6338032ee2e8e072d91a083bd90ada2482f3dc34874960e1a9ba331cf16227e28e9a7ca02594fc72dee5c891559dc592e6c433bad00312ad2e54d0659f5acbc09ff8c7aef36a5b72b478ca7f144842ed7902f28bb249cfcb8aa6e9a376aeca4523cf2be219bb2250ef008aa3a86c21cb74be7d3bb830eff82ca804919d88410af3bedce45e01d954bb717a4f03a1b58e75181d72184d87b026ce038443e7b7d31d143fa936fb8c72e3ba079bdd8fa08552b2e1b51f2da44416741dda9f72fc51f1fc80cfc8217221794c889256d1d7fb8c8b98c7723a59f37f5d6b8f874485aa5a96a8d9c2f20a72b89414852398ed4269d8d5577e106e63853348aa0c620f2864b4e276e7e58770c7c1e0fc75b8574fa5b4def7aba0c4b4d4c91d6bcba8d9dd8a90c7769def5172acf60587eb37ea16280adbac1ccbdb61553380236aa477fd22f90067248a77723e74dc43f3a2e26ece0b1347b3dbc9f70a49a1de7319526086fd9fe1a809c172f9fdca3b0f6fad27781f1d3a333cce27f1e9d272f949181ab88c65c6c23a5c72115a114e08c6a4fb40a6ccaf613b04ee75339a7c595699ef39bcf5f4f12f9e72ac5e69712eb76ba9de5d66e267b0e23eaee8b6702bcde5312e8f72bd1a97127293bf49328c18b5705f5ab3fdfdb6d29f7512d10afb5b66f0141616e0fd843572bb8032fddd8c0d8071998989106857d18eb2580556576eebf5f3a323c6dbbd7212bb07c04cd63c364c45d97598de282537812180f58895877a0adf126b0d0d72cafb5b72fe92b07f477b24458e1e66b8fd97988ba6aab6aa0e8ebe0f5d1c60647c7b4bad4476f62ca02fed3b88e6d52608aa9c574144455f5f4d0f114f8655529f30b5acb48d6073da6c46e0ecef8be6693f3ae23d2bb9c778dafbb65f15b272110c1ed59f5fc636b625c816a6f32c62e6db3d2a31c9fc70fb5c08afb1685872d18b39f86ab6c4f4651a5c979a517f82eb2181ba7fd2508e0e8531394dca591a99237b575e02cada49df14bfc1eb9c5e60e7efc835d0500419bf5bb389689f72c1ecc23967c4d2a3940d0a51b3e1f5868907e2e04dd70da73aceae1631bfe072329913447e24b7116d5b1975efbadde82a23ec0902e83730d7c9fa7afc9e577242edc2e3cb14d46a70773c05b96245c3a339ea43c68f9f354a8efef399b041728b00ce5dd13c24ba891f583c93d9194ca66ee339cf1a94bec98d5f5a89c24b291becc3e4b5f93b1dcf1ffe3642014f79b45ff1d857b4768071e3fc02a6485f0934e0ea1557e949d07ff49360a842179c1de1500aeefa4a09a46ac8c8c52cd8363b894c35e6e971bdee27ddca87fb3b635a51304aac7abf4623873b92026fee72ab3c8b2db28b45278cff1fde2a1a1c2af12e1048d5239e0c9dfe46968b79ff72728020b34c1b0a022e6fb6d50814dc9efdd3ba40156c7cf5f4edc2050f67180ff8a30c861270095dbdea020e8d47474f884a682a40e9d1f4f0b2ce51cb06c97285a0d97d75c45376ee04a49b8d68e4e879e24536da754a57d7c533da2d8df17203e15c8383c41588b48208702228b786fe4cac696bd1492f3e06a8744295c4720bdf687ac72986077a9bf43dde02eea695c416b64593bc695b15451e7e1504722d9cde03b24d16f8997d7b5c2dbb076575e2ce5cf7fc9ec6972c4f895ba26054b46533a3d4791e362f3260f0bee2ac0f8f015a67afb2f754c856ea9162645966378f2069ef407a92be4dc1cd412dbc1005e04145a39214168ff7f3db66c3552ebc88ba7dadbb64d03dcaf145b48cddc03a9708152c6addab8c5f51fc4994a81ce7e81049463e0fc265a93cc5a06068af184be8208d4f1f4aa67c3f83a1a0ad72cca694cf76561bbbf5daec5b3f3c12e97588cd565b7ede59a1eb0693015b81254de4c3811eb375df0e99eb33fe687793e59d270a6ff05b32e0663eb996c0a8489d5b8da70fce2d50713bdae2e4a869ecdd21f1aa0096e60f0de939609d1a7850d704f2e741a39d5826fe631a1e7f6b001cb9928ec3414deaf81ca5cd326d71722af6c516ff79903ff59d898d3a501c6fe4a2671f952e545195a213c32c68a07271332b9d0fce8d629ee1ee6d507086bf71e9cbb157b2ae58ad72c15b4281c7623fd099565af0e06694eb2d5a49361a1facc603f2673147305a2e06b82b414134e29dcdd05ca0de6cad1206dba3f6b1c2b4bf96b780b3b793135bd7c4eeabc1724472dfdc64d26dbac51e0d47426c05f77e764723ff459a0fd5a82864fec7371cfdbecb333ce57ed78024f8e8335120ac6aca05774c201f3a30fff791b3381e722dc71bf0f15e8ec41bef064c716a2f3055085722e013d9f853d49389501a930c3e64d64665aed411d0e413c19ee4d6489ab84a9c561fb88e8dad45cc0c64b572ba00a9607654b0f4fcc793f379b93327e7f1e32e14051ca2b1aa22c30a4c7627b3b3f763242ab606652bc340febf15c3f5d5774b5c2b2bf6edeec188e91ab85f72231f18ed45c8dac775317c7860dfeaaeedcd545c759c99d6a5fb4b75923b51beb42f9de6671617df96716ba78c9667e05f34381f544535a86b4ef028ec3e72816371bc0f1897e751c4dce55b627b652552f02bf014b6b19c4b347696ddaa72afdab112bb911c8426f70aa238cac175d741e0094b6045c980961686f70dfe03bd10c4077678f188503f8c596daed284ecf6b74fb149f591e8f373525f0b1d72fd22b9f1ef90d33dba32d63bc3b272274844c50cf03d295da9aea400da82544a3781418c21d620978aea8cfcc457401566430bc3e529a04d4e1cf8021d53de6d09296feeadb57d09eae55809aca9228db5c0fc6222be2ff3076d1654c8d8db686917d056a7fec66d4ec4035d35141cbad3f68f542788532bfa82f946e8b90751821347ce940e84bbbe4b52f80b6d0039a94cfce39b8c892c2d9f792a38b12e723e620ec72e6f1cf580ee47754666093644642aaafcafe8ebce72f4cfbcc18c16ef643eb1a613fdd707f3119483fd2c97db6583f48ded7871ea4624ece5821d7281277718bddac0a4c65c7185db510a8cb23baef0caa57610b332996c30b46372d1c133e42ab748fc7d5e9bfcdebf2cc43c073b0109e9f2dfba0f36e863b141052c84819c4333b37dd199fb21f647631c3805a87021f845a6e4dc52676f02ce72128697ba5ffc241605a1431de9b405f14d13e28db6488714477ef9a98fa7d67290bc1f12b45d586a5ea96f5962d95ec9d37eaf5bdbb43dcc17924f68710e8b33876659287a2be76822666a58cff1f066c5dc0cfa0dfc8474fd627220aa2e457259fc8bc61d15938e23a24893972fd97515ec3804337ffca19bdac8a39ddd9072ab019b077e97df7678f8cea97f4c57327bbaf22abe405ed763eb283ca3389d375c21e654c093dd69149d79e5d9391bfffc1876341b9ce8c9d8d4e2dfee98b972f9415f5eac0188544b9b0c168cc274e03e659a51eef77bab7edca435a6890a72218515a6bf8a0cba09b1772cca0a2603e5dfbf6b75779e5689d603a0870b4d361b9772e066cba512230f546999f1143677f10cb6e889aee9820342a2bec21f5a70fdf5c2f02f8ad8a980d1d0ed465f32e453d1a3b5f0d38750326e7af77b7372ac965f37a1a5133345ff4de90a6bfb4d27762c5cacde2628aeeb557f515e9923fc7d438c7ac743d0e1e3dfa3fb6b534a30c4c5c079b5a278ab0e9249eb457403fdc03822483fa199363cebdd2f7ad3a587a15beea3f0188bb742a8dde15faa0cb78ad89f9a9a968b4b76feee80fe455f8219ee5985c1902d12a0a539144cbe45b8c7f9cae29cb67513ae31b360cf4397798aff063385be8cef449196aaf8cf727d8db82f9253c8b5fa1985cd3a66ac3e284871cdc8550799475cf7134665fd72a26775c30194df266846d32376f770e59c9558164c609911c011ac8bd1f01f72b3d82eca9ca9ef8e255a496da6a9f203fd2d583e3ad1fd1bc8b5278fd77c9e2ba8141f50677e8603334672e4d1551fdb715bbdc8694ea5fb5282af0b53d03e340b0d56f7d0d423b2f548cadb615433c7f4167828446b526d16be3942d78e5c7292eac3646d2cf5cb872759d75fd3d884cb213f1d594f981b8199a77d105adc72b07792d21ef50777664fbad02e787e7fc1eb25405c0fb39881cb245a12fc6124eb63794f64657e4b417ffbf3e343c1a67a6e48cf11b188a3005a8c5810b33372dd0cfde42fdbc045c7ecc7926ff99327c7484df93cde66d74d4c79be51d40d28fe92c3d8f38da8e600d251c1a19f6837bbc80e72d74a30793b040a6a6c4b12640a074654caab09f7898cfd6f4411f8c60b74d75836ef78660f83f1f92f0b8772e9625f9b74b3c9a41719208a0a4a041fb220f08d64072016c12d3128741de720cc1e3eb1897e94d4b279d44f1d1cc34a4e29f42abde4ac8d9983e165d90d8f405ca216d5d8fc829e4d43cd28717a72a2ba74140886aa7a7bfe6c312c99ccdb72e6c23a572918d3493922bfe3b40014ef4f5917b7f5769ebbccd04593afa2fc0f7147a72c0461c5f59c0b7796117c12652cf2258a11ea4f176449ebc52c12837210485a21ed195f65cddbfe4c66a664cae9c4e755def45b9a63d54affca2de672783113684aea30df6e2160645b603ae9c7739839c47375c65da81fec91f6bc3d5f0873c70d45d849662bbfbc55e478f10952a73298c1e7920ff34d8ca33cea72bb0e13fa2f694c11eb5dc35f86f04ff7fe32343cd1880a2e1b18e509ccc87369d529266e7ce7a83d5a9722d847575105a7eb6d5aecc6fd397c0fcf4567dc1a401a445a25651593c0835e3b6fff0df9cd7e93871d0a7f0749eefd26eb148818039ba866b1dbf6c39ebed9505c356a461cbcc2ba2b1e5b1fa8b45fd107e8b26272e3ead8faa5d1094751f35e544c95c9cbff12a4a90ada2cfede0aa082504ce74413ecba083a2aa971206e79be219a920f1152dac471af665a903b5b1338c1dd729fbe6fc4a662036f297990cf390e40071fe46554a023432906727bf0f95f0a713e56238dad9a3f7596c5cf348e288aa58792d988f2d704f7dd55b21c51da1a61aa538f190542eae79c0be8d885db90ba6caaa4833e4d83a43a5bce36f63ec672b3580ba5cd83b346b11c838391b73f1b61e1993caba0a51734ac3c46be9877349a3ab3b41ad63b189cbedff53d908179673d1759229bc3384826bc58875f965625a7a5dd5a57ecb1ad11ac2393067efc9ee6eee6f890ed6670a636a12d9917724727528e5edbb408690ad968dfe4bd67b01914825a5ed397c1beb94ac97ad97200ec2ada83e7537acbbbddf802b79a3487e210a59aa6160a3311fbcd96751372bafe9b3cda2cfb2c9caf2a4fa7956daaf17fda38e3834a84b3e9c3d1bb461b72e1fb1929989e7286c47424d601077d89a25fe46ba1b284b0cdac9c0ab9cb10721bc8df2b1f256e37e71ddb86cae0133b5582b9f5043dbeab4f9b279ab29a54166bc8e51db8d00aa3d90c15a512610a7831d68e259d17b0bd7113f98dd281e9075dcd38b0c5c82b879125b5e419f5dee5c0207ca88075d6bafbe1e66f8b251668bdd892918a8da301d00fb39062d326f7308644c20d877270b44ba991284ece72668d4d25fe28dbd9b7ff367cd7f355586ff83997577fd1868418edf95cb07972d596b0cd103e48a6bf1f1a28f3558caff87536ca3eab15c6935ca69e4378a27205be9f2a1bb35bdba67dcbe153b12d51089a26867849a35ba921e48bba13b372f228ffb2ebd652f5c70c729aa9ac84727233d5a28b69a8d60ef9170056c4c172a31dd92e30cd1e35c91f4cc32d747636d369cd0224dadfb27a44cb115c69ae724c6b84ecf4ad5bb6be16dfdf84471891472b613943056508db2121b741c12a3f4c6ff6710c0a52ca9ba643bee77dd89a49746d48e4b9e5f8de47347493c4051b7ad54c77c669635ce258243893492cde3ca831bb6a76d65dd266407800cd2c72a79b642f8aa5a1a4737ba139a0c4e0fabc21d5c986a8d6953415c65336e70c41ab3f9f0477752c48233a76042b689247f8efa499b022a16ab35c0ff2f1bf80720882804b13a8e58691079f30c8df02659f91227866390c01302660ca6b22c772ff4ed25f14a7543cd08122d625e0fc988442fc80cff45d893d49455c20d97d725dd62896a5f32db58f1f3c633a14c977f455647614616f515b6ab0d47e7ba0727b7b0cb4286069a2459f6c3894716536cedb08bf3afefbebb68762623e645b722c1a59f30f07173dc7d6694b3bd1ccea5d2c18b8f8822061d1a89eb375607a723d7716c6620eeab91da76bf550a7fa2bcef95f725b4ac12f078f8e4a2bf28872c56dc5d02289ee2d903f11c4b8a69813e5fefa4fbb091052e5b9d2e6dd1f3a58e1ba9bbb8d85b8a9d7f8b4b02398a3effff53feae042799e564797b4759144265574f416ccdf8dcb2b5534931f78cfc93f70ace13fe5b0d2fa71e31225758372e6820f83fe383c758a192552aa412978d3f66bdc6ddebb3d0b2b2d90df9b5a72c25228cda9d270d05157ead07b0fbe5e3956b5a0f63e07def22b25e7af6bf2094dd238cfca69901232961f49842ad9d6c8f56e1729385aaf81af253f22d1bb1e4045ce48da79bcb8ba6337a073e21bae90a50f2a6a1512cebfc6fa283e335d6ef3d0451754f455f6596526ab922acfd22906d0d9fc4f654aef61cdeb375cf172c21fdc2726d64db8051e185b91c4de9f032d94a6789bcaa8aa6900242365643ae31f12263155dc5ae9ad40217434e52ccc78e3999799fcf62ba749088a323955091bcc0a5ef3fe2466e9c919b74c7657ebe9f00f322c67661f0ad6190f112a728aff5aa892c64e6dd94fffbc3f6462f8f2a7e2c96bfb158a008d47173954c223f2f8bc6bd5ceb23368f7f5d02a77caf0d301853f073e20ee1bdc56a7d1c28f7286f715745cec8c79d4e479d9f733c669bc3b71df773a76acdfacc32d9f91fb727c16e1cf4c33348639a56cc9c552b453f873cb9192c3963fcd60fe644f639e470f998591384b19e702759ac5e8e839e650560edcc7c591c1a09278222f533f7284b23cd18e2c85546cee7374763cd974b4e528033dadb8070fbcb5ecaf0fc23208629a313ee7caa2834b31b2fa30de5ed8951825fc8c7fffe84101175a39e224f2132ea27ed2db4250e1171e8fa1ea811c4f0b6e953cd07c8ffdefe6ab54f172b1e359b903d24e1584893832e3aebbfa5f0093ab123a912cc62536ffd6f2db72717b9f47d7889dd5b99e7b5fe5c26d8c9e98226955b3d5e47a50d5ecb54b403570b79b81798cf8b5efab7424e55a09bc4bc74e267cb8ab483780b77302f10972090ac57a7ad59e5da09de24e2ee08fcc66ed0c493953ac32e4facc8d330f7654efd8ba066a615b2268465185a728dd24ecd53f379cef044bb87b0bf1ce02eb00bcd3c2b8f69f91e0fef3a9ea7f50be32706a298e2267b56245b9bef210afaa725ab6f278bb3e3fee66955c022be51fa3cbee8c12c1e5e578ae8c32578634ad2dc46e72cc391a13a3058b4daef72cdb0055c18b850088d7e38d4bee2b3b396b5a0588f6f086fda60ea422b988826fc9a83ddffb293e6989cea6c95c5e05cf7272fbb969cf5f489ed705d69ef20be3e6255e547475d299bb5e80744235f2f41a06ecdc7f8665b9f0187e8df5a4afac6f1cf253e4f77ad734d370dc1498bff5907222868b1c2df97a31855781082e058c36abc31c15c5af46027520b7549a2abf72940eb4d2a131b08314969961a20eb11f28d4b910e4d004a9bbe757e2a8530772338231cf8388e6347c48c13939a661bc6e9baf361d41b83063ebda9143131b724eb75e98a25fd54db9c9ff62fe82e208a38af433508b617b9632cf678933de40be03709019c8d22a8137616c3fb1deb1323949561aa14cc889138f4adb0b5a72e83f2558c6fdf5a2e9715b9521d9aa7d51b68520941d71e49a2a7932e15f5a725ade95ad1f92013cc583ea6d5b1a4033a36b07ba7fd6a5f9f076235515f44d72c45bcfda3c143e8a82c89d5c2046aff150d44a233393d58f554b1ea0c7ff8972a248fc48aeff67814032adcb731de27e05acee7d2f81d217886f88746d35fb7295822d92a0af06660000caa041506567c1b2f35c0a9b48d8d46d5037bd0f960f8602de1c42d33a0381f0aa5d751315f1b39843fd9fd8904fcbfc682fe6aa8c230b033dd11e9d66720a614b41ea08c135322990826374b40a5879cfc0250b7872b5c359fcd5fbfd52fcec72be3239edaf1f5d4275733a821451203d1fde52e2725669d82536c04541bbe7335aad0178a577e3f1108621576636953563b8441c726b3790f63a461aef0366e770e37824b49363ed330202f4df36bec4085e797b72081fa752b3a64fe4e3f556539f3fb819ee940a62d48dcadb7831661b6012a372b25730e927d8f635e430d6da7ce3b49969d3f03e45227610efe2dea39c5c55723fe2b627da837dc0ff35ab4eb05b5fdd76a8f2c9c365031899b0b5f44476e2727907f27ff7f40688b856d77f134eee9e3c0df50bff71e604f1df38e6e8facd5263d5741d15df69be665bf9b79b54950212f219e466ee6df63b67158326981c72655200a2a3d1492f9e5fee103e6615006f6418756b059596469531ae77f52d401c6c4a88bf620c5375a63c717c00c9f21c44596c1d082f04ab9d8cf58b02402f98210fae374d27c995d6aa5eb0d8794c79cbab97709ac60aa4cb13086bdbaa7267a5677b026c396955c2c67545901d33a2bdb294034dd392f3968d129b84f56b712774c00b7f2063c40dde2651f7ad792d6eb81737b7d5552975896290110872ae55524fdab9204265c10af22293303560c6b89b8628e4f55901207cd109c672f612cc43a83f8d9443e67c88b1bbd901d566c879c9ca0c06cb1e821ac7dbd53591766d67a47303573102852b1b9f8988ce06791902364137086954227b80cf639e13a8f5ff1b9f3efcfbcdf0e505d2814f104a73b78b4f5f3d99b20aa1fa245c52c69c9bf9433c31582a374e6f408e6e5d5d1ea73b0474c4380a0d9429f337728899f97d575d039c454c64b68ac9f739ddef583f5632001fbeec65569f48ad721db2cf3e25d42cf426d40a367d483416e42cf53caacb02e3fd1a35f510945b729c7e023dbd6498755ffc62506834dde51974e0c6c92d7982aa4e03e555d47b720bd9ca8accfc1028b35328ac08a227250300633cbcca21e489c17e808b392d72ae85bf1da4df91e46d6a0927c14855ca3d57a65c51309322812ba9b95607e672668ea96fb150d3cb9ea61137674d13d1eb6f1e9f27fa22cb1c8ea7ca885b66722a54ddeeac6d40bf109461ecfd37281bc16b3598905687bed80fed28b8989c68cbf38914e5632f134515a956fb070b54cb314edcc49a66988e9407518b70f5728060674463448e02db07bb5a2490f0a55b96b97bc60864e824953ec5f3d6ff7216d5bc82ad27c8c822e31893d38d466c528b1f7aa552c5fbe0f88524283f3e7291a797d7d40b1480afa4d1d180c4d3e6f19386c6c287103f293e0a0e702a3072b947099b2dde2a23966f79564bd95c4b26713c6b8f8d70ee5d6f8b9e6b6f4442797f74399aea58ef6b0e404ff71f24497fbe468b4c2cdc85bded53d76c9524339f68f150c0a5111eea15ffd564f097b44763a5b86d874648a79f3f5286505119e84cce38b40c6bb54d491d26b60d2d70d954fa147bf1ecf388dc87722ac91c72d527e1e738b1b33118facdbadf524f7b907d05de1e2b7b6f7c14bf67c41bb812e2d394424cbc3c8792b1dfc1e3b8236328dd401f275dd8e72e1ffd9cdb5dbc1d4a5cbcb74e102e495384b4e74b016a7820526c38227dd8d783aa2ae9caf9b77277f1143bbe86433c7c6e6d329d6f88e79bab6ac06dd2ec494eed6fad1b040572c359486fc948551d339f95608502c809a7461a295a5ddb5ca3c5995eff91b77273988999b46a6bcb2a0a708f4370d53bd6b8cb3c84ba4e7fbd19b8349d95b2723086668084067c0f3a53cc8e348c5302194b4bb2d952c872fd17bac12d1ea372b992df58d528361c5e2dbf3d52e24b1437d3a06a9dfc9e656a3562a557306e72569bed73aa7dd45804d74327bb7d37c6085d0c11278459c08d7dfb9fa74b147251d1b154b03a3c46ca50e9f2e370a6c1012ffd88a9fe52a41687eb84e8f92472fa66393ddb823349e3486a7f81d16f82f62ad8d491f8f6769b90ef1def2eb00d1b5021fc6aee8dfe3417a066023e2f37698c1ee3c5186c87494e441a720952721895d46c9191a5d7686ce2bdc81abac2de4a8872d4f957051be50c774e70011b19daf540728bed195ca630cc56180226fb4c263e380d2be8b94f867c0c0a7717ef6f3e7eab7b080cb35b65e49fd7cc911e9374dd11e0f27b7724885379d6ac2f8acca311f55b1ad62ebb9f1340e4e090e8950226a68add33a1e2fee31292c35aea3592c5ab581f2931bddddc39e8c38d622c4cf8665c973f8717f6ed2d8a9472934ef3b3562c992bb3186e61982f3b613fc509f285933eddf56eab59caed23726e6c8cdf85729fd5b5bc39de6bf571a6520028f613cc8a5bfcaba98035990d560fb1487cca910ad8216073b55ad671d0ba1cb2abce73b4752e6030ce45a969723f56f8320da7ecf868acb430e2e9a5c290d2337de382b0599732d3766e845b2dcedd8ce058223f5bdcd421b83497b81cfcea99ed3124149b7fef83186783cc72940033f3b95df359c68919b5b8f16491af81418a1c0e28f7cf287d485488df724a1837ea95f3c5371b79e51eaa48a507e0ae464e1f53578f751b14e0ae53c03e349edddf2984595496904a742cafe5b7f61ea1e9aa27318960b8e871f6d538368f7fe743c6c8ef975d86238d1dde81f170ca6de32fc8f594dedb982c2cccd30fe584ce763d9bdf18fb410075fa5b3626fb206c5e5c74ff126d51e372b80af27269740683842e603b87999b3eb2734c76af5b646e14245f867515514954b10403fc6e8bcb773ef1ba58f753ba1aafd046488f0d50ee7ea6cbc0bba11403e072726412be893b4a400ebf7bc1ad88517059a042d6a406d77fd7bde16c06af10ea72a12c90529d16c6e24a020a05f20c7c4c7ec106a15b307088688d1871f65e3d6bd84336abe7dd4cda44f37ae5fcf56c627b738e9431354c4f9a0516a4d9744e389c02fd165f8a472aed253fdb756a5a471792043ba41115737c7e356046d0d57229c447bce528a0e6ef16a65a954a5a11bd11fb143fc1ada5eff935874c00b95150885b49748e6760d440dd39ba7d2217d4175cfebf68973a5b2d5865783d3e647f14a30f341129d2b8c2f9297074d77dcdc3ce8b88f3fba39cd3b69e58d9d937f03c0f2c1156b5bc5c2792e002e8e2e3d25e4df43efa93a1e82f717b4c0de5385f08f4e76688469a9fc8495a8aa4a9dcbfabdd30fffbc08c8ab00b267df60b007843e2e4fbc593fd91ad06924c81cadb6f97cbb5dd9848a34b6ae4af32972054a33f08a09e10adbee219f48ad9f6ec08a68900b3181311574e2683210f507872527979d3df158a19e8bae2e876733e44423538e16ba517ef6a61e031bd7921721debd41df5ad3dfb8e171c87495abdc15d33bac87f5fc2ca6634be9592bdcb3fbb13c299e7809e25167efea55585a7db31920bb80160330444cad54504efa97299b45a728daab0a35b7fd08a8ae4ec0d786b79fabde65d86c405b6a3354d887290f99a31b6d206ecf72c5ce9f8056a8b3b0fd2ee3369f3437e5ac7d38a523841cbf2eb838052f70562aba0b2a5eb8bf76b3502284bf8831176c1a874fd53a65d79cb48eced3f76e91cd0e57539b9cd6779b8ead6f18668ec9260fde2e1b2467245d3e7483d7bf5ecea54c6f9592119567d851170e2bd6b0995d42de4fdc6277235b6d1c4fe4b0d30a2a49a4f8a51e00ba88177e76aee5f5c516b9809c3e95e6c996be8ab661119a386cf04d82ff4fa8074c5aed4a6da24eb0fe219a2216f9f72f44a6fa6858878bef8184a45145e802b6cda056c7e3570c00fa44e6969f10a36a7c96c0acd2ddc4fa8c47b36f0507e3d6c42a741c03a7d99b277a706732e704dd9521c80c600c6212bf07c56ca69df50c91d793eb9722fb0f0ab85d95596ff4c7a3583aba1ffcc142c0b7c67c96cf2af3747f30477a5e5e5bd2e9a7146b20772bd8099b2efd872381a89accb7c917fead091f2d178251cec7a9448c9df66c22dff982b1d8a0c0b430e4bf01abda6432f985dd3b40628d7e803c0974c5129e172ffe026a4991ba1bad46caa1cae580081a36936ac483c7a0761b6a03e7882b5102dd15dc157093864b2b05e7edd59b71fea4e8d830a349f9f9cb56cdb8875862b9b28c07bc8664dcd3c5f31ed5246b550ae3e9f5989ab9c90c9e74a3a559eb67249bb954aa63efdf442a93f3fc46bb5ab72faca0244f31750b723cafd7aa42072bb97aae79113ecd997d9e8c1142b90a22667ff20b37163c3096c232c36830a4bd1f0af15c0b647c233c186621ff88c32022de5c2f454f59aa2887949fd28725f77413becaed02c001d52267b9ce00779411ac6fb63b214f1720dbece4640504c0ebe6e98fb894716bd6f0ced5683e4aab82835a7315170ea909846cefcbcc245ac2bfd27c8a6a71f6d9ebadb3c2d8bf86057eb0d5bcd49e8f45d91230e04297272b2a2e857b00cae0a49db3f020b01d5568890f4057f6375ab47b20ccaa7cf72943cbe4a723e604c258016918944f34cb8a32f90b713245de705eed16b49f945b12e1946830bd78bb5e50fcc6bb6d96e11e31426ad3786f12f8b166ff335097215c7de8ec9b3c88d514160720761268266abfee1e42fbfba168195ee9ce568726712d196bf987eb60706ec6d1b3392e2670fa08f57d45bcd0ede12d7d55c574519bc29003b04acecae9b32399ade0655a1c47a0de14b31abd291f146a1f50f24ca68b40bc486e27143500fad84bc29966c43d2fd37bf790180be3f1939f7ff252df06bf6df11865e2bbcca91d247d8479905a4c57d4021c10b2a9ecff1280472c624fbbed9e1370e199a81c176bb8c4e09dad47279e2911c415a004b37f6a8440d4ec0f1b7957810240b11adcf97e41cbb88d8a68927b319e07db6c090b8167287a8412fc602b48a685de51053af06fb124bbbeba933960b5f4d77363810b6515bece1709b1d0d8bd7bf4e332935b039b522891e34823f95c911ac2a35355872850e241302d5932711fbcf89f7a02857681e76c4b49d977b373e7b8502093972d3654eb3f249c4143a14eab3f635c06f3bcd251b1611208e8b7c47fec68916723ef2aa7c182814b7a24dea3aecb799d8a1b0a0629110997c419b12ae91774931641281748598a1ca2f256346120189472976df392ffe6b5e382747ece0bdb1721a4938de013ffce2317ea992dd1d68d50e150fd1d88e07bfdb1b55793116823c54947a28dd3179c00c1d4093bf59d7a1e8f73ad6aca3af6392b60d47e8c07972a527655cf5a8e9f654e4e611e881c6814658c9bdc63907a448fe42177ccef072905173e71c1c2b83b8334e4cf65188e20c05ec810c42c5984cd3ecd2740b7b72fcc435126759dcb83d36734593d934324534d49bf6d42a271b78da1c9688335fe1111b29c6972648e7efa7c49a1acf026dff7f7355cf926abfa4e4ca7504eb72e17b3c1d3b8896a6ea357f63bc880c4843d225b710bcf545b2e53d2557da557242524c92cc919d064744f3b2b34f3bc408011210c6079d063e290c26a263bb721c8684aa6de1d7cfa130c117de4e199a35c8912a34dc8eadb211b2a75d10be72d38d9c769f99490973058d7a67b745b694f8c1540d1f2904fc027340c081a2488a9827bd67b54694d6bca04f35873feec64780f9ff36ffab08462b2b3f3b47729a5c996ca87a7b33b8e51db971143f98e3c728a5515c54f315a3b1e092b8c33b3eedb98fbcd0886d53cacbc2c4a36f41e7cbf05e623647d5af51f63d579d1c727a86adfa9a869fa26d06c23b184cc584c52e2728e549ce60676509866dd9c3725719d65506652bfa8c84a690c88190b4cdad2cb77d136e5f1e17dbffa5f9f3726c3e78e45a0d6657b2d50d3d0629ee822eda9148578fb3ce7564d3dac51c53031da7c2a0c6b93d0fec396ab35cb1771eaf871e01423183e439b01a529f1f64727449a57f2b71cabfc6d630e18bb2c8566327a3990b8e1b79667e6baeee9cd6727967d621e9c6d3003b1a6000334bf6f647199a506553e14d5d398d64d4137a72a8232fab4c9baef3dc97d661bc647fe557bf4edd9ac1f35d78d3bda072272772c7216d46a561c60e8662822f8cae2416ae8fab6d26ac72264ed21b0da66750725ae6d19c25a7059982dc0447a5ce35a68c3633501882f06e989759753d99d972f1359579d7539aa1fa84f081326b842c3463a61586603367b0987c71cfc68d654fe92717db7cb9dcd48a037ae8481d31f17cbfca8a59e4ea725104d474395428b9f4a68e582912d29d18fa4e31f69acfbdd41b864c07ba28554396daf23e8468208cd267de0dbcd4314081e1ba557f661b1cedb7b4629ffa34b9d1edfe33bb03ac11b90119cab0d73f2a34651229a0afcbc34ee4aeaf23c5bb0746b6da8255720a52272925fce5fd4672eb732894d98554cfee57097b3fd502b562db9a757572f8440852a90accbc40aa13dd0cf3c63ba7a2daa56b83d0d68f834b0771cbb77071289c8cc78bd0634572b5194ba47a9b385114576222a56f4e1f27ee7479f1724f0836ad4f11b11736bb2b9d8dbc2a3658cf45ad3f2a3da5e9c66d5957351004c65c67cf939968ba9c074c19abff3198357aa6a8e971a70480465ecb266ccb724d2f8e12fb1f9ef35ce64b2223581d369f66c06530a6c1ef2f4dfeb3ed1388720ff96f07ff0d942271ee7b9fe267b5cfa1989dc4aa6861f3480567406cbb2c72536d981822a219edc485bc0ef9ed4b7c948e5ed8a220ba119e74ac5da32ada72ba26d0431adc38081e67a17764fc41f091ef7179cb0b4f818f63cb46ed8793447ff19e2e215d0ca1c6b02c3b9302e790d0728342328efe1e051ec4eed1c94c169ba32410c7c50a5226e95a8500e6c21b511a622f474d299359e7c8cff5c4cc72fc83054f62531a5b500289afd31760fb2fc615b6f109706c1e5f3c510597bf7295213299b9a3f594e3f1e4c527e49f15b20e02b589823446ad441e75a8faa343ecab6e9e70a5f680b40a5a76bab3c70179ae364b91c720baaaa57b9e2c4b4d6d582c3c7006acdaf2232223805fdba8ae2fa8c8610b64675db63f7b9b2fbbf172f32c650a34a528794dfc61037d885d5dd67e3671b0fa5acf4e95d3d374878b651d4693409ff4bdfcfa184b547c10896d061a3b6150392e0d70d3ee336e826c6cf0b2a1daf80a3063449fd50f58403fa99a60444e476626fb7fc1c8867ab42572f9011bf6cd614038a063046cf4d4f0e81d848baebe6a475b7b3ee0dd2d9d6c0cf31a2ce428ee9ac3b2193c26aaff758c44ed50b5932e0377c46c4f817bbc9272d58f2f2ae6dff99594587f754d633b64357ede03cb898ea58679a8cb5df1ad72b3d066e1431741f9daafa14617f1dabd48a29cc52f19a760d4aa373aff836572f9aba689d888a0cdf58d632a2d597ca7a95e6f25534cad4b5533ff92466b07725f8cda761d3a8041ff324a4e5c2b4012fc7af11b2912fea5acae7f65de078c72845a2516a14ecfc76d8f24b20511ad27833745fb3d411a895f746bd80d66995557a339fe73b854dfd14f16fab192bae5a9c34737d20d25a7d1456f53be48ba4f1a53c76b132e54d4346ecc96230cf5832bbdcc8f23ff37f04aebcd604c839972c2b06d17d6afe5d85ce365d564d3d0a0d9c210d8ae1fc6cefd5b049591982664b3b5914e8eb81a41dbc2d4f4f66dd432b2a1ed03dcbdf0121d048d0fbdebf730d78f4879cb5b402dbf40f1dd6aa011df5532b4f1f34c83ef137d387ddff1773b2ba11d0a0843121fee12447e01a6c104ce6b4bd5ca25c9bacdc3c44a3e5e4b0ddfa784925dcf94761344f1b48fde86d4d19033123df7746fc918d685d2ca2d72a5d162cf808448038dd32c47048d44309de029bee9c9df73bc2f05c51361fa728d32be3c2976b4eec0d53c6a84b93a8b1e5c1fd39b7e1464720b8f0c7b356f1f138e25d16c49f0b6adaad27e10d66df7bb0bfabe3b0422e6ee1e75152e397e0f6f409b4a5c79d93500229d13e195e55fd200b05cdd57868c0c29068eb399397226aa5b3a60a8f9d76fdb981a4847e7e1392e65ed39df6c7c78f668a983d68b729da7361aa826db2c3a68b75763f57326f39d03d62b1c30ab8c315618d1e71161a2d74dd40131b7324132c78481ceae019303f3512021b4c99d5b4d94c4788b3a15df17d29c0d5b1dd91ecd6f3f48821207655a3fac3c0afe2c4d479d8f049172aa137b74f97210c25e44dda243fab3849611b1d458ed2e96476a86d963aeba72afc2295a1e65bf9b359415fc6b4b7c9c95eb7f668e3a6546724a3b43434012720a94167b2c9b179da24c824826782ab57cf965e98fa1a3e16cbc7c80454672726599bd4970d119b26fe64a0d745a1170955f0698ad8a53af4639cc43c71a0a7299c3f94c020872572df14aa39195b33d20d54f51be0f31f42bf9e84ff36a6772e7960fd54579b54b00b26d78e6653b118c299c8f2ed406c1ccfd901dd923847225eeb656fd4d25fc7667db84a369c2a271d6ae5fb414a4e8d748afda01d75272945c9aea464f8aba433516306cc275a86b7ef23a46b1ea03a0faaea97c0169175de7cd6644fa9f432ad3a56654eb15b86a56bdc973af8cde7c425a96dcf8be723c2e9e514b0a89b1650a39f97b383cf089e0a69591a38aef1e1ddaa15613753b5bc7c14e8a1221d0198109c2059c0c8c7344f8ac7c6ff0b1b17220b89696ea51dba89befa2fc42741d734d35c0b7940d4718a747cc6937059e60e5845533417290ab889d2b7187d10ef87659be924bd3f5d198291742c18abeb0128c740237727df58751321db8c77a365786c2e58a1525c6cd8c024f93dbcee6ad1f94cbb372a89b742e9d803eeea09f178cce5f492203151aabbf0b0e8248e3b0127524b85e34d314ee769f40f5694faa507cca049ca31792683aebd957120c9a191b263e72059b6bca6623cb1897e1374aae6fa60a4a0c6ff48866f10332af4c9bec074c72a0a46054d8fa128a9fa9e8ad2abc1e1cd46ba73d94d9ba58421717e7d5da327276408b802d34dc68d5c8e289a06dc254773adf8f9b3555d288d5c957b78bd86765b4881a762143844df230938bdf1e98e80a9558b9d0698cd3fec93ae6bfba723c3b97f61d2250fb6f6593b85d028de8915f6a6b0b0228c98f6cb2806818f7720f823bec6fe28a61afc52507b7f3927f8cce50bf3cd6c62c1f30f6574e0cd172d41c4f8b4837f7fc1d5749e795a7e0dd933e28ba1e75b6ba7eec784d380a28726774b81e210d435097d9157ef6c994122afd926c4879a936c13eb96c8aebf9722d1b646878e4582f5ba54fd7ef2deca8d14cacb33f105372b7dceac92d81967298fc65eae4cd775ffa6fe000d7f57f771880a3b98ecf42fb3e9a325a43f32572fa6609e62304f900a7790f5c4fb9ac38688409d8806896f05d9ab1ed8a47a372b8eba86daf4f4b37cf11c0a2305e4da4a3f98837eccd7fae133caf944e50c24af81ddab399459b7c8c3d9fda14b353e2f87ea43e5ea8ea99f531c9fe14739472f5144c9e27295ab303496a2b74a73a3226fc4bcd0e0c799ca24d7a0434014d6c285c37350c52aac0c8d43e5193e72d3091aaa2d63c555a619aebae5c83e53d7261c41a1f7d8000af53b174e53c21709d8eac4369e732dc629da6dbce890f9e3cd72bb97e6dc5f1ff476a9b944b021a8d90b0c96936eb6ef4dc57e2c610c460723a0fdab90ddd2ff508fed3b0fa0defb4b9810438db3ead7c5df5172986831f72ae7a3b95b286d45014d27026660806bab05a825dfbad3a3a5a6b886ae6238535f916f2061da7197992fd8ee40b36b84fea1f5d58a48d35b778dfc35ffe9dfa72f6b9c95ecc81622bfbc777badb9a2489d53c34a510d765f851b00ae93944b6004e866484c7295daf77ff2c21f2e6e30aa481da60c9a8cd0a71a53f2032a5ee72a339cea9facfe3e4306d1f4c797fdc5df53a1f55eaa1fef018f72f1c85425d728ebaef3a7b6f9f4e1f6ba9b8cd569e4a0b91f40418193265fbc9cb89f3048f7203c687b92b55258ec8f40f24c0805444bc5e6ed09d12d5e30745282a2e83de72b9b36fb55d0466f133b9ff2f96580a3b22bd5ba8eb993d465a1a70fd81ffa4727b91908e0d6915e667840fe6380f796ca134e2288ffab53c37d2eb883035f639f4d51e05d90aed45659c10d252f449c022f96dfbe70c6f3067805a55bd89e372202e25c28c2ea0d1019bd65aafb455d19f05f7fcf3c480b2ec68d6a31d187f5758308228d11d990c485cecf0079324a45d674ae5ddb004e419020a039470095b247abb4c50c2440f9c60042801691a26975a1c601a6cf01243a6433cc87e5c72a7872a4b3a8ac149e6c5545123758c145c326092603c0fc5020da660287020199ec629bd62730802e1ba4ac66bce3c8d6d6074cffde822f9d9967241b3242f1f8fc01216647147a1bc161d95c5cf4885b5d160f5a8a676032468c425275c0b72196c1bf566ec8c7e6295fab9d8de2b0e0e28e6cd20ab519a2d2faeb9ec490d727fc8d1181b367b7c794bc60ae42596ae8ef46de223473c5e47c7ea1f379c5172dfd74f589e2f74bd96999902a4fb04a53c628436a3ca57c5f8e76d48d8707e721ef8305134c617f16ed668c0705bc3c743674d53f8560beb02667be2973abe26922aa42bcccacccb5e67dcd8da7b94eedb7f9362b49726dec66266cfa9b33e72b175100a502e07b9e46151cdf1295b2ec385416a3a08c5d7d5a0de451c6151147f148b0813c36820285a2d063da5a3004c9404871e21570a9b67888c95e4d2727c6cefe9567ddd9f2aadda65869eee170d5497fdd86f885e281c1518d438b27279aa0766b7aaa029029009ee068695c5ba3b80e5ee204cc8dc084f9dc4b909724c48d516d98bb898ab068a5da66ae33884c5751a07de32fe2c6c9b0cb2f81d72bae4c34c259ca02cab899d72528378d4ece77ef0a38aca589d7e4d81e882407297d010c187ddad12e72cc19a8f19a5df877f2776edf4a3a49aac27600cd49c265606d8bd799bb2fcffb360cad030c5656c68bd1aab235b82d921d38371cf5f11b47e583f827e811a1856f5d464f1b9b26cbf394963ad93db9af2f4fb690a763501907c23dc915ec6dd83e90b6bed527d2b9a0f0cb8c9e301dcdf7f7735511c69f204860d5ce9a4e7da30880ad532ed648fb842c1a473591b5a6d498c6ebcf072c5964ee6071e51319c5428b52c12c02ea7e69ffda53baf9830094f42e3c75a72d853866d36b3ff999c1ca2292717e1ec8c4d34b751da1632a9364b92d2dafc72dc8e3bab93c023a8283bf2fd8017f175902b72a1de8a0424d41705b06d643b729a5b4ee69011a5f605c56a443ec6c5087e83598e5cf75612d424c745578bb8725f461cc7267bf29bb5be24051ad94f32c0c1ba5b9515c44dadc4343e7e456c72532a70da7455a4524f0d054b004e6e46304958bf724157350169a7d08e5d48086490b8c8c3aefc86acba813f2391dea152a6660ee37a5b00b03661e20503720ff16bce5692da64d7e0421ce33c8d9b29c8910cccd7b499b1e69f0c666c139d729fe8899375d006df0035534f6344014c95172de5f4f6ee72aa342e7836d48d721b5ab5fca35f2db582373f080060483b393d5c229dba0220b326a3873ca2e809166da93bcf204d6128d03182e37e6a454c9c7184115f44796e7e1bdec54cff723fc8d3a7eeaa3aa08d36e3605251f3f6b037f15cce47c0351314915493d099725e9162e1ae247a975559e2afadf5847b1a1de398c9059e07436a90155081bf727e5a06b2318aa24b45b7764ce03e24c90c83ae5ac54a229effea22727d9e6c72cb578701a19adc85a2a01eaee772d76a6f23dd14d178b52ebc007f5bb6ed3662d6812995006d950ab12a841302da34d02364fdb94f9ad7614f2d327c4419cd6a1b8817b7c5cb96e6158b60081d1c914ffe9fafab3f66f1c482ba0d2848cae4727e92ece71021f61fdc1a94acbee25c3f849a4cbd22a3c4b4194c2f613f5ec5728ac308e980767193664aacd55a7328e6169be210fbbe8cbe805938b7aba66130ed514aee6c1f1e81fd5d87c4b5dc6e258ee02e70bcb250a5be4487bbd1047628078e8d4ebe8bd410171895c093aa64bc32ab60f306620bac0037f6f4ddd5e57287efccbfb4b73368c66b29aa9f3c2ce6a23cc4e16b27c12d04546f268678c65a8b98e4c0c9924530cebe4a2be0785b8b26b195bf8eb587b3d1a75b4e2284d345f3beaba0205ecd18de031f23fadd14566fb3880964a76236383686bf79741263dc403aae5c7ebfce0ddb24a7937d126cb8c7d7af0a3b5779bff27932a1839f338fde9abf166a655767d7fe09c3052a091f64527223a80770943fd9f8c8d5d37252ec92b476ee0e253718a6d0fe12632389d9bb7ebcee5dd8d48c3a065eb7dd66cb179340557fefd30be83dca68b496bbd60b750e09061189082883a065d5f272e7a54baafaef2e91b572ec34ffa832c84b2ac7e103c9850e88667e8126dea272bbdf715c5e3b8c2e3815b3eb159d88ed231a86e468a4e53abc8a852d4fe5791a3b929f54aea556526113a8160fb283817d42fe2301bbd95b5e078c0f06eea838e8e42125510ca34e359855ad4f0b70c699e4345c88b1b1b32081a19084f44c2e4e5b1cea7a3a86a8b973b31613d59e3d08fa5f557430e3a3deaea6321a303a4291b4e94840aec6b739ea0182dcd79d20b6c2847b9de5457be6cf27546047737227731cadbe97881cff8e2bd2c645cceff6ed224784fe0fc38fffb38633649d0776f6c538c4c3609c4c8050b7098f5497aa0fd74bc44f29fddbce691da2830272cf6be68d619d731038843b941ae4e8ac824fffe7b009624c01e91ae168b6866c32f19fe4de28f8cb91621a98d88de27a19bc0861b64f19f0c2008bed1508f363a28cb2246438a229738488726aa741650ae5f6cd89a60417fe9e90df90f323728ccfdd93b801f63566970e13f7eff2d9a26ec405751f1de8c6ced4264246b83e705009a8044328bad34ce7a9d42e2434ee911f9734a5074a540d3b6f42264b72f7ed960f6f43f6c19658ebe03789eb0b9f41fd7fb6a3698195cb0bb5077b8433fcad65e89b1dbb5f26351b428b30b266d3fed626b0ab85ea47f35d0d2927cf23d996a62bb73c26437244d05d242f6b356066580cff9b61fb51aa38d8a65a9172c7c3d1aada854b979fd815c89eab64ff8fac0c14e0335fc16f9f7fe6b50d3b2fe878c94fa99976ab91c32d4a5e04864dbd02f3239201fea366578631345ca97252d69980685f26ba353022cb7f974b0ffbcfcaded85087059afa25f858fa3d7257aff20e3462889b07ea2719381b4c788e2ba0da279e0d45cce5efddb58cb1724cfa01b30380d979d545c854294d03ab4a0c662fb2aea6bc918d2af9711b1e5eb73ff9a35033326a065a34d8b6d782f7cb1f71259a741e6e0189816af87636727ca06b08594f87fcda6e056fa106b83b72745b8df2baba38d6443e4ea3910b721d64581b3947d0d39a7974d9297a06faaa11c2cc9003e4433d0d8f0d5d00682bd508e8ec50f80c1c05a124d80778f167728b532035c493c6e800a5b0195f511a3100fb9035df0425f492583fee89d9209cf157b1b96aefab8d49fb17ff3c895c58d653047edef09efe3b7640d45da91e54a254aa081eba62cf6eaf78727913728b4e5f5af191be403e28a44f17136d04c0236f5668e97b1010eba2c45fba4272f193eefd17b93abd60fd53a769e7196dda7152979b8b6fce1303aafbaef09e7242782f88b93f188459b159ae73b7843ce61b619e2dc56a4af76989559978e5143a4cd6ace9e810ebbcef53e548854b980c973d6543119c0afaeb1d54b4a79a4abe39b3ccca7fb7d0f9fad3a3118ba9b65dd29699c3493b720a53e4d0da21ac72f37cfc9ed2ded12466355b5911fc88d69021caa126ce5f8b23417308ac3f4e72ae591dc426dd71303f9211f74d55b8795fd7cbaa27b80b46af00cd77235da9722f16ea997ae74da0e2fe7da3ed7d3c556b3d6c0ee4b8035088715ad89eba907267c22d9e74aa862bdd3cfd544004dcf5de89e845133e3484d61266126b87ae417d8e6b9c220e920381df595c97e02430b34a39d7f39ce349134c74560eae817250e368eaa2118ea065dca50bff7d03dc8dfb421c0ca07a648257ef7b7c2041727e4fe183117a1d59f297632f473b6cb357e5641e2ecfa2654a8d5e6d42e0ca1f5a204146a04a791a3e4f032081b82457ff73377c6d884f6852816bd15a33094e7ec93beb7dd6cad4baf6fca5612b52e59c3ae015d0576d5d769bcaa962bf4a72aba770040e368b55f7b3a03a2281178bc6cfcd0508cc571cf56ded412a1bf401edc324b82f35df2cf57a9bed5a023dac380e8a90cb74af4b0c96c1c48bc7f07274c34a738209a103f562cb8e542be27898a65be3e30e989a6cbe0be7e6612e725ba4288b43f3388845e803b647c6ab23acbbcd42b073e9a425db01f38fdd967232327ecc3dcfcab89673f2299a480599d7723509c1dfd8889821fa832d635e2b6a3c2b2645fea3a2547727b51bc2502870264ce772c2bb9af0e67e2b2e9f3172c61b6bc71f95e09def48d4917ab97cd2d630646fb8ee8073da4bc795b218f47209a381acd1b6c4c69c267a5495999f4a88bb956d065a119b97f0ed017dfa3e3a6fc1656ca42e300e72b6b116e87d065d1a536cae7465a0ffc1278cc690a5d97202c47d83d406510d831cfce6a66968dc2e3e33cc8109f6e4945589edf6a417386c651a18f832547f57457599de0ec5f8ad5544ae1865f1ef69ebad18ee5d980f5c0231b8999e29f02da13934b8cd23c036cf4282e3dd016f83c3039f2ea3d2725c4e81330d05f4e035bc90d9443a69ddfb99465a7c0b3a768312835c34628872d95ec630e65df3baa9cc05dc05933a4435a81f522e58ce5f81322d27bb814723d2d77cd5821f945195ea18a3e5773a2b2af3e9b3e10b15a9a575603d57d87072ed3b8f230a48e22e214d8d67b2362efe6286ad10804d546d3ee15b9ab5ad346ac515525601292b196452c193085319a577085de2335f8d4096d8b59c6c4b910b45bc4ff38dc6813457671c2fb6479bcabd4298d904901ed6dedd5db05c2e4665b994eba47a363d3e149e49deba7e2493e222aa4d42f440b42dff5bc2c6c0c84bb6b8d31bdc07d434a6dab63aec345f1232f75d9d777b7e47c125ee8528d76e7213ea7a29d85dc0bec2eece029b70ef8e164d5e0da119e2c38152c0b7178504720fabacc8d1fddfb73e86a87c1bbec882f3b96b2397a8eb214da47152e1e6561a34156a5e622fbd47287f66ce612a2d787cb9255fc3faba0a6ddbd41a678a1d6cc6463336c9e3d45ef356c0dffead5a6e54b427901e0c800f994d49d9cf8e5c72737481f95f702f443cf77746190e3e70edc0ec25c5d05a800ee4028fe631db1d3c144ab5ac7ee2aff89a23899b4c89bb0bcd12ed6ec732661d29a5ef36f300108322fee26bc7a22766c5b4064a09a17f3a97c6ba1116d0549b3d2d4002bf920687d409b4dfc156fa9b8180258172d7e2295b4daa47aa2541f75a186144c7b7723bef7e7e69a38067f34bd95415814e86982093eb10a9ed5863fda3a9232a54723d1b8b77707936eb694044f8fc887fc8e3afef6403aa20425abcacbefe636e726efd386e1f0fb1172d163ba69f5b1b721f8e0a2dbda685a8785bfa3cffe89b72f0c853c52743b08ce61aec34ce94b18ea30607ccf297e8cb564a1f279d2aad72e6e9cb6c214078c445428421319dd3cbc5906f8ec7585769f1b47368350b3a6566c2e08e78e08a55277ccb10c56c4e2b385b7296c7b0923fc94c39c872a86863fd3dc7cd1551c14514f16dcce18807af790e3a06796f08c2fd62536038cc4672e8bc922cb3b2ba383cb8bd8cfd77b096485f6df0863984a738ba12b7a40a71721fd5122704753c2f27a1fd425f6c7a03beb8897f9340e58ba8e84a4781867c7245d7bf4bcac5d3882df6ab276a774f17809ef79245f8a346d94f7b10810aa6504a7aaaacea7f7315c047520656f099e461ad340d96b4c8d786d9d01179aa18724e2bec545b20d7311921d3098c0c4b8d49feca1511370e0a197da84fa436ef3655f73fcc884888662ba0bf467ebcbbfd9e5fa17f9315d5982575792f9d1d2972b373284cc887442611021182ac3673dbfa10474530a68754c2651a58e7200f7205081cc61291298f46a7f8ab5aab6d3c49254b67592c11c73bbe192a7ab36a4878e24f3301d9380ec34b6239f29bc5ea1880ff30a5dff68bbfcca13ad590087206d58c58ce7160503ef89ad94e67f72ea4c81a2873571fe53c013a3a22c7237270ec9b5a224bcca3cd3fb6c9c32e14847b68e3c2eea2b505ae69da39a213ff72dbac6b3942caddd9f027d89a339cef5d7311c68822f1315fc58841d05e03f821f17fba3538c4396936eb0ff8ff59c14e47cbee3cb9215dc4d495e4f08a6b1772f13f7e336aa619f7588ec13b5e0b071a1c5b8799aad085f0e6c909e9eeb21172bf7c02d7efd7032e3e0e8e435b8ecffb5e289d91629bfc1ad9d56fa07646ee48131d39cdbbd30c9241c3a38410ef2bfbdcc4cf2fb77786f3fef4e4e3abb0d7034432afa294b5210b0cd374f4b4f9d39c8061e2337a944d74f9571ec0e90b9d55d9963b5e4605fcdc778287c6da003a2bc15c518eb7261856fe6f911bd06c3e448a0a38e3c09be87bd6e8cdb5791911a161281942b58889c35f6bfe0c3de37072671d7bd48b4174e1dcd90561f518a5cee6378a330e3480e1d2cbdaa5f36d392878ae18f44e957cf63fd9854a80866d45091a0f64e080b07b2ab208353e58cc2cf1a97f99c4ad6234554539954091a73d2dc459d6eb76545ed680db0f19f7ff4f9b2c99da14b3ebf477f3eb23b946caa29a6d6eff649be548a8cc82c6980a3630a0d3c8f19512c7e07fb59ef367502bd055e2bc50ca04462e6878426c33ed8772c5395847157f5ab33828a5b0ec0904be4f069a550aaf51b4c2da7fb10330bd723f7b58cb0af911080f165c4b5409a12af6fcc5e95ebbfb18858674639c2bb05cfd60c620443148e9dcb0559795834979d4b5f9f6c3ed27d9ee210147ffea1b6cba0fb038600617fedbcf677d1b402ad00c2117ecf07d6597a07f8f6bf736657232c68943741ce4310981bec4794fb73fd570fd82edd837711dfdd54635b376191e4a672bebcfff1fe03c290a5973a3c99b1d0a1fe456c3717f359d5ce4dea37259f77217b20037ccdf73fcdb2ff083865ae83f77ccbec9c0c5733d3a76f8bd5b2b53ffa479dc5afbbf3e352116510af744c0a0538f86ce658f9636ab4bfff372989a4bbc75987d119fbf5561359642e998283c2e91d61e7ea9a5ad91185f517236fad26a36fc5c63c53b598d1ca170625eba1e123bc79cc1f56d14feb59f147294814eedbb3ead4f97312e9f5e5cb401875d5aef46ee607c2a945f573cf818725abb34e21294d344c3719ebf92f79dd7225c85789a282c7a27a96a93c306930c13698938edc6f32d3bc6e0068767fffa188d67afddd5123bfa92dd809997e072615a71b80df676cb279b2141556f20fcdddc29e9e1c921b3577306988d26a872cfe6d2a483709259c32e30b7cffa4f1953d516e846bb1e38973ee70403e8f87231782550b4f65abc0359b9ca234f0eaecb6d9388cbfa4936b57a987ca116c91c5bc6825b36ee47a4d87ae56645950f3a7ff404fdf47d8b7c97d670db9a449c729d09725bac399deca8a044c5db7f524c5f3944bf58e6828244ab95bd3f40197226c90b59c92794c16dcab80f3a1a6dad3a9c61685c80287be6a0076ba2e7ee726b0f9d1c951fbba12c5aaef0f2d5431acdaf2ce6566022461837af1f0532aa72de9260d8843982b8446b5e08af643fa7b048639187230b7f38234eca7538532b5bb3cc4cd8983462910212559bc6c48dbfc50dce3b17ebed03667d6782f74072a3dfb7e28257366ce80c3b532c0321efc94e01e8e841199fd025bda8bdd1957231a8132a9a7e87b406c95947305724aec4024e4897bcd4581b44eee46144b100ba3cb1fc0b3e3e22b7e5670a4e540d72128357e073713f76c7e05d96ac89a667adc1ee2e897f19c451e4681474b10d8ec6386be5ca911852020f8b1f3efa846334f5d58b2a470d96d3a2122ee8f38cf7f7988e362d3651e7350087131acbcd72c32c14930185107a5e709bd68b786500abd0e0a3eed8dcd40c7717ae56232434b4b858452ee374675b26a805d293fa23ea3acc19c51c53b7b57e9af9fda94e72288e42f2b50b9363b2f469668bf260b120b7d8647fdfef6ea10ab2731a2ead722ef6b2333f1f1bc527e596bef4831c698e9f8c9b4a97be31011eab907b97056632aec76ff691862c1255c227c9f432e797b9afee6b8d6513448cbdae53e477668a3f63e73597bd5a527c13430e26c225487501273bdc9ab318588565350f1251ac25c3f70d3bceb77a768ee70a4349edf288e3af1ad4bf0b4d3848e1335d4772cf0f122f417308e8a32e8cffc1cfc3cab807af2cbd58b66e70f642e803e60f72320305ae0345754a67fbc88a1c974d2f5eeb6f51e5d36900c3a019ecd0c08b7268b415e18ba8f473814b5f59c0690a93ffabd2e008fa3eb7349a00ade9dd2c72b2e174c46b559bd54f6b461332b0bc294222f6150529b787bbe99bb6d877422ae1f58f1ef11b366cf6200a24dd0a9461a90d2f954e6e3877058fa8593331b9726f3d2a2bea1f8f73026bd0be5dcd3a673d024c2b4774ccce7aad188fcb3e991fcf7a47d60953416e55ce99931b1ae1fe87f963bb8bc7020711118662fc35e472b2c6cc9343d1e5557784482d54a3668678b1b52da1defd73653fea31579d5f72f5acf7f3e98e2e1f8216c831b65dc8927d977a38d79979d23cec033a3466c3727b8d02532cde4adf52c3b60c2653f6c0668578dd85accd72bb765dd6b63a1e7293672b28749aadce07c95a09742dcf06b74db11f62c4b9cc8f5c11b084b3f172e648fca7310ddce4f6cbc9ab0339f30a14b660f57684e0ea984279676182ae72e1eeed25c6a7bf5c1afe9a92cffc338c492d2a95bcd43e98e8920b583fde464200d08c731bf1a13720424c79bcd5c05bcf19dd62e064bc74e4f1184bfbd37172c0f44e9e8751f1b6f273208d1a41d6ab5d347e9c97c7e830cf25b0987f941a727a6e42e475f5ee6efd827ea2c3ec5a00b54fd020be798feb5e6c1f4f1ad3c0354372d8e18875adebff26f0d1675a8bf9bdef47970dea9bdbe47393e8a718b62033c295ff1ad3139b26d691d7f1c4df229a06006e06e6d83d3ff7c25253b78027b05107b68ccfab000b33715a73b43c76199355ade5c6b3003a829a61070cd5727ea26d2c40577c3a8064c8757cdb1c979e45b1d57b1d0422448503bf26807521822ba20b932f2bb1647fd98607cd22f9de276b6fd4a3e2f3e1c72427d44e58727bbf95606d52bb2b7dd1d7e7025a4251ee740761934053da291923d291b3683e019e465ba3db64943d904f860aaa07b007601a4c34bcc2e35b8c0f0b42a8122906b6af4a8eee31b94d869b936ecf7c528193e949029df7df40b3ba870fd90072599592382b6025480af386a263c297e6f816f5ceaf51e96094a9b796c93c1672a77be68ef9ff80ea2c2c5b69da1d46afc672b7e81fbeb60a2594fb57c3df9572ce20fd162c05fa6141ebb474e5585b464e5a1920d99672d2532ee3a9eb8538170530078b661df3e668e19ecf5990aed69a39e4ebefbcf745236c1b098794c772e2b494eb907b938c7bb049bcccdd9945492677101633cc111dcb563b7f798126e542f3d8ecee615fc5aee4ae3360d4225c0f5f7db7ae107e29206039a1243b72b9ece8f3ce70b0d92e7206c1eb5b6677d8df26471fc4dbf26a42bbe203662872bed0e7be9df063d1f0cccdf786a07636109a835d967d8a11af015a458d285c7290b0e4508b62a57cbadfdda6d3a8550e0b57b9d06fcb270f95c56ca55f10060438ceddeb704dfa4828bc950ae7769e07a10bd01893491bb10ff9b9939971090999715b2eaad3b844de385c89964ae31ef261e19603718d451001c7374043c572700cfeeaedb3a3c5d124d5d509954bf83660224f119268e3307481ebee1ed4728267a50641616a8a2695032126ce13b0736dc49cc6d1bbd6c142e2cfa93e980cc407abede5cc13b518d145a25a88b1db5abeb4c4fa2065a28ec00c8a348860139e6162267f83bbad3fd8aacbabdaad95c1ac5338865f1d29734e37bacd98397216eb5442238566ce8d5c6043547a4104bef86096272372c5f048754f67c2982b9303becc86d56a48dd04ff41d795da7ca83e7c1c52573544e773a26aff8a8c72575e479d020d8bccbca4ba058638506488acd41e921bf8effef7d1bc2070b672ce58e812041fdc369456bd2b657c1f8c78daf76da08029f61572eb467bbcd1728a977ee83c290e200b412713d8b52a6121a98a5a5e33690c628e05e2311ddc3ed3f800afa1f82ad29ae1979f00b4a1f56f4286381f4e3b5563537a28bb6b29413b57468ca5743af3b858cbde929ce0a8d5212dbeae7c9c5563d878905d535261521dad5e5de4f5bfc3bbc49f6deb4fef77fe0d0260caf6320300ae0762e9c57228345a520c9f723b2f2bb19aaf6efc3851563ddb25ffa8eff825eb958124bc72c2e61e8d90af7cc561fd4ebae9e0c3ea89526c762d1a62b0129258e451b8073d2619913de1db864d8592682e9fb4b38e7ce0d15e1885c901337c8586dc6d721aad549cdc271ab3d4aeeac447522967ca4471bed38691919822416c890baf5f72170575a64ce23dbf7284a369398acb85b3581e9e8893e0c981310163c01297388199e0b89053a457e3f3e68d7fabee5ac12300a28a812771b33b201830ff76444ebd062e254ebd6bd80bfcd9da4c0327d6d72d465ce6e86017217b12f23b9c252d5077965294f5171986af3ec3833a1046b7a04a5e77e5fad037a0f6a6fae672d7e8edde9718656e4eddc1c426a12a281e1b2a465d5664afe08445dd7e1b4631a2483541d98bcc3074f4402e3f2a53053a47409d8466143ef65997fd5c2aee72ae76121ef58be8d163de335e0586c98dc5c6f6d9ee6c3c21acc8857538a4e172a16de39ceb1c4e7c5a334090b6a882e860cbb0a3103742f61961c38465807272b2b116fa375544716b54234a0918d6dd9d57c88b51e0c82b44ffd9932b494c727fa002c523b2bc343f2e7f05ba80c6614b0850b9026794b9b8ebfacc0367d47204478e6ab29f67e38694ab3bd063ba0f3f350324d2dfbb415dcf4d3477da2c46d61e0782636c11e102a88bfd159331577bb4ad44880f1bf0042630888b1d000ec2437da63e152dd82fd71dba238c0353123bbb29d047e8808a9b967e4ab21f729689175268d9e0d65e4c5d251364cc2d281d4f1255671ad3b25a1aff1270ab729af681277c9afca06825d72e025d36a46d801cabd69963f635932e4fa1e6933e05aeb71ad758f8fd31081144aed48c21e4eb731a66757bc8d5f6f6af429bb26479d32058337f8faf5ca553a3921cb1e8d4ef0513183910db9467408b4a6a8a540afdcfddb67ed8e4c29c6a53344845dce5c6af3383c86779b02a63cfaaece9727b9eb899f06f1195cd22779a2a5e8042248f0a6e0c80bf002b0d3e2177882328a0b62161e1f451e7ce209004997ac6f4b55d90e2743dc9a8a52de698f1da5e724876367d78a73275c3b06682c93aa15ce012ecbba13832c080e657c79251d745f05d5b7be0278af4515d32cad1e9ac96a464d5ef96859897a734e5be923b9849494cac5eece5663db94a08aec1524356b5fc5117e136c44dfb8574093151cb72cd0cf97aaba6ddd4b0cc6ea81387578da9d59dc4f79f4d9df52d9be6475cf362a73e04f3092662984e87f2902ebf5ac400d015d1713bcaa9c7c6fc65c4509972303b5a90b030e2b6cdedf5188cda491720f15286344b2873a37ce30c542a5b44b7f6ca8665880792b3ea3cc9d53e8c7f4e4ed46f8e15ce1cd288e835a9f3aa72d3d2b0a2c458dc4f45a685ce09a1ce2b86654467a604aa1d92e3521ae3b4ca72cd9590eea6a430dac83eb6ea9f9037306b079ee577064f65ed30c788de9e36726e60d67ae1e1eded3c39a305dc0fefced85840b52135ba052cbb0969a6bcf372b4fd63ff288edfd587725e3aecf7e7ea675f266cc00c7ab74c9592abbc0cbc2f9c8d718fe1dac71fff8494eec3f35209b280c296832842a32932514c534008728ddb5cb283109d61d7df1193a80c25e1f824794e80ac844fbb0a5030da782672952abafd4fce7dac7f4d6a5dda6727d0edc965aec23b1a1e316122597a07db59ab84bde0fd8ff7bdad2f77ec20323d89442113aedc109d3cf847b8441ccc6d72a66b50c7ed36da8441eaaa5b512eb5cc311ed58b8c2930fc35569cfb5a5178724a2797db1e32704003b9b910395fefda8deda1847739ca2c29b382143da40326fe6c9b874621a494794dcc173732c1ceb47d5c775d962044e22566a8b8f5b672025d1a8226d95aaa8c0dd0d6d480c22a52a2afac9dccd4fe80d73a42520a84420f1ec0c528de8a9b4da15be235ea5c4a4dfd141ccc0bc23f29156e106a65e167ed3012f883350ed293afcb5e0772dec2a15f5a5a08128fc5514238ae2f26765941867580370e4e2387cdaecdc16dca43ef11181223644bb45e67cf1e638cb872912e4e456bd511627f947e8c0208b3daf04b72a2c5923b24b469a34333b7235999943e7241fcc3262c10c898adefe295ae1c445d997139d607337ac2a76a8e72fe5ae56b92f9334107d601cec504891af6ad2791f68cf41bde2f1310d0fa03640919b7e3aeda788dcab424d0efb12bcfc9b8649a1433ba31862d907b748ec7724949e7af135ca0ea5e7a27bd71fc7b99af8230a284dc1aafb15b6da044cdb802bbfe6147b46ac0ba61dad4e9a1ed7a48a72226780fc764b5986605b309400b72d5899d865257848e6063b88af03ad0a49cb8e8357827609347dfb63ef3cfed6bf3dbebe93af0e5efb14bbc9e64136fdbc224cb126d46454a05315f32952df3362af99c7687a0f4814cca98733943ebe2494f3bb5755f81689a0fdbf3eb3386725bd6535e6996b658d8b642b9b1d2fd0834059696268b8ca37d448b19e769f07213f635338310d879952694b2c2380070aa69019f46351e246f636a2449ce6645ce27308248c05a99435691309d51654af660022c9e972b2ebc5e1d6df6c87f7275b8e4740d0cd438af4f69200c3b43110107dbca378bedc51faf16dc254c2c0155d8808d12f569d456d82e81d4a6a0fcf534a0216926bd3b9a5ce7ef733e081cd6a0106e58be3d25f2b00f94d2af42a3a47f9a43802d2ff0299bb5b4d49130721dc0120d443f1f5a33d95f8937bcf4077b658b288ab94166d9ab6a02b051457221c12f26c2302003635b15acfbe29e640cf3871945fa880137f1f61cca6939723424f4f5e66d90cc3b0871fcff9ec502febb9e7fa8ce1070c9738351ab085972579db8d05578905f6b33e99d8ba0c44d8612789c0b78c52fe4eb517a704c6a06efd699688a36a1b1213ce6904e436fdd777f9e0b950c3822fe2de75ce547dd0616a1f966b7ddc04e8c8db249e2ac2a5af945f2bef16d317653080e818a5ca8727f88ca3c893d74283f1913bf73540472095d2fe8c3199c4cbba2118e68518a3430435f97016129ca0b02b7759b410a537c1246f41fd5268aa1e75d97931b0672ec704845f938aae3eb2524188d4741a34bb1dba23db9528c55dd01ec8bf6146198c710afdb61adf315e5ca70cc13675cc9c286e759628e21b1a7f98a45789721a5e048b5f27ebc4ab1cef13427bedd5c066b2042c35356c30d14be330f4c2d72f3b10aefe00f6ebcc14621a5f41f6865b31e63a7dd4c20de31db0e91e2eb0072bc3f3f4934204a423a47af1d8b38a24214a135f79ec1a22c9034840ab5e0bc174c21f724233ce535f199135552f6bae0800deb0eb7603218106b40884b373c6662027e6668efc40d688bc03126fd9de538923732090d4b5724a30ce1b07f051bb30888dc101e7c08f3fca42a1cf29fd1eaded2f1063d7a2993c6b4f4b8587b72ac2aca4f74f97035d4ffb5074a8449d66eaf492904c52a2a52c248442b6b035ed720ca554e860ce1812a62e43613ddeeca875cbeeea2cd0cfe75d05b3226cf7287afc183bdd5618c7dcd7e2c95004b5ed257c239322f033f8d5b7ba85d79b127d6d5e586db7f7c229c2257bb319923b1b4dc647da988eb472fa7da5c170b54721cdf45c1869434bbff1f36c9021f47e5d4d8dd72419b89109787e4587c2ec55b2209ed95d2e5c633ec1d32209b87ea38f32e35144ef8b380b0bd72daf21fac72daee4c8d716f4c22d440f04e1e1eaa6dd0f9ee957f8caee88edbef695ea54f72bc7e05e149a7a03ccd7b86e3891e7c3c0cb99ab4ed5641aef5896c57e524e868583fcb6db116806ac922ff46842c4e8a45aeb9b26f45903cd1802ee0abfa8763bf43a85a90d17082ba12bf286d9aab5bb688faac53c68b0565d9c3b700ae9272f94c39ba1c458f60e01ca5952b6714f36d10e9df473028957cb9949f68dc5972f7365afdc7c279c451a4cb3cd46c765457a9fa879206da39dda6dc841b450872d880dae20fe60c3abb7912ad9de525f46569abc13cd7de7359cf9ee19a789a5e5bb7f633e69bb7b60abff51f4e55e8e16d7cfec560624a660eb2223b38c004727cbd44322fee98b407844f561f1e41cb55711674e002ef031d3d1564de70d272afddaa9aed86b617e471afd3a5b814eea98b9ab7c698f2a472c7b4b905b4b872fe85e67c01a89b2853065ea06ccda1550821e671046a03d9e4efb5dac18bca0fa7a8f8573dc0e6bc01447aa2dbd693569af1cb6acb32e0a0459e29de2eb2f2729cf78ccdda5e74fe061062c02709fa706baa42a9fe3bd3515f4eca797834c5497097f2a50098d677c617329d42971a304d06cdfb44f6c1d184526524e83d8572fd60a62f6636f5da9e37b804b8183a8062252345acfbd7b83dd6057bc1865572cd4d55cd26344828bdac856679403b0a799266d07d7db3de11c05103eccbba6c05657580e4b8e0e8c561ba9a9a813bad2e34005cf69b4a3e814d4e001ee7c03dd6b5fb64ac669d2e741da47a1a5a7e9361965b7b292130182dfdc83acd639c3383a986127a67f2e9e53b19298d995afa37af8e81de1ba8a226f82606c96464729fb0ea9df7baf6ecf76770e36b203179a967ade6e50732bb7e9b3da033a0ef72c1ef0cda87475ffa3a67b4883af0feecd29512448481996af210e5f0db9bf272e597af9dd8abd42d5878859889da4e9d511a7fe9abb3edf3f5d8e72593d33872d2cfe515b4b2e372b2406a55ffa444fb0c6e1dac27135e541ad7ef0751433a72a9f37e0ae54f9d1d79c5bfa3f408679fbd20d023c8f90f242d0b8ccc6ff99d466df1c216e5326e9c8b03ebc348b4d7050421edb69013df412c75c9a84f43a372e0170ee840c1518ff355123dece58b8b8d175660ba7491748cc78f12342d8e72915c49632fe83b68ba2d205a56315ff5d51b0d5126f735b8f1612383c02176722c9faa4c72bdb6694db1aeccd5b11b2bd8f339deea6151c825541e551044c072166d9c049fcf29619d0072b793b653aa49488c37bab7a939e442d4ba6a82be6818d4eff88b09688110ebb1711516eb58adf4ef1432834b08933ac6f81da43572573fa4d61bf0f9617df43947584ea663ab3d1b2d13361dbaa2b5cab5f02ab272292af7f72174e32bcf78b75ae5dc4a8d379a348742800e2f6fdeaa2cbea67c72332b87e800a49f8b68cfbbc6273fc7f0d3c39f12527d6744cbbbde7f4f7dcf72c1d8b3857c9cd76465b44eb4dd503f96bfebc8fbb5318b990d47fa39a047c97202b9e1bd083acf96854a12f7bde0f74c195375f1ecb3f41a1fa720081ba0e472f8073287258ac1cb415d8ee43e5dc51267208e6f7b015b384851b1a2c13078720a54c5e8e8eab959183e694444f53bed02973ea7fe5a39c347aee0b21f081b72c97c57a8c6eab7a029b726cbf8a7e899e420d8adbbc00211bf808a6e09c42a721123de60b5319dbcfa1272ec76b65306113983b7623f27aefeeea6704f1c3f54d56de07dcf13ea52f05721ed440698c69bfd22742721238598c8d6d36df65972847c36238682b99efdbbdb88e5a6220ddf7ad6b1a4f0651b3bbd84f9fe6116682151ecc6a657d81ba9a1fdff79059aae4ca08e36756230d52ae1be0ef2030472750bf90e3c2f9a9a2fa2293a08838c748460fffe70fedb263a4bcc729c8a3c721ece12642805ba51a8550e6ebd63f8b373534e218ed38ed9d0a836c8034dca7212640b097ad936656f182652c5cde87c0cab0ab6fb442ea8d9385a642cd63e72a9ef59946a63e75de5c97a4eebc13d73605fb6564f8d834a18958d752cf3ed724e3c97fc44f0e8db9c76b9ce2884249ea5155dfde355a9791ec449648c41da2ca275cad5770ce47fd7ca2429d02b5adb4a533c4061d3cfa4065c78716ddf2b599df24f44c6cba50ee05f5c3068fc4c2d34a041ed63dc271be7226ec4acda7a3c8d686e74a12031c7bc1a289602945a79c29319d5869608eaf64b05685f24892e0b072bd19848867179b5aa1a295ded3a8fe379db79e94be1aeb39c8a01328e7243787a5e65764a7320a07bb41cc8ef3a03ca398b8d0f422c3dbf9b8fdbb80c7222fdb26194e7375fdc6e8cf1a6a461c7f18a58adf22b781985f1faf41ed4273927606712c54207bde86c8406b0358391e486735c5e20e8f20863c5b26b8fed6644d493d90d2bf00d5a7df34e4b941841533dcaaa1d757da4b7e7413eb6c90172778961df0845683e666a12a5f46449123b14f53c7529d256b093548a4695c047b4d8da1742ec9067b9bf7047543ed0fd7bc1124ba429aaf7b51f9c6999074572d24d1a244e7f1f547a70d5cde8193d35cb8ecdfc49ae26a7429ef13da9464021f0e41177473ac963b4d0acd7016b618b73bb8684064013caed1d169a4d480172f2763fe2fe2ea43df437d854a186ac7d08fb0e2d21caaa025dc3f194a243d255188f8af48767a3a860ad809cc7557fe7bd574af4359e1f3b3eac2542fb9e9f42a05a066a958f2b5eaf7b0f55b09b3289e7b73822611ef37bd30f253fe2ac997259f7bc9d65ff8bf8586762b9ef8fa059bc5b42034b8b453117fbed839bfd25659ba9c82ef9afde91c7f8ca4dcff5d156ea69c988014bb2ef593eaa2c5d45261ad3cf86ab9de86d8e38d90e5a82476b8ca355439fed7347e4d565f7eb935e4f4351ff7f5e306f0ed0d991bac3677ba2d594d4aea845be4303aa9387ecd5a7af729276c3b5611c75e4900727a2f6c09bc15e286460a0265c22f07ea70a7e0baa650028ae32224668510f5a259584e87e9664a20fe71aecd9842415a1413fa8d6724f47c128f674626aa79536e44d282904bacd5fdc50954d178da0ef2f702b8724291ea03cb9a8eb0e5ac37310c35ffbd3fb6114033dbcbbc3717106473a54eb721f70550aca613ec2a766ee070c64bad54b7064a65f5c3c1c684f40cd557e9772534fefd643231ed2b844a0b396e4ea25572fc4ddab833b11288e4dadd7e09b10885927896677434550400d24a1a589f90a4548e802b41d906b3f22a7dc1be9721a9341891169b99951a3368a6c108af3901f0cd07b4ba0e398eb2512333a3d72d3d11108fa4385b178c1df48981a1aee38e27b5b72c3571ede550cc89c7fb3721e16f0444e5afe578b1944abbc5de22b52cb57622b58cdacda74f6547688ea72b0c71088b4a3092467b3c5d1df1345601fa34b84cbdd48e1712536dd9fa3530eb5c1a8ed14ca8ec3bd9c5972b39552af46cf9c01c2ae2d7dd8a71b2c30f26f72b85f16d1d41a83fd7fe7f937789a370d64a057988dfdd57d9144b326abe7837252454a098200e26ab855270366600c8c35e602b0519438bc71f9a0ec1bdbf0727def2ed87debb2b7b33f7348521e82c5bdab2aa66596b0b47c4dc091a5518b72546ff3f93b31b70ed5ecad8adac1610789a804150b8a30a32f7bea7b80c85468eca2ff7cb5298afa48ef38983b6d383c0cad2b4abaf7b0a633b930aa86334172b766c8400e48bbb35764e6ece6eaee1053606538495e2ec063f9aa18e9c58d7271da499ff87ddb701b395fb1de999a0f287785d48a6ce179fe9b7329b7332772b13a86e76dbb3b7c09353feb38d78c1d18b5f069e6b831b7caf51b9548361204019ef2b70826d59a42c1753933f909895fa999c9df9841b2d266ce0fdccca87221bd81ff1550043dbdb6a9b91a0e55ca72a287ee94f1c6e2467636bc149e0c23650811eeaa14b61473008a79c3bf0a89fd50caffedb27df80f99f91314b28972310e7d5099e7715ab9c12907f2ffa00edf63111e166f1833c8bc25c6a007fe72e09ab079051105468b267e385be955328f508212382380718ab19927f3915072636984ee006592bd2d010343815544c7aa40e37bdc9cebeda781c85e7e7d5d72bf51b71a453e417bbd1d2c1209910dbc6534761e5c1fcb08e08207d6f39d5c3d55443124bb31723766938e8a3a7c6905ed5344913a42ba2c450d8c00f85f75720fce58e849864aeed867bdc97c1d49fa68c411d3cf8f5a0bfcf22c7dbdf5d17242863634630153100332f3c673812aed056e59a66f86d31d8b82342892c9e604bc2a7520268e889d8ae26af7c01fa118d4e67a61bc26a1d988b45a108ebe6f72ab0e5b14808012d86a63722c662c7056ccea7e63d1af35597409185d18728453681c887aba1337b529fe035361b281ce653bca0eaee1efcf77e29cd4ecf8e2724c3ef1a378ec7c361199547e7833ec73efdce6a29f6ff4a33e93053209ed3c68f0e03331f9a3666b94fe7656081f4e0c0d51cb97c3e867b96bd0079fdb2bb77260ea57ae9a8d4a95a16150d064de4fdbb9009a808e1e42afa25fd1ae74706e7293b0d613eb281ffcd715a38670e2b1a0c9538f2e83665b7a2a7bf3be82425b72c1cbf4a15769167395b87036fba47961a2bca6e042a638545113c4abfe3d3b72a59bd91bbc4b0ba79dd995253dbcb6eb311516c5514d10cf20f6514f1ab206720d776efe0bb42fbc2f59902b51dc7c8e113b56e221b911ea85812920b0ae13043d0459491ceed47f6dd016b3591d9377d0a151c264707f80e984962804ffd87241ac77445a4870086e69d727584d1f0a0bbd0b0c6c5ffaf469535335ac3b22724c04f8bd3dfd14e500819c859757808e7ad410a348558687c8e2b9952fa62146e6dd0444f2e84812014e5735219720123facc3e4f4f10d58637d15d120ca4b723d6de874fd1aabefacacdc0d00794581fa38f4d678740b370abe1b2df4f7cd2ace997fb307335281ef737129d74881d00fed5abbe8d17be4f108a6e30592e672a3d64c71031b81842c01a99de0f70fed953d4634ebd45cfe123e97f81ea799722ca738d4ccd35471eb66c4925b7f90fc0879ea5120d7c8be3481cfa8bb297649c978b053e1b5bec8df9829c2753bb348ca7a3e3cdbba795bad14c0879582db2740c196a3349d9c809c96033200070847ecb7325dea5e0297641424264dda1170b4d0c7fb7c6564ad593619f8ccf27575524010ebc3b3314b8fbe34ec7a23d731cfdabbac954a9b37e8d8902bd686d02422344bca4e7d4fff0c8dea3e0cd4270ed0a31a68bd97103cf6060fd31c5295d8d86dfa6806eeebaa06b7bcf058937d363e3771bdead6b6fdf5b2d8fcc7a8457007e6c377111aad4f15acd2a6c2cf0f72361ede742dba458013e32e20826c7fa06e8e8838afac64b71a908db179e7271dd1fd04ff62c6138b54901d99c5a6b4bb393f0fc6f5edd3d9e34dceea69a3b9342b16ff30b5eaa53de0876f051544208da688ceba8647feace257983384b72172c9dd682afccaa9a7d452e19e10792dedf5627a92e4aa9f9c102052ad4c4f4f72d15e997dd11dd19fa75a3831fbd3c6cbdc2e2018af5f2f330453ebdee54ade7252605271fbae4e6b950d5738bd89bb3215574a7b5a9c2df5a80abd4b999a3312b1193b31c1275bc59015528a063578e2b97becfd61014907cea7095ac1eeb972e4f5e91a755a2006611891ebac7c8f218b2755d31585eed99df3a72e9ba01c72d54d829809f4016dc0646f8aea2e86fc2f9e3e4166c572b3fc144303d29ab9720f8e50ffecb7cb1ca19dc98c3853d0baced2ab87cf7760b0819f06446f787e6b811fce4286628427766b1d6ebc0b57e8d3f77e6b9c3f1e75c1f5854651608072c09df50f855ad02aa65e2212b50995409ac6513aa50f1f5cee005229436aff728881c2cf0749d7c514f7254a18be09b308c21e11d9da0aa2a1eae8b5ad3bec6bba930601492253e5ea2695966a3c815b0837df52392e6c9ea2f6f51720efcf722d77683ef2e77083acdc1e38f271c357d1e40171517aad6cff442650d1b6287274fa00f29eb62f53700e4f25d0ffc8b81cbc599a7471e85e2b6d56c476b0c50f767c89f1558c6fb0349b2cbac2f4fa62f077f857080e4c0907c73f0d5070797226bdd7cd9f9de97ab3fff5780dc31877e867045537462d94f022840d53053f72657df8b454ed08b2d2713a85483704d15535bbedb246d3be75c017f382c6c272bfa441d094748541f797176cc7dc89cf916c8db5656574451d13193067d5df722725c8fdb47854930f3805e95ecc9d2250538d279f704b34b608df1d5d753a72433bfebaa123ac8f16a4393c73ef43cb620eb1b7763647ba5ad3fc30eed8b472555f17aca172a2ae6c093b2fac9edb17f8004f591b75a781cb7b3091ae3ad46188dc94a2593f8f757c663ebf3fc208b855fa3b03a61cdcbd60821c94d55938725bce6d69b6b0b385c17d810d08b10cf8a10179c5c0339196a604728ab574975c6eb7e929bf781e64a5dbfdff50f8a10e2b4d3de94d9e02b997b34b9f7455f5725e949221668976be2b3caea3377df0e387049f410bbefe4775e99cb76480cd01324a039255ac2802995569f85264ccb9b8adee6306e0d9ed9bda970c393c9e726d052b6c1b8e67762f821be9011594b7b9dc58e4dae131af1bffee9aaaaaab72f88027331f0c6c55914dad0b74bc5dd5cedb617ff6e72c4514740c2c06d8da34b983f18883b8b5758c29aae507be40eafdecf7f01f578a7ea8da11a06dc26148b3ba62371c4425f650952eb2be7c4b968510854a8e124a6303e082fa5b45200ed04664f126dc6368850ad64c0bb9b946230d8706eeb207bc313c522661a9e03e7bdd164c78b9ed16763f6b99e92c426b118076ba47b86cc736ac7e8212f1584bbd2bbe6e739d84170db1d8ae499a343293432753db32c95609995868d74ea82d52089cf847d8f9a8bffa7a1cee64a0f47a67453a56027ea405908b755a13da72b88ce3e0604ec1cd0ba1b8f9935770d844419f1d5d96a9bc0d193baaf3f0406c9363f314a82c5072b85c151f3eec46c6f61e0d924501c1413b8aa60497ff027212fdda4698007ecb55ab60c556ed894224a52dd2b91ce49994744730f0fce2353ce23d3c0c0ce02b71f880cf1b02bc7e4374b80fd17f3dc28f604bae82e18a720398866e36e824b5e4e6d5e814687e0b12ae4b2efbdb33fe33cc9b678e4ca172de61c9614490c4e006d1c50ee1cd65213268ca87085f5b45cad23254d4f71c7240bd89d5703b78c44338d71ed889d04cb054c28537cb0ce6f8b2e540b042e2728eff4eaaf5168285dc21f5624c4100e7b5eb09f82da51226dad21cc661b77272945696d32600db42e960149e4d248066be6756e027b6d47463a61c5cab548f72b0e38710f0dd38da21a9a49b2dee8bf3aad4a936f58db98971b4a6ffd594081164d4828286ca3b4b5180e211982d9aa0e7c77c1f872ae36d06ef10fac069ea72e1401d4836c3c38c47ddfa8d783b7ff1209ca63ecff8ce3325f2131596e9bf1aa9bc52fa76fb6aab0dda9fd705bcd2eb9bec9dd2e49e43427c9019fa5c3ffc72624b6307f7dda1db394987ef2f62cb0cd3a2f9d8d580820188f663baad115d72c82b2b5940a141d9492266ceb7e6551a3477877d4a3b1f57287b712c9418cf3051a6003de4425bf8fa5e6c42f776325874c6c56963acbac188bf7a48b9acab72a22e2a49771c386502bb6f5bc7aa72bbc6e4da87aa85aef234f7becccce13766e160f5b32cf051db1ab49e3a80c288437297e22d0aab585f5bf8e8ece54ad95922bebbd144cf99af85d787ff562851171c804228a44fe26989561324b69645726fc6f786edb48db035fe4b811974541812c929bea3e5c5c319f51f14da709372d9dd5ce75c324d616acfb06ef2d254ed7a6f3454a45cbe5f2b1eacf55be42072c748fe572c4824905dbc230621fc09c0e16bd8207334b2d4b65652e968868572d219b9a1233a382e04ab96edd4cdc28967056720e16d3da58236ed057d5dd106c43a77703bc7a85b83a53d7a9cbcef94758c0236764c0c66769ce5936d000b727ee927cdee575de129abd36125a5ede11bc2909f98767e3eca8916aaab41706b392ebf23a28dfad00de7e3a35a3d9cda8b7b2533dffedfa376d69cf1aecf76061dc916f89c5393288cbc15745695e6802b6547e375d2750fb1770e2c9470c472385f6ebdbd4c6de13b8c724a81fd6266e5c9a53b382f4005e77ab8d5f890ec554cdec1528441441422eddc0eb03f25a73e891f2c97312266707241fab289d9729bb3492b6404c5d9457d30c17f4556f8b14606027800fc0fe6f167055a0c24060b789c28e278df6623c0b0761d329881dfe76f014219825f0a20d4ed015f8959a9cf285843081b528e3ffcb9eae0d2385089a939a4ee839a78ec994d30fb1072cb6fc4afcd47b027e01af17196ea78d31f7abd5ed2df26f03f298ce2362af0084ff4b4b60c6a3bc218aaa172608f1508d820b736157f99b4b6549e9ee728eb34f8ab9615fb4ed39951bfe6a3135d7f90be7c34ad158b43b70ad8da6171125a5459b302c01be31e68033676c866e0b66c53cbbc5995ac0ee434bb525e0c4b52721a0525167a2cc37e4b0860f6e38bb8a57021c666cc69b182d556dbb46f7b8a7228db25d0524ee86d1c12d641396671e81dca2d54d70c07d620aed6c21eb218723efa80c358303b4cfc5fe01df205612a28fd3baba731384353827d7018719372aed3c224557d4d2fed3c2192a31e2f11062d0f9b0b6b1aee32a8c4bd7f8e147212ee20aef8d4a31ff7493ceb23dc6748c733aaefe7bb7b9cab744cfe858e1b517e478a985609f740edc594e720b621a546f091d2f59a3133e68d0d3bae14120709e41b45ec70fca914f46a1f5ba715f75c1f1418423553e0520c1f044ed68b72b0da2085b7ec44d1fd7aa05adcc203ba721f77fa765c087ded078d0229b7807255b7ad388afab56469f08816c13aa14a0bf89f7e46e7d49aa91f6fe68c50257277d97ce7c0f9642950e09db7efbe140ba4a385917478e48b16ad05a1e7a57e6b9be317d4bd53a3896793e38dbf3098e0bd3fe9cac2ab49e1f634f75a22ee4272781d64a09e5181cf969ca796fd0a3b1123b06f9eca24e5acbd508df0268e546f0c202b78192f1c5553428e20afbe748e09b5440f5ed6a5adaff3a6b4d311407285482a9954e3e31ca52fe88571a310139d6548baae71cd589275838d88b4c5132e7beff8bc5f07b3f57cddec52c9461249c277257b7bda112630111f1f80e31e710a8ba383711187659c7ecf963b32601bbfe86f5ac17c5690244fecce1b5072d4442bc89926623dffb0f020dd1e52f1f90b297efb2255e887d11c75efc89572581a4fde015b00c90eab60661bb347da333f5bd37472f7bef92ddf53095d305bea6377ed8c3fde6e224dabc67b1d5f5534945ad417e3929286d1f7d5a1d5187204679184ecbca22bd0750da236bfc055598c2d7fc0bdf5a4d75b364b1a30c472a78e06cc1705a113c9fc415fedd8bad0d8063281e38907dd7b0722e51760eb727ee88eb5ace269eb01708bc15fcb078460cd45529cbabe2892fde3757acdef72ce9b3a39b7fbf7c37251b6b81e70d42c3a9f9328a9c13a215b526caf713cc07265b0853f73109046cec470bb2c22a626865c7b39a392a292e8bcdd97bfe82e727b9005fb469909c6ea8a72bfbb6bda4f826af6a457748e334ac67d591bf78972739d7bbb5007f3f66601c244be440e3460f060a83139088c0cf0ccc374bc1e38dec01b20c030c44b5a737c092efe51659522dcd178381f1c7952af8798986270bba1abd68f1c4765b76f12211165adbd8966dcfbb2b64b76ba613d73edda627283e84936737e4eed58882a4ff40d60aad5d16496afb215b3fe9f2f9be7a06b7257233cdfa68e832e2741ad2a362035db32806731a58bed1baf2f631ee1e72e72b8226bddc249d08dd57290d65e37245b302003c6cee5b071a521d37114d0f5721343ccee551a4afc9e2885b4b22def27fca7aa6d77236dcc7c3a8ff1141eca4fe2943cd8b60be781da4fed126d25d799429c64b19104020df99e33bb8869737210b0cf18b0dc51531c0f27008ea4a4f789cdf4fb2fa699118c1821497a263772155f345e5f5510e26fb5644aa0197639d8e9d98a60b7eeecfc5e04132348d337eeae6559964fde935bd6d510778b50c37f59bd40b7a74adcd5e7b86ec4e587724a442f3700334bf6cc9094f50d1e784a2c8507b833e78b0b98ad9736920de37201e1a792376b9a46a4a4bed447ebba5190b3864f46731a557dba6112648ede723239fe9483e572157bca1fa643479f5eb5dfe7abe0666c75916d85bff159185de0e5c03cfe38dd743ceeb0d5f429446426fe349372485e26c657229b4ea5567293fdc94bcb6e1e5496ef0bf4374b31c227e31b421560a36aaf19c2ab2e5e095071b9dfa451d2467af3ac9a2ead26e507ba433fedee5ffb77a0c2ed702a0e940e1ff6837184ac7aeb34d84ffe93c030f57fd11df8652f8f9ba496d254b0eff53a9846a5e3a24191d2c9ba9100181372e48b7f019825632437e380c816330ee4722b8068565744d17a2924f3fa1e32508e1aad29ea5d968e25b4cc22f3681fac4787e29304773643b6b3146482c31b5741ae459e8cb530cd15f228eb78a14c2d728b6672e95d3fed9ee6f59bbed0bdbaa968a69f5c32714ab4e335c8940c7c3c08282fa4f16d245618ae0289e41ed3919788fff07fb1064c392230bd270055d9721378cec9f8f7579b52a19ee52bee5ca25d71a2f48bebd548d3534988add13e72d0927a1b44090136c9c4d396c26c132b92de384b4cde269c3a0bd8932f96b87223b9dc72acd79ed56a769e81732c1d9d2168cac4383ea6a02e4f57182ee2db7234b98fa8e63650a3fb48c01e49a8d0077a21367f4de401549edfcb1236866c72b1cd65661563ba8481f3bd1ed8af477da976f76c7595d4f903be81c119d82672367973a4c486f6c51a908f5f85cbfeaf69cd4b4a6fa1649971a694b4e6640572e63f9e41aebb9bbd05a508be4a0922a6699a60d8b3350592c5663c7aa37ee9729c76d6850c07c5a570845ed35163b524de5e16059037ba49d860f5fc8ac868720936e64b1eeecf9a7ecf701ce53616fa39104ce60d6faf877ce35a41ef19f472e9b5e057b57e673638d6f68406174567bb028ddde616406c63b743eb75e1a872191004e77e639bb343a97e8f6703251d7ec5720236a9207124968437ac89af724cc0b5f5a3f208f828671916d7103579ad7147f7abb042dcf101512390cd0e0c3f92e4729bfca11c9b4aa043618df34cc941c45f79bdc03c7e8ec823d0adab64b78d1892ee631bfd6c955ff128c23ee896b323dc73b3d2604657c8bc33f842721fa8b0ce5c8efd0e587028dd11347e9def96d97f7aaffa1e343810ad7a8d2f72fd7dd7fc38801213b2f80505a45567a7f54ea7187c421e63aa89de23254169654138fa52e5741f8307ff651a4b2fa19670e085350def88efa11ca6f5bdcf0972476576845dce2956b6f8d01f9209a8aa57fdb3786f3edf91d33c4cb5fdf2a472830580eafc120ba0949f0c233394689269d5ba58d79063f38699e5f4e3319d15eaa2ea1a47578a739ace35717ac9dd28e20e802070510f21b6d1590056db2b724f5d853a9d806deebc4d937ce108cdc6e4283936124061d09f35a177145bc87271ad1808fff62439d3d7a01aa3d23fa14a402fe3f97c9bafb00ef938397b3072d1f0501c4c16701c8e294fcc1a42953e570fef1838a2bdf135841568f2d406721d121804c3f9dd1b70646bffb3ffe9ab4689d789d7847e5e5694cc5e1f02cc34d0dc01053fe0b274806c863b41107342c579fe73e3b40c8aa8e94df90593fb7264b3dba613b6efdfd58890b1b0ebc86c904247708a5375cf30e3b432a8ecd75911193906a5dbf76eb5d51ab8b85f276f66b34ef5dfcb996e2e2d832edce0c272c921605cf5826a993aaf85e087a6e3e150ddf431793cc77a2193e32ac990bd7281e1281d46bb161aa64a68e2f9e4a1916614fe7a696990de40854c6edee0b842b16d17c9058b7afd817831a6894265acab69deed1f2ec482708eddecf2127b724a8fba95e5c0d36fb9d6b14840e46bf7773d47941f096b29be8b1b3d76e2004dd4fcfbf3bb8df028557d95e8a068024374c36d84263f0ef40346924f86fd6172e8835759c8cf5dd4b5d00fb38bf30e6cad88dd593326e3700c1aa08aa9f72672f5ca8596509da032418aeda737530271e22ce4a88c3512cc7c9320623dd8791fa0c2cfd363182f23b06b131242f4744b6749363e0c5111e65c8588308e053b726dbb2309e87289492b09d1f2a3b455ebec9088fa98fc828f678588483a3081632c9d01f6223772582b852613eff9d56acc1ca916ec6302db2b309f9733e4dd243a20d77d267540149493898d8e39ef754781613cd67f81de398dbe9d0b900f72880e757d4f9253bd4cec182a8eb7c50a0dd959e4d1ae7272f4a9a11fbd986b725d308b52f7486238ccc1d4e4caa74d73065da1945751f85f04cf4cc0fec0a672507dbd66ddff9f72cdb14dd94f1745449820e30fa3c3d721d20f7e36944c4772fc4ffcb67aa94ef0c76d2f5ab410dfd2b4cc8543bc503466e548e54e7520d6150fc008aaa30ccf5dc64b2403720fa79e233916af5be1b9dc8dfee8a82cde1272cc56881d1497e9b59cf9c444712d25b221c74f54c1aeb18c26ff4c4905a2e62b5f7f13088efef1c266930d2ef8d0ec18fa4a711f00070c5c383f63e12f4a1e7265b05444e868e1501f517c12e34e8846ff8a0a05c84c16f65f91946efba2c73781c4436525eb555256d765c372abf119830996bcfd3e89600ee0979704653472c8f2f049297193be1754d9ce3820c870c84a832095493cf687880b7bc6525372b95fe8a054532d1bad654d7a482b8c936d17f79b3a6ddb52a2d7775c407d154bd808e80817a35566584f5bbd0aed82b8f522bfec8a9c69c83c4c27bb2c9f8062412c170c68aebc65f8eb8b28cdc84c05ad7d9338214412df76f56d9f9256803a6eecad2739ce357eea8600da12afe07a1b4f707e6e9a2750d1649b53758392721a5e42cb6b7256a64b41a9910380f7dafbc20b87e4c039d1fed063be981edb722b852c14e0ea4adae83d7ca80e07576323d538c2c99e1eac45a21cbf9008d5520838514125e7595ead4c45345d1c12025e00f0aa1fdf1de8275a17aa250f90305da25e22eefe79a539646d152845f79f3ba53a21b59772c64004e1d6e1023872ddb818c0fe7e60ec127557345196710316d2d616f432096d2ac95097d3f574726bf9a2d0f31f5632bab520548851cd001dc2b9346b2209c4b9993798f35f78729a7534ccff76d7c230a1484bc4c8b82576cb045773f7d13016930def1f92f27240139b89efda2f8ad5255e725ae369a0176c31820363301d891b2c307a577272cf7b6a5608243fea6158a83303fce3e36c3c2bc3b4c08a805c92f02a25bd6f721c1bfbcfefb3d5bab3e4cf5e7a916f4e705453bb82a7374b649952afb524ec54cc0e2747e3e594f78f3c0669919a3f839a6a9f27c77b1e52d0b623cb62d84263a265dd8e62257af54bfdba57cecbcff61c197f036b5d97bcc587f648e8d581724f74b863c5c6c02a352c3656b9be8857011399b31111cf29b811f94355eae572afc9212199a73ff371271a5e0695fa12e37e9ff94fd0e0785c2c2983d306d272ce7868a90e6a36e3b61f7123bd812612f6be70da19b07403af0edb3dae74db727b4807954c15cf02dd059ec147e84b894d1c5bcf93d7c2e2e72d6af74164a652e15d6b1a03178ec4ac7e45b14f22952ba44c8e767f7b65e102f0a38824fdd239dcef0ba0d4c9fecc48820cd07bebfcdd6e3eb3d66f73aff7ae15bd3552891c00e6ff2643927d2264732b132afe8b86ae6f6899be10f1ab8a74cf0815d660a97231c888a996491fa5b13766de37eefb7e7d0c3cfba55eb0e6b7e83563a1f86272ecdc5f670ce30e629d4feebdd45b22110638886ad5346090004cf2da31633e72fe24701f1b7790e0c875332e0c3860a359cf1fa2fc792be5ca659e2640ec116e542c6804f9253e110490ae9fe0478b3bc5c7211788b5832832f416047db70e4b88065a91472e817674eb908bc67516eca083cd94e95fa32a20e234d92b577672ce6ffc345c256cfa354672f020c9725a6e363d551768c5023ff0071e0ed172721cf35f3321d9231646b3b14f2c13a4adc09ce28cafa683fd62aa187f19b986048f0ed8c1b2ecd73f4b93890e44841b10d5bc3fd935f6ce2361acce8a3f63237201f394f8cd50ca3e4d8b11f1ea42ad7006b9e403358177193db2f8cd6a222f190c202a82e9524c90950db1d8afee457b650d7dd0c65d78f33af298233b9ef372801ed364345ed4d30ea4d5ab8ff52100840cc9aa4ef42acc03d91ac51c634f72efc4c58ec770b08a69499d7585f53a1289f57fd8ce45e64bb7412b0cae435f08a2371e78852a693d159c0d94d123eccb863739ad66d10e25fa8189f879a4b672bd7a5a68a59d156a7e9097a0dbee9e17ce071197da1fcea2f04e5416efe706251d3181422762b0173c3e935b187027243328fdf647b3b75f45cf2b1038a17e729ff50c3bfff36cfdecc3b246d2d65c19e6adfd7e90e686efa9c254810b739441404e2701f27a8b974402f2d82f72769241f27da0605f9836dceb86bfecdf62722f26903d3bf5227e725a424114c2bcebc6ab24fa5115a7af120ae5f641b3687225a2fa2fe0c04ef7d02c10f5709ef392297ebb3bc9075d682afb7d17fd468772faf21e50fc7c8aa4b37b749a480d6f95a5b37a0f92327e7d89109df649b95f720d2d3a4b17b40d6fa24e6562116ffe3a57573f2e753795ae420e58de1da4ba720a3a354d51e2d64e2c3d843521f9720fdfd335e2ead4b184b0d2762c0e2dc3725a83b6db6eabd09c872f4b1b5071dfb49a766eecba0202f12a45b3e4bdfac655812df17256ce994d76381eb5a59d57c381bf2b124e273b1f7661d17c4a7c2d72f04031e854f3a2c253a72a5eaa8cf4081d2e038e57848f64ba3c1febca3614532af81878f2dd73f47659befcc461908b2ca60ec47dfb0f9550de5fc35f62d972f68c471420a66dcca2437e7ce4851a80d0d0d74a10eb78dbeb09f27daa9d7e0cc9ab71d1c47f913a2cf47cd223992dc262b704394654c0bd5872d670ebf5f917dea17b0fea5077e062dbae6945faf1ae6788a48d9e37d13faef488fcd20d01725733827195c7bc8e61726d1d56e8749f24628509c7a81e4527c2540b6b6cd64f0987965021054746b027437b9938c6d1a934e7a5a98bc6d30d4e32d0d9f69372acdf724b3eb3bb001e2b5834a07732be9cd499fdeac36aa42cabd198d95d4608e168583b7e1e5302f2d6843b5689ffa072a562eef95e220167b3799450c271729fa2dec8bc711c941ea9ee5f96a96892405c7595db48efc449b0db525d312f72d8cac8096c68dbe7fe27f70b8f4b089437c2e17ba77c97ee40fb1312d3b2e672e8d349b467542338e094afe4951418d24e09f96b04ca769fa7a5b370c869127214691da14c4fa710820ad5e9cfc3a6ae12c06dfc068c1fdab7bbed2dbe4cec72ac0637665452c2661fb412acba0f1b94f5ab97940ce68f75be6d6bc4452d406de46944b6e05cc19371bbf218fd4e33f87beaef23d406421d70b4872953cb5a52ad8c35d1f6162380c5dd56e31d3e87ec737a0a47d180e1bbd2760393387cae7286e595d074eb3a9517f2d940a4caa691e89e597ceabfa9af1c7a685abb052c72c2ab3293c327fc5b528fca6e05de2bee7b67ba3746c5160a7a1031a214457a7255c69d41d257613f41c5ccbf64dc75ac9246c972950bec768454f240102735727d37bf73c0c1b50fdf7b88cdbe180036f504a030c951f25113799593ad107c7291e105cd8e9784bbe10251097f23e5634911778ad9d9177b1b72e2958703a7368ad177836d12e38abecf34a30a00c1cbe4dce3408635f4ea224030a092f93d3f41cc33f4039f03fd202c11eb8f25b6fb9b0dad159b3f80b7e086aeb504c6cf727a79e08b8a8bf54f76e3938eda6a8d44e6b33816dbec8c5a320add8e4fc1a5722638c5d35fb4aa3799c6cd97d715e0bffeabe71d120ed8986e1ab8d342ce846040676f3a3ad5df4ec02aff7894a248c1a6b68b0d5de3745c0a421f637106a855f525afa947d83655aa692b4b5e0f5171b1e1722b4106e54ea32d8783c694fc6ba2c9e2e932114c4922cc58d72cc9ecc609f1e416e7cca16f3a1d9209583a4872fe0eb06228f29b4ca94a11b283c1549bf9a2ff25d8b01c2f5326a5d852955272f4737768c43433a65ab5609aa93a633ac231b938286e2b3d5ef2d7f0ce73cf1299da1508a0be25e264c1502aad024689b176cea537af8064a07bbfbd1dbc9c72ca64da623a8256f82a149c6f962f814589e01f516c768aaf7f290faa170e972fa97d48da9d4295ef48ab3728149def9cce47a939ecd7c42c29e6069e261ad22c19a3b01a4962ed59e3e89da952a399edcbb210968e55142ce17226a5f47ce21cf31e9d968d766ac4a92547b135271fcf92a243ba2c1d0397b3acde2f7d96be2300344d6e5aa090c9386721c5315bc62d8d616719ca448762c3298f8bea9fd172995face2cdc741d596b547641de25fde495133968195d8bc773dae05289d3172253c6f7a8f4d91011f58c4b650c704b1d135924569904a7b00518312c8fe2a7244a53bcb5f44d8d3936a7a89ced44a158427bb9caf44e325c5f1bf1c520bc4674de974a81e8dc910459764fa213f2961a80f11aeb4c33ab746245c00787bfd7233b031a1b0672becde9a42d38052588e6c17702946eb91c8cf954bf24764f272e4506e0296de5fb7e32419a5b39b09aff5b5b1d44abd3f49c21fb2d5a1aeb22b6efc08db6cad34ff64b086f5289d05fdfed99889b4a8b0bd34f2feb8f8a09e726cc4d84b6a5ae957b0f4f8b3c556c13eaa0e3187f0eb7a4a74ab50375be72072c9f5a99e2b3761ccc176f9dd125c9807619b17ffabd0cff76e152e76c451fd4a221911d58f458f2b91b0a1877d09fefa25d39c8cbcaf4fff379d0a44c35c77287d923ab930e361cef7f56f05b9d7d162a9ef69b8e708c9ce3977f802384b9b7201206c136a9b13e0fdc24e600227e16d509a8c5c34f1144c1528a9facc21fe00bf0c042486f277bbb000b90f617a3b74734e2c2a80cc29aba5b6cae23fbafc72bcdc0332d260e64d4be3480dc22a5535bb0fd8ce889c97c87a89d0f6240eac6acdf5559e12017455a6361066e14534789e1c7bdda81e4390e66caf7b0d0981724c5177751bbd97341ecf325dd32943dcf9707d5ac9d0e450ffbae7c857e7164afef894878719536eba38a217cf5bc1d67c1bb817285083e150c38a588896ab44d7694a448417a2a40fdb4c11c6f43a18dbc8a76642024e1a86c6161b3098f772b6534ede640880a335d809ddea005dde8e0b1c69a01d1cdcbd7811bd83e3747287c25304ad295bc16a48c411e953b1c199b6c3122a580a100d22656e224aae7262861ca068eb58c0ee37bd07094f5d91ed90a42b0683a55bcd1660e5b3051a482f98ac888c94abe849d99216eaa579d4797d6ed7cd629ff4b4ea300cbe56bc72575d41a6d7a41c18ed1cdf02f027ad2b4ff58e5baf2bd843527165d1c3511f72cac78a2789ad39ae996e2c4b53fd6ecc0b605adc3a31b7c50310ea92bad5374fa47c53279827728b52705c1c38c2be0430d5db77600fbf1804f6f26281c1332db4fccfc8bab809bddd52b1da4f0466671552845fd73f2b66ed98af1c596bd37234b297d4538fd57162f75d0309499391bf80649394ad475aad71b185006612729b671a480275e3e5b4deeedd05847cd19a5504fb43557209b4568a4a2401d931a7a5c7abab8ebfc2f10f4116356ade59a1152686db84258af513d640431d570ed5fd7e1dddb636a24604778145f62f0d4abc36ea86f12aa8a895a82557c54272a28f4f1532d1e8986b3ca7715bb0fa683bdb0d940a7da98b19749a9dadc440728a82b4cf227a457bef17ce1a5d4a040fe8407e4195ae07e310c157a902d21672c48f2247ebd48d61b506f83e0cd1e40ed3b982cf5586872aa745e467d7e4a072c6fec8b011fc4be91d16b6e7d1d4489acd9deb3e672b363781a5c45ab662dc2f850818d14b147b26f072e3e4ada7fb26cff97731a20e358ceadaf51e4e50212c8d83a6147672ef7ed0c5b764fb2624d05b2f78ec6609a31736e107b96d2886728c7e56a51425474b5035c0cac445068ff76aa4cf604eafc7cf1c1ff9c8718c07a572b0a459d45f9ff27205fd46c2a4fd30215a5c1b3f878bfbb816cf5fa3977204d326bde80c8bad7ee7885d5fe6fce993d5ad7df64283a21b8e9ef7aa314d727ff1d784152f871faf84d940bc82421cf0e49e6c25f71a059768ea3c85d6911daef765b276386480c6ea3b9b5c12e6332d111097c177b3b48549e465a99a6419febb36a1ab8d4c45de1cb665d7a66236722971e813ac10f8d8ea5cb41c5c9071e2799c0110b068de15f1e3a80329e13cdcf5b75f4f2015bb5c458b68a3215772065a475388f13cae4596547a3dc4113d9de1307809b8bf7c58ef4f8cdc6264720102b4b29359082c84d3dedfcf7e6b630e06a3a0c1fc1a0d827407e5e184a372a6e71a81181d0868b7bbed28899e2434c1abf02d3889ef3810174faf37fe69186a00f54b9aeb259ff0ab3d157b5ebcb98412e64c0131cd3d1555c580095ae7128e7ca6620a187a4ce6e35f202645eba5fbb12c00877ccb201db6d5dfc7406b728ba0616efd1497ef4124555c3d6d4e7cb9f5f37950172f73e3e05590c85e7969604f084bb935d54b9fd310bc934ca8ddb3f0dde5ad4ff735abaacbfeb6366d53cfcae508ed37e6dc8bf3e1ac3d68f67bd6d94431f88a8ef235be2b8c1b9c59677336ead654428f4daf45b23f1db26360a21cccf8df2c79033e214847af7ffd2fb627fd23aabd4273c1d22d5458174375d05180da5c6a8a92853e68e403dcc317e0c845dcfe7d42a737f594c4d38b52baac4cb2b7428bfa73ebb0693dcc09de08f4529c3163d8ff3275dcaaef3882359f5034d8273802a871cdc785e0dc2cc172e9e1e8d032fb0c3e07390bb16fed6f0566320c63762b281563be97e61827861fb4495969e651d05d972b8d56015b97b30c494830e929a4ab0b60413a32b87f7243f3cdaeb9176db1d2c2461c1c19348c09b3933e004476d2dcd79c5244be5b2e25c324897512b436f8fe5d0bf0f5253460af665300f371a7f07540308893ad72647b5839474e1f6f7227488cdf70b11c785af6646601696893b9f53909c251443591f11eb1e8380046bc3e176d1c735efd84c9bf39c384432d1f5c5782c4c372fc8a3d7a29fa9365efb0922c33f656344f4f594a96e8a13bdd8cd2abe850f427f0da9741cc5f1fe91e181f3185bc4c67ecdccfa23353fbdf47cb3e411043762596b1acfc9ea92b79de5090ed8395436050e0c0118ebb10b73bee94a427fd2f0cc98010090de4de2733ee07778d1cad944ba234be527d236d513d848e5f916172205b68d46153d980bc315d7f462d2ade52d93067ab91b91c184c15f6ee8a07727eeb7c54bbb6ab590ffdac39c49a1accc2bf9b18fdebdc9f82ab2fbd931f867201077d48da82265067bd989364998855cd9849542c63ee600f6d954a7d168b72c6b55916ed163fc88da6c592148cf44457142edc5ffdd85a002e03e5ef34d22f5f5d2b9891140ad91e07add7bd9679f2cbb0cd09dfefc8c80853486677366772674902cba5c63dc0d4ad161b4d0f3fcfd2126a8d0844fccabe15fb78e1e98d72f2df1d1ee5c5330ad7cddb33db8bca2a7f5b7209e6e30ceb6b58046c11a386729fc93c94b61a64dc436d18eebd84feadc7f377345a0c6bb4f08ff90b396ebc72ff8d94d39dbb6365456334c9847bd7a934021700befde58fee4b065ca7cab62d2822a787d58b8b9aadc27b1abffbe6cd33ed101115a003fc1e6453422779207217c2b5d4b0d3ebc7f194d617285886d42a2621839c2a2654150e5e52283c2f72d525f2d351f8e5334e5d27a0eec1a69fa9e1498aae67205f696148d969b0db62743e561cb6b80be8688473d3eaf4a2d919703a972a6836665ad3f19ecd45e0725872433fe528b3d49923637b1d31d64d12a249ad686cbd6f97e605f39e773741c1ea86621ac2f7748f1c70e169086fa077fc9b3239cf751fec3bd51ad2275e72aeab9797c1da020bc2b69428cace9305625c20d2cc94bb78ea8d04d144f4d672d616a7a604672976711fbdb387d6a86659f77678cce4d53372d64c120ad00d0c2ac7d33957a1b6b20ca3acdb8a91769f5629017374b9df8d85e511e2cff59b5454fd7bb1a714f316676e37e527b40d3bef3a3aab8bd0bcd2e5dc298b8121dc721311ef8bfd466efad3ec3ea44c51a277990fe65f183f8e713edde561aae6bd7235500d83d25b9e61238395160809620ac331e29536141d9671d91ca3f74328475841dcec28cf7942fd0c7368188db77b128ad273bfa1bbf7ccdbc998a15a3e72756ed87873208ef02f8ae068a0678b3ee711f5f12531d8e8b5080211320eef7249940df402ec0f81040af843f904b9fda122b0d6138b6b2c1950bb71d0298072dfe2ab7d7ed11d3d02af936e391521eef527adda47e73572400996b1b1e7d0721e1dc0f1d89b639323d893fc6c0dddb7a40e28847c644882d7e29eee1ba8525ea1f3a7e427ab77be6bc059fa5903a34996ec2940396b36a6feebad1350df4b582474ea7e9e6582027259e053aa4bed0117d9f623cee9877ce0fff85fb3112a72ac938ecc6515b2673e160f00177462af619d1e9a68f7b4973304798c10e4b67235c1988bbf5b2733565df3377c141139bc444a3e16de2cf61b437ec51882767252d0659b04f3a0dd99f3c680531728a656ac35a3473867611bf01e4c925f255af23de6abd6d90589aa57f87b3c38cfc36b5297d2dbad4049c39c3bd6b839cd724bd5ccd79b84c2895ac45414301875f4678aaa13f9942bb1aff5e0c20995587243175bc039c7507879d52c33cdca2d6d473f0b1030b5671f1836b6773776007269aa5ec417a0d2eed0bf94df80e89f91cb284106a1e109a0e0d76863cc04d872fbdaaef0fbf4e1b92cc08e6e63f0db2d42d4d6fd80ec52e63e16703bd6b15e727736f645dc285acbabbc8ccfee36825a4d2035877c6f8709f608417ff073fa72d0d32ae6f787918e2b297a22e0e14fa60fb7db54886cb87bdab633f61c9380315653e8a15ae19757031b2fab79fb54011397dacae1b5573277605303191f1f722f3147d139e987d015b4701c42a29b5c2a01c6592d38a334be2f2acef07386725cf0917e45146f454c576a0bcf5c6592c905a943636f1e0f1db9d073d5af2325a98c772d899a4a7d73bd94ed9ad4489c112f246eeb91051d936c08643dbad37255b88296e49fac605cd36ad5e4d56b830eade2d3948b2e2584921379f62eff72ffda79083b3d3d34f4a282ad5812e0889e52939f8b3a40f91a180f4311878672c292daa61b2bf0af02667f596de4aa1d417162b286b8003e890e48605454316b22e882216d46c92ba87e8693db1be36b71087a926c135dc6f05ab1ceeff1907201b053e927caaa1b18e922b10627d50d6504b179d283c466be537f7cc1c13272ba24ac525e1e8d247ffe929ad64ed0128cf94f9e8b0f31e482481d3baecef87203dce9538647f0d290144438d8af2b3880d776a7d4e7199b5ecf595ab8a77b7272c2a947c7005030870789a69adb2f88fbde17e813fd5ba99984146ec6730d63f267569aa8ec4801b2a5d22b6ddf9bac443e389d8fbf66e8d51dd5c2bff60072485e79b2f2c84936ffe484de8b635de966de76ac24e6e27511beb3c9119b8d21a065a891d0121a6f023aa8b49385f789c4965c36e9ab31815880ad9426f0f2728e3b23a78d859c5a0ef12e6e178ef2e4e545ece2a7fe5b759de11fd5c6456272af993e394295dd3ad0d89f732e5063722b8c21a1dc68cea3c43157add249731b8b8ecf1053480f16eaba702503529684e8ca4b95d22c6ce4321e08faa8b41663bf56f5495cae6b0fab584a92a6268f8110fdb49a985c28873fbad98d1fb5305b3eabf9fdeb6c9d3b7e145ee0593d004f0ad9cbda1c8f56ab9b992040161e6772e791631ce86bbd59485d5a2d7deb18065bd2122644d317fc28f14d67e80c5772118c3c71a7d739399c8c97f35c8a7805c9ded33c15670b53d26394fc81580b11364e49a030f481e5f55fe80460a3db6247b8ee1d4888ad3415cfdb2922f5bd077419b16e526c87c14945db1be770673a00164af29347d9b32e1adcaab3bc0772132ba47e3dbe2905a63e26c58e425aea4777ecdf365ce8da8f70f0dcb1ef967221f41d20083f158dfec1cb1b85faef17c82fc322e562a14f908ef4eb1b2658727da96c5e9b82f0ddc2cde015de436089a04bc4096467f5b948b15430060bd75a83bb536c3944adf381617b1b4e1aeb82d61fb51990019b2f6af885cfae0d065506205375cffe9168cf5e4dc6504ce5125d11120e84406da3178c7f7080c22472b44517b6b9589b234dca6129b3b51002be5cdc4acc57a8184afdc529ccacd36342d159212b24a33dbab3d0a979ccfcd3880a7e2d5bb2f82514d52e8a5512cf72a4cfb15a0ce946f13c0b92b13f1290cd6b873210572400ee0fa9dd77e240840a084194053c9df48127fbf79bdb0f35e33c0782bc41a43fda1b9644d05d611372c1f2bdfeeaf428245d1f7a62cbf270f31449439e7b9554a83a04648c71f756727e5caf175a4a3c2880cd299f3fe9a82f495bd9442c425d7855a7caee0bff92726216282803db0e904f009613d926a7945ae68e74c7cff6ec4282987768a54d72545d35fc5a68eb4922e20b1306e85ffa9c28c24258a9df186653a3744abab3722ba939f88e6b34d96d736d66d7703394fbb954d1b1ed65e0e05b7135fa6bfe7206503a0bd9e57f01dd05afb78382c523129b31710af0345f73119ca5223c080b670ac1494c8d8d9959ad1753b47c6df139b06e3799a8a3f1c1c9659cf7cb135a46ad6a26e0e909854569b89b1d9604da9aa2896a39262c8586e5afdba7baa172c4a23f934eb9bf2028228acbdae43fdb3227d7ef87dc51d594b409d28188f172561db464aa33b1a4c4a2085504c7d0674bb3311f713ebcaa18c5d1d4a894bd72f7bd0dc172c4d8925bd06f8d0ed49ad752575edbade96771fe991e4a6283597205189665b816cb31caabfa0ccce4078e0becc77deb4408119d4a49ca95399f23075063a05f843e55ffbbd4537e7ca8d540d4e59f20d1f16fa57cbb6e85f4d0682a21bfef8dd6a9a178907b7f0b07d0c8f3d31efe7d8db91cbed634b4c8abca3d1c3ea887eb015e108bbed56a82d5b35366fa3a9c548c43027dd98ac16fca665badcf6e19132b6ac5b34db7d5bb51862a97a1c500584474cd1f99dc186e065172d5ee9eb762c5b16264e24d85fccb4fd42b29753b1ceb62144b935aada3b3d551e6bdc3676b551f15bb2865a11d871fdeb4c432940924d71677b72028e0c59e72dbbfaae131b4ae1ba6c39654cffabafd6b3c11a0372c69940d1ce64211b2ef725ba73d36e06460c278db7b0bc9429f5465a82317aba0df761386ddd08406937256153aaf46b4f9d7a5a5265aa93da180808a6504af16b1567dfa16ac1ece67724a65eb876abb522cd5bb6a69e4ef20ba21405f7021628a62b33b1b49f11aeb726dd60b96dacce4d22e3c21155afcab3ea822106da3850e62c3f82b31d52ab772e917b977dcab7d45227c71671ca05d575c26adc4ede45d8e60948365eef02720166e14c8941d2ed0d54f9b1040d8679c979d51ff51265b086093c06374b66c723debbb0062f2ce0bf72b2ce4f5077988d54b79b76ceea5dc6a394ac5e24db244a7b9e1801b3e8bab3c1fa91d155d7befc262db39f61c8fc144dfca96178194721de466ddfc8029674f2762b5e92ca399e7d0451c3f5f5608c0784d1a08ccba50b15b3adb29847095ec5e5a4e2210c73009764c2a99eb621741f5d7ee5f8b6272857c4e433ec504132551f48a79056ca4ec6361f0cea0c1ddb8201ad4e0dc8272cd379aa328a87e1637935d64a9a20193b828bac4d79526c692bb3c530367b772563a7e98d502470e4a841d254a470dad976627616b08b77ad6ef4a0a37740c72459dfd3d4e2666d7b46431ece3d087a98792299e3a8bace4432096f1c8f4917282c367b044a8017da530acabfd221038ee557f013864887774f57f1c4aa8fa03e2e67f8ab36d5a4a73a2623b8df870bc7151bf8242e61b4665b1d9c330be6249ab6d878c2f92c330f91ee7be46b7e46fd2270a2d1de15bb72590b8380446ed726b803acdd9734bd1e50b2ee9be4dc3cc9c04b9c00cbc713d30fd41da6171da72ed80ded258ddf644e47dd57e4ba0d1904a781fd76b7a071c9e0c20b6519026722b90d5a85444f43174c1cb6e598c37c110216818e3b9302cf24889ccff140072a97fe904419545b8149382020ce3647d4aa502aa7854fc0128d3024c533d1050ad434902951a83458075267d48d9878005b61c127a0c3385669aafe13ee6810688a8c3eb214512136ee228ebeca601963e6dc82209a17164fbacef7041d7ce1c3cd3742df4b493c9cd66a027c28f66d15dd7b3e96f84c72792e801d5a1bfa072a84f4ccaa0776e13bacd96917d2cd5df7b0f4edfb629750ab2c69f915debcf6fa4b7ee810c96f697b9d659ab9b7ff4bd6c42fd62c92ca2f83897f051b5220d72691144b3dd30b49a32a33d6976079bb0fb6948ee6b8904eeab49f05b3f8443683f3cda50c6551084d0d02328c4c88daf10de550a73fc0ad39602c2a74245e072bcbdba1e4baa88daca579f5e779e3ddb70e947827047dc8679eb3f0359a4cb23d368f41f24624bad626893f88855ffd6f5c3e9de6033c44ecc5d838980c09372fa40d82c6e23bb03fba7d1b5dab33bfd1640f90998f329b3dc66d4bbbdb0450606793197683e8ef48855f77bbd9d8fc5b4ea15c2b8807c75ceabde0cb12e8e7239dba6c847b9dfe2f3a1a6d91d6638a9642d131d64fe07f734d46ad70503d572afbdd27f5f6a15ef519d55a41d34f9234956730a22a6180cc924717f1c9a7972508aebf2d92217fec88f28dd537d8f4967a06802a61a6742126f26d2bc7f53724cdd827a85f4cf20e1dd5f26239d02d761824f044c593927ca21820af0c2f472386c7cf8e5530a0c0ba8c65d4cf8b924a6c8c9b0387646c89fbf92b91751e4172be03f4340be61746cd87fad01ec9f2d7a7bfc5420981961662914e01dbe9e527f2b56083fda5ed0778180e16e2899908e7859122f9a867e0f84d785f96d6a724002c45e4c29bf16519a54552caaf37260fc3be324e2207a07910421f44eec72d9e51235f62c5a763e896bed6a4a9fbfea38ce7c7d6dd0b65a63bfcfdf6dc772cc969d0d65cce48b60fb25bc15f5cbb4e8194a25a7fe3ed7ee49e6cf5fe6d272a9abb7b5745cb5e2edc3bece84eeb2f8a5d8f188d1103bc7509a23bc657ede7220c833348a0e4248f0957344639330cd1475c9b4e625e8e4abe5df240f07995d8817216f067fa2644b07f50a687f8cfd3a2868f875a7402b65117055759ec372450be7baaab9a71fb41529ee25d3203d5cd36957d06ecd75cf537ff564791d72fc1b7c9a76e33108a623190e0db1927acdeec007cd60ac709201a353c4e36812aa6595b47b4db7940a2584528a1d549af5d7a3d88ca1831a83cbf668565a7419e273eaf5a0eef15c1917aba0556faba6e98e8540c79152e890d385856d96f972a8239e85f3f4488fe9a14462ddd81b369693eaeaf6b9ecf8b00e9c8d2cf10b6a1e58ffd64086a5ee6558f97b0d481b05cbf63a8cd827a5cd719b0781b8000372220c70b0c21c6a95b030fc229b9c65b15773e6ee22cec820a163ee38f970a74822a4baad7adb2579a72f1f42be1573702bb6c40591b71bd746a3aa5e2cca1b70191119a38d29dfa2bf723b4da30c698600f8909ad1c40e307a9c5d7905178343191b7ae0a2d914b2e9c6afdc40c7c4a83020e172561b0a35f97302820721f3723865e2aaa099599af6402443392ad6436578fa6e2b1d868c48284337633e8f7297448afb0c912cbced9191f82628ff65b937cffa2f866dcb340e40a6b2755f0d48cd79274c28420a94c9a93c9ccfd78e78531eaba2879cf67e0a9d4e7387127235829bebdcb692fbf85764fdda60dde7c5fdfa2aa49b0e6d862f499a6d406928c9967d6a249b5d964feac56f8f17b9a7d4c4f8e3115293393285a24ddb249b59ab5b78db0cacb8bb5faa9a926a35ca0173d04a639776e0feb08d65a3726363576aac110362f6a67d6b237cc2054cea52b3937036a863672bb123e140b434554522c61ced19f70783cdab4c8c41f81fb47ae02280ecfd8752f87fb2d79e220c7220d8aaf8bd088de60b5b0ab9e731ab14f66c491d382cbf90ba318a678cde7772676527af74705bc7def163900d9e207ef6d36c8aea312f6f14308ef4eac7d0724499144ffb61a9980991f49cbbe9bb8ae53eeea75f5756540ff430841a237a72fd87046a514a51db97e1e30ea7d48b93c1559ba56af36ca958b9d26e3d999672ee2cbd289f9117bd0da648fc2da389003535f419a40afe44fa9726472ed3f472ba96ad9ab1a0ab9903b091f9111463484b3d89b01d02e4524dbf21a64fea0252a5af3222f52b15d27b436780ba53e7e43581a4997ae0e4f6ee3951b41e4d162ba2dd0dd521a092b789bef990f5af7dad2ea8c6906e6fa82156c26a4c50378b72c3b22547079842e53b836043845dfbc798422aafbd25b659da22cf73a9971f222ff7a8910631376e2898c0596db508d4c72df33ec9812dc632fa02abf31ee7452ea684df3d161ddfd57ce9050da372b2c76a658aaa320acf589725911b82e45d5f05eadfc0c9fffc540a0cb8abafcdcf0e256f7a9e0b20a5a2334164aaaa5b7204b24865257d95f32b63c7c277117631fe986f794a1fb78c6f28eb38814e2f727793399ab3cdcf69025a45c6010740f99b37a8e1bae4045f8480ceabe5341522d06ccbd0d9eadbea0e0f5a251eb38756ad5653759d9d35a31afe802ddea51d72a47ea23b50cd7d931c708a581777578a7cc8e497ba6ee79f8947dcd190456772470f01e8f945cee17758826705afa12b1abc72c86aebe2bfe3b47d67b1e9e13cf7cf80f7b0d3939057a81536754fc2719fecdb2e84e77f83d686e967bb99067263c1f2ec091a6f78e92c6f13a856cf77e38afa21bbe800947544baac12d5a672d7dc240e1863e00160d8b78a31c6a7d2474c8f3e6e8836796cc399b7b7b91472bf8208dae328bc11de13f126e18795a939094c7f6ef7751402887af0892753724ce6eca036ce6abadd6bc458a43a5c96f113bff7bd021833178608c473720d72491d75aee3f1bcb99e386ab475b964389f808120791a194712833a74f0e302720ea680935ef50f813349436052b9f2e8956885597f0f0737044aeefd8a2e8e27f9a2daab2650d0722bdb5fac1707e3ca3c5b3225690d9c820ff7169a6d0b8d4e4a69770aa8e0c5c29a6ab04fdbefceda4d8782c7bc04ac9ddaa70d29a416f772eb56bca23000e04b51b7aa1b4f495d72bc5c30d63ca4ea60e02775abb13e406b8d1beaf3df8d5d57d81e42b6f6c2fe163943b471e02323d947fedb7cca3b927213c23b9f438aa1fa6ddf21dbe01368871e5de1114933e8bfdc28ed165c642b30ee595deba8d6e2ca2874a513c43c2a19e2baa646b5ec84bd3a6c442827f2ff72a942983f52ce0bafd7d119a88de77068ca70966c0dfce0cac680b19b2c2e5e57064def6add71be01151ddd0e7264ff3fe67129056e9997c80c1bc581d8628756f8ee0298681ceac6f2cfa79b56c0fa53eeef0349f6fdbae83d969033c6996b72ddb6b94e862c99534db20284f07da5383ca7e11a4af2af79c1a7da5d91c3cd72f110c22063052686675160157ca9f1c01234ace39de088b115461cf8f85ce872aa4ee5539c5da9e75a20b7a25cce973c8f208f4e387f51dd1c2d0d810030d972dc4657f2f31cb1befc9551a24e8a7adfaddf57a94b2aa70db78449a365f3a172827d4d9b6709cd7b3cd5a89eed1a5bf18dbbc84f84148674a280aa795923e36eb932dec7b85d5a0eea1ab45020cf833ded351ffa577f508f6dec5e759bdde872f7164a929d5d2814557531d188577f2d84d0445ea95e82857762807f722e0b58e275afe8d9a5990037a87ffbc867dfa4d05b35125b1b527327eccba11824e1720c44bde82f03e1a444d8db2d51c27ce112a22baa11b2cb99728153368d34b672237609fc571d52529e23e7c615e1fb94372ff561d11cf8a47a2a68d28950e172bcc0b835e1db9af4c3276d1c34d04b91413fcd6e3d837f31dcec852a4b6c2c0521bd4ee3c3d9eab9815dbbbb70533d2af099740e70bb69f23c5df3e659fd1a724c8c6479edf82bf70ef46eab9c1f6707bfd664d5d9fe75bbfadb7ae59cca7172a8d005830a7464cf1bf089afaa270321804e78d5484989a4fa3000a3fc90a612ce9ee2c5671a3876c2e65bd4ed8fcd6a48f5b02093b7d4be708b3e06e12dfc00bd3a4fb6d4d599f13dfcc2665f0e886ae4b090e12f3105c9e9b533fc3ff9de72b259dd2a75b832d39769bc980cea7a581c94c99bef2aa065546169ad447b31720e61bccec25006b5fdb51a1e973e770b6bfb5f22f6a713cf46b0c4c3da9305729755ec9e3c5ee6d1a6aa5a138f92836b2dbb348969047429e8b11f75193e0772ef57963215c1005888bb54983100033ae86f46d6c2003776d48928485cfddc72e54c03094cde409613fd2fae197ad90cfdc63ea888ca0f2eefab7879ccb32672d69c57df03b9ce0f9e7a8277cd06f44d083d7eaa079136db801c0c40e363307218c0934d54865b3fdfd7da5a6947c6ff6185a8a28afcb8f2de06c108631e007257742ad49efee5e110211daffb42b29a1e8ae120d0f78ebe530963de09371330112111a2aa9439ce6b3c77cc27f45b5e700d854916224d27c1c8247cb5a511729adb347e695f957315fd160aa7fbf92e2b0e8655a091d69d0f51398276ff8a72f18137bd6d5c7c613ab5b7bed051b314f6e15df755efbd52025249421ea191729479e2d990d58da415e807e4a99a3de404a6fb8e423deb4005166cc30f437b4de3119a07dfce8bb1cc33a64df1d716b0770899892c5aed70773a6a89cdc51572a7ed3dadbb2d56b2029b241d912674c52c1a11c34a810004d0c4625b04abe7726dd549f64a66107c43e45d2800da40099308a102108cd58e1f5c83041030ec72ea3ae6dd992d0523f016faa0ebdc7af1ead3de35ccbd93a07cc1edd32b18df35080571e5f8802769e4593ff864f1de7bd77250f4597c17df4ce4968c7f6c34630f465909c2af11d6c0aa4710da71230e375060cbdd8617cac6fa93010fbd737232072748ec43906c9709b7d3c3e1c3277a2ea1eac4f759449e0ac270e2cf6f19d78dbd71ddde8fcb515eda3911ab33643a5ca9a73698a84e01c363c3a7b4f66c23b636d7c21a14669e594247512b0e077c1ed3926c3c15738429d47b993a3c72b9ac2c5131b4a0bfc67608965c8684f816e4cde5c654455c28c072af98107272d49b49ff930d3243209a02deba45b0ab001b465fbf73e4f0d855aea14e00232f0d138357bc7a96d55e3c1686d7d3bfa4f10da8208c3013e4a76baf2c6da71072c6f05f9aa30d78f8463e02a1d50245d21cd4e803f84c20dcc77357c8cd140072046ede8dddc12f10d3b7b961ebaa119c82f35d575bb1c06a19ac9f913b03a272122ffd457dc5f46870d911a957359b81dd492371b64bf310b1a2ac7b4913bb72eb25a6027b3395b080c68735e7b18f5cf6506862872ff4864d29b406b727073464a9702b2fb7bc2677b037bb1f0c5bd57cac7c2dccc647405093692fb78bc0081657fd4e50c8838db8e841c9bd920f21f0928068f77e90ec699d85cb68cc85725db3d327993048e6a5a8f190484cb313ea1023282dd8f436132e67d4f85a4872322e94781c636200bb32aeebf3d5918a55bd1261be7d63ba7b66058f162c0d0390c7b431e8318d15adb0db5eda9d6d022d6bb25bed9a54e8e872a09cf1ff7e726b8bf1c93a96e16b5c0b6ba3f1e4d700aea72cd3b177257714be6d2d7ad115137991163470ed2dffe966e99fea84734ca720b000d8635b2faf039d382807f47260e047eafea4fb154843f4c79dfc648f08a1274e867cb4760a9e03d8c7be0d3504b3746b4981d02779969cf00a925734b0606929d9c17e05e9dc9171c0975f7238e797da8bd511ef5ce2ae4a3754dadd3a1029b672f5009ba0cac01976df1e7256666555e714a648d8afc943a31949819f6e1b4597fcf4028af0b21748d4d8727fe72d3ad01123910b8969f1adc4c5a05e9a2eb96c2ace8bb6fc4f51f99c5272e68ba0814f06e3650de2bbf17c1856dafcc6b2aa3cea2f6a716d4974ca6f4830ce7e2640a0a445ab1bcfc0e86b1756d30a0551ef45bba5cfb02eac57b3955d72ff6859ac223354d3f5e8515bb9dd089f2db4d119e6171cde70aea7daf5ea0358ad06812dd5bd3f689d6f3936265fe15810c54c14ae12bdccb3516fbad02da21381b28b0a6347353c6ee5ee438e7c4f00d828788e7977e7bc42f8054b9364e24bb0c1978ad62a35b16125e7d4751047772381a704b1efcee6ecfbca964701d74bf55e6c5c8a61c5f74756e3fad2549de4ed4ac926d7bcd1f79b226e732f9b86417b8ccc3a75c43ba37df53440fe1e96e62a28e6ac3d9e29275b53f2899d84d372f362e8eed9c8b10338af22d8e559796e037fac0284be08fa1386d4647ab38f72b6497bc9c6def9f279105bf1424f55670111c145e854aec362658ce12b120e7259ed9938adb6d0f0c4ebca19ea1e899d524dee23cf7bb4d64fb6c405e76947723a5a7b9ca92c175f4b9449245dc109ce2ff7d0bb21b3ed773bd41fb5e319f54703541ec1f80c837b332bd498539c8916ce618a4d89dcc575a38dad548d5a535bb9963287f184ccccdb61590f355e79a72be0b28b306f0812d133470aeef3767208eed8076cdf12a4bab5c58936af55c76994144f186c671548e887bb36a5f27232d9e2e5f3e17c72c7f764d933a351e9ccd739a9ccd623dccd8c45663ff6be0213d8b58e87f9ea9037bc78bf0ce0e84413ad4dcfe0dd0d7be2113d7925807872122cdd94d7de143eced5ab7b01cdaa5e12d67c769dce3c33fc2f71b2f344a66737b999bb7a3f3300243bdb590dcf567ed27f636989af2d1e698a89e88f0b0641e117bf5c9541fcd73df0537f6a1468c59b7c882a4aa32b8289ee022184c19572849e0d44a876bc2add6be45c5b77e1137abf949bbbe453f390b63cfee66f347260dd04013a87f31fdc368d4dde58654be5f4a8f88f410d0e51e5d3e52c943c72aba400eab5032edb4eedfa3089c9cff2aa0060cc962e2cc56c4f1127f7ec424c0c5b1f1042724f0079aa5e11fd20b172f85b6d3563caa854eecfcc6f28a0a47234e25f07899aa046f1411ab173b961003ac5f39a59edaaf4783c05b7a16c544119f563694072bdc59d9ef3ec94799d09a18341e87549105092ab8c1564b3237206248bccfb743c161af45365845bd790d4cd7cb8076777a31f1f276341824372bc8ba8fb82b0fdc867b1c12dca0f8e2c6ca9990c7327a3e11e53abdc6cc6e8724feaaeafc60df07d1f71da9b83b3e7a8dbc9a1583dd5e35985c26131d8108972afb137391afb799f525f2145eee441e32c99c7982f11304722bf9c257ddbf572969f87f5bfce5726f48042b5755775f55de351b736a47a3c7fcb6060b007df728e032199303c6b3d9b027ea0eab3ca54bc8a35f8bb74b0de78095a09a43db872b00c1b9b3aa387c8891191c6e1dfe8b61ad58b5c4dfe8c41a32c6802f2b73c729cb9135d747dbfad15cbb9be86b177b63a4bbf2e210f8e7fa5b612db385d99063cb2fada1c06ec7f88d084dbccbdac6ed30f56df9714031281f74337b343c720ee955a0875b7c0e4a0f4aaaa8883a36e32a8683db46230c430057b448698dd722c3d36919d0e5ce135f5100bcd77b0f627e05c4b6f75455954a4cbbe52085467db8c9d2bdaf00e75be8f968d2618385a037afb67e79174f0a4712efb1dc38e722ec8ca8d3ce181bcec1be7756333a4e1698b19beee0a311c96a0a19784eca65ea0c85c62efd001eec4d8ee84976c2515e10614a31904c788dc8a4d2c888c4e56bb7b71c774fede72c778ed881bd5e0e01b01cf5a3746c04bf907f24b1847e972eb411909c719009e5da00d1ee1cc4d6df68f9bc3023e14362eb1b47f87dd167291411e712fdb794f9dd441f464ded5418ba7f5c1b2ec13f3c97e31ad124df3154450f007ecd57ccd2a7bb32a627ca9ffb265d266c26f0ec12c25c6ec321f2200a8273e26b3149638e4e9f194dfb3aa7e9cc0f311e01c5823e0c6bfc8073b8b723be791e9cd169882c8a83f9c1fe17631cf55022df9801a14275aaf8f6d647460b97956c019c03dd076bccab12ce92ecd22483b2c639ac317581cbc41441727083debaa31434646ede30520d982b50bde69e840db041dc72b91886435ce405d72a68a23112e7575b2ebeb3140f42dc022248837d62dc81530d0d7f341335f70720478c5735f3634281f0cd38825c5c3daf2de8f8c03896b9ae71b32e10c0bba5c2169460e201470a858aaeda0e00af2a9e761ee9803319eacc203521bbfb9b672c930e4aa03d6b265429d036ce9b7c58a6689a2a9e22bda45f778546fcc27b072239056d4d595c811567dca3af932a1824c3c0dfada11f2b69f30c103e1bc6572dc612564b7d99ac4da23f9bca83d05b682132c9d81b04f27f9d24c7187a827726e2858453d46b87a3f23ea392d1c9b32288afafc2aaea5ed118da4150f6de472297441bb7e710896e2775c63b982d9f50df735a308f496eb1b208ff3ca6ecb0eb655182e1256d1c26d96045ad65c1168c10f54e1b60b17009fc610a27def917294e2da1e46e918316fba823499556faff629697d8ca1420970bac546bee68d132bb303e52b008bcf9478fda61baad74c78cd6e4003181d61f995a2ca58363e72fd23512ab6d0b0efc48015cb59f600aab6c20bb6a237b3a4e93c2155677d1f72e02a38faa7a48618722da748e5908a661668d06706108e0bba3121ed36e6e118303f43ae9f731ba76266933122893611ce9c1b78608a2d2c675f81e766eca47243d24309ea286763d78aa6d53afcc0e948b15948c8a109bd4c0a38fa0e5ca172d31cdec5ced6d4eb79c044453f0f9be9e3b7eea61d5a6e39203cf0a4e1e04c729c05721b1093731a8e31cb4cc594e2408c94304b1314964b78db55bc0457de5c5e461079ed006e488c0cf2815450cc619cb158b0ee54f7b7e95ff93eeff03a721d4037893b19d11b36776761e4d784bbb1165d0b7ae5e825913fb6475e87a10dd62611df016774c979de21c16a8864350e346cc2993db6cf31b4b7c831ed387242eee9ebeadd209cdbf7471889a1c8117264a5b21490ca3e112e481afc5eb00301480233fa76a8ddaaf28a3aae3caba505c6b4eb316bb3a2d12319700f8cdf728f00530eab0bda2b9944e21f1a3710bab265cfeb802c89ad8a624df544bf2a7280e35e026e222efe7efc517c87dbe49ec77c792524afc9fcec9706a5a739bf729162e0c915b51448d4113932997b7487844e13819b8e458f3198fc834ed0e072ba82bcb21072920c8cd3e7cf37ad19cbcab3f74e8dd70d142f03e9f39a9e0a7266d143ea966c15d163139b38909ebe05963084212f3fc0cf930af68a60680c72b4830cdc77416d55c5a5635d66b56673ab8c3be047659d2fbbb39b465a0fb7723ff76e17540f14fd11bfc5f5ca3e75d0a687cd834c3164ada22dfd7a96dc5a72caabbeda1431c43855cd82b800091fab4f51ef9a6786bc431f8665b1e014732cf442d0e7599c09e41fa587c523a585166bb66e6168bdec09206ffe4d6bb4407217bee6c6c0bb201f70ead8a7cb06d3373ca5e6d9a537806655fb3fc89e93f261818cd66de9c716c75f0482608040e8b5d8ec5fd4c6d813b8177475a641eee60feb97de1ab33c645365853a7d0290bc514fe1d72c8f6d1c6ada16569f0b436e72ed14daa49c2010a88bf972fa8c3015fdaa9b4c75fab585af9db0f36ce8371459ecc8f496110dde62e70ebc9996ad84ae50ce0ac0dc40cf789cae942713b6a720ce14d4c239951aaa453e2e8b064a9ccfdd04e91daf60e7c7c8145a0d014bc972a4786f5171bf243312679525f59eb3f7193542cb36684c92f443f205743af572e038b685daa6cfa056dcfd97a5f19dfa4b0407308ec042229419c1b63b61857268e08619c176602609c6763e80a652354ffb829550d338f7409c04e1e0f9e963cd28542a486e3f306a93846491197c231cd738d8395e7f7a6bc3e28ed0de066673ad0f88456e329cead5e40ae66654daa7f6300d9fedb150d1c0d1953c85de4c228748407c48b4d437aeab1662574afc56bc4347c831f95ccdbe3f905703e5188592c6b92c1c326e251f7ecbf8d2abbd8dae5a4c3b5949869a92fc7072630f72320972046b0fdb03f7fe08fbfa0abc2874aa919801ec56c7f734f7e14b993272d20f52195de98ec788adecf83747c49a1597be290c0276656b643ccac8e1727254d7dee1d5a5120ca4882358ba913f1f3c0110b85cac0e538a2b67ffd9f73d5bf48c0234c24ddfc4eb1cbe74f5cfd725164e99a016b949f92c6e2d42a171a95a7f9c4664cc9c24bec7dfab91cdb2c22ca4a823f7390ac309349d242f254492729facc7864fa4db6f814de59581f3874e85ab514e2491aa3ed343fdfb0562ce0bf9a0c08b895dc580ad890eccf5d33a7222cc18f661576dc1a8b5e02c1d0f8d7223f621515a75d9f908f5deb9d4e3b7a9792f93268797b91b3cc10b443206152b7b88c605fbb234327d2e61935a639c426192206a2b2683f63bd3efd114fa6f0b4d4699091755a659c792e98cf7361ba79a2dbecfe2234ad1fa8851e649a9563c6bf5a245f8b316ba375eee766eb1a9d4d2200ed2f76bdb13a564af67e49be17255e20b3e3e2de0249547c390aa0b5ccdce764539dd725b5fcdb4faa08b437172f5943dffa71ca5eab3ace6476e56485e1421c1d32de9300f9d2fb7ecce4f5672282983bcaa52f5e443864d7665724a5c3fc3a65bb304e09c9d3af4fc1a9cde1bd1280ffa3861023be0ae9c0b081fc3f8a930724acef9e2350b7c4deab0a8e44feb468991313537e6b7e612f6dfda762a31174d186c4bb4d9258269e7e75c73094d9f2d0a8422aa5017fc27755ceafbe6288f179cfb3ea2e88df95ae096da13728d366a48e111e653a11c3b9c2fa6cc9314ae4e7a7dc3b8639166fd0e3609002a5e8fb808faf582e76fc7905bd99947083c19e45fe711effeac14e7e99f251f72016bc3ebdf7a271fa63017725091a8ae12e42389c0792f857a11c47a35d68b728a2c56ecb817b804d2f26f292e48f9bc08d0da66bc7b62a8f8baa579207a72728680a3bfb6f82a21b01ea95d60fa86e2656a29d2913d3813725763046de90272bf909488bb9e27091a82c585ac26c30583ab1a67ce1a66c3bcf21ccae636bc7204fa85893b1c2e3dc8a131b6f3996d42411e6d1b1fff20ab14593183111fc0328e137be729354c56072900d30f4701ab606af772fb0a0847edd14d08e131aa72e83f0990df8befdf1a8f512d9560ce561c20954356210746240375dbba3e3c4b5e9814bcb15268fea87435d48f1b39a0c4901283475afe78f380b89f7c4f9c67781cee8507fc74a70ca8ac7f5025cc9918dd17ba2d1d81dcb901e28441fda63b387d2bc84e47056283544b4d780ed4b1405fd45ae03bdb3b39b6f8584b5bdd72b8a6ba93e4109aa87ade57bab3ab2d6f81aedfb5be9eac5f10a2ff64022e1b72244a2d487ac2367c7ee9db2777c1339de1a0cf0362717bee016fc3c41eb4f072f6b287a3a1391ac5bf948399d87471f26bd18732ecccb61b568dba175207f872ad0a2c47a11432fce58d443114f3eafa270ed1b9892fe4c3042584453e589f6451acde33ed7b5b146bd49751989b7a0415215596d6696007ac2c433fc9b9721523ebb4c759d522067fa37f936dbec72b7632980963d07990f69f4576cb5ccf72465fb13deabbda6fba38eae07e55fcd9bbc402a99c1b7855f33b6f9f9678752e1df298cedbbc58b08143cb1e1bd58546ad3be7995153b5e07954692775ae634946439324ea7275f582b4d19c181fb51eae609c250bd30cd84f98c2c93d03143116e9941243bf063d7fb56c59c86c1d87ed5eb39c8c2ea56552e89a371179eb07f0d676fab4fa3852e7acfa82ef01e5a1a1d5621fe36cf942f1332c5d3c51b8727a54eb3deeb76a05771ad3150320b73b667fbef759ac31d1e55e82a2c61795725e497aee331eb4de6a8bc6e8c48dde01e2947e4cb13b2bd091851e34929952062e30e7a87003cf493d98ec839837b1e33a4cf29cfd75d8d2c3dd8ceded3a9c72efedd2899be52800b3cb80fa497823454bf7c5ee3d77dc95fd7ddb1409bfb23fa7947dbe6397476cd7f1ad63811d2e1f3532142b82c5cbfc1ce12b4d01b24d72c8abdc015f56654cf7378cbec917b38151a2175881e14f7d84fe69dd7665961e76875a0e99067d81f595eea7bc664daf5537ebd5157f2a850f52db8d3955463145b832f4d4881bc0bca520ec23791ccaf20a8df4c659a1b0bd20483fbe9812108560c5ffa9c5ed44967adaf380d7906123249606984d891685f71598be793c2fda57184304ffed3aa2b7cba0bf987a9bc5555f7f60f4b137f52bb538c52bc6725b421b07797dcde3aa5b6ff530ff45fe47ea48a6eeae7f4a0388e52eb1a328729982c22406c852febe4434219a8f7d7344364d95597c58639853b161c0366d72d8d65b662ccb1814e78fa211114afeaf8ab42c95728c7f6e57e2be8132af5472a28b5266d7e41cd45e9be4533dc895dfa766240736729c735d9afc3f55915272e6447bcbfca78e2469ac464a2379f4b0304e2919046a3fd29e55ce1f7b456b5cc369064ae49da9b82a67fedfd3197db64e92ddc4c5bfb93eb812b275d1dcb2727c3d2218f854e6b68648a1ffdf8eec9e92a77fe69269c85d6768c71180018872f2e4f1c37d38b260c180404d04dab04827677e4c4849ba0ca002bc7f138e067287fd53188c2b5db0f6bef06231ae8271a6bf13e1603f8e9001df8521829d3a727365f031551cea37d2c8b3c5ffb9448af222715eb863672d2f28088a569f4e721efe3f71dc5a523db03187cd4a1ab0d902a2911fadea94e85fdcf2e9a499ff72551e239ad119caa8f4cd76e81a1d17c6e809bde535668bb62b90e1ce1fb9357299632a7687a1ce65370add5017c2a6d138b9f72f7dd3f35e3d65b0d063cd8e720f78203aba4bb346d1b601923bf3fe476af5e2d9fc119190d35d779455940172c2fc6157ec3f4e06c4cc15f5a3883dc85213df89dba94bad1d61ae5640b1e92c6184052eb0c96fc5cb9ab5607062cb773e8331c4a3979e975e87ee256634fb726acc492f66c5d7aea1418e2aaa5218f65aeb58a97c6139d8d537e172ebad3e377f411f2c099b70e512f2e49de9651ab80caa6c9d70ecf45fc7bd406e3785ac72082ed1dd072f52a9c94e38ead1c10ca17f23cbdc0f139d4e43c534599dd87b6b3b83317315c8efb7ca52dd8d7f1edbe8e20713feae1dd4f9da8cb752ac40a97129fdd2cb09361eb6b30515cea5e80d8df74511bf5503b229a87849e7bd887d7254ed8d0292d1d05d3ae3dba7ffc330543f1f487270d0c6722bd3d41429cbf955eaeafdc28df86686dda7f0752d98c13ceef0f3383fb9db7a26107d594837f1725dc406af4f503280bf53142e483c2d3ead56424c40015fdb35fd2aa6bd1928729c2396b997c1ad60bf33cbea46b35b4087077ef0fe18a1e274047ae4301d2372d61ed6ebf270841db7e24814a6ae19c58f4c6183dcdb1d0f7f8d0df0b4256b728bac7d2d93a8c985d9f8aa1bace9a1a38db11c3c61db7af2e32c701251ab1a72c7992b93c01114451ce54a13d046cdead0cca67864fbb8b979d6afe2d0ed3b13a1a887d02b19fcccd9495e87a15347b02c8e9a82ea4698cc31f68bce5ba6e219624c8e499ffab071fc6628d6956ea86a19d09d630ada1cc8430aed332446823d2414f446a43e223e7d2f3eb5b3e8b8d2436bcc25908fb25308684cf701528c72c184bbdb08c727d5248e04abc0cfeb2c10baeb2e6e6264fa77c2768a8d18471ac9c8b282c71e2997acd9c4a7d5b7493e1fbb0b4ad8f66261c89286799e759507dd1d01512b8478ac177c717c5db4a1baee2f0a70663d11b7f53931cb1e572972f7247258dba5ce2d6a6def35edf3157408ff2d9a9e0274c234b546153106927257b482e31fdeb63efdaf617de2fdf7b702936ed6771b09f54b2ff08bba333b532dec1b980b98b50fffbf70fb99df2943a8d992b959ed4e5d2453dbcd306cef6559f0e28f3dee7db0b53cf1b723939b82ba859c7e305e57f28f0b23cb02ab6272080a3fed91616510aa97ef2db9f6eede1b884dd82d0740f08589df06c5f5cd72ce305a7c465f761ddddc9f89cdd5dc411be4dc5a5ce4ef2cf14faf046088d65accb659b8046ef89e6b1baeef977d266c0eccb8a15554d587b56b39bb21d67d720a88ccd02ec52d757cc53176e347b0f0fdb98aaa78e6b7ba42f54ce4759be972885920359fc0ed87eb1cf54671dbf444614b05c713e80a55cae37e145151d105b73ff9cbafa477e7680f95ce0d291d7ce4ef86a71097f65bfc61e11becdba17296f82a4ae496d6b4d9fc89493f40d0e74a92f1b33ae04d33460bd3da44177f726445cfcfc6777fcbc3ffda01caa823a4d0cde901f0a4f84b333e1aa02417dc72d43f4b89cd84bb6ec115ef22896dd5fa4800f008c91eb67d33aecdfc8a0e6f634797d3041e165bdfd523be42f8445ccb0cc81b8df2d0c7c49feab2bc2b69d37296fdbe520d85e0fb88b06c4f4226a45000ec91021301c140c4fc7c2f6231dc7285d951d94c2c118ecdfdf627daeee7119390eb17f33408d0324ae9a1dc1d3111e556dcc1eabf420a5a50c814f21ffadf3396df91656ec9a7f4dc6bfc7b70df72ba8fc7259f8fb1115241adc403ed5af3ce2136c06281ed57b95159fe09fe597276e9f77b541020f15b288162797fa71f7164664c447ed2cf39cc6f2522187172d0caa37f946eac9ea3aa61661a63fbdd45604029705e217201ed82c60b405012129c642c1bc5faa8d131027ace108228eae8c2e5812c774b7d0a86b3c15e6c17aebd79b528e7acc9b844ec47791990351ac4540a8bebc2438a634d58d335df7289ee93bf47859a9a5878e1bee1c57038a6517ee4465456abe2ec24d1254b9b041c4ac64430cf97fc99bd1bf1e9716babd3a65f757851839a92439dbf41ad5172a5c7ac48bfca156f095414f23e101d675b068877ca3e3d67a06696bba0aa35729c3adfe9d8dbc42ccfdb94e25f4451070d6722c37282bd4c7db032592196de0ad6e6ff807a222ad22fd561168f8204c5390d55a490d88786e29578baa11bc572414f944e13018a5f1f6331797498f1959002e7276a85f691a76043b75fcf3f55f1a08b32564093c71da53b1cdfcb2e906fde7af38e583c5f812eb5004aa22372d3d2092730c39d5387564c1d5de770c159976cb1d12ba86511536fa19f62d872dd1cf970d900a8bfc2bfd00fbc150cc7841f6a227d9045cf625d4cc033109a217b60bcec484ae98b041d46b503e22c87455c5dbd7e313587415253b54c3e161173791582a7963c34e60c960969878f29e7ad327c2beaaafd35ad9ea2d320075df25d302df94721e4ce634ef096a230788e295422a8f1cd4474dd93c7c36206072212cd511d96cbcb85aa4ba7c5ef958d2a1c25600bb76afeaec5bea1a947df7262f9fb8c62fbe93ca830ce91ad9c5ad6eb8dc6a03501c1e6dd0de26367190f728d522c19834da78c4d3afdaf48bd4b7d8d6368ba7ca2297f80a253e123f3a872b857038c90b8658fc1887198bc21696ec134cbc85de79efa7f7d099ff5e95472958598bf6cb0b0c046670b367b14ad0c558b8f212757f74ba52983678f07aa509c8b5011488f0167e1479edf9d2e550b893b9c9eed3d3387305b2f50d2886626647cd6bf9ff944e89c951a5d525988ca273d3343cd119d626dfab629efc46b72806947fda71b57eb5b264fbb46f692f5456dcd69f216e02e7c4a713a85f093726917da41b60ef34c690d971b99d7e422a8503f4ffcb063b2ae943e75ad174a720d7a9df687f5c9f79ac12c1d91ae9d432641e99cdfba9b65e7ee7eef8b9ab87255422d77e8546d9b864ba4e8dfb4f35aa89fd272e510a44054ba58b1c1a34072e3b41509d23dceda638cd5b907a8dac9481bbc5d640d5579a166345cdf6ead72c1b9f032fa61588354f0d84aa0fff779f98f6faf9bbb8961649daf8617eef572d24ce481dbae6d79587fca9cdb96048e9a0a9a21dd67a5fff25786c2e9eb1e1cc222838b6e85becc34dfc232ae95573bd85815973f81e08190b6ddd8ac35be72711ef52921339363e6664e58a161094ccbfabacba50ef548a86c6e0eaac5fc7231856cf5ef4eaf77414d99a091e5bd6541e14c4485bc0df6b199ecfaec19dd72c6ac6bc2bdea3a255d8dfb49158e92a29ddbeb5ccb2304091c0052fe52fdbf722eefdf6dc4a85aa729573f9d35bfd6ec71daef999536a244c8303383c22393076094649c9153d49c9496c1fc4e584739ab1c9a4f1751bdd1517986ba1b352072fac57a126f938ad4dc993110ec2b35ac5c420cf54223f5fe8fcc855e64264e7269c55d623cf8995f14795d339bc6240d7fcb8c9b92b4597548c49c65df255772b7bd8f8d22c66e5d21cbbf67087d125322a3c3ff86f93a9dafe7709262be384914b739fbd8419fc9bc46ccb5ae92a6c5fd149e94397ceef63ca6827446139b39fd272096b6ca8f794f2a1da5f5638e58d816e04fce55dbc2ec6c32aaab68df72ba47b16a012665c40e28e18f5c1507000e8d2bf0ebbe136c7f3142f66207105f9844eaae3ba300c55b0d7a844e4477df640a3aab029a605d73e3179d343e791586d5082c79fd8bdea6802e3d7e9e58d03b2d75daa6be97a6219279fdf4faf7725b32263686132160d033a5dfcd098a5aa02b0ee3c412adf9f70e9497bf2e43725a76f34cc144e7dd3e0f053451ffce04fd0088a7420dd7b27071e83ad9922f72ebfc0bcbaba9bbf9c9f8850f00404817001d55bb3e2921a46c48657ffd4f2d729008ae720fcecbe3caf2f81862c7c84c3046e7b4c437cc65d471f835c8c6217220183300bc6108f4c240e348fac6ab2854abf79e5a878f80900f1885b05b40722d72a3a8d18781552845ec882e623cf643066715f17a318f0ff220380fd8147229aa30bfb82d31915b3eccdfad64f0cfc6d5f85f63314d15afdb38cfa9be236495cd04f923fd823b374dc5eac5fd120ab84a931181adfc52721ac5f6eb1c4743593a308c3802f5ae4605f8e4b430d458b2ae00a2d9fe07278445d4f8302d1572acfd09735b0fbee1794bdf17f95ad1a605e72723dacb64b158f65c6da8abe87227f930f63f22ee75e218583da39403ad8c704f038a8c4e2d66210df3514b5272e944224ab7daf625aa1035e4014d81bbf73a4b590b5fe8b820e0df5cbf74bc72f8ea2f4bd5d6bcf812a7188446b078da4c9ff6f227c99f2ca86c3b1db9250c723fe58f8815ef4ce67d4b1834628dd3133a0f9490f78a209389fae7661dcc0305d7fee4312d0cac30da7a5d3943acd69e03400c9fded87ce691dec03913271b72577eae7db601c433ef98aa5a00bfad6b436124bc940624534a5d64796a3e8f727a9c6b8b37efeeadd1010566098539d705476ed529ac1675a9250088c87a46726b02ef1402e59dbab0d71905cdad23ced63eee48dd26e0e52a9d04f9c6ac4a1632b3876a795644b1eab4566b5ed3bb5a36611bda71d6e775646c318ddb0ae167299b32908768d97aaded6b2545cbb89461f01d6d43bc093016e43782dcc27040bf1a7d0de52064691c573ad882c874ebc9132481ec3a5286d70716f066ff243050cafd104a190ba1b66b8eff1a1bf6871a9bcaa11baf8c5558dc17163555cc721362ab43719ea38f1612f434f11be17c72c52775683905ad0bd40fbc39006a5d65545344fee6c33e52753eab432ad6acdfe2b8b586b6fead83055229fdf2f34a146303df017bb9c02b8d93822f6f83e676c0738f8070e5845c9f53a0e0b70f72d18aa2950c34b12ccc9ec134c4acebd37b123e985b88c6d8ae448c64455f08725edfe5e587eb7acbe911e7a35d148720c988d52685f0b437d5b2b4b641d65d078b6589267573dd420a0ebf4c079a0d3857bcb1cefddac96150ca579a6b47937227e2df2d305f4bd110d032a00aa43f7ae68a57ae8efa3ec4eb5dbe8d4e138a7262b12719e51ee31262ca31d94bd33e39dcae047ab82fe866323d2f0064851460c92baa77c7233cab679d3d161ca69f43ff63f37024b1c55d3ad47dff73a569729ef8a5017b5334e14080f650b8ccac0fc528d9a56343ea606361c1faccc7944f998fb76fa4e809bba87fd5cb361bf26a12083fe8178d7c1d8e28a9512210f372047ca015eb1ab7998cdbd362329f39635e9df1496090ab95c5afe404fefd7a433021a61b5ea5413f6e93fee4948993f5308dfb4322e886982e1745df2b1ff83fa8b9052e44b598b2ccf7806278979222e6b897c04b074ec0cfebf51e0c8f4821fbdbf2da904f4cb0bf342040ad6a3e7aa02c93c5dffc355da6b4325fd37cd2570ea7e2c42577d231e3571c510772eeee974c4c967c8eb2d80033988b282302726bc1daf64ef17605e5a5284f27c454d86ff2f5b4a149c8cee37b5b32a1a92272715f9d50e7ad9e34c5f758f30002ec890575357b447efe950ce4cc42a502ba720dcb6029329ca1cb26804917e183ff319eb01a41beead4b539327217c1beae6076672fe51ff055e2eb8fa9307d88554af409f97187354aae77c9b002081df4728d6f453ba026ab7575c7c4ba6821b97999c55f10fb72fd22ee1cf9fab272886bc8ed64908c9d526eb3a1b5528baad65975e9dae9ed11d51269400b2b2c66c772eaa5c7596c3a7ee51afa958056e0a5041e72b3282d580277c32d143383936f1240c090606ade38495057c97f446fd6b9c5413680405045bb2c7de6a6ddfc127216eb988252378f7af764fa12c52c96f62ece79543a912367fecdf379e81c4072123fffeb2d34e241160d05c99e6b6387c87b44bd8b45aea2d50a2891d5d79472ca6cab70721b574e71f2aee9e993d168cddb3f99bc76137d9a65814478549b72849f34a59e86404552b673a86f50aa29d4ea247b1e74c5f7d046a6cd5a268f729d3eccf7bca2c3f69036687e187fdbf0fbbd9bf28d7e47cd22e1748b7754c8722a77882dfac6444dd3c12b943e87e17f94544571924803ed1eab73d16dba7f727f9308afe4f99f2460dd72488f232e36fc3badaf5410ad910aadc1a7fc44f5094ff83db1d44dfa8f0ed48cfbb0208255159108e4e4d28eb5058d0db6553ee272709b836e73e892a61e4ea0766cd7bc99206e33f54f2ea847390e47b3c395523c4e93767ffa05f5550b3e3671f216d3bf4db99584ef934a34049241b4cd011608c2cb905b3fc0a4948219c014f6ee2eae3b513a6401a3631288bfe1950b68286a4be7d39709a93c642a74dbd20dbcd0be1f8a124b9b7fcbf88bacf2bc5dbe28723990c2a5674be28feb6e90cef519a3eba5cf187884797f8fa43960de95093e72f3455d01e65ce227a416b9d8632696d97b32e4bb09d0b8580699816e4403aa6c18baa2944b5728aeff42b94804172c915fbc268b80caeb718c0afa7c0a4b3a5e6e09ad9e195d10edaf1225f1213b38c7d13dd65e521913995f84f849202aaa72ddb128f885323f2d80853f5028bcafb236f53e86058ba6658f7b9921fe63466c3c7accc3fbbb6ffe5d7ea209e8b76118b0826434822d506a66c894721158d943832920ad32fe0c86953247fbc50ed364b3759da8f44c68115b2619d85dd4954c6f02c5e3f45e2806d0499f12ff47a7666d30d734f46eb4a3b937b8183d74927238e84427f57c5ede7838617be243beaae9924f773cbdb1eec74b185e70fde93f5986a54f503a1b57081062297565c30fb05493352ba45ff7fc4e23bcc77f0672574d452cc07ed805f4a0905000ff3b6d4228df59c2010fc339aaba53f2bd6672214a57e4749bd7ff1dbbda8f2dd4be36c610432da8b3a60c8577b818f8e2b27278c4049d067466e61933b60b0d2efba5553d92038ef51574e7ebefb79d8aa87231d613c722bcd9122750d2a600a15a7a0cb0ed775c75277d45a405302f53244e6e587380876ead10b67dbb0c5da7691a31ee7f61f6d34e7b24d65a7ed7e276266859ce60fed250ed899f386c988fd2cd882e6e4b0f8daf4579cb79afd538a26c5cdea693becf82b8cdb09e123e5fc4798c3a461ef3133f47f9e1ba5fe854ac72ca280d836c41c697c71a689d48765e8bbe214dcad2c291e8c4c2e82b529ffe7291422d12605c939021601b6e078d82a858adb2e4f7ffa184139fe9899df67b72017477b0a344652b462eb4cf6636b4a924aec675624340f11fd31f41448f3d72ced3ce27f8fb9ebe6eef77c2b0f0f998b78d10877d576d6a611559b970b84369216fc9d95e49655a2d570d06ff842504d4fbd576595ae68955311fccae827465b9f50b08788752951c84bb1d23e39cae9cb2be2af0ab0cd96b86a2a4870daa40cb6bb13f702cbd831a4820e8f2409ccf2dd4ec0e3cd9ad924256e87469f8b572032262fd291bd48fe58e46b4a00dfc41d9d4e3c6cce24cf80cb1a066229445050f8b2f7e89052977bb560845dd3bf868542811184323285fddd57f460ad4737250213a4a1065b76a64a59d5acbffcf10daaa0db90452dc99dc10e33de85af36f9c953e5bb01133ca6a3835c52c187770d24df58ec88c598537e754e8eabbc27270da74c8776c257ffc8f51084005998f026bdc71bdeb2a6b77ed734d0109915095e2a434dfd941f50157fb7c0c797522a9478a9f519691226f07597b41143f2a886948c5b63a00e8d5d476faf3c1de374419a4a2ee09b4fc79cf7971baec5672705716f02ce11b220d31639ccc86dcff73aa74f138c5959a770a1ffb81936d720472a4d82d7d381ab4d84dc296d374bce1f7ae2b3c388a8efea3797c52f73872b2556fade697d067da89e0a7b796118866fa5de2a49f082ea10842badf54c82a33257e952bb5bc26953d660dec63269449c3dd4ae95c38e26bf57e69b761bc7287800eb5de08c5c6992e6da42104c983cf56c47181932feaf777e6d091f03c720902d929a2055714c0be3bad67312d9e09c397412540f69cb989cb200bad2672fd0bbdb574dfeeb8b6565748217ef94ebdabed05de1461e6f1162e719d839e7291228a51283942b9489280ad4f2c1d62ed3d14dacedd8c4c77cd023501df1b1aae7b4161440ecb18f880a5d22c38ebbdea6720a2183ef1ffa43cfcb6602ae4015eac073d1f1a5229fbe70401d598554dd446b8059af72819d4f4d36385ee08604e3a354d4f88db259bc2a6734bb962657fa491134e5dbb8761de7364df0d4864ebfb38ee1a784bf5d7a81054745552d706dede47462922f798113afe21de676a8938e6428c06cd981e5f5fa068980aba3f66f710948e5b9b6af37efbe6f6e872f8f59ee8393d686fb7b2a78e2090ad7c6de9d99afd3e12777b08f83a48309172c113d82b3272b59f6245f4d73000841a06483abf05a66bd3053194e149222c3f7ca11a514ead98773ac956fcd650f8f29b5426f724bdb3b0b4a013d0a34daf26b66540a73b14d6f31c56e767b1213780c536c42b1cc8c59aeefb48ab8e7cf626f1996bae165dccd8428522a7c85b1cdb6312add7f39dacd974d2094374f5213f55c1ed8c913b856b5e8e4016256f7cb0ff8c1a696e96fdd8a45a89cca5743f72400949536f27437adb6078271583e8033efbde52f667c241768546a6e68bfb5e40e23e0a3251bee7cbed9cbe3bfa9e86f025f900ec446cbd7a2c71cfb92187728908d6098960aeda385d501b4d0528235a269d5d5e61cce5f763b43b9028ff720ce3173e029d885bf7132414ac94d8b096b2034c3a2efd5b5878b9b321abc27269986f82a83d1277416bf24b9950ef9bcd32e61b0add7264f0f6ae74d9a7df72da04f619224be3cfabb677a71d6f9627f9fedd04f2b4931b1e1fc11292dfd372bbae11656f028e306c373b8a3216761c73407277dd081a2fb1939841eeaa7b7298a031da2a49893745dbcca5046e4c163bc0582ce11336895cea16a03d646c39cdfd32b0131de125c41aea6d1aaaaa9ace0bbac37fe6ad2fc9b49970baa55c188cbb24df201a13398cf75f71644e59383c8f02c458c0f9473227aa28ecd71c726aa7d172026c09fd3608fcf60ae0c3f900bc7f0e706801e181997dabaad7580077be804754ac9a8eb62dda90d2429d94d7be29550f586a9e43f172cf39ff2a727efc1f557a3cdc8d03c13431f726bb566e0b79b5c02024b73db372a93b6b862e3d4043c7e13147ea14a7d34c3330567308d04a3c0097feeebc1aaf73f57e4d033661bb55cbcceaccbb0f5fbdb8da7c7c0662e9b519303e8e3f89522d795c59728794e154c57a36039e93e717ce4b49303bd6e6ab2b894ee4462879982d688e724196f058250539895369c3f3afe56bfe6bbb1e55bce5fff28dde448b3f0f2e72a94cae4d4e04548b099c3271168038aef59b370e3f1c8aef92111a2d84f3826804b31b93b01145c18a509b8540e188dc4d6d5f3cc27d19d8a97ec2b74e35ec725b6371b06c50e5f083d81e6d6ce83bdd98f9952dce79a45829233d9b92738a7245ab1ea5b760b130199443d98fb4ff18bb5819bb90783aff8f5e032f350bfa70108c3878b7767812d8e720385276c866d6de48b7d6a86ebd8d75d8cdbd12af726badb202ede4a02a493f1b4b55b60bce883bba5b6cdf9e444209fd3e6c03db727a32576962d56a4c9e0a2efd85f0e5c4d32035fb9f1a02b5c9af321c6b2a181cb3dcada8d8ed2e035afa13f3ab825a2fdc917244759dca161b82d2ef0c553f72258d824bd3e66f656adc91809ed75625a21c1bfc965d03765d8f6a2e1314111fdf60b504f90a368b6818485cadc506fd2e6585b80f2d175930bb34a3ced3ae7292dc7303c22eeb7c12c6945ecea2cbce8fe06dc51b7e5ced8ca9e160d8bb7b0af8e51201964dd0aa07eb5b08f724d790ae167aee848a697b7f88482eb57a6d595be6e57dd770590736c59458a5d54a045a9515ad4645500592ea06fe3008a92c5e3ca1120d59b0c330364533f0689ec05ede3e5fb5d5695f30cb84459e6af0720c0a571a7f1ba934379b68ac919846092771b4f37d75efa1c22538094c1eba7223d683cd14993e790e48b0a426e715cdc9458c5626cd85be9fd3e1c88f94b9425108368eb704e1b7c66cabf0ffee87f8e0349151206b516083a5ff9fc0afb31a00a7c1a860368fe8df52057ad428c2202e91adc004da5c08c782cdab22cd9610b6ba802e2f720774043b659acbe3daf5570cca9941445caa507bdb8e5237232cecb71118c7c202ace067d09bae1edccbdf74f348d115b48abd86b856b7dad372a5511a9197ebe6689267d880525ade97e3edaf2fcfe6dc51225db869b00701647d14670def355a5ded39433b3c839ab02f298b29048205d4162f41de4520ca72a5e66942707e8618fdf5c2f673d6731700e873b2a561e1359cee57765215bc7216958482df5cec036d88cd4841c2301b895c7812408e12f6f7a2f21b7e0b2b33fdc22480d708f3d10e90478354738128ed56a9f7f9d80dfb7d2b55591442ea72324519741dfeea86b4b2cfdb3159f94b65a8794afafb748a07ad86288f878450dc3e86ea0dedaaf362c12daca4cf4abbe6e05f2b5f34650e437a958427749a5a89ce5175b77c6cf7b7f7988bf2dc1577ff1b938cacf461dcaf81654b512ffe72e818f87b599ae4af6c8857a97542018cecd5a218b4e91f7dfc6f401c7c13272ac4a46fa555878e8f5fdbf6117270994a20d5b98d5f389582c01797a15982e972b1b0593d29aea158a2c8dc62754ed959940e4592aec4fb9387fa543a3f51d272ef6908200adca49019089bf6b979b0c991f34ad9c10e93306dd14678c1379400a725e5358620715adbe9e3c992e69a283d843be1faa7fb573ddfcf5e0302b27238950533199d53fe576bbabb7c6cf9cfdbc65dd54364a732b2d393de1aca0d725ad1ddfd54a827cbb5549f5d609f99c091bd548d03513fb4568f484fb3e03b7225ce1fe975a2fe05ea08da7ae42aac4224ee191926f881d82545813ce554be6311a0b166f7f679180c7c52b5b6d1bd99b49cc1049dadfe336d26c3c499d931723aedf22161917c8549f5ac021bb3d87f309ea4e13c2d511b669a135a3ed0ec721f8abfd10a3e9bcbc31ad146593929fd2425d74b4524ff3619f43bc8f483037206bb1d308c6db4f7ff6ade2e7631a8647024a806d7c00409ea1bb3098de6430aa5038e87286ac4c741f54acea1495f3b9a2059322069c15880179d5f48d6ad52570aacdcbac8691b3be58af017553c986366debebe7aaf60fe500e4928a3a8120841990774ec66f765b95214a982ba30b608383375c3e62bbc1313add9b817394f5f3e5d73bd3b9da8e299d30a5cc7ea92da7111777c0ec190d9dab30f1bda43995351502c05b436ab0c894e1a060f7ee744296b026ad419ee253dcc8e08d538827da08276d41e80d816e4a3d82c9f684b49972a763bc425e370e4c3b7de6706d0a4ea22e0c97f60c763bf29e168e2a4a29e726db793cde96065e4e09eb7c446ec9be7e80cc60a595fd7ade30dc6211bc9724fa46e6f9f8055b698c4387b2c722ed1f3bb680e610dcf7ef0a2c49bf32a8599a4c399d264f9871a3d374198b005588ef0fd2cdc347a6f2da32e7e4fe3a0d825e5492514c241df7fe0476b8c90727dcd958b20974e5f7d1ab484d719c79932797a438decdb1fc17684834f5475569c7635a2f64ba1cf94b288c405f5128c4287371df970c660df083ebcb6eb504bf54fbe9ec633cb0e83c9eb382368ec5a81bb10dcaa7ef97d9ec548e0192b863665f467d3be5384d8264654ee1ce2bf457993ed9d5b757eacfd9960f585f78f72f20bef7ed311c46d44fe96c9d0227bdf4140cd1f729d46e671b5462a97f0cd729e37620c0a26272ad38472597fcfa4621d94b96a501d3e6148ad2b650bbeba5c18caf8a180c5aefb248e41e5094cceff09b9095654c51b9655ba52d73f1e9772521f516d419ca9e905b078625080d379d267d9deeaeeaeac336d4811821ba05530b38210c32b451fad0b4c83b634cfddfc88190a7e3f04508b4ca717a09c8072ede4faf17574c97a72ef5092765f5be0754a133ab3f5bd7af29d4af887981f721404303135fbcf38aa8ff9a58e1f693c3bf4bac082d71fbc1820d2b9913dc96e236fcfa2bee0e5ddb12313dc47d661e6145f510ed12f0ddfce19727407d39572b714b19979bdea5905ffb9b7efadb0225d0f6053eb223ad3d021d6173de7522206d8e2e0081db2b5da50f984e6d6a35676bbf59fd6bbc42f321670faff2bcb72023c05c42d3c94d421a137cd1e161457bf934b17c544bbde4e058d2bc2c14672bff4535bc5a0636cf985a7fba8a81705955852177800ab965361c9545c97c172e6d616abcf0073512a63d9d3d24073350126f75b580a50c0f1d10ec21d5e2c72faaae6fd4b80ac75d9bcc247c539a3c30fe65270e7236723a79fcba7245367675eb1291180505a06461c0305860856b24f78001b397a23f4ae4abe0b0cbf8707619523c00cb57bd467d28b23fbc57abc14b2256ef3c041077de5a5d38d844325deb80e8df40dbb6e873ef8de41976fd95b9818eff4cf19c2c48a3ce444cb8b72438dd749625182e604db1061f79a3ef8423a28f5459bd557493abf5bd42c0a2edb1bc1a756f9aab895149c287c534e3e6c47052c43864783c2f1a8ac76981172c7cafcdbc6bb8b846095b0a0787ffb345202eff0743aff22e355954c9e5d1758932bea0a0190a9b265f1ecca8eb10996bbc27c83e2d94d426877e59f5768707233dcd4b9d24ea65b45c16d406a805e20ea9821343573f243aab3b8114c645d72c91dee83ccfd0bf0b7d41d40afbd8705df99247dbf9dbf408df4e14d0e21ae2fbedf20f4ae216840220427a7e032a7add689e44ed8aae57900d5ae170e795c723d1bc570df92d1565c61eea7401bd41c443a0c46fd445951b2f078e3707d6d7241e9f853c53face92bb659a9c8bad5f9d31d5be0687bb2f8021eb4863954d063cb2af4e226b321d80c36f491c59952f095fc803690bd33a49004a40763104172d175815bbcc44e0f00a0612424d5e9e9ba4d168cde29b2c46295d6d8002a5e729f8416f0af4e99062b3990c87c35aa107c6a6d620f4427b6236b4b5d8b315572381f6f6b7b14852fdac981cd404b44cc4b0a67fc58663e6a7d6a6636a57f8c558fc2be9c095da45137cbde90f918bd6e5dbaae367667612dc05506ece4681f01cf0fb36cc8171805eaa10548e52e446453aa8850a0bfcb9ef92a833ac024e65e1b9ebb2292f552593242d6aff5e2caba180fb66a836d69d81a4764ddafd72f726b807c6db44ef8741c1fcc1ccef75e2b90d5d02ee89b812feb971087d8160f72397f7cf4c8333ac14fa7dbd07d0cc89a6340860156594b1eff18707d98037a1f0925fbb64fdd9734a05288c51db3a1884e3481d76c62246190340e640267fb6afee24318526133be872a2cf2d928091859a3c4ef6377cc476bcdbe9b88700b48dfe3d13fc052fd8330e9914eba95f65c7ed2ca528fd0d8e8b809059ec6d99d72a4a8fdfb9b3dd7641a2ef73ca8ce856214cb509e0211def5018ac2daa50aba01900aaa66d2fbf8aac48fe21483e4e1cbe74f0a74a06a68b74e1b711f54b1d86ab95a8217a4ab5fde0c423dfc77e7fc939311deee3bd03dda7c04f962821e4062fac1031726da7eda8e2e4bb0b1b52cac7df334a14d11de86e379c5184a5a973d54e7e527720f5f2207701a734a41d1bf1f41615bb9e78a93c5eba4a6cda16472237c4a1b25370e8900e289108a96f31ce28267eda1f5c4daf2cf5a0293a3eb107b82944754639a865409cd87ba05cc134fdee03b5464badc0535b5f015a0e104aafe16b294c16d3b89b3e31ea051bcbc531a2e45d5a1535e044ef257c1bd74724293ef7f560bb4f05c0e3e4e0a9f41a845237ad73e59d155f8348a9f1c7222721d37417a671f58760234ba6c8e05ed3fe8d23ba97a9cc4e9d6e5539e2a076e72f84bafc8e9cd2104b7241043c066804d66dbbb2763310008f1aec0255e141367b7936379b7c07378c9b315cbf5af34edfd96ca2c4beec49ddec4a42b2b743172780c2e98c9e9fcdfe56c0e6bfb36e5090934451fab4fb037fb3d2a6a791c915e8f1565e054c5a80c152df1e71907f2c62ea0e177948368ecd9d134c2072a35722099e378be18e8900586663ee5132fc8050acb70b4bf2de4c9cce3951125bb720f818cf6bbda902e6a425381ae96a82447691d082672766467e0ceef137b163ab38017c35b23e1d2b092da53605a94d4142c3bb1b4075580287c6f1f26c495724aa3d3d5dfa829fb77efde46184b276c3ec1be745a20c7844fbc70dc5c754572f65329dbdf866416c4c2fb445aa79cb0c012dcab6a8b296384d1a978e2355772c78b0d17d42d4340a6a2f8159045149f1e9159dfad89bd1bd08a4b312377942bd7f8926190994eb86c736864c400acfcba115ce178232a2fcc23c9c5447b9572a8ee54b811b9a3a65daa8356a0768ac94c053c745d5341ab60cf13639121957245041c41f268139d3fd03bc89c80eff7c3c0cc8ff65fbaabd8fe3e4a485b91724a69556e15e3369b34c29c59d0ec994508c50c1082a9f04b9b0f8a00212dbf11c968ffaa2bf438557a44cfbab5e14bd208b082e808fb8fa32821705baed78372ddb2e3100468432c14591782687fd27bf9a07353cd8bf45f0a75294a67ee9c5317b9a6dceae82079a8071ce5c795a5cf668e7a7082c5ea703aac9222f2ae61724136e7705d94ca2dfd1942dcf7b0ad99ccde70e449fd98f77fcf49da627f7f5109395b829b979231c95ef14de9a40986404db4a55e7ea6c37e3ebe7c8aadf272eb0746b476419866327312cfbffba0c765982d461d1d8eb0519929582e6e8f7298f60c07789d90aef2852ae7de612cf7eb3d729b8bffa001eef4fbacf35d1c72fb146b27a0bcf165f78b3ce6db428fddeafce10319c038af90be6b55ddc5dc72a444712b4ffb6a753e12b0891f7690c638624fcc47a5dddce7de7471cfc08672e1aab9f2897f7d9013b633e15eafc98e058e022867b7ce23a4563d7597845f72e7f331e942bd99b838e24d8acf2197aa42ef36e27934a320948f80c0ed72e8363f4e4dba15ebb2f5e84709e1c2441477390fabdb174a7ff183873b79a0bad84e5b579589aaefb170f22cea28ba874fac3598704c585f4a2782d0bef50815fd724eb1cf128fcef4203a966631eb67d363a718b7db6bfeba8a0e9f58eec3e1c97268926b57f0ea4f0e3c5326f6cac3c412611511edf3d82e4fd94e0e32d959db72e9cb1b7134b4f32886c7555c6c2487c1598ae675775f8ba501002e1846d7e472b02f74879a2c248e3ed3c6d6877957daad991cdc1925440446967adcb44d7663fd74f22534e6a32433532caf11ea90134f6e5b185740654690c2318f37e2ff725730975472c7688d9d589ddeab5d806b2b22158bc2e621ef16cee68074523c7225f44ba4186472ebe7e5b7da99af11caa390bbfe380960737d47cf9cc1ef07613eb22e21370976b76845da2a805113eb79636800df37a61de1b0a7a6e28e7e721dd4aea427faab17155cbc8f0050b44825b41397d1e4bc1dca6a5465d9365f7216919901e34c45f8bb75ba313ec3ced3d0a64b5cbfdd9f85ff3aca66d0367c54e428f41c67d94fb687d30a312a19a6b35246485baa330701064a6f4428a48213ec69ad30017d09ed4f068d737ab5c0390f9e59c377ee6db3eada3c5dd4eba60b5821f6da98fd9fc8031b5a598c16a69802b4db7b506464b4134b682705723c72d56f94a9540dda971f0c8ce0be625dad7db961f5af0fc7bf996b6a50d56aed00e01e0b55549048eb9210d0789b5a4f55a4116ae3410d26483296b27c298234720b9414d33dd2d27252c10437d2caa75c190180c85760689af1813ef3c79bed0f903653f11f6717666aaf63ead4a40f94499b27bec6230382d08090dd82392c72930da4f84c55776da3718ac3daf168d1e902e167efccc1865bdfdedb36ce3b721cbe44c6b3ed634725544e3c6afb9a3534c9f51e0199b905b9e9c25c02a5544043db925a1bf792e76324c98acd16a6a0a11edd62d359f22a2afc009aeeb9b76ba8e443888791e76bc0a8e14e4bbbebcbc0df76454b6262e4f9f7e1a52a2b5972a20812df21ade17959393dbc21993bc73c098bf3bf6f010a56d4482029470e26a6c948c5fc3e03b5a698960d1dc80576dcd65cb65509af2fb5177d8eb9a29c72019e420eea9b28dbb23d0505430232038cd8f53a90f77d08616cddda92f0bf1871aa33f041b2cb0ded8f5aa2b7a5f170535bc9a20534663b7c3e902f3c823641248de242a62f314b9786583565e5e94f061d268abb650de1446cc00266d9dc72dad24a35938cb98c56df66fb628727a748d72a54b9422b69da315dcb39401b05342865a23502e74f7f425054bc1ad6fe9faa28d11da8ead950ec3bf105fe554c2bf2054d33978d89467c9ec5ae288c14aeb23d1252ce1f5d1c27b9f3ce69584b591085f38228ea59a8cc5ffa59b550fd611d21cc959e0b515bb006188b8eca7215ee7dd88bd05e006c1ca2fbdd95f47a894450ace72ab8bb7e7af5d7aaf915721a23e7858443d91c1c97e9878a4a20f1002d1f136d61410e3ce3a819eecc473cc2922b922a6cea2a7ba3c57cc7c946b7c047e26c5a4c215c8311804189551b72c1c3213bd79d1a75ce24b728a3d3408c1de6193d6b0194c798e10c67d734b77200a629a1be3f817e66e9da5e46ca25773968e3d92ace05dfcc0cf584f3073c72c36006622d76dec00e438f6d6a3f4509066953c8b861721916d03629371939721f1fd796e71525396832a109c1f8a8ad5f5eca87f74bf4ff2aae2577b64ebc72923e259e47fcd516e23c7dec1cc45d579bfe9558beba39fb7923b5e84a2b26721e377ee648c6a23ac4d32ab3805d585236601eaaad9109369279b6803ca79a72ae4a1080d4f86beec726fb2bbf6c95e61536d8d2366b05b9a20eb610889b4c72f00effa665269123cd4328807c58859aa9ded62388b1d69c9ce3c032911079720dddbd1c27474a556d2a8e7c814ce59b5980994ca1f8fe8d1ba295c7f690cf72da8949412797b18c14f15e5e719e24d54662f03b896bb777322cfb54188c2e7216e15d25b980f408ed17d32a8c9a9d62566fddc33440b154f055d2b4fd6d4572407153b6c30cadb9ff890f72b8d041601b04c91617aee1cad8d608996f0124724a00fc242eaa8827ece679fee99c5189aa40b8cef8e860ffc3670e62c1f7b25b49eb3c46f2358d7a63222e144e2f9252254b252af23d0bd31a8247540b6afa72aa89cab366f201d57ca44bc098645b40b5dc104cbb5eb07cf29f9e2045cf247298b36ef4689820bfbfa88f5bc861b205227dc97328a8e41c9cd606d125f7a9017d895fc5e5b8636ce5e09e5f71e1968eb29b2bb4bdd509dbc8baee766fcbae63ec7513bdad76c73659dfd4f951b182f20ae98834764317e61edcdf28b0523b726bed482d06311a8c84c523dee9ebefd30b0b4a1fc7bc3b6246afca519a67872ecd7a656ad3685478f6b43d19062f441889acbd5d1bd5ca8a4f1874a9407c756c28a6fc99a3580d2b05123fccd8ce83c18609b9985f7ea099df9ba03b8159137247735cd2821c0ecc2d8363edc17caed65bab56a61608237c28ea3454cf96613385472bf818d865fd8a59f0f18991571ffa367249cd4b72ec01bc0ebdba78972345f06416601154d06d6868a9957d201868d7b85bb045b10204cbcaf513eaa072a958567be7a865abfc8a90b7a231b64b26697c4cf4e9bf514ba70257672835727afa5e86154f89e61325f78ab837536ecade374d1e6d01834e819208901a2f729a55a7e6f6f7ba181f488012b54c5a919fda49742e10f72067712a9b7a6a3072d96cb8539ed327e65a6f56d9a1baa2e158ddb4e85f727c975eaa6111171e9a06a41a0010c0ef1bf33f082fb1a599c56977c0a02dcd176b6294435efaf5c731723678a1e4bcff6caf6e45a3580caf13c4d6e8b04dc2e8ca8c656d35b35e714e72aa95f2a9519b89909a4975848525772d7c4426cf65b00dacfbc4d87b25f18c725de3f3cc24080ff53f67bc75e037ce3a3f359fe77c7330ef63425579270300081286d03e8ffa482bfee560a0072dc44820d127e3dd1ca0aa2f919745498f172ef53afc3fb3669d1f04bb5f1c4899606618b16b47e85e71b120a7c2aed2da427225f7614ff36834f8802d16cc4867ce772440006397535b4b0ec623d80447391c423acf0a8bb693f6dae67cd4f1127e316cfe87d9a5afe1fbff5961a9f5faf95c436b12c87a50ca9b092c110a6c2306b70cb72fd188be1afc6243ce0ac0d69b725bea46a2be48a888a387ed7f6a14932c76f67b65d7991d6fee10d7afa36ad118a10018268633fc4b20522aedab05c358a3e33bdfde2bb4dbba1b0ad70a781b72b1cb0930b8aa5bd32746fbaec5b885b87033640b29441d24ac6782cd8c400405d5dca354853bef422e25f92b28d7d3804494dacad50742c15e5f02ef959e98727a88328551c434e8200abd4557ca8ceaef8bf07f248681004ed33db77d2136720db31aea35eac182e855023ffb060b1a99004c6663bcfc79f37194f2fed0d049c49011e592b692081366b66706d62a1eaf65809bd3921c8b7e52a50b695af4729228b647725bdd03bdb1db595db6006aa85a15fd966a80b84afe33052c60666aa22b6ff12e1ea9a050cbd9644c1d0524e1d7f4d26bbf27016dba999956dc87729af2e8522160b195ff52fc9ab8b4a51e68288bf4768127616ea493e91687301291bde22e59878bf1aebbd7f1ef3303fedabb4ed7f4e2e35a7cb828322bf77c722e6283775178f629daa9efcb4795fc66148afb9ee2a0389e98908853e19dfe7218110b71d7172e4cb4711b2273da7c95e77aa759b1d836c1cac2416b0be2ac72db4105d389128e0f13e69be42415f9e81425f7d68c31b0a528f9665d0bef8731d748a0d28e773219df0f1459d7f57ee5fb8c795d503a9cc6ec1f745f51a8737223d28b6edf7eba468ffd9a63045f5f4c495210d4f7cdef6a94f9202528832672aa4346ecee03903cb590ec32e5c4a4f22d0b0275e700c8ac6737d5f9c7bd6f712430271b59817e99e6523ef2626013864101ac76a2b8c34dfd071e3710222072ddea70076923b4fae3480e34c061faa88a9dffd9f7c2914f5424d2075e1f4e726807f6dcd45c793c50b39dc957008f75eb331b38852c79bce90bec9f678206352e1c8e69851a9c32595f6d1bdd1c7936cd3cbb423972917e2b82beeb2ea0f972f31efbfad587c707f9b52008c7c9f415e37d6cb8cc8f66d73935a876e7afd972d3831644e1e0601a52a68541b2738c0d97193c91613fcca57bc677ecca5b63315c8f6032b518a80f1f7aa86cf6682ce2cab2ab3315d54bcfebc92759a8bd887282b3d1efd6048267ed1138176a8270c8b2564f7bb87cd6759845d12a5514de727bd6499019f91d5fdc20a4ad24ee624f385c10aa38261f3b156c001f92611272aaed72e7904d8bdc31e87b3e3c7813a4dd60ea896fd57baf5c76fdaf91147e4707bc646887453011a028cafc65ee688b38a519a934df621b5f03f78dfdc8495c95c3530d38d45f6912c84f50428de160ba101f7fed7642806139711c28700272bdc61b4abcc24b49e7a591619bfdd76e2b717f0135c82b58e3975059cf626b724ee570aa11e0c1938d648e9e60f2ddce2a3d8a825e9f5fb12df7c5c011d69f72addc01791650b9cb049f932149e18aa575e0f82e2f0ab3050bfe361b36eb943cd33d5e85705935865c74fdd25548eef9f7938a4feba55c7672a9d9332fefc3723cad2c4c367ef9e5cf1206b3307aa89ac2c0d017884de4ae5a8c0f6609bffb5f1680408469b582079ed90e6676fb4b86f21cb31ae639d40ad4b49ba52ed34372045baa11748b05288d6d7b368420788a1cb3cee27ed82c5918a67f5b2add1324a826c7701a986ed9ebb8dbcbe06afe38967c4b50c5950c8d1b58d28293ad477245f032a9e6b6ae4e21b0b66207591afc87bdad02a934535f042d52458fb88a72851a6a2e6228cbef087c83df4815ee9934252ac41804881aaeb5f833b63be0724fb51af254bfe6810140a8ba553a1ca033308d3d4f7414c5f7a7733691e12d018a09e722db903a854e04c97a52ba44fb3c2468d22893af8629a16a6b1bb7e872146dd76e6320b81facca5b3fd25343eb99f3896c5f37146fb6b055fe51412d1a8e98f1613956f72f3322f31144557f6b4798def72dd066d37f14b7fa6657f21e4b4a6e9316dac7e629a7d9c123b71530fa9182507ae63c1e5f345881c9caf55d87a387886c7e114e4ccd10c6125ef2e2db7524848c8b1aef75a8f14126137d0a169e87b82f222d64886c7029761804d10cbd8884949f6618b3b6901e4fc0202d8edfd9036847e6fda102235e8691029a5ad903d2df4b4fca86dee618b0b5787269953ed9c796f4ab1fdc0082323c6eb81e84e1b64bd8c3afc206b3836d102772401f7d474f270509f385b39c0cd536c258a7094526a0e671d3e322a0b91ddc72f7ba010b068594dbb3ac56ee45c66b35c70237ef3ba160c45411d778399d6672f699a39e6ca9ae077b2cde6fb9012f3723d16a30ef63eb161aa8806615602b725946640323f76ea1226f77c1a33bb1c61175e63342ed3e6a6f77432fd2883d3edf01e6fa0c32eb4f83c8f6e57c1601f4f5d03a7ff8a0d84c89ad6411f270a8728ac36b88f255babe9f610d5e5435297d019d699f807b9229b5b17634ac549d09a18ab79706bf1041583ab5538647c8eeb8f35b0098bbe1f59b0d519dd478487261d79f4c3d52da7946c19851ff81917ec91abce9de4a275c2624f7105ae7202bc31dfdd15930b512fe6d93b3351cbf7c72e9df2555ec9650661f9ae5a09ce7599c1715953c30dfac3d68b64d46886625c410ea6dd542ff31b9595a070ce79c724a010c60509b69c9308aae43a803a44e00bbce484238072edd46b029faf899066f10278ea1b3648fde1b945237e304dc1f1b0fb70af2329a682c8d0e4e8c5072d2cfd5cd744766f486cf8c94d28d20f1c87665bf892350a47b9f5badbd74fb724f0b403ccf60e12a4ef8b2ee4540d7b524a29cca8fcc1bd73d5b9f2c65c4c00236a8d9a1f4320ba7ec824b1b9b00bf0739b4cc1765d16a78dfb8fecee3ef647224f63e8ddb112cccc5df2f4c63f1511ac9b0e392bbb3625103164665605f9a72e30b5e27d2707cfa2c4fc7040669e984a22908aa7c581d98bcd66f2f7f42c671d1cc303a7a298ee5a61bdb03b4d328ec1ad6ba67a833eb64acd3dd2f0920f77248663d4e177a8725a783c09b4b78489b84475387655b96bf4a1bef760697c572efeaf2946ac05d37e4c58ab875da81bb9cc7aa0395fff840bc4b66be3bd8a2725e7b9bf08ff1abe052b7271b670cc609c640a310757e092058801886b86da772fd7f99baf0223c61ec88ef526ddc1bb497d0a0191dc3c04cfdf2fa704401a67295eebe5625beadc4a82fafd9b8304fa928db9512fd6f2aadff74c87492e64303d5f6239a87653cab83adae1422cad635cc9fbfb6bebd4c52eab149c95b7a8172f2d42854080b7c00fc7548395ed1a8af54820d400e0f347576918e8b97bdbc19a25e021cac45c1bb1102016572e88443a75fc4278a7206afcf37d8a81532297284981541f3477a9e223b66e2b1d3eee5a2e2b1295a8f64a342a1698a011ee51ec717f68a51a5696dd6286bf6a159b178181d944d5e83710013f0f037eabb731e3b63ab54b8f7833cb45efce096b36d00e2bea6837a6211ebfbcc697948e594721294c73d2809e08b93206478911ecc1f334125b55ff8576b1dd918262adb4472f1a8722751f1b995e84843f75298e0884e6bf547985a48cf83856d87a83f4572f37ba46d205c92d26986244a95639725b42ad20895706838793e6969dc987c0dc9b18ea311d69698cad0974c2081402b9a8882af5f4d18e0b7f8b27584cd737287c9b581bfa7590169f1c000505dd11ba2bc28737da78ad93cf501a51039ac7240b58fbd30e8e6d704e8be95f594f0b6f59809f36603776a8c1b26d47dae8a72713c6352ebefd2c46535cf1c45437ca94841109f3b3cb33072ea06db1d52197299ef35723a21327b1112629bbe668002df260af6760d7d5c0ce54064635ea372ea50ef29345ff8ada1fa43d1d9876498566e4e1fc53a442df95f739cf69fc2726cd5be54a5566f4019bf69ee4306e61998279efe03a0d0f590c055b74d7bf3002896345f952b4b3a41ea1d2b7e7894c1d8106b543eeb7c7261a71b51c2a72571c5c173b0fef4e19e0ad5f05e406c371cda3bb33bdc29ea76267c0d7f6542ed44dcdb55a588f998bca6e3934f3cd4d99afb93b9970761959230a0eec175dba81271cc489c149770db7712a42e152c735dcb81bcb7feb442314f267c715d603b2ac24d9dbcba93699cba4729338df408dadffe4630a6e0b078e31a6e0e8fd1db7244c7c5c8f8188ae1c3e2295871719902cb261b0e04cded751b4df6ca272ebb72900b2cec16290e241137d882e8dbf9121a24dcbcbac73c69aeddd824dd36aa727c7115457484ec43d5bda00c29fc94f9d3ba1b00c796c46102b612fc28f6fc725f80b91ae7df6f7174c57b77ac39c5b3d4d9f623d7f74a4151cc6f726d68e372d1d4a89d4b9b8ca5ed19ba8a571e729ab0014aa97657dab8ead272f5d162be7223d50bbf03bb7e464f9c5f7e4dd30ccfdd6bcdaa0bf790b1bbc2a7f4ded46b60ec668a2244774ad9c11a43541a040e091b7e6c219cac36d300c952f5e1c116147049d1f8dadca6d03a26259afbbaa58fbe56b7ad9384f00443791d153df37b3813f7f4a3b17108788bade0dfb6e54fbf9471b68f504a8c0baa53cfafc4a2247279e1bb65501e0b4cd827059d5e7fedfabacf1789560e348c68adc9cf7842687223109df800a9f3e9224eaad5f5bfb921c506cc73d46d417dc4e316bfda43c2724b388e16b4b4efb41ea3f393007c4b344df9ea383bdc381a9da258063a1a2632e3d0e296108ec978ffaf83e8eba326dfa127b345a6e1dbf1cf16f0a18c1056723a6784ccf663ad6af0a81ff427d342b493ab898bc29bcfdf4dd4c06a8ae6ba72c0c60f62115894c4c1bd1109d485f562aea7908353b16b2b6a1afe9a7da7fc72359c68c61abf61c155a2546b58acdb409a4e1fa6f833dd8261c747af92925772af23f48e1224fb38f6a30393d507fc120f40bdb7b12ccb7b0ab11ed664d64a723b91f7de9c7811076a52f3431f54a70c716cbe161af4c3370587aaaead8a7272993bc6ba4db1624aa4ff890e87e13c6c92f8da6f73504749575e2e1cc4918672b638adbfc73a30369611d690d0b7b26b3a2d1082fa18c0fa5ad577ff3331327205557bb2b358c178e060621c17378154abefa959c48faf3e9b05c91fc5f4cf42869259be99f0e5ba76c1496ae3007ae4ba80b478d0229048ae98d7c708185d6d0eee9b19b32a4187a2a97d66826c8aa390ef8672ac90aac41ddd64c464b96466ca2f76a5f1f97afd963bc71197a4fd1cf7710a7b4b440ab122e9146c2087d01e64086683097aa7d77248639699b0cc08ea8ba837ba5d4db339f5fffe32181472cd93abf4f055534457d57db01b1e87af8bb093051338ff184c8b772f28fbb47216608e9b61dc1931540f21c1171537639f49db19f5448031f789c601b433187258c2e684e3b4987a7b8423ac9c7e18e13cf5e9d0ec7859bdb533edde0ec550242cbb933626b3ff6e0c54455e5e04efef63953f440411f87ceb92cc44d8213a721e857b3257c096469968363c585107aa4bb6536817048883a43a383a9c571f727269b516879294343d5b9e50874d3004af77f6a6782bff5e0175a24c21b9e5461bc7d4798b57375b2c3cff3aaecae39fecf505fa8c0c4e5a8554d14dedad4135a8c0cc4e2f70481552462ef03a28eda2b6a0d95cd1c606ef8d72598eede87172e1aa559c47c17f0a55eacce780ba19e1aa2b1c7b804c0cce498660741f02181fc79210860515196dcc4c49b9d9fd9b92385c776346841640560898c68e6a693b14884496b30c21de86c4d9928ffd6e343586319e3e86d3716c5c9cbc98b4d1724727fccba6dc2e28545a20a2d9731df3e593cced9121cb70e2a29dd3f771ed4bf4f920652a5d44da1f677b472c8b0aa2dbd1441ecdf7015005ed29c3833ef072814c8f2b52a1bdb75745a63b54ff62e551a0719c066017ba059c40c22205d9725b1aca80f87c6d00d5538a05aba25178a42e7dd38e76044f8204bf06b7c2ea720099d9f6704015e69a6328009ee258d840f1dd5543ba583ea7cf0efdfda91e725747c22e3783073e19844e43ef5eb4617a40d2733d563b71a5e3d0aff14f2972fbbf6d0046a07ce897c8ac2094be2cc20290bf7565ce6f38cc73200bbcf1ff722d8baab809206df7f7a22aa00840306c6d2c3aecb6a6cc69ce727ff8a9f7ae72817343ed98aeed89e2397b4515b0244ed95bf0440eae054d875a2bc9aece7372b63ab5adeda1a7a7000ff69a563f632748623112ec572f83c7fe0852a49a592dde3035ee3e502174dd9014e165e7f15716dc6f4cfdee702eb713852b31ca3d72a2d33b638382efde761c9c8f5d3b6af3429634f0ef16884a9a3a965c45d7ed59a6bede6b3d63b8fc2022e0b9cd6c96c586305768083b08222a35a962a2d1df72f1791f01f7540a213b6d54e0b38317d117fdf6d152132fac61ae13f5046c6609d4404635fdf564ba92e31f23556ab0d1b432ce3ddbb511e8d721a8d01a2306727c2b92e4660e0d5051becb04ac1b07f4b44280fa4981f9821a9d2fbbe0bd7672b3eb296e09281d54873971fc6ceead54b5eb8076475330f7ecf939c7b3771e47056925bfb2df2e26566e60f0f83a4dad9af776207a4648b38248fdf974c459724de32c0cdc5dfff639976f8b3271411fbf944e4599488e12ca29cf623688fa729991a5338067a5256396f303ab8c073168ecf6075695916663642dc77e9e35728cd9a0de1e35f12e5658f239efffa9d899a382fb1972626e2b55a10188753b725ac4a8e4c2111afdbd3fbf2487d3ed71efd9edbac78935833d2182ca8f21512cd278c600f28d4160926892ccb342528e2aa7ef0d6b807580763a80788d0c907226e6328e9c2483ea0e85ef9b34a3a926987fa1e7af75dd21593a92fe80d719725b611e1c3d74cd98455a56d9fade7ca20764b9cfb0571b1fe559091eb8b0f572351d469b67562d86da4db18e0980740cfe0ccf5cfb96786538e00088f5056d72246f8fd8b48e31cc02dae9bdc5c84e7f6c41bacd8bcc7b9c00ec9acbe1824236eb92c2d0c3e7c39e62af3fe1d0b9ec722d9ff149f0d76fa3a692895bfad9b26c8703b1a84e8573ef1eb882d09e471c97e7fd3c4979bb9e44de981bdcba47a5727b4631821e55ac39abc33ec9deb4d35a4221b53e2feda23c1debbb569fc7ec72399e497f3054a878f0559377a65c18b852ebddaf40ed3ca118ba7b79638bdc599e7a85ca8a68aea728112cd949996faeb15be1b1e62c86d6622b6a108de8e32588cb512fcb1d3752863fd5df1e625b90cec0f9edd4b659368b1a8163a7681372cdbb1285de7e845934f1af3759fa422c43d27a5341b29f4e2df3edbdfd3e6b7228beb93a80c6d51704f5165f8492a520a9cf1549560b60237d25b96cd3cb5772a564a0293f249d59cba67a1a8dcc1603c42cd458f5652a9ad5aab609a5ee9d729a7bd9e43f6df8669af7d0fbbc8341856eb6cc58bbfa77ab1f611d45ebc23d7261e018716dfda6df8e664b9f4f16b49323bab1f16c1e9c0e26cb9ce97022e21447bafae4f75a939719c86890a14178234d8892dea0d6a2ff314b873333aa0c72468148bf68ce7196b24fca099e594a3758ea3cd9a90dae705cb2bb72b717f6286dda3377d0d57f1250a2f1f4b24b4c50795d337ee59675ebefa924f0a9e52e72648d75ff2eaa5b9b1d028a4abaf9acbc80dbe313e7e31cd4e9eb220fa0261a0750c3078af4716baca97fda5ffca8b3ca713ea6664543796e0d7c228e6ed3a572777198067c1fdc1b9cd03354d95bd062d05e05c0be2755278185abe6808b9e7224dc75afa9e241732868439d175cb9cdb8005e1e460cbf29ab2ba7534b75fb721accabf471843e3c1952acf24509d139c7023f3319ec188dc465c17b03d1fd6eb1a08646b2e43e479fb892271ae6746fc46ae9c8e7cdb6a8db81cfe831ed307244aa47bf699579abb09f48e722b69a84b58f3f46cfcb323ec1e53caaf0690b2c407452eca8b5862bb7ebef6e0239f09b06d4bb63ccf88c955517b2ea8defb3727bfa506e23773890d8aa0a36b6e11103cd582414bd55d65b82242500ae24783e13261724d308327b45f39499d07947144a9888815e8f14f23f6428d1ef84714d43907167b1a1b6805b064ec5e7f608ac0a6906c0ad9b3ba1a976cfb399cde472af5ae001aefff40811eaee7dabc6dcae7f5431525082ecd7241fe626ad9089727cab89bca1d4a65af72bd5782f330048a772c3c03b9e837b392e46cec7b070728ade4e55b6812e63242c8582457af8244fb8d0746c4a53369255caba389fb67281d9d4f8384c98d83b3df67abadd0e5c5b32f9a4bef71b7cbf9b8342ef109d3592f76e4468543f76c4e673c18331ed532381819129552b0884094d04e45e717272bc3b63759cd3a14f9668c3eae0461c402c1d051a8d1d23567828ae90229272ab7df272878ca415573a487c1aaabc2b10b17e170f522da5e80e54dd343dc716bd8d65a369175ff83d69870876efb67ac9ff953fc9d55d2e2653e9637b5c4272258944c23bc266867e22896ddd0963aa6e4e56e46906cfa916f91072c82d2f547e108b0bdf3c0640a14eb21890cf5e83c8116ca3a4a9e301f55fcaa4722fcb17f8d786e7d2ad709519556aa06c250c0f910f7bff84dd0f99cd2d3c8a42b2dc05233417ee9641626dbae52f2b53192a3ee5aa060e0646af82979560c3470587723edff60af0bb03ef69565cfbca60b9d2d19005d45670d39df39ccb3208468c72011d033ddce52d341c1c0dadc2fac2619f3815691593c9542725efbf7b05b672f1d56fd798a5d98e317387a1da54be63b209e47704b51d557cc04628347dfc72c7eccefbd29794d4db36222cf1cf859c908d235550108b7a88ed045132f646684681dbfc36e4424c3b25af0c031bb3839f8b41eb957f22bb4088a90517e98e723b3940e97370f4adb3ceebdad3f0c542709fc3718f0a8302faef55785d66f8722f2d9fd6e2b1ce47539c1ffabf0552e2d4e87338a14347ff7a4e1caef3e4182de1dac42069bbdff360a75e912915646fc626e593c5c962035dfe74407bc9bf72fb8df7107871dda845f33bb7b73a57d37c69c8712697d5c53d5e3f9c9296d535969c6dfda669ec2afa5c0449358179648dd97ba9e9a02aa9f82152850a1b845c7df8b35e341b99ed613f65c95e882fb978171b426d51453ad54e46fb36225754dd5b1abc0273c6f48420f0a5282ba2335981dedcbb023d1c326d028b6002ba5ed3f037ac04f9e93fdc4edc98f5896cb8c8bbfb41ef2833c92558cea5f04c223553053520dde47d9d3a27ccb939a5af2438062aec2efe3cf10127d800f6f84672500de925abac8657cba0585355b7e481f916bcf04f4b2c0a4e102e3715f50672f76d37e00293075bc763c425bac9f70e5740143c6c2d02e637ae7beea1c4f55f853764e4a4fbd4f2d64c356c9b708c227bcb1600ad5763dd9ee5628ea5b94903f495e86ffb0fa254120d634abd4117c1c7f7f85811efa5dbd6e4dcb0f0e02b72273d7340ce69d4b1eff373e50a39ce02c428a4a496009dc60e7f17a934514e41aec9a502c6009f42074558cba4d1306a8408df9c61bee39fe6df5a8f736918270efd18ac1b50fc96d98449143c2f250fd1ab0e04b8c44e20709b4dbd27691817e9470ed26564e8d6c275c0c1301cf3927a32fc2aa48616786402a529cf972141e460e25380c043ac40032106df14260cf185926e3c2c777399fa9e1a1042920cabd96a22fc8ed5da9c4401a611025300e32c46876bfd053f123676b1176e0172bc935bed08e0d7d591882f082af2773db7394d9e875dd54941d4f9e1e5cfa85706b91ac962a57b79e3b8a5d9b4f830837d7c88ba0c56fc6ea02228265e0f607292c8f71314e1a6da2f41bf14e32a7aa55c4915e99ddee44be18705c754c62f72a1f38e953c24980755dc2d7617abe43d23c6fcdb21679100a713700a1a211872fc486c64ffe50a2d838ce519fe44e68637b46a48c6de49e1d45a2a2424115c60652556d109848b0b962a04da6d6fc2446d2fee57ae45cc584d2f42714f06a17229d297d0bd07472ece89269ae3ef6399b841f514425e6dc040b5fd3ff9a04c72c7fcc7ad7d8cd01fb7975394f562467a64b70f38ea86bc5c93a4669aeae32035909205c30904f51af8d310e007dd48d6199f2e1c8588cc31b4df4b211d767b728cfce704adbc81acd9a314572534fbeb6e58ba5dc415860f54fa8cf162adfe4253ab4ab02adb804c8f5b8572dca0c3e62691597aabcfd63660f1125643c2dc7235d70653525c21b5ccf6936eadd49917cb887ce06ee9ef99cec8120e317a5572ae96854d01bba79302794135d5521c4fd9115946bd25bc535c1f045f6c64bc64674374067658438afb0f9eff4b192ffee3f438171950a0c6610aff4a2f993f72f93e1edda4fcc1c415b344982d21f63db7f49ce8af6eecda31ce22d251a31a3437bd641fc26c3519ce15946ed17135a229a34b99c15ca09eccacc37435903e0c177bfc15c81cd8ed4409991c4888f73d67223c81d1bcca6c1824e622792405723ae1286dcc3c13db4eb9209b6a365f874b6522dcf1463eb15658bc578ff3e40050f0017a732eafee6a4f009935b0e560d8254523d67d353389713cab78bf473a6b40c4a8206a7537590277768883a6bbc13ececc44e8dd2dc43213a9b80ff07213011b8755e316ff5e940e94812ce6081030c540de057ff04442c0a5c0c85672ff5d8a663f2346d955dd9eabf8894dabb6a1312890d3a1912d02585f6e984072312e634d663a7e8a12f6c78d2a75079729aec2c6bb87bdd97db5cf2357ba80725198edadd7b49be39425c8911e228f442e297f49a583f17213760cd2ef2a2365e7737cebcbb0fdf3410a0e3da38e2c1d1d71c8135a7ebd3b2c5fdd347963b272ab347adf2b7390825978fd91648a459c7da951dd845e50eb3e1a3f81f2f13136a05efbbd0f6390b2f589940fe41e774bb62a7147caf0832e07ecf7d3ca71bc729759bd2b0bba87912eeeeea99a35eda09db6c817b3da8d96ab9ddd7f51ea5a6598e5639b6f1bcc3edb1bec825f18f8b892bde3ebbebab5e68e35c853f079b1726e15bb591403e62e574e2881ca70293b1afd1f3130bdd24212d6baa2cc5c3f72330ea892533a1112d7426eb2135977c2b74a431eeca16bde209be71e58d3c96af982f48422a66b3d84ecb5c1fba51914b98887875fbe4cd521163f888cc75b606d7e0ab73f5d68ca0b1f405f5f9fc9705cf237e230cfa0aa43b4a024557ef44e551d5768caa01f9711e99a2e493041f87ea69a16824037a2211fb4a52144c572f4dd361f633318b4b3a9780945d15228bc7d9da6791b585d15115709d0446a64a2c5ba054dd1a9771c11013af1b384be5ac19a4fab263877e8212306a9000b7246d1b014ae826515756dd12ea82e424d22e19fad48511ccd2ac8890bd5a0fe477ce1a74e305f791500781280880ea1484102772e467dd138fb8dbea2ea32484bf0a8321e4db9302c42d609689e11546b6285eda124870126f0a64c5a64751668fd5dbcc0a4bd9768f3686f1f338c4c107e13ade72ef79ea830e8192afc1a1972d56fe8311d6f4424a66e7ce2ad0275e66aa87d694581bdf6ffe5d4f51f5cec725f1541976757ccb456b1b329d99fde0653052be360fddc8847a71f738f705c0474cd099e344e56fdf2830125638a030ad215b62fcac11268da0325831b080e12a96a76d0b907409f450ed3e5dc24aca5906d2bb3f39f3e117bfb1209a226420f9555b7a7ec02facde90180257a8b7b63edeaa72d41bb8d4e51d04c7882f20815b9ebbb1fe3ce2df72150b777585fb0bf709be10eaee5446ed1e24e86a04d661e9923331ea0019a43dce986d0d3ef65bb9bedd84815114a2dec0431764c656e68f7b5ec0b9d249a7b212a0b4fa61dc0ec909fecf72d768ed0b1fd507f2825241d1b2bfc4f28a72cba1e70bdce0655ea805e39c8e26a074cb90107785e753134729ef1bf12a703bd979689db922d5d775759bf6c5db53f0b850114878a91c744720708980e06b3b8f0119b32c81ef1a26d3c424ca23ac96fbce63cd864ee59da295c8494210f4471496bb98ef369afb0286712821dfbf57c83843607e95a0de7726f67a269a3b5acc3c519d8f991db70bce9f947d29d9da968d4c5060472c8d87210c95c9a3a2b8f6de83c366b202ff49dda84a6f139f595437c558ecefa300a2a5d8f37e7aba76e33a4814292faabb9d3dae575d3393af37616ff46a10682bb58cce481fc23e840733217c12f252f1b5419a1ffd65d9f128ab72d30035e385372bbaf0c9941793fe5f05817f7997013cfec3f5beea72c5ff3094db88b28e28872c79968627d0033b9ef7ed459639e6b66d443c34bec93b7dd9dfa76305f99d1721c6651e5efeaf93d47ee51cce0c829f6b3935b715f95626c4f35feeca6cb57215cb409a571627ea886d03bfa5f5897f8bec47bea91681f3bd4c80d9597f5fe03ec893086127f1ea42ec9383284389a2ba7c279b2fd41b28d480e745275c56d6d4dad9db0cd23b5f9a5d5372feab7fcf2ca4a24288bbecb4e9dac0b00eff44760ae6c817acaa0e3c5d683a9529feeef30405771306c34006db9501ac56f19596c3f604245ff56541e9ebda2347daa261d81666ae7a6f23d58f1d7c1f738598f221c029e85681e64a2065ed985a2c8b17454bc49d2ddf26f88edfe4fdc36866c72e747eb5032c9cb35f8afd46248994adb65763c80151167124218adc9f1cd8372e1a6ab7f94f513a95ba67bc7310753278ad63dce9ea7469d8c4414bc6a2872725ccd17a2b78f3b003b8f11a6e7e3931927ac89c27fa9bb0be28f9b88bee64e7297b519d5b8a07628c7b72610f66590825c0dfb0690748f961c90589b4ee30672cbd8f088da41ee70d9071d03fc4d42ac355683f689231b286faaba11ae7557519ad20bda527385ee07c3f23554afb14389db28284f477060f19e4c67ba27ba728d165c65878374122a32b34267b918f777cec662234c15e05c4170e4cae77b720c20f553dd07e09ae96d823c399686c778631df7b88a4ce35f5811d11e6fb242afdb7fcf50a6d942ade76b57af5b40eedc5158679740ff7be46ab1dde8ffca728e0106a640356e500dfbeee78b7f3889e6fb396a17da03c0eb338589d1ef9f58bba1481ca88c0a697b21f8c82e953249eea938166d1d20ee5d2ff13df8dd4072fc3b0498a6b7555d9a1ce14685df68ba1df1e5c504d29e849eb6d7f83688ac457551ce74f2342a61e49cb62d49fa986d3103ed786ff162eb40f59cc5d178787200fa45a582ba1df37944b14e95a08d3d622e9da3e6dbe2fff8bd50af145809729b56e4019d56e034fa0dd840d8542aa509f3d8cccd5a12106aa253911ebc3a7298825a8271ed3f59a3d6f734bfbeabc8ca12ce1f00e3a46bc459a82565a95407e166dcb628c5ea55ec04c3c735e4ebba1e88e57eb9961fa372d3dbe112e8572c676bf95ad130273f6c8ff614e1c11e07c42bc67c23c239b5e98b28d6a48b1103492b7ea9326ef3816fc34fc296183f8c7724c2d054321b26dbf7da2a8eecb60d49456013f8902ba459db2cf7cce68c85a81660013848ba75d00b9f72f277cb6b00d2b5e2bb8408bbfe8c36d0f07a156d1dff2274d6b7fde2ccafed3e10ea8046873c862565c2a7f41c84146cf04dc1e17f36a083bc25fbbfd02c76be74155e727b1da27f832759c5cb710a7de5539048bf58bae6854ef9b351affce106038f7233f483257ce1cba5013cd5445423b9b71087fc5638f2af2fb8e0408a1b7c1f23e2b209e8d59b0e90f4ec7b7fb23b3da725e6a967c182307db8da5ffbe052f7725bd0f8129ea7a60ce10086d82cbb785dee25411ed906a4b55e8a57b2f3022e72b2d693e301e7834e0b64f67185589ca0d393cf467a792d98d637a7cb6d242b4b3231f845d0d0e7d0d7dcdb01daa748fa2de92f85045c54f320b4438c8807e2725dba42aa78f96ad10753e4976394f5f4d9e35039089ba2f59b6878e54d6f5e721d8c693802c6534cca6c6edb296ce73dc9d48c036c29fd66f98abfed2571d328f9cc0eba4a3c7e57debdf7d9199754feeb609be6878c6bf36f63e91f7303192620cace219e53ccbd2f0fcd259bdefc51c2278938e0448dd90c2ace8d3f628772c5e0f811826bc49c900b173cfdd2bca865d5b536679582736a3ad99d358656257580fd8622887cd5c31ee6e25e956d721aa646da9dc2e4756c35b9f1936b9e7215ca87b99181f22c908b7b6fcf97df4d73a713007585c599f33bb5b3885e822f9a1f0195aba7c7699fe4a853323f099a9a4828727a26a8d16019189ca1ed270fee38469058fb3df9f55a064de469fffcca139c66d13809305ab464ce047e4672848514db2e659e02a51b44b890d477d5741d98421dc18c348a24cd7f5dfaac60037825291db4c9ee4591b170b67beedf76415efed59aca70b50ca60f69ea6d7280b22ef0e014071146df79eb3428187e8729bdd377290d49c151e98083ac5b72a5d7665124e15c70b2d766e80e6dd5f99ce0249a9fb9a3c16d0567e6b099787200024f0064cd37b7930c00afd27fb4414556aed6122f5adb89ed4aaa982bd872ab80657c0d150b14309c1ef03a470b30618b966eb5635440f5923c79e82c094db091d594f73a37ac5fefa57c97cee541328e8150305943d49d9417328d45d1006af53ef640950fa1a2094451912efadad42275f53bbdb6bc1ec6570ad2527072178303621e73dabdeddf42c5d66f4c75674b79c66c790127d700d5e49fafdc725c22ab25e12923cf0bbad5e0b231fbed553ad94c21efc33b5f406863b80a49476b1f1aae47c3c30c357b4192ac5876098e2b7035d8d1a96b34daf2a0e9647f7270c9fc5b45766160461b6593aa421cbca951a6bed035dcd7c031afa79b61e829d7ab7ec2bdd3bba22f06131845da543e68bdaef0cf28d5e8355965750ca60a723b780e9461e77eb782c91286d75845433b2fea103bd0eca141f4467b1b978605911cfa7c9795853c10e401febf7c6bdd31b63152b39f870763b97f32c7246a72a40c67ffa8d5631e2fcc46b1e7df71575f6b855568da325e95b1044b0d517672d372ba097ba1be6cf8fbbc15320eedd04c9978b4f3e8e67402e661602600e7268e3b651ced400566c24e8fc6a5e7bb6c52ffe588496db6743c9c02b2fcf91739c1374bbc8d9f7696c5c648798f53c48f8fc31a276774b6c93fa063d6c88e43725b5c62840a7fc7149d840f4d748c979b625d45c959de5e567c1983a0be093c31f4c2c10ac270ba365ec8324c07c4b060560ed77526593d0f49ba2a5950f9c607ff3071f3771eb4baac70e9e51071a8d7255045875e6edb03f345e5f5798a356880cf02417bba7645d5a6e508c60081c14ebb27342cdcd0f4c3ff8d95355d0d600a564778fe7c426a5fbb9b69c8b1fa375fdc92cf319c69b7a92cf6589916d472744adce267d740531ee0b86fbcbfd5f259f2679337643d91161971674d2a195497fc87cfd48caf3bf9d10a842115a85d1089759c33ba827b24c6b2f51726e172a669d03996e02748e5a2a351316bca9a039f6a6024e70cdf7e8184db9413d37291de18fde801256070e9d8808e8c887deb736eb00b942ce61f8fb5eeae2335729bcc571986c96b5bca911b10a2a8d1bb1228c8b11451074fac9b961cb975ea720c1c7cbd107185602aca51c320924c52f20e62ee7cfde8484b51a3ebb342e97275c6d2aad61aef0d90225ae7970fd25de60deb9951aabb0b678d816ec1d46d69c2cf0e2897fa16ab2342aacfbb7f522a37924790b62db86ab04dbeca9200ec7246070fdc4eca2a8bd75b47de29d78997833fedd8f134b39cfbecfa88535ac172f3efffe131b80882e05292b2818364f6fac46e6f9a6420648123a16fb891402c804161896b25c4f6b612e2dc920e46a1fe2591e5e76e95dbd2d387983437fd025433a642dec580e1da7519008caa79eb1b8dc695139db340fdffcd9b1ccd5b7244a03c0b0966a04eb1de5e1e7469112c3ba788a338ce4dc04b8819f7819d7372c712cd3be37b74fcdec4102b19ec08d382b6089c1cb7ad4849cb8dafc71319362d6fad02237c772b9e354a279a0d784b5cc62628b15ad103437382b1c25a1e72bb7a25e56bc972ebc8ba36814fa9f49b672071eeef4dd8b4c0c878628887466978e45e496ff28f1da2d81e4bd7bb763811610bc9007b9cf1f449311dcbdd544ad6a5fca1e0de21fbbcee0346c2cbc7dcfc067e3bfae42ae10b43bce68241f038ec2fa17c7994714b6b2f51c1634aaec849a356680ef19356e3db50df5b6d71726009315a9c860594e7f8c6c1e80155138171cd1e584b5deccf6f3a78404f5c72c698ae95a5bc58adc6a4d948d688a3fcf52e7b9e490e399dd63ac2bd6a7b5472477702a42187757dc9865e186c3cd8457ae6cb329de93cdafe698c6a30474172b6ea3c18ff432bfcc054d55962a8495eafb3ae148cfe62a728346f79b2ea787253ba079befda829d29f192dd4cd3e270eb9af2f5c05f7b6ccb3c696552b7a372ce8e4e105149d73ead9e562ec554dd2da929a037b12ea0ceec2cc50d26b33c72bba280edbc42ae4be46bcb07159649c625915adb8247a0c38e9ad87ac5a3057205f1520070ad0dd5a2b2fa7f6d827cb8397816bc934fa0768b0749ade43d0938209ce6fbe051f44d07938a458fc408a8f68863e5b7d82a22d6e8df46bc440a72dd1e10ffb73fd89bad1597daa10ff6a6d6dbf367b6c5995b461db330ea6ea672f92b2ff21ed93d3d4c8261bb3239d6ceb89c413ed48862202a37a0399299db1f4aebc6ec7b1880560f66068c4a8667c6ca9f9d0ebe9226dd22c75c4917fff6620730db132c73464ae74f726920704538e122c5348a6b338fe083152083f13c5192c13c1d60739d3a51e97a195d5005eca46b66b7e7dcb095de449d91f12b694d0e8d184c7b0ed991fe8aa56557fc1e233fa051ec157ec960ef7f614a6041f22f56dec6a9efdf3aaf107027c1d32159ed9e38c7f24dbf92a0a9132a4716559e59aa5a9ec6bbe0dad9bce604d9ce14df699f5cdcce7ff95210e38b35c3859ed90c21d2b980cd2a799874d113ecc9034c49d8c91c608e60b74cb97a19cfb1f65172c4c4e5a7c3c61ddf9adbd0b11f1c7afe655b0b9593d0e3eb4e9df2fa60554b341d8413758df62b6c6f5b9cb6e3ed5e35f87122f384b8fb7527cb83e0755566729b0ee9b1b013bda54b106f504002c654fd7feb51386a97231fa86c1be3fddc721736a9bde3dad4a97bee2a058928c2893c98b6b7711215eb5562e08b98dd904c5da172c06e3cb1dab2bd0eca3a91eab57a7ddfd8f2d84e34199bdf147981df72b6d4a0b6da72179dd8cdda652f823f4cb64c9f90af64fbee3283076c244e711adf3072c0d770e23336567d7ba129975a6d8a4abe152285f0db727fbc2bd3c572cb263b3b65a1ee65561f82632a1b49fa339d4a713f0f3ff56309066891c86a72f32022607590423dea7fc7ddb7ed5f13d7d1a11daccafe18c8a4641f5a18c87243382926b6a50e1b881a2de2d0ec91e5aa0a9d93c85ea52393ea634d8bf7037213b649fbf1729fae42f88247acc42830990f9c29d9f7d98374e08b889fcef724f6d57fca0c0691857c1ed766c8dd1f28f9695f61dac14f3566bf55e4a6a26155a1371b5ba955d6bcf72604d11a0775e8bc4f9a0bd974aed65ff071d366634d724c0a43a60f47d927dede10d71e8eb7562ffa37a09462f2de70939c490279c1085b0ee155d46d05ca5f4276252995efd5f6ab60e6148965289005b388245ab772235a6032a986ceffc4f4e778f1c7425185407f3074c2823d94f87d1a409b9772163fddecd84641d9ebe77b5da15bf5eeb1f588e9060e353a745031ee69f3c14391be1c1b43af28f08c5696d3f4ed375040b3b6118ff37550f0d3d4653de834079bce1a64d57928f936707539048490acbf6dd0c60d4ba63da71f896cc2c9fe721b8938334a11f303e6ce210ef9bfb929c1afa402d41f6e0eaec8b8fa5949ed721c83d07bc6a93bc8f205f0cacf50e13a7ccb8f52679d8b10dfef06bcc34d1c72d2e86bcd1bd4d3cdc14caf3e4e80b1b4396e83659b1582505ddf8f9e74932c7232176faf294ebc42bff0dc89fea952bd6c02206e7dc37f5385f40c7df0698d72712d607cbbf758548f58b96dd5b66adeb2cc7bca79422d9b7050c2eb3526407260d2be93589ae293788eda593ade861c53812b8189bfe3fbf666e3361223d4633085ab397bbaf84f326cb884e2d4f22bfbb082afae28d40b7130e42a9c926b1562d75fa73de8446889f40e8058ba303331b1ed410731fa29e79ea22f44d39225f4a5e6b6eb74d5b6db84db07fd2b638d232798bacc6f12ddaf79f095568c6d3e44f6dcaa25853a18cc679ed4f03650dfcc887f5584024f2c81c8a6ce616aac72bffd5b2b10de4f266ea3a5f2a61e235bb70350f6680d10132e15d3e0271fdd724c2cc07e04097995bb509bb8de707327ef61dc1c6aba54f67bb071ff42d3467213111748e6c6fce90079d0a8de79898f435c6983ea461c3ad19526d2dd3fce72e05a9fab606c8eeb75c0d52083f9a829f290e3892347bdfd527a094bc4ccb670ff894cdb885953699e49578917d513fcbd8a10bd5ea7623b059ed40f16f9b10f9e312a94eb9348a0808516186e403c28856fccc11d098ba972ae29efccb7da0b9f1f7e1979b115ce94c46ee7f45350621c23079da6aaddfb08064fff7acc7138cc7df470df0bc7b439098df3d80c276d67c29292ca17f5ec522175fe344b01726adf893def775c02b47acdaa0ffe1aa646d8e4b120c788c983d179362e1ce772244d17ae84732d401677d44193ca78274c7a305f46da4b26e33a2b2827277c72a74a7e85949cd16ad2bf11877367e918be74b26cdb6dca7d7baadfe06e7e7072dd8368335df890699ac8b89549a84299ef67b940575ab793ca36873d78e99a727a4e6e37c91b86f48a27def1ecb6d59f39645f12f869392903bf2795c121d872d4e99e1d3a442e68466eba25a8f1e90674ccd8c2757cb34f538c2c6ff2a16e13a0a3b04d98c0d5f7ce2ce8b412f57aa2b03703c968fa9c0ebf37271cf6ee68506517a78a0a91a1b782c4f57fbcc0d18923c8580299b966938f2fbe2983756c43ba27055a41099386f4be38e1207822ed8644d33e32e1ffb7e766774b8e341472796e3a6cfb92e4e927f4bd8251fbc30fb9bc65895d79ddb8cd04fc47bbd3ea0edc85a3fee659176d73492d66e5eaa037ea9062afb1a9da61a8138186bae03472ba58887e1f2c851068b8b8303ab04e4fda3408104024b334dc93ac4cc7a0ec72c04fc5bff53724723454ff1eab21938998b32de6848db4b9ab5e1ee9a2c86d72841984ba94a5d6dc6a18f61abb72ed4f8da7b4b9f014ab73bc5c6736cab81c0a027e49b953e7b274b8a48ad8162faa656cac0208452de2ffd418629027d3c403a7f179409040f36c853ac32a798dbaad713a5b20f504d1cde4eef3db2088ce4f124fc8fd6ecdaf45d9ab2c4802c2c9d471ad02e97b954c3a68c6da8d41510c72de5aede44a2a815ed6019f440fea366d37cfad141e3ca76a3ca2231900e57072e2b974db68fa0bd676fcda47e2d404b579f0ae0f025491360e2c643dea582472ec56671bfe5989f45d7ee64e91bd17645df1a8e67049f223d1c6ca223bd91872df8edb2dd6716474ae20ca25b8e5cc35bfa4a3289e736276479b4ef40f0d08720afd51cd911c0f523a5e1976f9f19f086014f2ecda5204c01af2f0b775857a6fcbda0dac72a6a3bfe6efab0d634d03ffe993539f5e879eac251ddbadbb2c63379af429362cbec8d9dd4063cc9344af1b521a7ed5111d0a17dd113f1b00c0af576d1f015966e59ddb192988f62db49ecde5ec9a48284a87044de293ee400a607246c8d8c3f6e538d45643e1cd347875ce3ba251f5b2e861189d76b9b31b0e6f72104b198889d8d27c792e5a5cc4dab2050aa8d52dfb9fc895615e69c17eed452bc95c266cd57ea9300c8445ad930806aef6e236f8110ff8e2c393d6e7b91f6d72a6acc0a02a2eda006e0f7fad4d5d23899ae33a1e991f66e2dcb5621af1c17d7221076c4dc80ac9e801b177087eaab72f9a915cccf5e2a335b630b60c630a4972eab80d8a8694803b7ad1184993bd913e3b620fa3e5ce073f6213209fb06c92725d65da5e926fcb3dbba4362e03a8f7dd57b62eec8fd4fb1f990efdf12a630872d54e7f37f8f1c02dc9c2ab4f9f57bc2857ec9383bcac0022d9aa08e16304c072eace0e4d2a381f85a20b5e103449dac1a4485a5999bba2810687df87ce5859720cca4668086d3ce33aec76b98fa4e6debc4a4aedb06e0ca08b39b2b721c55e4c6c14ef7f3babce65995fbdad881f851f22d2b7127aa26439ad1226b73e069f72af01bb0a390c13d35fe521c8511b959a79fa36ea1148b06907d4634eba2dc6725093a536a8e41e84dfd34eb8a7318d1c8b48b7498ffb253e3342a54ead2ee9035d0195e092cc8bc9aa3adb9f12cf17d52740aaf45c8f856a34948571e10a8e7265e4a5a0cd8a7595dba7248d617a03793a5a4e8ec42c5b6dd5275faea16cbd60aee9eb9d89392db0d684c3f136c9c0b45a676f50c67d16d60225c71040889217c189342e7f1f0b5f5609ba089ed6383a79786ee977074e3550ac1e5289d73b4135518ac62cb96badde59d6ca93d504207cb0693bca725dc4129f28d762f74e6d8bface678f9e899dd1fefcb5588518f71fb42cf3f4f64e8bffd7b45718f46a0b1ee553a7ef3421ac9a53ecc17e7862fbb509b0724bd3107cf419047fb8e720728c58256b6943edbccf584143a01ac92b03526fe201a3ddcd6cbe2f671f8a7872b6ada724adeaab73de3e226177f16a9a35d572b84a2c0dabda9fcef64b611c58adcbfdec133a14003b50db22aeac551849fcb6c77be7961246e6b81b1e8ffb0ac4de6f61a557e667358f79a5306e38c75c25866e3f393f7552fbf74e1a47ac72d0f51b4a50ec3d2a1c1ccba5148a2a41aa7c3d5f15f50ed9058626b6da91d8725d1ed3d5042edcab9de25f102055bab6502e625d66877c4d6c634260c6f5244916392d3ac1e2b218b872702e51570d1461dcda86159801aedeaef996a53be972cc47b3f3f7382c5cc8a17d0e07a101c7afede6de624cf296e7f53762b9087672610e0a03461c8b937b094af8d0994593b006dfb0ec7974f98e5f08e4c1de0a722f05f8371df11e5c92e016bb40ebc02b6f4cbe325b1ba0df108a5c031d720d69a2e646c9d9a4c3ba7997318c48d73a7ce947966665921317a0d1ccc2b680d619a61e6552bca48ff11a887e6d600e6e0004591a876b3844155a2f57f400a12662666917118149ff110908e4405f146dc937811aeea9dd84994626940f4d955172c356b149ff16f45418127f45bc20321af433ae0b931b5ad9635004dbaaf69572fec4b889164249d209de556d73757747ca6c5eaa89fef4cdf9fb48fcc3308572e9c5625c3d158fb502d10c5f597b6ccd56f57838c2c19a5531cc1e1ab472186281b306ed292821146d2c3366114bc2b8961676b751a373ec49ee6b3f2463d45261037c6872dca744abee28eaf15dc2deacd64bd89cd748c2f87a7f1845d5da278ffa7be9d76bc17895929d39180210e8bd8119eba6c763a326d7db615fc5be5a48161e215dc7f62ab5df9f7bb691b2c0cf792bbed0e28cc9552a40b1535a2e71e2810ea8bd6278bd5d6fa2bf2a965fc3dfc642afca503493183ddc7504672464a6462d8086bd61c34983ddd47fe4456a84b0fee63916b1ed62cb11f04831eb726d41bd6646e1a5d2a70538d135c6dfe622fcf486ec20ee9bc3a2ef4b303aec38ad3e4eb5464576d0fbb41ac27a985fa5c21feeef6d6e793ea4c9f880ee1cba721d81666ee17bf6ae21b0fcc7b3520c090e67a60a9442649faaefd597fdabda48c9143d3cb27bf4f01227617bb04b63d88e50e0021041ecad44ff38e853d0ed72ada0c6ffc43edee62015e4c5589c36259a7710132df013b402f913d363ecfd726bf038a47fe34d4d9bea9a2fca2f76fe6e9dbd4ca1fce90f952584b061c45072ae6bdc3f67449e7cdd0bf92b51aa0b3d136553715ab4315923b3fda1c0b009729bf51eb534270f377a9698ab0a2615d249eef266953ec3575250e6fe921c0b5dd012328ba0bbc8a1be752364995cabad63b00c68e1b61040ab7d3481baddec72131e21291fa8ccb42c10ed9ab526dc8300c195dd5b612b0213c3394367560b4329a38e733ffb67baa4227f3b8c3d3ae6a4601195f19100d0d26f5adf7d80bc72c9bbd6a5e5be73693a12193fce2020b87ae76cdaaf3f5a40a3fed798e531622b0bb5febf731563b7fafc089ad48b076d25ad15b8d39438d21534b3a9fa737c593d76e4f4b89ac94ea983d0377b21124939331e67353346c821e3cda2f5c89c61c3ebeb22e0e8bd755692985a676e7564770012ff93aea4d196a99b3f20e86372a262d09e5adb488485ea169f36af3e529de0f12617c33623600f889bdaacc972f8626dc9de6332cc2189d9319bffde337d67144556798d2db2dd94a65ab9c26ff366db7973d97b224e9eecb0a9cb69cdbf5795572f913e5ff15c93a07a25447260fa87c728021fa33fbbb1c7270c27f3c51a5c72ca865dc8c8120eb8ae0e7d72038484ec2c63ba8ef08a9d8b2de58c3986657b386898418adc4793b487099272eac21090b8ebf357d48a3ff5eb0f9335a56023246bc7fcf4bc7e59f78575c572ba1814b8b0a44a92d8bdb8cdb7601e6f6789a9306da6273ebfbc6a7d0f625372a031e5862b05db791259cca7ee5b59ce165cf5d57dc6f1f3cd3b5f6ab1970a72fdd2afb4fd78919a51659c240f05e4a4e11d1c8d4a2c989f7af080f69979057233d6cb6e6f681607a337ac0eec529179778b1569084d8180a0df2d0cfcb08836f071e8b187ba824dbd482e185187ae67a7d3336d7b97c59d53c30267eb72885f08c54640f8f6a2964c3325670de0b432bc8fdea1368650de96e850a8823635222a7dea5ab0d467be2a2a4b91efa9a246c90bc7ed368c53546c994c97788b34726eecd5e542a4170dda4a09d25fb3480a2f2f7f8198c2449c1c68289e3db1176e6d2b6d0f0b0586d563a41af7ae72eccebb72a9e5d3e980359cf9b9726ee56538cb888905df6797e4635ab0b5743d734e231cb04cab710233770033f239ce7d0a1a8b093a22a809465fe97ef9e554615dad7a72fd03dc2236f42296183013d51f8f82223db3ca52b5b2bb62dc1366ad17b1ba30028f87934cefe68d6922c93a02498ee284430ebf085f24de357186817470916dcc14cce5662aa6309bac0b1e722f8ca63d59aa7068da5ea9c73ceb36c558a850177e7f5f78293a7ae5c3acef39b167f0f8f05243126ecfc273359cd2d458362462d1c80060de5b5b097ac03d72c98df76326688526bcf372f0b73fc3ab7c753222416678266e4099cdbe4fd972da0b399b14b9d45f4d6337a644eda6311872969890ac2bec32981d25a227951e95b3e5e12e47163dcc3d1e0ccfe9273f40293afa6126eb703725b370c3b7ee72c0cfabd923cef64a443531aa84ca8b77a269164ae3f51b0357d930fe39b93072741ea178108210c0fdd2ceafd8abf7b6d5b77768f4d1444b378e7aa4c4befc5753f22017547de5aa6c39759672da04927872639dec3191b1d2cb019c5a1b564087842ef4911642ef52bcd387004c4583d47ffb92ef4381fc2a6ca34430248e72a0e83554799f11e0909a276ef3010988d43aff294cbe0e4f326ab1b4f1875a25ded08a6923a7dcc0c9df597dcb4cd353dd2197c50c0f1eac2c9eb2bd7157b37269cc263039ab1bf7a16730c48eadf99cb2dcbfde57d68c85e6024f78f90b80722fda181a528879637fa246039f934bdfb4323fe1fe1be5e3152f81f83cdd2418c0ba186bd4e1e0beb8e837c6e86e114346eb179b9f3b16ee125f676f5781547216dd7a502e36ce4982a3588f939b9d4eb8f12f7b308504c01a3dfb618b28507299a5ca1df2bee8448b9b5f61e910d1207cd7d8902301312b9a8a6bae4cbcaa5abec6dbccf761f8b844876148e3ee5187af9120754cf63295bd9a46cd079f86720d0fed48057f7c669c9baa874bea80b8b5ca591399700118c41a71ca1ede9810ddab0f824710392acee59873b254f3c608fc8e8600a6316166dae1726e2cd5729daa8551d4af188b0dba7036914c4610f07c27f438d2a5d2e0a611ea458b7472352846d6d7eb1aa184247dd8e6b6bddd972755d9c6ae1523efca44d6ee58d872bef53d832983ff4e2fee23bf857812b886bf766bd51ad05f1cb857b0fad402464a17d71e187f34d126c3ac5c51376cc0d6820c9cb1341cde3faf7c8120a3b26f03b517e12e6da22ffeace68fdc637cbce74b83c4398e57cc9362eccc55654e729f051d09278a71f1a4e6e4997069b6797f2278e4b6b3cdf735f5014628e8701e0f768990e1bba913f8e89469b082ab88b5abce62946da738e391f67824942772dcd27d380b18a1d422ad2be931cc697d19c0bd601a40e766d0e1616be82c8eba020000721a47a4b962599b89839d0febe02918e25c37e2fe96c54f2b7a6baceab5c0bd72747b7062e50e7f44746f180f562d560ba8493984222c6ef00474483cb3b9f70e7a5575f8cb35fca06c3c709e4d362a5e4f33ee5c49359a0b0347e472fb1082564e97cdc11c4b1ecab8f9eed6f8ffbd412be98be24448d4bc01fbe174ef5afa72f13a99044d29091daad2d1e3a1e99b5f3c5c4b87263641ebf4614858422d76727374f4e84d61712fa8525004aa7729d7d1b97a6608fdc70abb68dfc4e2274a723fe129e7382e175d55c73f5638972a35d78114ae4cb25b9a29f50fb74dec2402376ce47baffe1e3a941626d3b6aad2eafe936ec8816cd8d3c581bee66ea69700e661b8615942a72e09034c4def4c64dfd2f48c573b9d5afdf922a5cdb919c326352b6c3e9b70c7ea3e417403898e2e7ee9dffaaab1801cb359463be02cd19072f3d04ec0508d89f4a7912f335533bd103e1000771cbd4795c4a34d34b040831c0f68e67fb15709c31306d05db1d4a0c3d6d5f04d7e09517c897a77353f6308728ee2bbc81c79112b90e40b90da474332b3563ce67620acbb1c6ebfbc73af37722122b8b4303f611a6c35cb779cd7b803579bc98fed92867923381a29c60c617279e62dd11bb9b64af06ff4a2e01107d8a38c0cd3974807087bb0ec10d50b7972723f2e165f68075a43fd3988e47c07da2cf29d48caa1c85e747c9a0161e088721f29cabcf4d4cab116fb83f5ddf0aa742a8b8e5706304e49d471f34809a4b66cc89404f53afb4af0e5f247eb94082e979bc60cef1a8bb4410890672331fe36722bd022d275928f13b92a2865fab138509ee6f48490c4659fe48ddc55ba300c72a01c240674ea81bb51701f20c2a0f006d98c05a31ffcbd56256db8cb682c7b31e364fac635b03f32663c06487b45edcd0169163e35f1ec5269f213936cf4cb7203f08eaa16fd3a3c197a6ac6a1e433951da294baf8d18c2a9050d4f194b32972f5c385370c4b3640ab8adfee69e6533e9fee3e8424015ae5266e4fd2fb9bfc1ac36e86b660695bac3814476c31abd6c740e2acd5c7d4f2afd06cc468d74fe672dc95935af68635101a2befd81ee5c501161141f944c095ca8736c3497c579c72c06ed6a2ee4df7c7c6fc1b74c24f69d3b8cc4e09d960400f34222fbc0b01b872b73ac5c4ecc36bc47a6a26d18d18427e4ea4256ee39ef4b9984b0ed14667bf30e6f6dbb938ae125a74e4092878a563fbc703c353ca4ac9f33d6b3cfa264c79120b0b7e253c425c638e421eb996c79357a792886afdffe1e3e1483880bed1c172e98626e26c4295126a4fa6a174d089ef138a08b656f16722643329493133ce2362a761bd462f176aa7c6fde0d5f7324f2fd099dc5ba09cd2781d55f0933d8872e6a88c3f12c7c46f88273f9ebbe983a89f9e7e1d5f8e6a038261e11f6c90d172dded14e60ec7f05a7279ca67391be8debc2348440cfaa0cec63532c928b0dd5e3df7f54fca1b8e7189f4fb4e38216da9277bead6990b0975f87b100520e07e5726c7f487713ce9b7b04ca85a40a10e9284ca599021692c5f22256762d453c74210d496b882cabcba0f383da7719ce07479178761975254e627fc9baffd74df721306c7489a4e9aca254854c28365571814bedc9d484cd0bb9e751b101919c5655a9ca78c77dba85fc4e353de53a81b824c9ba7fd1bc0fcc882810dace0189c72ebd933ad77c6e32a832ef82a7bdb4088dc00db670cc2ee19bfbe05a581df8c2359da92558c2773d6fa6628f4183348ade2ea1cdd6d5bd24a306d79eafe991a72878f1b0af4dec637d055fdc30cd1121deb726a10de2130297ce4f9418b1d3c721b0e1ecbaa44a51743d3e7fb4c5a2c08ab65ecc4a3436c0c415c489efd36b172b39a5a30b0f65e835231f8381b1d56eae8baafb41aa9ca15e50ecd795104221f1df9060fc66ada6d075b923e49f08b981aa3d53e721ff687b034e14b05282972067c94246ab927b93b457c3e8a4dc7c44a9b5c43a72fbc78480d1b84f55b1572b55694ee762ec00d5a6eec338f4beb62f7dc56510d7c6cda9af088d80b5661722af3906ff40d92f63745f1d4a0d3294c97f8e00a60c9b1ddd6978174cbcfab3c7bd90723c324d3382c44e30a1f1734f8ef303f30c0e5aef16553d199a99c2a5047189d79cc54c00448eff8e2b01777e99f68f7079c164113f703885d53fea072c2926f791fcfc8133aa1fdc1b371abb7ce5e8aec1212c9c72b0025e663feb049cc3994261964e3892cce90d81f9cfcef36e62d48bac9727f98cbc0014afbcf51cb3a642433e2b7e712d586e2e21a6d6708a069204da2c55a4683945779175672a70e974abeb1dff6da84f997ccd3277a208944e382da8410c310609a0afef27258dc91e3be117d8f9691415f6b491577b1db49d7f0b8a044c481ad4550e1f957d09f167370a355f924a4750f267b092a55568cbdf00a8c6659068dbb6188da72f40a060f82c24fa8899ad3146cb41badf80c64495f535755e317e6145c5b495e7b7c77f5794cea278c5737f1eaeccc165df5f9484ad54588e6c2ab124b4e7e725554c35491bc8dc322700b53325712246553751d6cd5156f869ffe3886ad2955029a5b622e14e4a44ce3b1c391c37f0816744a34b81cd05d11609f80b4e59b08d8a6762ef154c397a2084c29328bd4df7133f7b7a4e19c42a741d0b2f76b9a4fb161cfb2709c2d80e9b08d75107372f0c313121f69b8891eecf974aeeecf693e84bf0fb00ddc6d1d0ec5b60d578979e064e494f3ab87e876398fd88b5b9cb772ecdb5723b6095b9ca7d6328c0a41f10a16b3e8d1cd3b3d1dbce47130f81d1e61763533ab2880b483b2f41ad270d5fd2e007565e5bfbaa1ff98cf7a57c5340a726940887f114f302e0321193c7418538ea4e8ea28cdde6451ab57668e7509d235109f9840d14f259c0c64e9e5be69eb2b9ef76d9b0bfbbd6da2ee63f6c37a655efe775f51be27de1352df921f55207826cf98deefc798a1efa4fb18b23f6003726cea99bd97c571d628a029ecdc4e72255183f2b7c915e42e23af0ddf28a71d202fba9902ec1d1552b27fcd1551106a364d89ff165e75899be8410b272411ee729a29f745678a9e7b4ede94b5d9db509731674b371f75dc29f2a6e359c01e816df1c433a5ff93e4d0f3c83bbfb6754b0b3d1585f4c7750cf498bba0c718f30e385f63d0d8e1e24fff719d220901d0abde3a2718fbde98e427f2fff391f51f1472e55479cb9f810077d9a6a224a51780ca66425c30054bd24b96ec757b69eb0049798d4a7d659870317c0914b7619b7fa78d440429a349610d89a44f0c3a10841f7d3641a8b682f3d67e811faf2e015c1e5ed92c56273caa9f8fe9b6f162d2621191ea961f6b3347df6ec145309a586d765896c1f120c536dea01d2b5350afd6567ab04c841fec390f9d9062aea40bad69858db8612d4863ae49fa5bc8c4ab84541282b6b4a9d20fe6a8781af62f03494f8f361aa1bd3d02d68746e55adf9c5201f796d8377ada9471cc6da33755d1101a8df740555a4f84b2c5bd3b05a1a5403a149b91465681d3cf7ea1e9b98afc2e9e7f0a0786f1cbc64fa2d93f88362734724babacf714694585223b8dc0ab66cdcbb03bb18140e9b8b944a92ed9909bb672d30132aaa68d7d140775990045a6e6947323f41f63a204f5a63d5c326b0f43729ad3980bce8b2d79461a636e0683774e9d748ed01adfb0389a59575d90f02072a899fe857c3b22e266eda420280fb272f467b47d4f29afc441a42bfb432fb20a0f10a0f6bf205de5bb476a2d052b9c24d258e18746198a0abaa84d971c60227281cd8e3023277a2de612a003c35260c4579f80eb4621c40972c2af10fca61c1d67e8782f3ceab9bf00d692e0b989691aea258ad7e7376ba8e4add56fa15cbc7268adc9cc3dc51e60f6edd6d02bf20d0ef73b4954957c3c867a00df40dcd47c5f9a26b9813b115cefe88e0807f72b7b717488e6ccb033911835fbceadd62bd372af72a92ee5e52ffb94687118e97892c480c44174a7ed6d9e1550fc9961279a72f5fd30ed988f257378516f0f36a9239c1d743a3d728d43af9412e91b1747ad35d4d55a9b2468f648afe6ebd679206ff2e3f7e05af15c0ae24a337ba78ec6572d39334230f8d0d9817f12cad8f3fecb3e33ef07685936ced3fb935d8b9b75281044518a05f3919a9fff314c025ec88bf878d96d9d2a8dbf9129bd86bccb89937245cc45a00726605263c9d85e5db1361171008d18db87c74b1724424af946c969de7ba628ddd000331db59d96be360881caf872d46606b261c2bae6917acb3a727e72d3b9a1a3c9fd0af1ffba47d64483d8145626b8b4d5e95ac8045e6b82e06ad8cdb7efa5ae2984ee13beaf96f3a384399a5ed995bdaa189eb3f5fd7457bd72bac1a400c908c2a040db47a0b1577f2f53427c57f90bd22674d061f55f514b1bac5be99daba62ca18cbb12fcd9f952d6d96cb5e299cb1c20a306ea608b99b7725f4435a51a50a16dc4c6d3b95491ca3f01262500d60eeb47e7229f09c69823727edf51ecbe57d1452694082da40849a4c8937081aa7a6cfd32bbaded3303a872ad501cdd8a37905996327638adce87bde110f1421a7725250e128629ca3bf216096616a077c39056dd535498e3a09650a0e4eace357a13bb1c2d3aa03788700fe6caf5fd2d11d5fa5824cd014882515fa394b370e836eeb69c0c5077789fb972ebb23e8825f3df541b918216ce1bfe43948d59bec09c479333b0aa7cd399f87205649735efcf1f29fcc8b6bfb555b36e04c92d34d761b291773d468adbb27d59576b0c37b53162e4b144bcce8ca56aaeb4081bdf810181e2adfaf1c878e99f3f08eaf35484a42d0883d8d70983534d5a75f8b16f05d8ab4f6175a1a70510cd728bc257efad00d55fda16af281ec49bf5d36147b5ab546032e946ad79b0b2bc72a83a4702d8624d70424d5ac724142801473c1c50c21a7528ab75b2473b1bb972e50e3a73881632cab6212e570dff6b096d222e293149f0bf5b4b04564803b472f2bac8eaa45a6a2b74873e6053fbb39c42aa8653e1aa907522820486310b5772c0a44d79778dd40f705264e066b170f28af23eb530787a40e9aaba85a294f8504a7a18057acbb12339c16cb694a97b0e1e607558253bb7051336916ad09f1a6ea42bcbb222c07eb2be147b393f4a7395e1a41e26a3470fe911c7930b9b6c9d72019a4cecc3ef844fd78e0b462c6fb149e9435725b1d0ee1d08833d351c05d31fa453347b98e05b918ecb3df843487a9a68e76cc0ef46a9a47b94892cd7fc981b0345449daf42c39bab224ff23c7cd2a8cc30f14d8cbc6d1f6bb032212f3c5672aaf45caac91afb232120cdf8ab8c64e5d59b89a0b97a6573616c9c2e3954442732afa0b4af232f3fe80281549d6c96f91930184e6d14ac775c87d721114b5b123ccf96d5d974ee20c1ad718e64ac81eb632dabf9bd5435c86922943adc3dda1578fd655369240d6a07ccc515ce399d514e44f4f6da10f4c8b156582fe718e36a7ac2a81b58eae5fbae5279efeef9591aea5812d96079eddb2fd2bdba01319f72b6e905ef34aa611af3b42ac06ecf3d7b12909ea0bbdc44e2ba6359d18f8a4d05c641fab48570ba4a9a5036ebf67f7285d8557a0426d7ed7bea94fe1103a98172fd6444d567c25856cb21c16b89920eccc55ced8b95fe4740b2cf1268f5a208722436d2656494f332312c91fb9534d48e30788d065d3439066020dfd495f7fc7213078c36d5ff40ba9137cbc1ba19fa8ddeaa19f26b85c2e2fdfa266f2494cf18c52f0bcc86ab70d76aec0eb11e79deebd1d77717c32b228d1af85af46eae246711fde3dd4b87fdc674cdb5278b119447baaac94f51b3a9aa91b6b11174727c3f8bc7790539ca4a2d48de111edba90b3c8db7406de8b5cd087a6b6b5d13520072d4d1e63ddccb58897a3ad8e663f453bcf33dab226d5092f11fb234d40abf8212b0ea12e7c20b1886b51856d385999ae53dbb141c2b5e261774f97407da8b65727502f610be773466af9cf73b1a2e66d92483236692aa13be771c13dfd7d6f4509d065c1b82e735476a34aa38611973636e12c0e20817f93b811323cbf41cff72e9bc9fc3fd382a792f519270f911ad1565d6ca6784a6837024cf187805b39f27a1c58c9eccb3467eb662824203c0d16d11fad65f0b71cacb0d98eeb51ab6f372873599a9ba9f619688c563e0497691e9aa4315a628eb03b7bd815246f3e6b8725be4691f7b5a71074a82aa75d799fc20c18e04f1722b96cfe24ed9e38d2aa557fe2cd609add77713b407e73d2ed96616e1cee0b683a4b6e498d1fb93e243142c65a46ed2f0beaf92065d09263b2a6ab63cf309ddc879ebf8e5f68bb2686abd377c2029d1a0e947487ed4d5bf8f8781cecbd1dc4f8691811513b45c43ffd4710e44dbf67718f5bb8603e4c475a6d5ebefbb4a4e8b4bdae6d81af0f986679d5905be448c07720d62d6fa1fd0edd19bf4f8ce83104450612e7fba54d247f4511a723b23f61bbe48b8afc0825d15b7a3cdbc1f6c8d1de6aec268cd1bde999f55012906c1e212091bce66d60b3bebdf7bf3c14ff37ecc3cfdb190cfb69882c812957279b7ebe22be7525ccd666167a9f17f41bb3f1b0348f8e0e5b144c476f3715672a19f57e635bce1df0f44ab92bd0a9262e8ab0ac22692d7f13059b69fd2350f646dc258af7183501be6d6f292385c24b6784ee8405bbe9943114cacc8c32d3b72ccb2b3d53a7a2a9b1066d7fb2700724d150d4aa69b5e2af47f311ea1b1f2775ba21247e32e16d286d671e652d3c71ca7d00b996ae5fdf783da85e2039ceb4c729793ceb3e38fe9f1b926bc55e6833e798abf88c9737ea84651cd78cf511a73726faa170fe901d89da58713203e90065849a086d305696c020802ae76d2621f62b580425875deab2897434fbcdeffd4c5b3ad189a0ea9be98cbca58d640427972ab25cf85cb055f9e26023a8e4edba9b5113abbe9da42d7099ee9f5c3864f60575c49facb6cddef19f5dccc21bd37b72c10a5135603d744e736e61d03cdc2a13dd4510ab85fdbd2dddf013692bcee6614ca74dc4ae697c8460ccb84668a8d0a5f7159f331de6fd1f8d05d45eb020cae42dcda21a27424a46b764e59bebb9bc45ec8e419a7bce2e8336f72aa82b7b06ff46fe32d8b9a23cbb0f4912e8e79959b1a50813468565529b5bc102177f5a02804b85b8766e7350a272a5201e1ed1d3c72a77763b9ba89382646e8ee395364acf1f2efc2141147d0d2b8456682f987687200ffd850f73cf6cfea1ef81ea584744c5c0422e6248c6677e41b444a8a988272ff1cfd6364f88a21fefda3fd995f0b6015afd78035b367962a54a4593e8062726e624f7095731213f12d5f393c00dc8cf269b54307ef304bd78859a71efe814de1722d12c7ad7a67e3577f477e53dad1547d37b70aa6411d9763c9f4d8173272ff592fa1e11eec4a407a2af20cb074b79ba95d648b1bac4da5af523dbfffbe72f3d09f7cff5b6b817509a2b1225ad4612d2a9b0f353696b522dd815579e2e27266aea404bd17c92311bc8db478075668923c7cd04d2f21b69a0f81dde89e7e727a050fcbe1c69820e83703a87b32fdbf90638ee865dace67666a9f15daf22405a30b4170e6ef21884ffcf976eefb463b13fdfd310e3f22c6d4fe03f9b32a8e72d16e1ac626e1222c29121df86883a94158262cce8fa4adc6e8bff6220f9f7065e7f421bd352892b8c4fdee55c865670654aef347e6baf8e6490145698a7d2065b9ceaf7d04613de8796cb8b7d3d86131d300a9d16505bf8724410c0055974212491a68e8594e744d16e0ec7ed67ae38108ad0b5fffb507ae078ada5a8993de0cb3918b17cc3815c382e60cbec720756f43f888877e3ada203a1353b7d3213372c0f996bc624b29c985094cc81c0c96663b864c74762c2a2786e1c6302c1b5a565a13edf667eb3a25cabf0a04351ffb57a18776f843abe5563a88816628ad1d6cdfc1d286aea94f29e3ce8530be5c8b027bc9162294a20750d345b1ecaedc00707b27206f17b3fa8131acdbc2d6adf062b66f403e72b13260a60fe76ef85ee6372e5e5ba5b2e28234bb4fb0177a791fc727d2f931b244203a974c2faa0a3a2172dccb30ba1908bf2cc6b140cb8a13e91f3780f049025bf7599fb9e614b6ca7d48a387c2dbbeb2d781bd82a9ef3f0e5018277036812def1b908889b512695ecf31bbced1897ca631c1080fbada3f376454c6f4bff421ad26b954b415e7d17bb272d86653652866bd7a4499b15532120d0356e5f4a6b9b13a82b5241f14e1f47e7286bf3fc83b5deec232fd175a1cd7b13f3f2be537a6b3ce78fb7bdc010d2f361b6c8acd6c6016ed2a050ece2722fe2990f5ec8788cf14188c56beea0ef1dbfb5946c7f1d69e0c102e76ed97af202d26de1a3c91456e9deaeaaf18ebba5e41bf7276a4a77359f4492bc4926b8965bd9db62ab4a6f6811c5cd86f821fa584d10214e01e09e27d236e5e5d20e95f31c5c03de82cf6c7219f3d217eb16ff457424872fc3fa3579865295b6430c94df62ded3f3c815e15402a3bb3b36622ca08d2f57233cf2b131370fbdb0bc38a13894a6ac116458a55c39b3ab9c352dc89d976cf57b7731728455fad29b3ca5524f23c4cf6b88803771c4c634fe59cea11bf917222e7f9fbb3f5df59c49334c8b75d98bc1128649e0abc3cd3a7849853a5b81d2f12a5a7df01f662ca1ac892cf2b942f7fd0719e608e373ecfd0c6f85e76e1d500729b76c9f9da5d1d30048811e4ea848890808e7d615bc214b81ee91b0b39c49d72390b331a30dbabe149f6ccb56d45098ad2c271c87b4b7c008c6910fed71e7072da96a7fe71bcffae1adbe2c28b6ad668279da440bf3b08b013f2d25918491e72838f12ee9a67ea60c165b861dc3a0595998826735a94b5be9a8367832c2247033585b2a39808db194147ffea6c77b1318190a4bee66c07c6ef32f9c15bfb702f37abd434d125f8685aa514737064becb08d45642c12a685f7eb0adc5ee33640c90ca70ca12a7b7f075533cd0fe2e1aafcaca3df91334892b2cd0291c0a24da1d932ef48aa1b9a5a1da3c9d84e7685cd39d7b7dc50a88376e97c790a9e642ed702a8bf998ed0b9315222b26c188e0e399a581dcc5e79d84c35ccf96092f81ec7255276f88d5128bc56dd8f3316f1225271e038686f489af90a8be420ac2150a72ed6ee421ab8f3058519c97bc2248a4ac1f04f1e057a3f235f5cf70ccc401d97268b1e2754d555233fb284eb5012efbf1ce13a4b322f16da57f94fcced9ce94724f15eac8fd73d581785a4b1e949afd8272bc38e3da5f6c338bc90b4f473901337bf6dc38fba583c2c30de3a98b0980a0edab9c912b11a8830e7ae106e8719772a276a89d57370489bbc628bc67c2b77dc5c049ab29d001e562ffee1f93c71d7295efcd2bd4609ab95e08d2ccb994bb15a048fa3f50d353990c2ad78ab21e255158e8667984f26c61f508f06fe628bcca5bde8c509869d4fa6c7d734ad784f21e2a4d8f00591bb31249a2b47d082fcabb86786d11f8ab5e12632678c1f6d93b46cc421baa4249bbe1092f20e28742962641bfbf05130cf4fc7ba8dd23083f19728b1530d9c3efc1385c5abe7f8e0b812c4710b547853ddc1d95b28df39c4df343141ade2ebd647c5a3d13ce03ee086a6b39af46bdb121ef355c59db692d1ba948e4913dcb8ee522788aea28b05c4960a7c4ad4512791e21e6ff098bdcafbebe727d0ccc22ed4ef9619fbbc3f95fcd47171e6e0a2260ae760d665a381537f632130b94a84059c626c6cb763d7a57566742ba31dfc6d67ab3b062b4a5cab386436feb0854569f3962d15c71ddb08845388873c7e3d060fe4bac68b519a8c934e5453ce0dbbf3d5460190bb77f291e3b52e9a0a64401bbaeda51cb411e63dd71385410a87d07688e9743b532e0e43780285c91e4f5fc1701e59b9dba5a1ea661c20b261eedaad8fdae4fc3c28aedf8794fcf73b788abf94192055e918519a74491726e7f8fa5c30080660d344a1dc2be9ea0fb5177c53f8fd3c542a50088695c7372d31c56fff209c7059f5fdda538f4750fe4a1e33d8938f761c36c18e8a6f486724a9cdf57f3cda7f16bdb50f0e4d37e136890abb4383e1ab82da8b95c61fe5f7277d7ee85960bae0d53c8b8387870c89ee8b5c3c5ab5b1cb3314b95929fb3f472b24f139a62b93b12331bc906b266d2935aed45d72df0cc97b06f10fca67b4c55c267bc28ac8a57cc356c1aa58d42575a5ec38cf68c5dd9505f6f9095d162ae143e722b08bdf9253eb2ccb5b93a2984db0c187b7dab582c405b1d55cb5a660a010e70ac549c4975c0f748db251383b0e90e72438a3dc3495bc3a54d3286df5972031d64f3e04272f55ba9db0a37d3bf2c08c4d8075f996f79e5e489d35e4b9c66ad21172a4153e53ba410ad8a300171160535831c586aa17aec16dbf36b9dbc59a85168677647738e68fdb80e97fcbc130a277c180752460853bd214c122039729186e916c8ec2b5d21c615655cd44113ba43973e3d9f883eb00e2508e4ca8672a1bbda27c20cc02df8aa2dce802f317f8ef32af4ae45ec4a85d8a33adf3be67271ff1b0b7c60e97321dc7d15b92b5ab54c379ae257b60028ae4911b27535e672bdc18f6f74ba7a8983cc257e775637f7de084043650f8d4f7130424a83539372ad54249a23dfbd682678343721750efb8330730160fbc9ffb6d7ef33a622fd201b6cd88a9c4dc5cbaefe03d868c956b14db0ad3af6ae40750b4adeb534b069144f109d5fa3fad5c78621eb6e5eb330bd01602eecc948b26ee51aaddea2060672bcd2f90101ff32a64e846fa8c03b0e85f58273b8ef07115eb81ac4fef3693704e5694d866740683b83f4d943399e12c5b157f5bcd5c7a4d576df583a5ea8a215b73320ddf595bf2d6bbaf7d3f60366408a44371f007330208d9aed3f64d89172a3e3da4530a9a26e6beeb43da976c404631f8260ef817f52bd2fcb6f461a0c723b492c87b3857c482c959ac9b3b5e0f009a450343733c678567caacab09d8772a7e5dcf2791e61a4cd6cbf2b4014eb88af6d8073f2b5de3e0dbfef40d8d17f1de2dc907b75cbb61d07bf19afa223f8fa418308473536be3616a5c93a8170497299b71f925687caace9d3c9bb1683ad58426b3f74db022bce6fffbc42406ff47251210d049c9528a4fdba39c8dd8a03ece403f89cec1e77a1df54b2ab1a838b72bf85348ffb270a888ad1d69c664b2a4acff3b2b49920c3bb2a830b01658a5772e58c5038bbe927696280ac61f2b074e8892604309df607adb6bc4b8786800d42fa94d7576d9e1a95c59a4fe26b693af4ef61a3ec4b8dd97fffa87900bcf83b727bd93af9733f46df9dd0d1e56d8595ed9385b7aa608ebe9abd5f9103b735734f8d7a3e27c38507fa170eb29a2faf7adea5b4a24fab546080a2b8c92073d7485db6ba6f0505b1867d97de4ac6cbcfee0012fe9f1c659835caa37766ee67673f72d2cd2c19e7529622da0addadc87bfcf0f838eb02888f9536aff433269b4fa772e3e097cf4c27d08bbf2f2c450574ccf16f6c653c5ba4fe586a3ad72fe6376f6f33753920b398c504a5bfa36e68a1fe7b64348f8493bd1313afd0c3fbf318405becb97f01747c9a9fde8bfcb33ebf7226fcff46c4af44b8ff6c99c8501fda950ba087d61f69735c3a571a2432daebd6168f5980ab5d5f44caffdc0e590e334b41632fe5b1cdf2894c58f4b3ad4032eae13cf9190ad83c1f5836e64c2dd9b14b7226320016787904d8c5beb2b17b90bdfc6886b97a6efffcea9a96d14b5774d127aa7cfef8c2987522cf82b5c9f97a4670ac66c7979fd5a6709d86a8682cf8057215b6948f12348a277df1d77738e2731f5e60af2cb4f967d9dd15a0b776a0f972ada75dec0f78358d9c0b17ef6588dd6252ee126d67f958ecc94e3c3b3e45665dca9746e5f8db92dcf3331f64bda8c57ca11284f4a40a006aa034b213a9aeae5ff85e22695e1680d8b4248642ed132396d2bf3cd3ef0f2d94b6808b86570aca7207bf7485663c39efa91e1a13fcf5d8a6497b24bb9aa8f92ea90f6df30c48d972f37435e58ab016105f88c0f4a03d14d28994bdf89281cd49bfd8cac2e33f884dd0664f37d8aecd35cc160c44719e6dbd7f55da79786396e447e53c23f5d1a172abd6008278a55ba26e5e7425939347597b5fe5b3042f8d58ddba34fd1a371b40d247157ed90e6c2e063c4eb96c4f53a69f3cb17f1f61cc16a4a9a5956a73e75e8f1d8ed2dc3994c3689e26301f749f4ee4383d56b0ec7162fef0966cf06e6072cf34aae74ebe199c30fd193d3f367294d4f91e2f01adf1a1c3d949650c5dd35c9c818176914c228b181ba40da81fcc3ac88b8436f66481244879909025a94372514f5281919870d24994191ff7c6fd7b2584436f6be37319a2d0f16a1ce8bb44e3517d71c66b650a55fd3323ad87d130e3a6e15df4c4b2dae8eac49e56ee2f47aa5b24f80207c1a2a528381ef5745a5aaee691c4c243df722d952e3d6b2f792f840d70629ec5490bf8ab57a12e28959f0626fe129164f33510592dba8b4f1a1f45c54b9adc7fc68e7eb4d88613653591af1527f26d64dd6322769962fc83c07295bdb40f62237bc1fe011252030f29d117d3540af563abf13ab4f4dcb2dc5021a3dad32e76588c03dcbe36a7ffe17274167baeb398e1c5f92de66ab0ce93cc463f484c162804ba9a2083979ae42955217afc1eed6748ca7c504e33138153437297b7da6011964357d3f8b8cccd1903d24727a0c1cf52e4673fd51b61d2095a72e483642c4aaa65988c6007a403bd535bf44ac83f33bd64d3c28331ce472a70722a902145e076994673379a1478f4c6b3fef5b2dcdbd366031a535d455bf0d772599ffe065226e0e61a2a618643230958bc71dec6de67c7fa154fb80b944a2d72a877f923b67bee8eb085e9537b4fabcb13bb15361216c9c94863ae7738a7e1720623a1aa84e86f992bee5bbcbacf516096cfb98473e635bf28559af942eb3d04c4a6509a59957db11a500db71a6a3a1df38e766a8189e9c51f87dbbfc8a51c1422fd347ed0122401f8aca279cb6e0888533fb685b8679c39cb1c1329142610721ac0b95a2e898a0213189b45c9e059ccb1afead5aa0a98032934202c43a22d724a2f2028bf9bba1791659bdf9e98b4baf235ec876d7637389c8bf60afdf8f6430c86fbe0747cda67642671cfe84657695146fe499daccd35e743bed05bd1c259561b92d6cfcc7b82a7975b882b6d1dcd33467ec88e8b5e85a149b9ac0c3b4e600703c8c3446b2ea24b2496d6ee47134eb0fec2b27efc0142941916532e219f16cffb10fcaf7620282a9cadabe79e29ce18b1b72e509c7c4fcc01f2ba0dd27421f1ae1e2c85697a11eae9f07f7b2658966d34d5693909aadc699e9de1f1afe67271f0225c04fbdfaa53981d8e426c7e3d81b507bb296790ea69f30160583f6a726629c6458f5618d86b81520980e33378a3c7e3853176615a2934e61cdda0344e7c524567cfcc03b25a0a8b30360d0d114a0ffe9defe39d8d90c84d385af13572d781beff00f722bbb72f8f67b0ae9382c1966295f9b8545431e60553520e602852d884bb78c284ded8480f4962a852c79fda6f26f25923af653cc198914e603144b5ebdd5aada434351595b30a6dee1d669ba113772ab120db5e487f889e630818cc95ac8210eda428a8d443db0c33d1647bb4be42eea41ce336cf2e906166060aa3e91467656e99f5efd5a2a289527dc7acc9c88991a565c12fcbe09eaca572693bde78dc1fb1235005c165811002cc3a572d7a0c3236c8121127cc83a34872fb3dbb0e7a1b7b32a711ace4b8c6e85971829a607575bdaf9dbbc17505830d727427380ec8ca48a9cb95332c9adb59b23a6e41199c84feac58d21a4b5ee5a17267636e1c462e4d4fb0d3708da97ad20b4889dde7b3ec4ad4a70703ddd5588f212795b241997a250e4f813a1d76ad2c2b0a8f99aa941f7ff96b9917e827601772ad8d22f4c83999c7d966b7e721462165f3a4f2d1854b1aefd3e259085971c072b3d82daa352daf70b16eae98a5eb0f254560350e0eeb862c0eb213670916cc72500ecf4489e36b04387794f9a8c13b608700ed50c6c226e3f18fe953460f7f72e9f6ce1e77285b751341f85c211fcc4078c1974ca528715ac90fcd8667017072fadee20de0620fbdda74b768a9cd82c2c53af70942a6fc05d1eef904e605cd3d34e5266d000f4bd13e4f105c5d8af87a5a4203efbed01ef5fa2fdb38127e8b72e12e20e1af79baefaebf9f1c70c0b44a759e2c1d8845ca31a7d8e5c1a1ce06391d702dcceb8e77267efe2cae4139e37bf139dc677d9a63c0fa27d9722512cc71a2f80986884a8fc522cf45ab67a89fb96ba7f9e62daaebe951fb0633f16fc60671f8f263b3558d54aad88fdde3031874d04688bb0a952339cd0cb505b50d2057c54da6120ea33cfc13dab9d4ee7426f43f220ac0cf4b2eeaade727e22dcdfe16f119e06fec44723885b21a4ede730e8b7c8764a3a42a0fc153336327b7065b725e768d86118b13d302bff308d568d7b7779b4468880c61b11deb9ae15dd3d71ac901da4d46a8666622f75d02e6726c84c42e96f88451bd7559c16c5599349f22434fb19afe04dceb9767897f737b1d4fae88e6d061e7871cda2fcbc77f6b42020b51691127a7aca9de6e6e54a5eac347eb2669bf3032db0f59ef7ca0b1c57843baf3bc8604d6acc34c69e42629e09db1979b2fd70e54c8bfe80c99d178dda072b3c5aab4d88e9a5174ab74ccfc83b201e54dff36ada47406e444aea6e4caa6725cb2b0a8b99a7702078fcad5d4243f04b7bd2e922d623866fdf4a13d9f07a11153bc2132ab3e4aa75c412e96faea6a89a1c5a80ca64a6f1074dc906418b50d724b0b183f5e707c263cae5e64f70dc118d5e25e5aa31c6350e728f352d39d0f7223b26d86fae2a1fcb199b66c1c695194922a4c82e6fe21661ad679b3a810f6726d5c64dc88c8d9e80a8fb48b6bca65c99f65848525e129b650a219baa5956e72e6fd19d9d38b72822676227538778fe74c98710c59f2b440623db3da0d61750bbd6ed9d47af0b0da59eeb724adbe1e4feb6a2334990057b802fb1f45905e0a7272096bf0d055faa8ddb3b0f7e8ce1c2b96ae6fa1a677cc7b0262411cb242e94cb218553feeb3e558afa4fd4e5f8edda5a312df8905ce3220082cb848e570d83562260301fb4e66af2ccedd46a76be55568106ef00c9116a8919df8d633b22822ebb97d55cdcd0ae4b6c4a7600e1c53ab315d693e5f91a970dfbef3f3026e3272a948d9fd5df0e4ba01d4879ced3acfdc0a99cc170ef0c9ac6aa23e550dcc467243bcb1bfea870c8facc3ba0106b73234a1d59ea6e921634694e6d7a2047cd527c220e993954e45006d2f1d7a6f59f8e32323075deab560adeeab346cba4bca44b1fc1057820f563a4216fe32fc3095a6f2a48c8b228a5c14ade87cce5b7395502af99c0ba1122be1edcca004f7bcae093ef566c55f3bc3a2e3bf17a73501ee72567e0606a50e3169ba434253f2fe221c1ced44bd122c4da47654075d04c6b5720aee6ae17c686916da20d9c8ec9d2eddcb079d8682570ba01090ba748de96c2e78cbdc7a8967490574bcb6b149ad92ca89fd652c5d2eb4857a66e9b1b5a09c72f5217b2ddb6a252ccdea1302f03954427c7b2370a41bf16d6738b440733d5572cf1af0717d4c95b6c0d2bca76f60af3ae2965229ca4025f15256056d19b4837219439f487b925a822ff5db09dc281d6967c8992adaa15a509aecd3469da53113ad01b5ec2fe0ee392a0e38e0a7427113420bff92519f2c6bb528e1ab06d24d726015ca094f8cafbc7c78396b758166365b38d37b5a599659556f7f3fa5a99b2a5685eb6ba09dfc33488b3650431a93ecea5503be23834aa184e991224e71217274a634c566b58b4b2b6b4f15780ee80eebaa2ce2316edb06a95f6cc65d87cb7291c5d8e7a47bd5ce50ee3052267d7a133a2ec635540c7cc2fa6d03a856c9987231fc2a1ef7dbcac6a2986882ac4aed4b4f2abc5d0092c25112cf74361d951a720b179924b74c9b621ec5db357bb3a75cc5a37212af416fdcbc697e13ba2b4e728c8b8b93196dcd93d30124fe8979383ddc22350d5dd436c192ccd12df1c1c47216b878b60c340c4948c11d56e72256febbebd46dee32ffcc7df03b0b0fa6e4728829f80281e1928c041e6b186dee6777efc390c47e45ba36f4c7aaed6401b9723f5a79eb9ec7aeb490786e88c872d826719e72ac6b3a42acf3b1b0de785486719dfcb12ce21e3cb97f6dd331b9f1eaeeb6b2051dab6581af889f9a253fe7c672ec8fb8ec8b8101d602e43b5827b465268d3a6b00cf4330c47b4716cb18a407721bf2334d3e123f198f43e12a6404aade53834f98ed7cfef46ff9e650858c46724505c8b607f8943dc1fbe4e08db94f7b1fec90c8ba9682b01aa5c37cb70228723dc2090b91c0262f2b60f5cc9f1240298f2b487320f4d2f419c2d2dd0a321372911eb484a0da17a7e139e4624cc48c0c7cdfa823afb77987584738ca48875272b7b94a52921517a1bd876844dfacae1a962b84687336365d076c7833c2adcf72e74b1f74aa5b6e8407d6558d351bb6b4382f7f8200dd8e4bcae1bf2c8b1f86726648b42cd1609dd1597d8a506d397c5d9c127d77f663a112a2fb1898b643625ea37276440f70c38a1e9bdfe6b163b662d9485dfb9b56df8b9261992657f595276e277e3b07a97bae9ebe7b92698fea29a6a571caa921365a44677a891f38185dd2bbb5e9798e8ec0a31255a2b7d1b45f176dad28c56f4363fa4a5264ffe364728a7afa70a7aa041776b7a6adc2a5caa5bc0d7a13d54243793049c3ff4350aa3419221a56c5f5cfeccb61839a9ff56c00ff0f408bf4efb4544c961025d0598b722b9ffb88c3700ee68fec9fe98697ad4f7919ad464bdeb347b313250f55f01a1c922e188a6785d754979c6466fe661160dd458299dddf2011a74fc74342df63723c41208e0a678706758d666e0a56e8f03a2d2e1fa14cd06586e1415390888c729b0b13bb0c98d528ab9ed3eed02cfa6bf6b8c8ec29ee4fc2e6c3f97e59a4f14677ea5972e17b4bbbfed677b77584b28fa0345c4773bbb1d7be8ab396c1484c67aec7a15d733766fd8f178e77e9cfc66513c8da72703f08ffa08ba10cc97d9d72b4b7a20b7051efb7d812e597c117c0af568f7d03408b2dc5db6a3fe8378cba4d14fc0194e24e06933099c859d9e2e6726b7a79c43cffa7ba2ee88bde1c510d72816837e29a14335bfbaf2316d9808632f8e4e5c7e5dd80c973bca748ac74e072aea32420395073730eb986de8cc859ae8cf21de6d647583e3cb33c2b0ac3e818a785d3eec89ee8893b3e12434ff35195688040e788a6772698f97bfdea577a721817d67e9f8f15b54656893f2f331dd8c8529d3cad643e057983bd95e735017298368ed34661102c40db107f0981e3c665de2fa2ee5ac2dabd40eb2568d7503d2768c1ce9bfaf327f09fbbddc02d22e0b0244ed483349c669bc0fb94c7227972b4d09cc8be37bdefd2a109b23ce28a24cb57700209efd2161d5afc7e778db9727f464038c6462fdd5448de13f39b2294f4dbd05e1fffdc5d229aac4e2ee67c0e2452117934a4fa68e051c18bb32d2d2a1d2f61ad50ae9d79a19c1626f1a8c628b4036f6f9ac850d02e72f849380742d78693c08e3a779595a4b02699614ae872387e8f71d37ce68f1d65ab6a0c8334e79ca98058aa4ad9ba51bf71d6dab3f2723ce566222f686be0c86504232cd94707312f3a9d6abe9e385633cebc52d8396d8b1bd854827c6beb2edcda0f4de81185c47288c201038a2339da8effef2cb254b82032d51a7f7580d8bbc56ea9d856b54920507866cc94f01c67dca217649172b16f56bc70e9529411b85aa87b909ea52880ba3323bcd2ed441743b33af9c672676fd64ab48c2076fa838b4da85efd1ceb454f2fbd476a4a2d17b36021a2f3724cc4291777b7c6dfd18ebff276be8bc4353aae88ef614700cb2a12a072150f1433c79edd68fadf3f26dc156fe54fa2f0ba2765ee5c9be6a566a794de793f7472d93a64a4cd938374f7e1b6a531632a6efdea35c4dfbcc08b4fb3fd4ec6b81f72d92bcbae142472fd49ff03eab2764dcefc39c18b8583256eb097dc930b89496a502b215f6fad7963705ea5f6d6d1bfe40c8a75384edbd9c6e503b148ca77c17202ca1177b30fd3a146d3b2db27bf1a96d3b60fac937e00afdeaacfcc339b111c63073c3b770fcea12f5e5a4d9afb10019c0b31a7280c78f513227790279af0727c1c3751c35d1383d51986507809599db4390fce87a1bc5477fdba06ea2844727dc782a938d36af9e350d89fa4e4059149c1ce77615f07cf82f6ceeeb77bd41daee5455c99030f7933ed70c6271f33a696667c65a1d47a65cb5039e421ce1030b489b9bb37ef00fb22b6a1b058234758ae01d1e05c689b3de7939f0bf840e272476b43a4cc600e86deabcb598d8c9ae108171d3814ae243057403c64e8edcf14d98d818c4be78222f087428c2d3f24ebac78937513542561c16552537400de10ae14f9cf482626175aa6c6a5dbd10d3b1a73268408581b120dcb9b48796e5172e0c1d88a6f511b8ce12b8bf1707c45e07a413641dae6f99ac5f3b7bc5efe58038c3167dd23d5247d162c09ee31d614968d6be077a3575676870d6b2e6325f072cf09343f640f9c355d0a1184835baae2be14fd39165ecdd83b255cfe097e4372468c0cac22f3607d9822999dfe1efb0d90f0af09c99cd2fedc7f21eb72644b1063215ff6fcbb4269e0087d7c61ac93f05ea63b8a13928239af1020087cd80d72b7defa7054aaa33b505f9aa28a1e1758c08a0a1ad0bdaec4199f1d7977311472008e38a71a2cd8d64d9af6db34d8028e1951a6c311ee5e21eba786d40cec4d52f4fba2a59793142a6df7576883c6e23bdb63797ca6e41dd2378f9c5031b7e372dcd9e0b101cb7c1972be721914ddeb3c2a3e85208da3499a60d7036a8784be66d14d4d4e310b9a08408fe700d31c6d2d201e9785af22e5d30811d716b619c823ad5ee38ff8a648a0105a79f4b71b81929590d6732c78ba2861674cfcc11f9945b068b12aab3aeeb0568b7aba4b7a22244810337b75dca8a3e54da87bfb2e8f7298c919d09c934f18eb6b0b3b42ee09c9fc6d6c038a2bbad410afa3a442e3e0723b01d97894c23fd626f78ebb9a259c2cfddfd922a290f4a9e0ddede90f97fc1d8d61fea804ff802352fefd830da5d917b91dfee3892870c912a0a220f1662a6dd19f9b15ab46680b5829a66736799e6951eeeb875bc65489f32426a017701e726c9fa136124ff0492998dfac2e936173e6da6626384330433383a24f2ed2e3724a4a67ab75b7a9aaeaed3540a0f5c5af94289b5b8f43f96ccc61ada780d585728ead56417f351e6bf384006bacc2475a11288c390e0a4fa44ddcd3712e47421ebd08781a9fe945be5d055f94a2df7dccec4c023148b56a51e5482e43ba6de7728f94aa54d5e24097d1ad1b350dd89999fd6003a04d154727e7c99bc11e589572a0de7ecd6e59bf68cd8d5f65c76726e8d8992cd9d53507f1b9b8a0facecee415a5b2cdcc0c5fbc892b0ac05e250cf18b8ae31fa51a2a440c681171d2cbf22272a5b84d3981feec01516ce04f89eac48e9e984420bdd9f789fbe78a4f622fc472fefdb8c173299a55e5432b5da473e52e5d4e7844f35da1b2e27f9b205ab559169f09ab518a652b332712fc14c71bc39ddff70d864655f231156ed18ace933f555d072caa942f0d1b32e898d9ed95e22bc68186ce5803f721cdb3ec1160df3c723d37ee420a940a5893ba34261c1c8440082dbd9a55005f1d63916e14508e05725c1e324566969cdca27e29ec091cc89f007ce60dac94ebb392ebe8a2f16c177226fc1c0209ea52321b0ef23ea45da62e41d5d48fed84984e18981228c7b5fa5fa0662cced7d41d679e478c7b18f55c3baac99206b98b4b63946938b6d0dc1872ded805c704e476b60b66e1be8daa04e3f116ab82a1a7a67198afab3d92c00071766fff759dd0a679134f0be228280d47e34f2be980ff09d065df8a3ffa6ddd728214d5354ce5d6a62482ed58ed9ccca742ef198155d3d870b25472d3cd3ed81a1e191e04ef08b79358c8aaa6c7a3fe6f8c13c079e3b964f0af5c1d523820a52467cc8197d4403b555a2db2f3c50e2b7f9cd333ccbd61408694b393476dbd96648c123f120c1b307619aeef3d3a078aa3e2ed1915068fd7d88cd51af36a1e39729514d14a5a065bc489aa64fcd5eeb569092616a875f41a1f2ffc3e1b29f30f72fe274a52dce692ed71d0fc176a2b4d7096752796428f55cf2ef26b0257f77450aa29be57cb9a81e60fd2b55c55463ce56206c8b496cb8e3cb6bb4577194427181b7187fcac2b87ae338404796fc9b7b8a71cce3813c3dead0e5f18be59e4286e8e682f41bdbbdd09ecb50d59d0558edcd05ee987a1278ee6fa0c233e9244ae706f093aec2675425ccfd505946d2ccccb59eb1c4de315a1df7a5f0815e05d6037dee2f6bd4195d0eca0207b0bd5efb80925d5896813976859511712da98cfbc461bb072782f3d21f2843cea722fb9395845caf48eb1a29d22cd105e5e4868d0726c6412d8f59eb332986d3197055d2ef1d16904b6a19c12eb57403f50f041d6435acd69704f29f574c81c958b6ba7ec0ddca15b1566eda1cfdaaa5443a53f1a72d95c4f4c0bdddbdfdce5cf304a2930b3883c4c2296fe0b61991d04870ba6037269049980046bbae702af70760d5e0d68d8a0b6e8826c6d534782129503e212729e65aa44d9952a8d8790128b8d1e0a1e115fb1391888beffa6c6983422e5d5723c7f72dcb43f5ff7a5852e6b2ebc27b6a87885c0e3bfafd433943acf96fbd3725a01aa5933c243636182eb50460821b4d36821e889d0987b390f756bfa99c746f7d03e5f50355740301c4b123e486418ccd33191da8051562ef1e6e59c2a0f72a40373656a774240e1314150dc7940ae8c875c9963c2f6d1c0e31f1e56828072b61dee1243a0af55e731701f0dd5f5b7a8ad800f542347329d961bcfdd1543726b5983fb39ff4bf3062c4267c5a75888696828b697cec2258d7babd083f7970c9de0c70bdfd35a55a06b88e8417c91cb88d26d2f9ee991e4964187b83a038c72dea4a10884e6cc3726b4903b71eb29f48634a1c3602dbb4ae9a62b2961a0df05b5da0aa34421530adf402faaaa46915e4df42003fdd0c98ebb87b92e34d45072392e15b91d34651b82326e6a13f27ea2c30e4060dead7e2517a5374cee3c326379264f904f252e79a5667b18978ffe8943142d82025b3fbe55d59437faa1241b6467fc5736ce96c7ed8b0bf14d86446c7ebb5b6170c3d5a4cd2d2274a1c80f72064caf2454a0210b35e16a05b208fa468931475601ed306cb0e16c74c6cdde7263d1d21daf27870271749b6645b005cfedffed6aa2d6cf4972a29aacbb67162184dd0e43e4042c7518686b8cf6b3b539543e3f9390ef06f5dd79c500b1677a7289bbda29fd506057c8c8376f913012d37ca13d5544a7eb990fb0f2ec8c91080b30f5f4bfe01f162f9ac408350642c1365b7af52171b899c013198eaccb50bd72314b2507f9e71bb8d2471f762c6521988171692724f1ef40db5692bc1a1dd127c3ba80af02a5e0b02b1e34360e49974307be7ae9d92bcc3e02b21822217aab0742b340461b18b17144dc08656e298abe0f9ab32cc5f8bad5828e07b539eff0726c5937b4a31c1afaece615cceb2f63720cf56d4b3e7fdb671b604f44d839732c94c783bba6c8b00c869ec77dcb10fc0b0c5698e2ae99f3f51afb8294dd5f95726d574964199b0df7f34451db316f41ea4432edcf807b21f983994ca6f29bd272eb732eeff8a79fa02746b3e9bd6948a1e960dd6f7d5fb5555be4b1615f29d972873b43af18cad6d3ad9de17dd910634d37631071af33f157f34f09281f8c4564e33d6d5f8a00cc93337697846a5d5baf79e1c074161ca2622d9b102872c4e231c6fdad23ecde13a5e3e2f6e58b096a6ecdf57bb7eab7be02aa618e57eb22c072768788a33ec846894c7125ba416332e41ac166fb9444b539df2e54edd1aaec7275f8a2d8139aff3872ffd257ad048839ca39df6f1a242d849252abe636d36d56f7307ebf11243228310e3c32d4880eb738e569ec2dd498b0b46813522ae9ee72d0643cad223999a65ce94c7567cf229ff3b579634a6570b6c07a7bf277ec806df9a7aba8a842189d049e0abd9cfeb06f90a9350c5aba5e6d1228063ad5e77f592ee4463a2147cec85cc48ca6643ec532af996a7152aa6ec01358f805a114db345a10f8581bb8a7ddf4cf356788cedc469721d9ab9ff01627d332ef93e67f5d72d6eb0de38507acb5c55afbf35420889b73ecc0a6af45b18878f3580d3256481735f59844fda274e7531faecb9d9a37e0892ad2fd6cf299ef84dc98055757f75ea9adf1dd6f2c3ebf5d4842c092fbbb71c59aa1652e3a51d4474f5c8d99d61d72f7ca0d97ae8c29444436c302ba489c392738f7ff553b3280a2a2d104cdaae272c4981aceb1be2e970423080d53529d92cbf87a9856b31da9158e52047315dd72d2f3fb8101b40dc4a770424c8339d118f51181b248ca59047370ea82acf356728ffc27ec982421471bb7026aff0388856634cf57d362c55127e6475cc4b38572fed067d0daf309591ad8dab13d31349b66e0d72fd1b907b487049447daaffb5b3d19a0cbea9f370358272e8b22443d9fba94f97e0825001805e7a3e3ba6465497cfbbbc96eec859fec3a3553d977f972473ec20fb87602a9a1f09686de956c729c9d2f1fc119f622377ae0fb63da38a90a38c5085abaa5c6e6044428cbc5f572fb73e8a8d1fe4210681939ef035712a15c44822aab425da7432dc8b52697a67266bc396a4dda9c335009174e7d3bbc080bdc261c51040a9293190145524d2272fe76146a74d39c10a11ee8b48d071d521b834b516a9c7b164c3acdc161a268727b48068663114cd43c48c7a314036a37c7578b2218b6cea03d69d0eb9193b55f4a10b0b6277b342ee5f175b126d1b95ce18db87a815f916f101f05cfcda7824478e70af804552bdf498d1015c107bef90987b5fb8d1269524743fd9cdaa53572dd92f3621dd453f9954f70a49ae47c8b543a97bdd0cd656886f6d3712849cf2d20374c32ac40ab06fff6f78dfea35cb16fc1d3c85b099a2b258f58133eb62c72efbb9721e6cc77444dda0da796813fc595ddbb503f69ab52d87bedfd894a14724e888fe01bc32fb83d339f69f30915219bf843dc782ea4a8ce7d43448e51f019fc00a862c9fbd5db14d8774a442a2f2d7173b0630e7a6258fc04ad943ff9eb2bc06c9ff6968ab055ccb7184cb90703dd7e6b2db39df19dedc339ae134afc84439e468c9154afc0f2a00ae71508f606af37303e5916a6f5656e32e848b1fffc72f4b78aa9dc09f39ac70c15dec774009df55099b1514dccaf2a753abadd4c607206a4d9508c0295f69d924408a848bb98d7c2e61fcc68d17a3da2c37cbf61cb72aad79cdd95c247122fa0b33e7be4b6df0050905c012983f32413207feb060772147b4485329869e3a5dddc32101c7621226c556fbd9f245d6580dcd20ee34172552a762fd632450728e11d906907a2bd72593dcb1d12f2478d837d04a2fcef727b23dfb3fd7ba2a85fe4d8cc5a5892beaf2ac36ea3ff25617d32f2fbd124fb5a98d09e29de7c6cfe17bb666c6475060792ffa0748dbcaf1ca4c4cb73c9b728722e2e330d7414a2a6b4b055149ce2ca1b689f3ebcba3ef3dd8df933c443ebc2727a73707b9385a642c9aa1ba17a29938ce88e4c68ae582c00c2d0b352bd0ff740afa71f462f1153b4f3d6839ab0cf78fb83910f877fdfe650d1cb0d0973237f72efd8887581baf2ac67d5d57f44d6e62e744d84487fcebbffc489ee73092c63729e0c37a4f6e83912333845b2ce3a9c7fd84f5d947b670c0e2a8e8da8ffdaee724f6be0a46c26740bd67105f9806c37bf7e5da77e5b837a3432902bc1c6f00d547219982b7f2967da3b14ec7346919f51b37699134429a0fad4c229ada6cf627227a15a90a8e46c724517c13da368dc0ab476ef54df997ed964a87b945cc3b672d470709512ad428b77ed356300bc8d0f2564b483d2771aa00146741095143f2e8992f5cc9b191f8b284abad1ed431974d016fb7099dcc902ff150b9be4429c722632ffb6bdb20883c7ab6161efbf9a012b60203ed39bea96ca6acff4aaa2647256cb66292b199f1906845378c53e06d91314b471ff76b8a851eee9cbb66cd77235d912b20ede0f883ef73cdccf9a2265aa4f07b09042e873c1f0cf2c0e060672c7ca24f4742a08a7fc4d710b08ac16825d433d34dec8fb9cb2ebc5e32a99997256b1a52150aa47e74c257798f7ad48ef16a76cd860acd88cf4226c3d1f5a66721f8e9f756f028dee3f88c45a9d9a734cf48b1be0ce7204c42d4d963dcf17aa72e5d613f0897e936049d98ca55a7fe412ad0a0bc9cf7d35c84290d7bc359a747210f81cebb22af20bd6d0fd3fde189e16bb1babbb3ecdda7856c580ab59bc6c31dd708ce78f396e0d739bfb212a225e3412d39dda61d22050465871353ec6df72585deb2fc95c6c7a6f080165e680d72902f9ff5d9d0f2b890d7ca6bd691649308f57363db6b978d18a205d21396e8b55e63a6987c1e2cf4dd2e82b8126ad50724d924f8cbd1e861be2730a02eee57cee7306a687e0d428b1dff28d658d168672c90ac3e7dbb7ff0eb303469dab7bf7777cfaef7453e47866dfa63b7e571b0d72503eefd4862884d4eee1e9416fd2f30d9e77e7707b3cb448a50645fef09ec272ca249af64195a9f3ab563ef1ded6d97a0bea77490eef6f910910b41449d0d907967724addb0285a22f20f4dc0c8d588b63e7ede7d4cd36ec0d631e885e467c34aef134f710722a51021c452b93a8e6d1290fa2fad30b03af2b32ac52a8e630721a3bf7bd3f3d5dbfdd7ad7df283e4f807f129b6994152d9c022fba02a29ca22d46682832df13855426d558972b1d1ad21f8d1f9c82d04ec30aac98f93d874c728965b5483f88802d068908f82474f67f5c9cbea2b2c5a2633cd4ba863bb02f2cddd6e4e26fed78fd44ff6b54b786026b8fd77a71f54ec88943172c0862440372584f3c0d935a217f4fb47c44b87135d89ccc154aa31325d8ab3a23f55ec7f834bf5db40dee4ec81e1a83596750885d11683d0d587bc34a9690c5c14ac2769a723101d9c7b8369815e5216a33d48ece9fda01cc9d4644b82b9e95ec470069340b1bcef538aedec56683ec2910da4a6add33c31930acc06c7a98a27e1c109fa4383b85a135e30d88e4224c067281141882492c26dfad9973d95dcd0640f38e770fdda2b3816d1df5f9ad89042616715a7eb3f161cc5bbb446367b88b208a3d00720cb69d9401ae8d5106f517b05774abae5ac7f8d93fdbdde78a2fee94d746ab72c3aec2124b703b48fd6a8a5f002c565a8d66471b51a9b0ecf58257c69e9ca51e9a2e6d3bd012b89809686b1b8424f99cb9bf08d0f1bbf0314fefad04435b3972e31ac2082eb5cbf3e4e133895b24ddb72a600a5cec44097bff6b49d11cd17f3e0bcadc01e208da6536bb713b63f1a88f3bd4147b9e279865f54f2cc87c6fe17282f76cc17ecbfa456c1e46c870ba22dcc44571fe0508fd999a9d2d54822790724f7665902a328e963b43f377fcc04426e91eb39e259bdbb41b31f7116af6b27227a7082647ff47ba337e7e68c8d7b9b3f545397234e0d2811ea38b6276795672ebdf705b60c473d36168a633f50425d9708ba6bda24251c15950f3a26b9b577209d630304448812fc21c88a45f6f13bff25a36b1f1d4255c0a1a9a338cc0d32e2ebfd8af62205b03232bee7973e0cea0b7a71024adfb0e1cc129396fea773f72154a860deac8a37c7d59118c3a2c74b38e11bbbcaa03ec134080c0f0d28e1b72097a0df72046668ef9c7fff36e5be1d8fef63a280e9dd576e7aac4ef0cac5f72d6314dd5cf4e30270fb70e6a434984d6dc2513ca744a23d457a8252794c8b2722cdfc16b01f8bf9f44421a48b9191e04e5373168194e0e26238e0c0f4d47225a579f7c3d1e8c848f26b618aedae2c34777e31dbaaa61418c002a0d8674c2b572091d85eac4e50aeb7b4d0820db50fca99269335f5ee76dc94432fec33db6223fd4299c69ba664102e0704e207a2ba59d8b9fa7d92d85ccf41728fba7b7ae8940ee22b29c25e02ab894a260117699a5024c94403b8290687d2b675be1dc620772a804e3407a1ef12d149fdc181539551c3440b671f129e34fff67048778306961745f9a228f8db8eacb5852baae1c4dc52fcad04fec606300a2fc5dd0d887966eee971ff7df6ffc7cf43accec7ee69498f12461e5b2eb4c68c9c0201321063c1e8e9ff8f4cdd64f2f203d345e60550b52a03123488a91440f3988f2f5dbf5294488e18bb4bfb1b256533158204f2b2d07b91d923be77166a9e4e62b55f4836026ce3bdd74a92d2b3abb30f23a2c6deacb184fffa12d3fd867b3ca988140c09d2542b91e64e6bd3cd0918249f29ff4cd04a75b903b6b7efe878518def643a95e2b55aca7c3c71b70eedb461642e7d63e51cac843966f69700d1baa3f4971a8b15bb66099badea9c70c60298d9bb4a34725d16cf97f3d161b1c1c4efa899c5105724e90f43554a5fdc5e77f9674dcfd077f86f0d2e026f08bb2c6fda10152eca472fc7b0a096a504deef461550ec51c12cb89dc78d157299d79cc6f31ff34d86e721a5a4fee308f51dd6ff20a339dee7af4c7dddb73b4de77e8607dd62fcb43d8726dcb4e9f3db80ea84301584b04bb3b206758d0a6d9f8c55ab48cad147f93ce3538a675868cf0c396b1e3b3a21a1a69b0a6e6bf36b0a008ded2c343e062e2b3729708b7f3347523da31278b846602e76987a398998447596496b4d2622ae4514eee2440c60cf1d0b3a84338450bb707cfe282a1f73e3685ce185e1acf18d13d3c09cf48c85e9dfd766146ebe0fc2b300b6574798552852f8b1c9d925d4f99131598e4d8983e3e8e081f49ed728ea4c2aef9bbbf682d4e2bdfc978f917e4e360720cb06d68c62e8ed1a24d7f5e6168dd45144ece4009e8f0eb84cac673079a6d72c620b6a428d70c82035d1bac8ba386e108f88e41ee9ddfc185ba82cd5eae4472c3d393c517095a6b87cb3ee6a3875fff26dea86d89bbf9611d58d3fc661f71725573a7fe6ad5ce15f5c846958ba0f0692798f1e295bc24c1ee4061596abe86720dcdbd3131065d97b2793fd5bece562a4e021a2fb636713c301278e6bb778653e60e908de3fb5de3c4dcc5d18aff5fabc0e491f99bf8573ac9c65757cd0996182a73a7576fdeec8d76039abd11c7bb9c16a10cfe6e1e9f6f710040d99cb8c772393ad14c0fa806fd181ff47db59c2b56bce773c39b4082cec8cb9e846928537299eaf276bd535881fa55cb5fdce9b46b900abaca587770a6998ad33e1d03ac08818eee2ec115cef5c8a91f1b0c94577fbd8c8d1bed49f608e4c546f31aad821870ffa672c23331dc6fdc8e5a0a836134ecd5623818d840f7953ac99a7d493372734d3fc07bc110396fc727ebb929dad655233821a3319849879042b4d40fc67233389b245491eadd04bc08d91d99346ee86576d975811c6846cdbf15e6b5f2720b59698a73f5e40a04c74ad79961fae16e6e7f85b52cecce7270f644ff4a4261e3f773479f5897b061c5cee7a1d26ecbd19bbd61bafd7671bf5b98c2105f646c96ff3f3c6297e4c67aa3817fbb501a40b929d12fe632e21a2de486aee3158f7244076c5e7a00728bc6a46b18b7b055cd725b3d483905ece90d33db4018cbe672f52036af80f9ef91d95f5f97baaf4ca9134d31fc2f5e26c10accb8ba516f9036ba252d8c888e643d6bedaf025f1d2956c69e7036427146a450bd646a9b4c737208cbeca03f40f20ad66fd1e68433f089863856c1dd7d768ece1b498dd40dea72043312c6ab71671e43a418d53ec2ba2407eb343086166d0e8bdbc12230cfb1721b35dbd58f07617b9d397a97291b1450d6ca5851e41931994084045cb3849e72456c17815e654bac55269e4ff26ae8566aaa58e17f5ab61573d25c3f14d15c5eb539c100be97c0704647698d2d11c20a8192422c35f87db671721db3a8042e72abf40ca74998a2db445516e50597db06b3b6abd867d53af499ac4108b50db872d5416a054d0aba76b21931e26ce9d0c45c116c714ba04e597bf39ced1db33f7296e9b5e9a098e9be36c80f00317d9ee2bb07bbeab3cb2c6761b75d6738479b64c1b0a69abc5b60894a078f91590a4644cc27ae7600397f29d5b1ab4cc748230e158116d1d5fc33172467d7d23aa02362f66e23c47d3d5b9ad94d29f3d20619720b1ad14ca94aa6defe16d8c787d84ef66765d17e251d93393c02f027a952f57297aa9bc780fb2f693c54e9dccc5f31277bfa5e6d9fa6acbd6acb6f955fba947258f825e67a354b5b2e9426d0168053083c8341fe6a7a652bda9e45d25cf316724b60bf0ba46ad7621ecf6918f841612a70478967140d740431e6d35ebbbfbd500ee9b5841dbba836da3566cd800a1e2f0558027b3f2d94468d94467234469b72ce8cda483d09dbe57986729ca804a711c798cf64eaddf17f9c972274aa3f97311e1078a839783862f8cfa66064afeac877f35cfbf518a4a84d394d2b98bf822dd930743c73b6da6f726d9a6b8f650c2a1ed085332bcdf2b881550eb66420e172e433be6ed4b4620e7d037cc926874d7822e0142db8e62f9c96aba1a0595109727d8f846a69fbae6ac0f69c1e174e92c6a4f11ba5557111ba5f21f3dc71231e7238b2655b3ad95aaa90709d5e91afcfdab3834b5d3f82150d04fd47a00f295b724cf3a233180d18a86abeee79f5918bd7f9c888eb8876ef25c4f42cb4d3dddc6f20bd1309449cd39c263f66fe5bccb16d4b2a24f3be22269548ff8ce4953b2672b63404366450d0ce70d80818aac6e2bf1bfa167cb6c873aafd9cb954918ba37225b3210d32e591b729f4cd448c51670c22f22c37e984e93183775b7480f8e9724e93eebd9c07806db71a2d3941d82bd4f600f360a00db2823c248c79082f27729b71ac13b1ac4e16dd1aa9869a28a8d7a4b64f500c4bd0ab87d7f805773b797211725db665e3962a8448a0f1bbd9598ceb9415d32ca079a7bd8b86a61492513e6998b8d751ccc1d0ef76fd5f2fe2a20dd63fceeae77dacbb0b90ce4ead8bec18dcc9e12d5566b3dddd0313bf4e47a386f69d96e1a8e5bd78f3f0592b1aca3472d1b25d8cb941d1d57eef973e04a2918f78503608b56d0088df85500e2f873172d11175afd918b3f37e21a54ff2ab9cafad37c828f528d24d1ae1125c43f9317240174ec738155c7f736cdb55afe9e3eda1c71ea34daa6672acd595d669ad4072b60ea9dac41e720c24c8180a6ae01d2a24801fa73f7f509d34658e86c4d0fd523204b4a4d54366d8da21560612d85f919591d1a135656ac4d666e843d87cbf37f1475758193db2abffff5b02793214d656180b93832863aeb971d10db8fb8972ff7666af86a302d74a4a80aebc39bf2069e97b84a66adc8a993efeeeeb366472516ec5ce354a473856edb883b7cc5176cc90b51b3fbc608fb33365a535247472770f2d8675f5faa1531f427baf5e39cddca437f5478f84e992922a91e8dbf91304a0208a4bce1e8c86bba773af3126405e015b2adad6a65179dd5f3e965ab47279e143de6a0e73174575b30fdbbd70eea98b1c9d9254cfac9cf15573837e5972a69a7535e233b588aaeec33f692309ac4bd4dbbd92b110fd422a70eb098b4972315891cc294c896dd2f080bf1e958507b40f2c84432615f4142a56d22e3ea24695ddfca3658caac331aeb030272895ffd9efdcf238642c9321caa8cc3b8ba072117c48a4f395b0f81795da41552053b2b2742b89b0789ba4667c019be53dcb7264ac31419a6edecccc22483aa757b9bf07b689413bac21c2ab76587dde65b472e2e261c4069901fba020e56fdea851847775f7c2753df23c4c410c91725dc643b8fcd86acb4d9df526a87ddabd3f155f309095f2f67a5ab4575afda8b22a0a201d88c5f4307322fda3ec8bd227d6eba52aede6fffd89fb3127984ea29ab49b727d793c7272e6806b76a40d5305b9ea64502dd022d4a6975b815a14bf62ac9172c3dac9ca24077a0b09c224c8eeaa3e4f419029eeace3c7056ad76801b956b572baa933421f3daf7172de0bc187d2bca34460f1b2989ba0ccfebb97ac787fb820323672372ae1643172642292644eb0d4b56fae4fc48b95e8bdeba3ffc41838723969678424e348339d48adee333ef3ac67edae447c1a61b7e1782621f171f8172ef1f63e27aa862f62eaa12ac815da277fa42df140d28b73694226349b141d72b5a5b462c59bd4520a782acd3ce13e9f1a8defc439b04b350625e90f341d7072a08ec6b0b19a5eab8dddd12e8961c98c270ac7d9a33d14f71939f8d47bcfeb7259379de6faca7870c093c25c61cc6d888ca1324167fb9b2095d5e7187cc99472d3a65510d971c87eaae6027835957dd9076d5bd9d0dd2efc602b1dcf76c37259d34a93527d4cf9de312e1414047cf00ffea3cb7900c0d6a7142fadd8a87c2f065a6b77e6775d3f1ba52a3ef7e671600f9074db94de37b477a53773bc3f13b672028dde3a0e18e879ce9e2726ecf93355b4e9c7d2f1d6837391a1591aa8508d72efae93588e943a57e5e167b6c6effb5b581553e75f41b40b09c786ce9c849c35c97c1273f34e6e7fe45fb38524447befd1298437bf3c99f7e4cd84a13ff267729c5736da84746a6011de23841411cc62b81c75450ba87cd9e6ac95de43a2537209e6b049e593d19d10d40611d93951c5974e8a817da4688d28ce59e6c8117572fb9b52c0912aded9b5e17e1c5921d059f1c9ddc147cbca358c6da85257a33942c9aa178708e17017af07e89d0163e50c355afe81cc7503bf50967638dd21020ca4ea9f324e7d69730931b9d24ca1d447fefe4e1e232074972b16e2105f6f2e4277520f4d7d33d54cdd61c873cd51fdd2dbf1c23472c4d1a69271409b0051c772dbc35e0038dcb8c4c1a7fc47baee6bd55aa2f0277b22cf331dfff3249b83a71b620639c04403ff70037a7736a08cdf15e23bff7574a4af100f1bde305b2ff018aa02c23ef5077220438619d30f53ae5157329022bc1e6bb5e99f92da636e63729f39f1be44d5fa49aebe28f4d0977cd55ad23adfba67fda3f198b47c4e0c6468975dedda1258dad9bd4e8af27beb6d33219301c63946f11a1e25908a002ea272ea67b6c422109a1c978f520888e2f7e6c2e2e35876f2a61a7b02366d9772ba7215b585de0a31c2d6c8dfcf7a4d2fab46e598e95947dab8b6c7614937609fb7728055c6f385651bd48d41e711f5aa5560cc19e0267652698c616e2e8f0ba7516127794fd80f6e6a69a42654f9bac31cb10c8d0fad5de8436f171e12581799734baf6ee73f1cc069821a9003184831fb89e5bf54ec3c576adf59b78b3e143e2d3d1be6dc837cf208abcac6fbdf8a59995655df36ac169dc1115c3a22e2e99fbd72cea9af522bd8ff70e3763466aba489edfd6dc31c935903c336f296fc7ef07272faa9164aa3e89cf75658a8fdb0b4b47ae43077bffbeeccc367f3e3d12e55407227d699df208cd6072a8081301e899c0e6b47a275b751af72e438b231520d3a72903622a31c7c05e2760f932bd055c2752ed3b758633c1d4cd1cdd51a9b513f721a3e873e94437e9968bd73dfcda498fe1fe4f736db378e6ccb2a1f22d8a5374479f89b5ff977941a9eb29b64a42aec3bc2834eb3fec47b8003cc23493909941ae68b5e079b5f09dd05168ead697ae3db8c04fef9bbd5b90cbcf3b53629d640720885d82239bb35c576c2f6fb7778966b991905b8be9b0d5e34c428f58e0dec3abadb792986a86a8323aed52ceeca79f933d7d31351878483e24a32cab57ee74b79ead6238b91be669afa47e725ce6353b3f479e314f6abc7ed97f9e4a93bab727fb6fd804281e297dd6aed338c48491cb52770f79ba25352e54c45069cf1037264bf231777d9fd5ad7c405f94e38084677d18af0f3074c6a5f65fa5d02cf8e726ff315bdd6c7e589b73abf2d8461b8d5b40d5de18ecdb0cd10bc8e57420e6572a2214519380dc595a1c0c38299d1b1d4c3713d660cee04a9f7fe1bb0e966a772c7a7962ad42cb7abc8ce041734503cc4b97323fb59f6d3a1aa0f45efc4567b723593619795f4b865477afd4dc787d5bcd874c35a01b12b638f19d568c5411172713e78f5418216468d29d4fc86631eb1cddff3e0c018fc95a922e8ddf1a0f272512fba63e555da09c732edd39da912fdf735c324215ad9e095e79fca662f5b6510983044071cea4505028f62910a3a942c89d17ad9c435e451c582c2a1701c72985e601532ad3183caafedfc2f335b967038b60355158b2196f26abb8a9a410d19b9c07c3e2be4c4db655e564f8b3ab10c5af852563d4b1b919b5ce7dce98d09ce41c74e58054fa9c151b2cfaeceb4d58afa171da63b288163a7859a70f17d7212a99a2c508ed73a9a62d203dded0a519f40bcee5ef6ea798e59ed293df9602968787c6db1d26de1461f4207ab760296c999a25b3d93cf35e3a09314f7ff154735445c51e7f5a38175ff02b91fcdc12634867146b5dc72bf358e92d9f7db3672b0e2fc4ce7e6a41c70b2d84c47099e68d75c25c7f1e0238119d3408e9cf36472dc8b6b36cc7e57b7cd3acbddf203515497272c93b0b787e996357a115db1cb728a998422ec3dc790e1ab64a12d15a7be5d2318e2f03276e0cb52d4c90f694517d0652e34ed168ae27876b6019da8fb902bc55e443fca88b23748347e6327ef3b72269597f2be763e83e27ed321e6923b0c568d6962914da1a8a5ed5e67636c72873bcae849b456f29bd6580a8b6302ae3d8f2b781f964e41939d73f865ad5172cc410ea06a1cfcc156acb06211277cba96b5f721d60f8c9b37896ecc27bfe3720a1acebddc5d12d0105e354337ceef27f8a8bbced027ad05575df576211c711831f1b13aa9c19d56aaac8f4e0f264d037783c534ad8002fc40c81c1603743972ff16a397912bb059596ce6187d4c0780531a252bbbfb4fc41b6f51c79cd54e7297817118451b6fb19d6904f7a1720738ac48a5a1d21495fd259b45fac15a6f66e2bc0d418bcca6069d27a12cedb51a996545215b231ff14206cb30a557d303721e8c0f1be764d301301b6c857d4cb1f8753eddbc523b22ac9566f26e6dfa9c5f5cb8ead12d9d403c52c198fddd82584e551e72b8992c8067ca4d801ddfb7f31052cf437f7e0396c3ca3573c6698a8a12c68b10fb2240fde90a3bc83c41fa3130aa3c539e284648baa1f3882b32d78d0b5cd931d59d6aedabffd7d295e4841c7285b78cc84d28b1bb17049cc57b419836d2049dbc059ad7455e1193a1c8f5307298d27f8b6e777bd3bc828415d11074a143c86155413aef66c7d9058b3fc3c735537cece5c607e8e4e55c869a7f810820b48fadd2dead2a9d89a2cdde9f81d321a7ee816b3fe7d122fa800b52b2a6691538c10ff40fba852550dc5999b562fe093c59b00085fc33bc8b408b1c5735b9ff941bc91e2ec3850ac4f2b9f3568b113b13358e0c02a40b3f476681980a8c2538699a10baf07e7801ac31855ec708ac0576301b4a1183f06cce4ce16fb1c50f47a309839ea9f66327a735edb20c2bdd72614e7b5d1a79a691ab2454d8c6be248f59658a4975247cdd5b4607f1282f423f5508a51e91b30c5982bfc301f169fc349b30607fc31d52917e0588c1d0980472b8d216af578ea58bfe3bfedfc4cd3fadd94b5518dc79e781af2dc79e2a3a026b33d53f44eb1a2789599c72137b00147607ac83e941bd0ebd92e6948ac4a0727229323aa872898e82c5bc1fee7574b1ba51b25fced6ca8202b62852f609f64272ae3e718cfbeaa800aec48727a10582d432d1cfea85cc6fd5f0df2ca4e6b6cd72f46a564741dd2cdce1d0204d553c7facd91173968fba7eb318c949e2090d3372dae40f96f7c933dd93195731863260f1533bfbfc93bbc6392f2937473fe1ff720ecd3e43e3bf3d1d5a1ed3a1a2cd4da97556c7deb844cfc3bf4d3ee9c5fee072e7d768ae12e56dd7095818bbb5ebd8d40d2f9de0d8a2aa7b7c72c1f182c1894111d7e210553357ff3e3713d37860dcfd2a56e90b3649086f8a9be06ba92a34240ebc0624a3f8da2a9db2b48cb3a9ba7f2b0527e039bdff4683220f3f11a4de33226c61caaf50492bb8db07e89d64fdb0b1235983278b2ec2ae4417b39185c372e87e52099aa558734fd2d7aad228987b39b3ed380721460b93b154317cd03b6bc73bfce8199f2162b6547dbc2d459f9f61dcb74a72eabd0119cc73e0522eca258ea9ff0c6a4866dbce9034e719ba2fe64728abea6e53a934745aad7b56528731f8e9577cd0f4acf18d37563a818e01181fa3d9caba87e87248d76bc43be24e72d6e702c36d79043be526146dd982d1f6ce9297718b6e3919067202857d1ed61adf868b4be9fc09328900d9977d49cc7529e12de9050bbb41017257928a2327720896e7a832bf303b99d8619284dd32183cdddb99d52fa4e2e1d2827643627c7163503e473ec4b1ca6e39b11f3315286d177632b4e6d37916c8044ab5effe8e728466e8180b2c945fc60d160d7892d1261b096dc244367d0c668d95b027ed1c1b3597557c5cefc09f70bc5aa97347ed03e97f5f4b1d4c14d3429dc531530dd55c18a5a0b0cfe925240852d5a4ce10e8f4ca2a6f88bf60a02bdb17b74e3fcfd32fd83ef416a1343bc6792388e5b48ed01f7540d64e62dfb667d9942613ed1be9728aa58453c1e95182c02f57f6681625d2e2fd21fa01cbca82ce2b10674ef92623158d86485f600d03dba6032eaaeb735d2f1a94f708316eb4fa6bfc1801a52c3b2902d2f41eb9d33942ea196952cc7fc132f882e9e9ade08165e631119de65c0e33ecbee5de5f38f1c5309b44f56117fa9159a0c4c6951c6b6472f91801b194151165ccbbcaad70871264c26df173dfac62a67c0d42ea73ece04b8cbb5497bc168039e7435a2949e45d7dcd095247758e402d7611b3a59994be44539d852c2072f5edd5759c0bc7171839c1debfc19144e8e136eedfb82bbd984f7ed59fd1cf5bd7812183e23aa956b8c8c73ccbcb62d32f1aa7976a85e65643ab44defec9e872a436ed816e30b6ca7858126118222cf4c3e0347b9260ee4116cd6f3e8194ab62766af842db78a04f1c720531f1d47cc30bef219b0dfb0e3f8212cbc9b0eaf6561def953aca7d1835c16c13e58fc20b91d3476c490315503d6f98396a29f3e42ccbc9c23d82bba2439fc682ab871fa9ce4878f06f1c1a7d1f0092c75379dd1330e6ce2376e24cdb49cdc311b4a8bc754a68978a4622801e908d8c4609db72e41158b85d39c044ede1968453151353575fafdaf18d6ee0afd740f58c106596977035937358033c02253adb7da1768272f2028e8b1617388d1e4bcf24d96256f172118c4da1e4115ba65e5fde3861e62f4e24b5e667ebba0cca76f87155ab45f9722c7ce597e47110be2dd68cbf56e92db961b00771468b568a3cde73208e930f722eea184098557c5108d5c5a5b297b5ba8160d513e0d53d12fade78594e4dde72be9bf40ff099692b9cfb7eb5885d01d1b64c9fb472d320392d68689928a4ed7209ccf80f0b792c6823228eb30dbb43af9f5b711364d52b058019ec6875012c63757fee681cbe3757a597136a2d72c154478e7b960dd40dfd1707068db7acc2720c6d9d2922c2fd82ddf083e2e0487320464f0ddf14792c4f5b35d2510c6cb06d6b69823fc9026bbee090e7673e14c550e331bf53a24976baa4d1bce0a525553455ddde3abcf9944ed9b16b8c4f89275594b1eeb0e3144fcf835cea07be050134c78d47064aaa833ba91577cb35c0b65e75a28359edd276ccf6e04c5caafd1172c4506b4aed3357edac7c849b495e6c752dc6d2c88f0af77090a0067299166c6023891c4d8dd7a518aacd0bd708e2c91b84f6a25d72ea870abb9564a1a9191b7228aa4ac613c34760489b78668b07117d76cc04ddc216291a597b0213015aab72f23d5027d72789cec3293de2666ee02f82ec51d2c8c02949c02efd24d439d95a0e5e4b70473ca4b7af8e0e155d797f5cc1d4d458048544e753f649049effcf7274abda2b5a9426e424df3438b2f77d9030266a445aa00705d76d6fff5fe94372cc2f2900756e2b36e2688f96207745ab94bf2e6437061585efe4b53a6c0f0f7213c8d827daae19be40be890441fa346e7a94ffc8a4ad052c412434ae6a838e4799cc9cc4df0ce71e9a0c805ccdbb658377b8a78c34179efdb42a26ac8aa674726bb6cd0a401844c433fd14a0f1e327003e8d30048ae0fc75a5d32adcf941c572b7a215b226a71a44a8f18b62345515b3bf3d1af79acff2d70fde22728083c072c4223c551d84237dccfd32a0e32af32ce9f8d03be274f926d33ccbe1b008fb1266ac3078b517c232081e6523a13492d9586413930b7b9e32ae1bf12e8570f91860818b3a4d12e2ea41af807abafc6c7195131579953506366e399d0d3041ec5dd948070a57b75d2876592eaa3a31fa3fe86f9ec8fd1c2dbc0ccb72226bbb0d724aeac73ad2a9b9920db329a3951fcd45a26209247c09dc313c8935102ff260453d27beeee06243a8ec33da594c119090a164f5306f0eb0d78e02bc2535654f721b9a74cc1fa4a3148e02d52eee24fe13df9ec883f75d1b139ef5b39098a4a772a477f3b3e29a6e9b97397916a76fe034ee143b83afc010139595298186376e04a029664e029c031545b6ff3fe8f54ae2868069f725c3ca361a4d6d4001323434bfdc1200f5978da0c527e0a2b58cc02879cde6399b07e3cb6ae4becbdd9042723f72f4bad5f5006f396819626ae2ac317ce6858e6e6a80ce518ea8ba8b28ef241ec6608cc082c83c50e3b193e3c3dab465fe5741acf95a8ec19a23d662cb8b344cbe14ee6f6cec5f1b17d1e2a5d4ab739a8b68bd20c7432249444ab57f51d172ebac260178b637d9727f7d4bcdd2edde566d6e58ba4e3a3905b3f943cbb5c7033ba69be38ecfe97ca496018e759f547b1a30f499ba98609b91cb2e9424f9ac2ffb1debff2dd0d81a00848da6d7c36e2e61b859f8b26ec9e7af3bec91c80a92726c1500eb7128d423fb2b24bfbadeb81a92fc789446210cdad4c566cc75aa7272f6b6cfa6ac56b1dcc17b1ba5349ee444b6cd0cce972cdcf0343367d5b428907273b3078f89da991ffafa25204d6dcf6ebd7c6e96163349c73f9235a6612f16723e959f0d6347b0a2124fca5c6d60211d8779240aa3e2022139719a48fca19d725dcbfb3a72dff92cb9fb4a5eaf90b9be5656e67d1cfefedd5248522c60a29d52c88f44acfb620d7805a1419278c9fd81587c41498461ce605b3d67ef5caa3272125c2501d1e2c1745110369906fd25d9d5de19d4766a71d403b0ef90e8205272621067fc5872e4d17e8c6e8cdca20df13c13a7b8417f9124ded6286b8f8cdf6b635bc1a52405c9924cda49829b6e316dfd9a1f25244d586bf425739106c37072a722b213dd151dba22b854a15a68697e254960030b1422cae11f7d9141c4516273c1247f1e775cf8691e2ef14fa0bd50dc694bd421d5928beb03db51b4cbee725838ce6b4b9e7347df351fefeb1fbb1f522037643d4e990ad9fd626c70f4a0724dd624c9fde54f1fca68f17631a67d695028c5515ed250623a0c2d61e63b7772bf705a9ca8fcca3ac020257495e8079d680e39253cc778b5814db22a25db6d1dacc174f7988b815650ce526e72c94a7d1beedb68a1a4d2fa2060242ad6c85f7251201406078f2cc656ccce47a347814839efdd25ea003f16dbb955243c1bb820d95ebe567dc25d79e4e633e39306de2adb3a93319bbb4361dc201471ea661872ae462865bcc88fa94b6bb40da66e90e00cf9be3411bdebee43327980e4dfb47210406f1fa4896a2dc90104e5ff5c5d22731ed8e658057b8a8c63535ed742922494a2d7522cae0bd78fe1475dd24ee54d5393060b98d399c3f22fee93aa104b25d945e7a4f5ccd8259f3cbee7f87e7ad6c8a8010118ca0c6f27705e49e92c594a284c6e717a157de0737dc0af853167cadf71b1969bf4afb28c3fb8693b1d8f72e3b193b1bcade7f02534c971af4334b63a645adf633dabda3afddecbaeaddf354b3061449c28b2ac9c7e74deaf7f5adf546a856a1f9b4e532d3a0ec2f1141b729a527b662e36eb7a721b1fbf7e1411e4f2261439b825b570bcee302845a7bb720031b1a9ea97cc05f4f2c8ed42363ff7788dc62d4582e3c12131ee19c8357272ecfe7a0935151cf1cb601d1b887b346245341a719c35fed7f9955c839aac6672ec918661524eaba047b44d2d909ab5fba563d412ae36632a515052c4ce625e5d7a37cffc69a7554271df50c1ce4e9d6cc78fd2f9a68ef3519e759470d770e672904496504eb408d9c29bfc15c5b0d1c18f3e428094f24550938667ed198cf24c719d260ef618969171e56cc9638ccadf3e98dbe8db17d448424939a33267b972b36581db7373b1f39b9aa973a120df52fe067ab4993051f70247f8e9dc2a167259baebc6910999512473ee9f77cef0341a258e248baaeed4906a1f75d7d4b46fe5afa8da3379f67a05e04e8c29fe9aa259f02408d5035898fa752eff69d33d035a6b779ba0ca6cb356eeacaf66463e79cf2f55450a94703c8208baac053d367244be005c16b2883593a7a8d45b3a55e5ac9ead288115aced77adc1c5768da172a3931d5a87fdcf1c480c0319a09e3acfb85fa61942859bc5f1df7e637548e972ef371de6d78e687b9f830fe1bf8cd6f6670cf1af4bf4bb50a55897bcc319e718465ecffce3e920a7d31abacf6c4c1a50da3e8b73f5e70ad2599edb2d08071f54b605a9d019b996c9bdb5bcb86b7e95890a307c99c86d57ee617f270121bfcf72b9b5a685bea7da25a6a2dceda9d1483fff092d9c5bcd3c2d5cc07e2ce2a7f3310a23d3af653ae4b140626ef83dc7d233cd3f6a61e577a7cd5257c854d0d16172f9208073fc8a68402f1d17b99069282ada80221c5c139750adb8b8a9d2ff21325996c572b1a517aaf6282a766168d82d6891e6aee4a2ac0f929869cd23297e6ee9db9e4c28db9b6fdbd4aa850868a1062679f30f384e3019e304f1f146b5d172e7110ecf5b55e0b3a8b635a40bf8da13bd0eecf8cd4c8840028c8aa6a63bed6fe55d2e5d137c9d2187642025612bf982eb7a130f34d9dc020f686d9b962f8c7276ac48aca704d0cb38a831242944baea0f5a2ce38ff55497d43ff7b086716e7220dad1f297f4abf8328cc07fedfbe531c42b1669eb0e39ec34d2f7bc2d300b3b2c63509a0a403871679c2c31627a4e605e918c6ced9b6b52cb37b506bf38831870068815921e88d01a4558eb02d98d11dea2b1deef185b9223fd4062570245727d948dbe959121b648f63d2a01d4da7a5b6ba2d9d7c94d13b381a52076a0db725540ef889474aeeb050b05c55e51fc52c472451ca632fcc5a435a9e90029504bb712a34de2df0f2b93d34e75269aa242da07f65fc315163d48178516265deb72648a78487aba69bda746b873af83b480a9ed0111edcec52d23cd7b006cd1c372a1a578d73dae9f80d209ab59625684edbbd7dd39290842865b6405674240fc727ea46b270503217bd7be0673b88f0c8751edac26d4e0703394c376d0c7b1fe72284c27d12ea9cd782e11b31ceacac929b9880d5d990228002d1a0f15a884eb0625837b4b5f1e34380499a535543aea9c72716276f4f31322afc154f6712127720ba2f9c38ada5986db97c10b36c16d144b292d9b11bcd60b362b9bc8711cbd70a849f69d5bd142c1243f29b0cc8280cc638058fc1ec28960b68a3936c04a0772f89f61f2c0704020bf3ba01c1ce836537e2c31cd335ebfafa59bc1864a0e2059635d2adb7b5f63e20d553a9e7a54fce39627c1659062e57b6f9a08049b375172079b2305fb78f898a1ddaac235840f50ac356eabd435cec8dda247197dc89d43c2b30db170b635326a1412ca3a253b54b463583fe86b19a9c90214f543c00b72188fae2b8ee1a9e5af9c5355196a206bed634b114af2111133d7b1ad1cf9426e1e6b8311471a1282dca7f9ba917d8e305daf8462a4c45207bc07fa3a8db35714dc8392c0883eba3837e2649d1c5feb039a44d34da63a7ae5ccc50283b6785c0d4fec761d5d875fd78e6db563881c83fe69d6b90777df2e8e3862656395335072d7e1a7d0911c4377094e5ba29385fcde5f4564b07e8d4d1aff40e4945309f072456e539cbcb855e912bd2a60ba7f73479129e2ad0f26b2d722ace3a2c8585744e1e1bdf07a8d0837f47f624314a7367d67de65c57abe471734fb38c818dfae72c1f0c30d4e91449ced0509f218350c55888845fb78aedeb67ff6300d798cb372cf75cbbef001d0ece7b63927131b33df7c0f01f4cc21ca77c764fef3d6245d72912cfc625038df09b9f2921c5907e16dccebd01178e8fc5c56a08cdb16c7287283b2825c0d7d21e7a8a5ef3824ded08dfe916a291b7e6fca1d27793e08668572b2d1738fbd4b4863d208b6467667b458b6a04e00a2c5c75b0efcacdb694c6972a372619cab3f074dc135da43823024cc1f635753c67c815770dbb65f84110c63deab44877c1c8670521895d1903608d89caa933f8298f068d3836beb2e2a361476fd74f8517f549a822c5e7adb01878e9950b06880ea88608ce8ed4a9adc49104927b3d9a75cd109d159436fbfd53a04b22c675d522d076d3d38889249763d046ceca3b9aca67a7230ffcae03773cc9e1db57624f726cbce11cad937d61cc27213373b384bc3a105a8f31849d4d7ce31727edea85d22d178d40631842f76f0727aed6dcb4b66631908b2dec4fc08b51da57e6f68bf80410ce9132983b01819281d4127952374b0a83954e4ce8ab962490b0ff00ff29e3dbff590539578b60a72521cf94ad51464c927442ac01a86f2cf4ef5c99fa3151234bfdcd09b24bcb77265bf0f6dec0545118525ba01bc6b2eed266d203e81bbd8f692d17f5379354072b93bcbdda7f8e770bb68fc4d75e2fbb6fa9aed0883a5d040216f4701a6004472478839cdd1003dae97c148fa1f3d1aded1e72de194fec1436719a57a0b60a672065346503249862d66ec5baa2e76efbedca9e07933ecf7ec62300e89a03d577282a04464aad8614195aebbafab8289fd3b565ac0bde1b639f5a48d4cd4d7a572a7b4b7d162ba8a038c8a6402a98f4c4a6df2772481b7686d30ee3d78a12352081471ac59c47f46f30299f3282e07b7db991b833c304ece457f7d075addc9bf725814a1b002ea9f7a318d44d8957a26e6372567b6575bf69e95e0a437150497725f5e2d65effad980fa2154e07eb938372b01b71f9a617c74f8a7932bb6b4a3722814fb736b3b1d701ad459f094c22e8be13e3d3f9c78b48a7cb63118e69f0b724847c990137a4d86bf6e128963df1b570e155e0448d7cd894c43ea7017b2367289da8897c6766d0f7a3a4e452a56246b513935a39ba7bb245534ffcce66be30a754bf816da84b9466e9c4f1bd52634dc23bdb6d6d6c08b8071cbe977f15da372b0c9af5b9305d5d49696bdb5d8ad3e3d02bbc0e15e16616688b866cad6245972423d7ee3bb135cc1801c4f1b0cfcc3d9411e1f7e6c2825a71f8317aa6d1551720f135264ff609a2a82cc7f462769799304931fce073a00eee413ea1f1d3c0e1da9d32734418bad15e828bbe026dd5ee68886deadfb764cf2fb47756e17bb366984a79c097880d31d2ac612f89fbcb56a27656a29a21ef5432f9237c68b7af23c6ae623767938a1a972cd73e6c61ff62751900446e39585c7ce36cc7974636f729e1acfc84149e68f6e2cd3508ce383dbaf3b75505e315f04e16d1d6e778f6864bfe4be07f57ca4e37d349b3f27c99480dbede50502453b25d9f0c98a3ba01b724b39ac03499f28f177f03b62ae1cecee3c6b0756e5de8d237ced9ea40dbb484ff76fae4ddf3d08254cbf566d527759ab6650f16a591b84f410599a6ad1782b35fc17d52716c1d686ed5bcccdd1262d9c1af59206c274de22522a148ef9f8056d9d83f5c00d6536faa5dff94f968dc1439353d8e9c8d918f7fcad02b1c5f42e72e704d150769fe462ac4e5fae561e8fde3a90ca4180c2a8055ee46d2106fee65228c9a40bc9329a3b454746dc5a48efae52b73faa5e54337e32c86adc6261b2722365ecf3ce0fca9695c62f354e5c3a1aad796d28fcf7cce4f041d6c8aed6b21bf58e99bffd4ce852e2a683d9859cf92bd6851296e2f539a0a6696255a4f514721b30e6dc2795f966d2a25977fb5bda53dd5bf31047c652168a2e2547611b6c726e64e29cbff43b47f51fc73aaeb9bdd32f29429afd26b52add41fedd532170209bc718046df91766241a19d88aea5f86d2f7e71a8e08e1f48532146b12c6880e0719e9188a0d431e7c5a582fb8faa7c176d12c771fa73eeb3032b52f23ae6672b9107ad3d5ee7e32fdaf16858e75ce7a5bee0a5a11c22e3589153d8648bc6772090583509d2ec4159357186a4ae629e48e4908b9b3cace2fb30a8bda5481707268b5b72347584068e665b0871ef1484903d9c7bb112b6882f4c7a4d45a5fe372653e51d458e39e3d5ca988cebe6bffe4da9a52fe1b452dcd840f1a467453da72d3888926cfc90d7a85dc9a1285e318e6b17b543e7afae43c2441363f5509db72941b18f7e7ce3eb7771b1eab5f23d106f5672099311dfe0b3cbe82360c3d23727ff29a69f7364a7988100d752433b4f3e44a109e246877615f3604964687fd728ea10600d4d2df47b2b4786e5925f79a11bdd6bfd51ab28bb0387db290eab033403ce01953f70f8eb7726fda690aa7bcf696e50649670bad191c7e707a167f722dd6fd51b87c096ea40dc1cf818948cd5a46af49d74f511ad1835d621d1469727dfd4b4fc3d24275054d69f5648b537ac533493b868af6e80527e3b69cc5d30ba8fc2ce1f146212b24aac4212def211bfa7c5b0fee9cb3ad5e943820c3ea19089e658a75cc49b2b292953ee5281feb0487a8ff4d8f4f7c03481e406cefb1df72e00307b2090a41f55a813d980b67ac2e53a40ac1e805e341b54d1dad5eab8c140541eb62776b69943ac81553069226fa510ec12a5ea6efbce811e7903f8d32724d75c2dafc0e9d5cbdf9a2932db33833f57e75a82bc0508832910ce317ed117213ad43832dea6db9886b3691f7e90df184a285e0323a30404ccf55cc6d80062fbf14c8dc36fb220689a2f915ebf9e2e4f524780941dc30c38deac9343008125b53a71314605640f795bfeae3770cb4a1b588bd106a257430aa0bfce206a7645cd71f90df1eb268d655fcd270533e0372c2ec88308baf6a22315a4498469034329403fa0383f22a297507b28be27d6a0c0b83328e9ae8d06d2581d6c1da69d003f57cc1342a33d2971c1d4a1445354da068c348d165bdeb137485266c32598672785b9beea980fe1b1511f79e57ba267110f808e50b95c1a1e4bef9fbd0018372d5b5974b27a76878de18b86cd3f0a4e71d55f642230e03a432adabcd5cc59a535b3f8e218d7aaa035c84532c980c42da62aaedaa198da16613bbc608b785d3026565787842f2e6ae242e342da2d62530a1832dcc9efe6ed2d2ed668075458372598ce6d076c844aeff7229695f1a17b6b6c7b0720275779c534327b2af036846caedb9efe25c724a22099957be4e02039c116690d67fe83f80af17102ab4b93e826257316f5ab8c654ae144082da92ec8a3102e22fe165eeddf16a05decbb403fa624d817d40b4139c65235743e696f5b4e6235934cc36f4b78a82f2557efa0be4c0fb4cb5bc8c2a398ab98440d89f79f923b4d1cccba740af1938e31dff847233f56b503575313cf3bb1d20dca9fe80f59cf9142394ae02aff7aac937e17f4aca204e294c8415715c9c43996e3b7b65320aa6ca2075316db51d8129aec73e72070ecfc4571b8aa68b4db5c44c5681c05c0bc81f14a99d1d93616f96288e7835c716faad289a09e864ec4d7b32fdce7c481fbfbd91073eb6f3e2b8dd36c358334ec9c8c2a0dd246bf31cf7aef13e82ac541df33a2bc96b1d7cbc81b85eea67722c17ef735721b41426f70df6031c5e185fac03a89798e673ce51a8b79f205d72f0e2595e925077f8e53d984f870256bcfa3c44985243152862e91c36e299420c0ebb0864500732d65a834bbefea6b6e92d89d080d6954ac90bab0860ee9fc172e7278cc9f6eaf9f470bba9613d992dedca6b4aa5e57674d09cd7804a81dc7d7249a81bd483e857c4045eb034e43d741333077426748facf37aefab03fbc32572548c805e89d210184ca1a92f4d374695d4419b959c815b9e0a746ddce3ab177257d07f4dc71aff445686ec945a0932d301dde05ce3f2b502ac810a65a917de00d1840b02ca9835206a31fac86fbd05a9a743f378f833a9f6790814159ea8eb72675c98fbac93e9020f40f673e6c1b2436311c8f5d44e803a9350736e19d6395f636b82df46cef1c4cc32c4879d27d819cf5bdf89d4065b674e75d543607efa7270081c7c4c3b2f67767e3409bf717c7810da31da1570f8f75c542e4e43d06572eb1facb6303c8b5a6c6a4a08b3b53baea9b6c2afe76cf488836f75574e9447721bf9609d7715cd8992870e32ea7ea6e14ab882b87786ee29b1932ad39f19d372ff7cfe7c23183b67b4488dfbc151a49370d67aa4a22583e5baeca9066a45f372d91a89d9dda5cdd9dc70e05d990b2ebb8c208bd9d209f443d767c349b43da072c0f627e24e78eb2512c1722e0eaa6f3243358684ea666ea76f612d7eccba7467c47f49b3b7a5ff28e9d9d8a875d52d0f63232fcac4d790f3ad941f6316bced4aef467421b1a0077d0cf6cd3792a248c378c2e5298e1ac7faaa44fa5c6e54aa72b4d9908dc0efcd880efe81190c123b3d9672128a46063838738b3a6963d0f569ce936529f6319140f4338f5d337741704c9ef9c23a782cbef0c8f3a0b87a67728b37d54ecdd54b5dfe3c6ef2d8061fb294e6fda9763f0acbe0704b5c64c00f7249bb8a0fa1a3d13c876dc6e20c9b799f4925be9493934a35b09891a402b65b72868d645d9ba5fd57f3b99e5577b6b8c235ef001f8fa1adee125d705abf560038cb8cad548b03b0e759365f6a2c52b5c48d4b59e891bbd0eeb0e4cbe80259b272a6fbc76e78eb201e249843e36060ba088409376cc8cf27eb23dd75c1847dee6e4ff36504a09941133bd55d7b61faf2a5ab693d99ac7600890e722e9d6459ff0279aead926dc5660febf23c243f2473ce80443348280e410fb4fc3acae573a3720cc58e477132a39789ee8713de68aee7c1b82b75a9b78586c165070a881cb57280e9a32701f7114dd2a315da07478036e8c84ad8ae1625a1583d5a872f3c1f729c3725d60d0ce8d4ab0cfbe3d95648a5111a2caf87048e0b2e54602f0a3770728da447cfc912f56de70a32a49cbd87ef1a5f166804799b95f186cd3ed0fd1e7295c5ffe7accd8f180f00c64a4b2fd7a2409f29de504797a727ab8e16af5d9048410f008e6c1ee3256f9b27db440a44c8d8f2043562b5b1a680358bd2956e2d270a5f1de7a24267eea58fb70c362bd54eca4ccb6d6527647b4b2d438a8168c2723ffe11821a4c49cbd04e5187d7d40a49acdaca587d57b27afdfe1b59f2a2067299a336016237f8f2937d6eab3c27df9e2febd6fd1c1fbdf8674e3e508c8e3b2d3c28a4da23c6defa2e3ab7f5b6955be3b54b67b4fd05f0a1e8689cdad4dbf572fc0cfbe7984eeb1dd14d1f98528a1ddd79011192309d0ea4b1c06ad4f582d04d227f175e4f102f2d886aae01503b2cfc893ba0a5d88d1363d4e0978f35c01572c5287f952e30d53cae599ca8682e0446b9c26fbaac997c301440b2e92c37a9726b2340d46f39dfb3a61936a634b20d68742a7a7e1d9cd04749ed22985066b27241cb6f5195db7cbb9113c5bfc92b3f2e8f57bd5f96c9d44508554fbd319f71275ec2a877fc3a1d151259d3e4132ad840f070030374f335a83c38a39f15ef2172776b3b0e21b3ed0e83975fec634d58390b7b1a986ac64a62ce237f03dcd698727f318844d401e3ffc3fffc4666b32164d2aa086edb3cf2598c44ba3c42cc4050b6cd4a43e3c5a02a7e989b1229373861d99e6173bd11bf573b64c9696683f73e6cd3a7c5b26d73d1127313d474c7c6b86ea29e1bfdf77ca253619b16bbff9d2159dc0793cedde2f4b5fff8528a1b42b94a74bec93dcbca257941604ad93c00726bf6546b3bb2aba0d2ed31baed67ed3c6ab5d2b42d60983b40405ba6186426729cc0898113f2d565ebac9a6e21956771c97b944b3e7d2876455e538194589c72edd25141ef8f61ddb01857bb36696149a2effff12084c93d5c79d1e130164e72751d0eaf318d68ff4e24454bdf6bead5d74fead2636b29b384094bd51e12d8729bc325382a5f8c73c4644b9d67b8eb2e713a1536b7b48b6bdaf73365abba6e3658919143bfc72a53bf16ddf8d4a9320d3360da4605315b05616dcfc929c6970729db3c4f62fdf77cd7a6b0c34ac976d1cf0f04419ff2b4be59565fd8c2b95072fd36d748383812749e35ee4ee4557d6a1fa8afec4c2c88f24aaab344239319722b62b7ff2233c10fb91480b1825c2bf902ced232d819b83e31c9a557a1c82772f431278193e15ac44c7ee76e7e1e9cc2d217380bf5ee8209f274299aea2ee872f094245caff034835e27df7fbc9b527bf785f3687de3055c9f76daf78f8dde7228158637b7e2be68d58f0094e63dd6ebc4c807a97250f79fad03715b1c969b59ef77251670d3336874251f310455ac2114a38a41b2e04deb598f256f9d01a63008392beb542470ec29048119f95a776be29c6ec0b405d16b4315ee0de1a1fe56842ec4b3710ca3be0ed4f413b611faa1899904e7b01c57fe5da674db8f22a472daa7db113cada63baece0502004c7f181c20393ddea213f8aeb96361b3a59317974264792849ea9c58e119e271ce4d2753db3f07cf372dd10ed5f391557dc22cd83afa8606ea819791959e1e945aaf35dc5af2fab08381cc5de299487b314e0d1701614188e423ca1efd5f392c4ab11c9ce63f19b773f90a203bb5b1a9a73b72034ed1aae033238a38de4317626696cf0a32b0838151583a5f0dd14f7c76ae726799fe1e947bcf40bdc75f9440e229f8e2b6402f5fe7256807cc23d269fbb55b77c89cc8e4876bd1825284f78d2229cb28c54ea253f48479138bbdb034dd965e42911cbb574b647805c5491479308c40b6709abd4256c92160eb347322b8777234eec8fd963e0b3381c48e30d1c43f92cba006b03ed99ad0a357612cb122644e4806cda85b88e8cb6ab51e7c464861f078437dd2f240d85b25b87e7035ff9d722a605fe440063fee74e9aae22e93bf16d159a39c16f386a4361323ec549c3272990d962ac1eb24c003a61e6d8899b3c8a89c77e6b3234ac3816048aee9a29138c842c3a662621afdb9b6d9e8ea2a96e7a3c3e87b617fb7a07ef7f0d0f0d30472552fb45086cd3577f018d02a7b0422b23a3cdd002c1044e3cb79f5436d4d1e723abd1cf2a37f21ab979bb091764a040dd416f523496c9d2e8cba6af8eaa92d2f10c62fd3bdd8311ed8a406b2e5b073a6d1cbdca4edc4aa5bddc2eabdc374a76b72827fcde801b93c02ecdef4040c3a32cb2059618af1e958ec4f9246906c2f162142d3a6a4b338af06a5bd79ca1d4d21587ec15d393d118c5f4374810021fe72bc7ae72140d9364bfa088fa14945f09a47a63cc5e6ac4ea8ef5f1070c86722723e45f097cf1466b3c15417c3e699d9153f30d8346fa011d5a66f451661c0af27ceb01f2e40ebbdafdd130abc4074985bc65dd65dd44349ca407f776dc98627391bf3f8e090547c5264bece24ae447f2dafd1cba6727c988a47c0a89373b05d72e1aa2b02bebcecba12d71cdf26f9f49abd03b7c5d443febf5c0f384ecdd31472961e6909e283cb3609f078aa984774de4043a7267d69edf20563dd5737beec72f5b8e586d06465c36e9a88779bacbf93c74c9c95f04cabab039a90ea2bc157720612f7100bff0b60f5648fe8cdea55251c714339d6e4c1c6d052174bd8e9903b9afca32bd286a9623ed933484fdb7805ea9b37f0e70127f9f91118f7f4e31c0ce4f7aa9721ef54c655a088ac6a8ba3be948fde075c1086928ed72fb4bf2018726bb77f74e7db2c48fba317a2e3ed3e835ec66910c085dedae43c7ffc0a9222728ffbb9e27ac48b0a10c9801b863ce762080a9da227f7e062e45b64f78598632956ee5f17d05daaf0ee0452ac636ae916dba54ca6d607d6c611a2344741868b45894336e6f573a763b2063d9e1ea4f17020516f219500291b4867a16fec87605104c2f77c1a7f13e654764f3bb16bb08c73bcb1ebb15676ad6aadaaa62febb572e3aadc7fe695a845dee56c0d111b3c8deaec6bdc71a053c028b393af47a3b9725019edff1354658eb8582ccc08015d8520b48dc5c18676c510ab9c97f604e4720d6f4da4695cb8ecb8071f16421310cd399d8530ff25b32354c69ddc15bbb572dbcdd77acac79d9803eb44fca0af1b8bd4954cea16fa76717ed842a19a604f34004db8516aa2ec196dd41b736fb4eea4bb5303873ccc4df25e65d1504df35b3853e352a0f2091ba819d2a0262ae379c435636f6c8c03515fa4e3da713737da72ac13e3c6dafcd3963abf8e1640f00d88488ae8bd2c5585ab2deed3b38bb70472f249424ea808ebb6780553fc4d885bb9bbd3cee1630ee1e84a3ca7f94bcc4a727369119a3e884ed579a976ac21c36995bb86045c77797ff149469f79f014a45c9563e1beb902d76801b52341e8d494b2f2f05d2d3911a7acd08218fe4c0661724bdf4e5d2cd1b0262b5cd40955b35999091f5cfddcf5f19fe49d697729565a728fe0fedd8c80e9363f011835de2e5a8ddda7ec907cf8af988313f3944720e01bc901157322883635dd3a9c08b1750d1a96a854ca07060d75df6109dd59134172f01cf7e8574ef138b3d134216562994ff38c53f540982ca8aa4eaf70d1f23031dd92b6c2e7fc06994d28ab0dc5c2e4d808d90244c02aa885f6fed4bc4d796272e939eb992583b999c4eb22f0557461bd9b046599771fd106a285907ae0d22572c4a7051e1b1c3c4b87eef96b832b3505858bfc997340c0a3627e40626ef7bb728312ac41d1f827c0e3ad42ea84bea7fd7c176fe423529d8c6d7718550e434972af8f1722cd19d7f9d70ac7f26258cd0d742b7f03e847ab2ff40094c28864ca54ea5b7374c1c5883ac1cecbd10caddce627eb4afd6312151dae25d08d54145d72f187e90ed398bc1fca9ba075a5900501c4f6528dff09496990d3ccd1e0445f0ccb96810169b2a803e024bee69d7c052afd6eaf7c67ede19edba0e7c652939b7288e27cfd4356b582c9c0fe3b4197705fbd915710a8984682590df3d143de1900e0886dd83c905741f163d7d4d338c05962ad6c1793ae1cd3645c36b5c0eb983d1120320cf49e46e90e5eeef818abed2e1a605c7f2d320b8fb380fddc8fc41472b374eb6867054bcb57dbdc12acfb167fd66d360c098ec33ea5d1665f2c1fc243c48b57c4faeeff80c73dfb73ad2e867b0123ce86e5d572c9e02fa5bb8d5bcf19f338957da94a1502cc8a5cbe4efad8abcde406c08a5811be7f967211ba593a720d593b8f8a67dc0d4508ae201dba7e1d703a452f5069433e78c5fefaa4461b548e527a9f964a26698916052bf37bb4060674492814e4e99828d8d494dc79df721172be9dce43ca0cc1b7df5afadde915c2bae9b011ce7b6ceaf68a7273b153719712a4f03c49f1f6495f30183acbafb7dcfdf2645a3145cd202e782b0fc81467ad8d0a8e69efb9d95916437002aa100047c8de840260c36000b90b1716d487727c530ff7fad4f0ff6de4341767cdc848c5c3a8f38f363f54fcaf95f6be922829f4044575a9bc502f49f6c3c24d0143aabe7c3758b7adfc6591cfadd5a40fbe7287862830bb4acb29e26014f1124cfae65281698522159dc39a5f400199cb4c72b77d3c2d319ca19d9fca45cde3400352bfa2cf64c515df8440d9bbcd9c375d728763d26abd9b0df41b566ec18171812402ca8732248b02009614a71f43d577725bddd6af9772cbbe637076abaacc4aca5b1fa4a3fbe91a9e51b30ebbef17af6e6621227dd0f3eb52dd20cdb9343dc5ca12a0a8b86c7844138fb861ed3f5c32581b4a84830d701575acdc62a50ac19a04439693feb360d5c65890e8fb50ef8f7261285a39b8127e97e9dbcfab99619df294a49c738ea0c8f74174a8390a1b9c218f2976478134ead988d7d95848460aa844cc417be6c2ab2d991bd1555408fb4e52c492c4db46d9d86322ccb1d38b109b3e044024d8a643e35380befb8f7aa9724e395a0ace456043ade31d3079176809a331065b6ac2292e4dcb9f212373ef729f48ec6f016dd6a7f4b62389af94859ced4c72b78e71e940f724c3462f7dc172a822c7a1f860a1a2878630f091303325037a84becc899622bc6a59be35ed153388a1f2ddd090427dad1577b7aa10374790db481d9b722377d090128d62a452725059bae355db6861dda93f2928a1ee933418ef40dc6e14d75a712cb8ed95bd72bc7f0fe721eb54eb673ea3479ff8de70813f62c4a4fdbb722b4fa1f0ebefb324366f7decdcfc43ade629890315297ff578cd8cc197a19318c491b84865cbf66e3098839b3e40d5c99252ec9e078338de127bc7d001cb8936a7c3c134f6175b723562ddcb5e907b3d2b3be72bf3c52451903ef57533fdb10a369ffb0e47827372f5e2e97a04eb80b9395a5f9a4194c430be00340061322a11bf39e24272af7a073d0076b230305868d321f21266c0f9f79510ce716ecf1342be680068fd5d527250235566488b59b6911b9fdb8f2e9578902833e2cf072933b2a62777958cbf2eb3f53fd9aa4e3e9b145e37ff2ba29e38517ec9928ddfa5068fc68042c3dc793b7ada64e6903d0f6fd11b02a030e1bf222e06eb4c040a37d5bb6af451894a6671deb31900ecbbc8aa38da6e7ec5bb6f32f9cad281e6a8f1397f87a899df102d6653b8d5832fd395dfd5404525aeafcffe61219f7d66dcc83fc46f15b6d6c9c45ee230107da11ce3f1b4cee615ed1c0a22a10a334d8ff08d73e91839067adeb072d51a558d3e6c530419bc8c5c1c6a01d99c26fdaed44f89217be19f354bc88e728cdade75f22f61455639225571ee265eb9711994de27e9909419146a164a923fdbb39a4ceea2da46301bca6c98a70148787c0121907eaa1b33c82c2c68895a6199ee59f383001586221f9e870143f3bbdbfac7ca522a08bd20efb08563b13372175ebbc70e8f559b79b78f601dbb9c869c97c3a3bb11e5fdc1557038956130728d20afdcb71965863dd21136287b4d57363527d7c59c230b367b600b07f44372ced2138f22c01e4c1038e97bca9d72910e638e21eb3a3ea477616e69bd2aab72bb297acfca7fda42d803904be4ee1c9ce986d70687a01dd91d924cb8e8ac6472ee9cec72d3f911f46d97f7d7d42e0100c58818e781fbbbf520b19089f6d906704814d0d495b69d645ef67f09b51cf7d917a57acfc501e1b6dcd0a77c958bad49a76364955e0c92c1692fce83dc06df420918c1426a3e08a09dd77aaf66417860848103a4a3f09362971153f02f7fdefea525e9110eabfcf51ef8f484fe2d9701457cb65a28ccba6db02a325bdf744f2cc2bb679372fae0630d9dd675ba7b2d7278834e6070c01b1d18ed76bf4b19b18d7e460763404aa9a9c335af5241f2b909a0bec4ec1eb25e9da4bd16bfdc8eaead8c8a18eb7215d3f64dc8aad32e7ce8728c077485bb891b41d03d775695b94cc7249ab1d1ad96f5a2dae9ad0407b04963b766243eabb22f8a4c2bf561df3b38bfb37c794583a108aa1aba601c88927c3db36a3b0a46e450f9638d842f997e49d52cc21189f9ed5790dad92950f8133060de7e9f07172fa8cdb5080f4e4da65f053fe1b86183abc1378b81c31b0296fc725a7575e9cdab44c896e8f76b3758e29a6a234612effe3364efd9e5050fe2f51b6ccd4f19ed08b3b5eb386fd59400eb00c5f4fc668b3069c78d6ed849ee613729bcbec4cf38a76adcb1d469e9e36ad0839c4ed6d9c69fd13ae1924baad2a7fa72b05015937a68e755a4663756ed3864b7bf466391d92306f8bd31460aef61ac72f1619bfd4dd666a1f0b526ca772a9bb3b8c9ae30fdef7c15945f25d18549dd72051010810b8b80390834e8db8cdc06f168b39c19e0a9fa4675500257c0f6ae72ca3c3bebe5a64bcc20189e0fe0edad1eac17e48ee323d4076d9308563c8c211efb29998508f6198e324cf51eb650310221c8b675a3a3dcd1d887926683e16943d18b12f1a6772ff0afd2505ce0f99b312686f36297660bfae4e10646c4ea6172b51a657fc61466b835272ee75ba0b1ec46cc69c6a23901c14fd583c11bed7972f379342c7b6462b44696265f4de18a3f36458a1cd2ccba7e1c99f228b722dd1e9feb934212db049729ad1ff81089be0a313794cff05a371df08dc97b6870584632ad94b322ac32ed36d03e51ec581c54441f9a274397065e0cf1c5a464de50723b9b2dde47b3cbf588545a39dfa26cc072507238705e8ed0ccdcca42339fd672fe674f8d6662dd60c5a10fbe94af6fa4949b8446f6312dde67cd8bca2de478724a219387e54590c2f2f3dba81e4b6229dba829a08ce9a124c5ba25996d02e30360a683deff408d0251a06cc9ec2a1aafbc8404af91992023b69a9399b04f041ef2bc7b71762d52a275557599b0b5ac900639b101bfc2b647f07bfe2ef55dfe728d71dab6ca5d1a57452b89864272a9ee61d03add4957c04278429a62cc93181cc079d79446434e7395ac0762e43c6e503f8b98d18a4966a3daa83949687a097249dd93e2e637ae2958eed740b75970d379807ebd58042f8a3f2430c1d6846b668bc5ed4bccf21070afcb506061eee270c904f0bb8af37ee21c30c0dde23d871d8628c7ef5d0deb6ed101e5275706abd813ad552d6e748307a2ba70b2fa03ee0a9d06435e848e0d535859f00067e20f9db64fe1609fcf45a536de92088e147c726cb6ce9a4b336a88040d02c3288381a3deb0d8765862641cb9970d08377b5054fafb21381a30b9862be5eefe78dd1e54c7ca0505ea0cc8630ab7b60ed2188814d24b332e512c9c1772f5c055ca24751ee8f3862a8889f63381255de475f67838139d4825655fa3e955f22dee4034885f28130fae607f0186a9d9cf1a1077f0722de2ca8a49da834a22fcb681f21222997a1a465930f2b696d327d7e860817e729798d3d07973f4b700a403f7c864e105c698fdf3d5eb8612ddcece496cf58c729be64fd9a6bed3c6801609ef88d585df08021eaf5a58396791c25f111db859534500b894b77742ecfd22f58e3f3fd3a9f4fb954cedb9ba72d03b362522f4427254ae1925707d5bcc031192447e553c0fc809eeb6ae683d8117b1a0d2f7432254bec6ba95e082656357395698341026ff994de10f74013eec75e180867ed999566ccfaea2be87f30a617cac4c6f3508310b43ee17bc64b776101815af63549e5b3ff0455572a1f8e0da819ba433640fb3edc9f07d21a27e1036a76f096a09b8016feea520e0303b84089b7e5dfa6d91c5404420c0d46382a416ec43c18cea647250e6294d290eea70d253701562254d1f59b9d520198b7437381245b33d34a106bef8aa66d8b7810ac241cd382c47f1744997321aaa1ce16f612c5b1d9eb02d7279be74114a0ee39a70a7bc7e35aba667b4bd2d0caf90cedf634e8566fd9de133d080978b189e7a2043644ff027245631be63a0d4cdef42e5ec7b17d2077f95455825119196913d51d50146c4682a02af9bd16cb51d18a32fc1b26852ed9474724bce037bd04e49337e6025f52f23d0cbae1b3a9747442e801bd0b04ae165034d4a8e5cf6fea2423bea0184bcac1f33ef373efa7add7e7debd3e82bdb18eb2c72768041a6496b0b20e0d91c5a4f9b8c273a05978e6cc27371b5e676da1fc3f731d1a4b88f04466bae24fcdd5be0619949a9cea4be9ceb58cc3fe66e31aa0e8c5b54665ec62feea993109def30c3361973b3b03da282d07b193a7b0b56ae4b297204bbf3ac367d147274754d333042fdd410f30936b81fe6d0267328940c56ab212e53c68cd3cac204a36a00b43e487b410eb96b38ae2eae193f6f18b1e23da07280ad0792549b30d9fc20c7c6e9e4092ad31d90bc7b69fe03b7f63587f78486727711f36b944346b05d6f22e57e0545a7322aba4b6b61f63ec5cd98cc22704f720e2f54bae82689741dcb2c4090492c5ab3b91bb6cc473fbb6cf3daf23c364f7254781f053a82449d03d5b3400d7d3871c1eae22d49ac76d2a5949700d8b69072b24d6aeec5a6cf92254e157e76d98e960917676d3245f559d2c6128761f46766e407929ade73a6d03e672b1a241dd232105d0fd925e64e0393a5d118af7a4e5d1396a5a9a905187577d394ef92df66064b973fd12216a53e84fae7a75cd7607262575aded362f20604fb50289e4ac057b004bae633262f7e65dcd96977930672f55f0b4435377a028de27d09e46931a00f299632adce74af01b841cabf503672d13a653737ab5ced71f77dbf8f838c698bd141dde35667d237394c0549a9010b41c445d8afc48a5103ba48552bd7a0db906e3203c76488fc7b5db5875368df72d5bdf2b03d9c96e04ea4b4dbb8283d22104b3caddac5c606704a75742971e33820ac36aff2c9c2a1e4277f30f4c86fecc923f572f6c91ace6e9e4d51fc1fd572365104f5b7259673493c98006a9f1fed8ec0cdeee881d19dfc99b598b62471726a91d9b2117d325df57d9fca6efe7c7faadfbcb4240c95a8c5ad49dde613c172edbcc654974ea85c5ea829ccab097853278675b7a680e98201c3b7ab7a5276728e62f52cbfa6bc2932d905e872d7dd568528d3124dec379c3c90db03ad15b17254cdd10b0dbc96ddca9c9053efd666e84af7656de7ddb8211e9961d700905272022e57cdd6ad9653e5126728146dcaf347b74166c10ceddfe49d7c2c3089fa727d16bf90d1d1270e9c2d90f657a17bf0dd52b7a64a09c9d50249b35de2ae702241fd53c00fa0df054d02c9c4d321f11785061f2972c9df386fa07fb2863f000d89e1690025e600e362b68281bfa39239b66e9564e1bdac989c8b0d3c19b090722485f5cbec90f2d6c06957adf13dfd7063d686c1602bf817d9806dca06ea1b3c52899feddc08e0aee8f5ccfb8da9c725fa3a923bb846fb0fd8254ebaf606a0634200a87ccd860470c6381602eac7fdcadc489eae12c2475445d1a3c2c6712072e2ffa0cff427589092e3115a6c160d47fe8bbd0d66c4dbb64a53d4bfd4f46c72baf74e7a399817c1240558f23e7aee94089d9c6440a75947c54076412813737294f37c996a10092235f13e0eb484b97e617dcebc4606284e5c1a115088f2807105ec9739c841e9b986f836091fe20cee1c05947d65ca8e7cbd96e2e82f2c0472d7a6a4ac3f066ddaae778964f260e4007d8805c0b3bcfcc1ae090c818592237232bb74dda91b9cea3749b1a6636ec37353ab79c733c69a27fe1d7299805ab7721d8b581336ac62fb0c8e4f947bd7b6a1e5729321c7ac25693f16d2957cd95672fe6e714f69ffb7eaf153119fa1d7a51ee38b2e23cdcaec8091325efa44949217212e423eb2c01ec84cdc8413de8d5bee1067779466da4086668f2a2993e94c72657c8322488f842201ae24d6340269674b9ffc700b5266e8cf971f32b4f04d36a2485585ad6773be9ba5e37556e1ca7a0ced96a800ef9a782bfc7ae77c78d07224d665a677d08dced597ef4d60a39fe5b5a4127842ab9f414b4d4e54fb65426b0a2d0309e051494aa798cd22164c63b70e3d3b53281755f502a5c73e0c6ccd11950ce69caf47dd43ae1a8e27fc10f1e121744efff7966b0862eec8a30edfee1d3a88c36143aa78a9bab02b09578e74b50e2070ff25c844a62a6df09703512d722d815223b670b1f08c4eac95300c5120993327e3f106560cf7b2d789bd11c5651c780e771c1ccc5e920e95b8f4f6a90d1d9ddd864b43e39281a82bb0d34a011c117036a83f3b446917745171e71c671236de0e7c9b975bd3deff8405a5f589728cb263b28affb0b699745c85960a7ae1d158740b2d3217645c124d051d552559aa8f5bf054ce4ee098da579aa6d8132cc3878c6e58b95d2344f1af8ee3ca1e72681c0ab5330e2b844e969f7dc3e67bf5de85046db9721ff0b6ba19333c799d038b992373f02387bae82ec12bee5c67df411774caf081a90fd3b02e16dc0f0958591f30e570db7b598fc5329306e20cc05506dcc47423b211cbcf47f822a1de724d7063452d551d5dd5abcdd6b3115160a2df3111f13f19e6a24c3736482bd9720f66bcafc8013d42345e196f1428f47b4b91c34a56c820415a2a9dbe48d4d440352daede4f6264299fe33f139fe611b37eea42297d437912d6db14201509c4163f31bfc697ea58f1e05f6a8dfcae10e682d08f614171b5ecc41d3979696dde72e0e3477b6e75982bc426c67b6547b3ba1a06a7818f920b3803c7c0319695550a6f465ca3511193d2581ced13bc3cf658389e539221d01dfe1d0e1aea4f747a72b4c1c7db779f118ab922b89a7b646867f12664b85f3fa58703516cda0c89f313b9ea94f37bc91a0d1166180c62e6ce60da244bfd043059c7c280d23a31d1d000a47c02a5350f90e00e67645690a93c3aabc5ba92b8104f978c11a3416cacc8721c5760df58b154fd64e3d9eae0a99530432b7470ea3262078e51a02109504072ef89c7d45b500d440a642dc6452e38fdfb279b7b8ff9c7eab1066d353befb472be90b03e4fb39cea49d622b10d32a9a7b52b3afba3fe35ba32b3078536243b72b14ec561a09fe4bf13054e8d4d0939da5d70fdd83d02b9b3a0abfb9f96dd74725c4d0815f8ea8662ec697c0fee1bce411ad3bd200745116c30786e6476a0ab4c5333e366f2475af7bf9567f285dfc625449bd3dd6c090009dc3757a3d3c4742de3a91896b231cb7122413eac26d56e3d31a765b46e0b3ab67628d588f08ba46610c37c3d63574f8f6a73e5c9cf46a9ff70ca85530a90874868a4f1c6ef926d7207e32e8be6073b50f0ed769108ecef7eddc5136fb6767a5f2f0986f8df4f087236a1b8d329a2f4f5bc751b0d1c3dc7f0cf9350d39cf5bd336691ffa93dcb390b1145b156d8ac332527563c799f4f0b2ea778afb284f551f759a8c56ee9662f72b20cf51bd97aec9ff4adf57c18a4617b5fe9adbc7f1a1d13dfed5a63457dbc3840c835d473e8a006b27371ca82bb9cd78b3bd11adaf6c6e637a716160f154672547916fe659cae95b3d1a1cae971768f6cac74a636ad0165e6612f87f6eea172bb87c98bcd89f516041119f1f7846e8f30e865330d9efc5156e3df66903e1c7233d04707b398c7a3cb3bb95776354b8bd196d6699eba19e7a911b50763b3751108bda07222619ac7d879be834cb740d158412f5d5df03c034f3532631ac12d70d129aae1d891f69cbf85a58cca3b5206f6813f734ebcd595345e973af09b3b723c30063a98ebd526aeba2b0f3942cd90f6d50adab59295debad7124545745c38d1c046e9bbac7782db4a97ccf0f770344a975fe3ae084339e04dfa2e38848f725c0b6cefea1f31ad2bf623d93652a3dd5bf5b44a5c00e81afb1a6cdca2a13f72346b384615a0c4fa8479788c3d1a2b33468c300c33b868fd9afcf2a124564322e6a9bd75138364eaf01a20345735081507b61eed5286fba41c27d852fba6e220fa231501fa5f21d87787a022576c64d3989f810d3617d1d15084e279303d8a3a94d89fb25ee57fe96d9a2a828bf1d24053f2bfa7391556a24be4d5bbedeb8b7237f2f52dafee330c9222a129116ca9fe89d560434ae35ffb2ddab4c896b44225c5e2dbd9ea5d9f16f7cd32ad28f83d9035bb7d33c8ade36ee8382ba454f43972573b32926d2220171f95398a71319df5caea702956a9a2d33c12d3039b219a726c300aed5e8f4e253de5b072b278c56b604e48fadc426dd6e2647f155e431b720a6ced4530b29adeb23e0979393af80ece55531e1268be1b953adc2815e2bd0eaaf3e2d8f11fcd0a9795bcddbeff113bd5b88c611ca56d319404cec0df004b7262a08d88c31146c989f115b2b64140ff29d2b6c50e68c4bf1d5c605d4a6e6b3c1c7eb426a62feb4e6e49415ba9ea123ebfbc3575034e2be80ace126380dde272fe110459c28af2e7c356c9d82b15e05b2427f146a072508a069ea7a15f1c1b60f57db43141b51e137d0a157dbafa3b6c66156602fbce7a909c6cba24705e1f32b909e723f745de04034bc23bd2f761dc3510e3af925c6bdb1fdcd6be3bdf637294ee157ea13f4b3914e017cf229c56d899d4e7f9f566fb752e0f691a2d937e72a63ec5dafe0b8fe158e4af7b9da8d13784ed0f4538e5a857bd69c91b722a1872bd62bf0e249e3c6ddbfdf71e98b40fd177f08af2ea119f09e3afe78f404ef14f4de80c4cbd512991d00e89b97ad59050327da885724e232390cee4e7db61807264751a0989d983f85e9b4e8fb03c3859cfadd3a481dd86dd6b5b6e7270dada0ca5220b3b73c72d2c41af234e8cd29a0e26ff0a31f48de5a297fd4019cc0f71701453f480697787372df8f704ef089ec43c7245fa64e2ac48c2b3064fc77c0f72c5044cb36a28090dafd3dc1fb8276737074ad4101ec46eac22664e8b54e0797206aa770b9070cca46f5c107d5cb72ab8f7b4bb4f8c36a188ba65a230b95ce872e5326369ee73fe1662576a291b5e1b0a6031d3652774aacab95b0f527cfa4151d9c7264b965babcb9defab8e8c31954e2d48df34f6e7b409e66ddb49860a5e723eddcb80a595959914134a7f57d41785916f3d19c30a43b297698effaf815872b8e8a225901a0d381e31980bd1b6580b430a9bda1c0d34917b2c3c369c6324727431fd8844a52b03d3c4bc89c29a84e2388ef2dfcb885a5f21e2d7af007fa4723b4f44a52f6f3c83ec7eb362850c5bbade3dc064eb5b654296d01055bfa04c5dff8d632ac75b5bfbd69201068fad04585a98fec2687589055664e4a8c3486353830010c0e657efda7080f626e3a47cd637882b711d289af4dce3100f59d79457af27d718f1136820e55e780a2b7d5b8c4a4d29eaedae3acb5b42032d6ddfae7252d04cc410b16904ffca88eec46a8dfb641238fa15f7e66f727df92690736b72227b70946f3dc1b16a804a8e8a83ae4c98ea2334d5bc1628195526cbc10a3b2a40ec8a4ace3384ec51ed4139184ab2f4fe3795dbade99f8c3fe1f610f4175c3b0fd01fcc574d07f5115ab066ea2ab06307a5891196ef4bcce1613e544aad6923c3a853fc3e5fc5d9f37d55376ec8c710f26d65034ae54fe7f025b721fce20c0581b04621bf58a8ace45428458e5d7b51d3097c49feb63582f8c5e352f303881779f597fd6fe56a5060fc38f63a5a1a09eb99d931cb9a7b0fb430818135146072665dd4a9b9b7910460b49cacb2a29513488066cd54d9eac675455679ba7812286f79dcb1f301f572955bc9b9490c9b5a1ab44eb2f3e3318498b79887dd138172ee24f92c1326afb9ff6b23aac42fcbc290828b23b8939560fbeea415e5f09f0dce461622d3688cc5806a52d0b12894ce4a047d944d0003e29d6c0687b5d9c6728c5695cb672cfe453d5b8537b529938f7032b906c854f6fa5e3cb746c7c29a0bc5217388425d09d4654ba644861ac403c3b320db9861b9191bdab980617262727167febac0269f13ca291292caa7605b0e7d90f80748c4764abb838333692172278756a80dea7f746972a862f5089fdfddb1ec445885f29601c6dd7286116f5c2c1d15547462295448bd980fd4f6565d19913e7e39637d60a72d94cccc10af0d84ed7c3510edb6731fb922c35794ec5c5b2f2747ab4576ff8bce2185e9c0137280b707f057fe333f2f60afdb65abc0cd4206b93f1b6af52bb9caf13bf2cfa1729a3ded3bc6fad7704971270264d8c297060989073b5024012de8c2c74de8ee5a3761adee1c5f10b59fda07945e46159df0028d4351031a709f28028f7a47b27225ddb850378c5e6e3778ee2716c027e20a4386490fb39e1c4a3875b7fb0f5b72420301abce1a0f323e2fc650d63fa585ee1ea23d10f19030ad9ea5353995a9727b924a116abf363c9861db34745ffdf42bb622b3fb9124680b88c3e986da2572003f59e541e434d32b26773728fb19385e89a06a81eb17cf1cf9b86f53d09b7215416ba9c1588ee349db3c3d3dfdb8a50de871037899b5e16576fe6d318a3c72d5eac542fe291f960ced5a32ce688046584c6266fae0f333ec341e144418f172f8e2c1f3590ffe736bdacf32d2983db66b93c8cb282e9a4aa0259990e5b7df7260b4a94803415f1a6400bbb0c9b8fde6cf209487a21875ac1406ffe578184972fa4c0b67331770d2333fc3cbf8509134e6318772390892010a12573e1e6fb32d6e6ca2c6f189941c743fa910550ed73e240e7224705d0595252c2201f3a75b72c88d4b896ced7da634c8ae32510176a02b18ad3444b81e5a593fac0fc547fd72ef09e54d84c8133ed29785b6df76b33ee1a78fc9d4f25027724ce146a00de972f54bdd602c320f0800cdf8fb55148bffe2728d6dd725cebce1a2f17fa4d49372e49e5cc52168ee5fb5eb202c42d9aebf1d44b652080aab7db4dfe6804d867c416cbd74fe32d4e0aeeb7cfc636e1388a602f63ae5cb04a9f2293700e2622fb4724c389253a3db5d46c010712a219852600e66c590aeee46c88c1ee95d660b4a72a9ce3199a3d7e3e4472a86c961ce412dca1b2da2d6deafec538d582e9d515072a82bdf33d82a7a478580150a1770a3b22c6a366fef9fab8f6927b78010e13a726196508df806563a374e643c0da7281291c48a82fdc760123436818d6525e672cb6a0bee5946ed0713d7bf9f0dbf8424ddd3f7f60933331dd2ccc2dc26511567f1c50f3ecfad48c79047ed717b0d5c87873482722065d5452b1137c69068b9725bc74082b4da2d3c09f971d5acc228a850051c3531729894de59fb8cedbf3a24e457f29a567534d31ecc6948d3e86ba1de3af3f9ab677ea71e09c10a6f7a083658d0d8265de3b179e3ed893ce1d51407755613841d460a361e55c599dc87365250ce6c8b35f78024f57f51c5f036e8b0dfb4ceec36620eed3e9230dad715e218203e8d3f953764ee560e3de9fd94593fb02c5af21f6c32e07a5fc8b478112e6122bdafcec7218f9df14a44dbc60576e271617820ede3e7229127824ff2c2645e0d5e089f429e577d198de811c91065e5d260b529ab69b7c3471a27d7e88e7241a74c08732ddef19361e2a977d853ee31738926025079875eaf8fc3be8967dc7212fff8509242a1ea445c0733605c6603eac2884caa22d9d442b26545973c3d1692592b050b3e7e244d28bb90415848f0c410e7289e6a745759349fff76f34d126b7379f1fe6a41e9c4e81a66d12f858975cdf4cce1ac53cb900c7a7246af60729e893f9dfd37a9a15c0df4cd56d7d57329814ff1a64a50143ee737346d9b595f2182ad9a52de1ec8c192a4194c83eb8b4399cf82c4d052659721ed6d2470df11b0bbc13be9ee0cb203398cc2ad6661214d92a3b7e9288061e5b8018fb388ac7213f6a37550406ffef4a48e0b86b2aac288418f68dd31e43b5729afe86154d6164b3946c6387dc706c54804428250f6e8f1d5420cd97df5b70fe1eff62ca93b2d8626db09d9c871298b88afe2aed75f7d4ae1ae7675dc9ade6900011e2f8ac772d42955ea1d32cc65273bdcd567a172200776ac6e9cf447fecb1ee288c46ac77247ec287f94ac927aaec779671e4ed75f92dcbcd5b3074f26dc35fa026d3a8f7233136d9254c1cf5248f2b0b2ecff467db08c99c8bcb74b85ddde369a338be6729f24eec56f177c6095825c599a9d21f8440558af2e5e326b854a899b24a68d7216e06e03685b5d37a0cc8fa246bb3d7f3161b519015edd3f63f8777696f2060aa9afaa2030b9140df2eb539e8d4c98a6ea9831a4be227b6a0ac71788ef4964723ea8cf2089215acddff8cd6c9cb9f2202df03e65e1155ade8d2d4083676c90551c43fcfb7efba59d1c24c5c8c5b9de0ecf4ff5ae2d2e7f490ec981c3e44cbc0fab32ca86c5d95c2e0fc4778d535f34b3f79eb30bf721ff388ae31fbd2b1fbd72521eb3f30058c972dec5d22ff2f816354b3333dd55b31d6428aeabe6e6c78672514714dc4cf42d37b9bfd9252b5d5d48bf3d3233894d85837a8b8108a1fde172954f9ff23ee9acce9cd107607330b89f1179d54674dbf87f0d604a9f11c2ab7254f43cfe3262a51e9b20bfd916e6a7ee9b81df09e8bfccbea442f993c153ea7224f1030ebfbf115a603ed3b573993aeb0d9cbf60ea629fc97ea1aecd3fdfeb72939fa0c647a02a7ea6d274a2268df8ce06def36f021369cd1e5b37e80cb23e72e52529cdcccd3c4cbacf0a678e6a3b9cd3ada0629a36c22aa1e729d08ba2f420ffe27f38c921725590cf7b9ba8c792a6e8d37fc206f53431b828fd4a5f5638728e8d701f13523c9345bb47e32972f09e187e5757218f958aaed8b7992277d217db36bb9418021be44470b2c08bbe276d276745da64a7df5166ac23b8ee8a7b727b4666d0effd8f5868bd6ded0e8a9937246e150e2b0f79b3eb3247e883295072130cf0f39ee1e40b2d2dfe245afc9619b7684e67e7b0aeb27c78fe90fa936b72a6b820fc0e1ae9d259f59ed38b8b3bf8bcbdd12cc15b98ee560e8f6ed0caf27238cb78f75a9849b7505c675708003e5760ff1178385b39e71ff21be5526dfc62047953a2f5fb26659579cbf8c6314fff92d2a24c8b1da396f02d985fbe013a729d51cee086593ded2add382f6506aae7c0bc917f3d128ab180377a5c5c7e38725939fb056e87f9a9d30197fba2b2beb7174a6050d26102ba4b860fc07a3c696bd95b0bd554c9617e8bbfd5e66189c83954ce7ca4c69a5686d45405f3c7a30e7280d498a71eddf8a0a9ff81144c6845fb7d931aff2584d20f0689865a3c248a72ad76190ecbdd8c515be7389c4bedc56f3824a04e9d2c543bba70009836bdc86bce0b2d32f1d4e6fcc23815ba6bf58420384d009fd7b964a9c9b998351ee7f9503bd02e88743dbd61d2975d6e94522e849c35cd2f4b023e3dac110ad8f9b7594f3425cfb3072097c09606310a59a31f37757e933c4d5556f84e54445a77c1fc72baeb83a00eb77156f90e20a8c785226b667f5ea9f2708ec4bffd0d65b1e03572e28c20b4d5cf686f74a2fccf8e4160e160ce2b4598021b62a6aecd80dcd1d82bee27ae0e0106de6df1a7ab01e5b85834261fda967b009adb7d492ba8dd1e6b725e098167f4f1219ef9161ce95f360a01eb145949f310db2f464464fb5e3e5b728df574ab4a1bff2fa8e78a4537f33985904be4fb05a4fba7d4b8df33f2e9f35271e6c2c55bd2c9a1c5f9baee1429d5604b6bb5c38126ea63042ccf91d215027235ac9f47559055e6bdc9ecd2a4e2853a43bbc4eddea187c562a795275a3f4a6d91137583dd437174bddba238da8d6a78a3984005501a36c94449ea272d39ad72253c48a32c6e6f4e562a2b54e3c5b07274444a570126541b53fa2760b47a2a6dc48fb8f302db626f2ff439751bc428cdb45ac134f09a9a33432e66ff3bd7a43bf1213c24635ef147be32183a72c7cc4a90218e19b1b4b01d556bb10998ee054b42ee0e73b0c538cc031eef0656efb9519b590385ee4bb76429513f008528f57257f81bea1cd7eef597c8ce091c345d10fdd1ae59888d6007fda0058894da24726491cd6e1d8da95371b94a055c85b3422cff18b4e4657a993d6bed0dfdde3b1ea426975ee380f4c3aa39b3ecdeeaa373b513d8b381f628254e059bc8efdc3a52059163b5ed6d717bb4349ee1e918ff0107db349f7525d6872c37c09c332dac72b5e18c101ba755daa637e6834c8811d4f85500d0259f0a4ede644e46388470195bf1d822e5ee46aabe014a2d1e015235922e6e9b30d8f329bf59ff0e61178472ded176de12ab5414e7de4b14fc77d0573ba58c303e853adf6d44213b3ffb2f72ea3a16a452efd5165933b6e4cbfabe255c781fc9c92928d5d661ef9d7989a96cb87e7f99838ca96fca17e63498a8fa676427fd2b040eeb696281dbcbe6adf0154af7d16d0f4961fc480ff39af22ade7bcbdce515927081d79b0297ac1f5af2727af0e70765934adaab707ef4630ec77a2544acdb0fbd7927f0778e0412da2b185cd4f0bf61addb9ed6f43e00c1e21251067e837911c5fc66ec170cf0772dc1365e9d04cac2014848c941a01b5db0a0e5472dab660d88ca31fe8bf716092fae724bea359d5ccb74df6b17f6caa28c2dbf35115b27bb263764bd32f7fc64808f03c3a60d9e03be407a21c029332778febc7ab626cb80fcc527b2e58f6dc6836f7272f1f02e5ce90c1eea0e288b6138c12651e8bb2703da9534ab4871eb26c215725610831ea183ca83c7da16ac6cb6474421cac2c487a3bc2879f44f17f0b4f154e5e50b8f4a2fabbe3585e797475c326976057379a8f13bf0c3a14e86e62be572c9814655a385674dfd63f1f17175ecda59ad84394d2dd375196ca9bcb195af6f22fd93cef77602ea851693a34f49aa8a8d07d7caa76482a11229e3c591f4696c1d32fc350581d70451b873a1d0c70dcb53f650409cd2d6844c70e5005dc31c72ee26be6b1a4919aa42e39a8e2df242a8cdd2b49c5ba22e42ac42a394dcf28172c83ca77bb5b175029358fb85facca4c96cd676ea4dfbe8edde09bffcbcf03f72093c23f357158f87e8e353d6a278102f61b9132f8037aa3974a3a5a14340877103e823e35f93460e59e8f4ec8824d095ff2a19d3ee771cebe9ee36f735775e06f72c81b283fb671aadac20d8021da7fe70faf88c1957e4cc68017a5648248d72cf66029dfa39a408318ea8332d086ce7ed235eef50c25dc21f2325997d54ff5eb69eb24f53df071893b73bf269448d54c647bbefc9c0806b6b19c8f869940f5e909e162149f7e56969489a914f76641679148398030254a8ca652f3ae76a9f2b43a6f89a3bd304d473ef44d79241ae10498b723d65ff9e3e18a456166cbe2d72de66230cdd25edd2c63083ecd42b19d20caddaccc1892f397ad89bf848958802dfc40c2cdfaf1cfd0bd863409993daa4ab26632a15b8808eedaaf15e4796fa72c63be48926e624f8678b50d552a99502f1141c35fa35dbbcea0e19326eea9672fc16d3d51de86d0d4443d9146c70418747982ec5fcb0b98a5eca9d4d938cff0ea738c13407a34a1b6abe6c5a9af870f81520e035bb80df553c6e91de378bfe4219c7f85ab29cfddce3428789266742bd55563e3f19e6b2d29b9ea8a1b75edf72b37d8a1f2f9bf7e8b36c3078a0011f7be216a6394847a6d87eea16a9b9eba17226f108eb16a628f7f3284ea5b38cdda59f40e17d652f39af88393a857be2f812bdc9f8eba8541cd636c16e3b9e43de59963e109915ef09a3d968c4b36ee7b251279001419a04ff002ed20a1ad15e2e2f07733394635e5eee094b034132834d72a3d9f3276954d62ea3e81da0a6725bdffe57025fcbcef3c4f97ef32b66233f720e4372576eb705b84db938534368d080d27a45c6e31b3c39b23f38f6b3a13a72a87915964896dbcd05de7fd16823792484f194575fcb3b3065cf75e2a2c8f12b71aec4ebd430b34202710655bdc93385a3cd518d18930f5b3f6da98f0fcbd50ebe433f2bd8ef4fb1e6bf564ca02f080e64f064f32064d574e2acbeb01daef7305e98acd2911596322e1ccae42ac58c6e7c2f1c2e1733aa32c1522f5a9b41c272894b009b46181fee77882aa786fbfab1044611909019a182837c0927bcc19b7298280d1fe3547f63f85b12c2d9c41d1999fbffe111e896b68a86984b7247dd72aaa6a1c19798b1adf4ef09c2b7636f3882ae6812ba9d5a8ff48ecfda2c657d7222fd830985652ef2e83a98b121fbf590754b2f82cce874b8576a2fb11201777294a4b4bdf0d87026d2d4642f1dc34ba28bdac1c6395f818031c76b32f7a92a5dcad6015a1690e3f2e9b905c02e5e16143c8bc0c468c4924b2b3210f18b8cdf0f7b55b012ff47b8493a6f69fba124c5c84c9024fff8c52bd2ad23b4da48a5d865461762c7c800d2cd0bda668222c61195543ab2954a736ce11fd471e859b88872cd241993d0ebb3fefb89dbcffa8efb97050bb34e6f616d2d48f02d0fcd936e42b173be1887163f862ac43a66a8f342a2b8e81d11907b8f28253a34f49d30792a86b16d073d8ed60e128c0e1a7545ff5da3ac1f154299d9925ff1552786341572a7bc555a76447f10b7feb65e2cfbe64891d641b7ad90b74e6d14dea5c1c0ea2057e61e4aca3c16023683e1e5d25451e0cd78539c5c489f04234fe7b2c721604586125a55d3062b2fdb6780f2d19d0a37c1779670a305a0fbc07c6bb14116cb725fa20b73f1d68a22bf569bd46ae1845b6577ad775c91ec8bcfb865a9e76b5636c8216d3182322bd15d5ebfdb46c302ab42ab134ff5e4ba05bde7931a6bb182726e2cdb28cc460a8401be15f284151cbc69c38c73b6abf841c9a9f746d658e5727a3af372b74c1848505f25a81e637afbe5b9db9ec947dfceb5bbe5f8d496077203e2499bab97b11db243cdd807e9f65c4ad17156984cfad31d2787d6da6a8f5f4a5c0f75b8cb3ed6658b4f89e00f0e28f3e5d91fb506bfaed700284f1857f9725cfbf394e1d497cffdebdf1b67c70ea3b41f8fab15fad4f79aa82a7a73ee2772f561b4af44464b43fd26bdcebc4f42c223e58a69380576093f214ed3726199728e9918789634336079c26a97d8e4025d95a8cce1bd75401b3b2451ea03613a7245ddd0f253dd3f4d7e428fb84b10bf2ff36a43e07526dae8fcca566c1e6ebe72423a7710f2eddda4520171cc1206d15a1b20fbdeeade257ff101cf861e7f107254f9029f6bb6a4b840362f0650ec7638df0a8be2aa331946d837af43f4387a5290c9f341522166c9d608cb69d81a95af77132709e95c574111d2fe6f7ba6601618b64e088cd1532541d2e9585e4e83a9d9b044532887f8e1adb622ddd0def219c376aaa9d11547babfec78857ff85d3e1250e56485942e4449aea2a1c7728572222eaaed25e6bb02cf86950c594a7eb4232cebb98a220c76837814d843374429d2b7513db2758b5c4148859929b66f35cf1dea0a98aa7b19b4790c4b2d176e3f72665adb19044aec7d1549e81206a0a4e7e0d9eeaefc150f73d4cacfa11905726b2391e69847445dbf95b81868c118b2ab6b92354418fe669f69846e0d2837247c0bac89784e3a21803ef7e54f7ec530ab8040d8d463ab659ccba96b2708aa6043fe76915e37ec9f88726913bcec2f1cdac4ff23b3700e989d506fb2fad6a32681c81d11f9fb48cdd536fb66b1e7c19abaf5123197be7154f524ca337c91aa72627848c6ec6f2f4d7894bd86dcbcc7b2590654109d0b471a1c70fd05d311b41f21cf47b6767d745220ab438df6749de22f769538aea36e107beeac7bbbe23335af5736786883365508df782f022543c413ea1158c3d9486ad75faf191713c856fbe4bf76e8e2aee979ac1a70d37722fc8540f3c4d9f7c3f628b66761bc7607280007fe0a907afaa76d3474e81e78dbba782ea93016ecd19d9019edf11af4532a8be84df1d7fe97ff830555de849305293dcfe3c1c43f8e4ecf4f280f95b2e972d37b4b0f37201319973aecae857adadba9bf40996c8e2d20c00a313e5548045937b8fdcc41ef968c945697fdd46153ea189e2d935f59d2d7782c225f771e8d1ba15de5b25b139e79d9d42cb0a4f94aa38aa4b7987382133a710f70702d93a7726d1987d7f20fe6920bbcdb8308d238b84990c3e3c3bb1e65eaf2df0300c8ec720826e410a53e9d2a45446b7d4df6a094374760dbb6d185bf6635346731d9996d61070db652422aba52566bfdb9d71716291f1c680f3893618e37e07e18e5557273fb896a0acee8db6e98228a040cafffcb229541f464ea45036f049933485d210ac8ae3aea06dc9c1c6c76b35dad089a05e6c7a0994cb6780c6673a86e4f202a44c4fd4cec7acc324313b4fef877ab206f6f5c8c5af1cbfa1faf2a9b329c2972c9ccd6865f15e92d731b9684ac6ba2351c6cc947e88333af519deb4958428e1c77d53a157848e3ac85411bfb0fa3e2944aa1e04d9e17141af2cfc19ba3f58a72413428d235fa30efd689f6794ee0a181c52650ffb87a8996a86e164a02d78e720edb206b44767b7cc0480fec899325dce7661fd0ecbbf737c1560f8f9f9fe129d6fc59696684b8e4df4bee2c92d43c126b2a50982107fef003ea065f1912de486cdbfe1b0a347527fb53d874fff61c54dce2cb2efebc7cee93d86c452490db5f3c5b8283c5c564664b6e9d8775ff897a62c0272479aeec13e14bb826b440f9515e98346c5ecb44a3c2cfa67f1966983b3fb1de29c64f82aef5ebf025dbd1f53cbd4f1e7e5ea4f0f79938cf784233a6861c98a0448f7aba8a67c1a663f9ebea2ade781dd30e5fc886ba3b65001585aae3f4146ecca2bf5c513f5f6022295d8b72393cd321d95d8878a098c7e58ce193831acd33fc5aadbd131f8625319025e672d77197ab2199de0f454d36f923db7777d58f6d211fe14aae24e92235a8a09d6253035eb59f8b10918de4e263f65ef7b8987f947cd7fbef39a8fba2aebb7f4f720b8edc57b6bb85a3cb8de6738b771eff595895a686c8da1d2a85f04828c02316ca7c49d1cb39deb9068aef67b8e6ae184d642be27b89701f78f98040a3e97c72df6299b587f6f0a11a35eb9d631548aba32c7de1a2ad672d2e03d2e3855cbc7212de8cb8d4d0693c1c8f33e32fe7e0f726c8b1f0bf12b9a47b4f3df1f04ce2723a8b0d3b1475130ac59c290588edee2fd448b7a586b4242f3bc1e177276d4672d8b8058a5c784ceeef96d8ad0e9639ec95e0e40ade22477c5dc2683c49b137611a5a885ca98ef18b268e04559db190d95048b95dfce1f9dc7693a9b8f738237217bc2f470cce3db6bb193c941ce7721b79bb198743c0efa3b333dd6d6597c61625d728490ae40bcbd483c8ee0353fa79c9c5070af93b2e6d101ad7f64254531d596609887f1f037bc743682c865904d311cbbfefeb28d48d2642f296ebab067285a2826349278f9bb6308d6aadb191cab1dc571b9f3f63926b14d9775eb099723c64bcaca29d2b766787b3391739e64cb52acb8ae2fa29cbec010ae109609170d0e3e09683c94018b1f520aa3799623312b2a0189a678faaaa5d1c80adfae4723aab69e1ca9df984b92ae226f5522e2d155868bb4b543a999247af5dc3db30729445650ae5a6475985354129a8b008a15cf08b4c743c6c93cf1c5b78b8001a4ef6fa2e2e288c5e386340c4d863bdfe9f847afaa2abf4eebf5585d6fd1b0d4b72d7b9ab26594f0fc2f05030c83a2af030132a1478756f4b869f60e0149c6c82727a016d15728e44cc1e5b41c074d150a97dd56f2e35f940b1b04fd16d27290272636f69e81612de098bd58092895c471b7defa6bed2d33c5879f4ee8fa4e2287289f5b05c33e22b37fa448b485d3f3cae0834e95d02dccac6ef93689798919472b55c75faaa3d8cba6a6ee64d19e8aed3bbcf60df017b2c54bf8953ffd888667297e135c45fa9b09de218a96ca4fcdef9fd7379ddc9b140fcaf940414b7e4b54a7358bff1957fd79a47e64830a9356448504ad19a3c29b919198a7845df3f7e72240badbda89cc847996b4de48be0dab2adbc93af92db38260f451118cef2d9079795c76bdfcefcb5c28623e39f171edf95f935aa51e96135b2c6b0a5af6941726c5147ea897e532673e2316baf79746f796a4ac51ce334ab7b5d75c1bbc46372ef2c2d315e30120265f1b91292d776806b15430d60632f5d76eb53af092efd553dd76f00878419005d7884a534acb259c314f97d091891985f545b482aab441a1b76dd6ffccd6ce8c035482e50e0835853455f9f826d8cac57271f737bf98636f2a11f4d40bbba35d03333648a74205519d625a9e10295b21ef9df152f37aa72915bf4db536db56f5855a1bf35001ff7d143a492eb978233e8f0c942ca4407712769d5f8d3f05dd8322e7f20c06932af1f296ac5c0f47478b3ec11f09481fd5b99f9dd97637668eea3826fd2bfddeccd9b21600709b3aac3e57f700382f3f50b5d7b8b86a124ad31fa8e465a5f410b9d2015b5497591c71b7043d4fed8a8534cbc3ecd0a22876530ce7a92f4e88ecef9e92101346195f11790968d21358aba726a543dab0b37df7b2c966e9c8431bfcd6d69a08ecfdc1acb5343777a71df975f48a8806f7030cdcd9aeaaba0be4d5d2850d8bdbbf318022b9e706656bc9c9563669f330046fb54b9e0a4789897cc06388edd2c4ec62cff989f08330014983a729b84f469ee1250649bf0d2d07089b403e7973568d946cf376c63c715069daa491893c5e4849112d01a6d0609d4f93dea4f1e7b72e1c9fe89ccbfd41f26b161432fc77440da92aa8998b88a57ef34002bcbaa69b99d4d65fc865bb716fda8c472e922a8f2d51502c3cd6d561dc4d627114623a805c5caf29ab5af20c3fa45fa7258615a15406ccf4edf479480c9ae5b7982a7776075c4e84a0a11ffe926fb3d48249a0c3364d69b1f66fca7c307791bf2eac1fa77779557da26a3fb9122d877657dd774b793a7526e34df590cdb78435aa5b80bc3454269b2b7d7820db5110d72c8d6af0b2361013e9b5d00d22f51a6d05c3fd8df484bb8131b09762fea7cd5727a119d6bd2c622a446a566415164fe7540e76bed0cd83cd37534db4202fdaa7275a3c019953d3fbe3846caa63dc6ece5ff69768b888fcc53536d94e73d2d1b7209e1d73e0305d17f487e878b3263b885f0f8a82ece1aef952603067ccc37442eaf36df5b239be2af06164959e1521d97041bf7b1b6b12ea446ebff9b7342cf72ae0163b6c2ba33d281f0b0c3a2c9d0653dcdb3c52ff3eb90bee7b1d81524a77284bd72204fc0c344f711769179d26114d674246c6f816e61f476b9004fce9d72f67f52ce35bc0141f0ac57892359e5a09d9d4e5b76c5da8ef741fb1aeaf65b72807d0ad6950ce9ae6917323c7df4280fc65e5f08ce2dc4b9ce6f5c583f0ed1729fafdbd73ea445fd910773388e3b2f5ffc3cb44a93620201dd887462c2fe5d30d65c11217fda84ef8373caf7b542c5e81babb380484989d4dbd87e98e82dee2974022004879a5a0b1cfb091afbfdac100f396829dc2b5c14a07de485ea90025fa11f48e183881669cca02d42c16d3f5bfa0e7f5646f0d125202db019d9b298723608936c838cb5b72bd8e2532e558641725524f9a074e7be080f16df791b93725777261890d44fa05d6c7750cf65ef677757365eea91fa393f09cc7a34a62872d0c490d5deb418554288eac7bb400addb54550f41d9e6b6febc280c6c2db9a729a575a0e76357263ec1e6064b392396d86ea46efb308e9773264a8e84d852872107b2f163311a7dcceb2ec5f5ba6a174175e6a8741490723e88cdbaa2bc6ca722d66bd2a7969293c608eefa352dfdcb117286fc7b27e970332101a7c963782721fa1bed443ee7a45929bf2db126bc3fe60cc4de7a80e0a752da5e013b480a272368b9b0aaa707027235892e6b7c714d17675205a143ed6673613fa2ff24a6c728ee02c65f5d54430dfefd2f5f4d7f9bd4d73cc03ec9ac91dbcab18d1c78d187222ec5a58ae975759246f8d47a771c8a7ae0004ed2a1d88610efe2a3941d7ee3625ef22fc9b6df0a3d55b3b2d67c106431bfccd11e97dd6f0a2897d137ea3d6728f74d44e63307136275b132128e262a8ed35460dfec46ac55b2a050b542078724ee6432999279cc6fee227b97dc30438975c6edc69cd61ca558d9021fdbcd07262225798480cd4f7380c3718aa2b3a9480642d01ca4c7caf4ed10a5fbabddc6f2a01815f097deb500f359f4d4bd5e71d86bbea11b432c0bf1afc2770ac86ed72a5dc4620ebe9f8a133756b45d6a5c650e7b54be9ea5f6cf7b6ac685ee5931564f941b270f2bd3f8324952123bbc1ac76abcefddddb4dde38a0c5dc1f4642f42e2c8b6c4e7fd458cfed92e9ca4def64484bb72c583f795be05d9a43f79b6cc4721d477ad0b006de57f9dbd72bcd7ffe79842d21cdda7c6f763923c774306920724c1e8e0be32a84c6a854723faf5ac2f2eb1fc7debbeaffefd813a943e7039f72818c35da6ad6ed7f252e2ab3134cd0927df527e962848644726d70f52ed50872f7611e6c58f095c7d31f4c6ae3d255a64204cec05913131d39456d06d618f572d2798058cfc09d173b0c3110df10913856dce8499d102fc4e844ed310de65c727e8c6d06afaabdbd4bf996419f03e173e6c0baf7bf2cfc35256401a5dc85d472aaa97d501015161a0d1c0be80cf854a303ca25f018d2932a1fc0ef0349fa90726f60c5a8cad28e0278315e6393078bc8e25126a1a72c32ef5a0392f82683d4727eb002c4e9142c504a565a1921cda4189ff79b8683717d765fb8411efeca495ff0c77033c222329324450d8b0c87cbe7a0a21d79714ac015b967611b44dfac72a437a72c3c85b5e8e376185a5f95510e5d81b081f92651bebef284a21a215b72e43a819b3f5c3a0fca5aa12d9d8c2b782d07a5c4047a32894ae9dd6f1c1d6b7201ba3308a406c49e183f640cb055512da8061713b6140b2b8154d56fead2f16fa3f187e7a7f951acf4ae68502888b4579c749eab10a298ce446a16ee2b191307f5a240e4b534238eeaba02e37271e5bade21d5e16086eced0bb2c78b000b751965d97c64e191143ea9ce41bfe54767b0cad64dae09fc14cdf436ae8b75115a726e565e813217e21f2fe95477b06eb2d68608d0dbd633a8d41b6966987eae061424a1d3e5be7f2ebec4e2dff490b1afdea98fd4d60af70de84fa1b886f2269c721a6b145c6539e1b4ac8129ab1d2108fc60a344c2c9dd4a5b0ecaa0745a817172565f15357dc41eed19afd134cd2450d730d695ce01800b6299801f9df261ac36607f0d69c26a4445e9bec814bc400025678f3e7d3fa70b8d2933714d20328060e512b8d4d11af32a59bd1f369105fc002c138aa0988d3028306599ee7307ef72546d5577b51c9a196592075e25cc6eefdf996abe689cdc470c68ab81e6b96672f74604314b442d682bf0b3bf281a388a66330f24b08063d63f3f1bf8926b702c6d0115f3b434fa4b75ce74e3eca6a9ab0717a0ab949449f1310fc40b0564b372d285a90398263febc71f73a2c2f480ea014f3f5f9d7265ff6bd10e6e7f73f521aca9898a1759f09fd4f8c6ecfb8c47eba19f383526ed342d1846bd0d2a7b7a585d3722de73f1a8e4c06b54e91b49f5eb066b6bc6b503eb116a73aff8b1526d66ffed53ecd953ccdb268b3367815045f1c25a8cb3fe5b6b371eca1faf27820772c27ab791bd94426fddeb49967348b5c8ed78ed3e910f4c0bf469f76242f6826ae9ac6543dace36108e305b3607834ab15ec9297e5bffe62c88ce0eaea858d4078442bc43dd70633e6732909872b637ba4b9b366b0996a95eea34181fd4658c6b2100e594d1825cd93af37ef7230688284930f186c127f844cde763b46502d754151940ff11bfde5ccd96aa497353980b9707c8f6ed29f792c487afdc8676830db228c44ba69bd136c9f0007529e5392774b909e4168b5ea8c90140f59b507d722176eaae7403bb97aeb01a417a3e03f542ae53699a72bf91b20ba025da660b411cd22ad18d894ee04bd62c3c94e0763214812694256fbc6c822a96edf8fc901428ecf3fc75dbe5253f4740904f55201b186dd060f3f6eaf784267a9c5c20c472e26b9a2db3e8f04941c7f1f680f5154eb8a90bebe7bbf590573f5e1c26a681513298dfc02fad612db015d356d44be3d63c5e7f489341ab34f1a2329a546ac2728fd5bbaad0cb5d39553dcb5dcbc2d4fdba802448444ec27a6b81ccac33057726c29bfb424affe6fdf32001e1b7697874b7568fffa9df85d1a3c0db4a9e0184721787b73b9be42ff2be8388d7f99354d36a4825c396ae8bfabcad3898c061ff72ad055711ed46a49208e49b379893f2a8f1825057617dbdcb1bac21d5c0f8f10dc9027c57968cc57fa1399fddf58867dbb5359df82068d3a0de1adb1b87566e5c076d7890faca4915cadada39cd97af5dc61b6bf1d9d33779dd1d7eb7c7891d228e5d7252f6bc28657687dd6cbf25a332ec5008754c0e18f0859593cb6194a8727b2dfe95c4610c225a9dce71a36d3879f3515774fc4ce5ab10e38b38e7fce672b13eb6c774a74709de377a9d766addb06ec59f2f4d5f56990ed4b9fbf496aa59e7a5b5675b2cff15ef8e52b0e66323eeed494d83191ee2d4fee291025808533b0d6fbd8b1bd635c04fe08cf068f16c9e0ac31c67385a89925c161f661597f37215e2fb2c9e4f9af71468edd3f1f552ee2b3d29c5720bae76ce374c1bc199aa42548b7ef93d59a5c557d01f3393ef6800bce16223a445e06d153ef22597093b27c461b46191353b6a821e2d048fc1cf44bfc9cd9290d67027ba326fe0071d132669ae5a8568b71c098181b5d915781e0cee922594fe669f91e2b662638e9e637297892e018d262a071b8543eaa90ce2bdc5d52abc8d6ac0f2c2399e0933b84e071dcb1300592bc797d95f17788f4953429d1cb4606930eeea7095a5cfcce37172cfbe71699f39bd07ef36e16ab6a9cb44ccf180059d8d98951c69628191d73b72d05ecc776df44df8240eaaf0fb1f0d0c8d76b4e94b39b94dd8a3d7ae7cdaf222cd4e92201ee0e20add7d40b1855ebd15c56493286c458ece7a25b7996267c8725d7e0e6cd5c38409178be6ee417f3150bc8a67c3efa828acea74918251fb9f4217675fe666d8b32618399ab763df84d5a099df712cef4e1e5eeb4c9922b95c72e278525a27c1a7477ed4ec602f4e516c6041becae8b3b4b9f500c3c51493c372b152b3038be118ebb6aca99adb5bd3a31db1e7e296e95f5d1c695e1e5c974d7202cd41f09a5a82740ab6ff214281ca4375261477d54fd1a0bf5ff71ae5856e375a1a642cef1b11b99391373b61b5db0398331f9919495243732e794b5cedbd00fa2547a7bea78048a7d9e6fa8a296a475a4613ad7af303855789fabfb2b44772b21c3af3d0ba19f0b556d04ba4890c31d551cebce16b608044070aa7d8375e72262bb64e4a49e30586ff7fa007afa0dd745c27b8182a3edd08a59cf1e42761727bf15996c0e466ec10744967445abb7978e2e3a7f26c04f59442062ec47b4e0840652d5ee8d1d905e80723806865a8f0e76dba2bd4f3767e6eb627ccbdb9971a98ef9e4fdfc08721e2741389458f2ffd84f39cee772bef0773e6c7ce76b6695fba130f3488a3db0adec12fb97f2b0d9e58e8f2f4cf127545750ffa7305ec4c4f72a0c19ffcccbbd0a80244b2633f4d8c32d1572b5f2a1b55f5485505b74ed044637c0cb7c4f69fd248cc15346d142c1171bfc8c8d3bc75f2113e7ccc519990394ba8698724ae9bfa21d423eb804ae0197653dc920427450fa199dea3fd05d7721cd10d77eb7aeaf2677046fa7c560ac67432a3a0b866853cc3eca05a4bc0bb6c37fd6eaaeba974aaf7aed17447065da67c01200ef2c4c5d299466e7256b1a572e04641a1d3c4096dcc4f62e777aedefef11d15ca932f53d0b1a72d2db70c3d728088359b735aecbdeca190c0539c5258de6cb892b15186c702caba3253d65272eb081b30abad1badec23768bd2e931c59c327b6a2f947412a0de8aeaa0320c0cb3735bcf7dc11a751dd18bc9858790dd5144f52f9499f26fe0c16bb9a9d44172e971161cfd066c9b06798088b1bd8e601966e5363b9777ae9680dc8f0b67417284baa9b41f16ca78f9cc4a192211746f19cf434c5fec878912e218a3d927cd72b79b3632027ec535e80794999e807a90dfde13b6c791b504b58f93ce2147e972ae94b948b3e17f38fcdad5960971b1a4357235e07352b6e3fba0b12e73d33b1a1fd939db5d4f2ba493f7c91800bdf4a12586b24d9a1d42ae3a88f971669e3b19bec1a6e354df945ca1d4b313eaa7b63aae3410cc66d0b3df72f0e6288c37fa72a78a67b7b960f20a04445c29d6a4e037d67c8dbfb750c8d120b64c37a07ff872cbfa59ca1acf7b40cec2114afc7de6e72b636b065a75d3d7ff1c20e7ccf06e38b941249b2f5acdfaa29b8fee54ef4ec676ba2ddbe99162194166ba777c85e7224b258224a04a3259e0b9164a440c1a7fddcbc8343bbeda9ded93460318547172f0d0d3269e516a9e818a29a5dcc9b7fa9e5d509bf115fc5f7fabcc0790bed0013a0029dde4ca4170d84737d9a1b5d4d5753caf6dfbdedd81f3728d34f0e6506db6a6d4f89e71593616428dda4b3980b8e98c71e9d6bcda6c1d438981ab14e2729e416f8bd0719d291a413dec39e60805d021c353b2743aa99a243c366b85284fec29c44b0135823056bdc7abebe07afc2dc1d85ab34b6793981003ab75604a72f1b5384501b0f346040f4c33edec7323847070ed1fdb191c3f9986ae56fd4e721e35896df7827d225d6df4738d603925d287f93726c43d8fdc309b4beb032272cd6692174a99cc947bce3e0cf9ae0279f2ca1944682be7fbb0a7e55b49227972540c2f3578739c2747eab0fcfeba69c1cfc56a7ccd0319b0a2df3245ce35315272b65ec91e52f2ab89bfe664b4a972d1a94cc1c7cf3aee53486b3b6a67408b72236d299c54f34a739b41737d5fbc9b21482d9c959c18045744ba4a6f8f4f9072971b5d3203a37aed4c62fdbe94d6ca0e556dc7a6643ae9e143b19b49d46c635cdd68ba5dacb58e4132c1d4b0692e46d1f3f41ac73b9215e8be273e00adb0ec5f02c03fe83ca1fd57ef5fabd8cc397a98f57a1bbc4ee7ba8a13e92be4d6cb777289c90214a59a084f67d69f03a1f454aa451073631495eb17b4b5a99f9dd42d72b05e7ed4fac7dda85b3d06accb29e6a7b7c678b07e2fbed79eeb8684e086b65b0c68f37d8e7cd633db5f0b25cbea5f07d3a4fce707262da7ee69938c9c0fb846754eafb98c501615a715b36b0c32c1d08f60dafac192081b6003a3e9cbd3ca723b51ce45a4e5e0ebdb6eccb75680b3a4a4721a92d7c546dbf35353a76697837264cc98fe747e73c510399c7e57263fa78c933eb288a0e7237d68ff2b56717e7237044472d09ec2c0f387c8422df6e3608d74956877d903b994d1f09caa49c67254a061e2705e49efa3c4605329ea79fb80e355b670b3031ffdc35074bfc0743916b32e9b325193414697dad6b1b98a1e98a295ff85e0d97cdf435dcb461983267bf4d54c99be4fd90fe28897a6b92a68350ad9a42e7625452b2426addd8bcc439d20a3218dac7c4afd1d309d8cb550519441fdad8c3781b59c4549e178f5d33f344bfabbe20ac0e228fd8774b02c52bbd87d86c06e8ff64fc0b1377a89facd46f6e19a4f704df24cb701bbe1d05d80cad52c4e1cecb6267ed656ce5406fa4d16eb1b3c9f5c67ab53dd20fd1386badf32cde8feb7982637697deb868afd76704e9c0d2b3e7ee660eaf82d0ac02545fb830f0999f32220303edca481e2667e97726e8f03547cb00cb558a3ab416e4c691fc845b123d7f4e822e35113aa4b9a1b2c27949f5c06b3a26ccf3dcd79b4d95f992cc88dc62ed7e27a34cb243bfbeacf2e86886502b7d2cacdd78944b0dded92dd81f679ea3e0711180cf34022c5db9b208eb0fca11b9b8fb3825c65df3b500202417e27b4ad8f07e79c3cea025250ea72f0dff5563eb54fa64f41812079009d3031dafb9e3bfef3d3fd5e36bf12833272dca22a3c409ea4ae4e21e7db4bad016ef1d5dc2f44ed6a4cd36e95ab94432a46c543f80153a3f34dccd4f3ff9b8c26c8877dbae82918a89bf36b175898fc74723ffaa6c150faa6f4a070af84be4e742ad2afd3fe351f6454852e5c2208e90972c7e7b7fcb58c290cfab2afc360beb4fc849aba9bbba55abf2ea36c1020630a7285a07df707761a2d0b2d4364cf0f45e9cf30c30e0ec49d823b72acb0588a6672015fdb620042a59e0d0cc1f387d8588d40b396bafee3bde6cd2a7b6c34adcd72f65cc6918d62f303173d59fe220357f157e0cf9bc5667ce8220f0b6d70f6ea457698f7a9b8f877d02f4726789bfa69d560a9f4963bb1426d5292b70cd9b17e41cc0f354d805439e0984433fc93b665ae6a93a14b888591317f38bbcef340501f730145ca4957b7ae11a33295488e434f80dc81f3428c684e6768ad7f36841f10f51108bfb2fdb7d85f031ac4b76f2a3db7cbf6601394cbe387d260a60f860d726a85ee592db69ab64e567e99f74770fbf775ba6eb788294d99c8920640b91172fdfff8768b7cfd6d1617d65b95ad7f4d1747f3ec3fa442ba9310f4ca598c7f621ff4fac473cf47a2d85e635c398000f3af890271a0514c223183e4d43c38437246c2ddf89483b54e03f63c291be5e217580ff8f51e59507b45b40bcc5c2a5a46a0810a27607289ecc36ff6414441ca0718f5b8b39ae733a2f32706df96835f415a3e6488d695cd0e67faf5ffb424fae8c81a92ff6d79fc2e5bcc463af50366707579830f98a6511ffd0cba1ca26974f2b6c289f6a8e1e023938fd6bfad676972c09042ffb16bb414ca2c0ae5560505ad01de855df1b9416e3b07ddd7453c550c693375f1dd0c8ad0789cdd7fc13c249d75d5296f21020d4a0368334c97002672d2538f1ed7bc629479a5a214b94f489df88a39b2803c7e7fe85e4676f20dab723f9571ef577cef8d0670de6295d5455e1dcfbc0e38965287c01746b11a02c272be46f99f64d6e5507d951934a54c1cafbfc82669f42757dc37fb6e188be808728598d224b5f6ce131a8d9b8c483c1c44986aac9ea3afef479dcc417fed995b196d2f6a876eb48d735f5d6e89caf7cf938e89cae8f13fc50e72bb41b00a87b4722709eab9bc4d278a1064372117ad1951b9576de274a590e603533accf6ccd972a8011c86ceead3ee3e15d4135798e50b34838b4690a10cb4aed3056fba6b175a3c324ee837b7d5450abf6b6ab66ee3ec53a99e25886d956a57d0c9de5e420b72e71a80f84d9fe204b74f8fb0ea27d76b28af578fe73745066691a0452ad4a442884341581aa2220fa86c6186969c77210588f3fa8f8fe6a4a65cd2990a7e3e32ef264552554ceb0848bf48bb589b33cd71a17e531aca7f25bdf5364ae4fd5472cbaaf895cd3616b997e307f0bdf566bf34e33dc230c224b4a49e01c7c419d2690644b2f9a767a9616ae16bf403c27225dbb685a5dbe9703d307c2c8d5811a772bddface7618c2a03a7e3d8659625ba3c7da2973251b696149af3104c02fd2672d9a103c3c08707d17a63008d4cc8ee2418e5c287228be749bcfed7ddc8673f72f814ed24362779949b902bbf4e89365bf8494fd92d7d9a540d644117eee07c725a36aed679f1029768362f5de11532ff7b6bba2378a1a2b09d773153ed8117728b3a2bcbee87ed748b6691e07f0c4fb744ebc675290c422524199b159cfa5c7267eb3a3f222e97bab963f82b30daa975cc60bc4635fd94004725bf147d42595c52f0e4c52cb7bd531118a9b99833e25683f766c8b92f26c11cf81988f13c15726419233e157afcceb33b5618087a42c7c3bc9d926e8e8a0ac0729d44b4bc0f09c15babe5d8818958771aeed0600733db328e942194350f5353a4eaf4133d4d7223578988468a87edee9f675dde5f888bbc9f4e57b684ee2b802ad485bf99ab72f0de03b937e81f49f9793ec4c591ac2b14626809b6f714d2d217d9eaae88bc1c0f35d9921f009b63156edbd18a6cec8043674e4a515e5545a2f97de180edee72ee5e22367d4424a8bfb1c887fb06e44009bdd73f77f78909bf8566812a311572b7ff2214c184be3769943d14b49fa0aa1da76d79b906575faa8c96ec762183728c39c18ad587103c10473fc0aadd77ab2ef5b6f5212ee3f3646ef1b1aa34e5242cffccb7e49f0b587efd2cae21e08cd0b7a0e983fbdfd34eed851b1ed3e7575e0d0a8a16296d6e6b817151686e1ae1add989b35fc47112ee3bb1bfd287282572061e3a8de5d6be7c1fa7b36718347fe6bbd3d057d8712d8990f92f3a8854bc72ffbe4d4e8afd0510c0e4f77500622c3cefeb503d19cf5fdd3661be8a49e77f2bf8a6b27a2b32d322a9a48f38452d823d7209fc9b9515bca012d41e63e03c485d785e79c8248222ea0ab9eadab81339764edd74200691b474ba4a3d8bec0e2b04e680f59e37669b9e10e90e000f73def48292419e175df5fea17d63bcecb57c7289e539553ce26ebc735f78b892c95765831d29c8d55c70e06eabc9e1b2df5572696903a45b19db56a36f8c636fc094df9fd6a23676185fee9fd220b5cb9f330b6c72d609b3e6d973745be9cd813afee5a4f0cf33707e5009e787dfe13513f86053fe32dab8a2e9230a2a5690111b038c0c66f92cd8eebbdd298ac4a3f103ca72d91d6d78eeb7fe008eeac40251e7dbef0951d2fdcb9ad99544ae212bd57bd3721ec88918c80f96cef7a006d3c51e520b3eb68f151694fb94a44b02e89e6bef72b3917ea3c58db75a0f3fc64ce879afd525971b6d1cb4b3dc252419caf775007244d4ecea640fcaaee13069a0cd48c28a69ae8dac65c28064353fc815cf004934f11da9334dc23a70c70ba8e8f1a2a41f8119d1b67e0c06324aa0ca1a3334e272825e7f4117ce8089aafcc4d497d68a27cffac74b23c6c6c3601e3c91b9233272ed039b283bc251bdce3947f4ccdee8a56e03171a5a7a31fafc70e3ad2139a70b6c3c2d4d5130622b1d8ef6384b1b1c4d4348750d03e4b53e3c0f729184de32724a342d2d7083bce814758ef23be5813a03d44619d9809957c43203a4540d7c463849cad0bba7b3998b09aa03e7f15fcdd3872000e78255f40e5a4750ea4d9472d8ed993502ec44b0813d505197faa0995c09d990f20b7e55ba3fbac0c56a4072d3468678ebd32cc937f43b0930d2acbf62b24337b02a3dca82702960322eb557b53445cc72f6271a56ec1f5e291c01a3ff00c466b78f57e4d6cbcfb1582ffa72beaff9ca4d5bd704d300a6fc7517cc2d601c4112bf83a3fe51d7ef347317c772ba1c0af04bc040619c15aa507d769b862fc1e5020b114997f8a29b69b999782b075f44411181a1a41920b2b1dbe01479bbd394ee7cfb542ccff40506499f8b72a16efb842e68918a5bb9503604aebbbf6c4aa83e0f88a2c2633b3405e30b8272bbb7dfe8d179bca581ab260c2e5935596fb74f8cacad212b536d5348de38847213c7db4ea8c8ffb15484252ab0339467ff0d1f3df88f807d64dab1506430b45847bf044aa4af54c6e859316939b2fc9612350e47794c7479195d758857636572afd1f776d8f870ef9d67f79861470e220e21482607411eb94d18b7c249f5e0722cfd617a5d300bf8589bee758bd2d3197d53b36568ae5cf859dfe01e25768130d168fa7f314f9698e36befe82d6a490c71c9e4a802883b0f8158a022c09691728b2f1dd6bec6ef4336c410d4740d636b69b4bc36d17f93b77a628ee30510d572def5992afe262855b85f8d9dbf31b9fa28072a261f7170867b9f23d54521eb72825906e022fa6849374441f62131020fcf9614e447eb403c497cfecf8145ef72d698d39dce1bd63209e502b8f587e6c4e894993ee9dd331a907e289c359beb4bdfbdd14699203e58a64710c54d329665ac09edb0be52dc1d6ca0cac442e73733e39e1ce7237abea0698aad06f28a15b83b30b4c8c052d44fdb85694f0cff562766186df10f6fc0fa7df1dbc5cc2a7a4a3363cba81beca3c579d5151a2b82517258c6b2fca50851b176529f76ab4f59c1ac6d0487a2a52c4389a73c2b54bdee1a4cd52bc688252a2468344cb2f28cb1a910bed27a3dcba31f3f34aaeaab944033829d2211a6b255774b84f4c910a2487aa0675c3ba8680328bee9707564fa8c729cf124bbb268a21985910eba8af2fa003443c3668b91812158818a63a3a6a572254b846881a49cdf1f1e9b16288b73b69d8750ac59857a84dd3845da18f5047256dd0b9250d1cf2343d20ab21f9c4a428df7c37a8eb625cdc648632de0343a2a183d8caf93e559b61177481fb5e3468c02d284ea5bc8a67110107c28f518d5729a2194d25214de42361262ca6e205348b7f7a7ec44ee6f6d78555fa0a983b514bc2a9fab524c956a90b6aed9e422be12ed28df9ba41101a049fd972077285672a756dd3237a7b41e84b7776c0af30166a64354f8ba029a138585cefdb67dbe3c6af72d70e7517402c61cd809c396a9c8ead94c2700178acb195b62290c50a8720fd4636431770f515271df19f87c4f9d1ccd65cd913c535d4990eb519e825b72fd6d791eb9ee9f6d936a35b2fc5d98c97179fe8bdf1ad8bafaab4ce7edd559721d728244f7f4d287395e7b7e57f9b6e65ca3cd63d3b90dc29d7629c9b247d9203ac0b9a6b47f8564ae82ff26b8135cf239f171adb5b2cc54dbbcc685dfef1e729d6dc8ba082e1b6693dca3b6de9783331963bcc27ae689c8d012b57770d796724c100c3d7ec87ea05049bd6032bfbcf8b083a1b52cd0e018161df84bfd7afd728bc5ce820bc4cdb41972def661f47d1f0ec3041ef4908fa16c6df7172e5f8d7278fc3a6482d53ec890dee6fc40c14e918533304794fe23329853bb5284343072d2d554d1d79fc2a4f787a6902feaac57a6755f64d7120e01b709eaca10bb2e7207f498b6dd6b2331e85ca1ef1f1c35a17bd267b912548c1dfc5148509ca70d17892f9699e011d71a3b04567fead4f1936985452c35f93a9038cd52f2553b697256ff77eab8931e00ad4ebcc28518e475f95b0efd0053a3c9fe8db2036da499120a86221e450ac120c8c3a8b70c3358e5865924ff1a614a62fd942f4a8e9779726348829e2b12e3b84fb43bc6e84ed14a9dba6e0db1e718a2ee37466214c71b72d507677e3cc34f71b8227d78094d39a521165684e3834761447637f411cb5a72db00478f15052ebd3debf958bf5a14ee23fed09ccd3c72051405671a65744f7246bc135b7f17a86d8e276edd410c34523944c2f7bfd1cbf2bf59d38ecf2dac5006772d2b81c8d8bd1815bc8273896c0f05c8cb6581107d69490cf3ef699bea726c114162fded2e2f485b686ecff8a7141b1c611be85f4515c60f88d0283799499d4a5c46d3636c24135f46266ad81d5c59d6f97d28b382f1563cc4e1c39ba572444f145162c918f4292ef9bbaa998019298bd08b22c5b4b8bfcd88776ee96618f3a5ab8dcbaa7b61874e9ad79643b7d2d15ed2e0469049772f345ba97b59b9729c5213c050e4436f620f4131457895f41933230b842d0bcef29c0553a25c17724b0edaae2b1be1621a7914558bed11ae8586cf9ef4ad6a2b00cd6d1942753f3f70a64e55963b332130a3cd2a5ff24ce6a8a05b68304ce6d267cf2cb460dab3055a2934310147da1a8c0849ee089a2db31dc984b4eadacaa0989d645beb3bff3236727673df28349118bc2548e5efd2cd55ce89ca563cdc8613c8026ce2e62e7284ce769b9b19b2460369bee5e4ffdbfd842240d303b63cac453cac403704bd72e9182f808fc958d9d97a42d1b4edb7520242be436b8d61e80100df307b666f241bc457825b1cfc61309bc2214726749ef897b9c6a854781ee2392597b210c472058d4c26895cb4e1e20384c7a6a39b7f7db3f177861ddb8c55a93f8a20b9a1728808b51d8a1b1deb8fedd9e502b618469cffa14efc6972ee34d287e1df228972672c51697fdf68a09795e4338a11e9cd7540e48c29231795e0f2ef08a2b4e072c311f614369fefb207b3162f00c8ca75e08628f49d4b1ea71a46815496b00c7223431a08b5d5488ad5238ec2d11f19812c8086c4ba11e7fc56f5e8dd892fc74821c0c3a79c7a65b155d7d2ee19d8c8e0deccee56985299bcf7cff62a78c2a77259bf54ade65b0c0dd8b3f0c7dff0c0d7b4aa9514f49171232a941242ea99ab10416e01834a5d220c56a3838558d3140f8520ce6bae5e27f6083d843c71cf3172d7601cf27686746d18859c28fcc99d6157af9e4cb90c7a9c95ef5a5266831e722bd9b3273aa344a179eb3d530927afa632df39aff981164a520edfe4eacc6d45ad36a4b3ebb824566b8db5cdef8edca02d944ab5eb4a29c2b4977c71bb6a356878221653090b66ea5a0c68c42a90ff5c3d7cf79f643c9f95a5435e3e9b4e3345f8f7e40705a7656f2657748817132d710923c415d6f771bb3d46f047086ce530545e946d580fa465d6f76d067093ccfb0b68c1e2b9731f422d7fadabd7270e72496e9c1a8b23cd767828310a4b2dab4cb062bca10d1a4a59ada63f743bf40172b3cc7402982b6673b59a76671b7b538a04436a2bbdf310f3f05515b792b71855fe7cce57cb0a8cb546f7a0c529379aa837e487242b78cf7091e9847ad77ef56c69bec9d30617bac97d535474294902a5f99bbe0ac05158ba390ad153c7746e72d92596f6fcf0c8827cdfa943e73d749720fd2c2a46b7f1722c81237f906a0656a52b2c2704940b50450e84e79af4e4e72fd0ec9ab8a716322998a35b52642972f8f072849f420cb89e8c364ba19649fe88c0c6deec3508bef25c39cd45ecbc72d814e641d8d2887f8644fab96a6d68b23a4a8e1a6a1bfaea93d54c9244f33e7278a6e881f6fc3f416f22ab81ec5b4be7101cd6f322beb5e1a6b59ecfdd482472013506d444b6df7d28aeea203b8d7a3a6f2c0e1f99ac60cbec139a4642875172ca2527326f6d1f276b1d8b2782e46cee52d12d4cf2ee9229dbcd0e0449e793327513a73bf15e9c07c327408f85e7de1a92713b9c6b30616d74052aac5f986e72dee0cb22f824e61e2044c21b98ef5a7b16bd71ec2f85d5c1768f83d2f41ea5723f22e769244adfa964e71e3f370f6375712201c6542d3383277eef4d66d90c721fd914c047d0b64eb3a53ef070d54e1fff59e7c5a7656eee0018684d306caa72b41cf2e7d47b6811b594bd58bbb5acf2e4e1f1f205294a239f3fb51c493acc17c1c5b257edf9f1f9a4beb01efdaa846b1066b79a0001ea05492f77f23839ff7280585cf9200b025b470f576fa4798ed7b4c9608f2c7aa69de1ccb4cc7c9a027202e7528ecdeb874337da25fb433822495091a753e2a857c8956377045452ae72789969718758688a1400f9c9a1af0acc7dd6e2dd08e38bad1b853b51884b305a428e1f27cbd496d161b5eeaebbbaeae421d5e7c6d53caf4c4c04dcbd8b9486723a6e82f391100ce1331d25a8181bf1667b84c71fe236afbfe947aaf4c79c18016eeae166cd7c32aff14813a3b43f110c3e537f1ae55bb085aca259570e756e72fc02ac99d36da0d625d088816b943f413c1df96449d88b0e14fb98d258d8545028ae6e25500ff32847c956680703b151c508c5741ee764fbc3ab00cf6af63f482cf62fb0c70509401a780f87e7b5614d800e7265689097b55dc5daaf202c3972c7d7c37958f4d250f6f23378a01923a4450501cbb504f68e2cc9f8ab28d3ab4ef7a28dce75164d5a44c993e1a36631f0736c8e95120f5a5e3ba4d7a59ae73d566269d5cd0be9dd5502dab969bf4bde7a6bdbfbbf8c41aa518392541aea83e70a037c8b6bc3f2517d880c0492c21e7bc6955646d4fcc4e4f8164c1b012dabe05b97fcdd54e1161cdf143cfdc21b156e7750d294de3df247aed37a99f2b42bca72a548d571a1033417620e600e637d96b6320ef9c5b7891374545333ea3cb4482bfa78197f91950f3c2831ecc5bffb4590e140b09278f9c7e2860aec3b6db39872f18baa7c377b7eddae543615ebcf0ff4d66f3175ac02b29e1f030ab72c6fbe1e1ebf38f0ae507653c6b1c7a754903de3bb02e947c9933ce7b61f7a144d20bd724ccab646feff89285af414468eaf781f021a4ccc7cfce385fdd414a309361a7218f4d5ce22028487fa5fe13bbbf5c51222913a831eb2a1ad8134a2590963f000da5210038c94a398c63815200df28bb301a25b7ce90e1e303c0f770feabb457243823788ead6c9237033a935943f13d0b17ca9e684552198e52bee0f58a1626ce2f78cfe60495534553372642b5e7f3f3e44e4b1f010607c31bb2bbfc77c11721fe48186ad8db19b67445e3e14c0158c5446ebbadde9fadea51056f229a66d729aa9902b0b68bb672d143dbee2e5f85135762119c6e8511c7b3a9c8631773f72ce3d60842c4ce950e74a15eb35faf1577d59b8d48ac15eaaaf81c8d92c7f237223e2736b15d85d442bfa7029051981f998127e0dae59f209d6938482d5d96138d7d95a7c1958f6a99748ef9b4f928f03448b6387fcb470a807dda9f583bfa6726805c0ee98e6e905b2e6c9231b79028760932f13d8edd45c3404eaadb7c4a770fc8c5adcff0b658a4934f67d6fae30252a92d8a6cf2dedbe66ee819ec1df777211dadec64edf891772f80680ce6701b330c3891b45076a6cdcea7f4c18914872457241b36f4b16ca0af11ba11b62483ce588c72999ca9bd45084aab437412d72f630c0fc94bc0d3ccb4009a0461e01e3084a34c0dfa9f0c10c9d5249bac3bc01bae979872aeb2b42ae74dada2d8387e53cfb3123111fc90590efa7dfba2b6e724e6e5bf9f0692ba4ae02e0d941316e0124fc7d2f65fe94c5d235c57233ec1e2acb73a6cc2a00a51a7f924f590856b40536fccc08a4dd3b634afdb0eda09511728e455f55ad89b6012ce3f52335f6b539feb1099cb24b91bc75547514145aab72b6dea2e6ceebe9c3159d95fc33f4eff9b8f275c44f3f838597dac526c6434671339ce0a3e0ab658ab7dc429e458194707518c963feee50d3c398606772a64a722d83e873ab0397d6b4a3b906f51fc3c72d7edec71fbe3af97b84233e8fc64e726ba7f07fcdfd1e336a8644405634d24bef91f63b1c62dbd30c24e1e44bd51e72a494e221ddf94113800f1c4d97f45dae1301628fbad889c3f9d4f167e081b320e11223583e2d410adf253fafd42a8666dadd6fbfcea443af725d2121d9573d72c5820205e013660ef10fce8c1f028ae02e3cf2b87a2bb3025265ad8d90839a1cdc442d9786af1337680eae73ef144ed8a8d73157ea626a418766a6c4e1f8e772198e4118a6a0b0af0d95d9be835d28f4983d451433130e8a4cdf566fa4eb99277e6c4cda3d0ffed9d1d20bb4e087bc00bbc93fa2c0ebcf1cfb655cf9f0e6c24b8b8fd0a414a736c958b073d7f67c31cf55cad2c398b454703f541718ec141137bd77e3e7099fa88610ff92b763f38149ba98db25cf81e734fad6e10dca47ec728311a1f0cfa7e54ea01dd8fe7f3385177bd41610a95e167bbaf5e48097059c720929ff97feb09e913ec62b95dd43f01c10d85ecf08d5fa0fb9235dbc806cc872f8fa28fd0e353e3e40ca3c52900b060a7bcaeee50dc5f4c2b07c872590900c019240a3c264dbb0cf158fb86f5ba2d0e78202e05fa4e4884a8e4c937cebb27615d5da5181f9f14c9b24550313a5b640487e10f39637dd0c19de238afd5b4de335d61bea2b18577960e0a3b6f39236c3d5ef56c484fb06597432ea0bfd60d6e964141c3bbea372a78e66fc445ccd7677b5c52158dc52f07e62f54d66e832f66772679b7ee620df3f4d79c9e0f4c7e1c8319894a2e55a21db31e1d261b9b5435502cfbf1a0da965894c042450a8ba2a84c21334084e92d111809370c35c0ad6eb722bfdd688e3d520cdb85446f63436e0961977eab9fe963650638d6ac1fd830272bcac4c35c2e7045512115fc5ddfad45685ff315757fc06c9a701c4626acbe51e571302ea080745379fd7dec2089d644c293e02cf78ff4be08b33359d54daf100f6bb84a9beb58bb99e0e7c8ea4007acf042699514b03436d92ade50ec2a63e7210c4cfaa653121105d3b640afbbe612ab4eda944df5025a98c059925408a2052bfd73a15aef309d89a5abcba1c772e2875d67db7cf4ea179ea06a55a4b907e634d1391ac0166b27eeceecd2a4ec4beb35a3a1acc34ef660e03415a12f65b77724fcc68fdd52d04b1cf6f0c32aadbac2585c1a55a8b0879a6dc0a56400ea58c72cdda111dadb89016fec88d29957fb638204bbc1ce389274b368c57c2e0330d72b593e78329ac0cccd1496593a59b5a34152fb63383ece64c83d7cf15eb772f7206fcc29c78730b14a058b58c4ae882ce62b702d820f3cd1cc8ae6d97cda8466c1300716ed0458caf7d27f452c5f0be9ddf5084d364cdfbe6367fe1a6d21b771335cd956b7162b6a3ac3d4cc3cc53797f32695be87e523934b96b431b37a716316514a8e90eeb39f5a74a00e84aa705afe43c7d95ac3cd7d885cbce942c90dd7264086a3d4fe5acfd95920658255d6f4a270397176e4652505584e71200a1b1723953a21ebbf6368465d2d55e285ff112b782e62ae9d0bc26982031243c69d737a4b8de5279c39670fce24c22ff109e954cdfc3012a167702adfab446d902c5292351f0dff408ccd5fb4e46aa4e999f55fbc3958c6fdb9a0371852614aee42f38b2ef72ee4a76c9af54946a2a53ea991eaa24b0c30c0502aa25a069eed5eab97261810164724dadced998787923788d1665a2344120d649e3c2f615e197d1eb6d256bbac834e630bf4ec04197738a56221ec2801c2903386a34448209665ec872ce4684ad67006d42cc0e4ac560c2ed7022545e8222ac4c202614805303cbac7254657b571b7c7945ebede41b3d5729e82b448d4355ae99ebcca64be03bf4d7724010ad4c0adf2d330012c9177150a407dfb42b723003e83e083e0edadc8c8172696e62dab93675ea71547aa0e299040a9aff52c8f2fddbe98f1f55a079d1df727219e21e4b8675648776fe889250195f628816f2a691288894ba36444decfe729a9c27c900ca05d7997a2c5d20a647081cf83bf059a45fdb34f17a2ae423b572641334432d87b656e59c0160b7a3217c2debeb98f5565d5c82fe58eca3c8ae50c566cad5d9edba5c18cf8480dfb662845ce9be755b6a00be747d58000db51e72353d75f62b43b289390e557420a696ab95c4b4021bdbb1e5e5bbcbef95f4df0c5bb448a5de12a4ef6625a53865f18bc7321ee3d10e622a0fc8c6b6113316063142f5f517865ab84d7b007913ccc68ab53270cdf877a3284c9735da0b9c679b727c256206214d031a732e2a477aab2ebcfb8918a14d90687e8ace7c00ae2d9b5789bb9831156277daea20475d4d70e5124caab67207599afcee4000b925d4d1725a46abb32a4da521f70156858984029584a3264f2c5c34d671f0c9fc2b05ce721e6c32189e7d4903982cc5350dbf6d6d91fc60a9edd9d973480c45c982c0c241dbb88a9360f37dfa875be91fc9c804670688a0e09a5c4d8d5d61c64932c11172aecdb4d2205e58448fabc506c6f7837c85e39463198f4867c4949af0d6a4977287364c9e1b65e140b6c0548d811cbcb699966d4ed81a1865e21ea8b3ba814e4942aa229cba2466ac243974db2779e6cf2f10dfd96a6acd121ac47cadec6530728729758245b8648d32346d8f276aec2ed810d0ac203bd8a5ec303aa927262a722058f921710f77d2729f46f6ce37c3b7fafd2ef3f3e8d00b23339fee31b1e372565287ecdf183fdb2468fd5f3254a81a6b6e82bebe13f020d904848f9d7192049d85e163f0e9b7fe5796479dfd0f3381d4a263f49b99e05d9ede5eb1ef85e659e2eb7e9190bf9d9771cac68567fdd270d15942019a2ca9fa5b035cb57a102c5338ae1a97488c195a493607a4f0af51ff59b13ff0c71d83f156ba0893b7767e69a51a919ed5ffa2ec4e178eaed98e1860af078ec24ca4931ce28b5bad1abafe72f7c770c67a6275dface0283c0941b90a469d8963a414f9f1445021bffda81172202ad6ab4729f60cc8b07ab4503366943e7a05181bee477965a74fdea42ba87202b961fe55e8040d6072ddf7ca4352a46fdf4c39802c7b20ee1612499cdb626d9604bca7a1a8c697ffad8431a0d41bb4b8ad29713be0b244129693c0f582fa142cb2cf32a73030b52a21e321c759ae77afb29eab9a6087364084906d10bb4953fa124d865854c745e6fc30f4c6cca2d4264937467a9593f563286d15250eea1f9e21b649de6200cf472bbdf14d259d76fb7122ee8656a99a6d686c1669eabf17fbac5f7f2665ddcc0d5ce02e573aa54b578130adfa143b32d63b1fe33e2e0a72b49ad84c6594b3251b2613b2d8e37f62be1090da9dfe20a9f14bf56f7ea9a672e5ef70707142bfeef815a582b4f264b221eba33bdbc60ede78c8c8b65d03304156af5f9fc31982bc9ec5eeecc958b4983d5bda11e2888d4a3b57d14d5fa5c17297ef56f3425290e8ea5b6fd74a10e3417a221430c8aa737e7e2abc07ee5d28727fbc0f36619543bb47c6059efd3d86ccc619aa7bd5ab730753b59801b450f0557a36e562267cb5f8ef6de27df4ba696c9517fbb661f5342548b9d1c279ddc7720fa19c624b3c687a44513749e7bf73f0d7e543ddfb94410c1214ce3b5f445f7207c9727943d35e098f4ea72e887dc97bfb6a24d168704e9d94276c1abd67ae6a71cdca86ab3bbae12c6a1d7a654e239a4153c52dd5944f627328546856c3ad35c4fffeccefb2192b83c3874eeae390243fff894927f8a3536aa9af6a3e88d80cd90d124d9a902bbc6c4ee5e44572020683c93b80052cca9c797d45f0f10f2a72ac95e639f18e2fdf8153b98bcbc12ebea25b5d3079f4c3445f8cf9b46b34b738fa4ab7942a469feb9351b6a05951eb6239c59f99e1e367fc4d89eef6f6ef83723fe96b5e18b5b1dd745bd6219b6016840f6eeab463536b3957f6e8fa611584478b3913d5a9472f6c58321cfb98ce9aff30ea608cfc82986928bde979b2189d03517a9abca35ef0134f3b49bb96a2de960360d83f4026221e0baa8eee4d87742d58ba47e07938d6e430fd4636756d60917358bc90f563aac1cc4dadad543a3c1944bb013b2d25a2b593df8cf708d3cc85dc33f8d89ad59d5ffd26854d73d7526c700085b05d54b9fc305bd7eaa5ad5ec23d7671755041d5c6fd7351e0b1139f720a706e3c7586f7fb6ce7dfe7d2204727fc40b39693d730c9144dfdc77757a072872538dd0140eee2358f0452db2f69e11cc663209d1d5a3f39c8fbd45d2a9a726492b03aa98a274855d82cc7c7018efe983aa04153ddf0e44bef9f9cc4844166d5c99af9b2fd2fcdf06d99a6d4514dc53f78abe217f596355d8af99323fc3d7202f5af13bde90b8616420de67700a1e9c4811e2b7afccbb8d8f211d7544af97245131a5d5f9bfd06d1ab8723795a1b98bb39c4d9513edfbcf6cb5d047e12da70897be9ee069703d21a45b19238ac4a2b967fc822a403a0f2dcc697d726905972f40834036e984ba5cfb7caf9846842f5e64b39b78a430bfba0a5b8d7fcbf736228f36f01ff5f3e8346efb6bdfd0e861d575e25c0f5730647d96b75f5962a407223dbe70590bca05ee3daef7a0eafdff0c5952e374e90333896220f0f537cb2664ba03b16b5f1a996c298b1d02fb4d6501b4487016b5370347d6c0ac78e1c0942a1b218d413ab0346c13ba00abd1fb2484bff62252f3ec1a5c94eb04ce19d1a55a865dbe7c81f031e1f240dde9fa6149d59ba72d061e884791d1f56d3532ee347d16d9d2e78585ce666c14e39a720b1df7b306157585446b7d896d6ea5a4592384ca5aa149c844958a199118722a2a242ca6af98d855bef4237e9a67f58ed3d198f6bbcde8155e5ee20caaf763a000ea63bd318f2ba91ec152f3f678f13b52c7298672f63045a563d7d2be2974f8f2c8db678f1f6bece10e4d32597f492829e5f4d47d1f0b21320aef0df54fb50a0d4a76f4b6c160dbe7642d2b8ecbcfdb96772333db1e8e262157de06ce3f934e62cb06131f84efa97bdd4e595e2c8b08f3568b56c2a2f8bf1d3f68d4eabc995cc63115ec419325ae780debf4b6bc90559177209f89e93c07f350e98fcbb107d99ce7e2be61d4b53a1998af3000290a9096229ca6bd250b5bb381f58205cc36e942a125d200553b9fc7170bad18691777e1c155e953420ccb1203de2e4cdd37c3f070ad09dc3d6d5de785615abde04123b7d72b866123373d7e4b037ca73c36480e740c423301ce9e46055b286004913f3cc72e6c7f0386af6ce56f7a863a9df5680141e2e625e0ee1d714f1ca9fdcc643c2720299c7569a3eca8756f93f062daaf59a54cec2e654ec5e2d6d14a4ade7501c720ca2614baa081f5069c6808e73f241ee328da21508fb5474ad4890944ab5ff72caad9d86eb7fa439efccad98d748e036174bceaf043f261d6f35557262cbc46f5c988b99c3776fb1264cebe4c91854b69ffe3fb2236be814f1e5cbe7a94ee972a4da7c4d160db90ebae8db9c3264292ced46b1c4d2c9ad8e3d57cc3ccc018169bfd212c06b262b7d67d14076dd6ba93660a76760707e5712277ddbad38abea729cba778d998e6368fff7371fe22cb75c2731dd9e0e357bfa4d603498a149c95b00a1ddeb7f3828d0e2856605b41ce75f2879454066a918fc2235f390d34e6a10597d5d84d63e34b9ab9a189fdeed7cd017021916ccf4999bb2b3a4553bc6ee72e3b871ac9d9d1611f8b7b745744c4e37c51a2b1b9e06feefbb91b2f8303c76728018b5d64cf2c014714109692c8c3227ec370ee79482ddf83487cd3be2aece647dfd18e8a1649a6854f26d6100226365e865c8039457ec13760dad41dedfc672b9b8d860aff3441ef29adba155ffdbfa42856cb2b5e32b229bcd13ca7e5a281b930ab51049132e79c0ee159b398ca0812183240c7a7bedd1bf11a302fa627172a9b2c2783158a1d197639c6a7c2815f85b99bf42865b44e129c5d5ad15b5c172e461d65d65970f385df95a84e4de82aedafd069c87ee2f76038f34b2d7720a4aa0c4e6c480ddf07a7538010fd16e4312f0866b554cd3ad8b63549ab728c6853008915a0718b061b41874ffdc425e353540bb793f9ad1b2925f3edfb940133072a805703121645c4f9b019c2b59eb09c8cef9cd8bfa7c8b4e2741c2a09117e763e67ce17fa82f89c1b3b0c46a416921f831e370f802b7ea59872408fef85da1277865610e15f2134f88f235305272cc6184e4692503aefe905ef564f1a004034db6e09c24c13146dacb7c4f5b187a7466d7d3cfdda55e3d42acb689f73c4683097b684dfd5fa7f99ee5da6b91586feb11a9856fb4db2aa5c3807e699fce67ff295e79d45def192c239a289b5bcfdd13324ef36ab716dfe6f73977b4e4f58d9972ff04f38c1d404cc6d0365cc2f91dae47d309da919455cef57e7cad972db3f76651322841b9c76bbf81534d9bb7c0e9ae620bbd21a6ba57b79b466e089a5d5f72bf178bd1fb9cd74481a2e0e52eaf7b9d84e2e6a2a9b61a3e51cf6f7efdedbb6601883654b78d8e9774f78fe8fe144e8e47d721572e196afbcacf3bd764a34172dcfd05dd645d6c755a48d580252309a5c92c2335db8006181d2b48eef33bf0727beca54cf75c50e711becff4abd01731e5304b7bde0252dac57c8329afb0927261260782213a8217ede5e23721a12143497b8d29eb1d3e9b5e9d256b64bebf7241038df219edb21c3150356022c1621e5b96a9759cdb27dafe19443fc60d6b725a01b0710d334bb01aa75445f7061d37c77d9baec45b37f594113e4077a6f972655f5b93fe4a1ffe1f35e847013dc59b2ddf7cda041240c9f7859e0c0d48a33413c60c4bc94b944d6023ce4574c71b5b4d6a61a0f656f2ee69ed8e091d04c621955e1e09300a47e57dcf1f02f99fdc2bd174757dfd9eb73f3e40e1efdee58672c37cd5e60753006d2bbc8dcf7e552c0d2a86e312dc5898cfb919512c9ff72857143cd0f0a19f535e5be5254486677ac7e53ec8dd5a30f8011f5dfb6e63024754e227fe8e235f0c3014345ff9f2764c59e8e186de186e404c58473b6be908ef72cb86ad92e12d3042755af6909fa2f99bca4e0f80c715ab43a5ade0a435a6861d1ea4078c400097a49348f8df9dda288413c25e8459bd95acec9868d1e7626f72ee5720ef4e684f639a20981e1ff279285ccf926f45dd294b1eae574d0f9562721424aa1bfbb052879ec1b2d2faee0c1b43718fca822e2da4d294920bd21846443cb019329194f8a1665d5c05bbd597f0b915ce7dc03f4567443cd77147161b66e60faf6083076e175209cea3d8b01b6152cf732a72031a0c8bf431d3238f5c729fa29700cb97754b60b6205ca3fbb918c5feea80f805896de688a1d2ef196672bb99155f335ce7875dacaf75d526babe4231aca72285d81d37558a38589dbb459dac242b9c5e03fefbb016083fed6b6f3f0fb73a580b5e9f836d1b661a0127721c1c8aed24a37373121458c47a40bc38916f1daf6d2ffb1c70c9aea174dfc8162a34d74761b1c2d7eeec068059cfb30be23c318c5a60c398c1f09e13f32c69728188d044d30471ce2cc268a4820a572d97040f32b20b8d4e15addb7f840f6372900684aefe0a08139a73092b17c75477db55a4a401b3ee57da0bf9c019c27e29e95c4ed95eeadf59cb47f8010070e568d504d0b0c88db28afc02a9af478320721f9e665e368ab326dfd63aa45dbadd27c62306ffbd15eed35de1473a485a4f72a83aca754feb4b8ee7e92288774ab55d8b9a2130a1c3af826b99d3eee313655ec06093e8fc126f8979f1d6d26734b990d7fb412d582d5fe358da5fe8def70972a898044083a093f97609ecd2fe9f5dd060399f5fbacf4c0c1c2ac929ee8d7f3672c07e41124c4b1c0299b91d2e0f95fc7584e050a4efa687d9699350245c647271e516570f45895afa656aae20669d97899d99cf2480dbfbae4373d21904005107e59edda8af50f8999412d8d216c40ebf896dae3f146d9a5fa525465516fb721be547c030a7d1b1e5095a3773680dc758df2fec74c3958bb1cab7c93c57682331fd6e188a77f5affcb3ee1809f99e06001548aa52601791e03c9b3b7eefb167aeb8e3ba306c53f1b5b50f080da42a158d31c818ab68c0a1ba8dfda42d16265fb6965c15785e85c441c3bbc7490d2e169c44481d70286fb14437c9ee5e82cb1646ee875311039d078824afd905c24b0a314b28cbad2bb4ef6df07280ae7ae5053f26ae5e598be234c322f6d4d5caca6c9be78915bb6d7ab77bfa3624129cc572aaa53e1695d0c12ade201215d82bdcd2dc70f3d4b1a61e0a27cbba85071e9c047c2107f1c450ffd9fedbeacddb81e28ccc2d7cf0a373a4e35719dc72ffd5c469b1fd80f6d1b6ca87d03198d7687c49e98f08f113dfc223cee329b95d14e9c072328a2f382a07fe59c66bce70336e5317fce76b060b7a56d7874542475fc80672726f34cf430a4c31055dda5f174dbde1de986b1a3a533a41221f1882b2022e58d78cb720ca602d67808cd9d381950af55cb88fcd52ab56d8671ce297644b3c72639ff017ee69c855e9029d2f28f2b7f1584f1368e9266bf881abd58498229e72647809b51de850c80340b33630caab1743f4aa012eca49e5eba088b209f7b743988a862a91a0eb748fce3527dcf7ebd71b82763129d9e24f8c55d4c609ca7a242ce1f5cb8748b082563ed6a59517ea96d879c89a2776b2b755ce03b7be417c63a53ad48e4455c0d8da45c0401a75a759334d3fe95e474efbb21bb610b91835725d70d6788d102991b1836988c0b23e1de907dc968abb5b1ca22671be4ca19d72ba757017a4aed9067f8ee25c19375631b27d19f3fb4807f6a4f3c249d647907206485a3ac36e711e38be6b85f09f60b248df571ff8f1fe6d071d43d1098cf07266bb829bd0308ff7dca50883da8ce0f8c26d4767b554dc0c9da7da9447192c72d2937456a1ebc356a33979024620f9c49e9cbaf6eb689602725f462500d62672768575de9dc88ccf873fda4a5407e699d819fe985dd0bd65d270edc22a6eac729d58764a6d4e86d02fafbb5e267c64a79a30232098eed7df7982afa9640c12725db5055be513292a13caf8d93e264f90940bd81a8d9242164cee4bf20cc68372d45536363d3aaaaff9604b6942291f1d82cc5a144b8270b88cf8c1f4072cd80c99087291f946732f7c51af70f8465e1b837667984507feeff9248faedd3c687249505803d1f99d2d421bb429010bfa626ab9dd983d1cbc5dfd39a3e7ba7860726ee459c8523fd2991fbc4b41645524902662e134d7b95b6db49c4f2d4e6b1b3094399b3bfa4403d72630bffb3b7c2cc2fe5aa81cf1b0c33c84084427b5812572b1f9f6ae662aedd62f413252aa56e0cf661ec2481cee715dd37906bbbe7316724bd21fce7a505983f95849117b0719a7eb2fed42ccbe3a94849f861432c44672dcd5df4947c3ab2345016ac4045d3f19de42777dd20d3c5ee14225cc29e376724f4f198230ffb3894bf7cf1d19496f9f3d96609ddc09c2d455db2c2630798a723197264eef949de84a376eb744bb5ce749c654c7daa9ad9f27bdf6278b31e2710465c7eb80363fa7cc9906aa9f95f88c1c559b07c4d44e8a69d74e3ff3996b34090d56dd0dace82319e72dfc8622a1346a0a01e251e2d322fbc791522f72bc2081a877287a9c293535ada52915668b91aae9637f141e10ac086ad340cfa31f0b7462e5be7a70d1747610232324a8bd6e3f91db9ca60e51f0a5f29a6123cd2945b61af34796dc8f5b903880c12e00da7bbed2ba1abce4a1218c3f9952a3dfb572f451dba141eba5ace1cb79a18546c68df42dfdf5a7d0a798afd5c94217944520139c092e4be7e029e0cc8a26b3038414a0d6c01f138f5337624d9280f7104b72063c7464d7a4f4cee5317d63c8bed9c16df68d4895e10fe1d3552eb5c878ee33b3b43a2f48657e977e7dc09a6b3206554a74c4f2cdae303cf724db47bd35d92a244916fce6fc3ce095277b27e8e892b973d801ddebad8f6e3c0f7160e0f234687e313c566bd00b6e837aa694ab9c42d8c077b9ba9ef054fced278f1a514f1d278e818702e244e6f7729a2a85a80012a7bade2bb71ba37989ed318902ef15a0596a60dab4019df06b6c13f315999e445230641f8fe2602e78aba9278c5274fe5dc363d4c270c27cd6cf12a4f3523e70011494e25d382d74b8d2842936ffe1b4722ce4ba70df1f94257f20e679129ee6db55ddd9f0da6d0d47ed0ea0c3a74349414c89d589b16559bbbd9e154147042b2c43afe2ba8bc31931ce423f9caaae3d72c5481d69e339b1616ea63a6a877ad06ca42569485874e20339571f2902e7856ce59534a64700da21ba46a2baff6435152ee3e1cdeff0f5bb975b1095bfc51261985108be98d2934e7b1532c130d3a4a8769f63cc07542cf7feeb6ce735908a6c522c9124b8c017472d17608a863fdf820c771f7c4f023effbbbe638f1ade8f72b189a183471aada9ee621b661fb0545b42a607b0f37cefe1763321fd828c1372c010cc093659d3db3b911f105f95c29a6a5ef408593f58fecd640570d6a1b67235a914f95c1c0b7d968ea16f8007dbe048531b2efd9ff26ba3e80cf4e95357724ee5fbdbdf5818d6dcb1cdca1faa94fd50703c7da65b9efacb27fad876a6737294f2cc8134f2a38a52ef91d377e9dbf1e87fcc50dad63d38b3d1dc09c816e572e000ad37da975574dccf5f2265bba8bad3043471354e3a222a07113c6df1623439fa7822be79adc305cc615efb6a940b10a72365954d8472a143b4df4392301b34961f46e3091545e5b39e34cec5f99146fa8fabb9c3634ac422bc13c1bc87726da4f0f864e6714e1707197daaa8bde899f2697002732b2caf543c820b485a72433f1619078ba41767ee0eecfb2ca7b4f38e7f5c2b59590201fbe799a0f0bb72f8c76f1928f035b3d88f4b9c0fa06fb590e806462630b99135fbf47d592b3c7202533efdd05f40c60d221c73807152c5580098671886c3b6ce5e5ff79b87ee13afa3a3ed4fea189d141e5cc318ad280a3cbec59dd5a9c9c18d382e92ae57bd46909a1da98025135c23b0eca32ed33d6458ef826455e4068af572a9d4b708bb72b5b0660218d936053146904be183837ea34f23570f7c7a4b936dc165c54f44729962ae4fda87ad7bec1ba3b7f965b8c3c86d35033ccf1714f04ada2268f265607ae81f0210d0e4a1437dddf61e8b4dff237b2d1c76eca1b8ba9093c4d7f8557230ac1200ec4ffa702036058141a6c13f477a8a71c78343ec87ae47fc5265cc726d23f40c35c0c8e5123d6aa65dc75f1c844abb913a15cd983a9c7b5f1d5a1672db523c30a8998f152f8e6f5adfa9e7c0f7ae9e16ac3e9c4a0eaf996e3d797c7257d4b8ed5307627f803af175cbdbfbdcd077e955df9e19033cefaba3020e2b1285467f227547e031bb9c0b5478bc93abb4dd08473b182d5176219fee2229e911e85f40d8656b5e74d093e6494e603159a86946dc509c43ac5902c233d06a0172932d991aac17699ac34c43a78705c5450f8dbbc9ec80eeb2a00c6ac9308dcb7252be6a2a78a60a34f5717d6481df35a0c80ee042723f1dcd4069feee3870d65c4f1054453ecef0eb5c84e98bb6eeddc2a237b0ba190c828edb99d22778574541e83dadd2bb00d078d899e753c780a44f319c98ea5d5485cf0745e1113ea5b73386ca464893a1f67dbf4b6e03c45cfb2c05dd035efddebdf20a69e1133187d0192da99d1d7e016d071f74893c71a6fb2985e3c75209a96d02ad60ac179ef0f07211d3ea245097dab229368b2a260b41e84b634e1978a2950eedd2a6be43215672272819e84803979db6414bc05c48031442d7a48bee05b13fd25db8eedf29837281fa72b4a1161ddde00a9468d3fd543af01fb1eff37b7b35bf3015dd2c397a7211c3463b00875d5934e8d3840f8fd61fa31f2ae9bc4a462f5faf626154026661ef4afcab89567f951962d75b2ea6a94ff681484ac0f1132a974f7b5e89435c720c80d057bb833b44c12b8c0398ce5eaf7ada1700c048ef52369c3472a945906185a023bfeaf0691f40d42269610cd787afec9c9a24ca91f8b2e775817905fe72f5784fcf77612594534c6b9c6177e8aada87f2b657fa6d721d8cdcd204959872d249df6beea6c97320363897099a51b3c86ff7d551d6cd6d27169f12ed7acf5d4febf5c5a271d607b44492561a8a4d9d464ddf6d8edb506254352fe77e0b894c0d8264cef14a5779c1a621a9b670f4611178bedd3e0a2fd34292cc805158fc0d980ab4003ead5c484bce7326e7d3c54632f3f8d0584b58a9a3d112016332d92df70ac34a483e0812d4f0ea4d3c513f53872d8d5c55bba3549e3b713e4c39064af5fd619133cdd68dfe95cb7541119b39a54c9e40ed0ad954a36c9a70c6d82e4d97bbd30737758a4f834993c910c5727407aeea3b170f3bc8ef23905de91d2921c4d221ae343deac9dae8505c3276213deb0286c292a90d9ce0105fc3af17b4726f5f5da59ff21cd1637a787bcdebb71753a4be157e3ad6bf6c328e49e4d40b72ce10fba39dcae285b702f305c2d0f84014cb828b45d5464acfc9e70dbb0a922903fa4913ad6f9c75d925f22dc995aea1e2d2cba34cd6a7eb31186b6f0a96fa724e1ab272cd68e2ee96f5eb06c192992a494668310069afa80a2f18f572ca0772a6a0ea345954bc5d004ba7b3d7f9bd1ae111409a0395f51819f2738699d4d672c66e09b69f7360b483017e81614f6e14ef3b778c4141e0bbf43b1bbff76b88724d8861d673684b686cf337ca616b699f59f61b012340d38da2bf12041857da0678ebc06fdf287855ff439a1f107375e13a51f1ac22c756c9403aa4eb02d203726e953e8b58f960a9e4c0585318c8700b3b52f3d379d6edd05a8351e63c5cbe72da2188e4da1c52bd93591a40727ab6b16923d3102b7d08366679635c07cadb20112138ab3faf778ee0c2d2ba76c0ab894b9e699a5b84e18d1ddcbc494e4377724b83c344fade8c695fe058ace37f7e04233d6d08b79ec8c3c0e62f7f2ab6514e2a84f9a35e83b363cb259ef16066f7e86ffb84cdf1d30f42f7b1619d0f72c272b1e62fe9b21222d80fc29009b1fc295d7c3279d40a1939e6ae5000aff744d6297c07827dbfe556cde478ecba4fb75409ad279ef87b561d7ee6db6f6c9736e172fde507c24654597701b8240f1826e328ccf48e69a5f9e8cf4a03b9be23eaaf13086ea04709e1be362203bf0897fee4666baf377b7650a451cd2d6b23ea54a431d75aa7c22e4ec91fb9fd8807d33efc0bd5b5ad61e8c18e9d0ca8e098d18dfe7240feef59241cab402258d472f3a46fb4d971be7196c7d4da28020fbd5d675a64445b0a58fe7dd875fe33d5a98b386900845064ad5496443101ec3c1938fafb3a1417d796a07d28df49993bddece3af4c80cfba82cf6871fb306fc6f0517c5826be671df9ea2d4e2f9247f40897778df6269a05ec4648ab797f7fb670a336b272c5e4c77f157fda87d8c7b84d01522512a3d2af7f81b3bdfffb0e8e4faa55620b6bd811aebdcfefb9902354401f84e0beef9c841531b464a49ebaf82238a652402a247d18609520bd1b2a3b8886c8b4c536e0ce192d217df3714cecc1bf484c72217bb612db156c04c0fc314c118080bba28ff39efb95ec29efcd682a9f0b1972c735cb467acfd111452317da0d78f89dab7d63487204173e74edb0521dcf7672d9d304d3c188fa9b70871d37d800e7c5b8568893733d9427c4a68c1d0aeae872714feac36bf016dc29026590e593efc539a37a443925caac9bde0f6226c484006e850e1937cb7a55f1dc1cfa7406e195a63c68f057cda5b5da11c5abc9b738139ebef552f4747fbe046d6eccf5a7628bb41db4a85c8a51ca029df52df84c4b7284f9d2e6ac18b33130dd90167241b6b47518da6b38d2f925a58006f2c58155632165aad126e8c14a4cc5c8106633f52ab13f68ed000643b4f4b6a48f38a26b70f942caf251c36e6dd9f8225d6b2e84c9b37619390be86d08edcc8707837c8f72750fcc9133877bcd5d5950b0b18952dd017bab2f788b96dd1d2367c9a088c63fd96b733d2c3e9a96f8066174e47c6f3a125eeee7932d15fe5da13d33cd8f3972d3e672357b09e9b46e880250754d5791dc5c78960647f1275e10bd9b5de82262c18f966e57894858ac9f24e395872c089cd176bfe8aca1b1db4e8863cba47f611ffc738e0bb2df722c27454ddb37868ef86a6263a9480a16cfbe3b6d1294f760fe08f34d09025c1966d8b97cba46c56694b56cf58fc5d6469c32187b3d6c20123af7fc77b62fa2b8ab8ad712f02ff42970de613f9c6a1100d43bb52976371672258987724e7d1ec49799b3d4ff24c5db1a8ecd08d208134d075b10ffeefc9a19cd72c565571871fdc4f2a18873976816afe0ece3e0aa69fbc5a2e6f9f946bf0bc6cea6c5117566b374b8c69a8e6d5c7abfd99063acb26d61c151b88745e2172c0b3f1e83a826c1a0dbde7823d2dee3c3b17dbccb56f99ecc10aa55a6d984db3f5d2580b573c6ac52c0aeef5622b9c500cd9226c050f0d7e083439c447ef4200037e13f25c0b64fae858b58bd1cce44d7e903a2c7825b7851ead4f9d0afc29602d82fefd09c719059d89ebcc57f3cdd4499ccb3538e6fc148cd49aba0662aea52b02a365011fef98045265e42c7f81fc8515e3f1e4281175e04a6a1e3ddd1541737138e9ee914feb1135ec84bb24878c82bd11e264ce66b3638b16a54205a11729eecc7ef33133fc387ffc6e97732e7892a53dcf3bcf20f054416e049f0a29a724a146e2f7cfef556d9cca0f4962d493253fb034466e69cb071ca2fb10d21fd72a41506c76922f5983998144acf75d453c44cbaaa06e678f0b7876d4c17599f284644654cae17a1cf632a98ca9ccaf6ed5e9cdac0fe31bc6afb2b37c08e668c34fb0c984c56134dc9b5f7ac6db1588961c30e66073047350071a46624ea9ebf04a4d952b9b489a5637a89964a8adf209fc68339ca3c4e339b4a01ba830b9fa472575ed2ca06c7261fa1e49bc86b8926f60f171e7e6d2e6d1692fc6aca5f3007724cd27d485bafc5ef8685739bf3d09d7d32d3ff4c52c1d907c7c07ce775a61f0c41c4a581d7b3e1f8e320b499c1b862463b77e19a1bb3c3d8a4b6069fe3a1ca2dd1eea04befaa2fe0507843859c3ade8009153d9973f287f6194515a13e28214736152c824c1f2e39162277d9fdb1778f82825a8dd2db2cdef329aa787d88410e2f86f985270d9461d5a785355cb894075d28df054f89d7667ab00a1ea4a359724640485d525c957e380f2d251f892f6226e2b8e336bfa8abfceda8d998b86e7210b3c3e4dde38c4d532aaffcd5039b1716f55ce2b32db7897676e0b215f2d13be35478620808729994ec74a7cb9c6c1a94021607244ecf9f24d3f80b59774d726d5b17f38b222f48e82eecdedd0fd9502202da4f56150fa00a95ec6c01b629721d2ac447729acb302709bdf2c4f723a7a1460964f34b3f81f99b0df32d557026e742e8e86790fa273a878a035787fde054c6ce3c2dd1ad28068ad50722fdbc7231e341a22231087aedb4d98b75911d9147d3f69d6d664ddaef11db2faab46272690a9f1c03e8103d523026a86f4a8db1cd205595aebb9e4ed0ec308c319ee848f819fcce45daae555fc1f1685935c20a5222c5d34f785460bfa4623377a01417f60498bb42c80bbf0f52bc2f5d2df75cdcc412c4ac08e3d5a0a1852fae5036667bcd1c606dc362551560b4da25de7c264dc69a8480ef98853d047f860a0d9772073dc7ce2862a67859d90909b417dbe669f3247a84ccfb1886726d8ddd7b87727fd56a81b5e821ea517256a0957e84f3164d13402e1f8f35af0211ff1ab37072473ccc812ae77cc727bc17f727fc31675527cb5d2d34d7449fa16c024816be722c98bcfe8fb8f1455ab7553addd31fef230d6bf8256e63e4ff6e69e47f5ccb7286f25e79982fbf005e3d0047497871cc385a1d4b7f95cf45bf2577352563b57254cc0c346e9685ea99da8ff206f248ce0f7340892731284327ef297743cf4772cf8fafcafa9aae7a6dc2265af583c6dc9acb2123f1856683dba251dfcdb88e668057374e361d7f0bcb3987f6946ba7fa5efdbe75dccac08370d2c6ddb40aba6a2acda6d47faf0d243064fa947266035abce872f8b15e6cae2216e93369b89c02fc86e844e746b656f0ec3a6df972c94cc8ea3a38fc87412a2502c3d0963f5d72c29fb7c74683daab2004da70801fe2d2939d80793d204357ed8350341c623372a46633ce9c6d77bd1dd689016844034f8780351b15e834662847cfb839e981722c5bfb6a9434eab5228dfd13ab61d7f7d2240eb564195e55fec46fd6e097407276f1928c55ade06ff74c3e36676247b6b253eb440c226613f9a253f8fdb580726a5067618f670f7bc1997fc97b6f202cd1cd553ff672a97d3700a6ff9e4e2e1b161adf3b48f165c1eb720fc9b48d85bc1ef147db64e134a52cda35328e632f72cc4ad7ea3240fa1fbf9251d0c07cdfe7da65c9a11a738a0efb22e4cf3b075d0ed848b6762f993d8d30403a5c98c910b0ef1c1c1aba4b1097b58415b30c426d724db8e0506c8d8c7c8dba55977f5da96d8b3a14ba82197506050c7bcfa8c39c1dafad9c32ab07c1bae23371d912b82e79dadd97c92ac2439b89653903b920a172ff4728855b7d1f82d592d58c95d5e7e4e9ce75e2c0546456742d032b890e79079d9395c5e5519425d3c0a8f3c6cbb7d38c0ca1ca10cf667713b3fc52da32b029cb3c6cab406d288d3495b81139a9a43501ec35cd8dff4528b29400428b2aa372d8da0d76ad358e928bd12feca3318cc51e78efe82451b02aeee0302d20c7f66d1370e330f235314987043e7cf99323b9a30ec8e6041cc4b8f9ddf5efffda8c1a2436d1a6b85c2b0b9bd0bb80edb7f3a0125e21c53f5b01ab7d0f21016577b4726e06b615a4fbbe6d1181dd4ab5529a48b8692b722c9c79365d73f78ea74829400e01992432cc987fa45ebe6d2286cf8e78a8aa434ffae8305fc8c5a34c7b170772b548e1f6e9854a54263f95e1b8c1814c7d2c4a0d56010b77e26b5195fc5472fff0eef4890dbc820267a2144dd69627b81fc08d9109ed9a0d67bf92d2280272727c280675dc584e43cfb1d87afd2feae715fd68bc85c65ada6b44959070311f918d861ba798874f23a6ca1d4c16e50bdeaf3ac6581f9e37f907b1ecda807b72c8b8afd88bdc0ec011ca46de03ee09bdfbc9c38ca2ab9ac8d5e7c82761bb6072fdef952c5e77dc5d659de6f1ce5ef9d5da863551849f504e3630c7e26fd58672115854b056990b40211afb48bd411614cc5e53e2f6ac033a022fb4a502e6f272fd628ef132a7d0ebf3c66fd9ceaadaa91bd41603209545e8157e8afc7030881f53808e4207989df1243bf08b9edb5fc92add3a226da4c1eb7d3420a6c466f15e7881cb48ac471e6d5f6ab974614faf321694a6f0a6c86fc6ddc07239ae593772e79ecf27677ab3bc69c951760ddef4b4ed3f7a808c7ef290a950d6497fcd427219173cc7cd1eedf0fd15a3df5685ea9fa4b177c7d02aa18e0e06407079a30372a2bd7876018fa30196eba527f807db5f02b232784a796cd5bdf0960081f63302ec1035bd88de1ac9a559f499051ea7b296fe74668886e288d4cce341e7004f72e75c518325c3cda4b080818e4c8e45d6cc098fba6cb382487eb8712718993c2708d4426b0f94b77eb8ce88fd64ed9a73feb328c3bf62eeefecd1b60853a7fe5f6b4f4405b9c47e72b124f2622199d7f53debe3c63239996b8d9975a8843234497faf6f2e1579f50e1eaa0fff21118723ba78949268e02ad92713312ba6e9a972b5563cb8afdac5c483a47dff9247f04116fd1cc9e63bc7960ee03a71def0ae72ae723c41272df3e3296f2374b5a2009067aec887e88ea501530ab16a50fcf172eafa376497501fa00633838f34cb8f8c7cb316ef96024be98f8ae8bdff9d1e72c0b7ac125b7be2974200c831effdeb5ba27e15a2439504df8b25edd1f12fab720dcf8ac5aaad246eb76b5975b01fe32ca9ed9eafa05d256fbba10adc09e0c77290afc623da1051eeaffb81d480179ffa5f6eacb033324f031746d80b7d00ef44a75950811e044a307d4d097151a3c7911fd5dfa4f48512608693e1626c92567280670afed5b0b57ab319a43c7d1409ae5678ab77419ec18b9d4523b6b8a8842f42ee8a23574fa1de3c3ec2ee15b756fe09a691d0a7fc01aadf564d62dd91346517a6eefd9d34484089b2b506e6540fffcda235bd7ee986189b576142d0f059723243a7b78e6d1d01ec9ba4811eefa9c7447d6552cf14fb17ad11d265e0fa0372223785f32438c88130be7f129f122c15d53450269da42e9dcdae253543908e72e62c29127fb8c84d72258779ff8d38fe1f08d9908d4f26465f1e73ac6a063272ffa97bea988853d36858ae812ef3e50f9c64c26ca0a20caadb988c8da8c74617a315faa9d754a912f71282fdbdefd565011de35567ce4ed5b40c57870a6f836035b1bc7c48b31e4daaec763214dd7767c1532df105f5b862dd60522376b6870092aacfef946e3961062c7d910f0ad5ce5b2ba63df2833e1b7fd17740100c5f07ac56a2c9107b7490b0def1f67a11fdde3e3b907c64be529c59802b9195233c72197a6f3ef296ae3414d0b36713ccbfff462e4e2d7db70d0a9aaf118ab0a1c6729d18b9f56add50f871ffd3b52a632cff7aca43fcd4b7cb82fbe5467dfbf83c727b42cda50bec1e15e6f954fab0471ef84bdd537abe4c18beb300beec7ba6b0722dcc45788d8547efbde3e060827eec2b99cb8fb6c3928ffcea875f370be9fa72ef2530ae4a951af4d3f49198a51fa4468c8bd4166fe3380ea42244d50fa9097293999fc5782f59dade1f7f930014cf1cb8b459b9045d2aa799630a925eb39f363d6302c9ac93cd6e249a9e4d1045c49f22a69e5e61aa930704cbdf7768a4b7543ba73d5635a75873118e2d5bd611e9f1ed42d812a2b22d6ac700cb945c5f03253845883e81a6b79f2364f6a97bc66fb3f64668f6d864833501b2bdd182493c720002271aef512bb5ae159bbb8c8392a7bef035341f7ec293f12e89da5a26af586f71f41b33165feeb57520311193e8a4bbfb972c5bbb9fbe92ec0c138d54ed7238fd58d93c636c17a0619d2ff5b4300c3abe79b418f046f64b63851c3f6119209b4a64dfd294d99d50581385ba7d5b2e8843ce1ec39da98a0811054a696c63724a6781fbd0483173bb85bc67541cf29fef6c29fcd406457b5b308ae010ff05725f69fd26bc22cb398dc7c98c6de9d9a644052de9d6457a1d7500d9e1bb3a2772238ea9cbef55c31b457739133c46d34727ef32281f881849aa43a1e73216dd2745512f52367f4514fad6c35d6c6e34688e1756db601eaa345cc84d1c1b30d7726c6f26a227949cab260cd3465a1699d9a53e4ad794782c6c012aeaac3d0156002040b19418a50345a28c27ff7428c795eca85ed53e07d0fb3302d29e578b9672769b998a2d0d9646f076ac271eaacde4eea244a64fa6a2f57ce53d29e880034ee26b0711a55b14a9d736b938a41c211013f60c4cb9739bc73be3a3679a3077720c4d94ed0d5020406d9eb3ee80532a0dc5460e23db143f16a5799f54574f0a5a27d030bea4731049d1809b356b0c2e3d02443be8c905c798edfd18d000f33a728258cf43319a85e32c453cd05976ab4463dfa8ab2bdba5bf6de09c539a63246855d933d03bb4a38003707a34379ac4002d6598349d3559ca926f33c3c4d4bd5627dc4b7aa180cd0a812a26c40648faac06b912eece97f3c55e7652acb302765c5b5a9d9c5d65365fdd075e61d0f1e136816f6eb2b74c422f3b137399f0640072e81702da77b7e9f8ac465001f00a369afa34b6c152fc4ad605f30a996a483272fd1d22bd00b08c6e6fb1ee03777d910757f248a7c3d1fd293cd03b9cf4dbf572daa3adc3b39a7c2f60aaf4e3ab4b1349d0d8d061c7f79a52c2d664867ac41172bb8bc1958b44be2cc0e1579b9abf564230f41c1171718cfddd9abfdc6bb8891d9be41f46a99a0254b81ca0ae656f971545be606b273f10698bea2d10ccbaa8486c874067ce79e980f44a52835bd4eb7f90fa61ac40a08a3b5bed4bb588d5335c08e45bbbc4cdfef733f2ed4b2b123dc63eba7617c7f4b3dced5f96d3edd87972ef451dd2ddd0f943452d8486a0f0d19e9083b8d1c86287cd92129fcb54135f720b168a818982469ed8c6b9ea367d9749a55fc613364b78c0b088392c19276372f2d7d1a3ea2e3b466652830f44e09d7e231e7e8a516d85c2ed4eb4608e141668b471f39c17aeddca4aafbd39c109dbdf0c64097055b3531e1cea2c244f5ba83417bf595c03ed2509d75fafd7d6be33ba0126fe432a0fb2e0e2a42e6b521e08171afce0c20b45587177c06c66eec67d170274ef77fd318e46d9263b999506ac723fb31dabb9db1a878041d7b7e9017d06b3c392b1c6efbbd05df3209f1fcd0072381e84bf43cb19d7eb935857a49aa002ee989f0e4cdf269fe8578d15f5f20e46cd64aec010a04d0bec39137921cd5927e3853b6a70c739ec9fdc195e17ad137262e7a27a6e7cc37c27ec1cef9783a969d4dbb9f977cc13d23ef0cfed2524d77206bfd9377ecafc805a09e21070b268ff68f81fe83d65b92ed929a2b2eba47172b1d5d3418ef3af163105b23bcfd2ca5a1bb7026b6fbf40100f2024f2a449d95a7c161a22e1a6adb772f5933a984d9cca1d73b53016453b93464b228ebda21b3f0bc6d8d00b2c86ce97c8648975fc66d177e257b57ec8eee63a343e465502f872dc0b7ede695ba21b8a20eabeb58260e0bc0d88aec95b7808a24a9b7dbbc35336f871c943804df75615f4e0bffb78fe50f21d96c955cdb77298be63fa0f3b54727c0b94bac052815bea7ac59aed9ff8cf9f0e03149b39f9f322fa8dab5795bf72ca0cbee8a35ec5ff3bedfefab968b812059b77e1db1f25532bef339dee428c6dbbd935690a8ca80c7540b8de19d1181922313247fe5a13abfd69dbc46023ba72a5ea12bba1b1dbc5fab24993eaa7dd8697f374c0a2c2b8b8febbb72f1c5a75412082097b6df717bdfc2c8a65502faef747f39b0d5085adc3458e89ff79ab4e163b5d0c0f00e4ab775acab765764f14d2d1cfc014318effc7245d294cf76bed0cee47692c28f44723a88b87b9d06245e83423f3db1f389aaf4322ab27e5a163720bbc87877f8ae2c9f729b8c56aa51186950414628b7b70a025b81dbe5ea96c72037a39deaada641c22412f3079c9611024b19c182d498e00b5ad1fff796525474f7e75470ad9501e42ad6b17b50fc260b5af9cda365f65fee445cef0abb29f72307a5f0c152786f6c61adb722741fb6c1347af06262f831cfb2e9b8e26f33072db6dc0cc0d0f7e659045c181c089e30ab32b3c57dd1b2b11993433eccf41a741b3eb5a1c69d13824d6a2b99f0e217cdf7490962c9ef531b4783297be096a2a7103badb1a044d60bf717966518b85ada69f8068e2d0266ae2fb608ea12fe9f82498a0138c365f64cbcec24647edb529defdd010835a7e66c65940599ebd6140721f2c519f2f6f060f8509333d49fc4e369a92e0e703479f96ae6c026b3a515316e8305f9775fd7305a18ac022470c44012b10c3bf187869fd3bed63fcc11de372572bb413aa4565b2a903b7b2f43bc83b1fe253c806a5a79fe88921d802e9753f4c141bb6ba23d9e12ef44589ca5d0275128c7594ee4cb6329661cd8f7d3a507224e4a5d8e283244b35d0339b378721d47aa6791b706a653fb1c2abbbbcb748724ab6bc1ed27c745e21ccf08247244940c25f01996ea730f888971147d635fc0d63a689b6207283d2c612386f2e5b023e6e37b87f11fa81f36aa029fc228c1572ac7d5070547c28a6366f040009ba203f3bcd9a577d91d7e29198c76d37d2f44483155e187771e4bf6094400581afadf2f10822aee36dd5f8952d12f05091887291908c58b7a4f3a783d9522c2b7b62635209bb880abbb36e0c0ae49ddd5f3d72db93c2f1dd5919ab7a0099e0480ee7e7a723bdbc4725affdb75296bb0c2f1172734b97e6d4196c2f37f26ae5ab635807db7446fe3d465acadab86cb37d097d720d14ba302dd01366289d8b1fb7395818b55de448e544ae9888563f192bfb66330aac129c8ab4379007aca84cb4821345a02c2d6c7ae243e302f140656afd246d2ef546422dfd81b52226a38102231072c587e9d8075857d174da4bcd64eee8720741bfc5eb5909d9c8b3fe25f6193fadc18d666e0fa7410ceff6691a346d67597e63630ab74736d85b784000a231b00b1d0e2c1c7fe622fd7704d041e6e2f972799a0aecdd42b43a30a5808cf9fb85e9f9138c3dc2c6e4349a70614ea9e88f72f80ebbf5d7c8b132d50a41f1526dab14fdd6f9050375d54d50bed55660ebfa72cf2ac49fd44c702d74b16342bbb5a9fae653852f12ea1bc72b54f3e9de2dbb72ece776e0ca346b1ab622a74c2ca3d8288995fb0d0d4f5d594da37168aaffb472b0b0d1c38942ef6756a8c13b7948134c5a6726b9e2745c845d92f7507a2b5072e1cc21dc820b9bbe370bca379bcd4605226cd85d5080d7a6b7e489f63fee8912acb95414d63475a5eaa1c2c5b2c2a4bca673945ce43c5ce1cb74d9f86f97ee3d1e96e1f9f7c7ae212855508996c9048d26c51c38ddfd20672e6ff3b9e8dbd43b4f89b32e58c597b2ccaaa04e9b8716cfb0863b1b0d839e0010e5a37db6bfef72395a42d117148ad985725eec9af0ab2bbb3d348bacdbc9dde6e50febe84c6b72c8f314519a191e071eac5586d6995d93a5a8a1be66c596e40ef480abe4f71e0a5a913d28d4249d0c12c0657f969d294b38fd8725113bd031c4f54d1bca5b0c727b6879812392648e54994de58bfad1b68bd728a706f88005d2537baacb1e4c20b1ff4102d26a1068f170203b6799b31973bc8845c40d1702b3134d9b069116721436b77800a3209833d0b54c3682fc83fede95f540aedae57c9fc45ad5561123b5b83cc44eca89055bc401de1a02b4b7776afe9938b1819f3b4318ecc521e57249aed616e5d85ead7d3651ad89ba2ccf8130016f65c7b5e1bede49908cdddd4344ae281f65c6f6b42b38856ac4fa4067963a237fc55b77eb1002d49ede9acd72c7b8543f7132019298871a731e38267cee220ee014399178146718dbed6a74728bd6162e4bdcb45fd3e1c3f10a527ac5dfb22eaa3e591bb49a826196993969721d2ad29ab7a96539de4b645c9d28e70f628021216e8f9f9b9609752316133d72e85491cd11a691f17692a2328987915e56a7180878e9f3ba25911baa418acb4ae97f5d33a61987a02a0fa193acc817a5128b01b28c5825f051023bce538a4c62c490fd7fef57787880a969afde7b667633ad4130eb50ce8f1defd6a2d00d1872b1138c8c308d7be7107ec6e4788e6f7923e0df9644056b4df495a08cd51053662130691d7777d006cfbc0d0665b5c64a8d66d36fda3fdd9f64186f7f6b89213799a913c5ab57c9044aa796d70f7c699b191abaef9bf31231af207bb7b10809726e7420aad71cc8b1f02d2021e7f457a83737fba22a0b1251a0557628eb9a1d725116d35059aba105c35b9f73d5b34cc34cb1f453eec6b3ff33eb132463723f22168944198ba24dda30a18bf3a6d7c818fe4505e352ec0a9c105d8966052f3b4ec149660e3c6ca2174e50905861562fb234a5b710f397f8913e5d5648d9b1ac726cf575075727613fcd003c8310386444330ab08cb300736ae5ad6b31d9d1a1726ca2044e784568023da27de6073f92b18371f5cb0bb519b8c54fe644d244ab3fb34263dd5a0e2470a0bd6b5e8dc2305f05be1fee06f81ec504666f1a8e808336f9b026654efd81963868ac8af0c4ebd3c3ff95f6c9f8f67ce4cbc89b5ccd055a42ee152feb09cd9337445186686d0e720f1967850420648454fd0810e8fd9022e21fe25b3bfa41aab17e46a71b2004d195497def0a3e77701280208ef39c2f6a17ba5660900cdfc7f1d532584f1b419828a03cc98335591a13ca7a4bdd94845116ba4b86fadcf673823486011e52f6097f92f5cf887da8d476aa5066136d2816f6f4e098e9a261ed014ca2eaeee3c109c965f69b55bf022bfbcfc482383ded7220a48128ed6587f71e960ffc73ccddc60c94901f474feac2b0cfdd069448be725c4ad98a5afbcaf55d5c689290da1b31cf79af96be5ee79f6781b1acb8bf827214a87b5a803ab138c21760b77fb82b71fbde0466d3795e9dd6404f796fdc7b72e06dd18bb2c59208b0f37b1fc4dc8c64d3171c326db72652bd57930e726ff4474cc94207e59ca6e4a3fa77d0889e6c0053fedce09c95b72421ed33b25bb4b072849af8e52bff878a56961b190262308c73275b0f67478c962888e39d425c3d72570ea6a54e9b6bfcfc8d4b3dee5747a65c76b787d35656e6b1490742bb2d3372af059f6034d0788be2a6c7d865acbeb9e2fd3000180760ad3dab488979465a722594222a214177a4da5c2b7f521cf8236f9f6597a8efd6e577d0073d5b7eba06447f782cd2bd9f0773c07cb5087c65d725492590bbcf064b788c05f2acc16272b555dbb496516ca6dc9b7894a49e368ce8d58ff4df35d58ea00864a453a1d7728831976ee847dc70d9494a7827812c017d8fd6f24f9cf401388ce8f080f1b233fa30c75c4bc2a4f9eb485d23192e92029b126fe86b1fb9bc01c157d1207d6d72981d8806bc80312d4a3941b15291765526e43d7656aef919da8c4b2e9ae80872bef45e940fd507cf9b3e630982788136e371b8393bdd515aee516f4ab8e0a3060860bb70bce3ed42ae6d1efbc0540c6bad58e42ec992c143cd1cfc1a4378f4726eeeb64225f58f48344a18ec4b0328c46ce52aa8cfdc9e0f7f7ebfbfabf804469ef936b7d822f34f5fe5c1fac24c633ee6767e04c13b099ce3be65253f1aa872e33dc93ef468175a936c9732d56211c88366b152f437dabac62eeb640d7782729129c0cb2e68501e2190e361d364623f7dd32421bd0faf0460e5e8106e37517242f535455f0bdf6fa60672e15332a44e8fa0ff738012cf3bc839c0ba350db37249869ae003655043c1c896c69c9ab5fc304c79570e5facd4835ad17cfdcb1d7272bf886642fa38454c4fe63766a621b87b22cc6284aebe56fca9741a1660d956ee74687c0ee297e170cc013d3ac27a57318f9edca30377cfcd7f93e79cb27472fe49bdd2379002e7dd54dbbbf6cf4b06d631a2db8e65b9038cff9cef4ab1667269c730c4623a32e1345b4de9126bd0a5f7611db349bad014c44cba8300d7a572a6aa03513ae385dafbad2b0e06d1f50e8983ea935375090c35404d16cb6d7d728b06a32973ef1f8a6d67f58db26e724afea2c5a2058cfc0ef77a12e8d26b4e72a98ee31608dc4c79e10e632281b2abb64fbae116f4702917bb1359038d7c464c46c46df761b3f7ae83a13307dd20ce376117b4590566bbe2ae05cdd691cf8872662c63a92af44e40fbff9abc3061ceac2da227346c6024812d3dd2402553ce33d84d0095eafc1b392ce390e242869f18cc13c7dc99a3de3f929af78fd377f872fb5fdbae61a1cbd66a9d521b588593d85777e15ab51d083ea8c4b20b52911a72cb96ef45928d6b1ed4d21efddcecad691b03030cbb9baba9e058d6ee50eca77267bb8d0dd2843578a07ff0b67c6d7e2dc21a6b24b239cf052b99a3dc13d0d9720244fd08593d929c028459b323c0ac073dce997c09c48a8a7122f68aaabbbb72601e19dd781b3758a4b7eaafbe91624c533af100094e496d80bb2c621be34602b30c407e1380f755208cb33c80acedbc45da05c9b2d664a812d893c33a873e12c569c957fea60648f2deb04729ced13f63e9608036644deeb09c3e574c78d9725eeb9da71c59c79e54dc9f7dbf57c3e33f84e8c9890ff5143125d66e3383442a27230cb5a9b38213b33f090e79f3c8da6ba855cf512690843a2c99ee66a5447282996f937c56c3236c04999646a4af72c72d7a30408c09b989c5f47dfd890413cbc68115f0bc9c5dcc9c9101e6a6668e920c6d4e4a4a4e1335ce2f38c3046e34cd3c55b2dce2fb7f21f295ea94fddc0507c715a39d18800cd106dd5a5effb75dae3a6719a753afcdafd4355366151e289f4a219c8ed5bf573be643826c6b61725245e45ee778e1934dffea5e2d9cd954193d7e5276ce9433519f68cef46b247262f2815432c1abb153d6b4667c994c527de6d43413d9ce94e75a9dad5485cb7278e04d468d060662b26e869e4560cfa2dedfa1a9f5ca7650cd93947ef2e2017246d8c7f1d73b6ce8f4fb8aae79899d9e6b27232a6245f49fa0a2caca7731095d2beb823e7d067ad4e1f2307380e58d2e182bbab206b8f837e10ce4943a8d2f7214130563fcbe2a36c5a4e840a1727307dede7eca6c2c158218b52c6cea5a4f725eb4fa40194c27e1edb243d892bf801d1b6cdb766302c59ba4c444db4bf50a7203dfab773fa9c27a0dc6d570cc5d2496fe86a35c5dd64efd2ce9b46e3f95bc721bb3ee109b32ee6fb795e4f7b2cc2389ff10fda0773485262114aa6b9737d94d53ae59945e4109e08de2bc4f15e0ee36b489fb97c79d560fdfd4e64fcd520072380de157dce0b3a1547e23238b492dfbe3248e56df7f0f58cef5dd002489e545e6fe910051612224270b127e1c39eca742780e93450838af149e652f842eaf3101d07c3949729dd9ae85bc11240984a167cc9e3f2719e9ce0af89cc461577311f31ed1f5aee9e24622bc7f00c84efcde926822bdf4cb7b03218aff364495ef688c16ae667b5ca34d67a05fa2516075dee983425e775efa187c9031013f8cb372ae22491017d03000705dad745b2b811daf1c8a37efbac2590d707eb56fee287241d3c87f44145dc6652ef232ca6e5852a7369919ea854d53fc4c7132232b86727d9b1f88ccc40d58481c1305b01d6a034f18073f3eaf1a948855ce940f932572180ad5ef5c72b314d302f73e7107447c7f98fd19777172932c04e3025e4b9b06504dc8578fb4729d249353cf0d992884f1bf77fdd7e12884f051c793d4797a697890266ea32ad98f2557390bbac5dc85d4a32f7569f6adb9760caa8ffe3270723c69fcf2b9d266b8169fd4735f270cf8a30200c51546ba56fc2d4781a8b0dd26a2eea0f6d23b9f0d2357109eb65a61b7be65b143ae93a2af08da6e4e55edaf72cd795f377cd7d4c41cbaa2bcd6b0af97e4bbb960602e51cf4ee05513d6e4b87276b43be6ac3dbde1afe450785194a52e0adc1848b0a771c1755521916493947231c1480ca253dabd84e7629ddcf289312dc1307d4a924f3587a2f000bb6758724ee96375338ef4112ce92789fa93be4969a42a81a4bb35f7d951493080838372fe9ca99f761023771d15a8228be58d40a77b3cc904c80286c27254869736d76c3497c7804f1fab7b55aa9b9ed313cc12e2237318178591fcdd4e8eb7883aba7237a6a156633db593fe909d6e1fddcdfef007d7fec7d7f803a170d3110fdadd72abcca9df4f962e6e2f4fdcb92daa0c48d37dae667c6bb9cc9d7722adf3741472fcd624fe32619c6b8a992c6df33f1dcf1a9b981ff6a6a67c00d980b3b4bf6619c54bd474f8713c90295a5e6736e92fec7ae8b2f7c415f1bf2b0344347d504b72e3ecbcd5786b669906be50fcc1de7a02c0f00a94eb6d1957e643df65ca42814cd5020cfe038f192b09b837824cf5fa14e7ae3379b04a62f9f28443e2dccc40725943fae71c1c77a992996da00edd80518a77e6860a64c70cde4aa45bf5c1b972fdd8515686cead15d9b106abec3698d12a7d7fd539ee9da9345828ad56af4c72ea9d330f8e3dfbfa5d79478bc2fc666e7c27134e2688319f0e48b3dcb9a95872214eb5865396b006b383a685341588f8e3be7bbb9d0fa3b347e162ecaa624a722d53da2587218981ccafac5246b2a3e3e2a0ec458598c952df291e90d9619e72dc52aff3b4d1142f82be18a345305d19b32a0cea3e96f5487d1e90a53c3e8b728b684264ae7d229fb2847ca982c015c05390ffb8cabe93f96b97e0a2224cf17229d6e0408bdb1e88d58f543b82b22416ffbf46d30cec81dbd973a3150ade3c727fc16bbbd983ffdd38382045a248dc11d64af7c07373ac5fc32cd76cb5e62a720caf1dd53230fbf3d9f5669f9e7e5a3e78e4f0e99753e24675c8602989856672859b24447f3e61df95a4937d220b3f5a7f0016bd5dd391b0f3793c7f816d7a0671906c2815b08cc08011f60f07c7ac96384bf07847b7ff600ed3757df40c2f08664bc74cc8ff46dbf10fb8613d936dbb45c614677898e1ff1010171a2fd9d27278cf9ae582e57709bd3fd238b94dd09c3fdbf4f6c73529b53290a36943de0172488fb7dd2502ec189e8fd42990e831e425badbfa68a63e2ca4c67e1d24e98e7297997d29f08060aaa27d11e591a582412674e7fdfdd03b541c5ce7eb841c19722fd9c8a113d6518be7db43e0bb8508d469c52eec3853a94cd246cc7a71d2c9725bdc3d5c2a7426a79b03b290ba581eb7afeff3da5a46eff37c061da6b062de722f38d9aa579ff8213a8bb255682e26aa8b7228dcaadd5891f3dfe83d85dca6724fee4dcbb5732dbf6cfcb93d1caea171d873a2489960743c99a9b0f5f7694e63718bcb99b75d2ce707a1c03af1551e03ab71663192d400dd8c4aa9be655dd33f1c6d75095abe35f121032599f7b0ada65f78e648a6e1489e09b442636af60472dc971a92e35852d29ae198159e536a973e2104066a605f8abdd98a7d7fefba109c32bcbc42d96c2b1926d1ba334c835df21c1fbb4c34acab84ad8be0011ca94f589408139f5ab3f75c274462fae6fe91b12a42719fb4271cca83ec0ef6cdcd72f4d7cf78915cf203f3f920c26ff2a12463558db0ae475409bea0c45734a6e8722901680fd6b2e4835e7264c02b52347443adf6049b65aea5d850a431a1eda0620eb8643010867bd90f7428b8398d87b5ab462e4f1f0e263bc205950249b3af726cc528f418408d9b7800491e9608f8f75ce91ad0fdddcae66c03c2011c54be68642a51fcec75db439f39fecd554d2462cf634cd6a17bf837ad07380e9d60261e131b4312a442364e900605b6728b5983c59232c768556023f42195cb670ec872059df0f4247c8666d40d6e91980a004cced38704ef9fa595d41d1a1f6875f072255be1f13fa76bbc9012983dfb6919403872dcd66f6c9d15892d8984e47d4224ed6797d583d66d47db310ad11e89fe1ac5378108ca368b4740cdbcb3f32adb2270ef061c2cccd82edeaf499fffce1c40073abfc7be9e2e64b417c3d4ea792d729721bcdb3e224a8b12d3270449cadef8bfbc21913a5036e30b3f590acb0aad72115f37cd2c30322d858b7caa0472099c8044a0deddced9f9c4dbf39267275072bf9e63357912247401a78e5758942c71861902637ac0aa7cb2440983cd8c74019ab2b690e2a8e126b936d9f5143d211de9edbd2a4f5d9ce96a2a2fe76a9118721cacbe428c1b7499da74e5382d582c4db79b6402fbdf47a9272e3564a8e8bc721cb3c0971b77f64b61de24b62820bc13273ace555024d4ed1de86f83b3f0b40d9f8a77e48f0ae055ce74cfc57897faf11ed3402e65a9582e36e4ae2ce84dbb721469fec5606b2291870b0543019905bc34cb523956d4f1b9a64ba1c104788d3a74f3cc72e79f13e01a4576fd205a7b6ed1d6863ffb2d58564b6d004b6d7d4872611af0420587089fcfabf59e3ac75d9f5fd176b82ac2f9dc93a720658bbf402aafeb75cab43d0e29bd70b96f86fa16be656bcf68a4aeecc1beb29aeb40a4034230f4ccc36ef35ab4e516400c225bb3afc4ffdd5a64151825ade1139e5db3be721dd2e274a2a77f59b433a01c53923f370fe802a50628fe618665da49152d78310a7a2e5d9d43b7885a1c377d23185ecb9c59f71d41586ba26053fb10e27c6872585703c8ecb71011aa5ee43c8ba689234be0818f4aca34a65d6e29c175f09969a43b7de81b0594918c0b04a9c873a78932a66674ccf718053e29e5aa3299e9727638f72ca5d0ea767171874af5c53bbec6c1a1a09ee90935b51d4af4e3b56a723322c13aa0d275103bc3ac0a200229a2b73f8185f9745aa86e2e557198883a725d9d9b93a91b20765a3564c1b92767e427b41c9ec4b832927950e74526260e01f10e68de52e3a70b8a04b497d9d99b4ef427bd543e78b0e9aa702337fbb43a638c863e9c482d1e61923fdfb1150fbe6022c08f7f1b5da52982856f993541db3cd55145cdbdc090d24cfdc06f0c3fe9c18a0d1657e42fa6f4dfdd7426628916724933b45571b3faedae3a5724d5b4ce94d218e6797a38931781ecf5b7fe9c0472db57159f52c6219a749ba2f83dc2a2b8e09f35df71f41ec27d54eba9860eda726dec2a960ca945e773d73a50a7dd6762d39207d93944f970a5bb6debd2f9c372c4656dac4c1db7a8adb7c44e99d218cd35de345d4c04c51dde7a0c82e03fb572478e741dcde676082bad34dc4757fd1fb17130a5b25728a385845278b575d11ba53e3cf989d2da78d9ea4ac317670fda09cf4abd75031f69469e4856ca3a71725253486d5c63a4f6e2b1f99c36f422b92bd19a714652414078a0a6d04b84735ea6c8c4c21a089404b426ea38cb9b2d8d877a409c3bbc7bdcd266f82046940272b70f9fec5956188b9ac962fa3089dd108a0c4ddf46419f939a1564d55f72050209971fd276f103e9110a34a8042aed9be4739d130ccd8214fb36ebdecc9f155f6a365d4b95f0128799d9629c937810cd88f235c6d641868c82eed2ed96638a72d4ec7eec1ef01d25b5cc18a59e059f52a6a8dcf26b1b92046bebfca334d1871ddf058f87c6c64a177d3c09b5d1b1a53432fead13673d67b6305631e83d349d7221c0edbe25f2aba76ac20cf75d7033f20ad329cf9c07b8a2176db35541403b7291a84c8fdbd5d90f629e02a1fafd18986f6f35cbeb9db55b77e74f6d98e77f08dc0b4bbc68be8ed8696a35442fafe84ab521186b4fc5fed68c8baf62762f4a5bdeacfd0108484187caf915d55238143780a7d42063ab0de227012c1512252050df639a933dcec665c8ebff70caed5226fe7de224a402abc9dd11c7b22689c20ec65ed6d83ae98a14ce46d0645802d775c030329cf377d88ce49ab067687b0d728cd65071191042f22951213768fa04c66a338cb4732afac06f0cfeec9c10a62ebca3ed7c4ad075f88b6559b059284f40c6e9b57b6eca29355667bd4c8d2b5a4f49f348e4ce30cb8838b4e8d9614f509fdc3b64774ed8c9afc7c803a055127b7284791a3609439538a515f8271e4c9b6b047047429f9becf82429fbe933c7e23e294cbee72348ac33c1ebb8352db4fb051b864e290bf387c97c68720b4183fd725a612f0321f50d703afa020eb5234b142a7f669025fcf09244e95250c221230258a5fda898d7de143e0cec90884c0c9f8d619727008cc1163fe390e523db3172613d93dde7994da181920059a2f127a43e7c1e503ce91efad66843a944fd3472bb9b07825d49d86cbb44f03037825e28797e661e97e7292d8af0081ee43efd728c165d9fab48b82a71f3fcb427796176e594b63da00f3722c05182d4c2ebaa72366c59f1aac781ab3fe58d61b23032e100bf391687829d3f8020e28db81c9672fb52fdb656df6a25257ba180785b4d9461baa4b302e94e4b8b77fe8b400c893eb6c58daefadbc5e6f658df74e2fce9cbba64fcecf26ff3d6cc4787d5e6e54f7236318a3dedd0b5ec44096c5ca0dc6a730f603ccf0972ecb60c048d67ee923433149bbd3059ed97ead7415a2b9c55e5f056f8ead910d5712679a11b1e20ed86726526c67e65a34ba2a9c2883f9922dfa4e3da539e6eb164dbabc66dcd0f77e83abfc23d97ad02614afdcd996d78f933e2bfccc0f06cbf004fae1ffe38dbadf572ae591e9fc7e289f8bd75da12e0be27bf11c4d862d4c431c992ed8aa9272f2c72fb75f20cc4439e4036122d378c09c826320b73e600570397232dca391e2020076eeb29b05e71d8c03a30603a74695b32c81d982d13605d6efb78c29a565063555a2f9824a18fee40cb1a55233b4cdf72c94da2e3423835f9801d69362b7d2c7251bfce678f5a2e2004fb8985890fc69653f098fb2bb052447df91fff1d9b3f44f748582764559d3626855ed6ec2dce01e4eb7e846731a0e414a1b2fec00c775bed5b7b9863be8c8a15f886b4569bafca84b6fd19502c3d69c7a76914ba06fd727389cf67803949b56641522f6c5a001ed9a2b5075d0db089b25625566f9f1a10c57f501bd53a01aa8840f6c128f5501aafb9e257b8808c5440470cb99708602f96d577239b48c41a3c1acb1b23b32cc0d540be190b91ed7bd84efa0b43d2e654380a7243c07318b4a88debe4d28d9edaff235ecc67522a54e892788861e50372f43733dca2a2cc150f8b24e9db1b8b570ff19827d6811bf07d98f61181998809443edc5591a9c2ef17b2877b13c99e019680f76fe9cf9a6c9aac3a1ff1161859b136f92a1b7e6474f81d58fb266ab91880df5c016f3bc9e92ec5331e410be164093252ec56c8583aefe7b2b77976708fd653818381c1522b461f902ce0b8e672f15b6325a3ea544085629d2e1971b123185b5a9790eb2a439ea53b59d5fcda721a7abe5690a8da87cb15dfdafc82cc6ac8047030b9b897cc3425fb0604286072b45a9d17b4658fe565ea9aa319c92ee3dbb10f1da8838c75733848320ab3ca723c56e004cef253c7f698c4a35b9577742c4ab5f8592c078bce9a70f4f0429938d9c03fcbd0b0564a220d52e8ed66735f8466f31d6ce38001bc8d67a31811161f8a799c2bd2406f2efcf87a4e02a5e0d00410209be9aeed47918d3e2be7313e320f30b5724c802cd0133649150b04b9265f8ab5db5a2bdb02db7a5ff73b189d5cce32baf77184cea8773fbec4aaeec8a0dedfd4496e1645e31e300d857ca8511853f133012375b2a816f80d7ed399cbfae1408c7e7f622339c54d9bee6c7c8972a75c6e8dfb25cd594c441962eff9a924c9f8e3d399b5177a04089c9fc1ca3305e24006fe6c5f68e16541cc2388f666f6c4646c22beef99dfdc6cbbe0a8807372638e384958852301177c909db882433437e9ffbc1169b059652f60e48028a0699213f5cdc24a65c93fcbd23b104e545751bddf6d97f07b060a63fe1aaaea5172720b4394f4f207f7824ad70886f0c6d4c0dcde6812ad441dee3776f3fb709672d0ffaa4660db159d4e97192b657879733a84ad1abbbdc8897db82b296de4b2330a54837be167ce05c1b487a16f041043b3d7579e4849b55015c1febf4cfa9472178df6739c7a5ad225401e85ac1e559ac68da52004066629d9ea564d0c9fa249a580acff0e591a846a2b9d6538cf3d120fc5a1872410fc38a600dae7d839df72b37608db04df22a436d926e3a6c9ce93a661574e6a1dcf89cf2f9ced73ce8f728489d6a7a62fdb90b0beaa8bc9339066f7a9935ce0676d11b13bab3cc95cab551f59b3187d72a21ef458bf19019a2989ce260451636b64130739cc8db8341a72ea1ee022ac50b726eca5e4f75845be33c6f35e45a83ae91af6e9c67192887e7214b25fb98da35d27e62910019a8261785c4ccb0f9ee7230df9d2ad0090fbaa720f3537f40da198df332d3b07c5f105cd6904877787e1b5bf1f954594d9be0972dd5fde56875c142cde9d93300a592f880049cf06c48e1954bd638d7ab9348a720fac8ac7104b3ad8429a9d3e150addf4908fe75e70aeeb9815f6514c10aaa5681604b379c3c4fdc2b61d596d954e6b0a7bc84c11307f27c20919d1f6c3fb6463869f4d981173af216445bd5b3cc7ec4244009d67fe3e0f2cedcf6a71cf0d3310cf0e7c1edb5c475d93ceb5a8467536ed2d442389fa8135ac405b7917f5f07f72dcd68185ea6c0b509f8eb3edd6133cbd78861da8dee6e2cc7864e8d90489b05a91a1ede84977b5f4c0ec06981e45dded3c78d64f79e96e8f829929692b83a572e8d533c4775736a0318c21a13184ea17fdb04dfed29240e91f083b40b3478672c27db5d5126d19932e1db23ea0a085ea9b27efe3ee506b6599d45160da775772d902eb650f0c61bb794d7632cc73c39c8605bc23bd0e6a75fb047431e62b7549c5d828e4a3c073ae0330d8903adec92a710b7b6d9e2acb27abb5f2796490027219f41ba406d6fb506f63890e35850a37ef90826868acba05860f738dda7fe8345d0847b757613d5601ac1cff500bc66254dd68aa290dda5bcf5c0aad1add2172467ebb78761a402009f0230686cfe03dba232c5b036df67f64ca1783c8a416729613aaea50177151e324c54e254dd8971f02228cf8910e1bf18a898966222d7282865c2fad58c1de9ebd9e4bf332a1261e3d014c8278aed2135d60031163517249cea645d7bab10fe33b2dd4a3c1b4b67f03394897a1760eb30664e1dc164a72d7ef759b2b8374bf59487c3ab67d40aeb93154f90d7885ee50e0ad31f252ea72273634fedcb6ebfc51d95f91c9ab2d13819485f2f4dd667b1c286f76c970614980a72bd6feaa9d68c70dd2dd99eb34a9a2dd89561cbcc7df28c5849d4bd35772ed148ac27981cc390744cdfbfe33a3daa3ee59bcf6f6a515b403790581561004c81705e739200222609eb78a11d41f6cf751206a9f3413aa4a450a95a66fea1eb1bcb6a5a320eca2d06a320eff477993c9173758b3de1169481e288f75f764721c642ba983dbc6bfbb81c380aec0ebdd400f681110aa6a4c5f37255539d6d57201307153abad1f392105f7a3a6e6a71a105d810571da1872b4d78efb0e22832b5c7ad18f0c91f7a0518cc014ee1d1f189536bf38cc2092559044633562ca1e021b2f6edc3bc8220c8e0b62394687b8afbf91800c24e72b3ec9892ca9d0c0b172058aa42e9bb442c57dc2b748bd4f752cd51d5e8f08a7d247b10d3f0e896ce2727aca8e0bc331be4471a64f3369147e59412adbbdf56c63a5254b48c385643d016ba18a2ff91e74ff89a01ee8e3364d6c7b1b76f78229949a8850db341d269a4291a531ba40f901e66dbd698bcd6576d650794640326a4ba372e25dede32dd7722b8041ad9843cba90941f0f2f202ec769edec17028ddfe5289e0f0151439d9327803a05216c9b05d3e02a0b30bd298523e3959d88c3e33ff1369e24be4c7d9724477333004046eb91d1ea2a946cf3092488cdb53e9755545ca794bedf68e4972679b15b38eca8b04d4159d5eefb7a0907b0e01dc43ed545993ed9f9931bacf69b9d6716fb550b49cbef34e6f0e8aa81e5fe180cd61214f455617f839fd1f254876ce8ede72744e0861f10041b51e10fd3c852dca63e5a70b0553f18891cc2472d9087f947b18fc06b4c1f3c51b0020d86cb7fe9b5e7dde680d99b5c7cf8ca3721a45ce174625d62dd39150cf710403f3d7c74eb4b81926cd583fb1a3a269e54f73e47c8b7f3f4d15d1507d74bc17363b2e763e318b85679a896bff5585d3847279bc79d01889b9bc078955a3c99805d9128143b8ef3a3fdc31a97914ee688172dae65d8b2e4b80c884000849f196af830f28096cc5ecac8c175c5bfc99220172002b29d7d672b8d66e561871036bfe80f0a66e539b0a4028145c0a8616fcb872660e751e67e6d85765178d1e891398b209517c635c960b77b25ba3716316ac72217a9a9de7a525781ca224cdf95bb99ff16cba5ab8ecb0cc3600df01860f0213bb59684c4ddd18fdc1419fa95faa8a19c8be252e1b13075d4ffd4c4c157b5e2e7fa682577fb538d3baf8365b34609f006f2764e97d07aec4bd79f5699e00cf728417f9a3d18e9de43d239f751030d46de3b12857b9f630a61f48c750e335d172280aead765ab02d9e60fabc915657f5d41a35049049530018ab36bbbaf956372ab2f7e9e007202ca06a198275e7cbe970d23a849d6eac7eda7a022e151c7d950324b5d6822ed105fa770bdc01af83b8dec0b7cdaf5824cbf17d146f010f75b1c321e9eda1a035036158817c2543cda894e27abeabea342d7796bf9a283044351fdd2220aad5b9c18cae8460ae613b399b45957204309d2173be120eec930af3c37aeae42f4dddde2830a192171f686c6d7465fc172df04c752093dc5c865b4726afc639068bf7758e213ca49abd5281935885fbfa2bbff32a6f61d88b6dfc367b041ea71f5cc1371792ac82ad74391b0ab0e25f5fb6c7dcc44be50cd36b4d41550aa22f79a59da6e88adec620b96800b56b0ca93a9ad1b0e4565419dfdf179720554f7435d556f29ca4b97126e2cd49cb2c760bbcd06736e8dde687c84a4ea428b2ea29a4c123f84f9df8bdbe0c2e436b3fee97a30db7b23a7f33f7764b678722c7f1af0fea1df7e06c610b9973b4b412d24cdadb665db3a8df1aacd9ca6125a5a292bea08f721c1156ddd9441b5ee99cd39b9d15b3e8c62bb04f1575817f73a12e6a312cfd500aa5c852a9bad767ac1bd3b2701f79630726b95aa5a2ca40a727e647de8c4ee4a8e8914a673c2c34f23cc8479ca12d0ed6779b444f86c7fac72aa4ced43148e48977b14702b3f6d751a3fb0b8a1d1afb68819fccf39a6346b727b2e527ffff78b121bed3a8b8d92a990e8d629ebaf0bf867d03ea31cdea4e472dff4e41129ef7f4889a3c571b861fe8eeb0b0bc9b0b3c490327fc01efa1d6a4a34cb47ba2b8d11112321935e32cb551c835980d50d7174f95f4c6dc7cda2e54cf4a02cd86d022befc49319d3e71892e5dd28b8531be362424f287c6fe6a9d3721370f6981f33a756b8456aaade5dd1d4599abc20ab49702621a997b1d8ea127261d3d5e1f9fe15b14a97618852793c17b3b87121b54b16fa1d5e2cc7ad97367222dbd0d62b8875d2c0618bb829451fae2cfd21251fe95a78dd1cd5de63dd4e72f1b20ec27759f8013e00506ac8b4badd30baa2e3a0154a4a9635e29efdcb36726171227a5eebae8988eee7ff92ff11999f05c494a514df1120b41a78209034566b3085648e92b6c9ae5f09e746f7b573982818894e3b319fea11bcdd9f22ab442c32705b1d44ca3edd56c34c58a580f4c99c0239bfab86fc2c33694bd308577290767996f43ab62266ba8440defd3ed5e16864120aceb0bace73448af63fd47211004d09ef3583e910afb49794fc716e534acb7556662df19908ff735cdf8a619206610a123532999fb108e4a5784b9e00ab379adbac994225e5d5cc6eb2bb7298f21657cb2b2a584259b0dcc342f693ff0ca33c3c2547697d02f111c20d7b0f7ce9af0669093ae82a9f25cb35780f6b23015d6079a526839f8fdd8f73dfe30147756e1bf6d79613b16d854317427e3c33a6279adc5e6a8996eba6fac6706249b774ce1424522d0996bdb733171c40bcb77c8bc4d4a6e34aad069cf7cc22e4727d890a29110d4a4df0af53e52c2c226525b94be0f7a2af0d5b34ed34f18b2872f6f3f05c166770c11f09fa26ae2c6dc0e09ebb0d2bd16bf4b1656c96de6156729ce57add6d0b42cf4ac2e55024eeb051894c2b9dbead058ea8a1305970922a23a684c9c21146f5f8a02804caf62e9289eba8a506e15d15fb6e9effd203c960721019b17555060269fca29746a54cb6dfdb6ec4b09dafb1d74b27dfe82484c372ae551af11e6d51c4d6e3ef34dde28d20db3dca1ee72ef7e5cb2e520fc973403021dae13c79d2cb0bea052b8aeda8989dae213540748c529f93c62b5df7d2e4724ce4dca7340f4431fcd8b15e7bf10108d2c6d4f7f01a3fa3729c5308a8e6c472092bad9b08673f606e5c849b5b42e076a8b564fe80d523d8299d698d4e539967fcec059f2cd839859d96c2656e416847de91acc6f13c530899821b8a57351a6077c80628a985802f5ee7aea1be32606162fe60ad183cf8156200793e0c3c4972a012e22fcd57b53a293b165cef7869dc4d947d5db68714090cd4e2bb0f23524b81c93dd6341ccca1a2ca41f78143935013083cd355cf4ffe65a807bba7fab8729d9ae102b5b0c760fc9d2f7ff15760a084163b044d83b955ae24b752de438d724e8448dc69e7e3dc35a0efb0a67dcdc24b3ff3e42c231e12076cb7154c620530f7ec3c18ad1e910f059d80e1a7a32d811872b00249d6e4b798edc0fafcbf48727a74dfe12f53fa0fcf343290e34460782742f731457f90c4981dfb118af6d472abae30c1fb6dc0488a83d0d72ebbf895b3914d9911189488d5664dea1d3d7e1b2acbcc77c2e5dbf624fb789c7953e8df8297a99864bc3678a6b0c8800d073972272077b199efe5886c6a464e01eb8375b05218336694a619eb6e88f1c73802261306712e32b8fb6d8893bd9fe7061bf84140affc3ce16445bf6e119b47a16b72c9fc1f1d888ac9d0fbeb3f054b711ba8bef866fabfcc3958fb2907f98e2eb07290796eb03c29e52dfb7b8f00cc6f791abde25e42693c634cc4501b951b423172a33fd3339105ba12b43edc5cc8111c4a7bc75df5e5e5c7fdb560a31d48cd7a69dac352dac521c79d2d0c58016e0fee67bdbbe9019ee7833ca92091caddc3937249c6acd5e706474bbb5c8de315c97c2c4c17920814eb0311c406600bf7c33e3b73f5e689bcf726efd82fcd2023bef31959df80eca8686020ab2c2a1842edf172fa7398d831b5d12e34c4f2ef666057e766403f4c58637ecd41736b5b579dd44a19987c97344764087b6c0f9bdf5f522e6205941442a2c64f819f3791c1416a721828433b8da6a721a57be867e65cd8e0c96a31d667e8d72e1e42025c1c905d30b005b5f09690a1e194e789471396d0cd6b8e81cd65c6e742e874e868c1d9bd49fdd393e3386bb2b73ec14d839e60ae430a18a7ecd72c8e72ceaad35b6cb1ce526e51001cbc38c66fa45daf41fc11a090e6c2543dcfb51fbcac780b98823b365cc93bdb6c345939d5921b0bd45c15e808df4c13da166a53d93088b66575200d72d8e615a58cbdad4730a348b773f61c362fe192b4a8be2581d0695173d3c78a26fbc1f98d0f6e79cb96cfff918d5868969744f1727ff7f53d3cd0eb1fae43337262e0bcb239761b87d3ae3ee66e5970a7c2eecc6e115541e551391e10923d5072cd24ed40860411c9dd15d30e88db9dea3d6b4ad36ec7d46e7ae09467b3779872393268d7522d5d12ceab54cb04dd87bd5801e7f2827e0d4a3f5280dbaf1c4b7234e0d279a508afd37198075a151c9a833a64dd40d945d994cdcf3321d245f704c320e9078e824811b5b1fc0e2f6b94c487568fd2a5f097f9fd6f27c0bef8e67202a08d47fbcb696a4f291cddbcee48cb5d05b7ed368213e55fd266738d982472453396d2bf37265ba7291dac9f146a672e6eb1e897a51851bb4579596964645f2e46ff667daa7635f71b6d891ca67b785ede0ddef2b80fdbf624b038a3bd90725a804b57328757f75d31198c1808145918fa768ba5f2e5d7e2e3d3fa106ccf212a644a9a9d9648b8858d0903c1584b6a69a7db9a9cd16e51fc59ccdfb1535072d754f25c9bf6b7e55c79c3c767689aaf5f90dfce3bc8b0b6a654b545354a86723ba53bb917a894c2774a3f309294482d53157f89b57f6fbce21c2d81fc8bce18c5403e306aa951bd7c83cf099b45b5901ec1bed7dd0cc7644becac75de3dd8725f6212f59376378f95e2fa3649ac8f42dda0af6075610def6ce39706983ce426a0e97cb7e0ed8f1f81a854e211f215131547b718530eb6a94d85d66a15a5a512098b23527ee368243261f9d07b1815bedf20d5432aea7683d91dc13411301a5b680a8c6b7114abd7d1623e86b4aa0bcb0eb3f32c8448d2a551726efa61453849045a4ce8c53ac4e1995c32a005c027c9b792fec60d964fcbe020fff82f7c045f7817f2ee373230333f8b1731d471d27b1bbd0660fa5d6a52020d3dde306feb4db35cf4c9fbc395e02ffa929408aad420bc15d05764c472a9edabc4823bd8e97246ec7ff3bfd7ab7aabdfb4cdc0439e8ba63e0e67a13a5fe70e5408655163b3721d2c06e9157ef9df290d7521120908f4a6caed3f76dd51d6ef8a84bac067f17269e62077556e99ec6d0826812028f334e049fd8588c29e9d7493165086905f725faa5c681229bf707cd2a04aca5f0783c7534b7a849b72d617a5c7722411f76cda02bd9116c190e0b618b78374ec423e3708ed89ccdcb93d66ea64cb07b87572d5437c8af9d9d633f98ebf20f0fe1e3363593e03f0b819ce819b6654f098732be6ffe21bf6bb1597eee91145b2b3bcddfc89e3cdafffe34a37627ce6cc2d7172e72dd62e51bdc625bf9c2d38ae2cceb7bdb6ffc8378a30aa2870c4e5f936e2046c72fe702b84b28253f0952a8529338c6c454fc6b9277f0a8423338c97fb8b723faac6cd2b46b386e25610c214fa4ce2cdc0b0fef21fd1afdec80cb96f88c04153b74425bbed8f1f832227beebdafdde7b050b13335c5c99da708e47fba65972eaf4dc04f80a52b7526608adef9d22af549bd49469bcbe7ca746338dc3baa27237847cff934421f5fd91e5cda30d8d31ea253e240ae6cdab5dca03339f663c72d21721c8e61466fd5fdef8dc0255ec323dc16a9b71d4296164065867d917ce3835a989d541c46c72206ae756d39b652c06d7c0ee8173baaaa5db8f3d493170723df0d268a057f011d0fd9a30baa02aa09f4ce9412b1c03e05063fc5945f98422824ff9ae12c54882ae9fb6fcef0c291c52e63edeaf52f2fc7203a47c6e7cb95b75027ec5c3dbedeeb4fe557244f729915260fbd9584c58b71684eff2257571336fb988ab77ef76a01e6807f71a04fea6d210d4a65c401100c9fba3b08f64fd2b0d6372986c170618ede0a2d5e990aa15295581fbb83cb4c0d886477c48d9d0584640917da4bd8656f25e6bdbe94b8fd9a3418f4912ddf19c9d90a8022f4b5572ea741a79ed9deb1b60e54a9a897b4f6ace34ab782b2255f6848556f2c159db62eeaaf56af3b7f5e650ee563379d37f290032a88bf48bd1d87bd17ff40904591ff6c456c06af115be009fb0ab238583df59b5f39ceb59b0fdf633a44fdd886b2c620f47a9fc4afd0888a9facbd681f65b81505763b73daffe95f5a60cfb1daa72c769683b0ff765df5710ce6b17075ab7c7c640f3629a6a47b8d7a0ac7508ba722f537a7500a54172bcabb2c0bc0e55bbaa9690a3e0560b7b2d4517e0d6cc886da8f6a606891978c2862ce40029fb66f19ae18113d5860cb429d4f7c98808f872e9f6ccb9dc48cd933c10fc3739fc21dc3bb19d94290a554915637e1438e0c86d9941db21e64a5f31390b0584af41aab4dcbd11f7228c14ebcb479098f32a8472f04a6daa4bb4eb952ab00019f4fb4a8ded7eb3f5290c076ddeb43432b4bd8e7234d20b4e58fb48c02f4c9b1e33fd1a834e5d5b400bf99e2a2d94621851bf947253af3d015514f860e93b43faaa74fb978973893f09e0321495bc2666aa7bf9724a655bc51eac55de8bbaa14d33bf167f8af5b24e7749b06dbde25de009a33272454970e5eedc34e5cbb43ab9a601f40ed2878460fa1c64f7934d1b59e6cc86265e817915c2cf3f232bedfcf20c27ef46d77802758301bf11a5b9ada69525a1729fd258c04a65e259b5e1dc7f5d9d8180fb8f1c0d7ad6c780a3762468a2295b722fc1ccb115377b11226fad53f361859a4423eada58928e8fec926ac27e23d5720ad8302a2d33d8073199237e09a614f3aed8fbd2bbd4d4a427f4ac9ed987b214a9fa5eba8298854d4e9b024275da06b9be377ca93c86e13b0b7255badc7d2b0f4312e700a17cbcd449e721d9186967c14cd3408bc824d402e30abf34b1bbbe72baf98c803e93323ea8aaa382c4ea9f5fa9a5906f292d93f5fb3cfaa601d74e314a36a8e02171ec2b7e746d6e5de3037dfb8710149d78b209b8f7f54baaaa7a72cf67aea8fc2071b5d84ccd881cade18616ebb8a651311e505457eb0c5e88ea720e03b02a4a9ab6415ea05a0dd6495abfa161489da4897626575bd21f9a92b6721b54fb55992f65f357e1446c473a36d1dd10de1b4824e3346d2b97423355e04fb86e143cfc61989f06efe276b90231b97471bee46b8b8ad3d54f28c4bb40f872fdae7ce7fdde78863d10093058fcf761c32cabbc0db031e2479b48916221d4721b0e278e38b9e85679d4c54db4664443ba9ed240932f2fd111777c22338df772e5459b92164db67a4786143ea4f7913bd968f8826363f5c2d9754e7f8f78ee1b76de844a9bb939fbee0970749f8b3f8e42a645ba152cf96427181fbede7f0c540fba160bad5a7cbe6979a4faf7dea334591c1b5397f73a09526e1aeb49392e507d88b9d1c33de8568bc03eb724b38e15524fb686bb7eca374dd08b3b043bf372fae9ba346457f211e8f755d1a0c93dd472bcdd665839d3942cf0384990cf8f722e4125d9378a60211b3d0161afb3077340b076faaf3e528225b5c722d16a323848b384945bd0e639e69df29c400bb3381d1f51bb0e87c0b826afbd69e37db1729c5e684b6bfd8959019f5b04267baa0810a984c65dcfdb8c49eaf332161bc172a5c557c9ef72aeed587b281003d4022ab1d879abb884c737a01b090c6af13b3509992c7695f8bf2204fb62c6449750f659031396e44006c54af7d3d92c76297278c9b715ed21c75a7694c3d99f5c011bed7d85cbee1c3b54e7d68d21c350c8514f4b64ce3862e0e8d40c6c544d443ac9addd75d2658a92bcde10674c510ea0722cf39d1fa28b569f1d7bef7972b12995191ee31485c035833e20c5eeaa72877279a1747ef38188b16dc5b74604a4f9515e9028e39e2330e5f9a01fa4d88b107222711bd5357a647546613c9ccf83d34f6e0ad2fab906d0ae50748c7eb61bb272718219abe8d7abdc593d7db792efbfdcabb4d9d109c8d6dd6bd01af33afac172569df026b90e232e1e0bfc9ba4a0657bb5fb6a11a1ddb6dd376ce52fcbe88e7261822209ef28989d2a1ebd8967cb362cb19692b7c57bb4e3cf2afd54f07f5b7295d1fead95335ab8324e205b91fe78cfa9beb9d01e119bd012beea8b94e8bc72baf5fc6a90c7d0044917b8a78b0528123fd8e1207ee41472f4a1ee67468236723cd954685bb7643e84cf7a243f61e44667e19f39548cc5d8d844ac5a6d69d572ac87f7457f04b92aa8964d6401460213c31798ca8812f323921a7bb57e652d721b1df6b3ecb244e59790c5d23000b83993c2076a6ec890b00e2f3cdf1204165ca7bc44ca2d508e66d2cfc53ffbafe0cf25f020dd2ad75c7266d9392c3ddb7a2a9151ec7dcc14d02ca6e033c49dc3107c32395efe4f993d802988c2a618da3a5da64c5b6b38761cae9564650329f7dd61aa0edf815398007b925a1c7ad03848725f537aca1abf60e08d586d26d1e11ada1698689fd0a95b363fc077a1d47a8972bf9fa45378d223128e64759a2afcc1ae5cd081cc03886395b9e1146fa264d830bdf9e714191664f0448ce69276fa3614102ac81b90067859cc57b3c5dfa40d72fef174327823ffb6fa4c2a9004704defa9dc052687bae7ca4fd305f2ab1ccd329efd2594b963afe9f6eecb0f40504e29c801148cf231b5c5013da1c0e6b52c29c9f70e2e8e1056e81f4dd9ade566999f5bcee7baadd5db96eb56dca917a1687252bfa07bf092c8cc3883ebd29b64f1edcadb892f38eb9c5f79e7e2621fb0bf1802eb2e2b287ba2e30d609eda66214988aa39c7c74aeea625ce5a8664128a023f7dd456f6f132fae485f4528a21cba32a6d198f322c079f1663009eecd1fb9a72ed09556f7fdaf300db7d99269f22f5f14bf411f1d55c1cc7563b09f9440ec90ce92927b8f3a3642c2f17cd4df04ab8d79c9bc498dadfbb1cf72b7a0df01e086f4fc0660f5238a2a39ca13ea262f92634952fdb46dbb0284dea94422320693e7219d2174aa1ee58dde4789c79f1c028a10cf8c1df971851462fa5adaf8956d2727ec4ce0bea8eb07c6aa827eb336945ca940c952dc7f8ec1ad137eb4bc0b554039f0780899758b9b38a04d3228b7d3d66711a0b30ab3c46c022f391b274ac8b55ee17b5fff24e5a5719b044b44aa72f13609795c6087dfb28dd712d4bff451e4bd7e7e7213b85461aa9becd7aed007b2487612e5f8aee9d14592ff14d44ec3a4044ff780293db3459956f30c8a62278b56d513f4856ce2d90c4e3e8d7e20d9f72b6776c9a19b3b9d34c96758c10c3088f132dd2dbad16810ee287ab6a35aff707cbc424587f5da830ca89674d553c3f51d54f585a25570eb3f6a6c67e8f25df721851c7ce893e948c631fd5560c0284f65e2252121d1d65271ee91cc9aabffe35f30348f9ac4b5e2968c98ccf0ed0e5b85bd613c35da7463cb43727bc36725072b5fb015622c3ae3f0c66333c98dff4821efc763c23c147471db9b7075015f4278a039c04babda81264dfcb367ab175ef49b1d8b6076c04e2cd87b03a67369772309501d951c4d8f74917f73962042ed7cf470b2f784075a7c7732e085df0b972dea38e7ad3a96cc6bdab126fe8deded1bc8be33ed5fa16d71101b8c23554c20dda0030650d7c29333918e924a90c548c973367f1b2cf3f9c46068da3eab0a372a20d916ce2db4acab2f08256e27e7bdebe61bce4bc765a62f5363b6f6b7762341a596e7cd114ea70e84fb5b3c13b21cc20c79b14a1dff74a4192b787e84c1c3ca0f2054de49376a0f2aeddd3cff8ebf69d923845b23980cb9ff17a8648c76a32d0be5d8f28bb17cdef1e870d6a2e271a9d9d2a67d832d1bcdd4e69946462235d7377cf3e024ae8dac82ced7e5ef82c1c62d17d94e9cf0366684d5340c1439c4c70357bd2e12d182288e583a3324d1a203bd0cdc23310156b56249f25b18dda088e373ba2d1db1d9bc1a0e6700672cf654ade1bf34d74b8dd4e61f66c50bb783141149786d67811a75aec3b855b49d1fcc7445614d217ebf0f487ada0b11b78722b485cd8324817e7ff2c8a9c050485a3fe611da62d3201586b7c49d0f77f614a8a6c2a1404d7847031f575e750cc4c7a13a48343cacd7c6b8baa7c5f88baec72c3d7549cb9606e8763f59537d32615c7437b7573270fa1d58a7ed2d5827265504ae75917623a4d9be1003d481c891f1a765cdc7062501e498418c9dd29933d0b17db8748b942ca08f8a83a0adc973af0d7ade40851e792f83b6a0dd94f281c72794693fa021bed3326fd5cdd64c1f4587ff96c9dc2f994999505d7c106e05a7286dfa589f78c9b11cb44647408332f4bf718b3a49b0e5e0f818fa3c8aae5bf72e8a939d401cbee1699b4566c4cebef4ff4a1a6b3e06654d2354b81a5ef97d9720703868045c7455431e148510bf3b322d30a31590d778dcdf0da1e440d59a2722de2b5fa81bf32d3fc8fbedf014b9a461a05c1ced1acbb8118d2f9725ad754725890fd6890da30ed1cf0c64cafcd12ec4a03303b3c3700604d07580418f8e828aaa4410e942e39ae3283edc6748c35558adbf014ff182bd253e7453668f8380902876fe9d9d2f23d736a4f2895490760d173b8b0d85acde0bff9ad12c58441720f08c37d0a5a7d4466e9511307c9b706bfeb6c8f6a929aae4e68cc32bb034128927f9833c66abc0d4ec4927913abd605e77e39d844da20cbd1f025ce72985472fa3c53502dabe6a81ff8a8169c37d7df44239a8bbcb6466f1fefd17b76611772294bffc6d5f3dfcb900e2bd0cb197e68da3f5a454c77737eacc3b21740eaa03a616f4818b9dbed89defd3bb498b9ebd27fa040a7747f5dc661d3d527afc19c72c2a3421e72986f9fd209ea0838e05f5690f354b1a5950d07ea0729e81e885d72038d234b03ab995bd6c79063906e8a3afe3d1fb5e6c06e2ad232fea242ed337224d9dc268b753628e9417af261d2c169e3f247239b8e341b685299ba61b68246df7a4e62d39504a75760e65dad4eaf164535fbda04d9abbd9faf15f9287ef6131d66140f3e4c54086a423c6bba9b445d04c2058c4a87b7f190b8ab4a57f54972b5ea1b9250977cea8f7a160fc3ed92b6352deb6c8414a6e27a0a0e39871c1d72084ea3ffea76aadd9aacc17105cba75d03dab4d60742b73eeaa0914069dee172be3b3070913e318bbc69aca9a659bbf3e2c2fc8f44ada0f76aa5a9924948e672a22cb3744a222b34c3fa2d44b1c4a6bf3179494612880d66d6d45a5e488576721c9b7340ed3c82d4141783e4c03373b7e6da5f6f85f4061703313913d3b7965cd7ae14fd342f7edc284fc34bb869338479a14ab813ecb99f72d879745b4505443fcd657c1a8f23e90e77a74429b68cd5dff7e3f0a940367fb5ed2de0ae9cb072f87db91e75bedd55416d11c07ad641d37924370d9d63cecc93e9e0ee2e1aa7725428bf3b5fcbd81048586e86062c1840194d027a370fb220f4676646ab701972c9cf87f671a2cfdaf2775df797a6d3363612e9129ede30f84dca12c8000d1e72ca818ec5b0d17733b3d55f2aaeb4ec7ea1f3afb30249316099055371df04ca72c3d8feefb24a3c5366982223d69aa118e995fa6a9c40610a0a8a30855c6bbe72684173371aed2f1cfb95f0d553b618df52d631e4c7de021c5f45d4dd42a8a972baaef3821cfb822eabfd5ce1bff466c80619e4dbbadbf90485fb1a1dbe3150440fd171c65cf52ad033324d033c499b775ce29d9e25743edf47de725a20c6d644439c2406a2a368a440e477fae25523e396d58c02c890396dd84201e49099ae03343c8d36f3c2b2c84e8e79939ab7743122f3f2b5adcbccabc5e1cd5cd9085c240391ac8d3d21b62174a0513c3d99532d212fbd48e6ee612187d95101ab223764a4f6d8fa22fdc40e42d23af83061143e23e165bff7db9895ab5c2c5a9d9f7072a83f302acb86eb932c5aa329d08330bbfb0740512a63cdf231203379c0d32d0814d7df337a047b3ebd78b5eaf6bdb4645bb02d0c6eb2f9cd8bf6efdc18fda872b21ef6109ef68c2afb0466b9f4b84cdfcf4fb189e3134a248349377a1cd3914c8908cc879ffeb761c3a155948efe9002ef65577fad61c5dfaedc2858bdc92d5d6e620d1ed531cb597db51175673210b41c1aef741a0ac657d6eaa5367a06c1728eec1d9347496386c507c998064ba08b2b0a8fb72bd0199e75229fdee5be1b72a2e6c7b8aa594b9b6f7e2ab6e532afbfa4cf3b9747a1263b70f256062337c814b42fccfcaee41f2a7139506c320a89e63961e3fe4440122332a772e46158b37267abc082d7379a061b1d2dbd8e70b96d1e685e4f08f2f28fe9b24c9afc67f22dcd549ab30d24bc3fd665c4771d0156d8d6d97d7ab0c7ed4eed47881bcf3525599e460287bed6a1464d328f3158713da037da2d37ba6d81aae5abba8293378f72894cc6e832e226bc4d37332bd8efb44cf14c0a0366bd8f17fadb75c89839dd4d8f8d4c4a7e253fb8a07dc49945ea06ad126775b6e9c54c8202251200827e067295661c51797151ac570562487f297b6cea6f78f521acc92052798a048d71b42ea9ea4f32a6ceaf1675ca310afaf4d160dbf4b75658366d154ea4165b4ecefe7214365beb9db2487ad94a8c432a596c87cddcf5e0c3dd8d9c2899674a6bf56d1a00d88a43d79e7d9dbeabd490cc203c30c324f2d28adeeb21b39a846ce816db72b282b153633f15a45eaf5e94b27769ebafb92737e63f6f87708e3ae1a8ec4e72cd1ad5f3c830621cad9b5d51496e23149740fe21b6bb2e0e3b8f4eec844c1b1f918ca622ea25f89d64189a676fe402ad6aeec6063af4fa136e9272d604369a3722878aaa30d87be0780d5ec3d832a5679b12fce85afcb24119f9285d33483033fa986ced30f6ae9a3a876bf3babea9b6ca80cb097b56d62732b755b50647fa3a384c6a84218b871f9675b60eddcdca08b059d3b04cd385fb0890cd7bc8939e30a81828ab67b3bd2bce0afbdc6557528559140385dea1290c5e5faea01a12563931358433708c74d01490a619aa71e0d50ff4e0ebd2807dcf08b3fbdc7756485cca365eae503cf2f4288117b9787ca3926cda4bdab655e833d606a861c8524572d48ec87c1cc34930638360e0f2c10d260745c1345c1cd612adadc3ad98576272088aef84bdcaf0e69dc3dca0907160945a58e1b57109ee472d2b4d9f78d6c07210cc7683599fb8c504360a2c7b2e491c9708608a7cf08a87c3507d7310609851c7f3769475eae225243ce253cf517ad4655c295e44607a3fa0a7625d405bf005bf8a87cd211a39132333c921964b13efbd4c2d30b5a36ff514424a1980cd426c384d1c2ab88c55d2f2a7bc52df2c5339075118cbafb626692b29e5aa69999e72ec8856a49bf0a0ac461b6494300f3f43501b1554ed5d641ec161a0d84711af72cab34fd80c9bb28ade466955c8c6a2af8f273c59cc844221576b8365857fce6ea1060e5cab055b68bc03695cca5627c4724923ea3182a7ddccc5d8f798a79972197a585c27a5835ca585d58a3a91343e2d1a7bf13fa270777c4f129c9365eb72c7a58b4075885509113f5ba4ddcde19889ea04d888c24c1c04f4b380793f4a3a6c10f12ba0679e21751e2b9b978c9d3b21af081bf3d61d6aa692ea542a16a372242c239dadece249022cd676267b97ffccbedf51eb37f9ef14fe0a0d063d617241ceda444bb6e689f410c77f1314b8306b6e17aff5660298d23fa1ef82abb372d7900aeb798d8f117950119d00903fc72cf8e54c0e7893217090594eb433bd50610901a2be1290583ac57f850399caf71af71feb51318483ec553d2c4177b272dcda6b071cf0d3696bd71e18a1b46c4f8797cc48919ee2f06dc3fd4fb8f84e729679884b71a647e09de864f4f48d1739509107656ed111bb50685f10203813550ba7510af514d9949a524eae31345ccb0402b21fc09f7e63c99d739de5b54d4a063a922babfbcdad44ec5e4cfe9d6bc4cb8a728a07d226247e5804934d2891726074575291d44cfaa838f9790bc48e7f8bf8c8d0e16a294da8107780128dc2720a5cb82ace4c183cb1ca0b1d41c4534f01a51c865a33048c68aad01956d1b0725a91c3d9c3c93885506cb05468cd6b0fd99b587440e5bbadda5ab4945acd8c5abc2a820fbc87a3571221f00fe2c7f0dbad5604e662ea620a1afeef93cf6838189cfd48bf8b89edf42c37dacd03deced067596b2cee336047ace17d2ecf0b3a72430d001eb2c0c66d259875aa5c82d1f9e69373c2ad88d18c44bb4ef46806fa7260100e2c5c561790ecab730211b3f1958c46227ebc4ab6807a75cefb3d20a1158145338342f0d0f73187b8f918132c97f3b7fd65c3135669f167d25a34f4372cdcdf6bb754c2879fe62d4c47f5965d5fc21876860cf174228b246aca012eef72ee2b622a80726c338e9d40a8802ba58e5ff36b9b7a2d5bff8b5a8613a81281720586e31f13fa93423a0a2ae27ddc3ec1bb39cdaa07248a3e78a63074eb00ed082ee65ddff42503d99d6f8a2c4de82c6c9fc756afdd7657135255322cc195e5720401c9dfed6057857097957697811e685a69ccb1691301c0b1d31c4b33007052746e2fecebbf931bdc32749c6b7d213924e59c461f68a407dd7ca9e18c63b172b408c3c4d12b25dde7d7456aa5b495343e386386292893b8fabe16a6a4ef1f7221d3ae128cfbb48ba3292def755dfa73b6ab3044f01919766b276eb0bf176e72a23ec6e2659085d9b41c349f9331b4c5783acc510539c455c9e2b5fffa096414252a2f924e48f73f80559d76de7a758807438533547d107b96599098ea2f57399dd7b5ac098c098c0efb9671d35f33d6e394e87be82e5242141ceede6abb2209f213f33ec23cf8134bf5375b85fa3662eeadb8832ac8794419e1232de79aaf0feb94787951faccbd532c455a8520e9aa7174b0948195b410c8d2a55aab5029029e8b59b1bd2ed2740b91ee221dd685ced4f2cb3d20f29455eeb621ea94d97d725117e0154095286ac8896e4d03da0dab759f7e80d8cb35e0c4b68d37221354722b858632b14865a64d628018daff371f0af56cce63fe72bff23bc61b894ca90ad00c38b574bba4c3f535537ed55ca980bdb561d101d837e48f1e0d26879d0972785c4fde56d85b283f65f503bbf1950095db058cc7877638cda41b881ea1385265d011938e13f5c83127d03ab9e6bf3a681588c4ffaeb6553cdec5f577f2dc3b3e8c52c3afc2f70c1b815e5d5798a5d7418c71a81b8e61a2c0fe49aa1bfa7572c59ed673748f1236803ddc574d14f150ba5d948201d4afcb998737246302b41fdae14f13184a070e83d77b8aa04f23cd27337c2ce5e9f03d7fb1da3104b97c644acf68451fb065e1092f9b918f7e0d8537eb148c84cc2e67549fcf1f6fcfc5721a2ca7160ff16ec85c0519dbf3a4ede77bb2595f067dd13932f3439cf14b9d7223815669173bfd56b4f430715b9fac36c21ffb49c8b01c0548c16734e5c39b2fb7f727aae1498030c20f7aa057f86f66d48271158877361843a1f5bc7702e37221d592c6d724853d0bb148979be3df4a165b62e345902c6e797dbccba007397229dc5b5f8d4beb75b30f266e61cb072a84080a46b6a69aaab8957b5cc112b9729f3bc358a8a351a40e2be7f767ffc78999ad3d6d7bc6cbb59ec38386e2cb4f72d7cc21d076ec549a8aeba3526f3d02c5b4516dde576b0eb7ee12b5438fffed7250b602544a273326c5a4e2db1fc4586d86332899ffa0a1dec8e296fbcc86663db845a35ce28b80b814f0c4558ce9cead6b61994a217548a7e8a48536fac08972b440f790af2b296d046f9f01d5c20f3fff796d954f9bba0efa5779bda40db07213f43835ec85df7ab981a0251d606957376361411a6117574642aaa6af0d6351d2b1fb075485e4eeb4dc6321adf3d872d9984657c74aaa162416f88c8f878672aa3f3d09262d247e9207611c647f69760cd2626f71c35a62b762792bb0c13972092e513337c5d30ee6aa13496a2764d66ecf29709bc529f1041cdb50e2b07b1ee8885c7e30d36500e138addc55579677f3578041869a52b5dfa661904f34111ce20871112d8c7903a219d3c606711abd9515c21d8cc6121e990b9696e8337472550378db37b8fe62fc4002ef3c554a0f124476be0d07c592953e41b92c3bd0726dc626722ca58ca9b031012078ed7b7b96b5c048f7449f67bea14d6fd719791b01d454da67a2e24e4f6c50f8879462fa7848df0fff84e8f9880449962b09177250bd2538c2f44a26392c1909a58890e4fafd59b07af938822a449e185d8f87726366d16252af3515ffc2c764ab6d9116aea4fcb41e164c132e717890cdecbf3b57129a1029109fd119912957a6515bfa38319bce06e4622679ba518bf720aa361f966758cb123162981517b5704df97d3bc6f2c7631674c13cb31b1ebd42db72d9c4007fb8b1cd457b1293e2567bc53deb0484040ccd7412e724ce755e0c140ab8f5a63d90ca7781ba94fab133ba5041934b560f302c4b0d4cae592606240972247a64247d336381ba5cdca42a2680d5efe8b682a1932cfd02ce9816f9d36508226ce1465ec1209d9eaec417cb65a9e3902bc1ed192a4ef232feac23a6bc7272b028b29894420d0efb525098197ab534b1ff00189c535d4f32ed77a7ac31547241c4149d24006932d52ef750f4ec27dc21c1697436894d569533b9c233a7e47283017957e27c53fe8fff027d91be58a8686f3ed1ecf40cd3357a9c021a9f0372a837ebcc98a93f8cdba7b68ce9e7ed98e49e8edd3c49d12c185f94e28726b672c03c5d8064a83fe202accd735664a79a2dff0d3283b2f7ac19a933d8c7ef2d20e93873bd7e2f966a5117fb8fb92a9e03ef64f8727905b2d914c353ce1913ea728b36b081ecf3d74690695d690f4e8149f342887294fbab10a21c65d5c3d75a33dbc857a33d70523a82e9b73bbd4279ac94aed7aa9a8e1089e6d9886cb90a4c4c93e0baea94dcb9da561f858ec52596e3a454db793cd83c8593ff9aa3b3e92e2a0c1e46caa2e469d1f1a2fc930f42f7e0cf31f1dab1fe25863f0dee45cc7c8872ad26f94d0bd6ed3f664c3446e9ca8e537af6e10f974e6556983781a17033747296fbb0ff82c4837dee1f643f9aa83e43742892bb9537397b6d81b7aed8b5a77280d8b538a9ceb1ea14b5de1bfa326139699fffc6404bb3cd870ff7f8062be834e5f6d125f30582fd8aba585d376620bb56beb3cb514f65d19b873530c84ac23d32fb83150e585254fd59e6af210c54a13617542fd6d3a44c273c7a94a0915472411190f616ca33268b5e9488011f86fa3ef1426bfeb202d754199e2bc08bd971c69809b005d864a76a5c746f153c61a3fa5135ef1e9657b74855b2ade5033d6d338fbef31180132e49cf6f749cf2889646354be9a668b5f76a8f6ca678f5ae72037074b2113acb35f73dd34ab0e00b63d169fe4e2f8347de9ba92c83f9049772a74b8ca2322f0670a21a51497069521d8889d68b7c0e34a3b31273207d83e430e537bada6d21286563863f61ecd3074b6f1448998fa6e3a34a0864bd6441a27289013867fca9f10b431dd8aa496d0d55e571f423d5def621bd2d74da2163e772f148ddb1a3a51ee38150adcadc7612aa99bf7cae8e51561661847f0dfdc76f315ea7123d765811a1bcbe6d80c4ef19742b69ca19c969c6c8d95638433f76d172169908df7d80aa4f28085479ffa69f226e60d1cb392e6c6a49cc87b43382df7207eeaa34549812da5cd0618037d1df5389c1f60fd298ffdf787abf6de2ae7a72a9afe09568ce13c82f6241b2711f40a36679e46417523f68c372a9312ef372721b6e65e5e84c91edf1c5845d03dd49950a347e05ac76324ed42fb1a90f25a972ba865c31de7d91f835da5e5afe4413b015b0cec652771af408dc37d827789d7229140a1c7f8cf5155cb2933079bdf2f244249a0806f3ac2ab474a73c851845721676eefe0118feaa2375f8433f69c65a97b1d2935ed2bd67afb46fa1f1fc4414c157a1dd7685f81f0575e8783b645dcf384eebf57aba1344b5370015ca7c217298a2d8ccf1c9b20c79951e7b1aee6dbb43565943b5f5a1eb25fc442c38035172aef8f066c7fff339cafc3fda2289f756465d8c50acadb06de1e9b9ebbedffb7238422d44b2b94b4447108f592f4af74c04a0c65226174e4b97f85ad550818a4d9cb0ca299aa7bcade94e644950896c4cf8e32128f1f631c0d8752db5bf110672e95bbcd502fcd2be0d477390da33608e7ef367752c361c4d6ee44a42f5f94272f182c56c2cc61a2411894488baf52da0472997b185ae5a33c21bacf4ef969135d743e8528744962e800f917fc20f6fa70cf6452e90821b47f8fbee6bbeabe5722d8c02f939ac2a6c034b787ac466a402e3bbfe54141cd36a2e1cfd164bd08a0ef3eb3f9a47699f8dc23ed80059c629897ae033534979e68445a902593bea5c2e61fbac10a081c0f8332ecac5ea24edefb370394a01c70d0ba6c4fc4c7ed10d7243df972cd1f17d3d8d1633a5c537eb4c61c549a861736a1d1440f2010432e408aaf5f2421593d4cb46b4d2ab4ad8dcf9495aada9e9ad635e1daae4500cc783582e288efa558ddbe3baecab101af7d447ffcd02b545e4865bce7d58db8accf572e622cc513016c65a502c6a4065863990f721e03607774afc7381bd23e1fce672886fda244d2384e90b9abda6f9d95cb42483e743cfc26a73c12b74230dab0d1c2479e95a5154223d409cd087e7732c352c665269c0929490fc8f9ec3c9f7cf18912086a7d84a058750dd27dc1659da6358cc9a13e4722e3fbda520a4d5ec767206b76b3a21c92e14a71681700869df914659e6ca2ffb04219b1fe4de2f672772e5bc3a10a5ead57c569e2b7c408f50772963fb0a67bf828cff422ef7f19ae472d275f7c5d33e2664d38cd62fa27f69d11171c476a15eb5fa506b3316c67b6c5aedc02f96d45312fdc5a8892f9a45c15fc6e4d68f8560db6c1bbf4cb18ad60a6f235b8014d4f855ea166789a7f889b4470c23b6d1ba7904818346d1e6d476147249957b08fba7c5a27666b6ed3542cba70e849c82f9c047c07d61d6c125809e72fdf556c6bf65c0336371439fddbafbf7f16a909b8b918eb4f89b50785a4d967282962974deca265b66d565df64d3231b15a698360c983044f3dd79750f6fd572d82076aff6b35dba500388c9f7352637dcbff00a179c1a434b8d97e32e730557f47f67cbd5259084fa656dac8822764965c71ef0d80233eede643e98ed4a4d728d0fa23af53e6dfe9057105005baee9cc0bd19f20d0662d2d8085f7dd4d8b472e1a8aca9a25cca864b2d8bfc0fab14432e29de2b91d1d8e37c6cca38bf6bf2724914c91a29e490affe4526d023f6b1e12cb8844356d3b047b3a8fa68e6e0a26871999a65ca151108fad232556f02e7f9fa3b164e7bebe7db0e8f9f7930a40372752d1e6d51379533f5d2a37a09d47de0a37e16d067dc8da06bba3787796a2c72b26c5170766ce0e37fdf5fef18c755b23fc1498b9c6855d412b33a2153f6815846598ca41818e4c13ee1a06277c53ec2b487cdacd6999029d6ab205561f2171bbfecd389b7b8e37a5b4933caf1114e18a84844ec552e871ed6d9118a99818a72e653dfdbca73696c6d36b26955775bf15a4dee05851a8762d4eceb978226811b98a6312c6790faf57383c19bbdf9eca8406d45cf867c4ece67459b577fd4f13722479c06a9f9f1b4770e04c16457abfb0d513ebcc1cdb1985e08bc6d3ed47f2689f1307cd56f7fa1812b9af12f0e047a50a05faf782a49152e0d0d1a527a966f0365c470c0510805e00762dc71a1fe187a3d40c883556c016e14b2efd2ea2772e84e65bec0d34b4d7ed1c2bad4da1f946fcc913124bdfdbc50ffd04cc89bf87253d23ee021950f76cb4629c2a88950f29eccba5210a16e670fb9751194bc8928a6eb9a353e07d6f4217ac79cfe513f7fc10a9f031911caca89673faf78025672969e87ba92f15412868a30682eb3d9ca14c4d740720c45573938052bca37b271044b6e14fb6e22e8af081498807110021d30e34b7600c722af814209ef0dea72d139fe38ef2d3af04ce31c37be8bbfbe653e78a1cc72df53dd52175efaba8a3bcd7a7b8169f668bacca34d941445a62ae3887dedeb0a0b2531a734823167de728446bd97720fc7d70dfe34d8c638aec668e9feaf4df44bb41c6a2269ba97e56f8c19e9ba68e44687b1c40de652012065a3e16db561ee4b9a4e745ca8920c2451ea0dd320b43f5491c2ceecda998140360e67840280bc9757e0a5e665c01fd0638cdd4ded03618fbf00a839ce7c0f0b7b1724c36290b1445352d584f0362e50728da57f652180f393371bdcd1e3e5c92fd02b7f1467c64bc6a8eb58c1b791f809c13c3fcf1a892da25fcc29e31e185ab73cdfe8f17d986193e51a77dbb6327b2a97fc9255391e43cefff4df5ffa3381b0baf3472f68a0ddd9adff8f8e6a1830678c0680de41a5011cf9b10d968f371f3d71eb0f1bf1a37c39cbef0f85ecbc4472f983189ea7e3663ed3073de1fa6af6d765158e419f51ac3cb23b83f129498672e1b0c6953fb3865a0ecb014b00c58ce9f192678bce9aa0882a7a125c21b08f726978e1b115d8a12c81a06de97c6522b676961a524115a8d1dab75e71ac4d037223b0e2fa6416560c7e1124e0edb0c08075bf5c2c659c7a1743ee8b0138a3b63bb4cb0dd0c5da595ead8ae825c4e8fc50fbbee65d4ac785461ab7800b3674db720b826cc037ee05e62d16c31db177335925ee16e375c8e891b5d0a2bb51f63672aa6839c9a41a8fa5b3a04aa7d5b0b059d861e9a3f02239f2d6528dcb4ca13c726e4782f1ce2195f56dfa68632ec3ec1b858d9b468149d0c60baa75046cc21172ac00dd16e6b9470dcbf79edbb17c953f5f16b7ea553783785d7610f46f1f467279e1fadce305ed8e4ed764344ab7211ead9ada4129c244d3faa04859c5ff561b5754c67bc0ce244a0ae409813532c7d6b5cde343dc7e903a9827745aef4479394bac5a75d4a77eeba185d9d731fa6d5eda4d679349ad528302705971d0ac1e296b178d3d3a821cff6c5727ba600d8da15554055154cada24d2305d731a7e800a74d4cbaa3d4970b4b236f79ed3357714ec513be5aa9841bf2b8d223792bbd072d89f8d11f1cbf6a28de0593dea1a98e30068cead00635b1beefaab6a55c5372b0328ec00877bca6fe0f2e9e762820fab16f8b1e7a2391d65c25fb5b6e0e44f721416a5b30cd150a0cb47310ae07cc0c9559eb18597bed259eea1e6822077ae727cfebe2fa93304e1ad8563ac1aaf4026d227b536c2c7c61f445b2f720159bf5e255191f0b58ab65608e23eed148f1489fd7c4464757ca2427e963d96742198723e941dd44d6b6f1d15efd8d5cf296b310b54ee465d4ed6ebc860835ac39f4672e559c00472b48944686e15601e1a6887d186df42450b0a158b1354bb809ea3729c33b5d7ef5df446e0756216926e50f73715280ebea2ef3c2bda7f49bb93b77254370f1ba986c8d5e199997f6c872a4b84feb85c4734d7d7c95f7aeb42f9d551be48a71eba899f144b13a9eb6ca5f96e6467a199bb37bb06d9f338986fa14a3851c37e201324582304ac8944084c6e2a1b5f5e08de0abff328d99ecc51512b72b3cc1c90deab1b138ff59fdea471bf9e957a1f1ee14064b6c264ef53790c321ca9d190c21f52610b15788f2d3502e8aedb33ece7bbba1a36b69154014b7b63720ae3d832be2cfe2d8a211974b849263568307c59ebe5b795f8b35df56cc0c11b6cccb76663942d35c972d4b7014378e71c10b6182916da1355f2c90df44fba72ba5441dda3235e7c3250b7314f5e4e408f2c47b99c7ec0dfba934496644162720131368f4dd948e1ed1ac002d91144d50df06410c70e1a90bd78deb33639991b6ce1ee330f5c8cf462e0a7ba705f33568e832bbf694a37648552af9800b5de72937c08745237387705789e539b7b26b129ab1a69c6ccb36c579e122eff2fc7720ed57b8d741f4914314e3135ccc1d518aa81fc239d79f25e91bf544080081d728c499c439af0bddc448a0ac64491b1d8c7edfd36bfd9d0eed5d8b3b4fb20f172ae869bfd708c8807b0ae00ac82be93d4182964a79cfdb0ddce914828b73e60679cf616076406682dcfc143bce36de7970ba203c6ab0ab2546e04eda89bcf7141165e915edc393fd827dd574ced2b1d85c4bbdbfc7b792cfc84e119e9275fd64d45437e35e2d29ab25a85f7200208cac2412c8a1cd0c58ea25f07f4af03f0647292db18b0560db9434701afaa1a1dc324f42973acf10c9d09f9e8ca5b69becd0e47fb027a95815ca25ffb552ca4b30ecf1a87519373bc9c6eae082d49f1b60f62947a477eb723c1a1d2fa3b2ab0696dda70b6620209a0d2ae842ae13c7fc3bb0f290af2f517bf23492a7ca92650b8a671961d0e2f218d559f790db3794a57ad51a6213ccf1b5fc7e25a99fec01d049d7d90303b351901c27e1ca8d52b218cce358a25026faec27a2cf5c2b0fd16643d388d1c52f6e97debc3c9f780dfb38c83044b448a55c7a279ab8e05082f5f7ffd1b1585468185778a41198663fa35d11d729e3a3c95eaa75ee3cac36f4f2bb39da3e46d6d3d542cd42856c858d79ed39572cf81f4c0e891350291dd5382c01a208f79d7e75d2b6c49411580936fdda6d47230f444a8ca012f872d3a00ab1d5795355ffc8ff6825e9bd09fe762e83d7e69056e9e9797224bde7b7d7f6d080d8d5e0e91ca27ee88218c1c092ccbefd52c2e72a6db339d97273373b3af79ff99554e1952f6e8e6126fdd04faea4c366b3c9372483ed8916bd31dc44b8d7a1bf1e8439488df45a4e6db32abb3994f2290720f0d28c511fd305a1e84b865dc8e48508232997452e9a570a77ce6950484fe1dce15bb2d8fb7ad5fbff487cd6f084add8716084496b674851eabf213babce5b72b1659454362127cae2b87c0c2bf3297a5f1202650e2231d38e8b963e197c1b1ea4d5f91074cebcb8ea3122fbcc0507cbc7bbdc0027b8c3c26f25f9b9b654bf2887233113985c09847b278b39bb9ee16e1f57c7df111863c650e955ca31abb68ac723b7ee6f933626613f910d9bf6ed906f6a99314094e32ee30af53ed2c52a93972c4ef274245abc84965d726c5d30835259bb1224b1c5f7f63bd553d65ffd11d72b3ff0377c2613835af4423c3e10c60439b2595512d59994a73680d34d80aed5428cf155456c4b4b464f95c6ea0fa2d16bb1ee066415dc3c7e210d77dc2401b72c0af7be59dff647b7dd523e6313b9d6bcb36d42a9d83a763b11fe470b307c672cc4e99e0a672b87cf59a33c1d21ae829e53588d8b1315c3caaea0be81473461f27b27401b4051caf31e242e55b60e5ef8fbb001f7ede6eb6e376ac02dc79c77221d2aaf4ca6c93b4ca3ba7f19948c15d1ecae1f7707285539e4e66f846cc0601e5ea045c7ed664efc6f478ac2513782197957447e8e92b5dfe33aaee02e5f1729763529377fa1502cb6b1fbf01fc479f628a888efb6e8a51174aefabc84f647286eb0d8d9a75295292d5faf410b5fb0f11aa540b4a2c11e3ade359eab02e0e4edfc3117d6c410072b5501dd5ce6e19d32d905068039e34b6d7d2b33d35a8c32455f03d852627c89cd1e3a9263b440a8a62412cd5315a2328cdea68caa81ff97281d8178c3ab7c970d31c00a7f392c0993205b6ae8431d605a48a4826c80ab2267ee5252332f1bd76a81cea7095ca0233ef84533efffa374d5f73601731428972e83da9175419dd268cb1ee2c03f520a7bc19ff97d1c1ca42f811a87ef1fd60560d9af8197df37821097f96503d029ec2bd95038d3afd69d97f864b169037a02137d9e48568b96e08ffe9960ee466c1aab639380ed39a7ce9110fda05349ea072f86475be7a28acb6fa854111099ddc8e428a071359f6e6caab0b92037dfd6572f4473daed4e65d87b9dede519521ab271d38c951aa75e734ebd986b2e89a940be92ee4edbe5b98e739512b6b344fa3f5d2ff836c9d1ebaeed463d700ce26767205767e7df42eef675309f7036211ac12378b4b900a2d8f4a25a2b40313d21b722f19ea72d1196f737ac1e5df0c44e8548572dcce6be8adbbb51e8d1822d6e472701cdadaaaf0385134630f0bbb51e65bfff76e5272031db1d3bde6d3a6810421292a7fe805e10acde5bca0cb29a02e82a97ad02d8251388069cb723aa4aea2726eeb736e0b826324c0e3cbbe3d734080e173f1e1f1cf1c861dc5200f3a97d651eeaa0d6f7ddabc212be67be103dcb3e3d295dcd1e1ff94b343f67b78f29a53344cac07db3f2e6e91bfefb74573d72dfe2fd95632ed54fae2a788fb7bf2c8c205eb2a0f73bb3dd8c8b87c28ace9ea4ddff5bcc1c74ad27bec0419b33da7ab016841f3883373c099338737d548d48343f97995c51237685ac93421671a02819d72f8ea58219a7e6579eede4da974189ceff427e3f7e50451593f01c3504fd3e7602c602e352857d1374114210238acf8a3fac64bb2c5398dc153ead18002a3aa725f612393b40e0e8e5dcd45a888b799ca20183007a897fe526b82f286fef7475661a4fd72ba95eee98b2998ce22d977f3238d66d92f09579ee9a891df4fc1ab7244f581e1c4ff2bf5f08102bfd7d8530a6edb31642390cd928e47fddcf9b7c872f90c4c10da755b19e3379bd6ea096eb183421898dca918c0274e039988f861726ad1c02fcce18b78762f67096a0d2556643d477aa43a5505c7d5a572713f54727186ac0dee702c617c94a96941fad534323f525a24bda02030bf894af3585e567d7ed9f554e28c65b95c0bc00962377cdf1641180d0939bed674f6b36527381efe7ed28d6e8c3076208586b1a3b423b53b833f43564893b2e0f23571fb050572c4503974f9d1df8c99ce9979d3fab09149562bd79ca2b0faab169d3f93e6587208ad490a12077ee870437a18932d530a51192d370d082cf5a5273e0df002510a6537df012382cbe0f1142963303ed2554c1c6e1c8e63a4d7edab547b7569bd5742864e7f86ffb1462e86fb571068c1164fae2f048c8959ea8a0c931e2d29735bb92a5f721542c87357b07da2ade2010d3d38f3924e534378f63f950fc9f5597297bfd2b74de423dfc90c1690ed6486a0d336ac2ddcad953b92aa6b0d57ab833549b43ee92236e5ff43f217c4476afa4ee46623a0d7c2b8aaaeb590ad83531e728bc92542bfae722b687cc5d58a920c3ee385fa02a258defdf6ddc7f17aedee6e8faee94c52d2951d797daacba6c73430d092457e4c3612871c13699fc90de8722d9bdf6c2cda67cb3685ac192c5f49706b5862ab939de588167a6309e91ff83bafbbc17b57b7d7d0db4441315a52f011da3250d6acbc73294b8691d0ed3ae9728afc556a29c9530102ab7af0bc734b88b5aa7b4a78a6aab03ecfed5ccf3f107204f27ab560c53adc789a5d7c9260e1fc329c8d8677039e93f6cfbb27db682b72ec7efed11c65efa37ab3d2787514f614fd896f96f5c574cc153891f9b55d7e72bb658a4420fc6ff83356da53cb9a5712aa60d842ae40aa5c3f3f57da857e8b728103024fdfbffb9dd25cb363e51f1935fd050625d637ebbbd6abb7ad58ae475952675b15217c21191ea1dc5369c47fd9cb025b296c5973c2341490406300c172af488b2a8fb2fe2a57aecd3331982290668a63e2f21096f35b37185902110572a6c15f58fbdb50f4a43bbf0771df65fd19df809310f87e0cfe7ae3c0ff87f9727e3283c884e2e2de9b9e98cbce0a731a3ab0a24eb1ef404663b1ac2c7add70722633ef04ecc319f69e9bfef03f02e45e9e496cd9175c1dd3e6345334fcee7372582e315d5516fdfd4a4e366b00aa5b2110cbc62e69b1fbf21b42f9ded3d08872f452d12aa6eb7e44b9d8175927651807619938447eaa4302c1eb7c7c6244cc01f30a774ee1ca2c83bc8b175c337622bb0d0fa8abc0e3f1b51933d9e6b5227072f414aaa5c5b2e5eddac3bd49bca225fd16c55f1390a47aeedb89c45a9b099951f06f8f69c9612b299155464749c34668c44ea5c0e0b7801e08e5225c5d2ceb245bffecfa2f3ba8c7bf0b6656dfb81d5fdb286942f6d47a0ffc49dc480cad1f721960618cee1c489154002ce533973f05529d428b828781d2418bab3d5ae50b5e535ea013f2f0d123ff772a5ca3865091ca9f9742d78d25b7393471af5d9dae7289cf204c0cbbd58a5b08b752bed4dbd393ed932d222706b817df45acd61cab729d673f4fe8d8eaf6bcfab53d83bdf0df10375810fdba315eb48837c27ee0a072da31a592185c126f75e626aacb9dd7d2aeb17c246e5245853049c5724a7c5a728f50e7b3795e352fe65296d4d099a4a69c5cdd901d5858726a94e60a26aa436523205644c4ba5abb4b2251f402da9335af1a876bf44e96d09827d8b991bfdb721b9bdbdcff32ca3a085414a62d103a255861f9ffadb516fb0a34581dce24bc727d6c325f92242fb315004d31a9ba129b8bb492b1be367bd87e4106676a0eb07277a2a0de255049dcdff176af2d427b3f9d28be7e4aef6580a724e905f5c76a7201ec113f0958aa15ac7f3bca4864417c80f077eb9baf98058561d3a602194d13e826bddc5dc631c86dcd4c83e01ffdf05706a75171aade57cf27ce471912b50b126d97a0cd2aef64b9f902b1d337f2a2f2215751399a755f56d6503b58be1072b5ebf8f19a26578cd0fbb8fa31b037ddf5192267b9004553f5bd5506dd77757298c84996cdccfa00e8ab730896dc24f2ea0a7f741ea5b0a352ad34a4a6d62072303591122c3e6a1600d1380f7e5c5f78fa24362c4fb85fb089bf1218d5a8d872ae269f5b7d632f32426e2117d4fd2fbd4fe5e49f6f4047f507fcb5e948a76372204cd4839518ef4738254196521daee255ebdd976fd148ad466a3507211d69724ea2af1645713d326b0a26a0c28c03102241e2b8d8bcd38cac328c0811116b361729db69a215ecb1f41d45f8c44936337efe913d82a60939ac8e748db8fc827224d935c06acef1b58cf0ffaaeaa87d7092a87a5f50a542dd24d30d158f635b72d1a4ec8211d14231e7f885b287f0ea1302350030ceda31ca55448faed3f46d63670f320ad038b41208844c42574cdb59e9f95e7f6a9f8f0b027f832aed881a724357c5c61737a72a7a5c45b77dd0d34eeb8b5b6752d5b66ee87be0cce709a47209d65e64ef9524101b409594bb64516aa3127c84fc9481f6eccbeb0b03015d116b5704f3f22680f636e3e3e0710987df17abd55b7958f7bfe24f26b804d7c372281f79e18eb5fcfc6cb658e9f4074708c2ef7b34e00bebd4ad19df3612e07f721adb4d0baa20688064c8807f88db9617cce3bdd943d2514a5963623a660efa72874a2a1f69197955bda399270960438bf5ef4ecec21b9d6a6d27936ab662d8729ef85df50a3279c3542bca6fec10284411d39a44cc767f1f9bd60dc42196da72fab165605417c2710d95ca02f26aabbf0b0c2c5692440a39a7aba960f1d85b72f3f081e6d3ea4b7c263cac5eea647519f4477a251f72dfbf560d994714bcea722ef729dc239755e395c477972bd1e8426cecacf7eab665c2794e1375ff5d88723248916d2e3b5d8ef1718184f4ebcbc2818056349b0157009a35e148773ac6726cf767a2f3568b9dcb6618be3d98193da2e920eed89cbf29045e4c125976042dc4e94587256d3fc348c5905b41938364f7fb8399f381f6fdfca7ad7e1920d30832cae70c0013cab43368950c11c1c20e521def437a038b1877525435bbaf8c4a42a1b07ba3f951864c65e3f1a01c8a0e68ee33cab942e11f80ea703b82df5960d4eb2d9ed4236b41772db4cc6f0b0999592d31c7bb182361319e2cf91c89bb3344d54ed1bd9a29231bc8959827bf90e0d30479d9356653be0a5d21de850e49720b238016eb10bcfd8820c8a8ee1feb52622c9008cbfafedcb14d1115f6f228303d00a18aa48b6eed1d17df9fbddeeb9c842fc927ca027bd6673289e22bac4e14258455c806b1340d192d0e4dad65a985bb05d49c81e2770587b898d2f80ae4728b837620e476270c314d5b1259a08418d934ff30deec9f5ddcf0881d699dc672d12ab877a60c86de9645bd22f818d6929e90cd98f914f21891ae4cc9127b354381abefa58b07878074c3ae5f24b4bb5f2422f5f781cc060b3fc6257838784772fe62140ff03541eb1c30e28b8f5ebed5dfdcb319ae7838cb0b3d85684a171572f00f6c92eb726c0a52c8ffcddac6cd3e4005570494727445642570bd0c45531e41432377f6b6285bf266429e690b8fd5c5d30946c6321ffe5f4bbc563783cd720c32cbec8ed42bef68d79d0b4bc60705260a848a7ed96bfc822ec26ac03f806788b0d4d69399f2d642c97bcbe8a7598799eea473b3784f3c8f5d52d59c395072df54ac20bc344f5c02743520e5937dce270424c8800e18c70860dd58d7b5cf72029d80dec9abdcb5f63d26a03233cfe2372ebca1c98f96c492941bcb16470d7249c511bcbac9fd58aa0bfabf538cd427c0b826e7df7fb5c0bced1737de8d3464717dc76220d5d81ca4e6d15f303d29eb51082b21630977ed0aaa761c94a8ce49a8be38bef4e08390b08e43f130b3a8a53900c91d7e0a43c92eedce485fc807375bcd9e2393e9ce1b480ef5e8fca5642e9670a34fdaf98360082c9111c8708b728482eef5458b93b276225902a81c0619548d88f6f66ce074a8dff6784cfe5a72405d952e1b245eb38fecbc13e11c776794d3e265e6b428dbefe33cbcee3a762776ee8edad47b5faa404bc3a43b7843cba8151ae55fc5a3dee7b5330266a38d196bef16af836a5e1d9b32f07264b21fd349475984b8ecc59174f440d0840d3a140187ce70e0d993b44fd71bea9fcecfe8c3425d6b48caa7ab350abb71bd05925d64f00debc23cae74b358282020681907244a0fc9e65f428f78e152b4bfc92b72e84dfcd16a994dcbb0020a82ed9d73660849a4cdecd5764d0464c92d58859472e45b56e2198475fde5b7b1f647c98bb4b20c030a4407f3caa83db94dea48da7232360c1d2fc42fe104bd6ef08cfaef3d040c4ad41c9c7e07c8b373bd74d8662598913ec62be7d4263935795be10323a02618f2364669f0469604af1b68c7dc7283f15df80aba705ed27b2468afcb0c685e0eba9579cd52988f40e3da286eed32a577681e74b2d567cae15e9810c51d01da8330e06339a99b7bae238c50a222721c0642afd887b3076ac3d66104e3ecda11a2ab8e19950666794df6f6abef790ff6cf64cfd92e0a648ef289ca4e15910874caf441e3bd346668ab18a75f0d9d72c88494a374623ca48bc9b2860d1ead1dc613469095347b476d214369d4f3e062932adb75b0c2bad290cbc0396afae1e495967ba32b880d7e1ba4136a6b5708114f833947c68c887c3f9d7ba016b3ab9b8cfc9611ec32652e70d9e757daf2a972464160ee6ac2c0f4fd9c09cdc6c29127d31b76f5a1cf0567dfcaa0397a5e702f471b64909ca0f9d882a9c4d76954f4a4e47f6be496f38244beb91ab7276cda2b42bbd3294c6f7e64a80d57e61a9d0938a5d05c0c38830514dd42600e515cad273bd91c73af18207beb83945df99eec616f07293e6f1227c970b687c394697e729c088f47c36fbcbdba2b8aa71df79ce8b63e47de38e5dc2c5a1ce95a9ff7764514822fb9703e5234be6278c2f45333683b1379508a8c71d901bef119b1ea253046272d5df044eede97848c0f86d56007c690e5dd05f126cfba024f2abe61c872dbf5b930a1e5614f40bcc3dfe18ad4b8ad221b860862a8e57b7a9c7660401d72ebeb70eb2495a5a3d88d8bef9ce4b3101653920e828f558cca25d8fea027295aa88ce6d35679b51daa3364e41d840109f5ead1781f70eac9605594ef87b571551c25f2f88a76f7111a2254143312fbaca90bf754230f2bb6d899d8fe9d4dcb725da5c062adcf37cd23f3ec2813268e49a25da36de9d1c87a245b3bb5388ac0722a8ee237808ee1329f29792db894b21aa83b40cf260e35f6c3d646aba44870726331b5bcd3aca45114bf861c2c84ba355e7f55f678cb651d9f1d51d11720ce2a51aca8a0618b64ba3bbde835fa2862a31d953dc8758c48f82c1300df34fb7e530a17183d32c6c4d7f5daf2e8a403b02a792fd750f77844d6b522f09d6e871859964ccbe6fb49883c1dd258366ce11cd17bdfb7786d2d607e9f5ca89f531cd2723e72f56157dd8b8658d17cf9eb97d377de62066dabb56313883de5e6462e722721eda80e8b90416beaf2927edef24f40c9ec4d968ed6571bf5041f6c7d9b3b723766fa239d083857eb0b5c3fd55ff2de5406df195b0288b02e9a6561746b6f72d3f5fe8f2ed699a544efce8fd0fd3a8608b666740a16018941f6f301b2368b727ae22c8fe3109ba99f08babd57fadb76bed024b777ce9c553a876e17328af972a096b59985831251b7a877c36fcefa9b4bdd35a33dcb90801af6be380773310add5c8f98cfda4219405a3e19001ed69e586a3c2ea1c397540b52e4f146933b7264b13d8390b72ee51f6f9de42c82e36112ac952cf2341e0bcbb8be00e862283cdf5fe883b4ffc6a5842a489e3c5fc2d932fd8f2a21677dd0798b9767ba35bf724af19b44c1c8a541ff80b3de0ff96b8d18559603c4997ab1126e07a4518c72724e3cc506077f26294ea825fb1088549cb8d16e2de1d68bba1acb5bf9531ff672b1c88900417d98bbac93b69ac1420d570dd16b36ff102a502cb797e51cc70b2d68cd2d001b4159f625d58dbed84a35160dd73aacdd51d20ebd30f2b96bd0301cb727df846691d688deec5cf432ad366dab04b31ea51722be8d67e9469a6a957290eba7346beecd61a59a68bfb58f76d813e077bbd0be0df1f1b824f062f9ef72fef5a7efc7d741a9ea033bae8b73843459ec962ba68791f2ea8b232734fae4729d97d296c6dcf4c4f4185f1de83291d981c8b506edcb3293e80c3abe898dbd443ecd3ca7c62c07f129ce9b0e88dbd50b1860ecfce1a5395a5c73e6972a7f4472f7d328f3e5880a3c21b9868f0efbfae34add83dab8716f6d4597e33bc83bff6ef18b1ef7221c2b9f5e121d0a08b22a529f0ce9815b6f8dbce9a46723514cfb72d70400b093d2076514cbbcf30556f7029878fa85e9bbfe93e983ff5daab89f720d7908b272f4b62122dd17eb63d6657bfe7bdcd7deacf5fe02b2f0b4a9e6a15acfc9fb3cb1d54614c250fb97407157a6e2ce0ff02715494bafc7cb82b8d40e478c868d7daa8366d3f8834930e8fd9a7ee4edc18ce82149010eac45874cd27a72e3b918fdfc7d818e9740348567e733e76bb66585faffcc649d50d0440ab7397292ed64982979f0a9609ac9f6e15d6b0d98e1dfdbf3627134a6626860770523729d1658b0d2dcb91b87738898e9a058b7cb9f405745a53e19142abf6f08feaa0ee934f7f8d54b135e838e75931e8f793024df83fb6d4ffdb34f9b2e7c0dffd03be1474481be2e2e708e85cfd0c3096d87dc1dd45fea5e5331e62e32f7086142723c2df0da8c6c4519af631dd5c181d8cb54ec299d06050b62407ad838dfab50388ae5d25e815506ac783269dc7d1f8328c4cca32907c9cc2b817017cf5577c13d3d3e48bfe17f17710b566a70f513593f898a422038c9179077ed9c3ca76b7c0c23d715b624eb55c4d7615583a3e056fd38769280ad80b81dc6e50257bcd3fa6f44ad1558367f03ebb2cbbdbabb2517efa59bbd5b824bde1871b46d8bfb1bbf27d0cf3770de463bca28b79e20bc6bb271bf3f0f676356a987278c52c89e7338274ac5a64447364d5d206b2da5c24c4227c33d693aa5d50e16acd416cc8d765472ba3998dc80eac4ad91e80fec68992763866927e991a8fa73cbe9bb4fa29d2172569365ccad1b2b8b8324585647c28328f17d435455fc1e82b2840bfbe97f77723166a7101b70a25fbfb33a8983cb4eeb989e8a7d6a213183b3a28cea9eb582729f9e9888d5ab1bd213d7a9e6c332b68cddd3b5265098f0f1e2e033251017a53225c0dad9263523d65ee5bc0c1a1382e9b5835971219c41df053ef9a35b9ed714815228c5eddf263b53622007046c5030c8266dd74e94e2b4c34318e0c5e1d316a8ce041b1faa71e0a9be5bee752ec337d69b3a5b47cd34d024c07cc344797d7255216d80986d6be6dacc934de5534dc0b0afa24f44c76ae292306821d7864a72ebd4860f142ef8f2978725e5e97d09dad57c427a234a0156bd580ed8e69962726d4907f8e5a7c1340ea842432d410e77fd72d2763a0d58d715991c7378b91472df8b4f2b52a9537062faa8ba8ad0b8130209d1251c4a240cbd3b0341dbb8fd7260dd82a4fc6f0cbfe2c8694a8497e3887280ee07838aa83bd2b9cae100196510e7a39c9367ef56ce83f01f39723b85b17c229e53e9879944ba1067da8102c47204f730c3a19e28c891ce1fa0a34bee4b64fdf0f511d908cdc8c4894f9fab5a4175f9df8eee86609a126fb36ab42cc96c3a790e71a0ee3d869af15dbe702f28720051e17174a0f8474163954190568588188cc48f44c6d05a089b2590384c1517a2db026a5185957a674cc4e6d2e4fab0f63a7eaeac86e4043a6877ab1bdedd7260a20ea68746d5aae59f96498bcdc5407c30cdf663655f59203422bff32f6972bddbfa8070c365e770de671514708a0c5e950c87036b9b06be679929d3d91c721fad39c5f0501485be4c3227bbe860bdefd8c2eb53e62ff8b07fe915d6117172bc7919e58471cad2957007807e33e8ac641c385aed526a95b5be03a8c2ff8a614ea74599c86ffe756cf9bc06c2344b8c98446354e3d2125ac866c39af81e5f71fd82d519df026d5e78bcac92ea0d40c7a220c2e3696f4c63bbc8a5fddf47a71f053bd00fb096543641ee8bb71f51f812fe9a48c3e59db2d2d6ee8833a776737240f632ddd031137302d100b357fca4628e409333cbbb6bb243851785d2df173a124be6a7bfd83e81bb8d13e3827d6b295a784d99ab763b8c92c6dcc4c0cdb71b97db1753eae68cb1501588f5aeb133f8714c74fdce65689435cd954d3190e07229c31659ac81b430249788f0504ee5a6fd591d67d8cad5a11fa956eae9b90a720f9bcac789c8277df89a8b9112f3abfaf253115fa3a8b17d90614f8c9833057272225dfc0d894bff65168eb276ac523eb6b2eaf7b041b731e55dd8e01721fd546ab8990fc3a6e502379a111bc7be6caa526ef36a477622ce32330330b78e2e163fae83d69ae765e146b614cc5d89fa1525a527fb7bc92c20dae407b3528e20720e648158e9ad876f3f7728628899a5c83dbdfb3ed77ee0e9bad0d6a9b58c754caecc65d826576d60c881a347c452986e86023de4deb9e3f3262a4ef29123f9267ba35cf6a1986220a2741a143790e052852ba544d28d17d49a35ac38516a8345ff8ece7d04d44a04991b6abfce8d54b5c49c77f0f35eccbea0c6df77846b4d721115e7bd8d4753f1b2e95ea588453fd2dd70c3936df17652073dff35312083728bb2177850d7564226fa41eac1cec13bb41f08821d1a3fc3529da0d83428805873ab1983a70fdbf1f213108292344754ceba31831aba7cb4a604512abdac507204255f192557ee7c232d57c066cb13f6e1ecacf3387769c81948f5edf8143d538a14e386de2f9c27a843b169dcd2d66477abd99c74606b9a9efa625829000354f8109160ddf5859fd266c3ece55051286e793760effe1722fcd5f12f071faa55b8c4c687802b51a5d4c7423dc83c67ce0e8536a013e5911caeefe4c23258e202809a6db59ee2074bf4cb7f683b0f65342abb64ffb6a179e9655148233ff31f5e95dd624f0bb970346b673fa760b2fd87e4678decbcd1f1d23e612586f5a6bd7246422a1400387d3cea59169730ecc9b67bf013c33e97487a1fb2223db2838b72dbc1d48cedb5d0e74aa48f76949a10b932ccff7e0d6b825f5a2eb2c33d726f721c82c73bd8733805f66ac21e9e2fa4abe1da5baa3992d1a53b7b9c2cd1d6b26093b3d6dae456f3e1d9962f2ed27f68832440aa8053bfc40bb09a9583fea9d872bf096b639683772b33e58e41780b7fc58a7e15b2b1e145e209617f40cab12333267b99bf41e782f1e6e46ee951c499345abbe755574e32a0ad2f52568e2628720519a95997c322e61021709ac862ebfb48bbf6be1d1960e7b88c994b901456722e7d7a3c7443d4a83f143340d926ec0202664cce71c6d545ea3a0fe1fa3d49720a5435aedcff71fc77894acdfc1d4f52ff908f3ed617bf7bf4f818ead106e8723244ebca94e9f71b91e1a0f9cd6ca5f87986ea3acc53afc4178407f08d3011729daf3995bb57b69e496b2ce66268913cb15813cfeb193eae6d70c6e8e021675d85ee17b13f148bba0a383dba32782169a09db54300aa67648655778475819b7200a9789200da65a2dc26cded75005b06258a328781e6fe820e4e46108f18c97243f0fa65736eb3323ad2f3039cc7e317756e37b14f3460424e4784510ca9a1722ef5cb93dfe7815c99cc87dbbf109dca78cd76e704d64f159fc3e74ac64e4444c8f6d09c3d5097618edab26aa3e94a95dec0f3c1d8c948eea2b900098ada5f5e93bb326bf840dceb136935009b6b4486a55802c95b379a0bcd0307ddc0bf1c551d335c699e994a085d28087077d323d5a4f68b277d068d5ee38412c7a4e31e72b7f7456c4c46baf065ff7e2fa48bffda1f44340d7cf35a4b9b028877b0adeb7222dc5736d5282a7eeda1e44b3da6f7b3aaebafddb68c995ed3e9b069b834317282411593d67cf9ccc43f2d9962368cf35d686160b6bbf61b5e0b55aeea1ca80ccb0505dad057e26a69c365f4bcfb19f523101f8eedef722f56c65897cfa8a134fa85c09f389d1f0486c4c50ece35ba9e08470ee8640fc0a1f230dcc47a4f5a7213cbe8cb73e00ca1854a98b122f17b4a80d03b7d3059f09a8a093707a1fd07639c7427f7e0c2bcef91eb246bebb6d94f1aad19682e2925ed760fb6ef0c87fa7220c5646674e35669131fc84dc806f3131e86e8a648e778c0e610a55aa0ce4c7296155eb8c1ceb6449ba6b7c580acc5bdc9534ff7dc5df7eb3f4b7210574c9c727f0d7c6b2bb0893d54c754dd9c0b9cb5976c019e366ef436d0c0d322aeae8e72df8d2cb1611f960cfb2e9113b0d2b0e5f70fdd56f831ebab14a5313184aa4e72a92a95f71ba5af6a238ff34b55173a90aa2ed96a4dfedc74424da08c439d9e72dc8c121dad306a69d5f10cea22c8fb5e35b94172f144492b74d2fede7e4355721ae4787f93067319c2a3a248d34c7cc879234fef7d5a21f71db8ad1b87009672e6d0a837696ded11a960884a40fbe2b818f75c819c312dab4b7d8342f7e33771f0f2ad47cf318295a59b6f8dba8fe707e3246baa68f94563f3a4baefe4d63172e4be91488822614b96a471ac4572ffd0b84d5fac1ce7b2180c04dfea51fd1a7234ca00ba1dfc94cf9471e613fa28674173f058d8265b0c75c1970936957d897221bd582682983727f843938e6c60fef11aae73f018de57c7ab0f685701762c7241250c56379f9b612630d82bc539cd7220477c1cb0abc4544fceeec5f84bb55a4b31a29f457f522c752c15a6a67f31359238ba23e5bed74b3ede747eda44192af3d1e730d4af6396c9845ac92b3f1f1eb0bec4a438ad4568b742b0ef74222a36a23bf53d548c5e20437d9b0154098e2a42b9b91655720ade65dfe1cceabf8e72b494c0b721bd87e8a7daf59b7eae891df395a3cbaff2c39d739a333cb70def72c36e990ef71b37979e04212f3fe85d1e783d6762f0ba17da17bb266f94f26c720338b85fea15c0dc71a92cba24b22e65a35054394481a61d6962c5147a695a72f07ef3165b166c6400c7d922b2f0e50691a8abc1464831ed5ec2e83fd3f7a47259081d930a38c6166c897b06892dfb92e79dc474bab414983cf9fc28e3caae155102d24821f63aa6e67a5cda9002b2c4e6a6c03f1829e0b920ff7fd95897d47255714b1e0548d620332e9547ade9d4115398a1a03860830ffa25772f02913572b624fc1fd8fa866e92e373e515b8b46bc57425f746e4b4c4ea65d04de2db6c26f9f8c7def07f8c8ebfd9ffdd2e6a75ba80416cd0d11358677ff78dc17e32ab7283e1ec28629911b1b4cdab57933c1c7307a21e98dc4b801dac7151135ed65b5e1d043d508d43bb011b083700a9b1d01d301c89b4163db87c2db0f5d755df2672026267fe00f5fd29f77e722da28b2b886aa70ac61ec250be5e9a0468f213ac1d2b7d8f88212b586e3bebaa6f8bb2b2fa9f9ddb14e62f6b57b6e197b2151f566022ce1cef6da1ce4f3d3792405736c1e01a39e18ec1524434d85edd4e00071572e42adcc5189982568a698968ffd47cbf5ec81c9a62f0483638124148e11e48729da4b4caf469557f7d447c93f89bd3e15915410d8a82f45f9bafc371176c1c72f702591e293cc6ca1f3a80a8cd2caa3c59cf816491f2cab2f07b3895bef48a72b3fdc0d1da1a85a47df6580910144629a9f34bf08d29b6d36604baf77e57180b1d425cdedaaf37e0cc6d9b4350e3468b66b4c4b8ef6493e2a615c2d63e472e72cbe0d837c12deb26c2f77467a40674aaf80b89530f4312bcd1b838fdbcf07c72478f0bafa0daf4dca33633612959c0649e5eddc77b00bc9934a93b097cc67c6d17183a7a3e4d1df5c059f02111dc06e4a5f1c7d125c7f3999e4be3a77ebec768e7936c7bbf06cd7c0fc8b5d8e21624f7c6af6c7ee9534911689cc1f7b70c9c2ede28a58505bedfcec2f6f9a3f29db06879781f1f4ec19ebd675d35c53abd921a45b9551f6964a46a93ab2502000f849ecd57185fa452ba64cfb30b6fab7735034fe4f9c8912c184a3e964e2ae97c22efcf5e4e7982b21b2f1e20105d52fa3a4bf7433bca5f48c81f7cf1299a6f611c98d58db8cf56becc869752e68aa69ba872f636902e7315abc5873dda64de027bd0f22a8676f04536414df276185d9a09527de77568048c4dd7364a1a23e218915004cba8ead19e65238e3fe1568d234b01dd6e407838d167ba9b6d339ff8fafdbc4e70d690f7175f7ebc477031653af1728f08e01b1fbbbe24da350f27dfffa979c08c37a2458fcd3b1be793fdcf8f17727b179502b4c78538992b25e0100ff87b1d29db9e0ef60d9f8a71b0836eae8d727c0fd172860af6516c6a878729f435912634b121ed9037b55ae62648b4c14a5772dd2a84060d77144c80c6a8f02de5414ba8b9f8e2ab2398c937ef1feb9f6d72b9b31914db9e0bf20a0b9274e3b73190ededdea2e38e5e03aa10d064b4f66353b890537c783f2833a3bb4683dc064b83a8a62e88ad11925694cfb03e41a6907236b95177a10edb0d747ff634d5feebf05a80c0a650e8b3c5789a6b9010688372b3014c21ab69e4e4f02479961351b904c21e1d428db61580d8a2d1e86704c6723c3ec18a3095115520a89410d6be085474c54e703ad0aef2c184679736668f7297b8d6221c47854c676c1ac6d98627a28153c5e884f08dee78f2dd3445295772881630b5e6db069209903f24dceb2f178b98d1a6385b0de01e6701944e1eb94c9dfd6fdf7d33f76701c96ed192b694daf4f256ce485721afd804d70f230afc312da50db48a712ea314d8b1efcfcbff5fd094bc950ed949dcab9077995e21cb726e121097ccd03c0d656ecc1d41f3a733d1d597d5fe24b3f1324d075203856c72ac74087324b604cd615e04bdf6b75978b2cab432325a0061af2614cce7b48772e7f2ac95d39ff53c12726827595fecfe533b43bae81d0e0d9686a4f8d3e99a07cbb5268d9ec7251eec108414dd73c2e9e72505b136357999e8ace2254c06f931cdea79c5defda93b3c9a8d6242ef7acae2a02e45a3fab5d75c8aa36a98e848722bd5deef52bf2bb9139de91f46406aca878407535c6360ed887389fea32b6672c8c6dcc9279a5bdb33bf37b1c2a843757063800b2b1c3b215c1856fa01d37613fe331e08daf67a32f3e733236bf782a4b679c146a42ffed41540521ef19cef72ea7b24c8e83439eeb17ddce37c8ea3689efcba76075e08da21c08584e3f6051304aa116c5d7e2553e44c9b0c65157ebdc2803512753b1a696a62e292f9001d729fb652d6a3ccb842f57335491c8d02c6701d29f715c7ecaa3ebc4ab307a1ee2b032fb0b05ef4ee1a9d7d195aaf583c8ccae75883201c8d2e787d8cedd1a4f7338dded4823d463e576c85b1415632d12b0b278b55106c101c9945243c85f80b72bd5b273c6a20179883879f23711f242ef4a4cd32b1ef9867658b5986a5d1a372c622a3ed13181f8db5327b7919a0908e98d0c79e5046008eab2e48754430a66b83d0856a49d9c2ff40d317d5895dfa7e250dc4da36be8f3c8b39fd09d13bbd0d1ca4e4025f4e60101113a0b9f8880c789396546878376c1811d645850c72a71e97c4ee45f4bfbabc395bb5c90e0f1110e26443aaa5838a1fcff2dbe98ea0ab72ac562e52624f1555126268611139097e889e60d96e1a2c8ecde2b6681cae4e7256f695609b5451c6317625909f2367ab1c8ef781e451441127a9469818629972426ca79185b5b5e4106099a52f021c3b11dcba06bd7c83a172319b84dbe2ef723ef9b9a0038f708e81b437a6771937520b55d6866237c1d23e92410d9501b32f4a1ff591aa3b244eeed52a9a7f51f8306c97050f4cc0c79673431f531686b55f4d5f4d5aa3c7a5a01e64de8f6553889113899afaf00fc0598aa915925828355a1f408e45bd5f826172278fab9ce019d0b999f2d94b4aa247663c5aba12c64c030172e5cd6f3aa1c18c2412842f7881fdf092567a44a7788156062f1886f921724c3ed13ea9653e3abe116c4f387ad951dae3b6ff1c39ac44998d3ccd72346372b2f5c44c0ab95b6b78101287233504ec7701042514aab3b055971fc4db4eec729614440c5af2687684707ee2e39b04a6f0963435a1e02029deb1492f9508e252b03410f04c23707816381b86a45fda43bfc640c673824ff3d62938905f4f43728d70298d584e991cd957a80996451f25be6ba0e2f467939ec9b3ac2b01498d7241ea472a7ea2648f2cd99d98b85a986a3ed8603a302463341b463968a7d0dc725e57e60cdb5d1dfaaea3461a3f1cc7dca258521a8e6f95d652298751067d507281d847a04edcf66a5bdbdf1f34f6e24f01a8588fc4d9f47616877c54494edc2dd21f4da238681983226cb769dc37f0987bf1b5ff3f0f64d22af90bd085690e7238f2b1b9c4dae95f7f192132f210e145e41033aa65f192795448fb60af2ea563d6420bfc3bf72c7696826370e3fb6d8482f8585440dc3cee3980288ab8cfdf72aae2a9087821236abc3f5e4d46c41af56b54670d506c362d7462f4ad173e14537004e42b3e12f890795db5fe86093c6526cf1b8c8f84ed10fc4c634ee98cea727ad8984dce765593fd3bd865b22d19ef8714eddf875fa7bf683d2f9b490ac9683e96b1af1326e5843466223a844cd36ac3b9af4045a5c37735c20e6114736c22d12df8a7af9ba8b97fd2e541230a6cdf5d805488b4ce5c1387faca708e6d81596436cb568906dbf07a2ccd0166b650e8de6d5c0297d2d8d31a372ca279706472c9d7098d5ae2fbfdc3eb8f5db583edd9313a602ce671cbfb0c01d46a96634872f9f95db75183a2e9fb9358f95b877b4231d61e209f0509801b348d0276bc8c48718e1a6fa8611b7a27486ee999b2391946245933e59e2fbef8b7c214bc8bdd720df00634f949e9b9457d0ffee5ffa9f58d27e959034d6b05a8be32fcf499c272faa40ed8cd76a04d113000e9a407a4cec458ec380c891c747419ba89e82594724b3f92bd180cd9eb58351b950ab8f2f6393a443aaba50c817e3c72fc11bdb472c52b47567345e318f21aaf908225b6fbf158dbc2f2a3588f02096d639cd7bc4ccfa56c7dd5c4e9b007072e2c3ee4285e26abd7d461e5ccfdd366ed0fe03ed02b84f643fa1599a3b1aeda5b1fab553a76b6aea08f7c77fc8e3eea72055b1c510dfa6055784c7ae8cc578794b9cd2fde1d10f641113aaf1c899b5b51939c21d6722277fa6c97854c98a3ad88fb874bc049c9652ea3144127c7643ce754ed09037238ab9a197aa19b94bed33199e0808cea281afbe464736d435c138c3b971c4509cbf23b3822ec38d0041843523e6f2b607ec40b4963651839eb69a870f1e53e4d5f46d7baea665af06411a7ccd9c548c6c3125bd76fd1cc6ffcf156e94725306986783e2050fbc3b30eceb80e73e7a2ba653e82d7a31019b4739f3ef9995ade72126b0dec14ff6fcd262348758bd224fe0e5fa58b6580238c0b26c934e2fd467281ca84e26a8fee7f3e9d9cad4083d607727d5e761f5320c07967c284c6502a722d806ed0f71b4d1102d986e4faf84cbde18fe5b68a1705ecc828dffffcb6f272f7911bb8fc26e94ee6f8ff4efbe08f1d01348777096c41add7e3b0509ca365729dfcffecc98f6ef3dde9207dc2d760918290997f09b2ea0e10c5d7fea49da744b0550a36925c2711ff3f81395bff76802707fad4fecf41c61d2ed4237523d172afdefbf0fb1debed705cfd74181c36ef75da74aedb0ca5b02370d24391b5b0720fe4ce447bfdd3454cac336c7f403502acf7c183026dc19a1c76d0dfc9e09706a30268749849eb4fdf9aa1f592a7d1640d9431ef4959d9896fd959f6c4b8b572f6973454da2d05ae8143c451a9332d001cce40442a3c354f10221753be43f9729ae9140e4a179d213961c81007433ae856c46c59f9f548f849de032752741b1b20b4bd33b509a79768ecc359854aa4286f6e0ed58331877c4c8794d62b39fa12a549b323c9e895042171ecb083dfb058da0f1ca96849eabba1b702d2402cb52a0cd508e611947603a23d43e33542c189be9e53283ef69df0eebaa89d0f280669397d383513cbc1e8e3e715d970c99b342500abbc24a78e41fd1937d4e4b84c7262f35e4016638cbc5d73f7ee3b0d614b1f9de8b1bcc0b4f2871767b2058e19725fcff4634f8c2c6bf5ef30f1c575d0fc93f46b41b33594e8efec82a04d43b4432c92efb2d8a5bd1bef6bd1e5b139b38872b08db271983ae1b3742cc6a898170d9b442f2e5c2638987f0582db9766f6ddf921c8c70478ac64be56415e5d735506dd6e4df35e34f64925b43aca94856431476feecda6ad3654860134d74125713b62c5bc48bedec12867163e07c71905d719a1a84ac45fa360306b480bb398ed7235381007f281f9c458b1849b61a0c0fc37fdf99e05cb8327dcc690af0c83935f0b4bfc43832b8d75309702791a8860e7e5441b4241b22e37ba69ce398cad9672a85725408df0eab6549884bee2218bd13295a0c8b197908f19b5fdba0ce67b728ddaed6965daf6727bab2aa529eb35b9b56a5b37d9d04d105e7e2b42c4360a725b7f8fdbd01aa70654f27789f0fc90da9734c34bbf6295fb8829d8ae1f89a872941544244221b06f9b8e3782172453749a78dcd72462f228e7963379255f4a491fefa9ab574e600b6adfcee2e01806e892ffb401ab69df8f77ccf083b704a95a7a648fce0ca01418e2dad74fd3a92081a43543613522fa62b19ef7ed836bcf72a56fe7be470032096f03618eb74d6055b3ed8ed74562d87f91b382dfde51147237f84693104eb7b0e9939b584419e6a1742a9ad8ed89e6231d786deff125ca72211e05a156032399100b0a4f4687f5bfefba7b2bb99a885fff32fca76987c76f1e283daf20ecf212283341c6c626deb4fbda32e6f95a6ce0efefbf5b68e32a72d07e184c1679e60935ce9314bb2a8345e3e00958cf3bbbc207fd404a576add41304a0d44a66bb6ac4680cf4eecb1877e92c6223502e02edc3d652df08e3f00728c517999da864c1989c38e6c8b47c64c993d522d93c0cb34ed93b8cf1071255517fec567a58e6dba40e69df92b20f25120552b06c188e1d93c5aed273c3f7c14c4a06517b61fa5b52d8029e710a92f0a0f079e48272368106a0f41f4c8b84b7284cee5ce84a4c653c09390fffe129d8625dab76fb2a5eddd91d1fee4b56b7c72fb4687feff590cd6008ff4314807a7d6d721b8682d0898f1531540630661d24229df7d35659894c6ad5371dac1a268830479c7fcd91d68d96782e080060cb230850ff851635b0b302f886903144da835491e061704b4d9123ad0676685266272198ff447ab39f3130ea078c2c2f667c2427fd79b795fa9a158bd68ac63b87b55e9059b0860896e81062313a9cb1bbc68d0001e6c8fbebadec8fdd6a16e5479490d21866353d3380c64e3a458573b8ac4fb79b1c9d25771f5592f47925643c82fc654c52c9022fcfc7566f50aa308a7679f82f7f224f3e0668899e0966b9dc8727cd25d9a27ed44e00a610fe6162a352b7a5a9a2661da272381c1c0997aa2585ed4af445b4fa40afb7c74a212d410a272bddaae3e29cfb8bb29caf9c4a696a2406d154548f80c08ffc06dd32c242575f0651526b16e4236cfd16f3de801ad33395791e6b84fe6e951bbba9cf886da1ceb4fec5cc4561d921442cae5fc74dc18703f54e8cc52c7c82774fc5d23c35c53dd30e9960d8d4ccb41ae6f5b8b6775a64352ace4551e3243244e50ac6e48388d63ba679db9b24fa4aa0aefa6c6082d9e452d2326a3efb1c8eab8cac00d54e5351d1f5d0becf15c3753abe2b65b7e22fe02d78ca431ebb999d4c3d90fbb4173bc5c217f5208d568c8dca27ba1c0deb8127237f1a53c2f46cbaeb6484772c0706a12df24639bace058d6b90d6af9c5aec0729fcba59dc5be949628d029c61e4e5a816e7d902f37221e71f16fb8b7d3b37a72b3e0735c6c489fe00634c16bcc73fb4d43ac1d8c2eed91dd07fed4a27e2b974cc4c458dc5916e036fe53621eaf69847dfa27f1a9b044a81639c2ca283b4c1f72f4565697b022b53325feb661c2a4d328b66291dcf509c86a794b8a1852f58072fe66d4c9903ee59661f2ff282f81059ba0d0dd773be4eb3e5c84db08fd559172a094986345c37ac14d0822224536cd244283cdd7ba5cc075fc5f3bc3207a4c6e8d1f240423011030fd6826956ada3926fae9241e043f434d603913e25f8ce9725e138e324eb876ca56c5ed59568dd2591b3a5e32ba23afc2c3d7b46b21eb7e2162be4b54d5fd06d7f882407b09e97b3b078491d8dd7086cc0b62e98e191ea21d30e022e2b128a9837ac4352232942e5df7bcbae94b53a8ace1ad3daba1539c2f7abc4435894d058fa335f13e9ab7750584ae8e623b1430af62cf96cec8c89d72232bb7a78ecaae968aff2499c1de23a3d39f8036332da5ff6f556379f46f4c72b4755dca5e24737fe36d2a3737d3fd010104a0c33cbee90e869de77dc53e32723983293049e0b354013a9a3b5c900ebb6875c0b89b181b3a0d8f35eae4dc5f7230d1645e73b17a1485cff04ce4a9fb6d99c31cc32554b8f570f3b453a463db698e56983b454dff2365465dbde2be01bbf1674d96c9da3fbd0fb3f8cd25a6370e65093d00ff43710558cda3f23edc99d63889fc823b444d9fb7a1da31db9bd0367b8d6c1fb15662abe115f5b2ab08730f2503843a6a7ef3f33eeabbefe50f037255d6b2a6f4205a214b075e3ac011db4c2c1f61c6b0719f33637eb554bd3e3d7224df65109a8f327291061a066df410bb5720440147a3c8ec50cb6e53d0e3887267944ad0838b4d55b2d92702cd574adb6a9cfca6630438bce16f8688861b283c95703c7ab1adf37446e6203682ae5dfe41982f728e5a39782a99735a97f4cf72086d0ae31cfeb17da3e65469e6c296cbf238a3562c487dc87d001f27dcc03a6f4dc07bf665a5c4c1425f644c460c056debc5ca02c381f84cfe5bc7a9b9610672e7857d43e7ef100b351668d8b7b160d91adb55d0f568f422c9121958850ea63d609bf0ca91d42bf028ee2105959696db262ea76b3d18c754bc3f2b44af157c72d50ad9de2cd40b7a371014191283c0e7b363967f231db9cef98e71304915ec72af95df6b74768dcac973933851e41453086bbe51c6ed692aa8550e0ebc84cb49cdbd99bb76d26016b2725eae4066dd9bba587ba4cb8ac23aa399975e8646e54c07bf5636039e621d30918e7fc8ec96c2b96c1c10a70973e8168539e6654ea172fda74871495af832949ccf5f93d6006d300e63b5888bc06d3c292c24708f7c440f93e53ac2f6a8e13b548e623d8101310c5884b0952730cf18fd90c560fbbf6c60420fd599c5a98c3d0b2a6712588c2931f1a9e594be3300ea5f87e29c2134157611cc9d8fd9e162cd08e611f37abde6ab35f6641ae4152ee5befb07101f56728dfe6cf2b3884faafe913a16c75618867d546a8c7dc268b5baa298717eba59662eceb4b486cda88eb02cacf7ff6108c9b622e5f1d932ceed817b2cb28c4b93617c15de07e9049f8c5b07708ceb652d6822ae38b8e56d56cedf81dbdc98710872483e61ee717bb1bcab06d246409b12c5d8f7246f57ae70389c07991572e4ad721ed765287c101365c41296018a8d2cd997c4804dd8b207ab6a1926f4c685eb729e982bf647cbd97f10a9f0143439a9bf014c12115942ea4584576dfc5119162b9ef2355ad7dc0383e355966773b7b0129dd6d0fbdbe62f7019d526a034c4d8726d75c2c3337046a56a10b6246ced33a53a1987858ce501808a15b2e85c1325222cb618b484124dfbe79f54dc026bd8d432ad5b86bc1d24e6be5a91805c68e77259565c9dff21bd8fca71a82bbc393bcf55cf6127d7ddf19ec373046af0d3d1723113f7028bc5d6bee35c1f0458c72988741d48e7d8b3334dd20384486ea8c772f7b0a0d952146c9b8034262ff15f38c89e976039ef2ebc9f445e7312d05e211867d3151d03fb2b19879132d5f3edc6f211026b8b02cf4d9729d2539bad27fe726841b5503aa0f5dc5022a5ac61f3d3bde0eed1fd81dba49e44af5f4b6f8c2b2c1e206b626647c7677ad003e24427bfcb4f2ec0295c59e812ad3fe076bb4ec038b318fbbd3c754f356a3d3096a5df0033654fc755cc10666c585ddacc9cbe085306e1564bb4a72ab02eafa825bd6c4f973b7f536a5db71c85ff0da263db44382abfa83f5c2fa62bf5adcf2d40d58242bc828de367f1b00a24c9ecbee19f59c7727590542c15cea69bf53cebd513e8d5d8ab97d7280fc9eabeeec1000e12e9386239ebebbcd5a4b2afacb2bf9e9abc104531c162328e220a0d28fefec49b64110239dde1eaf43633244591dda78cde38f0cae8fda39d966c1894e14a4f5cbe8d0e24e0e93d6c325d54ca9f825512bc6b1a503783fd4b0733d8c8af65aba8823f72cac155400fe0eb159c5f835a6c159f25ad978011a47d7daed3ac6f1d68235b2a496756b31ecc1ea82289b0015984d3837aa3240d1b8d95ee9905048cf760fa53b1eec340e83e930967530b94aeef10d68080606221f6ceb6c0403b6d5dd41272e8f874c8be23aaa506fb136c950fc068c4dc3e5b033bc907bf9d73bbdb14f072e3e245dce1f71d178fefa439b568fa122d8b88f26dbe567ac2512141ab593c720e18d51612602c6841051f26dadab316e3ce86989daecdff0cbb2889f5645072fe64edc164501bf18044d89eb9f19d2cdc101c4b3d8bb37508fe6c443b53e9729f99b265a36ab0f5d8504e27295a2950932e797248ed9ac7467b18aa06640272017017d1054d81bc991aacc8f4d65dfc5cce7e9a3eb9c2320b6f7d07c58e433479112a6dfec2144a4b60d71bfe224f25060e427c6b289752bdaac2f4912aea7211343f7a647e144b37b6e9e864a50b2dbf42e936de3fea0e385bcd7063300b1e5339f0ad36ec40217fa589e781499a023568413311b724272d30beef6a45ad023abc6ae9dc07a70c489132c0010e66ca4ec1388fd857e2a7a8b0e26da0ff7541687754f4fd6d0a4ce216b4096cff904439073213e511a403bcc19babaf9acb72c5fc2169203e1a256120d31237f6c9cf51b818e46430628209017913eac693720b1c799d53747490d7f860c43b2de76fbbdbc0bc85a19f737b6d5b6ac0a4d572f4c269f1fd5f5adbb6696f23daa4792c4175d4d8fa1375a9bd5776bb4a6d0e72979d3dca0ef2e8e96d92c2de5a761f2d2d7217c7be8c5826f152351f0dc58f15f0efaea2d32ea4bb18551710b7cad6489021aadd30fb96f1169ead8331eebf722a376d0412a9bfa348f3fb9f8d9afa8a52768e3b9d15232d7e364177e7f9d35503af8ccde571a31c3f0a49f718fc7b917f51cf9c5b68be430b526a4c23e2c972fb95fd670bf0a482d63a80f9c796c69ab87d248a44c5830d53f1aef2782b8d34a1eabf9aa53e5c0dd88e1442331ae36dde6a49b2ee23ffd7a9588681286ae42d9c547080e773ee6d628e7010c923ecfccb0c3deef8ad28d9226f31ddcaf45d725ae4463796405bdc31b71ccf99ba319ed0a9a1f0928b40d2313b6dc77084da41627e97a60ac42ea879861e783b9cd0555c85884177861bd22098633003a3b670932f190f562a9203161143fac021b59959a6da29124cd049175d7cc6291f40727d96c055f60a374a9e9fd9cdc60e79e64b67042fbe73dbecdfebb53befb0e47217beff7965b76575bf667f9b4536545ff9c76e50466db2cf9f6479cba778956cc258ebb6edfc409740e784575a0f521f8c3e6c9af4271b15452693a33b1791083617f3a4c05a0fbc85628b66b298221ff5030d004c51aaba9f4dfa8083d8c5723d2dff13d4cb6cc0b91e831b050d7a69720f1dedc2be888ff9d10c02cd767d71bcb621436b98273368e43b7154fad9ebca00bde4f52b74fca928738e33629972ce2ad605f01d54e40674ab3657c44fc383c8df057d009d854dcb711e8489342d8f0b744da8f246b3412c58adffaa664ca09780418dd2272a505c609e586f4a72e1eebd6a6d4e3f6ea54b1240bd8e70c0ce870adf3f5cae2436345329f13b1372febf1c7cbda3ef676032cbde0720b4b96404d9c1327972363012ed9f666d4b223f85407214e088716a1e6b5567c69a6fde01046326e8d8bd730b18a3f62eb46fdeb42c805816148a8a25889d9aa23bb845267604c3e180a5dad75d4deff2eb726bdcc326ec401ebe89b2a277a2e53e5e7fd4e995ea4bd91857cf642d7fba46727dcb5071b63381f4cef2fb741d6fb32beef9c46b6b87e83cd5bf7d84f71f5f723b69a8e84f701bf816962d3d695595fac9abcac72ed1ec51a556cbe40d1d85720b5d97d5ae18ba394fc96a400434518bccba5dc28ea77bc9a31e2fe4130fb27275d1d5adea23204f5ad3da2d2174d1f351f69446b1604ac61961096ca7f713727acfc99dfad297c616ce9e29166b4820dd3ce872b3bd48831f9c3f3691c0a75d761467343241640e2d12102d4ac073c22f62aa1bed7c3a118468fb6d6aa4cf71c641a329ebb454fd9f426992b2ce35138a033bd968e894aff3052fa094ae3872589d6692f267447ca595070b8e2afb54485c32c0ef5ad7f3e11ea6f57b289c64e8edc81c3d3a50927792c89396679cb5335a98c44e79fcaad274c69045b2be68456102fa9e5d547e11149f741e36ab4df4ea566cab3fd83391759c0ac5e8173895b49b226c146765ef246b27754f6efab7dadaac52c08abac4fbe0e65992113236133f22426833840d0829f41ca1df107237c19454553b2bb631badfa4378372a76a4638e093d1fda47fe66b17a1c008cb564165a791907571a5aa6ef0e7252a180b976535231bfd3372ae8eb67d02563d2c123c8c5683bae84410fa5cbd09725d0e7407be4927e0a461edb40d6c5cb15f0150f983a683dd1402f545a9dbd572100c8549b7fd3aa88631824d4f533ec40d4d316819711ac1e869d2195eadac463d07d0f736e7600e245d42ef6f9fc0356d4af07412b42e63d87bd0a5fc958572f00e1dc816063e291b9f8ac935acc6d6b50cce284367ccae2b25d076000ba372410200dcca6f51a9559dd7249a97415eaa1c02e6c5dec28de7f3ae310f9f2f721ae3085d44db48b3eb9a163b0cdd4027fc911753279bafa0a980f7d025422f00bb7e95615cd4d3c682147fa7253636a71bf2ca746b6ee511ee4a0e2281a25d7237b6d4adde19e2ba0455786cf714ea7c5576c9dd5708640580e0edd6bb2ca472fa10c631909fa1cad19184e7e0a5fa28acfe2cd5adb3a4afb3f7f7c7a526714a9e7ac458ff386b897d8305e3028d1d1c06e7d2b9b726c48b87beed10500fd037c8b1b5d18cd813f9e8b3f93834ee56331a3efbc4060ceff2654ecd1d2880531f64968dc462e3eadb29a7be57964946fba875117869e84facd56c3268a6d55631caea0f88645ac7fedc884067f184fd886622df54aaf3bc38caa7df8e4aecc7722e55b147ef671fa93a04a947eab2e3f8356150eb3614cbd5fdcac72204133431dcd0c5626c841b2a94d4fdd60bd96fe7d7a19d0df1b7f216add40404c8f670726a720bfb881fa2fdc2330b5870b07c2e3f57b442f566fba7784656be019be972cc2a9eebbb4b6ae5159d7fbb9327caf4bc7ed6a6a3e14acafc7d0f0ee2f59f7237fe882f48b85001e76edb6f179784148a60b86e9e1aff741ed530dfbe0d6b72fedf137c10a9dd90aa5b74587ed9458f473fdd6539b03907debbc25aafadf87221c26a77e7314f9536a948e06b68d2e90701851896124f1eec3ea572d18c8358aa8c1ebe9d87d3f93e1b6100bc2609161a3547502b8a318db56a5e2517da50248fd8f5a64ff2c9f3654f2982d1e91d546189bd1c44c3ef254ec8af6341076220916f1ebe43810bac987feb54f8c9686eac77863b70ce86dc38b5fa1fec8f2f725336029cca2fb0599e44cb80dd175a3ced25a286ba49bc9ea03542aa9aa26458479bc1b9acd7b97341d5d87226dadc8cffde3582acfdf668d194af874172d17224338df00b9e010ecf6ba64dc58ac7e9a1c21ba4691305d0d6a7ec8331c4a767be5016dd7f24712d808985553c82b7d70bae6ceab72d255ecea365ceb2d5f244f02db843e0f3386afcbbfb6e9f31868e904582a692640927353014ed7b8606720ce39ed1b2583223138b3c22b012c44c7129bf0336aed232938842daea9d3d7236146dceac83727f0c3785a6a7303e1e583599aa375d9c36f8b9cb3c85538d722d81564c050857287133f18d2946c5ffd3aa96bcaa39e9fc9f2845e35eb22d625282581c7a9f83973f7e1c2e967c6ba718f59bf6c787ed7fda052182fc74ed1c656fdce4134b22bb2a3227b7ef6b8c271382d66275ed4d7566433cb65603e17232cc6854de08b046a36cfec89705609be542fd87dc97b9359675516c98d4d94ebbba2742108ce24ce8b65647afb1fbfc98b7c10a93c2f23320a72333b040db72fde6cb60dd74a6e1159a6daa8420b90333c4e71631af6e10f8d6b8534ab1ef65beff3903f36f48b9d24c0e39cc82c03e24b3000cf23e95b2146c54e3594bd272a4a5b7cb31368a569f130157dd2505b2f53df892ed776f8d59c95d9ce37b2372996043e4a5f04fe266ea155959e5d6d83a9de1ddc74292a82886880ea04dd472dc2106b62f595995a91be8ddd6fed095acc47e2bf85d21ff9270f17611202f4a2169d92267c7cad40e68a8ee68441cefe5487929af51c487a4c13707d2b2f256718f53f7921bcf4f7f59a2f5b496e15eaab79c54a157aaccd924ededf74a6217826ad38b54633979752965b97ab5b75dfcf3ac3876c669746ef863c08b6cb07263684eaf9308b0c8f125fdedb984d5229758be950e90986e4ce806f41945e272c004e363ef192dc453890d155b237e8f65833c7f8e45648eef063345d60509274f819f385282e8e48cc45bfdcbd6214757a78d6435160b144ff2e5096c7605727500c0f801df0f54ba04b7969e2b01a75efeeb66b3001a642683234fa77f975faf9184aea26f2ce02a62e7ce4667240c5a249e0cdb3d2840b5d6a261541b3a0d46c81b0aaf44b4e40e75241f16aeb2a0211e516d0c4c7ea6b49a188c37527272677352664d41f615ca63a3f9fa5fd260788fca266d516defc5fbe29d98377509929ab980d7b2066e4187265bce1fdbf68b226b41d26ac0bfad317628e010673c88ba4c5eae23f815136f29f1725013572d53003d506345f2a4a9202633fb9464f6aac92228261f06841f7cd497f458d5f88c111ac7772115827ad4242fd81172ed0ba1784334207dc23b0ba23cec8238f607fd48f6ad34cc27dd15f58e4fff7212e349eae2c5d2867d03bc92a24f1cd3e084c53ab661c78b2d7455a9e28a8a7222921b207425f185c0a98ee4e998e0f61f0d52735476a49c50b9e158e7631f720071dd8e147fbcbe630916d03022c2b2f4505875ab21089623469eb6cf75fb72c09ecd33181536e5b7a3569de7ab88d4c2668f349f3378bb51522d50e4a7ca72cb424573fe736d2db72eff9547394b6fa49032595da942f4c9bed6d12061ba721c1f709c3a52cfb9eae1adda3464c8a24bb798ac5b5df08b899262ae4ce29341cd58058b85cbccb2bad70c62945d5a15dcf1d82adc9471a15eceab8950a0d072b9f4eda95af168c0f5246451419f5b0e932646ae4e174ffa30989afadd5d193b2fdc1e493c856ae06f6a319f6b50d63232b4f657b15b431bf91bf28f4558d5592b4fa7066682d8c50f0f3a73dee9fd5b8a9c04d119ae0ca06d5332029eb78c72b8b047307aca16f61aeba4dcec68f9a365113c46eece2295f3a11b0494afb372ee3cba509fb53a957e02f22ae1c0b4704cefaf2cee710c36d409434e46df2e725fee02daee557616c308b44619bdffb2062dbf97fe09ee15d7aeb16bf4694172a16a1914074a9e156b8d55f5936d12ec2bd602cc49d3e62299db98dd2dcaf972cfcbe414605784b6851315bbcde2f204597aa5cfb30fdb3e8240df922e723e1b489be86bb1f71e466832d0598b208702a9b245590a87991f621a12fb86da8f09c06074eb21fc139a7313ed62b5224233618b473bd0ca8acc19b667c998fec2729bb4e449ffc8faab4d910a1552928cb37cdf3f683ad8c2c1f420bfda43ce691aaa336890da10f2a4edf455751046fcbb25f19405ae4d2a5ce2c98a76e0355472ad0ea43b61241ccfec378c83ca6728383bb0f2f4993218def193894f734aad72b6d75c70ea1269882a18762fba09169dc02a6c277e0015d48376325e45249114671e95a3b90a05ad82b0d7689dbb77ae2e4b013b05ac95f09f197cd293e08b3a77c810379e584640005597d98b369a498c0ed91af2610c0f8976797d686d1072790557d7f169ed4d240c78474d45babcf34a01ad2e52e4662f6242c13b33747219298687013be6d294890867e6907b2791f712d6f6e8076fae7b336c896d5772dda18bbdfa2d9222235c0bec779ee9a9eebb2060f1372a3ca0bd447a2e75c2276b8b51a11ec81ab3c21dd4c51dab7813daf808cbe0492e59c334f65c7e26aa72581c6d5af142888b2b6eb4c9320fcf5cdb2acccb4d4cf131610a98c373c37e5ec13a12bb46566aa407bfe4a28940e4a47bff9cb89d627678747b06381cc3f272061aae3f6b9d1b19bff0f42e496617c8722da334773fb941942cefcadb971a45f7ef6b55467148d513763b87bf3c832108c553b6153c4e4173b04b43051845721bf19240aef4bc1157d6a9c14d500c57a31a4e78923965beb7b6f80186da76728eacd4ac122cfc1d223d1900760aa9e7ea882376b0faf56e41bdf1a0ea23d70e10e611661760f17b209002321bb657745b6bb3980596d9f4fd272d01983ee17217f46a8e9a56e6f9825c7197bb293e1e7a9053c6a21f7749e3e5f99a01e7766ee41fd1c891af6f7c0f86e4a0fd6524adb1ca6d75c860751afdb6d32ceecd567235a4661f2aefa7dc8c1be671dc64024d3b50402efa207fb22fc32dfb1b1faf3498028b162f5ebcddd14159a2ee110d9451b34740484b23c758ffaa00344c37726f331872513206e6052c81c1a47fa8a86af5b071cda74caa90355dfc5d472e7237a93e4398e19bbed9131bd6c4c9abfb9a8bc6ce0940897d68b63145af72ec726f1e17102e6b06e83c9f0d016011e2d946d35b129b0e401e827ac45f3f79b4121b48340633d472f522e304378f0b93be52bfec57f02dc387c3af3023dea1e9727caebbbc9c8ed4a23ab2cac8b7f81de19b82968cb6dfdca8dd6844f4967c4c721b15faf3bc8405c6ca740d8d82542abd3f9f5d746b67439c8573ab2d01e61872c65ce4ab6b1ff58e61b4525a4dc8e38cc7d324eac485c2b8ff3c558bca6bf302c64999c966806a49cb445f59620e545ad334fee4bd18295b9fd7a98a49ff2a722683d5205946f86a49261a0d6ec53469a9cbff9bd6a86fa60b2ea691d39442726de0cbf4ef412e97a808485b4957dd0f89945755a2e0ce1fa7f9219145eb985e1983d88a0dfb190ea082e3f42c4101ebff351919512498112d4b25f1dbe9a0728cebf60b28e2b6d1836718ae2fcf4964b7f400c3a350368b444194f8b8c0017286bb4bf449580793c27f62d90523bfe10eb4c0479d35e48425e380d33fc57e14612d1e5be55434d7c80778e5cc27d14e028a38e6c3acbcb1720c369d42119572308dd7a16160a17fc116f7d7f42fc56b9ac7a3db96207aa892973671c4e7cf727a0fe9152aa7b26c9379038e9d7efc21e84b97d3ddfa54a375a742d139efa572b39f215aaf0f3eef03660d583ea4613ec5e116b6afb9bae379771d8d987d9165c4103441001e50e67c2e3ad11650d257e9aa338a9eda79f9bbf211603609a1723abd4bc66bbc0a7f903fcecc1f875209dfd85d4c25d08e166aa2693c1a7de872350dfddd40cbe97c5787b865e84aea155d629faae6a73af15242b0a3b667025a44f5f756aedad70e44920fc1ce88dba0147d88e144ab005fecf513fd5102f8582ef0fbef9e9849184239f0b3eaa6808919ecd65c1026c54ff5b2254090350a04ae741fa11ae42868b07df30187ae50e9f2538cda0e00b735f36a46af601e8272fb36818ad53e7a326374a2b44dc729845a926f3f92f80e964da9b709db6cb0471f404be902d583bf0fb7c9da8db52236d223c9ffe77aecd2ce34c50136fa642c9fd58f496704b1500d9bde9f86ae146e49ba24f257f5eb5f1afdec22b8b80672ba8beec39406e3c031a77a9d22260d2abe01cdc5fedc7d5fcb236338854dc472b2596870296917105d43904573266d2b9b8f35533605a7d15dc5cd9ece6b781797f83558ba73a3916b8a67c249410f6046aeaad977c24ecfc4f660a5836603721885c9c5a39cd7fe63ae5cc6f7eea87418ee076feda56b641fc689e1300934581af519e6bc5f66f848716baf073b799e009ec01686657cf05f98531d2b650e722d5ebadb8f6763cab31ea75613064b6d3a5042d43ad7eafcdb8e10457752637259e8c649cc980cee4939a162df8799052ae25be3c282e80bec358157201e4c34e8dd236703f37cc7a8869579564b830c19e14f7d833a96a84149bf8a0398b76602f2007bf195f541af1aae0bdc14c0c452cc6b87ff12d2aacc4a9cb3962ced72b3a5d2f6138767e5acca554de5c4963fb66d7df97c0a521fbfd85f0bd1000e72d5147ad9d421414ecd652362d5516bbc824966fd4b259211932ef3674b2c757254b5140d443f0f72e1b170097feaccbee35a3a0f6fbf2b08c62cfabc0f262472163a300bac4857f14f452395be92c5061378807eaddb14937d8848e46e805e446cbc0b5a35403cfdd0d6ea4f2b4ead628f0ea5fbb347526b628200fb501a617253670ba168c462ec44250427b9e6c4a962988bb9160604b367d9866a67279272098ad88138b9e1e6821ba00df5e15d3785644c54626d39fb3ff1205674077524a0ffc51b8516134887695201a9e059702373787eb94c1bb62e4698162909bf5c93790ecf660b3b46e5539a30f7bcb4081a884d4d3ea4c78615e0885c78db2a01283607aeebebdcded9539b7fb9bc0186772314e01872bc381e0b96d3f67c8d660c857e5de3b578da558a27f9e80231f206951bf8f4e51bb5b9267b5fdaf74b72d249466dcfd1b7f7233898d3861d006ad09ed79ee0d101e41823d323104ad67274bad9f28e5804f5f7dbfcd65871b40dd97a0adf0aa34381de0071b60a1a7f35acf35ff457067caf00410ffcbbbc5ec6dfc6ceda8467e947f11151300bf53b7244f92b114e97c34d41bb042a3c8c3d1ce16726b23bef19704404efdd0f01557261b1b78872a3dce7e2e295099d1192e48bc97042dbd965a593a3163ac2552372b251adcc7e9b0deb3a5284f2e9f165784fb786d58e094bd5a0d297ecab60b4729475aed341ae6b6a063ff0cb61eb7fb20c2d6844f1a4f6e78d0da2758633f372c5b8f1d9e1331edb03f36ca4a8aaf21c1c8850443b09ea1542fcad6ea8c6275fc98f21cfcb2f264cdad92f2701675b392e42b6c1b42d18c634181d720ff8d0279fd363c86de43becf3f0ddb70542e07246b52a8c21b1751b26c15c8c2e7a4f537fef6d9f2ba8c6623a8a43863b8ba24f43e33ecb67a139de66d8c4ae9887b972f86c19cb1528735c9ed4329872e87675608d163238ccb04bc27bd2e3fd2bc0187832e2fd5d69b4e55c7bd7dc5224f905f4e9200e93786dec23bdf09a8ca97072f85c9ace2a502268a71756075338658eba7142b076bde0fa95961427f25ea63eaf5e9c8925397c85bf79c7e871cedd412ce57b336b68cae96cc22027c87b6272819dc214813e6fe2e0c3cc202057ed1c3faeb49e6dc424f684a1f398590f2114ebe42b750013014626a049e7e7455173085a933cc1575adff785131cc8720c48d2e5295476a3a33da84c0256b33dc5fdda9cd39eebe9c1dc496aab1595ae70720a0fa1d08927e2faadd6bfa2d5d4e13d3f482079ccb7823f5288ea6da9e4094e084a542070f35cc53cb81b3c468c033e00f9e9c805c3798bf4059e5bb187f172344abb3d0154157501590d5cda0278484f47000bafd8dc73aea615367cabcc727252717d4c1a141e3436234323bcdc1f5fc11aa51a991026b3790e285400da72e784192efa5283280f93b15ff506f0ec947f6e039854d17fc990522a89504a15aa2db46729492e83323780559175ee58aeca5476ca2a0f1faa15ab3741e0b5725335d673890bc16ce606ae483727e7eb50f41f6900d6a6a8d6b793ba44b3981cec19d75ee22e89bcc99b61f4dd27c56f7338094ba8fd61cae1137adb8b56d85b2c9f6af925b2729397c41b64f414c86014c9b60438a4c3a5faeac753eb8add042f7c8bf355b578a5d8768ea06ce1a30e2810b2373ade0a2ad90fda87f247961e9c61a8a8d288c57d9835cf402a57b873e016357b3ed64f9b1324bdcbc6fe9172595b5fdb36b56e09df3321b88d07787efa0234a9af3cb2a04276cc13bfcc87641bd27c38abb1f32379d447c4d75b8c346d333e7e42e831abb5f39dee903a5750e0d389c3d3e9ddb2ae818cf225621f41b0fea81f0f139814dff98b667d47e96e55dac01377c12bfe2be4dbe0da2126de5e8095f08b12312b32a087bd06fa70571a90e73f73f5af7367cbf3ae9d0a5a7f22eb407a4a57094485d05d082f90e37226304324ec647042e36fd13420ff2f49ff26e69bc56d591df539a63701125f7267ec02305256d478b0d7bf5a1880048503e50189f1e5f15a165154e490aeb372e2f416d4cc7a69d0df4f5bd3fa635d6fe3e542b836d78aa43f43f4f43452ef056bbb6e3673ea67603ee1cea0e3002497f6cee0e153f22db3117d13e70493114575bd71c9d3c4a7cff0b33764d7df4e2212f313745965e26f80462fa6cea88867c183c015e5caff3e45b9073d4f6d060cc1bd8a2fcd4db21cc509c0990a499f721b822e76cbab9ef39d1eba74ce488b86604864e63b253e458700abd861ae9e721cdebd067b9b5e8b9924dd68a173bcaec3ffd7afb533f17445ed79199d9dba723842bf92efb69d36ea85a92c9f82f766bc12ea17e4fde86fc0b1ee032a737a5deb3d264fbebb081ee24fe7e52848dd59b0df1414e06d999605f56a8968c5f36cfc7b3ee1a8414f12b15ffd55e0cff88bbfd916bba6b5baf8f69799c6e3ebaf72f50e4fa4d83a0480f4f3bdc9337b1b68796c93b197491b7a76750658bee615721f7b225960a9ae0ef4bcdd3fd161fd7bc096cd1b5572a5eac848cd9594477f72a7b38b9ae2be2c3be1b7f43d681112f3fc0c32e6cdfc0f0be4cfd4384b0d0a7200ad193567e4abb0914c83c234a2c229b457d2f418c2ac41e39d1f6c64c8127294feacff73a9e0f66c01161e1b1a3e47973bbf75409a3dbdcfa6411dc75467700dfe184aef3d16d05c1752e059de446bff778b0e1e07ae6c100902a726a09832a3ea31862e20dc96609ae05803d541e54e43be543240bc1d01f4da9b99f19e7282287b377f3b014be61bca3b163edec16b7b3601fe00c8fdfa55ee0bb19eab1d9ec7dec9c4ecb1a27822f19237c99302410ded5051bee88d2471b2cb8c3cd972a95115b7009c3a7f5937e845fc1d46662426b935a4d95b6d09d925f01589d46e6c3e93ced88218e51667e7b5de7d9563872573cda7f109659650023b09dccb7282e611da841a52f47fa72104e17962377b90a3b2753658d02981d9df676ad17135729e5f4c0903091b0efa4dcdab79de8e1e96e962dc5be84b10f3973a1c9212deadd521148fafbd9446f6f9999ba07f319b4a1335441d813be1620c45ea47501863b6c8a8d422485f444d7aa993e7e88492d687fa722f7afcbdcc8153c20e72147f797b6a91331dc3a174d89169bad5c3f138adaed98cb56284cc861c052472f37f850592514e837a7f1e5067b361c60ac35c624c4a9e73729eb71e498d4b72aa2bc58e225d5ff71befca5bcf2eed1720c4f7c1adc1840c6a1a6dc4be648f1c3c5698bcd907afa10e8df6cc39e143ba9143093a124433d9d87459fa55a4bd665912fe36104090d43a2ae3bc4666c452855ba9089f44054e460790e9c22fba7224f6ff6bdc4218971e90af0176b7fddb03811c2e72ef187d4a510bd635d41f724840d4e78376eeb20206309a5cbfe0cea6e4634c139660f5df8d5b583aebb540b2cc6f2dae4c9edcea0ddf3f0484004471eb8d08110fc691a86fbec8d28603725ee876cac7ecf6f969abafe1fa91cc01783f04ed33bb105b97bbbe529d0d66725916a243eafab73f327047eb04fadc246cca04549649bb5f6ba6e9ae47464972be7d551c565faa1507f407e42403612a4b3db70ee68934b1ab174937971f917282eaa5668a025024e17beaa300b7413c95d84e2fee23c60cc49716485eec2672a51c58a23a8171eed7fd80ce584b09672855cc83dc93d5388b406f06a3aa3472a0f48cd74eb224ac0a185881972b6b6aedc27adf011e5f0d8bee3fa7ebd42e72cdba593a8f87665c75fb1dbb41547392fcfaa9dbe6b6275d5f5e987708b9b872041e4c9711a88bd8686f338197bcf961fb16344f4058f60937e381cb4d223d7204382f633dbc9beb993f25b3a349b25b9fae04676ae45de9bdf4761487bac309ad89d3446a201d65f0a04ff8adf1d844f2966f943b631ee1b24859c14d53ce72819b0ca75409552dcf52177567ab77883811259a7ca4ea4d2533c91236b7c2720158f8a7532dc3785d0025f7ba6a8a30fa5069535d6a1a42c712ea652d45327264cd70b0cb46e0f9365212ec73456306fc1748b9bb34329de9b5711eae17fc6dbede065c86f26ab4975d696fb184d2fc79ff27451abdb194673e083860baa5279addb6f7d7bd6020c9280f670c619424f227129e24453ed86c6e9738b034a963805eb36275eff20b1787854531efcd8f2df0f2b056d0e7ea2c33b941a817cd72a655714b438e588cbcd0238c92fe128f6078b3062fc25b7230294443ee5c5e5e7b4ea0c43dad462fd0f1c12f69bbb23a589d183e2393cb473685f564e6a4d9519526e870c640601fda984831733b404bad16b68ca0efff51d5e72521d0f67572211e795d70d6c31ad47cea28d5e4cd1a9f5558392cfbcca83d48cf2afd4ef67282822a63d299cb00703007dbd153047811c2c53e1e9a495fd937953bdb39ff72858ac4fd071dac55455871d07bd8aff0ac3c07dd4b8d002aa54ae207eb6f1972d2df9789f4be37e9d8101614cbad511ca9d088b138b955270c2ad44528b23629bd3f6709850c8484ea4f728178d5554f5eb7a4a86619e2440c2c4a99cb9c960b970619722bbfff5351bfde4613284ba2ef5efdc04dd5786ba093abb13fbe631f932425ceb22303138202544de2c72ea1031989724a0959941b600f4dc5c53a72cad51f97e63ba593eaece202834ced70d9ca98fdaf2fa58741ff794618056c72dc88b8177d5d1035f9a166e67af8a306646e7d83205358372763ec3c5e0f55726490c1a9c0f5fe3b4a9d034b4c2e9baac2c82789325cfa15a5aa69595db091726ec358ba3cedf8c8110186557f9c3fd756b63255f4fcc603740da25f7172bb72607dba44dab5991ec00cc0c7e22e422fe488579db8c50346922deeb4cbb3a07231bacb2ff31bb2b0bed5261e8e0250bd5fc40988d401f39dc0d95f266030bb1cd1b6080f7319ab449e0ec497a069ee664c98fdd1c9b566aee572a3ac3ab983724c24864b37c55c0c140b584413f7a6e3ceb22ce5bf77b968b8199aa355510672696a134a60bf377854294eecd8763f28ccc5457aa17cd0c807f33e5b02b4595e88cdd03b459beef1812a6f9ee41286e080090ee8c794beca45833f39a9f81e3395534006bba4964000c8eee27ff80d64a35aad3340ff1c371f5b5e86b02e4e7205bf0b0919b4cdf7aaf2b88a5aa9395be368cb8854e2273d85cbc4951eea3f64da6123f153915879c51b094ca25fa63a941a3acb9a4ef44ba1c35b51e434ad72b6f8dbb3070569f3855a891fff2c64586ff6a441cf07225ab1018ab88f7d7b728713a6c752107ebbc24acce7212825d0768d2fe9dfce83cdbb20913dd63b4772bbf8fb860fd28c9e0d878a0f994f70fcaea320cc79fedd8718e73c9143ba1962ed1d059de28d0c3870adf9931544a9d328596e94e40544035fb389b03732a06ed1cb10c9e41f0d8ef5f32127dc54166223f26bd01935d2caebc690cfd2fda725a4777f2e6b95f186b3ebc678b16dff5f6d381fbb907538597bc166340cfe857203c48301f254839353e1ec76a03a12b73e2058221abeaef6ba742bb4c6efb53a7279bf4395cb8b163cff6c0d73fafef13cb6854673803706a8eb8dc91fccf17265586ec8051a8fa3d1e2216a583f684cf41e2e0cf6f7a6f3f037121c7c9fb631a8b2f78ec0c3a75aaf3fcc909e3b2b1601efc1eb93357e9a97ec7de04b8d56590e8fc18b42dbade96d60f19e3f8a7c7d38fb810a85ed6481f2efe11ef8f7da2d789c7141aa5a5bdd5f0a0cff8e01299180b01d396330bacc43f56d954dff18726559d06f3908d2ebeb743b386e49e658e40689079f68249cdeb7a9888c8a5c726dda895039512329dbdb6d30f149cd4aef92edf801c2aae38be54af652f08472a64d324bb7d3f271432f3d131e1f925c64342e6e82859dcc90f20b2b35f8f77205e94478e250d5c7a95309099d586d069966fa1e47a6e4980004b3718fa6647213431ab738237e15a26fae7636ecbcec390ec2dcc6d7630105878475f4190972447fae6c939668873e917d437091e96a3100c69cdda04e5451141f013dc6d8724419980010db2d2a199de33abf06143df2dbc5c443eb3191f55cccc1c3c2f5725178f999164b3f1d0e3e12e32dfdffd78912d346ed6119fa7f12c5787cf62e4de27e7e1c80ceb98af38d2f6f5791817f0f7b18f80d7d2cb212eeef937383ca600b1fb9337701a5d7cff1e1bc0df7a5447392eae22f83bfb7221f76637b9ad26193bc6bb7071efded3b0d914a3713eee1e0b99eb3b9e76901d7b3afed2383207239d1afccd1f7a269fe05ae67345819c0858cecdcff61eb1daf5386f881e12a728a07eb758f826ac2b28ec5a85151c85f2e952e5b73fb1c7e1931563864fee372504bcfa075ea08cd045ffaab2afd0c845b85d1e2193977f85322f6df7fa21972add7675a1bdaf362f81a8d73b8b8d1616f41d89bbd5ad3523d4604eb32cf587289e3b67418b2179e546c9b93997fddc3998bdfc484d15ba5d53751ca78cafc7209e98eadb86e865c3b688cc1492a6036dd06cced70538056c6f4babfadc94e552cbf7facf75fb7868e838fb76625dce5548f35ef17814bbdae81d06c5f0e267287836355c14a9140b524d3ee4b34c94cb256317caa21190f312398d1af120972f96c2f51b0c9a49c07bf143cd8cf4a4b8219d16931a2d15602c42ae20df8957288370b2cc900cd6834f7d5bbea582614f1cc9f8001c3f2d5efe4129570a13072eb9ee88efd9c896895f849bf049d0606b3259e03d9c1ad0991a0c22273a0b2722162f1da5373387283db57096fc639b22c79e0ff457394139c8ae23f19f3bd04d1bd4b5cfa60efad1b3152e99914a0c46d686f11e0882faff5ba2c8fe6697e724f539c14b760f18b512b0031f5eac005d599c7b20bad149de69b8d8f869bc820e9848c7dc2d40d29a93b089e5ef52efd0bf1f4c4649cab17dd70bd6d403a73728310fb44e508576fdaca54249bb8b04cb268c4c9b5862e6b85eff6c20c044d6ca504c1a2dfea863850d3fb5786ac8fd1506ac3a97f4ee435500e249a23c73c7229c5c785a1a50d1713af0d8cf4f027e58b09dd8e8de3cf9828451c84cd6b9072c6426bc3fa46d1aa6cf46a0bdd4023cbd58624a6710b8787e9e66820600dc45fed5b358a02513311371dac0c7219c523aaf769cd4cbceba8ea69c8b9bae411720334314b20cc78a5a305c3058bb8aa72327e80822220e44da4324a82e32a0f52c73183a1eb274884e55b242768423ebce4fc3d386af753c8f1323007cfe073033de6c2dfad0274c4465bd478989070fe6ea7b03bdc9fbfe00ce76e148fa603728ced4d75ddbf107c9a00db4865d1eb7f99f05547c85dc5bd2983515418c32b72e7250db289788412f848bc2883945d26c4648d9956105529f52778508704f17288a8fbd555e0e7325f5f5e9a4ee7cc9356ba5e7cf8d2392a14d718b00a4e5c726039b0019eff0867b7d5a90947d95db8934cd57a2a8e5717ad6cebbb7d5a56726439a84bef2f89abced5e55dfc5fdf176e143b5eddabb76b185368df68c0c36d2acc4537ce7f08a4665bc4ebbe0ebbcc385247c42a234443b87ba82dd59c9b722fbf750c90239d32f90c6e51ac4301c43f6fc761dc5ed2f360f3f39b245a7772ddce5849c9a37d9b783fa02f3f223a37275449be3e837209bf555512a4ffcf72e9fa6f9142e345ffe7c1b5f1097c3b400acac1932d43467d0633f26c0093ac0c9af925f3aae1f95aa57eadbff027dbc3f95ccee683d66ec201f50e3b64ba89725cdad0dfbce717acfe00d07f76df5e4367b80c37b17b84e1b4625d130d533b72a20388cd77def28641da4af7b9126c63906b2ec688450894ba7d2313b10e86722b22e768e0cf91a711876613ad65bb542bb8a039f97670ad1291a1c15aad5459c8b1eca8253bef91cbe83f61c3587cbd3ba8ae56078a88b394ec7fc99e75526bcc5a13f25e0abe6f1580fd4b3bf0ac32b9d5746d42f9368afc8ad1f136e3e472bd81d566019c95fa3115e33a4d5ce8ff1e7f4b507ef4f45fd5414e64895c4a72482b70c1a39e561ca337e532d5e1cd9eab4d20e13e8282c8a3d5c07483e33d3683e532ff36e9d1e28853669627ae05b78795198cc7faadffb3a500e9ea4b3072118f3f39622f4f9494e21c39c8295829d4748cfd50c7f1a576231f20eb2f5e50bf0b8e4248f41c729cb3f935b604dce714a018744a27c133b3dcb1bb59e30072259d098cd01ad68636b59dca4bb88b2bd11aace7faf1cf06dc6bf0d8db128672844030f0539fddfb45acf5f1ef12505f629ebff1dfad5b4bb7390d7b7f29ac14b0cdfcc9e34786b91ca8983959b370c7623a60f987284602d0f38d4c1bd7fd7203a15002a7e1decf8d84ec8ad55ae4cbf7d19e8a033ba5edb206e09d1eb6c772697c75054ebae81e37093a15c23f0d9f212599f83d74ebfc4bf9875510c77e72582e14403584848c828919c35db3eac97a9bb27a4e15b12c4571cb06f44b87720149d6d5d87369a821c014557159f1faaa3abc5916a81b3c98e4837e5253cf355b00a764e3b1054781f3f4f695a5af1297d8a48e9ebf3311527325d62c8669449f19f1a25617645f7f272e254921ebe8d473bdf1993ca94ae632cc77fd448b722ec1adc1eaacce233838f0149bb2773ff0e4719cf4030e6cc592a1f5ca512b727dfc970089fa479c49762e9548c234f889429a3e57da0b91642ecd516fd77072d4c03a564dd69c95286a026874ff02f59fbbc30eb326264ae2c15bb9f2822d213bdfa38ac7878604fae234005612a34b9d28a549d615de2fe637100cc235bf474dc99eeb7c922273646f02ad83ed63e203f801db017c20642a11a0bf4d7a135e0ff5421867bf0a9816a11d9f3ed7340beeea6bf86b2b234a4ed9e82f76f82d7233aa5186aa17f342486f8551f3ce0438c52cacf05fa20928b6e85b610aa05a4b7431779a3bc43d1a2e32f6f0d354f7ecbeeddb2a38c0bccdb7b932c44efe1020bcd4a0ef04b6b7c37065a17634f054351db24abfcb1d472b8cb1057a843f17ba02000072bb133cb1e3638ad7b8a3ff0539668e9e56f9b850ef1b2a810f5422eaa6c323721fcbd5eb0609752335000b5be4370bf8b98d4e0d2a5fb6b49291b536855401446073a397956ece2e0b9f5fcc512156620463314b3bd893c268f8535d48fa02335e8755012e9ba4ab22ce90271aa922f49929cdbc3eef77e782b317e9c1e972258173c6bebad140f8a83748b402054dab0a6f8fe7c9896ebc09a56003a65766351869f0350372f75ef9429e564d08f1c5375954f64241e7ceb1f22c27904bcd54489d05d0222e5ba10562ff003089d8b26c4f25bd2b16fe6aeaf95bd6c0ffe672b0bac2fe5be8b931d165ae53759fd0b09829a6deeaecdd5bf197d898bdb64656c8475e4c490097036697b5906447946e1fac2605987815ae14aead084336aa727a9020a78117d76c7034365084ef4c410c138996ad0c6203a1bb4af02f8d005d3abce2910efe3ae9b7cd9b206ab6f6df04e124554b95c52945333315901b7072dc5abf31954e5dc454fc36418c1a784c3f0731e4d1022453516c1a3b2801e87218d0c52fc85a2de2ef91c787df66cca158db634c64485a5224b1399eaac71672539e9bd153a218445fa9a30d79829dca6bfbddb7f1fc332ef8f60a3da97a4e722e4ea4bbf37793026e1f0a94eb644e3bc41f4d6404629095ffcc4b0e261888720a43ddfd9b650150377123fe2bab04f6146f9a6617f0717c0164798410fb9e72a3adb91cc20025bfe0038fbe961211d0fb090c4fbe2d626303ceda2314bafa0212edaa6c5b8b697baab517752836e31b61da0aaf23ecafa62d6468fab5fa8d17fcfe5f9151c64fcc7cac9e0960487c3639074302305930b7db978723e1c2b76c0eeac8f8bb2e2069144a18d9a142e0648193ef389d06d8aed6649f0298513e3875431ee0aff6b53b241531a73db9fb918af20131290258c87533a5203b4ab03c19d40c72075c4a420a93203279fd57d4b435a80158103febc838b2f972607c2be53a9f6fcfe04ccba0c9fd8bf1d7f70cc83f6f7a9e1b069722791e96d9b8422c6364bb84464fa5151f8ab1743ebaf3aa8d1652798090d19a4fcf435de7568c4f5cccd3cee0d1eb2b525c230b31ae8d3d13cb21700a825f59aa329a5f594d387274b65b3031e0b991cecf1226f8ac0e4a82c42d01c09476a8c3359b95184a1572803358c24021e3049ab31f1d2b58b5fdba353b1044b69171e9459c5c6198854ae21329ce4d4350d1d9db747f77e89cae5d29104b502a0c5051b1394ea0957e7234dde9f22285eba59e32fe24dde6c1732d7a1ae70641ee4c752b35b9c548b64bb54db46281febd57fe6065b5e4232072bcc5fca70b9d1bd177f7f515b3d00f7288cca8312d5b8c278cb1e08aa21520aae71a607c7f6ba2e4ab459707b1a54972e4e585582f51176c4354f5d98b5e42afeda80ea527a7d950bd939a848572de72c8ba3e878a1d632d5990b53385e5a631524bfb530df28e62897782cd0489fb72f5f129a8c85b08aa78bfc8bf65a3b924215e28c1b623c9782265a31d81f33e15e11c267ed9bce23583791ec5d1877771b1a822ae274390faf45012b33f889b43c755c59f13d1aa511bcf78c7ad939af672e10e19ce3addc510cee795f576262e348ea44cb53d5d7b94d43b45638b7e8a979dac84614fbe67615ded53e6cc3e721bbd95720ec5b8f8cdaa3f907412c38806a41847a0475a9deed31dd67e787f720294d4dddb0eee7ab3b8b7362490b87b7ff71e7518be9bdbd2dbc9c8ea2c3f71ecf54f3457e41d39b6ac1067469350fbc1b721f8253e182fea01d9703736210a1aad251ae1128da91c5d50bd72b23d6f7d345f9658a9d6ca0b6f121e8388af72006c49526b461a6b74c3e72c0f7dd4b08e62a3901bc64ca8a7445bcc9e0d2672efa1f45044d24c5e137e31bc28aa77b765a530864c99d83e75717c816c079522b6b50311d983b1ed7923e27b59e1b484cc4c388e8db3b2a094dcf9b8aec78c72049193178612648757ca08442fd35313a6b2ddd4ff684b48f518ce50e46a747283a8d405ebe70824fedffa8a6ce852afca588a610b696542121986999bbd955bb3ed691ffe83070842a12924cf02e808ccd8429ad789c9585c6cee6b39836b72bc7d90eeecd5f6fa50913659ab671914bfe1c03b19deeda5646c9798a3a4c2349990135252100e93bf85414fa4ea3219ceebad7f12fbff2ecd06d51ce0b9cd723e3696c28aa30b63bab7edec4011276ba0ecde498741a39ce20c8a0f79e68e72cef7c9de0bf00f34dcef84b467f99be65d51516c205815451281a1469504e47215a02f59853001f68e32f76e8857d4274d944a9072be1b7bb244e42e10684f1eaa104db20f9bc901ab65809af1205cd5ec540748be3cd94a8e59534f78252900ee2771ced6e913be0419e942242a3a4a775814b4391d42cd1965d7b9eb86db723e0519f6a6b770dc1004eea86615bf7f4f04cc44a4a3a35d9f53bdc719b3f57220f41427e178d45b4bb3865144318ab4e65347013dc214ccc7de165f9d3d8c72f3aa3dc1db753a64b4d99ed647c0f3847a88fcec4b42f123f38a12beb1c3f97213439818205c72bf076aba3207d4f2ff21e5134adfba68d4a5d9bcddbdc27572023a72918e89c473f424a56f7723c22d6ea585bdf118f8967c688af26425cf2d8c86ef81e17a6cb17400d455e179f35c4a92da7707782d93c4004524af30257272a578c81dca73a4098f9d0e717cb2d04f64f28b0deb517c9a3523694b99c45ab9c2a024b7ae064ea2e77d50731a9f2317f8d7e9b31823cb51e8aab11a1d4f7205f955797e1a6803a5d47bd30596a4d0daf3a9f3aa43180374c7168eadce557239ab1f78d1704a8f0298124fb0c6949bcb20de0c33dd5eea0098a1fd292a9f72dcd1b9697314d6c45ab232249edb3d49849f3c008a3e955949fc1e1718b7ac7239b3ae93c2229743e3e390ede10b706d7c0d4f6596686f6b6add2dd4d7994c723b74d477df13e7b34c510a7fcb17f2ab74997e8793c6d99e390cd38a5a5537729fadf53b188cad67ab4acc7e44401001ae595985f5783fe61eee7c6fd34f1f4dc039be7446ad55f27120908d456cb23c54e1991a57e6b83d8c19115384feb57263dd191f2d7535153c544441f093cbe6be69f2e5416e54a39f95c878f5b94a72a978f297309b351aefcf880f1d0d667051a3e5d07a2380c710c37847bba1ff70c050de040335c9208b100ea1e2d2644f224637c4a259253cab0678614fcbe46d28517208dfdf3fbccea834b2d5f0aacf894aaf2e100d95ef3f7b1d2a5f51f2720d080e4a823afefda4820d26715f8cf64215b73c94f7dae276555cdf41c8a11d68c341bf017c2b671babfc264cc94c3d8b9782072ad272cbfc40897961018a72dcc6b855ba8129c15c0a9d2b44b1d2b6590da6495ad59fb606d8550d35202b72fb935f39105c6fa38c80a36ca4b9f3d2caf58f57e66a95b044f4cd31ee2632729fe5f614ffc495db43e1cf8537fed94c8743976f5d22eb804a01f8982159d47282bcab600fcc64bf91f14d6cb49e798256325252ddea9f92577dac3c7664837210ba916515a0c8d3cf6793a4c5da5288dff8807c1feb8b4c3e14e42f2a5cc518fa67ad4663147c1b248722fdb15496a72ac3f5cae0caa966b4f974c08122e608136054f0a77afaf61f80be2f7a72eef2b15f3ef93304adc7f06e8c030383e1724db41ac87312559bea1a588cc5ea15e5588e7272034d9246a7fa8599cb24c0168abf3f3f4c75761ac23bf8481cdc4ea466d0f617144aa11f75e3e524ecb47f666b07e1c1c50a72b1edc67e943f075302c4d5c2ea2973de41cb06093525499d18c7fb6868da8b8a2a895b2049831c9ed7fc499748bd7b0fb4db339b1226fc4b345da6bf669fa25630d1e63a54e63fd3e2dcf4655409ccc429f2406d30cf1ccb3050ca7ddf2d0d6bc183a290f588bdfc0f5f6059d1bd157a625e183569d0ce1f06ad972da67b0a4e54d6589a1787e07be7fd6bed3a8453bc67de51a962d83a033a3e9ead009bcfdd3df180909fbb79a65eea5d39c0205cfbf7bbfeeae7f00f1a000ad6e815210f67be587d7f5be2eb8e00ecb27db24052cb80b48e525f07c05b0c781c90192b4cbd370d39eba9e059d624d8a33198917bc0d39ace4b1a5ced54347643b68104f0fb49f3445dc420539afdc6d38d87836a8e69f7883a81b93fd16d2278061872de96721eaff7cbcc3f822468eab8cd6193ea18dae2d616c9da07727f6dc8aee585c3948f410809ca7f5b7ebc0adb931c5d64ed96085239e9b7e172f9e592780faa6adc21d3ce8b884dff018870ff9cb71a21d881bba81a58ec1c72d5621e9ad0f01c988ca62069a05895bc2b9f2a61508bfdb60fdabbc8dd92c040eeb9e2b6a0adec5e652b6e7bcb48b898e87f9ddcb1fca3cc056fba770fa90372c89fe94b798c44b1ed223051420dcadd488c7e1c43ac5b22d26a3458720bcb08bfc6f5b810f4298da579e4d8830e2fb22e5421ee07e5befd9cf3dd06e6443772fb3bcc00a98faf821990cbe2a9bf61733f958e3dcd7eb99e93bc68e54216d372acc4e147212e571d9e7949d56109cc6620a6641c22390686d0dc50cc171d3c721daaa81dd6a2c605fd229fb90298dca39f0e353b7203bf11ca3e339e214d1a72af35a02ddd9fe9e65987932852f262c96344211a50a17c4587bdba224cd09e72676357af8d10e989043e6ece9eb275a28759dde777eac6e487a2b8c86db27d4fd49851ab771f5ac63cc855077161454b36ed923d3793af675df2d976dd54ec7252ae9241d9499227fca9c6c0e8f04e3c2fe5b3b30c7a5823a9a2586feef0757254b36a2947e8f71a5b21b6e836019358e80ff05f2133aee7253d900eef5d22720eb24f105cf317baf0fa724aef6750306580381ebebf379b6a6f0e8e6470983ca73d2bbb11ed8f18d3bf55353321fbedbacbc75b98ce0742d179e0427d2ff4721dc54900b7e9c43d63155f6a8d976754d7dc51b13519f6617fe3db0d40411b3ea9fbe5545568867be8f5d0333848dfc082e3807d6857b64e62c12b4adfcb7036e27c4b3730be515326ce714e1b6c81af61bd4d8510c0628c57735091cba8c34432eb2173e94f17f9d5a5c8d4077607cc7c6a62431b2d0ddc59c54ac50f587d7230fcf75e7b871763516c3917821504eaaeaaaf3ea22360a070c10779c35f8372284c69a8a91604f868fed64ea1585681a36ebe6d0f4090ec60c1ae44e46efa1d33813eeffc1511a3919930bf97759d1cc29b97ead7b4628fb3309d192c23b96665e3bfe1df8744edd2b4f1c59322202ae36b13edcddf4ed9bea5bec6c3af7438afec4b8fb5eab5e40110d90568744cc032774637f50864476d3fd8c7dae504023d888c7fd4c02a224fe79410889dee702c3b06c056088c04fe5d09fa4645607282b51cb7841daf01fa20ebf312070c9a83ffc7e2ea1e2a874c09e2114d526b72bf0a98a7cca93c0653d6b17b7dbfd76d22bd681b4a85c6bfe080188e46393a58b479bfa9f524cefef2279ce2094100fcff5bedda98dfe32b1acb94ad1e86ca530c4b2ddfd84fd92446abe30e8970a0092f56d93c5a3529c5363eb4954da6f072337531da3eca790f5c76641cba5565da9d4f674fc04102fdf8b8d38310fb4d4dedf3f425f381a8550e00761114f02dca16a20eb267782d91462b92fd5d13af727c7a8b7b332c98916df2677c3de711f1eb3bda268d8dba4c38a59ab79462277223f3bf9e1dd7d90a0b366559b2fb604d7faad36b23536ff024e690d3a417087214dccd186a3c70ba4f5c597bd369478618129026092bc55068afdc903049a8180e35d6e50eb62921bf081c3c0740525fd4246e934e19f0a943645228785659725370737bb36889667fdf0b3e3551e7e9dfcfbbeaa807558a74bcca09a9a50972eea10f29cb41560fe96c066ba7b8e999ec97d4b7a2fe41c878e6111e93ebf8125259e9d99804ff1db3d3f63826d760dadb4cd246acc4f4f67d3a41d42f9adb729433df8b879eed1b9d2f689a7f06883bc2f6554c167cae3af892d3c35ede9d729a86feebf06cf08ba23955ab10dbe1467424635e5c436ce14626455e6ecac10c8714c561385fdd866f7e54b7c9ce4c63299becd6f3911bab77a0a447c3e97f373328ea92e60d17c7ec8300b4a696adce08946543b971edcbf007352f3a2d12605dcffd82cae2d5bbd0cf532abf4884797cf715061bf9c416e57814045750bf3ff3847e84d540a817bb619b7bf362536f03e976ba980cf28844a8b38338dede4cd2f0decd8981ed8d9034c256ff3c65697408d4478ef7fd260c2a6c0d05c20e72c2c68ac66d5f674b7af14f81a4eaea02491712b2ebe135dcd64d53c05c54323c5eb62f56ad09dfb37b8954d7b2c947f926a9b6abc48cac27792de2a2087b1436dbd37c5adf0212e70bae3d881e9df1b79cc60086919edd9571707da6bf0dff4bf68bc2ae3b91786668f6ac3752a826c10abebe88b19e65c5541fbd2dde49c472b67180c9ecb5c71d14ff626a4e2d4d95a999c8e7a74e25f71b36e068cc864f7216c34f7a406b12ddaf2c87c615f2d8e6ec361bd5908f8a59114358264d4f7318f69b997c1ba970a78f63b62e42bf7bab7f283e712411e90d538e53d10b34e51e58a515746c3e85518083fc17726704af378b9fa025653f473eb4a0635ac3d772c719be064707c001dc8f6368181b3f2db2ae9bd37304623e56bc502d47073b72947a3b319aa52524950ca08839785478b39f10d3daf651781c6f5b23ad43fd61cb94d0eb47da4d899bbdb920d5dd1b3414e38c156e228572782bf3c4a6f63b4f82fff2c63f274b426a686d4b2856d1a62b72353eceaad532213ad51a4380fe723dda3fa67563004366ebd579ea2fdc7715481325e30418a116cb596d35b4b212d409e23862a5742682211df0937bf201797e6e7013032c1e85be599c393a7444e03a9435ef5a6bcf864e58480bfc80b2363ea16dc1bc475b93a109aa342ccf382349d1b354267ea14f051cadf4ddb1242c1f9690e8f16ed7abb8f46a5e50ef7247cc6cf1245f2bfa695b8542b6f36291f5d0c6e332cf98b13f6e7e616d2c4744c2ec6b14a65f00913aae7afb6dcf5ab1b80cb33bff1ee843987440ed9bf0141dcf88feda1b3fd2839a56393a8f00f95bbed375975ede92f75661fb0fa092e7421ca2a336e8823747b595b67ba94d3e3273133fedd69c00a09c4927ee32e5ef72d8adf2f3238927f99c8f272ffbb1512afb57ad9cbe0f5c7b41da6bb959ad6472150033e84dc60dc48926214d7c8daf2259a264385204d336bda29ccf8363347218704c437981d39fca9f5460ed06c1eeec51774522ad156cdd54cca5b80b382c7267a37e52059fcfa54fb103d55e9429f64989a1ba739567705e4b835e447372103ab5da4db4939c24355dc8824b3bc8018e0e73eba66e565b8cb99b36b37b7229519d094b77722ce5489620b0a8eb9423bdcc1d1a7dd3367065a2d0bf8165723dd01b51ecc3797872c8d8e5163e16fc6168b6b2933bb8b8273db058c8c63b7227d07e8160220970b32b6915e7d46e477ac4daf5e1ec2e3a01802dc525db627207d6391a18919ce428c19ad81a51dd8a284b9c312d072fe273559fabaa5be872c0f4bbc0ca79fcbbe840bb954c9e530a756a64a19124de796e8f6d64f1dd91359b94e54ff75c51d5ef91d8ff4775f9c8e7b32ffa021f29dd195db3739d92a5726b84a7b60eef9249dc1a0eabaa096eb7cbfaca4b9840035d9d1734b0b3adec36ad0f158f51b30779e31641819b405798b7f2d21ad24932ceb2f73937fa59187241f0c3d6089fd337fadc808b72bdbbec2a3e24c9f48674bc066461e65239af3583135027cdaabad4bbe6e4f4bf32376e86a62746f1f3b25731348a52b89acc07348b6835f741395a3ad6cb978b5e75c0432b5cae3772686e425039474fd3f42ad6dd48d2a5792eb8ecc7a7dc9ef429a6995c99b384910cd030732d159768032b7d4f720f89e5040979eb602c7fb79a2969252e0105c12b011eb43bcd3240086c667ee1de29094814b0e786c9f3065b3f3a789ac2420f981121793b9e39ed6d72dd07970c06dffc1a8b39e20fe64c2bfa21c2759efbba3a872fc483e34fb61372ba595efec16d8e93a317ed3b8879bc337834ad6a6697d8a5af14ca761f85817195dc6ede1ed04f4eb03c881d2bc854b99724662663ecdea145d1365abb36af728a7fb438ffb2d2c8e599b84349b646dc8ec80159e6b7b1a34329444f9d778c02aa3a02207e02f148519ce0aacffc6e7d717081b0b1522c6527a23d00847519727c956d3746c65f5dbe2cdf794c38c20780f7440d74fd5e73627952f8d0df6a6715fed6d6e31091d0dd1cc50a6a390e103d6b10c5b74aecfd8ceedf316a103e08b7fa4fa9733452e536117393060d5595b3478a7249169885685153332b82fb55dcfd70bb5ba1ca204b2b4553f178c10ff60fff8e4f0058ddbe68efe772c5ae5a824b947fcc822b712e751a6052ccab721168676519d5e7ad7c8de25c60a7bf72d91b2a67ebc67f735eaa3aff8f025624498f0d755963daf37047689154872804e0178436776d94a504a74fe86cde97232854d16b756f75dcc588e8b0ae8bc063e2f77703f35e08df7fd76d1838aee187be14901f314628d97e380f3b2b284872592c04a19d9b5d33a956f626c1f0c9343b6a0f54d2adf994ffff282ddb4e1d7218ef1049a2962ea03a790c9616222ef0caf0d5c17f41ec864488ad96fcfd42204ccbb9136137a661d1c98a8d7959b26f39c1567f958e40cb15a371c23916da4f5ca3718af4e4dd32b80daced381245b13b2a134290619f2f63755afce9ae7b391a2696e39bb5f52b39def532960573fb175e99c5a1f1a2aa3abf338b81d5cb6a71186f308aadea6c386ccc51ca2db6a3f16521535843b617406635eaa34c6c1e5f9149129dc18c8ae981895fc491266ce32bba910594337bc69c953ba2928e72882ba7f29d8cfd7300f6f2797f0e15fe18fa8fd10cc27de890a622cf2210c97261c6a937be4144663ac16d93d14d4d2b2073a6cb7c094aaf3c21cb8bbdee1772ccf2290d090ece383268d994903dbfa441fa0e7d49d3d5e438990e7ec1ece872cacba3d110289fc908263892fa1ee93fb46d45ce17541b89974902a99386ae48a581565f2b5fcf860fc33cc74fb64a6f0603a0c9310d05ac589575b00327440c8198823b28584714d6d9d541de3a3f6f5cc9227855cef92e23921239b9832472ebb11b63ccbfef74943261784c59db78c9ce5c5fb04f20479b14f014e7580b7235fa46c5f1e578210a19d56837c9672221f8fe1c465111ca7914fd51529eb818b09b866661f782d419262652f728986fcf6f9f60c03a5998ed640c40174ab819ff4a8945dc0f9b178c0eda63a6abde60f1df0091c2f453deb26bd5323c22776dceb019180c0730dea59a28f9666d1405274d5b3184cb405524a96599c3d32d03691d60b42f2a58adcfdae0510b44925d75d688c905089a35ed931c2f0fb0a130524f0a795040dfbe59915843607a5a0f13853e8e61a743de166d0ccd662c05721146a38a95402cfd8ce2de84a27cd3866a53e447472cce21409701999f8baf72b1f73e3585a23d3190198054e12b9d72cde09cf2ebc99cb26e97316707f2af49ae5ac30a82d3d38ac2b98f9d1d524127e6e42b7210a44eda3ab61f6a43bcd37268002fcf2c25a053dd716a06d2a4deeb72f5cb83b7b06739ecc602ab652a5072c7c7371f388716b3ddd9425ff3274189a1c736638e0c86058d06bde1683c8572912683d0af18393cbafa4d3de1fc11d24e629f91be6dc9c18180ecd5a1ed315b51733b89194785bcb40e1cb091176adeb73f8c8e0f06c908961a3b2991014b7275b0985896b8a9355ab2ed82b19fbcbae4344b11a3e154140329a3edbaa478726828333e8282f34f24ba523f42bef72e2634d417dc4f72f2b5d58026498b8c36fcf785d71d3cafb1486f45d107bffbc4b8481473c22aa4f38575e1969b78ba72d1cf93a6962fdf63e192b25d61a1f334404ff4db8b6800a6cf6944b118b51872b22cfe25ee91a8349bd3843acbdb136c3696e7fbb6513bd8049461069d314f72ef52b7ad14831d26e926987799b984f24c7afb061ca1ab5ec4d714330b0eff72cefa05ca4b00d4f624db864569557b58dab73d287bc00da24e0e8deb029245728c55d99af58c7a21656cd05b73ae1f8f05569075b0acf4f2b231b5a49278122a43d9cac4128f9e47341f1578d766a1e67dd83d39fa588ef8a59cde9bd9f4e12d89bd9173eb953c62196947ea7667ac96d4075c12c27f7e93fc1d93b1c624cd39cc5f519a173df15f1e380e6a7db7dc8910118937e83d4219d34ff8c947d77d72e1e99242bba022031a0613013115e81d1843bd9a4ddfe16f1ab4b7f4add7d97263e015bf6cfac1b782219cbecc8216dd0233a15e285ded3d8f3ddb649e1a3e7227abe6259d079c29033c8626760b3cef07e1d71f00b3c9937229d712e7d2bd49ada0ae56e190b910b78d6af5db35c5dad30d105e2d947d76e1716c0cb0a9cc6bb0ef69498beca043c065fb4e3dd93100148af4e9c68b81f4d79aea54e39e046e0df0973501c97d1fb2d45ae4e84079b265cce8c3870bb504732db9802fc41872fae79c8fc21e944d5f22ead4f65eef9380451e9e45f8ab3a0b10503b07b8f372271432294afcaae20042a0a80cb9eaabb74d44be309398a95ac6ecdf450f1b3a37bcc569f6b9a58d4b443ad1602aecf83863653964644d68092f5bafbfa65f724e458497b92c840d5e75400dcdd30e0e9538a853c3a770b26a1b7ed63723394cf8c6e9e8c64128d06f40621742c27a1935894d063e785491e682cffe0d0c4a727d6c1b7ec8df77b5ed1c5d513ad290b1d94f8c89456032f2b3a7af2c5b009172ab921f804061d3c0c2203d1514a8a0c844ffd6d927e86ec12d4d16141a9e581e5aa9b11ecb59097ccaa507ee07baeeb28dd6d5628ff19849acd7d34607d40e63aba0b13c8bc87a6dd6b7aba4b38c91454c4a8e195ba6190c97af7e9fb9f22f7200ebc6e78ba4c2b9ade20a41b08931fc31813d0b8c12220628fa66fe079e5c340cfc0fcac49beefada72c56a0582b7e475752d0c74a097c079e7cf267f54b732eff1b19be7fd846de43314d7e7b6b80ef499b6f0280a1c26250fc6a0e5683972c932e9cea27733d0b651b42261d5fe6509e1fae586bb618700da6f711a7b027220ee1916b928b1c707bda0ad525d7693edf05464fd524841485726d43d2fca7216bf287e9d774aa1349d04e652f81fa1ba1b70777d7332490fe94844fa7a57725f994045bc66e7dd582cd740771b8e6f8026f91f8225724575ac84fa88577b7237ff3a7ca13338573bfef4de7ca52a6991e1f61b4033ffdc1da516bf7aaf8917e074dd95cd428eaea87ab9dd4976288fd67e49fbebe1badb3b54caf0cc0dfa457c76ee16407e685f75a3ded60a92fae60a9dc1f6cc6b2d90cef0adacb32c1172437b39ed5fa64422d9cafad70af5da70d856bb8f51e1002621ce5ce4bb5880722e87028743249bfdeed05a57ca14d8df49f58a05f68a3ddbe6b9ddb175040909223f1c4b720617cd73fb2a7ec53cfdd9b9537077e298f138c71ca183333b69728ce9011ed338b5a9449d3e569e4bbafd5d23298d5191dcfbdd1a1c46d6560e1b038d911896b9679d727e5a7bf0cb6653a8159c6f5c612abffd720691288c62004c4708c2b301a1bba68c370cf8b985185346a6a3c91abef326a70abb59565d7251d35e00c43ae81391233990a1451d3fe69e6c435bf34edab871b463d8b1a824a58f74a50fd8982c92d492ba69dbcb30dc624ef807a2b1168e9a7e10bf7f3b72b4f60a2525d4d9d9fff1362db6e183b140e6195a2838dac37ad3717ab37806005aba11453c69a282dbb39971bbbd6281e0435012d17762f02c88b9a871082c27856a9170c9bdcbe1fcd873a8b118828eb65d81a8c399486215f27201a3bb4972809cf4bf851d748efd354b4a36a50423d4f0437c76a58414baa99c188e90e77266c518910e6e4da63f8ac062685b09aa404a825d4df2b5b311ce94a13bb10872acfbf8a5e32b0ec6971c5f8754d1b1ed5add83e56167d3a97f34dfb71b17f732ee6a44fff03619d616424cfcac0bb1bc063f4b7dedff8f39d15f4d911826875b78bc35a83c30dd464e2cd06323a87fa96d7d86c4a7902499dd2a016750783b6794e0a686197ce379fd380ae5e8e665f82c3e1934f6b67a95843a9168584b5223765f1881d5547cd2a16e77cf5e2ff20fbf6242e2121dbf8085b916ff2d054b7293fc0a422015cd8a4a86a62add27ded8125e4cd44f2a8887b03a07c0ad37b73ccd98dd3f122ed0195f31c535445f58675a52963e0b5b1fecf50ea2270e5e3372da0fb79c13657dd0da9a3ed796fa403ee79f18b63dca3ea75ad78fa347d12c6f9852a74e54877a798a8554d31d03d9d62ec73ea8667d1d4a22116d22c70d4572c4e663684628ac2d985b46d65958a158415b9293f18d896335d9358747c13a7282afa05cba33deec8ee9fb99e4bcb289c4304ed76d87c3dc977c9e3959136a72d732c7ea879221667114b3a7eb5371a77597c07b554473c2c4729f86b55e1e723b4c8862ab93a7c734f07738fe64939f2c92099c9ead21f279caa8dc2a1622725efa24df354b988f23897b003f60c3c5700af47d63d34e589ff1f0f87167b8729e81798305c552d3da0f0942679fee86574552a529d26d33048eb400398cb40122fc44d6952d7fb0e5cf257a67a2d2d38537947038f305bd2434da53a9d82921367bc3cfc59869e2d4e182cef592e14dd4489bf14e4249e63841e56a53820472bd2115a492e0b7e55e3ba047c3ba3aac2f76a3dce1ffa16f060107210b84d456e3456d1d0f97c67b43e97d6921639507255596237d19bd05bbba2dfd7fa5ed7291ae5f09e0b76c174d2a95e86ce95d6de9db937d901580f5593c5c46c52c2b72d428a314b2d8b6958f28b4d95467a2c813b59ebf9368020d15b3d21510525172c8192418843676eba68c6d68750d90e50ce4fde0afd48451a1f69597f358680d1903af5cbcb24d66f02c52e71fdf26ad66c816a32a5cb137dbd2c3eb50ce4a72a01e9a951e09d9c8b8dca57706c71c348520e8a8255995409ec865e161b959724da2b8aaec91e08886bc047664356aeed0186fface77c41f21d241dcb10f7708c8441025f172ab4cacb1d606ce65d43445b103b57584a376639c21b0beb6247283273957f5785fb3c8bb6014f75a2219a167f4194dc05e11b59c8b24d8353c71bad4d51ec5831c1dcb6f95553cde1aa37a581df389392f3db5c749b79992e4727ebf552eb12cf868973eab3961db66b255ad7400b1d7306fba46caacf86080000de4e11861897984b21ef31db8edae92bb83482401745d0b7524fa210d6f3a72ecc0027161cffb7218a657f1e80499bfba629aff77213e9eea7373c927596e0132fb27db415a72cfcfe7b1f126a3f3c7f28f2978f26df392fd3a4d8722c926028b618d7a265c732380b4cc1abb46d15c728d3fb852d311f6430c24784be5202c0c7ebf75ae9a1447d90d451e0f80e691f81c7bc787ec8643b9c887a823e1dc3104c966ac4ddae72fe6af28bb2ee363678dfa04a0b8676570b5e93fce25ded072b897666e052c110dd9bf64b4d22ce73679add1a5a513caedc4ad80e029daf9379361826395f4aefb1e432c89024c9af798f7a02787efd3a8b327eed038a3a34fa6f628d4ef267b4b618dfbeaca662e84d69e330941a86f1652760008534d8a72b4d1c5a5f295b41b20ccfcdc575601f7f7ede4128ad56b048a3d9785e8758a4d501792ee3996a8285a06c785f65f04f092d502c9dc14139f3555c79173c3dc72301066b671ca2cb0334d636320a3a13682028d5af7e113be289f298a878996726eaa62a65b5d3df7f194c6c9c5d8a51fe68ed9fb5f329ddfd3fa41defdc0cf7235ae75f8f0cfe0fcd778043a02f4e127743e0b8a6eddf6dba297571152af7d72278d805d188c6310beefd663102ac2d8d74bbd01f25a84285fa325af144b160bd96e746a97e175918a90d233307e731451381cc2457e1320208fff45aae14972f4952a054384d1a05dbbf77c44943a1553ed90a1194c752eeafe496688efc7134beb8fb785e0f4c1d0daaa727a212f731ea1a4ee05b2c64fadf88cbd98b2220e628ae65a105646737a2d6b475c6c88eb247146ab5a5508f3cd0535d76ff04a35cc0e03b47c41d462a6a656ab104717dd77df3997356add6f352b5484ad11c82fb4355902e44373c6b8d08105b73ca9f63ef059f5b50a0a4ae7050ab77282d2728e3b01ccb22722210f4dd0f4cf3becc7a10b00da363a6b6e944a9a7308e5f872ad3190aaf11ef9acb8bdbf5721a2e485832172158550f2ed0af64260686bf62a65a531daadc169bd1fc7fd4178866d0bd2cf988a573fd197563b1d428e0bc2222667b4785e4bc7d1ecdd87fc30be5f4bba7ff610b951d54a231161cfdf4e55090c8f7c3190311a22e78e6bdc7bd93e65769b789a37409e77874d0b46a78ec106a516d9bcf12dac4e7d4ac18ca723cdb3293b97bdb4bb5f658e0a4bd4695d210d7870409efa43f0a33f4f111a8323766907105d02acc9a180923f17a5909b8308d48216836f41e468d4019dadf0a207540c10aec7d3939da71bc293c89cb53a72dbc2c5073cc96598e283f2ba644a2542ef4aceb4fd1250c0a756dc5ec465bc725d1f4c29f60efd23532c1f2c631fb01425249a1b78be5317f81d391f4cf14e3b1e6206e16a6ee615cd6b9b71105b7b3211e9a111d975a1300ccebfc2dc50c37228420b8aa88228e15384e359b01759a822d9ff90e6fa22d8f26f9b3d00c5b4728659c3030e39669f2862f296633b3982de3ece8f98eff35eb0b816c21e700e4ea279cb857b3b4cc6a2b04ab5040697738499d8241ed691a0b843a3e88df85217554b33a90e6be90c7f7fb8e58d0851607648fe63383103492ba4cd71d0207b535b69af6e8a8ae176da517e35066c2033d12cbedad60fb6e8d73cae2aadabbc401fac63a7c32298334f245d23b6c0cc6d21db9be6d7082ccf271a63ba2edd0840e731962d765279ad7a2c36547fad7cedf5e914b83c3527e391070f3ab14335726cfed1a1b3419a48906ebd3d260835a66b4b86b340edac4ef0ab35613419740aaf250f4511280e32f2fbc8d77635e06faff8f9692e2a393ed2ff274455691843d550781f89da0a0058b201372000aae031eafe6a74eef516c83b34c1f6431872916a7dffc06750b5c3ee22e9f9dc89b91f0de9968a32380e1795ab912451bf0604b9a879e96239eb80e399bf031c9dd847b96d328f6f61f6ad9e5712080a874891784d3537a91e0e0e836ba739a500deb9fe698f6905d3fab89fd0b4899870058ab043ad583cfc08e0b9a88da11a4d8c816016e2c0137d8697ca64ef5b630872372c5c47335a76b241ee41af9dfb224aab410a22e3d41c1d2507bc931bb08672541a7d2c707cfb54ed22ff4f56f4d403aafa16a4d2dcb1184c4285b3e5abde340ab25a35c873d8622716267c94a1d4031f872b215c9f4c3a4a0e1d7f4b0aed720669bf6ab9caf128496bcca66003fd854224a3e5381469e243d20f058663ca7230256334fe2b73f555cc32aa02f3e4c6db610666e561d9b6072e7e6ffafde16855cf7a94dfd253553e52dbbbfb120e9b96c7e9411a19ea0c1d5f278c67402a3079579881509cf421a48f3629f81d944cdbb3b77655392d5eaa77917940eb8e722906a3d31ddd74f75f9ef9d55ba3d0e03908f0c481bc1b6319461e0254751a72acce44d84ff12341a64b71401abdacfa75ac9ac68e3338d023f265778bd9977246e73170571a7036ac95d75c949a1dadf98a9622a63a22131e140600a2227272c867f05bdb14989aff1ac30775151e0fcf896b6e671b227b7587dcc6b07254173b0bc0daca860c5ffb37616e9c3543f44bdd03e1a3c3baad577a21cfa8cd9d7274c4d9a0a32d2be091946127f6facf3ce450910b5a8e30f1edaa972d72e0ad0ed60ec3b72437946cc7455db12a233548eeee588cb2ef0f5fb1357969b427a3155ae029b1bd9723f59326ace48ae36e6f7144cdb3bd94e320e6ae9e1f2459c972e0f65d77a9818aceb31bbb1c326ccdbc9ab7c7adc074c072840fc54aeb25c837e9f61a1fd73f3d06e76e7162c874ab488b27e223d03b540cde5dae097e8c1313188b632ee55a158b6ee184a43d13e3f66e2201fad73d049f0292a80548c18c5b103da1d1d86a4ad199d51502b64128fdace8eb8e7a60fe5e8c3220936ab1e872ee894f07ca56b1fbd4c02e082f4a30a8dc4889da0ef95ba74290c18fa7df9f72942b6eb5850b043cc2b187857b17d7e8310b58b9e8533694c9a464bfad1b2b72968a7a7504d2f35a757ac8c34f084511db93ea63d367e2095a0efd8e7ff3b872e6b1921c9b09383e3b21a9206d605d10a6986ff66d35ebcc7e5e332421c1b372acc4fe884860525b6e00ff3e93588620edbc7101fe387f7b9d463dd2da972464761cde6b5cd6caf901d0e90b3b99c86d61f8afc6be7cb906151e64133b3336720763df3f3b01fdb77d1a28ceb2c4dc0a9436c374e419bafa389849e203123800527a6c0ffaa90686cd01cc2dfa83d6d82e02c8887b2a0e340465746eab8c1c120ce7eb470e1bea22c1bca743608c1af28775ad69521f35abc8a69469b78bb47237002c05c7634420ca1e48e3c1adddcb017e609ff7ba2c7dea23fca6d5651153901d09ac1fcbb9ab2097cc0a6c3932bc8a13ed30880b72ef3346205c2ea65972366ceebaca152efbad07a08ef31c7694451a6a4402f9e69be59544ba2ef69e72ffcb08910a0f71f00e01adf21f9072416f9ddb69de133e085786135a75f9fe555c3660de93d6c7353ee33505db00f7c7752a09254a554e83db3403425a1f0d728ccac8c66004fce9f4a3f34bdafcff9680298974af9616d87614afc33c41e172f2565916a6d5abf2eadc5b76904ec19e54e7704f00c3a5447dde0a06ac2b0f72a69f4b36b549e7a61a8738cfa4556852c2dc6fe96a62ae2bcf46b83f4f25b41527106cfe1f32a66b27f11bdb91a120bd8d75f759b29e423b628141049ee3e27233e8268a27ad669fcfadd278db0d1975e69d0d237ae7d5307aa0a62850dc1772a6f3e68efcdbeb257c26c84cdc5a2ff744912245b5dc727a28bb87fe98c356061e3bc9b311916a5e211c485ff7cae037236095f94e8982d73518d3a9b0ceef72dc372b108057e590f719c1e32a7c98d6fecfb5f7da94c03249209e49cffea10f0a48e036a9e3eb872c27cec5526e31a52fcf076a2fcfb0cf840b2649c85c3a72ed6797148c2084f64f49ccba16bbf1f8cc7dcb00a8227f59a3ab06a019b0f772bfccc9941d7ac5f9e31ce0d8ff571d3bd31eb3902cd7250fc36f41e1cf4af372f4eb369e712a954bf423020af8aa054e56b15c7d98cbd634fbcef076964cbe72bdb814855e46321813f975822e9ee77313bcdcb29c4dcc72215da4ecdf96fa72af1bfa2df5c5636a2438909facf9366e9276cd5fc8da0f1c43f67fe01e0ee872251a0d2b7069b6e78d7e224652868e70136e2eb3e490632e942fd22d807529728c1f715be5cbcb13ff6987e9a440c6fff854f4596f1906be3345f043762e55344e525754d6fa5737273a94d921a9a580a036edec3a53b80d8fcacedd616bea72ba844fac3f14a5a1847dcd82b45435823a7df7338efff88a8b5bd858ec758e725eeeb5d60addfea6adb12bb604c611a4f33a16be2cb01b09eb9e88396001c47292c8c836d763fed5bfc0218456e4ebe9c997ea14c578f8269184bea8975efd72fde245c5d778f852e7338692fb597f14e27d07ee97c665c358fa887764abab7207c682ec7e3cb854447ca4e3b4278839252a0743b51dd3896c19ef5a91add172532ba768c3be0e1e1e8e8807c2d349d8ebfb77a5c71c27627b966325714a6d72d656e3743e59dcaabe978432c7a2408a7253daa0539c8030fb829fe1f272c472673c76c82a6c79dfbeea549286530fe08ee18e6bc147f0b9b57713feb88df872270df814a9ff60b7ebb2860fa60d3d42e428e2f6d4c72f9a9c81c278d0e620724668579857bae3081eb8cd0d3660b622c71ac2cae4db13cd33909ca3779ee972e827b0e79c8dc4471d4779820ee2b95887b51446bba63cc45e203864787b5f720955672c2d2eeeab2c4c8af360baf0e5af9e4f85b9844272e039fed92a564872696d9e8fc83651779010dbff3a9876bf363d7d159da9afc4091e1741b3cd25721613c63f5ab1afac73513b59159f86d0f2f50a02faf175eef1cf9802bdca6972e12fd133dae00bd6270a17b12607f2afb3207bf2b8496c8b923cfe716c3cc17242260cfc1552937ddcb51a29ade15f1022d9c2ecc4b740e8d65e8e49fe7beb72e57f58d32004e743345aa96ef61305450f7a7c3d889b3af642411500dadfd772a538f738bfb5f5896686650a513efdfcbfed1c0aecef9dfca2077754dd40bd620911edc08dcfdd8c8ef0d059729de4e32ef1034124460ce525e003f7d6fbc7724ef3c98f6fb98f6a50084827e2ba530e589a921f1cbc18fd2712709811cc3f200e9a6b3a9268cb744df1c166995bfeba5c3b27b720d6ac49ff2ef67cdbd17e721f1569d6276b35c32a984402259444c2893091c2368ee73d0f417ac1a2e87972ef49328446230a15e7b73b56d0814bcaf64136fc527a841fa5caee338d3391382d690ed24b0c2f5b05453965f3b6ef4f11db471b79f626299f3b50a340ba7c720f7e62726b66a218fa47daf1a57bcf4b6747756f645ca7bc55deda482d8f05721a1b10d13abea8f95db1d1aa6aaea1d68cbfdc9e910b1dd5bf89b75cda618772343fe69a90fe2e8c75dea4aae5c714bc8d8f272e688d9be130feb2df20b05f5b49eea4ba3860ec0ccb36269810ab26cd55d0f878335475cd9eaa24cd7b01c572ebc383885df9bf9b57bac485c624b349ccccce80c5e632dcf08201c1568ba86545e0a4964f33a7165e59a1b33576aa8be99f0ee31c64f6da185e58eaf3ebc26dae82d7f552d434f94798374c0d797e6613d568aa9e9de1df4526f6a68cc43c11d7ee9da2a53daa8c2b094a4cdd53ba7487a245d537d7948475aaed7700a7b951a20464cae261ca91009df39ea39bf3df3a5d08dc3f4190232a7f774e0a8b3231870bc1c674a58449d1a118db9d43a39434767432d5ff811306bd16aed9dd4772d4e37413378cee3bcf76ad8015a788a4b6e58cb5bb2ac184b090945cc9c5cb5e30b48b2de20b791935ecfe4f8fb8be3745dc526cf24322bd7c4996a9b33bf57233f2cf4e4f547b5666afea5ed1b677105283318812c37daacea0d895f6c9a472113babafcb749b35f9a445bce82a3e31f0f01ebd20b704fa07503a43e655b872e1785215b665a56b65664794161a94b5e53372e34c70d0c0d368b16d4b36b772fc38bfa3876276bf5ba1366824b78686fdba7be4cb99eb1fd7d5ce598481e27224954fff3e0a4b9bc9bf90fdced807046d76ad9600818270265edc623e77890ab2875e1b7139f394e92720f3d06a3021f2719bde0ae5973cf459360f9767b06179742362947a04a857391b3f0b1fb1f672b63fdc81a9a7ed91415d92e6f9bd7261a0f6ae625b275686c26602018c16f854f4a349974ce66ab0acdc4a6c84a6727b473e36cfd87a63bf8315e919188deaaf74027473d70303c82bcef504edd2727fc827998203723457a850f6585f3fe842790f24bf48febc0f4fd3341b4e0672562d0c607fb0ff97e3758de1905598062f3a635f68e91658647ffc772a06b472415b4f6edff631398a4dae1ea099b19ba8a1cab534b843118eda702e3978c457a65d5cca0038c528746f8530bf03e5b1d4d555762531211e50a29319d4c1e9720a9e1e554f5dfda5079223f520c7d5784f6d906192ec810059cf8a9ff3c5457268f033d84e6bb074351fa5cd3c352987eae71cdd01e0b0c936b03fa96b82ca4eb8d06a5b5a3e7752a7a83c31d5ec8125a2333020387888054e0351a744c8b572c215428d813cd7352dab7397c42d3c113f5bdffd819ddf5880d0506f3fc30f722abd91b0957fd5b0da928b11637a4b1e4b45f7fee6f248a6511b454284ebd171455c0a8793a9db74b9c44b2c12a71e1ae8fd927b366230f7ffaf9e5aa3c3bb72dbfe07e1e21eddf4f92e55560d9c0758cc55aa13875f0f6fd0d2e60c953f8572e45fd976854a006662220ac3d3a3af5713f55769f3e22d4e100ca30af65bfd7219d3501971f3f3a3dd681b4786b076781cbc4bbedcf74e16e0b1be676c303f7270db3622decd31288459154aec0b87a8546a7419c1ea1f6c1c030adbfc369802e7df856b60248356091ef4c5708cb63d0d539b7f02e2ed6725a368746f4e0f014496dcfa17fe57e004a842c3525b562ef776d723022db7a6811c717a8193ea7211d0662a2c1e0d47b3f4ec467ded5f02e8b5a00e4dee8267e3365527c2e82c727540b906fbbdf6e78c9142f61325233893db4adc6208d5dfeb877cef0059d616f8fc6202e4138ac353f18308b1c252c9f36d4fd78d53cd8bd50932c9230bc9722c056ed7668c8f79307f7e84b4ad585dc531aea433e3bc629da17cb734a3432415e1c815e40e402c51451015e54e8f23b28a58b4ef4726cd05bbe4eb7033e807e8a862563dc6e3b0598a2fde01808f62e41f805d131aaf10794373f3ae4c30724427dae8f7859bc45c94e07ae06f8ed9ddc352484ffbff49a7ba7fcba5c4af5699fe2c78cf92f120c226bd386997c2df8f1d56a63d435cbc38d3fd0b106feb4aa80971879b9cded1042f67f75680ac72d33aaadca63d80d060996c5ce76835728a33984b39bb2ae454568677c53ccde1fcc30a2df80804c3115615e8086f967280c0c9538f8bf71c61a9881ca8ed2c8d456bdf32f9f700cdd79a0106aad0f045604b7b1224c690a9b108d21e457b145bcc5a58ad6bf09eb27892aea3546694727063f108b50205d553ec578c81d4c81998147d190bbe1e0c7f977b71656ee716af13bac14ac27ab560e1c5b7de138fbe2d4d3928799d8a09f07e9c56ddb7623915a01e2bfa0f64a6cb53b264241efb8206451b9b8295d4850c550de695d95324d657a3215bd5b8c1d195086df452883c9ab2112a3438d2f7f00c01d7aca8c072c93e8ffcd1b61e5cb1ceb8fadc15a47ea61ae8a866522048096ca6d968e86a72a2119ff3a6bca1891395e494c608829139c491209e0503aa3d790be4e1a9ff72cff850012da2b7e4b82de89136f5306a7f1f3bb306590c928508356b5d9d3c13d68291bfb41d2526673c81250528915fd55bed83fb56bd976f6d024b6eb27437a994df18f518b997734805712494b356654ae6b84408be476f19ecc5576da3722e749343e5c75a27cc3d2237a0eb4c0eb7eb2a1c59bce3f26a67a8a3466e477291d2e330aba462db11f771538d125023b83b5d6dd03b0da7597d16a64d84b172a834fb161a1ba880df34099f638b96391b6bcc64e2fd8a74847a5ea1b5ec75356223099f6229bfe690438bc3e5b7bee39c2c454cbd9bb1b775703f37137ca572efd80e6b4798cb8f802ff11abfad239e1451de92a7d1be15ca431e91b6966472c6b6c594499fd28d0fc414a5228fe059b3f1e1b1c7b7f5a5488ff85c9aacd6727b1bf7507386f9d1fd6844133e98eda12c0597b0e6339ba376a23ced4a285a530799ed56cef266565aaeba36aa79f43dcefb15ab25a2da7ea2e2e54659d294014daf665b9c40c7f0ee9b51c5b59952e3e2dd2e2317f89518751d7ae22f617c721a9b3d92439f6ed8960e5bedd6bb7e31b24cbb8624ba4e6d80638ab62888802eac15248c3dc0f440793aa44c6d3abbe11607a48e098e874e1b6880a58efa8f725d2e3bce454c7ed68fddd94313900982e93dccb6fa48404f8a1fc3057f74fe72c85040ac1c52f12ec07bd292feaf52c04dc5dffec9115788e158246336d5002d2047b39ed20a7d5bedc161b35c0d1aff5b5586208959426762c2a6c30905c71c8cd1dfd37b361e1b06befb4d272f429ca149f04797bd302bcce7005b67c3537231389adb42c04cd114848cfaf77c1d5bff374c081b8ee93a0218908f8ab63872915dc362ea38025ea3830d856d597fdaf3a18b3f63ff629d7078cd382aa25061684517302508f59cdd2580d09307b08f7f8df6d088ae762296f3943614766172e82e9de78b6ce0ffce8794d0a7ae219cd7807c2f6c6918257266ac215c41c972f4103aaa23d3665d41876ed75650f9d86e6dfb5b2f1d788b406a2cbf28594972faa713942b52d929088e86be5b7af6b89db8dc1880709917f15fd9a11ffb0672e1eb60ad476a6f8f293bffc7d1450413c8624c91efb1eede922d1f1a3bfc797203221a91230f521ba01cd859d170ba42afc6fae8d023a6d78456374cf0bc6c498193f4297f0f06a622717f6f1cc298dbb4e3c34dfc2e562d6c9f974942fa1c1f074dad2a87cd6532adf76df1bcde8798c5676f37d2a3e9899433c12c6bcfdc4011ac26db4a6eb0d77e25270af0ebfb8a79c03ae0dfbc6e4ded15b2fdd791302adaa76e2d2ca5cb25229dd9d89aea62f1a951d25d104c936bf416695cd313540440c5108809a13a6475b6c8444f156f6088ae3ff1d825aab9386e65e961a5d9090f0aca4e8765d3129f320daf67a7b05df02e3bf063597693518134c84945ee72da757c470c8230ef6822ce465d478628cb05f2bba6094e01d0a8e0f3549d476b208207b20a969503fe89ee698fa85d75596d77ff6622182db3c9fe5ddd5e9141ddbf744cac3c843271b4991a6059dd1b54a0899ac930b65f6ff11b32938d44700b09ff3ab2e02704570ba3d0501b24426fe84a7d8ede8e580ae5ff3d336e9b1e8b1b11f17848852f17a2cbf1394f27161787884f6046faa35afe003e772e5e72781a0b5f54f6742fc0ecfc4bf699403eb24d3f4b97d431b2e93539ec6d6a1f728f4895567afc5ee3b94e046a2ef855b5654cb00d8ab964773ab56f1c85d126728f253596914606e521806e82217ec0020e42eb93934e75927fe3c48928d6887232491b44d61ee9f01550db22f73a91c44fcd94da238940f69db5ff8343e6917293f11f00ec0bf84851a663fe136bb5d7a1c07c688d0e84fcd01b5ec38d1fc162f36a9cfc540c35fe8e3b3cd6466d5622b8492ef8b9e95720e14955ae37a878723934fcfa5af25e468c9838f3f8e815c03abfc5a4d96345630ba2ada98c4e9e3de998e5e1aba45813b549a95ab3b4213876efea07183f6e0ecfb8f02db80ddf72eb609ba34f6d22f129ab9c52f50aeac0b484546f91f6cb6beaadb534af17ec725a0e5a94296f5af2a1a8c2e12308d076458d954822497bf64e62103739d7fd3d0cf6a847bcae4917db13be659e8c82016018af38e41735ab18b706b54bbbca72fcb61a8c9ffbbd55213a9a0784dbfecebe49a271fdedaf3cef07c53569ae3d176f7362cdd78e23eac4fc3017beebe17d82e57a32c1c48d77b13cfd96438aa8082c7385c3d25c634ed9247884143e987807e6132988d4faf2e9edd8438ae117728bbb4dcbb81d2b1c6beeec8376ae3c7ed554374508c85a67519843daf4bae1690ccdaf981d9784edd910f89d8e3d7ad4d218c4e724722c96cf0e8b30e312061583b9f86f120cdaf908054f7959aaba6d6173d928555230b3e2e90a80ce80ba72bc78a3381470856b04aa8141b14828e5afaf2b9f1c4abee60b44f62c0fe7f1723614085a2c5fbb12e794a57dcb489e3d4a5ebf862efacaa7a31e3762773a4667a1df0f00c8f8906dcd51d560323957c6d63fcdc14ff1b284f5b1eb2f6cb5f9727607a014f13fb5890a4b40e3ed8cda499145dab35aa499c7d872a6856320187204b2335cc6d921095baa27770d6e8a7dbf544f2ef4f3c93ed6fe1ccd6d258772b69afdbe1aee6c31fabe4345f4971aafdc31ac93dc12025ffd14a592773cb072cbcb10f416204bb3c49cf35747d500c5f9471335ec3001688ec3199d9f66d4067277a3d8e028ba79d01dcf53add042ffe1d3e3b9304d3d6806c2635994740e6086a984969b5f3a5cbb965d3c51d67f47fb781e24003ede219fe8c8d25d934010d118c96cbafff6cd5a51bd1c821b92d0be03fc4f8e287357c51b387583ed5618666eaa8161d487ac1018eebff00a5ac3b7bdd24b8bf6ddde0eeaf0e9d7bc2e27723c1afa66ceae1de6290f7a329bb30c4350d52529d0897adc1cc35570bece0bf7a1629712ba810732140a864f265d09e2fd5f550cff1e817a1d577a107ae872718d1bca3a2a525d5e39e8d04ccb4896640b1df15f27e85801d4e8cbd412a62e5a4b4fb38f5807f149b9028ab92ef15456a3f0b439b99664e3561b78652129725c91f2303e7a2daf179df0176ec0857455834e3bd9488f2ea939712293172e6981647ec1705193a7a395e8ae75aee576162833ebed480ad9df37cf5c55ad6c373d3bc02601d80e072f1a9e5058723451bc55960385ccce0883490cdbf7e8c33b2f6cadf17685099364fa8ae42cc8e039b18787254fe69bb55f359a750bb85b72538fe09096f4e2619c54dea7ed38732d206f5a6667986613585faee10996aa72709fa54ae3378febb1386cb1e848544b99ea21bacf50ca9a6d6ba6a9955d0372a84d6b417b867f864ae93c6a42a94d7eae665d8a87287f771429b0d6ec152d72ce3b3b489df6e41a00a306430a1f05c0951ab5633ebb90903f741a96caf4454f126f510a0bd0caa1523d013459cecfac8d482d5fb0e35615dcb65dfdce78cd729d258d14d8948ef4cc1f24a670868911791c7d2ee967f429204a0e26f1238c72397357710f651b1acc9a0b8ceec768b2d1c7afc3242228bd9e5c81620e17a27290b4ee603623e34576ec9b67076d35b96aa8daf766b71cdb6b5a44d898e4c56dbb3986591bdbdf28717023aff2aa0bb31ad1f1cf298e02eb74c946a44d01a172d44810b078c5d26c1e41674f66b3ef269e5e04c4b5dfd91c04995528aa565d72fa46ddac9dd5b6d2f489930a77f85e22895e3b2b6cd82db2e542a69c57ef8a72e8fa49e9096bacc2a55e43354632a9037dbf61bb11e27a6ef56670ba44759672c9b5e1ef931a8f484c8d2985390e2b4bdd01719080cd782ffeba0cfa14525d3ee0ab56b1bbcb3068c1d2d47c66dec2ad197e2d21d3704f01cecbe00542b28072ba10a0cbcc68a6ae6fc92f4100fc000f919a06b33f606833f03e304e6345e4721e06c0a5b47b054f0652b307eb04c7c192b767a4309dbcac3a94b73102a4b5390ab7b117080981d84901993cc3396cbefe273f5e75221e050741421d3ae3b372f2d0cd39fc6d813c48d79341e45446b43a7b7b3e33ec441f43d079c65915b5720142f20da98ca3bce6b3143e88713fad68634a75f0150bf1aaaaf589a1941072e9eed9882f8e2c3693ba44fdbfc7c7ef494ebb8348499aef8581b4f9fbd2aa48f69f65e67e20554875c37ae92b13ac25704a198c96ca2f94e568fcbeac777172aef70c61cf955d2f0d5dde9c7ce8a0ee221d08fa96adcba27074a52b95fca723ef9a825f9a2fe29ffc46017ce47a63b0d15c44fb7b382d2df5376d3c81661d3247d06e24bff6b98b896dbb3181a8a5bacbb6c7bb05f8371fe0f5c9d291d2427258d681345eae5a1db4393efdc8196e327988e59187203f1e97b635a3fae37072c695c8d62e84210b01a5f90a0c310a43728f7318f27579a3c272dc7743a68172bdb96cde1cc229de4678eccbc3a91ec5938eda574d7262f40d95d41959dd001f4247b130c3ad4f9f4ce2c630690f09652516de1e8c47eafc1a95828b00a6f972a1f790f140f3aad08de692e6f445c5d07fb7fd80253ca71efa68fbdbf1c777354dd24a1e76fcc6a8ffe86986c2a35c76e4bc6afd672f0ec5a84f1c633013b67268e7d2de4422f75a77665528c7c32c10b496888e564d4a5c61405d219ab7a600d92421019e5bfbd1142ad2db09257a3c02237addd892ed622c7befb21bc26d4fe4bb6f1ef45579148ae92045136eeaa25075f82ad19f949b5587c7e248fe8d72bfc2f1cdeb0f8317155a2641cb575de3769fef8ba01d80415e0267052ef4252352f3c65847d8a1a35f89af4a2a3a4c564aebd5226a13667214e20853c50ad3720eb0d3ad9d2918d44bc5fb76414d033e109a6b20ae84a82dfde54572659eb93af8637016b3981520186ddb39d0486b788d94725df0651cdfa7f4415971236b722bdf0f8628fa02285ea11230afe3a84e799266596d2c3cba699a83a65db5fd7279fff2004c31d66923602f7021914c536d949ea27453378904bfa6a149918a0205809ab8200733917aae46e18f02a8f56bd1c2af08eeb1494b349cd192df93721230cfb93b93305ac40bba513bd795b7ebfc7ff1218e921c8c3f2bb8b2869c72ef292d807c3fc89dd73f7d87f9d218812f788703e4f9a4923d48525a07378c1fad3d2f4c8397b399fe816a7bcbe92dd2746d9f456fd95b1c41ebf5523ccbe172f2f8d7376132ac9fa1c5b7161faace2953892f6171df7341f73389332d6f9b72d1120ed09ba88f0a588833052be170a3a62ca67d14941ca4855ec0296a7dce1f96da88a5915039bb30bc1404a571a6cc7ac0bafc2e3e93bce98596982ce7fe72b83143ec35cacdd0c4fdaf075be25b6629b3b56b4aeb00cedeb3329530d32d724ad8a7d3cb1d5378b2a40e9cceb4a856143a36eca1ebe56b7d64032c9dc8d272744d0893eec5401b9d580aa4231d35c251b186f4e642316d39bea2ed2b4e932dca66f4ed43addf6950f0d3d6abb94fce12e4c57d1b8b8f441ecedb93b19dcc72ce3105e74f8f599ecffa14fa76b0bf097c0cb2498394ade303df2b8c2495ab3a034d8393b975cf7d1bf6767c658f7f22fc44a4cadc1cce9c09945e7e736b2572c2ddd142d217aa5e4c8ae190515b9181e2b79584b98d2402abc4e4c77736ef72162c8d1dbf3c49df71ff7705bc60586352f22cbdecf3eeedbc6f55efda212f720029fc819988f3f9f5a53bb5cee3bdec2d9d30570640598865b58f2ffd25615069862d1b1d8f56c9804e55add73721b336deca65ed2ea87b9bab672ca57db1725091a827459cd9410ef7f290fd1f1d0981e15ed3f1191c35c545744b5bf72c721bafaddbfb315cbc1d61b29633a6a6e448553d22263827ea9547f77ad6bcce57aeed4c535dceafe4db86a0b450b71c929865b0e2471b8c85d03ed5504aba9172d4e2dca77b57a5fde48f9b5a35a56843281c61e392015807837da94ac2960c72173d57b96da23730bf7915ad6aae3e710b8c719411b388de14606972f19ba919edd5790f922b92a1b2fb0a426b396607fd868f976ebed54b4acb4e1c961dc072c4848c8847a0a2ada5649c41f53c0b646af986a3d8e8fd1c3cb3aa3ec29216172e75602e8137999b8decc204a0b83e752b4dc94d58afc030f280a0bc5352c9324f0f3bb994e3f53676c87073b1ca3be8cbc4ee6b1007a96ef0d0cd87fddb2a44d987bb1c59459cdfbbe63275324bdc5fd483aa65872065b624e78fb88025e372767edc1607654a9089dd117ceee8fef03bd3ba796379623c46bdd9b9af3e7e72279b2f7caa8e1d54ad4ceae9465c810ea5a99202e4f9d90ed0903a2e927eaf72859ba7b0982337f3be5b1a4371541aff2fed90bba6e39412101114ffe90742722b343a63c5bbc4a41fcd66334b53d0f060603fe05cf457daf3c4ea5d3630fd05e495c560acdb32a4be2b67344e4f83402432017f66815197719a1dbbdd37a460b660e2db9765a758a39ea9938910e502897ab774d398b937e0074c93071eb372e216f3c4ba3acbb981427ad6fd0942d14eff1dafbc0987df2ddc64724fbb7d7218165c99c35f492650ab72def6ecf87f7ae853bdd35e97d8412d4c2dbb4a1f6ef0e2b9843349245b72e3dfcaf3d026886d2053326e20d3063af9545823b60f722c34ba7d84e4df6180b76f87cb49821a67bc1785fd6b5eab17163ac3004323431f8bbbf1e25f97496473881e857a63d7cadba2a8c43b7acc4219d59ff02d70724477093a76599c5d21f9be1ea9a276e8720eeb00efada9d675cd191eba644f613a3bc79687914cf2b6aed8a28f7ee5fea3be49750f27e8e892948f2402897f72e08bd11aab54dc9a5c05f21d2c0e4e6b46e27eda3fc06c043d3ce3881dcc387241ec98f6af8ece8b9dc4e22b14edf74a11c3c62ffbe04dd1135f2d982aa10962a89ee42058fc8e753f068e2a8ad432d7d41c72b1ae2ae0a4be69e4a828d9f4652d69aebac3da8f67f11ec75721260605ea8732240874c4dd901db71abc4c954b7739810d7393ea6de75098cf057d47d875f165c56d3123e9c8f9ae786d93050fb890d6b516fc4230106ca946b6d41aad24485c625a241772514f309d261f097267b0ddc5d87a37700e9c44c7ecd0422e9a598168358b72a807ac8957904afa3b4ce8cea9d1bec8e6fa6f0a5269c304ca63410f42fcff4baf86f1160102f6187275eddc76c00f5d5c364128a19f58f2916bc163e562930708e9f52be08d52b272bcb0e822793e612a1ce0101b9223a7db4fa0c62ec5b6ed8e23f77bc7a5ab41728b8fdb82faa6472d7f1da1f871f04c8958c7259e95644aa5811d25964667a172985a1681dd268753654d6aae8f4776812c4e85b0fbc9a7c116323d4a15e9fb729e94517058c6bccc464352d91914e9c9beff8e72388ef1d520c783346eec98728e5e2394d1f6a7664f74e17b5971e8115ffd7d57b614ee654596af9a918e3b72505fc9b2c421634524e52f416f2399b62e1324ac12eb8a602ccebdfbeb29585b9c618a56b73a8e435cf394c20618b6cc4e05883d613bb861df9b8e39b9d52f72826ca69ba939e37f4f1e3c07e1e5dcf5184633bc11d28ff74fb27ecf7a8120724d586e1706480edfb057aa4426ca59375defae5a6630eff235a77efbeed98c059753440185fdfc020b31faede89642b8db9c0fdbb11f46c7bbff669bb5d64672192d39d805e52f12f81271d694538602ad5b4b266673512f298704cd7e757810ad2122970dad34fdf7e85d94178736c5fef535b1618c3e7fb192789c7e91fb7172dd2aa4c35c513b4e0713bb5884e1f45632339246aa6729233e848fdb91d672dec9189af7b45b75b8bd4125a9fd1b354f422a20095f1dafa0a68ffe3c9ad4728a8997d2bd8259cef2cf9cbb96466b37a95d6f0945f775ab9b3023801dac3d22f346e3231d1454e6c49bb026c714aff25de5b44b28dae2748f30077abab8d37211f410b6177903225180ed3f9d78537df933da70a442ee41eab9dbda59195c729c09a6e32e10b18eede042ae50e244fcccc352888154b4599ba4f91b4aac2c7203487129420b7af90b2533336a3d775f893b513788ddf0515a19f87083806a6251a69ad35ad3147d5b19381aafa8c4caaf5189ddda2c0e80418c0c6a4eafb472f67e940fad913d87b5f6aab21c20812a0eaa080376e6a0624a6791bf00bf3772f2d5d8c42a5d19de582722e45ea297d147c222fb9187aca4a4e92541ea565b491d429f80c7994c95334fda3bf83af44adab16d71a31b0ceffe6cb06ab30f846a5a0d3b6704b684299dea0137b85c26d1a0d3e7eef452538141f61066b9f58862de0fdd644050065acc2ef8796be58bf5d6d8afacfae90b9537859e96e98e2735313a9054a56f4d5e3d7955438d8c4fdc94fa6cf94f5b6372eddefb07a74a397276135d8ffabd3aa5142ba25d560fd0c850b885acf6a10db643be713cb87ff67280539f505a1513f582fc5a4accf4a657711be5af711910c12f0de5e65325d443abbb5b9a399ace39560efd41df378ebd27a57f660c8b09088f9b151449e7155afb9a1e718a7283cd385d53f33aedb5df19b546fdd14e650e733f910c568d6b7264e966d8ae08db076bd88e17ca6f40187059dc632d2c4cf4f3cf6160247ac072c004f8907cccc007c7619cf703fcdeb5df2d112ae6925801276957d4c99c72660b7d01ff1b3fa04a9a3e4ff57efb6b61d1a972ac0b257c1a13463c2b70b2c5728a97caf6daa1e0813c01d7ba9184214e46bea310bb8079c703402be2dec33f72f0a8fdbe0748216f69474187e5482f683fbf309804894e93a37bae2f42889672d3075a011246afbf5c7b1e0b39f8bee5d031393a4e85bf9553ed1dc34df3351f191a982b094c031e44aaf9205baf3ecd6f6775013d25f0a9eab731e8607af972dc24cbec1b9b24d330de89a4f7de4d97061a11822d75ada1760e0c365954986810e7b43a5c4c636ec18dc1e452ffd85a6cb5160170642453e91065b168752c728a1da25c2ec64432649f83aebe715f454238fbb331e5b1d14b2a186cac884069ba847a68f370ffdd026155d13031c074b7558c60de75ce87bc731075fb01df4c439ca84a0c2fca1c0d7fe80b97d0c3a8ac9114455c97b07e16ca4f6572197338adaaed1a4e03079b39e79ebf7a1ad1ec7cf63f9198310e95981fb12b7a700453d2064e53a904c0538e082b8bb2660d1665dcb962c28d4d893e6eea392be8cd1de89f09263da7f2526c63d4760f26cc1c745169f0c7430f496a6864b342d1926079983262395af2f2b8edebfb7564ac18596076b4a3fe527b6ffaf8f8e6abb0727ab912f99b9e866b3beec6bd8488e51f42fdacea18da4f5de28949f6ff2d9272fede12f99ee8d75beab5c9fd96eb19d8f0f025583ed9ded67828b555338dad579cf2603dcf2fc7218dc6cb3dbcf63eba8cd2b42739277f01b91dece8ccc3d97232f949de39f77ab5e96fbadb67025eb1fc2095e5abab14be3dafc0c58b0008646931cc098ab92cf993a65ba7336b6b3859b48b7b45ea9f527c19f3a9bfa4414da3b530a71a90f6c59e0f93af0a99a8a23b0c38ce431aca521022d2d95560395340ee914b0c912a37034a87d52ba2043d1a229b4b82c5aef3718c7b6d09c546729f847fb7c6c660db6f3c21b0da18f0f497a6241d01bf4f16d3a6a6fd4476d372d003135227614ef8e8a2a851ea2ae2f8d6f112c755989177f0d2c5f596c0fa04877673bca7ed96eebb612c77df0bef4cf39691d29a9e124376a641225be101726d28fe3e82a3b36af630be6d36174892b667e4ede3a1285211471642055963722458b857fe47dd62d72ddcbc552761b38a1d247697de522e63ae418ce4763b72a230cf14cbf8411b6b61ada9112342ef4f58ccef057add35c7a53f7e60c6123d6e0b5600fe8eb06ceb7274f66ee25b59acf362f41d9fedcf946121ac7e5424628224c834293e6b34a53baf54c09e3e6a6e6899fa8d8e1587ca0959fb1c1dc1725d244a179429178af69bbe8cbfcd2b59e6bc7b131780f944cbf6f9134290325ebc21d07046d0b33a2537b8971650a1bf3462cfd1db8e0a6b56fa1a3c58349d7290e7bfaba19b476c2cf765aa3d2ee3967ee983b35afcf57a77e3867dfd0b5b3b3851c950755cc71e7c546298ad40b97de6bc8611c3a5b53f33a43f81a9720d2ac8feafc3e79a221ef340247986672e7502b3f886c8291c7d1a267a3dd46a9f2e2948e309a5a7b7cc9f9514cec0a6191ab3cc0928fb4b857ab34bc07bd85b3a28c2dac286be0ee7efe12d8611bf44254328ae62cf24ede1629d57351acee38772dcf70182e807ae001220b8bc514d9dbaa9649368dceca8f1e013ad1ce1ade4725d9f14866c3ddee381ae675c5af021b335178942e842c75d22a204b5d742fe727404dbb1f13b340c9e631ec5d0f7fe6f670d0c8f52c905f68b2c7dc4adb7d97265a09045c6f5b7fdf543f6cd5e10cad7a72db10b6ba1247894ab46ceb60e0e080d7b392b8d5d6a66641d97806096d615f6dbed0012acaebc60151a3f58d1ba72b1e3ebec48fdd5b4538b44b93b8f1d4d5cbb2f5520558de34836d78de59f92641bcffc96d6929578a8c819188f818990d24348e3c3944e78f01e4f024692d372c0c5de0ea5a39c0d247126747b8d7bec1f2219ecd4974ea1466c66a87569a24046141bac7160c00505da90208db48c153d25af79add66bb58cddeb9cc5e6d2726f343dd4f332dea01181863d37aa911cce65562a63aa67da4dc48b61c0edca72f283d6800c325b7fdd2edb7841151f94fe5c0c6d2f096519410f827f74e1c672daa51c8f51059559acd60a00bb0fffeb95539f6a22df2f49d086921190ef9f08bde3fd22a179f97320f150280f583341eadeb0b19381b4d8fddc52ca52b9427216e1095c1c1c59d3583a23112b789fa3c57b1b92821f44d67a2507eef1734f6e578bce92dd6a1e7d8d0763a2a8ed6b2e641261ef0d785927352f14deaf83ad40b96c7a9170f03bbf6688defc9a70bacba0d313520c68ed4a28b7bf07b501f272681d6d0b4d9695015a6da049aff8fb957e6e619333b087be9596944b2340c872b39f16805599fece12383bdd6b5f7b3797aebaffa26d30aa14add3ba580ef472b0d9da85c490839a1eb47b85c7be82e4379b121376e3ac958d32e616bebfec72e73f0eeb15d9b65c0cacd114c5b13a8976197b8f37c42e92ce5c2e54246dcf728e59e00d6ad3d675a9be0804ac29f3a5f1d450b1bf6b2860910e668d20ec6c72ae0113ede76afb593104b8eac0d2bf7ec1721da2d11d8a152f63917fd20fb53b9a5c10dd07db3b9ed121fe6650bf31e453fbf9608132613be5532f9667f23266f5cb1009a631f74d1cc68a93786419ed83d68e8466beaf496935777eb5c6be0b653da6f39def0524f4106ffcbe5a2e79494af61b794f3d5a10c672809f0d947237008e763d7c15d20eeabfdabccdbfa0a77ac2abe16b0f9141e6bb75bd06a9721d92803f20d52c110fd6de28b33f2f85d0eda8b875d97d0c1e061ad046cae172c59df8e3a22cfe05a5056d38848d23ce2a47cd74bc73043a0fd0290e2cf68c72b5b61768b86a0bb08545502a3f888b487cc10f2e20ddb7f2e0c360292f40507259cf106bd499528bebfb5d8a2e7eb8baaa410b31c243404b7911ec7f401de310a99cf4c2252d19cc1a0a46a5804f01991303e3775873b4a582992146639e46721a7ac1b89dd3dda65c02c9f9feea871226d06e502a1b9c63596377c6e5ebae47e42ab357abbcf2b08131424f88d1bb6ccbd46e665ba4b346029fd06a1d05014e369c36cfc51053677b251f823cfe08e4295eda5ade752c85aa81bda230a52e72aa394f3db6dc008335ef2ddb7914c91cd1bc4e8da2270e47c10d43a862935c0d802600fdc77a147b1589a47dcc6570fbda89387a8743f7b6685f3fa9a26f2d72a21338c43bb4d340743b602e0888d3b288982500bda1e7f66357432a3fe2e121c4144b8a84948ee0de9dca938f00ea977fc9cfcb8d49e3eef831c77a35abef63de4c385729e31482c8909535e12e26ef64b45166c81f05ab1360cbc15ee9b067fd8d3743ba745247aa07887ed8c641f26bedfc9eb7ac27fe4e0e5d896135525920bfd62cdcb28b87835821d98c3625136e3b17280d3cb6d5496a9f4d9816af72cf1ddea49e767fd22df69b4efb17033ada5431aeb3e7acdcf1498650752b2f72b6ec4cdc4ac4d66d128a7c449d378484aad1fec912563a70589defdb542c5d72da0c27a184492fd367b4c7aeb5d47f115d4a70a662449848b08723f310dbcd72ec37d05091799c569a8e56fb35f5ca14c3bd5cb609a12b9701383e44928b2b728d8f0ab09a902e19e328609c555cf667da1652ef591339ccdaa23ad8dfaf637234c12bb556fe54d5e9c9efeaf70e8a8409c6d1eb05c9d8ef583f7cc33c8fe072e1d8f402f4d3fc51fd4e91a45005433fb2709a2a73c8db720a081c3f5acefa72f6de38b1287e33cbb95e623343ebacbfc498554405a873f1bf1767f685b91372c90d05547cf0163aeb47fb241d609f7a224f5c97529b9cfcf4a7ecde07c195724d70c26f5c7fdf9ceec6fc495d39d81011a1b73773d115302fc14bd2a00ab8724edd0f66fc8242af645702b4987336d456a28a0792f41ff0a34e79231cfbc6724f76667f6ab5c0f612be0ad355ff970e3b57189f85e80c80cd15ee2da15f244f6d81940b0d947c18b72cc23f673134cf978e1ef1718eace82996418127b19c72b482c66b07c2d0b09c209199420a831eee26d04c455e7dc7619bde5fbf636a2e70491f812d14d52f624cc8f4d3b7ce53595dd5f12e895ade47b7b4084b615072fda492ec1737f81f921bf87b6cf592ffafc69369c57ced3cf5adf5e526de7872a35e60d17d0ff6fa45a01c0042f21cf66bcd264bf700638fa6288afeafabe7720a5ee0d2805e1cb6159ea1d702ff7db7147b83961e13a8b27e0ba21c8e3b5972ccd8debd6de1ed6edd6e14d2f99095c5b284dc4b3dcec56dde95b6ccd917566fe593490ff33a560a9820fc418bd362d37d19476cf053df0c7e1e578f705ed9721a05f461de6d1f23a18baee38e95cf4f0aa052926465fe4a6cff0f4a0595fd7246677773709548947ba4267fa609dd7afbe5da0e0d0c575e3a3fc84e9994d7724d5773439d7b54d45b971fe92895c139e1ecc8c92261d22d00f0c2c6ca7dab14a8fff0b038d79f806da6ccd1a1deb65cb97f75515eff13164dd1348f2e400d6e6b18f7f107bd036775ca3773726074cf27f1f170c3040e7d642b1dce6f0e7f723d08d3d41146f07225c5502c628fc88459e10edd991ee110546ccb3be4257456bd9e6e618cd009cdc25814bdeae800096ead0c5592e6301724c10a0aa8ee5865cc248298dc58ec0d7769e73c206202558448c1063f9e73e7814c5f4c939f5372d18ae222f8a315268abbc5a766e2986dfe6b39f80ab15d7584766bc9289fc26a6ce310c291c8fff5a5bb8c414331b52e3fb0af7adcb9d4ec457709f9b941c07278b87e9e673f878b22f12eb6c1d5aa7f4a23c1c52aa051b94abf2c75dc8a40728a6c820480e4b19dae19718a3d2c17fc93c3b30de5dbeafa132fbae18b33d30f36acdc1e55e3902603bf7cf48027ec0709fc58183029ea8f81ef958cd4720372df225545c342857dfd419345a607b8833114495e7bc60adb94d81b3312e8265e916b1356c8fdcb1b97eabacd2d64545e16e905bc90d9afd2da55cd91370796515e27156dafa4abaea22d392207208ac652836cd46e977f53847db39841c524728b84ebb47cc96683fde3ab4a155c46e86f42b9963670710fd506bb69c33dd2720184a68e41712eb5d324e35b0a32340b9a416b734323a1e6cd345c5c991cf072b04d66bbb1e0893ffaf0dd5bec2dba5a3e9cf44bcdc2514564b6a1929b79e35df7b128fe8b840028c03f5fdcc997a3de2ee9d7786edd1217c80e5daf4ef3b0371982b308560efa912bc7ff8385dc1b5bc2bf6931a6d1cfe8b3ec8f1cbb3cbe72f9358b079d4d4886d58f4c4cf63314b84e03e57c7ffd4a33f14817165d3336542ba47ee2df29f20258514d2516a187d9e09f2ec3cd5117c5c2fe6119bbb7dd232514fa0c5bd2f1d9369de625b3eb92846ffeac97000d9fcb5749bc1eaab21172530a7af08a40dd41a836837da8313e25d90a6f236b390e8819a465b92c298e57ad75556facde0d8826d29e431af6775d311bb2453316e56ce846406c6aa727729e6cee4e5ccb5306b0cd3631b84457b6876c752c42b976c1e84eac323a584272ec2cca1ed5ffad953706a72ada3c90c0f575a9a47283d5756be1549ddff4ad32358347b1a1f492d723c6e5234ebb7eaedd8d1f9ab12f5bd1c1e99b35349f483d6d655fdedea4c636d87ec2efccd9358a6a1d96e4e13e4c728f9663ee5d2d4472ff979d7563f0ec5c5e2d946428e1ccfe0ea0d395b4c17a077fb959fb2f3a34724131e4704c1eeca7bf76bcb2805dfd1627bb250034507b43d9fce340d9c7756736ba3cc119f0730378cfadecc93a79cfc18bcfea463c162d0048a4049714b0728bbd76fe28d7484dfc4ce8075671c2f2a4f172dfe32a33d4048ae74fc2b87662c21d72ded7ecfb04aff0faf405eaef60ce602931268991df93ce38003df87b729195e6774e58d62dc72595d41ae502eec866618a45b50cb31cc5e52c8687f30464ba04a0740b9073bab93e8b9a4ca36efea72860b25fc4baeb2dc1c584b55c728781008832e3c28595625bfe4b2efbe4d53387c1031fef055bc90e6d9115a2482547b9b4dbdb94f6d910b416034df6a818ac46d2356e2c28b480d2c1b578ff729807ab39bef2e798e5947786051e59493b78877f2ea9084889dbc97250b55b2f3c40159a0b0e7df1b20975408ba61165c37a8f69ee2701e7f28c445e8076e44adf509ef31cccbff1a93272bd7a7f56ebeba80b9ac68d81b3595c31026d006172f34d6018f25c7d295af2736e31c11ff2e25105cec6ae01404c88e3b0fc15ec721fb00aa1a4c62282bbe68928fdba99a4da895868a8e17d5333e144f72f1e6f0aecf9de7505d08f93ad60af493c81ab79f0c6f4d27b212899a910f3b89b28004b427435954272ac16e2b84db98eafcb6f22928f90bf354f004a1ed96d5a643306048487e479e3fec5564f7fa252b98c28a1ed0c3768231ec4ae84caf7dc42ac09e9aed3d2e98d34dc82ca9c7c9dff0d08ae6ad354e139693352e1d831bbfb96169a2e062252b9673f68457cff2010fdfd40e7adaea1d2e47adb62f48748b1653946895fca49fed1ea8a173519b42f7ea2c480a94d31fa4d50613c85c0e0804a729e098a08dfc85a5515f2cb70a3c2eded609b1c47dee573298467a07f24ef72091dccd3bbd7b47abf3e138175e2c24d2e9e1b762ad1e5ef064dadc646aee415725b6ba4a4e35861e5d9f7b51c5a3311cfb024ba98236193090252314f314fbb722b1f4d4cb084940d6d05bfc8b2d3ac736b3cbeed689847d2d6485b9a8f3b4b72d61a662cc773232138b0d76c43003d1e75aedb0108d5c5dc67b2306563507f72b9948a5e4676ec8eadd5cf45c56e38f2a3ddf2332e64ee734e0d4dd22f21657208bde875cfd58bd36c0a80df1dce2ad782ce7ca26317612e487139c7713f167201ee7018e865e387ebea571ba5edbdb4fda3cca795741d80af4d67816043a972be3a7a1465b766e269ca0bca8cca653b4622410b2a92ef3a2d359d331e04f01d6c043f96959ac0d7fa02c2c490f5c0c3ced7dcf0a6610553ad6447c0a8d88e618cabf51f32e887e6d0ac88505f5bf52bfdcf06d4d64c57196467616733b05272c9d3a6cb45e634ed5e242be9344975c91c197b08f4848b972b0294ab802f2632595eefa90ee97ca26e6d55b558f665720b8b264b60110625bb93ce65bf365324e91f48a647c896656360da3be628025ac10deccee008b77f4793064b87263072a7f41bb800790c6c40058dc149cfca480fb39246d9d2882eb5924bcf52c5dd7276ac289caf5faa506e27fce6dba787eceb4d2b64a40fdba82943f48c89ee35595130843d4ce0165d65805c65dea2205f409e8707064cc3877fe656bd2cb23626f30a48cbee66b5871d828a402ded43d115a221387f79b93348599bb6cfb1d772200ff073b6246d6464e10b1037b7b001a204f4492a8d1e722b840c06358e752715513611867d4702add5469a9d42102b7c001e78649b1f83a13be2edbaee9f66ca7b597b16718a6d8d7ac14a49fc8b043458a0b8d564ce42403a71bd65985f3681d983410d62c3baef2b5a2fb2c9adf5564639a46cc4f367ebd7083e2dbe6372ce59b0e84e66b87125d48219916f7b7b27e04236c22073c52c82ab588baa89729882adf046bb070ed3757bfa38096141b17bfaea9f6a2bce450c299194491972eaf27e3ea1f756d85bf9644e8fc5c7e1490c3319522dfed4ed9df813a4143a72321f2ee8914e96a91b65510cab461f56d5402dd1e9fe7e77b0134d2fdb79f4720e81145fd8218bdb0594c1f3cc0fe3cefb8f07bd5cf44500f66641c46403f772fd74a7e9852917d3c4a9aa26f74b10319cfd5e28b6ef43a3cde4f854bd01f3720a3898e3956de3f719d02b693905f1e90a5adb9bbeecb0598b6603a1b8d755727624cbf8e2adc1c5dd2a9a397a17803234a85250718453e62609480d2035d92e6feff7ce5284825d6624c1f80904112eedc85f3d1da0191479798d26483cae5dbb879da204c0979d336b059febbc9f227da63b5506a0a4c815cb7646d3d22c72c1eac9e2c55a1ea6292fdd6c53d1089e4fc33942f0d8d47cbc86b9954700427244ca69343e98b12452951ec9b5a773c78735122280180facd08d93c38897551122f4d4e6251500296994f708e8ea76d70d388493d25790cbf4cb78ab47f600701ac516933d6afd5253dd6f5ecbba1b03a6b29329af0269396e16735c2e77ca72f8297dda719c5907ca832e4ec6db21038933035491a6ead05f9b547210b8626d6d289ae0badbf2dc7979749354e9ced7816f1848d3f4cda663edac8b80137072c4d586b46e6228d1adae4db9fc97ede6826f956efedea11d4c0d0f1e32253572dd0f9745b5442c38b67cfc5b92c766dd686a634e3964d14380b9ec2d883750728f3fcc92dddbd23f3892f64b1cff280264259ae21907208571bdde90a5f76562ccc1f9fd0d5e4cfd1dcaf1155061a5f3c711cf301765d3e74f6abf7385f7d7721567eba10734a6af023f3a0cc0757ae37685a22ea4649a98cbe01940272614491892365ee3e13e15153b99b282cd477475236d2a630b4fb7f9e9643014e3c572d08fc79835006f0b418f4cf79c641b4c42a928eff577024e74cf67f341f33e72be757e7a7333d30d7a416d10138471936f924e81482bd1c6d6498fcfd7f7fc52f56a9f6ed99fdc47c46496a51bb315fd213a4328e7dd9730bacf818315d8e62c8a3373d56e5026a83fe555bb9ecf34646533614c80b7f49a74bd17fbf1aeaf5421757e5cb7b6f41cfcc46011ba31af737331c67393c925a1c1a72824175fca7235b1cdb6ee0d2b3dce4679e847629a08902d8a629b3439aa048adae9dbd27d72baaff07240e3d10344283d570ffbe03cb37171ab5aa2805fc21f34daa306b1723d2d2649af92d4b75a264f71994939fa16a35f4a7931c32d0e4fb6a58e671c4950a59d55fdf4058b25fa61b1e75f1885dc9e2d094a39a8b34db38276e8908372ea63a3af140396abad451869ec199d0613dced678384cbf1c4b6cf9fe9ab64099f7bb291984b0f9514726824c3af006e153fe345de6d1a9997e84a440d14e1726cc6742931895898d6f8a9b0c3700308313e2388dbd42053a46acd0195b14b0f229682d15d128ba5813786a66e392099597dc0f5cb49964a267ad1bf35beaa72d02d93a392ab74f61b8192b6ebfdabd8def7fd8957eeb4998d4f38bac2231c3a5fd6c0d7ebe53a3a331c2266e4f331730fe6ea874fae8a8d15293805c5745a72e246ce5b57cc645a1bc5b4a2f747a9790bb77544d12a5b30e43869d395f9605cd5965e77cea173d849549a4574779084357a1e52bd41a9f5f8e8339958348b72544c3c980c0d0d594e80281aaaee35920cf52ec51b0958034c9826bd1fe4d3723ed605c4735c3ca5a569aa1e0221d8881c6dd3665b1549b6a20ea5006c53ae72d48b8801694db4cd106d996f117d2b9c60e4736bd63d9a9fb9d605aca492a7726f94b6ab7a1f0035d3be1f0f752a152184bbb8c35257e8fe351bc2ee9e368124484bf9dc594095a5ab6328a6f6516592efaff955d00e8e0153fe2cc4377d8072a7b27345633e1acdd8d9f8772fa4f5e6ed65903586f5d604a23b80aa24a5695b23c74c8888f3bbbf6cab5c0a00c054bd6c145c0d2cfa82aaeea1b9587a14dc72f23666d2d572e398051fe68ef11feacbc41dfea37701ae11dfe3c1bd66c117724ae7ccd1b795812ca6aee251e8c13e432cb55a4d1ca190d31a95885710dea210b1693212a84a787105708ca8757d017ea8f7a087975ee0b41d451d7c0eada472546dfc9cd3db64a5ec2f1f1baa8ddfeb109f66caf2ab62c868da60b5059c187246edbfddb2b2a8f4460e54ca37aa5f23c2e0979eaa902091fd9082ca57d9fb72ecb061f41511068515c8ae5edd45b64363eba7b52a1b33d541e22c68f114f072609e5d0334ff6473364a652d70561b2280f9ea929ec175689d5d9301a2f1ea51a0af00eb4cef75ee12df5ac584923c9300a54fb4d5890065e3f82c721785290a1ffa39014ce512315244149dd1021bfbeed3ce20f45e60070ba2b06eedd78672f01d6af8e8afa8ea88c68781d46c366411e52e132ff5ee50293e150428b6d46e41b9e6b03d75e80c372ad58080b274f5b1be514d7c6794628a10334e74ddc97206741fb0145f4f564e17b9362cf9003fee3d8e6e321fa8f4bbcf77b2b54f507278134f2a4894b23114f3e542985aa9e91341d0210a754f7b9b9e297a43c48e7290cb350a4850cf43bfe34204f65bae70220a17f58cfb9dc832aa71ccb8239c729f078e82def711c88eecbb3cd33c52e62c936ae15a161a26934111cdab3fa04d6346aa760667ad5abb055de206a2f1f638029a2d40f44258162a693e3ed9ec5d22d8e0f188ae03c68c2a349c5105e37a5e20d46507e2acd92ac2bcb47e4b732a6c39e4a24c68d0d2d71f56d8793632debc0dd8bda21ff5af0b26f47b871ef80bf411c66a9eeb042259cf3484f30546253a3fe5517a5790ef18cc7af3f78fe54732958ace5fae3d06335666e2c7f8ebda77070ad01d032858809f0d7cb9c46c6b7a95fb49d8ef308b20409367d4fb2f5584676ecf5cb7226f19d975ea49d53b729e5fba295bcc5d2c8bd9796ec9d2e5ee1d1304a1ae718a0c8fea722999628972a2e74aa71c5d193e6bd02ead4dba5395acace01d78e1ba538a56866b7ad3a77266378f28a7ec24b721c3181796bc4eefe9b3c842d3817292dac9202cba52d872053d334457ecaf760e2160bae26fbb225f488f6d9d2e59bf6cbc43c3300d74230ebe49e163839eff2f6c596942ea6dd53c9a45b157880340e666e2909ab508724180591e660ce2fd44554944753802ddb4d22ef4bd1e32fb26bbaccd1ae5a53cd9359aab24ded547de50559d0a24718a26a426d7d274be25b4cc7fc9ce3a7f723283ea08686672b5b4138d7de1d9c304f2a55fbd056ddf419b5a37daf42c4872fe7f0181838b2497124e43b639259ba2572135b41d0a8c725dc09f83a2174172f88235e755420f72f312556b45fb3ca1f46e806e2176d54f1d3c76efcdb3b453be0c865c7c449970919973e604744c84068d72068caf9e01744c46f4d827787261dff019e36a4f14880a42c0b95b434fbd198a8f0f400025decc758c4a483572c2f63994276e99c48e3597859821ae50a52525252628f8c9e5ae310578ee93138f2fd4acbd00417c29e5a1855c2cd4e957aa4b6c905f7bc2081c3aff57ec4c72e3d3ced3a818229e82b75bc7ab3974079f203c9537a1819728e3800d84437472df3f2010c85d6c3eb8b021b12f588eb2ef2caad2ded8b64f4c0d1fb37817e572fb829d83ebccd3e3131df585f33c98f51a2bcd8ce7a70f60c610e750e1bb1a722f138497746ce67ac333021e4169ef70b782e2f40c14e7e6becff2f253d844720542889542d93d2200dee12a46fba72e556375e1a4be188a8312a6ad63fde9720bb2280d5b7f90f6f89a092c08062448462e065e55f98902b5bb32c744e72172d81885390adcfbc40454ce06464329a122aa79f9b5d799827b4fd7842655283c5303699971e4ad09f21cfad2e613801460c75fbe225ac75261d635f06b4167727178ccf3ff4b0c8b18ecbf9b3659a47fe8afd6a6d9a5feb25d67af25567c6b3e177c02f1921381d46d14831c6ad472ff634c215affcb3021b56a48a5b98c7843bfa553965bfb261a0ea089159d0c4f579b238c5eb632d28cac4c70c3fb4c0e5b77472fea7ebe03d0a290ea16fb19c7c596b00ba74741ee23145ea961acd9113c7d63652f88d16740990c2e017d9fb03fb7a4e916a8ab8b540210e7200542c362c2fc6433bea88365080677a676260f31222c63b778b1799be7fa7cdeed15f206f14e24355096b81d84683246a4e33c505aa0276c8ed3b2a0fe046b2f782d2d728a7f59d9c97936c36b2df385e1fdc4ea54bd6679e1f233acaba740bd0ae854720a5b7ceaa42ffd40c1fc9acda9e7caa1aee5ced06ad8ad5d475826640fcfbc720883059d327c65503201401b1434fdbe63bcd874f8cb13f4fadbf2d0f2516172ac49ff1dfb579e51c13c1d81cc986539dc8d08bddb0d6102b2b1c3babac30a7226386f3b2ad023bc901702fb5a8a8326156ee195c21ef99366f7b0e83eea1e72d2032b879d2f8b632941e35203eed724acc3e8785fd1682fed2750a8220eec724be92d48de9d08638905453633c75b56476ae01326b83c08b064ff45db6ad1323630dfd419f3a4f3762bca6cceb43a659659babf8056cb487187c117dc9411675d0cb4c879dad2fcd9c1779734607ab3e5a7bda77bef336385996bb74ba35169883e300c55d1126c3e647e18f2a7cd5fa83c340815a81c9a3f9b87e4d3f3d44929065364a27b4460159db1dddd10cd9128ef94c7af8a1fe1915850845bb68e720e7eb05d02ba5aaae0fae10f6f5a464146a85c7a001031095397b9410a70ea1e57623f0393507c48dcec7e9138dac37956e09455119e689f132745d691b6f841c864691d85fe2b7c49bfd867fc9bc603b731055343cca21fa6497294def841721e391b9f6f19cf59de327d6e2c516e168bc5ad9b2833712672cce57a558aa710c1ae12c874fede1ad04388a1f6aa5aaf0a770209233c3430db245b98f70d5072f14228268dce44394163b1e88dc8fe91f958dc4f57b61f4d2e710a107d479172b942088d58df1b0af2b0888d46dfe2b23dd11f0c0f4a5bd75d3d8e5de86ff4036c9c5ffb157be7d8044ff347f78fa5a8a040feef0afd64c9863bcc64e20f6672df426b1ab3301b28a18eb4b4e559382e882380a6132ed0eb19d52c122f33a3720ce2a8e21d4389c3407fc548e7a30be38d01f304c96c006007aee04e273337729b1aeed4f1bbe48663ef16e87cea19161f68c17f9f3e64dd432b5c1cad85010fce229357c60877fbbce2571d57e64473fa9a97672a68d4df9aa7b6366a0b676c70fb979757e6792b3b33c51f2d73aff5af4d74e925c84cc390e880d305565972911a25dc69eb90cd551d9f1a48762a7ad8ca6483432d90c3232ab16b0107b56d73187a66a7682238bbed0f30cdfad9c57ad6e91eb8a4e71bb90bf745f2271d72c1a6a0ea427848ce17fadfe34b9ebd6ed38d845219dfd6ff5b213dc0df65f0724e3a28ae0421b9fd3438fd6c851872b4a527560119c98b7456e4057b8f4a7b72c9cb7030a8369519e2df76c9ad07174c1a04740cc7c99f0354dde4b7c74fbb50911f4c33a4f5ab0d67b5ab14c5a4828d3098984d9800f1bc0ba70bbfce7b5d514d5e241434839312bc80b7f4087cf4ad8aedd62a8dcbddff79a9d1d739d2db72e253101d5f6eb634d84df8ee201bdb85aaca09a2e08d22bea859cef74ba31272a3c8080989526868ab2d257108a09e85f02c7e34ddbd7cffe5665781cbbb9272e64061dfc3922683d30f8ead22a60ced5d22aeba2b37711ff50850a104d2fc54aac0e0521af70df0678c08a0131a636eb3fd462fb6762bb2e4c33ae1f0677c3d200b65af90b7529cbc25e51dd839c7dff506be9a66a15058bc4882b37eb180095cac457a4f4d71e5e43c98fa113f750ac2f1ac2194beb2ac984a5214092a42720071c0300b5f92f789e0644f7c912c2bda604e04b038a6af004d0756f0a1666a50085e69b6c14c00885638032d23e28c6f3a9bc398052e4412f6db983e984f722aa4f3eb57933c9abfaec6b7c7425f133d4427898a60c8cb9ed21f3fd970bf7279bf52a0aa026f783f7ec1fe09126c667b180866d8122cd251bbbda575af61721a8d081038ae4705635f61dd8c06ffd00d6890ebf7fa08949417b0246ae60c72b708e2249b1071eb00132eeb0c4eade991c35df058ad9e30f69135f685e8b572a4d5e615a5ef113f70b9fae6c1c59079add1138f43640fa7ba300b130e200c7271c7069f09193066836f68138bb5537f85e705378366bcc3f8821eadb892b772c86baffbcb521c3cb6a3b95c0eb0205f2f52aaf1be5addf68a12c813555487596e828a6692ac06106c5d99e4a209aa839b4c3a2e905ae0b8e6bc867bb2d42472fccccccbd020499b8f65aca60b3f5ed8dbe1ae9e9b91e5a65504d3a435b1c9728533d10271d32e9fa7d17edadbaee6e8de68f3a1e3e3195ba0187fa7144e8c00fc058ed25c521e176fd7b9b4bb2eff1404c88a2d8b7262143b39d6f5bb7040727f15ed89538dd12db1e2288bb475157982762d2a96efed54a49b3462e53e1d72c0384e16a417086eae8fb5d5805c9fd50e37ff001d68bf8b3d4fc7f7147c805d15262f2cc811403264c338563b06a303610336345dcb7704e12bdebdb238fa40fca6ae1b78e15cc673a1efd63bb64e4d2155971dde410c16a3380c6003879c725ed1875bfa83d648e0604b29454fa7c0b1e9e5ab91dc6224f7e7f7115ce47f72421295ff2c5c85a1d2a0c8933fd84907c4761510b0472e877ca91ba2644af31f1c8559ccbaa8883806d36577ef49b92806e4998169c505f0e9004422c319046a42ac5e41bf1b24bdccfe8c82be8255f286febff9f336810440a5238c6f8d6472ce100c7b633c4e0323513698528c014fe75f049d8918fecebf8b481d2ae0a451e382bdd6f8b233505967662150fabfdbeb99d7709cc4d3e0008f85f8f47f4055260319bbbca52d9a078a3fd51e1b5f6bb2f1630efc93fa6c81d4f00434c29572bd51e3bb645755e47df0ec5fe2911483318d24bab36d62f667550b3890458814a4f73403638299b4a4bd803049828a1b4a0e4c35566edf76dedf481a4c3028722dbd5226287a78ad91f0151235717ed78b7451e30d8d97f0b667c4473e6c24721167780ad01208c10de3cac0ea738051854f283f7ce04e77de06d2bc9d60791d0f057d5b165d031d430a0025ef801077608aaec876013b9a21abdc39fb5eb5721244cd5e5f91204da0a72af1760d5acf22bfe5e7228f56301662c2886ee48c481e08ac2ae45fe1ed655f476762e450c779dd72cc1927564ca7b3450047bc4c7297307931752fc5becddcda12c933e9fa06f1183015af40206eff52fcb7a7ae51fa9b4f77f3d70adcbc40f94db3a20234a5ffc9f8dda9c086fcfc5056bbf6ac7247d3570dee9b8cea866318dfc6d2eaaf4d675dd81d324f7102f7537bad5ad81aae13f2732b44abc5cc43ad3ffba49970a2cca300580b2e7b2af7d7bc88b36f72193e39d6afb29937383792b7b7fb49802be2214e6b4e2fd402451e334335547277a9a34172f9fded08402b1e061a2dc7c7158968093f214fbd56431fb07923725a13a0a21bf00fe1292914498e9700d4e2ad593fc4e222bab010ef0dc643d13249e1626974d52f4220e6f403b5f120ad2146bdccac3fc038edef187985cc9a723ea862d4b3145ed34d1d0f8af0a76230968c4e5de8d3fa8d66d27610c01df73900fcde30f2204b3eabf9701478b0ef86ffa6f16b04174ebf7a7d58791835fe2e0bdd8f3f9dc7d22e6d983f9d62999623d063c40374b30c00ef367aad274a754c13ddc6bd0379520159acca1201ee38af9fc7e9491de49ff8dcf9556d21f2101cf7d4325bf055172303bb7a11b5ba0e1f9a2034c0b7925bb67bf872874c6b2b720488ff376fdabc5c407ed1d7d7d0bc505660217d3464dc89fe3d47c8acd1606f03211155f9327b562fd87be58edfdb4e57d6e7efa7f0b04690452527f2c69d7268367371402ee650d58864296de61147f39daaf92b44add2113241a741c7090303ceaf25ac33ca152279b10869a989806b25c4f4c22e295443b70736c0bbe865bea4c1a6ac443398dcfeb5815d9ae58453fe6824411df4f55291c19289aa4003a2d8379d9f5ca75451731d65f1df55d3b0555bcdd9c05f30dc0f98410a4c59728c343230bf79b80abe296c507651e6aa6440071443e453909bce515afce08f04f733f99aaaa6231a10e2675968ed9aab0f718b7e07213d89e9b9ba5eb719284ae6c497377adebdb382a9c98d6fc35153a513a1a366f495fd0c016446242e5e72684747fd9a351d233b65899b4e6a0b2a03fbfd760c81bf53037a199e217dd60a244dd12ad22d594f17c3b237e2e80d2c38da1ac8ee02ed7f18336ee13242c704cb3787f4bfd52b6594a2cb86f939b89c1b124fcefae316114ffb820c50a97361d76e1630e57753429346b3957b5944bab0b9c775537e41aa5cbb311c6501314767204a8d156298f13cccba5c0608155adbfb286997b60809262cf67279a2724f4f0c5f222c4faa9ff918a37af8a62cebe7bf95707ce590fd2d0ee4a46de4ff726009d235e7b4a88596dae4c5410e42390b25fed1f8d124272182caaec9892472eb363628b3e686b11020b54918817459bb34714ced081c231833a4d668c8ef72953b3b7dac8d1da9d797387833fdd74139b89af187db93ff6276c3daeb69993cde59e9f62f9934589f93066c7e2e153bd57c2bacebc91f57c1a3e40e71b03d250ff2e7af9865a1701ddcc87796df8413e890deee6ea843d7bc79975450f581204a0336392df882a50811cdca658646e969ec09129733e484ce896fd11d888255e7a52a53f58e1fdbd185f51cfb3833a39bb803a63ae0a6cc2057e94e2ca20672ed771138353e94bea6b9aba4c75ccd8c245a346e35b2f7d0d42dde8d56a0380a21ade7bc84474c9b0ac325a4b2ab1fdb7cb2527146029754a263db8df6857e72eea39bf7e8b77e21df0f1c3412916e8a843b9154d082f40dcad9dc448c293272da43bcc1f62443ef994dca4fbed72dc5cd44b813ee14c5d80ce1cbad7eb9e01c8f0c22205683dc6effd663f561da5fd6b0eea11d2d0481aec8af71afa1a7a672e7ca6d5851041f8dbb6df7dd27237bf2eb02cedeab82f94127434ad260cc6072bed7fbac3e256446b480d599f4def7ed9f2ec8bdc88512ab4262b18187df7e629f41cb1abebd9c28a625d64c5534e55ea43c7548ea1bb49659cc7ab32c7b1f1a9128d9c954db146ba84ae48b5fcd8194fcdba4ec1674013be73a17028f9ff928b827761c323849ad17aa8ddd6469d8180378fb4968f88c600158f9dcf3972272babf254145a521a02f8855676cf82b062dbcf2aa5e3a28d9ea1340dc0fb1571a945ad06785f3a0689134355496d0b7b7f99132c9014adda8aba59fd13bd6c12bcf5b5a62c9cd3bff3a5085544fecdccadcbce4e6b6c14771022fc193f31bf5726e7ea90d01316c6f87ed969873db5de761f2db927ad4f5aab626cd585a116c40d9b9d546ee9f5fa451a40c6b1c1f9620cba30ef0b2c410bccc728ac8ff8ac872f1fcde9aaf65387933420c1cb8dbd5f08bfbb9cfb4b372409e3be8bf715965476f40e5c3d5dbc0415b382de91f8a34d79da03787ff81780ab96d5eaed3c8f42f4dc791fff4f2c20b64864d897e6fd69e65a9bf5aae93c34f1efe4da1c09cf354025e8c6ab35791f85e42a562561bc3f504d005c4fc0d8ec1de1d30b159158872bd422fe4749e276a01fa95b5eee7b9c1da555400854cd11db3ea58903ca7d764849150d06aaa5cded24d16b10f93ca59e72d9e7c0b681d4629f68cc18037c17254b1b148dbdf10cd6a84df3aaeb2a3c59f69e13ebc498c7599c14e7268fa3272ec85428d82fe3ba3fde77212991c34ef8bbd41cedb86514e38361140a68e6306fcecade153f1506f620c0479f329ec8855b0d83a8817edc5f71ba16bacd14572e792df93a8feafc4bef4934e01e552db78f4136f61fafaed081c3f0ca04acb4c3606aebc8918d372286036be88c59901cdb4f64bc9c20844056fe2118570241c6c72c653a08432726acfb83f89f6784e248476418cb95945e16518b6d67bb6723f4a4e2fe528d5a25647f88418879ff18183fa5862c7137c1d6cfcde361aa5720a9760af5120ea285c9d025a3d1b0a5b7b2cd145735916d2bf38a10a6565a172d98fb18d3565cc26e83e61990a2ba86c564c82b2fd60efd419e09ac7ed247d69df16fda4a2685d2d56520b8f8025e540669ab8f0c66d64b0dcfbb05818d6446401fa25ae8318ac37694a95dc254a66eeeac023e460d5ac36256b90c0ce6f5072e724cd76feeec9fc74cfc4ca17e41266ec9d0a75e37ba9ca5daf67cbdf72637290a1c9850c09288eaff0f51b946e37491bddf5f13463ff47491034c451f2d972116f6829bfee365117494c4a240c4418175560812e1132466abd2d48d07a0a498e501b0b3850cf033b193772ab8392987080154922bf4de55ff5d98362658c7259c3aa6d9d549d0f37ecd86b73acf303bd5a6b66a4b168a76ae19022d47b3a7287c0b2c16ca2af31b2fc78c85f4ef882756831c009006c5d0b57b7f923558972f3fbbabe1456434ddddc71ffa78fcb56184e155cd951135c15b346f290b74b5aa0b2d56cf8d4f7dceba571f4c7326d6e608be55c54fadd0f4ba22c62eb18746c56a22219b89790c68d861d45e57a6aa68302e1ff1c6c6d75df87d31803310272962c59c1d77e1c9546542645ee921e094a1b6bfe8ea5338cdbf548b9186e94724b7341b6738cb3c104d06500d4729bd147ab684d9e5eb32013a875da8b1387720ca551d49cf36a3c9e72f8472bd570849a3bc6a64465b3f7ab208081d2c31d72fff92a7673515856add718add75d6ba6fce9360efd3b143c3a9eb341dc286d25e519f464b58ad9e1d12fcfbb24433870cc785eb8cae5b02f7042d8eaa6f1eb072be55a28e223053897c540a5d70c187b1ac5ba3556222f1c3b0cf6f9e5fe9272f4ff6d3f94ae82c092086fc34d334379401d12542a209115a79925b893183c72d0c06481e02ba62ab9ec6c17dbd57709b8d45d44458db58d0d1ba4197d6e567214fcb2de4398d3215478346d477513afe10e4b1d8a781c9aadf036247bac7c72b7b8261efce379e5c9fa651243692eda8e92896aaa716795d9294e8cb97a2151af18346a9d3173896c6ee728b5bff36c13b33640edef899eb697d702a1977120b786594ec117a11dc10c4a76707665f056f86f893caa2cd71c046350a2cdd53eb369a6ed082d70c3c7c15589b5b9b145fd9d6e7522dd2dda5ef206cc30d3f572bfa82e4c2acb763597a3450cb08cdfbb92449846a5ea4fa7b1fde4fc529f627228c97bbc0bc1769aa3002c742683d5b92a1f989a6459086c1e77a56d6bce5872f9a1424ff3c096b6e6276faecd39c9f9e80ecaafdc7c75b6400bb218a79bc214dd9124f9bad03315b4573b356ce420923499a16c619cd6a11f50fbac257f72722d6dadd9b965ad03c7f2cc33b04a946ff142fd667460b7ff6a0a3c9c614413106684cc727fe37d27168ab73c496800775722f546ac365a73848bb73d73002e721c1d30ada01aa23e1f8166480008d70cab0e974ba99b3fefdab800796d6dae72afc508a30bc9a41c2319866b1349599f810bb719e62d57c325873e3b45cb3072c44e26e3c4339f4eeb1bfca81c5b14f8423aaf687450a5931b7b2b1d2a885703e94337d10bd6c770e94bde7c5113d4acaa08092525cf670fd4acf74088977a712d51cae8fb4965c7ee6a0c2cc0008420e5e810a2ab7cacac656ec33677fe65702a9f8e9043c9775be94b1183e9358ed1878a920a127ae067bcf8beda7f522f72d72d47a18cb0701040907f74d523727b7f58b08bc6cc4b7ba7cdcdc556a485724371df9f6f1df8893c51e5bd7f5c1305ea7588e14329232c4bc0dbe31636fc72d2da39ebae84b8e748529b0642661614269c12ebf8c42e2847c2ec99799fe77237e93102485b0f818a86ce79e06b9f7f7b9f795872b07bf89814b4f96ac69e72f0bed41796b91e8848c2bcb870a786a5913c6c8d60da566e7216943d19387a728763aff861bd37e0fcdc99bc042d56a0366169e512684de79f9a0054f329bf721448aa459e64352961f0e4065a4240526b1f06cc849e98b2e991790d2d07ff72783b63f91f70a4eb69f90da82ef2a2cbc18f2416639bc51d510a7f143a0b36042d8cb0295271fcd515fe9aa0be756e517100a36e49dff8c180a7515f9ccae072ab4403fcdda885492b39c6e684a8e13a270197fbf76cd244c528cc7d549414728d0c2cc76a788f87d6e617b886424b5c77d40a597d2289a4a1d1314bace63d72c19556a6a2f818bb9c2b1ff14b5d3247e921cf1134d8d6f3f674c021c781d3688f79cca31ac767a0ed67fd356d2b6b6e265b275096d195914ff720beac7a837207aa73d64a704ffef9240bf32c8c9a4406a9cc8af953cd8237150fe0bd059c4fdbfcca400a346eb4b6a20cfc8f9cca15256055ea1d9e12606b7103138e0b847279bd457742f47f5d393d235032cf3734a1ef065949bfdd6785aeffe8d66e9a723ac529a5e2705108444a51e2cfe5ee661122ec1aab22d8296aab9c783eb58a3363f07715fcc3e3ad6daae6256e794e9d81e5a1124cfcc223eacae59f9b9fdf5cd716cb70f60d51a300283bf09bbc70820dbb4757a30a563bb23d11993ae3047201913e94174eefb266ad5d4cc95a4d8299d9947df6c3e14926073acb75f301721f99ace41bfa8c21994d45936729c05f6eb0045b633ce04bfd99e8237799f51b88ab23f32376f758a9930a96003498f41cafc6e616dd7653ed5e47a3570d5a404e6abe8462a4478d0e88f390c581a6eb7a5ef61ccf54c29f49ba1ff3c68c4a56c8a15ccb05ef357280ac49d1c346acbeb25816316cba21c6f150f8e40e7aad5b2e88fcffd7c00a13f6383c4d1354ea350c7db9423bb3059781841f7567150d40c7852783fe3c625e2bf71ab2aefdfb89197ff1ef7d0cec486867cac7903c340f67f51b74534b1403551a7285c0f142f6a2000dac84fe8b1634bf44221edef334f3c20c875433c50ae000d2106a2ef4bce17d38a8ec9c1b05040b0b44cebe6709b89336573c448676bbc1f1c00036de6d39cd319f16dbf6228feec8f385b39c2093c4d644798deff4ca59ecf04adf6773083ff622e9e6d7c949752898b4a3b020ae32445bb5a95917c99f58655ea1be836be6c3df0d6f8067813d7927fc7f2601c4d0d741b88d04e8045c9824c60d935aae5012e4affe6c052fab63ec83b03372ccc14e993a0c0772c3036b286fc8377142dd60960b44571cff354f5e1f125772f7b8c569e56c042335d66b98ff75061cb30e5d6bc941dccbe561ed44e4b25a655d7d346b141ae3e0abde5c22be2f6b7eea3388325a507281dde22acd89e7bb2fd52a7e3e262fc69d89ffa6f0a4322d6857737794f111431d8c430873f4ad421968f08818f6da546c556af83dafe86c906a82fcb43e0e52344c1ce55609d9ac72da608081675fc4468a1ffbb07cb263df68d2ce30989ba1fac99b37d2315a7972c6bef1be5a76fc409a2053615f1401600f809c6e37458693695add2ce4f54b727a0c6ec7d07f0fb66e2b5c78baf4e7cfe9276bedf0ac4002ce10946a5d9e8e12fc7e6be2e84b89ab33e93dca0e1be729aec752d0160e42aabd762a0836c45072a3a88bc6fb3d04053556f2c87472888c5d0dad46dae0efb466743727d4501b4a4fc607fc469c562ec418ce78c252fdc5dfdf05e3a399fae9ed67f0182b87bf725e42db7ddf3ab915db7b0fb9e10e59d6c5305c57383e885ad4f547bb429bb772ac2e468f4804df96ea18134bb88d8b4ac1a67ccc2b2e241503435356e6ff5d5bd878ad352908feca0c1f12cc3e645fd59e29b567259307b25bb77f21f54f8e72aef6072ef1a7a70e8ce33f563dc720cd49870947ad0a0156597c84ee5e76a67209b464ce8705772ac9467d781a5e64456a268550c9fb634dc891ef72048b4b085b31a718358f22c0aed1b5a3c7274ad7633765b0da17ddae7d72890baa469d7234803b49b4e42e89b504101a6ea266f72548fbacc1836783f456a6f086eb5772cb930ec3053032ade7480a1ab85c2442cc27ba1dfdf80fae38c6cf4542047572b16de69d578c3283c18f259b8dcacc3e0bcfe8cc9f3b7ba9950a5c6d0b75a1723ac46ca742a9b1ba6131851504a2b1440972dd80d7dfcdf8f267044721f01e722ff16d0923b2fb7029d5472874e1a47b3d79c4363032d26513e51f8f0f340172cdec2c792a49080ac599b4ec6f700347e4a44dafdb58cc083500ff0bfa987f7220fdfa44c7a55a4be187f7f557fdc9f6554030712a4cf00e29a7d78a7a5bc30cf5dfcc8aa06df37eaef4bb7c9a7554b7435859c448f840f07241f4b3b3d01a727225ecc8d8d73c3092958f697ea91fba09ba76c3d70a0b9910740c147131327277df2f73be5a288bee5aba5070fd86f50a06b5c68d101705230238821e2deb2774eb841c521d993c3cde6c2eff441bf76971fc82419abd4507f8415ad06983154296314b0c9e02e861285f94a27c6d0c06960556c07857dc7467c53b6c59e17273729e0247f0938480ea33e679c6403226bb34f8faa71220f2c39902ed2c3a42eff494268c9fd628e0bac69b1662c0790b631a13c3b7b3baee40346220daaf2ee28cd8273a6a96583aac199d4049aa4bb3b4d1e0dea42b8c8d68a60e48a504728813fa09bf0014ad64c669b956de599167d7c40f93a4fe459d9a33102f62d119bafa5bed89e05009ab0570a6e0d92cd52fe78b2697e0f66d135e09def7e89a72c80bdd556d32484c1a269e708344b8d7b2aea2ad9aa4d58ac6fe255a2bb4cd7231f1c1c43413cedfb0da37dc9a2516cb994a85fcf8171c68eb42ad71ac28b6722d393ca43bd5e1fee24916da7890905bc0464b282719ff78ded5fea821663b16df0b43dff4566f5ad6ba2854fb6400764a37e72e9f56e13fd0e2c31ab5ad9b49e4ebaf6f4420aa73d83acd63ce6ea9b04b6dcf71594f5a8355da8e4a5430d930a526050353ae221c7442df5bd9a0926b759e52a950c437d250deac76699add72f30dd78b32428cb5cac834c8a12bf5d464aea599683432bcbb7459df16921c72a67e09d4fa552db2310a05d536688e9467f97e093c1bceb0b40f15629f91a272b343964183aed7e156c2e7c579db14aa60de147e4378b1c1c63772d787211d6ee08e77272fe80ffe3779d37b7b3c9b0e355fc61627efcacd2b5f96e0e37b747206fdb7aaf6c0984729d3a907adbbd29072dce7325c9c7cc0e92c87c6db9d224192e496988a63406c3a595e35f416db33cdfea03fee2e4f63d9950e3c447beb542e7af508bf8ccf9759601a3458cfc05e14b34d9d160b74fe8f800e458c300b22e62b25468802cfa10587df6e899cc8380299355aaa30070222f3123367428f72aa572b33864f8325c2ae1fec63eceeecef733261b2a1942637b21d4dd0304948f1e541389c1779d272ec547cd5abdead303d9ccd63830f02dbb0561110008d724835fba1c538b05d0ebb63488ad718df0b24b220162bba2198eccae2678419065cab991415d640689fd163cce8423f862c4c5f0faead48a4c7e0da1aef18ec723204bf581ba19328aa3f7f60ed2447ed52174ab33a057a907664781a78af6f6e8e63461fdf55e59be8054ae29e2ed85b24dd7be63cefb228ac33353661c0045e63aaf55b4ed5b78fb6b777e0e0542471ba492874da6bea308383c0bfb12dd072acc8c166cfca784d3dc1f9a98adb3aa5da1f7718245acfc907a1b2b8bfb73a72f830e786040e763463cc7060a226cd9dfe9c388570a34efe5df8d3c88005d721e3d23e8ac67cda12b14ff97561fe289bec4b5405642c21982c971b90a3d90d7236f2fd144f88fbef7ddd0d6bec6a65bb5db8b4e37fe767dfc1a90ab5238a3a725a1245644add695950a3aac64517ed3a017a989cbaf2e1f0fe273812d09ef66b8866ebe50ed0817f63c3a1c5a4afce88183ae7f012663648d341ac6979e6ab0e6993b72f4d5685645a3e8ba5eff0804d0430239d177ba481a366c8b8c1b3fb4613d12aa3d8aaf3edffa35a7d3031fe4998c5949d820b9ce09ec2305cf7a735726775990ad8fb8e8804c3e91866418c25f2c3ad27c497f001b148e6efcd84710497ca195b726c5fc7c929048b9fa39a93fc24dfc14f4f964df3dde48449d6ea05f98b4fa51bda7ff2e486aebd1927a1c4b2f2a8137b0b798c39842efb73d4fa7228b80bf9406053c72e99acf12c2c6bcaf1b2afef6ea9d62cbb6064dd36b61c7253831ed788fa53b5013ac2ff2b2490378692234ea79f51f8c104fd45e8cd31271be897c108be826dddca7e84decb97e17af87575467b07a79e49d684583a7067bd020efafa0c7e57048840c970eabea5f2165e4039793decff19e8256a367972491c7d9807895f8b6e32e30fef44b9fd6f2fae646458fbca8adc669ca0052e72feef677fa2f39c70f530de69525a1f20d1b201c4fd49c8b45c9442176134c772c8aa7ef8a894713dcea5424372e3e2e68633b053bc5dc635c3ee77aa55767472c5a3f11c2d9107f6a0b03727c7b52d7cadd4fbb372fde1bcedbc0b388ec9f972d388d0127c85c9d72ad04abea69ee784bb8a8cdd2032529201a677464847ce72b3a8330b0f35e350d9c64ebbacf3f7908e98788f6e2859376546684eb4cbe172d81b0a4bcb6fe1321658f28d9121d3cd3e2686a4358f8c31fa6ffc26bdf68772b6ba8f62756a0a7a6d017385fe5ce36deb8424f9ff4cb147a8926bb4ab47a93602f752fc8a625868ad320cec02eda5de40b6e57710b0c16d8e2b5c16615ae663734f86009219bb99a00d657fc87cdfe28b205ea293bce78ab4347d73188d0e725200df08e21040606972b0d8b05605ce335be4f68a2949933c8b6d2db687a17255644e7c58897b5bbf0492cab971634b8c84582752101d953df79de21cc0674ed857500cc048364b5e50e06a662bc6b06639a795c8db59d11520ce0cd7c1a92a7e1098f180e77275304dd0fa22b27574b05385334ee1bcba32342865ddce965c0959c559d94068fe7c9c023c1d42a0434a46c4b2dc8359d9c552212031533d28d899f4cfe52d14e4c5fb6a7b1967e7f8766d856fbfbdd42203fc0cfae2729d72f1a1cfafd96ade382cdf7bcf4c11d92731d7bbf758820cec0e42c3f48cc534474b28b99191f9b162326f7237a3ffebb43864f61090f7f0e28b06f322d728c0724d07fc4eff11f878324e68c663a4307d576244792653e5178ec2d60e30760372f68427b51d1192143e28197e917581b9ed49588ff6298194ba3dfd48d73b3a72467151b88dc5e67444a6f37a5c8ab271c952cdfae024ce3e7632a0fabca39572bb850dc01c6751be9b75f2f9d1c264831804d531c2e57664ddb2d480607655722d4cf8418950f66808a5d89162ae112f361d311fe9a171334c833fd784f4b472a9fc636c791d7c6bd7fa516a102aee2f5e74b089eab6e668f82593796e1749726da36c40e0175af9182a6562c3af1b6af51f70e30fdbdb7e70645ad24a7b5a4112c553b2c6e210da460c7b81aca76d749ced034408cc339dadbf3fc70f152029406979977321c5a62ff4a6d7431e99c5e26674eb4b1a7f7f44e5772f1fd3ae09466de004891476e0a7c1997e7ada5a9afa02d2dc550efc9fac89f98ec1227a725c4c3c57105bbb81144c75609efb76aa885d9bb8747d56bf5639eca9dd1ff85024bb48ec9079ee5eccb82e8b2b882e73df7209ff83e9509b41511c64541bfe62c97635e0f8d9e95361b91154a947472270c2792521451394969a7a5446c1102e263ee9bcd5c4d9eabd34d9eddf9b97c40076d21862f71d5ae64e7b1731f9747288db75ca24a9d4b4cfbdff1ef487d7b10eb90b56fea2c7db2a9b03926a16bf59a5feec5d1851c4883d9bbc885572623b3240da9a116b00bde9783d9e6096ef7295450c35a2bef2862a7f2c5d540d00b9d0e74ab12dfca6529cf8e73ec7a9742334021089cb602e324deffda07a32595377ac0fd1805f2aa060de7834556ff2369dd4000445c7890e2fd5144a5a678c08eb15d7ef0b64c7eb26d67953ba46b235593c18e41d8fe4beebc97c2b806d0e03ea6eabbf70a1bb081e6e08c4645da42ddfbbbb8b4179e01061f4de8992f7ac0cf66efdba4722b4c30b3dd3cf541e294279b5e0eb729e35e5de7f5b7e75e5cadc077b6656767007959a52a56eca9ec35736bc4aa5478e90d9001b7e1e3cbe8bd6ad2e98d53bc1a9baed3f3a7187f3ce381bad2a9c576fa7811adae10dd11c763b047b21f486b18d600c07feff16e8831675bc7b65181eef5a0b9c362bd4f518df41891262c7e6466ffb0ee4cd234ce5723bd303e6f7d3420424dd9647d915fab5434cebc1ef1fa544029850835d6940721541d0b300d9a0e5b1e4c70135c756971f6b97431ddb5bfa45ab0403f71f4272c8eb82f01f689ae347a2808459132089a42e138314ff5263da720f2ade9d5e7298d3154ee1cadb8019e778646b8f83e6c92130baaeb22c22a9cad9f4dba6cd72ed3bc0d391511643d6a4e893178f32875dea38e2ad2a4deaeeecc9bccab79338ceadc570ecafb36502a1426c0fc6a43af0b4bfd185fa6990e8e90e590e89f172de5c9937965dcb279372ffdd699a6bebf775ea7481f3a3fba45673e6d0e33e726b74a1f6b54650d3a6cc7e06397d8b445c34a096aa390ddc170ce85ccb36506aef87cc42b34598a70436e9e3c2b634f5009ab68a3ce747c3ecf6ffcc5919fa342a4bf07d99d8352f3e44ff7408510217c6d1c64b0f72cf7bf05a45c86001db7209299ba5ca19835d6fa0203c3038e0ea0120932fd907f2146723542e80b19c72a26aba8f9b3077b6c68c2b9653c50358de719619a75d4e14b49779205bd90c72666e3fb3781704b179c2bcfe8e1b56f782024216f1d140203fa43a96b2fca21acccb2b0188d6538f1ba2b239fd001dea1b252af0594bcdec9c2a4e9a8babe73ba6586adeed5986701a504291da0562d7b9de19255cbf174bd4f0c5c7f989297237cba9b3deef2942f163f266abe575b2b97a4a6ee496807d52b7e081122c357244a20aaf7670b085e2a76b0e6a5192740b6a4296dcf02f104094552ffa426b72c8d8107daeb124f81adc4deb99f59418494dff3dad0693989d902deeede9f75f09d7b8836063d7b8f9cfdf55470187e662ee1a383f45a46cbea170ff9cebfc72c3fec70dab81415d10a7615e217d48d3dccc47c716756ef766c63379a5b372722cb72cde99cd617da53c93b950ad914a573d67d94ae6cf54b809f31050b518719d8cb6782ebe82a09c530aed29c50e40250b11d09809af7690ea81da5356323239814d8ebec5b60743c5efb316a75a28dfb96e85396aa136071c0d81ae8a7144e3c9e30cbf7e55ac3def269c23004079ba4af212405bf183111088fa0396785648b63a972efbd129fd56bd2632f38af0a529c9347110d0a8fcc9a0d10385500f5749e1cbfd4a66ec02922981c573a14397980b9fc481cb442e4d1076a0cb8009fbd3922f0a19a7e851e8c9af0dc13f3e6101ec9e515a6bdff7569f5fddcfae72fa3d372deec8b9d220481fec61cb5afe0dacb5ac816dbc16fe1e1948165c5172d964c209266be5be123afd576dcc8ae393893d81dc8aa2f5ee7ab147c48927192b62eeac752e354ee76ae2163b8106c3ae591ea1856f73f31fa1cf433e334f3df029621e4c686cb5cc5891dfaf196ac5bb8bb6ed70d59c9d0f7966ac810e0838221ab42664fb9e4783f23a545da6de3554404cd3fd4c3bd8302365a03210f84cb6ee2bbcc8c9f19ab4139fc3fb09702386a0da69f30fd04c08a1f9d24e599d5940466fc226cfc3593add21f9f0d918a445a98bb25169d818577a35dd4e35b472964f132721ad070b821d3dc6b9baeaf756e6b2dfd63b0a870fd4f53388cc0d58ddf19c47a22f1fb8aba44592afd0a815b296867b12f798838c3a0cd92b0e4572898fec436c9508da1fe7bb4f526971c48c3b80783d2910ac65ebd484b8617972134eed371ac7a5b5d556b8b40295977f08fe96789a83d82799b1e80257fa16579f08bd926c5500ac33fe25c1da2f2217c8b441d182c5e608a4aca98b2cb0c772da48ae042c15b04a36cdcc4269f5c0d390a463eb5b53bdaa5c3aa668041ac3726d208c404d6b1844d2c6581f673650697eca44481d9393efb294af8584eafd3ee4cf28303b2d44970259b419c11cbadce6d1b75664f5ae46b3a743cfca570672ac36782a2d369df846c81a890cc097a01d86224562d7bbd2720d4db6a804cb033a6533daa4467b7c7b5651ee1c8c65519ea21095cce307b30008d4bcfff4a73e0a155640b34db556dfe07cd193696b22179824b700bd3bf2ba4011e0f1b8397255e87e73161b8ebc6ca95343f49b3f0e963be382180456b4e0c7bcf1929ef37277560462e9127ed363be25f8e00967cacde56379720ee56dc694351cc2d48f7299a90acc7d072375127b80d5dadc0e2b64aff93c1feabe61dccd17a6361a6f1ab7d82f4da3a1ee343213e18db89b7bef657e7cc7ca2aa326ddb18611bbb7d97299ca96537a8b0e1eba85a19d0cf2b607a32e2d0f2fbe30365cdb4e78321a952fd496acbf7544f82fed44157609d2e1486c39228af8636bac8e7be003f034c67223bd3a85d3060c5dbf6ec3f6f0c539eae25ff2934a4b6c270432c64fc95b5d724903b11d541340397104f3affd494a431bdafa8ce7100a9fd9aee673eb70bf720ee039e0a4536b877f4c45f16bfe932b2de8b368422152151e9eed22ba09035590f9cf8fe12f2a3e27b05341502f98bd4f18fc5c3e373f9b47d963278df2641ff44ab8b3ee986826ff0d194180516215c675be96180f2b2d5c5088ebc7e0f972a86a09c356dc53e2be19eaa1f6ad96a8f0fe16c7b4c4b42771bc9d1dcf3d4c72adb31120009be8bd1f141db23468e61bbedc3d4c035367c73d04b51c85934b3342fcc289b154960dfdb015ea3e1db47d6572ba4c33fd20807dd7151dd0c2e372816ee2d9a1a6c35ab8872f48c18dacbd3a7c6804d646f34ad7edeeb824766d720e6238ef39f838e1e18e4570ae0645b00878dbf52f439562879e7f1a734d50729eb936ced6093412022a1270f6dbfe756de08803f731cc23472bea094f6be77207106db9c80363839035c67cc67b71a946121ca9b8a2936f2c00d555433d5c1e0078a16c8c6d5698ca62fd04d0818b87444c31df6112823047ab532e39898e723b0728990170112d8e8042dfdf9669b0208946cf7e8469c0708dd45287e40672262b95953a448679a1e677800380a29658d38997345eab7f76952e555ce42a720a0a88726bfa59c9ee0300d98c48698e2f065b147c114f9fb735a07afb43b07246766e1cf70a79d000104edb6562158022d8a24f26837806872873b43190ec727cbbbde6046dd63174297410e0e0ae845ebc40991825f867460ac62a6e2ca3723f73da33b9b6230b5590b97b09156c9cb212e759044f6a27a775a6286c70d27238795c0a3321b10bb6e7012f02049df5104cec7e820ef44c56481acf2b71717252c1e295ee24335d15c7f45928b761ac77ca3f54c1a31b383df5d502972e68723a5eaec35d9211ca206855f5febae6bc944a89d88bdbeadad184a9ad48cc51726307384e75dbd23a4ad39a3e524cc0740cbf357c3cffe59350fe3364399a4f724369f84d582d2629d4c6708341caebbb18b89dff3355233632e1bd01ee6ba335542f5b1263a64487f5ce9edf69eaed5f1dc4d0028e505ab2466e6b11b54dfb72773da52b1135f965065043486a518077a4cbb3e59d0e60b81441eb9f25817e62d9f64659b8523f65e54530f1006bef108f96c3f0d7f5eac09b94327f0cdc52612282b0e2a187101d99790fa628e7944e5f3d69cce68360427da32ff90aa1f456ab852007c87db6eaf8ad292b85b121425c240e8c0f2fc467d47db696d1b4f13a8c8e0f7490fe06142fcc454866487c3c9ad3254066a38d2b87c3d245688b2672b534c442eb1a28d3bfc77a1aab07838ffed6ef139f342436d8217f790cfa2f72d2b8b7fb8272ffbb07be2d470509f34dbba6f9976b01f025bd188c9b0a1fb84e0c2270c88c63996464ed61f373de2d2d373b6b909460eb7413c52a453110003ff2846a04e9611d699087e0e3987290fb4256b5fa1073b723092cd66010aa9072a979fe78ced0f53089a56387fedac3c1afdf245780253672b0cf5398568fb2721ae1105c26c18e82789ce48d2d410995537d1a94cc130c6d26f6e0bfab4b1072db2453fdf51519e5bd3d54c4d113693b70edc682049d84499c52267b5060937295df2b0f3a98b472bd7806d951e72e286796239a81c7e9b0bf0d0e13adde1e720d9ab002b599181f3955382ca8e0896c286ed2d077b0cdb50b78a1d80f4a7314b20a73e2f1f8a08c2054aff1fba44423c93ef88d0f781baeded822c7f4a94d726cf58c02aab7b58ee1122cf8a7ecd9e19cdf4c94ffdbbc103f9d83ba4507d47279a4ff89697c3de3567d00ad6f73a5b9c142351bcdc135dad129c0ae193c190c05a45da97f610d3a96475948c05ec1d2a3ddda7313ec56d3f4cc3b034ac2493aa81be19234229867cc83be703de9e2c08e59b661517285e8e74fe1d942fdc53a245099c96a8c9e183c686f4a26b209e8224a813aeeb7b13ee52e04f717d1d572bf0474c794bcbb54f6b738f5c3d517ae974c32464291553dd6842c4109af5011c5d5c271d1f14181ac198d015b5446c78bf5f54649ccdc6ea4f0a3acc8e16672350ef5f33f23095248c28e40f146d0d356af9f0a020acb9fd006d3afa3a4567290287e818dd795728fcac1ffa2d089b8b96fc0161f5032340c74f9671e9ba7725ed04a14ff852a90f72930335db0ca406f5f01b23e4d356662f2a689f3a68d72820b7356051069ecc4a59bc8a7405845e52bdc19f7a903cd8e849f301e85a3725ae85eb1f4fe9a72b40254e4e8e2f0243bdf6a4aaa88d67d94c6fae3ec8f7070d7c18af3ad9c2b4a825d5a64f22334516cd9f2dc8219029316e918ad94c62a0f219ee3548c10e6d8218c1f96252c03df580e4dfaaebe0f565d6e01802e6294728bc0999a95ecf8b31267f8111c3654365805c6713cc718f8897707724a358b728aa646fe2c1d9f9a1ad6c9142d07127684bcb03d2251ef41b32aa3d7d52e9b72fab09b7522dc571bcf076432090f291515f1716f1fb10ed880e1189921fe927212ae1dde3f577bb78ff84bfd4364b32e014150bfb7703be791ac1e4943206271b3695a8abe3dd5550f1b7dda70a82b2d1efde4825fec6ff7c8f832850477477209500e28a0db4ed6107abc88df7cb6da62c89bb41fa3eb1ebe041cb63e352b7275e7bb83ebe5cb6aa5445c89105abb472553962bf23e192380126ffe64abf55221ab708c368f12d380b2424e680f677bd0cedbbedfd4b70e7aad26068a385b723f8ab7d8c682d9965480ff4684bfef1a3d1cd3884128d23dbe09ecc566f760028b874605ef403083f007586cde0736a043fef48e44433b4a98998f399339517222ee72b95424d7cb76e9e2361958656be76078ff5135fad96328b911400e5a19d225fd0c39a80b7b47836f2872e0166e36722e7d8209b9dabfbe17b47e32fc3d912ce18af61e41cb7b55389532d792faf29041fe87167a563d3d48b3ed7fde725d8bb9b0118b82758f761c467352f2482022a3eb19af410e899a8982ef01c4729e32479b87d13598862197a4c32e27be26391347c183c57cd03771aec376665226c2ab21a16ae8f74e31ef9f3cff3e9f28e95463ebc12f477339b52a01a678720bc52c3ee676e112abd8ca0892aee80c72da5ed360f2894d5e5251bc1933064fa1fa62940fbbcfe64406f0e44b78e3accb5a33148fa52410b0a40a2400a84572f071c721b402e9abcbb13589f9ac306b3a003b2170cec73e115ab1e2d9b9fa7214edf3f0c247283ec3c8bb756ff198ceb4c5ac8106d3f35af623078bc2b1f037638f7503b5a18cbc59f0f6b67cb956dfd60f09de9d7ea65b36c3ff6d6ca13839e0c5e8df164dde9467a7e5274c0b5af8145432f329671e0f14786651b89d6072ee75066c01b3d5e8435f28b918710a4ec777efbed1c3285a0214f6301e56c672adaf5ee2149d20ed80fab00e056014dfb719c1c7be60e2dd5f4c6c6ada3031056cfffc5adadc24b683882662008b68075d87dc7ccd8e34c4f30af03aa0819672201aa54172e82ee3b0d543d1e5803d80428ab1f0139e64104b24641d15c26b2c8c8958a566e31dd997fdd82b69bf6aa3beade6073ac793827cd15726c571fa722ea79dbc0dd60872f6211da521792a4de33fac65cf4181f1f9dfac0dc8f4496acc14fab4f108dd308890ce5f02b761f72e92faf407608c5cfbd6274301066972d2c56d23c1bd850bb3ce3bf496fab2104479e74c98463346a149c478d9ee9f724f0cb78c1951315c38b011034defd03e032dbf5be83c0f351489d70f50298f72ea3e6691afef96f653a902c0594677effbcef6f12b34bcc5048ae6170233e5091df793a989cbf08829fa8182657f01de1f1de272c972480588438a761260721d23ac9ae0a5413be993840b6a1c7d2a4a53860deb2346333c9d317ed4dc8c3e72ed1dc8038999468048f2209f55f787311197e7b5845bf3baa4bfaac7d03b302624c5e86f6d1a4b43c8ae6214678a506062f850156043c51f5a8e90d7bfa323720dac54aa4e59e42cd4e4cd448cb2297a7a05fffd7b87f7cfc012e57dc2983e0c17ea6c0d174da767aac104c70d43a427b7460fc2bc621cd1a14a171700554172abb85e1747e9715867732afe10ff2df52edb4f776c9e8b8fda42db486e9085729938b1988dc550bfb48cef737e82b08d0a0894a016be35a085d96ae38556c27254813a729634e49e16bfc9966aa0b0b861e13105f96fc1ead04f95d5231fd472daeaca790e4e110ad014455f0b4ed9966aaf5c9f13cc15dbe508a9dfd4963272e15bcb83ef6fae885069cfb75b6b702e1c0181125ce3b16ef9fef3d0539bc408a5dc8d75c440ce13acea81ad90100c75bdd3ac91393f5d3d62a5a72e10c0074e36e2f88f5de2b92ca411ccba5aec8f33fab4e6f2e18d52216a4897c1f9a28323f6b7a9064ab82839dc72e8b5636998c0182aa59079a8a56cf0f2459f45707872d5e4d19bac05a7e160675081eb64fe91ea260755b7da20a4c48fa3b5fc58161f45aacc3f952e11cf3b1616705a9ecc2c45e1e048624ad8cd257855cf8b87eb72f5644c06bf5349e129810b3a88d9bb9a3be296473eff67ef12fdffa981e7bd6eb77ac129e66837e2e652ec3e482478924059b47e5227329ad1bce1846564ae048300f75e1586b3b73716ac8c153847e9b6a5a1f0f6aeca36fdfbc46a08b8a97217d98d5a9d1657de62aca2d8260c4098566acb84a0e9d85003c59e8d53611204719a49b186111007dc1e1ac330ce094ae647adcd81a0c671fab0a856fdde067272087ed07a2806495ff58c6a13d48d2403b55f5dc999b5c55f6820fb478193723eff30b617085398e85359d7c953ea7a0f94ab225e750e4b458e1ab746a8a372a32bae200f37145e02f693b75876457c1f476dfa70a816673a21847ed3799652ef4268b3a4e71e52e35f2eb91d6293166b766c26f7ab728ec7aaf1614917683bf6c0b1e9be91774e55dc7f743c0721c0429c69f6b82ac7577f56ee1005ee877273deeb6d95d08694b9a6140674879c4b8886073d7ec23d854a74794edc8bed0e6a3bdb679fbc0bdec7eb6471b2bb44090e0cfe73a62c872fbf9c40450588e261aa153cbacdcadb17b2116f2c19c82d4056f85bf1d64e977b3b6274627780f572d0a0e895c7ff328870d2143de37f60ed6631f8b487ec2dc71390a9e593c18872beb02276b34b89034ea5b9a11f30b9ffbfc46c4ea3cf99bae761f437d641776e7a1dd46df9f6e62e5f11ce18fb3faf51a8fa0d3cc501ef6aa2ed6f92f4d13d38b860e2548831aeef15da32f002b5e670a49cdc27b312b50127d5bb68562aae726bbd814e096ef04f8021270ad154be20ce2e6ae3ad789c54a92e53cfbedfab7242844e519ccb148ccb60dad4b64b75f34272defb4427cb1cf5901498b4299772f87e46412ede07d52378783f6c8795e11dc64015fc6d183144359c7a887ee05b71792154442eaa45dea501dcccf2b6b67c87ba1ed1fd410a39a470f7b1dd9800803bdedb5625f0c9cee9680a2693db4621098d217884b300efa2e97d0168257237981b699971ecf78faef4ddb5e7c2fca5ceb969d69b26cc7423eead4ecbcb724e8178cfec306cdaf07fbb043bbc1d12042157a05bd241b46c73bb3c3a72627248deb7c404d615d5e1debaa86f86f727ba6d71cb7b51d1321bff45e25eed48724ed47f5ce137e1bd1988d78a69f82beb4dd7b26ee015e1836ba061eff462ab5c5b2c2c082052864855d8d71fccf5ce0a78802eab6eefe0a6b1db9caad0326301b69e9db8dc0dfc135e6b8f61f1799b4ad4a6bc9e43cafe0b442434f5b6e4bc724fad7d27cb714fa9a3586cc2c305c2c0cb7396c5546adeba97897eac3b476d72b864e6ace539bcd0590c7f8407f6fe87a7cca31f9b7246a5ccf8f83d13def2723f74fa0469e7f03d8597e11734caf292d8a8c4789067e7caad622724515616728589117c35cc1c0267824e52e447d688a3c1a5aff33a48ccf909f2116ed8ed63dfc12f2a95e27fc98c23c4932f5a78e4506a468fee2c3c4217aa226fdb6a7f7298eb3a7c68153de0969b3be4c15f5c2b418462e32771322503df286f1749dd72275a5483a81be2de1db57aa0a59ead29304f659f43817e41d13c17a3f3a5406ee6efde31a4eda185b04ae055118d2b1dd82cd1ade585565af299630b8d48f262af05eb33d607663c96e0f052161dd4c53c24757e9ff0bcd69bd843bb7ad13a723f9939104b23b40d696b0b14f6a5490c74c4b3f69b1e8d0b60d238d37740f8725fd8672065e8284ca077946751da1be3c09c022670cb505bf0caf1c97cc0ad72c4b916cf62522e2049420c21f44c8074ba442f01f0ced733547513223491921e9f48d50b2d3c6f823a74d720a76b7147dc1c2abb20e52c6f3a954adf6c25fd72b64818dff608c8edbd15588ef3a8beca020e6bba9864633556debb44ca5f3e51407082a7e42405f87b239023189ad78807a92445135f3c594f1cbbba50b12472a7341175e932cb160b3cb42298354e65accc6dc28d16d6c8801633f87d57047210e0537c36625f8f8798cfda5a0e19e22edd6370ae0824a6e64fd8e4c7dfda7288101bfedbd7843999c01d66f633fdbe9a61f928ee464e4b0b662fe7ad4b11498c6fff2bc1234f0c703b566749d44fd5c5d0b61a895bbdd8aa4eceb54e22f47265bc28826ee8b761055f441e5ac0688a2f610df31eb453f111a08133c4cd5e3c77c41406c72a403ea85ba845df50eed084d50593fca5bfc2c59ed7cf4dffe2724e0d33c2d1c6666a70726942c8b6d6dc69982eaeb7a362d10b7012b27735a53055db37352e4a5f84fa70f620caa4bc23dbf50be4e942aaf0f9cab168e1cb18729f5394686fe1e6e4f72ace64f9068e626c0cb81ae44df1cf34e0f3b546495172f8c2930f03a5f19a83fa136b013990f5a06f033d5aae44f36d5c293eb907062508e155cb6a875e25a5018c8b782cd13b3a809a0fbb823a7baaf51045ed4c2272b247c79d7520ccef6bf9ee87bc68a9ad11ee74d63c1e4b49b8f8684947aea97235905e68726a2e1ff6e5472dc53463030624b36e69a37dcbb4cba9fdde8787720319c8e260fae06ec1b03645af0434c57eb5fe18a400b85716a9271c82cf4165c2c457a959a9693cc2bbf622ed4b3e76e1390a0c1ffca7a2365b5c16099d2972d57e556699aeb2d35715a7e3ea837f0a3f0cf03e1c16bb70ac770b06dbdce1727bba2f9bd94ee6d1f768e4995af6148c9320dca7004559816fb62b90aca2487220d2e22d79a0e922f3b5a35f8fd0a37217d5b7c5dbb88c2ae0eeccef58d4327281b137a55b65a698a5fa17e2a3c66782e41e227fe3db5782fe8b4a08b4b3490e950a2da8a3577baf7527b1c505e8ff04bd06c8c93680a08a66cc234486af67068757d8b0270216c010f26f6f79ddf19035230df630c8f376a051e54f837bb0089851e38c099a10d61c94520af9b32a9fec8f0cc2d8a69e78f74d91310ecca15814005fa622f28aef294743ffedda626238cc94c24b1a6e922416a117ea932f1c39ea5ae64b7fd9f1664c041c3dbb0c4d396766b495ed72383ff9289890fc90727e022f99a56969e97e51846a2b864ea50e2823e5d3252eb019196ba7dd04022b9cca4a05f51b31d0f749813e2db73bfc33e4e05d480bd8acceae88373619ae19cd1b7bfae93e0d7d7db00008ea0f56352d43dd199a7cf1358ad203406f9bc272d199eae9730fe4353e2bea634ac65bbf8e1f905e1d20fa311b9dc8db9a4af1632c5510a63bd9fef989a073933a78bf21b5b059bda5a3e3a54b4cd817945e4857eeeaf797888b6732adba6cccf31d3a2c66f1b5fbf91aa85d97ab59706542a172a027a361ef7614e5e76ece75546a6dd8c4bb0f9d4424e756961c12a72cd4d5725e2e7dc71039e6e3d8b3554808ad365e633ef4342c9f0a130d639604a400bf721dbca03af7347baab7197084bd120458188f7fbf2e11a05c1b5ffdb3761e7572fc2708fe4e7eaa8851e0d11b525497d297ebcd6c507da81baf241dc0f709da72466b4857b5138191172d462b7e3ff64f2279acad9c336da1ab56bd7bbc11e5160b06b64ba5ad8cd40eb5f34aa611ae66ec6f61a53710580efcfa1e59af938561b669d5c2763df44f24868cb1d791c07391ff76cd3972d45b8fa6184c0eefb813439048b4ab07646e918de6f10331caf29e12c7389a671e0669d177077140c972da1da0d044f3fe7ab72b2a26f15a7682059f3650f1b10bca15ea797fb56ef772a7d8296bc038a15d8987705447778731c9a3aa4bb64d3c5b1492770fb2068c723ce58def2afbfafc43e27edb6c1cdad93ddb8d403fdcf4da30b63e6f5328de7286ee563c0e732a3319d5ea67f84440358903e76b99d702695c32005affbb29722ca2cbcccb96a701999f1cd930e8ddbfdb1c4531d70a6e4968ffbfb2f1ca2d72a0beed21815af277513aaf7bffda986241bb51fed7a6fe196b29d73aeed94d105aeee30f1dee4dce26f0895ee7236cd8354dc7f14e320a8160c06823d4299f72079fadd1286ebf6961dc78f47584f03efc2b61287b4a321956be05e92ba297535176fbf68a43d550a1d85c4eb2e8a30257f7a3f8327f9d4b7d00a917615e9d72a9e730ef1b4c30f98b5abe47ca1f5d4315fe8a6f530403a9f3f735494fd58450bb82db6bb31995318ed6553e1dc8e749b3db0f0d40df0988ef9d4070696ada71381528c4d923bd026056c2eea6cdafda96cc655e1994558cf9ff02384a84c67214ee89f0c9955eb932503dc24241551cbefe82099cedb03a8eddff46eeacb9722d188c5d903917bb6bbf7541f6e821fba5fe89ecfd007b1e742ba005ddc8b84531e54f7a9dbe2d00fc28bbc956ca1f387e7407c4e4c06f71a8eac606ad1797722ede8680e20494a9ad02de43e044b50c27916392c8aa8a8534a8a1916625287220231ba721c82a7445272e48762cc63e1be0dde52fe90abca3b7419c7334d672aa41d366e9912a859f4ad76bcbc3a1ae3959a2ba70dcd421779b9019998492729638961e50513035701bb476dc055d21bdd58e26d6c730c601ae3dae660b3423e4a7ac0238f1538334c3807aa173066598f1898348413588cac4d505fdcf7d72729207804a5cbe8e700ea5ac4461c50eafa49162abd0c1f62d307696ea18ee72d1c228fade47539d85dfba4b75a26ca5ce57820073fff9c827ec2fd7b0d2362731587be0a69d86724b4a39eda8f7008f9566e69b8aa3b32644a2e25fb0f998726f6055cf810059734592a31349b74d1a36ebd647ed91c08243c808ab462825721b39d2112af27657b6bc51fc5775a537277ea7a0aa4003d5b4f070ecf53372002f40d8693adfa2b4963c00accca0bd64314cee2c8e5c4fa2ae214ae6604f7072f3234444ed7d7abb5c0ae939f62df62c3959774febfd2d37a4ebded19d025e72d454992ea14e8fd7c2a29e9a467670906f2bdef82d6648205e641c28b20ab572953516df15414961870930f1622d4c20d66336eec305792eef8db32b84e26e728b4c5225a862ec419fb49943ba0f47eeb41140317bdbc5bc53432410fa9bf9727740d5de2447e380a8dfda4fd0bca907a47fdabaa309d0049b617ea7ca08e672d4fe7a9650358bd9bd0c291b5f4b9c150a8181312a12cb7860a88f17e5a5e572bc6c1299f76f8a3bd82c1ca6341e940b0b883602c92baee9edd33ae32a701172dffa8c23f9580cc1139541e4c196ab914a6f850b7f8fec56069ec447eba5de72bff1c037bdef35d730a599965077574219239d5acf124c44fbe64888543c052545c1ae7a625b6ebdaf8958bbec2911926255cc39257b30ea9201a9ddccd11a7204671e2858f7d81f7b11a231d09608b520d7c034806d8e443d52e5acbc0a9256f50dff55d619e7abed4c696bf1904eccbb57c6dbbe0e05d49ddef793f0abf272556b33bf47dd7367863d1d22309c7a372be817ef9123c6e039a8a334c42e9b6a69e47d3a6b3f20250f8eb785d5dac9e690e29b6d48d388fb35e35f23ef00e2180e76295fc3bb02f1a65db664f132a555461994c82db5726b73124dfcf1af49729cc966939eed7e9806b168b096cf28972ae7e34d703743cee4a768644736ca725e90cb0f47b578442cb23cda973b7e79b36e33954c44f1fa43fa112942bb5a72330a513c7f7136119368af1bd28fc5e4ab235a89b0f47363c63700f1bf9c1c223b7dfbe47781c3ce9d883e49a1be8f42bcf685763b0c665548b7630dea5f0216b8b042968064e28e33c8da2e08819b2747336b081bfb0ac620959062feddab72b3eb4a786aae962fd655674307473ae164fca744636ec5e5aba297910926a55ee46f8197d399e13ef92c2c03b8feece4159170b10a0572737f8ca453feb1210e70b1361455f66e56f930fe2fd6a917046ee65c94baf33d6f786bb6b3d3057328e9f0325b9319ccec7b121dd335f706fa751b63fcbe35bbb59403ca578f436c1da5cf10706eed20cc5045ac5c0fbe44f6360e2966bc1b00f472c1139d787d337256bc62dce13dede43b2211b7a4aed32634374e76cfb629818d2d53e3611a6a7278f54e2bad62e0447bff4ac110b0baa33c6a3f90024750f8d4ae70c6f0dba27293fda02daa91149bfef24a90847a02faf2a988283d14a7c6e564ba37d17eea72b1bd916f8603fa5721e0aee0a627a166d69c482eb89e344e38d8c66589041e41c2f3b8b6a009956f3aaba1c24db171007b4fb142f8ed2d2edd64890856dffa6def2626a466593f7c1f55a13351f9496e1cc83e48453dc3426ea05e22eb35366eabe3a5caf6f5a9ade0429bfae90d7cc2256aeff51abf9f11d662bbc0e339ee72766af8961b68e1503a0a1f515d86ae6ef32c66ad48c141bca5bf7d57a0bb7d720aa74989ffd3c84e7ac3cbb626bcdb6b0db6d80d9ff4fcefc9a40f5089c5b7723040800c5e11d5d9d63bf22f7898f914aff6bc7586c9a888821fdbf4a27cb97201b2e0787a6e161a7e6c8098d3921bb82c284b4924f6d5540e2a2f7133bd44191b974278b9fdb5a8146004d215f81af30f1353608e7dae668a183d0c4e2bca55fca505c44f8afe92e745b2591264a5f39433a79e3884dad26dd86f69e54ecc572616dbbf404e24ca6223f9b3b56a86ef9b130cac54a1085cfc8357bbe5842e1a378abfb3dd69bb91d4bd83f52bf0739522f51ac0fc321475e33d393b035a32722f9d2cd80eff505ef49766fe3726e255704ec2bf454f83eeb69134e0d2524c0192cc4b9ee4f812f8ccaedd9f80b8ae9879ab1a825098724b461e380a61280e71b492a9c833aad3f0743d69867b045a17f09751d0cbadc77e9f9c7a320dd83872b145838bc41ead162784fded0479d17e8dfd1ae3136ff3779af4b06c2c45773b9c908c3f9df60aa18d5165a7857612576cfa2331e23d452b6a52861e49d2987207c5072a2ed10c3a92774301a163c1f89f6c839759c679722cd6b4cf026c8224b241297a3fab644b55ce8b0116bb94ba6a6a5bd86851eb285fb1bef702d81972aa32818eeedb6ae5fe7707e35df42db62721d65ac70de20286600e32cb64cc4cb64539e5fdd659ab491b9a0085007fdfca143a291bf8b883c4acf2997c99a05fab6ed3ac087806a5fd0efc31e3b5ee1c7e064816a6d92cf1fcd5cc57d694ba7215ab5e44817b078acc0f4c40fab48f0098cb883c32c6c9efe1c07c0b56070f6577e26bb812647666ccb81c8707b34353d147a3fbbfdabcaada732b6fa6661a72c4e76ff1ec3302be0f3318f0d34a04e062a72bb6a2dfff96447358695ae6580c1e33cfceda66bb08597e2cbdc11ca7f44d44bda28e6b6f6cfaf0ed004adad05aa32cbc2d00fe919ec2be57e16c705e9f606870060358d80167c5f61db8525c6c50b857dfb9321983f9f493de97841d1ead3099d1d9f7f650d581bc09290e9f72677db4c72c685bf548c148e21784740a11bf579f36fe5715f4f9e8b905e69761dba377ff63b8b5c9441d773942f348ae0288d3a6df2f7484c995f9894bb63c72f6ab19f9e97f7a6aef4cacc44af3b3367b6c33e8de52a42b1c92e53d8602ba086f4eb0ab8a0b1b815812f145b8ac32eac91f2e8c674416e1a57a9799a158f625cb933368b6965b54777ee390dbf53b9b93453fad80baf267240c3424dc3cc572e6e1c77d280d3228a99f5ea4a6dcaa46d35d278b71350e6a8f948390d071d37277bab0e594d820c5c776bd02488650c9b368435e9fe40cbeb55a05329caf304269c2b91c83a6a45fb0124c6dc77ff5fd250746160e4b447f69c9de2b40816172a1a71a76566bc094caf0ffdb3a1239daabd6facec4023e446a59150b5d1ffd72d652943b354f0942f64630fd536c9dfea930e391d74b21b387f7f444ea96cd72808c5d6766749ad7bab41f48f7c7cd58a0394c17f674384acdac2247db3c1f7267b659cb60fa0f157106573c99795d2151b90f5fa9f16a166d3ffb9c86de4e1558ec56ee5ff9c3dd2b5d8960a889b12b396e34535f972d4dd33bd62522a0b330e08cdc7fdb2a4fc9d64466054968839853036010884392d49a1846cffc4830728955a9738171abb0c93cbcfa380d7f28a6316dd8c1360f5ebacdffc71690d672f27de2c70bbf00a7a75bcede87e42d40d65511d40602ce8ec83ce06a0587df466a279fe1d598974c29af5877a8e38de4f31dd0f4711c18bb502e9d71afb17b72d963e86097d32d0940bebc32942c4a2376b0878baa2e41091b72c590d28e8072f238a2bb60151cd59f6be97931006367fb244b0aa6fa87c86393fee00336cd729a7a8e8ae214c7cd5375cb027b57ccd5f02445868df5acaaaa5d7ce7af0b59724cedb6e767938eaeb126d4f6979efcb3814cad831e62a2560fc49c338d90ca02b0423be3f2bc7b1afc0c60decaecfa5cd55c1f95fcf9fdfbd163e6ef91c8344aca44d32bc574a96c2e2a9809ec078debbc04cef9a9c786d11e8ef3d6e987c6724690bb88d1e3effc39f2a1cc5c6fa298224b22a10c8345c68766604e84a8fb72fb49f3a95303b11068098c6f5192d8dd0c7c84af66f2c065e8ded0a444794872132727d9f83f05030f6eef43c3647a238ce56c2dd397d6e69b5c6297e1e556209dd0a105da772db9b3e67ca1cc452f6aef20d99bcb97b5a2eb128d6b5cdc14722fed1d72b419aa4072e482dd7947eb25175811e2098d0abc12b828e91b1561722c11b95b4d07cb5ede13e56a2f5e06a1d4ecf02652a8fea22eb435d71bc30661cc3e9fca940e4ba479ff5cc4204f04c807ef14a554f4cbeffd33e0f93eb2577227941966f3287aa12f98437676681348910e6adf0c582981702d5cd4d3d743211dfd3369f9e38e8ca8b203a071bccca3a489ce483ef780549f28a1f16e376f52f3e855e5f058bec5884ea710add4d2ac96c213699b24683a3119a80d5cabc6394273393968e630df485e9692b7da97a9f15bcc8553df43a04d750f8f16dfda1e5ec669cd6498ddb5d6e6b14ee88dbabe755c3519821bfaff6f77cc48c493363f84b086f3087e563a1d9cede0faff89b310dcca6cbc700d4dd0266e967e524c728cc014d78a1a49665ac45cf6d018ad31dbb7a422dddb3bb54ba259ff4c2861333e7418519cd096d8e3bc629d842e6e3ce6777e6fa7a954c0e68549a7e786de1c1bc8bfeb3b1513e369ed80458f44de6dac87654d3a2f2cfb5e8f24dd9992a872b0ffdf74e2383c145d07dc77e98c8905281df45d66bbb500e1c37cd888fa1a3aab4f4e5f1f625f53b8f477f20e82fb39a34bf433a5985a4b6dd3b4a5685a31729bd48fced8c3f082ccb220f1e8e294f0db044d4eaeb4992d77ffd97f6d8fe206b987142a4bbacc39c602496d4d724ce4a1f35804fc59b47edc05b2d87729060d2046af6da1c1f555f95902d90b85ccafeeebf10fd80b662a85ff87fb0dbd1672489fa2d18964541d4b28ed9d10e357106ec7f6b4d92a45905f11cd0146c9495023c9da69ded14bf3601454a3939ab05dc2dff52f6631cc5c88eec0df903fe072aa94933567494ca78d46080c2953140ecfe8b1f7158297d80cec8fd44d761e381da08b4d06f7e3b38c380ec475506ad4f8e1c8709280566abf7be208118d2f1499fba2cb37e0f17255666d6bd1ae7c64a6982eeb34cb79572403659887d45d72f11e0a59c382f26c5b6e1b01d143059d0bdb6f0e83967e979f54e4c2e775ec72749eac367e1c1762bffd0b61676b9794eb9c4dc0ac6e1aae9f77ae12ca816c7298ecb6fa278d105a386c948fb0917a9490755cdb9ea4771c2dd7674c51efcf223399420472f5b714e53744c0cce68e243b393275080705492fec2c08ceb4207234fa14c662a33d1953cb7bcf9ba64772e5d3472ff17c3e7a53358582ac5cdc65b79964c3c9ea649df3f7797ddaac1270f992343ae1775d3cb598462bec536e51e5a12f5978f3d3aa3f241a29689578e63efaded0cfd4559bb5e42380c255a109e14f1599aa411842c7062868fe4c6c10f79d9602448e4d64ee01a2e61350eb725e661da53884f3cb345283c53a51e8fa95769a52ad9c91ad7b36de851662e572f557b9680b5cae1c050c416b230d84a86d53dba0306e75615ca025ac9ef0b064c32c148f44b5380d4fe502833f009c62e241ca7537df5f46ab0f57b088472e3ff84cc59c05793f5184f64b524f766add66641640075a33143484cc909e0fa66e7fb099d1285d68a50d5a012948f4ff6de29209f35dc9853207220914c1d854725639d122e667da8bacef19a115dbc81c6339093cffdb5a646949999ec2df6c68caed22732006241321da3ea8dc387438fd08b5fdb8e8e662a093a6d166d5817295924d2b75a4768afe1c5a5f030c991376f2ff57b56aa04993c369877ebe0c72060ffe37a7ce1a820fa574a3ccbd8b4a77ec72edbda8f1831510a84a17aaa672b38ef704e230175eb9a61b8b6bca722c2b89a34c28089d3b14d96ae1bbbded722fdb642aa3de8b22a658a23c98944253aed616014e8e9126f3388bdc7961e672599cce6c6cc11968dc4b798ae9aec42cb96e72ef915e7d5859a1c2088e048f4766d9fcead3dab0541e8576fdb4c2e9c26216d119e74797fdf89f99e4057f62723c7027aed75666ea1dad1e572491946bba1b827a8aaf2319da454ca37380ee7167dea3e72b400de0bd7f0b8f6e4f1cc3e38d6f64aad9638b749630568a7b99729c4a815d0c6d37ffdd3f518b0be48b7f255bf1764065324b88df3cfbe70daa727ef9b3de8272952cb5a9758dae552861345642cdce4c3be6942c3e7ffab2207259c96d40a6e3946fb5a0ab05644e9f9435c609d953bde9f79adf2be4fc4c7772eb0f6770c62296973e46c1c6b8a581ea914a77a1e35e00a08aa4656556826f72c663b5ad5f811ec30d82c4d8bdecfe88d17d6b35395f6abe57a739f86aaa2d4a11e3d07295caa27b22b0b766812adba504a6b0fc1d40e181c4f5e27387ef787242fd87291a1e761f8d4895f6147bfae1f66bc9572a48e3b481a963e08211e872b0f7d235177c87754e6670e3acf9ea068ad41148b115592327d050c1b9417b0532c0814846fe6762f349f7e9ca090192484edc209bc3b1f57fe2c460a745c2096fbbb7372603690f4b7a10e8ace3fce22339c749f3572e886bb77f93f0d1576d38d15a999b08d4d7a40c3d31989749fc006a59532d735e9200fcda3f292af6700524535a20c8ac93a41a51646e7a12c2afcc8452b841afea77301dff80fcc321ed07f8333f45264569ebec56cda201f577cff9c03fa4269d9bb7056dfb35712973b06645314675273206bc3e7c1733ad4fb3ed168a6c1a8c0238aa139c993d1f108c669afa13ce404f941379c3b6c144c4be853ea665b9f442173406f308c572937769de48720437541c2db4580a67446c03bf091676bc23a7c7e598e4451d724b0a0213e142877a2222dcff51cdda43078d18bd5627d658eff9822850fd1872739518aeb2c89e40af3639faa9036e66b7f1e038e3ec2bb3b770d3d703c97939f054e9d96cdbb306e1aa921ed2bf5bb245e2665c1f263ce906de83fb86671c723b50f15c5e3e481c73895a67516016c75fc0fdf59ba49950cfa84dfe9c4a8a722501f0ba3390f77b6fc7a2e1bc255998338fa9ddb5d70da139450a54aeb72464e974ca712236d56402ac573775307097f7815b8c47cc3c7553c56232f261ff727eaef311d54701337b91dd45848e18224f7e99c20474f8b9951b30407825ee722d0f0361fa73c053dcc29de81edc306940c97846e10057e6fd9789f67213da6f80f05d018f972916e8cb3f6e2a3fd8dece5caab0666b69e7166126d1996357721220bca03a50a549fae3414375cb751fb0d14aac9e179d5b09cf0999ca258b31dc6428b4a1c1b2babccbfa099b3e75c7ccc7f643af64c930c67eacb00ba03c72c8227cb1bd262ce93d18bc3f59d6d0a5c32be1ee7ccf808eec266191144b9d2e1f4e58de96f06f8967419f0bc6993df4eb3b05cddeea1f15c184c05e0c3bc409fc7bc405d5f1893c49967560338b96ae21ba522d279e95114c4d0e41124948391abed3b3e4f891a7ffbd20a577ccd114490c9fbafbcdb866d238ec9c92ea6c447e3d34296fdf2c70348fcf2899dd031dbc809a339075f86be96455d638ed6d72f8e099661b6de96c5b896fa613cd1a36cf4ca34819930638d8fae064c784162fb684f3293d513d26f97e22eda66fa6c5892a0d63a92f98201a923118060dfe7211578269e773949580a05f2758697f4b8d577f3c7a7e849cf50757fcbc0eb072e5e22a0160e64be34d0b7687d142543dc4afbddb680d5b0936fa60247e53897260b4a96f583008db6a1ce42484da5abca1b112311bbc8570fb14c1a04befb564a2aad03d1e2276d3617c9c5cf59a1cf280e509db9c5a6cc9f23074c094e8757220eb77cc15ad51d990bee5f25df86fc69a4dc566313aba41d3464abf4c46e554750a933047e03511446bbd2debb2de879f903ab59d776e8a87e7ea47cf299372e86209bd8e019c10b251b52689f44357c7f6b2f0bed33f2789c1d59dea04a27268d6b7e847ac8278ab9fd1cf69364b42c9f68d017241fc6b622459fe8e302872f251829dbe295939d214e9b43d740f7ccc44e2f62d6bdecfaa39d70b2c1b8c070a89280707ec1b6837054e58d3bc37fca46947229c9e795af54bd6981c9d8f6dc4e3bd3a813eebe7492dcfc6028c62409d322625e3b0936208d39c506e332646246fa081273e563410ba88849013a83b01ed5891dadabfe15e3a38ea1fd6eb72a82497c4e9df95d8c9bf33ebc1f25607e69c9475559001738f8b055465ce307222ce947fba841eb1a968c1c946b11c1636e68a8c796f8c5459211cf05ce25072e7d4d2ee96ff39d7238d9893c4f8402001d740319ee0d9775da0ed670f25df72ff2297658b84db3ade129be16df8c9346d47f89f4952ed51e3e29553489915497df53d22efdc16a74266e183ed1c32a796337bced3a2d2bdaeea06d80d469d5b87a2aabc14e58b0b8b7408c0f947e7dd936e6ad864fbdc02d67f22062a6da601069b95f5aea7bf10c3c5d195277d3da14708467b5ec6c7474fdfa2426c02545682bb0dc66362fc1922d7d2907360973e776443f7e85a2db9ccc5ffbd8da63d6ae7c4c99ca828359d224d65ac06cfff5ccb26a4684640998accd41dd3fd647d6392e6fb8271fea7b8268a26a34ed0b8032a83a30b510e89b63789b199d648de724099daa87925086844a08a3ea8dbd79c7f8aa3d6d5d9f0b575da30c3bc9ff672d44d045395bb306a36e24cab2cc6ab7fa076fbc40cd83c5c69596e0977cee23121ca47f538c9f99d5b2ad38437d32fd6b373cc0e0d0fbcef9f92c6afd24da12584f184667dc71d735276f2778547f11c98ff4db68db3d3d3f2f9c6d9b0b1473b526b5b7c3ecf48126dcfe542b6d94086684d4ec63463fb04c16513e468a9e77265211994e3bb87458ed979d38b1e6ba3a53b7911deb903f4558caef4365df3724c792df8fa63d774d37e5289f96974d70fa8b4f24b0ed76d5efb82b19ca07f729a268fe2806daa6abd944c5601113417f84f13b2a6676e7a0498cc9a98b753217eb5257579302d436145fa536c1fe8e7e42dba535d972a757b5f055f7f42f35652046b8735626772afa27dcccb72b4e7a1c6c2ad3f655a7946cb7cfeb72a25725104fb73d49f49f62a5cb9847d56a743a32981d4e11727b48eaeb64f58069272ecbbfa54bc33eb9402576699820744aa89b9cd5c606f394153348e125e8cc46309df9c8e5b2557b740089ce87bafdb91438248ed4b9a537135cad2b8ec72ac1f8fab7046ea18592bf6c55b0438dbcfae14eedd2f40d024c83d94c592e4f41e72aa21695de31964fe410a7dd558d5afc78bfa43e8da1f286581a3a7311b129c725338bece353b83763659d627663e67f62699f000f5231a176556c5b26267827220e7fab4b216e701a3daff74ed5c35660db278a9a8f9bab6fce040970202033e39e2cac6325d85b9f385ac4b7197a830bb66e32d66735bb45735141af5fa45257ff8568cf0cf7e05c58a691a79bab7e292f7e0bb10628b8128120f9fe09d62412361a541a87d43413bd92bd34ff9922c96a13a8f3658021db3da8fcd307fd336e4d3a80f51b196c975052a91bdaf064bcca92fcf71683d46017622bfbbf9386812c7d149e7ffa1eae60a8e20e51fec318cf062a49157ff59a4def4002dbdd7726ee22f9ff29c9e24ced92f7184236046087c7291f0b06a0f45b1a4a50694be72fc6dec5dd9209c1ba0a955b4dcb9cb128b73e8c69e57f7756876c8afa7ab7c4bf0162519d68ab34ca7a176380c19afe872a9ba2512a9a2d65bb7dcd6fe8b6a721157ddfd28509580558d2ebe244a7e3399b1fc62755f576e2355e3a5624743496f372d96143af9d4df53a6b07b07617e74ff771b3ea165d9899d941ac141ff7294c5e7b189abb5c27f8a54852de93628af39f37fcb6120f9fc232d0157315d72a91228c45c862297fe81e5e15197618698d040cc67b2ec074c5c0aacb2fe7472d536533ecf6fe09786e077215c0a44c40337237998ae2a5c8beaac280071077223f98e76c87c66d6cd8e29d8469f5e9acef4eb3cf2eed5d8a0f85998696c0a63478ca4ec1221669ca4ecddb4bd2626229bace502ca6acc47bdb47c178b59cd32e3d9df215c3d4e1c3cd70fccee9fe0112ae1560238cd063d17e2f6f9bb79c834bb2e47bece2b6aece3dd5fdcd23466094008e57a02f90b751c2f3563173aac72e2e3faec2fa2b0f6b7e68e657529b18f2b6ee24640abaff8d36a64c1f7ac3e72d99865c6d63349cd319cd7dc71e3b9bbf1ec337c671471d4fce8f764c77ded72795063708e569ebfc2116d878f4921c807517bc49a942ece880a12d977945672366be76034773351ae267d0f427a3679fa127cde9222e7a27afa8dfa544c9572bb19335892755e4829761dfd30aa41c794e8219b3e5da646b5e8f5a8cf233102f99a43a6c52b37d1c962d77aab89d5e4c8d71228290abcb1a76565f097e00972eaf97eb59febeb815b600bd65632fccf2e8b321e0899bac270a74fbaf25ed27266ff8accdf2dcef8efba8a9cf1b420d87ae3996da0f3e1570cfcba93a92e8b72fbe13664d39505cbc6198319b772f481dd61f9c9e70d076a8b278c5c0a7f9e726cb997763218d032914b33fb224eb7eb131878a29e9b8b4b22578ead64c8ef52dbb83c23374a1319892bc2bf53076fdab98faea6a9096917b63ad7efb26669722fbddb7489898bd6131675d4eddc2c0e938925799f41a662a1026d4e73dc65722889297c9d8637374d4a4791cf6bc9cd66634b9ff2ac2994fa28b0aa1f810f21a82182ab53691bac5acc7d7efd71e73b29ce93671b811524872b26b25040614318f1c9b082bf594304b306157e532cb15f39fe53b219ba43746dc7b80f665072262ad621ee40a01809a71d07228c25599fe5d729bb3184988ffcdad5a5ebfd72d800209b753c2c12a73beca9989f2427de7d4a1160e7a76af3ba91d5ec60b95534342641bc451d9b835c50a23c5156b51595f2ebab7219e7cb9ce995ea582c7282f77ff64604f6bf71b1ca875ab321e5437dcf617a023efbe6c6369aba1e53728b97dd06d67f491e47c4c79de1716e0c8c90a3e7eeb19ae562d0a077be7627587617d335db13dadd2197b5a1f21c427255c4e348be4e77d2e31f2daec7a1cf72639c16d6af1865960f99c4dab0e3a249cad07fde70cf3088ee134dfd65ed102667ccb5ab0b17a77a42aa36953a7e28433c1aa369cce65a276424847a47721a72f5a26974f96f3b86c6c9aed16acc1f3c8600eef9ca349f0cfcbd2c87b3b6cd026038713295afef8745346061d5af9d673d40ffc0e49ccea379408722a3e1784100e61534c83044a361dc7326e7de80e71a447e27a4faaa65aa5c58fb9a44df6cb77815d240aaf579a2689f87f4679e7fbb9678ad78d00a430449c9c0cd0676725ddb0baf8dc265895f3ac81909fca6546702cee66b561dafca9c61beefbe556c4eafaf59b75a657ffba8382c3c0655526c115aa3745eb651152ea27acc0af272f313ec8458b86c8504e720b29ac79234d1060ec443a009a636bc8f11f730e4511f29c992bcc3f3069304bf82d376c9d183f3435bea792b29dcccb55be3bc6872277076ad78f9a4d3293553acbe4e33dd189539c6ebcf1b1facd0017948929c72507e1d064bef793ac05c038811a81814ae67dc95874fccdd1e2d017de38175728854b4649afa4803b9d96819bf89d66690d2ab060438462d69743405533b5d72689443907543aee3d4d6235f53594511252899be8b57716ecc145ae75ca3cd0c1bd6116ea1129fde98632abef13d54dbd59337fd942daf8e1636be85203205723745e758894b4e44a7cd4fee8a6740c8b7913a6445f4832bd3608dad37357a723533a406f5671712b9a3ce3d8065ecfad3e8b2289d0a1a076f1de87b746a0e729a952331bddd131337aa37a576e5435864d8b1c33f3fbb29c9461cecf61bc5611303e0044c1da28f0a059c279519db65f9506b7f89750e9722f2766eeb293f50aa54438ecfc2a9e963f15596692b6d290e1e8c1bc1aae1338b3b4b502a969e03d379dfd55f717b6187a379833cc1410791788876c393e84f5788556051591d1833a9908df695539e965f4e9f77b02f8477b7c8c429537f4c6e2c3d92ee215072c08a2a206286c6175066ea02bdd6a37495b317fcd0210ab48b2784425f2e7772571952aa902602df2ca30358dc6e211ab3ad782b667695917169bb167057447269877564b85cba2244724842a2f52fe75f6a9945937c7b0c01d41c7d0e75e103455f18644539d51d81caeeaf661a658bb7bfafc540da4a634d2b7dcfa713c97269fdeb8efc8b1f51db49cf1d590342cee079c0fa68c0ca5280ae47a4e16a7f729979e6555414dfda2c990ff6c07a0dfe1ebbb2c9d990c34e96715f5e98fd611444f2b97ed202bc67f2a82e44a55bf071bfc1d58cc0336eb166031ed3870cc92fabf96adc6c9c41cfa333be8ac39c5c0a5097ebfccb6eee7f1d013343098b51524880b36e1fca40acc2b94c1ab3a3aedc1da6e7a2152102c12055b61170e95158507b2b371f19fd425a630af49537a2ac6a9c39c9fd5fadf874b5f9d81c860c1048f8382c49d3fc3c31aa5b42dbcb0f46a0d9e360c98490406a62a91212e64b60a61aeae644a5a92382bbb0961ab9a90dfca69417bede180cce768ac784b4a77297cfa74ba600ce9f2739e0b506074ec2e371f67562a078a8596a472146419872217e5a11f5b0ca75f1164d11836f79dbaefc27b36d037f378d42fcd9648b26722ed23aca9638d0652d557453c26ecae28fcd5efcf88cafcd5c3dd8745707e34663b7c465ef86e0fdee288b1aafe683d1c69133850c6b9eef4c3639687939bd72dce6297773d540037dd47a7cebd572c37b7679cf6808b947e85996ae4794f372a0208bf49ee7e2e117117f632a3338be1175a6d943b7fa78aff7b32b18245772ddedc4dd7dbf0ab50258198e89d9bfec1b887004a094e77e721fa216f7ce8053e357d45976d43eb73e80014c8ad31811de2c1e31148cfee70fb1824de331d84c60db1c9633fa2ce0e95e4da0fe8b7b5ed3be6accf9eb1f78884fe0a6b2b9c57212054bbb429e1c070b0dd1e5ae2e800e022e437a353ce3ffa2913bd59434c97278003a9991f12a029d9531c342a899969b29c5aaeb820488711844880e079f2d43c360ded504409f64177e4a138d6bd46edd585f89a924e011c9d7198489c9359b6b8c13bbffa57fcdcf5ccf351bbebf9fe2b0a88318758341475cf387adc42e2dafc204ba5661b99f5ddea38e7e528c2dde7657948c141b5961f38698710a4209a41836d6426f726e76b5939c589ee815f464ec0ed72c0fdee73b7d52b53d720d471c3147d59d888d2678153e689899285ce27936da580958c2a2218d04176b6c46fa851730c3044e8ce5d6205a1c25290756b83510728fc6826b5b3848ac15b5667cb5c554eaef12a3c4dfffc998461c0e9d65ef6a1a1e777f2537944e53727732dfe355faef6b9e491b520e37126f0f7216a694d8338cbe00c42b2857fe72c537159452abe9bc841c8ce55a6c9b8adc1117a23b6983b552d9196d6cba206d77e5741c1520f444ec72dbc956b0739640d8fe25c046982e86bd04652c65b16cb69856b5293b446d5bee5410721d83f92dab62068fb94a59b7596c304d3ee5723c60d38844f46be60f9c46af8b4b7d054d8e7d12d4597cfe8c55ae3ffca0df72f57b07f9e79379dba81819df216df0bec5352e398ee652e5dded83678a5b33726648e3869ed6e4d9acde305f6bb922ee33e1fee5b1a60eb1c9bf42674d656872f9b123dcdb8615f7fcb8388695e84c0c293ebadc5f6656ce766eec6c60069a641405f9babda93510a69b7d8b6b84037212ac3207ca8023f329df60e99235a920bc295adc5d611dca36728baad1b552ac988c144ff6b98c0249e2f25e20496e2ec2e3e17bceaee7590a2282637a388ee98b9979ec56dca570430e40c5e376cd720855b2d2d8d8191e5988a8eea82d98ddd7e1d7143227c71da39ae5f07dc82270211affd534c93bd1cae9724d8a3659beec892a838845ae29b2073ee229abfd72f713dce3fac9e738fc57b80ef9a0b35ea836e3950a1ab5970ed8c95941cc1f1285e8bde7add7b5b0096d629e69d452cc92f43c0ec6bf9a7783574316612e7f4307659cce21a46e77655fda008d07699090bec1de1f1ef53d92fa4f51ec8dad7280aee19d9f1ee235d545c2da60536795da1cb01bd2d945c669d264a3d72c8629103a26655fbdd5c5c55290438b3d2b5177307ea0f3fc6ddc684f9590a38d6072f7a08dc7317510b0b090971385c2493f259fcc8425f969d711b4696bbb4e2d18733d5178b2c5795ed9ef2ad7f1b88bb3809304ab99b431a794eab466ec3d10726878b84165e09988e57834691776128f0878d641a2bfcc7cd0bfb0b2c673bd610c610f1e25d592189a1af1405ebb5eb06f9316012837f0a99469261a6beadc0235a0bf1027c5774e1d40cf969252d4a0679b2dfdfc0ed4dfdfd7550304b3da4cd0e59ac5fd6ef9257c7289bf64bbe9ec5b057a566ce30a8798987dc2fb81e27221a25d03a4de134fb48fb193a82c2d06365ddd7ba798a1c1b59177ad03801a5204a35ee7c7e0f98afcb97a49bc66e79486984a12d60d0401e041b492a70d3a72dc02913010f814549e36cadaa79c1d2fa08df85834f1b0f3df5abeb6af2efd72ff172db4c77b5fb5429b370f61b0315f3698f8adec141f734f09b923a7fb8144fad46e9c71659fd08727761ff00fb810247bd13e464c189c833fd2b53618df727c4c5eebba6e28183af634c8c3ed9bee56d06f63cee1799fdbf503000661f172e38a926e4633454ae8205fcb0843460b68a9feda1204c651f8b48e7482679672a7b05fee23b5d12872736e3febdaff925fa3887422b18fb5161b888dbd681872fd8d427cd609ae6351f736298aefcfa215134c64448bce89a1786818184a821a04258526e509433f90f1258b3319495b34b0657b137ee5dc11356f994e148872d25d34076b944d48962093cab84b7bf5f906809ae9be09a71656a220fe061172774bbdd0d33167621323cfb2fc981356c0f0b4303f071e43ebdb74a963749054758e9a9c9073941a447b7225e7deca4401c77a75ca2e6433c78a0d52116a2272146057875253183ae2b5d56f3f2d7a17f165a5f8eb23c3c5a007db2e88563366a9b16a72df919e0be646f2da8d0de55b963ba3100abcc9d52fce51f13a076718ac6d5320119e730eea5f8aad766ef64f20daec75438eacfbb113c9f0b2b8600443462a2085b544d8bab2326fe230696bb357dd2e95b7f1db02099bd96172f27280ccedb71439f2389128cdb2e2d4aea12fcc1e573bb3b5a0638e795839ce717218241a7ea8c65271dd087bafc18d7dfe1a54edc5c62290a96462095397a25e04253cea0cd80d83604bf2ad1a9fe52dded94a594639e88f79c48004f14d0e935455c4840ed848d1cb8248d255a991f6a233992b059f391271ef64cb9d4c255372641159202a6da2dfb9f1cb6d1efe1690e59d363f37d29ef4f5db443545844472a1c45726f86259cd3980d6e213100f8b686b5ffccf662285cb659c1d5bca420021660bd5ea6dc98a1f044ea1710d1d471922fd8c61abf1f3bcea2b569e438172c0a1d0b93b06dab8b21dca7ebc508ef4f709709f5ff6bf6b44b74708a62eaf270e6f25dbc4492181b99213ea518d340b352cd08098f05dc7be452a0f6e10c15e7a40029e9b9bf793e2cf5660231d007f9baf063e36426bb89c557c2f1fae36726be58bd78cbda4666c1c1809f14f20ef8e7f9c1c01e002b83785005f64c574583e3bb7dd5981c266d5e3ecea85ec71ecf6f8760447246f4d2ca7521d6c720e60eaf51ae4ad20d844718cfd61bcc099f2ed280467025b74b2ec72a727adf561238554208aef4ed6b8a5c9e5937617959d831ce7e6716db3ecaddedcfb6e988b09c3e6923a228b9a618757561a073ef8cdb7a30ae8f7f13e6afdbb1923ac3708086836b2386e372fa797a7d6b845348300e17ce4674c2a9086c17e4201e387bd59c357fdad7a6dc4cb1552f5ea79ab4d432fbea74d49a44b91f1cbe13dfd38b972e3a2ac1efcb0dc987c7cffab299ee6f768af40471f75c1cd5844b0a92e39df722abe6cd0aacea815f59fdf2689d7808d5bf6df173c641c7f36b4ab1cc6db737277e9843ed2b9906117a42292ec2684f63e5a9e72de21e78974dfef941e40234dcede8baed59d004918bb3d5d561c2b5809abdfcbde7b1b518fb12de3e73c65726b85f23271b49ff8427b8814933e26bfcfda321005b616aa8f74cf68cc2267212f69d1f5ae582a21d8b63008b39d04397bf0c49e6e95d1752b1fa075afd52d5888f2b745c5a29903f64e4cf368f31b1d9c78686bcef08b93b6067a4b592bb024dda4abe96174eb63767c86a87a0ac8cefc59ef241b9888ecc9193cd612ca837205bf0369754093ad3e14f60ca9ad2144fa42b77fc18d595f4234d278c4d8c15e7ddb0d4cc3162737aed4bff9353ba74b7eb7fd08d35ca62c795bcc2b52b9931e58fc5d4fbe20a200c3b61917e585026415e7bd076328889ac3b2a80bfdfd8908447438e9df7b2cb70c3212ef92caa5a0da38962a239d42295cab0e5a94d41972484a775864abde8b6bad1f0ddfab3d6c087bf83f515f623338bfed9d06372772d8ce73c9265e56e0108f4e3eb0368e94d7b4c5194d4cd2eef69c9bac1a0e7b72ace098480061745516b72271fdde25ea4b4cd28c2353e780b60d7476cba88e7260b7f8ce3dd0afd18541af0fedbc973b6ed7b08462c99adfa1a8c6521af4b9013ac8786fd95b9dfd47c00d28a57c850fa29e585848bd87f8d4538e8e56b78a29b890eee7699585f63c48559c35f56283497d7292b9f71ccf660135ffb0f5305a5b4f4117458b61378cab1a377fbc2a896e180627aac5c4be445b49690cc3e21babd1da28593f87f78aef86b7effdabcf11cd5bcf8c4c8c552f21cdecd9eba172aeafecc9d89c73b46e664c57436ba35d31829f60cfafc76c3965f6082bdd794e5a55aaf40a9ef72e7b6d546a6eab20779b9c48a5ac7e105064d669a7b7218972933c33df8239ea1588247d2cb90ea8196cb64a90f06d52adc664b4b8e3a9557249593fd905785dd86dbc219adb3ccf9d65e2eff2caa196ce9caa233549192572559b49b89875cd4eca03e99eb559d180e253335fbae75c12a1b6915c30f2cd72e0d1a8ca5343270842eb4d0bc73345b3023885aef43246ba08525cbd39d5dd5bf0df6b8132870255c38612305c7142063bf6325ff3599444a7825d91f4b46b72d8063ac9d3c89223619830808ac36086f768eae29452df0f7eee2939b68a996601598ca322fb2fe56199c7e6cf1c1aaa7c59dfdb2a330738826617dd5f0bbd6694b36fe56ffc0bb8384750439ddd26ce7f677700d39fdd335cc5fe3d6586d4724804a181869772dc1ebed71fe73abe6de06d6d913dbc0a64f9e1570f93d9317282e66fa88f4614a786e174aef503a0c64de2dd977a8ddfa634dff6c8f707ac72d84ad06eaeb8574f733bc003f2159529d9428926eb507c3837d66d4a2e007151c41646cc7ac412e774d2948356c7801405648f9a6bf8b18b22f812e18a7617727ac5c99f064e024007f604951c62f5530176d54e99619695f44bba4f56717408fcbaff5d3ac2140847ed231d2b3b473817b67a49ab9fbb3f61f554e84f42d272983cf058855dfb97443eebddda1924e31669045ba130f837bcfa7139b3a85056e67c79cd7ffa14068d35cfc5a13955e6fbe6fcc4b1b86106150d2a2e7376dc721d7713851757e20e0eb25a8be895bdfde995064faa3d10f8d4c977e890a91d28c65e2da1037ec09da45354a22424eccca771035d783fc8021943f26df8141d5a3a3df52ab57ffcabe59bde8654585bfd233358cbe9618fa3f36bb75fc6fcaf721e467d5325baffeb481ab011c05a4b577db8d4ea343733c5a0bcae275125852f3eb44012d63406b6743fe290bd4956de88d4effc76fbd0058caaecf2f5e80f4edf9318d913d2b444ac95d0ac39dfe2cf4cb61d8b8df1b57b8ffa9dc590ddde72ba95f59355f8e8a1aaca4e04ca62279d7f19a62f8393c88c6af4f71d3640cd725ab1a871d656d3e190ba4fdea9cbc2afb71c615b79afe6441af05a554165357293c6f412c3346bf8d3b7ad4c133667ff1311951fc08a0822232cf4aaba8b28100c8d97bbf6669849b5d38b1ecc733f43b62bd4ac5e3895381a2957f4356d215253eb4b2fb30de70935e1f92eba3ca1873d196a66e6cc353e28e389aeed554b72eb99c2cb1482423753ee6ddfd270abff89b47c02a9b43e85a8da039c34991d72dca47f095ad156d8ce2b6809f2094d7a76e5faa15efc533705abaa4d9a6928721f052634dd61df4c0b0ff273b06a67b6b3b90767f84f3b2a9a1caf1318556c72e69dd7b530cffcbdf097d32f0296212827bfe89956fd9f251fda979a6837752cde7be90f2ad8c5aaf35920c8ff7b6a986918e6b3c2f0b488f76864988bc1007232c3c009cf08ff8cbb8686c267cc6846ac0a1113b62c6aed44b4483ce0f5b77233494e6b3806df59e6ba4105535539e164521f28aa6e6c3a3746fb4d0da7a5729acab11944e05f232950767e6752cbe32857b7a96c7489202ab3e32f3c2c7c7280f45a8b8450a1a6f69096371e8971249fe4e5cb60aef6f63b0c173165f7dd721ea25fef3f40da2560715baa0628351bc4149a0a9d8695bcda8e59209d91423e0759ba587c88ae58e3be874ac70ccce2875d63dece7e1bff2b7a20cf03e7557208ae554ddbf291f319c8ecf83de9f217e37ddbe092c6109d0d00a1431c217d72e30945cd3ae09f6d2a3dce551adb5657a756a1f4ae7476a4f9cc80fc79530a72f964e13067d04942b6bd735315324142e93ea6fda0af788e347dcfaaaeb6b236a4423818bbeed37f5abf3e5bf0a8ebd25b27005b7c8022fe5a38c3a3a38b1f57d48f8239466e668c6566fc76b35ab328ce10d2c62e88926ca480aa2fdebf4d577dd0987ef7719bc167cfa557c3c0556f97ef96b8137903323df28555e8521d72d5b9aff5a1c5d917870b5d36cb79a7bbd2bc908356ba356d3f97fbe815fec915047b48c0dc26e3315b37a178b3b6c1409ec9f031c9db6a355388224abcf2c57015c921c48042d0652e88005ec48ef96c41c22af013f44fca3612059fb26790722f20d9226422b31ac8dcd7bfd7445c971526aaaa5775bfd23cef1c922db7f86468b97f847f508766dd0408cdbe1389b3bdaea1ce9b5316805bb62a076c0bd14c955128bc17ac1e6d226c28d2b5ceba8a38cae2e1cf35a4d067b64f9240f7a972f718f34f6d3a7e7b6c0b5ae1d5f7e7a3b078f51a1dddaacb580fcbadab9bc311b7d4f5fc7fa76581d6b0dc0658dd24e4f5993b0596642ee51461077283044d72aba3c2897852ea2a30b3d6b58690b6bdb908c0bcd51dc9f63753156bbd091f4b766ee8499d1b527a3518b1ab8372eca304960ac68e1228750354b0157d541d72119ebbc9dbec95655e053e46be27de6e2d3c02f607ee72420d9aa08a1957761c9fe04541a7fd070656b9509116142c9559e6a4ed20f4c6c25a4d51ae6fb5822c72bbd3d2449b97a35866ad843be3f71f640e20ebd358bd60254b27d4703fd461485486900db05af9339b901d00eef9bf1f8d8efa53c7ca234f7abe46c01af67288bd91aeacfb1c347646d74938b62a2f95fc333d2df62fde9725b428db28602d83e0e4d232cb830a47e3fbb78561dab140f45c7d7a7663826401b714128d9872f2800ed360442b895121c08866e81e78e33b5ebfe736e0ebeb9a9801ae03f972e3fce8d120ff45949d4ff316a68e75f32c9f4dc378a15dc8d47a1d2615b3665311faeeb2c94d9b753b4a0d9ba444a21f3a7f0920fb3eefad59a3d9b3d540467231ac52fe2904441100eb287911596710814f104fb9655b85ff240f669d417e72c6e888a90588bc06b664fa17fc42f885add1ec0e9d59b220c6e56f37ac40224f28f484ba27d633fc54e012ef12673d4b36ecb7dfc6076726231def9282fadd10b309a88b3e5329e7833664b4e93954e513e81be4a340466f2d7fb33be1b2ad723fcead9241697368e53cf51a5a2d57d94796e10b1ac3d99805e7fa76b45bc072f9971ed456a207a439622f955f565de891e5ad9998df78db1326ce3f49f5ca729d665d58c12c980305e81307a64483e83b692b58422f6e7f50a0377687b7503e988190f15522fdf48c1fd8ee6ab0e5c8cff8a810ece6f5ab6839331abc9d2b72964e8c381e650e70b61073182e5839fcc8322d44e870f8854922b329e2383e1428cafc6e12bf8703b4eb245052f9d17771f121b62212c2d36e78e363f8016772bc49e214758fd74c7b59e227696351301bbd14f3ef2c3ca957a2119413f6ed72be36e99666f167a1dd5860d45ca70ce309410bbc0dd86a2e1a7418f11a145b588da7efb8b1bb9923b1f14f63adcd78092345651bc4d5550e039f45a315d82a2b05dfb99e80ed956a0706ee550182752b1714147edca82d7bda676f216d075d494ccb815927a8570407a329a4bf39c3274c7a07467df4ea74415c3d68c2944372fd0679f29ee95a7f5921f66150dcaa9e106de0a5dc801e0e5f888043d22ac272437fac46146ef317ce80844f3f634c7ddd874b951de205a78e00b46142d7f172bbf56bf9ec9c9150d6a1048bfda1c2785a827af90c8a5a84a0f61bdab2000a7202a1dcf8b985df0f485cc495c413f48174f13006b6ac2eabf5b5e86a553d4872b8aa3aafb4b10874d4bcdbda9315c3ccc396f449588b59608d6d3e8d0d489e72aa86d8953d2b433309f13e098aef9f8e363a851f09e5964605db5a165521cb7271e01582a8b2f2518f054fc913b8ae5a7e24c9470ed10ba8d398fc47a6af0a72e8b3ccc219e47ea30622dacfa75a05222a4cfa9f58f139d450a914e018781e72f4afcb698e94413ffc0ad6eb4403160fa42461e39b7bd9ffe8220d6a48de554269a1d79275fd5f452b71465b30264cce85acdbe74d65d818029c739987759872555eff0fe24b956118e5b1e744b9eef519df75979b7fcfc2384c915828f29328111f20cf785eaf86c627dca63041d36d27336fd380d0f4d13a3d9e181f4b4672b0f8b80c119b70cb4acaa7b54dfca10202fd4da14fc1ffa9252dcc614cdddf3d9266085849d0e254b28ffcddce44cc26d1eafef985d8fe7028a384e941c7883f2ee759d6e678bb7fe5f33fdb12f3b758d55b72d4c4e9c3c0806d5a70b2459c724eda8935358e082251f3acd75e46d65643ef6379683a72928ec4436dad5cb272de39cb4db1d19f6a7996e53a0002f706e228831fc1e6d90e60de15f5da004e235137bb0da4ac87e973ff565b68724f3cc82a75d86ddf092c9df300feae7da5336651b30ecafb23457dcd48217d77aee387c3b3cb3111a322a25aca3e789a754dc3ae6515e067ac31e07b3e7efe63287feeec6b1372cd79a072efa73428b5524d305d17a16c372b029a984c270e35d5c07064ada3e77ad7075ba47cb6497da2721e963d2a884234f123497f82f8bad1aa530f1039374b5363e65a6ab4d44ca47216a00e350d74cc9f2eeadb613e65b9f8084c7bd871196acd849bc08167ecd833b807b8198db64db17af2b4f3cf7966a4fa0d4ebd669f52c5624b4ff8f23360728f462ab80f50de9f7d0318b9283e3e162ccffa3bac427684646a8e3d9e4911729306c628a8be460757d1d4d062d0541e404d56c2c09316bd49989a08d9e364720dff9a3ea544f888cf7893a062d28f76e5ac22c26805805201ebd1ee3b98a1720c59a363255c3af403a5314604df196b790085c65c99c83565d534636b111272464e51330dc0bf97bc7b22b36011d6033debd13c9a71f11b8f6abea3819826727cf575df78c9249395acbb477322a5c6460fd9d7412f598816d109f19e2f05720ef9c9694ea8376f79fd0390fe447c245d64f6f97fb9506f2d5a2ffb77ebd47200d5f8c7c7df9765edec7818074c40619644279ad651a1f015a7945fef53025f4814bd54d951dd11b99b8251a49e3f9367ce4f86315b5741c5db014533b69572dd6d7bc60a2dfc31d1b56dacfce345f4df6ba0d4a92f8b2ff130509f6f5ad8424054b73d65bf738a50500d608f18e6e5aedf37972d74a1850990adacdb91482b39c79dfd88987769675172db351cc4f0085374534e77d305d24e23c5e97f797249a00b7dc98fb50dac4673fcabc18bda0d643d4fb0322073e62fbaf289705e720a841208e7955399a54fe3b498561a423e7d188f4346d97e1714dc5ff0cf9772ed26a734af5fd7af1b7e5f1d4c6324ec4a98536de33f70612c0cc37c4bf5714644a26e61cb08b28675e420fd0e297fa3a44649d54914c10c1b7fe815a995080171a248d48af00d5d4f43efa44c23a7cb19aa30bdf4062b4676792dcb94007072486db14bfdcce7d64c3c0d77786f961af4906a760f0ea7cfda180d7bea20a77200535fae64a46f3f4cf15980b655aed54cdd3b7b9ece4ffacb96d032835dbf729e83084270786574cb2d97db4aff9802c85f7c5ff0b7ffde05b22863039f26725d98a928d53df9cca94d0ab3adb0b978d1c278bfeb4603d0a353962515dc4972eab7c66e1fafd885191b550a78f3db91c6d34225de1c1d710497386047595a725902cd7b5fbc418b67ab77851c82640d35610b01e0325bef1793080a0655c838111f1fc9799d7af8f5dcd52f02075a2ace0083984dd897ae58ce3e12ac4742722ebe70c5f6d8d3173a9da3b8e1845ee7b8c422d7004911d92c479f491662fa6764c2626051ae943d330220e658bc430b62af283abbac0feba8b8f35895b562725d3b7d55e62a2c2f0142d8464b8f0d4c76a158ba233a3a16f78ad8dd8d45637240310aefae8bc6d0225e50218d79c145e446fbf785f4a16c4d44a0db7aea7b4d56ff4b7a4fcec83b033f82e1d33851b8b1c2dc3c25212f3e0e0cf04532b893726c2eeadf2ea20e8a7e947bbed4a96b212621ea8a6f569d55975dfbd2313abd7215e778012ecf61b5797abc76502ce59e39170cdf5a788eb3c128cc1426a7df72cb3804f787ad7e833dae204f32f7af5843e3ba1a0a606b4a079e6634f37ec42e7e324ffa428c320df4f217d23b72127754231e1239c167106050a4faf71752436325096af0ae8220a6016a18d94bf9874ddd865382cc06ee3f2e2b80884bbc7277dba37b622e6fb06e77f0d696c1c35b8dfa7199c610860d2d72ad4b73393d2a020966eea987b525c79b05e5c7dcb0c77ddb940ef127c88f16afd9be86913303edd97c4e03e44a4eec6c7c5c3ba24e2c02ce2288a71d431b016a5ec81f06047229cfd75925c00a640e1e64bd6eb4673c945e02372f83f047bf981020b6cd855a86a29aa00de233f3f86451d01aeae11139d03aa7a66aa63cda92812ed91d0d72f63fb28e32dcd4235b683fa5c2a2f618ac0ccf0bd4c62b69797a44a1eece247257cd42c7097ca629c0b819f393c70255be73a907ece8c47f335b5f52938ced72d527ce3e408a410e9a1f79ea908dfeb707757f441e9ad56051b9b2beca4bab721a96c6114f5239dc1003972597f52f306aafb48179d4aae96aae0bb932d30544a04b68ccc810848e25d87dc6e173e4f8734d78dd87d32784dd00a846282040720a0108e00272c9f42fea3a9fe40f4623196078f9a5bbbacb87803a8ccbc9944950c22e133037eca7041261d1f91dd124fd7257aa2d1ab422f7c0412441021363956355048aa604a0f1002f07b409f4e86a9c336bbf41e30794ab889b773a40726c9ed660fe34fe216e1c253d0fde4023e216535208c428a9b3cbefeb204d2372a97e8a668368bc7d7e16e8ef50ed1dc3b4604ba24a589d61402086c95da8542ea84e7786978ca1c042d2410f09350ba88dd0f7daee878e1156afc05345d46a60dc40ca845717ddc59c7cbde96c1ca2db6bbe99c75d61f903cc5f5587d4802072bdb7919518f9ffa7650c794e40166feee948357109cdf3d2aa55e5147fe768729e4fc546304e20c80ebf7ddf9a6d3be5743ba6d22aa184b331c80ab54ff0a57254a42478ae42ffc62e65305bb5a9603aedbb8c643c794a712a378deb0fa66555a4d15a8c17887589210d342fbb73dfd651a1ac1daa2609d0dfd2e6fc7032307214c85e25842dd91c6e4fda97b71e806d9dbe83ef1685c7db6def9a8c040aa9412da64c915e3dfe6340953bdffd8e27d5ca8a1df0c53df3d031035353188af23f9001f5feae8e84b5d6cccdf87b405c32af336dbd977dd78226a6fc4bdffd532df5404cbca3b714356d45190cdcf51a97f3176cb4b3ad1f143996cff7e9273053c10a6299eb5aaff4a78d743a0ccf02fbbb5a07466470f200c04d74b340956372e152e76c13b03f3a20d9a9f4cc0d8299e6eaa5bca298c9565a20a206845906720c08b4d06a5736371b3aaa0c304e6f2fd27538b589807d9c3659481838ac8d72ff1038f698cb86d10a3076c8128ea59375e417ebb93e5b80fb2e44d0e37632729182005dc65833a3338a853d90cb3e71ade48b5ec24e8655ce4ac07385d4f127e7dad9fa559d2d5e193ebe046089db399699f3b17c954c6f7188b12788c2d472b843addbab6b09aa6e78f0f92751a0ebb75d8f599b39fd0ac0759edfe99c6a37c0ded410844c9186cc568b8be7aca564c289db125fcfe4f7175e01702f6d3772495dbd711c8ed25e25fb060e247da309e3efc7c7021578487fb7bd42a6bc3e7242856b88c30807c4e5b46a79c41edbfc2149df200585c37f6752204179e683525101e7c527e309e554635f502469939fa492d6b40c860d2611e1659f27a46c1752e7da8350e9a11620b010c67abcd83c47106efbf7c96bce7d09f689abf0f972705604cfab54099cfdf9d1ec063daecc173fc11d8c217249ca2dcdb22a718033e3d107ca98107d8897ab912f3c28566a4c9ce3a618543718728f371a0932d85d8f73f46c74ab05a2619f8b4b69253af75995b6146079ba11c87c73ff3efe8b4ee165bf840f064aba3b70c4a54cd314bd964ba42c2334720cf3a55702a679e272291672e223414ec3dba3ddd152c24b6b65953aca19da508472497cea8ae9281dc2dfcc606c23b5850c14bfe7b27bb9cab4643fcf3b8fe20a9bed156578be17724a34eb9942906ab56dd2c24c7cb748a4491899ca472cfacb6ec2e2a9e7c040723e4cb61841ad1c64b92f982908a8d80feeee06ca537e9b36d217738b27036272e2c70b1f4a1fe059e66268da02ef4a148793fcd4bfebc08dfc54fa439edeaa412c98f6e33c930641394e875a411f08701e96ce1eb62837f16b94f660e95e9f728c0838dcda478dc7e2e3c0b8a81e8e523f4776af85820c5667de777753b8d1136056f9797a598b043ee5ac0308063bd2a6e740ff2328b064fa646477e0449e727bf28f7eacfe2d8a72afb4ee4fbca295c4b8c9a7bc081e9aab583a3d2d78067235b1876fb007feb1e35203066a2e37193e06e04565c6520dd84214a439cced72ceeca0ef15468580c954730d9c7f558775de1948b25fe5ad638dca6acbf49e0f87d87b1106b8f9415a2dd053a917736b538cacea906f6f0b28b5e15461ea0b72020fac0ed11c69533c3fce7d55806cca34c1e81de4195749cb63b99c17f3ec727c7d084e92e6ea81fc8adb0827954425c3a253e339fa21a9096e5af9e4daa7724ddabb8a1fd511a55193656b433f865e9881f8ffb5838b9f15fc94205d56a56db1bf24c41ead2c390970d97bd345d5da78c67cd296ba7714cec37da07e660e16ec857cccc38f0abf3dcb14368ef5037745991eefe4567bbe6cc96d28eb048150e868a40286385932cfb1ffb809fde8bca75058d894744d9c9d7b86befe5c557274ea12a3664063015ca6bbeef90d9fb29a419815a3f25453348f98af72e4137233ad8695719c058e00be6419d67d57c4c8fd1077aa6166ec75c2a7e41cef47722d99a982f3d5746b95d99f7cb49282277fd40c069d4ba3b62d746318f8ff8b274fcc7685843908358f76affd1d816cfb82cfb1fc889cfbeb3fef81448025f9723a6bd25e949150a597893af5d902f278aaf73a4968af5b52fb31d6aa9b982e723fb4eb73157a5961fac5dd2644d9fd658542cb21728de7558679af87f5fdb372358510a8c50b839d11bd59c58d47605317604abd566bfbfac0ac2a3cf2e5ae72ce818cdff6fafc29453de35479cb7a6aac991bd6c9312d09b04f9bfa74c89f725926f1253ce7e042dc89cecc232875f800ed20242fc00ec36fd40560098f943350a52e369172c1f8f62b42366627372a05799980faaf0bf4b42efe1c6653310bc00936f4ebdccbd989ad3a55cf6737f0c9c9d5583bcce48cba93dd15af14d568ef7e33ea41742159df88df6d75a7bab915b87109fd21aa6686e55d735d37db72b19a691be02ba818edd4c57f17a60d421989f3ea7bf17161a277ec0dd9c6097217385024356a0a16949cd7d1f9cd9d852c26bb5f3ae3fad38e00406eefbd3f0c4942a0a811b734790ea8ddf7d745d2dd0b8e9da9f9263d609d8d8d3d8eb220318b582d7b59c7ee4933b7bee0f98955e64ffc42b134ccf387a3a1b894a3248472b27b608ce578744ca3e3acc7f0e0caad21d01c3cceb1b74dc9a425de4cac0e4ed7b1119c586e074d7f641f155380ebefdb6021cb30094b61471600f86a81f15334ef6caf7df32f53bf63056f65b3dad28a8a4d319e56f52b606f2b7f795f2f72342dc38a6e20dfb291b4bf0f7703a13945113a00843d601b043d62e3d0e60172784a5f15807276d631992744448ea41ab3d7c486287968ae85bf70d389af4972dcfa62459ea7ad3d348327369439c5698e06d1495330aab904a8fbbbb9069a7284b1ce15c540f68a82b04b17a4b0241b935d6a03855099e9c9d973dec976e6015ea9b4f3af3651e1fe034e078d73b69938f9cabe90057d8ba6336d366d7d5d445786525902387d7fe32fe5cf84a0a6f536e525936e797927ae18eefd9a7e3a72fd4e04ea6edb21f872fefffc699089f7ed5c33a19018c1f45ea820a6abbe557261bfc44ffd9fde4d7b84623a7a9f7752b852f8c52be2c3f4149ce98d0cfa6c72f1406c50b3b79c4560b5ee4b2067d5bfafb26ccb71058cba01e5ad9f365b4f101200aa43b16a48ea1d7c6bac1d9a82d3bd83d2fb88b12ede817c0a86cfb78f41b3547fb81e92f61c4c24f93816bc33d1a1e0c2ce61592ca9af89b40e290d6e72394b91fd1e783419bfa4a429b225cb563aec75ca0480339b8243e4bd3a123f0458fbe15153df38091c2497fadd99657f81b74643f07fa37138dda75ed923940aaee0bf7584d6b9300a74b5f35ebc60232e60c7d5083d67d7e63e7e02cec1bd6dbb712f8a5971f3203893f9a53e2d7dc0060b6239801135d4ebc67e17fd144440ba93040cc1baff77a63ca8bfc86aeb785e8e02250d14dd92cec277c03dbef24a3adb9c32714f49bd40be76a28e18dea774c046748e04e4bf5f5f122617c5c3624abacc97b7ef13311ab566683ab7ce8735129eadfd6f0e8aca224b9c959ab70a15f359cdb3222f3b77f27b816f1691b8b8d2e0a911e06f63053d1bf174ff4a723392b0fae531b6a668f8a5189b7680c76d42507201ddddba9302f728edc35d72dce2451343ed4b01200e9b27a02c1a93d2d3f69edbc2351530f9069d7d583c721d3bdfef5cec1eb837b5eb057099ba589015ce269c26a656caec0289bdd41b56641b8de7f1017a614f4764f5f1e710e78cab409ec669cc059a25c9b5fe877346615a25d6a9a0d5b177f6d48f728dc13091e27b342e52a28fd5d0b667dc78e872e85c6d6cde3bfd8b2da8700e3adb9b2e4a305e2ee4094efc52aa8fff040f6a68720f2840d2466457923d9723659916de371f7a7c2050372ecd512d994a359572df08caf21086fa60181296b22a4b5e5de427f6c03583fe7d717096bc9d6dd57265a9d05849569a20f0532f608220e99b9044e5fa4d5377bddb5aacea4209d53cad46a5f09b49c43ea0dd9ede8c5b8e68b24b3e00d42cc9fa6d44c83c4d5663727506c44badc4b565f4ec8dc09d4a77d912a416955d685e2fbe2b4323fb9d9f4e2ed78808749902911948e5493e96fb4cd5205347759db754b9db1fde8bb17860c53a569d306b118156bbeff0fbf8e524015c52f566c65c94799bf303875b0d381e04980478b3bafa1d272f625ee89dbc5cfb1dc5a0fcf7343cc2a32765e3327208932b92e5a15ed78774a2b587ff461884bdb5d09db2ac98e73fb1041f7518720995a71f99f8ce32d43fd56210701e479d3da51ed916398ec5b546ed07299c727520219d23e67267904aa509450c78f74c0541e996b74eed4691d89d353d9d5acedc9bbc26e9cab0f750c0a85b4efa2244419d83a53b3a9a1fb0c073d56f73721d0df0c18ccd8abfcbc94c05b561bfc2e065bfd56c645054dd52ffcebc512c003b7f59d746e14dfcba8b911d1769da8c3cb1cdacc467619302d6b1b43d39b2613bf7a9de4d91b6f097bd1cea19e35f2109216499ca10332310763898f76a8a42322877b2a944062b82f84df448b11b44bedea679def44dca42a28db2e6e1a724b33157ba42932efeff58d893578aeeaf5038a0da7a4803f8bfb671636478db09d8f0b6dec18ed1ccdea0f34967c5691ccf12d1235378b069a08953ced5cdbd72b4ed9f3eab2750672db0f9a9f20fe5b7231e4d2125b1a01e53970bbac34b5372195e91cc626c6728a94d329abceb60ad63a0f6f06e3ea96fcc5d34b552790a725cd3aed20c1a31e654c19675cb2cef86792bf37be6920e1cf2b2170a5a6ff1723ddc22940c689aaaf7e648924b851cca624e28c088b5ad5cff26a9448521ab725ed67a9f12626d829cba8c71c8a2316e098728d029d107c099a6da1b7240bf59adfcb070ef77a4e6902ac1b064de03eb011e3e723c2b9226a9e221687fb012667e65fdf9dcc6a0c4d81f7a08325d24884b80232ee2dfa02c74637d80c450997220c7b4a28b80e2750569516b512e809d0b8bdba953a6d95e3fd30664c34e1c719f288dbd15abd149af2b369bc809dc49e919c15f0ded4bb7906c2175c048d400272f73025ab767a3e75c7c4538146509878e10dd348d0f4650d44c68d5f96707f853a4a04ebf2c28da2c9d702a4e4d9b97b66d0f722860a818963e97e7074a1dc26cbea7496ef9d40c17b2000f4086454dfb12bf26fd78d12687f55b32fa6b72005082d4312b29b9bc1c4049ea7fb70e2c3b2464ab387e800882bb710b38fb0b234db4bd7fb11688fcb21242b527ac63080149a1d7b4fbc96af8e49404c004722844cc7e08f9265152f06d0b9917afc864faad729a7258559bc5ea6523a7f954ffd0a55f9f41684db9d415b52cb55c337fe848107c1d301282ca5499258ef9462160f8bb70ad896522aa94b56409acd8c91ff6b1b85e6bbf665b2144b0093835f33119241e844f9bd36620bf335fa443fb7f0d0d28a5e121cc05def9bdd93d0effa3d3f2253bdf58395bc690ea4b10e46ec4c9dfcdca86a9e909ed0e371eba5e27ffc299fbcfdaa17b56ae98dbfee6050185d57146f9db63841dce599be5727240020db4a09cb467175e69cefcaa156f773a9339ea55b197c2dfbc9c627cc65b4d03a632e04151ef02b4b246fc2bcf8d32195759b9cb2e08c0c67822d1ca662fbe30811a7f5caaaa3e6a4fc7c829922f93b8def90b8343030088a6456d9ab20157b78331d884f64d53427b3c60d998e702ea5ebde89dc9beb9836abe1dc84f2c0cce076b309fe32fd0517ab0cb9c0050091c658050070017e83aed777cd80b72281cf0fcaaf18e435eb0a2006c44541c02afe3e882b4c621d9637622fa4a9d2abd0588d5ced56dc112febf83f568caeb1e23dfee8faed62cd7fe083414828215f1cb8ebd8b198fcfec7a77237b14eb40d81bf415b101cbe01fa51e10645bb63849cb14b463cbd350707f8fd8786fc0a5d2a4d94038d9f70c3b73b67a5957ca720c1f95b404202fcc9aadc92ccd178bd46670401bcbbb28ecdc2265c0db5563723ee110c38bcedd417b721feca3d8fbfd0dc135d27879fb34497323431efdaa72a7e040975a82b2863a34e47d45cbe2b38beca3a8aa25ecde88ccd87de20ff2724ab0a447c6cfde9386000f5d82898469c6ed41e96493eff0938112fbc5356f72e0fe8fd2190212ba85fe880235f98b3af1f19f1285780e60d5492560b97611341161973c39a963aa3290260577f2344363a045cadd9264474824e146c5f79c2ae8ee70cc2ee97b122316e8b4e2113b8afcb94a905926a6945162f12f7a908c0bdfd4f6993e52f2784408fa210f983a893f139be26ececf82a6802bfafcbb4846844bf7c705bf31a0dc88f15042bb787ed26a2b4515c9a3a7f1d7868ba563422f4984257114f7787fb4cacc5ad0ab82f7103c5a983e65d909d10fe6136524bd3a21950c9680887fabba18a62014577e913e78d5205667ba9aa9f8d009df210a0c2d19ad976ab21052b9d7c9fd09c1c54fb339098cda2a19165b7c7b72302a8572ad5ae1286db1d7e43e8d0f087e15dc5f500e624049c3d5830f55536b8c99a1725ead529bf198f5fb510fb7aa8526f6572ac7ed37e11e7648383f0156f7dc6a50a436fc77bc1c7a2ab168e80ba796d2948f7305a05813bddd2f9818ae61c1802355117cd3e117a8a080ec2f880f3b3fced5cdef93e14ffb21ed1677d3a31e672e9e9f0951e5f001cab128c1ce87c153e089a5bfb86b6b977d87e9cdfca41ec120bb6722660bc5809aa320061961b1e930c6ce6e987d445d10450b7e8c3ab47872c087a30229304387bdb88e2f8c83a5eaa62960dfc1fe3a857c151474abe9ba72e4482ee5a736058d835a90f4d572467ad35b41c67394db76c39b44d20756a47294d78a74bacd0ac4a1aa15b3cf892e9ba8fad684f8c9ec504f9f8486ee098e72dc850d42275c6149aa6d0f6f3726cc43e5ef42d57291c92838bf7bc12cedaf72f1f39fecca335d0f64e417b57dfc85029f70f7ace1bd9061206abee662fa5c1f7640033cb2fc74f5f1dcdc4a419e842e59f1b73a868d9eab02e6b567f5f08a7256367f2a8707323c8cccc6d66ccb8fe8939a19ca9b2a008714a555b6b0baaa0db98f1e6749ddcee2ddc10565540221721f1dfa3cb00b77ef217f7f8ed66966721bee1d147729d566eea28c8b21dba061b16b9baf5b0b1aed4a7a44a88d24001e400ad91948e920b3ec5b920bea97ae5f905f8e12fece0e3efc8e7ccb16ce6772806d372dcefa3d4241c8c119cabb677b7edd716e21877875a6cd7373345b5300f4cf5c3bc135ef46dd7b4529d81214ec92bf28bdecdb9f1261ca45ddba15d872d56bde55d8c42dbcb5ef5959c98cdd7c8544ec9e6c984f4f1c9edc7008a15072166b7d33c1d435a0248f0d02f8a435312e7df4e3d03735d647494445d5709e725f94e4ec39cca1de6997cd4a554670851ca6d65bc9642faca3187241708d097211239dfb28edf3b21c14dffd23942bcec55295deafb45bd5f67b10f64bd8f772f75eee983916ac615e5cf59faf3f242258a5a3f0865b03d4804b23d4632082175ac56af5a881b0d3801431ef165d8af671407eb8fad6115e3f76770996f508720b26f0aeb76b0bf60fee6b6a9b010166270ae607e2a15302913089b6dbb9365ba7179b5d68293b60cb5302627aaa4135c2a33aa1bb8c33c64b3adbe6c96bc472e27a6d4003f97bf21d51884e2f4236998948b0cecdabe8c73869373f73f50572aba9171eaf80a705f0b6c4d5c3582b9b880129a62690065aebf0e40088f97b720a0c1d4ab078d1376a93df0b5a1225c701fbdea5db64a9bf85613b746ad17e1349d881c87f9d9f0b2de100d8cc46d36252c4b4c307f4f1658fc1784fec7c8b24256e84b29bdf6300ab8ca6479c4aea6450bc9e3806bc06f7dacae1ba62afaa72da1564d6e1d3f0ac80d622e1c93c2198ea06937ac55b7961bb0193632a2d3e72118d75c54f198c247e3cc6b25f922719a5ddb68b244e4bbbf6fe300094f71172de3434145ed7ad1429f75fa2f557c5c02203a6fbd4075deb2d3f7b55bf302672065e53fb52892ae4d63bbba183b3d6986cc5e62c358369865a3eeb964efbb64785d1dd184679b6b9735e8d3018660a4cf989e56630211924a81714dc2f810772b05aa9c17134da619ff373a5c7e458acfedf659a9f53da6958af5a8e9475ed724a0563c5ec901c7a11612a2c70bf7339a70e0ed3f83489dfdcdd9e1eadfd0372719f726cef1aba9c49922fa7ef42cb7afd599af4163a821e3ba20c7253bfa6727a39dbedd1f723a25b0440ffc3481db6ae4f3e7ac639a78a6170fab340993c686f57010d065a420ec1423261820519c463ad0fbb75d2d3222fc9e7792454707291e85a478e9ed1c3ead2f9c97f1838af0bb0e7247fbab0f5bf18f7737fda10394a22cf7bc929e15640ed7c4d8fcb3dc22d54344d0cead010bcbfdd8a02dc52722850872f63e8f5781a02dd3bf0c9046bedbec76fb1653ffbf4ceb33cc019b872fb44c6e612f287275eae04f733bfa7768ff3089d5c0a2448a509a49b5023ff2f5a51c69f4294266b115dbec13c3d4321f10b0f03fae05a9316b9d56d900f8a72a9966c9cd4b7e1e2ff543c71cb61534373f5a7ee8298956abcf48a49946d140d6feaf2e5b68740fee6035879f53cac4b20911e876a4a6768860d2682366d8472706191e97a74f6146d85ae1d697f50b581b4b1cf461732cc41eac679165017722d8e0fd06ed6c6656af988e49c8012c7a1207f4f8e0cef5ba09e08fe7593c255f55bd1bbfe9334b9f9f5c5490f86aaca4751c6f5255548cc4c691b4dec3fdc72d5d3dc7b7aca7bfec9418037e128f32bbe8f733cc4ef9de8e6b5884c77163572e72bd881b0bedf29d9a6a38abdd2b27e9f79c7c26ba1c1c28a210e169a232972daf8bde6e188c4387a21c04e38ed1061ab86a14f5941f60a60de80e5d90960724533ba41d3ed032c1e3e1083d71f7d6bfb913b48e006695dd095771d02095872703120672ef004b2a2c7e0fdf020b77fd51bab49f34680a698106e417c12a872b35b785d33fa81dbb31c7d8b765b16bc4dca3f03acbed33ca89041d33366a672aba8a57a4c7968024317f2ba9a1744618bf4ac7c4ad38f90ef98dbb30120aa72b471f5da2838bd18abf91011c3996d8b0b05ab573245242f241e02d82f221d7218240772ecbc1732050210eb05f0359bbb38db6dde535c60deee0ec3f572893c84ac9726e77ac44a71fed19b0032a879f335c0879a7b2f770a61e994d813f072bcab3abb6af7db745002e904b9629236a4d8dd1dd488114ccffbb49c22aaf5726556efd51f7e07ba382c232ce6f378e25df6a39a87556a0f3585ad3c03c86839218845353ca7b83a1494974bdc631cf3e4759c058651a2d80dde18993f170f7297633cb007ddc72139442179c7ba94c6a828fa7c4d8a5ab6d43a4174b01e987211612c20a5ee8da5a33f2dbd8d635379fde21a408ffe82fea592684df196c15c1f343bb95399f6e30bcbeb7eaeb02e587a11665bf73c8fd825c9cbf6eb893372b917e8f06370fbac8e3b172d0a414d4e620266602d7381af166a75e28b8f9d484a27dcde48ffec80175d4a67d908237e3eebb2ae4cb62a46b985169f9f9a7c72c80ed353d09ecc749b3211751dd52bd038d8912d7092bbc434e2aec3832cad4cb775aa59489d4b61098974b39cc4eb582687a98e9a31d41e5a63b31e1a43ca724f7d059404216833c9ec1262e31304e0898cbeeaa6a10cf3f04992c77d87d972c346d8c226af5fffc2f66e73ae9f7155b7715f31a839f30cb7801fadf6d38a41c1ac15499f74804539dd27c26b4bb9c9d502d0935179f0b96b6e81b713e90972a3cb82121486aa93b09b750a859ab33a6c7b009b0d13279a4ed2bee8569bbd725f4b8946bc5492ea8f50706476fa46d01d38af0b6b125055b884802b23c5a0720ad88dfc865d3002568f7977048dceea4529967c5d392e993ddcdb03df826072ddbb32706c4b8ddfc139b9d8df84ee20aeb43d198edfde5e627cd8b901251172e4096d628a8b21e58066bd7e53631b1d0d772b12e28302932fa89bd5e9f9f0725faa2dcfec3b8b35b4457ec658171aba39dc0016b226126368a718a2c7d74c1ef53d4898e6387f27e0990ac841dbd866671e107899790151a4c7c5e7e0478b03224c357668889207e2d9122c7802fe4c78c6f404aa8c4a3acc29812516d84356cb9bc83fd8614a31d87f9fe3fe8bb904a34b941f9a7421644917b764b111f472b126b22ede57abdff93002246187535dbbd47f8d39091c396bbbda61bd1a52726bea39270e60a81dfddf3385cc8388a1de14c14ad4688ae4bf4ddefe97277272ea7eefe9d771d7267180e9dabfd6b6af19de13a2f70dd1b0de3852601e86f472edafddf54825787a42521af3c05a0913da27be1503419084d3511c8d08549949bab6f9e8a4fea7aefea35476ef77e8771fdd1f78fcaefd4de0a8e395cdeb4a72d4fd2aba2138a5480fafc349374ac5d30ce8e5741cc8f51465a029d54f016672c30a887913935cb794acc4e50e232db407109383746be9d4daf2e543db8d6172b692efcbf95f9b8088d24e37d43b1484c7ec4f6ca45bcded89584f59b77c17722b04e578a880a9ef4fd58106e39d75cbfb220be653cf1117844dad964cfa2d724181fae94ca249d613589c97a9ec119d58961e9d5d302a98ac78634416cc0e0b5d3818146dff8b41535ea16f12148665c351b0b6bdcb7a9727fac9c59ae5aa4ce1f15ce8d2687ab77a649d0f3556f9a4abbfc4f73b641c6af35ce240f7bc103181e2b37a23b61c3ac65bdbca6cde78085a18c11a541d6faac02f9b2062181b024c5b1b6b3e43754ea0a8eb89bd26c73775ae03bc0cc6eb146d721d9a8698e572132e1dc22caf547a35c9c5dcd93dba8187390e223daff6fa74e26e46f06c3c105eb585366d1f1a130fc1c892cc0fbd215f60b98ac356537e2d8c879b5e83e372f6afe82a2a635a08add15dcd715fdcabd4911d2465f773c62befe142e6ffe172b85fafd5ec67d65d2dab4388b72e24e429925d0339b2993880532737e40a0072186b174cbd9cd273df4fc30ef881784f2c355a99c22172c8f8b0d86f33001a7247411dcb88d06cf98a8a225277a060e3af6a6c432b4abf229881ab7261eb2d0b97934255a1808d06dea25953b16a4dd195d2f395f65b0d69eb78cb4537da8f72f9620493a8a8a53d2f6d6cae5dbccdb73cf18f8e014026ed6c248adef54a8800915ad90638a9c15a3655270f991382e8d0d958c6871264a0dd7b741107f98d5e3741f47335409c3fb4967ed11e9ec9cd805c91a98a22e9942d9f3e57d97dec03625f97f75aeef4ce9c65f6483a986b4a4a66a39985ec6ee5f914820cea277772efe3880c080bda54843f531b95a073dcc2ff3859daac3ff23739ee8a8340402b9e35865b40a3995447ef6d010ca9c7ac892e415950ba2624125e41fecff811728f4fb288a662b2f43f9d36744c271daa8317cb6fd5370cc5887d0ebdbd9d0c72f3aa5cf48198722696fa29ca910e7228d0bc769f78b37817e918315dbe9ce243a0b0bd46f4224ae1610d9c79eb7e0fa801c8b6b71293d18e6557b67524e8891ce5b75ab9c6488b04cd7aa48f155a3927e8b3ed7ead0ddf0a4ad43bb4fb1bdf72508ec0d52cc877c3abcb484333f16c0117e82df8ba649412cae87c121cc2926a7bdf92a95369d47b3b62f2d58da79969dd92d55bdce65d1500c08e160ebe18726e1f64e9293cb5632d8a4bbbd7cfe72327f7c7ff72ba97fa2cb268d3fed1cf724e0febdbaad9f3d6ad4743334db92aa979cf8b511aca40fcf6f98a05278fb272b36823b56f709586e30fa9af749bdc0282a0395920e035fe3a85a1b8cb953272bed7143d72cf1ac608c934d8b5742f38dcc90ac2c2ebf2a3507690d1cd43ea573705a34cb9492b93e2de0826368c05265546ea0fc6308f0f7ee6c62974c48e72fc8afd95a29c71a8e62de0335be9e899e3e9951fb60da6136b170894e78f8b47b69101264d55577dde8afc75a989f157be1a5dfbb8a2b63520b67e82c810d372060e80c9176a16f0635f4e5346a4b418ffb85fc3bda9ee4a7226eaf123fbb81518938034bf6ee50e4fd58085f19c18038e36d91efd33a287d434551cc02ffb72a9408d04ed602b6cea41043d208140c4caa5de8fd20d7278b7a7690e0b8fd55354641a7aa942071aec94fb88da0657106d745d1fa288a1ab2075c1f75a7f981ce2c344bed14db20d576c2518fc277590adbbcb6e2b88826ca326d2ebb246ea21c893038916f4fa6cb1aed010a72377ec133372950ce90c17f5512b11afb82772709d776692ead22b212a9d4a464c887f53e4e8fa335363e3f7e1d5f1c917495a54f40e3604d787a14e04dedc8a88ff4b52515d1505b4459b92a2c4d9f07ea320260652d3f17ad2dcb326fb27e8c3b83536ff4afb5fb34201ed14f88cd95ad15af528c8efcb49b900689edcc2134af07cb59d64e21aa87356d985e6ae92c78872ca87c4455e94e49f691d69e1ee0aaa27ba6d5e6d6f08e8ec2940f94d0d3ca40bb9f48604be80bc9de9ccfa91a0072947f7f239fe2a2af669f6f4535647620e720dca42f0e9e3b31d96afbbab0734d4c815c8549c4598feae17d003dfcac2b226f8c3aed43463b721dd0551100b00ed959e8cf6e11821788bcb4c4659a96cbe0625453d37812d0394005a8562048f6383e26f252bfbee432918ed15d7e86c3d72d435948f90a8f5737406de0f0adf96374c98f285d8afa6359ee06bedc24a88727ad78a21851665b44616e3ca65f88ba31ea01b577e71c55050a24fb6c004a4723fbfe9f1dcf5ba5d8af20c4f7cf23ddaf12c7200032889dc9e07fd6dc443041bdac411887be63476f72cfb3856a6dc37c98ae6457f3cd731a869700d81685672985f7ba04dfd4c4adcf3fe5a7cb373158e3d15e4778844111a9bb3e87e934c72591dc7316ee5e78a78247a09dcd1d91065353768202d77b80b0c59aeb4bc6472bfcbdb6797902d30baf40c21eea265b635ba970eb93b3623dd0cabde52baec15476f01fca65eb509990d75417d7a847a495d69c58f9d6948ddb5e13d35bfe77288b63785d7d52f57d25256aef0bf615b0f904dee904e9466e33a734e775c5372dff2730db354d65e56d85e28a44cbbf8dfe80b55a6f7ab9a846995c7b4323547bd3384cab026006046503394417c9d12ad6b4dec58a428a57daea7b08700a8141325cb00cbb4d8197ea651849275ebe5e3a5e252d3e9e3c8685da25e9c536d5105fcedd61254bd59f2b5a68ee5ba640734b363bcb818562000be1bb5a3cf3e72b74d7f33f989892cb82c62ba3186199865a4e24699741791194c6174bc7cc872df9d9af5b537c9d6cbc956d860482b6775101adb72d0bbb6e32ddd8fa85c7a3f6875a16d74fbd6bd31813019f74d93733b9f3d2a11a20e062b54d707fa3222046dc2b069b46c442b20ddbc14d649cddae80eb4179ec17dceea44e3802bc0467225c7f3b95b307bf24f7454978c6996c7bdc5598bb6268d0879f7e1241cd77c7202131efab6d6878fd35247f7cb5f8b93e5e447135f5da83f2768ede2be5a3e68546bcf1f485d89f89da7292d69787b75f039e752eed5263a210ba4aa48bc65728a28682d60c63c490f49c306a3997148a27b997ee93fcc8cc5362fc72fedb56f45712746cddf21edad7737529a4bf4bb54e867442747b6b0bfae11116a05a472b62374a35f1444b5a9856b150bd42a68e131abd93230296af5f5f8a4595df6604d85d564bde3135a912547f348218546f726b32742567089042c2a5c4b44a551f977bbeb1e0eb5d5c18290b0c846540c15bcf9ed881e07569bf062307ee4a87279d4d0168d648dfae9439e4cde1bc0f471bddb477f4107d82f5931b9b8293b72dfc4fca2bbf7ea528674190ad9d3f1649fbe0d1ba137df1a47cdd4431dd1ee721ec1c50555072b9fd4f40642b3eab03996f927fd97d9aa590736db4779e67b72bdb2032224b476333e4527af7219b47e33ceea948eba15bdc8eb23a9191fd8728f21c2e7e1b7d87d5ee29a90a6e8f40035e3079966190076d1a46d0a2f28d472fa520ee2e6ad1fe396da849382d53adc45be5205e5e88bcd99113115c681d8296e7b420195c97d251c33a636ff3ffad8445aad8017e5fa8b8edb2d1fb043c319f73474f91c64d2d825916dcd9473707fbd043a15fefcbb8561587b1b2df10b1d7e8ff4fa9d5c04ad2f9ad785300f6c7c35bbba1691466bf636b8881efbdb4b721a6ca0dfe49a9725644404f71e76ba6b09284d2a14c5f78bab8efb7f1d9a0b0327c4984c7f854e8426f9a5e1670419a6cc732d8f6f54ec1496716b06bf3a5f3b232685b142982c2d2355bdd0aa93fde2e698ceeba2587249b11bc438e1c2cf292d96f06dc6542e1d077eb1047eb8e1f279326c141fd8ff06564c1f079caf92720137d251afda2d26eb8239802145a5eaf89a8a0e7b14e14c13065e32abe78f72a59c3d0ff0aa5c8c53a1986b7fcf7c10a54af4972b53b50b741f8e6bb4deae1b08065acdbb029a66111de30e47f44e2bc5f166203a8c192792cde4a5009c1745243cdb8d49084fcc1fca6b1b40adfdc7c213fa1020fc915ebf5319703bf1403640fcff58bd248f2227f7ff005deb6ea31a0667f4d17c82abc4b9ee06aa193b727953e838ea7bac8c0d57c0790dd8badf2fc6d9170360769f259657eb975db4722d6fa523ab3427deb562a299622c677a1cccd3160c98ba6ff7518b575f1b3b1d10443e5b36e83426008b5281fb36ed8106db7ce220f77ac11fcd5e6b56ba2b72e69b655b39f44f6318198cd2afdc2d479c4b939a78a32dea6ac573980e44a572bd2c2eed498426d64e94c6c537ce344e39256f44abb0061d8a2a1e1ea41cb872f9e9f2a7161f3f8db63165f1ca67f3558695c3a3b39552d31a6174f2f6e22972ffab86d2b8d432b787b2b8baabe9c350273bb68f6c546d9f0db65986b7455872356e11782bd4b1d3e0de13364439a4823e3829b32bf27fbc263516bf58f55872ea5b433fda962923af492b13571e01327495ca5b7b84549c389016e157a4bb083aba12b5eff41cff06318e31fc70de37eb99ad768e2e35c5deffd30e20295d72fd9fe45fda00724dbb736c4ff6688b23bcd8b0646931706b5b00a0e2151ed959d76055d946ca491da5b1ebfb5ad9802326c00c4eef0eb09a24ab5397995747720f423b2dcd1142036813f4fb40306296c678d10b10ff21fb0f92e40c9417e161db4ead248a42990b38b78a2c721fefec41fb920e5f98b6ca8aafc7c5825bba72cde53fb176ba4fa9228f94afeb4680f660363ba9c52885684c589469b2017b4472113f40f2841fe4a562903209fe48e717434f447193f5b24b6eba699407c272d793d6418dd193ae97c15deac49cae2397955f29bb1ed4f09191477a46820072dd59d74af81e5e361e0cf96eff7666470b0380f027c0662c66ea7bda2e9f6025927eaa5deb797936e498db14643a8022674d9ed853cb829e7e2ac191b4053072a6cc5165e239f7b23f298a3dceadcf91e8f1bb70d6e0f5b69d7c30ed1f66cc72d33a025e5131129fd2127a9ae7c4c6cc5193b438a0bdcfde6f4bbb636fe9db729c7ee2c12f59bed4223c029c57d70bd6a72ae69fb7e46bd60eb68c96420be4725ac86805e7b20f37d5947113e98191f85c91cd9689afb76f2dea010d342a326797fd90f19fc0d1a5deea9007432164b0dc50cbdb002338fa37d859f031b64a7279357404ffd0da8556fd6e297cf1507045f143fc30c450d1a3eb04474bb6c872c3b6d346b2d2a99c91654878c2fd9fe931dd2c7c1aaca13a4d0bf83f8fd86d72c65bb4bb53bea8d2831a3081562360fd130c494b694d59ed5add432d129fbd7242c1642e38ad39d10ba4e47c0bf4ed40d558994be9b5edafee2dad3b361aa172db0ffa0e98b423ba79dbd406c8641b03ede9ff3711a4a0fa7b6c1d978af9162a6a8d306e41f56216db3ed90aa9f1686aca9fc51149145dd5828ff28dbb5d93123024f30f7b2c440f894fed074b95ff52097edf3710e7962e19e6dd0e9392233bd0404f3b44b022401c533fbac95f254d16806a4aa3f257e0ddd069d7c83328427005a9215c1c572f77dc2479bf56c68736b1de7d0982c4b99f9e5a5a7352f508ea619b775eeb87e0bf6bf004ec222f4560ee65ffe2b13aee5072d31b32379872e4f01b4cf3043eedec87b9a48beb38a04f6149ebb33a5732bef9c77c6ffac060d921a7f438cef1858193a2fc9219f024c30f918e0191e1977e9bd1f56e9c2b72c4d4fb3174cc1c85d5bdc3334bfbd87de2890cc10e58284e1446f540ae716d64d4505675030424608cdf4355b835ec6324aa00f66fd9963ec5617eb441f76817edf1c4bc15cd70d2595143e1dcca69dcc0c0569a21ad30dfb91d71993aa1c127bbe1c5ce9f7e80bf25f08befcaa67c248dfb2f581a49b52c1ea5e625e8fe31720f97bab34b84c2428a3003ee88260c4d6ae846dcd49b14dd460bcf77d75c3e58f667404f1c3c4d28ebbcc13050a1626552e04c2e557ffb23068e0594a2e75905f9041cd9835378c7390a636b673861054bc8735041b5b7808497bc992df91d461e55686d5494bc5991ff403401c5414ba2cc4e57eb71161903c0e02a5f66cb722f5f5c0b87cf60d973828eb91b002546b3532157993317d5faeb0b64dffd245e80afacc86f4cdf295b4c11f043d75a2d0a63b0c51271a899072601d34e6026722bcdbc40d5bf9faba70e0165eba06b998cef08029eaa42576605a4a0df3e88722339d74b32c7bca2b231b6639c9e562cf5795f009f936945ee4d0579f84eab68b91d17393d29e973795fde7a3fcbf2417eff76f679806e85e778d9af87f1f5725605e23487cc49adc0f87709cd5754292624b727d80a236ab512ae9c0d6c92725dc0c8fdc86595e1f3d05029c37afd24eef178ad95848d792be6b80468ade872f729615f7d9799764dc2c56328016d8664eb4dc5a0408c71d8c4738da77a8e7263a774492377ff84af1b8f21190fda28b82c470ec8d6db81d68cfa28a2276e725fff1232eb756597edf4a53204a898b5157ff7e9c58893663b5a5e43b089fe72d28a5deb87dae37ed366f78a31f5875d06d6da301996fda6fbb3d3979d4a505b685c4107f90f8efdf9d594ff0a86cc0bec99d2236b7d4c334851d91803ee036db1379ea4526a4971e953382805403ae7e6ddb43a2ee2340876e8bd01861622724fb0fc0b7860d8d9866183a275b223d5dd8182840338101863daacd73e0e10723ce2f8fc321f34a751526d0a6dd1e583d1707453ca48c08310e9380933fa3e69b8aee039cc38ccc7c33c4844b64d6bb86186dacc326314d797863aed5f8c0972e5f1f2158c82b24b481315ade111c7754579d5fac677ad2f5c5316a2492e55728bccea9226fee1f4e8391c7d66d96a8f04188a27c76c6ce4ef5d923b157c0672aafb63336727e925f296247d46a0623422e772c005a7a10a2633b7ac97fbfc72ba01258ec6f226d256435a9078ad41101196b6ff6079d3bfeed43070d8c61839d0cc2690356e4f2d1fb793420140d368260f2380d1e290cf699abc2351df1572bcb33002a9bfbf9c4d0d8a533468f4ea07922c4538ced2a205053a46d028df72bd6c6db09a4020834a347d07c91c794e43f621df4494379becf9a5a37a49951d1f94f5af0de5061687f81fdc8a5be2ef7fa6b9a7440098d70bd0057cad20a7728288e3e075a2c9de0334429aa58694a2ea10c89f01b02695157b24608cff2852e069071f821b1fa9d5c5022fd4ce0836b86e20e459f0f105911bf38a884d4932308c304de5e7334d7cd98db6539f13dfc98121b337925152af80207c90383172c5d4ba730db5e4c867298022fb37cd7ce5c4edc554adf3e9d829fe1ad6a2bf72bb0c363228143cfebafcc9aa9b1dc89ea9b355b97085c00eae89283a2677e06feefc8c6d03bd59afae9a15018bf6a191f8b1a88771ba8c74ba148c4650ccd3727bb18851b023f72f977f185b1f921443130d388debe9daea6beacf8cf7885736804f55a7cb5163cdeaa1946e01749fcb51a154ca18f5ddff8e2da7ae0ef10172a32739f89fda16ffcafba545592c079f30c3fa9cb6efe75fb6c887664e83c1725c5763d758c112e367157011b3a9b6255be7fbe1020dc44c7bfdf9d8caac4b4f9a941a7dbdbeb6f2920d21a9537e3774f82914c9f85a448f5280edcfedfba772f4f12317045f6d6e589e82c9e8197b0dddd04bcbfbde67692a449478e7cef81082f8641af0604e7ae5baba2ee6504eeaf39f6a266263a931be981ded3c4bf17248f9ed2405a12b181a2c9b7caa361093af8c5c3948cf2b87088362f4f7b26d72a3d6d58bb3cead2e48c6b0b93eb152f187548a77b280f8a8535e89f0afdc3928ea86f02844b5ea3fa006233283222e997dbf0f6be1e6b6e5f3a925a5d388ad1340b825c09fe0b0f740067f8eb97e37adaaea65b8c647fcf7ffe9af0736c74572497a6cd6bef2b2eb4dd413f9fe15ae5f086f7f8a36fc30dd2be00b86f6efaa4c20c1cb0287cfbdeadca3f4141bf00d99df5e07066d3eebf6e0e09c878b7a662fe59eb609ebc808419aa410c67dc7ea11111401488702c8126a05998a07945172639dac508b76d72ec58234b35bdbe2bb87d868c2c31fee22eb45766df837594151d8f8be0a3f718961bd428fd0656ae30771b2fd5c32988f426a91ccd1ae7f039eabf31fb06fe3718d5a0c695245a80fa360e0355f4e9eb76137e916a458dd729130bb128c2f1e6fbc7e638a3614d248c6b26ed7031dd39a90737539db7d9412f00f01080e89f85e69836b1130db8f4318c682d35ec0220ad2cab7cf635878574d06f0f3794c578ebc0927bb4646f14fbed08b4475d0c462180e73a51384777256e3c152128706d2899dd213e77e6f08b40438c603cf7cc7490ad86d9260fe2fa719345b5ecfff9a6a5fccd85c5a853cea3f3d243c695607275b1d78285852330f4bcabc78d9effa971d035aa1c91abbf4f7e6e87cf520861e69b2e2099dec5f2801ebb6c87ed2fd89b2e84aa9b7a0e15e91541e8341fda0019fc17765c4a77256569529d4c25cc9cb3e12d21619d3a62cd44a93a33bf550c4336424d49ebe231403dc2737747429c6df71e9e4ff649fbde53c487543b6ae768c34acdee45b72f6f2278ca87d86d6f41d10ed92b687272d80186573919c4a05377b5eedfb33726c0a72eb2c0bfeb30b0453b642368b4e2ae16efe5468a6975bb08a5cf1155d33d42ca81b9fcf7f9cbf6df2d86b3857903d0033143b52efa36986e39a3338d7384ed02c21d8c5c09bd6b50f83c2851ed60b8bfac5ce6512ba59b5f8cb0b15d87271acf2dd6c8fe1406d26fbcea3961970b53fe2c7aac1c0d1b96e2a0838d54072aae456f76ca38d8adc3369ae3d0e3594b710452acfb7c50a2086ae218cfcb611bca6685508b9d2f7dd030c4a415951ee645ae676ad901f90d42da5dfac216272aab9778db998039b17ea736a3919996671644629ec0558af5ef7b8714990e07245bed7f85c50527e5c918c662394ceb947c88cf4d65299d9bca5e3a79bb6d172de62bd2d356a2e6af236ea9c811db5e2d8b53f9defb288803c3e2492a2e0f90cd8f898a4b99bbcc7dabd5d2935492daa1ef75f610df4e02ed727a995ad533d7221d58898f409365b2106cb67c4e0af7f72424bd7e348731a0027a6e036342072b0c0c267c7f01a47cc7f06ff44a8a76e640237af49e3a92041ab5ee4952d61606e4d0f20dfac3fb2ada0c60a4d2b992990755902c955e37292953273b0657f4578864f7ee0fdce61c6a50fdab395687adc311b7f00c208324143da8131d0d938fba5642968f6282fd912dbc4f1a90116a2f2ade6930cc6b7f6fa5501c2779872b9c343b5a96d57e09637aa7b295ccc886da889c339f8c201a23beec8e97ba672bf67f92e658e95151ad4609789856e5b413ff20a41ab490bde4de83a50ac3972d517b5e92128a913fc016d22804616cac92c24c2629186462c61cc6be928250163e572f120d38d2c56ef17b4368131d7fdcb0aa5794a5e890f17f0ee30afa70c990f159390dd8b0850cd43a4c7647ce7de83c76ac7f455b0382a3324935653721bd7fb59e77f99c7ef5adf02cac59c0169bc1f1892dbbec4c4446e2d7a0096534b7fdfbaa6e6dff16f31a77d9dbe5efa2ec1ef8e7dc2aa0f26b5d7c203a87805bd7fd674934ae396d611c594d623707eda4fa708e48823aae3c63f52349a75723e00b9847af3f1e84aa0af8581b186035c45f0999b92144fd25cb05e0362757235ee5cb36f4829ac1859a9bf130f29f3712b7e8921b9bbdc44024eab7407b47231d25fbea6ca51a20987f9b56e28ac691388300155f4fc59ac1cd2be37cf8e725dac21ec994fb80cde574f42978ec5389f1537fbeb32a8143e25bb5619d1ae1e2eccab3a3f6a303508111d87068586ffacc700d0cc4dc1ef70ce4a3edd759072b3c9a5fb6759922dd9ac86ad8d89857debc54b07983e5b94cf7e58da143ad572c14c9ea5f43566d673ee661d748efc8a8ad22077554bc65714d362fcdf4e8441d9ab17b951a7f1d48ff44ed02cb6650d0cb6349a5f8f46e2a7fa3d4c9787db72cfc078231c8dcbb5814b1a032f66c9ab2d0088bc43fe052b9b0200e20f81de72a3e59812cd057dcaf53516fc394fce8a02a5c4883d131930c612b6a145a77d72de07c4e85242bdc07b5bd46b41eb072656bcac75ef02ed5c6eed96c141f6a4316c1071a4971069b8a096120902fefee64d4e6f20368efd793843aeac35a590726d93ad22f5e423be4ce326fe57e1729915ba55ea3bac6ecb1ca7cd46c2a2ec347ad6288bcec86ba5ea0d08aec2e500ed2fc475314593e8bcbd635430afa5f672484c45fb4d264dc6bfac03a15cc9282a90d258cd2b47fae6bdcc882bdfc96572afff5e86010f150fd0cb57ac63f5180462d4373dd395201a45057c9297a07671e7b4cdf65db2d0ea0bcd142dec826ca117d326d82b98599d05f0240cb8776272cd73c9e2ea07d3ff9bd9708a0ef697b9331dfd0c3507d2cf9bf62c49ae1d8c72a97eb730dc17df8cd01ff273e8ffa67d01a33aa94ed3a562dac69e25a348d55f9ccffb6474de9f0e06aef0bd894454506d9746d63c765d9c0be14c869e8413726b0af993fc2d1f8b127ac16cedc9b4885d286ab1d143a4847466e623e81d1672f3000f30b7e31496a371410c26af7e76a4c077dbc9d45ff73f688630806582314d10aec49c70db5fdee97a76c3c0ff959621befd09cf133d8ca93454d6933b2815ca9c3962cd40cc9f342c16141cb4696b84e3470126b848c1b9d32cc1f77b72747e22e5c85a65ff7ff181716ca5b53eabb00a4390c4740bf344caf9e08e8b724779ed0477f35c4673d55bb35c554e74d445366b40d7c0446f3194928efbfb04fb2863fa2febdd7b4ad7c5498d3a0c4b65aa52d4780b9bd9b4a295078ec95472eda7763f778bec0ab3273791acd7b7b34bf6a2772c3f010ef6bc34f6bb9dc372b499ab82bf0ac26157a4bf9c8d97dceda284ae75ec4dd2aec96aa6c07553ca729f55e80305b983e09810201683c1af85928d7d42791c762539c89ade39b45572fcfba96122986f05d6a669058171e39c8e3d75047572cada7002e1ad5db6f7729aa3379382d931154cff18844767e10dce3535de4efdd370ec31066b532e64347586adabf1dcde868f79ee00520eb8b394dce6902db279728d3a01027033694d97e8a75c13e75b2a796570249291cf3a7d1768413943838ec1d10de3792cdb725ecbe97aefaf988cb310c98bb7a3bc0f149e8bcac8cde741957716972d477d7253accc42b1ffbeaf36b78a61cdb7f977f3ae2a72fb78d616e3cb065250a83a67cc51e314728b9089c19be8a0e2e497a7403918d01dc0418fe27f0c3d787aa74f2a72782e669f3eb575a1ef3e118c4c7f65efbf5dd2f085b7c4e5296fe6b0a73d115361e1c8723d96c27717a8ad068b8c6d49ea6b93f5d70904a05fda211ce34990ea433c8deac3451a4bdad4a75422ccc912dd7152a03254602dd15461dd8e20c90c9ea32eba9f2b992e86d7ac8dddb616fa1a038b0a619abd93e4336cddcc2ea244a671173f12b63a059873137dd839a564b3a2b1b148d7341e193be4dfd744829378ac11f0250e8f1bb597b4d04f40ed0dab9539397d7b70ec9ec227e54d72afc0d9d0528db2a6cdfc73775f1ebbf53cc6e029091638e6cdc68533c16a3f4ac452b47fc5aaec462671a02c9017d0c342cf2365fb28edd614846d1933fa6416ce792231ace8a818e1145db948f70f732cbbc2bc14dbddc7f279e2508975ee72ac8eccddc2f4422407cc1624238208e45ed480c2ad444b9c6b3838489fb4572d93e8f786fe12c8812ea0c491e7313236b189bd5fe64fc35658a3e98cd4ac93095cfc65eec8de8386aa10238afd927bfdb5cfc852d0a276822b4dc5541f33af725853484bb5f07f4825670f6fc6a2c40491eaebdefc38968979655da60ae7d772462103abbdaf56b7dcb806a89d1f581c296a6b60bf2fb693c07d0d5121a17872a1ca71ef4d3beec259e85cef1bb7c4224ae19218fb227f3a5028545cad2c7f72c36161a3a7803ac669bbfba05e280a4fb6280da35c087e72e2357cf16aac5e4ffbc4290a10a166c8ffcd5203e54edc025b0f399bea1ab1aba8bff14f5c852d72913e4dc1a5dd1241ce0858dbfb39e95d6ae8c6f5f5476018b05948d9abb39172f2aa98a80bfe9878453d4e022c04ac658228acef0ecc6be3aa313ac36ba62b279cc8873d68afb930a6c26e12e0fc686ee7a910febb189b144c0252be69c8ec1c07f6650627399c26cbb44e2609f77af4cebc76abb2482f33c713c66d3fc4dd0944c27eb9860f6076f56d9f79b1826ee2a2b42743b071b46f5635256998b6450196b301ef252e04d1c1a16ade850464c469138df3c044d64db90c1ac65afdfb080b4ac421f18043ebd91e9f86843ae504b39b7af0a156b8922361ed0cae0a7116ef342d8a1be8101e41033d1d6a9ace169f647463db3964c8a48ed82074fdc372d4e838eafa6df29c8882422c04257d3d29fd607cc3a7cd18498e085eb8c30072b446606def9590c1f6eaedff0f9256b6eafbcfff99a97cfd191156b8c3f74972622387f3a4a3ff0815ef7e78c3a27758cc3f68d7241a8657d666c545ad919a1ad99237bac04eece54cd7cea984a0589a2d5f0045ac4217ac2d7c65dd883228287c0b238dfff46d3ff74a9903de40f05ac13705dd19b6612fbaee4d15b72984322aa2862c4b023c3ec05b8faeffb459bad7ea9b28125dbcd126957ad5cb37a972ae982bbbd8bdec546dacef64055e102f5375bbebe28fa2a16a15caab42c85c7240343de96740501b073315ebcb5e692cbcbe6a9799279219408b620ac955dd724fa781ae0eb9a347da629afd12f7b2bf7731ecf2b6173ed836956852cda05e72cc351d8f02d178bf84369e3af2b9601bc1a4a62cfd5bf01dbf558918ea4214727d2300878f1421475ed5f23fb2505b84d71d3ea3e36c57f3303a5b27e7f4cc72cfef32974cdb7aa343621d2422cbae83b223a553a0e0726e5f225961e80a73721174e287903dbf3da61e300b68512c58c65b4dc15f96b267141b7a4150a009726d9b086a18c62bf8452d59233a8ee038a9a6f3e92a5471cc12155d827e1f9372a739f79230f75bcaf17a060dbccb9cea23d60196ecce4364c93833f7f9ce9b7271717dd583cc57ee70204d85db34f0d4902985eff7abbed9494bc2ec9b877072d7c5a99d7f119042e99b8aff8ea5ef6040df73aafa35ffc11754fb70cf0eab72f155e8a4998ce4c0c78d77bb326d4a4c5a21ca74f322e8ccd98f8a21f2bc0b533fa5189eb9c69b0b42d62ae100ed87de07f3f4cc4b6ce2dc07768d043dc8ab6fc03a62e212508493b7d115eb49bbf7361c932b8d5dd638ac3fb780075f5b876b5017f0d5f6bac22ffd81e5cfa7faab1bab10f45a9fd35262f70c2c8f4684f8235d72e8b38339afa09966880eab1d0b5e18960cf8adc19f1b907f9bd012620962afe434bf8bff507b300eda53d2de96b266354ef164940a4d7b7244ca9c1a02624af37439fd4b2123532bf6e22ad937f507c35c2ab4c0d38bc1ef9e56cd8abb189bb7f445ed31efcbbab81377af9018eea65a8a6a2475adbddb31bacf9bd168723958f7418b47e8664cbcfe1eadcf1e1ef9556691642e405d1dfd1d286672d072fafd60585cf54c69ed0511904693219ac576c863e16d2bf22289be023d211972aff0f4a519bce9edfd93fc20d534190533037d96894c6aa40cf9a2b6e6338217a6f56e70f9a2d17f51c5a9090a29f16edcfee3c956f7fc1e15e879e403989772371d02fd2998ee67b4d35b1b1e57012342e14011ce9115dd026f6dd5596c1c72cd9d499c9e2a2e30eda8e9bb3408ffcdf88ad2ae4681441733859ee0700c1e721b21a0a34b1305eaa294ef2b988ebb995dd4d3694d577a4ba481b2e9a741647250d74a13951ab0e8cc8bef6eb8e3db9704978eafb810e95ce9f2e33d55e5781bd72c076c40b1c4c453fc3fa0a4bdecd64681fd28a82004d284b0f5f71625eb72611768c51700560e29b2d783bb8fef46e35e672f3e3fd842d064d5b68faffb72a4f968ade96e51267900dfb0d466eb484b6b2210be18c567ad0c2b521be64061c9b393f3bface70318bb4df73f3d71406071cb7bbdfdade59071cb1a74157969d568e24a5412b8c90d5bf00dd2cffa9b6c553912a23418295ca4d6ac35cc7f4a1e19eb615d7f201750deaa5e191f708993676db5b025b95b9f232ca7f4682b72a9ffa2ccf5a72d627c6e67fbeeb0cd3e7dbf484b6f62903932c03e0b04c1b5728eda85320777f43c635d2260e8a476448215d0fd7608cc4e75bd51e35e555f72003e14ca0aaa4160149c53845889c55e47005e4a1f7a37e4845a643939bfab72f351a369f858e950d21bca83bc87f83625679367d8d1538bc840104db0285163b6f8ceed1c3ecdf2aa689f307d4e564d97fe5add28b28e6bd83425814d20f072fe1a410d07d4cf4ed336a9f3198ee4fd367725a58813248a101ee02cc0b5cb72de0e658de2e8677e9403d6d27fde8503c480ac5a6b5b0af08f3325762c0a46721ae26da328f44ae06b61b5e92c527e560f8e85313f71dc0b13a4bef263807044b6addaedfd782809af36093f06604f63d55c348bcc1f119f1fcedfc9e785b1298901800d05a9c635f52093543d964e819eeaad0213a13fa1ccfe4b55ad97a472ca3e73b45c662657775a1cab300d7bb7faedddaf59a0991341f57c0ab6e224725dad59fe9d696f26650138397462e5a9b3759ca0b11aa377e2cf32bfe846e4728631d8a9168faf394e6e7d4b41245887268d61f6346814aced04418a856abd72d9e65ec1b81125759dbbf9ee3a0a5182105d0baa298aaa7768901d7ff418ce720bb889272adbff91df43b10da0a66db86b38176c87c9c5b58990a75b6b6aa572f8544e08ae745931f47e600de7b4af7033bd269263bc5b284a14de76baef963a2ac9fd88c2c7f94bf1d0449eea989e938722097ec525eb9e65d0a07ac5f52472095e898fd7c4491c07a280c93142a364a21682110ceb6f5fda393fba856d2b72197cee41732e74e44968cacf657c9bcaf0367ec5671eed003aecb982db55b072804bdb45ff9e064cb03ecb8dae5c7d7dd78495719ef8a62ba4a25b3cec93b672519a6fa55b4c12fafa458134799df00e0aacfefbc636d1f27280d093eb47bb72ec8a900132b3b042a3c70addd507f04c4907de83a90d04947a1c7366a6b671472b810a368005559bbb087651930b30cd8b019012c0c7a567031760a33551f37271edca8865b8c837bfb0e66763264e6cf0906ae2be3543067349b3b387cf9c1b4037cbd0d68743dd533c5095d9adf8e2426ba8cf4c2ef721113c55c810b42c725fd43b4b4a3017ce5ef2aaf270238e4299aecb6646a37d9943b6f535f5cdb972d969b5cfe30c9bf24c044f85056aa8e838efe192acb75919faee595c10a91172db144ebf5aee668945436aaf41beb42755a41ea3c0f0ef34591965a5affb2c720a475e13c7b18e25258c1396dd3f6fcffdb87e7d5e889637895bf55c466ec272c2dcc702ab84bed4ee9fe9ed86fd0621a04dc351f96aba5800e1f05385c59c72950830aa3d9b6765d83a850efaeacf30419f2f5d64559f438214d7672b845572da64cebe86dcabcfbe242ad627b209fa92f71df7c27e2f4d8237fd53fdbffa7287945bba58cb45a9026bf7aa58fdecf109b8f3371838280f0b7cab8f4e3a1a72f68e27bd8e393367e64fea41c2100c47e9a8e0053ad2b514fe2dae6e98daba1c724426c8c4015c2b6b8e3f0c960eb2eb43e38fcafc3b427984e5ffd6fa40bb7258228aa283b01902bc940f05584760859dc56b2c4aa787bffca253e2db65cd23a1fa1cc81317fb214dac14d4c792426e38b8f2e5f069aec6ef560dbbfc767f672b7987b7c00899f3f959463d731eb07df324461a8a9cce852962413719df3f475f59b46e000e45f2073d72d0bbaa1cf47222148077a8b6ee60df98e312bb5d0a387bf9fdee87d7c287c4b3c5384a901bb116c1bc6006889f9bffb6e336284172412b70593480bc0cc4cc2bb4a0b65a432c86dd49441a1741bb4e5f6fab96bd477fe2610afb94c23fa91bbaa450bd94264cbb94ae8ff3fc02333bf447bf4d1272a72403b0b6c9cb362b4bf19f2f649800d1f4d0869e0eb9dd0cb43c213425e86420109438eee97e585bfff3198e60dc9a50a662f1d482192fb0572ea1ecd4a400bc4cf2b809ac438548dfe49dea77f412624c89926a1c3a950f84d8271c1e7c72b1107d3a90186f294ace420ab437261acae228d284d940c60b1056896d6bb472cd2e4773fa4c8c4a5b02b2d5786390fac33ed1879126dce7a559e0623b502c7272c1064ca58b039464b9e1cfc27408f6a1a787cea31238099d9821aa7e9c7f72c03fa122307f50b81232cad0f0c04613d1f65b6ee7ac8b049f35cf292efddb72bb2dd003a073747a815b6336a2d91bc58c545a0ae1d7cee88a1efed9ad6424725ff3cddc583600f06089a0856ef0805f8e0767ecb6808606017476ad334258726785b68972c24a2ad21a977dc3083ed4799b399ac510743a2a739233150ef24cff82f967fb8b9f5f5e0dee100cd2a2bdfa128ed87bb54993a51d620b2d2a8516913165169de7eba8ed239cd86f00bab37f7a0560050dae307298186a4be05272fa380228293a2ad74388c5e5e64250ca1796bb3927110508302085a92fe6677235bfc3099b08935b6171c87804b94dc6228a8236eb6eab43a0eaa5349c8d727260d2300301ef70a81aa71b0106a7e70592638ce8815e06bb08fc2e2a51c27372577cb21ea8d75a5dc465f1ec2aef256d86efd7631e520caaae78e35152f94f72a5bf24e37cb276d68b005ddb41906b42dc33230958fb91efdcc5db6dce438472c1899eb6eba68fa034e40c92b5c30b139d4ac2500209dbe4bd2e69002b7005020b0de45c7e069ddbf3ed47cfd00060ecddd918f1474de770a08bce99c6b64f5e1026c6619aa318cafa6e9ffa2e7ae351afd4c741059a43c36186171e4b9ba2729ffa0ff4eebcec3f1694a296be0b0ffaa64ec0bda4368d65207d09780dcc4109d89f6efad0b0931831a3f7708d30920eb3dfcc9d890b89771f78fc02cad7b23498e00564a7abed1e0a090163db95294e43de99abde2b64b2dd39e194f3f77c503fc282bca3c14c37ab5178ab8edc1e35acab66ea035bb54750817f901b36a7354e4b75b7149ca3e84c6f5538958547d72c15d2ea6412cf4a5b1f6a4d84029872ad609ec14b7bc0a7b42d20cc6d1fab9ed4a871eec3281f511271d6a74f0866147851abcff132dddf12af9c6923be5953555677c7905c9efebd339d78299b950b75d297efb0c876fe1a337733380ba0876631aa75ecc261503c8082b7346e26097d5954a9a5eab776c7316e6a194df5b11604132d17e84b1e7077486eb5acbf0e4ddb89a150686695fdbd30cd14464112e5a2ec838b793275132412ca28805b722eefa148f127d3c6e0258e673ff0dd967f171ceeb5577af36485bf0eb5ae81722bd1cfe097be721ee713689fc6c1f25b2add317fff038c5b6999535e4db82b72b2b75d8e709f17fb0f8f4675303efb68a6eb1bf7befbd1d6e01737ae38c83872297ff2998fa40db6dc4ef0ca038e3a414d1c99c41b13c18612890b9c5cf37758b5ab37a17e6d1d0e0ebc762aae2ad656c74a84391df769daa62e812e5817ff729a88fc116aae42de5ecbfd7cf62b5bec676da3bad93cf8a752d47224d0366472163cb033b6c168b800b70041a9d5475fec5ae2a733e6bf14b18e5f1a541ad072297c7a6134985df3fadcaa4c8e28f8e4f15c369c6ea6640757cf1fb0329af214f705b4f63bd54074da3b7c343e1a0eaa7ee8e649ecaf34711b60e9eba78ad96f1fc77ce22d3810e61bf47cd3da8c519fbbce9e104744d72e5d12b6e5a0b01f7269492e1cccc42dbd23557f9e7e32a62a45c28ab6187d073b804b1c8d183c56341e838db9afc6bca3fce1ecc97a774cee2886d201cf0793e3cfdef912819cc672077566648ef2ad25d130f9950b0d1917c6ef1be26e326663ca98e857fa45071f4f3e064665f19439b898c5e99fb12fbe82e90c347aa9f8cf1e4e8ada6f9826721aa60772e89da5eacd7dd3de0b3a3f1eecaa8a5cc70086f712f557a2928ced72923060bb2fc65acd48c40f41203e56809771ae89423cecb06bf56963a446806d78fc600febb172d9c877d1ec69303ccc4bbd31d37fc674e158c4c9d5c3ddeb35ce985d0964804193fe505fe05f7de8dc0e09dc1b0da6d2d97c0afa24ddc7e53d3c1c1acb49ef16a11dcea76dc52149b73c91d74691d53f184bb6070f56e50272c99c9e205551a28cb4a31b2be2fc25d29419333db1823d201ccc1d5642333a634efd712b697c23abc6a3c436987bce4ff8e9c13ffbc389ba0142b1e9fc5de6575cfdb46b7870307bb657b87f7f85156fce3484dec55d9f9d3a1855103b0f363b75b51919b25d1b5f85415c4251c9c8c0beed3c5093d697b57fb25bf2ff9fc8727dc897b4fe458268bbd99a27c5cd024f53e3dbf43a37df7cc5b9a94cf751fd727e3b5cde83780a06f93d480288e16ba918cd0c67374116e72f6cfc5c75311f721a994660e9eebe83394d01d78036446f7ed85004487de07849d1816e82a11972cd196ea7fe8e43656fd79cbbd383faab88632986e2c3eb825f98224caa1cf718ace1ee298a70ad66710616d8979bd22f968e6d945596492ef2a3688f7e703d422bf67d18c6afad1206495bd2b439b7739174c640a87d8f3d2f3ced7a4fe66d5987994b2dd5ead61c5d48a92f5975b64e94295240cf7d56fbc9a5c2f7b274637249739d4e92deb84d462db64577994ca994d584daef9dbbf4bd7148407bfffd0579dcb0539ef5be35ae380de6e487d04c0f77047f60aace73026533b2ef9c577235a55f4a15c6ed7dc6a1f89ebc35d240e1bb5cca25082bdf0119ec7b008ab61764d3a127e0a7f87799cbd632ba0a85abe13f8e58f5ad4e8a7284058072de9d1f6a3dd1c519a72cb686c91df4d5da775cd7c6aa20eeebbdb9fed818bed04b9272bc07363563e6841a72d257936c5184fb67285249e3761f195e11b28d13685f72fd89e10259b673ab206649fde9d69970513314c6fb48027b108563003b316872f62584b5bf3a7ab5da9fec1692fe6001d04cad414e05b2e4168c8807b119474c82b90f91fa5421b46de7983ee23f19efac9e9494a0d0331b4bafc9559fbb2c102927f394398039313940aefe38e0e83baae80008ac33dc1e7d843fb9d6f7c21f81fb84daad6db8fd4434e524ee12c19a21e28f2de97c983af0ef9a8c38b458720b170e50a1d3b21468fda5c0b9a21351fac8faddd31c221a0caabbf93aedef72bb3abfb1db62f995042ede8d5596348016224991e7c762d7744a4fdd8649712e8ffcd46a5ffbfbe99e99b4f99764d8605261b4a9491170f68642c746d1fc5972e6c2c60108e5d1ace602c9162110cd143ed13a83b098a269d576a04daf6205006eac0af8e29335ebbbf1df71803c9cefd34e852dfd6bfbe391d6e309d393107120a172fad155250c8b94e17d47a999f170d577305fed609301fa14e7f897e072a5c2c629d975f1a0371cce3c35e962e315fef143f7fa7085bdf600a83fe990721d40379d6abd2e0236e1bc13b669c87dca6f9f15d3fd9fe7bcaf9ebe5c6328723eb6d6202733cc2e5152a42f3dda83d22b63578cf8b623ec83134fd220775472130d8f903f295ac61bf3b0422ede4313023fb59fa47b97e015bbeb26bbd1141b81ea4f63410693e6d50bf37c4a832afe2217de3179ed2c720da5cac8e0d4001b28d1377dacd6b5aa1a0aee28aa99a61781b81f846cb6620c4a0bee77a5f7af72d3033b954e2b86129386db44df52239e16b5554ef852d54c1dcaf8ebf8c066728f98a81494d9a7e32b47488dc25ed5ad62a172bfa8d8a0815e1b6efeb16ec7728b7a7a4e0ef471ceccc710539556a1bf1d9848146a03a9b19b14fa1a79b4557260ca675e0e31b398d5bdfb0dacf53717f3149954353a431b8db91003b9ef99721fad5fca8f7dd2253ee327fbf0885d296b33f6efa87a8b4439a6690fd4c64f0282c4ba3869ab4ca38a2cbb22f87c447953cd97da6bf1783f273029f2a37d49728ce64a353a6ec7c6154c085683dd9d185e585ce3ed3f445385ca0170024bff5208259bb52ebacf532374f62f07f36d44ad735a4d45aab8e4750f39a1fbb86c671065e902356243a8753ca18336e8ac2af3c1fd24bfd0d5315de3e05c46edcd72cd31ff4884781829127db91431b354e38da6d5e82da413e4661edb2a0b841d547cab460d4ca2ba77588c8761e543fca2fcf14cb6027f64406abc336758da4917415710d461d428fd25ca23d3e8640dc1a6988879cac777fe9a0b0be8c82f3b447051bc83de1cec82b488a03911b1eb133a5bb3439ea2eb5cdc136f2b41fa7372f94e9683a08b374d21333485457fc9276d77de63433de2ee7c08e3e6427e24720c385fda63f6eca1c72c8e1524f6d6649b3f1035c5b4f2476011bbcf5f22a142fbb5f9010753f7e7b9f21309e46d2ec9387c793273930d8db715f82650e48c7294ae95e37a7b70c55edd5182a0150d716cecec1f071af0d65318dcee1eaeee72de31500194fccd89c9184085fae8cf3c33a6e1a732b9127e824912f64ca8ba2963fe1cf57618bdb8471d89146431321c49fed23aed344fb14a42f2fc6923313d0dfc6e2b0c602dcb637c5806f2bead479146c5f76188c48b3794d3a16d08df3791be3da68395512f1896e2b5c6c141eaca1b7a3c9dc3761e4acdef6633b95872b02e234380c452d099298fe40ae6ffb58805b9887d598f65774297b8fd017a333295cea32109f5e320131172b4aac96cea0107c3ef5ae98da1e839e7f663107141163d767f897e09a659adabe06aa4a797e8a4305af800180acb7ae972c3c8547f3bb14d1007864e0efab35b552395030f05086ff3204de47b4a54e9c26dc5006b718aab74430922bf09537d3d227d83537090d2756b32ac04a832b10660743c2421fe2aa66e0e61a9177ccbbbff93129e7bc086dda520b682f86eae4336805f32e0aa274d4ed5e8ca4d301ada8c6eb598927b57886944365a7a8443f7afd06c0c4eb74c0f9851297214545a0d2d1f206b84fa69144bf08f436ad4249978295802fa5503b8ed679ab7f07d2f419584f5619fb2219cc9075a5ebaca6acebfd172fc22866dbfbb41f9f935fef1f8381d917cd058019469d3960fa2469415843c72d1f7d9289fb241f056b3d736e22d2429182ee74ae5165d03d13e5d5557972b72ff4403b4799acc5196b9a16ca7e87beae5a437d788ce9b68c053d18eaff495315334b83ac5213d4b579a9970f5c223926fa585cf598ce2d7aed136e821536c723253dcf11dc38b1742cb150958929f5397a9a792b36acb5af8d3f6b84066137230890c89e714fa7a607fb29d4c543b6e34d80b892c62ccc53afdd545ff3db0724ecb443725a81237088fede76364bfbba27bbc4e8f70597ca9075b8e2afcdf72fa3470eaa4cc2d28cacd3b63940aacc9b26d2e38e27e9b95b12f9e80cf03807226581c9ad20848edac9e3a9f0dc89c47cb559bb447ba55cd3f19e6b233a1db195ef372800d5a016a6092994aab2a95b064bf3e16c2c22baa3b6e87dabbfd4e68a8eec5ae9935d4f49be50152c7d896fe425f34c9abbd975407eae51008e40e72d751013873f4b1aaeb6bc9a281f5d0b12a54feb8e25602881d2d9697b597f272f6ce81cba849296ae96dccc12e7d22ad86cfc13aa4c45f11d76b490555417a729864ce414eee11671d8bc7ed97fe4729524ca48e891a696cd625de82c99eb972d8f1446be6253bbd79aeb237665d0ec52f07967ade295d93001fcabe075d8571c9a34642e5d70f469750486b2a0cb51f469a1446d119c75a9a4425dc39122072bdf33a9bc2bc810f272e583cab982e9e53d4806dfff5f606d79febcab5ca5f37a7c938e933b68e4648e922d04d38c8b60ea166199e78cf190c981ef9d5ab63727514a036f97c5460c3e937588bcdb594171002beaa80351685d6f54325cb325d8582007c1b6d8c0c78c5c84aa974cd9aac41b0660a06eff406b5ac7e41a0e925b19babffcd714525bea686c48c39a0910386bce7a085d2e841f2d738244ea272731bb47156867fc58940e5b43334dcfa32a050e8628996040c60f811c3b5f672c8f507ce8c73860b73b796abcd7222ddfdf35846e00ea4248f9032fe3650f064a1cd2764d452731f4186547e3ef353f08c48331222c6cfbacb3e3864f824827287887398a8dff28fc03a3379a212dbc571c81bb5237fa6425c49bbac1235e034e94bc87ae9e669ea33f1ce35274d4cd031bcfda5814123fc7d71807d25b3412eff4ef9a0942e08de8f748d908ee7f203cc1eb1a18d19ec71f00f5c620f6f57728a8586fe6f5000ca98d57caf4c688be74df40c56880f99eee620e52ec911d5729e54f43b27f9226a9cbf1bb1aa13d3f23bac22d10327da2d679b687eb0f36640e329306c7530296219b4e757cc9184dbf6363e256a3b77d25475bbde2ccb1b72bbfa12dd195a5d2657f76f68cec610f34133fcda4491581aadf7d86e42f00b4029a1739fbb09a885f5ebed7762b3c89cca2c76929bac86b94d2b8f6aca2c4f1d3ddb058abb0eb2a1359db0563e1c997dfa656ba8f3ded44705e96b1bbfc69572809195af7b3a1953c1ec72122287dd971da4cc62189661e26c4cd68e26179e2af09a10486a689c1a502ea34e1f2cd4878931cc5a3ffcb49144afdbef2154db6f7d9d7e8c04eb48109f8d50b30fc75d6663d08fd051f25af2133cfaad7ec75e72bb15e25cf6ffe81b6210c6ac0a9735f8dcdd8a0d1a2a04c9841b41de3189e372db948d8358d21c7f6c26041feed4ce07859bc7d66d0209ec7a37e42b1e06df7269aa740088349014918e7c489f8b563b639f9397d8197fc2cad5ec826cd50d6cb52c80bff4fcc585f902f976c2d5beba1f8f29f59072faf7c31f1f9db51bc1724eaaa57232eadf892ab7635a94259c34fdca70bc9254daa23e643b9802ccea07c35704bcb165332acc221c019ff67cbf0354af84d1d8a091d297f6f345c65372ee7efd8694443109f5a32a6c66cca445eec32d9157e28a964a3b90cbd989aa0ddbea515f33509a205a3f7d08f6bbd84c4754fd5464792e6d61841d3ba5341572114dd65617a62e809ca8495bfd763dbd39415e9bc2c15ed6f101efa129db48723b85775065edfab302ed52b3cb8f48dbe1fba7fe665884a8c589393873a6670236723043c843de784908a64f6e0aa548f64927c43b6b207e2a5f69c895204f7215946c8d1a30806d00df4fc8f5b79b06d76cd80aa3b71c72fc15456629769b728399fb0ef78127e3e437e0f3d49f213be6cec81ade64f5e9a02696680ec92272c69c9e664de2202c2c5d5ec7c84c66c2c0ec19091dba95d899e7bc164da46972369ebc1dbc412ba8b73c7b15203ed03449465b3116840fd3733e0cf03c29f210c331251cf1464b04ac34b6df209c75e6c081c82b99b28fb6019e3d1da34454724fbc862ce578b400044a345a71c0d184eaac1756c71cf7b4f3d4deb7a02e4c72ffbfdf1171b988423c6ac65c89605e2c5c974da6588fe482d322712ccaddc472e983938aeaddfbcb96b6aec5bec05fe16b47c3a96bfc32f108656964efbf9c6131accadb37a86599a5d868935c60702cf5fae04ab3afdef6cc7f95d532128872999fd92b5dcf6ffdbf95d261cd40ebbc798faac82e785078d2c3de470b2e7f72fcd69f134238fc4e1c79f2210ffc2f532bddd514041b62f05f728060055b9938664f13d673a77a5928262dabd9b8ed4a99cdad4bb44bb917c494a59967558f720b51f6c303434e151757dabbf6f147ab4bee9e0ff75f4a2f1b7eddb4dfe225727115cd1b91dfe2b388080bfec769d95f65723a469ae778a8973b50d3ea08572dfb1e5ec3dd9224679accfc6ea27cf299b513b4134cb5bd5e8d85ca6663ec0872e6d4942c7a3c98193119679c2d29197aecf4bbda81c03a474159c30c815713635f2ba050cebdc71487946aaa5a2d7f3f003a19d2bc933034af29374934b1fe721c621a6f764904865fcf6f76b6491a9ea5b41d477680ce72da3d1f1d27685d720d8a2eb1919fb8382f44283814c270f2adb65ca2c6ab3c1fde46630ed8f37f72a6c6b10baa41849ccd65477cc6b6f87ef42dbc8206baefcba28b018915608372fc9b5915ff3582bc925fe83392582b05edf3580e1463ffcb178d484576a4d3721600672a49828f90f8a546227ca91b5758f76e8b5a88c09bb8738bfcc8ca78286eef800ec11a64c34d15738063df3809e54663682e6ac8cf8abe7beca0751848764856471f1ba79f12097ca198cf911a48dcea0e3c5d7fce315ef300c522ba72712e7c8b0de6b075969ed33f418e3a8b3ec24e66ff58e9b8eb2b37d6d9ba0872a6362e1517485fe4e43cdbb3727d45de9881179086b4f64facdabe4982bbcd72a6f17c216abfa46b11f2e8c5dded55317cb2fa77d481084a0088edd3ee7219080cbf5899b8f235ca9017c460ff7cf932318ecf889f56252ffb2de841259dd472957f24500745ae712c17e4e86984f3c32b63bfc694fa8dc604591db90aa53070b30a4b82462d94754e7566ca6f819f890b0a31520040b5fd458a670fef16a47252ccfd27d4734e0a66a09f47ca91ec041bbb9b1bd58871e80987bfaf5a50865215fd4fc2dcb7e591e55e7c5ab4caea405bf741c236480baca7e13b661eed9a2db6cd4d7059f6bd91307b0f58370047d91b883d762dcc37efe66bb39fa7e26772c8d52687eb66c534f1e64795ba3eea3ff3cb2a632051b5473924862d2f48da0c40103157e58848f748a85690abbda45fc9a959682c4a15a269868e0e3be52072068ae93245e4430d98caeb61ae6ca304ea6db5a5d9fcf08e1dbd4e444ece1e57d8ecd791cff118bdc44ca677dc4bfb1003723a9586673eb0324f5868a3effb0a20232f691355bdb15500e2e06745fe3c7550c391864ab575ccfc5fff8ebc7572dc851da4a81bbb1db765c8d21af88f71471cd2dfb3d8e33f74e897bacc0b4e72ed45934968bd66a379a233893845ce4686b9b5c3fb5c74cc1bcb40397c85597269d768d47cad91a1fb5d2fc7549c29776d2eb0bbffea6d068be5fb06f7b6992203f4d128e3a0cb8d5e10e0dc777693460000fcee568144a8483b7b00f9328752629949bd7fac9d2b58dc17fb285cf7b649da777e75f7c1b9b779f07f045114479114f30f9badabb75697c6a1f9abad5b4b57e73c2ba3619c7ee5a4c041be8272bbfbc58d694c85a5951123ff8f8017025ee0fe94a4f7c5581790d55491d83872ec6f38389ffde35bf009ab0da7b63451d8303d7c5cce0cc2250313ccaae6be544e56782f9d04a9438245d6f4e87a5c1916ef186dd3a50d955ae2fbeef474f67292b79292c98dd0db3b6b09ac74fe359c348949848486a4d95c251e644207d441e97b4ce89018dffcf6b16af33573a3241ab011cacf9f59975a47f3f61d2e955fa2f492f4a0aab2b8ac224d2cc7ba5f9bd500f328be2211e05a68371b52f54a5966d96df8dcbc6ef1f27eecbb258b52c089c6f324b38442149612da9115ece972c14cbd529604c27b6378b53ece4576dd8018a840c300af2bfaea0e9ad60b3672f6adfd850c979feb6ee27b7df149ca6d2df48429caafbc83f47c10ae50a97372b92ccf6dcb1554e1fbf02264f9c9c3d55fce698354aedc27f32a00fec21d1272df2911581462b41570ebc901818567a652f1c1631bfcd31090ec2832f146516592a1bfa474a9e2000d42794176b942ab00c77d94f83ebf2b27766cef43048b62366a2f58dfa3cd9d16e95acb96fe01478ead72d5f4467326311b1b5ea0bbb97239820f0e32f27443198fc1192f0bf43510fa8a09869dda15fd40005c95ce85231677043af50d6b56d35e0b7d7b0d1a1f2a7623d6b6b93b7692958bcc5472a9043102cb220cc6ddb6801df00c352f683dd775458902cdac3e1b5af87663a3573059e843809e42f91eadbcd6f8af52d5484c12f6f6edb6dfe5bc7cddad1c9ce272077d09d395a2a36118f7bb0b4485d58e9cc58a260052fc2aa1e83e4fd4a76835c9f34e6f486832da48c0727967a022eff6bc19d5f2d0d144753b2bc1de2e6c008c77bd2051790de5ca21f8da429f2e769ac216c5c2719d3cfcead5f896872a61490640a0f5c7acf0639e0fc6ced9618927387c933b7a2806bf223935c8acee72e3e9b04b65c27597bf2dee7fb4b258f382d91158992716f07da57199f0c5a77201e431f93f46b75d48fb7f9da6266bb250195d3ce77754cb9009e7ce5d0b95722a24ce95b3b4358a35f6fe8090553e20cab167724a74d8d5b81eec8906b93f17cd76196dfa7bcc457b357a46437734a29fb2880e2180ba2a5da3fc3340f7d572a089fefe6f392998b665763fd7024843e956a22cc20453f817f4a7ecfa5ada7206acc13fc3156e6cdb6ff79d8dcc9f985baf57d2bc424b60f761e90e775c3772dde66381297833999884281be472e20391652aee4c4b6975a3036a6b2865ca724dcf6a83304fbbaab0c90966bf3bd72e12af06271bb38a86174169a76f844d7264c7d90ff7d8054703becf4a9d1ab8b1cf875512e3448aae7a8ab141796cb272538e92ed063d14cf7781fd181746697c0d75f0087d23caf450a709f2566fc372bda013fe446d7b1ca47ef54e047e1e9d5a21a3228a095019576abc734bea7772860b3bb146981814f25eed9151abf1a8a3db949754cf2f254190a8cc2ec7ee1c1deb7d8fd7dc7cf43578616c38ee7a09a002fe36c5f8476106a1e61a10d52c72c631dac8149a1de4cf1b78680094cf3466fcf5404ab42156a90527536e20bc7274c41bf4dd7d84e4d665bd58d89baa5e3cd98fafeeca3bd49b53a83902572172983cc7cd73ca7d36f486b2cc2ced14e1be7b4fad60b3dd6842d52374fdf309721628b22014dd8b0801c2686f3bc08b1081547977b9cc1f8192dc6cad5647d872dd982d8073bb9d2c1b03c293c21812f2f981f526a9a3cd749a4d99e3280d2372324b44f00350cf6480930a2ca43b1e6c75aca0fcc0de657c9d14a61b8145a4729a6eb16576b91355be3e5c7f6cc860d2dd729dc336d8a0b89ac28cbe74fe6a72e5b8af88f48f43e38bdc4fb8da84b0cc8f697b08191806f194b348f401384d061c9f59dcb4562d1c06284078fa3e6b4742b5b7410c8dc4d48697d1f2ed5e3a6fbc6e9780a8370093023acf2e72b9bf8ac9cf8935f61f0ecdd1366ccbed3da17218d43eaf7fe2480ced029cd1ea5be14b71fcdaf6fa251e0519cf1f6ae47faf72f9a1ec0440790dbd1900ef92bdd1739efed9b72a365016ff43a8466c5d1cf6427f679879fe3de6183d683482c1138d7e0a70cabb1babb75647d1db99a9f7d472f048c06fd5978070f4fd61738fdadf3ddcddc4beb8fa58f1da850d7ea5fe627224b830041b8a742dd0a0f55b54e35ff993b41435e82bc83bc83a7313b9b15c40430b703a194be5ddf21bc951b9a1422cd81637106978c7779d7031c8da514c72d7a35d4da2f64e5fb19cdec91c63dacb0611a9207878aaaa8cdf0d4da79a5b72e12b3dbec65489387b2fd250efcae9b0250654cf6df6c2fab67bcf6ee73601723247bf0420c438c92e69a9fa70823f05481ef54e4e6f657101215af4a4e3c2725b1855894b8117b4c4756246e9e03629a249af8b29a6951fa16349964b4e99726c654bfa1a9a9daac20f4277d1ad2b995f80b92f904e4d1ea3467ababa21163233c16752dc879a9a736f0cd058aa7594ae4115351ee1088c9d859c4497ff8e728b787dfe0f4dad62dce7b30d788f4bd693530d6298f204915f45f6a6a22e6c197a8319afe245a6898f890141147f97da50d273fedbdb45604938d0a5ac34ef30f0bea22bc7eb4fc17a68ad9186dccad610585db71bd91b5df927757f600a3d367ffe1ac75d4b5737ff901cc55da352279d4e815fa065069e276132b2b3f146037e539ded638c57bcf5ca762e22a91f172d78fbcd7e89bee2002a6e794f49b3049715940ef60ebb148b6fd35375790e62289e7cfce03a30f775a845176e023d72a640f287aba05284571d03d175d8c75b9fe468a049578ae60cc1f19c5e083172ef264635891994e287f133b2ae10d5676f033f94ba5b2bd1a47f94ba84cc611929b6c7d19c754a1b51fdef7791f19647b74351c531b66e326d29a4d1008aa74222b8f5bc156a6e388a4657999dff35b78f14f2ecf36b8811a028d53353fdaf72c73715f19321a3a5cdfc60422aced047f900d39f5b4e2a6d59e58692761d743a1a704110986c94fbd373f0f6c5ce27c07b12fa5eda680852ce84fdad98b1310dadbe16b2010ed40d526b34ac9364b5890c09a111aeed5df236451041f16be172f28af7d89576987ebb42d9b1d37bfd2b26578bb9ffabf9f50b8ad189710ca5726ab36a99579c8ff38e07fa259ed9750fb11fca7e12c08c038ae12eef195e1072a007649f4a1f54ffacd293d718e70fe67c7527628361e7126e7e68fad02ff60cfb7eb48fd619ae6d6e9e73d320157df44c199efeffa09ddc837c42cb86388772d71cc300fae064d87c600ca4e68a1a5701aa64f986d1bf4fdfbb3c6697fe8872f52335e1f9fc2e22743f6b49906f21e343c5e84e3ba1d70d4d96d5a0c748b97241df54313d7daf789c6b417f05d8980ebee0bffb0f4c4c6b99f6fb5a5fbb2d7269f1753aac0fd510277ff8d5c7a6c0c654073c0cc9643a1abb7080b3424f91724432d9c68c19ca6550e25f8f44b3129e714b33be9ce165993c92ac4ec8cc44720e2a2fce5e23b882347820da40394d6c0c7f8e315b30525d672c0c56b6d75072b79be230f4c85744c98db7da990b0c51a2db2b99bc4b33259dce7e40758e0350f5be1925f0473aa1ea59ce13ea911d18ae6bacdc72e07c4226f4790126e65a723fb6bcf567680049e4ab1123d1f881f90f64d8ed7c7caf49ae4e2a6705998d72bd6d616353663d8a5b89929fa9908b6165b1e3e413f3622b3ebbe15c7b28893deb68f50446976beefa43791e308243e85723ce852a323143386f70782055fd7218ffdc11b39ba289f1a25f4cecd7682bb8a4c5a814ae9b39a000bec1942da9399fbe20e3a962016753d43700e88688c2bb7df4485edf240c20f945819d8aba72f1005eaff668ce90b50e6a4fee177d3ed99f56f58d46bae33b4319a73188c01037e544124b99ec7435edf7efab68a1c6bdbb6f9b82b6b4d4ef8d1d0d309b51281c93a48a1c673d619f00845dd19fe89fd5a793ba75a922c679ddea0aed466572c72dca273d52ce0c7410c4f285f412f0e8abbbb40644293575d76f8bdf3aa17211fbf3e4713a9385cfd33c3103f19b1a115d2004e9fea4280bd0852d9c3bae471ff0079e7903ef4fb6a2c24caadcc0d9235bcc9e29524377a3c06ecf8d93fb72a14a9070fec4dd2e0936915ff5814a31137d4a34c9babdc50065bc3156d49e72589b7c6fcc9e821602f16f6b7e155b595447b43e501a33ec916aac7f490a6772192112bcaba096d10722918cf8193cf2395861c63d185cbe2926cc3fdce25d6368a5b00d493884bdf7cc04cd49c1770577c11e5d952af767f91730608bd6c872701e6652acb714b5b4ffc7e0caf9a63e324746402b1afc898e7a45ddbd02f77293743d34d3ac0c70d2b05ef1b091d77a6f5c2ca21b5c5198c1abad4c57842e723ca6759ed8376ccacc9226f645aeda990c447312d7827aac4ffed4890cddb55a9dd1661f5a389cea68ac76c6d39d8057f4aa10ddac715d7759843f4c1853807232e9512fc787f184770d2544a6cffd8c189499c9f03b79ea3fd51988a0aa4d72c2643db7a93348acf9cc288805b356dafdbe487094b87066b593ee184d176f72396169a08b6b8af53a50d684a548e726028025aa0e7629707b9c54d78fc3cc725dd5fbdc05b72daa2d620a8061ad4025128622021be4cf9ee35d2282ec58e972197a985dd4d4453c431866a9f98addd2c9f36789298f65e8f59e5801a190ce5e2362d55c33d6c85c8ada5563e82579ab573622162bdedb5cdf1a6c31f9290c6283e20820f88fae4f0d48a9838e8f7da1864ce8c3915576a2011a819339d37d14bde11d0ae98a4bcd8a88790bc6c98135e2e874ed65d6fd2aac7b49052cc06372ee364ba77000215c699d2c56ac1a89f66bbbf81f44f1d06384b1a2f317955c724f6699706246960272f8bf111b1e6d18fcce12e972e07dda6e6dc2302c48e621a1cc4fa22b8ddb9d0cf3d11b43afb400f780591317c97ef17d88733884f9d2723673a8102f1313f2bb48a50ade20e4db7c91b2ecdacf48ca9a8da75e7f20141799bfc059334ee2e19424df72b7750e02497cfa1ec506dbeaded9ba88f37e147290c8642f49eac9e6685ea387889f57414d7ecd2a0328f2aac0b7c4975ade40726985f94bae1437f867ec18fbf0e9a02edee84b46e0833e06689748815f491a112e4235c0b27132daaecc66ce786b5cf9aa4a6e3a6b23359fa2f8be79c4883a53d6a8de4de40be8a9741da740bf17359c2b34595c7bef2e87bf3083582b43f4034374c9386bda8cca1518d1083c9be911fb2c2b5573dba6c7745a0e63ba21030b64d9cd6d524c2b541efdae1d55271a5db64892fdbbb07b7ca4fb05202c51bf7220c72506e41a3543a2db4b073cebbbc5fe28c2cefe473480f7151104882a9872a12dde1a2ae7027125c774a2e706ac4dca583fd9a95397dcf47b88de551f2170353fc8030b2d266501fefd69682689f3a956c2a2c66abe0125c8ce93fc71c672addbd3f88bf188250a263dd7122cd40963a54607b0d2070f18639f1cac27537239e1fe5eff5bdc694165666e75a31f19defbe05bfe99726d6c664f00413c8f210e08e74a2ca333f0a4f14cc2bf31f566eb434124c1fc9ee28147cb4cb97f1528a66f9032e21defe4982e7402efd9868f6210d0aed9a3956255c09a1777f199729893312c2cf3fc7fbd43adbb7f10b0a7c0ba4d5f5bbf86175bbd49457be65a2d9007891da223add03f40b01b1a8c9a2156b657df310106b65d4ff2c591af57056b03711d7d1958c285e0ea6003e7813ff2b1a14330c635c72afc4e34e4138f72694c1ab8a1f17cdbadda099a3329842623ae909aca915cc59e8dd3c0d1109872861e1983a57caad2de75d6fdb559d045f5c4140d3e112da948b5b099d71788086a85ec1ae4f77d69028efa29ee320ebc0386352ba9a9cd5c712f7b3351ab5b72b1914e2716e03c662c4c28a63cfd63081295875e66966b05e2e8200c7aa7c17207a9e318512c90c337f9371f0959dd8cc476e28dfa0a5abb1a7662191e87a272f155c3ccec4520abba4eef71b87ee8ab6e00d64744b497432d8b540d0f0bd8723a3742d99df9646f4d6cf86774113439bbf712261a416b3f06e0876a055c5b09e2df23748bc27606f4c1fc843b0aac668d270fb1b1fccb968a0aaaa06e5d67726d9e1c8d57206dc24a38af18626c146cd08a990db3e20afdfd0567c8f8199431edd2552b17d3044b821278cc64858d7750ad37e6f8756fe700ea900bb03b507212b67275593d99e8719be489f3c6a393548d34288cb85e8755e87fc1c51e337233c9c7418f83745bc7d5514c1e83d5ab76cc60eb3373424858b383e9af36ec726d0660b1c59ad79d5d3d3284ae53e52e44ada93aa4ac9a29f9fa723321a5aa31c6fe13cd45b1ba7769872fb4b78fb97fdc80b3dcc3fc4afd1e77fdafd5072972b6eb01a13810e31fafe8172bef12fb0962ee109b4a94506512befed9ecbe8f725f84aa843a6354c8780d18982e1653c083ab41d365ff59475ee44c82a405dd726cc9866d6b08d8bff7af94c5f513b05a71d0abcac6ef2b6d553e375b1db33e3dfb14b4d7d33a00a5ebb5c0b59e3324ebec058287265fbf1c16af3051d5d8455ca1fdc3221ba902ddad4cad956fc759afdb71099788fdbfcce0b8e1d9118dc30755ca989f955d064c3b486386fa26fdc26925bf17e8d665ca053c4b9a278f7872902b8a2a8b37a07d7eef0c05d1376b430d4596570865db99ebf192759da5c672c8e5c45b0731d966974d39dd1f24d8c3e2343d2d5b5678709233d17346865a5ae9fe498dd655c8ccd152ba86aa4fbf287445d356ef46d9852af77c5c329d797297de3d7a024de9ef90db440f784642a58d9cbf9b61e178136728f2fc5e6ef51215679fb7d9bfc6ed335a7fb1cb88a406d17cd40fcf770436f0057d898fbe067284112d3a00e09b8c499c7d46f7413c851ca3d7c45487b4c46144488e8aa9d872c33b2bdaa4834f1bcc3dddeaccb727b5016e210f975a63cd5d98b49c944bc57256a56ffb6110bc22be02a848ccbfb2ccb823bca66bf2f3d16a833fdd723bc272b3b56569e115f119921df3241e3c925b15d82ebd9701268527eaa9cf9389267211ee6c1185e879cc4c34d4a87e0cea47e952c300ad22da3835ce62d003235972009073c0326a594d78c27e3808b0044dbdb90e26f70ce1e47123e1494976fa72f7184e6c2128fb7f95a8c2de04eba154e5794f0fa42aa98b3db49dc65407ac15e283378ef5795418d627a02c397a1369839002d21289b11cd7ecd96516b83a35776e31aad3ab203ddd85ab775bab4346b14538114dc988576977386f25474a72d9a15b180f00fb9c290e4a62c628de01f25bcf578f71ed7c5e7d608d4e128b05fd3a39f61bd15a506e9fb350608d3e045a5a56b9a867b202e1618aeaed9f4472cca868ac84eff007d6dae5d61978daac4296040cdf03c8f4fa89088a5336a836edba550e163254612bb88b6381357644bcd93cd72d6346033caf2ccafd540f12264ddb3d05cc09f56d2b24ace986c5683f0dd2b3558a22cfe4a51bf446214d72e7665efdc38350c127d196be84be27556c4b6f0630bf19a78bfddb880c7ac82ee0b2f7677f0d7b9c7b6ebc0a127a9e1078ed37126b52f7541edcb76260cc4172fa9f4cf753bb3effc6e7d7ca30ad6e38448b769f142fe5dbf6d021cb607a3b05cb8fd4419b785bd9da02161183fcc1b9a351a11dae0f91dd9f0b928d7dd7bf72cae005a1a1da8db009299430b13ce158b946a914e0e1869453919055e146ed6db2f708ccd20a5e5861cd6ca5be8c2d860043f87a583e001d533fb4f57526ec72cc382363273d1bb14771679a3f3d1e886916dab12f4702c60825462f7fc0f772da1f813d2f2d786cb0235e25c08d90a373195889d40ce3e26340c606b13bbd6c1ecff133f7064a7abe7b78ae6a6731186f956f9d28a666ff19b97d45484f930c81e9468aacfe59fb58bf208cfee115dd830b4861c643d5e95faf507f350bc672af43ae07dab342a5579142fe57f7693555282b2f90d7ecb5192cabc11ed12c720aebf207c166c69728b4778e90b39b1b1749b5a0806b711decb6904f3b14b772091b1cf517139e85f18e19d4dc01fcf40e488be330d06173d6dba16617e192723685a7eb788790a412b9b13fbffb5f06ab016dac73a842e56087a85dbc26db722f43c877d29c9653d4282da578fb1872305fe958cb8a72ce204865c7b4610f026009e5b9b81d7971f343c6a535850d08574d1a7aef21408ea2b567012b43370a2950736064f301911b684b775a56f785357cd17223d7ff89f3dcd1631621a54a01a11caaff4cb7d7792b3fdf82f32432fd15efcaf16d0591330a1deed48b267205bc54f1c9071d183096ac43d19c3808a9fac2984026b453a5d269f4c3043a7249436468f5150e5542573a0170a0cab2f703dfc82cd1f6649b38a9adaf39ee55d25020c6412bf430995f88cc49edee266498a371a138d781264003f63e857a72b42c131cfef19f079e01c5f43b32d7f0ffd3f443fbb185227c310c0ee40fd8728150198798b0613fdaa50448bfe85049734b19c6f5230b8fa28eb7df3f9a515e6d5296f8bae0abe07ed0a67e1002194884a8aa4081df24617f03251f34976f1d8eba451da5e204fa7c63feca7a3b65a57b9b5d7a46258c8042e71bf85c0d95727aaddbd510d28f97ea286006f50d7b7a68fd88d64b61c37093864942d4d9c958369dfc176accc22d214e6bd46a376fe6b7555a3042c49ca4937cce4b857e2610b9e717e7a343a67a39701be0789931dd784bc7021156b44b8da48c8af8fd1572843df479dcc18ece7b5b5078985d05cda26836765c4020af76c663211e5afe703b48feeb75bd75b592c13d0285cc4ae1a1b6d51fe393e96b1bae592635240a0f0a5b8a202dafab81ab13c785ba1a0e955ea9203793f35bea5566ced71ab58130bcbc6b723f35af8b7da44236f464b81aacaed32e11964c9a695a5709fc718a72c9d0f485383c95678c0a0250c8b5d076933428ca3a29ab71ea108b4d9910d74b557f541485a5d20b637c7e91ff8fdebede1e9d86469ab1818557299039502272ea97c4ab767b253c7a40937ce2928c85549a2f59531c6389130f6ee435c242726b2b1f9d0bfbe42fc36419a64b26bff7cbf180a96755f9db2c892aeade1cff720033e5d9b4aa12bfd2b519656a1ff8758341593c388f7bd91882499090cdd051b1a141d865f368c0f1738afce40d927d7595b26d1b313f7ba41fe10312085b48db0bd7b4ce389326e8e98ee54299c04deec82fc7b754418ec9307cc1102ef010486e370075948704ee47cc6cc0de92bcb9729f39805c0d30fe92aedaf81f606c4d8423237dc8c60e21d886ef7b359f04f6006ef10408b7c9ba11e9ff74383572c7306365b41165f6a8dc827b08d6b5b0a624dcc597b0c56898eb94f405f2b814fe48395c4328b4f1d1269fb842bf9941426b808b0ac36ff3e8115167cf571a72560f64cbeb96c7150b1914ad134a09e07245068658b98f4d2b40a983500926720921c15584d108a4ad5ca688c95fc4905b49694d89b3606613bcd1e5ca18872acd3efde6d24101d74172229cf168ab4d59ad037673e8d7de3d6f96353e2df272ea81f7b7bc2c3b3d3ceb7fa4645853c1e8c689f4bc5d0f150cdb0cd31e3f54584888bccf4b26e9ecde64cc318033880ae8f96e2a3c5ff429f3a97ce06988de72757f747b998f36d9a72b248893db424ce8b583e20bae6b054b224e2671590472938f567767a08e62c0c5117e53d5c50ea287e51d076612d55dda65ce58f37c52d95cace84538310fdc2be3fd7329fe1276a418ad8b34be4883b0eecd5656b50123f75207c0b771301cf38036a9f50a96c81fbc3a50789b1e99abef0135ad487277d777892a0274e1bc9c756c8d9a7357000fcde124a1f34fee59c21ac7690d725f6bf3684e7460ef13ac1b564bd2e5a48be80fa608ed3995fde394a1516f6972f98ba692072ba49e0e00ec2dff852a7a84933b7e4c33f2f74b9679583f42785ec30fc3956d32103b34fd01c62686e3a59178ce6a8529718f9f34867bae314a69aced5d2e031b6c122a2bc8d5d2f28b3b6c236dd0e40da8c48864f36574bbb272e390e0690af90ed50e4c61a2d732b681c14f800d82386fad4fb6d81fd251ec72d16cc0d6ac42388d2577ad887d68cef15e86c7e298179b215a829c58af382400548df275e6d0af58efe8c4dee372851d96a3f5f3c33c11afc3d05e1f7f0b9e72f3f6344b57402496c124bdac2db9e2a9c13ce69cbec20d2c1eca4a9fe92da072d71b01d4db6ac538d44a397e4310ac55d8769bc80cb4be300eb2b062050b8d6e8e39185e7c1904f631aaa529b0a5ad49f96bdb3d7808172b4f560e2249e72e72e1f32f8afa728132c675dc78ac4ae981f100f5020c3f3d41f705c29d9ac7bb073a07cbba70f3d112e4be88d81644e8817d4683fa343e564a4b8db69bf61f561bd189193578ff7f85a3ead6126b800a6c699ffd31609cc3c146afa6c8d542e21f5c9586d1ff0497948c1ce6ed24359a6f3cb109e27530638057e0d646732098646fc53ea5d30bf0842242b3ab50cb68a351e2a9afd77d8af65bf7c8c4b4674172886569ff363c869046892d5327ff5d25a69cfb079e2f9a6ae0ad28614cd31a72956d652d0d0350f954270c4026916c9c14bd9ef70550a63e888287fbdbcf3272dbc66f3ab4a04f3278416b8c8bb7e9e4fd63897786a41877c8402aa1747c41720142c17f052a29ef0f820e183fce0f053df4f68a0b2c8f6b7227ba388176be72db836ab368d25e0f102d5c06694ae929e8cefee84bc3933ea56c5b4f8e0e7772f2f117057afe6017555c70c7c91860f0eaadbe7484d6df6226d0c24b8098fb72f6676d139f775bce0e4f03d7e4fa6623367ccb9eda9e38fcd2f0ca22d067a93c853f94a8fd2a73ccef6ced102fb3491627347cbeb2676c685379da6e3e10fc3ccb99e180696790055161be70266ed83003ed1e8a572be99ba30fa4f1ea889d72ad0de95e64551a70598e9a830d86e805f9297312d8b1bafe4d9214842234b4668e03b34407e099dc6a079dc7d3a28e3d73d091063db04a6b12b912ab752a5060e49686e4163a5babdc6a5f4915c7a477c1aa179f89d6a8c7e1669b8d31e4d172074e412b6e3cb229b7228bfff7261675466c4b7a0b9c6cf5a3fc546eb6adaf5d5502b06b90ef77c901abd7b61fff6fd3d446030d93665129739e61d9680b0f729f9cf15d43837b2cb6e971bdc5e6da4cdeb3ceda77b4316c25da76f5b49229726c729c2e70f27147b19ac943341f0a021050506acc699c36f8ffbe46a540075fbcd98e723688ae6faa78338b9752337e2f0ca491ab054ebac92d76497f8d3a7259627bd96ad06f5904967c6c041145079b69c535d7804afc17c1528107a42a27586da8cc275ec0e5c12a28f8da9e8f6a98f80be35f7146f1072e7b9c2e02154667bfe439fad031274f0706b3d0cb3c153b9eec752c4f669865a2afca73c5d3552bdb29e78cfb7a2367950128fe06d637e3f6595a27a316b71243fe378b9b38056669c7ceb59388666bcee33e483bc40e0c5959d91d7b24fa79dae2f84b593f72a222bfaef2ccfd3ee5f6403a7672f611bcf2b97a381f2f6ebbc679676dfd427224ba5783fd7a4288799a3117d650e3c418e7f1da58f269e5ecae5efc5cdb98724fa37a780589b76b7301143318e7384c2b9768342dcc93180d99447b9254ea47ff7631a684daeed1892c382fc9b8c012206700b02a09d80348585d519fa2cd7228bbf2016e2cceff6f3c3e76a1881336bd1de327f3a34ec8aaded7e8c7e0cf728a47249cf5548e46b4928cb728eb940d187b4e55ad8da6414bd9279c89fb8603f2005b334c45213723c6a6c3ff3da6693b63018b92d6b8bb2a4c857ae9dd5f7266d69bf69bcadcde019b161b4f4e301eac0dfac58dfffabc42586fc2d7a5c272794d12488938feb3e112feb0d95e845532a25672904e88243cdbe956a71eaf687cfb78446c85f83e7676c4c54d22bbb2c556b06f26b6d733d2464c02838820008dfc4ac3ab53ea42a692bf5e62f22f9ab24f8491d46eb4e39d2b7af88cb0087283b39a4992555d28211904f37234143565f1de442cee7881f640f7e5e5edd013f50128c10d5837b03fed026558e057b7532d82c183148c3a579cd28f9850ff0a5a4c2aa604f98411cdfa266a0b6fd931c148bbb9717ef22ea0e90a8d3f9f60720b23a5e9a8c9be4b9939ecdceaf9b87464486dd5b36244357455c7c30ddb6d10b9075e12bc6f8d91e13c4a0aa2bdad5dc799929f90245a6deffa4226eb9ab272cfd64b14b51f8f35c3eeefd656fc6ae890ff3b4aecc41c8c83522e0a7ddd490f2f2315fbac3ab5413cc9f446fee08d77f380a25985179997ef2d5c9adb7cfc3a24c4f5131481eaadef8722a5ec32f46115883b67f71f1ecbf328ea83dd114a54aceb6ea89b9df5c51bb6784616c2afd16a68a3a1bab324b5f44e0475f7c2f97228dde164c195430e51a0f78109e3fcb3ffc1004a62236a979b3678465a00e2720ada40b39a798bf07a859087da4c221cc62c74c0e71a22d52e49767f33dd3507443c8c77e2d38e3b48154f7ac2c689c0b188c8418321fa40783abf81e199dd72169a9c267590f009606e96285c0ced3ecd5dda693b436e2743dfaf62db1bba140320a150c109808a78c8cfa994ecaa61a2752699f88baaad6a100e82db0efe72df2b7ca6d22306eb3df2dc5e032ad41cfcdbdb55d4bc2bc03fa9753ca1bb8117aa314bf3fc386c0af143ecc0f056a53517ee2ce29ecb06023b2988fa112ed67284c1ef890b395ff50706a5042c4951300421ea4a8bb31dcb596fb9a89f9ae372a1935bdf581444af584e0d923b1707c12a0c913c255c879f15ceaba17e1dcd64db72089df85f5452ab820d65194e5897d7be11420701daf886242ee13c26894aeca803e828a81f8ceeebb2d96b8485e8ed57096b4d7d2a40371d8fc2f83ac772b84dd2b857ec86d3e7f1c33f83617746a700c6072fb9db2a38824bdee39a23722ea8f837641422e0a2028e3fe8462815d1b9c6e6c5e9b6d7b28c255dcb8b0a243105c86348e5f4912642126b4f12e11871bd8d889f1dd4205cbc96baa276a7107e4b058ab089b554283b72c08c5e26adf4ecccbf7a3e50cf7724f4901edcf67282c171b231d1948bb55742b0cfe341a987259693c356854e1df5332c96728572170f01d8ef166226d34a894a5d6efaea2f1067c68b7fb5f3f9de97360aff83576074e24d88ef77f784a0e7f0e2bf47fcbad9aad71da7691a93b449837ca47d56d644b1dc23d30921005bbe6040d3ee4b2ce40d3e2a3448e69fa0f2d43b53306db0146f83431ff5899604d6f89dbcdd3e8719927ed3544d682fff00a721aea8383e61780e8b1fd88513024aed0262fecde5db35316a62f440d5a2d5acff7dc563620c38c4f27833075c3cb69c726d0b554602d0f9e72478e8fd5b9f074f4b53726504c496198ad3d54e89be7c704382214f97f86c8f6c288bcb06ee5896800654182d4dd12842f46648bd715ae6dbf51023115116da21e9a9aca8dafb1647280113521141f4c8103c6e087d9cd970efa51703504518e68c0bda4a3ec829f10d5a89df618b99d4c6deecace5ce9bec406a7bb02b1a6ab9c81648393ca5b48da75bda6745e3497eef1856c0ea4b1ae1fed561a119a2408487828b4c1b19517cb3724d40988797303ef7a078426c7e7ce06542167ce764b9dfb129a5cd9fc6c55a7090c52c0f269ad59b7691d977ee6a83534b1678134d47585c1d7ca1ce57c2a872daeb939a4e18b8a0b4366d098a3658c91f000fba9fdf4b3effac5d9423250972054b9aecee7530e0446be6a38680dc0b4e3c347a9f97681a78393c51df112b722ed319d9acd35b7d6de776dd8e50c86e0368efd8d5f96d509a3bcdf39cdd69726abb2b36a08b224fdcecdf5b0f919291ab9a3135ec3993c888ee9e399db21208d9c2f78ea5bcf1937cd9154dca2ec26e5ad336a3c6b8004bf09265c7f1968b4103b8be5a9f12e336bc7196d09f2f78397be36d2e644629e4efaf43f7dd277a723dfc7fd1c9d7edcd8abb9a0f9feff8fa37ea67f862eaebdc63d4c4c4f6a03d725c1d84d098b2c0cb4fe91aca6fc3270e29c2a336e56dd2bdffe2f75fe67d8172ec107af9c94ee007d4ae97ca51c6b5c74727fb9e8f993f0e53369e951a694d72eb32c3f48544b8b8630a066cd0b5c28c15d0fc1fb120ecfcadc146eb0cbd61720e68bfb24a0dc0e9c8391c42fd2e0b52246379ecfc77a1a7745a9432f7cf5363a24d7a1015c7ac41baf8805547468eb2c4f950c0d49ca7e315f23d79aabb20381742887612a8d937b19960de0c36a6607e6fc03e82505a54bd8bcfe00b0b66075868ebe38a655b2dfb2c4d5820d9a4d5ce26fadd19a9f39904af541b25cdf91a97f1ab787451912719a21acd0936d5111dc83c4714116d6cdaf68164b36cad72ac8460e12063ffb021086105e308509697fcb880e0e8ebcdf3a201f5943dd307e4054366c5f978586235927199f7cb94f0ab2fd68bfecca9e868b2eb3de6271d8b7f364022b01b3f07b09633a64cdb62a142b481679118ab148865db4e0cda72c44c66aea307e657ddc6ac1b017f793f7ba789ed367801eff9e523d6c260db724f8eb41d0ffcfb5135b00b0cead8d2f5d14369a5bdfb6f0fec938f252d6f894c7b44672999d9da11597f4a0771e8ec75899a194c46c58ddd58707edb73488872aef16adeac2a085d7f9cf2015fe8f07498bd949b3d883e83bd89d5333b601f72816e61ec7c02bb6ad2c6a00c40136c8c2d051afa82669088288eb846486737030f63f2988bbae679be1a954259cdb615305abb7d0116a52ad99ed0623a97177239c3afe6de0c1490d6eafdc0e05c3af6dda47c94d16a014ccc96350655a4bd727963c4d1a83876f0f069f4ba3dc8fbaa7fa7387fe0aa219c48b30490fc796a1836d0201ff0a47b46337c4e037fac12a784c28008a907cd6625fc1b3152bfd9722afe146493a61af25da58b1168b879563d99957c1061550de3ed86f541514d5a3ec7609677cdd5e89b3ce4c2e5e7cb191aff2063cabeb6229856fb6bd7427e1d34ae0dafe86c8594856b4ece6724d4a5a32bbc0b3d6b2659950fb5049f336605531e066c749e43accf09a143752a7e9dbdb568ac9c041f8f5e7939232c28b74aa886c5be31cc0402131d2c0a063c60fc7384bcd19988307915f24788b68db1723dad2604ca630cee21dad9cf241d6f78851bdd78f2ffd163807ab105154509572bac1722937004d08eaa42e18c3ae8c2f63e9e9f4da789edb9605841f3c8f9729dd2c78671b6b3a55135e9341330e10e61af3efcc57fb8202a688ab54058ac7252500348396ee46c8f224f933ec184c660073a191267d3e8752954965e09222a57dfd20274d46dad86bce18021a144d54029c484cbfcf1a664165c5dcfc9074d49df562c806f8b6dbfa1006774eb95ca6f044dab2d0d30eb1eb8ccae7ab3f3634806a9679af73a1addf3fcb864a21b8a092ed5eb984d5e000643f619ba611367d4a08aa0158247c7c32d4f1fa48db274bb3863a74aede9027250f0c29fd012508cd301040da4fd9627c065eb18026f9e6569602fce838b357b18aa6b92dee93ff2fc58fa402590734765e3eb15e0a99bde8920e0f4cd0a64b1eac090695c36336e801c27b8f18b48051348c4c2eede023ee3a9eace53d7183c29972752b7df7233b10552d5f15a9afa08b5d966613c30ab65d1639da6f580c1805727c993025e55ce4b2459dd9da7da8d3c7a4e55f8156011dcef0c31b46103bb3aa7731137391d0a829a91e2a2cbb957490b0a45617bd21c7684e3f1f75fcfc870b2aa1351168fecb56c0894838715f02cf356e356a546e3d75906e2cf855f46dd90629c9c701fb6bcaf59f1353a81f9656acfa63c48a854f9f4feef173abe2b62cfdec6b77236798bad8a2ed27f5d4c0db124a2fc08265274d33cdda561b2dacb0b98525e610b8cae77a8d7b480d0e4584aec522eb37f8c93014c207bb4ee046d879acfe972b846bc6845cb357cb4b13231af6f1657b89c64f22f3491244b7a300f0c31033b0f1e53a5350f1c01b4a33361c7a761b2c5c4b993e739dcb9dea1ee8f22bee7167f7f01897fb85b36caaa33d80b22a3762a68982a05e29829a412dadb278b2b7245286a16f98cc425d808c26f9312945306d268082d2e446c8f738d3d1e7b1d58ea75dd88c974d432deae31d4b4ae25a307915dc689e9781853fd08c42587517267bcf9338ca1f18b54712c61e44f5ffd43e5f0cc384213d2ee8dc815e060f672fae7dce5400a58c4f872969162843c6ee686496e2a60aca7fcd236d38de77f6b0074616036950aff8296b1dce8350e3ad395ef5991739f5c618860e0f904871ff54c03149220ede7c80a261091aefa7719c8197f5bf2418ce15d94bd780ea67267b5698481631daff370b2ea48e07305f7c4eeadab8acba226bf482dd5a30f472ad4ce93b10380c494653ad94c04825643d34a7fdf138916b1900257931bd572240afd0a8c1d3909c9c22553b71ca29faf670bb11c16ef8b17b6f93486fc5b3bc3d7953da1ead294a985f4d2988168420971f9221baa8c04c0ce0e7bf4e763727312e1e8aa3143b316c3c22a899d0c4184bc0b4f74255bee685669d823652c72dece8f1505887a9b1e5cc66a8249cab8b72d6e8354c7098da715f357b0457a0f1f33f47a76996794343a0c06e569f82e7645fd258b11051f73f562ac61c16f725f2d89f1edffdd4566668f9311e72f5767b156ef4e45fe23f718dd2d5deb63728e7277ff3d74183215820159c68fc9422f6eaae0b46fa213256cfbcb9bf98772ca54e3e3da7b589a57e46ca8ddca13b49a4fb15f9e089075e6c4aed99c8964105666890549619d3bb3476168a1487a3d04c835d82e8d12c6572ffc4cb25dfc7278bc6e2338c0f204df7a2918856a56769e1d3a888989833290e6c4c5525879643ebb5d4bfff6296350c2ca2512bfac60fadd0ef28fb22873b002a1dfef298f72961d2711922a560b42c1877a768bd5486698f6dad36be72a5ff36e68daa7ad72024aa645e27dd192336dcbac02473b28a88ee5079963db56acece1703dccdc56e7ce72a72589cc6bac408eb7cdf59903c33d3b187d6759ced586e503791c9a72e0544a44b36ab850d8d376238e4ed40297834396aaa8537749100dcafec1eb72fc92a6724d7c9ae7f544908b96a45443f42dfd9eb5d4d58cb6ab5c872768fa72de7dcb1b93b702edf95c1e76f2e36f180e626d93fa88047ee5694402fd360e72e82e4d02022faed733ff67ade5caf6759c9b32ead8eb848c38ad3dd606b66b7234d409d5ecff880ada1802da6ae4cfdd6cf0679141ffc2ad15175c7eddd53972178316be599391c9720f5a01a28e4a9cb7f8f352fbcfbb19820f5f9767d2633997630811c14d8b2364a43608f0aebc16a2bdacb559d519cc5b546242873d9858212029923840a09cfde3ee08cdce16b70b4762073f8b1d2246b8885cdb45b7720ba8413aac5d5f7f592d85418a72d0f40498b280127a91fa443efa238c0fac7274c39cccd9bb73580ec0000d73d730433058725532a908ed256969a4fe376a3601024f7198591dfc99eecd6af8d58a5b28fbde5ba3718e05dde05b4b0e66774999adfa23e76a89ff6932e36ed0c4d9e6f010529c5b7dbe17f8031b9b25d72a6ff94bfd6bf6c426fa9b1fcd1fb8faef38787eae7808ab1cff3c78084aeccfd611629fd94a21fa4929be272cd8fe8c30a765d4692051c4cc84312c7d995456b2727507b9a318c10c4850aedbb58a75e585cc25ed6e04ff5c6d6e986d1d4baffd72c6a925307e40f7c401d066583ae3aa54b244c1ab4c9d73977fd71f24aed0d151e68cebb9755070c453c17dadd300c04ccf7456836b158b604db6c71e04383b723d0d06a594f6a0c0cafff811781dcc6f792b5750a3492bb16e9f9f8c52c5ec72284fe22a700d37c071443e899aa49c1bd464dfe5195920e3c63fdffc411b4631366f772f04facceea7440b9de6f61ad335389848249b6588e2d728f17544467227dbb78b030c5b7b323b8a829031168741e0b5672f2eea056bacb4f8b10abf0deb7cd2b8048d27d40e52c5abf0f1b0233d61151a7c2cb4b2614247e693536772c03de4801b9a00166151422aa7ccc063835f47f25c4a22a8b52d88035d8827723539a00eef8184f4830889394401a7c87ce863029a66ca795a1557d7bb06e040368bca6fdd0870eacb5edcb042fb0286a6867c84071d1dbd35318dfe8a550472aaa451ee4cec3c4b98c77788f68e062a1a1d4cb2e986f6bd65af8494d55c140cfc5a2903e6d373bcc19eb932961b46522af68fed8c8de43d59dcd876710b5f726a41e3f3828cfc2eea7f78ad28a5230e560a606faf93773d7e79d86832ca3f72a899d81b3ffb14119f46065e82e6e36bb673b248bc8215baf55feccf3996194203f6a64db7dbc3e3c77e6ff609008ce5a23497770c29d304ef3623a440dc756cbc62e89b3ca3edf6fcf111fd0c35e7bb3e93ecd67d58162f407dda958f17ad47d054301d0c6e6ba601bdd0cd14028b15e319eb06ee0f1781c0e88d365f61c2046891a12964c32f304a35b8c463c05f94d25b6c4062faa7bc9c610824e570cb37caa5b480a4fd7574e13dd304610c3f69cd6c8314602ff34cc973d26a90d0717205dea01e8c14cb2c5a2595260930e460ef4627c3e920248c436805198408b22123f82ba50344b2544f5789fd63716f6185955321e3997e35e9c4f1be156b76317d75b1e959de202579609a8108a89e38907269ec386286105c45ba93b7658d721a5b2fff6bff5addc1be7595d3501d34f22cc5c20277798dc58032f719358a583ea67ad37aa219c631f25497d99041ff550bc7c1d4ed98f2c80d7f783d8d3d72ebfee9df59a09abd6c0f17ed7e6c12b15bbffa139837d737fa7ad561aa9c0b72ce2ba44f37a5f885c5a1a86265d5a9ee4e79633295dfb4dc90aabb647fd3aa72a56af86ccd3b8523b35332e35503159926e5f45d45a099bb36be031d10645a726a49990a45ac43b9c5adfccdb9bee1bccb71b76805185cedef63dd3e4c0d1f721dddee9a53d337632e0fbc519ae741707c69f78cb6964e5215985d8eef2f3a7283a0b44a760858730c6a9e5e7880cd33920680035071a251e150caff1a8428664a727bf2000b5cee9b82812e09b3026cb46f06e263e52c41765d418360ef5e7229037ce44263f3696f4c75f791c841016123fa8db3b0c055d8b459dd84d71a33a27cc739d28603da77ae35c852d45cadd3c25562d455df059152fafcfeedbb72e93af2f41d515017436612f9dd247d9ad03f98a7b58aa48635a48c3799dd0572345608c0c1cc05f34ad2f5ea0c3186b76f8261bc0fff91372870b232cf14b5269847a7c67a93120a91adabecd67ccea12d1413f0ec93f8d07ce66d708eea5872038038be83ac776a2621cee26538810a00f693ed9366309d0323056fdc39563d77dd87896aff64c7ad31bca9688c23e46d60129e2f574be5a36494f1b54cbe72b506bdba898ae82615bcfc28bea56cf96bb5e80f3ded20e4f386ad144e3fd47210edbc9758877bf21bac6b93ea1f0f0d5310fd3931a4da067ee046ff62a8de37c2fd5373c2c41d202ffef9da3ddf585ce7357c958929613e25346d41c48b4d5211c1a357a55f8320c7a7cb8b120036d663b224644651793d0a29c0424f791900730e972eca13b8fe5f121f5d19f24f85c675a67d6c6f9d6312d25eeebe615f72168acb28e0fe9f0674888d9096f1f576e9b1c750d87efc53d5558faa5410c3724ea1d06e62d99dfea7684e26c6b2f26fdcbfc425e6708c123bc6e24022184e49124ee152d7bd33a220e077e894f30e7737e39c2283db5b99a2ec89c74aa5e033e0e3815380a1ab603ba98dfabede12925feb40a7bb08c4d2ee166fad915108726484d75d491df7c64b18adb43efbc19af4ffda3739ba2a4642fdf2a14f99ef61a22effc6d6152e7defcad221bf18c01a4ca4c33eef4369ae42c33fd493d6150b1bfe59ce5f582063ec09bdc02c93d2ef96b78d9e22cb53c58ddb25369ec68e7237dd4fb7b4bbec98607cfb80ac6b0c1aba23a7856e51e3f2ed05bc1f7d28f972f0a7f598523c7bdf3812b29a82b724b15fe5429f9b5d7db8a95f39e2cd79b972693a0819bb7bfe4c79b874802ee5033c57f08f1eb3617312276ab79e23b5f82d1c49b815ead7e5d2b6858f6c7a9d51b556686e5060c41430a6480a1f137ad9726b81273323da7b9653337e7af6e82058b09efcb8e42544bc1dc249446cd8fb54d9df1517e178c2b830c420d251298c6182daf9acac5916481d765febf709617241a630fa6516714427b912cf2e128422b23355ac30e52cc034623d41f5e37172f47270a66c9f32e6ec96fdbdbecc79899dc90ece02e446913e8ff08501224746e8412c8de546e2f1725045311f6f4011036bc83b24442102258d4cb985b1f437ab8405d711ef73509679f545d59ca151733615d70400b9f79cba8634b10b8472ed10ef409f38bfc3797e3bb00f9e1ffa67cbb51401e51e17108d2af099fa8d37afd87e85a05ae31ccdd0c1c34208fea3c30547025b7a3cfb968fe5cceec43270cf673d595d66cb83a24ad2c04440876e4a76fa75c8d202ce802b2fc486313e72f335933963bf1385eb86029a2796a53236d7a1933bb3d0d26b4021fe3e9d0e2d89cd61754eea4b4dc7bf311417f7881eafd0bbfaa7661028608a04ea16fd64729088e326537ae948f357bc8d2d3e5dc50a9cd2b86b198025be4a312c4093f5725e5b0e1e30e442e5c52f6781ff4addbc5635ba1c96ceddb87ab34d6f1577d01f06cd39b143ae8cb82807639946ac9c4a3c5be10590f4b4d55dc2cc3ed1efb054ef89fc57b17e68e3799385d9ce2ffcc77e2757507c42e03f8b96951d118d0872d3f6061c07d0b06b5dcb5d63d60783fbd404bf261c9835c22e578e6140f35c375f8c8618ba0917bc9f745c917a2caec53ce974ce2a61893bd2e84cd137b02472fd2fe2e571a00093edb18c24f47a47e1113abd66ef25a90cf0d2284e1b26150799ce0530731e7d4eea6d6ab52cf7cc0efc79ce1a185c018339f06f5c8864816a3d6e3178c4d80b181f104156fe78ee91a19a1d1a732dfd09d45dede410d30d3c42621238a2934ce8e014a5d90af91d1b6ddb0fcbdfa7a447da5fb8435a83212cfc7e1639a681fa67469564fbc0e1420cdb4970d4e3863e7578d0b0b001f2de724185ee4181fc4b98498042ada90cd8e08d41ceccbd4f1eb2dd48bcbe817c3a39640667bd0d55b681ab06213d71221c283f5066dddb0c6f819a9e5a9c79294b65dc2dc0cc10b6b7647f248bfb737f010306eec24de92d0103de39ac82642c682cf5cfdb94b8494d3f31458475f60363fb852c0fc3ea36e9abc51e29d3dd5a6b728d99ab1dafe56077e89482ff6d30ed5d6b457c76bb7652787a05afde65746a20c0bdb6aec271c5d3b3042f62382421665e040b14bad2fd1b42dba7ea4c6dda72c6efe17d708d4b0d102e3d9ce90232c3b802c6ce59bb288a8040b8846bd79b7283e7c966387dff83aa0e2ba8374905e3b6e284759422efde596278db007af34cb444b923defef85c1aa2eb7faf042163257383ad70b39335ed12620ac065247298ead0155158b4d8608f8dc7e81df23800c332da997ad098b8a053efe88b4172ea474e9ad1aa93b1b419bb4924fff97b0fd97db9f682bb5e880601f964988f726d07adac3e4b6ccef76f55243325d0d28e37f618faab7c9f8f1e2ad4481b047264bface44e5f0ceef361861582def640e954feaaac900af1727e4b882ed026295a00ab8f78d02d5af793d6fb2af5f70b222ecfe898d5dd64e3a9e52288c9667232909bc25a1cb5afcfb6742d73cf932c52c9e0c54a85680e7c0b00e4cfbba972e045dc256d36ac4c84f2c679bad18b3b4640ecc2ae10ef853ec053ec68532872bf34e2d0e580ee8234dbc9ea2d992cfb79edfc4c3157fd807cf3f345986c671eb91dd104d547fb0cf49949c9b5864c686e618963c180bf53ca9190699ab8cb7232f38901fb51c83eec583af485ef59f45fa2833611370d0f402eb6df2fc28f7283d451341bb68f49e497bb74b1e599eec183f52e4e0c3749a7016547464f2372cccb0c3d500e52e8ada9eabd7743a56d64104a7a5623fc52a5dee390af69c770724c505ed7d1450c617e2ee0d3c288735633a0cff40349bd47e81f77947a7a5f9032e69835c8f9b43f72faccbef2bca71771b84e12dc26a7df9b057eb97d2718b5c11dfbed74bb907166b8b8fe65d5d16e973b215a0d0866573e950a60b8cb7260d401f5aec9c7058ab6896e8d729ea9f490166af12a9d0a07ed20ede7ce3d725c0d321204a9afb32e340af834fe4adb144b5906e0f28d10c6738511364abb72f0116355711723bed59aed4b16a4f0734f9b8d529c5a171cc68cca553270aa64fa7a06e354571ac98b81ed12dea79b5560d4cd52ac02193c89fa76ff932a08722addbad193770b06608c1f3c3291dd03ec5acec31ae7a53378a4c30a0a6fc26f416e209ada9ce2c424aa29c4c6e44787e650f29ebab65e18408cad7c5532d03ad9df476a1fd3ead02ad5661b6193a5b0e50ab596dada79e17cfb8219f200c9728c803f46c7254ed6a8c798998fdd7619da75426bdacdfa59c709120eb94bc8725a226a42b90cb27c34acdf6d41f934fed8b9ec5543d7fca2d8f991b43764ca38ba94c6da31dc6d0b3654bb8e4dd71d9a6677574d628e146905a220aa008f9f04fac21dcd32555d31e26945358795cbf6ff9482130323ec8f463d383e688a66728b7eb3414a7235485a736ff39126c271faad3ee7de8a79339545e5454bb5b1225435686dc3183a4dbe792f3d2999fc796030950fb69120201db6a0e1508fc972141aa54c5f1a38e9b27c5047b46f9c38b64fc09c5816f7edfdb2a4427dfb41729723ca112e609d427eabd5c7d139e71c188c5b5264949b21bb2ea65928acd0726884b187b5607940d3882011ab557655fdda38ce092dc90bbd4b25016254cb3344555de75615af9d9b005816bc9c3201761a2ec580d4fdcdca02049e3ddb6b11b1f236b37115427bfa5a6c6319e958e2290e146bce6e18bc860c38e4cf25d0084ca5cc674e8fae210d53ec7ab043afe4953c763abe217bf87ec4e68601e4d425109ea85025009c9b9c02dda01b0916dfebb00c8663032e3400f2363a05be49725d6fc3050e52a1c19f4d1540108697bab15f359905dfcbaa37f66ba416269c723660cb99e2f6df95d0ebf43a59ba6f94e8caf218993635976ceaeacdfbb0877268f1359b9b6fdc6b6aa378fc9861112557475d07614eded2e56c27fbb7cd2b150b8b1fe209f778e91f588c355e10765a02049105587cf6634671d71e0790ba2baeac31cef29fe123620bd9fccc829af1846b59821a15bf41928308f889ce6e7291bc27e369d4a742cbb9f0345025b87bbf01a3600947f65178fbb6cc478fc572878228cfb973c6c8040790dea24bf2b16b64a2c7a509811fa385a37d201048728500de39ff637a7ef391aab0f8a083e97859cdc8c9cf72770302038f10d32c2ae1e72bde273ae51bc3e934991542862965cb253fc39b0f521219c4fddc1d587225700949bac0b15c87e2f19ba0b6f5fc8e77d154a562dd023d40fc465891495d1785ad1e2e5e8aacb425c62f0eb3eed7c10be294febc71e2af9849c578cd304595a7c5a58f666a3bc66978a083489084b7b297ed6f3398466f94ce0f89943c0d4dee2606e1782bcc7af4714616c4b04dab82a9b48b1422ef24ecd8a5b278d172e764bbfcb5a44537180250ee9b537f6bacd96cd9c40a60818f510a07bda7cf600be595f101bf192973a60d0b45bef4b5106b19989773bcc55942c2019aba5572f2618ed2036e4b66b577830785e9584801b1ebe557bc61a15b68510450aaa45ae277518f898f94ab8d4550f690861f8c0dc6f2559c5c1d103ee5e0b9c9ef3b7298b20b3e940bc2fbe0d43523b56aa2fd17d6da23a5aa5dea6028f0aab93feb72928e3a38c0ae24d70d5310554e9611f256b9159b3696f4a57ebaf485256175230d33ccb6791203660fffdd1b46dea41ea1090671047c51dc4f562cf469f476640d935c36c8edf2e3ffa1e92f00e82c53fee4f8413e087b91208646c0269b0c5a7f680ed7253669c1bbf5bdb5edfcfe8f4f36c2811ca88e67d084cf7ab71f220987eeba8e3e966d725a3cfcb08f2c711e5e0461f9efdf36e93d0a6fb1e2fdcf3c078a645526a668b9b3405cbfd70d6f135fe37d4d58549881d6f12b7acabe4d23f9e1ae3fd8eccc7e97df4f4b9b7dd812606787a76e026d57f495877ee530ca45e6eb72c452227447098e4074a56519609e2dadf30dc6ab9da8990ef734aa0f2491f370bf9372fb6e8d2477b1c120d261c9e98d31f298c2ee5b3be647353e9a72964ce06179d9bfd07ada5314d0b96827fb4654fb5166a860f0cb5466ce46ef727261251207e2dc1cb9840cb052d53ff2c5b176215acf038498d66917760735721ce1cd0154f5624bd48fe935ac8d5b749d4868ad3070d6228dbdc72fb5af342b45943847b467a482700386ba34c68d424874264eaf6f13b6b81366dc4c7f5c53b17d4132b69ea73fd91bb553cae21e7f9a7992a2d15a06fa395e654ccc2d2436960710cc2e75b0db65375bb22ce39fd5d7281774944b7c2e8242c1d260794a726789ccb2e79c60ac70134bf419fcb08f3e5a30bfcb332be7703004a619422b0362f28cdf95db4afb98b8b081d32989b46ca5314f0b6a1f7886e4922bc5b9dd72f3060630c627e9a9cfa7b72e936012ff8eefc4d9490a9a1a73bdf868a8433472b5d35f1f2987602aa7fd114e2346700c46aba4e46e7956934571984fca17d1487ebef7e172dca68b1a240d681ae31536eaec04039c98a139a7b54b565240be724fc3ce2ba4436ae85301fc9cd83e2cd934c0493e13ea962291e2cc63d43459720c56dfc290cac9658e180ee7a13b1a432b9aecd4bdb362b5f205736fce95bd72b713a3eaa1593d7e8913ba0e743b90ccb0c34a4430618700dc3959dde794ee33b19f888d265dd1acead0f2c1537a17ccda4d81ce4f74122dc89ecbaa83679172632a5ad2cad8b063c89be8d01fc6a2fb28560c3d0b1e347774ce62aeb791ef727a18b3994119b281d9bc3fd5cdf05d3c27a5394fe671963adca87f140a1ee372ca537c95210a1eaa99a6e0d9264101a8e17a000c0bf70bcf57317e5e8c683472a40024f69f4c283f03754add7d847de2906263ec80018533b30c0f37a16d051b992f8eaaae28d560faf930e1a0a185bcaa8773a5ba94d565e8ae53e9267ead7203be921a01329ec43911c44599a38f93d880782493eb9af3dfaf587844dc79455e22e23bc6ce468e570e6a3284fd013629ba1574d87a892ca66a9316781d08724547dbcfe606f032b06dec4ec8aa28b110f2a2575e92f5468d138e8b56e6fe5d8fc34a898d2429803bfd94ad6f11d63e8399a6f40aed237bf7cd49357545b972f05da084dbce96f0a4ddc8ce7952d65f5123ef05765f90c21a78848814f0f849b68846cfad41f2f34b2d4e6d9e30c30aa7a3983f1545bdef82d17f195e13517243ce48da424bbbe8fd61ec16215d5112d6202d7faa86a26936c9db7d4875a9720c346d3fd380667b62652f022e4cda728a9b375de142d521d0389d09e10993214a82c59ff917701e4a53df4f60bba112d4773112fe12b391fed5ccdf4da4a572730182ce896df1fa090cd479551efa4bbee2354ea8b2d9e9f79bf8a49d04dd72425a3d6303798b4714d3736c406ecd247ab418fea31bb1e91579d06a6dd9e07259d299fdbd9caa776669dbfe6d8122bc9f1078dde4a11a550cd6705f9540e372db6c637489e17bc0857617190d00090e54db6af115298002b5f496722d3e65030355dde26103031cd8c682e144678e51c034b8b869400b50e240fdf6746fdf604ff0667fbb4170f20ae348bfa626ae771f699bf110585a8a46f9759760b33a7220c5451c6cfb7d6a09453702f11c68b0b92d4b0ee01c408abd85bfbcb87e6e72f645a69e35ffefeb0088abc8aed24d2d14328ae3dee6dea1d0d92ba1b476406137209f7e50ef48b99b02a92c55f121b374c97acbfd49b43e7cb9738be7110d724cd906c5276279cf9b702d3ec31b116ddfe7f80315c0286128c6cfaccc40e372d3365ad29d7a881cc7ae77b7b09be5a61da360d0790331e10161047ac4fdd672fbfc177017e54ef1d7513f761e6c0fe39d3d5157f4e2e3783512cf0707982c7202ec4cdf0abbf6ce99b894e2dd68ac94feaf5ef95f321b114249656fa334ac133d6ba6dccb4712318e5b64ec30bd7c6d9b88bcef516128e4cc9d300e8c56bd7233823a6b0e5de71988a8ffe5ef48d5984a379319f76a53abd1daf0bb991def233985994deb93223519a6596e77a7177f742811e7b1461db0f0aff3594056c52e3e26229646b03354433d9538382890647a5f99643f917b4b842f0011db7f1272583b8635d3bb8a249959eb1d28ee97243a38b334f04bda490047001dcc494b722feb194135334654b73443e8a4e3f15dbcd41e684a24d035d19b5883fca7694dd6b406b0620c3cc0403f479c77a269e7a2dfd48b6b921e25924225c0dc8d5672b150903cec2ec2c708980702edfe66e307aab7e7aef3c089a864d8fd67cb937229735d7bcf1324d07fd746b098a45d1f285a6198d345b32b90df3ce7d454757227a6ff1d58b50cd74bee41f8c922bff48b93ed7c7643fe2447ac8de1e79669725510b7c6394f82ab001fd0594c4784334f6eab8d8722bc9f8a9c2e41faf19572e32bd272ffc680a3b4d3fbb7267ba799155be68e1fb875c29d4ebce2db290025f4a93284132999834bdfe64e25518a32d6b5a93898634ca3322b9af65ffaf472ec76c75722b1b8124223dda0ad82296a9b8b8ef53155b2be37dc844acf6acb72e695d241c46e7de2c3c0344598c98f17b9b43c3de01a86beaca303088086477295bc100e8a6f65a00c00f2dc3b8ba5c7120d2ce8eeaea06b9824474507b281727f60dfb0be80558bc48a8aa53e0e4beec0708201cfd6eaabb3fb27937de34c72f99bbef0ec95ee8d273d25ef8ea64963a37e994ef8a590945df5e98fe34b7872b1b5f2b971dea6b91dd34d600c631ecc4ab62b646f7752ac1a3c063573ee2a4143a6ccf06d56bfb64df6368cf79373aa277afdbedb930bb22bcca180251e71723b6a285b48686b5e1b9b690f668df6e1bf8dc40118c3d4e69169068d79c8803aa1deede91349da1761b6155ade8bc6c547022a35bf13e1caed51cf177b28647226f38f59ea97e4766e049ead2fcf3560ba4b65c95982dc32adfff1b20f2fab3276e57abb22d545adacc346b9cde3c704cc3bed4eb45de7c1ef5ad19be3e46568d680b3e89763cd277ec16001e8d07c9c6bf34807b3bc650e018093ad38b9f051a6fbd4c7d41ef2d3be48321e108f122de65498e08d806b248a69d5ace7d6402e0e22d69655eefd67ad6853ed77c3a1f8ac6c432c10a20275e34e34b789c976423fb23b7bc644b2684d6cb671c9efc6da247dbcdc3597f6c4b9a8099e78450b725862f741ddbdd77deb43d13cd1a13482dab6d536c9791d928ec56612fa826b4e204e8a4bed9ed9a2bdac6e7916404c4424dc9478cf05f8a62e0425ace32bfa72b8fd3d6d8c57b9a9e7f1c21b5d0073fe4b8af3584e31efa4c8893339e9fe4072e102a7c12330022bba5779908bae3e732e6411711a9e975ab39dacfe3b1e2b72ce879ad81c9e235b3e66c5d1a768f66ddba315b80dc052a4493fd873cdcbd272a1dc3f6b243bb41f7ed91fce9ce57b0ace6cc805092971c1121f163581191e4a6db8c1807d5634ba2e2e5aba1c32ae27abb4059663f7baf5e7cf67bfefe39072ee6a9270b05726a17206c572d17de979c8abc659bfaa1a49b4de82a58e2b9c6ea4499da01833986528d816fd4ab10be909b5c74ecc1140325f135a2c3492707224878b54c98f3937587e19fe9c067ff3148e8d107ea71bf826e457bf38b05672ce821d75c224677c457e1241e984d00842869c1b6e6c39cb9ffd8d0094d8cf72bd48d0d68242034881f5eb26139645e3a63fe592c2df4699d97699dc7727167243f3f9d765bb9eef01bc43e6d77f7de075476e7c25236e0281149ea805863771032ea166b53a1a459c71b54383227d157f76f052a85cb0662eeab3651da238724dd47aeddeee25531fb85689cab80c825331fa766583a0a8a5ee3028322d03656c6dca29e4a2add5bbb6967c558e49da8a7099108faca01a11553d04e141ee72bd3617fc8a1f50dca33c02a1abac0a8521a7e7a7f5c78f3a04f9f30205bf00357c23af4cc509adec58d9f2652077f1d4a0e5f1027862009294616518c1e72e7235db55ad9577dd8ab8f254af5a34a9520a5b0662ae7833abd8c253612e2c5c72b199538312cc85092e85dabab93128e51ae6b214c24f17e4403c7ef0f4220972f4be9f1ad705287106f6481e9a64573fc9a0884a7ddfb474cd8d9e3335dd14228d55506598d14b5def8b4d940b829c01c7eaacf57c21ada3fb67ee2c8a72000d04fa28f1a9750e739f2a12bec5709fed8eb8cc6350cdfd4f46f1af465ffc7c726be92dfb8b76879add1d71312072bc3f8a82fcdea876d43bf61b5aa7c2f7ea72fa6ba990b809d8855ed48f485e7d87262113d4d4766c061bc2d003d19b24ff72d6cf13f2e376f0a34a6ffcbcc3e435c9fd7e4b05406d54a6b005da560f0d11729e666dd7a3991016a767d5c299be3a9c2027d6d6a2e2558ce31783246ce7db2ba1b3036ab15839225676c5c0085f8987716cfd5f0f42793da9c7841c0486db2689050357f8ff91fb76ec76ca62f773c2a4d2c0b4519c753c51f1537674574f72e17e1bf297abede1290ea9a353e821b0d00974a1f78b74cb3c2494f967e7af5a23246165bd9c743f8a68c3275e1efc5ceef4b9247dc42d0e3925910735366256c398eae201c08ae96c5cdd0a0745e56e426e3d8a2b2bee27bd36da0c809c8e7253d45ca0789b68ac4f10b10f7544a2d1b4520fdcd7fa1a32a0e8a900a5230433cf1a9d994f8b50eafe468eacd1af6d63d921978eea4d64a8be62be71d681247201ba31eee66d919c24095063b29570953a530fa848d73df2379dec582d1e607203ffd143e2a153a7dac36c43637be3f5c4a93804547cdcab867a9f6876d0e218f51d81481573f4f2c2f5f15233129ae69456232f7d74453b269e66de29eb4872d6408f135b421fccf0ed6eff1bd0e297352db757e3938c4ead3deeb8e72b462faab6f6c33e5dce7c4f9932d7831a0910326490c60d81ad12d7ce899aa6225272004ebf4b939f053c657f83944ac7cd6cfae07ce02779120f6c57693dbb1db072c639dd8198f5576ff41aa5c98a97383d7841e5eac24dea93cc300d879c08c672bb7e72aba5666a5ef42a567a2162508cf1aefe2a5839209d475ae749a7f64f7237bde2df3c4650c375f0948cf6c745f5fbf94918bf4c38d221add32dda4c0d727ecef017d9e815665bdab4fdca94f84137c05efef4445f76da1d83e03ed75372ddf595d434d2bf1890526f9fcf6d50886155cafde5293692361b7baa372d74670caac2a276f88564c4b66c0da933c09e62e0bd581acfa24f9a85445127122f20ec8a8256d5243cffc8a5caa0e38389e84301f9acda9903302cdfc4ec4ef7f472555bb25994841cc6238e00637897cf8964f0230b841724298fbdc6b80844ab720472f9e6e7f3191ed18b3edbc0c66a1c209110737d479461d8efba6c5a2d6f7235da7eb01cfe1108d1fa97eb3bcfb219808cfef2279d694f8a1666eb78772629b2e219d23dd492a8ee7080621de1a77357d0e8e3fa3d0d8db215237765c6f9727b89c5ab444aa362196882e0f0e1206b585cb517b794b1460e7d4eb52ab9253f3ebdf474c91a3334a1bf13d9758cb99c58271dcb4b42eb38d1fff654992dd372eac9ec9093bc58655c9c3174b41a10bb486262ce7eb4dbf1045941d6d5f8e90b29147ba88e6b888c91bcb7b68cf50230233ea1f5a578fee013f78635d2536f72170ab9eb59332ad0c9258e3f370e19d533dc4f553337d0ee027e119746ff7d72e38a4418f4ce9c18c967914450aa37ba42eadd1eedd3619ce96c60d130c91172b7f5e2dd59dd347b09546f53ee033107f00c8294598ba405ffdac06a04e863726980dfbf77ad05c6f979532bb9dee21fbb8b411cba5073507373a48096fd0447dfece4c8aefaf6334c3b31606926ea4d16f614beda83de53a125b4c574472172de91f694f76228a7969844949d965f7fc6b4acd643bb6d6939d94da4acf8a972399ea176eb4b22482b2f18d66353170fca4115beff69f98bb775b0fa421e68723ba84a06f5df8a2509e490d2475783a4311000ad19e9822bb101b0ef9e066c72413777acc3eb832571ebacfcc1a644ff797bbe099c1275f18047282266de4e0acd343d6931e7af23babfd0df1a3f4657f5d15fca55b009d352460279b245d90cff0bd445b5e36853a3a14050e24fd70f6844d281d6bdb72989fea94edff69b7277acdd66fca334ff9a15ff08c57b57b603b1f3a27512da1e1008f80a20c6c372f5b26a91993a921b1ce8bb50250b56983efb6bf06e71f9b48e38109bb103287285096b5014548a65b9d54371bfd8f834f8bbf2e3bc113119c97803ac65e14b7246abd2657e4321ec8d3e6675daffd2e7c4d690d8907a7a8b86cdd6c47c342772fd43a9ae5b5a166f11bb5ec51b8a689b3f48be77ffbd43ae56c84b83711ff42db129652449c183364ca051c40c884a12aa83f437623ddce91c0ea03dba279e729044fad8501d03f32ec5e087236c4db9deaf9d51361ab10b61f5a4ae013c5a72205814f3ec7c00f4a11b4eda93026e41bc76eb5fd46af3313e77d7e64be11e72e7654cb0a3e62c2b3053a8391172424e7bfd28dcf3a39b65ffe2ba7fdd797072cecddc3caadef80bedcc67e8a1e38ed4632d753d3a41c6626462b5206b9bd572280c024a684be76f4e68240a62e2de11101e5e253f012cf7bca6fa49b670047292cfda3b56c55c4086edd8567497399a402ed56a721f416ecafb4038b18bf9728e41fc442819156ab84cad1913a0dee5cf48cc4bbc1ae2ffb81c96bb840a75724e37d34681c95b0877208474072af147b6713c648c6481abffc880d10f724c720333560a13c71c4ecc1515d55064f49751ae319bb49d83ecaa94516b6501c372a44427adf34d8298a0bf0c5c799cc2860037bb62cbefa23448a6f0a85b0c724996fe928c9e9782070156c4a03670657e7902b2dfecf99eb107ac6d8e8a451210d05704f6711447524c1f5686a0f9e62933ca56b084ffe76f331fcc2d3f2f0b031c674d1671ed14a15ec2e206564e5251cc0c31b596582e18d19d70db6022d37290c466804805885f45895f30324076d31baef2852e9ec1dfa6c39f952c776004d06e7fdf0986ed6bc04ae2ceb704a7a39e269605712106cfb5fbd70ffa55ff53dc0d0f4b576a68e4c465e349ec23bd2e03542179597f439ecb1db3fbd9562472b5d993740b1b2f238fe1c45f9e23d6bf72f8972c145f0d6d7e089a8bf16c2c721eda17ce7006c509a310d1c0a6909364af853dc12b13e46176686fb3d59de04a1804743b1d9a27421abdcc7b96b3bd7d36865310589440e0583f93f97036c71bf85401498b1bf0efa34bf3bd9b33a85db54cb84b185a7fc90083d848db4680067970614ccf3e93ce9c72d0bb8e02128cc744d43c8800f30611190caf382a4072b28ed129a77ef42608f0fcef377000dec1bf367b9c8f4a24d29632ecbbf1300c131edf70f8e90ff0bb0ba8dd8eba27b10802e548bfff0755bf3c6427f8dd207276ab0ca5a037e2fcaa7137b30d46f6e8045b6ee0a6a153c92c8ec70596167972b70040554180eeab4b7e9af9ba19c5a05dd7ee5a67ffb484200e126cde1f357242f8adc52339a5bb2e463dbdd08be54281079e07f52017589c6b7f14a8ac9b5847a24e0cf6f5f140c916a42580f9fa8e147645c23f786611c640e5bc07d4400b8ce1e567b221584d121bbbbbeb0cb2ff8f20171a34d80581a232fc7ac6298172bfe6b1bb9d9fd75e5ac77aa2b103afe182ffb425ff47df505d247f5c4b464b72038823d6e1f2a7d1b81f07d101d6978e81db943c138896ee6dfeca77501e51723229d110439f2bc59dd066ec4069417a2a7c7fdf83f2b47913dfd8d88c71af58e4803c70046f4050b0ccd8f87cabdfa2f3831e4d5832f8f03a2409c3103d9a723affae7a0b172303092fbf640cfc6e07b9328baac4ab1d944a27a47f476d8f726e2a9f9aa4cd8bc68e5b9a20c50497976f1a6cb83f9a0a76792469c935315b2c2643230f6759d4dd7289a3d03c9176a9bbd388f4cf33a4821cfd90dcc72b29010c2d102d63b17818761a8c135a3aefbeeac59511b95c54da22a5de5c74f82b7226aa538e3e52ef5bc488f66a3e62c9d650d2dc7f111f47dfaa915051ec19ab72461d0ea26dba0445340e6c12374dfdc0f43b6fca7b45767aae46884d29a29f690d117419899a55b7b882158c3da0b98b1a4dd77a629da0117e94357762a28172ca4db97c671bc037bdb1aba1f4ba81b9b823d481bd34db713f2f0552b080b96d873df5a322f32d24d85f08b7dc33032391e7420fb4219a641d75e480a5f3226f73fa2e5dd9a0730f4553aff93417dfdd85694a81a5ffaf65f681f6d3e8e3e172bd605e3cda22d877c591528a56d17638f8d272c5673a39c1aae9ed3043d1245457fea5a1397580a9b2f4626e5b4191f14b3e5926e3ededca7db4d55ed8d74a729dbb983b7c03452cd51cd679a51c218f9cddedbd4a2cec3d9f7b7b8dad1464722832bb57e433e7b2df38a3b481b62015c0369ed070f45a02654642e0051dc457e4777efb1d9dfe7d3323b2faf76ffb6a0f6efa5858e33bb5b94980f2b8aaf90a78206982a80452a2b29b2e3603b2b8e34a722f651676c14e49ed2913889d7a3bee057fffb8231e888d68a0772804f02fbe43d05b49a70877d88e67de35ba7f7221ca61c8f9618c62316da693a80f1a44dd1f12e7c54e660730a07d0bde6fd97287591f7173d5f4f691fb04148369b6587a17094e541f6320916a6451f3664f472ba34ab93dbcc4217298c6620dc7a453f93bf16f78a0f13d67487d4850f86172112aa040040bca98e7f754d78a547b759f41386d538899b2b70dc60ddf519b3f50a7323148c69fc1b68758222c55b5c2ada0fed3136d2de6261144684a9aa672d031524547467a2affde2996c62464c6286d549d8a8b14998b1265c6d6bedd72881f30b1c275258f553c044947c79a0220e8a5f5864586e8b885c360b322be0c8163d52e45e390ff8fa3210b619a4f06d73d2adc9707153ec94ec654097a3772ce68fad36dd30164c0bcae27be33a5aa5708e4e66e7f5bffecfd45c7c68e4a725c9cf34ef6af21448c6674a20edd88966861892994da45a1e14ccfb755f74c7021cb06c1d9ddc03cc9547f4402f497cc8b631aa8d5337de62e847255a70a4172e31bcee1a79f627791fdd4446fa13e5562874aa81a85af855d6e38e46c237e72caaf5566a80fea4ad218636832e4bb3539eba1ba32aaf98aac0d52505eba973f4db12a0530fa33dfc9a28c0bd96c3b874999f18d07810ebd4f51c085d64e1172bdaf30cf831d4c25395a0db9fa53495810c1ed1bee61d294eb8283e7ab263c72409a9d5950911e2596d39be67acb60468cc7e5df040b3ce10bdeb3268d0efa0895ffc81e5fc5850f6bc5bc7626ec0a41b7b69a288b2557dea4c0133c658d607212be9ae3a8ef414810542395a0876ea72ed855d350107fefc983b5da11b18209be8d7ee1c9aa60147f9a8cdafdbdfd026e1c61bb902a4b7222d538bbd5c254720e16a3f0d4e73a58bc558f1016641b64cc568ae79458e607728be219231f6f36850345ab99117a07e58b3027ff0f1c08e9a9556b725fa580c3840dfcc72ab572f9ecfeea9cb06c4fc128ec135d3ebb465cd351457de9c1364e9a681f2af77572a49a9c6af3b0957986346853ed3dfe49b489a891f47939b524804ee2225d0972d686bb82a43b7ab455bb305116fbe6f675cc02348b9a33b225cee8617425c372afc2b8df781122999eb282665fbd9c3c36aed71e825f9761d397ec2f42bcd065901aa780cd46486cef77bf1ddadb794d69695a2e4e9e5ab5e4243a327341507265a2f1d667ace738f4a6b822202767728512d21e9db85e3a26b4fe4f1c9f2c3586e0e37387d9122295bd4148d6fa1125c78df4df3bb2c2490a82c9ccc95d297277c28d581f2608f890e84342f6464de8cd640bb66a423f590f8cb990b784113b7535be1b576115a5d2da2d62bd089b962e582867aaf6101f1b65ef3615c4c8586d5a31d51a0b4d8fd8a0303865572aa3a89796b49b567cc0af1849f6f551d07295833c55ddc12edf2606bb5c660cc23a38ceabd153abf98157148865664358721da5fe0f024748102c362e3f3f7be7eca96ea59c1119f38cd3e6b2feebb31e72e06e327959a02cfc5e22cfe06e864b0c84405ddab4666c992b2ededcf032ad72801ef4966c67c29830463287246b844374d6a08bee0f7a3e584245776af0fa18f5ff7b97658ed1dab5bdf76538a0fc5b7acd346bcc0db4ba514907ea274c2455788924121d55e0895241f54224910fad9c19ec25056745405ee4e41214a6da72ba4515e95f0f2109f1e3803932835ce091cd02ad325519e05873fe8c5c79d4728b6cc6b2da72539b7bc0d35c50b38713cfbb431a9ff6373baf990e31ec5b8f30f45509164f6fee738031ec15a51f407e6970ca060fd0f27cbc35967e165db57255727a86776bb229eb73901d13331f19abb605309534353d4d671d1e682b500a1a583222b8e46874c48dccc5d95c7cbfa21a07681b29cf3d4bc75e01c0dd18728d4ca1e60356c9fcd8bdf2bf9b557a9ec2b7465767668bb552897ccb79d40b72e2dfb010a8eff912a3506c27ab0bc6b7c2d920dcac0eb1de979d1beab9493772c63507b4b6f4140269dfee1cdf68b4c079a3b7ee7617c1d14ef473e2187ec7725622fd19ffca407fbeb722922766d017aa12ab36b782abb76329b4ccd1b1a6530b071b916d5c0790ebf64ad748d7b67c27127e005bae793ddc1ff211eb08ab1ff59bc245bfcc0310984a295ccb34031d8c21dfe3f1bd4a2869748a76ab13335b665cdd88464342d98a403e3bbc6111dbdc166dc3d85fc68cf70907d06ba35a729b86d6675ecfc19ed9345df340c40b37f9cde860960c581e50068ec3cda603726e1a216021294eb15357f5effe31935ca0865cb8b78824b14152c964c0e58a72e74c0e42d4ce9ec275e62a6b1dcfb2f2fd189eb63c185ba76f7b99732bf643727d25114ad158fa9a7b604674166e9bde98c9cc62fb34c4eb7f32e59fb074c962c5bafa16dcdf31172192e8f0a06408666a0487dcd65660912311c9d8f76ac007fe943c563dc700764d2a331a2fda9a8ba2bb20251c3423c7ea469d9788507872fc1756b0732f01c59e5d8b588b66bd9f3ba936f21a39766baf66a0b4fe72a52a111a0a750f20bde791cd64b94f0f200c0afef7bd69ba3086194c9fe19015ac7279234a8a79a9dd31e34f39574c989e9d0ccf4745a92959cc2cbd433381dc6a6c8ec2a66a8d669d98f14b600f6bbc34a9e2ce1416e621f8b741b1e859b650a1169c735de23f72f07d9cad54de18c0d445ec7b3e5543dd2c1b4ea30cc4be261313c4168613d4a9928a2b970a8d21e3bc10bce5c1fc6ce78e4c489560b5878d6c721b8fb4cceca58dfb80984b93147db94562d3297276d95f8df3da3167b47060449ce51f9a82322eff47f5c14951426cbd34c8d191817af17fe4e7dc5a2230427284b93510c6452f3d92d6c04532c47ee116fdbcaccdc6d19e1519d74395d9942eca47d7559ae7651a91241be0e549c3a9182d3cef1162479e48d8a33c9eeab0726d1f963bb27a4ca2e45546a0cc9c52de4fe1de4c003d4e1edc52b20f357cc172ca7a7ba524cf0744b7deb4d875bbc9d5e66fa71942fceaaecc46d95068a65272f9bcf8a29b1b2035f65d9755764086f276b1337d623df9abadc7a25003c94c33bc0708cd4d6d0b8f72f92d458f33fa26ac20e70c51d84a0036e7d5eb10e43b72d13fa78be5bbc3ec8b8909dcaa8dd1b7aee15e07163658cf8d0a02a3d3779e18913466e5101d5e57bb9898b50104ced90eb156061d1d8390bd0475618a472b72ee2a2500e05a93c2e89698a0d96b476190512da6047dc3e2ce0543ae6d579772fe953262a5dbf1842120a369a15eef8535d779c29ebd475d8f5220eba5fe3872ae6e4a6d4626ce746f38f70b23b99683970b7237a29bfa9eb3b7455400c2fc72cfe834a3ca0825e0e9426ed3cb80977ebd87e2d8e44ca8ed03930bcaea3c0d2c84bf7f39fec86537bc0c138684fa0fbaabfa6b02e93507b33cd5e55885ff65721ba0b459eb6840dc472954d0bdbb2d11b6549e522af3ae2d186a2d7e77350572f1abe2db00d9575265446bdff8ca9ffe6e58817601a231a7471dac8d4442e56a590fe486bfd7fc67b506bc2c8ed839e982aacb8cbbf97d825ab1684f651fbc727328083e2c3dd0d92f36eb196a11fef3c65decb88fe1f9691d0f2b5e84216737f88cb47429ff203d19430d55550d7cfe7a0edfee102653bdac05fb32542d991df831235f972c93990573c12abcb3a4759cd410ebfc8496cd257ff6426bd6d43a6eb81313bcb402995240abd20f081d6653a8d0d6bde2b2fcf8eb01699e9ed4579f4d24f404afaedcd7d01e00780ae4cb9462232c030ebd92d7f1c6ee87456e72dfea9020e6e755c0ff55b625d18fb95607aa4b1c45ba3bfd3ce8d0488bc4997253d07ec6b17cc565b7bd33e207e999ae20eaea531354c46d2704ac782d2ec672bc292ddad08b745682818829bf382a514113783ffbae4c78f8503f5017f8b34670a89ee3223a69eaa953d6d6f98e79d632a20ca53f4e9e5a0fef9ff4aeabe46633c899b92f6305018e539e0d497ed59c96671386220f4e232d342e0663e14d7282b1ebd521f563ba9a5274bed91d83e8c4517cd88429b624ce952f15a9a4293ec9185622ecd549d10a2ad484f9c8db939ed05268be5da0f503c8a107dd754f721835991f58a48f53307f42306e67fe1dc0925eeab1bf5f38ca3c348b59512f1f591041d2a454c9b8a4039d6beb3bcfc3d4c74e0f847827d90125f328121b0d2db2496032793980d24c1aff321004bd64d12eda345f7da4343e5f2695ae7ab372de6ed2a2ecffdafc8a80f822648df587714a107b5df16bffafb859d92ffe1372dfde0cf68cffcec1d7d262382488eba91649c14c02093a78ebbbcea051ac2572851238b88062804cd9c817f8462c4e753081f3a5c13e4d483a324331582f3c720a5570022722368af32f543907c91fd8f2c42a27d38d2cc8f2b91b00a8e577393e8af32c6c361aea5c52a5abab9a5b1bc3875349cb0ccb35f311075e68665631e87b5c9fd9f684dfccb15fbb0be769c79337195ec9f43470720ff7583b2852723bbecaeb62fc8cdd721750e5b3fb1a290dc8da83751fa81fab90521503d72f5f0c1e219146a3949ec634193f78e26335c61a155fe11900fa270561594b154072074ce9299702196a469f8ea6a647fe7c6c666621d7341a89e96735ddc90c7d42c549201de56e105b70c4f841557ca36ece89916f1ff24219e961ad828d39762ddfd8a9741c7896a9463a9ba57a5ff126b3d8f44abd01a3f24bdbb11c502a1572168d1076ecb7a065b41c2ea65844f4f65b43ce7f7239809ca0a9fa74c84dba72d91a41970aa9e8bb6366b99c9cb72e8fef2c5ea9cd7f6c81c8801d8a6287e9288ad7b9d5719ff37e012c94ed4bac22cad6972d8691e17cf5983b4167e056fa72fbe8fbb28f005175136fb90ba9adbef61c1d8eb56a5362ce0b41f60f08ef6c72df31a82244ebbd0856a5487c2f6a393eee577c014f755adcab0c8552fd0a237257cda5947463949d9e95dbc25053ce78c112ecc4e3f45e31867046a22eb22c663184e1ab845ec66e0457113e04cb0c942f60ec738f9bd4f97f3d881f9c248e1dbf9a8a147a0b9dc2f9ecb16ef7cc111ca4bb4d8bcd92562123a16e8a3a2ddb21a755d1285c28032ce28439bed0bbfd4819c136142d4e2dccf11a5731f2634855aa972378956d473298a1267fecf927b1595953ff697c4b126d0e0fb7b1e3602046e88eed480a5e5a5ab5d1c4c02d2e336225b70c6f8287f2829f7e6e79360472682d4f7a30699a3e45907edca3869a6f310cb075944e9e77c7459fbaf5c17472bd9956e4447ff2fa19b482e4183f163e15d32eb17e6f936aae88a2dcccc4180ef4bb096dc71e77e100a5b479e56a5534dd164d878b1a70bbd9387310b04c2349128a01dc6b15fb208cbd13c485461482bb07ab20c659b5533afbc24942304072e49a3c40d8b5bf1790ed8127f53cab815e9f491028be86bcfd555b72aff3b0203b2d8d3fc624346ffa5ff14735052494e2887a18bf9bfe6fcc1d22d4665dec72e7e31e21b08a23f7fad8614dcaca9d7d0a504b83ecd2d50b947adf6ce578e57274ddaab81614bb5fdb4bc9449640ab581c84f446cbf55ac7a7d4646fa14f2c72352a8bcb123d2fd7fe42846b4c4fc93690678b0ad54585ba22bfe4241f17323401c51b8afd07756b63caff3f04eb5fbfe88091a97af0429faa5f54436d651868e0cab32c8ae4e718478c31946efc8e7dcf9007747b66da7bcbe92d7313f6e131947128f224bac0b1a01b32b12473f63eb38e577d17de31c92750da62ada11a54f4e779a43546a7a571be8aa5f25b7f581345d8bb1c83b183181f3a301f1812721cae5bbd9e7396c8b76cb24f430c938d3c5b325c7689b3f7bd3423225aeb197270fb4cbb52fd4f5e3b4940e543293d4bd19dbb645ba3a3ed3c3d69157838cb72ff8f978b3366f84b548a2effa5ea341347afd71100bd47d56e10ac948c9f19201bda7052684c4f501cdf5971ddd9c3278fcfb7b5f4d174f804f82bc852b98a7210666311a61bbfe159021ad8dc5cbd5db55ae55acf8cb3f0bc0ff9fd2489d02d4ee055d80005faec54f7e4cdecf9631e827dd7e85b18b7f8571f7061742a5c3779bb2c3fb23c493bd2f644b6408150f662ea3afb37c598c7cebc4a6d4998a97202d0c2f208593722ea6cdd9d9e85908fb7c9ebb30feba8d5d0520a6d856ba2723945ca11c1f6e013b05e74853c247579724023e7b3e7c720eb4f9e3ec75c37725d59b87af62cc87d74d2b55cd53fb9b271264efe21d5b212f1835e2ca85072725e82c0c43bef40755c9c40516dea7e556272dee4b2c2be389e11d178e074e23d682e2c97ce06b79f7a46f276c9ad1b60a71ed385ed92933f0c1a0d1a4d383d1109f2e236fb5dfee2936f0152005456ff213c7be4fc4cf09fa12c2aab6c9d977238cfe24dd8df9799cd6cd4207f1d873b590f52042898e48f27b7634f594b5f722b1c2d2a5375d2255a0ab12d72b8021bf9dd98dfc4cd2d3af34c97cb01254072d0675ae0b90f8fb3ce10b6c70507ec263c266f86ef7b475866f8ecb1c89f761329da621a0107be293396af6c3e9a79a9c0158e0d94345c2c980a4791d2e6d472ef508847db3a55fdcd5a6af91da516eaf42fb875590f7e617d9f87eff041a14a152b4cbd082e33e20eceaf983f7743e0ac838a89a64a8e5ad3e0ddce1732e8722cdd1aee2f0c987fb1fa246703338429fcca178dfac87a65818093764c8f6172674c6e9146d076c38b2fcba06a0d42654a3e3d7fe48de70387043946c6b42372981ea595b9a31ffb7fa07f4f47a2a2bde36ef64429b0a276e44068414765a0720d71e6e599828f36adfab13e8e57ea6deb895f820638f959012a1cafc6e6a14411a94935e82f1395760c569ecd42902f01db73548384503704039a32ae16a21a029a6bca882b8775af81b575cfee21764c37e02c28e1502f2e17fa5ede5ee64c4144c4dabfbcaf8e0344cae6d30a68046201f8daaaee297253e6e02446ed4472633910797ff848cca0fbfd1690c08f700c152bd8174c6be7cc70b3bb949a944c4ec180aaedbf67cdfecf3dbc2ef6806be429dd8fae8613eeca5c5f30ff60074e992c137bbbdab10fb66873b6a438249e9bb1c6617af67b774fa13f48b3b4542b75bd63bf5357f176f6c340b0cf0a0f046b4489fbe2dd3fc76be6b62bf09e3672b5ec28a8a362f1c96256b3340e45706800b9b9598163b473af31affcda2efe214c9f83174ac5cdf098a35bd78328990d044a78a44a0812eaa7584526d18dea02fd5bf60a4a8059710dcbb2979c0fcbcbbcf72ef1c614c8bf95a3ebc63196d71516b00dbe0f9f1c15ac2d0a81bc66982340ca7c59ead7537683395ee93063187255ae51e580c6198f6b03d64729eb19af1dc530207a42fe64544690b9f24efc12f8a6d82c00ec2c4f7e6664167942b72390df1bd35ea04447374071d83d61b872854e844c8ca574687149891336a87969e274ffb1c844b438135055975782ba721e402ff0590938a69d42db142beb88ea02aa10f1937b69eabc531b7155accd729ddb344781e67bcdb1e60dcd1a3ed56a287e63d272133069948d72b798e84416c0cc4bbe7e147f9d59be2d71b4c3f694f09a855a7002cc20a96b8cbfc869044e0a9e2b0ab0ef49cd2a0ca2717eb2c75758b771e4c5b0ce6477e701a7bea9fb05bdbc6c1512cdcb61d99d93b4cde29fd3732fe3860f784b4c2a542eddbd4309725e02d3ec0ef81f5c00c231f0bc21c93401d6ad129c5bc056bec726de15479800a810a5adcdbaec596a7e0863587973580c2b41e3b0a602cf361231a553da1f60886e1b526ac5880bc76e5ac4ebc9b3380e32e82c57eae685217d7f0a307c8a59f3b825ee279c4b4b407eba50def0eb171bc2aa694fcb0b0dfbfa8c51cf488f722f5dcfbc02fb1576ef899c4d7f39b77f1a916dbcd827d026238109babd66e2720a6fc7977ac8cede7d5f9acd3a77b70e4beb0e134d2eea94ff3e512ca9eddc59101b1f14e3e9907c118e282603ae6b66035b98314191f05f915786cbee0dbc72ff74a0bc2433dd5aa11d445a7ba201fc7d4b7e604cecd8eca98a492261b23c72ac48f7121cf58ce5a86d7e8d278f53dad21bfa286698eaa3ff58dcb51fd98d45c27d7ee999948e9d54758ad2eb8e3d20170ea22c46a476b21d546b9cb4aa1c629fe7ca6e72e9e7604b37da32597756ca7c906dafe47add30c148d75371a0fa724d71054ee052260789c5d1753cb9a2fed9c08010b35851c8c147c69d3424f17200f2c6c624004dcc29806028701391c14448dba1c5161f4b7ccb52e1fda8836774c631402ced6155ef3ce921ba9a17cfb3fb13019eb2fd91fa46bf10a610ae05d3c1601be8d07c974fa9c285704c2d2e506e153dae6fa8a7051a207685d85312b8d6582b9f9591457d03405b5d4a0115e6d25bb11ccb477334eb5aca97bf84725687794adca88b90594ab310d1cb3f611af623f9edf21e07df652235b2d8df2d5163833a493e1693f12024a32562fd3bf0a0330388c175fc51a082cabdbdf672fe5f1a0c019e735c0254a915a2ed237565826053bc5bd74d2578f0f7442bf072cecbdbabdbb09e289a6006fbb95a53b252d30709805008ec7eb11dc475856672534e7661f837d27284ccb435df142e23fb49cd9da765536925ae69ca15305f4907300802662187f93aaae6dc06f8d7f64f68fba1fc62854ff44c8b04d7405222296db22a5bb9230b0708055fc94542ff3f7e587deb88abf472fdf4775e3056235573d3b5e43f08b17b84dff24cd3137dbfed8558fe5cda7e1053512c2ce5f05a941adcab584038c8b93dc659134111e91596dc8e6b539423fc0a3afa54712e4744ebc35034fb7f64772bf52397af1991074ee2b3ea172e9965779f6ce41c5b189c1a41569002fb80477661732795943f2aac8ceef8fd45c5cbe91e47293aba72e4a50acb197f8a6618803ba1e42b119488a0c887450d1c2134270033899b91721b2480f7f2cf0dd91c6cba668760a6f9a005e5fc81f6c2cd69038e025c33c0725339c743da589afaf3ffbb6ee644247997321ae0531a8151b8c7d685aecdca72264bdae635c40aa0427403a6eb3a9cdfc1374414fc63f2124e4b7a98c5688c72ca3fffa8a123518cedd760fa017b370a6b3c8aa7708c176e85c15243486b6a723182953c3ce32b895209f55a500c91066de4f1ae42f2edccdaa1c0907bfdd372d902fd65a34cabb1e1d248e4bdf16d8ff6a5088192e1b456eef001f30c471c725826f27fad6c240ffe313b776e98bcb202864f69b32e6b8a163fc873765ddb57ec9ff614ba2d64a94c633c95c1ff50215f4d4b117ea5feb5ee3ec2b8ec5bc572bec4283404040c41de990f47c6baa53137f12116d7a1ca109001a0b30a942272be273d8d7cd2cdcdcde2f1fc8f8e8922dc4c448d39482f7d06f3321f99e3232b845c8b5f47164b6ee1ebc0784dd2294bf0dec58ff9bbd6a6951a89f63b1ee4725e6829a18d275fb77b808d10b0107bfe0c7cad3c1b0e56866cee80a2d9e3094f22fe2d407e42e0ca9681ec39ae69825137fcd73bedb772975e0075c6195d450b5648b8a63f5e015efdc320b238318f00251d36b4551251218510661d51cf3572aa7b08cc1868e436fe8bbc2c582b9c78f3861c55ae3242922082259b869c8a72e6714fbab1f3221ca392b73a0e5ee4af3b9c305ff17ec4a8c7673e7bfe3733726a1b54851b3d7b20e64b55e01777142e5e3f7919f955476ab1638bce609d00143e1547406366fcdb0707a38b3abda197bcc44a6dc91d8b355c2180532b62ac72ddb199ce016a80a44bff86ded2fc03adaff7c14d17b05122b7ff1716b9a72c722494575832e4ad60fa74b7e5030e47478ae52c39e475fe57ff3644f5c04fc07288d9f7fb8c2cb15e3ff781e387f65da2b6dfd4399331781072d3c7e2be49a572fb68ea225731b75f9fba5a9a589318764cb746da456cf4b405f4f4f952b0133a3c47e5144e722da24d6e75159d546baa7162f9f8af2957b03393444fed01a272e657ddc7d03736de23258b5c2b07dbb8f2a9c82bc301d0bf9cbd7b36558eab724b4ab54c3779b05477e1cfba79c15dfa40ec45da48223f536129c5e7cd53c8080aa431dc9732311cf9c154139f3c9eafb8c69d59b0cbc0e179e2d521ba572969dd4f8ca998da0004c9abb0db01bd7380c4cf409b9dda4de252f545a2c65b9f720273343ca5f1a81343b7f4a5163ffe5e8f620002b155fb4868b00d5944a4c61c7778f69be37f44d646a7d7032ac18dc79a4f4c241fbde75e00ae4e12199c0172bcb89018d13b37e70c2e7fc5dd016e4aa3b9e2ea616d75d2b22038359e352c0514b1ef716e7bc10b44a84ce177da151ac7e9df478d9e06078395e04e0672cd72128baece4ab096333832b28186085e57e001a9d8e207e49e7d5332849bdfd94e0510b128855bc5f7234a9bb912fbbb9f2c151650b8d87042bc745a594da90f50e1df844d1f02e00f5eb8ae382eb6e412e92d6ce2ec004251762879c48807af72e0410cbba2de069a7d512daf7a8d19d25e93062f7a7fdc75ac623bccb8d4ee25cf8baeff307283dcd295f2e149e2f5ed495aaa2796ba675a97910363fb6c047210ca274369fcc002152424a189abff46496e422a9d4d1114d3b89d3815f84f72760def8df49a7a2532321e26c7d29f60d5f416aafb19392b3caa9d4a3fa89a44142b0da5e6681d30656d78e0f2e9b3c685e951a7b66c8daca8d1fbc7d4761817acdbd108a9e280b3e487fc320bf407575dcb75a1b9f8ac3536435dff4d13ae37fba436ce1b11387dcdfe8f16ba20045aeba8529260ff6709f4e41eaa56a27b5f036387c553449a2b52573597401129e74f036389c9ad71f8ff762d5d9fd29472ef7b05f06d20964964537eab44b6349ab89ed5085cb71c7ad9ee13acda4ab3725626bb4b878ab4e71c46577e04865cf0d8ae343eaad415ec9086dfd15258594c31d5832f4016051d986aea05aeb7eec93d1e113130e1847b9a91baed78a0b672f6a678e32d03119565ed640a79dd6c7eec55cd2aeb7963b9cd9d5c8a1a35d347a8419cf04792e55d04f5e60802f5ecb75d28f136df72ce1e3c2c3e2e9fe27e72049cddce546a25bd44244d7fd4591da5e2d08d82fe0bac8b8eabace9d06a6f6f09d3161af44e226e635cb66626abe2a38977f17878d660acfa9b8abc7d74263563bcad70a23765054d8ff0a08f180374eed88c1100fe21833ade35a1b3af4472241ab7ea7f661454647de30cd29af4e6e4de95477dcd357fd904346073e9764033d30f8e2379aeda76c0112f72a5d48323e866e615e13bf3ec4cdd73f1bbf7466633ceeb93a4bf29dda67a7f99ec9b1d3348cdc3cda0d0c06459ecf3a23aec720bc113455aa5439f05bd582626527a4629f285ffc676914946cfcbae1a437f7228cc7f67bfa131346859cd438d00a2256a3492eab9ed17c5324fd8571f13046283d36e5f568e86f28c191125c57bc1825d9760085620bd9d12133816df700872101caa6ea80ce6f9050379f22b5ca2c6e5203c079ba8bfa572616fdd659eee4c86688c389f8e982e23121c83b0473b28dc8c90f9210aa7d856f20093ea6c0850cd18859a50b4d969a688992a8a04f13655ebf16931e439732df0ef0f81edf372d1528e1b6825df618426df0c3a258e7a7297826259f42bc41cd3d044f6e588723f09c59b2bb17be125383b44fa6b38ea34a4a70b7ab1465da1f2399c07b9c072d2f9196b5aa5316c1f78ed97314f8c203b1057ebf22a5c8fa468ba984e54ac725e77062fead00d73fba8ff78ab9012d7c820326baf7fbc90913cdb8975541f72b477d5ac849424043dcdc79b2ec3ff1e8855fc466fb9ed37f17afdd39173ec72f192f73109c635598933fbbea523642f9f35b86b6638a61101583bc8f5d441721cb542535275ab1b82d8c0454f4f0a3a94536a32dd49277ad2625d6ba7fcf5728997cdbbdbf57ab319ae3bb5159884e3edda9635f16beff2d7ceb79c61fd7c2fc4321e4d6844b3d56388a93339edfccf452e2acefa33da5ad8de266c4b604b727d72f38b84af6ce272cf1134e342673fafe22ea102b9d35953b07ecf53bc412c45398ca3438ff67e3765602112ceb9d65b5f306cb44cf4d5505fb338a00b604f37dc0635bb1b6d318ff2c975e4374e604c759ed830561f843ea755a296aa15723dbb04ccf1706b86e2655e0d4598990f4d69fddcb72c63dc09d438e9ac991572e6f69b4a253328cf2574beee579faa4674e5dcc173462558ddb381c865158b6251ee98912121c599247a51bd3c16c727120a4be57140b68bd829ec41abb0ab727cb1e2a5395b0b44c6df4e4c03c04a73aa343059c93df13a1bd614083def5c374e271b4aa65b8277b9920fbc6e8765ea83e7db1264e7cf58ac666cf5d84a365b5ca30118f6c331f112f43a4bcdf264fbe0ccfd28a0f3be253d3fc97980a2f972680018f2f3f8a2fe8a5e3353474d70496933e24bd4465bf46ac4014ee070dd72e7ff69c46eaeb2686971e0aa83acfb61ce482d44c29889de34223624563c807271a74eb93b1b2ca32436b4614df7eea76719efcb90db245739b65b03f49cad724e45f9417a34d5f3afcbc8e66baa7cc19b199aab268e54db8b96f78894efa65fd9f96ab5f19f09c5dbb70460bd9e882da7400a081387b08b670ed31f547a546adb8d35c8d5427141930210d8f9a6de0cb8239dd62b74173aa18a922b3657d9727f9c3df1882957bf618784a036ce3454335fd7b8fd89d913c86342b4c50b9e6f4d106f37c8f13ecbb9dcff30ca0f06136b86af3ebe6c2e7936a59ddfb1831a72b195f978595e9a73c4b2c91e986c01e442467bb7b33e42837ce3b6a8957d2372e29e05a574bb2e131f9c5c088644ac12e4f3c2763c2aa45f406de1fd59893972e08ca5dd49ac07a374bad51ef43acb82bf12734b06a55aaced15d14342e3a172b13d63ed4f453f8eff1631ea2ddd6018a68a1af8774b75b09738081bee720923d65e120a16d4f2c825c5d2f9430659ade4a97bb25b4428af03c47afa11b71572b21991828f3c975a0380a207cab3e26ce4580e07fe15bd9e2ab233a4eb9f6c72bc11af8e6cbfa21acf8721b1169fecd3c980d01bc3492bf20fa16f60f75f6572cfb687cc89d56fb4ca32e674442a0dbadf0d827fb32c138e43347512b4854a5bac615d07f667269f4d6527448841c2c21aa83bed6b76c2bbf4e39edd2de7286864180f5d8404899b8ff57aabd76dea681e9205c04330f30ed603c14537ab7a679129129fd9f8b06336203a5801cd0d628e7b7db0460425896da4fdc95714e472501166d0acec1fa156dbd73dfaa1479cbfbb5f404bffc7d003956d2da95e3f72488618ca3e7efa6743ba06bb56cfc9e30551487bd8371c698e18816693fd9f72a1b60486addffeb873656207cc905f8ad950decbd3d0a274e732cbe9d4a9622e610d9354f6c72e7d969ae866a371f3a650cd244cf0cc515ed224a355b8d0801e8b822e2839113f779b8845288298e73e0ae93d550b3da3ef9c0b2c0c960b9c72a093f5049b04d4ce4a45ed98f9c08abab7897a0ae1bdeaae34485794fc49fa72940b2d00fde623ce0781235d227568a101567a7911f21fd6dd14579ef0a3a6727247e517d1a28bb403352e7aa6d822390cfec48249f1609c42d1bd04bd48ab53cf47ac929a5b4555cacf6f138bc0d839907330b42ab8a2bda509a8d4a4ecce72bb4baa46ce74b25aedbc0ecd840647baf3aa1d805be74f92e03d7b7cceef3f1449ff69e25186023524d5ecfec276c29fe9597acbec4f850082ac42e78af75156c4b36ddf9c38b534b081752ea4262e71ce15150bf00e65dfa19397831ab1bc72943925c90dceeca02d993e4cb004b543d20390ccc28c426acedaf3ef4b26e372e51ea391a49d4bfcb4e9d58a3392681d3c0063df735a9a47f5cfc8eedfe73666609e9def1f005d91ac015b6d65ed48df166446d92697daa428d4e539dbd5944bdaa011671eb7de190dde39945271445e0bd2244f662e536ad677af9d92fe8b727e4f5010124f340719c88b41085b36934008aec3285a25f90171da2fb772cc727280018257e7d8084042377ef854ec7831015160d9cfaee45450624405c79d72e2ad615a11d11a5ab975727b8329aa4ce0a4ce141f1d591b984548f9ca240b72646bb94e9c6362732a8532d0f2e74d82dd34164876638dd2562699a388afcd723cf4bdd6e7f68e3aafd23874d051e287efb5ec84f0dfd1bbea273ae52284462c2cbddb712d79ff76de66a7b1f0ead1415863930bff1003c705ac61e369af563f22968ad08ffab95c38911b405464c3d073bd661f0fdbbcdadd8226f32ab18f72b83ee435d179087dda6a40fc550084a5715753963743d1bf572c2c78c6b26572a320e3cae2a5c1755130c89b8d78465ae700818457aada9258e983ab9a965c6bbd87b8c324aa2ecaf84d5c969e7c8a966570868bc42e7f2f4362448c76a4ab0726fb32120e9cf74e24d94bea90e9fc4a86ee851ec9d7d576c2ec693c387724723172d3dd35f18adbf20776e549eb17565069504002ca6f716bceb0e5cb99087258ee2ec65f07718c1c806604fee78d62245f90825031b0c79035c9cffb92657235f8db11bbaa64423f0b676b7077ee9725da8ef3dc169e234dcce98c934c0572950592e0a49f8de21131b83af233bd8bdff91aae1eff9f2503b2d75800f62d72449ffb0d9f4ae71432ca6cc01c818e89adcd662080e6e4433383b33f53d6c372ba34a391ec55199817426fee4d7becd63ec36a6c7805c491af4e4c3bcad0e62177f942ea35554ac32e60d91e5cdd7f6d6240e5bffe0cb99fe1791b55ae71ef7240402e50846b818ba8e97199baa77034e6faae47a1285df2b0111acaca8adc72088350e5792dc6c40455d4284b8a22ddd7c32fc3a47d29afc47b01d55d162e72fad42ea1bae39f26061f93788512901a5e8be289619b3d0613d2df6c818b6503ce8d5664683b8bed7ad8ec82b2664e2b92f9480f0d37a5d8ff8cab74b3290771c70c738bf70e80ae0861f86a055964fa762d18f54b9b2a1fdb9d2456560a3072d408fff5eb7c9775fb7604552520c69cc8e44e23c0fb8fc555e109237b43e972f50caa39efbec0b136c688da443b9e5d1e516d2aece8add292f391db5927077229d1914596330f49070fbf104a49277849137f566628533cf0d566156efd0f4d4bc544ab7c1de5fc7858e955a9098b2eea9d5f711d243f1d3f65373c4fe1c472bddf6f7527c4d4b746de7981967eb9747f202a011f6ee00d15ddc6c47b656b503c2c4ba2d20bc8a29cebb4033cf7f39465cdd7081cba0133907a452f5d0611722faea3f291a9d219a74acc712cde757d30985e60fc8f776a38df4344fcce006e2aa401b7c6f2afc05ec31bdb9ca9522516a06583f22e17d438461f284aeca072a9886b176fa96617aad0e7cc3757d054eb92c2a9551af30004f30ed6d57b1872d227a916eeac048f38c006d25d2060985b946d78b0a93fbe03fc319459cf0c1f3a47566d7a6fdfc30ca21f5da1c1cb2d37b4ca92ffa54c696a7e18aa7a1b9662fc29f53461bf246ad164570b988288c32f098afbcd2fe0f2e84003d1c7d7d8725a207bc67b83559f71f452e67576a495be908c2fd3f8d367840a2ff137997b632016eee6d07be9d97bdad77dc83ce1c0ba3680068b2cea443fab9d5bddbfe4726bae07ada222013ac759868f9ac59074409640ed7f0c79daf81308d4994d067253aac95b566ccde15262248e5fe55569a6ad7b43f9a5373bcf8949480228cc70fed008e67599b375aeae67a9466a47fb8e6317c87822723c118decda991ae872262e75814c85a083dfd3e62c2604ba6d5dcd0d7bca8ea4cd39b23ee7ff8fc772933aa6fd13527ae005563120be39d7cac97119feec7384f0e791fbf781b9c17256fd560aec73d14311eaf858bdbfdf000d7c0623fb681fdd22093848a020195c838d1d62add8c888513bcd019ef525aad5668e2fd661d1a5652efd0fcfbe28729b86bf88de0c5a45168c1b607e6ea1b438331fd80113b7476fbd77598bc5cc727aa0dddddde80900066dd657b7d1ac460eb7301d5d51e387d2911560cb76ad7282acf01f2dfe2609e26fe2bf49790037350000c3669fac1483b3f705f69d240b3a6a2d227d94979cb17a6719f6f5dd18315145b780e9c1ceeadce7ec1667657229de4146d413f91dea4447f5143271492017e56e2ff704f40f755316f781666fe3385c86c69e42fb9cb44328b52b005e1867611b646e60026bf9fed973e65e723ab8345db26ae7d430b18cff1baf05dea722edc4291e1ec721eb8bf19b3281727de41cacc1b301f9980935772d8c5db9d4f998bbfdff8cd4e34d472342755672fdc8ca0cdbb1c07dab9356529784c714ea4aa873fa215210743fe3564d1c5d722bdffc6d1e747bbad0430599864ee16fe8f202ba1ef55090a8327729a8fecb72d12a7dc20c560b6a95f29fcd217642dcd9e638ac5e92f0311c81be2d912e55723499aa41965cebf00e2cbbbf93edad210f61761326276819cad726d32d5436722f70ab3f65ea686bf7825c2e9fd37d4131e03eaba14b54fc8f20db8e4063c57215e484146209566c87d0eb2a2f4df4ff08e71ce2740bc1a10e07c467c6e79272c2411ad1dc93d4b952c608741c6d959d81120133abe10a033e9a464ec3fb54721dbdb4f15b6297934645e35329f149aff01bb8b61d9794f153bb3543ffc02c72b83db67532ffc3a47b6bebf345db939855cbf37c89b40e8762ffaeb7350a866b851695a668e60c335400afd4c283d9039a90e5b59e70852a077fc06f09677c7223bb6ec418934f24a6d2f51443a126b13e91b60d43b8724c16de70bdbf87750612f306c4ba515929c96c74bad8634bb9cc7c91367944dc41fb839c0f76ce4972d22943b99109eb335f2ebad0bd32f2bc1bc48191c12702da3a5319c71210ca72566bcaa0c47a7a34abd60012f5084ed53d7b9e75ea232155a4586f88b1076a7245f177496697f337bd196b6b0db5a26c9bcfebcf1dded1e328f45d168e4237728015c77d4b2e9d6d20a798422c5c6319a773305fd4c7bc6fcf3ef9521aff37725cda481310303c707d20248b48f7d596f4a5bb756178d1f1ac210bac372750722e8132253bad2b4895e60948b269fff619dd8d4c76f58f775f49d51bfc8b747208d38ebe55bea9a7723ad8b985192db202c49ed507e6cbc9eb7c3ac9941d03721bfb1823b80c99a4b6d71c0fff685f146dd6207dd5353652954f619fbd97a9225c9f3baf52dc408566bcb6ba09236aae50f85aa1cc0aec0c58aa10cf8edf7d5f50f0e50173a1d43fea286108c6836a450768a51240d7eccc60393e1f58e3e25d98c2dd83c62530e2d55882fca8eb9f7456fdd0aecafaa66c3119bb2b17f1dd69e5d2c73bc5ae566073b564db502fb29b9c9d4b20903e83a97211a354e5fcba6c8a2b8aa9abc6e40bffac6f3dd0f82bd6ae4c20d2a8f11961dbb13d0efd169372550eb08be911495f0ddefd769c4bc8bb91224a858a760ab0ee5061fd8ab31f723324f776ca6391dc2e1afe303dcf4aae5f8843ee6c84c5a84311eb9f411dfe670ff35c1932bb345b9835d2a218be28bd58584b475de4c1e2ca285d773948c872a4d6928c041dc69bed07ecf3fe79c4915a566a925f99330bf8f158c7298dfa572d5921621955f167aafc90d5d101a5c95b29d52da0022d303695246e3fcad82e90dcea3351814f84477886510b580cf36e02ba92cfd78bec13e2537826959b7223ad491dca5b47468ded580ee7b31c817cddcca95f283765026d822f936fca16f0803be10c38c87426e77bae9de9a82c8ab312402670466c6d6b5a80d119bf38f8fee7c065d2c9e7ba386a7551097947eb4c3080676afb36b40efb4e5d64e2225367772fd99435fcf553089331e42019a810489f5609b157aaab42c170fe34725d6e010a87f263af9575e5415c4831f2352bd8d4671ebb552b267d6e777814059671a28b98cc63fb32edd0dd6d00fd983ec673f5ba2295ad94c564da04a591546b0177941e36ed6e7ff02731bd2695be2c429c54c3781766edd4bbba936a74726edd74d963c36fc7e28aeb95ee11c1c1c6b29a32ecf0d91b36bb956a53f092720088e8ff74466b50c0c4ba80ebc641cf6cf071e6a710f3d3acceff2ff2a2b60d0c4af72f58b8b7ac6ef89f65a06c4813fc5f8d059c0e4e7893bcf0419c438b72e1ff18d8a8cfb190ad2c7011ff41d68f37f5615c0cb60b4bb65d2dd731b842722b5ff32fb6ad39aa03c9a15042dc1b544b7f8bb840bdc64af86fc466f11d2b12bd404a225cd7f573d0f1b59d25354ba3ed3114a9b6649df3c972367842b90972ab3be36019376fe8af0eae1e36ef1e2c93bee4307adcee92cab7ae037cfb4172c2a57d48b107e20f5ac2fbdb03677aa9359c684715be22a976b764f9920c6555abf78f3408243512da4f1379fd69b04343d309bde80dab35c2e48e11f551940d309ef6bffb2afdb76969de56cc6456a6bede0204ed68ae6d3c1cdd95eef803729f61c748cee1442f50908dafe42edcc639fd3265d78e52dd5fe95a253bf3e37222d007730059ab792026827b7a339b07027c9493a695447b49b9bb6abcf171723fb96f6a9c4f986f77222d993a9146909374cf0c3fdaa88cb0f80ac3a8514c5ca7e9bf9d2c28afe62b06951452da6223ff4bc5de97cc4779c9ad6136e456a23abafa463cef570ccd72b2277ea3773d94c30b0400bd6854d6c7245ae8aeecc67282aa8c8913734a6357ee68f8aedb207d5014d6aad98c6154cffff749578227331521c995d08f7d0fa2a8396c0822cfb8ac40e3318c4b5121c31f1c34914aa05b966a8e7b833d95695d2ebda11e6e02ad304eb745e124318f811adf85e82e4572377877f53e709f2660f219f62205dee8c0c5c0864ee550f309d9985363f59472167ea397f3cc77780dad8db00fe0616b9bd1f860d2e28bcd7219f95398579307d98bce30be64db5b317e27d232890a5ec3acc6e77eb186060bb1e658542bef72675fe48704310672c1ae5b6eef75e0c4bbdd792bb38833b8d73b1a5859c50e72f06a5564e648624c422a2b3cc6cf6a31bf7f4cee2e9669b984f366625a18e645b717687a970544f21d8b48897489cf2b7ee946a2290fc6eb3e770b9fb665594da3a13717292d8b1a6b985306926f6933d52d1400188576742424aeef13da07410913839311a6908f04987db516e131a89b1cec4d265ae1b7d8ce157cf57fcd1827084789a07bd6e66768b3487313fc1ccb469c1cc7e166d10a4f8921a80ff0729a1120795c6c3c1472b5b2905f6616469d7237719de9a49b296c53c486be23667e4f1b4db7c93c4a1e17cb434ea6ea85e6b8d19a3e1fe07ba208e3b5e538d3726519aa67944af989b1c8e961349a1de006dcb2d4b547248b01d229bb40597165daeabe87b745b66a767fecc670fd0a915467a8cf26f01fafd299ec78709dd071e7e74e10036ead9b265fc0d0e4cdd78a0e807c860e0359c73bc3a8851323f63dd3092384661ce5cf6ee7bff89587c70af4d70effa5c33472d229ced6c0a153723ee77b4def0fe6202fbff27b9a0afcb6e894382331f660375c1a7155cda41d0f73204c5ddfb13438da1c0a089bab57a0e1ff69c3bd6db462bf76fd1274443500e26026f29db9897862097b9e2b8116e066380799b9d3183a8aca24df3e62426feae6f79f8bfd9eefb3894fb1eeffae3cd04a34235950b50f6b07f6e879e73d722a6f8ef2acc8958b0a9640feb84853277d64b6c547926a871d039f8ebd5b546fd617f8894427e6e7dad72c0db12aa4cebd69b6982f44c93b7551fd5eb34a9a7256b137d8cbd39fa62003217c077926f199d255ff937a2a576b63b1b559b0e972d884224fe681b7ff68ed77dd0861945c146240f391dcfbac694669e1c10d7a72741d2dba17fa73f80436a1800c5af8bd3dcb084d9d3ffd154514749670d281497b447e2c5d8d719c59fa60ee2c742ee2f0099fdf3c975defc4ba5aa3f9ee46721831cf2e3e9a6cdb58cfd10b84577cdbbd6b64ad299204cd6146faf2beb1ff3f33aaa50975ff0f74a5129b9f7ab4f0f57ab3edb3e4d68eac77ea5a634c90a672a50c35c4ae5043cca9e7bf118d08cda5c802801ad6b001ae063e2e9e7400777275d9ef2b72a5498b6b82bbacf6fc6bb17f09d01a60dca950fe98d9f43af9e92b13337337618483355ba61a3bf69ee5f1ac6a3e98d883f795e52570d0e54a777137ecd1b7de1911d8ca3bb1b3ff71e7beb0b05f1fea26e35c4b5b9b872810d81153810127c2c1f1c8a79903e89fe28077f0be8b66ab71c4b46be8283ffeb8ef3ccd3be580e8a49d6d4b85b6e59ae419dc65ca1ba61aefdea7803c59ae04456d727df1b3beff25de3fa427f4c59632ee06770b87145daa73cbbac5de03f5416f72964e9d7253df44ab452292bbfdd4631f26302f5a4591bbbc108c5610beb76972820de9ca1b1680d9c796c655b1d10895557a2d1f6c877af9f03f3ffc3c040c4e4a0c1b1fb22ab5cbebc4fb8e8ada81c1613f812be1405b235b84c092a3964e729d719357cb40db59f738af27fb4bd59ab4ed8ae8a48e638a6a6cbfbb73d34c72ac3f61aca4cdbbb1f356f5b0a9b05e37ecb664288245455de1c4c27ee7ecf37213b273eed3cd8af6eeedf89e8942f132a55a6641320236a48c45c191daa0087230cb004660cf775946d2444001f83083285136ba132104def136a2e5cf6d0f2d32ce6598f5f420c556b2ff3a4014a2065f4ba3527202df99c67f5ffef67e3972126a312af74797021d25f06341cd56161b9f5853e1c9b09841b95a1f24946472152939f87e6c7eb451ea436ed20d6e21bdac0ac3b730129138c59f5e73834846f6464602762d503d907b3f40f36683caaff1e95f2ab4e64cc493bcf33668c672839551766e68affa6ccee2d3534dc6b611c737eab39f34ffff61eff44510c372b54fe77fdea7c3ef0403f89dbfa3ce4f04b8c8206c26ba8fc3d4cf29ecf85f7214a768b0e8d785bc54aabff445b92bb8f648ff11f22447e480fc0d6e3c7f037251bbd094fad83cecdb67ed59e77afc82c7194eaaaa14cfaa81ef0f617aa00472710aa9b7c7d65de98adb31550a57bd0dea8b39f3c0c71b3cb62342360560607241f203e552337d8973bfa7dbd0a1ddc3ffd8a8279ae338fa38ffe749aa8f5872a950f913c082fe6a7c9a46b5ca10314c14b16dece924350bc6f5c0f0685e27722ae731634a188ca25d7773a5fb9ddb6cd9defb8eec6cb3636ab9bd68c55001727d5a9a09e2f282847066e2a36eee4641e670cadee0f925a2460676580026b83a1a03a8a95cd5e341c910d530836887c0287be947a64691b82634a833c2e4ea7242ead4d24fceb682e6daae9bf92a5e8c48c718f49fd8ab4d2fe2cb352a6746723e3f0cfebbd6303ec1fc7c0ff2ea76fd48941e097be3bde617988cc20875436327d19c91debc4aaf05c45218682e92c30e471fdb0fbe99716583bf7d6c4ae272d39db8783eacc2b602fdc34b134bbed4d2f71a4581377672b5b903671d95a87289dc2311f5445babf28bb89afa0ed83f3389b32b6bce4c32b4df5498b33fc638868a806dc35346c0fde10cde98066a659c5d1fa78a701526c0f270f37ff3d37231bb8b8b7958e86d664b4d46108aabafc160bbc77382d86a2bd82fd643918e72d4128e9b26a05b4ba468d3fb45772371698c2fc9f3b654e52696de1fadb7b472a314657d72fd09a48e484f819e9db1efefe4ab306ade6c94a1c2a2f404791c02cb4f8619f8e2d11fae55baeb699851de9d5bbe51a2ee524d8a0cb7924f933872260a400c810d1fe65f93456023969012c53fadae4a0980bc199f9bdfecc08e6a23096dc29bd1a3f5a43b0bbe785c03fd9e50c0ebdbf2cca23d86047f4be9b972e3f3c70e7542ebd0687ae08a39241993904c695f74556f127d51d4d4b34ae03d05c5d12e6724fb9411654afb60fcf099b51cca4c97f74495cf2e7c58d2255a2f15399e80b07c91c535c787c33193b63a35e736c68174ce672fd705c38f281e7286097f819931d66683c9da85b25a74920aa7b2c222cf530f2f1cbf75931707720439f8a9044edf88a052a399c5b6124796d9b005f0a813f08ebff52a1e2e23092f84b7d79025a3aaea8019c098be123a93ac9a51b6b8471e15bcb4a9bd815072d5d80dcb9d9b1b1ff3fe2087ed8966839b612f21d48a94819e7bc377f247df67e658c2414b236dc7f3a75de8888113b0fdba6680b1885d9313571a9e82ae261a27660d51bf6ea8d2183afb72d653031f481613e8a959769fd7a10644500c025d5aa10eed8b4edb9d70e51c999c03981a32aff1c6987cbe78b42aeffb183112364479fcdf921fe32b4dc349b06da20c8d70be9f7dd9d4356d171fe0746a379d72d9e78e80f79b400dfec9ca57ddae4d23296db28d0817b49ce270234fb2504a72c75196247baacf689f431dec9f8fb6d4d8df3df26076310a3f01e65ceea75872d8e4a6155162ffc029ab2e9c253fae083eb2063e4dc9ea0ac8915325510bcc47de5816df4bf6ce19264c82dc03d1c114f3182a554be6c91c38a9588a360997723c0b8d3333950b5b761432feb659302cedf81de350736750cfffe3990f7b117207c779f8a57770158208fc48f7e07978e7bda9da3fbc20cc452f0c061920f6729a7842c049428993f0d85ad3ffb9a1eaaf39c4acd13bd667a1c99bd9ceed3872ac1e83aeb488d682b478db5c5489278c0502f15d7878eda5386a5e12e8039762a316c39109f211bae7e2cdddff222091fe564492ab0a7f9890cd2ac3ad073072d9481343f3778a0a02933370150c6d5e722554174e83865c47e3c21e36874772bcc14a9d68ae98d6c01490b09cb6ee3f47899d7063dbf6e5640e6afc82a40214f99afa141df1a49f6a86fc480a5dd6200dedf29a1588e718927c6dad2bceb452a6faa5b6ee5c468dac8370a1270ee66671b41f8208ff6358498487f0aeb3fc72d57ae9034068374653c7c0495693a97ed70a87425787bef72fb20c8fbd2d467233bfcf51d873dfab90bfa8468f0eaf64e7fb843a39f1f56bdc88502c5304c172b20dd8a1602734431cb89a9a5447ae362946962dc0969f47079399e1d0098f7211920f850ccb7244b1eaba055e4dace0890b9187e2e246bd289946d557a08d724e844e3472938b6e9afa738e333e778240365f18f2cbcbcbe0dd00354f698a17f64a92fcf811bacd10209d1c21ec9cf9505120214831b02c72af5abe55a007566924e63398ac0662bd10c81e4099490c9dc164a34102ffa316ceecfd2d54492cc78b9d42463345cf00b099c02af813d31915e282bf07f37dc2f69efc28ad5f3c6ae18c5bc64fcf89b9c8e0ce3c3d00b74deaa6c976dd1d4927f48720519df614eb4e69d02890cf045e6e965a4ae46faf735c6ec822de9026fe14d9d1338745306b7b0c8070e662937311f59cc86d08b62d4f24db5e2d0593ff1be882f5cc35375c26992e1c7b7cb24333001c80d55a1f787161801a033529110e8a2582ff7933ad9025bb981ca735250167a12946c5705185966543bd2e32b5f33b5ffd628a540b62ccc497ea6c58479ab66714f10f94a54229fe716c3ee6790d1c4293fc2f7240648702605474add1365184925c8d5190603dd5cb31bc358b97cfe79bf4533afd2050601611626b8cc13a593df549eca4a382624b668d91bcff4da166d2437201ec3d8db5c72aa47c8aaf5a8ca90e0328ec0caacd8e49f77eef592c6079bb7238b1d2d81d216616e99a3e1b6b27880c32b28340e34d1ff163bbe0676ce92952dcb365839f30065f1397d6d316557749999fc0211ea7c96b11551ec8cb9b1a726f6d06f5c0340639de29c4534ffb027eca62152a5acbaed3f09d667904474d7286eada3dce70fb34779465195d12a67eb6a8b22cebc070d46c599285fbee937284dc2945a5921f23122507a08cdb761122aa00acf2a85cbe9b3cf5fcf60efb726dbae280a22f00c5b7b273d73c215b15f01544cc1d3d17a99335806377898372d08912a38f19113f89ced5e71dc66ba59946ce4d7a4085b852d3fa7c92c76272fd67e498db64dd538820d58cb0f6dab8a30c18b3f4dd4c3e1e3b039097546672f22ae5e1a9fc91ad65cbcbf2ee56d60db41e4a85ac6d2de1a7182217ce4931ba0200000db5549dd341d8a11a21a653574f960b92e8bef366b66b340cddda33fc9dad14723241d88d1a31669db74f926f37e771fceb9a8222616f76d43a258d2fff810133460ec230b1792f2163194b9bd15620b17d91112dc2c70b738c4044ce6e766172dcce6135e268306e26c93f6f2001a8164599a895f8c333c72ec4b738d214ef7268b54ee5790f8c54df10788f92aec3b019df6a192ceda5d4e13c1196ff2b422e0dff4298465beb07d94060aa00804ff6905ed2452e35d3972d4fb618b3b5cf72d94b1afcbb6a38749340ae40690b9f0a701943593978501f38a395e027d1d97245b594712afd50371dd6fa8ab9471107eb8989e1ce85a88ce3e2dec6d807c372ba50f8522ea7406f247f1d8b938c17063ddf840551db689c2b8d8838e5e222723a59e6b3379f021c257c6e1de954e7697ee2eda7984582ad64f7e359da0de77246a1796b474e03ff56f3e0cb73536ba99e5460f35528bada13fc52364be0a372af114ca7e89332df3be94ee5f3926673e77e8fd64b7dd29489d66e42d7454a12f4a5a63b5ae5ad3d831ad10588ef40493c4f43e37b509b182c94aa01b61c6872c7056c8ff7d4c2affbde805969745fc1a7624cfc3deb3e7531721468289f1772fc38788da34645b626d29a1aada84eaf24bec4eef294facb1900b9c2b218c772a5342ada8574c445d81f827c07769a63351759ea51bc80fe33b2a31981aed4727d44f67bbd596fe5800f76d86f13a506e7b6db20e5f32268c5381eb005eff0720d2de2736b59adc46a5c3e8f53fc8a9c18937662136aaa5274a4f756f09f9772b1929b235dfb9ded22985c83d130599394abf898e7b6327f54c622b16b8bcc72235afb289fdd4022f3ffb81e7ade637fb07c5617087c708c1cdff54e2b7aee4ef2dfbeed6d65b1a4e81f9b6f37d622b1cd3cc6aecdd0063e099f6b2d58ac12726369460bfa9b4204ccfd7b119f54316d75d27a5d06c540b60bfe6ac52d2b3c248a2bf2d8a06a818aedcb26abdd022d3b65e1f5d9f88dc05e240d0190dd24896c27c61e8b93c07505b8872e1ef6cb915297b3f0e78a709017bb00a59f3a85e372036b9a99493522687924c1053f640a0c18270e8e7f58f3c37831d8732aea2040b5a81f47b3353ffd195b71ea0be3bfe0d6c430d5edadb48cb25e3483fd16a24f900101be0c38b4b69dfe5e39932335d10b36711ba9db32eee781899e250bef2c915490f09b1a3e460177198d7e4ea399d4d5a75296eab087a7e23d42b05704724a2d32deb960200ad1e3c7558a83ea3b164a8e90793561c12f5cd77d27285972406a3f8c293e5dc124b01da5f2539953f911f4e3194b00a61e416c881647cf720dad0d29124c8860f566279d4db96beb720a9a28cf2fcd5bc3a5540a68953f72ff62c41cbd818f7e2c393760be40c2f32585544262f2eaf860ed8a0f1ae53f02d6cb1406d0c0e9f033d8459c2fb90c6e1220ff9b5b23c0b7fcb2ea3d221a3b1d646a3d3187d51c804c55b3379ae66106453d72ea84353f5748fc904994510111c772ad74e222a39390281b0d9780e610a336c40c0979dcf31a853398ed104e0f6f2d3bdc62c870af373dc2f0979e82d7ef82983559d1f58572623e2a8342d672b6a016c5f3d052af5e8eb5bdf775a3c0f946ca946bcdd2d704434ce2c71b7172ae5f55ba76e1c105e5110d991dd91bc15d3cdfc7cd01c950e0b1c39413f4405d6077499e04c63a890b519d3ac02769f3b89e3c34e48470d9c8a689548ae9d572d0cf138699dc552593ad49fcff04f2a49c3419221c73f63cbcbd6007224fb57269c3f016775c78071fab500729725ae4adfc6db747282768908842002c08a03ad4283f6e317c99bac75fc650e30314879fb6c2df3ecebed15dc627a993715648e040c5fe0b34f38fe83723a1deaf4c97d58f1272aafc9a1295ae8ead9672c44a0d194168c05da5322fc7a69951b6b2d7a8a58c5026356d162389972b53436172e3745ae8333a9fa877cb2fd5f3a7cb85beeda87d22d86f78c5d01095b16ae6720ca7713493fddc1e04190c64f7ce126b57c4e80abc59e0d9fc7bb4b715422a723daebfec57393edfae6343238d8ad4e0d3301c81f1f7513305d67921c2cd3c31f6e0af646dac5e1192addbe7e14f3b866bf895b70c8806800f0413dd64d05c72d4c8f9e89cc9816b6ab7bd659ee34aa41beab2c35b1e47f708f6bfd5cb2b0e7293d2b1af18cb8f56ff65ea1543941cd5dd05eeef7f6e76da9cc4a8c5fe227b16680133b4a63b00240ee6be97d45c876c7b713aff4dd0588a68a14e008881964645504369e4bce813e2ff0896a80d20a698d33345dd7377fbb2f6aba3453dc77229c9d4ba552daebbc9efd9a125a38654faad4c16e38cec9b7bcb9504f733085a72f36475a0b7c7e0c4c146b0365a9e66f37507bcb2e42f6fdf7e34447e8b8f09bca579f0f8c5e1ea78eb1a909ce7e52c4e6b6c81d81701486f3ecdb355c08172e2fd2e6e3c540bdbd3229195fc48c1eccd53afa5094d25e083b9647e2ea2fb728dd80d2a7933b5c92470559d69dd7d512d8f4035e25ec0b3e8d704e5980ac70978f5dcc1bbd9845f640c4e6045f3c3feefe03ce44403d122258a56d4470e9072383cfafa2c8456e1d62fe51249217242e326d8aa0590450d7d3848f72c8dd972e9d6f935c2fb7d8d8b78a2386066538013588f8f0f30657a0f3a82f7f98d20578f908379ef7d77ab749d303446a9ecf8167846e113742cc2c0717f8f96f067722519ad15806868ff9b90cd4d5e73658814d2c5747ed3973722742032f27ab600a392b1df4f6ff377d2542ae1ba55d0b3bd324237766f9ce108dacbab91cbf704f3694eb3911b9f132297872b1c030b7765c31304acc5cdea5003aa6c535ddd723553cc5fb797bb07b98ec20d4c35ab222a250841c415fa600372d3c9f8a23a7253b69e79f88604ec7f5e4b3fa5dbdb285314c3064172629d978d1a3839c72772cf1c3588839b0510c543f61dc32fbe2c5ea55a2a1e9f682337d96db66355875367ede5365ca0ea4ba0c60ea3e4b8776b38407e6fdcdb451c6630a65a0444a3727f62bb8ae135ad08682f87f1467d933225b875933676e37bdca4e9c828541c6babdce7ee11a25d00ddbe745867d0cb065ae7d5000d5d9d63c7e760ef8002687288d71618f4285a8a019c411d5e29609c1475f583f495a8b3270ae13e6fc7c17247af0f65c425c80be0ac0c6d25b7737c88d0688045ffca75a293d8a93bb5ed69ca7d9d521d75969a2de1e26038ddfb348c82d306bbea48213b54f7157035d672acdb506938e653f320d566de0ee185edef70c8c75793c344b270071e2c8e2b72eb5b1cd17374af1633fc412f8692fc7aff20896f8241c11f5dda57ecbbc30272c38be737908f9dcc9fcd70fc0b5b407917def1e6969ff1ccdb40b1ff2d5ae12d728411748f5ef5e9dc30e5c0d2277dbb79343a2f51bec95947a30f968940da635beac6168ac80b004ddd1d925c05f8d287313cbbf5de07961a994f982d902a1c413ed22578315d0ee2cff7d13349b615fc97e02fc88bfba18098c35291bb8072a12b8d62e95aa3b636f40a0b876d75acb107cdc8287a0417269edf1d0ab39672c4f8fc74b4be5a84fd932bc0a5901db8c748a56347c0f7f4ecb116556de24b0ae73a4aea4796d73afec54fe4cea0a976a0717dbf8b6593e75d07d1c2ecab0d2c9f12bc6c32d7a30b3da04245f3199d47b7ba8f88cfa12717e93a71c380292c7262e0e76eacb889f6c4fb2d83744edfe618d7113579302235f410249b40df3d4c3a73322787927bded805c2e9917b8cef1f8edfbe501db95bf087fbd1740c4072cabe46bf7d868075b79215862a6f834e6b26dd2794578a787a7a3dc0147e5272530b447dd883cb89e43df826f691c95bf3b4d29a43cb834d2d6f18535ac80e72323c815391e198404075774f072d6cb6864c8aa363b51511885d7dd8387f7b72a4afc63fefc0051ffc4b8525c35456ad52875a4de91bb4f155c517627737d53629cdbca8c49cee7c86a53053dadf879d1776f63c2780e2b30ee072981a2dac67339279ea639edbdb33e6931f1d6ae9fe098431b3e2ae295e6ce35597c8055a527c5155da58f78e1da0d187ce82177e6a43069b63bc72dc3db02854af5a5d48720bc832d39cd78b31fc01637cf226eed5811b2c503e331bb7f97fe5e2a4e6e60fde53f98e61eded135cd9782e6ae9f163afcc7618c61f20ba40a676ef97a41d7244d53b7e6dddd1ceb0980d6c70a8fecf8a2bb2ab5fe61a231fe1f0c072c15472d3fd157cbb923d07cbc5b96569ea594e084cd29eed1a704205733dec8d0dee07641e3421d22ed4052cd3f374c7cb247bbeb3e04fc1997a433086c15113d7c4340666f824f87a2b805405791c3ba2bb799419400e946dc60ede9f0f57b98800720886b797ebd23e2085dea3c04973c5e6744f156e6412f82c78a0933ede336d239e1d3028252c7203a90b74d5119996bc347ae6b2a9186fec5ac8e09162321a72dea43727cb4a634469ca33d14aead9493db60e32c269c31c792eba1631129c72c6145e27515959ac1d4994f4a78f2d6a7734c6e27b840582f2636d80093bac729efcf5b65803d5a7f5a88db6abde032674c10b8f863e061473191c5360ce746f54eb0b47e7aa640b850fedc5d76ac33a0cd8d2875b7c2254b757cb8cf348266aeed51e04268f96bc8cccb867619eb10a8effc2c9f53ea1b323b52c7107a9c40cad2f65d09e82757ca76dd56d5c075229233afa7d61337155fe06f21180f5f8729ce2d277d5c3fe71980459e18f80074fb1c7b108e104165d52e7f996904cee6fd88f5de802065a4c00d17b1ab24eed45aee813aaae9b7d53ae8f1308b253887256e2478fe4d8b908a3c131452aaf6334dfeaf56258f35b2c6b48bd147469f12ee9c1c9cfc49f22de7f3a31b527a06c2e4428f348effef0417a09ddb8cbe6374d51b14e0f3e8d358a5ed60368285574a26612ec672284e4d3b5707b75383e217274c1c7c60306448a7bb1cc6ce967eb0881bffbfd157481559741c8a87655c85220fa19470d7510bf20a5effc00bcff4342bd381f1c6aebb79749044659c8d95b4909027791ec656d7fb98adfa83f67ee9d11d59465d884c3485b0d471eb12672ab3dced1d856473f0ab50381a28e72bd29c47db9a5524cb62d40eb2b8a8bcf72f4bd74f3f67aa67692f8e24a55645afac9dc42df08dc1b09125e8b153346ca28efa6a7541ea84050d79b81631f8de424a2aa737fb07a473a0ef12c7a6b6db948b6cc68014c91b9ab501fcdeecd1ef1e65455723d21533a4c1bd3271db0d17472c64747141b6529ecd85861ff6dd4c76d7c33a8e56c866f6b51e41847814ad0729654465a591946bb80828d599024a63d0a493a9ab8b3d3f9d3a109c095f4dc720bbe848cd4238713011f063a96a69105f7ce06eae472a53efd3a1530c5319b5fe0d8db133034a6e024feb54fc118425be668ae12422fd1a0df2f954279e14772ae9be0806e5364d12c397218d125975330673afe80fbcd223103aab1102338693ae5a96c4a47d38ccd3dbeb63ae4fd78100f960495851847ca35a5693ffad572c1b06aa7759b85e2520daacdcf67988fd699704b3b8c2f625b5a1c2b9d3f4f1d5fc9730c0549967475c6ad94184e234c115750c5dc47384f3405cbb04082a023e5be2bf11d348909c841f8726edab8424b0077424a4630e63049af10b665c63cfed7cb5d61fa6b92923b13cd50021df4bfbfae037a1a09d260c6a5fb910ab340bf83aff03a84cfae47f03d6aafd73b60847385b8a3a027b561517efb8fe2d7727701e8b170a8860e4016599532080ba37e94b5f215f2e12d2fe0cc076a6ed7402220c654389b6eeb40a4c8a18482ff660560661dad467a0695e75caad84ad15b85d986d83c7c3a007f4947d37ae79ef379d939a284c046f64a458d1ecb2cf41538da27ea8b8b9452ffe02833ea7b9890d4fee9ebcc3374c81348bd65788cc872d4fae1cf43a2e5d1936c000a9fc42e18125124d0c098aa11a4d246c2b5cc641d35799e14a64f46c34767ed901f2c3ad3538eb7d9b2b2697bc2d1d44b5fe2fb456fcdd7cea4adb5fabb45231dc4f5856885982328f02de9c66ae9b2bff7753d727b9a6935ae8a1a0ddfb574e39027da8424e24318921b1438e67ec9b7b6a25d725f8b499c864980333863c4e5d602d4efff286c4f70b5660e9d16688129b3cd72c644fda3715c97bbbe68c2a10753971116ae36fb0e62986f661a6f39242c06296054e77becb5e6dd62429bdb8803726d2005ae4e2e268a51a53908249f2faa458b918966a50e10c01fef21c983bc5311aeccbcab799dbde90fcd44073967f6722370a1ffb2d80c12f448d0e1eae23d663c187e8ad2050e8e47f960409b477b24a4e532ef69e08707891c507db57aa646de08747d21d448fb54092353f7645a72417d286c74ee42fa0aab38927a900a653faf28a11d747fa658eaddea3f51516593c703ade1cd842ac9ed23f9ece48d300255f558a7f81d877d07d93e277a7672d00d6a46509e0b9cc7cd1cda14ebafa7952a225c12db189519bd2c02a37dff72e8ee7ec462b1342df2e0c973abe23030a2f7ca02c4f967967b230b9e7637b0728cf04c726236519b095c9e30728ad8d8a547f3b081332f13a0c3cf6597babd72810ecbde2375ddee0b7391a61486d2cf93c9427bfb2325f204c51f8e8239a9721b9f63c811a0243a031471b75dfd0efbb55d62312f8e2dcc09b0b56ed6d19f727526fc4cb308c653bc623fd03c019ecb369cda83da94feb7e5476d42d3088d7241d4cda40944d59146274a150ded2ff0fe770c4bc8b55ed74165c8f450aa5c6388eb0c64b831c4bf680b7acb448c0e1fc848cf51132b6d8ca1e340fc463a4d72437c48dd1a20b654878cbf4e89db54a3beb7ebdbaeec1d1a9771b74d2370815e1609c3d94edcc8ad78aa999e1a6c3ffe0638d0112c2294a7aec642afda712701b1b2499a44047f7288af1fb9de4cb51ef8e432a9b11fbf403a80533b3c21f40c23a95bce597a2fe491967aeafe0e9679f32d1a5ea6ea9b84d753ad9f75fdb0721ebfa8fd34c6546c1148875a209e16a97de498c35f221d8484eeb2989c6278005f7860634763e2316cc23e1df220b1fb292de6709cefdc9869db8579f0beb36f6f862b1a4476affb8eaba41e7818b13399d83004b5d8f34c939a564fbd04647238aecc6359f20412e19477b9fa8ed1a567c48b58fd264e38cd2cd58891e09d72343be80b396b2c6cb4dc06d97ca2a3faa42949b074d32ec9f71630495e1e1871010e4fb8651f2cdb7f663a8cb1da59d2b8ae4d99ac0c06aaa2092dc1ec78bd72be4a9698bb70ca678f979bf08f3cb6999c355456468c6c52527bf3e9c5783f34797b32500a69c8d7c1592330c3b943ff9581a20941ebb02f8fd1835a643787542302088b09d1796aee136ccb676d7b0f55aaf53e15536a11f461121ca3a36172890f338f8c769ab6363e10030fc982c88effd757f81e4d652e2073ccaad4690f77805981834fd6ca378cbd3547c1ef52c8f33190ff30677188aae091ccb6647201d54d8abc9730ee584037ae9dfa64d74a1d5e5bc78d033e0064c9b77c0c3a72c4cb8149b1cf243b02fd49e6274512283a9d2dab71aeb3fa9b00540498bb7744d09b8e1ce255e5236fa1b4b6f214169d1ade690f84208c0b783eb56cf75f12498fdc52e728c66416c038c776c9025a0016b61edc36307358fc89d5e13ea3b6729af03209d989d1f28af8e47f7473792f6255da0457cc7eefa5d03ad41834762e14486abd1958aea327bf59f957e13c7ea005e654ac8bfdd0260eb8a848cdef72e4b5b95791dae7bbeced86eaae9710a23baa12c983713d5437f66596ad5b3372fe69d6e9daf22e7b7963b10297f53e2336a9aa2d565d873dd8a26e8e1ff06772f2d7a0b72750ea7bdc1658a556eee3714bd70906aefe2934ea41fbc389f41c5b9abc76d4cd4301aa35fcd8a9f78c43b815dde5e27fa8b9a9e2393a426c16f9720b3172d02ff8fe259a9bdb89e474cf0522aea3648c1d5a626524937a34bed32664c6062f9002d5587dd52199d58a70d0445d25c4ddb5bfca53f5adf0c97b1335756909893ca4f33a5384cb72aa12168d30cdd18239b89457f227ab5aed29bd722381a2fd06d1979fe2659d47eb7006d1d25e6e76ba90e051f9a8cb2acd5c9d72da6677292c1229902cc11a2a22c9b59d246cfb11f076e6b295294f6d101be5728d10fac7cf9b0b5c15a502a4203b7b62b448a0e124ec763a570541f907d4c7723bb2a30ff974790884a00a57196bca0dc3100bd486498640110e9403dc350a72a8fca538cbb6b14828eeab8933ab443bd8564a6c505018d17b01a17e6164a9398619533d9c68493aa6d33c3006932af85f2b6009e9bd5403111cde40fa4aa85f9de6180eb1853ddf53afc15888bb2a0f65ae680e359187e7fb7cd2155c3d6272ed81193d9f6e0ab23ee4e36e0e90cc48a6aaf24855410c9bb71eaff12299786e12c06fed38ad596d56e779be113dfc98d4be849d7f293b9bf694839ab87204725d19b06a753b229e3c8e8f0174259ac2f2bd1757a6f2960d75da009113a24c72a609a2a808b3f14ed55d1c41f06dd47933cd633960b2201e4840f29c55088c728780fb289431baea9b8f95ad5e759b3c07cd17101aa6631941819d71a3ddfe72e0c04d3592205a2ff7e0d779ac68baea44f7daddffd98992f60174fe7f777b72a7cd25e7f133938c239a571abd7cdef7b80fa8efcd8a9dbbf9eccadead845b2512abc984f4fe12d17ff78d7eaa06a03d842a0c45805ff7348cf8c7159560257244d9feeaa6bc5847afe47478adc7d4e395c6002d376205e4b3eb294f5a8e3429a245026969952cd2373a8c9e00c3746f454bdd553d13b1f8df76033f72a15c723eb10088a4f6b747db9659610d250c5095f68232211580f1fc68d74d347c5c720e1e10f77b73d441614ae0a38e5fb90ef9d22778ae42340c6dfedc052427207239b1374941a75fbfaac4f8e2af7e08c156d30207ce84b35e31aa3705b19699720bc1efed5abcac4cc8ca7cb5eb5d2a38adbc6018f7f1ceb44bc92d8f94b3493682ae1eb1d7f8df0883ec2de097a6466c3dbc0a18cf0f7f2516f60aa084fbf072200a23c8e68dc6334759243d2b5be46db2b36607aee524968e048cabbacb3572a6e115681c2bdbf4fcfac057f6640b3244d808a0791defd6ae4abd30c367d12d0d470f30eb81c5061969928f4ae09fed1633ed935651a6f14c4e5893f53c6c72bab2091d2bf4b10ce630565700ab6faf501ecb556a5503f7fd88c355ac0d9d537a5bde1353fd5c0dde5ec56966902d1aa15ef636c0d1937b43dee628316d7332ffb709d0f7778a006b128c1b2fe3e8588707285c6759f8b9514e21d539da737262e32e78466f360ca01b0784c3017aff0c1f7764284131ca9b12c0e210245172e577625a59cd788a4fee4ab50a2c36fc5a1a655cf59a2508088e46aeb7d0553a7656f2e4d4079898a75e1b7df5f63813b8dbd57c645b916d07e8c67746ac8e0c5cc2f0e614bf989f94aaed60fea2cce2797c7cfafaf2df9632984a3d334bbd727b965c1b8e302b39c24b67ff8c534ed5e14f260c8547f6c9fda27b059869802648771148a7e00d7b42ab3d4963525e1d7cc6a1fa7983c3e9deb4006075eebe6230a1cc076bbd91d83934795687d8f4d5536b08914e8873d6dfb654d71c913772d7d50096555061ee7bf67485ba555f612a86dc1add8999736319e914d2cfe77219be32036ca8d5c9a9e0558a6c9a840594a03dc5f96e08cbcaf5047bce6e7572b59daff2d15d6d2cb98088b19afbea6775b38e2a4733bdde69c913f5c0f9a40a9ffb5deaf1ee0177c68aceb0a62575c60f5f4797c07e29450d89e9df377d2d7219a96c197ddaa0d23a6b498d22c17f478a2c7982a70f263b0f0a1b2dda991472a5f237ddbd39b02dba267aa393c5cbb53cdeb4779e71e491d0717aad2c361949cbd33f9e5f67f482d1c8e45837562fe7dd53ee012862354cfd9958143a9cbf727cb005e55ca689d37abe93a2c0a849924db4dd2be0f8bd4363b6a2019d0bf772ae2e5ac9e733cd820964d53ab0198ab8f6f1089a8d82e42be89c96229c0e65725c538f22dc9b427fb24d714541ef7da8b1323d2744e56ae1ab50a8d9318baf124834f1cd17f8384602418e1006b342530e4f5f78ed6b8fa3561245110a170472b942b3966bd3a1e22c4f3d0a666918bd6970b5c05a85c69e72550805a76f3272108a763df40475f4c0eb78163cceabff23b369fc541963ca9c8311da709a334800b8463e2527047434a01af4be47fb2f9eb6610f04d0089d1d3ff03c795da54f7569e6c88f9d0555e0ddccb77f979963fe0b80e23446b8473f91ae69b51fd33601ac0af288ec73f7d0a5ab09881865cf7e8d9981994424b85cce3305e04c6f727076fc4f195f59e5d1a9ac0b76e67cd4434108717539e2627048460a69d2fc729a2b6b24068726554742661a88f88a0c6e7aa3d674d11d050cbfaced85f889729d349cce0e72290a1fd2548171c431f95f3deb74a1b542ff4bd33e112d14c47276ce00356049fa30ef9b447cee2f245ee1c541a4b04b5f83d5dcc0e95afdad721e3f0c5c4f80988b87ac7133a15cafc03593289bb116588f6e4068c969acc341cac1288b14570abe59fd370298a1276b3097471c9a0b559f8d65ec0ba12d7172d63dddaef9d0153ab07d1cabac642cfb06af66d8d0b9c5958e3448160ed6d0722cc81815b635f4f5b36ca33ead1a1afc35aac2124cdf94842c67d071a32f860b7f75a53636a4a7bbc61e7cc36563b7f17741ca855e1fdf3084e02426447b5e4ef1e7cc2bd49d27de1fd53863e2a88696756d7ecad97d4cbbe202e44d3fa0c272ca38fba8f52b319cc6178ccd2057bf68efafb7db2b88f21532ce5708f4a8d7480933deb1fd400f2bc1eaa0ceb185aa2d450412042b4e3bed0ab46e13e4516c2ab2e499eed362d3667c6998e84ad4741660d63f084f71947f5200bb5378ac6b72eda6a49118fecc13623465dcda1e1ab73ac9d7d28146d3b8b6eec8392298935955ff14e33731f8134612401b907eadeac3fefda2590d40091af900c342e9ab5a2aed52135de3b51819894b7394d66a6920d061316c90854f4fde8f52c1f81f35d03198ec81694bb8e7422c02a43f098255463c2510beeb71fb0f3214bd633d7210b74e2fc4d991b52c3d8805162398fff43df3a52775d80c96a08715dad28210c9093d1458aeba925b8374c58a57e5bcb2cd708b0a3092d4917d5fab835e5e723dba1f040b05424b9eceeda510031414922cfe6ffc72298b63f60d34656666632a51297b2eb064202032540856fabef96bca05f67f1d5ab624fdfea2bafdf7322f88dae45d1d0e0ba84fcd57334cea339a55880bd1a1b01c7eb331f0bd8487722241ca1bdc6224a341f8de765bd7cb8cb241baa8836bd49c1f64de73ac649219cf3eb04ddf2f450ab6af3fb3ab4028b9a68fe3a9d7815c8a1b61abad8e0f4f72e6074e8a1cd986312a4df46a3d2eaccc0abf9713921e389a2a10ced2f66f9f23e5dd05083257125ad8f7e10ac3c190bcbd84308d20229793a47ec4acdc07be72848e5c396ac58aaeec9e946ea40cad3d450157200b8ca4567d296a938bfc10722c8fd1b3f3030bce69966b25d3947392bf38b90d4bdb142c563984ef8af066722a738fb7ef147347525dbc51cf34405e5c242ab4e8b6840f163c284087faac5cb2b9da1737ba7cf8b52d224f896bb166c3c56d5a0045c42e0b9a979e3931c230f0b95ac74770d2a45643230755ee7463948b74a9b6cbccb8c715525e4dfbbd0ac7c5cf28ca45a66ed7f429d89990f03516a25dfe9baafc2d64494c48293f0e7227cc78f5249e537a5ec972a15a07d463ba9a5f323a30dc03c4748848c8f6dc72f39ab91f5034e55f778455d1f35853d77e4f4076062d56244321ca6c2094ce03520919d0dcd5ce2360dc30a820163825cb5a1fd2ef3ba7047af57a8fb1d64e723cabb3b42c768e9f4a611a48c214df26168444fdba035bec25e84b5c3e7f7362906558e600ea19e4aaa9422664165dad8ded8024d53092acf625dfb88b648372ab49ab8cd439e63bd8a095c9d9fc625e3bfe718594ed244e622820cde9f99745f1334bc4ff580262d4192deb1c41fc73dc290561372502deb6d943de690de0475d463a8e5e9223bef6668eaa948862aa8fe884e2d7a48c85485095903674bd7279afe4a00768d2887fcce680cc20d3b2f9e81d5fe2b4eaba5c9219a9425b836142bfae0dbb600b99a896402efc7e2f3931680c376cadc6e6caac4db389de0f7230631eaa45a1ccf654d60580c3fed8c5cd0df75f7d97d028bcc1161b88d670728cdb5211c4df1f59999abee4b41fcf82b55172fcaa705892d18599b997c70012fc1f3f881d78d588f3b7582e9a46323949357636c4e4ffb6d5987411f05aa46626d61a8c3a9eef9a314b8d8c091b3fb8aec3071c3c96efbebca86beb0ca42a72493f14559d1e27fe20bf67fd4c3ee62e5310b32efd81d5831c3f227bc712e072d015d5184a6d8dcf70b87fee2013d6954da85fee859ce3d159ee15298c797c39aa658e17e7945d02668fea001f9d4841a39cd12c54a278eb7d22124eb45b2372c2bf61c2a62642d97823bc4e4425f9b63b71757311f0729b60195bbd802087729cfdc0198007ddcc47a75b98a8b98a5f9839a57174fce38f963b001f5ed70c729d833de13464e06d96e57f8650e45beb565dc32221b69c1a1e579f9c0972c2571db33ba492ecddaa555de710cc194634eea340c8cd966f316aca5060fbce6172407096e25ee46b426dcb32978674665fe91a57266cec1fa73249e1296666b951d1bf110920cff9cdb1aafa5b9ef9d5be28caabbd15efd141508a19688d112372a7ce37655407d2e62c71e7156f22623b751b918cd7c7d52101f878d00ec8ec72643bdeb7c787b0b39ec4bbe176e5a4e603fea0116f88db210b64fee31586067206d9dc47790c4fafb68e2fca827c585d9682c6986d15f7bca9d8e9a7338e3632198669f9cf9617016035d0e0aae3b7bdf219deab8d01d4d1b5faeaf2462718111c6e2bd9d29477601ba0f352ee36ba52eae199073b42829e7652d9f28590b457d12968b3061e6ce55bc9fe08d0be1e59250d398d91637ef3e25f114416bc9b3505fd933c38d9e0ec83faf37621d200261cac1240224d44d205ad993904401972659f42e06ae89402c507e769f2903ed8f8f57a82be5ab7b155d38a9c9b2abd72907c7000d522449d302e3ba5ad7adc8cc167d819952e5babfd5427b075af7b725c94d7b42c58b2239f4887c131e704ba94b06675e8fe830e559bea18c211ad724a89d761ed3b4cede67fe31ecce5452ddbe9e205f349a57687bf8f6811943072e402277bc3fe8e689d24e52208a1ef6fbaa70d0650865afe0a2e81b25cf2f9723588d4f2be6442c38421ca2d3867342f38be6bc864c25e810deba3fe32335072f07554133f3a798e8b6f8f5beffb01fa13eb00d1e9221cd591960e8fed69c558495e4cbad6459bfa1a34531cd30a032453d23c0ee835c3c6cf7f54b2e0b2bc72d9d996cfbbe7c59c87eab1d87e76bb0f65c35954d068a2644a0b9327aad0ba7233a04ee0dfc9e4bd484d2d6651ca337203d2b118dbcd42f8bb87b0dfebf0c37294a883f945d4e8605dffda5d1d79d1c2e25a5c15ccf811dc8df0fd2fd8cf0c725aad9adfb327e42a514c0103caf12330b8a80976aab05a321e2e3bbd4a3eb4435419daf39a2e5c568aa1ce34c55885c23f07177414918a8a578f780c419ce36c7eefbd8969a25e33670fb9df66287dcbdac0a4952f4a85c0b8e8a16cc11955070836d7467ba692d5eea8c4d332b36e7ae964e91f07094f0c090d50e5dbe488728be07f065952ef34ad3791c1f6bd5080e20f6bbfe599750c1ea330d541d5b0724682fe29d521a72bd7162e82e3bfb4a9bea28763df7ef95b7bda4cc7bff11705b6893a4273e894533a8a0c660be7c6e5c80720eaa7398be8b7c531a1993a537038b73866ea15e64c9654a5d60726d509f1bd3cc41410c3bc5b1178d1dac69734a95c4dac9eed48ffd7673157e1ae76c595abc921bd7d27178b4b5e1bfcebcd72218400552ab09925524087e6e1471c90011eeb861ea9c256c91e852eb6824672b2b2fa52f2963b6a8e9c205153b33087c0a92ab9194e2efb746cc5543a83ba72e53e96f7c8ddc56581cb7980461187fd877fd2256df0601e40aed2392f4f7372d7654c18c4f6f2667d403445eb8135bc32e11d89205b02eb2cc731bd69827c72008d9109cb9c6838236bcba79e99e9d98121af28ce4b8638ba6d6979d113cd6cde054ba9696c801370c5310b00d3ad38825264466ff13eba08c50d4d73e3b4729ca80fe409f85be52a0f0d75831d7686d460c373398d5f9e11d86a2a1baef872fb8b87b898eacf5ece2188b6e4a52ad786feb27861878728b54b6748da6eee35b4678afc9f31801e79509142f0f3595d2e84bdbc044cc61d01b59f10bd835b72241755275e9cba9758f03b47280b06cf580be5a4832dccdf1bb37ce72249180ccce96421e2f16aceb2a2602e30ce01feba1bb2f3c0d2e5beecdcafe511e27872e8fc0e801c32a8629caf554a9838c8e6730c7a11f89ca9b3b9858f536e15573cc19b99b0bf2d1b53380bc194e5fca755534435e9ab5362ee8ee5deb853f3fc723277163f6c4e92e778ddfd57c996d887ea3d50a15a2bbc2110b41ae132749272ce5836273819145a45e411a3b08380c358d7f55babf46f17200e27b65a9c237268ce78a0ce5ca630caed9fc22e7f5af213e6c58acaf108505bc1a76ca8027e7217ef48216b480145f5a43e0b7e519bfa3bd7ab5a4965eae6a624e7ed7242ba189ef42fbd35eef3d4314ae6aaab4542d7cb1881820e0fdfef8b5c51428b3fe7723751357bfeea365c9cf94e4e8db52349f570c800e4e7a8b219380e0cc51bf772c859161b2b41e74501b15e000c75117b5b732154763605a1b11aeb1f39afd672b428401f2a0d87b2f944f5cfb51d6eed22f33be41e7b74f9a3febe34f74a55722116325f3582574578816311a1e1941f1f8610f4e3bc6740ac34cd3309a91f03003ca68dfcd5e4faecdb2e1d06ac631ad83ac7c9957e77e7393634b4b264df72c9ed59edcec93510b2e1bd74f2c9170da196b267be9efaa65fb66fbb5be97072983cad4756ef254743515c15a4f89f6b1adf988c285f137b2180c8c32eb77572f28031598322aa6844907ecbad87fb917e6906971b8f02f0e45fc6c474e5fd5ccc7711f909c2e8d5ce30c30a48f9edb1a4c789c596a0f56e07c989b7cef2ed2a8075ead74155bab188e36fc59871f9072417e3896c779de2eebbc0d3dcf6f8729d0aee82d00e5469f20496efd848a46e6887912c2af74cb544cea1053abaaf72f8dc2c768363836d408d9c33863fcb9fbd279e74313fd05245425c713fef3b3e15607521dd3bebf6a7b2d68bafc994d80ef7343b686cc7913100c2dfaac4627256579289850e106a64cfb0c7bafb45cd0c0bf28080680d38c723a490d3497a72e684d0f3b86c3705c6822497321b49a50b63ecce061be1a827c39efd3552d2728d1c9c8f732ad4767bbe0e0142fe31b04265cdd9c97c6fc5aafae2a5d0a4ad72d3e6d6ae5cce78f21e51040f49c7062bc31c8c87f31b18f9cba0f82edee83672564cceff4d4a75dcd0dfc9729d4e6b922e087358ff68abf1dc85571e319cc7387f36515567cc7e9bb2afd03bbbd4a9b77658db324a778859db10b6ca1f84267258dbb960064b3c15bc78d10c45181efe846b01724f5100af9d091789613c51728a4a9b697d7384f8f935fea9a9d3c0ab2ecf1abb995d669f07fd3c4722d91c723e411241cfbd93eb528d0ca232d92fd6504680af8bf3f92e112b0bffad26250c7a653239e370ecdba7b08119c7d3fbc723b87a935bdc6a2c8aed7f7b045f3e72942bc2de53d8f86a487826e723709247ce924870b6f77ab4d638b3ab8c7ccf132148cd772b0e7d80f0b5cde5b7c123a6fcb0e0dde43a270b5cc1f6e175fa357203b77744638355650b8659db694b470830b57811ca3886bb48736d897580cb5bfc11ce428c7bf19c64ec30020187458fd2d9f3e9a322a9748d9258d4fd75a272079ca9670fc854e72ad005df42ec77d6201af6304dbe37a204bcd5f1199be672bac0d896a4bb77d95404342770f053217461d0d1ebaeaa262df173ca4dc8c567f781172bfc706b2ecb312af2664d5bf6d96fa76f0ec6e152d7c333c5878aca7223bad9e67805c6ecc06909331f93c2ce29cd9c7200462bef45351ce252098e7298d26801649af5262bfe776ed428c1234909a580083dfac26872e32cbedbbc37376bee6c556cb4b5b1678db363d48647c6fc01562c0eef1000582752b127ab40d211864e57b2c872b12c4fbcadf0e3b8716c5ab43927daec49612452a9634c258425973544001eee4772adae565148d986e8eabccf4a6db4284bdcb52f5aa94d8c111108f9ec3015e1ca14d51edf33225e636403d8063ee560b1f2cadbeccf720d77008e05db381b3713a8b75776d9a2d2776d71e9beb422c6d55ec5ae149453cea7c2a908c9b0e176c87a8a75ee1d36f78504684cd19a84cceedc3ae99f3f39132d7b9f73506d123c1e0ba00fbcc52c5e9791651c3029d530ceb35e2aa2c3721d2af509eddad272cae8fc034ea75647285fdeffad8637026bf3563d52bda972e1e8b3697fbb0e9992c269c2c04de51ada80ba8fc08f94246ca79e3142b5e272446102148e9fda423e51ebf28568319bcba99e11bffe7b84bc273e3a209482154cdc4d2cad2fb45064347bf7d1335c4e79affa97faf3b02988ff37d0d1dbf523da97c0165213a281b06554cba08967f8209c7e52e3d9c21929bfaad7393d7b2acae03dee22531a8ce5d042911dc8d8e166949c974215120de19838bb2f00b646227b639261c996c4370c127006bfd5fe419ba3da9658190ab68569cfbc31731516b6d59dab7ad824add7c5d94df77480b523d83fe7ceb893b8104dc26d570b5958205030948ddf7e90c3ad3c0c87f717e265ceed2a6fc45f483103995a947372dec359579f97bff299209b24b861f692a6d3b9d732e96e147f88a158298f0365bfc93fdfcb91d7b884f43e9bd20c762b7e8fa81958a8521b34c358d28261937279256bf855188564449dfe1d223333a8906fc19aa4a61fa237e699e649770b729ca58434ba2523bb73e10f9dcc011d71694cbc01b8688413d5553e8a855da72c3297ede8f9bb023b5a0fb8554553acf3f440d7d096b6f51b450bfc874a0bb472b2076d1fa19901fedfe576aa808d0d7b9a518fe1e6fc1252502c1fd2f96d5472e4f4b225de24eeafe1f5b9e7b0a6062cf569fb11f8401ca7a0d3d7f74db15760bf40623e1ee098e6c448907dca25c0f376d233fec5d471b544fddd8c97c2a8724697836eb9fe6a8fe048e4b9794c3b708e4f7dcb0cffad6550a49573233c207297423ee3d5036df108c0743ad97f1f768b250145e926acf42df968ce394ddf720893981a15b106abf13235f924fc4b22dca537f5ec55fe55a8e0049d7eae5228182ae5b2c3296b4a198558b06521fa905ee2fcfac632280209f3179b5c399f728462231486daad41881d8ad488c0557c93822a76b2c58457e3c75dca28d911721d4955c232bce0ffea6b6f855cb9b65412e7aed918dc0fca7673a88a18bc8c1262a57d04da2c21fade1ba9fdbe4694ceb97991f0a49643bd4209c955d3773a72c2928e4df5a6f8200dd945cd69c581948f3b2b1751130160ca6d81d35f0b8f721e5044c7f2e8f87caaa45ad242406ce8f0f3cf410af139245189242ca5edca72d80bd8388edfd0c53a75c7192052faf33c4e42951ac34d68673fd9e6faee6308016c463ca5978682d8fd45d4592ee83494f3ec96b8b0d843251872a38945ce62e1655f5d890245ddabfa69ff4f926affdc3d0be9cd33922bffa8675f0e98cc49aaeb7600bd48d6b6bafaa5989a5b53290f628b0b94402361d2e7eb0487e37672f1911b4447721bbb627fc59dcf1fb1e243dc720bb63954e53507e7d31235567218a527034ed0e5b4902c1d75c017a0d0570f393b4ccb44bb83fe31386dd22672a8eebcafeb2cbabece1187c86e8c0b83d43ca1f9126b6079b9ff9d7d96e2ba72cc77c18ac905382b50c907fbe6e58e0de553bf0a1b27c08bd29d4388f61fb25c8ea8bedb5a5c25c468507b506a59c4df0deab7849fbc4162cf6fcd83673fcd04d8ffa2d0f5e69f8059129ac00d53ab47376995a27cfcd005532d82998cf60e726d7a1f4674a85c700d4942a37b5f51407bade0c0e18e814516c644632956ef1bb7a4756f114411f6ce67ce4d96e213a9c7f678ef7248eb9473e77bc6d61bcf723877263394d59502461fbcf2374a76c6727922f67bc66c05993c7e63ddd50758219a9855d7581cf5cb9ea886495ec21bb5f1ae1c71f8e3717cdcd4030e980372d3b578cb56d4a6b23289c48cd9ab018449560b9dc9da357cf3cce39d5fa505724177eee91a3692ade644c16454bfc3da59122bc92d992aec7a03afeb63270872052947feee077e31fad12ca4f6478640be340c0515ec3557dcd5979ea4c603452c1cf42edb476f0810087bf47aacbb2922dfd098371440416bee134d34a7ff46ee4a3fc90c9670e369efc1cd7ab1a2566082ff5c3e1724b6f241fd98d04f234c26f1b97a65ece6cfc28af08fb7477021e7367c2798d3ee013c81af11162c2572ec923476519f79e548d0dce53b14fdf619a1dc9ee372f4e89f81ad6c89e15772da1ae919aa9b18717671fab998569db040408a9344807f225f5889b9b1592d1c2c322f51271bcd83afdaf9f9661fc13d6e03f0b5612ce5ee86519dd1a7a52f22bd1aba9d1aef58fa363c8c30f4d70e9ccbd99de7865fd25d08a843f61fafa867379af3464c4f6f28b1450cb7a386a91f579c0f6390b5454ae5384e6c4b1bf9727ec168abb939d95fa9c9411b9b83cedcd81652ae427c0392ef84733ba4b231292eb39c91638fca37492ab4820438da32ab497302190a226e260ca2ef0ea420721b37681d350a7bdfaf3dea9685baf9005fb6701992fe066732a7870c3dfc69203624b2dcdfeb6a22470ba6949f0a076ccadf1450eb4c321e73f2d9f05f40e903e4f80b2d9549e3a8e8edcc140c665fa1b66e50157adbd1e3085e521e148814720cf02af4dd395125e3e30f38b43710d674f28fd8be473c149d916a2d1bd68472a390353cfaaa95abf13f4e2b60ea1fab8c860b0e9c8d3fcd55b93c7e2f7adf72d69741da9a3744207983a587329e52281fbd11c48a5d1b9e02e2ac880e21597211d58002f39eb3710bbc992535af82c08a4159e7b347fddaa3bd3da611710226c71dd6a166440068543abd951304a6073c5b8e1a9bcbdd1acebf8a6029abdd7239d663b255c5c9794cc909ce1d9cb664ff720b9fc81028d1a0eb29809c158120c5a019d157150284e7273b0da448c6d7fa168a3aec5d4a198bd2cb083338dc1541cc9d4e93a08cb1d3d2f5e2b2e57db7e0ff603583fee289d009fa8ed966eb721fcb3505af38a4356f4e13306d7bd33bf64ca6d033d7e11bb8aa8b719462a772708efeaa3a63217f0098500d86b009fafafc1c07860c9304e205856fe83dfa4fbdbcbe0571d9daa65d38223e9b830d7853af4cdf787db556841a357056adb07200406c3570de6cf1956ddbf45488a98126134c6f8d5a51d4700e436b474194144c41247a007171d2e0a179cd87592f44d36418c01fc036751bae8f70bd83d3431d1889a9cbd3f7e5602bb9dca8af7f95f081b6d64d8ebe98a18fdd7e87a05872043f7e1d754e721764830289507d2f7e059dc6dd7b075d3a7b040447d3d9d6277db893b1ee4aefb6ed66c4a754f43203dcf2f8bfbceaceadb750ee902d4d5e729d9d9ba3bd2d2cf80548eaa18ba8f7d016ea1b1134676efffcd15a83e3e4c647b9353e0ee05077765781b5f2ee6beb251e99d9851ee411fe8a28f4d1a2e11072f3f0722f97fdd6e1ce9c022f03c04b535e630d9c538caceb46d1e8703a29bd729ce2f858fdd1b27d01e4c189ce24a8fa234e6e6ecf04d56f8e2024f374cb4972fede78ae70a80d7c55de79dea8ac90d41188a038db43dcb8610a74a7a7bce4439bb887fd93a6da1e0d91ef6a4dde649256a2f5867577ffb3ef9a28949164523240d969fff7ee4b45bf86a71234e1404b51e940f059e1317053861f70e43fc21c974d743eb244cae95804418454391c8cce223153753f0119bba8fa76a748c45ce22c1bf80791041253340dc4abc0bd2bcb205c90487529c228e5527b792b1e3add9e337b5e49d71beecadbeaeaa17f24d986aece9d8ca22620fd29f03c071c2d9b0b793f4a9de53e87bb8bc050b44dbf08471c8c4fa818a311e45ded546b76728020ed92b7a8f41a45092f706cbf2f18548b259871bda76e997b4dbf0fa83b4fbc9eb45219050023d0226769d63a2b78bab974d8c97da263a66431ff9c424b72719dd4a6cea3b520399c2e10c97be012ec2f00c4f27f7ccb98b67e73cbcba47288bfd8c0078c19cd4f1f8a2d6e718d980a9504d72b6a716aa34ab6a2da50cd72c96c5a1d96d4d73b348b55dffcf15286561cb81a9d05c1a8b2772f8aeb6ebc72e2dfa2760ddec49d1c4fdae5c8fea8933f0dc94cb6314332e27ecf895969d872b94f749dfe0f7d51062d6bdcaf80d1295709fb777f1255173464201edcd20d27114136cfe44ad4fb4e0888724a50a015ce74dde06d272b84f16d3275965c47721d70c8ca79ef1ef88c4bdf95a6581afe13be51f8980a7475382262161ab5ba0e276f7a2c6ee4d4cc0b0a8d75dcbcd5445e73bce1ac244efe389af9a5d5e52f722ab5b04749826d3c60502474e0862cbd387d4755c86658473f9068f84d757a5c983831a0735e0a95811e3bcd53b31531493b503fb05ad1a6a4b2e43730e2ab72c8fcc90a6a3d1c35cbb96b806e70d42af4bcc61b93ec0236bc6165121e562d723e3bb233fd8ca982ec74d4ec90720c1619cdde136d8da3ba412e8fcb930f4f50d330d82574649c5b1b9a5e6eb98299106ae362bdc8f64783a67bd4bb6dc3257218225c5715503e59f2ba5a8da1dbaa774de1c26029f51d268c46a389161e784beeec4038bb48acf6300507e583c66c6b8ad7ebbdbf0f21b1a2276fe8372831726fab51beb7dd29e53899c25ee775e3fdb4a5cb5dd11c7ae32aad833deeec9d721f2598bfbf29e4048e3067091f8d5aeb9443aa560f5e0201d07a68602c12472dc5e71923b7bac3fab7e1262af5d6e1bc556b68769fe5b72c5e9b5c1c65f244249aac2430d6af87efc6a17c6cc24c15cc1c7141c52864cab1a49bbd76bcc6ad72acbdb7b35d81880d0f25af28c97fceb1daf71d67913cf7b91c7a085831c8b3727c1c36c4fb3cf743bdd78cf2053f8734bcce2d35e803e5f84028c352f325dc6696c48a80e8fa7176b039a5ddbc87251e548e0fef566a7fdb1616530f2f2ed872ee1a1ac2b2508d2304fcf5a79de16f9ee68538cfecc369b2969198a2ccce23393f0dc60f5a6788cfafe973bd688922dbc29b63c1a173479efad46851b4497972aa774a13a59637c2100a9685269c35d2a6b2dc8ebc96fedea394e1e9876a807248604d81e8c5ba8688755482afe6e9d8565dfa907ceca246d830eaffe7fc38723c143a193109d26d24a61bf07175a4c8c4a379b263267d00a98fb98395115551c9ab0e3d7cfb2b9803987113e319042b0509875f5150a9423b493f6a4b0d9e72a4449419e802c35360517ef614e30935d4e684eafe0c99f62722620654e0bd115031eaf23a7b96e3b8d060cf8aa6864a1b4b1937726213c5a5b2df5b48817072266dafc5cc2372fea4eea0efb2a90f70ee28e75db86404f5b0ee27c6ea664572c2bc56b15340cf9fbbf3b54281817b531f96e18269f8f0bb0153957498cfbd69c7b47c7c2aa9761e81adc70679bc1069b9b23d0f6757662606ea222541765d5c5df51c1f2d494dcdbd8f2279801680c78238cd4a8d824134760f9d9d32a0bc72ed8a30068b504b4424a22e2e818ea79249cacd6dd11c0b68e39ea3c5a5ebc57213d35817a3eb8c6409762a6c9369b785e43d9740abe1cbfdd4ade89108d3a770bc4352e28b027defb32f51fe4fee3e0a1bd3a8bc28234c3368fcb1be24b5977201c39f5ac12128c0a815bf84236b85f9a358b0956c09e19fc3300a00c6453772201794ba266a5eb8b2767a3bff2151df2dc66a531d144bbdee0165e970830233ddd2b7b9f845f6a581b2b7fd304489ce0ed5e8375a3c1caaa68d9b3a456f7872c3c21d04631323562e1bb44a32da5ab178792d8fdd8060471a84ba70c8698872471cc16cfbe989b3b4ab9dd9f01f255abe8646c0a647c5f8354b1f5d30e385459ce0195f5adb437d2c016e8e44ed5df3e38245752e63764fcb7225f8e1f04611407c102eb75b6c83a89fab1b4a23215174f4e03b69837975b3e3435e33316b4c76c3663722db24276a48fbe9c6a07411b21d1ec650b022aee7d8a2447e61aa3d29c6b80881665dcc4800f8e85546239572e296d5d9e4e8b4051dd391bcd3c15116cf49b459323bb104604cbdb9147633fc1fc192963e0f716fbed707e16fab725b52d481bbe6e1e7856edee0a2e92e9981d0f4c8aa286bf363674cff399374362e2d5c24375026f035b8a7bf730c5bb7ad04f8b3ba05a2cdc9e7cf0ca7902155820d7951d67778ff30071137b08c684115ccff5234878671c5f2a6ae623f8272c3d216da1b51ef6600b5d9ba4c4e6944eb8bc51b52e9a98c23fc050cafc367697dc755366bca844003f53abb473163c0c70df259481cc65fbb817335cfadf413ea11edbe38fc8491cfdf59f313b075fe4d39d886cc1427295ad5b5f425ee661b7f3d5a241a50cbad706ec9c4f7803dcc8990ab6a0322b29852bc9c5c61f68d727b958cf02dbf20d19f7abfc26f3fede2381835b1b1dbb91fe03d0a21c411e5724f152c6ab7c0dd4dd27b566b895a18d3c2982038f37c4ccaae1140d8b1a38f7241b480db5d47918f56e506e2db012d15d9a2a7619663cdcf473277a9fb5c3d7249b8d25b5ecfe32ff3e605810f43d93993e971c4e739992fcc869b0229934f1511ceb3e9b00d814866e983739e30dd14f706f695d7ac5af4cd830743d2d96a212f2624c20923ab60d281cc6b14c253934addcf86e25a8ab76d363e31f15b24720e97ab4b20ad4b2fc2ff4a72b594c3c0c36bee59131de38e2698b0936b1b0672338393ea5623e0843997ed86f0ce6037e4d361d70e8248000b0c23f62249324a644a745903fd2a284b04ca4df021011b63125cb437b804a5550b75e6537fc21aa2add8f1eaa2d7f6a0921b8407ecca10e24b9618af7c8fbbd1620a7b0daf4572c51064143c6dfdd5d1b0de660fc81f5d74e3c8757a8989b89ce41cd3392f5d704f8818302bedebf3bcdce113bd7fa9e02e1973e6c6a8f5e7a7fa3a9eebebf572042a9819f8704955d58d50bd1310dd4461c989fd80a2eed9c2b25792d8778e0b1137f8ae1d5e2150d272c6daf26019a8983e29ad835b81dd1bce9ddee1a47472b3b4b58aaeae7fe853ae20ed7cdfe4003ce8dc73609258e0d7f1f5d59af959728739d50534d2910bd30451eff711a1b6a3619ad1fd55c5fb63f42eaa4c63a53afc6c1c72c693259899f426786f76dc8be01d9d965831c561117000421375b53e1e43722611a7dfd6cd5a65521e61c67928d964d1da7bfbf5caee189b377cfc724e30668072bc9753b6ddac916e37a7d5296bb92f02acd7f8e2602ab7c1544c5db76193ad8d4ba6d2e8748ee8648012e73966963be3502e0edf65704e487354722d8ef55db60aae85cf442c0aec555e00d2c7a89fd93dc83ce993db4140a61b72328853c128d8e46a032d55ce96838ef2dcab25841ffa39fbf4c20a1f4cf663544bdde0934bf1cf9ea3bd55ee725ab8dba1548036a4fac84d352c067e20e12a72214035e2c314552b7f05e96ef7dcbcec05beae724925ede4e4f3d637d5a1f827d33b58cf96007fe9ed5c98ad3d7b1b5b2ea44b3c6b5319f5ccbfe192fbc07e0df00a83e9ac618afa4f933a326dffc13484365ac7f5f69b7a58a88355c15438389ac8e219062df3b90c57187a648445bd8df0466de15063299863fcb32c25b2723924bdb426f47a2b30d4efeaad71dbb7d388a78a7ed95eaa3cc17cef07059d63125ea7e2cfb797e8c9a4fdd29d32b9992647949c70a71820e7b1fdb960c6e472c9fdaabeeafd5d87de2ea2a0a745dd525fc8c3320b9b285ec9da3bf29e3b8472e49a2300914739934c54100a83505cc43345eb80c0441af674bcf9a60dc3132da5c3dcaf9678d534f4417e3049d9058be4dc363c2c253980114ffe7d94d32362d0047eb2ab9fc1119dd742af7a1830f0c2f8609ec23408afb30bcbfdcec0eb72362e21649aee2daa870ced2a3b52706bf46c1ac6869bb14edfbd2626db21575aae0ca1477c615d47373281fb8d3d7d432fec16b8f3debb4177006fac0cdb9e023447ad3fa9358ad503e4a2e4c5b5bb7c20c867f1181cca998c02752a085d61722300bf62115d90869e5125cba5fcc5ae704a3e092d33abaa94a71870e0727225b811f89192130b4e3fdc038eb66392bd5a653a0e73ecd4f790ab1210fc3a897296376f9dd0d96b3121050ab6aeb56ac35d2c221ea987b231e5a9bc2cc98b0b61e401a2ace2f8c333ff4dba728d816a128f97447f3f72c5487aabd38d096539726fb78188d4a84220dbae5f17c4acc093b36c35d2dbe86614e49acb8ed8f1b8723634af5ec4242e2efc138b018adcdfc688aa79452aad527d777e46a5decce1723cac55f9888fbe7192fc3a3659bc51af5e339c9269e6d671dd686b771950153d2fed4c2c70c18c466a150e36a55b0282c4ac3a954b4d1c07a7ec0f7b9c3e16686d9de787827d7ebd8e760e494efe5d56335043ae71c6d4ecb568b63c8d673e729150932afab5ed7d199c9c01bf908aacd49d68f41509a6ae8ba552929935cd061f398c2694c5d8d23d43bb5272374d2dcfc7ecc7e651efc3664179ebaa185b7257701dca24d360df861fc1f8d905ac9dd9c7cb59462224f82dc8bf0e4881e62f57a645aab6359b3c1af292efb747dc47acaf1e0994aa4696269b2f40c8bcc33b0bc5d59d60d23ffc2b8dbef188f4d935666b69200f6477d798b7b3e10bcdc42566543f80969b968c1127a7dccd190354eaf0882dfa39f0a303790cf5e9dfec7201461291a0976d9e8565f5476f89cbf0be8f66ff7b43c0b5dc17376f3fc47d1ebdbbfc20ddf82b9ecdb5dec45f0e0e6a7c58647470679c3f10a86a1991b1e87213338ec77e31125eeaad7c796a3724d466fc8f542c805da12a4e2565ba7a7346ffbe9ef4037bf4c035994284aaf9a5680acb5d2c02f2afbf08a0a0699e5c3c587c6e9eec6ebf6511fec96f4227cdf0c14c984bbc5564cc070cbf926e999fc3725b01d44e492859af24bd281343b6005d84541942a73530dc5afff8788def5e20f5cebd8ea0bc05b40ea62be58666892374f83afbcddc287cbacb335d25fd10728d985ea98054dba51b359440c4d302ce3acb67099b6403d298f047bf6dd01b72cf0cb432a3b4f8b86abcec852a1b3d68d393a05d87dd7122f7249d463d0dcf32e6f85a924794af774afd38d4c2b15ed6d357c31f14c7cbc01eddabfa574825005eeaf559b3b3dd4fbb6487a453525e5094166765bf89f016c0cd40e4a20b2e72acd619d27a9d11fb1cafb40fd331c8e9e2481483cd4d17b657ab3843e1442c726fca29706d79371b271bee3edbd8347f85f2bc074a5cc0c2e9043949195324725370284f1eb0e869a933a72779d63f38d61d18e5d4ff2ff9a1ca256f3f2e4272b004be8704224246aa3015375514b25ab3e189cc6faca5dd67959e7e8f649d72f7591958475e6083cb2f2a123c0eafbf4b173a8a05ec496e9deb1acca9236172dad4942cd7fbd1df66375376b491156b15811779f0e3fb69d5b7b9f257d0db7247b4a2f1510075c15299758c284844c7dcab0e4d0c9b7785292cb4ccdb05db11d0db21a1cb35a8cc71bbc4caa6436be26b1db3101d7c61e9d1395d23103a66724e3875d7da71b8cb0548ded2e5b4c9e59dbf51fbd9ac0b7c746db0054a53cb727e417d4e6a826c3e12848612885a27dde1fc76dbcb7d5d2397125d207d44c672d17699547ce145e134668a11293827ceebe1283f14a9bd5461a4572e28e09372bcd8a3768535566cf39b9e6bbdbe22a90eed1ac830c9f02859640b9985e6bb729c58a27928030936931953f6a6590c5aa554068aed1e5a05636c1ffec7f07072a3f5120c3b971abfa762938b66a9f339501668c89b23d0feae2b5c8a95bfcb72160cad5514f38b83dd40abc7539407173f59373ce4d5bc03782a80ad9a90ba222fd9dd2981dc87a3d18df6ea3d10cb4734e8b6a7699f4ff0329d415430534d72b82c54e8c84f5ca73a16133828ee91a26c62cf60863202d7a7cb9e3ebb859c7266aa668cc29a353509089b54ed39121cbb9ac5db1420cc2381a1c94c960a3b04fd8fc2436050439a59f9e5814444265c435a003871f756ea555296e70b26ba727642656f71fafda2142f9030c0f666ab7d8e17c9d04d63f6b4593181dda7217220014b68c548aca999a54e073d7a3c513109b0d54f7ba8fb9b856e5811434930c262224fbd18e6168c301a4040606697e2d1a8c3b216f07d342fbae58ec3717205fc37de679a27b7a7023df36e39cfa2434240dcb1c400f4498adc3dd27ff772e3c55e0bf60f9b497411e0ec17aafe85a68ac49ea5af5486f5301e922c3d437272bcfd9e9eeef4063ace5b5ae07ec8116314bfcadbcb9242cc8539b496963672eab715543b7dfedb2c03dbae5b63cf4058fd37492bbd14ef99467c4580904272ccfc9543914f20d1f18b77aab109572888d9ff272a43116789235128658a86721a3edcb4ad1a2b65c2c5b5bc5c15c96c9048e9c4f1a31a82098aaa908be702712f7fc01cba01b3cee6c5f51380dcf6d120a4efa323f11f847c6f73864edf3872c3701deb022bc283fa5070ce3533457817616a24f85efb1ed131c2d4d3c26e72a9caf39b1793585318dc1bd31262d205dccadbea771b33719ee29a31bde35f72c992295e82291a98586b6b41774b8c021711b65995f2ff939a6d34a612064556931906efa589b29d18b50b48a51bf99d2ca25399bff15f83029952282f04de4d501b5f0f42b1927b91317892cd20f76254c102845cf7d99c1d0f0e6e5a0244440d3d74b3608031fa9b68c735db15b9f382933add10e911298e0b486ad9f94e72ec3c957b9404ca762464df6d0641ea9646c21606112c52623cd73eda5aea7b3d74d41be33cd803f4626c01d1890e805253f2c7e2419372c830d6e8a39217e77224d50c538a2fd90ed39b8dccd9709798a2a2f3a16670c77c04bcd7f8542e4372aa700bac4a8db1e170f08a9f370d40f538e2c9810beea87f3d56b493019c99550dbcfe9bd34d37d8512bfae010291ce7f48eb5251a2d3e635e855cb6473ac67213da2c4d8c228231144669b16e103bff260954922efc60ad587563f016fd027251287119947edc4c53fef3606d48e8eed56483e4c6b452d6b3841c65c465993996d7250cfb210518f0d4da0ab9138461f361fb730e660c4ca36090cf0078d548b5ed890e82bbd7546ef3bce73dca9d67d86b833abf922127687b036ffb102d0af4169594931f31a500fbc77c42c08f03a3d46c0731a3703e4df6bb356a4a6872583dc63e965160e14fb39de4b690b3d8d17ba50bf592e23256c724983a4e337298c0c8bcefe22c502e4bf3722029e24d5c9100956c4d843d649d433594972d72e5018c775b8ca456d2629e2b5fcfb0e0e611a8ceb08afdad2ed82834a2c48672fe27ea0b441af04a98381c2c34a0331a251abbe632dafbc02747727846f1ff6c25e8b12b0a8a9aa33e54da9a75f17c0b9107020c194bf00cd67b8e5593f62072daa1be6104400fd02866dee579a8336b8795b168c13871bc766426bdfc014112d6f0cb0bc5f537c7bf5ee04b6e0f59f91923b213b7665aef158c7a5db66a627234608048414739a39e026807f25aff0942c10d226271ab061bb98044980c8b729a695ad00a2e8f113beaf194aa59639a5b581c10a90a261e43479e037f8c9c72ec4c5e1dcaebdc090b260f76ea2ae33652c7f3af7fb36fd14883dbd09ee3fc0c81c932fe71282ddc4293bb013613c3bed7204425f3a61d453345e9cbab54237266c5a616fc597d2616c34c58403aed9c97583eea21199fc4025e4ed3f2e917442ee31b92ad3a5b5061402f1fff13f5ffdf9c0e858940d75c714dc34c577bfd724f23929b3c656b9237bdde0910a93cae130e39f558b7d91a417085a71d2a9372cc137b125c55a236a638386e0469e4748ce01c541a303528056b52c416c9917270b8f3d9ae5e8fb0128a6e1c5fc2cdc9b5deb81caeab139d967220ecf7600172a97425705b164ad5a126ba70897650f45cd5ac8e6bcaadf1837ed5c32e5e8935a7967d9faaca459ffa89ec26ca6a40be419f43bbd88fb7d521993e49ff69f672b2a9827434b74f184c91b03b80784d9ab92bdd91ec15b613e5fbbba362eca67275bd22b4cfa3e0588e6abf4bb8aa0e1dba7393d3532700b8e695e58ba27e85424951b3f5317eb6183d595b321ebb9497c5d3733d946e7ead6a4be683eeb742729f5335b465828bbbf12b6f38ce690821f6272004176afbcdc5034eb9b821777200f274fecf63e41ca28f6e36ef79b399fa1f1d39266c444b4e1509bf6661f61691d6daff8a0ebfe7c7561377ac8431180a6cb1bce1719a87ba2c3b2fbfbe4872cf557f854f203ce0a8cff9134669d0fc07760a5cc5aa928d52f217517d0e2b39296fb2cb576f034c0cae37e3ba640c2d3f9349caaafb5da532fdceae4aae6372142fc24d24c64cb4be1e8fc467399e8d2312834840d2826a6c8763184fa73f72f198c3ec944a732792ede04c25bed54b5f2318cde5b61faae62f69bf44334272de2c8a07d62f4c537b1fb4dd3bad850ede9d95e1f80a7929e18711d1854d9272a13b617075d4bacb2a8affb065096fcb50ddf0a3580fa55c28c04e227b5fec729e656f84894787a4ea871266f74de62725e4e45a3e548c5d862698eb8d036b7251e8e246927d43400401dda2e8fc652ecc07c291ce0e81a1b1d7f3d18653657284add1f1a8244270c917668fba48d819c8fc65d22ef60295047640062eceed4bbc339bf4ad086e9a471c676d80f76503e9eee2d2bafc15647b1383f4995676720871912c3a3736abdedd1d34f33942bc9650bab872219dcce865e0f3128b9f72ea7ecb48970c0f52ac1279423f3bae9d56e80f14ed1e2eae70c62cb596243c726461dd98e8c8806555ddf133761fa7dce138c857093c8a1d2052774473511472b8ce2d06ec140fc6deabab16139f6894d180090d6d976052cf946640d29ee872b830e33706a6e4c1befbabe0887aa4315251d906f8c4dc32327bf9f0a12ee371cea7278174e2e99dcc917d6cf0ffc69939932cf1f832e41fad2a226e8f305931b9e2451d182c5d77726211f1645ba211308b468945bc321722a4ab0084b81f3d8f72984ecdddfbfc367f05b131c3b9783f9d0ff6c20c6d7ea0dafee38359ba72e8b6cd9ce430c56792f56bcbc06109ba75683f243da8e667123e43ffaf149a585f3da3f845a8951f23345f96ec6df5208ecee0575fb9ea97c7d37a80503ced722b7420c2991547e61a1e6fc20afa0426c0cd490d17802903d9a5c4f361eaf472a58ead0fecfae62a507b24c313f1d3e86eea7e3111bfd3e74cc1e29bf0daf972e1ca99dcbd8501e0a34ac5783ab18ad5e23bb3ecc0a6a9bebdabad698bd19772ce955a165f394a64b9fbe24fb342df5a0b84c5c46b34e8722cda5648cfeb44727c879c4bca6c7d3a9ded9ec26953edd9604cf1710c09c500bc651ad937ae6e4bdf473f45eedd05e9bb89d32501df5d2a7bb8636c7e946727b4a0c289031543726040241d46c14d7795f0078c0e06569b3ad3e4cf4993a1d99be514ed3eac3772a714ff6a7eba6b0f812db4437738c28007f3bb84d45e257fb87f2a6d78adfe6c3912368a41fc34057400e4baab6e36cd3e30b0063af358e3220cd4c090d70e544d3b76d4d527373b835f29d3725924170608261f336dfd890909f6143c3aec07bace9cebcd0ee66152c2891bde779b46c6437e356adc1a717515d06413b54b3e5902257cd84fba59a86c22dc3f585806ffacdad2f0f5be7b61e2d6fbb584d255ad6e3dab9e931b5742dc04e7b79bc92e6538d0624e17407e939efbc86aa2c920798b65c53736a7aa44a1800c0e9c79e10acb7c763f676ffb10df36ede69ab07200ccc44e3984c6961acf47965ed0d4ea73e0f80622d82b2cfa253274709f8b729b2f41b1ea7dfd6e95d3638cc52e5c9e1f540c99e2557a070cf7e729c2cae2729948a09c5a4ab65427eb6dfaa40a2b0a3d215dd2736561d36fcf96006f921b72b7544d5e3acfa37fdc198cab04316e82e6458be80a7b9fe335c0922372348372561148dade1c28bbf8318df76eda3b0d562e63a412a63c2c27f7d0f50720d1500dbac123442eebec6f13dfdfbd44a884ec637da1b98206a809d2773ea86ed672a1d047286edb817aa2fa4226a9271618937ab34bd646ca8c6283831e6634d223eca6f0f839fe36dc1bf48487347e3c884b88e1fd523f40da962c98551843707231675c31a1fb5d33d08b10f6499b3b89983b2b1571d5e0b4f0b45fb78556a1255488de5c9fc1fe68d2001b370e19c80466a4c3de37148b7fce24f56c10ee6972963011f5a6a45df79041c4acf6f4b69204d51e868aa6631d928f16380a7f952280c1b2416c3479db2d817d959e74527dc08b8edefb483a68135b3dd514a5391345b5ad2197132646e628b93ced4fdacf6f8ef13a21a8af2988a243c1b885647232daa8bf47febf1bc158a2466f8010d670bde3208b91f007ab78312c1d6d4e244bc2ec20fa8eda515bc17cc147dc6b1f08b6d3bdb173a4dad7379e76bd72ed646ae0a5e8e75b4eaec4477e98f7c4d25dde51d00c52a7542d5f6e1b9dcce39872176d01e0debe4e5a5c05013babd5d4774c2c155eaa2375e6e5a03e977b5afd72fa5128d4a667ff4496cdc22d498a2e64b3e3abf9b6ed887531e167291c5ed772ce3c8b83492b630988e1243d471ccc52481f11033ab22361b875e3f78e2ea9729635c3009903138fa276c2a6bdcf75d8324c9f19202ebdafcb4e95ba9051d972f21cdb9d8d0397d158a222f8560466eb547daf558ed7e4d20a2ba98851c4697207b53520b88134eb306e8749e860f3273eec787c29b87846b171e36d24be53303894d5cdaaebfde2606dd2aeb02a4b218bf4ec7cec04c2fa73ec610054a1022ae726e8e32b87a204e4b7a367f72ad6b737cc71ad4fc0ce0b3c179da04e31890a61be055b901eb141dd0b58cef0b5f70c3c67a7bc2d6b35fb20f0cd22fec5c25850e37345e14ac319f043287e1afcff9befcfc64f81956084134ab7bffc76d84604dee07a5069ad92fe751ea0048a2e7bd0ca321e2c6d8495bf155b79ca633c724888a21f9c3421faca264c31727f660e2908518f31ed0f34c70f617d8cb82e72a77d84ba545a005e10890d17f3299d00b92a0ae85e8599feb0cf9f780c7fe8108e26903b72778da8f4989892df9da2420efb74ee660b80231258071ed2ebd6583cfed87ae2e57a477b4ae3ce912d9b479310bf1e3250f7b8d9bf6dfdcd79fc3433ccd64847a05bfdae68f59750f55c24fa2cc58832939079f9c97d525f18b323a448dffbce049d35e27cb1348820b805f0b68d626ddddbc5683e5796407b691f511d26d3b2234e2fdf1de99a9e528495aa5d088d164d6a458257cfc3c0aac572e6a245da5b4f5152e12ea195d78d4cf48d333c04e031763541be04d94efe2750a732ca8d1e4eb983d089fb49943f998a96fb32bc30f90b9b697e778158c1834bb93e6e36ed3e2345cffbc5dff24737160e6e1aba7f0258f2d6056be4c522897278529d7952c2fe4245332475e4c81568c7fdcb42abb534cf947859225fe0c60f011271bf655b3000db76eddd772b19a96429371b0b510c7757ae54b7fced0103c288cfe44b37b3744665b65492a087635848380826e0c0524abe3ea88ba40a3a1115e5b017303c38cc46c7e8751dba45fee876476c8ba7ec73c09efe28f53c48436921a714365a407e7b8d40c3e465be8ff626a14aacf714cda8d4676b8c3e7210db7ce873082117f4b1d9a13d833c345585ecdf919ef0f5f5fa789239b8ff0ca656854cea16ae01f4d6c6910e529f59eec063dd8ec2b30cd9d5ebf990f4b82283204c92b61e9304a18bddbfff87fe6e96ad440634a743927074ab953176ff72ca6897acf198ee7d0f6b5fa074154ee35065cf44efbf2fd994b97212aa205972b1080e7067eb9f04b38b25a54ad0ec3ba012827a053848dda529cdac819ecd30e3c6ee52bf2551871785ac4a07408c35a9d0b4df9e9246f4eb3755a8d29aa2725cae0305d85cdf02a739c846c59d891fb69fbd6d8d52c4c60ca6cdc98c56153b4a47acc62d5e936f2703e7bf6958df4c01ca94a473a6b4aaf990761a7fac53723bba3cc82a1b6792bddf5f0329e1e1c1c78ed2ba627b131467a23cc7d94cc05849099f7d44ce7af0ea794e40e5262b1556de3ed4ca3d4ea162cd57069d2ef06ebaf0414231d54402c7ab48ee9b5795845cecf515c0dc0c90707a841725b51b7213740aaaee5b8c930b9cc42b8e6902a8554da6364ef47d19f8843e6b52a41014ef5102c4b58066820e917efce500d943d40302980649c1c08d3684f71bff5a72f97539e7c801be9eb5f2f20b925dd1f595d13e94d9341736108d5b80ac12ca72f3c585a9b9e73d43c5897bcaf119cc2b1a252e085b59790e48b47885f767da4358a4cfc2b4255fffef0b808fa7f5c46ba6290755129002b67c36080848a44e0c08d624816cb192e699afe836b3363f51066fe8c55688770e9c62c69ba8cd81728e3dae627a71c557fe75ffbeca05d4f736facf4d42f1e1f5d9013d8a95454a1e8f2888e8a949772888e017c8378993489603c00ff5cf3e6bce7c7fdde5d93d72720af6c591c21c9c31f6472f4b6b91f7cec00b93c7d8dc56c4d381fe477cc772e52decca59d982b13857de3d9e228ab5b9782b5200c03aed1ed2695f0f6eb672c3b3fe8ef7d0237cff75d6121c346fae726eae691e83c5e9ade9e1437fa6b272971fa36727bef1dc2d77e24bb14cec12496ced6d337e85231ed185d476885a72a788e6ef2977b70b79a96c9153aae837ef7f015dd83552f4b74f180bc32aff50bbe1f3d43f5feba18f72442ae23eec0d701a8f45420550540ca96a25808aa122503a6c106b69be814a2217a190e71f6d73e7bd4cd294a4f152643a500dce2272f3fa9691f8dfd5df2f4dadcd8d3cd7df8206b12ebc72a41aea6ba67e944b6a72aa33f11b4f274cf51bcfad419c644520701b37a7f32896d945527566b3f1d135aa4e1be5d8e3c5df4f86e3e44c2594c771f8ef40a34459b5a193373b1102c572a0570daeb29bc9fa089b6d0813bce31850bc892d089fa74e25ee8929efea9f729c78d599feca34c19f669406bf82d80f8a333cf0e335098643e35a4b3563df72818e18a232162dd1d0af630cf4cd94bd289911a4ba71b6a69d660a08f0faf829bee0f09edb9d4162f5ffb94683402cd78432e5179ba99f26b483393454d4e772904a38111c6a65d2bdd199e6e8758334a3836f7918fab86176f970247144d6722da69a378500390423b30a10a36d2406ecbbd7f09f490362e397e266c2e10772d33aa4e7789089bc8e21101483bfff528d196e04dab84a613a43b91afa1fac72aa748af6b4d771f140fbd79bbefcc7a8b42b6f1f184dd53f0fb67dc803c18b486fdc8d9e383a4d22004a9b0b667ab830956ef340ca0478b6662a8ec236efb47261e2e669ece7697509d09636271269fd6c55e6f3afcb08b97c4fe88690fbb972d95c7117604e650dff347aa0d6ae351f978bb3724382e0a9f7bfd0cc576dad167d9f9547f172c67c539563bfdabfce89da6a7bd3c423036c8d74b71cd1aa20720f7988d92ef907dcd7fa77aa8d0c771e28d2f283b47f814c84eaa32a7311ee6eed4f879c016089d34773422315eecd5173cc2fbcb5b3a78941a3a5a20431a66e6153782e473a339ee718a1c570a2bd5ee3759faa3080a5eb4cfd10eed48e01728526fd51dcb9e9736972729df971f4f8caf3405b1e2d4ef9c282923217f61272bd8aae3f260f17cb51937872fac052fe4178936b1fa7f215d4fb341353e1af02c47cfbd859fb5f1179cbcdb6bdedf028c08160c599a74f5b6d95993c06ea7972b14772bf66ccac31e29c25c1c26bf6f72dcc3925220204c115ecbecd190f6872749e2d74675b3898433836b6226ae2dfe1cf4879ab1102e5e620861bef71ea28a518a0a5bf7e0098f489ef1045cf96a0e60d03d3c9c8cabafdd1f4379067d9729eb0f37afb49c9740593a1c5edb4e499918ca079154e80327b714cda497a48636a9a39453f5f717c9562de62ca2008963d5afdb5ddc9f0b9bc3f7a4171c37d729e69250d89ae681ae5e4321efc8184771ca1446576bd98b0744c4bd2b4a292727aa0f751245822b7db2b07074dacae6db6d6f68a68ee5c1c8447689d0887397260c3963f75b699a0b6092a5ee8282b7b3d8e2549279a160db4b571028c721b729eb6942e6a12ee8f89e0c2ba19e4b0e35f03313e6c74ac6ddc68b4b7c4b9dd695e86ab6ee3c593afa464d27fef79a9534478045790685e5bf1c51c3a81292872cdfb7eaf71ebbd60d54a29621f186975b1834ee5aaa1606c6aec812ecdb368727306d8b01c7dd6494c242ce3ffed3e5c4037a1098b0656f1440f0c3ac290a704d15ae5af350207a2993555363f78adf24c21036ac4bf804d0aeb30d6eefdad7244e5bf0bb96293b6c62ff3d9e63cecdd30f2b25e36a5c0a6651ab6af421423192ecd2c8428e2c4c456414201d73b60382ea618c63ac0068a5816a874f9053106f3655685669e3e36ba80a6aaa658e321191b789b627d8f213a0ee0f7d6e2c77268b2e578778d7d0cc8b24ec276185c3d541f5e6f97d7be3160fa6e2786eac669bc2891656e0b5cfa7799b1026b84dc4cd0de7bdfbd037856f064afc53ee751041c30f4ba745162b04d04d771331350ea8bcff2c01e9e5d0b41e267afb406a727922f111d854734223e44933705d5286d352d7a010878f01a2dd0b8d05be2dd02fa812ee0e7a8c93fb0375f5e37ce2534ea1705e3aa8f70ac09f25c625caf7e729848594bd6a4d9501f3d1abb8ac02f4d0269179563cb55cefab2dd35f45efc72cd21479f8c507c331dcfe42551337d0e88c503f97da771567e53d6e8377fb972d0071d8b1f458572d0b010df380521c5d359f68e646c73cc436e3277d83f8c1ae2d03982398c370d237dd83c4274f4322cc4d87e1f724c36c992af121a7e8565d240b3f29d75c5a9a3fe9d0558d16eb5ce3072ae05d11ef978109b15440fc972647ce3d097c7f2a5bbcb0183667a1df8cad7866834eae3040622b0b1e681f02b430b364f72fe3d35d001fae508ecf92f358e16cc87ea1155d931e136a753b572392d763500a56a88096f2e15a4480064b7669e5bc7b0111db923f694fab61f7214a2cb21f61c9a4ebc46dd99a9912c9a669a2fa2aca3c54028dbedf193309d7258a50a27330ffec3c343f01009da8daa2d6e03b8f6dc99c8c94f46d9c1563b7226004daacc96db052b60a7a07cf141fc331a54509def1fc60c565cca88b70c1d5f841e38bde75032ccea8cc7a99922e43ee1633bb70246eb638c33a966b70c72b1ccafe2175cd9b31e0956791fb2f600d4dca395d51dc28dc8fa02fcfb267f72141207ab2c9380fce060c02fdf3f3b41e70f2e39348ccbbe13da1e9d2c4373033d2668c6aacb0a86633b0e78609fe8c47f4f5aa09b9f558bf8a1760ded1b8015c314825da0d72ba290e1687c748bfdad3e450a93e7fa1d3a679ad1028e65d97052b58edc5e9d52ee29b141db744dc1b9477fd9a6252ecbc8cfab3deca5ba4872e78e9df3b0ffd990a4ec5ca9b83f6bcb69cab2c1bd95522697d994ba9f187c724c99da868f9ef7ea06be0ce0b5cfb916d645e4a57b325a96a75d3833763a547218d17071febcd56d15b4bc90a747bbb927284905a6848906f0bce561c1334127293ce2acc53ba62b24c832505eb78d91248f5ca3504893dbd52f2e48c4202072ed42ec36c020ea77d4af99b698c1848605a542b47f3615b80f37628a9107a71a33d0b6d8fab0bf6aac38e75a52c441e027e6d74b35b88060b2d7c11ecea1da1bfb0d3316f2abe7f1b894003283e1d18a209b09f968fd49e69a57fde29426045e2b8a81424dd20a163f4af3ffc1591c40029ec4a7800cf0f27fa5d8b66312210607771b631cf7039ec96b768b887c8029dbfbb92030e153cb38a7ab056b7b81723c97ab135f3a10f589d30eefc2714735fc02f37aeb2487908ef81f97b8082b72e558b1ef13df947caa75d62237f5e2afcceef9bc1df6bbc7b786a0b3793c0404be01f4041374d420cceaf6bf98fa93d433c525543d84da82fd68441663e7537232364ebe1f65373dc2178949253a595ae8abff0dd40086fb70cd6d8e45322772ec9d3d1e708cfc78c48c8423d99948686a17de89d75775ec35ba1602b4866d72f08fdc6f3331ed8571aaf57f0053532ef6e88d7a13f879d928097f1ad12f14721f4b5ca70a6f9adf7b5f71869cb23e5b2621f84d31c07b26a4e38bc0240e0210d8163fa1a2ac241c95344cf98c272e7bdc1769b4d731792515aa6eb115a88f015dbbdf8c5839fb7260a2582b679e4cf9e782529742be8cadb14e74e1705b53720f1fcdfd1c646e83552af9e52d06ec1dd877145b1d6427fe25843d7a947788479c9211faf815d41180e164635ad51ee38204d5ece85d6fc4bdcdee6e56b6b832b4344f9e064acffbc2e85a6ea0659ce9a5bb08bf382d175c7e8b603e146520724e1234715235b045fd6225408020c02949afd17ea52138ae25f5995d7b5f0f234d17435aa261a45d88836d00481561e269827a39c7cbcfbcf506a1593a793e7255adfaf6b848609a372d1920a421f97af5a447cd3fd8e18a55b3293e02b29a577e91e0011d7b9e562cf49f54f489e2a725bd67e02c45e43edb333b43495aab3794ce72edd3e965f38f1226a0cc4eab8bbb919800027009b5628730a3964aa372db9f56484527e896e62d0470a0df221c99d154b4aedd4b359694b2b82fd0ca72921ae071b3cc001cc5f6d17b755ec956c1a08bbd58f05f70effa9998c3e423669f80e6986a1dd73583de6092d2e215e61725c261ec77b217753ea4ed98ba937294acaef28389dda3be5188de601d02e1ee9b9b5970a16e5e39e49c660dd09f31e7d357114acd3dd5d819cd8b86104097ad6f692951a44992f39fdb500aa79953fe94540371c046fe17ce37629055ba27ef67337b9b12797f60db3744c1f9736364d2f55d9fe018142e3f136d384a0dc1e3e8f73c72c698a95f0bf017dc640172f15221ad6214226cca399da05b515492d296939f6011d2ddf7966c1258c2ae1f72fd60225df850ac6f4c1f18282cedb30bb47dbb96511c82800773dd8e5d982dd59cc8b778c4a8e1ef0b548bff48ddcf36572e434b81898dfc54681847602b72140807690ea63e9545bcba6024720a8a0485fb792c54efab890301a8edc0cb7227f1c760f47cd5748eae7f58581e95bd4695e4fb73c813e07cbe94f489dd12725eb324ff2873e393d66fed5e6e594335126a20f82a001506e5ed61baee332b524994a58e8d8f0a02419ac34ae4a1947cde4fcc87779ad03ba1aba35c5716c4724e40608c369b45645bc1de1d74741cf5254965ffa7d20887c217bc11e035a158a2e4094c6139d867db4caf1094b71852036cc111c58e7d3e4148d2e9f4c7067266a2435a53b37934284956a5f3564cd951cdcdb1b6613965bd65c95189ff3872000aca146ec4ff1dbae371a350ea6df0eefd56db14e5e5aaabab5a6b59d7ba1d8d887d786925cfe66d7e55d29370b056910ca08fe0c08b8a97241881ac7423729cc180dcf37d4bb9eb3150ede1ea4b6b3f93bcbf816a475d938ccb0ea04efe111f79113d4597a7a5a80eccb588bea1efec812fe5ac2c50ea54b91a3355d993726bcf934f490724bcdc28b7b77df998c99ae547e4c2001e6f10357f9c883b122a79cde1995c02bf891aa2c64903abda8958fa59c1130ab964a64d9eb6d4bee36db6f79b481c448e30438c1cc9bae7af23b859963d298f70feafc60da973200c72f55126f0e442206cfcef1c3160fdeb0cb41f3f87214f25ea04a55f77f9ec0172f76c147d93d495881b7c15943e5e72dc10a6858bbf37690fa8f91ce7d5cb5772d608b338095003cc1df5e5345c2c19948f665b0db14f8809a4ec2aa086dcba268c1122b29f857c8082a4277768fc670b15b3058680072b548d5d5d73145dfe72d51182036afb30d306f3aa8d93b80f884812b586bcba46dcdc877296e758a26bbee71abe7f6c15ca0152a4279a2f6357e8f37a1e23d5425401aa0ce2a9de6e7227fa27428717b0eda48244acfa5943204f1eb3efb1156e4a43ea5fbd19560f67c415831f1902fb9da1d34b99fb4e0d58cf96859f132a1b34affda48d18b0e5022c1ba954f267f014987cafb5c9c155357040acafe9fbe5cadd073b52f3aed96cfab9cbb1ff58e7090f6c2abf9fde5831fd011c03abfff277f4b4a22d63f854729cba50fbdf0ee3c15083ed18f379c393da918d66e340da67a682e3d390e3827210b09af9c0f0f57a7f999dc6fea8f6d621b30617c6eb910411cd8d4c177260334f1e85965ca30ed93a3a032fd5d4c9360b662ac2f240fff39518dfac5cb95003c45edd62eb471a7c3f00a6b217fc4164a283abf1bd6646bdfe323dd721c1d3728f423ab297ba15c3310575d13eb10e54bdd807767730ca9712378d6db296247218cfb19ec5791f6fb945c1d346a315f886c7ea347cdbdf268141cc84ed7bef2f4185c60fd96a5ba04c37e0c678e5bdf57cb8a8e1c93abbbea07dfa3f8b69246106dcbc49053e3b893f8043658f09b4c226667ce51b6d43f88fa32fdcfa49f8721e48c1d58bf493bb84594afea7792bd7509ca9510c66c4de4f87868d827733727b79d5cba499871a6232c007fafda8a556f7512738d3f4cb466e863b280b87726582a322d17a1ddc190931bb06c8665a31f8e51fa949110f7643273c9d2ac45f016b3814cb70cd43705f3df6ebdf9b3672183afed5481fe98b11a25d9b141b722d73f0dc3377feccfa3719f6e613320ac1592198715303450e8b4e565337960c837a018864193d32f16e5ef690397ed2692ac5e8f7df0b73b77f8c46bc56ec729fe0a8db696ca0a0739a1cd82bd25989c70691f4eb3ecc6cd52b491e3d920539ca2a94a6929f4a53fc0fc9d639e3f89e7e2db0ebf2f62b50742373f51fc8b472324511d575d89a6170bb4183676c505c5bf0a9d5a19a78325ef193c6f58c6472e6fe4a876750191d04cfd2a7e77b7f3c700be84f7331463b8b4f1b80174cec72aff2e3ea1fb71a1d5b8fac1e22dd39bfe4cf6bd8436602d270aa8518c9f3fd727fa2443a82de5d96c353ab2647369fc41b3bc038426c4a06c1d5e17db96002720fc45c3f1b598d55a369c7e35f546d86f21cbe346bf7e4c8c0f751d892f18b726e653ecb8306280f1b040a4a9831a276de1745e25d5592d2ab68747a241cb601cdd58dab584df83f3b4251eda73a57b2fb730708c34f0975d8f62beae7ffb82005ff910b6188d090e3cc46e55e3214ce6a34e1f05fdd1998154caf1fd67980720fb86d16f29ee76d382ea14d1a733f58d55046d54950ad31a0e2bcfcd7672014e5ac04078b810433b6055e30bef12517e212f2939c7af231f631e4e3acb0cf035a27335c1dbb9ed7acfceeeaf8bea2c89648e78f0175aac5545dbe25053e3a0242119279f3d7437d41cf263f9c7dae824629f8f17780aee540024e78789f1319cc153fe48b2d9e0860aa2b54c90aac746e22e696ef5fde272f5f52f7e6310f72511292939603bf852d85d8c8877609ab99ec8c45ecd6f899693b0b5090602d6cf5efe95d5bb7f5b33a541079dbe17d81727f31c7c2de4d94c7ea0a62be610a72b6ed7d29787ca506c260b4f22b07025ebadca3f0be6f8f63bec6633dd0fe96325cd18bac1e06454cceb1d66a585d037858f61bbdf03d372cafcaa48fb17d9e72fae9c0e0a57925f087f865fcb2101ac0ff52432f5b501cb42b062bb4f494477203ba55d3ccbc7a594c91ba9d38488264ba3438031cafe3aaf706fe295c1b677221c51ef66d8399b79c66dba8a89f66a01df70874734105ed007cb0e338f54d72d3a4dd7580d20649a332f1eed423c14e29e51af842c3023f4bc1ed63501b9b72d9b5ed2b46cb149408b94bcc9e902cb9bb808bc74c47de2f0e1e4a06d1fce21a3aba32832369631c7c5acf52d0f137304a99154dd0bb7d7f90fbbd2dad4c4d606b6171a6b0e2ba2b3468faac3e9980f5ac86285eb8e6ece6c4d83ceb169cae72a1630ebcee03535c377dae38cfd8eaac726639a0d5803a591895ee664c216d720ff5c219f2b7116749df285911b18cceaa8a109ad2452dd49c56e200c7e4fc728597a05e7f4c8ede6c0afef4b9304ff31b58fc8c3902459ef4820930978a18721dcbe3ecb7461fafb8a39fc13a89af52e580f5697b220c7183741dfc3f6f907223abae99658418e97853b1711716b4da0de8aae1ba9f0ddf0a7aa2b7ec9596729bfb3e12f4af85712fbb0177a3d795663b0a87643896e5db3e73135f0829916444bebdd3129605827420239d7f4503a0cf1d6de91242967e8692e98da26bbf72e2f90144d5028a639e824186f22206937ae8570a7b6e5aa3c63382d9bdcafd721e32957c3fe4fa1a91ee060518d1f0bef60b4514278ad141853b35025f1c62233d289a8bfceaf18ae47fe2456bdc78690bb5fd870c98044c96d8cb36e6497d0177396e308846d6538ec13f522fba3248d147e068b90791f28814583d77f2c30d8eb81650e164e7b681cf4900fab9a9df3446e230dec31aae55cfacff87995172e627cec8f6b5ed07681db8ed29133425c17b19d29203ba8c560986d6d8d0ab253c0c834300ce806a43d00abb892ec23af722c75f93a4698c1f218ce439b5e31f0f38ebe767e34feec14bc13f62ccb2e7e5ae554e445820061222e25291061a61e0ff6ee6f35240fd7027ba681acb69327de819c12fb59d106993504f54f10107de6e49694f7bb314b46e1bb4b1050c671f944283d7017d7ff0663cc69794db211a99ff080de29865c5572aba01f26e718bfc94cb24390e36aefaeab50544cf721508a381070d8086fcee72200af2a930ab5a12927e234b9d0e824f50a27f7f728f9dc68a339562a71dddab58a9bd64ce373d7f31ad745428c0f4abe507d3dd64442ab3cc1b9c83465947d68b873f413cefc0058eb6e7c34d65fc9a915f06a67244655716791a68dcae8e7d58f07c5340e2ce13708232918e3885bb9c8b855c70875663fc84a1965874b4f65d108cc8de1e8d1a496069e82b2fd0082116cb997282677bac00744e783fc32855be9d3af852db8443d508b103d8eaef79f09e9772af630716c1f41a3902e9ff73c6ff7184ac039e00234c627f1823f02d2039fd72af9915f888ff911ef2b265a36535300f518e5d727de21004b575fd68dc49d53201b7c8c82ebb0db80c06d5df3c1e7e2ba0dc604e1373883fbcf828883997cc27549ec48707d452287160c2f2093067f5c9eeba12b5bca58ee0204e41d5e68972b981b61417bd4a37c415d45b0fc22ad23d93be650c1b5fc49a9c436df4b768720e63d2b397394ffe6027f1d6fcdd0291293eab07754610a3ce92e36f8ca81a52fdb261988e30ed2103398510439416219f99c50c73e7d61b038a27c78e3c9972ff7c827c32d3ffdd70bc4694868d0fc316fa9c8014f75047c3301f324da25e7202b0dcfc8b5a015a016620de81008bbcbb8023a129ba6c942fc5c47436366e723eba7ce5ce702207bb85416e2ffc438aab159be06e2f62ed5a3d94a19b486a722000d5f7b70bad06d0b2484f698410dda6f7a1a1df5299002c42c40adb94ca72f9cfa305e8faef4493ef5b31dee571bc929b8fe61f0a6f49cc966709456720726eff511ac460f141d55c826a36914d93f024a95ebf7b2a493b6eb9efe421c372db8f914e791eef4b7e89ee86b1c39fb23bcb25393ca1cb5803ba36b899598249e815702328309437f8fab34651ac6e350c4e1ff8c57bb267c4ad52866d50b4676c53aa9e22b94d471c35033362ae71de5c2e3852b5d4f42bd03133fb1eae6c728db3d44880da619c70e669e80d1e460bf99e8f57d0459e665769eb2f2cc7007247b5663088125f7107562338c602447a81ab59f434c8f8df13ca42386ba12072befced48bede639081209e28449870188c925ab65949a29249edb90781f5123ef452aaaaf213389e9418f6652eed8333375e48222d09604def7ea0dfdfaeec72cd0145a90697f27fddd5425c32c5bf34ea0ef06473fae17cd376722d5d553872e98e1ad2b6ef2d5456eb88099a08affc67269adb85e9579de24cff35d1cdda72e68da06d06e924063a3bbe458f172e73be4d3bdd226b2a6e416c7078af233824d015c03150f161d79f111b28da3d4b462aa2eaa10a24a5c216a03fe1d3cfd272b9fad8b5c4016e1b57534f71a546b11f7d4a3564191427c884501c3068b2103d124c81a7da7adba728839931cebfae7a8e5ab647ac9d10c583b3f887bdea7472d1654ed60a881ae897b2f776703f3d997dcd41bf3b09cae85f425731e9cc9d38caa7acacc27338548bee62091fdbc68f8003cc0611ea8c03fda8a9e51add2459afee2f730f8d91bbe5352ed407913641a406f9dd81c66e3d3cde9a773ad2576b2b42c318d9be8d1a1f41b1e500fe966b7784fe1f075c23568cb4819e9d8cd1687514e914436af3cd5f47408e5b51e8c98a6e74fc76d8499b09b681e083af1f29855c7e9e901d89180c8b2d3697c4c8b1ce1022d29b10fff49a4173649844ec72a3c954d626547504013ecf3ff45831350c379daac11445cc9b74590713b74a72f948ab966d5d5c2a2d59578a58d691ce98cc617ebf438654f7c3721bb8cade33d451e408fd43dd9c76e7cbbd771fdc85b4f1efdb70c0f649a583995741027872a941f4f056b7b4cfd69d83d5571d1f4c6f0d9300e7c4d572928ccffc796e677206c570e5649078a4a16e5deedff9c697d94d36c2a5ac34ea5272e904f45fe966dab0b59d1e8b8ff14129bb267375157730686f37ec1f6b3cacb4ad0b329d68722454cf288a30228aba8467a43c619f6556b668b2041983a265a1f14696ab4e3c29bf9d4990f3706af1b842f49a0fd7a6a3d5e355721146a3756e325cd1cc513f5c9f221b6d8ca17d22daaf59e1de0faccb125f12fcfd771a49ade5d274f8286b7e8f238b38b8342d0f779e74ae0c1021526a6e74f39eab310860f56e0d13054a612ebaff0ee69b0c9f6ce2112dba7eb000ac678fcc5cba5dc8d428ca71eb0f1f0a46deeabf40e240fc1fdd29b6766e517552f636154bb87e0d19c79b66193b722a574e3ebcac7b4993454d6661ccf67f27ab3ef025ce75cd1b8a8a58d259ca72ab43df0765766507c7c870eccbdfa463a0bd88d9bcc227509ac54c8bb5f75c5afa06efae58637d62295f9703a47c99fc3de0578dc6a96cf3b623cab268d03172c7ca34b7b5fe330ce28f1821d28aec31f2965f3d75fd3abaf9913754d0d9ac72c07d5d2d830f7245d0af50b7028338edcad40a9ba095e28f4f8178f9e7a639720ea7934f74b66ca8db99788159daadc03a7ead3e2ffcbf8f1f43fc504089d23c765b04721c9fecb33b95d22ef40cb4d8f7a5b9210770866aaaa05669eba89757df58609ee4cbce2f13a9b973a2fdb78625e6e6d6ab071624e50d58d304ce7c7267b2301c02b17505c858ad0b42f6d8acd5681128a73d6958577580e197dec372fa05a82bd134107637a16d43ad9bb718ddc60c2df839527cc7102acaf59c007237abbdbb6c2acd283f51859d5ff8ebd6365f31b367aa299c43a1a1334cc6f23de65ccb12014cf9ac30b79552190761bebb66e4f06e801dc34edadd39ae2ed80f78c48a80d843cc21eee8a857b8c95bfd339fd34883e7d5b4ba57ef6204147e72d00943bf248499dbe7b14b6175a55f36b6a0d2f9cbc772724e930e0ed6feae064f1c826a6b229a6ef68ee6123fd0e60ba0f6462d63493923b1f4e3f3a8af0872f53f2b6b05860b2a60062ccd16b7a153b46d6aa546507f7d6140a1e43912e8725a8d8737cb5270952926d176eb7a8a2032494537b3e1a6a2714ea7bee9098e72f949416c7ed574d7f1487d956a1bbe391a20e4a790df18c17eed5660242dbf72af05ab58daeba94ac8eee77e6f1161f7c10ab0d36fa461e35077a1ee83365671ae6c59b71746237e9eb8b1d3bb662a27ae3dfb12f8a114e0d02c8b951d20fd6f84db63638def96884ea72327ade64d5bd5c5677a528f6dfbc8b68895090c515782c478127e1cc318739a3a6204c03e581585037d7253e8ad0a29719d3ad2dc30121605745696159e12deb0e156852f33fc79b917faa2305594d9fb9c8188687278124a995437834b7b6eeeb37fe81d6f1cbbc57c675a07c53c0458dfdf28ff060df3f10fe9d7910dabd2fc1199cba3b3bc246f1b4609fde9be100c2d89c1570d15c965cc9a6763af5ea9ce22490671e531b4b76fee3403ca74c538a1c4430f55de46acd815371a672434a55742cf233de09a93c782c0be80e17dd734124acf183d7f9f8db3edad3e29237d8a50aefe5d00f379907f3f0efab033a9763161d8726f5ca5ffcaabb6728a090172b14c225343834616bb44feac97f6ffe8723b7572615beae3cc8f5ae41dbf5ff9fe60bd58949516bb7a0e1bb277cd4f4e7d6495729060bce80f4565a67a3570e9e37b92e6cf6bb9dfe56feb5ecb137990c1cbef72faa9320446f757fad663def70037e31bf2f0b7e91e44d08ada228ad1ad8d03726df913a1fa4956a79a99ad5179315ba312aa25dd9f98f058432ec9c326185472294a5266e7c7f5fe166f9c07a8327548f4326a6e5e71186ce876483687d6ba727ec1cfd0ed4ea9dedb98e57547ae4f6792ffcae3c3823068eb012c27ed3e4572ce7f1e6aa1f6629137fcfaf425eba2314cfe97810f24231900de1975a290e02491dd74724022422536f43fab93e104826f4b04efc8db27df44841fe852762c724a1c34c4eb76736163eb92dfde1f5d99472c80fbf14f82859d3f56f737f07372cb05d1204f18f631afdb78b044392d87cba734ea4d7088b0cfe3ee1186f84b43fb249d3f7e4c4a1b4cea8d8d99d587e8ca15de81518002fa5e3bed438a89977222edaf224c939f1a7f8c6d8c203132027417a44effd4f11c67b4a3261851127257dd5de280abf7cf24e6b8bf34833ee1b30868bb5bb61c040c61c10fd5bf546b9439725d3afd56ba5104ee52aee1638f64f17ed656f723e2b18e84fe54d7a872a52a2f943cdc91f0264513c9b29985fa118dc4823ebc6f67a8730498be378e72c871f9f9c07ebd94c96f36bc98cb409096fa320ab0ef44ed02448553428c1472eaaff1d97a0ec2f007ec3956b5f8466c795b61d1154b4dbf5ce061aed71f5572782626c56ba27e8a1dac8b8bd967af665d335d3edc0388dfc1656b19bafc3e7290ec35b7040f4fd8554a0c1f49fbe1f9c34e2f076fae816efbc51ac342c91722667d25977db53f475dba6dd9828380fd5cae09cc7864930000ba9bbc5fd862729c80cb089a536eef900c2ec43f3c99e9a638d6fccda73b1249fb6ec9dbb2f772de2521b6ab13f4c2610d8fb1a5814f9f36136b9426602a0d3853ab44a408c3720c8a7ef000e2e7b8bc7177b5e1e5c02920ab607d4866f7ac3b3735f6173db972da02638df8c138e5323d61b88cf088bcd49d125da7651f2c132366d254496972af701ba210ec5a610232802c0e2762d93ed245fde39c3b15a426f7213e75d123c8ce6a2ac821cb7019a3757dd691d2c9f8dd712327b958f7d302702c6e5b197275e32f2fd986b22bb929a963670daa2c6e9158c5a05a2fc73a7bb20aa48afc184debb8a25248d222be00dffb9d688fdc871b0b5aeb369ff239feace3feffe17227d731a716594135d7672cb9e4dddfff2bf183e85abbe1c5b48b7a064d0bf972e595e56c39b37506d7de7c81f2ce7cacd06bf0fdc16e4736ed2dbf0b85b3ea72da43107d494962d298b843f460e624091eb33496eb470d16c2b78f24f93087119effc68517d0078cadb0114a95976220b448044aa129dc0f3a0ac7abc33579729fefb302dc19ceb55d2fc5c8339d4b7434c4a4df330653fcf096f244ecaff5729579eb18ee3ae3dcca180e383a95524dedb319cd9fb7b68aeb3d1539579e6272a3afe7174e41fc87a5aafe068a1b192db27996b59ab5cb0aa1c3e0161ab30f0f50fdf32e0bc6384ba1997564ef34d1dbcccd5e60304c6e9c097306c89f5c547298f0ffd82984d32ee0b61e65e3a8084923f4d7494263faf1ad6f841ce242ab40e56f46904751f6e1c23245cbed8eadb597e4fa3c70651bf3ce2227822c87d56bf07e9094ff3639cb8ed5002fa44d6ad7f7772e5c09a41b31cdd5d137cbd2d850221addc57c7622a9ee325f559d8fae49ff1541812e77a0cb9d0146bb587a446b69cdd7d05587209c9ec5390d6ae5c71c17996b92118f5bb8ae8b10750cca977243dcbdb6f1404d3f4bb6906a09d1cf04ae8150d99562f49c2c0b95ebd3462f55dc7495ddfbc30dc459e6b1ea0bfa37ee001979e05cdadbc655611d93842024723144034965b9ef8b5bc7ed2bb7838f0958fdd91c63ce10a6c13b0e99561af672c1e37bedecfd1857c08ac67f8a11bd28ff1a9d45cffb179476cd6df8d40e5c72ed5efddc371c0ca99d77fc5e505540fd4d62cff4fce97670a0f12ae17e56be72febab7fdbf7d32bd193f1b93552cbc31170acfa4509334b34d161044a7b4b47230b64cf439dc3f1874edeb840fab196f6b810a7a3656cde331b8c7b929cb30729392a3fe50e3b59cdc4b3a6da5fe929944e8fe697a11fd0502e55ec9a01f1a726d25a2b12c80b871248bfaffb25a5e54ed17203e921f6706e9bc7f68e875cb09d3f350a48663d85fd552f53dc2c8d7a50a28ebdcb393c65f9c99f9e238b31d4592d431595cd097f2c3c4021979a6140407c72f721539fa342e095eb3108ad708f90beae88b4d1794a7646b2cfd9e17e1c4d575fa3cb91d004b819e2464a18d723f70e23390a5bc12fc092e27cce0b0c6c60bd1bb42ea03739bf232d6d136a2727183cdeb91048fe99dcf3b87f6edfc3572b9d963fbd4e4c6222650fad5fe9f726304b81f33f52e3db3980be6eb4467f63083474169e5d9688c5a708d7fb3361b84890c31569f6ab6e392be2def8f2b7046685404b50842bef884255add54b91256894dc2de9e52a40ef7b02bccc27c2d429b9161f29f4c1707ae1903f70ad2625aa46dc7021447ab421e2b13ffbf326cacb1fa866441a96ba29101a3daa50e54d259474e9552d511882bc5b8cfb9b960ed9f6a82736d357079dc89018d73ee3035dd1d79b2cec49ead9b216e2791a47eedc1b431ed3d2766c1331e3eb7685d72b744212c1ab5889d1b1dccd6d5f12bfa6dc418174bf18672438c97c3d7324d26c3028ff61e20d2f2d6e5d63f93e7768be95d8c1fd3390f6fd1047d8ccb134372db334b9642302c1cfd921392acceee596587dbb741767737011f567a049378728f79525809fec38af1999c0cfb24227523327f2ddbe3a8f2857599d28746bd09b3130742d67b3bcbcc8ec810e0e8e7daa4a2daa1d7e4058de89a61fb47918635c6d542069c44a924af940a10522cda17bf6939de073e826a474619d789058e09904fa08595fd16198cd28b35aab5807e94da2f941d9b206c9735468cedda2e1af3765772101b556b65efc0a7361bee67bccfbb4c74b1cdcdbabdddbdef22417246eaa3c243e8d06e07dcac6422ff043b368755331aa3e91accb4212a2f735231e068296be05e257b4030d98aaf74b7d5d5504b822eaff4eaaa8d1ca5cd2ac16dd243e2661527d63262b5041783bcdfded603c351e8e59d8c0a2e8f5a58ed035c16936e94c357b6ca9c5d0bdd9deb570eb37701171ed27a78ba9ff01026342b6c2a727d7ec2ec527f21fef4871645df6aa434f560e73ad8b73959f5322c6f1872288f70267035cc429465548c87544ef6114fc4370e670e0472b3bc040e710972d06b025eec9bf9ed69ddf657dca51cc8f67a136bfa104e85f41e863ff171c472a513a889bbf0442586fef7ac16acda49c823775b24c3a9886d7f59ba33470972a5df20e276aba210e38853bb4a0034e2fef19f775fd75769f1e00053c283ce2c623eeae012cf1851d527be9771066deb89d47375c04a106a5c0f4df59bbc1e727cf628f3ed0a3b6b2b5d808ff8b77cd556823deafdc13b88b2878f2e43ccf872816bef61985733c566bc527db6ef1a3bdc8a1334bc2bdd9b0d5fe1c62dadc442206ed4e9a6161fafbd184d120b38a0c917d4e4c5187dc7053c3346b41d1fc31b078dd822852f945e7d326d0d907c2590367026ce66ad3d2af600baca2b0f7472fc0085182e8675dd077ef113b1c6e81aec3f0a95c3ccca70b58c972e44d71372f504d160aba33c2c853b7a3b702e169c6d571a629fb383b52135e978745e157271f6eee5737d8726d038072cfa3a642078efc46802dae033c6b1f87f4cbff1371e1266093118c2417d98b3f435b9d7d118e1953dce5498caa4280e6940007f724aa81b4c22b57166d41852a0ce10cd83d5fc4cbaa93a48f80dc0af0a5aa24f7281e3ec4f67651b1a6186adf658af237943f8952a76be8f7b93338c70190e077282cee805fa7e129f4e5c097d4753df22f61313fb9e501af461843018a39e96729820d1427598d82639e6124c92c170c60f055ca565e732da5d192d4fd74f9472d9172248c5f3a8cdeb7e59353573d3016f52eb7568233679c99b4560dc3df67214b59d1f8944a371465ae30551b96246c72498dd3367a17a49d6958793ca2f72bb9cb15f16090334a65624b81981f0a5b13c73b5515c9925ed9d8d3d601bea7257355b2d28656215f5d15ac327a0de508a376e931a547ea7fae7e3fc72f01c3d5c8abd9f40b7adeb3aa8014eec764810700f4a5c7043839329a4ae719cc5a872929f425a4b14346d081d3a9e50871c9a9f5c43c722f87329f6ac0723d9d90072b74036dbbf34d18e8a2aba0ff1812a46a12ceaf07d1a28a1ac0f778d44ad5072213d0f3917815c88e55e62ff1fbd7ccbfff474fa29b91132fab9cb188d9de7724683505f23ee32976f73d51e57303c5bfd8e6024ce520789a2f62ee11d393872051fae4212621d01d4ddb73b0b76f18e4de8d6b94797fd5767c9461f33316727f2aac902b212dac3732a23355f5fae7c4a04323434cd11dee8a29cefc433b501e546a8c704e554601beeb4a7b43211887bf72db64c8783da087755377570b224d74efcab2fe75b63bce581166ed49bb0b5e85b2cf2725ca85bfe4a66ceab8916d9e911eddce2e8b588f47f30ef4addc546eb6ee7623bfcfc884773a5e3c1f472cd5bee5698081aa7a710e025618deb04a31b063f49aac5d16fad67aac72d6a6fc52a181ff83aadc3ea42b126aee4a92ea21df0264ba03a64490f5c427e3470551d98c6075a30635e630eace6f0662021d7136c145febc0db0c17a7cec4fe6f72ae7ea33ea491a52b4c3003dbe2352d019e3581a4dee8eb4b49f0506b9457f23733ccf98193f2ab5c69306ed9385fcaf30644c83dca4ad685da5c5143d7e04e2055378103257aa4d03ede353df48d2a6989008a84e2144342417c273d143c357213f05bc95ee62d523df3aff7f753a2285fe53d3c06e7612fc485d83aadfc9c725dacc09ec6fd7b1360593222fd30483e98226960a2ef48bd8cab3236fb253052f78af50d36573ce6cfe48ff2517d02bd5c69763e8b923d077d5a113a7f063a0e86dd112877274fd3bf2d45fd4c7ea919939333f36321255cfc0564f2148fd17246faa1fe3b993dce55809297b01a45697d1f25ccc0e9b8e2b36233baa1e48c334bf31424d59caddf8f59399923e87b38ae43823b490f6e5005fbb43be019461f1c79539476dd09806a20dca021973cfe17d9052dccd4a245eb3d39d0dabdc57244b9750d91226084aed5e6c81af5a0960eb4be70d10d115a5caf9db4dde66f23f0d10cb6979ceab5a760c256d0863eac4ebdee7a639af3fd6be672863d623f34a97d1af3656502764cd82d5779c3330e4d34df866013b8724548c781b6b6614235e123e79971d64edbcc9495fa51549f3c60ced7cde49e7dd0f3cded61279b4c0031c196ab223660945bffba400527c400e18a47b54c2e7c5e36c5fa663e5372f706185dd3984e0d42e92b5fe97a0882b588e49bba891ad3d49c6cc8925737723e0092c1aeb89d7b700f1dcff14d23050bd1366edba7b6b2e718763cce2c1065d89211a5331facff20231037ec95d77b8b88c455813e374a371ad9e702a8380adbc17c7e9baaa411d77d20307aee4647623d23b623b733175ff9a834bc7499726fe38859996fb856d7f408db3c4174ccf20e97d45f55911aa30c9e6e88f616045b7d7a6ef7945f1d274a30e29674c481b0ff04964118dbd70c7ef296299a932272cd77a1840d7ad776621ef59a87756db0e1ff6c51bfcec54530e86b244a7072762ae2e47e777a90fda8f646dbf6bbfab934a43f1e9e5eec73d02e6b7b49ea72c925158ba7cff748900689bd05fb9f8721ede9442ad9d9836114e813b9f4271bc4e177d1d03fd054b918e360f2f676d482ed72d5e919446bcc77bbe6549fcd72e2a86772924bbcd7e8a029ea73c1ec6c1953be0512fb28a87f5a3366ff2d1c303e6fdd1ebe9cf86f91e4df81c4eee739703e5c247d39ac1e549bf77a6dafec66fd29c205aa197a12e9397243bb8a89178dfc905e3380b0203e0921ea8d0c3d2f09ac9dc97d4c3077b77df064e4f01b886d1797b609d53c522b395d96fd087172e6b2b5db948e1385cf8e38a202d62e32f9be06894d2fd492c7123896023fbc721e93a99381ce1d15ac79adaa746e9e6debf030567f32582a2113dc477ac751665385d1a16bdc1c5f946da46a6c56b2f8534aad0453ff7eed9b41f480c6c1967275ddfa17f04f472c8e26aba1cd8457a7ac1314f38b1109b1562f1ddb5b9e68722388eab79c926490d3acbf3ed72fd4262338da92d9d025a4a7e3f8533b410b72ad83afaefb7d2dc9529322e5874a142a0d6c3ab7687600a5b8693e6a6e2bf23eda6c1312ed430867143d3395dc30adb8f5473f6cb358cee5275ac00e8c6ab0720b2e0eb7252911da324709f24bfe23d0d2080e0d06b1fc1f56f94d7a585cbe72abf59723128fbc816d8e5fc19aaa467ec79303ee8980e647acec52958c635a720b94bdfa72372e3d467c54ced4274af3c3dba51cd11f7c2096f12f71385f5818ed441683cb8f1f3f2278081e44c181794ed58dff94b163df1c3a3e3823632f720913f8a8777f8afcc4545fccc233e0a72ef9815502de76c81a764345e9dda64e48e1a92e3f6307b568f46ccf26e4afca7d71ca0a4edb42df929bffc04082532150ac2cca65796942725fad94e3ab9702cfb4676ea6daa017ab49199e2d7747725730cd7cd831914d952be1cad510e1c9257c765931eecf6f98be2260f587ef72d00411ed49409ee89903e56419c3c7992cd87cc41ade2198b29e7a7d9f18a272e162ad1239e2b07ad4e0849f355958ad0f7d0205b1434c01b05339ed2a47e772d37c51d111ffe3daf4f57685eef01cb3d99f9215f2f0d04bc4dd01da56307a7262aa3cafe8a4c690fbd27686cd157289119c15aa3c85a9052028c70917a1bd1bab8e7b4ec2087894874b7f133c5335ae07c0965853ab644595a676888a294768f982cdacc855601e03a3a6db441c57349795c67a2b01c96890a59913ab7ff872868ad389885318e6d145d3d4bbf7a4002f09d71e155cb93f386802bc9167de7267e2f7563457f4799550edf1eb15fae2ddc98fbce7044b7656ba86b209dd5a728fa4f3bfb44ab152e413ec0e40211d0e71651564939892cb57e842495d3b623e427c8db17680d601409bf861f71332d22aa5a498be3441163d7395c16e3ba0721b1e04c39f5005b6f0277f50d0da9628144cdbc6d62c68eb9462c20f13b635678868d1272e1563e44b9455f9f557faf4963522614460e74063aa6859fc10b7720544c2d2100b7a74f37805550873a0d3cd77c553065b1b4fddcf088a9e6bb87211951424ea99b3126222f7a36085f3dbbf3cd137b785868dddc9f4c720660d2a0d7bdc7b83179aa5a9cda94f0f93e8095dd8c362ab0b329f7a31817d0b4990728012e20c3e91d52ce14f98cfca84f654a5363811d3cd9001a8b3039710fc2172bedd92055d5f1b4b5e77c8e84518c983ee4e02ac8f7294058624641a260b617298fd507c571681d4e50250743df56bf1694cf3c64c516488e8cb204ae6122e72d340006d2504e255ed421f733e062451e0bda2f9b77ac4f12543b09d59bddd725f9cccf4890d98356fe0c783d09e42d53adf5f7017ea9f92eb98157c3ec77f7250bb58661e7deae7fb6e06d1dac66d73f88355a9bf034abdf1d2851de1f2a449ff7af706f32f5cd4545b29951382b1e418d81f88478dd3333813ab1952bb1f6dacbe37293b22a21aecdb80575422cee2472b82f05692e1574e8db8fd0d455b0f8994bc1ae4264909388c526be17b53f2c4ab59c583a12f0e090d316f56a5ba72639766fe390a55b608cf047e1d25c6900e010f4ea468ee9e5e13385ce385b372132529a0686c9a9d9e729c8f7f1583c30e03e3eace01e31b40f5011c32dcc4726dd7449428e0212a03b8eeae0cb918cfb4f3d301ab60262142eff77678094d72c75dba112068be0257dcf4d7c9fa91aadbb4dd1576fc925919121e34234e727237df1b269174058006cfde290ad2f697306a1636b7c326f06e807d86155fb672fb8ad72ab54477f464c64ee703e7ed7630c106250d67d56f200191ab294a4372ca15449406f59e5aa8a661ec21848b80bc222a9ebca6e4d168d02de473136f728b96c04e5e319ad0f4322e160a805f8ddadd6592afd4453cc958b11a15961e72af16b11a3963db7e4feecadfa94d28fb12508e7f8d3bd40d5aadb72f1e278f72f5d0c736de90fe8adb872d705c6c79d7d8aa72903c72a572ed3c4d1bf32fa50aac5648cf1ef2daa597079b2f003d97abfc97b8209bdd6ae0fb5dede88af01f7204a30174444a7f13715a59d1986ab63fff4c81536dfcddbdb0b259c6f0ab83725b1de96d29a5db87b010ea526e9078d79fa098f315a94596b3069b441af3b57203797cded99f01f53cf0c8c99c58b415910330bfd58a44a4f88393c6fd43be72bbcb3ea5b11867aa9d19b3304aa69ec04665a23d1b554a5b0a3b947c6fbba972b722cd7a7991dafa72a9b2aac04637596cd548b4b781cb6fdc08e0456580cb7296b5f4d164307382e82757a82207fa9103b43a664ddcc31e24fa62c4d874b172fb00c85842dc258693c19a5822d419447a010155cf353fa93ecce200c3312c725b147d87d06cbbca270774efa3d038038d30ff5f015c3b7926c4b5cd9aa7685e6b11b5409cc07f500c9a4d7c1c4f4c03cdfd62f85e50900e2fea63886108633e87b6af459af3dbb99e96c9ce04a1bd7fd91f47195025c35ce83cc5d55874ba72f19f0da07a7fe3f7aa11ae71ca014fc5f6d3043c711d72d5adc8b0dd9ab8db2e4b72aa621ed4d353128d4d829ca98d106a6b713ded9f6477eb610140ad882472530d1f9f2f9736b8353acd7ab22153bbff2fecfa3b3ee0e77b6c233157059a6e5259aab2d09b21e887877ddbbb70297c13988aea66c7ddc165fa5f8ae104bf08e567733498b77116feeba01d0d0c1a27f108f95ea1cf97ae49ecfb35631a5b7289f4f93c535da232399cf2c6c26d93a3f389664d7c40cf4c079131a2935a6b727a6826a194766281df080670d47d0f1485d4b3e77bcd9a0ccc5c6168c55f7572d02131f8f5f7b04aac66336b61e956625264bcd8a70ef3ccb20341b9da0a8c3a0685f1434df1418c39e95e4904b6ba2247d5b82a775001576fae24562ad3613420c3200c1ee7869ab38d1c2afd936c0d3ec19e6019cc0da82797b7dbebce537280a0eb5ba5c9549ed4e8b75c448b075ac376b9a8a536330a7054b820a9fbd672ba64479686f395984bbd24ef869f9826002487fb6a089ffdbd374f7a93318072243c419a6080001f506568c3d41f626db7b3cfd605251ecb7a24eb3924a8d339a246a34d33106971f2f4aeadfdc776950b80a5ad58810f9ec96f0a5ff0fbd8137a43e25619a2bce017866ed1e6c7b522c25e8ac6a157cd42e209d7f9a74e1772a6a73c373f73d27e80b8e4cc8642060ef8d74b526c5b2d37b9ce43eaa02db07228d94891220832e67d267fd31af53409ec2cf74345d99cdcd49a244b0dea41329ba64a9979bd3ff931db1ed57ee909c520f9b66cdd896ea797cf439c83312407ccaac3aeeb8bec92de1efd201deab19e749102fd00867ab1de96f2b2570282652b6ebadb676ba4c40abcf634056b668d6240216bc74a456733dc45efbd4f8a72cdd5fa5f3014c4a54ab4d92616088074402efb18c08a86da8cb3c8dfcff39372afeb78fe1c0676cad3f2493cc85fc2339a5e63ec840d16100e173f58445f36726f03ba25fba899997812f60d5fc4c39ba5cd28a6dd3ab674cc739ed005c57900ec62d207cb1edb14a9acecc0025fb7d4da31ce2e6fa52d6a73d1259797c021727cb5a9eb021645d4e8ea921333d7ec3caccf71a32aec889bfedb0acc4537235536e955faa3665e25f1c05cbd03b278e6b8e1a1ffec6016a4a5fb501bdb758d504c3fa19000d806881c0ffe96a54aab10413466aa9ddedc01e65ee854605b7048cdc7531e1d463fbf3db0c6d16cfc02a039e95bcdc0134deb616fe689d6fe2a2a2c9bf8a2259fb79804534f7b80ba25e5e69db3e32a0494fa089f5d769cf50417dca865f8284a287412bd3125bfb1ea505ccfe7b13cc1aa701bea65d8614f1f7245d1fa9743a8993732e90510e4319c9a85658380cffc499f3a502074f3968d7239cc455b5203a1437fdfd6d42a5733ef26be765813c4a6be56df355d85344e728e5b542499ade37fdd4cd28b776380668ad9d54af2466f5e5547cb4e1641ea13bd4dc220252cc1dcb3fc5708d38f189250af38c26bbb1e85b7ed150632c4ea726822a69b431dd9c6e10fdcd36c21971a9cdf6870a3e7afe86bb1f27f12a7687253d8d3cf45b706b91c10a712c4ec826b79bbad9f3a5a27d0a9e57723a207f3724cda5027cd1a6073e08ab11db27fc67fd33e0e2f5d098e4d2f6d57529b4353725a7b8e47dc6ed1c55e86dad1f70fb5b43367c9d78b80ca2d05a528aa10d42d72af39e6669b499370c2123818503ec79adc5d637c51a042395eceba9aa1b42472496cd561673cd5ccdf78273ee3a9bb09897c6d4bfa5b9e7c0c785d6c0569a67244c3a1c1c044eeb484b25bf838e03d3ad0b109fa9539c5ed039e9345b5bab5722846a127273aa232de318673d1291b3a5e8ed2bd361e9a61ec3b288ca7530f72f9e4c11fa7577d389c855d6ffaba83814864dbf42c81ec9f83162bacf639ff72846a971f2b932756a586d02ec8b430b8109278eee27b731c2606052fba6caf72e8c9c0ccc16da6a5f82498ea920bd6f4528b00af0ec9e8cad98492a7a6028572aae67f1e66d69cab4583f7fd0bc9f2c79179a354dea6f9352affcc687084ae72bf9ddf79a3d0c426fdc727460da90715b87781cc600b2151a357a1799c4e3a727bf2c5c78cc8af783bd4d265b333359a4b97f5479bda7bbf11001a28d09b9072204aff978931f662317b7e9d9346735a6d9b6d80e02f36bc57e78f28c99c8a7237ae045f8554672b7df2d29c9a8535f3eb78cf0d819ebd6765dc7bd0de1ce0569a3472f325483c78aae72eef0b6eae7231ed43734a1a414a7b6db99f2b515f7274d772b8d08928ceaac5051ef0db8c6484b9a6477ce09965f1b0a6c2288a0772c5d5b5b9d2f3e3e414aa9de7fc847c23bec5e821593153fcaf539571b34c6b72076237b4f5025891eba485ab3e0fc20e2046ec7ccc5ab85e4b293d332455817286e515121cc355c673bc9df16bf64161f6085bcd929cde70e9d5e8f5a32a8a72a4491914d86ad54eeb196db1afebd744c0e743dd581e80a78e4692e19afa287270f3bcd00dde81f2f2d6d42726bc770e68e28a87ac931f14b84eedd1f26ef061fa2050988f7f1ee4b8d1b2f5580ad8bf076fcc6fb32c7d5fcd9c363ed415aa72e6ed29137a78ec00817d6a3d422f757248823bb0c727217690590aa646a01f720b063038fd59723e04fcebb2ba6fe8bae7c3ddca965b9f90fca0bd10d6d6b1723e74d1cc93998bcb44324ecd2fb41926419dd56512af3c12bd4ab83f0c4abb652420ba017a877bb2cec831d60e82c08e7f05293f51fbe25d4ee4bc1b3135c372dba33b7b04ee9de0b7fdfc577c7a05e46bf6bebdb970be0a484dec8a41590e63336a7840cf93714c21c86ccb451374289581f6377b2e51eee251cf0e788c3d04910af6f1b2893380b39b7cc8271d33d86234dd3a0d46162f08a0604d37d490601f322c29cd2c23728803b9765654652d93a04bba380c217d37b7a4548992b349746e6462dc90d5538f2a62b182c617c0ad5c31daeabd0e31742c4c060df900723bc4eec77b2af46ec98c26641bf407e1c18f83d9ca4d13a955c01ca8c3813672e5824219723d4e4c32df686c066e5252f06dfd72009aa1e5ac609d018cafd156129369a27232f59a7f75982cd801ff884fadc06ab10232a29422a6cc900705720db677ac42f1b6765caaddde42fa245ebf63240c4f37d3f84b9f539eaaa9367237d6aa9456fb49b0775acd530bcff323be7ceaf5b5fddb9aadf6551751a9b35518fc08f17eadd0c50db8de3e68efe5748deea7fd3edfa19a6122a0a214595f0d122064f95b029da5b24ba7bf8e01454ba08b8d158a0ec451a273fe3019e28a327610b58482ff48cb2e1e6adfbf2b4f94a4e1e4d9c99ac37e45abba7ba9269a04f0c753f7ad7e2f87503af807ae4a957929b59dc1d33127d93caa5b22aea2fb0e244b8d58113dd430f003dfc919b946bce51509c2b48403ddf939ffd36c9739729dcf520b05ab5d4c7ebe4520398db8b547713ffd2aac9690f6d9166ade16fe72e44bd253d223090b1c177dcc898b0199d754b1fbeea3d347b4ae44374fdc5572471676bc596ed4246577fc03cd69885619038fe48f621a2ae4c0bd3f459996038c74cdde8b93758fa97be275e3ee2be0da37d2aad2c331339f5d9dcd69435d72c35286c9995f2c0fd91fccc77486eb1dbe59cee849dbb56627565a4445f77672c85fba6ff0ea30e9c72127180ccc14ce9fe200b137be7c443eaf12f29b7a977203fee8dd8d5a9779ef9292591db347590ee1b71252ad48d69a65718b64465b725ddc115307c5b9c39a2a8f0d9cc0df58b94ca27d11a68a1aa133b6726bac5e58ddcab93217ddddde6ec5fbb76c14f819b8265eacf7dd2506421cf0fda4dcc76274ec1fac076adaaf3adbefc1f960b319c182fdc48b3da317dde6fd58224094721a729f430b2db59473a87253d7523a3cbb8dde9b082bb99a66b5aa68d2a65e72ae2c79d5deb62c4d86dcd55a82469e980d5ddbb26495359ad9be87774fe42938a62953f1a51ee7101862009be3af8a22f62265fc1027497e736156f5d4c1f33d4219671bbed00359148173afdb1d9f48e7c0f77a02f0b7f69b15897230f25d66d509dabbdb89f5b2f85a772ed4b4a63b24fb53500550e5338dbcf74d330414440114ff2330c5cbcb3b8b4d199d7abbb9897f93a5eadf02c3817b606f38b61c72531f48dec1b2d1deb5173a231f862a791a7251343a5ad5b240cf9e0713c3531abd70c5285c27336d961a1614e121487cc4bd6a48f631d2326f0f7dbd06754c7258f217c918b19ca987c38488df02baa7089b3a40a116e85f41a033c9ab7eca72832d338e97fce74a039f9d6b381c62f2097bf96c73f85a1232e12fd9d2b6ed72643f61e437afd82e30b2d7b50b0b757de7541e4aedef234aa893167723357e556f0862730b14f1f7726f427af781a3fcaa29fd499be90bc64ef59b0b3a4e910f4210d00a094a1ae7294a4e1af7b25447c811a3ffe9f1d454592f390a3856390c2c270a430183f20d3dd8b7e9f8a284e9da37424289e00cce8fad5dda1f11ea72b10cb0da2485a6e47196b1c85548f262e909a32a845b7e145314f356a2d21a4ff1627a3b6fca2defefe0dccec07642c38a298220753829360ba743b495db6f72619ab2c253cfb29a5f84c871eb3f4d04bbbcd824b519f27d45fac506b6611a72e04b1207950ee0ac4becc1d4cda15755e9069bd6c923bc32a9f669bd53b2e323d0de8b89f8db44564c8f416012773d980088b1235e8e2a70a6e8f5765a3b9c240218764482f8e141944d08776c43a0eb9710c2b3d48a7e5461229647c225f113a343f44ca88f5aff3bc9e11deae46e6f2192a60b7c8f2f5d4fda9606d270317019961dfaa17f5c3d5de658e9ba1a3d8054a1f38d57ec6dd9903c51a06dd84b729265058f36c24fdda7966ecf91eec30326dde150766d42e206ee1a61b7c6b5723fc91c985d4a8f6005341ab12c8697b4aec17706e623a39de63b2653fae5221140f180896462e9cb7d22f16ac05f62a73383b1be1e118b7fbdcfb26efb10bc72195900d0d42094182f1a12cf73c60d5770080dccb08bbeb728b28596ba09465207d32624720ba64318a5393cd9d8b51a18daa360da533fc44e2ad711fcd0c91f71c9f41ac2b71b3c724f986270930a44a7a8b50fd1b39f1e58b383a7afd47c729aa52e26bd4c8f43f7cfc2c00705b4ddab7ed5ace83bf21dcf8418123da87129beeb7b9c58d64bb374a18e599ff051230fbeab47967b3a7fbeee9cde3e5fcf72eef2dbd88f6a8140cbbb343cf8fa5ac17d2df57643fce5ec8b9fc341e2bdcb67c938ef2893a2815fa1a1fad5846246eb0258143d0de39ed669886a411eb9906b74d2235eec61f8c7e1fc6cac1415d27388e499afe4d438a7abded458affb996a96fed5eeeb76b605757fbba18f6681830d9b46c5e5cc2ba82765a405b1cd1e72a00f8f1c9b3cd987d1c41519107ddc238e7fe46364ce8b639e4d59ce84817072bb1dd5a66a8f715dc59a949cb116a76f0ac760e5b668b1dfa61d4ff15413d772494285558e7ef5f938f97882284cc5c60da55d776bba283587dd5db4de79de729af47536ae58903c7c3d70d55e6178677d29214794646dd6847c3e1f2dec8f7246b3d8917c204f50d9083096664712a49a38ea33e4316f09024312229e3540722a9ddb26fefc28fa5d57f408912b757fa32581a6719ae53033844d9b5333cf726ff713d640e53fe5c895a7b2e05a63c45a8265c15f22e5f9104bb826b2b8f74a7a413b1f4b12e11ae596d3493af4733fd634fa2a7621f0e43d604719d15a2e7290518b6f8fcc202250a859e76ac156636e61c12c2b7db4ce712593f841ff8c0520b285a15b7c840d6bab998dea7bca3170633eea4258a470c28b3b675ea1ec72097869e49576f67465632d0527eed96d8717f27ce93ff351916f8220c6f29572671ca93224833aa8cad64e75d7a5754a84f5fade6d9b368944b9659c84c75c285dde96349c5a04fc957d6f6982ceba785566c13b98841ea334ed555188cac2348b3eb7d13e6d82251f6c871f5d0ce6e7d337c5ea5422d14ebd49447c7a86da7259bea54809b3be1b97569e712def1ea4b53793410df346a7525c6123c8a32772ac8b038c44dc95f3f95b03a46b57a9ae0ca2ee82e1172b6bb8a5052ef5bc187207b5b56153c91bf17e4777e58e7c722908e23d69cac878b9753a888e3c1b3e720dd31fa5180b45c93e5184c6509903194464068b2a887b26e2ec0a467d6287604ec66cf3344690814574a7ea560edef6bdf5eac5880e95141a26d915dfff7c72680887842e06f04027b8ab0c38c2641c3f7b7d115525a6dbdd5331a090ea1c72fd711e9e7be8688f4ee1451d504c343c8f637b8e210d65385c77e3b0eed2df5c3e34bc166a87be7bcd487cd8209a2be2d23f1481896ebb9d645e0ce6f701bf5a96fc717622c7c4d1a836d9cba18a73253edea64541e2b815a97c92e642ece231f3d6bf5a68fd318c68c950a51653847f294ef0b62110e6c1aaea7dbaff67551fd4661e7c017006952cdebcf3ffbc80790fa80f5c06c1a0369538d716f4608b72fa7f7fdf32a8c3297a4c4ec45fbb3a23bde0de334dd7de7f2c05cf748d4a7372e37d07d0744966bd8c943e6390f29d0a7d4be8f3375a9fbeba0c6d42be779972dff9e5ed1a25f2d8cda93614fc2013de3e297d0ff4120648a0ac1e33acf0ac6d4c65f97f96dba534e778363fafb4ef07cebeb18b0f40093244829aba2f9a080663e743e4d54aba37c4732b388ef9e94f151d5a702e94309fdd14c625d0ad9c72519a11ecba9f975a3ea3dbc61800333fd1427723b9044b534a9c14e6f8866472a47440e3416c620954f69bed877063e5dc19119955ce331fefac6580e20795157e390d50e884c017d99fd2c692564110a35077612648546766d4e97da155077211dbd7eae894273e168db05f188b68c58f0a5091d9007101283c1b35f09515729380e64d556597da92bb844cfa887b55f9b8ea00bbb28dab9b7ad93e2cd7d172f657e746ea73cc59b4923239053c5664479afcaba031d23c32607829e7fd6d72d5f8fa64a944c8a81a7b97113e1ed82c4dccca377f20570c222e44cf28060b7250dfb643e6f1c787e02365fbb6fa8457ed272b7164fdaf9ce8d5739d6d7d340fcc714a5cd503d92bbb6c5667ecff18094698663203977f98b510e3aa3788d272576c2873f8f71056649ec861794b77150836ca2a43fa7bb2f8d06ce58b50a8722f304717e768da770177a813698f902c77996228cf700ae6d7f30758ebac4372a832e9dd2f2b8ac2bc13d93fa01a2a44e441b7cbefbb6fe19518770c952ff87273331511feff1332de5219eeacef32fa03fa2b76cc97b37badc74f3adad3ed7296cdc9f68338d7c6e3f042447e1b10eb7f69df6d7adfc935743c683b377bff72d1fc2e658529cde2662cde2763539f9c27e23ac290c34e5b049adae78bd01972e8d13d1f90e6752054e1d4a67cae080a200c408aecba0044322482a2b720fe120733ac942191889a6fb256f671409a99abe32f26273a72bbe7c9905a30594172ad29349bf83ddc39487f23d8905868f6b7cb3d0c44e1277d554dc50f7fd3b7477e4275cd999ff2b0d3b7a90d4c5705a5535d665876bf8a32d354ca68f0963b72dfcb35aebd7a8181e0f8e19f415debeb83991ce35ec404af3284899de546a4723b7aded8ac63f4da2ea57f0d49080def9ec39ce337f4ce8b5a8da78c142e34018b55fd396489cf0815d4f5528504fef7511231338428ec91430d8bff540e4959a2866d367aea9a9ae8b2931a04076296cc00ace27c0e3c020e5baf30e2d2795af273399dd8c4fec108db49603dc31fa1c3a55e15008ae1b31fca80ea31843c5ef01d10eefb5a23a892aecf93117f4026928fbaa0dea1620f920a558fb4ce4672a6254284a325ce84975daf48ac8ef64a887bb624d0196f4f404278764ccec0008bf38742e3bf2714125137c2829c0bddf3b9357203228b84fb8306445c4da772980b6a3d2cb8f2bdd32e744f4ccec1a5921cc9cce55bae3792fd83ba36448a3a0deb082a208d124f331fc038ca98e77e0675ff2a90269cbbb5b724c3a6ad017289ca118401a0980de4bbd8ef068adfb5c5136cbabd6bee33575063c018aca172dcf288907d45713d6d06ae4b3b1e1c006ac8fd426d4e489a41d99696ef15e97212fae895c94db4e4ca516fbde23da7f35139719390868abe9c392bf3bcdf1f4a59edf38a38ca122e85ca2d10e8e650fd6eeb3518bbd37b573f34d73e13d87139126c4c26806766494f79b7965cc4ae61b92581ad534f8548b5a6a8b2fbd992725b55e703893352fa4d9a6181f8bf675118d52b56e52424d240abc5e773b879728280f3df66e1df5bd24e679a55de6b0ce487dcecdd2ecc9fa6373519eb4f351b82fcd7a1bbe8197c3605d10ecb232e5a3c7040441c9988e9d50c48c95df12d22c9c126280c7f6ffbad5b715798d47c30aebe538cee9944cad72544997bf0c7727bedc95c97ef91139befe50bb4d041ada4d53174e2545dc41d863b23a99ecc724a1002a7a2bcde797337f9912b6413619802885c4e994931fb551abdf4e2b93edd97c119cfdd61b2ecb21b9dfb82df5a83714c60ba5b741a230f8e005efa71727fb14e5e1bd6c39f055f4cb2976a0472b82bad2584b30b5e716107acd71d06724354486768541a9712d5d194c609d4dcdf9ffa39f9a03473a9d705ebedd54772d0def6ccb923fa473d567574521c59a7a1bb52f0499d11db6bd8bc4dd3b00023557d55d58e5351e4a2b4bc2a17c7914250ed39e09b1cfaa5bc02c7822e7e24720a74fbbcfca4b4bb51c375afe74a2fb1d4b96095c054878cb6a850a7e548ca7221dc59b6da5b2253dc0b3dd7e150ec09852d5c780bb7ff7f056e25d5186eb0722742f99529bf62c5646b27ff3140bef5ec9f401bcbfcb54c74d15dfee45dc372f75f119a01147952d444995651eb71cb40c805e3eb809512234ef1d3b7bba07272f00b78b567471a059d3f8266b2697d1d17e9e4360cde6fe5800c597d82ef72559ae24df79436e0c16476261cdeaa729c197cc72a8d3f5ec271e3f3e46e8a25d28fa053aca022fc776f7c623dd576bb18e98bce4d54d273d06d90ca7bbec24db6e90f37f86b9b0bc113e64cc09c80929181301b11a3773b0ae955a703274a727566763f56acd2c3fb3bb1ff1aa94c2dc472146d227671fcbc2f63cb0771c3720ddbebf6a02602d5a30bcc2327915842b114fe4e0214a6950016368fcead017258d2b27800de53e204ead8f25726ffa74c3b737a454511df9956d7c031584b6bc59e45958713479e5f1513c02521c0589316dd40a54ce3bb2d1a008507cfb972e7c4f7b5e9aa51724b6d008f3bda20cee53259210e72e8cc628cd4accb983d2c5f84e27c39103ce17b040b355933a357abb880d65b424097b1a18a906c614d3c517ee6eb23289fc2b1bddf4443fdd2cea3a9a2bc318a3d61e2573b6330e5be726338dd9dc5b97a0a1eec6e6223a12def8c090ae1c5346ae6d8477bfdc4042b7294d3ce796752655a409aa430d1812ca5f5d99e56b39e7ddcb257a01fb3832072288ff87c11c399e3443a003d8c5b83d9dc752ef6b7bcc3b629111130c9b0ff088ba63fc5e20d048ddfb9c81276ee1bd84c371fa7c9969a3674068719c8599f72dd0dccb81525cd5ba2954ba0107efd623723b4c07506c99aa2da993ddeff69725d5f7fb541309eb22184b69de21dc615e84a63eb372718db09003a0d66369d60165d6b43bbe436589acb11f1c895b41dab82b3a35ca7e78d14d826fb7698d072b20ecd1d2fa508c849a86f5220817782f0ae8a315777222ca33d2595991db55aedf4e7592e9d8b088cbe3f1acce1bf331aa5f9b2fa2165619affe22ec5e407724bbff5caaa1c3c62baa8ebf16f3f242a7e3534996b43c3f1cbe6ad2bd0dd1272842470927a0e43bfc9ddf5cfd74ace5b7a50ad2a11acf69c46ad96b770e98947840833bb58896e0543dc5d0415f21c62b6fe14c39f84e40f5bc186c1bc838b726a03081bf40f41a42a59c58e54d752baf7449312bb13760678d7d8dffca39f726a96029ca8f0bbfdf2e60b52df06c1f3481314528e794a90e6cf21a25b29fa72c782a3e9650d9af52c0a5412eaacdff21d8ec83ad2cb4affa5741f54cc9d010716283aa62f370d547c5da5ff7e62e051788c7d3956a588059f1d941a0a412667724bae76d4347688909d64a1daa12c562c204dfe70c3b6955c2109ca50349d7213225983985a86c6df5028cf5ffb36008e265f3f4fdd8a26ebfe084518ac8e724c61123de77289bbca32197493cee3a9c1c552c5969f0eb42ea8ba5d85aaf562185fc327dd524efd257116d1e3fb9e655c4cab774d192abe385f8d1c0bbcf672062a0f46db1304b68c78f8dfb3b1d72b58dd524b81a0c9f184e52a7ada6cc16c0defa65cfd1f49391ed6eb2c91699b3396acb207bebc1ccfaf7a1fa94d87de726ff4ea7c245cdec907bc4572c925bcf5a5c886287cb88bbb0352225f0c5fa8720d00446d7c2275baa029147738a245dfd20eada490c1bb4ae622dcf669b6694791422045671a16c51f9ce2582222aec1e1d20f36273e7e1722c20af5ccdfbc7273417c9c46d5dac470c65e926dc4db416b6e77395b708922fad2a2647ee13b1c5ea8a4765067c20e04efe08f716a39eb3e27d56a599a8bda84e05a9c5df72e725158e0361b4cc8c099341010fbfcd7211444351c6257ffe2f8dabb358a43625fec3da96040b419f53eaf07dc6331f6a30c875d62a61c240b4eab2f2f0694f77241e01eb0cd19f740a44bcfaed808a42ed71fe439b48524edfa8b6628032508236248bb5c745c24dc6b2caaf81d7e27d69b4bc64eceea93e8c074c6f2626f67729e96e37b8299a7ad77fde6dcec9d052e5672f4cff3fbc433e5393e5cb25c8d72cb688e24b2852728d1bf2868653167b2d59639360a1efefb0e21a179d4153d3aef4b60ff5a566c229824394f79517dc0afdb5cfd945f3ecb153bf73e96874d72a8b652eae86231320341c6f62c15034e9b79856389c43113e830443b88fef8181c91d1ca833afbf7631564e8b824bbcd10dd4a0e9cbf71d381c363fc3363d264d8a8383dc0db13eb3a8ee058737df8e874526edae0cceb4619be9816a355c3595dc7402e02fcdae1dcf50ade36780ee3d8da8575a9db7510f2b588505fa9a17296aaac9c73f9d2904e65d90deec5af92791e357bdc67045420a752df90937650b7a028abc33cd738630707669c34d889ca2d43665ee5c45ce3a2370aea6c12624536d9abd51e2a90da189a5ad0ecf720b85901a8a63c32987cd277638f367c1563fd60fa4b1440a0d3b6c1ae139bf85379379ef921beca185988441007d8dd19138116b79efd23b7f88728a1b324f54c623e223cbddcae2b0ea6f6133698ae72f825b7dde8779e7e79eeec44f408da44f6a0e323bdf360767d2a9a489c4ba845ef92065e6e53f395ec5598eca7f3c9f432a73f7b2e6df8e228a06239697a8e72c35be8d4964ca027c41166a0a02bd5f2234216a91398c3e39f4472830da778721c77a9f14beb90d7ec0ac99b0fa92ec00873fcbad79a1e3631e5e163a91675330c8f83ae52657bf379a80ece7de52af3da8b6819aa9f2b58fa7efabd6ab567721c2714c88779d0dff4ca0804a02e2503100c56764d1c731cabf867fbf7dd0c355096e6334964c1dfb452850f857f1e7c34c78cde63bf94e6455a239ed636ce3bd35b35de639c820e64c32049b3aeb5d23637d5802bf7cbcb20c030f564079572be6366f31c57775de8cb0ec95eae31f152a165f3f0c010299f4f6f471cedab721bdab40ecaed8f373e5a09f568edd19d7f12987ededb86339ad4142f4d35b336685043271923305a4faba2d95b63e9a4ca6077ef3c0dffd7fcbb0c674faa93728aea14456d4cd451025b32195c86af18264ff394778e8cfcbd88d90cfd3b1d7279567dd3e48e08271403929569f90f8e75ebf8d31a32342d2e30a08225b85625b44b8ecef0c45fc366af359cde8220183dc0626458b7c507dd63fec89cc031120b68e234630a88cdfd78fc20f6b5bff9c3b91a6b050ae050322836f7ced84837a3b6c3d50815f7fedf8a6627b6e790024369f886b88f5d09e60298887821d97269452c60c73ae8a1f12bcb0ae6d53900bd582afec86ce8b2802cc7bf4c6cc253e83ede89194cc7912545bab77740996bef69770fd826660926496c589b922b6fc0ff6e107308bc32842704d3235260896ad15197d5697a5c88e47c20fa9b2c726e54ec3f9afd0204c0927032ba2489c6454d8427c72c4990865d908337193d72997419edaf72e2b1c6ab3d66940dd5db096ca39e24e4e9e6e5e5d121168bb04c3839a161d807b6238010ea8816464f9cca1fbdbd80345e06876e03c9fce968728aed683d55ad241ec75ccf9e88a0a1e7f55e8ee1bfde16247ad56ccf5fcedd3f26685680bc8df40f11c50d2638f835733de8d1a78ee2103107ef36d91d8496726ab4ece30a6cb87106d6d560dff7d4699c705bb596d3d8c1ba251afd77c4b40b5fde65fd539f4c249930d2aa80d240506352917731f0bbe993d4263977ad5072932a206fd4c1e054126a5f41629377a8f0aceaf5308c197c79ce1edf858639726c1eab74871ea06a42b5488e9b3673edd7f58e02cb75524cd10dfca8f05e3820354a5c9da36aa57533afa41f4633234d060d07e8f5bb47b42fefafa3a131a772cfa64a79bacdf3a2490a8d7dcd279bfe8fa4f9787c99de927e9be0f9c0fde872d3ca8cfc754dd0386ab3adf7a4513484b55f0b7fc3fa9f9724adf8aa1704ed7271ed1b44018968eeb204f3bdf6e2575ce25f2959bc2f18435b018783822d7672c72601ee45dd9d66fa5f895a05159bc656825de22291c1fa8e3baf96a205305a17f04d68a4ea941f9abaaf3d49958979140e6256b2b4dede2f29698c2cbe3f7237b00eb0481d214184553b2a160d3620fa85451541c1c0dcbd72c1d5ce1b531a6d4fb71888fee2750979aff45b32b6b9b42efce3de675a033e4e84764cc9087209d620808a639a52fc3426800d9eb9699e56e0dd07764af30a8997534c05580c8a32d5c9274407b1349b9925ca94466be6a3cd0d66a3b9241ebf6d60e2d23372affac13563aa70f6e2d463fb15fa7583692dbb059a7830d73a68bfc35ca3314729c04d988d6e92fced1fe52393e342f9e9abadfd9140db100edcf236484aa872d2490b557ab8dfcdf45fb7f4e2199a5375d4958f43f280b1398dde6bfff2c641c624c932532bbdfe3af6cacab8fc25355797c690ffc39d3aa7dd78fb496a827201d40b8843f1ae321f6978a20ef64e501a212a4cf4b432632c52d648bd09e342cc1698012040d763d79994f27892bdf14411969e6360b3a5860dcf30b120e91033eac4a29d8acee4b5be482e9615bf05a8c19fb8e332b0dd88e6c85fb589af72cee3944acf16c0b9b458bb41c23cdf9e50a0014317daf586c7359cccc4c9f61b6cfc07be9ea4523839e296f1876c134cebd904012fe3203fb467be9379df2c6ff3664891c558136cf53075b6d4f2733e6c50d7e782ae26f97fb5757e47b98472de45a652713d97f956d9543f737886e8c91d72827fe748f5c800c31e7273d07251114ffc0206cf47536a46c0ac50617c7b883e04ddeaf1bfaa6df7ce35f19c7223c65b259f420f8bc54634612a71af278161f5bc4f6bdfd42c7841f6f755df7228421cddf6d4adb17f81c3033c1756396d2faa90a35c6670114648e956aabd2b1c748a880484123f7f41e14b1c469450a01754ef7ca6f73749cb704fd99ed67230ea23f5727a8e09d0fd0d7788a8f29fea937e809c58efe9ee1ff6ba85fdcc7248f8316b5ffbba63954da03fcac0c421e486d24d1f4a564157a17d789fd87772087c202853aa0d9e80e84b2f9c47e0ef176d2febdc65d09667856392e5214f725b45b249afe43e630c91e6b258a56d6b6cb10fa4796e7f428cffa966820fb4725f41079ec3a518503e554694d48d6bc7fcab5da1675fe2ca372bf2b811db880681e46f19aa26e810a0d3089bf344fab270311d1b211a6ebb094437edd0007472df6bd83be4659186dd8a920a4ebca4354d615df8303ad15652dbbdc289b3856de83c8147e49155b12ae69b836a2d66aba8938d51f25d26634f3d38ba5ac5824f4a842f031c9fc5024b698c037d5730cd7417a9b634b4ede2703e4fc6100aa30f57ee0ab8371a976ebb23cbe55158d28ce44558e055a1d0c58c0357c71ff778722e9a46d0b35a047e435a4f8b9b31b3ff62e2f394e35fee7d5b3265ad99951672f44781c2ecd1160cddd69fb1d250fbea319bc02484807569a81be548af0779721eaacbcfe942e931fa9f6c2a814e0c6bfb859219d465f9137f03cedad73230728733a94533489621a431d628948515f9038644a8c587d8525cb31e7542d9de724ea5424b5946f8a2a10754e9df3cc5a52f851f8c314b39b022ed7b280ffa4a7262ad14ad05b53e95a41ff5ccd60a15bdcd76502ea50ea51c2fcc7735109883722a76cef2cd03c7ddcee56ff2631091448f04dc7d65d461a88876a5561004da72fa7a5926083379aca0ef83ed381485ab7b1702d4acf7adc3e6e1ec49deeb6072d3143704c1d97a9ee68ebb38fbce06de752d4b466dee66a18944fb5924e3427241b408eae8b271b888fb52575c82080df106faf01971bfe428ddf24959e2f54d3f68a8fd3a849902fce0cb81c715b606bd2f827b9af8f94ca9943103d215b17281f12e83066c90b78c5970dbd9afb0e5985a97ed4f9a04baf2817eeeb5498f1daa0b50c27f1cbe5e4ec2166cd7da11b74c2479d6bba2b68e89049d3982f6746953bf6d5924cbd13e5a6574a177c249674b43ae5c60e99b5da6e71905cba3a87266b2831f591ac52ea220a10062fa14c3bddac3d76f9f14578c56db9041117107e4457070504561388919944d85db0506b13176fc7a8f8a6824a9354d38d09c0d94da64bc09dbbabcb901174e9e24095c1ca10db79f5c999d345a3aef95f59c7282d01b00d215553cab5ab62aaf70f6f43dc7336d01b076e299a3b5734802297280d2656c8f3f0e36d0f6efaf3cfe20d43cf8f76c7feb45a0ecda3538df55603f0acd4115071845dfd11f29c5227e4c7de07d220ec890ed166aaa205608d504495972ccb2fabd25bebc07d4bc410434c8cd6f1449f8156e56102337372861fa721b2e9b34efbdd6eaa3dd72e1923baa1b51278829f68bff80816fce00f985d6729f0beecb2110a75626bc22473ae8f47d4bdd6ba9863cdf770276ea9e9c1974724789aa5f16210510e684dea95bd70425426aae7aac2e485a280f91494e731e72403ca164a77f5e9d604df8a6ce2ea98db661dbf3ad0ccd0a8d822bec693b5b05b6dd8acc2fb893d85cf65df615fa84593e2c74f295e79def5915435c62bad672926c06f9d57c22356bcfb104fc71621216d0b7b5a67487f1e17d1227011ee872f2b2bb18b8a64af76468d2c58832b3b08aaf046f935e8b2952f64eb62d0a6359d4d9682b216eee0d44387dd9ca702dc5cc05bd2232fe34ef1ab2b6214448ba72793608e397276130aef67453257929d738fc4be4a24af70dcd5355a3d560ed72cea69dc0f126d026ee0efc009163de8995d21b34aa837f50101a1b57598e633cb8afe1e192bb749b752bae279296a9cc12cc4f35d009682164ffeb425f0add728903018ab36b9ad2cfc3cd77684365cf8f7b328ab3d0dcdd1e8566bcfa538f72f2717b7f6e6a127fb7f754966441dd83229ab6d987cbdfe3f86d59f71af78f303ec9bcc0bd77de5247e0bdb411282f1f9a563989da56d3b17860c73f9b95937272bc28186a8e585e4205b3816447ab35fa9bb4de0688dbaeb407c686c1d8ad72623e9ce6f1bbb599f3b504cf39cd318c30a2425210253b95d3f185ad76db4223bf1e09aefbf640112def284fd297b77696830987d0d57683b9c2d564aa395572b876310e7127855be77ce695a99870092e09d2ecd35e024c04f9dbfa8fad1572b3fdf5af2f0d783c4277275e53b01960568e653d6cb2a3da46a57adb96a45072fc55ff1c794a5991b0c1a41740f09c2aeca22fdaee28f4756ca08fff72c0ef72875e226815ddcece5f31622bc318ed20c2d78121b0504d64dec4b23ef970f1725a96a5d8f5eef147d5df2a8eff605bc2cc64175c16b5e1e346dd9867f6f4b872c9ddae9722ad0b68c3393b1748622d425e1bcb8b7d1ad9580e31c3e86d1aa272f1a5cd904911219a67b30c733537de0cf43d21a10a31ab8e9e32ea454f62b172e911424e714a98aab6bf00d86ad2e4ec1fba20832ebd72fece598c42f7556e08846cd1e93a81730d13360232836aec7822b296127e283c9674bec0649c821172ddbc45014dedb857f1c45101c243f106530829337e151c4a59dd28eaad282672aefd5a7009494ffc7fc529fef2664945727aebf5f319e0d96a2e3ae28304c950650bbe5b627839d3af8a3a0701078f3de72dda5df81f6a885c6667f629e9447247bca070f2f12d3e9a5cf65eca31b7614983bb2e47dcdeb538675cf7de04147220d149df0f1894bdddecfd992661530f237a5d5033d6650780e1a589f964695c3bfd16245371558b7efd73b94d948c43cc8ccb91d24c2874ab99863fd9761772ffcca9d8d6d51233363a757b5c42bd258a9b9e1ff31eb52d02a1d0b5bfca7e48420bbbf9cd1e00769e85a22c0c4f5aac85fa9767d3742ee3d1efa29d8f02e972134b53f9f9e9d55af2488a04d28d45f01030d31e8de56ec36450e4c40e454a7291296e9d3fd3fa5e3550def7ae2d0a67fd013cdc1e2009a47c110afde57bff289e58b7d5a2c5ba3b2ffa6741182fb5f8f700dac4015f8425a8ea341b000069728eccf317af12ada29f51e08a83238f217fbe9dd0a13f35934abd5dad00cc74728b535f6c423bf1b539493876a5b2513c69bf6e3418dff1697cf6d035117c793aadbf41bef6cfe572a5ee12db207afd4ab9ffa5e72c9278b0be8fc85d76cf28727d8b47c18014d8c7effb159ed6d445581f20ee61a849c852ecf99d251a7be6720dcc76b93ac8068a3f30aacc9afad92e1d3a11a49b94c7ab9fd81304df343a72db53a0de1de635f2cb819309c42dc39b3ad15c5e93b4dfd6f23e57e6cb75c572e697985fde6a631e318920113db920a7ad822d13f379f6055b3970b2f8606f72edae8066e8ece0e2c144edb91cb51cff6d7faf376619c20cdb2344ad1772717206fdd4fed5aef8ef8b053d020637e525fd56c8d46d1f4449c350c1502838a572d3e7f8dad80a300a3ecb3e6f22471188c2f9a6b7e0fd3711a6493256078a99725cb85900813a1e7cdf1fa08830cbf5057852bbfd7918a360a5e2756fd596f549cf4486d99c4864037a4034498c40e5d719ee0c01d137b77a2b80ba6fe0e0cb017b2f5fc5e823919a3a2867799bf6f08465f28e3d5f176f8f3d4997790c7df1728b0ce7cbe0a064d9e832a197a0d70825d0321ea2b93e903fda710c5c8770ae5a12096de9dda2d83ea6f12a4b8c0e4d8b1f00d51f7c7482f3b020c034d67fa372175d9a2dd1289b645d6168e96ebdf51028a9c3fe8c2d3e38cd44f6b293314372da3a38cda4ff5e894b76983c967993043a57fa5a3047aeda1d1b1d14c7460172f0fb07b451ff9fc9982c2bbc4b15a7f7438416b7000fd39b093dd6e67de4ac180f0c8e8f6e3f916045ed453544f6486c9d765b2fc7dce3c5133e809ed7602072931559f4ff53b227dd4c57908e9e4ac48a1ba2fa1aedc18b198ab8c816de51724cb4198f7f67311d29bca1a5509202d88946ee972d5cd4e56b5fa0571f823332c22fcd7616abc04f67be2fe39c1e9d4f6d0d6f4a75d2d1ab9f6662e3d34786722bf418756383677078b022e1dbf5795e186d9bef266e0691daaf24f4d8db0a72179e011a8bc550e728fd8d5fb12cc011c2fe5adbf1755ac7208be89d86ddc172ae778ce8b5262b24b02d185a4f37cc324b75deb77994a23ac77ec74b1698e1726b8c077829c7bb7e6477a6be9a6f07065bcb86b86b980d44e16d2208f14bc172d612f1e97a979e9d46aed6ea1324cc59b766231aef3514d4fffbec74706d5f412db7b3de03be40475c74b4c98a4d61070912eecdba741d824e2372bc859b080eb2b85d9f2d46be60834060811347c1f8cb53438d12cceed7991a3899034d8b726bcd6222f9012125bf51904e979344d1961140f02112d712a52943e96603151f84ace9b63134edfc183e3d0ddc16551d1c61bcab68c3e29ca41b57ed2af9c63b708efeb42600218dd2758716bb4bfff7efba563ff7ffa554fa28163af8259c36808af2ef75b6b21a666508e56bb58305972d7b750ffcc29b948fd5271d256472a83e9ebe926f7b10a87db5daac05001787cfe23dbb73311eaa9eca417d27317260c0173e13bbacfcb1569bf5805ba4b19c89ad3fe58ae4e95c67736cb0be64724f925ceae3c148b34007e3c77fd65f0815ff60b298b73092df3933b6c9db3f72a61c96996999e31386e948bed0ed8a57454f0574abf91286c698f99c0b97737240922123bab94168a87ada8677f53bc35fe9174a5dbde20a7d65659cca35a872d6e65d02b5a041c400b359484872399b84b1394d4c3a302bc468878599925008305d402060145d43c9ba6b858d82c35ab103babddc91e98fb4aff50fd7dd7a72e035ee665f9a28e9a3dd3dc9deef7de02357a78dbe701edbf68ca4e2c0365d72db735e6960e289fc82447c001b6ed0a36fedd87e10510a17547a137cb122fa72af517f4012ab2f49c017661d6657e9cfffa29391f7cd1700fecb8eeb265afb7220bbf51079608ae8b0dba06ef44c8afe97d8e8bfa0e5ab1293c64e7ae755a872bfa032c1709f94c1c054a819c4ceb5462932e93a416d0ce296630d78214ff472e1d80b983a1b64acc9032ba74c99d636f43caeae9c419c2611798039d4de2518c4d99da426889efb51ecdaed856879ee5463c15f78e1dae9c06cdbe2efd88b72d503d801ded8144eebe2333205bbb52c2378d7e22216564047512f0d2cc943723334ca2138871cc19a2b68b7be7d8658b2fea0796036475ed944ba2585981158cb2b4fc40aa7b486f85aa4140f39fb8bc4bb665d649305dbc0d280b27266da72f89254d0944da681b8783d7faba39eae2410ee992de5c3188dfb6905427fd2724acc1c4dc22e1c4aafcc45e4348e33b5bc00aefb7ea5ce39a5419106475784728f9ef31e532f92265995a3875f396b9493b3979b042cb77d432cde08fc538a7207a5043dedddbd06fe7db63eb10debf4e90144f5c00cefe8fb154160bd16f5720f86cb4c22dca3bdc718c02f48276669b2925f8190bd351f52ff3fb1a78c1172b7f539adf7cbaae3360318efd4d6b1ced97a60c5196b7ad36c99a0079b057f72112aadf8b443d580ea7e57c9983318000ed4c2225d03584a2e2a37fc83895b1fe8b5759fdafdb7ff37a89af0a3d081626da369a4d34912e58e10e479180b807236c02b95a6c40a3cadbcee956f8e187e31b40ac09b03dc5a8b87a2bd752b2c7254919a923064cb42f0937b42d9e2cb3ee1ecf02be9c68aeb2eb3dd9510bd9c2077384299796f07362798ec3eb0693eeefa43321aa6c180f2e4850dec1918d421f87d125cce061f030883d6bc0b15dfc8bb258b2d7da1349ad663d97c6d0b1c5496d42f2dfc8d224810091acea5a33e103ea6c5dcc0c484e615c50dc60674d272f24fc4533aa7ad90f46f0e8fe9794f20f86b04b7b4d521bc56b04ce40413ad72d99e55c4562e3ccc7f654752bf39134450473dd57d340a5ebcc4c95c1bc7c872f40f461c9c7e3158399f6ba6e4f4bbc14f7b9e5263b7fc077c631334e3147d72f105f0b54c79f2923bf62cade36c9d668f9248d1728d73010a1cf5f87c45ee048838befabe75e0fc152daee5488cd8f27c17dc9cca454ae0de00021ab1aa0d02fe750313fb75624dbd84ac1a0417619e070dcffe6352f59dde0731f4974a71266e65b50b7412836d0319e99e84aaf1c874300244bc62eaf467b7609c21dc211a4fc1969a5541714871fdb2d3462f219c618a5da1af22a32062c16cdc2581b27279bd109a045662601d14d3c3bd6deee35b4290cdafa15b4ffcff4c5f6cb93d3d20af87b86c57f202b67749fd3a9e73c2a136f86976b3a38db90522e39e9d5c7243f480443a13fc86a3df2762cd376306bcfe105002f5c6e67356beb28e0ace1872d98ada5e160aa859eb458bb8d9f0f0db21da48742e35d8b52ad1c4d46c7a72c0c235b2bc0562b258a441817052d101518ac3e12242118d0bb54efdcdbd065664a1a2b5905cbd74143093559f667c787a0afc21ed414278b08fa58840a2f7726d2f151afe7353c9989fa2b82928b22bb6e7e09f4fea549b63ae225c07828c3af2e3113990a26b0eefc4cc36846a2c1fdb4d7450e31d5feaa3d9c66f1b2d8734d59c5da568c72f3ef21e44c5a6d0f6442b6d6a1d34b81d795f1c37388f76b742c3119fe0ad9876ea7bd8d48fd02522554079d7f0756769da1216963c2e66ff72034e4cb6e64634f746df09aee20ce7e8549ef6679f3c83e0a64bbf810457c6052839241df4d0474d732c5b016323d65d89c45eccfbc0de4ffbe341220c0ce972705607c6c4f3892cec9c2851ab4ea7dd73a0541cfc0bc3d2e223bbcf807d05729389e7a62b97c7a79e3bd6f79020ba27f389ae198bd986c2e032838976c7b172f3ceaa440a4959ad75032d64eb3a370b38368971c9dba7693767b7b7be2c3437cbf57bc2c86b69c42d2b784d1d2643c4e6cbf1b11db459d9f7670ad819ff8f09011e5d6d249c15a3dab6abf74b5881c308eef420d5de59144cbee354cbfc4d72932c8f5ccc333ceaf0e1c66bca2b11b7cbebedab3c4ed4b407f6c4b96204ec7239beb07442ed15bffedff21c9086d77d8dcfdfe1ccf24aaad2e6048fb53f573af4b8a874256d4a8ea38240bd14425121c424330da551281d2939b6e8917bde24b4b4e7a2be96ce68eea093edfb17e394ac59f34c2b92033203bf16c8569c5b7262d9600889dda80eeed6a73056ae5b7b0a5ed397a1446c501a26ab8e818c7249382d8b3ca75be2edc02e615e2b8b03e5866f61551d589c9011c13b4cadeca072fdf1c9f6ce48663335840bded936a469ea6e14acf0a686059ae341f8d8d4dc08c487cdaa57f09fc84aa855cd3e9b932a5eaa58303142c3d08fb61ea9a6b0b572b61f01ff45115c4221489fcf0a114267a1fc1b57951d369a6fa5b39cf0b0c743fbe36691a9fdee4f8347fdce59984b71e47b62d726f652e38f36f93303281734aeffcc60585c162d224addbba7cb064ae8532235779a604e742b575c85053f57f7375ff90e6dc0af88bc496f1cf46191ff3c5cf604ca3133992bc13e60ffb5728ea9d9178d700c3b4f101c871de1ca4a459019294d9541435109ed79cfcf2e7201c21b3547757cb5b6151208c97e7c70cff9dfd556e2e76ec234b199838053722b2c0f51dbf9bf655c35927d938fb864eaf4b6433508f9a2b4210ca30ceed95372536ca1c59168e6b5e34070caceca4d6294d005e74925890486fce31392d13abc9d4ff592e5ad5bcf2ca88f1279f9f92be0dae2f8279e705cb23e5272f2797269343451f4e4a88b12434f42063a2f42c65638e9e9e929165125d7eafa9f3e72ef6255ffdd49252dcf5560528a721141cdc3a4f6d0ec173c50ebacaa45f3d33345bf170f0d1cbfd43862602791c57dde984e676b406fcbc256a4fe819a741f0418afe2bcea8899ab799d76771c03b3a258b686aa5852f5c58b7b54dc30d0885d1ef48ea9567392a73a670606f03289473c6052bdf041d178fb53e0acdf684c5ec18c27021b10171d759491c666338f9f230298102a1cb57a6634494ffe13cf03acaa1283117c52cfccc146c641fbf8632bcfdc811487e1f9a5a4f95aa3b78e7296760156d9c6e3827578d870d67ec2c47129e41dc82160b32aef4bbf7edf0b72faec7802655486c509d064d0ee4a60c3cf91ede948a053af9982b35c5bfec420cfd10e53b26c1ec948af3f0e154ce46454a301b252c9e7c571f664c0d1f2a3390f4c04b51fbb90d4ddb68f406be80c35f922f551046cc0aec42978deca8b897216eeb212bfbf245de0ccf013dcd20384bf8d1c761ce743679455624ae09ac221362e4ea9b3b48ffe9690900809f99987ee5127bc692a0800db5f7686beeeb572c42e0c547a8bc4c873eb729f4e4e9f0039fc062e02e3b9a4671fb99a4688f072647ef327254c9fe210847849e61c9671d20a1e62d5e9f2d24dc429173d160372af2056cc4213c9310548afbeace1810a4da372a49c915c33b596f7fbe423a2722ee4e3df0208affa2b45abd82e4f034acbecce2cc9f814b284263532b5d1e0726cc245e07bd6efcc0364f77bf794c2b55ebe3b005e7bd1e311b51f9166ca117200da85dff9a74caac04a23d1fd4f19ef0ad303c7e974bfc6988e215a08615a6a09da608c55f2fb0b9d3fc75fc61e6f5dd02c45b53b13c669654f77fe178370729c3849c660645e3eb92783e542946d1ab5b81d2a08e0419442321eedca0d4e721794ac8899417e12a7138abaf1847a7d696d6e4fd9c081df348d1ea8e843bd72f96092b6e6ecac831b9f3401b17797eba5810f85a4e7e547608529b891ec31201286f778f8c8e98be26df78ae5a579d804f41b378ffae278297cd90d8080b34ab3e389a7257dfaed636f8b5246bd8e656c90df5873b4e43b5945e3cc3a9b062e5af95a6aa5067b21c006f7ec79e1516b84f04607a8c46f1558f9ce87944c372b6bb46dbab909702452f3b230dad16492c6f847839844092795de10feb0c4d1376af0f5a27763755d1271b08afe92ad19216a08f2b62e62de3b5bd600eea8c36a8e78b7020c06ca346884d23157417fcf133ac4b27190f61e40931daae6716372ea1a995651189a1242927d173122a0ca007dcffdbad627918b780bba103b5972ed7129c0fcf3ef565d8f3b7cb02b089b00b7913538f002532d57530bbc6b3e2d82b36df20d614cab99363a334ba50d5f180e69d461278e71d3f3bada2abbae7255276d116e062489119f0afcc0d85281083fdaffba361bf12d6bd6038a35a4725e9b15d229048bb17dba02619451bf207638aab7e6260ffe7d92e2dfafd2522c1ad79f69f1194f0a90a124354a914867a3ac79a3294bfb0eb74b8ea53d6fe90eda1440d8bb79edab40481c01cd194ababd430d50cb9d83a6a24ed78251baa769a9a044b577c978b0a52502917fab90f6989198dbc7a6cd56d9630a49bbaf877218fae92d7e8baa6b8a6388a8e4f6b200d4409658a6b3c67f5815fa47f1073e047fc51ba981fde0685dbbfc2b4813843c03ecb09c4a8cd7beac58bdf2436ce6725efbe5c6550c12ad4cebe6978b30cb772776747518dbe6e6a288861c86914372a10c0489f916ba21c37890dd3f88b38d7ee9f774beadc42a8d9321b3a883127206eaf144ff7227fa22f43ccbeb3b4afe3cf121932c96d426ae76469b5dec9672c2433d4c78a8d82ea6ae7438e1e75af573286cc14574f4ba022b3ab81d677d7265c650867de1ee2b2dea73ace6187508cba21bd1392a486bc6794f10313169723395c521581836f115b1f9dc08a29ef8bbc363455faaa3c8f6516642c8ddef724b6ff43d3b285f12a326e36776ce6bf717b167c345eaeb68fa7c9979418c8b721d3105590455a34768927c1379181283df4c908df101115822f9dff6995e0672996008fbfe77619b4dcc42f349a8e71471db8bb21c4e69f354530bb161e1640a92815a6ad0dda6db0321f00ec92c7a3968a97f3e5ed27d21954cf813cd73c8408d01d9343009bb2c99de2efdd428571a3dede9ed42d725ebb7cdff370085116f0106860fb8b177113e8130c6417acacca19517af6749216d7afd521b4eb989729877ec3d057e78dcbcf8c8ddad67dd8d99d30a2e79ab391b95861e1db2541f6027935d43ef829411447bd93c79e8804d971ccd03c7b31b3550f5c1ba3735b47256ba7abf1cd14fb9c97e30729cec9ac6dc101439da5f249aa238f92132864e5d12d674c1d9483c3b6ff4b79bf821d0fb8965128d54f08bb078a3d66e9d95e272bf3a5d6538773d7b0189a318e1580bcee13a77a5591a229a2475dc1871204e4692d01ca5624b33beec6e65619af455297a921da8a54a0806eaba6e7f4a790724d9bb48ba05ee189ef74b3bfb649c61ab80305b5e83fcc9040d745cab59ba972fa3dc2e5dc2bececbff629a74d2d7d9ca44859ff6441b6731d4f48f3732482439c37401d0e0e427907ff55523a96ed4292ddccdb239037bfc2833758903bd0a4cf8fe2d858f2473839ebbe2e6d7cd6848615f566d5c3d7ed5f2cd28fe939cbe72674fc45da49912bf5ad6d13380a1e7cad7e3c239d5f6050662b3d65ef7f34472e0d37ff5b62119674d69209e280270e9c19b3f0628803cc7a64563a80d73327255cdeccd695a432e264a413a1054063e727814e22e6fc1fad43417a82268ab0b28a0d12a671ab7dd0f3e58bc90434be586b76f0f25ca74e0fd91c5446c720a72f0f919a38d3942f792b63f9d03fbc97291de9f5a6d9dac30ebc1fea9e721fc729b88e5111bbbdcde90cb13272f39a6ef0db0da9ee17e26d2628e50668cd50e1548473f29a2cede742e73a47b961abbb0b0afdadf4edbb1517bcc8506cea6547206c479126050accfd46e3c053df832fd4ba1fa4d7174c529af3d3dad6760ab7263b593d9173728118ba399ea98b16378eca146f9f44e7930b2a7c7db4574dc376096222ad8d70e3e7e5a0a272d5beeb54c2051d446771f9d8185e5790cca3e179562bbd610016b2321444733b6022d4b9009bf3d537342fa6008a6673bbc50723c4758634bd9e087a7a0f1c9eed6ea225043c879131f95875b3c32b7a1ab9372a9bfb5a0f70f3126c6839c5a31c9c694f76edb375e64f1ff526f55ec2ec4e3431576a50e2f93a919ff87016206e69faed97cd283f3669073ab9ed5d647e1e057c5bc7e317104e75e7864556ad8fadc8ffe0ff3cac79baf57bab4f4f1f535df7269af658251e4d792fcdbc4b7cdf71e952c74f22aa55910af3a8123ff1ea08d72e2490e70457f94c91d312f6a1a100ec4e87e96e341014fc1fe5ac5f27f986f72ed1f0b035820d36dd41f5f47f5b89975baa4fa5142fdf6da28421efb591bd072a8c95f9557d084f47a6efb936a04bc53a0724361499818f59ae43aa35cd029722ca335d4cdd0dca6b347be23b92ba9bc04ebf28d1fa6c6b4d38d570070f37665ce33142b89f53511a903170d387ddf900ca1ff3e4865208d057435254057d242a45b4c3bee69474113b001aac22e9b1df12af42572ebe37bf1842d7c86d3bf72e977c9098e5b84c206fd12ac90d9dc71290ec379344b543a2f91473ff90fc05611138271220f13e21aa1eafc72d19de7eeeefecf004d73d924626d3af8393e72ffe810e754505bafbb6024c29181492b0e8f21a7878fe3dd73dbe5208a0be972c4499a683d261a91d05adb68e11e8c3145c55c95bb569d8183b96e4d44020d72a716638500946af9bea2d52494702dd69dfc72d4e34e74bd9735388daa43aa72290c4e229fc55f46376f7affe081bed12d49382b211125e36fa243280c482a72533b265ddfa95241116012cb5814d744783408202f5ba39769fadb526f58203aa85fd46bed278f1e9135f91fdbd27fbb0cee40e944fc65d5d829e68c9993e11ea2ac12f0f15a9d8bf920125331c655c0dfa3ed85576ce9a254d3bb795c569f052a526e3f5e1029c8057f128e89064d04de3a56f0e29838c1ac40a9a95bb0aa728c2d52bcbbe1dd573536f5b9765427f5e6b8b5d0164f6ad1e263362360fba572ff364e194ca4a4ce3e6992a4503a6393c3919b105877ea99970e701eee756f72f5ae2949bd21426caabf05cb7ebec0d54277fc1a78e8dd0b33ad6af1d16894729a65c6dfaf3e1e764d9bbed4ea92f54b3838eeaecfef5521142ccb9b5352127281bb7b14d1c7549d3f57d1e23deb8750f53972e783733f74c16dac7523b4b7729ee2407788eedc9c0fc256cbaf282462acf88d5cd297b48c5069a68b422a0072981cd6755d38a4f34a3134c52371d2d32263d596ac8de6360cc9ccd97957ed69d2c57c3b19491a0fe64864053a7fb3973e5d4e26479ee2524d3814679bf3e054a9e4960c07f475a9fc39683f3623a674fb0e6b405e70707c4065fcef74a64572c520eda46a39303033639dba52ed74373a1b0ce0ea5539cf5ff5e917038c1d72e000cda5de4f0c98fb5fa1d701611386bc05f35931ce0767ff464c2f60668872cfcaf04e788a55dce8a95b3eb2ac0cdc1ec0646fa8924218ad02a20ffb64fe5e58e7a90ce747088c3f93a66f615bd9c8b2f9188ddbfcb33d5a60098154ccb872ae593d1290488be5b609ab9369f12298500b5cec9e5542a1dd56f1714494e3721f91d1319b106f1c94ba5a01ab39304e037e58322663f557a5a55b3cac157d72d1364dc10408919f525584ddbe4f62b9556120f81199f6b6ee3291bf160cef1a5d34abb5554d77a4bd3f7f2e09d706571cc368af13b0e2096a0d80b573140172cb2e7cd0aeec5388536bb89ab28125979a1a7b729b3062730c75d0ca38046a25ab2b6f1bdb8e884796ef462db6876378b304c72765a369cee6be03518f573572919fe2ebe13d005d45faddb569f79c438910befffbe46d22f406234c9a341b3913ab3f4f8320a452992a0547dbb7d6ea90ed30e4ccfd9cac43db05209d6fd30fae530f3569ffa8f92e3a84041fbacf1366940094076cf1b42f2acfef9c0665591cc1cde22eafaebd849c7b5a6b6a08941daa4f7cee75836df2afaa2e14105f061fbb38a74c892e72f0e073c8bbcc491afe0280f493d679d5c94d2420a604b2727e4a76301ee8fc36bf8ada693cffe5b2cc88b6541d168300347d020d9002b072930a42a923b09c0cdeb92c0d6da178f73061e255242bcc94c20313fc09f1bd72547654b0a2883dd66f5eb95b14ae7c7461602485e492a568f4667977167e6a72400593f2da78e7aa03e4e48d005898dbaf921a34fdb2563989ea6dc4d4ce3f07989317bd9b88c0a559b41e878ff1f5c706b4e0daae1bd12c4beddd782da791583708dd7dc655cafd0335928ed67c2b1f12c17f4fe98f3d11c9993a5bdf1fe769e6a317b4e6bd925ae1e46ed5cc8fee38d7a63f4c31b10079fba3824045ad0e7232dd68596abf94e7bb5c7db58aaee14909417665f311da2e77c52fa079093a72243cab2b9d9834af99a24a1e31a5dd7853e0a76583f0e4455abc76eed9dd62423ca8983d80860ab23283659865af64aa5d0cdbfa28737200ede3f9d4208c627217e751fdaa7283c6c4d9df1e137a820b102cab7a800470d9e997943a4349dc14aca479e8d60a2f5892bd19e6bcae845e9e0e43aca07b43012c0bdcc611b50572f852d17ff7984f2eb0b4cf45017a28a1b3ca9c54c85b7500bb08a8974c138b60e232fe37f703af1d9796e0e19947c449e4cd304fada2d87cf38005c8aa51ed65c08b04dcfb2236fa48a9d9afdfaf38c38a62e41b8970c8e43f64de6a8f45fd723ea643a324f95dea7b6e0ed1abbcc4ff5e7d252a733dbff182ccb7380d2c9414dad9db073f59975c681aee05f74ee2815a859af7c1b52822f9b735b5203bcd72ca53a33417b1257a6857b6b17ff5a42648fa3898b58e1c756776c9f204bacd3e6d05519bb0715f4b311dc666ecad8095c7fe94741bb4ab230d4f0193752ae072e457fdf75f22bb4d6d068a0a2fddeac77b9d4f63ac4c7ceeda1f42020bdf4a1342fd8280483eea371e795828307e07dbd4914a472c628f3d2982a8ef13c7e6275f79276a37a034d8646eb1974ab7fcded22b2889c7c65bff4f5d26b25ae7aa5f8948bc8ccb253c72458d1a08e04479fac265933b42ccfb586daebd43aab51f72cac8b0f28e917127a318ffbe3bbd5b28b0fb52fbed9564ccb3a28473073d7272ca6dc0541340905f3ac162e4ad2af4d291ae39b195e9702a7388b259e2fbca720bc73f2a27a7206fdbc8f3a4d15bf3c8cc29a3d6c3f7b34df08cfc6d002bce7262ab3a1362f970519c0b186ad4f03b2cba9fac4f609078777bfc2fcea8f15272521cedcf721f43da54cc7431eb74efcd075a91bf9b38ebc3bd5f75974b42a82a13e3d0fff0595e198a2e98bdd517448c6c8fac242fa8d01751871845b09f4572f8c7833ba82300d1249c2b1ec5f53439b0f0f21342d23fc60b0cb41d3909035075e003fb4524c10959b1a58e024099747bd23b7a3038fd312b7fc1a32168cf72fe3ca585b2625598a4809b2eeae92e918c6d144e362764192c05e8acb44edf222192eef194536ea8c86c59e9b7ed777343f909e7c2a7b9539df38fc0eed5fe2de15782729c7591dcbc43078d0fc5e25fcbb06b51b5e02965a0ab461758536672eb21b88247e5962b1696565d6bba0502b18bca4d7d47caac337c56f09263606ea3158c7e019b4ccb9e05fee3a8ce9981b0c6d251b33a06cea08562ad1c20d9725c72f1c7c10870548e7007232c6f7afbdb3115a560a4490788fc02d0e8fb6425a9791f3fbe5d24dfb3e65ec7e4b30065dd67ce5e1863e5764d02672100787e72a5cc52e2af70bbb124a26307d28cc0833865a0eeded8c94df8051fadc2acf62e516005a3bb110bdaed39f049727c86bfa89e32e8f7edd9fa8724766bb38fd5728a0c9212a7194097d9e5d836cabdd9c7a2ab812ba4dfc64d90e553aad2da6f72b56b8f317a3ffb388808780b335397d1c90fb668a74221010bee59be989473728840e54f5b62b69296daa3e85c316b426f0d1a195c973a0a9dd9ffbe5b37594086fc36d294ac2eae49628d436c89d2a0a8e836a70a420ad6e9a42d5fff94ea05ec79d3758a2cb72ef8e8e8eca3d4c56471529d863c47574f074138b4359c5c7219841ad03f12b9a5c1afa937ce0665eb0685a31b41f3e9da284fa076e9b3ce728042851499095ea03dd8e373f1e00983e237e2383e9d3f996cbd496234cd9d72c302a2ea3b4cc826cf5fb66d9e34fc4217edeb434701861b88b3b2ab52158230f04ae280b0c4b54263d6739dc4504b513c8b9c2c88f6e92d7d436a9f6e40d60acba9a50375f838ccd47b023ba04b3d4aab15b1ff4920677c3179c696cf69154626ecf9e9f3c6dff0ae913d15bf805cb20729d3a7b376398aa9a7dda5c8da06724eff00af340bc04e451c108f9d0fe25757c2b7cb7281934ac46ecdaf379f8772566a6e8df0116399ebee22b55f83a5d9b6217011e30e4a5a6a19fa2a4089147252bf9032c9d1b4d052785fcd2602ff4ac940bb797ae5dd3b47c46d62ce136e1a4b586b063754bb06ba2f8492002d8cad62dff76b8b752dd22b38b964465fc823d840e8757adc1235a41954c22dc6ee82608767de0d2795d1767bd1de85071a72f5b9bf4ed259517e795ffa81b928fa7bc7098a890526227080dd02f8a1a6e6720a56c8628112ce79b63b462cc60e02b7a250694fb9e848d5fd9d6c7b229b851b2de08c0e09b1ee945dfbb1ae56a523eead5cf0aa48880df0f8eeb1cf2b1d6d45b6c64202169d4b9b6350fbfe3f7959b905f1714d3c5376fe96e23f303e2783144c27025dfb90043c67fc1b35c5ee98adb8a61b9a908755ba671121d806ba16723bbbea3251ac436dc4df801ddd02f8da08f75d2675c28a5d54bd859871be3c72099952c8d0ec9fe81dd766d35b762dce70ccbd963ba96f1a8b5256105f44b7252976905ffaf8aaecc4aabad1183a84697f055643a2fcc18f412dd00fd6e6dc2156ff69abf96509ee9688f4e0e4d57da5380a4335dd8345d4e631faf65b0b094cdfcca685b94f5b44da4e5ee4e1fad58d081e2d4d2ee336ab58a59b71b4463872ce27617c2ce1342b3947207d2c912be1056253bfd34affab500cee2687f31f3537edc772182867247b8e79c662c743c97e871321e40e0a55de2634a45313e159ce59c2d70ea77c14f33a0ad36afd81eaae820895c253bdd1ceedbdd88a48d47271f6a340d396a11565f9a328b5243b4cbb343bc5784596af8273cfaa754d13725d1d54edf3f5ff09d712d5cafad1256966ca7d5021977a7aedfe6cf81fbd7e722f1eab9a4080fec7e7ba09149579c70e7714204cc47361510e9a3df40286c44dec657223c100b91e94a740ad1746092037bb33630ff571bb56e185bc09d8a22ee76f98b7928575cd9e7556d96bc6d8dcb26581cb8f561e5102d8735387c3a57259c2a5964c1321fc84487597122418c43e8dbfc3fc268cd039217c01c5e5e724f71f79805eab665aadbb8169c13987a248f76e94e2dbb053624d402ca7854b7295d6f22060b309eb231961ad40f96d4f89f579e0a8bb2d00ca38e7e3643e4c63f4859a5ac102a57fd1ed71941c66a72176665240a0b060f478b3ec0ccd4b004be7e4ac9399cedf72ea76fbf004dc143ee4acd0f63d1561a91fbf8607046049725412fbb8c4ff2d9a329784da34657229dd62a13d99114f03be145b0d670120414584bb6fe5e7dc5ddf4cd6a9296d052e6ecb31d5b6150ee301df3f369d595872ae25cf85b4202bf00d8f39b93eba78b2a67bac1e9a2a458d0e3f2b07dc24050029681f49f327f7d9c41cce3a9e32c1d0c8e1988a104a54f6f10ae05b981c614d95d6140861b657e6a3b28d73dfd858e207ab6682cfcc8fc7ed709cfa65ceca5b41f35eb3795a03173ff269588a10966498fac75a8cd3bc5c77cb3461bdceb672144666853c9d0ea1a29a697d002561b96714d552c351eab16c85d3b21e56321c01e38a01d0d28ecf76c192b7a03adb6ef996dcec8200293367c2e53e1591e172136ca358a93e06dcd9a252ad3ec5c0022b47282050ccc05de445e622ed8d3072229a762e62f161b19a60eccb0b4226fbf9d11638a087aa9390342a2bc83cbd2c332d7f607ec4653b8a59b3fb46469d0787583e126cff5881f9114a43e3ba162da6a69ba44042cad66b92af2cadbad1567d251157d579c2563a60e90af0987c728cb85d02013e671e6a1c54d3e38afbbace98ae7a04e73ae44c9891ca6d78011ebdc21bf0895bf3afaa986af29da3a93a4cb67f9dc429e99bd4a34a9b486c2072a2e38f8f6c7d72df9779f1b6a969db2ecac76d165e14d9ba6c3fd266b886ef72d6863d1942b442ba077119b6376cd67ef78f827bb7f7971e17c03c2ff6fa6372b30c2ed8218e66717f3a6ae630990b8a9e00ca3e0dddd060eeb24ac123a04c726787343ddb12af6a821af6947afa9cb9563dcf0a6a5b0a88ef8358a9865cf672fd63a05873445818c9be47b7192c25f9ed3ab95ca44637a6d2d39d7fc3ad4372913bb1a995e0d6851ccc77547cb8f782b72de889ff766a110844bf616b861772275930a1cb32b73cad118cc2104b1a7872ec0009425cd57d15f4647f45ab2a72834963dc7e2c7f3da91eeca0cfa9de9dd82c68633837e7397ac94d58f8a3e366e0b20cd7de9a0f544f558c36da7dcc3c68afc2fbb36758960d99d81dce43a2724501d756d8eb0a952125010624b4130c4268445301edc86ea635aa63d54ded7278a36f0b11b0211863ca46d14ef71b46e39bd92a02d59ede0de1a7402fb84872f906dbe8e12bd248cd8572fd8e900a743cf4397ce841bb2865b1074eaa1aec72e49b60e963ba4db3e4e0904c1157365e2c629966595974b4495d50ff2031ed720dbeb56c04f1440832accca83bbd8154b5dc7c2d0ba79d43036d90fb791ac96686b2d1a05ccfe795797a085132d6738fa73f16bc734a9bda5797d175f4664049d0968cb077491a08b57e89bf1efcbee668d67050975e71f18caddb244ee16b4952080d827e1fec57913919ba04461ce0ddd51cc351b4bfced45e63e5ec049572e7859f0e9ac4799b0d788c0ca733356a720bb9e99e4a3d42324fcec42046287244070984f5890462af4e988541940d2473b7275063ba8e93adc55bc56b6e2f725292c63a9ab157f704a2f5c453e989251794c2b5f90be6618d3b89df323e8272710948cce3b2f45d1ac840ec77eca72644a3004e0a3bbccde450bad25af27572788d58ba6f2d1835b869e3c4b97c36c2749509fcc675c052d6028e1abad6d17258ab74878882a060090dd2cd01d9d133ef0970c47fd5cba4b920133b2243b3405d1edb8782339c40b5d93d4b7c51fa0624c248505d1bc4be70a31bfd30ac9e4cc81f109b1539563d893e143fed284a8fe9ddcebba5ceb57f158ef963b31d861d342ef2a083d3f47bf680f31f125d7139253406fce001fe0ea559ae463f89d32bf8083d402b2302147163e51a91aff54dffc83cc4ef5a86bcebe0619b9b4630313f58362bf6dce250a0448eaa734d1e976c187e97153890c64f0196dd5c2cf144af2eb9860f33e0abcb5569d942c6e7ba07bfb802706d33e8139848fc1dc7b8692762c15cf088a43ea3a9c5eb21cde87a0806166cf762b1c513cd6ad32084ab17a8c9f4e951d048b7d89655ebd43b0badb7a2376ea2ab108defac2f7b97068c25130a73773c34ade84e639ef6d6cfc567f170e7e4fdd603d53664fd160ca7a97293c68a2c23184cd5dfbdfea598b8eacc6efc0bf200c04276253c06db89c96171fb684e0e7378d8583c1e6943fa53b74842176957f4ed985543e329e3528516720903b77fc171913cf2a1e5759a4df1858ae832b8fcc57e653ed4a5f4abaaac7292506144d0f211cd79d905945048b3bbc0ac2d0dd7a59a615b2de63512372b72cccf816458e280a503cf03f427b2da60a4df1db810ab473c5efba868bfa52f7299f49fa5f496c3fcdd497e2c226d5d4a438ecb1fe2899d481d2935ebc1c3d572886be78e6d73b1f87a9636aea6a13613e1f82bb450f090285a12b7633d0c286e598a71c335946e5ae4307f1081a131abe6e96a75a238d95e13c7782dbae35f72e56ef7b64faeabb3e0de840a660e0a1b9e442aeac319749cf2b14958d442c1198a71c4ad797c6c658207023489b6aacbb17fc1d564091e25e8506d40276d4a54985b4d8ba3ed1cff5cbb00cb93d56676f21f3de3ec27a7e119c5158477090d72ddb4ff1b189915483504a9482180f7830c1cea18977308f8d2c2259d1ef304728e806ac25b8a87cf21102c3538807f8458bc33d7c9c0dc537a2ccd1b14b24c7208ff838e7bc21dd20b59c05ee5abda3ff0d9cb5a621b09cb7048e89db96eec72bb8a68ca5129a68ecb8e50fc23232175a69efe1bdecfd8adca20b1bf98e52172770cc4ff38511add6351518da92d1190039e1f810fbd960f7b80638693f6d843f6a2e1dfc91bcd5513b19f5a3714ed8871fe5fd9ca7fbe87792b1ac2695b36077d26e2a68ca3bfbe0dd7d0483e3bf7347ed25d48d4e7f8ba8a83b314469ce57222963264e31cd66726c87e0a8ab1fccadcb1faeb74c55e6afb52427c88455c723f696e0cb4993311af9804a9fca39092cc28e24e1210a80b64eefd47eaa8af728598d5be8416bbfe625529c4bace4d0bba2199c8b40d143d002bf4f6fcfdf4021c2cf98414f8067f83c0ab5d38b26b21f0b9edde53d880547ef41e8b08127972e73619c55645b5e4cb99a5299346125f1787426da098edbe8b4db19a10875b7287b0bb1a2eb10c9cd00cc1243b44289f2cff74b2fed185cc2280dde0401a057272fa48d5afc303eddce4ce6d881174d27ed028e49bbb335321640cb8174a5e726aba504d3882b9af9ab881813c0aed5bc6e2e10610327497e48606e15aea6e7263eadfca3a121d9a439c5792709990ee8c948ada431b470b71b3740cc4fe0172cbe4017321d87e1d0cde5599b9e634ad2381ec050b1f853cf650a89fc190fa418a11b2ff24ce582273b2bac23abac67736b891bf299e1ac87f6f61fdefd4d872e2e7b37c6e3ff1aff5bc099d10666208c05a7b6e973d17a84a1ed5fa86d47f3ba9516420b0ecff9eece2cc476fad1980795833462d21975f5fcf025772ccd472fb1fd4ace42e32eaeb1ed478c65dac50931f4375f4de6680a290d2c67efa1a72323e9dbdf2690561cf93ca824b5fd8057757a937b63981ef1af68c0c63c19a72acc2bdd64f0f9ea5d2e550831538d8a82b0e444c26df8c176d3e04ba931cbf72800b04277ec2c98f2b87faf08b8af08b8ea50a1e400d8bfd141006987a85df72580b36ba1e56ed2ab45f57805cf434808fa0695cf4a101ab1247aa7b444c9572ff02d12e0de079dd8d93028f7d1cce8a197cfe742197cc9c82ba66e2aa8be3727be697688f2a5e5517af317442c6ea5bdb070260dbc73b08380dbcb2243b2472a209e72b019cc933e94117e04e5e17adef3fb63d033a51e164af6f554f840016e4f49ef2e42fce646ee59fb37a40ef661e60ae4690d34c836118192ac15ea3720faec4060b88231e7d936ae24f06f264827d872dc1d23c33f3c6ed7ecd03b245b9a0d017f9004eb2893fdef7652571b7c5daf842082f018b18e3ed468e1bcb725a88cc46e1905441d64dc2df300c837ed7d970c425f53cae77d7da7af3f2e5720fe1b9297f4f404a73c8aef4c9232aa29c2ce3d2058c8a6e0ad33eaf4224fe72edfd4b7da0c0b9a2d34ed2a551d5e5a890b9794bf4bcb03cbb2d925d91105852d6f161978768d5def99149a03555e59e0d73449b534998e675b4837d3ea80d72e26e85f2fbefac40751129e264872f74ab0335b72b27ac5a7d8be8491ca4c37268a2dc5af9810e8a7d43fc5f1f5217b77b5af03eb01651a1de510e5da0278f2124e685acde35b9f4a77e3feb56ba9d5411a070812f13d4f4bcb72e222d823609fce8bd2a3d3dc255dbc7e45c3daf0492388a34153b8dbeb52ff7e7a0b2dce1295416bebab861b175f3ef73bf35bb2bd5a1380cd3990801a838f1c0383195857224a872f089a76315b14e2fd07be5adf7e43ce3d20d5321ed918c45df2064361eb3cfa7dfc372fda6a665351562dd62b7bd4bf8ad2b77c6633e31667865f1b8182360c6cc2fc733ff2aa31297ae5e124a2d8946533847c397a8c5cbc798a271729a4694f3dedb03c3f61f0b1ac86ebbf4c9f706cb6de713069a00edac096aa5727ea452f6750cc13ff2c74f1534f8ecd8d0c50befcc00f677f22d4ac47266d74bd763a72475c3b0e052ce03f33af9a3ae723c1817d0b27a5e2a8aa94773331f72df38c43db590b18fa7a4718eb010dd245478cc7584cd1d63533770edffb90972a7b2843d394a03c6b95082c4cc50b5ec73e5b4f3e7b27ce23b9a0392e5538772c9c53b4fdd007f5948afa033d99ee4f764d45178681156ebd042504b533a3672c91e2d12215d0d13f3869d60f88b9d3168362acfda97936e80a724e8547cde0f87c1c4434965e7ec08159b9828d88c0663a6aaeb74042e5ed5ad497f1052cb7236ba22e4abfe3383c601a94a960873facc460ee09a46486d24e36e3f798211728883eeea8a17821c04b06aa191e9fc3c9499029e332c995e820c063f18d03e71a64f387c579180123e47712ccbe2c032d2d0ffa3c73b3ce9621a8bb144962b72d7c0e2e0e904396dd9b30b942dd480b92fae5c2f739ca4fcf487650c6190ad72281e31e42eb770b6ad20fd5dd43fc504ab852e97026260511f5fe830382358315beb5205efd94608da92ba49ded4696114bf9708fe071410241e3b9b2dd280720ab8aaeac100938b50d41a89a0b65c80aa9efc4f109e9bc5e3fe7b0f798e94722a82999fc4a75514527521ea1025112c36d542b46b91aa00985f8f3e6856cf723287e54687cb4bd465da4bfee242954e5e9e9bb0f1d55fe65450eb7a54ef3621de625b2e1d1a51559f0fedf5c5f842f9b2fcc981e89a9f12315d035833dd182e20dca09b68128dc319111a257328f93b9d67025f5855e0a57367cdfa61ede972fdcc91a06524deb33f8c13d59332695bc8bff6f11eb70d3b974d9bfe5229c072f1735e5ae831db0511428ed0e8d73ace3f59260b04ee712c44acfdf8692dc0724437ddca10a821426637f6cb097a54e1bcd7b7a963a2d9700f6142b771e31a729e88a71ac57ccccf296cfdb49e643683f6f0b0362801944ddabd2d6d3e30037210fb2051cf556301f308f2ff051adc34082cf6068ba352414e3aa93b334b73721e4a83f3b5cb4cabc55c2cf44ea47cf3aecf2a907eeb76586715f774b0677672cfeecc1fa51f38051250dcc34282252465032da2db65f26739ff68bc45d9dc06c2693df9926854e25a7680185cf6387d1d65b722a9d059cc1edd45e1fa75e74c5cb4ef0eca717903aee80ade1a253e2d33ff46b23704d73b09495849f840d772376ee05e053184431bdcde191a52f45d5d4ec826e40a19fc811698de55615f6e877db9d5fce7a03dee63759e5c68bd27832f0beffd96cc8930f24355c1d9702d9a5b015498141e3cc6753f75c1065a1a88e469c2e58175d70eac2878eafaf472d3797fe597ca71850f7af2c1654db85fac3d8980f4d3e484fcc7a1477b1367727675c75530d57760e02d1465b7748f21bf09959c4df70bd7971f0369dfc3017259fb859b267be7b6056c45bfe492a2d4491d27e28cc730c6da551eff8b5001126f1586a5db42e53e379dfeb17df4c6479df13f4f45ae61b0e9b3291797af4f72509ee8111d44b75ef3952385e7e465adcc5c1ec78512a934181ff5dea908997162d019100c391f9bf4fe2efc864656793df77abed7537191c20e279107a83072eb3039349772c2056dfd6d21696d8378c8f1bd785477fd19ac39e2d926440972ce2653737fcf160868e53d79d19c77227ba6f6a5f46ec5a7c23b0b9550f3f238cbfc1e9ed4fb2f81fc2fb0dfc4634242f5d1e593055c858c291345633146de64bdedf31245470f6380cda7c1af08d1736c52bd1a580ed73b75868e7c7970b172003386dbe136407f2732117d3e4c6d60128ea7847a04211c102a9441073d9048f73328b4675c3de35997d697fa0928292ea3c501f25c2ed06c7bb0d7f018f467781963f007037b1d62ad52b3c9a00dfd4b587e96db93735200e935ca53dcf82f151bd967c76c5c3d3dce8ed1ef471721eca0bceaeea217a8df4dac29aedec42f8d77c3a4c558db52567f49d24cba3123c493db0f2fe175da618b637a1e05fd72d7b277ba9433bd827ebc85f3aed7b9920f5bdd62fc3b919c91d9b7afdfa046403a4f74996016c3d23aaad8d7e7dde2628707b0a9b46b81f2e512b5c5684f7b72788d6fb7057c701af88a749066c61c60c09fbc1068895c1dfd22c4ecee7f6772b70d95a91ce7f74daa8767245e5d2c76622cb9c070468afefa21867dc0b6fe7235daf5d864fc6d346555a461aee50c9c32035417d3cd756ed82c1a8fcb56a472b7c668822a286707a1fe5d0209d94861b4a7e1447808e3acaf9267b2a3374072d181c4659542550ec891cd1e2132fed9ed59b98d1988f80466ddeac172cd1d418d5394bf71ef14c0c45b09c770f38c3a669e514b7a47d5eeaa280fd9abc24f39dd6942f8adb3d6620ce7784bf45e0afa33113dd23a7ea5e175fd6c24fd0bf872ff1a6e884eae5f5e5b7a6491b915cb2109d1c46431a375f840d58c2534323372f002cbb4761b1eec84759f9456bbc8a3543f9cadc28772481ff212de81462d48fce0659554e220ad87741a3a8e3d82440a8a69339c9feb2386bc8c67631ff172000dd239fe48dbfa3396023631f1c4dada64ebcd5601567145414d307d47ed724c5eefc80c97fdcacd6f798576f8e36ae42a1764bb4b330fd729850570ddc9470bd50c4f9245ffead384af28b496bcfa9b5cf6807ace156f0fc933e5b228c7214b75989692aceebb5d6e644ead785050c641ec0523493c122aa5504e20a3e872229aa907086f4a755aab1ab3c6b45200e8e99c2a5afa604f4625335ff1eb5372e1591a3bd4635c75b681725984e8613c2857c0e66091e8dd86d8232c63e17a7232055ef0752d1998dad04a7cc07edb50249fbb3a6b963369bd67d82519be2641cc0f8ee3882698871e46329ea94c09df4d71addb738b43a77ba356ad2900a52d89271d19cf5154195ed8a5f22ca38844770ad732672b7249179105b8c91767725fa9d574eed67243008ff8b8582f35b94da1abc786ef8fb97df96094a6405f6b40a0459b785fab29fcade07f99e15e6b228f05a43a6a4bffa0afa01160e5457294561c45c3f2e458fad72256c1d89f3b2badaaf2ba06955b2278510b3e189b31d8fca1de4128a01a006cd9f859919de3a23390497634311e7cfcd040f1be3e72b56843e4d9a052c42fa7a1cebabafababc7c6279f3421a5aee1451c2385ba972add04fa2626cb5140aad99ecac939f590a8aaba7d1e3d5d95dc16b5d742a1972192435b02095f2c2f04745847b7dc5f5344d084272d1336d97426b917ef3dd723acd492813371e51639b2f5f50d8e14fc51dda6cfcf270ee3f7522c8bc07f47225523b7c7d064e9c8e070f46e0c0d46d07617e0f012c588e4b72bac4bdfd4372aafd6a5c8831ddb619396faf2dcced7e5ec484b0c269be1b255f8bf0ae7462728fa68cabeab14b282e87ff9daa3bc6512c383d04b4fbd0742952860d986bd0639f79b3b95a9bc94e07f928871a6cae15ac5bd282bffc95ff93707855ad79097232e15a2671475952a8e8b6bad57eca7b0c48751c03db85252a8a31c8a10de53f74348ecb40cd8ec49f8dc22aa197fe05ef8b252fdf87a11397351409b4a97f7235d9193c2cc651938120e781b3d6e524ffcc03dc7be81cf001a547802f41a94990d86bbdcba71fa2f9edd2b6ed97a4655aa99676f081556f6a48e791872ae313dbf26a2c17449514c60f266ea1757e6dabfcefbbeeff63e270129b62f497ff72d2df6fa54cca729f7e0493af33fd8e9b11bb35ebdbee91e006ea463374d15a6b6e748e9858c2caf08c409346cc72404559ea4152f49ce2c69a1a201f8a89337230fde62a892cfd92e1090ed1871c811923e79e3dc79b35f2c8cd408743b32c72be41318c1c74aa6dc5537adab9708f407c83e96cf0faf992e5d32cedf75d5c0438fe0c5f97451408f0a4e24ca80a7ee53ff75a795f7133a0d5eba6f61248d272143e44a6c7cf2b11404fe34eafc676bbcf5b6a7f052323a76806f23ad00f4853a2871250ecae754ef4dca6a28d83574f6492c0c9ded317d70ebd8a624a2fda5369ec0ffb092d1302e84c0628df0766ae9bf7b7eb8c67c3a80a486308b5f2277253978afcf91036dd4b2c040c40f4782faa6c79adb4c9e440822e5d80dbfdfc72dda09ad4244ed8651e43df750ee9c8f562cf9566bfc64ea07d0862416cbd321ee29bdaf03afd2c0852ce37adbdf3e8e2d89944adeb5ae95a7b58bab6131f3417a76e557bb409605ea44921d7d166c5a54abe8289a3d53b184c4d98f9a398e772b52934810d9293e49c53603e4fab1babb9286b953dc29d61dd682813ca580c72f012d33bddf2792caa974aabb2517d89ef09326282b5b24b827b2384612288720a06f540abe316c57e5b0070c7aa4917ef2026205985676361b4e9af748d480f5e08692947270844d921638e731225f30411db9fee8ee46f3f6c3e7d4cbe334434c7cbae0c4d20aabffbe5b95574d61ef615993082cb10bdf77e095645d9f372fde928aabf6b08ec068553d3a537ad6aa8c48e8e9c27c8fcc79df9148a0391723711c443c2027e1d1fa73e6ed058128997df42e220598443c4c9f62eca4adf722a7a92aeb31b75a9c0f0e7c021e28ecc9dee8eb528c6bfe9ba201e1ec0cd2a72397818eef0f0f74941146981d6227301ad96614c2d3f9a61c86542716017dd688bc749ed6a1d5524836d1fdcaee68a12638278036632ad35d2e6291542e7d70862bfd4528a00f4c264bf816d7d0b6d9eb798263dc911d0747e22a2e36530d672917b267010073ab68d1de46e43c20726c38198084de7d95545a4da71f0012c72b085a89299019a46025dc53e8950fa82b7f8109b23bdfd782f42e20e9f6a4272f9b315095944501355ee1e6ed1c3a316eed391b007ae1e00be60963a40dc572a16bd82e400ab5df72a8b7ca6d8467bd06c707907b16bb0accd9aab0c90e4b52b9e420aa45a871a53b6a0576f4728cfaea983a49e3ec3fcb53140ad6bd89fda72e02717772307119f526f1269fbf3032ecaa2676320ca2fff3adebe83b4bfb429a627a61869b448e5065835d32cc8f1164ecc1484fe79c7801b45e4c804b8ad3044a13e417399eb40417346b0badc1ab3ad1c63fa50a4db751811046c3bd686727a1cb8094306665cb61e1d2df0431c44c9e116a5d5f13a6c83ffd409437d97722bc2fa05c935f3e4a697cb0c8e48ffd4b2a83c0a978dab2e672bd0e780652272cef2a9fc86cfa02cd9da1888b939aa5321f5a5a7a0ec6d46da1de282d6853772f9b4c45bc861a487d1c37789215061b32e1d4c7bd838024a6ad731fa38631d7222ef7fa9f1bd963636793c4427a4c48c51f29fddd34224573f801e363ecb966073f0b25c7f6e5e86f2d5b03e5380cf0ce865bfca8e3317233270c09afc5de372b9ee1a656287296a8f013c5d2b9fbcbadafdd346f39f6e9f26568c2676e2217272335fe78c8e1bfd7432fa8c54a9833aa2862e211967d462a98815a4ea11b35c271f4e7f0bcd5f0b932c9711fb2cfd4a874a0b7156d897ecd493bbb7a0d15072edc5b28122fed3ebbf2442bf76da21200595f37b24ac0b33abf7b09bab0390727cb8a48f6062fe3b78791eea0b1408c255d18290bf595022c49d2b2870794972a66ad4c96d338fd5e4edf09f83dbdfb931e74810586bffd8999b6ada35a2a3725e7a60ee7d90541397abedb4054b9b39e2669cb10c46d573baa1a3056656a67222c045ee9bf9d3524950146e63499ca6b14f0b605d7e0cb2c1daa64e2337f972014f49a278e50cb8c57b9f58bbb4ad7c44cece743e81c5818764ecb65a42e472e228073b0e03c5d3666da304b96cf563167a719d04d0aa96732ef9561e13751c080abc2d96e345a146e0d4a3e73cab3ddd8b48bb5a0fa6df3298e68df704040f4ff92ddb0820e351f54de96cf89f7dec6cd8153256e13f24953854d0f65f4a7280027e039c221dd50f78f85e670efcadbdd612994c2cd52cc039a62c583cea1355d88e903e3e2f17907f1d35f7df7c915a14d43dd794301d3f1d144347fe36720786e2ceed635d95c8145f5cb626449be4d93bea00d74a9ac145073900c0c172c7141e64e1d9214dbc08e67a68ee7fc2f3f3ae521ed4fa3c94595a465dad3772dfddb38eb73ce3de99b5fdd67d3fe64276b684be496c32c418047af46f54e972edc95da5ad60fab23b28a31942e574c3564d53d8999265fb10db630d626f0b49e96a7167eb31015e3e6576d82666d2b80b4da2bd4c346d29552ac2a778b1695d71b168b09ade9e0605491fe9417004f7432418f0d253f0dd2642d41defc97872a518ea1f38451684b09300aff6c32a27c86aceb2f3bf3cfd7b26dacfd682834db392c57387618ccc0cba9617cfe41c79e6e18abd5a3a36bfe034532492493f030ebbb6a7c3656ff0dc64646acc321a9d4e3e9177affaa5b640aac6ffb8a1a95589492c0a2b052d2e39959cefd26438b3fe137466c82a45f4a1a105d62f4072725cc176a19d78d4105a39d9712266479f4b79dd0d431ad071be251c41ee02bf7211ae6e3c440a53b34ab543f372195eb0384c297e4ff0368adda0dd8cea379b720cfee7e29f8b69c5f84a9d4519c5bc778c4f70352f83044aac73422747ea0672626715a750c942cc2971512b71709f965d1efd41901ea84ed085ccc3318b471f41c99d17b353737c50654b296b3a01cca011e172cd7acc10547b61864d9f03722656b4cf8726f9eb02961cb0d479ede1c55849117f03ed46e39a7360cc8c86729eacac65d2590de87481dd3aa6b33e71651a042083f5c02dbf411bbf5aa03372afed8333395501e5b46f3ab8a23c7ca6090cc0a9f9fa184b90c69d7b2d7f3c1dd0eeea0b7956ef030bc5eec393d6b7269327def44e3710fcd296ee97a7d6317212060d1d388c9ce725130cab1678b8f8dc875c1dcbcbef4a8bc0760e4936447281482489cc8cb6d7c5c88910ca48ef42c80c8925a20413d526d104795ae49972ddc33ab4031a7876df137a19e9615ad73184c2622361c0c2cde942bd4db8133c54d50b616d080a1d49cfd4ba9118711e452a02ea33da5e6a0e6426a90e77aa728ab10a827e5b7ac84296931fe9be21d412b434e0754db1c502c8971cee43800df730247d0c5264777fcd7c5e36dcf1f850c4478e2b3e4b39234d0be6ce9c797284dee958772d5d115793e8d7769f4c32efed105f182a0ef49fcae8c6672d462af7e85465cbcd40e5bf9f30a4b759e3a4a837f3f09915ebdc4ed5e2279fa81672bb208c21e40830e0c65a92913d9313a8b5c8948253bb9f861571c10a24df293a27baaccc595cd08447ade60056cd3e8aba55882c105d00db18f35441effbb172bda3b3ebcd3f0c800a6c941248f26d0e200a81906c2f0275332f5bc723324164c9df318d0720a55a7a31d1c593f3a9157787d60158282140bf32377f1429b67216f89cf610439af3d2dbb589a09805f941b70a6c083218da716a1e85b548f872123e5fc100981c92f8c65b4edab306760db82d41251ebd5ccbe8301a35c0ec72b5954ebe7b4d2916903455eb6ecbe5eb367f8156f548b9b4f1427907fa3b9072955a1c5d6e22b2392c4bb853df8f154730ea39a2e833d4782596ed645c897e6f6e80bf7108a2291098a69c0628d23170c9ed258d4c91ca14f0ff72097bbecd7203f435e63b1f5af545c23b3d4a972f8aafd63865d9e3713a187fe8bd353f3f728f88fc3b790e09b8fb070951e3cd7f6367476dca35bc6e107d1c1000d10b7b724166e56becf1db07727a447ad61a313c6b58e429f2fe534bf27b63852677905c6eab809fb9ee27e7e39f949f1d71a02a8b10815a094e4c0de3a5ff94ac91ed0d113ef5280c6a6c6010510be81f8d20db0d5f6383d350d257b35f6cff9e9a4830edf277db99aeb61fcc186f8aab41d0bd22a48b2d4b40e800706f3ea4c3d57e728f8e5c972a791a75819069f45d10d6cba329a1093539bd2fca7ff3b7ec193572e7f4668400a86984acdb9f1e8cf18af8c83805395fbe985dc0959499b2e13272ccf4100bd29e498387d56f458c55711e48fe3a6267b9d88758c9cf07cda43f04e3c06b511514b79b10c45cff2703bf11496fcf6b2a96f2e664588952b0019772f7b9434175dd32a1d7b2e83679fcac68f12b06838acb7f5d052942c6a3b01159979a51b7a1123dab16b7d74b02198f7c33cf48fca4d4dc7af5c1c1bdec2868428e89519beebc9eead2de3d98e126fc8345f7d7011cf9c88e4693e1c3f2d7c9729f40009231da49d83f3fceb9af27df807d0c1a7c6036fdf182df3b7c5d287117d95ec0562a9c1cc7625bd145eb390a45b78894d61f7be4037bfa362effdd9172bc80c8bfd7b26e0b681716d024125bf5eaa7ce15a118beee08227b9b3079db40cf483bcca4a3bcf90447638d88b1a817d0613b895a476c51c6bfbb89293359723f3546c859aba99bc79054b33b55835a899c76d1d30633c88b39e91f174e681f6fc9089c8f4dba16cb935a1d35fba267b779a69ae85620b9debc985977630f72aa1ea2870fd8e3c3e1fe474913f076edc10873602ba4b16529329f465a7a1f7288fdbebe8bf690ac63356f3d40b460c02952269419d41b7db15eafec17660372130f8de450bd2444da667373d476c0fcb585a4db7dee7dd18debcfcb5ac581026d1e0488aaeae3f483c10881fc33eb7e484ec902b71695b4b484bdbeb8e22b6b812e8abd7407b8c7bcb61fecde883ffe8fc632645bfc9bdf707acc48170c48720424d481762e8d36f012d185e9e86a10e38b0a2039c6c96e060ff0fa33ac7a724e41ff974292ed5c6f573244edaec4965bc27002af5053f4fd6761e2a5d6c47255e27a95a2f1b00a8ab640cc11f6325184c63a5722aa88d7242c3c09569cdd72bdbb9186f037df769f62ddc5a26b9433c80abc1689680a50787b8b4a94adb172ec4771ed7ea895db2972e842ab61108bac4635959535766fca933c570ed5fd46470065218b2255bd086a973a0379d9c631888a47f2e71fee23ad269360f30f4d43a008185a5e15debf57320eeaf2d6c617cdd2d504a7610ccc696f8e0b883172c5e8ca3b44bcba566392e7c7f551e17c02daf7a4df42f0b57674586dff068172f96cf2bd27033dd9aecfb2f4e614ac1818155a71089dfbe537c0dea91dd6ed72c146c348e7cf3139a7fe2648972a02e94b4bb746eee37dc18c364ad50dfbf57257b257a9137873b1ef63fc671275fa0a93de645d9824a5b011cf884b9d843c72bbec26e1c6adb9d40d1642b668216e6d7de24b26312100ab6deafa8824a601234778b4ae870928ab3db8d5e2ef3e77fdd2c6abf8ea67665f3c49a084ab361b72f1d2c73536becdbffd59286780f6f437bb78e3be30ff3a21e658b033987b265f376e551dfea2bd045b4ab67ef69fd2d66e0b3ed51db517562f4a5a0c69ae3872e8df671e622e8581abc2260f05ab4e970d331cc47dc6e836fbcb8c51afb6061574648bb3fb8a680f53ed673750ce5260d236662080a12c39db88c07f3ecd4440715d0eac5925eb6dff300b55d964bced18a975b003c11c489899814ce4915f7274e0c59e1cc7a78d03612e23dc4f35493aeca96f5782671199cca11304309372096e7865764e4cc14577330687d85f4cf6124e251749137b974bf86791cb4372fdd1588d0d63e1fa5dcc6459e91b338778084d5997b12ebedfef048a27f9a07223a7e12ca54f5b4a8929879af0d674e9c9bede179928a3349bc2cc176ffb703b0d50364a3a8acc5d88eaf56bb027af1e6140f6bd208492ba31b33ab7cc1ffe721d53fea7034668fcc1b6fa53573ac802b200599e6370f92ad61a881a449e83720b5d216aeb636c63dced7ff8c8118143d9738124f43cead69f5bdff52b598872eb4e5a3a06f018f4e3947d2ed9f41dd2d8ce29ab24f9f60064cc090f2337e45a9596e4557a61bb3fd506bbb3ce7c8f942fc9d49122e872efa5004af339fc837256ef2638180aee749aa3ccc6682c47ad0aa67219f84a068b00f7e17cc655b4673f9c4b9dc9a260c7568e665ba69f8ede48cbed5db09b1bb4483953c9464d717247e2b50f3d0fda300bbbf7f83f5233c58e68d2c3af25fd22bb6713af0bf92a72e54abeeb46de39647a730632a0c82094d02d99c60be461d706417a621cf52f72d9f803140ca0aea0c1774f41c45f92807a5651a93060b421418bb5d7fbf23872bc2a151290e39c2e4f12f53579b24850c09e792747100db5994c4959deb2152842e9d67c938368b6cc8ff2c0956c67638a49a340322da3b7ee82a6a9f87fd972bbb62a31c3577e20ef70af648c45a2233ac0e8c78a66204540201361049fc472ebe78489ee3b4dac4f8995dc4353500c862c240dc3643d0b093c310bcbf4de72a39898258389afda847a7355d8d8f8d5adb9ed1c25ad99262f57d3d515333a172d265b20922c5d09dbd434efcc340134934586126e1d63972b0e908496dbe772d791ceef2b83e336e629414fa47f99b4ad5d7f86b861548e53226ff9dfd7ce72712ed03d57763b6a8018601127003677cd4b111cc290dd574974bd9fa4e58d7291841b6a44f05bb4f1799852c61bd32ee2c92bbd1c2c02004c9e7cc2b2eb2a7270ecc04db58c4d132fd8c01c3848727c5cebcceba2d61e6f9ddd0588c57ddd72d33acf0f0ae24ea0cb48ecac0192abc776901435285067f9a761d913e143d6211a8c28c7fd332ce8b5dd6f0d0e58c321d052936b266eb6a7fee840e0e5e1231fdaff2b0434126559bb97c21b65dd6ea5fd5d1a343e32a5742abca833918b7619ddbeb13fbf90e803a89ace502e48042a4c19170a14d89d71f876d5395c660b2145b320883fcda62094aa56418296b29d00f1837bb41af4117a1761ce965b1472f1a12e479bf70c0ff2a386fbe554fac0e389dfc94147dc278e7412f2a02be97239f7d11f2b3d356fc5f928ef5a0ed91c47e242c6755ff5e11e8e1f3251ff4072efa50f1ccee897dac28c603fc1f95d8441bdc1242a62c774afce3322413422726a687eb433d9733747d624a850ca19ce7afaa440a5791124a4765fff70376044fb7c790e3ad5c4bbd87941141154657fe982f624fcefb4125a6e8632ac759c554a4ad7249691ed4c8ec6d33f8efb828ad33862ca6d842f6eebd93dad6a6e4602df57c56e4c411afbe2b7b50b8ca6c1e4054e340364a9aa32c10264a18c794f7234c82504c884be225976e44d826d28d257304278f0058719d16831d92c6975351e2a9458557edbfab1e31ecf1234311d58e8d5ebfad78d3520bd705142cf7872c21daa346a37f8f6f43dd1e6bb48c94134620779a9ac9a3f21e460b85beb614c9cca09cdce13a20224b4097b5388aaed84015dd2ae63e73ea16925bd0d00e672e0be26fef1fc54a23bcfad325e9e4533d6be53808119846dc7f03e75af6b7e7202fcec330cb2bf93ae12ce32392406819b83ea8b4490ecafdbc63f4f73fdea72700234c45446a8665cbf291fdca797a28c2c89c675e127085c173e1079025372f63db1b5ed1ea7ebc6f8a47601a133c9f3b5b1282f78e94b8a9b61623fc5697218947d51036dbd46c6b984d8f4bff1b6db48736130a0cc77333630227d41ef6590526f0dfdffbdd8151615e54044dc82c03a856b28bd49ee848d2d9d6fc143147cde851059449f1d4c53ad0749f3486f86b65700fe343a03f8093284f5189a72ab516b1eb4d4144c015c1232b6794077c86f863f384d39c448ed1f1f7979777262fbae35f30483f7a32581775174eca7004acdc714f45db10020c347df5ba07288704e09c408740113f4bc043e36442119878bbda19890cf2860276f8f21ee72496d9994f98d7831b2a4d9e2aa9662654e93067b29ac222c1458816a59bc5a72f607a286e823e0575c5c3f9ce3ae33f865a34097229f02c47a934975f0b860723155a5c7973510cb6d5f8d22b3fce49cf15f570324992539f553769460476447fbef289c18b334cade9973861301ad6838e00eaf5f589db05d0e532852721972e0b1cacc95fd910e451a5470098d872b4784835488f72ec0526b5220ae51c372218793e257c80d1e9abceaad976cda4285863bbfcfda0325b04f9ee12e03cf2c2b26244290e57aaa7220629e087983bec894a95d11c0ca84f64cefa6486ba23160cd1b9cbad28842a77ee5dc427ec51ca1f6fdcb41d319d80f3e61b1fea99072ddce01fc20f654eb30fd4f018454afb35c1f5802839c2bae7bd59942a3bbf2721a1f52b2725204013ab356341f2360b75186ee56d4b882c82efd6aaf8c20fb729675a4a86104e26b403999dffa3a085319120a699a2998ce3888ebfcba5998641132b174e0a6060e9273d5c9258a644bfbd67b09710cc4d91c44ca3c802ff77233cc5d57a07d19ad94972dabe94ad6b185cff5d52d292bb0c61acaf8fb49f31902dbd91dfe8fcade556e6f356dd40db1bb2c4a6fa0b40240e8b8ff19b3fb9a729d2d85b187fca4ab20ec01e53b0fc41920acb97d1659d0dfd070499ba20c4644fabb7e6b0097f47f8b3879b6a4b24e5ec01520dc8be14100bcbac5e10f195571602f313d28e092c78dd29a888398a8314f5014eec48a439c771a3b30f7cfbb092dd943ee4f24ded908605bb2a0d432a581b74d27bd7f7904063bf16b99677f3330df36044c161b697bf8d90361c1f5c1fe1a930c3f8854b953c4615c6f7c9c1e4cf4f62e282e9318a1c439c6583adb33cef754962ae3674974d3a012cdf69d7282d23f85ff6a4d939cc3e9295be509ad4a9be5901d1dbd8bd27fd4246dfaa772f6f3930a650282a35386ce7b17c8e0738b703fb62cd6c7dcc1afa10a22767572f437ccf96c7588436cb835f4e7ec9e3b7c0d79993a6a81483a4f742975c9c9728f7e2fa912f45bcc6e34a97081930076b3aebf1e84906858228b1c286424901539c6953937fffcd0e281088f16d1a4e1657fc68905833999b71e938734ad437262df079957f11fbe2a4745cce60114c844683f907ec2c8838f816461d9c5477225344b6ee2ca630c734e030eebc766db9060b4ec8851b02cf03315eaaae7c072f76efc28e8373886478692338cff024e296ebb97ac6fae54c0da8d0d7b58e3726bda0ff324de8e6692f5ad2e0933ac33dd188a494df4da01b38c886451af454513ca969482775554259ae8e2ea99346e87f9c76e43eab430bd12502b8ec9d372297883af50ffec8189a86f548bf52535bd58e58fb5e840963ac6ec9100986b727a21974a4ef13ee650010b6bfef346d1c35675d50063a34b47e7a0296c0c0b7299748ecfb7c43cf35e6e38b2a04c450ac3c6800472aa75852240a9261760347259957997a99d613f073077821dc8a4e1c34d57bf45e0045be7b6c75aaa5b496e38f2bfdbef43c60e3e8ba31a4d7d7005feaa6fe8878ad28568f1e9728b6f0a72c2d2008cc2ed83fc7150524147e55d9d5d3185ecc07997b99a354e8182b07c72d74402defac9f6679fb35082ae7fb266a00fe69283323e332bfbdecdde2b17721fdc5affe96252d6530bd274253289224e5a931146f1931e546a5084f79691250fd6960e2e2fbc8647b4563efebda2683726a6de77379b3d9af178e70d5a457213688acc1bfe1ee27759f1154aa5d7f7864cee64eed0e208edbdd83c6a449072a6f5b74f6bd5ee2dd611f0ccae4292b08ec2a74d2d266cf3ff40539070b177721d34a881c3bfcdf4bbfecedad46a3cea723cb55593513463572e6c001d01c872db9d2d48cf89cd39d20453a6aea8c1b47e250ad39f52f46fcaf979e9b22afa6a5a81be1039672c8d844f061dad1215d370f68f9bdadbe2f27692289e431e2f724ae4ef7ea5be08f038d832f19e1a0078fe6e010d919eb318951149ee4d85f507d44ec8af60c017e353e05636cbd87a5934b29dfb603c13cd274625879323335941fdec8438b011c51209835fd8fdadedfe25e20132b510cae6c14bab997e170dc53fd2653214ace7691cfd678eb44ed485831133c4f518516287c36f43c06367c7cd2eae1647df24efcee82b890955a37c3054018c7d7a12cb0eb9a9df28d334b270b5307000bb458a4383167819754b3e39834377245b527307b78c7f26750ae5f1d87adb4447019e2a94fdac1a19f1540219660f5a5c054c12f5639cf83d5947a948a52bfa2d5e09a4614b6e7343f6605065c217c3c444e22bcf2b73e9e1478884739232ec485abc2d84522b13a7c615246292dceafe4de208a80dffa97f0cd2e389ec2ae1ec5db6f8e7d4090d6f5e17a1c612cf53743549628f67a927b272f338e5beb3f0b0ec3a236e4be40a8b38b72a9afb03cfbcc5e493b31b9cd1273cb45e2eb66ad6de051ebb9cfd133c42bfcc0bdea2b1d826c6d49f4049e9392172dbf09c3d570cc3c3129db062bc2cc05cfe226c33ad84b091c623ef29b60de4255f1027292f9cb8a0986f6f51cc553cfe89fb2cac4f19d76fb1cc34c23bdbc472ba9414da6fdb7cf92adf44c83f48be2d808a3a399a05a635c42e653e0341e360818fc0c37cbd7ca840464865b650919ffcac371ad4e1c8f00a2607473bcd24726453459f114a0d08294e59fd21cead203009edd37407bf8e949131e948f29672ab0f71114fb335c967a655fc8f498a4c2e308fc6426cb7a274dcffa25825eb7240a39e89048fbbd0be79b22aff08c6a0abe5a5859277d6289a2d1424bd83e872a45aad5e8932485dc347f1b378d4e53bd2ae91203f5921f5616e5fc7a4985745e6535590e9e00869f076f595d48a6d527dec8e6b8428451378c6f16578c7ba10ded144efd4d22dbb273918b313027b4774d89f33bffc468ea949cc3589b234726c3faae889f867c6b17ac1a846dc192c39edcdd721760b47318b57b39163a61d89accd9daa6d39413679bab1bef0d7d057a79b5d511a14dd2afcdc2e0bc3653254b02e4bbd678bf7403ace65695878f122fcd27b458574032d3802e777dee65a02055dee4bb3acfb8c1e028b01d76eb589c2b27e6c41b67f9ea348334f4d1d72b5a488aff308e2d3ec29f96d2a12cf00bb384cd07d1aa1cb8ef532157f7f3f33c151ca4454a51d27529c926bbe80f9377ab60e600ae89e02c3afa73c0e37f813c6016ef4e54c670340a4afbf1083baf1e684c20ee7e4b78911c3f27d6be6797254f1c2ee9125cbc3836ee89df623b0f8f3d7f44a032d1da55358dcf89269367296ac8ba0dfe0e6401a192e1e146549bc29277248fa54b2b8cfd6a8d430775a72c16bdd057c2719db3993f71eca0799af382264b7a7666b70717781ab8262117299f2e59a25334606a1216809423471187962ea2187e0a03df6172fe9dc02237283da10563dc3e3a5a3a3644b4fddedb21cf4729ad768a0b73bd1efc875353e65b6079f64b159d1d21006f679a1a07bbd3aba6de346a0ce131c64255a936eb07262c91bd50d1dff4c91ff7938742e5c8a154b8e53a40fb43731bfebece9d74d72923bd29ba3d3d2ab09076bd0449121e659b392f9e6f87be8dfe1cdbd6b0feb6accec22316c28ce6a165f0c8849de4a45662930cee283b22105271ba33b8796363ec4fb796bc1a81c8e257346095c1d7ab311728547fe1c3c8ae518e2c4bb262ce2546310cc1ed217cd5a1d80b748c9590dd8ec577194a2c8de46ee240ecedc721d63d907105a2eb98cfdaf5b8634a77b92bd5f5d65a79d58385f523837805a721c095ceca57d70f2cf0824e099d75a910172a3247d836bebabd7592c7047427046ed70554f6f7f5021f768aa67e35298a106b8fd5d2d3666f79fb0259732f7446a29b33631190ea8df759d58484e9e6ef5809090261ef9892d60cf83ab2d015488d2a7208d5cebb415ba00f7fc4470dec3a1dec9ef255994ee5f5282fce45f19d35fee1a48ba5d506adaa95bba5c52fd195660245e91b1e34c61653b20aac02ead6f4ce707ce3a1d5c80cb4c4ee39bc520aab856263fb682e5df26ac61b9585c800b993ce03e4efd9167c916fda5bd021dbc7c90c000940e7a1ef6a910145a4f066254bd73965750f236d3d916bd71564901b44cb09ffc649f31d19b94c3a61b92f57c4a6e33bf7a28fd43bd8e7d4d19d44e3d2df8482e7f14bc7e76c980e33b9d211b538b5706bf13c2cdaefa16ab103b9c59a9b45e05b7d45816f2afb7f52d8bb50a40f39cad429dce99bf7b515f41ef567cbf88aedd7f6e03989920070f726b79d614db44ca61876b981f3838ae1ab982cf00c30f0c923ad365317eabc572f70233c8469ea5acd7143a81834cffde909af53b5eb810b8b39b4c35c78b031c821f3eaf4fa838a4b63fad7e18230e3db42e629bb61cfe91310da5d8f1ceee26032b76183ebfeae244259386ec90d87c9670dfaea69699ea4f967ef0d870403f6a95a54690cbd4048e14733e974753fb876744e1105beff6abe5975727dff972e7bec53d6c614ebfee798fb7a9fb98ec92f8e845b71e2c916eaef5e3da481110e1d1d161fba10f9cf822e30c4b194918c8a2adc4971d9eb41e9101f235a4270c63d1f8158869aea73fbf2d72dae1d4670ab163bf481cca87c291d26ad94326722019c8fbcb0b3b85c02975e1f00eda0b173712225fecaa1cb480993d1ffc3e7236b49c3abc58d37a8fb9b668144af2d50b6bdd6978cdcf6c560018caf1a38215155c73aadb7b8a8ffc4852232ef104365ffbb3b3e4a991ef18677f91dda80572925eaa63c0e30a909d4039fed0055bdcc4492d640967624a4e79207011e1920bc4262e93a06295301e1abf19b377593eb0bca8747c4fae42301d9eb1951abf72a1be471a7eb130a277ba53091605cc000bf65ec703802f4cf487e2c43c34cc6cb0ed5d6f1e55445e0ecca5d30252b16e27f8469362d3f9ada81813f2fa82dd72c10c939ccceaf9b4f6432ee049bd244fed1c76d06d92b53c8f55596a134ced5df8ffbafb6edceccd3788c51074e95fe2bdbfc81a2459529325869af3bc337f725a69d9bdc5ec8e81e0f4fb583854984a2ea1c1e32e46c118a77b2c16a73aaa610f5dec7178e8657b95cd1ce9fbe3d367a968f29986b01a468dba062337fa9a6456f16d609f1ea8cf16a2ef350224b2d353a2a988e0b26f643f05d5e2fca6d072806645cb213eba75e55738c040f935738ed3a63bfe788ff76b23b1df2d167a5f929d7ae8e8eb9b5eac167ae7539b58d687e03373ed9461c5380a33ec75b01272a490276240c238567a4b5f21e0c00d7de4c7394d202aa5db21c1d2922d460d619329ab8ae74aaf7cecc2333a668208c538b41d9b6ca182dd33b87886b28dfd720a4074660e0ba73a7f35e95f67db8857a43b4efc78ec026677091e7bd8f2eb389b0db24642041dc21dead78b37e2cdf4b3e4c70753e7aaa46bfa5ce77106270dbafd41fc254fec33a267b8f3f11e1852362e684deb85541e29edaca76d4ec46952eb674c7b4dfefd6710a9b2646bceab425348a59dda77309364b24a15501d2a8ee2662c602f3322f2a7a43c07379c7d9a912ab8a69fedd066a0f8a796db4772351bd63b53a20b87f70a23ec5ec62b575715549793f1f93b414cf9f3ef18fd72e067c08157d6cd5afd6d266b619f42173858c8bf3d91365261b09c16f2e7502354a3bd476a93e7a824699ec7985f47bf3d9f2d6d14a4365141c0e74577a82f2d85f211098e07c52407e7c8edd237a910166572900f5cc3b3065c4e1f334383721d5efa7712c29b78adaa90d9919356106be45f03681223fb472f4420d6163d7262ae93373a061aad782a5c591824d4c7ab32e8efacebe697bc488d4b49baf700e68b79d9832d6c2909d7f49b0fba13a3631b770c96683b56587dd43f195e833d51514da29c745c9f361dff30517ff0804300347c7292b2ac49182f5cb33ec572089cfec70efbf6ec100922734d8cf33b27d6aec27abd662634228b1878709a72873b56baa6e89c84eaf5dc750bccde9fb7aa85d065b191fdc8575d40ade81830a56dce3b45372d441f06b528c5d8cad51d2932903f13bcc2c9ed51f91c2470709e45a588b53f75f237a61ed7731e1eaa12ae80b58a003c04b54ab002d25e6d723ee58a98577221bbd6ebe33863a63cceebd2ce23f4bdf67dcea6af91c40ded37b8d8f694e93e6c96edd7c3c8078f09a8c071a0016e0fd26164c66263bce64f6a89afc6f1d939868dc5ac39c1c8c32047f8855971c01611509c5e3e6264841b7278cd092625e96d33884a882f312944ba6114ef5a8e28bfa90b4785874f676d31cb0f4926212c62ae6e6130055d51bd636d5c1cf2bfe2f8322409ec8b6e4da864b08ef78b3a688dd05dbd1b2c2f327628a331bdf71baef6a87e98734ba4062272fbd22e8b7e549e4d4e28b27e3d7c2c7c2d9628580f0e022c4a210966a27b3172ac78f6efee9f25705919dd3a562020830095ee84f8e0103bf689d8c18b6add41496635e95bba176adb3b72b38c907974e55aa31fb26698b6007ebba1ca90157251538c163d2f28d663bc3654a32ea2ed767453aae6e435d71d2a9908c6ceda592f657ba96a4fec0043eb61e2852d1acd47d74b69735cc686aca8b7deb413e911c1cb508470500c658dd358dd4994e07e8b17b013af5f3ce6caf6b7684040f85e676b77d23f1570c6032449e0dc94a37b54f5f48dd0eb60a1b986624629cf127224d4d2f650cecd5cba2928a0ae19799c3ce477f8b1376a8c62d27af063219e00ddfa1866cbfbd9eab2aff3ed2640c6d8e7183b58a79b16dc772bec13920c6072d11271cbb82aefa6fa1b29412ae43aaade628bef46f1cb66ba3093e5ff2cb955d8273965db5064fee18a00a4f421e6c72e81aae1ddc57934afd6fb63f42dcb264f66abedb50a372a965b533aefcc70334a92e1ade50e82ee64d3806c461f6e72682df1f84421a043785e28aeaddc004c09c3806610ed3094dc2fb8372061ae72c4f3539fa70126e919e96c9eb4064e2f4591cb177b0d6ceb8a83aa9bbbfb5e724c9035ac55c0f6365363589818792172719bbe510a14bac4683c5d0d894b11722b728a28d7c17e9ea728fa58ba74b2a35987b1b7f40b1f33c32a47ce95384b72836839b5e54c44d0f21146a436777616381041990df6820fb089b99ad314ec2870fb4e3a2b1f7e0716f9cc75deddd2d0e026834c7e0bf4d93a94da483eb29972e1afb4bd85f89dfbbe36f0f8ed9e9b5ec9feaed848783224c24c66da94a1785513585483dd6c479215cd6eebef558e1fec6f9a30ff996811b031f7f14c399b72ab2452d8cb0e1ec161a9e59a51805141d861dc860b141bd4e1c7fa9b4d73057234434371261c6f33d1810fcb60a164f10c1ede01c38e1f1a52acba69609a6070ef321bd960b3aa823059f436d28eb3eaa4c9c248d390f054e2a2ac86a9630e1707eeeb09c9137641fff7bb88aae983dc11c103a14b3401cc849ba3080ed045723d6854db87f7f7f2c192ad3205eed1999fef4b3bcaa0f7723f108d52e6dc343011dd58d140880a67dd0f6735665b5b73ca2766c0dfb6b12389d03ff7210f367240c787db348632abff55ba7cfe2975c093e12f4842fe416c84d1617b0f58a738daab5152e97778a8e3da2c0823d829aaf086062fe4787fd318db992c333c3972a620c00fb76ec396d7baba9f9c30a9c823333e8db9c6027a24355fa0b569577216a41e719a8356fb9b86fc829c92f943382f47e248bb9995e7e7284a65063f72c41fe4e6882d20c4272e1d73727e2c1895b1284d5c80af09b471ddf3a2ed6b72df0847656c68616c060ee981d772eb364a7d874fccd1de9eca965c084b481d726d56f0929b1e59744c4e4cfb16f23cdd603c5e8a2744083baf411ea150247f721cfa4fc73e7a55197fcfc029608cb7d7ae6ad8dda7ef0db51b6cbedfe11d5e72f680580c9508a6f2598cff13cd2d72850d60d9fbe26c08197a5c4eed88e6d152ec59fde26ca56226123913dc3c83ee4efdb3c7b0c7e0040c700743c6bfb50272859b60484ec88cb1a98cbf4e0d5bdd0184bd74c9494eed69cf31f6ea116d8b721d7e958dcb16e20d9efcb4b8ddfe36626ee131a470096ecbdc468238e3376b72a5e4c6c9c392841b7dca4a3c700bf42d882cc6ebd2a811ced5f5a0a13c095e72f6bdb265fcf6e670ef30d0f1a9f8781f2e91b288bc8b6b7a061b5f6121a74c28f72511abb41c3503b88cd88170794aa1ad9b2bdb4cdc0c95a5f984071308e009f99941def20929d9c570c8d5f1a1b86a2b5895f5aa71f5d14ea25cfd6e9209723d8f71362c73473e4434e6bcb025fa72d3e850a54c30e3e5f06bba636120633b33aaa6f8413b76bb5b03f2c67416857c2ed0cfe912667e48800eac0c8a7948723133b613a213479ed53528463e8fed915eadc6a7c7fdd0da3ec4ec19ef7d867237701c645779fe907469a03cf3ad826c7fd744bfa8ea2b6f2584bf31886bf77232e0a28c5c0f6ae8046f129b037df7d2f7aca12e91f28adf1eedb8f7e43d3d72a626aeb03bff8ace2bcf4cf699cbedcbc0ebd8633202c7bbe4e704427a08776d4f664c681ffcb1e95cd9344e880694e6b0240c59ac118ba4fefd28455b6b62720a31541e7fd982eb69c26b8f7d89803644f79e930542e74bb9abe42a9da18f1bf70a0f184cac11043d2ae708ffbcbb3938694da4711eb444709a77f82353fd729fd25b34ee7a1cae4b03b0f7ee70b2305388c5fe7e8be09b3adb559212d17d7223c279613c336af691ccdc4feb1301e1fee12ba5ee8c4faa538cd550ceebd75416968157ee0b426ab5fb56a34ee4a83349c07c8cc1688e978c6107532669c3724b74e230d88632731d8ecc1bd9f0d49e2cef03c43b34edd1492a7df2ebe7397260f71fc7c42429af7c62053c329e392d24718486c0f600c6865a8ea3eb9da37266f356b8e6594bf3f55374ffa1611dd4a2353ab0261c2fd2476a314c0723b847312c67eb69b64d20b24820b9dc0816a5c525eff54a62ccdd7ba9528bca5ecd72be21046f4c25ecd6eee0e4cdd3d084192dd039c89be11be88812059d76ecf272ae8388b9d994d6021bbd47d8424f90fef84f9a6a327783c2b97609ca8ed3c83e0ea33c0c9d2f7e1440d6a79396271e0e3e59909074b9f4b8ebd189c57cbfb437e8f02d8950ef8e5381c6a97994101b622d104ec012240e4e5c34c089aa737572af60ea904f0268aa8b2b2e873766e9da2832cabe78f800b8222a46e64b738972d8a8e8686dc07e31a3ad233983895890429fac911a4799dcdef623eaf1fab67207213247025e10b16ce7e6558343c631e9846e3a69b9ebd787e7898a67d89972d1d92455fde8b1ceb68a711f7640ddfc55dc8bc3dd142cde18852b3415789e2af98cb089f99f6f317638ffd438a94914e394580d42d37bbb67e8dad831553572b878381edee393363b05e031c1db1fc18ce8d9f9a8441befd4e1ddf6d363743e756340306098a882099b53bb72599b43fd53e9c13eaf3350797715b8c61b096e2fac1a88bad229b331b4a00b0ef7a9bd030e0cd2b323a181b41c875b60cf2e728a66fdf798700edb5b55621cd2ed997cb707616bfa99a81f7b05885ffef8e772786fd9025c441f523835ad1eefe2d4ad6204f2b91fc2791ca62c7c2fdcd6a172424145bab4ba85b6d86f413e5f330f81c45ed2784a432f4ffbd7a29d75d5db72464cc9bdfd99aebc1c7cf6b0f7ca6031326d6653b79a622f739e432001215072febd1446948dd23fcfede9a20dff6a49598e46dcda0ee78989122f504ac5fc72161247aadd8f62e2f0802b17facfa23d2319ad1b662c29f25ecec7b3e7ac66729acadf4006eef6c94efdfe5038c2dab790f7555e1c64a62099fd915bacbb6a72cffc32d951cc8d24775677ffd998c9de0f877a1b170c5fc62b03a96b381a337264fe5aea8db6c8dc0e2262364338ac73667220715da0549270412b28f1ec0f72d9baa48968d8b534004a7d731d23c3e5cb5ca47541b2775478639ab2d242bb722eda4d50e92b2e0c0ccbda0593bfc488c1e8e7278aa2bb3cc6af3596b4a19f724ca18c7a22de222f2b36085fdc1a5096be74ef200ae7f33bdd72d08230bbcc72d5ec4072d4bffe69ed790a9b18eb2682a2209834641b9598065da2e2e6916f721317fede4efc526ff78996b6677c908b8630dda90c05ff446760fd44195846025d4df42a54f3e9f56de286b3c62eb0ff8e38602ea536ea7a70abebc6152ee672e2e48c1883e88ee2097554493fae00a39b7e56750fec5a747b098e5ced82bb375ab258e78cecb1e9792ce75bf402648436b4b524477c9bf986f4e22582bc7a724572549aa0a378f673deac6ad2f68443eaa00a93892148896c31ad028dcd2c72093a77898acad71fc607e694eeff3bd59f9ccb739757e4c7caca4409d6e1783cea202f5321caac52897ce0ae095074f9d327263756c617e22a2b94d7caea884006e0e2761810463c7d50092348dff5d9b2aa14958a04bbd1b9ae353668c2b7729079b363bdfa5dc36af109d3e812baf5c1e6986442cff9b064ba4b65d80ba87259dd63980e2723581f07bc8d538473f3d11875bd9c8e395e91839b0753c33208aff77b13249ba31cce626a9d60084470aaac13a1bb371520062da7baeec1e2546eb1ec4ec7c35d030908a395e910bfd0bc46da41a215df69cfe0f8ff5b0344006c375461499e6fd982addad3d7629da0dfd86723b9034129b0a84011bdcad948c53dfc8d8df9c049a77632e6e4c566c7e1baa58abc60c4eef6154dd51e07cd72e0ba937d5b08ed0d4a38b4b0e2fe322f0a434fe90d6ead86d3197c4d19515a7275394d7c20655ed0947702f0cbaa47fbdbdfb679aa8b5ad15e07af097448704f9fb1a8ecd7054673107d8f628f76c7617d4cc81922955bdc1ceff0ef72adf05c8f66c2ec08ce7a3b39631a0e6f9167725d2f929d4cf789bc2ba8fe4d46569e72697bdb1f04ffc4e85c28a4d56e22682d1e80733630675bb8d39c490d3d389c126b2bb0e926beeb75afa7f3e87e3ff32509dfb5a6ddc6b32c8179a7ff327e415ebcc28bcf50b34dd2876c1d6966f8e64f4feaea9498d19da31acf694ac40fba725a41fb25cd3464090e073f43f16e8b71758dc97733eaaa73374be4d7ec4ca072b634cfc714b18a6594a0d4212a3ce0aa0588385ca909d9ced9ffa39ab87d515fe7458303dd15eafc03ac71ba959903138586836151cc8d6a4d172fbaf9195d150f934c61301456fc3dc479944ce21a8e3f2949011f6b10d1a51cc4a6e9ac1861a76326b272be2125b9c1bdd6af3a44f3cd0576a58bddb7c4707b2272f635e9211e89b9a4cfa8e0eeef2342222dc0b4e8cba00a6ab5739c0d747f8f86478e5272416cfe979a3ea4fcaa06692141c37c3e6844a5793b277d8c3c75a253fa768000fcd8395edae24d0d145477c7f09f5419f3921efdb747c6633d612ed326878346ad92a14c3f12ecadf3f1f573ccc6713e0d7c45fdb9c8b0f330282ed05074e572ca25a7a255ba31c526a0931082ef9f9f05089e65bebb63a5e58a718003cc4072eb3b1c799f4171e8e1d4c3e58eef129923248f2a668b57ae800e3393e889e472f35e4c1ce0bebf761ffe498a83c169e3dbe0931a256e7c61f0ad8282bf7a3072aa48104252c4705951450dd23ba410df6c38a83cca8f57b3c32871fb8adaa272f3bf8263f7c72bf3982f4d729888f98c976a790625f1e2fdd27f5dd8cd7e7672d2cd41539a4c74f28df52d98fb38f4f0740cfcd7e35c2bbc403c9310480a88726c2196500d574051ab66ec41ec3fea80c2a07d170e2e2aca844a0de31ba00d725214a083e2c3429b6bd54bb68250fe7190050f2fc29cef7edb297df5b918037277dfa4bc0262c80f9df25c9d2a3246ffd5fdf56027c1d5d3db3a36c42d7fec7279b2599a1d455534deecd0f2df58bbcd30fb6cbd9ed896eeb316b76d43707451307d69cf9b4eafc21ad95946d6ec638911024d418e9f041b07e11cb7450c66726369ae932ec2992e2a3e3bc3164ad2d591c760d09496ad05b08bdb6e8ca24a3ada2b9f340e4d0a7da024fd98c43161b3c8e595e7008de81bf006289b0951807254ae9e9bdd83b66cc224262d5202547e260c925ade23ee4fc853c6e9ef24d6722912e12e7210b80705f4fe84198ddf235b35981a5e38333f0cd9f4681b33834e6d2387705a6e68a43ff1086a3dc75e184b8e43d0ea56d6657ca2787d22a7c8721c05661ba524b0f5cb2b11428ebf451f2d373de030296d297875476ab6020f7260ee3209edc80393ad228fb54466881d9fa1714f18adffcdd1b102b3ce61ec5b736bb464130d5e798ec76351203b52f356fbd00bd83b7d66a1df8c16cb4c060f5465f9b0dd95d133d123e250e70e40d9d144f2d45ae788c1591150c65f1e7772943b95ea35e4f32c0607c7535ed9f86e4a6eb48bdd68d7dc0b912d87ea830672f37423bf3330f3311f2ed63d8ac4a11ac9dc956aa1c85bba64df9f0f2b46da31689da9272d33ac60fbfc0241b634c3c887f986b72cb576662833205ea1af640ceecc7dd02da574a1ca8fdd21905b526e74a660fb2e4aad65bd724a64b8c4cf7276c0a8c3326e6bd08323907f3532ab9d7b4e349a05f6caf49ff96cd9d6cab30d7a01a76b3316472909030c2242ac88631685c229270ca45c6043956d8f898e72ef57730898119d518f2a8189eb9ae573b0465565aaba80c0c978079252742535c34a0db1233ece4613270cbfe19f0bac84f0cdb626020449664c7cf876ae79726f69e8db4f4f001fa7b09e606d31777d7f98d7901d3f8f28e903c48d9f0f886a8d3a9e198647c85a4f03e3ed9fb5380640455a607a1e5c7ae536341ea1310772b0ef7b6a909b5aebe196ea6de6df008c5bee61ea418fe9166458bd7adbe6e73a1176d40797cf9a52d70600fd483b899dca3fd43e2cb13c6b8a7e25abedc1d84989bb3e4bb5fe1dd8d310c0820e46ffbe333865688bfe0a1192b79be3d5e2507255d667ededed2f4701a5a16e948246154322a4184513e11472620bd86489e81e07a3e8653517705b84718acaba2bd0d5b9cac18ccbb606cd50be2c2d3e4cb00401876bfde6819858bb7eb3595bcd7ba5398a9fc91bb247931065f6ed4686367270670f44dc0feace0afd15e6da91332e08ae95643251f7646235acd895acad72340fccc4185ce5264522d62416a76b3e6fba2b85c347e8787639efa895b8cb5b89ff24cfd258e608f72dc0c680f925142bab58272dda934fad173a9dde02b4108c0a9d9e403218e956534052436090f6e68e18fcbe09d346774678c2d3170704fb7cc597565ecb2c8fcf36c5cfb2dfbba11a9b62683bf966d86d983e49d7a972fe4f454d206e8ec2dfa253bc9b390399255ab6cfd4cd119fea268c97a227b672b6943c47d3ee41ad9767be465f4680f05fa0a0456aa13e2eb2204aa2b1fd1354b042eb6a3014f384028458e24598baa7baff32d2e70e80bea2f386ce1024f35e4b40555d19bfd5f6f2ff6edd564d2ec8dc0dda12cefd1981f8f838e2a004ec72629a00137ff45c2a4525eb7b663fd8397e227e6ec1dbf49b23272675f4702644951a2b28d049cd54ea20854bcefa46b05d240562bebafd2db344dc47020c721335c520a2eae037d6c70f3469cfe3b66306ae5a7b40666965a986d70c2785b872deeebaacc12a9b5677431543b762202e75da80b95f9aec5fb1a52dad806c492e6886ccd489a65c04ad365eddda18135bcca39c0f8e86de4cf0d6ca937458a072458b322419b10d78c4028b70f65e0c095ca9acca435517321cdee84c08811672a4a3c26c9659440c30b2056487508d63e7abbe8ec0d8dd54407605da371f344e2ae7fbfed45897fa952f1a53331ecda3ce1c64cc2f3c990f175ba7e55c7db472adc3ca138d803df2d1bd89b0d429ce41b3a035b3d351e1570cb1c97c84e0ae724aeff8851f56fdc00cc112afd0e32cdef75142ec69c43793ddf26552bd5a782b8fc185d499b99fd6239de4e3b0fbe3ee2b8c69e857c58022cf43b774c803e3722a7870d88cdfc2079bfe4d1ccf30e65a00092860d02bcbd4034c3916ed0c455098b7f919170371152aaf9647d8202e48839d0151cf603bd52864c237467379678a2cd0b0cbae7e75d84376a856cda0e50361769840072552b09daa65c2ca5472f8f8ee16e97945236da16728b788f3f69f93b43ba072a916f61d747ecda0ba729b55cfd18f69b9d102eb369b262cc14df7a2aab07bd05c1b33607cf73e9c9b36430cb1319815f5d503d31e63fe3b0a95ae08952dc17eae66c47f4603ec171a2151f8ea63b77208229cd6b60a4bf4a1194430798d983d462c6464eaa40f183e3545e1cd98ecc3470f7d39ad7250de29d75ffd67322d8b782b6f7aedeb31b0303b2b1be74fd55b8f26598fa011f5ea2839c0929a504dc092b61900bbe3d0200072b8f0a0eb1f09de49dc807d349a1977d15b3ca91c6f1827d8daed8d305c1b3972d7d5ab8b69d531b2e7f2ebf112b3c03f82c267df169d9fbd65346fb3b1c7117294440ebe43a9742159e372b7378cc9e2d6383bde440a6154264338f568e21372888dd8fbb4eafb3af7e515591859e06bc1c6c548918ed7b099f27faa0eed063be00831062f73d7e971d8b4509e36ed3b94b5636c98867f855219803bd329bb4641211ac1006a5e06244e5904efca47c75bc8931e83e82ac24d0028c3a1b3547249fb32e506fbe24e8f819fc0332854b6e76c48b3a664e5c1b272e166b02fbb400883a174187e5e57b8baa03cacd21e3d380692ad0d03a938fb33c31512238d6cc0dd02ac53869ac12ff9683682df86871e700feb924be5792ee849b2b45aab727537bf3d559f7745d2332de9e074fc8b763cc22678d85162797eb186cec50e499d12bb60aded39e530e7e85b4180bc416c328188be7db1c51e4eb1fd6667da3553c9759a11c69e4f4b7f51325f538ff678d39f5b1bd2608b419089cb01ea6a728d1f95c5adeda4375fa32ac9728d427df6a57a6aafd8cdc3f534dc06ed3c3472f8f51f0b516b1cae5e53d4135d3525c0dd8fcf00c8d7473f1c6edd7a28a038720e031f8dbd4744fecfd099e016f08ae92a8dbb734a30acf000a28828b9bca8725ea2736a21c7a59ea1713c946f9406abffdd281ce1a7cfcacb449dbf8052e4722d8fb8c9a0ae28dc781f657f29a3be3d9c72abe524e8acf4aa206e7e5d1cb1724c0883913e400ebfb15ddd279ddf2a5eb5281c7e1ff1eab5c08c8774f3050d5e5202bb90c7ed700909bb5b5c65d09befb6659af91b17d44df9645adae568b14d937b6ea8464979570adbea5fe0d91d6f080d19c73b202bcf033ad8d10db3bf72d774211e2be6faf864224fae108ce49fd9e65e52cad77d6de8b19514a67eb90db5018b3eee41f5a87b4cf1093ebb02e2d6900e56f2f5f367341674c268fbf17257dd141e23776842113a5baceef2993b1e98d584f0abf9594655fea48965005e52a2a1020ab8bf96b5e70f21547500c725f0480defd16a0035c13f890ccb58377641812adac72774a01cd4bb8ec57da0cb41edb53f904b52008086e21b1c86727c50f1cd999cb99d9ad0aafb335dbbd9b90a7a21500d38895cf0002bf96f0046579e52eacfaa52126bca2210981eb01f75e95334858b07f8bf4eeacc3d0d0a723c87f0aaa2e2073df9120f1c26a32341237e1e213c2ad02d4706bb41d58a5172d9096ae1ef61fcab5afa4c37a07316d9bb3f08291cca0cfe285f3d26684c2a72ec039a1084593372f6a6cca518b257df53254f9fba296d07d02416d025dcc0720dd3bc4acbc59652dcdfe7f12e0354a9bc633be36731626d05cba544a5307b6f6c13009697d1649456befa528430a6598e4ab7d03965a7c4787aea5dbe19f525cbe1e85d4d5f77024ffc0dc2019c6828d7d01bf3aef2ab2e298414b40a73be72611708975f2507d3f353938fdf963ee02390cd32f08772d923f3185fc866a672f630a713f55bad93c480d30588d123f466faf56dee2d783ccd5d7aaf8d5a976a02780b36d72540581e1ba0a8a2f045915d5f8cc0aef46596764c1a4185a7e403f758e5e84e406525f57a21afdda4ff8b0710b2e3326d086a1713b18af2359f720bd68d82b95667d6ad878c276288fa687206ece89361bf06464d5c706f3c7b7236d39d9dcefba20429ccdee0e994300420ff4a7e586aff31abe279fa9b772b72bb62be4c9429609104c41ad8ec9dd7bc5602fb0195b0dc2f4f695f297218da723dc8fe35cbb083fcc59bb36e5979b9e2efff3ef3630348fa36f7cf73bf0ee072e071dd405931d84ebb3872b9fd72bf2104ac2c4a9bcae805be37e6e3bd0bff67426aafc65f314111c5b0d8961323b41a13a130be0215c06187e3ba9618a331699f36015e096ee0c4d98b3a86c5309e398498299d25e1c001476a93d3c215713cafb6d80f371e2fe0ad54fceeec0328d810d633845d2a9e00dda9d0499ddefc72304f415c2f5053efd6d92e13c8845c7d88ca59156a298cfcfbaa514bcab41f554ebb88b60cb45668051e2aad84298f96b4b0c102a343db7a61868c5fad691f7295497ad96af23aee03c0a0e0be1a204352f828af2bb50f865dba187dde478d72f7ada748f4e5d7f4bce19094ceaf64a5689f7dab72587519563983d7661e8472f33c9e9252aad3fa01c5ec26f956c3d699b5565b4bd0e68e24853e018f58571c0325a986bb3256a350b0c6fe32e01b4fd9c2c9a7b35331a90fa4cb4d312ed3007d2d9149ac4221d9fc6d77ef08ec2a1f650c15261d792a8b0b4d441d6bf2917244ad171921a399bea82c95e62e7dd9dcf7b683309499401c0be388ec2b074972c5fa7a7d628cd1f7d184e6cf474c8ed58a2f96f105f55c80edc09c6a640b3472b1b98fd923ab11cdb32e766f38b01f2799b66109412aeff9bafb186bb595c5509e3ecf768c53c655dd2a839075b7fe9e98ae71abe7e0ccb42d9eb8dc7faac97203f566a93c1c379ca53bbe97fa93b2e867bdde7ee445f77faeb31ef26ee5f072ff685c089462630e84e0b35166c22be2ef1e96f28c689d808518b766426e3672478f9bc9541e13b657345ac9dba7419f48c9bb08807ed4929532179e6c26f92116f494c7ac0356c91f89c0dbd56be4e14d57e9c10ce4953de3dda4323bf8b032cfd68eecbb1515939d7d2b36b59daa293c988ca21f499733d31a23592137d3721bac40eedc7d1fa6bf1e13916ef6694f7707f9bf8c8f0caf465f4265015ca6729bdcab80f5c24369df747354587fd2ca0e23bf8a24743888f4f0c9bf5216e5726b71a6361031548389084237ad41d662ea5d27ca0caf6855978ea274c836304bbcc68277efe7be3b27f4c68ceee37628285c54e891fe00ed2292f3de029e3508f7de1fdf653b652f450ba688fa7978f6a4a2594f2bf5d12b6eaa5c2ce6ff7237bd30abae9bb6a874acff819965044917030b56e37475a017d1578c1c4bb1a972e373dde49fa5e7bc34fbdd702ec71a8f5c8e66b6171b1dd4e7a98d3029cee447eadaea18975578b5371347cc587d6ffb169997b9411bc744fa8ddf10e2551b72de2f171477f2b8dd30d123013a696b88726ec9418b2b2663b90ae6cb64069e72c679a80304b001545978bec4a99313b20ef07a78dec8a4e21c17cb5900f6f51cadded8db6151c3247f8c1de74dc8850bfd4c93f92b17f909f6029cbfd1d6b401cddf63c0121e9e1a69b5c3166f77d68705151563df0c3d1107ef86ca1428f12c1cbc6b2201ab6e53e0d5db87508426d8b7154b865dca7e74546fc93a24eba16e324f3f8922edc15529b75c3c2cb7bc064a1bce8dc4f0070ff841d5b0b865566fe2ef7233aa3be8c05e56d80f85bc4e7cd821dc7cbec966068e069fb111619472ac8d2622d0106965dd837a1aaf769b00c5dab5ecdd27ef152cda1d4413379a723c89a047395bc329c53d16d7a20d746cc1f29e3da6239bdcd8f5710d6399967256fb83c935645f7d49e2fdbf66eb9d43f7f797d91a690bd4462ab0890c3b4872c18297b04b77c6b49c2d9305e2e5d6967c8f32ee692f4e3b21baa14442a540676bc7f2aa159b03862e556f8c0725e0d084f591fa7f5d2bf66dfc4198123d047270a001779dc0b861076bbef8bed806f4c7b22097f6e57ea11b6a1c75848b174d6688ebee1c3f156281da095d866fd6442652a056630fb8ea4906b7abe4528057db6454c07798fc23abaeb7d446560f410352ffae78f587219655fd441114405332d8cdf258e5500d03094c61e8ee216bc46f7ca44bc51be791325be458a6617212e0696b558965c446e9e4d7b426e18777beca3e32aa5e2f9fcc6dc2f9f0a2724f50eeb6cc62c5287c519c0f1c5104c5c3cfb8cbcffa0a13711c245bb3df85729109116cb9ad1a8a833ea85c42fe2aae1514b5f2760b04dab058a5e069003b4f39c08e68d043174836d4c73561f866f889c5591ee44371053018e3076d9aee726161fba667b29ee0d1483d0a0f7f79afd75b2d9600ac5485690a611e52d49472e7ee593500e0fdb3008e4f4d530a3d055ff2379cf96295d2317b3fc2ea3800722f932a189174aa8e4451da92c2d20dfe455e71dbc063efb654b867e7c2cb9472a79dae2b229cba8ab61698a01cf9c94f0d1371501b5dba51bbacfea3bfaf64191e7b847c90b1c2df1b5ba99c95687eedf41c5b593652cd96013280c46014f62f5d1c7f92645d03e123b409e22c150f98997491e73ab9c09aad142ec5203d69723c1becd3833294b2b0c25927c409125c6be65c7a18ee626c9f5ad9c2dc999452c1a635a438eca6ab64ad4a40a92b8ef35a56332628bb880ea44ae7ade576f266377cc2d71b1c243385babbc2de43bb700c802bcfa18ee8382272c8d8d0d78b727927a6169a5109df33039dbb51d59eaafbaf6009900e4470bcee9e55b63a4e724b46badbb03e815fbf5afde66149e20cfe411d25c53e01f35cc76981260bc412d292e536f44496ecb4bbc57fa762f97eec39299443e565c0d92dfca1720246729a43a390206d057f436fe3259ec41b04ed6e37fa0ff3a17dc01e101a4017fa722b74a4a6a3938b8adde2e81c9e950c68516b6f622ef6e5cf1ee787e7e7a01a181d2b6718bea4b6177ef914a92506de62fdc3ee273dfb42ed979040732dcca3729100f68fc9d024b044fe28f7864a90d51c123ebcd561cc184690f6c5784f9929ef5c9c0984695461e01da429d8d6cba8073f017f279d8e7cff4a49d49e822b72b5fa179c266edf37a954ac637f4ca232b774a314da3772866c27a6f8b985be72f59cb7913cea4430f480df9b2441d593022c5967f2bbd840dff72a28f641cd72c980d558ac5f5d40d63d615c0281326c4d09da155234238c944c5039f218a1592a364bb65f4411ed2147c1b41724a9838cd7f227b8cfdef6a6f8b53d3bbd3172726a6d577ea463049465f3374c7f9c28cae59e2e8ae9bd64c323f0abaf608b10dbdf2fcb58f1c379e88935e88faeeff696673b9cbd09d30cdacd3bfd17608672adf6ce299b53c0539ea7ad96b6d1bd7c22a40066d9d431e294c37ec827f7b472a82b8464807bf65857377c34bc308b410e7f0042e1c3f6c8eb0940eb5a38851cb8153cc910f003175572dbc0ffbe97c0e9ed5c05b70dc308ac0991b9884a9a72ff69aec49636ad99bc947989d94eeebf26e5b6d6ed0bd27f6e9d264885cd5d1d260a719ecf4b04cc96e98706522f165278ff1a80b7aff096933ac8a9f55f4f725f6db8a4a1661eb2483a74055b767263313f15480e6734465c4002730e59a9721606a7981cd2a9f01592bcc950fb0f354ed149199e3e90fac5b9ef3f3e3ba072dffebb95ecb04f3824b917f2cdc30f94c7c0f7e7d67379edbcfffa55c82eea72fd919d19a2da10e0ae675648eb15afa4cba8fcb0b09484b2c041177caae0f872d5674130a95256caddffdf02856590f6538d360e43302d5f4b6f335818ab5764b38a88047257e481df1d1e56626c04068862cffc92908aa000a3e7922a021d72edf0350ce3a4a43f3b6f2119a3fe5474d9651918cbe9d5331aa7a4781d99d256372d900a1fad28616e342f9939ca5a2672fc516053a487eb77d43836370bff30055e956a2a1c730d9e87317ae365f4fe74d69bdd064b59b74cd0981f76ba27727796c0afa0458778fdbf34cff44ec66b300efd890584b2f44721c4c1635fe23a2331c0ce065d1e1ed23e9269ae79a0785167da7fe349b5f01f980ce980832c72e824618a9392d20401cb8fbe51c72c7ec711a0a6a2d1c77b862d3c4ab20f356eff7f914c6554bb481952d0211ebf8fc9caea4cf036061d5aa7eb15f52ccfee033acb7dc52c26d039eaf78d8daf1e289f2b8abc9eb55b7d6b2fdced68c7563c720ebb936463104e9c094389b27c8829d36cbe67b22833ddf186f072a9146ca372d39ae99c086f5269a16e375772563b4ed0de91c6daeb20a2d817a6c2c93fd55bb25a424f76281c9b2dc883d96ad87ec8303b1dd3543f71a0782647805eb5010d5fe53ec745aeb80d12a6059e3a5b5b8c571a7c6ce9af5a86774b062642e72f57033f6de18d11a84710efc16ddffab1079a70863e5b772a5380f930fe60531824750866aec28fa0068ce29d3d1ada84055ee2c01f97051f69a0456e207bda94524e1637c3c568151dd583901daee1c3304abeee5b864896c5d984894561a2a9723d2700c1bebd271cbed30cad3bfa9df4740ab78d194265a7f96f66bf3d5c8472e1a8d7b6644ed4cc2effa0c899001bb3af4e123562780f122b280e0090a6f9156f5c0cdace026428651b04c7061da630d9f05f10ccb2a9b7c7cc22869b81077236cd524c5a41ee4dbf04fd23063cb226bfd6f25bcc43eb17fb074675e4170372bb5fd23487a7edde5966b34cba67c1c88f2f316c3bc7bb992dcfcb1b614e8d72c63b8eb47cc8d7980bca7456b5994ef9625415ebbc415155af7e1adc970b7572946374234dc97437dc200bf64a191694fe430351403d23360b0ed9f2d1389472ffc9032663815999d39d78123e933473ce6f19a03be6a1e35d84f9cdb9429a7267554f59028cab1c05522da3e49001a6fdfa172d6daf2b3bb674065910279e349514963e85d3e516ecfc6ea42814489a17647b7fc0f861970ba64a68a30d2a48756af9fe9cc059b740af9e9b7059b094520e3e9eee116c33dcaf26827e9e12727b34756883e3de32b0e23a2e60c78701aa257083ab9c11471769aaa6ee749272e42a74cbf52ae3de8fd27c93e49d6275509dd97625605e613243c62db9a55c7286c35d8fb788433e327bfdf0289287539b843b65b66f82aa443c4635156b8a399168869f4d481cf18aa8d60138e1196f0fc616f021931baebe2588c7b0c1e24c19ccabe2daca157699dddb9957fcf164b2443584131ce6ff7aaf2e0ff84cb572c42ee6a9ef162e1ed1378b150e0da91b52e840b39d3eaa382c2422ac872b2e67585e227583c5af51be534182efe184deb61c134ff926c04b104eef76d070a072da4c5dbd5fc8716e592d4a0bb7ae100cb7205bb382e0e1aa5a7011526e95f72dd6479707296b9bf0f12152d9276ca08c87ae31878f051332caf5b4d34e2ce0720e22b649511dc1a81403795f7b1dc53f9e6a5390362c052c8859a8d306878f72428cee064d6ae99dcbc7ed27e33fb3f22002b0c7b44465fe2e761212edd95e726e0b356d5461830db891f74ea0b189c8cb3416d838b2d5b9d10eb28bceee1f72213544880563cfe755bb750d2a2f82998792411e9740cef8f984ec2b8b765c721d1e3b688011fb28087a4d9396922a87b7856dfdc3796a499be1ca70e0e96b72ac37658e061ca42b9b9fc67fb9c49300d6614456a7b02cc9984d50f08ef50b0bfd74876573180202388d428a22c7ec32ec1a1e979179eba5ae70475f572fc172b6fa5b6e5ba2ba66e5e83b89368942d31736ec885bb05a778623a1e8031d3772e9d974ee4a91bb794c117f93141c17b08c1c6bf98f89f8f3061d9328eef51872027082da28ed3ff34d5b8ebd0d989b3dafe668136bbaffbc8bdff630c719a943fffcdf5d44acdccc49a259c4bd7e6b79e6564c6e28ecb2ad41de08019446ee72c04009ad103efb71ecb620ead75bca96f0914e2c02c616cef0c20f63c9d3b636e8993549f7a5562fc62b50c282e030f57550eec6ef64ef4bbc766d36fbcde072cd6ab300d49c076252f690962c91873ca21a9d3c6082a06a0cffc22eb6c5ce3a82011bbf7ed05c4d9872e24c50717070979e6a8c3a0d28200c37ee4413ce80720f71f61348e5e15122f6610355f0505d42cc4148f8af2d103605a118f4813205196e876fb071680acc3bfce2041b63e75a16294c678a617db0702a603d0969720a9e1099f110de4188db9cf39ac02a5986d3f779c73a047e77eda078c8acb172e95860b1d0d0aa276f6b84ebd425f7586987c83ae31c14ea3320b868b8c70372c0773b1e1ad9043fb6a8f7bcebc0796c11f9ce0fced6926396466a35d917dc72a26e401938bc7712bd01ac39b7bed935aed69db309f2f7eb65c4859fcbd36d7241ef4f192b629fc81269fdf0a24aae509f425aa06215e72aa5befb4bd1e46872e8502e246e5d7a8ed456760da2f6a59e0e514c12fda11c025e6eaea10cafa5441b19a9e73e5a0e19d8aeb1d16db2e920e61c19ce3f6705c33a3071fd8d23ab009add60d8673d520c9df10bdc05453de8434aa3d1ed95979a7ce00c8c44246d72f363501b6872a4e559229e604e01248bae95bd15a99033c657811b925579d1722dd6cc143cc29b6bd87305691646ba62cb9bc1be8acada547b6cf7d58207d37201fd9c233cc5566983ab5e4c6ae2ba711fa86064d4dc58214b7f4850dedae072368601a7aa6befa733ecbf479ecdb9be8eab5f3dec9c660aae1c881bbadd66374ffd4fa2a223823b5f677fc188e1741cb3ca2d0cae0a20f45faaac74d8622872c5c10c208ab67b6031d456e49d3ced36040be43751df8a92629ce56c4c90037236a66c86ad56d3138b45b6ab1ef3fb92beea5f638252a9a40f1547783688a0725d3e299f5b778e7b7f35971dc1a59fe61c8b005aeaae3657fb67c4a7d204f66cafa83a4a36a4f79d2aaa8c56711e6c627a78491fa40414a04d9ff0754f9eef72937c51d27a2be2e777cb197201606463f2e52a9788f785d44c2b67200ce8a94f770e3f63294fc05cc6a3fe679b901a1b50daa601eadde7f2439bfbd4701b58722326c25340b9b30feb6b444b12b4735793f33f13abcd24b6763b4db55bf19b4cc08bf1f7469b68fef70eca63ddb2ffc03b33c78d943de4b687881c046a277a724081e63ac48754b51cb8a3a78516467f3ca940b6ace9760ffdd3c5d0b7df5e7266ca495df8d31f317e33631dd62e734629df1e840c2ff1a622c18c14b201bd72fa71e9d2e0e62a5e9fe813a0cb6c5089a3c4825b1041db1c627907a9f3783172d216267407ef085fec0b2a49edfc7fa753d8f16b52ce549280b961fa7fbd80729e4a7e13773870476ae86eed925b9b0b60ca9c029417f2f6ac50cbc2220fae729d881cb0c6d16014aa00bdc282709b18d38bd918aea5a5c5d6ccd255056c15068d16d4c560d8b34a7904256eba8583e19079bd1e3a8a2b725b2430ad4158cb0b0d303762aa8304d33f51b70cb45a7373d0c732ce77fbaa9b8a2f67050b64451cdefb5359997331d515e6a1f5d207fc7e658d04e8c9d88135fb1467cb6a262b7261b01c7c5668387cfcb5721f044ddc29f26a0f6bcc1543300f1ec29b40cf375312e334ee516232dfbd5003f94a4221a1b5083df74ef37fda943799ef8d23a572b115e591c78056da819cbc01d55542f8b63a3b29531a280bf1705fd2904a4572e7a5971b5d6ce0c57a132b19a0d324b60968e43fa87eb5bd21e0863e63905b08556f73e629f1fb8bda0561e77b7edfa72e9bed93f44c49d7e925f4b09c50dc58680bc43cc0e6b29a246e29d9d9564c8b829a7815c853cd64cb0690ec8b432a27a5f3cf4512da4a1037df10400ba2de9c0b2ad06f9382394054035920ff2222721a70a56f46a59fadf2103d4078d3c3c0ecd7701fae6549d2e7a4f144c8725072480cc2b06a0990ac8b168fe3c6b7a8e3f1bae01e1b2949771a309359f1259b671a1566fad6f159b6923ed10a823f7532e9e0b4d3e50031b8225aa4465f66c972c041d1ad33e042459f361d8684b3272636365da8379d355213e1073f884367337745622d1f978cf71838285ce4d9a4178abcca1b2156f3e1cb6528304489b1720e0777d40851d6055d0f75792e28fa247486814804f9814fca63ff200435b26f8fcd2423bee4e856a8b7f13f49360d45b866356a8c4756aaec7c067c54ea67726491dc658c8b643fda5b6386ce49cacc71ab88eab6308b833f9e2052574f2e72ff0ca115d89e9e77725fde27fd1cd93c4a0189a1655e4883b9596eed18806349dbdf64b97f5121b518410c64337c14a473576e27c8bb9aad9e73b62ae1c7ba0dada74bb155ff7518e142aaf1e46bc9a6e3de4190f4ca92a86621495ed269b172dc15cebb1d578a08fe808feb02e35b238ddd46b32d6fbcdfe43c182590834472324456d0f314d29be91c1ab3e734985628c8a97313b0c839d7d9b22635b237725578710e8127b91003dfeb8891fa347fe95844303a6abd969cde2c2fd1444a1033af52dd576d91f9f4bdcc99f6175f032a77b2277406664efb37cf5695a55a1590807fd79bf607cde9ae154a60741f060fd5bb1c997875942e9639bd2678973a35dc84d3114bda26e5071cb6d02f3afbb89a27163f6dac7199b67c6945cd2a72a1bb54f1fa5af4068df72217a5d365d6a290a11c1aff0bed82e172b3e2571372dfe9f6e5d668509bd537b000d8a75ea04b4fa844170874eddc1e55b3a3c319723c04e91248904431348aef77ab3b371aedb98b6c8c5c76d6807b4eb07d06be72129d9a308541f7dd7675beca9f419f0ef94d549d14567dd69e604a3ee260f8726d1b288ffa82dc360bf7623cc474b239c7190be3b14478c973877e1464c31a24acd05b826230fef6c47b50f586c328d07a69a8b77a5a400ca3fffc9a1ddac072ac7bac3a7713559bb27a413708c1e55957ea2e8ea98336cf445944e1eefabe0dbc37060bf5cfc2b36d4d3f013b6183b30e11af7d8401306f2cfc8d1edb59b81c92cfd3a4b9b04ac1a24fed77a1e018846aaae96a4744b985c2f9be271fea39725945d047673333b121a3c14f3d224702999253c95c3d7b661e454e502dc76e4d817ddc334d0e2e5bb405808a6cf02c73dc41d6139ec105cfcdba0042df4a092a92586489b854a1c896838f80297b464f1cbe5bc548754cc51cf2cecece85f61df23c7c0bc97aa848ecca0e40f84f0c152ce44af5054ce5f40a20462f617d0a2787e49be08f79e2907c659e0d86874f5b6f075414f3924e20799322cf725035121857ad84937b902c71a53131eacffc28addfbcb34a82b3514ed96dea9903c7559db047fca514bab672810f2caf8584ef78f26403817099e6b482b7911ba9861c383aaef0d669df8ed15e563c3115aacaec04990ed2942f86e674c35c28b0c872b044176038eebd59ed8e9e3da2ad6ed0d0d0b48d53fea5b55d3e6cd752de1507332303a1ae6d3ba3483603311e6dc5835ef05920fbadd877b9ad338225a44c6878b861c9395dbd96402bcce197151a2080fecdcaa1bfab6ee7b49485d29d8072dab07e2b0f2837e0133c70a02108377513512856c8dbfb23c42cbdb3d8cf3e6a9ba97459f1be23a0c01dfac5304ea607023ec01692cd052d43c85ca3ee6676039c599d9cbbb62a546a7ebfcce67b53e7c94f49a0e800b896e18f52c9130584490a2da6f37cf2813f797621e576c08cfbaddaa24a4d0e79cb0c6cffacbf2b3467695581312d757f6ee8511fa645ef9dd68891249baf780cdebe72a970466bad72d8b6a2a8fdadd80d7c5fd87f91e4e2287e68440304b2b94be50915ab3570897270667112c78d5b4351d9f0eb77dc03e5e7e6d53c3194d40ee166baf45733fb63839339871b9594ceb3408ee76a3a3065276e90b045355cd1e54de78d4f18b655faf8090fbea8da0c382bec00c798ab6612642637f920bfd58bea73c76ad8597200c3c27dde55b7cff788d3764e366a7ed1ba6957938fc193b38be3252336d672a30ba714ea48db236d54049c513459f6137e547383253b01d7df9a06bfc0f53090e6bb76a0a7dfde1545fdcbc86d664ace56dd4669d2bfec0ef445cd9c944842636f33072a369cc36383aad4c97f81943115c3e68d5a3c5ba9c2d3480e81147243baa4f7fe2dd6f4d731df1aa8d5862628c02759e7c4b2741597ae4b212aa07261e3c86b25d74eba1e27f681a27145407abfeba67df0a732cc2d4826d8909a72652aceaca8365c5e2ea93e65efb851f76bee0ed5309025a3da5ddcc41361b172f05638a96a52e0589c4c9c2aeabf828c99cf9d8b6e3aa6b0a03c96858ad278720aca9da9148d30fe8a4525c3e06a1a67351cdfacb54467b1ab7c6162b5af957231a31d8084da3b88c28fa78bf525f0039d356f2f6709d8b68e091dcf9f710e1d8b1ada1441e937ce6be4f724b1a304af6e8a337bcab0176f8a0a6206d1f25959c66d23e929fc9999f51c779cef312a8a409b315b59baec3c392ba7da8fe104371655703dba9bff1d13e346e3cade49ee6308a2afe75bdd593eb4c7fc954fc42411dc469dcfa23d0cdaf7f2e78aaaa6fc0b11bcc44cf037da3a242bb56b31cd4d46e357bf71f59efb9930e09cf395ac8f3ddd14bb1bffc5edc02f74fa842cd639bb75328c542d09bed63457d63c6c524c0b92f069bf9fd29d8a2757ec6359dd72be52446a30317ecefc905ba3697765b92032ca0192c8bcbea0c6d41b805db4728800158b477e227bd7e91bd12195557d8956b47074ec2002dbf31ab003fcd87248412186962d2ab9e7bda96cf129eaa06ec0e7dbf9b7bd5a6f26b2b066c2a5722f9ab16b424d11b79f59ea6af9b3d346c1fbd246a240a8121b451ac022a563720c6515da70b5142abadde1a7203346d80b7a7f433bd0378e150aaef55a933a2ace2eaa16eab698806a300f9678c34e37b0b3a9813f3600a6c75cc803df0e9e728a82957c34fd58501d74d64225626213cd2ae91e1b7081c623a72e25a1eca872a067278b4d80589a5e6f30770d9ab662779ee1b88aa3af5e0f49db99c9595972193af06bd9e92d48f8726a002385e3c1440872c3b4e3ecc38d151f20da7ade31ff143c468fffe724714a75b7d173f0219d9011e6b85bf9ee2f5f9eace74906726f29f92cbaf87f87a2439505ad794785c20550e1077d44cdf449142243943a415f999954e4f7a3878d80efa792acbee2dd0aae94eed58bbb80012fe2bdb37d72e3e0fd0d9a7294c3b71df8ef9aa9f8ddba53a63f6cf1d4e96a172678d56ded5b1630122a9ecb4ef0b4e76f2e1b1f4390639782980d675e79152317b31b714f72f241d80c11722febe2e9aae4a2142e9b4dba69ea927fbf34cddfe21f73acdc723aec69ef57a047764b3948d4c848b3329d2b39f3beddae0281d4616990abe9519dd7fe5cf4d7de28c5bb7cac28b24b935f9bb356872474aa4fde8774ff906056af908419cd5ffc6e1b4d7ccc06b8734d86a6553c7d5272b2a96e5375bad83b369a8bab4087b8699d509f94eac86ac38c2e3566b58a91df4fea092396257481725525e7a672907a0bdd553762b180a7d7548ea5e45e75e17f1d04ebc922b67520124a0e6fe459d8a5f3cce31f8216f1d0c3ce138d53f93cde62210fa97997132007e6ccca6de9c23025d13b2f1492ed69501a7ace5ff2d6cf4e66a55996b36d72025f623a22386fea4620e024eef9811c53009edaf29be0d61825520bb16e1772368540118d54bd6bab56b6813ae1b1ee4d5c0c5a3a8383fd37848305dd4a2265794d21be1d1321e6f47c83e4ef88726f97dfe953987ef2c7c5f48b17b1b552632cee80f6b26b59a782f1dd29d951c57026a64aff490d6036e724fd14e0b1bf46410c575e9211510a1ec3b5fa0f191b29f996199b394ead84599f5e3882d4dc39d4d8ae183366fcb26b0f18fd84bacb9c18f9e999ee199e5073f1f1c042541272d348b8a0495cfef74c7dbab772b0d8ab2b6420c960606b464e41cfe941329872c296f78415bf0e2b1df210a1e80cd860d79ec6fd35428533efb49e56521a1f3dfbd59c687816d1b3bc665e2afce95c93692c90a317b3428ea4a12cd976ff4a729b77dd74c7c6784ef1a34fe496112b6deba8c3fd5f40f7115a965f77f79234721754f103f508b87c4d0702a7b001dfa78b2a0244465bac41d95888ac1d8ea2724cef45eadfcd3eccd3b451d096702394b8c4e49e97d83ca91220dfa9ec1a2225a143be3dd80977d26511406fb7e00e0fe6156879e975dc417e6f79de2ab079369f519773d133c99b2311aabc7158e67b80fd661ebf491226333f1f1e6218937218c130d09e66940703f24e23f4b44023d39a9e7b37c3a7810ff63deb56850d69ce4f0a84c1e3037d2b0fd8d63627bf44ba461939152c562dd56fbb61e7da360bd4e8ecb309382b521c45baf726e72f4c982e78088bd61dbf5014cb7f3026f516d4b7fa9c385acf24eabcabd45129845c24eac72a43797b9ae88e0be5ff3e493ef3d738a34933ca1c37c08a6e29d7569822cda0bdc1f03c229e87d1122853561b0bde0653c6e032790d24e0a0bfbe0e39b8e495868d877e32930f378a942b6f72a29c401ff3a6567c68cbfb76fb050a88ec583dded6d2279dd2359b4e55527172361b2e43e311a00c8d325708884c31e9ddfde3f0551ba0c860dfea1c1ce2e9720a39de1b24e477664d48a2d0d5a23dc4167142da583edd079ebf36316ed0e872201cecfae9a73a352a0d2ae4336bd2ad1f3c25689e5d538dc2372a9b73e0b3043076ebd69d9f9171b457aaf1e9af248a129c8e421747a6116e15a37becb686722f23ba6a2e9f5346d83fc42b73836848a6d9629330b283b52711973807b8df72c044b8b5b992e07dc361883ecabdcb4ba8fdb757f0b6f86d84849666bb93fb729267528a2419845f2cda48c4a31d695043b5c625e82e796d385ab3ff96530d2e84a455a6192d89a9e7f2c5a47f687ce916d00f322aabce3515e0e6fbdc0f0224d6d1eef4fabca7ae3fd9dc9c9e8279188047693f6c7cfee99f4218145abda8720b3a1a111f9cbb77a2ce027c2a5025f433e4246e19ac5ce0d84c9762ecc0f172accacd1b9e4c08a169cf63e60eaea406758854ac3b459464787a6237ed7b0d186ff3b724bb9db91ceeabc51d9d586813a5e64f509e50ab4aa08ea888c033bf3ba682a49d419650c007e06f1840ff65a6bbde74a5f1954d7cc49519d4a27577707700663f2b3edd963956f16db3b826cf8574d6ec94ea0594903f39c708629f7212b7599e4c87d6ecfd13fd62d12c2d0ac75bbf5cbadac414faec25ff816256725b3a3a015a71d3bedec08d4a444de0a1503da2683c0d3f45b7e550595319777216ca128ec0e508ea28947cb3ecd082bb8d75be859929a2f0681f5961463b8f72b566c5ad61f8abb288223a43a6ef76c0b2d9887c60c8d1af09c068a9d93cf31a98ecfb65af68317d94f0a1b371afe1d47ed09b69698e23990799bb461b596e2d93aeac831b59aa0658336749175bd27469cc1b36c68b58927c8c6dc081bb517256fe863a465cefa3183b97231059c7b6eaeb0721234476fad2cbedd8bdf70619c8b68d7f7e895ffbde874708fc9eb1b5c611059544d1405ba24df41f4301836dcb0bb55559eba8c0374bb0d85f113e484acc7ca5f8ce0e34c04c26effd123a7223a7983a24da7181081a2b0f77350bb4d21e9dcc41e1dfa7033a736737a2637253de75dec40644601b10fb22e1d34abef2ecd63ba7d377dca2cddb73142a9e7246e9b9e9e4d70f5471400b117e60b5427eeee6fd25b406c8984fcd467d4b547245ceeca4f2b55c12dcff8aa5af4d14b8bfda8ec21b90e2acf5a3849bec894b7271df1961f9d1f9e8cd7df5750e136928d9995889c45f11b27c9e639c6f7fa07290b0ce2315a0287a1b512b929ae3fc9aabd1c2efb50d2f003415320c7baae530ecdbf2b8745148d6e72ded69f5be7b7b69b98e19bba3f51b0514be2506fcaa1b908ded1472594680effe8f8c95a25c0bc23952aa7f0c320cc14df804648db2725692b824031de561e800557c7a893415814ca5632687314eadecf2e866077b72ac3ba5dd047cf0c1beee36c1f5e876ed0dbe87249de714df6f78fb742a3062728c1eb7b04a067faf1237ed1d2055a70ff0eebf929982570d6700ef01478d736af4580ffa68605c24d863c7e66d2ee5461eec9a6b9866fb81b57b4ba2f40d9572763669eecccb6c99da2a73fd1a662300a64815a798a9c21f4b403bf1b70cf6281ed8642d1533069e8bbedfb275aa30d184ccf8b8312b7ce7bf73b5971191484186396c5abdcb96343b109cea3f6da8b82b2dca0823140898b05162828d8f305659259683c35f65af7ea5250b447a48a1303cd6dc31351fb45cd8fb66836710720db52d306c4ede5afacb3440241cbc3811f03b99686787eb9d59a3d5a39eff64008dede7e575d8205fc6ab925bb4d05423f04afd88c623e2edd0d917cc0ae26a02055c1ba3d7fd6a04b47991a5777af7154148c44d41a8a18d24d7b37d3d4a1b7409afdc54ed1c2f576c30743e245651fb7003d76ce054ee31048cbbad275d721a481ce1e8cd63f21b1f17090a4ce9b20089abb48669c2cdee3d14911e176972d741872acaa077f20a0e82db1ee82c3d7888a95b069784efa49045d2cc756c72f3289eed80ef51a21527a6d4c75de70b9a85e628cb094b9800f9e3b3a0f33672710d4c94fd6d284a9e0ef43f6e5606cb1068f3027485c4a1f899460764976818efce816a4688706d5fd930707e50ef31e564a15870e7327d81dd2135de20b372b305a04d8329eeab45bda320baebaab167a0ef2597bb7dd4311cfe1b425b8d72f98545459c3713aa6b2fb798b22a5ed30c8c5bc1856920a202683a9c9799c534b70efc318ef08f5308cb5b881f70c78ec9b6d06da28d7c9ed4c7b92197c58c72b7e669e2309da63882242a0532fe05631f4a8160ebc7fe8d305e556b6d74e3726eea5b8acb6c70b755137bb0c49877b92e5265264edd66f7e30bf373a41024723e9df06e11a0ef255f0f5d8daf8da74aba95bda0697963ccfb57f50f70c8ac72fce8c368e8b83ac54b90a367f010c62b23ebb6fa83f71f0f4fa06ced8d8ed756dddecc64ea5dcefe71526518544422246f8328e41ef060fdc8ebb78a5a877f7228642b8c79370cbf06657ae1b49bb7ba9ace57bc8096c3df0613926d1f7c0d0ea320a6d1a25e1d3f8c5a363e418f73bb812150e8fed3e9cd88f0faee3329a37237c111d955f85244a1d2750efcf0b691b23287b1eef62c59c20c18ffa553147299d7732c88f2e60d27d1208ce8adb5cd5bc7e01f7d6a2417a30a2725b9776b721583979db6ecd6cd4452341e55fcf4935f157255a42bea227bb774c090a5e872120e7bfa1d8ea627a039b64e5925cd15afe9cab4107380fabcb25a618bba72729dcf9541e24857fe1009aa4d64853cf6a875cc5e0376eaa98faffcfb16e82e72c7b79c83d88dbdf77353e34ee2d035ae7e8205330b7c57fa94e72b5ebdefa7729cd2d2f7414320abd599a64ab6c250c720a57a5ca31de78a5c979fc601846d724abfcb8d177d1ce960fcc311f35916eed974e5dae81b043931c0c2788678f70e4f1bf8cb1db35778d5b57c5b72356c5d024f8482d9e2c343800b98e05fb5910b13c6bd8cdd10091bf5ce4b3517f5d03c9c23b5910b6b258b58803fde6a73397275f5257d1ba6b6caa962c431d2eeab4b3103a1af278409dd93a70039520b6b72551ac5ed0869d56fa5dbe1bed89712910ae90c8ad593508528745130f5381e3abc52649248e9fad03678c4a31282bf22d596efc6d65ca332f6d0a30cf458d5729d6d37f061f0a9a53306a4e61061ce07cedbb617220f3fbd326c760be2234b72cedaa891aaa72911a879537f5b04b442704b6c05aff608b28c6ccff66b3a30724fb0d42b7eea061b323ba0373718e6f1bb8625dc8f7b444d48b7386334977b7260737e03a6e81a5c8fce408db535100e0b2872d2cddc3d8690c65cbabd73ef72cc68ea7da0ab34c052580c2ee4d2ad44679324e40611e3d51bb59bbfcc6b43683d044ad1a011aa327759c8c69c821a25b7f5f5dceedfcb794c5395331ca75772bdd5e21682a7b00440b84d8df122e0cee9a5d003c8240bebf8ec19ffa3f87d72885c5915c48bee2778f86cb4fd73439c4557c2f23dc59ee82c8e0bc73c99f3398e8491d098ac02937b6563241861601275ce2456a6b7d8fc1ebf404f576e39726832a85c685cfa9c3fcf07f31f69b68a45d57a44446e8cae3c09276a87f5c86db6d7fc3b8fd1c475c5e79800601a9bbab4ce1986eff2366cb0ad1e602a527b726d167e8ffb26b8ea40b60e2386f845ddc60a0a75efb2ff4985492b93b8357142337d51a0456b055dbaff4b2b31a22b9ebc276a7a29564e374d01e88b768f47044899d07909bf9ad695c8eec47bd2c0e42790196fb3fba05b83611d45b766b872a383d0b43869817ad753c1f6a70919231faa85f8434e1b9cbb6a39cfe47f71480292af03eaaf4fdef374236c8d997cbacbe9d716b4d6b9da1371ee2e5d184b62951da67199745a3813119a8f58f130650f4017963ff1423c0d1c07743d396c4b7c9294da311bcf3fac70a02a12fdad6b23d3ee89ffb8fb13cc124b27015ba972e73730fc490659ae445d54a3db55e712d37e63f3ff91cedefa9ac2add772d732a2dbdf03a45271dbecd4eee60c22d9ed5dd41a1b4df071e4907d01daba74077290dcf82033b404a22018a0dd3b415e8d27aa1ef56ba709b5e0f15e26dec15e72560af27fdee110d4d97ddb0cec3fc15fac428364712bcccc7dafe355b518dd72b918cdd7106324cccb857513d7d54dac67a1840c4ecebf5189305a2500323429701e9ce36aa8730b6cc7e5bced83340b0c07267442b020ed60b2e809556ad4274407372dd5987b469ef3ee8fe413cbc1aff455e2f16b2b62ac2abf8242b615724acf8ae4b09d1f22bb00e278fc896c649c32e75f9982babd7e1be3225f9f1272455dc4f40d0ffb56715ec4d2068929a8238d69f67a8b7397be66fcf9a374353b55949cfe14f1e0e84b5c42859ec850f20e65577621ba009ad2722ac767c8167264116f60d15bf12c96a9729131dd93333cd53b75191f821b2971ed08dc2e45660c58a116191c2efd7193a9f54989d480ddeba9409ab2e097a8a0d9c04a4f491a3b6e2cc24e4481d4678a6e18c890b7e90ec924d15ade8eb9374172d352f554727460797e30f84b603339d2a12a58f4522a60a46147db3fc9bc3b0e9454767572188a0eb511aeb219cfe1385951c8bfdfc333b3db75281c40eb9d6bfc31facb1b8816dd41d13592eab706ea17295a4b92832055cf3d5dd42f7afa8c2ba1ad8472ed5a156d3d2ccb600d7b1abbd3299370095722b507c6feee5b4d9819547cd76c811e5a40913178d537ae6017292ed7fb78e033057e30a21514152b6bf5299f72bb9fbf6691ea3383bd064bafe186882f2237f1cb5c5289b331b0dbe57ef1fe72666d9bd354ad691f2a4eaa05c3dec791bad844445c526ec751e08f31d8b07772d510720253e1ab1389a9f9941e0b6863d17b90be208db928be8f0f3ccffd5072fcfff707a5478ec8f49c577f46d6c905ade9fa04dbeff48a59d88a150d0fbb55beb9f0c2e55b94c2960b8600efea5caf9d2f2365e04e437d7f5c90a9313ee07274cffe27f67c22af43cdbaebc415d105b527dbdb67b1d065898f4c4f637160671eaa820e9faf1af64fb08096d301af1cdea68c0bb312af42239bbee75a2e157261ff9590cf96ca3168a2f869bf87113293bf7040cb76866e8ab4535cebbf060a866b7639c106b0c6bb19bc3eff2e466662ff729c6217f5ca4a2ac3ea7e3d6a72ab48a9df1013ccfbb016bb1c09ed816f964bddabc6f019123a72d95b3d81b97215e7e32d607d7587ff08d99d914c49549f742301f4295ba8d86eaae596555572342a4d22377c7e5141087ed96afe448d31770411a52a9062b04cb8912287d572996e46b2cba06286c481a4093819e9f45e4ba96174525bc6b4ebe8f99d71a472fb771afb409df16f48ccc6be346edebdba7c7f1777c55be2c87566c3263642484276222eaeb27d575710ee3bd4b9ffde11f159373aee93311ba37b78407f3a26f57c5ccd02609fad4946746c185d953637fe7cafd6292d62f865c7c06c3b8a723c735ff923a45bce38ae83c72342f50adf1a25f910dcbb46e94287d42c3c4834ab8953de8ee65f5c5d0bce51b38033058054194d808c6fabc0305e730af0ad725a142ba8159ed61f63667dbb56adcf65a0d9998232b123fd5ff0abfaa0e89c7222189bbe944fa6ccfcc84176e80adcc1b8acf876c79b48f6a38c43c9859c157215c35ca3272966adc36b96cd2511b5ea6033ec3f3313e6cbedf4959262e2264f4becdf96137b1e7c1259084a0553485588f9fddf1a83c8252d76d224661cb742eb7df91fdd98b964d4cc89513c16134c906c7a411f355cad51276a457e90ee72c1539966532b5725aca119ed2490d2b5891d98dba69fe97f9c75d2e68751196ee0b712ffdc9da408d8ff851c9a8d9b0e1dbbe2a0ff6ea2e40e582c20de6e0c728be939482ed8fac8ee582a6fa8ddac2ffcd203c672e8f8e4a5ffa3d8de23c972684bc448a7f49e45e9edbd1313d4c96797ef0fbfcf0797e6039f3bb2dc625a723b96abb7a74e9f4c0c746697a9f20fe85f18e42c24ebebc9c66b2f7a6b8054573e7260ff91e17b2c0bf41dae479038062bf99e46c014401b2e82da62ab221b49f9fa0ed6a31dc16cdad6f0e738a318ac97a41d589e90b970ed32a5d643989d0f357bf9e2698bfe410f71a29e6f2f18f39e461d97e49b11127fa66250128ef7165658d62f39b27540fd7e07a942fc41763dc5bc77b51dff4de98035eb298507722c5c626105f7ba2babbce3818acefe8764d8ed6ed4f3d21c4133d28f8e49b1244d3094db6dafb75626a64da3a52a7dc9d7e067b20808757e22b92183dd08e71b7816dafb251fcdbcffcf4923a2b0c0904ac72332dfaf10466d4b2d78c3bd141410ebd8420ef05670b9096c5fcc8cbbe76a9a8fe1ccec193cf39e9eb62a9f1772ebf4eecbde4db32a78a3ab52267a9b1924ced03ddcaac36a8e0d9e419e2fc030b0032bd0ddf050e7da2c773624b6517861c6f93a7fee6997f88ff0db994351727752c6e3b3913610f282737ef39bb7f12022f0c7c68e811bf56d3b927cf74b72b15a06bb7fcc8a79dbf270b4df5c1fb1cd9df2fef79fea8f095889b57d42ef34d457e8b63d1dcd9eec51d74a25910f68b1b3e4df64bde3e194666857b7c7995e591bc8927f515390a47f26655f778dec514c2c13e13dc71d68834aa64f983c7266c4c9347e739de66096a19400fe97c21274588a2c9acf8141c8b5324309952dd29f84a8aff18d69d9290242b9048281c85aaf00ae4eaf0057883a9a2915a8728c549abf9d950403318b78c831059ad2fef8ce5feb78bbf1bb42664f7e0a6572ba577bcf853631a58a1bfbab7f71aaa0e1d308a4f140adc597b79b7d3390d0725ec51fbdac18fa32175baa969b3292e2e5b5723f57750e0ab0a27f8a10d7ee31f22e9a9bb8834ffbf60600eb197c835e4658b9bb5ccc8faabf9ac0b87403237232c300fe05401be16a311f6d2276791850ace4f5277da55527ca11fa4544b972fd7cb72ced20df71ed4ebe70e7673e9f1935425c236c962a8c906e4d14cf8d4265a15338e4dba207e8ea37ae54b93c7114b238131a5e7af362ba142989a4cf72cbde3db1c07ec5f514a73d513309d1bf69997e27c9f6c0aa8f57fa35889eda54c4b9e12b592f0553571e60462128ebfa2475cd764c28addba5f8a6f9f03fee59a0fac9b5265657ad1700e1a74e1d456ec71c8deb37f17c48e6c30bddc4296d27abd0ff6adadb2edbd65e5d96908baef9a65645031d144b3d5006d1b2a9933072f765dd87ede5a7cc4be01cff2d8aea98241d5e6c505123d5eef92b7f465c3a72231e978433877aff56df1a08dcdda52b855ca54f58b06442ddf91a12ac668c729a9e35546eef31894d14cc8d430eebc37cfa06a0ba107d00328ce71efdc07f728f24a1af395cd4a37a5737bce5428907f0dba8e8c5fd30940f068f1fe860f160e86005e70329446e95775293e765815cc15814d278d99c584d52f139116f02729dde8638541c9c33fc771430298d661c510997282189177bab245d16d8cc74724c4060b12c1815d7127e01c5f74d0aca60dfcc49b6ffad8ffcac0953e3d5ab4c368a048cee88fe4cd5617a08a4067965655b34dc35a1a28788a7ee68f0e0a072bc798ec447c4da30987177d21a673a52af519e57af5f30559602e00f2f0145722bfa09345d5e9cad146a1643acf778dca39b56e8864c7dd53486c04f1ed3d271337b02750f29dec853dd3382c94e42f47fe4ce636f499c215ed4106d4bbf46726bdc3d029ec9620d100e6a629db8a06e4866f12aba0bfb4e2c7dde7eecee11713bd2126f5636dfffd5b358bc0ed6594f8285120c136d5cf151dda4d7788f5b724a597eaba5260742c7b36131503aed3adc6a90507c1a30178a1d334109299823f59ab794e865c759e25adb41974128c1d409d5649daab2a75babeabfbd9c0d6a3a64621e530bb0ab855b4750b3df4adb423a773b3aefb1a78e38cf38b5cb623c8d19982f60c183a594f80b61653ebb89092ef696d1730bdafabda53935492472fced906159a2138cb2ae1d72c582d73f806fa071619f50f385be335a853e2972d77436efa80c32cb2ac4e57b1aba5eb75d8fca0847a756372ffcf8ad03ed58724d49a00c80c75be6ddfe7646e7a40f1a7f070cbdf60cb7b1d407f38e0039b8461b08b4e982fa5f8b211b1a46300693df19add7edf55baf7adcb92b2552b19746181cc6418d8c042f84391a0bf855394c5a598468b009e74068d7bc8745c9af727a8fc8123c1feedac055527279dd49f08ed36172b66c9f38b1e3e97b171b443beb372b9e3b3f5b7d9657f849eae9542f9722819e4f7e8e2e77504e3ba6156a445d69a7bff632830d250bdfa4b9ed16d348ec480772cf66d273849ddc781590106aff27f99b151388d327d790fc9bb92ce03b1d443f0d6586ddfa73fbfcf276720da1828437dcc57e281e36bdbdf38269a5e0838d9299d7e640234c9824774572447d52530f60e13b35b4799d2de9a43c60b4735d18c0058a5434f595ee7c4f72580716b2d95dd70a7c8e9b1e840e57e5abfe39bf46a5aae2388bfbc8b52b56075f7700ed64137bc763bfa692a9e57f8ce65b908dd60fea7e42e628ce5bd54172d71db8f6091e7a3b72a7361070e29f0d176e9596b87a385e36eb70af6e5fbe72a52e565ceb3ee519af7ccfae1f42dbb90941cade3e4bd86a3f811d4cb8b6267250dff96b206ed755c1a615dacdf5204c785d715aebaeb928ac1e45ea0b3e29725a6e94ca6177d8bd302238c1146aad3cd5fc78f0b3903d211d83d5f6ea84ec72c75bc85f59ac01065671f496fb54abdb36fccb4baef14db34b23fbe76c78a907937dffd1a3ec2fe1f085184a5bf7336f5d76564401cf788bceb61d17875b4869e4b964aaa199766553b3a5fac3ad2a58e7acd4cdacdf209fa2eed2564b9ff70dd1701f0a808626e991199402bbaf2ee7d8d9a86142b08e07252af89c51affb720f5a992068248cc0548567208ffa0aad67221bde6c8bae1aeaff40e419348d729b1e4fa29996ade4bb51c30a6f84d35ada12ef2807f891144e4a9978cd3859724ebf4004b5dc6d9ec5de6705613f5af5479effd543e65ccf3cd343a4dc637172a2cfc851bb34b5f889d2745d168b48424b6ee062f8f67037dd5950ed03cb7572955a225969083959d486823d916694634c09a399a84c543e47ffa28a9cf95372eeef41ac0e6c5352c71a88d48bab481509d89105b26d86b250e249b9ecaf6e72791e3e435118ae67aa7a1209b5aa3e3ce12df4a86891ef637ee95b45fdf33e72ef6b28d04b680a120abd7ed2cef021f617e7a8bb7b7fdcde7ff75f5a6b232a72231c49edd545c271ad40ff198a4aff6aba5164b796abc5c3226837e5e783b472bd42e3891f31595d54d0495c3e44d969c91ea833388e71e59be694138467e96074c6c35b4796fc7cb920b33e326262b65a0be0cd9cdee7ee542b464f307ecf3dfdd455570ab4f8f886bb2f109eb96de93b5b01149f092b3bbcf222c133132c72adc64a695e84b171d1ef4bbb343f38581159808264ea699705aa4e715f0166729015e8d484523105ee0bebe5ab656c988712fcd5f1d347c7e5c11e5fdbced4723534ded95d9ee6295026c8dbbf0771f506abc776edf8fb7bea80d35170474120c74964fa70fe26601ddc9d8156ec7f633d0e3868f7d06ceb3ba810d7402b4a7290d41dfa0d6e705b7bfa9061db0060dd6b54a35cf825315871767be3c1cc0a72aaf4f77f88bbc653b0abe42c002b0ec41352ec59ca95ecedc609d404108d6d6f20cca8d11132f378dab63fc80ecfef7aaff9495d5cc0756b42d135d03016dd727bc6bd36731b90c1c0e3a6363cbab17d4448dfc6e51a5eefe00662e3f716122b266c961c00759843475ef3fe493e65d71e4409181f09cab43c9a3bd6f9568272bd73205cf25c90578090a5d7da7665889b509a191d39a1ea306a6e4749068b722311331bc29eb6e15210231c2fa51d609278f725bd8597333f21c55fa9f865727ad270eee279211f0ee6c401c4d56830763392e67d47eb627bd02e045974cd7265967a106fd5017b11dde56bcb4f36233ed8fe617c1ce0ab6a121addbcc6a572be1d845ece77c3703f76f0c1874f5e1249d34dff3dba83c7bda016bce88f035a2b7709577051c62600952223701a47a7c4f8b423e0b6a7fa789f2cda04b0e672f819a634e7cfea579de742316006331f8253f06c4f4f2fde42ca6a1e34136a725e74046853d736f6b2aee12f2cc561ad6be54c98c66f682345c6d63d05ad5572b50fadad62c4a578ef5c76a5632148facc07c8948a7d504bff5f8bb5e058ff6621558b598dd1966c1c79bf2346bfbece0f870505d66212d4a3e3c958d80d857130996addfb72418633f96f4ff8832fbfb5719839fedf133cf9963c8ae8adab72e97571be1a2a96067e8f4b1e01e1d594f207f327ddbd3cbbeef256a64470a17273480f0b94cda46d507e582eb200e14d3acc2fda09270a4c8480c803f2e24e038c4e2d88f17f1c9eb9186ad72f52280e68941ef3c8a098b7379ad0ed807f9b72836dd5c21d0e948e79dc86d09ea7477e05a3742eb99cf5a5a6292850956e0e332c2ac411b62e42644404898d7ba8dfdbf1561eac6c4cd507f1b23eea86c4e608e5760031abad13f34b25826a8a2dbdcda3d0f2633ac37f9b7bc54e56ebcfef72cc783223b5196cd25c451eb6880cfd8ece87fb9f5a93173ab06f73e0f2e7c872a89ffd0c03321838a5bc8c93713dc4064d5a68e31bfb13c9eedfeedd4caf6972fdfb7ed1267071f80f65cb3e545a37d5e8c8ecf30e8f362eac21f59e5b0ea9728807d3964a9e67a7d7fc6e930369b6104dbedbc0fae878621c9eba339721e772e012ef847ac861d750d149d4140d3aa12b13a3f9e06595bbe031e072f56c1f72142714fe8ca9843e5dad1510d2e71fe4e1dcb62e6f3b580cda223707a7f024348520764e1bb54b15e23e71d65800e854500796d9d9f0b7c01e9ec9738fab8a7260a484133ec28a8242e5b8f67c4287775c54ca04105714ad8f39e88195eb792c3f34bc42d07adb1b75dec53c517d92746223c348c5d5136be7c932ab328ea67258e9bc2fd6831994d877dedd1591dfc000b03cdf2dbb02bcd5acd5188b233d3fb11ba912e75f1d6e70924739ac725aa72d5bcc12c0ca6a6219b4c09887edfa72434ff5b43758fa902d68b47ecb781e3c63149ae89e1686de21e398ab18952d723ad40bff50d64a0371199c408d70884d8752ee9ef315aa86c7a4d003481c69723f07c2543b8fe3aa3dce0dba71eb9d0a50b5c66f604f297f9f367376f134cc72a303a037373a320771e8ba479ad5e0ea9a9f7e02312860204ee21c308a2b9727cacd37fe74fb024a500925ea0faa6bce5ffc68b2f558ba953d0fb1886896cd5a8b5ff2a96f1d9acd3778d34ed0768bfef46cc2307a396959ecccde454a644672df6dda7ab5e2678c0d0e96023100aa125c32edd234e9cf37cd74bdf6f14d2a722423b3563ad9cc6de772f79bce73e74ff1d1091607137191feb212afcedd272a04bf7dd9400688b5bdb393df42e3abc6ab3b1cb07222686294ff528f5b552372a3d10f040bea403df3e2487cfffa064d152ffc6b0be202d9c64d3f3d754bee72943a754306c8bbe1892e92519048d9bfce69b2753f797cc526378237fab0995563e0e43cd19a127cdedaa184496a1f97fb4cb4362943aff45cba7d47eb8c3e7220e6100619bf578322d6855c3c59c2b040967cdb0c241017fb3bf9d17b63a25a6af98030911975e0f83e6f557f89ef9fbb90f60c3762760f3a30c5064ccd8372c0f05d205a97702511345e408dbbfdcf6863d98100e65104b8907d6dea196d72312e4985c80ce27ea1de4696208bc1a39e152c59ad2e70e04652885f1dd831724a00bc3acf7830931ceb97ed4a8bb3894aa5dca0be0335427f91444f2a59115f07ca8847a11f8ac472c5178e0bd27563abb9c93654d642c52e5ac2aae242cf728a1f17ef15ee4dfada93ebb134bc91086d7a3bc2d4d464d4395a795824bed90d24b3e4d0a5885917ec777cde26d54221cc4c9eb69dc54d1f9b57fa4154bcc8725e38daad3dfeaee88524b2b682d876b5406025b21ba50da53e4575d05c746f5897ee0ba68cdb4ba7d9eeb949ccd6f2ca2f69530b947c1f691c7ab4a29bb93d72eacd2ecb46faf4d5e7df519453edbf981e908632594190bb2083f0b34b02fa2c85249748bc0e367ef398b019ea26ff6930f1c97086489204585e9068db7e1a4cad76c0ea46bffe8426fb93fe2467dd85e17b5df83bf1cd8ef8b4da9b3266a3721af508acd9beafd8546741fd89648e7708607eed9178d0be89988783bfca5d0d30ad19c40e580e50fca94ff43eb4350d9a215bdf5c922f23d30e7817985063720107399eaaaee0d3d29775f86814323c170d9baf87cabecb682c884c2eb3352b11722fa59a6f495e9cb67c16f1a10ec7b6e2d34ea92d2a31a0723cfd04d5c27288c54c051ab3803834c10efd64d5fefdf42d549ecf686cef709aeb4658ece972ede7440e623226ab14653fd45f8671a42e20f082f3991d9fd3260528eb253172a1630b54f54cd21c0c25b39459b9b0b6c0809243aeace69b923b4ca552ccb12ff48fa603c21454048dcb2184c3c30bb6b21abfa6fbfb9465590069eb694e3a72b17aea3c72b22d628d307b538ee2e9d81369e563c08cd721ce210033e1401953eab30b9d456af26dc16b374d155c26085fbd20e50a8e1b300d481349fdf3cc4c49c19e8fea05d5e97c43152ddf28dcfdf792569cc5688da4f0bde7c32c324c6a4cdf869c16c1feb1ad5783e0afe91dd0a7a02e16126724bc01025d2ec9648c225b5e149c84dc88a27d87f44b9a058fd654be00b2d6a9c9e230c8e169188b9e72d5f37ad18d321f9fef4f1036ee5600e51f54d3d388b56ba4c6cdb44895c1de72399ff220f98cf17c3cdd77e362ecd8ae6d6f2bdc118b411ff0f4c2e450d2104458e2aa25c23ad3ad326d3236255f668988584db6007ba80cc31acee704385c549d8377db0446507e9b5db93cb0cc29c862372bf96d7d8a510358537002272372f35242ec0d87826d88e10de41dce087cbc1489a918dfbd3566001ae3244ee672c560195a30bae5d877cede009dcf4e2fe385122ca1280518c1bddbd5dbe0fb7204d4d53a95c20dc759a17a1dc2d9400c4515e0d0357ef5b5db773fa435186f726e806cbe620806a1f5733f5f448ce24e007ac00ae25916d36862763782eb937239590d94af8c6d0bdb2cc27325cf30471ec76a734109b44653bbce93b3bfba72580a9d44de7cfa050e051d9c1c919dd3e95c6f46a5e282781be9dcc4f5f5be7242f9833b66e756eee05aa58c7843ceb1e88d251544f0d6fe0775e17987a5a672dc84e7e6a940dbad3ce9ebd00002a2e37f734b58ddfbae8cbfc674730438b8722631800e54bd481836ca59b9b88a2005782e788687302482fa445b535a6e7f721fbba6282a693392d6b7af85c2046d3a9e0d17a2e6bb75494f605bd1fa50ba662777aa66a042e82e1c8f8623719e83187110446f1a77dbe3a38757c6c0c28836cff27f2913e2a5da70c0ef7b8d31021785c1b8966d9ac7b00fb2ec3ee817ff72b36804a52df6829b7c19ab89c3e7827999b370b3e8612c66f7bcbf963ef889723d277bc05ba44e93299f97fd02e3d2913a80ed829af70c74f2f305697142e95b1402011f1d77bc34521a5f6fa71d70e9e378ca7fcfba2e40330bd9d9b4c3787260c5854415c71ce10959ba92fe2207d4b2bbac2d94c70f845e4c8bf0f291b5722f2950c03924fca9c1bd9954840130bf561a6cd78f0b9e5c9614dd94d794ab724fc7940ee9dfc916c500664188499b33f1fc52f2a801f4f5d8dc39118dfd672fa9d32bade01e945ca9852691e1b11a538a77f914a3efdff88799d4130cac0072eb7b1235580ca5a7d8ac978e8fecf000a2dd6fbfb548a1ae8cb64af9ada46d72d30d8b78f641a2d9efc380049f008badc7c9e64685eefc6f4d373548be0b9e72b9f3455d38f6b979dfb2d7fa8bd0f0fe14ea2d83c18afa185a7942a6e40e1772320c281d78c08846f15dbed657d6a2cdcbb89d6c3cf534f7b20762d74b205c729b6525ff158ab70dcff744966b62874d9c4ca8dce111b47fa97fd5c5a061a230cdeed1c1d11c55531a6ac32df4623a6631768eb0bfa1ccefdf94552afb6cad360a3a53a2b2c7ca09af3c7826b7bdd7cacdc16523cb93e069cc9649667d4f2872ae4dbd743f7706353a2863c9e0eb224a3169557bb38652614fff108698b5fc72cab84990e80b6677a9d15563dea1afdd7b26368b4baa12171988083e6d72022548c590a324905bb9bf8d6de66bce8b1b52b568d73a204786d551f1f6ae5abb724cc0121ed35cc949d76305c4df97a6f88ca480537dbd2b64f1e70a8fc1dba956cbc8b08b1d5f7caa25ef7f980cb874ab6895d029d77f78ed987c2ba311582a6580cdcd1456cd5f03f3bb61acb3365066da3492aa68cb31feeaddb16cd1bee1726ec4e3888cd93b84a3b242e91d2f1cd21c9b6325a5ecf7f7fc454bba37225e725484993fc0c62ddfd6c68010bdf0ae439fb8c9904fffadc6e721904c16c5bd724155acca38929a5729ecec359635c92e1bf3f93198f1d581f17b846e1ad2d3726f084bbf8c6c35fc21c1c58f1779fa32e64929029fe2da18c1e7037d9ce233352a621cbec960cebcdbc2ae960d1f7437019b09c795ec25400143142c71dde772b12e5a53f770d09958a87e4effc1db29c5cf91df8b3414facef682036dcd6506892d111f3f68e6245c06debd412bc8c7f7fe18aff90494e304d71d98f8655b7244c4a5cd3edae66b69999c21309971a9a50b802eb81d1234127b5088e818e0339fe04c69df542ced921e5484344ffa7b98cf419358e62f45eed7e8478a0a0572f524e1c8473cf56f8e5f4b772c8d98c67377f88a580a50d3ac2baf8193741b4d8dd5450313764160b2be1bd952ea266418e19a44bd84620659f435ae8d01c62446f5572f3226221a83ea6c19a22b5a06ec7a45ed31fc99e6cc16e2ea4356f351d2aea67f870c492fed5cb4f09011b40a865a65c09a6c2301a28d3765a500697220216eff878c4237c8df10bb1e8781ba0e5aee25c016def538462496fa60ec25ac735a46dc1aae2482a4dd6008a405df7c8249ffc94c078e36abf8ddc6b8207249443f68dd10afd3902fc6dd6b6ab028cf286697123ed5fd9c2fcdce90714e72122d78bcb4f917f23f6fa0389ac0d477bc1ede8b90100a12bdf5ff1981640072c46abfb8e764534a9448fdc73c33c6bb33938fdf4678296bad969cc275bdb472fa72bb43afb1eeff6003d9467ebeef2ec1366870ecb4627dd771eefacecfce34051885080314ff30dee50d071ff710e66bf777c0e611311544f7136160148e72519420c26c8616200f521f14446400aea86c67c5a86c0d04f69f3be34bfd7872192a38629150b24c53ff5370ce5023c7a556755a95d006d5f1b3cb3f4b880170a693f450135e34d0728e61742f6128c7489d918a46d0a27378698d09bb70d670da1b54b6446229fa2ba0a321d26ac635ba81ce05631cd8ed3cd23f10979ed172c31c84cf9aef4468a935fc55caf3b01690b7815066846df53a39669c1e78487212999fe011d008cf5b23218d3a93470981879a95b6e8533bfd182f84ac6aa14e76650a97872de7d6378184bbf4a3cd0eb40a9843f4ccf84786a96fc62c7cae6a2e4e30ceaa81343b2e1fef204e03a5d11d41ff18822530cb4bf03559fca1b04d07c7a539de8d3f48cebe055d725e0d9f6ecbcfcea6ea947af08d9ee0a5a55b72112c6688b4124c199e0b92d8cdbd2e57cf4aa31effd4b17a279c92dce3c56872c9ec4c3f87bd04659bf7b0e105bcebbbc1a7fd78566cef93cee6dea37bb2e04d05f70a4732d3d9f622c7e74662e3ee8e69df6f594920c39d07608fed455bec72003c5c992d7fa345db9e512d78c55c92a6f396195ae1173f39ffe21b7e093b72e42b0cf9e9b462a81dee1a4a59a5ba4aaecc2279f57565859253bde58e3638724d9e6b64aa307142ce7df227d80b8b3a3e5f21988c5e71a14aa0c0054b83fa7258b637ba17e6770bc14fb43d89cdad34dfbea00582633c5d71b20a8b377f4b646f66caf5739f5cc4e9748d6d72596f826f46a72eeba68638037cefa89786993e39f3f65ca741f1adad0bca4304455092e7fe6d17a615e0c971395fdbcd14112fd0cbe5c7016ee7fd6d40f153bcf9e306e9cbc65c0e9d535f07cab65789bb38726558550f89b27203fbfde9d8955142e1a7800f10e4f2bc1b5d88aac49794c44aa311f53839f153e2d8bad363778f77e6aac30776e351b29865a4bf861f493a7239ed822cddbc940d075e4cd302f9dbc91c72611e518e8619c39c56a7d8d34872cc0e189d844ddf85214c6d0705ad9249d03fec370dc8482245a774600baf415fab2b5de50930418fb8cebbb5bf6472a68907cfa30e85df7e21cf12775b0020610a9acb187796ff85c397fd6404a1d84cfac754a8ecd14892f5b173e61d8b207298e4511447059fbcdf36202a7fc2f7e1c2e62d085a24cf9d4008b66d2a7cf2518cef55e5b3709802986f5fa62d58e6c1db3129dc986635fe4c7d756310359547493b0873480817d23caff293f7cca2b62569d5c8af7863b03a2dd92f3f26e672b37fc84cbb22d656f642339d07fab3452d9679a990270a6275b82b9d2cd86e2b82e48230544961ef64a6c9b32df0e523cb4bf05a7c46e7fe29c05aba5dc9a0720b495a7cc61ec7579eeb698b3ac19810001b9522b2a01ff85995b2765d1761720abccd59bbed01f22ee11dac60fb95e551728c33e145577e600d0a537c0a372aea7c79c5110676e58a4b5b0a789207951812f4803adc400ec74e339e221e1916f76e25428c99142b5c35c96d51ea21d08873bb02898b69392b4bcd029644356b0d0d0494e3f0261bdeecf2892a32c5dcce111b376f92c3f7160ca7727d016d12c9cc8f967cea5237fb49ce0b0b76d9d232f4fcb4d3dae073881c78749a2c4c727987fadfb47d7c50cd9961cd09c31327e9d86a5c318b35e8cc9807b3848c52723c4dcd3ce19d0988d0896bf39a822ed2fffc554065377f33c3e778e2deaa862ecd95386d4cb5e139ea55ed78f98698c24f746302238a69c17e2ce425b4b7f95fdbdc548636b4ba0b4ac0f71b6b6fe00d2566e1ae89f746a9090a004f205a36725dd228459404fab23c88dda271293b268880b1ef9212f0c07ed055f7353d4d725d4ca6df5ba65797072828baad13ccfc9e022f1205aca4f1e8023f350407194fdabb52355e77124f36f8ead2fe9df9ceaf325ee3908733afa6d6ce86e9edde40018e24d220bf10377adce6758e0a949ec01a9a8f05e0522b8648720ffdc871256d7b538b2db5f54f1e33ce8f6a1c57e587fef8ce852210a35e5a2c6ff8216b72ca74fa4dcac34747f1797c6c7471903ec477f57eea35a7509f1f8252d5543e4fb1d132a365b665e5dc42dd688116ea87f632e7d5cab76a0945d55a082b042e72b3e1728150baac3c5b40d067458a9d5c18a307f8c6ecf371b09d5b69da1dd7605e517f374fdbf8fff32e49ddc7183ffb6fb467af09e78dad69ea5b5aafc4a40e461642b6b84a05ecfe919da30f87041b1e8b1701a12a8b57b72c986b0fb42c72cd3a786b8d4232a0833159337e290bb5d4b4e7e39c042b209900ef1836ece74c4e440ac57bbe688e3e7557f9b39774b004e0755e173a3259ca08944abe6b834742b6184e0b56f4e3104c6a54f12bf1ad57a288d27c9e209e076347ae3e28994037646bd36e59c107cc2077bbb62776de129f2d586b2708556970b23c3b3cf949efb78817836b4aa04e5ed114cf501b03783e1777d22dbbcf44bcd13d32115372630f5139b21d1824a69d01569d1b00aeefbeace92a12c3ec65adb989abdd881f4a3a6a1888cc260f22021b73289217e4529eb808da1926e7d046a5956dfab0671039cdf71644669c946b58a93b5577246efd8a5e3351a5bcf74c2efca7f3f0683fca28ebdac7252370cdb0877959c23561eb1e1f34338bac9361d2ccc25a9672e9ed9ffc4f9cfb7b6e0bfdee5fcdae3285304b12f2a94dd41811b2cbcabee510d68fb5d2026a6762db1871efd5b0dd89de929d5feb6a3011833a3c2db41efb723488ea6f251107094772835186d677f0ced37cfbeb84ef2c060e03a952aac5722bfe54dd996ef6b5f216200f5e40b58629e5400981f92d60d7d4dae483ac47724c9b46790cf77713330a6774c7752cb73a7d3257eb2479f78cea4003bdeca172070fa3e2144bf4787b2a5d0e6f2bf1a00d8c0e86d72ccb09f6c28e225a71584758f969ebf3a3a0d990644e4c2bb7d17d7d636d3affd8fb10ea4ac887f2aba972220806dfb59bac3e42bc670fa7de7e0f373aee89b73f010f0ed43c32311e2b21b45c9d0bd687d5e7f5d00781b0a5a85bbd99733a3d47f771040cd7de0415ed72d598a629215097bc9a9e4707891db10c02142653a89f1bd30bdf06fdcfad3d21ddada49afd90d33d42ad07ece51135fdb9f708d04ecbc0ba5640d4596ebf5b72cbe2cefbc3b2a1a4a66cd19f5e9549c8d8300805a77c09d317b443a8be89c742b5f6c523d6743056c69e515d30f45fb16bf2875e3ac3aeb9a37923c4e44a3672c853283b7560a4e726b520f5fa20150c64e36dcb75ff1385f88e0a070f0074720fd1bdafc1efbfb65eeb8b4c300c0ae0aa8fc408460467506ab8df4a02b797098088265ed9382c1cb27e1b959ab2b0ae31e00aff476906fc0a34ef3984d31222b05e30abf53977d2c8b18822a1c11bc617d34feac96c4f31fcbca719c803e1728d87821666993707a0fc9b1753f6664c765c975ef72d854c06c372614f2bb56c50e7f7855a973fffa0c0d1fdbf83849bf0b9076397ffec31d486d397fe26d772fc9da6b5560beb2b13e20b5413051045c29f27911068d34453eb0bf9d5ce3972b6c44d72498e8ce15afc1a0f5d0f358d6c88e6d35f49d2a55519e3b6bfaf7b72e3b9a58b6732f4ddc7419bbc496c453e13bd12e9f58af6a57728d1ac21e37b0a88878dd49eea52f4fd3c8cdb9dcc0b7c312b74f66f1c77cc739ef09c272ad059b6f5e75080b9614cea12e76a8e206c6f00f855486eefbd187d824dde2fb06772945ed0f15123f44a32d1311edf6cc1e210ff3e8c92e117c9a252ebc120835372c6d77888e77872c4dd125be5b38cae53135bcfe7f5bb5984dc0f9c163138dd72865626af8e831fff3b449e791046f4419bbcdfbb0a614201133f4b84386a8c72e1c46d7880fcc7a2412cfc97827522777827a88d20aa6c918d7cd3728bc4bf72bf8d2d46ec8e906113b6c463e14615ef6ecda736e3e3d04a5b7b5fafaa457072389b900cf3a251ecdd6a2774bcc590145d7166d26455a7d7f13db58de57a8c72de04a03e16ee9103b07913022b31ea41ce31d61cd863372c975a9f838ef53572d5968b81faa56ed37ec618debef3f71bcd62a467263860bccd71a8205f17530606d2bb61df3fd9f238280efff16d3e6c340082cc4edaee235eedecf9ef577e72b5797d713bf470e08a27cf7dc3e5bb2303de6a9edacdaae23fa6b87dfe77454b2c27d598d9c16759eaa457a4045cc5403591f259c1e90d7a332cf16cf9a00160a2d9740474eace924d835c0506a3564b89b7f7d46a9ce3bcdd5a8c602204f9720826c09b55c761bc7e5b9c893f4b14c017bfbe56cb469eeb2decb31103b2d37206fc4f67f8eb97982b50b5420b6c09c1c4cd52d04db42486a4a40ab1d14d5f691f01d6ff0da6e05e142e8702ec28fb9806e28b46f26fe09f9a2a9ba8396e55720e061da9336740f14e5b7767d0e70f36083c0cffcaa2492251cb914788353a72d5de23afbe83101f1e61d9995c7db32128270b17b99b4782af841038a58e7106de2b7f8a1b2abdaa9185d95fb4389d909ff15eafb41ac365f4efe4b5ddc13f568c667f0701882eea0413cc5052b669d86edb8662076d8a121a3022c2478e7e188a2dcb7c4998a7debaf5ff065a4e1a69e7fae751b656c375187f1c3ef12ee072aa620a07e6718948bc5d8ba3f12dca3cc5315d6918b5aab09528455a0221c772c89a40030ff421b0c5da9ad5f2969dee875933ad8e525cc47742442b8b1f2671f727167b9d91beed059e83de6a92284858e1b70ff6866ded9f4c391383970272be38556e9acccc66a0e947df0ba4dbd459499ed64d4e170929a908dc7d5cf072b58b4e013b7b749684b95d4bf749f56a75b81337db6e30f1d80e5c3ff392e2721b516c5770a3c5d25f2b243e1d2a8dd766a6bb1eecd268d85d4f25747e5d6c729c2629d285f02d8d402e4db446af401cfc8336a56cbd57a2272a0595d6cd927243dcdb81210a9195c1d1501c2ba93870131f0687a491d65c1be2f84ddb3c7334c338806eee56a6b1da4b4681ed8b3382e8dac7e1aec03ac617943595d04a8c72d2aed41aef50eb5efac5aa20909b587c87f6869675ee98fa5c70cf0d4c80bb205b37c8cca4345063bfafc1e41d67d804fb82fb4c500a100c494daca06f09c01c9a64bebd65cf42db0a613aa0c4bdba267a92090267bd2f9cd0c90d5a48c68b2ab2d5c69e21ae97d929597a6f2e42762c1545089a4476bd774d394e98554ce62a0237955f2e4efd6778e36e4e17e5787db469ca654d408b654120ddc52f5c1572c687df27a4e75f896b5bdebf506fb9c8de83ab7d793d8fb51e76331db81bc172a7127d8b2729ecdf4551afce3873979514b6ab7e6b9c7d5dc5995014ea6dbc0586193b058de961b2d19000534c255eade7454fc5aafaec9465813086e2c8cd72605427e2c6adcbba4ec51c1c6c4bd871dcd15e5c2c1134c580ed292862f2847209743462e8f76a6c8d4a2480908d2e0a9199af16c70daf8fcc9c7f855bf0ac18702d4c908bd6984bcc4beda8047c6aff0f7268ff784ac4b2c36964dbafebce1e9d8fe1fcf9edaaeffb7c4b75244e3cec2415da79b5c0fdb1c7338d7a8295f3532b383e10cfd1a05183fdf3e4c42613fbc4399e6480e57e9b3172a7443b067f72a348b776af74b803a1c5f373c5a51d33125390f652d0a2565c3e6a1aa5e2fe25c6e3198bce7239ecd38b45e13d62921022bf8ec1538f2a624bd395d5279d482a0e566cd786ccbdd92ad7111265bd8c8585e1d7604e9e4a015079b3d6088865004531078bc11263125205cd0cf6003ab3aba72c1450f373e2d5d3e902a3309f72e3b2004ca0ef416eef91e41b70852c27cbd4f10c81570c970eb7db91eb10280be933791e600f3103fa060de2c0d8c4f4cc7b23a533b1411be6694e4c71fc2f72417dfcf747a929c5386e2860ea0f14201d59bf1b4ae7626c2a9a34e97d02616f42734a729821cc426119884b1348df174f856d1644aad135910fa1c3a6abd0726473230d0b76cdbd1162522ef30efc83b6ad1ca72c75426647acebf4f699ec0c97af15ce223571db1c3b2485f80fedf93cb5e26e7f2714f0a95d02af7e2f8372263a6f787542e37af8388e5dfafc27a6d98b9a3a8dd8c39e87cdf50e1e80cd72fd90fb65acfafe41740d7fa9b40d69e950c5dded362f102678d658336ec065244c1ce477f714626a3e2c8d1e203e56d0d4981f3957d11599ee147cbb51e6bc3ba223e28db6251dbdf387c043c77797a5b6c70d245183368f9659acaff736a372a0a6b14ea68598d4d8101925f2bd6650fbe78dca91683894e993856082551b72f84820dc29b53614c1049ba3bf9ebc0f663bb9b397696c5454040ecffac086728422944f354da3278f3409147f1c90d5ff0ccb0b918f9d38d950423dd64a333e04f6d90b19c20f0fddfdf9cf191243315f3bc5ce019c4855794cfcf46d09cd6fdd44dab62dac046ec7783a83a3ac5ea80211d5afc47cadf8ec0a7e6cfaf96d0303c77f1f3d7473d085c78e46d49414c065d349c5ef9e8e3cf4eedcff7b850e48afffd9473251b50754b9c812a5af419aa5041b58d02e00a2f24a60c88832f672c7dedcdb9d205e2de69f9906428d9bf6cdc2b793109f8dfb554c3ef233834a7233ab7ca6abc854cd09b4cbd30d8a1fd909ad7e8c5303a477532224abbfa5081b0ae42347c1bdbd2b14e12b4029c2f7804fb123650f844f24a5c39c4b8711347284c93d59e9b0fc5a3c1489c3c327661dd457c9b8080a8772d59a9f5c236e1472293e58d3abc0f093a6b534bfc26d6942b98a734af9d5f2477f53642ae2b7d51ead7f8c906a2b6b5d725a2a7fb65d3f9f958806868498804182dcf9051c05d272826f5ee49945978fff7ca79afce0c464e29c15d858c41e077842bb9e11841772a07273e99b7a8cac69124814a1c381649e6f84958438d5873149792a88c400216c834a69cba917b97a351377bde62be06348fd4d2f6301a4996c8e2de677bc724ae77f8fe726124f7da444314745608c641b4d956c9c6ac55717300e3ff95d725f5ab961b8dbebc063419231da87e4b52372da8539238a4c7e97519cef7b704cb07f1ee7e7fbbf8b5c23dc2cb75c211b9e07f3f624ea6d8d579c73df33e9aa42e54d51e04d745d4f3bde7762bb205161443bd8a6559f39ad19345f19f3a781728fd184e97c798fc98c6d9c6034fa963e85a9ef29d27fc6965507760bf5fcdb728d66c23ea0347236f2b46a42ed9f3c399fbf591a326e16444a8f69b825b9653dc6699a536e2d9f736e6920c49eacf5cc69ac7bac489efa36e50badcc5f64ee04b9e1770991f07470e403a656cf34ff3a42f09cfa0cf53374d68bfa835d30025db6cf6e0a84be20f21e7df5183f263075cec31f81f1f838822f1a44bc7ca03372ba30af59793f82d5342b5defaf15edf9007173123711dce0151fa69691968472fac25d3a8dccf08c6c159ee5b236b7343627f5390f285eb2bfdf86e7dadd9115e4606ee7228714a871052ae71a98a295e7eaa0ff3425e3de4f169c1c1adc327236a0a5dfaa1dcc9a270e9a41d654893ff267951302104d27da2aeefe61cdc84a83eb87ab7a5e5f45031d3211c6d2374d6546606860770cc26f147dab0a05c8720e7074f5ffd0045c7587bc6d6477f276e8be13aa1ee9a17ef7efe2031e61ae7283ef4cbc6f357a0c307f9af02c690a6ebafc28cf542913ecf6e59374fef69c72aabf8a614a885d7dbce1d23bc68a7c81079693c001af3d8adeaacf99f03e6872be5e545e873fce8c49a35210d1dd3e0d6b6cfbfa065ed8aba501f9daeeffa62097ef6c119bd68e33e2baad95da4065517290ca083b62bb57b7eb67b193c5c02fcf8231ac9e118f32594aa8e1397d93eafe4fc20616245c954912fdd72858a472fd247778e9674754faa3a407a2e1f61fbdf52840456bae69523b8436d10c5c72593d889ad2afce70b80831aa47b10ef7f1a4382ea191376a2b58c48c3e1fe1726b9892d579e74dfaff5536fbcb01d571e5630c1c10d90e79dc289a8dd3d81f724583483251495df2827b074250fc252ea4367b71b5bf24b2655327b39f4cba146e72f5a1ccb2695ccd4ca81c7a17a399bf0671a3385c3110e5822b3e8db9377228693df1b0d6b736f837513c72e498a3b3745b0231b2398cba865964b62d9a72cf44d58a19b61576c866648cebb5af6290462175598d876b9afbc7314cbd0e68dff0c7ecf779b869fae44c75d40bfcb4cd5377fd5a516c136fb3ec04614d2a72f23b041aed6989b9ee84a4e3e40ba1bd41b2dbdffe5a4477a42dc9cd597762725f63f7b76d9db639cdeb78aa92792d7cb0df9cd6a475295464d1bc6fea57c113a128fa162aa2d54d71f7af53ff0bf5633d0b9b35db47e0d6bedc955d2c3eeb7249ef5aa44f0a284f59b4dcee735e0d72ac0ed658c973da43dbf3361cad0aa77276ff6b92bd62ac19e9327e6377365b7abe63022c275fe466be93be8b2dda8672dbaf49ff7e0ab843da64d945785b5e7f533bcfa1617366f2f40c847b220e3f72b450f9617b0ec8834edce1a0c81ede2c024007aa91d344fcea8c9b15d62b327204ecc1e89a4c07c23a3c875ab63e0fa4a1cba2e7762394e4609d372cac4cb372da6bb7ea6b7e51d6649012e29136cc42f19e7088d58000f87dd831f2faf4e801980b290065e29500cbfef45c4a6a06155b5ac1c79d5dccf16fff37e61e02ba52fdd3e8b3896aa3af51a5f04dae084cc42b00c62fadd8bbd802799e1b7cf9a51be32f3ea693db2d21ca4cfddeff86b97cdc2396c35748e3872bde39c0d1324b72ecda5b5812ea3c86729e887b6b96a06b4245b9451b9cede23da64b560ac26371e0cd8828f06e8937acbbffc9bfa3afff56c184149b6f7964f5230e69e3de17722088186e4dc603833c2351ceaee92bd2758b3cb1552673192df42f42953186229d654fc7449a1a294265f5b31680f2a28fe6bda1203e18b8e809b26dbc099e72088632e21998c93d479a4514faac663fd22f8af0b74ac834ccca4d6521c60e7245f57ea305b1187e5bbbcbc4a5425126607af1336e085858e3108c186bd10c724696bc1b2a52cff7461b05d61e827af8f85965b36eeef59e0c7c164e20249c17c598bb3f8793802c3b15e6f282d17d5eb34835bcfb2cac200567a82e37b10c723be684ce32f3ec0534601ef618282a493fc45177aa7814b0c83355156ba45b4b4f2f6f117c3ec0d460ae528a7a92623cb33afbd2fa65157bd2e7852f4e3bdf1e603e2226f3d07f2e4bf633e1ab627f5a2f34431c480a5ae0774033e8720f151ea0d95e09b45b2d3ca751317f90de42e54bb3f7b7a313cfe9b9a1de64bb22bf728d5d923435832d9de1228bbf6650a6ee8fa007814f3fd4bb22b714ad1517b03eaf1d98818d3da741c8a9e3ca881d36853f11dbdfd2b0b926c5ff214295380472153a5575cd9279716b6fdf46c70f799169405f32f4fd60bfba236620ae79a6724b7bf9ef381e985e20cb188a110bf0e12dde244476a2b3195482a384d611fc729fb56242198e67826e5413ff526e302fc28c75dafcbbfc2d78cb6d28ccfcf072011196f60e66258513f7db2731e82985a20b30087fb9e10c8a1c58fb0895477215afd2a5ce7172abebda3722c92517095788a44a6e7c8633b3a82edc88bd2972cfd8cb3199cc4be65c82b6a5a05d15a81da0693139d6279c5bd6f93113453d132a5905303253b0de7b7248d24e5320fabfb06943b6cc0e6b750761abb900cd62e0a719ac1f60420c634b4d7df33b763431753b305c29416766ae1b42998d88170195270c1c2cc499483bf775bca9fe78300512f0f58f642f0e769d71f4b4f313158c464917a344bdd747d5fb323694d66f04b10256f0cffb8d534297c96ba76fce223832ec7aa426b55722e659dea4aa84d9ca049306c99560e154d3a64b817280ebce27e9af20d3430bc8239d1fe7189d84312319e71a2ceced37c302f35d3999a0e06423b45e463f80416fd2498eede0eb394a0e3afede8ba2dd1359f6bd726d95915bbac18a4025eb289e041e4d4006e5c9176a71041aeea8425564e84e72491979926f49355b1430b32f4b5cc57d2c1665b954538325751caaefa630e923df4c7187a1c1d9798bf4a8bf90e0e02597fde3b87840a8fb004afe07c9ba7d72b93f75475c606f352f9a22f1df1a2771f74d060d1147b330b79ebdfde880b45377fd8c35679833fb38635d86437a2a27c9df4e6074ed164cf00e2298a8b41972c779bf00fff526cf775624b6713879c5e71742d9c9d69a0e9ddccbc9402f7b52a0c09d4d8b2b9e7cb5f70945325219e443c5861f06b8d5642b3bb1a24421d56525b83b1c58864e55565bdc5d995131d5acbdcff3123fcae68d003f43968e6972c8bf8ab9d1c1b7da55bad170fd60a72dcfde06a02a9136f60ac71513a86a2156991315f5b78074371bd933edab78a1e8ad7c52d4435ee277ba27970c0bc90872f95aaaff91f49d38e8071f75df75dbaf8c3db6dadf6d874d365f20f0fcd0da729637cb48a5abedfd248d37abeacdfdd486585b48588ddef2072321497e47d47208d5a9bfecdf56ebf1eb206eba9202aa4bf966a156137c5f8e77255c83f1a37229f34e39dbbda1be086664cacfce95c7bf27d84b8042fda7c7e6e5c359de2b7223e27c53cd3837535558f86032a8fcebff5a45877626f00adea6f3f218f69d72240b0bf1505a67f80b77f0ff7de4546013304ef2657adbaeb0524a2a2cba9845b2648e73c617c25c4cbe05beb01823fda94d5cc357f964ce4f0efe52204f27383143bff952a1c7ba0f7ba9d5cd48cfd33c69a620cf458969dd60a4e14a013b722004590cbdba7cda97cbc808f16f88ad1971fb60463200de59d45f38a5d5ec727868a947b9f81cc94d7030ebaa34e6deb92bfa421d282852a8d6b13e38008b7271b7fd5a709053275545da82029d10d7ab7fb0dfc36c5180e4956040630da30892f5b2834807e91207df748c6bdfee28edb573cd807fdae54551b64ace8b28718d062bb9ec7986bac47c978d9661f70ca91109e60de6ca5c019a32c5f7facf2af665268ba59b897b019f5c255a3eafdfa543009cb551cc31814f3a08101dcc72e7f527f463a83892c1a30d836bd919bf68e09f92b7c5f39a5f9b87e0f8b1ad7229f87965e1e2ae09147b6ac3418ba2d4a969193bfed9eb6538effcf5354f0b72eca6375085da9d2adeb96db1575cb53fbf286f8507b910a046a2536058d26f728ab001fd6d5079e2da8485ece44f2721536f46a9894d888bcfec9a081da9a4146c506fa47cddc79dad1aff7653002120759a91739a66ae159ea42ba8c1ea77457f02c27d9d61c1a55a78daed9290ef142098882d56f6ff5b9d4a621bc22971283375cce98a6b46ece5dd44c04e3398cdf0a1f47896ebb1e4f2ef37aaf3ed5a72950aaa06003a043600c7a90f4207b6e6d8d63a89aca941a0f0c418d13ccfe317b9210e161bc94a457c9529f068c27b9c6cb0ab8b47f9c463dfdc32b76ee8f67291891c5f7ac3794d95141bd39a881f4ce6150ced8fe81a721652a00a17014a156087cbc8d5433da694fc8ef93ad1b84ce95d852a94fcd1323485b18917760272c6495fc4bd35c48a0f21ef76e3d5cf0e75843f6d27b0da8d244dc91ffa1f55725ce01621ab9e40c43f38ca739e75f93f2a90d9b5fe3404e2ed6ce1fac3a4551d6d9fd31edb3d603e5de9d3b752932b3dfb4c145d6c580964c25e6c2dc92a08321c47afa1cc3fdcd88c35d7ba2dd30ae993870f8d7298e5491f0fd93fbe35b672571abd3b16a9176527535daa9187be329c120eca2a78fe5eb8cb31ca8c884b4064e78353f7105207dbec7ff9dc81ca723bcbcc13897364873a0f1c8f7bd2b225f15804a979dcca147323570ef24ab65c7edc1ec9771fc7e365d62b5585cdee72a13306fbb13e094e92bc754523c46ba0548ff5580ff4d7fa287d50b79b153f3424dafc1fbb83b0ca3162f84fcbbc8fa968a0e032931bc4e9a29a216601c59972269f315a2fe8addc5e2ece2ec5b21353cd7ddb15f1dd1509ce19af1cdb15502af5090b84ef118744ef48ce4089d352958d30ca00b34c71ee4417b1b547a4fa5b56be200f9b76811071550e991e0aeaf9e67bf975afd529fdfd85e6155d505f0ea9b258b28f5cc01314c04e14eb6863689897c0ed2174896cf0bd6f509b6198726218a424b6743d74da5303c29c523d9e3b17a35949c326cc9b7179b2cb66ad15f907d7de5bdb2656544cef180c2d067f8dabc12c7187beea5b1e22258ced5906bfa57efc3b911d0a510d92c6c9b2e52046111316c2186fcd3a7f27e03b148272e806c4a2a85f5b276e26d08ada48477cab7daa89f184e81a9932d21e3837ef72cc0772a513629efcb9a3f48173078e3852071fe056192162cc757e5120d08f72ec91d5ac6941eb6c553b307818442481fb958c42efa52dc56ebc500a79355f505b5bcfa73d248143a8ec5e3b75549eb6b48c0c63e516d8a0a1a7ec1e6bf853721980ede2ded88914c3d9826965fb2eca2a28fb6a42122455e99c1dc1addb12729fa8cc7338719f941f4330a120ea83146e3425ab791ea33ae5bb8532d1770b7223651d0b699f4a1795033e05b1bd7f0e31c744e47b8854d58d35368e6e68c272546415595b6facc98e894440cbaba94f1641477a4a83a92803440cca013b9572820b1b6e1bbb141416dd91aae8fa5031b38162e7839c5ef289ac1a5cd94efe72143c4531f1d49eeb62e6a5d391f367a6dcdf2e155f59d5a4e398ec7b67367c21d6047a181e05292d4c8daecd305fbadfb290ac041b50bbdbbf653270f5379172ee7e952db4fec94f2af7fac84d162f92319f053c3829007adfb41bbc4f09407230513911c3321325a9e5ee5e5b6034c73aa3bfa4248c354aae849cb2916b343ae2da977ddd91f073913baefed586ea7e1389cc0a987629ad3f5dafcc6946e672f8b29829e18971ac411af08a25b87354a39c762f367269d061bbb00951277b09b3f153284f17578d737bedb8b34d2572301afb504d76db3e591d76587cf2330bacc0f6c3d59f6e3cb56ed6378cfca3110639475865e39f396e6de898077e7552606722e2c2bafc2577b00bb9cc42f2f17bc886b411ee37ecfa4c27e1bf393072545d9f7676902d670e788dee89366dff4d70a0e1e3de4faff0efdf49087799632056e18999003f56e175fbed8bc661446fb3b4cd4cda27fffc0f47667109907258c140c6d3a9ad69b0d5f15d9767bca066c4bee44442729e489d811f0a403e72663edd2ef560a85ea9c88a24789cbfbab1122d6804323fcbda3e0a6fff7b9b721b4e2422cd5d03522677966984923c5e345ca8db2599c202bf065f27e6845819bea35df37200ddef05fd26065eb7b1dfe7e9047773df354031c4865716304e20b93d03d5b3bde55c0ef223cdfbbc7e94dc15a2a755c39f54ada1258da276747293cf8b9f975e7e92b1cde7564bd5354e226fed94e5c90f6d0f0b7fe31795fb72144dab4e064f0cd537ba08cfaa24e5ae8de1f36483a0e68f6203a3e36a941c5f9664b117adc34d4294a9bc82fcb81e1631fdaa0ea6ae697a540ecfb8e10f8c72e007726295ad152f292c5ab2d2e79c631b531b9b0625ff428575fb73340b3f0cad148a87738bd5f408c8b1e8db414291dacc6f90149c4b2e8bc59a7ca94d417253801e5f54917562912fa917a6c23a0576a26545c848bf7eaf18ecf0d9ff61724996dd493dfaae6ac03905b19043100fe8a53ec5ba68f936dc73f2fe75434f04fd56010d22d42742bf93250cc40e42f9749bc7404a90b6f3207fbde1b311d172859b598d22152a04b4159dc2b82c7e5c6f34c646ef5b11522722f588596634725fae9a8f9d8f2e45939e95f791c378d65446e1d467fe4a2044a120c04d45cb72bbeafac95043cc4b1e74803e2129cc0f6730219d14cb6c7ec28c747fba14d8714287d04bf1ab9e4df0781e7a5306148536b8789cebec72090def2f843b76d44aa4f36c7a6da16000d3761b49b50bb8ab687320d094467960cec42a9ef148c272ec814de068e88db9885795878054e92490bcfdc1f71af3f763cd677ff4d7505078dcd27cd8fec17c23ff66da8da96f981a6d285319456e9cfae3b13d3f1848120ef1e7d20a690ef740be0de41e91fe03b4db3825a82602fb9d13e9d3c0ff2a7234f26146a2696fdbf7b50b73d620918bf4986884ddeb23d86b0412b13e83747220bbccf5f12fd24e9f26005c06a1eca456bb794fe6fcc9e2e84ce4fdeab95272d1afcc8ebef47940c2b0fcaadc45dd0e580542f35763c9e40230d7c39a7e82017a1e4eb4673bee12281fd08b981737fd7435e7bd6175d5697b796c665c707321b176a97c0da77b19d4376ade3e0e31221246a444bb544b3e2da02052bf1075553dbb07b75987fdb5047c6d2b3fd35246460ef9d322ff29c2cd9f0c0290ee8c6f7d7b100bcede7d84a6f7f26bdb905cda72aeb5c55fc9957bb8b5da5324a89472b6a766a94cdef446ff39e1e8984e5b737d6a1c44647ef6f97f0345236f5d134ac143b58783d6e333e807000e59d56a0268d4722c535119d20d9a34d8a8a17e63843e35a008aab96fef8903db46b8769b4cbd37604d4b135596ec0162bf75db6766334eb214cf9dd6fcba2b4b0416d7c40cfc45530821912dd45ea81fdd5e270b47ff3c6103ff5219c779712eee40ee06a0f6a5fe8ff79bc7f6799cc43c3f2a720fcc811efbc75f8d672b5b5e3cc31dbd8b16b71a2b0541ea0c846c8063416172e422f20e9d28f7262a289c03c500da0c20b0f5e6c1e9a1662edad488453f55729825d1aa90e3f5d87d67e98945a0cf837d8fff04c994d81bab1fca7fd2434f728d19c88bd2ed407d1d1fee2a87f4b49b428ffe58d68174e37a1ecadc74e5ba3f79fd7db06a651195ca80ce20063a1e708b316ed697f95404e0aae3b7b5dd81458707b436c53683e44445ee1c0df5a3a08091d38feaf41cf0fb12c9d4daf3e73b936867642f9e1575d9ff7bdf46e21fd0f16ba71122c23169e12bad1e247553729fce2dac9e58d3a89a93245ffb7235d66a1637934db457e456fb45c764736a723aa5263220bd576546643c19eab2efd89978a849ea48e0999c32e7d839803972f0581ede273e47e5bab023af1e10cd484700d0e34e25aaf37aebf7cac4ecb31e5b343cbaed26499f36ff19aa05a61d6589527befe7dcfd94faf2ae058e586672a2de482a0d7545fba02fcf09e8f9506af757e3666ce35e5c9730b5c71f897e72f797aeeda142e71dfb4a9b3bc77f3ad451b04bd65e970531c78399511dd2de72e897fb69de0ea849de7a4c50f93815e124fc052cdafd574f260b444d9f55b372647071ae99184e2f17bba92089efdd10e9bfea452737beb2a7d2b4fcee2a7f726bd0f20bb436e8474ee24c61d4c2539fc65a00540d5aa9369eb232fe719d5972cf98a88389a52f37bf0ea34bbc6441dbb55d283403cd6f2ce5f6d572838e321431164c4d09c5b3d083b75a58aee8609b429687e6e3b0663bc85fe350899bbc728ca9debaf7ff7b64d826cfcaf34ad7be1424b320806f058cda756a33b8cfbf7034dd8f1012703595f43f0e613b1d673fe4ec63cc1428016efb4a473c2dbcf372233181d64ecfcadd2bd77872b670810e7c5d437edfc0034d70bb7f15451a776ffcbf5b98a6f9d6b79f66a36116b3220d7fc409175cab9b71af12964957a01072e5c3e2e3892db092267424d18822f8e0b0ecc28194ce5a31c6c895b43a0aca7275fccbc4f814ce98eb5d2e94c488c1725ad3946cf3dc4e4a3a1a4512d0c53572f3b2ad0813abd802168078fd8e50fe63c0003ba25d4a8212c8f3d4cca943e334cebe36c3c28e730a18df412f282f692cd7c1fa6098e3a2c6203cb4550fb9a172fc3e656bade9e850acc02b8a6d5c7d3f7404877b741c665dbd3ff6fa5a9ba472085bab617796f84dfe14e65a7cb76dba34a0c06111b5608926d9cb932730bc72253d888eb6da4567b6e3a0ca914aef48c07708b13551cba94a76f6e3cf812e721d8a43a0883c9eccecd4800d905303a3311351e1c9585233ecd519762eda4c7293233af86097466444ddd2d34ff5672a4144d1a1d9f3132a834ac8a3130578729bea1f654bd33600815f130726857729401648af790483650959c4cdda87cd7201aab5f10128d8c4517e39168647b9a656e902a80ffe997377e1388204e9537220583566c740b2a38a4f37aa2348a430e2e3b62de13265643733c0a8f478570a686503d5f96079573fe5e309826ae0427ebef9ca1ce6cf0c0d3691a0115d407223b1e1a0f2d72646ec4bc9bf8ca249c8d1a361cc6edb0b217f5f9e3ceab7d26d3ca9afc669cfe8d208802df5aa2e2907d60b18bd4df2fcf80b8a04c9fb2fad726e2084eaabf1de56d4af94a90bc1b610427be9b02bc2a0f65e180697652b072e571a2d91510231301fb37f64e9643a66a891cb621e223cad431fc16308efc772c55c16f1ee86fe96c50f6facce36d7211bceb2964686c4f55559e82600755772acb3265ee6db2d99dda77c890e20b06a92c209abe1e60d10ca61b3f310f3196fd5f7504a907eb581309f3ab3ce4fadf108eebf304d5ca1034f1bcfdd2e89ea72dbaf57dc506ac17362031ac3ca377c70948978dbae31ab2c00d0a57629a3441c40f20ef99e21b09de5f0ce6914dd20580e2c9d632eb6dc1953d15444e7960b13aea239c43696edb23a9be42dbaaba48a916852f8a1860a003751479a5da77459d761a1b3e52ee8ad4fd5e4a6b2c8dae1881536fdc0806580aa9ce82cc03e1972e413c4591f8373ccda13251ebe5efedc0fc187c3edfadf0e2dcdf1eb712ede720b190bee74a904444f163438de49d06656e7c50d3a9ef25cc74650f72f87520abaf43333d332b29f71918e0c63cc7396e2c9a67396cefea1f3ab78a052d11572e730915163a8bf976941197bfa3fe5086b99bf9ebdafc042e2648cbc777eba72df71e2e22f5504aa65f9fde9d3860fc84276a81008fe529463df4f5fe3e44272049a2af714861c16d973fa1878c8b242487a8382830b81ae6b08991195edb6165e2745934c8fd98d1035b5b1496df440eb5ed085ebc56a77f3fa399df97972680d638b808a5b1fa8dc370a367bdbcf43ea44d16b9a48db34107bf9828719f1614988c83df319997704dab3facfdb4aa7c0c3b3970aa373a86487c01bc1a67872cdb6e9637aeb6e6ebb7d142958fb48b174471037303b6c904a9d0d0c8440bb5146f4c3f20f3529a1b6f4f5e62a9379ac8d29fb02ed80270214e2bce09819f172aed3b3906f673957cbce1621b53ac18ff3101bab11004a1feb57c6abba1e2d7284238d5ee3a8bd00806a76e326673b6146d5537f0a3b48106c7c5281c90e56723d6e0e521a7eaa2a2dbe4a00ab5376ca5263379ab94b30d35433d6812c1cc5724517a6bba4d5f4560a4c0972890f0f5916aefbf4b55e2c2d9fd6d5cbf3a3383a1cc191b4d9d321d4623bc0f9dba69f144a4feb91bc822dc02690405c9acb6c085bd274d2df764ac8b3386f6634cd04c52a323c8837ec4095927ff29d1c126e72a04482cc97060c969bf760b27a39cc9942758aa478137745392e124c40223e003bf03a74c1a1bc96b6db3bd6a59ccb3296daec3346e88acb452d1f8bbb80c472e7b48700292dfa3359b8d76ece7ffe5818491d3897b2ab7572aa4259e39a68140031095c5517ad638132f709cbc022362ad6bf23320d438653f17852707e8b728b6ab59d10c30184b4657cc7a490da1987fbd3fbf8a219d0415f4a6e8d05a172734f81ed0ccbc5218c214af3b20d573e32892904ad69d1cd5fabdc9b4f15e6729ffea5f3ccc300700baa9f13155798dadee3a809495301ea8551ec473ccd5872c5cdbda40675a4130fffcffd127b4f66df7c807444757bcff70ebdf1560ac472abc0b6039e28e557e995d70c40c98d15a6a2932648335e9988c2a983df944b72be3d451385ad60a6116627f56b22edf664db03636b5c4e80bc372d920416a844c324cf98f1cfaece4eed45f4dc4fcef9c67fc96f902da26f1874391faad0457280932d4e799b215d8b98b68da001bf58ec161b2da77472294e34450e28a57a1b1ef2095f90cb891082549a019d458e312d0468835a87827240019ab21ff5a36f4c03b2cd0354e3c92a58faabd452f97ff6e1561fd7e646fbd54003e3e230d97274caa7cfb5c5bcf9dcd0a6072fa6203c5a46ef2f4d3086ffc9547bc869f4eb72d860f6eea22cc90a50d3b0911c7ec25921e21201e17dac5efaa93d321b2ce8183a03dcb84caaf30658c2b1f1a74cd5371fddb40d5f4d052b3fd19fa1ec8a120fa536eed0ad25face6247cc71acd2b8d44dd1b06ba1c7dbd8cd7221eb4db96a72d8ac2db85b80e2dde2eff45d6badbeb34b70dbcd296b5cbbaf8321c0bd1e1372189b0b61e4176b1141bd958cad6c88dc4272691bcf945098e81119d989cd305458e31332be2ce64b7cfc89e315c56b931fe1383d63b32b171899e11348c51d4957e16c09467cb835f1d838cadd903d30d4e56e48544054f20017185d131d76722a58dd438af6a91a6090a559b3da18724da9a1e151aee054d0a0fd43685fa5540a5af1fa1ad41ec30a4ef71fa9a0422bf3838053a52151c4fbb8a9d71ac49b728e465cdf643ba0ff7ed3c3c3e3a111a0a48a5639fc178695293648bcfe15962701e3ea9f7966901703ecdebfedc71244f1bcb2afe692a83be4ea2868f4ad215ec804565931d967643af9502c95dcb79b41a5a8d45fb5be4e0c58150a74ef281fd3bb6a7d515c61c0334b40c42af46301519fc5e0ba6689fb46ce44777fc98e7247effe79f969d6180e47169d245e8cb03daf7701c56c46c9f3a5071a01633272a4e8c7f6745083994b7190190220f9696c417d55671c97e2e2632677aeb02072d97a12e1b954c0eea8575e3084a7eef7848a5266b52d7dff4689b67f0d8b3130745d1650153787c306ab62f6c2120ee6f78a5c05d1534c554315fe355a473331210af02e60e3f3c1b221b60773a3b97a1cccfcf7cf18831d8b1a3eeccb691940d4ad1fa9d6767d4a0afc235a9cf0686083d4e73a64508b107c02c7314ed0a272dcbcd9616f6a22b246514eb9af1d68587208bd0cd530955cb23c0107ca3f1645d9de05d12f832108e6c10f5358bffd8aa89ed61ff259f83e2cc5f073437ed04aa75ed7ffbd84853a777b18c0d1bd6bd4dd99387712ad9ce9bc9d73371f8788726f657b0d9cc0e4dea367c529bb8d08114e14b97c5aae0bcdf27a0a0c3ae5ce42ee734866760832d5ddfb85e0f80e935796bb6d5a4bd26630d36bbfcee2eb2072c818d1881fcea24476b3d7ad1966bd2446f9acd827c5b603557c4ae9e3c26829e896a4551168547e4f147c0adb8f0a40e33eba185b03f7087520563ee516ba3aea9b24a3c9de109a42f28c73e61f931d731048c10a990a0c1c18aefed581f672c2934df5a2ba7cf2944ede9b0ecfc9a667ca0d525b752f7229ab9ee7b3ac6572f33b2bc4f940942e63666cc6fdd87aeedc04013f4914ea1570abcdd782c01d08da0a2bdc98f45c69956834cf07941039c0a0a6ca9ad12d4ff56729a7c6ffd8721fd1e9c8dc12309a6ef0bb0b93585e4bce2c696995f2a0bbf22b149bf6cc11722801e2b82499602240e297ee8eb52e3320002df1a7a28ec0f2f6a1d12db53442cb28a48e178773accbc054ce188a502bbb0199c0f9f29b2e5fd8c33fafd0e3723a8be3d29e0446903adf70a8cb19c970c62e09dca36efe6fa6a272def36b46728783437df8a6b7ee107be928eb8b3d5065348ace635e8480718b244b30eabb72a0a585ac62716b860fe246c25062f9b53c3acdb5c1b5a554ccb84544c95365131178eea2b406b96c32abdbee7bec3ec076cd148dbe808a633fd00233b5162972775ef86c14c530c94309b7b13ed14e937e52cf5d6a62058741edae51c4c1320a3b67834956bbbf56212f50239d477a41e651dd8c41fa9bf5874c3d3352d961729736bac7ca638646ecec937341cf75ab91a70e5db0385e0b48cf8ea34e0d6d7259281bd2cfbb1e6aa96649b26ec7e63ff7456b14064a826f5a5e73ad0fbf9a305b440eb4612fd5cfa24e906f76154cf70bcb150b954850674496fc76e41eae721988e0b70bf9aee900b4bde79e189df4b7e9bbaa16aaed8fc714b20515793143c40220d659928a5baa0388542e7368eed7313a70f682973bf6b06b0b25b5fb01ba7ef3166bade63c9943b77f480a461c8a491e5351395a1f8f40b985e1feee7276c0718cf0cfddf049202d33cf534e9d1a5236c5655f1516829ca45fd9664c0dfc60bf1a8765073b353dabfccfdc29cb50a337cbe6839ddadc4a15bb03e9d02f416dce908c83fa811f95d3f947a3ca801694f374e18e8e44655334d5b3131572a98cc51b4178c83fa1b07ed5b1cf2a5f68342653644c6ccb5983a1def9f3210562addd8621c86fc55119c9c0a7f2641eec2e8de7af24377d95bf00a7d9c6d37267d837e289e39c40d075b80c281576374b47ad6793d3a58215adddc0b96daf72b5544de9734e8d24ffdbce124d4cc70c30b87a56347235ff41dab3015b228a72cb15e1adf4ebdc136ee7e260692e5b24fc08569108eb128e69ad5e27202d7f71c0689e28475334ef73e8b3ae7a611339d9a411493ff482951a8fecd6e7f9ec59fc5b3e18d7802bfa08780e4e070941cf669c8834afb9a2f81bc7772539d64b72a691d30e56b050f239d56f37d14045fc4fa86bca1b907a7ad1ad14dc89d855415740bebd86963d2b4fdbf658c950fa2bcefcf976d58f4b31998425a60b525e0800182d39cfb9b8a526af2bebda254789a81caddc77a00f419309215cb051e20f6a84a517634f688c3ebcc1315da578abea5b274579ca3a9ebc9bacaef7cddd726761c7ce456fdc5294e83809895cf43816a7c248b5faf12d0527f6cfacdfa07260f94c683c8a88c0b7b2b00f8facd34a578eaeab557269ab195b0ecc83e505658c3ce2118578fc6e1cb5283321376b5560fc50b98f93ed9bbfc64dfd6593af72cbab501d210ac9f8dc694167575a0c814fa5a879511b5f8c30f5fa5f071a0e3c057ac301831a04cafb613446099be62306153fe43dd5325dd2fdc63eac56fb0d4d3ca0f7a42bc1d6dfbea636aa53619685bde14f9787bb79df27f7457d44957204603b9c39b11ffed56540afc21b997c8a12958e6213a98e424dfb3b90d423725707f5d00069d283bf65c86dff5398634a68b992c472a390f82304ed150a7372917be8ced8c7775234e1b47822d494810b1b521bf39e321bc9c91145a4f44e706283a4548f2dc1a96de41cf22a741debd2a193ec575d580806e9ca133d02ad10f314ec7a3618a48432cac2b4d0fc173a771716365159a069bd5e282c1779f94e1523c1648e61930a4c11f0c411c45d70b0d65d74b4404119c2a1390b071cb57252d385975d011a4e9edb6d7bced0f02fcf759c8baaaa5004af00e34a1ff03404e21e26023414d0f3be0d6d35e7a10a995e4f5676199733b9a0237cdcc082ac72770cc89100780d05bfeff58c02a1c0e753723f57339458c5cf4c755c13369239102f30552402c4290f4a60afa32ecce47f2ba9597e55f11afcb30f9b99eabb60b7496345857f454b76c36fafa08c4ff4f2a26cdf64341f7139b851dd05ba0c4d1d0bc1ae8c7a0c91f15efeb88a539c98bbced194fb2ae55103a4cc3ac7eb3e72c5d80f7338182886735dc81e082bf0af1482b3b3500f7fce4c52e8062c176972249b56232b0e7a96635b10b52d3076fb6fd9279dc0c6eac753e593669d975e309d5c26112d0e3ebe6f0ed946d2766eaa0e304382c484fce3cb2550f53b8c547295896fb62bf96151cd5a05e00dc49813084290616815ca6dd75517199946c51e80c5817cd0427ef868abe5022f4c3edb111645a3de25f40a0bcb2aed660e0772284bedcd607cef04a6eba7c521ad79617532deb5630a38b6296de1799d62bf721a1b1b5183e2638160004b6cc8c14a3d217d343aea501a725264e58240323372e895f76a085a12da33ebc53b6bc27e629cba9f01305c9ded691972e615475600f6888d6acdf89a6e5509ce1dcc6de2f71b15c040a4b2bb8506e8669e0f1fc272d21a53504adf6a31fddc58f6d51ad4b31a9dca8f997ece38af94452d2ef1bd72bf88da4dd02b057d4395458cb8379febb36c190e02fc8b0f5454f326247c3e72d3e55ca6b624b14a95106fc3f33428fbc4c62ba7bd619e9ac7d8415d30976872d72037ee3f5515060bcaf3d6dec5828ffacbc69c3fd2be88f216e055794d903fe91804fa0c68c6a9028fb11142526110480381bf59f6e3030ff6608c2fd5dc72d8ddf41cfb87cb63616bf63d2cdeb4052c1ed9cf4f8e8363582c1b5af68ed072db56a582a6bc535a09bdfc3645a89a743286f35118f3164d0b43fcfe8ed1e53ecfe895b5b7079cc401015e902efd403264c0b8b907b3b2bc3d5522cc3cbb2c728129e16051fe474dbff1c22820fba6d14a67a60912700489623060b86752b8721567f5716ed7afb2d85639d604c94358c1d336a5f9cd32cca20fe0c45d466a72252d7edc9ff49756101f4b9a02b740e3747b4db4e3a781d1ab1ba410c1acbb7213128aa8f2fc44d28bbbd29d67f2c1a16dbb42b09bbfdb870f50b5a735b4887247fe5bb089990e301ec64c370558fc02bff86a87995d15f0fd1cbc0fc48f0b29f64735df6049dea0ed36cfd755f1eccaebf3ee01235ff69c9f77b74aa0e81b2ace10d8eddb852b3d362054ca5a5472724954ab7db113f7bc3aa53cbed2434b4596c77bf4d4007dd9cedc5c07da58288126383fb8ed08330d1207b56dbf3462721cb94aebb169b8ef50e2853c03bbf6af88406c11df04c7053a6f909a0fd6772c29ff54ed202fa323bc5014de4d51a14948634f8d1fb1306170589e2031c795720e8c83e27be231efe5788e1c0c9d95d29dae089d7e064a274990a37d0853c20a98fb79de419a1c624730014574bff3cbb253e7fb7c0667220eb2001d17d4bf722e5212e77e3c04603003b1d37563492f93a24579e4dcdb2a62ad98192a84f272a89b02ec76e66fc897ff53c50dfa297e158b59cc4473edf16c29ea842c1fb072bfae15c258a090eed314d6f029233b23e7f77168255c15d0571c038df1974a55bdcc5bb181ac8c5ad7b1e71fba8b02fc549f442937e13e8d6c6e4a8f99a5236362b709f3d028c805c679a20ee9f9593a0238f4f43fe75d602744c1bc306be77249c3e9d7ace1e129e9b0ce0a9ebb1926257ca3fd3fdd2d9f61013286eb3d2d728ed1dabbaf7cfc6be8515a380c5f890e72a352766d34d35907f193b1ed7de700317e1d2270d634ce07184d2bc0fdcf632bb6f19d4d99123657d84b19f8aa86721bc7f95085e4ee8dffcb8981533bf3e8bbf1f67d7a013b88094a0f1c7473697285a1b7bc212d62b66c289c30bc9727ed873dde1ab0d45ff981a1e0ab034dc772a9ba26d71798aa9521fa69acfac79801c4dee0ed655899d9d98170c01db4f572a2e32595ae761d247371ac9e564a325382a22962928b4868a0a9e67ea094172ad37b6903e469ad965a033840095b0333ffcba7ac66fe58ece4cf51aef0875b698bca381c73bfadd13d46216bb1a1b53abebcd0f22365a42b588785e345925b729bd39fe2f0bd0328106e93fa4d40cbd3e49c85867df18ce5780f8deb731eba72d4606eb400a7f8ce9459c20930f137b629d082a1f11cc1ccba7984836b009b7291d6d2cb77f047a0e9160df15a5bf346d6ee68efab2b1c120cbf5fe5392ff8722e7cc604bfff6835d345916c422c3f6fd9f51b7c3cb6b51894e289cfd5dfb6726bd96d543f50fd04eac1cc3f4ddc486ffbbce06c661ab1e1308648c857cf707290f201a232485bf4e7b8bc1c795c857a523010597ea44304758aa99cb06c0421a84a92db8480dfbf8b1f49c7a4120ec816b241a9d6b5238dec645e492422822a7c94033c8846c3b92315074cc0751482aad5c1280a2e426ece4a841560d8f07259fbeb8b28ac744f44921dbe155015e64f38027b8a71e4fb6a1421d941fc317268a75b572bb16161b361b0bc5129115cb6e914b3ff73f52cc59b445478859b728b9195159bb1df38cb0dff59e4521893b2c2373aaef1f240bea36393f2a31872321dd551b0c4619164e0faea9f8e680a44ec6b31ffbfd686b691bbee33f3397200f6fd3a8081bcb31af30029a50d5959aac83b376f52046de3563ba375729772de55461fe916738f18960e0c19a5927920de6ab7db48089326e9fe989787827221a01c8d4e9a1c75a2b02c244d828387eaa9f768c6682d37fb9e2da57cf04f1c7b3bf8089ecd4e21a555b983ebdfb3a5c9c47602d88772073c9f9ea38c7bd6727a3880acd908ae5f4327a7daee7f193a9972482bffad17157042d264d4ded772f6e06b8ac521209d5f05edd7fee84638b7a94a01718878c3123dd8e97a374954a43f1f1746b350e7ae9a6203fceb0934a360884c85654587c262939711718f723cd1497cfbdd9992fc6111eb9d1f0a8a2c66456a38d00b0b4de7e6d934e6944100e7c2483eeb9de9616ae7691803ee99bc2e2a3779667e0dc63a6b47529ca472e2cd267555eb14f2ab64766ca401661cf489fff7f73f9a88c77b2b3b78aca472a5f84e69adc18da75793f48cdc9b24796849b115dc41bd70b9bc76b2697ac27277a27dfb3ce425f0ea48d50b437359abf279af7457364547c92f8ac4004d0d2f98b9289eea2ccb0ca432049404c9427bc037ffad01820d15bc9d0d5d2170457249d180887d356309201734ded7d20acbdf31cbea1367b3bd156ceb2c8c2a974aaeb0a82e9b991686818922123dfec8bb8eef53f62d93888240067d22c0eb514a11f06b19dd7ce3c68f34e4333d194a6053ad1dfac005608457f73b6be5359c38a67b12bfbf3443acb7094b5d7ec857e53329a6349764bdc88a6950526eb9ac1543c3d2e861b1d5481cb0b0d2aa5e7e2268dfe8e197fc83e81d7a36659a664c7276758c56414729d2ee951046c41b136bb3fa645b3b71b8a6d092ba47115ae36608c288ae1cc4ac0d05f6f0ef0c8a39d26d6667750efda29a8dd4758134ede572402cf4c7f6431c1de3d1bd5da0f83dd7a005040ef62046a4971bc0bed7fdd865437f207884b446e055b92f136b51469fe57b4ba9a701a692b73067465d6aec728ff000dedd281cbe2dae323c8e9c23cfd8c94cda1afb85afbf4e9488db6ab0724e72448c0d54066f4710ad3a443a377c5bc7c311a81ba43e81499c86b50f6d4675be534560a185d12878ac95655894652404505c5188a5b9913e33775a763f72c3742929aa31135933d88aadac2873ccb7538e4b3cea7842d8a704684193c77286ace6fa8d612bc33791850700ed084f39dd23c3ef86cea42d85936cbb6f992f135ca51ce3958ede525f0b94546ffd1e835a940a2662af98dd43d789f47cf172d12416d885ad46ae632a3bfdf843ce710d62f5333f0145a9a34e6110f24b7b726d2eecea431be8c76d470db79d78f91a6ca598afbc60804d9f67eabb6626602c484b60a8c7009c4cafc7a834011195563831f7c53bb597e6a2b92cca9a203d3ed688bb45fd43db91a145f87772a07064fa399ee751ae0e9c76d4ce2fed0fcb729fff06fd52c39f49c66fbdf5b02c4552174715976ff7f6222f47298d75029d724e692411153809535b7ef67a46f29f91ba227e7b20024ebadc1456778fe466727e82e1a51ac9e98575f91f2ab2291dcb832452deabe691e7294bddb5bcc03172880f912a87b75e76df35a57866c5ad94602f3baffc71609a79d680744a02232eb5ed2baaa7577dadf0c60b26bd70683d52595674166a9c3f2ebf0276c8b29872823b9c7d1a57467a9bb4acd4d8203120670db5678c6605d33ac7926fe5aeaa6ca44095afd4d02e7ff28f3bf86c6695381865d21336577702d66843032b130472e4bf3b3bbd393a8f22a0dc10c9ec50a701dda695b24d668bb5c4355a1d163202e6c3157f138232ddb1b52888adeb77fba8754c1d5f76b99f175ab634b55ac812a9f7917fe4b3de99e7e3c249272fb3f2c6adda8c007857cf515d9edf8b80ae7274510dd84839b384ad50c5175fc401a7b41b37fb2bc388447d2529da233e67156e4ce3c8a107448204a38344bf475861fe6a15be46bec808931eb361ce513059d30d299590b7991c1a9aa43d42047f1260ac206320f6f6c447b16dacde8c2872052cc18642511885e50033a040514be2e56dd7721952a5b776ee76a24094e01a3e0927df37cb0a628111fa635b079be8fc374a66b05b4d114f33280c3e10337240ca16eae368dd1f20f72856cc30e570c99f793c905ca19ce8770b1026c0e3725d36620bf71409e2b7d65dd9b5a38e1880757067e4519a7db0793cd46ac953098361a480aff62ad6852a87d1f4ec879e9c7ce41d9f79306224ccc255108f6214cf6305875bb727f4b388021a02a145ab4dfd8239185149cdf8e1d2c8fefc9e72537f13d55f2a58d6c70ad5d3de7f506b82bf0950103a4fe0b537900c1c71ff72949e11071cae6c9aac10c99f93f7e702d1348838a30029090a94e79a579b7550109816fb036d3586cee4dbbe44a0150837ce188a2ab50b35781d80d3b2f4fe72570fae9a8148819d3efc4fedde709a3d341d1fe6a450fc6fab6f71956ac8df56eecde22d73c64bd8465fd2dadbf54c740aa20ff425eaf078232e2539530bd52cfac624dd34235156f63526b7f3a30ada94cdc0975d3f5cae7ec30b583531281a73598022b754c0b51ac22edc7906a0bcb05e1c1d707721d10176843c439d0a72b416e29da5434259beee8935cd300574693f254b0179aa30d463295a51452f1fb07e819cb20fa574c5ea8fa7e0ea1679cf5fffe5ee17f79c5ce47681f963103ffc59477ed6b2be16b7d2cb67c6f6f25016871a0e6a40aa86d31f571634616a723df22f03141c3f91d15bf831b084bc0de2b4b2ce543b7b51d33cd8bbc1f36772c5373a30c8525fa234ef4d276fba80cfda5ae57f32001c47fee9fda238075372aa7c4f62a8ec464b926731821b5fad8fbd17700d38ef194ff93527b7623d85480c02ae124d05d7290c8a58201d27a26481b3adc1a6ed7d4319be214a46239772cda4fcf6e6e43528791645b8ff789b5d7f26c949ddafa568f2d1c04be89753430350fe2187be76b6fae002ce0f8674a261587b91afb49dc6bd3da55bed3d7172248fffd11cea05b0d7ed750645927cbc6fc247a3435aa2c6fecb875aa0153f0529a20d45c9e6a62b77d82021792471fd5b2753d0ac80b3dd826a203426923472a24447362a54d81edfb4dc399a30e3b7cd084bb4f3788398fa49ebaa59ecf5236ca06a0b877c61561452b7240d1a2d8be1fb3daaa4df49894fb27e630cc8730e908c633836d322ae4d75649a7d9224ca1e0003ab2a0e919b95fe599eb49b8b72d98e4307ec4a9a7854be20b52ec56908ee5ae71b5a62cd85fb97b3771367063397df8b52b9bc2409b5a7afb11d88f98f91a6a946afa88161a86c0b7cabd6707245626cbd3e5358075637037ab13cadb9ded29b444d869ee77c9b7be80f30de723e604abf4acdfc825b242cfd561d5d37c878786957a178272e241ed79988497230b4be5603de28fbc4c6e89346a44f683386d672364eb60dc028caf4a995b472e8125733b58f3334b7d6b4d365c627d9bb8690db448a27ff329963f2bd6f146eed3bbf2b7fd81e6e2525d658a19d29755d90ee7d7624a407eee7391facdf0072fc71db7c10a8e4fbcb4ae0d8052550cd6a2683841be49d8312ebfb897c8a1472bf38048f4a772ba6135a1add78b9e78fd4ab8f44039162147c81b6c21c20936415608f2dc0a77689f268f3a280258a940e6de04b055edbcf8834e6195c653051c03f62c524c9bf24e12af746836ca55aeca85a19b20f6fac13a06b04b6ffcb2b28df8025e92009947508748ebe60d0c7ecf5e522f7ae63044f4eeb7f585d2170bc8fa2eaa5a841618dce27909b698693e3387807c6cd15fee73aead0f06596726ae3d6695cdcd093f4b8147cc0391ace90ab5fce9995cb5db3186e895416e17255acca0b08ceb5034cf8c107e221912132dd1c27ec7971c3bd58eb5bf357530248664790cffc793281d28c1c4fac4151d8edda1621de0fbb96eb9c7872713072a6d1759caede74cd8d15cac767f19ec4558891a3326ca738ea687a0db60a6372770950c8db19740f129a411407501ad47578ab24e347b4aa1e94040860f6687251a0a6dde85ba7478bbfde5d4b0341445a1d0fd04d370e49560a709efe68ed722e2eb804c98d7e0636db712af885d0d4a06ee4e9d4fe658c7b6b01f78bc8ae72b7b4d2fb2fcae5e264bff056eb0f41bcb729442de6aae223e56754218d36cb72125247b4952efba47e946718d40a911fef7dde2341ca81c2d8a85b4b4ec71b720b5b2c91d1f0fbccbd13edd6ef85cff095e996499e81e76b6251b500e522db6c8f04a8243c2fd472a3f2b8f18510556ee38673a67978a51605dc54e6c813bf21de6a0813f738688583c3a1aab24bdda52d8ff0cfbef1762193a6a93ce1dfb47256c434116460670328f271d5e40e38b73e91e767b5baebc08e1deba44bfafa47604d585dd8cad0a2cfbd4448b36e7c4168f07eabdf63efe2502ad99f57b0e572119190b681ddfb19f3226a3ad0f4071b425c3e9e289404138b39ecd2425842263d70a255adeba1e802d571a748dd3f1957097444cb0b7b81b45631fbbdde220a0551c952901846c91d51deb02c567a61a9fa1060e1e4cb03bd95b311cc4cb831a928fef0baa56c1312a3b3e3e7a99971838450b55fd438935716632bfc594372163b5888827a85d18f2415d52c907e0630e75a6a32287b5363dbd4155fe69472e05018ead985bb0e1c1c5dd2dbea4060b4696dc65120da72a37dbe60c0de72639d6b8c6c7e8c910d3018cb3ebf2ab4e1d7693c842763ea4fc641e974061bcf4bf0fba3e48c7d33d701678f6f6b8a53173675335cc157b169bbf507f7f0fa66561d1180be5c7f01deee339f4db68dab4bd7f69c7c52b31a7d77cdb05fde4d4572dcac883d4910f4cf45be2dd5a0b0860915a9ae0b32ed4ebba38899cbc307b97249aaf9adfffec16650598649f261460738cc2bba1268d1c6310554e4b07e67726cff0c7cf9ff7428807219e894e66fba189caf7720bc622d17cbb285b9f8c66cff7a180c3389746b27bbb062c7f26ce7e4a3b87c41e5692bdd3a429d5fd0977289e6669c7c1540b37ccf980d5b8baec49ba856db268f4670d5382bfbb0723672d59e3a2c45749439fce626c439948020bcef079ba6321c006208ec5b10cc4c7218d4a83d3604094dd5a26d3f07caeda23a5b2c7165dda2a15bda02d2e9f35272c54218150fd73e413f73b5325bb0f374997e76265371aabfef97263fcf25277225eb95fdfce07abc9bbdc11e0b55a00cb35a36e7a59fe2c2c3d5429702594e220d53dcf78ac31395c7fef6a5291ad6121faceaa842577ff41d13a27111c35627b28bd15cb158e37581bd8aa2eaa5f7225f118a9a50d6a0b6ce77dfddde3e116674ed65081eb2422864b0ec5f7ae5effa810b2cd4723e64f34fabcecdaf14e94c653de189718a0a6574ffc9b0b1893d577b27bb6e117a246b927b345554a3a7444202767d339593119292cea8af06eecc284c6275d652311ea87c0b69c7728e2aff96da269fb15acacaf8ff4012fb954d9ca37aade1db1f52f814b3415e0f5d4730f103ecc1711c2dc7b4cdc8a2f4c814e13a8f398de55733fa8ee602ab48171ff2a930a0b4b0713e7105b2449bebce67f657cbb34bed5ed78f485e64ab9a60721bda6f0bbd8697b59312cb26a087544ed1c4dbb3013cb4bfcb6048b5bc36c65f2f52a5550071a9b5fd95d2108de1982e1ae42c87ddf8a81530fdcffed18d862dd4f53f2d575bfb61be67fd2b0b9dc7a8380b68765333aaef188b328a3e96fc721631d3bc90571740ce7f6ae8986f8dc318562896d58f40ae91adfee21186a56d5c43d1d0100a31fb4736584e0bbc8fa3b90d37099a1a87783b5e8baf3ebf9872ffa833049aa1350c01b29a58fee19ded4bc8abdc40530ecc6cd38aaf62316d72d6770c9060d89266743abc7591839ea5f69f39c7e9cb71e0cf27c79541cbd5725a5f94e709a6a8adf62f49fd389fb36a993ae90be365f09864b20e09d6c5b172c2ee7ca6f378f3f95399dd18ce7ee3bd696a6369066f3fab3d58f65438b39f72967e0fd6dfa804d9076b7a688c203cd2a5970f5cada206efdb69b1288f6eef21ba8d951c30709d7745c2db02510959030797fff37c90839be66e371d4955a43d043e3bfb7ea7a88e1274b3a28a5dc65fae54252a488f848cde210584bf4bfd3a1e0b7952985f4a0a9f91753a5c5e181bfddd92f191fa0390b477c5fe20e97372e7dafde1c625b7cf56d3845ff9ac8699ef9c984479b421bbfc21bfbb5b4504463d05958d1e67ebe7543f4c6b3f01275b210e91257344e6f913f942ee6e4b7027cf7e54a6e1fa6d1deab8378f19807533f73b2c97492aa79f6f1a77587a80dc2eb2739a5f36e08d0e494b675b4680c645ad93ebd173b0878284e5cd10ee6a3e727da099648a7fd029275a96157b37b1a061a7dea75139e73ed26e079120f75b1c4cb1128d59b23178ba714280f9c24bb07b895844f8259d020d82366118b3fe7255a39fe375f0ec2de20fdab4ec5efee31f2ef890af22a35f13ba5f3841931a1d8dbd3f5a6a6d63c328de0cde5de0e070527fba6f9ee6dba5b27d09db206e62720745db671a4248a34bc350473a955134cd748b6704926433afab635176bda17228d27e6f7c8fe15d4a85e2d677968abb810fa99c712c34cacd6df54e482cc7726de798067d3249eae70a55028ac4816541341cddbc5a6c707c8edc0e4257ce4d0be2b500458ec3a289a1801ada4f55d8793834fb67b6837e08729c45056cc51641991d4c0b51a5eaf06e1a4aad806fc1b6a25d38f072b0fcf79a2013a5d0c829f29f315247aba23a853f50855103fa51e4c32e72702a2dba300a387ef14bad7298d5c7302471ac12723649d3a02c5a6eec4599eca4197f7ce6d3a63741e2830edd57c817f2a49a3f29f98a94ed1e1d67f993a8d03c7338a9d4c35b088d93c97243f689fa752b16ddf6a8b4d1f91b9e5b548ab61f7edc94a5ffaddab4a5d4a13883dcfe81f0a940b0433671c6e4de159004f5b57cc77322df0c66103ddd7aa72967034d05060f5dff43680fc51909bee9b7ba3238d72390db060f35e52a97e06b2ca35a0f83ec4784559ce06b544a40cece3b775030842ed55676cad7196fbd3c31f16b5f18f770290e2f6e6b6323f6fd5394f20d2d6b09288d67b6bb7314e00acfe21cfac2fa79cf3956eb971438d3b466145a41258baaecba1d28bb49957d26270b9c7b1810df4ddef8abc5cfcd759206b5f8698b9b2d5a8dbcaa2bcd6ee672eb086632329579d4a5fbb6186b6830e0bb6a938514433ec08dee9c48af5c93729cccdc371cf25addaf7b6c21dac39997691f04b3e59150ffc83666109b1f78727b11f7b37e4dcf14833b6c79f813b2e67331a3283ec6d3bccdf6034d45c62f720c0b752468b2615d15a97217f9356e0d1aa0efdd9a05f87182338eb20980074725a5cd3f1a702f1a78485399240a15e249bff6246f9dbfb73cc2b8df3711cb579ff37042fe3629d31ffb63e7da4febef653470c3c8ee5a0c236225a58bd4cc72e8d508ca1203a83eafc5596e93f452c67effd21a457eb9b99c4ad62a2090443960022af1459b12e270aa8d22f7eccf163694ca6c68a437eed312c7cc46ce83496f34023806cd595344bf9c4a3bdb3d2a481d922f6f8657b1500aa872db3aa20fb08a8d3d162f38e3b8bbd6a6b1031b1580a31c3cab48b3b8d78cb395b29079729d4f5c057ba24e6485767779e0109a16fca9354e18cd43b5845f54621d486a25b827f1c8314be1b91b56dad251679ebd60e981b9e4abbb928a7b210f9aac1c4889d63760d883baea74250cbd4ad5142d65812e729e0b816565a601f55481d77247fff5230cefded3d2f52302730c4d62b0d75e8542e86d42e561b5e18b05c572e6e4ff9ffb1546b78883cb680e5deda3e0e6c8c700a092c22f9f67f4db458a4025e09be488d01969ac3dc0536a7b1c18c1214a40cf8998ef018c7ac53bc0027241f4fdb61b6bc260969064ac2734114f787e7d16fa2b811f7abfe29ef19fc8729948bf13f31d48e8e8fdcecee9512a5ab377982f1a25f81fe84f7d92cb5fb672d2962b6be43b5a6160bff39db53b7878c4687b4923174fabf350cc4750e60572b2b73c15a5352d872f349d6b26dfba8eb4fd3efe232f58674de6d19e102b365a1eecc35a751bb36c69c47180ba6e6a1c74a664bd23cb1a66cb87bc9e2c148137c7554410127ec938c6ef769c5ff885c4684ab1bf301a29f5349127935af8c57282696757645d48123436ba7d6ec701a7a6bb404a07164df22aa853221683cf72026a311c4b2508f912b37779088717c9fe49bb380bba703fe318180371c2cc0db430b6e2826894e49e3d5c69c5250755f231a7f0d9dd586bd924b431fb62dc6bcb713c2bbd2f7144f4f62cb23b933bf0416f18268f126b9956baa96a6cfbc858a10f8bbec530d38f28b2e378fd592ae56ea6d0ff7fb6b532adb7216dad4da17285f69d5fc88a69281229a9abcc71fb5990459e9099226edc8eaa5d15e0a5404c45abc9f34fd892dfbdfdf55e5ef454d17f9e3210aa6e9b82a577db4189ca4b725b32d90736d6af61e3931f4dbf0c31ba737176fc77fb3a122418900724b96e6a6685b87e7b403dfbfb9818c509102204a1e4f2d217bdae1726b978162d7d911acd20d649442e0bdfab7a12b93b834eba60acb2bbd1f4f4331eb9416f79eb9372fdb407195ed87342dca622a07f08d3d7c415e49bbb9fb6b3dd08417a802a037294f821f31d2c2a7b3c0be7afcd771d5bb93279838b3f392b1b5846fa3b273c72f925d3bd7c5714434a654a0f7f2ca91cd172394854170f9248107c94c9bae3722e4e2a6394fbbfe718ebb7190f1aa699d052a5e1b9c782e831925c6c4782797214b2bdbbe5b9407ac7e9aa0cb4633faf78570199fbc7012a9f322445a28b5e6cc639a5dd15a8a512f3b7791b3d11154c41d94066526c5edcc4d2514a0d45c9724243f59a0fea6e30f28d75a03d94c47b94dc4c06c99b367d099257ccfe6f7a720ad0177eda05048a683874d5b9a75125ae34cd3b6857331504aa310c1d18f172c3232bcdc50e0942beeedfa10ca743025a9e4745ae5fe553456afd6b85deae72b79fed670b16069d8e14df39ebc264659e8b65bc91705fdc487733c1c388f908da5028b0821ba74eb1df763f975c4b2379995849c02ad11e39628e4e24d04872f144552584cfa869385905769287d32934babb9b5ab51542b3a2ef068782ce72eaac36971076898e0f04336c746dcd70ebc539ac03755f853fdfd74e77ddab72dcfa76c54208d5a6b880594438b6890c077a0594d9d22682e203ea0c7d82aa722dae973a03eea38eabef7164d4c0300318c145778390ec908dcb3e92fc975172d23f84dfb188d7b634ec1b76948a33ff98a7e8700567e73bf7fd41a438069672abfb01d1bb3a2b67228a0b03510933ff5b78457be2557c0c0a8e3d42c3cff0476b54e1e1c77ae46b14b375d233a489c19393cfb55a73afbab4b320018a458c72da35a6bb1533380cec6fda7d3d077c33283b0d77e5395baf689b3f06ca017955af96fa81cadaafe3f3c4fe5026485da98de57b0b84944169c242e03b960b0a485d38ec6073f62f7c3f94d8d9aaff10115378cbf08ef5c5d378dbe4e5a1da0372dd0156b1bc45e24173f536bb59addb9388cf41373e21be9c851edcc5b2bef5728faf75fcaaacae79d625c6621ee9bf308b295c4b610fe5bda2701332654bc872eaceb734a642d238162e8dafe39ef507210bfaaf186ac05477c06a655ce9b03400ef985358a82495bdb601d2496e59f45d42c7ae7b23052c7385566376499e720f46029e4bc2a43085389b6fd1a9463b5be21ffa57bbf4f1aa7c6666e702fc72ba8d0d3a308b624bbde08fd7035ec3467313ea775f5e73f10713ab57ba7f4772da9eaccc9fe4aba8c4bd5def02614dee5877b715dbdc7d824f63652b13975168ff9bd45fb400bfa2b762624efe1a8be3beebca3019d2d31811f7f5a3542bb501d3dc81850b5753d070dc44335aa0deb63b1c813b544e67e69483b7ea3b17c3724f6d50aef6be0874885bad23b43caba647fc6e478734c06a44d9a84220d1063c486958b46f19276bc33d09c5cf210f33b6a7c6645bbd0167fef0b22ec78d6172538c8ce32918aef343303f20e41c4feb4e0bfca80470ee0ddb7d7ce1a0aff708060796871dc096904a771ac41341ebb16b58e7600c176dbb870464f8463ffe5b8c395b94704ba23440a1a639b07f17d15b22ba8695a6e3fe2a7dac1c52204f7254219ec5240e73abcb398c05b09a521308d44ab8070b4f624a5d0ac40b67a0728f8b7058a36a2142fbdda92a292e3a1804226c64a611ce4c192a3adea9a15f3a27d80de0e931d4a172b445c6f6871736b7966fbffe50b288e313ed56f05ea53d2858cdf56735b4db0a1f3f8e3acb2a8dce0298cb26f813c989140b8aba6657721c358896d45cecbbb9773dcc990835c8910daf2d4d746be5810301f70cd2cc7271c89eb07a54ab2df0796d5d04e8454f4bc7014e86f6e7c879ff0327c0bdc272f52bff57ba65204ab11f6c858e9dd14ea9cf4143862164902e3c89fe3da1105ee4c60216b2f4f61e8836cbc7e04e68dc558e0881c791984b2d0972358126fa724dc1e694cbe3e8946d706fe97359aed717b08a0b6a224e77f7b1fc43870c8c722b32fd73bbef51bddb719e44fdd668fb260297ca62ccf1a5ea04e7083e6730724301a44d74c6ac0ee7369a233ee2d8c9afd551412fdc55e7b20d98ae1d81185cb8f0387770730e988036415540cd5a7e87d2d2f3fb1651a8085ba9c12f3b8b4c168ae11ce2c956392a4f190083bb1a1b6a28cd71ee21ce1468111a08fc392e7242ff1b7dd30e686b3b27d48a81e75eeea70572426a8c1b6b6e6c0930c4e7e9729eec8d78631610dcd419e687b063f847dfc0f7567e483654c914814ef717f246f4760f463ec307b37761631ab462c6943259b69d038c9455b517e1338c9e99720ca3d9d33501a34722a5b13e15d485fa44e4d36f88764ec2bc493679721bdb2ee397702668b315a665edf53bcb58a9e1f745d74179e5686ab333807315c075723e696a02b31405a9f94f66f0371971492fc281645770fb096127b40dfd14fd725c74ab4ab6f3b9a405a4d3af706bb666e97fc63e4876f9df4f8e695083c18d72daec3eb88a57787627a36a5cb953c26d82d056b74624c4731e2cfe085efe49491883724ff3b7e0d8d3bc0a6fba7e8c73f6074f9e431bac1e034eefde1ccc20723fd4b10541b04fb6bc827dc37ef1b9e1690110273d9a4d8befc4e41e168db072d77030a628b1ae79bb6f2d0ab096abcf11328d3f9e4c3398d37d01f2a016f72caad69f0a588d24dae933683e368bf6390700b614fcac429cfda2886889764513482ed4ca572eecf3da82fcdd5e7f957a52e5137066a3b02e843b314cc783ed3e29f8e7b3c594c6223c6ec5e2b7870806a5a21a0d3594b4e7a898c151aaddc427b89badf402e2e5d32b1686c21d0cc60af0c1cc0ef7e1ae88f57d9526591d4c72ddd34fc9276b222fd1b65bf5d6ee4f47a08031611face88711e356ab4ddda70b7b89551bb05debdce8249f89b84abd56fcd8bcab1a3519506847dc5ad1611c72f122e5fd47befdddb37a9739cbabe2147b1c5cad3100d9eb82fca106f8ffcb729bb71834814320cc0b553af18b2741719416aae8fdd593a3d59de1086e2dda018ba87433c30e0987c8e44228ab542e45d956473715483e91efed6a5c68ab6972c8b1335b6ffa99b9825afc9fa7d5e8eec19c6e08f5ca631fbd30ad8e13dfeb72df52c1c255fbcdb671ce3d5599ecf15ca16a85ed8c061763ade990b808528c72f151be26c62d813d97f425d918e7e0c12793a2e3cf419f6f1ab51e934288c272e3f080a9ee2015bb1e8734ceb98dc220a61c3e6725ac1ab1953cb0060876b372bfa4502fdfe53c85a5d09b0d082c46aee18e5f37592c4dbf8f622a6a97c1e97245f994aaf9fbd8af0a006cbe06b2ee96dc773231cac8a22a35cfa7547412f272354a4d81d945059398a0719e5119c65a07ba85cf8419a4ac9511211fa586633b640f9c2cad2f5f3db4cec90e1659ef528521990c5dcfb50e4f56ea23887b5d72cfa7e27b82c7aa9efadbd21877998abfcfc25c61c8131ed51d518f0a23787206b17d7184c8439d58f0360575c7db07819957d2efaad449a2150e9c9370d8b772d956d62553e6e93401d2d3745d0c3a8f7e39e2aaea753eae801c954fd5a53472727754619aeda83b9a3cb4c0a53d9a381fa2272961655d727e3175247f043372c98c7032995804c7e6ee527d46cc116d7ab106edb333a906641ce7ea7aa0f0727d7c56c3a194bd798c4cc983f92b03fd0079b904a57b0b1833897a8d491c157222854eb72482635c59119f363943c8e8da9707edaa632739efb095cbaf671772b166623e35c4937837dc9d68c0f6256b2cf1d3f073fe29a09c43862dc084f905b0b06e69768e595c9fac766fd643d753846243263f5dffbeb412b6c82a4ac94a6b9b070e696ee2f74a1600506ddefdd866d3d6f6910577144ad5b7d717a9451276b57a3b358cc187d874e4d9bd925f314977b98961777ed48e4dc8d3a976e23a42a9776cb1fa4dc8b56977084c207e0cf0ad502932c78775f37474791ffab272f6c336e128dc47760c01475121608aab26bea8c2db411733541b9b23a83fdb3ac25125690c4e84e3265cfd113dbe31c9e85b3d6a42fcdb39e5159ec370cd0a727aa18a6d9fc155fda04ae125aed05d59dc2fb49bc0ac3d556982a5f925a90b72da2eb04ba5a8fbb8465d23f21230e0e33681ea3ecb286e8e61212465035c387226b5e531c8f7b2eb3cfcff0b8aea30f179a1fe5b0c6538a62d24452cee8ac672ce56a861c1c0a4c5981f1c2e673f339f81d431cfd4bb0f37a7d9cb3e0850561fa81dc49b59aa8023e6b166eb3ba17a6eb568f7ceb1b1d51c06b48f2311ed07724711796c8674f5f1a0e6e00eff1bb71b59f1270bae9da1f7b5e811738a018c72ed554465b62cfbda3c308bc25530f79544645dc843419c887a9124505531d772302ca4f98d2d9da24421f6388f26090798fc27071e7bfb10396221ad7fcdee72ba104e49c6ad00da37bc9b9ac6667989473b3453d6e9e9e0cc6191864c190b722948bc9014935f10db9b4adbbd99cbf122e8a8f0b4a87910990453255e629c493bb4a010cef2316e748ff1fc128b11b49a87c39672697365fc2c75ae000d51721736d28ed0da9aebc6a87036bbf50d5430b33b46caad9726a087462e4a764172360c7be2ed6124182bb87e5d5f4cd939e6e4e070e1a75c2c8473ed0e730eb572eb1843b2502080fd855c82b7dc1e748d537f6308cdb4155d6eb39836fec0b172d28b0f22d8f6bde0097fe6c4fa7e658f38c195c22efbcd27be03cc8328113672be1b4352fc30aab84ca6b1c76d07c6508657b01f3488877e64531bf19ae44972c791f7ca332656efa18b3844281ee605b044dd96252f4fb70812a23660489c480640a9ad4922e4d16c8304f0f13e6b2735f79789d6a08c04bc39dead6e3c35036ff3518a7a1ff96353ef492453d9762b59d64140f0c45dde856c84bc3b0c0f72fd2f48b6e5c4a70255a69bc903b6bd1111fbecc6263f08aa0fd31db6b72cfc725f284edf29dd062e0db9f858d4420d5e7b15857d5db2d8df8d7034304cdd917251b4299b0f54d1afe5924ea4474324ab6195df624ec72538aab1f8f7d9f3ac3d294f30ecd1c51258f4491b5ebb3cc8765bb5acec892541401599dca494c0c27255259569048d46901be8a356194fe88e16fba97cba3e276d6594df653594d452ba6b306af1b2fe7ddb490668d2ccc18bde7cead28120fd9b379ada113ecca872ad5d2ed301515c45d54b21733095d1754f85cc947d2c73f8b903b19461deee5896dfa5ad93b2b8171fc347332cba01a3e1d85b08eac4e42542b24c2214b2f272eb78c235e4d30f04fdafd98e6f4e4aec56a2fc47e64f2a4323b32f0b66ddd2266d8a40bdde5509b1234e99917f62e05fb4a2a2d3568da4fd3f94db26d9a49f72beaf4574b9e2e36dc424ea6f67251228c39a775d8c641ccbd006d29f8198457293bed97a0429c6433371b02e2bb1eb5c1d9d18777f4bb78e2f7ed73a34957a4dcf1b5cd6a4c080b24ae2f0ac73d582f230dfda50cdc4f7a5ec497c2307582572036d5b9027cf8c8cf14ca932760c72f0135cf29aba53b5f6d019ab6e4c37687218230b0dba3740af3fd8f1238800a5006934034cc9a214148c12fb3d7bae3d7260eb53cb107c2bdab918d1e000d4b488a1fbc8eb678fb147f491637593c85a72a11d57352106648925b459d5a9f5f1749035c539e18288a3dc0ad9efd440ab3a7c6d90255281bb1b89d856525b527a10354eda979863a7c3cdc0756871649a72debeaa818901881e5b5af9f9c3241739df68fae0b6530b753394e40f80f28a726bc869135384bcb927aea69eb3298b3ef24ed5cd96cf4624e359376fa5717f2b1ab15a3fbffab5c43f5a6c1ae4b02926e28ba82b859b5d6511fb8b8a64f6fe72f00e26dc36e1f52c7cc1f0b979f38a30a41d8175237c3b4dfec9f805f3e1fb72bc9d20983d069827f28bc6d2f6a077a0278317a27618896c3721617e9cd7040989a7cca3a2459dcfe7854a6822e275bb2237c8d8565d20461c4380f01dde3d726dd1765161f2120be3bf35500a8b49bb4f7b64faa2232fc0f360d738e1d2d26f5a89cef02c3667e4e04ff5aa7f51c8b4b4251c578acd27864a8a41746b8c7a27c7135fbbeb55c96dd6d910d04a384676fd6f979488a5e6fec07308883776d972d3f3e8c768c98021428dd4d0fb45f41aebe02da3476255739a7ebffecd6fe172bc39297adfe9a8a3207b76c18ecf122efa07b3badfdb260afd5b206a0fc39072707c0a0c435586575e31850b4e6c89dff57d3fb9e0e467df636e85dfff264c523477e50b218726bc4c73a1e9937ac2200aa4811a261b7b627b9918ac8a923e723159d1d332e4e54a93cff2dededb9e04aaad74fa895dd0bbbd91e0eb3b7b05068d413089c2fda54dcbed2fd819df953d57b21ec0265edcd8ab772d19aa6f7672a4aa94597ed150bca5118bf17b6410ecedfde5125a92af4d2fae7182a6885d2d5acb0552f3bda8c982e2f156753b430d6bbe55f4708e504a1dd0d124d903d6722319230c9bfd5026298af7c172268572d1ef8644be4db4e871920ff84631005f619aaabfbd5dd1768d2a374a5d96a6c1025665f94805efea2fb964aabc27eb3bc19104cefa5191b89883e6f20e59fc983a05214ee367666868b826c4fcbc2f721e69e95d7bdf1e1f3f24ca0b5eb4bdc369f515b0814515d8db56eac2d7429f08ef1402d75e680ff08665250ea223bcbd65c8356648e452ea419cefbce4f0ec722b1bd7aaa80bdf36579db9ea855a1a817f5c272f0cfa9ec59131168aa057b25bd36a344a9cb9d3078738a286acf13b6645e4d33a03ab1c79b75cc7f5b3caa0ba02000045fff65563e17379eda2d9b8669b9a74fa206a5bc89db9464c8552e62ac9059e54135f932c1d292aad938ca914357d01696858daa110eba497a89eb7faf87ae72bb65fe6c4577d8aebb1a8fbb8666281ac606a92681003b42a8b9f255bbe510b2ee2c814b1b2c291b706043674d7206a904c777ddb43b4cefdfb4e5eb58efa8c72d7b8ce2164a10608ad55d39de0c5c5a4487ae9bc277d152030eb987481c828729d4d14a16ca2e7eb6b4978aed9bab873465f37fa8329c85033ec962accd6105725285938d34a5df288b538180fdd7f082ddf5b639414f6642b112f20e2c2b172be4a7e23ba376ece9be93280aadffa28ecb8c8769e5aa10dbb941910f4ad7c72662c442eb0c6de122b1ff20c1ff7ace91f6bf5e500a9826b4ca768df328e1f72363bb857f0b0ad4409f04656c5a6498f167d30d8add3087dde181979304bb97266b87ece3db3a1a58fc0b3dfa0e410fc3ddd1b7c4f07e43aa0b91a78bf02727203b04865e7c2f4e90409a83aefc000c950e426f82eac262034b1f7fe5273b472bd90e6069015a553b469f8ab39581e8292cf44535711c910e0e7999cb7309f1700fed8987aa2c433fb14b073e002288f0d8ad0ca96bc4dccb10dd9eb1a07d072691f57fa7de1ea546bd5cd18a134c04a4dce0809b3ccc268ecd8a451408ca372792b1c3ad59b60df33708219b816c6cd647ce381b20b22e774400000f66baa500caacbd0efbccdcabeb773c81deae7750160cce045709bef700a403ae7ab0a0da5666c8d49cd68f8eb84f45cb1bab546fcac5bc4a78423cfbd4e873c2b8ed66bd6cc6738f2f75636a7bc86041f98047079dcbab56f2164ea04e077e5e2526a6b969ec9a5f2b7beeb8deae1298ffa4a97025822ed26c1b954191373040a9512720c2e01caef16797c15ea19659756e28939b00efea9b02c52a885a8f7fb46e952c5982cd5dcd34041aa49a4d02aaf897e9b254c82390fbfc44f63de4990f2d17295f9c62932f1f4f1f68cfb98fab377504cbd3d37d957b0da87543a8b060c9e6ccc0d2c1c85ef7eead2118e3ea4d49231a7f90ac8ebd9b2161b89ee5865bbb472c2e4966bd3feb62bb889e52ebc7c3ced6543053361171b11a4c00921a1300b221726a26b1a812a63614901b19ea271dd3a764634e0ef22ce03c98a8846f42d71fc02067e46113cdc5eb547ada01e51c6863ba45037d339a920865c628f418a5e2847417aa083f8f65cdaea8e025423142231657b2a7a2b011d4e3e1e2398e872c1320837ad4bb670fa1ee2dfca3cf7cb96c22adbe1df1c52bda5ff65f35cd37275bf5a342a8dd32bc8c5cb10316d4fc3617486a0e2df81c68573d2e73a282772d05ec9001a49f3ee8a05b0beb6846604677ad50dfe7389ed93050360633d5d729ad2f4084a068bcd875318d902bb464c175c84b542af3951b14e84939d630846b39208174ddb61277931971b206fb80f45216b7bf57eb62fdc6c5073e857570bc07170b14d511de0244b10f5dd24e3558445ba36279c7e01a1fec66856f54b649f8088655b9a00781b1c365a024d9634040e075bc7940e78396f8fd90a28c312a9bf0096a9aeae8f037d05d1646e818745c4d503ede2ac8e84055e6339fca74d0e01cc7471b3255cb6330cb38f4673a6dddc28d4cd4775ecbac27b6a283c34726a0300a4553221d68684cae821974e29edfe975968947cc10e0d218fe4817172b2e00f1eb1506aaaabf9b60a8a64c613cdc160474decc5cafcf82c664a06497259e364a0683851dc5e70384449291a8f97d483c22f05bc09fca15feb99866d267c5e0957e1749dc455903703c77f8773b4dcb1c647e31e6281743f9fe6b2af7289efa6730e58bf1252b26307e68d335dd655a14ef9663c142266bea6987cf6728e42e726e7d957440ffd4ee11756fe9d629c918d266b35a534e76b714c4d7d72d608390271bf80827c67356a682b3db5bbb43e1c0d1e1981689f6c854da764729176db6b160fee3a2f8ef7fffb65259fa403b80c6d7ca37c5c3006e93e8b735e8d1f77ccabe55f4892532b27cc46cb08168152b94d079905c62e1a2e753b2e72911d61bb4cb7b4e8b6b8b7a33a7c3cf76da211396f5d83f4fc7fdf1e0b98356d4da709628b547be7897bd58f451db0e1e1db5abc63b02297b5378c07dac4b1726bf8121082eef213689d21d5dd85b621f9318ab7fbcef4c2f7b21e10f71a9d323d0a2e0c2cf79e0e55b86d98d00eedfd75f86e5742ff32ef61fd9d8979944b72c3000dae6f683d6b56481f9b99acf0e6fb8be35362bcf0996ec89498d414d26eace93fb8bd09c9ba20edf49620ec69fe9ac11b547dd59a6820b4575b24be7772c7ef532bdcd313d3ed2dcd8dfde01e4431152adb40648f119291ab1c2dfaad17be437bff93efeca59f9b6a1f05af5ff405a24cac0984cc55c85ffd21fb33794705a0aada83b1c3f1931d404111156d3e871b764323a1187d74b0660388d27072fde8a22c1ed77c8fb356c1114149401197201f4904183bda8d85f6d2c27f9100b87fb976277fce2a2713d591a8dbd5b0d21abf1a5ab958ddfd23aee5ceef9053f85f49552f7e3c330d530f9ad4f39e21e509810f4d8444cc195c35f84d9acd3df0ea20ea117047cc005140d0aa8dbc1ca9ff2c561ce32132c07df4e8199ca572dfb11a59ec4d4c7fa4685f9349035e16713aad21fe991aa4d675db2d2cc7c972a082ad4cb249516a0a40628d3b4fbad8617e9dd754547c9b624acc7fd3876a72de58c1dc332ee32f4faf9caf7a90743a88dfc9aaac2733eeee816ca9eacc5b7255a24879e0b71cd99508f0d7b69ed7099f54e7050d95054c9bd988d6d6fd3809aa0443ac8d83199435ff450d56fb4bd72be037be56d021efd72a434e2a52a67286fda25d0a30e833d4fbd72275818e5fa7740f3c666f22f89f2c2d6abb29195925d46fbe1e99d97e468280a32533f72eb7aba05a093cf5c8da029eb394d0ce5fa64170291747ed15eb53ba10c999b9eb34e6713dd063ddc6389b8ed69c0e4f72d70d29f5bb270c464f60ffff19996e34313deafcd48b51d3a4e240fde28355507101a0df7407fa3f80fb5e2ef9a2e3b355e7afe17a39343afb469ddc5d9f03021339728b74187f2de2f6fb000a84614d266b651fe17e6dbe6d2ef8d5cee677302a2d966a5402dedc3e36e2f2ddeae07754759816561516a73ae116a1dd1a2d50464fa4ca74cd36c2e5b598ab0ee6a1b7aeebcc9b70cb025891c7af5e43c3e472bb769f3c9daec7b1aced07cd4a11c636946be14bfee8f8dce0e1612020544672b493e8b95231177290fd7c948e3a91a637dd1bdf330cad74f407655cbf25df72163a1ef59d4e615ca86d05793ec7fc227ceb5b214c033e696d42c352a606da7221e5ae843d08faccbd4561607417365135a309ba9e5af83d63481a3a762c847259e83b9ed24ce8ce0403e12dc916d43f37f39b9994417f3c0deb50a06401ad727d1afe2b8a1a67ced535cbb10279de55f4d8ec187a61c17e731380b51514fd72556ba3ae8089964356c023d8f65f7225e1e8cca4fed04aefe108cef409e8ca724ceedd5b76a520adc4006f1852d1f073bf5c5bd19c9c17000a320a6a5cd0cb568d806a8a3d77c1f8c40a885518996972bb350302ec74e3d4ab1c37efcaa7c33f84bbe8caa4552818442a39f45f09a94cabaaf197a342bd5a7dcc9a4a269540721c62c91f1527785e298f677ec1aabd1eac46f3a5ba0a317af4ae7453c12e046d4aad7da98eb076bc3aca72e8fda59d6d6d75557906576539192557c1ef50ab49e39a33f2d4737f253e8c877686e73fcdb74244638705ac0b2c81c02fb24e60722c5abc7d26b54b6af34b0ed7ca71f51fa546cbd70be8cab1c7890d75a2d36d7287e4954d673fb24bbea5b2743bff497cbad637839e0c9900bc81e5ff97b85834c9082c4fdecf05c43c2998c06f4e123f538f596355d9304d07340be982dd3872a28b72761737461f47b9dddccf16926cd41a715f7af89ae6ff80a82499e73f7271d1fbc8ebf41de393567fce1e6ab04a06c5b9671af87f8b1fbf9fb3d9e805635c4f4309fd372b72669d446a1678639e403189d98420f898f1b8a567d706d059c871917b3c5459fc1262f371a687280a72126152749c407f6d95ec987caa5f7252565f9e51de0b841f48a2869c1b3b63220e7bb4161cdfcb1886b4d1e3aab20d7f96fd8df080440a8deef426d7e405d5bed0b4a0fb7ca937f489c6aaf16d7772c316900fd4f7fc756b28af3491bde50c6a356940665df8b0442b88d5d41db03b4783014eed8e919b3d7747461977df6fcca6330f66629ad1025e286af01f7119afaf64fd1c97940325e02f8a8f335a0f2286777ff5d4d37ccc86de3ca4c79572f0eece08aaf1ef96d97792a9fb5df1975409b66fca38192b524fa4cf061e4f72fa7be858c90b88829fbc002b91747e59f0110e4f6e59a1e5f99f17df1c62e572de8bc15a884dc228d735171e0729919ee690b06bcd572dc4bd7219154d1f6072963c484a0dd947a6358f521a72f8c92812f49157391c33aec7e161a894e69c18880ff0c96fac03e89d91c47a2eb0bb18493ab66e18d730b7d5cf429fa4ba3f72eb5a5f6dfb2bdcdca018969e6107e762b02bd8ae2802070642fe52077723286d300ee64644545b7b194f6d32cb6efdfc31c334972946b03fc969f4c32f0361159a33c0cdf45256899c28a7d93d6a8c5472686c81da96eedc7e1f86b27848b95503d9289eff5d442aa4c6690ce1f1b9c75aded8f9958f6700d499001d7605345c86ad528b913fedd7f4b9c2b97b148e2f32bcead6b1174f2ea3a4f2eaf850e07203c18086b0c9ba2dbc156addc53daf6e7d13cc0d12834e1dd08e129a5ce785729e7a191da6824f866354c9184351d350554e842416339dfbb1861ebe7f4a2622a96dac7ab6f54fdaed2d9aa171bef6045914f1dfef0446ce2493fcfb51b43072309e0a290a3ff72fbcbe708d8919049b63088a466d0f9e5437120e9a7131a372b5a04dffaddaa3ede959b770751c9d10dbc7d8c74443ba7c8adee1d57058df72a2f2ed7ef30d8f642215ce735e2f0c5bebe305ada3463337ca5b70418d1ff85e5a9f08dd89c2de382fc60bf9537ba8d8aac888736b779eadd7ecef9b6314fa15cae9b869e33cdf2efde10913576b386eee5722fde9137bca830f34566b6ec6720dcf00dc331838aaeef3f8e08348dd932d6803afe2dc9d74bf293d25eb6cb959aef28269d0d3c0804e7e36b82c845a31e5b9c1697a8d5355f39edbc40b179b670cb01077f011cf5a3d46cfbffd27a3f6473c5b4d3be9fa9eb4eb94a0e537e940f03d2c6505aad5c4beff66d2d9b8f97cb93baf69775c3b63c05d486f3411ce72cdeb0700dc339fa976663d9685a35bc133358a407a68a4fab2cfdf4e1fd0477247b7935924c1306fd319c47848cf00130e08898e3e481c20f5e409026098327222b6f87a72cf2f94567e643a5ba2343e3cbfb92fedcec5d52dd801473f38706c64264adfdb99d7af2841fc2bcc8336bdec0445864e2a36e7b526810737b9b072c35594cf292c76219f771671f7a39e81bdd06e13247bcde44120e680e6242c120d290ff512269109f31eb088277c480935e0bc9d6c63f15a997883c31e4a93725acea3e4c0c8986c3903006b88cadd344ffd26b7fabb77cae0b80ff9708ae772e1e9daf5d5e9cc935e950c5258587f95e635e3d010223d3164d2e9f0bd938472b34aadcb3b147caac8f89dd3d6f7a4583be69be78654eeebc5deb46f2c47537207a8d8a18c9a19bd03ec373f7340aeb09d45dc72206b13942fef8312aab1080416b7d5235596a8b3af93448cdb8e93d7de06075627a158711dcf2bf8209cab729fcd34d30611cc8eabe1ee98f9f3c459ffc1e4259493594044c32b99b855d77266203385bb793e805907b8fe4ae9ebdaa9b1f2e08f9e7ba5ef8613187455d32351ea70e42a0da0cf9f28408fec8d97ba93456ab3bfae1b68d7c63a00e0b66d727986824d8cf5e15bb7ef54b8efda9034ce5ebfcf9e89b5790509e3fed8fcce72bf437c51bf5c4cd42076aac6373d733fb7341fb074ef814e529de2e55d9aa601740ef6d38da0a0080c75cca48ce58c69f6263411ac463354b02956e667425d72bafb6fadd1effc0bf36ff820c3a85f7373ab86b28e74aa6fb94188fbad5a8631504490c8a930ffeeea2982145865247dfd93be3422815a88aeb9ca732e75047289268bf9c96a7c77daebdca6bd18fbd573818e459a96aed7ad802f188f2b0a728754fd4ff767da697319bbffdd15d69a2579761fa858a43b43b5bec6f3cb83725cbb671c8b4b1341dd055ad10e257683a5a207c625a02483d24ce5dc98f37d72cbd96efcb4b3cfe8c79d09cfc05f7f70f83b009123de5b4d8548f103ea631c6ba9f5153e3ca6781d23bd0d98db9d33d18a92796bee020b06218497e5ec2e5c721f771f1f6512e3e8481505d58dcde28107f40498265a3c7b0f54a3ab17456a72b5a495724153f31d2eac3c2711c03f71b39e55899b930aadbf9e27e2fcafef257acf627285f705bdd9f2030c2e205bc8fab825a4047cd888b81edf7a224ddf727cb4923c7428f7a2294311b8779257b73de72802153920445542f10d18bd7d72c10aa10f9b3c87a7760576653833aceaef78c2fb513f3d3e7f154fd6c723243a64e820d5a029c2c5c0a9a2f0dd59d91240eedf72d12c4d1b5d27a71c1b4bf1729f3ab4447cd02cdb72e6ec4cccdacfa36cd1398397b1c35da0b7627acfe6a3720f585f1b05c8c04720345819910248672bfb63866e6561fa1995fe33b6f10013565dd2019262f1d91f670e5b2f3410ae1dcf8fb4789d7194dcf122e4afff3d72c90b4751e3a6cb59876a23cf653b4fa708c17e06f905bfc5fafb969aee56e764c58cac18532cb968dcee00aea5ecd0955ee518b292611ddbb11aca9cfebf6a2c0b3b060a883faa35645362cfd89295d3b7f3683b9efb249f89d67aa887acc27228dd56ceff227c5cc1551274a6bfc9621b3aa647efc1b232bb73df51a728d9725270e8278ade9c2fc6f44cdfabc818426a87a88e2baa15c7d1b775ebfaad2d729ffec7953f5b9c4711397cd6f3071fd5727b9ad855f3a67965c3605f811785388567a11c37eeaadbc6cc600e050297e3bc32649234f5abd4c2148b146e765572b359fe049418383b3b2b72fb6e67a5d4b27f757b31b1f5e627b49e0b3d33ba72ae358aa2808fb361dad5b1841bddf9f7a925da3bb22369236b62a805c7b75f029a8ac9d595a6a6f68fef1b1d9ea860b9178f0cd2416fd7ef8ff4621fa7bfad7280b352922d298d706a9e95842094e61baddd9d44251c12feeda1ddebb1ae4b7201f198337622367ceca2c1bce52d6edf1287c44d8060299b3413b5853e0574727145c28eb1df8e97ccdd050f34d0d7205c471f44f037f83443a6e1135483d77269a43966bcbda8c328d0a17bee5a3c48528ad41be731248f2a0ecbc68c2903728f654afc64667f5f22fdb4348c53f3b54dc4d44d48fde25c914a7316d0e0685ac277a64e88f3be8106ff922a7975d1063b94046b1f4666800a9756ae59b9594fbcfbf8c4ff6711060ef956e62e235a6a8e016feb9990d16ded5e21904806b036f994543b725b823fa6ad904353a98fd664266b71f679da3a25a054c45be88a279804398a872f901c835fd34d5f00250068a176fc6cccfb4c746f4219a1fa82721812a35e52c46a49d8b8cc40748121dfaa1dfbaa014cb8615101c12c7f258c214ea00f9204e4c63d1a2c27a29ae4e9f5127f998e65d0e79cdc98d91b0b719c7213255d19fda62734ed6f53dcd7e67bdbfd12adfac597a0774dab193503d4232b7209b08f7ce1c467cdf9578b03d5133f707e39e9f9d7620648c38ee05622541695f2531eef1be5dcde62c58260a5dbc13de724701fedd1ab77c420d32a0c7472f1e76518a03a7aacdbc32f4b7e56f02eabb048dd5ba07cd6b2c9769c7e6f02729d503299feb45ee85b8e1062ab72da53a8d89174ebe8a1c05b491536e51a837244b0ae758b8120a076c1d93a71fe4f448634fbbfbaab7c5af0cd319c11365072b4323f8d9189658381ca7d893ad10cd329d34bd48004bd2bc78fb49f6bdade72d9ef03ef19ec29ef5c38eee937c0ffbedaf0aa37271d4c8c8a9cab4f80bacd02ae17fc23ba44f06bd2cc64467d5fcad864c26d2c3fb6dd9031bdf470ddce7b51e3c8d63c94827564cb0ca6afc59b5ccb7f62a477080f220326d5b316079e6972a6305fde7e88d936ee8c1dd63977384fafd58cf1b7e4304f4b891a011c380172db2e22424b23592f938e8f0b11106fefb5db1ff29003e5785d073d1006ded1727a2dfe44658717a7bbbd1bbad7a45797b9ea6a41b2acec9baba412d0ea6643680de2a25ef8b3777b75f9f4228a70b08b0d98b0b539e4e3fbc74bad0883039572c637b98da05c8b3b498f4df9f72ff99f2e86a59328772ff95b40fdd1c5ef1172723045170412a450dec9da6111b680292de8cdb71cf86ff22a0a4594f3f78972d018c58b60dd72a42c9a80e4c1d71013f95a0fcfbaa1b8f6cd44c2e3e2928b0a970d3d4554d030c964e3c6d008eb786ae202273978af17fbe8e211794e4a1d72e77228bac830c5457e4ad9f9c1184ec2fa98c948165040e9f8f907e53283ef0fe84de8296f2cbe30d5d0612767ec9a0c9c9d70683001204d6ce902988f7c3c25e1a3c4f81236fa4522ec0b75cf0e68a618524a27695994e58f062f17b359d97272be4dfb48c5ce371a03294a1176c4b2a7ee40d180d0a5bd0ab487dec2b7437230055cfbcd6c54bea7f9f57ab6de16dc91df374f70d8af79911a561755fa977244a677fe088583f5a61c3d7851979265a13b45f920b8a517036a1cade7705d6508e82ad928b7b31230b9cd8727a0d76e739b21ea13186580fe5c81ca223d8e723db6fd80be364b1ded4ce92438eb9408907d0ec0bcbef18560cf7c95b7915072cad1606764590e3411f14441788402358ff293691cdf094813fc8a6442ca5a0e2024590038597267694362d428469d10c9486de95b20bcd77a9898972b1ae21b8935d9685a8f3689f454559b57e4db8d2aeb66b303df451110b653d04e03d330783f074e18216a293097aeecf28a5fb7df177b9a3539d0e2a6b80e8fc7699272557a8d7e97b940d257bbe2035ca238778c877ce2a4382bce349fc6d4f571df0891fb0a1dbaf8b5f2ce8209b747a623eef55a37d7ff2df2ff79b44838214825726b1b9c4cfa3ed7e3d460f6cc0ef0facc6f7b743b3f3ad6a551f8d111b97d4a72bebadb4984010b5456eab404cfb11adc3054c42f571c6dcdbfa6c7bcc5d54c08e0793260c20d8bcba0aac90203085f1486ba11dbed2aa08e9a16d804e3baab7248458975d44fe7769f5b1ca9d6fb1492935974d26a08b47df057674f207cf872231433079bbcc4634b6ffedf6cf1af751bdd935a3def0001eeaf67c568e8a172ac619b9e02998ea66b25d837206ed7bd28e43c373567ad817210359f995f6d723426d57b9d7a1dcd8154c99341fdf9be16a605b598bb12d47291d8cf30ea46621e9ff5eb4cfdff43565b5ddc8a5db77d4f2c3b07b01ff466891834c6c8206772ae0da88eba2d63f4840a8b392b4d8695ce469674872ca516332306974f734d53a3f525e8af12f8068e182efcdb6cbf9d32ed71e6c76f6d1004b5a9277eac7e728b61ba08e8492ed5e6757eead54deb426e81e8d2a11018c3e7a23d62319e57727ad4f22226ef7fcde806204db2acbf4ca5c66877017ebef8251afc6009720c3f75236523ec096f92bda871e07230cd97c7c3f2671a470bd9b21a937d47caaf726ff684827293d0e8415f193fd26f39246089add5bb60e564e59b50530324b672cb6664edeafcab5998f07255f8fa52e0ad52fb3115761dbf73a2dc0bdbb539726abda13060ab7c28e4d23a19245ba4d16122037bf9129f973b60f725fbd60f724267a25eb34154ef1fc20b9030616c304a2daaebb053b9d8907cec550178c172c3c4ce609240f01412768cc352d2950ec37a3b6621cf6b4d774491516792be72c9715ac74a5945068039747c586598cbeae15453b6ff96d3230b8fcd75dc1c72f8eebe927cffcc7efc53b333ff0cb6851e4fc058c670ceaf12f31647e1c03d5077fbc6c1efc9c4db99d15275055e2b9dda145f7fdf75bf909204db0be85ce33374bb0bb43da1c2d6261695b7a9bfad2c2016b1da1008eb8ce13b08d5bffcb62356905918d9643f8dce2d25f5a1f22b39b6079096fc10a84ab274daa24bf53472b06093aca13cfb2f8571cfedf109b35e17561b63e725d6fdcf10a4b57d526c72d7d99be38077c6a4da9872ff45d89a8e92254fd569929f7d872b5d8530f212006deb7c942ccdfd932f81db1c3a8696b8ed6d0c011394ac86269a2596a5dedd726189c871e8f3d48c98de052ff68cf93c94352266b6766d18cf42a6632c1aa672ff8c8de57074cea7a9828f40776e3cd6644679c756b249bbb8d326e03b0aff727fdb3848e67961e6cdc91ba6c4ae819edfe845efd4fb734e47da2d44b8217f4aa4724932942c6f64e8ac80f6566c4dc950de18f51b2ed5160be9fd201ba41372c36d570ea5063c83826c42d7d0bcb3b890cebe0fd622c99acff27a51dc8d0a72d898a37e96ae3d8261494ec9f61521c2b841c6315a463001074a567913c6f372c396020cad20bbda52501e2e6fc8da57b3e6434ea30306d51e8b68e0ea73127229543d130ad73aa56a0f5c6dccae34c298a22b92bd216d5283aa7c74a6d48e7293c6abb377809f253b9140e222fb8d086e8f4cab5a44c22720d7b21e966d3d50fa9d196dbc3b51f77f4525f6eb9fc0242d27042f8ad472f9b2e42b68c6919b72472e9547b262f44dc75b4a5ee701836129748d9581df6476664e0c2b7ceae17200207dd60b385181d7cd0b0937190c6ba3c28785b23a23e8ec07da18e57df44fec7c40dece59e79bd7d90eed97bab62b77c439f5e513e3bb946b132c04acb6723a5b9e7de2b6005d940b0eb126dc6d02f95d1c41defe8088c2928d0148f5602b52c83ff522ae0233ff45b057b4371c5d821ed10abc5e02ffedf096e783359e72ee708cac3c83dc6c1da9908685f55c4902c75426498d51702d5ef3bb6a861e7278df3e4483872aed6670dc711f741c3b7ba05e5000e0e94ee3fbcae847e9a2728989620ada8223ed77782aa1636896b9a634e45154c8e5c4490c7eb0e9430907ad36c81330a57b0da1e6dd967edb1ee284c80f5eb3493f11fa4f7edad0374a72b7d868017754923a562cf1c31afaca7c0b78bf4aecff3c642dbaf49051dcc84f61aa2d7ea33273bbc772c2a837d9f24b88ae9d2916536be391709dadd6786c1f45489a3a52d7547738a9e09733374f59077ecd3be6ce4099752caa6626fabd53615eb042d5a3e8611e636aadfee95e7456a5613bff92f6c4fcebfe1ac52b6a1023c0d42048dfa6906b70b92cf2102f8465ed98fecaffed5997636aa3b8071872d8154e0958367ec24304b7b658a3f8f10c73872c74f287e7d70b247efe427113d2575452c131a5cceac2caa50c2bb4b6b208a95cf66395fe238fff97f59fb17284988f97b487b227dc8cc0b83eae668721282280b473a5f8632733504df74172b70852de8f1e0267d6c9e62ba4491057facf48f98a0bfd57f6a0ff6bc455b2728db6a2d54a624b73b8c6810cbd667f38dc71a044ab244bab8663e8df23116372085dad736e7c334ec962151612c516a37cc44701d3ae6e3312599c877c22356ce7b715fe83456ef1975774a3bbbcf8a446f3c3d4395a120f74b053b88cc5e402ac977987a887f21bdee4e1c2629ca01f80c0a91bc4137c44d58965d97816b77203832ba9fd634d207b77b8c758945260c62432fd8fa67ef49cdc34f2dfee6e7219dbb87fbaa6c086f5a332c6e6ba0dca4617263c10795e9da25ec6c76621ac72b881b6d0e511716831c29bdc5591124ea540e817ed1d59daf8357efa7a8bf3723650f1153c3571bc75dd7e368ec608e97e7c1163fa0ad1e6fe5bcb530cde6f721abbdcd6c1fbfb83ac599301ea0e3619e5c4015b913ea38edd8d740b9045d10f2b144d602ea0fb3174dd4670ba5017dc2c70c8855089f27595e18a63dcffe27230465b011e53fd362f82c59b8e156018641d868726ca16826d582c9ec20a92729669f22217f937e032964bef681d935185606049b673e76b206550b472aabf606be802258dbe7bd88ebe590b632af054c99ad2d581b52e20d518e35ea03d2e7281647cf271a314dd28e0f3889a59eacc06206573ddc79fe1066f39d40cbb9072be65269d091a8f6340d20fb012571fb3bb39680c532ec329ad597a2cfe2385454762b9218ebcf5a57d21033a1671423d6abaa6f696a481145031490611c67927775ccaf3f388c1b4acb8e84d36069d353f8b09f80b2667a27ebcd7e13d8ca01382a567675b2a40bdd0ef3763ea8a67d518fc31ae85f15ce98ad89a5ad824af72ecb52cb8bb632bab929da00507ed3f7f3559e1cb360d591779f6dd58363b5027ddc034391b3707a673b7ecf6d2ac76fe131509ffcfb99bb77929a9adb61f2372853ea2d3918bb462ad8eefa1acd28399aa3e3968a3d38075d9543c1211649d332c7bf5f6b9ff33fa3ab3acff72c2f95c7bea49de1eb506c11c32b9e5b643557201df280bef0e58f7d436c03fde16acc584a5c9a0b7517938a53ff127bf2f5272cdaa2359c6401be7ad59443088af26863dee348c7a62643d314209b85050a9720675a404aa8a30eec8a19185a21bc739cba0d2d3913b8f8cb40cbe8b57f4ab396e9bd60168435ac1593ffc48c6592332426032b7066f832e0d3bdb039f0ed767aef66deebe55d62ac18678d725b11ed231fc805592d5f9f0ef2d0ee2b29d4f72f06d8182ace93606473708aa22b0ad06f0fa8682b1ac44d0fe72be60c4ad0f2d978963ba31749bf72bd89b8a0a6264d2c566e02dfee2945043d0e5f6e298b27253953b99c2821952fdc1fcd51da1fa6b3a6912286cf3fd0496224cab1ac699154bc539206b5daa9b8a7f36c130e6128db0ac7e6ea0bbc37769768a1cdd26b372d211fb97e0d8d6408634cbe4ed2d10bf0bc758d508011b8dd411899e1cc6fd72556138669ccc239730bc682d6c3b443da98251887d4e88eda087a5e0d8b6af726d6affacdfa041592fc3f2a911abe8f044c722523a853f8fac0509fc9eb7513c892619b111dfe874960e74a48e6ed79c42c9f88f6673578d35604748f3c52b72caff2d1495aa46c761dc0fe999cf6dfd69a2e444301b4e1e987edc4568a241729bea4e4376ec52c0a6eef5527aa2e62043f6e276a1adf11b403338d1cc8951729e32794b0f2ac24e0872596491619eff8e0874b39bee0a4719559bc2a39d3372a73436c442d6796291c3dcf611e68307c9a5c51f5841f52ae570e5fff3e08b725dfe8ff863c03ad7729c183dccbcd1aabb52da76a7bdf739cc8f799785682f3475b7c92ee3af4cae76fa61acfd4650f0805288c6a5eb1fc3ff8df0b9c0e81e72944bb963153a692c529d4a3ad323e6e9a62d0e7bf95879f8fde7c368b0ab8d729de7e230e4a241c06868ddca370cd1b980aaf9e2976950506db5d6290d1cfd4b70dc86ed1678a02d9e416893a96b7c42dd703e2dd2ca9851fe14714735e56c1958ec431f7416374e5a51b3a708c981c1fbbff3b45e9a941b8f933286110b724a6c7df2dd8d945e654ace81ec726ec1d3d390d0faa015c9b2ac8417e6e099490dd5e44f2c2f5671b43fd0d04412f3af7c963e84e3e0c7f20009abab894c5512723a66f24f31e5831f4fd77d3d2cef2f7efaae18a30518116b75b2c0f62ea3c447a5eb374856033f499794c91eed312aaf37b716f0cdcf31c2e56c80464c5cab72e9b7d47cafe3c66891251b3391b044cdd998b9ea62bd3d674eb1fb2617c59d723f7cab508ef4731105ffd3468ad65a17c5028cf50efd760c4972fd8096004e724dc0ec181be138033da02332515ca513344a8c2a9c5a530371ca4fad02d67908fde7a43a3bb25ee46cfdec9ce4375f56df83504ac418ca849fc728d444517272a71db0e6afbb29af06043de01126128b7a408e5d015e82097034b88778aa7d4493598cfb0f8a34e5cd5d2b760182097b70db6e25998eeeb2e5290e5f7dfda12eacc323790ac07cd4829fd6b4d7ba5d084a6cbe22989f4f90db73139109f404722f816a7bafe620b171a0e9d49fe4454130b95a2dcd018a65ed7fa113818e1d723958da7926d7e0e1e58c1884a4537874ff9ead656138961080778bff9ce06e72461c23a17cf6c8f48419b51d7f2c6191899467abec17267bed7d390122f19072aa8c0e4603e588aeff2a8e3c29c2d1d356f588b349c4f92ec804cc76889532724d4fb8d9303a8e6d65a6d552cab625d5639f73d27fe73312bfdd7c9a0077a07229a127e3175f9dc21d07fdd9abe4a4f11738818d1458a2fab11595270e472672b720bf278b055ab5f446cbf6cec66654a2ea3e1502c9d5f70d2da77b88b2557288b329a2165899e972cbca30f96feb8aa487bffe3c40f70bc186b2edd47c87701d331db8baa9ac1afd3ccbf60925a995d0bca054ac39a35d4b46e6dfe2e6252d0287aa56ee02b2974cf30f83da09b08f02a4a47e80e2fac256ce92f3c1cab04e6f6f9a159064d0a4ea9cec6e0d7da0c2ab9bb7710ca06e76d4a298960838247285e45355744ec843b77b94df1dfc1b3fab9091bc0a0dd4197fe4b4c455f3d7723d216f54dc4c36fb4e9db2559d11edbb6dc32cac178b41352ce8f4ea22d95e729c1e34e01858a2be7b9dc00b47107afdecd1586a7da5e8a313f4d0ed811f3372d9748ab3b582f597afa5ee487cfd6294ea3677876283a624c5afc373d5057362372b2ad4135369a7df5e58457f50df1f398449f0c3adf27b21f1e39b03249172cab9da1f1b7e98f5721a0d93f1eb2ac5e64eb9847e9a02b3c70df0f1337a1f72eaae75970f0b389d433724c2888c6b1594ecefea019ff5e1f6c890e3e2564516a438ca4fce48a5412d457238811c7ae9463f6e23613c87a35e2ee7168efdcc7200e29461e4c6bdce5af64225c2cecb7e44ca41b168678d098e46e931349c7e2bb3eecda4e5e81c3cc4f168cac4cdf9f143f7d56c8d11a5b46021523484f3a364347844387f3ebce1193a05cfa39696a0cf4ecc353707a7b571e2c4679a910b6e5bde37617a295c4ff7b80f7015861bcb0b2f30a57a9ffc8042e93f7d814fc772359205ec1aed9b53971cceb0e17b830d63c7290cfec1d99d454de4dbf35b013f030385b31bed47dc0d973cd713276ddb3118771e09dde6928327fdc4eccc9672cf48d443d9d0be962f07c38747ff43929fb24fcac8fb91ce79ef21c741691d7251e7885524a3d231dc2d15d9d7ccd40735d5b70bd36c2ef7cf2bae66da1f54699b47e3763783d1e6f666e078832f5d3187c5a65780f0612566f8ea0a55aee911b9c0db6feabea344c5c7be3767b389bebe3aba3a881cccf42d939875a39d4a651ebc521334b8310bbceff788d96ce758e8e074e2821b698299776b448b3b2472f5fbf274fe2e9926283263f63c7beeb14a6876e0c6652151644a751ec63da83fd705276c1146dd6a1388983809bf9de9ec476ea1a15664b885110398a033667282fc4ccc37e46fa374fd8d603affc52b84e5f49e122868d14b53dc95e6d65a72ab564a5f6b7338b6fb5484381039af839285131ccc7ed8517320772c96c0cc1789f855eb6f16d1be3345683d599141fb822d4ce6dddac9fdd88e3515a3dbf1726059fddb4f630b4d244f0fa35a16f88e4e1bdf1d06cd8892f70630c32eca4d72825b90b563808d07fab6dbb49b96eb3617ac659368e532ba9327e4ca34c1602cf18ee6b2fff43f884f5c5fe6f9d8af771d28371f024cdc3b877434e7e286b372358b5cf6ebb919adf991d790c07b954a13e9bb28f13875ce626fe672f7f9eb0f467206be89b3a5ce2c2187c2dd7638c5a281b4bed392472b2504bccc6d5246727de4f434ca9ebf559c34e7d0b44feac3deddb1be1e073d262a5d80e07738d3727f54ba27ae9012a394625090fd347fc209e3d472970667e5d37cd8c39a52b852216c52b74f40a31e3621bf75c6176217c2ac3555c56a6b6431b23163f6aa6b5db65d6c553cde38b47337bd8b8a16d5d6b257d9c94dd86c9f8b90522eeb9320722376664d7aa9f34600cc4029d2ad5611b0d98ff200796126fe849a2bd90a1272257fe344a7e85a1ce968f7064e84146e7d0c9b6298097a5fd6e3728d66fe1672be48d69c732f86c0f6820188419bf7134e766bde875169eaee17989c46c4cf7259e668ac8e02883320e91fae8240c2cdd2c5de4a36cc8d583fc1ce7b88d8ca31698ca2b2e8d2f4c4318ef0fb7724ac74d31d700a97e2fa0933bb193c8fadc70a2a47fe92cf80034cc810b0b1b4b5adf716ec3541b0ed8df4933ae63245992e7247de1c45865be3000f8525b566e6f17960a203a81581201eb64af5e364e0cd72bcbe42d13c3d2ff04e16045961e508859a0c09eef192c1df578b6c1de55679724f5bc473abf8d41a897418e08fcb67dcdb21246fb0e7223e3495173e73435c72f2c26d2673a309858152ca95a3948eaad8cc0cb206a3eda042fad8ce591a5b725aab9fcd413975c641e4e2a2cce48caffb393af0779067a2a7fd6db15f4b7472509c0761ae2e7f239d85ed3bd38d866d6a272a3b29a564b2078d70d45b279872a8457be9a7b88967ab77c486132927999c9a03bb0e15a1fe871f2b3686f06d7269e3415fb3be3e59ff5a49d7fd648ece5f3eecfae1a5c0d8191e296254361b0f24fb19aaacc89c6bdf541e34223f1e9e24eae7f1d9811cbbb8357e01768cb67236fdb6efd5791d6089971f86f6f4ab9bd93cdaf3e8327c9feb6924ebd86a1072cb453c85417b84b95ffe1a3ca4df0cb8eb43b279d9585137f726976fc730546cb6d900bd6b9c9901665ef22cb01cca5912d22ed2f6dc760c997b91104999fc72647a4581b8c43717bd587c35fc66d51649e6005649ea62e7897824034850fa72ed6594a60c64b430bc4d30b8c795559f34b3b5d8ecbaf2a819c4b744bffde5727e47bd95a17b79c75821184dadfaaa75c562118ce3ad77d073960f0fc66b1172b449818685096dc72572e086c96d20f9232ca9224f7b5d61dadbb0d47f48df727961c5bc50918dba42355d6147ed8624ceed6a6c37a5a619e3941a32fbf2e30819b95021aca2fd2cb430271f062ab9ce77e68b36cd2e61121ac473f725e81d7249151bb342ab789f9eba1eb1448bcce6fac677e0e2abe3ec111754fa5f164e72fa901239f9a5e95240ec8cc45283d0d93e1243d14e8a9b9587549addfa6a2052aec5f821912ed57cd8ee83d8bf299dac0294136d9a7272172a3a356283f83f727be0739d25f493ee2c73e9ed9f3392ded146f8db367545149efd8d24f100e60aeb3dd3006d57c3fc1c86f67d4c520ef721029a273a9baf75bee5a2b900968e72ae88a5d827345db3970785a707447898896a118e2761ae1279ca69a419470172ec6bc6ad63305b00ad8c4f69d488ec8849cc9329ed5ad6c82585cceecaa12b721d383692b57335c6786effc9d8561a46cb0698160aa58d4f4051c148baec3b0b708ed4a5fc7ecf56917cad895990fbd2b9858a798e62caa0e09ff8c5dabe0842640fa739bdfd67dcbfb549fa4e828272669c8c18e078cfd719da2489d28248720252f20b2fcffbb6a83c6fd52e30c47d36637fe0519ec615dd9421fa7bd3e372546a95e24a80a89f82f8c68678ed54bbe90b7b69af50ff9482bbbbb3a05590726b5a8a312c111034135be7dfc8cb9eaeb17e315d96fed6228b93aa3ebfdd221beeb5fc31610119b9001185be9c91b2a7af47f71b8f99e7d8114b98a60740dd4902409e97e988c713c839b1e804763050bfdaefd011ca4c344663ccffd3ee3c45858711c27b1d491850433647648154d67d77b768ee3a83df52f9c8c3f1fc5a70042ea43405e6e27d37e01225349000afd885af31cf5c715fad6c81bc309a8272ee3a78940eb7399b7cdaa7454caaba3728dd1c1d57dc5b50fadd6ff4819a61138dbfea86a7c470b1ff6554ebb56031d7c113adc27ce53ff3f38848e64bb2803067774238a3272118bc20323e682be78292bd04958ff6fddea25e45802753514665125ca827c3906c47b2d15bcadc75d543ec87cd51b27c8c84f43568a7e480727a877532f772e0fc7867aa539e10c18dbc54d0c7dbfe170381a62d5d62e321721ef06cfe637f1e3f07028bc1fd501c5f38ed3d62c337697ce2a9cbe0ea9963222358bcf42eae4177c8be6057c4a147c57806aa4466d95aad79cb8d007d8c6d72d0ca8980d8ee346fd6a5cc8e7720e32cdd57ccb40f92c8cf9ae4ec39d2ece8726bad12df4fbe8aed5e376e55ba94ac924e138ee08719b11a13ee264d4cb9ce728304c3d1c68fd2a293bdc9ff9ae61225f6a5185590cc724604c2d9e4f818933a57a82b18b19fc4254a3d7fb69aa4cfcc149d0d3e5acfbc3aa3185f7f28963a4a5805f748174e824525fa3941da1aa2179cdcd4d298b3cd7110f8f3cf30ccbc1c0a417cfeea53742a76741f9409efa089535bc873f906911a27b45f471de94f28c52b80d65a6d4f31676d37b1029249a34a68e778d45ace108e7720c3c9d7990213caf1eef2a20eded4f1bf4dc663dfd01dd5c3e8a13a00d2803f758e316da81ef5645d28285a59c752c3067d833fb3010eba895978103392be84b0cd48176a3ac3427eebaf0f1ca682832a61a2212801848c90ff3c3ae7a5d355cf15d2b8aa72264ca8ca61eae60da231bcb67c4c318838d2d7e16c8381731ab9cbdcf2a51c33d96857fbfe6513496bf5d04b280e3ba0c861cad509fdb599b7e1a238a5120c7224ff340e7f83765c96bf6b65caf45ea0e32049664513cc9a1906a03341558d72d58610dd838eb7f8cfd9439a6c451c540158c44d7ed3fa194093705463d733302a49f6aeea7c68b318c9cd532ce67f45deb6f60602a5d46ce38f51874cdb7d72cd03fdbdd7a5af00167e355f70c8114ee71e0d22eb2ed5ce1158ee57d1e2b572bbe761edce3cfb82cafe94405936c249ed15dd3a0dbdda8cca3ec4e69715f44d04c794a25f0dbdb7a00abe29e8db8142346eaca59ad1362e48960d1a83dfea082121cf1a1239c784ea7876dd0eadf7e9a4698854a217e155120623a442d2772f7083b63fe5232e657e7910d3d91dd8ed777c27bddbc41da6ed25ebfa83d69e4bea9ffcbb8a6c4cee96757adb6954cd68a686ccf46473ed3df6b7189de0fad14f64e1a9be258df08406b4f1dc6f1ee2a088b70c5019cc3419c6068d2535ac727297bf8477f1d3845776e0d6506dc9ec051706ebc74fd8b202df1c80f7bf325872d99cafc32ee4d8ad4c626c1a0bb17a5e4a82e87cf862441eb60fd69ddb1bb76fbc4eef927154453869bd8c9f13ffc6e7c2bbb21f318a056e4c4c2347d662af551a316036888902cb55b586ca2f861f1965bf38ec8d18d7b2195c16e2b512f372ceecebcc62791281d19198be758eb48fc62718f6a60e33e9d163859083624972a521489a67890a2472f2ea3f7dce5164e36f2ab1a46f1a7a63086c0200dc6a72697eda938cc3b049cf338ca2f75b8894823d05ea642829c501fd20bfca171572387f1bf103fc6179e8cf045def25a5844b84f94263bd5352f7cbe2835d78921dfe014a2c74bbfb6c2269aa2a8b2b22b2c1f3056e799a1cb9e17350c33c264a7245574522e3d2a4f7d5ebac82c3eeb02a08db679fd3b04d446b51d424aa5b3f1d1cf586e41806e1722fabb53f14913852c55c6b25bef2360edfa85fd17ed3f71bf65dcc487cb2d05176e370da950157315ebd514cba80a7a26118938c50929c72703f8d1991c19a5c2dc52c53b110d677678d775ed0e840dcc6c0958167cc5f44bf8dd1e7b0952d676fe154e1e7c94af3ff5fa860642ec9273cf5bea07a850d723aea30f96d2cc960e62c788f05e862135e62fecb125a9968f433791bb302dc4a414b3a279a3781149f2b3e510f71df9deed64f2a926046f126c32bb84a9aca72646dab39aa91819da7c2463e00a3cd1323b9ab17da3e832362cd50c3b81cfe3cdd8fe6590388f039b221f2fe9c6438720439af8a17205fe5d1bf28c1f138633555007f96632d66f0b5476871c08839ba20b3b892cc76ba18f91c4e5386cc9e25398a5063d75a2d87ee82d4316d76f02b0c1d0814c8c9885b9b9fabe54f09f66b684fb40eb8785eaf48566c142bef0baf32fc83e1d6979abcccbd4c2a093e64729124be017396b2e76c1345e896524405a6fccf7ac05cb4d702e87e9047a0626b15df68636d90c315f6296cb2c26bdf1b9718a1f6c37a23a7985b4bfc99cb98729192f177990741ec4ff675995b62934c4dcc3cc003f0329e9ed45c6525994f7247d8fcc75a4769a2f8c7657b6b2f5a7b4a5551281eeef752274cf26f06c139467eee2899f22159475c1963924ad0f69ec61042eae96fefb52c0c3f88fbf10372ccf2fc24be4d42c96cca7ba1658b9d72ee188426818ee444eff10222d41f1d724dc0af0f3af8923a5a4debeabcddfec59ddfad4afe5d4b9b46beb98382d64472e60c5ee3e30257d6930b7c1693de2fb48ddc9840433f014cdbe5a1aaccfbdb413f58063f04aba20ad86cf68aa7e5b792cd5cf1dd5c722dc53279b1c23b602066923a5a2e9aa983f2ba1dc389c017aecd59dcafd33f805e35e3efe4e447c0a672f8763f48fc9ff4fe7556062a535fbd465f7d50e016eb5bba59e90d228963357268ce1946784b69d2aea29d2cfa316b0a1d6b0a8a975197d1bfbbbbf3ad816e729b401879e4df2688543498b65b53e70d9f7b616f9ec99c1fa4eabb4a477ea3725ffaf479eb31608ec8912a40ed1094ccc3f3a8589a3c3373703636c5c8b43765d9ec89a0b2cc37fa2f28a151cb2771612f9f792373ddafa2b2209fab790e5c724ec1942b01b44233988cd0be6a39ad08f340c2d0f13ec8174063846d9094b172a7fa6102870604fdfa492a9969890a9cf26226abffbefa2bf5d94e2b2ebf3e72b8dc9f76856a6c6138ca7de3cb110da371aac5cf0e011154581aa09d8ca0c372800672ad18ec95cf4f399a8d49e5fc9592888eb37ee3fd56ef34c0043fc6a5721ade2fa011d51507a44d3fe57e4b9cbb63c01f3d6fb77fe22d275a8f209df953ec4bff5d275e496a2337af6d925585aee04a5be110aad1c29244626d7d9c5f7290a5fa39b0a0e7cbb4d78e0467c44ba0c5f6d02fafeb60f3c20fcf5fd962fc72c3c46c51557c1d074e7a56b57b525aac2cb0d5ebb51ee3ed588a5d63b91cc8723023e2f40b09bc86f46e4116f9d9206fadcd0b3634cc50c57b5626cb25690972ca2572ce32c6c9f0969f86e246516f2ead90e6ad0e35870d655c4953f616ba72fd83e0b1f946495b31ae20cddba375f7c67e32038fee9972e2511054c635892789cfa6257165cd513b06ef49c60cae924bdabd5911b3e79f78a614bbdbd84972717b49e79c3b9f5eae636c55bf8acf4d0fbeecf2558ff31884cb3e7f521dbf25f1651eb01f817774b375fa70f8b79942aa286cde2b78d54d6292e06819514a280a781f6888e46a9e08bb7d1fe6e6419f35b83bbf34a50527d2b4749f1967a76ec00fa2a07d69b9b90209c565e3284645cd5721a7bcdf5c833e2fa50aeb6e7e1f9fc41d23c65b103d2875f35a90aee0a6ea6991c496a7288f7b700a33c65d9d16f2698a141dde3dc70f8258a519a94539ada90f8986e551eecdad0c76420bae724816244ecd69ded6c712a3057f4b80bcc5cca8994b996a5d092a36fad44b834a5868f45bf29b76f006eba642be6beffaafdda6b6eaef4a298f0b269da3c84b720e80dbca0a1c7d634071d9d320c703ef16b7df0fed0c607c335e2d26508d7e31a9b1b5f27735df32260378d8a32e5748eef1c2b17bf81405e86377d3181a7129c5b1856edf197aebd0e9ae6303caeb7aed116a92039f672d1ebf51a5388d1c457fd53be8921b39e8ab318407800a7b3801bbaf554dcb9631268f472678b53060400c49da91455b2abbf5a9fdb70202a1809a9d890c09910b167bc9ac5079b972e6ba112e25995dff91f01d88b7966c5067cf4a62e39f38ba4ac3f8cf3c583972dc6653b4d0e98c80468b42907e97da8868f6804a78b9f42b20b22642e51886728e53441989b767ee9d624e96d86ad36bd4e3a7320bf2086f2ff6a27f7d420e47e6404b439959eb32cf28205d1a30984efd6c65ca6d9006e184a295c2168a0372eb1aee73ae5edd96fc25f0e7095f48f85c5477f3539fd8bb0234633e4105f613c689715586bf9914bb85daf09f296d4181492f489920a05e080c1de95acbe072b85a1163bb3a64ae89effe58a07a307bf35061159ab1c0f11553e3d7a654b621b908a89afb95d3eba854e01d3984727dc84697391c0540c903f4b2f7c300761f4975f8aa42a69b1f54263a76aa8af9dec1c2d8e454abf8eb70bb6105ac6ca12a41e63b8647e2aa0dd4cb7936aa9fbe644f6bf972605f952e0a0106d3ac2acb6482a7428992c30c22068d8a8c827d02cb5c91741c77f9947ebd9fd3b34cf0481df7d30962b0e4c60ed8777c57c280a7a067f568ebb30823209b9056c15ca972454c0387db84349a2b235e84e81cba8643588a27a8999cf08d6886d6fec199f66bbc55f5c7b94a1ab24c4a03e120c7cdc4413a9bbdce83e7ad5181c91b24ba7f47de052614efd34c155beb983d5182290154bfdce8bff35b7ba88f8a5322d46b02d42683b4b363766816ac0e5b316875c0db3c2daece4d62e4f217d760f8ca2c7268c1c1fe157798830a3dc2e9285b7149c1b6c01d4d467e7c170565547ca4d0728851c551cbff784105d3d672895080aff9b5f0349d720ecdb756e60b14775f248364f7cf40ce8647f6e119f8e1190c59fef683bc672ecba99945609b93d59972e315b4c5aec25af1a82a6d71de4641594e2c5fb6e58a7a73517e6b24e577f514b487292887575b046f15fe1f3c09823cad3090a3c5f06683d386bb3e16250f724c5cb62299cde6f09a543a39b34f645e794eb0fc076e5f815439a704ac04e8721201b49965a868e7fdf68539fe6879150704167323697ea86ab45e8cb3653a44bccf0abdd3c8f7dfbb69daf6f2b6dbf522fae5563088204468b639f3728189727c6d07e7266e66ab870e6e3f0b8b87005f59fcb64436f2af7f166d6b7af614290edd63415efe18809d98807aad5468e0246ce00071cb4f0d4b821f9ed6a2f672334a4e9cbc21263e8743d2444fe52de9ccc793adeb80b4a1006d048778980a1bc73527ca4fe7101843bdc55829b5283ec4d45b54155016ac29047686c5a18572a48ca45330bffe06cf1249c6e60fe6d654720a164659088a07e07d54f5e8ac7259a6e5502741fee0830fbe5fd1a6409adb3c53d2ae1c06906d0e1a38f6c1502fda5374a25e2cea195b76bbdcc8d8143a4b20a2158de06eb094a466d70a7ed46cfe3ca9276295d1ce78d67e20f85a34c1ebc407ce55f3658401721449200db64092f1b907a288d09b6dc03c3c9ab3af85f667671d1bd8536f686de26b02cc1c3d7f1617525b1c1b41186a1bdc755276110db0da3e22d5a3a57a07e77387d0e26b2a041b01c7a6bac8a020764851628ffc295337aa011e6c6723127423741eeb7243598770b4814c8f7c23b00bb75801b5d58c7b6a5c3704f078e9535e9c974732043d8496f9c2e0a472e2617b4212605151389ef3876a3b57900e592a0d924d72632805e081dfac242e0f2e9187504fae2b1923726ec572c2a1404ab9d54bf2726a880ed555755d2c75430c2aacb1b553064418a21d4697297fbf76883315aa510f3f4f7883e03b3ca83031b63a97ae0cb24fbb0dfb7ff65a00f6a2c7c26d04725cfdfd2d882339a9098844a94ba4aff039ff0d90638484394f87700aa31d1516ffa7bb5b3daabe62e7312391c4dda5fd0b7cc4132d7e9f85f0208877cc5ba47271934d97883d8a6ef6a43ce646767222d1e3f9180b708792033655311024bd18391b9bcd190de35dfb4ca54d50ceac63a352dec8095eede6b1009c18927fd8510bc9eb289d3631ad5137ff74b07db2188d813e92856c3effb1b0e6aa3c94c54a1223720ca6730bc271c2bf29eb0d5aef108c2896f403bb176d7a580777639f2d8cc559ab1a69b5b900bce9028232181ce960b3ae52cf6a68f1867bf5694c7b67cf4c0604fa21ee82b6f0f6872e90ebe080468339d5ed6f22a1d282ef8f2d9672038883cfcb93bcf3d5de0796b79a5d5feac7a74ca703849998f07d8a48057671d0c794b158eaf480eda462d101d47ce3cfafbfa23eaaca595c2dcb1d9dd26218d941768bb836172b2ce6d474ff9f73b67436428a80a08eb7622cb2d4963c9b4d04f6d68fc85d4d32e41e4ef1cd3b7db5af31cdb3fd1cbd5dcaf1615abc832b7236a9fe0b6ada9d5c636af459e289d2b0e325e9c496410e8564caed4f65ddfe72ec267c09ba911a1a0f5b4620bb40afba7aa1b3074f535b82ed5fa16e29809d3b1b8c8997a582ad75d9447d3ec2dca5774890f36ea023ec88adfae8ac8ecab57222b92a88a4ab31fc459e200fa00c6e23c55e03a65097709b3a32326f9786e6280f377a453c0cd992e1b9348efe0029e9ad64bb4acaed8011898d5e1d1fbfc172a31d32211fe7c1f2dab3142b41b3d26b15274c30f53bf9b03df6cdfe13383357b9260d447c3e8f063a01829a62c2bc8e18c74c58189f4052fe6e74fb79dd6872b8171fcb07c9ccd906e6d8aba5779daab47f2d6f6126ddb79c7bab301aeb61728b230f13f5f792c4587a3024eb3605db2e1e7105e4a2ded0fb2583b77d8f0865640b052b707b35538cfeadf9a3bc09d63d3cb3f1c583a7d4a309a38c7358701e700c143cfe80c569378a6e33eea3d4afcfa556a794ac5728e90b7c92ecc1715e01c145fc15a943313f9c398d1f28f16a8e0c31f98b3d1e5a2099001264b39772daee16927c33199a1908e967c5a6cdf6e18d1e5c40e8c0b3206ffa7077beac2eafcb72e8f7f447aec481ab9e5177f8cd30476f4dfab95b47f174d70148fc0f32b3718d0049cd7c58978a3d48b1dc616e6c1b517fe2da9f1cd5ec02f678a64e5b477b7cc73240de04e5f6dba097bd25d485842e8eeef6f7d7e53077ff4b1dc07299d1ab0323685392e24abd1fa764cfba5f9dcd27a9d8793fea2c7cfed15c403a3cddb06144170d89175b4ba195bb4cfdc04d460027fbc20a132d582c8443fb7285cda80ddaa798594c0cfa6d13feb82e4fed5abfd49b9b12315143a9db37822e84e64ca39d36f96e3980aa8d9c9226dd9caa05f41d8da5aeb64271e913fb4558cc151f277e40bd9fea3059e8a25404ab5c320a24a6ff9987ec6aa50cbfce6e728674eb840e48af64d79e1212ad7e1ee6f14381777528a7840bfe42f14ece7b4dbb142a94658ded26463c319e3a1c6971fc9810dbaee51c8f5e25d684507e6e17d678260f6cbed22aaeb4495c886ff2596e56a76856305266dc259e657397bb72a827ba475d26ed0b498298c3a639849ab573b5dd097488ac282bcefe4429f2586089bae1882dad68574d301f71eddb770f0162c72714c540aeffc241aaacac720caeea11ac913f89deba0f83d991098e092a3a2d91420e3944866986472aaa722f5452d0432648be31a9850748dc2045c947dd9c06dfd96e404c9084502cfd72af3959c1f52f3f82586c8e0db10aa21364f4d367db1858d6cdbf0241b3968944439201a6530778b8a18b146cfe2bace693b7c4fd56f66980f76707253fa6137264cb8eea35ca6c7622564e374607b027d1e0d2ab2f9c0e8900291a1962f5fd32afe8a010fb40e375c630993b79671972d24b389f466cb2e22dcdcf109cfaa0721ebcbfb630fe0789cde3343caef7a492a7c8712fc9ed53d0f8aa4869a77b7072505a57349cb3a590008458dd97fdec501a70868a589aef76997676d34a973625525e1187dd61eefabad740efc3cd1eff49136f77f12d473f5c6c35c90ae6e3561702506e4f828036a9a27de8b0cb45f28de0b3627f7d6a06b785de422c848f72b97ad45e3446826f8cdda2290318f9866d419135863c13bdfd062850e2098b483661393b5f2a280adc764b98c1f1a2d012d0038f286f466564cf8d2a9fb1d172b81bd67750f57240154ef1daed12c6379a4600795bad6dd50c48cf0efe210945a180d57a78c38d4805e7044a6875c10b72600efb9f2c44db500ebe2937ad4972db199f759f0dae27986504b18cd0d7d1f243ad90df0c7441a5728e93962828330551f00fa4e52e13f9ea3d936963831cca513f97dc62d02524fcd6508e73d072a7926daf3a37149f80d8d3d5c56e1a22d683a9604bde2c4cb839f9ba7500942d7818280b05aa4be0fd4a6e5489aa7e0df51f929f7bf957daaa27d920d708ba728ef445461568049afe98a3739cda5db1b33dadc826099d66599d661ae4499e32246aef3a0cd562682521b0a90a29752a9560b096e5c50ae64cb3c024c5834a72a20fa32177aea5acc66ff9aab8fa8c6540630de099164f31fec1c2a18a7b243371085b1338335b3dee6fd509d4852fef17b6ac39f170500974c838d96b092c727ee470b8969ba4500ddf986332bc0f31ba4a9ac4f48053d14caa6b2aaf261d567731feec03dee1c99601abd4fc26aa55705f56223bb43fadf880d8eb5afaf1721336f67bc3a74839dcf201518ce6ad22e2b083356a258d262f721ac73d810a0572668a1c358390765aad435e1166abaf8b7028ab521bda01e1cf4faa9b605520b5a5a49fd49bf128c5d015a9e89fe007f20654ffd599c07aad833bea545e3272aa1550336d1923a9d49d0f71b678a5ee55d1c9503019ee66f9dcd61f80823a62c3194a505844b8d7b7f6044bda53add223d2417a816b699f8ad68775cd2cc872f4c8e973a5a3e5666d9fac6a72afdde32541ede7ac4f0c0d0f07fdf60adb9372a7d34ecac0a6f862a914c344b6ae71ade0342f4df76cf94a3a84f9d060a18c729770d1eb0cf23c910eca6b1d75091634cf02ce8b629c665ba6dfbb2a8be7d372286051d14d33860b938b824a03ea9e20fce07241f38c555f6f91dfcffc252e1cf1425432401dcb8bb6289f9dc0f2566ccf559708274131fbe96b55a1aa2c3e729fda25aee516c5a4e35363134073f25f5ba15982c0582dfc1fe2b2a4d902dd5b9116d7918593bd50f788cb4601ef17c8da7103a4ecb1e10ba2408ef47f943a7255df151f2ca6a07c3effb844461d75858e945ef97303238a49cb7d892928e372deb2b3c1f7f94b0ce6b143e53262da673a5942e0fccc70e97f26b65ad74685711f3da39ea9940b2be2c632d4ef7f6c4b30a48844cdf34aaf734a907b1a1b57348fe6a424c79956c107028bb708d5192e81269a224c86584029bdf035f4778a61b9994f4373b6814571c7c4e26f5e662ffc717d1a028c2be73c632e42f743ee72296d2d30d44c330386d13002fbcc8415f996117e52f5de5def2b2a600c379412e4cb01378c2da08de72882991271f71a7f6512203eaad54b6f9b5076c43b183203cc2abf2abce9bad4fe5fc88d6fad380b5f55d59a41fdc61a652cfa3fdc4a68ae32a280eb8f0e11ea8cad7a83e22345a3bc4f7cff74383b033470cf88614f725314a18f4ba4ea1ad7f1d145f4581c5696d9f2356736e6185f302b9ce4444a72ed633d3b38e9069ac53986106e77f98369ad2a7bc6f5164551c3d1fd8771fc509ec5c11b4a2f9f3d9e848aa7eef39938948517f2feedbc98a07d20c257846a72ebf42e7185a69a81c1722dd1573beb72c5ad0f6ef6c06ba3d396f7bc116a8a727ce1756e9e265838329a9e38ca1bce247e3eeecafdd11b4e049666db5c220a7261184a3e6b9ad055bc134245024037446e9ea7c9066d8e05fd032672bed9df72cbd2df6cbb164909f30fa7152098cd5b9c6a365c5e70f9521d78bc562822fd7281a68af91d5025f4695cb858f614f5e8344d639161361c960ebde5dcc4869b72b011a34cdff7d1120574ff74583f8cec3d2d5be44cd997ed76d568f72da0e66fe54d827f5e99900053a4022851e9d437e1ec5d290db1b58dd33ac88f6937ba589b1da1444fe6c69296b7ebca8f4a1835aeb4ad36fc733885701696bd21db78322f7841f95f29b5823f0ca51bbad035237a408a983a3f07a253a76110176dd5721c43ff924100b084d7f45f2b655172d58a3d1eea387197ee61797377eece4c7269a5b52a05f709d3081e1fccb2b2d0697adc94df1baf795552441111495e952fef9e4d2c08541d52263db5c302d5be12497ef16d02cf9dbacf5ab8aa9f85867026632e2561d7bec1cb374c9da5400ea4aaae47f4c6773fe6268d3b9bfe673472a82a76ed9c41d26a05286a917073807df7c07a8591212ecdc50ae2bfa42c21371659e2e36b48edd87f4c39ec624c6a1097eeb5b8451a41c72a226018e0e22472bd3ad15fdcaae5ede06e956c2c0b99e228dbab044407bc37b57581c8a371e22d071b6288f0792a657a9f2b55ad35c8ce4b0349359b7090f466d6b2dece176e7234d2fc643c20ebdcb3dcdbc52111154902f1619fb2d78956e6a41700b3b66e3b7e6bca96d632609acf460f4c8f74c38c57798c5d1365a0367c647d0c935244233e8a1c17208cef5ac13868e8c4fc5cab6bfd68ee0342c796ed3b5d6c6d6ea509e97e22040d6bcc3e39dbbe5d1ac3b5d55ef90e13210c1292a352028e1ea9fa5b9e45d2eaf6e9e83bc2229c64c2d87ef3db29474638db03ad6cde925274848b724bb93f0a425a8140155d475a1fd3349d02d0417f518635ff834a99f9adbda105fb82fd9f70b215c45bfb9059df48511a37a8421e9002ad0fc69ee44074980b72d111bebdecf2a95f1616d963f684582ff295418325a48e042f0a9a4157b11e385bb9931c0594a63ce43880684a6ea96d7d0bc263d30ac8e177aa9e19bd47e7324915a47060854c6453d834e9f868e2a399a24b575417480dbd3158e1dcab56721e9b8c15cbaf97fb62d830aaf37aae8407c16b5b823688b90b0689c1a012c7720489ef9b72259526460cfec975f54bdb338e9c4aa780e99be9f7c175d82f69031c48a24a867996c11c317379eeb8a501afffbe483ea9204ad8f2925fe95f071a8a9ba00eeba8567d71a44f17c75bbbb6e9c76f47744370bb8f96d4c6c5899054ca8f21f01a3dc97636b54c4e6ba7916357c538c1cade6cbe87559db5a79d576985dd77a5b03c1abf25d4eda219ae92c2ae5579189849a43624a2824224965a398916298955d4c49a761daf8a09c520c5fc538f4b75ecefce6f5fcca196159972cacf414042558be46b44f11baddbdf49a6f8672d89d70b6910c6b69bc102bc72f7357a32826729f59db463359f99af10c70af5b1640741d94e86ff8d5f8ec872f914f2af7520edf898471bd1c20e9f4a83153e4270b58b2d1f17fb8ece389a7204acaa34cea3380a19def61c32069584c431fb4dc2774fa5c94410c022b1867288c7cb91dd1376e85eb1f3cf4d71c75991fa0f4ab351cdb8a07730e9e52f9e21fb7dfd3907db44fb39a629acf46f606e553f7626b63d8105390a192b76c618724489e956d3745b7d08c69ead699cdb265f40ae0e04bba82ae132e8880289a626d10aa607f57219064cf846d3f8349a5e79da9d7a2b27c268b798ffc6c5f2960b81915af4d5b8bcdeb937b8e7bd4aa0f7df2351adf6283d1a0f69b7059e93e6064cd783778ed1c3737ae551776a8ed505432c31dc89b2d719f9a19e928597907234d1fcca44e83d7bd2f22540ad5e67c9618c344f95224ce20237ec93fd7263728482743c5fae4a16d621801b580fef7ff205654d8922fb1ffccefa6e0097e10ede6d503f749b78df387e90bb1414d8c46d399527992433c75fdd7846fe284e41dbb3377d255b90d424892cc5b36d25fa919e04a812f28fbb05646ea01aecb363aa27b0ce858b3688bbc7116b4672995f4a96c8389af0057fde64cb21016cc372e202d3552e58707e8aa6d2fca2243e10a872d342fff63901963ec3578f0b890caee12350b6b8d56b7209124df107e82f43698d693b11ed2f2cbdf12d0b368002edcc08ff5bf248fc3ff1f23c15c3b1cfda48504886be27a7b335bd4709e89d721cd69d02e095028c8f86550214ccf6436dde9c5c182bd4d62ca01d682b814c5bef52a41ae3a987d0cd301e886ef33684a0686f358dc3eca3740ce95ea58ce23f84ed2b6412d7a4d300f19f83ddc43ad02fcc481627793c072ee366fb881dba70910dcac857a57bf70ce1c5a0aa70fe40adee41bcea3414f4909c829c2f0b86353a6f21e007a625650474cbaf37e42d7504a8ffdb5ab784ee8fdc5d068c6599040c038d059a25b83f65ec8593202f7d36573af80951fcada3a4ed487d9db74472f0668adf6a978785974733b495076550be13f7333e728743b43c47239aadee295a913426a69b1adcd71f97070166768f7d30391ca590438823587620ce8442722f88af9670aacee7a654d03752a4f997db45fa4a6b7c667599e88c509ee08c72ab1fae9204a02c50ab830994eb679d6775f69ef6f04b117b75e38ca563574a725ad11514fb00bf8e84721e8f8ac5a4fd64758174213bd65766247a027f309b72dcb53940d367361aee6d9e56c3227884c33cdba5b7a52fd425aa257b0a95f5728cbe0ce5f8083ce39841ad655dec5e69c879eef6c908ff487fd0cf8673eec3722ba0c936a36b293cac795e0f50e51e480b5844051b64a40688a89d02a6d3c372ff3daf96886db6556f526be6da3d131af17ad1fc69e5e22a9999e3838e824b72760cee3fd805462c43679bd8a081e8dfb7fa4a3aedc318ad6648b2f8269677724c1b0021413f109a7d15e0bb6094a9eeabd0b39088b0087f02f0eb439de611729574d5fc0bb39577ae60bcd2d65af1cf2d5bb175f551add61a993aa6e739bd6dc5c64a391a27c152175000520163e3680b3b07d59a9a05123082a5acb861db50c6e3d82fddd57b5199de605b8189921a18aa6ac21db5fa0db4c2559bc0eb39724633316d235ad1c53b555673bbad770e9a846e017a71afdd4d7f6aa6a941fd7228830c30476824e25fe20f31f771a9b01f95750cc7d792e3c3e90bca25daac11c8250ea416dfd394b187c954f4c2fcb77a272838621f16ea07fe35a9d6ae1c72cec5346764c466764698debc3e3a04724181c3672d72d80f60422b060b42943833971b24b90f8cc727b8170a4f00548e83db579f747d572c5fae13635217287287489629be26d0428b49490578fb11da16607dd16f3f94ae412717399c0fe738ffde6214996ce7c2358078a83aa3e1c7b9cf6d512fba4f7b8f57606b746aa81b73d2edf15a520cbad444e8f294a61c1655dac6eaa5c1c8773f49fa702f1f8a72b511e57418cca22de75fd91c3adebe155ff1516d4f83da70d69d99fbf7c36d722a20bcf52f0ce37a13dc76c9e62cbfa3adb5f2c8abe270193bc35765200491721e8296143e42aaf936d330aa9c16d0099db8f189a5a48dc9f051a703ea1b6c7222f7b6255b5062bf241140857d2267ef0258fde3d733f0c356f3426e913f2c725e9ffa29a03e99575d3297163278c874b9527610fd29a42bd8ae13f22e16a072f014966f6a4593d064cc22c13871da42a2b7fe9a46ccda28370b9f9043b90272d825d15c458e85c3208aeea2aff076a43332ad61d022682bbda13246319fba724e48f5ea44746c2a509dda688a0b9e992eca17370198bf925e21eec017afff5697fd9c19259607283649571f2e70c60a0826b059d1408cc7a17d8302c76aed156ecfe35f759700de1d88c6455125cf21c0eb0d8f4f73c4be28ad6635b9fabc27004b5b6aae350c846eadcaf1f9bbbf26ebd72b34a23dff1dc59a3d6c792579724a6992668a4b01a3c865bd29c951b896d2f8f1aa105ee6198daddd579fea737258c753c22afcebb9e5a56d8feb100b7c827fe5dae2e075fab715f0a14e8ac9726b531c1374ee2e9916e706f062fea1ac02057c586335be0d609ae0a2590f1a725feba6fc15e779ca4ca48178f9da8bf30e16c114e3c0068aa3ddc2f5fb84df7178f277c1689217f14bb6f0fa833080d8fcbe7d7de4067141f9343eee7c248628ee2ab5b54e4d4907b04c584ba30a9e093720a0fae0b456b9f32042105b1c72725e9c182f206f4c3dd8caa6fc493dea9618c6196a3001e76ff3e825e8fe7cc772f9f3bb1e9856d58383fdb8c39f4661b3bd293bf798eaeed72d5f787a78c13c70ecfbb5e89cee0be5d90b8c8e531e1339ac1b07349db3382466ffd7f1ed3eab58527ea5b6c2ab7993193e945faad5ee49a65a8b34ceaf74afdc9232197c61f8727f23f16c19c45b2431d311d35ea693c70c81dc7d53be44d73586fb72f6759872989280c012c9845a922a61e282c3b58b8a5e75e6265b6ba5036360f383400e58025d4a2a5e2bf44739b89252b940808ba6c8871c6305ca8cafc15332fbe73972270d9a500e239dc3ab7c3bc3515699f102a6991146980f15927c81a4fda72372ed6f20fcd7ff7528f2ae2c5bedc6e674a62b9579ecb4dd62511ad36114abac7242627cb5d55cb1e2234886bcf16dce74ec83f1f235d2dcc609c9b496936ed01fb0198371b005e99651fb98478bd2b9f8d4caed48807559c7235bad9ec2738a726db248b2dc77529202805d836133e6de7131ce280630bb83a1c291e6efacfe722a4e11e168b89b51a54cd0f2cd5e5f10db29a7644cae82a4bb395afee8d0d572b2638af5380d9f94e4a404a47c78889cc8a5e47625e25f747725b57d20f74a41ddd00ef40739dea34d76211c0e59e22e5ecc3a49ccdb4b30ffb24afa3ae68f08929ce2c6033b2cf673189fbe1540b158aaaab5293403a7978c478428d2d30c7290a3d5c35e93c709d218849e94a5a294dc35a2f1368c6502c59be59eb5969a6d8d8d54154f80b30c13179e50d1a51c2eaea3e6b23de8864cbebd32df8bb0af72bd85978c5f7b226584c1972c5454193cd4920ba47646cdb8d021ce4924066e720dd3d3f87ab8654bf9f1768832959f74dc6ca715b52fdf5a64573e679109206084c75eefa40ea15b0e525b34d52fad80f6d106dfd62309e4202f525d66f8404fdac5723d3266ca8624b4bc8bce595a55ede9471911665cf8d405104413c32d3bab6c255b76ddbbe532c561201d94ab90ff4b53f2693e86dafee69b43cb855772848b636a6d636c212836db64a5d7c0484c433ee4417cdce8c53567216f4501720f9462702db1856fdb500b82bff6786c936cee35905fde9a9b65f0585ee0af2b23b7f4829844566aee858821389abbd26f9e310aa1128edb04afdc3b85cfbf72ece46ecf1e3471aaf6a31f92db8656a475bef68ee64a5c3398fba65d74c13172a44be9d85dd93448f75cf5b3ab8fb27ca99833045142243f6126f87ea2cb3d61cfd19dd672964d849af17dc57c2fad400cfa407c472584dbe89f20aaa7266b7216efeb863baf36f15bb0b0232db313880d09dbaad95d8d374185b04a4a49bb72acde23655d039ad3fcc0cb15c51a3356a785bd420cb5ac5614e9164c9f9ddb3207765af845aa0378ac48be9f85f589dc0edc185549f558528419e5a32da20c7209ffacc4bd5d57e5d04e36ad58371c82f0d6024660f5cc70b68e89b89e58950c158f2add4f67fc3a9046228cb0b036e036e7fb5957160e97847bf4ee52946f72833d9bf0dc1b6fd69c368abe3345c4211e9b652cec2f4bf8d02706e7a9336e3b8feda88c53b00e440877c6c68bb0b259652394b2738b671a7c5d314a3935b45a0c9e70b1ef901cb6d7afadcba1469165a30de92d846ff218eccf941866e6f972cafd7551d5a76152c20de114fc583557647f52098188696033790638821fd144864871e5cccec607b5a10f9d1e12987931f01eeb76cf794c784c1c3a019ec972c39039e8f201318cfc2d9efce63ee9d06c7cd3cb69fc1046735f0b762ebc8f52165b1bb341f592f5101ff0f43815872af15a02e949496d54585f80051ad60972c0997440d0d35a25cc657e47d8f0ca3304001310380b5a14034efa5edc6e3972b448f19ff0db29433be2fdb32d7f7059aba8aeb1f6f9d20bb59724a5f1eaeb72c4f5c6df93ffa2242d3836731513f96a2688fbfe973262773198a3b3ea6b0859405d7b9240f392efcb5b3b2aa4e469ddb9af0c64fa6934aa9783b88fa49de272de1d2797ffb676c519ae8e7a0c5e18ee0d5b07fec3afaaf56672f50d5737e0726f9a753ad156babb289bf0019d84e7d883e410bc809840fca24b04312a6a9e649a6bfd9199ab42b9d977cb4c0ed5e43d031dad810a2147a404d17496da0aa7725db356359b2f47f322905921531cf3efcbe15f23f1329615e0b5dc50a6b2ab72b2606e451b1787b1fb157abff7bf97a43142d316d5395a6ece3993536bc2f362f41a915dc9f64c5284f6d222ed1ed1f4961565b1b898315d7e022508c0976c408ce016637615fe191af3035347b2a01eb9dbef7e0887417c60821f10d732671ae39be61f0b492085ac004f0754a2e4d993e576464f06f782053f04263360b546cc85186348d77c9eb776ab493f64f29cbb688327f5d325c472652a0f6836521f8e811b42cf1dd070c063a9df6bd8864b50736c1727f1d1d990d6e8bf9fc41156870664e1d0ec72418de92a63e1591990e4b238aea0c3d53d65bbfd0f48194c5d874aae7613a3ff84e6f6faeec13b22890142d687f56998abc3c04f102722cf055d646defb111e69c19be828089c9488c84db8792f7a92bd7a202e85a2abb0d72735e412d79f8996d7a828dbf02bddaa2047b4073793cf5a0a1ee9063e7703a11b1697a8ee646c514d148d931e9bbed6d463d004af79ac61a6877f83003589e29904c722e99344b0b92c0cd1c9f73be56c2dded6f072d42a75601897ef4c83a35b035ef10ee93ee8f6d93e3bf8784ade582c3434ed0223996aa8ebfe7847033724b48b595cfb87a56479b100937ff24174b893c8e56e7eaad9301c57bcad3dc729f9e432d75a58c77a69fa1b0d956da80cef3f2f39de5f0aa514ba696d90ac14f01f87dc597d896a8a6533a3a959f41a9faa67ea2af872da36d9890a442b0b972a30112d8c2256e3b4c09fc6e94362d8e16ae476fe645f4e56feced51e5bef30a56d24ad2bddd1d7b6bf6f1ba8386776936c189a71803d8d475da93d62950d77217feceb8c692281dcee627d0c86e370585d47198e032e47f96b2252a0d875972ff2ac1ac57da9026edc57de13348cea827cdec64f240ec69e1a41fe0cc99c0490adbd90da954878c64eeb567e79d095c41b526928356e7d4c727847ae4ceec41a69c669d488576f8bde6cbf844b0266e410586dbeacde3600cb15728bffb271e1fa52e812a36e3bfbd38de4989bc98fd47ffa6d1b0db01d81cd708d7e0899972a83279411f26d1a7bb8604859e28ddbae5eac2a54f37f623e9c7f1fc7fd062518bf4177384ff7d9f48de338a0f27fa3eb6d2847c2c96ab338b512b68c89265722dd130eb4a8afe73bac97b00c568f6cae9d91a788282add581d645c1044e357282763dac45f71187bea01b2bf104951048becf02496022f1887a5f7aa0150a720664fed38e2ee661a8755b2ead543bd7823f7e9129587e803c4c53fd151a5b72afc1877fc50feba6082cf095c6d5742dbff3303dc5a72076883b690e6d6ba17278b1def8e1a6ffdb7c31ce0b4e6a416f0298af51be152871cd06d9231e2295726f753f30cbbed76c3488a76691368d0cdfececa42254478f677898eeb9651c72962b4332fa73b722fdd74e40e014669b0e16ae77d11cdd51a26ead59ef4a6b4a0c5ed0b1a60d5275b3e0252a1a991bb41f526a032621bb8f69d327b7384bf00009cb9afc7ed3bf4d2f15bc2d2bbbe3bcd49e7a8a84e861d515f436ff6da24372dc08963b9d43c35fba4a5d6b630ed645e7364c2ef913a6ab877be3c40d6b7572a34d34dae3e97d417435fc32f3e5f36dcc1a84eb2e61340a0fe41efac62d09723868cbffbe33826be1bb638128a5ffebbfb53c3f123d250779190631e1c16172ebd406ad31ca0c8316eeba79000c4ec302e31e9d344bd6768e7614724b770f02b1c915112ee4683da848aa27269a1a56349b879e64fd1e3b32d78a0681d7457224c308432eaec9f7dacfc0a624ed7f39a55e3f26a1fd3b9e623c2b1efb195327d763092afa03f84e00a42fde75b6db22e33c0dac66f07f867136a72c4ebc460302b9007b6a9a7042ab935e9e07fd732cc3371915a57eca3f418983b6f53c997233182fbc4269c2a86e59525ab53be8f6f3bb8eeb890fa24712bddde4225af1729dfc0c56b7e816f60c888795817217bffede550ffdc6b196f4139489b5a48b27455ddc07a58045bbbd0e96f5d44e45e3ba2c4f58360f7c69a71c7ae4fa664b352c5cb4564b349be7824fdb73fcd362c4f480ba97b12030176c8b906bf477403da173209cab415ac9abe4ec799c263be02bd81961e52829a8f0c7d60a46a59772d2443d7d13eea72608a016dc0efe546b0f792b8972c1f4ef15c601968ff24e1389df2260856ef5f2fb70db1c3e59c7cbfe6b056620911bf63280dcafc20d6c306b6f0d98ca3f9f4fd7c6c8f5035094d52475dfef22e2fe477f6dd629cc5b1e729a22a5b89ab59ebeb36eb84a3bfe6fb58d59b446754ff7f10a787dcc1ce9ca6aaa837e6dd64b92184331f6d70fc377c27bd79debbf2edc19218f0f1d7d83847228b09a4514f2863dff16adf7b4dacbead388d7610e0411e068498f3a1d8c5672984d6be21e60e0a0581371700a00b12e201cb8713197a48135261f13e7da2072db0382ec3b7a80104307541a7a65aa1d22464d27a180a92b4179cf455fc0d55ccfa52300f81cf688e2da13c0dca8d6a0fc8d9d6dc5bddfa9a372628906362e72ebcbc18c3dbef70ea6043c9a267b0ddfe21b00e31b244bffa6e7ec5cebf98a7271b10c9c5b0a315d7185fc714b8c4eb974b8ae946ae2a205141ed9c34979a7218f779dc2bd9098d309e5b10f2ef817c4f602592850bc9a238773a84d388df2724cab779c0ff5effe3f60fb947d5afc26732a81e09c51cf8370779dd24f8fd87244c934ed021be76a06610c51371f83f06bcf08f1d3638260661a1f8b940dd372d90e43d508b0b46be7e5af734503b3c69e7b2ed0a33d78e8dafa5c671e01ee446d953b1907b8cb6738b6815773360315a483bb9730519ed3a3b9bae90cbf41724ebc05de70958a1902157ae9e933026c91e7128aaa65a84f8afb72095ddc49024444ce5b7b00ad87245631910a1de0de2b20fe66ad84aa85da75870db913b446a7bd41172b3277a36b08b5444f3575c7daef6b0c09536e8dd3b06dc1ab5ecc080410c28abb71121b807eddc54817f696908f37961b3ce9377b03186b9cb8de72c9c51650be89ae99e01085a5373ab4b501a08fb89a8abe4ac25d3ff32eaea372a92e55a0dded851792ea846fea6955cb2ff8b094445988eb00d4fef827e38a70b6bc23a7ed2ba879cfe5ce8cc0c5e16569abafe6450b90ba088550414689f37235038385486c0a7a0c542ea4160869591fc17662d395fe42f62cd4749c099d3087c6ee70a9a1fdac9fab543dc1c512a1794e952e2631cdaf6dba13a83f21c410ce60bab58a16b5030dec63ddf5ebf4b1cf8fee9a7e3a5df7aee99cec9ad22072c83ca82cfbfc9273138b51c0c6b1f756daa582d463053d18bd40051e09d2f2222db77243d9e083dcf8e90cba0558f08fb18e1c0af10f5bdc3344a85577896f72473317ab5f058479c02872cafd114669e6a687427a5e7097164e227fd9d3ad72222fd7782377bb5eba943fa9682cdcdd1a8ecb4ee193bae1ce81a9bf839de4722490ac246b8128d6472b52ba8aaefd814ad477b1e4cb09b8310a307e2895af0c33339d3b0564247f0026c3767bb5fff6900c45a8bf348315eb9cea834be7034c00e33b19611bf3dc9e70fb656b1a873a4aca6d9f55cb5c6f3321e06379da2772cfa617c77729556e630f1a39b81719a24db33f8e4c4191cc72339ae741619e5580b18d7c074df08656ee44e74a5bfbe1aade0477f4d017019939aff2a1acbc174b59178344b53e81acb4956264dd3af20cbb82bcbcb56ebdb08b10f954437e72ab23717a8ec13cf390ab881607486c1220186135d669e74827db881acd8c2b7243d6dd6c4680c2fe568a52df8022b0d9f9884b7f96cf02d0cd11b7bb6dedef723dbe3252f706bbbb223f7577a94b78b360f9a8f0a73cd7988fe98aea55110072aa5a63d3d3698668d86f0b463225dde040acbda354eb3d7b542ccf8258b0a00617b6b39d0defb2393c7820a2fadf903678be1ba3cf27d5b1d855e9490350185930936c92a95fc4df2bcab99e33da8913989cf8adfe1ebfe1c41308a4ab35197291aa95ebc86ffaa3e9e49973694cdec9c9423d6907d906eaf31ad4f1389e7d2c718287f1785c53755951cd58d04f8a2a908951bf95201ff7a1b9d4d896e455728231d0ecb8afd5109246cc5402cf5705be2513295195a6bd1d997351459c033982d352ef001ccb192b5c6915cbde02458b9b5dc4a57be55282265ed2a1d0f5305b687b01c5d55793eab6c23e106b87445d57cc2b1ff78bc29a0e2ea6a6655b326801b4066b3f747c1f8e91141b62452549f34641154dc740e19bbe1f4ca21a722c86a7fcc11ff1cd9735a2bfea5c463d8d44b6921c38373b14581b40c0ed5830bc28eed0e4b573d0ff8a4e6ea9d6a5bd47ed200d8a41d481a85853cfd8a81b4ebfa14e6503bfe130deb2abd2c0e69edfefcfd89b8f4f8ac8fd61d3b58c1fc672380a076f147f8a3bc301f6e9f982b56adec6f6926acfa942d07a650c498e5572c2e42210adf947d7b579a4c315dcca9f263437e636e147b8ecee05725ed73472b53f653f0d52446deeeb56c1e29f7fcbd9a2a48d16bce02162c633ff87c76650d689a4659fce3f27f5c5958e4062e6f393cae3b1e0ab1bd18a322fec19878b6c37e22f0d357e82511da9c0a06a3c57a4d65166722c6ac8103020a058c2c99472c4cd014c8a214732b4d9cb02eba124085aa5e37df01da25c4b2fa7f7b8e188726d98cab5242d900c339d6a9a1cde046ebf4358ba12d26485d5fc7e32bac0ff72a8973ec160983f85abd2e3eab5e73e9da0de7c9781c2214d027491902f4c4972f4e26c798026cee13020e21cdcea31b132bf0b5b26181926f2f9a1c03ed0d572b440efac133a651df0551e9b1809a06c69ff1c8d90a0bfaf9636ce4f4c1c4c3e6a36cff63fa8cd39460e8f32baf85f8ff75da65505eed1ed644087875cd232725980f96f248551cfbdec146898217efd5d6d1560a91c64a953971a6c818e4f724f0fd188c21d81c6719a8fbbdafda3210b3ff47a482c0b0a3d92f8446f2d4f7208e9ce4382ed825b574cd77d76de2dc136458ae37260f3408f22f2efc4300e5115c0fe5b6a01e40f9edbcb4d63ebc064ff38bf06841bbb42e54339aa20a9c17076f1de645f25db349d3bc240e20f45cd444cc47d8926c8605158885fbf3d09720a25c2f886718c2c33452bef0c87b07fea67d95c5a6ed16d274bf0305ad89d3a9d57237cd82ffe1cc88ed345696e129020dfd91e3d33887010f2f8c68ed91c7259efafe7521997c6e305bb79a047ff99d50c464100c2186f120159f0c027cc25190f03da630b535bbb809fe107dbdef53e14e26853dd415defdbd1027b132930ddb2949fad1ffb3183ae7f0060a4c7c06df7c9e192c4c8d7b15feab7e869fb721d0e29ed5a9deb8d06cd21bf358935b6b5cda3f6865061416306f46622ccf5722085b2c1e610bef67c192d3cb4d59a346aaeb9243d7c6fd6b85fa79de2f1e872977169af5d300c0efda28196717e01b9157ecb1b28bb06c0e211d357231b727263bdfaf1ebbd11b5ff4ae62b434ec9b2782940cadaf5c2dd86e103b6318c9e578d5eeb11a8f96c00413f664dbaf0e51f12d1ef86bdf07e7581c155ce30020b72599e03cee201c6f4848e590e221545609f6daf1d2d784425bc2104cd5ac5fa6cd2041e5aa3f30ce4a72c10792a4baa467baa734f55b293b27f06e73a94c20a72f5e412e82606fe0aef98539f37f0c5aad283d04cf9099d39564c5007325bbd7270fb4e98b935a95a0d3405ca335b6e44a0f85edf185cbf186654499086f7ee728cf4be194077b0c45bc098a16b2ff690b7d32b7085a902ff8cc69551254c7e725fdcfd998658cb6200012d5c8d386d049ddfa8214765cd7943d8ad91305af5216b140bf9c028a6de58508e84c5ea4d26230c20f3f6ca881750bf2f07405aca721e6469833a9eb4210c3ed896edddbdaee4d975757f649378120a01ddd2497e72424d0b923786856ba30710df70b858fbe624be04ebb8c8a96e960923b750f02d9660f54a495fed6d498ae880a6e4c9df232b6541a8e7956f980661353ac451729b850058615553f6b28020720b177a7811501ff246063f9e1d3967a837393a72072c4018a9cb6b04acc812abaf5e99eeb08a43d596feea182839465fda6063720b58a200aecbb02dc63a959f494cb960b945b1d6c23c88364e5f4e0218178f4917b92eae40dd9c1969b954a47ff5e2c364ad9f759af0d51ff55ea5f29a37ba729ff2e45fc1ccdf6169b3731310452340aeb0023186537c5b948962920357a37259fbe5cb71287ab1dd7ff0409d3645014bcbce68b8012717ab31b9819932da726f5438802d258bb4ee170223b7760e99064da184bb548487ec0137f71bd0e25956471cd6c7a74391504483a4434d4a3e4a4fb98b62eb72791722b24e0a4f5572fe5c9935e5d32412ba9eb957afc706482ada9251ab8f29fb98cadd17f6769d723ba3b87fe982b5c1d381f68ee6d1d7f26dfed153852cb99aa71c21b0496cce69d32b3455c2494887bb57a1cc668609f4281b008a1d476551f203b4f6f2bab731f03feea7602fb79aacd741e0309fee21f9e46fb4891995c53ecbd477e5c5fe5ffa74054ea24520f01f248be7401a82dd6a978cb12abb93de6019d2e41ea98d72a47cb56aa6ba34a10ea5877a0145a5a249342a5208cad33eddd6c95c2150cc72024e914d13acb3a52ff94427d28bf1285ca66fc66dc2981bdcf88b3c5a2c6772c8e1e8632da9de9a2ec3c7fd619e1d4a4d4c4109b1a80df2207a6d694832b90f25d922a97df57f7c42dbaec72822dccb38bb6e44651628b0639b90823df13972d66cb8835a35fbbb1343f0f14f242335e7c9ae7813e3d7382f022470f9d071363d7c571a28a61ee852859e532e243d60b4a99fe50a7f5664a71561cb618a5872f5b6914d2aa9ecd478141b7bc0efb02ac14952e6c94c8bdc5fdfb950603a0652d7bd745d7337fcef2229ccf19b6ea3bbf5c1665e4ae3b1faffed8d4b416b2272041954504887d2e880cc03fcc6fe3994fb3148ac718e7d832d05282c58ffde72759fa6d5bd01705d7ad4ffdcdd03f32ad252fe060be6421b4d2982e81ec7f072fdea5633e0d8ae7bd72bf6e1591ee9b70f54db4b2a6c52565d4cb02b7eb66e72eefd8556cabb0438b19a53e0e32ec19adf30cbd89fd353a09b3e089059d2ec0618bac9d4aae11bc253b63c9fb6186945766c9a3fba3dcf7672c4121e7610d272c2daaeed564a7cc7999d946fc47cd22111dfbbeb878e12249a686a48f1107d126c354300db21385eb07cec60f274a85bdf0f02f13d1a62434cbe8da659802430418434b9d59c426c18023438e9f008071b8eb5083cf1b07df254548d5291d872e27602baeb1f900cfa809bd5322849102681b067a324e2d1edf84f142c1587030fe626f5333ed6109c3c5fb7b12886aa3c50f6b61cae979f57078aa16ac89057f87378290ac781c0b84c3c3eb130abf2ae6b7b6ef5f2f2153c69dbae1af559725a937b1aef00468150dce97adeed800a80e32b592bf3e7232ef3ad1c191b5872d70d7b0cb45c11945b960d50ecd65e1c72db2b4291fbe200a360038038d18f26f461bb708e0b72a639c7dd811b8d63c9602ed8012bb124788953eb16593afc724854dcf5360aceb63a440f724197aa0f0c6f20db7f1acf6d88f04a361681c47256df5e1c0e33f14fc77da2d03e968a852d4664ce37bdc1a0c0699b12c846cb7210387a291a75efc0be85eeb21d002f67de3a9233191689a64e50b182044f2b0883b5a8b139e8d162ee1b6ced7db7434ebe2f8b2f5704f196e67bfd3736918a72db0f3f8cb026a19129928941e832538ba4cd41f7483898798c6b88290865a7725de58d231b060a3fda38a6040b568ea8861b73d7e397c8b202ace3301826da72a7fa82214ae955c6c846579b27de610df86bc8109eb7dc6e6db8e75f20887e7234f1f379d1575768198c5ddad654f5680882ad0a1278c66455093e137501f76c8a8866f218cbc18f38b91191f5d22f3aa20bfc1b7d12bd752b56d1901abf2f72271db0b1d717347379f651688152b41bd898bac61114a4b2fff5ff1804268572b53597e09474736a590746ce17bebf292f9099529bad44893d6028de2b8d6872d5e042e99881f9a7fd57c6089a1a51e552e2beba7cf7ca0d09d18d3a680a1b4d67f28bc4dd601c2337fc85eadcb7c3810bc0b1546bacc1d1decf749eaa3cd1389e0d2adcfc4461628844de8cfb62821ec78c669e5e51f1290454950f41cb0a648f38e69c3853e882f4e0e6eef1f845da8a8e10f82a038217901ab1163f6ecb7289c5502736046ac6e2238a7dbf2bef36c9567e3db6caf66d53e1b5d77ec2fd72f472b7996ef236e4b0b5aecc5b38d6756601a35d91fdd4958b36290cdea7bb7268804c41bed01ffaae09af00054c6405c5d3cf3a0b2875d7fa11b6c05417eb7278c03f8e3c64015d52bbe30703659cdf7f1cd38386ed722311c061e4340c32558984cba32e294fa8e0ab8f04d213f9470c83da114d6433620e194f342ec36c727520602da5d1cda654f39d5241e396a9d19217123442fe405e8d42a32fa6100bfbf95b90bd759d657f2f76755dd1fb2c5b4a02e1570d85b0d7e0a5ed06a1a872598d44d6269db7d744d9bb6ce11f691bd903680c4172416ba4125f3e50f82f720a124034018ae566a495de8f51d8129f9e6921f0a006b5ca5ed9181dadab1e095bf41ce42a4252c994f7f4f5d4ff754bbea7c276719349fdb944051c071b7d720231d88e80239fb977a9af87400388937f9369bc10039e25755318db8fe39b72108f4858b3828ac08c768420e3191881f381533e77e6916b4927bb6fb462b172fbbc5dbe97392d0046d69923578b307ff19dd76fa154c18f98eb9bf0f9dea87277ed91a6a5f51fdd6f81a30be94599972248da64e8ae4ae29eb3a13ce5d19b72f6671d27b755adbf4f4c5d0b865e7a7ebe993a824bb2a30e314386185f55fc6ca1cdb82355cf7b26a24ae87553aa86eba3f038f283925c117419fab6aaeefb0510b1040c07b908198fcb17216bb91e42a658274c0c7378c3e8e503bd296e9d514d09bb5fd5fcff697c755129a30b5a00a98ff82b27dc99b8f9c356f9ad9a7c72b34be86f021687f28fd09c5261193138be6df1239e7040478a84f51067f7bf61dc81e35524ab9ac31177d60c54a738174ec1e1fc27b87951fcd4136f60c2b05c3f0b13d087f4a75cd5e7e92308d9eb3a89cfdf91122e0994fb2f86961fe6b17295cb971de01d8c172dbdbe3f2f43b13b581293546e7d751ac4b6d2ff9459ee37d4f5c71ad6226f55c4613af80d3657da26079cf788da8a51c5b11c9fe9dca25da373f315b7fc99293ca5fa45679ebe6f5453f2a2f5a141ff527ab129d591a129ab17880a4b98f60438b743650aaa00017fe20969662c79281cb8f7ffe705b111318c3027a5cb7aa00652dbcc24d52df7cfa36db3ba57d9faf2519b2f2194ba26224fb996204932a2303af2058cee38339239769438cd9b806b8ce4c65e19656bbf87b2b495be4e0b8e1704d67d175811aa5a9f0514cfb29cabfc2417b0c62372dad276596897b8aa6ee9b60dc4e3a19e5fc008f233ec887fade8c190f74c813686a41ebe981edfd7b2f479b7cd312b086b29520198ec57bc96cb00ecf5a48f185bb9470bec3052d1fd44b035aee61f081f3cc06d5b1fd9be63d1a43d2d2aba7268e6ea58f3b70850b205b73f818f3b1da518a2e27ff0bbe66f630d15a5358772f11afb9907a4159f6a28b9b66086ba50224d4a7c503d0c8bdae5df5aa1ee8872685366f99a4b9c1cedbf306f825dd343e7eb913215b7db29a8f2177de43fb16cff83d29c4f7fcb32fd382e0f9915cb08886942e0aacb1c6efa2d2008c54e9a72917cf4693d13d3221886318d4e83771e83755259463f01b9dae505d4a9f68710c3ebd5688abd76526691bb5e3630f99c2988353d6266e1be43cb692e7b8bc172666838c74b90c4325c0fbc83e0e0915c00351e631bf47fb45da5d8a87dcfad68503a990c5c39395b728e62e4c8a53332aa06ee50b5ea8b7e31556c757c2157725558cccb0978c3a9f83e833ec161f5261ca764d38f192258093457159905da721d12346f4075c86d685f7db2a6383545c0cf5500593b41b9ebee0497bb19ad7217eab9ff3c5e209c1c6771f4f85137ac612dde2ca0bf72bcc035bd41643e0a729702d4968ced90f932be9a7d34b45cb177c722259531878a16ee0cd2edc1ee19de20ffec322ad82e4f0a2d14a0917cb25f4446055c658d9a2636ccbe7d1d92724e70b84e399e8e780c65b80c2dc845c78365244805c1db68bca3152ef237bf4857aa9982f60214acaeb0abd2dd8eba627b275e790080a33076a46da92e1472012b93624d4562c8a9c659de8842a5307992d210d35a8938cc51b431ba7721a550150b4e816f10bf275c54abb09c38347556c2eea48e56638466dd57b557a572319836c0c9d4312a3c2948e35898ec160f5686c85fde290b609c7961d6ab20f053e74805554c5f4093a5cc834e31c47d646d74fd30b14c3c932a38b7c5ff8c0f72df826e82feb051ca670db31fb4e62b85cf8156ea08dc76a17881ecfcf8341272d27296e0a90c736ee5142ec47c5fd599110bd82a6271cba1264e1c5aafb6c77226bdb0565e643b4527f309629bf96a77d76edd27b1d807161868ba69d367b47166f51171584e36e1f0ac1b358a94aee6b3f69e299a00b160aa7759025017c61bdb6953bbbfce77ef897c52107bdcc4c5bf28228e85abbeb099c293edea075d35c43720ffd0c9b1ac634c043a9090c69d19647637a6d13808da0c047e65d71a24492390b641bd0fe2da44d8d1bd6e057a53262ad1300c4f897e1f90d99934a966fc7c4e88accc3c9efeada74e7e74698e5c52e030f2809e6947e1095c068a1b7220a7089545921a6084cfd9af87fef36eb65f2c1c5032df8f97f328fb587c9072d166ed304303c581fe1588cfac98d92716680bcecba11be142fb3209bc04b8011fbe259dff6b8da45deae01a2b29403ac5b1e0eed1891e1107866552c542fe72b793ee5a5c437310763921a2a382b5635bbf6497ec4009aba13e02681d92773281e2583641eae1ce82d380c9d340632d6e214f80bf6eb9cd76f9153d4ea23f542b4eba1c53c1791376450a38c6514f9c291cd6e30cff0bc63d3e8d0e9e27113537a644561413acd604eec32925958176d97cd5f4450499b66abbd6dd4cd2567266959e28a4c7f103fa7852de59fe180d0d74fc7cdad300ef5d4125a8a2951963d00269cd36ad42cbf51eecc2a40e9128e461dc71014390707490ce0e761e3f60fd85f3f0acdcc8b57a1a09b853c58e6bc0d2b08d38a1b3031d168e3b6bfd6372cbde5a7fe9e3eb0acaba14ce707dff0b51abc297e7874c5a9274a6e8fb79db72b74541317dd2643766040137fe9a862bed4586268070a279afa11bb9604a8511f22487be23b271941f32a761c86db1391714e3719acc446d6be4b08ea82c7772922f58350f1201f50748e00dfc0b5cab04d4d9801cb9b0add3dd5a7cac820772df37edeb57211adbb46962e37eedabc787e5c93e2863ad6ac60cac79014be62bf0e76f2c21a808a818006796403510b5f0ea2cc87d81642557ff7f29ab783c72caab3a243a896b8a70fab7a2a4465118613099b878cfaec909e6f6504c5526728656a16b433fcfdc7112a05ae1d41d3fdd649b46eb235cb5afdd7e1f90cc553472874e5d4df3ff5bea8df1aa177730b5d46809a1ce505d8b180c29e1478abd148630acdc098978bfd1b8a5a987b337aad3a65ac1d108c13b6efd19db4c0898720d9ad7e40f3c066a87479a10f7e8b03a07b5485d91ca47f3df5209ef1f4528724fe6a6bae05a9bd24c37544d2b21e8cafc4a0bf2f669898fb193a340ead40d72a6c7e246d4a4ffebf832f2f6cad830d5e393061887fa43071ab5acf9025530721186d8a19467b7b724b65082797debb7697f06214009920a0bbf0823e6be010568a94bf6bff69322e5750ccd86754272710ba2fd56c6ace0c39e0716c0765254b64e47eda32407cfdd9ebce595a8a36bebcfed8ac4433903e6cbd0ee3c2c585a8d59a8bcc43767436daf8e584c7d28d1cdd15363dc5e09359e4576ae0e009a72eb5b8e63526b5fa654389f0079ef89811a41c698c6ba6bcaac73a9b239698f332a4fca615452e72253d870939fbc1db3d0ca5c56b28197c247283ab7a6b38c725b455bf5c300948d17feea0ac73a6b2c8f561c9a8ffc61f507ce66ffb6287e72d4fb2fa6cab48369575e998b6d64162d347c06b03346aacf85351eb6980e0707c0367894a72e5d0fd12f527854553df1f6b145ea7697e1573d9093a7d6ca42723337577c62f12fce686349c461759f7b1d4d8c822190e2e17d6173756d3f4e72dfc814b61d58adac8507efe4c47c6ec5367f76d45512b98434cb3f6c91c75d729f4c5cec96706606ad5717614d686f5c3fe8002c6783c3e6393b97df5ec54a72dfb5233b88da8e4661ec3bed06dd9f5dbb8852fcdd4ff6be5bdd8f9abf9d15589afc202e908b00c27bed16c5b90b2ca2495da99bfa894303a84af292764604721df12403729ae4a7f141c2a392511ddb850e02428945658b44694159d4d9fc72212d65d2cd9006a9aa0c5c871781c758d0611ec53f3afe0440a16c783bf56e7210d883a4afbd3cb08fbc69de9edd836debdaaacf2f25f500b956dce979f35272c0e709830aa71983066de23a963cb3e9062a315ea9815091c84e302c1e443841c92b7e698c6aa6bf1cae563406ec0be03cda6717cdf3cab687ed5a2fe356b872709880f21ca0639c5de150830d4c57de4d7e089466a5c95e2a9131466bf08d6ca23f24db401b395805fac5b38f101b9d34e9d6ef695108acd4d205cc53324148d961a5a744ca5a2528fc47997c698fe075f4b220a071ea1946061567b317e87202d96cb1cfa2738c4d7f8bf28fda24f597d03d994bc0b62a631e51acd89398235f19a4fd10ee6e992fe04864681bc894736aa7f05c594826ce57ea1c0915bc188164bf13656ce2f65dee840cf07b5091888f4e7747a28b5c537b61cf4262f372427a0f3592bb6203280919a6728ad45e6b79239ae31dbff45423a8c87bc14772b084b87eefe3456228540439fecc8cd942ae389cfef8f7b2cad3e286d3f2c37210e4028416510e579e22c37711b602ac263e5ffaeab261aabf237c6195899a7261d94236e7a2a5d24dd0e44a956083fb5c5ad0764568c220330112d833f67a530d8fa10b9d590da0bfb1d6bdbfe4518a5d5675577ae634996c22c1c70415b072ead46d405af4c091411949e98599f8a18eb645a345815715c995659da9925a724ed831fd86aff8d168293982fa496f17239f132cd13c2033183ec6f4ac599c25a03aae1f01daface3c94a0b7601e1dcc9f52c2b396f4a3d729fcda734b3bdf3a1b743074dc26044df71e240005a7b0919aa29efb882a6be47752553eb522de722caf64cf9af3d265fc74620f987beeb93a18c36f96097d5585469d87f6a73d72079199728d0eaed9be7e2e072a771d29491da3d46de516be7eb7948719d9bc72dc827555b6930b4411e4e10e782f48edc75888b2b457e813c2ffb0ef804cb372b772835b0ee73e8b6a95f447abb1517be0151dbc7dba827ba66c6acc2398b31ec31cc8ba76acca5ba028f20dab0924da7bb5944d08d4bb51999d5aaccd4707564b04b3c3c73ee7487beb31ff6db23404f6c0bda8806bc70c544dc5539ab6c472ead83d9d571a648597b386e74c675c923614f6f0544eedc63cebfa6d74b9384bc0d29de1688f79cf3ce9c298b0a5be7c473943a832637be86c9981388dd1d2568974f38c7b3e47a6b9bbf8fd8e3d7f6300f5d32610402b3e68548210af64eb16a9b8c62794c7ee4997930eb82690838a7e7ec8047e38392f13636374b01cf5724e499ece5c006f35728c271cb5ffc448e240ef5e7547ecf79a3a9a3072272c7216516a589006caefac0363df47d7f52e2fc8754e99ce0627399fa9377abeb3060b7ffe259c892d1e1c1e536c19977b71bd39deb56748da6a9304b107bc4683729e8696520f822831e663989b3298db70e03fba20c8ca7fb19dfa054acbbed716ad3d385bf797cec1e1cec03e2a549e3f242cf1c4e8547e9d76caef63df6d1b03a17f035f8c66e5b66836c24cc8c6eb30d9bbafb405880c47e8e070ddca52cd722afc409269e800c317d4946c9e6aad52ec058e46997f90f47ba0dd3a443d45214ebed68d46829a88cb361dcdc55ba353448144adb7e1a81a9ed07d76cc283424d999e719baf68c081c1dfc83c1c0dc65c70d0ffcc3286c477e4a7bd50fc3e272d5b3e3aa048421506ccbd2c8a7cfd6a1811c62b5058d0349f809e5148940aa022e7f242a34406bece50db527eab8bde00d81c219afe6f6506a8683c83e5a703cb41239d2b643e29fdd28de479b30d5083b77fafb1e27c1b3d7ca58b977fc2d721d733aeebba9921ff0134ccbcaaf4b509c8c9f25d07268687ee91adff38e24720ee76a88f214521800dccff6d7ae059c50a8bfa449f79bd4fd8e4e9a9237b372cf503337de906a4b55a79e3bbb120c40dee53d4da458c93854a89e1e2d8a6572a5a9bc7ca3d2b1024e228f0c3a4b72b9b1d5232f17c144cf5bda1b1c3ab4e51450bd8e88fbc841089dbbe5f5820117bbf25207f8b4069819be940f0c1d20032976959c942a09171a9ac268a41f146415efacf5c1e0f7f82622173fb3bad1a20b56c8b1372f93c09a6b87b6b899350133211777c2b01aa15e13a1544565600547be4fd68d80406a405024d824397a529e284e6484f6f8b97e65a99087cd139d377fb0ff6d8d8927461072e32bf6029f2e876ee9e1cbfe5b77c23b9834137b77718ead147c0ae7d4cdba544cfa8a17c83fd1e36fc55e998369fa105b34d4b93f7281af9130f81ed93b8d3b4754f484de9221443bab87c24605da73b2a86998e772dc716c50d0eb9361b2ec8f577a82beb4c6573648d78c1abaf39875a6b59c5572c77d18d18d6635b23abce6b6a6b15385e5c8a8b38340f70ab241218fd5b4a672e154c27f85dd0d78318cc174ffd29b2c77874597d59e3f1606397752a9bfe172af39848daafcd26418e46ee6b6f802384f37b4aeafa2b8044f8aa1310e67517208974a4709923914858e99a25c51396c70d56930ac080962e323a66dd93d8260996a550b2131f5b61f9e67d909b33282602e2b0f4fe1b911db457c44c6fbfb720cd5a75851e60af97bcd2954180abd0368687e1b95c2fba47930cfc5d8b58d72ac89f186c3a754e1fce79319af60562aebac6013e947bdbd9023b4d2db76b06020ecd561f3da371d8a3c559fa0c63e30d20025e4dd46c71973c39f233d2f256ec06495415a5d867e6e00ae9e980b56beb192825147f39602588ae4390f983563d6869fee9e564592162c3b3f1da1bc49b9994be65cbd013c28fc409df119617213b2086bc423bc05432ba246a2787be2f3879c663e3e8d545c200050e51fb41fe11572f384173874b562aed3195ba322602aa1d698d432fc91748f94f870d9728c30ee914c30e1cfe8e01b9ea82204e5ab8493529977e993f115b04b18cea672589b48a32283fa4a8048f85f81819e616013e9dd0ff533ffaa1a69a9a690ad1cbe9fab9668381dac215feb4b310255be72a1d9a40623e4ba298467a4f24a5372cf8ad882ee68ad1fb5ab41e3bbd9bb10391421353f084349725f1e66ab261b720344dec9735624bd6dcb8609117b7418583d23b7558f69df2a3176980fe00c724d1299a879ce6815c667e6c9e19be1047bee508706e4683a66b08c970adada72d43a761fed7316047dfc2795f540c7205c74805e17af5bffbfced92761cce872c8625cbf246f88e181ce39a7807f7265839a00e7cca2e7b175b143ddb10e767233f0d4f52e4b57c7db0ef3b578b25e0445ded0867cda6d6733226a43b2ad87725fd0c688e9dc7270c746e6d0cbc359e5b9c629e4b7bfa30b8dcd68b94087f3723124b03b58be2fb9d517dfd84d88770aa8ace89cdd0394c3185f5c1b593f594e8353dac6cba7ee52a379cb364ee78efa22ddf47d6d4223d2d2da8e98f4269072b132d9305af5812d26ee004fe068428588dde21502872a2a8081f74df1f08e72e6896e81ae1c8b1fce20d5e232a729932d0da1081e10354be73ee32887df56464f98e63b749d3a4e6ebf3e0db5f591cb8d3da77be1f82ef197e9a4d54f22443079e5f94fb34a56a9cc588daa7862c6fdfe46b990ca9678ce0e69f4fdb250426b35a7843c5175776b0972eb3c7cdba9ee0e6a58c6160baa1b4e2344360960c521a4f4cc336e0bceebf15ace94c099f810810d6568ebd43c00da5429e55d19f572727895076fdb2750085b64c377e70b09fd84831f191cc6b1a4c4b079b408075ed55ba86848fb764b43e298e79dbb2b3b3235f1ce22d7416411fccb7098338772c7013c3bf9a9aad1a7455df75a3a6b5f78569822602af15370076b59231788723d672468b1d55776ccb58a1eb7d2edb8597503b714f7fa169e97357f98d62701e57d8771df0faef55140cbf64e72e129e2c74723d2e6150050835aa7f96da5723d984a3b22e95b78f734c74cdcce814f0885ff7c408037f6d0895028d62a894f6e8e2216b0770d29e3b76689e7c73f50586b705cd08a9bc372d3e89a9e8d245015a8fc828122567ad011e63ddcbb73410c9c916188dca694f7f7340facc3b8722cf379681d11a6fdcf92a71b3256d7ef0f825bdf603b8a89b8be9389e12a2072c17be0f1c1378977c0da03b431678b98bf52e7cc2b54fb6c816af70be08fff72db03e60717c26e6de76e3afb51d6d78a0d04797e7055fe63524599d1d9c1bc6aea7f5c13a2e2346e1913ccfada98f17c315f73a34a113d0d39cebdd127b53972fc208857e74906c8dba161f9c92683b97a0f61e7c942481e2622767fb5bbeb7298e5522151f0705cb04a16cff22d94a7a11c429d9acff3a956ff60843281367229df7c44e88d311e0277e2a3297411ed69871285a53b4d526d814848be5ece72306c8b894826d5e924a8fcb3a62fa95aea2048360afa8c76550e02f1d5dd3272670a0d4255cb6c534d7430763493d9da9b981aa1f91eb5a04e3951407534442694b2233e4e7fdb66c5c6c4b71fd9c1f11298f657f118d62cc3c371de095fc46c0b2f31007ef4b9b57ea1ea9035b07370372597055b6ba4924cb387b246dd5c1ab658651cbfc440c1f5d07ac8fa428f15938a480b8ebeb9db71ceb0d397913f72e660f81ec6298c95da4a9117ae2a85d17cfbdc182d5e6eec3186287233f46b21b936dd81cc0be25d180909416ca6125466fc57ff0722294a5455d35d3a56415be666aa357d427aae9dd982b208ba15627736bf0218aca6d8e7f181af26572d52841c66a6477d313a9a5439a92dca119c15e42a8355f16a8327666e7fc912c65000cb5e6ddcbfe9b81ceca280478f7c6f462a59599f5161cbafde5476da6e4672daf594ef0e12126a7cc1aeade2427a58fcefd1dddc67c623d8d8b34d7d137853ad198ee279b0fd463feb0a36ace906d72f5b30b81eb0721c382ee005c0563e721675790822d50f4a891bd46d54d676f513f8c5106a1ede09402c62397a95e34a6e3a284c55ed5825ffdd3f264db3e6d39afe433365c3088e80af344437385a72b365b3ad11ece73f78b27d4559b12badaae820719833a585eb4193be19c2d172745229fcf8c152eb008e1b11fbc09bb2c2b4892d708b256d122c3792427c766b6c51d7656a7bf60b3455a393974e33cb3f70d27ff433cd826f2a40ab2885800632d6a607e2df9bd4ce3fbf71452b5e4cac76d710400cc37ed3e9528d6e023129eeac03a109c917103edff5a11d35d04e207e1a6570cb674c70ab0eab81da3c24ed34b3b8c1c6541d3b4c597ab46c9f256dddbd265db6ba40ddc1a1ea571f0a7204d7ea4ed72f96358ec0e44f50a78168b4247ee250db8752149a76e4e5b912257fa8ea0731c54790cfc1ea40a9ae16137b0030de8797670aaffc32271e4e0d401853f7eefc744624632c821ed21cb2a993c7b3895743b960d48c3f7a390f2b4819dea34357d53cf18f98141d7d03de92b07c49ac62cf4a2a0c658c6acc2b853309d3cecb22b21663a7d440000da887a6a4ed96a6a71fd8bc74fd94b6fb0bca7238e4c74a946967ef958a6e286585df2485f0653a13dfeb8c22d779c86c799871ac90a3d127beedd77a446e054ad5433b8f3c8332ce2bd2310c5a9a97a0116072096f8795620feba1809a07fc17866022d46c96f04a2e776ac88239ed1b63a70eb6ec528a10655927eb7e9b5d82230726b02469417d8bbeb505cc26f322d5546ce86b1ee6b5a1ffdb4f6c2a12167ea8ad8f23019a6090ab42ee9f765e2766ca53eb0d49e7e2811a357e5144c52264106c9bf82a1dc2d0ef52e4726b87ca8d6a7201ccfe06c0924a1ab15df8bd8e86e2f5aa6505d5f9ea12e48d3b4d561f518772800aae05a606eb58b239cbccb42508358cc959cbfaa99b33158f3246454b1972c9a315a050ccab194c56927294e187ea6847161a1a478959e00da5e3435c5772a94b02081e56c579dac0709b91fc8cbcbac66d6ce39619774e3fee073a67631de29647c915ab98f15979baa180bb3852dcf325f3d0f02d72a800eba42f195b6c49907a5a3a1cd834d55453b6025d89e99239a058dc26b60c4fb44021d6ff0b7296d04afce284b962e2c495770f9045cd2abe0c3ce03b38b622b15d5ccc26ff72377600c19b7d7606780834aacc3d7df35ea2e3337b504567c77bef63e41d8572bca12dfb02038390484f40ade5e45beaa17638b79468c697b22cb7d77b92055d7d025cd12d592eb04b38cb85fe76ed7bcf59341ed24a2e09590014f50200581878a54a8030d401d952fd007baca38ee01a0eaa2b362dbe0abe5817362074577221e761ac4619ea430a700e9f1124cd2e37c6a38909ff2227b37ae91d9824917280b292a9cb7136d7c3019ed05864d8909a5ea1df62902b9297baf929a66c044c5646115d15b6ddd83911a13c2477346ebaffb6c006fe6261f1e66095aeef617240b48424025246eff5112db705ecda269e17662ac761a263fd5766c735851e729c0afe2336b341800c385140f040ea63e8d3039342c73ad116648bb791f9603fec80d31dd9544a5be0725cf785c492dd91bbeb9443076a2083bd976204d48272c55bc62d4d897bfbd9ab7109271f61f2e3cc0e2011c24ba5f52b7f228f1e722c062a9cc4e330932093af36a79bb0faa5b3510cd2ad06dfe4f6355e9259fc7372f2ea580265ae26be438dcddfdf2b2eec80d9e0f58a28f5da1fb4a8093f29996afb872d1c40635639520065fde122dfdc254db4314284a488e4295d6f6dee24724bec8577c0b78f99dd94563acb8a5db28dd50d561c7f05f295f033621c073d721ac762b847b288e4e108c1f1e722548c0bdcb03ea6100708db63fe65b0d19e7240f0c17120aff68012064868a6b3ef49794263ee21cb4df8f6a43220755d3a72edb274abb9acf0f3b496f90af4b5a50e7616b959046a5331c79dc085db4a2472f0b4fcaddff33d1a7dffaa24f34e3f3f079042654bf47d2fbcd4dfa5a03bb47288c3084f227b40246845c9d0b82bab344f746d6743f4da910785102c9d80377293b69ad725fbb5d44d673ea7de1581fc43dcd4e835905a5a0a52da5442b49c6e188fd6c89faa5d7107995b1d9689215d509577e2f6970ffdc04b7e934483dd272897318a085b9ebf7567a58a774d3c8a1233ef539335e48740bfdb66501538721618a018250bae1099af2e59d7e4a9ee9854f12c37a2e3180b1e3eedcbf81c724207b7ce98a32760dbf839ca2cc89fd860c2111f79fea5c8fc8596d13c44017249224262d12af0d6c9b3e34924e13ca9fd5ee3a1e22f424338ffced20bfc042d360051622ace07fa1fe92af9e8d19642dded1a9a18871541e0424d1ea790b7724224f1755cc3d7b126763b35ff1dfb50d640bf4752f3cec874355ae2873a617236b6521f4f520103ef2d1f3c40c5b7614e886e384a8a9d1cf2b08a5ea1b5fa72f950f58ec46f9973a6b92533d8626f476bdbc5dd2ab48095b362b65e284fa37207fff6d3ca3fee5470c597da4c637197ca08ff312f70835e6c69382f657c594c50ddf900310c1874ab05f6ef60dc7f56d225e57deba3f02066a1c1c12f8ac6723f5456ef96feed2b91100f08ebb4f7e7085ad349129d2cc168e96b8dad28d3728743cb8024e38c50b7692a461b48a5c6b01c7fa60bfdb5797941ee302b38ab5d0c2973d2187f55186a36a5a5c40b7ec18f41c4c0be7bb9803cf081a8c6170b5e59f462f9af142556e839a12e1f61a334bf340ce8ca6744fec6555595fa48630fd766de7947ce3f9a68fd11030373877ea6ee324f3194664477b99aa66e7a556b780da34f3e7fac9f710fd4be4d7df4debc01dcc9dc222941ae1ffcd9c95985722c380534a13474a772aaa9b90bd2e5ef982dd178569ca67c203b521e90a73d723924e99deb4c97c7713613527e95a46b95f55453cbd1e12ee62bc8b0ea61697294e80a1eebf53b2d0d2adb83fbd7f5bf7e393ab696cc5974d251d8d6c647bd7227e44905c3d8729670b66f9069537fde0d8aaf7afe6b623bc9eff6056f948772b05dd79ffa1f2fb647c3775de517a3bc7bcc1a572979586d9bc5a63df019c8722678ecb9ea8bf99f650cadea0332c062a9bfbbcff531dc928372f9c2017594722c5608da186cabafc7b57231790653fab0df83d27109003e839cbdb7cf6bd713de779d5093c5f5d069769ba8b9281e522e7084807e20a42e143eba88fc199a6a3054aff6c73159adebb782ae642ead51bd14a8c4715a942801aba5416d9af5729a89dff1c66e753a89a522d6b4c8df50b4fb679fc4adb1839d37fc02eb1ad272f511d64e1b7bb5769d9c51692d1447a944f7638b6f42865bc035810a1a679d72ca5916326d5fde44981025c6eb3414ba4853649c69f472fccb30e532624dbb1a230ad83fcb31040f1d0c1d51742aea99746ce60040e27cb8287ac14cb861bc5049d240d8d53cdf8120fd35fb770f6dba405f7bfdc5a44b0f9c6af02f23ffc6722a2645a35fd90603482f51a17f2c665dbaaafcf15ab63c922ace9f5c6c95ae726863a04bf8e77f89395ded7aa4c4f495f523654fd6c7424d057795ba73dbc8726334204feb208c5d496f5cbc9bd7caf8c1b6e950e3d348a3e6e94b8faad4dc7257fbd4412a7d441559dd578992b09f5976a77e5ef1a79241d8f66d12a5c4b41a0ee602880144398374e5fb75d2f1d9598c0ca4a8d319601d9ac82ed26553297297831c5400491664baa8ab65f7e1f47128696d8ba062774f1ffc4e7f6fc09102769f0ba6f43e83dd729b2425497495d1c0f348e7c21efc2538bcb5f6d071637232694110702c3d9ba35432ce52889359707f76342d4992654a42ec232a2b795d793d43e19b496fa7eeedc4dfb3286c519701080e889361432d4d5ef21aba773b3bcdf9a47ccea87b9f2bf26d1db9e1e6b898bf71e44ade70bc61dd53a920a672e02ecfad59e94ee574c62f28dd00b224f135c09ebb6f9487716b770ff0dee672b463de542c850708fc45f98f7a6f996933515738565478df3e4eb81fca30ea72c53ceba4938f4f0d7dce3b050390d804fdd8867f3540e78b572726ce3afbf07254e99867dc6d7cb9883bbc7f8bd4dab64a1504b6c635a05b3d09f1f92341d872f90fc944c36ac0079bd271ca407ed4987c6f8853efc9f9fe3ba70437a1eb50726b7eb1eb02f6daf615c13a7c4102a7d9695b35fdbdb6c58a6705cfbfbe8b417296f4643b3187c81ac3fa85c1dbd433165422717e157fc16e5718879caa39a51c413d1b25902e01c65c9287f0550c609544e5f0c7dbc8551ae4b0b8802ae237238f16d2d7873b31903991a731843d57ff62d6800d83cfb35f599d3e63057ae27299ec3c385e7269061eeb8192ab616a92103da0748fe77ffb809d5b7d7e5ba802bcce269f900c6db4117b87b447ade552b6c3efe6aef5c536e34e6d467665071070a240f71ff4f58b0fd6919d363b3c79818066d22ce3190341906b578a01b672bbfc38be106e46dcca0463061386ee5391081a9923c21250a543356eb943af72a20f036df147307a822b1ab1df490de3d21c59abbfc13d45ac3d2e30afb6941bb7b92d34683abf76d5e72b2beea1e1c82d2cbe4c2c61522aa3231471761d523b1d68c829a2e15adcc7b860c14f0e722f4abc01ac486aa5714e22909ad265336ea3a0499cf4e6a76cf5cc48480a84636e372933ac85ea2f01d737d0c82d936c452c007961df1366a7b6c4dd8a940584a9d0efc8d6189a3953a64560628817dc164a7e0dd2d92000d924692f1ebd7af583a09149b195055a24b37820c04bd1ce418b2255fc0eedd1510045eec02a5a2dafac9b2f0a0a2871defc4358929028d2728f3baaa0910fbd6ce0e30b5027d00c8804e15c25207196776bc0a25445647212e816efab2c3485c26b6d7adf5cc9b1dcc4a96bfe95b56b2e5cf4969dd296a07285818c0896fcd137ab8ba450b773ff17078cb8cfcbc3eacc790ba30191b9bc0ad10b32463b5ccd3db2e2c8b90838d6dbe8b30716223bd3df62bb87914d32813ea873a85d81a41aa397a7f9ea449fc98cf2d232115b5973cc672f32782052ef6786fc4788099d50712b1bd762cc4b9665ce5abb23adcea697ca52498d691ff428adeab829318cafb0e85d5c8af1e2d7b1de779cb4b32e48d27cec43dba8a39006e59778de4432505d38065e565e4252305756061fbb654133f70e4b710dfb1f72b407acea9848c712de5ccb9dc5bf8c45358474e5716e692ccd0f09579326ff722259952c6ba823a0edc56bf3357e76cd652abd26c7fa418d78a602b9eb8f6a27902515c84fa5c486baec7cc205fd4fac7e941ff1435c0a6d3bc1a4c3346c35725f47b238030a7a133e23cacdb7057240688e1b17ca3204afc4a38caaddde250c013584924cd24f76297ae83db5b0a4bf7442084348a6a20376760376e98bb357ce99fd751101df7dcc9c557577218e25f47d0a80db4b0872d5d6db693ca66663159e8a8cb8e6876f98da8c4113557dddd229c465f5720328ed83bcc7f00283265f2a94f3b551f775b098fed167ff8122eb17f828cbb9dc898c7d664083c14a72c4e20e79bf48ac42fc2d4a9aba41f7f624459749244f113e06a44d7937e40e6442415309c5ff4e678e9ecd9d412747c70f8333201ee03ced474833262f8474398272bc7b8a05856365dfe58aae3b80d3babaecf2e450ef8b5366daf89b781805eb7dc32012ae9ada14ce189b742a6ef66045a2ab1cbf63ffe933aec1449565723e3447d88532491ff9604de3e53801a751d458dcca0f1b0c5c2e8782d752f47249604d8d103f6b223699f2bdd18769fd563e4f000a7e3feff55883b334737b72213ba3e51f4d27649ec546c890abb7517bb0a9a01fc61498ada25a56e01b7c578a449391e59af962ae9727d713787ce54276e29e0c108ef703eaadca0dc2da40014eebbe3470bd0611ea22b60d2ca7bd5c50cd89fae1658c3110ef075f4ffc729e71cffd57aa8d9c62f3f95a592597131c2503709d5aa3c1d31d3fd4ebf8d606ce2ffdb9bbfe5fa8b6d4596bf78adf707fad686ecf1e6436107f594be3e22d72e668eb40e5e315920925b3bef64781edea15f2736fbf5f1afac252c6ddb0b168fa197cbf49a5e8219f1fecdf9b57f7c6999f18d53f205bb55ab1abceb3efb61307a14709d030edb13c41ef9863f88b4d1917dd42f68ec826db180784a0754d72058ed883aaea1abd11fe45c1620cfab86ce8b873cc9db8cf7cd9d9407fb870722fb487ea8982a6fd1c312e07f5d27d60ad0d1f037731a2e82062f9091f55b27235fff6bee5e975872d497f7a430771db1f5d71135215a8788efb076c158c05723f1b27add1bc96c0c10eae765f6a061d0acb084ed3199b18ce6a9c4c231c8072f86a3da866bcdbc08e7eb45788b0ea38311e382e309d86cba361855a6511f272fd815c48ff43ae88d43fec70e26e6c5c7d0a49d7ed36cbb5007b41ebb9a78772151f0f7f5c6f3ce36b5b289484f8e126dd5c03c99c49d025d99c74c7cf8505269ea45966d40d5c663ff025938e9de7fbf95f2612dede5f9bd56540fdbea68715cab37c2c8c51111ba04314b31fb36924aa312695523a5bed2f7fe5ae7f4d4a12f26879d5fda5e5f30dea1b0e477933a9a8ec18c35c6fbcf148293eedb5c3cc7230c546034354e76ce5fb9513c17826efe07608d4060b2f84b9ca51781ae8b730b9bd45c52c84d7eb509cb91cf4c49aa935ac08245cc13612c9ef85343b51f56647aa30feee17e1d4f2cbfb74db44fa64aaaf5dd48117442c97509235022a0a52ea7321dec0678e089b0da8955e4373766453a48d77f341ae63c1cfef7911773b9f59aac5148cf79ab276352a8348c62b3cab34b64ae68d4f582775d0b7b6ea721f58233a3e36a032794cce6b533fc361e1787d4f08e71419b8dd2fc238029f287935750163b49d5172e162346247f318e2cd7eed9ddecfd088c11c0024ffb4251bcfc22132528fe8f3fe76273d02e8083a882db29c73ac74617b10f902580a7224e23503368a0a090b262b7281f925367904a2a5251dd0efd0b9c9a11659815a120d8b4a0e858b14abe79dd58fae56316f1b736a067577f3169e6b8949d0c47234cf7482fd931825899c4b8440dfcfb0709b0e9e5900f6a481af1107db902011d76abbcf2371dd4dd5c8a3b24e73ee88ec0cc219d3bba3420afffa97dc010f6e95cd9de6c9bf67628ca00c231ca7657a5833ac3bb05a781ccd6185ae1919937215dc54f68c621d49a47d4b177c3ea4f78b77fa16400ef4128fde967ce9b07972fa90debeb5cbb4e1fb3236b92a2bbb949c736a7f71aee9c435a6118debfa1147a1986a00f4b44a9dd36b09b16368153dac35a3283aaa8d4096f967f5ef25b972981065d63cc2d6979fd8d20fd088997096347a62912d605d0f4916309be40372eefe0e3b6173dcf1ac795abc1ec633ac151e7719bbb773cec4f3737c219ad27205525b3ccc3255b1d526fe474e1814c40c52414d894470481c8f6e80443ec7533685fa6c007042c9fbb56a2ab3a7817e39f52b4056a9bc5ef055bb08cb373c72106cd45dc7240f2a68189c39a17babcb2bf84ecbc50170b913d26a80aac82b2ec55914c8ba57266244154dc2fb8417becb08aa6fb02ea2767d46f127c7818f3819835fe89b4fae8c7be3f6e856f0e8ae4fafc104db863cff479006a41e8be33b029bf97ec7847e7f5618e1c340c5b720393bb47d07dda122c578e0c34fff1e10081381df34fa35ea4f16472d30cd703d47540893ffaa28907d257e576bd9fa08c43793e9a9abf60a07d46d7239bacba0567ae16b521053ef3feb4a4a411dc3724e79da4f4457d6c7ef0b3bf4bb04bc24429719abb8f7574cc27f24aeae5c7b72ff2fae98d1948048cd0217ecaab2c5144efa5fbb5c0f4ebf690e710ce1a81572bb9af8df0a3c5d310f98ebf6eb794d1752f6523d5d54c1812d8d07814e5c9d7265ad9418d902c46fd48cef25e07d9ed88c34fb817d12080725a3273fa3f776721b0a3011685c6170d20f0de2dcda729c42bad14511e7d7a0b363fe936c31e950e2b33a7123dea7c23e90c4bd0eab492fde27f73092192efff506e7300cb6ed72861955ddafd2e308a57f897379c180fd31ba0e3118ea337191d2a9b39e4fac72da28f5881f7a6dbdd0538b7fa903ead1e1666219d91e76434d5d2f014392167217a271039824643ca4227218a93b31d47062b24fbb5dba8b9dc6e343621c0e72aa7cd44d4d3f1214a9b7dc3338efdf7b46c140fe23c74dd6bb3f23178d026736241f878c361cda2c413a04321d86ad10f03e3a33ac69d72c673cdec036895a38ea48be14ee5b322b5f32b643d821ae4d9ebe83d6cf6da428e1059a90f8fca67268b2d4a2e343ac53003a8167e21be83bdbacf2794d893de7bcfed24b88136b72299260aeabe9559b2e08e6f9bd754b35ff38ead6600812ed62a24d33e7f39b1d78325ba0edca7090e64935c4f293edb0487210ee5fc9d9d0f2694e036badde72cadc06c55920256b141306cc06c036b58174ae48c20b0af9391f1a412a826772ab0d28756ffe0d4f4aa28de6ae1f1b0956856d234527c207ed808b9df4fe4872162dc07622212753ed085c6e0f5af660cf0fc936d59a27e794b27cc5a6e12733303d7612c376cbdfd4508c88c97ec9f1f19be27aa7a728d12630a1ef4d829623950cf3f2a69e815678886734b8fdd30a5de102e6ffa64b74e06670bf7434d8721857397e9459c41675b43d9f7333cb5968c9ac068c0407fef8972ba2554a4a5b3cf93c70150cea4aec7901b4f64d55fbb45484c7ef28af2c9eb47e7a9f5e3172534b56ea205bef8d487c195d28e0c256a80622536482d51a7fae3c4c4eed717292d3c9a00a14bb631afea4bc9113fe276f968f489a6cc15d806a6fb91608f71a943bf8676cc32ef948def2477da60f4e24bc14beec563374fae411ab950d8046113730324a7f19e0617552abeeb54ad9487fac3b3307cdba0dba86c44157f96076dc9c741f3f2852ea263e0a6db8e431c25e91280a6ecab3dd12dec50dfabf72d83b612513dfde3ad1671ade01e7c8001ab3eac2ea29a0ba33b775294039396b981e43390bea58cdb7a73ba07146ab6566f960445230aca239286bde7c2883728fcc95fde68ac121f03c6771a7e37b6f81fb243a38737e2c9b2647317ee2982679a215bdd5fd5558e8741e4cd350829429c4e05ed58e198cd813eaa229b3677225c9e54fc8fdf6eb97d49d9c27b8699bebc4e160519ecd00d2ce83bbb472fa72ee784e79af25b582cb472016988211eda4f19c087b3fcbe43b07cadb8f1d4372437603925eba8f71eb617d0bcd1fb7e6fa4176ad7caf4a73b7ba077da0c28b13b22b4a0d0b5df6601436707838b6502fbcacf99a325b43e9b3421370d5ac83728dae855ca18738115bb59184073a5aed2aa0afa04f3191d7aeb0da717773f172eb1852166370694731b22f9fec8a13108079d7d6c5c951f91939908c9d32fd72f5a8ffdf5aa327930677d1eebd0f0dd5aa03af5cfddd673fb06073175a26f90911d493062c352d0e7b95a56b26006ec10ee1d34aef8b5991504fdc555bc2f872a07e4cf5f8bb835a0681092f76801b1cc429bc249b8e27c86abe13d9617ce0201df97689ce46e8750ce2b7d490433185f8479aa68b241ae2184b3cbe2e9d6970331c67ec4c65bcf3bcbed00c7ecf82b9d14522bdef065666afb75dd50359ac562275ba798af0822a46d861f957407f4d2cb641f5465c8de38f4d79318e3e8f432c59b3ad96f907c62916834b540b6a94adde1daf56279c26ada33502de22d560f2535c18a5209684176cc5e9065208223c854386460f43d482b9321179c03772de339cbcaefdd22d26ed27de0a6358522892bde516fa2e318ef66427fec0d1591ac5ca30d05762f1c9df1dd5aeeb0da9260be55ea5e9224e84998b7ef5fa87724161f076dd35eec4ddd87de7eddedac5e56d5890f60b81a4289319889e846872232d0cea9db10624fdb1b3f4ca45e45337c409913fdffe3828b714f7dddaed0e76015b726e3408fac3e9727e567111ccb4d5f0e3f0f7f62a189cf87fff1f7372ac7dc043638164e9d1943868ca7c5c3dc199e25940e7fc089b8d1dcd6fa07a72ab52666169f058968274b0761b13148e1696161d0faea656880a61fdc28c4772a83674f687ebb0c9d04d3d6cccfebc8c9da07873b02ec715c6687c6ca1085943562211e8283fdcbcb26e3c99c9dfb6bd89ff8e500bb6b4d6fdcdcaaaf738c8728721541cb8355ca63e8941c8b55a5f44ee761d03d0cf122ee5c45a3da879d46200025ca70d2f5937cbc326f710890b72a636e298d5cd6f994119469e7abe6a7202e10d8c12a67073897ddaa4c4af84fb3d053a623a00dd90f5957532cb80e672a02addef98099ed325d35870e9aba7cede2b1662f2794eaf541e990e22fde27271f926b661d93471809356144c409c8191b57cb2896d8b2b072466fd0bfb5872578fdca75b5e9929ad7e54b5786e0687d83def21f7b8f66195ad4cb540063272a318f9958747bfc80f882ad226cf7d3c987ff84d03feff61da710002dfc59972ce205f5c31ac047f777dabba568f9d2e5d666af1b8acbd530cc2903a71c37f11faa8a2dfbd9ab242bca271b8a59b4c00bb2b0f35ae6135dd4f60182dac28f6131601a91eaee974795d3c927f0b5d8911500a15689579bab8205a140e17901572f9721437b138687e813227e9c20c4984be213f570b76a0c90898cdd9aee2a7722057848594fa5546dd1988531a19afbd122906392e16c2a853f4f769f3a12772232f333e56aae564c334a3af57332d3f28790cba539223eee739d4d334f7ef6c38abbd310d74bf5756a64b49e13149d089f871070d6b3422059900bbb5e98b43f76e5ddae45e532a50f65081089e636180cdaadec1d0a1fe26e5c3f22e46a6726104adf399584fdf7d8462f6f655bef43db092ee7565f207b52c7e666d2cb5727a3d061bcf5428defb0c09da1b49cffc064de9ff0123ec63e679fcd113e9361dba13e91972a184178a33ed931a77ffbd1170d80139877d237aa80bfd93230c6f6854ef641a7dc768d589f931b64361d3fdb4852c297317c6de92d81e316542721ba3d9840b4795150412b6713b5cad35f812cede48d00de0fd669e2a49947d22504954f894b290761b9fdda3137ea5fb05c26d5e47710e1a9bc5a18394e12e3bbdbd9485b44747d8d19c7d7495a270507fa6866757571f956088c3ab510d44725f39230db46e67327d6bb6f64f1ca9b2e35d4cdb64104a67c7b4c0ce06790924cb44cce872a4f94f016f5d31ffb5dac365cf0d30e69b3087990d4f32f9fde64bb43358e7df8639f5715a125109c48885a52e798e748788d8d46cd3a29ac7f672d9eed87333300f1883c4cd50c901ed09cc3c24bb31dce6b4329c9620e340077217fc083835e4c5552abd4012f01a638ce4981712a32222f6a0277a950b684c3382db79ea1cc03d41f433a3f26da06adeebad0acc735f40144b2a28e24037c772d07e683ca33c6463a435c02ed6c986342464d301c0ff5673f7721eebaefa54722e7456df266a1ec6ad3ade8859113b303df53973bbccdc28a24f7f62ee415106635449478557a25f2eb6d6adb2f34f8718fe5ebb86cbd2c766bf8e53e7cb9972fca74c82bd1df79a1a40e11ad438900901d4b465b76de961961f899b6173e77261af172bc481fa84b61ee6baa133b0cc7b225bfccdf0e4335f7aa1ed09b7ef72fe1ddf01f5046c5e8e1fe4fa21324a8324f29deef656e415bf2b10ba6aa6063914f54a1ad33c3ddc70f1a550f9464dbfa49298f377af7b420646a6f0435b2e72afbea18a06db44b6ae014cda5336707b29d5b822dbf510df356583373df13a722e29bef589da6dac3b2d002ca2a6e5ef66cc97a3cf1c00b0647579fd4a6557534093a1f23f65bcc1ca85b08051ebcadf27c55a51a467463a74567681332ebe7240f0d2714debb199862a2b9698c9964023f70a83549e000ba30704cd0c1cd972e0c4a1c4ffa8bb4514863f3f0067f100f138d1dc7a88af0c5945d574fd8ff734dd0bc84f64146cc5b92185f3586f68764d3b582ed7173210c4008b3dab99fd7209b3b6f2ad82cb5a61ff40f3cba3f73181fd60de25cd85c9229b56f02e1b9e72dd5251bbddfcd02af5af9d2ab496b35744fcd24e7ebed6a798d17f33a3362e72056351eebda5429d58570ebb77bd28e48829cce94dd0d3a0ffc710614caed02e0b5d345d28cc0bb13191b807d0c401b16086daf6efd701214d9c0b84d6c4e85deb948599df869f4915a867f2f043c1ef689cffd7dff956153c1f238871b10d726a08146e5d017c0e2607af653309a26f01cfc29167648b41b023bb897f9c94129735b2eb1dfdce185ae99bf9cb205f8cd4dfa70f846149bceb2ffa2d6e5ff05f97fdb703910843bd258f818b7b887ee18112d473fe50b969162992726b187272cff74731b2b9c8d83b10113c58baf847a70e0295e5d961b5b74ec34966d7b97291f8e2d18ccd83b04bbbd439d3464a8bd8524ba3f1a5cd430e5c5fe76ce0ce726fb80416b1593def93a530add3b4eb9fb978211c8be46a4f49f3dea5c8dcd172cb7c6d338834e7523b505ede006e4ca29f7f4ada960c5e9ea7f9f855b322477281a06f2daa24141d232aa569276eb86ea845c5ebc6b9b622a2cb546ab2855072c63eeec3c305c40a8b5c090209af9abe26bcee1e49e2afb98a79c197bd904a72beff15f7c91a647defbb95914146ad3c0c1dd5bd5f7c5efe5002308d2b6a757289973f8cb2e52b70c73517cda1eb0f59d1334c329e18b95c6a6050935e7d3c72750005aadef74386239b839e0d9c6423b497bf3034f2b737d2bb7b48a6e2b1720b8941bc68f9effc2b99c4d6bdc48c2c911ea05d6d0327372f298e3757530f72018cae2e7602dfe4e0541ec32937a7cda41c9f0c35147a203ad13e321b4a6172e68ef89041df7e2b99e037348f5d756c2b902d273f3994f9d333e5d0e7ee6972beab94b70a47e211634d21283406fb4180880cd9056a4df5510b34fd10ec2e5a4066d21277ffc7330d36c0c6673ffc34de24bf483bfc9c34c75ec83da778f33784f583609fe0ec3e028c70b088b7cf304a04f20bf16cbe707a4304bde1f6e9720a88c35794dff537a726ed9735753dd62c6aef3e6128df4a3114753d3d874f7200e2c029f3f5df1d0cb25a182a40ed798f2c53472291433bc8b9c6c5657a4a72e4c2e8ef7f5160b3170cdcdedbcda5e8d249a071d69e4c5e609d9ecf33f05d61ccd927534d52893c1d161336c13391179a3b1c0ab7aef94f3a4793e672b1e4700485f90a16937a686d3756feb2fe956c35c8dc0c99c555e494170894ed41da3b7a95c3c87f71452aa27b7e73500e6115c74ed7e003567045d7f80ebae2f5e67262c4232a1a200d551c83c719842a98901fa28a4b99e2f4bd2b3247707b1b2f72c51da950e6405a8bf2898f3cece682b416394a1e32359e6e0c0f7499f9d34a7207b22a34b561f1aee0e0e93db32fa14fa63dbab09433d621d0aec0c84e01871b4056fd463bd0475e46f5a4f8ba69b5580854cc294bc120ed6417e043609cc072dbacbd8e35d1c19856f273e650d3bccbc0ec43627d50589a7e73e578b549f5725f9eb39b61e419740977ada24423c1a29cc6b11c8a57ec1d47affded94943b0076f8eb7989ea4b595bf993eb77de9d39ef97676cf486c92831ca7715e1ea6d13808cf3a21eb3d991a4769f313df17db73726f24e9023d2a0b0d8230078595272635c171b05936e751c1a57931b85644f21ec00f570f4bf5df694e2fc1eca697295913a0f64d00dff59a80c04c4f913e58e8d8b05d195defc82d3a6cd55b58c72cc6a9e836612c0047764f5d816ac112f0db4e9e9d342a7a6df325a1b6356cd3c7a9c068431e1ac9f07a0897b81674a9e75203026eae2f26c811313b07aacb9525d86e42b98e54dfc5568fe01f42d90da0be3114c09125311d926e8c19b82fb720f90791e7c12b5257065275180f1d4e1342f486d9b35d7f32c061d4935ed652054a8739f6aa424b1a5522955eddd8081917f52e77d6678524b38646377ab91509e80a1884f97af82bb6f3de75d8cda03a6d3451b4d45ad3bba2da272603351725112844a455fadb53200c945bcdd6f130ca2a11a1c5ff966b076c6e5415b8414d337b96a8476c18beb958e406db21b8f3a635056cfd09ac641de55045fe47b72639e3f959686847f396c01e23a35e297943f575a296cabbcd9799388aae653721ee89e62f2c1bff341c2dc1cddd45705919f4ee561fa81bd0bdc8b455b8ed94fa3a638e51324f5203fbeb737010f20793b405e529b6f4de9c555dd3f469d526d50a4cbc68d96a745fcbb886fd7e35c394216ce0013b8dd6a490a9d63a9c788588785e64f06fca76b6cd0aa3623950277ac61f6e6767af1f951c9d1b73fb84706d5a94688695ad8a200515a8c7391d444d065414a8200cdacd64fed8a47904672ccabcffda872ca29762f655c0b2ebbe0f9b9209e580b862c190e3490011f3b72d334326aeadc87970bf055accccc1adc0f8edb247443b57d71a0720f005646726e2efa666ff1a6a416747325641f7b91845baf244de1f8aff0326c3098fc954da6fbc970880c79a67baa38027b2a64766b0dd2b392eb090335db4bf24aff1b72a9eaf08a242b9b370b4987ec0b17d70e4d5a28badf55b413cd29171027076c0130211a1ca3696456b7f1ae8466dabed707af9c129912c4f9f5b7818bb1891472cb4849a9d6528cbcac36b14cadb7b57eb8d641d48314af220d79cb6267c13f358b4ec98141c1cb3f843c2d0e7bf277f454dde9c3f52b3ef5f2fdd48b3df7b0499aa159c934e876a5af5775c9b53ba68e47ad8c63376c2851ca0f48ab54a3fb723431ed676bcb86f3d41b2cbc3ac00d0c512ff00059751349ec1ef12e7fd5f472ec0a252efed4725bb1669ec4e6fba94163ae05ece0bd9cec4c5733f8e41cfe3f93fe05691656fb4efd219539b9e3134f4748c6f8c55158dfd06d0c0cb8ea82724bcc060e82221e311cc11b9d7ebd279738fc0917650b8801f681be938d4c4922f6c6a661c53c9097d10e3333626c9fc73a8fe810923316e450d9e60bd07f91726da80b8fbbda77ee90492b49369069b3f880ae43bd49fcc41e4e6e104739fa724f77da943f20ef1e0ca2f36b1ffbf4e5b8138f69935f1aecd909b89b315d8a665f58b68e02d6feab387d99a2793a8d8b7ddd07761bd3e48114c0baf561b72e7246e9cc76b52a321145ed2622060b6e97f13dc6709dcbb7d00beaa56e29e9cd7223be2a8c4115ec8df1d7b1f3c659b7c68872f457cef4273fd33d43d7a27cb843fcc5904c978cef3c956963a4b428544f13bb8420f63e2b2946866654c73f59389b31393f5a617ee764bf1de62e6834c6e2faf4afebdefbb612829dee18e97b7260eab6c03aceb0bc7da52346dddfd4692f8c9c2fb1dacd7689abbbf400cfc47252e6340c3c09f9826d943a5ea995f1e98b01bfb7a1e98007ea8262c92608cc2d0f1a8dfed98319e34ca0d4793c22666ce56d546e54e5b9a7ba0cf720226e7172e07c221b0791643f4ebd0ae9a217b11016ce2d0d89a05025dad14ae4d6bc7f726fd0cd5db8c1dc23a8e766487e5ebd7e47bf9b092df3ee31434ddbcc2d3a297214754c1fd5faa6907d5a4de00c5059c6eea6506b64c99c976fba8f4bc89cfd72416ccb32f7f7eeec5d2534f3420006c4f75b20ea41536d19ec014f8d88b0d827297fb022858d63b38815cb13e972bbcd892f7ea8b69c12a246e08f6960d68a728ff35dafac71b599f45c763ca4538c28eac4e1eb694f2e7ffb438c0cedd94f7222874c9ebe64ee359d6271f6ce9d50db622c60ba895108270620e1983a516772c5b49040bf38ca8f5347006467ade07a1e98047bbf840aad55d5263412ee2172900d2b09f783bf2507f0d68eb975e3d76856125fcac8902d010bcc16730c30720bd959ebd89edb389d3111ed54d7d7d379e6f6825d4272d38ae0c8296242a6729e163ad02a6cc50f139cdbdb910cc3dcfbd789120aa9569d9341bf92fad2fc7203b9104bcfda5e05f27cd78806500097c3dadfdf721ce05a01fe3c3695207472eb6ad31583e250c321c890b90347fbac1c212067b89ba31f4600aeafdd7d8f158d28cfd38605a423c644b305171b79c306acd138581d61f8e02c273a0f96d6724b38b46cbfd6cbd6e7d65f6842ccc3dc9a1e1995a4885d8a8784557105ec5e1e2df07237f696ac905e37914cfcd5cf5d6bf1888528459bc3e2d8bee615359172847c06ccc73ff22888d3247263689d1b3429bc7e3d5a1357de47c6d3e153bd4017fdac568eb7966c3a26684473bb0267c70777d083c5262168ecf697001cdb18373adc89655f9575c489a9bc3338ace0f076359d074cb596e9af12ffddd69f614b3addd3b68e9870e29dde2ca26de376f55f291001b749e18d8d99e6dc2fc3720c36ed757a113381d977f61ab24d57fa3c6f73a50d1acb21aa0414db6b4f3450410fb0957d5aeecae22b41f6feefb2e1b3d8e59d714b2d13a97750b6ff558b22eb450de0676201ef0ccaf693b74ca3ed99b0f752f6ed886ddd7535f8d741f372e06988b1b49cb82cacaca733067bbed57fbc9c8a28e60d1d769873bce85f40478c5dbad18e1dc5b7be095528bd75de48f0dc7df141caa030df3a5908886f7b28479ca1d88038af0f1fd99e9e22e3b8d5becd70b63582623efb70e9430d2bd772177d2e1ac8be5d8099cd1694aa6175eaed039e04eea98307097b622797dbc2108f56774eb869bd522b3784bf063a4468c1e3906bb905ab7a6622e7e44a97b521a1538c0c8a1afb8c1a6bc6bea699b9dda73fb7499df622e455a895a9db91691c95b01fb4c655e091d9c9e1c7cf389385e4b1ac98bf3d9e93ead4f3a09d17696511168714eedb4990f9587cbc9e4b74368ecfa3af9e968395f52f85a65b7e9072c38f62068a2ab165b230856132d83f26a74a37de6168d63d9691fe2d37ed4e35f188ce7b5c9af4030f93c3057223501017a3dd49ff4467865d5b7489ee3ad772dd2165cf0911b8f2874201b94317f8d3d3d3f89c4a1597ab3702e9107541e358f029e562f6362b407df1cf4852311d422e7adffb8f9faefd487854451221d757a69a3f65df976c6baf023357b2495fd1791571feb599db2f3c55372024103d26ec7cd07524fe1bb2e6725108f54c3f5b49d8204dd6e27c7baa1e6eaf70d3bb497ff89fac040ea2b25c474cfadc5f791d26da943b74a4a3385211fa473fe1225b90fe7bc16882ed92ededf87cc48415e03cc541378eec85df7950a510e51bf77246b04a61ac058a4c921b69c0b97ca9a70025a06bd92d7e2967b4813d14407972984d295fbb7679ca3b8cc5d726c74b6a3d184f72db547641b76aa82bff2bd5725b6b6b4e31416cb49d195e633ddad2694960977e61e837ff04411d8ef6d4cd72bd279db7495b00cb71a4aac1e7a491c38d45197b62fd9c4062f0ac57215137725a2666bc8f59a6fbd61a46bdf1310b90b73b4016a316cc2780c048a6183fff645d7c44654406331e8ece656196a57dc81c222ae4bbf46cf1c86b44573a568f53a46e84b7fadca98c591daf0c2218e043b383802ea5e77f616332d55ccd67ec72660a2b6482f1e19fde94b343d8b9f4bfdebaf9e7eda0a7cb33b8ba815847110f142861b63f3aec533393a8fd159f22110255149a64a73f76d7ea66697f7168090be2fca36ed8e1e4b3c1cd99a752a861803e24c6926aca81058de864891c515cf491b9c0589fa6b9e0d7bb1d2262ab379e1e4b3c12d07197fd692934ccd3f272656cfe870fb0b5df6aab86904fcbcc6b94c6a53c3b226a7a361c5a0253b63a72993db84ceaa9e7bf63212cea11e441cc3b41c19f135746aaef912b13e0b93047cd500ed5b51428a875a8f0608559ad782777bd727a0b9e40837ee043390e997271ea2522cd1e838ad892761f4053e0e69e1f1c7267728d450de35ecca7862b6328e6b7d9cf18ab30c8a1734b493d26a1b4c1ac06f593f87cee03f677fac69f726e8255c9a09a0eda7ea5c9c9b75969e464c8fe7cb8f1ac9ce4d94a3e958c767263aaa05b7a39a2cdf489f569c832b4965b183a787cec6e184eb93906da49e46cb6eb27396be81cef1863c32aa863682a6836f91ec3f653c8c9484441da26fa725ffaa9551e580d04f94065c4d06faaebf4fd3f429c3c1c5e796bc3afd3ade772367be2c101dc0c8945b78caa06d3837cedae46e5e8cbce7bcf4ee10dc32afa725744e960e08244123ddfcd99f5721073eecf9750a07db58bacb40fc5b391ab49b13e5005ef4c7a9f8337b8efb4e1a3ec1282349cf88b62aead662b631cd53d72dbd00b47c703a2e6a3f863cfc88bb18008086d4aef9fc395f44f86335ec9ab7258c080967e05d4db6ef0d38c09c8e12f4b5629682100cd3bd168d87935981572e1c34888eec793ac1259c33f614b8aa033eba9e8bab08e29d14c77a3229a817233f509596c38a74d96b1e72d1c3b6c87e6eb81cbb4845074713f1272f92c237212952b0a16f6a72e2a6d573f9d47dbd138062129afef391da2bc7b20668d2c72e634ed2785c9b4025622c3c79d92608b8d4ac52483295e6ea9509b971e977a12a0b0c065d93d53694edf54c22f1662adf89751dde5ab260abd2da6d7f16588467630a2312994c4eacb11cefecfbdddca926dd6606681a9f71a72e77a12c5ab1c3cdd8ad782423fb0bb21111e9f31ffa64b028f43f3cd650f7bf9c4f3ce845572277b1d79f56457a6de78c1b633e072f6262e9493d90fa9e5ce89574e1a6c9068929e7c6818565d371c2608528b7fc9a97a278ac56b73af79d90fe5895eeefb722364258d677ffbf8bcca2d7aca0da6a736c666f183f627c4eadab5f4222f4872a6e35c47810d82b3a46244b8e67d33f29ee1017ddd8603ce2d8861ebbb140c7275859d18f8519728a21ca4ce4513a0bc18cfc00f91ad29a618acb9d332d37e72dfea513f12210ea785464c67227e037734ab4d7d38d364e6efd58b58d29a280b7061d110e3714396640260fe3c1fb75de5339ee8006a8e74984ad8e13b7f5572fab73a6ff80fbf61c9eaa2e1adcecd8bdaa7155ae8e7e9f6bb11a106471a9d729c364a8db11b309dc37147d4955e9991b8821d5362f8c18872652c58f6d71e722b329911b8c921aa2e679406de5e19e317b7f5e7a72238e342b94ba6d324897259df52abb1c37d7365922404fef8a2cccef2c637b026aa5135c02469fb685c72f69e817544d875c7766143e330b6dcc199a1ba74019c663e7cba3d0e5dcb7a72aff1f7522120511f2a2ef91abf72f7f17cfa044844484f778b97e6219f1b0e078b6fee835d5238285be34810ea4e76883cb905b4cf20bfe87f7c24c15d938c2fe4b26f7c92b22e41bfffa51c5d6c33340df2d764e09826b0516342436269133c964e1a1daf14b76a2685ec0b1345c1218d73609c7153f62eaea3fa9583cf6066a7f37325e2ed33148a21322e559f722c5801b3e5a448e9e668b7666ef8f74311bbc925a250d251ab172b9d4c59471586e950c8f5688150cfa6b4561ce9d5ac64b92754a86e0327d0f46cd7b8a16df2da551cffe509579f8db5581f7d7fda1172da003c37670fcfa48d100a018dbcb32394b180ab9f42603ce4e5ed277b075a5fea27937628e1c7e666cac90f2e8195e08dbc8cedff0b88fa523e2d657d97bf3f4c38db18032b0ca1a7d3eb593aef2d52b90c12e2285c3afea09d4e1365b7ff48b288f0fa12c684abfc99afdedbc0811e8902a4da30409628b40afb45dd3f447265d62203960aa5c48f5f4197f47e25b35a87233dde2ae0fee7285bc7ddee5472e0300eb75a6ad61a219310ddb110bf0b79ec0725c4b6c27f719744c90e1450722420e2c3a6bdf05fc99b9435b1a247637cd109de0786aed529a8560424432d72b90b3a974c6cf3c94fd9344d23211ba54be25541e2050889b580d8b9c685de5cf048f169a6afdd775dd846061c8d76519c03dc0f4926c618c3f51d6966862072fdeb1342b40e0f8c2744b88b4fedb8f4a441115f8cff6131bcc4db60e13ac872cd56578dafc8a9c2f3e44f7bd7925a109f601eb78f4d0daf713906334ccdb472fc758f14403ce270e334c2a5a60dd186320bba318b987681f9397e2e28cb7d72455386f66398a99db2efcf27c3c9115e581bcc7e7894f437949b840f6908e5722de5ad669c146f6ba52c9bec7bd64bb80213f33b8e766d46597ec720caa33d728c59812d2e7ccf4e5bb8984d612f976d65be7d7d180cb6ca62207fb2116dd37202a3b9c65608efeb8232c948d7944fa18bf6143600dced25205b4ebd488bb739c50a679731cd1e1399aedef8f7367f9b467a954829c199d7529fc86624d4d21d1998c62772c4866bb4ae846295c956bd86028cae3f8c9bea7f88ded436cd8072030cefa10b03acaeaf123bf23b0bfe952d8a00186ee8f93a1950a3c3a69d4213c0e975278bfcc8740b35d81ae96281aa1a18c41854e2faa05bd944ef0798ec721a2f3a137191c05485db27f5b386610cc2f412294bad035918de25cdb16df572b2f6e2c2795f4fe2d7b323d8802b1b53629aa4ad8dfe3554f14b9a455786df7291473b72592fe59aeea59b37a7019afccbc5d1ab8343b262d3f19715830a420e5642ce969751a2af9f257753eb4d4a46f8e68312d33107eefff6603c3cbd5b72a56aa04c742dc1a33d2dfb3f08b62ae56b9844cbbb54799b9ff1832cbd6731722634bd62cd301469a8e83d4a84eada7b28a3f1212be5b0cf863e9fb031bd7772a98ba2863e4c868b39801a4a62936a00d6d6999395f3f09636263eedb4cd316a2ae6ef9554cdd0b0ea8489860ff50a33802604a518fa0699bf470a979c17d872b8dbc762ee659037bb2f69a3eded25025816d8ed5a480ea420d5c4efe4540072da3d73935aa98fb91ce3fee728d4f7269adeb88be4f8aad6c5cdc7a2817eca5556efd83178cd2fc27bbe8bc4a92dcb61bc1b20af32660730daf310894dfde37293d0a624f6845d4b7f11a5c0be730111a0a1646a6df8e3b0cdcbb56a265756710332a3dab04e692969b8d49cbb11a970e45c72535d940a0d23cf848110c6e072744f90c1e4d02533a9485f8ad6e6aec052302a8a59a1354b2323f1b73cfe7b3d4f08713b572a5748f25870ccd5f421d1f1cb64f9afa9546d1e05cd471ae4e572a56d94a394376a2171e0d87e6022c339b23f8cd53423735174b95b63de45d372f25dd3be6809f3d3a009c21e61c100feda6fdd682d0a0b7093290ff9da127a721dd2c98ac055799e2e087f7148f29d5a006a62280c54360aa325cb4aeec01a5fb6846bb81ca723038246103642e938f67c987341ba5b3f2e78abccf3ebca5d725c6bb5062ee5ec035f234085d8c22311948ecd2d1b946c7913a4ac30e1c40f72e3548e13887196ce6f521e0c1bdab46d245da5c9d078f5451b99c7185e1f13266b05b62a8def652190ebbe02b8db104bf2c25400d6262279781359288edc6241d8bdf4bc411e13e43f8d6ed202921857fc6db2921fd0519455857ab1f8ffa70e93fd49909bd712670908283562e74594306bc3a18fe0a3122d77dfa96ced47725d220e035ac4b0d7fdd67bf8431562bf2e53814bf9c25c07861752f647c40f5e735e1c3c150f92f927ed82d95ac7447d4feb31fd797eadb3c0d5903e661e79413354e86a161893ee5c6f17dd59d28ea90c83221171c24f9ca81e4fafb6ad7b72dcea42274f52c17bdb72898ad9a3c2fd31acc4ed5334c8c716cb4713433b5c729b0908422c19b6888d612973003a5eb395c0a0654a1ee4ff09209560e76f45728e45d3c730d62c5143111dd2c3c14e63e44e3e499513307430a7309c1822b0728450c31bdc361fd4c9423f4f5f4f3cc1704114e85d027d1482d182de63e5fa4d3abc27515177bd9541d4e0bc659b4fdd165dba8d67a98b44155906c359dfbf721304fbfcc8f8a1c20c7a0e1aceeca58a3156eea674f70c908c241b5af5f53b7285477340063a7420ac89233b3fab5eeaedd83b0bccf14ef4ece37e6fe34b1872adc5a61d8fca70c8f80967b99d165c3194ef17f484ed888d92eec419f6b599092989fedd9d62bf3575e6acabbe9867c40cd02f4def7e0567cdcc692f4e726c722174d98937021faaa424c99bf9fb80f87afca0e2f34c4e42aa5a5a532afa004128ea0ecca159ae9d62efa37a34c4525754fbd9ec4801f64b48b9b8d1ac77c26184aaaf20d9c35c66c34a5894c0660d64d746aaeac9ff2346566fdf3042a62472db65c29d61eb49983cd0f0f472058e00e494cd0afd79a183b4d864a4938731724816dee2bea11912e5708867f7c91bae8d4c09aef9660b2acbe3ff16941077081c56147f5aad5913de1875e4d5eba839ac7e35ce52a7719f86e96ef7d618f7664880dee4f3d46daecfbe1a3ed7fd8c37c6818bcae6b3bd4d6c6b9c1cfdfcc972f84158d96c217694a2e9b54d69fc6a431d1f3ff26d9e978082b1c244f1893b07ba67d39ba051b6eb5d69627368694b0cd51337a49312f4ed8dd2325533f4750a2a36349fea4a99a53136c527b043459fd3fb0c43ef83c40d641a0325a72a8c72aadf9762cf1a452977da6f786a146f73a5f20e31d717f383b0d09158f3d2fd728c8143f1867cc200e54e672f340901720035b4ab6f60dc8e341bf8e6cae45c72e74e4e30b454ad92d709683567770580575afd463afc71815e5a99f7d8ac49371e0424d0419c28e70cbe176f0a73b586172bcb55fad5885eb40fac0fd24602725bab4cf2c084c888e4dbec58c6882a5083ff7131a120cf74e35f63c331b17c6b1b788748ff2afde1ba8d8f511986dba2cf2bd27137b4fe44e961f1520c471d720471ef53e29f4b99fa3a7bbb9b39b827c3b72880caec381dc7e1768976f7ff1c3961d76a72c53767781f1c771894a87954c47668db7db5e35c05714d1d734e72c4263ea93e97c53dd40471d4f6e522bcccd189c9ab62c66ff2229db98a4ba73fde5d8370ca7d6760edb93312ec4b3c64f1a963f2931b0d52572fa008927b887261d87e2bc2e89ebeeb1b4e92d1feb23dbec94a01ce6910c872a5547719b90554c8c2a2be89951d203c26b2bb3badee994f335cfbcb9e5eba5ca4b419c038e060639b9670a17245b6dd2cb8ffed7959d4400c1307487c1ee35316834e97e11b720b27fe7755e95a8d0759bcd3688aecdabdecb52c0dfffecd38bf49af538fff72b0ee26ad9efb9973300ecb691e7d8cc35b61caeccb5e7b2171f4c88573f1397227067d0611418d5cc5da979a6e1d729c8815b276c63eb6b30d684f98f547c75440b933f79bebe0f4192bed7f30add324df9f3cee2c7835eae054afe763980f311d7e7ac7680149109979059a0f90e48b8eea140f456c36d8b3afe6da81aac572e14881ca404d93c4f115a90fa4a553afa680d777ffcec004847b8fb5117ba17236247f1fbff3239b3b94dafb839c44b4d847f6790246a42c79ebc637f4c8a23b39eda70a581ebd237094a1c536a8d4105f0020cdeb2431c07bba93d78832c372a5bb88f473a6006a8323375d6c09be2f9eeaaaaa2afbc552cda8ca5f982ae1726b86a27a27c004607fd256e614d0c6c83d9be204e2792ac4f89cf0663e5844036075c3a27defb8ce3b66f0e2e4c90422255ab32688af68afa8a749d7dedebe7237e75f306b2091a2226f04fd4f263b9af7308a2b0acf41f07f2f3a1d9a8752192b0c1e2cbeadf0942d8118f3032201cd53e0482411b719abafc08aa1ac5e8e019133372aae1effebda09a6377dbc5587c80bbe1dffefb6223e88e2ce982b35241ddc6f9bd8e0b968e601be02a435164be0f0833606fec816faa0fab22dc389725d45baa96104d9eb3edf92314b1740f439d76d8b97c7534173214677db681e1478dbd917d0fcb6a1ee5324c2fbe9b01976d9f1abc84119bc5d2e9c6f7a5387724b617ef842838d833c23350cf17026129a2cc87a4887be4442f9bf6d9219147248a6b380c1c6802b52c8fe06e396fb989c62282b63dcf56a74048973906ae5728f106bed5060ec04eec7211dbe73d2f61c43a6b17e49d59779533e8d0ff58f72c6f729fbfbabef1752d39874af6cf0be31bec68a364bcdfbb3fac5415b6662000fe90d016cd880ff19814f2230025cfffdc337bbd518493e5ad49dd30ebf051763a840c13d959fb8d4a97317a20877c4c5d8e4d79c873c287c7ac2c3aabbe85be100d15665f0bc575fd1d0b5b24eb17c5439aaa5d768f8a88ad44989ab25ac5bc30ac8ea754b1fc1e2d14ad19da3d9b883099a31c9a4e5261388814f0ac0aa720d3cbd428635364ae987cb96dad662f12965367d50c2d11261698548ddd99a23301128a3750eca13208bd3314a5762ab7675fe964e370626bfab1b261339c672f41553ff937328553ddda1f390e1475d55a3f242a45ceb43aa47beca65f95f72ebadbe01f0ea2596e0c80ef8173f3e4fd9fe7e074345f35a43eb05a26c322872cda9d1c05acd7bb5ac364cbcc7a3f322a1189e8b9c153931e5ae00e8ee00c77221aeff111962bb690d41229b3ce73e4cb27cddf63cb3ecb1c44bb008bdb4af72ba4da0348c3f4a83b6b9e24e553077cf9adb21dd6df51381ee847c84a5c5a61eb816687a483f76e523fa01968064dbaf934173b23d765d3bb34918e0fe1b2272223ed6952792c39c127cc262a9877ec8dec1a804a4fc32976d47c1d9aea075722dcd3a88bac3ae8dbf3b294cb985eaf9fafcefa8a74deb6d038b5322f7db64726b34670e6253ac076641540b432de08b10290623d7745d776c149e39b7854a287bc89687d02347d3ab407e7838b9a072b402475da8fd4608a1e108d512e5477237091e3c6bec7fbba1d69501e44c37800dc54215e7b4be07af53aaf9f31e7c6976d0ab17ece53e8c970b7a61b88c0f4a18975bea9b04342d4e2fb1b166787657baaddeac143f3f4c21695611ebf49e15cafb4b1d92f26f3f2bbb9ff76aa7667232acae923c4b41a4a7839dbb554d5e304135a0ba2c9f1f9114623f3b33d4dc72765fb7e662a83f875d89018ab6b26d00a3ff70ac3d08e4616fb397d1e2c6070f6cb086863ebb32d62ca675172ad76a7fcaf39ff19c1c575ac589c9351fce8d72d3a81c3248345c9a872f070c1b7482b51287e692ee0ba1ba1b253090e0ac94021a4e343ba53395b51de07bb27f92d70dc459ed3d0f29020924e7207e156dc5722ce594130fb560c3e65f9d4be64f3694b3f20773db01734625504dd3194295723b60922582db7939bdb071d5c431f0972852f599f0537abfb6e8bc37c84bd55e69221f1d262f28716343a36112cdc7a481c33b4e9e459205e396b1846a495f65912d47d009d4747bc185daf64c2f12fbc7404bd3431e4a1c83c36324f78ff0729da98c9f9d8ee3aab9800003507f823aa105968ce3bf59e2fd496906fb9f5b720115dbee0afcb198954d55237ab220afef068ac05ca9b2031529bd820b314f49d1c7609a75b6cb2893da4f7884977545649439bdb92fe01a14cfca72b4d0c30f0f945e0b81661e9e0ae7e9a3061b40e26bdf3da39a8e53c11e9847f2f217401f246c9851befb28e8278ea54b31a8f185b681517357a14c2539c242dc67a8b072c5cbe979f98f65b4b0491ed349b2adb7a3fca624ef5f882f2ca56e557811177294be1ef1866fc680c1060b8f0e8af052c3c996f5d2b98bcafd86166e1d64c94728229b867df89b9cd1ed17384f00a3af529a8d988a3d106f941d5bec4af3133600ebecd484211a416b29ed929a3387a863284431ed86902803b18d3c9e58bb721b525002e23e090337d2e86ca7719ae0aa594dd6559048433b9ae49ac6647f7260acc47e0c2560f05acf820448dea20237c48e950a8ede898b499bc2950aad72aec143a9d7a71013cb9c81552e943627136ee267487dad41d8f9afa8642c9e7283f89d93246c1e2b774ea063d8a0c0ec822c8dafdcb7f684f0d71697897ae310d7d5fc2a4e2851c98fc77328777b33b0b8ab97df7a76b985b54c146652705e721ebc712cf039fa12e0234058b7ec6852c2256920e4dc0b185f8021c57d2b9d72d4da76922901459b0061e4de502b98e16b7bd1d2bc55ca30247718442bdad541c48241e5764226836b2b12a961a20b58271c62ec5d2b6c18ee9bba1335ab2e27642f223a3ff4645cc4e543f649f52197d28211bb8173f474ece215406f61b87254a7cebafeeb82e3211ad6a4bfcbed1c735cf75472e58e75ba6c855c979c7c3ad357f69ce72ea3982b20a424e89ffe69f3eecef630e7ba1d4c8177ab76c9d472201a36a27736a78c83627436bbbf0682d3c46f8c926f25f8a59561c80dcf4a726ccd8414dcb637299ad83d109c14f5152cdf0d0d9223e038dd19f2974adc1e723e916df0dfdb260e6be92c71314885565a03ca54cd9951cb31cf8ab276b695220e36f68c86bfec821bb2af9718ed501449f1dc8a738891800ddb7ca7e36ff17202ca71bb97ea7d688ef9683d260fa21db3dc9fd059306902ac95a505fa9308727977ec8b3460658a15688b8b03e392f31c87bd25970abf1446ee42bb6659d072ca70c9cd39e38c5b4126f5fb6f62305f6431cbb0729f909e9ada17e766a3ec31d806f8625909bb5045555491b78e28c47acf814f69b57545404aadbdeccf31721c355331ba405cd6ba250ad75eb5d748c3c85e0cc37822340590595c3f60f7704b431eb5ce2030d6eb6f3d80d8d17a25f47844209b8ce8a25504f3371a1cb572f79fd53ed0e4ca001dfd36f746b074f7584a09c20cef34d700592a17271c4d7208096d273d4907026654ff8ce93a4b155dbe2c530f20a1af4065307119f9e0224e5ebc2b50ec40ec948d0b1de00e86eb2448167181c6832eb22b7e448f4643052076c40fe8a7c2bb7d06fd35beda469fd2910ad89328cde377a26b9571073c727ed9e7920fa7806999ba5158dac1b9828c6d894427a06c9950d835b79e30f572b7efac6195b49fe9aa710e3739e6b984ed833be3e846d53fab6582e1cf301c72360e5c806886792c37720fd5e1ef4662623747e22528c58ba9816932914e782c07d13190f98922a20e2760729ef15099d1d7d65b8821500b079d0ba26ce84d54d30674691f2fb117c2fa6fc85391faf0e8250def1a8ce8dfb151842747cc072de904e0941c18b91c9c1a903bcb6eb88dbc94eec722fb5aa95efc9ad773172e346034a96c9f4dc64384e5ef1ce629065ae6259d6ce43b40736daf8eda1e3eb5725536327977ef4f60220cbefda1202091ae87a8692264e8c36bc292de416f8624e08e2896f4730c9ce783e8f60dc6948e11bc0129cef2d21aa2d0f76ab72e172cf9c9092f331788f8cb709813b673fd5209c4707f73cd331011eb21b3bef14b2cb836113114f05dd80835a2f4bbf093fc3d1f8919f7e8992b426a771c892f0322f4b5b6e87b8a7d1778cd47efa0a366c10c23fedcfedd9dc48ebbb72686dfd933c7a0c11eda77e9a7e2a2546fada196a4bb1364dbe566983de279852c55935072fd35ed0ba66815a3e59700c68786ee0a75aa5315d6ffc4ad30fcf586226cd17238c234e943d2fb961b35df1b329ef77bd50280a7b850d7cb32f5462a5df67872e927367ee1103b7dc1ddf5c5760d7debd1320661f9f7aa831073d2e2a2a1d545c06f2b0c7359b0a95f7f4b03adcb6e7675f61641fb703a853b551b5283ca1f18c0f42710bc8dbc1b7ad044ef68e18602c667c19422757fd32a305ba97014bd7253cb181903126430b410dba2ee16c5b4824c6c1cfb6acfa73dd5a6a2fbc22451e15b8b1b398858068b01a4d41310c37dd4f657f0d3a9ec4e8f54fef685c78025aa83223635dfef30ac9d577a8aa12498d58160436758ac8d082dec7f24da0c72dc4efc1cb77edc381b1615f2a0b978cc2b7977aa3a344567b5eb72d8525e0172b74a68d7f76aa63d288eff8fdd6ed2939a5df3a4de17b947e3c6e6d06f9c0a72a2f5747c7c2a5a09926e46a3412bd0ed18e18c9068e0136869ff9312f683f87298944c68357cfe894bcedb600b9068a687de926c86a533394f99c52ad193724b239cee453b2de4d438e1bf27e1d7a994c8d9c51a45b63dda254f6c078d32d772521fec556bea17e96fbb1be2edcb0c56d1e5ef0e8bf8d9254f982e39452aea4ee21c4f052346d1bd41f09d26c285b6f1cbc65afcdfe85ad3fcb9cdc80fa0ad7297970b6fe640978aa123bbfeccef1f161197f4a3b6aea3ce871c21db951b5b729023d00f2b26cecc6e4d0a1646755ed40d9155ee85f1b8e948506f6113992e7247050b58f11ace34e364090545f019f1132a402c3fba157ed2fc8816d685120d433c36f1f7cd92c93058a322d51bba1277716288588d45bb1f192d91b5efaf7286af0114d4fa24f1bb718f73071518ebc0700696e34c877c9d79b773ba8942728e53f5e740b6ea21ea2d453cae13b91df3dbe6bb24d06418eadf9d6de1481472d1d1f04664fb2d2ce06e1f903bbcbe602dfc8c694358ee9de751b73fb0c6720e6c606fa568d8fde840f26d6769b6084f116b33d1a2a84131175eb1c3d251b8725b081b7cd9b4a954d25e9fe5b78fa235374e8b163791f62b792e249e7d7ac75bd86399f69780fb784cc5b079be987acf4d4b1ecdca8c797127fd99f5d67d5e7247c7303c78e0c94d2c49912a9acafa85877bf99a1d3d50504329cd0b00858d172f24380e2e7cb60bfa61b6d66480d714794f9ce737b3a13c7ff4c99495e857726e380158fe7b6aa1ce42cbce33033177102e6656a04da3aaf3b1de4e7c9bc37215d740c674f640286dfd3479681ee461f250dcd17c798a5749406f691ff96272c244c4c960d82df81550f5213810643d88bb4127bd84bfdeeaec3c3857417b0266560c99b32c65b37062488dad3b02c5eefed7aafa6eb1d3e16819ff4fee0a72e58b69bf64d20861b2fb66af3735ad6178df56cb0dad9522bc5503173eb18172d5e0369ce41b76c72095d6b4fe0855d2e0cd369c27d5332237099eb93cbf34085dc387a46e63a5d7dd5aa984b53320306eaf3e5a24caf642bce3a7578fdfaf6f1ec09f9efc8f4dec0268aea440bfeb5f5533291fcf0bd11150dcf1796c44b64b0c2f6b8e16b5dcea2be2ee8e32279a308115bac33c10b0b18ed4a4684e48a55bd58ecb28eec0eeb6071040bcc8cd6a94bd4efd9f0cdf98ed7bf9f07a52ce7c3d99751ba40ddbbc443c4e37f836606506b8c4f61b1578d8cb1966462ec1efee72d6e984b76e5db0e0d65d7204a2df941f823feafc6f8bddba5f32320bc2e0ae7210301adcbf64902903cbeb21d1bae3960178048348233d218f810b138eead236ca0300084daae72cc81f63306625b80329147aa663c2ebda2f50fafc27903e720c1b8c4a99ac2260ccd5c38fd7a2d6fa15d3d183438953d57af0992a700db072db4ea9daa2d0fd7c0c1f2dfec34c9a685a1c55c1aef1b63826d00d806e206f2ff5bcaace14a7ced09141e3b5f9351f4027ba42e79e97155b67c00363b046cd720920d65537921075b76b9a86832866f695a61d38f4501e6e1374794be6442772fb3f70bba0e8f596e09e379616ebfd58cfdc3ef71187bb6fbe9ed33a7b10fa2ede6444c0e1cff23012ebde7b6809be0088510371913b001a0c6ffa24167611722c448ead472705fc2717bae4cf9d29a9ac3aae4051ae322b48aea36f164e36723c11ca4f5bb4c646d8e140f9af1dfe9237dd08dba5af10fc48db5bcc66473a72989e19be7498ceb4a9f626bd217a6bfca48a00ecfad74d54f8d2d03f38684a00c8b38b1a1a0929a2a97f3ccfb399e9fb2fe6354ce586f46b5785cb22e34bab2456553b48f6ed15da731ddd7f9adaab5868e86f7be55d974fa2a5d02552896d5731a0c5fd1b0609402e4b08248c1aaae9a4b0bcdb52191305183d58c310c66f53f24fdc6c115e48e891029888ee2dfc234a79a8c656500584b35677703ab3bf3048fdb7a6786ed55732e4f03944d85bca65809a5eecc47fee221a10421e8c9b557a5d75fc0fbc50a270192c5bbf562981efb6da92c3202c3e1de95cfa65f25c4617f84254333ecf8da41f9250b01534f8808f99a36c8ba04174efb9f2ea9bf372b5375534c5cc74d668087e5eb781e311653434dbbbf7921851724d6cf3d6a47231ab4a57dcdfd942a2b96f3a9a0ffbea67381de38790dad59bde9c0f677d0c5c22f4801706728714daa23286dd36a27d191e3319c8bb0da46d4e5731ea03440794f736b00fc1b3ea5b89db52fd0b655444023ca3c741035de369056752f38e722725ed642ddc6dd43f6be1b93d82b0d954258e13538f17f98a5462db4726ce360b11333fe1bebfb5673f12ec74a65e370a09df3bc8ae687544c57bcdd681d772fc31a3ddbbd6f982623487bb7b3cc44f5145fc289cd3a62ef20749770b3cc72b5276f646227ebddf166f7247d94e0a3c15967b55f49ad62671cf0b72b885317262bd82930be69fa23c531d4f40264c8863b60c970bf2fa0b5f510de23bd43f725e79ec80654c64b1c8dbbf11de23a1382d084f8067321575fb1b7c55f3c77b1c6036f3976811adc8835acfd4cb4c1e95c7e2e244b2c88a2e60a48e829eff8f723c071fa8170d199f9d4313dd5c58021e90252b7397b6c4f4f418da8d87ddb672913c0398dc57eb48b5f53501383acd8d8bb88d5cabe8399c6db4488e87502015e1a38dcf2173411232d7ecdf59c4977ad34d6977e8d98c43e18566301db79c613f9a62faff7d605c9fb264da46d4dbb74618f71ae5194bf5120095fe3c892c7263707e87e79a74394ff5f033616e3a3663c754bbc01ab36a2f89e11099f4c472d8672766a8180f47713b0adc7bfdba74230d6f252b057d7b2bbb484fdfc45872b22d6d24240e6b612e00705576a9a2f85dc896478d07cecd546b2cebe1c72972de52221c06c9474e387855fecea9156606ec1072ece70a130ec3ba12c264f572aa4a8c549a22ba5043ebddd7b4ab71719c44590eed4d599474fd9cf0e31ead724686dfd7b2f101d0e23eb466c75d72264cf01420859b7b21448ce6806273b03423391edf171de53637c0031d396ffb96c4409af066f3c329f4bd37d349c3782f731850d31c784c58474930c5b413a5b63081ccf3654c047a82faa0ca528fc77202d2f55268d5e4633f94e42581a39cef3b1eccbcd5a9365109a9cf3bb654b17285bd43d476f3e9036492aec836f3453bf047a003e612d03cc00ff2195fbb6472c84e3b8a13dee0e240f391fd030e052416cac27b643aead9df04f2ffd7c5c3260af06eca116f24e9ba6693981470f9b8c41d0c35a3d7b9164b675c3dfa08167263da0c5d903ad02f995fc5b131604f700ef4a67ed59af65b37ef74407ba474035fd929c2264fc2f7119ee01883a77a61a0c1461cc7c406cd121f6c24d51c4b7264926f30953776a5d249c2babf1be9cbccbb5f0eefc9ff9412fe8a77a034e8008f5a0b9a57647f09f5e272d6f805f850be8e5028d070e670f5554a355730b717f8e91e9611160262ab69092bf80b9eec0296b4a444a54221d6333ce2adbe6a727c741ae3ccab7d087f78ea9369ec6ee5062a75befa6750ee08beba4e07222f1c09f744a0570c3f5c91307a0dc3bb81723775015d849821e95e400c8333ba680d284d07b6fd35d88c978d5780d6138618d8e15652def3760683607da5ff1a3b6bfa78edbd756c2629bf6d8fd6af275d4e701851149ef214612f8bbbbb07ac9c07fa6bdb0d11d4eb577094d63b11d843a21c7b01181c42f778a6759573d8268e3d43cf38d366583e804149603b6284feeb8a70018f2a98b69adc3b804a73ccbc4095b1fe3a4ce0fd5639dfa475a9e0e349d6dac4295893e5786990c889509e8c729e2bf28affdc9ba4db805e70e7668ddadb6d207cf13f2093e3364ad760366e1e875ba0a5feac1ead15b7e904a80fd6a05e076bf0211905dd8cd203e7eb75c702b707d63e937be4c327d2786a39c1103b2109e35757c686270c0a3c38604fd672067885e6e00e2f6a96209f7fe5ad74c2a80ec68207c7944d39286f5bba5d8b441d3dccca17f6f0aeefb0203b030655f6fb2894e69ef92e88de35fe4e7ee55e4ec72ae88c8b43cbccd28787e26c6ece344df32f0af99e7840348bf91a51a3a0720e1ce5e959038997ab23c71202e696ab9b10a400150bde90111bcabc3e187e358aa8bbb610df4386c18b0a96c0d821780098b112254498c2295e854284b2cf64fd1221bf59647ca46113eba5f5552594e7d638cdb1e7c8fbe0f09e9751a8757289554c2eb2ce77164be1f78e673d2bb537eb7d19605bc1a277d036b2785c207295bfc462770da52821beb52f299320c67a859a2ae0689e01904f82dd0d3a21726e0f1f6f3cea1f20d83fa5ce093aef11687908ddd0c142f516ce986019294f7216b6abf32586fcd12a980d4aaf2b0e442bd90aecff285bc2d5f5f3aae4ca457249035e3e646bb20cdeb45ae47fdc4ebbae009adc0b179871757565e692d971725e4d527ca45c8195bad7b713265628f03acabe2e0dd50fdd40e184ea807ce662da220df674c261cf2b15201d2745571efc573e544c3e6ca586641ddb9e7863725808e83c90a33585df9861e98f9814a1e2322514bfe724e5d4cf55896759311c5d627cffa5b1e914464576963460426a7aaa97ac264815203e7852444059a7724280c24c547dbca7b05a96775e648350562459147ab9c9a24a4637b75208df5d05b5115f8189dd2b51b9ee1098c957c77509e8b896afe876e26267db9824e7641f2befef11b2bb64fe58290b78fa1b921e84072e8b29391b1f6b80258b8bf338d0d79177a29ca9fac056b864c32e3b75ba04ea658e9b858fac2b75d83c795d6268effc26eaeaefb99911c35ffd4fa9f28845f317eebfb4f44fe8c8cb04f10e5747e46b969b97d1d51fea023f71ed5738c7428feb7cfb8e2d5dc6e4f1ce0adb72532d8f56d227d9d345f1086e500ad95ba1466f7eac6b70ccf21fcbb8e32b767229edfe50315997acd1183580a0d5a6335ae27885c41be7bc508fd3c121c64e512420a9b8b60736d3712715a136c3c97ce27b2621b9d7845c18d0310a213e14729958cfd8241e07c9df5b64bf2fd39a3c63653d981331971cccb498f63a6a2a72ac708475f31078a08a925f889665fae6f95aea6ff3f5f11599f963231457491f6feb622a733db513844ec030223e7590b0bc93ba95766756c559d4b0dacbba721c3edee11aa0a63cfa752dbcb88be81360564bfa858c570519a2a5f739a8e03cbc3028fdaedb7ce9938bbea4fea2567667b8154a776af3fc2ed670bcf44d711e642a74fb93c1d7ac60417bd48c370b77c534966a9ed4f7d67e0a47e5b97a4542024723bddfc3c01a0a629c2b04ea6d6534a94f032f69784c58ab21051f08a672f36c0226b38fa2e75ccefef47e36d85cee49aaead0f89768c9f17149da5c50722bf0a7f70e832839da7a36174e046feef3f3cad1a292654a2b224dc22a6d41367388d3bde1edbce4d9644d8345355539987216b6f08a15f3e3cf43f5f9ba1072c6bdd98b1c5fce28d82e5f1dd277b5913b38d2ae715e1a6d1a0471777fb7505290bac9d33a32f4f5f7011b802b7c4caa599d439456a4d34e003cfb4323a880727d1040e53bf894080b0a73bfa89979924988edc48871be915ec8cb8eaf028d72127a8b913327ae1609438c71ac674c956b75f6f09cbc97759fbf9a951c2c957265b401b873c3248980fbbef1ab90eaa3f516b7735930f7f7a2cf3a9f76f8150cc57af528785e51e51277b147a6e3fe4cd59d67fecc52fa9fa3a9d15cd1fd921a79c4050069749567bcb94ec288aadcdcfc1faedb6f34602c4bc5d0fc317f84726c81b27f27c31389db8104bfbfaeea7a1ac804dc14a5e71c4ac9f103bba07d72a90c7e4ebe58f956a84f313f013f6ea8f5eff6dc8e68d048e65823460ec44c72df7393656da4521685a2552f19d65e35983bc98f04a5cb6de7f1431ac7fd4972abcdbe2981a9d22f16e3b76faa028d68d85543c9ca141185d26413e0ca84ad652898b6189b09d381b292f4559b9c4b6aa3ead7acbf7a6415c98a180711116872399be7e68606798088cfe43ede1c25742aceb7d63a6ac320c0240d3fdd87484b15397ab1f93e6601cae35e9b31b24026b44df4ce1dfd99a89f850dc2048e0b72ed85025350726b7ede63a6eebe753cf66b231d6684abae1b8ff9e1df2243ab720260bdbcd043a2d3fd179b5193f312ccf95f36bf2d8fa9bfd00feaa3955e2a1933c7c96f6f32d77e53f327d98170c2ba7a570b2de9c62c33e00d79c22f1bba2dcebe87a48f97b1cbb49c5e26e3ed9b37ffff68d6231fa29b530c995abaeb98441328a32ed1fd0000b081deda4f48bb0304e4e5310f17331925c1a6a3985f9e729f4af1bf92441cd07686c4334c1e525e09c99236a7a651179368c4c14527c26e47198941bd7dbeabf732c1b911a956a76e20331f8afd7a16f837b2528c216c5c9b5cc86779d53d4ee4d14a2e9232352ccb04150bb511af2a7d64bf965f7d5b72850dbeaed85aa225be26e97fc24765849caab549f242723d5cc9371cf2367f72c6bf5facbbe4e1cf826ba2e1f9c24a5b6f1c40dc315b43e9e32aabe6c6ffd90508ec3237c08f515bbdb900a3671af880214a19fe6d3c6fec76d942f7ce013072e0edc2a5b666d2010c65b7808fb9fc353d63b74238a79e9be17c35aaddcb9d42a1577807ece043e999c3ee126814bc5d6a492a89172eb16d280661b24a899864510be2fef619df22c03068f927ac7f46972394eb03e5bb324b725fdaf8933f7249ee9f40280c40abcae7eac6bacc9be01baa03ea5a32d14d7d8910effe33ce72a793da7c74ace8afc1e2df9f57a2d03d1421e0439532209e9b43474e5087ca193260c8ffe09cbea20c38f469ab58a6d86fd2730b163a968a14cde45b6da9260afcccb69f7c0890c279f1de758a97a3939a6e0fa6b9d57f1820e2eb4409bb691ddc99071f1b68b30302318a2d3943085d4bf827e2d5bfb0ec103cfdc01eba9c727d13ce4d428f80ac877b2458ac27938132be0d6ba93d7f2bae2bde20162dfa10787d44d9372652ee11dc351f9d00b9ddba4f676ace89ccd0e20a5b3c5f5748085272954abe30a3f07aeb7e46b3bb85f90497235a191251946e89017f5376fe728a6012f3b405ec132b231c5fc2d97241a9755085778f6ea0024d8fa04abb597206715a275a2ad7140560cdab973a9786b709cdb743ef0ba7e58175ea4b9fb236631412deb13a1c3c96cf5ef0dbef0ea007643ce92d54e25ea503433d50b10e489e5b0dd3d75e623c914cc690381f3e9f95798e268c89b020f3b5fba06a48de3631e00ee1d51098acfc16f89d5cbdfb30fcf35b58be143cbde2ce08e86802810671aee0a82338963e9a54c05eb278c080bb27343dfc360c98600f1a5f9671a1724e5d48a3c77fbef594f9ba9aba56d16930017d9f0bf082d2ee01350ed9fe93729d676d1d8cc4e112e7800127203b53c10cd0a25809efecd50777d5ae09f54c0fef7294e51258645d09db39d9fd4f7c0e2e5763a6a54fde9bdb3c7aa535c9247262960e3e12610ac3f3974fbabd4b54c2f34aec42bc0eb4c64d2da220135e9472ad28ad5fa949db3819a27121ad58db56c88084154d8e1bdc4378d4eb516301726f3fc6d1fd98e5d46ef61aba1299f6917c92b4da91380f46e1b2c56827ed786f97653304b4beca8fc4a02e1d2a68c745dc38097975645278d5e01565b6944572eb702c7a224e3ca14fbed0f30b1d7a80bf6b215c3ed91f3a7aa55c728f582a72340b3ab72b96e3954b04499b32c80498cac102fa05ab885c5c7a03a1078a26188e5dcaa73dd1284b71c7dea9c16b9ae692cf34972ddf0c422892f1179bc3d221a74e29760cb3ff09e706f06e81f1cb0a17808c7c9cb3d873278dc7c3af678643b4719d5c3464358e2e591e5c30de537d63d6d223d3def791033f7fc5f8827e72378c0653b2661901e133d0c4f6d0b7db71a0ca1fa560a69db8657594b1af103d15f0f5711f5a2c585189cfdb6be23b75784c1ff8945070b9b43af731287f3e7224415b985de00117683b7973b8b8d6ca9acb2479009f683757401c5fdff38e0e6bad766782ec368f38380334bdf44c01b32d3d844f412ef1793e33ccdb7bf4024adb89a3605c86fd8a549c5819e4e7c76359f1efb141746d9be84fdf0332c3395ffb13b7745d0228f870cf770a1c14e233a30e29a3ff640e7bb244e121eeb71ea6dd3da9456935aff7616f8330e4b210ca848ff34ffd27a29ad00eecbf1f7f72f7330df50988fc8fab507b140eb9048f8d6f9e5b8b847770272ffec9ef59af72503aa4c68cbaf751e25eeecc815952eb5ca3f48c8fc02ad3b7393b561def73725dd1e2603716350e6966011a924c09e7f51a658ce7dceaf8e6b1fb0fcd9c8b723005a2e6e17dd54e34cc2449ea0cad2393d8194991f775bd50da3e38622392725e0d60be92b2db3302897af4cc0c6d7e638c6def871f6f5a120b19498e1a895248132b9f5341ce36b7fe0b2ebf9469b9c2f51bfbc3036b04d49cca37a4c1974be3067d58836ce823afc2bb1f34a9e45a0affcff926c4c8d163622b0ecb5e9a725a119ef9aa32932bbf4f0e3d956e212be6ddac44a5595822e13047bca0eb2372a1f747418e0ad72b3da8d8a807466a1894bfcb92f70e50b7ba6a46f99f1fda0e31a29c1d25af5e3d0ae65a39571d6d32641342c03cc4fbf8b058276f73fcad726d7bab58d5c4219a9fcf06851fe80f72c5e6a975bf4065c1920f2a94bea39a727165a1febca35590d854a6937a6f7ce2ff1ef77b6d09a38cff49ae530446cc56ca703b0f06eba8aa3510b9cce39bae006a8c7e0fed0aaa4174e3ed17b0ec005f8d0b874d7d271864bd9a2982c5d674aff440b63c271980ac8927705d69d25472c3c0f5a75fc786b867d31deb9ec6f0ffb9967ba9a127cb607b57f5c47a6ba87274ca75cf818078e6927cd2aa3d4acae239f3340e3051e33c12486d78fe706d6d558973681c8845887db1a5a844061e5a7c100d755453cd91898d1242320b5b729bcc38d9d377b6bf58ff49e4f8390b60e281d6610bf5f78858c00abaf81806387ce236a38371569c1ccec14250360a960e6750b0687076823327ec6a32700055e021b2557ed32c95bb0ffb1df9e66eea473715d16d2be139625c158471ac4c19faa71a1a3cc3c8549eae078472e866a4d90a3826c63a8e670cda6dcbc329e172f3da3b8138488be0e4c03c5136dcb993966b35e2c3d90ea9304396df76d7c129cd5725c6d34ed5d9dc636e53ca7a8c7e2139bbe2298c0ea7eb19e03b53ee1c50d420a25721073b72b04a153d8b75bd4dea9f0d9ba501577dbfbd3aec3f3a08724f9a2d70623872c05c928f40c155522f0747e4665bb7d14c049bdfbf664fa15fbaa53119820475279e9a99ac9229a55731797f57a8b29760069037749c555d6f59ef0d20a94b280886962043671b876b9742fb6cf9354131f8417675014690720a5ca8418e3f29cbaf31d3cc97dd087069a956222567708b0cb3fd5852622a0c5403fae791ca2abc19763c73536e4948b2dba02b2826fd8e419a0441398d9f727657f11b5c6d0aeb0e65c78d8071306d3339dc0ddb5a089046e130a75606a169421354768a07d796730faaa924038b1745b0c9d56b36df5ec41b09d92c53b2724fb96c08937660f5975f85cfd99e71a8a85dd9eaf93cb497d5627c8299c4a672e4f0990a41bb588c5b52152a3e291e8761161dae4263548b41bc3cea62d65a6d6c50833580de9607082e3593084286c2f6c206d83819694c0eafcc1ee2611372bf72457201a73272daef1fdd862082c476ed1da7303a0ee6835e65fa1af97a223869329f395432cd0a4e691121e05b37a2802a53c32455575c2f9fbfd9b20002939cd8f26405b5ea5f3d5c8b891bbbbe23cd7a03eb4671ef37792c1b9f568e72b1101d89e6b2d70336b3ada7ac16fd11b45463cb590f50b00c9ae4b285099d7218318ecfa118f232da9e0302322193239701754c1d42aad7012aabd47f093e65728f32415d6afef12b84bec53931900450be99b76a3bd2776a6a0f52d44cad725e58fa8c4a873f180af037fd8384bf420ec2d66231790564e165361ca2c6c072cbbd79e33368496bf3e8a84f90f4decc31b385d3e0765effd9e5dc3a7d9b2e3677c0783d72e4219268659893dc3b94fd072c1ef368149c787f3baf4aebebc468f4dc23f981f93fcc604ede6e3705c2f02b85ef9450bf18825d436e6420dd8d72524ae6a0ba63edf74e44628a5e19409297efc36b01d9757e5ce85f9710b9c5728ee7b67b2e8b9f8ba6225ca348abfcaedc0e432066c676862fef201d8219c20bae7782d3735cf315c196383bfda79227c64358b0f2be2ec0a6181b7ecfa088550d272e794ff7d8f3fb375fac7b0fcaa98cb83d0b3b35672a66d4badb593fb32999e3e9f0fae88ae62aedcfdc7ad99fa2ebc232c49dffad3f3ff3431af1667134dae9e6d173d626e43fa9050471aa64166cc534e332308728b9cf0eb1cd68ff7226fb9931564c9147486aa26481e393afc71038b968a13b2d3f6352e4f9458d72a51784d58c0c150c843f569ba894d3465ae8e1b845dad58b5560a3368876117275e9c9b606da2862224cc7ba054cf5547d32da144bd014916861baff65c4cd721d5f94cb18641b7cf102455995cc41f1fabb1858b5841aaf73be97cb056d7f7243e0c72a069716c767e0ee9a7cf71fba4d137d121fdc9c0639b6491fe4ff927262b0f54c33d90e3f7a8d79ccce5f61d44988d9808eb61174d11a69f9f7fa4d710f32e453da0e9b48b3b438a52b55e435038f53d528c90f4d63f97e176d11f07219f1e1973fd6f1a66a4b868829f31d2ffa31590ca4b379f9be16fe325cb18b72a7f0a5d550b73872a49a43de46c98747ced3369e5071e4cff96f9b06dd32236ddf1362a25f777864ef95c8282ad8f211c49cb00601661f93c8f89dff77fce172928a911346b50db4877a21f50bf8a47794a070df49b3b68cbb7afba93fbac472630b4661fde4f64ede0d30684337ea8905508c1b36f346937da714b7443d897242cde4b1087a31c718508aded51688b3321c94d0eba5d76a8fb50d53a2bf4f0f1d076b462f1ea48a4e2e22909c1e993b263eef078fb1bea3deafc9f4237f2561822a5ae52f064294f0118b865b4c713efbe4deeae36c68ab7e4de56433dc2e72659e253eda627704f5f68f2ff789668d706594d2988af571e45ba0924c23b2719b104b5ed68827e134cad28da9bc6d1dc9efafd9a84a358bf2f00cf1c4f66415a6171e5285f8fab4e6f46624ed77adb1fb92285746a36a0e95aa7ec2e8a03a72964b521391d25c1f952959b626723686e4b48fa3aefa94471415388fc3648372b713518c5965b54e5f781e0849ea68d33c88d73c0bda602d1dabfa0c89524f72d867a7927b99f9af293e8e0faa7f2404b139ab4cb98ea35a3eb93df370d3de7211097d7a33d67b3b8096ec98ba76733ca5f15a30f337d69e8f16e4283bb6b272b622b50b45e4ca770c239602624bd78c078fdb7d0486cbf1a1bcd254909853726363ff087f056ad0965d98edbef1c9dcab74c334f8e816f419f094effce6cf72de1d556623ebab353c5b4e92a58c76ec1a65ea77d6cf4c8076e9c5d3d9b45472040c73336a8426040e4fe8cb8a4d7c57b18b5e2563a52656aed4b0a40808332454a9c91b2ce893e0501ac3bc61126c11885d1e9e127ceffbeb7ffbbd57d0e94989dc1c3c1a0271138f551114e6d203078121caa9ac6f48f5e50b7661cd38f472d15d595023d1d6096781dace2290dbb376edfb7942846be656c0f00fc0bc3d722883b65b724c04f0d2bccddf1e1155b49a27dfea572d87c482bd60fd67c5555d4a67514a9bfcee88bbfea04e4b0f37d160aa2f72a4878f86a2eb2fef9aee497265181b87759a62d3321d98c256639805beabbc3327e96aedf31750c6a0f067727da09381a8c8cd37f89bac3a6a940e4d30c9d16ae6949d9b734513db1bc89a722824f50008cbc4a4ef7919b8037df662be904b603e76d59fb9152bed45cf75724afff59d717f8e85e05a463699976305ba1c502107f2e5305c7206679c6e6e3d2aade694504fe387e728381b398bac7b36ab9e5839da5fcca62bea2f118cfd158bc54173ff896b0b193e52adbff12ca1fefe4669ad9752ec87948a6a62a0e41c83d2793d4160770500b20f42230350e7971b2f8d667a74d13600a3857893241ff7ff709a9b922bda9bdf21fb969200f3c5508e5bbc18b513a921b6b7207d191d87204b61d43d141b2fd20c3782514cc99141e09bfcac4ecd399f749ab7623072f817416ba9e5ddeb186e2507dfbecbc84e2335068f951812be33c95c68f998729de973487a530d9d617143e5c6d64cf88bab69d7942fa0ace482525002232f72714608ecd4ad0029cb250dd19fdf1f56a9a91cf231eda2a8eba0f09232ce1972e1f96860c79d898828cfe6cd0d7a73fd1756bfa6f7a8656a5e4dc398e94a2841db9fe6b5db256ca7a533e918037c605cf9fad6df6a97f4f8151ed31400bb8772d440265a73b7d6164281133c6837eaf7a1cf46fd4335e138473cd476b514e838f2f25064e80236cbdb1c3f6c3d6b635ff4ac74a5d581c86db94e046714ea5551e183e8ed348990630e8b797dd2c98a56fce143ba6f35bb492c442c17125b57728c28a7cfb007d5192f42bebe199d2de1cf6d0fef3cbe59ef10d2788b8cf3032382825cf36a89b64b3954ffa7c27ad6e6aa45b8ea8079c465dfdd30378566cf720b10bb30ae4c7a1c0e095c7f968dbd7314dfccbe42354e0a4fe0c33bf0ec0672d0f9335e75504440cae1e868e8343eece7d4cd46051a235b551c601d9aeb3c725cf37bb70b4a1563cb63e72b5839c4a5c4376379514f30c8ec2adf1019f048110f6ffe28f32927eb676fd6ce5096dc6170b301af6afdd18d6bc157d7198cd472548a366b9277f606f5cd27db3391a0402ee647724f5b4977671d8a72bc862f432d7eeeb982a88691ec3d3dd94e99b5622898a79d9a7ae7c7ce5bb7f32be4845a578ed579c42206ba151c590f754fef86a15884b083cb47c2970c2597b8a59e06d678e0514c641f52594b5ed5321870780ec6324470bc173f85162ef9f8679e7222ecdf8a8eecbbbfc8aa5d0c36c48104e7ad9709bb5792638535df33bf2ff81605e609ef5ec0ada16f85f5eb880451384ffba87d4888e399de421adc9df4a872043411d72a4d96f73ee86e8936c9279fc04c68fe45d7b5308f7a211f65543a0bc0b39e80be83814abaef348c305daf8ad5756ca9a4603055897ba3eb37552d72b9bb5429ec1f2ee1fecdbfc1bc19a1765e9be15c13910a7873f328f04680600dd0b8471c078d4a8a3d3fef7d4d7280a4921f1627aaa53d5569bd2c93b0ccd872561286e0c73594b4cde259c005f35c96dc008e0d5e7358e7202b62c1c27796726d9f3a0e3f33b75b9d21df657b52e41b1f7105ad0bcfeaddbd394b604e894b24f342d1a6b48344cb86c7c6c5ebf784bd94881f25d402f7debcd6b3381a3fec1eed04905b15f96f16535dd0c9aa2a971b643d9f1922406ad5cc6232e3c1970b690411b20e8f30fd147e544c9e85da55ea0584a05f403b17c54b61021aee51891e54ed3aeda74345cdf13cc1453aca549d9064cddd74047dc9df683150c083784b6ffb5c9780498f278a02b8f2e844b0116fe52a456a224a50031ff5eb8bee5c6f3d012320b6798c3e546211437f68e763f9489bd08e0340911153002fb3e07f5053939c7461d8ea0dfaa4a9ecf7a85ae686b95b85d105aae5bdaf65e0882c046b05f3498a6d88f9ab0306cd63041899460a81adbc7b052d375f981bb4b2e2b53e326eba575784e7681f36addeebec610d77079c606da131be2c284323ea00025145fd6065162adfa114c946d2814195907f384b27e6576e23f713c3a4a1dd08725dcb72befb6659c87229241f045268e854c16642105e3b362cf229a400ddb5729aa4013f44d515caa9f14eacb3ce0ae9d289c2a12a1e1f2f096dbd2cd7cf1a44af5538376962f8707547797ddb65d5d703740943da3309a522447326c964be72b96b177ef516e7439021cd6b2d172f29d1937200e27b23b84e5bde837df94d728b28ce8c03383d2d47508a1181831cdb590e08e34d0a07ae2e61e068ac97c523c589fa7ca2f29720ac349787faa8ebe65505c237abc232f7167c7f6aabb56b7299f2dab07c8e011d358df81bf041a8b3a18b08990a1dd931983e0fd2ed8df96d480060fbf5f884f078c33625a5fe72f099947b93f83681cb0b940c7cfc28f22b6f945e47bc9f13100da053c44208451d2c6be2d3a49e484760c5814d66905d0e97bf728c6778aefca8aac9f6232c2e3c2d38c4f10f0afcd17d4d410cb731eb470a8607509a1e899ff773c54e713e6f00e19cc35a94c37ce1e448c8357905fd42386219d9ef4399db1eff739f79794e2a85651dfb3894ff41431e29b428d8b6728fcc879bf42c7acdfe128a5410b6f6fd4e8656f26186992b6602703f626e1972d97d4651acf4a91f3f46e6e521e2158a5241a7e9e4017aaa988ce94f295c13408de5842845058778475ee2055795f0c608018075e70b6c8825ca15215b1b6d723ca2ef1381deaea69341aa0530a4c2b82789c7b3f4a5ae793fb8390fe743517245b861e0d5ffe2e1ebd2b7390cd06895c569ce1bff5b399de72afc697fe0347207f684b90b6ca107d5a94d2212bacf88941484434605ce55f3a667be2d2adc0171d42a08dc25f18e0e3785700bbaba57b1b5d0acb2b2b930fc766122a6ae5f653fab66ab9f22cc79691d01a44996e1de8933e7f3a50e9263f75c040c32fde57244ff44f02d553e987ac0ba5cf7eb9761bdf41b5b9b07621193e38594cf81d315d5c9a9acc489ff6b24edfd146b04d0ffa0abc74cdb43fa4e220b5a5038c8d972418e749b8f89623db2047d4fba9269da8f27e6b1a15814807c32eedf14dbc27235c97896cb6187d930ef54a9fe3fc95ccfd64e2575c6fb32a47bab7b014f0b32ee084f802f4d810f737b191b178192828e9687e8f6f778387a1ee002733ab525a9fff85fe6ced27b922ec6922a9364530857697e60e6ea7ea5b0b802f8d2f72b253881533519498eeee24878dba2a608611c13afeb1a285816e43f0cfe72ad4d5b9c786683bd3eee7e9bdf61e2aae40c53789de8bf664de29dce29454dcc0426be41228b32a9b7c9f4073ce500878b41c2bccf260f022b23fdddeea78b96cf6a38df3c7169a77b9f76aaabbd88a04ddd6d0e990ceb83a606aa1a3c79f54a8672fd6ef6bf8beda9bd259f69c8fb8c1c9115384d603cf9c9b47bfbf66ef169ba01e83ece9d90cdeae18b60f84279b25406434718a846da07adcf06856c15fb5472f96bd39c7a49055a9c0ba196d082ce77ca38ffbe48718fc0149d0399f112f772a9a5e729d12186c2aaa68d7b4a2ed534e397543becad77ae70c3bd8c51426647a3b1befd6dbbf7aa16a074fefef6a79a6954dde86556f1719e8b28d4c7da56723d9e9d61a83eca521ddf3b3f666314b0dd585598a5f07637a6171e30824501135da580eaefe514c1c3a71fd4352228bc3e5e85aef242458b4aa28537afc03d36b90dc9b0513be3bcec3f9e86dbb95676992d094ee4036e53824c0556bc5d8c72f488aef75313bbd49c5ae8c5cb18e2a26daa242991646f9612efd827ffb948727c9e94be5ea765d20e03a6dd8a18ceab131f7773e45c5844a4e2a01a344ba472e84f78ca25f47794eb455a29ae82b65de5d9ce98916b4972dceeb7c094a5c272fba7175f2e5a1bc43b892f526ef2d66812c1fcd9b8989af8366b638c0baac2184ae745a06480609ff7899abe32320978db09f67ccd7d911e29831d014c9d406fb4dcc23dade3be98db598036277168efbf91c6041f96719633093e2782891313712679ee69703023269cbfbe944bddf44a59aa1b89ded83105779827a37fab63abe9b8bf9549d1edb84c2f4646d513faf9f1d011653d187b3f177e8aaf0bf072e30b20c7a22404646c38325330579e55083c51e0779913046da8025b6c3d1372cc3540e27e00216320156f10d86bca0b76b6e41cf527c5e4b6ff88461dacfc72aaa8b75d2b20de11e795b34977167a92a2ae186aa0900d1f412ea468602d2e72fd0f06c71fcb18c5061b241adb236ec8b841f5aaf6054cd2421f3e6983b27e729d20fc8ab88f599a664ef00ee05ef45bb43df50229fb5726d25b08c64409d53de316e659d3796cb56267e85378c24fb919247d86e9e750fb9e00d106e4e2d972eb37867991741ce3dc127a553937381bf6d0051974c0682e263c18a02ce4ed3cc3e25036c2a7ddc33ca64b8091ffbdb02337b35eebc2e290e8927ddcb79887724181304d11b2ff9150f31d635aeb9e995d050a9b8d00636b53ac4987f1ea9b72045bd0e6518e21b25f6f7d482bc4292d5b12e9de2db21f7fc4ea3561b3c23f1c5d3861dbb58fd7bd36cc998329109811082365299e008a8d9636da3d9c56b604d84b26e8932661b17c61576d938021af78f8b41186590ed1862c8c4721e5887263933ae28a3e4657e2906b3b6e7dfb88e44cd3933d5b4d478141e773c0a58c450443e850fa97436bfcb18f8b0d09dc2687dcdee32b4e744ea8b94e3b7e1c5a720e335bd7ba9da0470332c5cf4c871c8d6e714598a8eebac7cb132c79bbb1d672ced60797ef59a75fa8666c95fd01bd9619ac84f71670e608c6b232947937bc7236f112b1f586d801a2d62cc1ca176eb34b8a89c83f3245aeb6ba71bfa784c672e3be3ae26b77b6ec623085d12e45ef0197d4d06b159022e2490d86d6708ab4345fc279013d28d011af70363927e8cba6604de7596a962ab69b43f862a9c1f75c0e5cb3b0b2f81c605929207162a110de2d546c3272194b36d0ceeaa6d3ede472585d63045978667800f971d54349366bc1b4dac876442cebcc59d0e8f96d8772f3bd7e10944df0414ee950e58db6152306afe3651c1ff98732f1e1bc6d21d072d65034505f8ccb2729ca0aa7fc3042bb04e282f1b9a60041ef7af91d5833ff48758ffdb64eb8b6152d22d168b8ae9d8c0928eee3eed36d0f9eb217d4890e0172024745d9b8c92cd303ca8e7376f69f79773fa041bc916d8e6557a1a3dcd067202652d0d8e6e696d1f086fa2f76332df063e174aaba23d78ef76a510b40e88a72239dbf614fde02dc86af633201904748d5ed9f3f842ecf1eb394561284c37845d0115221594e518e1300181f69d25c53b4d5af7a4e1722b46a1607d26271d272ad5e8226ce36444755117a871cecbb789e329d3e96f2a76ce8323f588a193e72e117e5bdab07ddc57816c93c939a17901ee05895c05b628d0820d5d05b50c53aa4d02624328e7fb104ee4b696a03302c8a1f29b2de5c596b3946fcaf56acfd72cf79bb99dcdb764e49263df5a9c3394b178ffbe72198f8acb12ae04269b5e772d172d7cc00c25569f682b8000ee584cffd7bda554793c60bd7358723324f81725fa711bc6292eee75342340f28afa64a0cab824f374eeab3b157754f64c2cd725253f633d9f5aacfafb8886d7e362364ca1c41238a44c21832ab87e0e7926772e905c7c72d55e7d50f578723a8f19881be543dc2ed6d3f7f2d53ce4da255d727697e40051e3eabde7895535098b11260a0aec313d6012e739a29fdfde9f43072aacd05731a6b345245c8f633e7acf1554e4c25ec53d354dee53ff5373c542a72a04c5ea14f04ef61eb93b5f793adf866e62b8d2bd76d8006af7db15521c41756f7c1606d96f7475dec7e1f3875179931419bbc5bba3519cbc8ddf02c63ea305899068c6c6677d872ececd4b26f80c7845d40a2ffd3259d218254f66a3a62ef72d76058e9648774dc87a9957a898e952b6ef83a47c74fa55dfd1c8b1df9da9f5420126814d7ce8cd0233058a0f1a63990d4b2af619ea171cdf91743107392353ee556472379a5fee5893c1117a63b09ed107759b501c04ebfd30574f0b88db064007aae91b62b4ae1df634a1f42561127a03b4e927591afe2154fba36da5e6c7201ec6fb190ce437646ec26cfdbbc4de38eba77c021763aa9d323f8cb79bc7d26a3fc94386e70b2f73221416d61d52c807f60d064191fc0944d981e8368e1281e434248cf70a806ae3afb896ea4ac5dac49620ed9991ad4173e2e7f7fb25f6072bb74c9ea274cd255d37712c4b0c55f199b6a62f3099c02b52a6ad3c70436180b775dbc280cf5d72a65f5d4c538c3c63a516f8729163e73c1bd4c30d7736da023217f9f51696ee31173bca78c3811a9f398712ca3bcf7d13866114104d3c49872f735a87c8106f46606e4a6b7a6b67272dc4a8ddf5335bdc07dbceee60fe3e57279c1c6e0036db69160321efbf4aba6aaa1277b850bdfe8ee9f733bd63804f072332f9050ef0686fdefb0950109dbbedf2dfc5950a1f8db575057a4c585ae034f6cceeb3a312455798d21f43c2e9920befa0724f3e49da8c7d504be12e2f114723ad23e0a3414e3653111e690ce05e9ca97c95b8258864a4dc93dd83204413e72f0fa1b8b2a57c127e2d76969e49b1e16fc8be5db1d1ed75cb44e0cfeb577f0727e8a0ca56dd04ac011f78ff41f96f1b8004660ba7c013dfdc4affe1e7b2df57217d746665de0d86cc86186389232f34c8ef96f037fab6b9b4d33a8f1adfdef729c9d508eaa6efdb55f5d89360b41dfefd33bc2ed0f93bf9b3193891dfa5cbe7279b7653c7a3c78cac4df3b5c843baa0a6858fba32ebff3b73c77bc6a3763ab15a27ac97ddefacabb20ee3c61c692e8376e4c726dbe3b5cfcecd3a572566f816e36320a92fa3809821bb06fc02879321466281806b4ef4069f9fec9c72c83b97226a5c6da592ff58bb64d06af4c7242b1398d6064ff631502d66d7775cc924e72d908dfcf0823e715d9e8dcb36686ea8dfebf68748a2111975d9e23a62bae3872d43fb7711d641f7c2d16f4b7fd1d97e7b0776d04a4a6762d15618715eb2ee86a411d27fab4e2f15f8b60d7f39dde7c04dd34307b505b33157c3293bc2ad6cd6fad8298471b3d87dba0ecea24f4a07c6113f55a73bd99630c942d2b9928e04c4dc66ee59af415e0bb512bc08928eae1918604c092c4eaf84252db2af253da9a0ab4d48698a3758b8cbd089687ddcea7871f5fb33dd5c06b6407f0c7b5c51c6237a65e0b8039eb2b55c40a15968dabca2310dc42ee937d7e513d89bd9083ba3a725a8174c82af887d1cbe3621ea82e24304fa9dbbd990adfdd072fa56bb4f8c272effd318d7321bbecb56127c50ec89fd1cc25194f3d9ea0776206964a7976a05f432e7e0d9ca36677e0de913d8b49c1e20323933416f5f009ef0d6b5d15165f722bfeaa42e07265c1bd22becf4f33590d8335fe03b68bf28a73f56994ac105a5bd20ae735496f80decc1258f09f3c1ca10ef185437c6f553aec0a141fcbb05b5fea7cd610fea196d7eadf5f5499159706950d447351e4029b09ed100f6869ca063561e086693f491d768c21a9fd753b51841f0d5f5bf2390f71a1a0746a39f47224e934e88f3323c14f1f8766ad85fc0529ed15f0108874fc65b9685deffc6772d459303719d6a44fdef41e88027668317e8bae73bb59db78c05eeaf09f3f4e12ac84548b70eb5bb7c98b672ed182f1f55d30ec685fb8891083c3534bfb3d48726234926b0db2b452d899260313c0e59ef1c08239629e3b3837e171a4ff259970e4bd1d6eacd8be10ef518f7db40362237c217f8b38df5e96d6d5771c2c9288729376d1de3bfbd234a1c2801a437750196115661883a172614634a26f2c00e672f47aed5a49409bf2dea37bbc0ebce98a47e045f20bdfb7a04297c7c0cb4175726ddc70c0bd8db0e79d5bca66f30d0a902b7266acf66f7fe104ab375076b01b2d75276fd35dc7b43b87f09a2844ed35cfff28aba93b9d6e7f563d48b6d5a7195d8c6125b6667a933b87dd96e3547c74364c0e4973308278c70baae031d38c71727e6b948725c629fddc838edd5d0f1c6bc8847c9b954414f0fe1cbdd22f60a7723f9b694d766be2de1ee78e5ff0145eb34ac0a218474548ded7affe1bb9d4d32fe3c199263b60d8911b730fa25393465bd0171f9be5413272e9273a787b1cd351bc253cc4adacd26e31fe5430459aa57eb9b0742a3bae3c256e3f24de7c54ca72d1dcbb7ebb4ed31c4689a5a528ca91a9e6e022c65d43234d7ea2724a1c686f369597f611caab16c431ab9603e39e9c4bc0cc44522a019460440a232f71708f72076c4e35942c3633a0265a067aa3d4478bcf46b7e042b2f5dd9cb0da01353306ee38f19ef0562effdee5b5190228f3cd41fb69a68adeadb1078b7a2e436c8872941bbf2e0ef63e852b77d439fd2b5fe54bd8f52f6c5568ff9ef923ae2f239e72a1fa2dea96b4ce1036f3ac3d014561d81d1cf4b65db9076d8a88205ad8bf5e27b5d877b074a707a072cc1751c70b812bf0e6695579e6643e23dacf32c68bfd72757dbd1819d87b85f13c164d33cd889835ac0f6f4091ae848b3e45882732d3594b060853303f3b957854e49120962570e469ae46b3c5f7dd5a4fc92c58f6c16f0b146120822dce78136ef3e54a6607fb0da734d80f9f989cecaa23adc560bb5df10d5bb7488d2f19ca55052205c9ada44e7db28482d5bf740600688bcc1d3a729fce8eb16d8218b2a925f89eb9e2a9e0eefe745f7eb76512a9f457e5c18e2c722d99bd8fca3edac5a397d431fd530476665c84c7cb0fc2346f53c297b97020728e06c7e0ecaa3c06e409048f4a8aee4918bad70411cab1093361a2c3fbd75f0306880db653d61353d3565d7a542e18036f5a255b87e57bfbf81de297eba179724a20f5dd77936d021b12b22804c996a095a82b41c369c8ccea3a123bb14e3a5b2dff752f026423dcca8c3878e84240b08708f465d4fa87b4f130fef0857b461252f8cf94c4147ac261cef7802a6ccd2d99e2f5cf0cc47e77a1effb0a219ef572147d22c2a5d620620e521a2ea8ffab9f606c1972917624eec00ebb9e5ad7f00bf85bc98fbd0831c44665d6937236b54255b7d0c987489fb30b190d7e193b78724026c9adf7427372c63179270e5d25d388c377a465925b4e62fbdad96076a972d026c4d3c39642889c058d9f907d98f3691dce7eba7f6f1755aa87bd8ba9252ca6860ccd123b7d050f071f90b3aab21608b465fbee9f16a61b4f4eee28094a29b406c4833a77fd6e325f8f53054480cda80a6bd8a440b9a359518bcf0ec2676a628ab06abaa78f1ecd9a0b4230458efdd18418c432ecbdbf3328807c1d49496a8bc1ff8abbca998138f1fbfa2055eb02c1730606330b175fe4dd7dce1b153705849d6114dc35744a9fc40232e614ba903fbb2bec7f362a1352d081c72fd17872528ca2ad9ce1ac5f0a7d19fe7642cff1b1df0e0d4f2e2b6be0dbdb7ff50a827253ef821e9f4e3e084ef97f73b3d08520d692fed02477e352d6542732153b8f6fada819e4ea04d0dcba2db14999d377698d97adb774f6ae64d0e5fed39d06d57291abcfedc9604e1b296a388efba82e5be0af2a0fcb61308573caab230d25fb4a68b6f7953452e84f2c8844e890295bf465e13e0cae83036978cc3d31886e7a722f34049c1bf70a33a4f612245bf8cbd6ef77fbe467a7683dbf2188df70e08372e76a27fbf3fcfc692052bd06197d88b0ae9ec467348e348aa6adf7a6321a4372d7f08a339d685893d1d75431226b47c11e0876242e63b16689695219878f627245d3e9e92541471d680fd7e0d79f1e3e71f64d6967c1fb86591329840bff97727ac57a54e1e72a2f213bc2ba9c6ee3dae1980282eb77ca8f4cb83191c27ac472e07dc53ee4aa067521c1b60ebf08b3a601fe8960db45ba9f796b69228710cd728d7d923df62e1e23fc06355cdbb8fe2762ffc5772f84ad0a111492b819205172cd978e533beeea0810f82bef0cdfda738ed228f8b27eb84154cd63d7dfc1c27254c56c3ee96622e08070b9fc1bb7215f79378476f91f8cf8b418a025216e4b001522f61a4bdabb8a4d33bdc536852084e1d23907e426e5df43f0d7db052ee2725d7baa05a82fed9624003bd7cc199f74a3da58ccfc92666a1628c7fdca9f1b6ca19641335a799e6cf8e0b636498fdb6a5e05f5f8f9fab39eb23c6c6ff1e49772f60280c481863e89566437ddb31e549a12075783942a6e947cc844a48a6df772030a6b0a1c851c9b52fefe09bf1dead6d6f1bf88a78707e77e10737e95104272b8c6779e4bf47d2e43072c979bda176c11e0ce173a90cc819c3b6662c3898c04fd2db9a6907eed368c69a267589c1c11f298f72b47cb40b62859356ab5ae4c4e3513088bd158171ca10894d894bf61f49b28fa613356d782336f96f6fa2d05725cb22ae0383dafe21b5bdb3fefbc18d12679d5e49879df705a4e1138b5409a72e442f360f5ed415781cb6c5f5aa65f38f06ca016ebe64458d778f6d632cbad6f5e3fc8b07ce1643f84d4021ff281e2393a8c4ce1fa88550db7c47371f8056843393f1d685b254935cd6543f89747b2db0baaeca9082f2ae2a68ec233bf32072ec83cbae0370bcf3400728d1f483c77b70473d7406e112506e41218455df94772f7971f4ac4c32044edc59942c18b780990d3a3d208b16d1640e1493143cc6672d048b49b01824d62646f4f95fcf2a4869445355bbf0efdd170ae6ab5eef56a722a2b5a34034f434c7724a3e92ce49955881bf25b949d771d336930f7cd08ed062c45784efa4c465989fce279d15c76d4592ef091e2d55af4b9a71e5b5b5d8766abf7637aaf89114ec1132cdc6184394eeb07a124eaba4ecfeb0bdf896fa7aa72c87c5656228f0c2e7881b3937e4db93a2b5723682eac864f62005eb48b8f35553e624563c719602116cf4ea736e7d65c82836ff7582af417ec8c2ffe05b708206285f2f18278a65c9d61f5a58c9a01a2b4ee20c241763dee796371145ac0cd72693c81609ca000a74046bba7a51693735ba5ecdde4660911bb28ae0c5f5cb27286334d2f4ac788a3cdf88a8c87ac5b533f30d014d98dc713590a15b098043f6add44570a1033f9ef0b76299f98b730ca4955f8991c58485bbe0b1c1b34127d3f12168d8cdf24bc1d3dcf60a28af149e0772e9dc1f688cfd8c21025af73fd627282c6fcddd99909f5d8da62b5d03dd913d0d9b895d2f0dbacbd89e1ba4ce4651c73fd9e00387fe81d647279e0cbb915d812d9fc230ee4e246c6b99700ba95d272146e5818acf2a1ca07c01c0922cedce6a30e0805685443efc2205fa3a9640e372292cdcbfa85bfb6c85ccdce7ff42ab2209e369f50a1071a74728c247cfb624ec7e6783ba1c024c5f14b6d0c4bf0bee6d77f18edd731f0164a9992b9f58a5972d0cced19a0e0dc5d179d10242201a2ee70f70b346090d7aa221f7043a72e4755f5e70775b4aaec8662c3e848143964d312d98a89c870bb10e482d2b259328033beeb5adfb3dbeafed633a9042763a7ff2aa2163158979a45ebb675d062eb0531bed1244f7f13e2c675265d1226650338068d86cf1e77deca4cfce66684a53e721e6b81624f8ff15bcd8dc7e6082055df1ad4d7933bb2adb4485d70772eafaa64f09e84773c56588bb46bc54e892b73432014118d79dc53443c214ea1c211457205f13b6c715f2bddfc0d10c800ef93299178e0fe6a3b9958fdec1f75f0a3257290dfda0a1ff9264ee85fce5d8540052276431a89b27f1c3ce8e8f42505798c7202f2b2b849bab0cf439bffaf8ded1b54770fbfa54f4871a2326faf26158d027298d9be1648a053b44b1bf2d132323937cf8d6612333f00d93fbb98f6a7a52f38cd20eb61c2761ae2959d567cbd9e9a3914c64b75e1aee850a3a970d34b2eb0728528051de19a163ecc42975931feb42f72a40883b7004a5a4d17896f2ca85c72c0d27725840d5d6c42a4bd20b7de320c94f7dc4da4b010e176add5e8a1e8fd6151899e07be6d5c8ac888fe2d49ba52ced463451cf9b9191e35d6b38383663a7269967d0e3733780ebf97051cc4db339a2550424ddf2449cdc7c1c6e716055748f82adcb13fdb810d7d3d5a8cbb4510e34ba70937ebc512a17a734d6ab658b6727d932cd3aff7cb358e0f28c8dff4bfa1b5fa0394b46fdb4a0509fe99d8477672cddd857cd730ce19d58ffd2ecb7eb55e1de4906a98f4f042f28e602c0e4cee3477fc916fd807f323135263335f89d5ecfa98f349a08606354055425e1f1c99729f7c02b64f51e6ef78eef957b56dff5d787f9aef73363c136f253c40c740c104b08e84dd224576fe7ecfe4006c0f486040c5e428575d9cb7ad1b3d6bc38f26724b3bed5566d245e036735b6a14d7ba15535f0a03b699604928bc2cdc07161172d70edffc917ef944660708ea4024181780b79fdf3ded2d54d3e7a8106e6754006de238095e10e2e351acf2fa95b05d20d63e78fabb65733bd8efdd58d86835720fd1f1e91cbdf9363ad00eb45e1151709bbacfd878539170c53e63bd9ce4987293371a4b3b1e391d5b3a96dcb1b50626f0b5588d73cfda81fd0006175e65902a274dd34a0942d8c3d41933e671eca58fc3e735dba8fdb004568e1c05eb7eb87208019338613b17bcd076ff911e30be6a2f32d79cd3697b970c038b792ba26d2591818ba386e298cf7cd3a3b4084484728ff3a8a986b72be8a40bbe1d704aa072fd6c32c4fc7c07ca0fd42839e94990289c27e0f6e1f869e527ec6cb99e328072a9be732c952f86948510597a755b23c6627d48a9f7d7be43ea3a56296341062c7f042c633818ac187ded298463ab83135c697a335ed1b9d5051dcab7e7c9395e102819c170b76796bd3a07cafff30cd2410d1f870d4b4409ed68b9ad3aa491723e98b6d3e568d273c8a083be34b2d6155cf6faf52176df2f68f28b707bc91d24a89f396152bab74d04ddfbad3df25f081928d4f921242640ba6597d857cb1a7270027dfa755478da2313c0a756debad1bfcf539120ea14a5c4bf2a876d445a72c7d5fbf8d8d13823d895d2a5714d1c3ec8d6da03a6d0a619477aa5aebeee1146857b170257ef370443fb91bf68d9f3ac8d58ee763d890413eae25869225c9472a1886ce0541377d48f5a468137952366d0974c9301fb8ebe55c59b18036b0972d9374f389d3ddbb696272a263fb8a2e3f19fbb1775955f623b39dbc2ab4d8472b7c7b4d1b0fbc4f08bb3abe8a0cc10e9f5b0c097e3b19201400740ca2b3fb57258df57e224553a1d23d719b20351c7e53e167fe54fb79b76462dd901788f00204aeaab1722bc124185883541b5bf4d2d669733170d8242d4cca4c28042d209146af14d1668079138ee9d214a3e6fca4a525d8573f3f5b09e1e60dc160bead8729bcf7833bb3766cf5491f0ebbab5853502068f5abc3f5f46d67cb039c4892872fbafad7b5018cbb159a07cd121b35016e7c14ed253c8fea0776963b471fd46727c54e489999161890bae9e76513084923febfcb47bc2e801c09cf404eec5117288d720ee2755a69fb77bc244eb0d3ae468210c540b46e8ee97d5dd525606c47269bd4cc1188e2ec537372cd8bfca320c2c6ecd0294379758e21def501c2f746dfa34edb43feeb3c65b8b9529c68d9d8004b02783f5194b293fc74513c7e3284c8491fb0696fbaa115ea15eabb230b3880c7feb37a07ad57ae7e920b77d2b0772576e926f793630777dd1d698fb79683cf0d4b51e31e1c3f96377463fcf8d57563522671390682b44338d888a6d1bdc805c56ec92c935f0dce7cb4d118089bc468c90c19565add75a5014918262e2c7d05aece6402167b9b237e98311050cdd72e5596e54061bc6a72331728600fdebfd6c81d86a12dfb7b54fe85efa0a3ff9576030ed27f73110e9998c174ad6443af6fb947af4f99700752f40a2a7effce2729b4aa93050d1f03d55e739cd21b61bd2aef042ae11d80447ffe08a411983f072ed0a80542ba199b638e69c11f8aa16dfcf8ef0e6802d9ea3669baf29cd2fba72ce2b5e5de89bb6eaece99e2e75627bf818d4f3004a08608274590c82d8968f51d01de0c8cc4b4e4cb1f077bef53cd13c40f3153a50edf0850b12bf826c80807274cb2bad63b783dd81031c5815346fa2898b61fc2a2bc3d508e15ce42513467247cb0fecbfea155586941fd843017b10551741498792939e806717b2e72cd472c39ce2ca7965041cc4d15be033f28d9042adb25425338a08a9f71ee326b1734c25c247e0e66446197cfda8234aa54368d14f3d2b2f9d23cada521266ce10fb72727a47448dcb06a332157d0beb61cc8ea10920a2e152e867b1e013b16f82283de93688acaa67c791ac506c445f02a81d9e4a2ef912544c952cc3cc0f8905640043988ec5e7a0ffeca4ffb38ce414841a11709183b89adcf03991f193d8d0507235f75b2fceb1198416755d7c8280bcacb848a6b60afa6ff74274acb29755eb72b6390c70d1f9e2fb0f4c00c69cb9ed2368848de738fdbc17cf925c8d28139308c44782de1606eea567f74047fdc688779a2a47fa53b86383f8ebcc000bc67c72e1ef1dbb36d7fe810b151c5ec372bd508d5d1909102cd9f2c4c7f0d61d613572a5dcde14a740ec3e0e557640327017395af3cd291191acd06623427b66668328cda20a1f39d5725e0f769df2a896dd7b81767948c73b39ffb8ad1255c222e85d23931c116bf70318c0dbbff2f628acf9f77f3a04a2c462a5a765735d9769937219f00708c5cfaaf9b98260a30639cea4f4fcfe0a81a0754faa9dd7d2f85264723268dc1ec736c43ed7dd618b144bf6dce748eab2f062a387a0ef0ea804c93672a368683ef24d5ddd179ff8c34245a416e5f9ecd5789e31859431a5fd5dbfac725b7ba4bfc1f0b883c0875f5004a4defb5f0e44b1f80d05e86220ff33489c8d53386e97a6b1ebc672a61cb7cbdd6d07b1b943dffa32082d0ce2257363e893710d37e560f0fb7516ba440b09c65678278cd9cf7935ef4bf4bdf51123733e56c772a58e7461792a08d25e7db7dcec5a4b08ca8caef158daacdb92f14cb979e329725eaa0bf28858c69c2ca0e3df7c2b9fcf1eeb951aca703b4c2ae8b0c11db6714360ceb8794f1cc87ae8874de6360529b4033d927c7b57c58caa892a9020753a43cd335fa0a59d6289e23cc6f29db2dbbd4c09d3890b35b5235e46e882cc9d6f4ebb1d4941caabfce781a030b5b9c484d28634fe98da5ee78f6582f0d6a2b133725b3fb4756e1843343be807fc70264e2dd0e98514b136c901cb3551a6e1688072fadce609b599db48d0a823666cf0622dc37ed958532035ac7cc216856e27d4721f3863869ad4c1478cf26d53eaac7a67b069e10dab0a9b4a2827ace793fc5818d8d84272216658774cce5d717b7c05c6c5d4ada2f0f5b817b99cf79802106530b2e26437172574ffb825520e2a5f2e91cc6629a4a77f925b345597472e3ad2729d9d7ca7e9012370c6a022ef9c9e4ddb5aae53ff98f578e65d65db11a5f02172bb9badb62ebb46f9ac7765952a87d77d51b4c99af16a980a580865783870e672708ae13cacd9f4ff4a422af782a99d9c501730eba38158f479f1c8edb7470f728e5368ab4500599d1ea5309c29fc75632ea682f8222e5cac046b1272b2ca2672c6dbbd4db1124cdf7d00cb2bb3ea1d8898735d9bec597d440de3ecfec027ae724c051fe7036bd5ab771941771944128607fefa7279bd8dd6f2c0973ae738e372a33521ccd6dc4fb6866ce2b7fe8b697b4e3e16f5e87af91d6a868cb9bfa45a72234282098cd333eea78016e4dbbafe6007a8579f613f8faaa0a7974bc0c1ca3670bd144ff9ec179f6daaf4114a2619161ac08e7510cc66891ee57f9bfc57052dd4917f3a58d4607033d84f0fd5e301defc5c87819cb13b038502bd3f21d8d1682fb54c94533cbdfaa78a8b43aa5f8b31c371782f170bf7222278bacdfcf27a721180d62b876038854ecafdf942be7c4bb3006010b726c064ca158877e3f86f3f55fd11ef72249718b92a2e000b0638ce19c6d6a23939b1677307e65e6a452a136306d383ae0f078b04bd758c90734ec00f44f97671f7d4fbcaaf7558656d762e3c975f11220765a4695bc4532d524e75b4d44167dd56c2205bcd70a22bf5e135e566c4160c2140daf837b711c8cbda2a7dcea338a445baa92bf5f63b9bd79b725cf8f5c557bb502532802f69ebc4fb6f11626721cc80066f6bbce4b5455c675fbd4413736734a626bce4da0ea86598ef69ceb694dba50f7dbd03a639a69cd9317801317b6c49da6cd6b02980e1ed1094a9aa0b96dcfa1d5b6416775511616772bf44f562331fabd3cfaa9bfd5f69fae241fa8740a87d5469b733a5ed96b78855eeba2cd2e6524bee106e62a83f05e4bbcd43f433d86e628346a4ade3c31bf172808631d9a6edf486141ebc4f1ebf2b8a5110346f2c48d0f8b799b4f72f51987230bdd4b807e77f2a4672d2c9f45defa1302c4ce263cda7b1dddf50a654c8d80648ce478edc94a09767b6d3624ec3b95d1fd14537e9d5329be52a8affa07bfa6fb9708b0fe439204cd99a0bca51641c8aea96bcf63b7823c13170564728a81c2fb4ca2a22454d15280717701327d37a932a2ca810e4fa1be8057a4203f283c52be1c91e4d8492922595c4e21544a591e1cfdb1e1cdeeedd8447f43bc50b376b13a1a09ff3810e329e3c79bdcb1b0991b22867efe8835efc67d655e3877266b97250970f2c2d252b98b5404d4af4c58baf6eb93a2e076828b8c20d021516417c729bf7ef046eb9f25883b1c8e93f0bd1ef33eabd2aaae7faba82354e8cc9558272e87f9feffcfec0b506e968299f98b307eeb0df3a4ccfb248d9b25fb5ce5a254e55aca54216330b602a013787d87430e2766f161f9b445658478c37317c91d5724bf1677d2e3c99125ee2a06e9961426fd8050c557e99aebc1f2a7bd55763fc3f83dc15ea133aba1c216780dda1b5c5bcc4b5bfa1bf1559396398984c0bc2513ec8b7b90bc3ba7acc3c21be70c69148fafeeddc1f9894527da2d7ff60eaaada54dd44ada0aece9fac52be5279646a351952e186ecac2b7d125a38c6fbf7bc802493011f34ebcd7270d1fea6ecf5d11932a261d245c691ac6d2bae903ad584150044b20a89283737d1c3387a47e0a355302ea9ffee6b184a644d4c4f22f1370b72255fd77cfdd476a5b34d142da12e33eba2538922bc7111e54214de8d1dd14f725f2a4fc93e51f5b9629380d2521595ad3af7976fe23d32bdeb5ff8024233925267133635991409772dd43e53d6bf8173f8064126d85996660adb07382e987504f4ee9d66e953b80832ed0513fd953ee10f08eee83b88ba9f75e8db8eef1d4e51d1b5673fbf3cf27180f69c2cf6ff378ba8578bd7f73bbb6f255b656a83e439555f4691f6e9888dcbc48a9a90daf35f7bd53660aa581f3fe9f0b10f47346b42103449165ca427687f11bb7c4140738af2b79e3a617610bd5abf44b83865f1f870aa57c16dd55f20069701938fc96c83fa602ae14bf71527892cd83f3335e69072f307b1e5da7cbff6e04a59a65d940bbd6acb0e5aab06860084adf4d65841db0c02c120a143e640cd23b5166d9dc980f2226d380605f02b0502838765bb9f1b72afaebd8c35b797c15b80f1f7579095363f970274448f0a1002cf8d4445762e722ee3400b49e35d48ae6da4d10c3b0b616cd3bbbed56a0ae88326e1eabaf61472e3d2232d2441f198c7eeeaf07b977c2ad2a08981771ab40521aef384a68078233c8557f7990889a2edccae533312607ff90443abab967d6e80667488addafc7291c2f259f12b391d026f81922698b3305ea547dfe5ce2215803fc64ae656e04421710b93142858be8273ce54b902dbb25e399baaf73799b285a6d6fa8e6a9f0db13888e66bc99d411dd2dcf5c0b4285f77bc23c632c7d0d397de610523ca5772be2f9b35290cb0b5147d8bf92d949b4301326f1048f8f0d34f0e421afef22b7262fe20f885d593bb6b2bcf9e77bf047d46fee0f840b05e0bdd51159e16169b72c97004f609379bb154268b36f2e2dc4ca03857b812e94abdfb142a7b02325e72e03170ab3aeb08f987a47b5faec01c31cb17328b18a5d9cf5fb7fd53392f3a72c9c9dfa23ad5d6c74dccdc866dbb8ea9d8d89a29c13f150f09a5fda74eae2072e19a9da766578f61bd1613aa89a7971fdc4a5a00e936d6a37e127a8095504472e059c38d1e8a9e5de75624896925938255a6612254e526e052d9c8f2ed927c334740c8159c01c4068375cd3e8beda32f8b1d674e1fbbdbe1461b069c9fbaaf72b5acd7d851476d618046c8b718d947feb0e22ca0e780edae793ae4351d90d172ce29b310902808140ccde8289e06871c0b710d1af9177762c09200a090c6e76a1cda6f8a56b7b47454ecae76899f1d7deb05db00ba0cd401c10561844b989e698633fdfabcbe830df31e61de77e5f512754fda7c7539d72cf59e36a243c2567287efb5c51fbaf45569753cb7771e5ed672bc3412681925f719f7a1802a8fc072d3eb87bcbf0ca5c45a3fb4e73747d2dca3823d96d4579678e4f8ea133aaa607297bc6f568e6b17ebbcbdaf73174bb9d8a62fd9fb7539b4f46ebf6de10f000872b1150640b056de77c8ab88b912692d700c09a3fab99703436297ca114076946e1da643a4015b07bbb951513b34d67ee3cd1ed3b8ce99fac70a04428fa78ec724325e5477c96025deb3a841f690eeda900a0eb60b6d00f0a22b534d9eb0d86b4c4caa166b4a6c4018c53e209e4da118c0f95182ba9f326849cd6c63bad61631453e00bfa7a2bf3621ee78827618cf0b65b150007c11508f33cc07bd3ba6fa94729a050c36e5221e85a04166503e72aae84ba8e09e967b71a1adf8616335c8e525e3be09d0ab561d2b073abd11f4e88d7815400a62956ff342c4a0a03591a75c6a2eca3bd0f6d4e490dee14cf8386c03c0824516054ba87295c38a3d4c4097e6279bde4ab350b06667f5f9a60cf096dcd04c8118f98c9e192de432047fd03a8d72814206d67cb1e90272e287443434a74e7113cfadc3168560654e54693e771472713743c56169dbdf10d8d901ead3f63f8a31113d9a5c7709376477dbc94dd272f77d3e436186e83903d86d625d1168552245b769b87e4fedd08ac0ebe5164026c507e05e5136aa6d4c49e0778e027ccab5504ac4b6ebd2637fd3450f5d0c7c5df49d66859e70747688b196765151128c012fa382a48e5b4fd9cd957826e76906c64f815aa3be9dfbaed8e0ab18dead7014ff3ff4ba921bb347e90dee8980206085793fb09e6009b10a032ff0fde869db409387cf945cf3936ee8da852a708456252ef8361024ae788aba26fe2aa07fe626be2d0a1f8e33597784253c35a78971bb430d3a37f914f16aedc393ef910b0b751d75341911a2bcea212d9d9cf0d672e85694f041e72eb5761cea4e68d8503b52cf610b4616e3e848b3ac26789e8872caf10534040c285a34fdeb2cda83df17f58777bcbf8cbffea1bb5b6c2feb7b6add11074dfbe56583f23b36601f30922ef60e893ea30e51f6457da2b2cbdfe872563a7e3d1de59490e0b400b86cf25d06fc2217614ae650987b2b8695e4547b7258d80501e92385a4a24b466bf03a22b0f9c9fdbf435f142f3269e44b501f49726e1ffc6680a8072ce5fbfa22f0b7af6129cddaa56add6b70be0111b7f19fcd3a2580c3be73e77b810e9e99e7eab26570300b3d1aed5291564a4a12a946605f4e2837d3918cee4c130f90c0ffebabe8cfbcc0ff21ff33a93e903f3e517f10b172d70c08a0820b7d84b15c21c52391236e344f2c99fe2765e9ef091b06a848fe541f2ce0a484ce0e0cb7b3147afd758234bee448dfbffd566c344b643b6bc97550b9e7df283859d7d4ae62ce629c8e39dd9e01cf7c0e91319878b2707127373772638fbca7f0a2406b2549c934742afe0461905b3ab0fa23e4e311308ffdc87c7259d6c22bc128f2eef156a60d1b3e72b9b948b4bd99dfe98ba61ff49f845fc872188057bff8af4725a2ae37f3ef0ed0fb9d05ff3f3b1294de17d8f36853bc3372a20a4f16bceaa3a0610949365f5ec9c7b86938c42452e7c6c58a74defc6018722551a0c71a657a4288dab5adc77e9d4d5d15fc8dabc663115cd2a2a79e86a57245d6a2ca5753b99638252644f059903d161057ef4e3c90684fab0645f11bcd72886652dc7373ce5278876178ceb08c9cb1a0467c2a1976f109d2344769859b725d02a22393783b167b959034e9b568628f4c7bc3b2c7c7e7cf82b4d511e7d3721753b78c616aaef150a5367fbf077b54fb700475850efae26c08438054ca042b3167e2a288c2db99eda85351af11cd2d0469c9281f981cf50ce4da55dc0ed41a2788ea488e5b93a58534f7cdfaa8ceac4462c3c0383833226c3ff92a8c0d165b6b16a19043d681be2bc95138e8943cb1a806333777f35702ea8ba99abcc2ec72f20c7c7144f139935dbd8b631842cada857c01d1f19f05caa0e7e094e934a272d93c0489c27303288da6e0aecbba4787b5856f2da318f7fd534a776361687e7281ecfab9f86ce9d7d571d01ffc5685df4862505f8203530dfbb645ac50918872844abe20bf85539cd774d5ec675879963ed4c9665c9c81f0534a95db54471f17c546969ed2cfcc3e783e00e799a2d4aa0470ec81f00008cc4ae40f70952d2c723135ce03b33f2df56eafc24bd64b4b39bf9a7308f4c60b7819a90c3459ccb343009757731cd402c43c40174c9a2cb1cbe745a719eb3d7ac13208db3688f62f1d00581f11c0fd4a9bb34cfc62c792292ed4f4bed3ef16ba98ea101fd513c808727e365d6a366877cb3678150c5a0da8e2349a182c757dd87cc14dc59b9d7240462a2c57901e6853c57e990ee177147d14ebdd60dd6b6f6165bab538ff46881a0dfec8b796d76dee6e15a0323097bf99c855207e7db03f485b4500c062710e5a724fbea943618a0100c65c1c95bd4234a9a456b3eedf752efabe74d9b5df96e63c1c13ea11462a6aaf98e57e1563e47585f5460f0f75b95508c56f22a169b35e725410dc0d23aa877ff19d0f57918092fff3f0ec73b844f5a146027ab383d77d67cc93217770a7ddc8f40d438067b8ee11f58523abcecb2cdc2ccc13af2232d205ce6ecd5c82b6c3be4827dcefbb65f9a0efd9add15ef80e8af5a2dd9b482cfa6cd968976cfd40e4643401b03eb09b9b822629bb49963a317d028659651cada85d910284517142dd48e1de716fa10a4d2464633b598132d980860662ed23916972d216932d207f8ee7659ae5d3a9dc28621750a5f8dc70fe80d620cc945f56de724c6ceb530223ccfb8b4dc4a2fb11816a543601f1cc77fdba15e3ad7f62a27c5b526e88afdae20a22b3581f4f92f62a66e3b9cf476e52ad642a5ab0ed5377d535b9c5f4a6416a8f67d3582d5df78e8b29b8620c5a3e65e1863db28d3a5df44c0768d01e64c49c43bf2986697425a85a75a19f235b9d6f583b2e868ac9a3c94b044a2b621387b2c83026f599a985ea6372d8e0d7f39b0b0eb0d95aaaec1021db5e2a747d7c231f9ccaf59c40811088f2c6610b5dcbe9841d7397c0398b0b5e8a7237e78cf6a0c28cf947823e6d014636254628e034276d820cd092355c2f177a72dfb31eceeb55a0ae245ffb634c4ec744cdbb6eba0e22b7da282876d5d807627218392418aa38c2488e8476c0f97e9d658d635ec5531d034cb2be55f1b3047c7232858ff3cdfe7e376b00055bad8c2784e2d2ba064935c7ecf97adec46573bb729f448b904d28f204dd99e675b8b09ad267ce8c17b687b1c8870c518d25467c72aaa0d746c6c6d78009b0d91efd1752da898be7c5e430b946afbe570e0abecd6f3fa8d96bbb5315e80213a1a65d444136a986cbbfd91de090ce5fc1b7294fb772e32a1b5d0e0fda9d30f892e60bf54009e9274f5ae0b6cd888261dc2ee15385214cda2d1aa13338674d78bfd70fdc75e79fae5acf98980015ca5664fa3f9b8852fa81032baee26b8660ef439d076e6143a6cfdd5f4c6a4c633a96bf6e604a9672a4bb06e0a15493b7a35e97f2230c8c6cf35a0138b343a41b39921c44c684c972c574af1720e9060f4dbbc0263aa09b572253e29d4be4811135f115bd5ae7d10a9e45070d52d383a7a23e074c6aa2ae6a17d6f008e6d6323953939632df720f1848e97bbae22e139d330238b71bfcb0aecbfe772a7386e511ef0d2af466fc2372ac8bbcb45a393151cacabeef179ec48ed6b7f97e87e2390a28b6944f4c658d45680ce42ddadb2e58b6e3ca9a31e11c214d0f505e631a4ac7b1eef0d1e35f9b56f27e32eb7af7ab49ab94ad91c0f2adf86fae1a2e733318a7316c20302e66e9728941f88f317dcf89fa4e5ae2aee44825e00b38eb47d668ffc8f67cf3676cb172d6a600b0d8470ecd86c0e0d4d1bc3e07d2cb34cf1e21831227296556385996437aef53ef22e36c977772c21e3ec8fa9d775628f06652db8508700a00cc87903b914e64df9f5aff772cdb41f4605f0aebf692e525780b03dcae5bedc778414c4a89ffe36d76133c2481d274e1dd421e4f7761328a290e02100ab98f94d8129916c12ad4cf3cba59ff996fc28401840ad4959b3d7f80c0b050dabbfe8bfab325721bbbc2d61c0a9bb51be3e6014ae8b89215372304d27b9f8c1280bafbd386b64a8200297364a6ce23ce216ab9f296cee85c89c51841cbc7d32bf27c348ee2e81e42baa3fd2e81e9c93b993e0188f78c50baf26d167e7e1d44de4d1477c912bb72b44440eae78a2c71376582cf72bb54ef60d61a31b394ae0c90c3bef88e23b972cacacfae8d9de81cb809745ec4a20b4d9ca51a88f08e08929e94b41d7bf11c72f01b1ffcee6b0abb0affbed818548da3e98d3887d2ff87577398248778c55c476d31ee838b3dd42aef13120041abec367bd42cec14277e01690379d6f98c782ca73031d590dcb61fdda38241287674c51ecc3cbeab446d5539ad4487840c3e72ce95a4d48bb9f9597862ecbaea2645826f5c1e4d0700b93bf42b021f60dd3c727586357b7f3dcdadad23102690dc4b315893f4d6ee1ed787fa4cf8a69ed815728d5a5169ba96de7bc91a8300b2341c2b3fd8662af2d9ab79d8362b72b02c1072135ae2bc6093c8aab5aa651905a5d2028c517c48fffe21fa039ebcb742125355a35f8dc0446c9caf1def531f6b871b089ebfc6a7bce7cc87f3a4a2bb1ca13372b8323f549d905108bab0d32c932dd221192c5c7975f5b740a1909fa82d393a5c1ca3c723fb5e9fd7828de6caef44cdce61f9bcdd4366bac8f309f09f87ab62720f0aa1077b6c5ac26f9870affce9234c2fb6fc1ddda2e4056c667364f6019372797624ab706ec8bfa024b790f37cf81f499ee45283c687cae65bc88c799034727d2eecfd70049cf1bca41ef28bc28ce2848438e7606762e2dc05e86ebb59cd021e3b44772ad7216912034446be5eb14bb8954832aaab90332f224e18d56c942617d4f21a127346180bc9e4c404a0135c96b56f429cbc2fc45c114146727bd3723bbd35a3b5e7f88c7f0b1f112e42e1f0f2837547a01868c8e13ee2bd92aa31723d2953499a763cd9509dc2c10342f6397776f47ba6d7b57ee72d6a4d9344d8720f8424891fd381bcff272a6b0e57eeb7a50e54bbcf4f6379b0c12d9ec14c0c602e7ca070e898181b9047f170340cb8059dab43363443e4238ba8e5370efdd4727a4b7d61dfdd6f58e095feb38c9a158f471eb922c430ca8c08a669460bed81724bd712f3e14c5d9bbeaa10d526b6be7c4bb0cf2e1a711dac3e180ceec3067372d2cefec8437df17bf9ae9d9e6be885b97fcaf505b3bc3d4555bce9bfa464d23d236cde5f52bde159d274ea4cc1bb91d64ee94326febb5c21bc73cbd23cd7892193aa58a0fca4574980d12360aaca5a845d5c467a5348d5f4a4b5888d847750009b9955141e710bef4c380042101d004176ce97bf62be6b81b657111a038559721e1d0392ff9cf05eb418270c97f83c198301f8edf27cb15f56016f89b5de063c8f8bf15d0850ec3786c03cd8a570aea825f5639f5bcf50ebf2ea26dedc47ac2aac6390a008bc0fc1c79cb7e0969d1159be4bae4b9c154c80fcad816168617072da647fc8dd44551fd845570f8387d7a51a7a280a7b4a3a4dbab9d0418fe2f07293cf0731c47c7f170f5bda86c543e35d04b3a9b5885f209a9d77a0f032f13272deb3a84ef601bf7d617365899c4ff5a78ad3cd91ed948c63ba29b0eeeb9d3c484b6eaf1ccd80330d76b1ce1527cee85c1018f891d72b798b0b79db6ad3758e72d6463517dae1bcb6a17b16e475867ff62af3fd5841f2e0e010a4396b71b10d72da870ac331cca1699c8cdc6165fb9767655bf545c97cf2dd703d8cebdb21293154d55f24eae601c0af23cf35c716b969843781bc2e22e9ef9d6876c3e81de5080298e7bf080ca68a73672aa71dc1c5ca25a1de8151bfc757707c4d14fb533972f70185d214a81d12c8ad45e8d487bd772ad27834e360e43462ee54061f42ea58253d376cbc09d166970084b2f783b9b0e15ace9a306a97b0ffad309ac9eccb3c24131162a440051f9b965d4f7234126f1207b314c1fe2b7489400e5d6d67b441f96b0b3dd67b4b697e75601ec24b448a8c093a79a69c260db2d1b33cd61c72507b1ca7d99df8e34edc93bef2d6e271d4605e1878815ed2a7ad1589922cc9033aafbdea1be108f28cfe07a3a1ffeef9b7b2fc80c069da0cb260e3308d985da672910e53211bbb9aacaf2ef3943c90dae09b6685cbaecf2ce848305080add4b4726fd8a70859b93b0869e455842073132be9c4ca113b6e0a76a22d132c7deb2372f5d47896590f81dedff8e0a73c617eb43b6e08c1995f1a452218b881c9c2301e044db0700c7b4bbb9d43e6b347a3dcbfea39e466d69a25fb6fd51f08a6097672eff740339fc7e2afc5338fb7913ef5b81a939c7deb3eba1301384ca1a409f065a4a52d930daf64309e2c550375acd7e1524b5c82473652328673729af7b96e728710f0f4e38959eccaace91ee601c4fb44190f9c9930561d611f916a209c7a72a0135a12d5cbd4eefa1a939b3daf5f513afd0501470a108b0af3c481eec02772ed3efb291012caf30806d62d5156c14c3a679dacfb61662e9c1a1e98162a8372dc0840a9fa372f5846308083640fc3629fddc6018cb774efdf2e915237fb06726c6531455fe7cdc9b2a75ff254e52a0ed10bca6e4ea2c4ba83025aac942a3a6b3e4a0a7e22faa1c5274cd7d52d262434bf171dbefa966b688560470de2c49772cfa1660c7d15ca90053e71d4beb4f53f327ec8afe411ea7c46715ed3ef4f25725548c435b783e1336ea39253a0f95cb5d38d5395d8a2efe0fbdcafc28879403ac51e1c7ee497d88dfa93733d898e8e774245a100ff41b3048b7d3549cf1c426eb198f1c1acdaf7bf93c0a7c97d663d143ea9fc72b6af406c68c1946159f5cc72289c88690e1b97607f0f617f12ec256b8eb2116f8240396744e6b0d33ed9407286a1a31529352b30e8bb3dc653ae98bef6e8a9ef4c641d3bf73c65a0b2410038f5c03cef822eaf1fcd7fcfb9b1658c680eecf8689594bc0d1188720d508cbd31f744ad53f5665982d072bc4c5bbf00ca576c961c6791998871efd7d6fd6f713e754abe7898e0c12e62537c3c7733b88a7d10ba238de008c9b0226d822f26493ffb81d5bfd7c610595a13bb00cbdb271ef1606e866404bf6dc3e4dd08fd0be372cac1a1f2f3a5cf652774f4cefb11d96617bc39ac369c66981494d90ad1686172f2404c81721c0d0fd0a9df443d097a0a9026308ee2267e2360b03cbbbc06bb2eea69049e36ef1816abb407da33f3d903ae0d776d8c0b85c79f806797fe37601c2633ac851216687e3b63e753808fe3b9065c6f16b9691a87e7408cd16ddf6d7230b006aaa53620e0f7fddb18c8106224948078858c010e0a65359b01adac060b7ffb95a0ba7cce1204e1201cfe565a924829ab6761a3fd2e738a19835921c7727d8f9999041a04c4a670c8a9096e51d387c89ed90c775f0097ccce5195bbc514024ea48ca6d7c75d25c2b61add23826cc3d9c55677c4686d286c20ec4a40ab3df1ca05920f73f4e57c7153a84904db642e036ad0832039fdf5f6f7adfb5f6e72836e04725dc207c080beaf52ca12fb075916e9f1594ae749001452b9c6e5b1726a23cb7fda57a629496542455641ce79db16dcecf153dcda449ae1ad66d868727fa2135ff20b648dde3660e67e6b583b5d969836eb218235cdf4d00ee57e9772e53445e35da33b452d2cd14cada0251408a7abf923069c2f5ac995b592e8697222726e5bde20248843935408f242f0c731b0484ce527d3c638b03e36aedb4b08af8bf4912f595bc26d43e97da643158ae6fee8dbb2e369579fa56edd1e15da1763a3d8d9c2c507b6f3cf16580b4d4d7a78782f8c269c5a3bd14b1e73b6f42c6b23ffbd912b24d48f330dba1cd93807abcf13a2effe093be92bbf3f127fd3966dcc851a790f4ab0b90ed58b638518bb834680f02788ab71a68f5ef2ade6be0734fbfcbd59fae5f8e2202c9e5afd89c091f34986178ff8015062869ce2a4f0802e3cb305826911da15bf9a87db4ca6a002916de5cf0bfa3f5aea542e195e0e021aa569ad3690466da36f3f9e8e61528f9aea1b60cdd66488ddeef74dcd9deafe72e3b2ebcf377597308bfb5ce66efb2cdf9c9b1a70b77bca56d80301e9584a3a2bdaa97bbcf4cd3fa65c25cdee26d7db5f8b574103157181c1c6f2b4c6761d0567c64fc8ac33671254dde0ddaee94ffc3781d1fa343003e046080a94a24f93d172e6bff6a4d5a23ab388caa089d4d9659f04f523a35a6bf547949628a82803817216692006f252554afa59240eb9d61a1acfd1cb5ee08ff88010107101ad2ce24071a11892bdd112b4b9b03c60c5203e41a59d72e8f2dbce36cff6eeb5c5d9872859e11fa42ebe5127bb4bb4606d9d4372d6d998f241f7dbbf132094082262ed724bc671a0c1d15bb4bbdeda7b381f63729bb06c96d3308666307eed22e0af8e39c26669e05f95ffed6c3b6f2fd1d8ea4430398cd49470e789d760b811756cbf5a7afb82881dbe1b373e6d67629c0d63531a9f884a704f0417f1ab22dfc2d6716ff3ec66f1ab5cbc344d7c5bb954ee30b8c3a84c591995d571568d975c4fdb301e7496e01fbc6a9bbad58871f36ff4f1c98dda7bcb9b415195bbbf0d6d7ef90d725e2b8d5c0a05a13d464abe7e3b48c882864ac3190269d4fce21de3ddfe495a43c3c5f67178204c3a96169d1e84d8f18beeaa214627591d6a1c24a32c2a6e3772fe1b606d1a5592480a897b0e1d812ff96a7e27fe4597dbcce141a688d6812f7259e3e1a904a7a3562033b2a4e02bfc32779af2596e9dad89aca4c1bc6342eb72e9efea7453048c2767c389e67478d16742b2436af9fd6d76da2cac1e7f137c72928f3c4a5d9c8fca07d42a6fce162a19f2aab49662312e0829ddcbe66564a11b96eafd82d0f8b413aef08d62ec252b393d7ff60e404b90969c55ebc4e8c2d97239bc5836ce6f05e30bf09d1a8d634b9b8ca29b0f0ce6b6d8eae1b783a0e1621105e547875d91348b10715d295b026d4eefdda9d8fe88aee412ef91757c492a184291e1d4a24fb8886f020e122b10e6c79a3e3835c81c1eb267f901852e9f2941cbdf5c5e0678569550d2988b70c3fd70306f287e6fd93d4be8a9803d2b02ec72dfceabedb6dd49a580cac8fb8efbfa63fc6eeeef0933307de714509bcf27f772eac464800cb0d2cab806c9b7cdfe1775abfa3cc5d5f5ab0a187ab63cf6aab94265acabaa11d5fcc255e9b27b2a1fc3f9ce4b806a2642aba6b71f336e2b96624369c25ed512c2ed5aea746b9b6551383cc3ac5eab4a96a3940293ac97586266574740111e8a61b41db5b072fce14acb288a524ff0fd4c5f6f50a66a016eb2c7726d2dbce953a3706ee6a23946424cecf86d22a59ca63c941cd04933de1184ff39b95ad78f8216821b07aab4442505422b2fb957f392864dc17c86561cb340ea55655ce4d2b9ae16867b73e43fa9ce8aa59ab9fae2ddd85cf3e0cf8093caa09972b2bb16e65aa57a3dcfbcc73085b40a50fe93cc6292a6af349ec60399e2f441729143499cdf13cca232ba292ee62626d52bf4396eb7c68e7f1c729172db028d7201908fe37aabd953e33fb98784fc7d5a1e2b36c62666c9a4e46ece33ee60dc6b4468df6624474a0c07861d87924be51e183b51e70689eac8496cfd0bee09e572c843ece0737bc10db5bfa147dab9d1e193481c9b67a316b63ccf00a5e13e607274786b1ac237ab7ca4ce60023c43abf8816ec93875322e97de83f6f2e797416dae6fd2a2bb00c8615e9e6e274c873b0d14d19dc8211844a14467aeb0c742573275fa9538532dad840dc72da994ba4eb9c6f1cd93ebebe52733add824414486725c81f0ae80004085e9e690df0227b4024ae150970d8944671f5fabe322f03c1ef8dabe768d930f2000986db34911d53981e4710741fd9d43be581d625df7b3512118a3fa4df3a193c2c840f767558fbd6a022e5fe0a37fb65c2f74f810fab138b9fc06f8f0bbfaaccd0e27a98c4ac8870dd353af430cf27da959dbb88b2c4972f874e2cc760abb7bcd3cfc4f3a2fd811aa42377139f7e018db0aa14904bb0c4d7dd31705b6e0f78d2cdaad587e4d4afa59f7107dc44543b1b498c0c2ec6b954746aff7bd03ebd46c075f80b51ac7b2df13ed276ce53ce142e0b7fa13a8e1c672b22123da40128ffc5fa1ad6abf52072b1be207d85013047221ecb0d4bc8344724d3ac35d711682549e5837132677be0d1e18d2589b4f79d68bb1a9e60a51501339e436c138255763592f58a807e798b628dda40995f8fbc419327582d262cd2827b4c19eb75abf972fa37d871ed15128810940bc9a23b7074719ddfda9741372ed52078388f5039b8b1963dba5262c3b7212a7f6d674d9b2e736cc1a4f2a1072dc045c479293336bcfcc4d615087eb931aba8e342357768d503a0398e4814c4824669e5a19fe008dba955f471580182b26288b637709ecb2c5a8fbe1d2816472eb4099d6c36378bac3e0fcf0c0999bef5c289de2708727c02e7d8b63568ecb72149a0b81ae219eec2f55d8810a4e359d539f1e433190d523c7ade6a9cc51fe72581489c3ed695bdfbb3b6001d19d6c3eb64f2e791219828f347d6e590a4a733074d9f42b65e238ea2dceff8efb9302ec729f4367fd09267540d49af48cdcc4727564a5e5da384c9e3f08dd9bdb7f703a5acfeb1a1e6fc7043fdc7d89de044e72340b69fe1ff17dcb846f4f4855ad8c006b154e5aa34ce87957ca0f8d9fd37672cb3a78912e377d4ce5898eeb3d5f589d35da30f1b77b614a3da5a2884d1dd7720b29294cdd57193855af9158acaf5fd2e5254cade92d6cbae7bd603f28782b72e98408646ce566c476d1270c76da6f5596a949c80da17016d9159491946a7f72d3cef53e504ed0148a49d8574f3a71b705746cff5215da3b4efab37c2be91c7260cef97a846c88d6ab98166b34d6c49d148a12c5bac243792b04d452d5927a3359b61eeef6903afa62a63fe478a5f8daeeb99f2312649248ff9f071ab1445e72e4bc2cfaa39c01bd54fa84ab38608b60e1c8378924cddde7403777fd9a51ed72673ec427407f9b736feba6b12e95bd867fc90ac42b036b7f4860554c4a6294068d30c265988ab4e677947110608733bccdfa9feb18971d31db6c3ded5e661605f92093ca965c21b166cb5b5c0b48566fbe3d69caa436089c4caba2675c0cc80e853880a405b712892e9e2b08cb01e243140e1b1b344154944ac5a6c3acce5472c7f773cbb7815360967584db7e07594b4001fa616015117c7558751b666d3a3ede1d30457c1a4fe1a6a51eac1d06f600daf03e6ea8e32f9fe9303b5ebb4b91722fd8ce5bd22b210c28420a337ed49dceff9e30504b2e02a89254b4fe438dc272be41f7ceadcf55c4f621598e3c5863dc43a6fb4da6cf6e45d6e752ee1c453a0015040fe0ea1e70e8cf616a30a141d50ccb89537390bb00d331883a987b73c27215e4d645848c0c76afd6930a8c9f97170c3e6e59b7bd90c9f0ff277be1852e7244c7eae899d1039d4bd6c685a78f9fcc2e3b7672b55025bfadca4fa5fa177e1427cde68bbac42c963bb764f1073bef95d9d984bfd3fdc00018700f12da805701be7ac21607df07668702925c1e2e387990623a809b76df42cd01c16609f50d0cd4250506ed7b5e9a00ec8ef2683b0c4acb7b0a3c9d65ca6779b37c4079cd326a84dd62701e19a4501d65d4dec232d85a0fb2cc0ab307c30d941853bdfb172372bd536601d6266921eea77117db20c523d58f09f9be8d8e0f79a57b087bdd0272a1c36bc37d85f7c6a80ccb6cba87c42ec8c8b3961fbd13e47303e708f0ea1272f6d3d6ac625bf8e14c4cbb0e6f9a8101f9538220e36b384b0e7a68492a495f72a753c7e8ae43e6dc3e2cfc8c03390be60b565a19aea51d6fff4ee8784fb23472cacc4de432bedc8f94c4bed4c64e214d0a128b4fe2fc244c292c541d92cc311e6dbd3c5bf7cf7cbc7bfcbc0ce7e5af0f7c480e89fbe4f762abe941f8878d4d726ac18e4f943b18523293fceffb1702498fae400a451d55d381c9a688cd05f4729203de7e2430dafee4a973e65e0a2094169e4728fcaa54764d705c85d48ddc72456a184da2f69bca5e5fe7e9883294d615d99f52412ae49067ad5ea67d443e721fdaf8a2f7627351c2e3718f067f6943ceaa967f5d827aaf323d8b1317523c72b99e067fbc5378f8ce415eb6ccb5012caf61acd61600b66aeefe52c8feb20c707b591d22775264cb0cde365c3b087173dd82037b9d31661868a5a88e5267e072838ec7cc0e4c7d4198da853007df61ac8010051be955f1f7acc54a339c9566571e54fe3ca75bf8fa224009f15aa8121f35ddfbad9d0db065d2a8d6dc0e552572f508ee437f23878299ec9d8ce2957ee7f57cc1a419bfbba9843d49ac7be8d97220b122ea4376ccad1d216932d5af695ee81cbac900ffe76c5a158a295944374d478df1848d5f73faeb1a762ae8476568e85348e363a73a33d382e5f3dbf0c85666e5c8b8c76c7341dbc29f3e14727507fced7e556a247497e7148387439ba6724f1b6f911e46ccbd2807f1f5e06ac944c5399fc6b0d7c144364af2c2a099377205d5eb2cd73bc8c54d720f2c212af70bfa081189c48da99d94754b90184a9e373a98154b8362becf1ebb84de7959bece6cc421670d0f1688dad98a2755aa5250097c474991a162556d80a09e1cd71d9b5e56b0182c0a06d61535c5925683087240d5ec7b9dbb1939ec41af388800fd54d16a4d16a58c55ec19175dcc171f17727b366ee2671687199fc73eddd362796cbbb359139620fdf0dd5d4f6a30c67d72972f73e0e6596581039392d8fa2969d7f656c6c9cfe1b23334a246b9bfcb8362dc42f3a06629e716b058ada226dffff2ed131cd949b858ef356114b13ada59720d413035672642b39ab22575d32e69d235b8651e12e7a7e92b27b26236f55b72faca68607ed02e2eb880b3f43ee873a84910c41c919bc1720cd2bb72d8483830a439f604be3c0668f5b48b1a0940c5581b447b3163128243adb4a8c2e67b33728adcfa7f28c2aae350e62178d7e6135e92b8ae35d0b302e0f6ecdadf05102b0d4682a27c4815c7710e60b09d6b085126122312e80e497296cd0dd1f830b644560b121e9da6b28bd3b803f5f7a9598c2a1d8170e5eb569883f89ecb9a4f3d3572c5d7ef2898c8701c5cdc0bdf7789929785322017b555cbf08fefa7f7c1d42a72ea5a00c59e4c35e73bbfb08bce727351b4db66361578d85ee4bdbe890909b172a9d569d857e052f5e6184c68dd575d6b4b5db5f14f81ec7dd976818005a49572646ba8e837f3aedffefe5ee90b3419ffd2d999d6e81047709f5b642019fb44705775258fcca3c6daa82847457f9301cf9622f71a8a3c8ad5f9827dd0eae64272cdb9116d47c772da81b9144c5f0a9084dd84a1c8d165bdf55aa248f0a1ad255facf31597c70832e00e0473195f409a6cc30385d67eeea857660ce2f39f603f72e45529d2c8c4a0b9fbb41c5acd5720754e9a581fbc4274ef99e4bd46c820733860a2068e91bb45c7b9bb95dbe84058d019a09076b40712b105cf0566d3efa7329d56ca20be149debad6a663405e89310e160bcf363f9e5068aa730fb9285fb72deaffe54444c491ab63b4feea958bd52f0bdb25017071d380a53265f34bd3472de0058667ebdcffd435c90c2559b93f5f884256af6d3fb8101318b4b6e5b8f72c687b3ebf2392c7e32e00be9012aecb114d99e92fcde01f4fe8d0ee6dd737772970037dbdeee4ab74d32bc73497b6cde96d8123a27f7897e45ba05c6d7fa94724813524b4bec35e66a6ca85d0dc5840ee8c0a69b52eaa5a39cdf769b4764f459935f03e9de9bc948891a860f054e4684f75eca4d34bf38a62695cc0ed73c8c7252e49c382e218a490aa3881c70991ee664d1b5eafcf1fc722b8c4fe383d31772929277d0923ec312a0f470f6392e6f6677e67ba229d9eafab6718c1db815cd0fdace43ccf29ce5d092d0ee1bc5bafe9cb83f7a2f0b26151e5ab169d44ec2df0d9aad56f868dacd20f08e51178755cbb809a521e5574b6045db85d1242ed112722c235aac95da755097a611ea15024944a1ed8b4ca788f0a956efdfaf10df9424a4635ff575c9843b43caff294f97801dade456363628b25e83ba3202c99654051e96fb1b57be7b6c4bf879ab589793385492a8f73eefb982dd061420b49a07678b8c61b494897aa9422e09fc7875a28c036584c7baa5ade28d6c156ff410b40ccc2b1715bd3f5163fd3762857615408a91121d1320f23b09a3763bbedef953105f97a772fa207ddb3e075c6d7df316d27977d2d7d210f41795305fbaf0ebfb3c0512d33cfc7b84f1524cb8b66aa6d73a5ca1825569d096c7536a8f1f31e8db52a024a6eb1fd77d618ef257097fd66902fc114daa242b69c27c8fa444b28b084aa8428c1bed8df9036afb086bdde39190ef1c380808c8890636fe1bb6d0b42b3ccaec0e837836f7dbdd52b9d4a815409c923b90e08e93e02ab820de469a5e5b72300592bd3580c308993665179cd2129445fc01999ca19c307444265e4b86d86a07f15493e062b8ef912d782e644f40e67aed282c8da6b30100439e505518f0728f25fd85fa8e6019ac4d4501f88f0caf575111719db4daba271024056c50ee72f617162e31441a5171acff8a038b9ee686b2b36ed8e865abd2ea00a03ed16f4c51be409ead28074987a472f795d97b6f812171821733696167f855ec56d2c62a7f1073611c71b5cb0bc459f86accf9afbe1411a115cdc721e4ac9b65b91e0a3ae6c3d0213f0c8fbd6cde352ba8445294a2a19e9a7160fef97ed5c2cefda02416cf14bbc6fc5d6354a80c301feb56f12b1e8c242a09a8602b962ddbcb3fc43e72c9da8bdb9009a78ad65ed82fc1d39142dd3249afe0fee9267b3edfdcdaf21a72862f8c3e5e9369bedba79221c4e0b2cd44da8e05fc87899d29505d86ecc5980a788d2884dfbee8286c824c6b4e997bc0405e4bb11c96eae1dab81d88e0ba66729e08e478d1e5ec23c0a91158c1f74dea3cc8f415d96feb2353a40b76eee0ec609fa064f1ebd2d952a415e64920b2fa196421e85dcf3e1d61b43349a64ac12572ab6249b6a69da6430476bb37ce0ded3a7134a45cbd9d667ef469be4a073e37726c7541f087b1383f2906f989852488cfdc9e0e0554187113338e2316722d222ada12012ff0921a488b89ac4b3ed712fa9e5198c07a591222565df48d98524472496460fbd3f611cf0d6bdf8e0f3432ec2b8d46905e3cbcb18f6fb707b6968a4e4c3117e32e7a40b289e1e041e2e098730fbd3410c709294d6bc7ecae74151d720238e0013e30af4ddd5f96910e1e6a8fd31ecacfd834b3d142e24e9fef4f4972114e90ece1bc64589a0f9dc4a69b6705410f31f154d0a3bfa7e6c26521ccbb7231824a646a9f734fa720526bd719672ad01ce674a35cfd54d8929c90d64ee2729e13cc7fb71a91d67adfb9fc80e0f4a4b620b50248cafce1fd1c3a4172d2981bb705ffae61a931059d0c0fa3c9bde06702b188b5b58549dd4a1f79c9768bd36495cac7a1f7eaf577182a45dae7998770510b9c0e013919abecdde53168e2f972217048a820142c31b4cbd74499bdb5d2cb889dc653960eb87ff397c3cafab735d70204241ebe1db0dba4f64c733311bf9a86e67dbeea8d1c14c1ec22d98ba1679ac6979150916dd5fa8bce87df89c193e0a5e5ee20a143e6cacc756e6aaa8372d09233010553f28ce3bdb153fe41f4d32c11accbef84b0a23b98d8b8dba48e720d66d1ba95c7ed2452a768d92ca715107178d93f587bb244517bee3221904c29ab717cbf22323ed0fb75677d734790beffa0b615b82abfab73ae189a081c9c7282b690ef9d61c391a403b195ce3ad769d6254a2aef63c26bbc6705b9cdd16472ea5995bb4f88f1e2edc1fd9e6779959810ab32b3e8116d98a5b85391c649927201aa072c0d40b758993584f5f4d468b48e64f06cb0bf98c3f966bbc19ecba972c877624c844bff8aa637ca52488cd90090c17d3864781d55984a15392bf44426f5a7226d452c2b16f368b3679116d414b5363e1ead859499ea0f18c216f4d70d4824e88624f2d3691c4365c62b0c59a7e944d4246bda3bbb497f78f12977ba72ebc734c88ffba4dad89e1f5a8e436495fe4c0ef30c46d2712c7d32042535ee72652ee9d5859616c6f4a8abc863cc5d91fdf588b6ff0ecdf3dfe5c7175b129c4423f8b23d93e678bff4c6ab45d5b9b145da88c33bb777798093f37495d94eeb0a11b4821a99cdce390d6eb0bbbf94fe1a3151364f6b59489f1dabcc7b812a7d72ca0fcb42db6c29c09c4e4e779e789b3fc8d25ca1318631caec4655ca603ed972a885a5053bd34bb115e9fab62cb45a2217ed7b3cda02747208ea99364dc0d65bd28ca9ce54cd1aace927d9788e85e1ff2d173659ba4005b5e9e7adc4fa1d33721a8571920d6d43d62bb4dea85cff9a590d17818dc4624021dfb03cd2fe60107203cf6139b115bb3b1d6dbf4b2a185c67311dd635ee6d53c28759e23a6011ef353cac66b0fbe4889980165aab8d86e7db7bc6b79d2f6bbd606fb26c3b50d84772e7fcf6babecee1a684a652b7f96a665fa411dd7ebd359527a011656555c70c723322ae17be877f8777a5d541b11a45365a1c8106a5853b2a3ba2fd47f4370a58548e60618528cf23b1b7ac91eab5703c7c84d669306fdf4a7d7433921143504369500c03ec8a624a6d6b48723d9ded9bd13d774a3ce9f1dae5528a1012485072b5b731c11b8bfb3b0d99d83d48bc9a5a4615f3a4a76dd733780f3a362ec65872646fa8618013166c2494724f38291d27f649863b02291d3377efef1cdcc1262f9eeef3d11d7e24df91fbc041341af6be01c102b5157e368d4bcbaac0bb699572fd07591a15bc9f94d65ad5e773a9cc6e47da38834293d4850580aaf398992e72a33df78031c07efecceb1a9b628b29477bc048b4dc6e5a701fc158e01423823568443270e125ab3db74f2d7be196c4e6ec8cfdfbbb9419a3f23a316fbcbc2802a432ee39d7c1241159bd68640126da6a4819b17a64e1832cb5ee21df0c3ea572158fedcc3f88447b271c750bebba3fad78ae26f1512230b707421ead5c251672fd34f23d8f348243a8ec1dedcbddfd3233740eaf0fa32c57218a1c1218e2327245ed6533cbf8c9663b55852dd5c9bcd5b804d8780d7bee26d1c67c6f7acad8721a4f1d658d64d65ba73257de49939c782e9188ef6c177cffb55e373e044a7a725db83c2dd7e67533ea698de0ec759541ba5329aaa197b5140571db4756e30b66756fc1ad1c75126ec85853eedaf790b7c65139baf196f1edc8329861f89259724f0e50d93f12801170cb98b2d7745d5ab7c7a267334357ac7d0430490bda6f72afc45452ddb334824e88eb363d503404c5e218079ce30a9141a63178c41de80fbb8b337b00e4ee7c53193f3962685fdfd4b51aa8eade183481859857ac286d09dc252cb955d0d2b63c28c18170faef291606458e4cbe5502695ec0b8256cc07227aafef521328c15a28517b934e41a0963d80a31249990aa5ee1bba5f5e558680bd8217a2bda988c9d44b03146c18b98aed54f96286f25d022ad962f0d8681265d02c7dbcec77da63456e6c1bdb9a6d4a51ef0dd710c3a4b687519e8903f2b72bebfe4932e7d1b87c7fc96edf9461d0a002e5e7908dc8b55cfcce0570e543e42c9e35e261eedcc74c3925c623567eeee80c9e998a0c7b2d24a617830049f3a5f1ae327fa43d6a788847a047b6f0e8a5b4230bd777bf36e7cb083a3c380c0c8724fdc095547b80317fa957c18b833038a7bce28faecd2b255abb19fe63413b7725b3628249e4ccd68f3d31599ff4d31868a3aa02bd92aeb3e968fa07133579727d72fabdb122769c96832b46f3449291a50e9c243ab55fb918add606ba8553d72b4b1d1567bb103917e37e4ec3df2e8ea3532e902dddcb9c6bcda48941f64f1725951334eed3443d3a97a3abe6a49920a6a4bfd1357328f50a5d08a551057751c9cec0cf7a99507ca332439f1d2fd3d5334ebfe818c873056acbd3beb6e4bf7720de78b8fc64f476f98fa324d7536319b098b398f5506609dc2d87a579f350f72c73e416cdb3709c6c3723f2429f44b22caaebc932b2839521ac4e0e5c33c6956cb52075e5e73fc7a884fc599c08fcbd2aa3d513b7f7d91575a7081e08b415f24dd19855358f54d15ce958d10f650613555fc0f49f9f1c2e73a3e53091209e172a31b1dec316590fbc3e9b11d0b4c0a5eaaa40d0208d2e707f8d548b3eeb3a01f2732567c8cb7ad4efdb3368c727916045dbc14996a83b8c3f0a48946763ffe4ef2bb03a8cb4d47a656c646f937974269a691b89255444f8685538bea01cb24729f7af4ade74a14211d592915e50357f3d541677f5efcbd7bcd8a1b73e8544c60e3d8ef5fc9fa7e5729f1d0c5441cd3097598179379f50f16a4657eec19741059d046377717ac74d9ac8af2723ceec1f155f936ce409bd676b1ad0ecb3efd0e72357dd532c6b69cfa3e6a146684cc73e21fbb473368f93b683901d5cfcaefaa26d901962a1dc38adbdaf63b8bb2725e7d8b28f9a7bfdbe048b01038b8cb71fe1717cefea86a407f2f56b477d97bbeb5462ac32c0818e38c58a387470e51a7736f86480f5fe7dda28741fc3ae4389b4383ac7f8e49749c9ab563277558dc723d05d2fc49857860eb69029da2305ede6ae515cf19f84460f8a8bd465f12da76dd72113816ec97d5d8f4d44a31b3827a0149fedb41d27c0233b205309d2feadd0c729013dfa724a370b530b2bd7e4306b3377779e4a14b1f67ec43267ceeeaaf7c7208ce4bdede64a04f9b4bb914ff8b83e7e6d8d3e97edf1a70e4092fcaaa58416678e923e7bbd292d88e1cc49893913d980620e2c0e63f5d00748cecb264421c020493de051e23d1836a8a98118ab8f5919a21a24cc6853920b88a3f1825cf5572a5bf9e2d0a1e91a5346556cf8aecc42b4058c36d552b007a3ee3de8de0a49e2dadac0357f9186bc62d9da3a2a04084cc6734313ecee1cb82a8133fbcba4f9572817243def3058ccf79df549bccbe4b4edd18290b2a9d5db2791971096a4ec47279644f1cadf3b7567172467577022c13683aeaa9787137845fda23af16c15c723351181c3871a142bce5bf0b708106075920ba9c715b1716c019db909f55c872489b447d7a7230a2a3be9556c065bf3c896311ad13093fb85576a2cb57ae6e048ce5ac843ba9f2e1a7ed5be009f887aa1e35406aecef4ec546024f66fed8dd112d68fa084da9a18cd8c7f5ba81005040ad8f2cd7f5157a731d37676a8ca487348b2ce554f13a7932f96170b60e85dc6ac485106dfd19c32c33864bb253dae872f69ad2b7f114a902eba2e16ad66e932027e7d6a75df823b91d1042f706a6cb105ba5e8024d11db4b9072abdccad45bc40c6a0210c1ef09cc6fc143754d73181e0bd49638b465f0df6309607e1ca8aae3f08bbf4ec3d2c098063504a4d97d9a72462fcb850ecaa00cf9f60b709344848fb77a331008ff21c377d03ed65800bb29c64fa6279e888aed2ab20fccb744b129aff2060a62311a13ca3b6e547ff08372ab6d572422fb3b7b37b9d00626c0697a4245afea4d1b959543f0654a12187c72839ee31669d33f7968c892598f4ba2b5ac217b2250f5eb8f8515926386ecf37211ac0e1bd9d4352e910ca19235a2d2f34b7fef3f0ce8dee67f3db6d3a1d0b93f0fe44869d7ecd336b10a336c985a39dae641b5230b608bc8a5fa34a2cb105872c7224ff3737c9c1912736e51d1ded72e5c4b92d70419999d167bba78fa58066962373340503d270a838e97cc7af18b49d03d9ee5fbfa2c9c0e1356ba080221725980abdbdf6a89720580741dc325584a6a01253f2b96508bb659d44fb859ce72b165e7525da25ba07478b7bb04e825f3beed74f963858a4a7401dc66ad6f284e36814ddfdae58f4abad7508388fc66088cffd612729f0b18c1943514ddbb3c72465f6fc5bca8f79eb8adc1f5f0a6be4d81aaa81de0498b52a1979773e0adb21f2be464b9587d2c8f9b81a6bc36ab660fa2ac1acf888511d4e0e73ad169470a721673763c4fc2af96c2c80f04c4f02c7ed8b9a6248104a02df886e18d0bfa4272023342c881fa0d2a4b6197aed5239e6b429e46b8c149c5751df1e992cee6604ef2eccd7ed55a89b271c67d6cd71120ff681bc473899ceaf588ccef2898769d476923cb687952c5c4c255c80f292382272e24ba3c69b66c8a7ae7ac132bb8567209ac7bbaf10b111a54c5beec76491522dd0e267143cc74389b5bdc6c91a2ae2fece93965ca1a125c64b9af795b11cf625b3e6bd66337fe05b22856d40e6518727cd54dfb98111021a5f24f7132506e3575a56c62abac5bc889c2f9787825ed059db5e19756ce79a40deca639ca0e311540f2d1172bc2b65783e4a571f753e2721b958f6dc5e716ba6bca24249e71ff1b6c5149b3c5d4a7df1799ad1b3d6e2d72db476f9b697a974b5edb7ebccc2f2cbae013dabee466d8bfa81690d1aaa52a4d9e5c57bf1aa0bb6b6903d0e4f313e46939b79c9f5a5abe9890891892fdb0df729e665301668a0c532920fb7d6d213915faff063996a6426d94baa9ef481f8e720b6c8d3111fd4c033602db619f4953bbd897164a546b73a384303ca3640d663601c0f82b8d6856dd7abe267c2955424361381a81a0c49f438c22ab878e603472bcabb8c1a09eabb669e5435cbe54969a5721e870b865b0356dd72a0cb53d8972532f311a2ccac6407b66565889983a8130801dada032cde9d30d2b15ca8ec472de364873cc024b4916e4001b16f7e5a7efda1e16bb6e1ca26a9ba1e0c0add3727606c9c1ad9b22ec265c2e6cd938b6e2950826bfe5b6c607a7d034b361f59c7257bbf97755dd3abbb965d54227ae5ba8b011c87be3f4d1a421b79ec32f203f1b9b95da493a8d05831e7bf1be9d47adccecf8cc847b46788a022998e66dccb263fac49fe6952da0d924dd2b269665230d7ecc2328e3a3e20a73a1ad8269759c2a2063fbd791b2f14076a936c336e98aaafbe833958960e5057eda0eb789f2ce4de69e981c76a5b0016844c8b587f5718715d1d8b922cb3962fecb0910d9c7091ebfd31ca0c151e8f8b73ddc7d32105626fc11766842ece865c9881a267d11e072c2520927fc3e17b87b0bc05813a1704c83cd0242c4a9df3d92c35da3f09b6872d77fe29301650c3f7267eb95cdc319089da85c37bb5d319f71e0ec37b458c461e979b29c8ac7b94cb9704777b9fff47fa50289b19285ff53c0fc950f9c481772c67a81644861c45cc88b817155ae78db8a6d796b9ebbb722d500bf80043620729e972df65a2744e258a081640aab988da646a101a06fdc1bd387b44eed7d2c0ea77951338a0c79cf13e6f8a11a837f351a46581341211167a89f3cdeaa517b137cf4c18b277ec95bd22f70a0709d457594f3921b689ce36b4452abcb50fcb17217c7794fe932a1491038a2e1ff94646ad93c7a5b2455a861e2bf0125533c4072b5cf94220b171defaab7bcf664fbc39472bafa42cc92488e9064080f41272c3d10f3ec9592755ed7546b4fb986582d8ef8c8c22f79f0450540585d36f126ad071f7f4aa8568b2972b6622271d75e27e27a747043046d12829b5246d3fd83640f4700eb423fe17d023dec6444dd3cbbb0182a5bb47e7898d7864336efcfd51c7294e5637880e39f82cebafd45f91da7a8588b78523fb6537fc328e0676e235644543f0426c9d544216fede3bc0b31c89b7055ca392d8cd81345fee2ab2b87d4726fab98743f3e0a841bd1ac67da6f1104a2be31c282b22811dba0781a6933437226f0f191bc008b221c02eda5313fe888b12c65ab4b8295dd5a5e05ac92dc5872ca3843b21040e716fd029c82fc41a8c64acce31a45c26385d82dd18da62830723cfe8027c098aab443d1d71e00497d99c5cd9d58705792a269ca4372de6d152fef713514b48a548fa092b3ead0e02c8ccd451696bef257e089eee8b5d6ae7272d40a17a6c64218535ad4ca07c1f502134e24421c0b6fa4ee616239c666120b7256c761c06351b6bddfa7e437d0cd4f715f445bfafd993ece1290973edb3fc572556d8fb12854285705b67feb77166a8dcf913a3552083ee50f1afee7700add724f5828f1466c7c3375476ed427888b9daf822f27f78206a93936a305632e9c696e2b3305e5f97bde7257b04b8e53d28ad11b317b94da93c6386a650b0d7c0d72e9a4ca61a30b22952e39392cbb27311a8a9777fce0914eec7175a3295fbc8f729b98af83eac433ccfb533829fb0d6bf9d8d549f4cf590aae5fb1e1e04788bc2c6eb8756e5746b65e9d264ffa4260a55266029e0280211eb56d9918b295e7f165a1eb40c6549986be90c4e2213de0dba701526287d49f14aed5924fc3a4036b6d57f97ff5a80f8ff467f94f8a466aec075154e3f08087ea8f2983c1924cc4ba5ea958348c5aa9180a69eba8dcb80bbd845adf6f3905505b0b310c2e46a4aba972fda8e8ec5f8ac9c9b02f792e8e33e50e8e78edc15539cc20d9553280e3d2847275aa71cc96abb8d55e527540ffd042111c16317728566ba21213a0913368e25981e3f5a4f67cf8c670473d67a6144294ae863da499a9813454d024616049511c3514f102006c62a76af64b1c02f965373cbd2f2ea6bc9f98a38ba6d1c8109b63100a2add0579cfb247a234fc8694b33bd2e2ff57ffd0bf4e5b38b603bace9e72d250b454c1aa61bc0a27df3d410a92ddc541e551fa0b1d80ac7168ca98be626443408094a4ce5c0027569129ff72de3190ac8c31f167cc0fac6f8be557b846693c5c377bab726c19c5889a3e3166da703e6c681f7ae2a871cc7e486c64f9567217432ce71e9d36e0acf78abcb281df9d716c72c929337b4d8d22b2bc42cdc37267a497fdb55acb342eea6610079399d403a3a442bbc0a7afc48fcc74a93032064a3c6425eadfc50128e08a3ee9b928ab3a5ecc4c13b93622831da12e9d2df572d1b2d62f1582a7ce82132236a603dd2710fc3be88a9f5f98d22a13cc0f48f120e4e4d9acfc59f4fda95b0fbc6a555c78dc6a2f0a80e75634bd31d8331999350553402c737de389dc0a15d928a68d2bf453a6ce21764d7cd9f4f4d93ffe61495599f56cabf8d6c389cff831afb34de9d8aa9cab60004d01b2f1c7b87042dc5f72876a99d72b160a5418ba18af55d240a840e76eccb9331aacb773294610b4d6404a007a333aef6595e44234d526fccf22fc801fea9ae950db98126202e40c98729b2e9df8386ee28c18ae9dc7ed4d686919a3c88ef8ff25c7a6b3a95399b19b72ef7b82b7b9121390b7b9a59ff8d829ab0626f2e8925e5721f923b4b6e093827257d203beb7c8fc2584c69c1c0545a737fd4f4b077f7d8dd784a2a257b5eaf308015e72f51cabf3e1952d41196119fd078efe93d59509f2e8d463be32d59533405a950488e198f6f94e67801269092b73e4e0f4869a1536667aa3c59b0e0f3a27f4b79fbf1c5aea9495d9689851b7896a2f7107a99f6969e236393bf251f79a1f801bf7a0fc2481087a294b9cf40685f269458703ca0174b871ba3413a8e6b67297271194541d9701d7a1660dcfe396394ad9bebf8a7278143efb05b1743a6a0b5537738d3f9739cd41fc34d5ebb3a1139b5a92c495022dadf293255ccd10f069c9bbe2c1e6c94fce7dbb933d8c614bec99c0f71bbe3bde49e95d4f301679e67281f8efac20caf267998f871e409bc82e81461992edbcc1fb42208a89228e3c72bb6b646b1bd1203a850929d94f6bade144b36354197fd0e3f99e4d378bbf6272d8f309137c6d257c7ca14e2fa76355f8411b2e1f88b00f372d198212ff83c52bd193b520408c8b190c21423cbdc76de74432bcdaf89098616c2b441365ff981b05692b4b5aa25ca1f4e27059fe9f7f7627aa6721a3b4457c0cad98bda08fd5725f4706bded0e2018a27b62d41e663fb95aef37d217e09f6a6f655b712b017072acdbaa01087d1af2366f7d7d3e439b188ffe7ea447dabc1fe71a1eecb2629872b23cecba4fd98c8ec13db60595f7f2da612586313130ca13b4c2a964bd8229723c0eb90e65412f3b891656bc45d5747ba89eca1661d4362a845ad2babe8d5a1cda2d413ebb18babea677cefec7272c49f6bb80620f0599acd8611da4ab4c2e684bb53edf1a9e85e265f39b0928463d91f1f75a6d13483aaca117ec7aefce39724e34fdf2725c5b6af4c54f118bbfc244a6ccc02562b8f3d78feb7c515788e5722136cc665c2d3390e9a902a058b88585aaef652fd917bccf721e0d30f629fa2271a20399f4a2e39b584ea4da18be206aa762d343ba7f1b8c11b3e6ae899bee728b1488b379494f999bcce029f33b1239fc5984203246a5ef9da9828545e0b572c5f2282f9099e98672fbe25daaf201fd9e1d5fcc797cec42570b13a8d80db57269a6aee3ceddf5796df6acf2536ecc03adccbde755ce1c20b1ce0fbe25281913298804f4e7acb503b5127bab679f2197d18e07c7a8ad7ebc91475a61725aeb7215d09a2a3a3b418d8b5cf29f1f3e874b95249e0596265aa7021b1cc6a1082f72dc3cc284e94af615498d6d9c5b7a893a24de803e5d072ac2c8e5b6a95c643d1fed61e61bcabae9dc5132b292a755476defb27ca96cd9fe155d2f1911cd1efe674a05aacde6ed21222a36aa1e7a7a0ceecb379edd15b84e0d4e582cb8b0a81072f56d6e30436d323923dc794586531f54c53c84811bbeb54abd32fe5ddfc9730d901d126b427cb1e08c5ae9479d2c9b4613c43d1e35c3be45a005cfd1a70e3b004ec6a58357c8072412e755a09f280dbdd29915e95927dfcfc5581223c428ed72b0541f92b460ca55b741cf1e543b1bc486aab03d3e1bfa35fb9d8b4cdb193672af588da9f530348b0627b324a22e4427432166d9adf26b6f24bb6ff883edb072d331068da2d4d34f8f150d2c9977cec54f1c8166f94b409dfddbfef81fed1172bc627c47f598f105b1a7f242f25a8ef9f78325989663257fe50f1f86429ce772567566433a2443b9e75356d50ec828ae030e0d61c2f58512fd8bf5fda7ce2472df32cc75967a3ae814c6b3749e239fe45b88cd504e15adb2e2b5c4de0accc4005db3a61dff4865cda5148199ea626c411d33758c22bc1ea150c304da1380df7200df447a918a17fb66ab624cc8051a685c96666c2ad98c37265a8e85247c7954a8c8b3c9684d04e6e7ee6b2437d8d6eabb3bc4ff7655ce8af0db0cf051da5972c0365cbeebdf910e7ee4ef6a371ab5f69b84ddf5b773f4fac1d393b0a7154972fef05d6c2b95bb0d1411ca76d70690964bb05c43c2df6126f8cd1e06b98d8e5257c26d644396af97390a7dd2f85b83a845dc854f7ca2036504f663185fe2c42e9c1b2c4cae337f4498d87f4fe21865ab781e85ae7006090714376726284ea853c23353ab3e9a0857e0c49d2c6c0fefbae8fa903991056c99c0d07e9c6a717e7230f09989d3c8fad79694929c5e2811b51b8a34db433f6042d7ce40a6fcf76236e5f7a5c3c54340018b81566b55f17d8b6b35a0e7d672707dbfaba4b34e667272253f17e580cc2d41f4d6ad6143d3192d9190a6c6ba2c7e3691c6ad0c29880a00d5bfdbbafe718f78fbba873873b07637636ec2e0298bae10290084d440ee8972d6e48557b7253ff22b24bdaad05d822e39383896617c4dc2676242658145c9125621633b9c0f400eb3540f0b8f7ac7ebe1bd2c0148f8d1e802f9fbde49f8e4720d5a6c7b9e8d06bd89c28c4b73f97acb30b73481d3a448d04adc02b3e61b070ebeaf6b5f46bd660eadbc46d9521236e0b102a3eb8e462d2d6ece4934feb07932df3fc81d0364839473f3b2d1c30c10aeb1971af0d041344a26363e5233bbc14524b39a66f5c88a2e354759b2761c95631ba06805e79efe253f56312b1496332b5cfedd670206e9aacef659fdab1ba2c31beaf043ca883d02c88de5060c74be72b9aa329a918ae586f3fc03a0fdd70b15c6f303b8463bd95eddf28de312d823726c5730cd87fc079893a7cd28048d9e466f42dd9b7801c2ec474f9741c1a00007f9e31c3d8c79f9066c825f8ada426bc000b1e5afcdfdda7c5362252f12b1e72c0c67418b9375f68db4a692114d8353eed214a1de3a06824a6dc425c7aa980926d9b36737ad1a13571cdd6e28133a51019cba2cd7157bc74eda0a078693e7eb726fd179a9405418c4480869d5424907b2cab0eb444fe3bc3d2c6267d1c2a57a721f0c18f9609f0661330b2e048744f61181e8dd581f8437d52241e12a8de08a7202fc112f0ad3e7c648450ab6382a4147b6c011d241c47350a7565ff795eed4315b2293293b9425d60753a5f9fd47f0e20d8db0bf0f873698d5cac3154bdc05515faf8eb8d1fd0b65044eb6735523dab992f737be4c3bccdb0453618c0d1a0d7296fbdc6c2882f10fca43a00a0dcc6bf26cc0c312e88a43385d68ce0668842d7208c1fb3f9ec267cc142eb33b92d09e3a251db9df32f57e4e82fe2a9c307d815396aa4869480e5dd838fd1cd82c1b54af9f419eedb82966d21e04b97a6d230f322a9a02dbd4b3afbdb1bdc8933edf07aff304aa43b07ec3424cd092a238ec1a64dcec441080b23a81d62144af2fbd4203c0319e334ac000b83799ee96e21eaa25c4a4f75eb66f3eeb64451eb82b847ee2d6b02f6d000433dfc864f10a4e144a7283d6d2804dfed285fdf7c38ae7ea1cedf97b1937a4877a9028e041b1ffc2f5720efa1060e9517ddf75dfe21387cc847419587a26cdc8a327add1fa12648ad5720212280e020465d89b2ff702e04c5297377f58dff966ca1d312e3b8814e2677264756877e1614e806e3d2feef6f5d2e7a7d69323881089cfe23e789e5e252d728e1d4a4515862c88fe49c99840e971655102ecb273f2fb79338b6beb3b03c30ebc7ebb28e62f1e9ae663522acced00f41ecdd498c82da694a6866cadf8d8737240cd7e381fbd6af63ad23fb226c6ba336c3e65adbdde5f8f7f3b993b2cc977586c603736e3485620779a1d372ea02c7847f7229758854fd26d2c4df0e81ba8727a6695ffe398c599ab064224c2ef7d935e4fdb270818f6129b9a88486277a06ab825bc68d49de8fff7b0069e527c41a26e2e50f83a4ccce481ff3d9b199d14722d2bee70111caa47b2978d30ce7499841155a7b99304b60e7004083932675172675c425b9c9d4a21f0700a3cdb759e50368a8af9c08379ab368db2e087e9f0728cb562905daed32a9d34a87b860dd4646f0aab6b7f50301d09674722355ad933d61245bb23947a6d48b9f9a4a7a2dfd301b1ae123a92895e717e62dd3f03f172f11ae79a5eac770d7076987696ab5a81bc1549bf9d3f467f5730acd5481fc2374ef541e7aab7b59dc3067b9ca6159510a2cc7b40626944f1fc469ac88d2bb572adca8499653f45911b4b48f492bb9acdd907dec453cc4c96ec7697f152d1cd480613a1968442f0f9f6364a48768f63344ebbfc8d46a03e5b9cd774da0637d5726585f196dec3d2bf4075b800ce7d938db3fba9900459094c03e84bf9f62a504202505ae69faef0950c746b64c16c4ace8e82d3979d7c74b05206ced6f64e5672e6107d2901ab8968b3a50a9dbc4fc08ecdce45900632be35281def1ddd20e4727d2483b8072a3d0c42b224d99a8b003d91cabad462096337759f2f7ba4fd03722ef5e7ec3babeabc646c5cf3533a1a3ddd38addf02f9c52e0af18b6755d01d159db25d0accece667a2053f8be88594847f8985769d5b5a333657fa09a0319572baf0e691b38849dc68e082b05ecd9732fb0d67865f9fd04d20e73f3b70a07d72a577e8da6f061c04d5c01796383514a02081ef0e6a4c51899a62a081035c8172f9b2c3ecdb7abb124f6340268f300d0d71447f040cf2823b64bd624b6ca2d85c02b1840ae354fcc4850958edc4d63a80c7f95c1db57049c97a9fa8e006826e289ba4d5bb9865b944165aa3e25efcc6c21c44c7fdf922f864dd767cf06de4726de200829251d3243953539b5f3d4703b08833e76e8d4d4fc4b8c826ab20a10d0005f4550fbca608c5456bd8e310e3a93e25066a4ed928d9178de082a16c32bb7257ba0341ef7976100a0b79a178b23e657986b40afb1ce1dd4f491177bc913c72e2e4b24a813371ceb2fd1f2702b03fbf25ed747318c64e9b55b57df2edc965147c830daa9abce9c9b90d5f2ae68aa0614c0e7cde1367f792519ffe1cadb83a721fe4705a39ef0d36910faf6ee8462bd81263c1a17be7ea55b68451559347e928716bb793045711ea4545b988d60af035455cb65ad85e104a7239a0105de29e3484b96b22dc7e65bcc087c2ae1c27d3c6988325abe6faac6973210ed270b84c0f09f613aada52920c00342bffbfc23cd59146996ecb4d8d8879201821611b1b7233dc3b4a74fcb9671cca04cae11a49a21fa35095b1ebf937642368cf890bc7729cf1c7566022ee57458e62b4951c508487edfb5cfa2809759e9f04fe25b92a2f2817acb13be7ade1e60255a776932a7d481d6644d870589ff023644d5efe3d72d8c937a486be55304643b132dd18ce4186da400ceb263b65fcecfea7f7b88f34330ec59c423ea6fa2680938463c6761bba04752990a81c8653f13ffbad7a891026e5f07aeaf92463acf183f426ee79616f18dada81ef239b24c7f83c057e284627bfff512d913d56f581b1ba55ff860a8e1f5b169f1eea3db4fa0de34a10af722f5b5d34c451a28707e01bae8f1fa223221e4c13f96e732090b7b6756b0c837266a25c3215a05c2fe70c7d3fdc9201c8297b5984e45eb7a67fbe4c8fa1b46b726119cac9d449935d51c2e58191f50e5a16ba648c71f93e465e50b4f8ad72c41d8ca9547061f60a392decbb503154bbf5003b8d9abcba8a9b38cbaa1ae791103895f4889e06559cfbd883ba6e925c19de0e9e11b27f1405cd22792b44f51d5c721ff0596eb1d5c9704e9aeedffc62d2323809b5eab77dc4fa96b20c3e2602e172fd2284dc08da596ca48bf1154125c7d90deb45993f413eb2d39e453fdb05a9198806313713ef7161514c334dc93d9df1842470f756a74d3540176f1c655fa972c3107eeaf3562255079d198c61e81d2ce8a78f9633b28408a6a296f836c897724fe1a19eaad5fff843349dd5bcb4360114ea9dc5638c4515dca702ffaabd4c726cac572859c236b3139df993ed05269a089f2b5503a13b4dbbfaf90a7d85b00ff22b8472328fb99336e4bc200531e53575c3d2f8b176f5f5c70c94ca07a2560da057aa697c2a6967cc5806a0fda43fb3cf5a0dbdeb4ee6ba17c73ded8ba9170aa6ee8049e97682972968babefbbd60ca3e0980bc58cdffb20a3f595136c76572cb12b70fce67cd0fcf9e0deb014e87e2222417f93c7efe4e75956c9e0f5a4262a0c0e5b8454af62aae3511da8b5ce53ffdca97f8c83778666636a9a03cdd910b0b1835f2e6ef8ddccccb90295bde59f4931b6f7f3aed0f8076cd5d81ebb81c7266b2519865e8804de8b2e0440e08fe970228e787887e3a35b7bc3682ad7175724639c02b19d0faf4088443aecdfa822b8925dc54531d37dba6f45dd37b63ae72b60cec849e05cee25857cf6860560629aa18f07e20314608bbd65cd6408b716f5505df4833f56489ad6a7b4f41050323609ae0c7af9fccce8d95a380e92f8233a88b1f9d73fe295f9ed2d4cb82ee319dbb0911f67de3c7bc4d9566535849015e18bdf130a8ddbc74408694fd419068995d4859115fd7c700324ea1b1a8e7982a49093397d69f3292931e62efa15a17cb57462beaaaa67cb578d784a835fa8672dd7daa93e926c91714e94420348b32d756c8ebb16bed9532b2a3eb1566d3cc0b664bafb065555022525493ffae7a4ecac7e54301de2266a2b7eaff2e6134c006707d79bc3815d77e7652db9e26983adb004e845c594cb15dea7b11133d7fbc33102ff74a4122ad2a9c6da79a8c4ed66d14201c064e338c0aaba08b52baa9fa725d2bfac0dd1f130da12013d5b3da1ad49c41e1cefc5e50bd123ccf05e99cf0728d87b7209e27193dc61e569a65f8fc9db63f297a16ac4ed056047147a9b1634f9637cd4f8499282e2cfd1c1e3629acebacfe62d0b0b1d73c118b025e838d4172f44cb8a0a4e882f0527bf5a3e90d9a5748ca9d2193865dcf74bf9b643e56e252bdcd8c9f27c989ac290025855ce209e9478cc65c5eb36a0d62889d9841d6be4f2cc5361991370c7da4d43be50c10c02ed35227ef82e99cbe5ceb4fe62718f6721a480a7fa55620155709c78e75d14bfe6a4142a47c795e30ed6343906096c172f851a2f9f22b002e7ff8d2f31fc512d46c7bb073023ea975e4a41bb2daae276abae1ab9cfa2c223680ecba4b0a499b1343bfdad8050bbd4a8f68c3289a600b72e739348dcb8009639c10af6d42cda777862cb786c56ff72ca099c3a18236c01a152e85c1ab99ee8019beb3d64d668331c1f6493782048298314d7e584261e57219b30befb4a6a1bc04ce77063da65724d5d4bde5c324417fa8cfa472eb2c3572254c39a1afca9d9d633148c9751f64e07694f5868148e0202f137ff69b3d087204aabd4233dd92342f725078477805a6263afcccaffc305be22bbb8b6e5be20d6986f34f4faadc972101be2c643a069316b909c8213266655e7c447bc489dd72b00a0cd7d396de0aa184ee56e6f883fb340f1aeb97316753e59a8e08966384722da13b553afab03ed872d51ea6d1639c00734b6534f4d78aee4113a914b5f7721cfd15402347c66bc0249bd81678f0fd0ead340667a0caef86334e4b3d3594726794fa2693757b4bd70ed0a8870c023f8bb10c3d6f392ee8c321f68bd920415c43f0e0bd990151e11fcdffd14c7f342fbd5a237cec5932034a7be44666f22427bf71722769fe573c04c69f116ba10e274571cc30cbdb1044605454dfbe7dc9089832d3183ce9efd0a2436657b9fcb754da8fe07abffc035f234513b4ba22f3216197bc0a94cbca7fefea5d1c38d82a274617d2343b1fade855493ac5cf044e729b0748a6fe44de31bf7b8c49fcb5b68eee6e1a6014b6fb450b7798db7c55660c95b8bad135f979594e526ad65c03dbd684131bc96528d46deb387d7aa2154c728c11fb2e2fa3b8a4bd455a1df917e49366b6b68108a51fd9352744425d0dd172af7e4b1d2682663806112b9eba630f3fb0fc7e71c6bf45333b7671bffeb5d6727e5e28106186b89f0aec01a28cd0fc2d2deefa71559b11543351ceacd899423a9dec9bffb1c03f0a0e6504ec1d93e2653079227baceabed57a105e815d4fc7426bae2f6a90ca87d579074e75aa3ed382613177b6d894da0b3bf95dd5f57b141039ac0660269a4a4db3af661f84ede67eada66a9c20ae64dbbe7b700023cf057256d83f6143fda3200fd970ae2e5d50f8cb91ed5e7b2a75794d7af9a59b040372d1d0827ec053a7f023b319b582be2d58122d2be31b0f5fff2f0492e3a1fb50086c927c83dcff736cdfbad0f2ed93d03f2c192b0ff25886da8aec0fc60542cb724174ccd355e808d9f7aa34376ab09ab71ab063707a6ff1d1aa3f57c01092d17262b4ba620b50fac126a97cc84400cc09b0076fc291b713c1f9e8c000ef123f7220f2b71dcf7b56c233a9b01b016bf57d4ec1857d89810219a45f59e6bac42a2b979a328a69db652ed7e6780f9745babc6bfc25d19732a63fb9a62967d46aed06d7df0f97c1ef45aa3270ef1f4177002f6990ca99605c5ba4dd2a55084fa7e172b5d593ebf051637f946fe6747b1f1b48134f97418208dfddfd14ef943bfab326ba90d1b935783a2b299af6d707795eed69eeeefaedc1886eace2c3885ea0d472705f1ae5cccac487bed6e54002cac75bea0faa817aec6d8834984806a782ed7217bcdbc2eeb5c74b6f83d40e6a8a210826e030439b373e1a0fb4b315ab3e2d42b6cff62df6be678925b17d6b399ced3ff87f58b679fb8716c1a23b684d67472d8c124a2acc6b95e92fcb379557ae50f56bb772c6fadf603494854082e8c058239df09eabb7744edbbb6ccfeec08d30882fa60d68e17772d7d890940086e20772b1ed1ed70d387873420a16bcea99a6873b3d5e07c4bb281d3d4c85dfb1c6dc725413130f4eb1ed9468a514831f4835f250d5f9c13d90da61c17849ce555e6a7221da7dd850320edc4cf561c2d31ab9e22d74975fdeda3dd6d280ffc20c47bb10f7bf7c2d8d1164c3bfcbf71d64471e2a20b2adc448296918b045e50b1fb59a72ab6c4b35b711592723db32defa108c1479bd367a23670e7987dd1b0c2dec4f72cf8bbdc2b98e4ec18995d9cd87ef9afb01131e34d3c09cc8a48ee3a6e8908b241108e340bb85fa7415d1297a152f064a2d29863a8af10d6a31457c756d139b728e746213461bf7f036a321077f89df877cb8664bef670bb6855658926aa59f5b1ca39bc94e2fa8f92daeded4847162d90427c221dbbff4d0aa4898912b3a1545bcad274f67113e1b9f538d428570db2ac534af21e0c36a9c5e49de4c66675b04b736aeb669441dcb00c3c6e6b893f77ad11d2824f3a3113367b98e6172569d72ebe8d27931613a16140ce084ec16f999b53f1b32b6652e03234a7572aa80b941190056177be116e21d3f674ce4f88baec79470b147360d1b05895281e9cc507289820fc38caf6013411dac27c7c66d4507080c03938bbd3977970ec34f3cff7278daa977d02f33ce1af6b40bf00265178fa9dfe29c4ded353ad4c9045ce0d1726fea49c0e71562f172b35271ff661daa2f5e454682977ba7a8bd88ac6bca692ef85ccd9bab3c58798a756f5111a4e145acc8e6c1d776070702d4ec15f2360c72b9b236796464bab22ca48eaad426984a01850a6234fe76ce789cbad70ae8c372a419febf1dcb8680756ed185336c2e90f72e829acf9f8e7188bdf9d84d5378727ef236e6204a66f4368d88189ffe94801d22ae9b5f917a283b32b4e9414682572ea954d5ce4f0f24bdd9ad368874aa7d262694ca3eedf1bf0c3c50729540e472934421f07ca3e0b0a587f5214fb54ab78f1d2c5a90b9b208ef81c220ee2e70721d4ed5ad136d756bc00087f1203a2776a9cc744ea20300fb039b166272ae3c72154614cbad2a0a96353f7f1b85a09fba57154c0b36b406dc9e62ad1b6449cb72f9a96a3ad23e230c54eee939f6feae9185c3d860b86bfd600694e9eb2d2c4c723c9875fe0c2d943e410216801b9a46e22cc7d5c495b8ddd1326e57bd8d1c9c7215b4eea47b37a724d6a29d86fb8cd2d13080d8dcff8d7d1fa6a5787f7aaec638bfb8753d763f9b68caa23bd1eedb15ec8097a8139496dd9209164f8a07fd8916a426785372ecc79839362b055db52facc9650a96970b7a73521c26260e7e08720f6d360e5e3517159e6ebc74bacf1166232c5786989d9adac494c3f773c7ce672423a0889269b2113e495673267bd4218df8daf2343f6a0544b930ca059c0e72fb8e76e38df71f2d4996af0e5a69161842bd32845d981459dfece66a8073b71fa9c9f046aceea1b2730ed2a9d15c8056ab1a7a4faa90520e989f2897ad20c772058617ee16bcb4f8131dc76a61596a7961f979b982f0eef475f660effa2c0572fe4b266c3bfed3ede4d7d12a7ddd9b4f23fc52552d0c713349077023409d7e16eca541a011b360e3682428efaa4d1ff270c5f7ac5877f6b16f43211342b541723ab04cd53e0b6399e93c062a9b5ebdb505a1d1c9ccc0277c42bc6dbece8e10444b630ba77644dc72d9cc0b697a3ee94aad882b3c835020fc19c27019d342ab64e66d2b22801153287873886ae2408de94190a326cc86c6f782690fa101673a6200bc906427fa60c6c3ad243081795aa3386fe9dde2905acebade282cf0c4d472d12fb287b57d86b9a717edbc8caa7ef5221ece345b93cb4587ccc48959a10f20f9175ba85a61447daad118e465472b0c78aec8dfc32ac6a6862a539dfb5cb9729b52ae76ee3dc45f0dc16aaab785ea57858f027adf3f338fac715e072cc2cd6c6d5f110f392900f285acc7d41323cf7e750824348065ac3680282d8b94613811b2f900b38e11172684d225274ba730fcc1cba49edf9681f2a1ffc51f52d56f72261f50c0b739be31b509d54fa5a0ab381d2c09662dcce9a462f61aaa5b1a1508d0491bf0bdd76450c80c0793c41a927e8ea0ed68cea1bccaa6f9d86d541eba72af335a2cf6b5317c5abefcadc2b5283a381f7b5b4aa9103b27c610dd6cbb5e00e9567d23cabe98fd87b443f3ce5cbce350cd1989a5f2ff3a0a67a6a364883d723d7bf3a493b46a1c1879f6bbf6e79526c6c0c11929f0cc18981c16f01840e2729af4da2098eadaa39e532d587545a776b4990683a3d59afcf19a5522c979f63a418abff6c929858293a78ba2c9edceb25905b393b8a9bb2880b0c7ca41130f72ef22aed02b50df649cbbc825d37d295c00a5f74903744b5aba5520cc2ee68872c73c2c0eda84a149e9d8fe59dd870743688576882e53c09a16fd2379e07e021d76b15bd81442ef5bc49139b5583a969fc56529ec30219c39544b2543d7869e08e4f495c7c063632730923deb5a753173f5a3906d7ee8da7d943e421ad4e50353de23d0627c365a2d8bafb22c89586d4f0afa536fd85b41bacd52dbcdd6ceba286da909fc8faf5f4b25bf1b6e570e53c9a16c127d979814e0e477fb4c249a8b72bc81df72f445fcd96b4f7393487adf9e9380245f600e63151076e6507aac3e72b01f4acdacf1e460f731b22d669725753f40f552aae3b74f6e91c92d2549b2729fbcb3b90f8d7697e021b36e400302ccbe5b3c06cb78c7f3a443375c45227372fe9c897236507cd354b22388c7683495f689f7f8ce38f36a8fa362b91d288c486d782feece8fef4c65f47a7b6c1e1b8c578d001ab32d9bc76f8e314c0435b3725d29285c15e9f3da01d657292d2862468dd5c8ba4b31d864b254b3f5ff292c0f41b752bdd9abcaf73fad5fcf1a228e62a20131aaca20179be580627053a2e62c18551661bbf07219ad80a37a45faae0c1b2b38118eab0b1e219660a52b81fa72ff680e52e0882f43b3c26c36b380a1abd802d643c0f6db3faf9c79d704943c72c96b8c2670e89fec094cff791ea8f97ed84fb672e52235bb82a348686ad45c562adf77d7edeb1fd93a5bbcfddf560cf590238006e420977690a589e17adf6872613257fec0f42064bc60a2d744a72a77056293074bd9ba6a032d432dc2ec6e721e2710e7cf2330acdab6775564a8575e8d4b9cf8ab68d68af83ecd75dbd4c07241a82469d5e6feda917df86f2dd8dceb201e56e3115473c2c2706b71cb21ae7298ca4a895b345ba19323b5dda75601b5bfc132133c51bf769a8e43a058a14b72275e19ef47177421ff8a50b70208cefb1949193053ce0fe2e286be7b5419c9523d2e79495c7614c57337d8d20a6a28dda8cee375ae9905bb776c0df791d08f726bf030f025f2fd8dd2b93c9bec6e5a85cdd5f23d1c6e84d238765c66ae8b1972bb0338ccf8bc69003dcd758d58bdb7766674df8a0158275085e014005d6078494016b053e9545d75d8ea90835e7bd8638b6075b90cc7d5a50b230d7f2d3748729d64694b5a3a6d35fe283f3ca733eb7b33d89cfad7791a5f13b562ec2bbfbd72cd308262cc980167d49e165e79c28b7cae2016f849337c14e358a57c48f3992e5a1f9fd5738f2d3d98cb8c61dabc852a6cfbae872a26035749c3633aa16494729b585fb244a7b876c4daf9d4960730590800ecb5309c7ef82f33eaefc3584e1e6f7f78833ed5504c6cb0d53004ed1aa16f988ef0ec8ad2463df03c8934f875727bdceff4f0c734262c1dd28666eb13d51e57e8c8fcf4f035b6a167c165cc4f72cc1eab69e3030b8a8a33cbe50e7c76dc49ffea9c718d46247beb7cb5a9933672f1986dd9350381ecd1cc0127a73697f13c6c2ef7283832442840f6bb697c883896a42be3529d1bc3fb7927457aed4448934967c6c242197fe1add84d1ea2a4723fdb749fb7cd09b2620c5b178813c967b474c25e32e125df5b92c21b48f90272167e8b2f0144832eb59d1e5987f24d043eebea5317f40f01a18ac3dc0b14083f42665212117c2fa7e9e48d10e5a26f72628bb3cfd5f8fdd08f4d30a72d99f4728bb97e380716cea765047ef632772a76fe566aa54b4250d0e4470e8cef3c2b10cddfbca7b87b2ad36fefd59377586dd326c7aedc5061a04b483956c18bd3b53da5b0ff2e2ccf9ebf1df19d31111812b0f2e38f15179a692a065529dcdbd7ea7293a86f414230f165b226c97fa5668e81380cbbcb86e4b2ac42a5b45c61218a72d7d9fa0bcf5b9172a07a81e24450baa7d450fc3807f065beff528053a6c2f5723a594defcf0aed6f5b604b054143127ed4de24e14a17a156cb887a488ec4b472303d5cfbbe7442baaef6232101605f5a1eea264e527361a1384e98960995d17237257a575b544e8580a0eaffddfdbae224c9867da9112391d21a8c6b0a6db772e890f417d8654b30387e4ce03291e7c341f781da96d12a5cda6a4480add4b90ce8d32dc6d9974d4561d7f5e7d89e80047a2429cb49f240bc40cc5b2ef5cabf0baebd3ce6d2f61a0682f74eb053b067dd7caa047757793ba9351f1561c2d3e901391f1199da8138d9dfde58abd698945ebb9b429ce711d63df3c4f35d2eb0d772950175e49907c4c2c3026ad3f3f061e58a4b2a761baf7c4355f74b472a92ab2a58b79a179fdeaa235d176fc3fafdbb0ac739275858a5c155c2210e44968d2f4a40abbd12fa32236510361e23f3a238bf7e64e7937d493ba40a9241d5bab52c0c31bd9e64e3380200ab39934489e9bb076c461c56e1f39e16e25bf82aa3676b728d664f66a9dd498b7418b4e421eeca8c70139521654996ea227463e2693a7d0c6718e475d28a8fb1e0e05f93577669d726c7b89ba156870ab386eaf77faa68722df062004f21d3c83ed8695e94233e2ada07b69cb91ae72de9493cc7a9100f723efd221cfb149e25cb59ac1080ca651b8d299fcab694326851d62e10d174bb72218cf685015f5106a92e2932a3bfe02ecb9bef89330ca8fad8a809e9c068a23923fd4555d26c5ffb59c07181ca2a6b15d0d575530e475b44184998cc5289481b8a2fa0d4ab4a31813fdc8dd9c3861cc38082106eb8a223571f6f99f0293c9b72f15eab0c9133d61558ab16e671da4c8f14b3294aaab0223c1ae46b27c68f3b5a08476b0e9f1297d06187ae2289966c69b09135311fe9686aef65f9b21c484d1322ecfe16443a7faf4c5c32b94801129a99ab0555f62cf96c5d4df711ecc430728954dc8b01c1f2ca084595c698966102f11193ede1dcbe941a1b1c420b1d1c727e20522af855eaf8a2b8af089c5d9bdfddd22ab88160b364de33ba3dbeabfd720650d7a998156cea11255592c19f04fbf34e0047e48414cb8fca2852271e2b590a77f853db777dc389b60589ae4b8e107f4d35fc8b9532da5f0d58c226c8a2532ca970df147b31538ff8d2f3fe9614eb2dc38a0572b681d9d27fb4482e55316cd9c1b223a13c795372387fe043fa5d6b12ca9048e478811b685b809e783ece7232a93168b16cce059c083f21a61e69efe341b7b75fca1baca2370526bfd94d0995f1378b7600f2fb32dc541d85cc9f2894d98bef00ccec734cc217f6594d00726411f9f37a54ecced515cee1a885de6cbb453dff8d8e3c085adbbf68395ebc72b9177928d1718a6e985247a0fb713e12b01deef368c2175623bde4a930c72f5eeb86c84a1ff22475874c44b3ed875b07acc1c951ee530ab7013edd34b5a20350c12b70cb146093736fea5b831f3dc341dd756ded536202d27066989f3cde8a726b9b3c826ffe73c6848db8bc25232da154ec33ddddc5cec9d096a78834c2855906516fe065c32c9e738c41b216ff4bbb7385a6770086002dcfe3904f5e5200443cf4c5af260c7b36cbddca5021f4dc65a3c80dbefb2d828bbb99b0a88aec477246cee81d10dac5c947aa1ba8795de963e6fa2a370813ade31234b71a7d9844683ef3bb893e1e459de401acc9567a8de2649178df967b09e16be4b34a274ded721e623f922cbc4be743c0b5c176be59ae6575828646f3b04b767736de67962772164c30ceb22c50e81bd7d3d744d8f3a640639af563e70fb2a38b4384535ce536be2c504239ff819c894bd6f729ce7a24346004864173f44e2360d88a81bb4672a2eba9e59afa5e5f0740e7e2ccdae0381c0e7aa6e69e8706300d83c0c9bd6f7233a33ebc194dc9de340db032f96151b61b3b927c133840a74076ff65f2156b72d79acd72b21ca83b4bc6a074d4f4a2c0cde1f6d020825a15510ef297bba5e6728d2adb6666e325441fe0afd76108e2e10331717cd3b929ac004f6858a4139212a4a5f4ef29d5ab1892a47a016806d5a84b73644309bb52c915834c4bb186230bff556d73f1d3d99a45b6185a3c7d1ef7146d373efc7796f2fe20dbdd7bfa8272095d76a040edfb65082ae3ba29e1b4a45b6cdd576e22133e90cba198f39591727574180acefae7ad4a1ad630968a3ff5815c67398da44e1626a2078fffa5e65af3ec734432228a6a6007da1d9bc21406d823ad22d11d7922e021156dd20bc91cad27e20f15f8b34f98168dd02ab3b14d023d8e6748f157aa7f6246a41a8b370b93b88dbc0f7d0ec93459ec73a8d49a513f86ce349a1d645301b4650277aecf585220c29e9cf13bdeb3accae5beffbbfa5b3d82fa970286926b7ecf746389ca721ba1ea763ef9a4ea1a71367d844afaf1450dbcf3b7d4074bdb6bf6c238b4c250849b1f7c6bfdc2f58fdf698f460973e00007182c77bc3f8ffb1630b90775eb703ba6a44127498391b5b9dd9d18e1ea02f5dec44ee6caff86af7f4195b324747246411adfb57bb150f7c81e6d740edae3ebeae6541b1a007f9f8b4f5eae4b64721471cbb52278d23a9a5443749248aded96bbc08a2efab3f1c2bfc2abb159865c05a91347e724b20d88912e61a4f8defb1bed96f1f0d1188cf6c6d60b768888728f17145f6a05f963af48845afe9659e61a8a3b9a73fb1557e55e9c026b2dd132de007a05d83471802a415a4f7b4f8d9652f1d54ffcbc99ac939c86ec81fce0727caa5f7da138b17cd886abc702406ba01d22db8920c1274e9f4fd9a0cccfb448dc6b20ba599e781a84a22e2029c0e028ec17b302487f653009e3a30075810a251870fcc268a363bc1bcf27d3ede9fd42a04f3d77c2fb9a1c46bb79cde168082b818af86f41641f9230481b36d821bdcaba3996a27ef3800d938a9a0db602bb568ea790b4ee29505281fcc20880e31c45d4f7803ef8019580bbb1e8aa2776c57282ebfa135f499243916771b5e17e5438b463da80c13bf9742dd1b15bcbfe1008d12a8e75e549aa4667e90cffae07aaead15a246f8fb7854e1f9ab520f89b3c28177eb99fda81a13e467aaf3afc627e7446e9f075458ae5912a0de85b3dfbcf72f55019b96386e5aab161111a0cbfbeade619190817ce06547f6f5507d1563a31e241c8c04180355b4966a2eb98b37ee9b8df81ba40418fdd4027f4adf4c6195f7c16f8d866474eefe023dfebde2f9661b7df691d3b7ecb5868bfac79bc3c5305f366d937f690478904ceb7c63aa7d46613c54e16b448aa7f817711ca4b28ca72206a8937e716c3523e23fd1e665de2932dcf10767968ae9d200dc44f0d3017723318333462884229c42bc8762f9ade133235ac3337ff6231559086d37b758a72301feb14f6cd4fbb4e1bde9eeafa4ff72eaee7b2b305e532c672c9a316f2ee72992878cf8178f174c191ef93a95f3e7d368413ddac28fd005bdedf77a5869b720bc098e791dd6da180a41f83a586de84b7ccce352a21eb88c52e1d91187c6d72e318fa41d13c178bb04578b9a5134344693b99a682aee1731177941035d7d21bcfde90a88a7d3cb02bfe50fc935468ec9fef8499d6c73f995bc3085d159461721c34a88a49c964bb8b61aa9f4514240be3d4bee612d9ee5530b6c007b3c6b872c80b133e5b0a68d9d093613d0db5506ec664ddce318b3d301c7980221afb9f4d0075033c54dcf594a59499a5fee064607d456c31799392af35ac3663b4e5a972630556813bf18a15da7726eb688d6b16d7fec508f4f09380fd7569e8a411e56752b006b8bd85d80e05b2c784b6bcbaf4a40c8572708598e4ab23785a3a7a6672ccbc95e8db8cdd77bca859688bf22b9db6cba8f857196de476044942957d1a72360c03f782d93a36e1bfbdb06a795c6cb4f14838ae3a1f2e773769c88bf7fe3efe9e7f614ad65b3145f09badb3e2c59eec5c6499b9c88dcd833057e75dfc787220d56217cba33b0a9f1f30b33b6160a9fc1fb3a63bca6e2db8bb738bd1eeab72c7e135fcdbdf2b71c8043ad69c974f947ce81d360b51679ca851c9ff76032938809db2d547116f53c9b00aefb059b3b3721dcb2b79b7fb01265c298eff03e372c213a96e19fee4ac2aefc627ddc4921e855262c4cf99cdca8c1085e238228272ac2cdf22611a1d443e6a050ab0cb97c1cc3b68fa7e3b8f8fc3c0f657befa387263c44605a49edc94a685445bd9a3ce8be08b0a506b78315e65c85b910c098b1da131f5710e6ed2842bca7f4b517acb4c3987d7503bc57d9205cf47581f90fa72cc44c4de310efa1759d3cfbb961a1c2ed4465af51da75c75f9a5e84d845efb5ac5e59072d5e1710b39f3d7d71f1fc7bdc341256c0c4ec5d3f2e6abea3439911d1668ab49bb1c7f95bda9a1ab7bee9e45cc5dfcd27c248ba61fdc7f9ac3f11b5ea36194f8644f77cdb00fc99c33b90197a2bc4121dfe9f94bf36be998eb8c4f72cdf63effb244902fcac56de3ff6aa62027782565f3a9f7f437a1760f1f505772c57994c0a06cb439e062c2fddb39e3dd637b49154056ec801fddbd74f1dd2f72f46250fe297ccca84ba029563a42b675e39e5f1198b37c5c3f030815499fdc3bc952ce573deadc7b6c937a9c8e7ecf158a63ca20f470c4a07c4484d4ace1b172927278e9c02c8072b0aebfb1e5fc2ed9574019de443ed618219620b0fff69072d944ed66458fa383a4660feb96eb8bcd98564ef01e7c89f3444e7fab1dba0b72a50081ae9978d29bc24d769865ba1861f8d7b3b06b413947bb21dee91a62a67246f5230444c4151cc7eae212e4a26f71e72023a0d8289a2b055ca0f6c7bb98722321c73f86e621183fcca1b37ec0abfe15a00732ea4844400297f2bf6c5db972246d7a9209c9ba7bae1298a5890ed82167c3fc0d1272d080500834f03cf94f728d525c14a23f6c1e122bca05b936a8e908c01f523b3a82b12dd48381745d635a164fcacf67ba19a8a8d621d4e45bf2df1983a0b961af4d1e9af9a92c30b138510436ea55bdeb410feb802150a70b0ad336298b21092ca8fe0bca03fb1a03757249d9abd297ee7a10fbc26d5a94d5665773cea4a24157bc72bfe9291003252c72de3f423fdea5dcd049111f6a3b221ae3b7dcdf43bef4cf6b7beb11029b644a436bae754cf3b4d5b04204fe3469421bcb0bb7c8d69941dd308e4626a670ae4b72e328323d930bb67652c8ecba5dbac7035ef96431fb0d9bf9dfb6cf2138fde0723121fccb1408adf105b49a806cc0ee7ebd7c7ab484adfe6051aa06ce0086487204eb1f8df55c316d1f418cd1bf3afa2ea7cec1efdf8096118ae069d17415c915aed0a8d9c54adb1f59ae5386b1d33b0389d0691562cda607fde794ca27ff7c721fd4403f04dfd225eb3140c9db1532e1e3b92c7469804c5ac0b755c7f862b149fdb20371236f5afea936d4e96cfdfda5577f34711ac67be48754270493fcfb728e3d53d27814c643dfd7091e4fe94037621fd35a7db092a9ccc15595cc10cd72dc0895664dc68354633ccf379279989c87633dcf50a96b27c892ef252be4bd722243a8760b3800d58e877f93f86f02ae53b53d89268587265836f8d9fa7b7e6c46e5e86772f824102e0c23ed48b1abaac3bfe617d3870eb64fe84a94f7264c6059af8e0f5dcbd59347c270c76c550d0accecf6d70ee66545303834994460f1600e800b24737b86a2797b2015ba51efd3974eb8943ce93e21c647ceac72ccba25a6c6b4b2eb131879f21cbe944114ea8a50f75e011be0bd716f3cbb13f5e8092cc1acc1cf64f2ba9f81a196cda8089bf13a7a0e2d44eb10673b58d786dcd61b0bba432f352bb2a876ddd23879869a02d78ef9731dd7d772317e9106ea33e7d80e06edc865071617a7bc521d9c40bbd24f3e95684ca4534b9f2dedfe304a955772666ee6b2b2cb48688cc219c005bb1f304bc0b5df49cbeb4cd239c1d61b69d734ba0bbc11ee96c5e5326ef2d2fb2506354628ef079c52d068223644430b3a256f96255fd0ae7e16c7f121c796df218a5f8d75ee6302cc438aeff7051261111372be31e98d47b31321b87121c14f615952f62bb24413a532d1c4ed08768f78cc075e8ae105b0193a5c3cc2d77ad2945013f2038fce718497eecb4cc7fae4d838720aa31cb07d1291603e499a31e5564ae613b7ae89731c0d672ff485b14e58313f2a2cc64597447d8c0811d5a5d12d411719f00cb1605c67531d8ab72b430dde7272818adcda5ad27e751a950a275704aed487218c3d32b318f5fd6faa6ce756729dbec22ca180fb3a0c15e1bbb56a8f5dafc221848faaa2b036b1282f38c07d7225e2ae576e2fe5b73cc3b6d6e0999928fc778079124e54f688371a61c4ac9f72de1ca4a7afb1d49997f8eac11e121af56b4bc72344ba9e7a48a44c76ff3dd972cefe50afb9d99e7aa7d8b091a1858df807cf329607113dcdde080c9c409e0a72f598ebe557d89f9710b0e0a6f5313e443d0007b6c2b35aba9c358dc59ad9a850ccc0f6113e6a64d9a5fa9f1b6d66fc3f34de3db855db7a160a0924f39bc55001f812de5fbc964cb1ab17cb08046e9122d87ab4eb77759c9fec3a795276e26430ee288fba563f69c7e1e9c01e0f6d66aeda73f8eecad7d90386355f389fdeea267091c91e7c72ad601d684789cd382c1ad5eb2b69a0c8c32d59afbf71e2eedf2bfd5a86447751761645987ac67632141970755fe2eeac24e02ea9736a5ccad272e4307c33696a1792e871cd4549f0e1025f1470517b3a31c9c81b916ea296f50fb1358855a5f30dfa7a2b7557ce2d8e9992f32f8ab39a5f141928f8a8f2ad48042e9616bb71d8fe64a770be846911cbe0c5936e5e511c86ccbbae868ff56e160b15679cff9a89952889eca40eff42d1c243c5cc7629f5c46599bd4e5a2804b54f9182617af81c8e545818a17b8c5c0d9d608d5854bcd088be54c3a897dddcfc725ba40dbbee5292d9029271f622e9746292925522d5f72168284b1b4e75b630721ad2bb33024495a26038a904b8bada26ba3b867099e0a21c24e7cece31517272e1561e86d831264724070e01962e23e35dc95f1f9b985d992e39c39fe74c5a6ace6bdba238d352378bf14f993803383bca8c7810d06af3f66dd97f43a157387248df09394da1b51ddb481871d60f36cbda8d43368d329366a0170f449ff8b27277119967de8ae43668a52c11718e6bcd40f45f8338c47834d9622e264ab41572455232cd214122081b617b85ce50a8f79013e63acc1cf811bc4b4f6233bf5f72cb24d0948bb4c6e00073395eba93aa5e205a6e13091c2063507dcf04d85176724fc54bc1ba501c2a3443eb3764abe4cb7a8dcd1d6d140601923e6980d0f01e1e1e333e31b86c206a9b54aa1b582cb403637ef153a595479399c64d81dd366172f81aa60e2a9073aeab047f4a09382fd62e33effd965e3e1fe5009660190e0f0229480f0aa89ec2e2361d1f211b7e4b8917371468acd62bf4c6a6414c3d8d235cd1830fa4c9c19db282cdd8c1474ee59a3a5c48ddbbb2578d4bb5e5f61098417273360780e62377a639d2e3467dacb05d6c1b58d8cacfe28040eedff99f33456b58bc01fa01e5b904063d991574f95f971d691b78bea3a79779b16ff007e12c721774196644ccc006e27dae5ab322cd706912b639f179aba177f48f29ed594f72a31a244559d357ef942865a2f142572809fb791cebd9de66827ff94b82b26e01e053756ce1eb3a431d47cc8028d289e6c265bcdb08c499433e7c847050767f726cf00fe9cb0adcac93e5b326b226c9c0d82545a623c5a9dae9fe8a079d776d7271a66ac281af5e4cf522cb63f061236857cfba0162d78ace2b2f67331c1da953630bffab0f2f922b997086ff6a22c5269598ae44bb5c0d0abe65b08954978f06dc264920ede41fa20d09ffd0fb870399a9c8c308f3fe3980760b51c7066632722927d9d3368c418e4423339b320e8fe257ec0cc84159694d936201736608847221bd328b89c9f4b069001e172dec591ad00e04b8c29b76cf37d119bee77e7f72688442b1fcd64d3008faf680d908f6ca1358dd02885b86a2665460b7ce14f472eecd6c921652f04e833f3e4a375476867af25c381f4d5e088430e5283ce91872b4148f462a75968a73186304fbdc9dfe2906317c0fec885aebfe032d943e1d72734958746f3fd41522e5b598419e6a353fe2f2ddaa7c8ebf9381c2fc8539e37245337e45c04f5b95af3c420f4f18e3314088f20940dad3cbe416e8abb31103726d5546159e72e8501860f753ebfff89b5b3d85144c4f60af3babdb9cddb876728589d5146bd83f61c1d3e9b105679e858d7901d60464b7492a04fa0327e6c45d9876d80d973ebac7a0737aa646fa5e7e5810b6342cd3a7586cb8be990557b312eac631bc43ce94e63fddf8ab609ae0864e76937dacf48a7bdfd2b1349012a072b4320e46ed4c03e48ea63bbceaa1da0d4066e6a9e44317789941f1463551cb26bd0bd2f2578887aacffe5430a89b4a8d47148cff991e835959a2b17e7a9de81f0c75a65132ad0fddc5d7d062418bfa1d618be737ab4f8f761b919f8daef8e872334a88e2c6d983683bf242c945469edf3f55f0e62cb69e01b7254ababf8ee119c13e3069ea09c2cd7b139a5d02b4f96d54187b07d65aa5135d66766300848b42ab535159b7cc5ae450854d87018fedee5ef7f6b60a6d85603eafeae0693d1e722cbc40fe92ccc125eb7ea32591b9c2bf49237dd1b8c65587154ed2fdb5fc1372f517bdb8b9010ff6fd86539f74bb407300d869151f30bc89b0c125014ae192722b6fd285668928307c380fecfb62a06d3877ab41954accf3ed744feffa061e5e6bc5bbcc3863a21b52bcef3adb2f3cd1ab173a12de82d9730d17ecbdf90f427287412bbc05c3218121fd6667977197ffdb2243a8c2b78cf140187e7285d60272e01971612e8860843f34182e4e3b844b2d96292cf673df0ecd8debd70e46917266ba748942e589e800c851e4c0dd94ff1049500d4a2a01d0d008a5f0bd011b334e8a60624c07e5e8b6224938f85ed18ac65e9c6ddaa703516b191061e2e5dc1aeb53541627a3368e7337c74d50977d6c16c46d810d3403684f75b004828d6965020ce358b313a67966f58fe4ec7ce7c4e23e13b617069c4337b8e622bcb8fe6f0b85aab1940544ac79ecebdfc22f2cf9fcdfcd7c9ee696151f46958c793b472f4ce74eebc83117be95e6e5c3b1c673b229efe1f45fdb58ddc3d000b23f330c729bc338709d744661b2f8950f0875e18364dad88ab4779d55a3d2c53135c363729cc05b62d620924826e8283246bc6317964a814570f1e8115b9cde94586ad40964186b88f191ef2197ce2af82b143bf6cd85ef8c3c744c835757c08a71034e72fbba43d246b4b0b00f81d5f12e65b54db8ed0e00503a31601d59056d913527438b929ef4f866dbce293d1eefc2813918087df4dd648d08c405b2d75b0f3c7d1cba2254520fd024f4c567f4efb46c412ed662a8b8203e12b99e7d9fb3e3974f0cfd55762f6a184fd18b4b4a22365056351d6e300660b7f3a5e449d508df9dbc72fa84e0f3db35a9ad906e5341fd3d332c7123d76f6bb5fdd1a7d62ca40e822e72d24237f71329da493c478cb95f2393b3df99edfe89c16fcd8a9ce877824ffd42d1bcfa0940fbeb48c50c8055172c744721cf1b36fb5f59dcb436b57765adc068a72829cb6ff39dd76ce198d32f952994bd569a68bed42780e7a50b4345aaff72622edcb56c0d10e569e4240e6a45b00be8c580e5a1954f89ae5cfb11d85285720994bbc32ce0b74049663cfd9595a8995c2ade54ab457c110b94b330324b707237fd1f52dab4a6b4fddc03fd13bf280eb1fbf62fee10df10653fefcead4bea72c4a8e6b1e4653481297e981de639cb32483882f9e720dc676f02cb7c954dcf72ee28d9c0f24ddac97720aa7367ab94461f6d99cd0bd3b5108a72e20edf6dfc72cdbc9cb7cdd3b43560c84493683e04f3a95e90fddb5f3b972ecc8e2919ff22723f77ebaec23d0fd866e5a1021ffc267e2e7f03ca071dfa385316651dd1c18c0414dcde06a3ec5ee08b01d8515f1c8cb86f540f823d0a321c7bd9043e0e5c8d72a18e9df73d62bae05da4938e912d47773a1fad64e5fa11259553149d4491771513032fdaec7f09a2c1ec3e2ade3bcf63b9e4657b33b94420c7573302c8e2a12b7edbc9e48710f8b301c5b949137ae7004d55f33751d76cb46eb9fc6ca068e141aa472d021c3c316f146d1652bd1f60dfb96b7c021ec83f2daebd1ff6db12b84692887e7ff70c62d0ec93f2a0168fc1dda19f9844c18d87991205bcff1bcb9e7209d485612b3f626fe769059c2d7fab7c56c65b0d597f132be571bd9b5529d272c7e91b73d1b77727b048926551c4f6c2396ee1f8054c666acc32a169030b7b0eba1aa56f51943b39cc0f39a344698470f2fe96ac82dc85985395ae87dc8ca672b2866c145eac47459da14269fe36d7d631f03a3a2d32fbe4b1a6298f2f81d87288218a2cf520e747596bc1bbb36ad86ef0c9273d49d459278c8ddd121b64d17207b659d224d838782aa9c1ba7b0fc8b59f671d132fb91a1fa4bfa3c003dab6728cc94e7e7f1c0590388364f4210e0c23b0a4f6f02f3c304ec536c65c5531231a2a5d8dc163cbc481f8b546536b7a62c82439d479a7aa985453059ea705bfa3722064533567c812743fc2fded766785ff0374666fc427318f37699026e593864b550b6e8c0306b55e4109f3f0a6490f38cc955d088ada1d414324292f81f86a0368cb504fe4fa194f6cc73d0e2a140c13b5a39f7df17df6a9a0deda945e08be25cef91485eb46165dc5bf172aeaccf38fd0339e051b7cfda6f47d3e1938eb451aab39f93162ed9cf0bdf9ed0c1090f21e69bc0ad59ecc7a85f4c4e4988a137b16c7ab3b34fddbf0f61c09fa68ade1bb78f7b580708b02a7ea67da574dc949887241b14ee6de3872f1756062d08c52c9a215103bac400bf09a3b23576f1da89072f14c6af39b8bd3c497072d50b6b2520cf57dd3109a7b655b29cdf5fbc06f72726ed14b0831c5550afdecd23d70e1c14f1760beafd9b8da9e61b22aeb3c658135fc9bba4cfe580e1107f01186c9560d93bf077e96203f77f28a225f548c0ada728bd80468b734dccf52e6c9a53eaacc31c1970e83f71928d03743d5ee30a73159bce0a6eb50b7149530f207285f288515c13b31db540acb0d8b51da070e6e3272d94aa247b359ea6ec91fa22d11c8fa0d19e46d0045191502d7341486126aa5271903d562f03d25e4928194ba9bf6355af2a2aba7d5c88c48791227cd8736f0587502055d83fa71464a9bac3f2576f99d8e75f7b894c7f13e25882a0856a75a72c5b4ff02acdfe2c21f55c640034f94bedc315faf421bc87473155df49c4fca0fa8480b932030268be3c392af1a1e56ba971bb78f16423e3fd42e5202ffb6aa08e32569796f27eb6633797db145e599cb011ef9b3e565725b9f09d380e0acea72d6f24b29689da87e3862a732777247bf9db4e16567211675b87621984cfff3304736f0cd78793c3c2bf63f6b9bf1a933be4ce753317187133f19aff8c4e94e7278e17bf839be6bf5d1e7cf2626a22abd5af39b8f46982b21e4a3930ac4f16634fe41991f5ef31d04eb6d995bbed6d940fa052b62293fa0a48ecbb0ce731e3163b99fe63e3f15e479d6c419397a2ab4bd2ab4ffa08af6b73f73a5a0779c70c6720e22c6f16c1bdd29c9135518b3caf0d47e0c303e153b9b1e9930404129b19a266a833a9cce6761e8472250a69eb445919c3d657992947a3d70525d72ced9eb1286171fe4217cc0c418ce80d078804599e07cfd7f5da3cb383b0c5f394bfc2272dd8bbc3c985c9533f8b15317ac631c2b1b802033ad530dbd2d9c9cffb43a6a72f6bfdecb194e7fb7b773747319cd324bb984a81023018966ebe1c7cc16060f0cc6f149dc42272f36ac1f64edad097935391e81e055bbc5021e28100c06361572a10fbd807c95f3b4b82d922011743f7b88be277415eda02e98402111631abf081154dccd90fbc26cfd44bb92ec220ee8fe658f3525fefe6fabbb859554252b71d22e63d8dfcc45add6277dac87c56299a7d12efdb10e5de95bb326a983fb4172106e28f4d871e92e79b5135328188fe90d1e52741d6bf0ae846199855972823c23c5f5e99112a10411d3da0db8d78af6f8db1e0d033f9fa3affee1455097d245c8c866a3f13137424dce2d6ba7e7b29938aee5b460da11aab290d599cb903800b118bfc5135caa0bfbd0d68782be27dbd8affbe0cddf26d21c33f3a476ede172961d6ee84d2784b13ce6c1ccbfe712d33624cbab6988175d4d0f75bd361df8723fbb156b9e56db6bf411215028cdf60dd15f244a3d3e3d2cd17067fdcde84b7284e548aed0e7ceab04ab9518b0eeb181ec36d5d488d000d763166d3eddcccf09ad66e7eaaddb43b45edbefe50776105f955569c8ac9a2869deb2560c342e65580ad0ee1131bcbd546b60eb9b2984281f4563ed6659a536182648d8e669f32d72038ba9c7c854ba68077abb27170c8e2b25ee747080021f55a25b7cd344d6b91f3589771deb7a1c90de762a93de35077dc98d664fddb87d5773d2795286ce6f72f9dea443bef5f73c81e94a6dc1ad58651704063d95ed4257c7d746709e377e72bcb06d6902fdd4c551b9277760686667cae64e7d47e458fc36c8e0ad1e4d8b6d9ae2f52b1f72969f7b78c290826afad9b3d7338c642362f77ff1ddb7d82125728747c2588c54734462b2baa566bf6812fce288356918928807a26b2b2e8b153b29a63fd976d56b9a43441afc8396170037acda59f17bfe2dce58a75755b7ef7219884fbf0173d1f1234ea5061305956bb0c522c8704418936168375e14fec2323dbc3bcedc2214f51bee4ef2ef9ab3987869dd8a9b5f59ef129f22c4fb336316f0334971fadda3b63a23be84afb433d928be4d588d78bcd0e77b8300cebf46721f8391d56de21e921e927b8ad9f2c4d1a0177958b55bdf342f72a998ed4cbf72fc890640a43ef79031de18d4f858531c2dceaffb8bbb41f4298b17dd65606c6e0b4cb97e638ffa7069a3b61fef4c189c4e579d65a74ca6c8874d2bb0f7a85472fea931ed03f3b1613a881d2bc854ef2e92d61e1a0dda3e2486be75d076e87d64d487bbebc32b367f2635b6e6146c3bb466eb2e83332c4ceca85eefb522c8e86c4e57cc16c515e1b8b5696099dd960aa25a860ba724cbe466ad64b6df6cd3c372670bcbe315377f32cc9ea8e1922780af32f59e620f55ccd76b04d69a8d9dac161efc40ae38b287bd3c2767dc249adaca4302188a87b0d220897c81eb18541572762249efe8f3a3fd85eb3541d69d7dfe8c280b51b231f85ca864f8a7bf0588720ae2f0e414a1429bfdd2760e1dd9d71c3d15a67e8ec6aa21dfd8aa65228b367267db90a57777a96a9dd7cf359ee4b58a77cbfa0c34c4f5dc8727e66d41fe90725f59c893e6eca4c7cc9b972cbbbf032e4f4814ccd78cba02eb58d75af78b4a39d9eddb6f2a8a49d2812206e6fdcbf09263b2cc36ab5427cb5082873d4486aa72ab3ab79059825944166a81acf130fc06c2477f764abb43f5e7d625a7bb9fac0f202efdc775f1d732291bc1b5535d75c785ae8639804c3b68d14d2cca059ebb723518157240176c2ccbed4ba2f8a8dda696262b3bf324ab7dc616d47796c706722bd90b102dec9f844ee7ea0357d0580d682293a01cf60fb5d37e29f3ca0af172d2046008c56582d6e4a31e5d4c3c71c828cc945bcea31c6a84f3c1f77b1bc119c059f227964f33be07903fc6148e80de27e77c1c65466ff6255513856ee15e6a9caf0c1f4b1418d7dc6b224629a25ac8add09e601a3e2396ccd240f31b417772036f52e36d5c2741a6369629058cd7c53483d7c52d7dc6878df96f0ad5af2a2a3ce30a6a769f0bc6b544040e17832af053eee043bb2f67f6ed82be7be41b4d6cfe482c0171e56972cc9ee75e23019106648d030fd0aea9a8cf012887e7271a014dc27101937db1fc95a4225bc5bb4312273b14b3f76a41889b51413098f34d28128d953d037bb20d3839117df080b903a1865b54b415725aa3d2ad2e424b5772e45afe829548fd0db818fdad6f812d0a53430f9af8378fae93e868740470bb409707681d899ead24f98d20782dff3c48dc15639d18f374b90c772a5e984f22721231a996c6dc01c5c9cf5c3a9f544488435cfbc4a521d0db7ac0e3b36f2b2e06fc14da75872e184dae304ad5ed4eaf3df66d11ef28fb371444fe89ba7773a654b141a761089464ba90d3a824612842af758f8ec92ba880b334d8f01f06d19c72d60167e0c1ecece50d35c382c1855dc65a9e85874665082cb2406dab6886c6729c5257943dcfd5977d5aa48e2e9c70a434ad973bffcfb94c65bc9b1651324c72e4c8c33097f3936fb6a498f231485fcf2109cc6afac4d88b43208354e5f06372f8bdbb97f86b2449af36815da703afcfce111b065a00f319b847e21886dac072f785e844aa80797626ca744854bdb1ee9678cb6fcb12c7c4ccb08f3d7b7e05724f4609c5c5f06aac0246c62b052f3b4a3935c04b5db2846a1039d265426b4132ff10379577236dcefbf077fb5c284ed5943530a4602405d9c9e9ad8e9281fc723e561b1dc7a833fccde40417084236b76c4956b2f7e401e525fc11d4c5d74a350c686f77e1b1af71eded87f8495d71a2b9c8a0bfd00cfebccdc57d405b3b7e3cc184052c76b24da1bb2de47f63e64c3da8f7ab756ed6ceba7e4dcd544ab40a728d66bb6dcfe35f4bc3d68fc3f514079ccb3f5622926a8a403fee96ed2ede2c03183dd104c7ec42a2203771b20087f989dda0953683b718d1b0b6bb61f06f77722175d4aa48d7aa8fcd1544b75db1f6ca96236fbe133816fae82f4bc40959e901025eb36855625a61d38d9e81769f5e3a69e5ccc6c83f5f8c1dd01ab7eea42024d047a9738e6704ac22a6dccd81cb983a4576cb9082a06d2f37578c3bcabdc958b429eed5beb0c01eb60e0dc29832d2511bf5bdb1613c6dc7a6d3a6ace9c62e079474626f61c9b77ff04a47281944651a758bac702adfe0dac0b051f8da192e72befb820983b8143370d966926334df979759355dde38d81c8a108223f7ad8b72b79006b2469abd58986200d094cde1b54d8fe0c18ba7ac5b62a335bef7ebac72399bdb0852e101b0286d2341a19f9c5faa19076301ae7309bb18bbd0b90c4372c79e525abe05ec539eb8ebca329956ab32c325d71998649563d29ad279caae2f398ca24b9afe35af75baf84b1b40129de4225ba11ecb7a333375f290c2d5f272b283718fde98925dd201516c626fe6aa0b9d127913d20bfe8f15aeeff2396d01315b5ef8824d7fb5bb32c128295bb3f0a86ed0e6a82b478b6b603516bb303f72d5674c2de9636ec161afb631bf7d9756e8f86caff4e90e25f9b3ec9134ae6c693540d97000ed188fe9352e25d05572e41968fd91f11e1b42839c5219c7d46e723fef7e26db6b055cb3b48d90366db5cebb31524f77a08f0ee3fa7a7bf06abb726ea3f6eb29c3b4eae311c3c9b9ed586dc0814824e47a80a2808c00c5bff8ab72c5a378e8495553b1121a8c12b57c0fb2e08ae930269690324671a02de4c5230b57802bdc05f2e879c6dbf2bc354493ebffafda0095249dd15e65d02c51bfbc11c5f974cd28b1399b50d5da0c91ef0cf62ac22d564ac23b0ededd3faef721e30c1a71a898362b1071530fed0a9d65fc3e9288580619a622c62c0a87aa7d188327094cfe23125f1af830cabd5ca8d0250fc087d20d8832d776ce3e4913a1a10e00e4a95f8d1323954e84d2cabd3d65ecbe89db8fd49c59bf3cd4288a35aa4a1c4de764a96b48e3b7a8b2f8ccf884bfa19cc8b89b32f80f49d41d0e3c0ad19e4b7243b2d12d08b7453af403b70363899b45f0e3487327b653d2c784e69af4ebd31edae6d80448c4e12769380b72a09d9a0decf5f686b321cd1b01fbe0cad99bee17805a86952c27ae28575763e59e79d46568febcd9438259a5d4bb08cf1bdd8272258ca058b0955b8e7449542647043e9d0314a4be5b535e623dd7cb2a47b89f7262adc1ec685a804af24511b8a496a1daadfd21983f085c8aff92ba03b12b4720898c23164acbeb70b08a26881099c2ec20502acb88b8ca8fc8da171857c75a6a283134f298eaf9ca4cedba56e2eef0e0b225fa3d40c1e9a0fd79fe67ad9030724706af594a15a0c4ba021033ee546bee619d5f6a33a7b4947ccd6b1fc3bb8d72897c288092f3ddcf697d3d899acb1553d147865b538b0fdbfab8103e4717936218bf8df381d4943a59c552cc5ea77946d0e45c9ea7ac248953f0a231298ddd720e683dcaf33632481e09abffe556fff0ce0d4dce784779f38f500cb5d833b44fd6b1b18422dede175449a40bb45727df1e9726a6d721a9e1f56c69a5536d5572811c9cb4c04ff74a3e8925b46e86289820c4a4c8605612bcd0b1a0c417607172c6ebb63b84c2ec54da77ef2bd2a26b131de5ae117194453184de69996ed75d721e867664ee7ff8697be1659ae14e827c07fa49350935473a4966eb8429c281120eac781242ebe67d9ca746ea2847c3b4952235c456732734155811c2498e4859752a2d05a2d64c461811a217d6c2421f5a5c2e95a337feaca8fe6c664d03d272c47d0d0a6202aa674b46e32a2e4ab47aaa7a4c215daf55144f5e168949846472022a742ad79f9e192e2d91da02ed71d3b1b806208559ccf0687d8c4935644572275d5c73837242ad2fc71c203d8eb2865fae12433488ccc45cf679971703be72e2ccfee5cb3a377487c58db553a4cc9b52b5fc6384d1b2594bc373e4391ca7729c760d2f3e5e04087b0aa1fc4b3dcb4664d08edcb693f3915756a50a51c6ce72f0ad9c5710db18ee0aba5ef50dfb4e1981979a3001c582ebefba860907be940a53616216d0548a8aab8e32e43e145864ea326353b1ba5367d220f485d668e300680d137e06653af923b661bc75b6eea67bb7dda5a01cd87d7547b4e8683b14720a2a19864999b53a225fa77c028ad6a0e68dd11858310a5548bcc8e5b71b1772ef7b32332ec8f05b37d41d82e0e5ace202eeebc7429c16e0b184d4b053d01f221c34bc7d7697c4ba2f533df84c310327535b794bf16231e042b9a197d098780ae23138f7be34a450e3cede4ab30db6fcd18c8ef7750ec2bb003c07aa6d7365023f36885f6b55eb769e43106623f04f508ac5729680dba9e70bf565a1179c9c27df3f674de2ed1368c2405273935984d955f7527123d83aadc5f08e10ef22027290608a63b0f6a3be19dee740a12db58736397c2d67d2525f9b80b4d1fe53ce72c933cedfe0471492baa0d3427129d227c352173a7e5f8695001eb5807b6a3c722d10610fb363ef7cdf7b77ff7513aa058919f239d587db88357dd52e2599344eda24f8cdad65b1f3a7c31052eb649a8a830773e868715fe95520f9a1f5394a7258ab0fe840f2a07c690c9c8cfedb8e3d9ebd7ef53b6955c060f58fdd363fb3722254084345177c4c4b71495e3ac90f1b06398c63eb340e96813f306c74cb8c7227f62cee640f1b4cd5b8bf54ce806f587389a571aa4150df81d7bd4609346d4b127c1ee3a1ab55b1b698ea6ecb2550f437e6f37f4dd2efe7cdfec9b4ec564e726367b09d67ff116ec5555008dd4ae1c91325078f9c5fd22ea10064416e6318153791eb5545c2c33a6b47b2fcaf3b967563a1cc50b283d66ef9bdeff53730486821dd5488163c81854af51b59121dfc55c1f0dc4e77c2cdc85babbdc2e872e972554524d8018c1f58019f7d512623ac9ee63f278e830ad687a25b6b7fe4f5c772b90217316a3b5a1e6ee9e5e60cfff278d49b2f020c6494cf5328e27da7488272e1d54437ba469c1607fa614396fff8d94957a60e9b04380e037fb9cdb75f4872a90d831648178db58b4e6f5732038df91031c860e521752d46701456b3852c722a297e135610173d5ba165ab660ad7b991a8148310906d428620d5a3503a211cb50ff72d2b39fb5135538a091c55cd8496086179d2d3b2315ecd1fffe94ee27271762879c7d19d47251f933487739f37757aa5f067f30d5deaec47e50a116472e3b7a72dcb241fcd85d01f8c39ff5c9609357c6d3a3c3115d066cf33e0c28572f11a7b497f2c476840d82b44ccfb2c7fb5991a6867b601ba1413fc607b5a9d725250b42f1906c35757f44fbd869a10ea1b49a5f6be91b8ca41405642c158d87298e48b9575f79da3ee8af6c1a891355ac29462b5fa051555810704365cac8f7266c20014d8c6c6de2c226d3fc629d9850005e3a127d2a590b067793ee2cbb07278486eeb2471e4c182c884f23637550f30e97978f301206f60de6a6d35afb07213faba9798292d0a884e0102f8e1d4391e14471e21d85a3838a0d5cf5c5f6c56237dd0eab325a4cd30f68bcc345315361c242afbc1d9600849ec924b69e912721f06a3a73e5f6736a7ba510c6e5bf4ab9587f46b0b0cf588108deff52f75c95d762b427e275d3f36593b38a4ba7f79459bf3acf434eecc2383e078c49a252546941e0b62e1c0972abb5004d9c07bc1b14f48f8e2b5bbf53d188fe7fde8103a4548fa17fb68ad6a73a673139f1a9a0aca85642ac4f20cfeef3f5c9294a83b2b2c10192ec282a024be1b6ca6ca4a472ad692c0d77085bf9b20735d27b2be08f0723c25c1c46ebb33e6ab23c5beb164a783f873ad3fcc3c9adb19660bb01d24d22c81876dd7cfb56c20719309d5333c933957991584303be62369958fb42c7f0372df7b886e3462f750616be42a3ed1c2c1a1f60aed77a04d214a8c680a4305a672bc073277b1999f10b2b4dc5bee708ff3ebb6adf41f5a2f39dd17eb7876add4727da54ab6642ee614d081e89dc01ef8519b5590f6cef80e22c3e91c8e88cf0072f58da55e18abbaaeede93e3d0cac76fca886cca4374f72a3eed1957107cbbd45f95211ae3983c070fc30a0e6e35bbfa56ba12a157fb4cc63d3932d6093f842721efe94cb62bf9c31ab386bff33d1ab77846e81588cf6077c31f76229389a1a723fea8df6d0ff7f3b5859cc757128d46ba71fbe1505bad31e143125eb666ea972120113459a736bfc6ba9645fe2ee174f7b436075dfeca7003c6a3f11d3f3c14a91bf03e794cab6907d008971272eb19d3e90493561c516f463eb6923bb3ea072dc0ee8c82439eb1db78a9d04310a39565faa626b356e18b84264a0c99d5350634311144af6078bab1d0b1d5e8b4b434a66fe995b3666ff7b4660e3c421e8c068830308559fcbfa2ee463c55f6ba3d284590d457a5148a6a28282f408c0401472333c9ee2013e7d5c99a61ee9ad0edde11fc7809cbe80dccd25805d0bf7294f72dafc80530fb5bf46fbefd2fb0f0694cc7401d922f569714ea2490ee57a00ad723b3b6653e2aeb83b3fe51a04da09f6066e3f28e882674850de54767d62640972999fc5ed8223d139daf9d3885e826d9927fd0316c9f53d606e95e04dd7cd3c11c33fab506f433cd5628f126d46225bc3d4a75d3012df4026bc3283b451daf62a59d5385660388952d88960d54d88180a7546ccd3085f5b6c5d97687da6e71a72e530ec98359a227f568d09d80db73bf0285b28cf86a36778b2244b7d713cb11f0149cd85863f56ccf31a5d4a8b93f9587c65a4812434881d732a237a64cfa1721883266936ce4528f197763cf389fbc75120ebd89b6bfc5017f39aaa192f56726f4a1a78c513610331feb30bdb9330ae4db648b13fee403a8a10798e301ea533afa34bd88566d820b7f5521a67b1bb3010dab0562bd76936d52c15bfe262b05c5a9aeaecdf5709e4d52bb216001cf24c7fd533d19250cdbded675cb7b92d04728f58c942a36f1edcdc95b529c869ec83a1e98d6ebad234d15f96efe2db0ebb72616ed4681a65c71a98bf6998c0d0d4a3c44bf99c95c7b2a3ffb160c85e7f2872327f0367e320877d52bf7f366816a8ff170c31fbdccd91b35bbefaa15e208e0ccf45858c5e5bf632f0ea5c5be84fa5d25d2bcfb75d25aa12166bb8a90a47b93816407dec2d8512b7e15a4f74f31181f758144e3b0cb357467793f4a28d79e55fa590acb6da46abfec9f6c1044a2dbdfc92e03b4a7a7020562f5390d2b6ab287244b32b316eb824fcff554756b48185fcafac0a13f44f04596328b612373b4e397a8f46d2f7ae98b058e929175e40f2ce9d7c8e457dcb58cd523b0e6d5146973c1aa4ff6cc79a3cd94963ca7944f5c25c03622e5ffdb81d02f1ebd37046b4591d40c28ba1542abfb5202acf693301d715250219096305df57f5a913f978331572b6a57991ac88c787fe06601c3a7c3c731ad88e10f9700e10b3616769c211304072ebf96c07a3094f966f189c01964a3e7eb64854d70d3386ce349aa4b72714729928fb42c3139c52909ac2933b69cebe71ff12515507c1ab45688e56dc585e26f8b3d146bd65717f2597f2dfc06f50a4052223f7eeeaec3662c6f2fc06c1db0335a850ef179b66855b71bb4c78047048cc354ad8d85411c0f618a20067bca326c156fab35e1f3976a48ca7babca2d89f6e4883891fefbf4446bcca13a9a292722f30278700957e302c9d071616a52eaff006f84aa80be837578bc0774b12251f5592bfe5dbba956a19b9a0b188367251687862dd5bfe425d2e6ae6b6b5e6e143698a1eb39d94371f87f7c1adc9db4ab8b96d5a1fc15f7a735caa223aba8ee3451dca28d95b7b5613d0046d84d22cc238af8ef2a842fbaae0e29ea12b11b532036230edd97b464ffcceb9efe790e023afbc789f99a85d575a0d2660ea77acde723582b83384ce5e687dca1dfbdcdd72efba7f23c9b171b40a0777ad34708b36727a44e5d3135aa39d404513692338ba4d6933200ac559c946b162080978e0cf72dcb5ee0fd28e25561cb5ec4dc2499b39b40e55cfc7f07fd2f9c875ea5162f672a2fe248feede1bdb5793f72c1efa5d86767074aa838c7405ea6bfb5a38db64721fe64c06c16d78d14dae5b4afc9a0f014fff8cbd63a0dd8739f8a7f55b54ac03fc8ec0999efac256764ead9e93796ab332639eeffdcd8ac5d28fa33fc9b11569887f5762e2f62c58fac2ecff8e6b6a99e25fa2c0dd94b197666d9956c1041372e08e4601dc673e87f5bb866417bc0c50797690d5ab87e47504105a075201671ff7455c950256da5b7e5946233c89b128ba459f2c725cda29616f1a4f5b10e47226f29695770ea6e433183198911a9922590a24b70cdf96e49669a5892dd0d76a8849d8e026dc31a200e5d5a0ed397007ae8d06d4de5d6a7d12b6aab28acf04727fc6d95f944b2b10576f3f6fe8fdd4ef963554df56764465e3438b05c83baf7269426841f604956c584bbb9beedf7d7fa10e030abea757edccd153c9275ff0728897028586b6419500984c742197897e9730e5ad01926e462d7894c671ded94cdc701226b81d35b9e4a856b9d5c67dc85842615f96dfca2c695561890198d106f22b48c891344cd925b2d1167a70c96140bf7bde21f7e8923947678cf69cc072138c5ac78cb7c335f70d357d4af37487a57769437fabc02d50518af291996872e585ba3c5df11dc234a82a0419cd5d17d64d5c00c199bc89c7cd7108683c9f72376bd9884dfdb6fcc9a00f284cfdfc4c7a850d4673bbf5aa28da78f4dd87bb726e3b4b555c6c35382195c084be9c107175610972f563bb32088fd6bc944fd77282b73ad7390ac412b0ca49ff8d4f9237b9b9782c26871fee2488bc10bc2ceb61f0c850e0dae29f39b68640a1709d9cb32f45637e385d458a2b3bfe740d8efc6aad3abeb7b0c8ebf2af5ac4c60acd79023185cc001c5fff114d0bdedd169f1872bc4aa30a25110fbe0afa633c45024dca847cc4e7fed564a711257a993c95e13b7c694be61e251a0a5984da0c00befe2c7c2358926b9af261b82c31f15d68df1e131b7f431b0e9aa9b88cc5d9003c80b5f24d4015baacc4d9f047436c7d5de11d6c64a92181fd6e2d0d4931d9bd5aa40007dc1b8799c5cd5a1d252e34879a9b72f681776ccce61207fbd97433d9878b7135c9267e9d95f6bac4c6b958310b7d722674f1cfdd9f718d1a83f7544161257e344c813fb18bd669c45c01c931357a727c886eeec76fc17046aed7026f2bb8069201db708282e876c2c3c2a58e398772bdaa6e0deac21b99e77547a1d3c1c6f4be0e26c59c71d2842ba05440bc5a87722360ca6ab7917a93fd6d9b9811b013f3d5c01cf53834f80b608649ba2d54297277a7414ef2b8c0026abb90e943eebf0a8f6394f3939e78cf74ed5abeef89ec7218a3d83942923591eb32e97ca29195dcbafed1250a02257cf7649e5b7ad20d23b1c970327c69d35a7a64a3d53e18463d394e890c396fa05292018edf346ac44a244d48b28a34c708b762fbddec848d00ef096832b99512daffce5a115c86b47265027057c26f01b9d458c836a0380ca97654d6a05b2be8eb507edc210863d7727e495b693d2ac4082baf9a5bdd46f9e743ee206c41eaadaae4039ecbdacd7d2f1d572b0ca054a96303ed7d1d19c5f483f65ba9d6fd7eb6dd17bd610d0f75392701c6f16b3efeec9127b6eced4f2d9745ed6b122231bfc9394a94e3dc74471172a9616fb15b0aea374f4add0dbfe02991e4b17b29b974d5af9063ba3a8c8c1e72f4cd9930230ba88b6cee480b3fb511032017fd7c721e1d5cba9ae22b52f3f27271b0a2328587ffd20e062b7ebcfd6f8034aee9400939947a9adaaec6490704724fdee8575c7ac00fce24f5e649b519e40fc2f7fb9e407f8b3c6349a61236f651e36637977ea4a248b9fd7fdaf442e1a47be1e2653c751db2beb746c95e2350721aa6dbe1b507293ca09aecfbd733106610fa74bc5e80d854ba687c6b5f0ad271e4b3dfacc84255eba36bbb8bf9c367709431b59719e4a28ab4d6c94d8bba95101b46228372aab99075de6da4a35d703883b57f9e90c6da55c6614635d401b2490611fecbfb025d08dbe866ea7fb85880a37f7e54963632417e6ae62bf9a0957202f305075a1cc858c53b5c837946c96a66387b6786003f530632ceaba97da9729d43df44accefe237450fb35b21c888cf572b22dca56f372c9f1b0032c4b94722f2ff8d538f9b677779a0df59353ae2dc6f9dca47e470e73f86de0dbfcb5a70502d0545fdb2341e9a4307f6d81c6a4b51736a14b8f0a8ff10d580de2d9496972c96cfa431ede014f1bff12bbb72c1a77806ae09ec0ea58babdd9177c5b71f9723628f9aa93bd7b7c1a8bee2604ea6d87a4eab1a6c34b3f7ea61be966eea166565720e2e34fe42e2f945b1b853cc25aab7bba69b0c6e7ce09fe5e357aa83a8b7246d1fc97713a1fc50efe02c7ce9a890001132fae7ca5726cd5caef05a95a6b2cdce3971305caf6465b85a8048a107a7067536f039edc8583599958693ec96372a970148cfed7df5ae8dee6f640c9cce9db2928e0f807146bc69a481ad511d155da0fac73f8e0b70dcbf9bab903bed42a7f80afac67f9e39b8fba921b43b7bf29e02d4d8534d67e686a6c63907c6c875b86eba7ac0bb3702483c4dafe34a35944c9828bac50995f4a1581c6ac96f2edfa38c1285f69eb63d7d949d0960df64672de528387a3d07b379869b8c5e9724291e060677c08d4de2283f565546444ca65dc403be7329cfcd00c5ee5f55bfbff8af08b626def687ce95ec196be9867ec72d0255288f5ffd4de6b2a83c5d4ec78c9c1215346bf8604d20c4ce0f332afb41011dc10e9ab33d31da310539a46b7a09c9b0def2cc604faeb606c656adccbea72147532d6a98f325117de20d40b3b4a347449b8094d547abb5607bc3856d85b7206a229e38a72fe16fb1e26374941792b0b2b99681f7133e62e28a40ea6f7fb7264fdafc1234c566d5ac2a9ee3805ad9f3697c899d7e788e5d7e185513bea2472bfea42f72fe83f2a99ae95ba4af6474c97eff4d646d002f5826585c23d865072080d79b2a5a6f6bf5f506fd7b93b515c23998ce37b32dc5bed05170ea388153c38feaa55b7c661a9537993b40db978292f289691e386bdffe9d0487f8cf46529d0bbf1c790556f2bdd9cd6754f8e9255fb2ec7bf6ffcff18e41d6a477d90a25f717863d2b95c156b2e3ab6a3d3ab635cc7ef33588e98c222eb46f95b242549729401f01419c5d499eb02573de9daf7f48234a8e98a3859c463fc0b8ed2854d7262261df7642b142fbe58f2a49a72291e7e4bb4075c770d3f8cb28822bb738831963ed08c4695e01cdd7cb42c96cfbfb9ed0eecdc30e0d8eb29af2ba96aee9a728a5dfdec05928110cb1bbf35c5fa012b832dfc671c9e556598443ae4655122450ab7b802ba64a70b52959c4c0e722ea9627d071e4f3b91c293546f5088289a7204ffb33bd2449fab2a5ad5fdedf4ee39171ef127d762186a5789e39a378889727b87cc79ff75f1dcc81293cf1f046e8b2efee794797b621a98b4919c0e954d6e9ea5959e7c067b25f493121508b17da7dab5c0d2d9033d2b5b1488fe17288111b7f3f4e771d830462041d4aa48118f7e5e0e21f93cbd37ea51b1d50bc9933272245ee71ce2d2143ace73000996d44a88b8a4f2d2207022fdca505f50e7c3f44c432dcb421893b1e8d277e33205a532d4b1596d2d6432ce97c5e1fac814b41e72e624f4e7412b1faff5d61acf3aedec9a2eb4263380dd6fc2213184f16c771e72e5fdce6e114a12fafbfa402ed3a681cf0cc4689d34152dc4198c5785eeac56727ef5de40e39fea6fe2a04caa31e1ffd13a4f14ebb6594af906f25989a0614263d5bea8312a425ac6d9809862efeaacefd6ba00bcf9664033d969bab904f910722383102289016deb30ad6aa585aab44fb8767ff4e83322cbf0aedcd1b597c37277e190352f485f2fcbd944ae3b42ca4c1ff28d9ea8c3debbd7c2a5531a78cd7259046ed01bc8a9159925623c6f5bebf08009ab1249b3f800b617d07efc59fa72d94c02cc4031fb1996c54dfdf28bfd6ec8a607a688147a8d5309273d6451057207a73cc4f04cec6dcd70102cc40ee91f4af8d23862d2f12b84a3b445f933575791536689ccfc1ed359080a3c4f84211b57558da0aa78110c335564a839e7ac1a2699355e75bbc5d862bc0d6b4e6f3c09d5ee1cd3f3f5703f9676ffd1366cf272c86250c509515ea6c73e4bb9d9bca881e4ef36bcdaaac5b335910342049877614c4c330018c252dfcbb57ec975c0f01951256dd79e7ad0205d1e58903e638011642d8d98a0d7ed23ba21f6a2e5649bfc3e25252c126c323985ea37e5694383723352eb2d46b226c6989e105726fea2f69ea58a87aa9d5f7b8c00fffcc97a2a1872118e9f411a48eeafa5bfe6418d0521cd8047d2ada33047cdbb89654c58af211d3fb52bc5cc806dc1f4d048e37d7bf92ea364cfa10bd4f18694150218158806f6dda8b7e076b7ddeacbd02d53500abdb15a18f734590a4a6af7eb855846d9724745f1aa8e14d0a6b81d4d9a68f59765fa86fb83cdb594cc583bb3a4e197d50745fdf5a49b2ac67240b9abb03b3c2b1db550ebf16a257eda373cdcd0fc9eed6c05dcf9c49a5b6382f1837cc5eebaae41dfdb76df3041fe258360e41cc57904031241a54fc084c564ac42594b7887564ce854d2db28186ae4f21c44519a283d5c82bbce1bd07a49b345e0bb480977da6f0faf210985c7d30ad2ec472aa8dd9c72bfdb1a55b4f5a2c7521241f4642c64d2821d1c43816b18190d95c67672c15a725533f6d4d1360b444721ea33f269677bdf94d74323335d615c152e4841499172028bdff3043dcaeb4b1f57fb9e3294b897abc813e5b72b195c06c91953151d1dd6382425649207fc0a91ca26f6347ee2f95e1007e9b13cf518eb652583dfab72522441a7f85a2c6f1a24eb0856c7197d6787a6641bf099e1a80529a7303cfe6956569c4d59728ed156986d12f8d82bd13849482589f54f5ffa27d118e25c205296cf7b64023993f598391d043f6453b6b9ca64b79e61fd4ab6430492e28f01723ad0a1186674b133755195a3cc9c4fe8a03f930abaf1ac4c4775cbd0350d4e413cf7e6002752be10684717e1877cbcd124c27926a3456d2ba0b4e69ecf4f0d1fce845e2863fc80e4f937a9b437295f3f28674ed066896017763db97a61957272977160e72a4cede9f3d61795740a461eb12d5f636f425850e119d3868d606f62dc7286f208f0cfccf09a89d65cd46ccd0576ef0253152de66d8bd855b5bc11722f324841ca359bd30535035fd968cf38e8d13a5f2327ac0d68714f4bcfde244bc43d2c69362267c63403a166a6712b69bacfd351122830a88a25d0373dfbcf72783d31be667805aada138e4664c3febe2581b63e42e9d3bd68a92a70be434f72e8affbdcd9291ef3a444a7efaf37e616204a87493300d56f4e04ae606f2b5d721a0d3d6cf8aa88b5f426b3aa79a1fe79bee4b250352ab288d3e6e04dea2224721ec64ed9b8b6bdb1b37074b076ddd86e4ce93a63b6486d6875e40b6246906d72c8716ff4bed4b8c39bc0aefb2e267169b27bb7e2f429c7f1c96063c52fd3403cab4abfe4a7206dbed81c9a5b1c1dc7b88f31b461d04b67531c169fcf30848c72105fa57fc07fb3aab4bf469ed7114bf64d346ed47ea749c4769889c18072fa720afb360043f1c0d1d35b678f6e5e0ae2fb34ddf444ed15bbd3e468b13bc33020ae13369f7368d022ba9711c8a5b9611910bc9c47e22ab58ed0a5bfb84e5484013082a46e76625b52a1bb8a148167896be66c5ae8c90050a548f34c7903853572eac4f0709437686aeda7a0765b3bebe927bd79e2f0fa3d25b5ab5bd1de363c72391fb7018d76c2470225f47801b9ef5e3bf549d4c062005cc59e538905061f72601247f3d6840142c8e5489475eb4bd708fc602976592af8f2a19760ad8c1c69f55c0a0d4b6850a385523e5dbc7ff29b79bdd42923de25bb52a877eae5f64e3cf82f6f3326f46e01b566b1a760e5c52eef6038956d61b0edb76ed2215f65eb51a003213d0c12d487c36909dcc87194952998fc6d4c6d8c9312c82c4feb3c78727d045b894a737a4beeb2de8ac6fe016bdb2df88669825b221b84187d768b0342259010d8d3244ca570c479583b4721abd2de41f258ee5ce64962a6583f2f3f72d440efa09dec560037fabe35a9beea9887464edb2c6b51326cf6ce2a52ade572d6d27c75cefee0d4cf70779f7a304c665e8f8c9fa36dd1c5ff3b0f70c1789a40b2d6eedd394a2a1bb1ac8feeee03de5e5e8c14504943699799bfc6b656529d728145c8cf7de555cc0c143c20215bc55a10685213c12d9da9d1007b417f503e72265e5ffe56f422bf117dd01269831d39f87833683dab4f42898d3654182910196f546f2378926a010e2b403132f8d9c788ba180847330e9bc73f19090effa522ac66db2ed776247d996172d12f3e037c33d54923f4a24f506f33ba888fb09f72cef6191bdaa075ea6f81b3b9ce5ec31fa53f43016e2551677dff0f08dbf95c72a0ccbc72025e48f01c93dec6c32132ad82fc968ef23123d1c1e22a92864c1d40a343fc302ba1307d45d10312356bf3b681c8ff86d3305a021e9a4faa7d486c72c5e2f9eb786a09e71d3fd666bcd983c0614dcf83b2d0efa76c478e2373461327468b28d096807fa3a97735d151584d2839b109e5df3fcd1717a95fce134b0172575100b7f285f90c2799f025423626c80619cb2835f90722543f4bd683417d7217280d86b68799ff4657ef28b52d4856710da70862af85cb4d3c85a80c246772b3adf613c040643a011e7ff7adb615f180774c91d2da268c044a33c05672510912f5ca10c5a37b0719d7c7f021d35807fd239bceb9805ef9c028eb790bbb9d6cb3a6d1387fec4daa629488305d4c7e8cfb93e09967132a116f698450a4db0972fd96792e44fb4c62de5de75f54637ec97bf5a0a04103c4668741fdb2e868b4469901c6accfb596b2b0a47c9b093d8945ecc14baed2cdd3162a5f1794c84b541d12fce90def769a14fcde2ae5a0c682bd3dceb069017f76cf48d8f3f0fd62be30e07eba260a38aa722a96e2882b0a7f63624300625b1704a6978763c190dbb30cfdd2ca59fbfce862d364ea1a65ccda6bdb1620a6aed4e499ec2652bb1de2af728be8a08dbbb3473f145513a87602731edfab35956e571d7747dfb3490ba7c5711cb4fcad8812d884a24f9d3c263bef0ca99306f0a1567d479d232c547b05eb72d95802aa05f30675a8fa70813ae4f33e5db22c942be545f2c53e3a723e3b80506f49c2358256ecbc2e805d3b534347657644b175dcd95edfda9adf14c0a6fb729af0bc4a2e1a257f149a13bba362ec649d6d35262131064032c2d00e605d303431dc88a583c8cb26735c363738ad616eb0401f11d4d2357f00d4b7ffbbdf6963643255e815ff8d2638541c25c704ba5d3d99b60485d70bc9565d85131930b866bd63ca444ae07c19833b1afdf420e6e07ce71b883678385e8f98d64e408cee42f0ec917fbadaf087ad0fe2d7282855c4b3b6abfdf2be1890feaf9e2d093fa072d08d67897f681aa14ecbe2e37657d9fe0c1fab8b8734660f37b7c04d66a89f72c3d51f5df9384fa325a470dc8f4db7d918bd6eb09d433b2e556b857e286ca7729592acd9d0716f5fdc7405019a3d2cd7e57282d077c30db906a69be5e3aa187069f2dfb6023fcb47c0c668634b0e2ae8c52b2ee4444f3a491b6c98c0293ca2721f03ae6db3dcf2addebda7730fcdc3bd8353357582d59182d8078f19cafdd66b4513ac1b93bff369f4105e8f2670a4384567ece39bdf62cf279e0720fdf514728ff1bbaba9f31b063473cbeea144c8194e34566b18a55f5016c558747d681f72499969bd204c0cd2385d21b4fff511131419be2cae1e7bab43e8e4b4d9210172ce2b609278d6e9585362b48129b6bbcfe0f596814398fa93ad6a6f53c3f8525f36b1cfa6b9751b4d8cee71f4b51378d173bc2fb3cc05dac2d4380d0647f3047257fc45d71a55671000fa6491a48a38453663f99abba0f771b76dcc5d0986dd0daf964420d23ed841b81bb7119b98b036f4537f7b017952220a62fd5e07cc687265d7228fec908ec210b8369ccf5e0fd5b27d2b66e5189391b2e401da7960cb72b9ce6db3617a713dbb9ff1a05fa5b2e5260b78a2c6571f99858ce86f0b36fc728145dbcc43e91cc530b8a8b2a5dd3a853a591d5736656c6dfbae21fd6569a451ec8ba54053c5ba0450738d12acb049b74a41c8b2211e40a415d5ca4a502c3b7298b02176aad6f1c60965590de8fbd821529bb95f63272da58aa14f9a9075eb697f27095f9ee8f17e0f125f4bea190f9d36753d9d1d9d60b01cac055dfb6047725ae5f0fa732e884615494be71f3dd37e8cb6c6be128bcce8cb2a73fea8ddd87193391edfb33ea138c77d924d22cf3e2fb8755ee63fd447f790e85b9faf171572a98dbf4a281a7b829f1a1c8a8fb5305302dc37530f53bd314331f5f5b9fb75724a599dfd8e83d571fcf342e38c9378b28dd68e5a1bf01dc3d3b7a555c8317d2f9dd072bff8ba0e3265273af87f163b92b574e50fac259db21ab762030069ac3cd60eceaef2bba5697c718b54f02431a1f0f144384d6e0e2bce6575b5390c6872aca046dd1d0b51d5631b42e400feabbbf353be3a162c0ce08cbf9da62e29c472f07e97b8bbd181cfe9d1717450eb96b9e1b6f1da825d2ce2aa6ad557ffd80272070859083189cfc5126c3a426151955fd6e3bdfac53121713e656e0c4ef6f172e796262f02f96417b6bab07eeff64c3fc9e757d6352de0bba13b090cfec02e72279d447a9d21869a5e5df35004a35a56c1e6f84c5d2173710d93ee61ee46dd72b0cdb66fc28227ad609455ae83f753fc295227f580e1a59676957c5691a8b3725c27faad117a14d25e7b98cc49a22dae1e134a6ed2dd1ad6a36573567a8b0272ef1d69593137362481ad7e811c5f9832019a969b1112a70effbe334234f6d772dba6e4b5a7407229fddd2326962e302104e5463687115b5e47de48050d5d700924f2b430349dfe52d651db037496a4b1ec1b6bde4017aded19ccc8f3f509cc09b93564f6a9cf5cfc6675f435cbc2d8d95b113f819ea52f2eab1d7c8f73904e721a64284e16e4db4a7a15beaa825de70588fb9b64d3ed6653a86496b30cdc5e4559017dd6fe899d5a5bb8cef576a87351eb1f31df2cd549fac9cb13cbe7878938d2f35b0aa8a43ee26873f84fd9a05e44bae41c1e3d0d703e3dd65075f26d3b340ac11a195d31fc51ed03aaeb36e5dfb6e41c3e2f32dc5ccd93054eb0c0569a620779508c1f133602ae0d59486042350e3ec4fd2c1a882af5bc2719d23c86b9726e95a5b12ce0d47da4569052178d219e73b8586006a07e71e37781664ffccc0a3216789ef40201e0b570bb1d72a8d6ed289bbd79196f7c1ffc50258d1e6b1a72b031eb851f03b4e466d8ccb19ebdb7c06aacb1ddc23f73f284fa54b677d296725a891b99431d9553546de7ffc436840fcff028defcda34354b06c98f3421b263a228bb1d2195b46344ee9b7a53280a739f15960d235ecaa09b5284573b97df4b22a94f429e817ae409812d156014cd9b406e47c5b35f9d8e1407f00deac7e9643c15bc587f9b7d3181451e79c491c6b2dc1f16fc9e2f0a7321122fadfe3b71727114bac513846dd2e7993d643acccc9bdc3abbb3a9423141dd573f2c6103cb72ac5162a8c867200c1a07544dbf4bcca35f4543bbfbdded0fbdbb99474f4fad72eba1eb376088a94a262c9bfbdfa32e88009566fc57f0349880ebe8927014701dbb9523c6625b0df8c95f75eb58c211af807cd0ff8959075eb56e89e04f5de06535dc63171cef96b8b18dcbe3e30405345cc47f0c6185b1509cbab7e8935f3d72a59066cf7a4b4fdf4a627caa12f462ef0313f701a8ea90db2137ef7897c8d472f75c9a6a60889cfeab34ae16175e9e63431af78f20c81f04a1cb5c6876b24337247306986f023c5896f661755ac3393ea551b35753457617a39592d96309f572555fd0f146af76dfc72571f26de863312801372d36ba6ac3f6d7a8f04d83e77230d30c2a40f8cf97fb341bb31d6559a548607c59008c340b4cd6b9be226d02590c0d3aed333ddb1c5870094baa57b66988edaf376d53a49ffb24a0a53fb6582a6aea11dcf47418f9dcdf63164d0ea09671270f8651373a6723f0704915ee2472f741ee5428ee6f3f2d599f3e06c54d42e8f11ed7c1434ce2abd8d435417f8c2920698588111545725be7bf4dd3e821b637c48942ae85ccf1cc1494f4c4e43c599585b247c0796b9d44c7c99c78a7ffa35dad342f6f79bfdc9f00fabe6a6a4672a74f93f17404c24c9df49741ea2f724339fb55f1a41e162415418309efc020726791ef6b55daf7e9e3bbbf93932446edc8a942e4d16ab245b101e4b076847f39d2c8e6012db37014e6053ad5e021709a578002e095e4a2f429055866f3c2d5728c7b1ef6409a8cbbb879ced221316c54e87bda768dfd00e43d181298532b7e62ac8e1f36b705b3939c9ecf00aa3f2eb6cc76bdc7a1f41bfa7a0fe2c279a70372faeb54c216cbaf35e4bc7183510a436247d755ff4864986f92d9c73615a7e8729e90b223ebd3e395763599441079f0a42c6116fc5df4ebbb6830cbe2a0018903b715e1585e43f2f3fafe2093e79d98d296f65c28005fb4780af8edd4fca1ce724b619c23a6fe4119a3064bec9692b5ff32c3065b7fcd195ab1dcba6855382e72e48fc7ce4cd50b762bb5c33f06db18d1ba625cdf9d4ad6910ef9e55007cb8372790e835d00815ecee2500b0bfa4107269370f07e228b44875a111f839c7e04722b495c6268076325f0e85b6498407a49f799a1ff5ad85b3e1ead8c06bbe69f09137e65ae927158565522cb6562d87bdf8a79cfae43e9a84fa0a766e7e568ab0f930696654e4ce42e020282f04636b1f2b600f6639921974d3c21d2d31d4d3552d9ba2667cfa8fbe18c68e02574ece62e0f15feda065ddbd816893a5f95c339728ce67bc1f0000eed41080d153e9dad6194cbcce805e54a7f4bad285e00716572c1a5f2168d3c4dd5994bb815fcc56c3664bec069a3cd241e9adb9ec889022f431baf6c0972b215f5199616eba7ef354952732ca2f1e1536e9c324b49c4984a72a90e75def03485c613b21f184e1529ba051a1e95d4d7407637f737f6af2ad972a13d37b3d6d139b4d68c0a5ea30f9ff9b2b40c2d2ccd42dee582c71f34904a5a3ca8311e651e3849c8de4be8dff35c3ad01628175dfaaa74a9799a5b016f004de2d85450347b5c5ef94472d37afc0ba282813910d29a53e4e91c82d2a49c2f5bc8b0308419809416452bdee62a265c01f493976ad84ae0c1cdcdf22c2daae8723fc0101ac3788d4e85e742e8934d05fbda516918e8504b91c4cc185e01594472e205128881192d2bbc7b4160223c2f3878c4a71833914292729145610efdec7275b13807b4da0fa21916276c66984159d645a7eef6bf1cee3bec70fca0d3f810f3e4cc58ae5aca11e9ea3c0e813e24585672567f2337c906d836b37c25fcf172ce8b49f289c5a46746e3784b470463b28a7008777e42774b1abac2f1703e1072933df3674d9e19ede6366775e9f189778cfe3cd48db3ce79464291b9067b233d89bd9a2ce5129f1713a7907e6785ab8260b7c72ea7997af539155a0100b24072a8d6a8894e4f96839d0e05c020500dafe337c0e8b48c5b6fe82532fbdd5560720f648bfdb6ab899d7178a004510b8a24f92ca1006dec24e7a2b301f12a7b584cae44dbf024d09cb3536dcb4847f42bfa4297a474801d1a7dc59ca1f12c69ca72f600203726b67f9c2b8f83ed2d93eb545bcdb30bb19d432f1baa05978a83ed72f5f1721a7493ca931f1a51efa02fe2afe0427ff4db61916884694dbf9543ee6857b1a0e9a800f2f4288e7d19cb3ab1d06b6441ce821ad1815c0aca7b5cbfb111752612f393d23018d55fe42e7bea1ec6a113991db1c04212890e545451d7cd47d2e35eec488ea429049d8b0613fefa7df76532cae6c87eb3471cb91c8e43dc720dca8f13b9f1404ec81b72fa1e325d107bf8692bf4cbfa5277539815029504345ea32d110167054737cb7250132fb87b4540322485371297d01ac9248dc6617288d19a190829e511f1a92136d5c16b91a05be20cf121c7f152bba5b6ef9f11720e35014527eb561c76898c1cf7d11d303e6525cf3f9883350091ed4d047e2a72fafa6ea35a4f18929d5e90c269de1ff8e2e86ba799d3cef442caadd5e3bdca69e8e1f7b916d20ad48d640728662f4e6f0bfa7a992df6266a3312f77f1dc6ab3ba0d7cec4b4ee1a504a3e0307c7c986abd0afed8cb90a1b2adcad6b40f692065ec1914b2c4b9fb4dcb1baf0c874e274e85af6f50ac5d52425d0c53fce64dd8026e03f5aa27c26a72b57427fab3a252f71c30cd8c33ba2b930d325738b607f965931722756cf56a00b75827b0b32f408242eabd20ad99481cd0483583a09eafb403e1c438ddae73f44d4db48f6ede3d5c1a8a403274c3992f3aaf2c51d9e31292de02e0fc52f426ffa9cd4fde3f130a1d63fd57e81d24f63eef03f0495e767a916938d28a03d81a1674424497c7799198b0f145ed3a9cd297b0f26d68527b9867225a5eb5daa70d81c903d407f4afbd9eb35ba2bf2ee61e4fc90db59aa48dff41e61da8a2f096161402bcec01826b71010fab80d3f40b61c5d30e2ff947fdfb872cee207838ddc49e3ac2355c5c466dce3dccfcce7212f5be09c0d5ce4ce1eeb7210f1dd7bd0da74642a3276171aca0939e99c6e5058703134803341bb4b0da76421d70618b5f99974801d45e81e92bd0a3bb8ea3104ad05710731c55cc265db71d121d857aa0175fae8d0491e96365c8099baf08f3d2dd85e35dc351590a18f72a07f032b9bd9c91a1ab015aa57619fa343ba11acd6273e1568ee54a034bebe166d7999dcbbc0136e2ee30077b065a21445c6e26574fbf19730ccce88f2a9fb492cfa4c816355f3e45a836e0f28a46fd0a633383257b7f601313b57a0eb12026b4727ad4e94402670e131f2b4c7e96a3f560cf442176d0c8040a6e12d52e93572c4083bbb7e56751bebe47cceccc7b675404b5322d7d88ba3a76f9a699eea911f619ed010baebc70d13f09e0587e1580873479ad61cad1bf80dfec6a43dc630504f694e546a88555378eeaaf17343a9002eee9a8720b8675ecbb6f5713f213e723dac6c89c2a10bc9bdffa6f0ab424698a2945688a6ea07ca995c2f2c31e8ec5ff305a867b6d5128a54328b6845d5d74d139a5449a8426f52ac95b29baae74f72676e098dddfbab7b46f937ccebd00503ed41695e74d928edba14dad8e77a56488ef6ead7c15f97dfbf7750f838736d848465120ee16150c05ac2e92bedf1790a4b827935642a46f16b47cf36aaa4768d0388ab3621b2acba100b6a67a7875172f2e10b52f4d14bdcf2f9838a9535e71c66ac00648104be98094f687d1079ed723fd59fa6c518ef3ad6909a271ea44d9bf5a6432f1db9390d8bb0c3d57cf50472696b1ecc81178d788d4979384648d56f20ccf135f8f138d27dec45b6f2395f1dc990ec2a2ed3dbe1a0d0ae2eb3841f8a9e6d444225bd00e6036a5c83efd3a21126ac705cdcfd79b3213a7a1470583217f7f828256333aa186fbab5d343ee7f72811abdd153eec0ee2c951a3865e0e19ca75a0cfe01af35194b6dbec0ff8ed472f12414156c4176d599fad3a46b9a4dbf16028fbd06b684437fb3639698c212723fba016a03a05c677b47842258749cabc8b2865381ea101027a22a957279313849dac39df7db46784d1a626d10166c063405531009aaa7133feaab8adde40372b7f6381bd1c9f94b52893fc8d87e33d6ceb28643b1e17790d4298f9d264609417ff5d0a12fdee341ced3cbb546122d1ac95ded611e9e318206f8ffb8d3829a372ebd371014b9989deb2e608841188f97d06288d83d845549a90b8e937161a95aab119af87a1a1895ce0d8cdab340992176b4b81a7dfd9d7bca1179da75af5572c3f80b63acc97584db9e4a5db480928610654dc34b025819b3ac6ddc9043af72a2db264451153af3e6b4082b2162c04b146e848321aa3d6c35ef3a8149e8ce656dd9e983090ee23ca90f00a8f1b5e2e611b61ff0e73b341e8c2faef4e3be447280cc354b2d4f97f9d398371b1e7a63016de3f2f5def83b7e06ba7ab875fe7605f346c21a0f9a84780fd5d484020e76cc106d6f7ca671a3c776cc9a1eff6d5c0fc6174d494bf5c7832158d1b11c088a0231a0198f5da149d8e6036e679d110272672a878b3065578ef74c8e5d5cf7ddad67a1c5d948b761a92031d030bf4cf372ea7d8ae0845fbd1e8c69a70996ce6248e52ebda646a5fb14f67e72b2e968a172a75e9f5414bb8e884b278e3fa8e2399a5107feff6f6756aaecec896a3f4570540e3dd2b3f6efef2e2ebcf200c6c066505ee20d1e1ab8251abc88f7c4b0b7080267e7c590cec7d42b5a42a8bfd5a72cdaf349f9c6257352d119ab84f62e5d7072afea114e93f9520259e6e8399b2121b65cd5e298ebf9744551670b5880e0b272ea1b2a740ec2766a4739538465bc4bbc320caab7f6e45333b41d48420113b3722696e352f721b4371cf2f1d102306c9f53ef79b5cbe176019a06a2261a7863724fe3f9ae29e55897cbc38684c1e12cdbe4c35d1ca2484934a4b7b1aad808027214a9b9732edbe6c3076415c348ccc9aa57c0f6bb66b0bb19a8ecfcadb384f972e72a930cd74a7d17eaca8252514a96a3776b27f3f38c3bd0200116f3ff5bda10adbe6409df4034b1ecdfa6b84e2a38005760db3dc9ffe5e1c20c7af58eea286fe170e2f2be33e58b79b33d7f6753e88c559f6eb51823c3bdae6d2ee375a97e460e84affe1123b3e8680bfe6a4642fa48c8dfa053b6c1cd3f624c4936b8a16972ed7c379ebc2a83330cef6930784570466cac1b383ff90619175c5ac71886ed7285cec707ed8d849d65419af9191b23a256eccc46c9d580b9458f0e4c761c9515a1fedc7af34d9901322f2d4600fe6984f3d92eb2255319780f37756a9babc16f8ee11759d618c44321411b77004ec00c741f20b4518ad59fd1290f0b4ee759725620734b758598b182765723f80645dee566bdd7c68f18d9ec25f0caa5233872f12a8abfed4d7069dff5ed772f4f59207d1ef18ce58b7abfd7fc8f7211a57b725f308a2ac0d492204bf2d37ea49b33d15589d732e1f225eaae6845446cc94072e2b78bec09bd30321c4b2d654f7232e91e4f3bd1e0f5967de22a4cb687f2480318dcfe5cbcf163cdc7abdf51bc44090ffe565b2eff78715a11e2a61abd152172e65f348c4d3c130c64f7d12b096a528540c52570061df5eb7178476ccb06666f9b00d2fdc0f8f2316de8ec3de6cb12deb9229742f136a28d34f48d4744bb4c5f7f92bd1dba0dd1498d961beb36c623e4f9e0c6b0e9fbde4b1d80788ebf96ad53ed4127f1d469fccccd4c772093fc66c990a8a79f8b47c25f78f41462856ded7290b2a87ef06182bb32749794c44fa68614ab19b544317e3dfe411ed609fbf072c15c8b9629d765a5bc43752e88e58e52b944c19e7d9def532ab3b9b234f29818b4f7caf1fa0875ce9fa721173ffd1ee96dec8f291026b54f3b75caeb54b7a47249f0a89cde7a849377d7adeea512319b842449b6ea05b5164c868bd34030a73293d7dbf07477eb76a37e4c3fb51e78921f2291e18137e6cd06452d4a4b86f8721465ae75435abf334ec6c8c9c760893521538db8f16c1adc6bd773f6f4a2f172ecbc35465e1784ef69ae23a15a96168ddc41df26de71f062007e81f28a99937258d0d47f52b006ea696b7ccd43323d54d9e2b0c1107c21044f58510f1a2a140c8886d0103bc2e231260217e9b0dad13303470393aed1de141a3f5466f3da39729b7bd70bf34fe30c92ac97cf3cc1c2d6a72293ed196fb5a5d78aa967d68a363564b1d125d2a65b92aaac9ee823c12e75c1130d1a3718e9468777d87bf4c8317216d9a79c78513eea5dcd546d3ef8011afffa091073006614918174b3676b784d8221f0241708378e23dfa6ed1aaa221f11bca473c2d1e8e2ae2713ea3ee4a77203ab8d13a9809ad7a19e0c1704f5357266f4537b1dd1e5dfed1baa6bee86034cc9a0144f025c4103661b956a9f20961980ebccb42fb9cb89a83ee1dd0f9b387230ead59388ce846358c50cd319f38c8863df239575d8d7099ef83974286fed72541aa02fd8de0c0ab60c0e40961c36d73bd6be99137602d80216a40f37a9f47235ec15fe2c5bc6747f1121446702c7366bea077c8ebaf03c4971a1c0ca728705dc57dfa5c26267da12826188fdf17e679643a15abda082eefa7b877035abdc40586989654e84b3b566c10d2a7e5671ed8f7d5e77e7f27117a34c5f3d5205ac7219d85d8bcab25dee91909f9419150476d31fa2fff104ce8a2882aef9ccc0be720e21a7ec05ead1c92bf76266e47b6302cb2be9108834dafcb73736c068b834722001fc3aede0b36f698a6a90de0318c1748bb6fb4932dc8de5a88ae353b7116b72ce0b68b067ee7b9e2182fdeee2db1470c32df7ae7f921890c88d0ad311e618295c37d43f958605df825ce352bd8166ca1d6b7cbdfa7a0f7a4290df124bbd49c91cfe15c64577767768cdcc56cc0af63665a0d0e153c2fe774d6fe4b2745c72757edcdefcc6e5de63c36beea50bf4b476b9473181b1a3eb25cd5332984426f90126b08ae67b453830a75b2da982ff0062f7a771704aa1c0951c81b20eefcc71e8fe983ef5abb7bcdeed368282ecd5ad49ebd7b0a7a6cb1e832710854e83011467e7c41f04a16e9befb07605f059ace4dc2847b36578bdf3aed2afa99ca6fea386884df9b0a550ba1382bd75af3b229e03e851b7aa80f11c6b7865063002f503880f0132234fe916641a0f60d1b68feabaa7336c35b0866b8b2841aab6d6f38e1c120a846219d34fb924c243e4fbff7a7e3e2b6f62745129921e17562cac96c664b06b127fe6b0ab27aa02f39f9fa60421fc19f083094df24b47ab14346a334a427a98ec7fca60f0e196ef986c97511dc1dc8996d4441eb0ab95062397385821b7f04a409d64d247243c6dbccd018aaa0fd51587be43879dbc3638c61367766e27a6c79ed2c45d21f90126b096196b342a886f4ec3190e39bee30f26a5b17f70ff4e04f02bde5e6ddac6f88a94e222f52bf472270a5beb3d2b221ba9b0a2950248f93484fc776148d3a0c2e2eeb3620fb60eb9a1e37677d3ebd203223f4a964e2743794737e37bdae871ec3110b0a92ea16d4e30f02e6f4d97da8f7e81413e99f72ab97fa5d5b874b0a3a9d39d4d951b80741cc925a31f7c641d5cc1f3acb094605a950d9d93711877fca53dac4ad74542a8093c293d6c1c57eaf6ef36f0858ff5e4129d24284934acf6928a99fa55b0b72caba9c9e10cf2649f9a7955b0dfc84ecac7da11d77bb5627df4cd6e90ffc259d5ec82a4ae63c2459216b4ee75ebe4b0b02c259ee37ba0ba27fe5038cb98e0bb1535b99e9afdcdc248b425559571a8912dd861382457684a6007057ab027a6ac"]} -<< (9ee7c86c) {"jsonrpc":"2.0","id":44,"result":"0xec101ffac20e55c2ed5da3cd0d083192a5263941fa2b132236d8bf4328c22811"} ->> (9ee7c86c) {"jsonrpc":"2.0","id":45,"method":"eth_getTransactionByHash","params":["0xec101ffac20e55c2ed5da3cd0d083192a5263941fa2b132236d8bf4328c22811"]} -<< (9ee7c86c) {"jsonrpc":"2.0","id":45,"result":{"blockHash":null,"blockNumber":null,"from":"0xcf49fda3be353c69b41ed96333cd24302da4556f","gas":"0x186a0","gasPrice":"0x6fc23ac00","maxPriorityFeePerGas":"0x3b9aca00","maxFeePerGas":"0x6fc23ac00","maxFeePerBlobGas":"0x1","hash":"0xec101ffac20e55c2ed5da3cd0d083192a5263941fa2b132236d8bf4328c22811","input":"0x","nonce":"0x9","to":"0x0000000000000000000000000000000000000100","transactionIndex":null,"value":"0x0","v":"0x1","r":"0xcf8ad6e70d36e4ffa4d40ae236cf9c573f80f807722bbd12880b60e88b4ffe20","s":"0x369aa6d06cca08cf6b5e39f46255c32a914e686c82326717097a223a0584e1e","blobVersionedHashes":["0x0141250b4dd123013070c315e8967bc1e21d184ef0bfced01fdc7a14e8d1b446","0x01c391c5ef61e947f664acb7e263f2e5c696dc28cd8f214c0c69224a988d5457","0x01f3bab5d1ddbb50f2fb44c19d216b0d4e323fb3492100ba8b2c69a965d4c495","0x014dea33181152c38f5d9c3924833d12f5372e1740d2c8bae1dfcc165b511db5","0x01c44b79055d9ac017a485a29f90d0a4f1f60c3f713a89b0d0978ee67f341fc4","0x01cf352375037a809a60df2e6aedf20d41df94f015e071ead5bf395821002b9d"],"accessList":[],"chainId":"0x7","publicKey":"0x95a6357daf5d9f91c85bd4e1f8b6226cb18396d772c35620d071660400a543d8b51f13cf95d7191e958a12c6109357a4e1e50eecd92db513dab323a5c1fe7ff6","raw":"0x03f901350709843b9aca008506fc23ac00830186a09400000000000000000000000000000000000001008080c001f8c6a00141250b4dd123013070c315e8967bc1e21d184ef0bfced01fdc7a14e8d1b446a001c391c5ef61e947f664acb7e263f2e5c696dc28cd8f214c0c69224a988d5457a001f3bab5d1ddbb50f2fb44c19d216b0d4e323fb3492100ba8b2c69a965d4c495a0014dea33181152c38f5d9c3924833d12f5372e1740d2c8bae1dfcc165b511db5a001c44b79055d9ac017a485a29f90d0a4f1f60c3f713a89b0d0978ee67f341fc4a001cf352375037a809a60df2e6aedf20d41df94f015e071ead5bf395821002b9d01a0cf8ad6e70d36e4ffa4d40ae236cf9c573f80f807722bbd12880b60e88b4ffe20a00369aa6d06cca08cf6b5e39f46255c32a914e686c82326717097a223a0584e1e","type":"0x3"}} -INFO: Sent blob transaction: 0xec101ffac20e55c2ed5da3cd0d083192a5263941fa2b132236d8bf4328c22811 -INFO: Executing step 5: NewPayloads: 6 payloads, 6 blobs expected -CLMocker: Selected payload producer: 9ee7c86c ->> (9ee7c86c) {"jsonrpc":"2.0","id":46,"method":"eth_getBlockByNumber","params":["latest",false]} -<< (9ee7c86c) {"jsonrpc":"2.0","id":46,"result":{"number":"0x2","hash":"0x90c8b9e1e8e51584453f5516036bfe0031ebb595e47c5fd664bd56471ce618d6","mixHash":"0xf8caa5bee858bdf1581f3920c0a700cd25923f194fdafa96e47fb2198779c608","parentHash":"0xf9e9afb4b42515283ae292d4f9982cb0c87d2338c717af437fe39175fd5e5fae","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0xd51ab9181e964dcec962295995b1fff437d9504a88ab944324bc1227c0c94bc2","stateRoot":"0x724b6cd36d03f71b4c088b69f1db0a61b3d9789546c24ef32d59f1dc2007041f","receiptsRoot":"0xd50521034c860197d235df5876ea04b9bce05f69b7e89b96e597d9f6d35b1492","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","baseFeePerGas":"0x2da282a8","size":"0x408","gasLimit":"0x2ff7d8","gasUsed":"0x17a25","timestamp":"0x1236","uncles":[],"transactions":["0xd886baa4d7824402a508487d94b8efed257832170ecb5f5a85b8d2e15317728c","0x3cc701f8f4e4c7d32e1a55dacbf4175dd4a61b4b8f26be373b8f7b0bb4c430e5","0x6208c8da6ad2d0b72a65110f972a38861ab4d12a45397dc6026d07e5623f748b"],"withdrawalsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","withdrawals":[],"blobGasUsed":"0x60000","excessBlobGas":"0x0","parentBeaconBlockRoot":"0x062367f0b23e2d49ad5e770d9ad17b83c0c1c625c3f9a290cd9572b3fc6cfc9e"}} ->> (9ee7c86c) {"jsonrpc":"2.0","id":11,"method":"engine_forkchoiceUpdatedV3","params":[{"headBlockHash":"0x90c8b9e1e8e51584453f5516036bfe0031ebb595e47c5fd664bd56471ce618d6","safeBlockHash":"0xf9e9afb4b42515283ae292d4f9982cb0c87d2338c717af437fe39175fd5e5fae","finalizedBlockHash":"0x0000000000000000000000000000000000000000000000000000000000000000"},{"timestamp":"0x1237","prevRandao":"0xe7f1a1fb5c08adeb1e62a01f58dda79626f9ab56bce81f8ffa587b3045e007bb","suggestedFeeRecipient":"0x0000000000000000000000000000000000000000","withdrawals":[],"parentBeaconBlockRoot":"0xe19f3a77f925a661b19f84ba74acddd84c121b4813b7470d91df7d3270667511"}]} -<< (9ee7c86c) {"jsonrpc":"2.0","id":11,"result":{"payloadStatus":{"status":"VALID","latestValidHash":"0x90c8b9e1e8e51584453f5516036bfe0031ebb595e47c5fd664bd56471ce618d6","validationError":null},"payloadId":"0x0062153d35c5e4ca"}} ->> (9ee7c86c) {"jsonrpc":"2.0","id":12,"method":"engine_getPayloadV3","params":["0x0062153d35c5e4ca"]} -<< (9ee7c86c) {"jsonrpc":"2.0","id":12,"result":{"executionPayload":{"parentHash":"0x90c8b9e1e8e51584453f5516036bfe0031ebb595e47c5fd664bd56471ce618d6","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0xfb58f52e7d31c6d3232b10a33799f7b5b8f7b883a8cbdd0c41cdefb207de4648","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xe7f1a1fb5c08adeb1e62a01f58dda79626f9ab56bce81f8ffa587b3045e007bb","gasLimit":"0x2ffbd8","gasUsed":"0x0","timestamp":"0x1237","extraData":"0x","baseFeePerGas":"0x28482258","excessBlobGas":"0x0","parentBeaconBlockRoot":"0xe19f3a77f925a661b19f84ba74acddd84c121b4813b7470d91df7d3270667511","transactions":[],"withdrawals":[],"blockNumber":"0x3","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","blobGasUsed":"0x0","blockHash":"0x69607c0fc6af60144538ad4c3028d0ac86baa0cd45fbc1c080b2d5f095811c84"},"blockValue":"0x0","blobsBundle":{"commitments":[],"proofs":[],"blobs":[]}}} -FAIL: Error verifying blob bundle (payload 1/6): expected 6 blob, got 0 -CLMocker: Closing engine client 9ee7c86c -CLMocker: Closed engine client 9ee7c86c \ No newline at end of file diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/block b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/block deleted file mode 100644 index 2edb73de0bd..00000000000 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/block +++ /dev/null @@ -1,5 +0,0 @@ -<< (9ee7c86c) {"jsonrpc":"2.0","id":7,"result":{"executionPayload":{"parentHash":"0xf9e9afb4b42515283ae292d4f9982cb0c87d2338c717af437fe39175fd5e5fae","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x724b6cd36d03f71b4c088b69f1db0a61b3d9789546c24ef32d59f1dc2007041f","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xf8caa5bee858bdf1581f3920c0a700cd25923f194fdafa96e47fb2198779c608","gasLimit":"0x2ff7d8","gasUsed":"0x17a25","timestamp":"0x1236","extraData":"0x","baseFeePerGas":"0x2da282a8","excessBlobGas":"0x0","parentBeaconBlockRoot":"0x062367f0b23e2d49ad5e770d9ad17b83c0c1c625c3f9a290cd9572b3fc6cfc9e","transactions":["0x03f88f0780843b9aca008506fc23ac00830186a09400000000000000000000000000000000000001008080c001e1a0010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c44401401a0840650aa8f74d2b07f40067dc33b715078d73422f01da17abdbd11e02bbdfda9a04b2260f6022bf53eadb337b3e59514936f7317d872defb891a708ee279bdca90","0x03f88f0701843b9aca008506fc23ac00830186a09400000000000000000000000000000000000001008080c001e1a001521d528ad0c760354a4f0496776cf14a92fe1fb5d50e959dcea1a489c7c83101a0a86c1fd8c2e74820686937f5c1bfe836e2fb622ac9fcbebdc4ab4357f2dbbc61a05c3b2b44ff8252f78d70aeb33f8ba09beaeadad1b376a57d34fa720bbc4a18ee","0x03f88f0702843b9aca008506fc23ac00830186a09400000000000000000000000000000000000001008080c001e1a001453362c360fdd8832e3539d463e6d64b2ee320ac6a08885df6083644a063e701a037a728aec08aefffa702a2ca620db89caf3e46ab7f25f7646fc951510991badca065d846f046357af39bb739b161233fce73ddfe0bb87f2d28ef60dfe6dbb0128d"],"withdrawals":[],"blockNumber":"0x2","receiptsRoot":"0xd50521034c860197d235df5876ea04b9bce05f69b7e89b96e597d9f6d35b1492","blobGasUsed":"0x60000","blockHash":"0x90c8b9e1e8e51584453f5516036bfe0031ebb595e47c5fd664bd56471ce618d6"},"blockValue":"0x580b2ba33200","blobsBundle":{"commitments":["0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","0x85103a5617937691dfeeb89b86a80d5dc9e3c9d3a1a0e7ce311e26e0bb732eabaa47ffa288f0d54de28209a62a7d29d0","0xabc07739e8026f10eeb9a014f804934b7b8179be4d0cc36be461163862f6a575c564a3acd27de42dc50aaf4cf214ef7c"],"proofs":["0xc00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","0x80c5f2e1eb23939cf3600f61872e3e9964d0acafb440634e530d6139a193b889c56a0c07d737729dbe0626706fc9f25f","0x98daeed734da114470da559bd4b4c7259e1f7952555241dcbc90cf194a2ef676fc6005f3672fada2a3645edb297a7553"],"blobs":["0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","0x722662154e6d76b2b2b92e70c0cac3ccf534f9b74eb5b89819ec509083d00a503ae5c198d17634e79059c2cd735491553d22c4e09d1d9fea3ecf214565df22847267ddd7c47030f8667b61a9860b085c060c07eb215a4de991a8fed0cae3821f0ee8de64d25d2de4a710ea9fd89f8f4666fcc7451e6d69a02098bbe5d5b87c60720f6db75bcbad49fc11512930d9d97bef3f2b4bf6c8fcbc414bf61c98d6bff23ba6ab1d76e044908135e6c8720d3d8ca18be1e013c275ba9f2a5d7eef5c2b5f3868df470b4e7848f79d98cfd7381c8e38e9ddbdf213af8d3eacbdd1b7d0fea5724fa5aee5f5b819f35f8fdaffbddc2a2bb2fd21f07f24fbe11f98ebcdbf6e327257389fb0bcb43108092945312d767d4485358fd543215807db5e5e3cb3741c722f2581d15ec8d6eea7c0e7db2ab6f9b5a774be8341d32a3739d103929ce0547208b324b7b29a85552d2fc90b40f9e510680ace1447720c98002f7ef7b7fe09308ff3a8f75036c48afbb09daf42f1f36f477e64606c3322f987fb05603bc62d2171de0fcfe3090c32234a712d7f2483806f192771cd0e2ab1472efdf428f5fa72c2c5a5ca73e3f095ad763054ed2a4a8fa6a05bf3b029646c5f8b61033ed851727e7aa4d120f9817ab2420a7cbdefb67d6d1666cc00a1a37b564ceacd14c37c72e0ae06fa1a09bf6307e226d29942db1ef31c65de6a49922f8f4860ec2d8a71726f74cc29496a85a775bb29103f75247b2b34718a89010dd9371557c1ed466772087bfe3148d17f8279de1fe8387104d0e25bb36e0187b2e1d4df9df3efc52d72f896e5aae4e5371bde4571421bdd1e47b1ece2a8fadbf27f27b87d1689f4453d87d4bdc1f411bfa13789478f4b2f6203af19c7c24063e3d2663dc7a3c97d1255862ec5121e4cc733e7e6929847f32353a4bcf6897f1ff52dcc66d8c9923f0d525a22699ebbebcc80fb1f158beb8c61511698ee9fe9b40a2ddea746c7a921e8532281b46bf4ef7183597e208aff1784d79436cc69ef5bddc0db29fe2da84bce72c78b23dc94c1e76598dcd6ce8c24c0bd72cb581605c4d8a4e711efb957952f7266f9faaad1e0258462eb68de681032d3dcc3047bbf1c7f1f3337bb67d9563a7212a42a90dc93647b636540c774a680e8ae8958f386d4639fa9d61e02fa5293540f6f733de496a0548eaec3d510ee6e1ae2822d348793693e1dc38dd3e69394720adba55b6792fad47817a9ba0881572b4ecb1a91cc7fed3e2dfc51569419777270437988bb74e5eeb888d5850f9de432ebc6a201546325df68f10049d54c9672b3f5271eb5cb37a995d9ff04162a5320a480c457409b28a663ed71b2b40ad22b541b5ffddc62d08b8d4c8da62d21fa429a6d658b35432a73651ad9f9c2121e72e172dcb51ae6ee4f2bbb50c51a97ce7c742fad786e465de42cf569f543c4cc2a37942bf3455a31fcecaa6c1e2718d00478871dc9d0ddb6e984701196f49ac972a3fdd50e631e828c823a3546198efe5f8ca312e31fb2a6baf148cabd595ab8720657f8dd99b792976e6e087b711977ef17efd4e0d28d9cee48ec113c5092567248b25bcfd5bbada813642323a7fd7a76356cde8dabbbea44cde6ce1fcc1e667280e5932f0cd08b3d2399a574629e1eba98b0a0db467a6649945f8e271cfef1223006baff8511b554b9c1181d74c499e3516c03103cb6ccb1e549e9e57be80f72e392508bc624d9af47d2da2ba898fc32608a4df1a5ac4954d819e17ba938a57237c699abbde80e4a73aa42a4ca855c8108d4658b0293f09a94fe192a5c040472b56dfeeb18c3563c86f325b8305e9ab84b276811af6e07bc15aa3ed3e4781931452258d387e284a4dd23b7884b8e9f0a3ae41779a20b18607d692283a413bc4338ad9312499fcf8b17a6f28eab8ddc3151ae89805400a1096164063653267f724c8b712452cbe999386c45e54d94916e0a8017e0dadb21ad27c8059214d97d32c76fc47dbbdf528745ae9e55cc5da5ff0f9aeb46d1b79654600fa3c7df49ac723c0fc457440862b4c6138d577fb8d74ada728be3c5438f5c2c1da853305efe72a917c4dfd89735e8fa5c07c9cc4452b6844463a18781bd9accdda74b1e958e1ca7de9f49c900079ab6108670cc5519b6744ad2d105f6b0d4ec5b5ee88cee9b4e3148da1dbc9c813ad96e911faad349151d6843954d89572c17f63b83cd2def722859980a43cb112480a70af211617d8e346e961abf2f5df65136da1619e0f07247b1a8589f00e9deef61970a23edd6b96b330e54c98e0df0e602724d00368c729f09a21d1b52932276b1aff4e63d00acb3a341f16999d5a013c2454f95ced30f0d22f6c88f672ffc3e61d5223da39266f62ebae5dd75a489efeb6df0862d1172cddf0e365f75c18dbc848f6746a3ccae1684752a217409b8b57a91aa05eea272f8c96a09594956c3e5d917cd6156c096c26dbd93f9a4fa5c8a411092c0c1c63be9403f19d6c6a9a052fc4a6acf46c9fdaaf8a662591f754c309c2d5f6275e92f64b6f81649fbf5e8f05c7e72842ea62acc9b8ab8c82e49b8e96af2219b2b92722cd5a628ab14f7483f8f56261aac17e7b8bd1016a4a9616075829c20eb540672ca8d829470c0050d8f08417cdf4eb417ea0c5864e9e7cb8caca88832cb245f726d60a46004f4bf71f107330d634b86d9fa89051573ed947e7e095d8cfd677d72396912aa12b9fb8dd7d4f21dc0bb28f2d1e290476787f63fa3e4eed2469cf772cfa7451fdf19674a0100d60ffc535cda86c248de1f17c60cde104b974105ec6edc9b7e40822a96b3c7e6018e60db2493d3e53aa0cb9ca9e359940644347ae23fe53c98062e74cb991ad7e03342614f8696bbfc097b1ba768e04449dcb3298d72e50a0733a644a6c06d06e8274d90e3d9994ed7b928ae7c7efad145e2306ebf3a2f258223afe8f1d4219bf37da3670026264d4dc1d2928c1b7865bf99b036de7289286ec6abd1235ada7c4efb349d21f79ca0562ce3e931eb50d4116a4af7bf72bcf2f390341412c77cbdeba26cabab50d0576a00b313fec424ba03d4de19a372326b171e251700a72121d7fa0009e4e8c94e186a7be365ba1568d993ac16b9720764806197b2eeac2d7565783b938a6fbaba57cb60062b53c1e8ce226983a972deea93ced7b88c2e87920a0156c1b94a4bec0e47fcfb30e1972f047b56252b69df22f121758a49b08d8e5147a761143e6ec5e2a4322f9feda4dee59b268c4872550a76c5bc0c07232a77c7d04c47b30ef6965840756e007cc872f71e9faf194a62b69787e017762fd4f9148f7a43b9d50d8abe4290fb986864ccf24f9f52f8483c87e5e4e45c4e935f4f337b7d88baa38b9039fdf03ce40a9ffdc6e111fe692f38c650752019db283668560fd31349c5849183f0d91de0d2b88931ed393a3172624649937fb1afb04a92ff2c4469041679057762ddd15a4c8a4501b6116c9a72a2260b2e8588948fae691303c5a7041027ce4a5827c39edc6a8714434b537d724884fbb9a39bfbf887e945157ca2ee990fded68e30604af9942f8e39195c5972a532333ef4d5eacbc9be8534273854503a0eb94eb6007d4f7ef386c6aa37a866e1c12c0f34f5db2a92b1e3ad467c7e1f75bdad36e05474e15816bd1df4ec85259262d3ff7a5184f80122b13de208e87bd9b2c607e6efae864569a2d8b464fc0473dea65351f4e3d52a497452c79740c458450914eda689474367217d18d62042e63487cdab70eef512f6681a2aef4d2359783936cae2a5c3df843c93c7f35f7208b386b9822f79d21154a1edf2568b4b355ed7da3cf7e5e7954ca7582217a2728bc1c427610a3831d67b931f296b3c30b372d19fcb4d60b3c0b8bfe0f667503dc7cd70d41df629ff139d0f66952901f42795c4babc68f653ed080bc895f56f4053aecb97f6603926a2ae285df8cf20c57e34e56965eaadd4f7bd1b20308f8072508390724cae5611f0574d1e83c0dbdfbc61ab06fdcc0e65be32bf272ead8672545a8145bb1179511c8822c63ff1e7bfbb3b8527c48ba5be283078ebfcaa2f72eaf0a362ff747e226c4f228281fe5e3606eb826f4161828ee1a501d48c6a362749ba4302b8941d81be751f23cd27b040c2d890c9a6a2047b3a1f7a33fce612725e7d8319decc545803a420099ad5a7ca52b41b0f51e588d3583e71244d75027237107713dda0803055314c1fae8fcd8993632a7ed91bb0fdcc791cfb3d720372d2f506986989051a6d32777c2d1aa025246788f30f54c144c0535989333d57723f9ff54a691d043f2c2d84ae95272807860d6c576a0c6dfd90c4bae6b650327225078ac2bd96dc5d646a873a6d7543c2e1265b71f99d3c9f1e2a06a3059c06727c0f5196419afc19a455bc3adb4ae23e69ea0073e7c600fc5dfd7050bbab8850ba35b25de6dbbd8f7f7b395acb6b6d460e7b7883e93508c42d7f2afa4c8f9a26f769f1a0d31fe888ca7ca2771e1c1287314d2833ebd53726af856e4658a0bb6ed55c41ac04b434a3e6736cd9261300b9315ad364c467238487ba0108aceedd1c8814f7fa9e14b82243164e84760887a2419e1559123e9d5f941bd41c8833ca72390d33a27d45b6e73ab4ee02f23b3b01ee0f94b4a98304671f1aab423feecd2879fb6196503b6eb499f20144c56836c3cef788d0ae530e8aeacaf9c01ae40172297c5f46f70b085417bb1c4217ef6b0f3400b4994e57cef7116feeac37c929392ab6bb95ffe92f643f64ec7e036af2656f84727cc308354091c1cd7eaf4df17244b1a04514d4f8e1629881c84cb6f5be182b40b498c3faa55b28a786436929728da528e60150034e1466b60a7ebcf37958455eb09686965c81b585bdcaaadc725ace191deda3b0d0fb3e6363f28b626c14e45918bc314684682b68c51fdd127235c1dfaa021dd9625ce84ad3dc3acb7978f57825a6faae9e704b9ab51772da6119e184ae1bc99ee29bfadf8abc83ab6805488e89ce22a201400c1d9def057462da34cefd8ff96ab767dfb7d2df3b9d2f137fc400338fe88a6c18f8577507f1188dcd5bc975f0412396b1bd5c11f6f6065e058af459835ff942ae4f80ae4c117239828b98b3b34d092cc8cd53a7f7e804d2e337c9fd857228e52c6370eed766729553fd9dcdb643a0127ae1cfca8fe8b3811380b3327c3128db4b6695a6b4a572af57ce3e5e9af156f3c384becabb58717849f493aca027b6f69d6062ccdb3a2fb8230770b229b0ad7098273521675654dc773ff906463e1f3ad49bcb6a418f4d5fac59377fcb804e3961475b38a25e3763c699eb40f33aee021e41d443f1017256cb851ca61b1370239f4b03a34ca4f37c74f6f6c22512debef1be3f4f9bad72f8afe5e15b43685b128d3572a72a4fc33d32aeda6083f2653c7929dd49b5b52c9a686c0d981cd7ad501f137cb66af04bd05188d937c4d6fc0444e119a50f07721a297dd9eb1973d530f9b38715555d91c1eec6225c64b1f9093c903a0ba2b9723b644ce3d551a6ff5f9321ba199b3aae1fa76d478024308ddd8f13c8e177bd725efce52fe97dc5675c7b6916e8589d5e4ec332893e5182a539099c9401b7eb724ea77c823e92465de46d8506c47eba0855bd20f44b0481c4e5aac4dffeaa9e72c236b9021a4f3e40d87d7458be37200b9d53fff411464c3ca836fe28e228cc1ba779f1389f2e71054227bfbfdb43b609e097153ab9e6261468406b34c79688726a4aaa9d768dd5908f762792c833d95d8e194b2fad756aee5c37de5f4e2ed44170d1f80080a562e7a107f0ff741707efdcdcfc116f5d623aacd314b91438ca72f46a4000c0427eeff855d6531c8bd33cafbe7431778b7314519a30712901e0023b24aa992fdd04d537cb0d100761a09977b374209d78ee4a7167f8f36c0ccd7282fc96317f9a1ae0999a71b0291b4fb3cd9bd5f6ed6e78d25f3532394cd167135732b24157d0ac1fc33576ad1823ae1d194a204ded34dfe469ffebe7dc990b72482e9be7351717592fe2f2bec5ecdc91129927b221ad24011c6d11bf3a35f3727f9343726101841a1897aec82d3cd3b08cb95ff74115b202b036373a72416b72c16e4ffdb612a659ceaf00c477078be9cb4a744b45230b8e8269d23c2276ef488825fe04954d85fecee29d72ed7174f8369b7c7a92cd0a1146b8b01b292aa44556d3f4f0ebcd7c551de21462c37e2fb248a5944fee3b730b4bad7c98a095c6726b053c55a55939e08338025cfb41d2397c919aa9ca24e99aad7e262e04f307720ca07ff1216adfe9768c95ea2f31268b15e554968e0de7698d920e6b3c885a7289df3c060501d14822eba963db261b7ac90bc04b1153069580963acb0a7eaa35023bc710d5cf4b5795f6735fa0c8fe7b5b8c82a9b25b9902b2beae749c305a3f7350a85d3913d50cf277e3a0cb2b4c97abe7f6b751c7a541e20ae8b95c112f72a57aa2a793d96b7344fcfa8f2ae7dca3c77b0002489b2bc9986b08c93bf13f72fbccd659056a078da51fad2b3e9374fe1fbfb5fa2c1b5d929e806ff19c23ff30a34c17fb42fe2c9758b0b6b9ad11b63c70a906357b954568e1f67a86180c8b727a6d6153aa544e1f8825f67d8d3e34a5b2de4881bb8fa6120d3817eabbf8dd720012d82975f3343bb2214c138beb912ba8c823b945e89b87c3777f300ea40172b2f5c6e92d1f084e485fd7310408a7ddac1152e1ae7a2a1842121db65dfe8f7205f5bc346a35d3f8feb1f12bf63773417479f8d77b91b5d08ca33c5fc448726a9e874a18aca01384468788d1c76a375960707e27a96f414d56e75d301609c172cf73f8674b44fc19889d02764727b3f00b72b19cfb011821985bf5b6c404a87238b6b4f5b5aeec031af043e0ad241b75f2217d39e388c8194c81e1865707b166e8d3305ddaaba522984c2f16adc97a0e5b03803748f4407c6d4458d3ef4d4324fe24df2ab497000f70e0950c6d9d15bc9e6244e6114b7164d9f9785ee7003172b381284a9a047e78ff4f24700a96cbc204c4fd2551db3053e39ed9b40ae5f7721942dca9e69da31ee7b525010f1b05e574fa2bd0accc49c332fef85c4a62b934c23be43dc5d57d7616d315b8872fa25c565b42da8aac84419f89d8914099bc721e8aa67fa922570c8ebbcf32062db11e2564024707dcff5f9f1c442f7f4062729e5a2154d190d98bf30e60c9716905da3c6cd526b44d8424e17e26b20843704a8b8757130f18c89f533118a4894f0ad2df09c5bd5f8f9f6913a5e67d2b2ef215576dc32227618136e680c1088c5f14cff5cbc717a963fc387d8bb19e01e7fd7256d0695995ca54363328194a5f1650fa13b594ece69a133fa545bdafc26f93725a87ccf2f60072a8135b94f50bdd6bf525b2e584f2923b8ee8c2b583286d9928115e91e0bbc097e5c15853e39f1b07a5709db8d67e19d7ebe86feb74fdabcc65f0abdac0c18bf865851639950680a44a2dbafc3b82e5d901518734d81063c6728442089150aeeeb6e1fde66966456570bfea301cf9f231d4d9d85a7f50a9647248b26d930dc67bb301a4ee15db3c73bccc7c3430f5587bcf5544a7d3dc16994ad88ada2e1f86c08cd13aa9bf59740c3d53b56729137eaf274e3b211cbe09ef72c70d2e975a79bdc7ac33b65d913af0bad759097dc689509a6ec3eae7de87d6720484f7d3dff6671c4c34f8c154467c6ab82234556ca835507f9c01b29c624f498a2a1a4220128f9fb94e0aba663ae81f8538db88b9637a507d7e160ec07e0e0de58869281fc25e6d523bc095868f3e86902095dacbe62da72c5cfca8a4634067e7fd8c932fd65cad326f5054a5ca19e658cf16e8d389d2b67d0ed89a959d30727c804844cb0f7091d18f5a727cfc58207e3156a0edb3d7934fd1b040a3e74072545b77dbf3430ee883a713917c82a721773935ae3e4167b272c1c6b2a911e872e95804e3ee667c63c1cd44b7be371eb5206558f92eedc6362e793d5d6164d745fe1e6447a33636639c46db25865707ba137c57d2aa40931da57d603f5b2681728ed29c9e33fa0a20c2c7738974f4010262b29dd80a16d89c5b6f647bc740f44925be33d0fe2745f1f851d33354252f678c48a125ad6dfe6e415488c6cf5e5472a8eddabf37db4f3083766fe31072c68867122086d05f5f4a7ca181620ded5b20907cb6675e3923e9bc930cddbc38a35f1e5c45fddd75447d6887242b0c69ed72e2f5d57bf97f5ecb94ef2a99870963d7f2ed3d5c682cddb2e12075200b762072031eafae33b40b88fb871d2fb0c9599ea598beb4be4268991c47218488133e7299ee4f43ffb7f3c144c8b0984dfb74448696ac72f37b678aef102845c27b951c0ec031f25c192176b9262a2c81bb5adfbf52c461767e8a63d80a80b0d51491521214b845ca333059f174daeb9879952344dff8eeb7c35b3a1d4d426fbbc8b772a1ff091577f031413cb2f19e96d04dd4b53bea75723105746248ef6f91d80f4f2009ec370debad1fd2f646cd5dfb0c2c162ca8a92535b35bc526161d3643561fe3abd951a4f00fa8abb19ce7c8d9733b2629fb9ff8c7f5cc6037c70c228dc625606104e01f93c81e9bcdc07e2378a8d79bbba35e7a5fad47279932ca03e00472d36e66f55491c860b3a2b0ac3b8863dd453e2f62ebab7288f9ea395e7c165b72139c9b5c5da1ff9f3204bb05a9df069ff12a9908b752d3de38c17c865790ce6da43ce01beb565ab3d18c6220d2c433552f431e536158f4bc38cc4d6dca18d172c1475db6dd16ba788373bb0193094208bf5de4da76d68026448fe1625325ca72d511aed2f0c5c58918cc0207106858a804de142eca9883504449c950479e7f725903aaa55c5fc3ce0e77e9be43de16f636e9e3352aa9c0f3f3cfecaf95b159724faecff96b575143ab2c42489f3c34daf215b85d64f8bc41ff4ddf065c605872ede24be716bf588fa2b8c04162baeda5e2d204efd5b838842507b7b06f68e4721eaf34be7b1f657ceebd981622df84d536ea6f4838b3643b40304664673ec64fb9174f38547d2481e3e851f9b3cedf71310a9832d1cadb9c63cb8c81516eee72fda00b7bda35301844c0c026a166ef8716608e57bd04013e7a8aa93b23c70272bf2c145d66851d25783ae26da22fe9a756d79ac42c82fb0df0e150c1f9ee8d72b219046c462b1abe8a2ac1773efe1134b6c6cba637a6cfe5359c5d853777d139a662a4c19d014d515f00d024609ba54b1e1ec845cb414824dfd3aee1381eac65cbed7d0768f94e1024796397f2fec000e18deaba42e7e27f62fdc0213fcb15720214a51a1ae0e3aa0ef5d723b01d63b8cea2324549b1cf15336d331a9cfb2c727504947db5aaa1af52a6fa1225b9e2031b48fe5267a4407d07874b5b1471504d495f51d6ee6c87624672f8ff8d10fdc20ff538b518fee6286e460dfe861eeb72d31fad3edae3bbffd723153935b054a709f3cd48f9400d7ca9458beb67acd544406ffae187de4e17480994c97775a2ff137834282a250104254076bcf502084c35c24b223f69b2d510829c852a09facc27da5cb47fa027eb2f358b66e389085df181ffdd4a957382ebce4a10c2a2f272aab7f451ad89e9fb12a8823267d6eb62662caf594ab3ab196c7d6c8c757491b41f278619c3fa0bb3badd91f58be36a2efef3dad05317dc01852b04209d4420b5c2a6161b94a9ee5e6f55cafc79660572e02f2d41358004c75f56448bd7016c87a590d2bee38b2cf4d7c302c2c9430972ab03d07a8098b16c046d04c14cc80e5b59e16ffd870a55277a074840aa28c9727f6e77997f2617005616d79c2dd03e7019078917d5e0e5d2e638ea3d214a391670c4512818a4cb8040dfc25fd0264fac6abed2f131ccee0ad7f8caa26957d6498a3f23015ef4da19e915dce6e40f0de5be101857a843d6d60dcd2a12e1dad659c4ac3ae494e382a73b2b3c8cec58e0f35f768521ee89ee39f155ffd74ceb50262bc86dfc3b00e3bfcbca32694d20e1f4167959206e12864921c76f8ad7f46272397428bf84beb8b86e4417086fa2905a744bcfc9d9d32a3c9b9eb92dac9cfb283ac49f4dc172e8c1b05c6597dbe6afd9260d63317143d962e56023e343671b725f28a3a2a2d482b40a919b8271443a6089d28732b999e359ebdfb02024061e1c33f383a8fbf4bfc083885812c4761ced88489465792651b0fee9e208d0e04172abd728a5b9d4c75c221ad4aea8792384f6a6184c1027ab30e6b94322751adf724930aa77c8fccfd7f27d5dfa8bb2afa100d008864adf087165c40f7480614172304686e694796669a4f8a1195b96a7a55583c87c97bbdfd3f38679bf1430b8724a7f29fc23b36fb8ac4dc421bb848bdab1823d4003a3973d4ecae8894cdeb67222e70e36a3d5ab9be292b8c78878cf2b61210724d5cc1a2d8466e6526b7e325834635d6ef11a243c1af025164ea104051aff08fd713c1fd38c3c07d5c8e6bb1618c7638d7a58412fae59db6ee9dac6ff70f90b3ae6b5322153663ab5b724b7727e6c79cd9cdeb3e047c35bd3db80594567050d5e0974df7df04a1e474c17a35e8ba2708d7579bae959f0c0ad5c255132b0cd6701ae0453c097bea87d4dfb310ece09c086e2b1a375a79f6f181f16a2c35742e09edd7b003298e4b805d983a0725e825b36d027c6ac0aae07962dc20c9b39989bfab054fab17139d05ce14e3363c20367883626231f7e602d5c7bf8afbe07a0b8c90854e6f6ec3d8afea616340267e3b757d0facd4b70131233232e91a74a8f93539bc7dbd66ee49bd2c85946661b3a1b6b1b2229e223d59ec7eb742e9c68abad97a7c1b968e7e7b555dd42b56c8ff9d7803ef77b1368e798a984babd1bbe488366f5a8bc1cea0b687b84fef77224dabf4fafa99fa0a53b95e5bc5d804fc80b237b1b941a70921fae76a4e10e6468fef9e04e46ca16c00087aa356aa90cb0dab4bc7ccddeb1af7744d461a8b672ee3321a71f62bfe3cdd21d32f185ba5c2701f9efb32f28063c42925751ea8f3f3d6a4469f94e004cf2b21738bb206f410529a846d6765adf4a22a9179774e245e242d13c2be27c631410a16e754ac18699b6fc8530ce72967bb14327ab286372b65d740ce8d25d5fdd2c4030ce7d20d67d945fb8558149ece75006274e5adf72d078522e81a2623aaacd45a5eced756ea6c0aef163871e514686ab742c000d726eb2dd6a127a3f5aafa8e0ce6ed49d3aa96526eaaf619f940824a341c5c445072fc345ca71f77a2fe313e11f281419136b764fa5130825b2c5109dfe3fe3891a71a181a7a05ea1cd6c098edba4d1b049fd5626eddb35f0d8214424d9ed6c0f7202ec32519a49c9cb435f7a80986b65469f8b5b8c2cb21d00ba499de6160b3d1ff5c4b94dd917da88fef2a11492da8c9ec1020debb4b74f0ff920f62f99705f726c6b74b97b617de69d4330e72f3859f0250b81c6dfb45eec737769cc2af88956d1205523a954ccff10e5c6455c3ac53da19e2ee5ea05418b4aaa9d87014b9e723074c30e5dc326977f3351c88dd3d5c930bdb9cc592a08bc3267d255dba18172809c96b83b88feeb86619abd15fbe4f33d8bcc68b0ae41977513151d6237b1720230507aa59e4535ca1c433dca37d7aff430e545659a5ed458f33efc8f8e4c342ff8e0e8dd8cf19503d7127ca1c011231fe9945c86ce702463f15641f066e26d10e74173f9d374e9f20d268211487113568170772205d2ee9eaae88671a52972411d5910c7e2ba4f5689d9dc76a43c33bd3e542d8c72db08b3b16e2cba009e7212c800b0292f526b61ed350bcd531c5a61cdd462405a5438a214986298582472731ad982f9c8ea1e089ab33d7a105e824139261fc40468870a7a6a5b910f2c29fea513d73966e3b8ea3062af73f61199266f6683e0d7c99981a150b205d20f7223c194bfe90714cf529bd28f8086d387bfac2bea03cf625eff5129183443752b4b823f03fd1188fc5e92bb2bf2fa153a5ae95cddcd422d6008d5243762007b077897a27e2d0834c0a46ed0b5d090b51747696e30911fd88453eb002e1c0c892bb0a0e19864b7e604ea03578b5420b937cfde1b41fbfce4ed40f51d481a468e7238077637e010d4c4ab122a5b2d5736c9dbbf287dde274216d311f24586093572fabfa4f019ae4c2d7db60744d49deaf5f320d1abaddf0e11b2d1727385198e70fce6e6a9c7ff29cee07e39cd1efd20efef866e8064e745984133e39e26a71c0b8eb3ea21a89c2440ccfb44de8dd748962b0ebd3c942e82f65e05f076902e7572809e88fb1aabc5fd39afd427379bf132da8b887c8b53870c6d42f326f37e543ea86a21b9b8a6eae5ba1d0ed7f416f70eaf328b0166b03cbad0fc01a751bd0a729f1e3994795bbd0e251606a57aeea7a99de5f08bbc09a486767e28658091d4687a45f5d31c62a340e92f289f59ef8a16f84ba5e94f2bf12981bc1c6cb883af7283d9e25e31089b51af39b85f1e3c10d8dece04b7df8021d009dd47483928f07260acbc359dc8abe54699d8e4ab1dd208e164ae9685e141a567e3c08daa02b77215d7c8eaf12a791d5af5a3cc79de8b3e40e7348f1f35b7c8498cafd2d1320b720d84e6f38abb8248c5755a922d4423eb639303624a6ebc93d2ae7710c8e1ad5bf1e1fc55c543a2a6016c2f4c9e44ac1a11710ba2a9b0c41edf53adb33c08ae729891ac4483ae648942b14b8516d80968411068a3fb69808be7d2889da2a9633b2680006f5f8f35dc8b44a739c065b5d32d7ed78f3c75b6b5914888a6e6b89972baca4c4b04f48f9dc42f539d694bc72fe6adf4b855b4cc90e922d3967e17997230ce42ac72989c2e575cd5b69c1bc9541617964a919e80a2ac3f353f1541257225860231a656d71408184fea81e017b4c380bd9197c43ff2011f966d83c5f072b3091988a8e087a604c0837a32a03bc6d0050e521cca1b3cafcb90731dab417228c5df9760777edbfb20e134301e6e9965fb289c2a85a25552b2ecc0e0a1707271e61920650d1a0dbf7841363e67292cf2dfea06ee6fefc38aa53afc96d5ea72fd30514591efb45991000e0f9465a30fb90ec68b98026b0252aee1adc3ccd772734aa3f566bb3ca235634dc25ce78e2d119ffd6d44434641e6bac9170e97397207c67c577a6a56df0737b6d9a524e020c15a6943da192dfe32932ab26f3fab4d8d47cdc0c1e1ffcb22b348ac5f3156d4f6dc6ef8c0f349e834ed9a0a9ac44f2c1c51ac98fdd718ec79ec66172fdf2e67ebd6c505ac55d378e4219925725c8872dfacf149c3435a7199b6a62bdcd316da8c467e4e0e3167577388dc8199c61572ae4f01d8c2f8a98740d44d3a584c8ca4591efd905275e0ee2b9b98e61cf4f96d3da3e7c8a63687bb4c1edf636df23e91fc71c3ce7a2fe38416bb31a9744e3953cc7fac6c91199798906bb5b56dcc36d74472257afc0e41ccce2dccd5da16ba0474a453fe076a16e5afa6aee9a5f447bfc466be01127a4ae01f1039349a5d33723045539a87c7898d3f5cc15ba8f38c75a949010a2a7ee0e30eac3882f0673c72abff2479f600869e98d67cdf47d76e0735b8d41fc038999b93d5741a46e36b724eadcb569bcd5c4bdd50ca8db6790583317f6b36f3e67a5838718325dc8584723f7d2b5ef0a12a66ec11d3a2bc9c9383f34fc0a7ea360c5207e62b43997f446dff11934554b523de562aa3fb2751e70248ce16c85c31a61292c08f424c4c5872b41231a942d88989d66feb3c1070c3bb0005b4e5095d6269438d24a5723dfe72e1e2fbf7dd00a589be04414cb2b60f6d41a228aea9511ec261ea544a4e5aa14c609f2ca5551761c1ee4fe4bceea21a7e226349d74eff8c420d1de8a79c418e5b3692f948058d2d0caec220cd500244e259d9993e3b053fa9d7258bf396992872f716113ab2a5dbf6c7608e716c1d2f35d5d7de24bff3e9420c095370f384c8721676a8f252d20294e1ad468c6136b9cb872fe98f8588e9de476845997fa2236a6beaffb983b4dca1b2a928f44c354bc01e65cd1a34b4f58f400a407862338b5fd3fb1585f9648bf9f4040a3b6c73212896a748b58589de1a594613fe6852b92bca1eedbfd29e4fd9fdffe890ffda7ca471be5d0e07493df9e241eb68723a9e727282757fde6c9faffbac375feba511db1848bc824b333424189d79b5db0cba56f8d85c72a154e90579db7e6315143a4ff09aa0418227ef0f38375fbcfe28d03e8ad70198bed5c3d024a8c8f121d66431ae2e544d50bbdd3012541db70399152133ebfb0283771ada32cfb00401324ae7505095dd33915230eb4bd9ea96d69c72880e28dabe5208ce685b82ab6ce0e0170e25e2da97ff2a49f6aa37f6ee0d9537bc2f858eaeaa1acf4bfb6a8ddf08943cf7636cea95d131e09e71579d07c189724eef62f9c94cbbfc4766403d3b5fdfb07ac0969006012806c1ceda3a4e99ec72eb43d2e69babdf311760b775473598b45e7dd623dbee17ddc0ba99a32be72c7254060174044b9e943283133e326eda4b06600a692f8a4db67fb3f560ee1ef572ee97e15024640c66370970e999bbe09e2c3d5c154245be4fa6023a3cb2561072b96797ee106ed99bdd4ea0f6f7d55106fd6ad15605c35e165d1ce0838056e74a66a0280abf186eeeb1148d83f5c8a178b9523754505a35082cb70c95e92d4b0907e89be23def17d608b0ebedf89df7bf134db2a882cc327353f33df031010372139f9335914eaa548b63cc9cecd3d1eea88f9eb1d1dfbe28025b41a92554701992f6aca9ee6d3fb398eb36d95636fc72be095d731ff2f0d7a53c5bbe190fdd72692031a5147f61b8e1b1717207559ef397ac4e2901f30bbb8d76f341f263fb72b181452db6a7feedaa53d0db3596fbcb8dc600d2581d6d3d3783296c14acfe72f340a6db51d18c468eb97ced177958b7143b8ca652917073ed5d30a64ba2f130d0cb06a62998d8b015862f6064aaca062cc3b84cbb1e1b499500d6138c4cc42a7551534f74cabe3b68fea2cbdc6035cd671e475803a88a42c6991ba27ac30e72c7482b003febe8be6db0fe0964a949a18639c0dd30b0061b5dfd7bc6b2ce34726748981a30500603dc04b2ca64fa91ef4fdfea170aff48507ec9d63ca811a45c4d2013af2cc2adb0fc7d234d3bc48bd7b4b823401b62db51b6d401e742711d72f11224f2a0e7468faafbcd1a10301ec7e68785265ed6e928dde68ad05e6e617214e0907d854a92fb476493e5ae75706284fe793c7b9abcbd16b4daa8c0a68369be6b1da8a8c4b343d5acc7116b456aeac5f35b2cbf9e3477f6cab9c3b6bf49728ac76725fe392dca164797b176531d5b889838e32c49d3b1cefd4e8494f15872298ada7fa5fcf547988b8e6d6351820b7b390fc0ed1b7caafcfc7d342d1df5727ae55b194d6a130162279ddce9befd0dc192a51c58dc73a83288e758e573b0723f9138905a2eb44c6b9933354934167d9ac693be5a0b3df0d48bd45030e24c720b45a1a2ac964fe819917ffa70437f710d6bd69dbd8d90da5df5f1b00f5310724b17db433d3b9ae113d4e319514cdb25298e032f288bf582f6b79dcb68f5d34be9178a6617dd548239c54f04450c1b7cd74ff97a2e88afc9b2fe4c6b2ed60a43dc982b11088b60b0068e4b1fa0ba086ae7ec7bf8c3ad3606178a92245b48fa722e8158cf169ee94b1e939c2cd4714b8656214aa7fe295b1f0db7f01a5fef6503f33466706f9a54d2051062aca1670652cb48fca2df283eeb60116843a8f7367200369a00f2d78017a1c2a3ca94666c2e7530a26058867c725098dc40dd6f201e94c3aafdaae10302efb7d7a0c11617ce42d4d6e9e3e6911ea3c5fb4311ba5f1fe4fec9dc933341655dc8afa2695c888daafb64c02b8d16a3d8f1f1743e00f14092cb650800dcd075c7a1e6a6910f09b426991c1d6aa15e309d697b46d752047264e854f9b92e2d34ccc0d019d8db60bb497d3b749c91524bdb5dff84b617727250b51840308455ccedb2313b1650aaa415ffa2ec27c12010d24508d156f6ad720bdae52f4893ad6731ad5dd1e9365652efa148bc212be0531ef35e246f8258722572d80165c4b1e5f02563a344e058291d1e57d54de3edaf2c1da4f2b702d572741d3a5f982f538fba10bf0bbcad7cb283eec6375cc5a142b9033b17b987ac72a71b13b21a27fe343c7249339d82ecd270dbc7ebab0648fb08945a312f63b072ac3d16e5b7058433b624510873e9020583925e66656ea4f6264b6f6a6e129405841dbacb36e9a3ea99792137fbae02b8fcd55fc7bf3b1bee8375d4cc4ce2ed72fcff87f903770e53b5073eb56c0e1a3dd289b2881cc0e0c9ef73ca6b025a785561cb40d42018647e573fe5063b1a631166eef44393b1ff87f5ea5b2505aee7726c3ad8e0751ceb1c1102a829fe18376fe1599c936a58ed939ca7fc0cb9f467585e594a3022b341fc217af72f8cb0be6cdad983e9960806360087cd26069fd40db67d60b5e2483e0a9c479f3001efa93c87b7afa26ec293758029c79239c9a64a48d3fe60d6ead94389ca2255ded5580742caa566a29cba00697ebfd1f76d2472cde0788a881888070c408e613d5646632c0422ba8b6943c042c1e801e18a995342380f0c18639fa603fad98b0c1046881f6e420190ca7896e2874c727ec570035bda71cd0e4a8e9cc3aecc659edf03a474233d8a9bcdce920e2bceeda14b5a72c2f1b465c4b2bae891ab11601d158f1eca5abce00ea8b3f153b8ffe00532bc72ce45b0c118bbdfa24c19a8c3a13442768e3a9540d6950e4019a8332bee6a317251670ffab978afe466d12533eb1b2ebaf65a95301461fe969fdf6f0c9f8be572b377f7627fca97ab9c5f47410b4923504133a6660e4316fc2c6c591f1656664ce33b1b79304b9e7f56e0d5c5ce2080e57a4d3290dc435698c1fbfb39cbe2db4d8b0f4b814e265506531ee0830689139b13e003c5a691e54d0edd667c8fccd31d35112b7cb10bdafe1c512d8b7a5134c6e1113710f56a6f16953dd40913f2c072e4a80ca938d425a3cde7b9aa307064e383744a54830216e87264adbb894fe058f6e10ef940c8e599946053b29ef7543eca6323513a815a83a83ca57037ca130647aebed529d380fdaaeea06e1108846d39539674e36619e132acf6a4f51d75722b193221add90165e1aa332be800da81be7218dc5a698cebd9300c3e5cf89d53ed1a45ee087aea3c139d0d996a791c821b63b9b17ea71071f3e632f5e5bd9b722aa5cf085555e421d44889ac5ec1f8540caddd2618ef7bff8b758d7e48816e57adbc11ee335c908210b634d7482d2495733fe9bb3b89ceea8010c5e53214705b60d1827a106c0f483782d97d7ed52587e6117b0dba2af7fe8d6db8c564064c1a28820eacbf4f30dec793577eef3f906848904d6774f5c7c9550951e9e60b7e5d820394795257fea64a92cb79d266376b6ef1c600c425a57506f39a916588d3315cc2a8e65a6ee3952b15f139a8e90f3ee54cc9fb85af716ce4f400aa6af33c525fb0f55ed44dd14b13e1c8a0575c947092500bb40b54e45ad99a1a83ba4d4f3dec0ebaa780837b025f9db0c9c916101b7abdbd916a6e614585599aa24a30b86aa27456254d1928f63813d2c9bc96e7c747fe9aa4555e7417f1d41d18884f4a063373b6e34eaa805431fa315a10add6b34c5afd4c5b6f85454d36abe410eee42f9f628ea164ba636e0ebb7ed7ce788f0e8d7aa406fec43c9509ae0e4ea16a4972bd27c5e7eca65a66735d782a35834ce8e4114da1d47abad1aa5f25620b7ed672a761752a7d547ea36e896e2ed5d34a92937119eb2b2059cd86862a92bd5bc4489d91bf2e602896bb11ad46a98715c4e6448d363472b5c3448fdf393254124d72585d705d1c3379133f9f62ffe2095e0d977fecbc3673208eb061e3fa12fb205832313bb8503e0cbcdd45081f75738204b6551a07aa2f86dc49bf638a99e8334ab3bfe5dd54e9e408fb633c55372d6182245198b054196ba2ad28fb5528db1a0865ab1f6984e5db4a0c421df59ff4c6be6f792a115ca0d221bcf3f27a42e52d43efcbd12827e519f49a376f4ffcd59cfac80bd15e147b2f9c9d3dc84d9f816b59fa7ed6e6e4cad051e348df73177bdb64a368e3d7669f9da72d7ed5a445f851493b7214bad8650d1e341f39556c5661e3144e0a34ac6d57739f075edfb1af0072af66e76bbbd3b2f59192d7176364beec1d70a79e974c1a6ea34f4860ac403112c1ed6aeaac9f05ebf3138d34aed1094eff04b549a048f62f2f967386c9b96f5bebf9282577c11a551da3a48339433407bbb3f5df4f4a8a33253f5d325c3f0401c90aaba8cd0ea5f8736ba680f1d33cfdd8a2aa305acb831e315c361a86fbf372b4e2b89971613f1bc25074e39b4d1f3a0abfe2acb13309f345c6ab685b7653721e8359d1e2baf4ae14813b4e3a227a5f4a3def6fe7ca65f2aeef10aceafbd972e0005a451a7c3052e08a04c6d0514f3e7e54e060ffbd99642fe0fd7e7a259572c2b6f40596105c64f7355b1913bc37d8745daf4e68d42aafa875f6d0ecc5882ba965099162fb424304b7942da870d130a4b97b6c0dc3fc20b792a4936872cd6b1614f375e1c29b1429ad2193ef58b4a85a057f4578d4a6959f2b14c74d8f253ec7065a2f8e21efc2fffccc87b00eaa383e411f6901fa8278db6fa6a0e1ae6a0a392e840bcc4933dc5b693242e13089992d16c494bde0363a935f8da22b7ee972561fc50cff7de1a683c1ccba90d96f86aac76466e0cb0558331f224d991405722512f2ff8f9f460de7de53abd164e8bd886da57d5bc626328a65a24b49277a33085a0bee09695e4a513ce899d1bf92634db6d12a4a17230ff056b7a66f341f3ca6870e3745b48205077537f6d24a75328036dd0a783f2f27c4077dfdd5c3ba236773c221f7a9ca4ff8772566dfd871ba8569376d136ea244fee0c50c6d01142fd206ea3cfd63eb2b40657e7021225f8d020697aab95a3c21789caed87738a85e560be75bf2bf65ac3d7f343215b1f00ecd6ece839d009c5ace15ca033aabc572ae45c8c2c01c031e7a0fe5e8e8e620f745c898207dc5f307304ca5b92141507253d3a4c1c6b62f3b651e0aa5ca1d99294b40e377ce687ad7c4169e2cfd0846585a14ab6a451703268fa794325d001ec7804adc93ef1be1446f50bc9eaad7ab7241f65cf6a042935221b6a82fa76500a7c82729064ac269ec15b76014fd55c7721a3f9b48acfabb9b65e89ab4d358f1367d69bb051db5d4623695d444be2838370a00932d9fe8653db12bb50a8a55975bd69dd9a714e591cf804432ea87b06a265d4bf4b3272c8442c028e9cd384259a73777eca4b937fc769409db9221654b729d20663b7517677c314e9045e78e72fcf6998c08267692e804cecd117a8f1c727bbacc8b99880520700b0c6744b244e0c9b2a1c9b8300c59fea757a9f1781f56ff93e1d12c98e4b64d7677febea24df4517e5b48c3a0754d9770c9bba3fa2972525758859ba08b6552db038c75bfb2e170a244b9b88f73dcf83de76f2e38de204990d6aa381ce7de435515a79bf495046f92cdb2cfc1cdb46aa66e702deef65565f6d900099137cb942869d577a846420874ed931ac066f4f81bd46230c2e67291d8a8678921944774a44069c2628456b6091ee32920ec9fb3bf0708f1967f43e2cf2ccd0b755142ff659fd77050d477bb3b406952c467557e942a243cf6ff726b74fc939c901b3f92d1819da7a3970bda996bfabf8334735818675721f3ef72ce83555743ab97c53ab63264d64ed722270074bae43b401d05a4e9b4ffe09535e6bc30ebb8c374966fdb62012a2ff79078658fad90f1e2cb6c3c563c3b42bb72fc69024ef54439ebc346c39684002305249c8ca1388432f29955b4796266bf1e4c89ac2f64ef0387941e7422237783fe68d78be9b7440676f1467b5a6fde1a59dcd11b802695739a65603986258ff4980c147110621da3f210d4edf1609bba723441904bb47ca79e2291ad73d07ebc94cdd71d5ef3b09b44ab6bb2500c365a7280068949f72ca5e9d69d1d5d523e23e0ca758a779b7062dfb8ce8d2e7903b972986c2f008a47bf3a882965ec5d89bc2df8f5163544475857e674825605b13f727432ae470ec0c4173c4f36c8c3b5c46829842e9ba5d8bf6f34dc407359f363721694e333d3eb9a24ab218624b582f3428987022dfce70a6b4b169915463a0572267db40594966c96d4ba14eb8c7e18a24b78e6b04dbb65874a4e315e00470572a556f291b7e5495f9130222185ac92aff25dc65091a79aef537e957733c39a72699181fab4452ee419aadfd9b4a939970d0f5985f7ef7cea6efc4ecd4265f972512df8051698ecc69363bcc1d1a7e77a9db62f6bf365dc26ee40cc99dd8091729846874157a6aaabaefcc43bee6fff09734e24479165a1daaef603bde4cd5572f3a17914f3cfc253a14571cf27a883874d001c70611525631adf0afddc81363e229216a685eb936c182dd4b20ca2f361d12a4661635d1305a55e840877fcd158bd7b2b6bf78626fe9b3dea42ba464fa8f4a43715c512a2e39f72dcd69c1f5c7200f2bbe67928dd3932bdfeada35fd7bbb80fe41fcba9b074383d4c0989c97c72c2a7e5d37805ba9c326fd7a033e744100c9905b364ff8a86f1ea1f7e7c42f772b2fa5a1102359de32db2031f9148cc7fc32fd19ada004bf0116c665e3ab1115d1a65063df396b1d77e21b6e721bfe4c6ee9679a61b522c80d67955dade4bd7725bdce69b8d24758b3a9b4c9201dad196b57296abb29cfaaf34485fa1c12b7969850813ea19d14ae1cca6f894c838d3038680ff78f2986b6b17206f39a552d17292c40ba879e511dbe466b4920083b5dd3ecee967878be930871139937663f61a6e4d9831feb0565692fc9dd8b5abaf3db20d6bf087c8dd8e58b054094c137a004ffdbef218947ccce5ac106810a5008d76284b59dd729e7460e759b1963723722ff1b47e288d9c67531e5e9d9ce0eda9820b6122fb09948826410cfa0d22e3729b700b4db3f6da3c994de573315a45f0285ecfe164d8442821abd3ca12471d72ca3e8058333fb70be2948fae9befbcccab4ad3e2fb3b99fc832aabce9a67ef72ea892873a6757fe37e74e64f91e4c0f11721f8b295f5ee0b44ef0b47bee6614153ad39c9d38a1df2af2fb51b7837757ead7bb2b9ddf959fc8fba6e07f8a0bf724a52e3cc6f4366652f0ec1c3753108ae6acf9c7fecdb441e3b01ea376ed2dc6bb707f85ea04986fc90dea4d57d3da1d7d159439c43dbcee383c6d99206c5725e5415d548332226472741056dba769a865eb95822a24c79e36f33e924406b96720e2b469eb556f28629b0f03c7eea5e03296159e6f0730be1bf534448e71e6d72ce06a6f0f30b279318a3edc80e594713feb504127920ef9de64127a8a2623271b0b3f4b1cf20a79e620fd82da716f4de6be46f76a4dd592b18bde8631428f572d0a598b3e168208adb94bf51593c9902d43cbbf5f179f0d0b3dfdbe220807b082141902b2c399b95172bbccb6ecfde5a86e01e6f1eed272c75b675f810b88b72f56f4dadd8d900fbc322f603ac1ed52ccb824ae96338cadef4a6cf980bbcbc059675ae1f1ec3a869081b4d0d21def9cd1b0f7d22f901a1bd4fabc85a70b94b7229ccd17fbf84f858c69379c270df7dd4ebc49e29909bab97ebbe52d42a0d2f070319c92530f3265458ce399abeec5e1941834f4386784887be2b35055b949d7208a2871ec0f31177bc4c255d25b1a2303d01055f8785fcea1209a3046af5c5725fcf2e6462da7cdf83427bb45ea994ec55b53ad02472387a99bc8500b51b72725f8576373f92aa81472f915ba89d7531b202889bbab57bbc655e3ad9c0350e3a075de14c7d6b671024723a1159ce5c962bfdfffec58136a68a070985f87dc209b4755fdfef06d76546686d7b89d85c5fe1936c57e54bdd272f7a42981e802e727368feb41051e48790e0f3da5500a4138392c5acd2fc0ec3c73bfaa6f6f2927230ac48e7f3d8773ca338bf8a9c0d997b1071a3eba23d5bb092fc8fa2368d3272e1be0e886d1b3c95ecaeb44bb1a6afe9464a805c0dc8ac0a5fa170f09dfa2154b80b07093016ce40785092a36ee6ee74862c227faf3f85f0fbca4cad7414ba04f03c16b6325b4842e308c5b1b18852fe8964804758a945daf7c7e8545ae29c72d1b6d015b8ef38d9bd88fa43725e19f06960e27a18531ad028a63b896ebbda20fc9a06a3aa43a0ac2b018e7395e5608b88cfe357fca746838513120adc03d263bd7dcef941b3fd8aa7f757aa799c55ad5fd31c1c3d97516fbee0ddf9cd03451162744bd18b6f155c912863602003af96600d0620ccd951d9eb10632d821eaa72f49a68a878f3c949976b00dea3e8b20917a6b2e856eee4ae0f0f57ab3112cb50761c1ba7d56ab3c2dedf9c941975aab7ecaead89dcbd907f412b5fdb0a7f9a729f0e175c293845a1d63f4048440f1133368850bad5e160b4a827fda2c298f204c27b290e284af8837cc66c794db221fa9b4e59a28cbf9202d704bccdbf221f4c5fefe8f0e0c462926a0400789449e425a1f21d7180a487c8b01c82ddb660a4359d5dfc9f53fcd6be462c8b298968643b944072a8ee299fb9ec6f7fd615806e6e9505f0e6f66e19e8e0064f836096d82aab3bf57bca3555f04907fcc1de597157827112fa3ae6cc3a0ab1e7ddd8235f3892496a596aa1b98826ad91b345459d70f959fba3de3ce29ae187a7016b92c8f3a6fbe2e6295f634c91eb69ac681a2472f44cb340d38c201ff03b96d3f43ce0c61e75e95032c2c1094c169f00e50c6a6b421a56120e687302b828396faf9cc868f3027e92f3448574189348909a6b4a723a10c49ce64ecd660cbbdb54d76a55519ce762243f626de1d390b18b5a0ae272e862eb9be3e1eb161f3d84c6fb0f641f95d1ba8668bc78394262d006bc1b257291be0669aba497ece5dab749f68b642c83d1ed765d5e45385fdb8effed84757251eeea5eac8648fd8a97d2a9d9a63c6a6bc708b65a390dbeb2a33cc41779f721af23634b91b89faa49853cf32a1beba0078821361c8c2c9c4d305cddb91aff67e8f120d4258733d95eeffb778208ea08f4c9b0893ed1166d6a4daea561a28472127635a8afcf28037dca162353c4d325c22f4dcc30b28ae90e84d884d1817d7224c0ee05bc638d54b05a3024e6244a6b5f94e7783f1104e32a845a43df4f7f32d2cfa716d1cc4bf726cdf80c6e0e164dd2de202cbb527762d6dcf6d2c0ff722eb201e83a49fc68e283bb76c6d5c43903d1b8a78fc17fe6eb766119e722762636410928e07cde3c60f17c6829ae435184e8f34c8ac414163fba05dcf0bffad5691590128fbcc3a00f7a183b5e031e093b6b37571a1decb68ae334c2d65daa850ba296110f7f774d620ebe9248ee90b3b2332459dc80bd8f2dca2862b5344fd863a931c0bc8d6d4c2d32d193b918c7f247b37642c820a505bbdae63f4cd1315e6edd507a746b91d14430e4a0a3c167abb7ebd71f3a015602b27c3be51efb8cf93be0f87a83be40eb861d5a37185c45d97e4a09d703d02e30341d975f101382e0727f9c0ef874b41e2d58a5238a36b0d37f63bfff63abcbf4ecee8e7fe3019eb8401ca5cddf8c8e09d898209a39d8e61a309939630169b32dc14e4a6f6a5e20dc72bce71bbb0a9b520106f88ad586399f39400ee6e608d215562c487e708a1b0a41da933941d31dbe70d6393ed9040964ff8d943ec0df6a524d771f7c8d471a6a72d4b29fcbe1ff1070b285f214e477f2c58072516a3a0bf60a2fb4eaf79eb06e722dc1d43ecb8271e91d76fff745b96404c3bea176d0b43870fe350b22e2b17972f8dc5a76305d66508809336d415312dea502791f367bd7ddcd0ea8910d3c7f6cf2b4c53a850aa142f9fbf22a79be43fb0ab014a8d6ba7005e959a81e1f02bf726dfc7fb85d43448405a2432acdc42e416a65a35c8a16c56fb107ec2604873a3f3a28295aef873fbe24c80e79c38211814245f25b0a4b47f91b35c5dc40aaea7289a53ab3d35ffa3f55dfd70a8203a11857bef424094952d26fd364346388da3071457eae0cdf977f79b5bbf9cc3d0db63f6c509d90a81b94f1a67ff1d9d98572279252b801ba9829e80e3aa8ed2bbdb3a9f295dd97998c4c3da96b9a537c7172f84a1d1852a2058b8f01bd5a15fc6d33b8c6ede0e33180cd16dd1711011d8f4d141b656dc279dc9dc88a991bd307895dc856328953820c4d63a0c93ca8ebe1723ef723456172cac55e147391af19b2a601aad86db34782d46054bac591ad97621e83b8472dd975e40081027e7f83bd98e8d0c017e03a3b9f818e7434ef55135ae71477a1ac78a91f851071baacb175b82e2239c25a79da625e4a76f04225ae723991f78d679fe45318de8d3227f36e553e928fa6ae6a897df84e310b693fbc0039687abc8e1f46d35dd5937a317c434abec126352089b916cd64ed8d99728c7252f44c8bb062e422c8b00d15c1023079e0bbb6e87561e0f4af83bfbf6013383014bf608bbfe3f740b8390dcb62deba134d73173e3578848dabc5ffcdc6002172d95d3ee452b11af3fe0e78b52d82150e84043764529ba2fa03a0e93d08235b72f03857c3a9eb22267ffcc6cca49f49d46243e6a155561f408ba6f214be2f5572beb17d68b20e0f8dc2da19a5a8526087970a147999e99dbc34681f0ada8cd4728e2699e964088b16b837a78b29131ddf9017a42bc24077babf9984c03af06272198812b2ba6e4efcdaa557c3058edd3ee041bbb7f2141ae05f73105b0546b372c6213a9fa593cf9bea2706d092824dcdeac11f9d23c3b4e5422e5679ff5c2072ffc7fddcf2fcaeefd2f0738029043718c098ebd1aaf9727b21c5e38784f7d25c6d33fd7d966c690620cf676e57bd2ee37ffdfb9bf391a616d755a6bdea920772997d1c879bc611898e5207b70ef7d22b151ce5a379f13fe49788e5480367fa3af5dd391e50cc6983eca45c11b030a6e4f03d1417f0d551adddea63d61bf912725a7d35719bf7b0edaaf92e80e65e035ae50f1d7a7c28541ad0a1b7f4a9b64b724ca323e91d4eb673c617c3391861aeb5f6303ee86bd2b42479dde16a1c47df729eb4b22b087cfaa1bb310b8c681d69845efc50e066e5e98ee2645d26d72b2972db1981d17b6e8febf58fc471d46462063864eca9666cf126cfa3ac3c091c3e5eeb8199458f303852aafffec95b1fbd3d5bcc1b3632fd05f3fd8dbcc44a566c14285af3dfde8560404d4178d0adc5dc1576c6411a23f585e3c39f563a7636000fc4052878db562df145c9d2f41168e21ed5591fbd2ddadd4337376fc46dfd8327a7b64c0cd0795883bb739a59d2afea4ff1d189242164d25cfbdaca878a67b572090a760ab0cff7adfb1d293027b1539d891681624b8b2d8eb6a003d8dba6bd25b48ce53f114602357a366fe19102bbcb28ffba7a85de6087808701bf2a310e2e2a460d618896cda777fb067e8c733e8d9ff1f888a0534df3cb161475188062722fcb7e1fd27f041eead3299e8b0b0fdb3fceace3811f24c9ab6a7f3c9d89d90daec694492cb3f5aafbd70a5bd8d1622b338f20349fcbb7a40c284feee4feb272cbaafd7c2e8ccaee0e5e6dec1c2b55c0f49c72db370e7aa289a0184435458a72e4f1032e0f8ac247075cb44e0c17d640ed2b6a612c16c39ee5fb159f1bf5fc72263b00fb7c8958524e4eacad8806cf7a09d9ae868b61ba95869557b7f48aa80f7ec7d72d184ba6a8c2f378e31ebe2599ea707769c2ec9d84629a6da9c1046c012454436679a888eb24cad806b720f012e1735710ff48c5c131e21b2decc6be27ac0a17e010586407efe120b7ebd66cbe486a0ffa506fb15150c474bcbcd4c172942bb643f60434b80a3f6e4ff900ac44a0b11f5647e8d95628c59068cf8fff524183f15eb99810e6dd3e6f13dba213cb2703b7f0131bef7cf75164c59eceb1277532227169114559a031b57adba788adf2139d5c33102d36831c6510a77e5c72f5d6f02c8b600ec866ba1a57c509235c177b9fd91f7a147e3c451331b1c2c2724f262f93790db580303f9cc07bcd16857db221212b2fc5301df5d0aa338d7b72fd1c8f753efc8da5d5a2aff2042d9185de8099a51eb1ba2a780b2e04dfeafb32deb66e9c5f81b998039fd815e2462175976ffd59a8aa9200bf1d0878fa7f9e3e8ec1bb6d7f5786644db73104af296c5795201a307134004a90b44bfbafbd1228f92e9b366b98fdfda1183ba7cfe300daea8a858352fa5559069d0129e896de7282f1ed090bf1a8ab75f2f4082e7cc3c743602d18323f3563df1886bf7fabaa273c686e2c8ee77e7a3ccdc1bae6ef376f74125ddc001e0ad55659b664059a65729558a1853bc09d200ef41cc9086ab87c02008695c43c972d68be385d247d5f72e47785a25314e46b8a8d1595eba4267f90e16b97d9d292005442cade29d8c75d34d93e1138280d443617092432bd060628c0e67464f59d5c87544f51e760f00b100df3ca1ff2b88878416ee21d981d376351af9bb05633ce3c6f64d7acda2041ce564ac1df2a0cca16ee9c2664d3134050b692c7f8c5712fb04b70d9e6a71f20c81130de76369b79e0d98cef5197c54e329eb1c346e1bcca281460a3469e5c72884bdcefc8e16b4c8a0a97b6eea1e9776e62083e4b56523e6aa069f3f978a372df20e6f3eab1737ea647855b79076e784e3946c9493be8c85cd6fe2e64a250720256f295e8aee61805b43b547c0f14804e0aa11ad78254d42bc54d8e1a92e9722bb3b0822cf3165329e34d60d9ac2af2490cbe4367d619f44aebe64e2bc52831a52db28d52932131b6abc5c8714ce0c7d1137a494852fafe3f458f4c7499245d62135db829a653e6dae9823311d382ebabda1dff5cf5948bc960360db7a787248de3d2ca26a67dfa062b93d437c2f5fa71c688a51375b27237308180d987f372a19896e6dbe3912455114dd108435197c378237b36a5a0a6175bb0023408ad72f4a9510e735bed5ce1320e6b705ab5d9306ecd9a935e62dad769910b2a74a4720d2b9da7ace4f8fd188c7032c6f2b9f1b50c3a697ec6192fbe98716569748a6bf3e914722f58dddfd4a820d12fa9ef85edb18d074163f342f95b6fb806b58872b65d67214889284806411cd34fb11d1dc0991afc46b5f5307666f35432774072edf56421b14a0155403fda89b6d2fd903268dec22904b2f8dab6802bab0588725dc3ec248d97e6277d8d6989ec1204723500c4240f63ba45f258c95cb236b97268c5aac952447959209f40aff68b14c152e34ffffc6c25f99da57901191bf95a74bbb067fb356ffefcbf7d27bfedd983aac6790726f9a5bd6ccb495cb42f86722067a2744c556102d98838734742935ed55b8fd03a0485d02b293227b8fc4672a9a2710b699493f566d85f16a4ee8654cb626c386ff185ca00e51cd8e985f363aec3dca392e6bb4116742fb7b1a3fa7763a3eeaefcf6612b53d09edfc98455721d803498e3243843fbb87c62642519745f3688f1e1821cedf2b589ac2a5d2e6866e32de731582ecef5f2ca83345e4e68447fedf253c1759e70af456f5dacb672bf7af369061f65dbdf550d6f41e0c32e2c6a466bb62daa7dabf6581505c3ac7249680e1d4d78de4c527b510e8053c43c4b991c776d880fd1833df883e05cb372c7e61af2262a1403cc8ee8708218f22219e996c4cb237e2a3a31e13b9d92f872d230b901db4521982ec892a31075726664b7d89431014f29dc96dbffab16c67215ea9ffd1715456839436148aa1b5166a2ce7d5a5389d3027c071ac9b355147214ed06ae1d57b58843303de750e1f831d006b07b9ee96147bbe59d56c4d8187246761e999ca61e655807a6990999066a2165f5265abd9bde8a8a719fd02f3c7252c24539e7a934a927045f3cd70837e2e86ebcec7be959fef06a0f3279f5ef0e03cbe36707cc3679fdfc024c02ee210a4039a952714c9275ade176ab91da4d72d42871852a1034d67fc7ad68ee3c7095ca72cc0da217bed53306965cfb1fa9310a989a4720fd7934be7999020d41b213e2f125cb69dd28064ea4a1d0fc9b624b57a21641f7d643b2390e73486fcda1c6adc0f387d74b3c5c80323d7f116f2f72aa76efec1e12167249840e5a988521fa733f45b9f1f029736f13507c34d1927221cc96302779a3c3c726b7691625be2f5fc58c1834c72695b9f7e89883e9d67221dbd359108d0f2717a0bc6f85c1702025f2c4450716bbb7cd64fc123b02e641443d4885e84a635faae0c304165c32268ca91d10a49f46eedc807c45dffedb729915056cfd0c7257b531d8bc83e7b320815b7ba5633d27181f1ea4f6bd321b7264caa1110321632d48ddab5f716d8486e4da48257fc37b4b88568a6241d86d46d45c19e032647e76f30b62442357a4b05c9161f6a8591d6e4a74e4f7f17b95684960f7dabec9d7bb3eeb71f2ff96d05204c5b88d9b0d9d2ceee61a061184662516912282c650e8c35723aa4d89a0b44d4063bffddb50e87d1966fc05ae679c3409d8279cd9411364f8bc3313ccf40b8231cd290fba7c88342f945f77cf0151726441c58fd8105cb8ad9761da8a53af5ac27be88936cbee1e2fe3f8150455566aa6e6ab7b1c568f686ce0babbfc54e49fed8766c4e0fba7d79831921ab0b03b7224cf509baaa9d780cca1ab68e91312fcd2f6af778ed9040d6ee144463b899a63327c3fd4d577d771561f195b120f4eb4434da9d458879736f5fdca40538c0214115947a09cc2b00501841a576ac84e5eb71c3793ec0f7c9d95a6627d5a159372588ae6c46c58152220f6e0b4b6ea00980d34132e768e2b5ac306dbc437c355720839d2fdb49a828e1def70dd4246a8eb65f9227f404358fe0abe0effe7cda8635c919f3f4476b0c61be50eea8c2e8b7c30d4dbecc30687df93ee08642f0d607207725e0d59bc3bffdc8bd715047add1eedeaacfc79707ba83d8326783d933b72303642a3af52cdf7a574dac13f07949a6456cbfbfbc4db9e4f39f0b9913d7f6cf5ed633a6fb60c1d14775101232a6f61057de905f725cf7df2665b53012c6172ef507a6e9cb21947d48a908568b56f10abd27eaf1ff846eb7ad05f853f8f9e59e82b865e62d350b02fbfff52dc8679e856efe8f291fe874852a5d9717c9efc01ab17ea8eb5d10e8987813bb48cdcd8aa1e1ca44ad6059409b0156c66b425ab0f141c050a8c24c19f5ba98f90a22a96aa266c74a26a55ef2f5136e58573671e72be8414ea329eb71d8544af044c1c89fe8d2878588251433de99f2821359f806e7b68454a4eaf6ca227069fa88307e5a7a130defb1b1817ef69638b3c6ab34c47c47a060872377bf94e7e94acbd57a124712177a6060c51b05bde14434c0a7e6688704e63cc6a8714e6127b7032a8e4ddec84a8cbafdbf610023e2324ea504c7295227f6b390a1e460a69af7ca2631dd3c2ad38214194c210eed928faec0cd31e43b252f254cb05847daaf66ce38aa524baa613042734d10e7bb13a34bda1841c97d05b0fc8a2497d5abcb28e83d1bc074cfb434f7a37bdfb9ad4032a1d483c2e8112292cf030f2158b2d615297cc98f8806ebdd46ce2ceee19947a4b147b2b2066b3b47f8e63fabd8c73193fc77e0b0e9f1d14e807e2d3c89d5ada0610ddc2161a63f7dcb5bd83a396aedc64d8be3475fc41472511f9f234ea0bdfbb1c154e7276cba627d9337e45bcf453d77167461a7430ba8dc1f680a9e84377bbd827831c57f5ecbeaa13917182f09fe83c64f4f3a84ab8686752c3cae190f8f0ef64230e92215b9ae8527045e329f89cd7276606ef3035eaaaf01412f52e85697a111462362448291cf8701caae52693af551139a9a831601340e5202e05150b09d5c37202952aa656697ce2b72f07625861ce7a300298622df29ac8dfdf83e71a8bcf241eda292df5e8adfcbf7ca54ccf2d4bb670cc09ccab5f984e614264fadbd10072fa27fdb092e98987e388110b53ac6f538f8ac4cee213917253bebd6af2726772c825cbf3635114e3a8e591f5a0e1d02ce8babb3d3337b0723d1c1bde79bc305f0e3a50c137bea8f3fd7a6c17eda287e1e04e43050085c2b0a5684a8282fd7a72fb90fc97535478b9073cef8bed8449d2fa18036d4b1f9c6e41c9bd49cef11e72d0f335eacd762347d285875091451192329ef586d398b5b3b045a5d8eb97607288b9da6bbd2b06b7a898a96948d048611ff058abbb7b820b76ee81e4ab856e72bbcc1adaa9f862c4bbac0e7a176a47ed967dc733e2db7437f6f5a5cbb0470772bf6c611209a40a92ed0e086624dfd2f1666d54965ad0b61aad6142b2468e3e4f20a074f641538d74bd5a4ecfaffd3fce7df438bfb797bb94e25a029f399b06727600ec2362c2f8e1d5d5c1f0c74aac8bd46a138cd4df21743c79cc64c7de2d72e8d4b580b1367c3d759c123daa9caf77e2d8f1cf9dcb250f6e4af1652b6e5e36396d3d5db01f5f476c1318d7a97e5b591e4cd709cb412e13f241d353a795aa1f73893d53d9f7ad538544021b57189e33002d0ec54d37f57c716ed83d4cd5317293257517799ad152b42da428a6dbbee5a34442330143fc887c02b0e997901c7229c892f960d7d4a1764abea7927bf72a808c3774d263bd67664e9b139902987227343ba6611fccc82128127c91689e953ee6be1629737da5f1ddabdd448dcf7264c18eb95d711d4d4f71b6daeb765ecce9c60ba28b29f44f74e600ec1d52092598d43d2724d04a221c7ca0159104383da2de746a7006023b537260490fb0c672c06f872192eb4ed18cb93025b9a11fc23d907f7cafb9a7eeb9c1215b767feb33dbf23278b9aaa0025072151bccc1b84f6a4254818b8455709811d89ac5fb4e522cdc0e47d11d575cf3875ee6e0a1d837915380ebf1b7b8ba48df6fdac58d9772c28fd63193e4db5e0b7136512877137f83051a1f8f3e9ae07389000053ecc2724ceab3387cfaeb9437e461631c7aec2583d90c63e0b5b33531e0e32f39865670b63e0a670c34a1dcdcc052886b494fcec50931cc433049d2d282c64e195921728def7e60b1bb38312fcc0fa85e1b9e50ede1444713188e840d72ef110db7b6727c8a783611385c2cfd65c4769818ad877a50c75f06eb3f7a7b444a04f946177214d6eb9026e3f326154514145c1e7ed4ed31ce3ed9b2fde9b7c8675196f90e72eb453a764d01977c602d4d0dd3970214b59b48f4bd18057d867065b39da192725a7aca010fcce4c27b4bf931a7ed000930a0c57a3abf65687005d86620e40c72716b2fc54d367514df9f88159dcd302d2b41228ec0f71ad659ab56c65d1f69723318a1c9df0e27e8b649fd5bc7701e94f54438528e56cedeef2471f1c1b5f10a540a2b84007150f01788ca78ecbd9bddbd82145197b62c010198981de2f100725aeb480e6e0a6a863f0e495896a3220f4fb5b0e4de9861cdd93786920c9e25328853777047f8b17db92e432941efc55818f542b5974d1ced27b69e0c40af55728c1f20a798fe2f072669e90d48b9601f5784340d3d88aabd0568ce44977eaa4f31381ee09ce3ebfe226e7009be19905da2c42faff8b3c438d792a487c06981729feac8809c1e04b82dde68869b53ec52c10019f2127c3533b8a98775ebc2eb72d8e82de6e5b3c29632318469a89bd59f871c164f09f936f91528097c902ab448df926b0f5be65a56152b38ef3ac6f13d5bbe99a8d508987ab8996519272c022fc30b05dc128b0d78b48615edd05dfc599f575806395c8ff49bf7f55fc9c6927235d7a447504a80058b350df70a8f5c11b290f66008330b69075869a7488d1f002c9dee70251037aa899b36cdf89bfa92549169a93b1be2da50b10961f2eeb972b138d9ff26a10fd2a2ffda4a3ab34c55633e5243bf5af0127d45fa8459aa1b7231df22105ae3d57814372069f6a080218e1f5bb953028631782ec54e48f774727adcb84178e9b4e114d4d725bb8bcda7152d04d1f93c86179143a4c22e9e2a7206f2f05f54a1eb64f1682251e9ccd4087fd08fc2918462469a30c631366049727592c494e5bcdb21245cddf7de9b0a0b2fa7451080e7de9724a4ded677078b7296309c545901efd3d878f3533f8713869960b140a2feba1ae5e9d0db5378537209cc17516fa2b881afc960ba79cf0d21c591be9ce3fcb43c429c99a35068db72b251252b0b377144822e7b43bc99f44c6cadd501eda9b7de1b93a72cc6b682729b666e8f817cf16bd023719d42ce387380ac87d2362c4f330e4393d4e8ace372d93f0f6f3a60b204844160b33aa064551203d17d52eb759676b66b4c9ac5a272ffd7ba87a49293094b706c77b137fbb39bde5f51513d8fe98267b96aff8d011123f72b7871743f690ebad75d668a80f8a8903b9ece9eaa7f3f51b4f53c6e505e3a4a29ea6f959a90e8d2b35525627d75066bb5cb804273e2f11c9b396e979e53cb3a1ecbdf48bac967d5bd13c8f519f0fa71bf52921256aae09feb50f66dd772bb147844f187acba7500bd53340cc37248e98b95fff5a04f1797a9f2a2537072bb220695f4ff8d58afb7467960e8cd9a6b2e8c164e624129ce574f42d283c072813d284fee66ed9967ec06d266d24861416112491be50207d144d7ec67ecfd728db4c18d85292c6cb1bd3c77a39b5c81e2fbc2e534f43ccad2396b9109365972a9807c78a0213ebc085081ec358652a4ffe0e6f92cd03429a9127fcc8c2fc4728317122ec30d5977a989b93bcade56f9bb8ff3900cbd1fb20a09f3d2209e353c0ffa335064d866ffc23e5d7e696bcdc34fd77607d42b3f93b714fc7882ee776f736fdc8b66be03331ba4a872bcdfcff0a0cc53822f497dd3509e38a3d340f37285206ef2d8de9ed26d2e7e818766bdd0176f62aac9bf2f802939425ed7775c72f0197c9752f8bc6620a85801c895e3304408f87a2ba43ef14513a3b98f1adb4ccdb4c1021f5e2c46c3d53b32e3e44fa27293f885d6ceae98b993be05968de972d0edcaededeb1b57bcdf0fd1f1a20679ffe668a7db111062540b306e84ec0f65e54c7c649a4c716b6b9e522107f37185177d64b8a5bb172e6b8a06a3779d187233b8369a2a6102b1438305922f9c4888a85b7ed79794d89e600c4e62d64deb0c8d6a467db1aa0dd3405841d6980dbc98ea33ae8ef2d15e4513e730c1faccb87210b3be463b1b07a6ea2b88f7de107532f8a4b3b6ecd11b03a935c5c410a58d7214b8c331ea7b54684a800a934772ec45be5fb726c094c85536f7c15cf3042e12a3719cb25767f76ca4ea7d5c7db9c427036e6848055459b56fa6ef021de78b518b68c634ca0769799a9dcf3f327d43569523dff23001b36229716a7678b08072d51473ef5a3c0646d473b28d4b81bb946f2ca8f8e1ea353793eb59690d169572a0c012ced19cd4bca26e241f4a1a4c097fa9a16e503851c90471b64d49d77472270793d1188a1be316baeed5d412f86f925a8248e3540c527a9619e06061eb3ecb63343eb7f6b28a41091e6178220ed2f981b6509dbce58aaf6ba6d6159b31724c85e2740a563e980c657e46bc76b982f73e9d8dd2c5f34bc851829c8cbb3372706b37c14526cf4300482b3aaf8bc613c3bbe805285e952295e4fc143a802872943ff2a089f053014dbfb0656360157b7444f6b77815708276c1a365e69eba03a917a35fe1e85c24d1b16c2aaa52288107286852863109d34abab3e64b047c4f14d0521c0d87d75d40d58b10867a156060c14b9299c672a39187116312ae02019cb580274d54df6288c34ce38350fa5bde109bb3c213441e15ad4ab6aa736445aa337eeadf4b1e9dde3087b37be6257582babe33ffe1b1fff6cca05cf1f9ae72b2a78299851476697f4f56fe4e36da8685783dc954c9f001a56f716925def12df41935248c72b356a0f05c28f89c61a4a66995d04b1001c7a48c0a3e3258a76d63e13a5ce522870af41c72d7ba4c639e5821d5e20779202e4051ab08b55c3172ef7b7d73a9053b494eced42de77ae6ca1e581f8f3d7a52902239082173c9236cc56646923d558e73f7ade82516d90be6cae69b27676d3c935bd850a13632aa72a3b621c5111cec897f7a6ffcb7dde04bd1329b3a127167378c2b80b0830c5a72f9592652cc826a6b709798a6c2ce3f288d07de6c8ec813f657cbaf933c1a3b7248679e2d6c9ad5b8b4bb9a85168553078bcb4b7eb0ba8a7c4a617ef6c9f74072b7dbab0cbb6668e62048fd6f83ad87fa803a8d8935b3ca323f9a7ad32995f360b0e269207e3124653c4ca6400c993d4420622eccbec416e158b3512255fea47253a8fc56f7dbedd731a3b29e06fd307a66bee4bde9a51d10d4436f7404aa58475685a94640a110c547439d0ce7afed7d3f434bff9a87eef59d1297117b9d33723670f86ff9d508897a89c6c861184f3ebf8ea163581ae7465be28ae9176a5e4ec4a426d644a38b995b3c96acdca4c7d9a6387edd4df4c251fac85102f62a1d72511dd1582ef0a095db2491761e2d1b152ebf8ccab442f4896165a4cec2cada72badd3824e9851a61c94b153a4e76d8351f1fe90680043c752bbf2153495c3172ccdde16310ebffd7b237bb59fc779daee97b3e20540133a4aa7384e3618b6e4912e663e6805e58364e1f3a420e2640f297ce388815f06f0eaa3e7edb5b6f3c72803b8f97d426a09a5762bb64b21f4540cfc2613f875080bcf113e193b2339072166b88250b866edf632bed75d7e3e4c0c32768fbd6646e2bba1d1f18923586727fa5f29405aef9e6a18416a17d4c3a6fd5bc9ee19a4a220f6c8803ee93901f22d5e6fa378d02c8aaa4b75b333051000bec6706bda5795e43505339326e7eb63fc833540c75c2606d4768bbd480b3f1971159e1114cabe632a183d66c5626f103667929b055b9320924e748cdd97b9c21d5a7ecb82cac8dbcaebf521681803d7259368a004f053a9ea380b412dc1acb24ccdd9d6c7dae2071eb59d00555af527220d2afdc1a04c58c311132dc6c2fa93a89c8687cb76c0d53e5659afeb2df3c39c3a79578c93d50b755967b879e1ec19f4ff86d1c438edd3b780e1e9b4f017c023e011c209511df601b7d379b74d819d5264680200a09ea8bbf6bd8b92c1c3e72706e3572de2ddd183f33a8299c1de72bf2769fa9a9836a913554072633b1a172b48dfb61d70419b07517cb6d8f3c7f42e2f808de61a6db1486cc88000b1a270d9807644876a53d9cbf558cf8ada377bae16b81138ae1344642e87801c333b802ea05ec2f9c6aad6b0c29fa1101841d8a68672e9e47632651d479bde033e549360b968436362503bd0468458d4548388ef515e1d62846a51f86ac532b649aa3572a08ddf9106a1ab4edfe3b43bf2f7d8d1f42e73671d2fe23293d48ce20cb6c663fa47c281ebb7bc199939b3eadf38cd1b88d4ab1ace13f314e7dee4be862e072238301bd3ffec1cdddec6caf1b165f2c6d5fe257dc2afa262376c1c39b8e5f2bb97c6e030df1c458dc7aded123c0143adcbe6e64bbab20669c7d8b9dbf93016ca16566eb4fef98101fd16576255c41244cf08063381af32a7ff4cb63cf4e2772e9d630a3ef93c578886548e80053946826186a252cacc11acafe2685f4ccbc72a133391ba64aa52724686d2d2e319268c8f5df11c25a6346a2e40ea891c81f3376c112f933b79f5a81f01c97729bbd05a0d6c315e73e8f1a36ead77cc53f3633c28a9af155cc4f5d4d12abcc1120f5589d99ffba30cec4174d5fed868299b872899325f84df0dcafde694e3b16b103f25e1a36afe2f793c3e5000f3c689c6f72d78eff552e5dca3cb6bc218d3f2fe2f3c7ef117ab10f69a655d951f4d5c5b65a0d225ba27326cf00252ffff19c0e13f60db447f43c57e408ba30f2ed8dce5b720f9e3184a053b841f23fe74b015f0906c641f7284965fff46dc6da23cf3ae131f39c406dfdeb690d207df3a82d68f673eef5aedb25563bcfb212bf16d9e30f06cf73c7b392068c9e335ec51e6fc0ea4c4a1cafaacb8a8fd22b0c2dd3d02e30722d47c80e00f2b4271a02d7ccd51d7082493a7dfae21b67cc1971df903969e672cc427f5afd8dcf4b232e223b325074d6c76c2c4262f9833831ee90b34765c755182a3d5cab9aad4a5a9d5b28e92bd2e5154d5506a74d17a2ec31dd5b3538a22f6f66efc15737508d097bb836abc1c598ba3b5a26f3406dab42ba9380feb50f3a17d8ca28fa4ce317642e6176c466842c134036d15e062a7e14d6f4293f1b2472dad48c7c09d5ca3220a305af1931d216fdf69495c28b34208e849753f41a4a72ff483015636778f1590422b783497539f619e37bb35b8af95a6d1151b1557d720d01ee690347d902b15c24337cd07362fc9175fc0e8af7d8d359507f239656063537b000b404832daacf9f52223411b7c5cd85e040e1983c1c20506b48ae82724c6cf329db983c215c055a0d79685d7825e1cee7a14002b57be819be2083167221599731ce1484378a080054b9744a8ecf9e16a56c803b76d0f1d9dc6e039a60c07dbe31a4761e33e8a41b4ee0677536cc5fc8c6ffabbcf58e7076ad82f2c34123b428bdb2f416e278d95474dac5049bc358614f05365d605162f371a6cb9972effe1104b2e87352af8a41805e0a227c556cdc2838d3187574d78bcf6e03fc729bfc0a800de7b915dae7a9f3d359536e631729f2e9454a35778a141d86661443f5b3bf4f402b6bd082fc83f897938a269795c80d4bfe342b46bedafe361bea725e99c8f2fa1d00c8a4e04a38edad9eab6267de459fd8635ce205bc92b2e488037d23559c007cc75be45e8cdab2c8a7d261a3ed32dbb47bd052466452d60d1c72b86379a3fe62051388567b339cff455a276e7d6ee5075af64bf527469a616049786070eef366f739e4787ac52429dea53a215362b0ffa72c94093df7f751a4000d3df81ea4114ae64f93011a12f52f8a876bd43ceddbc6304cd5d5d9a9360d48d17fbe8fc814b97ea1ddbbb4040bbafd486852760bd15c285206276c6f0764729ce8869d2403c188b4d42174a4e1061bbd10bdaa07082ee64fff0c5fdceddf72017c41bdfcd4f6249fec93b65ca7c16df5ae74b785702b1fced08b6275c2d3720f1869bd31760ce1070bcadba0d63e784d8392b50d58fe1623fb1f1168a51109fdda4f64fe875f1ae75e3cc847f38c5650d751be17c4d7ddf81dfa64909c9e08ace438acae3077aa42d17bb71f158579efd0cf95f9f7db79e3c73dcd85e27023c9f11a44e5d9d17f23b7db24e51cea9de52a571dc097d1d611bce41a94e66672054b00b76bdd9f794749de5d6234bd995800222aebf8272b60847bd4190bc852c12a3eec79cce9c1e5dcc61ac2b3b6102aaa261bd5c84152bbb132ea2e64fc564622f7792183b4fe11458488151def5b087d824f368a18802ee6223a2bda76596759423e20797e0c5d2442cdce175725cfd3363836f8e2271d6b3d0c4622c4726c42dc39f1565577bf99b8ed9f8a269d6c23e1b174bb5768795049d11e901d72f46a90f1faa4fdd1ac9229a6fc62a846cb407a173c643037f75776f8cc54e24e079728309cbcec6a2c2c94bc2db6f1b58a83e8e2eaf986a3b21c6ca14348df31d1f47911090f9a5cad687f1afd0300ad927b89799d0e2b5f621ecd74bf5d9645771da8eb42a6462914b2e2792ecc0358f7bd1dc4c9c12fb1ec87d5b834e30472e736470b3c2753f9f73bd2b4bbd2406faf19aecb0474869ec0966a4affc13572e99dc019a17f89cb5853f92fae0ac041829db1bff1901ae1949532d7247c3a5a29c6ac8307805f55f66febf28210f2f6196063f8e7d32fcbe4a23aab66043f5137d91844c7548a69c523db29c76f40a6e5ea71a7a26a7185cbd60670001c460aead27e4a11f0de74b6652ec0ccb079c459c53ad71d6f7a4fe786f7d99911b572dd7bc7429462ca01cfa5b331fa4af7e6ad787463d5fdf5406555d0f4321f7020d9847e1d9a4faa12eb32c08297668056cd20af424c6dae0b07b0e4bc24e1334cbffd3f11d948ef27ca33cadb3280c8364392394652a378371c3d129559f7b041af70b7cc30aa21aec0d6ce8bcc955f8887a952f6980622d4595f0c5a55e159723b4d6559bf6f98141741a8d707185ea416fb6bcc04a05d0a1847cf78d056183b428443df6a1b539bb9b1eaf0ea2a6dad5a4361e4fd024301703ad399f8f5227248f888828d0f9175298cfa2b265c69cb7b809d402ab11f3ed6212c24fccd2572c8e414a8993da435c641cc8027d2f9786353f15982dfd6cbc98fd18076885972f67c7e3ec28fdd232c0b34ae10ce126453a29f4d6f97696aa1c6945fd443bc72d8486291733e003d358841ae741efaab045f2e7b1c22789b40a3cf0c989b8c72feb645b5861ab9cd33c6fcf70ff05509b0d44fcb68bcddc7ac0895a4cd5e127286e586fa2599694c13f02c50dc3259a62b3a52592b942fc07a3ac31818292672294364f398958c6b313c769176c44fea9c42e982035442fe50ca479038a1bb53b2dab11df120b59ab23f5a20e3f90cb01553a92abe947da1190ef2b1046204029799f162c9e9b28957644897605d8b73b7eff73bbb875289c7d9d4b8d1b1856e30e0e1b1d4b070025f7ce9cabc613c6b49fd44f16f6120e71df433d0064d3572b24a2d020a4e92d5eb77461cb829a3abc3e58f53c36da273597b9670bf413172eb80bcdc1111b7d56751f25cf5164ffb65bc8a8aa4211a98c82199c486734f2632defae9840f38ffd9ddda0605b42c14d3807ed9dd4b764cdfea87e7024939727f34a6ae0d784b5c9b2fb92b76dc95c60c43894d80e876791b1f9aa70bb0c072557e41d7b878edbcd8d4ffb87347e6d3e30a4c2f8f3bfdb4a6563ab62f238a726fbdf2973cf8a8377500db30e37639417071e74476ed82894b87e30df398936804db3801975d70c6225b13774cbc1e265d39a23bdd39866cd1a4c99a4251c246f68fd10a005ec0f6a54d236ae592ff2d630ac0a481c639b853b488764d10474bd753f33ebf055e856b46c7c7201ed107409417bd4c2cd9c7493ce62444ea8f724cf5614d762566568ece39e03385d7046390b38e19e27b23cb5d8888b5bac2721afa2738bf86ee712bfce6e481c49acb29c50eb3da81f7ad389a1a07e68fc8727bad8552e022b4a645cd4725df5f27d6331f256b93208933afec74911ca1a372a8ddc668e756cdb026917a6b6672096f3a5ac5fc4bb9470183d8c3ff40f41155c8bdf1d1951a66052ee7853ace7accba79183922c3c3dd809eed4e44856aeb72eefb9bede73f747e5ab4ea0703cbb46977bfb2e7c896f9b1614a7bf569abde1382d3094640e49d17315283b177a8b1554af5199d74cdcd52155fc05acc535a7242350861273127357c52e4bb381782cd6e829451154c33ce5911e2bc6be00d72fc4875943def905d2142e276776d49b5d8566508af78494d1a47662f93949e42606d1d31041be81a8f52da476d2381033e1f856a23cbc22b6975d0a8906ad672dd273167eb101334a1cc47ab93c8b7db17479f509ae4cce4ea450c63a1ee3b7284629e8775afd2b2dbc68bd6167f33e2775091c0658ba8322067fad8fbac5272bfea75ad8218aece5025354fe9f72c8234ae116397ce080c281d387b288ac372db103cdff16da23fa15a2f1d737d2c9e429f2661ed54a4fed211f40d754cd27249e7950fbd3c34aa40d24c0b9bf5435524720c8c4235767243d5381b8c767872295e1f485f69174ddf1f81b8283081607d64c75ba3c5aaec52206a764de24e72fa1203ce8b9b271623c342acec6de5c66c965e8f9f6f1611ab608cdcca4ea451122f445c99fcce41eeda8f825ce895799fe7075ce78670ce11ad69ba98ff20729b4613884cad6d68e4c081241f5d7ce3cf3f29ae43d1095f8e9d2acfbead1753c5fe4b74900a8c77ba94d823be338d1824c0b269453b99a651952cbb1dd38b72c093de802e2ff351f5dcfbb6c218761e634cbf3398337399af57d6bbecda661b19a5f203c559a3a509771b96d55853c4eb08d492ea42bc3ed6dfb6c097ec530bf664898b5b32cf8e2138dd6dbadfed82aa65a0abd03e9b23c76b4ebba339bd72970e0d0ceb71f2c529ed91d5067d3b9fc0e78ebd91d1ac60e00c63a412a8e5729b8846c936718e3906f345d5c3f1aea89fc4f4bab5297e884be20ea3aac83f7269a5bf744c6b805c2aa5a5287af900cca645596377e2a9aff33c621b34846771a064041d0e455e8e4f7e14c5a671f9d2937a18b1eeeb656c53ef2f164c0f2a7212e7cf7e51b2d6816f5ebac08d2e8d681fcbbc372a20e169d54d51796aa79c129d38944cf3489ff352179d39b713e2c221baf14fe0c5baf785ec10bc5503f152428ed3e6c3e7ffbbc8cb883b3101e6f06640891406ecff39a8e0047c14243a729a4e9cf317d202018c8df01ec1fd7df5869022516ec0d54217efd915a9f8d072a5f1e8a093d3010b8eb68dd6b73530feb132c74e48314fe34597bce0a1148448ce2df6022f2378f7abbdae39ceafec662168c712cfbf12c020dd31a29e140e309da4e9af85045730fc063d9bbf502f65244f0f23cfce6b36da183974d9f6e15acc8664961e2e4756c4353992492c83db54b28911431c9730ab359bc32d72b672243641fe378e35d714286f36d1be1de9c9ab5c3a261e817f3faf419f14fa931f4ef08200b3c3a25bc0fe06750fbaa1d4a8bdff64a10a826f166e5e5a3ad12e72f808f4706fb6a178ff4b3a070bcc2ed0fdc7ed6f9b9ad4782c5a75aeccb0c57276b88d2ea5562a948dd6ade5c02510c36e072fbc9d87eccc9c693a2e465a1c72db6ae77293f116966647d8ee14aa0aec3f6e70727073540e30dab69baf6a9572e1bd6e67aff554a04e0ef02fa81bf701569569d275e987fc37b503a133975c2c7a57c1cc9c4136320ecce49473fd143eda54810f78a08f906a276523dd30c770fcabcf8d372c5b5b0cfaa3812d5db216a0538ad7c842cc6912cc04bf36b07f4945a83e5ef9970fc2e598ef25a8fcea251ea07c1c6a911ecf1ad6255e1e87a409109faa5965c9f2da232d84b3c33bec2558f8a1cd0b8a76b2fdab0b090d2e4216bfe4974423eb1eb4059c25cbfe6baf270006d21ec411319a7a0b17d89a329b62e1b6cb638c546581e9e3bdb535dc15b41f9567a648401877b7b7225c92151123ca19e2c0fe4f4ce4c1cb251814f130d42c2743a6fb2f15dc2e06be8dc0d7cf2a0ae8d30d89226603840fb4f0803bda88a6695737d6b54118178c08bf7b804172f55841a3c53267f04e64e3c63a9a558192b4dc3b2389d103374d41f01941682f88fbd8030203a77828d188bec622eee8f61e0c1c10f8620179ca55f84c940672e0a41e7b8b0fa1f6d35d405b5c923d57d747a9f494fd0644470a1b8fbde10272aa9fea3ae35b9e187e665316737a854aa53735d5b19f220076634b03d7a58b72012215568cc6ddecf5b94c00c7788fea75b18ee29b7ff4c538638a580327dc1b6c7b965fc4f3f053e5deb843d603938c73069ca6e664d00afe62f382c8516f72e4026f712fe490ad7dab82492008a676f4d065ee7604594f1da1bfafc2b60023fa1db27c661576e320dde24a2f6d13d4590c3e53784663d7507dc2920513585dbd92777340673444e2fef6d5a2bdbc2f8005557ec05e843851f849b253cfc23ff4a23eaec86d438f15d4fb84cf7596294b57e9ea6386d0cf89f2ee96e79b94726567f16199c6659311058bb760eddbaf84d42bf458d7f9a2af81b2b085f9fb7245f78ceb3e32d7b046948916c84de8bc1bab2b96e7d3fbeb11af05d07ec89c7233dac13fa20c62fbe9031a51f45cb1c85747d88d7ecd7284257466268863c8106553480adc1919a8950b92506fbcd3bb82202dc3a91a8c74cd70d78cd338f14a4d6084a354624d382618ea347d81d6edc4a12f65c7d049c26a1ae63b62b0cf2f7dfc2a016e64fa1e34a6673877c578697e28a574076f13f5a5772e74dddde263f39589e7bdec156f9151b09ef1c413c1d49b50d1d4cbc2b0a92c5adf392fae723d7d84e7dc4406061e2fd9be043f7aeb733bca49ab5420a98404b547fa66764a4481cfc6c003191c5828aae266ad34681cf491fecd7c78d309ec757b9206ec721179416566eaeb3f1a70d0462f5d45c10b263feb3a7692da6cd3d777744ce13ae0b0b21dff48c3e3301bcfa90666af1eb6b2ce558eaa69930a963dfa07db484015954e35382af49a2fc15a6a841c306cf64e837c282c6b699cccf8751e58fe2a12bf8d8cd532a16da686d4bac0c6ed1c2d4f7b7f09055c1d02196fc8a45b5c72211c470fd4a826dcf595eaf4fdb2abe548d64f24c9f86d0018fd2d41ddcbb0501ce8d17815fc99ed29adc6983ea5f29a99492d2cc3f421eb4b69999484d51f57a1c442fe2f1c6b2ad82098d87495abe388e9e58fb57d7f778ea8610c4946fd587fc6a66adb8764dfe9a497b5e34d6bc32da8f96800d75b3fca87c7624bd5d072c3cfd4dbf5ebd875dbc58168c5a0801fa0691b925af6b30475d64a29b8fbed72abed18106f8ed82d08f3c090c05c6b492e28c2a7641e6a2d3997df4ee7ed4135fb376934c33b5b30916c8fd785d3b9ccd7a4e5523843b72acaab5eca70abb5721c41eef68e58bf6b69b1d275a6442494d98b9cd195f1bbc2eba70dea05a7e50745861af3223f22091e31d027447d2ed2422a30dcb71a864cfa161707b091d772167452c8f76189925beb22f0f7db8728a456c4319c1f0e1f7fc51474f3f8d807339572d0f53477f5ab333701800a561c2a9e1ef46068fca0c5d40010bfc9d0729f192554f9e7ba1a07d05b498b7dbef6453c6ce7eefaca86cc0ce0a8a11eb5408aa4ea08804eb68fa760615fefdf718750beeb68a2535175f720cfd46f554922195e3c435529aa8b970478e635cb3e8751fb037f039dc253a5b7fd2b2389c15a588c2ba60504840d6681a925b8754c35fbb7b06824acf6ad8d02fce17a424a72d81b50a3b70e67e0cc1c0410b803f655dbb3c4de7ec96bf57bc3551ceca900222e5c42ef5b73358400bf1f536712f063a1894112980456ab3c1ce9bb754aba72cbd4522c46507281448ee8b7702cf2a78122e26ca4c013effaf6f2253748d72f70eacf9c5076f9e4e1c687b853e6a93aa361b2ceb49298050de427af8f425772aa6957d09dc6d6c46154b0fe351e3daa4104460d41991fc5fd87df2456d6e87296da62086be4f739715ccd45a2056053ef2881c4c5344f2c05c4b8c86df2e072fb385ea6b7f6a2de65cf8a33d7779b80fbda8b92e8374ea5775829a65d0c0a725571b6272ca5f215c83e47eec66129a3b7ad67ef15e0a30a8b97bab3caa5c801732ec32fbf1b72aeb4a4d1ec8edc43ca014fe8ee5e0377d366f7e10249229a5d83053a4d60834e15ba87bb7ee26d649e8679b00a138704c28442023e1148bf72d8c52de42f320b341c377a139f7b1c26bb92236ada2eeeb612e5df0d5618434027835f364ad3931f699a4851e6e1936b4e1e5e5f27e0c00d06d92ed440c9c6108ee7910345f97e1b5cc63648b3416664bb7e0a6920d30c55ed555ce239beae41f76fc21f609d78583b131788edc6a4ea45b1b8049dd72614b7c6c17b4d357972b52f2ca13e262af1ad6f8bea337e7d4a23d30a04d3e6fc4176bd9d3adff2aa312938298baaba41706fb6ea1fd2afd76d92d198580aa8acce4b409b9ee5fbf07214abe34195d335c16ae3e1c6d428f451fe42e2becf111e05115c25c4e4b3d9728de6b5c4fb80cfdaff23f6a07c50040b443edb5e0d8a6dd95bae02a6e15f635e549e8a8812f47aa3173f13e26fa06572a22b3b50fdb27c08c419a237d58baf7277d089be3c3c74609eeb5e8d754ade7d4b23f06b039329119bed7afd79231447cb692f53642854acfbf5be8e8157737129253b5d20f93b45da9b6dafbcc80f2961743ca1aa564749a215294ff53f55a6769da6355571b75ca9eb1f57da3e085027fdadba71a57a312fd983772e07b8cc745a49073c7ad9b0ac0014af71c49061fa86892c12444b119b1a2d03507aed642babf37e5ac9eb28dfbf22d650e8857205722e53092d556acbcb7eec3c172e9e4a985d0e45ece6c38bca021805f45e155856c690ad7b620be78f959647c6b8c640794cb9a22c25a8abe545a13866f63113b4cfaacef50e249973538d9f342ce98b66c1c0d6ec26b4fa7fa684185096510e9db1190b663c96fc5bf4d1d4c0448729adfea68179c4fd6e65df5804344a72969c536b46e8152a6798ea2c6ef56f94f1804d85d9ff4f7f1860a021ae11ee05134200e3f3410e7cf804260f15109649b376368e7250164fa697e601f897ad72fd9d77edce2ad36c5d061837eb958fe3bba5ba677dcf53eb5c2b2f693173bf72abcfb428839d43d4e4c0815d27edc8d77f66170f27800c7e8f985ab7f3468572d0d01cf077f92a4294c099b603b63cc3abbea18cc5f7456f4fce71488569b92585d22152ad221007cf2356229e393a83150acf90d26e35f3750a2f51191ae30f44254fdcda4e2ce1829aacac076a4fb2633951ee11ab32b3f9a809871a5582728bcd15054e9b243037907480f0ecc4dc417ed61dc240becfa5c188e4f119e4469c9a81888b368e80a04c5314399a8988160f1f400c64eccc0a343ed737938701622bec649e568961ca192939b87d10117aa97eaccc94ef3a7979380dcaa120569ca06975da51536b181dc35cb68c30c090f7ef2078c2fe0212ea720a21b00a724a12962ec0b9b4454e78d8a7ec328eaac8915682a95c862ac4d70e44edc0fa72e46335d4a10b824ee8c2a0b5c037ecd9a62000298bee8debe59b0e2ae3282372e1c764eb884eeaa86f3212f68207bb1013bc774aa88209930e533808c95ab07264eb900f733c576f76029dcc518bb78e0ed1d239527c226453627df97698f9727cd08aa83345cce9b0b8540a1e362bb4c8b31aeb25887ed8fb7ff22af92e497235b5587102ca69404a3623ac7ce85a802fbac8fa3da92bebc9f3f304fccff372ffd4074410bef7091f51ec0a8faee2f587a308034b69079dfc12b883c63f7e7288250802e3dd9feaefc55ff4b37eafd0bfd8591bcebbf0100eca941d9a74f8007fb2fd472309eb488c4d7c4ebeb5edc5cf2f24f7a4513f87c9e2120f54bb113805ac8e23c695a93d0892fca98fb65759a01df1db6fb7f53e574cafa8b1b12e72a35a0e67f75bda44044504e41c0f6f6cb77a03fd12761a7d54441c309f93927212ca45e54b7769a7946aa34cdf819a1bfd7530abdc662340195a861813b1f70fb0fc1597b53a9da3a8addcf231fab399240bc37707e4152e40305b4d6058ee728ad546099e70dd68ff0a2016a5726b7304011c7708e1ce40b1ce8cd152bf3c72f940e6b85b2971ecdfcbba485759dff6ab51ba5341fd684ec6bb8deffab742097560c7fab6c57d42f66027fc031062ca388d653e4335ec4bdcde322cf4f748620631ad7deb0426b868a099a8a57c3e24925506fdcb6065a54cc31f856c4ae872a0ee420acd2e7a85bfce496724b1dca19f5d2c7623bbc120e425745d3e75aa72666bac82768588da5d8bee788e7cfe58d6d05d5967f4c53e5b5671c32112ff72f169c72f051c8a41d4866154a15773b9b2be853e57941e37c6c5d817f2e18d48f6714e58f87de19b5369cf5d29f21c956ff67aecaee552c9ce986e3ee851093eba2349db456c54eb65beb3ecc1d333970d6b5bcae84a89519add56e6755d7a3200dad3705e68e67286b7a7cebf06b281e41fafb74112f389776fa4a7920fa1729a744edd179c9f0d30ed4611050f77055ef223b3c459e6b61e4d984b757a7b6dcd81f649166a4c20726e09ab4ea12952e41225780f5aa1f1b522f75176a25506401ec8a8c376861e720035b859d12968a69a44f22c60bc440cacc59860f5ad72f6c9e6d3249c001dc81d124acfdcb150d170aab7352d3a632120cb3c4da9d972ba7f75350d7c3af693ef2944512553596400fab6fba62d5677e7752ac4ce49726ba4d9f4239806ddc2bfb1d325b6db2acbb90ba598d7a1d4e0b80691c5ef1671286fd4b4f24a6b0f95a3cdccb5dea1d6a3bbcbbbd77495cbb4c152cde7d8c372507783164813423d66d5a882f2e2194502179ea6af8f89b7df326549437e3c3ca6f708e2452997be1b9ba785e987c404bcfdd699e96e98b7d5aca5347fee9e727d75c712ac4aaaa33061c5caf9c2a5c31be705f397a298353909fa9bba82db7227eca0374eb00bb7d958624909333819f3c7f93f2546dfa2486733575415f772638ae00100e996c796f9ab6ab4b5b5680c174d6a55539b54121cf098d286dc2de4fc9087594689cd0070eb6e94c4ac0c4c0245ed39ea95911f2c378ef091d65d553e9e022af009bf6fc377fa49d6392f53f7e63830c7dbca5374e624836dec724f6d77cd43faf246834b91d76163d86192e8010cc21cfe558e69f5b1bd04cf72f8695249ac7c26ebb493c77df0b8a1d295a503a9c04237f126f22c1fd9215472388815d9ee350b4f0e4996e004e06a474ae348415d7dc5b6a97bae686d237f4134c9e62b5ef410d3e8f64422d2f51d9f0ef832b4d3c198bc5791424ca0248b7255ab7a59abef63847c0769b78909870334f68a762f3f8e6d417028dc8445595960c292f5eea0d9c652d61f38bb3364e972c3d946dfa100e8f59cdf98323a0d19301b679fd2096d5577a626b167668fb8632ab709574a8b98151d055d66651f10239649757773cd2b4423738ec6929326e451e4db92485fd86efca2d5a2fde91b793da324200244b7b544fa1659d3969b10cf80988e36584d514727de2442027231f0e6e5913f272385864b45fd47b1d2dd30c7d050bbbd91e3526cbd86f1ad72a9867bda1acc3b13482b3d67ea31a1b2917555133b1a7e73c40bd995f88e89721703dd410ad3acef2c7c2561c1deaae4418922fc4a64a18e02283b838280ae415b6066d55c44b99609bbc7389409e432c62ec06242a0aa1fb6e74066ffc8267267ff0fd6d15b5f6271824ba6daf54401d4fb6e486b7b7d4889973094c51ca84ac781b04aa953f4b469bc8e3b75d3faaecf82e7b082af0e662112500a85c746725592872b15f4b55c975498292de10278a50ee90e8184444d27517073daa9a472409094ab7f448142bd7cc9c953d10fffa92cb5fa5186b307029d5eae6e75cc5d63a52d20d779537f49e78678359fa5b9d850433058020130ece64e54be006c4a8ffe5723d326831c94150d93097cc3234e53877aae5899bd65d648ca113b7746cba1da844042adcd539732f8affe382fbe82ce9702cfb72cca55416a136b06680dbd1af6b33596e26fee0216ddd34c47390a7a8040cef900febff7d9298b2b723333e9649262d53a4a4719fd58cb68e62643717ca2f1a1e6481807e0f52e8417c9a8e7709fad26b326b873a39a24ae6f0450d8e3c9fb69a0471171c87ffe3e726f0ca856c991f93ebc40958e41f064fe266b6bc03190412ae3e95bef80833172705e1b6a4e6f48246a47fa427ed4926824c55b7e6ad287a5775d44393d47537237395fa77a41901977d0a48027de9645c969fbc2144829da8bd868d92808b472da924b757c86ea96d69c17c20bd85ec6ce34652189d41604f9398dc555ce7b725def373e9ba3abbbc8f6a6337bd273309206973979dcd8e765e0171432f32305b3a1b000f2dc899b29a3a2195dd3a8ade9e3b962010d12409bea4111c58c33721fef34bc4007a4d8340be1c151d0a05fe78c1cf884baae8d29452e2dd651b621521a5ab4cfc4e5653d6ce87091fe5e02429275b458c7670490bf1abde0ebd05983bc1de1f01e46feb4892ba016c3ca8d72f833a7b1a95f85a042f39c296f6e72a4b02270a47067582d04e62a143c05b8c070e11285c870a250a2d85b6e4c7572716d9b3af44ef4d97e13d276e0eb33eef07f602e3352f0b601955d5a7b6cad722f62de8a492f08e45768d5e5106f78afd921def8a29b440ffd53bc14211b3052bde336ce78d83c875d1c117696d2297ee13982e935d93f319379f1281007a172363e3d331f9a2995d6f90581e936293213e80757799642a7e9a2e60df014f772029a91a7a9a8b3af48c5939a552af5f5ea71a729b556edb67cc23611105da70017937c5b6ce839dc6ccf8463f67c3ffc44e0d8143b8847220034b7c52a243a7294a47f9197dd1bea6d0b2fac260b1a83b4bf35130b7391ab56702ec4aa919172f203ea12f8d8bff5cfc9df7124c0d10435b462bb1ff0b5c953eea82a1d3f0c43f335947fef7fccdefe0829c013194816a1ada41217482d1d18561888ff7c3c15b27e123f7d2b44cf79e34ab0bc240f8db8dd9c1f153ad60a7f093d51a2a1f7103721d743a420304e5f9d036cfdf79e480157cbac44a02a01d4aca68870a5297218eb3c85231593b3cdd72b31585be369cfe27b23a1d0d856e220061911d38a5851a4e51839ab9d20cd953453f61019d340dc33f140399d441ddb81b3e77015729ae38e44361538d652e885b58e9e1f90a4d146968267f0540ff86fd9e36f0a72752f4df93a917366260b122f77d9e10e8058c281aec6b4012e6c2065bbe635720181579b40140be0062bbcf28333eb556c8635808dcb5a44ef1419d84296874cce7b3d213b753e45d17b76fdf8ed7bf9187264128ddabb77309b41d06b350672e9380355ab44e8d617c5992202f524031b4bcf22c96b6dbe3886f6b5cf15cf236c6de0aeca04add6b70b81b02569a98ac3ed2c6ffb6c58371daee1db1e06496b2b57460cee6c0b5526b7d48e27c3cf7fe02faafbd99ec66607d8eb9bd69ece4155b96d6f6cf740c0353d38fe5dc7ea4eb74f9cc2e59482e5c39a9aacd6a8893df708839288a643ef0ed17b52330880f8e7cf0744acd9254eacbd644e4c1c2372437ad46631f1e5f24d400dddb6f04204f6a273d103a98032b40b58ffc7a7e83ac960d2ed489212caad674ff8ceecc74c58db5f5b75c308e829f184c409ed1865e3f964c1b06bb889cdf6183c23ad5535a2329c4752b16721c1a9935eb2b9df29a7741fad819bfa12d2ee3b995461997bdff9286d430e3985c2c02c55ac663255a45dac83dcec781750229dcf94c1d8f9a90bafecbde03a4d0fa6c82315066b72c3133d807447986ef7f44c2fd5bcb7e4d6f2f3e821f2a2b4b310f4ad184e40507fd8533416eee6afd95a7542bc4bf90ea5e76769def06cf1303eb72bcc72a73c1231ab11fa168eb0e95d12846daaa5913ace49e670fd82a6a81540239b8c3d723eaf74c72a2ab2ee34df45e3fb4572c406db2d97261357b514509fd04e6932643e6d064ca72e7ffb6b1606cb5b40af661b8f32d7066e6809b43b7cdaad272b726b3714e52755b4ad40270080a57ee5849d23db87ac239e79fa751090cb21ae1a810ee73180a34cc376a00ba1c219157886a62fd22160a8b2e0a977dd45579463a4ed3d1496f98cfa1c5439a85fea1feae10a601b1131c808e108892e5f85d1729198cb1c9952965473bf733e754ba60505ae5833993e96012cf9b195293106725f8d9a4934d8b7d43345a677e6622593fe60f3a2226fd1a702c7b96505a5f320c114b5ae33889a3db886a4e17e690e413b3f32f19c14aeb33ae67852bc895823168815c4660352a1774a8f5378c8b6d6c3fe89f76dfccd784557f645dece1946168af9fed15a301a2b1c4c9b7f3090215f5fd98ee35fc2c63e2da69d4c63bb7226a646062ab94efc8452791ebeca190cfae9563502f8974f015ea542027ceb7256e273b2caefab6c0388f39f7ebecd8754681d7e07b168f76e3ae82d494782727a7bb155283d1dcfa861773ab32a17e99a255f0934df6cd0647f937ddd228572c209c9c60f04a56ba919fb56c38d71d85e8a28f4ea9265a96b839c4f23219572bede1bcd1b508afb70860ff2a12da8a51ef97de0c759d04ae0042938b255197278ff7185532ecc1460873e2398c6cbfc86a4d456c956aff49a203a8c20b54e564eb4e2ef533d65012f08c96f45091e71fc5e8331f193b72c50797ef4126648727d9c162df445fe81e537f0c75b936e3c9de28f2ea58083b4bcca544812902072246671f321e6b8536495905fec1879e9da027c9bf12de21c8ab67b02eec3ab72802257d1d7807509db48fe60a670e689c3634c7426db7947cfcc37be95b14229921f3425d5f8123372b9f5418029d04832af975625883c79018d77bcc5309672ebd3d00d851bac257ff4e2ef477b542030d4b2a5265cea0cc94c44cdceb06e335d8f2f7dc504a5e201bacc7589a4b991716d5e9c55cccf8a021e308cd2d64c72ff4e8afa4e2a8c753de2dbacf9a0c08b6dd5ff2d57ded55db53560fbaafeb57278d036cbe58a2ea17b363e32d392d3b808859c0fb50b9aa805057859c9d6aa72a08ff8a11105483b20edfa006ea85a9d57646abdf1ec8efb5cea1676cb927e27378579ff65878e544cb3414f02dab8a2def123a3b33adbb7d792ec90b9cf5a02b8709ca828c82c8a5212dc65257ac17ec7d8b3a4794d6b041b19e312f36298654299492c95a11e0e1ccfd978280c7f50177fc5d8af4b4e6a7441f485f1985272e6421060f9f520f259c776cc09501df4d84794885fe7bdd6d9c14cefb5c8941d6b714743bb08c0b23f197d479e9aa1bf190e6dbdab50d0d2ad2699365752625d439b1e038ec1e800cb2d6b6d6e42e35316de1e2c8cf39f6586d8e8ab8d0c8772d109046c23fb12e7a7076da9abc2bd290dd0893a5444e5425d726d7544fc7c23656e50e0332e5a844979fdf77d4ed7137aa5e40541451746163a203ba1482e5c112fccbaec2588c1d5139d8e4ba53c83a8b016182c833948b5c577ef4266206151084cbbb7c26a428b2b28a404bf9efe0dfb12f5087da232e05a34a18851a972637b75613065c82b051a54b173a56a46f3f5ee299f931294d963187ebaed8b72e8a22e33570870fa06a33b6d570829b80b1b1a90b77a99aa1a83af91d938f372017cc1b5b6c81fde08f4d6bd5423ffd298cd6f4b11377ea216c802849d6d4046a05c9f7b43fbf4b079dd663963d89e8d2c1fd86c41c64627735d24aac7c99c729a5934f504304f7de9a906dc95983bbd813243f6e98e8e85a3f880da4a57a272508528176c4bb29fbfc6fed99ef935122e7023252178bcc540b6020522be6e727fe0d197a0a28a6d5c10af3cb3df85b49d23397c225219a63627b5d4f1adfc04e8073cd60bfb09125bb7943d3a667172bb3345a6c2eb9225246a09872f0704519aba2b3424b93f53fbb498ef21cb76433412525ae1408dc50539df6e808c8e72a6f89a4d70d15f1b44fb1710b66d683cc4587741b0826a313c6a11474c8fe800237f65d45e81a9cf30b114880a9b15f1fc43bce904cb5b894bc99d9bcda1d572070e9e7a3252084076f6d98b518e39a4ce98446a8e0319b5f34d08c824fb0332b4745233b0c0f3c417e0963fc33079435d1464219b4c8a214b104297ac5e273dfb8a051b8ce5c78903efd5c9b26fac4bd03d726e512e2002ffdbeca83f922d724dac9329e1300f70a9bcfbd53dff429dd4117fa87d8530741c4df985986ba572afde32629649d306e1cbaf594f1c82e329f824517827f0d74b50de427f4fe07227e48b3ac64a78c237d10432ee82f4854b05b88ceeba05873e8fcebc1406a672e7670794c4f843146b668acdfc940c2cfb617eaee1f2408f50c01fab28869139232ee72df2a3874068c2bae3dd2c6949cdc4417623170d54cbca23a7c655364d12ae2cdd3f7b8f0bbc796d7246874e0ce7148ab5dff46fddc8dc7e02e6c96a72f30143f7f44e29e8986dc8a05b70ea51f205b5bf651de1386913d4997bff931121a1563a46e7f439390a54fd2f91b378db0d917512e9deac4af727d172d220722daa65efafce48c28cf484925f1e9e0be4c8a739b2124f060dff9f1d5e34a72655f365cf009dbbfacf0d565ca5804b750880f8088f4ec074cccb06037a7bbd728e0e402cb857517815b6c31ccf0973199791fb1afe96e74597ca50d5535930258b8ef0f30eca264bae7d908e3f258741a9f4e11cb0b6562f30d4a19a7d5d24729f4928753651970135f83ee49befbb950d89da18834ba65b3807723eded244726517ab9716af0c413e4083da5e189cf59fbdf2d02c9ebfaa09af02b297bed4726ceacda23cb31ecb1547eef8d8430c3b79df3141b44df9521af86a6dbfe162152d96a37548ae81ba3925d996cdaa956f7df9817295d382aeb8d64851adaf6172f1b0eba3d0efbd32c3acc60dc4d9e405913770556ee631a926d14851e9454564cb657ffb736d1233f0d3ad7187234a0ee84f31435dea7b27377e17c3e95466723ef2e52a47a15526346c3e3553a17ebc73cdf311dc5cf184380101a8bed6ba7249de7b85357a14a81a80e917f8430d3bea08e8b097591ebee0cf472838c5a954b1a56f03af4c8f902539a81a69eba4a2f71bbf5fd75f4db98416669a96c68e720eb2206336447860966d4f4deef3737a33162173cd919dd82b121a21f5b3c04fd8c1100f4781ec2fb34afaf78ebabc6b3de1861db694b8488ea9d8d9a7d42372b214ab5617e97d11c3079a655d25abd20e383966adb11326c1bcaac1f580a6724608bddcf72d6487e3ddb491cb99a7bd734e835be339c3a9db56c19f16275b6c3a8287da78f9e0703150b8a194d12915687a8cbd523ff90f9b04a0115d6b45722604d867cac5be2554cb8a8293e7e4b86f8b0868993a77d4fc5b1b88b24a85728940003a013d0ae96ea72ff04427b1d2ad0603160a12394b18dbe1035c041672a002d6160ef50be1c04d471e6c9e6ad56f690c4ce429d651c62eb096f6ca8e3d78ef64d0bb1f36dfba9c9082423c80e35998c7ca8d84aeb9d196be27e3cd577274b73d9dbbe52f1105bdd0a6f8243a8ec53da6585cc1958d4d0f50b4a7ec692872135a2e9acbca62ff943fed5e2734cd0f7b5c984068d8c7089df46a28ccf372f3a178f4bdbce58136757f50b3d0d86e7ece0ce7dea903baf14c8c4737ec50721494d86836503b686cf152b63a5160b723cf523084ae277087b36f406779227218e8948927d5a04609f82b548c056339cfe523558db220de404ccd2e6bce8a7267fd158bcc939b4024f5e3a1c6c0c6066b1266dde768a3add2163cd2e6bed33296442c7c74deba64941a6b3d5aad2f66e9d78e4a9ccecd80d4db35a99784a872b07a305e64cc7ad49d7b60fdebb9b5e0c7dd80f4e68e15964b998aee2335be24b84a36583be03d8acae2c546f7122a089641e2d3c1104b8592860dea4336b7722bdd8234268326a454c9aebf618bedff95a46f6d40be16c2a749822a536438728df00d103321238ebc0054202a003216edfe1454c059faba619fecc2aedcef727c50222f531ea437c5025e526c5d6a0356dbb286e4923a2f98f19919758bde23795d8322990d5ce217f49dfba69d1246338efedbe0b1cbf14d50dbb55a5dc44bf4fb3614690f0d174e43ed734cf1efca3e245cb417d61455447476438a63df4c67930520c293f71c3811fff0f9072c51685905d87d8237c9d4311aa3aa379a722ae4da132b95891b1e4f47e6b6158cb62a58bf016c97ca0f7700af0aa0f6c7729b074c3676bbc75fc71bd52da76b1ac08343ef10f0013dd05551ec0d232c5072bc0763178c5247fe1aa63d7641a1443dd0d4f28abbdb1445628f791e43772c72c10d153b6c1b872d07497b51de0d1c7455438fa77c2548b50f12b7a9b339855d7c55ca6930434e71510b6e3f179cbd064fe8443ce5bb2d177365995d89a12a72388b7d3dee27f33cfe4617582fdcdd4b2ef9d558a61d65ae45b543f7c62ae44d40a0fb652c49f3c5bfce2984ab3820d64187591d2f77b06e61d0716cefea1e72a008d1936a7a6cd57c0f99ad92858900306e99c4c8f11937d450c8861bbfad72dd2a2fdd6890f1088050d34381999b886fd6f01dfed4f331e410dd845cf8a672971346b6976fdfe36bb5e5e92663d91b5a5a57d269eaf0ad86b5cffeb001880d08ee83866fe651554f8de389d7bff4ddbd76fc561489216223ad536d82e7ca2e18261eb659013602bc4de430487ecf9d777bdaf23eed3d63405b5f4875f96a1d2e181111315b3e012693f7572d5b25a6e5ec8afdbca02205a1d1571afb7a2f72fa24bde9616365069b230a744009269cde1d5c8f60d01b64c7eb1d11c468f065cb70d072136ad534bc123f0699c3d7d96c21811280b14d0c628c20d2e8414f72dde839d6f16ac6f84bf1a702a6ca6f165d51afd6c63ab9349478ec5a469abf721f89b28d4e238581ec3a56ff142698f6d0dcc7896a7789751c0977c02a12db722725505975d6ed2cc4a652e39e8b3ca4ccc3d65e6055c2d32b418eedfaa17a72082680503ce888876e1bd73c27429c55a8a715b3d6ee39edfee99854c644da352ba94faebfafe3412236dbcb1e0afec478047f35ce50da6f5c6d794f36c86f7298554c1bc5f87db1b6c8ed0de14e5dda7c200bef34513ef77d794142c4242772a149f9f34bbab5e9980c32d4ec8d8e2f0f220a300ddc14ce50ad335c8cf526578a89087b74b921ecb84cc5c16c035dfb084fd3df44db0ed7542510f8b56ff9728408178067bc717a1577da961c6f658943241a69c8299bd98e087831b64ca77225d67b09ada4337be58913bc3512c537df72e1f5141c4943e2b4fcad2f61b0721f58ea6229b7d1df92be55668bfe39ce6b1b2636efdc340232932b68a9a98c72b803ff522360913dca623999d860171fb3ee45942b26a4b6a66364fa3571ea722789f16875279150c2dc30fe84de94e428c4d7dba6ba9c1ff7ab7b8a300f54172f283075b3f84a63200a7168d57d3a9458fe065385cfc0a0b3a9d6e7f7e83b728fc6da6cbf62f43e700e5865b7ef42009c9fe44df623414757d4852e5d1aa4727f7766b26175e3102905396d6b0c5e0f14b1535840eac3acf16f9c7be2c1c120e0933323ce6e9ccd0d6ffc23c070704f89e77a6ca753e97729cee153215aeb72f5672b7821539c38339b4a4fbef9f320cd96d934fdd1ac33838d5ac76da057373471db491729e4fd7b8976468f550b0b7791c058e4745f3d9da3d6f714e91f7250cfaf5294accf737cb3e46ed2a32d0aff4fb251ba316a30e2ce6ab8097f19721c95b5707bd2673d02b01841f3624a39432af3b8301f7756e75c62dfc2382a2c31915a3c09de4154cda2ab26e1419b7f9363a98fd6764a0836f9ad96dfa69272b2574ef41d19351c5725165b70d6d7dfcc2882993143f6a14f0e4606065a3234923a3e76443768be78c9957e1cf7ebc8d7681b5d7743b348432dab4a37c66472c8aeedf777c6b31ca6fde929c645ae0d3704691b3de494ca3e845913701a767254200f3fef88170a8511fb6d691a4d7073a2ee753d6afb3dcf3e1bcd5679ac01a8acd9a0c9bca87204bc72f398d6f7a0a3bf938dc33c413f33cd709988aeee369d7a2820c14f0c87777b8d52ca3485fa8e5c25c9ba09d7b43184fa8cae3114729d917bf003c84f3b1a5f146f1070d6f1460bfdf1e1eb084215d268a5ab5f027278ffec38e5e71a45850e553da2076d1a0f9f19483b490d3eda803daa60039472b1b78ade2eedf7088654ae732ba958dff0681b046a86652e00100bda1cd7b008b5ae6f4935b6797d870c50789297114e6b853760f63603113d3a71abb9f8477229715f49f7b5b471a8bf2ef27aeb52a752d411387907574631a831635cb29f7263d7c7f0a3c31df38335f8435e065bbc4ccb2821d513307638c022c8f26b406d3d742a081f5075300a1976fb1cfcc4ea8af1570ef39c0c7fb985f9ded2d7f2723446af8cfbc6a2c2a1252710532838f70b67a5b35443a7891b02b4159c69e272ad4f549278fa1ce5b93093ed973693e315fc64cbfbfff5ffe22f617733e5fe6f200e74ca0759bd1828cf2415f8e44ddeba302f9525ef427eedfd6a09f962a2577f28d672a4b4d421b2fc7de36a7f2dca77c5b2d9e29f71fbf022d82b6d0eac33ac30245d7ec218bc42200112e0b879499573d12d4e05af8a9959a32be0817f72ab3d5f986a19373b05ce19253eb5aeec473b9241b2a662a14b9c59f8af007a5b7313793a735734b0830ee116a327bb4be42169ad66be9f5d77924ced35be16721e5124ebefff5f94b2786d1ec7822e19e816450da53cb704b89fa73705c32155176137e65906d361a8ad049e3496cd9dcf95c89ab4ab80f1ba24711ac415bf724b733316316cc0009c8b8378db700e157007406871b40fed71641b8e4e3dc2729552706d9aa405ee4f27ef10c00f54ccfb4e3a06fc71f90244587efa6c72627281410c9bb4ae8869e57d139e052026df6f3f34a5528279fb59b9c31982fd737203ac742a61d463819dedbc56b6bc3d65c4571e86aa5d757c374a399f3a80305ccc112bb4d01f915770b9a56f9ce883cb3d48d085e1db6a74fb6fc230dd223e72615cff7c32c2c703adafd59cca271afa56438cb710d198ca58738a346a96f1722f32e363a7e41b2285424f717968116d6e881445209498b19db9b960c274d60b8d4e3aa64cec80023adf5524fbcbf35ab6486148daffedfeb66d055b0d44541556db89b7569a41acc6e0b805c15aa44ddea7e19eb0eebce59da9e86eb11ae472b4f592dd027ec451d4a72b4526a516813c59dd7aa8739681d4c791d725965072fd225616a9a0f3c0e1956880362dbf43d92b32e353417a3757ffafda2ee0a5729feb5ce3c5a3cb186b15585312c40cee7b6c1fa422e2bfb74088062705825472b1f0ab33e7f0c25efe0daf6498addce6f0f0ca671a31f2f996672ed1a63b7d72282f01e8e9899551942ab005d502d93f7db4127efe8d9c9ed5a9a8832796b47269881f4758c34222d2d8c8de0948a24c8ae8daf11d3f5f8a0e77efffa2cdde72e4e4e3c75c6034acddaaca447cb4f19c1da26b762cdc6489790a1360cc23ca588dbddf1458771ce520bd2792832b96bd8635679df8817e9df719766203ef637258eb0475b93a452c07b86ef77ac661b26bd4392de61a637f9782d5ecd39091720554bf0830421d981c2fc8b8ca43436139914cf24154c2e2f649aeaf8f0d0c721b4ddfc64dad60706ab102d501f5c35325c984754e0ea8253b3f99f52fbe7f7247f708efc16d23ebee9d06c55bad3d2679156f181c33974a92dcb4ab48cb5e72e51952e0a270c6e62987d6554851f4375f3bb912b00b76e60091dc9535a00a2bb2ccd1e0674a6326660f9302d9ba5628b50f5c6841f03086acb5d8fbc8a9b972c9bd049ef26851490add12d0fe9137d36bdc5cf366d09360113046c8c5b51267caba3abd1a3f05eed140815c06d07b3647467675ad6e599e4c6b8c855cb8373fd9c32a5a41888b2d1f42ca8c4fb7e1299c3dbc5ff12e23f00f90ffc5cf5ecb72ec363f509f17b0f23746fe8f1cf95e3049ca917d148c8e6e5d5f81c58cc6a472502cea01dc4a6a1f7436ce5c1549a28a9f1f20e3b182fdd4ec995646eda68b72ba1510f4991b62f53a16fb77cae0f1d32d88990131f4d6fed0ed51108b1e506d92730ca3b096c50fc09316605f456dce696904bf7f081686dffb601a249a681d45b3375595b3d7c6fc55c08e29f0f1274fb88529b19d1fb2b067db797b62717225bdfbdfd026f4c0603d58d67e25cf6dc3ae87416542c2f39460e9186a3318723282ce5a3007e75f88bfd4d621acd782f8e891f642e729ac07c69f2a90823672a3db8146e92e9f97ed40dc4029d45fe440d4c697a7078edaf1469788c6b1754f2494c5448dffb913d70170ca81f61ffc8a255f8fca3b85dc61c3a0db50ddbf7241b832c264aa0eb17cace0edd4c9da22516f6113152212a5af6d795db576a729d27d0c379654dfd8a75097c679f1ecbc261816464c2b172894f4978aae0646378977db467b0055779ac00c8368edfa42a019cf945f0b3d18e621d66920cf207220b9d5959f5c9d7303a6d07fed5fbb9402ec4c6128d6ca36019c88f5396af272fc482dc46276f8ecd05ab29556205146d1b8983e8bdd63b71d1bb6d1b12ead52dbc269d011da5c3b04fd8e8792b9eb8019be1d460c2d1ae27919917b311b8d722dc70c93a0bbeb83976387a1570da3c27f3465be54dd33526cdcc2f11ea76f727bb7ebf88dcd7e80868c0e0f2eeae2e78645b2258e780b103ba6de9e19864172e840ef400efa913752ae4e7bdd63dd4daa0fac3b6dc02756491b37ea4f8e181a81d4bb9838092a4308082f59a17b6925cf4871ccc992812d9f1139d9bb2e317218062d0cefbc36b5e92c60d5d774627953fc69fe5c3da57bd26cb0597c2f8516ff509ae3447fcbef1d0a0444cc4b82bdf954259cfd344728783fe7b45fe32772a6447d74cf4371b4a4e696000256599101b18298b52d3be62b8c163652a9c63c2989e62469dbbe64ff492088bdf7ad2ae18bc53003a91751f2fd35158da21d25674101e0e0d02689349d1e2cb724b5258bb4ce830db9f92969bbdf76db65ea1c1fc518825b3b11e8579a1d133034aa12e32979cc41ab8d8cbaf4c299edd535442ef142b282fe2617015207b44fd51ef7c8116e6cd99fd3fad53d55e7c3f2ff3df734db271170ec1eff15612db185a4ac2e8ec00acd0a28348608aa4af0b39f03c51ab2c6e501f7c291096ca230bdcf42b9feb3aed5a4bde52820a186f207166635018a36d7605f77532f6abad6ea0d2405c294b936273d7623a676a48983a26ac92ef8b589095dd82d0885d53e154e74e0abd00fd37c6dca8d5809a538803d46fdeedaa2b9b6905edf2b4dae7c3623692e0fadd090593657b8474b89a630a0720cc6c1099bf82786fba3693a3ce19f38d045e8af0bc60a2f3fc869ef7153b072999474e71db7b922b772cfbf6afb25c8c1183f37447db479807846ba844b706d54e913a788a4f2062e0b05579b7f56d932a542d4819d37427d95fb2ef078f4720c933988b13f90eeede0c7e8a729d13ddb5ad9bc5e1291617788ef10ba732e7206ad2706b6db2a72ca70daeaba5373eccfeea218cf510d42b18874dfc2f54f07fbf530d89088dc3b18f088609d384a111c3b44dddab5bf603f8a8f7309014d4644ab30886e8f9f5f0865c4cef9afa67af3605038fba11c9590897196c96e1372f01e349d78ea0bd35d7b6a57a0f7f954871869c37bc5926750aad87748b8aa61b8f96da7c83ec3b6768de75920eb858238c08aef41fa7e7910a2681dac51db16934d681aa43c1239426d352f0b6694fb7a370dedbf63b7ade105243d9535c85459c494eea794eb1c399e6b795c7a5e7cbe5783ea7325bb9a5e2e537739e55872fd3511071c79f08b7fdd3af793f2e8f6709132b98c92b00beb9788d291d9e67238e0ecacc603cca68b7c6272c6e93f8da0cc0a3bdf0e4fa4c58661ed22ea20720751b247ce875fb74cdea3e0db5ef363869faf67c5d1601bab85e8e8a4f7e872f3db425afc1f7f20ecb9a56858a9a1d34013b2e9a457037972a2aacba56f5b35a97baffe6e5c47bdfd3671ee0124503c10556a0a1076b06cce40a00b585312720ddf5a35d2fd04ef11716832971ae1d64e0747a5ea1bea9f6d0617e4f5dc9a391a4a7f57fd5817a782cb5ffd05aa99cce7e9a5ca08d34e371236e0743517d9721b6df50cfff6ef424c0ac734b66f3dfdf75931858f873c7b1b9beb7a056f58729a8116a87fef3ecbf4b7521d6ffc8aac78be3cdb54059f56f7d401d2d9decd7250fdb51b547f52023f30d2de904c9bd36cd2edb8c46b0edbed60ce9e5045e503bcd2d08eaa88ea950bb48543b1becfe980b298c162949e679cd66c07e7999b72e0d3cf943e4756c4f92c5bbe5d87b5e5c96e338f52c4a9105ddfcd90a3f12872c1d6b50fd632d0ddfaaf05c56c9ade85f8da9db171ffcc44a24c4320e9285c6fb81cfa92a0bfc0a12d21f30c588af8b9f2b22db5fec274f3b82a8ba1a2a6687289371809577d0a104fc3bf1fe132ae8bd078527014540a50ace6104675cb2e61e8d292effee08d2c97f144a4759adb9cae491a36960e82ce2604f312bde68672a2aacc599681d3833878f879fdfbfc4b70747701f738a387273ce9129fc2de1a9f2d8d91d44aea8853d4b58d1e34c54262c4fe84a73469b3c35603855d08d8427929d8d0c806b28f87a938bb9bb26442a7cac5f5ac5f2eda7c8880394658887214d998595ceaae720dd56312eed614ec206f408fe3ddea9ee1dc7376cc798655d657370b4fe8751901b096661fc31dd587317a6316252646817407e0a4cfe9259f0d4b4bfd5b0fd61532ae1f206082b9c1f789f6ed83a3b9ec3c724cd6579172234ad955a9590a589eb6c5754ef04b4952ce8aa70748271c71845ed6e453ff5d3eb3bbe52f8673bb721592e4c5f98c59981942815cdc8961df3d787430f1197238b38f023372e7dfaf15975fa8a4c4a15a7b0ee64653ba7a5dac3b40c3d3ad722ce9bbac990f142053b67c290d3eed55fa1386f3d31cdf6f51581ef942a18854ff8d5551879a4a5b8f21fbc5a322d21564ecdb6bf70b90035387084e659a462288d56b1617345756e07399f4fe31ac9e5604dcb3b3f70474536146ee959a5b65f806c402d43d5f52d4bc3e80a84e60a0d84c0debe4a409f5fe4453151a0c2b105f45e3ec6219b0ce860643a73337ebd8275e8d9fcf57b4607c6a483994ab323bcd0e9970d98919de28a5cde390311aca81298ddc5955217480df7e7c5430837263094467ca2c0cbcc7e9e7666239f0cf5564f705d2ea8eda509b776587a6f340f704fe439aceac7bbae948061411eab112306399574798eae8a19c157fa7275909fa0b8af6a00fa2944651e45ff6b56fbbad5e27e62e10e09e368650f65d99723a4d43ec746300bde94e0a5b01a20dd8cd78f61fa9eec27ac0c6b0015595ea31f8630fe26154a9fe615c59525385e18be3202fffc367bed594639c9bcd18c625d434e4ac04a8742bc94ea770e82dbc669d24e9263dcc1085fbeaa99afda90d722fc10275d0524198446c7f72506c4fe7f989ad351f975bc572c5582fe8f4f64713f83f656d745bbace2f25ed6529a5c848f39836df3531f1d62819f9d7698206e188012719dbe5131f503c1b8d7b9472658ebb680fb6d2fc2f1ea9d5331e3572397d7c00342cf20ed32d3c9c4e1b205dd31a5dab86d73a2a5967d3eaca71f972dd13249576072edf686614e44a27b7e5c900d3aab703799eef847a5eeac3ac721bba543b8a42c7f6db20fd1e5234748948c2c7cf203b464a5055772e946cbc4b83bdf5ad227e516b4fe8b2ea1017f11b43ded503523c407b521fc68e5626e772464853fc499d0f6765f8bc2bdd455b8fe373a53a8be89e428b0e0fdd0692fa721b19aed5cd7853c58c0df881c88c06afb4299388b1fb886a9c1b781576de5544bbe47bdb343ee7677f84977eb7e19786f483aa3aceb4f539517b6a17341ed47230a410ddfd797b63881ea3f5d8ef8b255f71dd3d667ee77d4a3ca8795063c572e29a928f3ec321a839c581fa66c58eaaec2102dab3df2265617e10881356ea722c04b1e0d663979b3d6d2094cfe7a284ee160c7da2057dfc5c43e0438f19c072a55ff1c758a6d6392ea58d45615f8c7adffcabdf305e8e63bccd5a35df7a192c73a24e3c14d9f93ec2ea21ccdd3e2f8f33303d36b79cdf69caa4ac9d2c71dc58972f9c7fa747d34337c95e491c571d86cdad94958a9a710612c3d5ac3ed577724887af29da95615910dd50f6dcd0fa24b838517b7e47e2ea1535b314d6c18f72eeed5ef85a48390c97d612024da8a82ab078cb939deb33c12d02df1143b5bc65ccc9ae9a6bb5b933aba8403886ccf6ce2c990261fb43047b0af7ce20cc6e8e478376e1b39ffff0ac6c2258e9b874f3054c0c0d71b2a2c8e926406354dc7c862305fc63b65368c5da1eeb18e18003e71e567f3a49fbb8cb85697ea9dd30c55452ab5804474041e2d44d3d2e6f77714ce863712230fe626400ae4328f1fa17a754edbcd2b4e38139833094bafdbbd580288de1b71c9b091ecf4fc40d487e85d872015f8f308c4e383d9ce57980c31363ca70e5cc90713c6cfb9693a6ab7ed501728c6d3dd666fd0eca8801c8fedaa7d93ccd2f6600e1e77c7e389b897f54cc07723fa7eecc9e21ca61b67d064972bb19d7fcd703a2247adb92c17567e297b3857205b4b3f8d467e2fb3a1e802cfb077a8555e1570aa6f1b4ba472f994d84e322241475f178f3914a1dac1704411e0ce1a454539915492826c3c0f42449c0db783c62a85e6c0fe766f10d2541f4886b65656d8a32e927783896577ba41e03464d5a45d3896de184d34ea39583a1b67be3038b38c42eaa3bf77d40f3b2127765167203956a06291638a04379a272c476f574a9ad0a29af518e165666381dc5030228180f86082a60c38aea4a506384db343533f2f015f134725cd6fb878b23f8f805f7e66b6a563b528da6f13cbc52032cd30cb2be4f77eab5ce5903f64349fa237295cd43fdb75cf7c6c087af1bf1009931363b7f6e44637753db61daf6fe2d8a4e895b33113a47e6c72d33a9091de58d57aeff3e2ef9a9eae81ea9d8b2c17a571f4b105797c0a352961415a9aa457761c8fa9097f3747e9259e398cc4a1e1bf272c3af8f9c67e4943b66ddd26c25b40a80f0c484bd1724947741ada96a86cc1f720fb84479e823fe0a1b4ef100330f4547b5a53a9d84449a9c61ec25ec2aa4ca7221972b123ee2999121378f1c92a5b3dbaf6aafd549f22e3e7f9ebaabcfb77f72d5221dd2a2798cb60bf7772e41b5310d5126a25030226c04f69e65894777097271d43e2dd18ac1c48db90f4e844f21ad17be66b97e8a395f38a100ba2faf353fb55ad60c6be344b0aba8c369abdb4d9a4cb7cedf830623a2799bcfda2435256f7f8f96c34bff28914947f9ee9a799b78241282f2af3ad1d273fe1e5c97d1e4722b567ed142f36713c3121079af0ff868c22fa37e177d005dd1b6ac56dcb9f372a2cbf2956d17f722ea9040854ab97123bc9291d1eeadcd7a32a72c3c7988cd729bc185210b26658a7e7169458dfd3f4f1d55f3158df8745693685f912a9c1c2ff4a89dc9b53c4f428fd16fbe8bcc42b88f6140c2862dfcbc252c50db98a5e872e6f9160b189e86f46dcd6c247c1d5153b5e91ac904877dec207577df281ba01c54f7cf45e4b224c8c0d2cbcde3e1fb7b23e89a53bc1ad4f7f225eb0251eee12cc01de151ab21bb03bf450ff01b8b3c22403a760d1f192a231a1198b610155668ab7317b4c44791013cb827dc5549e3735cd3e2bec7d2fe9a5efa4445a2dd6f2a5ac8c25f427e6426249ae7374125e74873861550954a838687a52ca447724e72beb4f7357f455fa2b521ac272308562489c7d2c0e3ba2662d468af0ad6a9137219ca330511b0540e3c3d01d9e2064934f5f253046a2cc7b1ce7f86c727fb5c5e8829cd06cc937c43c371672434e0f353e0bc688fe3a5c298ef6905660119797271da6cfe90bffd8d27703c39a85718c49be0311e789a202badc2291974e33b028d539ecd6c4347cb87b968d33365729fc29d96a2fa892ec3841382d817065c584873dd56bbef3bb2faa632c4b53127a60ef213ae4e5ae4b65ac3df163054685e63eec64d3062e87f9a5b801331896aa6b054a9f3f70c8ea014024469ad59c9723607044965d2e3f733f8daf779f134a0f874966e249696a869987d3be21c43722c8786c56eea1fdf36ac53ec5b276dc3c7f85e9af747acbb408d7375a302b343f24ef60cf71a830e8387d3246fa3a742883a1d9b9f6e48dffe618d3cc8216929b1c52a32e109333c07fda16d1169ca8de8f1673fe8dd1612cec71a4d0510f56326d2deebb18ead26712f975febd23a456905f09b917bf7841ad72ac0f36af30ff1fb1e0cbabe018a3346b46513900021230b7783b6e37e1edcc55e207820e843813b7f0bd51e513597995f03b45884886fe60fa8ec924e8144dd6a60c476ff0c0a09781783ab5e522283077cc58981d5315f3d3f538d521296a55d6e22a8cc72d437e1a9f5fd1bf054313bee524c2b46e884fd9db77fdc004a68092344b42531dce6d2cb8e36d760c8942ebea4f902cda1079bec48fa65b3afb019a5f047755d3a30a808a4af173eeeae8c4b4c05848ddc0373c605c8777ea86c4db4e2c6617259bfa0e1e436291f0388af1c5df51b585dcb9ada63b779905ec6854cc37b7c7285fcffef459caaeae81b562f9ce8948279d41fcd79541aa7ae7dbf51810c8d720e3b1dd11920b269b0caf356f9a020e07e8cc5f46beff0d5be5b3b582a537772a4a4098514ae23eff9e13291cd73047db9e3406ebe04d77ad3f3b2d158253741ee48308de14b6d4936b6f0b24b0e7c36b470cc2faccea7d48053a35334634872412e06720f76d86324bdc0f7ef1246477d61ce1449fa821f39a86e3a006f5a72ed4320b0f1ece5d0444feacbd06de55d23ee9636d5774ab2bacf295752b7943884b531599720e3f2ef99919a44b13bb38c277771db75582fafe6691af2126872e24cb98e0e3bb2632a9c126e7d84ef0ffb530cd09c6eeb4192eaea9a777eee476ee6f2bccdc99e19dd4b180f28a98684e455a0c81b1113ce560eb83b02bb3030794c326acae0ebe15587adcafa4d6ed27478530a7ebf03d05947c5309e9e266c1943c303780955050902a98331066ca1676deaf436d8ae0ab6cdcf8b4882a9725c8b34b37dbc062cfc580007eed59793f011af08709395a5a9bf41a8bac66819961a6790d1076ac0c7a76ff95bb3f914b341db0ca0b52f824c12cde1ee3f16727be47bb9b90fae7e5463f3aef9a329a4529637facb7b037c6e31275c57de2072b19d37d5d353356b2377254ba3e238be56b0806db2fa1e01c3dbbcd2f336007216a61e0754e4dca0d41a6f69988679c2c159cc8d10231f35d3e137b5dcbd8a72a1b0989c4293a00736890e9a0b748be8868b40c1e69964ace50a72c12adca0729842fc5db7cbe14dd495b5fe9b9171752f47abdf55fd02fbc3c56e5a1f87057202ace04e7c3c49b3a77acb6ea64cdfd1b146b0e33967b6106ed1a25d11720772462a86ba6e5124ead81f3f9f3ba4c10af4fad40b62799deaa484bb7141c5216b8bbdc966d1c5aca25cdcfdd2d26e7734bea1d249cfb56ae9c882366dd133af7211df738961de4cd69dbd1a8f459d6abcebf41d23a4226eea4715074fdb555772b15fb24a8d1446b0832a43327a26045d4e95805aa38d745b0ea519c46656510f1b2279c016cc8faf08e84bed8dd0f6c1623e53f9fd284391e84de8e14bd1bf3e0f490547fa461ce340a6f4103b1f6a920d5d45b0cf77c1dcd6eeccb5f1152a61072550c1beb692e2bc405787f711f1a49a84f4122aaf86ab85d9579b3ce55d720c3c7d99fcca531d9be1732d4bbe401146cd295bd5d3e25cf125db1429806c72ade140c2fd63a88a887d59463b50723cafff3d75e0058d5780e0d290275a9e70188be3223794dcf4556d039ff2247802a4b5bf950b55b7122d3459351239322a69e83b43a2ce244b47d96214c897fb0c22e30000fba795df50c1c601a2d4667234ed49ee61684a2448f47ee708ee25a621f70b10346bdee41403c3c817b55b086ec4ab1da91c80eb6197705c7eb1c28ec07f82a42eda9093331bc413fef69572002dcb458eb394ed874d561ea9311394dca7d1450d069057b32f21edf80bef6251a9123a43fbc76fcab7142ec36b5006a5531a1658a822f54f03142b65acb73e8ee1b06ab472628f1dc635621c3f15951b78bad01192b210e76d0282beb3356e840cee20a4dc5c5fe7cafb24db1cd39ce5b4ee1ce03266172811f1371203315a0c414ef8c1587bf165ef26be0eb64f7b74500fe9a386e0ca561cdacadba94d5a20f223d3db01d47b9e591d1eb72d541991904225e4404d35507f9858fe76c4727ea525e67a741c8d0642b0fb7fda04092b8e1d8ad07d542278878172198c6d03dc46d2729f3a3b5e9adf25e82e32ac29ea2a11b96980b1624aa7162718ac677295097f8861cbf6f0555b6c9220462d023d4e6fc4f3b11703177f904abc5f7f72f9ea37934181640e6c9ff6ee8d325589998a66fb0d2038b5c863c8cf1d990a10d4c7962f8a50315e4cadd30ef69480c0f83e29a220321a52171d81780a5e1c49edb9132eff5854d71188db63357e24c7cceb1d6a032ba0c2772835cd1e201953f6883d16d7c4f73a9626fbdbfd9b4dcb858d8f284c9aa9394541ff5ecab9bd72f4e39d209904ae9e657a3db17b7c834849413c98ff97aa9168e52daef39dbd609d4e875bcd8f85ab528ac6232285e33b8966e34dfa8e4da363042db893f6a5725baa98258d9888b400a72b9ae73b4e10fce47c990bcdba65879802d5d43fc7628612b7c487cbd19569ee738974555ac3ef1dccca4edca5615b98524ec4913d726cd2b42cd0794eacf185dd876fd3cdd71bb4e77e1462e2ab310f696174ba6f72632d0461651c20fc04d2b4f35d53e16867820ed458c15f566cc8a03e9971fa5ed7b05ad3af8f69d0f3f896e555e8a3a376d9672fff68e2555d93c535106d8f723f4d752044b36d689fb8f99709e979b5ddab53cdb9646100d717e19c90c21372f9f53c4dcd7adf63998057274a27119e018c2288a748c3ee8af682e6c9886118abeb16d230a42e0a01384c7dfaccfde4d04e367dd7e49d3aa7fc82621d8cfb7209150fe58aa70da4068ec2545c53d98d0aeb9838d92f018585f6404766d0ea0e4bdd24ee25da469acf1218abd04b496f2d5e8eaf02a9201daaa3509a2b0ff372fc358a1af4079c4cf04c6172ac2d9f9ce8d264516cb8e5efa8a8d18481ebf301aebc7de81aa52dd3af11e68387055f34e0058a4fa4c2eb35f7e4416c03d42a0ac9df2df685842fb9e78b6be939f2be8469d28e128bc7a29ac9913064163b5f72003229f3b6643bc5ae036c4432739a6eac726dd2e3060a7722b53ee7c4345f5593228c1ce1cce2282083cb8dd94647b8338eafcc64e0e2bb6591ffedf16beb2e9c4ea8de8ab9e9491f879d0631c93328fed216f8cf24bf2961d5962acba4e972ee31320e4cd2dccf120465c365be2f249177f0b5c7b89c235461453ffa7d577276f9b050f4fec9252cd1fe0f738ca5ce76208e4499b7aa216f56b76ae587667245c18e9a71cde1bb4152cbd22a9fb5aea480feeb185f8b4b8f2f7a78cae6837237772c51a90ca44fa60ebda79c96b15231612c03c4d4ebfde4ac95bde1b1d772ba6c05d00f8a08021abfb20514c9d76a8c804f5fd677f72282c734461a4179061e0a3b7f0949ecd88413639719ae9b630d135646b7576da101d6a965e5f043589427dd2e507fd96685d0fb36cd5d2c5bab438d34f618f76aa30e8407edcfe12d4f3ee4dd9495ca6a832d1a6e01eb7f2d4683f42ea4aee82d0d2d74cc4dfbd426dce258e6b0a82424b2a36265bb90824e23a6b07d32e672f12a33096a8d3e4e7243197f5bd2a36366ba3ab40f1587610f4416065b3532539e0edf35f73d51ce4c69909ecf159790076f19189ff4223c4b304bc1223a94dda4ada3635441f0ee727acf09b12485d3888bd77da82979fbd96adbb2f77908e213cbfd488cea89ee728e7ee59300111a62f8580294283b522189ad090c067bbb0ffecf81cae4351809b65af8a2f402313fe649a06b0aaf0d8f867802b3cf80266809c8ead5a4fb0d140b66bd1ee7386d682984e94433c81fa244a3829a35ba47893fd36267cbc9dc72f2e4521b0a26ae07d20334a9da8fb98239782d08362e006616fbc7c8638a9572d81b78d9b4560f9cf92cdb1a547be5a3d8971d2b87980d595f0e75eb092b472eb65b8ff0b2cdb594dfe47c82105c643aac0cc84e4b4fc6a567b6eabbbbc4a24aeb673437a29d44e0adf9f8b838abb81620138310bff24241ecb04f988c384e33408a937b9ec9e342a3b8ce9f2489f76383194165a77edf97fad0844b371828726b3c56cee9ae870d63c7585ee2e1787e88ab403af24e29052a21f34fe952a53ee8e17a0d55904637f1a848dd0f709caaafbf9345138cd8daf059d950d1f37472575fe0b4b30b01c8bc87162ce0bbaf72781cde8e085bd2835ff09dd667e0ef723faee1e0bf636ae20d77ce42a193ac9d7f9b331624aa5d42fc767bb338d6c672b705e2300fee92b3cdc1f31ad9f5158a8d8083e53cee1693618234ecaf3c4a61647deea91d18740173090c0e35745436e90f2c61d531685fadf86251cc252a723707bf037dabba29dc5d604b1e704c878ca6970ed80f1141fe35def5b02cbf483954f5e638bc988ac1cb2a6efdb80cc57535695e4488a7bd3f73f815f485b82d58109910226b388528e9a9804e1fd3325a629563760d52c85675c8f1c9426e11027ed677ff65ee643f90ca7538560813aff34b26d9036c3c299258214c0fd92dc470b9116f021f2c68aafe68d9c27f47af364ba84dddecad77562cbf2b502b646be8ff3555a63c427021fbf5a65f22c2cf5d5ba2ff37067738c84a96761c8b44ae29cb4beb8ce0b7c1e575e336db69eea71897d5666778a6fc8bee4490e00868425713252e3e25d19d8fdbaf29e8627c9ad51aa0113f190ab06b1279637023270f5549da774a596857ba97995e9132ce813c118eecb8c6ff4e3e2afe34eb5c727b99218c830a7ad8b09a4d28ba61ec61aab6826f66bf07dfd611f856cc165d720ca0d52dcb56bcd7b4bc048a01c52553d17aeff55f050a5533e7b09bb88a3e7213d60bbf2c29a09b5a556c16e407eb07f33101b29e44ec68b3abe6e1cbcf18271744568284de404074341573b5f7a4c3e40e619ea37d6986c24fdfc47c73422601d33ed4266651318da55149e1fe860ff12df900d0e09d7f9882636353713928ab2c3a634b5fcefee2c3948862f47e9df87c98ae1d0c80dd9e390954f79d9a197605697f96ed78c53c7aff0107b1c231bb0e3b736f08a95c25561e09743041727a636d586eb45c8db89dd71f83879448fcd946aa9eb14146f65b06d7ed2afd72baf24d77650e22ccfadd695af6b7bca93ced601c3728d9e12e8d75115fa45b7260765e469e8993885ff0201e001c1dfae463068c81724b87ec9bc741fbbef467f45b1ba43521ec122d0f7c2effb92af4677ee07b1ace8a8a174eaad7b9f8ec728a55f2671360845834ddca7ed8a020493f17e4f17255e0fd70c9d645ef0cc04006e308a760f046bba10745b3b2491845bbdb18b6147b0879d5e914820291601e7594c14e40ba33a20dc5dc29a13622b113591316f659d60d1b80c04c567dd37272e86043457d5a5bb438ed52ebe975ca1479da77ebcf35fa3feb1994be8c107204b4f00b62eec32ecae0d59835e968b6b4ec009f558d74dd937e0308b13b1672330749f3889354db8da18d6350ba921aefb4e5c75293f00fafdfd455f0d20b72be610a26eecd1566642bdec37b2d9f8e7c7fdd4740f7350295c3c207f569c008470a1813a321e8509c3a097600a7c28fc5adbc2b1cd2b3df4a8fed5733c18034b882619185242544e162cc7edea0ba448e95681e83878a78a8dff9a755e2ff726bc3439304f11f9dc424c1b8f00f7e1231d3e0ee9ac3ed2a5e7866712c4b4d4adfc90f7c41142603c6c006c4e76c382be8c0b06ded0f0293f0b9b346b244011778884dc4fbc3a42b08bd7c8dab7e310955696a74a01ef71360c71f2593a87772960dc79af2f465e82a393798a44eb9c9f5dfc2ea112f348ca8958d4a7258fe728d78c19da6957fc75ddaa1accd43d995dee69c3cba9c8d03c5916dd85c0d577251714ffe285a64c7681deab74fb71dc473992592e7d9680bc6522a600168c772e69b0244dfee9e8875d02f35616e69bbe00016d60c8d69fb272adaca17a8ad72191e3dbdb71b3f75c7c506f0d0a8be0e970505260ac258ef18d0f88137274d2656e3693c25d02db2d6dae6324db52373261bbf18fa5fbe917d9d9ef74e46910ba475de44654ddd27f300c9cba5e5688725b243e999cb9cfebe81d449dd1e8a1fcbd2a7ad2c605b1187d005e4cede57e0eb72d1e9065ce648340c19dfc53a184aef53cedda31a1f651e4875ef63957da549bfd369732f9f3a492520152c7a876e0f8dafa7fc450d92377196e87791658ddbaef3ba774410c25eab6b224778e072ffe3797b7ab41df75e87d1867d405f4e7bea031aea096cf62202c50bbb2a945df98022c44452f629d508f43624642a5439f153780718812ec9b5b0d10287ad72c74a63e9ca5a944ac6a532fea427f22e1c872384a52b80735329d92fcef23d72ec1d9c67d7fd45a79041987d7f5544937259c350b6d915b6a87031381089637224402125a5e9ed2af35528b056d46338cf65868ef3611ccd4f3efd896c8926162e1d3aba9bf1539fd834817e6b4d996738eea98ced293ccbdb4211f009911d1d06b28464a3f75c56908316c20f7af092f34b9fe452bfbb07205e4a058a7e254b5c946d58f5595c92f0672c424e4ba30c3b50fc21550a7de71536bdbb93143b72a192000ba8e99d61fd0a42d53066d9850617a51fb6bef3633273cd9203c4ae71a458a6beb5abf25628bd026433890ad1b28eaeb914b943edb66023faf6e5316823e3df5daefbe7bed434de50e0915b9f4e441c364fe8c4d549c3afbfc6548972ded29428776f0bbbeb112db3a01a6cce2ce1b8b0b688d9c29d39a2ab0c3c8a2638ff4065d8ffd9852600221d43090774470095a85dd4a1377769efb113eece02a08791d27e537446efd30b52e9d561d2edbf15297bbcf0e4ba2380cc0aa1da722be0e9f752b5587d9a2eeb846bdb2de6474debbbce2f51fa9a7e5bdb47375f7272e390eeb00145a3719145a04256f3e308edce3e6478be0e7a426d3983b8d1724e221e355a8000a621fc2d7cba83f59068d0dd887862c00b5d18eb8ac8a35772f11afd3498e912c813419a55a8589f412d7fba095335d4e7456b4a8885b88b72f6da9ddc721e26981736454e0724e8f089ea6ea13cd6aa90da4439d2f2b7936c8b90f0ab1db33fbdf4f835d02d3f5627dfaebc78b51e848cd40bde89a11710721c6a4413e4ed830993630aff7b567a96dc2c0260c32e1b5a47eaa23b1533b37224a4b1982da9463ca4d7790ecd852b3854d8595ce2c6660994212567089d5972c8e718a3cdc67405ab629d22aebc44e40fbaea0835e809f924604e7ca9cc2b7254818e9ea5bdf09e5a92960e45779ed360ed5fa031fdeec56f88f75cb56561664bda0ae5ca3e5a74c80ad8239ccc5e71d27f551eb10bb9ddd838052f8d32cf57209bac4b6836dc5aac4cbba87c88f78f6a24260f77a21a348e3c452d85ffe9725804c1bc835e4768a5460cefc9bde565f7faa819a8ad48c962ea79a885b55a724a216a2ed04da3c805cb607d933bf36349c9ebb25d6a949d41f81c31a7af28722aaa5dfaa3aa5cfdfaa0c378b3b8dbd2d7bcbe603b2f122e17897a5e4d5a24726ab6cb2c6e77509a26e2f88182c3cb1deff29bf34278684904d452fb05439372e0fb4b52bbde510729119bb5319c754acdf24bd5b28811999d9cb1884fc1f6725b0c578893354d2acb8261a22f9d5bfb23dd3dd2f2a31096d10fca71b307a03d665a57f865d21b87edf6f7dadd2b98918514e31d21ccb0f649ddb4765a581a0dc88f68ea104a01481970c6c2d8290bcbcde1d59c8642575db0aa882e1fee737206e2e28cfe605aa898463929666cfbc1ee2e04a7c5b10fdd37ed83d3c801e27282a066517c07bf2eb04a820e905d9bf861d6b2e181175ed58f6615d2735a2072fe3bba85ec8819bf13ba2f8c640383194db7a6c51f959c978418b69c1bec5b3e55df7979e9eb0431a4eae2ef80d794d9ce879ed189bcec91336afca24946bd722e6bd5928ae7d0fc4bec31ab697003628c6923cbbd7bec571861a25da51379611640101493c518692f00d5495d91e2c07437463b577c052b747a77349732d872695d93088c8d8ea5ac981b0b5df0c1309f0bbfbb3a9a095465550b7e764c2464c4d894d3ae8fe075d3c7dc32fb444caf360ca837025fcefe4c0b0c0bc68ce5350a3737220dd8ed0bd30b6bad3056dff9da84456d42c7d6132e71addec5312b72362423b318c6f3197fbab6a4760ff48368bff2688f36687b1d79a0b355e36372c454ec27def1e5f545d5cac50768c8a0e56eeb90ed319332b3971ffd2b63e072273d954ce03d8f0a036e21a6c3b4417e1fb5fcd4af782f369538f8e7e2946f72bd05c76bdff04dcb0fc614091a0fb237829fa93ed71cfc646f25507dbf207f6f7b6fa538aeb773a7acbe1a77dccf2ebe0c9d2f118012a1631fa154d0e1c4c672955bbd97e1e406319de551d718c8a6a32e7f14f95dc1b17c67eb9a2e01aef45dc0707e966b12969b55294a2cbfb5666fba91c3926817927a61c4ee769c439672cecb3cf4bf5eecc5badf31c29bc40344d4ac261588213e0767a1a6053c8838724c8a8ecef767d9330a57530acb8c9fd1e6f5b3cbaa90089afb45520c2224262988c34275a56783e1cbc42079b07abf414e59219368643f1c2bd6ad97463b1839b1febc578459ba55a01f9140682fa55e0aaf5f28a47e6832ccab11869d5f463e42508fef9230d0cdc2f2e4c8d00a05b7b4157fe076eaee1faa4049fdd92da572e2442632d7c6619891a81ad1689ee354e4b8e8d17f22d6b274b950d466d73f2415e31ded3512e315d03eaf49b7a05b135fcea92ee93cffb24f78034ef715de3869089eb7b2e24663f1b6dca30a8bca955d90b5c159d4327896d02723618d05729e98d4d7b704064a0a1bdedaf9ef107353cbd5b2632116f834c4dd0d466076720fe42c0b05d605ca7a507a7bcfd282c59b48ae45f95d3b447b2e863a23735872bf175469b1b3e485302c8cb62f5f2d93c2841eb8c402e4f3961d2c06dc111b7214ca695ae1ee8ff24a2f26dd58b6ee775567c454dbea7943dd27ebb4571a376381ad554fe275a7c3ce8848eb9b85c333650d124af4428309f0fb158bd9f77772563892eeb6516f25d9d1e655dc735597a26f2df5fc696a52b804e09b5d904b724ac5e5d6ef37c6eebc53a85473219d41418a4811df8a433601715a9bf5f65d721b486f673152e5d9ef2017496532bc16a3e5fefead1628bf5bb1279b36972a54d1f21b8b12554238303115d44fb36d27311e43f30e6bc39d8c0cb3fa10ee5b729620843c448899a08989a5a80882a85a7e69f934efb2b97e26922a4e51e72f729c68e2aea686748829e4eb2a277cc946a161b48c2a4da0b1d4c50065222afc27ac0047cd1192fe7f60620d100a67f125387207d8c4351623161fcc8f4afc45725a6f2038fef1c5ce89fe4ca59a193e20bdc95aa8e873b0f294758eeab302156ba294c8a8ffef4c5034dfcd814ff706a95be8e1cd1a2b7d0f7522bb7ba639f972002683384c3b16d2b919f8cf66d487c34e9c8525f9105eaf18c07f0513d5505dda521f6ab4f6bb8da03a4857950e43033195c8c82c04f9a7ca3846745ca6de729bf124f952653cc6416aef1477a221388943bd8270f9e0359f32af907dea3c721106d593069ed074f5fa01256c85da90831c487aac1f945afb624e2690961568803e24185da328dcec75434249e4529010014bdeb85e0aaef3470d5ec409f672601a8934f9524030e15c3cc5983b62ef215be3d54029e1a2e9f520b542879756a3fd7a07ec1c16fd5607d9d123eb15252a59e435ff3275774679ffcc2e7172729d13a581f37a5993855aee87781a235a0807b193df297690d7820c9c7f445a720bb7e20fd1a13ece1a778a74ff04bac7a847633fa76eaf0cedf3e994fd33c52250e69c5c7d503b93bbb3ebe83f2635d028d1447ca237a64bbedc121f0f79917211c5809d16d3efac9f5cac94086ff7164f1579ccf9d8780b63598d738956ab726c58b3b3e101da29db85d3be91a09da6ca6b6a2395d2dbd07e093edc84813572b66cc8a0c3f2e132e2a4ee64a7f19f3d804d6c56be3ff7b6280b84e5a20b335499757c08e2912bfc82e8f605d5f31d309faa7d5b71dc323ca030ba8ed82af246aaf89aab4ba47b74837171b71da2097c2dca43b8193b3a61a1d7d063b0f19c7273e205afd84cfd90ba092af7963ef78ecd3c06baaf18233dcdd519392b88135fa4a6290888060936050628a25fd658eeceb4f5d9c2c522842a28faa9cbf3ac72f0e4541201f4221faf16bfd7ccd222fb017cde3216b63fb521e79a378d91e155a22a7750eafacad6f14f546501628d401d8f6d5823cac9e7a7f0affbab231e726eb94faec17d25030d304a1da9e43d0056c6bb34f451c1df5546c93536a1d472fadbb9630342732fae1b27cb2fa41472a4886cf7e685b5e7a47845f3a6d7b43babdb0f7baea989f169ef1c32b4b32b658ccac227358a7e6853aa6dc848f81372777059edec4a7954e9a13e3af7419e5c0b8abbaad3233fca255d88b49a0efd72a192554b1eaa3c06c258762ce11610f23a59d5ed48e19988dfca757e4879265980cb1dc5b00490aa0a5b23a76781cbce4b30734b5e1ffdf90569266ae5c12872b9c2fc50938cce71e89d7fa028a15034181d47fe6f6ee2e313c3d1881846e31b277e77562de13fe7733a28cf70ca2d08221141d7b7732a1e661358de73359f720bb307276b168c33501375934ccc5140d5e83ceaf570b556ef844ae6cc417b724e6ffee0035f3487800107346c069d119511ab1a03c1f8db2ee736fabed4c9727f88927d45f56f7be5088240fe8a0ecb21fa03f313f8de0db5e2349f146df2729b8963a780783113ea13f632cebae9f6b5f3efe83bb5b198075fa1d7bab9205c563673272dc5c156f5aa45a16dd7692f20070505b929599566133f953353ef72fda78463789e522be43fb087da7cfc9d714b07c3f89ec963ad83bedcb276e852bb355dc797a5601a95bd257fcb2ef8ee46a1343423d3a476cc7446074de6f0723f48008be04ceb7dba12787cdb84edb706a204b170b7856250c4b81b3f7aef728103593bf4b37b5a73d8991029699fa7c6f0c00880880a61cc8ab4f614f5d369a1c76fb38013c3dfe10e51d348055d3d602220a7e8ad350431ec2a5e6fbbb6728636d4872f635646308851233085d2d6ec294e3a1971855d1a13ba3cc14fab72d3e90e677699528242b2c44807456d7a5a0f273eb4a506cb4fef3c2a64b3567225cd041ace1289d6b1dee2e4bc1edba69aec2f87e04e2aa5f7131f991c2a3472f9af6bf1f17fe41260d8ff33c83c100d15f4921640fb0b067ce81db62465635d906aaee619d70e1dcbabb918358125269508b605d79080c2f42b50e56c211d7274d2a866e9b5e1e5b8ac8218cb623139f6d5007fc581f5592062143982f2647230c4a00503c50e5dccac79396dadfac019628def6aa52e64bb4b39c86e04a94107bfe6401ca111143dd8fb548e1dfdd177482f1dd7ac5253e45c2a917966e272deb84c36a6c6cb194229cf0e2e3dc3edd6d268160fcb4c927a3702ae2b8274709b8b95ef7af90f7ebe1608b39eaaaf1bce0ec57205cba5202c33c89ddae44b46e2197b3fcf35383e0f70628d72fef441a41356818e1068c9feab8ce1825d3a724d6f7981cfb707a3b4fcf9fa11f134efc14b26bd7c141859cda192061bdcc9726bdad6a030b46ac844017d6344f0174c56ca8590d77c04e070770c0abb82d672e690d24e10cc6281df48fc297a5f569e9bf75e583f90ab9257824e140fcc4b7258afb1f107681b7a1a824654894d6e07da18b00ee93ef545a3b8bbf777fed254e87887ab4c514cb6a67a3b0b31609b68ead81912d59e4b4847b0990a523fd1729544b226f4c3efa7ada70d0edd4b4f1432b0a2ce97666c9d2b3ba32d9355f96dd0e8a596150923cc670d729df35e3bff9dd01823997fe745f2ead07c85ab137228d723a4c830d34e9cd4de2dc3deea81807c64a542e456a3f4b12f51802604723a3f30d70f2e5f547dc2263f2df925a5a6699e1aff143d2a7b29d1bd59f712466f45798a457a4e07d58892ab2b53916e8f951fe746f21dff8015fbf1aec29b7262944e2fa59227b0f6669b2e902daf4e237e1e04ff211521becc9af5a6077d72c912add0fb9a54c8d62eaa8ab699781629d5253a1b92efe8240ba664c0e4cc72da8603b1d2eb02b6156dd209867cfd985edb0a35a3bc94891a8233ab5eaa2c3089912581748f79bc9ee8e07ac509114429ecf6b6911771861299cb3c1d91e472cc232cc7a416b0f33e7b4e988a3501eaab41d3cca886b13686e11190c9b5787223bb5e7f36987570c8ab7f2e8f712151408f8fcfdaa941bcf245ca19a6aa8272e1bdfebb3db3f846c7cbeea74135f3d73d9b598386e81f801441f563c2be7e72d3d44c05a18f63fc7f4e5857880d6c14483d02a5c5dbcfa1f35a9eba80e7ef4b3925bdec91bc5527405833bfb6b336b448cd637c2fb89c68e2594058aac3e57296800afe1ed29daec75dadacedd7e67660d0aef9ab6c561623039d0e33a69272e482f68a2263727fafcdc034fb8fdfa8866ba613aaa9f1455d0446d1382aa07299bd4947737f0d1af82d85f7aa32bdb997b67975df852061ba309a34b67ed972241110843eb7b172718449be3ed20b30f50c30c4c059638022e602a21f28633e0eac19e1b693d47c0db3f6bac09aefcb2e24dbb1acaec21652c7f1f21ca5c1722cbdf76d1329a774e28da9e266f60dbfc52b0d2d28bd85947f334a275a433672ca6faef105171e110de0dbde0fa9c3f4fb367af223d77fb16f1f8274e860367224e4011737a176efd6db9554fbdd8dadcd6c877cab05357b9dd6c8ba23d65c6f83baa24c129e9bc56964ce0bd59aac848b65f708d5ca5bc47a645a39aa59892e06fa2fa9f147d7d24cbc6b0970c71222a36481c94549b010ed0133a7c1e91e72428b0c761c3ad5b6137543bb8f13fe31a6f3dbc3f7d9c90597ac406deaa10572bc39640e72466cfc0cca849f29ba0e971587898b5c4e7d91e5855da1ccedc572cff1d4723565d1b9cce424424cdb7b021ce8c6dcb931c6d3cbc3e4871207971adb6c93ba9015ae6601e1e3040703a5f380f57091d55b8378884ceb863515292ad15aa619673056e61970f97241393e39b1b8cde36af47642dbe0513b76bbb019c7299daf0d6fe1b14bc9e1fa6fbd693c867ec0f89eb1f7648f5e9a9a032ce31e51b7d832e4a79aabeca092865c26fb18a85bafac724519a330203f86c8586e3f575e269827cee14319c185d206f7cb982bcfc0c30ca3791992fd5d6f708e8464d3da134cee83b9d144f42efa77ed982bafd67b789f76621e4b7544cfe4388872fa0337ed44c9b41859227e3c8f69cdcc7935ac2998ff8e6708de6270d1f73a720081cdd60edfe6ff20e800272b9659d6980c75a739c454273bd8e6be836b3b72e51a64d2b84a3bd39d4bccb08abe37f7f8856630de6a0d07027db8b35ddecf21ce899d84f9b695e436c4c3753e3552a25e7f977d9ff8d04bd397fc510adae37241e3aa37f816a4607e41b4e38c0a83d7b2da56245222012b4c39f6935f394d33da40e46fa54887de76029fd994a4f1d2f65d5a4a2dccfd293b8f1e521559b17287aff20c39af4f2d079a50263ca0c3929045ec7d907d99290921a19d02f0282fc25a1dcbe2d33608c1196a536ad0913908e6b38ae3af4b5907b0422be5e55d72a0cb0af440ca6429f72e89bc0ea2750b85c9b9701ec4e43136d73d18db88265c10e9b22a4498ee947636c0a98c36cd1f78b9cf76a3bb62e2fdcf1d636ee0b664fc55ef808b0d29be1e696cd531f139f7a30c2fdfe9a5edead7063df1747cbb052b8120f55bd89e692dfafa039232d2be80a2d472278436c7025c1d9998aa1a72420e75e32a719a3409a3474e63012bccff4e116fad533d91580fc1f62e799c2c46282f53bbef46080c36762f6ead8ea9df9403afff560adb7daf897f382fca72107ab3a2dcb83929483c6bc88158fc6edd9fb1987d0967149e0f28dd3985c072acce972702f30a631b2968a322b8d3bebed14bd60ec1c505e23899790788aa7214b500c659424f87574978e72bba08f8e2bf7a1777af86c251e6852c3001d37251817325fd87c8281064faa517029d363f8b1c3caeeedbc6c56b63c9318b143723ec5c9503af2f022f1a8fd729f3e9f54339341576b67a1a81f10b67c63caa2d1b2ae2f6340421a71b618c60e6eee73ee044fce64c335714e2c9b24391ed81688adc4cdb2ee2b3b5738d62168458fcc93a0936eba5bd0d1ecc7dcc94d93e09156b3a39ab8ede977fcfa229e9ec7f66f80286520914601cd19f62e37ab53edf28edb4e5eb8dcb04e51f7f8bed34ea86b0b4f691db385d892ed8a95ca39c1de77225ae437b26b7c14653f53ec17dd006d669c9485bd053fafffe620552ab207072b9873fbb82e36a2ed00bb3e58edb7d0c78144dda96575c1ca99b891af8cf6a1e239fd5459cb5f19bfaa3feed60cd2ae3bdbbc55b1ed01f62591ad31c28f99d72acf1c313c8a119189d07513e8e88e2dd05430c1978eea5bbcd65722a8aa2027254d61a89db0ff37566f7a17ef08914ea6bb664467b166a56456c54204aef4b72b379477375404e92eb6b7fa112f9d610fa9ae55d83edcf89d805fe05295d0200d8b6faf225ffab5cf5447c9a24baf3327f6347b4f778bc28f8b2a7f76341915fb73e68ac5cfccec0ce10fde844e0483f37a3cfe815506c33fe44cfaec1b40d72398e9b501d51b0ae7cdc0eca5074d87795a030a7537bb20e64abfb1836751972ef5642908c3fce5f7209359a37c093edec1bbc1a8c942b54c8f796c70bf9d97290ac54239fb39e24f9ce2f0d12d9f6fa87fdcd5f1bf1652bc11c0cdbab30695538d71fd784f90e60f9a90ef7cd02a7a76203822771ca4a087d6bc81897e70272d96d3dd8006ecf36ce3fe255c39b60f76d13316fa7dbb33a3e99339594358c72f3e145a14d7f05a56e14230d77618335b3246f89f58c3e90d39dbedcfe492672ee12148a09eac9ac3aaf670e2c67645712724550ade77db9a2094ba8d0aeb072a28a58a85409e2f6b9c78357e32d9b33347915ce3094cc50709af92ee7288872fe92fbccb571f46263cfaa6ed7e37e34f78464669b16f7ca73f9963132f4382a8ac45d02c17f734b4dedaf6a535fd4b49801a5a4a7e580d06ad3cac7d62b0540a4db72f22c73b4b58a4a6a102a4fc9784d99b1231b51d9c1be880b994142fe7251228cc20102b8a54cc9e1ae33e8d21d6555cd5b5ab960c7346eb33ae768c0729427666a3644c5028112b0f2d0fcfe4185fcf873dfde3aa3a9b64cb36d27df72b027ab744eb148fa24cfbd6bc43b8b08feaf289214603f85e06a0f3aeae92372519c78274eb393c694e222942c482182c19e257fb0ba2940b87041d64d0eec4e04dc33246eb6ae3a62afa8fbb22f2ea4e734616cb5bbcfd3b817a172cb401c72a8c3d93e1a15efd8a6b6903626b4b3eae2ddafc6425cc1d14bd41e30eecd7672086a7f8bf95dc9d4c970eb6b1f20ff64027435cc19f3a0764e958c4c44e6cd4fa67026fb937bf0b2ca239772ec5235ecc6891e093a116736588073ab8b12c3727067f26fa63d9c03dc862c0aeef76bdf2aa196d05da75b67b4fbab7d4855f972bf9c833b1d08accba4eb3c0863804c1b38c423af551bf5f2977204215ea660567a45fb05a7f86298ea052de76b64e21fe04faa0015a905c7155154731b2fb472008671e14eae47c21b16e5b40f91f1704bff8088f62b5bca22b030bb1fab61727b8a9a78566d818812717ff123be9c4dd693ffa1a6f056e26ab271af9a8ebe72dacb8e095cdfe2a5b1fc14eb01935c72ab7a7d0eef22667e4344b11b46aa3d721f4e6f59ac99b03799b5c1a9e164d24d226f03428d99c43c4e4d277946a4c515d160a022ed66bcd79284d5bb06fb65f2e2b2df11af7ebb77e82e726fc640534250fb37bca1b0de6401c8919ed45364dfd0a712fafdae65866ff737d6c06ae969421c0086a41a459c45fd67406e046fe63effce3d6b6059088d18e7a626ec3972dcdb55bd916cbfccf487328e6abb724cd369ae409addb98b28a1786bc77e971708c7986cc5fafc7090972d070bdfb4be3f160febe267f7c847681c9cfa070e7268a9e8914a888158f3599e430daee704dfbe6ae00a3b1450b92da8b418e09515fae7502d364bdf343890628686d0bcdd17ce577ddba3c91984f1e7bd98d4ef72966471280d9f77d6658627030782684e27f86fb16b0cce4611a4567e43095b41673e0d05bdbbf33bb35a1e742defe1932feeda1042fa4bd6086d2bb447673e3d2134fa328ee50608d5439f657ff045c93d30474a61f3d422a684a71d4a1ef0727b24213a5f181d75dce2b103208f7cb6a7dbf38b7daecec534d195b484f5e61847cabdb9c6a2e3127d8d1e01d00b42e10f42242d8fedb3e80aad3d652948694267468b0d9e8561862cc335e778885d651716de2d9436c83e66d319d08bfc500192169a47f82a01457260fb18758eacaccdb5ba75c35441cbd5990bddc3ca92563c7c461c2efc379a79455420b482a9b6aa355f250114ef23ace8b2a3afbfa57210157dd86f32ed7210f2753d7ec0c7100991da27343f1b86c32426330fdb9c4131ef3c1915cec9329f0cc5cb9132d02dc2b241cb5c46d974cc4452f5a2a6c212e1df9703ecc3438726e67b692b863ccf6b9fabebb10dbac41305dddf00434572f4af6d5fc0706f73849f16aabffc8c3d2a94036e9ed88c8da0f367bda2131f72652ddb3e71fc9ad7eaafeda0f9c73736facc190a1fc9b40ea9e33e30389dc3723212708d7463d0c441fe7c111d370264bf73b7ae7049791141ea22e38948727239d7d6cd0456bfcc21324ae3380f1756418c740cf45b0ad3f5a38f3cba144d722e35bc9a29d8b00c5dc9d45e70f88d3e3a37209c2b67fd8b5b93d0f7ca143b49d33b1e23645947220105d33e8dd089e9e5e16a657b73ef842b6970b3da023c2cb505ee8155aa0caefe03ad8f36106e1bf657c415198b74939a8e4892cada2a7226abde496befd2754d66e4e4213c4b2d9e54bd85064b828ba7df26c585d7391ff18e3af51601dadb3d0072036d26ca11276f3eeb0eff04cb8ba5dbc418321972d6520a80dfa7c856699010f65f970a9258ffb0b00a4973ff693fde2300a72170f3c7693a90b8294b445b92c91ee87a5e9b5718996c1dd8adbc30337aaa2a8241e8c159b4e5b8f4beb2338b3bda1576905f8fe7b09e81ae8e7c0b2f90bfa1cd72324aec32dbfbb01292acfa760f47cbf7d80089841fd7268b46164ef1cb7729725df409959732d911f4d3a7c4efd7d285067c57d30c6cbc98f4ddd26d70d1fd72a6feadb80159f28eeabaed725029029f4388a1a3e0f75a5be253d524c45724723e25d3fb65286c9f6d43f6adb6ffe2666ef1687d9b9c0f1df36bcb25f2859072af1c68ac2c867f3f3ec2d36b26fe5290d6890ae8b2340ec73c019445adca5c72ae1d918c726603c04a22f9199a5919d64fa867579592bfd4b77035122a9ce1724d164eb8575a8feca668e8816a021e91789767fc84ac33d340d25ff0fb81ae13d5487ae158d4eb2852f006424f1e85329efe8823cedd0c55b4b427ba603fd7405b46458c0c2a28e49cd3205a84816a73c1ae106fe9c94ff0a669020e4a8405263cd838ea3da8d1f508151977bec7feb939e2fa84aedd40fe45e622cdc204935490d5b2900232957a943763621d10e5bf850702018a27a3714f1efff917063472927e885f0011bd90859cbcb153af4e3a842965eca67f35455d5c37c9cc49f072235e273020156ccfce1e94646d1b420fc360c621a37796707a0822bcb2c85d2c6b1de240464214a33c41253b25a6c604c2e33e48dc9c0cf3002a4c853d83e47284dd4ca76a28d1b9ac06faaf8351495867b64f29e56413b418f422c0f3d370722b3142b78be1cb73ff3f2680240aab480de992ab480699cd547935d158872e30db83e536cfcd4f084b79080eeb50e2d15470a77e06c1e2d78d27ade665aca9722ce7785c20b77e797413fbe2137178bcf13f8c9f59d8a903461ddb16f7793472a74e0993c1ea1eb2c4b4df2069cc2f1107750f18c5c4a5a1d65cd315acd56872049a05fbb592b04bcf052864f4a896a0137262b9e5f41612298d25f1221ccb7290524478ebd5ad8b71071dc80666b13c7965f0119a3c8436bbc11ff78b45da406a7195a794d87404b3ae55efd95265fcdf3857c57a801091ed38b4cebd04f23fa501494f18f549a367d465abec69d9ce4ba45a8d39bad591f5f065fc4a626a727ffbca0feaff8ae72a88b30f212fa688e199ceecfdbf6dd7cde0077d0f955e72bacf2a7db9fb95b7ba7323e12eee634c87dd093b2add95eefb0dee4e9762847202c0195cee0f5318e8e83b7424b720abc7da89db79c6c47a847ed86768184c72703b462b41dcc5193856d426ba0231a407e98a6174352a9a3ae044d2f65e082a5d01775fd0b834b4ffab54adfea2deb5b0120ce16821902b277a3372d50b3f7226d122d31a9c353aedbd347456323e9bb332802b9f3651324db85533d8bbf87282709508a11d10c5011a7c2d3f15f67ab6472ff7eb252843761d174aa21e42729e5560daddfd4f7aa1be8e93ab2c0078a8783c35c75b7c8ef75f81e768e11172b6a48bc0b0e6ea9e6ce5b74a7316d14d04792b7328b8a10987ac8b05b5127e7242980a7489269dbdf866521738b4129e03a554621e997dac108e7d7165c8e834d5f0109485bc8d1495ade3f1239d3f24e757201c9acd693f37f6767305299b723a5157eae97c7571e6a43b94bb201d07cc5bda5bb2acaa4f208a0a93f9cd4d6d8199b100b02fdded8b36540550fb8dae3ee1d743dcac43494a6d0abf0ff98572c7f7445f4e1beb048730a5372c4ee917fc7106e504b02aee44e0a55a5550c072d66607a5faeffb0450129f2136cf1d256a1a5387127756954ec7f305a4ee45722477b7bb90e9e8c8689034b15f742e347ddf2c5db1b3520983c3b106195010463d53245db79e849eaee70c1289aa7105b5c9c223cf51a055197fa88df6aaf072ea03f575394e64a2dea01086d5604f66f1428f6eb2c610c92fccfce7af585e72d2f2cf3e32ff94987b5e916e01cb04f7c1067d992b61fecae11f6e733a293072d1bdf47490befdc33de9d21e4d895c0b905766a7e75c17c682cadf52f91244722690a2e335ebba4bb65d070935dbea6b36115fe014bed4f919af43f9adc67c66680e0f1d36ca81d4d3f4be5bfd4fc58f935a46222d82e268abc6cf3d41f15172858dbabab4b4330303065d3d598dea52243a9a7920ff3c6c3ce246a70997d31095823e745bc5ab9d8b9066588567f988b17c4f39052b9626d93cb999916bb137e47e24f4e2c0b1fa131c5de30f33af7025a467a4002068bb500f3ecc137a7e727e8a60001d9489f9ce180c4d9b3cdc0b9f0421401baedde920fd97c9a4a6e72c406f165c2167ed79a91d6dff3b895637c4c055428d81ac55f62e5d32e3d24769d238be8d6cc44497024477381aced561a5c4847c178aeb7ff2ab283ef0ec1b3b1ee727cbe06d860bc519a989e1b6f4eb2e7f2cf3837be161999a31268838a2727e96e7eef608e36592950159535e04857f3acb5a7d7d6685ffaab2d26dd11972b39e5ace82d0ca7c62e902e09a395bb2ec9a153b45343e7976661f2fadcc3c7236e8b0305cd75806840176b46b58fa57cac129fb9b2f46dcc8f47411bd64e872e4877157fe509a87a1058fc5942ab4d95a3d2359bd087819a18e810e1b4a2718a933fb613d6ffcdd8ee2658354a89fbe7eb5002e1f7499cd6c75e59edf9a1972248522ca7e4da05195af522991dcbab94bec51035295e0cc26880fa1ad138872c3687cf343c8134f027ceb6d07cccee2ba8d75cc529d3de165e220af1eee5557cead641f59d4840e905114a333651449441cbe4b137b5d8d36b4441bc914a248d2b3b5948144c02b6919580c2b72836cd716b1c59afd0f898132184376933e6965f4d7362940f16f7b176d4146165c565e07049112644099a29ceb05e296337234829174784756c93fc1ad73ff36a7421ea038afaf88cf786c5673d15a2cb763c87e3b73ee9efea78daa7c00da124a2339602e4a239ddb03b04fe0afaa89ac5f6237f6f11b9d7be55ae8c91c2e03206797012e81483f09f3d2b986b45f5afb721d7360b174973f9c032833a94698edb476f7eae65974ac50144361a21409ea72592705e25a575c0d1505a64bc7d295843dcd54960bfefaf0613e30ec749e707297d38b5cc905eb8aced3bd3b0067f09b9e4482cfd4ec782b780e60a509f0e74aad4a6005ea11d114bdda3e9ee996197f12ce014d0cf473a415e4f0afbeb127726118ebff2caf0423a02e80ea2a261c2a73af2eae99ffa9f4ecc6710ba89b4956318bf4d9beda744d5015dbebaabba1ff1d0cbcfd9ed1836324cbdbc7352b4429335fa2a62029d87b414c80394a75ad19a9144e857e9ed553b4fd126411df3d14c4c9d28343f46c49d1278d31df6c09f88c9b56a287bb1a8fbb7d96abbb675a727c911069fd3707cd5cbf55283cde2851d52edf8a04868566f7388133f5950672be7b985dfc3c0981c7769067152b9cbc15f23f74f8da2df9105cbd6a0132cd0bc919dc0dd8488915e52f76e69b119b47099e9077b70f5ec51b2ff4fb3bf99272c78f76c3bde861541a04a482593b400c0bc861a40b457cdacb20e4f9ab11af728b5983f3d7bd6fffa0bf218f8354cd960c5c02b48fe580d8b36b88c8c071f9148d073e3fa4b0a7eff9898744a31ebc74d29855c92db892d113d6e533e5034e72b6a19e7b6d0bca707fe1ebeef216aa7147330c3527ece4eb015f965b61b93b7254de1f1aa1a5a687a1170e59ba6680358e221e16e81013a4cbfe2140d04b92021d795052f3531efe2f00048b8ebfca69976ff853700b4cd5f7cd6e23c698b372494b436b6e8703da569be311916cfe4d51502dc2c949e50ee9cd31de5fd7e372cd9369e26a57b392e3f1237781e551f574df15c351c0a0af42286877a58d397265ff51e1cc2b4cda99e2014de5cb4dbffa21c9d549a8ca12b494ceea3cb088569ea5c243d3d1221f9a2feee89341cddeedc85f48cc1186121c2dffbf335d6d72145111e5128c0f0f0f9d13240414ff4ccaffa72d3270258d49f2866543b31b725b1d29c24c7a5dd2d47d10c5ef15d78e9d4092973bf6c8505cafc2ad1cf1a73ba14efba0f1c3eee5f6102384c3e3548f38dd3350e067c47a32a4f7df59551672240e18f02acee1a3c4bb954d88b929f26863ee84e52c5e6825198d51f15efd724b4268a30ae59e198c6906de7bb2045813046d3f7ddd2673fe02f3f38023a2726483ee17b0a9121d4938177bdf1fef4bba888c19c316c010e3723a63d02b8d7282fcd5290ae4e104fcf6646866a81dcd51e86ec79bbb5416f3b224cc5eb58d5fae40ce684a7328bc078551b2b2007ade81b199ee5573a854577e2c1a8b18e872a5c214917bbd585ca55f8608e8f2bfbd7c7adcf3345d53ce0f708870e968fd72b74cce148a3a96007190bb8a089304e3d576b582a1049de7f3289472ad52b45f9492b52acdc2bcb3c235a15b1eadf76865372491861a8b7e6127e3c122515572a11804de6cb35cfee2caab2b0b76ecf5208cfdde6946e0e48b74fa8e5a5f15253c17a23b9024507f20f6d628cba51c5db51dcc1a5f1b012ce72c1f0b3f900272ce5fc88f8a5475796a84faf4d92b0ad8b16d46b6985773c6306b722ab571a972cb336a01aba8a68d9ea27c21a3ac9fafbefccb6784633bee18aac48640ffbd41ba90e4905c9aa078257fdca793e3be812097151127e0ed613fa50cad4f9b2155da4d6d554b806655a157ffe9b18757623a1d8ce86da76605099263fa7c13e672ea74c2e4aac2715403bd4d74590380f98d751d19e176da963826b7a5501026722648097dbdefee90ba0a9649d2062e336a83a44b12dbb0820f944d2646c2a76d3fc915cab6e5fc5bf57ee997313ec9d814cd1a179f795d49d1eaf0070cc3270caf9f5f32d0c76108696051ace3977a02074712fd2032794d4044f494f8e9d25e96ef9c983204bf985188acae6d0fa22ab638fac5e46cee100a6eec65cd47520ad21732c9bf4552462fda28346dd438504678ebffd150711fde15ba5713391c727f2e4c85edc6e795ff5299474ab35d33f6dc40479cd41c678ce179abc9247c3b09932ae482a48840a0abb0a1b94fce6b6d5454a1cdd2b3fe02dcb656ac8eab5473cb315dbdca8bebfa42ae5129c6ebf500a37bceee1f1fd7711c80ca66676750cc264911a4f656e8f480ae31beafb4482dee8dc59190c154b9cd8944c2291429d8b7e226e94df91da25b8c747463515e140313b98d11a9539eab52738d08111a3f181278b67f370adabc586209a770d22dbaa852146334996a9c6765f5356172c47277133ff77a641105288d1233b835f13d428d487ee23404139a30bd41224571ba576d84c79b7bbf810969dd3bc6a0fb2d1be4b339d3e74e54a76f948bbe50c264caafc5f16a47dc16452b4a3c95c6c6e72debeac353c71bdc36167277c048646ef5eb312db9b22dfcf58d8b843abe77477199451c37b1328b4efa5f6fee56a1d1dfa0e9e655258c6ad7bfb09906f4bf6b9cb5c86e125115e90370a57ce072b485657ed587606f0f003ec6bb77f118167ded341a641b611561f40a34cd7172afdbdb52ee919da38038190612d52b79be27004fbd4234af362c2e6372113f234f8181fc0e6db093822eedf13bf24fe5b9e4d4a43ac6a5ec8f03ea21482acb720ee0284071f98b647fe6f1c25582f047c7b4dad59051aa7c7cef5e2f7ecbbd691b44a2a8cb36fcab1df9c9047a28d277841f5c64b427776468c0028877710472de6d2520e0531bd2394b5a121911740a3c3283893b619e7dd41c191dd0d8ed727ad6a4246717817fe6333456be6c0ff0852c623b43f2cdfba6d3fdffba91381c48b41797250743dbb93feb8bd18bdd0b016c34bb495356da2fc13610bf7f5a7270c3fff3adb373f963119b3b01deb08f52bf0c6f66d818f52f6ff2bac27bb026e84f38a44173dd486f41ef124384c65de25e0c15223b43ee14d4b4cabba9647239d5848623e6a6e81b21099282813cc5c057e73f1d989873e982daf7454e67727044c2877258248a81b8abd41019d0092c793c11e3f9eb36ddcc5ad91c522f6d1365f5ac914d1e6cc31dc7d18d26fd0f8113ceaf1c8e75f1525f3ffae5c2612fc3d0b6ab106c6a87e10260ae59f79688ed3971db359166f0eda51596d4dfc7591421dca3e7fe4524bd51da204f0abf5a3a396fe41cb6400057ec1ac8a1a02a56205ee73acb926f4e0b0864d36dcb8b859104c6e52551b2957c1367aaccbe491f9eb186a5e42bf8adec3ff04eee477e43f1f7bb9ed6c48053c26fb4958d8581725b6ceeb3e2e4cb5644c10c28549efabb0455e8b207c5547a9a4c213b061fd9727a2684fd0bef937d8064df5c616b0d29600f3279ffb21e262f864824c84ef906a2dbdb027913fa739e250a9071243330f69aa2d20e71899d59fd7f8240b5da72cb002015cfc76e6bd326e8399f185c6392c717e26864df57f5f0b590df172672b8384b7f6c2f1695e10d1cff0aaac02ec77f7bf0f30e807e6074b9192e2ffe724c48dad36dcb342222ad4140bb4e6640b69513b017711efa12285f4ae955a13e99c6055b0eb444c7db62fa8a56097018af08faa1581b9dc212437b894c56b80860ca967ea4bdc30a58b47ee50687967a2a3c4f1b012d896fc376a306247ba6722d01bca9c29095b6c7d68d9e0b3cb378142b0938960a7eb28fd79ec1b29a2872fc2b236816c69afec4ff22b8b8d2d969f0b82b0af2d599ce0e92a73baeece572828fb257e58ac2649b051e6e539de3676f5745a7c87a28a307dec243179c956507d274ab91aab1833118e2b419358329c2fa7bcf766a8448fef15269b1175072b08253877e8d3be58f79913997d07e5d88eed7455ea312706d0259ec43fb5372d15698def71eec8f8b2114b967a0c35321a204dee5cf0dfdac8c8fa202298e3ad37bfdb1c24e8d0282c282e4738a3a37e1a85221d64f1b9c8c57fd0067357e72ce017929d6c0c8d16ce6dcd3ef8fa10822a2330a3256a816f970ae4bce46ae72e5231d70369dc6632f717563327a36e1a98c58351b74e633c9e10f32790d1772e3ff58e1414ee45b5f6a87a59544e867f64dbf0491e8a36c071d6aa25922d472617157e1a3871d7c95b2e8d10884009f9e707b18daeee2f3bca6f36f26455d72ef44bb7699c00f8797bd0536cc924dac6eef888c76ad672a71f33df6b3cc1f72188c5c6741d58629432b85a8dc0896bfca1d2122332c4355ba07279a208164728d8bc195144fa48c5f93716acbb4e89a1a28deb0f99fa229520f1626082e641d1e4a12c0f6a8f5c2df3059e666381ce05ba711fbdf6edba1f25e7d5cd6b05372530957868c77949e818b452c8e5d77df2c7cf82f90f23128bd473f2dc7e2d072ff0409e2550b3a6905823e1bcc6fa90cec9da45f29064392a84d9b86dbf9c94caec2a693e1ff15e3b7f688ce470f5c53b02b460059b5192d4c99ee1feabd3a4129930de2aa97859919f76542e14e1f69c1a851d3422ea5efec7181de61511c7201d957b01ec286f5b1746e04fcff19d4996d054af49b4d6d37288beb7aed19567594c181f1d94b8ab40e32c0920e4a520d522ca1311adba319c0632f12ec7218524e9d8db536376b28cd309aa4a3377ceab391330040ac6d0462746c38358f6cf06a050e63c8952974d4cef608c956c90c57595b03ed8218b46c25041574fc5f40f0a6dc3073206f603aaf882cd313cd09a25af45fde90c5e5b6990ef17472722942cba53b0af041e7e545056771ace1219b04a6d49b0ef5d996645cd65eb87222dd70a70810eb25c0b8fe3d1a346e2785ce2a1c38f1847d42ad3c9e0d8e8672210998ad1b5867fc19620c91a860bc2046a73a766c0ad5ca2586c2959d0d66378a561dc9f5e7ad37035e40157302da04cd56a0f7373edfe590a34317c2b5ff6ac088e702cd95aec266da459d1c188f4716859eb7ea003b5433a71fea11831e722e83e351a1791ba97cc2ebf165300a9632aafb355389c9c011d01b81f7c16a72476497f2b094f481f5f6f393d61b99d60d3cd6d0cf4b24df4d7157779ba5ba72468955f6e76d15d429d176dc8be431f371bfb9944f9311dde55a3d2578fefb72a4a2234203b1557d77a9191016f442a1aa79776e26ab252d414f0561846ea4343b3ecf85cb71d0b414ebe9cb9d6d24501d60536d944fa61483a1bae92e9d3272074951b44936dfe83102963b0b4a1c20fe3ac3034e9cc7e2c2baba29480f106451ecd483e548f1a86b3f57a431cbd0b86104ced0757a4e88d667b441a76516724d198ba7b7cd7603f7f32a0e2cfbee55ae07835711dd575410ade8a8d305167287b173d594eb3e68c25a4d4e063904a3a1d24e2a2d89586f96a33379234ab772c3e4991c041420001728e4602a273148a9162eb56bd36dc582aae6eadd272572a8ad7d8f9d4b5e603c5044e8c97f5172c08daf7e4e0c95b38a8118f10e227462d1061adc0355044c2142fafcff14bd632b9fa9726e711f3b3a52e2eae225a236cd2cd276644c3d1387239e49e04bb065d07d51db8331223cf7a5af8d867c124bc2c1635dc3705ceb1fafa3cb1466a0d34408fd60d2b69f8ebc3b825d6bdf6a727d0cf868477584364c14690a00c2e2793a81d373451461a40548570156b2992d41ee6c8f6d5e2c383e7d4ecb54bee8de72a3cfc2fcac85be6b00470b884162722731f096376fbe1199d86e7cf1f351566bd73d78ffb072586cebff6c31c8c372a51ac607627d1351f22ca989322d3e61190506495fdcc958f69764efd2a1f74eaea5b869a4fe0b44f6c67c2ab5d8dceaeee015edb1018811efbe8a735f73154783520ea3759a6acb6671111c956e75c15c94f187306eeb09a8c344fd17b2bb72b8dc1575b45e7022cc444699e6c8d36c02017c627862166ca4b546476eb9c872e86bbda79b3fa1b9e2921fa8451f2b22b7a600f89e0507b3010f8e19720aa772672b44dc78dcc5c775107b831e9988336da3d5221489f45432590b21d5119c5c27a7b0517169db747ad4080cf144e4016eb17029ca7861d9ee3aae593fcb2e7264979c33529b66475f43c0e650572f53f05e5cff1ed04ce18f23a6ba2d01b472bfd37ee926ac2d3784ad377b68c33be90a21c4d71018fd76bb792f30e1799472d43cebf97bd822e4756f4ce4e5ea36e86bff8371779fe9dd7c8e056a83917172b29fe2c6490b5993160dc7d3e4318e9383e51c791d67311f031f0c730556035aed371e69448693912f13464138f1c4ec1502b201bcd2170b7da53a13dea0196bff1c869d818fd5495bfc6403a43d2e7599cea7a85d6f2523ada860906be11a72913d6495dc47b3c671edcff0c46d5cdbc777ffcc97c1978ba5e9f4fdf3366c4960366ffcc4cc23713245bac3af09168c0abc75a7e04a61e793591d974fcf1025671d9b61a9d60b70f8758f8f9724281e53ad9b01986d3f4aa768ef87d871f854e92d88ad6eba9434bc79019d1ed4520adf9b670753031f0fa9eddcc36ecb7c72e49e41485bd61c11333a1d33013250f3caf76da8b2b67d46c747d26a0df75a53ac53d58f1a91b1e77c568caec53b88ac3bf39660ac89de6ce4fff7d393b6aa72424242abcc40b5308753ffdc6511ee51abe7d1e53b5e99cd79ba3767b35f8172a3698587ee35bcfe71131c62b31c373aae4a7a5904ef415bc2fec19ae8ad507262570aad69e989073b331c6b1309df213754ba1872de6a62d3cb2262a8459c7280b174f95567a8b06c59369c97bb172b7144878acc0095884fcf00c07e811249ea87bcc41db3e74474227691e90ec1e86501e4adeafb43f51e5713eaf1947072430cadb0f6bad0fa29e852c098d042acadc70bb241474236ef41f41311602172d31ee329741f233277bbdc49ac424040258b8e82dcfdc7ec8daa799c8aae847232bdf4c54ead1cab53810ebd5cc7ea2a2e7dff847de5a16b63f5b4f6b74ad20baa0cdae25dcc13c0a573a01bb3fc24d18b5e6c40df680ced1dd097434bc2d6165fe288b0e9ca2a59befd9109d482847c99be3f98c1468e2d4f7b0741ab2d9f5cf084407654cc975d552d9a83bf8f31bdaea1c36d913886595b61207ba15b204b210d633ec46a1a6d91a47bb4c62dc69808c1b49dd8e26aa172d0e8d520e08472cb99ff7ac6111f4bb7a1fb7a33b5a3fb635897276e49b2f0f57a95a917f7b272de7f2969025abce969af4ae9f4752f1bc8653b0af158c18d95b3279f158f51724b3a7660c9c84cb8dfe41fd1d62c7d0f5f63c5bd2d5f0f30a6987698f95a6d721f65db54f2b42a4fdafbf39f35743b7ee845f02e9d4c302fba05788d0fe97f72b74e9d66d58b5a3858f09bcaf321b2a2dc3d5c4cbae0f335a9e941b2c7b98372677a45974f65795e4c0e03208857c75f081f563f5074a5bd1c5ee1200167b8725c9984cd1ad442c32d9ff4ffb4fee47dda2394a7a764c0188be9b2b2e4800d658e491667461a31b6c7979a8db7e87aec8552a40b6f876a52933ba43b75a1c94a10b0fa23767f04a8ae24d89a56c6c32f2e502dc025206d58674f9ed940506172c8e78a9385d78f6aa4eb192adcf497c9b879019d40edc2baef5eb01baf112172d2a28c3bbf992e40d4f09ef32e47d71b5426bdb267312b141e450222461e59726614a8436cc5bc7c085d689d5d23b371732a4875898628c9acf339b44d87127248e307725d3465bcb7616551e7baef2f43b06afbca32df787c80782fbc768b15ea6b9858ac2e868c5e4ce832fb6067b5f1dd87480ac5cefad0ad4beb66e7cb14d577e9bc4dcd1819517caa682af575392fd08e2050116cfa7ad95a162b38255cae39f142c03e2ae3bdec840ac4abbc8cc922babc3eee1a8cb4878aa111a6457295bd56f3e57701d33f164416da0ee897530f207c2fc5e8911275bf158448b506e4d57db2885b8b4474d95775fe3fe32fadbe29d5b1473247441ab2c2d5f3270c04e1b1af265d4ea583cd9137050ab08c6d7cfec5aa13f514dd80171e2a48bd720436037073a793f1d6e61678532296d76ae3b2b5b30fc13c01a1f730ee6ca517d75c1dff3c2e48cae5b88c3254afb71d091e2b2680791d5e6dfa3270713290723333f5e373d1452b44a37635298905214caa9cf809834fc6e69a1197345ed2725320442e779a083a3010ace7b6a5038adf15c01d5f1578ea317982c3aa5e960bd82ea490628c45a6c483f1d2d957a96bc6936f95a981569ee55769fc4b37a3720b1c5e024c3657f31ccf107cf5e7b5ef458a0978e469c7033582dee104c47b724c8ec264c5e694088df7854d3843d3a0162d98fea3c76acf7668b9bf4eb98f1920150a8553af1b4523834a2be5b7b51bacb800b3edbdfb585fd0adc575abf872833c58e266c59f4a7ab54f862de913986f65c846af4f3f7c99b53c22dae8e1255700bb43b933a82025a1d53ed07a992286c39c429d37dd000f7eef1c86d5e772218979bf16399d4a2ac50bfe234545d07d0be2390e79b9b9e8d315f330da4c72481dbcfc9f5f46542a45a5e281ed6707096c939ce5bf4539740a7df039926872d7867416793d10ab1ea161a3c819798f02c4e4a497f348d81a3484625f434672c00a72597215c50039ab361026b97b88afa9925487576b047bcf7216c2935872be9a8bd33b0b646efe1865985b9dc12bfc0db0c43b63a2a5f0df7b37f31d56726b4e125a1fb84887195eb4591a3b88d3822072293996102514f315b6d1ded825f7f06f9421978d5981faf4f5f4d52488d21ffcff6b14b04870642bbf50eccf5fd824a648027251f94bc6b20d396f5092c44f22c41bf081c9b8b253a7e39a7972db6aba7bfe5faa14311a003ded27e6a501c99b7f137c364562e4d5b4e4090d7290a94f4afb3559997e4676c214f47c42cec8f1ed539817b076934cc050037c7294034435aa47678fff9f156675a845ef3bb50e5cf63946d6b0e53c09cace007214dfcf0ef0625e1af43926752b7720279749748f36e8409abc729bd1a1b9ae06daec9ea564e4ed301a77ed7c33911ec044d82e9c4296cc173c0f01298506c972ecc790eb0a63faba74adb1f77fa64068ebab8e3c9840be74274374af0250f3728dcd4f350b571306278c7fbecc95c6a98d7a16faee8598801d101bf0ce90471c113e12223604e4cd0d324d796ea5c52a87184da4aaa490c5fe08a00dbfcff302f0c4a94ff94f98ee3b0da425b136fbee8040cbc0670a6f421504b2205cff6d4797124d33603770a2d1d9e79cfe90ff43df98cb43e0ad0858e1b39c2bd1713d721b2222f71a91e26c0c722aba90a22ed5eff33601874d7be60cc6bdf5b98e045b274496d955af3db9196011fe0bc73d7ad077a43368d92e1f20e7e23a4f60eb5cc7f42fb126b659e141ece5c2bc3c984f2777c8ccf7ac55cfe85bed19d15c0458212b30417a74bfe19949bcc3ff2bf70bd0cfb2b92df88b627c086d814ece4a72c47b041bbc0740a255d984e729cb6db1551cbefed7b31e2fd5a673401bc24e53e59433bcddb4fd669f056d437d1c3a2644c75d3fc7bfe1511c43cf9b5c6b5d454fc7f6ec3852dc4c2f38b742f94598c9267b8d52434f34d88d6639b6f8584c5c4f3cc00a01fccb31efe09134bb2ecf20ae6d35a5c09bb076ea78c1802434bb72ce7a7ee0aff4d58b4494d9273614fe054af5cbb3edcdce10c3df402209fd0f42364a2acef9ab633ce4befc6fce152443e36c92f853db5583f9a273de97685a7251b94b7c3a6476a1a063e801d34df7f43823150cd2c433803b8c104d88690a72159a2cd07cbc935f0ac6c3d3213bfe91981808c2c92240bff40221587b1cf472f85e32ce6d093117672e4fc41dfad6e79e413d3f0780478dfc766665d93e9c72f76447f6bc323fffbaa7983ecc07a6e614c0a1b180ee41dfcb5ec1fbce2ce40bae968300561a1f13fb5ecd51b9ab30f7de45ed561e78f2a20f1e1543e72f25723de87b3d39480c5590ebf7fa21f494915766726748333512ca532dd89a15fb09f62d624e95eae3720656a79eb814ac96bbb79c3909251205d427542b0d3f9b1ae39d711c1243412bd4948dbe264cfdbbfe0fc03b725582fed462b386ceac8d729be4ebb8f92c7d4f94279f8f46e2fc0a1c76712bfb9ec4eda7891b6bec250d7204dc32fa43012219740faa71987b0e391346fc6377cd1ce5de8e843deb4a0d5d600eb164c24fd6639a1665145ba3a31d531c05ac1eeeacdebfb9c7c58a3afc2b736e54ba5204d7f8d946db3ad89bcc8b0e4c00f8037cc4b977c00f94b2b60c723618f3f1c33c121b28520fe5fab7fbceea38e7a8c3432958c8a311aa8901506752e7a1bad17c577ae8591eb26c33bea778aec65167334bfd0891906646baf2725f03b83c3e1f19f47f96b7b9c459fa70687434e199d213968086d4d9d414900927138b1a2af36e3873157cce1cbcea1f0afc8e07691f71dad53f660d7cee8172167348a11b6b71a9bb281702526cf82fde7041bbe108c934c25cd237c5d9d311a6c54a24f4a659e0dcf0e299581d23bcb727274d4be19df0dcc536b097d92521565b6b40db6bc877e86de879af614eee0dcbfe4509a95366c8b0f3264467cd72ebd813ca1d9401e6c7cc52a46810babe4b7d4a0eaa5af503633bf73331a1e57200ed85a4339afbd44bd91cac01f22e63b873e36313375b72b2897912127a1b5c33ea866ae8d61ef0653e47673f15db268882e0ce3ec6e6ce7d0da6e1a9b91725e21582d6a092d83c3e841b78a0bf639fc83d3aa081835c96519e9deb6cd11b726c3cc29a12f7fb7265e30fbe3e963d5c53a048a385f0bd702b5ef51f1f59f47260c2fc9871f66848e6c2a8221134fdc8fb39bfed94a11ba4645feab0da53a572df3a5c3b95f7a7ec30a4303474c21dcf11249b6eb0682fb492592d16eb561a726547dfea86695cf53a7e55c582a11a2dd74350f2ef34e88f39e55d4321f088317f56cf5c87eeac3de4980602e69dd3bb55cd71d5f574a681bc099d6b1c939272537710956fee24aa4b33bb98e12eefec0ada917fbe5322dd085ec2d3b9a77972c0e3c7598f346ca4e9d173440ae1b36c01f212cf88520ff53cffe8c45feb7972ac33d4b0bf1579bc6f874e5c8766814c9359f17706bb6f3b8bfeb66edf4fc172ac2e55382f6da2e319e81479b476426ff403c01ecdd565edcc13e49e2428be72ddd5efbf2d7ff8346e9bcd4d5b91ae8e88607ac4b6bf5b8c1918cd0f314dad3daf77d2196c92a26b00e9b616da6c04d49a160a776dcc30d642b4c973f092e2720290af94fa5e5919f6acca66223c40163c35ff7bd48268603ff10d42337f26605f2bc2d0866eb27eee0a77a2a6178cc914e77f5c41ee1692b915b9ba9407ef6e9a12cb95495358a383e589125a4b6a96cbc414caccd59f86e9e69ae8b730471a42e44a6361ddb0232f16b6beca3087c660116d72e7d95a91c6676dcc9ee4f872f9b1c344e0b4937519ec56a07d58897749f8f2f253931e3109689d14beafa71d0fa4d38d7b45443a238a485d4ceab7358543663b590e26d40c92c342c8a97d1962cb6dba0411d2b586c30114daba6ef395cdaff3f8ea4850cfce2050f4972b43d1be8e39e759065024a5d2c78baca3f51bdb5fc956758ed617c7457c32d3b8700257dca038eae810d95b44ad82fc673b87c8e33f5540cdca11d36fb9609169729495629e956b75dab12b06160fd3e3d9949b1e73290bf15683f2d5270c9bf372949864969d508832a2de22d29fdf97651e21e2c32918e7ecc9b4036d41f2334745d4219ca717b8dc249d3a9ceac59b5008ec62760f5184461eaefe13a4c5fa721890933282796174416b7a440d74ea93e136ad026610dcce8b1ed17708cd915d17a6ba5dc9c2dbb88057749efc9dfe932c93002c9513d1a8c39a359dee829c7235173c04e0bf1c615c84a7e6d042e5fb3770064083b862e9e9547b257e75f30b3de32d0be5d08aefca1733d695e82f3d5c22028ad3f2d03fa9dabd26abbfa57286a5a33859ff90bdd170a089c1071aa5fd50862ac15e68e947ca726758b2cd72cd7d5234d54dc3256abcdf10ddbb9e96e4cd03797654fd25a22502497259d0727a2c5e9f458285ade166e5530bde1401cebd7ff5db93723684ad6471c012df728b4ddb24bf151cb7d1bb910ca05bcf291985199d929cac2cb778e8ebe8014e72264e4c1d9ff51153e07c7e228a22a821bc9f27159a4d74da6e259ea3fbf97772b672ca205c63c6937a09927f52678e56e4a6fb7aed2c61074825b6e9a0c29b48c82bd1f3ce8f8fa7937aeae2c4ae7eafbe3c24d425355b071ac61f7acf17b672434a9904f03141948a903a2fbf93ee3fc23fca361413c7cedfd28226d02b4863cc70056f55ca00b8a7ec95f16e2e47b190dedb83a5116d28b3b75598954086690a3b73a596563a79668a764453eb8adad772a98a0eeb97928f17d958ca511a7235b50e36c918e3344732c8e99a986ed16c6693050ba20c871361f01efd6cc7132790065fb271c96c971ee3ecfd9a6b7a103399db2a9d24137707bcfc2d0b07727da884fd501810d718ea5f1440410b222b33b959e25f3d99e083433e38160572c95c510490a5b188b92e8fa0931db56367b2cf7421760b0ea4c0cb196a08a572ae3a263673b4fdeb0d1a1bfd7af8cf163d065e1e3917189fb9fd06c2ce382f6abcb80f65385f8325c2014faf50d5600574bc0916a7ae170e6da5f4924bf34b097751b71a74730e0336174d456066c1bf33d12609840477a08cb4acf8dd5f66721a6f1948bb0317de2ff3107decb9066da999e0454b730c525b57fbd3d4d2ff0a976202f63770c6f7a44f334ce91015350ae5fefb42b93f219fd9e86366eb9e10c5c200583d8bb616c83415e9dfc876d86df0025923f19f53a5f66dafa9bfad72b65422edd2a02a69b296f8a3bb648053c7da85206453e899a38bd6e14e49f0132ba2f5a5435266a8cfb5c338c192e3c7eac5143cb1d838ecaf3b5b5ee5b49150c0a2251e5051542232fe7f6a0b066254f8af1901004c0b8d93419e4d5b480a0c76e0bce7031cdc23fcb5f4c361fe1c7bd518c5cb332d8b0c4377e0c3cf914b726a6bb7c97f6e2a63866ae89867aa11c79a9c5456b4a71af39f9ca96234ed0f72a3df4380d1f9668698ee713ff6f9bc6bb8fb4add652b57e34badbf13e0c95868dfc367a3afc19cd603089b425d7fcc76dedff2efa990e18bc4ddecd90d12eb72f53983765e33d968d42d755092b05d3c0ab466f1b5889733a3240c6a8e468b391251d70d2f8bd56d5c2de3f1763aa4b9ec56ce04a3ca42f96c6d0515fbee5453616588049e8ec5eac44f800166777bddf33744d1baf6a94368b5db547dc7ff134f80664c0d005c732a0cf54995c9574ac4b4aee13132bd3a0616a94b51d49a2153fa656da99755fe55f5f1c24dd14efeb20aaf3adb739637031d032ce203c0660d7d6df97b85c15ddd228af2058f55088c23f423bf0296a099dabc19fb51f0722456260450861bb281efb2042039cc69242aec7edfd25875c7fa57a7d7331e4f3f98daac72e6cd552b779dc1374ea799129fba42e5abf11b51eaf431da9f9111600e855e703f7409d198f9135b03c8e40b9cad93deda81e263e58a49c58441728a618e11734eb7183883af6ac917e113e5c65115deebc7ff305d61b27c551662f27c84b4deddc5e4095b3d10ed53151051f1a8289cbdf1ffd7942e55a5f23b72f3afa3fd9706f8d909096cdb3519157d2492f2a67a45e195b1709f651b78cf72fca4b41e6a11886f479999a37f3d82769102f46e4e9dda09339145a2006d4072a9de63b10c6dd5d4047b8ceab6bdb6c5b79f5052e5b8836c1c55b8493fc87572bfa8a6d8f7216561dc8d05e1177e56ce8265f45bcd1b5392ae4c3e4af590ab362838fa44e4bcd237504574dcc6a534516d167f424b3cc7af8165a3d718c11972b75f561c11fd187133db4c9dd4c6fb365a181951c10c4098acc38873c97d7915e53f32f7261aecc18f0ff20da53c2ad9865fd50a677be21d20bb90aebd02a47202e5c746b77abc67d6673b5257bf7f49d875ea999b44d6de771f3cb9c96873728c62e2df50d89eff1e2c67a9e539ff181a7ddc0ffcf2b7f4610251acc8cf5a72f8a275110dc82f9bf1190602706dca33023d0b33197391d77a320ef9244551725d586318a891eec713f460f20ff550897ae3fc74e237ba54b9957d3605009d72683afd2db8da0d9d37e9252632d2c2f52e0bb5084604fdeb484405ce2f34b91c8b83bfca3d03e3056a3167bc93feadb44635435ad959d8784c4352e1f8d25868e4eccc716dbd8377ceb939933a43e50f533c8c0bddd3959a8e162163ea3b2c2f7551a93920aeb65e3555bd35e4977cb0635cbfa3e7adae47ad16faa8b7aa8e7250e6ecc47bb15a108b0afcef1c24622bcefa939bb02c05e0af6f6055c8505b72b5997360b253298ec3c93113c01de8e3336a999448f0f35382ddf80c9ea4e35a1c1cd03bb7a0103ee7304ac1b4f8e63fba97ff1f09ae7162d5629fc804348472ead799a9822bc42d11e8ef4c54261a1c53fb6b4c2d74ba2ebd78717cfb24d372fd6633ce5bbfbc3a0635df18c9932b230cc547d0da9004ec2ce26c06ef95c628067cd0082a06f7cef4eed9970c2d8c7cc60ab4fb5d6ec7bb08ade6cbf5ceab4697f75db0ab58d6585b8e5732963d58c37b6904bf7d3cd57215725111fbd23120214ac83c3c810c9f3d4a9a1e43286ffb3835972bb6fb0521c107a6a67696f0449095f504ff1c75d0669052c6a45ed2c67d1c59a792ed2b9bdfa09d6fbf856772960633fff9399c0f86b02e16025b7176ac7451b3ebf384792e98e96660a9d872b4ca5aca54681d6c47f1db218a588341df779fc202f8a9726a567d0ea1a7b23af7d14bae90e86d5c2798e7e0207c6a63d99e0f698e19e8fd2f876b5505c3fb72ed4b49e323b26f4cd4b42080f513eebbeb2fb8e39c31ea34730873989c461200f99d1fd3dc40cacb6e48a523a53d0c6806ab66423da93de23e503f4e94d24423f801e788987a9ec45987ac3ca0695dd535375c28025891286e5aaf80c995ee2ea0cd81d13c282b8a29b6acd11e6aebf231135ce0673536e48dae37928439ab72b10e60b470cf8d66dcd9d3422ad47d0c95f6a25f17e49633dcb0a52a209ef2482ee2467ccbbc8a97e665b0ad7a29a811f1bd4dc76af662ecc6d824a39d58b50f3a54e29d144a1d47bf4e88a672af073596bf0681d4aae55b504307b48fa7d572d2db25c39554e83366b745e4a0e25d24f95a63731804647fec26d6b9047e6772c76c01b287c2bc98a9de0af5ad0564df6b7d21c431464888849e645b916da972da4ce24af386ddcf9811edc09eadfb16713ca16ccf95cda4fc56206e84906f1aac30b31716bf035ad564b9dfc5bbe9bfc85095554d28852149f712135d036d7282206d4819f89701a7ce41aa3dd434d198494409311f4f05431ab2124e789d15aa7a5a93d8973dbad29eed903bea29ba233c20641da427f231c0bf12c1ac4254dc1a9d4d9a2cf228653bd386f9dc0c283f04a4e91ee3a2d6c03aab50c0abf9647d0721b4718d54db43324a944812b96b6f844a9a656fd3d9c4acc8f0f21d867241ae8bce70fc98fd1f577d7bd7da2f309ed7c45f6eb889f1a68bf25f3ce1d13871eb3f31ba977f20cc7196cf4fd2e2bb055f9a779e0034ed56592601b7e91510702e3058767b95debed35917a334dab2e14ff9b0a834daf2bc2d5a27f51d3272f86d7cc7001aced755f07090af7765eb2b77e66b5bc95b36aa02878d8156c32895fcfdfbaf9c69e5409e48884d6774f32489b9bbda65f581e3601bce5c950a727f276b356821fba8c41ecb536b36b2e632fbb4e998d01d5c109a1ad1d7792672cf731cbda5f13a30f913e4e2dbda5245d21f09878222ab1bc37efa3738f652471797e333e6f53651e6623bf8ba61aa03fe190fa96d5d1290854ddc60b6ed38721e184f0fbbebcc5bf50871e5ad826ee9f5a198f071a57dfe21fb8eba2083e71c9c49d9d4e0bca26882ed3c2b2b757696c07a16b7342bd8b3b11e10d339ece82367c71a0fc255dee764a5457cd534d43090c87750ead34211d2d47fc84dc2cf7207cffc77b0e890803e1cd26bf20a0f2d22acef7646b5f9bf090c4bd6d15d6c72da14938b6a7b63818f2a173e679a5624d56588e6ab5e608e9c931cf36343f26ac06c4c6a8d18039c69c20d4ac72e4881512ea1afab9d3601b66c5c14116f401749df77a9551c70b4bd9de38b751030eb7ebace01a304df92e78397e2cacc4368f5b2bf3827a569b3ef07c5ffe1396d7e9fbdca0b841570d302c05c142bd1fc72c759fcddd767c263aa79384a7ce664c3c31942ff9645937d54ec8e7efb61410c79f7eb640ab67f9b33755e6af5e6f8d2900fe08c16d885f162daba609842e94776c0822e5035145fb0026ddb7a15082c5fdfcee19d24bb220704a5dd136aea7237e3fcdc73c00ceaeb7e13375033f63b8c28baf463d2e8e54e809ed7b24ad2728a486d4601d2c044d4188fba8cf2ca1c21b33289ec0770e0f4fce25af0f5fb72669a55c77668bc03a16b80c718c5fb8fdaa59b8adeace3a72cea905aea099c623b607435b5cc364c5ccce75d667ea04bdc4a4adc35acb18ed099ca848c0c6772a4ff596d4320a50a545b6de93efbd86df6830e06fac47ac95555deaa4d2a3672465a6cde8bdcebd9621d66d39aeceb032aae7900ef256c5fdcdc2f47db12a072270e9fe137680d3016be3ea505327c1e1bd0941425bea1bf1c5cf72648870a72d213d99ce1461c1db59a253ee8b6920560ea9b7ea5ab1670078cba2b75c54d309b41a850ae492ff3c95c2758474d5d9a54e05987febe31118637ee8ef5f1d972636cecbecf9c48ff99473fb8773c162c2a2907dbbb5cb459878ec81e23673672df4ff58741ad55832c5a47735f45ba88c7e94d9aa675b4c7412819878730bb43112669439c974c075aa62813c29efdc9bda0c73329ba1c271025920c61105872e8cde3718dd0b7a78bb51773c67af74e0a3042528ec5044258700bd49061d32c2cba0af1b7771781ea550075716741815f67ed0ebd4532ae36623f2613ad607201d6e8155f0f84432f5fc4811e98cab603c40bceb6091abdfe6b8c313f8ec85fa16be66ac97177c734d5e7e08ff3f54a970a49505d6689632bef6e92a2149a0eec28400e79de33193d2a9be1736e904951790850635492609b936f0760c6ab7293af98be574946a5a12855a01816a90f162200e80c44ede6443db33c4a3ffd72bca7a4c985317870e22a5e3d4b9e2997e08301446a934d59baacba23af44f472b42774e9aa2c23aabc96dc6b9c28996874d6e942b08fb3263a2a223a61f0637204da842fc163b69ec063c5d07390461c80d0c55c2fc453ca5ee74253ef26cc72f7d39ef2fd871aed09859c6fbea86d7cae656d549e13bed802b9b252b2eb6f0a2488d03fbc7956aa7062f770c699971a99107c9f6dba87ae334d2bf16a057035475d28660671620b82f921ba273f973d45ca75707867630da9491d0b0a9e3a1959432d4474265065ce454d4d9f285bdd12d98c258bb17a3754e028b80a4276722b45e45654f21dd6847976d9b4c9fdb589c34f83426a15922013cfac24d7e972e1b87895ffcadc766d66e4d9399af176a747c7e216fc46112b81504874fc086f3d2c56677b0e0bcd87c54e1ed20a3dd999d08bc91a6c44e84f1025406a302d724befac85b43e515606ff6e52c36b94d352b7de45ef23ddbe07c5d41db2d793634d9b860761e00459d96cb70b9981368171072e945ddf12551f45fca54ee9cb72eb6e540eb90f4e08221a51da175283498f8ed374419b905238b49e4e0d946e72ad31ee54b4c9069acea5ffc990851e40150f46f4793924e77a0cb58b4fdb6950bb37742de32c3e2401467e29513a2ea1741cd1128d41d3a815128e4ae0b30e7262668a96fe2756ee6293436690d2c9d87deca0b66aedb083388f9159751a1a72c4748c1450e8e32ef382f0433a607c1e3d69c542d771acdc744e92d5d9484f01cbdf1a7b8248f4643be7c82abce45165899d70f277b08459a7842083a8c284729ee783389319dbae76806fcb6dbe69b61809b563f60edf5dbd1f83e059ab3d42f3f74726841abe055926f483d7c6ce901ef672839f20e6bc553df0ed79c0d072bf81025ad5ffa537c6363db04a106dfa01a4b0951b296b5ceacaf1034e323e00d53d5802d326102f95f53b4257436c5a76aacaa4ef708645e282191c9ff039396e930ebea6036b3cdd4b8b70b98d2d9e51586dca8ff41b4ae214cac7bf7cad7232a28a6077cf9b37217e943b4f02f5ddc7f89106ccd834d9f9fd4604c39d9472b1dfc20735fb987bbdf8a359c1c64c6df3effb3cf3d6583f9feb0bf7dbd03972cdd4b7e076660ebaea608edc673680153d61c5963d078f7a20e706c98465b272c771ba07d922197fb85641509f6e6e9f0cdf25115e8c9fb682a397ab80214348c99bc2548f321b25b0cb47b7b0e1a75fee1f973da5a900d15df967d0bc34c97231875e09b0ed64016c5ce9d77d0cbc7915c5817987938827465a4def801c3a3bbb0bcfbe385dbaad3d9054198913e945883a9a577c5f1cdee3849247c9a6d4667b4c563340ab6bc49d1013af1224f890bec160e27977a2032a3b5045e3e1fc722ce2f8cda5adeb1208afe9af694771a59ef78afc2ea0bccab5624a431b2496720c46d801935bc70139e44d2efdc8465f95e23695617f256db8f38657e65ef3727f33d8635517e1f64393c44637e7abbcb394794878dedb3c1bafaf72bda6547232989d6857ad2e931d5145d72de05ed7e490b6fc0fbee3615542634cd0acce501b9e9a6558af001d4d44e8f15e33c8bb51392cb02d436800226e3ec61f81fa72de0c5140534951296511ee7d4332ca95e7bba0cd6c0f8fab01a0a227985b8c72df0e16b50dba872f971b10a3a7819bb2a405aa9bde16185591b44c854fcd417266bdc077495d825459b6d5e08d3183d85dc41f58371ca76c38544c81c995f02f229c0afdbbd2ecb2fff2b5b81df219d273310dff4713ccbfb40cf234b1587a680e3b46a6ad4d0dd8274054e8637c31cef05918a27e2b1b67c24777676a71456ae93f7fb982c62b9e2c2ecc54634d5e8d47dc7126ede6efaef07287e7ad403127543944df66e4d942bbd7beb4b4c100f578d26e4f03cda2ea52bf5353dc97d35acba32c5b38f684b4cb7c43f60b437475c7e9fcce7105b7894726b374bb6ae172c6b09b58a58aeb2fd85295bbcc263132f9a8c46a7d697f6d57e5a18c16bbde58cdd1ce4dda021eb17447c9ffae857d40d87f347468e1dfacc2799b3a69c2a01329b423b9b8ee8c2a079abda0d6f0bcbe69a44a03195fb1503a73d210a9f8076968484eaf87fba403a956609ec3a86c3f7b63df238e2396586f10f260fca9ba5a734272cd5444bdbaa55c88858c0e473062976ffaae23ebc14d29729332538172e94629f9a6879c117ab33c04e819a4395ec70c76602bd6bc36c9f9e1e3c80e72b9aebf9732fc3bcd025d596006796b712077785e7d0e87b737f0a3e5c4380e3aeb67e664eeced5b3039f631b8099323539f5c9c864a2b4f8102c2bd38ef9d37216d1662d043f59f025d9379d6c420f2794579fcab003abf83a216aede37ed472de917483d7dcb1fd67184c602aec2822d727ecdbe996dbfff674ab0ace391b17be6fd182799c86f1812fcf591c703fce0360a5eb4d7582d1322c38b222fca572927f6b5cdc86169642fc73a0273f2a52d0abc507db75507fda4c78757899d615dc73de4ad111e9cddf77ae7dcb4af6d4c27bf87b8461fc8a485417bdc86dbe721718541bcac6a24ee2540f4fb9e82bfbc42b5e2a5c7619f4d0951c667cd13140b599487cba1465106a10c0a850e5c5ef4ff0d6b1f31847cd6b75abb3408ca772438425ab823f2619f23f1e0608fbd509afb873ae07ab34e6b4bff579f7b73418692aecd4f96daa442e9107cbf03f5655e380b3c9d58afefea984c04adf41835d3c11b4a8046a9fddd94a322577cb359601ed329fee2f8a7b43ca8fce524ffc4230af1c1fe54d55bba943ca4f5e2d7bb75e46f64b470a12c3a60b2c5e6e2b3a24d2c46f05e1614793a08ce4d055483c1e2178b1b4ad1a3b49c8e7c141a2212c72e8236562bfc53e8bb0d945ed505d9d40d3b902a53cf17b3e0687a647b015c0723c66b20c32726f371b891684cb9b0a9576151a5f5b91b48b3bf6ac288ccfe372e978c5b33c55e2ce582eeb6c566a5b06cc57cb0a8e3bf8b19e3194366c18ab72c527533f76375f2c6f29fad5187b1be3be926a299cb64e71c9bba094b1aafd27dcb34d4036bc2b0439a76fcfa3962b57d92c43f422394ecb841ccd8ab21e73149c564c4d93453ba75b6f43fb5d3cdac73ea511725336472eac3f28dd7836807242592388887eac273321a8f3ff244674b99dfe294e9bace67422fb46d7a1ba7231df200f9c30be66de9ace4cf62154b228e8f23d3f86331905cd7136b54fc772a08d1a581989b702bef9facd5f013ba77d2fe9d59302f633d497862d1adfb949c0957fd48c8e7e02beacac8b7a060b47689568c5c6d916c0dcdb8daa5ff8d41114831072077d2e5062262f96c82220578ec0821278e00046447a38fc92a0fa7280d9ec7f7df7c1a4662147c8e31b9d2c8ea5605ae84a7396438c030aec790f727cd66d246a1192b34134c891c9fbccf43fa7a3eb76c2a947e73ef878bbe0ed3e29ea7bb6b1081351f40c31c9c3c45fe6ea9c9f8e962d3226a411369650953a7296d81988f4e0eb1eab0287ddb614caf623a74c917d7560900935ed403762051975d503b24a0c89584a456d9fc3f90fecff8c2b993ff56d53f2e124346f44ac44978b8ae94aa42a7acb9bf0a3ce9e39207accea19bab902ed9ab4c6d3aab6c0728db3b105019239ad5395e325f3c77367c45b3703c888c4d2d9652db9c431ba7299a810a8977499001fe3cf37e602ed938eb5042c8fd37df12da1182ed1ea012e9c0864a4f6c1a0d512e2ce3947fba644020968d8483bdb5d828a58aa69b5da10e85af4049b4424a769c7006e13067a4ed492f625eaaabae8c834b7e8baf1fd3bdb8e8c2be5a71ef36e9bd5c3ecc46eb31ca4179201ebe4af8c33b299831d235da81b0cbaaad0dac73500eb8b4b0c4ac84b90bc5caddfd72810ae6692ca87b072ce9db328ffbdf074d4cba6b8537d3556a12d5344dafaad03b44254751adc1610de71f5f358f4f37040bfc6400fccad4da96665427fefd41c36addd1acf0626342910d851ef2bba8b2819b0025e868034201660448ddae3e5c6e1ad4b75cdda421202ba913d69060ce34c575ec6ebb41d03df08a5cbc0c88034b6a3fe74b8407295f34efd50f9c289ed936ed202c0726f0ecb157f4a1002e0054b671e14259924ea076f2652f6f75d049cc5084fd4e94162e3b91c5db200f98ffe691aa096440acc819b03fbfc57000c93767af64b824a6a959691fbc8804188126afd7af7a90484332944e1e3764775acb9207139ab7180bd6764226fc962d5eaf16aa3ae463072a817df5dbd9369a69765507d79996afdac87c74cf0e12ba73c34bfd88f440a3510f6fecc4f9e1bbd28d14591ea07f83447fb31417a8c6c585a339e3ab423553acd4cf18819ea18f8e894ce319902285023c7367acf23e2d0e26f0569df0d7248eac4d7800d9695091f6364797f6c3c0d287889a75f62a530a60f8d8de8c172880a329aebdafbc46aa82589a85bf0a82434e9b51420466d05662f2ad45e4f7264076382222d67c560e93f86f0bcdb1358531149eeaf4d291ee286df74e991421de5da730396e5e604f4202312720b080acd901fbdef8cd463d3706159e1fb7294b36c7d19375848a2776876bdb21d433bd234d019a9bdd978691541b89bb80edd89ed2f30856921f1807fc8b0a3c06ebfe1871f8c958d9e626b3b797d3d8a7279ea9d19a96b690c3af531eb5dd6538116c8d11d96bdd6edff1538a8fdaeb46cdcc1bb3c9fa8bd0498d74cf418d1b50860359619f310f38baac5ff0ebde6b1727d53e221ad813cbe7db5b64677576fdb74d878b6a34eaa6577c1ea86f679ad727c2efd3bd25e780d45f7aa3c447b49f8b65e77a5174e788c3642390b2e25dd7284039c7feccb12197f7abbaf1a9bd533385bf7b537dab1c7bda6df89350299726abe3aacad8cbbb7c149afc7a767032c06c5284d25a0b04fc89e26002c01eb7242ccefab2a8fd7368cf7e138834819d500a3e829a715175d1bf047b8505ad114ba428bf4ebac01fb00f0134a0fe9b1e7289e752ccaf17d3d32beca715ed8a172d2233bc4aa6a470e900b703c31725fbbaa129b7a77748cb5f78f78ff9a88bd72160f353831073969a7b641c2d657aa31be159faf01013f26d72c2edace3f1965fed315e3e7c7cafb0b8e793f488256d799325744e508a5d80a0201fd606be91b1a15824e51e846791c93a98682e745aabdfda70fffb054ebcd573e64e0c51e721678e69936e991c1bc71bdf75196e1810870eec179050a30cbcc0dc35490757230f6f9eb3b0debccc27e117c070c4fb01a7096baeb3a56d6f4140d97a72f2b2d624fdef92c81a063ca9480cb7a1ceaa7eaf8a57507f53b25ce85167213f87f1abb4a671f5d68acbc99df9b750b4c0a5025a8059c2617b62a5deface7d7a6a87214b150b9e48cbea231bd9c800fb96f3bc6036b033883eb9e41c00775bc1a597215db895bd5101238e866f2363d61a0e2f799c65fb3c889acce1959121453e668e93cbac71829244add1f46d3ee3f39549ded62bad7a10789bf073645d510887290b83790e6bc46f5832dc49de90ab1f19d1bb13fed8d582e5bfb51430197de72ed7133b7a5adeb561cf4b9a27bf028635d7569ba5266628f9bfcb7b253eb7572734b17d0d4cbc63e42c68853cfd8d25b0669615802f2d92f8c35a93eb2ff967267ae65f2be58be929de8dcbf3ae76390768fa59cc2a5884db01a58e435ee9c72e1933a3b805052fcb27ccc1d0d3e788a504979759472fbcda554eb91455e473f00f95265c4908555a8e892af0f31ce2a90e61cc96b6741d936deb569ff98b0594729a3a2b665931a39ace2a0dac24bfeca60e1ced52e12d0473927faa2d1021fd180c86908c0c5d28e9d8a8ed4e12341c61dfbe525ea623920d88d4d80b1ea728c540b86f744931d92a8558215d372496e0846cec06c410e86bdebcb9881d02acc24803272fd3bde274704fbbef95d9121a7f332aedd20d95a17b65ea1e22572c2db8c347b7b41757feed0d6612c89f8849c334d95a96d2f3cccf1c1ccf85a2ce21735e234b0d7d0c5d73a68619389398de5b68aec85e25c84c5943c719e0772087dfe8f9578abd49caac46c3f064635e639828f6584daef97fe422932226572bab44920f1775581f3a4fb78aa362918f256eca6a58ca5b06ef21c0299c9f258d3482a460d18177e1528165d7c577e90789b5a07af4eadcdf776ea34e87cf90820d217b16aec87c89c24988d413553350175808f8096be1d519a3b8d289084728ab7bdd063fc7a3485b51436e871fe07a5bd6cbf7aa39d19f3ae15a63a2edf72489b6377d0c7d0c2aca3a729d8a1c0f0575be285799fa97de0c4858ca7d835729192ca6a97426764b45858975bf484fc3cf7db7218bee001cba6d4fddca2f95b692f95f2a64d5fe79e802b9a98b19a9cee6da80287c61f6f4a1754f408bc691ac084931c4cac1b37a5fbfacf65281869759535bc138e678ad35c6b649a2613335fd6bce2ac8074043024599bdc438d6f12ff7c5a29f0c5c66df419edd480b81dc59d858a8ee0f57ce617bcce0da81d814f11f0cc05e98c0f599a5617ca59c572b4b84681575383f19a24c9aad05180f0a5da8a82d9d95f861d30ce9a4d7d8a71613f75706c3a9685e291a09b5ccd441fab2d666d9d8b4c4da0febd13b7be5b722d6cefad9779fb1e9acbbc4a347b3c702e08f6f55447fc24af8da49f5fcbe472f71361723f03005a9a21b4df8c2b939dcf05a50015bd7a3f10ce482ac3b32472b970abdc41300c71b040b79ee3a30b8ca75193687a76cdfb4fc49218ca72fd724dee15aa9a24bc78ad7a971933ce87f3735051bd10330452db784f83ff70bf103a0bd6e63659d210c370955a54c4acd3017fc98b0d3ae8c56b30267198f6797263ea6624cfa015c4f9d28e5032746d9383360cc509381ddcda8b462671d369480e529b8ef4f6e94cfe1a0486d4ae3a88712e9385d7e858089635137309fb7b726e51f180ab098c7c2bb2f676af6c2cbf401760d452f8fec0e30d890c7ced3206d70ffce8a05600a1f50fda35ad3400f8154e4eedbaa3c08bcdedc240d2c676720400cdfded54a85c0aac8fc0861183209084f733bafb5c5de75b6d002d79a572c3cabe5a88785bb44f94daaa07457b14c44c18e34132df572a898c0682a17372dc2be664bfdb0f19a090a91114abd9b895ff1d36e7b70a1717b20a09cdf582728362ddcafd703f6653b57755ad5b6985dd5639eb46c947c9151d5e97aab1cf720af2fc14d9d764adeae58f5c08065f21dd4cd4e6e49d3831426a938fa2231f4d00b57655a5669d0897cec95df0e8aa6ab65bfc78e7f49157b342bc2a4ba78f72f1c4f13945cbcce962989d0f1b3d0f2e1b4f4cc6e071975814f4b5a988d7077223a96da8a2bc20b6073750f6429711c50f59b4af36ed600f2cc9103c16d78909fed95234ca8d188a1ba4a702338b2a52e4548ad5645afa290a604528d10be872e551677a0b72c21372fc99330e310f4090dc7e092e707856db44730733257272913dfeffb698e154b32ba88fb94b5e0edfc5efa3511778aa3cb59a469a6c7c727c6d303d73c6a6d0dc91b0a29639da07ab6a29e10d4bf9ecf32f7644e6fde37230304ab2a229c5234d6392b9a4db2aae34dfbdbed5c9b89aac4a5df57805487255c8b30bb1fbee7dc0ab459b2608be48d832b4323d17fb0f4eddeac8acabbc3024fd6731b8ffe2619f258012ae73fa6b202def3546207bcbbe7a6c43589366570ea1b87860de604555d13a6ac9c4e34b82970060573930c6896403668aad01721fdd0b98e3fbdfe9cec3a2bdb0c3dc4ae525952b67349570fe7898558d11ed72c1441e85d594c5527883d675028222c3cd6c51b5426365e4b80a684fddd0e8721be41b5a0dad80063ad6f34c4d3416ffb0cc13b8bd045e99f5777258a5078132bdd62180b70b8d3c640756e923d838a741e0df9affc85b984ab88e39be0edf2f53b0da1e632a2fdb880d8d347b9da4c336d225acf973e1fab57df8da8beafc72129add80a7205985f120c1c4f1ef4a8cb4d9f95aa7ec666fabd91462a790262be3663e422731347eb58c2f29f5dad3c21e0b9e2d3a12d93cb2a50d19f2755b01e3d818f3473dcd38f404d70b875343e382fe1208619ee2c767ab97c34c94cb72b2f390afb9a98d85ca9d30d05b006e9bc90dc434e40ca5f855814e5899b50820f78b16ada83b6a8f1f80058b6d5e37ddd3a4bfe99e3786fc2742c2474273542534e0fc54e0bb63ac09a0396455fdb5e526922cf4163514d3a178eebee1734972921f092b592357021c88442107fa19d5cc15b61d473f29c3832a49d52bebfa7245353a5f58d77ba18b77724f02f076e5ccd3cc8a9adfdb0b20df44ba8165aa725c6707d4821838ef50849625be414b0b115ddea76b0fb2f640e055faad57064ee32727acaefe3986dae01a3fd5c16f0a3082542722a77e734f7e039bc48670729711d248a587a508cc6565b1707c97eb3b8ecebcc3cf3500a3f22be74603c87242a8e68ad5e6cb31af9029e61f6580ec83ec426521cec57b6f6d324155a8be15f5109ed64c6e0f45ba065370a0d11687e261d044ae14a5e8f74ebdc7be879d72478bad8bde488da57583a84440d08378f29b0b596729c4435ef2eafd47dfe347ba22d5e04d8c8a31aed7883076e168ccc28d0636a8ffecf9c40885a72037927246491f086b138ccb04f01c7095d4f49f61a3eb9d391c5faa304392c5b6401372bfa8a24fc6a089a8baf908951ab9c365e3caad80f5a5be0cbdb884f14d6877722b611b87389334af8a1476f2cab7b25e9ca2242bb113d77d82bcd604201c472452f9148663ffb34a8ead13cda98ea821557686185450dcb796987f6110cdb872faa6172ab401c51e3ff5bb46581534966d43e07733f47cc945f0ac86c536c062fb7bca7f867739e21cedb8f5a2e708143c4eb8ed8a2e04196e1b8feb4506975eb8205ef635e8faafbd5cef830bfff80ca43cc846730d35e35ad2a914e572e172c739e181ab9c6abec51c033f7bdbb7cc7cc0009c3539fe544260eed992190372c772ec3dec01ee71844407502e80fc93759d6d97f53ecd12108819886e60a972267edaa1309d1d38b38910f15b1c5e3875b80ee322d79c5fa44459012f9181722c4bff07da53f9f77e50154e64ac1d199eff8db4d8d0f950e043c65fcc7b15728fe8ad6d20a0dabd3798edce1a066081133cfed57837866dc3916c3c2856a7721e45c275522202d446f29b45f7423aee214c14f60f0c8bfede0bf24551a58372e639401c6df4679d21fca52eef24e0ddf380642f1cc3656f110e787a26ed5636a4e0fa52597315621a3c2539d37b2b68b9c146e5295c60da03d7a0b1f243726ad26730a058f87c2e9b93d1b3728cd2117ed8f5cac3fcfd31dea3f8463efdf7729244b691c7cc8d1e93497a5c07f90bdb36e997c16cb083517ad10ef272caa91157114583d30558bfbf9fd5f2c5b5bc033a0fd6ff752c822cbf929d1e5b9bf3662980da2a5c4b80f2dbb999514d03e63c992538c42da631c26c966e98f7372a727b1fe7104e5956bd653b6a4152895c5b46aef8faf7406ceddeceea786806ce05530fbfcb0f18f2e98897fd6200e819f865e910c11f7f6bf936b96b0d7f171972fe23d4951df0d1968940e988ef4c51d8608ff59c07362ac1d82e586dbae8817272901a5acb906a827ea5f205078c754b5c9c6b0c5d93fb8fa7e01f69d3fcf9627dd3e2e445db32ef5bb5dbd607821b2f866e53b8c119cb5097387729267f2422f2bf8c592291c7e87135d9e41c25a0c0196d517e4f171b25bbc6ba26f89328724abc2e7a9c091170b1f0ab9a305f1c2c856bfed7ef1dc684f57de4ac4f434472bdd745525bdebb0f7fbecbfcaa7083e28615a36a3f31148ccd1cda31665cd4728bf537638772fd8f6e8edf2fdc20c6c3c02caf166c0e62fa561b45067376924671cba255642cc06131d4030955361b89ebddcbb1d4250b5a5c51a72ccd1393723d4ec32b881441bff3fd975eb52e61e77d7ce841c0049ef703c9dcc2a827d972532ba66d64919779613e9879c8275cb6fbef29f1b90e59fc2785bccf87ebb8725fbae3af4f3b60dc232b122eb22f7fc1e2d841a4e988f947117ff1f766617d7232de233b76478070e344bf008f46a3c733c54c3b12f06130d184ba894e97f26deab123d42bbb6430fe0a115010d6a94fbe99d95db2263fc9f42f22b5236446729fc77f02d91e0850251d59fc204852efd264ca0e0dbcf0abb6922ca1b82bc27244915aeb3dd5eba656dcf54682d7ec2eac4d122ce32bb2630145e59a1457f35877fc9b2374419152f70d559a5f919f8ba58f8e459e3814eedd90028e615f682a059266cb90f49473b1957403d5c4b65663021412ea46e1d1ada060bbfd09bf724b2350e53cf2dd12bca43adb3e79868178d05721d2725bbc8e05339c31380372840ac8f1cd9d4446d8416d59e0ffcd98bee88cc79097294b48772b4b6aea236b38b6abba99837847a9bc9db5e4ef439c8a0a1c6267f3929379d7b71d939dca729a5e54233721c6c9a7057e00f391a3e0e84626e5079872f3571b8789e34af1722944260b6cde5447115bcf24ff60a7677303048326bd4ca93a0d78198eeb6172266fafc29ef7f462ff7d53a24faa11a231d723d8f67ba988b1d75953b958787270255a2b3cb50a6174df2e7f81480f37daed01d9f91852bcede8a4019b9c2872d086915221fc7b9e3a591b0291d876163a9160305faad828c236cfd90e17f36211c43368884207f97fe33cbbfea2f7578c87589951e7bb9cb316721ba7531653d54b3b8d5ebc95a5e70f41a1129d1084002f385cc24e5cb8bcbea13b46b8e1325ca722b12796d5ad86a3a438415a2964c239f43484829773c1962123fb725772517ddaec206967acd2c2548110c59ddebdbc220ab26e3f4d6f420bea037de1266af1da4b52da0e0f8857f239221f35e341a9bcb141e7c537501e3c693a110e1041d287ad2fd22fe5c71e8b033122f10890b8bb4c7b3345bb915e51ffb6f2e8560ab0b1ef0a552158feb3937049ae6cad4486d2d2ca2f1d010ab2f89cee28b50f45e438553f3fc3b097700dc749781a70212dfef686e489c7fefb8414db32c772d273940b64aea827cce32603af30e581c4513d05ae38443bcd289cd954234072be02f1184dd16b871f031053123ebd9465aed2a15a6e0c03986d12ee573008017921512b023c7f0fc266bf643970893c39e411a50ebf063c955c69386f728c72306e3027707246ef6cfbe283f7861786f58a9a494aaefca6a04d1a0f9f19c0727b718a92a73e8314575d760ad2e0891e98216725d341bccfe2330e7a0252b100d77d8c4f6755391ae5fdffc6ee47757271df036d9eb73b57dbf410103c820772991c4d43c94107c06973e32060dbe90699c66aced1df7e933a75691faab696723814eafce4796d5996816b427eff779afa686db04bc3146b11beb2306c48fe0cc29439f8cc8204fc193542501ffcd4d01036bf4e1c54c9cf45143dbf4b5d53725d8af727bd52663ac21c16a3cacf0afcc7d10671b6c8c0f15c0c2537d714017253f497e42ee3eb599dc663617c7f9b76e8a98ea9570299cb435f415023571a727ecfad5337949f4e411c988e337320e49491bd74500a42cc8ee219599624cb35fc0d6278dc51a789c073a3ffb1f6ecdaebca82af486ff792ea07a7ff50acd472c0b94f0c726e5832042baa2c7a0ec707ebc8d4fb7f1f0bdcfd0d88cb4ac21559e340231639f72a20b3e8159ad06f2b10352484053b5e5d3ad321393c9392fa7259751eb8078ab0849b61270cb5ecdd561d0db386d1362981516cab8305ff582ee39a6d22dbe5c0fae0c44bf3f2048307c17bd149fceef340b377e7be4d88c30afd23d1d95f5474c9f1afbeff1d0ac68b596a95cc545e895b28610f63cb461b72bc387cb0e592125c856bf525b49b367fa8c0f9ab150eda2963b9cdd4cb32be72b906f9b609ba3e07625df72a06d02e36805f7059cc1679c1789545f893da06728af3115f4a2ab6a9381440ed395b0ad7a04f874a8413020f03acf4bb98476c7207f5d267ccbfd35b0961d655ec64ddb4f7c31d7a18ba8ffa4b11b8c329148d3a019735492b98b94eab926492c05018cc67e8996e538b2cf6f82dd7ad30fc974da3566465c9c1ba7316aad69f74b0aea56e605ea2fa3a1678e2e59ba997fc5d07c3e115a1765d800cf1a953f088d6db301c78ead71434ea3b7dea4a4746bbc07206a3065aca5f3d1fadeddd1e7e1c77b88cb0124fafbd32f25562b2fa95a495518589ddd4e044e79cc8d8d4ac7f40f30e465b2545634a1db60d79a1b2f47ba3722cf1c04dcd28ad570d71e01b5780059841d76bbf687e5515794c4d52f3ee2219e3978c98e1103f15ce36c46e4a63ffab9aaf408ef00ffd69932bdb5283ec9303cb810d275eeb8ac4da46e9920ba198460676bb4de2a3aa997d4d6de3854b89728bd65eae717689fcff07d4555bb02025c5613d93203427ef6531004a2b723d1c83ee1eb72524a190068a621dc5d34b3ba86c5a6d1bfa8aabec76a2257db4f73b0eb6e71191bd227141fd61f0f55abe2a77e200efc576aa86cab4944560dbf072944253b5210df46256227191e6a151550731dc946d967bd41458748caaaed272f7aa9e6854ebe598b829ae3ec4197f01d606a3ba4ad7bacc4d876082feddaf50a336833f864ee20a35dd5172d842d9f830cf57db92806e8b16ca4b57a755000b01225146d4c73f6e8edea86888cde7802868931efb3899a75fb74d3a3b8b7672b6da864bfa9806dd2521309d39cb485861743fc1bfb28f8b7c77e588c88f4b5c59c03fea5d6b4d52e93b59ecc586a032c374500f821115bd279f5a17b152f603099674462191d8838d2a9749ea26be5efdae8fe8e2531880045ea1ca4d193d725942d0574dbae61010928b3d4c46529a193026ee7ab92cf566186acd1f42af722e7b049713c6572f87a0c6fae6247485a1d168e0ad6f2e264ee188e87086f02efa881d71b159d7f17750e9d2196d11229779ec6e63a61c4d94283c104e6a905e91fef0cd5ba64d76a993461488bb6132c9d3c0dfd8ae3b5573703290b782e915ca2381ffed25adb755a2deea4df095865b1c34699201972ac5266c3c384d5a72cfb0fb4271a45e01d10dcf77e2e015aeec50849f7fecf3af9dc57d7fc7df302f4955b8c230a9a98c1b8821efebe8f7c2f252e52889996f788d00bfe4ddb1b931af25ae0ba65654dd33e326542cac9b4613511bb6002fc471dd57085f678e47342a16d7d650eb260c673a41e54291ef0b6ae24340a6897c7d70f9efae705f6a72a0c5c62af5bb312a988d5509a64bdd4ddc943f825247a812601ddf2cc337db72e6f5579d52114dad473da364a2d1854f144f4fea8e45c738bf0e901ece11f07294999b2098461320d43a455f98f7fdb50a877dfeb376535ee3855cd248bbcd714a0e30c13c40c9d1a9323db6dc307162eabaf9967754b280a31e33c95de6037280ac620f731022e0d9b7b3af4dc8b7d674fdb7eeeb7a3a36d0ce9fbf5a9a426ddd614d3f6bee1cd4518672cd7c01f708da75fdf56d54f1a6709c5298f128a272ae190e0b0644e0410654d49e1b4fbe6c2460c12b5a1ef9cec730211871a4d5720cb8e9c72b91096cee9685bcd0c34b4c9e2695dcddc925e087cd446371aec056fca963ef52a460476c422f648a19eb9765391b8946ee9c347486745d8c07a77209433ca42605e396598e3c7f5b173a0852f26b3bebde16e82449934738a2c772f003318177fe822d1eb022be178a0ea70dc336151c1b0885bb9f2a29009fe0728d167d752dd56892408ce7e2e9ed8df2b7d8e70fde403b375d0d84cfdbf4d33965d479be0b0f9149f8ddddbd6678c7b0068d02ce073461e9a599906bf07cff2842f0d9d291b748a47fb383c531759e65f923d3d5b1303f264b45ad619c13677287781a45bda7342cc48136bcf3665d8cbd818050d5e42a1c210c2ae900dd95725daaa0b2388efceda53a97dd29cc22b603419b6a0d1179e4d61e97bb9a16ea72edea88b0a59e2f598b28369270d04918d90b3aafb8115a535b3028996aed5f374a80eee63726eefbd970c1bf08e73246ca059e02a8850efedc4764d131fd3872e1416b5450ed0f2461ae5c4fef66e1fbc069c5567f52c7e567defa403e3e0b722b33d4cb1e59850ac95e30e54bb5614372601464abc9a6aede0136cd85b75272c1717ecaea520715f0b5a27511e66e24bb47ab5c1c0a0df3002c278685c80b48e59f94d30f508ed3edbef11820ba253e03db27bfecc5e8e26d1a2d7d21c7737284926d97be1d70b6ba98d809ae1dea0164d55d03be87f28b355027845a6010723bd1d55ce9f448acad9a11b169074ed8914daffad679492216c2d258691781724d421bac9d270ca9b1f6bbcc0904f2e21f9dd1ee95ad94dfbab2b4e63e53ba30ef8d512980b64e5eb645d147b558a86f05bd36dbaaf66533960b683bd9b823622f3a65ac6d6b7d389b78e81a2db26577320f37e759b5d3e464719a0f390813180c86ceef8310df69f0ecd86e61c2deda83b00643bf8805218701c559ff59b8715dd9351d000f352b393fe43b5789a45bb06c1cf6dfe4b3b722f29bddc2ca926b03780737b30cea026b80288d63ac4290794083bc2adc85562ed57dfbe6a08b7257fc7aa7018525c71e06e895e653f7182abe8ccd9868330f43362939d02ef3241768cbb193f59be2bd55deb2f76a4bd7f9338f4d26bfe0c87cef4c6e02113363cc4c7d44aec9f9a4ad0f748db86a93fc043e9ea87e42f3bdaaae12066d8aab721f973cf7c831bcd42023a56ed8abca2dceccfefeb502fb049fbe15c816fe5c726169cf70c80f46776d86600e4aaf1eb48d82757885d20ffd6fdd46808f76de4f985382adeded768f4bdd07304d3461bb9ff9d9b42cbca0cdca6dace4ac475c0379470a9ce59b9020f1103989ca34259bf72dc648eddf01e22cfcc000b1d5a6228cba51519edc2b1d7e05059a9787119eaf40c97d74ab7d60fa63d75b9f4c8d0ba6fb8152c80b698382a29007cf1c0e75efb70b4040879a920b3f1284cd9c392f9932b68bb9e3f2a71625d03943fdc8c278699ec2c4bac03fd2dddbbb8aa61b725958d9f57fa64270197c5653f47428fd52656323566756e172960f7b2acda87240731df471fa15b83b894f8b88ab8dbfc0d09f4b05c6e5e377f65ba4903bc072843375f93c35ec98a8c7c8a901e62e9bb08a237428070919d0899cbba5cf455c4ff3a39fe8d48bb64221f8107a42e4250c650e3aa7f76d0a706d406a9de415727faf05539d7f7ba860e0b929b752973c29053888193fbd829bf165c753435c7246ae8585b6da6c4334f38608d41ca8654afece11caf9dd063fc7319ea5d47c7215d1e4db49d7bfdd93725ddd089c97aab4f690dda4fe666fde8bed370056c57285917af79caa998021f93fb4fc385983177fb13292b97a263eb108b9d0493a25271e5183a81ae25297a267cd426928754a2f69d2da8a36bef842773b978b99260f4b8d8018484e95b63825799e416ae87217e08fdf296aff96ba0de9eccb6f72e4abccd093595694f7000e2b517b157f3a0d4d98cfa84166957cde309ebaf87266afac050ed490d141379a1c2c0bec61451b799ecf875d749d0fa5f5ba178872a179ff7763156af531053795702ac2d4b01ef5ae7990ce157d132a78e377e9187059d5b0d58d3789aa1067bcf74743075c1b47bc47b981f5d8c58047c984b90b32c7ac25ee445fb6cbdc8830178e962d12d3524d398ef4780a9f759440635b57e6e95c663f9585ed1ae37998ea0ec0fc4a1708641dce7098dc7a1d42196ac7727d02d86b3f8127349d3fc470d782bffb62d1f0e426b11d810855af1479a1e104dde3bcc38c10ca9377f44543e8df2caff0c5980083a83a9499d5ef6c89fd350715397b157bfc36b83e01455794e3401a7a895cfecf3512d77d3f8e5178c1d8723112d5bc0f516d371db647db82e2c0d25f661fe21ca17eb442cc65719fe1bb1aaa008ac8f61777ebb1f8a441a6b217bc0bbce419fb9f4de46e74b9e10d3bd50ed35bde3f610b8a708963ca9241b702f265006a77a04120d14f2d68d76f4fe0720d5676d6dad39d8336c450e04c1ee8717b7f3907c0bb58d4a6f84bd79597a9722fcd3c17f6132b4d96dee424e1daa230ece74bae466970b055d96df49b775272b2c2bb9f36c44849554f18922d7e32387dc29e929a1ab7b04b8aa658551c7272832ed1465faa974fa47fee464a6439c1c3896f63e98ffa89fd031848365481721b2c546c5e0906f24101f639c649a88de40dbd0da3a3e3a65e49bce281b05153961aff413cbfd28fce9ca410f1439e5d6ad141038f6d4c15c9a9a4baaa8e0972502d83dd921b3a6d3b84979e3a6aa8fd205887c7570d18ee6802eb452709f41ce798a71265eba5817f983ca0b4556d1445ed5ca5ba472296ef14cb58fe9d0722fbe67519f61edf190350abf184a15cf11f1561e8689d503f1c5f9e3f0bc10972ecdaaf2e9057b2f0f8b6e61d5aa40095d8fc0cb60ef13ca3aaeef844f228d54ab143046ad59f6a66fa6a5fa57509a4a05e7b4928d84507ae6ecc27d6fb6ebd4632d35ff5a7ef43d53ad7096da8dc1da96bdd94e056577d131a825055abf64e724041ebbc34a84e8345580f70057d17747c0ebf4205f76367a3263de2de604c61a659c72662211cffc5a0bfc693f5322466f3dfaadf2f09650b1a77705e083a72fde415c3689b3ec82afa1d757222e8627a077d070b9b3db8647759bbb2918b160c9e058bb57863bb6debb1ca260dcc22bf822b7dd708aa13fa70635501f7750c0fc2cf618b4fe7afe64ede551c7dc0d74b9c1a34b0e5a06bec4935b9a15b25723d5f7b681337cc15873724540d52244f41dcad676592493cf292647455466a5de1d90f40afd2a583dd841f039552d1c65f7473f04f3e59491b25342808911872f9fc0aad626c53569c4efc93e88a0ca4eda9ce4887e9334ebd65e24317cfa77225fc0e7038bc06d3bbfb1f4cb670c3de7d57c62578bdd8e56efce88ae3b90872b0c02ce4a0914e3f9386d86874fa9f0ba101bcb925605acb1c22011d6025ac72885da4d31712530433cd6f88c69b5284f4d6f59c936a8cbfeb55a5d2fd73b817af76b22bc50eba6f05e4dc8469f24ae780300716a963bffcc16b44ec001d447202166c3cac24095af91ab0caebde97cabe103cf206d14ecdc2104a519dfeb22181330539cf17301f54ffeb7a420e6affbef817fa017d20cb0cf35bf90faf694a547131d6f9e644c275bf0abc3574a32845558abb3c89ea45fdded08ab23b76725ba0d36eb5b8ae2a3ab0cc0a07ac4c2ccd74779798b19061fa51180ef44ef472492a6791cb7620e80ade4b6d10503f87ba2642b0ce5bd453a45b586d9506c3727529fe514028b30ab2e5e17e2d4aec0e33d8009c74995fff8baf540138881f72d37fbcf5611fc0389fce11e1bcd3f1885ff5280a11508e4fa47a993d3706067264ca3482b02c8692cf98acc8821051677a668818d5be7a2888dd7196630a095d30de324390e554a1db5141235e55bbfb5751fddf70a8fae23d758117f71a35728774cd26093927199dcc9ae2e5ca12dca96d81764ad3f3cc7493239ed927ae72f515a3fc0938cc3c2682133e58aae3ab38f0bdb50f9eb2d7605d304776f53e72c5f3ad63c27d38acc03c452141acc363c17cfe7290da9fc3baba62b2cf301d6dcfc4626014ae8f5e30694f604412faeba685f19a44b7db4c036c3c6c0540be7200a02ce0d34154fb1a5ae890b2bc2d5b6cddbacfa47a4898c35bb1920bc57b72eee32c48e68673fa54d93c5d22bb85a1a4e957601b8ecdd35b0427d2311706039d910344561b615406e7c790d4f50e37d95c0e15fce5e484861114fa88abc62e446cfb6d682a6ba236e30972f3aa8c76d39da31ed6a24a60d3244e3d3476224580c9fa86f7a9adc2f237785ffe656a74a791b45c486d833695d68eb45dd80a7254b22996aee8bc1d6cde15142d925b3ca1a36d8f48034505f8ea88c38963eb72debeae0f7644044484b7187e38dc0366d15d1badfcaa75febe6603620db6487261375de9000951632fe62e52fee48215f960128db6b7fcaa6c06e4b080d2ec72d1eb4e69d012f0feb858dc72a9ca0c208973c655c63c7cce9f97012c4a0c0a7205367779e959305a653262d5d375a7b13226f0b22df8da5cc00795a40a01cf72f7d1632be1a5ee101b7e7ed7abd22655c8d00b49457c6e18d30f4425efa7e9725a6bf4becf0e4fb4580fa7f33bb63d7513ba7ce013c6f8773459e164b98d0463ce18c168e1381d8a75ddf4b78d6cf334c620b61be353dcd23343a03cf50750720ba77c380ec4c9d1e99f80642c244f75bb28e9f12e0b154aca0c8041198d873b6b759cd3f0b00d14de5633def16da25d09eb25bd1ab79f8ada2b9be4f3e0fb72a1b7c23b78000f3301d91b5def969e849963bf9888c56a50f5f4df62bc73cc723e6254c90302d17f4e8bf8afde70698ea75449bb6ee6e3488982d219d6974a666ce77dafcb7a1ae0a50bba46e09de37c9f00bce24835e92a026a9a46cdee2c724eba973c407ae9b4b9aaa0c9ecb7d9fe694b1399d48e397781dc8b06d6b78172522f3d00e4a3a33be11c6746de23c7cf7e5c8bcf4381d5ac8ff2643eeca6487281d876b61c01f314c375af87b83e6db54ac6e7d7ded504af8debf62bf575f76511e13377009a6cca6a642bbda096f1ce4cda5117361426b13a77c745027a78202a77daf65cf82cbb1938b0d2abddbd9dc2db6dc133c4b9c966da1e103cb101236cb0a086ad499f0eca9a944acecd8dbb620e4cb5379bc23761c67293193e02720744cbab1bfeb91de044c82912045efe92f3858515c74d7c1df08f457e9d4c72b40969b231c62d4f1a7ba97af160e3e9cad772f9f1297ae15e2388f559cdd926fb26149a0546b3555f9e31d174184fe47e60a5d1e9adc9e81ab3910c2cd7b8727330f94ad99583d06e63f0d7e9a2cb5b9b7d1420950b78216230992e9d261472ef5c73a8c61822c87a59d78b6f1df6899fdc95933a94b5ed5fedd3a45a33e372f7eeffb10a077f24f29f16d6e2a7eafd865618e104f3fa310c980969464f8a6610c7de9f7c54da10f939fd9eebfba6b6578531b3654f86690055548beebfe7721f951cb335bf496b437ddfe3ba1fedc95dda90c9b2885b22bfaef60e08b10c72ee7e179d8bf1777637eaf0fb1bdb462919cc7221656d44c8ae696378ef15cf7237a0201b585b1943cce1ad04e4991f832fe7bc2c5ca6f0fe0ab5813ef99b7f722761f4a4979d1d778d66c1de075a3fa6a3de771b714e6f9fb8dd862e4e68d672cd8d75e7a3e419e7cd0c6b4893b939feb5d48f95f8e69eb0a5cb4b38cead5b2a59cc0bd9bbd93ef1f60bde61c3a7565d727f34c0fbcbacca72986809008b795116175cc95fe483c8b39de763a66ee517d155faad2aa6129bf9e2aa7a9456e2729936a07e52c53a7a3688aa7bad1944e41ba23da32e6d68037d75cf80a45124728cfe5e8716200cabfb3c67945b32e6ab802ea5213ef8c8d29763de59f27cde253396eb85834c0ac6d7b729f51e9fb5fa0731670f83f71d6988937fa10940f272f27ba35406d40103473977c44dcaac323af7129f3decf32c9e56a24766580c08b6de7967ebde1b2d9aec2d2f1ca27949f89d39b13dcdf0437dcd313fe7bd4663b4b88a82334ed279d7ad5ed89a2bb9354c02f3524f5f90d7cfe7aae569e1d661acdcd74fdc749831e6b089f1502465d9228b195ad02816310db4fafed4f2f3721706ce21f614379a2362027da2528f0c6274837189bcd261584d6c03fc496c6a8e0d2d2777486550496a1d1547cc7bc6001e24c346041331c899e29214f88872f3a420f40d13f21d71fa3926d19b14870524afed4381bcf67ad048e4028a4b7226d0e833ebd884d410c18a1111d72933fe33a628c1aebf3d593cbbca7108d33d2ab5d1ad718dccb92528ca240bf86b5b0f094fa5c956356b4d56b6f8dd4ec747ff85841cbbb080547db92faf1adbdb0907fa65c2e7c6c30b57c9ab8f7b09677037051387b17c6caebf25f27b58ed19f8818e99965adfc975fb0fa14c1583a643c5adea20df03f06f741e7f19f08dcf7811b11f8bfa947ccc4fd0c3987ce4ce72c526604a0defe4e0ba4eaa57c5b6ee0b9a889ba66265d20ce4abe9062d1a65726be4c7b631196a2db343bc14b8d3364054b93281bca8bd3c3dd29b1a8db9b1064ba57aac56550b81b015e20cefac545b92f3e473e7571a7356309bb75fc2974f62bf79e5169fcd82aec25fe862035e244dbc74f330408d9471a4aea8b6333972f49df54c2108951b761fc8f81bd6a18c6e076e1a45d9446c8ee9a37367e4de72ef70e12ae922bea4d3464e5b464b9326eff530316f0d12730361e275982954399e3ab2e13f211aeefd04379b68d3806fad62fc527eba1ff6527c74e4fd18db4e00dbec2548f7cb4d736e98b76d48c2c9cc325df55b8dc9968e476f080f362e72886edaf606d21c4587c69ba11db770d9127a8f0302e6f95c9d97922dbd43377249ede682bf8f1e38dd2f1eb35daa29ceedecec72d3a3cb4e651c69090482085252c240500dec31eb627edadee076aed3cb50a135b7d22a8b58cd43f1ac99a672b98b834cad6051dafd1c8ffb2003636d21e8dc64b50cc2ea7a76041c0feb557294bf2042e909855b4f74750bcc98c3bcccb232cdc16d4816d82217f561239d0ebebe3e278fc918660da15745bc04c5ac354688d3635c2dd74311789cdb25984bc9e99a57e590c555fbbc56acf5202d9acc42b4eca9de53289a9559f85571b53d0bce7f0da63269a5ade91fbc965f97d5edcee085251beaa301f3da07b2cc070e7a03d6fb3903b36d4de720df7eb570b6030082ad68a4473a5fd38be9215533727040b2c0b644aa09cbd5f1fe751cc9852ad4c667d2644e53a7953f4052b7fe72585e1bb01cf41221d9648db5e81fee886d880e267b55d660efe65c6ff0bb210d2af8278d43ce63ec4fe828d0ecbeda48c252335649a2a3495e89c9fa017cf17231f8845b2e0b4510f7c608da2e483186b24374b8ed51d31c59e993b1ba6a7172e9446080b699c8fdaa5b57223ddebec7c806dffa9b0074cbe7efd831bd1ddb0ff0acf1a7caedd9ec68cef0895939ec4093c53752b23debd1c27a486140f45c72078d5a1ac0096cf80e33893d93173a31410dae309bab8d89eeb269d316d30d70d577a16c1f34a5e6e247d9736a06b46ea7f89f692ab3e8d581e7b1a0017dd465289f653ea930666a4908380b97b1d8907bd961050e9c12db7bd89dcfe825343082f888711d6ce82b38db66d9c1d7b52c9ffa55cd1e5c23a8fa92caa2fc33191468ff3773f5e56383b96fea593f5c571be2fec4391b644f3347687fbc7c13760268efcca9b53de8a4fec7c718af87879822866ce0c25966e535cca6ec57240061554b5cdb04ad7673269acd082e3cb42f5eb423af2292889def8ca90ee51eeb7247a253cbed81a20a12882f31a01b1a814e25bcde1f509de887f5577621cca5728bf4273746528aa32e47171f84d774df2ae1a981f2155ca624a3cf0a50913f056b7cb2f2a382e241398574deb5da083995a0c0a44f90b2ee4ea25e17e500f83d3743aa078164c8e7fdf7947451798191293796f77afeaebffbeeaead9c81d03d37e91e72534741146d4bf8be769df2142aa7997573abd67d37f77cb3a935302154a9198bc2efd923582f07e1c9c634c11b6a601f75f4cb9a50d5df767a84f944e3c21f21f5234ac53cf6a14cfeac7e46b1dadce034ebec6679e0634f52a3f55c2d577e050a8735d169213e3468cbec76ee2a435900c6232631598d8d9a441d3b2542faece96dcce5d0a2ccb03422fa2c5af4a7f67a050f3fcb9a1d7488d8e2612d348856aa0e3ceb06bd4d35af798eb6911637f279ff0e20758bbe4cce3f4f724db59c937fdeb0719486c938ed6511c472d27d53b27f6cd30c66f5f386fa4f7255990aeca498ed31f21f83442f07617a67aa4d8220e54d505c58d9fa0006d172e66a007338b7e668c9b78a40d4ac8c185d4bcab1933c9af41e415b47243cab72414cf001bbe3d63761b7e7626d758f27dde2acafc16fd506689fd4d6fc44147272eb00e91e271fc84b796726fe7bb8cc954c24da56be22de82fa3a0e8632d5724ea3d00d38a7ba49470f788c9b9c4a64de33164948d6376ef19e6fdaecbe4b698d66a005e11aa41d9c33cb945b9e45a84bf6e03db5bfaa7a13fab46fac52b302552866b6fcd89122858500b04f354d5ce5225575b0253394298d51621dcfc772fd04f07897535d98ba5e2bc4dda5f50244355fa2960d3c0f0db6c3533f0fbc728f5f718ec89539dd16d9218751d5826e86880daf44b016b31d7c6173479b767215a936301bee24db90aeaa2c7c8440d8ae394e2f9fc0d30e89b12f6cd482e272496ca48dbba94e010d5509cf08fb461d8334be246599bbf3945f3b8e76ccfd6896ca92711da145d1bc7fab8cbb43f6894c44b17d19098934166e50deabfebf722add78422226c79a1b44c934f4086f90532e9edd098ac7c4b29f33f5731d97729fce1a1232a9f0ac660b24706810f72e8a25ea09b30e0cabb4348e02565741724e5dcc56fef6bcf29ada458df597623523027403603449f8a2463330f0eb8b22005730641cad200146c33ebd7ea1bb5254360675bbc2edc01ae977ee141d14027f41d49abdcb993bcf15efb847a368d660924a70fd4777397a21b8823a22e11da7940aad657c969d55eb0dad6c55b93f588adbc450e779178874f7c61b92597222e4f14a14179bb0510554bcd86ab399568a90aeebb61cd99d0e6627417c8343570d20fee2e5718df460cea774d5987b74e1e8111b5e944f2a3f583be97a090d269345453f49a909ebd123450a9cf16487248d8efaa520433c7a56746cb8fa72594c6c4f444115255b3aa9d41f8ea974c714634884aaf829ba4038c8ed2f3d724b7251fe7b8e610034d7efa421fe0d63acff2a31185c79e064e7e85d79944372976901fda69aa44cca76f5ed0534b14b17952c67525ed9df9ba4b200adc8192e95ce2b3cc083533f81eb3da5d17711fa3cec85c44fdee5375dedb4ba2faf7543ac8ca83bab9e93b106821742664a417f51eb754979ec5ce70a50c6b9c8b80172b24932620656fea87ca331cb5841f43c1b568fadca708df32b4047fe6c1bd972cac8b6cacdf4d493839ae9c8e79fc23dfe63eef93872ee3f421967a2d372fc708ea3a6750fb24f80a6bf5c07a678757aa15ff48adaec1d287270c3ec0fa73a32ba0e6f643376d8fc3b2114b3db3a0d7dc0bf25fc7bd4b41b99e54f6c12cf7b35979a5c4abe038b9bee2d35d469227982e73f074dfb8e85a89d5ff1d72660962c3a6ca252acc6272087fdd57ac3e7328832c16bc7516724f36aba7a0a6a0db872a8e978b7f3d4d87e07f763c22961c85de8a556c8cfb670fe9569c021e2682d3a12b93a147c074aad070e234e1b4ac40cd96a5bb76edfd4cbb8afef8fa466d3612b6e07de0e2ae640da2da25ab273d0d38eb9401e54144e1dbe9d61f3407d1529c50c7090489326c211898200f4edb9aa50f72b92678afcb82108bee3f51cd1726c4b74d8fc7e82d6af65e037eb4945e7501049d9c5c5f12d36e93d6c1f491972e053822d36fd4c3c1989b904f10ffe38140ae3d9c9037bc7e91fbc34fe92e772fc3a1f79504a164486ccf4b2d7aeafcb9ae6f3b2f71fbcc6858a89a99e600e724a02b7ff480839ce03294f15a2d15533036e3c4b21b8ae107c6cd8274d429272eb80cd74f0c9d557ede286caa41a589bb3f1a6e6418b296c0253adc2b143fc728078cbe7a3b0526ae6610ced6aefdbef44f3d7e1030cb58f72d7655d52c88c72705a44acfd1dfb4e680d852ec6aa8657ace280614c47e0f8d6f9f7a6e94857722387a5f12c10c24e5bd2eccb3be7fd49e82d851094f3c5205718bcac443384456b22b4d88701763e7b40cded1ab3377ffa8be9f99c329d8abf61883043f08855b4cccf09e3509aead46dc7feaaeaf3937de1a6ff4c0540dec90df1c577477672ee83b0070c717c4e1b81550b8fcb57e7a39ab5e3c9812b4f42bc731091929f7217e72330a7cc76f30cbbe3f1ba950f9e6ea168d96a44257b98bf8bb16ec74772250376e9863e88f7c4719becf96da2afdb7c5745086f5fe9b7989e65c4057e72917164944118e4861ec74d3e220ce18f7e51e67479db420d9ab96b1072882e71ee537cef171c76250a81ebc5992d258fc19459ec2ef984809556e141b23cb244adc1da9bbeb174dca4bfa7213779450cd7eba5cdfb41342be9f630b4596cef3ef822df93a0db9a11d753ccd87c8ee329001c9ec8cf97e5fa9e7a76de3442f50320aa1896faebdfe452f1eba0aa3e5dabcff52688a219539db49c3adce059d4726fd9f31c26d0f27f6b8408da0d8c21317cd13b02554d0a9800f9b6737aa2c272cda4fff2968a0850f0fbef99bf6ee9d8c4e9f86a160cc48d5ff93a9ad829a66245f8fb39c34a3ca636105b53a853ec275164d56a09bb06e6f427a45437dbd0727000840f75cc0419be8caa345e8b369b69eda9aea2ba7d952c91d4a697a0fb2ded57ecd93ba6708eb36c6633749f8cdcfe84be538f8a85f92eeebe28a003c652d6d50e3b522b954213d50e0e7e5f3ac55cd38a9338d40874cc10fbd4ec8cd772f2fbc05797866bee79fe1b8773d440e5b5624f625c280ff34c594a8d8d52a20151bc342c89dbcbba446310ae7ab9cb8bf46b4cd547ac6ce40e5ea4d316718672ece16b5f348cee2eaa953f43e8836bc8c45d6f39b66d7b6beef073fc51062f50ffc4dab9e595312848cbf90c559b0b5c10e8173aedf1f1e05c4d608216edbe72c18136406a135c7bdafdd7e6e62421aa99ba97b7b014c6914ea3832662808d570bebe0860682c75abb3459eb5aa8491d0d28fdc8a4d56ba2c9f8e35236de41261792f8ed3904b2e7d97731c0d02107c1d6b2010918e96aa7d2d479694ff23f17f294940fbf21f04f1665cfa6f7e3ab64b0413525afbebf486ee8ce5d0ce3d319ca86c96efe869b01a1bacaf6d302cd6fc2bc04503da14cbad308b8caac9bf913dc095fcee4e00c2bb4ff6fc38afcb078f474666cc220479351e9fe949f153a729401f7b9b23f11806037d99ddfe2f4cbc83593649e932a1c7ceee584cd7a7072b5e7cbe90ea5866638cc1aa85ae7cd451e37e751ff500e5893629e2726578e727e2cd441677e5aa00338675994a9c64881b9da4e8a31d68ecad95ca8233f33727a508549c2d687a38c50ff9f639d2057acac8d8e95a2f1a6b767ec41c2c766293a1e3b0d02773ab22892a8a0924209b412d1594536b3a769f888f95954309e6e355a82084990a26670c815dd4b429f314f0fcadfc3b3dee35a1a0d1165de8109dcb47dfd9c75ca3b3b75fdaaba746790ee1bc8ce01d9276ca70f936cb5b3bb09432cfb25b0f3480b229e0a16df23c1a5b2778e6d8cde968c61a7bc2634f5da72fb4d04e9ffcff6c33776d8c288488e297adad8601fb916953c2f42c88f128a727fb67a3a3a2d38329548b8258e87a9373c4fc8f06b87058e7b55697721db2a7292a743563232eea2e65f3c44c015913f2564a6599a5f44e59fc0668b58857572d5679e387c65414eb123a305c04d737d0446007f3922d34c9b67023bae475c72f690b1e640d4a0a84d0814c2fe16e255327740ef2899fcd17f4370e638413072fbb1900f6e4b04bf135d7b1ee4bd032641d05537e13b1b2a3976401204af85725c835e65344e74505185725b125b432e253bcebde21727bc1b534bab8e3dab4a3d2d9183d8a4a6500ed2609753c2778e19abf21c4e77f016a0618ce4d483320a839ee89520e6c782229ba4da0fdd00122385f55e94d27e3a5e5430e69fe5e3293169435f0bda8fe4c445e11e2b3273b24cd15a12dd6706daccd0093133c3067241d2152771056c01ff87507e3da9742723236a434ad91e584afc0279e876752110411dd9ea2276a10217a2f901fcb6ffffb9f2108d9e0cb38d6fde7a4251a672f8bcd326d44d965d83d2e3b4bb4620f5a59dd0bd4004811f6b084356ae8c98729c90f0a49420cb815fa86b16e7183a216218dad980c5caebc31ee83d29283f72b0e50ed9fe952a3e1f68140a59669907dd8e78bf859a9e7e04a6a087c9485172a6428c67038b63a7f979cd65117e2775500f4d2909a4586030d5fac087bc0272ccedd50c5d74ebd267abd0cf84a1bc72d3ef023b310d846d74c88220545d84723e598cd550b20a9e26d02353d35fcf2762ccaa7c081a7d17d5fc7262a7a29e71cdd5454541110b7724346da3c198509aa88b03db1d3031c51aa02127c462690b505770058cd8b89d54a9e4a113873c121c6fc15ef5a1735029bfe125fb736072c078abd712b6700a16f66aba2cbb67ee711281cf35821180cc35a21d7d3f737292abbd93dd85bdad41dd5743307c56a2ed77d2fbf58a5dc2bfef72159a11b47267cc7147ec079080ba9bf0291ce96e706b1628e54cef87b2b944b672c2c3e172418ab2b150f852950136596e2a767576f2a3c82b5fb3f7daa6029f70c6a1df7203067cb836847c102a76dcff3d30f9c93ed55c4126004e6eb06d8240852e5c1a36323d2efde90c4488f6edbfe0c15f9f0eb865bc8c466b4684fe3add7c8b396fac99b53897bd57584902dc46e0f3065e569563775dba55727ca8134f4c69b872495e25e62d5d7995f6c9f7ae305498c62f1183946b9fb7410eea985810be807299b56d914c30c2b25f37fa81950bdff9a240f600966fda62337e9d2312ad9c4f41ee4b0274c0c6137343332af71062be72383637549e5bd4a2556fe30bd78972e170177df585d6871dcc9c5fe98c92037fa48bf900950ac9348bf6c3065ba8727bdd50ba06439e2a35b5dbff0a33b978aae0518269fdfac094928fa478010365ef7ce49d629195faa64eb64ea82686a4050e959d1ed60818644f2ad90cf21e72a7821cc5c9f6283c6a7ea77519a9cbb9f694ce623cca55e7d31fa87bbd1c13724fa656881deabdbd8f0ce8bdd4786d048a71249600256c7d1c08b943a4ea8d72e7831982e6de480d7e847c577d1ca94caf583c27ddeb862d9ad16265e85f824c53f64883b5a189c7c3a07f35f0d6511e73fd8adc4d661cf614643086f3d67072cf3dc41204724a58d00556792e3f8ca505630ac1007eb77d8279207300d7b9723021634fc61c5154e43b91514d0a284961d0beb175b25278aa0170447d89ea726136a63bab66a8b6886df800d7895db9540d5bb6531a9833e709b0d438843747e977d5e2ca19c833d3a6cc56fff1335c4b67cf6e8ae26b00a3671d93957f0672542ebbcb36b5c316cd76c3218c9b9c3ff4689e8a75396f8d401686c2132ed072fd293102c7eb9abced31e315d2e415c91f2d1e11f8731046dc9c2b5755524672e5c454268ccee055b44ccd59235e7730a992f3cc0c39397c6db1c4e8782b7e720f97c3783b991cb8172479c833e9886b15576fdd78d1b6418882b333d3c9d9729948f212e7be98e710b7659fce1000c5801c02b83e22223a6505b7b4dfd9b672e8cadb23d274610045a00bc828063b76c33d11b3aeb4880c484dda25d2ec876e0d67311ec76e8049b66e566bf90a192793a6f9695e4579b63627b4e5fb94d74edca244fc76e84ea0e9898d6d59690e4105770c8607ac9664eb493fd475c15772253bab8e920ffa817671179799b667ec4716629efa67742b22df92dc65282d6ed7b2743c044b717d0b0fdc0625b3719188c62fa97be0472248d12e039d2a4c729585095c42422c933fad88898b8673aeffadf06ce3c44755463f58fa18149b728512b52d317744a7a2429e63caf864d1820c4fc37f282f0dc99c8128ac6f1f729d529ff902b014ea9c8ef9fcf5f19f71fbcd85a5dc85bd56c8b78c0d065e8838725bba6d90e65ad151c8725742ea5abcc645a66e294b2d0309ee10304999e753d77841274413e8795817bfdf81ffa1d5521bdec89202510835b2d98c77a1130236b5b5e3294cbdd44e4b050916fa8855378430c72abbefe32fe26b960423ea72e294a3cb7b490566299ffef4411c11d83af9a497e39c6f8caafbead4b4e2d572eea9d3228357d3af5538e1459fdf3173bb678b43718d92329006177bb4ddc2727786e853b7b353e6014217cee136cbf9bfaf589aab77dd2a220698f17c929e727a2dfdff13e326229bfa76e018f5b6769018ac18e9b3efe4110750735f5a4d27d442cab514a6e0a4e3a6a6c8ab2f7dadac5a3728bd7582a592af8c2166c10672797f18d969c4e440e2d924a0e8edd649c90940b75da56f4601b2038344fac900ec29264df894dd0358ec9dcb8e33e07027fb0dc0b0ab4aede65ef2fb7168db40e32cb8969b073d8b495e43062f7a84e65f699c57719aff4414825dcec1ee7e7281a158f61ec76e90d0c16083e9b5b2486078b774f6d0b5f1dac20ddd3fcf190dcf35fefc0c9b6f0705fe2dba613256e65809baafb786cee4d15da812b5f4ea0fcfeb48298b50c1a9a40fc8e49583cca6a573ba4610aef7765fec5c85a228ba2c4b7e131016163e81a666a534729c731bb9bbba1d884ba114f7d55b759a05be72b9f9d99bf3d6b98dc702b9ca29370212a60f21807058370902ddbc6a73b9284c4fdf35e8dd177355013e54d2071694a88abd1ae79eb886980a7bb0193e7e4335286b248f2bf247ad1b310e7b05e9f6bfacaf5b92e37e56d613bd450c065c724a6fd7aacaf7561e80ba100c6f40a13096fbb47053cbd1787497df213af868cf720244ca3a73c4d9e7f901b77713fbc9834cc8dee01322c56f3a237ab74d431d72eb25177416c8ae575521432ca983919fa3543b029bf8042fb575406b1c84a0720fded20c1859d014b27f91305a8052cc7343e4766ee027af6a60a24f4b203636bb6f70c440b1af0153636ff2608d1e131e29d45aed04b8547749d628b6e3270ecb75a7acb36b119ad07821e7fb565c0cef7933b619ed04ba46eb4634c24da272dc59617e3e3723f7419457197953117077fa6c99ef334e1d3325f87f55952a72f116ec641a719c572daa2b4f82c6ce6989170cbabc9ff865295d532dd2ac8e719992dfe45972337360b517867354202aab254ee534a5615e9898172c94349172c29142f61d9331efd01c6592f1d38534794ff03dda5fd42bc49a3ae38416de72897116a8c991abe531a3489aafd990c0724950966d69f948a9cee3c59772a872229252bfd60117b4d7d11d670db57ac9a52a16c2543d3c6d4cd341e309dcb37204cc28e4e2f4f2071d9c1a6af74d886c8c797795ad8cd1b355a0336ea4282b72da1f752d0bce2631dbb0b9dacfdfce6bf10b4844c965e7930ac91485e810ac72fedf4e837c5dbc6dc8e61fdc02405ad500c38de0a3b0d99d9b9043addd59a772d4c89ef48fab8bf8e47f0102d0ac8de2e7cfee3724b2095958fdd570107a18729a8d5bd27592dc69292e73905514b2d222431159c27cf0d01d5c38c1064a3b7267a11cdaf12f2c355d9c7bdda11e19978ad52d0722ca44a95f72cb27d5e42a728c1dda49135086f51156571d28fba5e3c5e7096db4df107d1d6773a89a5aa37278bfc1c3740a10a1bf1a6c84df7445b7c65e93b41a1592fd05abeb832abc9472c6f8a478e2c66cf0ed291807f0e83bfe1b2daff541ecbecb8f992689bac6327278b158de25324207e4775ba7a4ae64a871db1c46a887f79fe4b69634292c15728b0f75bbac33de751ee25ce7d4800c0516bce374aa4eec3db89e0e1c8dedd02a414f5540a6ff949694b26b95e483ff17dc3b9b66723d1721675cb72e5a1ed972797006944ee27f3b97e7f4f5674ffaf79645e2aef461a70b6dcc0e34eecf64726b82e4250da2da6e2d686f9ba687bf2c1cb414f5c8b9333d1a4487441c88fa3e0bd0e5cb0e199d0fb9a93db2803610f35d9e27e6a92e9747687db539133430722e89ab11da9c60e8c1dcca221fa27869d0383dfebaa484d9994acf795857fe4e8a50dfb0b4c393bca85d0d792d1d7c8a774d0af89cc69315348fd0a2f3dc0e0223438688e0c3f7d724751173af5912c402ac77ca81faaeab6f441f17a3c77b72f1a46ff95192ebb6bb00e33509b201a827b6834211aba27e08425f72c3fdde724bc268fbe019c404c6611569fbe203c110e98c9f623d6951272dadddfe435772d0574af5a1ae0e1da5db73c4816a28ac6d0c233d99f46053d97e2a4b1c3e76729f7694e7fce923453a4d8bfcba2c60d179c5b8de01ea4ce1f136744ba096b57261066f6db3ca41fb248826bd930210e8a4a1cab656c9f71e82feb4417e044c727066dad28f46c2b8201d7b227aa34d6649381eaa4769f8f0059f5739913ecb5b92841eea47697e1f4c3943d9fad0427f81dc576cf918959e31ec027d1eac4b7274eff819a186e39bde9c1489fb036ffcf6a429cdff0f574ebd2808dc8839e5726e81ca2f8922f5d596dd91b7f219b9f5d4fd147002735bde0935551c934f03726c31b12063d5169a64677307928536f171bcbfc042e9180c62cbb25b45457029a2f50b51b062778b9c67cdaf948f6dbec5385e6c79d81843b3508f9cd12ed172f4d6a1ad90e501b1cdf2e1ac22c3da5004c39b1b1c55762c188fd29df1f06b34a6939ad70bebbb1705f5bf5763311304e781ac8c10fdf558b71f995923c22772655b430fcdf5dacb037d688c8d673e1d22055075368bfb10fd60adc83cc59c0351fa32f451b6ed25eb7a9fa7ffee2b6861b0f409b7b04ed23f7e4d4ee1cba47208b0d1c2f9d7b4cc5f13cea07f29c9d4b528964d9719faa3b3c8ca662a0d787278b586666907e39a55f9924d693d94c118e6dfa5444af30f0a3a2c58dcfe78720871743d16401ace91428cc68f8cce260cadd682a79f3c99748646f3a68c405a88490ca48b19e906bb27b6ea3331dd83215b722d4b6def02cd6b2f673695f81e8dc366784ef6c3e995d93766e41a0c492a9277b362f419331b8d0ef15576547245fb171939820d55b15227db70c3887001243a14444938f3ab5ffc74711533724388cbab0a3af28a78aafb98a5139c023565bc2e97e6a166f9cac551ed22347224f53e491e79d0eac6c08b5854a95758b328fbc07fd45f59a594ec3f32c5700d5e3f6582e5b750d5d5c27f5274923dd0bff76c6ab4a8d6b6c7fabc45298b7949795c42e299d66bfe4d6e42bf59a8d4990e265e2b1e478dcdf9d59337ff261b5e961939f4a90bfc425ed4f6fb4ae388782cdd30754bb68448f41a29c9e8cd5443d85e1b56b51d0c5d3b42835e858d963517e4ed0abe00b7998bf16d6aa1d19258c18bcff69c0fae235849d5d830c45b8be1887172f2f41d229aec33f7e13725724ee64cae0d2b157c1d12bd93ed2721394883d423e4ff317a38c893934afdab7205e9def197c9193de33e67dd2115bb3107d2c75a6050dcc6db08c01305f32c722b70114c12b06a47fbe3a198d4c5ed9769560ba9e3fa0182cc970bb2e21229721d11652150b4697587fe3190a3ca3da3ce6ad66df836f1bc49023992702b7572d6d154de3fb8632b8a93d619719443c040a6d38e135204dbd0fa2182aed34259e31b7134f46147e47ee42ca412026ec31b7c3cac2bb2316c9fbeb65aca3cfe72c9c50249e0fbde05489dee8924acead4902175828bfbd1255af5ed75661e705ee07d775ea1b37304c6c71b002f8e27b421e525c9843c3fa4d3ccfcd94cf84309580d5ed0ad14146440ad14460c68d8c626fcda8e9a81930209a500d8eab2805744dbdf4e803556e502d4c529f7d06f7186f19ff6e5cf11f8443d6439442610725de4ba9f462a4887430eab0564fc25f33659ef1c279f42f2f040021b288f2d5b98c399ad4dada7f4b7efcbe313d8bc83aa45ab95b699114b04480155b7dc80720edd90eff3401facf7d88ccb5958a24946e80937d1c65df8f6769c6daea1bc19ebf9266d8b3c2af0ac00f02c717b50f7075368762cc43ae292cd34299cebfb724e8a6d8c110cb5d595f8e8285fdb898a95013f9549bd31f303151a96bcc76e34e09ecfe16cf067395ee51b9d5f6a1afb09512cbc4249b4bd360fb0d0c789b105dedba8278d19daf1c2c39d595457d115b96b3981d72c328c2411e6a0c329a7105320cc39ed9b5d9a0a7fde2020fb02cae55525043a649a88c513f6bc23d5207276cf17dd58fd379beb260336fda1c19628bdad7df9c6fa56c8fd7ad64e2c297230d1e76a36528c25752b67ab973ea97816951b046c5b8aeb9265b4ceb4f6c31032991ce3d64bc12d6dcf8e7c731b3566dd9e58a1afe91d87dcaa7092909eb70135a0f90a28b45b15c1554256ae2a534bf10f33782d91c2b2cedfed596154e54d02d8779a700bb40a8edb8801920461bf7abccb441187f384d15a36f31ada045e62c940b60d80e7aff16db4993e0f15e43fed21ef5ba88ea52aa5fa70a4325028c33dc6702c0371bd75432573b6d7c7b3c625eee4ddbda7b861f88e2f6fbe1b59849fece06e37c6ab50f8d6ffecce2394c225ce70d345b326302694de9ffe3f2490252ed8dedbd97d1c6e6d868fa1a4f30095fce3f509fd6b17a27758ff53f0640cd548bb42242a525e03b47aa2f8e659376b797ca6bcb5da71c2a54f8e214e22ae0b1d50bd2afc7f0163f150efd8a47a38216325abedcd01ebfbf7784c2ec472e86b3462b52e3240afdb7348a0c82d38ca37bd6ce7e07c9596daf938f64c3e0f22b7a25e59920eda4793017c2243bba6f6445a670e5587dd3680cf07a597a672e81868b226b930d5f16cfaf11ee6114234fae483159b9a5ca4c1498058d2542f74322128afc2688d1316ebd00299d34442a3dab297ab7fd6adbfc5b55f174c10aaddf508630c006a923f17d84cb43707cdba51288a452c40ce99b14ad0349c72fb474396136024685688b74b32176047e35961cf8ff481ce80a2d32e06528c7246f1a0cea8a06a61582af2d1fa451ac173e081decb925345b8a1763a4fd671722d5f31128e030f5b25fbaa4ee36a223338a21e2459831c4375d0b572d49d9b727abd463640c9e2bb08c4fab6d1011515c05691490f225b3728426ee228ceee023c3dc8d17e70175d60b56a927375133cc69c45fa9522c56dc5dae20b5b8fbc4bb79674ad23c07c681a5e110e1b90143dd0e7ed5f275c3dbe02b51cf3ae302c50ddb6bf8a446b328c6bed19a4dce9d1871ab854219fb6ad8fd206f14bf2b38c7279ce3dd9ee007802e8f56421b2062f8bba062dab27f221d982b44464e5357815a72d0e279b3123e98ca4dcab47c70c2fc5af54787c5c41341556f190ded7807215ec53d141b21502353f141f90c414705cd6f073f704264f1cd4eeffcdc5ef72239cda58bca7a5287ea0aa6be38f191a31910b2c034e909c53df6163650cef65aee250138ac318989ff74345042fb38f9b67e74fcb46d7e8d6b3906226e670508384af25d54d2cc40ed72239578cbba05499bf6f583b403375f4ca8fb58e70609da521ff4e9cf9b3d24b78f1144696facfa6723dd82f963a05a282aeb0e62872c8c7d001b966b0b0cf95949c38096d7c7d1b6b9dc31291963a0b234509e9a972c74a2e1cfa6b1c3dd09ea0a61ded1c9c3286110a5a569d7b2e636c0b5bbb4b41cce62b93aa67034e59443d570de8a5cc733aa5474a210b464c45156488a32972b96275bb7bc50abb1163827eae19b4ae16a3ab433446d05e20f6b21fe08dd372acdf74880adfe8e8745f4b46da16eb0964d81bbf55c5827d6ba9486b453c9d7293cf5dfdd61d0259912f72133e5099f9ad7cb95adae007c9432a6d328905f1726569823dcee5b9c2042f79370d08a3e120c020c2d8f4c03dc5b290c1a7f9cc722b8c379d798e6e8dda693b44ff74e4f4ffc533d530c4deffb3f4b5736a8b3b4a1cd1b469b45570a04aa97ecc44745c7a0f46ed8e8869ad9e4384804c4b6f306e8482b99076a5332c674c310ad4238c9f22afb95f741dfd0f98be7be0b8675472cdf411baf013d0a01990c61d2f350991740cfc4037312eb39ff112e8f423d07217e86267b3978b8bbb25660366089110a37e898410ceebc57fa0aa620e0c4972bf1ab5351f4c228cdfc210c94754b424f192807117ba435029a64ff6b910fc72e6e8eae1df9eaef7da77a4fb41dc9f1c7ccd7b27275e9b353844b6c73712041cc092883a02f7beebc45d7b635854265d8b002e7723f0f12e96cee77fedc4632f49c4fa3c7c1ed8c0a7df8fbf07720627e8a0f99fbe425b3bb0a961c89d722372f385a4027fe24286e4bfae31dbf91510c3138d036d291f5c40a861d1d1ee47320c6a208122c91e2d85e32da77597202131cb3c0c218c6ea7acfa2780d9947e5e758060eb4e6be3f496ad0b5fdf8d657a21caabeeb84743036fa8e6fe4e7a1f502d8618f64aa47327cc8b88bb598157a61a4791f6941e8c76a081a7a895165a727a32e70726731620d52d1e91fad0b27c541f7a434e4cffa783d4b78e063aca7285df8891b465232f396e7c55b74e5d650e2fef6bbc86a4eec2390dfeb95bf272b6a965fb4d49cc4c1ec0ca40429aa2917c2a02b926bb59eeb221311434c5a1484c05ce5f306d15118ef7c982885ecfa385a2a13c71e7d578f22c4675d5e79772931ef771096ef23e02860f8206ea52fed6fe26965a3db14339eb6d94e233d27254d7113630b676faf80b5af84fdd6ba45b14699d43aa8092e6539e6924a3aa727fc00158ee73b8a040aeae2cf8ebb7def934db8b48f88873d327baca6b53df49f0a1499f6706cbda08848ffb9d2639e802c5252cd4dd49bdbf80798ee802ab2626e5629eb2fd364604358b8d38f0983a04a0cf5011ef037e24e70c7dfd55217232e08daafbd7f9c7a06af9369f2c106a008ec86d71203be5059ec0d9373966699ec748678b9b768dc4eaa757a8f7f2f31fbbc828ddbd4044d6ff348347aa0942d1417400166fae45553872357b9b58e921eb268710597433a02280a5623dca7235dd49b9fe6ea0cd869918f66d4df93b3a94dbaac00abbef42d0d3a377fe047256ceafdd8ec67a77b6df79b70a934583c8f2d87c54f350931da18c29be38797298774d22f8c6d101546d00a70a9beb98e90dc0b19ac722ea3eae535f7c65731cbaccf7f11662421b49312c9858ed2ffcaff00ea2a4ceef20e88e6316c5d41851226d575f7a3cdc92c612d54df27007403bb3fe685e0d70afe2a09504033f06709b15c0bbf968065ad65351060223f10cca7384ba3da2896d012aa8ae799d166f4f1f0800d2b2445ef1a8a71cbc41575d39c22b8c635c706e7911498dccf6e6723383a8e610b6d4b5009ec4e7e59dce87816b7a15e98df6110977f32a81f633727369ea60521bd1d69fbc9a70dcbcc6a2b919b540407ec8fd48afa635f3e6c01409760f6d4cf5af8266b3c4c12c359bb2fd55fbe61229b24c6b498577410b93054e11c4c5f2a99ff482ec5d8d1164e9a832c709339272720f05e687bc00d3bf725381dbd43d89ad84856fef2426f6d87790713b90184a01f2be37107c6e94d830839d93ffa2aad6ad1b4c687eca21999652e44494a4f6c3757a55ed2b3f5d6e183d35d0f9833dfb22a7b9662236d6be698a1ee4656fa10d372e61210855a64d356e4bac48620fb14f1ccc08eee1dd65d00d2240ab4106bd9b49b73f340df61c7235738bb97d254cd4f149bad3c3aa5cff78abc40f6b8fbabf4eba329f929c2472c15e7cbbecb62ebbc86df636094966cfba48091cef923bfbc84e81b741a57f5294abb26bb1136a79e837ce963f86bbda24ffbe3879da804da9cff71a519cd6729e12b2b38c3f569b13a4ef052828df21d8ed3139c28d501e43b03bea6d5b227268721f8e598fbbcfe39428ea037dcc295eb4a63b74aeb28538488856742dd03ffc2280ddee80d1e57c0a605e36f3ca4866b3afd68be94584f668deb087a4b20de3769dcf9f2c0ef438d01b1e5451f5f239476bafcf56eb64534be204a0766b61180e1935e004b30803e92e76ac358d8c76fb3be28ea67fb39d1fef34886f0e7286e6564e276c4be58e796a9907dc57a6ab38cafcca951e0672b9b0388b7f45729dd6f1c687a24865bdd08fcef080f00f122278cae88d30b3391a28c6c3bf24720e2862008f87580c3f50aa9ebdb95501f5baeda74e7f1cdf6ef766fe94efa26d71296f3b912f54c82272868c8b28c99e3f23257908f90286fee581b6d82ca8143f1a7f608cd6874016155a68d4747a8071acfb4aeeae866755d60d8d065236722e931589a25c8680104886d0f3b6caaa732c0d12dd6e1c0aeb30cf2ce86f4e72ac836415b8df074cd33d472e6276826f645321c34aa6396772a550d7863b3c72d0785d0f2f7dc22d70d3973265886831896523a00ff4ca845adb73352ae01c2e155e5a1909f8c31e63f4bb10a3d48efe7671eac9807eb86e2f8cd1ba26bf90184594bcaf222bcba75e7dc4b9277d8d18de9131b9c9c6b926e7ca82bf5fc8a44337766f6aee9446a7479d42e6757896e0f9d31acea681044402aae95e71e3243174d1d0c5747a8fdff4c8a3419b0247d0fc5cf512389d9224ebd50dec30c9936f82d9fef66423120044e79ca6445d0cc7189fcbe0706dbc004809a86154ec8772eaede7f4777905744319a9a4b1adcc95b0486c998ab81d5797b9c3fddf1d5501f5a7c23432c7bd37f5da5ad63f91506db8a1b58fb9f9ed8294461bb8d23d3a185de435e21d3076a7aff13630f93dc188a78de6979a5bac32be31975898e1335f5bbc8a802da5a03f5e9a946e00fd5f556df6a48871202abd66152e21c557c9729d701573c178495fb09f76ba8f898f84dd2ea88dbd63d298a4395e749db7bc726665e6a2800f9223cf7f3ee6aa0e0cc800f0ffe6ebb4ecad0f98be04db242a6ec23ecaa6a1c24dbca7766aa700f6eaf3f2e49a5176c3bee0df8b0025074a946e62e776f3174fdb1e925da2c629ba422c5348fb83c5b69050dadbe9bc5181e4203bc685e5f84db52e234582e889642177fec2b830a909f96b0ece6a251f757872c6cb3789cd47cba07d93fa70e33ac976df55726c1acd98e52f92cfed21e33e721bfdd81d4458cce9fad25e56557c763889d16b74fb0bdb1f918431c453d09c55eea3fffef8d00cb57ad6de4da48d55acff9449f2249de3dacf0e1e9ebd6fd272a3f9ad17506adf58366ca76416a0f5f59cb98fa5d6559594b3795401971b7e72f3d7b46aba19e31a7c3da455f1d918a4b1d4ab79e0e2f5d74c8220b0f6d8b071439238495c58d79b237321a5bf9cc67f0c6b3e368d63b039897a7d9f9d001359dc32dfe314892b7051aedd0cd3982d0874674a4b297d8379b96c55bf292b8072328865177fce715890c27c7ad4bd0089eda711ef2e0ff9716cb8f50dc5502972ee52d67f5e481396f3c13bd95b206279706beee72de0e2f3c5034a7e10174f0d65852a45a8dc42219544a8b7886970469f20635081fc5fd403da275cca4ccc5afd03cd053283ed99c56b659faabd2b529158be4c34aaf205ace79e2c808de30ce3bae0e73be538defd1337b8fe492761e6389c118536a9f56b1ecaae0a8dcf122ac93304faccea11a776b94c9c72c324931d8c24024a162c535bf9dba051ab725416301276557c302d8c1e157dcd04e16db9e3ec7a9ada40c9fe1d307b3b477241991b57a6580642ea1f831d7e39226bea57e6757e2323d977b552fd6ed8597231b6ac6adb70509b0d42ec3cebbf11dd2594b28c143a7939647af901c3f00772a6b165fb6f40e722a9c924aa04aadd449aafb513dbb5b63141ce6041f18f8f72387d9e445edc72b7b929c8ded6430760006f291697bf1dd278737b8ecc03100936ede73ca9a5920cc4a692928293936d3181b985024dd01a90027365546f6e64923ffa5ab470f1980f874780712ada800199286d6c779f3ff0fa50d7afcefd7202f1b0e13dbccd15ae5a29b5b64f37a00b7a48af9778173badaa9a231c6a8172e85c79197d67dc72bede6958a5ff6a0683bc18fe013bbae2c91cad53da55ed721fdff9b5e591f3ef8a192aac440ff4a3f18d10a164c647fd86fecba88c66b9727821206b37d833ff599597d1cfb3239995a7b6f15ce7beafbeaa76935a4d4e39fa114de14c62f49654c73f03e004de6732ee53c0387581f0c8691e159687ee2c6e7000d5572e7dfbd39555fda1b36c7e4126cbf6f622f648f2b21d81dfae687282188445f75aa35037022067b02c15e4373116d2ee9b1c01ecc5ae4967e41d17d0386d5ff7a03aaa2cfbb571a0bf3eaafb59f547a22e5b87fad504c152d272584a5e2a51f28102cb05dbfc75cb2c5701e1854b26ec39e49174d1b87aa3f2fe647d897f7c34a69d04926361e601210c5a127fc72a16ddf0225737c7aab0add0429754cc750b29bb3f3dd1f99813cd590dbfb12501b1bf9f8e3c78b884f19e5b72952daf32c8be259c475687a2d55287f18dd844d3cd895b864f07aad5d06c5072d5c584c1e8f32949ebe8582fa19e16e2e58215f5a6c225c7cd6c14c779d8d6723177731bcf439b59c5d0a67559dea8ffa02600f8b0f2f3824f436a22252f186f84cf8e2550624b2f1d322701a69f18fbb4b081b08afe090841711a4245cfa572256f6e586ece22ed30a67792a848b349158d8fc7ebc07c56c302cb6f76c65f23bd8f488ac0f74e575fbca3a21c6e715316a11a87b9e449c432146b44b3ec652d5b09709abaee725eb7971a64b73edea605960ef0ee0ee8870990745c019d91147079e302740364147e5c8cdcf1aa85431d209cd77787c6d2d91c1e998c8bbd721f74a24ba278db4cebdb6877ea010bcb30be2e8d85ef1030d2ebab5957f177727955bbcc46be2cd33b7ccb46a21e6a2668f977332e653c5d81c88db7e6b95e725264b59825d4a4130b0f4d5012cbc30df24745b2af089f955ce2259fd7566026b6e5ae63ce3d53128da7d015143fcdb20f51e0011610d5c0f7650a136f389d60e5aaaa459cb59218d205d0128cc0c16e08fe7f48f383dfbfa5a857fd3bf23a72d9ec2c13375799f81b455d15cdb66c0068eeb30eef2d1d7d97f11ba5210f7e72f0640d5f27900f3d86a6c0afe207553bfbc480d9e84e04edbf1f8c9fed7025314577f000399d6df0ff4f92cd918711778a1c1f93f44543ffc1319fb4bb5b0f30f26c246bf0bff5f51d5bc8a37055afe320f89ad579cfb473f1ded348640fd67214850f7dc039fd6be9ac7ad462fbfb2544086c273821f0c774a6de682ec330724264ab41711729bad7f4794d78b8693d2ef59a1d96336af3d498e68358866613498f59debf88fa36ea470c88967a6bccc291957162e4883e0fe173449a5ba13cc67afb04c0f26bdc71e239f7d6f127bbfa6369ee1bb46fd9d3cf81da5b060272e9bed9b77d964715b8106a02b02979f30b9509a7cc9bc615242c97c711dea73000b727ad7f74ee453224d01ad2e3b41818a6eec957d8de3fce42c69b0e720e72d5dc16579bca120f63ead8e420902b5dea49dd399a767e2a6984debce3160f7260ca9bf1592343f22b10ee2a69268791339421ec63b610681ed976628f98aa72427160a41ca08d8fc75272a6e11eac086dadde9ec5c4a4ce602562b7b1cecd61b454da8327ebb31f71025fa9d00669baa4fabc5766badd9622f7b7ee21c88d722fa26effde9bbc4f087e15b9d298a6e00a029237547d9d4fb403b8b682ce3872d509dce235aa4ada4f16feaa3b7f81b66461f361a7291a9ca0b752a364266d723862fd87c0161b215c6966f4ec1698db7f71910d6f61c80ab475823f265eb2725bb0f2d6c4b1b65716eab23b44450549a72422a5e1119f2392ac55d2b9379372d48fa5ec91c15bf7db2ec3f4795cd967e9da1cbc40836cb7ab5995b9a4ec01727f644173791ae1832db66510ee1271e19f48d945ab9b5ea78322faf8c31a5372328020512087a4508b5ea46ba7558eb0d3ed12fef4ccfcdfefd8bacef6d7a272ad3eaf296d6144a9403ab8e084d87de704b04f999fea2bc051679e1b406d73728b87db6bac300c1968163a8f037dcbae4ba1f4d5ce277ada1c7dde75f863f11760888b20db37e774748c3b0db91dabb8244743abc80c59f41a93360444e1fc72fb799a538a298e7404726a75d6b390068a0ac3f7986d7a54145303acb19dc57239fcb664eee4f8100dbdd02ef3a15900213f5c2365e6183eedb94d5a633ed9728456abaa7a548190874d5fdd4756dd313145afabaa8872ce38daca50bd88067272bb7b0f997a641530d09b23452f5176a342f3284b4377316324291db17b4b7240f7336fc63249b7ef09e1d0f4bf9878857847c8821efdf594c8d2cc8b8c9c72d94184cfe24e6a91efa7a6adcc6494d0a5083502c1dcc57483d1966ef0846f49f6c54dea6252f1728333405b8ff4e0414285e1ec5d7b6ef5470b4ce7e7ef2312df53c033ddf8a6a5767c30d7fa865c7688b203445398a4e0eeced76b51198172f263f1ee104ad505517e7cc0c37505c254032b7ba83bbd7cebb6b9eb9ab25372885c2fd6e346d257b3953b0c314e3fe455a0783f97bffd18eb55e948343f431022e4178ae4d6a1b289780d50bea933c011f7aa36cf8aa1b0b355fee5ec258a72fef9c5ac300e18ad50384be443f25936c35b1208b0baabb87afea98227088c172a6c8b41bb5d0de247610a2e8da89b7f3659034a1486b64e38daac3beff26b72846e4c5f70ad19bceb65a543df8b46a647a13ec67ef71672cc216924d224913864c5941a554cb8c277ab38d45f014520fe79253cb97014e36de85a517beea20da3994851f904a17556b84fda88813b6bd3d6c50ae3deec69d3ed711a29d17e1dd38d9e12f7b6e2f0da3f1223dc5abe5b72e797f493aadab08fd244df121b4d65cc1e52ef88f3380d8d15deea66ba719419a02fe010cffc3e7d51a4bdc4c14f1967429dd0bc4e07bf6c94ef5762fc783d7b4d3f3eb73e5bcd57cf5d92657315725cfd9c48222164c8b57bcaeab7dd0a10bc0818e48e6abf3283844ba13f6aca72c73cedcbabe099923c7799680a073fd9b71c1840d576ab4c7cc2badd6327f23b5d651be195a37e4da1a1e215ce7d4af9c0354b2cc1692a756b615d71a448327278c47900952459dd606a0f88425d2e7d5b61ab1506f136f6b194f7d1d4e3c07218ff7e74f8a4a7a84bb1ddcdcd66fc084c8437f94c967cb953429f3627b79e1e164089d5ded8308300d0cfeb751ff17e3156897d39e2f9df59aed3c417b55526c42b5d94ea6bf02e4c29afa393ab9cae3df012d5be87794b3d5e7a0fd5603d4d05ebb9d865118d560ce0eb76950c7ae87dbe9fb3c5823c6c80a93ed524c8da72b6927660629d2b7945f55ef62e46e230148511f55e2518a3034bc8dbc0ab491079b3a09a533688347216d0b9391aa1e03cd3a90222739a36b04c6356299adc6c16d2971e7cead60ce2d1b3040bbadaa4a0f18e08102544ca8db8e4629a2d1b72d2ba0bcb505a69d91ed763f454c56b8eb448202e9a3a21c787731c84005ee0024128a7f0cfdcad852eb531ecedfa9cf34bca75ef614d034f7c489fed44e12e724e1cc305bc9b7265b58f717797e5b03521f3013584a78f0106f5a3af3937160cc36e9fe94afd77908258331b0c51b578cb4eac9444facb4e51bd18d2ce97de5763e5a7f3a3dd5503d36c7537e484bea6242e86941ffd6ad52127fc7afd88d4726356183382da27172132ba01574d80a8ac7b1cc79963255b0407d00c612c7e727fb154101f00067b630370f3d5486b29e469c3b82dce3c3cb430a9bce7d0f372c7c8c512e14a58cf14cf9ef347b5e6faf5ea6da8b33ec02a9813d9455c3ad2727349d19ffd0d6937cb6228a2624d7c134fc522460248981d1c0636b2fcece51c752b075f5a1a8b318b52da65423c1edf7b1f37ba5285ced045b7cefd8c6ca839578f2e7cb2ba3014dd3700474e625b4f89158e97c2d3efdf13d98031d9217672f55c612d75d51ccc6ffdb5759a65816a05aae6b48005996799f4568cfc1a7c72b9c61124c9ef759561308ab16b2721aaf6f120096801205818a038c209766f2d94d054fc7328cb5f756ccfeae446657062843a944526138ed47d8787f13d137263ade9f53974f4ff12f0f19a72758d66d58428fad7a7ae499df0d398191bb77292a47294138264cfb0656ce4b013b24aab3c793d4425861618b2a347e204b324518fccb56a3acf628895d53f4111064ac68d366f3af385ccf03e55c5c8f0481aea69fb5c51ee86af0750b52fb4978c2e1109f52c50e2f41f4a92e357dbaf1e72d61f73e95fbefdd49a7477131f2edc0916a61fd407c23e1d660376e1495fa772564a5bcad66c44eca4aa9c23244a69545f8683d56d8ca305b93cc9f8256b0f5bd69a172e087af2ef66ca8398474f96ad6524b81c4266ee294f9c310e5a457c72571779df9608ca2d1f2a3dacda960b4c98188f914cf420cff469cb726d71256ac334615b862fab6c9603f7b3691dc0fa98ec7bf7f74b000a659aca0448556c72460532b72824936e5f1b9d01a24eafd6b28b2ec15f46cb9e1953bdd92b71e247156f1d04b88af6f9bd88530e7f43a118c6e1e347be7e9a7595879906bf6faa20d8d149fd9dd5a82294ecf4d3f75d90ca3cdded619f1442bbcd753b705ef39d2b0ad010cedfdb0bd7ae969e5b6925fc50d9176422bf76b0391f2eb626d54aa27250b74d7371a975b62212c6f2a17215076585d8e312ab4983be3c67dc9be9b9722808ca76b5cf4ed42f343c0628d7fb88d0523d68c4c81ffb78abe1c53b139472c52979a983f4d1f8dc4969683fbc7f66d9c69ffb17e8806585a9e543edffa32a5ed1e4b346a465dde0379cbb9323df4a64f749e9c260ca850f3fb424e8bfe22178a797eb7575882988586078d181b37fee1ab3438fc7849be27b1045fe44cd72907685c4a6ffe8ece1e2dedc31b1424e3236b3bc1e4051c89396538064eb4e696a0370bd67c2f73ede995e919d2cd904e0527537e5ac683dc5b697c70723c672aa8c102f0872c10eb23cf3051daf50452427a32e828eab29fd9a3560ab8529128b09f8ee4802ad69380e187238f9467cd68a744f59c9c5f95acf544cd3e96646c43dd83cd4f3d7399834b5481f2297535019f91343f0f395cb05ad09a311637254e275b34fdd8ed52a19773a48ce6a67e45a7e9ddc37bed60c28973e7379c63bdcd705c7eeeb58174fdd26088642c171ba0c27251ad0ca3054232898227cd27206f1e3a74652c0c85566201be9630704524b2868895da27693506b9b15c0550bb4889de5fdd2684ebb5d7d6f0eab8e14b49fd46371fe49bc3516ecbbf4a21e72dfdb9acea9921b11ed0c1b7c7219af132b7eecd291df78e6cacd9141fb234f72876a96b0cb94e0e2b2204474f1d719a30852662365dde0b5cadd3bbfd52610721cee96a76be1415529c7c7761d695dc52114a3c95579df1d0ad21c59f07a114bd83014721cba4464ae55f0a56d12234f1b979c5d5d1555d67ac07d40c4681d72942ffb2821a28af177301c9302cdd92433305e02a5fe9a19d776cd9dc4b8cd72eb9161442ea3bef696df6499cd06aeff982549f69c6adf984b7694f8eac30972e929d9b1cc4ccc196020ac203ed7a4ff27a7973ef8f8ab88dc9eb898638c56598a67143401031e3e9a4ba4105b27a43f55c1ac5d07dd2c8be6033a15fb277a65ba0113253ff8a8535e1b72e21fafde43815bf76bbe7196871693b1443d1efe7246b89c78a0d227d398ec79bb3ad77b8a187eb0f1a501a5997520b5635c3ada38378a414d48c848d5d604d435ac331e3ed4c2a01d08935601883c5eb5b83b8272bc84111259de84f3710a3ce674451eff686110c67b16fe5a6ed2a7391f73f3720ee12e37acdfa628154328de0858515a7a808c54e235058ebb57cd417c490d72338231910fc194079846e6d9adba1aed281855b6b299a5f88da662db0d9435725dfe93407e115fe5a048d179b1704485b839e6aa0b189a312422d89b3a94f02a0c4d734cd3f7151f3919ba4f4e6442a3b37ac829cf186fa59a5a6309655c3e164846b9ddae53007857f5ddc34c32d9351164ab08dd2bd883cc9cc88d6bf32f721d54f3e78f67d9448cf9005ed5fcd09ffd2ccb2c4aff523c8c5b9510f7936a72c76b1f9223c1729edf839eeb563b0d42dfb82ac4e9d42f46cd99d4b13eb38672adecc306be10942a6482c8249783f2bd96905bd2d2dcfdafbb066cf97d6a017256f0aaeb102617898af9dc29fdb794554a494f7f7d5efea208abdd7aef910d720f01d841c5f7d2f1e6b169a0d75f77c7b0b2cc2fdfccf3e30833dc6d43d4ee307a91bc2bf28248943565e81dbefda82098938afd38d9ac0f553f08ad2f221a72b20ec33ca67c7698c1860a9ed3771d4e164bdc6609c20f76238997464d456704831feea29dcc4880453ba718da96893fe39f7ce514276d2cf7d785fbafb0df72df95ed4bbeabd67c3e3610b6439e02342494ba875759a06a2b0f2bdb6b013972727353cc7fa4eb3dc2af8271e60585fcee32ac59e72d1257d59e945a12878b722602c291fa15c77747fe8f7777244f520dba56e0b01efbf1020e3127025f47727d460162904bdf9cb9af743f3ce62bd6e748ed995062f00b0f6ba65b2384b0720d0620970967f1a0bfc4684f9d119d455e9bd1e5f16d1de57a32c2c9d5460634501c4ac9d0b2f2976966b0f1b7ee9c755c7188d5e5e6779891854e290b37f3728d7680af4f92aaef8ab6aecab7de0b71efe4401251c7c9f3d41545caa5d97f70eeae269b9263e90dbffc9908c24d0821dc12ef9821dc0c6c8a9ec95967ac277238f67ad061fb677358fa0791e3e58cc3ae181009e5da54be3cedb9ebb965a6054fb3813cff486596f2f7a1049834a438017876fce81bb5d99409a2b33c873672ef230b1c241ee399a1929c6356743895983691b1d02df6b586a3882f654fd322d9d40a53147036078b04eda3d448ed3c4aee1af0202e2cbe10db25a3f7b59d7275388f5f99914febd3f8d5799724bef4727339a44672ab6f363372913fffd972e302651594ead782871c30e69d918545243f4399145777d2533414fdae1de12fd8553f2c599f2edfe2a4096ede36f49c45a99b27daf53c775e8516f3c7bb100c25995e7537383f12232c7d7735133558d19a5e6b478e662ab886b51cc0c00572b5d8f55812a203a73199d39a6838064c4c98e6c7f370fffd9968347546b368690784335dbfef0cdbd96163f1293440d688de552b73bdfbf7ad3abc2a8eefe872b25639de263aaa40cd78c3a42e1cf4bee52a498e7f4c82e363ebe7976fdccd72bc348335bdf27edbde8a5a11dc706dd90f68df8bcf1f0ae731edb97ad11889727baf60a9b7f048ed4b2c51562cc82b82c4f78a08fa8e2425818fd5d8dc297361a3595a0585e51f1a9ae78a5b7d0d82addd0937265d49e52755f56c6865395772611a189b4bd91ba7803cbdf074ede2170a6b9970271a18bcb29fe8695a95dd725c02c341f287a2cb5729a497ac3f5cc2fa0e2c5bae0a560d1cf19b9774f9fd6c3bb50292bb303a847fe3acc047078c029c83cd7d5683b8fbdbeae99237fee225c11e59adba666cf3b90f38ffed2c77e8676757fd98f7151163e45648eb182f207062c8d0b73fa5d0ec40d92879d6cec683c44443e7843ea0de6043c1ab2ff472f4a25a90b21ff8284defff482be6f37679fd723e18fa8fd5564579d7b1f029727064d6dc09a3d00ee30a404099ce1b34886ec76e50229e8291a7790c6b46fa5b9c09ed6a10b2637cbecb8ec82a07a82f73f1c154b97a14b1422c1d6ebfd20d711096eff24e5edb0553afdde83ed59b232df9269dce4bb17361008f7b731bb45f431de075f141596bfe4a08fc8f707a8b7e4fccac6f60489eb88008723d81d77261b7d2dd6cf4b26b1be471cc55f774c479efa629ffa580502964c61a89b99840ba36483cfc5f2af9cb1dc736f3989fdae223f45ab00fecff19a9c7c59a593372535edc9f0306e2ea85d37cfd662f63653cca658bfd677102b05dd5ea39a1f040ebf3717a4f74394a25cae57ca2e013d897ee107c80bebfcb80ced8add0b35523e6f98ad3a9df98b377a2412efad9f7c0aa8636765d7fa813c027fc590c0149723bde3ce8ec88cbd29177d7a38c23eaffde4781f3bd84adf5ec896b443220f67247fbc82daba2b5bc2b1edbc0d10d9c8de832b2165c93f12676923567fdcc4e72c9719f010d179aa959de4fe055e3ba50f1b79477e18c54de1b7fe3cb731ef3723d7439070a5bf96b58ec5979734a24bc2d95afef09c8bd791e0cd098a0aa5909e055ba65edaedf38126011ab9be3f97ce2e3df5bfdbe42f5a2fe5cca9a306a72c9d3e5fb295cd733fe581d8b842955fc594a381bbd785a6a3f1868d5886cc6722a0fb3451adeadd4aeb3240b110c38de84096625345eb0ff13536af5845c507274cb6004e2be37fa00bb073dab5626e0dca4ca72fa315fe85f8338b9911dea7003456f3b88b5bd92a227748aa235e81cc19ed598aea1ee1e15d550940d866172201e32639fae1673e84d552d758a248d20432a3b55cb8a4ce1efa4e8afc266292bf4685c19084c397c93fae4454d14ee2bfd44aa1cd35f119258a2b9e936ff7214ce7e7892ec6a52205a6b1a0d56a162cf981742ed87e4a6a27ce63dd3720c15cf8a987fd1b186cf231d98763bf5fc43004fd8d8785ead3b67aa4ad04a70c772d6289a551eb463089c2acdfea4c77fc7353e75cf07e69c53f85213c0fb4ae57270af2a9f70dd1105685eafc5ebcb8d8737d454cd4ea11c2045f6d22be85cc84d45e8de6dfde7cf4baea6db8f309ce8575facbad7ec3ab5c8cc95643101b3cd678d16a54e857cb7e27fe7256ba0755dd804d6aa6b97b686a0112ae62ac0633a1b62743e6c5667956f17e98ebdebf3e2e63f838431b01fe52878fdc82993edc43eea150de6427fa7cb4ad6b1c779ff03324e8a49690651cd581523dd7b4ceb936be4b00553af4837d667fee1b4cf73e1e56db5dacbf7524d68ea04d4d5ba530b408f16ea2e700c36746cf9b39d9673abf17df88cb0c6c99da4c6cb67eabcf3c357f816fd8bae9359f99b695b16be120b67815325715ca904e4f5a026e2e30f9227c35c1db52232adb37eb9d3856c77351d4e2545ba555b4935ccd548d7ad72c6640f47ae969356fad04f1742f0f4cfa62514013ff47bf133d885fe86872e8dbb52a28fbf048c00514e43aa1121f46850a4cb7fd23a88aabd175d5508dab43fea72fbc33da00cbc8e4713aef5055cc5389f8208206da9056be8808411916eb7d9723512e619238fcd4123931660dcf4ede867ba2d700dd095cb16b245e49d361d30e2ef2f993327c0fc98ffa4d5ceaff919ad743858a9b62c3537af4af8770dda722ee4ff67f8b733b22152ce7531dc898e213950c498ccc15c37474a77276a2b72e8f4a2ae69a0cf90e68fa50116e7539b45fcadf4a998f81831073a7fb8a31b2b8359692273db5e5e0b4eae8e5a33cbee65ed115102f82c6aa938d696bde0e5720c5db6944f7b474415e712e5d2000678874dc6ef14e24b887f9142e4a692df074f9eb6e9b78e78112ba3f9d0bb6ecbdae1b67a1b0a1cb54401e9cd2b2e6969726e40e1ea15a3f335617d0d3aba2756a419a7bb46b4efce5effac7d87e07ccf72887c8d62d9be98c0a736b8e92a69544ff41c0d3ae4d679d47398a763d79590528233c35631165862bb4c7626a4b61564b3516169559d9f94a4b9e5c373cca3320a3ebf9ef548e4ae101ffd155f193e5e8d5b5338447f4296b43864c75998eb72033bd2420af205aeba9fce548af6e968e3c093e11a14cc4fac88d997fbfc6d7299829b9b84a2336c44426e36ccbfc6a063cfce9229080bdadaead3a635073768d979976aa4a0c0413b8b936f9981ecec83bc537df037ee3f6adec697ca82c35a51a323fdde754522f8313a0d24d5c2b900eeffedbceb5edc6f81b47ee968f472a47a1c485cb6530289f0287deda19695f41d2875970ec9c5dac279099b424865cee46071fa38d7821f66354c87f90e73f0912a8b5777ad26ebb71e3377759472746c146b67262df3901be8dd35efe1b26bcaa0557d74a2153ff102bc7f0ba272db86df73704ec3856bb3991420088ab12e709c7b4ea7d9255cc05ac78ccb57724c0447f9df935867c8d81c5717da376a904723b43bd1274fb364019232416672d1ad082cc855aff84956cfc087ad2d6a278e9c396e17bb5dd32e9e4850764c01043f9d4b7f40abdfd2219d12d73e4982c84b6d3b87dc1a63a6f1822b893c2e72f37f832065816545c6b7b4aad57a41cd12c58985844f0cc971cf088233692c316cb3fd3dc55fde643d0686ca056387091aa8abcacfaba4570c08da407079b2720a143eeec3604a99373f0ad8707be24c3a7ce99e10be093bae5c54043e6448086950a012909794c042cdf8358c1ae15f3c57390debdc0798a9767a4b0df4251a8d61956e38fb43c73642565dd4068f94af216b4c17e7aaff6388efe58af224728e333cbdd138d5aa62664f8b9bfdcc2260e267472322000cde2205fa07d49b72e1c24fa92ea5469a2a10fddd0b7f7882cf95dd626cbf424b18e0b465d5988e2a5675c53c4e04f60c8e521c98b4fd0386a1d1444ab9906ee0ed68a86fb9cc4835b59e06c934c1431d9f5044b25d8b74b1352ef50139ce50be3dda465ce424c97289947882ab10c885abd3dbfe2db9bfe276fb297c52c07c739b1d914235ccd76b0bb06e85fa39bc8a2946a36aa5ebbe52db7b1c5f39d3a0016f75d329babf8300c89d64b55871b773c587dcc390774515d64e3cf659cab8737f3e684dcc8a4416a2d54878447007799aded77839739ff0b57f0daf387981c709d078ae8a7a7363163f01394053b3732436d3c3f0e8267d89994874293ca72f38514d1e8c4461720fd372ed9e45b62a71136ae690c3a60a0352b6ce53c521068b301b4cc19b9a612ef8452178a2dc86932cd7546feb8b8c3be12c24a4485308aa4375e50976c0104c1986c6da0e1ea364709311b5d954cc2cf0214fbf327ea56e82179625217072762b0992f07f0fc9d60d097845f5799a1d86a3f8ea8c30ba202f7ec2f636ab7267421f2a54995d0ae012c7b4f4d2da5079fa972e2309ac46743421dd28b23c6f6c5288ab05c186012a275e04d75c231f563a5dbc9b469c0e84793409c3cfd3727cbe7817bcba77b9280cf7e8ea738373cbb6b2fc169c8df8b2e45c0cdb8f453b245fbe9fd4048714ca95bc04da844294134a1fc3ffeb378a36b6281e9e7f6411406068f1f560bf26454d519c474104b60bb2d55c99087108cf457baeef5a5c38024734f97a7e5d0fb0cae7c5384ee126966a412e2f6b21a8da1897f655c32a72eda4b164dffed7f0b667b6d7fcb2246aeed7b46c740dabd4a6c71a0eac9cfd72416ae2e190b592e386b32632ec9ece046230a6f174ef844fe1bc2ba02c240f72cf726aff6802f70a2a3086986db08ff25b8105732164eff9a0cafba857c10f72d2981dcf7d052d3b4e269e2410508fbdba4a6bc056d9a2260e12f7a74e8e4f72b4377f37fb8b10ef8d63aa7576f25c052d2d99152d883b4b7a89c44bbefd4f721549b60f862222f53005f5ed08be602f59078c3643a38b4721d385b68d961959915628523cc2fd79fb3a03328e2d83e64c66694afab05bb4d7935227a98e03722aa8b8b04ded1252f8c9fdf4329d2130a216034f7061375813c47878a3d52b72beaae052fd39f15a5307a18a92775b60c85280744fc93326dc9b8bfc0a4c101ef1d17d173869963b9ecf5014c07308f9fdbd15e18d34402401693002012ac37249e03de4745f9e420b6fafa8bc67f221ac5fdf1fe2e57261cfdafcf02c6c113d921ab833d636d7f50891582a07c5ef90144b67902fa7f2e40d627165a3875e2a4b7fd381ea5dcf356fa42a70ef94d3189f081eb8c501ebc3afdf5ece748e0d721e78b6e70c3eb861d0dad7c0c5587658377c94dc558ba91aab56865ddebe834d002b68aa9e48367ae2aff6954f625e42c692ff9e54bba3400b321d91f7c5d772163dd747176c41c94e9ffed5ef4c2c3e33011d93280e92c8a4f588238e72735a6229031770ab8b6591cf3311d5ac54db00fe674f8f50a2bbd74e333d73786972db02722ff95025cdb2547e6bde061d153805a7ba70b0fe0efd2d1b6e27589101bace80ca22ee585924489ddca4bd2d3c99cbbb3e3eec07bf5f91f6270e239754f94e66a1449ea51588bc50c17fb877d5174e7e2eb2d0380f14efe815b77cc6721c28c55886e46fc7d2702a5246e44c048200d43392a4af8d244bd92b7e2ee3720f3606b048cb2811dba4b0db180c85b087821a0e4b9235b1ca9d6d2aeb308c72af1e84ba1c88d2c5c268785de59829e4e1888a1563b336d29c55a4f14af1820d705d9127331cf27252fceefda4e65bb9d608135df4786db7b7b6ba1666f0a8721e14916036e150ad50123c5d9db69fe3be1cefde11e121cdbadf0e0dfc2062724d72a4fc59aac14365bda857fa495a66235e7f685a55e80aa43743080a5765726dbd266ec06c4ee3e521353d9d65c7e2359f3ef3cb07a51ccd153a7a41decf6a96e5b4b0ff2089ae76942cdd6dddee9138f3b8f76087dfc2a1fc4fe6911d66727d3c2a5898ae14e6c3b0cc0ae70452981915f03716863f3a7640bf10916d392ec3082c7b9859aa49fa46f1cdb73acef339050835ccd32277368017219935f6729eb26ace2e79e34b51ebed1627fd594ccf43e8cb2910abe241bbe305efd7fc720099a88503a0bd081351c6d36e43196eafccfe265e937796d39cd3f18041457200b6639ff52ea09822d411d7ec8d1b0cec70b471769a402a41a90f236f2dbd723f5dbbcf5f7d9f37ea88b8350acfe8595d8dd4823750f7506817aa5779553072880b9872e96f1b1be386f536905057596f626ace6de5e42454a385fd3046650142e31927321c5893d020a7926ac0160e8315905dd460efbd246d6737cc8f41728911ad45b63ead29a8c4d229fb02dfc67ab04885751dfb06f7e7816098b086724b21784f0461646c41e0d35e978a1c3afc1c6aee7fad6653627e47a997134272ba46673da329f0a20bf19e904f89de8e80662d79a73467a9daf6020edb488a727b1ab391a0713a3d64ef4a41a9c889372ccfc179b9dc83364d4a360138ce3c282194748993a07f735d6d5e51f36a33bc7e3f542a03aa62d02782cce92992ce5953729527240c6891bf8542b6e0f34393dfa36650781742ff600fb5447d199411073183116edc5411d60c8ca5824a93c98f12f491dc96cfc0fb5269f4725708724e62abb4867b3a4e15bae1877a66424abe1eb0aa999b167a2507eed89a0e45480f5e8162bdf1bd1ef4c9d7f892976678990adee3f000499346ec02fbb97b6b07cec9e38405a6a7ae1aaec5862dfbb397094aa3c67ee4fd9f301df46e2cc61c72781aee2de8ddfeba3034c5b90599bbb18c0f4f67b75e1b21d63137928491d63645d9682eaf388dfcb99802323e92754d41c9ecf0d695fe13fef54705b34d0f4ac26c49ad5bb11b4a859d908cf038829394f1e7bc44051c97478be8a56361d072ce2543a9dfca288454da3bce9bb09d7e0ac1e546dd50a668a2fce4b6cd5ab73357e59144b32a84bbedf61fcfa78a817c0d459af430d4e482f82e2f194ee69e259404e3fc48cc8ab356f105869f9c16de6986c674462d24a0d5e911c126e6e4723b467fecb841d0cae567ea9cd85e1fcd2c3fba34e5e82203cbe35a53a4d87972cfeb42767245a7f0d0c937a959b4c20d388f516193d3ded646241728c4b8cf72c557f804f5b44c27fa3d88552a6f61241ea490187ab2eceeaa610e24353a6b72bc1b790161a4831e5932bacadc197aa78f50871f53a43c146b205b02bd765c72a13b0848d6c40b473429e9a76bd4931135967d5b0cdbf0f150b8c7af06ab2e61fb646882b07a453869a83f733a28337aa8671b99e5d02f5aa515af3a9b39527200a83b4505ac3a0c1c254d0a543dee275628aaced8200602ca4a9a23d78b8a04251058f12b9b7aaf9b95fbbe921f4e6726c6337c86038193f97db2a6c79e3f725c71a7cae5f3e97016abcb8b20f283b13f0deef704c45ebc56f8a39f25ded872070cc467a005a494b186b7eb830b091fe0532b8801bb0a05e5bb9cc9682d2d7284e7080cbc02b6d0fb914c274b3f6651ef459558af28ba2a8f4b6de98919b77220a6e5ea4b30bcf4a088bf3b27e7e3f721fb35dc40cf913efff1b892cf7b6c243920cbbf861624df5419b4442a839eb471642521b91ca01afe52272e390b6f63196513f566b39757fee670a7d683998d874f9ad047000652658de448f79a7d59888260eff835bba1e28b9f8accbfcd45c5e77731aab1c0e486156eba5761d567bfb92beb882134bef6df6af7f3ae3c08e372144b32b98d9f0e3ba7418454f672ef3f149c58084737832c331068bc92eac987ccfcde181db82f9d0205befb1b72078ad4987b790cdc7a776f83749275b8459bdb1cd010b6ec33df0d2594a782312b8720d23f59ddd41e2675962637111a717d3840be1426ddab1f410f7c37f145fa9785ae4f8072c786fb33252ecbc365e78d75b71123fbf44864d9a0782f74720ae7483025a1f28eec77dd86118dd3ffc6b39979eca42f5837a9e7d32d8ea60b81a78795a0179222da4818ee7e1c60a05368ba10d937a80ccef522aa2f74a92b90886766c9e018d5cb574922cf791ff3bb9704256b38132cd9d68cd1643cb772866b4af3ac9b5088c2914047226b1c098137f4bad8a18441751c2ef2cf902d601a2947a6fae1e7571dd8c2de11575c342c2b036967326a8eda0c0973b63d515f12be4d8386d5e993cca073058628cb438b6e945e88909a11e304efddcced360164c66f039a3f4a1e50ccd60876694b3fecc3c3d773202b8d415c0fe5fe749902fbc0a3fb39f1bd5cebe27e9c05543149ca72af4cb791d7108e2a400708de2272a1cfc25938c0588835b536c0e36f391cde9e800baf9b9288f4969f3a705a830382d20d701ccf14c87c40a1f82e0a8dadaadfe9affb629393081158592789ff729a8f440adbdfd1479467cc7f66dc43ff46837c57bd99b1405987ec7f52d99166426de46010fe345df14897ff12963b6a872da7970e6068fd25232d041d8328370da3a19cd85f1d3d77f6809846318926a4984db4ce6a2c5690cc5b8adbd28c72695c9146160ce84070ee9351c4f92f12c8b01fba3bd5bcbd8e56856cf9ec7e19377c10197310c9bfd1a9da771b86dd94570cf17f96cc02144ea1b95de9a6771b3dbd3b94cde104401c2b5a41c32393b4fbe21ad916626ad1620dc46b2b6bc60dac6f842486fea20d2f9bbf8541ee3d0b59c15a2cc8e99df47104960e292a0672e78c1c670dcfe7424632c199ec9d9882edb6ab84a14bfdbf2f063534014f1672e665ce9211ec0dd14563fe4a92ae0929e170e05c85687323ae6efaf02cbddf72c1648bb51554f19f65964074f54a6fb02e9656abdeaf7fded5e7715ca69e5c728400fb2acdfe5e8b49b355c9560ee90f3e616cb1cc97d84497287761a58c757204fa627f6e4c6bdb75e14d94b43f454c26c300f87796d9e0b33a4ce66c87317269bd7bdbb5d273b47bf7ed9522a7047e039affccce46b828a71d52a07f5830589b48675ebe5b06c24b87344c3dd58cfa9184c4012b4436c6e363d0b1aba5891acd6cf91ee14144b291802b9eaf76a3c936fecd004de2c38389a028247db3c772cd518c2c699f21a6ce87f6bf425f8ffde0c18d279c4b5c3e6b5a171d741a80724737afa9389a2a68de5a74becba471b8cb4f5cea6bdcbd6fcddcb28dc4ac3f7282d3e7b54fe4c24ea9457e9ba6f1eee2df228d414ece8b024910ee35230c0632fd8d6297dbb0161c7060a960e9020bb6db5cb84648deb729e1adc668269d2072b489c74c38eab1a99581c81ecf920432dfcf4ffc453f9eb1f024b03633eb3672066a7d51f6add16d58cc096517f91df2a1a24e0b6a9d2d9e556ff2898f93307275a59b6d692ab7b1a2d85eafbe099dd5ec3dedfa69880b5dfeef157079770d32fd01c6d808d0572b0deec1aa9aeb7503c20014d0d3c7940f22b3d3bd03b104728bedd32261dade7c4d5a35879233d1cb79aa729cf33f24deaac652736354547244bfb3504f9986ecb809811e4687e75d184abc27401ba60ed0206249c90a0772d61b8ab847a3e00a408ca56dddd3004c7ccfdfa58a307107ffc551c6cacc49724324d087729076e2dd8953e95adefa96fa4f669628c132897ffb0422666c8b008fd9606cfe2c7be96c2c17fb9a2cb4bb338c6c488cbe0a660d91bb6458a56f72af8d229fd246bf7a766b46f62c93c246aff8eec9a7c290d6f38a8fb3fb9c6f362a9d07cc5e8b3f9632235b8dda70a42d15ef97817197e33c3a5d434767e49a63d97778e77cb12d3c8fc6653933799a864ef41991b83f6f6cca299b3ba0e82a5f3870673afec780559ae675c37e906d8f22c47e715e97625e8738bfff511e32722c8d108ecbac2fd5033780d2ddf450cf2418f33905e10c03ccaa432a0c1f9a728f04ee204ee1d802c76dbf0e64c016572db36b2d77935702b8fbc2a5989dc5723d3e576f432a641ea733c5dfb0f52266cf318de2f52f45a79d23278119041c722e5d2ca0c0f17c915bbe7bebd691a7202fa5cdd5aa1a9b5ae82cbea72513ba72d50669f3f2a4830edab9768aaf7302e5885999de7153656cbbcbe6463e518a72e3d69174f15478ea6d80b01a3bf636fb48df8e06edda9cbce05ebad0464fec70dae90d75226f4558959ac43bd9725232238a9129eac481efabdada32d7e5b77280debaa6ac03444fedf830bc27f103d8a4a4133d381999d341d5acdad4e5ec0be922f2b7562d1dc3524db02a689ed0ce2c15aa701a1337144eff70304db82420005477b0b1fc6041f4ee534e75a786446aeeca2ff8ea00efb3c7a0c879756f72b351af7fe24516893a2f2d467e3f1da44002fce340050786e48a984a306aa17017569d7280b99f1936a24e6df79e352328a213bd2aa11993b769b282b4ae3161aab92de2a807a0ea8b1b0da7c218dd25d8c155ccb3f5b240db0b881f63487b1a372e5b24e11a536b8f3b04fb7906e9955ac22557a23db81880e67f018ce2cd7216b9d0733d2ff01d9c16c4567bba5affaf8024660469450ffbb58977a01d0a72c191f4b69fe615794c482b68c2968bcedf68c17b67dd8ef7d754582d85fd05723b8894a21ec6ae223ae9419335a27e0c9bc0728a5157dda08b7df996dcf7cc474117d52c2fb4d6b94187a892765bac87cc20f096aaf1dd88a17ed5b9c6030a729819e89c6263fb89c4b929384a0a2c244263e846a421be859ef38caf03867172d14a7ddbf1314ae0448119ac00f3a534719ed82ea85fd393494c1a9a55348b72a6b5226baa1e654cd2120801b65d4e7bab12932c95658faedb30880bbcde437292557a543933fd820b3776e451e382aa158970dfc35c79046984296411f21372fea508a745516f02c0ebaf31263003739dc656eb7dbc21970264e0b64b49e53ca19a85885125be2969f39d484631936f616d36a002e1054145676e5a53237a724c2b074e87ebebceaeb4f864e595d9ea4feb119e9f92f15d69fe27fd05d38b72e567552ccbccb1334cc17afc8fcb7289acd5b76135019df002c5788757acab728ddad716c299becc3c0c2fc14bb44706211675382b22f59cf79f5fab2b90c71efa6ae6a429bc496dbbd952d3c36f1e52751d87c445d9d3e9fa20eab094c78203281d25c88210c116aacc70d0bccffe89c31010b7b52033694788b5d4e2a68272a6ff8688d9b7ebc157701e867e37d58e5e035bd9a9807b7cb07ffe13a4dd8a18ac4b4ddb831995a25219e0148fcac14178c1cc146c17040e74a19a836c22d472b65a64459ab20747e6bb6623083f0ca3bdfadb3cca8e1597af321474c0526d6cbd1e7f3a3824516fed1de18dcd0f8dfe939a29402e8f8b42bebc218fa58a3172aa73e229dc20e73878e1aabe47f0e0f07caa00b22eb83a834b53c505f7ef2b72b539ab440a587ecc95cc7d8d56cd003843614ddb41abb54bc9608bcf11e09726c09f77fadaa3741b0ce214af0bd094cbe4c3df524fd96edb70702793decebf341a7f26a2ecf9520874d77c7270afdf0279180091679271347b031a54c8953d1f60eb4cd594cbd3729001fb0a81ad16a5c4702277543b2c6e4f9b32ff5123fb09de9b1eeda1f79dcfd2a838b0568c95605fb06cd435290a25861b1c6d458be34cd319dd508172e3f83212f02e6e8fcf61eaa66a09a3239fc13402802b110648725e9b7e63bcc88d4bc0e6f70af28ec3cc21b540f3efb6437388f4b59162b2a1728be7ef927523756b5a7556ba2e5d0b0d83ada110cacb2d6fa3e2792c5da37072f7715b83b8099cfe54174743a1798ef03d8816387ccecbc96edeb96f52b324723b7a73147e743228b02400cb069a2b89b9aaeeaf7d7c1ffb96717b2f599ccb729b8dfffd1fd3386c4179e3a90011874109b31106ae45587e35839777776b0c7266ee7c85665addbae21e848b484dce734721b124fe94c01785d5bbc619cf6672e945ac5303d555836e1ab59110df7eac4f5b74e3d8035ed6068902b4ac76eb72f9deef5c201937e202c3ca4dcc5f619a6e97d7e4cd736dbfa6b02b237c365e09bd79ba01f5abd046c52ed5a5d135582957b2d7d10a58935e9f02e128aff30c03580ea239682323e897fd320455b8c77e342014201617e4aed94504d94e62ff7286014c0d27acff137cbb7b88d2d1734c3b4a3786320f84b9dd93a1d6e13c3072465a478e4a9c3bcb5fc52cb214149600da9ad6148ad188892273735ac4a8266c2152ff8236478af0922a6e18968c7a192bfb640ae2c4eca704b751aa3bc9f13a5697b760a140530418b9f04deca1ff88800fa4c83b1d8e250df949a2d22c74183fffd54fe68e2b5b713c8f1752fe5184a46c191692ba77b6960b0be0099d5a720abe923a231962fe7b61889ca3e5ef0740046e37c6103b57eb4e24ea1de2dc72cd16c8a91f77b53875f812b12c22d2e1ca1d91b6a3322970637ea4d0c75ac3118de95069af8bea9e381be466d57a25cb2d2790b4e3d6d32c039a216e85468672bd1c10b8445ca8f938e6eb27519375712cb4e5ae322fd92cc8c3e10a8c652472ace731d733497ca3ab5fd1a281f5ad295008371eca20366b508b769495fec972fa4e4acb99191435b942abdf535554dbf746f8b766ed87a1e579997f66654d052a74ff117bf0364ee5aa1d2880fd0e1a2c8c17efefba215366f6d92e1c1cd172b63536b4aa513388855a0fce1ffde451e9d995db71746f3b393da3daf8a2d072f35a6b5e2592da6d404ea8bc8d1e7cbea52d044556269cb31677037d39144172dc568852d8a49a5abe91ab51bedaf02f60b8b980b3320f941534d42cbaafd86d15a2991151d71be634213e3dd257c1316a10e58c5c59c14f860d2a6d9cf22541d7578eff226ff338411e7b6d9a33da2e1d436a5ac51527cc7746815f172ada4e0c39923dc244c5a5164b4a4eae71f2d498643312dd9c86de22feaca591856872608dc10a43a5d055fc8e63e04f7291016227455c2a8ddb23d9fbeaf0d19c5c727d79eb16c2d4d4d650f2867749a5361e79a24c16c16a24a56725badc03a53872152e39e3788fbcdd8545ed7b9a1f8d5ddf4dc7b8fc611d1961da58a06706cf729e72301107a031ced62ebf78bfba23921361ad3f0418f1f7768256b488fe2c7272f5c6621447fe3f1fdfb60339df88009983311310b954125c7496a61c1e1b48fc99a7b6b9e00889810510efdf79d20473b468b2426ab1f063abd995f55916729acbbafc20ec5e091f8d3d7dfcf63be11220d5a604e898aa0967ea0ccb37bb72386b9cceb3105f79196269b47094da5295b2a675e936e01a9990da94bbd1f172c5b9492afc4c3dacec51ceceba6bb66ab2ba2c215397dfe99005ba1977715372194d53af3d133b4d9e5949306542ee7bbb3972de7e7f79859233b73726ce0972efbe103406814b74927c9561a07b96aca8ed158561062549295b8dc02a03ff1ab9a8e44c81edfe36978ee90216061dc618cb5beae39ddff2ccfbcc7f1a0af672f37b59fc1dad3d71270ceeda5fe83e35ecc67fb728f8569947d771efa229915ee2bee5406fddff79248bd184a888b8a9c49f36e89b0b4923ac62a106dab7b7725bf139c39ee2cb599f8fe74c3402301cd364d41d263590b38aa0f764d6974f673a65db665db87ff85bd3b5eef3f4aa10b3e23b3bca3f0e70994ae5e171108f72cd46adf71a5e794afa4324dc35e0237dabf0dccaec14d6d0efc7078c11965a2fc971c661f318cce32dc46ec3f5dacc394edc18d79f3d9ba8ad267b8d0410d37253512de03ab2c0aff26f2e9026a7bd634f0399ec56f38358b9ce696227a6f4362516a1b3a40e00807c8f7b0c2b48fa2e829b754f5774e1f0458f50f1e90c0b0b16af26d86b4e41ad877eb13311e54d9f3d4de527883da82739f88e1e79ce1f47458811df6bec0ceffddffc9ad30c6394e3e51eb5d8b33e72deb0d571eabb616fee54c3b8b0f6eb96bf9055cc0714ff375b66ad487cd846ac0c6d462b4d8012728710f5b3b00f6a7e4a503f5c661659e9f32beffa8052b4fb317d96702c6854722e6a3e742577e9c2594b3952873392eb549095cc5e9d81f0ce833f3112cde03125f656033b9fe36b5359792b9bc6efc6ad25f0007de4a6072407bfeeb1dea922635a9365b263bc18c532a432438e33e710932338d48766502d37e969422cb9723ac147f2af4bdbfb2ca9de000fdcd4c478b756a96669ea0c6e0869a28dfd6666f4671e16ca7127e346e235405c7f3392c6ebe428fbbac1dd55d562ebfad31b722fbdbe15790e415cf9ddef515b3e05bfdacd121b07159458a115b3429b597572388d90369916651da590fa231b26c1db1cb7456dc2a99fc6d1c139fabb4dd7722b708f4e1413d14e036774755265d167b4b7806c0618390b5c26d101ccc08a729147597c9c8ef58b2beed9b01039871133dbdaf1cf9166372033a1ca35ff3a3d950f43d4bb9fe3a354246f91a51d86b890fa29ea8074ff3bcce5f0d481fd4a723c46348e3948a5f5650d74ca66dca2e71b32caa73c2b53ad9d50b73054b1f97201aea877d11ce91f57a675f256373082fa1f5aa26bba155be9dbc2587b1b13231ce9716aaa2904dca28742834cae342a34783a045940185292cd005e2bf6a6721c2e0c1194dcd0c305f89f834c07f6fe30b1460fe81d654111ebaf9446cf0160903013f06ac239ea6ed0d0e15ac0e7a6b8c81345a28bed216f39aaade382e272ec72d0635fab25e1431a933d77f3cf52419da0572ad5d8dcc2efbb3a1a2e6c72800d834d11630f0778bbee7e591b4479433fb9f820f569b0b66e4f895d7dc64cd9113dc07cd7309b98acfed9a9e66778f041c5debfdaf14134e40b904bace0095b54ee0186934696f0936c1b20d1808d961e17aaeae4d9792e3e640a3660b701533306ce7cb6e24d77710186430138d2fb6c721bccb4d2f5643744d4b4465b72cf8ac53ca73113ca251d60375ec405361daf801399e1750b3ab0845256f5d87272b81614eb4401aa64837a607be4e2717e9dce8b1b674a91e79948d2c4e9601d6f4ff87030eab79d65ed710d7f81df7ac45770e7a1e9d77da283896a3ba9a372a518a1a0bbdbf34424713b7554f2959d29344b2b6e70d476ffff3205f4cf4d727da609fd3506140cc66d695733863d75a2b1e387f1bc3fde3d9781f2562aea5d2649499a7c8233f11b102a64cf9d28b8167893cac581bf1ab8a02dd0435a36726131fe50a97a44d57d3ee4c3934b2a8aef0ffdbfe9548d748b62773428bcec7237508a83dcd9a1b7bcb2ee512db1228d8ef54c60c0281c5814321c645256815b48fdbe1675ffd5bbef426f14dac6490aa14379bbfa58446086ad3a68e03a4472fa2153ca5c88a332cb4505546bad5a1f4b3c561f438b5f6b35c214bf9535d26eb4edf08f45388f9f42fb50f9b95f089c55c33dae8e682eec4650187ad896d6728edafa9c86ad9bfdd8314534250d8492cae341b2d4a32a5b8c053892dc510a1368ae2ed985fe0a532652cbc0192545cae6140839b1f7ff33898e025847b4ea17f6c372d292fecaed14a3b5d9db48d051e81d8b469535f14bc6d6cff2415b607222fcf22f190f236cba3d188fb0ecace2c4c9e551d66a817126eddcfded7e97269f176a7e91edaae4b904fd6238937a3b7792c6864df74cf12c59038be0a1c76d7cdbc818c4ae9ecdd05e0351b65865c401367eecf8b9804677a6a3536ff43f684aa829592851371cbccd1ed19f7617ef3aab89739e4b83a3e16aa2f8605e2a19f33e1aa64b8b33a7a989efb31c6d171c1a2bf734671dd06fa80e6bbb2a14dc64dc5607a506b79094ef48932da3ff53eb7d536b26ba88d097118483f504278a722f5f2c3812c2b28f19756ce02ed56965270f9b55c58bb003b5c6caa5e414b1723bcd0f5616269b1a8e8722ecb331f180b8dbe38b386f2aee92bc4c2568381572b8546702f0cfe7d54c92c4fa6765a3186ea3f951ea5e51808874185b576d7122230774f45b12ac0a7660d782805bcec1b8c8a1ee8858cd65ec61babcce627a14b03aa958ddb29af752510ef6146e6e393d4d6bb8f8cafe104d39f4de5e3816729a137d81fd4185fbd05b63d5bed116512817dc1cf8d8fad07378a5c6733dbc727b648ff222080d60d42ffede56ebd93d54420e2523bfdbb764c17f6edd308c72be2d51e61fc4b2c60f78352f59f4f0d060ecf8d21b5ef38e2182046371f3bc72c9b57417f935575090aa94863153266ed2069e8c2aa8374ca9f4a5922c4ff87260b9421a1afc7e0001e71db2cb028c5745274172a15cd6d0cb0062ed9396f2720659c0c640fce49f0ca4a05e32063664b727f4d8b71580ed55ad879b07b00572bfe970e0fc33b957a193b8e11f9bb522230ba8bb40d43f65b862d25774d7e40984ffe6e9ddcae2c2d861434a45f8e045d1fb3cbd6444aaed7c0cf2f3b5acae72d0a2f2c0da1d5750e49586e2aa71a97329c91cc1a17e39e430cea005e794f507c9ca18b5ad1fa47305bd513e7d9b9b30f85e8453c6c53029abe6ee259f85ed726bcc207f5abba68754575e74d92a679bc64a73b688b33acda9ae398466356d291af6d5656dedab13a17fffb2891a8f874a7485c00c3527dff20c4aabb77e0472d9a63f6189f6a667002917f36d01927ebfe9cd5744eaf9cb188aa1a1d9d925726f8e4cd803f41056e7b559bf7c472a7db4af34e5582145c2dbc13f2b25a1556b3a335bde2a4fa2388a56c1f984f83364bebda835847546603d9dd87d8c8d86679a1d5045dec2e6d4d8b933592b5cd397a78621b431fe8105f4fafd4519d16872fff549351bfb4854a5f91cc84dd860eed226158395c85c0d96ecf631586b7e7228329a64ff3dedb32aa9c73ca10fe5d029de2491fa22938b899f74e3901ce8729970ee9e2d63faabd876935caaace6fb954cc72fd6a05715d04c8265a417a820e3c51a8f69756f9c3b446f4ffc2159d11ef962720dc1d97c95ba38620ac224724006218bbfc18c4f2398541d0932c47cef7a1debc465c29d690a9aa55139403d8907f15f413b04616b753be6e71b5eca0cbf9b71e2049a679e7ed5488e13e072b20851c7e9121e3253c71eec9f3c6a37ac5d573939f2f511052288b8045ad5723442e4aa2e3dd9b3544ef18e6ba1f4a5d780b475a0a737abf7c55f44ec601f72e05519c0798a1d3fc11d27f4a55070471ee0b8381eb6eb4fce087bd2c02d1f34f8f24e01ff93d25545a0a66a85a0d1559b32ba174eaef1955a3f29b0c265d772f03a55685f8085d77161ed0bbf8ea09183f97732588124aea2d0342a417261724cb2f584b2d1e1d6180a3fb4d982207db4f29d4dcb16045b90463d3684df11722adad4edd29811e730935515a8a93a6a4c1f2c996b30cd021e16b5476cbf6d074c9807089d14ce1e2467d495745fc4c716c663902f6515603d69810768efe872c21a1993ff443969d6abf469bcd782e0f591ce011bcc7793aab8651be36d5e0999347d829419bd580c38a7457397340f254ea71836d20d04b23a8e8ba809d3167f4c9993cc55820019236be775540f34a730692d88f81ef2104a48df9747fd722fe16b2826c64f29b757865030444186aea303333b8280010b58f7cd5fad527238c6e1db058d26d327f60ad171c25a6861df8b7eb886e0c53f7c7c0c83eb0956ae15d3edb03c964015cc9b0fb2c045675e32ef4afc4ec9386ecccbc7c84ac5725f2dd8b5daafe09d62be8ab455fa778c91230767f99eab7453d327a0287f71720c0b04c063b37b3abecb530c39169bf9e403851e8d11524d5319fb3f5e3420725df03dcdd9f859d61f987bf436c12e93868b18fc8facdffea815dc49ebb27b4617ef3a281a7f1471ef4ac1f1d38eb4b7a0fd337532e60945cb580d22cb109b720ac9b95dc4126f119a1e8d1c3317ef9fbd4e460875998452110ed076704579724018424fc94ce9a9cc7ca11af1eacabbcd0c4b2f69457fe7b78cbbd70316a5443c63c18188d0990438f3bda762464cc560e3269fbbbfa054558c3c885d6e7272ada05a52ad008834b80b33716ea2eb375be70cb195e948e59f2b0c1435532d723bd4771226d91ed52034760f6bf8e0cad693324e543d6d125a9a1d88f9cef25c27fd42e9de59537a36e06abf3ce78101fbc0ce969450f327f666d246cb08ab5f0c83dabdae522174205dafc252b638eee2f506f099e3ce69fb26a9648822b772c3304f05967f980415b615366d80a54f53a363b815478e3c7418dbc5483446725d89cc7230a1675643c5d5c50cf62417a698268198b1dcd7a0701f7587e53e449ca6021fe9e9e031efd81c0b523d03274507f780384cc02ab9aa14e1576a9572e7d15440db7cf07b183d39786547f7eeda4a592aa16a73d7a0559737e985f310ea44610b2eebb943942e01803b10cb43702c371920179d393db2cc1e83e169729fdd0cd7a632196ba1cf68969a2b7fd413ce90623bed6b4206da90d67564d4727a8069ee8189a5b8e7afccb33d5dfd48396697aa40d0e550eb32d8f335899445283ebabf6f42d11bfe7176ad68e0fe58c88f08a0a28c655dd272af6bda1dde7243d7dd1b15d7e50a5a1a2a41be2581da28680adb27b094cd03d16df0fb275e021a076482af50c710c15ff93ab0c53ac69e4a71a3dd486eb9065cbf4b142e12729ab807a33f70999702cdc440f125a6a7151e414831ea24910707d6911a6c5729f540c9414d61658d812cd857f9e047f7916ebf2c7c0a05b6814f3c82cdfc2b184ba60845c93c31f1d346503745b084ca1f5151ea485fb67ea55c9b3194090872e5dcbf5667fbf7aff05f6032b60954dd07f3e5e094c28188e690e7333ee7d37279adc3a977cef66b4295fcad139b699cbcc937b7a3e7d7550993af7ee0aa7119d4d9c5d81e88f9eef789248bea42c0d73c370a0137edf6d05bfe6a873db6ec22e641a6caf1ba7ffe6cad9dc9c1466fdd4a0d94497680262adfcced88d61ef3666d1578fc85ebe8ba1e714d0032b2036e92b718050ae6a5aea08e495b7f9e61727a81d48f553d7fe6b9bfb8fe81d2c7826e23b23c1cadf3a72a0286eef50d4726e6a9a03d8e0eb4a46d465671374216c70983712426c6b447e1597017c69f0235e3ead2ab30e39567e3617d73b92f4a8bc46eb195a45159076eab204e09aa7972c682a72b67b033737c57a2634d8247be16d01adda6ca85a59ab03756f5c4486913522e47d6f67c2d1cd058c9404b1dc6322e1b6e3b190809f307ef53f18f5972468a6f671257344e10279f0655ef792722fbc213906c866b2b25fca2108fa572d2fa01d5786fcb15704bacc9d385936365430b3e54b4d87f78814092c2d0db725f36a8d1bc772a2b485c6f0430d9380b6620262bc3365726ef9115739339034de3d337510f74f0ebff3de5c9cb99f5247016a81348b31d0e70f9e975fb350d72b3e40566d305f8bffa626ca60dc8f5d60cf84664dcf1268b230b465bbae0a5723d8f3403d685afb7affab348984672b3e61a3a0517352d5c76a276f3be0f8a72287794424c17a1e63f703257785c777cb8b37448ea37736487d4b18ba4a47f656fa9b285630a8941e16771de56fe423ae3a395e87f0c6dd3cf01f3db96a77552f8168acd52d3a7289f1e6aa1ac7644b710edfad80d7dabec1c9ddb66e9cf9f2b32164b343eaaf5dc40d9098f9dee79e52fd7f9f37b8e8af1ad8083e291dce972c9c03cf3b0c31785c191446c05a6478874ff654ee1836d1df5f091aeb0f7ef4a8338eda99734d07fb054199cebf0267ec0f38b4395c8be2f5a01e821ba15ec6188d597dee246cd55d7cfa40e44997bf46775ba2cfb02293b31b6b5846a151d72a75f033ed286d8ec52e85e6e46cb05310c719cc52a830a376086eac95c018272846884c26a9ecf6256b10c6f3e28d752b710e7c44dc614ff3aec8c95e6be537280f4595fe0c6ebb51e96eabb1a479826cae2b39478ccaf6adbee1c897e255272966ec28def7531d17df63dee36154dcd41a39dde8a83c6b8705a87c7e24d64724c0e998c9cd81740097277dcff58c4d3b630b52cb97bb83f231e5a1416264472dacd55cf6515f3c198d144f806b7215a30ca754e04960adb032d12d5c66b3643f70d092a03c627ddcafb8d0a4f9410bf8918fc0ca495a3d4ab06c5d258e1517293edc760f4388e2fc509e7a07dc446f1130487a808536e9ae81eec132335f37254a3342f63172f4c1f0364e23507c1539ed0940db3cd7567569dc5a0dec1cf4668ac3f46edda25ee4a171f33d54b75e199ff1c5a163c4d966afb9080840595722c5d1462df4b89d1574cd019ad84a678b3e8cdbc090a38db46ef133dff657b72ef3d45298756635845a607661714e7a13872c6f9e3490e4bc0fef47d96d2d372eccc3abf1359b34877ba3b9dfad10dee9c7923e327d0f464f8f3f1cf92bef45ceb6ed4e80226bdf2713958cb11f5ba6ac29371fb0fba72ac1b303231ab7c0272924764c6d73e355c6493337bc1a1f70a9127e9dcb1366bc88480fa2e15fe7a72fc3262696037b282f09d9db6f75efb95eed8d5c9a2b24651d3152db0ccdef7726d9ab98400f1a6966af4c6b4f275f575ab0d7cf1900742082dc2c73b71128f278ba72446f0a253059e9192d5485d4f35856c179c911f931f5ed6621221f54a01b4ba8735532f8920492582b6e32174517c6239dac751e485b3b00125591ce2199624bf52b33f0d55c331b2ca2956908e249dcd374836ead3418c17b99f4a5a31dfad5b84911c653934f64fb0c2d44496b8d33c5bcdd43c9e65fa4b146a770a728e35ada4c81fe9abd0642f1911d09f1c35290bc6e99ec8c5a9ab4db662cf5d5f477a5812d8234fc45ed09b9b915d29f3c46b8d7d545836e009e2f33e0755973485f187e6c754e8a22db0549ded298e8c032ab8de38b920bf1947d921d62b794879f1d14fe9109fd45955fd2737e239ee10da56291136f6c604c8f648f418067237eac751e530a485fbfc43213591b5bdc5edb5219e326df74b269185da19a9494f3b2fc0846315ceb75b71e065286334e34b1e60dfb934b1486a23e75138c172d11b05dcd5747318ed432de5cedf316a0dabc16dbb8bd54975d3b00d2e914b0017e0bcba33c26a24eeebacf3dbde398ca1d92ee7b16f27bcc00c2c2adc1420729595140c48d7c68b92adf9e2aea8feca9416f83bc853abf6e849f354412379627d8166c3844bf60cc69cd71caeb85d2715708f386f5ad1fa0814d0734f7edb3b95ec8cbaf2173e7c4105a7a80af72bad55515de8518945e9b149e5306fffe572b26142b2985e55f487f018e2e6a2a0adb738dd3a372bae00ecd8c30036a8b00f6579f8fc72c7f8c2890e77a02b0484680af530ad5698051092cd8edc27aa9b723e367b37032c10f504f4e20778a6ef4edaae7348c68826dfe7b7eed957e1da58dcece523d2605b1752a22f2c665d33bd8c6f30b52b40a48e297cfaec3f1fc172a16b0b7e1ec334784171c945899cd28e42beb620e7be6ccac7817d29fd1c5f2ad485f8b4a44fe00948430c6604fffe52ffca8355ea4d4e32b0395ae843eaaa7205e0070d0ecaf6715cd4e4546ba0f85ca446f6c7ba958dd12d153f8f17d248724ae8d569f3c7c5d510e892519644f89d8fcd96c035268359cf54812c10de5872bef589c95edeed53dbe78a6ccd8542ecb154c6a38f392eaa62c60c6f19bf7272898be9d5a05628df5310b342ee9f1e5b247b73554b593856f74b2cbf257c4f0a45371dc03d4f41147db13fe91fa7ae56982b4b959199cd58065e276f598175239a72655ab52b4461a043da4033ebb8cd778a8538e54c8f856106cb05f25c7f5c8ce12d73aee7db524d16ae61c49c33520e42a22956150fbdc9c312ca40ce5e061ecd55540c8a227568d6eb51905e193bc6b20695adba9f9d3dfba84b52a5dd6d6c68148bac9d3a144e1277b0c546e30873394f51f8e4293da521a77465b87a0593787ddd71ccf9663d4463fae0dc8b58c6be2a9533cac53fae9712c68e9b5172b17a1eeb86beb19a4cca73e409e899662aa233f92aaa42006d6e0acb52953b3dcc20d77f46536e9742cf4c1881a69921c44a57f39d5b4e66fd9001545c6df50487391d183d9b77eefc715f421294e97962c6af99e542f43fb79d62fd15adc4562101ae74edffdf3cc4e905de989685d8140d8c2f19df75026105bfd3bf12da72ad0d6e5eb11b7fe49513bc26dd061a5032fb6951f91324d37867f0b70c70287242cceac68160fc1b073b94ef32f9f05e6ccaaa3873a0de6b79f1511be258021b320b7b399d2ebd6b1002433f3a48efcf1203db34f8068e35dc3233ef2df26972067896f3fe37de09e561ecbba5304c7aaf9dedb8c7195111fa10575f13add548d8b883b64ffdebc91969f8c85c03a90b99a6b11efa092eee83ff3a812c893f7255e22a0982aafacf4dded262092dada60c601dbafc91c4f406ee187edefa3072217f267ee97a5bc067a9bebb9ee760884daf314d8d5e15b8d3f3f3372bc4e260c5eebc5a36f572a73f54e283f0851e7836a505d8c0452595eed7fdec990ac864a62cb3ea97183d9d457ee78c7f747d1024609ff9190e7b38f582e5b39cbf75721223e3e1de8d44d8273c7e1f82911b823d19fc76ae8ca8b8dce28f6c20d48c72e72429ef36a7487680da95122944679fd14d3c6d28c81518795bbdc811f1ca6116dbcec24b26c708ae07d82b8f7284773f2334946f15bc23c95728110229553b2abe58924224ed0ddda47563a8b45ac805b8fed5584b7372bbfd3b5e6070f66cb48714f3b42da31cb0114831e4d194b869a540a65060622ea39e48b4b2b5397281dc7fed91f42d1c39c1ce86b43725748ca2eeed7171339245b201ed12c65c177ce4e9084d8ee6f412246fe5d058d2d3ae638db9117084b5cad196863eb9735c57231f9be1011183013c39e2d2d02bb6cf9aa4a765186a6804b6a893eec6bf724438eddc7c6196d4a139ebd6988a1bab0e41de685b1302480a9e0b964715df6d49e94b9b7eb3cc1711d780d13bb16311c6f67ec5421ec6b608dbd6f37d13dd2ac8fe5859c6b705d084534cca31cf08b2d0c786d1631b83197b23ecc6aa5300726994b2680004b531b600aeb208ce53546dc606907bc5e340195b6f8284341a720058cf3a644cb9c030c3be90b8b10c779edcf1ec0ae00e15245bf964d41ce772ce56e966fcdcd8cb6c7d040b1245af5d084d1892d7466d43d02fc2ed14bcb672ae14db8f2377ab216f24b519b9957e49a9ac022543a3c3630b1835a6650b51723ad4faf8c62cbe87b75246156696342cc7f6654750e226b2a38fb83287f50072030bdc6dff5bd7623c93aac277da9257d4a0e574d91e492c05b49ecaab3b6f66b12f911b81e4392b372a54ad567b11661d584598f0a407da3f68487f5a369a728b43a0650dafdcf58efe31db2e35d603d61d4b7c776c3821c9f3efb7ef6e3e720b267d3863e7a862dffd01b1c9320f4cf32759e22fa73b5f21361c63663bc3725a26679bf2deef3a9a56b90ebd844eefbccfc47f889944c1b91df982137b8172652ad984c9105787e6287423cf4237a11b5c5a65e8533e1611a80505f2a14872058be83d28d3bb1ad4eec67ba537e601da47435319d201e4e6e2706f8bbc3772a7ec76f570c2970761dd3676e22d0da8aaadfbe120b6efcb8f1e7d49c17d5b72f768b0996cab0cd136186645bed53d75a5238c90869a575cdab37b2ab1a0d77259ac6934b5a8f7dae100675bd24ed24d779aeb98daf1daadb34f10bb5ec22a66e432713c9b367ecd91298cbbbca4e27f03e332d93f721e0878efa9505b7a2660b64370d37bb7d0fdc444580608fa64baae9781ae018b0569ad3a238a55966b72b6acff803b69c15fba08ce425864a9d844dcb562999956eaa059e4cb98d36272ddb340c405c6206628bafe8c34a67c161f4c8d46459c610b08ba1bebc264ee672a9e65b3dc566899727d183804fe02224009a9b1708e364c8ef66767c7acf8049fdc6d20ba24c219e69b4958a2e0706128977fb1aad9a5ddee88f8d7cf3e890118263d92e669a44043b30734f5768050d40ac09cbe8d95cd9436619f5ef4185218564709cf92cf78f7f8a6f2b292c0ed08bee1c1f1f8037d222c76de1b8b5e721db60053801dd1e9d5808c84d82073d64588ad082ff2b20bd32f05e4e0404203836377b8d3b80cb57be27898839d4673a0b65c7b8ac8ffd16c1cda358c9b0a08d94f9a0dd1d8fe5cb9af5a1468f38f10518b21026e50a6cea7fafc7699754572e8df1990ede53f0c6f7af08e99240874bf1533bd3f597588843da30e3f37094ce562cba42e0edf05860089c09860a8e8ffd54ca587faa41f01c163d65bf92139a529752a350e16015c5a728bd814d42d9379e48d52e09294f6369336cbb32f721e1ae920e096171f25bbfa1f568dcaf9199a21c5fc1609c536f5080dbafb877250e4794fa19da950c9790e101b3ae69b92044024622f2e2d0f0b84efcc122b3760f3b778228c66ea7b2befc5184b9943c140473ed40ae3eda7d04711af25131363b8b559f9f6351d03591788532b6a42fa4e64677bc88573a033f59c4a636f7227a7332661869e77b7c6a79ed311f3d9f3f83ee1eef2a9de4c5724fc6633da32ff9673eba7ba25b4660814b100ec88a2db4884595b95a8f23ee5c8da2ae4be723e2bbe43d0bf691d2b460d9ef9e8163802278bb6b033b561d1f55c45b137fa1975cab9f8edee4e7ad1f8e517808e12a49ffb1374ebf75c980eaf5f42ea14a372fdbf62915deb5fab9a6b6d8f70f09ba9533799c8eb4ff41ad4d8e6c61a82307268d6212517e1689f1e5d49407d948d1aa71b3e55ac633d0eee0292f6646ccc2e56d4805ea1615b1c17a61307d611004b9340d3e17c86ef315fe7b804563ed172d33d7d1b3bd0dad38ff4648e3c7a5714992711069fcf4b09d360dcba14b93d72824fa9ce05f1cc284bb628c7cab64db8967728aea388d7d5b0438fcc0d87a57211cfc60b85c77bdf4cb3e9cd7c7a4ca1693058421c034d76c188e9fe10f83f47e82894f1446984873bb1522bccbdb1ed2111585b7b22d658ed18e272bf340d72fb9465591682bcaa71b4c0618f92b09041ff7609f4c3e6d0725c9bacad6e59721106539724d02fc9fbe325fd0361d9ccb0140c95589fdb29a5dc1cf21cde10718f5ddc6b32d73d97afbc66cebae652b5438a19239de89780da5d0f6946c3c372a590afa6fb1f0cd82904f2868cbee5b5ac6f2b2b6ef09de8cd9ca9f63e50ae0d0c6b1a21a1077da6816b78115e4de82ec86d63837f371e4ba533dd28db84a8727a49cacc0ac103cb98769c9739292bd651e000e818f079bc04484dd124f41d5470fcd8fd4f54e5d2f7047948bedb7df14a3b210fde93e29ed05d19c472467005ae561615d4f9b0503ad3023bb4c128c513a68ce6aa0a30cc5cbc1d8f956b594250e5141a1d73300bec80a43b595bdb2bcf7ca7bbd166cb045804c64b6124c972f358f5ec38c4cfd11e12ae6b44132bfb2854fd50ff2af59b38c17bd8ebfa667217d4e98bf89181370cce75300c290767bb3bbee37df8137b8f1156fba685ce720d611f24a8b018e848094936f5b15dd81825fded2d7d8b11abc508a26edcf872293d3a3652eed9a4c5322329efefad0f9bf41cc71f01c72010cf67af8a092c72db918c2cbf17f14dd59568d3d85d8435e73bb041084c607eed84489dec95d5631f552248b6f30e69ba2ee47909a8c6eab4754aa8bf650494831768c9ba837a66e500b92820a95e3e914ef649ae1f5eee896b28836c88643c9acd319a6280d672c6a2566909f4d1fa5783ea392ebac30dae34f86cf48a2289934fd953651619724520bdc7491a7002f7d06271484834d997c207885994c7b2f94db3676da69b7285966ee77b554ee85c7d69739f82d373ab18e3c796cd125d25f91efb897e455265cd23cce7d57a19474b9bf616100149b1abde999abc73ad51d67d62ec4ec8723788a0a8bbcc9ce2eeeb19e88ddccff540c7d76fddefee396760ffa6cbb08d5ee3247f80f8720cf9045bba660611483824429c6ca144061a0fc0cbcdff39d0723c8371691ccb6018ce659a10bd9ec650f3cde65a4dc38230e5e6e97f600ad772885b5f1fef7d8a88d03a4c09391895b7444153829c04853e55b31bbf43258b5f68557078c1a1ac6dcc29d3376f1a9cf454942ccac690cf8121b2ace6fbf4c472458e6869a9d6a4620ec6592cb14fcbe73f22e65b0afce4fedbd5c281337d9072cfa211e469ca50d40bd60f05d1681af61d8e07d3fb1c4839e6fdab310bcad6036c90d4d0bf1be5e822d3c429b57c73831efa69c62e733c9d925673e656879459a1fd278b93d8f2637634e7e000af8d1ef64c213ea376ebeed2c858d409a61e72ad90e49d6160b44244da2d0fbdb9578c5f6bc9bd891c7a8d2a56fa3d12d0903c0d839bc42f2d1a7e14b9c2b71a844f0ed3e2839109c0f7589902c390c2214a6a4fce3a2dc61975e769750d8059d394183824370b610db25cd2a5fd4c4bc295722fcd3dbb767d3bfbe6486e319768483d6bd23fd859c98837fc6106a512aee1559ff1166432fa859243cb2953675a24913c2fcad93e7a67020a5b9a07f992f0727587f3c1b36f14b1a1b217f481df250d85b1e9ff21bbcbfb628e450a6c3d946bc5317f906d52528820ba0d99ddf068a9405916a10633d76211e994b49a1a9172751075059336e77dc758a2d675e0d139d2f08829c8a31b842b3ba556cb6dd9304f998b749cf57e12f17fc64f69ebf28af68c39e4b7b1348cd658420b5e3b44724a0de7dd702517dd758f3c31326dc3e35edefee59502cde1d4ed0e0c9ab473721e1a25daf980c92c2d02f2e96af0506fbbfd898f26ad0469774080c16f009872a042a1b39045eb3f587f12ff6b158a44f4f66c2f0c80d27299406f4b77368172d119f7df082363cd9f298072757fea7f7217d60c9ba37e126c71e0c38fe6c472ec6303282d50d97b2c9cf4d50641a44181e86c3c4b5927bdce7480d8d300ab728d2aaa6baf70237550ca6bab1b3868197e805716d6d723873ed8ea2ff6e40172f22dd49abb90608812c45b6d8dc93ccb978ea75f4d039677873a5dc4218c111510be183972a4de1cb7aee152c556ff048f5b7c32efc33ca5dd48232b6afb4024b7a08f8a8f09d7144ede6dcc9a3debcacdeeba7f87294543c604ad2ffff59072840d1f63939b372666e8f3bbc7f5a684cea19ef7e5ee8fea8c9913a9d594126ef7aec9130271ecd1b37421f8f17c7918303130ffaff51551ed873ca255690d728942ed8e41cdcb7eced6667473fc2b570abf38f17f0cd6ffce9555de644feb7280dfbc530fe14c946c43c1f6e7b561e172f662dd62559df31b25addeae5908029f783e0db5c79acb59e4f9e6a602e7e58c666f0494710672fec76531963fb806a53a34c46bd9c4162f6944914865fccd1b1d13179bde6fc9de05cc020ebb42728cdfce4584dad35c901723d2cde49c9538ffa15feba89cb52e4d663f1efa4672c297434a90907d5df23d3ef93a2c6e4247976c635f96db6175e3023c7f024f72c5f206983d225bdb93cf99541afae81e18c476e4d5afb9b23deab3e1aaed3172fc0afbfaadabfa7ca8cab08820d9af6ff41a77796ca5d80c5b6dbb8ceef77f72923e0fc1857dfa62ccbe25de5d0bb124734e9a512abb7a70f138cd54ccc1e57256e5b7a86d37474029b885a8b786d790861d8563342f43a223613c33aed8cd72e1637cdaac83156b7040fb8ab6b1d9bca6a8afd407d73e9005dff4a6dc34b8104d556d9e86d885cb3cefd5e3d80e4888109246d5574ef1fc39e0133821ee1d72d2fd54492a7c644855d8730aafcd6dadecfdac64b5f6cde40003cdcab89b6772885230911605a81fb8821e77fc356439a694b65cac3ea1225d7802fd299cd36081673e27990d8b3df99fc19872d3e9b737003b6765ff7111018fac8ff28b420a247875505db29295c42bfcea59c32da72da540aaf0d224834083d097b7219772186f1543d94d6d87f5bd4297a9f95adcf9124309ad7d9887aa4437b552d217720d96f1f9456e5b1c32cdbc433f1992b2f16b008ca78e998c61d1f858600ecc7200572e824f7e933b2bc9decd7de34e9919aefc4f7991f71d8309afeaa3256b7280cbeec3d715c78e9587c8ada8537759e420fb3722797970a680fb9d00118a5b5f21cb730ea6bea3a0d88dc95f187a46ec15ff09dfbdbcc4c72322a6b10a23477a9b8cbaf685767bd9f24d27584d3a0b7f6bb153700142643460e2d61471a4651faa37cf1496ad2a5be1f8d9b01ebcf52b4967ed591d24d0024294ec235546725bb442d1f081ae7437c95f27a8efc376f8d35e3d4b9c45587a8714e772b3bd0f7bb3022320c61ab3751ed199bf84035a2bf00211d4d9c62437ec413ab4e9d0726964e8b3d8f2e912406febb55bc0e1776940eab17ad858da985f2ba94492ee4d41855be9a0319c9d5fd202cfa5d8306b6999a870f0bdb4e2694e3d562bb3225d3a6033b476b894f5ed8c762c8f376625846492395a8464eb4ef3f64ea0a30d729a4f129a538835fe6133f152a33d59b9062d6d94c1ceccc80f334f74b373327206ab6359da32effa92239f8de476045b40f54725484d6b133f60043a4b505172943ae341534552ba8febc8a95df1256d85640b8a0836ff92f32f9b3c825c1972143a7ee51111d87741a3b596cfe87f10c0e1bbf7f9abc641ad506fbffca64472f8bcd4de8f72a58f15073c36561d05abdde2b943e344d675afea188b7a3a007207670e78314cb93c27d88da3b55d0c69291fbaa8802c0672fc64d037edf6b462d0748da98462b26bf866aafb71aaff6881d90794c1ea333539cf7a0ab9e0d772a2b8c216e596920eec9269a93109b24e104a6455ffceec0bc1e47449e5554708f5445acce1f6d56eff99c2d830cb170bf79d012ecc65c8c3381bc6034be8e872d065f686067a3d154af9f22b0e34d27105e47bf2a2e8ed073b64505176107c727aeb1abd2272858acc47bb5535d559b14b91d84592ac13797c8340c22052fc198688061364b571e2a753ff520f1ce0ffa9262411be4a22a9eace4a55b50bbb1a246f51d3ca5ab52ece4bbe2003a34e20ca9a2dc7cf1e728932ba62aa9b4a0372254365ada967c9c82cb819117b545eb0090bf5bee3dec41515674796464ce472c1b0e5b04186cc3ca01715e567aaff5c72848af0aa441bae2a0eb160315d8e72e4b3f949e64048e001b4bda2f2b5e6bbe44b54e8059329cab14037e9d10f79721c1a1b363dbae06cc8311a68fc2d82e16892541815e80a0f864a150dde82cd7221f074f342f0c2bc556c1457e9271e13263f7b1e8de2998e4561c44e90c66360a62b7f7576d51d950a61f7fe240d446de165eb8add4ad6f858122ebf2b145a722325ba092234ff1ac3bd8ed3a99511a6fd0190f7653614e67c650a9c9d32320be2504aed2bd27d6a478e8143497879f8b982e4e9ec40ca193b320e8a13167b23288e52105f776a1f8ddae9fc44cd033c6ec57d3a88705081bdf59b08af63f57285b4ec7a451a7e9a8e16d38bd0e3bdb6e606c4fe08749b224b566c12328eeb72a137c10d4a83069991d7098f2b40dca66b5f5d244c9c7faf2a51fa9d3609ae72786db23ac629b1c7bdada96208029d6f2471725da23f84599cc2f6397009f131c3dfe3d9abe11251fc4367123a8ad6c2061fd02522d9be58845b5426a38f600803b9fd8c5d1570cca891199d1a810178e11994b95d182ce5b538d6f376e61672511453caeec266786f95914ae7b7fcc6f258898e42444b80ce2b3f960fbe37720540ea9f14fb17743c3fdba9aa00409a6d24344ae82885f7fb05e5218556c76e1c39345c5fe8af2e3b8b17ea0599bcad23d1489d7f202a343e50780dae6e7a1c6553574491c99d96c7ce0fe1bf28af881893ade044071bfac1ec6e9c338c1a1aacb6e089500ee541f31d984ad0df8b388c6f31b935043e715b0454b63d3fb472eb9fff083f749d866cd5163e8b5c895cabc0d33da2cdbf720dd78616fabbd072f4ba53b9e3e64e43efc592558af7998871569a220eb90a356dac155a6559ed20458d2ae9815861c52780859b7e984f9c3d0dbfe6f2f2f7a3c9d1ea78d268047292d97842521927aec0ba49e69cf54573d51cd9f9ff59883685fea4c40459336ab950225fd7caabeb4e442be288122de33d50846d5fb57b697e4a35563f6b3c20c9617688802687e508da6e143fef8763f8c82ef33a1106ab92915d609e6b2701aa9ce393fde84742befc0d18f91b25e2c67bb3634e850da103f54d7feb9872009ddd250501e384159dd9cb71e945ab30b5e45b9b8511fb60310be84c69b11972adcd32d2179809be3fde58fdf1799097b483e348455c1aa0ddc68a1f18dc2926e5f94d318adfbde181200423f5d33fa9315dbc002a6437903a07f5ee77e2de51e18502a46db9f5b15d0880cb8ff5919beddf6c4820237001f944d6ffa72c9b72ce0f50186cd91560e61979548def38396f1fc3d3e83d69dccba4e9f97fe71072245b309d2b2d1f12dc35b1c317c61f14f8fca27f5b23f142a77d847dbbd9ed3b7e16e3061358fbf8924aad62088cdaca924e541c22be7f8b315dcca33fe89b720d96bfd4a65df599c1e82436265663db6613e6a39804179515471e5f145909573f904c747c423e0c116ff1a0d1a4b75bddcca9282d245d22095babe004dffe6f5bb054c972ba2a861a6470298c21d8b08a571aa7e2f0fafabe89dd29499f1b72b55f7e1b7fbc33cd1bc8b84c567ed668c314c9a7c0811a6633a2fff999f4587206f542ec5bf8f2e47e2413e6887f2c6d6310699112ff51beec9fcd8a7098a772f627b1e06b480f83cfed52e05315b43925da8684085d409f179dfaf130593872b6a52bb195921720f71f8141a276b36aee3bcab8eb7926601a8857e74d1bf272052671f67fb5bc713d6d1ac2cf3d9148f8db7b266464bd53529e72d049caff4692acef9c832582392d486c5648c5b03f162e8d37dc76e6a0716c33cf83143372d1ae7c0a4d1f5c2b6236eddecd05411f9066e89132bb11bfe22f0948fbe32a72cb87d82a51164d19daa18c7f648eb83c97f5d5975b1cdf7d16de9233b6080c72695379157f23d7a55b489fcad23a1c1518d2e6357aa672be4877c13fb23b900b53327474be6d2a677f8d14a4baac6c47eecd9c7373cffcc1323547a1a7993a72ce105debb0ba9761ff8e68329965df274363547cc5a98d1971b38896ae4b797269ffa5998eff53c35cbdad337dd298d2c869709f58507a4ff5c53c4b4b10fd6a7db491a99caaa8fd248cfb980b7861dddd6ad5bff492e69b2c64c91cb555ce725bfdac0b1df812a83ee24f8abda2043bc29e767cb49031b1a0fe8ecaf2559d72775a3356403f9222ae864bb16d310e293a3ab56af5a86eb306ff6b459de62b72bd75eed546663e71634602cbeb43933620de330f5fdddcf7d2a54df15acd8972e40bde8e53c5ba3fcc54bd70c5735d9ee479dc80da366d38580db1fb3e1aa072e240bf3475664ab4fc31404ff7d27937a59ac49a62902853cc4c60131a386c062ee6a5de325e2d5a1fc48a15bddc8ace1bf533060a9a944729b3582b382e2c725488a5605cce2767ddf39237c813866f4ab0209cbe4f13982df3dfa67d551272360f1df0e227c30c19fd0232d685b1d3dfe2ebcb3331d173a18ef301b83c104eb44d4ff1d39ee5a4ad580652b700fe246015a1929f1c8f3a6284d9f905c84c72e0762eb8c671ebd50b9159c6f16453773f11eced58006c58c1c6d4375c185b1973725e4cd7a8804df1481af960f513188a7a744ddf363bc1c6d24c65746c3572325ba5492d1955524cd2f75b3baa5557e10cc10839f0e680179ca5ff3edcc9721eec830b348681f7d79b375f7c5c103edbd1b29941d7c2f969581cad9fe58072deb0c649d2177a0954e7c677f4c9df73b8ddefb02e95313d5a0dbaeea12dd8227f9b8462643d0df3a4b8782bf35ee6f3ad56292d245c6f39b16c0128636485006797a96f47802ece06700efed158c444696e2fe781ede23b24f412c5c66bdb05c9e0f2157cbcc691946935fc220a09f8812e040c8b9199f0744c8631c0078972479e2e64f3810b6f5d23d949a068f847133fdc6c0a67c7ffe7cc8beedd53fd43aad5e82869e4a421790e6be2f107946b65746696c4ba4f2b8961b0cea1ae2f6264052cc0e13311646709a5db9a88e6b44fcc87bd2da2fa2f9de83789ac511c7235c88f6d68c5b4cd7d03e7edbfae879031a05418cabb40de5415da99ca145964753c4e8b8f52c8dec61feb02f7bb9663f36d7eba8dfe4b685789c369db786b726fa859ac1023780c0d8ae2527402d92a9b2bd3d5de8d7ac7ba4af548d3c7497206054e84c8231976b102c9ee9513c8534c346fffeb04b3a22265b124d6482f56dea63f165301816c717971dc810ef5007a25ec41df034a6fdf1e597ef240ca13be37c807ac2b5d48d32caafdc3701b8a9df18fae2e518cf51db233c27dea377228156ca38ef261d6594b7a74d69909eddccd2b4dbae8a05d5c4b9050863e82195d85b9bdb3236fe86db10227f842e35522f5ea22bca2269dcb4cca9932ad1f72085c1bf92fd90cc6f9272099e45adaf8694986f29ed217a17ca031f78a49d2725911d8c2a7c62d2448ef22ccd8368b53cb96e7febfaf08cef94ef06d8176f57258b19bdb2ddac5d47bc37fde5ff95519d8128a605834cea8035f7f0c01642572a0c46051c6180ebca12687bdbf24985049c13d9dcbb461e795a0acd9f635855cf86c86a5a86eb0e5706311c3507938b829ca2e52b6836294eb43771479129e728240adc08bd08053d2186a6f68c1eab70e2f8070cd7758d8be916402ca62df7255ce09423e7e44c2f1a66773b9fb749edf9fea960e355edc1e0f18bfe22ef172bf7a8c7d2c9f61ca4ae490d096b7167a7e8094f64f6be9995e618f31e4bf6f72314b9e452244e8559589f9d4ce42f18fad41b90345115c3117812553a7a3c94a4c732ef225cecca5d9c6bb421df6058789c8cee9354a6d3354dcbd6e67cc912a68500ce20c9d296e62637aa9cc9aa2fa7185c1aebb35ca9abcdf968d2b2ac45fa016990cc15f7c94a0f1a5c488a1d08ad78ebfbf6528266e0e5ceb62cba29b72e34e4ae6b7a9b566c6d2a374606a8e890e7fab0a5d994f3a796c85bdf8d64b4b89a21511a6d7e607fd1aee007ed819c710335949e590b75e9d36c48aa273877245233a445a4d8a109338290abd3c8d2fe2411d268518023376320a29c618277216fa28a67da45db99e3d381cfa208f7c703997541aae19e5f7bbd4283187287219fc522e3c125047cd5db09b0a7b062699cd2c144ca6d430736f2e142cdceb223e9d154fe730da99e145a0f7bd808071c4e1d6fc14547b4670242115d169d472ccaa3973ce0ab886c27a5155755a6edf07aa8709a974ebf47b78c87a6768e31e8333e668c7affb5d0cad0dab2f9542612e5cae2c72c55c0d1f10102c7c666f2c379fd5ec1b909cf522bb47bad8de37dbfffd69101fe4b9a3750da2b9ec1527722068ebc7283787b1ed64ab398d80491531c31c57026637059f31d89b59d3b5721e7fef34dc82093d903c64b40d20ce2951e0b84bee5f21b4526f1eed63539a0be62d5f1faed587d6d7f6f160ca84f98a945d3052ac58e9b7a136d17ae323733b017a65d1d1bff08a5444d263a7241a2c0de299cadec3a895df7ed3c48f980c6e183554bdb0b42694339d7503e08e146301a5572fd5e4de5802d9e46a9654887281809aaf3b57875dce2c0fa0b8950d6f28a569c67d85d192c94bf8b7239ec172d547dff8658d3e4f237f906397c5419df40f2a3067c9a67fd93365a8b20dc3720a2a38646a5fba7452b22f6e29f8d7a8116eb48fa58d658794387f6515167821d68b1bb0602c01a1361db6f56987ef798262e7539e0f1bffe8adc5c10d26e672173a3280c2cbfa03869d2497679df47a3d930524a33787ce4b60be94cf657672ff19a2acdf42b1fdd5eaeb6dbf7de7587f7cdc45ed8bd3079151f80c073e5e72f75cfd64fa2b2af4ce86a3e74f91b1f161fddc640d80f01cc994c979adbac072111a52735999ac47cb5ff79cc286faac74af2d26dc185f1f0cd2b60a97ef6572008dc7a8fa82416c6e986f1f670a7fb51dc5341b7cd6ab29065320528c4b3572fbb00649ec63a7e5c1749d1765e00974de35191b7000d50032d08fd3534efb1646c9a6c0e491086e654e954e75b4cfccac7dcb8cf48803463e349c8592520a4547d4a5d56097395527fca6cdea13a14bfebfd1f1f269c3272ee7fb319273a868471cbf78180f4cec601ae91e3b0a260fbceef48256fdf337bbc234cda2c53572f1bba84a8b407e53397e51891961a32388d6380e36080ebdbeed05c4fb4a8c4b4afeea157646f4c00974d672f5e2b99838eee829421314f46576c2ebe3fed272621672e5a0b47ad08ed27c87659083686045b7f48e28bb2576c80c0b51895a72e529f30d6de464680ddcbf263b622fa16722ac5dcd8c747d70d82e9734c8637237e7fdcabfa2d81150327064d3a036f156f7cbff3ae446317a711f397023e972c72330acc6ea3120fb06d167e7ee9f00e8e06e75e3dbd30e550086dbff9aaf72bd2d4b8a0f7a0e88542f7597d178481573f2135f77efcbf12da58dc141a3f47245bb26b13615c13bd6e8ac3a76452e1c4e7be676daa3c44bf6d5f2fc242b2930e3605ccbd762c4ec1e7facc11fd5356ab436adbde61e31b77689fde11a936f2213dc53d498819c00fe83fc5ad3ab42adf8735ff26d7d8eae6728b19f5ea4237202015568f790801e29cbb28aad8106f51bdaf9422da47c6eba6fd82ea26f207229ee1fbc03ce639b1342adb91fec9555ddedf1dde1eef070226be286115975174c0ff058db722232b0099133346406a4e2ce9cb311c65461a8924588e94bf97049b8e6d36cc7a568704028275101bfee309dc3d78b0e4cf310bcf7a115df3b4449df616abfac742bc109fe76efecc9e19a7293951b3d798642cb53a827b50b7240c66bd71fdb488df0b3d678a0a3b6b08392b2b5b7a01367bd9cdc8b32c50746c2e45d75e5fadd3ec75bd727f1c3b58b53118b6a7b7907334e5e1a2b24d2b572253781b6b934264a5c4925c60143cf6b272d4de4282dba6f0d1581b2bc948a72f4cef4224f62ea32e67dcc17130dfb848076d7528739d5251af776e73a6e8623755a8591f0e187278a27c10427f400c6bbeebd8a9d1cbd132b15fbca953d9001d68999cd8b74cbd7d911fd8dc8cdf83d0cdfe5b7b1ed89cc435c73eeab38a272d90f7de4534ffcb507382b15b1861b2801ac14c12795d06cc148f3f15763c8724d2b3eff3ac4b192a9d4e4b0f700d38f791fe0fbe242c10197cb5bc32615f543d13f48dacb8ae1ed3891f5697039f820d521ac2578060373d18d3cb3768a9a724ec8666fdcf611497a9095375596af21e9d057c1c74266c961a2f0703b8a3d638edfde971e718057108fa5397e5d439ef33425418f5a86d2aa4d2ac874f19472917f041ec53e9c80de19aec9014cafcace6f3a7910233871ad912cd84f59da3b277ff3624ed9a54df8042a033565c43ed37eb48e01d7233211cdbc2343c598721d686d03b1532e5be1aa704833681cc003f8853008271c58f79aaa368e1cc51d6b8d5fcf6d265c0bc12a7c8736db5223356908eda710a6afda9f62b1242a5272be7890a9de2a3d5e859a945c60248667a07038e9f8b5a53c3dff468d582108725565665d395203b92ace207784c53934b0f9f278bff771ca6d420787fd037b7229b43d9aecafa2770f104c77a6c3899693c376c82d704f8d31925389f20c3304ff76de54225c2d6c4826249a617e8cd12275bdbbc38c0ed97c1f5512b34dca092d74c52c6ebd128874e2bde4604620a0106b87932bef30e5f70c5baff8229d37d77a173aaa056324faea24bc1e8c4d95f7d7acc94891f9453f21b8813d788b720ee60c335d61875b4e478482796e8daee6b91d6e9e2faf2c788fe6a8f76b5f72a14a390877ba0239e3f60965c9559ed95ff0668095f0b96fa12972b98194ee7211263a766cd0b3883b2436111cc1b01b200361a4be660e92804ef4a6b25e04723fd3a7c5a502e548d7f57cb76787870b7f5c02d66efee5182da3c8005ac3202621c744f74c8522c618f4ebc2216c1d0915ab06c44dd420455cc89e517d37df50b88bc3da61042c8dc8a30e89713fcdfa5dc30b8755dd5fffeab7c83f3dd3d5728a53d9a2586789570d1eec14dd6e5e72f76fbd33deef27ba97763277d8de00726d13ea28ad64a0500b0edda6e19f16d831692d8bad2bbd6077489a15e309ec53cf9251bdafabf991ac5ce7059116a78855015e4413da1e7d7667a017d9ba1c72ee6a60d91aa6d239b2dc1086aefa344b3755fc6b018a20ba1b56d144c2566c721f3fdd2686b55921ca7589543cb1ccbc83141d5aede3bd973cf83521ac72c32eb9e25c78567a0b0c3a04487d120af648d1a9c16b8017ecad66c7121f185eec7235c2896c50ef6d84dbf20a6dbd5e1be65685b0829ec24447ee9b0f91dd649825083602c1f9b38671230ac83eca9d5e4b339e982e90500cd4ba23a6435e2cb97222c1a4c82b26751c06362a29a16bb43c19b11d6bde8d23ca40edf65238d56472ceef319470f9fbc550a8e3267b01c4fae1b1a5f9546f447ff57ea24d6932b37220bef66c1c2c0e0d19f4df997e9a825b20c14c735b33c3b08eb00def1b26337271782bdbc4acef327d65ddfc9aef288aad517d4c5b244a7dfe2100f89b4d5c727486ddd829ffab487349bb8bc47b6b097dd2193e762feed26d1287b17934877253e2e495854326310ebd6f98d1264d6eae88935e2966aa9e9d24867d35b8aa38911b5f6fcfecb49c3a3b1d0256aa4f64d834a318c87e1f6c2e37696b6d4fa072b81a64c35824cbc5ba983e4850ab3adadef97b3dc8d843d58da31c666e57cd72fd65619b97d5881d817360a3995de611760e9df30b9a7f6baf7422cd0dffc0725bfeee514b67369f0c6ff75a6a179999053c5c9a23fe27be70be40cd2fb970723bb95eb67d15ab6d041d69068947ac3e644ef5ce11a3dd22777387b068a49312ebeb101c80b019703b9ffc02dbaa965c6c15d2a264524bfa35662e99b26fea45186577dd9d0a2351445b790ae300fd9db5cba1f1fb2bbce470fa1b1a7e9dd872f89468e00fb30793f19c49cce568efde2112fa1d83dc5916af373abedd1c93297b34ec5f735b485ed4888e09319a73eef86a687540d324465f9ea3815e9b3b720dbc9a0ba072a75c447e1623236a8b9823bb89e2c82d0ea6b6809384329ae07271cf5b6d4be58acb3857db4f4cfa663f6f736d43962cbb7230eb0a983b87480d00267c508540429f1dbac8bb6f0e5c1a06ab338c0c8a6e8200520ea647b28c45b57764611b57e2143aad3c2effa213963b7f7fb385a9ff005a578fe36c8b374f16718bdff64c56cd67c092be7f5748f94eb2170ef9ab4495d545785b5bb77e52722048b888ff3823b2a38bf9b3522875fc16483cf1e7877e827e094047c09172ff9bcdc161c3b4fc35d7e67cf9531d0fed9bc50f0914ff600283194715eb6b4c5f1ecea9bfcc61b7344832150e6cfc1c9c095424e23de92f1ba7e1cc03b3f672a06d3d07bd4e184f52206237400efe86b6b635ed40c4cf80ae03b520dcff1272d938e8f0c4b3658b9275ac47aa5d25e71f3839f92ddbf110b88a3eb54980cf72bf2b4902b1f4365fcbf735d920a76d3d7abce37699d62495c405c8b7fe23cf5e0977c47d462dc460d91834b4a0f9233229939c6f6bf18a98fcc535f6d283415e386d07fb73387bbfc0bee763a732c28aad821bd28e886ee1faa6301a578cce01ac1c84484f16a8f30fb59b7b820c7de23b33a3f47886851dad145bac2d808272bfb435a357815cdde16c12c4f9bbfd080bf9e712dd54bdbf0b76ddf133196372d196221f2d68840f09044d2b3dd8c450e0545f9c80a9a75a197e8afd0ba3c363e5eda7709ceb943441d7c871a1a61f2e140abb91f049513996457aa2cb132c6da6520ed22581a0490043a43953c9aed29eeff567447fe391caa83832850d004b820b087cb16d3d86136a8d3912c1d38a647a44e943cf208867919506e08e48722ecb399d4cf4a423ec7a287b267e6290257cd07967616e43505db585ae4c95724106c1ee2c240fc516050ec116bc3751ae9ed4cce442d04acbf9617f196c3f39882e204b076a13d8a96b3486134c41c5f8c96a7ed9522581f408d7c45fa6a2189ca8fca7b19849ad735464f772cabd59e27cd1edbb7579def96833f0c97a11720d55702e7a4ed987d8bbeed164c4ab56d95054457205708d8d52c82f120f517275383a99b5ce7504f0e3ff3b01baece26870c8451f8a63e9f125dd3b2f9dd766505583a464e4c689ee30f0c98df9fbb8691566df686d2fa665ce2ccd58f29559ec075584d0555d67a3e2d47019f084a062884adc1e75a05e8ad1bc17708f0072f34931991c1ec16b5a18a3913138e9da5729c3c639f7dfe3fc953d3a90596872cc9e674d539bba01689956f2eebc5c7ccd3ef97ab74e2e3e8a377d39d7d0465a9bd64fa7c218cd5fa1172c2065854ab7451333235d4c2625fdec842cd7af1762ddaba09802698476483725a35aa2f49cc35390254deda29e4e22865492a19272a465e84e74f8205f125d2ce40380479891f12ae9ae2c6e15190e63a300c10c43254fbea081633e501643d32a3808c3e28507ba0721dc3ac80ee27192f0bc54721029d9d0fac2387f66f2bb699084c661f6dbbfd1c3aebf7031f9b6d6152aad03c38f6ff8503ecb99a165d0d810e989ae8bb807ed270623c705bf4bed5242f872fcd90bce7a521760a76d6fdf3e1ed5bb3bf886a50f870376d69531ddc8db18","0x7204a4754498e06db5a13c5f371f1f04ff6d2470f24aa9bd886540e5dce77f701dc67b8108505c5bf675014ca41c819ef786c1de2a8099f6ba4c79bdb87da6055fdbabfce532894fef0c2db87a07ca982d1743be29a773b3bff5e6e59c2e298f72463dd5d53f5e059b0765cc9abd2b26a843952879df2bcb8fe88a2d09d5c612728079d9acfc46433269113e3858a4f22d7a92de1846678681669d38b1a5e2cb7266af98a66999923431cd5d53e797ff0fba6a0054c1496c3afb5564bcfd19de727bbc74ee6425cce0c1bbf296d913f6aabc7dbf6947fb984d01b8421ca684d87244a1d3b5d788838df7bf2d24afcc411db8ad3c2bc6b4ff3d964655a5d5e55e72c62dfe4bb029af1502597969500b93c0134f8cac9f66594aea7347e21e4eec4166541a03ccf0be5c7e24d33e3a57bfd0dbbefc8ce28714d78a369bb87f3d1447f26f9cfaeec65cb142cbc158484deb1b82e28545813459ffc10e6cbbf855dc72544c4df4205eec1b934b87518318c79e3abf9e706b80fb73b19081f344d7e272b1289104d30c3dadacd25ea8e28cbc6d1da0353f346e674b8feb4f1980d19b722e5b0c197590050d5c20bb2e3d5369763d3e7352486e0fa57f6c4dabf56f7a350dbf01e73b24c032c0469c6729c9fe210e2419ad65e4f6848017e18f5ba65d72f3733aceeadb4148267cbadfe7b21f12fcf95ae3584227128c74bbdb14040b720426e7e3d1ea6e7d23a4bb7bc973d46c867c53464e3b500333b03a4fda717857ad21acf7ecdbccb5c62f016db95b5bc9249650b696f69bc53ed6a41a20b00872d8fc88e52885b9b8856ab5f0868b21d36b6498801b70ca22f836ecd5713a5572e3f85ba611ef0d69d3ee322e4d82e9565d16b9944d80e8357b288a5ca9681f502900ea5181b7950b8b8f606688a7e32793d2695c49e6bd3cf7947465c0d8b172f6f7ab8c403af8356794055e8d3fdb41df400e8bfd0ec36886d6920df17fca5b89687c28a43d46e47c20c41f0e626fc71c848f3fac6fd8f6f6fcf4c328efae6a6d21212edc8b79170213d6eb89014e8cec02f869b1b735ea8054a872181009722405f9dafb4d7db5626803c1dbf3716957ac20bece27991f9110186290915c725516f6747d6687f4c748989f9bab899ee8e8c0e320574de9ce621252d7a98a6029042617dbda870fd147db91573f8b50e1d4465301e7d0d1e711dedec7a098491fdca3a66067d6fceb31ad36dbb2fd92c997f744fe7af340eef510ebf0dc72722fdb1354b9da81ef977a52843d1af8599776eb9bd6529b3153cfe8f727fbbc72c4b20539a45ae92f1101902c468e586254808560af368d2505bec1b22d9a4c5039b20ab25b5c8e17a4188d0073fa57b30c20da250b7d97c60a89b85f0aef6c308939a1192fee65a538e775bf8cd82fba17c144f6080bbf5bbb910dbbb8c4935d5fac68645ed58c7b226c3c275b6bc6f8a8f8f80282b13562dea35683d295a2393629379bb984c18b1166e42eeb264666b530bbfdd3449dd9758237cc517999568b57121751350ab7019a5feb8e959b967018250829783bce3d13452ea698377215048f10f786e606da085074a81121672faf05d8d706993e1bf12d0f6df5ae7234d690f93495bf1aa0c6b9c620358ca50848f157b13f016f297100c07f599e72b6485a7905d5b6077818be8711444b32c9873dffb587d5937bee071892ce9e72e9dd7536d4d9e5abd349ad6c4735964e7e68a8c6005f77078b7d21c0b8796872150fef2e9f5af0a62de63be673ce3919f794ec5f71144d25d0e0bb517c8abb723d72cb93f9aa2b7aa112b13218eb8f524b8db1bea060c730efe60029ac7494727d9b8ff27833f5b9b7c5a85e3a359736884cd6081f1461114d4a0a535da1810376c6cf106035d7e198c1428e42526d1adfc5e907fd1885d4dd63a23ee45d2213417e5d1e61635361945e8b81e124939a1028d500e15c2a4b33f056e4aa31da277b51d8573e44d147fff0003ab828de6613d54e47d63b03c87d2bb43749015961d4d15edf362a352810fb7e0ee848e6232097d557d1f70634d7e39ba61b06c872b860b33d8b718cdd28ccb8c8ca4c35100060e1fa94d4f48921f390ecb2f8c14a33c33c27bc7b75b78a3df72112be1390c2e494e282b6c4cc1bd12737012d3222d346c4ab3873ceb4900c611003ff2b55923f08b4ff72ec46a3bc192b6d777210a008db3649e9a9487ff8016c6d3e257bb759224f25d88a300e5e70509de54666ac5003e1afc51c9caf62dd1c78c9b26460263ed73a2f675a09c6427348d79a285246b1be213c2983814db5116a94419f2f91149ed9fc39f29ea2afb15486257201a68180eac98f4c38ecbae2d3b789d75ff049415bde9317125fec204f62bc72f22a92b2060e5a312181a33d55be8a9540021fba92d309fae96d248c2f4d15724788ab1b022591bdbac963149f0435f21dc5ef7eea783de58f3e1d7fe882b17276ccdae0ddc7565e79547515ad87b354c13f16b2534d3b2835489f76ee57a84d07dfef7423f608039889cec153de18d62b3bbb80d59cf73f669910af3d79077282c4e0e0ac792b3a51fd7d6b459f376bab1ecd63cfb9963c40a4dd416d37a503b226691f701b9c211cffbaeb23ac880e28b8c46eb0b40d34f01bd811868715723ac015fe0c90ba9665ff231040713c20afbce75b4e09e6812b0b102958f72f7215f07a19522e5465f5ba15babb6234281e8fc81c8dbb287201c19a560d5e2872acb1c440d4597edefc84713235f2985c699d7c82bde0bdbd21f475f72a694972191588900b853e16ebc8bc6ee04e4000c789b73792ae9f4963a96c4980c5a2531019bbbd951aa7dbae207d88a298be95c613d3024e4059d5cdc43b29bb66840ad854b6ccae894802f7c0a6b0e2ea3b9e6c37ffbefeabc3cdd749948689d5de45ff13aa24722979c3e2ac2c525efea7591431b21fa16158a142619116ba87b339cc0e4ea71612d2d6153cc1e2159ba31a3735d023e3b972ccba8eaab4077c1f1ea39dda1d3f22674f2f30554b9e3ba0ef8a5ec74c83db5cc3feccb6c1ede0c8169941ef30ba1bcbe5e515413c00e88d6b51966766b2ee3c64c1e43c061cc020246e5757adc61515534dbe30e90047b46a9da535bb40bf25c1a10b98a2c9826e721891f11d63bb0a144e719591ed3144da8b6c8169c96a09ef6d6b873320bf9f39a17b131bda4fe44c26afacb4c8e3010bc25347b331f39f0e1d7953eadff6e031f689760d7c04689315a37f517cfd363f0610ff5a45a1d3362e3d1c99aac8b3726aeea188811faa87ae78bc4465960a2e6cd3dc0d5ef87b7bfec7aef1bde05a6af6b8ef8714e9a15f55e6211472cb4baf423b6f512379d8da2227c311e323c272db7469d018290246f731f5a746683c601e96d3f1a51aa860e29d80b99daecd7286c105854a166624025543561d70a631760312a292dc82562d951ae89ea2b672c4d3b60a5d1e41161b19efe6b759add6b64c966d6d2e95822fe0b248dcafd15367fd7bd53c878a8b731dafb6d3fdd0a9fcb22c21b9ef5badb008c12613ce06726897d1272b9abe6cf0007fba825150dafb6de6854aab55a73a76d1b6fef42660fe96fb97eb87364b6ab7736c59f9f6e587cabc1fab98b3599f75c54f2294764ff82215efa63c0a69f8fe98681eed63e66074e028a0dfd5c86de20c2da94ef772b58dad140e17250d00a1e036001cd37226ae59adc4db8a4c476f0d684ecf02288ccdf2125e75800179ca4111743fade94e0819f8fa9e10c2a22195122c675e5c0f57c7912f8425747f59cae4be826e53428102f63208402c0fe2543c626ebd729da7be6de9f001e0e3020dd25253b9b894d1b3a7bd4b2831f2cb39229980b13a8d45a6d73f5d7460050e440b8425cba6fb550bd9a47f29e7efaa2368a70c9141f63adf227ae1a606072ac29f0cc200323fdf74569433a24d5d45d10bb60fbe72697f491b93b7fbda060f49464e98f6b7fd4a0c23b70cc942ea1ff40e3d9a54727c817e34c3189ec81e4e6c18e0de9cc97868f0edb0cb889f136c6a0f8e4c0e72202d056514dce7386abbc7d61085590fe07083e5a24660edb5445d492be8566860c2437f6660d8c6623b690556161e7f875d5ae2f947e065673492af7f0a2f721d2e19a8d199da272b266c5569e9140d925cc36e443041253af7b8dcfea50b72114601de8d643c62882dde792ddede7e6792966b2cbeafd5c86a6039a6e70872e8e6f4a2235a88017b7c4417b243ffb074a77e35168b7812bb8968cc903fb656758c9b1e74b7eceb56bc93b4deb0850f96b94543852107753ebcb32a8b187d1c24eb310f5fb422c12b750f8337ef911a46d41c6e41e830c0239e14e9aadc0f72246732a6f47079f3d48e3d209050f834fd4a7aa5b656d37ef937d962594d641a14139abb20c3627f5f992a40182dcaf4a25945c737cc634ec3db936ab2e21672a5fc807987742725dae8aaad6db48517b6b2eea8ad4fecdbbd00e5d4ae27c172e357a419bb96ac32d182ccc0006deb2a1ee833ed43e1bf06a2016c1928d72f72355098db30b4b590e5fc191b95a91f3e0db83d7bbe9dc74665cdc85495e63515adc66ebf8784cc56892b4a6c50d9a3a642f155e425dab61f2d57498e262e647207a65ce52b4f07038ee8e7627de8ab0f224e4dab081ae8ea0682675866ef4d729471050bf173fd0244bbe8b378fa528e2883b69d09c23969ea8ca5e91267a645d497c861b30e5c642d7c4dabb778928b68490e4a3d078fadda6553c17388e70bd104d6271459a2481fa12136f64547085aba6602b3e91aa811533c6ee835b168067f0240d1b6c546974f91aa2e45820b50a7c787ae2e362a1db1ca4e6a977e11eb5f71b3c24d220482377cec86848ad027199da7e6245ef69b6c233c83b82e701e2d8d91625fcb8cd80f970bef79c981593bb440886e310557412ecbf4a6046aa154e98384629de25e8b5d4418875c6e8a370ace778d263249813abc12ec0d7203e19845d3d3425fdb2a3f97c7881b83ede703719bbf1f0b36197429bb582040892c2553c140da68002bcb599e079567bf6875ca315e972ff16c0f9c7cc8aa72b0d8977a86f228175104427f09660ba35891d4e4407c95a37ba4275756a62672425c8307cd2ce249cdd8b8a0631605666a68041a2be2db57d81f3f9ad6f15472a593daa11309c58c25a0307f0afd4f7d0c13b756cb77446cd95c42c138c3b6724a58c4835d4fb55e2f0f9d1da59687f1da73c39139b50be37c36ada7fbb9b3722321da60cfbecb79847096825e97d3232bb91bcccd17e9ef30e613e583fe3472803faf4a75f4cc8bf7b5a5001c473120c557e3aaab176fb7041badfc774624720995f7eb955e84eaecc39048e37dbc16879719842d901dbc7d6683c7f450a9695b2f9dc41d37d9b4d4b1a4a8b1186f25c22f765187e6b3f1cbc38ee05103987230882ec5a82464f7dc95208ef6ee5be392aa24352ae2cfed5457e805a243395343e46663e8524712bb348238f95bc0723ff49d9b098b6904ce672bef6ba3147240db3adbdd543229261f74cd5846a650a66c55e0ad4bfa3abe6ce2b75cdd1931d0ba4e00e8479b50838a5883a9e322f3271536a1109b0ba219df866f2e0c5172fa5b2d3ef9ddd1daf4fac29cba9514e73948144dd161838d2c30b8d0aecd2872cb306c391cea16f261624f0339eaa1aa9706c77557756c3b257da4ed9cdb6b726b550fe5a38f305d420808976404ca27dddd95ccf233a6c54e9bdb76ffe09e72587a6f136e5ef8e8b519f2ca388ab9d3c52166fa73947833d3a80e1e8bbfe072dfa40f3c45ed7339cd709596899d56cf8e01c8ad37b96e8951979b36d6a8b872d2b9dcaf843607a32fc5c16fa9167cb18901608b13265368d4264ff49153c955511be1310f93c50f3faabc7e86fb096f4f6b6e50e56cc89752368036559ad272f56d9d84017256e025e3f590aed2a150033505ea449f19730973485fec7faf725346e3594abd07b4386a5e4a5339380a71981072ff41681f8afe9bc00704de7233419caad802710a4b781bdad09a27db8825d287af9d2a526cdb207b7a52166eef938e23bc9c3d1012802b18e73bd1d61ebc7e6ce87f88ffaa67500152752472a328160f3e30f548c6f9214d63e7fc2ec19a53cb0f7c37536743d66b6e4f9c72585697b05a0ab77da9841004c098b6c10e57d8e4de66276bbc7f2f7a21042472fca0bb8a3bd0befdf153403b6629832b90d366883468fbc75d8ea72b9f892448c74ac8bd1d8ffa853a77e3182aaf7fc93cc24c4dc76a77d12b49950f79cfb16e83ba4c826e9e7f215170f9d23bd9927ace9dd38f328334f6434d59a23cced972251cae5706d437929fcbf07544a861248589d084ab7c0d62d47b201e767605729bb3734d46f1a33f5ae1afcdbca268433633e4422036aaffca8fa0086c0530085ba5b28774cc823134ce83d7d8d329470f44ec7863a24dd6ee47c50b4771eb72fcdb0809b4ece5002adf7985ab4bf45ced13bbfb05df19ee123ec74cdf6d5f72c19a1eb8fa72eec5fa035dc15f88b87a576a3d22a66b9fbb48027286bde4d872c1e4d3558bff185000f57a4a876ce9a9f04e8b15e109d311f80c3095223bb5720f58faa968deb75f3cedbbd32742bfb6f06878f36cb088cee6503014dbd70453db21173c2f79d4751cdb7d0253543adde152d20a0cdacca47e9c80c2ba8a13728ba8495339419b97177de4dfe27ddc5d220c89019df8e698e1ca56f4acb8dc721bfc3d4166dc0037885ca4bf85bb1c36111c24811750ef44045cb3d82a380472374da760da2e61ab04aecd8521915c9b0109c18277ebea4b36b561ab10cc5e301586af7bd82ae1a471fe64c28950066f86166de67238281a2f10bb87e1ca67603ee10c2f42cace79d5808c1e082bd61755fb657c5de7652adfcdebe06d38530731da6ffccbe702ec595908a3a4e3fa58637791109b456a2f392df5c68188a41f3bb80448301e98ae55e319cb76f7d255c156ee02b37ef9f471f354977f270e72295fd7714b4e7257f446c7dad2709abb40c55d6fe0c865b3eea824705fb63b52ccd2663f22648a1c8a070ff213b9e56ab438594f19bcdb2256ee2f9f9ffe295ff6f9d36873d5f36e59e66f3e3f1ecb1902fdd4620dfc4bf998a88e748c3e790e1cd60cfdbe25cda5123c13b1296f82f9a29a3313437fe9b129d9b753250d385f3b565c85b5f1e46a8cdec2de2849e50a477b228985035fe30d6411161996ae00e0d06dd7a2139dd54d5451d907707d8aedc71b8fa5d39405ac95818edde0273ebd8d8b4e7468c00e74e1458c3b7ccd0d019d9da6557b441d903d76adc85f5d72a7bfe03ef84f0febd7e6e811662d2e8aed2f7f3943c4a9b1063d19b903ab33728294feb153435613ceb083f6a561d3bc8353ee6e5948e89b7e8a632babd41772c881250984c5cdf4529592a9f84d222ab1de62c600e48e5c53f77bc4627ac4722d0aca458c4de30e8ea28e6c574846211057614f501c2dc218a57d8891cb16205a5a8dc8fa045cacfb3b3a546dee1ccef24e0eedaec237dfce6ddcb034c73b72bc55f2b5a3d2959084cc990313fc095966bb48bdfad9c65eda6240d524e9e45b62fd66427b983929d6cef3e9f8647921c34e863a1bf2f400e163e49a65fe79088620e115698b496e623e0d46dde4f45a8af9e432e36107a8925e6948baf25c5fa04bb9110f163a7ba3c6cecce2ed269bb1318554166dbcebf3b4a48d024c113eb3fc7d5ca48aea3580b8da44d188a1d7d5e2043d7d566ed4b7df6e540668ca72a7125d7bcef42c7fa73322ba9e19ffd89340841f8055aa048ebadeb9eac9e32f13341244b42b0e12d88d93313c404f6020b3b1d6b5ed2a89a1d0738e4ea9ff721a078a001dc44fedd67fd652c562dde767f9800571c937f292fe43aa8b4e87724f108c504c2cc018f51dcdbaea3300444628aab60a1847872ed2fc790ac66a72f4b6d73d73239e4029c7d256c261f6aeb1233a105ae8f8df76ba40541e9966723ee1db46c2fec6f910049c4019fc8ea58edbf5b129e4137b94c471fd8859fd729641a0f06c293001e352b94937eed68b9224a97da1ab72a13b48f0b8c90de972fab8b8f5722be35c1a461fd5e4326f3526a99c86802b3c8a63a251009525413f91183514efd541c32f3e63580fdf6b09d83d7aec0d2a1acae76230ec47a8c872244f53455de02e784c05841cc1a86df5faa767115e63292489ab07604d5ff57216431efc65536a28db1e05bdb69d813f34957fb39e909781b8bde70ebd76f872689f8778e8c70481d092702c0a99b3187893d0fec6fa8c28c059d6e9760fd372024b217eb8c533acb2546fe2d1b9b667f570ea6eee699a304386264ebde49c44340654e17ff6716f1c1a2295bb2dc1018e549bcefd8a45df5e155d6dfb740439da79011b9609a8a97add48367913ef933596e020a64bc1f67c909cb620131372b82d5748c848cc904cb6da8d0c4d698887ade4ada931a7f9267e656394f4cd72977b35aafe623da9a27f19085eece273904a45f692879d19c2310240d6afd96ad3d79997ae448588dc3fb773e740c705b2aafa5077dfbedf9d900078810c7027c2b214e97d76d3667a04c5c55cd923c1e8589933a8b95444f049dcdac8b0d54bb11f908172f902625f1fbbce99aef67b0cfea8c50286fab192ad156bf0c6915cc715a4d179fae965f94a7d04452b5f16fd8e32b61fb5edae3fbfa5fdf049b6138798fa35f88e1f9d76e9cfd39fb23a9942a978b1c7dfc5a69e4b0ad2e3ec1a172ab5796e5feb9cccdf319df3dccf9d272c3f7ab6c23337ba8294e6589f983b72e7f52848bba42d19e4315e8fa6c70ab6287d0eb19550b71dba43ce28a5110a16e3b0b165393ccc66a850d65a074d96f87aca954e7244a6700308adefe3da0d72e4b95c660d495059b345958a5b90b58d287065b055d0e2b357062abb7ca335728dc3f402c6bcd0aa5a079509ada3fcc0a9b5ff2136c31f08939e662b36fe9e2a0ebb7e7f1ca365db0ec6a416fe81a950d0557c39b6c3f72df2fe1f48dd757138b212ffc504b7801d4414d5e48376db6d2aa89c0a27ffbc570e6d3eba599c06722f0fd6477fdb342f2cb238baf7eea3b6a6029a0332ce1096e877dc013f47f8725e94dfdaf24464af20df182b74986b3d62af70ba08237b567d0fb79fae57230ec210ec1cb7e256f5908c601284e0c4ff3202fcaf6cdcf5b5d60c21f4df0da9720765373f02b8c0e8fdc0ae2688432356443816a8e9a75028f64c487393673107d1516ac73ff50f5de3bd7a8d4059583b1a03bafae46ad8b6a6fbdc9d06d43872fd4b475ba61d42e59507d8627a12ed3d9a57e2fd5b220b385ba248707c2eab3e04002048a244c9ae072765656256a56c8dab2475796bf7b784eacf123ca2c372ed5a177b600308d3790ce4762397e76a86c07842d068261d48f028e05b6b0e7233d50cf38642f8b8ebe133740ed60975d08ecc9d1e6d1c93a10af064a69e98725394aaaa8147c2b6984d32dfb683b600ce3b51b72d8adbbf3f219713ab514a5a2214612cb9a5fa05e2e13bd27f795595b88d1d144fe9ddfa3324db15bdd964728ea03124000d7b23dfb6b6ba12d5fc375c59bb8f0d86e8b0fb3d7032d2b7a072ceb55fa4d238dce00913da3b19c862dff39ecc80173347f4e97536c62d2c67040e2250002ae61a863aeef3b9624075b61cd84620b09f004a62fefd6a24ba997204a0c9dc4314578eb5eddc076a5a94f4bd9f35ea948f1b64c123abf1b1547a2648bc29e128f0b7fa9c5971a98d7ca2190d0ed6787b3d3819c7b7a999e71960729def0e8f65f36002aa0ec69e92662d008420b6f821679ac43fb49912bdfc1d72706ec09ac0919ef07c1adbf89d20e6420d21f031850fe7b6470ef3af5a95147288f364f9b48e9d5c9d79650d0fabb45576f4414271c2d51bb7aba44acf5477481c3dd5836db486ef7e9d0a4904dfc1ee98975cd1600550b744545e65634be25b6a38a3abfc74795c45eb9a802df8fef1770e18751c516cfec333d01250c18316ee7dde13960ebdadb637344eac3ab18d6619965304af6df9dcc82b7c0e92e9723b42270d197b9b287d59e03940afdff0f263deb450a2013afbf0002308e5c9720a3b35544bddbbcca95a84d3086cd07d92754ba78334878f813c7bb2ef72e3722fbbe9e5736be9066948bebc415a9ff9c38e66336ad097c2a7120cab4b638d7238a9d684775037ed2b3be221bf7b2cb8c7f76ba21860ca0e11c8a5516973c572ca7bf51b2da201257ac810bede1b013a29e82a42ebca24ab1f56f4ce3de59d55aeeda2c778a3a501f86e862e8bdc6d5fef9c60525f1a63796f8c48b0a80020724227fe9d007efb1c55c8e2cc865bdcd932ce00a236e9bba7ee591818123887721bceda94531c7663750b2d3d7319f06e0c42f8c4c9b22942587aef109cbcfa0280b4bfa83e04666bc46e7b581259140fc7618a4b8dfe3e193992395814d8a17257e9d8f04186eca2662ddc46b96a1d64a8ac4650fdd6179adf686847cd420b3f4c5e436bdfa14edb2c8e19c765f816e02445ae8096d4bdcb51f117ccbec09939255345c10665356d499a3eeb2da242099d17a8a954087cbd1c8eb263bf7b5a45fcf1f9f8fd347cc9e21ba6cdc2f8b82d8a12b5a1b83cdfae2a22f31e9cce14181ce3ee7659ae3f947d4275e7142f2aa43abbaf44c73bb02cb801c727327a0e7279811568a6394858d4a638f43eee09c09cb6ce1c5e6473ef7ba724f70a35c4437c35267c181f461e6a0765e4ab8e71cf46777d9f4b5e2c4828488863005af6725144f9ed484b3cf3ea61914634bab51517d8ad9f55e03f15703484eb68227e726a506269581da5744f1b807c5b66aaae7abfaf2de1aed935499ac06b3ea98a6549b718e5a4e64f4c66a21a0cd6a236d06ae99aa2fd6aa617b422f6bdcd85e3720bbc6c66ee0b39f11a2a350b060739fa748a4bc6568f645363c11bcf3c6b3472c59d5c37cf24745110faf08b1008176cd46be0ce5666532b87e8eb45b184407245f0bcc574ad4a528c5010564463b7334786e31a0059ad6fc518262c0c5cc87289f6d1d95f43055d3c0c8ba79e97e88c594b653a37c2bdecffd88652d891043a57f287921aa58989cddcd1b087e35a15bbb468f35802ce379c1f9deb3e63a97225021458821e27a6ef1a2beaea86d638927de1f31c8620a90f17550812cbac58116768a7d5e0a55313e39f3378ff40d03bc8d747dca39242e4ba57376940dd728430833ff212b44a3c45b404d42a57b21189806f4eca8c9c69a3918f86640330840995bc4038abb01e12ba28ba47a1f931abfaa5a4a9c7fe161fb97cfbe24c704a7719bf75a2c99fa61d3b02c7c216b01bf1b3df4d40abf6673a994e1de2716e908a7d6de6e33fec8bfd0bf61c5c4e60b9728cafd5cddb2732c4977d31ea2972568cf9499dced170a1658b150c840c840e1afb133753710de3cb8cfaef087e72fd9151eecc90e32717013ada69cb6c4b9cc74cc51024cd31cf36a03590d89e1ebd3a37bd20546af0f13f9b44beba8e7e857bdc28c1f4877ff347dddd93f81762c59eac576963e0d58c9f0fc23b4d5af6c4acd466ba9c03218bd21d94acd3de7293cc9a4e500e80004c20f577b2ea8e2d84806d2666b0625d63f5ee87f43d96728c079309af6ef32481eed560c6b9cd2138ab3af3da7de63de34d1d0c44547c72d558475270ab41b1eb25524dff965632484d413407e84758ab45ecf4a4b84e7246c5f80dc39ea00033f393e4d174806f4e9a025023b128c39ecf34373f5ba8720354f02402724d6f9d6cac9df5cc67661eccdd9e66fe80ec505e8884b76203721d8314ef0488989d435623ea6ada3a5c7ae319ba6ec48adfb74f8e767739c2725ccbd44592c7e834f2405b93cd3397ce433c7f6cba938d673679479db52cb672b77a91c4d0fc3100dce72f2216c22f3a33283a17cde0439e4633133dda5e622077551b7f175d4db166ded407a6f6e0e0e59ac62fd805185f247b8b0210ba0b72da881f13928443aa5b1dcbd9372e363e1f021fce4b1faec829b195f4842e677294020e8b8bd32f2701929064b1926ecacb476db4c1fdb0d235ec0760a3012f53e45d69ad74a4d08b2d1247d301dbabfe0e96e5ecbc97f73c955969ddcbfc8b7284ac50e5a4e23a5285770c45c8fbd1ef93346982606066e674ec57aa1617951486de0c79c2c08bb2125b2844f0dc61669dfc11c4c190b2abbca865502419f0725d4e6919a415491fae19a7d4c86aa93e0ed5899701c0a8bee7998584dfc80b72bd3d53f6b8d86011a69172dc58eb2cb57f2806fa10e09968f182477dd04b0672623a9e6aa7b998d513b1e3f80b24e0632946e5f39a063b01a63fb99ae78a14433192c05294c28387392c63f1a175bc2236a506efbaeb620d76acb13239cad81846a3267dd56177dc17b2aa2c6615ec6627e06d5144ef83d5f1c236f0763ce30a8bc9cf2c222e2dbaa67cda4ed6e27ceb1440cab6a6deec45c2f73c06ba33a772002709f699be70fc848fe0c4524df710c9e9dc7b17b6785a525474a706e61b2a15e555052daa3c2a518d7ff32aa135faf43574270481ab3d5fc615fe72dd005c3010b0b310bcc1d12f0bc1cba0f1e7805915d8815024df1265575dc0b3310b1133b009cf688fe5e14b65be42b804dfbdb0af2daec673d0cc108ea663cf6f1d724e91e7b85d9adf658bddc04dca66222230a72c127b36d96ce5dc30edff8630119b9979464d66aed0537ea4eed654d9b54de8d339ab4c8bda101badcf6d344e37bed78013f8115bd2ddd714a02aac36f70f955dc2be2c716566fed33cefe2cd72a64ca092c87dfeed2279a31406fbcccbe8bf4c7e0eb6baf675806e0d0e9a487200aaa4c36a5ea439a7ba70aebfe71a8f4d6409514135d64060ba44640dbea372d4d57b7d73ed50275b78d98d29936dbacc0ed3e7dfa594f7b8393260c086d672266ec1dc63a4a476024971b717830a3e3e8f1938563f42d5aa6909e593bd2f727ef089a87b6cd29424d5d88e4e18aa94072fa920ae5076aa79ca5ae0ab177f725e0cebb8131a91df42d9a246e5276776a303e44ed5d94d68ffd90d7892b5ed724069da5a1d93fe7eefc891dd3d2bbc5c970857faffca94427cad04924ff8e672e2e84c9d076346937264e86c4bf4490637a598d20ff21beb9a98664306b8ee729f1ecd2cc10ee42b249d99641463d8992c9b327de1a5cb22304ebe2146519172ffee0496af4993bff7f98b992ee53ba73278a0ad4ea70481fff18b6db9aa0c72a93ba0484a3a4374d42d557d5ac0e7e27b8b15f8b46d19ef03cb1710ccb348253e776d61364840a496899c8c72608258433f7978a176dcadfcb02dd9c9cfcd1b73f716e45dfb755c0dd486cc1a6a5ff478d05098b4e4084b25d484642df9a77293fa916219247d4c24f3008357a48dedad6d37072afdcd9af874d5ad79accd7250ae6311a19c795d661193f23703844e6c28314a2a2e1c0ade59c96ddd16ac721dbf80e6ac0deee2e49afda99780a90603b382c60d0649ab3979ea3d3dfb83525f99d02ac15c938552ae300d213680fa5a722ce74caff8e6bc84bedb1a9f9a7298ce233eec2ad2dc792d3ac92bc9ee6410cb04a5c33802dbf3e3d70f3e8c045c46ec43dbd7ec7ed6d2d30886d2e53c9fae98a8ef24a9f212d996d84dce47831bfd430910c08738f9912b035b6e5222e533d67a4106532c593640a6840acb6572c0db975cb53f1e75c5b1a97af9e8d53cd253e1ef743167b4d1d5ee38285d2d23c14969a21f0f617a8240bcf7c3752d843887c5e3d3f359b42124d45dce5eaa72a71c866dec24017eaa188c26a89dceb5b121796be810a56df66f55ad25b93c72ca56d400909e324aa11d57b5758cce0730a47770bd6bbc7d14de71831f54ce72f5b0818c35477dace5245fc248f4da121f0d5092b1428ee26987a8103799363e027cb4bc40339092b548d7666ccbe32a469ab2fbe2bc9e5f022d2dda0da93f72d0f627b70d6233e260aef00e9384e7253574fc1ff82c48e44e26e182094f0a720d00dc3c42be6ace6a17caa453b80b0b5ac803a92899c62060c7fb9ded1958728afa283abeb2365d0d26d583fdf9a73a55494ca4bff555cadce915ad10d447159556044fe46cb8045dfcd9fd7f52e91908093d05ebbdf8a51ba5189e83b24d72d81d5e5e48947d8de21c859310a961c348b9e98150cbf11b03f20134328e75729ccb626afa5816dcffdbf29db637a0c68ad7d0fc81eaba5e9a4fa0a802ac4a575f976c07a919cfaa1fea12354a5ae2a9cb273de4579fe468faea48c0b6494572642e5d4334d3b32a03df5730ccb771ee063117d648be2b54541332f2184fea607883eb264be75bf95e7cfea2f916761ea7ffefe9785d2fbbe37355cf568e3072b8abeee95b52d5b533a2d1a374de4aefbf464d29772e4bfd68afd21de68feb1b01684987af0fddd7695cd2ee4eb48df0c3838684289493935d5acfa159c33350752770b674e1e4912ec73a0d0020bac8a3a4a4384a0ebe969ac33be0f449e030377fe1d1bd917ae5953cbdd866236c754c2aa370bb0acc418d0d39ed0edff157e1cef7b5d55c3a5364e5e593c08796ef5add7aad88dae196b63c5c69e8bdd272d521065831483ed98bef4e98ba11b125198c4b3bf15d914a8e17fe3e659a880025d6508287abf682cdb3519e5b869337d6da285f3a0b2b1795415d3eaccbd372011cbc6a0a2e5510f43302473bbd9b2c6b52bb8c4d7e96bb9ae46fa39527421221632cc8fd159ee9e1db2590352347f4ceb811fafb13ae4b0398f0f70dbf9f34bdf9307887f4279582b7946830aff473063859ac9e963cbed84417676421010de6e77d7b5241fddd21ca87e93beaeed1e2ad6f9ba17e2b4176afdd60c8746672d967b0eaff91fabb1cc62b18dff754b40131d456c10305d545333946df2d844aed6f93e94dade00e6a0716b9864cd3e14abde3bb6d2de51d64f156d76d5eb41f6ad2830facd54cbb123ce9ed1e75ab23de7b9535f85fcdeb632d609ce5d09d72b7741e504dcf08ca4281341f548bc5f591e1e385e06a76f059cb3c19a167e472870c1775f880e58ec1211604588ab6a0330e564223af237e4942b72d9d1cc85f66fc89e6f1e7236c863ff774fc7fad13b86abd31bf6bfd815e2a1ed36388d972cc4a23dae9bb91cabdbb2ec8544ebdfa807a54bcf31f665b8a826acc08149372f6da5810c990eb0fc3bba2abe88947529812b2f2f0fb391ea54e6e4c5c293c725b74933665c208e9e9661bd8ea14e66dfe1637db310f799b8973a92f6fb213727a9bff0accffc5d2d89a22f7d30a7390caba563100a21476df5ab8cd52835a72ad83205cdca65e3a49980a713c9d6767f7196db7e2283433076d88ad22747972b70846452042d402eb859782618b6f81f31ea71da447032624fdde8e1285ee017b4edaabcd18a71eb2e12e446934272263d487449aa1f558b67bad5c71a63241c8cb64a9879fa0212e44d4de761900ed506ab67394c6d30085529c6f3ec1a1724fd54ec0a9a40340b6c426dc828f923de216f6e680dd8e518cf2e11ce406e4720e2a56a0d1bac6cc2a93300f29ccfb756811f0e7339372112b04e46fccb2787204b7fbec7c0504fdc6488b8b7b55924f05ed6e63bf8ca92327ecbdfd3efe8e7261089ddd684063d777034ba3de7b82826f9cb42823efd17b48d5f32005295a728e4d21598ed504ef083ed305d31f60cea0caeb93135c6108bd4384bbf3afd042313bc212b3bdd193fdde193c4b57ff00a3e52418f5931282112f8bdc97659272eff1377492f3e2fd5e4eda7357b83aa81b908afdcd5fd9e7b69e53da900da8085649838ccc5d480435202d350a15a2240fb82d79700afdb74da1c41a93782b666fa36302c5d1beac16d243bf939758abc9a528bbd01badd9d90dcfa48ab57939c88b71b0e1a737f09aaa6d36ba7399914b6ef8d25a56dbb4b63d56d12de18472c315f978ed9b5612b965e4ee1c88e141868efa86fb673d1f442e4b99e7e20e210b5c5215b40b2546bfb0f6640594865303a3a544c1350a0c86a9753a34db2b1f1e932c00ac587b2e3005001ea53df95ac90d14e2852244f461dfbfee2c49f972e89df9c573dfd855d956a48ccab27e8eb3f2a916018966df534ec2412df9491b73aca0889cabc1ce4b8ea25c28949c0b3b59f77f3fb0014e56fccb6e9cda355296f7935a2b4e9454c471470cfe5adec083402e74121627ff32fbafba898f6672315cd55825cb7d508faf5c8cf2d9f6db650c2d702afd1d2ea9c0f92db7719b70cf26b61b6c0a56d653daf9d0a2ee7a59c0291959ed8f6851ed98b04730964029d2277f95ad79be694f76a7c2d4692929ad1079cad139e3dedd4128060efd44578c513473cc11aaae9f643e34c61ae1f310a9f13d6493715725e37041e43afe7263ea2e96c41db50a41a4db9a164e1f860a18cf155a7f77fa51ae17174da7ad11b2317bd3ea9b2723b009f82bb306363852a4c3475fe4cc3f2885a1a890a0953a26b03e2655b5c1fdd2226efecf4bead99f6c506de550ad6fc627151909e4096cc58b303530eac0197488a4ca5a174fb46cb250cc7f8d667672d0bbd2cd31ad5e3d07d46ba3ffc2ec697ca75f2d978d4cdb382db537424e51e4a60d4ee4da2a601fad62090e5e25a8698b60e4c4ade37d4edc9b714dfc2d7d77d59fbb60da065a679dacbbd9c0ac9590e27f51a836e0c090bab7f69c418e251eabb7c3da2be314229e28329eac8c952d88839f00ca59de05b17cf210a8d20528abce4c0554c8725e6374bd0b1e6d3b6713f89a41aa20e496d027cb259055534289ad7c44055e220f275e3735bd50f091bd49b565be7abe7cfde3fc34d6c2923e73a1c3a15c4a72f4044ecaa6206cbae0d647c5897f5e4fa1f1c6b918f5c198ea3421e54fefa0722c5b492f33f4acabf825bb84da1161d431fa4ccef7ec02c86db9f31a0f1d4b6669b086bd1eb16aea3ab301b34df45700da94f493df6a5ac1f12ddaf9cb9275718ccc9cbb7ddde8bb0ab49ff6f289b5fa0c638b8fb54adad36dc47f58e6b34c72473f911e7958aed99492ebe2c5f3f521df29618c05b941426b155ee1179ff072eac9147a532f95925d15de76a04742a11026d230ff3cdee15b2d34d622b5312ca73b65ae7452bea6152ecb6a5ba3d33e6a40c1583021e67250ac94e93b8d2872ff52c74a4d6b5a117e362e6dda746c6b12a8562c4a9a9fb373ad8f7e0803d5456922986b5fea33df79475344998aa5ce10b7c765b30b7b4c2e19b25cf196985aeaf8b217f7efb87dee1fc6ea51e146c52ef03e65c25b7aac5ceffffcd26b357206e2b52f8e14bf611a3d0a7de9da302f9d6e5b159a49dff4c1e700ede4527c7206b2ae59ae8416437d5496464259e56edd0a6a3427d0df5fd83b363d166cb33294e16ec2237b0a450a959389c44d990c5fde6a92b4083ab0bb8ff999ca7cd172c86a47ad4bb1ff586148729fa35e028179bf961973c2de9120aefec76e1073505c4af839d2d742807b6da67b4eeca129cccce1612dfc9bc45047de04a82568715fcbd1519f368296c2ddeb3f4bacb7d58519b4e80be853f00bca6ae0c3a49b52209e96ee4c02aa15a64583a255a8413f5dfd27e762d2a54189b5645585bff47226ef09a48ebf7c77d454e1b4b20a5044714c50a0028c96e8e839e5531c3a4d1073161ceecc02f176a19666f2ff46f2388472d36db9421dc924fdcae7ae88801154843616f8e63e0b5538c6d190bacf50f2f60d6ee67d74ceadc72c524257ca724813fea3457cb0a8c9fa44eb4a1cf4e0e9ac8f4a45c390e961590a6922ccfd7206fcb77f82a3ee494df42a2dc5cd05369d2a95fe9f05bc5a89444bb31790c12d679010937199b67398a998c5adc6cc37dc4cdd2958b0328a9e73cc1b79937c729c211c45ccfc970c433b0ca8f8b225533cbc8ee67b6af8b5c8d92e4684b5e77238937e2d03679e6b7b4a73d19b49f801cc097fc15e5b3ee1cb7d9b2520057a72e8e6d52ec19e063002f55641262a564cf8b609190ed590c48c954765b83baa724380a861955fb28de26f6e14ffb6fc507de32610397dc5de3952ba10e31f6f31aa9f8909b24aa0fcca49b0795f999402d38b1ffd50ceeddccd6383a52f3cf172fa0b8e4603042e77cb9c6a3bd8e30d387fcfd05e46e06d31abca18f2acaf8b6869d893664a2ce47589fe19bd413900d9131bedb364ee07ab42cfc8a4dc23f8151b4d66d80a7c6ee9b3ac5e449758e315e87ac6aa2bcd48d7a7ea61c0a5f6907298ea34cd83741f2820873d4614074c473a5163b7db4f84bde5a9d81a4b000472e332ea49f50a450d84a18120a1a76b430f1f50bc2714b4838d02e8182106a372ca5d5a387c45e737c5aa3c4005ac9e0911d8add51e77814862324e322d700d46eeefa4ed99b3cff2be369503a61b4be038a0259d97921fadd40afbf798139e079dd880f9e120f26c327b5c8e0ea91fb9e96fc7ad86c50587060864c3b53a386f2f537c44a2fe9731d113afb112d25502da12c244f4a5a537fc39e9e56c1cff20bd4c8c895acc42e843c855c83c47a9b45a9335db961e0a4dc5173ab442561f72675e060667bd9904d63a79e78332086e195707b1333c82cfbeaf4a6867970f72ee39235e503986940267d50436c46a738b485da0e6f5777f974e1271c9b6cb339d94f51123c43fbc1fad24ea4bbe4b5cb852c3580ef32a9c797a3f16d5f11312db501addc68ccc65c6f84fb6662bd89f2828ab145c2e1f6699e32886121dfc537edcf717c7d696568f0f22093680a49f54536cf7c87bbc959e2c8cac4fd60272bce65a4692685e0404118eb24470f4742163ebcbbb9d586c3de2b421546574151cfd25d7e424be81e43b1383bb02ca2a39d2dba341161c9978051d5186135b72096d7e6657303d4033e01bde8bc0db26fbb2fccf6896ce7862a33ac94f6e19119c3178b85927aaef0fab238e7dace76526c8377a2757bf03f05dcfc6eb74a643f208e27e756bb005fc88f3cb2b695d5ff5fdc30999a8357cd09c434b4e210d722efe435a4c908f7c3bde0c5c6a80d5fdaaf9623b9a3e024352770a1995ea867222b2570be2b651a434ddc14019932e16454a27616e06a23392c10506c70188044e237555447de78f0fc5a78ac7755dd540b9b05acea0cdb42ebbb3a7d7209a7257aabb0d82f796cb21b37e34596ea9a0fbc47314c132bf0611fb69bde7c00308c2a1033106749d2b1115a52efc63412da3f261159a58bfc373a856222afcc26fce3d156d5848a455e9da3fd8c740089e3295a46fa77529a3262bdea6fa558c729c09e7c81d4dd480d9f7038eab2bff7a9e0adcd4e9505d8793c665923e0c2157ce32fbb89b1364a602b37d95a35f0e9f7f942f8fe233a18a0f3456f42f5af92354dfc8aa4f7d16cc4e1a3e7e677c85b63d1008cf638f90968060f8dbf8a21a72ecc240edd5c00d8d6ad08d38925fcc38a181c6792c20ddbc809a2dc0b554f172d9d40fe15a75808e2452210475f0bcfb762f6145e3823bf8b505915e39935304261b030bb995662e3586333df1867df17d0be33996d31dcf47ca2c67fac2b65cdba632169469a261ae18c27a9a81d0452e821b9262bbc0376a3458b782066e727cd5bc4fd07db8b169dc19c52b74aaa7f37f5482f679666baedc7cc7fb07a172f805c63bc7f025473fbf63d1c8b5555855bfc0fda6442adf2cc0b4a7eb719872bd9e417a959fecad51d324a3047f995d22de1f0cf2c9c0f212b4dd2b7bbd6e72c0b82fa81660b20c92d5e8392b8e7f7d71e2237a09d9f97c0248bf3c28b9e072c996eda6bcdc8a7f4ae8643934d1c6141868df1cd287e80aa1e585e8a014367236ab8c4d7162fd894458f96e858248000a98ea9fe42e3ccf699aba721aa35553b812230dcf7e404b78671cd26a0940749048b7d906039855279f7a32cc3bc77231ce6c6793f89d71d3d6646d5f30eab475382aa75ef65f8f0f0f73023917b71160fae3564e1c6a261f28b4c31268021925e9313f7a2be2024d7c32a41bfd5e72b8237390d5a24c4dd91bbaf15509c40dd648184a4c85e3a128599f37ae845f72fe18fdf14d5cefbe8c5b2f77db658eb1373624ab9aa3673e19bdc2289926bd5c8e13c30c11318e9fb3c004053ce17ac3fd7f3405ed8b10c6031076c67cd59172a8f3c24cf0867bde69bd26e31ae15d1fe05681322ddc7d96d14161e31a718472264356fa73bad554c9d6dfff6f1887109b427c7707b16be6a35fd03a06370572001a4e6fe75b88d6fa63e15d7fc10895aab97e3b2a236da36fa76144e72da572c6bf331e2390eac9a64d1576131b61f6dc1fec3d4afe96aaacd0fd89ac4bd91649e3e2cc4caf1eceb264f54b2d14652836c0b0c2a3f28958a7c9563912abce72cee44477de40f007505975bcabc870cc94b1961c49ecd84725f8be155eaa0b526258a34997396f11a8564c8e62f4b4036409c6632cd8869f827f993d10085b7227c3f9226a4e10f12c4be2644607ed542c27e610bacb7117afa397301c0c07428ad93cbc78dd34da15382a7e6943ad6b60ccd1d763a901183028d341a50f096d299f5d4b840e084a59d6b2be17865e773fcc2033c2e4272998e0601409940a72daa7aa4b78c4e4f98c561074e6c1bc929a8499201fd5827f3cec34d04df56e72f6df9980743e4fe07758399898e8365f1c453f6dd8aa7c831b7c0cad52c23f6b11b1419c61adf49cc1836088c535dbdd4101b25d4ba1b8397dff2c038cee8f1122486c58304d7dc79343cad266d8a0fcd5b6989ff2a679f58ade4aad4cf75c721f35e6509af5e622cc0a86abf0883b3d23baa5e6e7e798e7cc857c5a54e57d724e36bf8f711219e36fc94cdc8f454e6e292096d9661696edc7bc889e67e32872246656b771d3750e2726781cb4c1d99b1acc2edf40a39dc679ee076f98c1c54b8a67f0783aac2127da5b3dcc30bc359c946b998c8a7ff933719b2a418846ed3fa1e8fb60e6987f855209a3c1b247fd2c936fb2d9ba14c0c060bdee580c765572e1ccdc224f940e7087f4d462f8e1a103f028c80a7789d3ceee78063c43a1747249494e38de8a54bda3c178df663844bb9093c3a4d2e58211df6409ecc76ae4722d8edf1d97d07530d9697d0c2f4790bbbc719bd334a2c616040773a33d8cf24e09e4c138a678bf67df11c2f0ba8a545b410d4ffc509117755d35f73a30135272547813ed4fab7d8ae2269dda92f46024c790f45bde60d1217b8f6cc1744cc27234d5da8b0fcefc3488b229f8445d62a0e09c6ad85485aeafa4f679d48cd5a85ddb90002979b41cfc49f03fa2611fd9031bbafa4ee06121983383cd5784ee0f68db537e5511c6874aa5c80b768b2952af57d4f985cf702068efdebbe442730772ee76dbe8639e331fa7507027b2109876521a2db2fca42e5f742f67b96649d0319e6fcf3f7589498fae13550152cfdb62d18326d93445145416f8ca03f7b3ab6d4047f5432091f1cbdd915b9d3591bd180d01e4fb259e5e1161b21df77287d1234a223c531cdea485dacdda58ad07bea24bf04e607ebafe03fb721ee5efca0e5e183d57ddd5cdcebff4e652cb00a2687e492795d39ae5edb84f3a0189ba1c1972e858416fa32c51a3ee0461bb6a6e8fc45daa12b5d51de981ce93a2ecf081e9728674b385e274e77ced08a6632eff5a9faa3032b186494e2e759a038c1eaf391178199a12f445ffa1bfda077eac310ab73b3413a792432e0c6fe28b45ea60f77271aee00dad6662601f4a77630a893a0b02acc15b042cdc0a4e962bfcfc08dc337ddb126856bf62f1a09feb5b679707e277b1f8a4b37b92f6818f619c6864c60999d935d4820e0ca0f73002b758a30c8fd2498b81b85902c6e7d3ec9b4c28ff1d54cb0f9fe2c7d25cbbfc8c46f715b99aebc41ecdcc66b960da7af2960ab0475b2296b973135376ce8c69486bb39e21513da3163c18079cb87a07607f26021372f035a701cd3fa1279099bb9a5f1a39f539fa82d4fd581919b7d062f3bdfb3072140218644b5e29c67efe5949c945f4fdfaa3451f1f5829d8b1b74ae03d448272754f3fa1a9305fad90d5b2f679e9360c6a36f92f6912c00ef9a759feb9ca037260354cc93a7ece42f9138912197ef93049beadd346d460310c18f9e22cb5c85f1f0ba1d5b0f398f5deb79654cdecca6711573fe54de543d3af00eec731d6b3722a4a325de0eb12176b8b973ad80ff2726983d7b914313bc0cd3457dc0501f87229ccf8a41bab38859558a50734a175aecb074be0d6d0b9f6c6bf9f2479c97001a1063415b3c6bc04b53d2125f419e5b15bc612732b94def50fe8f485467f7a7201c1ba50188569332434c5783445fa7b7e4f52631ede01a55c2bada0eb9a760a5bd15267817bb1c75be12391c9146a77821c1eaebca706f0b9c9cc39718e8132ad5feb600e722616e0bc4fea3393c6ff9dc4ce668c569cb4c3447916c076b5726a80902c45c876bb35f6c5ee2a5907be3694e3f282df0f5e96ebeca1146fbc22c348fd2bb2f847d7a4f3661eeb5003b4d2ee41173ce416bdfe90366356734f5c1a5d23d4b5948f2b20339efe1846813d45dda122b99031a9b6efdf2aef759d681f012c93ca079fae9410a3100dff2a3b51913b99c17bf5d8cd1d27367af9ff120e7ea8a8f1f3bd051bb50a0c01662c26343f1d6e6657d0b56fadf35755bed872449b413b05ac509e05bde80bd8d03e41c6ccc140bb4bb6b700721a682351b57213adb3eb43b1f3724d4b8cee4e1799d60b4de5bc000e50edd8cc2a54ef26090be5346d79ec6b616277c56ab7fa96042366e59de51985cc8a59ae8cabdc352772955dcba5cd5c9b2c5887091726a2ed801f1659c3d2ed06e0ceefec7ee403602ce2a41b5be1808aab5a215baf720efc49136651cdf324ebb8776cf7cb0f0a9f68fbb349b613e8c0325dc4bfb106ca4d7e8e9378d94ee34e0372a21bbdd3dc816f0c3693921d35ae2d722bd79339c25e58233a3e4327d63dce7830e14e490ea7727dc4c0a04372654fa1c08419ceb0130578d86f32d933bb62cd79071222a11d72a7f7fe0b24a76f996f9f744055850275569c3407c539276c6867a4b7d4a76972dab49873e41aab52e2614442fb822b2ccdbc7931fb3f386baa77f057ae7a3972ba81183bda16f7e450675b4f5cfdb8f74c53165d01e345ea086045ff6f7a9b72118233da963d0beb3ef92532291ccb57152e3131b1332cd8e6b222d3c1315e4babc2cd9445665faaf6d4a579ff6c29b0b5c577b9c814e37d4124433e8c42e3723d63cab0658d5f22d5921163ff796777b548f6e39354a058a26b9fbd40fc8c033d9c85acea68de2e16c5257f463c2de2655bfd89229dc428e4880a606921aa72ab362545fc535cb015e8f5fef6aa2e64327bfc606c19e7dbffd3256be7791c72fa7d4e4952818cb34c6c2fd8d1d631adbec4762568718d1970c7d5dd17029072083b0da089e24f10af94c35a761b90600a1bce434725f925ae17322e7914c072ad1a1b886f64e8cb0896dc0b1a71ec03b9f31875958c2bd2e3e041e733e20e059a0e7e356693509a778c472f508ca875ca664d52e0482958f857e0937268e5501e94319416fc311876dff57809dfaa3981013a04c983d940723a88c5d5b6180ef7936d1c1ac41e5f098453658c5d3670e7abcedf039d67946d9162e74b1fff2d0235c208e131c8103f03f5a1840b4e56255e4f257e4b7de4818858eb3983870f4a9b4ed7aa9042ca99388e7f778d986ab0330066dde1f319e8ca82d8c9c47972c774632d4c721251bbc4867202a935636011eac6caf3cc461692c0b1de5be2724ebbf7bc7ab81795910429a08ffd84a09f69ae74a46427ca0b86193d87a47e5e68dc995468ff6066bca5dd2e6043f5644b0d5f8d7cfe362f7f0609a29acf3472ca36e56bcd5e43aac1fbe3c253caeb6bd1556701840f84873efa6f84b2fab250c52ec87251838504b64d4f2d5b8da4749d5687b593f346f6f7cd5063a8e01972affe4668264823a95aaff98827d5469cb0f825c03a4b198520cefdc8a88ca37283044ded6d889e73df148b360fb99ffb77d0f00e9a51138042e44435e508da727924882babf7d596344c82f61cc46968f042d504f10be78bf284ababcf557655ffb7484e0eefbc13e7d02bd0a0c0bdefb596110ad531db4536938a81b9fbfe72544520e4e6c715c2b1591d66e2734ac8ca1431b5049b93ef035db267121f426dbaf8d33728b3bc019af2741a7fe3a3f2f120aca785a70b930690f22acbf0b5436b7316ea6cb42cfd4a4f2811bdd7ce1dfb006c3772948ae5b2c01478edff841f58cdb985f8d385e5f3e278e089d192d227e426808e6d8e7c24e60e3442c43972ee50bd2f35db795d5efb335d9d4ad5a75ca60cef0771b5ea379a9a2196d1ce6b09ee66e766fdb1d1bf2425747e7835ad770b7dc15937a4a2d7165b4d8a93ca0ba6e970c3184b37b59f42b50d2e2dcb67ecd71c2e0efa470801bddf651af47772c159bb97a3f481766a12fa29208bd900945f238dda4ee68815aa7eda4c9a00720a2ca8c11c77426ee45b0ff6fd870153e94292f18a8b445cd6f97530ff6d6372dfcee6597547e5702512bae063f6386f2ebb1799acfbdde8f988d91ffdf2c872a6bac4bb00aa380b1e88bc4cfe485f2874388900d201f6b0cf3f99d02d0cdb27401c5e01bfa89e19516512c321a03f6c88c06db503999557052b09f223aa6537b58b44b8a25688c21efae89f7c49bf6173b77016f8237a3ce235369064771a4da10d92bda331ce5314c8b32ca70e20089035e184a8f44c368ceb56d94d18b039a51edf1b789671b7f24cd08ecc63243b3e8c73a21a450d1a499d644c9d7a056d2e69544c3bccf01ebb8e31df22d325d9497a4c33b4bb34c9b21cae0fc97e8a2eb96e6d1ae35276ecb484eadbf44b4202c633ec67570f29f79a37488795015772560553e9c5058cb50fd4070a4ccfede415022c910409e9247357bc34b4a82e728162d9e18f062b9abc8e631b9075b7f3276e6c21f10eb504cbeba3e8e45e4d728614f26239d5fd6aa10b29a9f52cd8e022f6201d5db46988fdff16219940ba51dd08e571efae5dc5ee2056122696d28c16c85ea67147bd196ef1e0117402b44b9cad6c922a6a9469f0439dc1f613e3f554f966d810ff89f3343a7113c3967a0a4fe5d50d81e2042cbf754d434f2a49b968ef0656cac188a0b1165f0f2d62af72a90d8522e105591e47e67ad82c3ab1f5442ba06cac92d9c08796334cd0ad9203cd7c1a38f781d8cd8a810604b005312ab8ce66a620dae1bb9c25b162e21de655edb78ebd2ce4c165bdde6e110befac6b7fa69c9123e01933031e3a5ef5faf2726b6e59c8937f81cfef44a7ed09388f99f492495e8d24d938025ac3f614215472b50ba102ea1172288c35a76dcc68c8faf0df374c4f268fab2f2e054e19f0734fa48cab870585515ee52a138c68c8df4e0f98e2851dabed0ed9254041318ef764ca33ee7ba51cda63854d450ba0b6745f8f5e66523cc1ecbd84b47a621a590e72c4fe7e9a683acb8b0f884b18f78fe264f4b60935baa97a4859386f6269122772c013817cf6e4bfd2c3c39b4bfb9f4cc04e7a2dbbb8e6bb518fe754e30e9bce7228a2dfdef01822448016c8aec0f01b4fd3a7b78e72bd6166ec2e1d43b95e935a9eeec1cfaca08b8e6b18f8e227b3e5e5eacf315e67fd39ba52f899f482935f72589d757142a1de11f8ca394743e66c15421f93b13e3d061b8e209d85fd5e5267271acaedd8d9ae9b17ce58eaed9664606e8c0bb49ffacfd19a5e208d6278ac72cce8a36c39d3936b008a501152e733b2e81cce4af638b83f0bb97b93b369335f66da5cb3bf6d4c81946ef554a6bcc529a9ec34a8634f6e76a162365e61b214720c0e77d9ec3d2a0c4e8973765a5a65825017ec286728df2b81a9a56fd34e53728b548e554d4397ec4711630a09d53511b573081cbb3cf5584075dc70175ba80e45ed8a70df2ec101c569b136ebbdbe2bcf73accd2cc098b30e4f0d0929466472b3957ad19e09fff940c4811e2ec18b3e2c677141a7be5fc06dbb6c99ffae8e3098089f969431ed043ce4a6bc638fbe71d176e4c6c46a22bb2bac6168f852d172d861668caf08fb806da53b58c33f02291a45f083b01c8e5817b55b48d6c36072869046386ddf28bb31f4b59a2423357e2507e310dcaafccd2b7495dae05ba77297624c4750eaf5effefef72fc10ce389bba6961073520f1e226f3b4404a01372c2f08a9869175200a89954f06923ab576f40100a09e141d1b434bc77aab5e072eb7a19fe52423755259eaa44e0d1a091495da323d0cef89176af24cff3e90f72854365d4f6f2dea34bcf384a287544ca952f546f59ef6dee38a5812b3bbe264b33d54154cc5c7e101e418f5892189ed307b92da6b1b42d365857620105585828757b0d67120a519b7b31ee83ffa71ce1351561a36e76cb4ab729fea6e5e3e07287b03c198e446693eb73c6cb30deb831739a6699a0c78f114f3c7c743d45287252192815e28075e2eab91a07249d5a5b7da689bf2ffc67f6ccbe5733993c683981ae9649c5acea0666b3fff124583b6a93594b07bee8474ae38d92ff5c49b1727ad3ff79ab0795f520eff09920976019b1dc46d471ecafe77b9001dc86dd1672c62e7fb553d72f359a095c5aa08be9985d84089fd083834c467ab4c3820b5e64ac73247f0b20f68db30442ebc578f4145f06049f7e864e77449b909c9c6fa3721869382cb0756d97edd03dbdb503b1a705613b693d8826f20e76b37683085d72fe87aefca9bc1f7e2bd3757d999022d73dd6791222ef2e1885a8d8dd11a9c63e27fb1be4601385ba67429fdfccd3c0e0c85be9e98157aa32088735ecac6abc72ace2be312803f19ebeabece23e0ba014c57101a4eb03774ba2769e05017a7d64ccc1b61da4025cb3afeecca62b1a7ad4ec70c96364a444b8cd65f0b5befd3d72d748c44067ce6535dad690d2a5020400ffe6b6357a3409f532a1089750408070742bdbc92c2a9c936196829bbef1cd8adff014fc2b8fe99bccea4e40c394a0423f5e81f7cc7adebe56a8e8f45a24e3478b00f1fc53e5cc212e309be69ae3e12bafabbc9f52eaf5573efe2261735655e15600b3e8471b46345226d0dd013a6c5ecabc12c5e3c860e7b6f0433e59f92aaa44cf7eb7be224f645bfba27e61efdd72189f2b5277c16b5ad154c447a6a729f80495c11d288381bc347a4d7cfcdb336eb79af457338319763338b8adbd54659c17787ee67e4e44a3f89371909252b672afd86ca1f9a8fafde131617913799d3276e5769bad492983712e83fb5003da72a4f88694b3d540b79b7661e960745e456b6fd5c5ad75031042759a3b94f6d8729a84dda254388e48f8ff9ce570de8c02510b58aff588969f8f53e1e8cabfd7724c1f1787e71dff4323dbebcf8189ceb4d8c966f68f81f8a630d5b3f144dd13112feb3799dac404a5ba5a0dc4e36f420167b9726ab511180001b514952ad544423c606bfa49a176165e5c6958754a4e65793504ce4c29bdc5e6d684b213fa9e72d212e9524a0929261e7f5cf58aa3a3afa4737441534d119a8fcb1dd85c66bf72f5e59b863bc1436df6944eadb25e56bf0ac18ab25e98399e04a4e7b79d9c8172c590f46b829cafffa971a85c75a25f8f366e8d6a83654c587d2e18653e7f852f06cbfed0240a81bdc27d207c4ed2b1649b7040221f8c1fc83c8b0fcfad3e490ba896b4fbea874da3ad19eb992a8dcf6d9f32b620f4ef9699c8b7b7930ef07072a2d28bde5ad41d52fa807b94554d3bca35486b644a3b1aa66bcf5001e3c74209261a14a5367907be7f9d4ea8a59d3a31551d7072d482e9391d3618fa08395b6e72c29b7c746efdaf59f7f1a31455e54e4140c7b525a9662feac994ef4b43a3154503f7d5640027801b64d83741405f30fa3ff4657c48fa9252814fe11ec5035d81992899c8b409c7123b986f5d64f6daa6f5598fb185af544a0e2c25e35ef60be5e020f1d2f8728a8549a2600c8704f5a82e3dafb9f62af564b47a998ffb6e72ebd10acfe22363287f2ecd8fc97a8b89033bcb875d82e3945fc6b5f58eaf9f72546aae6d5b2a20d2169b5dbb999c12bbf88f283fe1108e0d8297cf80de49db0db002e9046e6f7bd73d8a76fd24db97f69b5284d2f3db7573a5b893424aa30272ca3ee6444f88c40f9b49551defb6c91e5a98b0d2aadcf97273cb6a2fac82567254e4b49a93b8e76410d434107021fe097c82b9ff0e0de888abb16d70d420ec14dee92994df6bed915641805ada4a52579e0f30c532d1109f7a12f257779bb1720ab2b169c7db0ee42b6b4735210431e8d6218515501c2c560cf36300fba5e1727235e23e51b9b032be61bb382e0a559d7195393c5b2e79e87fd6fe696944a809cda7f323540aff44138807a8fddb2c62927c7866bcdfa2c190ab20bd9f39de72e692457ce127f10d5246db2a0d3fb6766c04bbf4b4963d009d66a2aa73ff9822392c932852466ff284f1907b25ebdf34bbc77ac0867fd7c3f02db54a9730360c0ef6ff6a7792463e0d47d81d7fb20cffbfc833b9481e8fc68e056b860e0a517291f9b2261677484d4e3307f325d1bd797e010448f54fc9a4046f6f8e8083e459a03ba464cd868ef6d97d66b8045492db42b6d1488c5f87111ee242c66c65e6721f3657a082e697180222026bb9236279b0f5f50d5a881a1d692fa199d4e714726fbc2c10a998e3d8bd580d77049eb46d23c2dd232e8f2d4c8394146632b0a62a6fa0621b2d0104273fdc9d3a8024d8eaf2bc6cfc93f5c0a940434432da17d07247ebff11115c8ea4429397d5ffc50b9fb59d66e15370ad745721e98a0821f872b91d729220258a4970012647e04fc3c47de2a560864104b770bd5e2bfd639340bb03374722e78e03d202ba3e7631b5191e67748f7a09bc212811e674a8c11b1d72692510af70347ebf0552e2fdf964e7d0762d54339874ba8e08474efe2ba972f03339611378a3ba1933bf2a72f51368870e3adcd84ab81dd79cd7072da77c72fb1b76a6f20bb6fc57e4963fab47043ab8badba8cebd6ef330fbaec1c6fc8e72f988cc3f9194157036cdaecd812dbe6ca1481375dbcb9dab0031a00b45855772d211a3c4ac70978f0ca80eee2189d84ce29caf533c1071b042169c148dcccb726bf8727183e48d87b6b1a0aefc3cfe4bbac379d5e9e901a5a862cdae55cffd725d94cbec4bf82f7ab3c58b739d3ea8b5248a221c5298fca9c0f8019dbe5470727daa8fe38297621c49137d47d41f00154c787661f1c981fa958bcc8685437a72cbd3d76841d8bdb2f4d0b412fc181435dca4657c3abfa4e049176debc5826c72b5522c33300376a8f9ad1b353c37ef5215a9feec452f40d1ea26b21fda44e37294d497b3ab48b24ff3fb8210139b0984125411021c3826a7a1ed9550af6b4572f4bf62a4df2d5edd0a68a2782e7ecd9932b91eb8c4024ea762e33aa143cb71727225abd4ab37b01d483673708fcad4e7b8216701a02e6e1db2f81859a87fd372bf4400ef67758a073092fd9c0b27fbd97e7ed84e94e5abf3ded896c634589538cea0722e18009dd3e587bd6d3610d9ebca518848a04bf9631f38f385efafd0177ce49b0aa763ae25133310589be976f7479dee6fae06bf2b6038d956cf422323ea138ba5f6104cf61853f35e35068cbed751487ea12eb96341057c39525fee2b5c196b651d6b269a89cb88ea1004dac86fe90bd796feab18cecf194eea7ea1722a8fa63dcf411e343c93335d7846714342226f41c6b92d3391a4be91522ac92b40a1582164bb2819ac0aeb237a3fffaffed2bea4491efde8d360f8e4d2ada672d83c2fb0e72aeaaf11bc74fc4d7c6de32b8bb1aefc7ede9549d4ff8938b5d8059430cb3c4569decb95922c2da8ce96316ab4a60e7e3b18174834566d6eb2ce72d59b85eb44e2c77c73f9043141ef1766fe031bbcf6e7b6586f87a08272c4021618ca6b81d7eb21a7d0b98a20db0984a302da8d700cb73889dbe75e7261775b0151871b0692c57e4756b65ffda2cc96cb76d6c0502bc243b84b692355f2bbd211472b7274a2e16866b0d83c565ff2040dd41ad4965624f3e6f54e2d92118e1703a0024e614a463eacc0bd7e6a87fa2de993cb4fcdb78917c1d41543b816dd7317bc5b4d6f77c53a84acc35fba2488d152e12e14b1789ff9eb0d1e54642e6d8a6ababaa1660d32aa8ab8fcde8da657de943dfdbdc763b02ea0be19e340c94ef021b321b3594a30ff87b7663e589d30075f1ea874737cefcd643669d92693e51672cd7f783ff1b88271b7ed1a80ea64fa8284d7fd9512247138e8d44faba0a0f620671cb0fc8d43a18e46bfbd0f23ae4a4132891565b2ade0c782b3996db68415729987408a0402dbeecd71342530e54bd2de7cf4b0e7246197030a20c43cc31831966ea69d31bdaffed4c68cb115726565925c6fdd0a939354da5c263ce8d61c727c8750c9da800c66b4b468c918018e1c355ecf8d6732e3b1b212e3b2d4125a2113a8e5634a61ad02da52c690c83c95fc07b2f11e61868c047486ed8b14b9cc725b8499caf9bb13cfbdb47189fbef5af45dbd6a80ce77b138224e1a25d40c70680cf308c5b44f4f3ba965d6f9eb70611846b65554768a861de36c435917dee47226ea2f88ed409fbcc9e44420773404539c1999feef69c13470fca6996666834ee4c82fc23a133e73737d0c47284d80eb69f648730103aa385ef1ca2c72519d7263faabdb137826b9324d4479b38cee6b392a5695614defbf6de1c21a4d8cd94dcc55798893aeaa6ba3a0de952a931a2cffcae1dab287e1444b139476114c4d69a1f8a6b33de7780cc2d204804faf671f23edde9a55a0ff5a245eac92560805121735f6c3c5121c8f19bbf786876e205565736390ef442437ca0869bddae09372bd51731e97d271712b0c74ced671fa08a89313971c543ce674afe586dcbda6723915dea4186c3b36087bcdf835b254fdc3a06553077e42c9276d894aae61eb7269d8cb0d63d769ebd2771a94613aba3aed9c55a2399c7f0c8eebefe24953b3116d48491b4afedadec3b2d4915f84e553a2c8bd5e0e871c96cf8d172b145f70728275cf2deab6d54a55cc4202676414b8d802924b2ee7acee25b95eacf5eeba72c46d6c9026bf7f41b8c0baf5fa3dcda085bf816105c90c5ce683ed56cb07b672a365494b8e307f3338152f5a62c37cb75df7646d2a5b10c70014a1f034a768724cb09fa7857b34de9c61ee953828b51014b9d8b5a4a5654ff6cae6f13cc82f3cbd53003cafe534533ba276161571f09dac45385e1dee36740e7b52185df92772c10481bab47bd7df8359a68f0c37ecea4166c2f9dc0a2fdea8f5138837c157237a2f57817f818942adebd63a294b3d3921d538a61a2445349ab48219373150728d7bc1fb9da6fad10e3108e98b6e727a20ff0e656fbbea7b4cec2f9987f5fc72ba53f1f28596fd408c240c33752aee6697238bd64aaf141577d7eaffeebe607214dd85fc02f587b7e8feba4c0e4b8fa72e4e5f91fa43630322c7983d6572fa5cc9e80738bb79bd2a2d5033bc5426c80ea46ce5dc052456df6ac7403eac580572deb60331c2ca9c9afbcd7a40fc3c1c5f79c9a153aa1a4cfa89e767c57ae4da724482715fa25df7fa43078ebb1a32b263b1881af48b77616ed3e0d0cf83afc3728b9186eeeb55fa023a82031e03a5f329bdd2520e49fba7ef1f7870121bed827288c453ded215a96280f8c869d55588248a9d09c6890d3b22bb7ee0e46499e67224e927ba4c2af65843b75a70b65132603a457039d427a85b0a82408db22a2d722e28d1cc9a1b6929316eadbdfc060244aba7402520287dda21f5ed777fe8fa726504df10e9a2c48a3edc9de31a8c343a24039bea9f0fe0be31cf3a3d153c2e72a260eba454c0076e3f1f7d40259278e0d2d3f3cf54c38aaf848b5ff1b15ee525fd6b43f8bf525741d5b3f86c80d99def949ccae834b7aa9f7c06ab06c0e8c505466ad717d67430d1183957344d52f713f43989d03c7bee9e437239c269664d14a6ae3cb1082759c383f4a9f839af61912ed129917f689494acd67a5ed944cc3c7c13f66368864840f99fca626f48636a046cf1eab1dde8efb6cfca3316c68e72d3965b254c20c643e2aa2e0064cd1d3abe44638d2b4cf6a5374b61cbd8479a7253994ddd8d42855ae1ab19a7607f521596a0bbe98370a74b81ad501dde7d5f721f7ea0641b1ee21a8b27764f7ff6c9854e46996d3edb5883ef7ec4bf2c289137ccfc10a5c1d950f186e8077a68c4b0701aee39a2489fa4840cc3aacf2556c372f6c717220e002f7e3909e96c710a2f43643fd681bb201a6d38fe64a4df069d7297750d78d75e4cb35bac575ae1028d47425a3a3907c5cc3532d07bca997fc072ac5a2201aa3d5df53d43d3ed2daad5d14d84fe978d919f74ae99823d0b791435df29a4d9875014c971d89ec6d3eba13d4efdb5c11b0eb4a637db3d1668fa2472cb45d378ab2ea6971f6189129e888d63cea7f25a4e7c9f410beb426ccb191372cd39a8060fffddec0fd770589d96a35c7615a2318b0bd9e5024d83e50029b00ee6ad152989672d0f20d337c43dedfdf0dd1c70cc123f2dce908ab5a5aad81f32750dd0bf7f306729cdda89a80d09dc588addc73e98240bb28cb53c675dfee7729445b1925f42f23026dcdfb91597b47a6a0729f9e150d5ceb0974c2e1ea71d0756323138253b23052b3707e9f10cdae994783334050841dd6044d4d6c4a018728568adb537c659c56c00517a7af4e24637acc000d176dfa3a59b328fee313572dd2d70e700536ecfb437654b35fe18c3f909ee6608144c8de0e99f8d5a58dc7213385453234f3843d969e7fccc12562baccd1a0165504820e5fd2e4ad47f233941e3af645461db7c92deaf3ea4fc6e00086bb53f7f84f9fa1fca8e2ba359a2724d11c527224db5246a3ef899d0aaf19bbdcf4dc11cfc7f017761f4ed6521bf729146d572b268539b36a8481c0832044126fd595e18e1c8343cf434e5c7a0ed726a2e3ca99907b0242e46403bab48cd1528ea3377484f4b326e3ca1fd3c0b2a72c2769562f9a803fd5aa902441057969c2eb136cad87adc3d5bc3e3f8f5ba4741c607d0cf99d20ac318dfa7e9ef317f71a08ead3aedc8a6ac11656818f248925dfcceabd6818d822526dc477a1b61d2fbaedb282358b7ed42a95b538aa1d99c697f5c71777e0a8b36ac9566ab61f97782c812a24203eb54275694c2a382acf27250f4fcf4aa7d0c203d0dc525c401b210355c6910fb754d67e758b4db8c30bd581d4e97f2fae1fccda1c6c70b3348c73a91f23e38015308692a775499a89acc72f0268be3c97d697d3c63aec5d716569bd7498a47e8ad322d0323ebb3a5ac1949f88718179c63aa75738bc51c0cf93034b6f08f087ad18a5ec635768143e273727d08a6c220fbc5e48e441ae80baa245927c6538a8558f88d04e6ebba615fcc72e0dd6e86b531c3cc7ca6dc974fbf1b6842a449ed1d0236080579d4ee35a48e2ce8e2791b7ebdc20eb32d1ce3ab211dded99da847c3885e33335cb631a5acc2722778d0364331751352d7e6a9f4f3ab4ade75a3abd37cd98fc25e581eb103e77236c51f2665c8a33290f9442602307ccd8d4a9e577b676ccf03fb84991e25e172f8759924d52ce7c9c81ff19d2062e90ac6456a237d0c6215a16a1cceef4fb4283d8786967475a2b76bff166e8ac41ca00f09bebd0f6ec7b90bac0bdd5fcb885979e6aae6b2f6f61c6b9eb8bf9689682660c65ef82e8ef945210b5c9a9a9b8c16a9994b53d054591c4ce6b8dbd81f2f6ff59dc86f46282666a0c7226de1813872d3d28823cd05c9657fe9f9f458daed3f4b1d4ae276916ac289e588132f752572cda91f56f6bd3db2865e4ea82d470f20330303f43d9cbd3ea1e6952b7a706e72eb931879cffcf3d541035b988aca6d2378381387e9539b9b9cf0392d6bef98728bf33f80d6fb2cf01f29a970a3abd7d6ae2166150f215e6d661da7c69004630fd26d95b911f1c35d1abb39005ca9f446af0b9112e6858e5b6c7dd643caa27550c20f6e9bf6c20ff6c7811fc1477491669c884c578dc89f77ed8733a56f01f672917b98fc7cc930fbdd592d39360054977a2a43be2d7d38a06b1a3d2afbfd20723e42f7c7318f2717c4021df6ee58ebb5ca3addd11501925391307d6371734a7228c28b948877b77c02b76877c81b23518463a6c00925e576575a7abc829b8772b6e4736169c4b78700f0e4423021d06d59bc9f0baaa5aaf77f3291673e36e972e16b1f5843e748066454534dd49fd1e4132adffb54c8f7b44cc7704ba470be72a6bedc8fc76702282c051503423373524f1dc541d51e41bc068bdafdd7f02b2e3eaea0b04b80e9503af5f4989988a02e77f2efe62f7e13d2bcec474cf68e12721532ad41907cd8674730d9fb19c6abbd79f1707dbd2af23b742267f1c1f33872bb4dd5ca4fdcd3644a55e33ceccd74a1bc48ddb2f2aac75b004f990c4aea816e13f29be23af4d42f67fc7ed26f3bde431f0d34e8ae552975a0cc81cc90c57772f2ac224db2394f99a2e9ac5dda77988e4ae08cf56bb5d174cbcef142388e107291eeabac93a61e649651bdb7d5d38b5d11219fa32bd6c2b12398134f3bcdc1729784e1030f6f118ba5a31947e69a490ae9da4eb5017651e487b4f95913c374720d7a41de2ea0827ec9684ca4d6dca0a62eda73c2fc8da7d75fa2256a6c889b72657857027142c02004a34f70f23ce8a21597eb62e4dbe9f7ff8ff9d06ab84f4944da820223c392522dffe9a1b70c3f0b26e9f838c4a3f54668bfdd8c470c277258a430cf48e539de862375348934d066506a7f5e4f772fc3d33bc886de984f7272b3edf79c9960b300e05fa3fd70d758c249a376872fd84e37b9a3975988277280f66a21b556e3fae5b66c57bb2d9f78d514d7ba957ca450692e9d8abc094372864fe330a32e7183040b69943fd1c08c2be8aed7854a7924788855236a0f8a4e1b71a2243a0dee1eac82bdafaab1b5272ec5ca9ae392924b4bdb62230457df72ab64650f001da444a6315d07c47c5e4334edba3f7117bef228242dd8813dff2cf8cdbafc177fcc5460cc4f1f8471a4519cf434205b50f472f609f667166536729706f91e88c5aab0ee9114cf8b1b506620bc6f12f6689a7a168f2219fa6fd27271683bbb6edf75367437050f3d4e07b6d908b03f171c1d1982840fca34b68e29b4eac6d116755735ebc0eccd8eccfd2599a97c8e04d50e93206af3d65e2d7b10d284c0642f25e21ec1cb1fd259cfea99488dea0a2fc2ad7356abc33d71933172ddcacc4e6f2f91d2b9dc27be0324bafb902693929a0ec541614af4f703e6e5431eac6c2de28ba32c2da09e64d209932cc3c4a1ac0e53b8a760c9c1c4588eff72cc780805aa8c89e125796d0396a80098db48bfdc66678244f628161f09028272069c930cdf7f3ce1cb4cf1fed0baba6b988bf45159e25e8497a8af674aae6772819c60503c0fdf26986dcabcb9e301d985d5fd217307b7288cab45fb9ed2c86eb11695f095f71584aee02a8a9bdb89b208da450e80cff876a445ae0c6e3fc17282fe2fbd969d23dc94f2124ccec5f0b3e7aac3816ec3ffffe448a3bffd404b6aa18491cceb34d41a1e26c5989c0b6598a10446437d12de44b75ab89eaddd1a72f707a9d8f0df733e2b2ff4bc22df0ee021180fe030e7d3df3dad99cbf58bfe01914a1671e39b245c9bde05f106b26a023d2bf29d962db00d9f62c20fbe0efa7286ef03e7658c6eca21e930d01816f7dc9ee7996399823df2309f892a4ebb5e13531c5476c2fa5acc6e1eabf20081b6cc1d1adb74901883e82d9e6b636e80c672c360db123b2376cdff36f851a492ff5639c6d6be5265a6dca6cf2c461691917249c316f0b6cb8283a82bd309b844046f5b636b7f8dc936e32ff112564a169f72f0d13766a18fea726d1167b23822e5895ec5d5256f2bbbf4a1eed5f5a92ff2723389e906964ccc753d19b01a806c76267c6bca3b3514d1c191205d3158c33e727fafe2adfede0453680d252a359adf7afbf1366cca877d44a14b6e2578334972709d97d5a9f30014fd1fdd52919a91f3c995a3bce504aa9e795c63e8d1793c51ab9f03be32eda1aa0ff622b7be68eb87049f52e5738c593cf5a8f5ed7a91fb4fec0cb033c3326ab394a11e8fd748932d7219312987ee2c33a9a414461fc2be549d826e8ed39ea325c288e06a1ecc9de09c59d0f2f4d884fd8e69fe1ae2fb90055dbd334f21c47af51d0284b008707653a3ce78a1631ff00ecfc271f1a50a0e726803b7b5fb6c810ee0575ac1903760cbf9d35944522c9d0ba74c5ef7566327712eb0dc063de17b6b4da753c45f2edba3c30ad54e7b0cae1cc86a3797ca747d53ead4a5832a57ae7c1e9bc3f53f18f92932a546a141736de66d93eb537631de31c330c6fed92fbe5d446c4317fe41514857695df0c006dd40ef9023bfdd750172e22ea5483a31747bf97cf05bfdeb463a97dd25c29098d92acb60705383405972bebc8f2b82b8f46ced4dcd2f6bb1756e2193f5045397e78a8107e0e79524c525f47f5a952a9297efaa25953a0d1e04b425c6ff39492f832af4e242daacf24638eefa8bd09da2737120521297376767b0991e9883018537d7fa8117a9dd2bed72a3787c7fbbc5c2457d7744f2403ce779b67e831c62d8d3a40bc3e7531dae3f72101eb4f6241961d85c1d1035d8fdf174cfb9e70cbc8a0d1815048561c1cce7729f91e0987314eb0c2e59b3d6b453317989578e50cd78585d3601e559afee1744ac2efebaab0252a3e9f58191233177741da58042bdfc195fb00201170a191a723fbcba534287671f6a413663f19e16042b961687cd9d3f7a76bfead3425fd4723611dc876981f11c82017ab25b77d2ee9977a74c0f531e000d1f2a8595ffc33e6b99cae75c9f62c77f81c9ab63a1baab54588978595c9fa20f149d2b8a583f7225893caaa94b9bcbe77e45487cbf41c8717a05ca9688c3b0116e9596c412ff48176cf0c84c29a19fdae334e07a17bcfe96c39ed7693979e44d25e0c2f690462610ac7b5be9d19c74a1e2b1c32de8c45bd6e1f79f04d120a575f88b7077f5a614ab578e761d23c145b17ee601ad56367afbf47a258338d05ca89d6ff4e6c69a729b7370567fa7ad7e5ecd3cbd250cca07d6c92d957a31e3299b778b9909aa0918e1eef66cd01b7b2756be5dd1deedd939c3277497b63d2937d034e5232760927213d97defc00d5fcd7763028ca169e5068c33c4c7671ce0cb0216b70162b6da21059343bc5bda2aae11e6c48655a942ca5d506a955eda5d06eb28d637914a761d25fb044a63e79f4f9d76f8b049197e319be28b04614172d7f59cc1cde1a76e72a6fca80ec8c65a510130070b9aa910f5b638243578e62185f0f2d7495dfd432f5bc3cd748ff143178a6f0d2bfe9f82d2db7a3c26618cf85de6e385a12276394892bb6ff97e924add35a24378746fd1d970c077ee567c4e2291f3e389dd028272aeed3ae8efc1cb4d04b14533b96136c6d2bedc6770ee279b4d2c004f001c67723ebc5a734d390d1cbed0fb9c6ffd8fbadfc8268f91f0910a6fa871eadd78567291a166a0a0fce14115a652f2fae1b5cb59cd6bb8b42399dc62b9c8233b8d2272d164bdb8cf6672bb6373083195485f6c87584c85094fdbfa08cf761188ba3372490d6e2bd823454c9d2157e8b1103f9d40a2651c7096bf07d55c05c9e4379572499c9b1fa330c2d9a7e115fcb37b067e3aae051e55f4649fefa48028b74c0a72c4aa30a0fe63cc91642861656b841019249e718dfbed0155cfa6f20a33727f7284f81aaba2aec26cfc39040ef1b944619e2cc69ef903bdfb307253beb0080772d1e3921997aab19a5d62f82db245842cdfa5ebc1cb8ec178a2317c909cfac672d42f8d085c309a857e325871e3a8935e907ee6bcafbf6acafa0eaec82b8b5d05d45c7afe9c53931428325de100e45b36bb7a37c2a11e34fce7e6e185d03a6172f6491d15f9c122aa4c30ed284d9f2a09fc92ce82bbafa7f6109849461f688450c0043bbadc1e8e057d6b93cf3ad0a14271c2a4a5c4100c64c85f3d4b21a0ca6ef512af8e41140e24483f48d50c9b615f8b42ad90f0fcfb39ea67cebbc19dec72df98721965bd054cd81eac6172bf60d93f20c2ab66c5170b5bfa06cd0bd76a714449c2a08a9aadc50abf182ec708dfc70b2a66c998bccae682d44cc16e9685624df9631aa0eee3c115ab41c56862c3c07e1d3349ab652c96183682386c9efc219db4d567558563fdf43d1d0df6669cfee3fede270d59525dcdeb9d9644a0e12dbfab9b08c5a73a481ce31f8bbd90a2172a583821756a7aa94ad67ffd5c8dcd72b8adaaaaf9754411ad35aeb4c72217d527f4f07b56dedeaba9de8c02270f017245c0daa474c4d4bd433ffc3931298f0fe1902c227105d4335a7ecabbe088c672643071854354e050a38472f677a5a431ea805140523f980b673b0c8529c23472749ff182e18fd59744f1d3de0802816915780dc7eb8d83ffc59434eff011dd720632b228804f9c63175b6a7cbc50c86bf2f2ba3943e8bd7969f48d8f46556e7296195c0acdff3349bcc4f46e9c7b5a37b1d3697186d96e8cc6cabcca215e2c72590cd90efec3cc08085bffb4fa28e30292abdc0868bdd54310207ad12d6b2f607651898067f0954c3e524ad97419546be9eb59db69cac189fb2394729f66f93b9db1ce7e83829771fdfd6b3ed1e34e920c3b9f5ed889c1ea2e326fb3ae7f6b7252301f80165cb8623ca430e62125b4e1ea49a72465850ad8ef060485f65c775f713c002bde073a88876cbec9e448c60bac962ca644b3feea25122dfad71e61729613a92b80b933a09d55cdc0d89649ebb1fe9c9f9ae95b8838806d0b005a332d26092f56167c824c5f21c4046077d86d523a11fb3e7fcbc10a8a319612237f727e4a3087aea46f805017192eeed86af3c327a36b56e9fbbb72719b8cb020003552aa855c3a8c68f41516d9fc0e3171a970a61babc2f0022fdf396b90e84ec00804d3e9781f01edf24fe02d5f5addcf30bf7ab54c624326349f1201400adb5a72f0429baa32394b1b134591d934827020e591fe066e9b3e42313594099f5868725496e94fa6966e52f0110cff9b88254c11a2f484c56fb6c37b4232ae1582810622750b063770162299f0ab7703b17bd2dfd00e1f1d25e2cbd74557828b8b1272c019727aaeb4b000a5ba0a12a778f0b7157fc32d971dc7da4de7efc59ea5c4729bdfcb2d96a6b5565570225e6c70e572983a1e61f85d1f7c34158b13aff9c85fa4e70023bd99c516068e319004504e8f2ccce5d8b86ecdd692f062cac5bdcb72924804668b24a8ab49cdcbcc8ac7d9820b3d589ec13d881688c3c92d0e04b3492adb6b5ef627a33614898e23885d7162ac6afa01f83a82a1be496cd9bd4fe6657f4fe5903c09ace12dc52d28ebaf365fc469fff69cdd4c395821a0df2ef87c7259d87a27378c3465bded40269cb39dbb099332bd4268057f36b884cb0a9a8a72b5ba73da416120fa1962a95f651a0094f1baca2c7a3324bc0f01620704c1224c6f2c57fae3889802407c023781c710d7a507ea540b01614bc16a36ef092b856171d74cacc2ace517f77001e293b27e1635d7995b44c617baf794cfc3ee96cf526b10b239599fa16018c9e4c4ae6c1ff869a78e078118addf30995d9d16e648727539854e2ae2b5ed14558ffa93c274a6ae8c3e7a07bfe11a04ff6a881ee796727c697edb4feab665943948fff0526eada2b591aeb3783616bd92b18ddea86f72634d1c6a0b5e18c4129b8ab21d5ca07bba96a9eeafec855525b3a8ad1f475a724f368c5c35ed2b3f20fd124a1252a08350141a085da2880e32b97265a6ab4b4cd06054889c401ea0be16fcb9b645a4a2e39805cda20912e6c61ae567438695725f101f33043cea06dbe8336b11ad465cc1617314086212b7dc4d51ac19b23109cb62606c3e0f2d23d493169cd5fd23d40686d02362efc46fba77610892f47d72b3a796355a5496717c9242187f656d8108e33ae205d6d5faad16586e71a7d2722bff02e591e171cdc0c05e0e584faffcc025a198d42c61d0b7a82f3398833d729200eecd56da1e1572ff5d86be275992500cc7a7b5fdd65c689bf789630ab37296242d3d3027607bfc677de63769ea4022f217e29716aedce17bf32648e4fb382684d500fb5f53ef446cfd7e53c9e5026ef1369d08d9f050a3a3e8bdb1405f72494b847cbde3e92ed6ac5b002f46589d0a919d4209118d8b0656d7d8248f0172bad43d92b2d650ba352f8beb8264f8da3b8a3cf9b6b98bcc49ed9c96d429b272340f5558188597a329d97493a24798345903bc0799a8270fd10206ea021ef51e7a2eebf996720ff4e170340ac9e831600e28259dbc5432e6a392be8751cc7272ba0f0436e0faad86acffac7817c6f950bf7808164b5897a8115634fbdae7aa727ee2ecbef81409b3bf528b35c81e5fb8ff21a29f1c3a3357d698a7b3359c2d0bd819708d9c9be4b3fe3cf68c70abbce2ec257e0a8222b5bb8b47e3ed5df2dc62d80c099207de022c161b0e780d67dc24201d5c0d87ecb0e5b5f24befe22c0572b93b9cdfe752a16e36019556f338f76fac137ae12e0c30e39902cc88913b652f94f90e7a85601712a792eed1c4a26e8a996d723d9c0e518c1e5e2a8fb2171c4e6f6109ebcb0f0246e1f8e02aaae72e4f32eb9f9919d98425abf37eaa34b9163f87b17d6ec6de7073a9a05fe9ad497a85ea845a013192c34c1d523f62f7243a723d740aa5d618ffdc00c816a3a4f1938c816c0fcc440bf4ba8ae4b26355db4f7225e728325d11636e16fd46c0ccc26d16cff2ad5a57aab14a25d836d15223cc728fabe40f1ecf6f5676054a8e96d0cfd6da481e91ff90b61348350507c3f1357208126750d19ff227ef445047652c9d32d99ac089dec7f69a9a48be41a8137b368e39dee49013ead8ba192753423e83d0868c01dfa66f5c871ca8a3f0c68fa7725335a4495cbcfd602cf71aface6b0de876902d894b84f4b3ef2f2df49ad9045b308aa373ba8a78c6c41882b78480ad82768df7d5c64c05c8b801ef653766d572c190c1d231df30444796ebba3edf39c24f709e2f77aa5e52cdcd22944d63c64304c87e2a9144b03f1d57a40d0f3a852f8ee39a85c13b01e9a2700f7fa5361317abdd13a6174b6979935eba7082d9e57f24ddd02d5936b6c82b60008d95aa5d724f4e5a5bd4d938345ccbfb8c070b455bfd22349a068634e34856c2fa97effb6da884ed56a1c335d533d7ef14658ade1e7a9b2e823ef33b3ac526f48cdf25465da962c879cd1f0e7a51df3121228700ca6a043a7edefdc378f0d0822fda22cb3df47062dee7216f6eee92a373767085bebae2a69f3eb48ed431c48e11edf2c00b6cb96dd967357e51c51c24e043d0669689765e24e673855b1d4abbc267d3aa72191d91cb3ac3ef7f0dc3b120a746786c10d3f8f7072888f5f9c99fc484d82572cc85c110e4cbbef9d0f1d369550004be03741ddba89b08704ae987d407e4e572bbdc274afeaf29e38cd999c35fe32f28857d0bb920c6399326a295ad1273c5728cfdafb908634f6fc2ef9979a3556c71797bc3718fdce66a2f3f903d99831572d09bbf879ca0c2601f8826f356416ffe088b0c90b3d7dc2bee8f1b3ef87f6320c56af987f22b88d9322af52d10f7934b2cd70638b3e94a8122be1a125000d26a01189903235ec574527c23ac4b2ead798e97c80278d8fae4acebe9c0821cd372c434b306a36492f9db131f674cb033384ef1d781700e81e691f75048e1ead64a6e315e4e445ff0815a5bb17dc1806fc230ea47f15fb545eb816734d1820ea97287cb3dfe223de126987757d6f3b3b0ab64c899bd0a28317a59dd09b97a562672e3cbdec5a44dcd19834c5634535c42cec5a2a66faf18e73b2a999702d71ed5725693259c2b5534678923bb54e971c16362110f4494c95ea975d5ee8a486d6d07211added758c6bd1f34f0f27a621f15472956d7a07f28cc65d408a103a373372565e3c0eea273ad3e0adb60272ffbb4482c55859f556a5cfec4b96a0ccdcfa725fdc32b382d9c05ac9ad9ef65914298d47ebd9e8e4134a83d27011232ae2da72cda9ae84f94cc48d129b24a7274ef29e9e8bf336958969a5b153256dcfc45c72967d88a123a9a1a212712919802b390b3a44ac4c226d41310a0fee933785967204933713ed9f086b6788b70761432be92ece44fdaaa131763709d780402e5f72848813e93a1e24affc4f22f6e3f56ec9fdbc1e5a2cc4669b3949ebbbde18ad723c7b249a834895e4ceb6ecc6858ceb873f4f290b328c13b74120a29d9639a97214100665f7c58c601e16d68ae5ead76a4d3321d142ec074fdd5e074f997d724cb81fbfd45f3f33fc18438c33f01afad42e1f30084c1ef6ca747156a3569bc8559d12bab804442d496864a62fd7c09838515a71033a02572946fc4eb81b43616978a24fec01547dd612bf61e91de55fdc20c7f363819d453251e7972022b3a958bd87c79ed8bfaa8b455686a7258acbf0356c19722fb784b4ee4220bb42c4aa726e1cce5746e168564eb4edff47382e59f27f16ef9bf316d506cdbbbab57f387265d7fae124629c7e4e38fede7427b46053bf079b8a4ae89152d99bc3c43b9737b2b83586a48eb6c0e54e6b0c961f6d3e0193a86aaffe36dbf80e4fd0b27a176f2300797d4cc0dd3ff882d79f4769a195f2e6f7768f6a1ce45ba7f3d2e6217544209d3a67a09cd48a170e6231bcc4cba581b0b18986342c333f8e73155decec72140f80d45c7755b744f4d2962918c6320222b42c3ba9eb316fb5818dc525ae2f4f1308455cb2dc5e509244cf0b9ce963cc2a67703fa437b6b553fe2c5377536c4040aeb831e824b82d92d52b3563cb761d731c6b7ed2069f1a0f33a9ba97612a8709070e6144af26bb035123a6d2c2e1934bff1b0a2603f3d4244f582187724b3ef7d6e2fb188dd7c5b537784d9359f4c1d5ab651d0d26bc6f0b9915e914f5629476833beb4e3f49be059d0cdbee021aaf8f638903e81e12d9dea0d6ad8da82cb507e46c86384ff1f42c1f249c69dce4fbe697d6c07d9f18e240d5b8cfec5c12302de16079f0dd582867886fe1b220f1ce4ee2c3f6f87b1be1b710c243dfff72028681886e2b6de3b7efe173780f996c5642996c3401c563a43186f5437330721eb03e6842ba72e69beeb61836b7802d45c528eb3be22bc75528029a467b9e72c39773132c54f6b7b7b79fde1e98d88129d5107842dc82ce0d96b235d9fdf96d510811927175d21dc3a8c7b1670b509e15098ae50da5a05be150c60242d3647265f64ae6793411cde3fd6ff9dc3f8ee60cd83ab35445074a6ce87628fbac74725b401254700cac0c3cc6a3d09158c3e922359db0067a2ea9531daa9d242d1672160690b68eba17d471ad37bab5f23bff11bf529a62870b2f2b4c89632e042116ba862621f7b7c1a5fd2f2d6c33815f19d7876cfe0db6f8ac394302f6a9e88a72b6afbf862d6d86e2beb602eaabb5424a6d81fee6d3ae4d9708fd00a437fa7f352adb95f5ccc241bca42f18c6002224eab40cfd8950b045015184c2243bc1d33eb1351863ab42ce371b177c030827b8294fc71d5713154d9a0d2848ab2ee487728f4fccd1f520af16dd0907d355757e856190affbbbf70b455f3d682c0cc47509f30560c546517e84905ebffd6b170f3c10d67ae5e0b5be339fc90287971305727d2b2b2ef5ca8189dae4c7eae5567b826eb010ba48ac5cae4709860dcfcbb672828525c1a71168d3a0bbd7b113f2cfdf6e7323507bcc8944989c2e42639f1372b6f36510df08f9694623846d3c95a40b0544e26d11ecda78356b9766255ec858f0c6a6a144282430bfde455b857a29befcb4a801a443a5eebedfc97950491d37d4724ff50891cd3bbb4a073e67507b6f4e0ff29ad0110d803a933c74beb1a570de51bb94a37277f04f62f4d10910583addb7698e021d32d00de304f853b83800ad231b14894eb5a22718c8086aeb103059efcc6db116014c612b67abb4d80f0b0111e37ce47f22a2086d7f3b4db293ac2ec88fa2e533c8f4ffcb99b554b08f72d7ae9d7f52c47627406089536ea21f4ebccac03a0f662c9dacab5a587aa9b17299e06d68a355797bc518fc3584bd80dc484b6c3d0febe9abc24020ec13999472440fd99005cf683c6e129184960379bb437326881cd35984c9b88c1700e3b67269ecb9a1e43737d6bcfb5f06c3e78ad1926e6ae09ec827600381e87e5dd90f7262005ee8e71b7c8f1cf69b9b6eacc8b2343aa427ba5ca7939143c77e005b98577e7304f217368db1c4e9af1b39743b5b6013290dc164d6a093a39f033bbc26729589e705a4f2bde7ac48a268d923edba69dcf9858403857826ed3e4931ebf96ce6c1e6b1129807a998beabc98fc24025a95fe5dbe45d77345e6bb7d13a847972c461675c4969e526742832170e8baf48366949ba911c020902f6c0ebefe4ee5960d93334905de9fa2b20f647edfb372942fe17a67f4443c6e1afc563fa53153f3592fe218eb95a745c61404ccd899ba2cfe8425edf528ea9994af443ddc5ca076e4f381a7515a0391390051bf7b1145f22ee38937837d8d03107029b5559b02de5395fcd1146ccfff8f1291f6956381bb8643ea2931c6804e4a81ad109650a7201a827902f46babff4f5c20a5e6eee4c071a40172d6a5ee36703663164a55548517a15d0f4cb4f8a901d1d6b074a38588771634a8fb1b3a6119a6d25ca129069d8f1338de4ef664ac938da6a3e94d86fabf939aaa3a2b952dc65a23b4b8b9c65d6614fcca44689b9efb1152bc46682f4b2f02a99d5aa8d0131cfd56e368de61d03bcb7272211cdefd12cc4e973e74085ad3b60d9f89f9b9283bbefa9ad35d972a5a518585a293861823b878d673e2f8eb27276a8c50e222a3c9df39d37f255306aaa8f06922b75a0da614db8dcff10bc0a6816902d3c6c7910cbd52e1569a04112d22b19f857e15191e53ccc1f6b0e7412d863daee96e5996333442709cf8c72e30dd30849333ed62859d133f886699eb99c588e47dee2a6fd968fa14eb35a7284b4f97d4ee9e34d38f8df0147a616ba6b13708b3e64da0dd218b75ef9887472399044303e414e46f9338abf84db3e51b0962bce5c8818920815701766e8fa72792f9f7a21f67167749e9bffa1eb8e698bc28a848016712da5049c03fc095c4a813ee14f1248067a3647df1064abb72a59e92b83c72bf31532174cffcb926935e56f8f11af446b85256a72d7fac55a2cb6f958a8a00d7f41aef5024cd5980372686bccf3edeea1ec21c3768ca718424a15b743314f10a8679584d20d464f6c34f89324e3e27d8958d8cf7c6f91f95708a33d2c96ad167d5a1512c51c159cca7202490ad89321e9390ada3c9ebad8df0e71ddd21510de02bdd2943bfe571e75727f2ef5e489fdb8b57a8aa95a29c1d607143134a0218c0ab07fd3649de0523b7274297ea78b44297551c33da48d91df14733022f2a5e1ddddbc229573d2e0a172324350d776c93ea3c7b4511c42b5bf7ff431571e4d04df73b3560639031b84723af43dfcec32a93d8e8ccea38533af17693435abbfcbd137486ad325203e0c72b3062ce686767bb537aec3f44068d461518b793e853f3355b432cfcf2cdbd472e9ca67c0c62e7973fe7545d66aa57e34512a2aa387f6df0525bc242a73854a00eb57a1f010992f4ad24bba627aeb1b427390ffa11305610abca17b80f4903d6d61ac89eca8489847fee4d10beacd278d3a17e453a15cef6210f40c3ac36080729f63650569e3b83160ec7f61949bd53043a1b7d87a6b4412966c0b7c45737535b5214bbf5af84c934090fed4e7e6615dd0ac025bc15621ef7cb79f7e07c37f53a43a624465f14c7b09b7b46a36db1fc93eb070b36c11c2f764333c8f47359c728b26567c9b78b588a1c5b8582b9508679e7cb66e7afbab81cdef2bbae192c84c07baba11c3c814b40f3e45e90e407a783859ac7612b38a46ea406872e99e5b62ca2e28a2d6e42d7e022a8d4086879701e6b387a092d926f0e94ef0406c8d8b05b5a8ce8ebdb9c3040f9bd9b0ab6cace1eb7b3cacfcbf8b3203106894b346617288df732f90c572f78e890a83c1438aa9e83607bf665dfb9d5dc9e5e79921e172b31823dda26c6cac8808327672cfd97347867ab2875a48c0521d7380e7e3c810667453f4f466300c80e93d11190dfd1d3905428cccdad852b87b4eca09856a7225b90d025b62c372b695d786870764b0b35da02cecb88523a1fe4f71ebbe9c2e7e6c5de7aae55c953d6c65317962cf88f05f70fe6b8aca7101d214a979fb667204d50b6e93841d3b23b4d3fdf61cb58ee7ca8bfacc53cd3ed915e93ba33bd070c23b56bb484400f0afe1a19932879290870dad6925cf5b325e3100deeff6bd729f3dae9a8d36d49c04a3d32c412f69a391acfc748c749430efe74f4d3e410f72018a8ceca7ae6f644fa17a8e1f1bbe8979fe3501d2905cd71ce4f6aedb66f12d71b70846761a9be0dee773ee6365337e2f52f89e60dea99a5f8249130089b9728268ee4081eaa647905c3610f9e458357abc236da7d7e4ceeecef683f39a0472649d06e8d1b2b23bf91bff67172c4be833aca7eff43e20026aa6078eaa5e742ee99c37dfef24cb50aa80b7c35d81e83ce5c3c4c9f84c0c21d7c99da03960e61b87ecc1ed16270fdf8fd03516d06d8650ad26ec50fa8fb5977f5ab89a4453ef56b5286215f8de14f849d73892577ff3a9e89b9c4e3f663d5ab791a9bc8232697253ca178e962608b117633730619be3cb062c662e01758c15d05212284cb015728b7f456fd1d2f83d2f83df1e64bbb43699610f9961096c0cc9b3e5baa52a7472a336d43b2fcdb461d2ed867565a9e4c797fb73790cc1081226f64c2cf18012723e552373f0101ba1b4887887701e5c1ba24328fa276a7b9229ee99537a73be3c70a14b0f06c2ac3e2ea2b5a3ec1141a47eb7b12fbc65209f06726351fc037047e7ebc0c5ed85396eb14fee626d217f1eb9bf3edb2874df467401e07a7db52424169886c756e157a2cf68d0f1c6a2f4ecf35b596a0c5cab660d95c9b236d59572066e22f607946bb6cb8361e9c077570c8ae76051e7237b9849758922e6beab72ccd0626cc99ac9bf2b82df938b7427c072f9fa254aaa423b38527d17aadb0c6c4687850a9761e84053dafd28371c45e3ce32aa5f9555b5c1db688cadc7da4872756bfb8616eac6e90a01df3303186bae77418b2f4ac20ea8a86b10f85d73975ed7bd2b0696071b153aaed4d11f1ab42ed63c7e5ffaee47fd09d19c40294bd855baa334f10021f46fa66fb9783ecb5616b282bc167fc33e9b1b5321b7711a9b72fc1d0bfb5c8ea5f239734810394094526ae5af54aa17a44ef7f44a581d8afa7240287d7144b35c846dbfba57c7c785a2dcc8b6194e574d7528d65adbb490b77222ca91b93f5a725723e071736ac33974f60b5dd1bd1d4158ce6a7a705513b272d9bcf67800069d3c067dfa8a91018fe4ad871f884a54ac0ace27974a22d9e7586776e8aed2b0ab08ad1afd4c14dbbc65b3cb2bea86a120ed29c82ef60ff1eb6e502c675199f246ae80b422ad5be787ea5d35d83dad000ae8b7451b12e59fb368ccc6c34ed7d017d785a88c24595418eda1d66cf38fab61030bca60b2d4185372f2a0accfdd3a7b29500c0be907a80ee79dfc766c94b417b377c6c65cce19f4721d4f8197ac82696015ba04fab5d84c3737ba41b3568642315d4636c6d714fe72c04f2b010a367cacf319bb9f5670074b71620f5ac0c57ad26144ab89a0e5e572bb20bc717bd0c05a0c6bc2b343300cdc8c26c427349193c4b0e713126e68f54feb9827c5124f93fe9d923c76ed7b927997619d54ec65dc05b496093776278572adc57b6b543dd754131108d1465b5bd9bf3362a587d2e15bb1bf5e301b75be72a670df22adc71c24bbf158103f7c063166b7d869031cf9431cd231d15e58657246279706398d1c19a90ffb00f6f40c6fbca1252802c94206b7f8f22ee401610692b709100fc0fe13403339518f0ee1eb75cf8ba1c3318e54efd2ab8774f14a65fbf8d95aecef50816a1a62975e544c79355a8d09e1d4951c412fbc88b2cd6072ea2220e14566e73dec22a7aaf51b2753c2b5a812d2cf28c39e78279a13c7b27268b51cb678987ca3151310301b01d477d0d85f31beb177c67e2c3a42e5488d72fe946091db9a47176fdaed5183d93188df6d90fd2b186d9c8369a40faea62172eca61f2d8063f3348ea3909dbc5e29f41c4854130e790fbe87747d0f1fec7a72ba98ac257773e69ace8ce649562fb75eeaaa696e5387225d15c54c9adbdac807a90adf8bcbfa688c8042974c86dfcee606125c38a782becc976e2e15f1c59b725e8d1243008ef575d57c838511e34b38c5b573a9d24c8f228fd26715aedb0f4eb3ab5040cc7ec59e23e6d8a3e27ccd8416d067dde0d1d8ad0fa0ff43f24a177227aa8fc11ad98b85f2f1935ef15a8202954238ae0939452d543bdbfae970b727e1131e5c6293d54ee12f6082540ef8610c6502714e47bb759e3dce0337e44c7288dd0737ac2e1905e9cd349c79d21c3998d3c2dc9a214c63454dee69e1764772c1880c0b6caa495941a8de0e33189475dd0007f1785e3224b1621f434420e572a964d3142ee72e1d7c79b70b78508d18623601eca284b945696e8abb22eb5526b18014ae31de321ef3aaf5b559dd6b6324339911bddfd64d488978bf51880439f971e8684b7c6fc60a24279055087a300c748dec57cceeeae897b6adc3f45e72eb15e2714ce5437c7cb5a9b66416e70ebfed9aec828227dfeb6335bba8eddb4aa374180385851b9dd1dd6e3c64b5c174c973142be1c79d0abaef75afaf275172657f69f886c6c85bc171eb8901213c865de05bfcc403b8ab48b184540346006aad51c06d6e9ac06995fb11304c35a59c34ace7a1b70d5146dded899309696772d21b05dc0faeb86bb293c8a9fec5a1120a54f6ac9c7cebc371d526cae2e157722886744c191a3782ebfdc396eda6338266f3b36c0132901298f0ccba5e8d0f6d0a71e01b8a573a71712e8db5666897b04cc7ceb502b03e673533f26649bc7272c9fb825da1af6a0f66c43c73cd3ad89dfa0fda0fb732d74812954279ec06b4729fee0155a840de9f029655b8ff339ca1f78135c8be1847e2d9e98c0e2661277243eb292f525809a7b93fc1f6bdfe8db02688fe99507703475bf8a10df9654431b96466dedae4532c6511152f257e43f04feb0b5ede3a1a758ee1ba8fa86a9b727505bf42fac6dc4c4ea9c17fc809f4c3456fd276c7da6b32532fc249e66d927293ae89acffd99e5a465be7d12832b25591f0629007509244ded626a0f31e553915d499871c37907d074790d1f84a2638afbbe7bf8f4a3aad5c11caa2603aaa72e3432f7029ccabc9c177a721086b9a2e1f44b8fc25bbb68af1d1d85d2980d8723bf2a7cd88fc39c9d3a782b6776878ad88b39e6a711b456a9c9dcedf6cb9521d1cf92e652b4701248b4ebd6e7cb5afeaeba8da6832769ed5d4428e2853cff27290ee493c1de3d7d44ef08e3f66fd023fc1e5769a45992264773302618c12c73374634b807b526b73683031960610b5ecfd6bba816f25943efd9745ebb18e9f7248bd73e065f65dd2766b37410736f072c5325dd5784c764ce4af9190e3e9bc48d70da73537e694ead101522e3ea7c1872ffa5ff1b9829e9129758b46b677251844fc301fb93898cf70018a71affc1375fcd92d7c81ca99c2282d801c15b8381443af6b2f5633616da4222ff707b789a8a2d6f5ea5b5cc934afb3d143c2c1c115eb23989ddefc575c03138194edd049460eff765bfcd2e8055663fcdb73c1b45d9cbe6897847c964c91ec8047cbd206c560e419eaee625b82dee8cf3db8b2c272cd1cc1dfe9264eaf344114bfbba15c7d808f143e75f738334702495280dd5f725148561174696ffc8e6861d4f6ac6a9d59c43596abe6b884680803d2df6344721f7bdd0dd0b05d9135312272eee5dacf55e634f2d68409e956da751c980b58139fbbc56d6caec51848c2ba3a06bfe27d478ab9294f871261756086b029e8871dee34d746f30dd27255d842564af68e86bab7597a8090ec09adbcc29052fce07241a7166ea2021adfce872b1ba4f139d73fe9b63fcf81cadc72b32f3af024b5729d28359b7954456f95320bbc8b70d103669e49d33d512a841a3b0151087a37727d37c1ec8db096659b3b974b4c06f3dccd3f7ae1a61541e46d6c6b5d8854ec72c998e81ac7b5e6561c22ca9b1df0bfaa32070457481f19590084cc209c635c72b49cc21b7fd05dd78fc4578a051c19ee2532be14f26693b059db41f0d6d5f16083cf70f71a24bae55394d3aa07b0697e86d88d52e2201c6a2bda53376196160d34af08c36c3e09bfe5a820a2b008f3b463c0d14719754c91c0e1f0f0db1e99724c706a6870422599bdb3be69cf5dbaaff74ab1934618eb9b7cfc2d4e13a2432018a6ebe10c863c73ccc7e1283dad681d711d947e9e61a77dfe7ccb27708b4672d50c9d2a29287e880eca4538cbf2e046500e1fc5b29e7d98e60685e3ceeacc362c249338df0cd46b440c8623ca51f6ceccd0d65a368f675539e4994eea9e68728d35bf665337bc36762986b73e4d1d989c918dade8937f9707fa799c2f4e58724d2a059cebf5095f9888f61ab8b86bb0961bce36299e81bcf40cf15fd30a10726ef67825398dfe6cf702cdbfe997c6e017e1973d0f2e80051ec45c9a982c1c729df803935d2ced7ff60f220b5cb26e7bde68e4fc3b21b76a099e4e217b3f1872aa374eb5f0209b9a38437df664618a7db900b57d997626fa0f55f884f9dd1172c30f7d352f459eb7ee96194bf326df1e2de13e35f0a398fc07596fe522ecb072693d9992f30c66346ad7d74937be333040ced95475be35381462f34d97064b724dd8a11d2651e9fc5745aeeafb7e0d827eebc3bb68d199e3a445e6e66e2c5772c4042d46705d0a4c090bdf5e164d4a6b63d71e92d33b86455209b6309c1b4d1380f17f31b555144eef34e61b72d11c101d27ab0bcd04a7f47b22f039faa921725c876a60f3d1404f6d7715835f6133a9e03d0e7f0e49a0a49f9325bcda3cea17eeb3d12c5d3849ae45f71518ea8ab14066b926b8767f92ff507a53d58013891cfb969282f689bd54ed54d06ce29f7b0025f551ae777c95200cb22768dfef39721179d52e6d92dd86f218cd80df7dbe6d711def5a0b64f74a523742aa6dab91726e63c1d7c8be8835149b98419b5b7984189bc4776a9c6fd3307277bc8cefd0726ab95d86e5cb7905dd5b5b1abe0e10975cc8f0bd0258b86c05b7be400289c972b7e71c3e56342b1a222f6e68497268c8352ac33c589b9fa0bf34f58d10f708729224cfe0b5a0d39901681df122d5071554f8e91f714f92ab642552adcf937872aad0d5824c990f76e3117ac8a5ee6f88cf6c3956185c22da328eac8e39c37172ab81952e023bae173f2977073c01ac0e520f999141abe0d9c52b59be39f29b72b82599019855533d6ad8ce81e38d942210229fe27f2ae4393f4d67053416095715c18b93f99660c8a5efd28d8e6ff2098e3a9889b3217a556f626bb11682cc059f190a56d66a7f938b4a003592ce6560438e3a17d051251a523491a06a15ee722eb0ed6a22bfc34505b3a32458c4093e99b2fceafe1937c89fba340eed15572b9a5e1802346b2d9463bc81092f0ca194eb4bb1ca32760347b6102758fce7d655808b520b097d0bfb6ba20074e3c53b9febf7268c707724cb889993c81bd3035fb88aa5584f4cd08b51d38af9568d1e7b7a14683b5c27199098c0dc903668c41621a6dd8c1b27d5e0ad41e88c16e3777043436f6af889eca73970c82ec17a5072839be0231effa326d0a8dcf1664396e9acbe4fa9e76cf51f3c35758f38f27937a84020062270cd1e7518a40e2eb2032172d816fb55fde98ea1cda63172f54706d629f6f6a8b299ea9b127abe224a7f6599c93d6e745fa6149fb2d83156c90864794b1cb8f891d1a4b70df8128ef52c7dee4f9d1993578f88a8b673a507aaa77286264c3dba35eb531a40d2c49507fa65706bdef23a5aba23cb91e3aea5faf372a486cce307ea4148d5b4c010d58e0184b3c5463b1ccb71067e278ab5429b80563140eb59f86701f07d6ce2d6fb3efdc392eed5d5440aaa5d975aae526618290d6af8ef2e10ba11b0d65686f2f0866c1473481e84bb8a06c396cfd02b3d2ff37229aab21d2c3a5591b16c4a0350838ac3f1b98f586e993fdba799428b92a2f24f527f6958d922752f23b62704bae5190add8ad91dc43412a63a841d987bbd9a72cc1f7f41104405469ef2c95e5a9bfa0ff54085efea82d84b3e12ea03f20236118a97785f2a6ce23fb2261bae96683c6a82c291d5947d3c951cd72d75a2b5767281412494440c5a6b16876ad06292f06ab8e2be7100610dcfd6e82eee653ae76980863e3a64ebd8cf134b05f1cb0b5fd2177b516e1162e736ddafde91cf461b72bf26d0ae12928f5b379dba2feb622445083fa7114054422d6ac00bf1bb4f27726e1bc83f45fa16e5b191ca448677a1e7fce023d073fd53dd1b311b9c17729c7228ff896785819b3ce5d4248d9cc16cbbe232419bdea0d81b46181fdbfeefcb36b33f9f11ff5bbf926fa4195aafb0e7d09b1665686311427e98b0c65a4b10c4727801da694dde5d7f9b3d20f83f1e5fd2e732dfff3ec088590e2e6c6c84659c72c5319f1ad23f5c3efc7d0fb56bf0be1302b0a1cd7a172f3eb93ef39f75ea835335a6d2787d73c52e2924181445508418e27cfed782636ec3fcbfd436cc0b1372bf2b9ac919a24cf9ad4e5ec6e994d8dd12aace1a179856a79cc6612632d8f27227c35097d9076994d6135c2c0b96977c911195f35640eb08aed2f271eb9bfb2d9db7147e9cd6bdff3213791fccb91692d36f3755643677a25d15a5cfbf1ee9238cc6d4b046832b6de1ec68fe1a5932a71030bb07e91137e93f36d994b0a98c72ab7b843f8aa6fc0d29a0ed7fcb4e97f8393cd7d05537b05f684672be26f992720c45a87e69cf57fb56f0b8a10d1c1cc562a766e126dc6f2bec59dc8094e4e4151641f866594b161f450a5524e1e89d8fbfac54091d768de6bfce383025efc372b2d3ccbd80934a359a1202d918c66b2cf81be3216a0da569d518cea8c45af7725922c1922a5935a1d77f0e654fc0d24e75337a03bdbd25958dd45ba146cf4d118bcb11bdd4506810d37d28b8cdca26a05859412b1daf7933277ea6f8beb2ea7294fb1119988459c30e2a8f99cdd94ca238194ab8121e5ecc0196c2336774ad191505d9b3726ffacb01a0a6a47ba5be2c6aefcad38b9cf3e4b9322b08efbf73728bb09c19c62c8f69bae31d3c4af3302972b518e74678516d6d0b4ba65ebfb317c5d9a27409573d821e954db86390bcc512055734b739565a4fefcf02a8b149723dd6f52af7267520ee4f5991d98811a7d1e420d3ceb5135a7c4c3865b1b626726211a6ab97efd8926ed9281d6179bd2a6b245eb5e0e57723c9f87834fe4efa72eb3189d8cdbf09f6169068b86b6ef7917c5969d6a6beda7276660fd0156a4d272653055345f2557fa4b44957414abdab8dd56acca2f87cfe6614028de104741dd90219625e4b58f3b91759f80381855c6ccc6e8be9a8da214dfa76389bf7bf7212424bc8ef74853c62eec6600be191a059375b8fd038e82fad260a725f8e4d725394e00d35cee3e12bb6b83c4f0f8520419e6276aff300d56985b8f6bf77ad7200f45c389413aa36e1b42909edc3caeaa29c70ea6c57c8c4c5239ceff1799862862cc56f7d9aa5724c54278f49a9cc6fb0a7e7ebeebac8e72219718bb0617e08b12d8b8bb7b0690377f72798a95a32d0208374e205d2f430eb17417d57403b70eeb1cbfcf806110c5c157ddff86d46b0391e3c2868ee470e1e47549d02dad772df1534bfb83d48a30e859bd78cc436b12b9f479fcf6368bc85a30b4a0dd310727d6313e733de84998bdd2a20e8b344f7cca1ccf8eb5953bd45c7957e202c18725ccd14a4eed4275d443fc56b0cb2c1f58f66865b462c7294c8b76f2c6a3ce77245d5b9f218635843fff9047364bc4365dc7fa162ab46a63035128a1d043af1537b20404d12d8772851c84aa330e9f8a3b7b83de50c12dc776d564a3d9cc0ce72496edad59f4560d26ead98db159d974e174ea334a64901eeca3933b25ca179728feec09b53d4e1819f3b5017eb832fc3009eb52c9dba2277d925c27281636b7272041e11285560ca16b535a06d49b301351c1b4aabbf2f7420d4f86fe0e6c27281633f7a12ea043d0883464bc9ea6c65e674bed77a54f3d21539b7b0a3121a175215931ebc1d5831797ac80dc12f473566572526309744e5746d06def608e872733c5c0e902cdd4947d4611cb6e59a3fc64ba1502155f047f66ae246f61283727fc18f50aa8ec4a00a230423f2ff31afa72fcca7d4f483ba5257eb33b5ea36720919f8128d9cb82a20cdb0b1cf18abf076519ec643b38a6b615d652fd67efe64158778a3e3322ec7df17ddf76a9eeca0312d945c77b3164fc713c209c2bb1b723790cc0ca87ceae3012ee4c76c75c03d6520fda04d69d79b220f39d9f4e6a8724c962e5d48c416e661a9876cac31ab17e4bdb291960828f63e0d4c0f9d7762236ab43859ec9b4892819521a8b9fae040df2ecbc0c53e97afc6b730aaa2c2d17218a1db88353860f09fc12265b94d5dff6ac21efc205bb9cf06c6066a578f99724e75eb867319a3a44869e508a923916f9a6d5597452ca222d3e8c518d6a6e172056ca654a6014be366b36c91c464b513997a20a13d5f9a92c53b6f82f6de85728aacd0a8a60112b1f3feb094cdf3bfeaed6a83bb1af2eb8ac8c43cffa6afbe043ddabbe927b2f33d04bc07e3d80f91a5cf98c40985b267553d2d7ca3f52b9272d1794548072633b49f2243eef5044e1b00fd7f3943a161ae1aa55e3e080ff144eb57c4f8d6afa2917ff4afaa839304c0bb3d34d7ff57d47fc74410f3897e371eaf7e4de78d33e25ed7b15e7e8b0c991393cd271836d214c6b652b79effc47430fd598738231e2ed3729df4277d4f558bff49ba61fe4c107581c5cf41a3ad274de43af3dedf75ad76cdea4ca0b20bdf38860d918ae6c5d197026987b58365f672f004485f2ccbc59e6965ec11fd05d619fdb3e5e93ac1ec1194d45a614fcb63729019b4a0868ae2d30ac91209ffd8b9e8e2750842fe06ec9e036148140e903b72e8bcaaa1975d7ac494a5ddf1e2580602aa2bc12db55f212aa59ccce130069f72044c2869dfdb0e529bf01086807036e59adf7d5ca0bfabe9036f6da8c2214d2fee4887e2b6b01562d70e03dca24b3679f23047f1d2e6530239acf6bd686bc33f63524f7724f9b54680eb7a27de9938273a80d3ed87741f4207780ace6fee7d72c0f05ce098ceb497ecd4488af2b3f538c4dbe8e249331c06e244048625f05d47a0c2eb9ce76307495b32eed777adf398cce206a2e20062b0092c15c8c0ede672489fedbefa7049765d07c376f3ab1cb3a26b9840a3c250a0d47ae71a5273c972501d44cbf47a47ae657ff01095a9de2b04ae88b942629b40df849a5ad336780b08275bc210a0f2536d77dc7bde03acaab69dd5f4c70fde234311f45bf1fd7472a0150db03ce65a95243716757b5c510dd1005c1846fc615119bfc9636ba99543828bf0335f646f82ec60d8f53a21b827f4bb574a99875ecb05c5f00959980c0b2e627b82cfbada8b833e4345702d190e4eedbec4d211f7a28a9f637b17e36f39c4e1857b00180d14c068d31b619b6afb7305ea2f0af90692b120d166eeb9465e9fe1af21eb21a3ed7f3b9018e6ff6c4a991ca493cd7fd466672a1abd36b1da725cfa2ebf6335eb08febe09c17ea812e1468117457771233a7f30c629545992728a2d4a3ab51bbf6b2f5243b7a25bbbc92dc2d1970d95648854a5938511505172ba3a1b9ced94c703421b0766a0b6e4d1605ce406a39631aeba3b09f12e29a07222279dd6431ecb5852a6eb67ba01259210155b6696d027f15d728870aa02d927fcf64e8b7b7fe681fc5e286818b54ea216c9b85846c9548ac77b13e063d20c722481f351318216058d361d405f0ad3c40f7911a19199df44c88383ab8e009b72638cc4f4c078b9a1eedf74892fda303afcedd0353e01d19c8c7b442e5d985f19da673a62191290b408467e8f53329a8aaea4c83e3ef71ae9e45ed08c460374723cb4f63095609fd6db47a15343d8096f4eabbc31ffefff25988d007aa0666244b159e35042dd2b5b1aedc54b90172a02d337beaaa99890135f71bb26827685722b60d4e9b9c6885d3c10cc682e4858c7dd89968b4cf6a6ff7d8c719df4bd897297201272cb7fd87a2f13d69c826e831a625e2b9f4369ef1e34928adb46d11f724d05a76baf235b4cfb4d41ed7943d19ee60f6aaf549e0cb73b6ae03e1205880c584ebd1406a71f6e39f876babee95d3697a6e7704326bfcd0a4dd3a85cb2df335d66a9d6ae44925c72b0f49ca2ffd96fd3d9334d907b69b4d7970a2aead8a03ac11897d9f7ccdea94fc5a9c961351bf45a1c5f4076e97248bd3320b9997a7a3202754fc52df31ae2ba6bd2d5456939fc14c0c08ed2f8d77b0475d20a95be9c72d0afb2a5bd998e43339c09fc3bbb4b4b5318d09dac81adf8f48e47aef8b85f617676a08ee56ee4a27527131355a1c590a8841ed49ba60363cfddab9466770e2160f781c6788ceafad43bc988613508850486e76741f2367545af399205eece7261955e5353fd539abe155a9d111e3642d0b0ee076aaa7daa7a94bb4bb26ec14e4329b6fd03ec53e08a775f4d776abcd9253b100cf584d337ffc802fac12d1e72c387e507015eae206947e431ad536fca363df410904d2311a7333c6fadd1ee72aa47f46211ff32378fecd205199546f99b49e048cd9931c4cd6114f37771c80983783884407bdc8bdc91c6340f2c28f2729249525b649ec3cc1d49e42a3102102d30a7d5b9be0599b0d66bdbdc6b546eb5d7fa9ed56fe187952664647d04863568cabcc796d8e9642652298dd0b22ea7fa0264f96bec4a68de28c86b76a7e472ceef0ab3ff2949672377820f0412e08d98f8f45db3176fd3f19cd00d78fb6542a21ae6427bf51f3b2b656e719a0c31d39c0d4553581f2632c5d40336e3e37672e7a76c39aff2c6f879651dd419562ebbaf0a84e9bf7cbb95bb9ec4d45ca4f66644bd4a11965c3d674ffd1706a9a14b9c4e27ba4f9266edf39aef81fda759f9725da404b76fe6e3d51ea09623acb48cfef00e7c0ccf3117073aaee3aa2edf2272c4f75c606f4ebaa002c2ca7ada1450af7707416d2f30279b1f0672f3b1fa324068df1dd434cbe9c5d29665ae790a0824ae0b385d15741e6587aced07d2c37b72af8d8270e214791aa2c866223d1be62507fbda7e3a78f3878fbce378c68e5a72980571fbf4be6a2a895819e6256981a97fff243399afa73d1190886e72f08910d622e8b231feacc9eee12a7e30fa0117cbcf3e5922520371a9f348d67018d372b12eafde0b8a9e43cb41cfe6b92a70b56264b144ae3685cda90851409365ba4493355cc9d709e4a60978c8fdafe31e67d65bba9228c9e1a9598c995ec04cdf722e00d2c6af0f550134fb843d7086cf759f39b0957d3573807c55addaf9f77262eab185323c2299cb5a645511717caa1323e8058a53756ed38b443c4e1d02067228efc1f54210fc7d1e5f5c3b46ac224acc3ea3a3a9c9eae395e2a5ef7b275172c232ed33ce395745b89b5732e8e2412912276af51e7a1bdb14f60d2d20e1167292cbd51236d8455e2d3a3af09a06108b01d951916c8c2ab3f3cbec5995cfb272fa1ab13597fe475d9c63eb851c43a57bf3a00c17dccdb7775cb45224bf83ce1902335c4db424cf091944d77e432a8979f727bddb80b043ce34e2149b9d88c4317f78a630496ea438d3c9c202d86ec09a4325d19beca14dc47d663a707ff0b531518be9d78b3ca672b9eb877dd230c72d7f682135ee27bb2f1d4dd40343c1f06e6a16962abec6ec056fcd382a59a0ae63ba70c5d24b4c0ea80287df0f3d6005728e64dc49fcbb0d1f2f5d72e73367b80940654088f742d355a4ab8296f2afb43e40f473f851098e7159f2dd8bc84ddb01019a3e7d5e0c06020aad2266b0808f4c4d113a3e2601474635f10b620166ffbce8dae98639871f05cb0ce167b256e27225fee4b4648e08b80652d2551574b4b31fee70be3a28d2107593a3d976694a22d45e3a2700c14f791ae02926ca38487326742948651d78a1b6833d10b75faf72f083c6b6a4c73a222d36819e3bc7f3a3b87c11c1a4d257f343a2f7dae923be0bfd960d790e4fc06cce63839948ebf3392b20ea8e9619581a89fc190b4baf8d46137eed677d675826d5efa5d42a831bffa8707d16c1ecb24a984d4d96b729c572059c4237b2253586ba04888ef0d159530335e6b57b279c01cb9135741ce59a53ff3bdd9f1a93924df7f471293d28ec0d8f53c1c2c5fbbd2dd991961b4bdaf172bb2086e19c0cf3fb206e57e7aab1d4f5f01b21549019ea90796f2be2aef6d442bc407a72090df2797eb540799c018991596541c39179a9b0161e21b73363761854951097f1e1ca1c26ec9c6c3c0832fd0d2fce1dd9ac1b447ed8b1eda972a3117d358ff0d18abc76b1a54e27dc7c48258f620b1bb9394525a48e5376dfd9ce72490c8068abf79afa5df89afa66049b9643318337d967484f1389f2f683d0d6723e143e7a12b9aa08fa59cabb2b84ec29905f63bdd6ef97309a81b775f8ac2a720877dec6febbbcfece3ee5c2e6fbda53a3af96015c9f13dd3ae3cd46418157720b73828850514de17e4d99631a96cd6a6a0b273cef07c565b4554d17dd298972e207c90e92aed1a33e5fd44c19def847ffd2ef57165a6f457e8730c0b0720511fc83323e7e075e13eec250d901ebb06c059c383db365811c09f3fd0f48cee30240369db5a5948833a0ee0b14f303b74518e08431e2c5612a198644e4126f4d72b47e30d9ebfd7f284a46da6a3cd0b1e851a66d7ac56a4cd6d9244ea788a891724bd5e8f8ede0b9e9abd58af2e140cb6a200febd4b8aed7f84f17c3ad0eeaf6723eb47dbe98efcfbce01f06bc76983367cd83c9e4294e114ce5074514ddc45972e7cc7768d8f57a1b54ed3fa7f227d64af50204ad75fc637d74344c7bdbf3417206612762c3489216dabd4ca2174333b636b5fc4ed28f9bd4f190656cffb27272b3c1ee5c86698bd5d14f2aba746e52b926ee86791cfefcae1c794b5113c624648d66c5c3eec1850cce3314d46a0a79be500db437d86dbe2e8937f38c23c35c721b5b409c1588dbc738364d8d1ed417119468d5a1bb194ee6eda2caf5517a6d7206528c0d79268b921678b4b2cbef30f615871c1e381c52e476afbac930180b72dfcfc3db5b2765a0044d47da3c914237ec2d97d3b0bbc24e032788896e09763e029a21f65e5a0d9b15f60864137066fbb7e3370e0bb06c181cb62b1b37350a7207f5a898f0fcec0115d9b83cff6468e615e84e076360673051d690d6918baf72683e2c1882f76715426ac65c052f746c41d14acfc0eb31a1e01052a6e076d372fc8ed290bc3e44525fdac372bab12abef8f5dd7d2a542499cc23585a9902e572af2117dbd1235fdb1715c27f664b57d7b2c41f7850d6fdcb72d91e87e9dfd1724361e32d4edfd8502d1f1711c5e36a8b55364028a1ce797cb532614380596a7237b9a6cf01e31be158a9c6972185236903891fa515c21a88275c3cb5e05ec63c2d30c8fb9d0e9a6604440f5554024a0ad37325cc55ef782856983101fe3cca19b11f8639e4ef1193ffe8ab20fae8167da61c47040a116f081471dc63124e2e7221c95dcfdb9d8ddcc52d96033bc6075d1b6e8f3ebb7353d3b0ed59140b611d721739391715cb6bd0aa0813ea70d2deb9b7330ca77c47c5f115dc8c561761d9724c17af3308c1533d2f6400646b6488cde0330c4248a97229d6a9a202f445c83ea90b2f1858d9a1d3fd7d9e12cd53ecb685b5423474d46870acfd772b0a4fcd725b711c64b2ca2c4b6deb7c78808a79c22676749b214c068291cc4904f3a61c72c540c361d7d732332bb53ede719f07ba3b47aa6b9e79a629d8503d01a3a88372029643cc212752be7cb90fbab2eaa422e1ddafd93c1c4d85b7be17614d167e72aa00ffc254bb6238d712126114e8cbd5c83507c8d9b7b5f1cf924301b7beab7251c8ce8870c476c0b7a2f270308eb87c4472795be529da61edf9a2e07365a8729721f05046e841727465b717bfd51cd036222b97cff8a7d7e3dba6523fd50e53f04ce7ced37668e12eb3b21f4f38f373c1d1fecc2e6df36f1b53e6c0dceeab723b993c2bfd6e0355836f215703556edf9aa9314a87d397d893ade18d33738e72e2fdbd48b6055d310edfc51ba47a4e55c0e8acec04163a73522902de373daa72c8ee03200ec2afef92d45ee16a8ce44108481f4a0ad91349df4b7ba71350ee72ed92c505f493962e9999751df24bd1fcbf4768d1f35e075061dfce91e2bcd072be45f8a97e53adfb19d15f595be2acafc28424fb8e2121cde6a1bf145809607239b507a69ef295c2d55124e992bc23dc4f0f19cee1c69e19b43ed4f83e6575728dbb535c7521369cc3a3f6a53224907d36a2ed553c1912c44ed9c34a727fee72c23f977208008409f9fa158b21ca870a341e9c5dfcedb0c432a51e79c55df072ff6acc0da7e580bfb4d715479348e52016386203e54f891b2e71abeb160c505589a93bf6df21b847475c89ee8bbe38f290666e032eae2baad646b706b580c20896f7c31e24f3fb0f0b81086a872aaccf1f26925af9dc212c7a10dc99f6478572c4c304ab99f4461c6b119cc42746351ad6c19c21ebe719446073d33613ad0d72d8a5ace4644d9d3b071f1986d3ab84c917fbbbdc809aff3c0c577ba09b6c88728842198168cb7a2c2d275f61693e249230dde2d13199132995bb45db2fd3021b7e2cb6bff817681b4a474e02528f74c5e8fe850a91df113c140e8957cf42c3512e2dc16023e2f3e9b9f8295cb8ebd86e75bbbcd1332b65a5d85cb7aa4f1f70604e32fae669f6c1e182acbdbec9c636b9d8e1fcdbc69e56ad14fb680483227f72ac2c065c01ecadf1dffd2209c9f2f4ca657b595d0f6c51678efe7469a6b35a72303d85cc8510417a97395cfb453edc965b13d21f18724fa220d18f33f7549172e4ef2d3ef6618ef0c0c7f9cb9cde877606870ce0daa1bde9c66cb2f26154bb17d230bde7b19c8a89b7cb2e7b1c9b5f9a86d66c2035c8d6f6294ab13e1c22d76b602a69bcdc8d268cb40ffd57d2d888ea427d75cee97dfb867e32b1ed47989c0068bf82cf116204f1ac9f8c73bffe36208100bb65aa1f89050d4abcecfb6c3c7283ab178f2c25513b5e008c3ba67ccab6802f8d13dfb2a04733ce9e2980b8983a9ef99165563af696fd37fea1fe6a30af5da82b8c34cbb9673c4fa3287fc55f5e0140e7d46c428fef3664a9e83667281c249d849f4640685558c827a1881a3f13d3fbae79fbc1d7960ee67aa3d66fc00eb8bb6375db2668c9248c1397c7603b72206414c0a3cb47040d002ed52592cafd5f581b21322a379d224c67e9b4a0b43e0542b652e637aaeed59b2688d3e18a0c5dc10a3099ae11b5cd33099a17c055365fd3dfc1b59563a93ba588b67e46f48bdfb9a789c320dff51ef9514b5a6d404252848ed881e65e609a9a962517eb7a1c4549f3984b002dbadb27bf2164dab1401e7630d4fed45ed5a0a7e0ed24214fb1d00dc8350cb0c771c6c3bf5e92924372a9cb956a0db2e82c226f1ec1eb56433222fdc7860ca6c12c9666d9b2abb4837222c36132ab6c6a8927b8b1e8caf3696ad84d327f00b61cb2ce8d9f535d06061e10fefee216b7cfd9504eddc157c4b4a1c310ec8fd850aff457aab2fe9bddca72dd565fcc7561cd37492c14a0010910f4a2ddeacdd2c5e3843f9ccc28e668c372a45e5c9c835bbb8f790fa713fb2849601499848da69a3a9a49126b15ef509f7276f7bde4d1b272853369ae0306518e79e1358bc3b59a3d239d81024ce6efc72fc1b542a89a5e8e7e262b83825b560c0fcf743e0d97cc3d829fbcf0b860f84e6c48122f8c00a8d9d027d4c12e6d145939287b7b7a52e9245428b1011dab91d27205748c99c9cd254179904e7fc17278e8e7c52ed959281870153df23501c05742ad5ebd952ede4525867ddfc7e4dec008c68a6818a7a8a5a7acb9a3ff7e28d10d75cebdcccb7a6d9842920fc4640c0ac1b8edebc61ce11ca2b6d830056b870608bbb4eb8516be1f55f51ed7a49e50f2ec62810c47b68c9a1a1146b83542244e72abc2ce52cad25b87081856572caeb1f3618b1460d097096ff34d5837d7050172c790ebb4a5346bea0358b1bb2bac7a8c4bb30c4906e3cc380cd4e2997cdbdb72a200f1535766cf483dc26956ecd6bb7f24aaaea8ef62c1dd604fa3be500ce5531e1aa397436034aebf760a9a2b920b47c16e937184f6dc31b7c481257457ce72af9efbbf6767759e83ac7fa4bc59772a3032bf604844f6fa8b08ef800d41d1721ae747ce4e09bfc5b3753f845a20a1a4c2fbcd57a253195ff1c16a405a64f54a3ec97e2e2ccf8d37e14c6d64f2d38dd0626844667fef28ad0a67271217b7855a341df8cc24bad852445e65fd85c82c986c8185661b140e5d15125dd18963794ff36092f525e1016d7a38e76aabb1afeeb754d7001c0b92a1ec94a1d8adc35c728a83784118090b5a0aeae15304e38ef8c1ee7d022583def2bd288480eb0e0a0ce89b55bb272f3bbccb68aa3edc9779171fec6401c7eacd9972639ad6358034726277bc719488b3dc7a1d240a74d85dec5baa5f0f9a503a2cd992f23e85880b699b8c36c43a1df76a3952f7fbd48c8687eb956fe06ec271ba095f55d1e0d49472b83ce5baaac55eabbfcbc4d8a7204a598c5100ee2a9079dae7c3f221b6f1c66fec26bd48ce2c63cbbbf6e541dfaaa70ffeecc0aea475e5f75d33e6564502ba722a34a3ceafb2836e8fee7182e431573f533ab027740363c4282073957084af51309caaae9fdea6f622131849b945898314d5cb23f8da5c5b19a06743ce018239f0fa55c9aeb9303e3e502bf6545e6b39811d943fd150c99d8574103657e9fb6e579430723084ff4df8acd03c019985c69f36a83e6dd63457a80de4f3b7e30772610d8b429d549ea0c4af0888408426c3d7be883f282502a002d563a8cc8f3f6e0f378f5481c176c1fe8b44c4e0224b8377388b981b1cc5459e9df392be68aa725ed56c9f414f0980be198ae73bd84243558304499fd1702eaafca56b352ca52daa9c6541c01bfeee2b2f5b75bbc40255315e5bef73f1e268c1a65d29d3f5a43f5471e7f83284ad699823609f82d9b3ae538e5522653391105ca415fa90c92c584e4680111b64da283a064a2ca8c1a5f889163132d4beb19680d22d66cb1f2e727377a4059a9b66de2776b69ecaecd8dbeaa9c4f2e7c94511ddde1f9ce399015fb7a3cfdf4cbf6ef2aaddaa79851e810069c6647be2dc71b39cb19f5bcaccc55c65c020adb35366c199985a594f7a9b4dacce8cf248971ae862fc4ef00807682e14ab564d9e0fae043e8f9550ef1cad7d80f966744fda02cb352556a83806060af57c610f5afd84ee0be05e7c7df920c2c7b0c4f7520627107d502c601922bd3b21188c72d0313aa540ea83610265fabdf9616fc20b3a9eea0bc738c6d5aaf427273c92a2c553490f12fe7051951d0b84dee99b49183bcbe68cf9f60b04dc79728500457816eb70ac18c8fe7d8deaa2a66a9f42650a8ce22c39e82ea62f524535990dba8c53df8efb59ff1f17d36e9ee868f014e2eb325ade6a03a721791990724c1c333de48f69a5eac9b439d7a197c070fcbcf729e3412430477f77d38d1b720802c45dcc24b879e3c970ced3e7001f748e3d41f257137050245737f025be5c7d991220ffe045ba29cc1fd800657774f3dec7a64a5456c9b3116a7e4a97e272e1ce3ce520098d1a27aa87fad9890b64f10beb9753ae8ea0bbb885526e0d4672cda8b2b63b48d4a7bc710236f217e3294c27ff65bfc50fbcb23452930fc7026a0aa34f19ac4151c1fb381bcd5187be8952543a6cf4b90fa111c28ed8007e627223b16b8943948d3332bbfc9c5ad1e6d7430d0185338197cbd0f8a492ae1778726eb7ae7034af7f1f99a45746f3a5033f4ae1f852e057098a721224304c4c5922730fb2ff834814aef46bceabf72e359dba402b6252095f488dbb8c879cc7f972e087c6be2cfb21c9b46c290a8e1542d3af6517302bedb322ffdcd44c6d79f2727d9f98a191ed386aab8e1376a36b80a8e12841b746bdc98c1e1fe0b0e63b091715212154308590ef6f3250cc384657d229de1788b633279e2458800d511fea7269b330b15d69dea4eb17512b25218ec149068f7bb97e409c19fa1cc7cc8c4c72c68f1342700aa0b273cedea7028002277ec9a5e6987667cd2ea4abffa1c1ba6a82f31fb1eeefa66e3d5c995e1be46d276ac1a79aa7d7695b0f0a6d6079264872803dcbf2f210a7f1d8af90f7c4da0b1d40608d80b2eb2a2cf816fe48a9af1f72f09b5031386b9460a9d8c47372f039585cf7f0cb1729b06ab8b136d1d62b081e3b11cec4bc1efe924617eb4471ada1334e6b64c554a589a05d3ad6116cffcc34e329856153759ddb7f51365e34123f93e34672c57873fa5890079e05f8964250a10908a44eaa71cf189b519e0a2b66b820e414a5220e4105e99880e0de4dfe72e0f66e0957db4815423dda6e373259587ab6eb4577c8f2f5b331cbad783b8868dff1d6d691dcf7b198c7e0d9655d7370b0cbc242e7c7cbe199bf903d4979082d3320305a11effca90a05638e40e2e841a12140b38b38d091ac2b2663581b0b3a97d9cf5a4a8ca022314f171da5c6bc894a5f0781a3f1e11a9f8daeb43d47d572ce47795abdc37aee3d4c7933ed953f55c7ca5f642f29e6ccf3f728cf5796e47206f12a9feef033e9c35282d03349398c776f07b6f2f04f47de795a8ccb3f0b24e5a3885e4352487d70a18e8bdb35db935b8974e826735ed16463888baf1d506659d37e3a260738311259af6851efc2dc09c1c4eedb8e67a57b5281ca2a7c1072290b34b330b68f22bd2bdd1ae800405d8ee16a663a7ca26ef1f8749c01517672d5ca8883f9a849b452e00b37a33f94aeb5f861c6dc50f36c1bbb8cb26c2f967262cf421397f4aa8cc96fc1e8a1d0543dd112e1a8cec6f84645ba0b8ea6d9bc72317e41dc273fc82124597ba8f976a5241097a060c8085e5041bbfa04a00c6b646a0c3b3fc5dd956eb4be2a307432b204d7730cfc2fa54c8eb52f90b28805d71b359e06517676ef4222db69a82c84c20d72845e45635d276fd8c7db5d50ec00726b8eaa44c75516b175d39c4ae72f5a43b9ba28434348c59ffbccc9b4486096062e1305d468169636ec8911ab000d0efdc2a48b87323d9756cf9bb3290e6cd91be679be6e662481b33e7dd25a4acc99787efce63d3b80cfc0432d37c0a302f037c8b18a5bffc2ec9fa08c966e619dc3a91aef0a6b26d7c13adbcb6d230def70727934d6051c471ab03a2cc085939ffe0cbcd8f839dd619bd904700ab9f9391f729c2aeb32f5793cd4151bed26a726bed6188f32cceb3197df08e3b8c26c6b26722863b04c58bea52f33bb87ae057267b0da76549af7af1630226053dee65b784294c11931943187f14125888c4d52359b33256088893a4c78f2aa541a927dfb72f5cc77302ec0ef8bd76a6933bb0745f72150e61fb8c7cea27836419a1afa8b7220f6870b16cb0b2dbfbd2bc5a94d544039d4b173a8b8f8b728a26fd76f9c53729ac1a9422a4c6224d9de1189a7841c5c65f981b47a6fe458695b61f785ef0b728a9a6e3bec83db690a64b23415781e33b14aac945a54e0f88a48f2b49fc8ba721a8f73f217085f165d5d41637381ae80a6bb21f3c82a3f6c96697fe03da34f599408960c83c69938f1a1cff91d67c06dbfd63616983586b8ea734339d4dfa8720ed81d1a71902c50c10abfb64afadf0473bfb07e02b864d7012d9cbabde621464835bbe11328957fcd144b2c783cc2585dec7fe06ed0db88b2a0ef7645b1db5a33b218c897e157a68270f1439a14a47ff49dd4d421852e3197a654f6604fac72ab2c94862149a94976d99973daaa7533f70fdb1edd0812d7fd2dd96812336172ee1b2225517a1bbdbd60947e586d932e1a17a626b2e96af6bbf9cd742b66b3150234804f7674060369ebba46415de5582d00b2ac9dfebae67d9b363c3f8ff672f970a65d59213c8f4be9ef794d2318d9b721f8b7887f82918db3dd1f339aa55400672effa3a747dfa4f11ebbe23c709f0d5b9a8a67eb4908c30f766e5f032272ab5adb0afe4647927db8d0dbe319b91ecfc5f171207c2e0cbd95a46eec03b40bd6df31abc3d831c39c7785d8ec35c5ae09b3beef1ab13175feb86945e7830e72d0be10b0a0bbf983e5194c60c513d0fcbbc653760db6e6cee3a21eec3bc45250a997607ed2c0bc9ef20c915c5896a0c9e6aaca73bba96d80532119176decc9722ab557a11c6fb54ac32e933e8f268897d5e56af286a62e17e8cb04db6e1a7c3ebb3af0606a636f2a59d2d04579940961ced871d8dfa3b41247997b56878c201b3dac5492d5e505bc4350cca136542bc5a1474fed227e109207513706dabf5d5b3ac7bfb3cafb94f18755487e968f36c6cdd0590238088313859e1f535530762fa8b9906b5fa7bb4be71f24165faf0af863924281139a4b8d798bb558d8d8823b2db25190ad18d7e507449f1ad25a056f23065891f2af885c5b16802287048b270b3070d73583975d5db11c93fa2dca93f5507e1f8c07a083f02558d8430373724706a92e1fb648cb3960d337dda77337d382cd4f338df535f22479fbb4838672d6caa81ee921fdad59ef75eb48b4d03e278ecc2639a0dbbc44e886f5ac93ed72dc2e5a0446d7de370c19f37f5c7070d3ae49a408adc1df2b9f09d4a0a41a9472edd8ae938f6ffbc5f3eb30d69d565848be523201a7ee43b4ee4356ef35f74f72646f195e22e7875b20045e80925b02c7318f340b0d0cc137816052961fb65c547c4721935644ee13477b8dde987f0fc9c5129d5d5b1e9103eeb01da89cb66572d3d9b7d12f0b3e9a77d3bb5eaf01d64dc20ac5ca7859715e714a7bcb9ccd31723c6fadf4d358146244d52e3bcda665dccccf6df0773dbca1a96e8690472a38727a7b37a8bc7244e5658de076b442cdb29b5377ce591b1020eef95523206fe6048d6841ec968d0b42dede18bcf4fcecead470822297ad808c676ce2fab91e5e72bc819468c1c7131440235727a2ed421f9d3b20275c1c8928a2b1b3a0df77c41fc197cb88d307c1da4eb1e6c62065172b0d7da0eadaca13306dfb23d584a70272864a7f4a6e431defc2ea0b30c1d4216b58fd33427d2a0b296c26c6648cd7cd1162e644e03ebef0cf362ec98b1e88e191632c87f33cdb2eba1e66184243386a38685cc992bfe88a9af6a554c73e289c0a8178c7572c67f2633d511ae58baca117d8ffc00f90ad37981f97c654bd9e021c6b3a910ecec1e6ce5106a7b3b33d0a7276f20edbee8ec92804ee80120e622176d4581f887dc36de2fd110c29f7cca926c2dd016f22c8cbdc2cec026ca8a5942638f171fef1d44e5bb34293e0648dc63b3ac621bf10972118ac38388c14e2abf2fa479e6df6010f4e2df4795e79795c53295cffcb638804b352b33df132ac3645f930fa61a7a9757ae7d93b9222d26772e1709a94a578789fd99ffa8379d38d0b747bef0f8eee252628b8adaff0869b33464123ab758ab3a3f61a39aedcc3ea0307d18fc80c17a36cbe0eba80671629720274a83c675a105227b644ea340401431ea911342e90d878137ff03414bf3072822d9dfb5558cc76ca8d676d444679205ea12aabb89922bb983acd8001ed7272669fe7a6980759087de80730bdf001c156c4428eaaa51e009b8cfa30778e5112e349cbfdadccc360e2a65b2121c30a16ca5cec2500adcb98f8a2fc74ebf7461720f5dd5532f2fe64243cf751fc0ebd84207cb69ac300d788b04998bce5c4d1720399a73b2452a244a476be97a55718136735f484ef1895177b4546c67965687227c30b3c45253a4ae6e1fb3186d442859541a0e2db8a836b088e14fca0b6d47227630ac21f24dedc37d62a9e4848e4a2d8275e6b58726f8d2a50da07d0e280725cba412471e407a60d77c622043416a2a8fa2701a687d7b319ef249f1ed035729a34a12f461c4ae1c68345547def7fb0db65406e74d01b3f5ff43291ce88717297cbbee9e20e17332956ad19ee8d55ab7efa7827b1134bafcb58079ac9d23d36dd3cb8bfccde680f2676eba916d437aba323c939a58a6116a39f10b5105dd86048ec006bca0e5f157d5ab38f189510dd6cd5fa0227690132ef4aa240db014c625b6ef4ca31c24393e452a84fd3975c36b07671597f227aeed271563e90c45c7266fff8e5ea60a2a878c9a53f9ede8b0858ec4af0880931bb3f553a518f38b17219628eedc5aca3ca2211ddcbe1272c80a030837d64770b4463596a5dd40874721fb8f0af8f77556a43ed973c58e5cc6a66a347132b554c0cf710d7cee862f8728111c9080a2fedf003b6fdaa610e6550ac141e74f549dda188a3877f2b834513bafde168d59149d0adea36538db35b27cad23a747b0023c7b159d9fc2265b136aeb9c2f12a82b2f7e1f7e826b1845b8d38727b6cf544e2df9939a2045ebc55725237f7d32226211782d328081283c898893f4ab519e128e5a51247a2562b191de4054b8915b87c488b03d9ba28d526beaa153241e859bb013bf1153dcdb6326c98291d128e2c8cc454ee42462650770c4fed8e960d5ef5c1f329a54609e1266ff4d8b342491169f75cab353f99adc3475a90b1d57ac4926beee5bde3096dc0723b893d64935529444ce13b96a41b1c727ea0069a7171c51b2c99d72395629372d61041f4c25bcbfc99657e16897ef3dee585275cb1972e43e761cb1107b3fb30483b77551211b39b9f9a5f9c7d5c1167c4601a56e49177f322df8a8b731b75729ec326e8c6be8f220e8395b0bc06a1c89c50d821f939707f8d0b1831fc046b723db19abd935e1c46587c8aa8ec5a25a65bc46a34be9dc9f6eee68fc875930952bc14e5619aa9ce9ccaa7cb134af0861c22fd6df5be81bdcb3f9776eac1a33672b087643bc6f1766b00e066ff4665e33613159571d07b5d8c9595afdc9bdf30728201c66ad25514fc14f32d3edda5330da3e5cda0cb9170e0521eab2afbb91372db56302034678372bf296a9235debab9d55d81bd3681829faeebb5c4344d0e72116b7964731174423b6ff3cd62cc8c4df7a5904b7628291b9e2ff24ea044447216178530acc6fda234474303a970f6b0a9093af308d22692a7c89e6dfc7f1f0d01afe5748c38a79863a4a4815d3f0ced3852fe8e8d15bce374f5ee4aded8b60382f7d5ef7447439c26cac1dbd5799c5da7a2099908ed63f8795721be37bbe2720363d9a3bfd9923d62b8816cbaa0a576ddc850e9eee00371b6d167097bbbaf7244f0b0bdc42b7de08de8f4f72abeda4ab6c5d82f0b3cd9b834b603003efead45096f0bac0546988e3a3c3a44e8499027c697a38814ceffea6c9bef82fa07d372f4521eaa8ae9e28cbdc2b92af5195dc449fadfb9e1864d07f9b001b0cf2b1172f92cf6693692f048a14a276ca9b2783327f426ed86dc7d9dfc3318d60b5fc14daceebba7938e60c1b09f3b578acf318736c79d5ce6ba8d4291e8fb7fc5ffb472052441219bd351049c685a664fb305f80bf77e2a29ef96180ed63e6877b46372cf0dfd6641b786ddb5423315f98823b5df815d2fe670890cbbd0aa2b77dc1272a22cc39ab9c2cf557b9b983f13c9e9466688361b88aa9362d59779d4d0630f727c4be07c879c0f4c3fa127de0fd5c028095563f0db748c5f0f3a5c02f8cf4a22e5b643015f3a5b250f1de8f80eb3a9fb80983d5665ee4c84b5f384d31e0f1a1294fe8e4886d1cee8219c89661877c1a95b3b06226cebbd50c66beb8939e0ed723780283e317e93696aeec52e89cd2053666ea3334bcda0b2791228995bb75b725cb5da4c9bacdb836293a491a114cb6b4e1df2816dbe2682d17728a6254f3d253d5836a95417fc82b6ca00d99c08ccdb75d0c516d9eb0b5dbded166eb407fe727ff0ade8ba0bbc3f1d1b72680b9213c3a16351968241d43820574e12f8b14972f32e54f0b7a36335972c6e82df67727ad40aa8fa7b9b61a596ba036584741e721cb0008f5b1646fccb7b10c0c767fc5f63a6142ac0ccdc0af75adaae04cec97252899a05bab8bfa6228e839cfdeafbe1e92f8a16afc6b9e8b9f772abe472363434fbc5a3dcf1a55d12a55dd2f9e51f44e93fbc1dac993bb1f327001e13c4e405149115df83d9534afa448fbebfff0e6b823eb0ce4d27bc167db0b447768f8118be19bf2d0ebeb0bcd110499b73558a1dd4eb026ccf9bdfc8f72bdb2ec6df4b727e5caa7382cb55ea839fa38323177c3abf6bc1432e19c52a8bb1e554ee180e3ab0bbfb1617850906f48dc3e112c4f3702cef57875eee9b4d92962eeb0934f87288cb511e2d60f7fb5b260e9a9f8d74d8034ec58385dd85eaca1dee4f97ecdc4f16a05ed1dc1aaf0a2b9a31fda032fbf93d406b7e7f3805a09cce8f3847c9f7726345cff5118c6ff5380697ef033426ad15eabc87ea6fd5937f9ff9a7626fdc6add5199fe01412e8753612c9ced2417090ae27d0c948c9433fdddfda8808591647ea48cd5bb647dd861939407f383425072a1e84eb7505d6627e599a60d440803342e009a4c277b3da6749cf09767c0dde220c75c4d0a2a8b5df44786e8c6526c0243c34ce6cbde4b4cba3073c37585f7b34b11a6a03aa46165ea3961e889fc72be756148f2f5085cb307b17c7a45f5c447126d8bca006848a55de0f82d2f3372c8ee901b5087026042c94a0ca964f3390dd31e6e9a5df3a8e09cb64c1c2917721cdf8b1bb3a4c063d395b083983a501ff995b8d4c437dbc2a22d1656206be5726aacb91bc730c0f6d2b5655bc8e33cc733de3dc987c8a3594cec347bc1e36a517b3eb22e800fa849951fc9d577407a58ef4ecc3945c622f30cab9a279e0fa12205fa375b52b15ae8115b6c2d63c0815329e1c7631b3696bf68eec9c5a1feb0729d27bb386b38bf7eeb6bf8301fae44a1af0d7f31987ba97618b4c320061b43722f35978c4d8c0f7d13afcaf31a39e2a3b72031770644fa7e67be611ae9e3411a5b146b887b33030b29993ce82340c094a76562c7409bb6e737f6f0cc3c4bd672d4f9a384a0419701e390985bc0f4a924363341f290fedfb2d4a7e384c84f447258fe81d3d13d04480ca149a7d01ce5e8b10cc32834dfeacad0c3273d25a6f0721567b5ebb3b12f30619c4e80285473314a3f22605adc1e4eb0da0f3a6da56d5f42e9c4a47a886f636fc74ff09a049b4e1d273244a9ec3e5723b190ad9f43d82a6164ed612d28875db88e7ea1687cc64176f24a8302d438e2864d3e8d66589e0133bcfedf2fd1ef411516a06b8be7a724af7007f38641d68b852ec20ce2366e0b85a409ada581a962784bc8e205c8140d867a72a71bcf024b9b71f3ea76162b7221f8b9442a1466aac5a216af75fabda936b1f4be0cdcc14e2fe258adc379eb72e2960848c1ff9d457388cd9033d7c5819966c5fdc27a5fb61c6c01c4fbf57d72ec8f32d0128d97f3dc2ceb2ae744f7bb774943f73153014ae9d4ee2217885d7207e9150a6f22536437c52279b564b6ea9b13dd192cfb68a3839620fbf8677a531981b2a8869ea24607f74100242f09d3113712f5a713be85c96373a9be18843116a1dfd7945350568c74a5acbe129a42dc1aed468d708c356c5ffbb7a7f8525378560533e0ba71863841feff5d9787621bf193de2343571047fa6e3315cb5f071dd301b46dd59eb1856f92e20639ea85d7dab5235497beef3cbaa008f6f35e7226594fb4960bbb787319abcf57f3fe0999d9c475763ee977476584a2f9773872244122343e85ead9a862662c9dc16280094b5c65ae7bcfa4c96d8518e6990f22fdafe932e069ad8641c7a13393efe0a9cce1dd8757d9feb0e81ce9496504e5726327e731797158de994e6eabe6d9f78f849db720373b1c6f2472c2ce7af015444cc77fdc4330ab486ff16c319b1faf62419f80a547f4edb2a4ade27e2be8ce6d2bb8fce7d3f36043486c9d43ac52ad87f69d0d821d550494292accfbe252e572232d975aca75d9d4dcdda01660404ce83c734c507c36fe7c0b302563bc838b4a2ba2be0cae4ad5919cc9bc7e0d21ae4093abf3ed102010237d80cf7a9ef1715d160b10ac3e8a72a627ab690904596b3ddd98545f845f5737ee78764135f1431c45cc10c889a6fad3ac0dafab5988648a2c7bf900e0bc90c6a0dd77af4c3fc60d00e4338e3fa815789e4c42df87f7f092d9f22994af225c97a84ad7e6f1620f72e850b8a84501d0816e4b7ea0b24d6f659a49db3aa0dfeccc9c9c5b8e5c116e72790b5b7ff94bb974e8a8f074352e796828ed7dd49c8367a6040e759204e9d672237592be6f6089a65c9cb11ebca632747f72870b1f6fad532b5730a95de81e2f4fa105a5d95798720ec31263b8f4b2b297197bfbcb3864069f05e7a1639f691af3a5c94bfd998f8263f24266b9d86c91466e99a2e19f9ff61c5e1179837ba6728b37c78387617f2f0d5fb1611aa4bad650cbc7415afd25bc9b08fbb676188b72fb36d54bf2df02e4aa389d2bc242dae2fc8c585352971b37a7f6f6987c9aef3717224572f16562bee8ee6c8757d8da8a819c44fc49217a0ce6fe1e5d936061726aa64bb50241b7ac9729d8a05185d6c7541db17973bca681a0c917df6ce01572692366fe875673b03ae54f96d972441dc6bf40206179b9c598973d853ea5f30b4f9b5663e154a6173c83b1125eede4c96da8c3c171d75681ad801ebab2101b72cc6dde5d5cbd66a90a59348e8b1a57c9331e9e93195ca7258f4696bf84b3176b240be92fc8816009f5b3e61c81c0d59191d1416294c054e48185676559aa5272188a2171bd1531b64f1513accc866a5cd770b04e583d04ea96b4bb618651a959396a4f9e48a8023c73fa951c448b76e1100ada3a98d63ce71f9d56d100dfaf7251d789a58abb765dffc629d8f8c252886fba535d813915c726063517d3e87c4979fcba6f2e2a82ec174af2910cba44188bdbf3886c6ccdf87121b376194d8e1ed0ce19397e16efc9772b10dc177a2a6e05bcca2d98eca9842f8f344c218b7428244e8b5dc05d403d58beb937cb71bb0a73edc700391ba91821d59cbf481d09723b3041995b4d3b672ca2b04982fe420c9473c527a08d281436e6b781224642634c1298b6af99a790aada830ed3d2db80cfe318754cc31f1ceda4509e353d6772bcd73ca0083cb90c19052da77a98ee9a0ef980aa38ee33907971e476f6b2d14c624393d6aaab4044f4e0241d5c5aa9428eb7e7ca026db20b399a917eb525541083f9225eb50786433835b668f35a045498f98ba40c48fa8621997a5e07ffbc72f731a70478dd68f72f98b612e2a2942a2f7e27a42a84bd1d680c45b48e4e007283c1909473de6d33b62f861831382c1d59ccbe79e25012165abac273146ed426cba2b879030bbddaec73070d8c0ffed9780564d0ac6cddd5e99af4605517690cd9851eef912c477b8a85509784bd028d73f50612716dc3be5c1c0b03aaaf79726d962107b4e0967c8a5eded4426fa3441bb8ff81276a7d042a99543eb383330fcbccc26adc76ecff8820b70f5ed4c979f1413101e88b53b00269902c89358720fdbd173e4ca088a31f2a51b955e59fc5ceb9fc401260b62261e4bdcb19df3072d5e4ff90e12eddc4e3631d2b57cf5a774c17f5e346ed30b34d258e69642a1b0f3b61693e89dffc2b8584eb3c537ec6e10e376b2c5a8a53ff7ca55717d93d7072707c94b7736f506c69ce99a81f5699596d29aa1b48efc21732fca02b2c1a1f530cac04df11c6f0c25c73d3cb0bd12dc01693bb102ae94ba4ecc87efcdc40a55a0267eee5f00f777f018156b8ebafe499f513f0228d5762529fe3fa17bfb84c0a75de4f9ea352f4d04e3108f0306a193fb9d035f41cffa8acab1a51063bcf457206934e3e038c33c9abef1b3427ad09b09f8b62c7534f61bde789036325071138ac7ae91344e9b7e240946d964a05b7ed88a91e75c25145e868bfd0de72ec8572da5ad15bdac92a45c9a175bbd8e82fccc9be7b3f7ba5d8238ec0fea7493aef7292329cc365b07242e439d63421d4bbde486065a74ea6d3d19471fe61ffb13001468c6a4f0ee74f3eb90ecd18185a0988e059fd6e335c974348ef04a6472a3f4fb4f11d48396d89a275941df0f9ddb2ec9238ba80fea5805ad39cd1e3869a144aeffdb8b7f30791c568f106a997f8b7286419f5097b6deaf0880a1ff5194b6a71687c35a0f07744c321fb8cef6be00c54ad38a71ec447e6dc14c02d671b69f21c2d4382ed85a23fdaeff835a46d5d4e5f67300b1adf33753683546ae7e860655d677326ea89a184e4f2429ec38890ac6d2696406261d6d3268e0f61134a03476d5a183cad0b8b73d8575225b7237ce0d039e288fbc75edbc91f2716dd3d04f072e14cfe4cb7915622a3ac624aff1ddc6fac0446865bb3e27cab38de0ac359bf72d28daf66b62e53fc5be0f01911f46469ac1907d5a45894057c0ae5d95f8813722a3203595f0c2c350dcbdc3986b24df55939f7758a9edec0d785d6edbe896e22d451139d0e0e3156e1c2f6ba925bbb8dca98c344dbf93f6de93c5f085a1c2672a87925e16c3076647303ffa1b1b585684a3320f49587c148fbc75d6e0095a47226ce306b6f52f8b3f490cc2cf79d07bba7eaadea4850a1341035e48e14dc74694817f424cc1c67dc862299bdddf01433d5f6b1ad80998f13c46adb93405f3b72fa1c3a8b80147450b108b1be2d94ca25686bbea7bb5997cde091af117ed2e8124b60fd4bf5ce0064ed6db7cb1211a185e0efa7ca4b2029279d68a1fd77884a434a8f21e1078c25d0cda535bf77c3ed7fe8ee84a7a452c9f3b3f22582b46d59720935ba7188be8180304b8ef6459184989f1ac5774b6c4ab29c4da2bb038582275f91c5aada26a3171141a11b3ae5fa71a08c26fd031bb48dfc1c359f72aee765949301d1fa1160731663a77d8c28b081149cf57f9bd1c12db98a8619351966728bb7223f4febb272829e9ce8f87df15b844f4706dc15915322d39ddc2fd8a572b6edee5765cc9a67c3c0bc056a3e29715409187266f4ecb3429baa7fd5330f729c194b447ae45397790ad5d5f6575b2da1132572d1dee9bfecc8d7fc413277721ca63b5784bfe7344dccd4a837fd44c85463d0b3a877570ad27ece6641d21f72bb9df7e2c6eb072fcf7724d00c3821f0e3cc7ac03bf7e043f18a64380fe1a972001b3ef171abd80f6342f328e62018601eb17f8a067b1ea7736fc7548e15b6722a02a3d2cda8912defa108cb9c89b6763397a2ba082d0b99d0e69ca9c3e3de0c3dfc4262b7c1e3de79e10f643b1b17cd3c5f055ab39107bb84b3f296a842306965b1d1c099f2ea0a2993cc9669638034612746cfa861db684dd81f294cf8865124c6d6a1d7f86d874d5ba1060daa0270ed0fa7e3f56a4de6ee6852437d570372060fd149184b829187904950874d531a731d9c40e36c56b3b86ed90b4e578614b919be3bc943a6b8fa819ce74530327197e9e322cd6d57cf500582b63d4ec4494fb521e15bc9f721f78ef471e30f408118b771e8fa72b357f7c3c915ba5d17729d994b981645e8e432c1c7fe918f7bdc54156a77d82ea8ae35b9f764cc823b72b239d7664b4221525ac29ca4adf2029c99f405482c39028aea4afa9353be28722b61e519fcd5ac8f4ac8aea57f9af3747aac7ba344938bbc3cc24a29e0619d7224aa6fd65c62329036f266650207df325ba49f54981b8bfdb7a39d124cc2d972686f3e70bf2b380c84367638107ee69f9b5021f99c3dc0d85272066ef816ea72c26fdf4cf711af4c2f7262acc955090f191f8a14eeaa8f8ae0bddec4c85cfd725cceeb59092dc3410c2e4be39a7d61f5a90d2caf8bebd39bc69469f83766a06982ab6ba899ebd3369881dba6160aa438eb58f2f82aec8ddaecc386b44fea0e728ed59be4754e083a40bbc8d9cfce7c6a8788409c31c81e10d3de5accc7fa0272bd96e1f93850ed113bd1603d23fc67c95d5925d2e7a1b9517502f792c6609672aa6389fbf3abbca1b1919667f36f4491ab06251c343e4d993910dca086f67e72b45e922b48b84163b5445742b2770a4e5afe77a1b1deec8dfc02f27684af4b1b10d69c0fe2090983adf57a4302dee47818b6d5f0532e12f0fee95d1e802cc572b72205260a578d546362a853c8e088c60528d998b2d0f1559039353f4c6976725b0179f1784b9f13edf0cfdfab1365ef466449ae080b8998a10c22b157ddf772c7689100d21839c2745279f1bdf4545f8c6525a4c0a6925cbf6030885d877472bb605180cfae8669b9d636cd981a95dc8e50cb24df63bdcf36c7bbdcadd2c072511c65a117517f634629bb7c3d1d4e9dc8a74a5e54b2800dffc40e3ea3cf957267e24c0f2a539959e734b4fc3f5e70da57ad03d74b26202c05aba55b69c2960553e2431b4be568382eaefb2142340806352206eb200adeca512bba05273c1772406db48f135f419f01fe901a3c54be0fbcffaf46ff9fce1b88bc95da9aec487208b3cc1dc334328ba47caed31eec448b7563cf4caffd36cd27694818b87f32369c299541f6fa212140497ff62f44a4f0e686804061a8147c53b8d170330e2a7243c30aeb1de3848e310e19f4bde5890ea59de47fbffd0fa8cb917eb6bb0a59080a40229c16d176a159b1a38f5ffc97be9917a074489f7fe60a674821fbbff14b0f573dba5ad8b38f92789fcb4d31ae973a050fca0a491e48b44f6978b7066e0f178b395f78fcd622b38ea48968b1552350e5276ea7e97d2036ffe2c4d349672202582e05ec96d90a1bb253132424c51bb4eaa2219ac40bb82763193b50fa4b72e1a3f6a8aabe005d71eb4ea0655590ca5d54e5f3f9844fd8bb30c1a8f9ef41720f0ef4724181d4553cdbd48c564dacaf126266dec785fb1ca744bf7976636b72c35e46f71bb1ba909a7da44efafa2ce934c6b972ef1242b0a8cbe2f6c7cc8b075abc22848660b24da874a2e85c60793dd30ddf0da2b1d78fc9ecfd962c8b1c72632e49d49b0a78348ac0f8c5d5f6ffa8c1ec70d8b13a9ad0b3ee7abc522482612a321b9ed9027e71787795280604079919a4727bfb4d78778d14c3f0d4fbfd72d4e72552b89e97aedeab468a63bf91c9f38dfd025d921e627e00d64ea600c21ea32b0eeff6f55128afbc772bee8be9ebb6527e547548d76ebf818b7bac81a3556c546175c4fda21ab752ee0a14c3a8900eeee1d959657eaee213d0ca379594726c6b0ade3e050f8286e5a42f7e8cf869c2993ceac1ff920dfc2abba5a279e57245835c185b3da0f12c8840ebe5ea6cb00205c2d3d29783ac17fd85e3a5be9c6aa0e9cadeda04f5128a991e1bbc79beaf266912b31fa7328abae69e0cf4349c3e61b6c8ef66c18419f7cbf488f07ab8c6339eb77bc689b0566fabfb1d9db3fc72b27ed22cc2b20698fece3f60e9e9bb8ba646f2d8e635136d008a313efec23c724d66045044d15740a114ab8f532ae4f39330e1e188130c6d03e12cbbad24ef7220522ebc803be15983c71276d718e905c63b1f7dd8cda3ed3b46ed8bf18edd723ae90b9ceff10f56c68b202460ba413fa4037729780e86d1cc8981c98f9cbf720d60317dda05c4631db196020a7737cec524bd3b776f384e810524edf471e97245ffa7274d6be783fab20e757fc1cc5b0a78cdff1eac8017ce048b0e82c09644e85037d58dcc0893794a4cf18e8b6b3bc3ff619b5469f747af1d9385a7e74b7276f480140e9294dc5e97bb2dbec71a37b409f80cc0664fb4117e12d3e0f68a726e94a49b3fc13aad70f835daaddac7a566abfe3d8a26d47b5e8b616aab6bc172067fa1858787d216e6684b856e65638a0c24e65216315c7f6327922be3f0454954f0a6b552cf4e91ffa7f57e6fc495b607f72dd2ad86c1d1475a087c9e96d172db712e903875fc5cda67f7dcf68f95284283157f34faab07ad938c9f256bf472c14dd32e4f01bc579aabd2f08120655569e36cab0883f52d13065685a2b19672308c6e8b488b1c9ebebabcc24da00c779912aeb43e966113501b730c9e8dc75c8d915d75c98b5ce573b27995caa5bfc4a718ce72565816fa0e561288fb06e972fad859d2ec7bc2bcad12783f74000f5ee470a2f5414ac7dd9fad1b2a767bee72d7a485c200abe5f83067e034ab246b0976fe4dbc239850e2118df3a209fc086f94fa6d166a29a16ee324e1e25dd61f88341c5e43f3068c101f2c15e3e95988724e91f88d35ab03ffaa3732b745c9c3d52f6421ce0b3abe2e03b34171f08205726130f6d0a795d3ff2432a20f32b10a8e72fc20d43ca17c67151e1e3b68814c72086f80733275ee8e8fa99dc38d564fe651cfee4c420c4d1fe45fe99f3d17e01980ec2661b8a7f238f37593a6860c35c84a47020fa698d279e11a5e94f51b0d721b40b935dcda92acbd8c546570c0bde0ab2928d6266144c3a40b071b73ad9918e7dfada62e95c8663602e16831448886cb6409f481988287112a0c8ea765c72ddb71cc73be64109744fd06cea82391e436133b32d04558d73727d14143e95a726264537d6a1b4be54f4a94ea514182da515ca14f4bdc3bc0e832b625f61f78725a12ebc820db2ecbc38dda414b63927b9af0371dfa96c7791e264176f18c3f30d1bf566f46cb6095c8526f4a9b63d2442c4f2cf1b223d0aa7fe1b9d078057372c47e11942cf0a1281d8bedebb2ad74bcb72b695273618e00ef5b15ef7075c932e86451d0eeb820531ed58e53142582d36615ebaf8aa109bd1f73779e9fae71723a4ce44cdb1a739ab35c43939e8fee04faa77ab48afcd5d04d289886fd9a7a7274bac043df801a3389ee614c4014bf87bc2b3e5ddb6cd6e21bf9804bbae15431046cd92ad0907277814b0f17c29dcaba2534e3fa5a08c09a38a046d8e4ff4472398a99536175c58c5ef6aa8abb92a641054679ab5904f1e74b04e5c539349c72293d512a23622b027f823d06dd22a797bda4144c741827069f093dcb2d5ef272b84f9a12a6763899a30c2abe23daeed5680513bf350833d211cf74a86658293e27ab5bd078695e7ae2834fc64eed95007473c1e324cdcd533e655d61dc474e4bfa821875a98944806a009672bcee1dc4a9bb998f3eedb25aa32ccc8fb1523f729676b13de12139356b50d6f12e151306498cf3afbbf506638b397849ccdd15723eccd291b3f803aec29c06f121caf6c4b69f531ef8c5330298b92f6d70eeff0b65929233939b8e0550947d4baec8d508285f50e884418527c6712451e343d6663e4b09bf540002dbe02f1fca3f89480f44108e5b073098da8f4ae83510d0c61d5f5499ecc78f063358c63a2e9fb2daa4921538c254b687b14e81a03e64dc79580cb5ce239fcd7a0ad5bea49bc8a2d98aef94fb3f400c1c37ba9d0ba66b641672fbe1a26255e714756ea71b16c80c82b9fd2965795e83fd56273c2e1887670519b3af0ccd15e3116545c422f0940cc1e1283fb34416693eac9d1ec46b10f11472037dddcf23f89c3cec080fe14b435e3226b04a0d7d707bd2540237dd3abae37243755ea8cbbcd5b9ca4f1bc6c3b87711e0f7c80b4f7ef37900e51db9f9bf94277c30c28064d1e1bad05dfd120cbe4a016302a1286776e85994f9bb75c5532246d704c00b41cf701e320d45fa0fd2f6276d3e0f91b4269fd4598b4c20928d8672347aa4e7df38467cf2723765e94d6b2a9b3808e5e535b5635f4f2f58cee2ed722d90bea89001200329cde2e4bc1ae20edc659e0dd1acb3fc5eb30bbb346418726d76fb06a7599242737daa730aa5b853cafcdacdb51ad8eef18acdc26e0cae724663e52572690ae7535b9a3171f5002d7ff4e2373a413526b222ad993b33a66939a7d27de4ea434674de0b2dc9c036f57bc9774d4da6f55df7feb3126ed88d1efdc8098854746425043e0532c225ee3580cd75c0d0bfb9215d3dea13de447f415f69b57c6273c1e956fcc642482c7a6a9554cd6050cc6c70a2fe0493d9fd4e7270816e40af8eea3d2164a908e697e44aab71d4444fcba5495b891bbcd63f684042b7d955bb8c678d41ff3dce96c70f73ff93f998eb3ceaac9e67f01593da8b724a2a1cc5e87e1cfe31b0c5949558b422313485946ee505ed4a00ef19fa7c5872126b67a44a77d5d68cb755e27d4ae99a4495f23794f9502ece8a7b07d3e8277268b58e7587e1dbf0a9a098eb22b8d8c3f968056aa5d08bf64e74d59d90b59b72e30ef0628e376be51777473e720c8479de8c7a883ae650ffc3878cec293dba72a39535d8e7012e34117e2cfd2bc61db120166b52e9cc4bd4b9816c121e2aec03929093cf2fa204deb60c42ba5627d6a5d5ab8c7631d3816b0f9c4c108c3b9e728c3e9e40671b500d3c2c2e108a781f16b87ef50359bae4736bf3be40ef1579725587d7f61a6e1b3ed3aa06bfa94e61d96932f43a5b9177956995368cc8f9cc72dc80e33e9f0cbc676194d2d86ae83098aef92386fb5a9270195eeb33720a7a0f5c6963ae1efc8d39d7bb9a132dd7de1a49c9722859573f10cace351e81d379628b1647cb195c067208b0586d96777330000170112d043559ecbddd2056456641303a66c383afbfd044524a4dc94a827814c72e406a00f5b8d7fec1c7deb64272d9ac0a2a921db770076616a50e633d6f8ea54ee26fb15ea114dd37607d252c72b7edeeb32ecc899f84a66b94bc5f95b25b4390a237f1abd6da1c08ab1f08d038a20aaa627ee4de44103760100d7da3361de87869f721a4f64a32d753d07c8c724da4e9cf7c476f550b12b9deb2e545aa1394afcd6a3d5aa6d4d0ff2cc950972c35ec0ec55b5b9be1a3ecb1f1100392426d89123036283205de0c94120f048d68b9c537fe4b7ce6d9f7d56b89f94582fce6a3e219666e94b48663965f95140f14bd72011d2c4851a3ba7778f44f4cb8f50c80b4544b877629bd0f0f9cedd49a720069814a718fca658fccf2665578cd47bbadd498bb47282eb928f86b48adef3d640696317f992e3c42fe1944d7258585c4947296db662409f1c904c82e930032f6bbf41fb79c1fd8871a7d4d69c541f6969cd329e57a446d22ba238121791f72ab42dfcd8e90dd2116a4cd47de7208d8a0ec35dce9a2b59bf2da0ef46b4f370df1d66bc46404c8fd125e3b3203a2620623cf707eda1956f4f8130823e6041172597e2287743c73e969662cdd1caa06e9a94ec591ea7aebe95182cbada4c32a1a15229eb6cd768e63460052b23bc8d1c85412d2c19d1a9d3479b1653884578464256871f5406ad3110936177e5b5a2827010a37ea6bafe37d5e65a78c67f74e427474a7d244180160505593bc3230d22d4a24538b7ca4611e35f26a3ef1a39e72dd2d432cbc385ffff709aaece082ba3d466d90b2b330a3c80e07aeffba889f119b962a201fdc170c5ee87772c62882aea344f45937f4409ede82df5e6c953a31b7adc94a86870f0756886d684ce3ce888dcdac746dd3a02efa436243fe00a8728bf1c30cc62774659cace7f90ec3c5035a0f8ea9d61ae3f24ca5f18d04897d45dd367e3945771eae5891eb5c3407a7b86e0b27db66b7af1a9e88dbc8503d57728d97ae2f59f265f4ff208e16a711d9c3cbf4a787d5c4e5daa1cefdb93014aa727f8793538ff6ffb6de6bf8771132e8bac80438bfe0b5acc1920a8d5d5fdbd1720e9870081642795d9c6d64b5cdb2ce40182f037e41b438b3108091603a1a46725119cac46317d264c7c29a834622bc72ad28a753db49cc80d82ab5b8f29124720e7a01cd60c9f497571c707dadda4cbabb54f4725de6002d00340950346a845af055430a292417ffaa8ec48462bc61175f555d581130b7d61c1acc7ec9079f57c029b94c7aacb17c99ca7c96f6831202deb897ac1bd844f19f347d1871482d0ea573a4770313d0fbf68de5d936d5ae2d870f44a2aae945238fb2b70da0d04b70c2930b2478f8ce6905188212a2f205ccf2ca3c14209ea80ac055d7e452c838728bb76a9d8f622e296b88dc9cfe7bede2450e4b1e99ccf5dee7cb517382eae67230345488fe0e48efb4ff642ec2785adaacb8aca43b9d86b74e217ac5e01cd70121591a1056291d2a3ca426f502aff0171a9da5f458e600754680fd717b7302071abf87c89051afa171b4d54f05320d111304d3229aeae67e9348cfb5c73a34489df4151c0e432615b367086c438614d1148d4a895280e3c791d524e39efec25afd51e56a9cf2aa92c8cec9c36c11f3aa2b675e38b19f3f9a0969c0fd09a5fb477aee2befe2d1dc5055eb9d20efbfdfafd0e462c5cc99135125a1757aa877c872e2ce9146537e0648e234bf1965787d5fea336589e150fe79aa590a87cab6b52d6dccb101016fbe87249802a05fd30fbc6b7e36a64833610d49666710b8b79927620925a31e702777bad1a9dde0aaa593f0ea81cc568ff0932eb99ae67d0de572ab18bd953507f3d82fe7ce57d1c667e5252a46a0256270d6a2995e80103d02721d12d266f23bd6e75179ccc681c0abbf73bf4e989d98f7ddf2216ef695016549800acfd0d5085118113e231fd302ec34029479c15994078d052d1532de7fc070d97cc4b7540a06ea318bae93f3f0b8a0ce3f7ca52939a484ac3379ddbbafbe051757be13a62d63e85c755a2c73927c695fa6b18bc4b30f799b2b81dc2e9ae872495849f863fa51147692c2e612d154e801015853249dd71eb7f3b2205143e6629f9e8535b4432e6113970cdcc38b17180ba52bc3e22051e5d549fd4ff3429d7215448281e6ddc7743bad9649eb5380872c571f7f442f3e46c8bb8c400498a072313d57b685e8e395f163d23608ff8df711d24516b3bbcc01840de1838d9cac729db4806f610f99f76a8ad10702f5fd0d38a8be34fbfd60fc4e22516b7a6a91716d60c5c1393acac31564cb30ddfc0a75d1a17719dd19986c87104315cef37c60ba5fe21eff1d4fbfb3ebac48357786ad65727cb1eb4e95efebc84d90a7b3d71bb4746f4064a92f67ad666c50ac220dc48f72c32bfbcc2a626caf30853bf2bd26d9b5d08584b22bfcf15123acb6fa34e51601006a9d7ceed544527db05eb9b972fc3558652bd18ff910798b14ee26d614360d39a54d7cdca868b793bb0f27427271db77ffc800432b0b16aefec3176ec869ff8f4253e4ced7f59d1108d5a408726018c0ca61dd6e183e26a402ce80150b9a0b386a043bc305aa0aee590ca95d72f451b97a39344baa8ef6a22080c2b1cb374daeb063496814a5b9e2ac9b4a975ef4f775fef8941526dcd39ccb58802cd1fd12a73def3f9c4ce5152aa0f0a6997231e8a21fbfbecaaa5cf9654e0366d32269125afce1cc50abc32ff52479b3027257ad3abc4a3856abf6a79208db5ce6500bc1a7781e0319dbd6e9341b54f2d6413230018bfacbd1ce7d8b5552afa5d44a1553d953a351b835dfeb5a7147cdbe72b6b65088890970285f0469e294018241eb549f078bc0ea594941460c807d0072cb9cd2dec8df1f27ed9f83c463e85c5111ca7740c60838212e2ccb8dd57bc47242b0af70f9e0234aec5d0563f839f0d5b15a1d5bfbca70f4ba988049a6712e7272aad3ddd5d981271c53faf04e7b0e7470ab82876f3ee0b3a74c2d706ca4885814ab64abb4bcf8105a00dd303aae0abdc8ccb335b7a2bb4fdf9237a2d6fc9c7205c2ed652493e8e5e60751baa60f3279988932caf7d99859267fa6ae7e18a52360a7a9e1a97fb534047ea59a429f0025baec4721648f2e6295fa6709c3e31672ab7c825d67f84f9b5d7a3d71951514a50c26eac1bb6fc5be7aa42b27b117e72fde56141b24985d477f0f7e04eb15c72312d3ce1e75c72f80170b122ce36fee3df75b2ccb34b727f3f67bb41e1b561a5e7f3e0ff57a689d0f23102426a6bc2a518f6e698a407957ce3a2656974d8e2cb8014131daa378e7e9d4f11769f1dcb75bd771e730dcc29a5caec641a79cd9228eac9d78085d4b50850afa41bc84a1cc1ad573d42b8f273bad04113f72b14a187572741e1fca18d39247923c0791e11d5643774f08fd1306e1a213dbbc0034f4f28e6eb8a68e588f9e51e39b9ca9cbfe72ec6390c27056d21502e41b50605435b5e0942aed422cdc5433cfcc5fff70f6540d684de0b5f084a07d714cd9436eefd98289154d908993eebf9a48d20806df72fa51cbb0a427410a965103d80a685da72c76a8435ac61859a5f43434963f077240f826e4626a49c582f373ba8fd2c88e976bb87ce74c447aea3d72519252be6d9d36be48bffbf892b5e405d30f96684441548c329204723e2042595ac2b50c724a2f06024585f8989fb48f8d39e224f06a03cf3cc3f308f7280acebfee585b7242c5a23a0d5eec74dff63a5d4897e01e4b80f193aac3b25b0f2ab1ee3dc6a1721b25983b6448cacce22cb0a1146c87371ecaef67f1568f9c7323c33f5cd9ae72a858ce6a47114e05200d7c00b69d86dfb23b2b36a56d6cae0786a239becb8c7210ff9fe823d3cde8c9db9d10fddd61dcba075ce8eac6fda615ad69e600e57b345b51c1f199922c92c251ab402fbb45d9722b804cc8f582540888b46370850d72db92f72892942cd78473da9bd1b2893e840841b00b82591b431af8a38b57415e93d9db60800745f020664525f66ab38fc1ed875510eb089efda562a1862d4e7255ab2531263712a064132be98edaca298cf5dcd9d93b44e2f854511f99541472498f73117a6bf4e3f731d4aaed9f1af61b6bcafa68c2cf9c75a7a9931d306f72e853b6519551e5ef31a4436fc78b14389dac68d7c64cd2f1a9e3b18c3beb8a3c4aeb132a54f3d0fe006cef73968af4eb3de1bbf462d2f899a155672b443dd3723a48ffac70e9d163ec0f8f3d0749fe355748da8ccba02905b2eab40f19a6a1686c6d6b1cd96afe344714fb0c63348615799c4d0b0638ad72c26cfdff78f799723402e602cfebae1723783827080856c9783004afafddf195eb0ee6c8b83244728cd913513c227768a5a6ad2bb30b676cb09a0ae7517529637b7efd5fd458be3f8bd443481454bfe2e2739df983aa7b7a6f541c268dd1a0ec07a3e29e163b857241d25ba21e814613ffc919c03bf85849ce877cc36c52d008e9586a3449bc0072b78f10addcfd51f1913742ad6049376b3118691a97d0351bd98026e173cea56840e4860d1b49689a12779204e5b139f31a25a72eb4939f5a0e17bea986444a2d09d2a34c6119fda71201862e6b3c0af02b737766c9739c00a912f1a48944bc72ff386e2f51d677dcee0ad60df42b4fc1ded9b3ad1d4a34b30f61e1c0636c9846718e7cf08aa13a379ca419314cee473e6305dfc6408d1b6ff089258f7a7b05722cb69afdbb4aa1dacac97bbf38213a86393ee80a32d8914a5534a9d697d1bc7233de72727738e8c314450ef87b98f142204ab25a1910d28ee07ea3206c878c4f17b2006a579526cfa6e8eb18e349f9cde7964aeb4eb3eede2d1b4005b49b0f4247ec38c079752b268c52172a931bd01ebea7426f05fd466fab3a32c1c535f372b5930f8b6c183ba913e10650a881bf05725fc52a25cd5030478e40ca5ae6dc49376233d708ccb354b92526bb59d545bacca7d7a2bb6aa520c59d65892d15ad7208e9ed6ee8564e9a807f1e91cfaccd8706038e11152f45bed232680f728abc441824f33c7e876bbfe9578a12159643d6c8975cc9f987e305665c1757acc30272fbe75383c4c7764baa19d7bdf460e523ec12c87b8de95ce5fa57da58d89b6c5778604899d33c25d103563315fe7c0b30981231d226eb7e7a2465a5bdf24e7372ad7aa640c6dcf29fb8385a3d9589d22dad9d63a17b5cb51365a27e2fd21f6472cf3160ee742ccb7247e78b6b1c2628a51861cc8b034d06e6cccb40001f80e3729aaf47f2982f3fc8d9ceb8960d6af000f58a294b3c7371adc30f934fb011a7729bc184039399cd6b26367fd38bb38349267e4b22d9ffa806281e203c1872d4722644d81981772da6ee24e8733053f9e038c4ac25a390a9312fe35a3859e33d3f7a04477d4db59b837c15f02367633feec3d55209eecb053c26d1b2d136b4214b96c7db9dfb956b909eb0b6eae34edf53cbac07e634aa55776d99352d49b4a625456022b134e72cb6bf7d5e198a2d0ac6127fbe8f02500ec7bcc3d3ca67864d27ab7ec902a955a126fcb2189cd6bd5b7e0451accb2f948728f972e70a4e400c72bb44cd6a3402a06dcb851ecdff54843f144437724fbc6d2ad3f2547a18c2e272f67f9d2d91080d0212dd06a886968eaff8a456e6067c68c5dc7f261d24905f1fe52b38a6cfed7753acf0f432cb536e22e8aefb2ea5943abd589af8e5a71511729aa894b599567b75a30549ccea07d2410db14a0abc5d11fd229ec93664791172c497e257d9baf47ab12f365773061c406480075b8818afad40d4f01cdb9656723c23fd9bb69858041d1b42fd2a91d9703caaf25d5842cf8ecd692d382644e072c1082bb65359fe3f838f5aca19f5c4cdebfe404550756ab7916aa6588ec46504276c5a6cf8517fe6332c156a6cee4f3a749b1ce6451c0e88967911c3548c356947836bc79f83b94fbf07ad073e9eabc72c2ec3261b4af0f0f30198f829f9ac7226f465d6d76f80e59dc783c6ed778678a63a2b3699a154e7e55946c9795567098c9cbbc2ed72329262b4125d60ebbf480c5c5fbb496c6e71b2b385dd732b1a72403a444fdaf1104c5c63f0be3e73a02f0b3cad9fd5884cad291a1424e8ea9c5f472c3d972772b0edccc6966482f1a0162ba3e6226ef6fa57ea5c363d3fa42f727cfd9519798a5e417c2ca889d79ca637315cd317872012586c36a9e8836b57718f46f9d26e48537d0b36a1600e800443ae621c9036d7714a4fe7491e2406a41d5eae48849a11ddb8f595b50cf800b73d969a141797238b424725b1ff194c3b726e5d98548b58268a4a2c69ea1ee5ff2e07ac0562dbd3244cfe3a51f3932fdf38d151167caad7ffa1c1f9533d90ce10b3c3dde3479270b8fc3e7fe11e438452721cd34ff678fa054bd0a369bc938d392a30ae5573a6e4ae692d2d7162b46e5b4c769e62cde5f39856c417239ac14e7badb284e0fa320c820054c5324e3ce31c724df574da7703d7fade04dd6e8516624a327771b3e121fe52173986f0a476a172b254522e33595e9d1829db308fdbe29d37b0a5ca8f302b8b8adea01c2172b772303933d51823b395940f52ec516e4688aa6a73d87fddb01f559f8f811e14133292fd57e9ad0fa8a7a3edd7620751141c693326107cc48672a01c8fd10f9c8772edbe714e42d1b13cf2ba59af850e15a985264a0cc7a2d08f8ecb3ab62117e872f581b16e6959e213350c6e70f791842ae0370f0c84e3071d84a70e9fb2684e725a5d495385990ce0a00b6eb55b501c6bece98d301dd4a74d34a4368d412887720750176b9464e35b1d5ad67bf06ee6e2f26e37a6b9de1f04033cee9ffbca727274fbea08d1f48f255d929c3ee102b0bee5a36f04154269b468c4a5533744f2142c3183bcc74e7b877a740fd4d9adc8101049cb92fd7fd0a5a79b8fa0560fb5727e6761fb9f0104cdf6d9c1ffe27446f693650b36799011cec020a28526e526725a218b9e8f756b5e29ef2a72c990a748aacf30a31ed0629eb15f6695d36f64729667d67de52751c9a5f29ea674a56521c1272fcb645d1a18c896054d5cff5e727e17e8dd186ed257d480f4baf5b1ae397c238eecde676a28d410e0f3f495af727328f1d150c0c7bd92a76a6cbd84610609835f20f3299d9b2a9611e35cde317242d7905699e735001fc1f48a9dba945dc5105a792c91912742aa7190efb3db6798e5826f37dbf82fd61027a3efb179a943ab0fce869a71572de0abba513ac95517eb495adb7bcc0145633ea578b8f4c2b5c22dbfc27fea1803826a13bf677a729b9e5ee5eee3db7ecd685793eebf548c72e12871ee39477b30dc15d189ae3a72faa9d4e17ba1329d8d65f2444740e75329c9dc973b60005b1b9a918396a98d726e6260e62d0f04b46c4420d9bbfff0b5a31fbe236f0be80ea965fe6a8709df5d4367d104601476682993c73bc69ae54758513159de188571a9f104b3ef0c472c198e18a37cd2137ffa00492b73570eff78e21c933e7df2820f2e3b613fbf76219d91caa482e0ae6a685f8439f102e785b3102afb16bac587cdb45c28957ef60c4353a690786fec3af8be6409bae38aabafcccfdf2a3f777db32029fcabca2b3c09ae0a72463676c45d4a8c3e7fa1a06c1a31b52301897b6adf83df145188371a38113b0e3c6633102f9d384be2f1f3c1f1c73e16c060daddc1ef71aece7fae21629039215e1abd7c22f0b5db7ca7f96fcc99823dfcf07f7fcef41d1b582f39633386184372cfc6fcf61446bc3610465d2f77e8a9cea1c126b9d8ecb7065e28722ddb0dcc3621a6ea2ae4145efcb74e13c2a082b464a10d9513fc7a27040c0e7284a3eb01c65d77f50ccbe40b74679087c0e324675a44ff482035ef6298ba21329da2f529cb734eb6d002ec564012cb27ca2683b57ec479547e7c181589f5c670d702e6bb12b9a12ae229200b0eb3b980df9aea450720c90891e4185b25f22872dc4879c695365c39b9a7adb900ee0364d7ce0f5cfdf5c8fab9dd4f0d0e33197291535f1b9004f634eb2d79b8534c633961cd77aeba99ffc6e7f85461894c4c4efddd461a5fd4b17b57a0c16de6ad79cbd037f863cb2ae728ba0678ef3d4486139e0011b16b6fdb22988c3b34aa65ac8dd61c28c340a7efbb489d4cadf94339723ce50641c278dcc3c8f2062ddf74504a1a556c9c75eabe291d99c5f069db8f06f139272ae30c18d32de73d5ef694b77e696863a5406edbfed84d8dda8fa7171df3fdbe3d3f74a4f905a8dfe5cc11cfaeffe7c8c1ffc2cc526bb5618fb7c03872d554ebb5c94600b0339628a800698d83ebd90690cf0729c9cc05fecaaa216a72b90396fa252e4d6c3cb4e3755c302b5dc5f5592200afa3164a3caad91cc96d1ff05c46652697ba8c5321607856816de709c397abf423e8156fc97471c57fce7255b73c83e227f170f444be6a2ff38f87d8e0ca9b6aca7c8d8f605b1af7622172de2983b82b845f51174e633699d30997b6f7925f5904d2dc4630633b88606c729df2c436d702d307b9f1e0f42b9b40e90d7248a2a0040ff207e1eb05f19c427244e4115710a632d8178aacbd8eaa1c1d35b6285137e8193b888e5f452ed93272fc4c6c1692aef0eaddf8abb23cde790b6a7f7c4c9a62f58b623f0c5b96685972d00cc9ed3fa3c177a3c2b3a077dec8e215d5cc9087592954311ce5b51b4c6e72fcbf2d627e126adf9f432c77b7f16fad6061656b86a0ef43c057e8f62305f0720bea0f72a4626842d2a5263901ab054d68e4c1a6e80679655c1831e65ebaa972547223f9cb08e208985775bfd28ac0abbb79e6f0428c673fda712ff71b260d7257eb81e1a3bf33b2db8a7594313f3187f004d939455ba33ef5336ae84a49e26579af907f946cbfcb15ae9a66ab6b409889934dd6b20ab02c60af96945a950d17fcdf465b5033a1df6bffca71c13ec21695c6cc6f1240e39c6ce0a6819890b6723759a3b589a7d2e4e1eaddeb154c06e5cc6280bc8f5e56ee966471ff4b603172f489850b4d37c1b08d50c32b5aca0139dc66a8983f77215baafe54fca4548e726ac0f3bf0bf3551727b9bfef11f767e9112f01aa317a014cc3c50014d2d9fe720df512b1686ecbe1a53a76e560b9e784f046064129836f59c368a8a4772d2271adeefc665ef1774a013089667ed101e934a286f91b2261752584eec22f5d09466a832b637a7f3189fea190a4cbc93f796661371808dcae0d02ea436ddee7fd720b2c5908a65bd797d38992b6fea7dd5da8f89e8730cbfd34c34610ca626ff2723b27001fd8342442aab380cc8f4cfac7c96ec29b3431d66708c53bd46e0ef67202b3b8a557e124e7938405e5c4b2bd15b198f5346e00d7c3b26de3624055944559e06b0943f6a196d8d69b76bc4b19bd680c67db1f79f677fedcec01be1d56438b7a9527dfb8fbca10b94de4df2b71fffd9acb1ea02141a3bd1fa7eb1473b6722f9f8a5809cd92985a7464d65ccd009c5a38a48665dc0b7786b7f41e45105f15237ce4f49cdb9093c8dff0089e23dac7edc7926c8832ce53cc71c6988b2f7c271c77aa254a0309189d3eb561ecbfc3479c08d82ea6dc7d480e7478669865fa72b2f3ac71f7b1868c5d7e00a4a78e73e7d73dc144505eec2d47b4b5c19f2232723b275569d2e84f0f9770b519da68c906639b98e1d632e3acea29dfc210bc1c7207e12881a9f258833ad4f36bc5b83613ce59a1ec924d54fe1161d27ff415641a59bcbd24550fbaa50800891e2447126e5ed52cd6d118123b6c76aa09ad7ae43afae15d075afa81c090647507d29bc7309850dba73c1141502c4c9b3ad8b5b27283defaf1697053934d2fbbcad1259769cf67d72d92cb42bb74a24cd5508eb27253c08cf6c27bfd5cd594585236dc1e86dc041c300bb98b1e7f3d5ec9ce4a96669ab8b19359254462128663bd99e31a964478a77877499c9bbd45e822e588cc72a54036d7ead3fe4749cd1293aa5e14882884015735db2334c4508ce361ee8372bd2f6c5452b6dbe00ce675ddd65b0974017d1baa71885b83f86edc7606119d007aa491d197b665422ac63fe8a6a38161e38d9a839928b7587de8a5522889dc00d42aabe2108031fb6a7b6e1a8b8398e11b7b90bad1c5ba9f9388b4f342fea072808672bc26e6b2fb45417f1cda32fc938bfcddd3602cd9d41662b9a1679f921815cffbdd3790bc9ebc72c11809d7edb71d804fdbdc637014e0c5dc987c419372ce95e2ad0059d0fad20bb5a62427459efd4cff96a7be7fbc8073607f671e304bc7bd3de216ce898563ccf18a3bc6037ff44e7d8ae0f753547cd71181a054fa4ce3f4710ce29af12250424b301f6a7fb4e743e0aa542ec866c2a4f9408dbf6b2b154bdab36a0b4c31c44e3b5643fd7d02e5308dbd6f8edf6c974a364be6d256727c918dc847d421c170d42480933a3f9bc628bce21a5fb4f219f1e233984b387299566661aa87d7bb6c323496232c283e19a15c5c3ee83e3f0dd2c2e7f05dec72275009052a98992245916f7c9d43bc92e48eb0ecfe45a1bc4fa464e52b023572455207b5cbc3e831f97cee0aca7ce2f4d04f22b1a1b77552962aae22f712e072fbe60d6e3dc00900db31c8e8d6155bff8925040ea2a4ae81d59db65772ceda72922ea2463c304747e125062f28c8b9dc5469b9e3f7f6d13f4d7435ce9ee94d537ba74b6502f52a6b570adbeebdeee43c0f25e73086beb1e0cc00c930a15feb39964758d32ead7971ef5e012ae13be224587ef6c476aa91d7fe86100c078b17724afed9f82df23faed3cc89bc08aac537dc8de4299d45457359175ccfbd51d229b6693b81037822d91c18b8499fdc2006ed2b17c0058918605ae39eb2ef69e172f6db80945d480b759a339959b352fea1210516d55a2b3f0459da2fdc41149672d38335eba259bc5a7f0470f81694ca6187284490864d31a1ad6471351406327287ed150abff3b828bb26b6f152d2051e7d93de2eb1a8b71425803ec56bb5cc5926b739da307bcf7a770d90fc62d2aa7e3b618e4fde17785fd73c4cb5aeead611fb0c93a3b7603e779153ce1172e5bfbd2ac4ebafd0149b5f09ca921a45f9f972e2212da09d9b38b496b83f2c4a1263f049cfe51ff38daa8f2c73c0ceae64e0726121bb5dda8228de72e07447210ef49a1c79a4e1a5ae5c971a5058881305b9727e75286eb44d0324f29cd9d60ff6584256a960a8fe4b5641042c32cfda55fa727b8678311a8d19e23d130cf6de01357200e17aa6946a8089983dfe0313d35c7285ad0da8738a44ac8302e224f03da36cb7ef2bf0edd323d2a4a60a9ca49fcc72c7fdabc1ad7a32dba4f09ed0e7be9f932a5a48d891e3354d3f99017b9b8b9a72806a76a65e35e848325aef5381e1133ac08bebbc986dae57b849b63db722d47272423ae219db0c30153526c8a634b118e1b50c326b12134eddd8b1d03008c752c7a932a58a36ca2a04c79fc20d6d196aa13f5431da68660160d59b3b23720b5ecdf5b37541a6b7a32ba19468cc942ff1c79de8c4e2053ab122dfe7237492d0721d48af62c38cbfc2937d541a42651a41d38af21d4db9b3ee77761fbd6816ae722d9670d5a0f75888ac97c9aff40abe82f8fd4d5c8b5982f2ed52b4c9a6aebd2bd8a3cf0be0702934d9c855765d80a3f25ce96e027746a5c5270e3ba1816d1d7283c751def104471172b54ef298bf8b8913a6f6e019cce05b09cc3f3549f9e941a797feeb1228f0f9733dc6cb5a85d5d550fbd484c325d5aafc1b85460afcec3bd97df2731e9ac55a74f931c33541de4561b9c5d7576ccf0ed635601c3442eb33429bcc7796da793328e1f3352899db16dfe5cdc8d737bc7c87291b17a9a76d72e7954a40c16a5e01f95c02fe86d50b2eb8642b286aaec4704abb21afc4c2d2723d944f3ab28db80f803332c0b0dd83eaa86b1468649b51208a8b321cbccb8e72e0e09618f3386e592c000d8a2cdc5de15d2b153650ee980c32951052ef5422630e6d31e3c8c89db03de89d80d5fbe3428e17ee1bc8cc870aa00b183732050272310fecec2dcafa77035b77ef19a15b925f4fed59badc5df392949a02e83ad97264dda9196b888096e1af43369f46923541d386824c11e8225df89252cbde1f6f936c0f6ce47aa9805c0563944df33cfb8ee0bdb2081ee56f107367e2c44f7132c6fe84868a500b96beb52b740c7255c5cb42ff815346b1050f66fe7a70c0cb72c2e9e38a2b44e0e52af13da90a67732abc6d88c8d55443d83d877a439cd83f62a02b6165a9d100e4c794b755d33c39e4d540f79a7134191a600b2d9f15e1bd72eb0121eed8a7a89c550f2f5524609d4c98f7c871ed3b79d3d19288ba0332e13ec33edad6b8996699e0fc71ac96353857c6ed60d65caa0a163f5adb59e23464192b9a6757b6b05f6a8b6d99ee8a361cf32a1662915934c747a3b109994e0c96319f118c235bac0fbd7b711694ad4ada7229f3c332e1c16ee7cbd40f8cf2e5806bd8c5da5afbfe2cce99e702ea27499208bf34984b62ef9275b8d5f580f36b3625942858bfa015c884e134b33f1f8b7a988ca8e2dc74d730e601f542e706ae353d821b5553d3918e86a9f64df65dcf29c00c87128b9b2049da6b4ea5ec48e33c55c7afb79d7958717197f8a41cb783921ed8a0f87f24ecb2dfcb6a887ce4c2637288879205482f86a16582acc00bf29b7fda07ff121f1379a0ddd38d5656e5621965ec50cded8f28cd5fcc530c2abd0b45e65a0f9022223db75d2623dbd69a9e11b10bfb7d7b9fb12afa9bdf82ae8a818237b02ebf43c466a307a77afa2b597d2b1e3856bfd9a56e5302eb9faa4ffbdd937342eabcae02aade5182a9929bfb544ce92f054e2b8289aaa712264a383fb87935effb73ca288d11514cd59265cc4d720c115d3db7bcdbd2944ccceff890281e07d858e87d9c8d09899c98ed2a240d37638d851594c7e14f53487e362a8873f41907adc414ef7cf0d4d7a1559194177268452b304bec4779a3ead63352d6e5a5ba7dc1fce3cd327ad718c466d56b7272422f609d17957f0c1c82494b573b1c4ff823ecc7ee1476f2b5e2da822804357207db8eab13aabbfc282dc85697980ad6340fc6c2f08654099a5cbc907fb250723994c6126334721177412c7f2d96a5c7cd9caa44bd63b1464b2ec1c113ea13509240af5bd9efd8a95a1521b6902dadf19ef7009207d5d6722e3dd4ad14d212722c1e6288dcff9b7bb9fe9311f3a00564a42c54ba5dedbff164847add2cbc46727ba1fe1ae4c21c9616e0ff309d32f8782f451c364bf1da1d7a214a7eec3e9272cc443aff729a94654085203a0d60a009d8b1c06ba3681681a750941d63e52872f4f9f8a3ec0099d2023b40c6187628d281bdf43a7af87624d5b7a73374ac1a7274af23ac7b96300b118d6b4692924f673071aa41ae72161fe9ed43e718a307726563b5985209f63c3e5f96612a22380c577d28960126eb8aec2e2dbaaa8d5a47ca36e7a99c2bfcdbd7f41f4564ad433276ad6125b9a74c16cbd73964a60dae72d99bb06d8a1ebaf1fe363bb673edb8f37c3337c165f6b4c6c070f23263fecf5e66b0d44d8d7ac9996d90dcb08cee81b6c0ab5822421991686c6d0b9b4c23c87225a3c53a616e0dac88fca1cca5f97cbc6b2814ef820444c25118ae1a6c256772f1c61fdb4afd39f689485ef48dc2f67e7cc99d5ca3facf569bc7cade53758e729d5a672e78d473d20cacb578154862ffa3005615cd605f684000420e38af5b72fa6fdfe9afa9b952f4915f69b06461f2d9e52c84387e65e6d07feac4b9c7e7729c8c261f17296f0003837705e22e5733192471b98a1292ae55e37d3da93c4d0aeb8be3558d3d56945fe23dd3138075f56f4b4e24e5bf37287f8d67a77e62c25f16d116d09956572186396726188ddca91ec37623ddbd2c8d5a8a14391c91504a9c14f5db4ae1f4ab87079444ec01ffc62cf7db1e7edad9bc89d0e7141b3a9407d061b2967d187b4cba243ca58d25f61946a438e8696acdcc009f630b5ef11630a14d792a7a5712ac930b095ea34103c388db184ad180273ef75f9bf28b2f2c72d31dc49da74c83165a206e86fed028ccf6086232f71e2b56d6dd2240944f6c7252e0aeac32311075a7b5997fcb867706028859906790033917664e95f2757d721921bc2ec7f3b50c01d95d1ac624229aee367deaaa8188ae5fe1093d124464728165f025268a9e1b6638ec1ed9ba71763e01b753f85d6ea05dfe7a83a002a72ae2e8860daab55c824a280edfce37ba6fd62e37e1defc38b8eaa1b4f403742572c9b7f89b526618c58b006e8b9945db0f85c67e94e24305ff5a24c75d06e6de331e3ebfd44f72c598f5d8c69562f03569cdb103e391b338b0433f9c13d7aee44809730fb066261447ade6e5636e8a6d852966f11acb8b39442a08170dc0a5037212250b9c9c31ef8f284f995b57d4c360c7d2dd86e7ca2944c29f9c04047ee23ec09a70fdb56922327dfad779fb4bab58e0d58b16b8e911fb911e8d98faf45472e9a16db8c651e7d701466ef0c6e79c5cdfa406140bd17d64483b8c927ccc901dbb4fbd1e2e0f7020e568b3d96c2dfb26ca87b8390ae36f0064d5e281405ec5233218842d914e632492818d9cb9a6965d2dfb21a20dc844d5aa3eb401c608c8721e6b608c7743a0bd92c892f83eadf87a6a258fe57c333af48ef25b63d80cf072b9dab4f98ede563368b8ce2b1d2d0951c95bcbe84631776b4106380421920272934835b0a8aa26d715a6d1166aa762efb8fd92ad8a931248c879a502673db17243f3fb0414201c5b8f31c93721f8c7e39fd62370dfb9d3fa817a696e37981e10b233a699b8d5e03324087179ab7cafb6527f31734d13fd783ad9f565ed55b32af60975e4fe8a8ab42054814b3dbe8af4de43286afb6c11a86e62fc24802fb572380240029497887748305bc92e7e98a1724f9d13a797545308a03f906308b2724ed4c2a176a01e348ed4ec5797a4f103480e316e8536f34d0744c9f3bb185f72c9235df3774597d18aedad2a495b1e34ef27f448e2c2ed4983497bd4fcb85672fbd9511cfe0874c28d44a8116ec3ae53234ad6f011b7593c2608aea06b41b1720b997e3437b1d2ec8ec147feb5c8987f4a301041958d72cd86a1c272f884a13a46731600627e5246b89f6c7bba764eda28fff5012be6ba4d5abdbd3d7758e272688477648008b8b058f6fcb5f2bf90fbb8212f9b5a8328ef8601fe6ff133b34b287337e47e3b68e41adc577db0f5d3b0c21e981e492d500ae93f913e60051b728a8d764596705cdf64ae2f8eb7ffe7a97dbb3b32ff24ee3f06d747cfcbf1d072e62b5ffd0bbcd24fcc81c4623b3f7afc3f9c88d8d9c731ba122bfa7fdf3469016c21421d2d681dce56562b679f4c7cd7d5b4a3057e657ccb28d4b49c26f08472e9da2a06cbbd80caaee5ab233e776a54b75e995d2ac0f6b8a6a00027085a7a72b5091229ced7585fb47441d9ba9f9422d1d5a04a699ce2d472395685a61c6072c00bf3124fc6079e85d0408f63ab4bbd915746ab6322885cd5fcbad665defa72c3ec0df6f4a337f93840f8e16e99b644bebfb3aa202fc0623a48ef3fecffe04dd5a560ff6e081ed949bba96c2f77f95d683edb1dc7e5b0a151242158e444d837aac0817de62f12ce29b858c7a1f81ac1ee438663bddbbc7af1065cde7daa76716cdd8817acb3d1072e7536a30c08f6c80e2e13fd4406840abf484d60c9703f3714a26f8e5c21d9e6d28a49cc41bff6fab646ca3923b93b6078b12744215b5a72c25da3e090538011c4a5f2de76a89f77e3e7c92d24fc65aaddd7468717c8ea72eabd959d67a671af40f7156d77332fa7a96dd24ae11e1c63c71474bf7d3a3166d117946396e80836bbaa5b24b8bc0a21b29784c80370830d15b25e4c3ada30723486ebe06a0b32667ecd7c7176ffa3f7fc7abbeca7f55b9165a1035a75ce06234d04d91294e614ac35e41e6b2e4e437f82c6083d685028414b46f363db5d1672989fa02d9f2f297b9b231ea2d8cb235510624d7c9f694fae34c7bace00a729724b8b685fcf0ad91689df2edc9f9ae850529530bd4624bf21914e28104c125255ad1a1ff6a1b60b8bb2da4c341cac90c43de6ab363620cb9a97d3f621cd0b3c38d1cdfb0aca0bf38cf6a9558dd1e42a1f519dfb3ca2da888fef197c8ef9d884729b1634405a35bc9886894871f4e10a76ad65a0c185915064b77ae0dd7705ed04067b31b478045f420e2551cb003ec94866f6372849d353ebe47566593f91924c2476ed940a07b42a540675d9a7ecf856d3489294b7c0ec5b2d9871d79cd49a6465a401dc452809808cf76b05215110630c425a83d7945baff71f875f629dd918421f6ec1014781be7bfcb56b8b0d22b29c3d100e94768716e15dab51fad6eb4663bebff6296638f7d1710a2f5d28465b51fdd5621b00f7f77a75215ac24e2403f13f8c9079a9b7984d11af9efd6f28bdd411b722174dcf52f7df56d9ac1ffc72a3f3a6dbf7444976cb3867b305eec82a31ae52e6a917e1675e5817a58134410ee2dfdda7a2b1194fa051fbbe91aa7a5fb2f3fabda776694eec3955f2918862679367fb933cf9339e1ab5d7d6dde5e2ac3b3d7f8139f7018c42df82cb30e28a720d12712245d3f36d9ce911819adc560f85f8d06add6010a0cb8a24884d697b0015abeb384c886dfc48f3c614165ead70ba79b329af5f6a32ed0aa8c27921137185f2fe2f38082a65361fb2c245c2e60cc7ae61ab8493a7ebf751a5a07a269c4da8b2c8f870ce5078e47bb7aefc8e10aa4031a140e5c33cf2387cee4d3ed852120f288911ba204c71252b4c616b85e3acda9457a06b22988dc73e57495a1c4772f663b22fe3a5b1f4677f204c7137ced5b4fdeb7f61db4613f5ae78daafb67026e4a43ff2a14c97263652449b318b4f6a99e431d46eafcf0ec339a11f84e81172e89257cf43294ca5b2d2e24c2b170dcbd1354f6976dd00749672d571a2c1900f02d69f013ce1e843ab2bacc819c3bc880c4b0dc6a7fd4a084900894c7b5ed264051cd5f3ab4a419028b0e4927c96f6cc5f806d82e8d218bdc8e3a347154633725d6cc0a67820abfe482a557d06ca6abb1e4e4849cb5082b8b95396c26edbae720feb8e682f2333543bea1730680499574674937870a43cd681991914d86d9572fcf983e4427389033016d863cb589b28759f7de152e03c11938b9e2ceea022602620b95900925753ffd4a2b4e609842b0f7f943d6618ca4fa8f2f44ff3625672377db17d35fee09e9e6b8c10901641076c1dcdc8c1ff79c959f8e8ff0ebfa572c27f5faf09348d5b7de1b2a464c11d33027e8fec13dfac0b9fba9d0c071bbb72b58eafd989c282295fc4c98e73727de863ba367abe92905e981f0f51a7993f687f360d03b839b1feb478bd2d3d0d6d96904ef427c46ddf26153ffe1e276b6c2e9e026790de0d780dea88197867f8a54bf6fcc5fb4064cb8ab1f5a4f4523c9e72af00d9352d7beec062b0ce87f29e0c94280253645b16fda9c490581aa271ef2d64aa8332b4d5dce4e9a651406bb3789361a0494caa9f4aeb081bda4293d7ea0d9efefb52594656337dde6f192008319b084a21b14055af993bc5af31a96d1c5bbef9944f64bd3ed681c340ac159e25478ea474d920cbe5d1762c651a7067e4442af3aa7430b85cd6d614b030df0ee31cfeb52a536d25a1fcac55cee2606cc94ec2d716b3c079cd16194e0f3a7cdf206a1be61e8530f9e2012a709f1b45d5c3726122bae7a5f9d38d0e06f00f5ff69bbb58b62ffdd610f3b297453c1ceccf5c37d2878010826e1f2a7bfab2d1ffce9c187c9b26884933ccc293c5c531868af75544a6391129433d9f1a8ce47311506068b0d78f6f7c77beda6f1cdee68536fe63589970505474caec0230841c63c92a923eed818f65f57131e5d236b5442a8f72efad5fb3fec61b7a0480e8325dbd2687c881e5e52e61cf51718276fec9f62c40f77c978f22e42f145ef31dd53ba0b6467796bd15bf9ea33ac0fe428b19fe067217be46df49292a0cb8be4c49ad839d102237e9c06034ad259bdfd4a3dcb7c271f8e8abaece8c7a93c89f1ca51792a76d19835f997153850128c813430a0f5c72695b9d0219aa8cbd0c47689845f4ecfbe8064a142285814f6b2c9b4fd766eb427804f374c1cfc73bb19a1bec6f3ea05cdc13cd945566665ce8213afcf87aed72b064d89bad412f5bf7c23b4690fca1cca8ad844f182c63bb1b13c3c8124bcf720749e5c1d58032e31c2186637565a7a8b93452ac20a74e0267e685acdd7d6d64fb06f680f87ef40d0005936d2c9c48346d505d986354c356f10a449480f28c0a3b71b08333ac7140f5917e578331be4cd861e360d73045569ac5412ca8445e56d7197bef7c731f4641219cb39c437ea7b6e54ef53d91a66cd7fff62faa86dc72358ebb60b79dfb723f3588dac7ffb20022fd6171606a87ceb4889c9812fa3b3cc86608edb0d21125eea1ad76e1558ec89f4a642451cf89470609ddb92beeb17283483253ff2d24548ef22e34428d2eea7d8c22cd5567072b9eb13d1d38a5495653f7373040ea17321c05b6f7b4cd5b3844cfa405ec8f404315b28ca76478ae72815942ca12ebe08230e625d320a7d6f4e80f486b025f5ea38929e518464b813bc035bda5c5319f879d3d8fb686ae2e97531949b811f603c6afa376bc5a9fdf2fc8b18be1649049228eb5b08281bbb91a52f725f1453354fedbe686700d322172fc30102572ca413f99fb65015c2c28a7c3dc065b1acce8702128f600bbdcbc72222bbfff267011f12eb7837e311194946435c2f4ca3e12127978f3302606ee72c1be41446e5c0bba60f9fe998f8ac19329e6b83d8de62bf2ad2d7055aa1a91729eda2fa2c4cf39c781db4ee87e6713691d954fbc439499babd117f459ae390727f0193ebee49e874f9ca8b2830646786ae93757ec408a1cb495adfdac30ae6728b4db41d5d80bab464e9e3350f9663a99a9961ed5c5dbc768424d204a1c391725519d07839b62a490ceb83a3dacf24bad27351298bd472e5f81a652632cf3c1c0547f44fd6ab64b064a26a3f2f7db05af83039cd9a542bee7a4393fde6315972c71c393426722ebd41c6015c1487f5852bda0fcd029fab672974853347ce454022319931f880586253a5353c89a59b9a76361a945407118cdd1268b4a77760473350a6fb53b3d9311d0b0dc32333d4f23000edc5f1b5680a0382c2c9ae3b9b1a73815e59d373e4b61174b3d70f58c2f3c98314ab1a1ca818f9c08958c71eb51dc00bb750f2c3600a8d408e0b836623ee03f9414e44c15a4560f4176446b3ef093db3dbe65fd660a55d8ba3be83763eb550bbe2584a5a5b1318d8105b97222d72b6d3f10b175b799d9d3582a1d2ca3c5effe146085eb5a1046b54aca0019d1b721bb2bae843d9170a2f341713b63f256fbbfd4982ba356a10506752d8d97ade6f21dbcbe290d129d53b264291fb0a84a8d10d8f94d3648de90e4a51245f67157298e73443d7c641b13b86488ca37ea8f3f45be7719eeca8439c1b39fb887e92722e8c16d446ff787653c382a6501b323196f8ef4402bebc03655ebf3d0262347210c9111542a72b23461f6380f5e4481528b1d2c56fc591d0195acfc59cf57708a01200b2af2912e37e89b13a26528068c947747c5de72a226f3f89404b96fe7276d6946f578d0baf1064a27cc997ff9d5eefb2e78db7e9916efe70512370e4725ca264e275a6118790b8349796d866839b0c8c4ae23156d2ed824c89f241791559d3aefc22934bc84e18993d17cf715476c1ac369435e1c73040fd8738d1a372e6444ae4ba47166475b3a36db9e581160ac91355064ea6811b531dbb9200c97299920751f837b6e08084436addb954f12242da39e23d362e1ee69edf57bc994f3a42478123613cca00485f022c0db892dca31d2bf4104f44eb2d240f0ff79c725c7cb2b4c59843aa789809f94dbf3f9d5907e5a7a5259389f622cffd96c850477253c3f13fd70908b048341dfc77c9ba4f54b580a67e52dc44f3275d8748cf594b5e25b805b0e46582a9d0aec90f38226d4e0496460f3e7dcef41b5bc8d5f864710f3b9e9d4e80debd4318a2c6af95f90ce371472bba833bc736c2fb5e1b657277d406291073d58423c90f52456836478b7cd50abc4eed50635581ea51ecc66f3d2366b1b618c525357ab060a51461231ce5621960fa68860d56709441377f103820a0fc554c3e3dc725406a81b95c526c074083f9d58bc42287575552f5a072ec4fd851afa1f1b7614916bc6c48f7f336e5177028f42f4e7db1fc4db1bed672c9702c6c353818ffc5a26436b2461936a502aaf23df7de717031a68e9c040c72010a3861b7f55e854d5968da3089f0c6d2aa32014285e4ca13fe5257627cb072a0c566121dc24b5a6ddc985d2bf6b6adbb6209ad4ddad189b7c5aacd85bf3e5bcf2b30c7042c131c76b01ea7888dc9630ba7572d9b7d4166eb68940a618d9d50b58131efef120b8b0558090475121dea1c55180c65acf9cb0fe68019a2cd6b72a7c28779668ae5295774b11780c195af2642468a5be6c241e1eef211fc33667230e76b3d5d68a98cee444d6722440f418328499965500304331c710ab2132172bfcef7a0ec2de0152924c3d142771c3a5f1adda5d6b54bea1e2afd8179595d72cb5085696b6948675c504c47a07a123bc67f72bc7f0c137bccc7eaa200e80d725dbe51c0f995d61bffe12a828911edef039368a803edd6964956645317b7021e52497f770e86a26f0554a2616473d277086171a56e8d366af974d5d5fe7fba72d9e85d15bb9a54a930f5b7462f50d7aeecac89558f36f567e382dba200672c724326c641292634fc5f86757167452515acb76df9055d5591af3a12277c74ee722e13473bc37b5503230c1637b2c5fa82e86071658ad79199b8264367f92c194b77bfffb3905fba867144a74b8f1e2c66995daf731730fe4bd0618ef42b9ce172efef1806da9061f4c350b14e41be5892d199e5f375e45164886631df380cdb72588ec38f6f0fa619f9f80ade475fe32ca1468345dbf19bc377692077eaaad57280d051750da3dd822ac98567bfb6b41d2a038721267f5a84ede9ef346c57cd72140df33f196bdac0d6a15f2dac93c1c45995fc0836bed6627a74b0d192e0a90337a394ca052f71a8756bce6a7fa22bc21360ccd93e26a15e3e1208a3bdf23e68683bf68e46dac02dc5af24fd6fdc1fcfdabcbde0411f1b4b1417abde7f52c0221da243e177ea36436b3b55fcd1f5ecd5100f9711fdb6abd7d35f956d658b2a726560aca63903674b8b98c3cf2b4de972e960d5f284c3ba65b84908aced2c7572be18770a1a3e69a0889f9b212f2b856db5498fea05d2f7181393dc1d9c92796e0d4620bd2670fcdf54dde80450d0f1a5abdbe9a4bad8c7a55934928f9fa6e37216dd84fc4afb495927d2715f0c013cba6641b03cd869558ea61e3119ad465e72e443a942aab75de805d4c188c22295b5238c7416b601d5db9d54b289db3c37702b67564db234cc19c069d25c9b9addda45010edc4545900ea5538411e8783372e6b89e1478eb778337e93a94a666af06f01914dffd02d74e952d480e85fb50025533b8bb908923a7a66f616503d252251e85ae0d36803129e699b5d0fff67472188a3c332c92385b3f4ffa410a37d519077e97657d67706da58c8ed3228a3e72dec50801a915fadbe25f65755954c30a4732d13fd552bc425eee9ba01a0a4972ca10d01eda88867fb907085a070eede4bb3733f7780e82dda5c1d99d820d8b3df199f5f2a339a4823627dd1e69bb09b22efc3944b67b3b914dd52e65cbd4bc10ede6b6e96db985c5d0a13d7baa0bf71c21705498510279b2b65c9fd7304cd5721526eb981ee3e65c075e504b10919664b99a09393159471405e75b890a7b36725e97bf2bb46803cd791d19655c11ad1c2dc96b35b4cd2f97a5695da7428c4832c68b6fbbf14c72ab61c907e5ce9b024660e9a3d1f393bc9a7859e9a70a85de282dc92f8f67afdf82cfc5007ca060ff2c6fb8c6a0068399b3d93c2d3d528dbd720c6d9a4cf372498f5f35ed1003b7c102118ff4fc2f0299d60d3f93af09f3647268cd7a9ce7d8dd7c812b2d59821b3dfacb1ff61d89b13f7833bf037c55c6386b16974fce21af0a4bfb15de50e9cf4bf3f9b5578b44c57d2911e562e9ade1b5615797c018520f622958181c44387db14ba2e003eb660994852af0828b09d5984530ce5c165cdeebfd5b0ea2ef72cd202884586af247ab65fe4d69cd0ddd871c726e38c5fdbc006b2557313004356de4b70396e10683c443562ddc7f38bdb70870045f3872479b6458ddf395ec19d0c9fa042239abe8b80c6de973cb3fa1f5a072fa13b5274115009d452d410fe6276fb6a319efb932ea846006dfe064de14c96e3180e5355d2b3e0075fbe3b65f723ff7d7a04f9daf93f77bbc319d8880dc0072324f6653698923824ef0ca5d6b223eeccee4c17c46941f2f5f5e1125beec027280fbdb1ca20a07e2d23047090a224115b7f13b648a3cdd18520b05f252de3d72a85c81dcf09b7c45d51ca43bf8661b4571c686d909545b57d17dab19a7c7e672542dd26a1adc6f7ad6ff5156510ce0d738ae9244da3aa5b04d8653458ba5e9727150bc1501dcc792bb3e7d0c3309e4d641aa3902e0586449660c81c663654572f48f8dc78bd11183185947c4ac22bbe8969c7c849d69116f65122317cd7be3555cfde552dc695b4097f4e2182eb9af9c2716fa9d146fe20ff2c7ee7721e296720633a2c420fb86d86245dd3008de2fdc995e4e6b2b74a11682e112f01ee338241ff6c2969f6511c03d6c7a8e04cce3eef997553e237f1346409d61db1b0c737290cc7e1421b83aa25ce86be91d019accd222453001826a22624f7a73796a1a204fd57b5e283b669ddf96cb1925f551f7807342357fb800a82a0a8509c1e71434c58b04df94634a948da1bffba9ab800c8847eca70ea4acdabf56294e3f781f72151705c91fc99676ab5a1430618977de4d14d61d4cbca08042aaf08e0508c3729affa49611c644a88e2c52b494254790dd75a4e7e258a22a1dcc75734179ab0919cc5255133939980af2d3735964782f50a954796b8f180ac311796a4524fd723a5aa28d270cdea2872d386656081c581a281507542d5bebedea91dc8d716172b81031824b22aebac20daf04fe1034a2aad37d621334d2c2989ab84343de7e51fe22f0fa56243e588bb41aaf8d2e9c86629d82c08cdef3279aa60b52ca7a9f72070528a251a4ec699b54ad06f84acb13864bb197fa5d065fc2513b75820ed5724774da6ad4b62b98dd397c414ec5597c7ab565915833800c872d71415c119c72e5d308a847d626bb7f1da5955fd6f3f324223acf1feb235ee988fb88cb8e9a7267dc32d68d87af308d17cc9f08680fea59c8c2f872846613b3b96dd7fc699072a990c14e986886bf6b7ff44bd74517c3c6f8a501213b96618c29524edd2c137282d4bcf7cc3a34bd0b1c1b51ee88e66ce417ef2f32ecae582b13b25cca7cd2290ecc5fe0135309037aae8ab4bf8ae9998274307120af58e835fc52ed1604cd72b24ef4f1bcd7afa77671d734735c519bbbe10a3c3850ad4e4e455aa40657f20825c3eb70eb6df645ca1a990918069dc46c213fe194e773536c281ff52ae1f34344083fdc5ba32e502661f8fe1db6f0d8b9ed4a280ce6e84974ea932cf13198721bafa8df8c703de7ef8cb38c3dfe5c97dddc2d7df08c13b6e85a39a59b11053e038011d1d1cd9470ca5fd1becd48750b37256ddfbcbe01f1e3f0e71d1ab3f7725609c27c8e9c08945f69d6dd724c96be43f43ea6df80ac7399ac7697dc40e772f4dd907b2afb4cce01bdd19520195bda522b817777bf2c9e48cfaa724bbb097220ea26beeb7621b5a86e45c591cdb55c462c965816035882eef04b6c0a0e4162120c329ad36596af938b7f13a12d987b5c0f456d654949bae1033d98f7ba2d72c8b5d41c79e3b263d85adc1990b17a999b0da68519dda66d9930b5b7a219d02f818db6ab00b32bdffd85bd7c7d921ca595e90b04677096a5594a5a675eb69d720677da77279e214769a6769b38aef288f7a42f786aa7c6e0bdbf0269530e9c0d001d510efed9e182fe39f986b6f022c0f8a1e4c2b9f956508602b3edaaa48b5452057e07dd07cc217322bd11a2e53e4b616b61b69333db931212c32e230ea11bce8239235e9e98b940d51c360050e719a203404222916a52957e4280adc9c1502728180127e953558296b0273d314269ad97f541268d723798702b4ae8a57972fb6bd056fda5f753a7123186619c2e35172d7af3c05e432be388498c613a00722347ebdb2a5a7babedf03e5d04dbe890b9d14a4c9177dc458b02cbfe72f21e2340118b4740970867a7ecc4e01baca80725a5eff55d37eb18020e7277d050dc6c38e19a155412e714a518b63bfcb5be22b97e16e0e32a72ddf9c069c3d019f43b762f74ea8d7e4baed5185ef9a837decccd647ba38dc92881a86c9b59e9d0bb6c8ac0b70259de8b80bded1c219e20d4e0a8bc0d43cb963e7ede5894e037f1207259eac7cc29229664aed0a38fd1688587f7b11ccd3c5dc1694c71fd80d6e8b47289a1beff5c66486ae6f57248bc3d16cef8437dc2ab5e0590d9db082a616a5a281f3899d122cce46c61191da8b75fd1f74de5953abe912c63791aa02b3ac9fe456374ae177da37347644475ee38d5d00bc6d1ad080b5efa77202c2ec609ab7a43c76be76d805a42ce0f1b27369bba8bf23afbc3b5b5a8584c350cd2becc04aa72616718b515e2373a034c6bb16cd0a4e9a66beaa9a2a678f25cc8a253eee1876c2195f1150c385bfc5805a7fa03aa66c4305806e9558545219ff6975aa5eabd72432f36bf8352986fc79aafd753a97f7bd49319e730cb446e4378bd67a79c59723f15c725a5db8f9dca9d8592e8bc5ef9ee20588f1fd535bc6fa4ee18a11fb85f5207ec0712d9f98103f5ea21b8f2289609ef0d346dac2793a1116c2040a7e35a6de3107bdfa01823ccf99dd48eb09fc441eea502f12d6cc6a4e764b99003b972f984548dfae758494e163730b20e7172ae0c48584e8d3d9e15aedba18fca2b720ac3f7a307af406d4b724b9db032bf22941de510f8d5a6579ef8de9775b247432d556e763a55d74a11b97d3a095f731a9e48cda3669f088f5b3024c31baf117237dc4f804a183b37dea5afebeb524486c1f4d4b2be3b97847acc130989e46a38f1984c1b2cea115af315fa802a4216590553e5be66fef1deff8cff3d25714f15fd13a54576882d31280a5c51386989f5f0d4b9a9adb9b4e3f13143ca0dce644d34155916abbe8282a0cdbd64d415d6b24baca1b1b27147bbe10c3ca4e36da34eac91e259064df41242a7051fd1386f6462289ec8009da40259762735ee694a39548612504a6b17cd18f288c3cd5d1e0830104a20f4c2d679531555b7c4c07301bffb071150a5158550e247f6d5931adcf89a88d32199edcbde7792693d316649c17b93949ec6996b48ff13383704aabfedb8162d42a31ce1843cad3dffa4af1bc33f6a7fcbe40837bf850ab6a68005616a4d2ac89fdf0f1eacb3713e7e95e472d1828e5817dddf41f882f0dd101a2f0d486deb671f4269f2f3cc4694405c06726b51744e48c6644431084069c276a64922aee265c501badc4b463f9f71347572bec3aba51316b6cc3dc077da03fc1681ed8430290b94af6696dc5d0bd8c31c72ec466d722b2c7ad0c4de82c378e5e273aa715640a0ee58655d22ed02f9a8752e5087e99fa1d2db1b026b7dfec43fd837af0fdc982ff39c6f10939e25ff7d52118ae63fb953891b0f6af023d5659d15db7a32ba727b2d066f98fd066789a4555a526583f0a1b015ae843ee7c897b91216e9a96ca8fb080cbd1631988858b982725aaf6575641efce25d045b37fdb68493c888fd2c9f7451ebc768709abcaa70391abc5420469fb5919c5af32c689326189b3458095d7ed6a19b1b0471e852ab2852e0c33e36c7da1a598a317ae45d9ca621661344a47a65c0d2de9f8b0f973a728355351e9f07a78a64ce9c6f2937a21aa22a75354be438be867f17e71b1c9669e14fdea42918c9ba5320ae66fa35acf63c2df88610a3516ee7809f6f00103a728c11a87004adef376d691c83470e7dfeef172c1a42345268b64f30a35659f15a2a8447bf2db3ac9c6f3fcd59b2a0c20c1a1d59473315ab45b052e60b75888172707d1c18cbfa21745c2b9c155f47789be871764dc40d877cbe6e8b05c65afa728142563e7b6c4da5ba2e06c5ebd452405026a9d5c989b6e5169b91155971c6388e98a57aa965ea4ddc81222a0c9e5aa888ce29c97175e48dc3c0456cef9d98727785f8432d4e65ffd4425ec737fedb252767d44ae5bcaef4f8f32c74444d2572ab7238cc41ecfbba0b7740e4b7da9f9f0b300340a620cb99f6d38cd623527672343616fe54c1aafc39b271494e3a265ce3f2410a3c3a14d6d7c7d16dc23d6072bdf933607449381dd85feedb73588fc09510372eeb99ab9b090233df8f1adf7263c48503c143529aa94d79ffd60ebb2bbc72ef3611b7c0bd6354b7af4b28c3411acd58ef81c4b4670c7c124a865d8d03fc9a87067a3e1419001b61f5752322725dab3cbf6ba862207fe2f33b44f0e20fe8b7ffb87427ca71b2d65d4d26650c179d53e9747177c255c5afa9453d6ce16fdca0b704453ad5356e336629469b7f720b2aed0213b6c853bb426e7356356829df2e5a78e81462e7132cbcdb2f090472ee1fdb557b2a9ad17d337c838c7fa12ad301d82e88d26503a6ac05befaa2647208c63501538d5e155f6bd04e7f0fec1c547c4179a99aa45d7f60f34a51429c315047e9dcb9b8e36174bb3a07bc398d457ef4e736517821ce9007813b110262724157f17883016c66581c33f64f78998b2534950147500f4c0f31e66959edb446efe99b05a96ddce27276873cb593ca008fa7a6e82a1017af19f0232802917f02679fa7ed38bade46303f54124be475b3755cae4ebfafd532305e4fa1cc861d7257905b5816f179dabada52ca384f2adcb3a96005fc38487ffeca2cd10810db72a8342aff32b34a0d3e8ad50a010640eb8314ef1b33d45118a52ef126c30f4f728d7b6566b9774b621cfd51a5d0b2dc5ce2d8bc4d4faf02e24e48754e8ff6247257fdc11746f2ed6de4cd130d0e366ced3b5856f456fae499cbf5107d3188e27217e8c0bea5859bff8ed34c58627a7364c115980fab925daae42cb8bc9dd4774d2dbbce44a91a5422be23221e633cd21f95b1b78a50989aa48b61749df5602d72f146b6a484e35203831cc7ca09a6c59fef09112d8a01c705e85a1d61f2f6d3721703f90d4b30fa0dcced2e2cc1d080ba3d442f66f9d1e34544b5678bf657460d548dc18a10cc8eb344ba9035636fd9cbc10dce2a634a34ffcdb8e0ce5a796172a6df555a09307146a4a28a93aa926724da026b58b9f12363c9dfe394ca4a9072188f3f81af04f68e34d4bf6a9f6584dc9be15559fff91ec903741436817b5b61b5ced415779b120060f2c614acb074d25e1b25a6b91cb3188c9f8af1d25f5123f4a8b5f3376a39ed4d32e0500f508e54134d1f6b1ef9dd865866781283f74a72eec230bd1e097e97a39f61b86e0d7247957b444fae628fe33b1ae06cc572c772c70055f4c9f707b94aef3a3268e9dea88d7b06ae6b19e4177f25c4f08dcea0729df3acd727341b65e26069ff8ecc9a84d072e018bf70d506c8083b2fc8e9ff725458c90ee41e710198896b20e728599aa8aafbc1e0c1ef4959fe051be0feba72dda0ab7002ef9e0131e18788bc64c6fd1a7722d0a00a4d26a5c95425ca3a5d72d12726811b23ba5ac0e9218ccaa8a39b59ef267137d9e801abbea6e7063830193ad38d667b7096a948a354a8a2387d14ca599844eb18308bd78bacb3ad09075ead773f5252f40a9167f670bf0be81f6a506b4cdda69f77f33045780398464a724a4de2eca2056e0143017f0aaa5ad2d5d9b05af027f8d9a2ca5d0b747c505544fe5484bce0916edcae94db5c954e5603bb404beb985491694d7e77782a90c97297687f6d8ecc5f2ba98c67c7bdf5bb5f59ab6d3f9d730c24b75b58ae02bbb95581319118bedc1e96d32546ec6e076ea280d27f59ddf202af393a94fb8239a15f7578ea242792b84c49e07b0aafb961dcbae1333f2d950cd21de87e5bb7584072b78cd288630d350bdd351bbca38b092896aef3eced50ccd97e9ad653e6dce772f514265031cf6586f956f2014aa879eb2b76fac911f0b30384b74c32df082a72bb9aa6560ee324e33d57cb4ff728c52541f9694edcbb411e70d3f305412b01728b9b4319d462d26438741291adb1bb0c6ed3482a227148f257ff153807f28f7237360fc4f29e8096c9108677f89a389c9efce9c1f107a6621a6d4b22c3eb497263e4709781d1f9675232ff0214bfe4d3f8b25fcc8b47bf9e3e33b3a7ec6c137204d8302f2c796f0c9c87ebc000b6e5538da4cb6374a6743c275ede79bb19ae68129b4ee50f4038322f39adb4ea38333b803bb69e96a1ec60e15d28789bef2256eb6e70c9f1edb4a8715912cd2e5c6150ed2230876cd9e47706576e24fa561d63e0cb90c755f7e065e24ad216b126c142829a4e68ebe949174eded2c00f39754b77ac8e98f1f5b8f2116935d3e4bf346bbf78cd33a5ff1d84453804bb07ba5c72c7a26932149bf37c1996abe4202444addf5028009117c88923354213179ff006f7ab41c3a71b99c5674ba276f533110e20dd8263dcf693c9b64b3f4bf0a28b723000a51d11d25502bede1f8f725645173f6ac896e4a4e42f33987e88c1f356729b5862da2501f7206f2292621ede9b70f8a286d4fa2347949e8686a6c1f9602363c2d4e994d05b54482d40ec380abaa3f9c1871926dd35f37202c44878841872badbc629497293b64f2afae47af2361c8d559db3f95c8e8afde70554527f907201affad0e8ff88c876274d3be9779798f34126d032e26f33003230d35e7e56723b8ad0423297ca713323c84ee0b7d60b7ff6eeea747f0babccf439899206ce72d6558ab974121506bd37d3b0595d738e01fdc1ff0ea1ed03e0d7015d4c573f2e3db2ef534a410ff24ea0b227919ba4f18502c65c1e726098960d96f74012227270e6d9bdc8edbf3f29e8423fa2ac8d81032fdf9bba84699438ff0c0b5c9069722627d02bb6142fd8d2cdee57afb6a88afc68a9c2b05ab7bc547fbb6c40babe1daffddac9885146cc095ba5710fc7ac04d0623aa682dce02c8ec6dd46e718b572d22eaa74d5fab0971f9ad50bd6959399e0df6c8a20afbd04f7a1ada1db5cff72e930e638528517e13f0882df73bbfccea6327a5114148ccfd05860be2e8c057219a368f3595d3f49671a56e05fb279ad247fa3c0c3c0c6e56c8bd97ea3236172addc815c531fe298d7c25dd55625f002533ca1980bb2a7ca7af0c447bfda81725a3b25fc9eeccc15c72a83db55fc351e2c2fe3881e5189acbc1101433f2f8472d327530d7c7569dbecf2edbaf8ec98aebee719bdd2f4accf34c2155ddbfbfe72da7567789b52717b1de1229fc3e7a96530da2e554403aaf241d3036bdcbbee06ade44caf55793e18646394897a48ad0215c9d41062b431ba7a786b8430349272948ebdf2cf946c3e15e352e06e89de22e94db124353091e6e4bb356917f73316317525084e446544fa7fb14945ef53a900324479b796d4f0e38f40970a9ff7720e116d8e563b44d821a813ce1ec9e183b51fefa8e4043f96dcd5a56f0da4ed403e37a9186ee03762ba885453cf18063dbbc84b4df0055039107a162f44d1b972ffb157001a03e90548b0399e3b7a823ed00b5087679bd50afd8fc73476fead72af018ca1e2d44f5c94fc1fb052aafdc1d50574b8e928363d49a78dd1d6d2633d5f97b1a07cf9703bd3ecdcb222b65824e24c434df329d4107b5a867f57d60171311128ed6dd5e13ac5aa591da3feb47292136853379f47390a4284963a4221468eb4f9ce3c8f9c9e538e5a17b0e471d750322807e7145407a378ab1b9ae754532f4e4eeb374e66aa4b90268931cd72caa8f7d16cc509ebe37d5eb8f24c9e2c60020a9cf145bd353b115256aabddc95cd2d9ae6c45e064e3ca3e5abd34cb70229ba991738e7cb0b49bed2566a0ca1961c12e2ccdfa9a2d2ad977de61720b85b2b2588f1dfb3719ece2231fc2c7c4d4edaa41e612af156f0b59f3b21bfaa30fc72b2c20d95308a01a69766b130263564254ccc6f51fb3e3f35dddad1e088b70315ad374610e1b9114be9b6d6a8ad3028781b504f363585ab86d7a0fa04f7dba772be5a088a858fc333feb3b61bf3b7786f3c092fc5007360c33da69ecb6256d66df64cf446a91d5067f2924ee1449f16da26894a2e422e255e7a3286094f4c617216c00fd9908508e028bfb7ba60c0dec6a1d00b11cfaa71d922b55a5417d1c372813ca80c800612c59505da0cc1682490bb3db1f44574876e7b3151069db6d869fa058d22a0cc33b2ed50cade3e7e7ab115673c98ec0a69c496988a6b52cf732d6ed20e8e93e0e0345d07b55313ed832d5e24899d8e7474c0777b2a3504fd8572fed420b0848993d034b27dbcb8e4b9ace286dc3cf6d74004513228c2f7b4e30986e5463b818c53be62a67a43322cf31cb05d8e82e30a60c97ae7b6f633b7ef41958981301f41db2a62cd8cb623f9d4e5ec8440c7c97dc9f00bebe89dcca232728e16e3ad5b154d60f272ee32f6d6ab7d95fdd5bd0f9e22b5c7023df972a29f72f6f947c0de5f11aa067333b2c14530fc751a4870fac2001b1b3650c1db695521d70385281f81b050236d01fd32bc5515a485d3fc7a3775da41a063d0f7bfd3722f46dc43abadaad2785311bfdfe67e3365dc5e8c2a36a697b4acd539aeef147288bad98ccc27a7f076502ecc4467d260f941a4bef0f2caa8007518238c658e7205ac9ea895f96b2043a97c61e5720ccd323463c1ef39fe60fb86801572307f5f76b9cc8e65ec514c253b6792f94c1efee44c27d0fc23f3dd8111c4dea16ed372fbc573fa40ce721bc3ef5a49d2e1b89096e30c2174f326d69ba1ebceb11eac72b2aae67c2d205b6bb0654f8823a680faa1a1dd7bf3827b257b98abbb3e5dad7227d7c5deebef55b51bd051ed3cf5e6528d1690cf04b30af3f9bb796bcd6f611df6eaaaffb9f5c7581d6d1a20730e62a730c90ef355e05a6d93a41ec4d7d76672a31ccbedcf1ddb6a1635b9489a54b884ddd2cb1686b21b99ad52e3015e0a4855a3c35d2f7078739842027bdcca0089bdacc93b7c35c23ea57287bac2a0df95721c8a6c318fa8d962eb411d23d8f770106e7daa881588f804291b71b521c98708bb351f9f884ddb19b28794057cbca1e3d969be8f213774556a085ee2a9e15d72b140be59df9adcb946a8933e386ba99b53af5d5df61a868e8b510f9b3dbd967226df7b05bde21e150ea7f704a8c20d790985abfbc284414322931631d5344455d815116202a985f1601e4bd75691e1a1e1b5480bd86f756d0ffe41b023101b4b425781293cdc091fb8e6889499ca9c0f677b90024efdf69ca6d377aa16845c4b93fd6d9004cd899e478aa8b59d57705eae171cb8d36472d694ec0a50accbc76b3c9015cb14d08add176b6d4cabc6ac7bd4bca209be9bbaed28ed327c85222c726a60e987a26ec2fc747d7df21c34638744bb613362ff1f890692b7a696ddae729a66a7bbe097d696de161216007f63768d72230b57c82b34baa35931196fe163e73638bc5b6c95f60e3f4791d85b7ae24a970af206d6ee6e015e9ac61ffa7f3ba557e8da04b31ae7cab4c8448b464bc509f540e75124ca97a3c8aa14acc34c4afee7c4c326394ae8d5298c76d54516bab9c3467c1eccf947e491b130d0f7aa7262f0274df2ab657969fed9627fc3c2b02b42bd0892d89b6a575087dca37bd172cddf65a59dd924bb1548fb53a52af8a6e0866b0db04186d43e99df0fef811072fdf5151b55da992f2093ce03e68c3da65f6f85e5e3118c499bf8c0e6689ad972bdb9c7092962748d2c946b1117335f72392f3c792cea7044ca45c30c4dc6e572200f22c4eb34e1675069ef98a8dd17fcda1269f4613fde7649c543928b8e1c35b5d410f0cf468143f962f0d514a0f6ca6d197d8377ae41e6d9100613642e0172ab7374142b9f3a504b0b734c27aeb640148b656995c2e982696f9e4123ed4572a8ae91bfe670841a0899c5a3d7def912da5a14ae0d3d759f719ddde14d988b5875a0fc5b6fdd952ccc2f3a18cce9b3c20dd346c0061ccb12c1b44ac7b045f028cad896013772555e36912edc4c3ddbb8073636841972581f47d204bcd6f94a5d4f2aaa9d6a6d9a5462415be12a96f586205a913b4bf2ff56f917ba8089a70272575c1c36d84a7bab921b9ba24322dbd87f0189a7969ff185ded6db9aa29eea72dde19a6bdfff2a5e5f92d5eb6ce1b6aa934948800c37f6cdfa839ff3405cb372830a285344337d6ffe6f0780e6ee635b8ffe87f47879a64503557fa429ed7d2ddfc495a68843296b1c3bede94393f5b0d57f71cb64cb64e1e97e5d26ffdc893a9d55cead605153ae677d57772272120a3672e1bd629e6458e674704a4d28f17253db2c9ffe5f970eaec2cc2607f8b536ff773c5692e4963d979e7f95c5521272f8f466e9dd4db6d90a48303001952ca7d48f86aa236ae4043176fe169d8f017283a8c75536a5ba83c75d0458f4e8887e4e2c6194bfb056549e5a14f154252c721453b456b90e42042d1dc6fb509a44bad603a6de78b99297c61491621b728d72244c9cecf19b6bac5e3346f0b4fcf93612ac27f2e0660b5d43afa81a6928c272d5ac90966812316a187621d3fe9b38cec33b68c93c89f40485be75d80f0637615ad3497340a170240c33183839e67b8f07e9be2c806837a3ce8e618d22729f72e8e999345941383027623df7e38dcb3b833e4d25243c54ef881defa8ecae4d70687c319f63537cbfe2930e147e3e424b6f7887c0d8cc1a1160ca1a9b5c593672b3210120eb41cd1dcc86064ba560d2879a74cc80cf1e8c0b2038e32992b5ea72d9bf56d59d72a705cb23225956a3547f4106bac76c5e321485214ca968ac522d5a3d4decc7ed35f714255ad099ce22e462b0976c04e5d3da2f3341f7cada697282501bfa92800188bcb9fb01f846645102f567b20a6eec4db43e1dafad85b8722117a8ad94ee1d9f371912d062542f594107ca9f21108e2c0cb04f1acceef90561c8d59331f9e56c462b74e3a483b64bd2db16e2c7d1a5ccfb1b0629ec35296706be582e24a8cb8227e6978afb6ccb6c8aa51b33361b32986884b81e6c140f63da597c5f082284be12e627d1f5bde91e1612e9f0ed98fc6ab89b561b989bbe32e54e7c2af6252bb9b9614b0ee7bf7f1eaa520674d93bc4a3040281696e1cf9727dfca423c4dac4db6f0a278273179625bfd71d845b46b95eb10aeeefeaadb272573fb68b7cf24c80a90a4b810f27da3ba6ad2c506455a1d16315bbcb37c2c371c6cfdd092c6c1a8880c5457305b72b53451132edc584c3e25c4ec606682ab5725c742024aa29da05cf3a7ca629c6829c6c3bbae86f5292d4fbf4213d15497c7271aa9ae6895fabe93a13116dcc9e6e934864812a8f6d1467ca61b87d137414725f5d7ead3a47f28ab2b24bac79e56f906c8233245fe793771fca41738cf11f7266a7cf01f05c39e0c1dacab06ef56d203898a4e217a6624beda485d62b3b4f72a0be8e35e1b423ebc6cfb1fb533cdbf18f219a2251f372acba54d4b27c36f872239eb04b9ce898075f7d22013a5b0c166d3492c984da6f498ffcc519fa70fb713aefa16d7ac0e7906932d15b31dbf8ea6aa9648472d49b25f57b467d69378e72539a710672e2b41c73369f1a7f9048eefaed399f005c3d179126081c22fc8f723ad303031feaaf1bf3867265b2566ce38f24b0c1be3e2d233bc6fdd49b56e40657605d2e8f8f03457c8ec7f82c8c3a5c48dc7a5e367d07c861a054463397c73385ca255a4733ce25d1ba22771a6b3ec0449ab824c5eae87148f7ca42f5a01d13650273081fa782420e8caae2b346ce9db51f970b29d1df1a7cb0325a82a3ab27c1a8bee1b5ec502b8cddc0e81ca6091594c21a169e3f7a67728813458d075a72b62e0cc5d005f877f73d87bae96e6f3a045a710f8ed28289b82d2d1d2b3af4729e5ea74713a28aec9abfb180ccf3e518e8a8543bddde59d87524551979cee972e9ecb1a8a1323504cd17a7b64b98a5076706e414e5eaf83bf7768725b2518910a9b175c316090ff7e1224cf64e627097929302a6af86e0aeef85f2b2bd09b472d06ee4c8a004527ea640b109180aed39bc4741ea9251436c6e06ce484b259e72f5eb74e628398ee0b2810dd51ac887995943791a4816bfd8c7b5297b7d1aa972d504a06bee74f7b319e5e358ea740640e9663f64d94ea6d3f6ae3808ed425672b26beb94c2a0479539160d22c3268339319caa1697e877906441204cfd324e20cf48f99ab62544115902eddf3470289f9ec6e0b671ded91d5f79b363c133c0724cf95088245dc5c8f58e475ba9db0cd8d94d3c4e77ebb16c6f8409eec8e369724d1abb756fde02aa169dd0122bc05778e523115ef6af4512d9b892dd84018a6c3d140e8d9235e72b88eb58c999d26557df40cef1d794001771c29fa77de01d1fbe446b67214f58bbc24ddf4535adc14ece268cc039f923a507a7a780c39edf72c67139e5f2a0468e9cfc22c9c58a5615bc4be95cde369414814c75d1fefe5972d8e4f72c4afbbec475ac38d7adea97f29f16d3997fe0d62b4a631408d996d961bf23e8da081d5e1c8fef7632b07abf2f8ac3b296fab204fc3aa74b0ba9dad3720f84fc3a9b043189e76f160b730b4c3685b7e3f24c30051ac095248444fe0727e5d76ca1d0416d6ef6213da87c9d5db50130e71b4eca12913c57d6934dfd5372202d70ef3f5f11dc3abf0256ef2259adb83da80458477162c4394ba02899f44dcf6d25f3ab7a3fd7679139a69c23a1a95059169aa3a7e1176c2b12f6ee5215727c10526056c4cad112da123ad2d1e2d2b46c950ce9835aa561880bb996881772e94427baa61ae518af04a328954d7557c02b3e1f744e84bd145a86c382498e5a0e3754ab18afd76f55cb4d78f4e8b941b4bd55dfd8131d2a461b5aaca07cf734296cc01aea56a981fa9752b90e1b8304cdef81610d0c5e697ecc438cab65e772f0fef9d6848333a2e50b21705e5369a71dc4a975f0f55af29a44384fc1453c724c5866766dc8de304d11f50fa96e88762bc1236e843e6c7bcfc763c2d128b572a89cc8bef59860647342ae489980714b7688978667109041e91310f5764fa4725df8b3973761239d87edda3c3ebff81e0105b645a5a385a6a6480fedbf980f7239aaee1ef9241d972256dd421a52983c80df28f28232d70cf829a9772d82903b90d77b57bf6e77aa73657078db3bc3607123dac1288e8a9a815a6d01d2d60d728e9f36340957e00cb01405bdb812c26faeaba09dce1683a7a177e1ad9ecb4a38483f22d840db1a23b328e0d4b2c7f46a7264a9d96b1e26229f4c2b23e5962e725e3767a357214a6a5ec95a08cc666be277fbce5d55df900a4834f72f4a8ab803a9d3baad031b739637b2b56acef02aae54bfa1f9089ea0008d31ea87259b72685ca87b0146823d7688fd6b2d3c5474f34b53e7c4e4329e4bed54364da36c245aa18a4b02ae6d91ac3b126c937935d9bf119da06ff90b41ad92ab9fc0876e1b030f9534b7a92f9a845c08f2537885949be2aa1a3ff83e59a50f4364077e31a272d84b002e73467eb551eae130286672ad522cae758fea385cd6574e3be84949726b0960831b1b9ccab61b67fc14fc9100b3e1f9e8a6c485fd57c59864beb7783cda608ede6ed11d5398c21f85b7e749852ea083308ce65d999cf3eb2ff34414725e65c9a40ad1633b62950b0c920d65916fce3ce7e7d7d99b123af9d90e92066fa653a934ff03775b64a2a54b7d88a8be5239a42ba4382eaf77963ca01e36cc6ec797a18772f5922a27c4360182f67b3444d035018dda549975e6f694a371111341f927c1ad3ca82f7e3b3aa9419fd43c1040f5d18702a88646f47e45650c4172b1d56a6414283572ed0f79b71fad0676e03df61b21b3c3da65de9cfe6545d07249c67cf9cd20782c19bc831c828dafc97d30ef718bdfbd36ec91543da090ec72987b2c2ea950b339f36d71ff8573ed331018f9e60638195440d189ca05836c720dfa54bca13ba39b2584e1a6641c61bce34482b89dbc92bc3746277ae92fd72c20c00a87ac02e8f68afd1f83dd3f161ae5f20abd29139e7ab952af8d06298672279865d9cda68bb32ff33104d295b0d1e6d4a2aa81e159bcc876c36c0946365e67d745ddf6f80bec35f6625ed8f120dab2901b239d06d72c99cc9d60fcd59072f6d56123c2db5e1101d00864d00b369ac2619dc196669f1f5f642f967c90a0729bf455a5b03b64fb7f67a88b2c3f221ac7a59099f87ababfbc9546219fc14e7297ea4cbab48ea6224f744068b342cb65d0f858d2395d2b10d783e648a41ad67223519760dd5ee8946901ba53b22ff43270ceb9900b9a9c41b6396e9031827172c4a13a310a542769ff37de967d5618756f5f709f717a1d806d3767842f6eb772de4e25fd22a1c1689088c176bcb6dc9b903f35118e5c7800155e07ef79e2cc72dcecd645b41bc47fb183103895cc92f90ffce30dd452515a6e21ef7f2bf12854739aba274c52a3a37ecc76aceaaaa67447cbe03622cfb4d1388f273ca7803c72c05a0c1f95d4794fc7b440e7d9155f2d9fbcea93e75b61d55a9af5755a4ecf72df7625b289ba7b27a70978ad52270a5df35c06d77725a74eaf9370469dc5e566a12458fa9c5eb6260cadf94e4ac2d0668ee58a013ec66f239a9851c5742fd372f466e7ad8e2df72e491b4ed5b6cec4c1b98d7075f3fbafae893b7d298c33bb72c9fa8311edae73d6fb837b3edd4f6691178475bb3a33532ccb4c0b64c095cc725d6efb89414cb559785336aa7db3ba88e0c15a23f27aa7835c0f1f2f6d47351a82f842e3b22b6b8471c6ebd2ce45836cfe869a9f308e79ad48bff32c99a3d872ae1c91786a8e8205ff801f5accc5673053d48f6df46d2cd9fd4691bbca279d727e63c8a64d89029c080265b1d09d636852ace09a5b7f56c604f80a1724f2b0519456b71e3fe192bb19a149e3278c854d64ac2848f19a5a9587b073f1b5a545126131cffc9e87d2bef3a9ba1ad071ea56b1600e71a4f2837d7197eae212f09d5f8eabca5ee574d6243c186a4b5d9e14800fd6b7e74a766b897846a0fa164e452491829a19b49e586c08b224ac51c4dc78259f80c0e1227fa22ccc0724057398729a8b4af17e7c6f58cee9eb9475dd75d85b2e10d817bc21be4dcca83defb2b22ece33026b5e6eb9832ff19f05b6aadd9194ae1e2918f99cf214c489251d8fdb72b12138e4574fce4559c00aecaea77393a86aab54326714e1d9ad60a77125c572f6cb05ef53e4a0659633ed48757f782686889784e77d77ae87de3050f47fa472ce2d5b4f3f91c952546492234d9ba489a4e0f5ebb6b8344a8e346af6d8f9e9720af28e4ffa6c53145cb570a20e08741b569a1b1848098c3947737140955c2d72ae2ec0bab6dd8b9109f57a006afc5a1f7177ee63baa2be4091cc85b4f402fc58ee4d7b6545fddce8d9e947584673c5b4db6769a4e594ec7d3efb12dd7c156872c19874cfdc4c0fc55c777ebf32f6cb2b5ab8298bb906445037009bc22566547208447c44af9761aca8aed5484ad443924e46ff081c816f5854c5d20f11fedb7250b49935e38b50ce12db03bf0eca496f54665c4794ea3f4cb5e321b9d74be37279f4d8f8e8012ee533a9d8492311b40b61be10ab1a2023a23804d3dc51fda872044788d24129b06415bd551954f9b83ba6efa7958416f263a6ba770632927672ef9717cb7ea1bbc3fbd6d624867d1a2cc5e9e1a054c7e69876d51188f350a043dd371f13e1b315e22903fdfb3d80c16809683503f626be691ad85cbe4a283c723b03c0dadf9da4d112025c0fa48b8e2f73c5b0ea9434e5fa78b30bb84066747296716658235ee9d11bf0026fbc802c957fc92d5d073e922ee9a9e90297e253598f4c8275a58dbc0bd727d6815d3c92db7d5b6ff566f415a555b4ce8d56f0895efdca7507e555ffeaa996695f53afb1ddbc41f6994a0923b340cdb47da9b6c272725b40cd92d84e28af744808927671bfac7639edb7ebb70f2b9394b5850dd472b9650ab4f37c47800cfc5dab8339b6c6b27bc50bba470702577bffa3e184aa72c03741130ea14173c1334e632f8c5a0969249121eea421627767a86356b26e72f958a4c43964b5e3b191631829addc5b3e8c37404d8bb92653821de1b9b1c04caa72c47ea2fcde002ce1c6c1c9910f32f71ebf6552c90848dd764ccc8ad191722ffe0d667198f201e41a502b2b73c7fed89dd033444661f7c51f05c29133802f2118013ed3b0119fb1658d32cf927cdefb4f9f23288f7e9c1a4a1d407a14f53f4c63d0481f4f6629855d08125941ce8f20d1c26bbbed8b09782a694dfc2c6c7222b22d05e0aa1f23d1533994db99fff55f581329585f7d8cfb61b79a986a6e7239bc3405d765dd3d3c3f25c6c14f198e499c138cf7cfa85498b19c2428651a7277848d4dc9b588049133ad825ebb6abd84cdf045f3a3bba2000d1607bfde2072d0ac0610373dda9ea78eb755eca239d48cf0207428d94041fd35596169c2773c9350bbd11b3a4a709d31f1420e9c38ffc8a949c404bfe22b2411012bd50b7272163cbba397291cdd4c472a67804795807a93df009600553e7889ce0bdb522c0460cdf125c90b5c12134c4012368fe27fc55d9c7c7960a6534e7d96aa93d034350881aaf51653bb227e22e2ffe4485d0917910e2460ee2e6a86e106ce12b12c65d9fbc313d956b382c610c24cb5bc444b4d6bb5766ad132235955e83dea3b8e722da8e9efc0e5e249649b267bfc572a23e93f424862222fc72b4aa1c21c27a01abd70566de1426263e4d2c5d9d2233d4d75d32986bd9a139916be27194a98ea4480dc3d90413545070d67326456ea3b7a811a83bf1dc9da43803122959015a049424b2beac782d78de9bfec39cdfae9c46601f0a600a8f690b198535c03b6835b74b61fb425947ae4bdeb2d31fe84b0d2153cff8b0393b7f0194e0192bd300f2c4752ba7daf6bdea1282bfadcf0f9075886684154852ee4cde7d1a36ad0e2b372d4af3fabbd96738efcc60b2b09e976dfcf02c623c9de848e1db1b0321de98672ff954b2f501c97dc7b917654c89226e72202eef6cae7860069839da959b69f43b602a26eb90df3a2ebbec0f3d4b65c216fb99b08246d7ce1679bb084fc9b1a7270e46d936eaf98507934f09fc0405b9f49ff6b4fc04c5bdebf1812f75d132309c6c95e3018bbd81c9fbf84e56a35506c51b747af490dd9a84708282c85395a66204504291b319d0a468f78f87306c231a7e4adfae824cb9dd1c5c6049b2787728c0451f7a701a7bf02eabf5582bb752659d8b17ab47ea76e9e5a8d8378e88847b2f699391c417a2497fa9376acad677a24ec1742b03705403618fff333ded572bc0fdcd3660a4ccda7e076e2bc4da31dafb7bbc228f4a32665010f32202960121ca4af76191579128b39c487ed9a55e0f6e953e49d497b7062e0e855e35eba5f834ac5139af119412f5d70f81cbc285eaa6e9ad2d3c2ca28a5023b94ae1b911d0c5c9be8e5d907fabc0e7db00b0c722cc882b627cd4805b01598b814d1144572800486d9e8d4873d8ab8f06d7c0b3eb3b113cfc4024e3841c9326a220ec57c727e0a5a2e4608aaea045ac658c9ca34203b1ba6f1a907e71723d609c174c4e3400ee2b5df1f24ae426519b0cab68ec7ba3738b323984fa990ccafda395f0e957275d90d83ac6c67328ea4117dd826f8d732f5841c62b396e1fbf66e9f6d627d189f6337315b987814450e35c52c649b6e3bfc2e300646ecb1d60b0c6d293a166390e106e90e58298fb13574c3cdc3085d476c798308fd5786634985d1317f9872fa1b863408612a8ba523b289455796802f451596147350b71a72d0f098b4067213a4c21b598cb9225bab9360ee3a080d1e1c6b74ba0c9a9702de9a234116da4383ee02ae8378f02add9572babfbc10db5da06ca394e94258c89190d8f3248e1092b9683a3a37d48d18b839630294f01f5dcbb62eb4eace220013042f1bb08a41b523a45cacad166eed72b3f65e5ac755b9df87229b04921f84f0560109e66e7036feadce1ff8b8bc301855b6a7490f99ac4c75c1fefd6a2d4f364561a7ee187257ff1d936dc73ed7d9e2252a8ec3a197ee5467c9ad8ffc142588231c0e893f027e556dbf02d9690c97201d210c40dc9cddf3129b2ed7855b65fc85dfe043747207a27367f72bbf26dd3c958e08d25afc3c374e4cf813da39982fda82bb027872e8ecc2875326fefdfefa1011f9ba2ac2f06df78b3216aecd150f5a2820dde95b7ed7c374a5ec28269d0366690667dda0250ec18118cf93e2b12dc4f6a67907720c87af1d760345d42d1b508fd7e7023daaaa50d690e8a5b95ccd0367581a18721ef4c62b98d5ef73d6db02c25642fafe23bddb951dabc2d50d0ec1409456f072128d625126713e0cd20c0e9eb1c4ffc56ae42bab37c138290c16caaba20c6c727d4f24ec06b6a78699b0b2f42c06f66eb2c4558138c751538e3034e8995279727b43aa8a2e5efb3cd167697b39735bf981b3dfe404321e7666234276fd1dd9723aaf283f7912972e0cf72c92373d1adf1608224316a0395cf6ca6aa78639c33aef8e9f3480f7c372fec7e9693cba5a3e81fae2acadd819093471b396e19e93004c64cc680e6edc508c0ad2e1c5c406e80f4e5a4b3a62a35e1be0eb32b083f41c147495a838878dcfaeca6dac5800bcd6f7e7454b472253b23b79195d6a1106176aa22e0a6392f22f15cb245623dc15efff3120a017d83f39e6de2b2b483cb852e3f15ca774887f61d563096f218a7285c0699427de7e99e497272d0fd585d872dce9eb09fff5d2415e60174a9e161de70dc5abaa9fe5f08a6d5ce072b1dd0c7286b1f2f9a66a784a9ee185662d44be4df7887269fad906c3615e15db6d4b5e22cf3a401107b948a676d1f235a923562d77706f61ba2b7d3b4beea19a5c0fb81b6dad94b3cd59a9f53c32a9f95e2a16591b843bc65ae77037795c235cdd35a91adfa7b446018963b2b42972ccfbb56c9dc1b3608df27d854a0b6114f6ba302e727756cd731bc25f180fcf1368437f3caad20f3ca951d240cdb340bcdd67700e725adc32bef268a78df7dcf6c5d140ad1df23d29cabb11131a82e2c0c900500f727b0872971be9910c13e0fd015c2d5dd45e1150b79a3cbcd9e61d15ed8d81335a629504546d8b99d29e0bc8bd55fa7f829a528c50ebc0097edb797723a2b17272cd6bc584b73ff608df0a53f22c4406c86fcfbde063e14d28df4ff2c01ba3141037d0ddc2981f44fce576247c007c3ad2f1d9328e10b2f24a0e5c5941faa35372ca3177de71858c1a7adade1b6ea40eb305292fc1acf4d68984257b8662a79972445c9953995e8342554c599454d8d454e7be8c4f21e1c7a2c5baccf918be4572b7fa914c3e66c0033ff474d4698ecf4a2dc19e5967433b84f97bb35aa03d8e7282179ade802a6d775e4494a4bbe11f4272bc65dd0df6446b7bc4dc11c14da572d1fe5e35aca40e542d4e7882bcd11942ff2b367a7f568c585e5cccc021b8c91c4138f045437f5fa9d0d5c895ad8b613b874c22fddda9ad46acbb55c8e10c6f5c69971fd980cf9adcdb1e8c0fc5727ba41a65cd22d5f47f34429beec9a9de9b4cbe3435911f09d200a62d9fc43e103696db79b98be22fd2e194e90b2d18570672319ef9fe3aaa8b705b7ae3fecdfe1402aef868bc2be7577c0350d638a4c10f1163ac63b1ce3d33e6fec17dbc7fbb45d70ca3222b785d8318f62132aaee8fb972d5c0ac153b3e367dd7415dbb4d5924b995d229c7621da2866b029e52cf009c729e7ecbca81f6a6909676dfa0199d89a93dab47e03356711e722f729af5d15c11a3fea86e86d4f678d9d011b857bd20bbc95456cfb0635e0cc3511566e839a46dc96492d4b7a61d76d9af9c2c7480937544517f5ca40ec39604b77640fe843c2647483611e2d68c39f6e581e161ff39eda65ea7e42562dba49ce857b8098d2072d9446199f533a61554d81ec164df1dba2645a9af3da70e4efa2b212ea630280355d896540debd46cf37769bb58c5228a230c6fbeef8eb4c43134bb746550494ec138563d013a7c407307ee476ac49339271f258260137c4ece56b2f82cac4e7293cdfddf64a462fc37cb9523c8bdf8beb386dc5fd82d835d39c8df89070e3536befb5b8517770baa324320996ac5262db50faeb619980cee14c80bc27ffcd6729f97bf906604fd2cd1db41aa7b0338d1248c310eebcb7ac6df69aa65542b7610fc0b17c58a0cf36a9758f5f83a4e079fc057172c7990f659e6be59b3806b9e3e46ecfa7af334feac9612f6ba208d3214f8cce129100cb5330198d03527c3616cd7db535af9fca207988218674fd2ad6549128c1da931ddf6da400f21d6f687010394fcfbfdf0219cf9d221bc768ae273c1cde79dfc02dba3342ed2a3dc76986ae7d2fef9a554707d973a8f848580f07e46cd97f993578a3ddac1300f3533835251d9fae2eda2118315ffa0a100c5f93ff506e29a6f6f57c48befb1cbfdfa68728090f75918d72ea9b08cb6ac123361d86b7a0e6b1c593193b4ff6cc5d7b69d72e77656d4aa3dc4762ed7831b0684b2f6a30dc4298eeb4e187009d6b12bc228727c7578d861095f858609583497b45909f06133c7e4bb67ca307a7311f95f6072e6e71546e690865b71de317a0e2014112477df9e382947fbfa0c46898a2a6a722d8e451c65afb93df4570f1f56d5326138772e80c12cdd8b9ec72c56825efe72070bebbc0e4dca2df9a024d0c6c1f8932a8e9cfc9ff650f9555991c685eab408d49377e5201cd11e85c5395edec9954e2fe907950b3202f651dcaf929da53a46703a24a29adbe61f63fc813bdebf654ffaa459cba7f4740c717b21f99bbbbb13fb2e883232c573ffcd5f35de39038c6bacb5cc119d5d3d895e708d133745e7723c1006d5a2de3c84261a33c60594284ee21c2d55fc5653a465393961b7b76556e0251f4ad3eca31461938f2c74316e8bac3a0c170392aaf64f24e93c5083bd722aea050d8200f8912efd9b5b013f715b44b70a8423dcd808b6d6dc18a7f7f32deb65afa45469800839998d18d2da963c1ffd71515f3fcec52e94308e815fc0723f0197322513cac8db2d4f4eddd531022a279a72145f6555462b94442eec66727ecf5c1f646d2cd705aa888850793b4139b611b03292ed7d65563e23016c9c7270b3bc4a486e8c8e7dae8050529162be099ddf8f92059b6d8ef23ad53e77594a82f940dea4cafc3c3ec948e1d61cf331daf31e4efec01d1069410b9daa40d072292284ed57be47f669a3dddca29a37cb3bb1b62ba9e630b1ff71b165670b046efd9faa12c6e9516ad67d0132fb53841c12642eb6cf63e79fb51e1ecebbf04e1858b57039fd01b61bb56bb1fb78a6a232c32c3e00b465dd7dcd4768f09cde554f807d78f0b9ede7bc4855c01095efc3d655488ce455aa1930212dee4d0dcb5972b498a6db92c632e3be011a3d73c3e2a2e46694bdaaca0a3ae2273a5f3ec6e037ff8c94ed61a0a8fc5f6cd6e17ca104274160df05513cc65cc755a34fbdcfa772c974b95914e307e537884c095eb680d2e8e6c435a47bd6728f0eddb556714a726c1b115da3a9ef91032d1853c079ee9a0e73922bfd14acbbb4e762157911237222c7e6be86cf0f470aa15b0e207a47eb517b1ef5bf22b3985088f1d7e00e3170c53fbe6615fc156fb5e51b7d5133a4838b01679fb7280954d8153b4d0a67293c5633b9d768160e77bb79ab5f90e4d203f63782388d7a7e0613fd80ae259ba272d739bccbbd3b655605f8cac338259045a98ceb7a9933ac8b7a183f0a5a5a7b7248943f46615d32f31fc797248300769a96493575844efd7b02ed58dfc98a7b72324c79ce53c7073c9e1c54d49ea2b1f9a5bc74f466a3dc5d90f805881a6e2772f4a96e1c7290c0b77607af4b6e766734e0c818d9e34e91956e19f8336ce44b72802505b0145820cef8b06f3ef5e2c0a744b8c2c4b27b13db1e8b27248caad272695d8cf660d9e67f0c702145dfeb447209bee7dc5c4b9703a2545cf3b4e4ea557a1ab9fa833e1a5ab0749c1fc382fd87c3e54f5baf0c0cfe6618c99aa1b63472564b32b45639407dd961c026e18239dc9446753b1570a80bfd50949e6275bb723065b99200e081859d6c565177ddb6e75b5d1d8ef8a957466e080e35f456f34b8a994c0f093637eca76a303f5def15ca82b0a13e53137e98555b7e45e0e19503e5c7de6148d97826b8a39d045fd4d0ef4bbbcc47f5d69b0e290c5c7b3afc977239003a6f4cbd779adfaf0453e4ed78eb1f701a1bf508e4ca8405317abec52872bff450ed7c49c081fcb686c05da2ad75a9b71b13cc2c8ddd1660ab7f9c6d3c140c3f08eb3954d6e2a0702a06c80938eb3eb1e865cb26a0c5a49e72075b1d035914bfd9adbd71c4079f82b8d45a776db11b5bc3d85ce85aab8cd39c5f5b2971727eec74d59240e3b42d0bd9c6f44b7fad2a9fe494c69f26a55f0368b8dba9c872eacdea674d1215b633b3daac0b3487db40ac8c52cd8c68dfc7dba36f945e430809954229ef740a5dc20bdda9b2ca3ff38e8d0721ba09c3520ede8539b4fedc712dd6da77b8e478bd2585b82e8b5ca633a30d186cad86111bd54f644374080772036705426cd7a1eef4f138ad87d2257b1c37951d79f11c1424b6a823ed92520d53855af43cc0b537968cae16164d7d82e367c77d2590aa82a0bb40b51e7c3072c6c493003bb216bfc8532ef2bf8abdd3c227dd1b3b67b8a931571a4e69ff7772453f84fe3337e75ea8939924e32c64f343e3c3bc4eefbea4b19207d9d048401a2ec10b9433a5a466b13b6a59d6bf3f6fcbdc7660777640ec6437c75d16dceb16c147920504da58c529b142ef5787a4ef4351a46570a8c5ab3ffbd3882d2928246b061ddf4309d6d7691626726f5b8949dd5dc9325e2ef42920fcbc7b11e069726fb6616f5a1c9d761f1cdbdbc06b7eae7b6aa81a12208872de7ee10ed4e65a72f5b4a21932027538ee0ca43cadae83b79fd22f4beff5fd861fff8f9c99961272a12c2bf9ba0561b75b0cdb615f5dcd517395a1495bd8680f678da8c8d509d272c95f0a166218f411d0779fc253c66634563edb0d129843c6ef53d0f582a0566237249782dc01f2d9e4f3eb239e5c318104fcabbeef264fdd6b0e62c862759b2dcadaea174ef5119bce5ec92a334f93da8abd22cdadc2c23e3179a285f6b300082d3c9fafa5f82538d41960a63fe108f1512953f97a5cf57b99827d8840637672b4a72dc44e124a2eed8ea638b776f8fe64125fbefc77b9bf59b25410e0b16f72f66d4633917977b6a83e3cb6f98d447210906c2dd4968e3873c3bf9a75544b269695dddd7544047c88a995e33c49d3883768f9d489084308ff5b3102a506dc729322618a1d8daf0c690e6d1fd62bba321a8f027d554d4616fc9f1cf5e2bdff59e2d2305c502eb1e865b329925eb30ea5cfd5d082e2ca58b8932125432767f167e557edd913f568f298a54c93da0964a6747eacfef4ed557eeb6f2b49091a07724e897ae8c39c64797289ebc1adb37b0ef472f5ca110722b1b6c4b41b0fa63f7226f35b1250137213da89280ea780b65cd3634b9e611e12e10b46e7424fea0b7259f2177c0e8f14f019c71f07b685267f8d60b78be8ede13b8fa3e6c1fb927e717c9c667b5ce7a8520024e1f9a7d09d459eadfcefaad419f868d430a16ada987213c4e2b490d726506f439bfad56a60d763a08cf6291200bb3da4c525f18940725f00b0806d049c2e86735f1f2bc3382e7e36f4b6e186ddca4c59a3cab44707724c04159196839e9c93f194f6b4bc79236eee3276664919cfecde59fbc95d4d7214de1c25089ab002881f5aec257a91fe18821f5a27e15db5ad001da56fe9e40e4d61ce048ed3270e491c44302fa4483b9ea473089fe2557f9dd47ac00993e54e052fb054190621a9664d2e4dc2cb3e2f01945c5f539d4879c175541952bf0172d46a0006982662abf6a370f425224747b0906fa6f3b782b343d48358ed033672f17ef6d45cd15a5fa47591b8c16e095b578d723fc83ec9371615969cc6d37104435f46c5c8b09b08cea4dbf431f8e200d6b0a88c7244f3dc03e1f4c92e084b721bd6e11aa8c2f9c4c1fbc7260a529721017a35717b79cae7a4ecb6a9fac3212abfdd5d6a46ec713cfce4293fa816c87f9fb0aef87562e17af978c36a5698415f6e820862c9fbbc4be1a652decaac513d5f347b95fdefd36ab1ee1fd56c21373697ab76d41d89f938841912a335a5fa5f9beaf32c8cf2980132b7e9b05c838e72f2d4f5aff71e5bf2560a2abf53f1eced79ba36ec7594931335e20b1879b60a48129de5dc121d577bce58ede0d828d112b72d217e29534619545adff6fbebcd60e3ef0bd155f9ece9999b8f727cb3d8e11d23b269099b050676c1de5119f714725b6fba2c0f4f2b0b336413c06ce4f252295f487ce8fae25676c86347355a7d50c3cccb812add48b59dda55c8178bcdb9871ffdeb3c0dbce8a21c3675febe302379d8827788b002157efc2aab7b2976884b4ffe4cb9ba04e6fc1fa1842e617a72b8c64ee82726111c62fa442f8f4573825db40eb44c17385fd111de2a6d83374503da764563ac27f0a21dd314d6733aee22ad21e61fd2d06833db296a82225872b9edde524c1e6a78131379239bba6802c9621e93e1b01fdd5aa9bb49700f3d723b616b90200f536883792232075f1641165d73732c8432b96371caf5447f5272543a9c4c4152af4db17d9a2552fdf0155f4b1ce90555fc972a90ac20e15d7351faa72bc4bcf0dbae8b4a12ec4fedb9810d1212f0668e6557f3f0ef824317197261433da74c747c724ed45675908866b21ede40a26755426bc0da276c0a3ff2723edc336bb861ad1611211eb092e957a9505f98ee7f4304d10beaf91f854ac00be64d9e5f23691362cf8e09fc79d0b9c3760f8a4ebe9fbb3506802e3b93e4e2179f75547efba5c61a0e048ccdcaeb2b3d54a65ddfe1223669456ee33115f26c7220ee64bc47a346d0440201aefa9af5c439f4d94264dd3c72dbc71d418fabbc72b52950b0a9d7f704f0efb0b444623138623a0761238d2fa63bd5532080cd8172f7d1e6dc00867ed3a31ab6aac38b953f06d4b2ad8b8d4e581d93594f21fb687227975e0690d22d5d1fb4d1cb16d33046d50c55c6f50e2e3e204639e5344c8c72828c3ce4067ac73c83edb61573b8e42bd4baf0e316463e8881da4eec5196010c9c0d5dfe54fae1cca167025b62ccbe5d0f69e8f1a93d19f0b48862ea7b9965728ea0054f24d426223f3560d778a493bc49a20054455966e68cc67ce44faee8720d9cb3695993af069824d9fe5b989b25f74e6ed913b82996a4bd069bcbf5ce7236f6a4985cac3620381b7c4d239ba6c1ab1828cd1c4f400ba4db7a5a25474f726c120ab86455390e689fe3a3dcb4c24b3f4200eed6ce923bf2453de17f230d000cb328b18ed58d92f27b2fd24426ca19ef86e7a303cbdf30ae2c6c55e1a884729903df7ce0a9df710d268684208ae940697397563043b05bbeea29022556e96a4a2b8c7cffcf29941fa245de8369fa78d8c18c715ee4876ce44651e862f93842c719aa8c54d30e3297906594d1832044d39a4b2f0e3ceb13c3fdcca492db3972c2957b6511ed8280837a1843aed492fdbf25f9eab5ad8d7b895e8352da88fa0e44da9371b216773706ed8e357e5c9cb2b91878864114fcfb2292f747650f247248f00de53fbe5b540e20bfb15448259ebd7b4f5cf3ce8156ad01afdb376ee9720b6df116458f88f1375d917cce38761ca284d792a0be505660f9d7f66a499f72f62df0678fbbb9f35b86d3814ae3d4be786b7bbda9ef623ca8e6634c309c14724c171259149f452930ec7a2a78eafa64f24d98ba09d911a6ffeb58e61be95f563ce3bec88ca93f8b87515d3ef795716d2687b15af157e47c907e8fd82feb0272c213c729bfe63847f4b810760ebc59a7f1e445284b748b9d5a65d35664023d725b49bd78f31049673a89e4c8902b8d67a47341aa6e0904558b9bad69d045ed6b090bd1f694c538ab4083a7a01428bdf9ffb02a177b89ac454b95e928bfabee72d995a4ea331298e3fc7d27cab57ff632722064fbac901e2eaabc65042958324eb88c023b1d92f431453c126a275b24f5f87c262dbf315f96a5fc3ba4cea08372dc167e66b26511dcee2c1b03c2fa7a28cd43bf547dd54b14891ee6fc98db517203604b3cf6ac41d2716e00c1890220aea7d7987d2bf09c49f400464410921005651412420c813fceec1492b159f2b7f6101fd9f99cf43f25c30621ef8e75e072c96b337c5450abac715c09c9b17802b37673d37f8c0b398128f79e463aff3f729d47390f777fc3af50039deb5689342b59144cacadbdb1f3c12b9eb3e2554264d53c33ddf64e1684a5f6bc181386f837dd4c9475135e6d121a5f3c31fcbbb52bf38a3dc3eef262677723dfbd7a7d6b44abb5df861ca830038e165a3d0235544ac28dd2840fa4189f31305063220e65358b0f17ee93d07e32888a5bae1c134864af4ea72dd9efd560b9b5c120dfd048288886be69f9c2ff9c8d9820b229b64322cbcd468e61fdc10a39e3dcbcfce41c64adf42d8ad900ca979f4bb5dd8e526872dc87166107f06061a0ead1e1a7f999e45d44bf126d889e8490c2cd2eb37b9e72caedda7f27300f9e59976549dc71fe5495d458c00ef5386b09217084020a8372d40b8531020a65e0632e69497dc1d31d743ab65acf0d4232f424131cac2b55726ac5beb96c0f5eaa9967df3bd08563f69423bbf04b7aa9af9235b6433444477204e741048addf63c4ecff8705a912e27eae99892b63d250347fa70e00cfe9837edc9e875d2417afd092e93030912821a70d67b49b9e77d29aba8651cde4a50721685007f8ea41c5bd441d95f3e8808ad889e1924ca867260eef077917eb896242d175ea16082836ea1879474d0ab84117185a07613775f333021bceb5892d8630f1a247c158c7c09cbd51f9bbe0296226f7b020a1384269306c7bb1416e4010f8e91244d891389c61b8fbc57c1b931c54fbf9ff40909698e7b5d040b46037172b3ee5a21d17ebfff75c07ba084cf02463d762c4f9a3bcef9f59109566734e5720d68460c7ed72711decc44143c6de7863b073cb1e44a7950ab4729eb6b3dd3727d27ef3709801597aa9edfbc88e55b323773be8b96b565c516adff6907bdd45c6f0354c200ffa340e2107a7d8335d4c71af51c960d1c2c45e9c2f36ea4fd08728d7d9393aab13cdd610dc5072d825a3e186ae42c964bb8e4a50cd29de7e04772e4f6f3ca5263ecf9efd0a97bcf859693b29eea2bf8985341e3ce7a6917a24a72d688ab1fc7c18c5d87d5bdc39361ee53d24e7c53f72faee30b33ee4661f42238e127007109359f7f798c04ae588c957955ae31337472324765187ff383fb591e17cc504fadc0341bc0e102944dc9c3cf5fec022ae33f4390a4ed38a8eb6a09681e9b2680b7e31da7b907a67fd6e2d24fd1b7ba8f1427ff156831dd84ff039d729242b149b7fd045e4f7169a79fc3f33f76f810e03d32fb71770a356b94ded16333e31d1c76a30864f7cf92ac583acf384053d51fe909538f631bbb29e9ce32726efabbc1383ccb857a0db0b458f2ac92a26bb2713814c6a13af3f8f6a37c2a720bede1cc43e95f201dbdce41481d960c2b5aefafc087c6cfadad4c274d1aee20e0ff65c722ca586563708830a6c072751381eaf02ee5441658aac913e736ca7210f4c7e2e73933822049237434b8f1d248c705fb0ec7465e016ba28040105f7235d50a60c9889e7e94c7e6d0ead3a4a7621068108a3964e209edc6ce1e69181be98888dd7e8a3699b62da86b632ff124ca0f290a9cd1e2d64dee177537671351169676f6c6013b37b464b5502c957f7ca16481cdce0674cbcaed19c8aaebae164b1ba5b5e3516665bba0867c1635295287d295108592ecd2946226a075ec49433d528d2aea6e38f8752f7ec438dd5d93839fb0f22a4562194ce0a84c4b0b6b13870f18188c0738f7f5da627f6550197484ec16197c7227161ad3a64be577e82f17740ae3890a29f73428ce8fdc3fb9f6975e1a321e416226f3f08f56fd42787267e74b7e6117714ea1319ff60211659dbe578f02393065b97d2971899acc6c252ae0196b3c4cd96e958a8044ee8e146141d15fac64672a8435e15bc34211ba72ac7f77bd0ab71abbf1af21c6aa9078ca3719f4b4cea80cd31df51d080b2a1772911de18a3e3b6c0b8fc6cb791a905ba79a37c98ef82061f2b2b2c2992245fd7272396681f27a06c7d382b32bd42c216ba6bc84bf3959841b031228afddf50372b5d99315a94a8e201ed6d1b5d50bc6d72cd6b2d9623b397c267b7188d337204815f753ad3b57ccb03239da00a42869b369159c0c8591d52c7e7c6d208cfea972b69b8444e8078e4e96f4832d2e9c349680bf7cf1aacb691dffe4c1307a380f7210a6d16ed3a88616e3897dcd72b36e22a0d9a90d25251e217f367a1bb327ab72f6f5651a08b7d585b2a12ca65b014078209cee0950e60cd195208124b0e59e4683a92c286efd4deea76d52510fed1e7bbc53583d5bc3f99af9719c538ca796723a49f6714ac2d1f976a800ce9f8ccfafbb9ef032aa9a551a4b2c74f594907627ff6085a4cc7211ed1f48ad769911f17adb511d083c4edbdcd523ffcbba37425bb6fb66c618248b72e9dea6325db51fff9c0aebbca5c1d3d26133fefd4e79201ad0093785eed3d0a098c4099821762cbdea089d44401f5557f6df92297189c172a46a62764e82fffc0a7e4f2d315d6e2f2ed2b0047c155cf55adab5ad3ab07d72fda8a45c826cc6297afef387bdeca2fb9a1610230548e774b0494c257b39c372af49e9ff3043f92220183da68b253145861d5e20e31d8b5967f1a84b28861c4d8812a75cebf70a5584319194ac721b69d91bbd49b065caff3ea3838c4f2f5e728a6b4789088dc327738713717e717b86329875849a3e88db09b3fb83921eb772d162e537e4e37e6a06b0ea9a7271734eed7753d34575b9837bf18b2701caa90a25747b5770bd23a9d19722cf4c85a54bc16249b19a6dfe3f6c05d6d4a6e78a44475f3c57a1104afe24c9d942e0aed0d22e05a1946379db3810264a3a09dea472cecd330a357b934c211f2e750727520bfd7606bc6090e8e098a768c212b8331ba9a3c6a896a19e42cee35f4bb2937554400b8dd695004814b769064bc4b51e72eaedb1aa6ae23c8e4a5e1dd9137b822326abbaf4642cc2c1d1c3f4b8ebd7f348c738a7894acea280ee050b11090927a9b86a325698ef7d476fd6c39a69edeb72846ea71395d3740df85ffcf23b37c52332e6ed06003535f608634a07abd34272667dcc60d80dbc9b68eb20e1e58fd173c6a26c7a7d6c8d03e8795e19b4a316726db310533752a1755928ccd46877d196f4463d668b4325901f4697bb14894a72b7cb4eb1ce739ab128505910655f3ad65800552ea2ae7c2b7be3607efa77fb1e1de4f246d882fafc17a0742a1c26512d6ad51fafb5c216a4eaf6088966e49a184eb53c2ee517d171033f8cd7396a4445e5e4224e34894c5325252ba274a6247205090684a0087349360fb2df62552b23c3cf47925fdb15440bbb9c4266ef9372ebb716ba8eba21b59e9f1ae70a7ccc2068ec6a0bb94a0d773476596f878e2f72f8d82b48d0f1d8263414c025cba4b1099ef6d6290322c52d169a2becb516684a846b268375f1d759f372ae74503d17a8a5df56ce5feb0230c6d3e24d3dd66f720ee5824e2db5d1de435ee15d6b5d7f1fc50b112af55291d0a7122836d155c05a7496418442efa320bd6f75011a17217eb08ad6cb1e66b1f2460b80bab1844372d7be2a66cfeba31b4f9b9eccbbbb691d6c30933ef3d21346ed9fb993d715610202bbacc155076f7016e7c02a098bed82428e697a59e3d02ff91a7a6b4a17977274932d489f97a6fb93e07709ada3ea65058fb4ee89adba867d2274221894087273c1f7b4182ad9c0dd4ab8d65b87ad16cd6ecd124dbc73f070562b2708246772e884193c4dc68d88181d8baf9f55ce758a3dafc33258a2ec4fe741901007cd7275097331757547e2e48db09b74221205d527131e7786db5ae55491f0cee206674b4a0c63f2b8705ffd975e9b0015d97aab81e0cab94c35956936c3315533f702cb70a28a400aac92276e4049b3a5bba0d325df48ebd33c052520d856741fd0728c2815798b12a030c166fc1d20c85f7707bf104483a34f61e4f8355293f28072387c86d8404a9ff75f126c6e4372f0843be565822f50e3e304b953b5adb5c272f204ae224024102394f912e32ca31b8e041343e0476dceab283c775f383dbd043a3781f91b6616941456122c999f3fe1e69f9419815eadc5a83bb7a84580b97283767afcb61fec5d9aa33e8bc2a3da9854ff9d49a96360a2c3bcb480a6e622721aa71b1983a9f42b7ecd3930f5e05acfde780fce57e471759a4aff81b952203258c834aee0400b39c3a14fd66ca97d9f6ad6c52a0f3b476846f60871e09f200d22051f336e0ce16e929df1bd7156a74d8250e87a69ba9c7612c9af3451f71b31433c02a26dfb7007dbbed8b5776967b7d788f4dac0e3787dad5e759dfd0d2a7253cbd2dcd98fdf9bd06a2e8187af8ad492a66c9c629c8b3ebbc9ef39454ffd72a1b9eb5bc83b7180e91c87f0eddfdafee3919cbc6b1e489677c701e775e65972755b3f6c492c8f4493391c973b9092b24d2e97ded9139cfd319ebd722fcca8722a82fd7bfebb67125ef9acb7673244a4a509a64b40a39bb7e223b1efaedebb729b22dfb2d92cc3493a54c4bca42e7da69243969b4f745bc63d608524277f78724797cd8fe3bd310624b3cf355eb8de6bbdfd51b68ae3bafab7cf1f7b6b5bfd72c4924e7a077ab078ce69867e326fd6c7dc713c226b657d71bfd32128289919545dacea2f8c38eabef9ca72b0392529c52416b6560b6fb67e10b0c072f15c3a578da837576198259f19151a6cf505a0bd86de9842e94125d94046bfcaee28e2721922105aa4c53ec71660bea721a49f54ba3fb3d3ed274ded19a080b8f3b0aa72825981a1aa50ca2b3e90d8dc6cc304d93721a80789b3a5c26187b7847084740aa303ca5aae65c1af02d44a8e2d7d71ea29cacc8be8a39cd02940a9faaf3f023232b958343efa63cfad042896cb595d3e3bb8be5b8df660e2af78f44137e40c0885749edfdd8acdb498f1ec897aafadbf91a14af2687040160931d86dbabd2472d49f41529241eea0156c3ff04cc748c95469eb983f513205b9028e2b421e545a658b4a5ce33b5781dae02dfd660d04e392eeb8961a56fd36c397c1c5cf55ee721326af91b151516baedf9e376fbd42fc66d0d5caaeabb57bec2d3f1861c14372a9ac49e832af7f2823a392ac692c9afc3c864918a30e09165bcd87bf72685772d9638c10e2b0c5c14d6dec276744deb10b0bafa38664ba24d0881f1f5acc4472a5412775b931cc295601c94fcb0db8280707d0cc1b8098629349e11fab099400ce4d88d19fb84ed79e4f7abd5058253ab416fe4547eaf065e9a3295ded6e5b282e1ccd2066a36bbcb093f8399940f79428fe53d0f6be904b5ff8b8c36e8f1772e8f2f0f9b1f8ed1a16134f0fce7bc63e969166513ca962ba16b23bca6489675c614fc84a73df4f4e5a9bf00dd515cf250987cdfd1313c144ef9ae47e6af05743374c7e1eb28dbba8650909b4584e4af4cf9bbeb86d120b3abd992c4eae884359a3975f45ebdb413a4d76cb019c8774a121216f1ba16731b6583190f002a4b972b97d029fb54f13dadafcb305adfa37f776e80bfb43f348cf8529b6f332fc5867798aa31e8d3d822aa06e284904b581d5968018efe08c23fd7bcd9c2aa1def77259b2aab4ac700352fae2c65e8d336c8541fd7b436758673cfeec729c4c2b0172d4e4bbf785214823f52485950554cf7b9ee00ca63820e42a87fdf827138e6c47673e4f2a00404078e9485f406723a4cc6f8f4eccd2aded3894bd1c6a15635c72a3ece203c3efc2ffca8d7d2ef40b82f812298799bf8b9757569014f5e7791569cdc21ec1f792ed2d80d12cf533692c14541d5de48edecd6c9014e1b0d23778506dab9e266b416a577dc3709b3afefd70d220d360a821477ed6bc6742fe17042c5cdf818ec78b95e46c77f4555304e60911e9770cd69605d09f91796d5c187272fba34be5968f561dcd7ef693df0e14660da2ad755a20ae4ea4fc086b3a387372c38c965dd1c6ccf760759604f73b3edc0abfccc511c11b1d76193c6473fe05728cb52c29af6aa6cadc17234bd346a2a52ba3517e12fca7bba737ff5fab8c6472725c92f8e2733126d2242524726519e5595d66c9e78b02c433b2bd3ff5140f72b4520c21284c282e8082f24d6ba251447c72a94dcdfa3294be4e05c337e3b05ad3be542fadc4bdef42afdf0870ec68b35b534bc2996150eb450cf18b85fb68723206a122c7dcc4777dcc2a54d46733f71d6cd2ab6a5632c8feffead2dc28c110fea0e95ae04f79b95cce8ac6f32b26c14826107db14139e906c517b353f9000071b21879e2753d6c3233a3ffc264e65c03f8d19b0e81bb6ae554474c5d9735729c76b1a48b5e767f26070aac6ad05f0366c5866835dd37a69ac251d305ab893f85330bb69e46bfc6af9287900b9a52cfd939d1683aaeb58e459a30e4542ab23566aa90bd3ec007a3aa684c023ca817cbd37fcebe0b72f8af4a65f059ab57500799e4ef1cf7d1003d529213183173f21b5b5abfe9fd1f7aa03a47aeea1bf9da72ac701303b15a86c707fec8824513d6631567a620788d4fb34996e54923f4051cdc7f0b7956e43f41386378d46d36b30987f563dce608e51ac7cdde9d6e541f59a948215ac1c027b9f0165691c8a68d22ea509401b4abd0f2d40bed8e9a4895263045d5531191ac309e2d196d7a54a77a650374d39650b91e020b329c077d6038320af9dd40aa875fca697249aa77852bdc7a192e87f898fab54c01244d0cc4726de31647e18a3caeaf0c9d272e11d97ee4dc9bf6efec3172a28b7f631f753972d55fd6f5de091137da537e484ac7fe1812506d5815026a16bfa29d797db28b729859c256d8ea05939d5661964d0d4df628810e0b82680c380ac90225951c3c7123ff747cf72dbff26b9243c1d493d1724d18cb2f79d0e030bd6f84a2c45da9725112098b9cef703d14b0e379107784971dccbd9de295bae314be83ec10e121726d969843a94a6fe9ec874233aefcbb7cb8073cef4d51ca43df5d66ddb6573e7262b3717db6aa1b947ec418dc6974f347340a9f0ff57d86e1146d365553b310727a8755d7134ab2ef2fcdc31e0ea776afd24b944d863fb338721368a64f0b7672fa3f5c23752b60b7acd414866a2f8a23bdc44071a1a0d2f2491f4b9b6d7f243cf69d53bf43018dca6331338bbf29e066b9d92c1486c64a62574cc43f0954365a0393ea3847d740f35f913518914c7e60c59e10b32e3695af8ea245907fa79d72a50250720558b7ba66be187e4a710df05970b7c7a3d424cf016bb6884fa478726449395ad3eda164adaab51fa3daced07ccb6506c216ae89be327345da189a570004cd7136c02fbad9dff827baa0ca9ecea8462c1199ff666f8cbb4ccd9aa272fc9ef11e826243ca69296bb6483355d3916d77f9c578a31b9bd3946145a49607d39cfabea8c7f50b5a4a1f8fbff911ea3ae396813bf7fb71d97d81313821d772f01c3c45d0ef2891562113e331f58f56c309d8a2759f84b2d94c0e3fb7663303e25433ecd12d73ba95ead74e2e00b860c5fa3967663d83d22b28becc1b6e084085f51c36479e4dd0f61237c7363869cf9d4fa6c56a516add840a8c9dd697ec30a7d5f808fc61baddbbbe1d18d39d30afa403158b57939859e5fee7dd5e9acf6b42fd62bbc35249ebd68fbd3be6b21193cb7d1945d70aa67e507a3a8c0afe0572c66e7344555355e143f538eca9b1a38961c05a77e8fce2bcf7818923d67f876ffaf1e3545f857a7f3de6174a96f6c01f5de9d7e04f09566dae789b65a74eca72f1d14be6dc34fcc503d04cfa070b315e62b95d08ca7190550ee0d9986134486fb0396752810dccd613a52f793b7bcef4af24ebf1ccc70231c2c075bf6758fb729b4c0116370a272b60f845089f70ad3dc0feb4edac325560957d5fe8209269720ea922b27ea0ed1710cebac100bab053a5479a213b37eb5ae410ef655404fd4163b6c4cbdff7ffbbc2f4171e787959b007e2c3261156ac93d6575fcbe16a6469ef23ad9500dc5c9ec4b37f22d2f05f6b839688006fa56c86c46a8daee9cc3172535637d4980bf3eb5accdf13ddcf5e0d33efe49acde626709ec51642d380bc14a83ca4f4743dfb4466f27be8c1264863d81817f20271f2240a012445e255b5721349be7c0547f9bea87f06a3bdc1f0becfec498ce425b8b6d15470ccfe8002724a2405d807ed72914fa72ccbe2027327e966099607419148b65651f4aee40848eb21ee30deccde7c068ce7c5586e4d674ad7626a7808bb7db4d29b45a2cd957286e8d6d2456775d854d4c55eedcfa9f5686b53e100258495319b18fc66119f72f1607643d42ad6dd179dddd15e92ab11563e1e34002dc43a3b5a03d324f4044a523b040ac870128c71dbf2ee24c62403dbde08265f3ba9a530610ab9f1d50d249c55a620aa62e82fec0e723f7aecba0ebf40245a4154600a0b483c27f252d34adf3982278870461d243931f55b748ed1f2e3e32bca5867e503f91f4d60142d01927c8a889ccc22af37a8ad5dabab2688aed42a5d8bdca0c8e5a8e41eea88eb72a5e0f1c89f06e10533aa4ad6eb7ae94c614eb7d6efb580f6378a0690a8fb200874f96966abb6782d018469daceb7a462cb3aa74dc1375501428b585bfd625172b21a03cf0a00a376f33b48e871e92ac648763c83bcdb0dee4134142d87684a303032c69fdc7b50b913bb361d5dc8539fa0eddf77fc932a6f792d5e1dc8a2df72a80d9a5cf49b722966230a5f226f948f542c7186b4bd8f434b5fd92683c6df72cac6decf163d3a304d4df6b0b45e176ebab2f9d7a9bbfab3c36505a185bf1a240e50af7e414266788f17688e1d7f22cb9842041a69e1e9700bfd4420787884720b73953b0b83319ce45d20b1597c9f0b3e514ba4f6d8601a06f201e94474a60bef2edd71abc9ff8e086aff3f6285a6f95061f6d1612a263c0812e77b00cbe9276985bdb09c0f12845600cd48e1d3691b9d52b9d932ac987a9a90c88ae3440b2332395a781fce27f5f3e0b13af32410895bdd7f5e4827b5f1cf858e49904c87728bed336be5c8d50b8021290612d05c862773e1ab7b977eda2995f8dbba5a0e72ee12d68f3fcb717dc0b70b8553b7b2a3ea2b1f2439a87ea1d4ba06b223d0b87220b7f70ebfd2ce2a191af7c07a4c8d20d6cb4515faafa350e4516593e416eb33217055552613477b0c1c1f855af8a1859241ac864c1da3f9f88911ddc637942936fa4410288abfc866daba2d3f1ae0ef5202405aa3a63a2de4011c00f81822613c3f2461afd4864b6c6c7736c73879a16a99b3ac53d4765e803dfa9ae3edd3129ceaa20626b2f667b96eccc4a220addb829ccc24f7161b56a6e9609056c2f772f0e0b7bbf34f3a3da91d1c417d0b696c8be1063b3bd393711b02d6653885f34a423200370c34e99f4705ee92543b60d8f4690c463f4a9747e790663844aebd33e0531fcb99138dd358cd785249cecbfb57d84c272f72cf0620554dd4f9fe9a72360fb2f690bfb1e00ecfde8e36caf518abbb3a36dfc2d38a06e255f02f5274702a8b48ebd31a9f667575e650afff2b9ea823ba53990988f966ddb8710f5ee072926115d9ae8695abcbd7273f61a9e1aeb09b82efa0fe62d68350dc1224b7164c96066a82eb062454a79815f46a5125b3ee90ca92214f6c7e71860c5dfe07b872d2d067a6d07de7f6bea90ba8290250c6bdbc1df16aff34e0a38b0cf26fe767728c487c819ecd7646e6d07188aeb218be6357196a165b1dbeb7281f76aaef1f07d630f1a5a0d6b3be45d0e465e6127b9e9bfedd4511e559a4f1c33db95333c672984cf9a3addff6875f8f7d15804ffe673b6f6981cb29299d06a971e6b8301136497cd6e286313505a73d2beef0a6a22699f79189f9a73e968f8b2e560e087326972afc019226658b0684af0063b7d74b402884bd0907fcf2e3921b9f65bffb7266dfde62b83f5b57de827c7030cce65fb06c2d509445a172fa44a4ba74503d70691f17a99af2512ec57c2391c26f73a42e8f42316d469a09b1e73c1d4c7f6372e6e36e8f422092796ec81cc2698afc5d061579d4038919e786c6406d76f26460d72464acaedffe7a6691c123581ff9d58fb864bf582b0943b3ced6842a1b3a72b8c79a66906f457810de16426af17c89557a04d9587e01d06a15e3e8973a0972da9794f2902a35f632ed379fe3af0690ef6a7558d0b7c2016ccb93898711b272d18a15b7c51d231ba55e3c38d0ee285194848a5da7051cc621ce359400886172beaed25a61c807cb9c28f301f8ed1e6593d78f8806d89146536ae7fd7e6ac50648e34311811c0ff9deef533558f4c6c860a5e74ec3b96ca20d83cb293d1e426481e02662623e1f18715f4ae82c7a0aefa444876d914f6a59039f6508522e6f38b58cd15d6931009485bf4c76622ed084c4aec7f39811fee3212b44f940848272fed3faed65eae4191e27fa75fa85498c159d2eef6de423fef933d0ae0027a7729ff6f8978e1b7249e6ae4f57e03f22cdfd3f16f34638a30aecbcfbe2d6140872d6466880cc45fb4d0290bb46dab9dc7db3ec2535f4a57391d8640cfb5164b57285ee94b0c787b92b8260f3e4eca1bc0a43c5bdc18b179b46bc4e053a4c6aed6bb66e8ac9f2da4c71f20cf864fe8647863a9179a5d4b2002f6601b19645fa5e5fb88fe141873d76665a193a3556f370599e760b3684ac4723eb7d15a50715814f98d10072cf21d555d3ea021adf4f9ae9f4ffb622cc799e8ce620cef71504f95754d003fe3c76551b4a3ccef42435823b3a478e5c7f04aeb989fb9b39a55a1e721044267498d81decf715590457b83216bec906b8cce9cf2e94f9fe4e39964e720ea99237e4d9579e2da0e676be72723ccfba250cb90735d4def6f4f00e41d172a9d861ada74494a252a30405eb8be9241e7fc5db6c2c71c667d116aaecada932c7e3778ad012b9a8a42ee38f3726e2af47f0add85a6b912587a2fc296c96a572b01e2217a502522d58835e45d70d1a19c0037cdf26d0278a71047f9eeb6df35cf78b0274bbf9f991c7146cb4a7363db1d6a4cff77b9c968e4d98fc99d8e6591eb3117fa09b22ed766d1c01c37768fbb9db3a3bac17e57fd90480d1b764d7aa72061ad1139001babe5928f23f64c2d180409f0792bcb745c171e55c9d4d018c729c5de96ba7096a90959d3fb4ccd5b08306de35c5e21f608cd6d26b4d756dd44533cabc9fb2d690d6232109da12fb3d212605446ea759844605d64901d450f27234b3128b2b6e606f80375f7975fd22a97f9b29f173a43e30d2ed93c84ece0d2ec9afc6ae9eafa479feb7a6e90ce09795552b4657d5438160608b60a8667aeb72db11585ab254fd78a538617f3668e05626a19cdd8f3ebb642b412f0f5a70540d454232c9af8bac674d7649458747a244143b374d4e416d7f78f48fbb30a0826ee2d63ef8e3cb7115540eabbcb48c9c10b0ee83d6349d2626330f8ab632417a7220ce0144cca882813a848db18951c9a013d7618117113d0f9bc53ee57536cc720f927652bda96c6d8ac0563dc20b4cff20fe8bf48ac176da7fadb68e59b467724f47ebdf146c3ba7fcb0425748fbe07a9f67609f83cfd70838a8fd2e306d5920f0752c1f06bd56f3bfc5dcbaded92acfe2ba227e9517d07ea2ae0ca6b0ace341ec1448d09cc60695e732bbe508160740493220b9dbe4f3a3e4047a169da34372b9be0cc7671283047458f63f69cc687a945f1b7e576ab0dff5f029c49b7aa4721feaf8a94fb0dc5fd14457eab0f29e8171b80d552603e25a086a93f766228a7222cd5985368b35f08e7824ffa3160435ccdd51a462214583a2949332f859620a90b6d1970d074b49822c00e151d1a25fb9fc345300318cd605cd1ccf3bb40e727f82ff2bc2f3864f9e01661715daa9ca89bf665ee81d1c6a36801bfa20fad90a29683549bb8df523b198fac93c6ce2eb220fb973f558a135e63cdddd49554b72dffb55ef1907b8e31b3a16f1eb84c3e6aad9d92d428586584aea09af1704d252ad3c7e4d56b2737da1dc559c4ede5c9ab8a8378562efeb59c794befea3767c11d706fafdf6098f9e142f6971de98bebe5f9116ee960fefc3ec9345c867e6127289a890f8f3f3db859cfee78c41cd4365b1cf964da25c1426204cd86682db9970c95db60af1fdbd0ee19210a6bf52e06d3a5dd4c4a5aa3b449fc3fa41e635ba3b53d0fc1f286cc5172dccb75f5753838176929985a995578a4e92a8814ccac472781f66b122628938f1febfd186466da5ecee77427e60e606b4bcff53bd987c67e611dde3bcc25b566471588e8c4004686f731dad19104867f4c786c5efa5b672dc46389e9615eab4a3cd705dafae69ada9e2f86e056fc19d43c2efb258231a72077d6a737ee59a8652185083c4fc9dd0abd2e890fbc8228d10735edc0079df5b7b89bce6e5cb171533d5d345d939f38ff4a11d559079b203e33dc1f683717c723a6a382723ec0ebc3ece0ca83e54e30622fd9a779c02a4f9cdf0ab41376d2424ce85e4a04c4afb6060c108a66ca419a6dce1d98da71134ec7edd452e6f3b6b72cf703c56dcfb67a651491a5cc33d63a4b6ca1c38c70b2272b64a1e0332582905ba1b386e42fe4f7b4842415ba74d2d404a7977f00e929e4aac21a0afc1fb9072d7ff9ff694e3f594fbab1807f6073dcafe5e78484ebd803444eae728d2568a72e61056bf406fa303a2d8ce8fe04716a58a5ff403ed08c4013921401584f19f720fd33a84a544cb828ded753fa7c9ccd644833da1173bd15d6ab1b66e563452728ad0e45ee9b724fafb54d00b7642b5fcb84bea527a49bb98581782301e609472f13de09fdca5a0fd7f27c8e3c1447896337ae99ff158063b364e69ef72eaaf4803105277075a012cfdd41f0ad609b5ce3a81271b8b257f3f84d4585d017c7b1e3d2ac8534cd3218a4d40aa745f1ff5e2ddf84df9d53b664a174b32989498057086fa446b62e36fbc55d678b688616852da523f8e89d90fc0d9dbfc2331951563a97addc474775416ad1ea8422fae688a5d4eab8314d8566697e47935ab9add726d5e432b6a214b44734c695e2b83e09ef70d633de4a8393f6fded0f715b29972f4d38c1a92a443b60fee5de90e2587d644e46b2d7798ce714b1b5bbc84d2ac00cf3a59cc07c1a9986f3f59cf7602a69c999f65fa2fd1eba3c19e21cfa555b3115097e8de1ac6a6b055dd172707788b750223a4f96a20886a05b20657b140b6668e9c77f3940c7ac99aa1433a2868c5d90270be97286fb849340c42a0760cef1aafa50bbd3bf63042226e1e8b36aef2fd6b721ff45dc3d493aafb9d01e5e7a3103a07cf98b5fa345e9cde22afe0cc37f75f25acde6215d312e693c6d724711212d70385ac2dda7deb90291681b86664947816ca78c8953b938bda108dd35a037295a42b60e28c8cb1a232d53b2844b7df2f6e2bd9c0deb06d97855787fe5e624aa5f96ab7d0112d8c1f2ed6d3eb771c7411745b6a3a7daa3f8316d0e7eb18881dfdac531ab708c958e0e835c01c88169ffd930d4eec73974ea24339f0a3f59d72f1db89d96ff8b11ff83d00141bd5bf1be66b770764bb2ca047dc58744e45fa7234beb916da2127a189e1ad0bb4290de76d4fca43f256d085f0fa4f356e649d473ed4ad61133771474c1a4cb65e5bd61c78cfb774b895997fed41c49ed1f813727839c3df5b85b885fa74b07bc49b75b0fd084b902dbcb3ef6e71a8065d57ed720f01a9b76ed18075df0226d0f021e3224f2582b032c573817e8bdcb64212c6720c3254a444b0b37bedc7ea0fc23f9aee8e728517203e4c34302e0236342b3e51047e2fe81ea797d3112f9aff57648ef148a5cb2968ca18b6120b156751bc70726e6f6abd0bedb3436b14863ba4a54418dc6ae27625ef53177eec647ee1d8fa72f4e8dc703ca598733a35381cd09e183c90c4295c37fa9af331a8fd466cdc5572f84e175b0de2787075e22458e8cb8d8af804c79e30064809282a94b9b8440a30576d9172f6734b29c69207c9afe124d75a28f5b6e235d1ccb36c553867cba40be3691cf5616c24e80258c2923354d4c620959fbc5c51442e2369e4e298d6d77276531500a0c3fe9095c0f792b57307345494b36232c7e2a65d857b9ef5d0cd72bbdd9a48df3b35c6f2986c56aad7b22e683c0b140bdb9b9b16c888960be2dc72714857ece5fa5cc32ca326031ee99e82bf04185319c639c6f485aebc8eec222e2be178ef41ed1d3a6ce38466d4bb8dde7cf137d95e1168fe0a178b48d4a27472da8dd6536fd82c17584200aa2191c9606a49fa8890ef28352e449c385879b372dd89b03e4e8ac9e9342c330631151a76f54bdc5b9d6932093fb0106f4439956511f3dbc04d428bb25fffda3174c424fac55c5c470947be15399c80eb2b8b573c760a1d99340073017c3e8944029b9a74dfaf8953dd720f84e52a2d9cdb8ef572e819071449567cf2c00349abb91fd7b50e01f78e82ad6a0265ab1ea78b5f8b723db545bb6a81c035f9c39ea514b9ad202402e3158d0a9482b9d71cdb9fedc8720285deb94de77351f4dcec4f19cf534b8ad9546005494a7ebf52ff3607da0c7226c9f54d67aa52631e51fdcae017d4ede19929ed6ba38635c876c31a4c9516724e613af080508f02c49d36397f64f8072b0f0a1edbfa78ebf303f88e2b1dc57242bdca65f18d54197eb080aa7526773f36b848dcdaa004d8ac3e91d23c02db72b9b5bc20b9a86a646083c2bcc606cc15fbffddfe9acaeae1657bab6b488fd672daa4ddc3ee3f39d964f44b666b5dcf7a707dab63e6ece4e6680bba502da1db14b10569eabc9293504d9ece9aca1721890ddf2649c3ade5f71fd14bb63cbe1872925c3b0667c010e8a3e624dd959b78655a53804c361c44dcc206166a5057c50fa328afebd9017ae0468c51e7ab89a786a49de65e0037051133f992dd5b19ad079f47f5fb80b5a2025477583d97d0a88c5024b5f19b298c73103ffd8a44359b722bec5923a73942559f10649cda5282e7f8aa980ae6cad79fc8f02a22dade8e7209ad2f07cd0967c5ea94680631c19c15b5c3faefd549e94348da60906c85f7720be0abd7bbeae25dc2dca4aebc640c7f9a86fa5448a0c2053fce1b34842e7c728eb8a0b369d2038587b093d9669386cf69a9e06bd78b2e722bc94c9e0e180e723d5dc500a85ef098788b24bd06a3a0111c981db7bf81242b40100f5d2c0b9a721809b41bcfa3c67f9a5cc6f842a07adbde9a3f37c3e365d3a3ede84cb7e856729cedc60dfbd967d4e243ea90921f1dd76bb4cbb54a5056b170e4179147511472e703a52ed96134f2de802c6d2e452c801e41b0be652e179e9f991a3238c2de72022b8b5b48e615f2b29e91fb3c55f90f55f81be24cecef14116c743c36bd781856370c6786dc04ae0e48f24c0ed2f0f51221da9c1056e6aa8580de42fec236670e996261164b784ddee02b97671743f849c8434b24298d50969c741414827c091eb1b478d59e14235077f04dd05304a958e1866ea9884bc979b15ab479b4b472c1ee13003f3d45db2e6742274b7fc9076fa02e64bb91dd0ce4c85a18a6db0072097c7f6f706b9f76c818211ee43fe4908db66345d9059d168bd3a71098a1a24733fa713393b9ffbb39d833bfbfeeec372476d287ec7e7f8f16bcc0c2b10b3172d0c1207d4c420f1bb851f3af6825682352344f9588f65c167d9d105607da8c72152b291e8e8d783060b414b43c5746879ba708e8d98a90b355b12f6e32b37300f9955e19258d5ea1cae05d24f6b019062f067a2482e598e7494d08381a23ed726d70bf4fbbc85e3ea56e909b5181a57705fb6fb98c3b956672e146889fff90190197363808878bb3fe16839c6bf7d9388a9590ae6a575dece6d84cba13677d727431fb70eb3c22e44be6b9648d9d3ff8c24a9aafff304ec016efcc46f124a07218c2a42a28ffe2525e41423de8e93d52ed0cf13f6aa1c4383658153ed070157258c3ac89ce01f9e7ca487c8d0bb633aac9e91bde6abf05a678ece89d41823f7229f573951b037d762cd6a34c5e47f87649a3fed670e88a076bb888735468fa7243fb994e17800e978627f141f36bc1519608c15babf422d158a77efbd54af430e67cb5b36a47d096d1af7add8cf711bcdf001904250f4047441220a0bbb6e37298998f1aa71deb166a7924081dc78bd1cf6312ba176d58f00634c8d9f09d83721ac5406d2273e38bd382ad29a53c5b1aaf8c7323d1771d8d98adc0b34663be72e622df3fab7ab7a9156bf6c77ee5e0993f4b777ca19ebafb247ea8b6ea7cfb72e481b2bccf8c522916b9aae7c9cc60f57a8e84689647bf7362d792bebaa89072708b84cb34ab8fb93128d943ef11a05e2daa6e8eacbbc14515e04baa87b5cf5496afa9b4b664bfbd56d3e080743cfa393eedddc0e7711bb15ebb0e9adb8d157296beb6c993b3bd11a5c157372de09d4c9acedd49d24f6a6015c4f0d808d7ea7207e51ed73f681ca2c15a6dce8458bb620b98afc7629fa2153477a819ca3cf9240dd2b5021f8c8e2b80e3564521cb7ded300b30dfee032758888f7e5e623a0b7270f8fc5d736f6bc996559e892dbb261223f87fad18550cf896e417f70302e87259cfc4d6a3b18f421986eeab5badf4fa9fb35477ea3d5f1e58d43d2f9b84c47246c9fdbda29590edaa55ef3077b4053184a262b43633aab3f053b67bdb88b8720af06cef4ad7ff1cc4650c55d735ebdbf1d73e7ca06c16fc39488ead574140725f0676eff98d6eb23d9dec598ab8ab0c2784b398e7f5afad50f85a4e407e8b72861c41d97009785f609b869ce342245dabc9314a99dfc3d64fcae4b17e8f7c4c1a429d18b715fd3a54cc960a2c83a0040ffbae9f90d0283790d6986d4c725b72587880776339948eefed4a3702c04cde1735d465331061f93747cefaf633410a7c42ac450da89dc10191fd55b6388123b705302ba7b558cd9e7abbb44bbc61424c37bd93708ead7b9a7fb73c0d552f2e1ce0ee43cf6fc5580e69f3b22f5df5729807eee17736264f94d91692448b65f2ca0ac8e1b76077550b46bda245e11e5c087203351caed6a4858316241f21294bb4e3815e9cf8e61e73ed2f703c2844392a5bea91eea5a50b7b68901aad3a67e813f28e13dfa34d1a3b19ebf009688b7264295aa43e30ed1894baade055edc49a346a1932ddf95ff9a324b4a2658b2372a8bb951b1469151e34b45143a9bb35e4e7ed87b2f73045ab91f77a2bb3ba0372df303b44fad6aa33d32471e4df3fbfe0c71020ee4c22cf786c9a107a6f2728216173ed4d77a1c00a7a46c8f138a31bdedd3785165b37288aada7d68bc7282d3a279c6f7037097dd1ef4959a54300a828fde0d743234d4de5a072e6ed5695b0726080eca1a96efc19e8fb014924267b9f79f446125f2c5c317ab1916dc874944f5b0597fd1534bb9d1d891eb7ef817bd5f600501cdb89fe93439662246633613e57807c4addb04404b6b1f3bc70c003c6ec4bfc0d147dd5482791bf98b0eae0721749bd50bdc51434dc8ab61f52301a08d3bfe2198260a130a97110408edcf01975b1c9f5f77a26617791e3f4748154df8e496613ac131d40871bd792f4489b28d403950d8d09ae3da144628a868a3e46fee76c07186f1bb8b3eb73e63f9b192b8e59d112a7efcf70f3dffb0e704d4448db69b632681026492355521441b9037225cfaa4a72c0f695cfec127a29ee70439b080bc41e186eab4b3cdacc0003d5055dcdf5c437bdc8dc05655e669d2e8b1ac5a68a88c2d8f7260a793d0de4c8167273261ed12484c60b58d085cbcdc6ae69e48fc21ada41f8bd4367140bf7430f29c2cc3d6ecc105b6418ff199ee275ba9deb1522ff60c7f6214b92383d3983a8729f80f81c7e73f33c215528a139529a5165cf2c5a810204d108b837519121347254c9f06b2d57892cf55cf4360cce3379643c69924046e67150c3ae52522f0e7210a4de8772955a2ab662daf757522be228d3485285fa1c117e53daccf7129521301df18283c51f9eb71236eadc644a4332cc1a1658ef70b33ef450d2d26e4b72c5eba4b078122dd98d29f7a79bc4657ccba7ab7446524f8987a4c4be4f22081030f2254edc64c0da6d8eb2c6a46f11696b58e67f459ac783f34f8e2ed9d60800000e59884838311140cd5d87345f7e2888963100a6a8062dbd905bc20425bd729879fd1b478fb5078c09436fb58f2352394763a0c8c491f72e13d439986a1b721ce8d1137c9f2a9244868648ed4320ed6dffd87f192bc0f44f6939b87d4411723e8d7bba03291f574ed47952459b46645ba79f91b36879c48387bdfc4a757f727158e611f81955d3d5bfc606fd0c73dd3b3d14afe2e7b5376587f7c9dca0d4726116c333d74fed07891a4a8be732fae890e321fb3d5b8feb003f01c1af95c85004c46b428e75a4d70edef27009b2114c8c8a4e5bc76814e11f6a7c361df7cf72ae501878ea911bb49211e21edb2eca756e4e3c3490ca9dc5e061068f3c77d7244d002d1c0b641db9bd776bdef0ae8b3e6216c233f1bc73eb1368d57b542ead1289aed033477ed32b5ec7c49b8024b401e088d938da4793cff45640d755652747848583276179c65ce5c8e4a7f3ad33d59195923cc551c3e259a62e0fe5985872e9193113e0af4e40b1749f2d2463a1a1be45b9d2ef6584f15a65a04055016a72347e17c9b143a479ab3a24c8cb1cf8d18827868647d2edd56dd818b279db6372546a28a297ee785bc9166753a2ed49273967c311082c9bf3ed5c822db05a3a72c6d5f88076e3e4b18d994b892a503369448a41578a03beecdf9418b65b31ab727cd0eca7d0f4d7482772f7deda12e4faf1fea704b377e4563d08594936fd90717f899cddc58a711eb8d32d6c48c65370ff5ff9f197b71bce2aa14de3077b63729e5794af85b035587c06bfcb30162a29a2c223a3af5afc642a47688592e1e172802f10fdcdb88cdf3467b8c22ec0fb31b055d8b2e032e84b5c43d38f297724034dbe4ada4169a4a3d0999af190abe59074107387cab9e78edcd6b49c46bb1a7219a00c7e69358a10c8dbd1d072fe660ca9aa746f9c641265a7283a71b39f8512430a10bbffd69ef59669c1ad59b77926e44814179472ab45c243d4f1193a2c727a70e4e812aea8827b5d27c900792cf105ebb37daded67d6552941d2854d022dd132e18c2230d5c8975b54a9bf41dd80fe3d53e263376fb9d8e1fb6d7b8f0a4e93555fe1dceb7c488321784d81360021db699a2d52abe32bdc1bdde62e1c7c72ddef9ba10a8fbe8a53c79051d5857ce6ce5d17d8d8a667dd8db90fe79df10b7204cf94c3c979f7532e4b855c9bc871582409471e4f9ff2d478fa9dc81248942a144b5ca82e7b8920be3a4fbf827bc71c72791d6364d2ad348716d7a932847a4cc00a46c787fc2100eefe7fb356bec6192bf48f233615588f4ba87a14c5e6f972d89bd0660976c9e12751fef52f957e0be4acd30a2c69d023209a8c339046296c37e2b5ac61cd45509bf58f28842e04c7fab403eff825df9253efbf043abe5b13e406f29aa9c051ed7f555fa0fd5ce0fabe5b5b67501fd681ec127366794afe424114fd8394b97e8a8a2af2232412ebbc4ba33d6775268164ba2fff35d420847233bddea7f771f4ded168432db5b4c2a39142afe296e908e19f436b66a7e99947e3d1409c0420b27894de8605afe7e03e8d3e30fadcbb09755777c617c323ed727784185344bb0f5169e9e2cb756c5ed28d8fdd1024595609a9422f26cb29cd72bc5e2f3f9d6ce0223ff197c3921e08bcf2855d8041d0a8bb92a77ced4812dd3af2d055aa59b6a10095c582f54f4d1c9da4eb58c0b55766244318bf6008229b164060c7647ea033255c14a21b88d0f85ef971d4c9bdcc01dca111887431e27d728c01af17792cacddad60390eb8500e151c83b443e333e1d0025139ada979784ac7ffc875bc9db826d48c30ad59c8adaa42ebeb2ed09a7393937e23503e5aaf72584ba204d4a8ceda62b550dece91f31097e345f19ce2fe7ddc5902cfbe718b151df49a8422d001f0044fd60fbd6babd656dec62c0ef9f0c1656b4426fa1c1b7228c1ac1b28d7b8da361db759c2fc6dad27f6f7c52f7c48083624efec8b915044d77330f0c744fcb24159674b9bb1bd13d43b3e1fbd28076a9f15e0598d9127723d60dc6d14b0ff3765e07493d70e4905a03b42ce391a18f79f4a0ddbaabc753e58c82e6e723ac3ac5727f0fc66a911a577b496ae06aebac0e6f3f907f8fb56109c1600b3671dad1e90fe6636e6673a2b25e9bf84f8b0e2d08e02d471fafb07265d3c8a35b6ea634f6ddaf632d68605a110b9af05cd98337bb157275a0fd884721ee03336c982fe5df7466e92a93c52fcd4e4087102190c5ac86770a93c3c9d136b94ee85f9bb11d0543eb3f7e2f2d66748d5245c41ae82a17df743c82bbe8e72e9e46c65d8c5c4493d0e6932f9fbb74401e585e629c8aac38c2ef31f23a1194f38afcbb675b2335556d298ab0c17fe862498e5882343d967f8593f4bb62d1872cf6dc67910677f247d66552bc2cec8c9ce4328ec8273dec3fa24a8a81168bf434b1399d68d96bc953404e6cffb2444a9f5a179a85601604ac9ec6108f2946872f20f4c1a350fa1acd790b3c0c60caf05b3c27cd30b7c27a9bea0012a18fb9b727422e9772fa9afd373e3957b554426658635479c3bea194fb5c2e8790ad7d572f5edca3a34e7f0efa0901c4cbe26c1f1a29b44a22f2068f0b8c54c9396a5fb4230f7e9648421aa922abf8a3949378cf78866dcaddefedafa3aaffbad6c119427d67b3a4afd817f779f168834cd2c9d63ecf0f3dca48ad052035dd8588b277218fba785355ce6eb222eed18e77fb820998e18212b429f23ec37eec71a58e5c34929b4ace6590cc6ed46f6d04099a8131e8fdb32d2f203b26953c0cd69ba39a7729134792d9fa550a98aaa0d6bcf035a860f503c08600accb2fc70d9c75d3d1d2ae24405dddfbc984248292a5c6c49ca2b195c4a485ee0137042bc33a47162dd7257e332bf60f9a20d445213a745bfa9212a79fcb0561414b144ba814320596c568afc3ce4266a1cecf368d383f546dc334b347a70271f44685e79b4616c13b77254887b12ef72cbe02e764398600833043ca7377dfb8f660c088f43794f34633557f0729d5f04645ba7cfa6f4545a0d818054b58256d01bc097d702feeb0f7170ed58cfbae10630565f222812d96559aec015cff882f203074fd0fc4d1683207265801838c797c3b338a68031178c1475b73e11776eece428d1b7591e0b4112216f93275f19e65a90e34e806304bcfb3305cbcab28ff27b1a873c1b652c618c72f994036d01c8c66b367110a1e4f70d2e1ded909f38e20ca982f1687e0ce6a67244d3995a152e694bc5d75017234aff9434cb746a9ee7dac7987d2fa05a802872fd81c7423f266f1a9f6ddde1274222655328d290c5b846a7431e39648a25aa72afe5896844982a152b37bae630f816187b38d4a096957da934a54a504d3784002e5cad54e18b1cf680e392f943c39ef8499ea5832ccc056e07e63707e0366768215c9653f8dd14688938de69b3296bc04bcc7a0071d665b53052b02c3c4eb4729178bf48aff9330ab549d5e529f486e00f1975b966720e7e7b36ab6d96c92b721212932e88e6626a166a668d8372f8a9342c3514478caf8f08b8cff3f2434d72ff2259afef4cb6d5e3474552b421fcd979db6194b4a5668b403fe0de528266699d89ee07fa644912655e9f73ffa1fae735ac05dfb24731aff8ec4245b12167727e9dd4365e0760c272b50e3a0bf4971167979c80290d2fbae1ed2b1c5be250727a464470432606588c098b61debc32228b86836d502fe2ff5f3ca0dc75209e72c995273d4a5fc0d8ec5a712c04fa976b26f99e567e893459cf5e033e5f5b73723d7bfdd20002d666a660c9354a35d6f7a8034853d49f04a83b5b9641a23d53020c9ca4ae8b359cc6e70353e81fb0f0ed4f2837196ac88fc5b51c88e35672457270194e71dcb18369f3d609155b3b84394cc94b1f0efe6b235bc26a97c53556723821baa73f4837457fc431f2f744c115402ea588504a7dffbd8997b911f82a617575795e94d16b7ce0dfb20f4af9ef28e3846cbcc2fd87ecd7b8cab35016b4725c1ebd2e31cf2d0326741c0c1468c1724b19afcd1fdf808cf978e9bf19453672ad802170e373636d5d038c26c5fda628dccb9ef8da6d31c5a69da40d23ae4f2f8e392e50f539639f06a9e92f7e3a60b840b2e347758a769b31b4d7c30d2b415d140952b0d040a63d8c32bb1977f461841508ac9874261c91064f9590050d29053aa58cdfed6d4a69984c94cc4012185b189e3c2ae946adfd17a093b30b402b72462c8453fda2d779418c56ed615b06b503ee2e305cb91929710b8473b5e091721d25820c19375941a4ea92237bb589776951f83a89d946acdff1aed02677fa72f4e4713924015cfd26c8b06fd62f1dc14fed0773c1ccdd51d53c7e662b968b592ab01cf72be04ebc64b2d768e512e1452af41194c0f65e8d0e84f904af6349727c70f4a05d959a985ce8ae836bd5d954ceaa61178df7d3f143233eeefb7fed0c0e3f1f0d3ecc32989bc3428ed78e6506934308b9d3be9610f20f9892704c48722a1fd7e3ed78225b95b6264cf910c1d8e7b3af54444662b42ef1cbe18412df4ddcbc90fd2e1d3663e2666c2f47d558278f6b2bcce0c81947e344ec11460bf1646ff0b83f21415ab2e4eb0b488770f776d13a2baf5916677b8da331d4aeebae72ae456100c8cf476614539c0f71603f294caae691364264e758ca350262bb1e72477e5fb158c62bd3878610656a430afd522693e90926d983d52667aa4b65e872661a9dc2efead0beebd23df040448f8be226f2d87bd2f77bdf854fb1f315f272dc687e7af2290d23be603eeac10d8f6c6d101adbcbd7170d22f0b0d735071c326d4dfaf7fb37327b51abae9fd5c726da493f141055a8ca5f3c4f4642a174665edb48903729e7fab3bd91d65922077289e8fa3a98263e86572edf20ada4997834d67e70cb78f0dc0bf41c350b13b93479b4443cb30d4ace769a95080c57b79a72bc2add9066dc267b02110ec162e3bd6538eb8bf2ea54386e2d0d3f607b9c2f7277c4ac2fdd91ae3951cfe35fea7574661e734b42da61a0d63f40c3f21ca23d529c5de91f41eca3a6ab5eff80530e654efd9c2ecf8d9c01caff58d889c3b24c7233c123c1466834bf2e30b4a46061281537749304c4fcdede0484028ac68520466fdf6cd42061a356bf074bef99ef56a2bc1292ad564bf3fa2a39d96364490972076ba1443bba46c99d3731789fcd9a40451b599f03a421f27c5cf6d38cc9db5b50b603cbcec20d522a43b43e626e5db81766e69798f612db7b06d481b47a45727cd0b57500b6c9ba613db6f08dccf9f367e0cf950f7afce9d18acde3eb69ed72ec158de3d79f5d9543a707dc72a4e1677308151e190f1f7c79eb135b02e9c22e778f69dfdd61c4785624c7d619a2f6012b8effbbfa0e5c7a9da1188fd1c19f23bc47e9448f02e3e081285945833388194eae85d33823e7e9bc5c9bd097f3d9724a68c3b360bc7e1419c507a6ef21b2fc221a1ff58c7b935a0f0d945f713cee722062ea482adb5ec5a58362c0f29b781582e5dc5a4f1ac7a37ea8ef8aff4cad330bd26a3d671e8317c7e983855a751fe770415bf80f842893b2d859e9e885421446702e19904600a1741ee50b99433dd3ed0e022c984736d8ef760c5083c0cd729b536eeb49f3f6cf5521eb7ed0dac97af4a770638c9502aa313a3d229ab79972c59e2c81376736e6fa311c0a62a35fb4f86008dff20bf70bd44fd95480555872aa9181d985e974508aba2118f3485b5557ef6b892b74d9f853630ec1e4db99729ac9a4f6af5118ab17af813bc35b4e9156cbcd69db1612a0ade66b9a419af33452ad8cdeeb324ae658733b4c8e47f3f356f944543e6a0067882223780d663e7216202665a161cdc07b934126b871731621a000e7ff2f203afe51c95878e8223f436624b536d3bafa64bb8a4c9648cf70ecbc0c241861b7a1c7dbc0d65dfa470028550fcf8368af38fd4dc710c19445c28c12b30df1dc43eabfdba09754b4857262ce8926c74ab5257fddc56928ab5bd7ba57d655109d3c2c462e123ea387cd72fe3d121cbb33b96e6279d295d0b01c0105321ac9524932f3cf7ca70d6826d6729571a15489c80ef4f1c13a1023fc306a4672550def25be6126cd3bd70bcc567206c4cca1c4767896ac37d16e16b3c2e80fee7e413389e998ee416fb6df220672d1d077b68d30b2fbdb164f89080da7079a585e603174cf32a31dbeea943bf972aa9e7acb4ef5c5235ab2cce8ad75ece7fb4bdd7c12bc31fd640b350007f78e7244df3041f3b495136d8dc0b61ebb1fb11ab0e1ace92257c32712843112a463727324b8e8b4d0af9e977bf6b2cc940f208967810acfb4c66e4f9dde90d8bc0a72278cdb1462a344a7059066efb347ad72237e594fb61a04984fdd2f5c1a821472e3f51ef1d0fe76801613fe1f32805364d80ae18f942ed2fde20e76a808a0fd616a669bb5eaac434e247ce811c9e65112796fa3763d85aae33fd98087fcf4c972f0dde48648e4ba21fab38995569d2a466372d36e6f8f95aa89a563d98e1b32587e105e22b96a36369185f2fe38cf4ae48879e73a06d34e0f3583166d193df0728829642871f0f1300994113367259020d31f63b83bdc95c69a3fef5011482e3db94b2f43346996b2aca4f05e1f44086582318545e21974a89b39abdb3032141da16b10306e2b7ad509a3841ec42ea92f5e6c66b6bcf6225f0ebd40b99b0ab4724bdb294eeb1fa426a1ca0660385b67633d28542987ba277c24648e21f2e78d68055f203eddcda7eb58f72b5451dc9faa502dfa55547a6c916ce73028214c50728a70bc032d8f47a5114ee5f28c20c326d2833b53244c8a6db728f0251f2eae72d18c037cb22e82becd0b812d45e0777fb71d8ecce0237025776131f12a989552b7a0ead0d1658fbe1da0c731d3d095684ef652aa562ce397d65bcabab8ad2572d3db902708864ee3e2b1247f4da308a08e377a7b9b5b5cc66ef1a7b79783f0728814738219a6be221a6d3c0282336de4ca40338db909ba069831f078cd9a91722230a357ea5426942b0983fea9052db9c980ead770278a995429aa77b251db23f232aded124292b52b90b6e2ebba89ba50325036ef55c6ca09f25d77422c9864047f392b68bf33b04f3131f3414be88ea9ac9049f32cc72f598a2e07297ce20d2c2a37340d38db761cf0222e18500939633871f82f5efd56ab1bd8c2fbac2f728686301f765bff331bf9898d4b10823aa4877bd5a5f248f9a3cb206002c28672839fa7272b2ed26038e250a869de7c94330b7cbe6b76cf03be4c8254c4b9ca72ff7d1fb34475dfe646a095a9ed48a5fbbc6b3e00ff8253d34b903e9804a12a726b71816ee53a545c5b3e3db2c4bd279bf87016709bb7eeab4777f1a586811d723c71800a1aa8cfc84990e6adf3b292019a0f53f32262b5dd4adfbbfc334c602def51ab42d26dafb3bc31358af51e2072363c25cc6d667cc75d509bd93aca992f490715611b9ce34e805eedfd64e1df3b490562e5b6e2519682bed9a1d23da2728a33595660fee44a4e871cc3702202063844b3993faecfe780efee72deec7749a406b995db71f2e6121eb4079ac4bff88b124d3573d7e14caddd831ac62a297289d3315fe9c8b28f01ab5e8bf37b9bece602935776597bfbccc268a0ad9815475a0a034b7197a0e732497bd8eace9704c786c8902f3446ec677d0dc526123063ef8e93df17a5b9a4397a415224116db4606c51ab15b1ccf737744d5f6d9a9c130ae5000bc5b1713f5bf71e2f20ffad7165e95097dce83ba7da6ecd967f79ed729d47a75b61e4a9f2a3a170986aabe8aafe9c3e24bad89412f0892c657f8c5a72c6eb731ecbb83f016a995f49ca3328b5ffda2089789a4db13e1a0ab8bd5f1908ab3c8b2c0b40e6e1a4b6e17c9ea9369696357e210400b88005c837395446311f83ff2db95162c9268881238a0ed2e11ca0aa8e6ca556888f235e08d3743bc3725e0be63c568c9e27d4d56b57c6752b8dea707255a492f50e2ac559a744531e673cce6e959447f447b4446c177d56c073efc3bf5a515c5c4ad5d999c838e60d728394b9f4a90a88854bd8eea02c49e86aad17b32c95311fa04e4bf0546a5b2b721672c71d1c0d44c77199a4c8f6231dcb2425fa7ddb37cc35fe07504487e5a272c1b4188f604c429c85f290ef0f42240096b6a3d3402d5002a1cc13c0f01d47721be6b0b4c6a8037cc6c1111508d5ea9aa2ca5164ca6983878751c97b4b7e9265fa2d823251d2c1deed327137fc7a7abe4b66cfc50936bc1c58f19c46d449d77218cdd9f9ba28f7e6f0a1e4779fee2545278fed14c0a24b471835c0c1e9750f721ac4a5d03563caf5db74f41fafb31e2eb0daa2f2208109155fc1e7d2804d3f36c17e94d7a118dff55ac1dad8d202435e5f1b758b434b117741b3150b768f1e727663113462f3fa44d9a1e7f114ee3cd4a8327d6214095034af35f816849d2d723cd29f851e16f9a7e93ff8731d39a9565d769fb6239c21daf4537c2b59d9a772357b6fede9a41b807e2a385b7416b8ad72c128b91a44f78345e762faeb8def40867de86c3fe17d813748405bdc19ff4cb8d87e9bfee77bad0c8fddad8742975d1d2752cd03e820b12e3655aa65b7b15922e15ac06cfec5febfe856cf6f9bd3669565d224b723cb45124b8d116d602412155cd9b69e4166871fc6b3733a5de6720ebe70f0e4d4f1d0fdb5cd2a70bec346a168bcd25754856c44764066ae552a60eba5a4c99ce6bd746c805656e47fe628f47b68723018d2cf5e6aab56beebc87266ffd1c432396f66d00eeea28447fe0a8fb9bf1d104fe2fabe85e8507e21cc72d454dadbacc8ce189a582b8a6ebb49cc51d9a3f9a7e25251f03dbf46b29f3f5ea5f7cdbd0b34495a763946b849f601b6cd9c6e3caaa6fff0399269388f9765725b916e7bf6ba72946e55a33bf93beb294b489cb76352e3c7fbf55d24f6348a72ccc3d44f662e859de57479b401703ae17ecfb759e99bf303b274f0502a304472e460c661fe307da614bbfd3bf227265992812d31ad6de0e3f5385fc5bbc98b54ffbe87d16d59ad5b01a397c9985e960c50f0662ffd52417270e934a03af68953c1cfb403f68286465b1363e3611a20475d5019f6ca2550c9be900f13b0dd5872f82093a782b6dd1229fcb27712c5f2acab2ab2f028e7cdc88c8854c25899a550632fef4a8ff1693261aa06bb3af9b8ed18742761f80550271eb5f394e5063e72466be377a51052c66066333a24d5353ddebc1b30109b15eb5f7fb05fcb883c7222f7c4794ce8686bcc3021a1062d53bdb90f8791320de7554c601af4671a2972296234c5eecf872e0ec70f236cf1adc3b6c8e8dc5659c0d7e4c5a8cf2ac73e14a29c1fc63512ffe83d59104dea62e2afba6243608ac1683062be76b98f14c3561b39a47ea80528523d556db362c44da3f50e839dcadd2aaedb4484f7f1573c7245a66d174520c278917f84d8eb8835badeb66d83d166854113667c1d7980c16450e724930765c45cec47120e5445d67a2cfb88e69ee51e9d171a708188bc897224bffe5eb1bcc4db737ce42d4cd3c8763efca82314583aefdccca4154e813e72043b652e31703f670b69ea597ddcf7118b62a98e863ebabbd0f7018e8f90477232b74925251da8a9e7f55cd8890f836e0e71b263c4e1defc258141307cb85f628a2b5cdfb512e332222dd8ca9cd54f5b73d7c0e63ea345fcfd5f462078549d72b554d066d56aba4b1b21bae5130e3c6359df483ec94b2757b0b02c8779eed16eb2147c3c264fe63f62da82649b9c703d6598407c1cde5c2b3e3ba54069514f2ca8257f32c0654135b7fc0c515c549036c90dd71f13c97233f4ecd79b6088987200bcd136c3711801d106e7c9ef7710eadd541186d646fed3c21b6952e866e55a400a5b24c160e6a698a266e02209a8c47c1283aaca9a34263d6d8aea1243ee721fcafd5c9ac0e6346d70bfeb0cc361399ec1ee58249f7c5173d9dbbf6ebfdd58ae761025919a9fe5649138dd15f6d21d0f6bddde3a7f00324ed8ee292ec62e72a72819dd3f513f9ea9eba667cdff4671188fd1fdb5dbeeb9b2940766ce440f0e3fbf09091bc806e6cc784976f8ed4907eb42e0ad4ed7028e59815f6a6c2d326d93d070890f6eca3e36dd0f2356460fcf1d08d5e14424d47ab438e23316b82072047fd6544fd030939f8cebced38832d62ee5d12ad4335f6a7620ee835d79751578319065317e8d8e524a1fc6fa5e7beba31d4ee0f9cd1b057f2055b55801f628f417bb4e30e2baf3f1e71388f1a9a429169a41cf2b12bf5692057851f1a1297299b6655f15eebded10a03f25e3b2a1557823a858f2e6068306ca1ba692497904162c535ca4a0247c0f5a88438a7eba17d63206a5c0756043cf112b660064e772dea9ca5495eb6d2b7fd85a7ef28a5e6e64220d147b390944693316e004176c69d4531e2fe71e242eafb769440026c8fe17d98fb3a60c06a5c2b4a09999140972b3760ef073ff07034f9d02ade1e719e012fb8c3e6ec4bd3287f81d05f0368472e5293ee0e9be96540d5a28dfac3e545f8f431dd8f814d08382922cff62f0cf402bde3b60e96c23a4dcc1c50e120c8372f6118c6e2edbb6c1b5af1d2a4ee4fe6931d1ec29ee8875bc4efaf52c689a1968f9097d4084905c2d027139c33c8add725d98f1bf129d4aecc669a2abf9981eb44b65f4ba6ca60413892533b147ac7972f40dbd8d771b528a8b2d437b21247005667c45452a6e2981251e524dfd8e1c72d9aa4f7c3478bd3c0f1c09e9845fb3ff194b8daf7e2bd8bae2e97d7f851589725f17b584246997f348ddce9a0d507c8cf1bcae81bd932a1c52bab47b2ff501723de028b2f18a308a1b2f07e6c93bde2d96aa99ad2910fb42ca9e3198ede35a36e42554bc84ac6ba02fb18f9506d85f2569e266fb6b5d560cd4c68e289c53400ce9c1db2ec57fc69acec01d6a3a87037dba48a76c86b4035e87fdb32e43045658d99a7199e4f6e55fba34c98a75064fc37170453a346562b0c73fbf50c8cbae724a869a1bb55efbce9dcead4e7960dcd3ac6be918e9366083379278020f98e7688743c75c81e23ed8a3f5f60cd0d49321ca41d036333db7f2652f2a536ebec770eff25c52843694e8f8f736eeec5b9026c3ba9efc945942ebedd2e9a85c1b5e1c53b27b3c7ef188fa223421389253881bbcc1cca14ead33fa54a3467382cbbe1aba95c4dd2a760a042a6edfb4b5558f062c5b1f778349b512b93038eb8c5fe8727059e6697745ca328d146b03e597ee8fb71118943672fd7f1fc3df171c06b24fc06dc4599d0612360ebd65e8a07b941fffad59f240e320d43cebbf0da855f15ce22aee34e68bc0878587007492b6cea5cdbbe2c39ff60307d2005390217042040648d3f82a81d2bbf4a5fa15d950ddd6a770b3eea533ed44c2c4915ee7658b72d4e92d2c69aeec64ef84297d31e545934794e6081ecd3d339f256b0d7383713fbf59a5cef303d15782cbf10fc8f81b681b1bbedee77e94df19bf25596125e972a2a5308382cc7039c8c596ebdb71d06679a8f20f8b19b0f810a4e0dba0c794720b1cd581f5020535290607c26e51eec3a312045db42b20db91e6e604152c6925a97518556a93046693ce4e8500dd05936b7355d1942c0cdada2bab063b268b72dd55d7109f9a823caeb74acccb2190f5a58f0598de9aa1fc82db0119d3383f7231a426376a01de061272c250841a09f2e273b48dfeb848a1ad24590306ce9c37ee2cbc669b535b4d1229f9a26ef7932851a959203226987efc69cd45d1358d72fc805fbb33ace9abff3480b180691eddc9bd42e2ceefd83888a50497ed1c0b729f1e454f4c6fb272fae8b36b232a9a4d2cb7397a46319ea63e6c5870dc75b42a165c7b264051582d8e80a12368f305eddc2a8994748323ed58838658a5634172ae74f4465e8bc4d9faecd0ed51ef3d606a100615dde190c42105df8b16273615226af37cd3cab361c2b5affedff5604ee8708e7a4e861ed2f2d8029b061e1428ead8b631983042238d16d7e9e96234b9b27afb9699719bf8d7af8c0154a81544b5e2218f8dc2f5db1cdf5064edb4110dfd93de006fd1cc5a9c747fd7c7ead94b80628499c1329885e1d4ce44fe080913f1c6c6cea71ee711e634dc268e056172fc29ab1ea5eacd2a7b302ecb593cfb549dafe7aa35d87120d6bf8adf45fd5b72b1969ae0c65951258fee5c18899008c2182080232b31619e4d5e0af6e64be87273c4fbaf4518f02f9a2ad1793f208b3144fd29a365a9e70d0db689329f52507231ddde803a19fa4ad973fa70e3d4749896079bebe1b1d47f3f1cd3e7273c203b06b9c383a22073b46e8150a17b66be8e26f8a89ec574459efe74b19fba2bf7729f19621eb8914dd9c5556a8f00e9f72db9d7df7d97f4e45018ede1c06f7c6268f7bba6428ea87a33d2d6eedfebe4bb2caedb4a1cb8f9dda196168b34906e5269ef5f320f40bf4f5816f3724e1305a6e11f699cabeeb361e1e6eb87c4d00a8a605642ef6122067acde69d671cc11e37e66e666c103ffb5158c8cfab86941a17729842b622457b5989519be127fff0357088b1012ee88d7b492bf1af09f0ec127283867cd4b8d38a14fabcfaba7099db15ea8303f27972fdd13380588fc7e3f53ee5f69edecf98504e27ebb1d60f629bc576340544208e4ab2588a2a51c614027225730261b123065af0e6980a6c5907e59403df0cb12b7b0b9eec6b0654687e7286fc32fafa5badac4267e728e404487db6db51e8b88751fb9108bfd96fe72b2371312ccf16a7abe2dd8e2f99d451661818296ab37a7aa848ffb5afcdde497d4557b1cae154e0f307b97b419bbb093d8a0e8ce8654c3cfbae0952ab2924b180721909ded175964e55ec3777ec88c464aabbdd267b655be4882fe566685c78733398c32e3f48c3bdf8956e3447656c971317edc42474a09471ad509f1532a09a4b30a1efa1f1daf64a57393b23c3b7f71f38bfc4f506d90d5a60689208d8b87c6ff653eb5f7c0b51c80f1372a2472f9c6fedbfcb659b37072ec70706f256b10e72b594b33fee75b25e50f3e1390b52923e7b7cd00969482022a2b4c0d30341ca724e06e0a8e5e3f337415520dcca82c3fca0952bca24241a7be718660e9fc4eb31fb2640b27be3178627db43407ade8b1eb07b65ccc196dd8db3e335850467fc72c0d212df3c119f367ab7e92b6b73af3127f6eee97d8e5688fdce51c22c476472cab8e1abc485960bd4621772631f3d98a84cf213bb7d14a72b3efbe99dcede168bcea5cd7dd2616ab4639442b568d982cfe89f2a07b51c35c11784a0aee67c726f7fd5ae52a629a513d1cf05333839ffcac62b49ce1e8765b3b1821f5ba503103317e4ffe45a758b36876dfc7e67b848d4ab9dba67767f9931c562703706c42f8f8c43c13019cba1f5bb19b490044a31227f0e79013ac9ffabdc44fd7a89ee30ff74de6a7a79851aaa36af6a9076b5c02053e1fa74511609129b1e8f4a4b4472a9e3603b142ffe66ec975b00b5c5b8e0b9d3520f3d0787363c01b63dc5136f513fd8216433ed503d67fc9e31ff15b060558a618f06501a73813ddcec6168bc2af5ce8a9e3fb48432816467e0d8cd821e4361542b461977d1944f1c4ab5108672b5513863cd73e5a5905afa7f6f7212626dfa5cf67a52ed8698bc63810a36367273ca35109e7748b59f06ed5a2bd8612cfec93e34a078ec320890f98581c7c472393081f0cafd8ab8ed7ef912a50b355c363fa59b8653d6d172fa17a2e48e45728e6f9c28499af846617c32cab4ba26e4b227a5d58aabe034de3f074130ef5b6656e1925bb79f376fff10594de90b1ac30ba0ea48895eafc6cee08bdc1d8dc072ee48cec6aab27a11bb700d59ba23861f7864cd0ef70b82b74da40e94dfb0a318b42c5fab91782bb8671fca138c0eb5089472023e7721ade4231c73751d7551729361cc702438f8a3a7468ad66e9d1cb2c4ff8f35133f0d0e73245354770d127268c1c923533f13486f6c5eeeca2754a9611386463c115b65d171591e1939b272f000056ca07ac49ff82516a0d2f2bb445d74f5488b8046ec556d8fe83f420800f96008dd41b55ed892c6e261e06edad17e11829a2e84c32047bb15dc317291722996e7e64589ad9d94b41d76024707bbc00afd44c6a51148ec980fa77d18b872f3ae6dec6cb5940c57570fb512c618710879bebd77c9a8caaf1c8e6002a69b725a71c92cd25354fb5c4d07ff9eec870da2f03192546912767682d47983b32231698094ca0bb84c13ed432816d70fc00269708df602aa795b76a1e79f69982d72588b8dfb2d929a75d5a4c3ffad527eb858f150746f47375617f1ea2e0d1c462c8e9ceea185cb138b399c893b0ed965b097a26a92b4978d1898a039e8fe079c723fd32f2968ab15a58c556e5bd86f90e344512c296075f336bd7509fd1566e472579a421987e208f832aa7ddb2202d72a01cb41452739525d5b951d96e9dfc5722a99b5f550d913f19bb04f9929508ebdbfae165bac9fa4de47470d5ff491e672a225ff25c7894cc5e974b27fd5262a497ca47d1a67e76d5c5b3f9ccca866a372a86113625b4bb53f9492bf0e2e917d70a0e8f4c08897e94e773c05a8e1b512364ebe4e1cee8c8cb4507eb990200576be2572e372fcb935d0f0327efba76612728e8f890634b0657154d6d9c4a221a0dea64f3a3537cdace785db1835e410a5723bc1b784ab4f3f2184a908ea3757ccbaac191c4914b607d2a6be4842446dde06ca397bf74106849b8de7bed14f8c045085db51f093064a3cb0105fdc3bff32729611620e42ced03de666970b6674c0cfed0b15044d6e824347bb3fe4df031072f7d69fb9f953cd8594180b811ee9c9918ea9f966f602fd10ae3cd15ccf70a272eec7b006b17b7e516898b40be27fc6e25c29f891d66925e9c0e0eef76913b207ab6da4762bdca34f8123ecdc05d6379d7774da73b53712cbea188cbc28ff7b4854a63e21281b4343bb81855c56ce42e208a77da4e7f7fd9b1df730e001b427726d21afc9c556a6999cb15a481b056c6d9a5deb200624e82cb210a6fa1acddd7210a5df024e84e51f118e1fc60a160c3cb723dfa09dc6f3690855ee5f42e1e5054ef6d9c04cfafd2ca142ae1de55ea80a288d198a7de59d50c68dc728ed159872796948de5d35295f615ebbc2ba5cde4a74e4f00f0148f26bbba84057a9a18c72a26560977eca8b35028285ad123ccc3dfc3181f985c7ddded0355a093e00017221bb1a703985c9ef94ef4dcce7644d4eef94f0641617470f428ee1f3c8fc9c52206c59ba06b6c6cfc40256621d16a29fd62ff848a7de6eaecacc1939a899921919831bd0745528dec7b2845734c594490e76227ad789af95795e96bb84dad472606bbd50ae12179fd561f5e7488cb82419d0529887ce06aa8fa48e3221047f72cb3ab0c3db2413925344efd56e4ee57d2b0f3ef65a0b72d9298d4ad8f48395729ca41380b89d159787d28051b20425632a278866f3e70b8f04b0c24d7da24f310083d67759fe7978e92fca706d57d62a8e0820ecc6461e972005770ac2e30c36ac87471b168b0e962b9b3df0890494533361d1ccc55b25afb224c8208cfbbb72be6c0c0e9a5c89bcbcdb4e2dea3ae3ea2531ed19b8a40a8352be749c0074fa48551dfe52c041d4bb14e019c15027471e60d88b1e7eb69e3c2fb231f7f4a02d72a9cfde2fb463178b88c84510f458087d53ad193fc63191a9f7940b7754b0082d7bb14fdb2b83dd64e8821d320bc2aabd93ad0733a4a9fb1c30d563825834270595459870d820c88089e55e3e0b8c8a43e39c9f7309f2765903c50e9aed11510e4b324dea1d27b60fc86385afa5b57e469ece2e36ea8aab0e6bd861f297d0dc62884bff0b7b6ac211f0c806b8a7a5d3dc43afe918799fda8e57f6367bbfee165d82a36ec1e21ab313bfb8166b85c4734244f07bde3fc79f5bb085a7232c0ac2723390a41058e50b0f89aab4fdd68f771a925e42ba2c1b03621c8b075bb4d9ef72d95f840fc7015fff0ec78939a08daf33e7f7d12c09d9731e5123919b6f4ebe408a3eca36a17d067c5d172507d08f0cdaa33e54b05db42c06a873179f4834be726ca75c310d129df4ecea23fa8b74648360f0e4a82397141ad48e1f550024c87233cadb406b5ed43cd1fe1e457b5d423f4b5806d0daa9232f5826161387e104726812d84eabe64be491278db03530c9e882b15e3272dfb7e2fe5f158ca238bf7234d4b131d1b5388835eca05312f2e64595fd55a28c0892a319a3b6ed96055f41fb68168f0df554374d82a35b6db4aed6302dee9f3ac2dbc04f2d9739f7834f72468a032f2716b8d5d2c2bfd8bc9a1ff23da0c1ee0c07b69a0c5222371e622d723140f678ad566554ee8291777501ad97208a0440ae73f8f94a82f087a92be27271fba680ce46d2e4e6f06448cbb8aa87994e1e3dfdb5fa68af5eefef3d4aa54c764d476f3a38fb0b8aeae1b1afb713072a7c9b1ad8b0ff8cb9a1ca459e77ba722efc868b8c54b8eedbb2082137b5c131966a9afeebf37abde97e109ce2bf8972457d946131591fe352eae87a952671d8efd8f52869a4e59b0cd049071cc0b072621977ae656c5fd310c36f1624be775f2e3ad9afc0fbd215bbd3f8d7308b280762ab63ab5e7cc7271d6a34a1b37101c9cfbb650ed30e33d0bb14b11387b9387258c28f9b1e9c148b1ecb17b4a81b758cf227fbca8b0088af1e8385b94c603b7235661116defbd2d0689d61a23fe980ec53b9fa3779e316885edab61da3c29572a4302d2f1ba7d72538ba5291ba9c69bc1ecceb6a51d8a86f3275c05bb9df815546e71cf75da219daa39a76605c44d6d536a8041c8bc43488ba79576c63f3fe482017fa6a8613e6c4205f4f756ef1a3e4fc45cc0d4d7e7d024f463f959c258d1cb13f6aa3a23ad8430cc8fa94c130a64db17e2a657c711e7af99855c40892b072dbe8a50e81197e3dd566ea36fa9ea5e0644bd64633bdd7687dcea85410eec25272e00cf1a1ef1893e918830e5fc97b8f331fbd3eacb3a505c2a17d4a50571c725ae998ea498d6316e77cf317a876b81010de4616046d8d43dcbcee95088f861cb620dca50b8d26c7138c1114c4680937402c06dfa8e6479f63fe2623fb9cb65022ca818a00c5a62c1915752674721256f310257df634dcb8a30a94caad5eb060a6b7f578d1d4f14e0c718392bc90b0cf80ad176eb69a4fe8ea5c873a1436d6632f2fbbf0fa4a8b8c1470b6b20571f85eb1dd98d8f3cc4f97e8d05fef3f4197726ddd1dd5471e2f7a5c0e601d6f59555326f635634fe84938daa5b58011dc5d1e6ab5b32ef5d652e6458e0048c15e2eb0b95edcdbd9ae51d021df7d897565a85ac99e04fb06cbb3ac8b858e0909fc1f5cd33af601788ac475e59daf7e89d884648714060456e0eb4d5bb83da8350e0321d51c40f34129b9535f87bb02493c98721fecf88c5448fcfd6f7b4c2b80d4b8badb804dcafa37fba0cbe6c4d2ec119063dabaca96ea0301d8b71983e1bfe66894147671ed9ecceec9bd2a585cad0b1772a836410f7fa197e076f30f6f65a876ae7734abf100c0ef75d89111bd4f60437287ff75e1e1c6946a2ecfd1a044f05e7acb9bd35bf488a50fb6eb2d5a7eb4917295a5c606873838e5555283479d9eef62a2dfdc510b6aa29e24e0515795ba187266de92d50e6ae8e70472117f5e6891ea6aaf9e7721947c93d51e6b5aca7a7872ce27ed00b5676fdae841334f2d5e37cb07a4e8ddef39597cd6f7ba5d92d86172d0911042c50b6e3970f295495b6c764014afb9bf37004dbf4fe4c732edb59338a5952ac3626fd686441d4f9143214da9ddb73133b7d7bea2299a2c144753f15e19ad092b44934d96ac68b67e9e5aa1ed8a69569ec6b970722d506ba827e0487253cc709d03ae30e577ed620261c2091ad09fe945b7f40e16389a146a0866f67218a29df6edd19fa1deb11465ac41a91757869abf01ad15a2f2ac65793dcba07291645acbcf82d713e84f59d4390d9d03c0d304e2b6cc61465de7b64e67405372e70d931b1998284b8db1d1652e8d0037a15f60d73a53663785f90b77c66ec87230d7c4a98bed1773884387c524c522839afbe5779f82fd6c326cd538cd3ec5722262a12fddbaa153475115ad40ab64d4d08b02610c28ad8718565936ae8bb0727378312e023922dff75fe28029da3210e826b93e32ccd47ecd432030b20bf063b21e918c6bf64c3665f2d4a3658cd04f84d07f3408445c5eefc9e6fa76dc1a438c8145a7c2ba364baeb753eb725027c37600a672495139f4c4b47f959f3e0317a6c89fce29ba6ad073934fa2499f27d62fb1713254e4d24123149e16c283ee6e7b01a9b00821d98a2fdc64aeaa90facd55de177fe137ed48f6ac33ddeee4bf697687816e31f00b42eb4677d03f4a146c2096b250868ca6f408541f868dc22f72e706dfa5d0cb37f4d53ed88cc340201248613665bcc4b56f76ec9b078076725ae42fa9da490e6fab3d82487873c33c37ba7ac20ec01b8674c61d90fbc9c56472d40a82805235af499a1f9c2b1f3bb891e1a73d47dfb28a9e5f062caf64a5a372fa93b45e67e1a32b1a5a99e6026201be6dfa440673c7243e90f871aca9dfe83c143e6eb3ad0b367c94297aace419f9600b3506e45c7d43b2aafc609f69e9104c9b2434a48c6eb35aebee05013e7c415c72cb6fc77f0f11f39d49b012c488c472701e2619dc002d31c3bdf93086b8f27d595615ce015bc471f920a1640dafd12c253214789cdbc1cf5b2f135743584dfcb602c04ec2119b4cc1d657e5a0215572a7eeba869c4cbc6a635f1456eda34cabcebef0749a13c0eb9c2a1065f59de172937502d8f29743b4c10b84f4560b6c99fcfdb8cabe5b445c2734579f8d348c72e16d856e99181b15abfa44d99012014f8f433c0e587725ae7edd88dfaf7de82f2447430aa21652b8481a25d02bd5ce244123eb9f775407ce34e0d065e5cd7d7239ba885e73fc4daf0a2d3a9cdefad0c633d8f6d88d3ec509aad24ba2a3349a5ed2ff079c113ba8ad7fa7810eafec395b6edb314206bfb2243ec5b104a255c15cf7899e66412e462a1a3da3f21630bb329f2c39c726679c72186520f376852364680b8ffef421021d9670963cd8087753f45d6e957747315e874c4fc75f9456725e24c53a5d1a299287615e684b152037a6223c56be30a40471a8d142d1c7dc0526e9bd4d64b3ab653d6585ef33dde1fc886e6ef5f5f66b5f572c177665cad872d3b89bdbaa881296124fbe5e0b171ab954cea169bcf78c433a12d03531fc1f2021b7fbb13ea9f377b212fb5d264987452af891719c42bd6494db872cd93e87723603bd433940f0b2e5f77f1570024424962949d967a7804b367a2fb7b7ee39727fba21c740de08247c3e743c52e18e265ceaa5a62451b60e77c29a8958e9ba6354416f0223f98e0b5b8ecb83e31b7a8a56b703f2dbded105e67cadf021d15a5fca24982d7f2c64188140891adf47ff875c33a06231e3bec4050fac92c3459a55d41b8677a44dda172bd63d8119c4155eae35a732244c1e22d784654ea2f644526c0e49ba3730f572837d78e59568e9917b3add618985b6718607c3c7f51876094899921ed577e7f0ca09c020274e73dbbe6fa6af0aaabafb7ccd7b494a49bf72694473ddc04ac6aeeef9db2ef453773ab7b8886361825eaaefe344b488233d724b7aca023a65fafe9bccbfa231df7c256dffae78c292cd7055baa17382125e3327b30122adb79fcb5ae0115ff2affa0f224eb70d39502cd3a4e08bfe970561728c90adcbc9e5c60f562e32f2b70b68c8e41e0f78377757f37b00b9980faff2726f78451b050ee162000320c817e41058ae8f006265b6d1602904b0420d02230d515efd0ebf7cfc3c9d9c658d7a0ed51921f71286fadd16b5562444c270941b72535c665d58292d3ef26b861ef96abaff6199bcc6d6aff6dfacbe25a608f154722f873e5917c31091a707c79736c300e2c4e9bcb83b4c7c37903694761897d33c322fdf7a5fcef250819f335ee5f1cf092de579820864e99709d6b3370b1ca840edff4aaa338880d7698b65e86d4d6fcaf74d9152aed4b244783acd678f2d2072f007507ec1272706e39b1725e09e7964e059989e0f4dceaafbe03af00b8e12608cbd0064209189ef85ea02f4baf347ec99f05805895c5fa39599e4b454fccd1e1f4a77a006be0a846fbde670f1c7e81dda869dfde2c377851bc73b5bb58031722b16144f7d6ad2ceeca1298e76058a1126d37e4aa028da672524c9801d6cc436854132ca696ec581ef96eec34e8f597b791ae7a4e8c9e69f5d3c90e04cb60272547c1dfd5fb3d99f1da04d596e40e9453d6162d3ff2fd91215e8aafcf79d21720fa015eca24ee44b21622749af3858d5dd303c1ec5e94908c27e486328fc074445a660f87dbad04ef94ba185254c8bc3bdf8e5d346aef18a447c4df9eec3a672347b5a81f7674ca8658cee9479a921b9a55fcc94d8770837458f4c5d41cc6772a73028dcf0511c2e4efbfbc0747997c86b726827924fb1ef0679e768af5b4472b89e0fd52da7c4453d35cec71b275455fecea042720db3ea20b15a8d176ccc72a7329d5c152ed7cf28d08adfda5970b2fccdacdac95d51d49b01d9d3a4147d72145e9b9eaca13fa931c0ece268fb8aa709a2e9c79ad8c253c41f7077dd373372f860826f5a1fa40c0b37ce6e04aa87fc7580d30e8f2ddfa9994a7f043ef75f72cd0a7761d851f430d894e63ab18c13985388e865c60abeff19cd572ec99dd9293ee892791bb6e286da930e68d4d7edc67bee9864adffdc80ca703502747f31628854eda853ea9227db85c471275051c5ce681a655f46ba4b324b418ca0451472db323dcfd9fcaa3cb95ce5d0642f13ea60045116275b99c080a0685c9ff31b72410c2f7d3d9dbc34b8ca4dd6e8289f065bf1a73fbea59c5939127a671625967203197c04d8c4be33041bcbce73f17b4cb2a7538b85c21091579aafd0b2f67b334d58df77ad2801746707f04922000f66bd05fd0f03e7623d1fa60ba97377ea26a526c7a0e7c8deb3f327619c9c0022b9ec81df6475753ab549256eb7aae7653f88cafa0763d4b55d81d30bd8cc33837919ada886b2f835835cf3a05a12f74072553cb6996b4da1fa219e58800a8de40cb5d43eb49d8c0619ad782cc36cdf5e3a3adcfe8bb930dcc1cdb05fcfc59eb586ffab49bcfc87e9869e5124fc00073d633ada60134058fe0098a06b17b69645858e7bc586c01a509a5a4c2c9f37ce56325dd78fe91e0d3d2d841cb4cd86186a54cf9ed0abb40cb257757297534f018d677d8b0c50b8e43bac9d519f380a056ba67828d141347d8ad6d08bd6f1fc71df4f31f8c7ae40c74f4b4e1afb0c0acec5c51be5bed6a2d01829517a7419a90afe7204cca6e5af1736f1e0c7c5fa11a0c5d04e3704b45b0bb088060b4e01d345ad7263fc2e3c8d0ee64ae5b9b15bc47c97b9a1ad1b31fd5f154104b6d11423b01d72e639ef6c81fa87a896744bd2587c5bd14f9694f41e043b64c849e8aee2560672943c3ac22e345f64d9bc65e6c800a98d5f022f897308440a59ef4c30b825ca72ebfca802269caa448f38845b917b182b219b5c21917aabb337be15a552171f72fb0f48446c8922c98aecbe3199c131c1f1c920e29322560978e02a3102e724726b20d6c374c34fb088afd30a272da04148b159b810deef1d6878bb2636809072470276da116e71cfc9e5ee3ac27e80324a1aa124e6a4c06baea00594ed2a4330c2767482411dbf0c7d6d8e1e30ceb69c52dab4c372c17a041f3c100a4fe3db66b821f7b6789fa40f1bfb62d3ffb1f1bf4ac91edad66a31f441d5ad1a8318b93209015e1502574620536aa1d030902949a5d8735e673c34fab44d5e18dcb33b50516a2bf69208747b94b701bd569f23c9050e9d92ca7f5b5b55bfa621e06c7347f981453bea2a5005e836161ac886c5693d4320d813cf9b4340e2df13874b4d073d7cba9b75031d07cb0b4e038be15ccfd5f44bca754fd68e49c9026156d7f80c149d3b0b7f346f33e045b8e92122d0a29ccc96ab9dca24184bc36bcf7e2b7772cb02c77fa95d06dbb1640427712463d64f2870fed5eba4baa756cb9b19a8c93f3f88a4dc06ca4041c7b93c8fc652c35d4aa2521c730c14c1a56a5825a928ae7296633f3018c95163eeb9809ce8d2b58d9038c4f948001f4ad288e615e479e05ef588f2170bc2a7d4a492025588820e6199bdaa7b3f6a8fb9493f9511e9f59a7250cd9f0c77c926f0bfea7fc6a46188a8011e5f7e9ef9d36e33f9c6a5d14ac359365b448a66e08d84328732fb0761d8695b6af2dfe36f1d3212fe70d74f2c7e06d6aa6c00343438cc3e3c4de0a0c1b045c8852443b69447c5939716e3965db50b257fc829018367760cc240ab721ce6bc3b205bb673576790d38d46f037cd176da37c4e27ff9565657c2632bdbf3e1badc6c33181874beec64e3d7177193a5772ca9d49a09e97ccea2fc608ce59cc2e46b197b07aae7bd8ab0e5cdc513a29670960f199b084294dcbcf485f49462b2c14222ead7ea4f5152be3c7cd2a8d1ce572291c1a48c4cb7597b10fc535c8dd2025a7054b756f74ee83e06847cb2a4aef581f41cc70b04f01b94f710f204be86725475187f703f27958d628cb44a30fa3435636430abf038c05e31d03a679b23dc3e80d336498ed31d099086e0da8afe9724cc9147eb18f8c0e2955755027f7a1aaa56c7f72f4f2166a40f7bad01f54d32573b598bbea5d1c87cb5c40c4fcd234272e973c9ef6647bd540e2254fe13a6972cdd863f22a9c6235ed4a210f6119157d72e8aef3a3f9d5c2c7f19cf65470c272aec664cfc88715bf5aa65ad57c6f4f229fd8d73b1a16eb888f67756dab1d8b29e5a30a3aeef832e1800e7095f1896b82975d23b05b6fc519371446b23bb18f72dff56f1a8ce4640d268b1570e42cc01b3fdaedf09ff1c91595a33744ff886c2452f3ce4253f341fb6e32987ec134b5e93c3774091a5970431df2cbbffb59f3722195999b2dc201edeca0981dc19491c09c0892c0123bae2246baf0bb85b12772bb1279a7489982f0939bd30728f03887a4730d0e24082a388ece064de34001720aee700e7b3f993af5c7c7195c5762a5292a59cb7ab5984550f480bef002ee7252dba3386f83cde1c403e3db715b8fe54ff84bd5c5f9ba3046061786c6955a72fb330cbc2c6b7dedf8f19beb7e6c0cab3978440754449925627ca1ddde5c4b723bf770e711b93edc10d5b783bbb826a4dcdc5ccdd8d61d6ca3c8115d0639c172817646a45868a102daec7e46c3cf3d6245f9db29a3c0bb81bafae7e2f54eba624a32083c4b2be60bd5fc526f68aefb469b678f0365475953164be1d27885fc09283e6e20d135d69fc95939351308f2ab4e486275ec2c612e4452200d29e03252e8e17d6c740bd80512245743d7fd8b68ff82f7b356e94015e2a5c6735b39e34643dc5f27764d3fe37b02d2ed8d3faadd546d726f54507bd75f7b2a9d9622640e1ce3b02476573729e1acaaa53d9ea9699bb19b3d051fa798f88710c2e66d6032ddcb029f1b3ffae18fe3de3cf1c5c896359fa70768aa0b11a9d587b421dc627288950c44bb704331c6dc41a3e7ae53a4ed94f211c7195b53c6b23841bd34337264962137880def55273bbdf43d7b82ef152067a1585160039f4a6df25dc4675208a145da573612143fca1036e2949ddee58717cb751464798ed01e9438afcf725bdc170ebb9d0c89e9add4224f50e1b948d2ad502b7fee212b0a14eac3545722e435107158dbb0367964c952fc83e5844317e0c27234af735f39ec85190e0272849bc28b2fd2c2d3cd10a55332989ed1a135811acad3bfdd90c373da5e4d60725be984ba42c14856020ead8c8e55ab2f414b18c66066c86f7429f9bb8e856f48bb7e1aebc27a3639425ccb883d727c3fba2a4671f036c0de7055611f6df5c6724cd8bbcaa718aaff170f5b642601880eedf4b8e43441248a55508e5503cf3c21cea37dbca2620a2386d7a7bf8393a91f6d10f95a354c9476b3ecd248fe85bb24b76bbc787b45cc52958d33c4c6f477184d413fce3d15bdfb330905f402f24572b9f62ad70572a3c013d73326c9b4ad1e76a361844fb43021ef374451fdf97229ab63a1cb78891866a762eaf1fef022d812acf5f4b5794f9f390d2458c35df34601a367f601d576ffc9c2bb38e36961770aca9a5da145395c400c360f7a679b723a79e1ffe02b97e9d3206ab24e07ba7aed5c427798620b4176cd624a1784150b32066e34f62c34e626e6027f10c6443fab470b1450984911090c6bd4d7c64b724aa870d9367abf6c9d14e5c57f1dbea5358d9bcacb9a482c9e03b3b023b01372d2a5576d091d8909aba52aa1e7def340a7bb869294b5631234b84c8698125a229d3c63d6ab695c943c933d4f58c68e26838c009ce97e42aff9aa95a514f8c1724feb570c7b4b093af1a1dc6c7e68445fa334d85d048edc22e0803534d221c94fbc3e96bd543d1e2eaf6f16709b501e85e9433f8156d3b02a483cd166bd501947bf20d9d640e07a466781552501e333ae2d22f843980963494e5944e448f1647233b873f6d03ea516aff029a5faf2ef2ded82d4c9eda7aad53807c541bf496c588d02681b26cc020707c51aecd7b28b57449d1710128e17756c467e27b101ce724b2120626c12680fe0bc23575df80900c4686490c9d1aa6686f391bfa1c42e08f1c3581c855ac503471618bf91ffa39cd317be64b49422c0658075e14984cd725d4a688fd745fcfd2534b960864c73901065b503fc221957d6a09bfbc8b2cd72ec5dad58648b5eec23b15348d814316e03f97a0c36df73c5b474cd31c42154722db30a3925a5570553ac0c2fb28ac296e2c9491b3874c73f39524077c734850affe00da5480fab81585cabdd9d38bcbb965aa3df98e8c9b94fd36c6ca75eae72a1d755fac7d3cdacae472fc581447c41eeaf247f74562a6eb20b4b282267402716d54dccd76596c103676102b90bf5aa852cef75aba95378d2bc526c793e6772a9706381020fa11111e5e44c0eaf2c552351f375f15229d3b5ca67237e05d472a30e5b43722cc75b0147569c08aaef63d3604b3751da9666427c83bf94ae61721c413be9273f00b53b4f3ae3044afa465a1dc9bd755e40616e78a40ca74e423a0279dc0c456b38e1c9675d489dba6c6bd232c360864e022c78e9a4799aced6727a4a3d21dcde63c79fcab153d8f05f834968d75166cb2fbb74422d44a7b0d9725ce58d5d6efe236c0ac468a0ed802b70542d1e666ab6b42d8f89d43b38c1b24351546916fe8bd2cf832f4c4bb473e9920ac39e62c4244135530f021f5ce67b33f5f9e0272a9a2173e3218b00e1ab23fdcb50c9496be6a1d8420ae852bdb2ba7234fd539a04d1f2626761e9e77e40fae20d6eaca2377c2d56953335c6b0089172c9bc4998fc0efbb00c4b6716292558e69098a32cbfd808d07d5844fc0253bd72786ac036947d7d6789a0755ad5f664cdb9cc257010869fe427960e34f86d01455791e351f5aa0eca21a740c1196960070b6eecf1a6b42ee211248f16236b2b4a7250cd4e50e52d0a7344e30597c8dd3a5b115bee1c6a07b37321cc9566133772e7e550632f2bb4a1b7dce6367312053d96d82d00e97a87ec8ff5a312df844c72922144d2b8bb0434c2fd75e75ec62be14a8a8136388a345670f5f3d554289c72fd3c90fac095806631dd05cf390f6a5346796888a8586f54be7bfbe16b05e972643d865fd78d37f14338d205d3115982393af7d341439960629e71f72190796d6c1bb47f3312c22d90692b43b67ceddeedb7287f9be800a0f2d7553439687f24a45729f9865f37475f65b0f5277537a94b1845a3b8718e4f4c043d33b0f59b4f174551ea5637c87075932a67c03ecf6b2123eb507e9305c0b6970f2d37cbf172981095d89298c44d024202dff612d68cf20738d2d4248b23195b3f8823dfd672e68c470761c72b96230b7ad8132e2c23acbafc2893c5eeeffc10b8eeb160db729be81e90d64cb9610699b90eedc09f4709c34349ad1eee52edad50a4aeb9725ce1ed7c39ed71b8df5abba626da22dc0fe569a0cbcdab0ec4552b7955de37d936fd4eadc61b70f430d7bbde9cf6d227b0d902d5c5d5d9fba9e889907b8e646272d1b8f5508f48d543323aa5b09db25dffa2d58b2a44dfdfaefc1862ffe7ca8e722d174e82401adda656ac5e47aafb56f52e7f6213a2edc35d122b2841268c187260674d594590b5425e393fcdb24384cafad9074e6ff2d115b93b527b90aaad729cdee2d0a7323d23c7f674eabcba538ef50bf760b700ade2abbc37b71475227277cb6f1f47d56028128e62d51e73ead43d44fa6752f030188a3ccd6cba24e3532e915dfcdceb196f88326f4e94f9d44ca2a68a558dbdacca22afcc5737d5006e7cd10a15787c438344f94cd9dfe1d494322efb0b48346de3bd844696f7eb1b729317529c971e7481336ca4f914040f4283da1e66ccea3b8b3fc40ac13f2be6671eb76c90b46ae35b09f78644dde1fb0c2b04a4e41f0004adef8b0d62f258b433b8187f00648875fcd81575aeb89c604ed1903a8e2f8983ee0293fce2b47eeb435f27f55a6e7b448786f27b52db9856c46181725e9846756f5db03798c2d9fe16546b7972fb4b1544b37d07766ed01cea36fb1e33d841b3226f43a2f066d3fe7295277d190365f6d5b38555ce895738f41af7710dedb4e76c95777138f917ca03a21b7c28a7f72dcb37191ccd2a81bce427d09e0f138c3c7be873886e4bba817116070dd4b9f6537669035a63c0e805fe78a1ed73afaadc2210be85b418b16353d3d865cfe41ff1e8ed5cc3b6faf2a03c163bfa48068831d1e297c17cb5d8714a0807ef1abfa6a26e33cf35274f3f922cb4af69b73179f2d5943d5b584d7a6454e593f63f2e8e3774cf2447f228e4ad0ac84c5f2082df941ebd43fc7e837204105e4eed57ede64ded45729b3f100aa543b7cbb0c40f8bd08e8e5eaf9878dc4c668896500e644a938d6466bfa8f4a849c9d182495764295f473e461977c8d8b0723b0df6ba5fc8ae15ebccb71a8fe05dca3f4f4b127d5c3693b0dc56847b1bfb72b09fb018408dc6d9a33623d7d7b3d6659c7ad4d804883a4644f8ecde2a1bcc25eac9b0c02806122546652865146c60be943e2203e99093a4883535b4f40d1e7259337fe0131f20fbf627dab9ab3847cd5e2f956031dadc0a3563b3477199ac062e754e307d2cad3354cb4efbd4d8cc0c91cb56fc6174b8a9dc87c100247d1771b54750bd5bfb58b3f0d19ca02c914bd8a7b253b35ec9c93b726d131baf5a94729b31518ab57ec4b3fc7032edeffdf2b0b10421afc1d5e7083dee557075c2a52528643ea1ee521fd1325e1aba256a60bedec176aea1ad7cdbcc2843faec66b772213e140a8f07cfd33960e96453090253627f591b2fe7a2c970cfa6d72475ed5e45d262bdc8e02ffa255a047072245cd780c6e977dc552f518fff2a75e596a7664ff9e620fa508ce1d76ea0d0811cee6e4b33c8538951fd5920cb66f8e5db3b725818ac3f106c3bd8ea01bee1fecdb805b7ce0f62d84bcb313e4c53f94852dc72db22dc6d7d64d318fe56bfdceb23b0225a7892ddf6f87cc8a8ac3e5452c6bb511f9fc44e74f57476d95459f95d2943bff1f69f338c7d377eb5446bc7ee0945727e16e34a8a5bd77a607d51ff455c24e8d715efe4b0f5539960b99d0b95ba3b727864e484c29bf0ea82e8665f60a44cdd784e8886adea942f473d59074615d072bc38828de5c6d05b52a59beda25634b8f62f332af409b62016d2949dd9692a31c8084b5bae20bb81c19359b47eac9583d20947d335340c9c0a62f0a32af06f724af0a94bc9bb25c5039587f44e3652fb675a194de63b24b5092da9dc2f9bf6726cf05ec1be23ca5efd81d182d601782c93c7855d36a4977c70864d60e582257298c20f11ec55fad14821628aa0f303d1ffe347ae8b295303fe3af08a0a4d847217ff845b8cd87690d8bd807a28733435498aac4713d1210cd57ae0b403b79f725233257c2f3046f1776fd3d70339c50e80a3cf1e5a434bd6a6717506cb133d72f47e4f2c4f0c1b95aa0b55d1b4e8f0758d713e5fe7985364391bdfedbf664b72063721331f9763aab5f1865953541f69f9bbc1dea829b7d9a56da0f0f4c0440fc1d217775b0317b37358079d6994190fb973bcf61b2c7bc4d927e76727d2316438d0340fb15bbe4d5bf1b11644e453f61e98e45215bac479ac0738a88308ac3aaa5c8f3888fe88ba84661fd8dcbba495480f60820e11f9bbc30d061f92892463f96778c716c9ce3491cafc5bdfadfc254e5d58dd5051c70dbbb47d2f9b6d5b65bb6ef209c04574c0dc3d5457f7eb27dc6fe118461d9cd6865b6de98861161572e89ab03db4e53cad32b0b2f93e60645d7958871ab6ddb1ba1a4429b24b2c7b726c935b438cad35f906eed67a662e3c1190fd6099a46d0423ba710dfead42c2156033d8864ba22bcfed78f0a533530be8c54bbc9006dca003804c78ae3d97847223f32dbc47c0bf789ab08f63f8fa4d5f34059af4b9aaf0ca509623eef3064072971a242465a2e79456b7e61dd11469218fc6f722fd15102b88f9759f3f163d7273310e846fd6e6ae193490ec8da9b01664f741579b0fe19397576999a7877017415babe91d967e6c73c1463dca7582fe8aa1e19f5e0e3ade0dd8f9046cf4cf4eb827d6f03be693c5160b0c100fa5d8431519b7cc31012793578295a0126ef972f1dd3fcf1c10d7197f4408dfde21d0f8ea5b9b13c8aedb991b1bc05cd1d7636686b22dbd91b54a70eb781d51c51b247f9ef2bfb251ddbe2015246c976abc8172fc6c8cfe28fc1539ae9f523cb467c9a61935038c16ba5f79e8bfe640e2fa1e6a94022b1b2e7a0c761b240e4563fbd9c947868f8fd4548480c127335e6888e272b4abfcb338633c1ff9b87cab8e08ceb788c2aa2a4554218be2ed12f108e05372c71be5675ccfeb72adf50ce3666012436da33a7b840b67557b43d802dafa8272dee31e5bb32fa361704385bfe9488d81a98dd6e4263e450ca0f86a81ebd5177262258fb2d68a3cfe292c01207a0cac26e4b1becd57f8ed2df6d797a23b97d5722a5004579c15dc912e49cf1c64f67cef8dd6947827c621770f78fbb8ac247b72d97aa4b34f1bb0755fdfcd684c42699ed59c8072b70058c38066f9f87ad8023c38b9b7c913c871c5cbe74340d45b4fabc04bced1a7a71f890aba472a6b49527220f515bc1063072fb11cce11998f65ea09d24265730a19dec8ec9a574ea19d729677f6d1ce25a3224717095874ff8dbbcdbf69375df62acbc09e190cb61645337956bce6117f8dd69f4f204aaa016d39281fdd2ddcc0ecbcbf5d5bb7a7f735728a72dcebe5edc9465c146892adea3fc0b9f7a74fe531cb22a305f9039f120a19019aec34fa8992f24a1d2d21df6ef14cf72df358bcb7945883edbfd698cde972807f88d0dccd4110c78fe2a51da2d6cc44683d0f9339a21a9b7f7140fa506d718d84d35e971d788c060c09ba1195ee0b03ec5f6b6049b805d04599d5ca1835003d13732d865aaaa2d6362f15c16b11cd357387526a7774aa72b7a456559a2d722616506c1a34b63332056e841966e7e57c00fdcd69ebd54e7ba8025fbcb2da6ff3e094f30a932fb1c37e6e4033e56fd681db14fa26952c2ad4a31eaff1690865e3d93dc09bc79ec44de9f59429c5b49826bfb88bdf3f187719c6621d2c2d2d4f5a64dc48a1284df208b98d4be1e7fbe4776c0f0a0e761fd6b5afc9eb0f42d8720d9ed4e830e267eb62b8923f21ebb599e65bf5ee892ef7927a6f16e9787c6c72f50da718ceb68529c26e83560dc56c5c0b3df62dd66e0164e1570e7a51939a729bc78ebb5fbbad260b856ea03050a5648233d0871a522cbbca87ee4fd2050b450f0b52c0fc6b06baee84f8567655db0aca188d87bc3a8303b13f05065e392d721cd17ec966884ee0da019cd9e5d09bab5c2f7d01c3979cc2b8dcf8340fa78c72ae7f1195836e82560f37885d3c3d8e0e8b2afee47a80eaaa7126efecced1af57879ba553b4dc5b89e2c2f6b9ded2b2ba0e31dd0b31425b97b128e5fc11d2bc310bbce48071823329afa470b6a74298a50d7a1f03c8ac1621bdb0d5afdacbc4460b31579b3478666eaabc86503fbd7ba802879e3a530f91eb6687a195ec143c2279a0d40e4dc0701e07be75463a9b19a09dc50bc910c047adac133a5dcb8d3772a79f1f42ad8119ab28d5527c0f24e6f9589b57071a3578f52d042a40206a58720e412c10094214d9fafeaf6be6bea868141a008875b4b113d415282e15b54572cb4ee611289eae12482052f925f8cfa8f8890356eb99b644f9fb9ba55a255a724d3413a1ee06f63791275d408ecd67f7d8194dc815d72fed858cf43c88c308729ae4c3b1f949b9dab8849492fbbfc642e45601e872843af7356a6225a44b6e72a9fd2c7ab7b0b0fec058260e3e265007276c53baea045e0f009429408b4ee672d96ef8a244d96ca21af9c702a875174a49c39b147b1e9e7b8afe2fb4f4167c728513bbbe13609a61fe996ccccbf04363b2af406a513aadad61f576631b0f7b0acf1a0f2c21e6660a7b54e86aa1bf3f53305188da28970bd6ab01a9e805ad8823b7fb479a3af167f6d6323b2d415c1f10fec0e0504673b25994e60bb79e54d272b3342578f76c70a6770d34c50ec344e8afa22672f5a5542707c427c98816ae72a1584ca8aa80d80ac4c9343a52414e18a0d0572d70607871ca1e72cb516f753e5e3e76809d24d155ef68d837967e6b80a72e3b13848329155f7db1057db66a729b6459e25dfc8e70776cc43005a67f00af8d9319c6b02c165b75b144c62f571ee7afa97235847a0f2796e1e1b3bd9ba6c9a28744cba833e695761cef9b14191b0aa9c1e47ce065bea5ee9ba17ccd5accb3d3764063d4f3d5b4f79e71e3bbbb335833598cc745f4b5fb911dfb5cc6b390d46547656faae417f015d17c67b1524feb559e4fffe658d836fb3a73b2213b01c4c61cf6c1f6ccd851b9eac4bee8b14a9cd283a47f890bc3ab7d10afe2fc9779f9ae044c75542944d88644d1c79935614743f9a7837e89270f05439d102c13adc239d3baea12d99b8943c2c83417ff6a9c684fcd5550b736a67b1bdac381d6f0114270029282915d8448eea48de4137204d245e8a9fffeb9efc8404262601cc4e4b79dec8b4b658d60f4efaefafc97720b72ea291890227407eb366ff9fffb453282beb1e8118e4bbbf3570c7a6a8237511749b5bcae5a05aa66d967703ee6aa73ee7e3ac5ef82be898c64ea8eaf1d72a7c6112c505394412cfc51bcbf8674986c289d921c55521dea3771c584160e727aca577626de675f7e77bd119a35b08c8c1554f97fb89543b5db607e89a8830500b4a59bce84772dd331ca72008c5f2b379181622952069655b5f4b1d7bbef21c8180352cb933ab0e8bc7eb62b5a87466fd754cac782d806c50d1dcd81f85b72055280115204c641053d41d6120b9121b6a0f6a69e1f3698dd4443b831fd833fa6a4a4133d034cff24f16a60415fbb0da03e9c5b2cd4a45938521baa7ac18c247019effb4a435b92d91c0f03d0ce00608c8ac4c9989705fd084a1922a5ed26523d8a873852c0e8a22a7862921dd854ac79bd505a5e3b919f2904b0cce17a5472f2766ad206e981c54237c1aa298fefc39e0dab9485a003c069776679e419e0725c1c3774ccdc7dd41d21a136004949a4278ce0df9ad8585c21c74ca78810ca720741cd59149abbe8a844d9a8d84d8adf81356506140f54b529e8d7559d11e30ae963cf9519c20e66dfa346fa5a73363bcd99fa321c66609307970e9b4c6d00720959816f665178512dafc58c5b6f5c39382bc83edab5b2a358ba8aefd4243b061592aa68d8fb73a616b8c90538d6f60216dcab6dc8a6da7a3489364884c8cb72692c7926affda7655f79ed918460ec3993cf7358a72a1efa6fdd58643d877f0a6e94d42326dec28684ee8bf15254714133226ec10481a0d8ee0626a870f6842d7cc9fdbebcb93b7d34c4781cb56d87c8ed74cab77e735b2572fa473cdec00572ed94bbce17ed1d6b80e3286e9e214b6fb31083c0b1a16b9a0d6ce4a2e2990f72b44ca70c08cc6d9378bdbce9aceb8cdc0022b2bb771ece3da5ff119d9ef8fe3ecbefd25dddf410bbea8519d78d6c123bda67fdff709ebf08ace273564788fb3add8ffda51bacd51748939d32fc5243c393f1dd5aea34ea0a688d04ce894331721ac280a8b08321ef4b68092173a4a3a02511e96ecbcbcff77aed356f77c35b3de4bbfc82a056f36455d678da500bbabb9234e3dc357308da500352c4da3c8f723a9972696e7ee2c2f7ae13b29b9b3c87244bc3b167955197feae0b6f15332172c72b0dadae86c6b5de5779fd445e2b7b156fac2bd210dfb49d9aa2a094cc9b720cb8be826e080feae40743f9be9bcb725b33e6a07670d5e465197ec6c3aeb77280680f221ce90b1ecaa9549c83e8e54ec35c01febb10f21f188be74343972135b5d3c58e6a94b0acb8cca44899e9718cc860f7aedad6e037ffb55e706079125ba568deb975dda96cef6fc681a3afec16a4d1248e6bc4a518c886cf761b83fd72fa5d7b41151235c68f60cc3b084a3c77f6b7680e04548640b192c647933f2a6f3e5aab67d5cb7377f27921e9cec443ed93f0b44a453029e230883f1516a13c23bf339e1f5f7f34680301f6f835ec89eabe8951e7708af1f9afcc5eb858356c72a5f65db1343968f268b9d07e4d145cdae0c1a85f9cc75e489a1dab37b78954723b890379453cc17104552ef60c59969242c00092e792461ad95ce3aaa1ff4739dd15bef6327b892ed2205da3da150558eb8c7aff910b7fa35d6306a23da62572685fa412847f54a7c63bb35f2482e35261f847623360618c15f89735ed7add722f19c1793c202f92732703c7dd574e02409f6eec353d9f45a1e5b621d42c6b7237e11179b43bfdb00d6ea18980a62121c34c137d6113c444096e09b938236f72e7e035523bc768f29999c4f4409c0e2750e8e842618d73e9905ce8fe20f2052b16cd9be156b3c054e2288a78ac3e896099c536663ff0eceb0e1bfa42239cdf217a344562832237c6fc33f17138071f4f75331aa0116592863fc353e1659c68154f34d31c6a0931532dd4d951e976b67880915c41059eea23dd65f66b6308d472b5e85bba9f37164f32b14230c68c81c57a1a824d6af71e354060abed70f45272756cb0ad33f26867a43738d443da2a9aba11b68528e9b466617e5f9a81130b4d8886d705d072cee0f808058614c05b859105e7dd15cdb7b205140af103d74972ff6a349f2b6e30f1c8cac06924b2d7c8acc97aa480efca1a0d519d8faeb686687868834d40b060bb4c90bf07ce929ca1a9fe1690238f8af5ce8c266405d05c69b63fb4cbf119286c963aaf30126892d9e469b31bb209ed6145eb6024020fa66367f89c1396596e7df71e2f95b5e7d12a1fb62e78171f2fb14ac9646ebadd3c1554ecc052ea45197010f7c49d93c228adeb6738b4ef7146faa88ef0774fc77872a7835b3e6e03f4fd0b12299ac7667f49cc5794bf1b04507fff9602a16994877251a8b373dc0b22c1e040050c8956b665f5091e2f055d2c1cb883f7405a161c53905473d52593a97681bf9bda9a0150d4edf92a85459b8f0ffbcef2e3ebd4657253ba3a6e3505e20e702eb8aefd14263c649e2d54c606c8e036dc2621bb137072495a6f0a7fbc99300fff99ed6acafb17a5832243d7369c47d7a9045bfea0ea4804cfe34976f79c9a7e57378e08b430c9e82d0cd344883708cfa6239590cc5372bba0f2db39cdc02f6e633388045eab2d2243df8646be12b79df88274792b912a0442f99a00af75fc4c07b04b0739bbfaff19c883a06384d8464836334150d521c720e9a386cc7b100be86b93dc157a91270d351a350dcbee56fd78212525f57255fc43118b6deb9e30e1f7044072f957c716b44f7598ef9dd40dd9b06a8b7e7264f849ea107c983b42314ac031c7a5a25d466a5d19571da9c27f9ce71f0e5b72bd0425162e499a770bf209c9ee8e5786ab4e19f7042ef2a0a00f1c4059ac146e860dfeb055d2a3b85197a36097b259185e02e7c565a3a9e553f301a017868d727abcca18b32d90469310efd654db6cf1d8fa114d7734ac9f3a13ce3c0e7bec723ec0849105b30de714013802b50c38390cc23b2959df96e18027fd5fb76a16722d559b13947c72c9692c0474ab3a05ce6e141c2785875892e892bebe6566b9728b054a7f1211198291ce4e3de783b2a166ec59e3938351994497d9798c07284d7f391060b0726ba0f4f3bc4886f74489af7e3e2e6babb7263d65e03c82359949e8decfe33ded713a1e921deb56e860b441b706650972f9b624f3fc0a1c9920721b44e38369993dce2b4c8bcb7b59abe1b4eeeb9d33466774de69f1abec993a516d7ba57632412638ebc79c16dc287bcd53c1941d75934c124e7af124e0381038dc1e643015501dcf6de259e7127adb3e51760502255b6c84aee24bd1b6efac72fcc521fc6ce8a964c6cdc94bb22a665e8ec70e93ceaf851df3fd2989fa0a1a639645d809e4a3a8867e758d71c7436900c6fc95b7eff145011fe0346012e7d672d057a193571743a0d847fccbc1bc80198e82fe77984fb3839fbcac48350bdb72e3583ea7250446e7ea2caf5548d6fd47113642341d7c6fc02301782635bef0255f6dd2489b3635015ec257cd2b685c3e74f6302e49a1bffebb8758a1c7031472b8408603ce9b0f3015484e2083261d1218969cc730afaeb3d8050030b173bb6d9df59965bb405329ece6c5420a5ea95705926d1d1f68b74315739e746f4c7272626bd2046feb7bea1eb199fdc7d7dc9a5c0178ee39523eb0200cf30d0248ca720ee0476e776df6e46ead7988851294001da6466c1d6792d6e27467fe14ad15722a9e124ef5c068082ea0a9f5b466b49ba9b7e1fa48a5c19a465c0f7b54262b7258ca7489c9c8ae6c6fea0f849c5806eff9eecee8991ae73e5e56bc4043541a726032473599b31c08eddc91a0ca57aff91608d373aaa646ccb84a6491bade16255a233b9ff986a5ffb480595d9fa74670f98b613aa370e6fa80bc09c2172bd5338a80a68659cac8bc6779ccdb84dca7f307428e208c577888e47604df036ea272616654221f83e46a6f17922b9eebb45604a234da9c5e0630345240376378b42c43cfd2de5df271a183792cb139ee3937800a9028e43f4edca87f05e223329d6c02fd70152e82ba3bfc399ca6aaf73d617a529a38ade2048ef7a562404c1e3966904d29e99c7164103383f806d4c99098c8baa626379b0021e9a08586d113a86e0cb7d74be7440dcb43e5c39dc7de5aa613c84eabf0303aa9645c4a1397c8a113bcedf43136f40f4243e07db6b8bc54f4f3c0dd896251ae8b3cdd25618c0593723b5d4a286c71c238487057bf881573a3420f0a00fa5f5bd0295f5dab0b55e072de12254ea4157a59f3f10a5792bd3580dbc3bdd4a46d13a8984a8f737903bf37ed0343d8cb2179b221ccc14912f0d3cba1eddb9970b700cbd1100e9c91a24b727ba547d8a48eb9884c453526c4e91263c2b5184f3c16439c0d5c660c054a900c0a445fe89ed0b5215c86ba38fb2149aff33260a1e2059fd99512d1a5e0191b722672c4f3a4b1ba992c5e61bffa5c1ea3eb991789eb0bd3a5c06587d5cb88827287b425981b3db7994cb3e77d1509bfd2439f0126f20421396b1d01fe77095443a6034ed59d72e3120bfb4a50a321b19d2838bd402212323dcb735bed1fe33647169b37c6eea04e56acdc7eb4ba59c4b23a494bd050dc480a189c837cdc360d724091980365a0ff755aca5539e1bbdd1b84048ecbb10cbe3c4f0f1efe59f7d46bd0b680e987ec2ce39a1fcb917f822ec7733fa023c490172c3a213ff44d4d6f39a3d575d40544d292c03a8583eb25c92843d2cc191578a83920c639a567d54e3c1b3d39c5055e3db59d2531d95b8f0e9d076e98852809011f941369264201847274e295b8ddbbe883d70a2ca57e48ebc6762d7c310e80b259b399155fede11572d96eb8028b3ead10177dc507d4aa30efac79348377465cd1c35fce116312af2ba1b4f8cf253dc8c6b7292e26658785339d48906bbd7f2c28e3353a882d152f1bdb923db79993bce891f90faa74c8fd135bc7449b886960d068720975d730412b6dfbf63e544f58c85d01e41c6487bda23a5e6f0145843f297ea061a818bf55729ef3be1a8f383653e5e2835ff787117dc96805c8e46b058a5a24847341611b727b99753865b9cef573a4ebcbbf03218e9948875aa1346261f52b7ce112b8897280e62ea4fc2bd3f93be79b34f13ca109943e3f1ada9ce4beb364542abf98711698045fbd1be9be559fa6396bde2c704ba4e6736569dc89ed3626579fa51c9f724b31f672d36aef974cc37c67c8a50f8b06f923f1478ecb93ff1b66c52a0e644663a1831c0c45b2ed8db81ab5cbebbe6082dbb6538a2309e63805741708b605552fed26d5391be0e4ee3e0ec2e8d8b5eb334df8294b7d0f9e17cfd5e3d536f46745d003d7fa6e2028327efffc2991754ce3c0d49ba586daeedcb108043420cd"]}}} - - - -<< (9ee7c86c) {"jsonrpc":"2.0","id":12,"result":{"executionPayload":{"parentHash":"0x90c8b9e1e8e51584453f5516036bfe0031ebb595e47c5fd664bd56471ce618d6","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0xfb58f52e7d31c6d3232b10a33799f7b5b8f7b883a8cbdd0c41cdefb207de4648","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xe7f1a1fb5c08adeb1e62a01f58dda79626f9ab56bce81f8ffa587b3045e007bb","gasLimit":"0x2ffbd8","gasUsed":"0x0","timestamp":"0x1237","extraData":"0x","baseFeePerGas":"0x28482258","excessBlobGas":"0x0","parentBeaconBlockRoot":"0xe19f3a77f925a661b19f84ba74acddd84c121b4813b7470d91df7d3270667511","transactions":[],"withdrawals":[],"blockNumber":"0x3","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","blobGasUsed":"0x0","blockHash":"0x69607c0fc6af60144538ad4c3028d0ac86baa0cd45fbc1c080b2d5f095811c84"},"blockValue":"0x0","blobsBundle":{"commitments":[],"proofs":[],"blobs":[]}}} diff --git a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/block3 b/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/block3 deleted file mode 100644 index 2e1910f41b6..00000000000 --- a/ethereum/referencetests/src/main/java/org/hyperledger/besu/ethereum/referencetests/block3 +++ /dev/null @@ -1 +0,0 @@ -<< (9ee7c86c) {"jsonrpc":"2.0","id":12,"result":{"executionPayload":{"parentHash":"0x90c8b9e1e8e51584453f5516036bfe0031ebb595e47c5fd664bd56471ce618d6","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0xfb58f52e7d31c6d3232b10a33799f7b5b8f7b883a8cbdd0c41cdefb207de4648","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xe7f1a1fb5c08adeb1e62a01f58dda79626f9ab56bce81f8ffa587b3045e007bb","gasLimit":"0x2ffbd8","gasUsed":"0x0","timestamp":"0x1237","extraData":"0x","baseFeePerGas":"0x28482258","excessBlobGas":"0x0","parentBeaconBlockRoot":"0xe19f3a77f925a661b19f84ba74acddd84c121b4813b7470d91df7d3270667511","transactions":[],"withdrawals":[],"blockNumber":"0x3","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","blobGasUsed":"0x0","blockHash":"0x69607c0fc6af60144538ad4c3028d0ac86baa0cd45fbc1c080b2d5f095811c84"},"blockValue":"0x0","blobsBundle":{"commitments":[],"proofs":[],"blobs":[]}}} diff --git a/ethereum/referencetests/src/reference-test/external-resources b/ethereum/referencetests/src/reference-test/external-resources index 428f218d7d6..92010754908 160000 --- a/ethereum/referencetests/src/reference-test/external-resources +++ b/ethereum/referencetests/src/reference-test/external-resources @@ -1 +1 @@ -Subproject commit 428f218d7d6f4a52544e12684afbfe6e2882ffbf +Subproject commit 9201075490807f58811078e9bb5ec895b4ac01a5 diff --git a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/core/TransactionTest.java b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/core/TransactionTest.java index d36a0b6a87d..9aa04bd0ffe 100644 --- a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/core/TransactionTest.java +++ b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/core/TransactionTest.java @@ -26,6 +26,7 @@ import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; import org.hyperledger.besu.evm.gascalculator.BerlinGasCalculator; import org.hyperledger.besu.evm.gascalculator.ByzantiumGasCalculator; +import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator; import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator; import org.hyperledger.besu.evm.gascalculator.FrontierGasCalculator; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -33,6 +34,8 @@ import org.hyperledger.besu.evm.gascalculator.IstanbulGasCalculator; import org.hyperledger.besu.evm.gascalculator.LondonGasCalculator; import org.hyperledger.besu.evm.gascalculator.PetersburgGasCalculator; +import org.hyperledger.besu.evm.gascalculator.PragueGasCalculator; +import org.hyperledger.besu.evm.gascalculator.ShanghaiGasCalculator; import org.hyperledger.besu.evm.gascalculator.SpuriousDragonGasCalculator; import org.hyperledger.besu.evm.gascalculator.TangerineWhistleGasCalculator; import org.hyperledger.besu.testutil.JsonTestParameters; @@ -47,14 +50,12 @@ public class TransactionTest { - private static final ReferenceTestProtocolSchedules REFERENCE_TEST_PROTOCOL_SCHEDULES = - ReferenceTestProtocolSchedules.create(); - private static TransactionValidator transactionValidator(final String name) { - return REFERENCE_TEST_PROTOCOL_SCHEDULES + return ReferenceTestProtocolSchedules.getInstance() .getByName(name) .getByBlockHeader(BlockHeaderBuilder.createDefault().buildBlockHeader()) - .getTransactionValidatorFactory().get(); + .getTransactionValidatorFactory() + .get(); } private static final String TEST_CONFIG_FILE_DIR_PATH = "TransactionTests/"; @@ -62,7 +63,9 @@ private static TransactionValidator transactionValidator(final String name) { public static Stream getTestParametersForConfig() { return JsonTestParameters.create(TransactionTestCaseSpec.class) .generator((name, fullPath, spec, collector) -> collector.add(name, fullPath, spec, true)) - .generate(TEST_CONFIG_FILE_DIR_PATH).stream().map(params -> Arguments.of(params[0], params[1])); + .generate(TEST_CONFIG_FILE_DIR_PATH) + .stream() + .map(params -> Arguments.of(params[0], params[1])); } @ParameterizedTest(name = "Name: {0}") @@ -125,8 +128,36 @@ public void london(final String name, final TransactionTestCaseSpec spec) { milestone(spec, name, "London", new LondonGasCalculator(), Optional.of(Wei.of(0))); } + @ParameterizedTest(name = "Name: {0}") + @MethodSource("getTestParametersForConfig") + public void merge(final String name, final TransactionTestCaseSpec spec) { + milestone(spec, name, "Merge", new LondonGasCalculator(), Optional.of(Wei.of(0))); + } + + @ParameterizedTest(name = "Name: {0}") + @MethodSource("getTestParametersForConfig") + public void shanghai(final String name, final TransactionTestCaseSpec spec) { + milestone(spec, name, "Shanghai", new ShanghaiGasCalculator(), Optional.of(Wei.of(0))); + } + + @ParameterizedTest(name = "Name: {0}") + @MethodSource("getTestParametersForConfig") + public void cancun(final String name, final TransactionTestCaseSpec spec) { + milestone(spec, name, "Cancun", new CancunGasCalculator(), Optional.of(Wei.of(0))); + } + + @ParameterizedTest(name = "Name: {0}") + @MethodSource("getTestParametersForConfig") + public void prague(final String name, final TransactionTestCaseSpec spec) { + milestone(spec, name, "Prague", new PragueGasCalculator(), Optional.of(Wei.of(0))); + } + public void milestone( - final TransactionTestCaseSpec spec, final String name, final String milestone, final GasCalculator gasCalculator, final Optional baseFee) { + final TransactionTestCaseSpec spec, + final String name, + final String milestone, + final GasCalculator gasCalculator, + final Optional baseFee) { final TransactionTestCaseSpec.Expectation expected = spec.expectation(milestone); @@ -144,7 +175,7 @@ public void milestone( final Transaction transaction = Transaction.readFrom(RLP.input(rlp)); final ValidationResult validation = transactionValidator(milestone) - .validate(transaction, baseFee, TransactionValidationParams.processingBlock()); + .validate(transaction, baseFee, Optional.empty(), TransactionValidationParams.processingBlock()); if (!validation.isValid()) { throw new RuntimeException( String.format( diff --git a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/core/TransactionTestCaseSpec.java b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/core/TransactionTestCaseSpec.java index 7f9c3cb7c63..7ce7384e1ab 100644 --- a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/core/TransactionTestCaseSpec.java +++ b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/core/TransactionTestCaseSpec.java @@ -24,6 +24,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Assumptions; /** A Transaction test case specification. */ @JsonIgnoreProperties({"_info"}) @@ -109,9 +110,7 @@ public Bytes getRlp() { public Expectation expectation(final String milestone) { final Expectation expectation = expectations.get(milestone); - if (expectation == null) { - throw new IllegalStateException("Expectation for milestone " + milestone + " not found"); - } + Assumptions.assumeFalse(expectation == null, () -> "No expectation for milestone " + milestone); return expectation; } diff --git a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/eof/EOFReferenceTestTools.java b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/eof/EOFReferenceTestTools.java new file mode 100644 index 00000000000..6a736831ff4 --- /dev/null +++ b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/eof/EOFReferenceTestTools.java @@ -0,0 +1,189 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eof; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; + +import com.google.common.base.Splitter; +import org.apache.tuweni.bytes.Bytes; + +import org.hyperledger.besu.ethereum.referencetests.EOFTestCaseSpec; +import org.hyperledger.besu.ethereum.referencetests.EOFTestCaseSpec.TestResult; +import org.hyperledger.besu.ethereum.referencetests.ReferenceTestProtocolSchedules; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.code.CodeInvalid; +import org.hyperledger.besu.evm.code.EOFLayout; +import org.hyperledger.besu.testutil.JsonTestParameters; + +public class EOFReferenceTestTools { + private static final List EIPS_TO_RUN; + + static { + final String eips = + System.getProperty("test.ethereum.eof.eips", "Prague,Osaka,Amsterdam,Bogota,Polis,Bangkok"); + EIPS_TO_RUN = Arrays.asList(eips.split(",")); + } + + private static final JsonTestParameters params = + JsonTestParameters.create(EOFTestCaseSpec.class, EOFTestCaseSpec.TestResult.class) + .generator( + (testName, fullPath, eofSpec, collector) -> { + if (eofSpec.getVector() == null) { + return; + } + final Path path = Path.of(fullPath).getParent().getFileName(); + final String prefix = path + "/" + testName + "-"; + for (final Map.Entry entry : + eofSpec.getVector().entrySet()) { + final String name = entry.getKey(); + final Bytes code = Bytes.fromHexString(entry.getValue().code()); + final String containerKind = entry.getValue().containerKind(); + for (final Entry result : + entry.getValue().results().entrySet()) { + final String eip = result.getKey(); + final boolean runTest = EIPS_TO_RUN.contains(eip); + collector.add( + prefix + eip + '[' + name + ']', + fullPath, + eip, + code, + containerKind, + result.getValue(), + runTest); + } + } + }); + + static { + if (EIPS_TO_RUN.isEmpty()) { + params.ignoreAll(); + } + } + + private EOFReferenceTestTools() { + // utility class + } + + // + public static Collection generateTestParametersForConfig(final String[] filePath) { + return params.generate(filePath); + } + + @SuppressWarnings("java:S5960") // This is not production code, this is testing code. + public static void executeTest( + final String name, + final String fork, + final Bytes code, + final String containerKind, + final EOFTestCaseSpec.TestResult expected) { + EVM evm = ReferenceTestProtocolSchedules.getInstance().geSpecByName(fork).getEvm(); + assertThat(evm).isNotNull(); + + // hardwire in the magic byte transaction checks + if (evm.getMaxEOFVersion() < 1) { + assertThat(expected.exception()).isEqualTo("EOF_InvalidCode"); + } else if (code.size() > evm.getMaxInitcodeSize()) { + // this check is in EOFCREATE and Transaction validator, but unit tests sniff it out. + assertThat(false) + .withFailMessage( + () -> + "No Expected exception, actual exception - container_size_above_limit " + + code.size()) + .isEqualTo(expected.result()); + if (name.contains("eip7692")) { + // if the test is from EEST, validate the exception name. + assertThat("container_size_above_limit") + .withFailMessage( + () -> + "Expected exception: %s actual exception: %s %d" + .formatted( + expected.exception(), "container_size_above_limit ", code.size())) + .containsIgnoringCase(expected.exception().replace("EOFException.", "")); + } + + } else { + EOFLayout layout = EOFLayout.parseEOF(code); + + if (layout.isValid()) { + Code parsedCode; + if ("INITCODE".equals(containerKind)) { + parsedCode = evm.getCodeForCreation(code); + } else { + parsedCode = evm.getCodeUncached(code); + } + if (expected.result()) { + assertThat(parsedCode.isValid()) + .withFailMessage( + () -> "Valid code failed with " + ((CodeInvalid) parsedCode).getInvalidReason()) + .isTrue(); + } else { + assertThat(parsedCode.isValid()) + .withFailMessage("Invalid code expected " + expected.exception() + " but was valid") + .isFalse(); + if (name.contains("eip7692")) { + // if the test is from EEST, validate the exception name. + assertThat(((CodeInvalid) parsedCode).getInvalidReason()) + .withFailMessage( + () -> + "Expected exception :%s actual exception: %s" + .formatted( + expected.exception(), + (parsedCode.isValid() + ? null + : ((CodeInvalid) parsedCode).getInvalidReason()))) + .containsIgnoringCase(expected.exception().replace("EOFException.", "")); + } + } + } else { + assertThat(false) + .withFailMessage( + () -> + "Expected exception - " + + expected.exception() + + " actual exception - " + + (layout.isValid() ? null : layout.invalidReason())) + .isEqualTo(expected.result()); + if (name.contains("eip7692")) { + // if the test is from EEST, validate the exception name. + boolean exceptionMatched = false; + for (String e : Splitter.on('|').split(expected.exception())) { + if (layout + .invalidReason() + .toLowerCase(Locale.ROOT) + .contains(e.replace("EOFException.", "").toLowerCase(Locale.ROOT))) { + exceptionMatched = true; + break; + } + } + assertThat(exceptionMatched) + .withFailMessage( + () -> + "Expected exception :%s actual exception: %s" + .formatted(expected.exception(), layout.invalidReason())) + .isTrue(); + } + } + } + } +} diff --git a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/mainnet/DifficultyCalculatorTests.java b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/mainnet/DifficultyCalculatorTests.java index da9bcda0b14..cfb56d1ebc1 100644 --- a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/mainnet/DifficultyCalculatorTests.java +++ b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/mainnet/DifficultyCalculatorTests.java @@ -18,19 +18,24 @@ import static org.assertj.core.api.Assertions.assertThat; +import org.checkerframework.checker.units.qual.N; import org.hyperledger.besu.config.GenesisConfigFile; import org.hyperledger.besu.config.JsonUtil; import org.hyperledger.besu.config.StubGenesisConfigOptions; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.core.MiningParameters; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.log.LogsBloomFilter; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.stream.Stream; @@ -39,6 +44,7 @@ import com.google.common.io.Resources; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -46,70 +52,73 @@ public class DifficultyCalculatorTests { public static Stream getTestParametersForConfig() throws IOException { + Map postMergeOverrides = new HashMap<>(); + postMergeOverrides.put("shanghaiTime", "999999999999"); + postMergeOverrides.put("cancunTime","999999999999"); return Stream.of( Arguments.of( "/BasicTests/difficultyMainNetwork.json", MainnetProtocolSchedule.fromConfig( GenesisConfigFile.mainnet() - .getConfigOptions(Map.of("shanghaiTime", "999999999999")), - EvmConfiguration.DEFAULT)), + .withOverrides(postMergeOverrides).getConfigOptions(), + EvmConfiguration.DEFAULT, MiningParameters.newDefault(), new BadBlockManager(), false, new NoOpMetricsSystem())), Arguments.of( "/DifficultyTests/dfGrayGlacier/difficultyGrayGlacierForkBlock.json", MainnetProtocolSchedule.fromConfig( - new StubGenesisConfigOptions().grayGlacierBlock(15050000)) + new StubGenesisConfigOptions().grayGlacierBlock(15050000), MiningParameters.newDefault(), new BadBlockManager(), false, new NoOpMetricsSystem()) ), Arguments.of( "/DifficultyTests/dfGrayGlacier/difficultyGrayGlacierTimeDiff1.json", MainnetProtocolSchedule.fromConfig( - new StubGenesisConfigOptions().grayGlacierBlock(15050000)) + new StubGenesisConfigOptions().grayGlacierBlock(15050000), MiningParameters.newDefault(), new BadBlockManager(), false, new NoOpMetricsSystem()) ), Arguments.of( "/DifficultyTests/dfGrayGlacier/difficultyGrayGlacierTimeDiff2.json", MainnetProtocolSchedule.fromConfig( - new StubGenesisConfigOptions().grayGlacierBlock(15050000)) + new StubGenesisConfigOptions().grayGlacierBlock(15050000), MiningParameters.newDefault(), new BadBlockManager(), false, new NoOpMetricsSystem()) ), Arguments.of( "/DifficultyTests/dfArrowGlacier/difficultyArrowGlacierForkBlock.json", MainnetProtocolSchedule.fromConfig( - new StubGenesisConfigOptions().arrowGlacierBlock(13773000)) + new StubGenesisConfigOptions().arrowGlacierBlock(13773000), MiningParameters.newDefault(), new BadBlockManager(), false, new NoOpMetricsSystem()) ), Arguments.of( "/DifficultyTests/dfArrowGlacier/difficultyArrowGlacierTimeDiff1.json", MainnetProtocolSchedule.fromConfig( - new StubGenesisConfigOptions().arrowGlacierBlock(13773000)) + new StubGenesisConfigOptions().arrowGlacierBlock(13773000), MiningParameters.newDefault(), new BadBlockManager(), false, new NoOpMetricsSystem()) ), Arguments.of( "/DifficultyTests/dfArrowGlacier/difficultyArrowGlacierTimeDiff2.json", MainnetProtocolSchedule.fromConfig( - new StubGenesisConfigOptions().arrowGlacierBlock(13773000)) + new StubGenesisConfigOptions().arrowGlacierBlock(13773000), MiningParameters.newDefault(), new BadBlockManager(), false, new NoOpMetricsSystem()) ), Arguments.of( "/DifficultyTests/dfByzantium/difficultyByzantium.json", - MainnetProtocolSchedule.fromConfig(new StubGenesisConfigOptions().byzantiumBlock(0)) + MainnetProtocolSchedule.fromConfig(new StubGenesisConfigOptions().byzantiumBlock(0), MiningParameters.newDefault(), new BadBlockManager(), false, new NoOpMetricsSystem()) ), Arguments.of( "/DifficultyTests/dfConstantinople/difficultyConstantinople.json", - MainnetProtocolSchedule.fromConfig(new StubGenesisConfigOptions().constantinopleBlock(0)) + MainnetProtocolSchedule.fromConfig(new StubGenesisConfigOptions().constantinopleBlock(0), MiningParameters.newDefault(), new BadBlockManager(), false, new NoOpMetricsSystem()) ), Arguments.of( "/DifficultyTests/dfEIP2384/difficultyEIP2384.json", - MainnetProtocolSchedule.fromConfig(new StubGenesisConfigOptions().muirGlacierBlock(0)) + MainnetProtocolSchedule.fromConfig(new StubGenesisConfigOptions().muirGlacierBlock(0), MiningParameters.newDefault(), new BadBlockManager(), false, new NoOpMetricsSystem()) ), Arguments.of( "/DifficultyTests/dfEIP2384/difficultyEIP2384_random.json", - MainnetProtocolSchedule.fromConfig(new StubGenesisConfigOptions().muirGlacierBlock(0)) + MainnetProtocolSchedule.fromConfig(new StubGenesisConfigOptions().muirGlacierBlock(0), MiningParameters.newDefault(), new BadBlockManager(), false, new NoOpMetricsSystem()) ), Arguments.of( "/DifficultyTests/dfEIP2384/difficultyEIP2384_random_to20M.json", - MainnetProtocolSchedule.fromConfig(new StubGenesisConfigOptions().muirGlacierBlock(0)) + MainnetProtocolSchedule.fromConfig(new StubGenesisConfigOptions().muirGlacierBlock(0), MiningParameters.newDefault(), new BadBlockManager(), false, new NoOpMetricsSystem()) ), Arguments.of( "/DifficultyTests/dfFrontier/difficultyFrontier.json", - MainnetProtocolSchedule.fromConfig(new StubGenesisConfigOptions()) + MainnetProtocolSchedule.fromConfig(new StubGenesisConfigOptions(), MiningParameters.newDefault(), new BadBlockManager(), false, new NoOpMetricsSystem()) ), Arguments.of( "/DifficultyTests/dfHomestead/difficultyHomestead.json", - MainnetProtocolSchedule.fromConfig(new StubGenesisConfigOptions().homesteadBlock(0)) + MainnetProtocolSchedule.fromConfig(new StubGenesisConfigOptions().homesteadBlock(0), MiningParameters.newDefault(), new BadBlockManager(), false, new NoOpMetricsSystem()) )); } diff --git a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/rlp/RLPRefTest.java b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/rlp/RLPRefTest.java index a4eff43c98d..b6d01dae83f 100644 --- a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/rlp/RLPRefTest.java +++ b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/rlp/RLPRefTest.java @@ -16,12 +16,13 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue; -import org.assertj.core.api.Assertions; import org.hyperledger.besu.ethereum.rlp.util.RLPTestUtil; import org.hyperledger.besu.testutil.JsonTestParameters; import java.util.stream.Stream; +import org.apache.tuweni.bytes.Bytes; +import org.assertj.core.api.Assertions; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -29,23 +30,45 @@ /** The Ethereum reference RLP tests. */ public class RLPRefTest { - private static final String TEST_CONFIG_FILES = "RLPTests/rlptest.json"; + private static final String[] TEST_CONFIG_FILES = { + "RLPTests/rlptest.json", "RLPTests/invalidRLPTest.json" + }; + + private static final Bytes INVALID = Bytes.fromHexString("0x494e56414c4944"); public static Stream getTestParametersForConfig() { - return JsonTestParameters.create(RLPRefTestCaseSpec.class).generate(TEST_CONFIG_FILES).stream().map(params -> Arguments.of(params[0], params[1], params[2])); + return JsonTestParameters.create(RLPRefTestCaseSpec.class).generate(TEST_CONFIG_FILES).stream() + .map(params -> Arguments.of(params[0], params[1], params[2])); } @ParameterizedTest(name = "Name: {0}") @MethodSource("getTestParametersForConfig") public void encode(final String name, final RLPRefTestCaseSpec spec, final boolean runTest) { assumeTrue(runTest, "Test was blacklisted"); - Assertions.assertThat(RLPTestUtil.encode(spec.getIn())).isEqualTo(spec.getOut()); + if (!spec.getIn().equals(INVALID)) { + Assertions.assertThat(RLPTestUtil.encode(spec.getIn())).isEqualTo(spec.getOut()); + } } @ParameterizedTest(name = "Name: {0}") @MethodSource("getTestParametersForConfig") public void decode(final String name, final RLPRefTestCaseSpec spec, final boolean runTest) { assumeTrue(runTest, "Test was blacklisted"); - Assertions.assertThat(RLPTestUtil.decode(spec.getOut())).isEqualTo(spec.getIn()); + if (spec.getIn().equals(INVALID)) { + Assertions.assertThatThrownBy(() -> RLPTestUtil.decode(spec.getOut())) + .isInstanceOf(RLPException.class); + Assertions.assertThatThrownBy(() -> decode2(RLP.input(spec.getOut()))) + .isInstanceOf(RLPException.class); + } else { + Assertions.assertThat(RLPTestUtil.decode(spec.getOut())).isEqualTo(spec.getIn()); + } + } + + private static Object decode2(final RLPInput in) { + if (in.nextIsList()) { + return in.readList(RLPRefTest::decode2); + } else { + return in.readBytes(); + } } } diff --git a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/BlockchainReferenceTestTools.java b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/BlockchainReferenceTestTools.java index fea3d8671da..aabc6740870 100644 --- a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/BlockchainReferenceTestTools.java +++ b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/BlockchainReferenceTestTools.java @@ -44,8 +44,6 @@ import org.assertj.core.api.Assertions; public class BlockchainReferenceTestTools { - private static final ReferenceTestProtocolSchedules REFERENCE_TEST_PROTOCOL_SCHEDULES = - ReferenceTestProtocolSchedules.create(); private static final List NETWORKS_TO_RUN; @@ -53,9 +51,9 @@ public class BlockchainReferenceTestTools { final String networks = System.getProperty( "test.ethereum.blockchain.eips", - "FrontierToHomesteadAt5,HomesteadToEIP150At5,HomesteadToDaoAt5,EIP158ToByzantiumAt5," + "FrontierToHomesteadAt5,HomesteadToEIP150At5,HomesteadToDaoAt5,EIP158ToByzantiumAt5,CancunToPragueAtTime15k" + "Frontier,Homestead,EIP150,EIP158,Byzantium,Constantinople,ConstantinopleFix,Istanbul,Berlin," - + "London,Merge,Shanghai,Cancun,Prague,Osaka,Bogota"); + + "London,Merge,Paris,Shanghai,Cancun,Prague,Osaka,Amsterdam,Bogota,Polis,Bangkok"); NETWORKS_TO_RUN = Arrays.asList(networks.split(",")); } @@ -75,22 +73,26 @@ public class BlockchainReferenceTestTools { // Consumes a huge amount of memory params.ignore("static_Call1MB1024Calldepth_d1g0v0_\\w+"); - params.ignore("ShanghaiLove_.*"); + params.ignore("ShanghaiLove_"); // Absurd amount of gas, doesn't run in parallel params.ignore("randomStatetest94_\\w+"); // Don't do time-consuming tests - params.ignore("CALLBlake2f_MaxRounds.*"); - params.ignore("loopMul_*"); + params.ignore("CALLBlake2f_MaxRounds"); + params.ignore("loopMul_"); // Inconclusive fork choice rule, since in merge CL should be choosing forks and setting the // chain head. // Perfectly valid test pre-merge. - params.ignore("UncleFromSideChain_(Merge|Shanghai|Cancun|Prague|Osaka|Bogota)"); + params.ignore( + "UncleFromSideChain_(Merge|Paris|Shanghai|Cancun|Prague|Osaka|Amsterdam|Bogota|Polis|Bangkok)"); - // EOF tests are written against an older version of the spec + // EOF tests don't have Prague stuff like deposits right now params.ignore("/stEOF/"); + + // None of the Prague tests have withdrawals and deposits handling + params.ignore("\\[Prague\\]"); } private BlockchainReferenceTestTools() { @@ -101,15 +103,16 @@ public static Collection generateTestParametersForConfig(final String[ return params.generate(filePath); } + @SuppressWarnings("java:S5960") // this is actually test code public static void executeTest(final BlockchainReferenceTestCaseSpec spec) { final BlockHeader genesisBlockHeader = spec.getGenesisBlockHeader(); final MutableWorldState worldState = spec.getWorldStateArchive() .getMutable(genesisBlockHeader.getStateRoot(), genesisBlockHeader.getHash()) - .get(); + .orElseThrow(); final ProtocolSchedule schedule = - REFERENCE_TEST_PROTOCOL_SCHEDULES.getByName(spec.getNetwork()); + ReferenceTestProtocolSchedules.getInstance().getByName(spec.getNetwork()); final MutableBlockchain blockchain = spec.getBlockchain(); final ProtocolContext context = spec.getProtocolContext(); @@ -126,18 +129,7 @@ public static void executeTest(final BlockchainReferenceTestCaseSpec spec) { final ProtocolSpec protocolSpec = schedule.getByBlockHeader(block.getHeader()); final BlockImporter blockImporter = protocolSpec.getBlockImporter(); - EVM evm = protocolSpec.getEvm(); - if (evm.getEvmConfiguration().worldUpdaterMode() == WorldUpdaterMode.JOURNALED) { - assumeThat( - worldState - .streamAccounts(Bytes32.ZERO, Integer.MAX_VALUE) - .anyMatch(AccountState::isEmpty)) - .withFailMessage("Journaled account configured and empty account detected") - .isFalse(); - assumeThat(EvmSpecVersion.SPURIOUS_DRAGON.compareTo(evm.getEvmVersion()) > 0) - .withFailMessage("Journaled account configured and fork prior to the merge specified") - .isFalse(); - } + verifyJournaledEVMAccountCompatability(worldState, protocolSpec); final HeaderValidationMode validationMode = "NoProof".equalsIgnoreCase(spec.getSealEngine()) @@ -154,4 +146,20 @@ public static void executeTest(final BlockchainReferenceTestCaseSpec spec) { Assertions.assertThat(blockchain.getChainHeadHash()).isEqualTo(spec.getLastBlockHash()); } + + static void verifyJournaledEVMAccountCompatability( + final MutableWorldState worldState, final ProtocolSpec protocolSpec) { + EVM evm = protocolSpec.getEvm(); + if (evm.getEvmConfiguration().worldUpdaterMode() == WorldUpdaterMode.JOURNALED) { + assumeThat( + worldState + .streamAccounts(Bytes32.ZERO, Integer.MAX_VALUE) + .anyMatch(AccountState::isEmpty)) + .withFailMessage("Journaled account configured and empty account detected") + .isFalse(); + assumeThat(EvmSpecVersion.SPURIOUS_DRAGON.compareTo(evm.getEvmVersion()) > 0) + .withFailMessage("Journaled account configured and fork prior to the merge specified") + .isFalse(); + } + } } diff --git a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/GeneralStateReferenceTestTools.java b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/GeneralStateReferenceTestTools.java index e0b0c7332e7..354642f1ad7 100644 --- a/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/GeneralStateReferenceTestTools.java +++ b/ethereum/referencetests/src/reference-test/java/org/hyperledger/besu/ethereum/vm/GeneralStateReferenceTestTools.java @@ -15,20 +15,18 @@ package org.hyperledger.besu.ethereum.vm; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assumptions.assumeThat; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; -import org.apache.tuweni.bytes.Bytes32; + import org.hyperledger.besu.datatypes.BlobGas; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; -import org.hyperledger.besu.ethereum.core.MutableWorldState; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; @@ -40,18 +38,12 @@ import org.hyperledger.besu.ethereum.referencetests.ReferenceTestProtocolSchedules; import org.hyperledger.besu.ethereum.referencetests.ReferenceTestWorldState; import org.hyperledger.besu.ethereum.rlp.RLP; -import org.hyperledger.besu.evm.EVM; -import org.hyperledger.besu.evm.EvmSpecVersion; import org.hyperledger.besu.evm.account.Account; -import org.hyperledger.besu.evm.account.AccountState; -import org.hyperledger.besu.evm.internal.EvmConfiguration.WorldUpdaterMode; import org.hyperledger.besu.evm.log.Log; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.hyperledger.besu.testutil.JsonTestParameters; public class GeneralStateReferenceTestTools { - private static final ReferenceTestProtocolSchedules REFERENCE_TEST_PROTOCOL_SCHEDULES = - ReferenceTestProtocolSchedules.create(); private static final List SPECS_PRIOR_TO_DELETING_EMPTY_ACCOUNTS = Arrays.asList("Frontier", "Homestead", "EIP150"); @@ -60,7 +52,7 @@ private static MainnetTransactionProcessor transactionProcessor(final String nam } private static ProtocolSpec protocolSpec(final String name) { - return REFERENCE_TEST_PROTOCOL_SCHEDULES + return ReferenceTestProtocolSchedules.getInstance() .getByName(name) .getByBlockHeader(BlockHeaderBuilder.createDefault().buildBlockHeader()); } @@ -72,7 +64,7 @@ private static ProtocolSpec protocolSpec(final String name) { System.getProperty( "test.ethereum.state.eips", "Frontier,Homestead,EIP150,EIP158,Byzantium,Constantinople,ConstantinopleFix,Istanbul,Berlin," - + "London,Merge,Shanghai,Cancun,Prague,Osaka,Bogota"); + + "London,Merge,Paris,Shanghai,Cancun,Prague,Osaka,Amsterdam,Bogota,Polis,Bangkok"); EIPS_TO_RUN = Arrays.asList(eips.split(",")); } @@ -122,24 +114,14 @@ public static Collection generateTestParametersForConfig(final String[ return params.generate(filePath); } + @SuppressWarnings("java:S5960") // this is actually test support code, not production code public static void executeTest(final GeneralStateTestCaseEipSpec spec) { final BlockHeader blockHeader = spec.getBlockHeader(); final ReferenceTestWorldState initialWorldState = spec.getInitialWorldState(); - final Transaction transaction = spec.getTransaction(); + final Transaction transaction = spec.getTransaction(0); ProtocolSpec protocolSpec = protocolSpec(spec.getFork()); - EVM evm = protocolSpec.getEvm(); - if (evm.getEvmConfiguration().worldUpdaterMode() == WorldUpdaterMode.JOURNALED) { - assumeThat( - initialWorldState - .streamAccounts(Bytes32.ZERO, Integer.MAX_VALUE) - .anyMatch(AccountState::isEmpty)) - .withFailMessage("Journaled account configured and empty account detected") - .isFalse(); - assumeThat(EvmSpecVersion.SPURIOUS_DRAGON.compareTo(evm.getEvmVersion()) > 0) - .withFailMessage("Journaled account configured and fork prior to the merge specified") - .isFalse(); - } + BlockchainReferenceTestTools.verifyJournaledEVMAccountCompatability(initialWorldState, protocolSpec); // Sometimes the tests ask us assemble an invalid transaction. If we have // no valid transaction then there is no test. GeneralBlockChain tests @@ -151,7 +133,7 @@ public static void executeTest(final GeneralStateTestCaseEipSpec spec) { return; } - final MutableWorldState worldState = initialWorldState.copy(); + final ReferenceTestWorldState worldState = initialWorldState.copy(); // Several of the GeneralStateTests check if the transaction could potentially // consume more gas than is left for the block it's attempted to be included in. // This check is performed within the `BlockImporter` rather than inside the @@ -169,7 +151,6 @@ public static void executeTest(final GeneralStateTestCaseEipSpec spec) { .blobGasPricePerGas(blockHeader.getExcessBlobGas().orElse(BlobGas.ZERO)); final TransactionProcessingResult result = processor.processTransaction( - blockchain, worldStateUpdater, blockHeader, transaction, @@ -193,6 +174,7 @@ public static void executeTest(final GeneralStateTestCaseEipSpec spec) { worldStateUpdater.deleteAccount(coinbase.getAddress()); } worldStateUpdater.commit(); + worldState.processExtraStateStorageFormatValidation(blockHeader); worldState.persist(blockHeader); // Check the world state root hash. diff --git a/ethereum/referencetests/src/reference-test/templates/BlockchainReferenceTest.java.template b/ethereum/referencetests/src/reference-test/templates/BlockchainReferenceTest.java.template index f8b99e8a735..e7789905b35 100644 --- a/ethereum/referencetests/src/reference-test/templates/BlockchainReferenceTest.java.template +++ b/ethereum/referencetests/src/reference-test/templates/BlockchainReferenceTest.java.template @@ -1,4 +1,4 @@ -package org.hyperledger.besu.ethereum.vm.blockchain; +package %%PACKAGE_NAME%%; import static org.hyperledger.besu.ethereum.vm.BlockchainReferenceTestTools.executeTest; import static org.hyperledger.besu.ethereum.vm.BlockchainReferenceTestTools.generateTestParametersForConfig; @@ -16,20 +16,20 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue; /** The blockchain test operation testing framework entry point. */ public class %%TESTS_NAME%% { - private static final String[] TEST_CONFIG_FILE_DIR_PATH = new String[] {%%TESTS_FILE%%}; + private static final String[] TEST_CONFIG_FILE_DIR_PATH = + new String[] { + %%TESTS_FILE%% + }; public static Stream getTestParametersForConfig() { - return generateTestParametersForConfig(TEST_CONFIG_FILE_DIR_PATH).stream().map(params -> - Arguments.of(params[0], params[1], params[2]) - ); + return generateTestParametersForConfig(TEST_CONFIG_FILE_DIR_PATH).stream() + .map(params -> Arguments.of(params[0], params[1], params[2])); } @ParameterizedTest(name = "Name: {0}") @MethodSource("getTestParametersForConfig") public void execution( - final String name, - final BlockchainReferenceTestCaseSpec spec, - final boolean runTest) { + final String name, final BlockchainReferenceTestCaseSpec spec, final boolean runTest) { assumeTrue(runTest, "Test " + name + " was ignored"); executeTest(spec); } diff --git a/ethereum/referencetests/src/reference-test/templates/EOFReferenceTest.java.template b/ethereum/referencetests/src/reference-test/templates/EOFReferenceTest.java.template new file mode 100644 index 00000000000..a08efec2b6b --- /dev/null +++ b/ethereum/referencetests/src/reference-test/templates/EOFReferenceTest.java.template @@ -0,0 +1,40 @@ +package org.hyperledger.besu.ethereum.vm.eof; + +import static org.hyperledger.besu.ethereum.eof.EOFReferenceTestTools.executeTest; +import static org.hyperledger.besu.ethereum.eof.EOFReferenceTestTools.generateTestParametersForConfig; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +import org.hyperledger.besu.ethereum.referencetests.EOFTestCaseSpec; + +import java.util.stream.Stream; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** The general state test operation testing framework entry point. */ +public class %%TESTS_NAME%% { + + private static final String[] TEST_CONFIG_FILE_DIR_PATH = + new String[] { + %%TESTS_FILE%% + }; + + public static Stream getTestParametersForConfig() { + return generateTestParametersForConfig(TEST_CONFIG_FILE_DIR_PATH).stream().map(Arguments::of); + } + + @ParameterizedTest(name = "Name: {0}") + @MethodSource("getTestParametersForConfig") + public void execution( + final String name, + final String fork, + final Bytes code, + final String containerKind, + final EOFTestCaseSpec.TestResult results, + final boolean runTest) { + assumeTrue(runTest, "Test " + name + " was ignored"); + executeTest(name, fork, code, containerKind, results); + } +} diff --git a/ethereum/referencetests/src/reference-test/templates/GeneralStateReferenceTest.java.template b/ethereum/referencetests/src/reference-test/templates/GeneralStateReferenceTest.java.template index 4b93b877334..139766a4b83 100644 --- a/ethereum/referencetests/src/reference-test/templates/GeneralStateReferenceTest.java.template +++ b/ethereum/referencetests/src/reference-test/templates/GeneralStateReferenceTest.java.template @@ -1,4 +1,4 @@ -package org.hyperledger.besu.ethereum.vm.generalstate; +package %%PACKAGE_NAME%%; import static org.hyperledger.besu.ethereum.vm.GeneralStateReferenceTestTools.executeTest; import static org.hyperledger.besu.ethereum.vm.GeneralStateReferenceTestTools.generateTestParametersForConfig; @@ -17,20 +17,20 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue; /** The general state test operation testing framework entry point. */ public class %%TESTS_NAME%% { - private static final String[] TEST_CONFIG_FILE_DIR_PATH = new String[] {%%TESTS_FILE%%}; + private static final String[] TEST_CONFIG_FILE_DIR_PATH = + new String[] { + %%TESTS_FILE%% + }; public static Stream getTestParametersForConfig() { - return generateTestParametersForConfig(TEST_CONFIG_FILE_DIR_PATH).stream().map(params -> - Arguments.of(params[0], params[1], params[2]) - ); + return generateTestParametersForConfig(TEST_CONFIG_FILE_DIR_PATH).stream() + .map(params -> Arguments.of(params[0], params[1], params[2])); } @ParameterizedTest(name = "Name: {0}") @MethodSource("getTestParametersForConfig") public void execution( - final String name, - final GeneralStateTestCaseEipSpec spec, - final boolean runTest) { + final String name, final GeneralStateTestCaseEipSpec spec, final boolean runTest) { assumeTrue(runTest, "Test " + name + " was ignored"); executeTest(spec); } diff --git a/ethereum/retesteth/build.gradle b/ethereum/retesteth/build.gradle index 10b978b1534..2fe2def4396 100644 --- a/ethereum/retesteth/build.gradle +++ b/ethereum/retesteth/build.gradle @@ -20,7 +20,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } diff --git a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/DummySynchronizer.java b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/DummySynchronizer.java index 6b10f2c6807..b8d90e9974a 100644 --- a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/DummySynchronizer.java +++ b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/DummySynchronizer.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.ethereum.retesteth; import org.hyperledger.besu.datatypes.Address; diff --git a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/NoRewardProtocolScheduleWrapper.java b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/NoRewardProtocolScheduleWrapper.java index f4ad7774058..99210f57d29 100644 --- a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/NoRewardProtocolScheduleWrapper.java +++ b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/NoRewardProtocolScheduleWrapper.java @@ -17,6 +17,7 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.BlockValidator; import org.hyperledger.besu.ethereum.MainnetBlockValidator; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.core.BlockHeader; import org.hyperledger.besu.ethereum.core.BlockImporter; import org.hyperledger.besu.ethereum.core.PermissionTransactionFilter; @@ -36,9 +37,12 @@ public class NoRewardProtocolScheduleWrapper implements ProtocolSchedule { private final ProtocolSchedule delegate; + private final BadBlockManager badBlockManager; - NoRewardProtocolScheduleWrapper(final ProtocolSchedule delegate) { + NoRewardProtocolScheduleWrapper( + final ProtocolSchedule delegate, final BadBlockManager badBlockManager) { this.delegate = delegate; + this.badBlockManager = badBlockManager; } @Override @@ -57,7 +61,7 @@ public ProtocolSpec getByBlockHeader(final ProcessableBlockHeader blockHeader) { original.getBlockHeaderValidator(), original.getBlockBodyValidator(), noRewardBlockProcessor, - original.getBadBlocksManager()); + badBlockManager); final BlockImporter noRewardBlockImporter = new MainnetBlockImporter(noRewardBlockValidator); return new ProtocolSpec( original.getName(), @@ -81,11 +85,12 @@ public ProtocolSpec getByBlockHeader(final ProcessableBlockHeader blockHeader) { original.getGasCalculator(), original.getGasLimitCalculator(), original.getFeeMarket(), - original.getBadBlocksManager(), Optional.empty(), original.getWithdrawalsValidator(), original.getWithdrawalsProcessor(), - original.getDepositsValidator(), + original.getRequestsValidatorCoordinator(), + original.getRequestProcessorCoordinator(), + original.getBlockHashProcessor(), original.isPoS(), original.isReplayProtectionSupported()); } diff --git a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/RetestethContext.java b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/RetestethContext.java index 2286fe67cfe..32d4e0bab10 100644 --- a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/RetestethContext.java +++ b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/RetestethContext.java @@ -24,6 +24,7 @@ import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.processor.BlockReplay; import org.hyperledger.besu.ethereum.api.query.BlockchainQueries; +import org.hyperledger.besu.ethereum.chain.BadBlockManager; import org.hyperledger.besu.ethereum.chain.DefaultBlockchain; import org.hyperledger.besu.ethereum.chain.GenesisState; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; @@ -41,12 +42,14 @@ import org.hyperledger.besu.ethereum.eth.manager.EthMessages; import org.hyperledger.besu.ethereum.eth.manager.EthPeers; import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; +import org.hyperledger.besu.ethereum.eth.sync.SyncMode; import org.hyperledger.besu.ethereum.eth.sync.state.SyncState; import org.hyperledger.besu.ethereum.eth.transactions.BlobCache; import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolFactory; +import org.hyperledger.besu.ethereum.forkid.ForkIdManager; import org.hyperledger.besu.ethereum.mainnet.EpochCalculator; import org.hyperledger.besu.ethereum.mainnet.HeaderValidationMode; import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; @@ -59,10 +62,11 @@ import org.hyperledger.besu.ethereum.mainnet.ScheduleBasedBlockHeaderFunctions; import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueStoragePrefixedKeyBlockchainStorage; import org.hyperledger.besu.ethereum.storage.keyvalue.VariablesKeyValueStorage; -import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.storage.keyvalue.WorldStatePreimageKeyValueStorage; -import org.hyperledger.besu.ethereum.worldstate.DefaultWorldStateArchive; +import org.hyperledger.besu.ethereum.trie.forest.ForestWorldStateArchive; +import org.hyperledger.besu.ethereum.trie.forest.storage.ForestWorldStateKeyValueStorage; import org.hyperledger.besu.ethereum.worldstate.WorldStateArchive; +import org.hyperledger.besu.ethereum.worldstate.WorldStateStorageCoordinator; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; @@ -71,6 +75,7 @@ import org.hyperledger.besu.util.number.Fraction; import java.util.Collections; +import java.util.List; import java.util.Optional; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Supplier; @@ -92,6 +97,7 @@ public class RetestethContext { public static final int MAX_PEERS = 25; private final ReentrantLock contextLock = new ReentrantLock(); + private final BadBlockManager badBlockManager = new BadBlockManager(); private Address coinbase; private Bytes extraData; private MutableBlockchain blockchain; @@ -154,9 +160,15 @@ private boolean buildContext( JsonGenesisConfigOptions.fromJsonObject( JsonUtil.getObjectNode(genesisConfig, "config").get()); protocolSchedule = - MainnetProtocolSchedule.fromConfig(jsonGenesisConfigOptions, EvmConfiguration.DEFAULT); + MainnetProtocolSchedule.fromConfig( + jsonGenesisConfigOptions, + EvmConfiguration.DEFAULT, + miningParameters, + badBlockManager, + false, + new NoOpMetricsSystem()); if ("NoReward".equalsIgnoreCase(sealEngine)) { - protocolSchedule = new NoRewardProtocolScheduleWrapper(protocolSchedule); + protocolSchedule = new NoRewardProtocolScheduleWrapper(protocolSchedule, badBlockManager); } blockHeaderFunctions = ScheduleBasedBlockHeaderFunctions.create(protocolSchedule); @@ -166,17 +178,20 @@ private boolean buildContext( mixHash = Optional.ofNullable(genesisState.getBlock().getHeader().getMixHashOrPrevRandao()); final WorldStateArchive worldStateArchive = - new DefaultWorldStateArchive( - new WorldStateKeyValueStorage(new InMemoryKeyValueStorage()), + new ForestWorldStateArchive( + new WorldStateStorageCoordinator( + new ForestWorldStateKeyValueStorage(new InMemoryKeyValueStorage())), new WorldStatePreimageKeyValueStorage(new InMemoryKeyValueStorage()), EvmConfiguration.DEFAULT); final MutableWorldState worldState = worldStateArchive.getMutable(); genesisState.writeStateTo(worldState); blockchain = createInMemoryBlockchain(genesisState.getBlock()); - protocolContext = new ProtocolContext(blockchain, worldStateArchive, null, Optional.empty()); + protocolContext = new ProtocolContext(blockchain, worldStateArchive, null, badBlockManager); - blockchainQueries = new BlockchainQueries(blockchain, worldStateArchive, ethScheduler); + blockchainQueries = + new BlockchainQueries( + protocolSchedule, blockchain, worldStateArchive, ethScheduler, miningParameters); final String sealengine = JsonUtil.getString(genesisConfig, "sealengine", ""); headerValidationMode = @@ -212,7 +227,8 @@ private boolean buildContext( Subscribers.none(), new EpochCalculator.DefaultEpochCalculator()); - blockReplay = new BlockReplay(protocolSchedule, blockchainQueries.getBlockchain()); + blockReplay = + new BlockReplay(protocolSchedule, protocolContext, blockchainQueries.getBlockchain()); final Bytes localNodeKey = Bytes.wrap(new byte[64]); @@ -231,8 +247,9 @@ private boolean buildContext( localNodeKey, MAX_PEERS, MAX_PEERS, - MAX_PEERS, - false); + false, + SyncMode.FAST, + new ForkIdManager(blockchain, List.of(), List.of(), false)); final SyncState syncState = new SyncState(blockchain, ethPeers); ethScheduler = new EthScheduler(1, 1, 1, 1, metricsSystem); @@ -252,8 +269,8 @@ private boolean buildContext( metricsSystem, syncState, transactionPoolConfiguration, - null, - new BlobCache()); + new BlobCache(), + MiningParameters.newDefault()); if (LOG.isTraceEnabled()) { LOG.trace("Genesis Block {} ", genesisState.getBlock()); @@ -274,7 +291,7 @@ private static MutableBlockchain createInMemoryBlockchain( return DefaultBlockchain.createMutable( genesisBlock, new KeyValueStoragePrefixedKeyBlockchainStorage( - keyValueStorage, variablesStorage, blockHeaderFunctions), + keyValueStorage, variablesStorage, blockHeaderFunctions, false), new NoOpMetricsSystem(), 100); } diff --git a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestGetLogHash.java b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestGetLogHash.java index a0790c9fcdb..398d72d6653 100644 --- a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestGetLogHash.java +++ b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestGetLogHash.java @@ -16,9 +16,12 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.api.query.TransactionReceiptWithMetadata; import org.hyperledger.besu.ethereum.retesteth.RetestethContext; import org.hyperledger.besu.ethereum.rlp.RLP; @@ -40,7 +43,15 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final Hash txHash = requestContext.getRequiredParameter(0, Hash.class); + final Hash txHash; + try { + txHash = requestContext.getRequiredParameter(0, Hash.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid transaction hash parameter (index 0)", + RpcErrorType.INVALID_TRANSACTION_HASH_PARAMS, + e); + } final Optional receipt = context diff --git a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestImportRawBlock.java b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestImportRawBlock.java index 0652b92cdbf..580b107a8b5 100644 --- a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestImportRawBlock.java +++ b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestImportRawBlock.java @@ -16,7 +16,9 @@ import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; @@ -52,7 +54,13 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final String input = requestContext.getRequiredParameter(0, String.class); + final String input; + try { + input = requestContext.getRequiredParameter(0, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block parameter (index 0)", RpcErrorType.INVALID_BLOCK_PARAMS, e); + } final ProtocolContext protocolContext = this.context.getProtocolContext(); final Block block; diff --git a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestMineBlocks.java b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestMineBlocks.java index 3060b326bb8..00a27bb7a8a 100644 --- a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestMineBlocks.java +++ b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestMineBlocks.java @@ -16,9 +16,12 @@ import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.blockcreation.PoWBlockCreator; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; @@ -44,7 +47,13 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - long blocksToMine = requestContext.getRequiredParameter(0, Long.class); + long blocksToMine = 0; + try { + blocksToMine = requestContext.getRequiredParameter(0, Long.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid blocks to mine (index 0)", RpcErrorType.INVALID_BLOCK_COUNT_PARAMS, e); + } while (blocksToMine-- > 0) { if (!mineNewBlock()) { return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), false); @@ -69,10 +78,11 @@ private boolean mineNewBlock() { protocolContext, protocolSchedule, context.getEthHashSolver(), - blockchain.getChainHeadHeader(), context.getEthScheduler()); final Block block = - blockCreator.createBlock(retesethClock.instant().getEpochSecond()).getBlock(); + blockCreator + .createBlock(retesethClock.instant().getEpochSecond(), blockchain.getChainHeadHeader()) + .getBlock(); // advance clock so next mine won't hit the same timestamp retesethClock.advanceSeconds(1); diff --git a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestModifyTimestamp.java b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestModifyTimestamp.java index eeae7faa18b..717cf41b459 100644 --- a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestModifyTimestamp.java +++ b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestModifyTimestamp.java @@ -15,9 +15,12 @@ package org.hyperledger.besu.ethereum.retesteth.methods; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.retesteth.RetestethContext; public class TestModifyTimestamp implements JsonRpcMethod { @@ -35,7 +38,13 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final long epochSeconds = requestContext.getRequiredParameter(0, Long.class); + final long epochSeconds; + try { + epochSeconds = requestContext.getRequiredParameter(0, Long.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid timestamp parameter (index 0)", RpcErrorType.INVALID_TIMESTAMP_PARAMS, e); + } context.getRetestethClock().resetTime(epochSeconds); return new JsonRpcSuccessResponse(requestContext.getRequest().getId(), true); } diff --git a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestRewindToBlock.java b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestRewindToBlock.java index a2bc59ecc23..d9b847af425 100644 --- a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestRewindToBlock.java +++ b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestRewindToBlock.java @@ -15,9 +15,12 @@ package org.hyperledger.besu.ethereum.retesteth.methods; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.retesteth.RetestethContext; public class TestRewindToBlock implements JsonRpcMethod { @@ -36,7 +39,13 @@ public String getName() { @Override public JsonRpcResponse response(final JsonRpcRequestContext requestContext) { - final long blockNumber = requestContext.getRequiredParameter(0, Long.TYPE); + final long blockNumber; + try { + blockNumber = requestContext.getRequiredParameter(0, Long.TYPE); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid block number parameter (index 0)", RpcErrorType.INVALID_BLOCK_NUMBER_PARAMS, e); + } return new JsonRpcSuccessResponse( requestContext.getRequest().getId(), context.getBlockchain().rewindToBlock(blockNumber)); diff --git a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestSetChainParams.java b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestSetChainParams.java index 1059f02c200..1446fd939fb 100644 --- a/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestSetChainParams.java +++ b/ethereum/retesteth/src/main/java/org/hyperledger/besu/ethereum/retesteth/methods/TestSetChainParams.java @@ -128,6 +128,7 @@ private static String modifyGenesisFile(final String initialGenesis) { maybeMoveToNumber(params, "mergeNetSplitForkBlock", config, "mergeNetSplitBlock"); maybeMoveToNumber(params, "shanghaiForkTime", config, "shanghaiTime"); maybeMoveToNumber(params, "cancunForkTime", config, "cancunTime"); + maybeMoveToNumber(params, "pragueForkTime", config, "pragueTime"); maybeMoveToNumber(params, "futureEipsForkTime", config, "futureEipsTime"); maybeMoveToNumber(params, "experimentalEipsForkTime", config, "experimentalEipsTime"); maybeMoveToNumber(params, "chainID", config, "chainId", 1); diff --git a/ethereum/retesteth/src/test/java/org/hyperledger/besu/ethereum/retesteth/methods/TestImportRawBlockTest.java b/ethereum/retesteth/src/test/java/org/hyperledger/besu/ethereum/retesteth/methods/TestImportRawBlockTest.java index e8ec55e2d61..b156f2294df 100644 --- a/ethereum/retesteth/src/test/java/org/hyperledger/besu/ethereum/retesteth/methods/TestImportRawBlockTest.java +++ b/ethereum/retesteth/src/test/java/org/hyperledger/besu/ethereum/retesteth/methods/TestImportRawBlockTest.java @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.retesteth.methods; import static org.assertj.core.api.Assertions.assertThat; @@ -21,10 +19,10 @@ import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse; -import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponseType; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.retesteth.RetestethContext; +import org.hyperledger.besu.plugin.services.rpc.RpcResponseType; import java.io.IOException; @@ -71,7 +69,7 @@ public void testMissingParent() { "2.0", TestImportRawBlock.METHOD_NAME, new Object[] {rawBlockRLPString})); final var response = test_importRawBlock.response(request); - assertThat(response.getType()).isEqualTo(JsonRpcResponseType.ERROR); + assertThat(response.getType()).isEqualTo(RpcResponseType.ERROR); assertThat(((JsonRpcErrorResponse) response).getErrorType()) .isEqualTo(RpcErrorType.BLOCK_IMPORT_ERROR); } @@ -86,7 +84,7 @@ public void testBadBlock() { "2.0", TestImportRawBlock.METHOD_NAME, new Object[] {rawBlockRLPString})); final var response = test_importRawBlock.response(request); - assertThat(response.getType()).isEqualTo(JsonRpcResponseType.ERROR); + assertThat(response.getType()).isEqualTo(RpcResponseType.ERROR); assertThat(((JsonRpcErrorResponse) response).getErrorType()) .isEqualTo(RpcErrorType.BLOCK_RLP_IMPORT_ERROR); } @@ -102,7 +100,7 @@ public void testGoodBlock() { "2.0", TestImportRawBlock.METHOD_NAME, new Object[] {rawBlockRLPString})); final var response = test_importRawBlock.response(request); - assertThat(response.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); + assertThat(response.getType()).isEqualTo(RpcResponseType.SUCCESS); } @Test @@ -120,11 +118,11 @@ public void testReimportExistingBlock() { new JsonRpcRequest("2.0", TestRewindToBlock.METHOD_NAME, new Object[] {0L})); final var response = test_importRawBlock.response(request); - assertThat(response.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); + assertThat(response.getType()).isEqualTo(RpcResponseType.SUCCESS); final var rewindResponse = test_rewindToBlock.response(requestRewind); - assertThat(rewindResponse.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); + assertThat(rewindResponse.getType()).isEqualTo(RpcResponseType.SUCCESS); final var reimportResponse = test_importRawBlock.response(request); - assertThat(reimportResponse.getType()).isEqualTo(JsonRpcResponseType.SUCCESS); + assertThat(reimportResponse.getType()).isEqualTo(RpcResponseType.SUCCESS); assertThat(context.getBlockchain().getChainHead().getHeight()).isEqualTo(1L); } diff --git a/ethereum/rlp/build.gradle b/ethereum/rlp/build.gradle index 682d26761bf..b95243e2ead 100644 --- a/ethereum/rlp/build.gradle +++ b/ethereum/rlp/build.gradle @@ -23,6 +23,7 @@ jar { 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash, 'Automatic-Module-Name': 'org.hyperledger.besu.internal.rlp' ) } diff --git a/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPInput.java b/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPInput.java index fba43e2a1c6..b31184ab5ef 100644 --- a/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPInput.java +++ b/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPInput.java @@ -50,8 +50,7 @@ * word, a method like {@link #readLongScalar()} does not expect an encoded value of exactly 8 bytes * (by opposition to {@link #readLong}), but rather one that is "up to" 8 bytes. * - * @see BytesValueRLPInput for a {@link RLPInput} that decode an RLP encoded value stored in a - * {@link Bytes}. + * @see BytesValueRLPInput */ public interface RLPInput { diff --git a/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPOutput.java b/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPOutput.java index 140e60d9d24..e871eeca95f 100644 --- a/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPOutput.java +++ b/ethereum/rlp/src/main/java/org/hyperledger/besu/ethereum/rlp/RLPOutput.java @@ -80,6 +80,7 @@ public interface RLPOutput { default void writeUInt64Scalar(final UInt64Value v) { writeBytes(v.toBytes().trimLeadingZeros()); } + /** * Writes a scalar (encoded with no leading zeroes). * diff --git a/ethereum/stratum/build.gradle b/ethereum/stratum/build.gradle index 5c7dfaed20d..83de6a8a5fc 100644 --- a/ethereum/stratum/build.gradle +++ b/ethereum/stratum/build.gradle @@ -22,7 +22,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } diff --git a/ethereum/stratum/src/main/java/org/hyperledger/besu/ethereum/stratum/Stratum1Protocol.java b/ethereum/stratum/src/main/java/org/hyperledger/besu/ethereum/stratum/Stratum1Protocol.java index d3408dc4c94..4311647948d 100644 --- a/ethereum/stratum/src/main/java/org/hyperledger/besu/ethereum/stratum/Stratum1Protocol.java +++ b/ethereum/stratum/src/main/java/org/hyperledger/besu/ethereum/stratum/Stratum1Protocol.java @@ -17,7 +17,10 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator; import org.hyperledger.besu.ethereum.blockcreation.PoWMiningCoordinator; import org.hyperledger.besu.ethereum.mainnet.DirectAcyclicGraphSeed; @@ -170,13 +173,29 @@ public void handle( private void handleMiningSubmit(final JsonRpcRequest message, final Consumer sender) { LOG.debug("Miner submitted solution {}", message); + long nonce; + try { + nonce = Bytes.fromHexString(message.getRequiredParameter(2, String.class)).getLong(0); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid nonce parameter (index 2)", RpcErrorType.INVALID_NONCE_PARAMS, e); + } + Hash mixHash; + try { + mixHash = Hash.fromHexString(message.getRequiredParameter(4, String.class)); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid mix hash parameter (index 4)", RpcErrorType.INVALID_MIX_HASH_PARAMS, e); + } + Bytes powHash; + try { + powHash = Bytes.fromHexString(message.getRequiredParameter(3, String.class)); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid PoW hash parameter (index 3)", RpcErrorType.INVALID_POW_HASH_PARAMS, e); + } boolean result = false; - final PoWSolution solution = - new PoWSolution( - Bytes.fromHexString(message.getRequiredParameter(2, String.class)).getLong(0), - Hash.fromHexString(message.getRequiredParameter(4, String.class)), - null, - Bytes.fromHexString(message.getRequiredParameter(3, String.class))); + final PoWSolution solution = new PoWSolution(nonce, mixHash, null, powHash); if (currentInput.getPrePowHash().equals(solution.getPowHash())) { result = submitCallback.apply(solution); } diff --git a/ethereum/stratum/src/main/java/org/hyperledger/besu/ethereum/stratum/StratumProtocol.java b/ethereum/stratum/src/main/java/org/hyperledger/besu/ethereum/stratum/StratumProtocol.java index 275724aa965..a6a8ac88071 100644 --- a/ethereum/stratum/src/main/java/org/hyperledger/besu/ethereum/stratum/StratumProtocol.java +++ b/ethereum/stratum/src/main/java/org/hyperledger/besu/ethereum/stratum/StratumProtocol.java @@ -15,7 +15,10 @@ package org.hyperledger.besu.ethereum.stratum; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequest; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters.JsonRpcParameter.JsonRpcParameterException; import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse; +import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.RpcErrorType; import org.hyperledger.besu.ethereum.blockcreation.MiningCoordinator; import org.hyperledger.besu.ethereum.mainnet.PoWSolution; import org.hyperledger.besu.ethereum.mainnet.PoWSolverInputs; @@ -78,8 +81,20 @@ default void handleHashrateSubmit( final StratumConnection conn, final JsonRpcRequest message, final Consumer sender) { - final String hashRate = message.getRequiredParameter(0, String.class); - final String id = message.getRequiredParameter(1, String.class); + final String hashRate; + try { + hashRate = message.getRequiredParameter(0, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid hash rate parameter (index 0)", RpcErrorType.INVALID_HASH_RATE_PARAMS, e); + } + final String id; + try { + id = message.getRequiredParameter(1, String.class); + } catch (JsonRpcParameterException e) { + throw new InvalidJsonRpcParameters( + "Invalid sealer ID parameter (index 1)", RpcErrorType.INVALID_SEALER_ID_PARAMS, e); + } String response; try { response = diff --git a/ethereum/trie/build.gradle b/ethereum/trie/build.gradle index e8ce5cb9b70..080b9e8f51d 100644 --- a/ethereum/trie/build.gradle +++ b/ethereum/trie/build.gradle @@ -22,7 +22,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/AllNodesVisitor.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/AllNodesVisitor.java index 35d4caaae13..1bef1d2952b 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/AllNodesVisitor.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/AllNodesVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/CommitVisitor.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/CommitVisitor.java index 6958a719426..4d4d70cce97 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/CommitVisitor.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/CommitVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/CompactEncoding.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/CompactEncoding.java index 6303c63812f..132d0036e75 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/CompactEncoding.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/CompactEncoding.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -24,6 +24,13 @@ private CompactEncoding() {} public static final byte LEAF_TERMINATOR = 0x10; + /** + * Converts a byte sequence into a path by splitting each byte into two nibbles. The resulting + * path is terminated with a leaf terminator. + * + * @param bytes the byte sequence to convert into a path + * @return the resulting path + */ public static Bytes bytesToPath(final Bytes bytes) { final MutableBytes path = MutableBytes.create(bytes.size() * 2 + 1); int j = 0; @@ -36,6 +43,15 @@ public static Bytes bytesToPath(final Bytes bytes) { return path; } + /** + * Converts a path into a byte sequence by combining each pair of nibbles into a byte. The path + * must be a leaf path, i.e., it must be terminated with a leaf terminator. + * + * @param path the path to convert into a byte sequence + * @return the resulting byte sequence + * @throws IllegalArgumentException if the path is empty or not a leaf path, or if it contains + * elements larger than a nibble + */ public static Bytes pathToBytes(final Bytes path) { checkArgument(!path.isEmpty(), "Path must not be empty"); checkArgument(path.get(path.size() - 1) == LEAF_TERMINATOR, "Path must be a leaf path"); @@ -52,6 +68,14 @@ public static Bytes pathToBytes(final Bytes path) { return bytes; } + /** + * Encodes a path into a compact form. The encoding includes a metadata byte that indicates + * whether the path is a leaf path and whether its length is odd or even. + * + * @param path the path to encode + * @return the encoded path + * @throws IllegalArgumentException if the path contains elements larger than a nibble + */ public static Bytes encode(final Bytes path) { int size = path.size(); final boolean isLeaf = size > 0 && path.get(size - 1) == LEAF_TERMINATOR; @@ -88,6 +112,14 @@ public static Bytes encode(final Bytes path) { return encoded; } + /** + * Decodes a path from its compact form. The decoding process takes into account the metadata byte + * that indicates whether the path is a leaf path and whether its length is odd or even. + * + * @param encoded the encoded path to decode + * @return the decoded path + * @throws IllegalArgumentException if the encoded path is empty or its metadata byte is invalid + */ public static Bytes decode(final Bytes encoded) { final int size = encoded.size(); checkArgument(size > 0); diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/InnerNodeDiscoveryManager.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/InnerNodeDiscoveryManager.java index 5190ada7ef9..0e33c4c95b5 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/InnerNodeDiscoveryManager.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/InnerNodeDiscoveryManager.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,6 +14,9 @@ */ package org.hyperledger.besu.ethereum.trie; +import static org.hyperledger.besu.ethereum.trie.RangeManager.createPath; +import static org.hyperledger.besu.ethereum.trie.RangeManager.isInRange; + import org.hyperledger.besu.ethereum.rlp.RLPInput; import org.hyperledger.besu.ethereum.trie.patricia.BranchNode; import org.hyperledger.besu.ethereum.trie.patricia.ExtensionNode; @@ -21,7 +24,6 @@ import org.hyperledger.besu.ethereum.trie.patricia.StoredNodeFactory; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Optional; import java.util.function.Function; @@ -37,7 +39,7 @@ public class InnerNodeDiscoveryManager extends StoredNodeFactory { private final List innerNodes = new ArrayList<>(); - private final Bytes startKeyHash, endKeyHash; + private final Bytes startKeyPath, endKeyPath; private final boolean allowMissingElementInRange; @@ -49,8 +51,8 @@ public InnerNodeDiscoveryManager( final Bytes32 endKeyHash, final boolean allowMissingElementInRange) { super(nodeLoader, valueSerializer, valueDeserializer); - this.startKeyHash = createPath(startKeyHash); - this.endKeyHash = createPath(endKeyHash); + this.startKeyPath = createPath(startKeyHash); + this.endKeyPath = createPath(endKeyHash); this.allowMissingElementInRange = allowMissingElementInRange; } @@ -62,7 +64,7 @@ protected Node decodeExtension( final Supplier errMessage) { final ExtensionNode vNode = (ExtensionNode) super.decodeExtension(location, path, valueRlp, errMessage); - if (isInRange(Bytes.concatenate(location, Bytes.of(0)))) { + if (isInRange(Bytes.concatenate(location, Bytes.of(0)), startKeyPath, endKeyPath)) { innerNodes.add( ImmutableInnerNode.builder() .location(location) @@ -78,7 +80,7 @@ protected BranchNode decodeBranch( final BranchNode vBranchNode = super.decodeBranch(location, nodeRLPs, errMessage); final List> children = vBranchNode.getChildren(); for (int i = 0; i < children.size(); i++) { - if (isInRange(Bytes.concatenate(location, Bytes.of(i)))) { + if (isInRange(Bytes.concatenate(location, Bytes.of(i)), startKeyPath, endKeyPath)) { innerNodes.add( ImmutableInnerNode.builder() .location(location) @@ -97,7 +99,7 @@ protected LeafNode decodeLeaf( final Supplier errMessage) { final LeafNode vLeafNode = super.decodeLeaf(location, path, valueRlp, errMessage); final Bytes concatenatePath = Bytes.concatenate(location, path); - if (isInRange(concatenatePath.slice(0, concatenatePath.size() - 1))) { + if (isInRange(concatenatePath.slice(0, concatenatePath.size() - 1), startKeyPath, endKeyPath)) { innerNodes.add(ImmutableInnerNode.builder().location(location).path(path).build()); } return vLeafNode; @@ -106,10 +108,16 @@ protected LeafNode decodeLeaf( @Override public Optional> retrieve(final Bytes location, final Bytes32 hash) throws MerkleTrieException { + return super.retrieve(location, hash) + .map( + vNode -> { + vNode.markDirty(); + return vNode; + }) .or( () -> { - if (!allowMissingElementInRange && isInRange(location)) { + if (!allowMissingElementInRange && isInRange(location, startKeyPath, endKeyPath)) { return Optional.empty(); } return Optional.of(new MissingNode<>(hash, location)); @@ -120,23 +128,6 @@ public List getInnerNodes() { return List.copyOf(innerNodes); } - private boolean isInRange(final Bytes location) { - return !location.isEmpty() - && Arrays.compare(location.toArrayUnsafe(), startKeyHash.toArrayUnsafe()) >= 0 - && Arrays.compare(location.toArrayUnsafe(), endKeyHash.toArrayUnsafe()) <= 0; - } - - private Bytes createPath(final Bytes bytes) { - final MutableBytes path = MutableBytes.create(bytes.size() * 2); - int j = 0; - for (int i = 0; i < bytes.size(); i += 1, j += 2) { - final byte b = bytes.get(i); - path.set(j, (byte) ((b >>> 4) & 0x0f)); - path.set(j + 1, (byte) (b & 0x0f)); - } - return path; - } - public static Bytes32 decodePath(final Bytes bytes) { final MutableBytes32 decoded = MutableBytes32.create(); final MutableBytes path = MutableBytes.create(Bytes32.SIZE * 2); diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/LocationNodeVisitor.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/LocationNodeVisitor.java index ac4ce53796d..6a7990baa1b 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/LocationNodeVisitor.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/LocationNodeVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/MerkleStorage.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/MerkleStorage.java index f9bb764376b..62ccf33e54e 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/MerkleStorage.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/MerkleStorage.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/MerkleTrie.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/MerkleTrie.java index ede4fac6460..c9442e26844 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/MerkleTrie.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/MerkleTrie.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -28,7 +28,7 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; -/** An Merkle Patricial Trie. */ +/** A Merkle Patricia Trie. */ public interface MerkleTrie { Bytes EMPTY_TRIE_NODE = RLP.NULL; @@ -75,6 +75,7 @@ public interface MerkleTrie { * @param value The value to associate the key with. */ void putPath(K path, V value); + /** * Updates the value mapped to the specified key, creating the mapping if one does not already * exist. diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/MissingNode.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/MissingNode.java index 92035330e95..e04384aeffa 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/MissingNode.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/MissingNode.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/NoOpMerkleTrie.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/NoOpMerkleTrie.java new file mode 100644 index 00000000000..53d0ac5b58a --- /dev/null +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/NoOpMerkleTrie.java @@ -0,0 +1,123 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +/** + * A noop {@link MerkleTrie}. + * + * @param The type of values of this trie. + */ +public class NoOpMerkleTrie implements MerkleTrie { + + public NoOpMerkleTrie() {} + + @Override + public Optional get(final K key) { + return Optional.empty(); + } + + @Override + public Optional getPath(final K path) { + return Optional.empty(); + } + + @Override + public Proof getValueWithProof(final K key) { + return new Proof<>(Optional.empty(), new ArrayList<>()); + } + + @Override + public void put(final K key, final V value) { + // noop + } + + @Override + public void putPath(final K path, final V value) { + // noop + } + + @Override + public void put(final K key, final PathNodeVisitor putVisitor) { + // noop + } + + @Override + public void remove(final K key) { + // noop + } + + @Override + public void removePath(final K path, final PathNodeVisitor removeVisitor) { + // noop + } + + @Override + public Bytes32 getRootHash() { + return EMPTY_TRIE_NODE_HASH; + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + getRootHash() + "]"; + } + + @Override + public void commit(final NodeUpdater nodeUpdater) { + // Nothing to do here + } + + @Override + public void commit(final NodeUpdater nodeUpdater, final CommitVisitor commitVisitor) { + // Nothing to do here + } + + @Override + public Map entriesFrom(final Bytes32 startKeyHash, final int limit) { + return new HashMap<>(); + } + + @Override + public Map entriesFrom(final Function, Map> handler) { + return new HashMap<>(); + } + + @Override + public void visitAll(final Consumer> nodeConsumer) { + // noop + } + + @Override + public CompletableFuture visitAll( + final Consumer> nodeConsumer, final ExecutorService executorService) { + return CompletableFuture.completedFuture(null); + } + + @Override + public void visitLeafs(final TrieIterator.LeafHandler handler) { + // nopop + } +} diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/NodeFactory.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/NodeFactory.java index 69d61e3ff00..dd0b56d7e73 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/NodeFactory.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/NodeFactory.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/NodeVisitor.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/NodeVisitor.java index bfc517dfdfc..bbf3301079f 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/NodeVisitor.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/NodeVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/NullNode.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/NullNode.java index da2ab9cddca..6ef63400aa1 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/NullNode.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/NullNode.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/PathNodeVisitor.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/PathNodeVisitor.java index 958c1bf5d6f..80301ab3071 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/PathNodeVisitor.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/PathNodeVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/PersistVisitor.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/PersistVisitor.java index c524df2ceab..3bdc7399640 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/PersistVisitor.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/PersistVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.ethereum.trie; import org.hyperledger.besu.ethereum.trie.patricia.BranchNode; diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/ProofVisitor.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/ProofVisitor.java index 0b85626d931..25ec8c9c68d 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/ProofVisitor.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/ProofVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/RangeManager.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/RangeManager.java new file mode 100644 index 00000000000..99f81989f06 --- /dev/null +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/RangeManager.java @@ -0,0 +1,194 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.trie.patricia.StoredMerklePatriciaTrie; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.NavigableMap; +import java.util.Optional; +import java.util.TreeMap; +import java.util.function.Function; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.bytes.MutableBytes; + +/** + * This class helps to generate ranges according to several parameters (the start and the end of the + * range, its size) + */ +public class RangeManager { + + public static final Hash MIN_RANGE = Hash.wrap(Bytes32.ZERO); + public static final Hash MAX_RANGE = + Hash.fromHexString("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + + private RangeManager() {} + + public static int getRangeCount( + final Bytes32 min, final Bytes32 max, final NavigableMap items) { + if (min.equals(MIN_RANGE) && max.equals(MAX_RANGE)) { + return MAX_RANGE + .toUnsignedBigInteger() + .divide(items.lastKey().toUnsignedBigInteger().subtract(min.toUnsignedBigInteger())) + .intValue(); + } + return 1; + } + + public static Map generateAllRanges(final int sizeRange) { + if (sizeRange == 1) { + return Map.ofEntries(Map.entry(MIN_RANGE, MAX_RANGE)); + } + return generateRanges( + MIN_RANGE.toUnsignedBigInteger(), MAX_RANGE.toUnsignedBigInteger(), sizeRange); + } + + /** + * Generate a range + * + * @param min start of the range in bytes + * @param max the max end of the range in bytes + * @param nbRange number of ranges + * @return the start and end of the generated range + */ + public static Map generateRanges( + final Bytes32 min, final Bytes32 max, final int nbRange) { + return generateRanges(min.toUnsignedBigInteger(), max.toUnsignedBigInteger(), nbRange); + } + + /** + * Generate a range + * + * @param min start of the range + * @param max the max end of the range + * @param nbRange number of ranges + * @return the start and end of the generated range + */ + public static Map generateRanges( + final BigInteger min, final BigInteger max, final int nbRange) { + final BigInteger rangeSize = max.subtract(min).divide(BigInteger.valueOf(nbRange)); + final TreeMap ranges = new TreeMap<>(); + if (min.compareTo(max) > 0) { + return ranges; + } + if (min.equals(max) || nbRange == 1) { + ranges.put(format(min), format(max)); + return ranges; + } + BigInteger currentStart = min; + BigInteger currentEnd = min; + while (max.subtract(currentEnd).compareTo(rangeSize) > 0) { + currentEnd = currentStart.add(rangeSize); + ranges.put(format(currentStart), format(currentEnd)); + currentStart = currentStart.add(rangeSize).add(BigInteger.ONE); + } + if (max.subtract(currentEnd).compareTo(BigInteger.ZERO) > 0) { + currentEnd = currentStart.add(max.subtract(currentEnd)).subtract(BigInteger.ONE); + ranges.put(format(currentStart), format(currentEnd)); + } + return ranges; + } + + /** + * Helps to create a new range according to the last data obtained. This happens when a peer + * doesn't return all of the data in a range. + * + * @param worldstateRootHash the root hash + * @param proofs proof received + * @param endKeyHash the end of the range initially wanted + * @param receivedKeys the last key received + * @return begin of the new range + */ + public static Optional findNewBeginElementInRange( + final Bytes32 worldstateRootHash, + final List proofs, + final NavigableMap receivedKeys, + final Bytes32 endKeyHash) { + if (receivedKeys.isEmpty() || receivedKeys.lastKey().compareTo(endKeyHash) >= 0) { + return Optional.empty(); + } else { + final Map proofsEntries = new HashMap<>(); + for (Bytes proof : proofs) { + proofsEntries.put(Hash.hash(proof), proof); + } + final StoredMerklePatriciaTrie storageTrie = + new StoredMerklePatriciaTrie<>( + new InnerNodeDiscoveryManager<>( + (location, key) -> Optional.ofNullable(proofsEntries.get(key)), + Function.identity(), + Function.identity(), + receivedKeys.lastKey(), + endKeyHash, + false), + worldstateRootHash); + + try { + storageTrie.visitAll(bytesNode -> {}); + } catch (MerkleTrieException e) { + return Optional.of(InnerNodeDiscoveryManager.decodePath(e.getLocation())); + } + return Optional.empty(); + } + } + + private static Bytes32 format(final BigInteger data) { + return Bytes32.leftPad(Bytes.of(data.toByteArray()).trimLeadingZeros()); + } + + /** + * Checks if a given location is within a specified range. This method determines whether a given + * location (represented as {@link Bytes}) falls within the range defined by a start key path and + * an end key path. + * + * @param location The location to check, represented as {@link Bytes}. + * @param startKeyPath The start of the range as path, represented as {@link Bytes}. + * @param endKeyPath The end of the range as path, represented as {@link Bytes}. + * @return {@code true} if the location is within the range (inclusive); {@code false} otherwise. + */ + public static boolean isInRange( + final Bytes location, final Bytes startKeyPath, final Bytes endKeyPath) { + final MutableBytes path = MutableBytes.create(Bytes32.SIZE * 2); + path.set(0, location); + return !location.isEmpty() + && Arrays.compare(path.toArrayUnsafe(), startKeyPath.toArrayUnsafe()) >= 0 + && Arrays.compare(path.toArrayUnsafe(), endKeyPath.toArrayUnsafe()) <= 0; + } + + /** + * Transforms a list of bytes into a path. This method processes a sequence of bytes, expanding + * each byte into two separate bytes to form a new path. The resulting path will have twice the + * length of the input byte sequence. + * + * @param bytes The byte sequence to be transformed into a path. + * @return A {@link Bytes} object representing the newly formed path. + */ + public static Bytes createPath(final Bytes bytes) { + final MutableBytes path = MutableBytes.create(bytes.size() * 2); + int j = 0; + for (int i = 0; i < bytes.size(); i += 1, j += 2) { + final byte b = bytes.get(i); + path.set(j, (byte) ((b >>> 4) & 0x0f)); + path.set(j + 1, (byte) (b & 0x0f)); + } + return path; + } +} diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/RangeStorageEntriesCollector.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/RangeStorageEntriesCollector.java index 595f46164d1..475f0c0c637 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/RangeStorageEntriesCollector.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/RangeStorageEntriesCollector.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/RestoreVisitor.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/RestoreVisitor.java index 556db062b9c..a4d61cb8ebb 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/RestoreVisitor.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/RestoreVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/SimpleMerkleTrie.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/SimpleMerkleTrie.java index d938f098f3d..e337bd7f2b6 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/SimpleMerkleTrie.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/SimpleMerkleTrie.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/SnapCommitVisitor.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/SnapCommitVisitor.java new file mode 100644 index 00000000000..dfe18d82c26 --- /dev/null +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/SnapCommitVisitor.java @@ -0,0 +1,138 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie; + +import static org.hyperledger.besu.ethereum.trie.RangeManager.createPath; + +import org.hyperledger.besu.ethereum.trie.patricia.BranchNode; +import org.hyperledger.besu.ethereum.trie.patricia.ExtensionNode; + +import java.util.Arrays; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.bytes.MutableBytes; + +/** + * Implements a visitor for persisting changes to nodes during a snap synchronization process, + * focusing specifically on nodes that are marked as "dirty" but not as "heal needed". + * + *

    This visitor plays a crucial role in the snap synchronization by identifying nodes that have + * been modified and require persistence ("dirty" nodes). The key functionality of this visitor is + * its selective persistence approach: it only persists changes to dirty nodes that are not marked + * as "heal needed". This strategy is designed to prevent future inconsistencies within the tree by + * ensuring that only nodes that are both modified and currently consistent with the rest of the + * structure are persisted. Nodes marked as "heal needed" are excluded from immediate persistence to + * allow for their proper healing in a controlled manner, ensuring the integrity and consistency of + * the data structure. + */ +public class SnapCommitVisitor extends CommitVisitor implements LocationNodeVisitor { + + private final Bytes startKeyPath; + private final Bytes endKeyPath; + + public SnapCommitVisitor( + final NodeUpdater nodeUpdater, final Bytes32 startKeyHash, final Bytes32 endKeyHash) { + super(nodeUpdater); + this.startKeyPath = createPath(startKeyHash); + this.endKeyPath = createPath(endKeyHash); + } + + /** + * Visits an extension node during a traversal operation. + * + *

    This method is called when visiting an extension node. It checks if the node is marked as + * "dirty" (indicating changes that have not been persisted). If the node is clean, the method + * returns immediately. For dirty nodes, it recursively visits any dirty child nodes, + * concatenating the current location with the extension node's path to form the full path to the + * child. + * + *

    Additionally, it checks if the child node requires healing (e.g., if it's falls outside the + * specified range defined by {@code startKeyPath} and {@code endKeyPath}). If healing is needed, + * the extension node is marked accordingly. + * + *

    Finally, it attempts to persist the extension node if applicable. + * + * @param location The current location represented as {@link Bytes}. + * @param extensionNode The extension node being visited. + */ + @Override + public void visit(final Bytes location, final ExtensionNode extensionNode) { + if (!extensionNode.isDirty()) { + return; + } + + final Node child = extensionNode.getChild(); + if (child.isDirty()) { + child.accept(Bytes.concatenate(location, extensionNode.getPath()), this); + } + if (child.isHealNeeded() + || !isInRange( + Bytes.concatenate(location, extensionNode.getPath()), startKeyPath, endKeyPath)) { + extensionNode.markHealNeeded(); // not save an incomplete node + } + + maybeStoreNode(location, extensionNode); + } + + /** + * Visits a branch node during a traversal operation. + * + *

    This method is invoked when visiting a branch node. It first checks if the branch node is + * marked as "dirty" (indicating changes that have not been persisted). If the node is clean, the + * method returns immediately. + * + *

    For dirty branch nodes, it iterates through each child node. For each child, if the child is + * dirty, it recursively visits the child, passing along the concatenated path (current location + * plus the child's index) to the child's accept method. + * + *

    Additionally, it checks if the child node requires healing (e.g., if it's falls outside the + * specified range of interest defined by {@code startKeyPath} and {@code endKeyPath}). If healing + * is needed, the branch node is marked accordingly. + * + *

    Finally, it attempts to persist the branch node if applicable. + * + * @param location The current location represented as {@link Bytes}. + * @param branchNode The branch node being visited. + */ + @Override + public void visit(final Bytes location, final BranchNode branchNode) { + if (!branchNode.isDirty()) { + return; + } + + for (int i = 0; i < branchNode.maxChild(); ++i) { + Bytes index = Bytes.of(i); + final Node child = branchNode.child((byte) i); + if (child.isDirty()) { + child.accept(Bytes.concatenate(location, index), this); + } + if (child.isHealNeeded() + || !isInRange(Bytes.concatenate(location, index), startKeyPath, endKeyPath)) { + branchNode.markHealNeeded(); // not save an incomplete node + } + } + + maybeStoreNode(location, branchNode); + } + + private boolean isInRange( + final Bytes location, final Bytes startKeyPath, final Bytes endKeyPath) { + final MutableBytes path = MutableBytes.create(Bytes32.SIZE * 2); + path.set(0, location); + return Arrays.compare(path.toArrayUnsafe(), startKeyPath.toArrayUnsafe()) >= 0 + && Arrays.compare(path.toArrayUnsafe(), endKeyPath.toArrayUnsafe()) <= 0; + } +} diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/SnapPutVisitor.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/SnapPutVisitor.java deleted file mode 100644 index 0cce55dcf4a..00000000000 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/SnapPutVisitor.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.trie; - -import org.hyperledger.besu.ethereum.trie.patricia.BranchNode; -import org.hyperledger.besu.ethereum.trie.patricia.ExtensionNode; -import org.hyperledger.besu.ethereum.trie.patricia.PutVisitor; - -import org.apache.tuweni.bytes.Bytes; - -public class SnapPutVisitor extends PutVisitor { - - public SnapPutVisitor(final NodeFactory nodeFactory, final V value) { - super(nodeFactory, value); - } - - @Override - public Node visit(final BranchNode branchNode, final Bytes path) { - final Node visit = super.visit(branchNode, path); - for (Node child : visit.getChildren()) { - if (child.isHealNeeded() || (child instanceof StoredNode && child.getValue().isEmpty())) { - visit.markHealNeeded(); // not save an incomplete node - return visit; - } - } - return visit; - } - - @Override - public Node visit(final ExtensionNode extensionNode, final Bytes path) { - final Node visit = super.visit(extensionNode, path); - for (Node child : visit.getChildren()) { - if (child.isHealNeeded() || (child instanceof StoredNode && child.getValue().isEmpty())) { - visit.markHealNeeded(); // not save an incomplete node - return visit; - } - } - return visit; - } -} diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/StoredMerkleTrie.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/StoredMerkleTrie.java index 835bcd5b680..03d66fb0c24 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/StoredMerkleTrie.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/StoredMerkleTrie.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/StoredNode.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/StoredNode.java index 8a90287cdb0..752187b899e 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/StoredNode.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/StoredNode.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/TrieIterator.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/TrieIterator.java index cb314d87959..1fc9e7f4094 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/TrieIterator.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/TrieIterator.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/BranchNode.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/BranchNode.java index ac5eec524b6..15f242b6006 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/BranchNode.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/BranchNode.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -55,7 +55,7 @@ public class BranchNode implements Node { public BranchNode( final Bytes location, - final ArrayList> children, + final List> children, final Optional value, final NodeFactory nodeFactory, final Function valueSerializer) { diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/DefaultNodeFactory.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/DefaultNodeFactory.java index 2c348c7a86c..b4c2975c589 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/DefaultNodeFactory.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/DefaultNodeFactory.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/ExtensionNode.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/ExtensionNode.java index 50df015e380..5721c9f98f3 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/ExtensionNode.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/ExtensionNode.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/GetVisitor.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/GetVisitor.java index c34bc96fa6f..3cdfd836b6a 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/GetVisitor.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/GetVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/LeafNode.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/LeafNode.java index 38690d95050..18de15dc3f5 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/LeafNode.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/LeafNode.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/PutVisitor.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/PutVisitor.java index c536fa56b33..ba49cc32227 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/PutVisitor.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/PutVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/RemoveVisitor.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/RemoveVisitor.java index 3be8f8f4411..8e88db89217 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/RemoveVisitor.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/RemoveVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/SimpleMerklePatriciaTrie.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/SimpleMerklePatriciaTrie.java index 8bd14cbb93c..faeea67c23d 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/SimpleMerklePatriciaTrie.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/SimpleMerklePatriciaTrie.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/StoredMerklePatriciaTrie.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/StoredMerklePatriciaTrie.java index 0ae7cf344e7..74077e9c0d8 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/StoredMerklePatriciaTrie.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/StoredMerklePatriciaTrie.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/StoredNodeFactory.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/StoredNodeFactory.java index 68e0d293f3c..a79f2a9d8b5 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/StoredNodeFactory.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/StoredNodeFactory.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/TrieNodeDecoder.java b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/TrieNodeDecoder.java index 3d99b7ffbec..6179f45fc0a 100644 --- a/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/TrieNodeDecoder.java +++ b/ethereum/trie/src/main/java/org/hyperledger/besu/ethereum/trie/patricia/TrieNodeDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -16,6 +16,7 @@ import static com.google.common.base.Preconditions.checkArgument; +import org.hyperledger.besu.ethereum.trie.MerkleTrie; import org.hyperledger.besu.ethereum.trie.Node; import org.hyperledger.besu.ethereum.trie.NodeLoader; import org.hyperledger.besu.ethereum.trie.NullNode; @@ -59,6 +60,9 @@ public static Node decode(final Bytes location, final Bytes rlp) { * @return A list of nodes and node references embedded in the given rlp. */ public static List> decodeNodes(final Bytes location, final Bytes nodeRlp) { + if (nodeRlp.equals(MerkleTrie.EMPTY_TRIE_NODE)) { + return new ArrayList<>(); + } final Node node = decode(location, nodeRlp); final List> nodes = new ArrayList<>(); nodes.add(node); diff --git a/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/AllNodesVisitorTest.java b/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/AllNodesVisitorTest.java index 5bd3535756f..be6ebdad062 100644 --- a/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/AllNodesVisitorTest.java +++ b/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/AllNodesVisitorTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/RangeStorageEntriesCollectorTest.java b/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/RangeStorageEntriesCollectorTest.java index 6b931d041e5..6f507d3668f 100644 --- a/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/RangeStorageEntriesCollectorTest.java +++ b/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/RangeStorageEntriesCollectorTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -28,10 +28,11 @@ public class RangeStorageEntriesCollectorTest { @Test public void shouldRetrieveAllLeavesInRangeWhenStartFromZero() { - InMemoryKeyValueStorage worldStateStorage = new InMemoryKeyValueStorage(); + InMemoryKeyValueStorage worldStateKeyValueStorage = new InMemoryKeyValueStorage(); final MerkleTrie accountStateTrie = new StoredMerklePatriciaTrie<>( - (location, hash) -> worldStateStorage.get(hash.toArrayUnsafe()).map(Bytes::wrap), + (location, hash) -> + worldStateKeyValueStorage.get(hash.toArrayUnsafe()).map(Bytes::wrap), b -> b, b -> b); final List lists = @@ -47,10 +48,11 @@ public void shouldRetrieveAllLeavesInRangeWhenStartFromZero() { @Test public void shouldRetrieveAllLeavesInRangeWhenStartFromSpecificRange() { - InMemoryKeyValueStorage worldStateStorage = new InMemoryKeyValueStorage(); + InMemoryKeyValueStorage worldStateKeyValueStorage = new InMemoryKeyValueStorage(); final MerkleTrie accountStateTrie = new StoredMerklePatriciaTrie<>( - (location, hash) -> worldStateStorage.get(hash.toArrayUnsafe()).map(Bytes::wrap), + (location, hash) -> + worldStateKeyValueStorage.get(hash.toArrayUnsafe()).map(Bytes::wrap), b -> b, b -> b); final List lists = @@ -66,10 +68,11 @@ public void shouldRetrieveAllLeavesInRangeWhenStartFromSpecificRange() { @Test public void shouldExcludeLeavesNotInRange() { - InMemoryKeyValueStorage worldStateStorage = new InMemoryKeyValueStorage(); + InMemoryKeyValueStorage worldStateKeyValueStorage = new InMemoryKeyValueStorage(); final MerkleTrie accountStateTrie = new StoredMerklePatriciaTrie<>( - (location, hash) -> worldStateStorage.get(hash.toArrayUnsafe()).map(Bytes::wrap), + (location, hash) -> + worldStateKeyValueStorage.get(hash.toArrayUnsafe()).map(Bytes::wrap), b -> b, b -> b); final List lists = diff --git a/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/SnapCommitVisitorTest.java b/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/SnapCommitVisitorTest.java new file mode 100644 index 00000000000..d0561df5ac7 --- /dev/null +++ b/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/SnapCommitVisitorTest.java @@ -0,0 +1,127 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.trie; + +import static org.mockito.Mockito.mock; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.trie.patricia.BranchNode; +import org.hyperledger.besu.ethereum.trie.patricia.ExtensionNode; +import org.hyperledger.besu.ethereum.trie.patricia.StoredNodeFactory; + +import java.util.ArrayList; +import java.util.Optional; +import java.util.function.Function; + +import org.apache.tuweni.bytes.Bytes; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("unchecked") +public class SnapCommitVisitorTest { + + @Test + public void shouldDetectValidBranch() { + final StoredNodeFactory storedNodeFactory = mock(StoredNodeFactory.class); + final ArrayList> children = new ArrayList<>(); + for (int i = 0; i < 16; i++) { + children.add( + new StoredNode<>( + storedNodeFactory, Bytes.concatenate(Bytes.of(0x00), Bytes.of(i)), Hash.ZERO)); + } + final BranchNode validBranchNode = + new BranchNode<>( + Bytes.of(0x00), + children, + Optional.of(Bytes.of(0x00)), + storedNodeFactory, + Function.identity()); + validBranchNode.markDirty(); + final SnapCommitVisitor snapCommitVisitor = + new SnapCommitVisitor<>( + (location, hash, value) -> {}, RangeManager.MIN_RANGE, RangeManager.MAX_RANGE); + Assertions.assertThat(validBranchNode.isHealNeeded()).isFalse(); + snapCommitVisitor.visit(validBranchNode.getLocation().get(), validBranchNode); + Assertions.assertThat(validBranchNode.isHealNeeded()).isFalse(); + } + + @Test + public void shouldDetectBranchWithChildrenNotInTheRange() { + final StoredNodeFactory storedNodeFactory = mock(StoredNodeFactory.class); + final ArrayList> children = new ArrayList<>(); + for (int i = 0; i < 16; i++) { + children.add( + new StoredNode<>( + storedNodeFactory, Bytes.concatenate(Bytes.of(0x01), Bytes.of(i)), Hash.ZERO)); + } + + final BranchNode invalidBranchNode = + new BranchNode<>( + Bytes.of(0x01), + children, + Optional.of(Bytes.of(0x00)), + storedNodeFactory, + Function.identity()); + invalidBranchNode.markDirty(); + final SnapCommitVisitor snapCommitVisitor = + new SnapCommitVisitor<>( + (location, hash, value) -> {}, + RangeManager.MIN_RANGE, + Hash.fromHexString( + "0x1effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); + Assertions.assertThat(invalidBranchNode.isHealNeeded()).isFalse(); + snapCommitVisitor.visit(invalidBranchNode.getLocation().get(), invalidBranchNode); + Assertions.assertThat(invalidBranchNode.isHealNeeded()).isTrue(); + } + + @Test + public void shouldDetectValidExtension() { + final StoredNodeFactory storedNodeFactory = mock(StoredNodeFactory.class); + final ExtensionNode validExtensionNode = + new ExtensionNode<>( + Bytes.of(0x00), + Bytes.of(0x01), + new StoredNode<>(storedNodeFactory, Bytes.of((byte) 0x00, (byte) 0x01), Hash.ZERO), + storedNodeFactory); + validExtensionNode.markDirty(); + final SnapCommitVisitor snapCommitVisitor = + new SnapCommitVisitor<>( + (location, hash, value) -> {}, RangeManager.MIN_RANGE, RangeManager.MAX_RANGE); + Assertions.assertThat(validExtensionNode.isHealNeeded()).isFalse(); + snapCommitVisitor.visit(validExtensionNode.getLocation().get(), validExtensionNode); + Assertions.assertThat(validExtensionNode.isHealNeeded()).isFalse(); + } + + @Test + public void shouldDetectExtensionWithChildNotInRange() { + final StoredNodeFactory storedNodeFactory = mock(StoredNodeFactory.class); + final ExtensionNode inValidExtensionNode = + new ExtensionNode<>( + Bytes.of(0x00), + Bytes.of(0x03), + new StoredNode<>(storedNodeFactory, Bytes.of((byte) 0x00, (byte) 0x03), Hash.ZERO), + storedNodeFactory); + inValidExtensionNode.markDirty(); + final SnapCommitVisitor snapCommitVisitor = + new SnapCommitVisitor<>( + (location, hash, value) -> {}, + RangeManager.MIN_RANGE, + Hash.fromHexString( + "0x02ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); + Assertions.assertThat(inValidExtensionNode.isHealNeeded()).isFalse(); + snapCommitVisitor.visit(inValidExtensionNode.getLocation().get(), inValidExtensionNode); + Assertions.assertThat(inValidExtensionNode.isHealNeeded()).isTrue(); + } +} diff --git a/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/SnapPutVisitorTest.java b/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/SnapPutVisitorTest.java deleted file mode 100644 index cbedbe9a288..00000000000 --- a/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/SnapPutVisitorTest.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.trie; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyByte; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.ethereum.trie.patricia.BranchNode; -import org.hyperledger.besu.ethereum.trie.patricia.ExtensionNode; -import org.hyperledger.besu.ethereum.trie.patricia.LeafNode; -import org.hyperledger.besu.ethereum.trie.patricia.StoredNodeFactory; - -import java.util.ArrayList; -import java.util.Optional; -import java.util.function.Function; - -import org.apache.tuweni.bytes.Bytes; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; - -@SuppressWarnings("unchecked") -public class SnapPutVisitorTest { - - @Test - public void shouldDetectValidBranch() { - final StoredNodeFactory storedNodeFactory = mock(StoredNodeFactory.class); - when(storedNodeFactory.createBranch(any(), any())) - .thenReturn( - new LeafNode( - Bytes.EMPTY, Bytes.of(0x00), storedNodeFactory, Function.identity())); - final ArrayList> children = new ArrayList<>(); - for (int i = 0; i < 16; i++) { - children.add(new StoredNode<>(storedNodeFactory, Bytes.EMPTY, Hash.ZERO)); - } - final BranchNode invalidBranchNode = - new BranchNode<>( - Bytes.EMPTY, - children, - Optional.of(Bytes.of(0x00)), - storedNodeFactory, - Function.identity()); - final SnapPutVisitor snapPutVisitor = - new SnapPutVisitor<>(storedNodeFactory, Bytes.EMPTY); - Node visit = - snapPutVisitor.visit(invalidBranchNode, Bytes.of(CompactEncoding.LEAF_TERMINATOR)); - Assertions.assertThat(visit.isHealNeeded()).isFalse(); - } - - @Test - public void shouldDetectBranchWithMissingChildren() { - final StoredNodeFactory storedNodeFactory = mock(StoredNodeFactory.class); - when(storedNodeFactory.createBranch(any(), any())) - .thenReturn(new MissingNode<>(Hash.ZERO, Bytes.EMPTY)); - final ArrayList> children = new ArrayList<>(); - for (int i = 0; i < 16; i++) { - children.add(new StoredNode<>(storedNodeFactory, Bytes.EMPTY, Hash.ZERO)); - } - final BranchNode invalidBranchNode = - new BranchNode<>( - Bytes.EMPTY, - children, - Optional.of(Bytes.of(0x00)), - storedNodeFactory, - Function.identity()); - final SnapPutVisitor snapPutVisitor = - new SnapPutVisitor<>(storedNodeFactory, Bytes.EMPTY); - Node visit = - snapPutVisitor.visit(invalidBranchNode, Bytes.of(CompactEncoding.LEAF_TERMINATOR)); - Assertions.assertThat(visit.isHealNeeded()).isTrue(); - } - - @Test - public void shouldDetectValidExtension() { - final StoredNodeFactory storedNodeFactory = mock(StoredNodeFactory.class); - when(storedNodeFactory.createBranch(any(), any())) - .thenReturn( - new LeafNode<>(Bytes.EMPTY, Bytes.of(0x00), storedNodeFactory, Function.identity())); - final ArrayList> children = new ArrayList<>(); - for (int i = 0; i < 16; i++) { - children.add(new StoredNode<>(storedNodeFactory, Bytes.EMPTY, Hash.ZERO)); - } - final BranchNode invalidBranchNode = - new BranchNode<>( - Bytes.EMPTY, - children, - Optional.of(Bytes.of(0x00)), - storedNodeFactory, - Function.identity()); - final SnapPutVisitor snapPutVisitor = - new SnapPutVisitor<>(storedNodeFactory, Bytes.EMPTY); - Node visit = - snapPutVisitor.visit(invalidBranchNode, Bytes.of(CompactEncoding.LEAF_TERMINATOR)); - Assertions.assertThat(visit.isHealNeeded()).isFalse(); - } - - @Test - public void shouldDetectExtensionWithMissingChildren() { - final StoredNodeFactory storedNodeFactory = mock(StoredNodeFactory.class); - when(storedNodeFactory.createBranch(anyByte(), any(), anyByte(), any())) - .thenReturn(new MissingNode<>(Hash.ZERO, Bytes.EMPTY)); - when(storedNodeFactory.createLeaf(any(), any())) - .thenReturn(new MissingNode<>(Hash.ZERO, Bytes.EMPTY)); - final ExtensionNode invalidBranchNode = - new ExtensionNode<>( - Bytes.of(0x00), - new StoredNode<>(storedNodeFactory, Bytes.EMPTY, Hash.ZERO), - storedNodeFactory); - final SnapPutVisitor snapPutVisitor = - new SnapPutVisitor<>(storedNodeFactory, Bytes.EMPTY); - Node visit = - snapPutVisitor.visit(invalidBranchNode, Bytes.of(CompactEncoding.LEAF_TERMINATOR)); - Assertions.assertThat(visit.isHealNeeded()).isTrue(); - } -} diff --git a/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/TrieIteratorTest.java b/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/TrieIteratorTest.java index 3264ccffb72..ebd01edf240 100644 --- a/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/TrieIteratorTest.java +++ b/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/TrieIteratorTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/TrieNodeDecoderTest.java b/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/TrieNodeDecoderTest.java index f5c15dd18b6..d0372e13b7c 100644 --- a/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/TrieNodeDecoderTest.java +++ b/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/TrieNodeDecoderTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/patricia/AbstractMerklePatriciaTrieTest.java b/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/patricia/AbstractMerklePatriciaTrieTest.java index f201d3fa4e0..284250a79b3 100644 --- a/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/patricia/AbstractMerklePatriciaTrieTest.java +++ b/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/patricia/AbstractMerklePatriciaTrieTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/patricia/SimpleMerklePatriciaTrieTest.java b/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/patricia/SimpleMerklePatriciaTrieTest.java index 33eecfd2286..06c4ae184b8 100644 --- a/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/patricia/SimpleMerklePatriciaTrieTest.java +++ b/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/patricia/SimpleMerklePatriciaTrieTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/patricia/StoredMerklePatriciaTrieTest.java b/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/patricia/StoredMerklePatriciaTrieTest.java index d33746b062f..d33ca930737 100644 --- a/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/patricia/StoredMerklePatriciaTrieTest.java +++ b/ethereum/trie/src/test/java/org/hyperledger/besu/ethereum/trie/patricia/StoredMerklePatriciaTrieTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/verkletrie/build.gradle b/ethereum/verkletrie/build.gradle index 166b02efe17..00c7016fb63 100644 --- a/ethereum/verkletrie/build.gradle +++ b/ethereum/verkletrie/build.gradle @@ -22,7 +22,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } diff --git a/ethereum/verkletrie/src/main/java/org/hyperledger/besu/ethereum/verkletrie/bandersnatch/Point.java b/ethereum/verkletrie/src/main/java/org/hyperledger/besu/ethereum/verkletrie/bandersnatch/Point.java index 18f9dcc8afd..bc10d80bb1d 100644 --- a/ethereum/verkletrie/src/main/java/org/hyperledger/besu/ethereum/verkletrie/bandersnatch/Point.java +++ b/ethereum/verkletrie/src/main/java/org/hyperledger/besu/ethereum/verkletrie/bandersnatch/Point.java @@ -1,5 +1,5 @@ /* - * Copyright Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/verkletrie/src/main/java/org/hyperledger/besu/ethereum/verkletrie/bandersnatch/PointAffine.java b/ethereum/verkletrie/src/main/java/org/hyperledger/besu/ethereum/verkletrie/bandersnatch/PointAffine.java index 400fa4fbeba..074033e1935 100644 --- a/ethereum/verkletrie/src/main/java/org/hyperledger/besu/ethereum/verkletrie/bandersnatch/PointAffine.java +++ b/ethereum/verkletrie/src/main/java/org/hyperledger/besu/ethereum/verkletrie/bandersnatch/PointAffine.java @@ -1,5 +1,5 @@ /* - * Copyright Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/verkletrie/src/main/java/org/hyperledger/besu/ethereum/verkletrie/bandersnatch/fp/Element.java b/ethereum/verkletrie/src/main/java/org/hyperledger/besu/ethereum/verkletrie/bandersnatch/fp/Element.java index 02d2e43b9f8..535cde2c5d5 100644 --- a/ethereum/verkletrie/src/main/java/org/hyperledger/besu/ethereum/verkletrie/bandersnatch/fp/Element.java +++ b/ethereum/verkletrie/src/main/java/org/hyperledger/besu/ethereum/verkletrie/bandersnatch/fp/Element.java @@ -1,5 +1,5 @@ /* - * Copyright Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/verkletrie/src/main/java/org/hyperledger/besu/ethereum/verkletrie/bandersnatch/fr/Element.java b/ethereum/verkletrie/src/main/java/org/hyperledger/besu/ethereum/verkletrie/bandersnatch/fr/Element.java index 8fd84f7041a..28fb0522359 100644 --- a/ethereum/verkletrie/src/main/java/org/hyperledger/besu/ethereum/verkletrie/bandersnatch/fr/Element.java +++ b/ethereum/verkletrie/src/main/java/org/hyperledger/besu/ethereum/verkletrie/bandersnatch/fr/Element.java @@ -1,5 +1,5 @@ /* - * Copyright Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/verkletrie/src/test/java/org/hyperledger/besu/ethereum/verkletrie/bandersnatch/fp/ElementTest.java b/ethereum/verkletrie/src/test/java/org/hyperledger/besu/ethereum/verkletrie/bandersnatch/fp/ElementTest.java index d7f72dd74b2..b51723c5783 100644 --- a/ethereum/verkletrie/src/test/java/org/hyperledger/besu/ethereum/verkletrie/bandersnatch/fp/ElementTest.java +++ b/ethereum/verkletrie/src/test/java/org/hyperledger/besu/ethereum/verkletrie/bandersnatch/fp/ElementTest.java @@ -1,5 +1,5 @@ /* - * Copyright Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/ethereum/verkletrie/src/test/java/org/hyperledger/besu/ethereum/verkletrie/bandersnatch/fr/ElementTest.java b/ethereum/verkletrie/src/test/java/org/hyperledger/besu/ethereum/verkletrie/bandersnatch/fr/ElementTest.java index b4046ac6ab4..3a3d6f1b124 100644 --- a/ethereum/verkletrie/src/test/java/org/hyperledger/besu/ethereum/verkletrie/bandersnatch/fr/ElementTest.java +++ b/ethereum/verkletrie/src/test/java/org/hyperledger/besu/ethereum/verkletrie/bandersnatch/fr/ElementTest.java @@ -1,5 +1,5 @@ /* - * Copyright Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/build.gradle b/evm/build.gradle index 83caaae31b1..a48cb916493 100644 --- a/evm/build.gradle +++ b/evm/build.gradle @@ -25,6 +25,7 @@ jar { 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash, 'Automatic-Module-Name': 'org.hyperledger.besu.evm' ) } @@ -44,9 +45,11 @@ dependencies { implementation 'io.tmio:tuweni-units' implementation 'org.hyperledger.besu:arithmetic' implementation 'org.hyperledger.besu:bls12-381' + implementation'org.hyperledger.besu:gnark' implementation 'tech.pegasys:jc-kzg-4844' compileOnly 'com.fasterxml.jackson.core:jackson-databind' + compileOnly 'io.vertx:vertx-core' testImplementation 'com.fasterxml.jackson.core:jackson-databind' testImplementation 'info.picocli:picocli' @@ -54,8 +57,6 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter' testImplementation 'org.mockito:mockito-core' testImplementation 'org.mockito:mockito-junit-jupiter' - - testRuntimeOnly 'org.junit.vintage:junit-vintage-engine' } publishing { diff --git a/evm/src/main/java/org/hyperledger/besu/collections/trie/BytesTrieSet.java b/evm/src/main/java/org/hyperledger/besu/collections/trie/BytesTrieSet.java index 20e2d49af5c..a005d4eedab 100644 --- a/evm/src/main/java/org/hyperledger/besu/collections/trie/BytesTrieSet.java +++ b/evm/src/main/java/org/hyperledger/besu/collections/trie/BytesTrieSet.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.collections.trie; diff --git a/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoList.java b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoList.java index 72cdc56fee1..22898dba89a 100644 --- a/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoList.java +++ b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoList.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.collections.undo; diff --git a/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoMap.java b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoMap.java index 08a20b447ca..89359ac796c 100644 --- a/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoMap.java +++ b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoMap.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.collections.undo; @@ -32,7 +31,8 @@ * increases the global mark, so a mark set in once collection is usable across all * UndoableCollection instances. * - * @param The type of the collection. + * @param The type of the keys maintained by this map. + * @param The type of mapped values. */ public class UndoMap implements Map, Undoable { diff --git a/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoNavigableMap.java b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoNavigableMap.java index 3bbdc242e1e..fb268d43f8b 100644 --- a/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoNavigableMap.java +++ b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoNavigableMap.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.collections.undo; @@ -28,7 +27,8 @@ * increases the global mark, so a mark set in once collection is usable across all * UndoableCollection instances. * - * @param The type of the collection. + * @param The type of the keys maintained by this map. + * @param The type of mapped values. */ public class UndoNavigableMap extends UndoMap implements NavigableMap { diff --git a/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoScalar.java b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoScalar.java index 0e3356c379b..ff06a9e5e72 100644 --- a/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoScalar.java +++ b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoScalar.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.collections.undo; @@ -61,7 +60,7 @@ public long lastUpdate() { } /** - * Has this scalar had any change since the inital value + * Has this scalar had any change since the initial value * * @return true if there are any changes to undo */ diff --git a/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoSet.java b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoSet.java index d1f018ac14f..01a3d8363a9 100644 --- a/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoSet.java +++ b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoSet.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.collections.undo; diff --git a/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoTable.java b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoTable.java index 0109ccdc8f7..967cd14f7a3 100644 --- a/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoTable.java +++ b/evm/src/main/java/org/hyperledger/besu/collections/undo/UndoTable.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.collections.undo; @@ -35,7 +34,9 @@ * increases the global mark, so a mark set in once collection is usable across all * UndoableCollection instances. * - * @param The type of the collection. + * @param the type of the table row keys + * @param the type of the table column keys + * @param the type of the mapped values */ public class UndoTable implements Table, Undoable { diff --git a/evm/src/main/java/org/hyperledger/besu/collections/undo/Undoable.java b/evm/src/main/java/org/hyperledger/besu/collections/undo/Undoable.java index 274bbc2297b..e92154ee42b 100644 --- a/evm/src/main/java/org/hyperledger/besu/collections/undo/Undoable.java +++ b/evm/src/main/java/org/hyperledger/besu/collections/undo/Undoable.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.collections.undo; @@ -49,7 +48,7 @@ static long incrementMarkStatic() { } /** - * The last time this object was updated. Any undo requrests greater than this mark will result in + * The last time this object was updated. Any undo requests greater than this mark will result in * no changes. * * @return The most recent mark. diff --git a/evm/src/main/java/org/hyperledger/besu/evm/ClassicEVMs.java b/evm/src/main/java/org/hyperledger/besu/evm/ClassicEVMs.java new file mode 100644 index 00000000000..07b0f2c87f5 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/ClassicEVMs.java @@ -0,0 +1,64 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm; + +import static org.hyperledger.besu.evm.MainnetEVMs.registerIstanbulOperations; + +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.operation.OperationRegistry; +import org.hyperledger.besu.evm.operation.Push0Operation; + +import java.math.BigInteger; + +/** Provides EVMs supporting the appropriate operations for ETC network upgrades. */ +public class ClassicEVMs { + /** Default constructor. */ + private ClassicEVMs() {} + + /** + * spiral evm. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM spiral( + final GasCalculator gasCalculator, + final BigInteger chainId, + final EvmConfiguration evmConfiguration) { + return new EVM( + spiralOperations(gasCalculator, chainId), + gasCalculator, + evmConfiguration, + EvmSpecVersion.SHANGHAI); + } + + /** + * spiral operations' registry. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @return the operation registry + */ + public static OperationRegistry spiralOperations( + final GasCalculator gasCalculator, final BigInteger chainId) { + OperationRegistry registry = new OperationRegistry(); + registerIstanbulOperations(registry, gasCalculator, chainId); + registry.put(new Push0Operation(gasCalculator)); + return registry; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/Code.java b/evm/src/main/java/org/hyperledger/besu/evm/Code.java index 3ccb87c3a10..ac269d281da 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/Code.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/Code.java @@ -17,6 +17,8 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.evm.code.CodeSection; +import java.util.Optional; + import org.apache.tuweni.bytes.Bytes; /** Represents EVM code associated with an account. */ @@ -30,6 +32,20 @@ public interface Code { */ int getSize(); + /** + * Size of the data in bytes. This is for the data only, + * + * @return size of code in bytes. + */ + int getDataSize(); + + /** + * Declared size of the data in bytes. For containers with aux data this may be larger. + * + * @return the declared data size + */ + int getDeclaredDataSize(); + /** * Get the bytes for the entire container, for example what EXTCODECOPY would want. For V0 it is * the same as getCodeBytes, for V1 it is the entire container, not just the data section. @@ -82,4 +98,64 @@ public interface Code { * @return The version of hte ode. */ int getEofVersion(); + + /** + * Returns the count of subcontainers, or zero if there are none or if the code version does not + * support subcontainers. + * + * @return The subcontainer count or zero if not supported; + */ + int getSubcontainerCount(); + + /** + * Returns the subcontainer at the selected index. If the container doesn't exist or is invalid, + * an empty result is returned. Legacy code always returns empty. + * + * @param index the index in the container to return + * @param auxData any Auxiliary data to append to the subcontainer code. If fetching an initcode + * container, pass null. + * @param evm the EVM in which we are instantiating the code + * @return Either the subcontainer, or empty. + */ + Optional getSubContainer(final int index, final Bytes auxData, EVM evm); + + /** + * Loads data from the appropriate data section + * + * @param offset Where within the data section to start copying + * @param length how many bytes to copy + * @return A slice of the code containing the requested data + */ + Bytes getData(final int offset, final int length); + + /** + * Read a signed 16-bit big-endian integer + * + * @param startIndex the index to start reading the integer in the code + * @return a java int representing the 16-bit signed integer. + */ + int readBigEndianI16(final int startIndex); + + /** + * Read an unsigned 16 bit big-endian integer + * + * @param startIndex the index to start reading the integer in the code + * @return a java int representing the 16-bit unsigned integer. + */ + int readBigEndianU16(final int startIndex); + + /** + * Read an unsigned 8-bit integer + * + * @param startIndex the index to start reading the integer in the code + * @return a java int representing the 8-bit unsigned integer. + */ + int readU8(final int startIndex); + + /** + * A more readable representation of the hex bytes, including whitespace and comments after hashes + * + * @return The pretty printed code + */ + String prettyPrint(); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java index 4ce73ef9cc6..69575143923 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/EVM.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/EVM.java @@ -14,11 +14,13 @@ */ package org.hyperledger.besu.evm; +import static com.google.common.base.Preconditions.checkNotNull; import static org.hyperledger.besu.evm.operation.PushOperation.PUSH_BASE; import static org.hyperledger.besu.evm.operation.SwapOperation.SWAP_BASE; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.evm.code.CodeFactory; +import org.hyperledger.besu.evm.code.EOFLayout; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.frame.MessageFrame.State; @@ -86,6 +88,7 @@ public class EVM { private final OperationRegistry operations; private final GasCalculator gasCalculator; private final Operation endOfScriptStop; + private final CodeFactory codeFactory; private final CodeCache codeCache; private final EvmConfiguration evmConfiguration; private final EvmSpecVersion evmSpecVersion; @@ -113,6 +116,11 @@ public EVM( this.codeCache = new CodeCache(evmConfiguration); this.evmSpecVersion = evmSpecVersion; + codeFactory = + new CodeFactory( + evmSpecVersion.maxEofVersion, + evmConfiguration.maxInitcodeSizeOverride().orElse(evmSpecVersion.maxInitcodeSize)); + enableShanghai = EvmSpecVersion.SHANGHAI.ordinal() <= evmSpecVersion.ordinal(); } @@ -134,10 +142,28 @@ public int getMaxEOFVersion() { return evmSpecVersion.maxEofVersion; } + /** + * Gets the max code size, taking configuration and version into account + * + * @return The max code size override, if not set the max code size for the EVM version. + */ + public int getMaxCodeSize() { + return evmConfiguration.maxCodeSizeOverride().orElse(evmSpecVersion.maxCodeSize); + } + + /** + * Gets the max initcode Size, taking configuration and version into account + * + * @return The max initcode size override, if not set the max initcode size for the EVM version. + */ + public int getMaxInitcodeSize() { + return evmConfiguration.maxInitcodeSizeOverride().orElse(evmSpecVersion.maxInitcodeSize); + } + /** * Returns the non-fork related configuration parameters of the EVM. * - * @return the EVM coniguration. + * @return the EVM configuration. */ public EvmConfiguration getEvmConfiguration() { return evmConfiguration; @@ -230,73 +256,77 @@ public void runToHalt(final MessageFrame frame, final OperationTracer tracing) { case 0x56 -> JumpOperation.staticOperation(frame); case 0x57 -> JumpiOperation.staticOperation(frame); case 0x5b -> JumpDestOperation.JUMPDEST_SUCCESS; - case 0x5f -> enableShanghai - ? Push0Operation.staticOperation(frame) - : InvalidOperation.INVALID_RESULT; + case 0x5f -> + enableShanghai + ? Push0Operation.staticOperation(frame) + : InvalidOperation.INVALID_RESULT; case 0x60, // PUSH1-32 - 0x61, - 0x62, - 0x63, - 0x64, - 0x65, - 0x66, - 0x67, - 0x68, - 0x69, - 0x6a, - 0x6b, - 0x6c, - 0x6d, - 0x6e, - 0x6f, - 0x70, - 0x71, - 0x72, - 0x73, - 0x74, - 0x75, - 0x76, - 0x77, - 0x78, - 0x79, - 0x7a, - 0x7b, - 0x7c, - 0x7d, - 0x7e, - 0x7f -> PushOperation.staticOperation(frame, code, pc, opcode - PUSH_BASE); + 0x61, + 0x62, + 0x63, + 0x64, + 0x65, + 0x66, + 0x67, + 0x68, + 0x69, + 0x6a, + 0x6b, + 0x6c, + 0x6d, + 0x6e, + 0x6f, + 0x70, + 0x71, + 0x72, + 0x73, + 0x74, + 0x75, + 0x76, + 0x77, + 0x78, + 0x79, + 0x7a, + 0x7b, + 0x7c, + 0x7d, + 0x7e, + 0x7f -> + PushOperation.staticOperation(frame, code, pc, opcode - PUSH_BASE); case 0x80, // DUP1-16 - 0x81, - 0x82, - 0x83, - 0x84, - 0x85, - 0x86, - 0x87, - 0x88, - 0x89, - 0x8a, - 0x8b, - 0x8c, - 0x8d, - 0x8e, - 0x8f -> DupOperation.staticOperation(frame, opcode - DupOperation.DUP_BASE); + 0x81, + 0x82, + 0x83, + 0x84, + 0x85, + 0x86, + 0x87, + 0x88, + 0x89, + 0x8a, + 0x8b, + 0x8c, + 0x8d, + 0x8e, + 0x8f -> + DupOperation.staticOperation(frame, opcode - DupOperation.DUP_BASE); case 0x90, // SWAP1-16 - 0x91, - 0x92, - 0x93, - 0x94, - 0x95, - 0x96, - 0x97, - 0x98, - 0x99, - 0x9a, - 0x9b, - 0x9c, - 0x9d, - 0x9e, - 0x9f -> SwapOperation.staticOperation(frame, opcode - SWAP_BASE); + 0x91, + 0x92, + 0x93, + 0x94, + 0x95, + 0x96, + 0x97, + 0x98, + 0x99, + 0x9a, + 0x9b, + 0x9c, + 0x9d, + 0x9e, + 0x9f -> + SwapOperation.staticOperation(frame, opcode - SWAP_BASE); default -> { // unoptimized operations frame.setCurrentOperation(currentOperation); yield currentOperation.execute(frame, this); @@ -344,13 +374,42 @@ public Operation[] getOperationsUnsafe() { * @return the code */ public Code getCode(final Hash codeHash, final Bytes codeBytes) { - Code result = codeHash == null ? null : codeCache.getIfPresent(codeHash); + checkNotNull(codeHash); + Code result = codeCache.getIfPresent(codeHash); if (result == null) { - result = CodeFactory.createCode(codeBytes, evmSpecVersion.getMaxEofVersion(), false); - if (codeHash != null) { - codeCache.put(codeHash, result); - } + result = getCodeUncached(codeBytes); + codeCache.put(codeHash, result); } return result; } + + /** + * Gets code skipping the code cache. + * + * @param codeBytes the code bytes + * @return the code + */ + public Code getCodeUncached(final Bytes codeBytes) { + return codeFactory.createCode(codeBytes); + } + + /** + * Gets code for creation. Skips code cache and allows for extra data after EOF contracts. + * + * @param codeBytes the code bytes + * @return the code + */ + public Code getCodeForCreation(final Bytes codeBytes) { + return codeFactory.createCode(codeBytes, true); + } + + /** + * Parse the EOF Layout of a byte-stream. No Code or stack validation is performed. + * + * @param bytes the bytes to parse + * @return an EOF layout represented by they byte-stream. + */ + public EOFLayout parseEOF(final Bytes bytes) { + return EOFLayout.parseEOF(bytes, true); + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/EvmSpecVersion.java b/evm/src/main/java/org/hyperledger/besu/evm/EvmSpecVersion.java index 88b886bb213..7d4344c6bcf 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/EvmSpecVersion.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/EvmSpecVersion.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,10 +11,12 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm; +import org.hyperledger.besu.datatypes.HardforkId; +import org.hyperledger.besu.datatypes.HardforkId.MainnetHardforkId; + import java.util.Comparator; import java.util.stream.Stream; @@ -24,66 +26,78 @@ /** The enum Evm spec version. */ public enum EvmSpecVersion { /** Frontier evm spec version. */ - FRONTIER(0, true, "Frontier", "Finalized"), + FRONTIER(MainnetHardforkId.FRONTIER, Integer.MAX_VALUE, Integer.MAX_VALUE, 0), /** Homestead evm spec version. */ - HOMESTEAD(0, true, "Homestead", "Finalized"), + HOMESTEAD(MainnetHardforkId.HOMESTEAD, Integer.MAX_VALUE, Integer.MAX_VALUE, 0), /** Tangerine Whistle evm spec version. */ - TANGERINE_WHISTLE(0, true, "Tangerine Whistle", "Finalized"), + TANGERINE_WHISTLE(MainnetHardforkId.TANGERINE_WHISTLE, Integer.MAX_VALUE, Integer.MAX_VALUE, 0), /** Spurious Dragon evm spec version. */ - SPURIOUS_DRAGON(0, true, "Spuruous Dragon", "Finalized"), + SPURIOUS_DRAGON(MainnetHardforkId.SPURIOUS_DRAGON, 0x6000, Integer.MAX_VALUE, 0), /** Byzantium evm spec version. */ - BYZANTIUM(0, true, "Byzantium", "Finalized"), + BYZANTIUM(MainnetHardforkId.BYZANTIUM, 0x6000, Integer.MAX_VALUE, 0), /** Constantinople evm spec version. */ - CONSTANTINOPLE(0, true, "Constantinople", "Did not reach Mainnet"), + CONSTANTINOPLE(MainnetHardforkId.CONSTANTINOPLE, 0x6000, Integer.MAX_VALUE, 0), /** Petersburg / ConstantinopleFix evm spec version. */ - PETERSBURG(0, true, "ConstantinopleFix", "Finalized (also called Petersburg)"), + PETERSBURG(MainnetHardforkId.PETERSBURG, 0x6000, Integer.MAX_VALUE, 0), /** Istanbul evm spec version. */ - ISTANBUL(0, true, "Istanbul", "Finalized"), + ISTANBUL(MainnetHardforkId.ISTANBUL, 0x6000, Integer.MAX_VALUE, 0), /** Berlin evm spec version */ - BERLIN(0, true, "Berlin", "Finalized"), + BERLIN(MainnetHardforkId.BERLIN, 0x6000, Integer.MAX_VALUE, 0), /** London evm spec version. */ - LONDON(0, true, "London", "Finalized"), + LONDON(MainnetHardforkId.LONDON, 0x6000, Integer.MAX_VALUE, 0), /** Paris evm spec version. */ - PARIS(0, true, "Merge", "Finalized (also called Paris)"), + PARIS(MainnetHardforkId.PARIS, 0x6000, Integer.MAX_VALUE, 0), /** Shanghai evm spec version. */ - SHANGHAI(0, true, "Shanghai", "Finalized"), + SHANGHAI(MainnetHardforkId.SHANGHAI, 0x6000, 0xc000, 0), + /** Cancun evm spec version. */ + CANCUN(MainnetHardforkId.CANCUN, 0x6000, 0xc000, 0), /** Cancun evm spec version. */ - CANCUN(0, false, "Cancun", "In Development"), + CANCUN_EOF(MainnetHardforkId.CANCUN_EOF, 0x6000, 0xc000, 1), /** Prague evm spec version. */ - PRAGUE(0, false, "Prague", "Placeholder"), + PRAGUE(MainnetHardforkId.PRAGUE, 0x6000, 0xc000, 0), + /** PragueEOF evm spec version. */ + PRAGUE_EOF(MainnetHardforkId.PRAGUE_EOF, 0x6000, 0xc000, 1), /** Osaka evm spec version. */ - OSAKA(0, false, "Osaka", "Placeholder"), + OSAKA(MainnetHardforkId.OSAKA, 0x6000, 0xc000, 1), + /** Amsterdam evm spec version. */ + AMSTERDAM(MainnetHardforkId.AMSTERDAM, 0x6000, 0xc000, 1), /** Bogota evm spec version. */ - BOGOTA(0, false, "Bogota", "Placeholder"), + BOGOTA(MainnetHardforkId.BOGOTA, 0x6000, 0xc000, 1), + /** Polis evm spec version. */ + POLIS(MainnetHardforkId.POLIS, 0x6000, 0xc000, 1), + /** Bangkok evm spec version. */ + BANGKOK(MainnetHardforkId.BANGKOK, 0x6000, 0xc000, 1), /** Development fork for unscheduled EIPs */ - FUTURE_EIPS(1, false, "Future_EIPs", "Development, for accepted and unscheduled EIPs"), - /** Development fork for EIPs not accepted to Mainnet */ - EXPERIMENTAL_EIPS(1, false, "Experimental_EIPs", "Development, for experimental EIPs"); + FUTURE_EIPS(MainnetHardforkId.FUTURE_EIPS, 0x6000, 0xc000, 1), + /** Development fork for EIPs that are not yet accepted to Mainnet */ + EXPERIMENTAL_EIPS(MainnetHardforkId.EXPERIMENTAL_EIPS, 0x6000, 0xc000, 1); private static final Logger LOGGER = LoggerFactory.getLogger(EvmSpecVersion.class); - /** The Spec finalized. */ - final boolean specFinalized; + /** What hardfork did this VM version first show up in? */ + final HardforkId initialHardfork; + /** The Max eof version. */ final int maxEofVersion; - /** Public name matching execution-spec-tests name */ - final String name; - /** A brief description of the state of the fork */ - final String description; + /** Maximum size of deployed code */ + final int maxCodeSize; + + /** Maximum size of initcode */ + final int maxInitcodeSize; /** The Version warned. */ boolean versionWarned = false; EvmSpecVersion( - final int maxEofVersion, - final boolean specFinalized, - final String name, - final String description) { + final HardforkId initialHarfork, + final int maxCodeSize, + final int maxInitcodeSize, + final int maxEofVersion) { + this.initialHardfork = initialHarfork; this.maxEofVersion = maxEofVersion; - this.specFinalized = specFinalized; - this.name = name; - this.description = description; + this.maxCodeSize = maxCodeSize; + this.maxInitcodeSize = maxInitcodeSize; } /** @@ -93,7 +107,13 @@ public enum EvmSpecVersion { * @return the current mainnet for as of the release of this version of Besu */ public static EvmSpecVersion defaultVersion() { - return SHANGHAI; + EvmSpecVersion answer = null; + for (EvmSpecVersion version : EvmSpecVersion.values()) { + if (version.initialHardfork.finalized()) { + answer = version; + } + } + return answer; } /** @@ -105,13 +125,31 @@ public int getMaxEofVersion() { return maxEofVersion; } + /** + * Gets max deployed code size this EVM supports. + * + * @return the max eof version + */ + public int getMaxCodeSize() { + return maxCodeSize; + } + + /** + * Gets max initcode size this EVM supports. + * + * @return the max eof version + */ + public int getMaxInitcodeSize() { + return maxInitcodeSize; + } + /** * Name of the fork, in execution-spec-tests form * * @return name of the fork */ public String getName() { - return name; + return initialHardfork.name(); } /** @@ -120,7 +158,7 @@ public String getName() { * @return description */ public String getDescription() { - return description; + return initialHardfork.description(); } /** Maybe warn version. */ @@ -130,7 +168,7 @@ public void maybeWarnVersion() { return; } - if (!specFinalized) { + if (!initialHardfork.finalized()) { LOGGER.error( "****** Not for Production Network Use ******\nExecuting code from EVM Spec Version {}, which has not been finalized.\n****** Not for Production Network Use ******", this.name()); @@ -145,6 +183,14 @@ public void maybeWarnVersion() { * @return the EVM spec version for that fork, or null if no fork matched. */ public static EvmSpecVersion fromName(final String name) { + // TODO remove once PragueEOF settles + if ("prague".equalsIgnoreCase(name)) { + return EvmSpecVersion.PRAGUE_EOF; + } + // TODO remove once PragueEOF settles + if ("cancuneof".equalsIgnoreCase(name)) { + return EvmSpecVersion.CANCUN_EOF; + } for (var version : EvmSpecVersion.values()) { if (version.name().equalsIgnoreCase(name)) { return version; @@ -161,7 +207,7 @@ public static EvmSpecVersion fromName(final String name) { */ public static EvmSpecVersion mostRecent() { return Stream.of(EvmSpecVersion.values()) - .filter(v -> v.specFinalized) + .filter(v -> v.initialHardfork.finalized()) .max(Comparator.naturalOrder()) .orElseThrow(); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java b/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java index 0d9fa18b591..998776d7140 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/MainnetEVMs.java @@ -24,6 +24,8 @@ import org.hyperledger.besu.evm.gascalculator.IstanbulGasCalculator; import org.hyperledger.besu.evm.gascalculator.LondonGasCalculator; import org.hyperledger.besu.evm.gascalculator.PetersburgGasCalculator; +import org.hyperledger.besu.evm.gascalculator.PragueEOFGasCalculator; +import org.hyperledger.besu.evm.gascalculator.PragueGasCalculator; import org.hyperledger.besu.evm.gascalculator.ShanghaiGasCalculator; import org.hyperledger.besu.evm.gascalculator.SpuriousDragonGasCalculator; import org.hyperledger.besu.evm.gascalculator.TangerineWhistleGasCalculator; @@ -52,15 +54,25 @@ import org.hyperledger.besu.evm.operation.CoinbaseOperation; import org.hyperledger.besu.evm.operation.Create2Operation; import org.hyperledger.besu.evm.operation.CreateOperation; +import org.hyperledger.besu.evm.operation.DataCopyOperation; +import org.hyperledger.besu.evm.operation.DataLoadNOperation; +import org.hyperledger.besu.evm.operation.DataLoadOperation; +import org.hyperledger.besu.evm.operation.DataSizeOperation; import org.hyperledger.besu.evm.operation.DelegateCallOperation; import org.hyperledger.besu.evm.operation.DifficultyOperation; import org.hyperledger.besu.evm.operation.DivOperation; +import org.hyperledger.besu.evm.operation.DupNOperation; import org.hyperledger.besu.evm.operation.DupOperation; +import org.hyperledger.besu.evm.operation.EOFCreateOperation; import org.hyperledger.besu.evm.operation.EqOperation; +import org.hyperledger.besu.evm.operation.ExchangeOperation; import org.hyperledger.besu.evm.operation.ExpOperation; +import org.hyperledger.besu.evm.operation.ExtCallOperation; import org.hyperledger.besu.evm.operation.ExtCodeCopyOperation; import org.hyperledger.besu.evm.operation.ExtCodeHashOperation; import org.hyperledger.besu.evm.operation.ExtCodeSizeOperation; +import org.hyperledger.besu.evm.operation.ExtDelegateCallOperation; +import org.hyperledger.besu.evm.operation.ExtStaticCallOperation; import org.hyperledger.besu.evm.operation.GasLimitOperation; import org.hyperledger.besu.evm.operation.GasOperation; import org.hyperledger.besu.evm.operation.GasPriceOperation; @@ -68,6 +80,7 @@ import org.hyperledger.besu.evm.operation.InvalidOperation; import org.hyperledger.besu.evm.operation.IsZeroOperation; import org.hyperledger.besu.evm.operation.JumpDestOperation; +import org.hyperledger.besu.evm.operation.JumpFOperation; import org.hyperledger.besu.evm.operation.JumpOperation; import org.hyperledger.besu.evm.operation.JumpiOperation; import org.hyperledger.besu.evm.operation.Keccak256Operation; @@ -95,7 +108,9 @@ import org.hyperledger.besu.evm.operation.RelativeJumpOperation; import org.hyperledger.besu.evm.operation.RelativeJumpVectorOperation; import org.hyperledger.besu.evm.operation.RetFOperation; +import org.hyperledger.besu.evm.operation.ReturnContractOperation; import org.hyperledger.besu.evm.operation.ReturnDataCopyOperation; +import org.hyperledger.besu.evm.operation.ReturnDataLoadOperation; import org.hyperledger.besu.evm.operation.ReturnDataSizeOperation; import org.hyperledger.besu.evm.operation.ReturnOperation; import org.hyperledger.besu.evm.operation.RevertOperation; @@ -114,6 +129,7 @@ import org.hyperledger.besu.evm.operation.StaticCallOperation; import org.hyperledger.besu.evm.operation.StopOperation; import org.hyperledger.besu.evm.operation.SubOperation; +import org.hyperledger.besu.evm.operation.SwapNOperation; import org.hyperledger.besu.evm.operation.SwapOperation; import org.hyperledger.besu.evm.operation.TLoadOperation; import org.hyperledger.besu.evm.operation.TStoreOperation; @@ -131,11 +147,6 @@ public class MainnetEVMs { /** The constant DEV_NET_CHAIN_ID. */ public static final BigInteger DEV_NET_CHAIN_ID = BigInteger.valueOf(1337); - /** The constant SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT. */ - public static final int SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT = 0x6000; - /** The constant SHANGHAI_INIT_CODE_SIZE_LIMIT. */ - public static final int SHANGHAI_INIT_CODE_SIZE_LIMIT = 2 * SPURIOUS_DRAGON_CONTRACT_SIZE_LIMIT; - private MainnetEVMs() { // utility class } @@ -223,8 +234,8 @@ public static void registerFrontierOperations( registry.put(new CodeSizeOperation(gasCalculator)); registry.put(new CodeCopyOperation(gasCalculator)); registry.put(new GasPriceOperation(gasCalculator)); - registry.put(new ExtCodeCopyOperation(gasCalculator)); - registry.put(new ExtCodeSizeOperation(gasCalculator)); + registry.put(new ExtCodeCopyOperation(gasCalculator, false)); + registry.put(new ExtCodeSizeOperation(gasCalculator, false)); registry.put(new BlockHashOperation(gasCalculator)); registry.put(new CoinbaseOperation(gasCalculator)); registry.put(new TimestampOperation(gasCalculator)); @@ -247,7 +258,7 @@ public static void registerFrontierOperations( registry.put(new InvalidOperation(gasCalculator)); registry.put(new StopOperation(gasCalculator)); registry.put(new SelfDestructOperation(gasCalculator)); - registry.put(new CreateOperation(gasCalculator, Integer.MAX_VALUE)); + registry.put(new CreateOperation(gasCalculator)); registry.put(new CallOperation(gasCalculator)); registry.put(new CallCodeOperation(gasCalculator)); @@ -457,11 +468,11 @@ public static OperationRegistry constantinopleOperations(final GasCalculator gas public static void registerConstantinopleOperations( final OperationRegistry registry, final GasCalculator gasCalculator) { registerByzantiumOperations(registry, gasCalculator); - registry.put(new Create2Operation(gasCalculator, Integer.MAX_VALUE)); + registry.put(new Create2Operation(gasCalculator)); registry.put(new SarOperation(gasCalculator)); registry.put(new ShlOperation(gasCalculator)); registry.put(new ShrOperation(gasCalculator)); - registry.put(new ExtCodeHashOperation(gasCalculator)); + registry.put(new ExtCodeHashOperation(gasCalculator, false)); } /** @@ -792,8 +803,6 @@ public static void registerShanghaiOperations( final BigInteger chainID) { registerParisOperations(registry, gasCalculator, chainID); registry.put(new Push0Operation(gasCalculator)); - registry.put(new CreateOperation(gasCalculator, SHANGHAI_INIT_CODE_SIZE_LIMIT)); - registry.put(new Create2Operation(gasCalculator, SHANGHAI_INIT_CODE_SIZE_LIMIT)); } /** @@ -805,6 +814,7 @@ public static void registerShanghaiOperations( public static EVM cancun(final EvmConfiguration evmConfiguration) { return cancun(DEV_NET_CHAIN_ID, evmConfiguration); } + /** * Cancun evm. * @@ -879,6 +889,76 @@ public static void registerCancunOperations( registry.put(new BlobBaseFeeOperation(gasCalculator)); } + /** + * CancunEOF evm. + * + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM cancunEOF(final EvmConfiguration evmConfiguration) { + return cancunEOF(DEV_NET_CHAIN_ID, evmConfiguration); + } + + /** + * CancunEOF evm. + * + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM cancunEOF(final BigInteger chainId, final EvmConfiguration evmConfiguration) { + return cancunEOF(new CancunGasCalculator(), chainId, evmConfiguration); + } + + /** + * CancunEOF evm. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM cancunEOF( + final GasCalculator gasCalculator, + final BigInteger chainId, + final EvmConfiguration evmConfiguration) { + return new EVM( + cancunEOFOperations(gasCalculator, chainId), + gasCalculator, + evmConfiguration, + EvmSpecVersion.CANCUN_EOF); + } + + /** + * Operation registry for PragueEOF's operations. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @return the operation registry + */ + public static OperationRegistry cancunEOFOperations( + final GasCalculator gasCalculator, final BigInteger chainId) { + OperationRegistry operationRegistry = new OperationRegistry(); + registerCancunEOFOperations(operationRegistry, gasCalculator, chainId); + return operationRegistry; + } + + /** + * Register CancunEOF's operations. + * + * @param registry the registry + * @param gasCalculator the gas calculator + * @param chainID the chain id + */ + public static void registerCancunEOFOperations( + final OperationRegistry registry, + final GasCalculator gasCalculator, + final BigInteger chainID) { + registerCancunOperations(registry, gasCalculator, chainID); + + registerEOFOperations(registry, gasCalculator); + } + /** * Prague evm. * @@ -888,6 +968,7 @@ public static void registerCancunOperations( public static EVM prague(final EvmConfiguration evmConfiguration) { return prague(DEV_NET_CHAIN_ID, evmConfiguration); } + /** * Prague evm. * @@ -896,7 +977,7 @@ public static EVM prague(final EvmConfiguration evmConfiguration) { * @return the evm */ public static EVM prague(final BigInteger chainId, final EvmConfiguration evmConfiguration) { - return prague(new CancunGasCalculator(), chainId, evmConfiguration); + return prague(new PragueGasCalculator(), chainId, evmConfiguration); } /** @@ -944,6 +1025,119 @@ public static void registerPragueOperations( final GasCalculator gasCalculator, final BigInteger chainID) { registerCancunOperations(registry, gasCalculator, chainID); + + // TODO add EOF operations here once PragueEOF is collapsed into Prague + } + + /** + * PragueEOF evm. + * + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM pragueEOF(final EvmConfiguration evmConfiguration) { + return pragueEOF(DEV_NET_CHAIN_ID, evmConfiguration); + } + + /** + * PragueEOF evm. + * + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM pragueEOF(final BigInteger chainId, final EvmConfiguration evmConfiguration) { + return pragueEOF(new PragueEOFGasCalculator(), chainId, evmConfiguration); + } + + /** + * PragueEOF evm. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM pragueEOF( + final GasCalculator gasCalculator, + final BigInteger chainId, + final EvmConfiguration evmConfiguration) { + return new EVM( + pragueEOFOperations(gasCalculator, chainId), + gasCalculator, + evmConfiguration, + EvmSpecVersion.PRAGUE_EOF); + } + + /** + * Operation registry for PragueEOF's operations. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @return the operation registry + */ + public static OperationRegistry pragueEOFOperations( + final GasCalculator gasCalculator, final BigInteger chainId) { + OperationRegistry operationRegistry = new OperationRegistry(); + registerPragueEOFOperations(operationRegistry, gasCalculator, chainId); + return operationRegistry; + } + + /** + * Register PragueEOF's operations. + * + * @param registry the registry + * @param gasCalculator the gas calculator + * @param chainID the chain id + */ + public static void registerPragueEOFOperations( + final OperationRegistry registry, + final GasCalculator gasCalculator, + final BigInteger chainID) { + registerPragueOperations(registry, gasCalculator, chainID); + + registerEOFOperations(registry, gasCalculator); + } + + private static void registerEOFOperations( + final OperationRegistry registry, final GasCalculator gasCalculator) { + // EIP-663 Unlimited Swap and Dup + registry.put(new DupNOperation(gasCalculator)); + registry.put(new SwapNOperation(gasCalculator)); + registry.put(new ExchangeOperation(gasCalculator)); + + // EIP-3540 EOF Aware EXTCODE* operations + registry.put(new ExtCodeCopyOperation(gasCalculator, true)); + registry.put(new ExtCodeHashOperation(gasCalculator, true)); + registry.put(new ExtCodeSizeOperation(gasCalculator, true)); + + // EIP-4200 relative jump + registry.put(new RelativeJumpOperation(gasCalculator)); + registry.put(new RelativeJumpIfOperation(gasCalculator)); + registry.put(new RelativeJumpVectorOperation(gasCalculator)); + + // EIP-4750 EOF Code Sections + registry.put(new CallFOperation(gasCalculator)); + registry.put(new RetFOperation(gasCalculator)); + + // EIP-6209 JUMPF Instruction + registry.put(new JumpFOperation(gasCalculator)); + + // EIP-7069 Revamped EOF Call + registry.put(new ExtCallOperation(gasCalculator)); + registry.put(new ExtDelegateCallOperation(gasCalculator)); + registry.put(new ExtStaticCallOperation(gasCalculator)); + registry.put(new ReturnDataLoadOperation(gasCalculator)); + + // EIP-7480 EOF Data Section Access + registry.put(new DataLoadOperation(gasCalculator)); + registry.put(new DataLoadNOperation(gasCalculator)); + registry.put(new DataSizeOperation(gasCalculator)); + registry.put(new DataCopyOperation(gasCalculator)); + + // EIP-7620 EOF Create and Return Contract operation + registry.put(new EOFCreateOperation(gasCalculator)); + registry.put(new ReturnContractOperation(gasCalculator)); } /** @@ -955,6 +1149,7 @@ public static void registerPragueOperations( public static EVM osaka(final EvmConfiguration evmConfiguration) { return osaka(DEV_NET_CHAIN_ID, evmConfiguration); } + /** * Osaka evm. * @@ -963,7 +1158,7 @@ public static EVM osaka(final EvmConfiguration evmConfiguration) { * @return the evm */ public static EVM osaka(final BigInteger chainId, final EvmConfiguration evmConfiguration) { - return osaka(new CancunGasCalculator(), chainId, evmConfiguration); + return osaka(new PragueGasCalculator(), chainId, evmConfiguration); } /** @@ -1010,7 +1205,75 @@ public static void registerOsakaOperations( final OperationRegistry registry, final GasCalculator gasCalculator, final BigInteger chainID) { - registerPragueOperations(registry, gasCalculator, chainID); + registerPragueEOFOperations(registry, gasCalculator, chainID); + } + + /** + * Amsterdam evm. + * + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM amsterdam(final EvmConfiguration evmConfiguration) { + return amsterdam(DEV_NET_CHAIN_ID, evmConfiguration); + } + + /** + * Amsterdam evm. + * + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM amsterdam(final BigInteger chainId, final EvmConfiguration evmConfiguration) { + return amsterdam(new PragueGasCalculator(), chainId, evmConfiguration); + } + + /** + * Amsterdam evm. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM amsterdam( + final GasCalculator gasCalculator, + final BigInteger chainId, + final EvmConfiguration evmConfiguration) { + return new EVM( + amsterdamOperations(gasCalculator, chainId), + gasCalculator, + evmConfiguration, + EvmSpecVersion.AMSTERDAM); + } + + /** + * Operation registry for amsterdam's operations. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @return the operation registry + */ + public static OperationRegistry amsterdamOperations( + final GasCalculator gasCalculator, final BigInteger chainId) { + OperationRegistry operationRegistry = new OperationRegistry(); + registerAmsterdamOperations(operationRegistry, gasCalculator, chainId); + return operationRegistry; + } + + /** + * Register amsterdam operations. + * + * @param registry the registry + * @param gasCalculator the gas calculator + * @param chainID the chain id + */ + public static void registerAmsterdamOperations( + final OperationRegistry registry, + final GasCalculator gasCalculator, + final BigInteger chainID) { + registerOsakaOperations(registry, gasCalculator, chainID); } /** @@ -1022,6 +1285,7 @@ public static void registerOsakaOperations( public static EVM bogota(final EvmConfiguration evmConfiguration) { return bogota(DEV_NET_CHAIN_ID, evmConfiguration); } + /** * Bogota evm. * @@ -1030,7 +1294,7 @@ public static EVM bogota(final EvmConfiguration evmConfiguration) { * @return the evm */ public static EVM bogota(final BigInteger chainId, final EvmConfiguration evmConfiguration) { - return bogota(new CancunGasCalculator(), chainId, evmConfiguration); + return bogota(new PragueGasCalculator(), chainId, evmConfiguration); } /** @@ -1077,7 +1341,143 @@ public static void registerBogotaOperations( final OperationRegistry registry, final GasCalculator gasCalculator, final BigInteger chainID) { - registerOsakaOperations(registry, gasCalculator, chainID); + registerAmsterdamOperations(registry, gasCalculator, chainID); + } + + /** + * Polis evm. + * + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM polis(final EvmConfiguration evmConfiguration) { + return polis(DEV_NET_CHAIN_ID, evmConfiguration); + } + + /** + * Polis evm. + * + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM polis(final BigInteger chainId, final EvmConfiguration evmConfiguration) { + return polis(new PragueGasCalculator(), chainId, evmConfiguration); + } + + /** + * Polis evm. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM polis( + final GasCalculator gasCalculator, + final BigInteger chainId, + final EvmConfiguration evmConfiguration) { + return new EVM( + polisOperations(gasCalculator, chainId), + gasCalculator, + evmConfiguration, + EvmSpecVersion.POLIS); + } + + /** + * Operation registry for Polis's operations. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @return the operation registry + */ + public static OperationRegistry polisOperations( + final GasCalculator gasCalculator, final BigInteger chainId) { + OperationRegistry operationRegistry = new OperationRegistry(); + registerPolisOperations(operationRegistry, gasCalculator, chainId); + return operationRegistry; + } + + /** + * Register polis operations. + * + * @param registry the registry + * @param gasCalculator the gas calculator + * @param chainID the chain id + */ + public static void registerPolisOperations( + final OperationRegistry registry, + final GasCalculator gasCalculator, + final BigInteger chainID) { + registerBogotaOperations(registry, gasCalculator, chainID); + } + + /** + * Bangkok evm. + * + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM bangkok(final EvmConfiguration evmConfiguration) { + return bangkok(DEV_NET_CHAIN_ID, evmConfiguration); + } + + /** + * Bangkok evm. + * + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM bangkok(final BigInteger chainId, final EvmConfiguration evmConfiguration) { + return bangkok(new PragueGasCalculator(), chainId, evmConfiguration); + } + + /** + * Bangkok evm. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @param evmConfiguration the evm configuration + * @return the evm + */ + public static EVM bangkok( + final GasCalculator gasCalculator, + final BigInteger chainId, + final EvmConfiguration evmConfiguration) { + return new EVM( + bangkokOperations(gasCalculator, chainId), + gasCalculator, + evmConfiguration, + EvmSpecVersion.BANGKOK); + } + + /** + * Operation registry for bangkok's operations. + * + * @param gasCalculator the gas calculator + * @param chainId the chain id + * @return the operation registry + */ + public static OperationRegistry bangkokOperations( + final GasCalculator gasCalculator, final BigInteger chainId) { + OperationRegistry operationRegistry = new OperationRegistry(); + registerBangkokOperations(operationRegistry, gasCalculator, chainId); + return operationRegistry; + } + + /** + * Register bangkok operations. + * + * @param registry the registry + * @param gasCalculator the gas calculator + * @param chainID the chain id + */ + public static void registerBangkokOperations( + final OperationRegistry registry, + final GasCalculator gasCalculator, + final BigInteger chainID) { + registerPolisOperations(registry, gasCalculator, chainID); } /** @@ -1098,7 +1498,7 @@ public static EVM futureEips(final EvmConfiguration evmConfiguration) { * @return the evm */ public static EVM futureEips(final BigInteger chainId, final EvmConfiguration evmConfiguration) { - return futureEips(new CancunGasCalculator(), chainId, evmConfiguration); + return futureEips(new PragueGasCalculator(), chainId, evmConfiguration); } /** @@ -1146,13 +1546,6 @@ public static void registerFutureEipsOperations( final GasCalculator gasCalculator, final BigInteger chainID) { registerBogotaOperations(registry, gasCalculator, chainID); - - // "big" EOF - registry.put(new RelativeJumpOperation(gasCalculator)); - registry.put(new RelativeJumpIfOperation(gasCalculator)); - registry.put(new RelativeJumpVectorOperation(gasCalculator)); - registry.put(new CallFOperation(gasCalculator)); - registry.put(new RetFOperation(gasCalculator)); } /** @@ -1174,7 +1567,7 @@ public static EVM experimentalEips(final EvmConfiguration evmConfiguration) { */ public static EVM experimentalEips( final BigInteger chainId, final EvmConfiguration evmConfiguration) { - return experimentalEips(new CancunGasCalculator(), chainId, evmConfiguration); + return experimentalEips(new PragueGasCalculator(), chainId, evmConfiguration); } /** diff --git a/evm/src/main/java/org/hyperledger/besu/evm/account/Account.java b/evm/src/main/java/org/hyperledger/besu/evm/account/Account.java index d57a1093f20..8a65adf617c 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/account/Account.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/account/Account.java @@ -17,6 +17,10 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; + /** * A world state account. * @@ -28,8 +32,10 @@ public interface Account extends AccountState { /** The constant DEFAULT_NONCE. */ long DEFAULT_NONCE = 0L; + /** The constant MAX_NONCE. */ long MAX_NONCE = -1; // per twos compliment rules -1 will be the unsigned max number + /** The constant DEFAULT_BALANCE. */ Wei DEFAULT_BALANCE = Wei.ZERO; @@ -39,4 +45,39 @@ public interface Account extends AccountState { * @return the account address */ Address getAddress(); + + /** + * Does this account have any storage slots that are set to non-zero values? + * + * @return true if the account has no storage values set to non-zero values. False if any storage + * is set. + */ + boolean isStorageEmpty(); + + /** + * Returns the address of the delegated code account if it has one. + * + * @return the address of the delegated code account if it has one otherwise empty. + */ + default Optional

    delegatedCodeAddress() { + return Optional.empty(); + } + + /** + * Returns a boolean to indicate if the account has delegated code. + * + * @return true if the account has delegated code otherwise false. + */ + default boolean hasDelegatedCode() { + return false; + } + + /** + * Returns the code as it is stored in the trie even if it's a delegated code account. + * + * @return the code as it is stored in the trie. + */ + default Bytes getUnprocessedCode() { + return getCode(); + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/account/BaseDelegatedCodeAccount.java b/evm/src/main/java/org/hyperledger/besu/evm/account/BaseDelegatedCodeAccount.java new file mode 100644 index 00000000000..0e5219d8355 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/account/BaseDelegatedCodeAccount.java @@ -0,0 +1,92 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.account; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; + +class BaseDelegatedCodeAccount { + private final WorldUpdater worldUpdater; + + /** The address of the account that has delegated code to be loaded into it. */ + protected final Address delegatedCodeAddress; + + protected BaseDelegatedCodeAccount( + final WorldUpdater worldUpdater, final Address delegatedCodeAddress) { + this.worldUpdater = worldUpdater; + this.delegatedCodeAddress = delegatedCodeAddress; + } + + /** + * Returns the delegated code. + * + * @return the delegated code. + */ + protected Bytes getCode() { + return resolveDelegatedCode(); + } + + /** + * Returns the hash of the delegated code. + * + * @return the hash of the delegated code. + */ + protected Hash getCodeHash() { + final Bytes code = getCode(); + return (code == null || code.isEmpty()) ? Hash.EMPTY : Hash.hash(code); + } + + /** + * Returns the balance of the delegated account. + * + * @return the balance of the delegated account. + */ + protected Wei getDelegatedBalance() { + return getDelegatedAccount().map(Account::getBalance).orElse(Wei.ZERO); + } + + /** + * Returns the nonce of the delegated account. + * + * @return the nonce of the delegated account. + */ + protected long getDelegatedNonce() { + return getDelegatedAccount().map(Account::getNonce).orElse(Account.DEFAULT_NONCE); + } + + /** + * Returns the address of the delegated code. + * + * @return the address of the delegated code. + */ + protected Optional
    delegatedCodeAddress() { + return Optional.of(delegatedCodeAddress); + } + + private Optional getDelegatedAccount() { + return Optional.ofNullable(worldUpdater.getAccount(delegatedCodeAddress)); + } + + private Bytes resolveDelegatedCode() { + + return getDelegatedAccount().map(Account::getUnprocessedCode).orElse(Bytes.EMPTY); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/account/DelegatedCodeAccount.java b/evm/src/main/java/org/hyperledger/besu/evm/account/DelegatedCodeAccount.java new file mode 100644 index 00000000000..1eba364c194 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/account/DelegatedCodeAccount.java @@ -0,0 +1,124 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.account; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.NavigableMap; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; + +/** Wraps an EOA account and includes delegated code to be run on behalf of it. */ +public class DelegatedCodeAccount extends BaseDelegatedCodeAccount implements Account { + + private final Account wrappedAccount; + + /** + * Creates a new AuthorizedCodeAccount. + * + * @param worldUpdater the world updater. + * @param wrappedAccount the account that has delegated code to be loaded into it. + * @param codeDelegationAddress the address of the delegated code. + */ + public DelegatedCodeAccount( + final WorldUpdater worldUpdater, + final Account wrappedAccount, + final Address codeDelegationAddress) { + super(worldUpdater, codeDelegationAddress); + this.wrappedAccount = wrappedAccount; + } + + @Override + public Address getAddress() { + return wrappedAccount.getAddress(); + } + + @Override + public boolean isStorageEmpty() { + return wrappedAccount.isStorageEmpty(); + } + + @Override + public Optional
    delegatedCodeAddress() { + return super.delegatedCodeAddress(); + } + + @Override + public Hash getAddressHash() { + return wrappedAccount.getAddressHash(); + } + + @Override + public long getNonce() { + return wrappedAccount.getNonce(); + } + + @Override + public Wei getBalance() { + return wrappedAccount.getBalance(); + } + + @Override + public Bytes getCode() { + return super.getCode(); + } + + @Override + public Bytes getUnprocessedCode() { + return wrappedAccount.getCode(); + } + + @Override + public Hash getCodeHash() { + return super.getCodeHash(); + } + + @Override + public UInt256 getStorageValue(final UInt256 key) { + return wrappedAccount.getStorageValue(key); + } + + @Override + public UInt256 getOriginalStorageValue(final UInt256 key) { + return wrappedAccount.getOriginalStorageValue(key); + } + + @Override + public boolean isEmpty() { + return getDelegatedNonce() == 0 && getDelegatedBalance().isZero() && !hasCode(); + } + + @Override + public boolean hasCode() { + return !getCode().isEmpty(); + } + + @Override + public NavigableMap storageEntriesFrom( + final Bytes32 startKeyHash, final int limit) { + return wrappedAccount.storageEntriesFrom(startKeyHash, limit); + } + + @Override + public boolean hasDelegatedCode() { + return true; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/account/MutableDelegatedCodeAccount.java b/evm/src/main/java/org/hyperledger/besu/evm/account/MutableDelegatedCodeAccount.java new file mode 100644 index 00000000000..0e1e1145dd6 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/account/MutableDelegatedCodeAccount.java @@ -0,0 +1,161 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.account; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.Map; +import java.util.NavigableMap; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; + +/** Wraps an EOA account and includes delegated code to be run on behalf of it. */ +public class MutableDelegatedCodeAccount extends BaseDelegatedCodeAccount + implements MutableAccount { + + private final MutableAccount wrappedAccount; + + /** + * Creates a new MutableAuthorizedCodeAccount. + * + * @param worldUpdater the world updater. + * @param wrappedAccount the account that has delegated code to be loaded into it. + * @param codeDelegationAddress the address of the delegated code. + */ + public MutableDelegatedCodeAccount( + final WorldUpdater worldUpdater, + final MutableAccount wrappedAccount, + final Address codeDelegationAddress) { + super(worldUpdater, codeDelegationAddress); + this.wrappedAccount = wrappedAccount; + } + + @Override + public Address getAddress() { + return wrappedAccount.getAddress(); + } + + @Override + public boolean isStorageEmpty() { + return wrappedAccount.isStorageEmpty(); + } + + @Override + public Optional
    delegatedCodeAddress() { + return super.delegatedCodeAddress(); + } + + @Override + public Hash getAddressHash() { + return wrappedAccount.getAddressHash(); + } + + @Override + public long getNonce() { + return wrappedAccount.getNonce(); + } + + @Override + public Wei getBalance() { + return wrappedAccount.getBalance(); + } + + @Override + public Bytes getCode() { + return super.getCode(); + } + + @Override + public Bytes getUnprocessedCode() { + return wrappedAccount.getCode(); + } + + @Override + public Hash getCodeHash() { + return super.getCodeHash(); + } + + @Override + public UInt256 getStorageValue(final UInt256 key) { + return wrappedAccount.getStorageValue(key); + } + + @Override + public UInt256 getOriginalStorageValue(final UInt256 key) { + return wrappedAccount.getOriginalStorageValue(key); + } + + @Override + public boolean isEmpty() { + return getDelegatedNonce() == 0 && getDelegatedBalance().isZero() && !hasCode(); + } + + @Override + public boolean hasCode() { + return !getCode().isEmpty(); + } + + @Override + public NavigableMap storageEntriesFrom( + final Bytes32 startKeyHash, final int limit) { + return wrappedAccount.storageEntriesFrom(startKeyHash, limit); + } + + @Override + public void setNonce(final long value) { + wrappedAccount.setNonce(value); + } + + @Override + public void setBalance(final Wei value) { + wrappedAccount.setBalance(value); + } + + @Override + public void setCode(final Bytes code) { + wrappedAccount.setCode(code); + } + + @Override + public void setStorageValue(final UInt256 key, final UInt256 value) { + wrappedAccount.setStorageValue(key, value); + } + + @Override + public void clearStorage() { + wrappedAccount.clearStorage(); + } + + @Override + public Map getUpdatedStorage() { + return wrappedAccount.getUpdatedStorage(); + } + + @Override + public void becomeImmutable() { + wrappedAccount.becomeImmutable(); + } + + @Override + public boolean hasDelegatedCode() { + return true; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeFactory.java b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeFactory.java index 23b074e4334..18561001205 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeFactory.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeFactory.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,80 +11,110 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.evm.code; import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.code.EOFLayout.EOFContainerMode; + +import javax.annotation.Nonnull; import org.apache.tuweni.bytes.Bytes; /** The Code factory. */ -public final class CodeFactory { +public class CodeFactory { /** The constant EOF_LEAD_BYTE. */ public static final byte EOF_LEAD_BYTE = -17; // 0xEF in signed byte form - private CodeFactory() { - // factory class, no instantiations. + /** Maximum EOF version that can be produced. Legacy is considered EOF version zero. */ + protected final int maxEofVersion; + + /** Maximum size of the code stream that can be produced, including all header bytes. */ + protected final int maxContainerSize; + + /** The EOF validator against which EOF layouts will be validated. */ + EOFValidator eofValidator; + + /** + * Create a code factory. + * + * @param maxEofVersion Maximum EOF version that can be set + * @param maxContainerSize Maximum size of a container that will be parsed. + */ + public CodeFactory(final int maxEofVersion, final int maxContainerSize) { + this.maxEofVersion = maxEofVersion; + this.maxContainerSize = maxContainerSize; + + eofValidator = new CodeV1Validation(maxContainerSize); } /** * Create Code. * * @param bytes the bytes - * @param maxEofVersion the max eof version - * @param inCreateOperation the in create operation * @return the code */ - public static Code createCode( - final Bytes bytes, final int maxEofVersion, final boolean inCreateOperation) { - if (maxEofVersion == 0) { - return new CodeV0(bytes); - } else if (maxEofVersion == 1) { - int codeSize = bytes.size(); - if (codeSize > 0 && bytes.get(0) == EOF_LEAD_BYTE) { - if (codeSize == 1 && !inCreateOperation) { - return new CodeV0(bytes); - } - if (codeSize < 3) { - return new CodeInvalid(bytes, "EOF Container too short"); - } - if (bytes.get(1) != 0) { - if (inCreateOperation) { - // because some 0xef code made it to mainnet, this is only an error at contract create - return new CodeInvalid(bytes, "Incorrect second byte"); - } else { - return new CodeV0(bytes); - } - } - int version = bytes.get(2); - if (version != 1) { - return new CodeInvalid(bytes, "Unsupported EOF Version: " + version); - } - - final EOFLayout layout = EOFLayout.parseEOF(bytes); - if (!layout.isValid()) { - return new CodeInvalid(bytes, "Invalid EOF Layout: " + layout.getInvalidReason()); - } + public Code createCode(final Bytes bytes) { + return createCode(bytes, false); + } - final String codeValidationError = CodeV1Validation.validateCode(layout); - if (codeValidationError != null) { - return new CodeInvalid(bytes, "EOF Code Invalid : " + codeValidationError); - } + /** + * Create Code. + * + * @param bytes the bytes + * @param createTransaction This is in a create transaction, allow dangling data + * @return the code + */ + public Code createCode(final Bytes bytes, final boolean createTransaction) { + return switch (maxEofVersion) { + case 0 -> new CodeV0(bytes); + case 1 -> createV1Code(bytes, createTransaction); + default -> new CodeInvalid(bytes, "Unsupported max code version " + maxEofVersion); + }; + } - final String stackValidationError = CodeV1Validation.validateStack(layout); - if (stackValidationError != null) { - return new CodeInvalid(bytes, "EOF Code Invalid : " + stackValidationError); + private @Nonnull Code createV1Code(final Bytes bytes, final boolean createTransaction) { + int codeSize = bytes.size(); + if (codeSize > 0 && bytes.get(0) == EOF_LEAD_BYTE) { + if (codeSize < 3) { + return new CodeInvalid(bytes, "EOF Container too short"); + } + if (bytes.get(1) != 0) { + if (createTransaction) { + // because some 0xef code made it to mainnet, this is only an error at contract creation + // time + return new CodeInvalid(bytes, "Incorrect second byte"); + } else { + return new CodeV0(bytes); } + } + int version = bytes.get(2); + if (version != 1) { + return new CodeInvalid(bytes, "Unsupported EOF Version: " + version); + } - return new CodeV1(layout); - } else { - return new CodeV0(bytes); + final EOFLayout layout = EOFLayout.parseEOF(bytes, !createTransaction); + if (createTransaction) { + layout.containerMode().set(EOFContainerMode.INITCODE); } + return createCode(layout); } else { - return new CodeInvalid(bytes, "Unsupported max code version " + maxEofVersion); + return new CodeV0(bytes); } } + + @Nonnull + Code createCode(final EOFLayout layout) { + if (!layout.isValid()) { + return new CodeInvalid(layout.container(), "Invalid EOF Layout: " + layout.invalidReason()); + } + + final String validationError = eofValidator.validate(layout); + if (validationError != null) { + return new CodeInvalid(layout.container(), "EOF Code Invalid : " + validationError); + } + + return new CodeV1(layout); + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeInvalid.java b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeInvalid.java index c28adb1b4a4..c7efd230f38 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeInvalid.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeInvalid.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,21 +11,22 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.evm.code; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.internal.Words; +import java.util.Optional; import java.util.function.Supplier; import com.google.common.base.Suppliers; import org.apache.tuweni.bytes.Bytes; /** - * For code versions where code can be deemed "invalid" this represents a cachable instance of + * For code versions where code can be deemed "invalid" this represents a cacheable instance of * invalid code. Note that EXTCODE operations can still access invalid code. */ public class CodeInvalid implements Code { @@ -61,6 +62,16 @@ public int getSize() { return codeBytes.size(); } + @Override + public int getDataSize() { + return 0; + } + + @Override + public int getDeclaredDataSize() { + return 0; + } + @Override public Bytes getBytes() { return codeBytes; @@ -93,6 +104,41 @@ public int getCodeSectionCount() { @Override public int getEofVersion() { - return -1; + return Integer.MAX_VALUE; + } + + @Override + public int getSubcontainerCount() { + return 0; + } + + @Override + public Optional getSubContainer(final int index, final Bytes auxData, final EVM evm) { + return Optional.empty(); + } + + @Override + public Bytes getData(final int offset, final int length) { + return Bytes.EMPTY; + } + + @Override + public int readBigEndianI16(final int index) { + return Words.readBigEndianI16(index, codeBytes.toArrayUnsafe()); + } + + @Override + public int readBigEndianU16(final int index) { + return Words.readBigEndianU16(index, codeBytes.toArrayUnsafe()); + } + + @Override + public int readU8(final int index) { + return codeBytes.toArrayUnsafe()[index] & 0xff; + } + + @Override + public String prettyPrint() { + return codeBytes.toHexString(); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeSection.java b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeSection.java index 6eacd9447fd..7a9982bf2d2 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeSection.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeSection.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.evm.code; import java.util.Objects; @@ -25,15 +23,22 @@ public final class CodeSection { /** The length. */ final int length; + /** The Inputs. */ final int inputs; + /** The Outputs. */ final int outputs; + /** The Max stack height. */ final int maxStackHeight; + /** The byte offset from the beginning of the container that the section starts at */ final int entryPoint; + /** Is this a returing code section (i.e. contains RETF or JUMPF into a returning section)? */ + final boolean returning; + /** * Instantiates a new Code section. * @@ -51,7 +56,13 @@ public CodeSection( final int entryPoint) { this.length = length; this.inputs = inputs; - this.outputs = outputs; + if (outputs == 0x80) { + this.outputs = 0; + returning = false; + } else { + this.outputs = outputs; + returning = true; + } this.maxStackHeight = maxStackHeight; this.entryPoint = entryPoint; } @@ -83,6 +94,15 @@ public int getOutputs() { return outputs; } + /** + * Does this code seciton have a RETF return anywhere? + * + * @return returning + */ + public boolean isReturning() { + return returning; + } + /** * Gets max stack height. * diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV0.java b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV0.java index c2263b42ac6..45b9cb82eea 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV0.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV0.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,15 +11,16 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.evm.code; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.internal.Words; import org.hyperledger.besu.evm.operation.JumpDestOperation; +import java.util.Optional; import java.util.function.Supplier; import com.google.common.base.MoreObjects; @@ -59,15 +60,14 @@ public class CodeV0 implements Code { * Returns true if the object is equal to this; otherwise false. * * @param other The object to compare this with. - * @return True if the object is equal to this; otherwise false. + * @return True if the object is equal to this, otherwise false. */ @Override public boolean equals(final Object other) { if (other == null) return false; if (other == this) return true; - if (!(other instanceof CodeV0)) return false; + if (!(other instanceof CodeV0 that)) return false; - final CodeV0 that = (CodeV0) other; return this.bytes.equals(that.bytes); } @@ -86,6 +86,16 @@ public int getSize() { return bytes.size(); } + @Override + public int getDataSize() { + return 0; + } + + @Override + public int getDeclaredDataSize() { + return 0; + } + @Override public Bytes getBytes() { return bytes; @@ -139,6 +149,21 @@ public int getEofVersion() { return 0; } + @Override + public int getSubcontainerCount() { + return 0; + } + + @Override + public Optional getSubContainer(final int index, final Bytes auxData, final EVM evm) { + return Optional.empty(); + } + + @Override + public Bytes getData(final int offset, final int length) { + return Bytes.EMPTY; + } + /** * Calculate jump destination. * @@ -297,4 +322,24 @@ long[] calculateJumpDests() { } return bitmap; } + + @Override + public int readBigEndianI16(final int index) { + return Words.readBigEndianI16(index, bytes.toArrayUnsafe()); + } + + @Override + public int readBigEndianU16(final int index) { + return Words.readBigEndianU16(index, bytes.toArrayUnsafe()); + } + + @Override + public int readU8(final int index) { + return bytes.toArrayUnsafe()[index] & 0xff; + } + + @Override + public String prettyPrint() { + return bytes.toHexString(); + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1.java b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1.java index f45cab025e3..a1517d1d7f4 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,21 +11,25 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.evm.code; import static com.google.common.base.Preconditions.checkArgument; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.internal.Words; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.Objects; +import java.util.Optional; import java.util.function.Supplier; import com.google.common.base.Suppliers; import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.MutableBytes; /** The CodeV1. */ public class CodeV1 implements Code { @@ -36,16 +40,16 @@ public class CodeV1 implements Code { /** * Instantiates a new CodeV1. * - * @param layout the layout + * @param eofLayout the layout */ - CodeV1(final EOFLayout layout) { - this.eofLayout = layout; - this.codeHash = Suppliers.memoize(() -> Hash.hash(eofLayout.getContainer())); + CodeV1(final EOFLayout eofLayout) { + this.eofLayout = eofLayout; + this.codeHash = Suppliers.memoize(() -> Hash.hash(eofLayout.container())); } @Override public int getSize() { - return eofLayout.getContainer().size(); + return eofLayout.container().size(); } @Override @@ -62,7 +66,7 @@ public int getCodeSectionCount() { @Override public Bytes getBytes() { - return eofLayout.getContainer(); + return eofLayout.container(); } @Override @@ -82,7 +86,36 @@ public boolean isValid() { @Override public int getEofVersion() { - return eofLayout.getVersion(); + return eofLayout.version(); + } + + @Override + public int getSubcontainerCount() { + return eofLayout.getSubcontainerCount(); + } + + @Override + public Optional getSubContainer(final int index, final Bytes auxData, final EVM evm) { + EOFLayout subcontainerLayout = eofLayout.getSubcontainer(index); + Bytes codeToLoad; + if (auxData != null && !auxData.isEmpty()) { + codeToLoad = subcontainerLayout.writeContainer(auxData); + if (codeToLoad == null) { + return Optional.empty(); + } + } else { + // if no auxdata is added, we must validate data is not truncated separately + if (subcontainerLayout.dataLength() != subcontainerLayout.data().size()) { + return Optional.empty(); + } + codeToLoad = subcontainerLayout.container(); + } + + Code subContainerCode = evm.getCodeUncached(codeToLoad); + + return subContainerCode.isValid() && subContainerCode.getEofVersion() > 0 + ? Optional.of(subContainerCode) + : Optional.empty(); } @Override @@ -97,4 +130,61 @@ public boolean equals(final Object o) { public int hashCode() { return Objects.hash(codeHash, eofLayout); } + + @Override + public Bytes getData(final int offset, final int length) { + Bytes data = eofLayout.data(); + int dataLen = data.size(); + if (offset > dataLen) { + return Bytes.EMPTY; + } else if ((offset + length) > dataLen) { + byte[] result = new byte[length]; + MutableBytes mbytes = MutableBytes.wrap(result); + data.slice(offset).copyTo(mbytes, 0); + return Bytes.wrap(result); + } else { + return data.slice(offset, length); + } + } + + @Override + public int getDataSize() { + return eofLayout.data().size(); + } + + @Override + public int getDeclaredDataSize() { + return eofLayout.dataLength(); + } + + @Override + public int readBigEndianI16(final int index) { + return Words.readBigEndianI16(index, eofLayout.container().toArrayUnsafe()); + } + + @Override + public int readBigEndianU16(final int index) { + return Words.readBigEndianU16(index, eofLayout.container().toArrayUnsafe()); + } + + @Override + public int readU8(final int index) { + return eofLayout.container().toArrayUnsafe()[index] & 0xff; + } + + @Override + public String prettyPrint() { + StringWriter sw = new StringWriter(); + eofLayout.prettyPrint(new PrintWriter(sw, true), "", ""); + return sw.toString(); + } + + /** + * The EOFLayout object for the code + * + * @return the EOFLayout object for the parsed code + */ + public EOFLayout getEofLayout() { + return eofLayout; + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1Validation.java b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1Validation.java index 6174a2734af..0192c6f5787 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1Validation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/CodeV1Validation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,558 +11,108 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.evm.code; +import static java.lang.Math.max; +import static java.lang.Math.min; +import static java.lang.String.format; +import static org.hyperledger.besu.evm.code.EOFLayout.EOFContainerMode.INITCODE; +import static org.hyperledger.besu.evm.code.EOFLayout.EOFContainerMode.RUNTIME; +import static org.hyperledger.besu.evm.code.OpcodeInfo.V1_OPCODES; import static org.hyperledger.besu.evm.internal.Words.readBigEndianI16; import static org.hyperledger.besu.evm.internal.Words.readBigEndianU16; +import org.hyperledger.besu.evm.code.EOFLayout.EOFContainerMode; import org.hyperledger.besu.evm.operation.CallFOperation; +import org.hyperledger.besu.evm.operation.DataLoadNOperation; +import org.hyperledger.besu.evm.operation.DupNOperation; +import org.hyperledger.besu.evm.operation.EOFCreateOperation; +import org.hyperledger.besu.evm.operation.ExchangeOperation; +import org.hyperledger.besu.evm.operation.InvalidOperation; +import org.hyperledger.besu.evm.operation.JumpFOperation; import org.hyperledger.besu.evm.operation.PushOperation; import org.hyperledger.besu.evm.operation.RelativeJumpIfOperation; import org.hyperledger.besu.evm.operation.RelativeJumpOperation; import org.hyperledger.besu.evm.operation.RelativeJumpVectorOperation; import org.hyperledger.besu.evm.operation.RetFOperation; +import org.hyperledger.besu.evm.operation.ReturnContractOperation; +import org.hyperledger.besu.evm.operation.ReturnOperation; +import org.hyperledger.besu.evm.operation.RevertOperation; +import org.hyperledger.besu.evm.operation.StopOperation; +import org.hyperledger.besu.evm.operation.SwapNOperation; +import java.util.ArrayDeque; import java.util.Arrays; import java.util.BitSet; +import java.util.List; +import java.util.Queue; +import javax.annotation.Nullable; import org.apache.tuweni.bytes.Bytes; /** Code V1 Validation */ -public final class CodeV1Validation { +public class CodeV1Validation implements EOFValidator { + + static final int MAX_STACK_HEIGHT = 1024; - private CodeV1Validation() { - // to prevent instantiation + /** Maximum size of the code stream that can be produced, including all header bytes. */ + protected final int maxContainerSize; + + /** + * Create a new container, with a configurable maximim container size. + * + * @param maxContainerSize the maximum size of any container. + */ + public CodeV1Validation(final int maxContainerSize) { + this.maxContainerSize = maxContainerSize; } - static final byte INVALID = 0x01; - static final byte VALID = 0x02; - static final byte TERMINAL = 0x04; - static final byte VALID_AND_TERMINAL = VALID | TERMINAL; - static final byte[] OPCODE_ATTRIBUTES = { - VALID_AND_TERMINAL, // 0x00 STOP - VALID, // 0x01 - ADD - VALID, // 0x02 - MUL - VALID, // 0x03 - SUB - VALID, // 0x04 - DIV - VALID, // 0x05 - SDIV - VALID, // 0x06 - MOD - VALID, // 0x07 - SMOD - VALID, // 0x08 - ADDMOD - VALID, // 0x09 - MULMOD - VALID, // 0x0a - EXP - VALID, // 0x0b - SIGNEXTEND - INVALID, // 0x0c - INVALID, // 0x0d - INVALID, // 0x0e - INVALID, // 0x0f - VALID, // 0x10 - LT - VALID, // 0x11 - GT - VALID, // 0x12 - SLT - VALID, // 0x13 - SGT - VALID, // 0x14 - EQ - VALID, // 0x15 - ISZERO - VALID, // 0x16 - AND - VALID, // 0x17 - OR - VALID, // 0x18 - XOR - VALID, // 0x19 - NOT - VALID, // 0x1a - BYTE - VALID, // 0x1b - SHL - VALID, // 0x1c - SHR - VALID, // 0x1d - SAR - INVALID, // 0x1e - INVALID, // 0x1f - VALID, // 0x20 - SHA3 - INVALID, // 0x21 - INVALID, // 0x22 - INVALID, // 0x23 - INVALID, // 0x24 - INVALID, // 0x25 - INVALID, // 0x26 - INVALID, // 0x27 - INVALID, // 0x28 - INVALID, // 0x29 - INVALID, // 0x2a - INVALID, // 0x2b - INVALID, // 0x2c - INVALID, // 0x2d - INVALID, // 0x2e - INVALID, // 0x2f - VALID, // 0x30 - ADDRESS - VALID, // 0x31 - BALANCE - VALID, // 0x32 - ORIGIN - VALID, // 0x33 - CALLER - VALID, // 0x34 - CALLVALUE - VALID, // 0x35 - CALLDATALOAD - VALID, // 0x36 - CALLDATASIZE - VALID, // 0x37 - CALLDATACOPY - VALID, // 0x38 - CODESIZE - VALID, // 0x39 - CODECOPY - VALID, // 0x3a - GASPRICE - VALID, // 0x3b - EXTCODESIZE - VALID, // 0x3c - EXTCODECOPY - VALID, // 0x3d - RETURNDATASIZE - VALID, // 0x3e - RETURNDATACOPY - VALID, // 0x3f - EXTCODEHASH - VALID, // 0x40 - BLOCKHASH - VALID, // 0x41 - COINBASE - VALID, // 0x42 - TIMESTAMP - VALID, // 0x43 - NUMBER - VALID, // 0x44 - PREVRANDAO (née DIFFICULTY) - VALID, // 0x45 - GASLIMIT - VALID, // 0x46 - CHAINID - VALID, // 0x47 - SELFBALANCE - VALID, // 0x48 - BASEFEE - INVALID, // 0x49 - INVALID, // 0x4a - INVALID, // 0x4b - INVALID, // 0x4c - INVALID, // 0x4d - INVALID, // 0x4e - INVALID, // 0x4f - VALID, // 0x50 - POP - VALID, // 0x51 - MLOAD - VALID, // 0x52 - MSTORE - VALID, // 0x53 - MSTORE8 - VALID, // 0x54 - SLOAD - VALID, // 0x55 - SSTORE - INVALID, // 0x56 - JUMP - INVALID, // 0x57 - JUMPI - INVALID, // 0x58 - PC - VALID, // 0x59 - MSIZE - VALID, // 0x5a - GAS - VALID, // 0x5b - NOOOP (née JUMPDEST) - VALID, // 0X5c - TLOAD - VALID, // 0X5d - TSTORE - VALID, // 0X5e - MCOPY - VALID, // 0X5f - PUSH0 - VALID, // 0x60 - PUSH1 - VALID, // 0x61 - PUSH2 - VALID, // 0x62 - PUSH3 - VALID, // 0x63 - PUSH4 - VALID, // 0x64 - PUSH5 - VALID, // 0x65 - PUSH6 - VALID, // 0x66 - PUSH7 - VALID, // 0x67 - PUSH8 - VALID, // 0x68 - PUSH9 - VALID, // 0x69 - PUSH10 - VALID, // 0x6a - PUSH11 - VALID, // 0x6b - PUSH12 - VALID, // 0x6c - PUSH13 - VALID, // 0x6d - PUSH14 - VALID, // 0x6e - PUSH15 - VALID, // 0x6f - PUSH16 - VALID, // 0x70 - PUSH17 - VALID, // 0x71 - PUSH18 - VALID, // 0x72 - PUSH19 - VALID, // 0x73 - PUSH20 - VALID, // 0x74 - PUSH21 - VALID, // 0x75 - PUSH22 - VALID, // 0x76 - PUSH23 - VALID, // 0x77 - PUSH24 - VALID, // 0x78 - PUSH25 - VALID, // 0x79 - PUSH26 - VALID, // 0x7a - PUSH27 - VALID, // 0x7b - PUSH28 - VALID, // 0x7c - PUSH29 - VALID, // 0x7d - PUSH30 - VALID, // 0x7e - PUSH31 - VALID, // 0x7f - PUSH32 - VALID, // 0x80 - DUP1 - VALID, // 0x81 - DUP2 - VALID, // 0x82 - DUP3 - VALID, // 0x83 - DUP4 - VALID, // 0x84 - DUP5 - VALID, // 0x85 - DUP6 - VALID, // 0x86 - DUP7 - VALID, // 0x87 - DUP8 - VALID, // 0x88 - DUP9 - VALID, // 0x89 - DUP10 - VALID, // 0x8a - DUP11 - VALID, // 0x8b - DUP12 - VALID, // 0x8c - DUP13 - VALID, // 0x8d - DUP14 - VALID, // 0x8e - DUP15 - VALID, // 0x8f - DUP16 - VALID, // 0x90 - SWAP1 - VALID, // 0x91 - SWAP2 - VALID, // 0x92 - SWAP3 - VALID, // 0x93 - SWAP4 - VALID, // 0x94 - SWAP5 - VALID, // 0x95 - SWAP6 - VALID, // 0x96 - SWAP7 - VALID, // 0x97 - SWAP8 - VALID, // 0x98 - SWAP9 - VALID, // 0x99 - SWAP10 - VALID, // 0x9a - SWAP11 - VALID, // 0x9b - SWAP12 - VALID, // 0x9c - SWAP13 - VALID, // 0x9d - SWAP14 - VALID, // 0x9e - SWAP15 - VALID, // 0x9f - SWAP16 - VALID, // 0xa0 - LOG0 - VALID, // 0xa1 - LOG1 - VALID, // 0xa2 - LOG2 - VALID, // 0xa3 - LOG3 - VALID, // 0xa4 - LOG4 - INVALID, // 0xa5 - INVALID, // 0xa6 - INVALID, // 0xa7 - INVALID, // 0xa8 - INVALID, // 0xa9 - INVALID, // 0xaa - INVALID, // 0xab - INVALID, // 0xac - INVALID, // 0xad - INVALID, // 0xae - INVALID, // 0xaf - INVALID, // 0xb0 - INVALID, // 0xb1 - INVALID, // 0xb2 - INVALID, // 0xb3 - INVALID, // 0xb4 - INVALID, // 0xb5 - INVALID, // 0xb6 - INVALID, // 0xb7 - INVALID, // 0xb8 - INVALID, // 0xb9 - INVALID, // 0xba - INVALID, // 0xbb - INVALID, // 0xbc - INVALID, // 0xbd - INVALID, // 0xbe - INVALID, // 0xbf - INVALID, // 0xc0 - INVALID, // 0xc1 - INVALID, // 0xc2 - INVALID, // 0xc3 - INVALID, // 0xc4 - INVALID, // 0xc5 - INVALID, // 0xc6 - INVALID, // 0xc7 - INVALID, // 0xc8 - INVALID, // 0xc9 - INVALID, // 0xca - INVALID, // 0xcb - INVALID, // 0xcc - INVALID, // 0xcd - INVALID, // 0xce - INVALID, // 0xcf - INVALID, // 0xd0 - INVALID, // 0xd1 - INVALID, // 0xd2 - INVALID, // 0xd3 - INVALID, // 0xd4 - INVALID, // 0xd5 - INVALID, // 0xd6 - INVALID, // 0xd7 - INVALID, // 0xd8 - INVALID, // 0xd9 - INVALID, // 0xda - INVALID, // 0xdb - INVALID, // 0xdc - INVALID, // 0xdd - INVALID, // 0xde - INVALID, // 0xef - VALID_AND_TERMINAL, // 0xe0 - RJUMP - VALID, // 0xe1 - RJUMPI - VALID, // 0xe2 - RJUMPV - VALID, // 0xe3 - CALLF - VALID_AND_TERMINAL, // 0xe4 - RETF - INVALID, // 0xe5 - INVALID, // 0xe6 - INVALID, // 0xe7 - INVALID, // 0xe8 - INVALID, // 0xe9 - INVALID, // 0xea - INVALID, // 0xeb - INVALID, // 0xec - INVALID, // 0xed - INVALID, // 0xee - INVALID, // 0xef - VALID, // 0xf0 - CREATE - VALID, // 0xf1 - CALL - INVALID, // 0xf2 - CALLCODE - VALID_AND_TERMINAL, // 0xf3 - RETURN - VALID, // 0xf4 - DELEGATECALL - VALID, // 0xf5 - CREATE2 - INVALID, // 0xf6 - INVALID, // 0xf7 - INVALID, // 0xf8 - INVALID, // 0xf9 - VALID, // 0xfa - STATICCALL - INVALID, // 0xfb - INVALID, // 0xfc - VALID_AND_TERMINAL, // 0xfd - REVERT - VALID_AND_TERMINAL, // 0xfe - INVALID - INVALID, // 0xff - SELFDESTRUCT - }; - static final int MAX_STACK_HEIGHT = 1024; - // java17 move to record - // [0] - stack input consumed - // [1] - stack outputs added - // [2] - PC advance - static final byte[][] OPCODE_STACK_VALIDATION = { - {0, 0, -1}, // 0x00 - STOP - {2, 1, 1}, // 0x01 - ADD - {2, 1, 1}, // 0x02 - MUL - {2, 1, 1}, // 0x03 - SUB - {2, 1, 1}, // 0x04 - DIV - {2, 1, 1}, // 0x05 - SDIV - {2, 1, 1}, // 0x06 - MOD - {2, 1, 1}, // 0x07 - SMOD - {3, 1, 1}, // 0x08 - ADDMOD - {3, 1, 1}, // 0x09 - MULMOD - {2, 1, 1}, // 0x0a - EXP - {2, 1, 1}, // 0x0b - SIGNEXTEND - {0, 0, 0}, // 0x0c - {0, 0, 0}, // 0x0d - {0, 0, 0}, // 0x0e - {0, 0, 0}, // 0x0f - {2, 1, 1}, // 0x10 - LT - {2, 1, 1}, // 0x11 - GT - {2, 1, 1}, // 0x12 - SLT - {2, 1, 1}, // 0x13 - SGT - {2, 1, 1}, // 0x14 - EQ - {1, 1, 1}, // 0x15 - ISZERO - {2, 1, 1}, // 0x16 - AND - {2, 1, 1}, // 0x17 - OR - {2, 1, 1}, // 0x18 - XOR - {1, 1, 1}, // 0x19 - NOT - {2, 1, 1}, // 0x1a - BYTE - {2, 1, 1}, // 0x1b - SHL - {2, 1, 1}, // 0x1c - SHR - {2, 1, 1}, // 0x1d - SAR - {0, 0, 0}, // 0x1e - {0, 0, 0}, // 0x1f - {2, 1, 1}, // 0x20 - SHA3 - {0, 0, 0}, // 0x21 - {0, 0, 0}, // 0x22 - {0, 0, 0}, // 0x23 - {0, 0, 0}, // 0x24 - {0, 0, 0}, // 0x25 - {0, 0, 0}, // 0x26 - {0, 0, 0}, // 0x27 - {0, 0, 0}, // 0x28 - {0, 0, 0}, // 0x29 - {0, 0, 0}, // 0x2a - {0, 0, 0}, // 0x2b - {0, 0, 0}, // 0x2c - {0, 0, 0}, // 0x2d - {0, 0, 0}, // 0x2e - {0, 0, 0}, // 0x2f - {0, 1, 1}, // 0x30 - ADDRESS - {1, 1, 1}, // 0x31 - BALANCE - {0, 1, 1}, // 0x32 - ORIGIN - {0, 1, 1}, // 0x33 - CALLER - {0, 1, 1}, // 0x34 - CALLVALUE - {1, 1, 1}, // 0x35 - CALLDATALOAD - {0, 1, 1}, // 0x36 - CALLDATASIZE - {3, 0, 1}, // 0x37 - CALLDATACOPY - {0, 1, 1}, // 0x38 - CODESIZE - {3, 0, 1}, // 0x39 - CODECOPY - {0, 1, 1}, // 0x3a - GASPRICE - {1, 1, 1}, // 0x3b - EXTCODESIZE - {4, 0, 1}, // 0x3c - EXTCODECOPY - {0, 1, 1}, // 0x3d - RETURNDATASIZE - {3, 0, 1}, // 0x3e - RETURNDATACOPY - {1, 1, 1}, // 0x3f - EXTCODEHASH - {1, 1, 1}, // 0x40 - BLOCKHASH - {0, 1, 1}, // 0x41 - COINBASE - {0, 1, 1}, // 0x42 - TIMESTAMP - {0, 1, 1}, // 0x43 - NUMBER - {0, 1, 1}, // 0x44 - PREVRANDAO (née DIFFICULTY) - {0, 1, 1}, // 0x45 - GASLIMIT - {0, 1, 1}, // 0x46 - CHAINID - {0, 1, 1}, // 0x47 - SELFBALANCE - {0, 1, 1}, // 0x48 - BASEFEE - {0, 0, 0}, // 0x49 - {0, 0, 0}, // 0x4a - {0, 0, 0}, // 0x4b - {0, 0, 0}, // 0x4c - {0, 0, 0}, // 0x4d - {0, 0, 0}, // 0x4e - {0, 0, 0}, // 0x4f - {1, 0, 1}, // 0x50 - POP - {1, 1, 1}, // 0x51 - MLOAD - {2, 0, 1}, // 0x52 - MSTORE - {2, 0, 1}, // 0x53 - MSTORE8 - {1, 1, 1}, // 0x54 - SLOAD - {2, 0, 1}, // 0x55 - SSTORE - {0, 0, 0}, // 0x56 - JUMP - {0, 0, 0}, // 0x57 - JUMPI - {0, 0, 0}, // 0x58 - PC - {0, 1, 1}, // 0x59 - MSIZE - {0, 1, 1}, // 0x5a - GAS - {0, 0, 1}, // 0x5b - NOOP (née JUMPDEST) - {1, 1, 1}, // 0x5c - TLOAD - {2, 0, 1}, // 0x5d - TSTORE - {4, 0, 1}, // 0x5e - MCOPY - {0, 1, 1}, // 0x5f - PUSH0 - {0, 1, 2}, // 0x60 - PUSH1 - {0, 1, 3}, // 0x61 - PUSH2 - {0, 1, 4}, // 0x62 - PUSH3 - {0, 1, 5}, // 0x63 - PUSH4 - {0, 1, 6}, // 0x64 - PUSH5 - {0, 1, 7}, // 0x65 - PUSH6 - {0, 1, 8}, // 0x66 - PUSH7 - {0, 1, 9}, // 0x67 - PUSH8 - {0, 1, 10}, // 0x68 - PUSH9 - {0, 1, 11}, // 0x69 - PUSH10 - {0, 1, 12}, // 0x6a - PUSH11 - {0, 1, 13}, // 0x6b - PUSH12 - {0, 1, 14}, // 0x6c - PUSH13 - {0, 1, 15}, // 0x6d - PUSH14 - {0, 1, 16}, // 0x6e - PUSH15 - {0, 1, 17}, // 0x6f - PUSH16 - {0, 1, 18}, // 0x70 - PUSH17 - {0, 1, 19}, // 0x71 - PUSH18 - {0, 1, 20}, // 0x72 - PUSH19 - {0, 1, 21}, // 0x73 - PUSH20 - {0, 1, 22}, // 0x74 - PUSH21 - {0, 1, 23}, // 0x75 - PUSH22 - {0, 1, 24}, // 0x76 - PUSH23 - {0, 1, 25}, // 0x77 - PUSH24 - {0, 1, 26}, // 0x78 - PUSH25 - {0, 1, 27}, // 0x79 - PUSH26 - {0, 1, 28}, // 0x7a - PUSH27 - {0, 1, 29}, // 0x7b - PUSH28 - {0, 1, 30}, // 0x7c - PUSH29 - {0, 1, 31}, // 0x7d - PUSH30 - {0, 1, 32}, // 0x7e - PUSH31 - {0, 1, 33}, // 0x7f - PUSH32 - {1, 2, 1}, // 0x80 - DUP1 - {2, 3, 1}, // 0x81 - DUP2 - {3, 4, 1}, // 0x82 - DUP3 - {4, 5, 1}, // 0x83 - DUP4 - {5, 6, 1}, // 0x84 - DUP5 - {6, 7, 1}, // 0x85 - DUP6 - {7, 8, 1}, // 0x86 - DUP7 - {8, 9, 1}, // 0x87 - DUP8 - {9, 10, 1}, // 0x88 - DUP9 - {10, 11, 1}, // 0x89 - DUP10 - {11, 12, 1}, // 0x8a - DUP11 - {12, 13, 1}, // 0x8b - DUP12 - {13, 14, 1}, // 0x8c - DUP13 - {14, 15, 1}, // 0x8d - DUP14 - {15, 16, 1}, // 0x8e - DUP15 - {16, 17, 1}, // 0x8f - DUP16 - {2, 2, 1}, // 0x90 - SWAP1 - {3, 3, 1}, // 0x91 - SWAP2 - {4, 4, 1}, // 0x92 - SWAP3 - {5, 5, 1}, // 0x93 - SWAP4 - {6, 6, 1}, // 0x94 - SWAP5 - {7, 7, 1}, // 0x95 - SWAP6 - {8, 8, 1}, // 0x96 - SWAP7 - {9, 9, 1}, // 0x97 - SWAP8 - {10, 10, 1}, // 0x98 - SWAP9 - {11, 11, 1}, // 0x99 - SWAP10 - {12, 12, 1}, // 0x9a - SWAP11 - {13, 13, 1}, // 0x9b - SWAP12 - {14, 14, 1}, // 0x9c - SWAP13 - {15, 15, 1}, // 0x9d - SWAP14 - {16, 16, 1}, // 0x9e - SWAP15 - {17, 17, 1}, // 0x9f - SWAP16 - {2, 0, 1}, // 0xa0 - LOG0 - {3, 0, 1}, // 0xa1 - LOG1 - {4, 0, 1}, // 0xa2 - LOG2 - {5, 0, 1}, // 0xa3 - LOG3 - {6, 0, 1}, // 0xa4 - LOG4 - {0, 0, 0}, // 0xa5 - {0, 0, 0}, // 0xa6 - {0, 0, 0}, // 0xa7 - {0, 0, 0}, // 0xa8 - {0, 0, 0}, // 0xa9 - {0, 0, 0}, // 0xaa - {0, 0, 0}, // 0xab - {0, 0, 0}, // 0xac - {0, 0, 0}, // 0xad - {0, 0, 0}, // 0xae - {0, 0, 0}, // 0xaf - {0, 0, 0}, // 0xb0 - {0, 0, 0}, // 0xb1 - {0, 0, 0}, // 0xb2 - {0, 0, 0}, // 0xb3 - {0, 0, 0}, // 0xb4 - {0, 0, 0}, // 0xb5 - {0, 0, 0}, // 0xb6 - {0, 0, 0}, // 0xb7 - {0, 0, 0}, // 0xb8 - {0, 0, 0}, // 0xb9 - {0, 0, 0}, // 0xba - {0, 0, 0}, // 0xbb - {0, 0, 0}, // 0xbc - {0, 0, 0}, // 0xbd - {0, 0, 0}, // 0xbe - {0, 0, 0}, // 0xbf - {0, 0, 0}, // 0xc0 - {0, 0, 0}, // 0xc1 - {0, 0, 0}, // 0xc2 - {0, 0, 0}, // 0xc3 - {0, 0, 0}, // 0xc4 - {0, 0, 0}, // 0xc5 - {0, 0, 0}, // 0xc6 - {0, 0, 0}, // 0xc7 - {0, 0, 0}, // 0xc8 - {0, 0, 0}, // 0xc9 - {0, 0, 0}, // 0xca - {0, 0, 0}, // 0xcb - {0, 0, 0}, // 0xcc - {0, 0, 0}, // 0xcd - {0, 0, 0}, // 0xce - {0, 0, 0}, // 0xcf - {0, 0, 0}, // 0xd0 - {0, 0, 0}, // 0xd1 - {0, 0, 0}, // 0xd2 - {0, 0, 0}, // 0xd3 - {0, 0, 0}, // 0xd4 - {0, 0, 0}, // 0xd5 - {0, 0, 0}, // 0xd6 - {0, 0, 0}, // 0xd7 - {0, 0, 0}, // 0xd8 - {0, 0, 0}, // 0xd9 - {0, 0, 0}, // 0xda - {0, 0, 0}, // 0xdb - {0, 0, 0}, // 0xdc - {0, 0, 0}, // 0xdd - {0, 0, 0}, // 0xde - {0, 0, 0}, // 0xef - {0, 0, -3}, // 0xe0 - RJUMP - {1, 0, 3}, // 0xe1 - RJUMPI - {1, 0, 2}, // 0xe2 - RJUMPV - {0, 0, 3}, // 0xe3 - CALLF - {0, 0, -1}, // 0xe4 - RETF - {0, 0, 0}, // 0xe5 - JUMPF - {0, 0, 0}, // 0xe6 - {0, 0, 0}, // 0xe7 - {0, 0, 0}, // 0xe8 - {0, 0, 0}, // 0xe9 - {0, 0, 0}, // 0xea - {0, 0, 0}, // 0xeb - {0, 0, 0}, // 0xec - {0, 0, 0}, // 0xed - {0, 0, 0}, // 0xee - {0, 0, 0}, // 0xef - {3, 1, 1}, // 0xf0 - CREATE - {7, 1, 1}, // 0xf1 - CALL - {0, 0, 0}, // 0xf2 - CALLCODE - {2, 0, -1}, // 0xf3 - RETURN - {6, 1, 1}, // 0xf4 - DELEGATECALL - {4, 1, 1}, // 0xf5 - CREATE2 - {0, 0, 0}, // 0xf6 - {0, 0, 0}, // 0xf7 - {0, 0, 0}, // 0xf8 - {0, 0, 0}, // 0xf9 - {6, 1, 1}, // 0xfa - STATICCALL - {0, 0, 0}, // 0xfb - {0, 0, 0}, // 0xfc - {2, 0, -1}, // 0xfd - REVERT - {0, 0, -1}, // 0xfe - INVALID - {0, 0, 0}, // 0xff - SELFDESTRUCT - }; + /** + * Validates the code and stack for the EOF Layout, with optional deep consideration of the + * containers. + * + * @param layout The parsed EOFLayout of the code + * @return either null, indicating no error, or a String describing the validation error. + */ + @SuppressWarnings( + "ReferenceEquality") // comparison `container != layout` is deliberate and correct + @Override + public String validate(final EOFLayout layout) { + if (layout.container().size() > maxContainerSize) { + return "container_size_above_limit of " + maxContainerSize; + } + + Queue workList = new ArrayDeque<>(layout.getSubcontainerCount()); + workList.add(layout); + + while (!workList.isEmpty()) { + EOFLayout container = workList.poll(); + workList.addAll(List.of(container.subContainers())); + if (container != layout && container.containerMode().get() == null) { + return "orphan_subcontainer #" + layout.indexOfSubcontainer(container); + } + if (container.containerMode().get() != RUNTIME + && container.data().size() != container.dataLength()) { + return "Incomplete data section " + + (container == layout + ? " at root" + : " in container #" + layout.indexOfSubcontainer(container)); + } + + final String codeValidationError = validateCode(container); + if (codeValidationError != null) { + return codeValidationError; + } + + final String stackValidationError = validateStack(container); + if (stackValidationError != null) { + return stackValidationError; + } + } + + return null; + } /** * Validate Code @@ -570,13 +120,15 @@ private CodeV1Validation() { * @param eofLayout The EOF Layout * @return validation code, null otherwise. */ - public static String validateCode(final EOFLayout eofLayout) { - int sectionCount = eofLayout.getCodeSectionCount(); - for (int i = 0; i < sectionCount; i++) { - CodeSection cs = eofLayout.getCodeSection(i); + @Override + public String validateCode(final EOFLayout eofLayout) { + if (!eofLayout.isValid()) { + return "Invalid EOF container - " + eofLayout.invalidReason(); + } + for (CodeSection cs : eofLayout.codeSections()) { var validation = - CodeV1Validation.validateCode( - eofLayout.getContainer().slice(cs.getEntryPoint(), cs.getLength()), sectionCount); + validateCode( + eofLayout.container().slice(cs.getEntryPoint(), cs.getLength()), cs, eofLayout); if (validation != null) { return validation; } @@ -590,85 +142,255 @@ public static String validateCode(final EOFLayout eofLayout) { * @param code the code section code * @return null if valid, otherwise a string containing an error reason. */ - static String validateCode(final Bytes code, final int sectionCount) { + String validateCode( + final Bytes code, final CodeSection thisCodeSection, final EOFLayout eofLayout) { final int size = code.size(); final BitSet rjumpdests = new BitSet(size); final BitSet immediates = new BitSet(size); final byte[] rawCode = code.toArrayUnsafe(); - int attribute = INVALID; + OpcodeInfo opcodeInfo = V1_OPCODES[0xfe]; int pos = 0; + EOFContainerMode eofContainerMode = eofLayout.containerMode().get(); + boolean hasReturningOpcode = false; while (pos < size) { final int operationNum = rawCode[pos] & 0xff; - attribute = OPCODE_ATTRIBUTES[operationNum]; - if ((attribute & INVALID) == INVALID) { + opcodeInfo = V1_OPCODES[operationNum]; + if (!opcodeInfo.valid()) { // undefined instruction - return String.format("Invalid Instruction 0x%02x", operationNum); + return format("undefined_instruction 0x%02x", operationNum); } pos += 1; int pcPostInstruction = pos; - if (operationNum > PushOperation.PUSH_BASE && operationNum <= PushOperation.PUSH_MAX) { - final int multiByteDataLen = operationNum - PushOperation.PUSH_BASE; - pcPostInstruction += multiByteDataLen; - } else if (operationNum == RelativeJumpOperation.OPCODE - || operationNum == RelativeJumpIfOperation.OPCODE) { - if (pos + 2 > size) { - return "Truncated relative jump offset"; - } - pcPostInstruction += 2; - final int offset = readBigEndianI16(pos, rawCode); - final int rjumpdest = pcPostInstruction + offset; - if (rjumpdest < 0 || rjumpdest >= size) { - return "Relative jump destination out of bounds"; - } - rjumpdests.set(rjumpdest); - } else if (operationNum == RelativeJumpVectorOperation.OPCODE) { - if (pos + 1 > size) { - return "Truncated jump table"; - } - final int jumpTableSize = RelativeJumpVectorOperation.getVectorSize(code, pos); - if (jumpTableSize == 0) { - return "Empty jump table"; - } - pcPostInstruction += 1 + 2 * jumpTableSize; - if (pcPostInstruction > size) { - return "Truncated jump table"; - } - for (int offsetPos = pos + 1; offsetPos < pcPostInstruction; offsetPos += 2) { - final int offset = readBigEndianI16(offsetPos, rawCode); + switch (operationNum) { + case StopOperation.OPCODE, ReturnOperation.OPCODE: + if (eofContainerMode == null) { + eofContainerMode = RUNTIME; + eofLayout.containerMode().set(RUNTIME); + } else if (!eofContainerMode.equals(RUNTIME)) { + return format( + "incompatible_container_kind opcode %s is only valid for runtime.", + opcodeInfo.name()); + } + break; + case PushOperation.PUSH_BASE, + PushOperation.PUSH_BASE + 1, + PushOperation.PUSH_BASE + 2, + PushOperation.PUSH_BASE + 3, + PushOperation.PUSH_BASE + 4, + PushOperation.PUSH_BASE + 5, + PushOperation.PUSH_BASE + 6, + PushOperation.PUSH_BASE + 7, + PushOperation.PUSH_BASE + 8, + PushOperation.PUSH_BASE + 9, + PushOperation.PUSH_BASE + 10, + PushOperation.PUSH_BASE + 11, + PushOperation.PUSH_BASE + 12, + PushOperation.PUSH_BASE + 13, + PushOperation.PUSH_BASE + 14, + PushOperation.PUSH_BASE + 15, + PushOperation.PUSH_BASE + 16, + PushOperation.PUSH_BASE + 17, + PushOperation.PUSH_BASE + 18, + PushOperation.PUSH_BASE + 19, + PushOperation.PUSH_BASE + 20, + PushOperation.PUSH_BASE + 21, + PushOperation.PUSH_BASE + 22, + PushOperation.PUSH_BASE + 23, + PushOperation.PUSH_BASE + 24, + PushOperation.PUSH_BASE + 25, + PushOperation.PUSH_BASE + 26, + PushOperation.PUSH_BASE + 27, + PushOperation.PUSH_BASE + 28, + PushOperation.PUSH_BASE + 29, + PushOperation.PUSH_BASE + 30, + PushOperation.PUSH_BASE + 31, + PushOperation.PUSH_BASE + 32: + final int multiByteDataLen = operationNum - PushOperation.PUSH_BASE; + pcPostInstruction += multiByteDataLen; + break; + case DataLoadNOperation.OPCODE: + if (pos + 2 > size) { + return "truncated_instruction DATALOADN"; + } + pcPostInstruction += 2; + final int dataLoadOffset = readBigEndianU16(pos, rawCode); + // only verfy the last byte of the load is within the minimum data + if (dataLoadOffset > eofLayout.dataLength() - 32) { + return "invalid_dataloadn_index %d + 32 > %d" + .formatted(dataLoadOffset, eofLayout.dataLength()); + } + break; + case RelativeJumpOperation.OPCODE, RelativeJumpIfOperation.OPCODE: + if (pos + 2 > size) { + return "truncated_instruction RJUMP"; + } + pcPostInstruction += 2; + final int offset = readBigEndianI16(pos, rawCode); final int rjumpdest = pcPostInstruction + offset; if (rjumpdest < 0 || rjumpdest >= size) { - return "Relative jump destination out of bounds"; + return "invalid_rjump_destination out of bounds"; } rjumpdests.set(rjumpdest); - } - } else if (operationNum == CallFOperation.OPCODE) { - if (pos + 2 > size) { - return "Truncated CALLF"; - } - int section = readBigEndianU16(pos, rawCode); - if (section >= sectionCount) { - return "CALLF to non-existent section - " + Integer.toHexString(section); - } - pcPostInstruction += 2; + break; + case RelativeJumpVectorOperation.OPCODE: + pcPostInstruction += 1; + if (pcPostInstruction > size) { + return "truncated_instruction RJUMPV"; + } + int jumpBasis = pcPostInstruction; + final int jumpTableSize = RelativeJumpVectorOperation.getVectorSize(code, pos); + pcPostInstruction += 2 * jumpTableSize; + if (pcPostInstruction > size) { + return "truncated_instruction RJUMPV"; + } + for (int offsetPos = jumpBasis; offsetPos < pcPostInstruction; offsetPos += 2) { + final int rjumpvOffset = readBigEndianI16(offsetPos, rawCode); + final int rjumpvDest = pcPostInstruction + rjumpvOffset; + if (rjumpvDest < 0 || rjumpvDest >= size) { + return "invalid_rjump_destination out of bounds"; + } + rjumpdests.set(rjumpvDest); + } + break; + case CallFOperation.OPCODE: + if (pos + 2 > size) { + return "truncated_instruction CALLF"; + } + int section = readBigEndianU16(pos, rawCode); + if (section >= eofLayout.getCodeSectionCount()) { + return "invalid_code_section_index CALLF to " + Integer.toHexString(section); + } + if (!eofLayout.getCodeSection(section).returning) { + return "CALLF to non-returning section - " + Integer.toHexString(section); + } + pcPostInstruction += 2; + break; + case RetFOperation.OPCODE: + hasReturningOpcode = true; + break; + case JumpFOperation.OPCODE: + if (pos + 2 > size) { + return "truncated_instruction JUMPF"; + } + int targetSection = readBigEndianU16(pos, rawCode); + if (targetSection >= eofLayout.getCodeSectionCount()) { + return "invalid_code_section_index JUMPF - " + Integer.toHexString(targetSection); + } + CodeSection targetCodeSection = eofLayout.getCodeSection(targetSection); + if (targetCodeSection.isReturning() && !thisCodeSection.isReturning()) { + return "invalid_non_returning_flag non-returning JUMPF source must have non-returning target"; + } else if (thisCodeSection.getOutputs() < targetCodeSection.getOutputs()) { + return format( + "jumpf_destination_incompatible_outputs target %2x with more outputs %d than current section's outputs %d", + targetSection, targetCodeSection.getOutputs(), thisCodeSection.getOutputs()); + } + hasReturningOpcode |= eofLayout.getCodeSection(targetSection).isReturning(); + pcPostInstruction += 2; + break; + case EOFCreateOperation.OPCODE: + if (pos + 1 > size) { + return format( + "truncated_instruction dangling immediate for %s at pc=%d", + opcodeInfo.name(), pos - opcodeInfo.pcAdvance()); + } + int subcontainerNum = rawCode[pos] & 0xff; + if (subcontainerNum >= eofLayout.getSubcontainerCount()) { + return format( + "invalid_container_section_index %s refers to non-existent subcontainer %d at pc=%d", + opcodeInfo.name(), subcontainerNum, pos - opcodeInfo.pcAdvance()); + } + EOFLayout subContainer = eofLayout.getSubcontainer(subcontainerNum); + var subcontainerMode = subContainer.containerMode().get(); + if (subcontainerMode == null) { + subContainer.containerMode().set(INITCODE); + } else if (subcontainerMode == RUNTIME) { + return format( + "incompatible_container_kind subcontainer %d should be initcode", subcontainerNum); + } + if (subContainer.dataLength() != subContainer.data().size()) { + return format( + "A subcontainer used for %s has a truncated data section, expected %d and is %d.", + V1_OPCODES[operationNum].name(), + subContainer.dataLength(), + subContainer.data().size()); + } + pcPostInstruction += 1; + break; + case ReturnContractOperation.OPCODE: + if (eofContainerMode == null) { + eofContainerMode = INITCODE; + eofLayout.containerMode().set(INITCODE); + } else if (!eofContainerMode.equals(INITCODE)) { + return format( + "incompatible_container_kind opcode %s is only valid for initcode", + opcodeInfo.name()); + } + if (pos + 1 > size) { + return format( + "truncated_instruction dangling immediate for %s at pc=%d", + opcodeInfo.name(), pos - opcodeInfo.pcAdvance()); + } + int returnedContractNum = rawCode[pos] & 0xff; + if (returnedContractNum >= eofLayout.getSubcontainerCount()) { + return format( + "invalid_container_section_index %s refers to non-existent subcontainer %d at pc=%d", + opcodeInfo.name(), returnedContractNum, pos - opcodeInfo.pcAdvance()); + } + EOFLayout returnedContract = eofLayout.getSubcontainer(returnedContractNum); + var returnedContractMode = returnedContract.containerMode().get(); + if (returnedContractMode == null) { + returnedContract.containerMode().set(RUNTIME); + } else if (returnedContractMode.equals(INITCODE)) { + return format( + "incompatible_container_kind subcontainer %d should be runtime", + returnedContractNum); + } + pcPostInstruction += 1; + break; + default: + // a few opcodes have potentially dangling immediates + if (opcodeInfo.pcAdvance() > 1) { + pcPostInstruction += opcodeInfo.pcAdvance() - 1; + if (pcPostInstruction > size) { + return format( + "truncated_instruction dangling immediate for %s at pc=%d", + opcodeInfo.name(), pos - opcodeInfo.pcAdvance()); + } + } + break; } immediates.set(pos, pcPostInstruction); pos = pcPostInstruction; } - if ((attribute & TERMINAL) != TERMINAL) { - return "No terminating instruction"; + if (thisCodeSection.isReturning() != hasReturningOpcode) { + return thisCodeSection.isReturning() + ? "unreachable_code_sections no RETF or qualifying JUMPF" + : "invalid_non_returning_flag RETF or JUMPF into returning section"; + } + if (!opcodeInfo.terminal()) { + return "missing_stop_opcode No terminating instruction"; } if (rjumpdests.intersects(immediates)) { - return "Relative jump destinations targets invalid immediate data"; + return "invalid_rjump_destination targets immediate data"; } return null; } - static String validateStack(final EOFLayout eofLayout) { - for (int i = 0; i < eofLayout.getCodeSectionCount(); i++) { - var validation = CodeV1Validation.validateStack(i, eofLayout); + @Nullable + @Override + public String validateStack(final EOFLayout eofLayout) { + WorkList workList = new WorkList(eofLayout.getCodeSectionCount()); + workList.put(0); + int sectionToValidatie = workList.take(); + while (sectionToValidatie >= 0) { + var validation = validateStack(sectionToValidatie, eofLayout, workList); if (validation != null) { return validation; } + sectionToValidatie = workList.take(); + } + if (!workList.isComplete()) { + return format("Unreachable code section %d", workList.getFirstUnmarkedItem()); } return null; } @@ -681,117 +403,283 @@ static String validateStack(final EOFLayout eofLayout) { * * @param codeSectionToValidate The index of code to validate in the code sections * @param eofLayout The EOF container to validate + * @param workList The list of code sections needing validation * @return null if valid, otherwise an error string providing the validation error. */ - public static String validateStack(final int codeSectionToValidate, final EOFLayout eofLayout) { + @Nullable + String validateStack( + final int codeSectionToValidate, final EOFLayout eofLayout, final WorkList workList) { + if (!eofLayout.isValid()) { + return "EOF Layout invalid - " + eofLayout.invalidReason(); + } try { CodeSection toValidate = eofLayout.getCodeSection(codeSectionToValidate); byte[] code = - eofLayout.getContainer().slice(toValidate.entryPoint, toValidate.length).toArrayUnsafe(); + eofLayout.container().slice(toValidate.entryPoint, toValidate.length).toArrayUnsafe(); int codeLength = code.length; - int[] stackHeights = new int[codeLength]; - Arrays.fill(stackHeights, -1); - - int thisWork = 0; - int maxWork = 1; - int[][] workList = new int[codeLength][2]; + int[] stack_min = new int[codeLength]; + int[] stack_max = new int[codeLength]; + Arrays.fill(stack_min, 1025); + Arrays.fill(stack_max, -1); int initialStackHeight = toValidate.getInputs(); int maxStackHeight = initialStackHeight; - stackHeights[0] = initialStackHeight; - workList[0][1] = initialStackHeight; + stack_min[0] = initialStackHeight; + stack_max[0] = initialStackHeight; int unusedBytes = codeLength; - while (thisWork < maxWork) { - int currentPC = workList[thisWork][0]; - int currentStackHeight = workList[thisWork][1]; - if (thisWork > 0 && stackHeights[currentPC] >= 0) { - // we've been here, validate the jump is what is expected - if (stackHeights[currentPC] != currentStackHeight) { - return String.format( - "Jump into code stack height (%d) does not match previous value (%d)", - stackHeights[currentPC], currentStackHeight); - } else { - thisWork++; - continue; - } - } else { - stackHeights[currentPC] = currentStackHeight; - } + int currentPC = 0; + int currentMin = initialStackHeight; + int currentMax = initialStackHeight; - while (currentPC < codeLength) { - int thisOp = code[currentPC] & 0xff; + while (currentPC < codeLength) { + int thisOp = code[currentPC] & 0xff; - byte[] stackInfo = OPCODE_STACK_VALIDATION[thisOp]; - int stackInputs; - int stackOutputs; - int pcAdvance = stackInfo[2]; - if (thisOp == CallFOperation.OPCODE) { + OpcodeInfo opcodeInfo = V1_OPCODES[thisOp]; + int stackInputs; + int stackOutputs; + int sectionStackUsed; + int pcAdvance = opcodeInfo.pcAdvance(); + switch (thisOp) { + case CallFOperation.OPCODE: int section = readBigEndianU16(currentPC + 1, code); - stackInputs = eofLayout.getCodeSection(section).getInputs(); - stackOutputs = eofLayout.getCodeSection(section).getOutputs(); - } else { - stackInputs = stackInfo[0]; - stackOutputs = stackInfo[1]; - } + workList.put(section); + CodeSection codeSection = eofLayout.getCodeSection(section); + stackInputs = codeSection.getInputs(); + stackOutputs = codeSection.getOutputs(); + sectionStackUsed = codeSection.getMaxStackHeight(); + break; + case DupNOperation.OPCODE: + int depth = code[currentPC + 1] & 0xff; + stackInputs = depth + 1; + stackOutputs = depth + 2; + sectionStackUsed = 0; + break; + case SwapNOperation.OPCODE: + int swapDepth = 2 + (code[currentPC + 1] & 0xff); + stackInputs = swapDepth; + stackOutputs = swapDepth; + sectionStackUsed = 0; + break; + case ExchangeOperation.OPCODE: + int imm = code[currentPC + 1] & 0xff; + int exchangeDepth = (imm >> 4) + (imm & 0xf) + 3; + stackInputs = exchangeDepth; + stackOutputs = exchangeDepth; + sectionStackUsed = 0; + break; + default: + stackInputs = opcodeInfo.inputs(); + stackOutputs = opcodeInfo.outputs(); + sectionStackUsed = 0; + } - if (stackInputs > currentStackHeight) { - return String.format( - "Operation 0x%02X requires stack of %d but only has %d items", - thisOp, stackInputs, currentStackHeight); - } + int nextPC; + if (!opcodeInfo.valid()) { + return format("undefined_instruction 0x%02x", thisOp); + } + nextPC = currentPC + pcAdvance; - currentStackHeight = currentStackHeight - stackInputs + stackOutputs; - if (currentStackHeight > MAX_STACK_HEIGHT) { - return "Stack height exceeds 1024"; - } + if (nextPC > codeLength) { + return format( + "Dangling immediate argument for opcode 0x%x at PC %d in code section %d.", + thisOp, currentPC - pcAdvance, codeSectionToValidate); + } + if (stack_max[currentPC] < 0) { + return format( + "unreachable_instructions section 0x%x pc %d was not forward referenced", + codeSectionToValidate, currentPC); + } + currentMin = min(stack_min[currentPC], currentMin); + currentMax = max(stack_max[currentPC], currentMax); - maxStackHeight = Math.max(maxStackHeight, currentStackHeight); + if (stackInputs > currentMin) { + return format( + "stack_underflow operation 0x%02X wants stack of %d but may only have %d", + thisOp, stackInputs, currentMin); + } + + int stackDelta = stackOutputs - stackInputs; + currentMax = currentMax + stackDelta; + currentMin = currentMin + stackDelta; + if (currentMax + sectionStackUsed - stackOutputs > MAX_STACK_HEIGHT) { + return "Stack height exceeds 1024"; + } + + unusedBytes -= pcAdvance; + maxStackHeight = max(maxStackHeight, currentMax); + + switch (thisOp) { + case RelativeJumpOperation.OPCODE: + int jValue = readBigEndianI16(currentPC + 1, code); + int targetPC = nextPC + jValue; + if (targetPC > currentPC) { + stack_min[targetPC] = min(stack_min[targetPC], currentMin); + stack_max[targetPC] = max(stack_max[targetPC], currentMax); + } else { + if (stack_min[targetPC] != currentMin) { + return format( + "stack_height_mismatch backwards RJUMP from %d to %d, min %d != %d", + currentPC, targetPC, stack_min[targetPC], currentMin); + } + if (stack_max[targetPC] != currentMax) { + return format( + "stack_height_mismatch backwards RJUMP from %d to %d, max %d != %d", + currentPC, targetPC, stack_max[targetPC], currentMax); + } + } - if (thisOp == RelativeJumpOperation.OPCODE || thisOp == RelativeJumpIfOperation.OPCODE) { - // no `& 0xff` on high byte because this is one case we want sign extension - int rvalue = readBigEndianI16(currentPC + 1, code); - workList[maxWork] = new int[] {currentPC + rvalue + 3, currentStackHeight}; - maxWork++; - } else if (thisOp == RelativeJumpVectorOperation.OPCODE) { + // terminal op, reset currentMin and currentMax to forward set values + if (nextPC < codeLength) { + currentMax = stack_max[nextPC]; + currentMin = stack_min[nextPC]; + } + break; + case RelativeJumpIfOperation.OPCODE: + stack_max[nextPC] = max(stack_max[nextPC], currentMax); + stack_min[nextPC] = min(stack_min[nextPC], currentMin); + int jiValue = readBigEndianI16(currentPC + 1, code); + int targetPCi = nextPC + jiValue; + if (targetPCi > currentPC) { + stack_min[targetPCi] = min(stack_min[targetPCi], currentMin); + stack_max[targetPCi] = max(stack_max[targetPCi], currentMax); + } else { + if (stack_min[targetPCi] != currentMin) { + return format( + "stack_height_mismatch backwards RJUMPI from %d to %d, min %d != %d", + currentPC, targetPCi, stack_min[targetPCi], currentMin); + } + if (stack_max[targetPCi] != currentMax) { + return format( + "stack_height_mismatch backwards RJUMPI from %d to %d, max %d != %d", + currentPC, targetPCi, stack_max[targetPCi], currentMax); + } + } + break; + case RelativeJumpVectorOperation.OPCODE: int immediateDataSize = (code[currentPC + 1] & 0xff) * 2; - unusedBytes -= immediateDataSize; - int tableEnd = immediateDataSize + currentPC + 2; + unusedBytes -= immediateDataSize + 2; + int tableEnd = immediateDataSize + currentPC + 4; + nextPC = tableEnd; + stack_max[nextPC] = max(stack_max[nextPC], currentMax); + stack_min[nextPC] = min(stack_min[nextPC], currentMin); for (int i = currentPC + 2; i < tableEnd; i += 2) { - int rvalue = readBigEndianI16(i, code); - workList[maxWork] = new int[] {tableEnd + rvalue, currentStackHeight}; - maxWork++; + int vValue = readBigEndianI16(i, code); + int targetPCv = tableEnd + vValue; + if (targetPCv > currentPC) { + stack_min[targetPCv] = min(stack_min[targetPCv], currentMin); + stack_max[targetPCv] = max(stack_max[targetPCv], currentMax); + } else { + if (stack_min[targetPCv] != currentMin) { + return format( + "stack_height_mismatch backwards RJUMPV from %d to %d, min %d != %d", + currentPC, targetPCv, stack_min[targetPCv], currentMin); + } + if (stack_max[targetPCv] != currentMax) { + return format( + "stack_height_mismatch backwards RJUMPV from %d to %d, max %d != %d", + currentPC, targetPCv, stack_max[targetPCv], currentMax); + } + } } - currentPC = tableEnd - 2; - } else if (thisOp == RetFOperation.OPCODE) { + break; + case RetFOperation.OPCODE: int returnStackItems = toValidate.getOutputs(); - if (currentStackHeight != returnStackItems) { - return String.format( - "Section return (RETF) calculated height 0x%x does not match configured height 0x%x", - currentStackHeight, returnStackItems); + if (currentMin != currentMax) { + return format( + "RETF in section %d has a stack range (%d/%d)and must have only one stack value", + codeSectionToValidate, currentMin, currentMax); + } + if (stack_min[currentPC] != returnStackItems + || stack_min[currentPC] != stack_max[currentPC]) { + return format( + "stack_higher_than_outputs RETF in section %d calculated height %d does not match configured return stack %d, min height %d, and max height %d", + codeSectionToValidate, + currentMin, + returnStackItems, + stack_min[currentPC], + stack_max[currentPC]); + } + // terminal op, reset currentMin and currentMax to forward set values + if (nextPC < codeLength) { + currentMax = stack_max[nextPC]; + currentMin = stack_min[nextPC]; + } + break; + case JumpFOperation.OPCODE: + int jumpFTargetSectionNum = readBigEndianI16(currentPC + 1, code); + workList.put(jumpFTargetSectionNum); + CodeSection targetCs = eofLayout.getCodeSection(jumpFTargetSectionNum); + if (currentMax + targetCs.getMaxStackHeight() - targetCs.getInputs() + > MAX_STACK_HEIGHT) { + return format( + "JUMPF at section %d pc %d would exceed maximum stack with %d items", + codeSectionToValidate, + currentPC, + currentMax + targetCs.getMaxStackHeight() - targetCs.getInputs()); + } + if (targetCs.isReturning()) { + if (currentMin != currentMax) { + return format( + "JUMPF at section %d pc %d has a variable stack height %d/%d", + codeSectionToValidate, currentPC, currentMin, currentMax); + } + int expectedMax = toValidate.outputs + targetCs.inputs - targetCs.outputs; + if (currentMax != expectedMax) { + return format( + "%s JUMPF at section %d pc %d has incompatible stack height for returning section %d (%d != %d + %d - %d)", + currentMax < expectedMax ? "stack_underflow" : "stack_higher_than_outputs", + codeSectionToValidate, + currentPC, + jumpFTargetSectionNum, + currentMax, + toValidate.outputs, + targetCs.inputs, + targetCs.outputs); + } + } else { + if (currentMin < targetCs.getInputs()) { + return format( + "stack_underflow JUMPF at section %d pc %d has insufficient minimum stack height for non returning section %d (%d != %d)", + codeSectionToValidate, + currentPC, + jumpFTargetSectionNum, + currentMin, + targetCs.inputs); + } + } + // fall through for terminal op handling + case StopOperation.OPCODE, + ReturnContractOperation.OPCODE, + ReturnOperation.OPCODE, + RevertOperation.OPCODE, + InvalidOperation.OPCODE: + // terminal op, reset currentMin and currentMax to forward set values + if (nextPC < codeLength) { + currentMax = stack_max[nextPC]; + currentMin = stack_min[nextPC]; + } + break; + default: + // Ordinary operations, update stack for next operation + if (nextPC < codeLength) { + currentMax = max(stack_max[nextPC], currentMax); + stack_max[nextPC] = currentMax; + currentMin = min(stack_min[nextPC], currentMin); + stack_min[nextPC] = min(stack_min[nextPC], currentMin); } - } - if (pcAdvance < 0) { - unusedBytes += pcAdvance; break; - } else if (pcAdvance == 0) { - return String.format("Invalid Instruction 0x%02x", thisOp); - } - - currentPC += pcAdvance; - stackHeights[currentPC] = currentStackHeight; - unusedBytes -= pcAdvance; } - - thisWork++; + currentPC = nextPC; } + if (maxStackHeight != toValidate.maxStackHeight) { - return String.format( - "Calculated max stack height (%d) does not match reported stack height (%d)", + return format( + "invalid_max_stack_height Calculated (%d) != reported (%d)", maxStackHeight, toValidate.maxStackHeight); } if (unusedBytes != 0) { - return String.format("Dead code detected in section %d", codeSectionToValidate); + return format("Dead code detected in section %d", codeSectionToValidate); } return null; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java b/evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java index f92022ca445..8ff8c801587 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/EOFLayout.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,47 +11,116 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.evm.code; +import static org.hyperledger.besu.evm.code.OpcodeInfo.V1_OPCODES; + +import org.hyperledger.besu.evm.operation.ExchangeOperation; +import org.hyperledger.besu.evm.operation.RelativeJumpIfOperation; +import org.hyperledger.besu.evm.operation.RelativeJumpOperation; +import org.hyperledger.besu.evm.operation.RelativeJumpVectorOperation; + import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.Nullable; import org.apache.tuweni.bytes.Bytes; -/** The EOF layout. */ -public class EOFLayout { +/** + * The EOF layout. + * + * @param container The literal EOF bytes fo the whole container + * @param version The parsed version id. zero if unparseable. + * @param codeSections The parsed Code sections. Null if invalid. + * @param subContainers The parsed subcontainers. Null if invalid. + * @param dataLength The length of the data as reported by the container. For subcontainers this may + * be larger than the data in the data field. Zero if invalid. + * @param data The data hard coded in the container. Empty if invalid. + * @param invalidReason If the raw container is invalid, the reason it is invalid. Null if valid. + * @param containerMode The mode of the container (runtime or initcode, if known) + */ +public record EOFLayout( + Bytes container, + int version, + CodeSection[] codeSections, + EOFLayout[] subContainers, + int dataLength, + Bytes data, + String invalidReason, + AtomicReference containerMode) { + + /** + * Enum tracking the useage mode of an EOF container. Detected either by opcode usage or + * determined by the source. + */ + public enum EOFContainerMode { + /** Usage mode is unknown */ + UNKNOWN, + /** Usage mode is as init code */ + INITCODE, + /** Usage mode is as deployed or runtime code */ + RUNTIME + } + + /** The EOF prefix byte as a (signed) java byte. */ + public static final byte EOF_PREFIX_BYTE = (byte) 0xEF; - /** The Section Terminator. */ + /** header terminator */ static final int SECTION_TERMINATOR = 0x00; - /** The Section types. */ + + /** type data (stack heights, inputs/outputs) */ static final int SECTION_TYPES = 0x01; - /** The Section code. */ + + /** code */ static final int SECTION_CODE = 0x02; - /** The Section data. */ - static final int SECTION_DATA = 0x03; + + /** sub-EOF subContainers for create */ + static final int SECTION_CONTAINER = 0x03; + + /** data */ + static final int SECTION_DATA = 0x04; /** The Max supported section. */ static final int MAX_SUPPORTED_VERSION = 1; - private final Bytes container; - private final int version; - private final CodeSection[] codeSections; - private final String invalidReason; - - private EOFLayout(final Bytes container, final int version, final CodeSection[] codeSections) { - this.container = container; - this.version = version; - this.codeSections = codeSections; - this.invalidReason = null; + private EOFLayout( + final Bytes container, + final int version, + final CodeSection[] codeSections, + final EOFLayout[] containers, + final int dataSize, + final Bytes data) { + this( + container, + version, + codeSections, + containers, + dataSize, + data, + null, + new AtomicReference<>(null)); } private EOFLayout(final Bytes container, final int version, final String invalidReason) { - this.container = container; - this.version = version; - this.codeSections = null; - this.invalidReason = invalidReason; + this( + container, + version, + new CodeSection[0], + new EOFLayout[0], + 0, + Bytes.EMPTY, + invalidReason, + new AtomicReference<>(null)); } private static EOFLayout invalidLayout( @@ -62,14 +131,21 @@ private static EOFLayout invalidLayout( private static String readKind(final ByteArrayInputStream inputStream, final int expectedKind) { int kind = inputStream.read(); if (kind == -1) { - return "Improper section headers"; + return "missing_headers_terminator Improper section headers"; } if (kind != expectedKind) { - return "Expected kind " + expectedKind + " but read kind " + kind; + return "unexpected_header_kind expected " + expectedKind + " actual " + kind; } return null; } + private static int peekKind(final ByteArrayInputStream inputStream) { + inputStream.mark(1); + int kind = inputStream.read(); + inputStream.reset(); + return kind; + } + /** * Parse EOF. * @@ -77,76 +153,191 @@ private static String readKind(final ByteArrayInputStream inputStream, final int * @return the eof layout */ public static EOFLayout parseEOF(final Bytes container) { - final ByteArrayInputStream inputStream = new ByteArrayInputStream(container.toArrayUnsafe()); + return parseEOF(container, true); + } + + private record EOFParseStep( + Bytes container, + boolean strictSize, + int index, + EOFParseStep parent, + EOFLayout[] parentSubcontainers) { + String subcontainerIndex() { + if (index < 0) { + return ""; + } + StringBuilder version = new StringBuilder(); + EOFParseStep current = this; + while (current != null) { + var prev = current.parent; + if (prev == null) { + version.insert(0, index); + break; + } else { + version.insert(0, '.'); + version.insert(1, current.index); + } + current = prev; + } + return version.toString(); + } + } + + /** + * Parse EOF. + * + * @param container the container + * @param strictSize Require the container to fill all bytes, a validation error will result if + * strict and excess data is in the container + * @return the eof layout + */ + @SuppressWarnings("ReferenceEquality") + public static EOFLayout parseEOF(final Bytes container, final boolean strictSize) { + Queue parseQueue = new ArrayDeque<>(); + parseQueue.add(new EOFParseStep(container, strictSize, -1, null, null)); + EOFLayout result = null; + while (true) { + EOFParseStep step = parseQueue.remove(); + var parsedContainer = parseEOF(step, parseQueue); + if (result == null) { + result = parsedContainer; + } + + if (!parsedContainer.isValid()) { + return invalidLayout( + container, + result.version, + step.index == -1 + ? parsedContainer.invalidReason + : "Invalid subcontainer " + + step.subcontainerIndex() + + " - " + + parsedContainer.invalidReason); + } + // This ReferenceEquality check is correct + if ((strictSize || result != parsedContainer) + && step.container.size() != parsedContainer.container.size()) { + return invalidLayout( + container, + parsedContainer.version, + "invalid_section_bodies_size subcontainer size mismatch"); + } + if (step.index >= 0) { + step.parentSubcontainers[step.index] = parsedContainer; + } + if (parseQueue.isEmpty()) { + return result; + } + } + } + + private static EOFLayout parseEOF(final EOFParseStep step, final Queue queue) { + final ByteArrayInputStream inputStream = + new ByteArrayInputStream(step.container.toArrayUnsafe()); if (inputStream.available() < 3) { - return invalidLayout(container, -1, "EOF Container too small"); + return invalidLayout(step.container, -1, "invalid_magic EOF Container too small"); } if (inputStream.read() != 0xEF) { - return invalidLayout(container, -1, "EOF header byte 0 incorrect"); + return invalidLayout(step.container, -1, "invalid_magic EOF header byte 0 incorrect"); } if (inputStream.read() != 0x0) { - return invalidLayout(container, -1, "EOF header byte 1 incorrect"); + return invalidLayout(step.container, -1, "invalid_magic EOF header byte 1 incorrect"); } final int version = inputStream.read(); if (version > MAX_SUPPORTED_VERSION || version < 1) { - return invalidLayout(container, version, "Unsupported EOF Version " + version); + return invalidLayout(step.container, version, "invalid_version " + version); } String error = readKind(inputStream, SECTION_TYPES); if (error != null) { - return invalidLayout(container, version, error); + return invalidLayout(step.container, version, error); } int typesLength = readUnsignedShort(inputStream); - if (typesLength <= 0) { - return invalidLayout(container, version, "Invalid Types section size"); + if (typesLength % 4 != 0) { + return invalidLayout( + step.container, + version, + "invalid_type_section_size Invalid Types section size (mod 4 != 0)"); } error = readKind(inputStream, SECTION_CODE); if (error != null) { - return invalidLayout(container, version, error); + return invalidLayout(step.container, version, error); } int codeSectionCount = readUnsignedShort(inputStream); if (codeSectionCount <= 0) { - return invalidLayout(container, version, "Invalid Code section count"); + return invalidLayout( + step.container, version, "incomplete_section_number Too few code sections"); + } + if (codeSectionCount > 1024) { + return invalidLayout( + step.container, + version, + "too_many_code_sections - 0x" + Integer.toHexString(codeSectionCount)); } if (codeSectionCount * 4 != typesLength) { return invalidLayout( - container, + step.container, version, - "Type section length incompatible with code section count - 0x" + "invalid_section_bodies_size Type section - 0x" + Integer.toHexString(codeSectionCount) + " * 4 != 0x" + Integer.toHexString(typesLength)); } - if (codeSectionCount > 1024) { - return invalidLayout( - container, - version, - "Too many code sections - 0x" + Integer.toHexString(codeSectionCount)); - } int[] codeSectionSizes = new int[codeSectionCount]; for (int i = 0; i < codeSectionCount; i++) { int size = readUnsignedShort(inputStream); if (size <= 0) { - return invalidLayout(container, version, "Invalid Code section size for section " + i); + return invalidLayout(step.container, version, "zero_section_size code " + i); } codeSectionSizes[i] = size; } + int containerSectionCount; + int[] containerSectionSizes; + if (peekKind(inputStream) == SECTION_CONTAINER) { + error = readKind(inputStream, SECTION_CONTAINER); + if (error != null) { + return invalidLayout(step.container, version, error); + } + containerSectionCount = readUnsignedShort(inputStream); + if (containerSectionCount <= 0) { + return invalidLayout(step.container, version, "Invalid container section count"); + } + if (containerSectionCount > 256) { + return invalidLayout( + step.container, + version, + "too_many_containers sections - 0x" + Integer.toHexString(containerSectionCount)); + } + containerSectionSizes = new int[containerSectionCount]; + for (int i = 0; i < containerSectionCount; i++) { + int size = readUnsignedShort(inputStream); + if (size <= 0) { + return invalidLayout( + step.container, version, "Invalid container section size for section " + i); + } + containerSectionSizes[i] = size; + } + } else { + containerSectionCount = 0; + containerSectionSizes = new int[0]; + } + error = readKind(inputStream, SECTION_DATA); if (error != null) { - return invalidLayout(container, version, error); + return invalidLayout(step.container, version, error); } int dataSize = readUnsignedShort(inputStream); if (dataSize < 0) { - return invalidLayout(container, version, "Invalid Data section size"); + return invalidLayout(step.container, version, "incomplete_data_header"); } error = readKind(inputStream, SECTION_TERMINATOR); if (error != null) { - return invalidLayout(container, version, error); + return invalidLayout(step.container, version, error); } int[][] typeData = new int[codeSectionCount][3]; for (int i = 0; i < codeSectionCount; i++) { @@ -156,11 +347,12 @@ public static EOFLayout parseEOF(final Bytes container) { typeData[i][2] = readUnsignedShort(inputStream); } if (typeData[codeSectionCount - 1][2] == -1) { - return invalidLayout(container, version, "Incomplete type section"); + return invalidLayout( + step.container, version, "invalid_section_bodies_size Incomplete type section"); } - if (typeData[0][0] != 0 || typeData[0][1] != 0) { + if (typeData[0][0] != 0 || (typeData[0][1] & 0x7f) != 0) { return invalidLayout( - container, version, "Code section does not have zero inputs and outputs"); + step.container, version, "invalid_first_section_type must be zero input non-returning"); } CodeSection[] codeSections = new CodeSection[codeSectionCount]; int pos = // calculate pos in stream... @@ -171,42 +363,82 @@ public static EOFLayout parseEOF(final Bytes container) { + 3 // data section header + 1 // padding + (codeSectionCount * 4); // type data + if (containerSectionCount > 0) { + pos += + 3 // subcontainer header + + (containerSectionCount * 2); // subcontainer sizes + } + for (int i = 0; i < codeSectionCount; i++) { int codeSectionSize = codeSectionSizes[i]; if (inputStream.skip(codeSectionSize) != codeSectionSize) { - return invalidLayout(container, version, "Incomplete code section " + i); + return invalidLayout( + step.container, version, "invalid_section_bodies_size code section " + i); } if (typeData[i][0] > 0x7f) { return invalidLayout( - container, + step.container, version, - "Type data input stack too large - 0x" + Integer.toHexString(typeData[i][0])); + "inputs_outputs_num_above_limit Type data input stack too large - 0x" + + Integer.toHexString(typeData[i][0])); } - if (typeData[i][1] > 0x7f) { + if (typeData[i][1] > 0x80) { return invalidLayout( - container, + step.container, version, - "Type data output stack too large - 0x" + Integer.toHexString(typeData[i][1])); + "inputs_outputs_num_above_limit - 0x" + Integer.toHexString(typeData[i][1])); } if (typeData[i][2] > 0x3ff) { return invalidLayout( - container, + step.container, version, - "Type data max stack too large - 0x" + Integer.toHexString(typeData[i][2])); + "max_stack_height_above_limit Type data max stack too large - 0x" + + Integer.toHexString(typeData[i][2])); } codeSections[i] = new CodeSection(codeSectionSize, typeData[i][0], typeData[i][1], typeData[i][2], pos); + if (i == 0 && typeData[0][1] != 0x80) { + return invalidLayout( + step.container, + version, + "invalid_first_section_type want 0x80 (non-returning flag) has " + typeData[0][1]); + } pos += codeSectionSize; } - if (inputStream.skip(dataSize) != dataSize) { - return invalidLayout(container, version, "Incomplete data section"); + EOFLayout[] subContainers = new EOFLayout[containerSectionCount]; + for (int i = 0; i < containerSectionCount; i++) { + int subcontainerSize = containerSectionSizes[i]; + if (subcontainerSize != inputStream.skip(subcontainerSize)) { + return invalidLayout(step.container, version, "invalid_section_bodies_size"); + } + Bytes subcontainer = step.container.slice(pos, subcontainerSize); + pos += subcontainerSize; + queue.add(new EOFParseStep(subcontainer, false, i, step, subContainers)); } + + long loadedDataCount = inputStream.skip(dataSize); + Bytes data = step.container.slice(pos, (int) loadedDataCount); + + Bytes completeContainer; if (inputStream.read() != -1) { - return invalidLayout(container, version, "Dangling data after end of all sections"); + if (step.strictSize) { + return invalidLayout( + step.container, version, "invalid_section_bodies_size data after end of all sections"); + } else { + completeContainer = step.container.slice(0, pos + dataSize); + } + } else { + completeContainer = step.container; + } + if (step.strictSize && dataSize != data.size()) { + return invalidLayout( + step.container, + version, + "toplevel_container_truncated Truncated data section when a complete section was required"); } - return new EOFLayout(container, version, codeSections); + return new EOFLayout(completeContainer, version, codeSections, subContainers, dataSize, data); } /** @@ -224,30 +456,31 @@ static int readUnsignedShort(final ByteArrayInputStream inputStream) { } /** - * Gets container. + * Get code section count. * - * @return the container + * @return the code section count */ - public Bytes getContainer() { - return container; + public int getCodeSectionCount() { + return codeSections == null ? 0 : codeSections.length; } /** - * Gets version. + * Get code sections. * - * @return the version + * @param i the index + * @return the Code section */ - public int getVersion() { - return version; + public CodeSection getCodeSection(final int i) { + return codeSections[i]; } /** - * Get code section count. + * Get sub container section count. * - * @return the code section count + * @return the sub container count */ - public int getCodeSectionCount() { - return codeSections == null ? 0 : codeSections.length; + public int getSubcontainerCount() { + return subContainers == null ? 0 : subContainers.length; } /** @@ -256,17 +489,18 @@ public int getCodeSectionCount() { * @param i the index * @return the Code section */ - public CodeSection getCodeSection(final int i) { - return codeSections[i]; + public EOFLayout getSubcontainer(final int i) { + return subContainers[i]; } /** - * Gets invalid reason. + * Finds the first instance of the subcontainer in the list of container, or -1 if not present * - * @return the invalid reason + * @param container the container to search for + * @return the index of the container, or -1 if not found. */ - public String getInvalidReason() { - return invalidReason; + public int indexOfSubcontainer(final EOFLayout container) { + return Arrays.asList(subContainers).indexOf(container); } /** @@ -277,4 +511,344 @@ public String getInvalidReason() { public boolean isValid() { return invalidReason == null; } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (!(o instanceof EOFLayout eofLayout)) return false; + return version == eofLayout.version + && container.equals(eofLayout.container) + && Arrays.equals(codeSections, eofLayout.codeSections) + && Arrays.equals(subContainers, eofLayout.subContainers) + && Objects.equals(invalidReason, eofLayout.invalidReason); + } + + @Override + public int hashCode() { + int result = Objects.hash(container, version, invalidReason); + result = 31 * result + Arrays.hashCode(codeSections); + result = 31 * result + Arrays.hashCode(subContainers); + return result; + } + + @Override + public String toString() { + return "EOFLayout{" + + "container=" + + container + + ", version=" + + version + + ", codeSections=" + + (codeSections == null ? "null" : Arrays.asList(codeSections).toString()) + + ", containers=" + + (subContainers == null ? "null" : Arrays.asList(subContainers).toString()) + + ", invalidReason='" + + invalidReason + + '\'' + + '}'; + } + + /** + * Re-writes the container with optional auxiliary data. + * + * @param auxData the auxiliary data + * @return Null if there was an error (validation or otherwise) , or the bytes of the re-written + * container. + */ + @Nullable + public Bytes writeContainer(@Nullable final Bytes auxData) { + // do not write invalid containers + if (invalidReason != null) { + return null; + } + + try { + ByteArrayOutputStream baos = + new ByteArrayOutputStream(container.size() + dataLength - data.size()); + DataOutputStream out = new DataOutputStream(baos); + + // EOF header + out.writeByte(EOF_PREFIX_BYTE); + out.writeByte(0); + out.writeByte(version); + + // Types header + out.writeByte(SECTION_TYPES); + out.writeShort(codeSections.length * 4); + + // Code header + out.writeByte(SECTION_CODE); + out.writeShort(codeSections.length); + for (CodeSection cs : codeSections) { + out.writeShort(cs.length); + } + + // Subcontainers header + if (subContainers != null && subContainers.length > 0) { + out.writeByte(SECTION_CONTAINER); + out.writeShort(subContainers.length); + for (EOFLayout container : subContainers) { + out.writeShort(container.container.size()); + } + } + + // Data header + out.writeByte(SECTION_DATA); + if (auxData == null) { + out.writeShort(dataLength); + } else { + int newSize = data.size() + auxData.size(); + if (newSize < dataLength) { + // aux data must cover claimed data lengths. + return null; + } + out.writeShort(newSize); + } + + // header end + out.writeByte(0); + + // Types information + for (CodeSection cs : codeSections) { + out.writeByte(cs.inputs); + if (cs.returning) { + out.writeByte(cs.outputs); + } else { + out.writeByte(0x80); + } + out.writeShort(cs.maxStackHeight); + } + + // Code sections + for (CodeSection cs : codeSections) { + out.write(container.slice(cs.entryPoint, cs.length).toArray()); + } + + // Subcontainers + if (subContainers != null) { + for (EOFLayout container : subContainers) { + out.write(container.container.toArrayUnsafe()); + } + } + + // data + out.write(data.toArrayUnsafe()); + if (auxData != null) { + out.write(auxData.toArrayUnsafe()); + } + + return Bytes.wrap(baos.toByteArray()); + } catch (IOException ioe) { + // ByteArrayOutputStream should never throw, so something has gone very wrong. Wrap as + // runtime + // and re-throw. + throw new RuntimeException(ioe); + } + } + + /** + * A more readable representation of the hex bytes, including whitespace and comments after hashes + * + * @return The pretty printed code + */ + public String prettyPrint() { + StringWriter sw = new StringWriter(); + prettyPrint(new PrintWriter(sw, true), "", ""); + return sw.toString(); + } + + /** + * A more readable representation of the hex bytes, including whitespace and comments after hashes + * + * @param out the print writer to pretty print to + */ + public void prettyPrint(final PrintWriter out) { + out.println("0x # EOF"); + prettyPrint(out, "", ""); + } + + /** + * A more readable representation of the hex bytes, including whitespace and comments after hashes + * + * @param out the print writer to pretty print to + * @param prefix The prefix to prepend to all output lines (useful for nested subconntainers) + * @param subcontainerPrefix The prefix to add to subcontainer names. + */ + public void prettyPrint( + final PrintWriter out, final String prefix, final String subcontainerPrefix) { + + if (!isValid()) { + out.print(prefix); + out.println("# Invalid EOF"); + out.print(prefix); + out.println("# " + invalidReason); + out.println(container); + } + + out.print(prefix); + out.printf("ef00%02x # Magic and Version ( %1$d )%n", version); + out.print(prefix); + out.printf("01%04x # Types length ( %1$d )%n", codeSections.length * 4); + out.print(prefix); + out.printf("02%04x # Total code sections ( %1$d )%n", codeSections.length); + for (int i = 0; i < codeSections.length; i++) { + out.print(prefix); + out.printf(" %04x # Code section %d , %1$d bytes%n", getCodeSection(i).getLength(), i); + } + if (subContainers.length > 0) { + out.print(prefix); + out.printf("03%04x # Total subcontainers ( %1$d )%n", subContainers.length); + for (int i = 0; i < subContainers.length; i++) { + out.print(prefix); + out.printf(" %04x # Sub container %d, %1$d byte%n", subContainers[i].container.size(), i); + } + } + out.print(prefix); + out.printf("04%04x # Data section length( %1$d )", dataLength); + if (dataLength != data.size()) { + out.printf(" (actual size %d)", data.size()); + } + out.print(prefix); + out.printf("%n"); + out.print(prefix); + out.printf(" 00 # Terminator (end of header)%n"); + for (int i = 0; i < codeSections.length; i++) { + CodeSection cs = getCodeSection(i); + out.print(prefix); + out.printf(" # Code section %d types%n", i); + out.print(prefix); + out.printf(" %02x # %1$d inputs %n", cs.getInputs()); + out.print(prefix); + out.printf( + " %02x # %d outputs %s%n", + cs.isReturning() ? cs.getOutputs() : 0x80, + cs.getOutputs(), + cs.isReturning() ? "" : " (Non-returning function)"); + out.print(prefix); + out.printf(" %04x # max stack: %1$d%n", cs.getMaxStackHeight()); + } + for (int i = 0; i < codeSections.length; i++) { + CodeSection cs = getCodeSection(i); + out.print(prefix); + out.printf( + " # Code section %d - in=%d out=%s height=%d%n", + i, cs.inputs, cs.isReturning() ? cs.outputs : "non-returning", cs.maxStackHeight); + byte[] byteCode = container.slice(cs.getEntryPoint(), cs.getLength()).toArray(); + int pc = 0; + while (pc < byteCode.length) { + out.print(prefix); + OpcodeInfo ci = V1_OPCODES[byteCode[pc] & 0xff]; + + if (ci.opcode() == RelativeJumpVectorOperation.OPCODE) { + if (byteCode.length <= pc + 1) { + out.printf( + " %02x # [%d] %s()%n", byteCode[pc], pc, ci.name()); + pc++; + } else { + int tableSize = byteCode[pc + 1] & 0xff; + out.printf("%02x%02x", byteCode[pc], byteCode[pc + 1]); + int calculatedTableEnd = pc + tableSize * 2 + 4; + int lastTableEntry = Math.min(byteCode.length, calculatedTableEnd); + for (int j = pc + 2; j < lastTableEntry; j++) { + out.printf("%02x", byteCode[j]); + } + out.printf(" # [%d] %s(", pc, ci.name()); + for (int j = pc + 3; j < lastTableEntry; j += 2) { + // j indexes to the second byte of the word, to handle mid-word truncation + if (j != pc + 3) { + out.print(','); + } + int b0 = byteCode[j - 1]; // we want the sign extension, so no `& 0xff` + int b1 = byteCode[j] & 0xff; + out.print(b0 << 8 | b1); + } + if (byteCode.length < calculatedTableEnd) { + out.print(""); + } + pc += tableSize * 2 + 4; + out.print(")\n"); + } + } else if (ci.opcode() == RelativeJumpOperation.OPCODE + || ci.opcode() == RelativeJumpIfOperation.OPCODE) { + if (pc + 1 >= byteCode.length) { + out.printf(" %02x # [%d] %s()", byteCode[pc], pc, ci.name()); + } else if (pc + 2 >= byteCode.length) { + out.printf( + " %02x%02x # [%d] %s()", + byteCode[pc], byteCode[pc + 1], pc, ci.name()); + } else { + int b0 = byteCode[pc + 1] & 0xff; + int b1 = byteCode[pc + 2] & 0xff; + short delta = (short) (b0 << 8 | b1); + out.printf("%02x%02x%02x # [%d] %s(%d)", byteCode[pc], b0, b1, pc, ci.name(), delta); + } + pc += 3; + out.printf("%n"); + } else if (ci.opcode() == ExchangeOperation.OPCODE) { + if (pc + 1 >= byteCode.length) { + out.printf(" %02x # [%d] %s()", byteCode[pc], pc, ci.name()); + } else { + int imm = byteCode[pc + 1] & 0xff; + out.printf( + " %02x%02x # [%d] %s(%d, %d)", + byteCode[pc], imm, pc, ci.name(), imm >> 4, imm & 0x0F); + } + pc += 2; + out.printf("%n"); + } else { + int advance = ci.pcAdvance(); + if (advance == 1) { + out.print(" "); + } else if (advance == 2) { + out.print(" "); + } + out.printf("%02x", byteCode[pc]); + for (int j = 1; j < advance && (pc + j) < byteCode.length; j++) { + out.printf("%02x", byteCode[pc + j]); + } + out.printf(" # [%d] %s", pc, ci.name()); + if (advance == 2) { + if (byteCode.length <= pc + 1) { + out.print("()"); + } else { + out.printf("(%d)", byteCode[pc + 1] & 0xff); + } + } else if (advance > 2) { + out.print("(0x"); + for (int j = 1; j < advance && (pc + j) < byteCode.length; j++) { + out.printf("%02x", byteCode[pc + j]); + } + if ((pc + advance) >= byteCode.length) { + out.print(" "); + } + out.print(")"); + } + out.printf("%n"); + pc += advance; + } + } + } + + for (int i = 0; i < subContainers.length; i++) { + var subContainer = subContainers[i]; + out.print(prefix); + out.printf(" # Subcontainer %s%d starts here%n", subcontainerPrefix, i); + + subContainer.prettyPrint(out, prefix + " ", subcontainerPrefix + i + "."); + out.print(prefix); + out.printf(" # Subcontainer %s%d ends%n", subcontainerPrefix, i); + } + + out.print(prefix); + if (data.isEmpty()) { + out.print(" # Data section (empty)\n"); + } else { + out.printf(" # Data section length ( %1$d )", dataLength); + if (dataLength != data.size()) { + out.printf(" actual length ( %d )", data.size()); + } + out.printf("%n%s %s%n", prefix, data.toUnprefixedHexString()); + } + out.flush(); + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/EOFValidator.java b/evm/src/main/java/org/hyperledger/besu/evm/code/EOFValidator.java new file mode 100644 index 00000000000..3cbdb404f0c --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/EOFValidator.java @@ -0,0 +1,49 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.code; + +import javax.annotation.Nullable; + +/** Code Validation */ +public interface EOFValidator { + + /** + * Validates the code and stack for the EOF Layout, with optional deep consideration of the + * containers. + * + * @param layout The parsed EOFLayout of the code + * @return either null, indicating no error, or a String describing the validation error. + */ + @Nullable + String validate(final EOFLayout layout); + + /** + * Performs code validation of the EOF layout. + * + * @param eofLayout The EOF Layout + * @return validation code, null otherwise. + */ + @Nullable + String validateCode(final EOFLayout eofLayout); + + /** + * Performs stack validation of the EOF layout. Presumes that code validation has been perfromed + * + * @param eofLayout The EOF Layout + * @return validation code, null otherwise. + */ + @Nullable + String validateStack(final EOFLayout eofLayout); +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/OpcodeInfo.java b/evm/src/main/java/org/hyperledger/besu/evm/code/OpcodeInfo.java new file mode 100644 index 00000000000..27289f91203 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/OpcodeInfo.java @@ -0,0 +1,336 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.code; + +import com.google.common.base.Preconditions; + +/** + * Information about opcodes. Currently merges Legacy and EOFv1 + * + * @param name formal name of the opcode, such as STOP + * @param opcode the number of the opcode + * @param valid Is this a valid opcode (from an EOFV1 perspective) + * @param terminal Is this opcode terminal? (i.e. can it end a code section) + * @param inputs How many stack inputs are required/consumed? + * @param outputs How many stack items will be output? + * @param stackDelta What is the net difference in stack height from this operation + * @param pcAdvance How far should the PC advance (0 for terminal only, 1 for most, 2+ for opcodes + * with immediates) + */ +public record OpcodeInfo( + String name, + int opcode, + boolean valid, + boolean terminal, + int inputs, + int outputs, + int stackDelta, + int pcAdvance) { + static OpcodeInfo unallocatedOpcode(final int opcode) { + return new OpcodeInfo("-", opcode, false, false, 0, 0, 0, 1); + } + + static OpcodeInfo invalidOpcode(final String name, final int opcode) { + return new OpcodeInfo(name, opcode, false, false, 0, 0, 0, 1); + } + + static OpcodeInfo terminalOpcode( + final String name, + final int opcode, + final int inputs, + final int outputs, + final int pcAdvance) { + return new OpcodeInfo(name, opcode, true, true, inputs, outputs, outputs - inputs, pcAdvance); + } + + static OpcodeInfo validOpcode( + final String name, + final int opcode, + final int inputs, + final int outputs, + final int pcAdvance) { + return new OpcodeInfo(name, opcode, true, false, inputs, outputs, outputs - inputs, pcAdvance); + } + + /** + * Gets the opcode info for a specific opcode + * + * @param i opcode + * @return the OpcodeInfo object describing that opcode + */ + public static OpcodeInfo getOpcode(final int i) { + Preconditions.checkArgument(i >= 0 && i <= 255); + return V1_OPCODES[i]; + } + + static final OpcodeInfo[] V1_OPCODES = { + OpcodeInfo.terminalOpcode("STOP", 0x00, 0, 0, 1), + OpcodeInfo.validOpcode("ADD", 0x01, 2, 1, 1), + OpcodeInfo.validOpcode("MUL", 0x02, 2, 1, 1), + OpcodeInfo.validOpcode("SUB", 0x03, 2, 1, 1), + OpcodeInfo.validOpcode("DIV", 0x04, 2, 1, 1), + OpcodeInfo.validOpcode("SDIV", 0x05, 2, 1, 1), + OpcodeInfo.validOpcode("MOD", 0x06, 2, 1, 1), + OpcodeInfo.validOpcode("SMOD", 0x07, 2, 1, 1), + OpcodeInfo.validOpcode("ADDMOD", 0x08, 3, 1, 1), + OpcodeInfo.validOpcode("MULMOD", 0x09, 3, 1, 1), + OpcodeInfo.validOpcode("EXP", 0x0a, 2, 1, 1), + OpcodeInfo.validOpcode("SIGNEXTEND", 0x0b, 2, 1, 1), + OpcodeInfo.unallocatedOpcode(0x0c), + OpcodeInfo.unallocatedOpcode(0x0d), + OpcodeInfo.unallocatedOpcode(0x0e), + OpcodeInfo.unallocatedOpcode(0x0f), + OpcodeInfo.validOpcode("LT", 0x10, 2, 1, 1), + OpcodeInfo.validOpcode("GT", 0x11, 2, 1, 1), + OpcodeInfo.validOpcode("SLT", 0x12, 2, 1, 1), + OpcodeInfo.validOpcode("SGT", 0x13, 2, 1, 1), + OpcodeInfo.validOpcode("EQ", 0x14, 2, 1, 1), + OpcodeInfo.validOpcode("ISZERO", 0x15, 1, 1, 1), + OpcodeInfo.validOpcode("AND", 0x16, 2, 1, 1), + OpcodeInfo.validOpcode("OR", 0x17, 2, 1, 1), + OpcodeInfo.validOpcode("XOR", 0x18, 2, 1, 1), + OpcodeInfo.validOpcode("NOT", 0x19, 1, 1, 1), + OpcodeInfo.validOpcode("BYTE", 0x1a, 2, 1, 1), + OpcodeInfo.validOpcode("SHL", 0x1b, 2, 1, 1), + OpcodeInfo.validOpcode("SHR", 0x1c, 2, 1, 1), + OpcodeInfo.validOpcode("SAR", 0x1d, 2, 1, 1), + OpcodeInfo.unallocatedOpcode(0x1e), + OpcodeInfo.unallocatedOpcode(0x1f), + OpcodeInfo.validOpcode("SHA3", 0x20, 2, 1, 1), + OpcodeInfo.unallocatedOpcode(0x21), + OpcodeInfo.unallocatedOpcode(0x22), + OpcodeInfo.unallocatedOpcode(0x23), + OpcodeInfo.unallocatedOpcode(0x24), + OpcodeInfo.unallocatedOpcode(0x25), + OpcodeInfo.unallocatedOpcode(0x26), + OpcodeInfo.unallocatedOpcode(0x27), + OpcodeInfo.unallocatedOpcode(0x28), + OpcodeInfo.unallocatedOpcode(0x29), + OpcodeInfo.unallocatedOpcode(0x2a), + OpcodeInfo.unallocatedOpcode(0x2b), + OpcodeInfo.unallocatedOpcode(0x2c), + OpcodeInfo.unallocatedOpcode(0x2d), + OpcodeInfo.unallocatedOpcode(0x2e), + OpcodeInfo.unallocatedOpcode(0x2f), + OpcodeInfo.validOpcode("ADDRESS", 0x30, 0, 1, 1), + OpcodeInfo.validOpcode("BALANCE", 0x31, 1, 1, 1), + OpcodeInfo.validOpcode("ORIGIN", 0x32, 0, 1, 1), + OpcodeInfo.validOpcode("CALLER", 0x33, 0, 1, 1), + OpcodeInfo.validOpcode("CALLVALUE", 0x34, 0, 1, 1), + OpcodeInfo.validOpcode("CALLDATALOAD", 0x35, 1, 1, 1), + OpcodeInfo.validOpcode("CALLDATASIZE", 0x36, 0, 1, 1), + OpcodeInfo.validOpcode("CALLDATACOPY", 0x37, 3, 0, 1), + OpcodeInfo.invalidOpcode("CODESIZE", 0x38), + OpcodeInfo.invalidOpcode("CODECOPY", 0x39), + OpcodeInfo.validOpcode("GASPRICE", 0x3a, 0, 1, 1), + OpcodeInfo.invalidOpcode("EXTCODESIZE", 0x3b), + OpcodeInfo.invalidOpcode("EXTCODECOPY", 0x3c), + OpcodeInfo.validOpcode("RETURNDATASIZE", 0x3d, 0, 1, 1), + OpcodeInfo.validOpcode("RETURNDATACOPY", 0x3e, 3, 0, 1), + OpcodeInfo.invalidOpcode("EXTCODEHASH", 0x3f), + OpcodeInfo.validOpcode("BLOCKHASH", 0x40, 1, 1, 1), + OpcodeInfo.validOpcode("COINBASE", 0x41, 0, 1, 1), + OpcodeInfo.validOpcode("TIMESTAMP", 0x42, 0, 1, 1), + OpcodeInfo.validOpcode("NUMBER", 0x43, 0, 1, 1), + OpcodeInfo.validOpcode("PREVRANDAO", 0x44, 0, 1, 1), // was DIFFICULTY + OpcodeInfo.validOpcode("GASLIMIT", 0x45, 0, 1, 1), + OpcodeInfo.validOpcode("CHAINID", 0x46, 0, 1, 1), + OpcodeInfo.validOpcode("SELFBALANCE", 0x47, 0, 1, 1), + OpcodeInfo.validOpcode("BASEFEE", 0x48, 0, 1, 1), + OpcodeInfo.validOpcode("BLOBAHASH", 0x49, 1, 1, 1), + OpcodeInfo.validOpcode("BLOBBASEFEE", 0x4a, 0, 1, 1), + OpcodeInfo.unallocatedOpcode(0x4b), + OpcodeInfo.unallocatedOpcode(0x4c), + OpcodeInfo.unallocatedOpcode(0x4d), + OpcodeInfo.unallocatedOpcode(0x4e), + OpcodeInfo.unallocatedOpcode(0x4f), + OpcodeInfo.validOpcode("POP", 0x50, 1, 0, 1), + OpcodeInfo.validOpcode("MLOAD", 0x51, 1, 1, 1), + OpcodeInfo.validOpcode("MSTORE", 0x52, 2, 0, 1), + OpcodeInfo.validOpcode("MSTORE8", 0x53, 2, 0, 1), + OpcodeInfo.validOpcode("SLOAD", 0x54, 1, 1, 1), + OpcodeInfo.validOpcode("SSTORE", 0x55, 2, 0, 1), + OpcodeInfo.invalidOpcode("JUMP", 0x56), + OpcodeInfo.invalidOpcode("JUMPI", 0x57), + OpcodeInfo.invalidOpcode("PC", 0x58), + OpcodeInfo.validOpcode("MSIZE", 0x59, 0, 1, 1), + OpcodeInfo.invalidOpcode("GAS", 0x5a), + OpcodeInfo.validOpcode("NOOP", 0x5b, 0, 0, 1), // was JUMPDEST + OpcodeInfo.validOpcode("TLOAD", 0x5c, 1, 1, 1), + OpcodeInfo.validOpcode("TSTORE", 0x5d, 2, 0, 1), + OpcodeInfo.validOpcode("MCOPY", 0x5e, 3, 0, 1), + OpcodeInfo.validOpcode("PUSH0", 0x5f, 0, 1, 1), + OpcodeInfo.validOpcode("PUSH1", 0x60, 0, 1, 2), + OpcodeInfo.validOpcode("PUSH2", 0x61, 0, 1, 3), + OpcodeInfo.validOpcode("PUSH3", 0x62, 0, 1, 4), + OpcodeInfo.validOpcode("PUSH4", 0x63, 0, 1, 5), + OpcodeInfo.validOpcode("PUSH5", 0x64, 0, 1, 6), + OpcodeInfo.validOpcode("PUSH6", 0x65, 0, 1, 7), + OpcodeInfo.validOpcode("PUSH7", 0x66, 0, 1, 8), + OpcodeInfo.validOpcode("PUSH8", 0x67, 0, 1, 9), + OpcodeInfo.validOpcode("PUSH9", 0x68, 0, 1, 10), + OpcodeInfo.validOpcode("PUSH10", 0x69, 0, 1, 11), + OpcodeInfo.validOpcode("PUSH11", 0x6a, 0, 1, 12), + OpcodeInfo.validOpcode("PUSH12", 0x6b, 0, 1, 13), + OpcodeInfo.validOpcode("PUSH13", 0x6c, 0, 1, 14), + OpcodeInfo.validOpcode("PUSH14", 0x6d, 0, 1, 15), + OpcodeInfo.validOpcode("PUSH15", 0x6e, 0, 1, 16), + OpcodeInfo.validOpcode("PUSH16", 0x6f, 0, 1, 17), + OpcodeInfo.validOpcode("PUSH17", 0x70, 0, 1, 18), + OpcodeInfo.validOpcode("PUSH18", 0x71, 0, 1, 19), + OpcodeInfo.validOpcode("PUSH19", 0x72, 0, 1, 20), + OpcodeInfo.validOpcode("PUSH20", 0x73, 0, 1, 21), + OpcodeInfo.validOpcode("PUSH21", 0x74, 0, 1, 22), + OpcodeInfo.validOpcode("PUSH22", 0x75, 0, 1, 23), + OpcodeInfo.validOpcode("PUSH23", 0x76, 0, 1, 24), + OpcodeInfo.validOpcode("PUSH24", 0x77, 0, 1, 25), + OpcodeInfo.validOpcode("PUSH25", 0x78, 0, 1, 26), + OpcodeInfo.validOpcode("PUSH26", 0x79, 0, 1, 27), + OpcodeInfo.validOpcode("PUSH27", 0x7a, 0, 1, 28), + OpcodeInfo.validOpcode("PUSH28", 0x7b, 0, 1, 29), + OpcodeInfo.validOpcode("PUSH29", 0x7c, 0, 1, 30), + OpcodeInfo.validOpcode("PUSH30", 0x7d, 0, 1, 31), + OpcodeInfo.validOpcode("PUSH31", 0x7e, 0, 1, 32), + OpcodeInfo.validOpcode("PUSH32", 0x7f, 0, 1, 33), + OpcodeInfo.validOpcode("DUP1", 0x80, 1, 2, 1), + OpcodeInfo.validOpcode("DUP2", 0x81, 2, 3, 1), + OpcodeInfo.validOpcode("DUP3", 0x82, 3, 4, 1), + OpcodeInfo.validOpcode("DUP4", 0x83, 4, 5, 1), + OpcodeInfo.validOpcode("DUP5", 0x84, 5, 6, 1), + OpcodeInfo.validOpcode("DUP6", 0x85, 6, 7, 1), + OpcodeInfo.validOpcode("DUP7", 0x86, 7, 8, 1), + OpcodeInfo.validOpcode("DUP8", 0x87, 8, 9, 1), + OpcodeInfo.validOpcode("DUP9", 0x88, 9, 10, 1), + OpcodeInfo.validOpcode("DUP10", 0x89, 10, 11, 1), + OpcodeInfo.validOpcode("DUP11", 0x8a, 11, 12, 1), + OpcodeInfo.validOpcode("DUP12", 0x8b, 12, 13, 1), + OpcodeInfo.validOpcode("DUP13", 0x8c, 13, 14, 1), + OpcodeInfo.validOpcode("DUP14", 0x8d, 14, 15, 1), + OpcodeInfo.validOpcode("DUP15", 0x8e, 15, 16, 1), + OpcodeInfo.validOpcode("DUP16", 0x8f, 16, 17, 1), + OpcodeInfo.validOpcode("SWAP1", 0x90, 2, 2, 1), + OpcodeInfo.validOpcode("SWAP2", 0x91, 3, 3, 1), + OpcodeInfo.validOpcode("SWAP3", 0x92, 4, 4, 1), + OpcodeInfo.validOpcode("SWAP4", 0x93, 5, 5, 1), + OpcodeInfo.validOpcode("SWAP5", 0x94, 6, 6, 1), + OpcodeInfo.validOpcode("SWAP6", 0x95, 7, 7, 1), + OpcodeInfo.validOpcode("SWAP7", 0x96, 8, 8, 1), + OpcodeInfo.validOpcode("SWAP8", 0x97, 9, 9, 1), + OpcodeInfo.validOpcode("SWAP9", 0x98, 10, 10, 1), + OpcodeInfo.validOpcode("SWAP10", 0x99, 11, 11, 1), + OpcodeInfo.validOpcode("SWAP11", 0x9a, 12, 12, 1), + OpcodeInfo.validOpcode("SWAP12", 0x9b, 13, 13, 1), + OpcodeInfo.validOpcode("SWAP13", 0x9c, 14, 14, 1), + OpcodeInfo.validOpcode("SWAP14", 0x9d, 15, 15, 1), + OpcodeInfo.validOpcode("SWAP15", 0x9e, 16, 16, 1), + OpcodeInfo.validOpcode("SWAP16", 0x9f, 17, 17, 1), + OpcodeInfo.validOpcode("LOG0", 0xa0, 2, 0, 1), + OpcodeInfo.validOpcode("LOG1", 0xa1, 3, 0, 1), + OpcodeInfo.validOpcode("LOG2", 0xa2, 4, 0, 1), + OpcodeInfo.validOpcode("LOG3", 0xa3, 5, 0, 1), + OpcodeInfo.validOpcode("LOG4", 0xa4, 6, 0, 1), + OpcodeInfo.unallocatedOpcode(0xa5), + OpcodeInfo.unallocatedOpcode(0xa6), + OpcodeInfo.unallocatedOpcode(0xa7), + OpcodeInfo.unallocatedOpcode(0xa8), + OpcodeInfo.unallocatedOpcode(0xa9), + OpcodeInfo.unallocatedOpcode(0xaa), + OpcodeInfo.unallocatedOpcode(0xab), + OpcodeInfo.unallocatedOpcode(0xac), + OpcodeInfo.unallocatedOpcode(0xad), + OpcodeInfo.unallocatedOpcode(0xae), + OpcodeInfo.unallocatedOpcode(0xaf), + OpcodeInfo.unallocatedOpcode(0xb0), + OpcodeInfo.unallocatedOpcode(0xb1), + OpcodeInfo.unallocatedOpcode(0xb2), + OpcodeInfo.unallocatedOpcode(0xb3), + OpcodeInfo.unallocatedOpcode(0xb4), + OpcodeInfo.unallocatedOpcode(0xb5), + OpcodeInfo.unallocatedOpcode(0xb6), + OpcodeInfo.unallocatedOpcode(0xb7), + OpcodeInfo.unallocatedOpcode(0xb8), + OpcodeInfo.unallocatedOpcode(0xb9), + OpcodeInfo.unallocatedOpcode(0xba), + OpcodeInfo.unallocatedOpcode(0xbb), + OpcodeInfo.unallocatedOpcode(0xbc), + OpcodeInfo.unallocatedOpcode(0xbd), + OpcodeInfo.unallocatedOpcode(0xbe), + OpcodeInfo.unallocatedOpcode(0xbf), + OpcodeInfo.unallocatedOpcode(0xc0), + OpcodeInfo.unallocatedOpcode(0xc1), + OpcodeInfo.unallocatedOpcode(0xc2), + OpcodeInfo.unallocatedOpcode(0xc3), + OpcodeInfo.unallocatedOpcode(0xc4), + OpcodeInfo.unallocatedOpcode(0xc5), + OpcodeInfo.unallocatedOpcode(0xc6), + OpcodeInfo.unallocatedOpcode(0xc7), + OpcodeInfo.unallocatedOpcode(0xc8), + OpcodeInfo.unallocatedOpcode(0xc9), + OpcodeInfo.unallocatedOpcode(0xca), + OpcodeInfo.unallocatedOpcode(0xcb), + OpcodeInfo.unallocatedOpcode(0xcc), + OpcodeInfo.unallocatedOpcode(0xcd), + OpcodeInfo.unallocatedOpcode(0xce), + OpcodeInfo.unallocatedOpcode(0xcf), + OpcodeInfo.validOpcode("DATALOAD", 0xd0, 1, 1, 1), + OpcodeInfo.validOpcode("DATALOADN", 0xd1, 0, 1, 3), + OpcodeInfo.validOpcode("DATASIZE", 0xd2, 0, 1, 1), + OpcodeInfo.validOpcode("DATACOPY", 0xd3, 3, 0, 1), + OpcodeInfo.unallocatedOpcode(0xd4), + OpcodeInfo.unallocatedOpcode(0xd5), + OpcodeInfo.unallocatedOpcode(0xd6), + OpcodeInfo.unallocatedOpcode(0xd7), + OpcodeInfo.unallocatedOpcode(0xd8), + OpcodeInfo.unallocatedOpcode(0xd9), + OpcodeInfo.unallocatedOpcode(0xda), + OpcodeInfo.unallocatedOpcode(0xdb), + OpcodeInfo.unallocatedOpcode(0xdc), + OpcodeInfo.unallocatedOpcode(0xdd), + OpcodeInfo.unallocatedOpcode(0xde), + OpcodeInfo.unallocatedOpcode(0xdf), + OpcodeInfo.terminalOpcode("RJUMP", 0xe0, 0, 0, 3), + OpcodeInfo.validOpcode("RJUMPI", 0xe1, 1, 0, 3), + OpcodeInfo.validOpcode("RJUMPV", 0xe2, 1, 0, 2), + OpcodeInfo.validOpcode("CALLF", 0xe3, 0, 0, 3), + OpcodeInfo.terminalOpcode("RETF", 0xe4, 0, 0, 1), + OpcodeInfo.terminalOpcode("JUMPF", 0xe5, 0, 0, 3), + OpcodeInfo.validOpcode("DUPN", 0xe6, 0, 1, 2), + OpcodeInfo.validOpcode("SWAPN", 0xe7, 0, 0, 2), + OpcodeInfo.validOpcode("EXCHANGE", 0xe8, 0, 0, 2), + OpcodeInfo.unallocatedOpcode(0xe9), + OpcodeInfo.unallocatedOpcode(0xea), + OpcodeInfo.unallocatedOpcode(0xeb), + OpcodeInfo.validOpcode("EOFCREATE", 0xec, 4, 1, 2), + OpcodeInfo.unallocatedOpcode(0xed), + OpcodeInfo.terminalOpcode("RETURNCONTRACT", 0xee, 2, 1, 2), + OpcodeInfo.unallocatedOpcode(0xef), + OpcodeInfo.invalidOpcode("CREATE", 0xf0), + OpcodeInfo.invalidOpcode("CALL", 0xf1), + OpcodeInfo.invalidOpcode("CALLCODE", 0xf2), + OpcodeInfo.terminalOpcode("RETURN", 0xf3, 2, 0, 1), + OpcodeInfo.invalidOpcode("DELEGATECALL", 0xf4), + OpcodeInfo.invalidOpcode("CREATE2", 0xf5), + OpcodeInfo.unallocatedOpcode(0xf6), + OpcodeInfo.validOpcode("RETURNDATALOAD", 0xf7, 1, 1, 1), + OpcodeInfo.validOpcode("EXTCALL", 0xf8, 4, 1, 1), + OpcodeInfo.validOpcode("EXTDELEGATECALL", 0xf9, 3, 1, 1), + OpcodeInfo.invalidOpcode("STATICCALL", 0xfa), + OpcodeInfo.validOpcode("EXTSTATICCALL", 0xfb, 3, 1, 1), + OpcodeInfo.unallocatedOpcode(0xfc), + OpcodeInfo.terminalOpcode("REVERT", 0xfd, 2, 0, 1), + OpcodeInfo.terminalOpcode("INVALID", 0xfe, 0, 0, 1), + OpcodeInfo.invalidOpcode("SELFDESTRUCT", 0xff), + }; +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/code/WorkList.java b/evm/src/main/java/org/hyperledger/besu/evm/code/WorkList.java new file mode 100644 index 00000000000..0e30b35df8c --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/code/WorkList.java @@ -0,0 +1,95 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.code; + +/** + * A work list, allowing a DAG to be evaluated while detecting disconnected sections. + * + *

    When an item is marked if it has not been marked it is added to the work list. `take()` + * returns the fist item that has not yet been returned from a take, or `-1` if no items are + * available. Items are added by calling `put(int)`, which is idempotent. Items can be put several + * times but will only be taken once. + * + *

    `isComplete()` checks if all items have been taken. `getFirstUnmarkedItem()` is used when + * reporting errors to identify an unconnected item. + */ +class WorkList { + boolean[] marked; + int[] items; + int nextIndex; + int listEnd; + + /** + * Create a work list of the appropriate size. The list is empty. + * + * @param size number of possible items + */ + WorkList(final int size) { + marked = new boolean[size]; + items = new int[size]; + nextIndex = 0; + listEnd = -1; + } + + /** + * Take the next item, if available + * + * @return the item number, or -1 if no items are available. + */ + int take() { + if (nextIndex > listEnd) { + return -1; + } + int result = items[nextIndex]; + nextIndex++; + return result; + } + + /** + * Have all items been taken? + * + * @return true if all items were marked and then taken + */ + boolean isComplete() { + return nextIndex >= items.length; + } + + /** + * Put an item in the work list. This is idempotent, an item will only be added on the first call. + * + * @param item the item to add to the list. + */ + void put(final int item) { + if (!marked[item]) { + listEnd++; + items[listEnd] = item; + marked[item] = true; + } + } + + /** + * Walks the taken list and returns the first unmarked item + * + * @return the first unmarked item, or -1 if all items are marked. + */ + int getFirstUnmarkedItem() { + for (int i = 0; i < marked.length; i++) { + if (!marked[i]) { + return i; + } + } + return -1; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/CachedInvalidCodeRule.java b/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/CachedInvalidCodeRule.java deleted file mode 100644 index 34c14ec8a7a..00000000000 --- a/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/CachedInvalidCodeRule.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright contributors to Hyperledger Besu - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * - */ - -package org.hyperledger.besu.evm.contractvalidation; - -import org.hyperledger.besu.evm.Code; -import org.hyperledger.besu.evm.EvmSpecVersion; -import org.hyperledger.besu.evm.code.CodeFactory; -import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; -import org.hyperledger.besu.evm.frame.MessageFrame; - -import java.util.Optional; - -import org.apache.tuweni.bytes.Bytes; - -/** The Cached invalid code rule. */ -public class CachedInvalidCodeRule implements ContractValidationRule { - - private final int maxEofVersion; - - /** - * Instantiates a new Cached invalid code rule. - * - * @param maxEofVersion the max eof version - */ - public CachedInvalidCodeRule(final int maxEofVersion) { - this.maxEofVersion = maxEofVersion; - } - - @Override - public Optional validate( - final Bytes contractCode, final MessageFrame frame) { - final Code code = CodeFactory.createCode(contractCode, maxEofVersion, false); - if (!code.isValid()) { - return Optional.of(ExceptionalHaltReason.INVALID_CODE); - } else { - return Optional.empty(); - } - } - - /** - * Instantiate contract validation rule. - * - * @param specVersion The evm spec version - * @return the contract validation rule - */ - public static ContractValidationRule of(final EvmSpecVersion specVersion) { - return new CachedInvalidCodeRule(specVersion.getMaxEofVersion()); - } -} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/ContractValidationRule.java b/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/ContractValidationRule.java index a32718e7494..c5c61dd7438 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/ContractValidationRule.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/ContractValidationRule.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.evm.contractvalidation; +import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -30,7 +31,8 @@ public interface ContractValidationRule { * * @param contractCode the contract code to validate * @param frame the message frame to use for context + * @param evm the EVM against which the validation rule should be considered * @return the optional halt reason */ - Optional validate(Bytes contractCode, MessageFrame frame); + Optional validate(Bytes contractCode, MessageFrame frame, EVM evm); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/EOFValidationCodeRule.java b/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/EOFValidationCodeRule.java index 3481035ee0f..e61a006f0e7 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/EOFValidationCodeRule.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/EOFValidationCodeRule.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,8 +14,9 @@ */ package org.hyperledger.besu.evm.contractvalidation; +import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.evm.Code; -import org.hyperledger.besu.evm.code.CodeFactory; +import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.code.CodeInvalid; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -35,11 +36,9 @@ public class EOFValidationCodeRule implements ContractValidationRule { private static final Logger LOG = LoggerFactory.getLogger(EOFValidationCodeRule.class); final int maxEofVersion; - final boolean inCreateTransaction; - private EOFValidationCodeRule(final int maxEofVersion, final boolean inCreateTransaction) { + private EOFValidationCodeRule(final int maxEofVersion) { this.maxEofVersion = maxEofVersion; - this.inCreateTransaction = inCreateTransaction; } /** @@ -47,19 +46,20 @@ private EOFValidationCodeRule(final int maxEofVersion, final boolean inCreateTra * * @param contractCode the contract code to validate * @param frame the message frame to use for context + * @param evm The EVM against which the validation should be considered against * @return Either an empty optional on success, or an optional containing one of the invalid * reasons. */ @Override public Optional validate( - final Bytes contractCode, final MessageFrame frame) { - Code code = CodeFactory.createCode(contractCode, maxEofVersion, inCreateTransaction); + final Bytes contractCode, final MessageFrame frame, final EVM evm) { + Code code = evm.getCode(Hash.hash(contractCode), contractCode); if (!code.isValid()) { LOG.trace("EOF Validation Error: {}", ((CodeInvalid) code).getInvalidReason()); return Optional.of(ExceptionalHaltReason.INVALID_CODE); } - if (frame.getCode().getEofVersion() > code.getEofVersion()) { + if (frame.getCode().getEofVersion() != code.getEofVersion()) { LOG.trace( "Cannot deploy older eof versions: initcode version - {} runtime code version - {}", frame.getCode().getEofVersion(), @@ -74,11 +74,21 @@ public Optional validate( * Create EOF validation. * * @param maxEofVersion Maximum EOF version to validate - * @param inCreateTransaction Is this inside a create transaction? * @return The EOF validation contract validation rule. + * @deprecated use {@link #from(EVM)} */ - public static ContractValidationRule of( - final int maxEofVersion, final boolean inCreateTransaction) { - return new EOFValidationCodeRule(maxEofVersion, inCreateTransaction); + @Deprecated(forRemoval = true, since = "24.6.1") + public static ContractValidationRule of(final int maxEofVersion) { + return new EOFValidationCodeRule(maxEofVersion); + } + + /** + * Create EOF validation. + * + * @param evm The EVM for which we are enforcing the rule + * @return The EOF validation contract validation rule. + */ + public static ContractValidationRule from(final EVM evm) { + return new EOFValidationCodeRule(evm.getMaxEOFVersion()); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/MaxCodeSizeRule.java b/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/MaxCodeSizeRule.java index 9a82e89a6fa..9fd7cb801da 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/MaxCodeSizeRule.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/MaxCodeSizeRule.java @@ -14,8 +14,11 @@ */ package org.hyperledger.besu.evm.contractvalidation; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.EvmSpecVersion; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import java.util.Optional; @@ -41,7 +44,7 @@ public class MaxCodeSizeRule implements ContractValidationRule { @Override public Optional validate( - final Bytes contractCode, final MessageFrame frame) { + final Bytes contractCode, final MessageFrame frame, final EVM evm) { final int contractCodeSize = contractCode.size(); if (contractCodeSize <= maxCodeSize) { return Optional.empty(); @@ -55,12 +58,37 @@ public Optional validate( } /** - * Instantiate ContractValidationRule. + * Fluent MaxCodeSizeRule constructor of an explicit size. * * @param maxCodeSize the max code size * @return the contract validation rule + * @deprecated use {@link #from(EVM)} */ + @Deprecated(forRemoval = true, since = "24.6.1") public static ContractValidationRule of(final int maxCodeSize) { return new MaxCodeSizeRule(maxCodeSize); } + + /** + * Fluent MaxCodeSizeRule from the EVM it is working with. + * + * @param evm The evm to get the size rules from. + * @return the contract validation rule + */ + public static ContractValidationRule from(final EVM evm) { + return from(evm.getEvmVersion(), evm.getEvmConfiguration()); + } + + /** + * Fluent MaxCodeSizeRule from the EVM it is working with. + * + * @param evmspec The evm spec version to get the size rules from. + * @param evmConfiguration The evm configuration, including overrides + * @return the contract validation rule + */ + public static ContractValidationRule from( + final EvmSpecVersion evmspec, final EvmConfiguration evmConfiguration) { + return new MaxCodeSizeRule( + evmConfiguration.maxCodeSizeOverride().orElse(evmspec.getMaxCodeSize())); + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/PrefixCodeRule.java b/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/PrefixCodeRule.java index d6be76d599a..b19c4729313 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/PrefixCodeRule.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/contractvalidation/PrefixCodeRule.java @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.evm.contractvalidation; +import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -30,10 +31,15 @@ public class PrefixCodeRule implements ContractValidationRule { private static final byte FORMAT_RESERVED = (byte) 0xEF; + /** Default constructor. */ + public PrefixCodeRule() { + // This constructor exists because of JavaDoc linting rules. + } + @Override // As per https://eips.ethereum.org/EIPS/eip-3541 public Optional validate( - final Bytes contractCode, final MessageFrame frame) { + final Bytes contractCode, final MessageFrame frame, final EVM evm) { if (!contractCode.isEmpty() && contractCode.get(0) == FORMAT_RESERVED) { LOG.trace("Contract creation error: code cannot start with {}", FORMAT_RESERVED); return Optional.of(ExceptionalHaltReason.INVALID_CODE); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/fluent/EVMExecutor.java b/evm/src/main/java/org/hyperledger/besu/evm/fluent/EVMExecutor.java index 9946b6f57a8..a182ae9454d 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/fluent/EVMExecutor.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/fluent/EVMExecutor.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,6 +15,7 @@ package org.hyperledger.besu.evm.fluent; import static com.google.common.base.Preconditions.checkNotNull; +import static org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; import org.hyperledger.besu.collections.trie.BytesTrieSet; import org.hyperledger.besu.datatypes.Address; @@ -46,7 +47,6 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.function.Function; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; @@ -72,12 +72,14 @@ public class EVMExecutor { private Wei ethValue = Wei.ZERO; private Code code = CodeV0.EMPTY_CODE; private BlockValues blockValues = new SimpleBlockValues(); - private Function blockHashLookup = h -> null; + private BlockHashLookup blockHashLookup = n -> null; private Optional> versionedHashes = Optional.empty(); private OperationTracer tracer = OperationTracer.NO_TRACING; private boolean requireDeposit = true; private List contractValidationRules = - List.of(MaxCodeSizeRule.of(0x6000), PrefixCodeRule.of()); + List.of( + MaxCodeSizeRule.from(EvmSpecVersion.SPURIOUS_DRAGON, EvmConfiguration.DEFAULT), + PrefixCodeRule.of()); private long initialNonce = 1; private Collection

    forceCommitAddresses = List.of(Address.fromHexString("0x03")); private Set
    accessListWarmAddresses = new BytesTrieSet<>(Address.SIZE); @@ -160,9 +162,14 @@ public static EVMExecutor evm( case PARIS -> paris(chainId, evmConfiguration); case SHANGHAI -> shanghai(chainId, evmConfiguration); case CANCUN -> cancun(chainId, evmConfiguration); + case CANCUN_EOF -> cancunEOF(chainId, evmConfiguration); case PRAGUE -> prague(chainId, evmConfiguration); + case PRAGUE_EOF -> pragueEOF(chainId, evmConfiguration); case OSAKA -> osaka(chainId, evmConfiguration); + case AMSTERDAM -> amsterdam(chainId, evmConfiguration); case BOGOTA -> bogota(chainId, evmConfiguration); + case POLIS -> polis(chainId, evmConfiguration); + case BANGKOK -> bangkok(chainId, evmConfiguration); case FUTURE_EIPS -> futureEips(chainId, evmConfiguration); case EXPERIMENTAL_EIPS -> experimentalEips(chainId, evmConfiguration); }; @@ -221,7 +228,7 @@ public static EVMExecutor tangerineWhistle(final EvmConfiguration evmConfigurati final EVMExecutor executor = new EVMExecutor(MainnetEVMs.tangerineWhistle(evmConfiguration)); executor.precompileContractRegistry = MainnetPrecompiledContracts.frontier(executor.evm.getGasCalculator()); - executor.contractValidationRules = List.of(MaxCodeSizeRule.of(0x6000)); + executor.contractValidationRules = List.of(); executor.initialNonce = 0; return executor; } @@ -236,7 +243,7 @@ public static EVMExecutor spuriousDragon(final EvmConfiguration evmConfiguration final EVMExecutor executor = new EVMExecutor(MainnetEVMs.spuriousDragon(evmConfiguration)); executor.precompileContractRegistry = MainnetPrecompiledContracts.frontier(executor.evm.getGasCalculator()); - executor.contractValidationRules = List.of(MaxCodeSizeRule.of(0x6000)); + executor.contractValidationRules = List.of(MaxCodeSizeRule.from(executor.evm)); return executor; } @@ -250,7 +257,7 @@ public static EVMExecutor byzantium(final EvmConfiguration evmConfiguration) { final EVMExecutor executor = new EVMExecutor(MainnetEVMs.byzantium(evmConfiguration)); executor.precompileContractRegistry = MainnetPrecompiledContracts.byzantium(executor.evm.getGasCalculator()); - executor.contractValidationRules = List.of(MaxCodeSizeRule.of(0x6000)); + executor.contractValidationRules = List.of(MaxCodeSizeRule.from(executor.evm)); return executor; } @@ -264,7 +271,7 @@ public static EVMExecutor constantinople(final EvmConfiguration evmConfiguration final EVMExecutor executor = new EVMExecutor(MainnetEVMs.constantinople(evmConfiguration)); executor.precompileContractRegistry = MainnetPrecompiledContracts.byzantium(executor.evm.getGasCalculator()); - executor.contractValidationRules = List.of(MaxCodeSizeRule.of(0x6000)); + executor.contractValidationRules = List.of(MaxCodeSizeRule.from(executor.evm)); return executor; } @@ -278,7 +285,7 @@ public static EVMExecutor petersburg(final EvmConfiguration evmConfiguration) { final EVMExecutor executor = new EVMExecutor(MainnetEVMs.petersburg(evmConfiguration)); executor.precompileContractRegistry = MainnetPrecompiledContracts.byzantium(executor.evm.getGasCalculator()); - executor.contractValidationRules = List.of(MaxCodeSizeRule.of(0x6000)); + executor.contractValidationRules = List.of(MaxCodeSizeRule.from(executor.evm)); return executor; } @@ -313,7 +320,7 @@ public static EVMExecutor istanbul( final EVMExecutor executor = new EVMExecutor(MainnetEVMs.istanbul(chainId, evmConfiguration)); executor.precompileContractRegistry = MainnetPrecompiledContracts.istanbul(executor.evm.getGasCalculator()); - executor.contractValidationRules = List.of(MaxCodeSizeRule.of(0x6000)); + executor.contractValidationRules = List.of(MaxCodeSizeRule.from(executor.evm)); return executor; } @@ -348,7 +355,7 @@ public static EVMExecutor berlin( final EVMExecutor executor = new EVMExecutor(MainnetEVMs.berlin(chainId, evmConfiguration)); executor.precompileContractRegistry = MainnetPrecompiledContracts.istanbul(executor.evm.getGasCalculator()); - executor.contractValidationRules = List.of(MaxCodeSizeRule.of(0x6000)); + executor.contractValidationRules = List.of(MaxCodeSizeRule.from(executor.evm)); return executor; } @@ -488,6 +495,21 @@ public static EVMExecutor cancun( return executor; } + /** + * Instantiate Cancun EOF evm executor. + * + * @param chainId the chain ID + * @param evmConfiguration the evm configuration + * @return the evm executor + */ + public static EVMExecutor cancunEOF( + final BigInteger chainId, final EvmConfiguration evmConfiguration) { + final EVMExecutor executor = new EVMExecutor(MainnetEVMs.cancunEOF(chainId, evmConfiguration)); + executor.precompileContractRegistry = + MainnetPrecompiledContracts.cancun(executor.evm.getGasCalculator()); + return executor; + } + /** * Instantiate Prague evm executor. * @@ -499,7 +521,22 @@ public static EVMExecutor prague( final BigInteger chainId, final EvmConfiguration evmConfiguration) { final EVMExecutor executor = new EVMExecutor(MainnetEVMs.prague(chainId, evmConfiguration)); executor.precompileContractRegistry = - MainnetPrecompiledContracts.cancun(executor.evm.getGasCalculator()); + MainnetPrecompiledContracts.prague(executor.evm.getGasCalculator()); + return executor; + } + + /** + * Instantiate PragueEOF evm executor. + * + * @param chainId the chain ID + * @param evmConfiguration the evm configuration + * @return the evm executor + */ + public static EVMExecutor pragueEOF( + final BigInteger chainId, final EvmConfiguration evmConfiguration) { + final EVMExecutor executor = new EVMExecutor(MainnetEVMs.pragueEOF(chainId, evmConfiguration)); + executor.precompileContractRegistry = + MainnetPrecompiledContracts.prague(executor.evm.getGasCalculator()); return executor; } @@ -514,7 +551,22 @@ public static EVMExecutor osaka( final BigInteger chainId, final EvmConfiguration evmConfiguration) { final EVMExecutor executor = new EVMExecutor(MainnetEVMs.osaka(chainId, evmConfiguration)); executor.precompileContractRegistry = - MainnetPrecompiledContracts.cancun(executor.evm.getGasCalculator()); + MainnetPrecompiledContracts.prague(executor.evm.getGasCalculator()); + return executor; + } + + /** + * Instantiate Amsterdam evm executor. + * + * @param chainId the chain ID + * @param evmConfiguration the evm configuration + * @return the evm executor + */ + public static EVMExecutor amsterdam( + final BigInteger chainId, final EvmConfiguration evmConfiguration) { + final EVMExecutor executor = new EVMExecutor(MainnetEVMs.amsterdam(chainId, evmConfiguration)); + executor.precompileContractRegistry = + MainnetPrecompiledContracts.prague(executor.evm.getGasCalculator()); return executor; } @@ -529,7 +581,37 @@ public static EVMExecutor bogota( final BigInteger chainId, final EvmConfiguration evmConfiguration) { final EVMExecutor executor = new EVMExecutor(MainnetEVMs.bogota(chainId, evmConfiguration)); executor.precompileContractRegistry = - MainnetPrecompiledContracts.cancun(executor.evm.getGasCalculator()); + MainnetPrecompiledContracts.prague(executor.evm.getGasCalculator()); + return executor; + } + + /** + * Instantiate Polis evm executor. + * + * @param chainId the chain ID + * @param evmConfiguration the evm configuration + * @return the evm executor + */ + public static EVMExecutor polis( + final BigInteger chainId, final EvmConfiguration evmConfiguration) { + final EVMExecutor executor = new EVMExecutor(MainnetEVMs.polis(chainId, evmConfiguration)); + executor.precompileContractRegistry = + MainnetPrecompiledContracts.prague(executor.evm.getGasCalculator()); + return executor; + } + + /** + * Instantiate Bangkok evm executor. + * + * @param chainId the chain ID + * @param evmConfiguration the evm configuration + * @return the evm executor + */ + public static EVMExecutor bangkok( + final BigInteger chainId, final EvmConfiguration evmConfiguration) { + final EVMExecutor executor = new EVMExecutor(MainnetEVMs.bangkok(chainId, evmConfiguration)); + executor.precompileContractRegistry = + MainnetPrecompiledContracts.prague(executor.evm.getGasCalculator()); return executor; } @@ -540,6 +622,7 @@ public static EVMExecutor bogota( * @return the evm executor * @deprecated Migrate to use {@link EVMExecutor#evm(EvmSpecVersion)}. */ + @SuppressWarnings("DeprecatedIsStillUsed") @InlineMe( replacement = "EVMExecutor.evm(EvmSpecVersion.FUTURE_EIPS, BigInteger.ONE, evmConfiguration)", imports = { @@ -593,12 +676,7 @@ private ContractCreationProcessor thisContractCreationProcessor() { contractCreationProcessor, () -> new ContractCreationProcessor( - evm.getGasCalculator(), - evm, - requireDeposit, - contractValidationRules, - initialNonce, - forceCommitAddresses)); + evm, requireDeposit, contractValidationRules, initialNonce, forceCommitAddresses)); } /** @@ -672,12 +750,13 @@ public Bytes execute() { final Deque messageFrameStack = initialMessageFrame.getMessageFrameStack(); while (!messageFrameStack.isEmpty()) { final MessageFrame messageFrame = messageFrameStack.peek(); - if (messageFrame.getType() == MessageFrame.Type.CONTRACT_CREATION) { - ccp.process(messageFrame, tracer); - } else if (messageFrame.getType() == MessageFrame.Type.MESSAGE_CALL) { - mcp.process(messageFrame, tracer); - } + (switch (messageFrame.getType()) { + case CONTRACT_CREATION -> ccp; + case MESSAGE_CALL -> mcp; + }) + .process(messageFrame, tracer); } + initialMessageFrame.getSelfDestructs().forEach(worldUpdater::deleteAccount); if (commitWorldState) { worldUpdater.commit(); } @@ -971,7 +1050,7 @@ public EVMExecutor gasLimit(final long gasLimit) { * @param blockHashLookup the block hash lookup function * @return the evm executor */ - public EVMExecutor blockHashLookup(final Function blockHashLookup) { + public EVMExecutor blockHashLookup(final BlockHashLookup blockHashLookup) { this.blockHashLookup = blockHashLookup; return this; } @@ -1147,7 +1226,7 @@ public EvmSpecVersion getEVMVersion() { } /** - * Returns the ChaindD this executor is using + * Returns the ChainID this executor is using * * @return the current chain ID */ diff --git a/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleAccount.java b/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleAccount.java index edc3edd1a43..6ad10d37e4b 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleAccount.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleAccount.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.fluent; @@ -184,6 +183,17 @@ public Map getUpdatedStorage() { return storage; } + /** + * Does this account have any storage slots that are set to non-zero values? + * + * @return true if the account has no storage values set to non-zero values. False if any storage + * is set. + */ + @Override + public boolean isStorageEmpty() { + return storage.isEmpty(); + } + @Override public void becomeImmutable() { immutable = true; @@ -204,21 +214,4 @@ public boolean commit() { return false; } } - - /** - * Push changes into the parent account, if one exists - * - * @return true if a parent account was updated, false if not (this indicates the account should - * be inserted into the parent contact). - */ - public boolean updateParent() { - if (parent instanceof SimpleAccount simpleAccount) { - simpleAccount.balance = balance; - simpleAccount.nonce = nonce; - simpleAccount.storage.putAll(storage); - return true; - } else { - return false; - } - } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleBlockValues.java b/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleBlockValues.java index 8099e27babd..edd4f174807 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleBlockValues.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleBlockValues.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.fluent; @@ -33,6 +32,9 @@ public class SimpleBlockValues implements BlockValues { long timestamp = 1; long gasLimit = Long.MAX_VALUE; + /** Default constructor. */ + public SimpleBlockValues() {} + @Override public Bytes getDifficultyBytes() { return difficultyBytes; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleWorld.java b/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleWorld.java index a9295568a68..6d2437a8877 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleWorld.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/fluent/SimpleWorld.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.fluent; @@ -30,9 +29,10 @@ public class SimpleWorld implements WorldUpdater { /** The Parent. */ - SimpleWorld parent; + private final SimpleWorld parent; + /** The Accounts. */ - Map accounts = new HashMap<>(); + private Map> accounts = new HashMap<>(); /** Instantiates a new Simple world. */ public SimpleWorld() { @@ -55,13 +55,15 @@ public WorldUpdater updater() { @Override public Account get(final Address address) { + Optional account = Optional.empty(); if (accounts.containsKey(address)) { - return accounts.get(address); + account = accounts.get(address); } else if (parent != null) { - return parent.get(address); - } else { - return null; + if (parent.get(address) instanceof SimpleAccount accountFromParent) { + account = Optional.of(accountFromParent); + } } + return account.orElse(null); } @Override @@ -70,45 +72,46 @@ public MutableAccount createAccount(final Address address, final long nonce, fin throw new IllegalStateException("Cannot create an account when one already exists"); } SimpleAccount account = new SimpleAccount(address, nonce, balance); - accounts.put(address, account); + accounts.put(address, Optional.of(account)); return account; } @Override public MutableAccount getAccount(final Address address) { - SimpleAccount account = accounts.get(address); + Optional account = accounts.get(address); if (account != null) { - return account; + return account.orElse(null); } Account parentAccount = parent == null ? null : parent.getAccount(address); if (parentAccount != null) { account = - new SimpleAccount( - parentAccount, - parentAccount.getAddress(), - parentAccount.getNonce(), - parentAccount.getBalance(), - parentAccount.getCode()); + Optional.of( + new SimpleAccount( + parentAccount, + parentAccount.getAddress(), + parentAccount.getNonce(), + parentAccount.getBalance(), + parentAccount.getCode())); accounts.put(address, account); - return account; + return account.get(); } return null; } @Override public void deleteAccount(final Address address) { - accounts.put(address, null); + accounts.put(address, Optional.empty()); } @Override public Collection getTouchedAccounts() { - return accounts.values(); + return accounts.values().stream().filter(Optional::isPresent).map(Optional::get).toList(); } @Override public Collection
    getDeletedAccountAddresses() { return accounts.entrySet().stream() - .filter(e -> e.getValue() == null) + .filter(e -> e.getValue().isEmpty()) .map(Map.Entry::getKey) .toList(); } @@ -122,8 +125,10 @@ public void revert() { public void commit() { accounts.forEach( (address, account) -> { - if (!account.updateParent()) { - parent.accounts.put(address, account); + if (account.isEmpty() || !account.get().commit()) { + if (parent != null) { + parent.accounts.put(address, account); + } } }); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/BlockValues.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/BlockValues.java index aade1f98437..cfa7079857d 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/BlockValues.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/BlockValues.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/ExceptionalHaltReason.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/ExceptionalHaltReason.java index 06c10b9c025..729d10ffa50 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/ExceptionalHaltReason.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/ExceptionalHaltReason.java @@ -19,46 +19,59 @@ public interface ExceptionalHaltReason { /** The constant NONE. */ ExceptionalHaltReason NONE = DefaultExceptionalHaltReason.NONE; + /** The constant INSUFFICIENT_GAS. */ ExceptionalHaltReason INSUFFICIENT_GAS = DefaultExceptionalHaltReason.INSUFFICIENT_GAS; + /** The constant INSUFFICIENT_STACK_ITEMS. */ ExceptionalHaltReason INSUFFICIENT_STACK_ITEMS = DefaultExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS; + /** The constant INVALID_JUMP_DESTINATION. */ ExceptionalHaltReason INVALID_JUMP_DESTINATION = DefaultExceptionalHaltReason.INVALID_JUMP_DESTINATION; + /** The constant INVALID_OPERATION. */ ExceptionalHaltReason INVALID_OPERATION = DefaultExceptionalHaltReason.INVALID_OPERATION; + /** The constant INVALID_RETURN_DATA_BUFFER_ACCESS. */ ExceptionalHaltReason INVALID_RETURN_DATA_BUFFER_ACCESS = DefaultExceptionalHaltReason.INVALID_RETURN_DATA_BUFFER_ACCESS; + /** The constant TOO_MANY_STACK_ITEMS. */ ExceptionalHaltReason TOO_MANY_STACK_ITEMS = DefaultExceptionalHaltReason.TOO_MANY_STACK_ITEMS; + /** The constant ILLEGAL_STATE_CHANGE. */ ExceptionalHaltReason ILLEGAL_STATE_CHANGE = DefaultExceptionalHaltReason.ILLEGAL_STATE_CHANGE; + /** The constant OUT_OF_BOUNDS. */ ExceptionalHaltReason OUT_OF_BOUNDS = DefaultExceptionalHaltReason.OUT_OF_BOUNDS; + /** The constant CODE_TOO_LARGE. */ ExceptionalHaltReason CODE_TOO_LARGE = DefaultExceptionalHaltReason.CODE_TOO_LARGE; + /** The constant INVALID_CODE. */ ExceptionalHaltReason INVALID_CODE = DefaultExceptionalHaltReason.INVALID_CODE; + /** The constant PRECOMPILE_ERROR. */ ExceptionalHaltReason PRECOMPILE_ERROR = DefaultExceptionalHaltReason.PRECOMPILE_ERROR; - /** The constant CODE_SECTION_MISSING. */ - ExceptionalHaltReason CODE_SECTION_MISSING = DefaultExceptionalHaltReason.CODE_SECTION_MISSING; - /** The constant INCORRECT_CODE_SECTION_RETURN_OUTPUTS. */ - ExceptionalHaltReason INCORRECT_CODE_SECTION_RETURN_OUTPUTS = - DefaultExceptionalHaltReason.INCORRECT_CODE_SECTION_RETURN_OUTPUTS; - /** The constant TOO_FEW_INPUTS_FOR_CODE_SECTION. */ - ExceptionalHaltReason TOO_FEW_INPUTS_FOR_CODE_SECTION = - DefaultExceptionalHaltReason.TOO_FEW_INPUTS_FOR_CODE_SECTION; - /** The constant JUMPF_STACK_MISMATCH. */ - ExceptionalHaltReason JUMPF_STACK_MISMATCH = DefaultExceptionalHaltReason.JUMPF_STACK_MISMATCH; /** The constant EOF_CREATE_VERSION_INCOMPATIBLE. */ ExceptionalHaltReason EOF_CREATE_VERSION_INCOMPATIBLE = DefaultExceptionalHaltReason.EOF_CREATE_VERSION_INCOMPATIBLE; + /** The constant NONEXISTENT_CONTAINER */ + ExceptionalHaltReason NONEXISTENT_CONTAINER = DefaultExceptionalHaltReason.NONEXISTENT_CONTAINER; + + /** The constant INVALID_CONTAINER */ + ExceptionalHaltReason INVALID_CONTAINER = DefaultExceptionalHaltReason.INVALID_CONTAINER; + + /** The constant DATA_TOO_SMALL */ + ExceptionalHaltReason DATA_TOO_SMALL = DefaultExceptionalHaltReason.DATA_TOO_SMALL; + + /** The constant ADDRESS_OUT_OF_RANGE */ + ExceptionalHaltReason ADDRESS_OUT_OF_RANGE = DefaultExceptionalHaltReason.ADDRESS_OUT_OF_RANGE; + /** * Name string. * @@ -99,21 +112,19 @@ enum DefaultExceptionalHaltReason implements ExceptionalHaltReason { INVALID_CODE("Code is invalid"), /** The Precompile error. */ PRECOMPILE_ERROR("Precompile error"), - /** The Code section missing. */ - CODE_SECTION_MISSING("No code section at requested index"), /** The Insufficient code section return data. */ INSUFFICIENT_CODE_SECTION_RETURN_DATA("The stack for a return "), - /** The Incorrect code section return outputs. */ - INCORRECT_CODE_SECTION_RETURN_OUTPUTS( - "The return of a code section does not have the correct number of outputs"), - /** The Too few inputs for code section. */ - TOO_FEW_INPUTS_FOR_CODE_SECTION("Not enough stack items for a function call"), - /** The Jumpf stack mismatch. */ - JUMPF_STACK_MISMATCH( - "The stack height for a JUMPF does not match the requirements of the target section"), /** The Eof version incompatible. */ EOF_CREATE_VERSION_INCOMPATIBLE( - "EOF Code is attempting to create EOF code of an earlier version"); + "EOF Code is attempting to create EOF code of an earlier version"), + /** Container referenced by EOFCREATE operation does not exist */ + NONEXISTENT_CONTAINER("Referenced subcontainer index does not exist"), + /** Container referenced by EOFCREATE operation is invalid */ + INVALID_CONTAINER("Referenced subcontainer index is invalid"), + /** Container referenced by EOFCREATE operation does not exist */ + DATA_TOO_SMALL("Insufficient AuxData provided to a truncated container"), + /** A given address cannot be used by EOF */ + ADDRESS_OUT_OF_RANGE("Address has more than 20 bytes and is out of range"); /** The Description. */ final String description; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/Memory.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/Memory.java index fe2bba89cf5..fbc96b034db 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/Memory.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/Memory.java @@ -18,7 +18,6 @@ import java.util.Arrays; -import com.google.common.annotations.VisibleForTesting; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.bytes.MutableBytes; @@ -55,9 +54,6 @@ private static RuntimeException overflow(final long v) { } private static RuntimeException overflow(final String v) { - // TODO: we should probably have another specific exception so this properly end up as an - // exceptional halt condition with a clear message (message that can indicate that if anyone - // runs into this, he should contact us so we know it's a case we do need to handle). final String msg = "Memory index or length %s too large, cannot be larger than %d"; throw new IllegalStateException(String.format(msg, v, MAX_BYTES)); } @@ -180,7 +176,6 @@ int getActiveBytes() { * * @return The current number of active words stored in memory. */ - @VisibleForTesting public int getActiveWords() { return activeWords; } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java index f8e9d3d6121..16551876d24 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/MessageFrame.java @@ -18,20 +18,20 @@ import static java.util.Collections.emptySet; import org.hyperledger.besu.collections.trie.BytesTrieSet; +import org.hyperledger.besu.collections.undo.UndoScalar; import org.hyperledger.besu.collections.undo.UndoSet; import org.hyperledger.besu.collections.undo.UndoTable; import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.VersionedHash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.Code; -import org.hyperledger.besu.evm.code.CodeSection; import org.hyperledger.besu.evm.internal.MemoryEntry; import org.hyperledger.besu.evm.internal.OperandStack; import org.hyperledger.besu.evm.internal.ReturnStack; import org.hyperledger.besu.evm.internal.StorageEntry; import org.hyperledger.besu.evm.internal.UnderflowException; import org.hyperledger.besu.evm.log.Log; +import org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.worldstate.WorldUpdater; @@ -44,7 +44,6 @@ import java.util.Optional; import java.util.Set; import java.util.function.Consumer; -import java.util.function.Function; import java.util.function.Supplier; import com.google.common.base.Suppliers; @@ -216,11 +215,11 @@ public enum Type { private final Supplier returnStack; private Bytes output = Bytes.EMPTY; private Bytes returnData = Bytes.EMPTY; + private Code createdCode = null; private final boolean isStatic; // Transaction state fields. private final List logs = new ArrayList<>(); - private long gasRefund = 0L; private final Map refunds = new HashMap<>(); // Execution Environment fields. @@ -278,13 +277,7 @@ private MessageFrame( this.worldUpdater = worldUpdater; this.gasRemaining = initialGas; this.stack = new OperandStack(txValues.maxStackSize()); - this.returnStack = - Suppliers.memoize( - () -> { - var rStack = new ReturnStack(); - rStack.push(new ReturnStack.ReturnStackItem(0, 0, 0)); - return rStack; - }); + this.returnStack = Suppliers.memoize(ReturnStack::new); this.pc = code.isValid() ? code.getCodeSection(0).getEntryPoint() : 0; this.recipient = recipient; this.contract = contract; @@ -337,71 +330,6 @@ public int getSection() { return section; } - /** - * Call function and return exceptional halt reason. - * - * @param calledSection the called section - * @return the exceptional halt reason - */ - public ExceptionalHaltReason callFunction(final int calledSection) { - CodeSection info = code.getCodeSection(calledSection); - if (info == null) { - return ExceptionalHaltReason.CODE_SECTION_MISSING; - } else if (stack.size() + info.getMaxStackHeight() > txValues.maxStackSize()) { - return ExceptionalHaltReason.TOO_MANY_STACK_ITEMS; - } else if (stack.size() < info.getInputs()) { - return ExceptionalHaltReason.TOO_FEW_INPUTS_FOR_CODE_SECTION; - } else { - returnStack - .get() - .push(new ReturnStack.ReturnStackItem(section, pc + 2, stack.size() - info.getInputs())); - pc = info.getEntryPoint() - 1; // will be +1ed at end of operations loop - this.section = calledSection; - return null; - } - } - - /** - * Execute the mechanics of the JUMPF operation. - * - * @param section the section - * @return the exceptional halt reason, if the jump failed - */ - public ExceptionalHaltReason jumpFunction(final int section) { - CodeSection info = code.getCodeSection(section); - if (info == null) { - return ExceptionalHaltReason.CODE_SECTION_MISSING; - } else if (stackSize() != peekReturnStack().getStackHeight() + info.getInputs()) { - return ExceptionalHaltReason.JUMPF_STACK_MISMATCH; - } else { - pc = -1; // will be +1ed at end of operations loop - this.section = section; - return null; - } - } - - /** - * Return function exceptional halt reason. - * - * @return the exceptional halt reason - */ - public ExceptionalHaltReason returnFunction() { - CodeSection thisInfo = code.getCodeSection(this.section); - var rStack = returnStack.get(); - var returnInfo = rStack.pop(); - if ((returnInfo.getStackHeight() + thisInfo.getOutputs()) != stack.size()) { - return ExceptionalHaltReason.INCORRECT_CODE_SECTION_RETURN_OUTPUTS; - } else if (rStack.isEmpty()) { - setState(MessageFrame.State.CODE_SUCCESS); - setOutputData(Bytes.EMPTY); - return null; - } else { - this.pc = returnInfo.getPC(); - this.section = returnInfo.getCodeSectionIndex(); - return null; - } - } - /** Deducts the remaining gas. */ public void clearGasRemaining() { this.gasRemaining = 0L; @@ -414,7 +342,8 @@ public void clearGasRemaining() { * @return the amount of gas available, after deductions. */ public long decrementRemainingGas(final long amount) { - return this.gasRemaining -= amount; + this.gasRemaining -= amount; + return this.gasRemaining; } /** @@ -462,6 +391,24 @@ public void setOutputData(final Bytes output) { this.output = output; } + /** + * Sets the created code from CREATE* operations + * + * @param createdCode the code that was created + */ + public void setCreatedCode(final Code createdCode) { + this.createdCode = createdCode; + } + + /** + * gets the created code from CREATE* operations + * + * @return the code that was created + */ + public Code getCreatedCode() { + return createdCode; + } + /** Clears the output data buffer. */ public void clearOutputData() { setOutputData(Bytes.EMPTY); @@ -892,12 +839,12 @@ public List getLogs() { * @param amount The amount to increment the refund */ public void incrementGasRefund(final long amount) { - this.gasRefund += amount; + this.txValues.gasRefunds().set(this.txValues.gasRefunds().get() + amount); } /** Clear the accumulated gas refund. */ public void clearGasRefund() { - gasRefund = 0L; + this.txValues.gasRefunds().set(0L); } /** @@ -906,7 +853,7 @@ public void clearGasRefund() { * @return accumulated gas refund */ public long getGasRefund() { - return gasRefund; + return txValues.gasRefunds().get(); } /** @@ -944,6 +891,7 @@ public Set
    getSelfDestructs() { public void addCreate(final Address address) { txValues.creates().add(address); } + /** * Add addresses to the create set if they are not already present. * @@ -1026,18 +974,6 @@ public boolean warmUpStorage(final Address address, final Bytes32 slot) { return txValues.warmedUpStorage().put(address, slot, Boolean.TRUE) != null; } - /** - * Returns whether an address' slot is warmed up. Is deliberately publicly exposed for access from - * trace - * - * @param address the address context - * @param slot the slot to query - * @return whether the address/slot couple is warmed up - */ - public boolean isStorageWarm(final Address address, final Bytes32 slot) { - return this.txValues.warmedUpStorage().contains(address, slot); - } - /** * Return the world state. * @@ -1118,6 +1054,7 @@ public int getMessageStackSize() { public int getDepth() { return getMessageStackSize() - 1; } + /** * Returns the recipient that originated the message. * @@ -1204,6 +1141,15 @@ public Deque getMessageFrameStack() { return txValues.messageFrameStack(); } + /** + * The return stack used for EOF code sections. + * + * @return the return stack + */ + public ReturnStack getReturnStack() { + return returnStack.get(); + } + /** * Sets exceptional halt reason. * @@ -1237,7 +1183,7 @@ public Address getMiningBeneficiary() { * * @return the block hash lookup */ - public Function getBlockHashLookup() { + public BlockHashLookup getBlockHashLookup() { return txValues.blockHashLookup(); } @@ -1396,7 +1342,7 @@ public static class Builder { private boolean isStatic = false; private Consumer completer; private Address miningBeneficiary; - private Function blockHashLookup; + private BlockHashLookup blockHashLookup; private Map contextVariables; private Optional reason = Optional.empty(); private Set
    accessListWarmAddresses = emptySet(); @@ -1404,6 +1350,11 @@ public static class Builder { private Optional> versionedHashes = Optional.empty(); + /** Instantiates a new Builder. */ + public Builder() { + // constructor added to deal with JavaDoc linting rules. + } + /** * The "parent" message frame. When present some fields will be populated from the parent and * ignored if passed in via builder @@ -1620,7 +1571,7 @@ public Builder miningBeneficiary(final Address miningBeneficiary) { * @param blockHashLookup the block hash lookup * @return the builder */ - public Builder blockHashLookup(final Function blockHashLookup) { + public Builder blockHashLookup(final BlockHashLookup blockHashLookup) { this.blockHashLookup = blockHashLookup; return this; } @@ -1713,6 +1664,7 @@ public MessageFrame build() { WorldUpdater updater; boolean newStatic; TxValues newTxValues; + if (parentMessageFrame == null) { newTxValues = new TxValues( @@ -1729,13 +1681,15 @@ public MessageFrame build() { versionedHashes, UndoTable.of(HashBasedTable.create()), UndoSet.of(new BytesTrieSet<>(Address.SIZE)), - UndoSet.of(new BytesTrieSet<>(Address.SIZE))); + UndoSet.of(new BytesTrieSet<>(Address.SIZE)), + new UndoScalar<>(0L)); updater = worldUpdater; newStatic = isStatic; } else { newTxValues = parentMessageFrame.txValues; - updater = parentMessageFrame.worldUpdater.updater(); + updater = parentMessageFrame.getWorldUpdater().updater(); newStatic = isStatic || parentMessageFrame.isStatic; + parentMessageFrame.warmUpAddress(contract); } MessageFrame messageFrame = diff --git a/evm/src/main/java/org/hyperledger/besu/evm/frame/TxValues.java b/evm/src/main/java/org/hyperledger/besu/evm/frame/TxValues.java index 6ef1143530f..81ebbdb767a 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/frame/TxValues.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/frame/TxValues.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,17 +14,18 @@ */ package org.hyperledger.besu.evm.frame; +import static org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; + +import org.hyperledger.besu.collections.undo.UndoScalar; import org.hyperledger.besu.collections.undo.UndoSet; import org.hyperledger.besu.collections.undo.UndoTable; import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.VersionedHash; import org.hyperledger.besu.datatypes.Wei; import java.util.Deque; import java.util.List; import java.util.Optional; -import java.util.function.Function; import org.apache.tuweni.bytes.Bytes32; @@ -32,9 +33,25 @@ * Transaction Values used by various EVM Opcodes. These are the values that either do not change or * the backing stores whose changes transcend message frames and are not part of state, such as * transient storage and address warming. + * + * @param blockHashLookup The block hash lookup function + * @param maxStackSize The maximum stack size + * @param warmedUpAddresses The warmed-up addresses + * @param warmedUpStorage The warmed-up storage + * @param originator The originator address + * @param gasPrice The gas price + * @param blobGasPrice The blob gas price + * @param blockValues The block values + * @param messageFrameStack The message frame stack + * @param miningBeneficiary The mining beneficiary address + * @param versionedHashes The optional list of versioned hashes + * @param transientStorage The transient storage + * @param creates The set of addresses that creates + * @param selfDestructs The set of addresses that self-destructs + * @param gasRefunds The gas refunds */ public record TxValues( - Function blockHashLookup, + BlockHashLookup blockHashLookup, int maxStackSize, UndoSet
    warmedUpAddresses, UndoTable warmedUpStorage, @@ -47,7 +64,8 @@ public record TxValues( Optional> versionedHashes, UndoTable transientStorage, UndoSet
    creates, - UndoSet
    selfDestructs) { + UndoSet
    selfDestructs, + UndoScalar gasRefunds) { /** * For all data stored in this record, undo the changes since the mark. @@ -60,5 +78,6 @@ public void undoChanges(final long mark) { transientStorage.undo(mark); creates.undo(mark); selfDestructs.undo(mark); + gasRefunds.undo(mark); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/BerlinGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/BerlinGasCalculator.java index 50a035eb84f..f83b14e2e77 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/BerlinGasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/BerlinGasCalculator.java @@ -38,15 +38,18 @@ public class BerlinGasCalculator extends IstanbulGasCalculator { // new constants for EIP-2929 private static final long COLD_SLOAD_COST = 2100L; private static final long COLD_ACCOUNT_ACCESS_COST = 2600L; + /** Warm storage read, defined in EIP-2929 */ protected static final long WARM_STORAGE_READ_COST = 100L; private static final long ACCESS_LIST_ADDRESS_COST = 2400L; + /** The constant ACCESS_LIST_STORAGE_COST. */ protected static final long ACCESS_LIST_STORAGE_COST = 1900L; // redefinitions for EIP-2929 private static final long SLOAD_GAS = WARM_STORAGE_READ_COST; + /** The constant SSTORE_RESET_GAS. */ protected static final long SSTORE_RESET_GAS = 5000L - COLD_SLOAD_COST; @@ -56,6 +59,7 @@ public class BerlinGasCalculator extends IstanbulGasCalculator { /** The constant SSTORE_SET_GAS_LESS_SLOAD_GAS. */ protected static final long SSTORE_SET_GAS_LESS_SLOAD_GAS = SSTORE_SET_GAS - SLOAD_GAS; + /** The constant SSTORE_RESET_GAS_LESS_SLOAD_GAS. */ protected static final long SSTORE_RESET_GAS_LESS_SLOAD_GAS = SSTORE_RESET_GAS - SLOAD_GAS; @@ -141,6 +145,7 @@ public long getSloadOperationGasCost() { // Redefined costs from EIP-2929 @Override + @SuppressWarnings("java:S5738") public long callOperationGasCost( final MessageFrame frame, final long stipend, @@ -151,6 +156,32 @@ public long callOperationGasCost( final Wei transferValue, final Account recipient, final Address to) { + return callOperationGasCost( + frame, + stipend, + inputDataOffset, + inputDataLength, + outputDataOffset, + outputDataLength, + transferValue, + recipient, + to, + frame.warmUpAddress(to) || isPrecompile(to)); + } + + // Redefined costs from EIP-2929 + @Override + public long callOperationGasCost( + final MessageFrame frame, + final long stipend, + final long inputDataOffset, + final long inputDataLength, + final long outputDataOffset, + final long outputDataLength, + final Wei transferValue, + final Account recipient, + final Address to, + final boolean accountIsWarm) { final long baseCost = super.callOperationGasCost( frame, @@ -161,8 +192,8 @@ public long callOperationGasCost( outputDataLength, transferValue, recipient, - to); - final boolean accountIsWarm = frame.warmUpAddress(to) || isPrecompile(to); + to, + true); // we want the "warmed price" as we will charge for warming ourselves return clampedAdd( baseCost, accountIsWarm ? getWarmStorageReadCost() : getColdAccountAccessCost()); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/ByzantiumGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/ByzantiumGasCalculator.java index 2bc25458e96..55f113943ed 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/ByzantiumGasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/ByzantiumGasCalculator.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.gascalculator; @@ -34,6 +33,9 @@ public class ByzantiumGasCalculator extends SpuriousDragonGasCalculator { /** The constant MAX_FIRST_EXPONENT_BYTES. */ public static final int MAX_FIRST_EXPONENT_BYTES = 32; + /** Default constructor. */ + public ByzantiumGasCalculator() {} + @Override public long modExpGasCost(final Bytes input) { final long baseLength = BigIntegerModularExponentiationPrecompiledContract.baseLength(input); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/CancunGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/CancunGasCalculator.java index 191e60888b2..79165e5c210 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/CancunGasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/CancunGasCalculator.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -36,7 +36,7 @@ public CancunGasCalculator() { * * @param maxPrecompile the max precompile */ - private CancunGasCalculator(final int maxPrecompile) { + protected CancunGasCalculator(final int maxPrecompile) { super(maxPrecompile); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/ConstantinopleGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/ConstantinopleGasCalculator.java index 0284269c4db..759847aba92 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/ConstantinopleGasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/ConstantinopleGasCalculator.java @@ -15,14 +15,12 @@ package org.hyperledger.besu.evm.gascalculator; import static org.hyperledger.besu.evm.internal.Words.clampedAdd; -import static org.hyperledger.besu.evm.internal.Words.clampedMultiply; -import static org.hyperledger.besu.evm.internal.Words.clampedToLong; +import static org.hyperledger.besu.evm.internal.Words.clampedToInt; import org.hyperledger.besu.evm.frame.MessageFrame; import java.util.function.Supplier; -import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; /** The Constantinople gas calculator. */ @@ -39,12 +37,28 @@ public class ConstantinopleGasCalculator extends ByzantiumGasCalculator { private static final long EXTCODE_HASH_COST = 400L; + /** Default constructor. */ + public ConstantinopleGasCalculator() {} + + /** + * Returns the amount of gas the CREATE2 operation will consume. + * + * @param frame The current frame + * @return the amount of gas the CREATE2 operation will consume + * @deprecated Compose the operation cost from {@link #txCreateCost()}, {@link + * #memoryExpansionGasCost(MessageFrame, long, long)}, {@link #createKeccakCost(int)}, and + * {@link #initcodeCost(int)}. As done in {@link + * org.hyperledger.besu.evm.operation.Create2Operation#cost(MessageFrame, Supplier)} + */ + @SuppressWarnings("removal") @Override + @Deprecated(since = "24.4.1", forRemoval = true) public long create2OperationGasCost(final MessageFrame frame) { - final long initCodeLength = clampedToLong(frame.getStackItem(2)); - final long numWords = clampedAdd(initCodeLength, 31) / Bytes32.SIZE; - final long initCodeHashCost = clampedMultiply(KECCAK256_OPERATION_WORD_GAS_COST, numWords); - return clampedAdd(createOperationGasCost(frame), initCodeHashCost); + final int inputOffset = clampedToInt(frame.getStackItem(1)); + final int inputSize = clampedToInt(frame.getStackItem(2)); + return clampedAdd( + clampedAdd(txCreateCost(), memoryExpansionGasCost(frame, inputOffset, inputSize)), + clampedAdd(createKeccakCost(inputSize), initcodeCost(inputSize))); } @Override diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/DieHardGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/DieHardGasCalculator.java index 4dfc52a7982..4cff31490e6 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/DieHardGasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/DieHardGasCalculator.java @@ -18,6 +18,9 @@ public class DieHardGasCalculator extends TangerineWhistleGasCalculator { private static final long EXP_OPERATION_BYTE_GAS_COST = 50L; + /** Default constructor. */ + public DieHardGasCalculator() {} + @Override protected long expOperationByteGasCost() { return EXP_OPERATION_BYTE_GAS_COST; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/FrontierGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/FrontierGasCalculator.java index ed0c3c90d93..0376903c64a 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/FrontierGasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/FrontierGasCalculator.java @@ -16,13 +16,14 @@ import static org.hyperledger.besu.evm.internal.Words.clampedAdd; import static org.hyperledger.besu.evm.internal.Words.clampedMultiply; +import static org.hyperledger.besu.evm.internal.Words.clampedToInt; import static org.hyperledger.besu.evm.internal.Words.clampedToLong; +import static org.hyperledger.besu.evm.internal.Words.numWords; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.internal.Words; import org.hyperledger.besu.evm.operation.ExpOperation; import java.util.function.Supplier; @@ -75,7 +76,8 @@ public class FrontierGasCalculator implements GasCalculator { private static final long NEW_ACCOUNT_GAS_COST = 25_000L; - private static final long CREATE_OPERATION_GAS_COST = 32_000L; + /** Yellow paper constant for the cost of creating a new contract on-chain */ + protected static final long CREATE_OPERATION_GAS_COST = 32_000L; private static final long COPY_WORD_GAS_COST = 3L; @@ -83,7 +85,8 @@ public class FrontierGasCalculator implements GasCalculator { private static final long BALANCE_OPERATION_GAS_COST = 20L; - private static final long BLOCKHASH_OPERATION_GAS_COST = 20L; + /** The constant BLOCKHASH_OPERATION_GAS_COST = 20gwei. */ + public static final long BLOCKHASH_OPERATION_GAS_COST = 20L; private static final long EXP_OPERATION_BASE_GAS_COST = 10L; @@ -119,6 +122,11 @@ public class FrontierGasCalculator implements GasCalculator { private static final long SELF_DESTRUCT_REFUND_AMOUNT = 24_000L; + /** Default constructor. */ + public FrontierGasCalculator() { + // Default Constructor, for JavaDoc lint + } + @Override public long transactionIntrinsicGasCost(final Bytes payload, final boolean isContractCreate) { int zeros = 0; @@ -150,7 +158,7 @@ public long codeDepositGasCost(final int codeSize) { @Override public long idPrecompiledContractGasCost(final Bytes input) { - return ID_PRECOMPILED_WORD_GAS_COST * Words.numWords(input) + ID_PRECOMPILED_BASE_GAS_COST; + return ID_PRECOMPILED_WORD_GAS_COST * numWords(input) + ID_PRECOMPILED_BASE_GAS_COST; } @Override @@ -160,13 +168,12 @@ public long getEcrecPrecompiledContractGasCost() { @Override public long sha256PrecompiledContractGasCost(final Bytes input) { - return SHA256_PRECOMPILED_WORD_GAS_COST * Words.numWords(input) - + SHA256_PRECOMPILED_BASE_GAS_COST; + return SHA256_PRECOMPILED_WORD_GAS_COST * numWords(input) + SHA256_PRECOMPILED_BASE_GAS_COST; } @Override public long ripemd160PrecompiledContractGasCost(final Bytes input) { - return RIPEMD160_PRECOMPILED_WORD_GAS_COST * Words.numWords(input) + return RIPEMD160_PRECOMPILED_WORD_GAS_COST * numWords(input) + RIPEMD160_PRECOMPILED_BASE_GAS_COST; } @@ -210,24 +217,17 @@ public long callOperationBaseGasCost() { return CALL_OPERATION_BASE_GAS_COST; } - /** - * Returns the gas cost to transfer funds in a call operation. - * - * @return the gas cost to transfer funds in a call operation - */ - long callValueTransferGasCost() { + @Override + public long callValueTransferGasCost() { return CALL_VALUE_TRANSFER_GAS_COST; } - /** - * Returns the gas cost to create a new account. - * - * @return the gas cost to create a new account - */ - long newAccountGasCost() { + @Override + public long newAccountGasCost() { return NEW_ACCOUNT_GAS_COST; } + @SuppressWarnings("removal") @Override public long callOperationGasCost( final MessageFrame frame, @@ -239,6 +239,31 @@ public long callOperationGasCost( final Wei transferValue, final Account recipient, final Address to) { + return callOperationGasCost( + frame, + stipend, + inputDataOffset, + inputDataLength, + outputDataOffset, + outputDataLength, + transferValue, + recipient, + to, + true); + } + + @Override + public long callOperationGasCost( + final MessageFrame frame, + final long stipend, + final long inputDataOffset, + final long inputDataLength, + final long outputDataOffset, + final long outputDataLength, + final Wei transferValue, + final Account recipient, + final Address to, + final boolean accountIsWarm) { final long inputDataMemoryExpansionCost = memoryExpansionGasCost(frame, inputDataOffset, inputDataLength); final long outputDataMemoryExpansionCost = @@ -280,12 +305,66 @@ public long gasAvailableForChildCall( } @Override + public long getMinRetainedGas() { + return 0; + } + + @Override + public long getMinCalleeGas() { + return 0; + } + + /** + * Returns the amount of gas the CREATE operation will consume. + * + * @param frame The current frame + * @return the amount of gas the CREATE operation will consume + * @deprecated Compose the operation cost from {@link #txCreateCost()}, {@link + * #memoryExpansionGasCost(MessageFrame, long, long)}, and {@link #initcodeCost(int)} As done + * in {@link org.hyperledger.besu.evm.operation.CreateOperation#cost(MessageFrame, Supplier)} + */ + @SuppressWarnings("removal") + @Override + @Deprecated(since = "24.4.1", forRemoval = true) public long createOperationGasCost(final MessageFrame frame) { final long initCodeOffset = clampedToLong(frame.getStackItem(1)); - final long initCodeLength = clampedToLong(frame.getStackItem(2)); + final int initCodeLength = clampedToInt(frame.getStackItem(2)); - final long memoryGasCost = memoryExpansionGasCost(frame, initCodeOffset, initCodeLength); - return clampedAdd(CREATE_OPERATION_GAS_COST, memoryGasCost); + return clampedAdd( + clampedAdd(txCreateCost(), memoryExpansionGasCost(frame, initCodeOffset, initCodeLength)), + initcodeCost(initCodeLength)); + } + + /** + * Returns the amount of gas the CREATE2 operation will consume. + * + * @param frame The current frame + * @return the amount of gas the CREATE2 operation will consume + * @deprecated Compose the operation cost from {@link #txCreateCost()}, {@link + * #memoryExpansionGasCost(MessageFrame, long, long)}, {@link #createKeccakCost(int)}, and + * {@link #initcodeCost(int)} + */ + @SuppressWarnings("removal") + @Override + @Deprecated(since = "24.4.1", forRemoval = true) + public long create2OperationGasCost(final MessageFrame frame) { + throw new UnsupportedOperationException( + "CREATE2 operation not supported by " + getClass().getSimpleName()); + } + + @Override + public long txCreateCost() { + return CREATE_OPERATION_GAS_COST; + } + + @Override + public long createKeccakCost(final int initCodeLength) { + return clampedMultiply(KECCAK256_OPERATION_WORD_GAS_COST, numWords(initCodeLength)); + } + + @Override + public long initcodeCost(final int initCodeLength) { + return 0; } @Override @@ -411,12 +490,6 @@ public long keccak256OperationGasCost( length); } - @Override - public long create2OperationGasCost(final MessageFrame frame) { - throw new UnsupportedOperationException( - "CREATE2 operation not supported by " + getClass().getSimpleName()); - } - @Override public long getSloadOperationGasCost() { return SLOAD_OPERATION_GAS_COST; @@ -461,9 +534,8 @@ protected long copyWordsToMemoryGasCost( final long wordGasCost, final long offset, final long length) { - final long numWords = length / 32 + (length % 32 == 0 ? 0 : 1); - - final long copyCost = clampedAdd(clampedMultiply(wordGasCost, numWords), baseGasCost); + final long copyCost = + clampedAdd(clampedMultiply(wordGasCost, numWords(clampedToInt(length))), baseGasCost); final long memoryCost = memoryExpansionGasCost(frame, offset, length); return clampedAdd(copyCost, memoryCost); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java index b38768d847c..fd453deac51 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/GasCalculator.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -144,6 +144,20 @@ public interface GasCalculator { */ long callOperationBaseGasCost(); + /** + * Returns the gas cost to transfer funds in a call operation. + * + * @return the gas cost to transfer funds in a call operation + */ + long callValueTransferGasCost(); + + /** + * Returns the gas cost to create a new account. + * + * @return the gas cost to create a new account + */ + long newAccountGasCost(); + /** * Returns the gas cost for one of the various CALL operations. * @@ -157,6 +171,46 @@ public interface GasCalculator { * @param recipient The CALL recipient (may be null if self destructed or new) * @param contract The address of the recipient (never null) * @return The gas cost for the CALL operation + * @deprecated use the variant with the `accountIsWarm` parameter. + */ + @Deprecated(since = "24.2.0", forRemoval = true) + default long callOperationGasCost( + final MessageFrame frame, + final long stipend, + final long inputDataOffset, + final long inputDataLength, + final long outputDataOffset, + final long outputDataLength, + final Wei transferValue, + final Account recipient, + final Address contract) { + return callOperationGasCost( + frame, + stipend, + inputDataOffset, + inputDataLength, + outputDataOffset, + outputDataLength, + transferValue, + recipient, + contract, + true); + } + + /** + * Returns the gas cost for one of the various CALL operations. + * + * @param frame The current frame + * @param stipend The gas stipend being provided by the CALL caller + * @param inputDataOffset The offset in memory to retrieve the CALL input data + * @param inputDataLength The CALL input data length + * @param outputDataOffset The offset in memory to place the CALL output data + * @param outputDataLength The CALL output data length + * @param transferValue The wei being transferred + * @param recipient The CALL recipient (may be null if self destructed or new) + * @param contract The address of the recipient (never null) + * @param accountIsWarm The address of the contract is "warm" as per EIP-2929 + * @return The gas cost for the CALL operation */ long callOperationGasCost( MessageFrame frame, @@ -167,7 +221,8 @@ long callOperationGasCost( long outputDataLength, Wei transferValue, Account recipient, - Address contract); + Address contract, + boolean accountIsWarm); /** * Gets additional call stipend. @@ -186,12 +241,29 @@ long callOperationGasCost( */ long gasAvailableForChildCall(MessageFrame frame, long stipend, boolean transfersValue); + /** + * For EXT*CALL, the minimum amount of gas the parent must retain. First described in EIP-7069 + * + * @return MIN_RETAINED_GAS + */ + long getMinRetainedGas(); + + /** + * For EXT*CALL, the minimum amount of gas that a child must receive. First described in EIP-7069 + * + * @return MIN_CALLEE_GAS + */ + long getMinCalleeGas(); + /** * Returns the amount of gas the CREATE operation will consume. * * @param frame The current frame * @return the amount of gas the CREATE operation will consume + * @deprecated Compose the operation cost from {@link #txCreateCost()}, {@link + * #memoryExpansionGasCost(MessageFrame, long, long)}, and {@link #initcodeCost(int)} */ + @Deprecated(since = "24.4.1", forRemoval = true) long createOperationGasCost(MessageFrame frame); /** @@ -199,9 +271,37 @@ long callOperationGasCost( * * @param frame The current frame * @return the amount of gas the CREATE2 operation will consume + * @deprecated Compose the operation cost from {@link #txCreateCost()}, {@link + * #memoryExpansionGasCost(MessageFrame, long, long)}, {@link #createKeccakCost(int)}, and + * {@link #initcodeCost(int)} */ + @Deprecated(since = "24.4.1", forRemoval = true) long create2OperationGasCost(MessageFrame frame); + /** + * Returns the base create cost, or TX_CREATE_COST as defined in the execution specs + * + * @return the TX_CREATE value for this gas schedule + */ + long txCreateCost(); + + /** + * For Creates that need to hash the initcode, this is the gas cost for such hashing + * + * @param initCodeLength length of the init code, in bytes + * @return gas cost to charge for hashing + */ + long createKeccakCost(int initCodeLength); + + /** + * The cost of a create operation's initcode charge. This is just the initcode cost, separate from + * the operation base cost and initcode hashing cost. + * + * @param initCodeLength Number of bytes in the initcode + * @return the gas cost for the create initcode + */ + long initcodeCost(final int initCodeLength); + /** * Returns the amount of gas parent will provide its child CREATE. * @@ -545,4 +645,35 @@ default long computeExcessBlobGas(final long parentExcessBlobGas, final int newB default long computeExcessBlobGas(final long parentExcessBlobGas, final long blobGasUsed) { return 0L; } + + /** + * Returns the upfront gas cost for EIP 7702 authorization processing. + * + * @param delegateCodeListLength The length of the code delegation list + * @return the gas cost + */ + default long delegateCodeGasCost(final int delegateCodeListLength) { + return 0L; + } + + /** + * Calculates the refund for proessing the 7702 code delegation list if an delegater account + * already exist in the trie. + * + * @param alreadyExistingAccountSize The number of accounts already in the trie + * @return the gas refund + */ + default long calculateDelegateCodeGasRefund(final long alreadyExistingAccountSize) { + return 0L; + } + + /** + * Returns the gas cost for resolving the code of a delegate account. + * + * @param isWarm whether the account is warm + * @return the gas cost + */ + default long delegatedCodeResolutionGasCost(final boolean isWarm) { + return 0L; + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/HomesteadGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/HomesteadGasCalculator.java index 092dce26b0b..d0268f58eba 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/HomesteadGasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/HomesteadGasCalculator.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -19,6 +19,9 @@ public class HomesteadGasCalculator extends FrontierGasCalculator { private static final long TX_CREATE_EXTRA = 32_000L; + /** Default constructor. */ + public HomesteadGasCalculator() {} + @Override protected long txCreateExtraGasCost() { return TX_CREATE_EXTRA; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/IstanbulGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/IstanbulGasCalculator.java index 62fb0db4aa2..a997f7fe16e 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/IstanbulGasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/IstanbulGasCalculator.java @@ -38,6 +38,9 @@ public class IstanbulGasCalculator extends PetersburgGasCalculator { private static final long SSTORE_RESET_GAS_LESS_SLOAD_GAS = SSTORE_RESET_GAS - SLOAD_GAS; private static final long NEGATIVE_SSTORE_CLEARS_SCHEDULE = -SSTORE_CLEARS_SCHEDULE; + /** Default constructor. */ + public IstanbulGasCalculator() {} + @Override public long transactionIntrinsicGasCost(final Bytes payload, final boolean isContractCreation) { int zeros = 0; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PetersburgGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PetersburgGasCalculator.java index df5dd960ff3..e499e7be480 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PetersburgGasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PetersburgGasCalculator.java @@ -28,11 +28,16 @@ public class PetersburgGasCalculator extends ConstantinopleGasCalculator { /** Same as {#link {@link FrontierGasCalculator#STORAGE_SET_GAS_COST} */ private static final long STORAGE_SET_GAS_COST = 20_000L; + /** Same as {#link {@link FrontierGasCalculator#STORAGE_RESET_GAS_COST} */ private static final long STORAGE_RESET_GAS_COST = 5_000L; + /** Same as {#link {@link FrontierGasCalculator#STORAGE_RESET_REFUND_AMOUNT} */ private static final long STORAGE_RESET_REFUND_AMOUNT = 15_000L; + /** Default constructor. */ + public PetersburgGasCalculator() {} + /** * Same as {#link {@link FrontierGasCalculator#calculateStorageCost(UInt256, Supplier, Supplier)} */ diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueEOFGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueEOFGasCalculator.java new file mode 100644 index 00000000000..5fa2fe87257 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueEOFGasCalculator.java @@ -0,0 +1,57 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.gascalculator; + +import static org.hyperledger.besu.datatypes.Address.BLS12_MAP_FP2_TO_G2; + +/** + * Gas Calculator for Prague + * + *

    Placeholder for new gas schedule items. If Prague finalzies without changes this can be + * removed + * + *

      + *
    • TBD + *
    + */ +public class PragueEOFGasCalculator extends PragueGasCalculator { + + static final long MIN_RETAINED_GAS = 5_000; + static final long MIN_CALLEE_GAS = 2300; + + /** Instantiates a new Prague Gas Calculator. */ + public PragueEOFGasCalculator() { + this(BLS12_MAP_FP2_TO_G2.toArrayUnsafe()[19]); + } + + /** + * Instantiates a new Prague Gas Calculator + * + * @param maxPrecompile the max precompile + */ + protected PragueEOFGasCalculator(final int maxPrecompile) { + super(maxPrecompile); + } + + @Override + public long getMinRetainedGas() { + return MIN_RETAINED_GAS; + } + + @Override + public long getMinCalleeGas() { + return MIN_CALLEE_GAS; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueGasCalculator.java new file mode 100644 index 00000000000..33fe98b9968 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/PragueGasCalculator.java @@ -0,0 +1,60 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.gascalculator; + +import static org.hyperledger.besu.datatypes.Address.BLS12_MAP_FP2_TO_G2; + +import org.hyperledger.besu.datatypes.CodeDelegation; + +/** + * Gas Calculator for Prague + * + *
      + *
    • Gas costs for EIP-7702 (Code Delegation) + *
    + */ +public class PragueGasCalculator extends CancunGasCalculator { + final long existingAccountGasRefund; + + /** Instantiates a new Prague Gas Calculator. */ + public PragueGasCalculator() { + this(BLS12_MAP_FP2_TO_G2.toArrayUnsafe()[19]); + } + + /** + * Instantiates a new Prague Gas Calculator + * + * @param maxPrecompile the max precompile + */ + protected PragueGasCalculator(final int maxPrecompile) { + super(maxPrecompile); + this.existingAccountGasRefund = newAccountGasCost() - CodeDelegation.PER_AUTH_BASE_COST; + } + + @Override + public long delegateCodeGasCost(final int delegateCodeListLength) { + return newAccountGasCost() * delegateCodeListLength; + } + + @Override + public long calculateDelegateCodeGasRefund(final long alreadyExistingAccounts) { + return existingAccountGasRefund * alreadyExistingAccounts; + } + + @Override + public long delegatedCodeResolutionGasCost(final boolean isWarm) { + return isWarm ? getWarmStorageReadCost() : getColdAccountAccessCost(); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/ShanghaiGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/ShanghaiGasCalculator.java index e3c8c43928c..a44300f06a3 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/ShanghaiGasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/ShanghaiGasCalculator.java @@ -1,4 +1,3 @@ -package org.hyperledger.besu.evm.gascalculator; /* * Copyright contributors to Hyperledger Besu. * @@ -13,10 +12,10 @@ * * SPDX-License-Identifier: Apache-2.0 */ -import static org.hyperledger.besu.evm.internal.Words.clampedAdd; -import static org.hyperledger.besu.evm.internal.Words.clampedToLong; +package org.hyperledger.besu.evm.gascalculator; -import org.hyperledger.besu.evm.frame.MessageFrame; +import static org.hyperledger.besu.evm.internal.Words.clampedAdd; +import static org.hyperledger.besu.evm.internal.Words.numWords; import org.apache.tuweni.bytes.Bytes; @@ -43,20 +42,14 @@ public ShanghaiGasCalculator() { public long transactionIntrinsicGasCost(final Bytes payload, final boolean isContractCreation) { long intrinsicGasCost = super.transactionIntrinsicGasCost(payload, isContractCreation); if (isContractCreation) { - return clampedAdd(intrinsicGasCost, calculateInitGasCost(payload.size())); + return clampedAdd(intrinsicGasCost, initcodeCost(payload.size())); } else { return intrinsicGasCost; } } @Override - public long createOperationGasCost(final MessageFrame frame) { - final long initCodeLength = clampedToLong(frame.getStackItem(2)); - return clampedAdd(super.createOperationGasCost(frame), calculateInitGasCost(initCodeLength)); - } - - private static long calculateInitGasCost(final long initCodeLength) { - final int dataLength = (int) Math.ceil(initCodeLength / 32.0); - return dataLength * INIT_CODE_COST; + public long initcodeCost(final int initCodeLength) { + return numWords(initCodeLength) * INIT_CODE_COST; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/SpuriousDragonGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/SpuriousDragonGasCalculator.java index 2e905c414e4..f8e20c9c078 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/SpuriousDragonGasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/SpuriousDragonGasCalculator.java @@ -26,6 +26,9 @@ public class SpuriousDragonGasCalculator extends TangerineWhistleGasCalculator { private static final long EXP_OPERATION_BYTE_GAS_COST = 50L; + /** Default constructor. */ + public SpuriousDragonGasCalculator() {} + @Override public long callOperationGasCost( final MessageFrame frame, @@ -36,7 +39,8 @@ public long callOperationGasCost( final long outputDataLength, final Wei transferValue, final Account recipient, - final Address to) { + final Address to, + final boolean accountIsWarm) { final long inputDataMemoryExpansionCost = memoryExpansionGasCost(frame, inputDataOffset, inputDataLength); final long outputDataMemoryExpansionCost = diff --git a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/TangerineWhistleGasCalculator.java b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/TangerineWhistleGasCalculator.java index a5df3a0f623..cdcab74d38c 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/TangerineWhistleGasCalculator.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/gascalculator/TangerineWhistleGasCalculator.java @@ -37,6 +37,9 @@ public class TangerineWhistleGasCalculator extends HomesteadGasCalculator { private static final long SLOAD_OPERATION_GAS_COST = 200L; + /** Default constructor. */ + public TangerineWhistleGasCalculator() {} + @Override public long getBalanceOperationGasCost() { return BALANCE_OPERATION_GAS_COST; @@ -62,7 +65,8 @@ public long callOperationGasCost( final long outputDataLength, final Wei transferValue, final Account recipient, - final Address to) { + final Address to, + final boolean accountIsWarm) { final long inputDataMemoryExpansionCost = memoryExpansionGasCost(frame, inputDataOffset, inputDataLength); final long outputDataMemoryExpansionCost = diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/CodeCache.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/CodeCache.java index 25014b093bf..a7ce5abf2e5 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/internal/CodeCache.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/internal/CodeCache.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.evm.internal; import org.hyperledger.besu.datatypes.Hash; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/CodeScale.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/CodeScale.java index cd736c3d572..a4180895b4b 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/internal/CodeScale.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/internal/CodeScale.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.evm.internal; import org.hyperledger.besu.datatypes.Hash; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/EvmConfiguration.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/EvmConfiguration.java index c3ad7ba17aa..ef7265dad9f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/internal/EvmConfiguration.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/internal/EvmConfiguration.java @@ -12,11 +12,29 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.evm.internal; -/** The Evm configuration. */ -public record EvmConfiguration(long jumpDestCacheWeightKB, WorldUpdaterMode worldUpdaterMode) { +import org.hyperledger.besu.evm.frame.MessageFrame; + +import java.util.Optional; +import java.util.OptionalInt; + +/** + * The type Evm configuration. + * + * @param jumpDestCacheWeightKB the jump destination cache weight in kb + * @param worldUpdaterMode the world updater mode + * @param evmStackSize the maximum evm stack size + * @param maxCodeSizeOverride An optional override of the maximum code size set by the EVM fork + * @param maxInitcodeSizeOverride An optional override of the maximum initcode size set by the EVM + * fork + */ +public record EvmConfiguration( + long jumpDestCacheWeightKB, + WorldUpdaterMode worldUpdaterMode, + Integer evmStackSize, + Optional maxCodeSizeOverride, + Optional maxInitcodeSizeOverride) { /** How should the world state update be handled within transactions? */ public enum WorldUpdaterMode { @@ -32,6 +50,22 @@ public enum WorldUpdaterMode { public static final EvmConfiguration DEFAULT = new EvmConfiguration(32_000L, WorldUpdaterMode.STACKED); + /** + * Create an EVM Configuration without any overrides + * + * @param jumpDestCacheWeightKilobytes the jump dest cache weight (in kibibytes) + * @param worldstateUpdateMode the workd update mode + */ + public EvmConfiguration( + final Long jumpDestCacheWeightKilobytes, final WorldUpdaterMode worldstateUpdateMode) { + this( + jumpDestCacheWeightKilobytes, + worldstateUpdateMode, + MessageFrame.DEFAULT_MAX_STACK_SIZE, + Optional.empty(), + Optional.empty()); + } + /** * Gets jump dest cache weight bytes. * @@ -40,4 +74,27 @@ public enum WorldUpdaterMode { public long getJumpDestCacheWeightBytes() { return jumpDestCacheWeightKB * 1024L; } + + /** + * Update the configuration with new overrides, or clearing the overrides with {@link + * Optional#empty} + * + * @param newMaxCodeSize a new max code size override + * @param newMaxInitcodeSize a new max initcode size override + * @param newEvmStackSize a new EVM stack size override + * @return the updated EVM configuration + */ + public EvmConfiguration overrides( + final OptionalInt newMaxCodeSize, + final OptionalInt newMaxInitcodeSize, + final OptionalInt newEvmStackSize) { + return new EvmConfiguration( + jumpDestCacheWeightKB, + worldUpdaterMode, + newEvmStackSize.orElse(MessageFrame.DEFAULT_MAX_STACK_SIZE), + newMaxCodeSize.isPresent() ? Optional.of(newMaxCodeSize.getAsInt()) : Optional.empty(), + newMaxInitcodeSize.isPresent() + ? Optional.of(newMaxInitcodeSize.getAsInt()) + : Optional.empty()); + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/FixedStack.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/FixedStack.java index 21b913f363c..9c9b47e32ca 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/internal/FixedStack.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/internal/FixedStack.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/FlexStack.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/FlexStack.java index 861cfbce8f9..dedd5c953e1 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/internal/FlexStack.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/internal/FlexStack.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/MemoryEntry.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/MemoryEntry.java index 6e0fbb098e8..193ed03dfe4 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/internal/MemoryEntry.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/internal/MemoryEntry.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.evm.internal; import org.apache.tuweni.bytes.Bytes; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/OperandStack.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/OperandStack.java index df27579a297..a88c0e65ca5 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/internal/OperandStack.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/internal/OperandStack.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.evm.internal; import org.apache.tuweni.bytes.Bytes; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/OverflowException.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/OverflowException.java index f2e4efba353..0621bbc3244 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/internal/OverflowException.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/internal/OverflowException.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -21,6 +21,9 @@ */ public class OverflowException extends RuntimeException { + /** Default constructor. */ + public OverflowException() {} + /** * Overload the stack trace fill in so no stack is filled in. This is done for performance reasons * as this exception signals an expected corner case not a debuggable failure. diff --git a/evm/src/main/java/org/hyperledger/besu/evm/internal/ReturnStack.java b/evm/src/main/java/org/hyperledger/besu/evm/internal/ReturnStack.java index 565f9c8cdcf..5601ca381a6 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/internal/ReturnStack.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/internal/ReturnStack.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,89 +14,16 @@ */ package org.hyperledger.besu.evm.internal; -import java.util.Objects; - /** The type Return stack. */ public class ReturnStack extends FlexStack { - /** The type Return stack item. */ - // Java17 convert to record - public static final class ReturnStackItem { - - /** The Code section index. */ - final int codeSectionIndex; - /** The Pc. */ - final int pc; - /** The Stack height. */ - final int stackHeight; - - /** - * Instantiates a new Return stack item. - * - * @param codeSectionIndex the code section index - * @param pc the pc - * @param stackHeight the stack height - */ - public ReturnStackItem(final int codeSectionIndex, final int pc, final int stackHeight) { - this.codeSectionIndex = codeSectionIndex; - this.pc = pc; - this.stackHeight = stackHeight; - } - - /** - * Gets code section index. - * - * @return the code section index - */ - public int getCodeSectionIndex() { - return codeSectionIndex; - } - - /** - * Gets pc. - * - * @return the pc - */ - public int getPC() { - return pc; - } - - /** - * Gets stack height. - * - * @return the stack height - */ - public int getStackHeight() { - return stackHeight; - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ReturnStackItem that = (ReturnStackItem) o; - return codeSectionIndex == that.codeSectionIndex - && pc == that.pc - && stackHeight == that.stackHeight; - } - - @Override - public int hashCode() { - return Objects.hash(codeSectionIndex, pc, stackHeight); - } - - @Override - public String toString() { - return "ReturnStackItem{" - + "codeSectionIndex=" - + codeSectionIndex - + ", pc=" - + pc - + ", stackHeight=" - + stackHeight - + '}'; - } - } + /** + * The type Return stack item. + * + * @param codeSectionIndex the code section index + * @param pc the pc + */ + public record ReturnStackItem(int codeSectionIndex, int pc) {} /** * Max return stack size specified in In other words, this computes {@code input.size() / 32} but rounded up. + * + * @param length the byte length to check + * @return the number of (32 bytes) words that {@code input} spans. + */ + static int numWords(final int length) { + // m/n round up == (m + n - 1)/n: http://www.cs.nott.ac.uk/~psarb2/G51MPC/slides/NumberLogic.pdf + return (length + Bytes32.SIZE - 1) / Bytes32.SIZE; + } + + /** + * The value of the bytes as though it was representing an unsigned long, however if the value * exceeds Long.MAX_VALUE then Long.MAX_VALUE will be returned. * * @param uint the unsigned integer @@ -92,6 +107,29 @@ static long clampedToLong(final Bytes uint) { } } + /** + * The value of the bytes as though it was representing an unsigned integer, however if the value + * exceeds Integer.MAX_VALUE then Integer.MAX_VALUE will be returned. + * + * @param uint the unsigned integer + * @return the least of the integer value or Integer.MAX_VALUE + */ + static int clampedToInt(final Bytes uint) { + if (uint.size() <= 4) { + final int result = uint.toInt(); + return result < 0 ? Integer.MAX_VALUE : result; + } + + final Bytes trimmed = uint.trimLeadingZeros(); + if (trimmed.size() <= 4) { + final int result = trimmed.toInt(); + return result < 0 ? Integer.MAX_VALUE : result; + } else { + // clamp to the largest int. + return Integer.MAX_VALUE; + } + } + /** * The value of the long as though it was representing an unsigned integer, however if the value * is out of range it will return the number at the end of the range. @@ -168,7 +206,7 @@ static int readBigEndianU16(final int index, final byte[] array) { if (index + 1 >= array.length) { throw new IndexOutOfBoundsException(); } - return ((array[index] & 0xff) << 8) | (array[index + 1] & 0xff); + return ((array[index] << 8) & 0xff00) | (array[index + 1] & 0xff); } /** @@ -213,4 +251,29 @@ static Bytes longBytes(final long value) { (byte) (value >>> 8), (byte) value); } + + /** + * Utility to decode string to unsigned long + * + * @param number to be decoded + * @return long value, unsigned + */ + static long decodeUnsignedLong(final String number) { + String parsable = number; + int radix = 10; + if (number.startsWith("0x")) { + radix = 16; + parsable = number.substring(2); + } else if (!number.matches("\\d+")) { + // presume naked hex + radix = 16; + } + + BigInteger bi = new BigInteger(parsable, radix); + if (bi.bitCount() > 64) { + throw new NumberFormatException("Number larger than uint64"); + } + + return bi.longValue(); + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/log/Log.java b/evm/src/main/java/org/hyperledger/besu/evm/log/Log.java index 02b523445e7..0c578dfd2f9 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/log/Log.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/log/Log.java @@ -26,6 +26,8 @@ import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.bytes.MutableBytes; /** * A log entry is a tuple of a logger’s address (the address of the contract that added the logs), a @@ -60,13 +62,38 @@ public Log( * @param out the output in which to encode the log entry. */ public void writeTo(final RLPOutput out) { + writeTo(out, false); + } + + /** + * Writes the log entry to the provided RLP output. + * + * @param out the output in which to encode the log entry. + * @param compacted whether to compact the rlp log entry by trimming leading zeros on topics and + * data. + */ + public void writeTo(final RLPOutput out, final boolean compacted) { out.startList(); out.writeBytes(logger); - out.writeList(topics, (topic, listOut) -> listOut.writeBytes(topic)); - out.writeBytes(data); + if (compacted) { + out.writeList(topics, (topic, listOut) -> encodeTrimmedData(listOut, topic)); + encodeTrimmedData(out, data); + } else { + out.writeList(topics, (topic, listOut) -> listOut.writeBytes(topic)); + out.writeBytes(data); + } out.endList(); } + private void encodeTrimmedData(final RLPOutput rlpOutput, final Bytes data) { + rlpOutput.startList(); + final Bytes shortData = data.trimLeadingZeros(); + final int zeroLeadDataSize = data.size() - shortData.size(); + rlpOutput.writeIntScalar(zeroLeadDataSize); + rlpOutput.writeBytes(shortData); + rlpOutput.endList(); + } + /** * Reads the log entry from the provided RLP input. * @@ -74,14 +101,45 @@ public void writeTo(final RLPOutput out) { * @return the read log entry. */ public static Log readFrom(final RLPInput in) { + return readFrom(in, false); + } + + /** + * Reads the log entry from the provided RLP input. + * + * @param in the input from which to decode the log entry. + * @param compacted whether to compact the rlp log entry by trimming leading zeros on topics and + * data. + * @return the read log entry. + */ + public static Log readFrom(final RLPInput in, final boolean compacted) { in.enterList(); final Address logger = Address.wrap(in.readBytes()); - final List topics = in.readList(listIn -> LogTopic.wrap(listIn.readBytes32())); - final Bytes data = in.readBytes(); + + final List topics; + final Bytes data; + if (compacted) { + topics = in.readList(listIn -> LogTopic.wrap(Bytes32.wrap(readTrimmedData(in)))); + data = Bytes.wrap(readTrimmedData(in)); + } else { + topics = in.readList(listIn -> LogTopic.wrap(listIn.readBytes32())); + data = in.readBytes(); + } + in.leaveList(); return new Log(logger, data, topics); } + private static Bytes readTrimmedData(final RLPInput in) { + in.enterList(); + final int zeroLeadDataSize = in.readIntScalar(); + final Bytes shortData = in.readBytes(); + final MutableBytes data = MutableBytes.create(zeroLeadDataSize + shortData.size()); + data.set(zeroLeadDataSize, shortData); + in.leaveList(); + return data; + } + /** * Gets logger address. * diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java index 64cb42757ea..932bb4c3915 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCallOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,6 +15,7 @@ package org.hyperledger.besu.evm.operation; import static org.hyperledger.besu.evm.internal.Words.clampedToLong; +import static org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper.deductDelegatedCodeGasCost; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; @@ -24,7 +25,9 @@ import org.hyperledger.besu.evm.code.CodeV0; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.frame.MessageFrame.State; import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper; import org.apache.tuweni.bytes.Bytes; @@ -40,6 +43,9 @@ public abstract class AbstractCallOperation extends AbstractOperation { protected static final OperationResult UNDERFLOW_RESPONSE = new OperationResult(0L, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); + static final Bytes LEGACY_SUCCESS_STACK_ITEM = BYTES_ONE; + static final Bytes LEGACY_FAILURE_STACK_ITEM = Bytes.EMPTY; + /** * Instantiates a new Abstract call operation. * @@ -146,7 +152,7 @@ protected long gas(final MessageFrame frame) { * @param frame The current message frame * @return the gas available to execute the child message call */ - protected abstract long gasAvailableForChildCall(MessageFrame frame); + public abstract long gasAvailableForChildCall(MessageFrame frame); /** * Returns whether the child message call should be static. @@ -154,7 +160,18 @@ protected long gas(final MessageFrame frame) { * @param frame The current message frame * @return {@code true} if the child message call should be static; otherwise {@code false} */ - protected abstract boolean isStatic(MessageFrame frame); + protected boolean isStatic(final MessageFrame frame) { + return frame.isStatic(); + } + + /** + * Returns whether the child message call is a delegate call. + * + * @return {@code true} if the child message call is a delegate call; otherwise {@code false} + */ + protected boolean isDelegate() { + return false; + } @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { @@ -163,7 +180,9 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { return UNDERFLOW_RESPONSE; } - final long cost = cost(frame); + final Address to = to(frame); + final boolean accountIsWarm = frame.warmUpAddress(to) || gasCalculator().isPrecompile(to); + final long cost = cost(frame, accountIsWarm); if (frame.getRemainingGas() < cost) { return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); } @@ -171,9 +190,17 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { frame.clearReturnData(); - final Address to = to(frame); final Account contract = frame.getWorldUpdater().get(to); + if (contract != null) { + final DelegatedCodeGasCostHelper.Result result = + deductDelegatedCodeGasCost(frame, gasCalculator(), contract); + if (result.status() != DelegatedCodeGasCostHelper.Status.SUCCESS) { + return new Operation.OperationResult( + result.gasCost(), ExceptionalHaltReason.INSUFFICIENT_GAS); + } + } + final Account account = frame.getWorldUpdater().get(frame.getRecipientAddress()); final Wei balance = account == null ? Wei.ZERO : account.getBalance(); // If the call is sending more value than the account has or the message frame is to deep @@ -181,9 +208,11 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { if (value(frame).compareTo(balance) > 0 || frame.getDepth() >= 1024) { frame.expandMemory(inputDataOffset(frame), inputDataLength(frame)); frame.expandMemory(outputDataOffset(frame), outputDataLength(frame)); + // For the following, we either increment the gas or return zero, so we don't get double + // charged. If we return zero then the traces don't have the right per-opcode cost. frame.incrementRemainingGas(gasAvailableForChildCall(frame) + cost); frame.popStackItems(getStackItemsConsumed()); - frame.pushStackItem(FAILURE_STACK_ITEM); + frame.pushStackItem(LEGACY_FAILURE_STACK_ITEM); return new OperationResult(cost, null); } @@ -194,29 +223,30 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { ? CodeV0.EMPTY_CODE : evm.getCode(contract.getCodeHash(), contract.getCode()); - if (code.isValid()) { - // frame addition is automatically handled by parent messageFrameStack - MessageFrame.builder() - .parentMessageFrame(frame) - .type(MessageFrame.Type.MESSAGE_CALL) - .initialGas(gasAvailableForChildCall(frame)) - .address(address(frame)) - .contract(to) - .inputData(inputData) - .sender(sender(frame)) - .value(value(frame)) - .apparentValue(apparentValue(frame)) - .code(code) - .isStatic(isStatic(frame)) - .completer(child -> complete(frame, child)) - .build(); - frame.incrementRemainingGas(cost); - - frame.setState(MessageFrame.State.CODE_SUSPENDED); - return new OperationResult(cost, null, 0); - } else { + // invalid code results in a quick exit + if (!code.isValid()) { return new OperationResult(cost, ExceptionalHaltReason.INVALID_CODE, 0); } + + MessageFrame.builder() + .parentMessageFrame(frame) + .type(MessageFrame.Type.MESSAGE_CALL) + .initialGas(gasAvailableForChildCall(frame)) + .address(address(frame)) + .contract(to) + .inputData(inputData) + .sender(sender(frame)) + .value(value(frame)) + .apparentValue(apparentValue(frame)) + .code(code) + .isStatic(isStatic(frame)) + .completer(child -> complete(frame, child)) + .build(); + // see note in stack depth check about incrementing cost + frame.incrementRemainingGas(cost); + + frame.setState(MessageFrame.State.CODE_SUSPENDED); + return new OperationResult(cost, null, 0); } /** @@ -224,8 +254,43 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { * * @param frame the frame * @return the long + * @deprecated use the form with the `accountIsWarm` boolean */ - protected abstract long cost(final MessageFrame frame); + @Deprecated(since = "24.2.0", forRemoval = true) + @SuppressWarnings("InlineMeSuggester") // downstream users override, so @InlineMe is inappropriate + public long cost(final MessageFrame frame) { + return cost(frame, true); + } + + /** + * Calculates Cost. + * + * @param frame the frame + * @param accountIsWarm whether the contract being called is "warm" as per EIP-2929. + * @return the long + */ + public long cost(final MessageFrame frame, final boolean accountIsWarm) { + final long stipend = gas(frame); + final long inputDataOffset = inputDataOffset(frame); + final long inputDataLength = inputDataLength(frame); + final long outputDataOffset = outputDataOffset(frame); + final long outputDataLength = outputDataLength(frame); + final Account recipient = frame.getWorldUpdater().get(address(frame)); + final Address to = to(frame); + GasCalculator gasCalculator = gasCalculator(); + + return gasCalculator.callOperationGasCost( + frame, + stipend, + inputDataOffset, + inputDataLength, + outputDataOffset, + outputDataLength, + value(frame), + recipient, + to, + accountIsWarm); + } /** * Complete. @@ -243,7 +308,7 @@ public void complete(final MessageFrame frame, final MessageFrame childFrame) { if (outputSize > outputData.size()) { frame.expandMemory(outputOffset, outputSize); frame.writeMemory(outputOffset, outputData.size(), outputData, true); - } else { + } else if (outputSize > 0) { frame.writeMemory(outputOffset, outputSize, outputData, true); } @@ -251,19 +316,25 @@ public void complete(final MessageFrame frame, final MessageFrame childFrame) { frame.addLogs(childFrame.getLogs()); frame.addSelfDestructs(childFrame.getSelfDestructs()); frame.addCreates(childFrame.getCreates()); - frame.incrementGasRefund(childFrame.getGasRefund()); final long gasRemaining = childFrame.getRemainingGas(); frame.incrementRemainingGas(gasRemaining); frame.popStackItems(getStackItemsConsumed()); - if (childFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { - frame.pushStackItem(SUCCESS_STACK_ITEM); - } else { - frame.pushStackItem(FAILURE_STACK_ITEM); - } + Bytes resultItem; + + resultItem = getCallResultStackItem(childFrame); + frame.pushStackItem(resultItem); final int currentPC = frame.getPC(); frame.setPC(currentPC + 1); } + + Bytes getCallResultStackItem(final MessageFrame childFrame) { + if (childFrame.getState() == State.COMPLETED_SUCCESS) { + return LEGACY_SUCCESS_STACK_ITEM; + } else { + return LEGACY_FAILURE_STACK_ITEM; + } + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java index d695edf1c8c..5d9bea61ce0 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractCreateOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,13 +15,13 @@ package org.hyperledger.besu.evm.operation; import static org.hyperledger.besu.evm.internal.Words.clampedToLong; +import static org.hyperledger.besu.evm.operation.AbstractCallOperation.LEGACY_FAILURE_STACK_ITEM; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.account.MutableAccount; -import org.hyperledger.besu.evm.code.CodeFactory; import org.hyperledger.besu.evm.code.CodeInvalid; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; @@ -29,7 +29,9 @@ import org.hyperledger.besu.evm.internal.Words; import java.util.Optional; +import java.util.function.Supplier; +import com.google.common.base.Suppliers; import org.apache.tuweni.bytes.Bytes; /** The Abstract create operation. */ @@ -39,8 +41,12 @@ public abstract class AbstractCreateOperation extends AbstractOperation { protected static final OperationResult UNDERFLOW_RESPONSE = new OperationResult(0L, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); - /** The maximum init code size */ - protected int maxInitcodeSize; + /** The constant UNDERFLOW_RESPONSE. */ + protected static final OperationResult INVALID_OPERATION = + new OperationResult(0L, ExceptionalHaltReason.INVALID_OPERATION); + + /** The EOF Version this create operation requires initcode to be in */ + protected final int eofVersion; /** * Instantiates a new Abstract create operation. @@ -50,7 +56,7 @@ public abstract class AbstractCreateOperation extends AbstractOperation { * @param stackItemsConsumed the stack items consumed * @param stackItemsProduced the stack items produced * @param gasCalculator the gas calculator - * @param maxInitcodeSize Maximum init code size + * @param eofVersion the EOF version this create operation is valid in */ protected AbstractCreateOperation( final int opcode, @@ -58,19 +64,25 @@ protected AbstractCreateOperation( final int stackItemsConsumed, final int stackItemsProduced, final GasCalculator gasCalculator, - final int maxInitcodeSize) { + final int eofVersion) { super(opcode, name, stackItemsConsumed, stackItemsProduced, gasCalculator); - this.maxInitcodeSize = maxInitcodeSize; + this.eofVersion = eofVersion; } @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { + if (frame.getCode().getEofVersion() != eofVersion) { + return INVALID_OPERATION; + } + // manual check because some reads won't come until the "complete" step. if (frame.stackSize() < getStackItemsConsumed()) { return UNDERFLOW_RESPONSE; } - final long cost = cost(frame); + Supplier codeSupplier = Suppliers.memoize(() -> getInitCode(frame, evm)); + + final long cost = cost(frame, codeSupplier); if (frame.isStatic()) { return new OperationResult(cost, ExceptionalHaltReason.ILLEGAL_STATE_CHANGE); } else if (frame.getRemainingGas() < cost) { @@ -82,66 +94,83 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { final MutableAccount account = frame.getWorldUpdater().getAccount(address); frame.clearReturnData(); - final long inputOffset = clampedToLong(frame.getStackItem(1)); - final long inputSize = clampedToLong(frame.getStackItem(2)); - if (inputSize > maxInitcodeSize) { + + Code code = codeSupplier.get(); + + if (code != null && code.getSize() > evm.getMaxInitcodeSize()) { frame.popStackItems(getStackItemsConsumed()); return new OperationResult(cost, ExceptionalHaltReason.CODE_TOO_LARGE); } if (value.compareTo(account.getBalance()) > 0 || frame.getDepth() >= 1024 - || account.getNonce() == -1) { + || account.getNonce() == -1 + || code == null + || code.getEofVersion() != frame.getCode().getEofVersion()) { fail(frame); } else { account.incrementNonce(); - final Bytes inputData = frame.readMemory(inputOffset, inputSize); - // Never cache CREATEx initcode. The amount of reuse is very low, and caching mostly - // addresses disk loading delay, and we already have the code. - Code code = evm.getCode(null, inputData); - - if (code.isValid() && frame.getCode().getEofVersion() <= code.getEofVersion()) { + if (!code.isValid()) { + fail(frame); + } else { frame.decrementRemainingGas(cost); spawnChildMessage(frame, code, evm); frame.incrementRemainingGas(cost); - } else { - fail(frame); } } + return new OperationResult(cost, null, getPcIncrement()); + } - return new OperationResult(cost, null); + /** + * How many bytes does this operation occupy? + * + * @return The number of bytes the operation and immediate arguments occupy + */ + protected int getPcIncrement() { + return 1; } /** * Cost operation. * * @param frame the frame + * @param codeSupplier a supplier for the initcode, if needed for costing * @return the long */ - protected abstract long cost(final MessageFrame frame); + protected abstract long cost(final MessageFrame frame, Supplier codeSupplier); /** * Target contract address. * * @param frame the frame + * @param initcode the initcode for the new contract. * @return the address */ - protected abstract Address targetContractAddress(MessageFrame frame); + protected abstract Address generateTargetContractAddress(MessageFrame frame, Code initcode); + + /** + * Gets the initcode that will be run. + * + * @param frame The message frame the operation executed in + * @param evm the EVM executing the message frame + * @return the initcode, raw bytes, unparsed and unvalidated + */ + protected abstract Code getInitCode(MessageFrame frame, EVM evm); private void fail(final MessageFrame frame) { final long inputOffset = clampedToLong(frame.getStackItem(1)); final long inputSize = clampedToLong(frame.getStackItem(2)); frame.readMutableMemory(inputOffset, inputSize); frame.popStackItems(getStackItemsConsumed()); - frame.pushStackItem(FAILURE_STACK_ITEM); + frame.pushStackItem(LEGACY_FAILURE_STACK_ITEM); } private void spawnChildMessage(final MessageFrame parent, final Code code, final EVM evm) { final Wei value = Wei.wrap(parent.getStackItem(0)); - final Address contractAddress = targetContractAddress(parent); - parent.addCreate(contractAddress); + final Address contractAddress = generateTargetContractAddress(parent, code); + final Bytes inputData = getInputData(parent); final long childGasStipend = gasCalculator().gasAvailableForChildCreate(parent.getRemainingGas()); @@ -154,7 +183,7 @@ private void spawnChildMessage(final MessageFrame parent, final Code code, final .initialGas(childGasStipend) .address(contractAddress) .contract(contractAddress) - .inputData(Bytes.EMPTY) + .inputData(inputData) .sender(parent.getRecipientAddress()) .value(value) .apparentValue(value) @@ -165,38 +194,50 @@ private void spawnChildMessage(final MessageFrame parent, final Code code, final parent.setState(MessageFrame.State.CODE_SUSPENDED); } + /** + * Get the input data to be appended to the EOF factory contract. For CREATE and CREATE2 this is + * always empty + * + * @param frame the message frame the operation was called in + * @return the input data as raw bytes, or `Bytes.EMPTY` if there is no aux data + */ + protected Bytes getInputData(final MessageFrame frame) { + return Bytes.EMPTY; + } + private void complete(final MessageFrame frame, final MessageFrame childFrame, final EVM evm) { frame.setState(MessageFrame.State.CODE_EXECUTING); - Code outputCode = - CodeFactory.createCode(childFrame.getOutputData(), evm.getMaxEOFVersion(), true); + frame.incrementRemainingGas(childFrame.getRemainingGas()); + frame.addLogs(childFrame.getLogs()); + frame.addSelfDestructs(childFrame.getSelfDestructs()); + frame.addCreates(childFrame.getCreates()); frame.popStackItems(getStackItemsConsumed()); - if (outputCode.isValid()) { - frame.incrementRemainingGas(childFrame.getRemainingGas()); - frame.addLogs(childFrame.getLogs()); - frame.addSelfDestructs(childFrame.getSelfDestructs()); - frame.addCreates(childFrame.getCreates()); - frame.incrementGasRefund(childFrame.getGasRefund()); - - if (childFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { + if (childFrame.getState() == MessageFrame.State.COMPLETED_SUCCESS) { + Code outputCode = + (childFrame.getCreatedCode() != null) + ? childFrame.getCreatedCode() + : evm.getCodeForCreation(childFrame.getOutputData()); + if (outputCode.isValid()) { Address createdAddress = childFrame.getContractAddress(); frame.pushStackItem(Words.fromAddress(createdAddress)); + frame.setReturnData(Bytes.EMPTY); onSuccess(frame, createdAddress); } else { + frame.getWorldUpdater().deleteAccount(childFrame.getRecipientAddress()); frame.setReturnData(childFrame.getOutputData()); - frame.pushStackItem(FAILURE_STACK_ITEM); - onFailure(frame, childFrame.getExceptionalHaltReason()); + frame.pushStackItem(LEGACY_FAILURE_STACK_ITEM); + onInvalid(frame, (CodeInvalid) outputCode); } } else { - frame.getWorldUpdater().deleteAccount(childFrame.getRecipientAddress()); frame.setReturnData(childFrame.getOutputData()); - frame.pushStackItem(FAILURE_STACK_ITEM); - onInvalid(frame, (CodeInvalid) outputCode); + frame.pushStackItem(LEGACY_FAILURE_STACK_ITEM); + onFailure(frame, childFrame.getExceptionalHaltReason()); } final int currentPC = frame.getPC(); - frame.setPC(currentPC + 1); + frame.setPC(currentPC + getPcIncrement()); } /** diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractExtCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractExtCallOperation.java new file mode 100644 index 00000000000..c82e8c1633d --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractExtCallOperation.java @@ -0,0 +1,225 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import static org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper.deductDelegatedCodeGasCost; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.code.CodeV0; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.internal.Words; +import org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper; + +import javax.annotation.Nonnull; + +import org.apache.tuweni.bytes.Bytes; + +/** + * A skeleton class for implementing call operations. + * + *

    A call operation creates a child message call from the current message context, allows it to + * execute, and then updates the current message context based on its execution. + */ +public abstract class AbstractExtCallOperation extends AbstractCallOperation { + + static final int STACK_TO = 0; + static final int STACK_INPUT_OFFSET = 1; + static final int STACK_INPUT_LENGTH = 2; + + /** EXT*CALL response indicating success */ + public static final Bytes EOF1_SUCCESS_STACK_ITEM = Bytes.EMPTY; + + /** EXT*CALL response indicating a "soft failure" */ + public static final Bytes EOF1_EXCEPTION_STACK_ITEM = BYTES_ONE; + + /** EXT*CALL response indicating a hard failure, such as a REVERT was called */ + public static final Bytes EOF1_FAILURE_STACK_ITEM = Bytes.of(2); + + /** + * Instantiates a new Abstract call operation. + * + * @param opcode the opcode + * @param name the name + * @param stackItemsConsumed the stack items consumed + * @param stackItemsProduced the stack items produced + * @param gasCalculator the gas calculator + */ + AbstractExtCallOperation( + final int opcode, + final String name, + final int stackItemsConsumed, + final int stackItemsProduced, + final GasCalculator gasCalculator) { + super(opcode, name, stackItemsConsumed, stackItemsProduced, gasCalculator); + } + + @Override + protected Address to(final MessageFrame frame) { + return Words.toAddress(frame.getStackItem(STACK_TO)); + } + + @Override + protected long gas(final MessageFrame frame) { + return Long.MAX_VALUE; + } + + @Override + protected long outputDataOffset(final MessageFrame frame) { + return 0; + } + + @Override + protected long outputDataLength(final MessageFrame frame) { + return 0; + } + + @Override + public long gasAvailableForChildCall(final MessageFrame frame) { + throw new UnsupportedOperationException("EXTCALL does not use gasAvailableForChildCall"); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + Code callingCode = frame.getCode(); + if (callingCode.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } + + final Bytes toBytes = frame.getStackItem(STACK_TO).trimLeadingZeros(); + final Wei value = value(frame); + final boolean zeroValue = value.isZero(); + long inputOffset = inputDataOffset(frame); + long inputLength = inputDataLength(frame); + + if (!zeroValue && isStatic(frame)) { + return new OperationResult( + gasCalculator().callValueTransferGasCost(), ExceptionalHaltReason.ILLEGAL_STATE_CHANGE); + } + if (toBytes.size() > Address.SIZE) { + return new OperationResult( + gasCalculator().memoryExpansionGasCost(frame, inputOffset, inputLength) + + (zeroValue ? 0 : gasCalculator().callValueTransferGasCost()) + + gasCalculator().getColdAccountAccessCost(), + ExceptionalHaltReason.ADDRESS_OUT_OF_RANGE); + } + Address to = Words.toAddress(toBytes); + final Account contract = frame.getWorldUpdater().get(to); + + if (contract != null) { + final DelegatedCodeGasCostHelper.Result result = + deductDelegatedCodeGasCost(frame, gasCalculator(), contract); + if (result.status() != DelegatedCodeGasCostHelper.Status.SUCCESS) { + return new Operation.OperationResult( + result.gasCost(), ExceptionalHaltReason.INSUFFICIENT_GAS); + } + } + + boolean accountCreation = contract == null && !zeroValue; + long cost = + gasCalculator().memoryExpansionGasCost(frame, inputOffset, inputLength) + + (zeroValue ? 0 : gasCalculator().callValueTransferGasCost()) + + (frame.warmUpAddress(to) + ? gasCalculator().getWarmStorageReadCost() + : gasCalculator().getColdAccountAccessCost()) + + (accountCreation ? gasCalculator().newAccountGasCost() : 0); + long currentGas = frame.getRemainingGas() - cost; + if (currentGas < 0) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + final Code code = + contract == null + ? CodeV0.EMPTY_CODE + : evm.getCode(contract.getCodeHash(), contract.getCode()); + + // invalid code results in a quick exit + if (!code.isValid()) { + return new OperationResult(cost, ExceptionalHaltReason.INVALID_CODE, 0); + } + + // last exceptional failure, prepare for call or soft failures + frame.clearReturnData(); + + // delegate calls to prior EOF versions are prohibited + if (isDelegate() && callingCode.getEofVersion() != code.getEofVersion()) { + return softFailure(frame, cost); + } + + long retainedGas = Math.max(currentGas / 64, gasCalculator().getMinRetainedGas()); + long childGas = currentGas - retainedGas; + + final Account account = frame.getWorldUpdater().get(frame.getRecipientAddress()); + final Wei balance = (zeroValue || account == null) ? Wei.ZERO : account.getBalance(); + + // There myst be a minimum gas for a call to have access to. + if (childGas < gasCalculator().getMinCalleeGas()) { + return softFailure(frame, cost); + } + // transferring value you don't have is not a halting exception, just a failure + if (!zeroValue && (value.compareTo(balance) > 0)) { + return softFailure(frame, cost); + } + // stack too deep, for large gas systems. + if (frame.getDepth() >= 1024) { + return softFailure(frame, cost); + } + + // all checks passed, do the call + final Bytes inputData = frame.readMutableMemory(inputOffset, inputLength); + + MessageFrame.builder() + .parentMessageFrame(frame) + .type(MessageFrame.Type.MESSAGE_CALL) + .initialGas(childGas) + .address(address(frame)) + .contract(to) + .inputData(inputData) + .sender(sender(frame)) + .value(value(frame)) + .apparentValue(apparentValue(frame)) + .code(code) + .isStatic(isStatic(frame)) + .completer(child -> complete(frame, child)) + .build(); + + frame.setState(MessageFrame.State.CODE_SUSPENDED); + return new OperationResult(cost + childGas, null, 0); + } + + private @Nonnull OperationResult softFailure(final MessageFrame frame, final long cost) { + frame.popStackItems(getStackItemsConsumed()); + frame.pushStackItem(EOF1_EXCEPTION_STACK_ITEM); + return new OperationResult(cost, null); + } + + @Override + Bytes getCallResultStackItem(final MessageFrame childFrame) { + return switch (childFrame.getState()) { + case COMPLETED_SUCCESS -> EOF1_SUCCESS_STACK_ITEM; + case EXCEPTIONAL_HALT -> EOF1_EXCEPTION_STACK_ITEM; + case COMPLETED_FAILED -> + childFrame.getExceptionalHaltReason().isPresent() + ? EOF1_FAILURE_STACK_ITEM + : EOF1_EXCEPTION_STACK_ITEM; + default -> EOF1_FAILURE_STACK_ITEM; + }; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractFixedCostOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractFixedCostOperation.java index 30a7b79305e..bc9058c7f73 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractFixedCostOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractFixedCostOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.evm.operation; import org.hyperledger.besu.evm.EVM; @@ -28,11 +26,13 @@ abstract class AbstractFixedCostOperation extends AbstractOperation { /** The Success response. */ protected final OperationResult successResponse; + /** The Out of gas response. */ protected final OperationResult outOfGasResponse; private final OperationResult underflowResponse; private final OperationResult overflowResponse; + /** The Gas cost. */ protected final long gasCost; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractOperation.java index 58ebc5b43e0..82b20b268bc 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AbstractOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -25,8 +25,6 @@ public abstract class AbstractOperation implements Operation { static final Bytes BYTES_ONE = Bytes.of(1); - static final Bytes SUCCESS_STACK_ITEM = BYTES_ONE; - static final Bytes FAILURE_STACK_ITEM = Bytes.EMPTY; private final int opcode; private final String name; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AddModOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AddModOperation.java index 5d3586d7d88..4e21e36bc20 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AddModOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AddModOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -56,7 +56,7 @@ public static OperationResult staticOperation(final MessageFrame frame) { final Bytes value2 = frame.popStackItem(); if (value2.isZero()) { - frame.pushStackItem(FAILURE_STACK_ITEM); + frame.pushStackItem(Bytes.EMPTY); } else { BigInteger b0 = new BigInteger(1, value0.toArrayUnsafe()); BigInteger b1 = new BigInteger(1, value1.toArrayUnsafe()); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AddOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AddOperation.java index fd1f0ffe941..976e30c1d5d 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AddOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AddOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AddressOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AddressOperation.java index 6f517639764..2a159ea9de2 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AddressOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AddressOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/AndOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/AndOperation.java index 726a2df7d12..21047b78c2f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/AndOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/AndOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/BalanceOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/BalanceOperation.java index 18538413355..3f1ce5611df 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/BalanceOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/BalanceOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/BaseFeeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/BaseFeeOperation.java index e65f7f139c4..e403fafdb72 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/BaseFeeOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/BaseFeeOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/BlobBaseFeeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/BlobBaseFeeOperation.java index a7168473882..f4a87c8da76 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/BlobBaseFeeOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/BlobBaseFeeOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/BlobHashOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/BlobHashOperation.java index 3fe212b8b35..8b00d2fc35c 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/BlobHashOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/BlobHashOperation.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/BlockHashOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/BlockHashOperation.java index a9bb5c08363..019e1a7237e 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/BlockHashOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/BlockHashOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -29,7 +29,18 @@ /** The Block hash operation. */ public class BlockHashOperation extends AbstractFixedCostOperation { - private static final int MAX_RELATIVE_BLOCK = 255; + /** + * Function that gets the block hash, passed in as part of TxValues. + * + *

    Arg is the current block number. The Result is the Hash, which may be zero based on lookup + * rules. + */ + public interface BlockHashLookup extends Function {} + + /** Frontier maximum relative block delta */ + public static final int MAX_RELATIVE_BLOCK = 256; + + private static final int MAX_BLOCK_ARG_SIZE = 8; /** * Instantiates a new Block hash operation. @@ -46,7 +57,7 @@ public Operation.OperationResult executeFixedCostOperation( final Bytes blockArg = frame.popStackItem().trimLeadingZeros(); // Short-circuit if value is unreasonably large - if (blockArg.size() > 8) { + if (blockArg.size() > MAX_BLOCK_ARG_SIZE) { frame.pushStackItem(UInt256.ZERO); return successResponse; } @@ -54,16 +65,15 @@ public Operation.OperationResult executeFixedCostOperation( final long soughtBlock = blockArg.toLong(); final BlockValues blockValues = frame.getBlockValues(); final long currentBlockNumber = blockValues.getNumber(); - final long mostRecentBlockNumber = currentBlockNumber - 1; // If the current block is the genesis block or the sought block is // not within the last 256 completed blocks, zero is returned. if (currentBlockNumber == 0 - || soughtBlock < (mostRecentBlockNumber - MAX_RELATIVE_BLOCK) - || soughtBlock > mostRecentBlockNumber) { + || soughtBlock >= currentBlockNumber + || soughtBlock < (currentBlockNumber - MAX_RELATIVE_BLOCK)) { frame.pushStackItem(Bytes32.ZERO); } else { - final Function blockHashLookup = frame.getBlockHashLookup(); + final BlockHashLookup blockHashLookup = frame.getBlockHashLookup(); final Hash blockHash = blockHashLookup.apply(soughtBlock); frame.pushStackItem(blockHash); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ByteOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ByteOperation.java index d8f67570a10..e615c20ec2a 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ByteOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ByteOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallCodeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallCodeOperation.java index d2c78a2f500..471da16906d 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallCodeOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallCodeOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -18,7 +18,6 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.internal.Words; @@ -84,31 +83,4 @@ protected Address sender(final MessageFrame frame) { public long gasAvailableForChildCall(final MessageFrame frame) { return gasCalculator().gasAvailableForChildCall(frame, gas(frame), !value(frame).isZero()); } - - @Override - protected boolean isStatic(final MessageFrame frame) { - return frame.isStatic(); - } - - @Override - public long cost(final MessageFrame frame) { - final long stipend = gas(frame); - final long inputDataOffset = inputDataOffset(frame); - final long inputDataLength = inputDataLength(frame); - final long outputDataOffset = outputDataOffset(frame); - final long outputDataLength = outputDataLength(frame); - final Account recipient = frame.getWorldUpdater().get(address(frame)); - - return gasCalculator() - .callOperationGasCost( - frame, - stipend, - inputDataOffset, - inputDataLength, - outputDataOffset, - outputDataLength, - value(frame), - recipient, - to(frame)); - } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallDataCopyOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallDataCopyOperation.java index 3cdbd4f4b5c..f863d1b64af 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallDataCopyOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallDataCopyOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallDataLoadOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallDataLoadOperation.java index f3c63db1fc3..b1ea8f781a9 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallDataLoadOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallDataLoadOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallDataSizeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallDataSizeOperation.java index 49f8693279b..4fc0ba5295e 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallDataSizeOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallDataSizeOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallFOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallFOperation.java index 998d8a2e923..c6990634155 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallFOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallFOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,17 +14,19 @@ */ package org.hyperledger.besu.evm.operation; -import static org.hyperledger.besu.evm.internal.Words.readBigEndianU16; - +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.code.CodeSection; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.internal.ReturnStack; /** The Call F operation. */ public class CallFOperation extends AbstractOperation { /** The constant OPCODE. */ public static final int OPCODE = 0xe3; + /** The Call F success. */ static final OperationResult callfSuccess = new OperationResult(5, null); @@ -39,26 +41,18 @@ public CallFOperation(final GasCalculator gasCalculator) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - final byte[] code = frame.getCode().getBytes().toArrayUnsafe(); - return staticOperation(frame, code, frame.getPC()); - } - - /** - * Performs Call F operation. - * - * @param frame the frame - * @param code the code - * @param pc the pc - * @return the successful operation result - */ - public static OperationResult staticOperation( - final MessageFrame frame, final byte[] code, final int pc) { - int section = readBigEndianU16(pc + 1, code); - var exception = frame.callFunction(section); - if (exception == null) { - return callfSuccess; - } else { - return new OperationResult(callfSuccess.gasCost, exception); + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; } + + int pc = frame.getPC(); + int section = code.readBigEndianU16(pc + 1); + CodeSection info = code.getCodeSection(section); + frame.getReturnStack().push(new ReturnStack.ReturnStackItem(frame.getSection(), pc + 2)); + frame.setPC(info.getEntryPoint() - 1); // will be +1ed at end of operations loop + frame.setSection(section); + + return callfSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallOperation.java index d6b7ddfd22a..ea0abca1ae8 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -19,7 +19,6 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.EVM; -import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -87,37 +86,10 @@ public long gasAvailableForChildCall(final MessageFrame frame) { return gasCalculator().gasAvailableForChildCall(frame, gas(frame), !value(frame).isZero()); } - @Override - protected boolean isStatic(final MessageFrame frame) { - return frame.isStatic(); - } - - @Override - public long cost(final MessageFrame frame) { - final long stipend = gas(frame); - final long inputDataOffset = inputDataOffset(frame); - final long inputDataLength = inputDataLength(frame); - final long outputDataOffset = outputDataOffset(frame); - final long outputDataLength = outputDataLength(frame); - final Account recipient = frame.getWorldUpdater().get(address(frame)); - - return gasCalculator() - .callOperationGasCost( - frame, - stipend, - inputDataOffset, - inputDataLength, - outputDataOffset, - outputDataLength, - value(frame), - recipient, - to(frame)); - } - @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { if (frame.isStatic() && !value(frame).isZero()) { - return new OperationResult(cost(frame), ExceptionalHaltReason.ILLEGAL_STATE_CHANGE); + return new OperationResult(cost(frame, true), ExceptionalHaltReason.ILLEGAL_STATE_CHANGE); } else { return super.execute(frame, evm); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallValueOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallValueOperation.java index 0cde3d8336a..f54da6f03c6 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallValueOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallValueOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallerOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallerOperation.java index 384a980ad1b..635bec22b48 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CallerOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CallerOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ChainIdOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ChainIdOperation.java index f807d745979..c4836c00f6d 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ChainIdOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ChainIdOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CodeCopyOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CodeCopyOperation.java index f135880c2f4..02da96a428f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CodeCopyOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CodeCopyOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CodeSizeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CodeSizeOperation.java index 690b5692f88..d6dbb9ca36f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CodeSizeOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CodeSizeOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/Create2Operation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/Create2Operation.java index 8d4fd36250f..3338a1e26a9 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/Create2Operation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/Create2Operation.java @@ -15,12 +15,18 @@ package org.hyperledger.besu.evm.operation; import static org.hyperledger.besu.crypto.Hash.keccak256; +import static org.hyperledger.besu.evm.internal.Words.clampedAdd; +import static org.hyperledger.besu.evm.internal.Words.clampedToInt; import static org.hyperledger.besu.evm.internal.Words.clampedToLong; import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import java.util.function.Supplier; + import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; @@ -33,27 +39,38 @@ public class Create2Operation extends AbstractCreateOperation { * Instantiates a new Create2 operation. * * @param gasCalculator the gas calculator - * @param maxInitcodeSize Maximum init code size */ - public Create2Operation(final GasCalculator gasCalculator, final int maxInitcodeSize) { - super(0xF5, "CREATE2", 4, 1, gasCalculator, maxInitcodeSize); + public Create2Operation(final GasCalculator gasCalculator) { + super(0xF5, "CREATE2", 4, 1, gasCalculator, 0); + } + + @Override + public long cost(final MessageFrame frame, final Supplier unused) { + final int inputOffset = clampedToInt(frame.getStackItem(1)); + final int inputSize = clampedToInt(frame.getStackItem(2)); + return clampedAdd( + clampedAdd( + gasCalculator().txCreateCost(), + gasCalculator().memoryExpansionGasCost(frame, inputOffset, inputSize)), + clampedAdd( + gasCalculator().createKeccakCost(inputSize), gasCalculator().initcodeCost(inputSize))); } @Override - public Address targetContractAddress(final MessageFrame frame) { + public Address generateTargetContractAddress(final MessageFrame frame, final Code initcode) { final Address sender = frame.getRecipientAddress(); - final long offset = clampedToLong(frame.getStackItem(1)); - final long length = clampedToLong(frame.getStackItem(2)); final Bytes32 salt = Bytes32.leftPad(frame.getStackItem(3)); - final Bytes initCode = frame.readMutableMemory(offset, length); - final Bytes32 hash = keccak256(Bytes.concatenate(PREFIX, sender, salt, keccak256(initCode))); - final Address address = Address.extract(hash); - frame.warmUpAddress(address); - return address; + final Bytes32 hash = keccak256(Bytes.concatenate(PREFIX, sender, salt, initcode.getCodeHash())); + return Address.extract(hash); } @Override - public long cost(final MessageFrame frame) { - return gasCalculator().create2OperationGasCost(frame); + protected Code getInitCode(final MessageFrame frame, final EVM evm) { + final long inputOffset = clampedToLong(frame.getStackItem(1)); + final long inputSize = clampedToLong(frame.getStackItem(2)); + final Bytes inputData = frame.readMemory(inputOffset, inputSize); + // Never cache CREATEx initcode. The amount of reuse is very low, and caching mostly + // addresses disk loading delay, and we already have the code. + return evm.getCodeUncached(inputData); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/CreateOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/CreateOperation.java index d5d311e337c..9537c5012d2 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/CreateOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/CreateOperation.java @@ -14,11 +14,21 @@ */ package org.hyperledger.besu.evm.operation; +import static org.hyperledger.besu.evm.internal.Words.clampedAdd; +import static org.hyperledger.besu.evm.internal.Words.clampedToInt; +import static org.hyperledger.besu.evm.internal.Words.clampedToLong; + import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import java.util.function.Supplier; + +import org.apache.tuweni.bytes.Bytes; + /** The Create operation. */ public class CreateOperation extends AbstractCreateOperation { @@ -26,24 +36,36 @@ public class CreateOperation extends AbstractCreateOperation { * Instantiates a new Create operation. * * @param gasCalculator the gas calculator - * @param maxInitcodeSize Maximum init code size */ - public CreateOperation(final GasCalculator gasCalculator, final int maxInitcodeSize) { - super(0xF0, "CREATE", 3, 1, gasCalculator, maxInitcodeSize); + public CreateOperation(final GasCalculator gasCalculator) { + super(0xF0, "CREATE", 3, 1, gasCalculator, 0); } @Override - public long cost(final MessageFrame frame) { - return gasCalculator().createOperationGasCost(frame); + public long cost(final MessageFrame frame, final Supplier unused) { + final int inputOffset = clampedToInt(frame.getStackItem(1)); + final int inputSize = clampedToInt(frame.getStackItem(2)); + return clampedAdd( + clampedAdd( + gasCalculator().txCreateCost(), + gasCalculator().memoryExpansionGasCost(frame, inputOffset, inputSize)), + gasCalculator().initcodeCost(inputSize)); } @Override - protected Address targetContractAddress(final MessageFrame frame) { + protected Address generateTargetContractAddress(final MessageFrame frame, final Code initcode) { final Account sender = frame.getWorldUpdater().get(frame.getRecipientAddress()); // Decrement nonce by 1 to normalize the effect of transaction execution - final Address address = - Address.contractAddress(frame.getRecipientAddress(), sender.getNonce() - 1L); - frame.warmUpAddress(address); - return address; + return Address.contractAddress(frame.getRecipientAddress(), sender.getNonce() - 1L); + } + + @Override + protected Code getInitCode(final MessageFrame frame, final EVM evm) { + final long inputOffset = clampedToLong(frame.getStackItem(1)); + final long inputSize = clampedToLong(frame.getStackItem(2)); + final Bytes inputData = frame.readMemory(inputOffset, inputSize); + // Never cache CREATEx initcode. The amount of reuse is very low, and caching mostly + // addresses disk loading delay, and we already have the code. + return evm.getCodeUncached(inputData); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/DataCopyOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/DataCopyOperation.java new file mode 100644 index 00000000000..a7f7b014794 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/DataCopyOperation.java @@ -0,0 +1,71 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import static org.hyperledger.besu.evm.internal.Words.clampedToInt; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import org.apache.tuweni.bytes.Bytes; + +/** The Data load operation. */ +public class DataCopyOperation extends AbstractOperation { + + /** + * Instantiates a new Data Load operation. + * + * @param gasCalculator the gas calculator + */ + public DataCopyOperation(final GasCalculator gasCalculator) { + super(0xd3, "DATACOPY", 3, 1, gasCalculator); + } + + /** + * Cost of data Copy operation. + * + * @param frame the frame + * @param memOffset the mem offset + * @param length the length + * @return the long + */ + protected long cost(final MessageFrame frame, final long memOffset, final long length) { + return gasCalculator().getVeryLowTierGasCost() + + gasCalculator().extCodeCopyOperationGasCost(frame, memOffset, length); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } + final int memOffset = clampedToInt(frame.popStackItem()); + final int sourceOffset = clampedToInt(frame.popStackItem()); + final int length = clampedToInt(frame.popStackItem()); + final long cost = cost(frame, memOffset, length); + if (cost > frame.getRemainingGas()) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + final Bytes data = code.getData(sourceOffset, length); + frame.writeMemory(memOffset, length, data); + + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/DataLoadNOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/DataLoadNOperation.java new file mode 100644 index 00000000000..7431ed5ac77 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/DataLoadNOperation.java @@ -0,0 +1,54 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import org.apache.tuweni.bytes.Bytes; + +/** The Data load operation. */ +public class DataLoadNOperation extends AbstractFixedCostOperation { + + /** The constant OPCODE. */ + public static final int OPCODE = 0xd1; + + /** + * Instantiates a new Data Load operation. + * + * @param gasCalculator the gas calculator + */ + public DataLoadNOperation(final GasCalculator gasCalculator) { + super(OPCODE, "DATALOADN", 0, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } + + int pc = frame.getPC(); + int index = code.readBigEndianU16(pc + 1); + final Bytes data = code.getData(index, 32); + frame.pushStackItem(data); + frame.setPC(pc + 2); + + return successResponse; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/DataLoadOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/DataLoadOperation.java new file mode 100644 index 00000000000..29db444fac6 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/DataLoadOperation.java @@ -0,0 +1,52 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import static org.hyperledger.besu.evm.internal.Words.clampedToInt; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import org.apache.tuweni.bytes.Bytes; + +/** The Data load operation. */ +public class DataLoadOperation extends AbstractFixedCostOperation { + + /** + * Instantiates a new Data Load operation. + * + * @param gasCalculator the gas calculator + */ + public DataLoadOperation(final GasCalculator gasCalculator) { + super(0xd0, "DATALOAD", 1, 1, gasCalculator, 4); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } + final int sourceOffset = clampedToInt(frame.popStackItem()); + + final Bytes data = code.getData(sourceOffset, 32); + frame.pushStackItem(data); + + return successResponse; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/DataSizeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/DataSizeOperation.java new file mode 100644 index 00000000000..13cfab0df2c --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/DataSizeOperation.java @@ -0,0 +1,47 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import org.apache.tuweni.bytes.Bytes; + +/** The Data load operation. */ +public class DataSizeOperation extends AbstractFixedCostOperation { + + /** + * Instantiates a new Data Load operation. + * + * @param gasCalculator the gas calculator + */ + public DataSizeOperation(final GasCalculator gasCalculator) { + super(0xd2, "DATASIZE", 0, 1, gasCalculator, gasCalculator.getBaseTierGasCost()); + } + + @Override + public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { + final Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } + final int size = code.getDataSize(); + frame.pushStackItem(Bytes.ofUnsignedInt(size)); + + return successResponse; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/DelegateCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/DelegateCallOperation.java index 464495a15dd..e7fb3f7c626 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/DelegateCallOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/DelegateCallOperation.java @@ -18,7 +18,6 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.internal.Words; @@ -86,29 +85,7 @@ public long gasAvailableForChildCall(final MessageFrame frame) { } @Override - protected boolean isStatic(final MessageFrame frame) { - return frame.isStatic(); - } - - @Override - public long cost(final MessageFrame frame) { - final long stipend = gas(frame); - final long inputDataOffset = inputDataOffset(frame); - final long inputDataLength = inputDataLength(frame); - final long outputDataOffset = outputDataOffset(frame); - final long outputDataLength = outputDataLength(frame); - final Account recipient = frame.getWorldUpdater().get(address(frame)); - - return gasCalculator() - .callOperationGasCost( - frame, - stipend, - inputDataOffset, - inputDataLength, - outputDataOffset, - outputDataLength, - Wei.ZERO, - recipient, - to(frame)); + protected boolean isDelegate() { + return true; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/DifficultyOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/DifficultyOperation.java index c6ecfc8c0e0..b2a9c0913b5 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/DifficultyOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/DifficultyOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/DupNOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/DupNOperation.java new file mode 100644 index 00000000000..61108e2b610 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/DupNOperation.java @@ -0,0 +1,55 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +/** The Dup operation. */ +public class DupNOperation extends AbstractFixedCostOperation { + + /** DUPN Opcode 0xe6 */ + public static final int OPCODE = 0xe6; + + /** The Dup success operation result. */ + static final OperationResult dupSuccess = new OperationResult(3, null); + + /** + * Instantiates a new Dup operation. + * + * @param gasCalculator the gas calculator + */ + public DupNOperation(final GasCalculator gasCalculator) { + super(OPCODE, "DUPN", 0, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } + int pc = frame.getPC(); + + int depth = code.readU8(pc + 1); + frame.pushStackItem(frame.getStackItem(depth)); + frame.setPC(pc + 1); + + return dupSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/DupOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/DupOperation.java index be5eec56677..d0b44c917cd 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/DupOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/DupOperation.java @@ -24,8 +24,10 @@ public class DupOperation extends AbstractFixedCostOperation { /** The constant DUP_BASE. */ public static final int DUP_BASE = 0x7F; + /** The Dup success operation result. */ static final OperationResult dupSuccess = new OperationResult(3, null); + /** The Underflow response. */ protected final Operation.OperationResult underflowResponse; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/EOFCreateOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/EOFCreateOperation.java new file mode 100644 index 00000000000..3ea3ef1407f --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/EOFCreateOperation.java @@ -0,0 +1,89 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import static org.hyperledger.besu.crypto.Hash.keccak256; +import static org.hyperledger.besu.evm.internal.Words.clampedAdd; +import static org.hyperledger.besu.evm.internal.Words.clampedToInt; +import static org.hyperledger.besu.evm.internal.Words.clampedToLong; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import java.util.function.Supplier; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +/** The Create2 operation. */ +public class EOFCreateOperation extends AbstractCreateOperation { + + /** Opcode 0xEC for operation EOFCREATE */ + public static final int OPCODE = 0xec; + + private static final Bytes PREFIX = Bytes.fromHexString("0xFF"); + + /** + * Instantiates a new EOFCreate operation. + * + * @param gasCalculator the gas calculator + */ + public EOFCreateOperation(final GasCalculator gasCalculator) { + super(OPCODE, "EOFCREATE", 4, 1, gasCalculator, 1); + } + + @Override + public long cost(final MessageFrame frame, final Supplier codeSupplier) { + final int inputOffset = clampedToInt(frame.getStackItem(2)); + final int inputSize = clampedToInt(frame.getStackItem(3)); + return clampedAdd( + gasCalculator().memoryExpansionGasCost(frame, inputOffset, inputSize), + clampedAdd( + gasCalculator().txCreateCost(), + gasCalculator().createKeccakCost(codeSupplier.get().getSize()))); + } + + @Override + public Address generateTargetContractAddress(final MessageFrame frame, final Code initcode) { + final Address sender = frame.getRecipientAddress(); + final Bytes32 salt = Bytes32.leftPad(frame.getStackItem(1)); + final Bytes32 hash = keccak256(Bytes.concatenate(PREFIX, sender, salt, initcode.getCodeHash())); + return Address.extract(hash); + } + + @Override + protected Code getInitCode(final MessageFrame frame, final EVM evm) { + final Code code = frame.getCode(); + int startIndex = frame.getPC() + 1; + final int initContainerIndex = code.readU8(startIndex); + + return code.getSubContainer(initContainerIndex, null, evm).orElse(null); + } + + @Override + protected Bytes getInputData(final MessageFrame frame) { + final long inputOffset = clampedToLong(frame.getStackItem(2)); + final long inputSize = clampedToLong(frame.getStackItem(3)); + return frame.readMemory(inputOffset, inputSize); + } + + @Override + protected int getPcIncrement() { + return 2; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExchangeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExchangeOperation.java new file mode 100644 index 00000000000..cc6f8c9351a --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExchangeOperation.java @@ -0,0 +1,60 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import org.apache.tuweni.bytes.Bytes; + +/** The Exchange operation. */ +public class ExchangeOperation extends AbstractFixedCostOperation { + + /** EXCHANGE Opcode 0xe8 */ + public static final int OPCODE = 0xe8; + + /** The Exchange operation success result. */ + static final OperationResult exchangeSuccess = new OperationResult(3, null); + + /** + * Instantiates a new Exchange operation. + * + * @param gasCalculator the gas calculator + */ + public ExchangeOperation(final GasCalculator gasCalculator) { + super(OPCODE, "EXCHANGE", 0, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } + int pc = frame.getPC(); + int imm = code.readU8(pc + 1); + int n = (imm >> 4) + 1; + int m = (imm & 0x0F) + 1 + n; + + final Bytes tmp = frame.getStackItem(n); + frame.setStackItem(n, frame.getStackItem(m)); + frame.setStackItem(m, tmp); + frame.setPC(pc + 1); + + return exchangeSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCallOperation.java new file mode 100644 index 00000000000..7fc41ac85c5 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCallOperation.java @@ -0,0 +1,70 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import static org.hyperledger.besu.evm.internal.Words.clampedToLong; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +/** The Call operation. */ +public class ExtCallOperation extends AbstractExtCallOperation { + + /** The constant OPCODE. */ + public static final int OPCODE = 0xF8; + + static final int STACK_VALUE = 3; + + /** + * Instantiates a new Call operation. + * + * @param gasCalculator the gas calculator + */ + public ExtCallOperation(final GasCalculator gasCalculator) { + super(OPCODE, "EXTCALL", 4, 1, gasCalculator); + } + + @Override + protected Wei value(final MessageFrame frame) { + return Wei.wrap(frame.getStackItem(STACK_VALUE)); + } + + @Override + protected Wei apparentValue(final MessageFrame frame) { + return value(frame); + } + + @Override + protected long inputDataOffset(final MessageFrame frame) { + return clampedToLong(frame.getStackItem(STACK_INPUT_OFFSET)); + } + + @Override + protected long inputDataLength(final MessageFrame frame) { + return clampedToLong(frame.getStackItem(STACK_INPUT_LENGTH)); + } + + @Override + protected Address address(final MessageFrame frame) { + return to(frame); + } + + @Override + protected Address sender(final MessageFrame frame) { + return frame.getRecipientAddress(); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java index 7801b6d7d37..ed1e4697778 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperation.java @@ -16,27 +16,46 @@ import static org.hyperledger.besu.evm.internal.Words.clampedAdd; import static org.hyperledger.besu.evm.internal.Words.clampedToLong; +import static org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper.deductDelegatedCodeGasCost; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.code.EOFLayout; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.internal.Words; +import org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper; import org.apache.tuweni.bytes.Bytes; /** The Ext code copy operation. */ public class ExtCodeCopyOperation extends AbstractOperation { + /** This is the "code" legacy contracts see when copying code from an EOF contract. */ + public static final Bytes EOF_REPLACEMENT_CODE = Bytes.fromHexString("0xef00"); + + private final boolean enableEIP3540; + /** * Instantiates a new Ext code copy operation. * * @param gasCalculator the gas calculator */ public ExtCodeCopyOperation(final GasCalculator gasCalculator) { + this(gasCalculator, false); + } + + /** + * Instantiates a new Ext code copy operation. + * + * @param gasCalculator the gas calculator + * @param enableEIP3540 enable EIP-3540 semantics (don't copy EOF) + */ + public ExtCodeCopyOperation(final GasCalculator gasCalculator, final boolean enableEIP3540) { super(0x3C, "EXTCODECOPY", 4, 0, gasCalculator); + this.enableEIP3540 = enableEIP3540; } /** @@ -76,9 +95,27 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { } final Account account = frame.getWorldUpdater().get(address); + + if (account != null) { + final DelegatedCodeGasCostHelper.Result result = + deductDelegatedCodeGasCost(frame, gasCalculator(), account); + if (result.status() != DelegatedCodeGasCostHelper.Status.SUCCESS) { + return new Operation.OperationResult( + result.gasCost(), ExceptionalHaltReason.INSUFFICIENT_GAS); + } + } + final Bytes code = account != null ? account.getCode() : Bytes.EMPTY; - frame.writeMemory(memOffset, sourceOffset, numBytes, code); + if (enableEIP3540 + && code.size() >= 2 + && code.get(0) == EOFLayout.EOF_PREFIX_BYTE + && code.get(1) == 0) { + frame.writeMemory(memOffset, sourceOffset, numBytes, EOF_REPLACEMENT_CODE); + } else { + frame.writeMemory(memOffset, sourceOffset, numBytes, code); + } + return new OperationResult(cost, null); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java index ba6cd22d9df..9a8cfe83865 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperation.java @@ -14,28 +14,49 @@ */ package org.hyperledger.besu.evm.operation; +import static org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper.deductDelegatedCodeGasCost; + import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.code.EOFLayout; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.internal.OverflowException; import org.hyperledger.besu.evm.internal.UnderflowException; import org.hyperledger.besu.evm.internal.Words; +import org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper; import org.apache.tuweni.bytes.Bytes; /** The Ext code hash operation. */ public class ExtCodeHashOperation extends AbstractOperation { + // // 0x9dbf3648db8210552e9c4f75c6a1c3057c0ca432043bd648be15fe7be05646f5 + static final Hash EOF_REPLACEMENT_HASH = Hash.hash(ExtCodeCopyOperation.EOF_REPLACEMENT_CODE); + + private final boolean enableEIP3540; + /** * Instantiates a new Ext code hash operation. * * @param gasCalculator the gas calculator */ public ExtCodeHashOperation(final GasCalculator gasCalculator) { + this(gasCalculator, false); + } + + /** + * Instantiates a new Ext code copy operation. + * + * @param gasCalculator the gas calculator + * @param enableEIP3540 enable EIP-3540 semantics (don't copy EOF) + */ + public ExtCodeHashOperation(final GasCalculator gasCalculator, final boolean enableEIP3540) { super(0x3F, "EXTCODEHASH", 1, 1, gasCalculator); + this.enableEIP3540 = enableEIP3540; } /** @@ -60,15 +81,34 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { final long cost = cost(accountIsWarm); if (frame.getRemainingGas() < cost) { return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + final Account account = frame.getWorldUpdater().get(address); + + if (account != null) { + final DelegatedCodeGasCostHelper.Result result = + deductDelegatedCodeGasCost(frame, gasCalculator(), account); + if (result.status() != DelegatedCodeGasCostHelper.Status.SUCCESS) { + return new Operation.OperationResult( + result.gasCost(), ExceptionalHaltReason.INSUFFICIENT_GAS); + } + } + + if (account == null || account.isEmpty()) { + frame.pushStackItem(Bytes.EMPTY); } else { - final Account account = frame.getWorldUpdater().get(address); - if (account == null || account.isEmpty()) { - frame.pushStackItem(Bytes.EMPTY); + final Bytes code = account.getCode(); + if (enableEIP3540 + && code.size() >= 2 + && code.get(0) == EOFLayout.EOF_PREFIX_BYTE + && code.get(1) == 0) { + frame.pushStackItem(EOF_REPLACEMENT_HASH); } else { frame.pushStackItem(account.getCodeHash()); } - return new OperationResult(cost, null); } + return new OperationResult(cost, null); + } catch (final UnderflowException ufe) { return new OperationResult(cost(true), ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); } catch (final OverflowException ofe) { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java index 9d891f7d37e..c2795f364b0 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperation.java @@ -14,28 +14,47 @@ */ package org.hyperledger.besu.evm.operation; +import static org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper.deductDelegatedCodeGasCost; + import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.code.EOFLayout; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.internal.OverflowException; import org.hyperledger.besu.evm.internal.UnderflowException; import org.hyperledger.besu.evm.internal.Words; +import org.hyperledger.besu.evm.worldstate.DelegatedCodeGasCostHelper; import org.apache.tuweni.bytes.Bytes; /** The Ext code size operation. */ public class ExtCodeSizeOperation extends AbstractOperation { + static final Bytes EOF_SIZE = Bytes.of(2); + + private final boolean enableEIP3540; + /** * Instantiates a new Ext code size operation. * * @param gasCalculator the gas calculator */ public ExtCodeSizeOperation(final GasCalculator gasCalculator) { + this(gasCalculator, false); + } + + /** + * Instantiates a new Ext code size operation. + * + * @param gasCalculator the gas calculator + * @param enableEIP3540 enable EIP-3540 semantics (EOF is size 2) + */ + public ExtCodeSizeOperation(final GasCalculator gasCalculator, final boolean enableEIP3540) { super(0x3B, "EXTCODESIZE", 1, 1, gasCalculator); + this.enableEIP3540 = enableEIP3540; } /** @@ -62,8 +81,31 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); } else { final Account account = frame.getWorldUpdater().get(address); - frame.pushStackItem( - account == null ? Bytes.EMPTY : Words.intBytes(account.getCode().size())); + + if (account != null) { + final DelegatedCodeGasCostHelper.Result result = + deductDelegatedCodeGasCost(frame, gasCalculator(), account); + if (result.status() != DelegatedCodeGasCostHelper.Status.SUCCESS) { + return new Operation.OperationResult( + result.gasCost(), ExceptionalHaltReason.INSUFFICIENT_GAS); + } + } + + Bytes codeSize; + if (account == null) { + codeSize = Bytes.EMPTY; + } else { + final Bytes code = account.getCode(); + if (enableEIP3540 + && code.size() >= 2 + && code.get(0) == EOFLayout.EOF_PREFIX_BYTE + && code.get(1) == 0) { + codeSize = EOF_SIZE; + } else { + codeSize = Words.intBytes(code.size()); + } + } + frame.pushStackItem(codeSize); return new OperationResult(cost, null); } } catch (final UnderflowException ufe) { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtDelegateCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtDelegateCallOperation.java new file mode 100644 index 00000000000..49b0a2a54af --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtDelegateCallOperation.java @@ -0,0 +1,73 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import static org.hyperledger.besu.evm.internal.Words.clampedToLong; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +/** The Delegate call operation. */ +public class ExtDelegateCallOperation extends AbstractExtCallOperation { + + /** The constant OPCODE. */ + public static final int OPCODE = 0xF9; + + /** + * Instantiates a new Delegate call operation. + * + * @param gasCalculator the gas calculator + */ + public ExtDelegateCallOperation(final GasCalculator gasCalculator) { + super(OPCODE, "EXTDELEGATECALL", 3, 1, gasCalculator); + } + + @Override + protected Wei value(final MessageFrame frame) { + return Wei.ZERO; + } + + @Override + protected Wei apparentValue(final MessageFrame frame) { + return frame.getApparentValue(); + } + + @Override + protected long inputDataOffset(final MessageFrame frame) { + return clampedToLong(frame.getStackItem(STACK_INPUT_OFFSET)); + } + + @Override + protected long inputDataLength(final MessageFrame frame) { + return clampedToLong(frame.getStackItem(STACK_INPUT_LENGTH)); + } + + @Override + protected Address address(final MessageFrame frame) { + return frame.getRecipientAddress(); + } + + @Override + protected Address sender(final MessageFrame frame) { + return frame.getSenderAddress(); + } + + @Override + protected boolean isDelegate() { + return true; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtStaticCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtStaticCallOperation.java new file mode 100644 index 00000000000..7413213779f --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ExtStaticCallOperation.java @@ -0,0 +1,73 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import static org.hyperledger.besu.evm.internal.Words.clampedToLong; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +/** The Static call operation. */ +public class ExtStaticCallOperation extends AbstractExtCallOperation { + + /** The constant OPCODE. */ + public static final int OPCODE = 0xFB; + + /** + * Instantiates a new Static call operation. + * + * @param gasCalculator the gas calculator + */ + public ExtStaticCallOperation(final GasCalculator gasCalculator) { + super(OPCODE, "EXTSTATICCALL", 3, 1, gasCalculator); + } + + @Override + protected Wei value(final MessageFrame frame) { + return Wei.ZERO; + } + + @Override + protected Wei apparentValue(final MessageFrame frame) { + return value(frame); + } + + @Override + protected long inputDataOffset(final MessageFrame frame) { + return clampedToLong(frame.getStackItem(STACK_INPUT_OFFSET)); + } + + @Override + protected long inputDataLength(final MessageFrame frame) { + return clampedToLong(frame.getStackItem(STACK_INPUT_LENGTH)); + } + + @Override + protected Address address(final MessageFrame frame) { + return to(frame); + } + + @Override + protected Address sender(final MessageFrame frame) { + return frame.getRecipientAddress(); + } + + @Override + protected boolean isStatic(final MessageFrame frame) { + return true; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/InvalidOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/InvalidOperation.java index adb6b1bc4aa..b7ca197d0d3 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/InvalidOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/InvalidOperation.java @@ -24,9 +24,11 @@ public class InvalidOperation extends AbstractOperation { /** The constant OPCODE. */ public static final int OPCODE = 0xFE; + /** The constant INVALID_RESULT. */ public static final OperationResult INVALID_RESULT = new OperationResult(0, ExceptionalHaltReason.INVALID_OPERATION); + /** The Invalid operation result. */ protected final OperationResult invalidResult; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/JumpFOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/JumpFOperation.java index 612d70fb16c..c76caa9667a 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/JumpFOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/JumpFOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,8 +14,7 @@ */ package org.hyperledger.besu.evm.operation; -import static org.hyperledger.besu.evm.internal.Words.readBigEndianU16; - +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -25,8 +24,9 @@ public class JumpFOperation extends AbstractOperation { /** The constant OPCODE. */ public static final int OPCODE = 0xe5; + /** The Jump F success operation result. */ - static final OperationResult jumpfSuccess = new OperationResult(3, null); + static final OperationResult jumpfSuccess = new OperationResult(5, null); /** * Instantiates a new Jump F operation. @@ -39,26 +39,15 @@ public JumpFOperation(final GasCalculator gasCalculator) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - final byte[] code = frame.getCode().getBytes().toArrayUnsafe(); - return staticOperation(frame, code, frame.getPC()); - } - - /** - * Performs Jump F operation. - * - * @param frame the frame - * @param code the code - * @param pc the pc - * @return the successful operation result - */ - public static OperationResult staticOperation( - final MessageFrame frame, final byte[] code, final int pc) { - int section = readBigEndianU16(pc + 1, code); - var exception = frame.jumpFunction(section); - if (exception == null) { - return jumpfSuccess; - } else { - return new OperationResult(jumpfSuccess.gasCost, exception); + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; } + int pc = frame.getPC(); + int section = code.readBigEndianU16(pc + 1); + var info = code.getCodeSection(section); + frame.setPC(info.getEntryPoint() - 1); // will be +1ed at end of operations loop + frame.setSection(section); + return jumpfSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/MCopyOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/MCopyOperation.java index a36c0f060a4..21c2737f363 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/MCopyOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/MCopyOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/Operation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/Operation.java index 4a926fd1efd..23c8673694d 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/Operation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/Operation.java @@ -25,8 +25,10 @@ public interface Operation { class OperationResult { /** The Gas cost. */ final long gasCost; + /** The Halt reason. */ final ExceptionalHaltReason haltReason; + /** The increment. */ final int pcIncrement; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/PrevRanDaoOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/PrevRanDaoOperation.java index 0785d121448..3b3f805941f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/PrevRanDaoOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/PrevRanDaoOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/Push0Operation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/Push0Operation.java index b9d4b048ff4..a9748994eb4 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/Push0Operation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/Push0Operation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/PushOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/PushOperation.java index af107d994de..006956207b3 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/PushOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/PushOperation.java @@ -25,6 +25,7 @@ public class PushOperation extends AbstractFixedCostOperation { /** The constant PUSH_BASE. */ public static final int PUSH_BASE = 0x5F; + /** The constant PUSH_MAX. */ public static final int PUSH_MAX = 0x7F; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpIfOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpIfOperation.java index d6aaae5cbbd..cf345fc9308 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpIfOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpIfOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,10 +11,10 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.operation; +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -22,7 +22,7 @@ import org.apache.tuweni.bytes.Bytes; /** The type Relative jump If operation. */ -public class RelativeJumpIfOperation extends RelativeJumpOperation { +public class RelativeJumpIfOperation extends AbstractFixedCostOperation { /** The constant OPCODE. */ public static final int OPCODE = 0xe1; @@ -38,11 +38,16 @@ public RelativeJumpIfOperation(final GasCalculator gasCalculator) { @Override protected OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } final Bytes condition = frame.popStackItem(); - // If condition is zero (false), no jump is will be performed. Therefore, skip the rest. if (!condition.isZero()) { - return super.executeFixedCostOperation(frame, evm); + final int pcPostInstruction = frame.getPC() + 1; + return new OperationResult(gasCost, null, 2 + code.readBigEndianI16(pcPostInstruction) + 1); + } else { + return new OperationResult(gasCost, null, 2 + 1); } - return new OperationResult(gasCost, null, 2 + 1); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpOperation.java index 9f7b912f21e..eb63d2409f2 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,16 +11,13 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.operation; +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.Words; - -import org.apache.tuweni.bytes.Bytes; /** The type Relative jump operation. */ public class RelativeJumpOperation extends AbstractFixedCostOperation { @@ -59,9 +56,11 @@ protected RelativeJumpOperation( @Override protected OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { - final Bytes code = frame.getCode().getBytes(); + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } final int pcPostInstruction = frame.getPC() + 1; - return new OperationResult( - gasCost, null, 2 + Words.readBigEndianI16(pcPostInstruction, code.toArrayUnsafe()) + 1); + return new OperationResult(gasCost, null, 2 + code.readBigEndianI16(pcPostInstruction) + 1); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpVectorOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpVectorOperation.java index 1657d325c3c..22d4d4e53eb 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpVectorOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/RelativeJumpVectorOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,12 +11,10 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.operation; -import static org.hyperledger.besu.evm.internal.Words.readBigEndianI16; - +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -40,36 +38,42 @@ public RelativeJumpVectorOperation(final GasCalculator gasCalculator) { @Override protected OperationResult executeFixedCostOperation(final MessageFrame frame, final EVM evm) { - final Bytes code = frame.getCode().getBytes(); + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } int offsetCase; try { - offsetCase = frame.popStackItem().toInt(); + offsetCase = frame.popStackItem().trimLeadingZeros().toInt(); if (offsetCase < 0) { offsetCase = Integer.MAX_VALUE; } } catch (ArithmeticException | IllegalArgumentException ae) { offsetCase = Integer.MAX_VALUE; } - final int vectorSize = getVectorSize(code, frame.getPC() + 1); + final int vectorSize = getVectorSize(code.getBytes(), frame.getPC() + 1); + int jumpDelta = + (offsetCase < vectorSize) + ? code.readBigEndianI16( + frame.getPC() + 2 + offsetCase * 2) // lookup delta if offset is in vector + : 0; // if offsetCase is outside the vector the jump delta is zero / next opcode. return new OperationResult( gasCost, null, - 1 - + 2 * vectorSize - + ((offsetCase >= vectorSize) - ? 0 - : readBigEndianI16(frame.getPC() + 2 + offsetCase * 2, code.toArrayUnsafe())) - + 1); + 2 // Opcode + length immediate + + 2 * vectorSize // vector size + + jumpDelta); } /** - * Gets vector size. + * Gets vector size. Vector size is one greater than length immediate, because (a) zero length + * tables are useless and (b) it allows for 256 byte tables * * @param code the code * @param offsetCountByteIndex the offset count byte index * @return the vector size */ public static int getVectorSize(final Bytes code, final int offsetCountByteIndex) { - return code.get(offsetCountByteIndex) & 0xff; + return (code.toArrayUnsafe()[offsetCountByteIndex] & 0xff) + 1; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/RetFOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/RetFOperation.java index 327dac1cb81..4efa71eaeb1 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/RetFOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/RetFOperation.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,6 +14,7 @@ */ package org.hyperledger.besu.evm.operation; +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; @@ -23,8 +24,9 @@ public class RetFOperation extends AbstractOperation { /** The Opcode. */ public static final int OPCODE = 0xe4; + /** The Ret F success. */ - static final OperationResult retfSuccess = new OperationResult(4, null); + static final OperationResult retfSuccess = new OperationResult(3, null); /** * Instantiates a new Ret F operation. @@ -37,11 +39,15 @@ public RetFOperation(final GasCalculator gasCalculator) { @Override public OperationResult execute(final MessageFrame frame, final EVM evm) { - var exception = frame.returnFunction(); - if (exception == null) { - return retfSuccess; - } else { - return new OperationResult(retfSuccess.gasCost, exception); + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; } + var rStack = frame.getReturnStack(); + var returnInfo = rStack.pop(); + frame.setPC(returnInfo.pc()); + frame.setSection(returnInfo.codeSectionIndex()); + + return retfSuccess; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnContractOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnContractOperation.java new file mode 100644 index 00000000000..2860c93795e --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnContractOperation.java @@ -0,0 +1,82 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import static org.hyperledger.besu.evm.internal.Words.clampedToLong; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; + +/** The Return operation. */ +public class ReturnContractOperation extends AbstractOperation { + + /** Opcode of RETURNCONTRACT operation */ + public static final int OPCODE = 0xEE; + + /** + * Instantiates a new Return operation. + * + * @param gasCalculator the gas calculator + */ + public ReturnContractOperation(final GasCalculator gasCalculator) { + super(OPCODE, "RETURNCONTRACT", 2, 0, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } + + int pc = frame.getPC(); + int index = code.readU8(pc + 1); + + final long from = clampedToLong(frame.popStackItem()); + final long length = clampedToLong(frame.popStackItem()); + + final long cost = gasCalculator().memoryExpansionGasCost(frame, from, length); + if (frame.getRemainingGas() < cost) { + return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_GAS); + } + + if (index >= code.getSubcontainerCount()) { + return new OperationResult(cost, ExceptionalHaltReason.NONEXISTENT_CONTAINER); + } + + Bytes auxData = frame.readMemory(from, length); + if (code.getDataSize() + auxData.size() > evm.getMaxCodeSize()) { + return new OperationResult(cost, ExceptionalHaltReason.CODE_TOO_LARGE); + } + if (code.getDataSize() + auxData.size() < code.getDeclaredDataSize()) { + return new OperationResult(cost, ExceptionalHaltReason.DATA_TOO_SMALL); + } + Optional newCode = code.getSubContainer(index, auxData, evm); + if (newCode.isEmpty()) { + return new OperationResult(cost, ExceptionalHaltReason.INVALID_CONTAINER); + } + + frame.setCreatedCode(newCode.get()); + frame.setState(MessageFrame.State.CODE_SUCCESS); + return new OperationResult(cost, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataCopyOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataCopyOperation.java index c2cb651cf60..e6c91b0ee47 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataCopyOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataCopyOperation.java @@ -29,6 +29,7 @@ public class ReturnDataCopyOperation extends AbstractOperation { /** The constant INVALID_RETURN_DATA_BUFFER_ACCESS. */ protected static final OperationResult INVALID_RETURN_DATA_BUFFER_ACCESS = new OperationResult(0L, ExceptionalHaltReason.INVALID_RETURN_DATA_BUFFER_ACCESS); + /** The constant OUT_OF_BOUNDS. */ protected static final OperationResult OUT_OF_BOUNDS = new OperationResult(0L, ExceptionalHaltReason.OUT_OF_BOUNDS); @@ -50,13 +51,15 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { final Bytes returnData = frame.getReturnData(); final int returnDataLength = returnData.size(); - try { - final long end = Math.addExact(sourceOffset, numBytes); - if (end > returnDataLength) { - return INVALID_RETURN_DATA_BUFFER_ACCESS; + if (frame.getCode().getEofVersion() < 1) { + try { + final long end = Math.addExact(sourceOffset, numBytes); + if (end > returnDataLength) { + return INVALID_RETURN_DATA_BUFFER_ACCESS; + } + } catch (final ArithmeticException ae) { + return OUT_OF_BOUNDS; } - } catch (final ArithmeticException ae) { - return OUT_OF_BOUNDS; } final long cost = gasCalculator().dataCopyOperationGasCost(frame, memOffset, numBytes); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataLoadOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataLoadOperation.java new file mode 100644 index 00000000000..7d8a5e42091 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnDataLoadOperation.java @@ -0,0 +1,56 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import static org.hyperledger.besu.evm.internal.Words.clampedToInt; + +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +/** The Return data copy operation. */ +public class ReturnDataLoadOperation extends AbstractOperation { + + /** + * Instantiates a new Return data copy operation. + * + * @param gasCalculator the gas calculator + */ + public ReturnDataLoadOperation(final GasCalculator gasCalculator) { + super(0xf7, "RETURNDATALOAD", 3, 0, gasCalculator); + } + + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + final int offset = clampedToInt(frame.popStackItem()); + Bytes returnData = frame.getReturnData(); + int retunDataSize = returnData.size(); + + Bytes value; + if (offset > retunDataSize) { + value = Bytes.EMPTY; + } else if (offset + 32 >= returnData.size()) { + value = Bytes32.rightPad(returnData.slice(offset)); + } else { + value = returnData.slice(offset, 32); + } + + frame.pushStackItem(value); + return new OperationResult(3L, null); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnOperation.java index 56dbe9205cb..2e23e9b6df3 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/ReturnOperation.java @@ -24,13 +24,16 @@ /** The Return operation. */ public class ReturnOperation extends AbstractOperation { + /** RETURN opcode number */ + public static final int OPCODE = 0xF3; + /** * Instantiates a new Return operation. * * @param gasCalculator the gas calculator */ public ReturnOperation(final GasCalculator gasCalculator) { - super(0xF3, "RETURN", 2, 0, gasCalculator); + super(OPCODE, "RETURN", 2, 0, gasCalculator); } @Override diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/RevertOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/RevertOperation.java index 8f08aac884f..6c681a5ce7f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/RevertOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/RevertOperation.java @@ -26,13 +26,16 @@ /** The Revert operation. */ public class RevertOperation extends AbstractOperation { + /** REVERT opcode number */ + public static final int OPCODE = 0xFD; + /** * Instantiates a new Revert operation. * * @param gasCalculator the gas calculator */ public RevertOperation(final GasCalculator gasCalculator) { - super(0xFD, "REVERT", 2, 0, gasCalculator); + super(OPCODE, "REVERT", 2, 0, gasCalculator); } @Override diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SStoreOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SStoreOperation.java index d5055e0bd81..1c3c9e086a3 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SStoreOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SStoreOperation.java @@ -31,6 +31,7 @@ public class SStoreOperation extends AbstractOperation { /** The constant FRONTIER_MINIMUM. */ public static final long FRONTIER_MINIMUM = 0L; + /** The constant EIP_1706_MINIMUM. */ public static final long EIP_1706_MINIMUM = 2300L; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SelfDestructOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SelfDestructOperation.java index 453dff9ce50..ef07ea46d19 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SelfDestructOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SelfDestructOperation.java @@ -68,7 +68,7 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { gasCalculator().selfDestructOperationGasCost(beneficiaryNullable, originatorBalance) + (beneficiaryIsWarm ? 0L : gasCalculator().getColdAccountAccessCost()); - // With the cost we can test for two early exits: static or not enough gas. + // With the cost we can test for two early WithdrawalRequests: static or not enough gas. if (frame.isStatic()) { return new OperationResult(cost, ExceptionalHaltReason.ILLEGAL_STATE_CHANGE); } else if (frame.getRemainingGas() < cost) { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/StaticCallOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/StaticCallOperation.java index e8eebae4e4d..29a131f889f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/StaticCallOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/StaticCallOperation.java @@ -18,7 +18,6 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.internal.Words; @@ -89,26 +88,4 @@ public long gasAvailableForChildCall(final MessageFrame frame) { protected boolean isStatic(final MessageFrame frame) { return true; } - - @Override - public long cost(final MessageFrame frame) { - final long stipend = gas(frame); - final long inputDataOffset = inputDataOffset(frame); - final long inputDataLength = inputDataLength(frame); - final long outputDataOffset = outputDataOffset(frame); - final long outputDataLength = outputDataLength(frame); - final Account recipient = frame.getWorldUpdater().get(address(frame)); - - return gasCalculator() - .callOperationGasCost( - frame, - stipend, - inputDataOffset, - inputDataLength, - outputDataOffset, - outputDataLength, - value(frame), - recipient, - to(frame)); - } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/StopOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/StopOperation.java index 7bbdb00682f..c3026be5d43 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/StopOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/StopOperation.java @@ -23,6 +23,9 @@ /** The Stop operation. */ public class StopOperation extends AbstractFixedCostOperation { + /** Opcode of STOP operation */ + public static final int OPCODE = 0x00; + /** The Stop operation success result. */ static final OperationResult stopSuccess = new OperationResult(0, null); @@ -32,7 +35,7 @@ public class StopOperation extends AbstractFixedCostOperation { * @param gasCalculator the gas calculator */ public StopOperation(final GasCalculator gasCalculator) { - super(0x00, "STOP", 0, 0, gasCalculator, gasCalculator.getZeroTierGasCost()); + super(OPCODE, "STOP", 0, 0, gasCalculator, gasCalculator.getZeroTierGasCost()); } @Override diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SwapNOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SwapNOperation.java new file mode 100644 index 00000000000..badd14b34f4 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SwapNOperation.java @@ -0,0 +1,59 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +import org.apache.tuweni.bytes.Bytes; + +/** The SwapN operation. */ +public class SwapNOperation extends AbstractFixedCostOperation { + + /** SWAPN Opcode 0xe7 */ + public static final int OPCODE = 0xe7; + + /** The Swap operation success result. */ + static final OperationResult swapSuccess = new OperationResult(3, null); + + /** + * Instantiates a new SwapN operation. + * + * @param gasCalculator the gas calculator + */ + public SwapNOperation(final GasCalculator gasCalculator) { + super(OPCODE, "SWAPN", 0, 1, gasCalculator, gasCalculator.getVeryLowTierGasCost()); + } + + @Override + public Operation.OperationResult executeFixedCostOperation( + final MessageFrame frame, final EVM evm) { + Code code = frame.getCode(); + if (code.getEofVersion() == 0) { + return InvalidOperation.INVALID_RESULT; + } + int pc = frame.getPC(); + int index = code.readU8(pc + 1); + + final Bytes tmp = frame.getStackItem(0); + frame.setStackItem(0, frame.getStackItem(index + 1)); + frame.setStackItem(index + 1, tmp); + frame.setPC(pc + 1); + + return swapSuccess; + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/SwapOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/SwapOperation.java index bcb11bd9b8c..12a389dcc5c 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/SwapOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/SwapOperation.java @@ -26,10 +26,12 @@ public class SwapOperation extends AbstractFixedCostOperation { /** The constant SWAP_BASE. */ public static final int SWAP_BASE = 0x8F; + /** The Swap operation success result. */ static final OperationResult swapSuccess = new OperationResult(3, null); private final int index; + /** The operation result due to underflow. */ protected final Operation.OperationResult underflowResponse; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/TLoadOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/TLoadOperation.java index 7187e5d12b1..684d18f987a 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/TLoadOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/TLoadOperation.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -18,7 +18,6 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.OverflowException; import org.hyperledger.besu.evm.internal.UnderflowException; import org.apache.tuweni.bytes.Bytes32; @@ -50,8 +49,6 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { } } catch (final UnderflowException ufe) { return new OperationResult(cost, ExceptionalHaltReason.INSUFFICIENT_STACK_ITEMS); - } catch (final OverflowException ofe) { - return new OperationResult(cost, ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); } } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/TStoreOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/TStoreOperation.java index d8b8730cd5f..9eac70f58a9 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/TStoreOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/TStoreOperation.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/operation/VirtualOperation.java b/evm/src/main/java/org/hyperledger/besu/evm/operation/VirtualOperation.java index fdb61bff490..309ede2d9d5 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/operation/VirtualOperation.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/operation/VirtualOperation.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.evm.operation; import org.hyperledger.besu.evm.EVM; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/AbstractAltBnPrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/AbstractAltBnPrecompiledContract.java index c02b484eb27..5f8e4b0a8c9 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/precompile/AbstractAltBnPrecompiledContract.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/AbstractAltBnPrecompiledContract.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.precompile; @@ -20,7 +19,7 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.nativelib.bls12_381.LibEthPairings; +import org.hyperledger.besu.nativelib.gnark.LibGnarkEIP196; import java.util.Optional; import javax.annotation.Nonnull; @@ -50,7 +49,7 @@ public abstract class AbstractAltBnPrecompiledContract extends AbstractPrecompil */ public static boolean maybeEnableNative() { try { - useNative = LibEthPairings.ENABLED; + useNative = LibGnarkEIP196.ENABLED; } catch (UnsatisfiedLinkError | NoClassDefFoundError ule) { LOG.info("altbn128 native precompile not available: {}", ule.getMessage()); useNative = false; @@ -73,7 +72,7 @@ public static boolean isNative() { } private final byte operationId; - private final int inputLen; + private final int inputLimit; /** * Instantiates a new Abstract alt bn precompiled contract. @@ -90,9 +89,9 @@ public static boolean isNative() { final int inputLen) { super(name, gasCalculator); this.operationId = operationId; - this.inputLen = inputLen + 1; + this.inputLimit = inputLen + 1; - if (!LibEthPairings.ENABLED) { + if (!LibGnarkEIP196.ENABLED) { LOG.info("Native alt bn128 not available"); } } @@ -107,16 +106,16 @@ public static boolean isNative() { @Nonnull public PrecompileContractResult computeNative( final @Nonnull Bytes input, final MessageFrame messageFrame) { - final byte[] result = new byte[LibEthPairings.EIP196_PREALLOCATE_FOR_RESULT_BYTES]; - final byte[] error = new byte[LibEthPairings.EIP2537_PREALLOCATE_FOR_ERROR_BYTES]; + final byte[] result = new byte[LibGnarkEIP196.EIP196_PREALLOCATE_FOR_RESULT_BYTES]; + final byte[] error = new byte[LibGnarkEIP196.EIP196_PREALLOCATE_FOR_ERROR_BYTES]; final IntByReference o_len = - new IntByReference(LibEthPairings.EIP196_PREALLOCATE_FOR_RESULT_BYTES); + new IntByReference(LibGnarkEIP196.EIP196_PREALLOCATE_FOR_RESULT_BYTES); final IntByReference err_len = - new IntByReference(LibEthPairings.EIP2537_PREALLOCATE_FOR_ERROR_BYTES); - final int inputSize = Math.min(inputLen, input.size()); + new IntByReference(LibGnarkEIP196.EIP196_PREALLOCATE_FOR_ERROR_BYTES); + final int inputSize = Math.min(inputLimit, input.size()); final int errorNo = - LibEthPairings.eip196_perform_operation( + LibGnarkEIP196.eip196_perform_operation( operationId, input.slice(0, inputSize).toArrayUnsafe(), inputSize, @@ -124,6 +123,7 @@ public PrecompileContractResult computeNative( o_len, error, err_len); + if (errorNo == 0) { return PrecompileContractResult.success(Bytes.wrap(result, 0, o_len.getValue())); } else { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/AbstractBLS12PrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/AbstractBLS12PrecompiledContract.java index 159e4d39962..67fecb92aa7 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/precompile/AbstractBLS12PrecompiledContract.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/AbstractBLS12PrecompiledContract.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.precompile; @@ -19,7 +18,7 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.nativelib.bls12_381.LibEthPairings; +import org.hyperledger.besu.nativelib.gnark.LibGnarkEIP2537; import java.util.Optional; import javax.annotation.Nonnull; @@ -53,7 +52,7 @@ public abstract class AbstractBLS12PrecompiledContract implements PrecompiledCon private final String name; private final byte operationId; - private final int inputLen; + private final int inputLimit; /** * Instantiates a new Abstract BLS12 precompiled contract. @@ -65,7 +64,7 @@ public abstract class AbstractBLS12PrecompiledContract implements PrecompiledCon AbstractBLS12PrecompiledContract(final String name, final byte operationId, final int inputLen) { this.name = name; this.operationId = operationId; - this.inputLen = inputLen + 1; + this.inputLimit = inputLen + 1; } @Override @@ -77,16 +76,17 @@ public String getName() { @Override public PrecompileContractResult computePrecompile( final Bytes input, @Nonnull final MessageFrame messageFrame) { - final byte[] result = new byte[LibEthPairings.EIP2537_PREALLOCATE_FOR_RESULT_BYTES]; - final byte[] error = new byte[LibEthPairings.EIP2537_PREALLOCATE_FOR_ERROR_BYTES]; + final byte[] result = new byte[LibGnarkEIP2537.EIP2537_PREALLOCATE_FOR_RESULT_BYTES]; + final byte[] error = new byte[LibGnarkEIP2537.EIP2537_PREALLOCATE_FOR_ERROR_BYTES]; final IntByReference o_len = - new IntByReference(LibEthPairings.EIP2537_PREALLOCATE_FOR_RESULT_BYTES); + new IntByReference(LibGnarkEIP2537.EIP2537_PREALLOCATE_FOR_RESULT_BYTES); final IntByReference err_len = - new IntByReference(LibEthPairings.EIP2537_PREALLOCATE_FOR_ERROR_BYTES); - final int inputSize = Math.min(inputLen, input.size()); + new IntByReference(LibGnarkEIP2537.EIP2537_PREALLOCATE_FOR_ERROR_BYTES); + + final int inputSize = Math.min(inputLimit, input.size()); final int errorNo = - LibEthPairings.eip2537_perform_operation( + LibGnarkEIP2537.eip2537_perform_operation( operationId, input.slice(0, inputSize).toArrayUnsafe(), inputSize, @@ -94,6 +94,7 @@ public PrecompileContractResult computePrecompile( o_len, error, err_len); + if (errorNo == 0) { return PrecompileContractResult.success(Bytes.wrap(result, 0, o_len.getValue())); } else { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/AbstractPrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/AbstractPrecompiledContract.java index ed2a4ad585b..b09c1b91042 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/precompile/AbstractPrecompiledContract.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/AbstractPrecompiledContract.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/AltBN128AddPrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/AltBN128AddPrecompiledContract.java index 026a78c4a25..8aaa276586d 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/precompile/AltBN128AddPrecompiledContract.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/AltBN128AddPrecompiledContract.java @@ -19,7 +19,7 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.nativelib.bls12_381.LibEthPairings; +import org.hyperledger.besu.nativelib.gnark.LibGnarkEIP196; import java.math.BigInteger; import java.util.Arrays; @@ -40,7 +40,7 @@ private AltBN128AddPrecompiledContract(final GasCalculator gasCalculator, final super( "AltBN128Add", gasCalculator, - LibEthPairings.EIP196_ADD_OPERATION_RAW_VALUE, + LibGnarkEIP196.EIP196_ADD_OPERATION_RAW_VALUE, PARAMETER_LENGTH); this.gasCost = gasCost; } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/AltBN128MulPrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/AltBN128MulPrecompiledContract.java index 73b824f763f..fe58faa68d3 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/precompile/AltBN128MulPrecompiledContract.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/AltBN128MulPrecompiledContract.java @@ -19,7 +19,7 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.nativelib.bls12_381.LibEthPairings; +import org.hyperledger.besu.nativelib.gnark.LibGnarkEIP196; import java.math.BigInteger; import java.util.Arrays; @@ -38,13 +38,14 @@ public class AltBN128MulPrecompiledContract extends AbstractAltBnPrecompiledCont new BigInteger( "115792089237316195423570985008687907853269984665640564039457584007913129639935"); + private static final Bytes POINT_AT_INFINITY = Bytes.repeat((byte) 0, 64); private final long gasCost; private AltBN128MulPrecompiledContract(final GasCalculator gasCalculator, final long gasCost) { super( "AltBN128Mul", gasCalculator, - LibEthPairings.EIP196_MUL_OPERATION_RAW_VALUE, + LibGnarkEIP196.EIP196_MUL_OPERATION_RAW_VALUE, PARAMETER_LENGTH); this.gasCost = gasCost; } @@ -78,6 +79,12 @@ public long gasRequirement(final Bytes input) { @Override public PrecompileContractResult computePrecompile( final Bytes input, @Nonnull final MessageFrame messageFrame) { + + if (input.size() >= 64 && input.slice(0, 64).equals(POINT_AT_INFINITY)) { + return new PrecompileContractResult( + POINT_AT_INFINITY, false, MessageFrame.State.COMPLETED_SUCCESS, Optional.empty()); + } + if (useNative) { return computeNative(input, messageFrame); } else { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/AltBN128PairingPrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/AltBN128PairingPrecompiledContract.java index 992544d5c0e..e6cd892233a 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/precompile/AltBN128PairingPrecompiledContract.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/AltBN128PairingPrecompiledContract.java @@ -23,7 +23,7 @@ import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.nativelib.bls12_381.LibEthPairings; +import org.hyperledger.besu.nativelib.gnark.LibGnarkEIP196; import java.math.BigInteger; import java.util.ArrayList; @@ -43,6 +43,7 @@ public class AltBN128PairingPrecompiledContract extends AbstractAltBnPrecompiled /** The constant FALSE. */ static final Bytes FALSE = Bytes.fromHexString("0x0000000000000000000000000000000000000000000000000000000000000000"); + /** The constant TRUE. */ public static final Bytes TRUE = Bytes.fromHexString("0x0000000000000000000000000000000000000000000000000000000000000001"); @@ -55,7 +56,7 @@ private AltBN128PairingPrecompiledContract( super( "AltBN128Pairing", gasCalculator, - LibEthPairings.EIP196_PAIR_OPERATION_RAW_VALUE, + LibGnarkEIP196.EIP196_PAIR_OPERATION_RAW_VALUE, Integer.MAX_VALUE / PARAMETER_LENGTH * PARAMETER_LENGTH); this.pairingGasCost = pairingGasCost; this.baseGasCost = baseGasCost; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12G1AddPrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12G1AddPrecompiledContract.java index c15f5748305..59e4336a09f 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12G1AddPrecompiledContract.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12G1AddPrecompiledContract.java @@ -11,11 +11,10 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.precompile; -import org.hyperledger.besu.nativelib.bls12_381.LibEthPairings; +import org.hyperledger.besu.nativelib.gnark.LibGnarkEIP2537; import org.apache.tuweni.bytes.Bytes; @@ -26,11 +25,11 @@ public class BLS12G1AddPrecompiledContract extends AbstractBLS12PrecompiledContr /** Instantiates a new BLS12G1 Add precompiled contract. */ public BLS12G1AddPrecompiledContract() { - super("BLS12_G1ADD", LibEthPairings.BLS12_G1ADD_OPERATION_RAW_VALUE, PARAMETER_LENGTH); + super("BLS12_G1ADD", LibGnarkEIP2537.BLS12_G1ADD_OPERATION_SHIM_VALUE, PARAMETER_LENGTH); } @Override public long gasRequirement(final Bytes input) { - return 600L; + return 500L; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12G1MulPrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12G1MulPrecompiledContract.java index 42e90a9f478..8c76571e1c0 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12G1MulPrecompiledContract.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12G1MulPrecompiledContract.java @@ -11,11 +11,10 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.precompile; -import org.hyperledger.besu.nativelib.bls12_381.LibEthPairings; +import org.hyperledger.besu.nativelib.gnark.LibGnarkEIP2537; import org.apache.tuweni.bytes.Bytes; @@ -26,7 +25,7 @@ public class BLS12G1MulPrecompiledContract extends AbstractBLS12PrecompiledContr /** Instantiates a new BLS12G1 Mul precompiled contract. */ public BLS12G1MulPrecompiledContract() { - super("BLS12_G1MUL", LibEthPairings.BLS12_G1MUL_OPERATION_RAW_VALUE, PARAMETER_LENGTH); + super("BLS12_G1MUL", LibGnarkEIP2537.BLS12_G1MUL_OPERATION_SHIM_VALUE, PARAMETER_LENGTH); } @Override diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12G1MultiExpPrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12G1MultiExpPrecompiledContract.java index 536ff56d606..353bb203ee8 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12G1MultiExpPrecompiledContract.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12G1MultiExpPrecompiledContract.java @@ -11,11 +11,10 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.precompile; -import org.hyperledger.besu.nativelib.bls12_381.LibEthPairings; +import org.hyperledger.besu.nativelib.gnark.LibGnarkEIP2537; import org.apache.tuweni.bytes.Bytes; @@ -28,7 +27,7 @@ public class BLS12G1MultiExpPrecompiledContract extends AbstractBLS12Precompiled public BLS12G1MultiExpPrecompiledContract() { super( "BLS12_G1MULTIEXP", - LibEthPairings.BLS12_G1MULTIEXP_OPERATION_RAW_VALUE, + LibGnarkEIP2537.BLS12_G1MULTIEXP_OPERATION_SHIM_VALUE, Integer.MAX_VALUE / PARAMETER_LENGTH * PARAMETER_LENGTH); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12G2AddPrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12G2AddPrecompiledContract.java index 906fc62fd08..3e12b5c8b2c 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12G2AddPrecompiledContract.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12G2AddPrecompiledContract.java @@ -11,11 +11,10 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.precompile; -import org.hyperledger.besu.nativelib.bls12_381.LibEthPairings; +import org.hyperledger.besu.nativelib.gnark.LibGnarkEIP2537; import org.apache.tuweni.bytes.Bytes; @@ -26,11 +25,11 @@ public class BLS12G2AddPrecompiledContract extends AbstractBLS12PrecompiledContr /** Instantiates a new BLS12_G2 Add precompiled contract. */ public BLS12G2AddPrecompiledContract() { - super("BLS12_G2ADD", LibEthPairings.BLS12_G2ADD_OPERATION_RAW_VALUE, PARAMETER_LENGTH); + super("BLS12_G2ADD", LibGnarkEIP2537.BLS12_G2ADD_OPERATION_SHIM_VALUE, PARAMETER_LENGTH); } @Override public long gasRequirement(final Bytes input) { - return 4_500L; + return 800L; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12G2MulPrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12G2MulPrecompiledContract.java index 4b7fef54b11..441b59c5ae9 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12G2MulPrecompiledContract.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12G2MulPrecompiledContract.java @@ -11,11 +11,10 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.precompile; -import org.hyperledger.besu.nativelib.bls12_381.LibEthPairings; +import org.hyperledger.besu.nativelib.gnark.LibGnarkEIP2537; import org.apache.tuweni.bytes.Bytes; @@ -26,11 +25,11 @@ public class BLS12G2MulPrecompiledContract extends AbstractBLS12PrecompiledContr /** Instantiates a new BLS12_G2Mul precompiled contract. */ public BLS12G2MulPrecompiledContract() { - super("BLS12_G2MUL", LibEthPairings.BLS12_G2MUL_OPERATION_RAW_VALUE, PARAMETER_LENGTH); + super("BLS12_G2MUL", LibGnarkEIP2537.BLS12_G2MUL_OPERATION_SHIM_VALUE, PARAMETER_LENGTH); } @Override public long gasRequirement(final Bytes input) { - return 55_000L; + return 45_000L; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12G2MultiExpPrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12G2MultiExpPrecompiledContract.java index c5ce2856218..3344f075ad4 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12G2MultiExpPrecompiledContract.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12G2MultiExpPrecompiledContract.java @@ -11,11 +11,10 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.precompile; -import org.hyperledger.besu.nativelib.bls12_381.LibEthPairings; +import org.hyperledger.besu.nativelib.gnark.LibGnarkEIP2537; import org.apache.tuweni.bytes.Bytes; @@ -28,13 +27,13 @@ public class BLS12G2MultiExpPrecompiledContract extends AbstractBLS12Precompiled public BLS12G2MultiExpPrecompiledContract() { super( "BLS12_G2MULTIEXP", - LibEthPairings.BLS12_G2MULTIEXP_OPERATION_RAW_VALUE, + LibGnarkEIP2537.BLS12_G2MULTIEXP_OPERATION_SHIM_VALUE, Integer.MAX_VALUE / PARAMETER_LENGTH * PARAMETER_LENGTH); } @Override public long gasRequirement(final Bytes input) { final int k = input.size() / PARAMETER_LENGTH; - return 55L * k * getDiscount(k); + return 45L * k * getDiscount(k); } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12MapFp2ToG2PrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12MapFp2ToG2PrecompiledContract.java index e56517253c0..ddac7627acf 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12MapFp2ToG2PrecompiledContract.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12MapFp2ToG2PrecompiledContract.java @@ -11,11 +11,10 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.precompile; -import org.hyperledger.besu.nativelib.bls12_381.LibEthPairings; +import org.hyperledger.besu.nativelib.gnark.LibGnarkEIP2537; import org.apache.tuweni.bytes.Bytes; @@ -28,12 +27,12 @@ public class BLS12MapFp2ToG2PrecompiledContract extends AbstractBLS12Precompiled public BLS12MapFp2ToG2PrecompiledContract() { super( "BLS12_MAP_FIELD_TO_CURVE", - LibEthPairings.BLS12_MAP_FP2_TO_G2_OPERATION_RAW_VALUE, + LibGnarkEIP2537.BLS12_MAP_FP2_TO_G2_OPERATION_SHIM_VALUE, PARAMETER_LENGTH); } @Override public long gasRequirement(final Bytes input) { - return 110_000L; + return 75_000L; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12MapFpToG1PrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12MapFpToG1PrecompiledContract.java index c0cd00b2e63..c2598358d75 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12MapFpToG1PrecompiledContract.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12MapFpToG1PrecompiledContract.java @@ -11,11 +11,10 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.precompile; -import org.hyperledger.besu.nativelib.bls12_381.LibEthPairings; +import org.hyperledger.besu.nativelib.gnark.LibGnarkEIP2537; import org.apache.tuweni.bytes.Bytes; @@ -28,7 +27,7 @@ public class BLS12MapFpToG1PrecompiledContract extends AbstractBLS12PrecompiledC public BLS12MapFpToG1PrecompiledContract() { super( "BLS12_MAP_FIELD_TO_CURVE", - LibEthPairings.BLS12_MAP_FP_TO_G1_OPERATION_RAW_VALUE, + LibGnarkEIP2537.BLS12_MAP_FP_TO_G1_OPERATION_SHIM_VALUE, PARAMETER_LENGTH); } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12PairingPrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12PairingPrecompiledContract.java index 309b3e377ae..e0e56818875 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12PairingPrecompiledContract.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/BLS12PairingPrecompiledContract.java @@ -11,11 +11,10 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.precompile; -import org.hyperledger.besu.nativelib.bls12_381.LibEthPairings; +import org.hyperledger.besu.nativelib.gnark.LibGnarkEIP2537; import org.apache.tuweni.bytes.Bytes; @@ -28,13 +27,13 @@ public class BLS12PairingPrecompiledContract extends AbstractBLS12PrecompiledCon public BLS12PairingPrecompiledContract() { super( "BLS12_PAIRING", - LibEthPairings.BLS12_PAIR_OPERATION_RAW_VALUE, + LibGnarkEIP2537.BLS12_PAIR_OPERATION_SHIM_VALUE, Integer.MAX_VALUE / PARAMETER_LENGTH * PARAMETER_LENGTH); } @Override public long gasRequirement(final Bytes input) { final int k = input.size() / PARAMETER_LENGTH; - return 23_000L * k + 115_000L; + return 43_000L * k + 65_000L; } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/ECRECPrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/ECRECPrecompiledContract.java index d3944f08a5e..f129cc85141 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/precompile/ECRECPrecompiledContract.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/ECRECPrecompiledContract.java @@ -36,6 +36,7 @@ public class ECRECPrecompiledContract extends AbstractPrecompiledContract { private static final int V_BASE = 27; final SignatureAlgorithm signatureAlgorithm; + /** * Instantiates a new ECREC precompiled contract with the default signature algorithm. * @@ -46,7 +47,7 @@ public ECRECPrecompiledContract(final GasCalculator gasCalculator) { } /** - * Configure a new ECRecover precompile with a specific signature algorith and gas. + * Configure a new ECRecover precompile with a specific signature algorithm and gas. * * @param gasCalculator the gas calculator * @param signatureAlgorithm the algorithm (such as secp256k1 or secp256r1) diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/KZGPointEvalPrecompiledContract.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/KZGPointEvalPrecompiledContract.java index f4bcd7b8ab1..b6baf9dd012 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/precompile/KZGPointEvalPrecompiledContract.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/KZGPointEvalPrecompiledContract.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -22,12 +22,12 @@ import java.nio.file.Path; import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; +import javax.annotation.Nonnull; import com.google.common.annotations.VisibleForTesting; import ethereum.ckzg4844.CKZG4844JNI; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,7 +39,7 @@ public class KZGPointEvalPrecompiledContract implements PrecompiledContract { private static Bytes successResult; - private static void init() { + private static void loadLib() { CKZG4844JNI.loadNativeLibrary(); Bytes fieldElementsPerBlob = Bytes32.wrap(Words.intBytes(CKZG4844JNI.FIELD_ELEMENTS_PER_BLOB).xor(Bytes32.ZERO)); @@ -57,7 +57,7 @@ private static void init() { */ public static void init(final Path trustedSetupFile) { if (loaded.compareAndSet(false, true)) { - init(); + loadLib(); final String trustedSetupResourceName = trustedSetupFile.toAbsolutePath().toString(); LOG.info("Loading trusted setup from user-specified resource {}", trustedSetupResourceName); CKZG4844JNI.loadTrustedSetup(trustedSetupResourceName); @@ -67,23 +67,18 @@ public static void init(final Path trustedSetupFile) { } /** - * Init the C-KZG native lib using a resource identified by the passed network name as trusted - * setup + * Init the C-KZG native lib using mainnet trusted setup * - * @param networkName used to select the resource in /kzg-trusted-setups/ to use. * @throws IllegalStateException is the trusted setup was already loaded */ - public static void init(final String networkName) { + public static void init() { if (loaded.compareAndSet(false, true)) { - init(); - final String trustedSetupResourceName = - "/kzg-trusted-setups/" + networkName.toLowerCase() + ".txt"; + loadLib(); + final String trustedSetupResourceName = "/kzg-trusted-setups/mainnet.txt"; LOG.info( "Loading network trusted setup from classpath resource {}", trustedSetupResourceName); CKZG4844JNI.loadTrustedSetupFromResource( trustedSetupResourceName, KZGPointEvalPrecompiledContract.class); - } else { - throw new IllegalStateException("KZG trusted setup was already loaded"); } } @@ -94,6 +89,9 @@ public static void tearDown() { loaded.set(false); } + /** Default constructor. */ + public KZGPointEvalPrecompiledContract() {} + @Override public String getName() { return "KZGPointEval"; @@ -105,10 +103,10 @@ public long gasRequirement(final Bytes input) { return 50000; } - @NotNull + @Nonnull @Override public PrecompileContractResult computePrecompile( - final Bytes input, @NotNull final MessageFrame messageFrame) { + final Bytes input, @Nonnull final MessageFrame messageFrame) { if (input.size() != 192) { return PrecompileContractResult.halt( @@ -142,7 +140,7 @@ public PrecompileContractResult computePrecompile( null, Optional.of(ExceptionalHaltReason.PRECOMPILE_ERROR)); } } catch (RuntimeException kzgFailed) { - System.out.println(kzgFailed.getMessage()); + LOG.debug("Native KZG failed", kzgFailed); return PrecompileContractResult.halt( null, Optional.of(ExceptionalHaltReason.PRECOMPILE_ERROR)); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/precompile/MainnetPrecompiledContracts.java b/evm/src/main/java/org/hyperledger/besu/evm/precompile/MainnetPrecompiledContracts.java index 454e7280215..624dc66a9ba 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/precompile/MainnetPrecompiledContracts.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/precompile/MainnetPrecompiledContracts.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -136,29 +136,28 @@ static void populateForCancun( populateForIstanbul(registry, gasCalculator); // EIP-4844 - shard blob transactions - // TODO: allow override to be configured? registry.put(Address.KZG_POINT_EVAL, new KZGPointEvalPrecompiledContract()); } /** - * FutureEIPs precompile contract registry. + * Prague precompile contract registry. * * @param gasCalculator the gas calculator * @return the precompile contract registry */ - static PrecompileContractRegistry futureEIPs(final GasCalculator gasCalculator) { + static PrecompileContractRegistry prague(final GasCalculator gasCalculator) { PrecompileContractRegistry precompileContractRegistry = new PrecompileContractRegistry(); - populateForFutureEIPs(precompileContractRegistry, gasCalculator); + populateForPrague(precompileContractRegistry, gasCalculator); return precompileContractRegistry; } /** - * Populate registry for Future EIPs. + * Populate registry for Prague. * * @param registry the registry * @param gasCalculator the gas calculator */ - static void populateForFutureEIPs( + static void populateForPrague( final PrecompileContractRegistry registry, final GasCalculator gasCalculator) { populateForCancun(registry, gasCalculator); @@ -173,4 +172,27 @@ static void populateForFutureEIPs( registry.put(Address.BLS12_MAP_FP_TO_G1, new BLS12MapFpToG1PrecompiledContract()); registry.put(Address.BLS12_MAP_FP2_TO_G2, new BLS12MapFp2ToG2PrecompiledContract()); } + + /** + * FutureEIPs precompile contract registry. + * + * @param gasCalculator the gas calculator + * @return the precompile contract registry + */ + static PrecompileContractRegistry futureEIPs(final GasCalculator gasCalculator) { + PrecompileContractRegistry precompileContractRegistry = new PrecompileContractRegistry(); + populateForFutureEIPs(precompileContractRegistry, gasCalculator); + return precompileContractRegistry; + } + + /** + * Populate registry for Future EIPs. + * + * @param registry the registry + * @param gasCalculator the gas calculator + */ + static void populateForFutureEIPs( + final PrecompileContractRegistry registry, final GasCalculator gasCalculator) { + populateForCancun(registry, gasCalculator); + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessor.java b/evm/src/main/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessor.java index 2bc98b58efc..0d394b2f887 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessor.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessor.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.stream.Collectors; +import javax.annotation.Nonnull; import org.apache.tuweni.bytes.Bytes; @@ -70,7 +71,7 @@ public abstract class AbstractMessageProcessor { // List of addresses to force delete when they are touched but empty // when the state changes in the message are were not meant to be committed. private final Collection forceDeleteAccountsWhenEmpty; - private final EVM evm; + final EVM evm; /** * Instantiates a new Abstract message processor. @@ -187,15 +188,12 @@ public void process(final MessageFrame frame, final OperationTracer operationTra if (operationTracer != null) { if (frame.getState() == MessageFrame.State.NOT_STARTED) { operationTracer.traceContextEnter(frame); + start(frame, operationTracer); } else { operationTracer.traceContextReEnter(frame); } } - if (frame.getState() == MessageFrame.State.NOT_STARTED) { - start(frame, operationTracer); - } - if (frame.getState() == MessageFrame.State.CODE_EXECUTING) { codeExecute(frame, operationTracer); @@ -237,7 +235,17 @@ public void process(final MessageFrame frame, final OperationTracer operationTra * @param codeBytes the code bytes * @return the code from evm */ - public Code getCodeFromEVM(final Hash codeHash, final Bytes codeBytes) { + public Code getCodeFromEVM(@Nonnull final Hash codeHash, final Bytes codeBytes) { return evm.getCode(codeHash, codeBytes); } + + /** + * Gets code from evm, with handling for EOF code plus calldata + * + * @param codeBytes the code bytes + * @return the code from evm + */ + public Code getCodeFromEVMForCreation(final Bytes codeBytes) { + return evm.getCodeForCreation(codeBytes); + } } diff --git a/evm/src/main/java/org/hyperledger/besu/evm/processor/ContractCreationProcessor.java b/evm/src/main/java/org/hyperledger/besu/evm/processor/ContractCreationProcessor.java index 7acea893724..a68c304c522 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/processor/ContractCreationProcessor.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/processor/ContractCreationProcessor.java @@ -22,7 +22,6 @@ import org.hyperledger.besu.evm.contractvalidation.ContractValidationRule; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.tracing.OperationTracer; import java.util.Collection; @@ -41,8 +40,6 @@ public class ContractCreationProcessor extends AbstractMessageProcessor { private final boolean requireCodeDepositToSucceed; - private final GasCalculator gasCalculator; - private final long initialContractNonce; private final List contractValidationRules; @@ -50,7 +47,6 @@ public class ContractCreationProcessor extends AbstractMessageProcessor { /** * Instantiates a new Contract creation processor. * - * @param gasCalculator the gas calculator * @param evm the evm * @param requireCodeDepositToSucceed the require code deposit to succeed * @param contractValidationRules the contract validation rules @@ -58,14 +54,12 @@ public class ContractCreationProcessor extends AbstractMessageProcessor { * @param forceCommitAddresses the force commit addresses */ public ContractCreationProcessor( - final GasCalculator gasCalculator, final EVM evm, final boolean requireCodeDepositToSucceed, final List contractValidationRules, final long initialContractNonce, final Collection

    forceCommitAddresses) { super(evm, forceCommitAddresses); - this.gasCalculator = gasCalculator; this.requireCodeDepositToSucceed = requireCodeDepositToSucceed; this.contractValidationRules = contractValidationRules; this.initialContractNonce = initialContractNonce; @@ -74,31 +68,23 @@ public ContractCreationProcessor( /** * Instantiates a new Contract creation processor. * - * @param gasCalculator the gas calculator * @param evm the evm * @param requireCodeDepositToSucceed the require code deposit to succeed * @param contractValidationRules the contract validation rules * @param initialContractNonce the initial contract nonce */ public ContractCreationProcessor( - final GasCalculator gasCalculator, final EVM evm, final boolean requireCodeDepositToSucceed, final List contractValidationRules, final long initialContractNonce) { - this( - gasCalculator, - evm, - requireCodeDepositToSucceed, - contractValidationRules, - initialContractNonce, - Set.of()); + this(evm, requireCodeDepositToSucceed, contractValidationRules, initialContractNonce, Set.of()); } private static boolean accountExists(final Account account) { // The account exists if it has sent a transaction // or already has its code initialized. - return account.getNonce() > 0 || !account.getCode().isEmpty(); + return account.getNonce() != 0 || !account.getCode().isEmpty() || !account.isStorageEmpty(); } @Override @@ -117,16 +103,16 @@ public void start(final MessageFrame frame, final OperationTracer operationTrace LOG.trace( "Contract creation error: account has already been created for address {}", contractAddress); - frame.setExceptionalHaltReason(Optional.of(ExceptionalHaltReason.INSUFFICIENT_GAS)); + frame.setExceptionalHaltReason(Optional.of(ExceptionalHaltReason.ILLEGAL_STATE_CHANGE)); frame.setState(MessageFrame.State.EXCEPTIONAL_HALT); operationTracer.traceAccountCreationResult( - frame, Optional.of(ExceptionalHaltReason.INSUFFICIENT_GAS)); + frame, Optional.of(ExceptionalHaltReason.ILLEGAL_STATE_CHANGE)); } else { + frame.addCreate(contractAddress); contract.incrementBalance(frame.getValue()); contract.setNonce(initialContractNonce); contract.clearStorage(); frame.setState(MessageFrame.State.CODE_EXECUTING); - frame.addCreate(contractAddress); } } catch (final ModificationNotAllowedException ex) { LOG.trace("Contract creation error: attempt to mutate an immutable account"); @@ -137,9 +123,10 @@ public void start(final MessageFrame frame, final OperationTracer operationTrace @Override public void codeSuccess(final MessageFrame frame, final OperationTracer operationTracer) { - final Bytes contractCode = frame.getOutputData(); + final Bytes contractCode = + frame.getCreatedCode() == null ? frame.getOutputData() : frame.getCreatedCode().getBytes(); - final long depositFee = gasCalculator.codeDepositGasCost(contractCode.size()); + final long depositFee = evm.getGasCalculator().codeDepositGasCost(contractCode.size()); if (frame.getRemainingGas() < depositFee) { LOG.trace( @@ -160,7 +147,7 @@ public void codeSuccess(final MessageFrame frame, final OperationTracer operatio } else { final var invalidReason = contractValidationRules.stream() - .map(rule -> rule.validate(contractCode, frame)) + .map(rule -> rule.validate(contractCode, frame, evm)) .filter(Optional::isPresent) .findFirst(); if (invalidReason.isEmpty()) { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/processor/MessageCallProcessor.java b/evm/src/main/java/org/hyperledger/besu/evm/processor/MessageCallProcessor.java index 1963bba8a5c..e3ce69d74ab 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/processor/MessageCallProcessor.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/processor/MessageCallProcessor.java @@ -153,6 +153,7 @@ private void executePrecompile( if (frame.getRemainingGas() < gasRequirement) { frame.setExceptionalHaltReason(Optional.of(ExceptionalHaltReason.INSUFFICIENT_GAS)); frame.setState(MessageFrame.State.EXCEPTIONAL_HALT); + operationTracer.tracePrecompileCall(frame, gasRequirement, null); } else { frame.decrementRemainingGas(gasRequirement); final PrecompiledContract.PrecompileContractResult result = diff --git a/evm/src/main/java/org/hyperledger/besu/evm/tracing/AccessListOperationTracer.java b/evm/src/main/java/org/hyperledger/besu/evm/tracing/AccessListOperationTracer.java index bce1f3c4da9..20b2db47cb3 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/tracing/AccessListOperationTracer.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/tracing/AccessListOperationTracer.java @@ -30,6 +30,11 @@ public class AccessListOperationTracer extends EstimateGasOperationTracer { private Table warmedUpStorage; + /** Default constructor. */ + private AccessListOperationTracer() { + super(); + } + @Override public void tracePostExecution(final MessageFrame frame, final OperationResult operationResult) { super.tracePostExecution(frame, operationResult); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/tracing/EstimateGasOperationTracer.java b/evm/src/main/java/org/hyperledger/besu/evm/tracing/EstimateGasOperationTracer.java index c7b11620311..ede5c1e338b 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/tracing/EstimateGasOperationTracer.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/tracing/EstimateGasOperationTracer.java @@ -25,6 +25,9 @@ public class EstimateGasOperationTracer implements OperationTracer { private long sStoreStipendNeeded = 0L; + /** Default constructor. */ + public EstimateGasOperationTracer() {} + @Override public void tracePostExecution(final MessageFrame frame, final OperationResult operationResult) { if (frame.getCurrentOperation() instanceof SStoreOperation sStoreOperation diff --git a/evm/src/main/java/org/hyperledger/besu/evm/tracing/OperationTracer.java b/evm/src/main/java/org/hyperledger/besu/evm/tracing/OperationTracer.java index edaed8e2416..f139386118a 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/tracing/OperationTracer.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/tracing/OperationTracer.java @@ -14,7 +14,9 @@ */ package org.hyperledger.besu.evm.tracing; +import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Transaction; +import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.log.Log; @@ -23,6 +25,7 @@ import java.util.List; import java.util.Optional; +import java.util.Set; import org.apache.tuweni.bytes.Bytes; @@ -68,13 +71,32 @@ default void traceAccountCreationResult( final MessageFrame frame, final Optional haltReason) {} /** - * Trace the start of a transaction. + * Trace the start of a transaction, before sender account alteration but after validation. + * + * @param worldView an immutable view of the execution context + * @param transaction the transaction which will be processed + */ + default void tracePrepareTransaction(final WorldView worldView, final Transaction transaction) {} + + /** + * Trace the start of a transaction, before execution but after sender account alteration. * * @param worldView an immutable view of the execution context * @param transaction the transaction which will be processed */ default void traceStartTransaction(final WorldView worldView, final Transaction transaction) {} + /** + * Trace the end of a transaction just before mining reward. + * + * @param worldView an immutable view of the execution context + * @param tx the transaction that just concluded + * @param miningReward the reward that the mining beneficiary will receive. + */ + default void traceBeforeRewardTransaction( + final WorldView worldView, final Transaction tx, final Wei miningReward) {} + ; + /** * Trace the end of a transaction. * @@ -84,6 +106,7 @@ default void traceStartTransaction(final WorldView worldView, final Transaction * @param output the bytes output from the transaction * @param logs the logs emitted by this transaction * @param gasUsed the gas used by the entire transaction + * @param selfDestructs the set of addresses that self-destructed during the transaction * @param timeNs the time in nanoseconds it took to execute the transaction */ default void traceEndTransaction( @@ -93,6 +116,7 @@ default void traceEndTransaction( final Bytes output, final List logs, final long gasUsed, + final Set
    selfDestructs, final long timeNs) {} /** diff --git a/evm/src/main/java/org/hyperledger/besu/evm/tracing/StandardJsonTracer.java b/evm/src/main/java/org/hyperledger/besu/evm/tracing/StandardJsonTracer.java index 5d90107a69e..a289c665d2d 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/tracing/StandardJsonTracer.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/tracing/StandardJsonTracer.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,15 +11,15 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.evm.tracing; import static com.google.common.base.Strings.padStart; +import org.hyperledger.besu.evm.code.OpcodeInfo; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.operation.AbstractCallOperation; import org.hyperledger.besu.evm.operation.Operation; import java.io.PrintStream; @@ -49,6 +49,7 @@ public class StandardJsonTracer implements OperationTracer { private Bytes memory; private int memorySize; private int depth; + private int subdepth; private String storageString; /** @@ -138,6 +139,7 @@ public void tracePreExecution(final MessageFrame messageFrame) { memory = null; } depth = messageFrame.getMessageStackSize(); + subdepth = messageFrame.returnStackSize(); StringBuilder sb = new StringBuilder(); if (showStorage) { @@ -171,18 +173,37 @@ public void tracePreExecution(final MessageFrame messageFrame) { public void tracePostExecution( final MessageFrame messageFrame, final Operation.OperationResult executeResult) { final Operation currentOp = messageFrame.getCurrentOperation(); + if (currentOp.isVirtualOperation()) { + return; + } final int opcode = currentOp.getOpcode(); final Bytes returnData = messageFrame.getReturnData(); + long thisGasCost = executeResult.getGasCost(); + if (currentOp instanceof AbstractCallOperation) { + thisGasCost += messageFrame.getMessageFrameStack().getFirst().getRemainingGas(); + } final StringBuilder sb = new StringBuilder(1024); sb.append("{"); sb.append("\"pc\":").append(pc).append(","); - if (section > 0) { + boolean eofContract = messageFrame.getCode().getEofVersion() > 0; + if (eofContract) { sb.append("\"section\":").append(section).append(","); } sb.append("\"op\":").append(opcode).append(","); + OpcodeInfo opInfo = OpcodeInfo.getOpcode(opcode); + if (eofContract && opInfo.pcAdvance() > 1) { + var immediate = + messageFrame + .getCode() + .getBytes() + .slice( + pc + messageFrame.getCode().getCodeSection(0).getEntryPoint() + 1, + opInfo.pcAdvance() - 1); + sb.append("\"immediate\":\"").append(immediate.toHexString()).append("\","); + } sb.append("\"gas\":\"").append(gas).append("\","); - sb.append("\"gasCost\":\"").append(shortNumber(executeResult.getGasCost())).append("\","); + sb.append("\"gasCost\":\"").append(shortNumber(thisGasCost)).append("\","); if (memory != null) { sb.append("\"memory\":\"").append(memory.toHexString()).append("\","); } @@ -194,6 +215,9 @@ public void tracePostExecution( sb.append("\"returnData\":\"").append(returnData.toHexString()).append("\","); } sb.append("\"depth\":").append(depth).append(","); + if (subdepth >= 1) { + sb.append("\"functionDepth\":").append(subdepth).append(","); + } sb.append("\"refund\":").append(messageFrame.getGasRefund()).append(","); sb.append("\"opName\":\"").append(currentOp.getName()).append("\""); if (executeResult.getHaltReason() != null) { diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeGasCostHelper.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeGasCostHelper.java new file mode 100644 index 00000000000..fc677703147 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeGasCostHelper.java @@ -0,0 +1,80 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.worldstate; + +import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; + +/** + * Helper class to deduct gas cost for delegated code resolution. + * + *

    Delegated code resolution is the process of determining the address of the contract that will + * be executed when a contract has delegated code. This process is necessary to determine the + * contract that will be executed and to ensure that the contract is warm in the cache. + */ +public class DelegatedCodeGasCostHelper { + + /** Private constructor to prevent instantiation. */ + private DelegatedCodeGasCostHelper() { + // empty constructor + } + + /** The status of the operation. */ + public enum Status { + /** The operation failed due to insufficient gas. */ + INSUFFICIENT_GAS, + /** The operation was successful. */ + SUCCESS + } + + /** + * The result of the operation. + * + * @param gasCost the gas cost + * @param status of the operation + */ + public record Result(long gasCost, Status status) {} + + /** + * Deducts the gas cost for delegated code resolution. + * + * @param frame the message frame + * @param gasCalculator the gas calculator + * @param account the account + * @return the gas cost and result of the operation + */ + public static Result deductDelegatedCodeGasCost( + final MessageFrame frame, final GasCalculator gasCalculator, final Account account) { + if (!account.hasDelegatedCode()) { + return new Result(0, Status.SUCCESS); + } + + if (account.delegatedCodeAddress().isEmpty()) { + throw new RuntimeException("A delegated code account must have a delegated code address"); + } + + final boolean delegatedCodeIsWarm = frame.warmUpAddress(account.delegatedCodeAddress().get()); + final long delegatedCodeResolutionGas = + gasCalculator.delegatedCodeResolutionGasCost(delegatedCodeIsWarm); + + if (frame.getRemainingGas() < delegatedCodeResolutionGas) { + return new Result(delegatedCodeResolutionGas, Status.INSUFFICIENT_GAS); + } + + frame.decrementRemainingGas(delegatedCodeResolutionGas); + return new Result(delegatedCodeResolutionGas, Status.SUCCESS); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeService.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeService.java new file mode 100644 index 00000000000..885324b10b6 --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/DelegatedCodeService.java @@ -0,0 +1,103 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.worldstate; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.account.DelegatedCodeAccount; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.account.MutableDelegatedCodeAccount; + +import org.apache.tuweni.bytes.Bytes; + +/** A service that manages the code injection of delegated code. */ +public class DelegatedCodeService { + private static final Bytes DELEGATED_CODE_PREFIX = Bytes.fromHexString("ef0100"); + private static final int DELEGATED_CODE_SIZE = DELEGATED_CODE_PREFIX.size() + Address.SIZE; + + /** Creates a new DelegatedCodeService. */ + public DelegatedCodeService() {} + + /** + * Add the delegated code to the given account. + * + * @param account the account to which the delegated code is added. + * @param delegatedCodeAddress the address of the delegated code. + */ + public void addDelegatedCode(final MutableAccount account, final Address delegatedCodeAddress) { + account.setCode(Bytes.concatenate(DELEGATED_CODE_PREFIX, delegatedCodeAddress)); + } + + /** + * Returns if the provided account has either no code set or has already delegated code. + * + * @param account the account to check. + * @return {@code true} if the account can set delegated code, {@code false} otherwise. + */ + public boolean canSetDelegatedCode(final Account account) { + return account.getCode().isEmpty() || hasDelegatedCode(account.getUnprocessedCode()); + } + + /** + * Processes the provided account, resolving the code if delegated. + * + * @param worldUpdater the world updater to retrieve the delegated code. + * @param account the account to process. + * @return the processed account, containing the delegated code if set, the unmodified account + * otherwise. + */ + public Account processAccount(final WorldUpdater worldUpdater, final Account account) { + if (account == null || !hasDelegatedCode(account.getCode())) { + return account; + } + + return new DelegatedCodeAccount( + worldUpdater, account, resolveDelegatedAddress(account.getCode())); + } + + /** + * Processes the provided mutable account, resolving the code if delegated. + * + * @param worldUpdater the world updater to retrieve the delegated code. + * @param account the mutable account to process. + * @return the processed mutable account, containing the delegated code if set, the unmodified + * mutable account otherwise. + */ + public MutableAccount processMutableAccount( + final WorldUpdater worldUpdater, final MutableAccount account) { + if (account == null || !hasDelegatedCode(account.getCode())) { + return account; + } + + return new MutableDelegatedCodeAccount( + worldUpdater, account, resolveDelegatedAddress(account.getCode())); + } + + /** + * Returns if the provided code is delegated code. + * + * @param code the code to check. + * @return {@code true} if the code is delegated code, {@code false} otherwise. + */ + public static boolean hasDelegatedCode(final Bytes code) { + return code != null + && code.size() == DELEGATED_CODE_SIZE + && code.slice(0, DELEGATED_CODE_PREFIX.size()).equals(DELEGATED_CODE_PREFIX); + } + + private Address resolveDelegatedAddress(final Bytes code) { + return Address.wrap(code.slice(DELEGATED_CODE_PREFIX.size())); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/EVMWorldUpdater.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/EVMWorldUpdater.java new file mode 100644 index 00000000000..928118acfed --- /dev/null +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/EVMWorldUpdater.java @@ -0,0 +1,125 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.worldstate; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.frame.MessageFrame; + +import java.util.Collection; +import java.util.Optional; + +/** + * The EVM world updater. This class is a wrapper around a WorldUpdater that provides an + * AuthorizedCodeService to manage the authorized code for accounts. + */ +public class EVMWorldUpdater implements WorldUpdater { + private final WorldUpdater rootWorldUpdater; + private final DelegatedCodeService delegatedCodeService; + + /** + * Instantiates a new EVM world updater. + * + * @param rootWorldUpdater the root world updater + */ + public EVMWorldUpdater(final WorldUpdater rootWorldUpdater) { + this(rootWorldUpdater, new DelegatedCodeService()); + } + + private EVMWorldUpdater( + final WorldUpdater rootWorldUpdater, final DelegatedCodeService delegatedCodeService) { + this.rootWorldUpdater = rootWorldUpdater; + this.delegatedCodeService = delegatedCodeService; + } + + /** + * Authorized code service. + * + * @return the authorized code service + */ + public DelegatedCodeService authorizedCodeService() { + return delegatedCodeService; + } + + @Override + public MutableAccount createAccount(final Address address, final long nonce, final Wei balance) { + return delegatedCodeService.processMutableAccount( + this, rootWorldUpdater.createAccount(address, nonce, balance)); + } + + @Override + public MutableAccount getAccount(final Address address) { + return delegatedCodeService.processMutableAccount(this, rootWorldUpdater.getAccount(address)); + } + + @Override + public MutableAccount getOrCreate(final Address address) { + return delegatedCodeService.processMutableAccount(this, rootWorldUpdater.getOrCreate(address)); + } + + @Override + public MutableAccount getOrCreateSenderAccount(final Address address) { + return delegatedCodeService.processMutableAccount( + this, rootWorldUpdater.getOrCreateSenderAccount(address)); + } + + @Override + public MutableAccount getSenderAccount(final MessageFrame frame) { + return delegatedCodeService.processMutableAccount( + this, rootWorldUpdater.getSenderAccount(frame)); + } + + @Override + public void deleteAccount(final Address address) { + rootWorldUpdater.deleteAccount(address); + } + + @Override + public Collection getTouchedAccounts() { + return rootWorldUpdater.getTouchedAccounts(); + } + + @Override + public Collection

    getDeletedAccountAddresses() { + return rootWorldUpdater.getDeletedAccountAddresses(); + } + + @Override + public void revert() { + rootWorldUpdater.revert(); + } + + @Override + public void commit() { + rootWorldUpdater.commit(); + } + + @Override + public Optional parentUpdater() { + return rootWorldUpdater.parentUpdater(); + } + + @Override + public WorldUpdater updater() { + return new EVMWorldUpdater(rootWorldUpdater.updater(), delegatedCodeService); + } + + @Override + public Account get(final Address address) { + return delegatedCodeService.processAccount(this, rootWorldUpdater.get(address)); + } +} diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/JournaledAccount.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/JournaledAccount.java index e527e5301d9..d2fc60c7f18 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/JournaledAccount.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/JournaledAccount.java @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.evm.worldstate; import static com.google.common.base.Preconditions.checkNotNull; @@ -321,6 +319,18 @@ public void clearStorage() { updatedStorage.clear(); } + /** + * Does this account have any storage slots that are set to non-zero values? + * + * @return true if the account has no storage values set to non-zero values. False if any storage + * is set. + */ + @Override + public boolean isStorageEmpty() { + return updatedStorage.isEmpty() + && (storageWasCleared || account == null || account.isStorageEmpty()); + } + /** * Gets storage was cleared. * diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/JournaledUpdater.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/JournaledUpdater.java index bdec559e071..9987a5be752 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/JournaledUpdater.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/JournaledUpdater.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/StackedUpdater.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/StackedUpdater.java index 471a807e700..3adcc9fd752 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/StackedUpdater.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/StackedUpdater.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/UpdateTrackingAccount.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/UpdateTrackingAccount.java index a8c697a21f6..9f065aee98b 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/UpdateTrackingAccount.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/UpdateTrackingAccount.java @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.evm.worldstate; import static com.google.common.base.Preconditions.checkNotNull; @@ -130,7 +128,6 @@ public A getWrappedAccount() { public void setWrappedAccount(final A account) { if (this.account == null) { this.account = account; - storageWasCleared = false; } else { throw new IllegalStateException("Already tracking a wrapped account"); } @@ -296,6 +293,18 @@ public void clearStorage() { updatedStorage.clear(); } + /** + * Does this account have any storage slots that are set to non-zero values? + * + * @return true if the account has no storage values set to non-zero values. False if any storage + * is set. + */ + @Override + public boolean isStorageEmpty() { + return updatedStorage.isEmpty() + && (storageWasCleared || account == null || account.isStorageEmpty()); + } + @Override public void becomeImmutable() { immutable = true; diff --git a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/WorldUpdater.java b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/WorldUpdater.java index 9b57b6ab9e3..4cbda732798 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/worldstate/WorldUpdater.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/worldstate/WorldUpdater.java @@ -24,6 +24,8 @@ import java.util.Collection; import java.util.Optional; +import org.apache.tuweni.bytes.Bytes; + /** * An object that buffers updates made over a particular {@link WorldView}. * @@ -73,8 +75,29 @@ default MutableAccount createAccount(final Address address) { * #createAccount(Address)} (and thus all his fields will be zero/empty). */ default MutableAccount getOrCreate(final Address address) { - final MutableAccount account = getAccount(address); - return account == null ? createAccount(address) : account; + MutableAccount account = getAccount(address); + if (account == null) { + account = createAccount(address); + if (parentUpdater().isPresent() && parentUpdater().get().isDeleted(address)) { + account.clearStorage(); + account.setCode(Bytes.EMPTY); + } + } + return account; + } + + /** + * Check this and parent updaters to see if an address has been deleted since the last persist + * + * @param address address to check + * @return true if any updaters have marked the address as deleted. + */ + default boolean isDeleted(final Address address) { + if (getDeletedAccountAddresses().contains(address)) { + return true; + } else { + return parentUpdater().map(wu -> wu.isDeleted(address)).orElse(false); + } } /** diff --git a/evm/src/test/java/org/hyperledger/besu/collections/trie/BytesTrieSetTest.java b/evm/src/test/java/org/hyperledger/besu/collections/trie/BytesTrieSetTest.java index dc92cc47d72..54efb9d685b 100644 --- a/evm/src/test/java/org/hyperledger/besu/collections/trie/BytesTrieSetTest.java +++ b/evm/src/test/java/org/hyperledger/besu/collections/trie/BytesTrieSetTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.collections.trie; diff --git a/evm/src/test/java/org/hyperledger/besu/collections/undo/UndoListTest.java b/evm/src/test/java/org/hyperledger/besu/collections/undo/UndoListTest.java index 1ca96c470a8..5c1bb4d495e 100644 --- a/evm/src/test/java/org/hyperledger/besu/collections/undo/UndoListTest.java +++ b/evm/src/test/java/org/hyperledger/besu/collections/undo/UndoListTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.collections.undo; diff --git a/evm/src/test/java/org/hyperledger/besu/collections/undo/UndoMapTest.java b/evm/src/test/java/org/hyperledger/besu/collections/undo/UndoMapTest.java index b2eb030eab9..eebf984cd0d 100644 --- a/evm/src/test/java/org/hyperledger/besu/collections/undo/UndoMapTest.java +++ b/evm/src/test/java/org/hyperledger/besu/collections/undo/UndoMapTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.collections.undo; diff --git a/evm/src/test/java/org/hyperledger/besu/collections/undo/UndoSetTest.java b/evm/src/test/java/org/hyperledger/besu/collections/undo/UndoSetTest.java index 545cb68df12..d76c3c9ecac 100644 --- a/evm/src/test/java/org/hyperledger/besu/collections/undo/UndoSetTest.java +++ b/evm/src/test/java/org/hyperledger/besu/collections/undo/UndoSetTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.collections.undo; diff --git a/evm/src/test/java/org/hyperledger/besu/evm/EOFTestConstants.java b/evm/src/test/java/org/hyperledger/besu/evm/EOFTestConstants.java new file mode 100644 index 00000000000..f71adfee2a1 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/EOFTestConstants.java @@ -0,0 +1,95 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm; + +import org.apache.tuweni.bytes.Bytes; + +public class EOFTestConstants { + + public static final Bytes INNER_CONTRACT = + bytesFromPrettyPrint( + """ + # EOF + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0009 # Code section 0 , 9 bytes + 030001 # Total subcontainers ( 1 ) + 0014 # Sub container 0, 20 byte + 040000 # Data section length( 0 ) + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0003 # max stack: 3 + # Code section 0 + 5f # [0] PUSH0 + 35 # [1] CALLDATALOAD + 5f # [2] PUSH0 + 5f # [3] PUSH0 + a1 # [4] LOG1 + 5f # [5] PUSH0 + 5f # [6] PUSH0 + ee00 # [7] RETURNCONTRACT(0) + # Subcontainer 0 starts here + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0001 # Code section 0 , 1 bytes + 040000 # Data section length( 0 ) + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs + 80 # 0 outputs (Non-returning function) + 0000 # max stack: 0 + # Code section 0 + 00 # [0] STOP + """); + + public static Bytes EOF_CREATE_CONTRACT = + bytesFromPrettyPrint( + String.format( + """ + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 000e # Code section 0 , 14 bytes + 030001 # Total subcontainers ( 1 ) + %04x # Subcontainer 0 size ? + 040000 # Data section length( 0 ) + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0004 # max stack: 4 + # Code section 0 + 61c0de # [0] PUSH2(0xc0de) + 5f # [3] PUSH0 + 52 # [4] MSTORE + 6002 # [5] PUSH1(2) + 601e # [7] PUSH1 30 + 5f # [9] PUSH0 + 5f # [10] PUSH0 + ec00 # [11] EOFCREATE(0) + 00 # [13] STOP + # Data section (empty) + %s # subcontainer + """, + INNER_CONTRACT.size(), INNER_CONTRACT.toUnprefixedHexString())); + + public static Bytes bytesFromPrettyPrint(final String prettyPrint) { + return Bytes.fromHexString(prettyPrint.replaceAll("#.*?\n", "").replaceAll("\\s", "")); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/StandardJsonTracerTest.java b/evm/src/test/java/org/hyperledger/besu/evm/StandardJsonTracerTest.java index 95772986814..4f18f582d1a 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/StandardJsonTracerTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/StandardJsonTracerTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.evm; import static org.assertj.core.api.Assertions.assertThat; @@ -49,12 +47,10 @@ void eip3155ModifiedTestCase() { // returnStack was a valid field. It no longer appears in any traces. // (b) the summary line is omitted // (c) pc:3 is in error, the size of the memory before the first MSTORE8 is zero. - // (d) we don't report call requested gas in CALL series operations as a gas cost, just the EVM - // consumed gas - // (e) if memory is zero length, it is not included even if `showMemory` is true - // (f) if return data is zero length or null, it is not included even if `showReturnData` is + // (d) if memory is zero length, it is not included even if `showMemory` is true + // (e) if return data is zero length or null, it is not included even if `showReturnData` is // true - // (g) if error is zero length or null it is not included. + // (f) if error is zero length or null it is not included. assertThat(baos) .hasToString( """ @@ -70,7 +66,7 @@ void eip3155ModifiedTestCase() { {"pc":15,"op":96,"gas":"0x2540b95bf","gasCost":"0x3","memory":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000","memSize":96,"stack":["0x40","0x0","0x40"],"depth":1,"refund":0,"opName":"PUSH1"} {"pc":17,"op":96,"gas":"0x2540b95bc","gasCost":"0x3","memory":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000","memSize":96,"stack":["0x40","0x0","0x40","0x0"],"depth":1,"refund":0,"opName":"PUSH1"} {"pc":19,"op":90,"gas":"0x2540b95b9","gasCost":"0x2","memory":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000","memSize":96,"stack":["0x40","0x0","0x40","0x0","0x2"],"depth":1,"refund":0,"opName":"GAS"} - {"pc":20,"op":250,"gas":"0x2540b95b7","gasCost":"0x2bc","memory":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000","memSize":96,"stack":["0x40","0x0","0x40","0x0","0x2","0x2540b95b7"],"depth":1,"refund":0,"opName":"STATICCALL"} + {"pc":20,"op":250,"gas":"0x2540b95b7","gasCost":"0x24abb676c","memory":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000","memSize":96,"stack":["0x40","0x0","0x40","0x0","0x2","0x2540b95b7"],"depth":1,"refund":0,"opName":"STATICCALL"} {"pc":21,"op":96,"gas":"0x2540b92a7","gasCost":"0x3","memory":"0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b00000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000","memSize":96,"stack":["0x1"],"returnData":"0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b","depth":1,"refund":0,"opName":"PUSH1"} {"pc":23,"op":243,"gas":"0x2540b92a4","gasCost":"0x0","memory":"0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b00000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000","memSize":96,"stack":["0x1","0x40"],"returnData":"0xf5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b","depth":1,"refund":0,"opName":"RETURN"} """); @@ -104,7 +100,7 @@ void updatedStorageTestCase() { {"pc":15,"op":96,"gas":"0x2540b95bf","gasCost":"0x3","memSize":96,"depth":1,"refund":0,"opName":"PUSH1","storage":{"0x40":"0x40"}} {"pc":17,"op":96,"gas":"0x2540b95bc","gasCost":"0x3","memSize":96,"depth":1,"refund":0,"opName":"PUSH1","storage":{"0x40":"0x40"}} {"pc":19,"op":90,"gas":"0x2540b95b9","gasCost":"0x2","memSize":96,"depth":1,"refund":0,"opName":"GAS","storage":{"0x40":"0x40"}} - {"pc":20,"op":250,"gas":"0x2540b95b7","gasCost":"0x2bc","memSize":96,"depth":1,"refund":0,"opName":"STATICCALL","storage":{"0x40":"0x40"}} + {"pc":20,"op":250,"gas":"0x2540b95b7","gasCost":"0x24abb676c","memSize":96,"depth":1,"refund":0,"opName":"STATICCALL","storage":{"0x40":"0x40"}} {"pc":21,"op":96,"gas":"0x2540b92a7","gasCost":"0x3","memSize":96,"depth":1,"refund":0,"opName":"PUSH1","storage":{"0x40":"0x40"}} {"pc":23,"op":243,"gas":"0x2540b92a4","gasCost":"0x0","memSize":96,"depth":1,"refund":0,"opName":"RETURN","storage":{"0x40":"0x40"}} """); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/code/CodeFactoryTest.java b/evm/src/test/java/org/hyperledger/besu/evm/code/CodeFactoryTest.java index 79ddeb12429..6f60ab7865b 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/code/CodeFactoryTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/code/CodeFactoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,27 +11,29 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.code; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.EOFTestConstants.bytesFromPrettyPrint; import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.MainnetEVMs; +import org.hyperledger.besu.evm.internal.EvmConfiguration; -import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.Test; class CodeFactoryTest { @Test void invalidCodeIncompleteMagic() { - invalidCode("0xEF"); + invalidCodeForCreation("0xEF"); } @Test void invalidCodeInvalidMagic() { - invalidCode("0xEFFF0101000302000400600000AABBCCDD"); + invalidCodeForCreation("0xEFFF0101000302000400600000AABBCCDD"); } @Test @@ -179,8 +181,440 @@ void invalidCodeUnknownSectionId3() { invalidCode("0xEF0001010002030004006000AABBCCDD"); } + @Test + void invalidDataTruncated() { + invalidCode( + "EF0001 010004 0200010001 040003 00 00800000 FE BEEF", + "Truncated data section when a complete section was required"); + } + + @Test + void validComboEOFCreateReturnContract() { + validCode( + """ + 0x # EOF + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0011 # Code section 0 , 17 bytes + 030001 # Total subcontainers ( 1 ) + 0032 # Sub container 0, 50 byte + 040000 # Data section length( 0 ) + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0004 # max stack: 4 + # Code section 0 - in=0 out=non-returning height=4 + 6000 # [0] PUSH1(0) + 6000 # [2] PUSH1(0) + 6000 # [4] PUSH1(0) + 6000 # [6] PUSH1(0) + ec00 # [8] EOFCREATE(0) + 612015 # [10] PUSH2(0x2015) + 6001 # [13] PUSH1(1) + 55 # [15] SSTORE + 00 # [16] STOP + # Subcontainer 0 starts here + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0006 # Code section 0 , 6 bytes + 030001 # Total subcontainers ( 1 ) + 0014 # Sub container 0, 20 byte + 040000 # Data section length( 0 ) \s + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0002 # max stack: 2 + # Code section 0 - in=0 out=non-returning height=2 + 6000 # [0] PUSH1(0) + 6000 # [2] PUSH1(0) + ee00 # [4] RETURNCONTRACT(0) + # Subcontainer 0.0 starts here + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0001 # Code section 0 , 1 bytes + 040000 # Data section length( 0 ) \s + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0000 # max stack: 0 + # Code section 0 - in=0 out=non-returning height=0 + 00 # [0] STOP + # Data section (empty) + # Subcontainer 0.0 ends + # Data section (empty) + # Subcontainer 0 ends + # Data section (empty) + """); + } + + @Test + void validComboReturnContactStop() { + validCode( + """ + 0x # EOF + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 000c # Code section 0 , 12 bytes + 030001 # Total subcontainers ( 1 ) + 0014 # Sub container 0, 20 byte + 040000 # Data section length( 0 ) + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0002 # max stack: 2 + # Code section 0 - in=0 out=non-returning height=2 + 612015 # [0] PUSH2(0x2015) + 6001 # [3] PUSH1(1) + 55 # [5] SSTORE + 6000 # [6] PUSH1(0) + 6000 # [8] PUSH1(0) + ee00 # [10] RETURNCONTRACT(0) + # Subcontainer 0 starts here + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0001 # Code section 0 , 1 bytes + 040000 # Data section length( 0 ) \s + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0000 # max stack: 0 + # Code section 0 - in=0 out=non-returning height=0 + 00 # [0] STOP + # Data section (empty) + # Subcontainer 0 ends + # Data section (empty) + """); + } + + @Test + void validComboReturnContactReturn() { + validCode( + """ + 0x # EOF + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 000c # Code section 0 , 12 bytes + 030001 # Total subcontainers ( 1 ) + 0018 # Sub container 0, 24 byte + 040000 # Data section length( 0 ) + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0002 # max stack: 2 + # Code section 0 - in=0 out=non-returning height=2 + 612015 # [0] PUSH2(0x2015) + 6001 # [3] PUSH1(1) + 55 # [5] SSTORE + 6000 # [6] PUSH1(0) + 6000 # [8] PUSH1(0) + ee00 # [10] RETURNCONTRACT(0) + # Subcontainer 0 starts here + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0005 # Code section 0 , 5 bytes + 040000 # Data section length( 0 ) \s + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0002 # max stack: 2 + # Code section 0 - in=0 out=non-returning height=2 + 6000 # [0] PUSH1(0) + 6000 # [2] PUSH1(0) + f3 # [4] RETURN + # Data section (empty) + # Subcontainer 0 ends + # Data section (empty) + """); + } + + @Test + void validComboEOFCreateRevert() { + validCode( + """ + 0x # EOF + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0011 # Code section 0 , 17 bytes + 030001 # Total subcontainers ( 1 ) + 0018 # Sub container 0, 24 byte + 040000 # Data section length( 0 ) + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0004 # max stack: 4 + # Code section 0 - in=0 out=non-returning height=4 + 6000 # [0] PUSH1(0) + 6000 # [2] PUSH1(0) + 6000 # [4] PUSH1(0) + 6000 # [6] PUSH1(0) + ec00 # [8] EOFCREATE(0) + 612015 # [10] PUSH2(0x2015) + 6001 # [13] PUSH1(1) + 55 # [15] SSTORE + 00 # [16] STOP + # Subcontainer 0 starts here + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0005 # Code section 0 , 5 bytes + 040000 # Data section length( 0 ) \s + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0002 # max stack: 2 + # Code section 0 - in=0 out=non-returning height=2 + 6000 # [0] PUSH1(0) + 6000 # [2] PUSH1(0) + fd # [4] REVERT + # Data section (empty) + # Subcontainer 0 ends + # Data section (empty) + """); + } + + @Test + void validComboReturncontractRevert() { + validCode( + """ + 0x # EOF + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 000c # Code section 0 , 12 bytes + 030001 # Total subcontainers ( 1 ) + 0018 # Sub container 0, 24 byte + 040000 # Data section length( 0 ) + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0002 # max stack: 2 + # Code section 0 - in=0 out=non-returning height=2 + 612015 # [0] PUSH2(0x2015) + 6001 # [3] PUSH1(1) + 55 # [5] SSTORE + 6000 # [6] PUSH1(0) + 6000 # [8] PUSH1(0) + ee00 # [10] RETURNCONTRACT(0) + # Subcontainer 0 starts here + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0005 # Code section 0 , 5 bytes + 040000 # Data section length( 0 ) \s + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0002 # max stack: 2 + # Code section 0 - in=0 out=non-returning height=2 + 6000 # [0] PUSH1(0) + 6000 # [2] PUSH1(0) + fd # [4] REVERT + # Data section (empty) + # Subcontainer 0 ends + # Data section (empty) + """); + } + + @Test + void invalidComboEOFCreateStop() { + invalidCode( + """ + 0x # EOF + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0011 # Code section 0 , 17 bytes + 030001 # Total subcontainers ( 1 ) + 0014 # Sub container 0, 20 byte + 040000 # Data section length( 0 ) + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0004 # max stack: 4 + # Code section 0 - in=0 out=non-returning height=4 + 6000 # [0] PUSH1(0) + 6000 # [2] PUSH1(0) + 6000 # [4] PUSH1(0) + 6000 # [6] PUSH1(0) + ec00 # [8] EOFCREATE(0) + 612015 # [10] PUSH2(0x2015) + 6001 # [13] PUSH1(1) + 55 # [15] SSTORE + 00 # [16] STOP + # Subcontainer 0 starts here + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0001 # Code section 0 , 1 bytes + 040000 # Data section length( 0 ) \s + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0000 # max stack: 0 + # Code section 0 - in=0 out=non-returning height=0 + 00 # [0] STOP + # Data section (empty) + # Subcontainer 0 ends + # Data section (empty) + """, + "incompatible_container_kind"); + } + + @Test + void invalidComboEOFCretateReturn() { + invalidCode( + """ + 0x # EOF + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0011 # Code section 0 , 17 bytes + 030001 # Total subcontainers ( 1 ) + 0018 # Sub container 0, 24 byte + 040000 # Data section length( 0 ) + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0004 # max stack: 4 + # Code section 0 - in=0 out=non-returning height=4 + 6000 # [0] PUSH1(0) + 6000 # [2] PUSH1(0) + 6000 # [4] PUSH1(0) + 6000 # [6] PUSH1(0) + ec00 # [8] EOFCREATE(0) + 612015 # [10] PUSH2(0x2015) + 6001 # [13] PUSH1(1) + 55 # [15] SSTORE + 00 # [16] STOP + # Subcontainer 0 starts here + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0005 # Code section 0 , 5 bytes + 040000 # Data section length( 0 ) \s + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0002 # max stack: 2 + # Code section 0 - in=0 out=non-returning height=2 + 6000 # [0] PUSH1(0) + 6000 # [2] PUSH1(0) + f3 # [4] RETURN + # Data section (empty) + # Subcontainer 0 ends + # Data section (empty) + """, + "incompatible_container_kind"); + } + + @Test + void invalidReturncontractReturncontract() { + invalidCode( + """ + 0x # EOF + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 000c # Code section 0 , 12 bytes + 030001 # Total subcontainers ( 1 ) + 0032 # Sub container 0, 50 byte + 040000 # Data section length( 0 ) + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0002 # max stack: 2 + # Code section 0 - in=0 out=non-returning height=2 + 612015 # [0] PUSH2(0x2015) + 6001 # [3] PUSH1(1) + 55 # [5] SSTORE + 6000 # [6] PUSH1(0) + 6000 # [8] PUSH1(0) + ee00 # [10] RETURNCONTRACT(0) + # Subcontainer 0 starts here + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0006 # Code section 0 , 6 bytes + 030001 # Total subcontainers ( 1 ) + 0014 # Sub container 0, 20 byte + 040000 # Data section length( 0 ) \s + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0002 # max stack: 2 + # Code section 0 - in=0 out=non-returning height=2 + 6000 # [0] PUSH1(0) + 6000 # [2] PUSH1(0) + ee00 # [4] RETURNCONTRACT(0) + # Subcontainer 0.0 starts here + ef0001 # Magic and Version ( 1 ) + 010004 # Types length ( 4 ) + 020001 # Total code sections ( 1 ) + 0001 # Code section 0 , 1 bytes + 040000 # Data section length( 0 ) \s + 00 # Terminator (end of header) + # Code section 0 types + 00 # 0 inputs\s + 80 # 0 outputs (Non-returning function) + 0000 # max stack: 0 + # Code section 0 - in=0 out=non-returning height=0 + 00 # [0] STOP + # Data section (empty) + # Subcontainer 0.0 ends + # Data section (empty) + # Subcontainer 0 ends + # Data section (empty) + """, + "incompatible_container_kind"); + } + + private static void validCode(final String str) { + EVM evm = MainnetEVMs.pragueEOF(EvmConfiguration.DEFAULT); + Code code = evm.getCodeUncached(bytesFromPrettyPrint(str)); + assertThat(code.isValid()).isTrue(); + } + + private static void invalidCode(final String str, final String error) { + EVM evm = MainnetEVMs.pragueEOF(EvmConfiguration.DEFAULT); + Code code = evm.getCodeUncached(bytesFromPrettyPrint(str)); + assertThat(code.isValid()).isFalse(); + assertThat(((CodeInvalid) code).getInvalidReason()).contains(error); + } + private static void invalidCode(final String str) { - Code code = CodeFactory.createCode(Bytes.fromHexString(str), 1, true); + EVM evm = MainnetEVMs.pragueEOF(EvmConfiguration.DEFAULT); + Code code = evm.getCodeUncached(bytesFromPrettyPrint(str)); + assertThat(code.isValid()).isFalse(); + } + + private static void invalidCodeForCreation(final String str) { + EVM evm = MainnetEVMs.pragueEOF(EvmConfiguration.DEFAULT); + Code code = evm.getCodeForCreation(bytesFromPrettyPrint(str)); assertThat(code.isValid()).isFalse(); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV0Test.java b/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV0Test.java index 07f0f17040c..3b16b1c9238 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV0Test.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV0Test.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.evm.code; import static org.hyperledger.besu.evm.frame.MessageFrame.Type.MESSAGE_CALL; @@ -26,15 +24,12 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.EVM; -import org.hyperledger.besu.evm.EvmSpecVersion; +import org.hyperledger.besu.evm.MainnetEVMs; import org.hyperledger.besu.evm.frame.BlockValues; import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.gascalculator.IstanbulGasCalculator; import org.hyperledger.besu.evm.internal.EvmConfiguration; -import org.hyperledger.besu.evm.operation.JumpDestOperation; import org.hyperledger.besu.evm.operation.JumpOperation; import org.hyperledger.besu.evm.operation.Operation.OperationResult; -import org.hyperledger.besu.evm.operation.OperationRegistry; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import javax.annotation.Nonnull; @@ -47,24 +42,19 @@ class CodeV0Test { - private static final IstanbulGasCalculator gasCalculator = new IstanbulGasCalculator(); - private static final int CURRENT_PC = 1; private EVM evm; @BeforeEach void startUp() { - final OperationRegistry registry = new OperationRegistry(); - registry.put(new JumpOperation(gasCalculator)); - registry.put(new JumpDestOperation(gasCalculator)); - evm = new EVM(registry, gasCalculator, EvmConfiguration.DEFAULT, EvmSpecVersion.PARIS); + evm = MainnetEVMs.pragueEOF(EvmConfiguration.DEFAULT); } @Test void shouldReuseJumpDestMap() { - final JumpOperation operation = new JumpOperation(gasCalculator); + final JumpOperation operation = new JumpOperation(evm.getGasCalculator()); final Bytes jumpBytes = Bytes.fromHexString("0x6003565b00"); - final CodeV0 getsCached = (CodeV0) spy(CodeFactory.createCode(jumpBytes, 0, false)); + final CodeV0 getsCached = (CodeV0) spy(evm.getCodeUncached(jumpBytes)); MessageFrame frame = createJumpFrame(getsCached); OperationResult result = operation.execute(frame, evm); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV1Test.java b/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV1Test.java index 7f8547d6643..5ae88a6cc78 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV1Test.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/code/CodeV1Test.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,21 +11,18 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.evm.code; import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.evm.code.CodeV1Validation.validateCode; -import static org.hyperledger.besu.evm.code.CodeV1Validation.validateStack; +import java.util.Arrays; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.IntStream; import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -33,7 +30,7 @@ import org.junit.jupiter.params.provider.ValueSource; /** - * These tests focus on code only validations, which are checked within the code runs themselves. + * These tests focus on code-only validations, which are checked within the code runs themselves. * Tests that depend on the EOF container (such as CallF into other sections) are in EOFLayoutTest. */ class CodeV1Test { @@ -41,37 +38,88 @@ class CodeV1Test { public static final String ZERO_HEX = "00"; public static final String NOOP_HEX = "5b"; + private static void assertValidation(final String error, final String code) { + assertValidation(error, code, false, 1, 5); + } + + private static void assertValidation( + final String error, + final String code, + final boolean returning, + final int... codeSectionSizes) { + Bytes codeBytes = Bytes.fromHexString(code); + for (int i : codeSectionSizes) { + CodeSection[] codeSections = new CodeSection[i]; + Arrays.fill(codeSections, new CodeSection(1, 0, returning ? 0 : 0x80, 1, 1)); + EOFLayout testLayout = + new EOFLayout( + codeBytes, + 1, + codeSections, + new EOFLayout[0], + 0, + Bytes.EMPTY, + error, + new AtomicReference<>()); + assertValidation(error, codeBytes, codeSections[0], testLayout); + } + } + + private static void assertValidation( + final String error, + final Bytes codeBytes, + final CodeSection thisCodeSection, + final EOFLayout eofLayout) { + CodeV1Validation validator = new CodeV1Validation(0xc000); + final String validationError = validator.validateCode(codeBytes, thisCodeSection, eofLayout); + if (error == null) { + assertThat(validationError).isNull(); + } else { + assertThat(validationError).startsWith(error); + } + } + @Test void validCode() { String codeHex = - "0xEF0001 01000C 020003 000b 0002 0008 030000 00 00000000 02010001 01000002 60016002e30001e30002e4 01e4 60005360106000f3"; + "0xEF0001 01000C 020003 000b 0002 0008 040000 00 00800000 02010001 01000002 60016002e30001e30002f3 01e4 60005360106000e4"; final EOFLayout layout = EOFLayout.parseEOF(Bytes.fromHexString(codeHex.replace(" ", ""))); - - String validationError = validateCode(layout); + CodeV1Validation validator = new CodeV1Validation(0xc000); + String validationError = validator.validateCode(layout); assertThat(validationError).isNull(); } + @Test + void invalidCode() { + String codeHex = + "0xEF0001 01000C 020003 000b 0002 0008 040000 00 00000000 02010001 01000002 60016002e30001e30002f3 01e4 60005360106000e4"; + final EOFLayout layout = EOFLayout.parseEOF(Bytes.fromHexString(codeHex.replace(" ", ""))); + CodeV1Validation validator = new CodeV1Validation(0xc000); + String validationError = validator.validateCode(layout); + + assertThat(validationError) + .isEqualTo( + "Invalid EOF container - invalid_first_section_type want 0x80 (non-returning flag) has 0"); + } + @ParameterizedTest @ValueSource( - strings = {"3000", "5000", "e0000000", "6000e1000000", "6000e201000000", "fe00", "0000"}) + strings = {"3000", "5000", "e0000000", "6000e1000000", "6000e200000000", "fe00", "0000"}) void testValidOpcodes(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isNull(); + assertValidation(null, code); } @ParameterizedTest @ValueSource(strings = {"00", "3030f3", "3030fd", "fe"}) void testValidCodeTerminator(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isNull(); + assertValidation(null, code); } @ParameterizedTest @MethodSource("testPushValidImmediateArguments") void testPushValidImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isNull(); + assertValidation(null, code); } private static Stream testPushValidImmediateArguments() { @@ -84,8 +132,7 @@ private static Stream testPushValidImmediateArguments() { @ParameterizedTest @MethodSource("testRjumpValidImmediateArguments") void testRjumpValidImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isNull(); + assertValidation(null, code); } private static Stream testRjumpValidImmediateArguments() { @@ -105,8 +152,7 @@ private static Stream testRjumpValidImmediateArguments() { @ParameterizedTest @MethodSource("testRjumpiValidImmediateArguments") void testRjumpiValidImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isNull(); + assertValidation(null, code); } private static Stream testRjumpiValidImmediateArguments() { @@ -127,28 +173,26 @@ private static Stream testRjumpiValidImmediateArguments() { @ParameterizedTest @MethodSource("rjumptableValidImmediateArguments") void testRjumptableValidImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isNull(); + assertValidation(null, code); } private static Stream rjumptableValidImmediateArguments() { return Stream.of( - "6001e201000000", - "6001e202000000010000", - "6001e203000000040100" + "5b".repeat(256) + ZERO_HEX, - "6001e2040000000401007fff" + "5b".repeat(32767) + ZERO_HEX, - "6001e201fffc0000", - "5b".repeat(248) + "6001e202fffaff0000", - "5b".repeat(32760) + "6001e202fffa800000", - "e201000000") + "6001e200000000", + "6001e201000000010000", + "6001e202000600080100" + "5b".repeat(256) + ZERO_HEX, + "6001e2030008000801007ffe" + "5b".repeat(32767) + ZERO_HEX, + "6001e200fffc0000", + "5b".repeat(252) + "6001e201fffaff0000", + "5b".repeat(32764) + "6001e201fffa800000", + "e200000000") .map(Arguments::arguments); } @ParameterizedTest @MethodSource("invalidCodeArguments") void testInvalidCode(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).startsWith("Invalid Instruction 0x"); + assertValidation("undefined_instruction", code); } private static Stream invalidCodeArguments() { @@ -156,13 +200,12 @@ private static Stream invalidCodeArguments() { IntStream.rangeClosed(0x0c, 0x0f), IntStream.of(0x1e, 0x1f), IntStream.rangeClosed(0x21, 0x2f), - IntStream.rangeClosed(0x49, 0x4f), + IntStream.rangeClosed(0x4b, 0x4f), IntStream.rangeClosed(0xa5, 0xaf), - IntStream.rangeClosed(0xb0, 0xbf), - IntStream.rangeClosed(0xc0, 0xcf), - IntStream.rangeClosed(0xd0, 0xdf), - IntStream.rangeClosed(0xe5, 0xef), - IntStream.of(0xf6, 0xf7, 0xf8, 0xf9, 0xfb, 0xfc)) + IntStream.rangeClosed(0xb0, 0xcf), + IntStream.rangeClosed(0xd4, 0xdf), + IntStream.rangeClosed(0xe9, 0xeb), + IntStream.of(0xef, 0xf6, 0xfc)) .flatMapToInt(i -> i) .mapToObj(i -> String.format("%02x", i) + ZERO_HEX) .map(Arguments::arguments); @@ -171,8 +214,7 @@ private static Stream invalidCodeArguments() { @ParameterizedTest @MethodSource("pushTruncatedImmediateArguments") void testPushTruncatedImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isEqualTo("No terminating instruction"); + assertValidation("missing_stop_opcode", code); } private static Stream pushTruncatedImmediateArguments() { @@ -186,15 +228,13 @@ private static Stream pushTruncatedImmediateArguments() { @ParameterizedTest @ValueSource(strings = {"e0", "e000"}) void testRjumpTruncatedImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isEqualTo("Truncated relative jump offset"); + assertValidation("truncated_instruction", code); } @ParameterizedTest @ValueSource(strings = {"6001e1", "6001e100"}) void testRjumpiTruncatedImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isEqualTo("Truncated relative jump offset"); + assertValidation("truncated_instruction", code); } @ParameterizedTest @@ -208,8 +248,7 @@ void testRjumpiTruncatedImmediate(final String code) { "6001e2030000000100" }) void testRjumpvTruncatedImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isEqualTo("Truncated jump table"); + assertValidation("truncated_instruction", code); } @ParameterizedTest @@ -221,12 +260,11 @@ void testRjumpvTruncatedImmediate(final String code) { "6001e10000", "6001e1000100", "6001e1fffa00", - "6001e201000100", - "6001e201fff900" + "6001e200000300", + "6001e200fff900" }) void testRjumpsOutOfBounds(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isEqualTo("Relative jump destination out of bounds"); + assertValidation("invalid_rjump_destination", code); } @ParameterizedTest @@ -242,44 +280,44 @@ void testRjumpsOutOfBounds(final String code) { "6001e10001e0000000", "6001e10002e0000000", // RJUMPV into RJUMP immediate - "6001e2010001e0000000", - "6001e2010002e0000000", + "6001e2000001e0000000", + "6001e2000002e0000000", // RJUMP into RJUMPI immediate "e000036001e1000000", "e000046001e1000000", + // RJUMPI backwards into push + "6001e1fffc00", // RJUMPI into RJUMPI immediate "6001e1ffff00", "6001e1fffe00", "6001e100036001e1000000", "6001e100046001e1000000", // RJUMPV into RJUMPI immediate - "6001e20100036001e1000000", - "6001e20100046001e1000000", + "6001e20000036001e1000000", + "6001e20000046001e1000000", // RJUMP into RJUMPV immediate - "e00001e201000000", - "e00002e201000000", - "e00003e201000000", + "e00001e200000000", + "e00002e200000000", + "e00003e200000000", // RJUMPI into RJUMPV immediate - "6001e10001e201000000", - "6001e10002e201000000", - "6001e10003e201000000", + "6001e10001e200000000", + "6001e10002e200000000", + "6001e10003e200000000", // RJUMPV into RJUMPV immediate - "6001e201ffff00", - "6001e201fffe00", - "6001e201fffd00", - "6001e2010001e201000000", - "6001e2010002e201000000", - "6001e2010003e201000000", - "6001e2010001e2020000fff400", - "6001e2010002e2020000fff400", - "6001e2010003e2020000fff400", - "6001e2010004e2020000fff400", - "6001e2010005e2020000fff400" + "6001e200ffff00", + "6001e200fffe00", + "6001e200fffd00", + "6001e2000001e200000000", + "6001e2000002e200000000", + "6001e2000003e200000000", + "6001e2000001e2010000000000", + "6001e2000002e2010000000000", + "6001e2000003e2010000000000", + "6001e2000004e2010000000000", + "6001e2000005e2010000000000" }) void testRjumpsIntoImmediate(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError) - .isEqualTo("Relative jump destinations targets invalid immediate data"); + assertValidation("invalid_rjump_destination", code); } private static Stream rjumpsIntoImmediateExtraArguments() { @@ -304,7 +342,7 @@ private static Stream rjumpsIntoImmediateExtraArguments() { ZERO_HEX.repeat(n) + // push data ZERO_HEX, // STOP - String.format("6001e20100%02x", offset) + String.format("6001e20000%02x", offset) + String.format("%02x", 0x60 + n - 1) + // PUSHn ZERO_HEX.repeat(n) @@ -316,63 +354,52 @@ private static Stream rjumpsIntoImmediateExtraArguments() { .map(Arguments::arguments); } - @ParameterizedTest - @ValueSource(strings = {"6001e20000"}) - void testRjumpvEmptyTable(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isEqualTo("Empty jump table"); - } - @ParameterizedTest @ValueSource(strings = {"e3", "e300"}) void testCallFTruncated(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isEqualTo("Truncated CALLF"); + assertValidation("truncated_instruction", code); } @ParameterizedTest @ValueSource(strings = {"e5", "e500"}) - @Disabled("Out of Shahghai, will likely return in Cancun or Prague") void testJumpCallFTruncated(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isEqualTo("Truncated CALLF"); + assertValidation("truncated_instruction", code); } @ParameterizedTest @ValueSource(strings = {"e30004", "e303ff", "e3ffff"}) void testCallFWrongSection(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 3); - assertThat(validationError).startsWith("CALLF to non-existent section -"); + assertValidation("invalid_code_section_index", code, false, 3); } @ParameterizedTest @ValueSource(strings = {"e50004", "e503ff", "e5ffff"}) - @Disabled("Out of Shahghai, will likely return in Cancun or Prague") void testJumpFWrongSection(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 3); - assertThat(validationError).startsWith("CALLF to non-existent section -"); + assertValidation("invalid_code_section_index", code, false, 3); } @ParameterizedTest - @ValueSource(strings = {"e3000100", "e3000200", "e3000000"}) + @ValueSource(strings = {"e3000100", "e3000200"}) void testCallFValid(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 3); - assertThat(validationError).isNull(); + var testContainer = + EOFLayout.parseEOF( + Bytes.fromHexString( + "ef000101000c0200030001000100010400000000800000000000000000000000e4e4")); + + assertValidation( + null, Bytes.fromHexString(code), testContainer.getCodeSection(0), testContainer); } @ParameterizedTest @ValueSource(strings = {"e50001", "e50002", "e50000"}) - @Disabled("Out of Shahghai, will likely return in Cancun or Prague") void testJumpFValid(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 3); - assertThat(validationError).isNull(); + assertValidation(null, code, false, 3); } @ParameterizedTest @MethodSource("immediateContainsOpcodeArguments") void testImmediateContainsOpcode(final String code) { - final String validationError = validateCode(Bytes.fromHexString(code), 1); - assertThat(validationError).isNull(); + assertValidation(null, code); } private static Stream immediateContainsOpcodeArguments() { @@ -396,7 +423,7 @@ private static Stream immediateContainsOpcodeArguments() { // 0x60 byte which could be interpreted as PUSH, but it's not because it's in RJUMPV // data // offset = -160 - "5b".repeat(160) + "e201ff6000") + "5b".repeat(160) + "e200ff6000") .map(Arguments::arguments); } @@ -441,14 +468,21 @@ void validateStackAnalysis( + String.format("01%04x", sectionCount * 4) + String.format("02%04x", sectionCount) + codeLengths - + "030000" + + "040000" + "00" + typesData + codeData; EOFLayout eofLayout = EOFLayout.parseEOF(Bytes.fromHexString(sb)); - - assertThat(validateStack(sectionToTest, eofLayout)).isEqualTo(expectedError); + CodeV1Validation validator = new CodeV1Validation(0xc000); + + String validation = + validator.validateStack(sectionToTest, eofLayout, new WorkList(sectionCount)); + if (expectedError != null) { + assertThat(validation).contains(expectedError); + } else { + assertThat(validation).isNull(); + } } /** @@ -459,85 +493,88 @@ void validateStackAnalysis( * @return parameterized test vectors */ static Stream stackEmpty() { - return Stream.of(Arguments.of("Empty", null, 0, List.of(List.of("00", 0, 0, 0)))); + return Stream.of(Arguments.of("Empty", null, 0, List.of(List.of("00", 0, 0x80, 0)))); } static Stream stackEmptyAtExit() { return Stream.of( // this depends on requiring stacks to be "clean" returns - Arguments.of("Stack Empty at Exit", null, 0, List.of(List.of("43 50 00", 0, 0, 1))), + Arguments.of("Stack Empty at Exit", null, 0, List.of(List.of("43 50 00", 0, 0x80, 1))), Arguments.of( "Stack empty with input", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("50 00", 1, 0, 1))), + List.of(List.of("00", 0, 0x80, 0), List.of("50 00", 1, 0x80, 1))), // this depends on requiring stacks to be "clean" returns Arguments.of( "Stack not empty at output", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("00", 1, 0, 1)))); + List.of(List.of("00", 0, 0x80, 0), List.of("00", 1, 0x80, 1)))); } static Stream stackImmediateBytes() { return Stream.of( Arguments.of( - "Immediate Bytes - simple push", null, 0, List.of(List.of("6001 50 00", 0, 0, 1)))); + "Immediate Bytes - simple push", null, 0, List.of(List.of("6001 50 00", 0, 0x80, 1)))); } static Stream stackUnderflow() { return Stream.of( Arguments.of( - "Stack underflow", - "Operation 0x50 requires stack of 1 but only has 0 items", + "Stack underflow", "stack_underflow", 0, List.of(List.of("50 00", 0, 0x80, 1))), + Arguments.of( + "double rjumpi", + "stack_underflow", 0, - List.of(List.of("50 00", 0, 0, 1)))); + List.of(List.of("5f 5f e10005 5f 5f e10000 f3", 0, 0x80, 1)))); } static Stream stackRJumpForward() { return Stream.of( - Arguments.of("RJUMP 0", null, 0, List.of(List.of("e00000 00", 0, 0, 0))), + Arguments.of("RJUMP 0", null, 0, List.of(List.of("e00000 00", 0, 0x80, 0))), Arguments.of( "RJUMP 1 w/ dead code", - "Dead code detected in section 0", + "unreachable_instructions", 0, - List.of(List.of("e00001 43 00", 0, 0, 0))), + List.of(List.of("e00001 43 00", 0, 0x80, 0))), Arguments.of( "RJUMP 2 w/ dead code", - "Dead code detected in section 0", + "unreachable_instructions", 0, - List.of(List.of("e00002 43 50 00", 0, 0, 0))), + List.of(List.of("e00002 43 50 00", 0, 0x80, 0))), Arguments.of( "RJUMP 3 and -10", - null, + "unreachable_instructions", 0, - List.of(List.of("e00003 01 50 00 6001 6001 e0fff6", 0, 0, 2)))); + List.of(List.of("e00003 01 50 00 6001 6001 e0fff6", 0, 0x80, 2)))); } static Stream stackRJumpBackward() { return Stream.of( - Arguments.of("RJUMP -3", null, 0, List.of(List.of("e0fffd", 0, 0, 0))), - Arguments.of("RJUMP -4", null, 0, List.of(List.of("5B e0fffc", 0, 0, 0))), + Arguments.of("RJUMP -3", null, 0, List.of(List.of("e0fffd", 0, 0x80, 0))), + Arguments.of("RJUMP -4", null, 0, List.of(List.of("5B e0fffc", 0, 0x80, 0))), Arguments.of( "RJUMP -4 unmatched stack", - "Jump into code stack height (0) does not match previous value (1)", + "stack_height_mismatch", 0, - List.of(List.of("43 e0fffc", 0, 0, 0))), + List.of(List.of("43 e0fffc", 0, 0x80, 0))), Arguments.of( "RJUMP -4 unmatched stack", - "Jump into code stack height (1) does not match previous value (0)", + "stack_height_mismatch", 0, - List.of(List.of("43 50 e0fffc 00", 0, 0, 0))), - Arguments.of("RJUMP -3 matched stack", null, 0, List.of(List.of("43 50 e0fffd", 0, 0, 1))), + List.of(List.of("43 50 e0fffc 00", 0, 0x80, 0))), + Arguments.of( + "RJUMP -3 matched stack", null, 0, List.of(List.of("43 50 e0fffd", 0, 0x80, 1))), Arguments.of( - "RJUMP -4 matched stack", null, 0, List.of(List.of("43 50 5B e0fffc", 0, 0, 1))), + "RJUMP -4 matched stack", null, 0, List.of(List.of("43 50 5B e0fffc", 0, 0x80, 1))), Arguments.of( - "RJUMP -5 matched stack", null, 0, List.of(List.of("43 50 43 e0fffb", 0, 0, 1))), + "RJUMP -5 matched stack", null, 0, List.of(List.of("43 50 43 e0fffb", 0, 0x80, 1))), Arguments.of( "RJUMP -4 unmatched stack", - "Jump into code stack height (0) does not match previous value (1)", + "stack_height_mismatch", 0, - List.of(List.of("43 50 43 e0fffc 50 00", 0, 0, 0)))); + List.of(List.of("43 50 43 e0fffc 50 00", 0, 0x80, 0)))); } static Stream stackRJumpI() { @@ -546,61 +583,61 @@ static Stream stackRJumpI() { "RJUMPI Each branch ending with STOP", null, 0, - List.of(List.of("60ff 6001 e10002 50 00 50 00", 0, 0, 2))), + List.of(List.of("60ff 6001 e10002 50 00 50 00", 0, 0x80, 2))), Arguments.of( "RJUMPI One branch ending with RJUMP", null, 0, - List.of(List.of("60ff 6001 e10004 50 e00001 50 00", 0, 0, 2))), + List.of(List.of("60ff 6001 e10004 50 e00001 50 00", 0, 0x80, 2))), Arguments.of( "RJUMPI Fallthrough", null, 0, - List.of(List.of("60ff 6001 e10004 80 80 50 50 50 00", 0, 0, 3))), + List.of(List.of("60ff 6001 e10004 80 80 50 50 50 00", 0, 0x80, 3))), Arguments.of( - "RJUMPI Offset 0", null, 0, List.of(List.of("60ff 6001 e10000 50 00", 0, 0, 2))), + "RJUMPI Offset 0", null, 0, List.of(List.of("60ff 6001 e10000 50 00", 0, 0x80, 2))), Arguments.of( "Simple loop (RJUMPI offset = -5)", null, 0, - List.of(List.of("6001 60ff 81 02 80 e1fffa 50 50 00", 0, 0, 3))), + List.of(List.of("6001 60ff 81 02 80 e1fffa 50 50 00", 0, 0x80, 3))), Arguments.of( "RJUMPI One branch increasing max stack more stack than another", null, 0, - List.of(List.of("6001 e10007 30 30 30 50 50 50 00 30 50 00", 0, 0, 3))), + List.of(List.of("6001 e10007 30 30 30 50 50 50 00 30 50 00", 0, 0x80, 3))), Arguments.of( "RJUMPI One branch increasing max stack more stack than another II", null, 0, - List.of(List.of("6001 e10003 30 50 00 30 30 30 50 50 50 00", 0, 0, 3))), + List.of(List.of("6001 e10003 30 50 00 30 30 30 50 50 50 00", 0, 0x80, 3))), Arguments.of( "RJUMPI Missing stack argument", - "Operation 0xE1 requires stack of 1 but only has 0 items", + "stack_underflow", 0, - List.of(List.of("e10000 00", 0, 0, 0))), + List.of(List.of("e10000 00", 0, 0x80, 0))), Arguments.of( "Stack underflow one branch", - "Operation 0x02 requires stack of 2 but only has 1 items", + "stack_underflow", 0, - List.of(List.of("60ff 6001 e10002 50 00 02 50 00", 0, 0, 0))), + List.of(List.of("60ff 6001 e10002 50 00 02 50 00", 0, 0x80, 0))), Arguments.of( "Stack underflow another branch", - "Operation 0x02 requires stack of 2 but only has 1 items", + "stack_underflow", 0, - List.of(List.of("60ff 6001 e10002 02 00 19 50 00", 0, 0, 0))), + List.of(List.of("60ff 6001 e10002 02 00 19 50 00", 0, 0x80, 0))), // this depends on requiring stacks to be "clean" returns Arguments.of( "RJUMPI Stack not empty in the end of one branch", null, 0, - List.of(List.of("60ff 6001 e10002 50 00 19 00", 0, 0, 2))), + List.of(List.of("60ff 6001 e10002 50 00 19 00", 0, 0x80, 2))), // this depends on requiring stacks to be "clean" returns Arguments.of( "RJUMPI Stack not empty in the end of one branch II", null, 0, - List.of(List.of("60ff 6001 e10002 19 00 50 00", 0, 0, 2)))); + List.of(List.of("60ff 6001 e10002 19 00 50 00", 0, 0x80, 2)))); } static Stream stackCallF() { @@ -609,225 +646,231 @@ static Stream stackCallF() { "0 input 0 output", null, 0, - List.of(List.of("e30001 00", 0, 0, 0), List.of("e4", 0, 0, 0))), + List.of(List.of("e30001 00", 0, 0x80, 0), List.of("e4", 0, 0, 0))), Arguments.of( "0 inputs, 0 output 3 sections", null, 0, - List.of(List.of("e30002 00", 0, 0, 0), List.of("e4", 1, 1, 1), List.of("e4", 0, 0, 0))), + List.of( + List.of("e30002 00", 0, 0x80, 0), List.of("e4", 1, 1, 1), List.of("e4", 0, 0, 0))), Arguments.of( "more than 0 inputs", null, 0, - List.of(List.of("30 e30001 00", 0, 0, 1), List.of("00", 1, 0, 1))), + List.of(List.of("30 e30001 00", 0, 0x80, 1), List.of("00", 1, 0x80, 1))), Arguments.of( "forwarding an argument", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("e30002 00", 1, 0, 1), List.of("00", 1, 0, 1))), + List.of( + List.of("00", 0, 0x80, 0), + List.of("e30002 00", 1, 0x80, 1), + List.of("00", 1, 0x80, 1))), Arguments.of( "more than 1 inputs", null, 0, - List.of(List.of("30 80 e30001 00", 0, 0, 2), List.of("00", 2, 0, 2))), + List.of(List.of("30 80 e30001 00", 0, 0x80, 2), List.of("00", 2, 0x80, 2))), Arguments.of( "more than 0 outputs", null, 0, - List.of(List.of("e30001 50 00", 0, 0, 1), List.of("3000", 0, 1, 1))), + List.of(List.of("e30001 50 00", 0, 0x80, 1), List.of("30e4", 0, 1, 1))), Arguments.of( "more than 0 outputs 3 sections", null, 0, List.of( - List.of("e30002 50 00", 0, 0, 1), - List.of("00", 0, 0, 0), + List.of("e30002 50 00", 0, 0x80, 1), + List.of("00", 0, 0x80, 0), List.of("30305000", 0, 1, 2))), Arguments.of( "more than 1 outputs", null, 0, - List.of(List.of("e30001 50 50 00", 0, 0, 2), List.of("303000", 0, 2, 2))), + List.of(List.of("e30001 50 50 00", 0, 0x80, 2), List.of("3030e4", 0, 2, 2))), Arguments.of( "more than 0 inputs, more than 0 outputs", null, 0, List.of( - List.of("30 30 e30001 50 50 50 00", 0, 0, 3), - List.of("30 30 e30001 50 50 00", 2, 3, 5))), - Arguments.of("recursion", null, 0, List.of(List.of("e30000 00", 0, 0, 0))), + List.of("30 30 e30001 50 50 50 00", 0, 0x80, 3), + List.of("30 30 e30001 50 50 e4", 2, 3, 5))), + Arguments.of("recursion", null, 0, List.of(List.of("e30000 00", 0, 0x80, 0))), Arguments.of( "recursion 2 inputs", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("e30000 00", 2, 0, 2))), + List.of(List.of("00", 0, 0x80, 0), List.of("e30000 00", 2, 0x80, 2))), Arguments.of( "recursion 2 inputs 2 outputs", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("e30000 50 50 00", 2, 2, 2))), + List.of(List.of("00", 0, 0x80, 0), List.of("e30000 50 50 00", 2, 2, 2))), Arguments.of( "recursion 2 inputs 1 output", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("30 30 e30001 50 50 50 00", 2, 1, 4))), + List.of(List.of("00", 0, 0x80, 0), List.of("30 30 e30001 50 50 50 00", 2, 1, 4))), Arguments.of( "multiple CALLFs with different types", null, 1, List.of( - List.of("00", 0, 0, 0), - List.of("44 e30002 80 80 e30003 44 80 e30004 50 50 00", 0, 0, 3), - List.of("3030505000", 1, 1, 3), - List.of("50505000", 3, 0, 3), - List.of("00", 2, 2, 2))), + List.of("00", 0, 0x80, 0), + List.of("44 e30002 80 80 e30003 44 80 e30004 50 50 e4", 0, 0, 3), + List.of("30305050e4", 1, 1, 3), + List.of("505050e4", 3, 0, 3), + List.of("e4", 2, 2, 2))), Arguments.of( "underflow", - "Operation 0xE3 requires stack of 1 but only has 0 items", + "stack_underflow", 0, - List.of(List.of("e30001 00", 0, 0, 0), List.of("00", 1, 0, 0))), + List.of(List.of("e30001 00", 0, 0x80, 0), List.of("e4", 1, 0, 0))), Arguments.of( "underflow 2", - "Operation 0xE3 requires stack of 2 but only has 1 items", + "stack_underflow", 0, - List.of(List.of("30 e30001 00", 0, 0, 0), List.of("00", 2, 0, 2))), + List.of(List.of("30 e30001 00", 0, 0x80, 0), List.of("e4", 2, 0, 2))), Arguments.of( "underflow 3", - "Operation 0xE3 requires stack of 1 but only has 0 items", + "stack_underflow", 1, - List.of(List.of("00", 0, 0, 0), List.of("50 e30001 00", 1, 0, 1))), + List.of(List.of("00", 0, 0x80, 0), List.of("50 e30001 e4", 1, 0, 1))), Arguments.of( "underflow 4", - "Operation 0xE3 requires stack of 3 but only has 2 items", + "stack_underflow", 0, List.of( - List.of("44 e30001 80 e30002 00", 0, 0, 0), - List.of("00", 1, 1, 1), - List.of("00", 3, 0, 3)))); + List.of("44 e30001 80 e30002 00", 0, 0x80, 0), + List.of("e4", 1, 1, 1), + List.of("e4", 3, 0, 3)))); } static Stream stackRetF() { return Stream.of( Arguments.of( "0 outputs at section 0", - null, + "EOF Layout invalid - invalid_first_section_type want 0x80 (non-returning flag) has 0", 0, - List.of(List.of("e4", 0, 0, 0), List.of("00", 0, 0, 0))), + List.of(List.of("e4", 0, 0, 0), List.of("e4", 0, 0, 0))), Arguments.of( "0 outputs at section 1", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("e4", 0, 0, 0))), + List.of(List.of("00", 0, 0x80, 0), List.of("e4", 0, 0, 0))), Arguments.of( "0 outputs at section 2", null, 2, - List.of(List.of("00", 0, 0, 0), List.of("00", 1, 1, 1), List.of("e4", 0, 0, 0))), + List.of(List.of("00", 0, 0x80, 0), List.of("e4", 1, 1, 1), List.of("e4", 0, 0, 0))), Arguments.of( "more than 0 outputs section 0", - null, + "EOF Layout invalid - invalid_first_section_type want 0x80 (non-returning flag) has 0", 0, List.of(List.of("44 50 e4", 0, 0, 1), List.of("4400", 0, 1, 1))), Arguments.of( "more than 0 outputs section 0", - null, + "EOF Layout invalid - invalid_first_section_type want 0x80 (non-returning flag) has 0", 1, List.of(List.of("00", 0, 0, 0), List.of("44 e4", 0, 1, 1))), Arguments.of( "more than 1 outputs section 1", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("44 80 e4", 0, 2, 2))), + List.of(List.of("00", 0, 0x80, 0), List.of("44 80 e4", 0, 2, 2))), Arguments.of( "Forwarding return values", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("e4", 1, 1, 1))), + List.of(List.of("00", 0, 0x80, 0), List.of("e4", 1, 1, 1))), Arguments.of( "Forwarding of return values 2", null, 1, List.of( - List.of("00", 0, 0, 0), List.of("e30002 e4", 0, 1, 1), List.of("3000", 0, 1, 1))), + List.of("00", 0, 0x80, 0), + List.of("e30002 e4", 0, 1, 1), + List.of("30e4", 0, 1, 1))), Arguments.of( "Multiple RETFs", null, 1, - List.of(List.of("00", 0, 0, 0), List.of("e10003 44 80 e4 30 80 e4", 1, 2, 2))), + List.of(List.of("00", 0, 0x80, 0), List.of("e10003 44 80 e4 30 80 e4", 1, 2, 2))), Arguments.of( "underflow 1", - "Section return (RETF) calculated height 0x0 does not match configured height 0x1", + "RETF in section 1 calculated height 0 does not match configured return stack 1, min height 0, and max height 0", 1, - List.of(List.of("00", 0, 0, 0), List.of("e4", 0, 1, 0))), + List.of(List.of("00", 0, 0x80, 0), List.of("e4", 0, 1, 0))), Arguments.of( "underflow 2", - "Section return (RETF) calculated height 0x1 does not match configured height 0x2", + "RETF in section 1 calculated height 1 does not match configured return stack 2, min height 1, and max height 1", 1, - List.of(List.of("00", 0, 0, 0), List.of("44 e4", 0, 2, 1))), + List.of(List.of("00", 0, 0x80, 0), List.of("44 e4", 0, 2, 1))), Arguments.of( "underflow 3", - "Section return (RETF) calculated height 0x1 does not match configured height 0x2", + "RETF in section 1 calculated height 1 does not match configured return stack 2, min height 1, and max height 1", 1, - List.of(List.of("00", 0, 0, 0), List.of("e10003 44 80 e4 30 e4", 1, 2, 2)))); + List.of(List.of("00", 0, 0x80, 0), List.of("e10003 44 80 e4 30 e4", 1, 2, 2)))); } static Stream stackUnreachable() { return Stream.of( Arguments.of( "Max stack not changed by unreachable code", - "Dead code detected in section 0", + "unreachable_instructions", 0, - List.of(List.of("30 50 00 30 30 30 50 50 50 00", 0, 0, 1))), + List.of(List.of("30 50 00 30 30 30 50 50 50 00", 0, 0x80, 1))), Arguments.of( "Max stack not changed by unreachable code RETf", - "Dead code detected in section 0", + "unreachable_instructions", 0, - List.of(List.of("30 50 e4 30 30 30 50 50 50 00", 0, 0, 1))), + List.of(List.of("30 50 e4 30 30 30 50 50 50 00", 0, 0x80, 1))), Arguments.of( "Max stack not changed by unreachable code RJUMP", - "Dead code detected in section 0", + "unreachable_instructions", 0, - List.of(List.of("30 50 e00006 30 30 30 50 50 50 00", 0, 0, 1))), + List.of(List.of("30 50 e00006 30 30 30 50 50 50 00", 0, 0x80, 1))), Arguments.of( "Stack underflow in unreachable code", - "Dead code detected in section 0", + "unreachable_instructions", 0, - List.of(List.of("30 50 00 50 00", 0, 0, 1))), + List.of(List.of("30 50 00 50 00", 0, 0x80, 1))), Arguments.of( "Stack underflow in unreachable code RETF", - "Dead code detected in section 0", + "unreachable_instructions", 0, - List.of(List.of("30 50 e4 50 00", 0, 0, 1))), + List.of(List.of("30 50 e4 50 00", 0, 0x80, 1))), Arguments.of( "Stack underflow in unreachable code RJUMP", - "Dead code detected in section 0", + "unreachable_instructions", 0, - List.of(List.of("30 50 e00001 50 00", 0, 0, 1)))); + List.of(List.of("30 50 e00001 50 00", 0, 0x80, 1)))); } static Stream stackHeight() { return Stream.of( Arguments.of( "Stack height mismatch backwards", - "Jump into code stack height (0) does not match previous value (1)", + "stack_height_mismatch", 0, - List.of(List.of("30 e0fffc00", 0, 0, 1))), + List.of(List.of("30 e0fffc00", 0, 0x80, 1))), Arguments.of( "Stack height mismatch forwards", - "Jump into code stack height (3) does not match previous value (0)", + "invalid_max_stack_height", 0, - List.of(List.of("30e10003303030303000", 0, 0, 2)))); + List.of(List.of("30e10003303030303000", 0, 0x80, 2)))); } static Stream invalidInstructions() { return IntStream.range(0, 256) - .filter(opcode -> CodeV1Validation.OPCODE_ATTRIBUTES[opcode] == CodeV1Validation.INVALID) + .filter(opcode -> !OpcodeInfo.V1_OPCODES[opcode].valid()) .mapToObj( opcode -> Arguments.of( String.format("Invalid opcode %02x", opcode), - String.format("Invalid Instruction 0x%02x", opcode), + "undefined_instruction", 0, - List.of(List.of(String.format("0x%02x", opcode), 0, 0, 0)))); + List.of(List.of(String.format("0x%02x", opcode), 0, 0x80, 0)))); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/code/EOFLayoutTest.java b/evm/src/test/java/org/hyperledger/besu/evm/code/EOFLayoutTest.java index 3e8e17f2a65..2cce45830de 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/code/EOFLayoutTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/code/EOFLayoutTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.evm.code; import static org.assertj.core.api.Assertions.assertThat; @@ -22,6 +20,7 @@ import java.util.Collection; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -30,219 +29,235 @@ public class EOFLayoutTest { public static Collection containersWithFormatErrors() { return Arrays.asList( new Object[][] { - {"EF", "No magic", "EOF Container too small", -1}, - {"FFFFFF", "Wrong magic", "EOF header byte 0 incorrect", -1}, - {"EFFF01010002020004006000AABBCCDD", "Invalid magic", "EOF header byte 1 incorrect", -1}, - {"EF00", "No version", "EOF Container too small", -1}, - {"EF0000010002020004006000AABBCCDD", "Invalid version", "Unsupported EOF Version 0", 0}, - {"EF0002010002020004006000AABBCCDD", "Invalid version", "Unsupported EOF Version 2", 2}, - { - "EF00FF010002020004006000AABBCCDD", - "Invalid version", - "Unsupported EOF Version 255", - 255 - }, - {"EF0001", "No header", "Improper section headers", 1}, - {"EF0001 00", "No code section", "Expected kind 1 but read kind 0", 1}, - {"EF0001 01", "No code section size", "Invalid Types section size", 1}, - {"EF0001 0100", "Code section size incomplete", "Invalid Types section size", 1}, - {"EF0001 010004", "No section terminator", "Improper section headers", 1}, - {"EF0001 010004 00", "No code section contents", "Expected kind 2 but read kind 0", 1}, - {"EF0001 010004 02", "No code section count", "Invalid Code section count", 1}, - {"EF0001 010004 0200", "Short code section count", "Invalid Code section count", 1}, - { - "EF0001 010004 020001", + {"EF", "No magic", "invalid_magic EOF Container too small", -1}, + {"FFFFFF", "Wrong magic", "invalid_magic EOF header byte 0 incorrect", -1}, + { + "EFFF01010002020004006000AABBCCDD", + "Invalid magic", + "invalid_magic EOF header byte 1 incorrect", + -1 + }, + {"EF00", "No version", "invalid_magic EOF Container too small", -1}, + {"EF0000010002020004006000AABBCCDD", "Invalid version", "invalid_version 0", 0}, + {"EF0002010002020004006000AABBCCDD", "Invalid version", "invalid_version 2", 2}, + {"EF00FF010002020004006000AABBCCDD", "Invalid version", "invalid_version 255", 255}, + {"EF0001", "No header", "missing_headers_terminator Improper section headers", 1}, + {"EF0001 00", "No code section", "unexpected_header_kind expected 1 actual 0", 1}, + { + "EF0001 01", "No code section size", - "Invalid Code section size for section 0", + "invalid_type_section_size Invalid Types section size (mod 4 != 0)", + 1 + }, + { + "EF0001 0100", + "Code section size incomplete", + "invalid_type_section_size Invalid Types section size (mod 4 != 0)", + 1 + }, + { + "EF0001 010004", + "No section terminator", + "missing_headers_terminator Improper section headers", + 1 + }, + { + "EF0001 010004 00", + "No code section contents", + "unexpected_header_kind expected 2 actual 0", 1 }, { - "EF0001 010004 02000100", - "Short code section size", - "Invalid Code section size for section 0", + "EF0001 010004 02", + "No code section count", + "incomplete_section_number Too few code sections", 1 }, + { + "EF0001 010004 0200", + "Short code section count", + "incomplete_section_number Too few code sections", + 1 + }, + {"EF0001 010004 020001", "No code section size", "zero_section_size code 0", 1}, + {"EF0001 010004 02000100", "Short code section size", "zero_section_size code 0", 1}, { "EF0001 010008 0200020001", "No code section size multiple codes", - "Invalid Code section size for section 1", + "zero_section_size code 1", 1 }, { "EF0001 010008 020002000100", "No code section size multiple codes", - "Invalid Code section size for section 1", + "zero_section_size code 1", + 1 + }, + {"EF0001 010004 0200010001 04", "No data section size", "incomplete_data_header", 1}, + {"EF0001 010004 0200010001 0400", "Short data section size", "incomplete_data_header", 1}, + { + "EF0001 010004 0200010001 040000", + "No Terminator", + "missing_headers_terminator Improper section headers", 1 }, - {"EF0001 010004 0200010001 03", "No data section size", "Invalid Data section size", 1}, { - "EF0001 010004 0200010001 0300", - "Short data section size", - "Invalid Data section size", + "EF0001 010004 0200010002 040000 00", + "No type section", + "invalid_section_bodies_size Incomplete type section", 1 }, - {"EF0001 010004 0200010001 030000", "No Terminator", "Improper section headers", 1}, - {"EF0001 010004 0200010002 030000 00", "No type section", "Incomplete type section", 1}, { - "EF0001 010004 0200010002 030001 030001 00 DA DA", + "EF0001 010004 0200010002 040001 040001 00 DA DA", "Duplicate data sections", - "Expected kind 0 but read kind 3", + "unexpected_header_kind expected 0 actual 4", 1 }, { - "EF0001 010004 0200010002 030000 00 00", - "Incomplete type section", + "EF0001 010004 0200010002 040000 00 00", "Incomplete type section", + "invalid_section_bodies_size Incomplete type section", 1 }, { - "EF0001 010008 02000200020002 030000 00 00000000FE", - "Incomplete type section", + "EF0001 010008 02000200020002 040000 00 00000000FE", "Incomplete type section", + "invalid_section_bodies_size Incomplete type section", 1 }, { - "EF0001 010008 0200010001 030000 00 00000000 FE ", + "EF0001 010008 0200010001 040000 00 00000000 FE ", "Incorrect type section size", - "Type section length incompatible with code section count - 0x1 * 4 != 0x8", + "invalid_section_bodies_size Type section - 0x1 * 4 != 0x8", 1 }, { - "EF0001 010008 02000200010001 030000 00 0100000000000000 FE FE", + "EF0001 010008 02000200010001 040000 00 0100000000000000 FE FE", "Incorrect section zero type input", - "Code section does not have zero inputs and outputs", + "invalid_first_section_type must be zero input non-returning", 1 }, { - "EF0001 010008 02000200010001 030000 00 0001000000000000 FE FE", + "EF0001 010008 02000200010001 040000 00 0001000000000000 FE FE", "Incorrect section zero type output", - "Code section does not have zero inputs and outputs", + "invalid_first_section_type must be zero input non-returning", 1 }, { - "EF0001 010004 0200010002 030000 00 00000000 ", + "EF0001 010004 0200010002 040000 00 00000000 ", "Incomplete code section", - "Incomplete code section 0", + "invalid_section_bodies_size code section 0", 1 }, { - "EF0001 010004 0200010002 030000 00 00000000 FE", + "EF0001 010004 0200010002 040000 00 00000000 FE", "Incomplete code section", - "Incomplete code section 0", + "invalid_section_bodies_size code section 0", 1 }, { - "EF0001 010008 02000200020002 030000 00 00000000 00000000 FEFE ", + "EF0001 010008 02000200020002 040000 00 00800000 00000000 FEFE ", "No code section multiple", - "Incomplete code section 1", + "invalid_section_bodies_size code section 1", 1 }, { - "EF0001 010008 02000200020002 030000 00 00000000 00000000 FEFE FE", + "EF0001 010008 02000200020002 040000 00 00800000 00000000 FEFE FE", "Incomplete code section multiple", - "Incomplete code section 1", - 1 - }, - { - "EF0001 010004 0200010001 030003 00 00000000 FE DEADBEEF", - "Incomplete data section", - "Dangling data after end of all sections", + "invalid_section_bodies_size code section 1", 1 }, { - "EF0001 010004 0200010001 030003 00 00000000 FE BEEF", - "Incomplete data section", - "Incomplete data section", + "EF0001 010004 0200010001 040003 00 00800000 FE DEADBEEF", + "Excess data section", + "invalid_section_bodies_size data after end of all sections", 1 }, { - "EF0001 0200010001 030001 00 FE DA", + "EF0001 0200010001 040001 00 FE DA", "type section missing", - "Expected kind 1 but read kind 2", + "unexpected_header_kind expected 1 actual 2", 1 }, { - "EF0001 010004 030001 00 00000000 DA", + "EF0001 010004 040001 00 00000000 DA", "code section missing", - "Expected kind 2 but read kind 3", + "unexpected_header_kind expected 2 actual 4", 1 }, { "EF0001 010004 0200010001 00 00000000 FE", "data section missing", - "Expected kind 3 but read kind 0", + "unexpected_header_kind expected 4 actual 0", 1 }, { - "EF0001 030001 00 DA", + "EF0001 040001 00 DA", "type and code section missing", - "Expected kind 1 but read kind 3", + "unexpected_header_kind expected 1 actual 4", 1 }, { "EF0001 0200010001 00 FE", "type and data section missing", - "Expected kind 1 but read kind 2", + "unexpected_header_kind expected 1 actual 2", 1 }, { "EF0001 010004 00 00000000", "code and data sections missing", - "Expected kind 2 but read kind 0", + "unexpected_header_kind expected 2 actual 0", 1 }, - {"EF0001 00", "all sections missing", "Expected kind 1 but read kind 0", 1}, + {"EF0001 00", "all sections missing", "unexpected_header_kind expected 1 actual 0", 1}, { "EF0001 011004 020401" + " 0001".repeat(1025) - + " 030000 00" + + " 040000 00" + " 00000000".repeat(1025) + " FE".repeat(1025), "no data section, 1025 code sections", - "Too many code sections - 0x401", + "too_many_code_sections - 0x401", 1 }, - {"ef000101000002000003000000", "All kinds zero size", "Invalid Types section size", 1}, - {"ef0001010000020001000103000000ef", "Zero type size ", "Invalid Types section size", 1}, { - "ef0001010004020001000003000000", - "Zero code section length", - "Invalid Code section size for section 0", + "ef000101000002000003000000", + "All kinds zero size", + "incomplete_section_number Too few code sections", 1 }, - {"ef000101000402000003000000", "Zero code sections", "Invalid Code section count", 1}, - }); - } - - public static Collection correctContainers() { - return Arrays.asList( - new Object[][] { { - "EF0001 010004 0200010001 030000 00 00000000 FE", - "no data section, one code section", - null, + "ef0001010000020001000103000000ef", + "Zero type size ", + "invalid_section_bodies_size Type section - 0x1 * 4 != 0x0", 1 }, { - "EF0001 010004 0200010001 030001 00 00000000 FE DA", - "with data section, one code section", - null, + "ef0001010004020001000003000000", + "Zero code section length", + "zero_section_size code 0", 1 }, { - "EF0001 010008 02000200010001 030000 00 00000000 00000000 FE FE", - "no data section, multiple code section", - null, + "ef000101000402000003000000", + "Zero code sections", + "incomplete_section_number Too few code sections", 1 }, + }); + } + + public static Collection correctContainers() { + return Arrays.asList( + new Object[][] { { - "EF0001 010008 02000200010001 030001 00 00000000 00000000 FE FE DA", - "with data section, multiple code section", + "0xef0001 010004 0200010010 040000 00 00800002 e00001 f3 6001 6000 53 6001 6000 e0fff3 ", + "1", null, 1 }, { - "EF0001 010010 0200040001000200020002 030000 00 00000000 01000001 00010001 02030003 FE 5000 3000 8000", + "EF0001 010010 0200040001000200020002 040000 00 00800000 01000001 00010001 02030003 FE 5000 3000 8000", "non-void input and output types", null, 1 @@ -250,8 +265,8 @@ public static Collection correctContainers() { { "EF0001 011000 020400" + " 0001".repeat(1024) - + " 030000 00" - + " 00000000".repeat(1024) + + " 040000 00" + + " 00800000".repeat(1024) + " FE".repeat(1024), "no data section, 1024 code sections", null, @@ -264,37 +279,37 @@ public static Collection typeSectionTests() { return Arrays.asList( new Object[][] { { - "EF0001 010008 02000200020002 030000 00 0100000000000000", + "EF0001 010008 02000200020002 040000 00 0100000000000000", "Incorrect section zero type input", - "Code section does not have zero inputs and outputs", + "invalid_first_section_type must be zero input non-returning", 1 }, { - "EF0001 010008 02000200020002 030000 00 0001000000000000", + "EF0001 010008 02000200020002 040000 00 0001000000000000", "Incorrect section zero type output", - "Code section does not have zero inputs and outputs", + "invalid_first_section_type must be zero input non-returning", 1 }, { - "EF0001 010010 0200040001000200020002 030000 00 00000000 80000000 00010000 02030000 FE 5000 3000 8000", + "EF0001 010010 0200040001000200020002 040000 00 00800000 F0000000 00010000 02030000 FE 5000 3000 8000", "inputs too large", - "Type data input stack too large - 0x80", + "inputs_outputs_num_above_limit Type data input stack too large - 0xf0", 1 }, { - "EF0001 010010 0200040001000200020002 030000 00 00000000 01000000 00800000 02030000 FE 5000 3000 8000", + "EF0001 010010 0200040001000200020002 040000 00 00800000 01000000 00F00000 02030000 FE 5000 3000 8000", "outputs too large", - "Type data output stack too large - 0x80", + "inputs_outputs_num_above_limit - 0xf0", 1 }, { - "EF0001 010010 0200040001000200020002 030000 00 00000400 01000000 00010000 02030400 FE 5000 3000 8000", + "EF0001 010010 0200040001000200020002 040000 00 00000400 01000000 00010000 02030400 FE 5000 3000 8000", "stack too large", - "Type data max stack too large - 0x400", + "max_stack_height_above_limit Type data max stack too large - 0x400", 1 }, { - "EF0001 010010 0200040001000200020002 030000 00 00000000 01000001 00010001 02030003 FE 5000 3000 8000", + "EF0001 010010 0200040001000200020002 040000 00 00800000 01000001 00010001 02030003 FE 5000 3000 8000", "non-void input and output types", null, 1 @@ -302,20 +317,101 @@ public static Collection typeSectionTests() { }); } + public static Collection subContainers() { + return Arrays.asList( + new Object[][] { + { + "EF0001 010004 0200010001 0300010014 040000 00 00800000 FE EF000101000402000100010400000000800000FE", + "no data section, one code section, one subcontainer", + null, + 1 + }, + { + "EF00 01 010004 0200010001 0300010014 040000 00 00800000 00 (EF0001 010004 0200010001 040000 00 00800000 00)", + "evmone - one container", + null, + 1 + }, + { + "EF00 01 010004 0200010001 0300010014 040003 00 00800000 00 (EF0001 010004 0200010001 040000 00 00800000 00) 000000", + "evmone - one container two bytes", + null, + 1 + }, + { + "EF00 01 010004 0200010001 030003001400140014 040000 00 00800000 00 (EF0001 010004 0200010001 040000 00 00800000 00)(EF0001 010004 0200010001 040000 00 00800000 00)(EF0001 010004 0200010001 040000 00 00800000 00)", + "evmone - three subContainers", + null, + 1 + }, + { + "EF00 01 010004 0200010001 030003001400140014 040003 00 00800000 00 (EF0001 010004 0200010001 040000 00 00800000 00)(EF0001 010004 0200010001 040000 00 00800000 00)(EF0001 010004 0200010001 040000 00 00800000 00) ddeeff", + "evmone - three subContainers and data", + null, + 1 + }, + { + "EF00 01 01000C 020003000100010001 030003001400140014 040003 00 008000000000000000000000 00 00 00 (EF0001 010004 0200010001 040000 00 00800000 00)(EF0001 010004 0200010001 040000 00 00800000 00)(EF0001 010004 0200010001 040000 00 00800000 00) ddeeff", + "evmone - three subContainers three code and data", + null, + 1 + }, + { + "EF00 01 010004 0200010001 0300010100 040000 00 00800000 00 (EF0001 010004 02000100ED 040000 00 00800000 " + + "5d".repeat(237) + + ")", + "evmone - 256 byte container", + null, + 1 + }, + { + "EF00 01 010004 0200010001 030100" + + "0014".repeat(256) + + "040000 00 00800000 00 " + + "(EF0001 010004 0200010001 040000 00 00800000 00)".repeat(256), + "evmone - 256 subContainers", + null, + 1 + }, + { + "EF00 01 010004 0200010001 0300010015 040000 00 00800000 00 (EF0001 010004 0200010001 040000 00 00800000 00ff)", + "dangling data in subcontainer", + "invalid_section_bodies_size subcontainer size mismatch", + 1 + }, + { + "EF00 01 010004 0200010001 0300010014 040000 00 00800000 00 (EF0001 010004 0200010001 040000 00 00800000 00ff)", + "dangling data in container", + "invalid_section_bodies_size data after end of all sections", + 1 + }, + }); + } + @ParameterizedTest(name = "{1}") - @MethodSource({"correctContainers", "containersWithFormatErrors", "typeSectionTests"}) + @MethodSource({ + "correctContainers", + "containersWithFormatErrors", + "typeSectionTests", + "subContainers" + }) void test( final String containerString, final String description, final String failureReason, final int expectedVersion) { - final Bytes container = Bytes.fromHexString(containerString.replace(" ", "")); - final EOFLayout layout = EOFLayout.parseEOF(container); + final Bytes container = Bytes.fromHexString(containerString.replaceAll("[^a-fxA-F0-9]", "")); + final EOFLayout layout = EOFLayout.parseEOF(container, true); - assertThat(layout.getVersion()).isEqualTo(expectedVersion); - assertThat(layout.getInvalidReason()).isEqualTo(failureReason); - assertThat(layout.getContainer()).isEqualTo(container); - if (layout.getInvalidReason() != null) { + if (failureReason != null) { + assertThat(failureReason) + .withFailMessage("Error string should start with a reference test error code") + .matches("^[a-zA-Z]+_.*"); + } + assertThat(layout.version()).isEqualTo(expectedVersion); + assertThat(layout.invalidReason()).isEqualTo(failureReason); + assertThat(layout.container()).isEqualTo(container); + if (layout.invalidReason() != null) { assertThat(layout.isValid()).isFalse(); assertThat(layout.getCodeSectionCount()).isZero(); } else { @@ -323,4 +419,11 @@ void test( assertThat(layout.getCodeSectionCount()).isNotZero(); } } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/fluent/EVMExecutorTest.java b/evm/src/test/java/org/hyperledger/besu/evm/fluent/EVMExecutorTest.java index 176f0070980..aa9dcdb23f3 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/fluent/EVMExecutorTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/fluent/EVMExecutorTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -22,7 +22,6 @@ import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.EvmSpecVersion; -import org.hyperledger.besu.evm.code.CodeFactory; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.FrontierGasCalculator; import org.hyperledger.besu.evm.internal.EvmConfiguration; @@ -36,11 +35,11 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import javax.annotation.Nonnull; import com.google.common.collect.MultimapBuilder; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; @@ -50,7 +49,7 @@ class EVMExecutorTest { @Test void currentEVM() { var subject = EVMExecutor.evm(); - assertThat(subject.getEVMVersion()).isEqualTo(EvmSpecVersion.SHANGHAI); + assertThat(subject.getEVMVersion()).isEqualTo(EvmSpecVersion.CANCUN); } @ParameterizedTest @@ -127,23 +126,18 @@ void defaultChainIdAPIs() { EVMExecutor cancunEVM = EVMExecutor.cancun(EvmConfiguration.DEFAULT); assertThat(cancunEVM.getChainId()).contains(defaultChainId); + EVMExecutor cancunEOFEVM = + EVMExecutor.cancunEOF(defaultChainId.toBigInteger(), EvmConfiguration.DEFAULT); + assertThat(cancunEOFEVM.getChainId()).contains(defaultChainId); + + EVMExecutor pragueEVM = + EVMExecutor.pragueEOF(defaultChainId.toBigInteger(), EvmConfiguration.DEFAULT); + assertThat(pragueEVM.getChainId()).contains(defaultChainId); + EVMExecutor futureEipsVM = EVMExecutor.futureEips(EvmConfiguration.DEFAULT); assertThat(futureEipsVM.getChainId()).contains(defaultChainId); } - @Test - void executeCode() { - var result = - EVMExecutor.evm(EvmSpecVersion.SHANGHAI) - .worldUpdater(createSimpleWorld().updater()) - .execute( - CodeFactory.createCode(Bytes.fromHexString("0x6001600255"), 1, false), - Bytes.EMPTY, - Wei.ZERO, - Address.ZERO); - assertThat(result).isNotNull(); - } - @Test void executeBytes() { var result = @@ -176,7 +170,7 @@ void giantExecuteStack() { .blobGasPrice(Wei.ONE) .callData(Bytes.fromHexString("0x12345678")) .ethValue(Wei.fromEth(1)) - .code(CodeFactory.createCode(Bytes.fromHexString("0x6001600255"), 0, false)) + .code(Bytes.fromHexString("0x6001600255")) .blockValues(new SimpleBlockValues()) .difficulty(Bytes.ofUnsignedLong(1L)) .mixHash(Bytes32.ZERO) @@ -195,7 +189,7 @@ void giantExecuteStack() { .accessListWarmStorage( Address.ZERO, Bytes32.ZERO, Bytes32.leftPad(Bytes.ofUnsignedLong(2L))) .messageCallProcessor(new MessageCallProcessor(null, null)) - .contractCallProcessor(new ContractCreationProcessor(null, null, true, null, 1L)) + .contractCallProcessor(new ContractCreationProcessor(null, true, null, 1L)) .execute(); assertThat(result).isNotNull(); } @@ -215,7 +209,7 @@ void anternateExecStack() { assertThat(result).isNotNull(); } - @NotNull + @Nonnull private static SimpleWorld createSimpleWorld() { SimpleWorld simpleWorld = new SimpleWorld(); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/fluent/SimpleWorldTest.java b/evm/src/test/java/org/hyperledger/besu/evm/fluent/SimpleWorldTest.java new file mode 100644 index 00000000000..c7f997bfb9e --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/fluent/SimpleWorldTest.java @@ -0,0 +1,331 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.fluent; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import org.apache.tuweni.units.bigints.UInt256; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class SimpleWorldTest { + private static final Address ADDRESS1 = Address.fromHexString("0x0"); + private static final Address ADDRESS2 = Address.fromHexString("0x1"); + private static final Address ADDRESS3 = Address.fromHexString("0x2"); + + private SimpleWorld simpleWorld; + + @BeforeEach + void setUp() { + simpleWorld = new SimpleWorld(); + } + + @Test + void get_noAccounts() { + assertThat(simpleWorld.get(ADDRESS1)).isNull(); + } + + @Test + void get_noAccountsWithParent() { + WorldUpdater childUpdater = simpleWorld.updater(); + assertThat(childUpdater.get(ADDRESS1)).isNull(); + } + + @Test + void createAccount_cannotCreateIfExists() { + simpleWorld.createAccount(ADDRESS1); + assertThatThrownBy(() -> simpleWorld.createAccount(ADDRESS1)) + .isInstanceOf(IllegalStateException.class); + } + + @Test + void get_createdAccountExistsInParent() { + MutableAccount account = simpleWorld.createAccount(ADDRESS1); + WorldUpdater childUpdater = simpleWorld.updater(); + assertThat(childUpdater.get(ADDRESS1)).isEqualTo(account); + } + + @Test + void get_createdAccountExistsInMultiParent() { + MutableAccount account = simpleWorld.createAccount(ADDRESS1); + WorldUpdater childUpdater = simpleWorld.updater(); + childUpdater = childUpdater.updater(); + childUpdater = childUpdater.updater(); + childUpdater = childUpdater.updater(); + assertThat(childUpdater.get(ADDRESS1)).isEqualTo(account); + } + + @Test + void get_createdAccountExists() { + MutableAccount account = simpleWorld.createAccount(ADDRESS1); + assertThat(simpleWorld.get(ADDRESS1)).isEqualTo(account); + } + + @Test + void get_createdAccountDeleted() { + simpleWorld.createAccount(ADDRESS1); + simpleWorld.deleteAccount(ADDRESS1); + assertThat(simpleWorld.get(ADDRESS1)).isNull(); + } + + @Test + void get_revertRemovesAllAccounts() { + simpleWorld.createAccount(ADDRESS1); + simpleWorld.createAccount(ADDRESS2); + simpleWorld.createAccount(ADDRESS3); + simpleWorld.revert(); + assertThat(simpleWorld.get(ADDRESS1)).isNull(); + assertThat(simpleWorld.get(ADDRESS2)).isNull(); + assertThat(simpleWorld.get(ADDRESS3)).isNull(); + } + + @Test + void get_commitKeepsAllAccounts() { + MutableAccount acc1 = simpleWorld.createAccount(ADDRESS1); + MutableAccount acc2 = simpleWorld.createAccount(ADDRESS2); + MutableAccount acc3 = simpleWorld.createAccount(ADDRESS3); + simpleWorld.commit(); + assertThat(simpleWorld.get(ADDRESS1)).isEqualTo(acc1); + assertThat(simpleWorld.get(ADDRESS2)).isEqualTo(acc2); + assertThat(simpleWorld.get(ADDRESS3)).isEqualTo(acc3); + } + + @Test + void get_createdAccountDeletedInChild() { + simpleWorld.createAccount(ADDRESS1); + WorldUpdater childUpdater = simpleWorld.updater(); + childUpdater.deleteAccount(ADDRESS1); + assertThat(childUpdater.get(ADDRESS1)).isNull(); + } + + @Test + void getAccount_noAccounts() { + assertThat(simpleWorld.getAccount(ADDRESS1)).isNull(); + } + + @Test + void getAccount_noAccountsWithParent() { + WorldUpdater childUpdater = simpleWorld.updater(); + assertThat(childUpdater.getAccount(ADDRESS1)).isNull(); + } + + @Test + void getAccount_createdAccountExistsInParent() { + MutableAccount account = simpleWorld.createAccount(ADDRESS1); + account.setStorageValue(UInt256.MAX_VALUE, UInt256.ONE); + WorldUpdater childUpdater = simpleWorld.updater(); + assertThat(childUpdater.getAccount(ADDRESS1).getOriginalStorageValue(UInt256.MAX_VALUE)) + .isEqualTo(UInt256.ONE); + } + + @Test + void getAccount_createdAccountExistsInMultiParent() { + MutableAccount account = simpleWorld.createAccount(ADDRESS1); + account.setStorageValue(UInt256.MAX_VALUE, UInt256.ONE); + WorldUpdater childUpdater = simpleWorld.updater(); + childUpdater = childUpdater.updater(); + childUpdater = childUpdater.updater(); + childUpdater = childUpdater.updater(); + assertThat(childUpdater.getAccount(ADDRESS1).getOriginalStorageValue(UInt256.MAX_VALUE)) + .isEqualTo(UInt256.ONE); + } + + @Test + void getAccount_createdAccountExists() { + MutableAccount account = simpleWorld.createAccount(ADDRESS1); + assertThat(simpleWorld.getAccount(ADDRESS1)).isEqualTo(account); + } + + @Test + void getAccount_createdAccountDeleted() { + simpleWorld.createAccount(ADDRESS1); + simpleWorld.deleteAccount(ADDRESS1); + assertThat(simpleWorld.getAccount(ADDRESS1)).isNull(); + } + + @Test + void getAccount_revertRemovesAllAccounts() { + simpleWorld.createAccount(ADDRESS1); + simpleWorld.createAccount(ADDRESS2); + simpleWorld.createAccount(ADDRESS3); + simpleWorld.revert(); + assertThat(simpleWorld.getAccount(ADDRESS1)).isNull(); + assertThat(simpleWorld.getAccount(ADDRESS2)).isNull(); + assertThat(simpleWorld.getAccount(ADDRESS3)).isNull(); + } + + @Test + void getAccount_commitKeepsAllAccounts() { + MutableAccount acc1 = simpleWorld.createAccount(ADDRESS1); + MutableAccount acc2 = simpleWorld.createAccount(ADDRESS2); + MutableAccount acc3 = simpleWorld.createAccount(ADDRESS3); + simpleWorld.commit(); + assertThat(simpleWorld.getAccount(ADDRESS1)).isEqualTo(acc1); + assertThat(simpleWorld.getAccount(ADDRESS2)).isEqualTo(acc2); + assertThat(simpleWorld.getAccount(ADDRESS3)).isEqualTo(acc3); + } + + @Test + void getAccount_createdAccountDeletedInChild() { + simpleWorld.createAccount(ADDRESS1); + WorldUpdater childUpdater = simpleWorld.updater(); + childUpdater.deleteAccount(ADDRESS1); + assertThat(childUpdater.getAccount(ADDRESS1)).isNull(); + } + + @Test + void getTouchedAccounts_createdAccounts() { + Account acc1 = simpleWorld.createAccount(ADDRESS1); + Account acc2 = simpleWorld.createAccount(ADDRESS2); + Account acc3 = simpleWorld.createAccount(ADDRESS3); + assertThat(simpleWorld.getTouchedAccounts().toArray()) + .containsExactlyInAnyOrder(acc1, acc2, acc3); + } + + @Test + void getTouchedAccounts_revertedAccounts() { + simpleWorld.createAccount(ADDRESS1); + simpleWorld.createAccount(ADDRESS2); + simpleWorld.createAccount(ADDRESS3); + simpleWorld.revert(); + assertThat(simpleWorld.getTouchedAccounts()).isEmpty(); + } + + @Test + void getTouchedAccounts_createdAndDeletedAccounts() { + Account acc1 = simpleWorld.createAccount(ADDRESS1); + simpleWorld.createAccount(ADDRESS2); + Account acc3 = simpleWorld.createAccount(ADDRESS3); + simpleWorld.deleteAccount(ADDRESS2); + assertThat(simpleWorld.getTouchedAccounts().toArray()).containsExactlyInAnyOrder(acc1, acc3); + } + + @Test + void getTouchedAccounts_allDeletedAccounts() { + simpleWorld.createAccount(ADDRESS1); + simpleWorld.createAccount(ADDRESS2); + simpleWorld.createAccount(ADDRESS3); + simpleWorld.deleteAccount(ADDRESS1); + simpleWorld.deleteAccount(ADDRESS2); + simpleWorld.deleteAccount(ADDRESS3); + assertThat(simpleWorld.getTouchedAccounts()).isEmpty(); + } + + @Test + void getTouchedAccounts_createdAndCommittedAccounts() { + Account acc1 = simpleWorld.createAccount(ADDRESS1); + Account acc2 = simpleWorld.createAccount(ADDRESS2); + Account acc3 = simpleWorld.createAccount(ADDRESS3); + simpleWorld.commit(); + assertThat(simpleWorld.getTouchedAccounts().toArray()) + .containsExactlyInAnyOrder(acc1, acc2, acc3); + } + + @Test + void getDeletedAccountAddresses_singleDeleted() { + simpleWorld.createAccount(ADDRESS1); + simpleWorld.createAccount(ADDRESS2); + simpleWorld.createAccount(ADDRESS3); + simpleWorld.deleteAccount(ADDRESS2); + assertThat(simpleWorld.getDeletedAccountAddresses().toArray()).containsExactly(ADDRESS2); + } + + @Test + void getDeletedAccountAddresses_allDeleted() { + simpleWorld.createAccount(ADDRESS1); + simpleWorld.createAccount(ADDRESS2); + simpleWorld.createAccount(ADDRESS3); + simpleWorld.deleteAccount(ADDRESS1); + simpleWorld.deleteAccount(ADDRESS2); + simpleWorld.deleteAccount(ADDRESS3); + assertThat(simpleWorld.getDeletedAccountAddresses().toArray()) + .containsExactlyInAnyOrder(ADDRESS1, ADDRESS2, ADDRESS3); + } + + @Test + void getDeletedAccountAddresses_allDeletedThenRevert() { + simpleWorld.createAccount(ADDRESS1); + simpleWorld.createAccount(ADDRESS2); + simpleWorld.createAccount(ADDRESS3); + simpleWorld.deleteAccount(ADDRESS1); + simpleWorld.deleteAccount(ADDRESS2); + simpleWorld.deleteAccount(ADDRESS3); + simpleWorld.revert(); + assertThat(simpleWorld.getDeletedAccountAddresses()).isEmpty(); + } + + @Test + void getDeletedAccountAddresses_allDeletedThenCommit() { + simpleWorld.createAccount(ADDRESS1); + simpleWorld.createAccount(ADDRESS2); + simpleWorld.createAccount(ADDRESS3); + simpleWorld.deleteAccount(ADDRESS1); + simpleWorld.deleteAccount(ADDRESS2); + simpleWorld.deleteAccount(ADDRESS3); + simpleWorld.commit(); + assertThat(simpleWorld.getDeletedAccountAddresses().toArray()) + .containsExactlyInAnyOrder(ADDRESS1, ADDRESS2, ADDRESS3); + } + + @Test + void commit_deletedAccountNoNPEs() { + simpleWorld.createAccount(ADDRESS1); + simpleWorld.createAccount(ADDRESS2); + simpleWorld.createAccount(ADDRESS3); + simpleWorld.deleteAccount(ADDRESS1); + simpleWorld.commit(); + } + + @Test + void commit_onlyCommitsNewAccountsToDirectParent() { + WorldUpdater simpleWorldLevel1 = simpleWorld.updater(); + WorldUpdater simpleWorldLevel2 = simpleWorldLevel1.updater(); + MutableAccount createdAccount = simpleWorldLevel2.createAccount(ADDRESS1); + simpleWorldLevel2.commit(); + assertThat(simpleWorldLevel1.getTouchedAccounts().toArray()).containsExactly(createdAccount); + assertThat(simpleWorld.getTouchedAccounts()).isEmpty(); + } + + @Test + void commit_onlyCommitsDeletedAccountsToDirectParent() { + WorldUpdater simpleWorldLevel1 = simpleWorld.updater(); + WorldUpdater simpleWorldLevel2 = simpleWorldLevel1.updater(); + simpleWorldLevel2.createAccount(ADDRESS2); + simpleWorldLevel2.deleteAccount(ADDRESS2); + simpleWorldLevel2.commit(); + assertThat(simpleWorldLevel1.getDeletedAccountAddresses().toArray()).containsExactly(ADDRESS2); + assertThat(simpleWorld.getDeletedAccountAddresses()).isEmpty(); + } + + @Test + void commit_accountsReflectChangesAfterCommit() { + MutableAccount account = simpleWorld.createAccount(ADDRESS1); + account.setStorageValue(UInt256.MAX_VALUE, UInt256.ONE); + WorldUpdater simpleWorldUpdater = simpleWorld.updater(); + + account = simpleWorldUpdater.getAccount(ADDRESS1); + account.setStorageValue(UInt256.MAX_VALUE, UInt256.valueOf(22L)); + simpleWorldUpdater.commit(); + + assertThat(simpleWorldUpdater.get(ADDRESS1).getStorageValue(UInt256.MAX_VALUE)) + .isEqualTo(simpleWorldUpdater.get(ADDRESS1).getOriginalStorageValue(UInt256.MAX_VALUE)); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/frame/MessageFrameTest.java b/evm/src/test/java/org/hyperledger/besu/evm/frame/MessageFrameTest.java index 9d175ff6f9a..b41883f2a9c 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/frame/MessageFrameTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/frame/MessageFrameTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/test/java/org/hyperledger/besu/evm/gascalculator/CancunGasCalculatorTest.java b/evm/src/test/java/org/hyperledger/besu/evm/gascalculator/CancunGasCalculatorTest.java index f8e53703b25..ded199bf3b1 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/gascalculator/CancunGasCalculatorTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/gascalculator/CancunGasCalculatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -18,6 +18,7 @@ import java.util.List; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -43,4 +44,11 @@ static Iterable blobGasses() { Arguments.of(targetGasPerBlock, 1, CancunGasCalculator.BLOB_GAS_PER_BLOB), Arguments.of(targetGasPerBlock, 3, targetGasPerBlock)); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/gascalculator/PragueEOFGasCalculatorTest.java b/evm/src/test/java/org/hyperledger/besu/evm/gascalculator/PragueEOFGasCalculatorTest.java new file mode 100644 index 00000000000..46fe1dcba8b --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/gascalculator/PragueEOFGasCalculatorTest.java @@ -0,0 +1,41 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.gascalculator; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.datatypes.Address; + +import org.junit.jupiter.api.Test; + +class PragueEOFGasCalculatorTest { + + @Test + void testPrecompileSize() { + PragueEOFGasCalculator subject = new PragueEOFGasCalculator(); + assertThat(subject.isPrecompile(Address.precompiled(0x14))).isFalse(); + assertThat(subject.isPrecompile(Address.BLS12_MAP_FP2_TO_G2)).isTrue(); + } + + @Test + void testNewConstants() { + CancunGasCalculator cancunGas = new CancunGasCalculator(); + PragueEOFGasCalculator praugeGasCalculator = new PragueEOFGasCalculator(); + + assertThat(praugeGasCalculator.getMinCalleeGas()).isGreaterThan(cancunGas.getMinCalleeGas()); + assertThat(praugeGasCalculator.getMinRetainedGas()) + .isGreaterThan(cancunGas.getMinRetainedGas()); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/gascalculator/PragueGasCalculatorTest.java b/evm/src/test/java/org/hyperledger/besu/evm/gascalculator/PragueGasCalculatorTest.java new file mode 100644 index 00000000000..c528dab64ff --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/gascalculator/PragueGasCalculatorTest.java @@ -0,0 +1,30 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.gascalculator; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.datatypes.Address; + +import org.junit.jupiter.api.Test; + +class PragueGasCalculatorTest { + @Test + void testPrecompileSize() { + PragueGasCalculator subject = new PragueGasCalculator(); + assertThat(subject.isPrecompile(Address.precompiled(0x14))).isFalse(); + assertThat(subject.isPrecompile(Address.BLS12_MAP_FP2_TO_G2)).isTrue(); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/internal/CodeCacheTest.java b/evm/src/test/java/org/hyperledger/besu/evm/internal/CodeCacheTest.java index 8c40a1a53a4..7b8a74b0cd9 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/internal/CodeCacheTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/internal/CodeCacheTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,13 +12,13 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.evm.internal; import static org.assertj.core.api.Assertions.assertThat; import org.hyperledger.besu.evm.Code; -import org.hyperledger.besu.evm.code.CodeFactory; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.MainnetEVMs; import org.hyperledger.besu.evm.operation.JumpDestOperation; import org.apache.tuweni.bytes.Bytes; @@ -30,10 +30,11 @@ class CodeCacheTest { @Test void testScale() { + EVM evm = MainnetEVMs.pragueEOF(EvmConfiguration.DEFAULT); final Bytes contractBytes = Bytes.fromHexString("0xDEAD" + op + "BEEF" + op + "B0B0" + op + "C0DE" + op + "FACE"); final CodeScale scale = new CodeScale(); - final Code contractCode = CodeFactory.createCode(contractBytes, 0, false); + final Code contractCode = evm.getCodeUncached(contractBytes); final int weight = scale.weigh(contractCode.getCodeHash(), contractCode); assertThat(weight) .isEqualTo(contractCode.getCodeHash().size() + (contractBytes.size() * 9 + 7) / 8); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/internal/WordsTest.java b/evm/src/test/java/org/hyperledger/besu/evm/internal/WordsTest.java index 40eb0a0b898..871f6ed0b1a 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/internal/WordsTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/internal/WordsTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,17 +11,18 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.evm.internal; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.internal.Words.clampedToInt; import static org.hyperledger.besu.evm.internal.Words.unsignedMin; import java.util.Arrays; import java.util.Collection; +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.params.ParameterizedTest; @@ -46,7 +47,68 @@ Collection unsignedMinLongTestVector() { @ParameterizedTest @MethodSource("unsignedMinLongTestVector") - void unsugnedMinLongTest(final long a, final long b, final long min) { + void unsignedMinLongTest(final long a, final long b, final long min) { assertThat(unsignedMin(a, b)).isEqualTo(min); } + + Collection clampedToLongTestVector() { + return Arrays.asList( + new Object[][] { + {Bytes.fromHexStringLenient("0x0"), 0x0}, + {Bytes.fromHexStringLenient("0x1"), 0x1}, + {Bytes.fromHexString("0x10"), 0x10}, + {Bytes.fromHexStringLenient("0x100"), 0x100}, + {Bytes.fromHexString("0x1000"), 0x1000}, + {Bytes.fromHexStringLenient("0x10000"), 0x10000}, + {Bytes.fromHexString("0x100000"), 0x100000}, + {Bytes.fromHexStringLenient("0x1000000"), 0x1000000}, + {Bytes.fromHexString("0x10000000"), 0x10000000}, + {Bytes.fromHexString("0x20000000"), 0x20000000}, + {Bytes.fromHexString("0x40000000"), 0x40000000}, + {Bytes.fromHexString("0x0040000000"), 0x40000000}, + {Bytes.fromHexString("0x7fffffff"), Integer.MAX_VALUE}, + {Bytes.fromHexString("0x80000000"), Integer.MAX_VALUE}, + {Bytes.fromHexString("0x80000001"), Integer.MAX_VALUE}, + {Bytes.fromHexString("0x1000000000000000"), Integer.MAX_VALUE}, + {Bytes.fromHexString("0x10000000000000000000000000000000"), Integer.MAX_VALUE}, + { + Bytes.fromHexString( + "0x1000000000000000000000000000000000000000000000000000000000000000"), + Integer.MAX_VALUE + }, + { + Bytes.fromHexString( + "0x0000000000000000000000000000000000000000000000000000000040000000"), + 0x40000000 + }, + { + Bytes.fromHexString( + "0x000000000000000000000000000000000000000000000000000000007fffffff"), + Integer.MAX_VALUE + }, + { + Bytes.fromHexString( + "0x0000000000000000000000000000000000000000000000000000000080000000"), + Integer.MAX_VALUE + }, + { + Bytes.fromHexString( + "0x0000000000000000000000000000000000000000000000000000000080000001"), + Integer.MAX_VALUE + }, + }); + } + + @ParameterizedTest + @MethodSource("clampedToLongTestVector") + void clampedToIntTest(final Bytes theBytes, final int theExpectedInt) { + assertThat(clampedToInt(theBytes)).isEqualTo(theExpectedInt); + } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/AbstractCreateOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/AbstractCreateOperationTest.java index 1a703f489d7..4c5da84f318 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operation/AbstractCreateOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/AbstractCreateOperationTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,11 +11,13 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.operation; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.internal.Words.clampedAdd; +import static org.hyperledger.besu.evm.internal.Words.clampedToInt; +import static org.hyperledger.besu.evm.internal.Words.clampedToLong; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -23,11 +25,11 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.MainnetEVMs; import org.hyperledger.besu.evm.account.Account; import org.hyperledger.besu.evm.account.MutableAccount; -import org.hyperledger.besu.evm.code.CodeFactory; import org.hyperledger.besu.evm.code.CodeInvalid; import org.hyperledger.besu.evm.frame.BlockValues; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; @@ -43,6 +45,7 @@ import java.util.Deque; import java.util.List; import java.util.Optional; +import java.util.function.Supplier; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; @@ -54,7 +57,7 @@ class AbstractCreateOperationTest { private final MutableAccount account = mock(MutableAccount.class); private final MutableAccount newAccount = mock(MutableAccount.class); private final FakeCreateOperation operation = - new FakeCreateOperation(new ConstantinopleGasCalculator(), Integer.MAX_VALUE); + new FakeCreateOperation(new ConstantinopleGasCalculator()); private static final Bytes SIMPLE_CREATE = Bytes.fromHexString( @@ -74,7 +77,7 @@ class AbstractCreateOperationTest { public static final Bytes INVALID_EOF = Bytes.fromHexString( "0x" - + "73EF99010100040200010001030000000000000000" // PUSH20 contract + + "73EF00990100040200010001030000000000000000" // PUSH20 contract + "6000" // PUSH1 0x00 + "52" // MSTORE + "6014" // PUSH1 20 @@ -97,19 +100,24 @@ public static class FakeCreateOperation extends AbstractCreateOperation { * Instantiates a new Create operation. * * @param gasCalculator the gas calculator - * @param maxInitcodeSize Maximum init code size */ - public FakeCreateOperation(final GasCalculator gasCalculator, final int maxInitcodeSize) { - super(0xEF, "FAKECREATE", 3, 1, gasCalculator, maxInitcodeSize); + public FakeCreateOperation(final GasCalculator gasCalculator) { + super(0xEF, "FAKECREATE", 3, 1, gasCalculator, 0); } @Override - public long cost(final MessageFrame frame) { - return gasCalculator().createOperationGasCost(frame); + public long cost(final MessageFrame frame, final Supplier unused) { + final int inputOffset = clampedToInt(frame.getStackItem(1)); + final int inputSize = clampedToInt(frame.getStackItem(2)); + return clampedAdd( + clampedAdd( + gasCalculator().txCreateCost(), + gasCalculator().memoryExpansionGasCost(frame, inputOffset, inputSize)), + gasCalculator().initcodeCost(inputSize)); } @Override - protected Address targetContractAddress(final MessageFrame frame) { + protected Address generateTargetContractAddress(final MessageFrame frame, final Code initcode) { final Account sender = frame.getWorldUpdater().get(frame.getRecipientAddress()); // Decrement nonce by 1 to normalize the effect of transaction execution final Address address = @@ -118,6 +126,14 @@ protected Address targetContractAddress(final MessageFrame frame) { return address; } + @Override + protected Code getInitCode(final MessageFrame frame, final EVM evm) { + final long inputOffset = clampedToLong(frame.getStackItem(1)); + final long inputSize = clampedToLong(frame.getStackItem(2)); + final Bytes inputData = frame.readMemory(inputOffset, inputSize); + return evm.getCodeUncached(inputData); + } + @Override protected void onSuccess(final MessageFrame frame, final Address createdAddress) { successFrame = frame; @@ -148,7 +164,7 @@ private void executeOperation(final Bytes contract, final EVM evm) { .sender(Address.fromHexString(SENDER)) .value(Wei.ZERO) .apparentValue(Wei.ZERO) - .code(CodeFactory.createCode(SIMPLE_CREATE, 0, true)) + .code(evm.getCodeUncached(SIMPLE_CREATE)) .completer(__ -> {}) .address(Address.fromHexString(SENDER)) .blockHashLookup(n -> Hash.hash(Words.longBytes(n))) @@ -173,12 +189,13 @@ private void executeOperation(final Bytes contract, final EVM evm) { when(worldUpdater.getSenderAccount(any())).thenReturn(account); when(worldUpdater.getOrCreate(any())).thenReturn(newAccount); when(newAccount.getCode()).thenReturn(Bytes.EMPTY); + when(newAccount.isStorageEmpty()).thenReturn(true); when(worldUpdater.updater()).thenReturn(worldUpdater); operation.execute(messageFrame, evm); final MessageFrame createFrame = messageFrameStack.peek(); final ContractCreationProcessor ccp = - new ContractCreationProcessor(evm.getGasCalculator(), evm, false, List.of(), 0, List.of()); + new ContractCreationProcessor(evm, false, List.of(), 0, List.of()); ccp.process(createFrame, OperationTracer.NO_TRACING); } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/BaseFeeOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/BaseFeeOperationTest.java similarity index 95% rename from evm/src/test/java/org/hyperledger/besu/evm/operations/BaseFeeOperationTest.java rename to evm/src/test/java/org/hyperledger/besu/evm/operation/BaseFeeOperationTest.java index ac816137af3..13f04849887 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/BaseFeeOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/BaseFeeOperationTest.java @@ -12,7 +12,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.evm.operations; +package org.hyperledger.besu.evm.operation; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -26,8 +26,6 @@ import org.hyperledger.besu.evm.gascalculator.BerlinGasCalculator; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.internal.Words; -import org.hyperledger.besu.evm.operation.BaseFeeOperation; -import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.operation.Operation.OperationResult; import java.util.Optional; diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/BlobHashOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/BlobHashOperationTest.java similarity index 95% rename from evm/src/test/java/org/hyperledger/besu/evm/operations/BlobHashOperationTest.java rename to evm/src/test/java/org/hyperledger/besu/evm/operation/BlobHashOperationTest.java index 0f789a94477..9692c27625d 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/BlobHashOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/BlobHashOperationTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,8 +12,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ - -package org.hyperledger.besu.evm.operations; +package org.hyperledger.besu.evm.operation; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -26,8 +25,6 @@ import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator; import org.hyperledger.besu.evm.gascalculator.LondonGasCalculator; -import org.hyperledger.besu.evm.operation.BlobHashOperation; -import org.hyperledger.besu.evm.operation.Operation; import java.util.ArrayList; import java.util.Arrays; diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/BlockHashOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/BlockHashOperationTest.java similarity index 93% rename from evm/src/test/java/org/hyperledger/besu/evm/operations/BlockHashOperationTest.java rename to evm/src/test/java/org/hyperledger/besu/evm/operation/BlockHashOperationTest.java index 9614f63316e..3ce7e3c207a 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/BlockHashOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/BlockHashOperationTest.java @@ -12,19 +12,17 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.evm.operations; +package org.hyperledger.besu.evm.operation; import static org.assertj.core.api.Assertions.assertThat; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.FrontierGasCalculator; -import org.hyperledger.besu.evm.operation.BlockHashOperation; +import org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; import org.hyperledger.besu.evm.testutils.FakeBlockValues; import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; -import java.util.function.Function; - import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; @@ -81,7 +79,7 @@ private void assertBlockHash( final long requestedBlock, final Bytes32 expectedOutput, final long currentBlockNumber, - final Function blockHashLookup) { + final BlockHashLookup blockHashLookup) { assertBlockHash( UInt256.valueOf(requestedBlock), expectedOutput, currentBlockNumber, blockHashLookup); } @@ -90,7 +88,7 @@ private void assertBlockHash( final Bytes32 input, final Bytes32 expectedOutput, final long currentBlockNumber, - final Function blockHashLookup) { + final BlockHashLookup blockHashLookup) { final MessageFrame frame = new TestMessageFrameBuilder() .blockHashLookup(blockHashLookup) diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/CallFOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/CallFOperationTest.java new file mode 100644 index 00000000000..9d7fa85d416 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/CallFOperationTest.java @@ -0,0 +1,61 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.testutils.OperationsTestUtils.mockCode; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.code.CodeSection; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.internal.ReturnStack; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; + +class CallFOperationTest { + + @Test + void callFHappyPath() { + final GasCalculator gasCalculator = mock(GasCalculator.class); + final Code mockCode = mockCode("00" + "b0" + "0001" + "00"); + + final CodeSection codeSection = new CodeSection(0, 1, 2, 3, 0); + when(mockCode.getCodeSection(1)).thenReturn(codeSection); + + MessageFrame messageFrame = + new TestMessageFrameBuilder() + .code(mockCode) + .pc(1) + .initialGas(10L) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .build(); + + CallFOperation callF = new CallFOperation(gasCalculator); + Operation.OperationResult callfResult = callF.execute(messageFrame, null); + + assertThat(callfResult.getHaltReason()).isNull(); + assertThat(callfResult.getPcIncrement()).isEqualTo(1); + assertThat(messageFrame.getSection()).isEqualTo(1); + assertThat(messageFrame.getPC()).isEqualTo(-1); + assertThat(messageFrame.returnStackSize()).isEqualTo(1); + assertThat(messageFrame.peekReturnStack()).isEqualTo(new ReturnStack.ReturnStackItem(0, 3)); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/ChainIdOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/ChainIdOperationTest.java similarity index 89% rename from evm/src/test/java/org/hyperledger/besu/evm/operations/ChainIdOperationTest.java rename to evm/src/test/java/org/hyperledger/besu/evm/operation/ChainIdOperationTest.java index 7202be03ce3..acae6249c4d 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/ChainIdOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/ChainIdOperationTest.java @@ -12,7 +12,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.evm.operations; +package org.hyperledger.besu.evm.operation; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -20,14 +20,13 @@ import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator; -import org.hyperledger.besu.evm.operation.ChainIdOperation; import org.hyperledger.besu.evm.operation.Operation.OperationResult; import java.util.List; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt256; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -51,7 +50,7 @@ static Iterable params() { void shouldReturnChainId(final String chainIdString, final int expectedGas) { Bytes32 chainId = Bytes32.fromHexString(chainIdString); ChainIdOperation operation = new ChainIdOperation(new ConstantinopleGasCalculator(), chainId); - final ArgumentCaptor arg = ArgumentCaptor.forClass(UInt256.class); + final ArgumentCaptor arg = ArgumentCaptor.forClass(Bytes.class); when(messageFrame.getRemainingGas()).thenReturn(100L); operation.execute(messageFrame, null); Mockito.verify(messageFrame).getRemainingGas(); @@ -68,4 +67,11 @@ void shouldCalculateGasPrice(final String chainIdString, final int expectedGas) final OperationResult result = operation.execute(messageFrame, null); assertThat(result.getGasCost()).isEqualTo(expectedGas); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/ConstantinopleSStoreOperationGasCostTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/ConstantinopleSStoreOperationGasCostTest.java similarity index 92% rename from evm/src/test/java/org/hyperledger/besu/evm/operations/ConstantinopleSStoreOperationGasCostTest.java rename to evm/src/test/java/org/hyperledger/besu/evm/operation/ConstantinopleSStoreOperationGasCostTest.java index 00edb9c5d17..b8a254bbe40 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/ConstantinopleSStoreOperationGasCostTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/ConstantinopleSStoreOperationGasCostTest.java @@ -12,7 +12,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.evm.operations; +package org.hyperledger.besu.evm.operation; import static org.assertj.core.api.Assertions.assertThat; @@ -23,6 +23,7 @@ import org.hyperledger.besu.evm.testutils.TestCodeExecutor; import org.apache.tuweni.units.bigints.UInt256; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -73,4 +74,11 @@ void shouldCalculateGasAccordingToEip1283( assertThat(frame.getRemainingGas()).isEqualTo(gasLimit - expectedGasUsed); assertThat(frame.getGasRefund()).isEqualTo(expectedGasRefund); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/Create2OperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/Create2OperationTest.java similarity index 79% rename from evm/src/test/java/org/hyperledger/besu/evm/operations/Create2OperationTest.java rename to evm/src/test/java/org/hyperledger/besu/evm/operation/Create2OperationTest.java index d9b41d4681e..4b2a5449ed0 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/Create2OperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/Create2OperationTest.java @@ -12,11 +12,12 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.evm.operations; +package org.hyperledger.besu.evm.operation; import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.evm.MainnetEVMs.DEV_NET_CHAIN_ID; import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.CODE_TOO_LARGE; +import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.INVALID_OPERATION; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -24,17 +25,16 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.Code; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.MainnetEVMs; import org.hyperledger.besu.evm.account.MutableAccount; -import org.hyperledger.besu.evm.code.CodeFactory; import org.hyperledger.besu.evm.frame.BlockValues; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.internal.Words; import org.hyperledger.besu.evm.log.Log; -import org.hyperledger.besu.evm.operation.Create2Operation; import org.hyperledger.besu.evm.operation.Operation.OperationResult; import org.hyperledger.besu.evm.processor.ContractCreationProcessor; import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; @@ -43,10 +43,10 @@ import java.util.Deque; import java.util.List; +import javax.annotation.Nonnull; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -56,15 +56,11 @@ public class Create2OperationTest { private MessageFrame messageFrame; private final WorldUpdater worldUpdater = mock(WorldUpdater.class); private final MutableAccount account = mock(MutableAccount.class); - private final EVM evm = mock(EVM.class); + private final EVM evm = MainnetEVMs.pragueEOF(EvmConfiguration.DEFAULT); private final MutableAccount newAccount = mock(MutableAccount.class); private final Create2Operation operation = - new Create2Operation(new ConstantinopleGasCalculator(), Integer.MAX_VALUE); - - private final Create2Operation maxInitCodeOperation = - new Create2Operation( - new ConstantinopleGasCalculator(), MainnetEVMs.SHANGHAI_INIT_CODE_SIZE_LIMIT); + new Create2Operation(new ConstantinopleGasCalculator()); private static final String TOPIC = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; // 32 FFs @@ -81,7 +77,7 @@ public class Create2OperationTest { + "F3" // RETURN ); public static final Bytes SIMPLE_EOF = - Bytes.fromHexString("0xEF00010100040200010001030000000000000000"); + Bytes.fromHexString("0xEF00010100040200010001040000000080000000"); public static final String SENDER = "0xdeadc0de00000000000000000000000000000000"; private static final int SHANGHAI_CREATE_GAS = 41240 + (0xc000 / 32) * 6; @@ -152,7 +148,7 @@ public void setUp(final String sender, final String salt, final String code) { .sender(Address.fromHexString(sender)) .value(Wei.ZERO) .apparentValue(Wei.ZERO) - .code(CodeFactory.createCode(codeBytes, 0, true)) + .code(evm.getCodeUncached(codeBytes)) .completer(__ -> {}) .address(Address.fromHexString(sender)) .blockHashLookup(n -> Hash.hash(Words.longBytes(n))) @@ -173,8 +169,6 @@ public void setUp(final String sender, final String salt, final String code) { when(account.getBalance()).thenReturn(Wei.ZERO); when(worldUpdater.getAccount(any())).thenReturn(account); when(worldUpdater.updater()).thenReturn(worldUpdater); - when(evm.getCode(any(), any())) - .thenAnswer(invocation -> CodeFactory.createCode(invocation.getArgument(1), 0, true)); } @ParameterizedTest @@ -186,7 +180,9 @@ void shouldCalculateAddress( final String expectedAddress, final int ignoredExpectedGas) { setUp(sender, salt, code); - final Address targetContractAddress = operation.targetContractAddress(messageFrame); + final Address targetContractAddress = + operation.generateTargetContractAddress( + messageFrame, evm.getCodeUncached(Bytes.fromHexString(code))); assertThat(targetContractAddress).isEqualTo(Address.fromHexString(expectedAddress)); } @@ -208,7 +204,7 @@ void shouldCalculateGasPrice( void shanghaiMaxInitCodeSizeCreate() { final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); final UInt256 memoryLength = UInt256.fromHexString("0xc000"); - final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1); + final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength); when(account.getNonce()).thenReturn(55L); when(account.getBalance()).thenReturn(Wei.ZERO); @@ -217,13 +213,14 @@ void shanghaiMaxInitCodeSizeCreate() { when(worldUpdater.getSenderAccount(any())).thenReturn(account); when(worldUpdater.getOrCreate(any())).thenReturn(newAccount); when(newAccount.getCode()).thenReturn(Bytes.EMPTY); + when(newAccount.isStorageEmpty()).thenReturn(true); when(worldUpdater.updater()).thenReturn(worldUpdater); - final EVM evm = MainnetEVMs.shanghai(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); - var result = maxInitCodeOperation.execute(messageFrame, evm); + final EVM myEVM = MainnetEVMs.shanghai(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); + var result = operation.execute(messageFrame, myEVM); final MessageFrame createFrame = messageFrame.getMessageFrameStack().peek(); final ContractCreationProcessor ccp = - new ContractCreationProcessor(evm.getGasCalculator(), evm, false, List.of(), 0, List.of()); + new ContractCreationProcessor(myEVM, false, List.of(), 0, List.of()); ccp.process(createFrame, OperationTracer.NO_TRACING); final Log log = createFrame.getLogs().get(0); @@ -236,7 +233,7 @@ void shanghaiMaxInitCodeSizeCreate() { void shanghaiMaxInitCodeSizePlus1Create() { final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); final UInt256 memoryLength = UInt256.fromHexString("0xc001"); - final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1); + final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength); when(account.getNonce()).thenReturn(55L); when(account.getBalance()).thenReturn(Wei.ZERO); @@ -245,19 +242,16 @@ void shanghaiMaxInitCodeSizePlus1Create() { when(worldUpdater.getSenderAccount(any())).thenReturn(account); when(worldUpdater.getOrCreate(any())).thenReturn(newAccount); when(newAccount.getCode()).thenReturn(Bytes.EMPTY); + when(newAccount.isStorageEmpty()).thenReturn(true); when(worldUpdater.updater()).thenReturn(worldUpdater); final EVM evm = MainnetEVMs.shanghai(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); - var result = maxInitCodeOperation.execute(messageFrame, evm); + var result = operation.execute(messageFrame, evm); assertThat(result.getHaltReason()).isEqualTo(CODE_TOO_LARGE); } - @NotNull - private MessageFrame testMemoryFrame( - final UInt256 memoryOffset, - final UInt256 memoryLength, - final UInt256 value, - final int depth) { + @Nonnull + private MessageFrame testMemoryFrame(final UInt256 memoryOffset, final UInt256 memoryLength) { final MessageFrame messageFrame = MessageFrame.builder() .type(MessageFrame.Type.CONTRACT_CREATION) @@ -266,7 +260,7 @@ private MessageFrame testMemoryFrame( .sender(Address.fromHexString(SENDER)) .value(Wei.ZERO) .apparentValue(Wei.ZERO) - .code(CodeFactory.createCode(SIMPLE_CREATE, 0, true)) + .code(evm.getCodeUncached(SIMPLE_CREATE)) .completer(__ -> {}) .address(Address.fromHexString(SENDER)) .blockHashLookup(n -> Hash.hash(Words.longBytes(n))) @@ -280,66 +274,41 @@ private MessageFrame testMemoryFrame( messageFrame.pushStackItem(Bytes.EMPTY); messageFrame.pushStackItem(memoryLength); messageFrame.pushStackItem(memoryOffset); - messageFrame.pushStackItem(value); + messageFrame.pushStackItem(UInt256.ZERO); messageFrame.expandMemory(0, 500); messageFrame.writeMemory( memoryOffset.trimLeadingZeros().toInt(), SIMPLE_CREATE.size(), SIMPLE_CREATE); final Deque messageFrameStack = messageFrame.getMessageFrameStack(); - while (messageFrameStack.size() < depth) { + if (messageFrameStack.isEmpty()) { messageFrameStack.push(messageFrame); } return messageFrame; } @Test - void eofV1CannotCreateLegacy() { + void eofV1CannotCall() { final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); final UInt256 memoryLength = UInt256.valueOf(SIMPLE_CREATE.size()); - final MessageFrame messageFrame = - new TestMessageFrameBuilder() - .code(CodeFactory.createCode(SIMPLE_EOF, 1, true)) - .pushStackItem(Bytes.EMPTY) - .pushStackItem(memoryLength) - .pushStackItem(memoryOffset) - .pushStackItem(Bytes.EMPTY) - .worldUpdater(worldUpdater) - .build(); - messageFrame.writeMemory(memoryOffset.toLong(), memoryLength.toLong(), SIMPLE_CREATE); - when(account.getBalance()).thenReturn(Wei.ZERO); - when(worldUpdater.getAccount(any())).thenReturn(account); + Code eofCode = evm.getCodeUncached(SIMPLE_EOF); + assertThat(eofCode.isValid()).isTrue(); - final EVM evm = MainnetEVMs.cancun(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); - var result = operation.execute(messageFrame, evm); - assertThat(result.getHaltReason()).isNull(); - assertThat(messageFrame.getStackItem(0).trimLeadingZeros()).isEqualTo(Bytes.EMPTY); - } - - @Test - void legacyCanCreateEOFv1() { - final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); - final UInt256 memoryLength = UInt256.valueOf(SIMPLE_EOF.size()); final MessageFrame messageFrame = new TestMessageFrameBuilder() - .code(CodeFactory.createCode(SIMPLE_CREATE, 1, true)) + .code(eofCode) .pushStackItem(Bytes.EMPTY) .pushStackItem(memoryLength) .pushStackItem(memoryOffset) .pushStackItem(Bytes.EMPTY) .worldUpdater(worldUpdater) .build(); - messageFrame.writeMemory(memoryOffset.toLong(), memoryLength.toLong(), SIMPLE_EOF); + messageFrame.writeMemory(memoryOffset.toLong(), memoryLength.toLong(), SIMPLE_CREATE); - when(account.getNonce()).thenReturn(55L); when(account.getBalance()).thenReturn(Wei.ZERO); when(worldUpdater.getAccount(any())).thenReturn(account); - when(worldUpdater.get(any())).thenReturn(account); - when(worldUpdater.getSenderAccount(any())).thenReturn(account); - when(worldUpdater.updater()).thenReturn(worldUpdater); - final EVM evm = MainnetEVMs.cancun(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); var result = operation.execute(messageFrame, evm); - assertThat(result.getHaltReason()).isNull(); - assertThat(messageFrame.getStackItem(0)).isNotEqualTo(UInt256.ZERO); + assertThat(result.getHaltReason()).isEqualTo(INVALID_OPERATION); + assertThat(messageFrame.getStackItem(0).trimLeadingZeros()).isEqualTo(Bytes.EMPTY); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/CreateOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/CreateOperationTest.java similarity index 82% rename from evm/src/test/java/org/hyperledger/besu/evm/operations/CreateOperationTest.java rename to evm/src/test/java/org/hyperledger/besu/evm/operation/CreateOperationTest.java index 822f515741e..482df37fd65 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/CreateOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/CreateOperationTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,13 +11,13 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ -package org.hyperledger.besu.evm.operations; +package org.hyperledger.besu.evm.operation; import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.evm.MainnetEVMs.DEV_NET_CHAIN_ID; import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.CODE_TOO_LARGE; +import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.INVALID_OPERATION; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -28,14 +28,12 @@ import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.MainnetEVMs; import org.hyperledger.besu.evm.account.MutableAccount; -import org.hyperledger.besu.evm.code.CodeFactory; import org.hyperledger.besu.evm.frame.BlockValues; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.internal.Words; import org.hyperledger.besu.evm.log.Log; -import org.hyperledger.besu.evm.operation.CreateOperation; import org.hyperledger.besu.evm.processor.ContractCreationProcessor; import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; import org.hyperledger.besu.evm.tracing.OperationTracer; @@ -43,10 +41,10 @@ import java.util.Deque; import java.util.List; +import javax.annotation.Nonnull; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; class CreateOperationTest { @@ -54,11 +52,7 @@ class CreateOperationTest { private final WorldUpdater worldUpdater = mock(WorldUpdater.class); private final MutableAccount account = mock(MutableAccount.class); private final MutableAccount newAccount = mock(MutableAccount.class); - private final CreateOperation operation = - new CreateOperation(new ConstantinopleGasCalculator(), Integer.MAX_VALUE); - private final CreateOperation maxInitCodeOperation = - new CreateOperation( - new ConstantinopleGasCalculator(), MainnetEVMs.SHANGHAI_INIT_CODE_SIZE_LIMIT); + private final CreateOperation operation = new CreateOperation(new ConstantinopleGasCalculator()); private static final String TOPIC = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"; // 32 FFs @@ -75,7 +69,7 @@ class CreateOperationTest { + "F3" // RETURN ); public static final Bytes SIMPLE_EOF = - Bytes.fromHexString("0xEF00010100040200010001030000000000000000"); + Bytes.fromHexString("0xEF00010100040200010001040000000080000000"); public static final String SENDER = "0xdeadc0de00000000000000000000000000000000"; private static final int SHANGHAI_CREATE_GAS = 41240; @@ -83,7 +77,7 @@ class CreateOperationTest { @Test void createFromMemoryMutationSafe() { - // Given: Execute a CREATE operation with a contract that logs in the constructor + // Given: Execute a CREATE operation with a contract that logs in the constructor final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); final UInt256 memoryLength = UInt256.valueOf(SIMPLE_CREATE.size()); final MessageFrame messageFrame = testMemoryFrame(memoryOffset, memoryLength, UInt256.ZERO, 1); @@ -95,13 +89,14 @@ void createFromMemoryMutationSafe() { when(worldUpdater.getSenderAccount(any())).thenReturn(account); when(worldUpdater.getOrCreate(any())).thenReturn(newAccount); when(newAccount.getCode()).thenReturn(Bytes.EMPTY); + when(newAccount.isStorageEmpty()).thenReturn(true); when(worldUpdater.updater()).thenReturn(worldUpdater); final EVM evm = MainnetEVMs.london(EvmConfiguration.DEFAULT); operation.execute(messageFrame, evm); final MessageFrame createFrame = messageFrame.getMessageFrameStack().peek(); final ContractCreationProcessor ccp = - new ContractCreationProcessor(evm.getGasCalculator(), evm, false, List.of(), 0, List.of()); + new ContractCreationProcessor(evm, false, List.of(), 0, List.of()); ccp.process(createFrame, OperationTracer.NO_TRACING); final Log log = createFrame.getLogs().get(0); @@ -186,13 +181,14 @@ void shanghaiMaxInitCodeSizeCreate() { when(worldUpdater.getSenderAccount(any())).thenReturn(account); when(worldUpdater.getOrCreate(any())).thenReturn(newAccount); when(newAccount.getCode()).thenReturn(Bytes.EMPTY); + when(newAccount.isStorageEmpty()).thenReturn(true); when(worldUpdater.updater()).thenReturn(worldUpdater); final EVM evm = MainnetEVMs.shanghai(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); - var result = maxInitCodeOperation.execute(messageFrame, evm); + var result = operation.execute(messageFrame, evm); final MessageFrame createFrame = messageFrame.getMessageFrameStack().peek(); final ContractCreationProcessor ccp = - new ContractCreationProcessor(evm.getGasCalculator(), evm, false, List.of(), 0, List.of()); + new ContractCreationProcessor(evm, false, List.of(), 0, List.of()); ccp.process(createFrame, OperationTracer.NO_TRACING); final Log log = createFrame.getLogs().get(0); @@ -217,17 +213,18 @@ void shanghaiMaxInitCodeSizePlus1Create() { when(worldUpdater.updater()).thenReturn(worldUpdater); final EVM evm = MainnetEVMs.shanghai(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); - var result = maxInitCodeOperation.execute(messageFrame, evm); + var result = operation.execute(messageFrame, evm); assertThat(result.getHaltReason()).isEqualTo(CODE_TOO_LARGE); } @Test - void eofV1CannotCreateLegacy() { + void eofV1CannotCall() { + final EVM pragueEvm = MainnetEVMs.pragueEOF(EvmConfiguration.DEFAULT); final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); final UInt256 memoryLength = UInt256.valueOf(SIMPLE_CREATE.size()); final MessageFrame messageFrame = new TestMessageFrameBuilder() - .code(CodeFactory.createCode(SIMPLE_EOF, 1, true)) + .code(pragueEvm.getCodeUncached(SIMPLE_EOF)) .pushStackItem(memoryLength) .pushStackItem(memoryOffset) .pushStackItem(Bytes.EMPTY) @@ -235,48 +232,23 @@ void eofV1CannotCreateLegacy() { .build(); messageFrame.writeMemory(memoryOffset.toLong(), memoryLength.toLong(), SIMPLE_CREATE); - when(account.getBalance()).thenReturn(Wei.ZERO); - when(worldUpdater.getAccount(any())).thenReturn(account); - - final EVM evm = MainnetEVMs.cancun(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); - var result = operation.execute(messageFrame, evm); - assertThat(result.getHaltReason()).isNull(); - assertThat(messageFrame.getStackItem(0).trimLeadingZeros()).isEqualTo(Bytes.EMPTY); - } - - @Test - void legacyCanCreateEOFv1() { - final UInt256 memoryOffset = UInt256.fromHexString("0xFF"); - final UInt256 memoryLength = UInt256.valueOf(SIMPLE_EOF.size()); - final MessageFrame messageFrame = - new TestMessageFrameBuilder() - .code(CodeFactory.createCode(SIMPLE_CREATE, 1, true)) - .pushStackItem(memoryLength) - .pushStackItem(memoryOffset) - .pushStackItem(Bytes.EMPTY) - .worldUpdater(worldUpdater) - .build(); - messageFrame.writeMemory(memoryOffset.toLong(), memoryLength.toLong(), SIMPLE_EOF); - - when(account.getNonce()).thenReturn(55L); when(account.getBalance()).thenReturn(Wei.ZERO); when(worldUpdater.getAccount(any())).thenReturn(account); when(worldUpdater.get(any())).thenReturn(account); - when(worldUpdater.getSenderAccount(any())).thenReturn(account); - when(worldUpdater.updater()).thenReturn(worldUpdater); final EVM evm = MainnetEVMs.cancun(DEV_NET_CHAIN_ID, EvmConfiguration.DEFAULT); var result = operation.execute(messageFrame, evm); - assertThat(result.getHaltReason()).isNull(); - assertThat(messageFrame.getState()).isEqualTo(MessageFrame.State.CODE_SUSPENDED); + assertThat(result.getHaltReason()).isEqualTo(INVALID_OPERATION); + assertThat(messageFrame.getStackItem(0).trimLeadingZeros()).isEqualTo(Bytes.EMPTY); } - @NotNull + @Nonnull private MessageFrame testMemoryFrame( final UInt256 memoryOffset, final UInt256 memoryLength, final UInt256 value, final int depth) { + final EVM evm = MainnetEVMs.pragueEOF(EvmConfiguration.DEFAULT); final MessageFrame messageFrame = MessageFrame.builder() .type(MessageFrame.Type.CONTRACT_CREATION) @@ -285,7 +257,7 @@ private MessageFrame testMemoryFrame( .sender(Address.fromHexString(SENDER)) .value(Wei.ZERO) .apparentValue(Wei.ZERO) - .code(CodeFactory.createCode(SIMPLE_CREATE, 0, true)) + .code(evm.getCodeUncached(SIMPLE_CREATE)) .completer(__ -> {}) .address(Address.fromHexString(SENDER)) .blockHashLookup(n -> Hash.hash(Words.longBytes(n))) diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/DataCopyOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/DataCopyOperationTest.java new file mode 100644 index 00000000000..af5922aa074 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/DataCopyOperationTest.java @@ -0,0 +1,177 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assumptions.assumeThat; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.MainnetEVMs; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.PragueGasCalculator; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; + +import java.util.Arrays; +import java.util.Collection; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +class DataCopyOperationTest { + + static EVM evm = MainnetEVMs.pragueEOF(EvmConfiguration.DEFAULT); + + static Collection datacopyTestVector() { + return Arrays.asList( + new Object[][] { + { + "Copy after, no overlap", + Bytes.fromHexString("0123456789abcdef000000000000000000000000000000000000000000000000"), + 32, + 0, + 8, + Bytes.fromHexString( + "00000000000000000000000000000000000000000000000000000000000000000123456789abcdef"), + 12L + }, + { + "copy past data limit", + Bytes.EMPTY, + 0, + 24, + 16, + Bytes.fromHexString( + "0x0000000000000000000000000000000000000000000000000000000000000000"), + 9L + }, + { + "copy from initialized + uninitialized memory", + Bytes.fromHexString( + "0x0000000000000000000000000000000000000000000000000123456789abcdef"), + 64, + 24, + 16, + Bytes.fromHexString( + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000123456789abcdef000000000000000000000000000000000000000000000000"), + 15L + }, + { + "overlapping src < dst", + Bytes.fromHexString( + "0x0123456789abcdef000000000000000000000000000000000000000000000000"), + 4, + 0, + 8, + Bytes.fromHexString( + "0x000000000123456789abcdef0000000000000000000000000000000000000000"), + 9L + }, + { + "overlapping src > dst", + Bytes.fromHexString( + "0x00112233445566778899aabbccddeeff00000000000000000000000000000000"), + 0, + 4, + 8, + Bytes.fromHexString( + "0x445566778899aabb000000000000000000000000000000000000000000000000"), + 9L + }, + { + "overlapping src == dst", + Bytes.fromHexString( + "0x00112233445566778899aabbccddeeff00000000000000000000000000000000"), + 4, + 4, + 8, + Bytes.fromHexString( + "0x00000000445566778899aabb0000000000000000000000000000000000000000"), + 9L + }, + {"large dst offset", Bytes.EMPTY, 0x8000000000000010L, 4, 8, Bytes.EMPTY, 8796294610953L}, + { + "large src offset", + Bytes.EMPTY, + 4, + 0x8000000000000010L, + 8, + Bytes.fromHexString( + "0x0000000000000000000000000000000000000000000000000000000000000000"), + 9L + }, + {"large len", Bytes.EMPTY, 4, 4, 0x8000000000000010L, Bytes.EMPTY, 8796093284361L} + }); + } + + @SuppressWarnings("unused") + @ParameterizedTest(name = "{0}") + @MethodSource("datacopyTestVector") + void testMCopy( + final String name, + final Bytes data, + final long dst, + final long src, + final long len, + final Bytes expected, + final long gasCost) { + DataCopyOperation subject = new DataCopyOperation(new PragueGasCalculator()); + String eofCode = + "0xef0001010004020001001d04%04x000080000367%016x67%016x67%016xd300%s" + .formatted(data.size(), dst, src, len, data.toUnprefixedHexString()); + Code code = evm.getCodeUncached(Bytes.fromHexString(eofCode)); + assumeThat(code.isValid()).isTrue(); + + MessageFrame frame = + new TestMessageFrameBuilder() + .pushStackItem(Bytes.ofUnsignedLong(len)) + .pushStackItem(Bytes.ofUnsignedLong(src)) + .pushStackItem(Bytes.ofUnsignedLong(dst)) + .initialGas(10_000_000) + .code(code) + .build(); + + Operation.OperationResult result = subject.execute(frame, evm); + + assertThat(frame.memoryWordSize()).isEqualTo((expected.size() + 31) / 32); + assertThat(frame.readMemory(0, expected.size())).isEqualTo(expected); + assertThat(result.getGasCost()).isEqualTo(gasCost); + } + + @Test + void legacyCallFails() { + DataCopyOperation subject = new DataCopyOperation(new PragueGasCalculator()); + Code code = evm.getCodeUncached(Bytes.fromHexString("0x600460046004d3")); + assumeThat(code.isValid()).isTrue(); + + MessageFrame frame = + new TestMessageFrameBuilder() + .pushStackItem(Bytes.ofUnsignedLong(4)) + .pushStackItem(Bytes.ofUnsignedLong(4)) + .pushStackItem(Bytes.ofUnsignedLong(4)) + .initialGas(10_000_000) + .code(code) + .pc(6) + .build(); + + Operation.OperationResult result = subject.execute(frame, evm); + assertThat(result.getGasCost()).isZero(); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INVALID_OPERATION); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/EofCreateOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/EofCreateOperationTest.java new file mode 100644 index 00000000000..d5d1212dfba --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/EofCreateOperationTest.java @@ -0,0 +1,159 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.hyperledger.besu.evm.EOFTestConstants.EOF_CREATE_CONTRACT; +import static org.hyperledger.besu.evm.EOFTestConstants.INNER_CONTRACT; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.MainnetEVMs; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.code.CodeInvalid; +import org.hyperledger.besu.evm.frame.BlockValues; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.internal.Words; +import org.hyperledger.besu.evm.log.Log; +import org.hyperledger.besu.evm.precompile.MainnetPrecompiledContracts; +import org.hyperledger.besu.evm.processor.ContractCreationProcessor; +import org.hyperledger.besu.evm.processor.MessageCallProcessor; +import org.hyperledger.besu.evm.tracing.OperationTracer; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; + +class EofCreateOperationTest { + + private final WorldUpdater worldUpdater = mock(WorldUpdater.class); + private final MutableAccount account = mock(MutableAccount.class); + private final MutableAccount newAccount = mock(MutableAccount.class); + + static final Bytes CALL_DATA = + Bytes.fromHexString( + "cafebaba600dbaadc0de57aff60061e5cafebaba600dbaadc0de57aff60061e5"); // 32 bytes + + public static final String SENDER = "0xdeadc0de00000000000000000000000000000000"; + + // private static final int SHANGHAI_CREATE_GAS = 41240; + + @Test + void innerContractIsCorrect() { + final EVM evm = MainnetEVMs.cancunEOF(EvmConfiguration.DEFAULT); + Code code = evm.getCodeUncached(INNER_CONTRACT); + assertThat(code.isValid()).isTrue(); + + final MessageFrame messageFrame = testMemoryFrame(code, CALL_DATA); + + when(account.getNonce()).thenReturn(55L); + when(account.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getSenderAccount(any())).thenReturn(account); + when(worldUpdater.getOrCreate(any())).thenReturn(newAccount); + when(newAccount.getCode()).thenReturn(Bytes.EMPTY); + when(newAccount.isStorageEmpty()).thenReturn(true); + when(worldUpdater.updater()).thenReturn(worldUpdater); + + final MessageFrame createFrame = messageFrame.getMessageFrameStack().peek(); + assertThat(createFrame).isNotNull(); + final ContractCreationProcessor ccp = + new ContractCreationProcessor(evm, false, List.of(), 0, List.of()); + ccp.process(createFrame, OperationTracer.NO_TRACING); + + final Log log = createFrame.getLogs().get(0); + final Bytes calculatedTopic = log.getTopics().get(0); + assertThat(calculatedTopic).isEqualTo(CALL_DATA); + } + + @Test + void eofCreatePassesInCallData() { + Bytes outerContract = EOF_CREATE_CONTRACT; + final EVM evm = MainnetEVMs.cancunEOF(EvmConfiguration.DEFAULT); + + Code code = evm.getCodeUncached(outerContract); + if (!code.isValid()) { + System.out.println(outerContract); + fail(((CodeInvalid) code).getInvalidReason()); + } + + final MessageFrame messageFrame = testMemoryFrame(code, CALL_DATA); + + when(account.getNonce()).thenReturn(55L); + when(account.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getSenderAccount(any())).thenReturn(account); + when(worldUpdater.getOrCreate(any())).thenReturn(newAccount); + when(newAccount.getCode()).thenReturn(Bytes.EMPTY); + when(newAccount.isStorageEmpty()).thenReturn(true); + when(worldUpdater.updater()).thenReturn(worldUpdater); + + var precompiles = MainnetPrecompiledContracts.prague(evm.getGasCalculator()); + final MessageFrame createFrame = messageFrame.getMessageFrameStack().peek(); + assertThat(createFrame).isNotNull(); + final MessageCallProcessor mcp = new MessageCallProcessor(evm, precompiles); + final ContractCreationProcessor ccp = + new ContractCreationProcessor(evm, false, List.of(), 0, List.of()); + while (!createFrame.getMessageFrameStack().isEmpty()) { + var frame = createFrame.getMessageFrameStack().peek(); + assert frame != null; + (switch (frame.getType()) { + case CONTRACT_CREATION -> ccp; + case MESSAGE_CALL -> mcp; + }) + .process(frame, OperationTracer.NO_TRACING); + } + + final Log log = createFrame.getLogs().get(0); + final String calculatedTopic = log.getTopics().get(0).slice(0, 2).toHexString(); + assertThat(calculatedTopic).isEqualTo("0xc0de"); + + assertThat(createFrame.getCreates()) + .containsExactly(Address.fromHexString("0x8c308e96997a8052e3aaab5af624cb827218687a")); + } + + private MessageFrame testMemoryFrame(final Code code, final Bytes initData) { + return MessageFrame.builder() + .type(MessageFrame.Type.MESSAGE_CALL) + .contract(Address.ZERO) + .inputData(initData) + .sender(Address.fromHexString(SENDER)) + .value(Wei.ZERO) + .apparentValue(Wei.ZERO) + .code(code) + .completer(__ -> {}) + .address(Address.fromHexString(SENDER)) + .blockHashLookup(n -> Hash.hash(Words.longBytes(n))) + .blockValues(mock(BlockValues.class)) + .gasPrice(Wei.ZERO) + .miningBeneficiary(Address.ZERO) + .originator(Address.ZERO) + .initialGas(100000L) + .worldUpdater(worldUpdater) + .build(); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/ExtCallOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/ExtCallOperationTest.java new file mode 100644 index 00000000000..93b10115015 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/ExtCallOperationTest.java @@ -0,0 +1,310 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.MainnetEVMs; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.PragueEOFGasCalculator; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class ExtCallOperationTest { + + private final WorldUpdater worldUpdater = mock(WorldUpdater.class); + private final MutableAccount account = mock(MutableAccount.class); + private static final EVM EOF_EVM = MainnetEVMs.pragueEOF(EvmConfiguration.DEFAULT); + public static final Code LEGACY_CODE = + EOF_EVM.getCodeUncached(Bytes.of(ExtCallOperation.OPCODE, 1)); + public static final Code SIMPLE_EOF = + EOF_EVM.getCodeUncached(Bytes.fromHexString("0xEF00010100040200010001040000000080000000")); + public static final Code INVALID_EOF = + EOF_EVM.getCodeUncached(Bytes.fromHexString("0xEF00010100040200010001040000000080000023")); + private static final Address CONTRACT_ADDRESS = Address.fromHexString("0xc0de"); + + static Iterable data() { + return List.of( + Arguments.of( + "gas", + 99, + 100, + 99, + ExceptionalHaltReason.INSUFFICIENT_GAS, + CONTRACT_ADDRESS, + true, + true), + Arguments.of( + "gas", + 5000, + 100, + 5000, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + true), + Arguments.of( + "gas", + 7300, + 100, + 7300, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + true), + Arguments.of( + "Cold Address", + 7300, + 2600, + 7300, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + false), + Arguments.of("gas", 64000, 59000, 58900, null, CONTRACT_ADDRESS, true, true), + Arguments.of("gas", 384100, 378100, 378000, null, CONTRACT_ADDRESS, true, true), + Arguments.of( + "Invalid code", + 384100, + 100, + 384100, + ExceptionalHaltReason.INVALID_CODE, + CONTRACT_ADDRESS, + false, + true)); + } + + @ParameterizedTest(name = "{index}: {0} {1}") + @MethodSource("data") + void gasTest( + final String name, + final long parentGas, + final long chargedGas, + final long childGas, + final ExceptionalHaltReason haltReason, + final Bytes stackItem, + final boolean validCode, + final boolean warmAddress) { + final ExtCallOperation operation = new ExtCallOperation(new PragueEOFGasCalculator()); + + final var messageFrame = + new TestMessageFrameBuilder() + .initialGas(parentGas) + .code(SIMPLE_EOF) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .build(); + if (warmAddress) { + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + } + when(account.getBalance()).thenReturn(Wei.ZERO); + when(account.getCodeHash()).thenReturn((validCode ? SIMPLE_EOF : INVALID_EOF).getCodeHash()); + when(account.getCode()).thenReturn((validCode ? SIMPLE_EOF : INVALID_EOF).getBytes()); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.updater()).thenReturn(worldUpdater); + + var result = operation.execute(messageFrame, EOF_EVM); + + assertThat(result.getGasCost()).isEqualTo(chargedGas); + assertThat(result.getHaltReason()).isEqualTo(haltReason); + + MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst(); + assertThat(childFrame.getRemainingGas()).isEqualTo(childGas); + + MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast(); + assertThat(parentFrame.getStackItem(0)).isEqualTo(stackItem); + } + + static Iterable valueData() { + return List.of( + Arguments.of( + "enough value", + 40000, + 35000, + 25900, + null, + CONTRACT_ADDRESS, + Wei.of(100), + Wei.of(200), + false), + Arguments.of( + "static context", + 40000, + 9000, + 40000, + ExceptionalHaltReason.ILLEGAL_STATE_CHANGE, + CONTRACT_ADDRESS, + Wei.of(100), + Wei.of(200), + true), + Arguments.of( + "not enough value", + 40000, + 9100, + 40000, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + Wei.of(1000), + Wei.of(200), + false), + Arguments.of( + "too little gas", + 5000, + 9100, + 5000, + ExceptionalHaltReason.INSUFFICIENT_GAS, + CONTRACT_ADDRESS, + Wei.of(100), + Wei.of(200), + false)); + } + + @ParameterizedTest(name = "{index}: {0} {1}") + @MethodSource("valueData") + void callWithValueTest( + final String name, + final long parentGas, + final long chargedGas, + final long childGas, + final ExceptionalHaltReason haltReason, + final Bytes stackItem, + final Wei valueSent, + final Wei valueWeiHave, + final boolean isStatic) { + final ExtCallOperation operation = new ExtCallOperation(new PragueEOFGasCalculator()); + + final var messageFrame = + new TestMessageFrameBuilder() + .initialGas(parentGas) + .code(SIMPLE_EOF) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(valueSent) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .isStatic(isStatic) + .build(); + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + when(account.getBalance()).thenReturn(valueWeiHave); + when(account.getCodeHash()).thenReturn(SIMPLE_EOF.getCodeHash()); + when(account.getCode()).thenReturn(SIMPLE_EOF.getBytes()); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.updater()).thenReturn(worldUpdater); + + var result = operation.execute(messageFrame, EOF_EVM); + + assertThat(result.getGasCost()).isEqualTo(chargedGas); + assertThat(result.getHaltReason()).isEqualTo(haltReason); + + MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst(); + assertThat(childFrame.getRemainingGas()).isEqualTo(childGas); + + MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast(); + assertThat(parentFrame.getStackItem(0)).isEqualTo(stackItem); + } + + @Test + void overflowTest() { + final ExtCallOperation operation = new ExtCallOperation(new PragueEOFGasCalculator()); + + final var messageFrame = + new TestMessageFrameBuilder() + .initialGas(400000) + .code(SIMPLE_EOF) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .build(); + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + when(account.getBalance()).thenReturn(Wei.ZERO); + when(account.getCodeHash()).thenReturn(SIMPLE_EOF.getCodeHash()); + when(account.getCode()).thenReturn(SIMPLE_EOF.getBytes()); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.updater()).thenReturn(worldUpdater); + while (messageFrame.getDepth() < 1024) { + messageFrame.getMessageFrameStack().add(messageFrame); + } + + var result = operation.execute(messageFrame, EOF_EVM); + + assertThat(result.getGasCost()).isEqualTo(100); + assertThat(result.getHaltReason()).isNull(); + + MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst(); + assertThat(childFrame.getRemainingGas()).isEqualTo(400000L); + + MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast(); + assertThat(parentFrame.getStackItem(0)) + .isEqualTo(AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM); + } + + @Test + void legacyTest() { + final ExtCallOperation operation = new ExtCallOperation(new PragueEOFGasCalculator()); + + final var messageFrame = + new TestMessageFrameBuilder() + .initialGas(400000) + .code(LEGACY_CODE) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .build(); + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + when(account.getBalance()).thenReturn(Wei.ZERO); + when(account.getCodeHash()).thenReturn(SIMPLE_EOF.getCodeHash()); + when(account.getCode()).thenReturn(SIMPLE_EOF.getBytes()); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.updater()).thenReturn(worldUpdater); + + var result = operation.execute(messageFrame, EOF_EVM); + + assertThat(result.getGasCost()).isZero(); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INVALID_OPERATION); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperationTest.java new file mode 100644 index 00000000000..b45824a92b4 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/ExtCodeCopyOperationTest.java @@ -0,0 +1,245 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.PragueGasCalculator; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; +import org.hyperledger.besu.evm.toy.ToyWorld; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.Arrays; +import java.util.Collection; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ExtCodeCopyOperationTest { + + private static final Address REQUESTED_ADDRESS = Address.fromHexString("0x22222222"); + + private final ToyWorld toyWorld = new ToyWorld(); + private final WorldUpdater worldStateUpdater = toyWorld.updater(); + + @Mock EVM evm; + + static Collection extCodeCopyTestVector() { + return Arrays.asList( + new Object[][] { + { + "Copy after, no overlap", + Bytes.fromHexString("0123456789abcdef000000000000000000000000000000000000000000000000"), + 32, + 0, + 8, + Bytes.fromHexString( + "00000000000000000000000000000000000000000000000000000000000000000123456789abcdef"), + false, + 2609L + }, + { + "copy from uninitialized memory", + Bytes.EMPTY, + 0, + 24, + 16, + Bytes.fromHexString( + "0x000000000000000000000000000000000000000000000000000000000000000000"), + false, + 2606L + }, + { + "copy from initialized + uninitialized memory", + Bytes.fromHexString( + "0x0000000000000000000000000000000000000000000000000123456789abcdef"), + 64, + 24, + 16, + Bytes.fromHexString( + "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000123456789abcdef000000000000000000000000000000000000000000000000"), + false, + 2612L + }, + { + "overlapping src < dst", + Bytes.fromHexString( + "0x0123456789abcdef000000000000000000000000000000000000000000000000"), + 4, + 0, + 8, + Bytes.fromHexString( + "0x000000000123456789abcdef0000000000000000000000000000000000000000"), + false, + 2606L + }, + { + "overlapping src > dst", + Bytes.fromHexString( + "0x00112233445566778899aabbccddeeff00000000000000000000000000000000"), + 0, + 4, + 8, + Bytes.fromHexString( + "0x445566778899aabb000000000000000000000000000000000000000000000000"), + false, + 2606L + }, + { + "overlapping src == dst", + Bytes.fromHexString( + "0x00112233445566778899aabbccddeeff00000000000000000000000000000000"), + 4, + 4, + 8, + Bytes.fromHexString( + "0x00000000445566778899aabb0000000000000000000000000000000000000000"), + false, + 2606L + }, + { + "EOF-reserved pre-eof", + Bytes.fromHexString("0xEF009f918bf09f9fa9"), + 0, + 0, + 9, + Bytes.fromHexString("0xEF009f918bf09f9fa9"), + false, + 2606L + }, + { + "EOF-reserved post-epf", + Bytes.fromHexString("0xEF009f918bf09f9fa9"), + 0, + 0, + 9, + Bytes.fromHexString("0xEF000000000000000000"), + true, + 2606L + }, + { + "EF-reserved pre-epf", + Bytes.fromHexString("0xEFF09f918bf09f9fa9"), + 0, + 0, + 9, + Bytes.fromHexString("0xEFF09f918bf09f9fa9"), + false, + 2606L + }, + { + "EOF-reserved post-eof", + Bytes.fromHexString("0xEFF09f918bf09f9fa9"), + 0, + 0, + 9, + Bytes.fromHexString("0xEFF09f918bf09f9fa9"), + true, + 2606L + } + }); + } + + @SuppressWarnings("unused") + @ParameterizedTest(name = "{0}") + @MethodSource("extCodeCopyTestVector") + void testExtCodeCopy( + final String name, + final Bytes code, + final long dst, + final long src, + final long len, + final Bytes expected, + final boolean eof, + final long gasCost) { + final MutableAccount account = worldStateUpdater.getOrCreate(REQUESTED_ADDRESS); + account.setCode(code); + + ExtCodeCopyOperation subject = new ExtCodeCopyOperation(new PragueGasCalculator(), eof); + MessageFrame frame = + new TestMessageFrameBuilder() + .worldUpdater(worldStateUpdater) + .pushStackItem(Bytes.ofUnsignedLong(len)) + .pushStackItem(Bytes.ofUnsignedLong(src)) + .pushStackItem(Bytes.ofUnsignedLong(dst)) + .pushStackItem(REQUESTED_ADDRESS) + .build(); + + Operation.OperationResult result = subject.execute(frame, evm); + + assertThat(frame.readMemory(0, expected.size())).isEqualTo(expected); + assertThat(frame.memoryWordSize()).isEqualTo((expected.size() + 31) / 32); + assertThat(result.getGasCost()).isEqualTo(gasCost); + } + + @Test + void testExtCodeCopyCold() { + final MutableAccount account = worldStateUpdater.getOrCreate(REQUESTED_ADDRESS); + Bytes code = Bytes.fromHexString("0xEFF09f918bf09f9fa9"); + account.setCode(code); + + ExtCodeCopyOperation subject = new ExtCodeCopyOperation(new PragueGasCalculator(), false); + MessageFrame frame = + new TestMessageFrameBuilder() + .worldUpdater(worldStateUpdater) + .pushStackItem(Bytes.ofUnsignedLong(9)) + .pushStackItem(Bytes.ofUnsignedLong(0)) + .pushStackItem(Bytes.ofUnsignedLong(0)) + .pushStackItem(REQUESTED_ADDRESS) + .build(); + frame.warmUpAddress(REQUESTED_ADDRESS); + + Operation.OperationResult result = subject.execute(frame, evm); + + assertThat(frame.readMemory(0, 9)).isEqualTo(code); + assertThat(frame.memoryWordSize()).isEqualTo(1); + assertThat(result.getGasCost()).isEqualTo(106); + } + + @Test + void testExtCodeEOFDirtyMemory() { + final MutableAccount account = worldStateUpdater.getOrCreate(REQUESTED_ADDRESS); + Bytes code = Bytes.fromHexString("0xEF009f918bf09f9fa9"); + account.setCode(code); + + ExtCodeCopyOperation subject = new ExtCodeCopyOperation(new PragueGasCalculator(), true); + MessageFrame frame = + new TestMessageFrameBuilder() + .worldUpdater(worldStateUpdater) + .pushStackItem(Bytes.ofUnsignedLong(9)) + .pushStackItem(Bytes.ofUnsignedLong(0)) + .pushStackItem(Bytes.ofUnsignedLong(0)) + .pushStackItem(REQUESTED_ADDRESS) + .build(); + frame.writeMemory(0, 15, Bytes.fromHexString("0x112233445566778899aabbccddeeff")); + + Operation.OperationResult result = subject.execute(frame, evm); + + assertThat(frame.readMemory(0, 16)) + .isEqualTo(Bytes.fromHexString("0xEF0000000000000000aabbccddeeff00")); + assertThat(frame.memoryWordSize()).isEqualTo(1); + assertThat(result.getGasCost()).isEqualTo(2603); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperationTest.java new file mode 100644 index 00000000000..97461cdbcdb --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/ExtCodeHashOperationTest.java @@ -0,0 +1,184 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.frame.BlockValues; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator; +import org.hyperledger.besu.evm.gascalculator.IstanbulGasCalculator; +import org.hyperledger.besu.evm.gascalculator.PragueGasCalculator; +import org.hyperledger.besu.evm.internal.Words; +import org.hyperledger.besu.evm.operation.Operation.OperationResult; +import org.hyperledger.besu.evm.testutils.FakeBlockValues; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; +import org.hyperledger.besu.evm.toy.ToyWorld; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.units.bigints.UInt256; +import org.junit.jupiter.api.Test; + +class ExtCodeHashOperationTest { + + private static final Address REQUESTED_ADDRESS = Address.fromHexString("0x22222222"); + + private final ToyWorld toyWorld = new ToyWorld(); + private final WorldUpdater worldStateUpdater = toyWorld.updater(); + + private final ExtCodeHashOperation operation = + new ExtCodeHashOperation(new ConstantinopleGasCalculator(), false); + private final ExtCodeHashOperation operationIstanbul = + new ExtCodeHashOperation(new IstanbulGasCalculator(), false); + private final ExtCodeHashOperation operationEOF = + new ExtCodeHashOperation(new PragueGasCalculator(), true); + + @Test + void shouldCharge400Gas() { + final OperationResult result = operation.execute(createMessageFrame(REQUESTED_ADDRESS), null); + assertThat(result.getGasCost()).isEqualTo(400L); + } + + @Test + void istanbulShouldCharge700Gas() { + final OperationResult result = + operationIstanbul.execute(createMessageFrame(REQUESTED_ADDRESS), null); + assertThat(result.getGasCost()).isEqualTo(700L); + } + + @Test + void shouldReturnZeroWhenAccountDoesNotExist() { + final Bytes result = executeOperation(REQUESTED_ADDRESS); + assertThat(result.trimLeadingZeros()).isEqualTo(Bytes.EMPTY); + } + + @Test + void shouldReturnHashOfEmptyDataWhenAccountExistsButDoesNotHaveCode() { + worldStateUpdater.getOrCreate(REQUESTED_ADDRESS).setBalance(Wei.of(1)); + assertThat(executeOperation(REQUESTED_ADDRESS)).isEqualTo(Hash.EMPTY); + } + + @Test + void shouldReturnZeroWhenAccountExistsButIsEmpty() { + worldStateUpdater.getOrCreate(REQUESTED_ADDRESS); + assertThat(executeOperation(REQUESTED_ADDRESS).trimLeadingZeros()).isEqualTo(Bytes.EMPTY); + } + + @Test + void shouldReturnZeroWhenPrecompiledContractHasNoBalance() { + assertThat(executeOperation(Address.ECREC).trimLeadingZeros()).isEqualTo(Bytes.EMPTY); + } + + @Test + void shouldReturnEmptyCodeHashWhenPrecompileHasBalance() { + // Sending money to a precompile causes it to exist in the world state archive. + worldStateUpdater.getOrCreate(Address.ECREC).setBalance(Wei.of(10)); + assertThat(executeOperation(Address.ECREC)).isEqualTo(Hash.EMPTY); + } + + @Test + void shouldGetHashOfAccountCodeWhenCodeIsPresent() { + final Bytes code = Bytes.fromHexString("0xabcdef"); + final MutableAccount account = worldStateUpdater.getOrCreate(REQUESTED_ADDRESS); + account.setCode(code); + assertThat(executeOperation(REQUESTED_ADDRESS)).isEqualTo(Hash.hash(code)); + } + + @Test + void shouldZeroOutLeftMostBitsToGetAddress() { + // If EXTCODEHASH of A is X, then EXTCODEHASH of A + 2**160 is X. + final Bytes code = Bytes.fromHexString("0xabcdef"); + final MutableAccount account = worldStateUpdater.getOrCreate(REQUESTED_ADDRESS); + account.setCode(code); + final UInt256 value = + UInt256.fromBytes(Words.fromAddress(REQUESTED_ADDRESS)) + .add(UInt256.valueOf(2).pow(UInt256.valueOf(160))); + final MessageFrame frame = createMessageFrame(value); + operation.execute(frame, null); + assertThat(frame.getStackItem(0)).isEqualTo(Hash.hash(code)); + } + + @Test + void shouldGetNonEOFHash() { + final Bytes code = Bytes.fromHexString("0xEFF09f918bf09f9fa9"); + final MutableAccount account = worldStateUpdater.getOrCreate(REQUESTED_ADDRESS); + account.setCode(code); + final UInt256 value = + UInt256.fromBytes(Words.fromAddress(REQUESTED_ADDRESS)) + .add(UInt256.valueOf(2).pow(UInt256.valueOf(160))); + + final MessageFrame frame = createMessageFrame(value); + operation.execute(frame, null); + assertThat(frame.getStackItem(0)).isEqualTo(Hash.hash(code)); + + final MessageFrame frameIstanbul = createMessageFrame(value); + operationIstanbul.execute(frameIstanbul, null); + assertThat(frameIstanbul.getStackItem(0)).isEqualTo(Hash.hash(code)); + + final MessageFrame frameEOF = createMessageFrame(value); + operationEOF.execute(frameEOF, null); + assertThat(frameEOF.getStackItem(0)).isEqualTo(Hash.hash(code)); + } + + @Test + void shouldGetEOFHash() { + final Bytes code = Bytes.fromHexString("0xEF009f918bf09f9fa9"); + final MutableAccount account = worldStateUpdater.getOrCreate(REQUESTED_ADDRESS); + account.setCode(code); + final UInt256 value = + UInt256.fromBytes(Words.fromAddress(REQUESTED_ADDRESS)) + .add(UInt256.valueOf(2).pow(UInt256.valueOf(160))); + + final MessageFrame frame = createMessageFrame(value); + operation.execute(frame, null); + assertThat(frame.getStackItem(0)).isEqualTo(Hash.hash(code)); + + final MessageFrame frameIstanbul = createMessageFrame(value); + operationIstanbul.execute(frameIstanbul, null); + assertThat(frameIstanbul.getStackItem(0)).isEqualTo(Hash.hash(code)); + + final MessageFrame frameEOF = createMessageFrame(value); + operationEOF.execute(frameEOF, null); + assertThat(frameEOF.getStackItem(0)).isEqualTo(Hash.hash(Bytes.fromHexString("0xef00"))); + } + + private Bytes executeOperation(final Address requestedAddress) { + final MessageFrame frame = createMessageFrame(requestedAddress); + operation.execute(frame, null); + return frame.getStackItem(0); + } + + private MessageFrame createMessageFrame(final Address requestedAddress) { + final UInt256 stackItem = Words.fromAddress(requestedAddress); + return createMessageFrame(stackItem); + } + + private MessageFrame createMessageFrame(final UInt256 stackItem) { + final BlockValues blockValues = new FakeBlockValues(1337); + final MessageFrame frame = + new TestMessageFrameBuilder() + .worldUpdater(worldStateUpdater) + .blockValues(blockValues) + .build(); + + frame.pushStackItem(stackItem); + return frame; + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperationTest.java new file mode 100644 index 00000000000..8c324692234 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/ExtCodeSizeOperationTest.java @@ -0,0 +1,183 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.frame.BlockValues; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator; +import org.hyperledger.besu.evm.gascalculator.IstanbulGasCalculator; +import org.hyperledger.besu.evm.gascalculator.PragueGasCalculator; +import org.hyperledger.besu.evm.internal.Words; +import org.hyperledger.besu.evm.operation.Operation.OperationResult; +import org.hyperledger.besu.evm.testutils.FakeBlockValues; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; +import org.hyperledger.besu.evm.toy.ToyWorld; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.units.bigints.UInt256; +import org.junit.jupiter.api.Test; + +class ExtCodeSizeOperationTest { + + private static final Address REQUESTED_ADDRESS = Address.fromHexString("0x22222222"); + + private final ToyWorld toyWorld = new ToyWorld(); + private final WorldUpdater worldStateUpdater = toyWorld.updater(); + + private final ExtCodeSizeOperation operation = + new ExtCodeSizeOperation(new ConstantinopleGasCalculator(), false); + private final ExtCodeSizeOperation operationIstanbul = + new ExtCodeSizeOperation(new IstanbulGasCalculator(), false); + private final ExtCodeSizeOperation operationEOF = + new ExtCodeSizeOperation(new PragueGasCalculator(), true); + + @Test + void shouldCharge700Gas() { + final OperationResult result = operation.execute(createMessageFrame(REQUESTED_ADDRESS), null); + assertThat(result.getGasCost()).isEqualTo(700L); + } + + @Test + void istanbulShouldCharge700Gas() { + final OperationResult result = + operationIstanbul.execute(createMessageFrame(REQUESTED_ADDRESS), null); + assertThat(result.getGasCost()).isEqualTo(700L); + } + + @Test + void shouldReturnZeroWhenAccountDoesNotExist() { + final Bytes result = executeOperation(REQUESTED_ADDRESS); + assertThat(result.trimLeadingZeros()).isEqualTo(Bytes.EMPTY); + } + + @Test + void shouldReturnSizeOfEmptyDataWhenAccountExistsButDoesNotHaveCode() { + worldStateUpdater.getOrCreate(REQUESTED_ADDRESS).setBalance(Wei.of(1)); + assertThat(executeOperation(REQUESTED_ADDRESS).toInt()).isZero(); + } + + @Test + void shouldReturnZeroWhenAccountExistsButIsEmpty() { + worldStateUpdater.getOrCreate(REQUESTED_ADDRESS); + assertThat(executeOperation(REQUESTED_ADDRESS).trimLeadingZeros()).isEqualTo(Bytes.EMPTY); + } + + @Test + void shouldReturnZeroWhenPrecompiledContractHasNoBalance() { + assertThat(executeOperation(Address.ECREC).trimLeadingZeros()).isEqualTo(Bytes.EMPTY); + } + + @Test + void shouldReturnEmptyCodeSizeWhenPrecompileHasBalance() { + // Sending money to a precompile causes it to exist in the world state archive. + worldStateUpdater.getOrCreate(Address.ECREC).setBalance(Wei.of(10)); + assertThat(executeOperation(Address.ECREC).toInt()).isZero(); + } + + @Test + void shouldGetSizeOfAccountCodeWhenCodeIsPresent() { + final Bytes code = Bytes.fromHexString("0xabcdef"); + final MutableAccount account = worldStateUpdater.getOrCreate(REQUESTED_ADDRESS); + account.setCode(code); + assertThat(executeOperation(REQUESTED_ADDRESS).toInt()).isEqualTo(3); + } + + @Test + void shouldZeroOutLeftMostBitsToGetAddress() { + // If EXTCODESIZE of A is X, then EXTCODESIZE of A + 2**160 is X. + final Bytes code = Bytes.fromHexString("0xabcdef"); + final MutableAccount account = worldStateUpdater.getOrCreate(REQUESTED_ADDRESS); + account.setCode(code); + final UInt256 value = + UInt256.fromBytes(Words.fromAddress(REQUESTED_ADDRESS)) + .add(UInt256.valueOf(2).pow(UInt256.valueOf(160))); + final MessageFrame frame = createMessageFrame(value); + operation.execute(frame, null); + assertThat(frame.getStackItem(0).toInt()).isEqualTo(3); + } + + @Test + void shouldGetNonEOFSize() { + final Bytes code = Bytes.fromHexString("0xEFF09f918bf09f9fa9"); + final MutableAccount account = worldStateUpdater.getOrCreate(REQUESTED_ADDRESS); + account.setCode(code); + final UInt256 value = + UInt256.fromBytes(Words.fromAddress(REQUESTED_ADDRESS)) + .add(UInt256.valueOf(2).pow(UInt256.valueOf(160))); + + final MessageFrame frame = createMessageFrame(value); + operation.execute(frame, null); + assertThat(frame.getStackItem(0).toInt()).isEqualTo(9); + + final MessageFrame frameIstanbul = createMessageFrame(value); + operationIstanbul.execute(frameIstanbul, null); + assertThat(frame.getStackItem(0).toInt()).isEqualTo(9); + + final MessageFrame frameEOF = createMessageFrame(value); + operationEOF.execute(frameEOF, null); + assertThat(frame.getStackItem(0).toInt()).isEqualTo(9); + } + + @Test + void shouldGetEOFSize() { + final Bytes code = Bytes.fromHexString("0xEF009f918bf09f9fa9"); + final MutableAccount account = worldStateUpdater.getOrCreate(REQUESTED_ADDRESS); + account.setCode(code); + final UInt256 value = + UInt256.fromBytes(Words.fromAddress(REQUESTED_ADDRESS)) + .add(UInt256.valueOf(2).pow(UInt256.valueOf(160))); + + final MessageFrame frame = createMessageFrame(value); + operation.execute(frame, null); + assertThat(frame.getStackItem(0).toInt()).isEqualTo(9); + + final MessageFrame frameIstanbul = createMessageFrame(value); + operationIstanbul.execute(frameIstanbul, null); + + assertThat(frameIstanbul.getStackItem(0).toInt()).isEqualTo(9); + final MessageFrame frameEOF = createMessageFrame(value); + operationEOF.execute(frameEOF, null); + assertThat(frameEOF.getStackItem(0).toInt()).isEqualTo(2); + } + + private Bytes executeOperation(final Address requestedAddress) { + final MessageFrame frame = createMessageFrame(requestedAddress); + operation.execute(frame, null); + return frame.getStackItem(0); + } + + private MessageFrame createMessageFrame(final Address requestedAddress) { + final UInt256 stackItem = Words.fromAddress(requestedAddress); + return createMessageFrame(stackItem); + } + + private MessageFrame createMessageFrame(final UInt256 stackItem) { + final BlockValues blockValues = new FakeBlockValues(1337); + final MessageFrame frame = + new TestMessageFrameBuilder() + .worldUpdater(worldStateUpdater) + .blockValues(blockValues) + .build(); + + frame.pushStackItem(stackItem); + return frame; + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/ExtDelegateCallOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/ExtDelegateCallOperationTest.java new file mode 100644 index 00000000000..62a10dcc2d7 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/ExtDelegateCallOperationTest.java @@ -0,0 +1,294 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.MainnetEVMs; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.PragueEOFGasCalculator; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class ExtDelegateCallOperationTest { + + private final WorldUpdater worldUpdater = mock(WorldUpdater.class); + private final MutableAccount account = mock(MutableAccount.class); + // private final MutableAccount targetAccount = mock(MutableAccount.class); + private static final EVM EOF_EVM = MainnetEVMs.pragueEOF(EvmConfiguration.DEFAULT); + public static final Code LEGACY_CODE = + EOF_EVM.getCodeUncached(Bytes.of(ExtDelegateCallOperation.OPCODE, 1)); + public static final Code SIMPLE_EOF = + EOF_EVM.getCodeUncached(Bytes.fromHexString("0xEF00010100040200010001040000000080000000")); + public static final Code SIMPLE_LEGACY = EOF_EVM.getCodeUncached(Bytes.fromHexString("0x00")); + public static final Code EMPTY_CODE = EOF_EVM.getCodeUncached(Bytes.fromHexString("")); + public static final Code INVALID_EOF = + EOF_EVM.getCodeUncached(Bytes.fromHexString("0xEF00010100040200010001040000000080000023")); + private static final Address CONTRACT_ADDRESS = Address.fromHexString("0xc0de"); + + static Iterable data() { + return List.of( + Arguments.of( + "gas", + 99, + 100, + 99, + ExceptionalHaltReason.INSUFFICIENT_GAS, + CONTRACT_ADDRESS, + true, + true), + Arguments.of( + "gas", + 5000, + 100, + 5000, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + true), + Arguments.of( + "gas", + 7300, + 100, + 7300, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + true), + Arguments.of( + "Cold Address", + 7300, + 2600, + 7300, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + false), + Arguments.of("gas", 64000, 59000, 58900, null, CONTRACT_ADDRESS, true, true), + Arguments.of("gas", 384100, 378100, 378000, null, CONTRACT_ADDRESS, true, true), + Arguments.of( + "Invalid code", + 384100, + 100, + 384100, + ExceptionalHaltReason.INVALID_CODE, + CONTRACT_ADDRESS, + false, + true)); + } + + @ParameterizedTest(name = "{index}: {0} {1}") + @MethodSource("data") + void gasTest( + final String name, + final long parentGas, + final long chargedGas, + final long childGas, + final ExceptionalHaltReason haltReason, + final Bytes stackItem, + final boolean validCode, + final boolean warmAddress) { + final ExtDelegateCallOperation operation = + new ExtDelegateCallOperation(new PragueEOFGasCalculator()); + + final var messageFrame = + new TestMessageFrameBuilder() + .code(SIMPLE_EOF) + .initialGas(parentGas) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .build(); + if (warmAddress) { + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + } + when(account.getBalance()).thenReturn(Wei.ZERO); + when(account.getCodeHash()).thenReturn((validCode ? SIMPLE_EOF : INVALID_EOF).getCodeHash()); + when(account.getCode()).thenReturn((validCode ? SIMPLE_EOF : INVALID_EOF).getBytes()); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.updater()).thenReturn(worldUpdater); + + var result = operation.execute(messageFrame, EOF_EVM); + + assertThat(result.getGasCost()).isEqualTo(chargedGas); + assertThat(result.getHaltReason()).isEqualTo(haltReason); + + MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst(); + assertThat(childFrame.getRemainingGas()).isEqualTo(childGas); + + MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast(); + assertThat(parentFrame.getStackItem(0)).isEqualTo(stackItem); + } + + static Iterable delegateData() { + return List.of( + Arguments.of("EOF", 40000, 35000, 34900L, null, CONTRACT_ADDRESS), + Arguments.of( + "Legacy", 40000, 100, 40000, null, AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM), + Arguments.of( + "Empty", + 40000, + 100, + 40000, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + CONTRACT_ADDRESS), + Arguments.of( + "EOA", 5000, 100, 5000, null, AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM)); + } + + @ParameterizedTest(name = "{index}: {0}") + @MethodSource("delegateData") + void callTypes( + final String name, + final long parentGas, + final long chargedGas, + final long childGas, + final ExceptionalHaltReason haltReason, + final Bytes stackItem) { + final ExtDelegateCallOperation operation = + new ExtDelegateCallOperation(new PragueEOFGasCalculator()); + + final var messageFrame = + new TestMessageFrameBuilder() + .code(SIMPLE_EOF) + .initialGas(parentGas) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .build(); + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + when(account.getBalance()).thenReturn(Wei.ZERO); + when(worldUpdater.get(TestMessageFrameBuilder.DEFAUT_ADDRESS)).thenReturn(account); + when(worldUpdater.getAccount(TestMessageFrameBuilder.DEFAUT_ADDRESS)).thenReturn(account); + + Code code = + switch (name) { + case "EOF" -> SIMPLE_EOF; + case "Legacy" -> SIMPLE_LEGACY; + default -> EMPTY_CODE; + }; + + when(account.getBalance()).thenReturn(Wei.ZERO); + when(account.getCodeHash()).thenReturn(code.getCodeHash()); + when(account.getCode()).thenReturn(code.getBytes()); + when(worldUpdater.get(CONTRACT_ADDRESS)).thenReturn("Empty".equals(name) ? null : account); + when(worldUpdater.getAccount(CONTRACT_ADDRESS)) + .thenReturn("Empty".equals(name) ? null : account); + when(worldUpdater.updater()).thenReturn(worldUpdater); + + var result = operation.execute(messageFrame, EOF_EVM); + + assertThat(result.getGasCost()).isEqualTo(chargedGas); + assertThat(result.getHaltReason()).isEqualTo(haltReason); + + MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst(); + assertThat(childFrame.getRemainingGas()).isEqualTo(childGas); + + MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast(); + assertThat(parentFrame.getStackItem(0)).isEqualTo(stackItem); + } + + @Test + void overflowTest() { + final ExtDelegateCallOperation operation = + new ExtDelegateCallOperation(new PragueEOFGasCalculator()); + final var messageFrame = + new TestMessageFrameBuilder() + .initialGas(400000) + .code(SIMPLE_EOF) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .build(); + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + when(account.getBalance()).thenReturn(Wei.ZERO); + when(account.getCodeHash()).thenReturn(SIMPLE_EOF.getCodeHash()); + when(account.getCode()).thenReturn(SIMPLE_EOF.getBytes()); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.updater()).thenReturn(worldUpdater); + while (messageFrame.getDepth() < 1024) { + messageFrame.getMessageFrameStack().add(messageFrame); + } + + var result = operation.execute(messageFrame, EOF_EVM); + + assertThat(result.getGasCost()).isEqualTo(100); + assertThat(result.getHaltReason()).isNull(); + + MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst(); + assertThat(childFrame.getRemainingGas()).isEqualTo(400000L); + + MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast(); + assertThat(parentFrame.getStackItem(0)) + .isEqualTo(AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM); + } + + @Test + void legacyTest() { + final ExtDelegateCallOperation operation = + new ExtDelegateCallOperation(new PragueEOFGasCalculator()); + + final var messageFrame = + new TestMessageFrameBuilder() + .initialGas(400000) + .code(LEGACY_CODE) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .build(); + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + when(account.getBalance()).thenReturn(Wei.ZERO); + when(account.getCodeHash()).thenReturn(SIMPLE_EOF.getCodeHash()); + when(account.getCode()).thenReturn(SIMPLE_EOF.getBytes()); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.updater()).thenReturn(worldUpdater); + + var result = operation.execute(messageFrame, EOF_EVM); + + assertThat(result.getGasCost()).isZero(); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INVALID_OPERATION); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/ExtStaticCallOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/ExtStaticCallOperationTest.java new file mode 100644 index 00000000000..ed306e89c7d --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/ExtStaticCallOperationTest.java @@ -0,0 +1,220 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.MainnetEVMs; +import org.hyperledger.besu.evm.account.MutableAccount; +import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.PragueEOFGasCalculator; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; +import org.hyperledger.besu.evm.worldstate.WorldUpdater; + +import java.util.List; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class ExtStaticCallOperationTest { + + private final WorldUpdater worldUpdater = mock(WorldUpdater.class); + private final MutableAccount account = mock(MutableAccount.class); + private static final EVM EOF_EVM = MainnetEVMs.pragueEOF(EvmConfiguration.DEFAULT); + public static final Code LEGACY_CODE = + EOF_EVM.getCodeUncached(Bytes.of(ExtStaticCallOperation.OPCODE, 1)); + public static final Code SIMPLE_EOF = + EOF_EVM.getCodeUncached(Bytes.fromHexString("0xEF00010100040200010001040000000080000000")); + public static final Code INVALID_EOF = + EOF_EVM.getCodeUncached(Bytes.fromHexString("0xEF00010100040200010001040000000080000023")); + private static final Address CONTRACT_ADDRESS = Address.fromHexString("0xc0de"); + + static Iterable data() { + return List.of( + Arguments.of( + "gas", + 99, + 100, + 99, + ExceptionalHaltReason.INSUFFICIENT_GAS, + CONTRACT_ADDRESS, + true, + true), + Arguments.of( + "gas", + 5000, + 100, + 5000, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + true), + Arguments.of( + "gas", + 7300, + 100, + 7300, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + true), + Arguments.of( + "Cold Address", + 7300, + 2600, + 7300, + null, + AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM, + true, + false), + Arguments.of("gas", 64000, 59000, 58900, null, CONTRACT_ADDRESS, true, true), + Arguments.of("gas", 384100, 378100, 378000, null, CONTRACT_ADDRESS, true, true), + Arguments.of( + "Invalid code", + 384100, + 100, + 384100, + ExceptionalHaltReason.INVALID_CODE, + CONTRACT_ADDRESS, + false, + true)); + } + + @ParameterizedTest(name = "{index}: {0} {1}") + @MethodSource("data") + void gasTest( + final String name, + final long parentGas, + final long chargedGas, + final long childGas, + final ExceptionalHaltReason haltReason, + final Bytes stackItem, + final boolean validCode, + final boolean warmAddress) { + final ExtStaticCallOperation operation = + new ExtStaticCallOperation(new PragueEOFGasCalculator()); + + final var messageFrame = + new TestMessageFrameBuilder() + .initialGas(parentGas) + .code(SIMPLE_EOF) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .build(); + if (warmAddress) { + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + } + when(account.getBalance()).thenReturn(Wei.ZERO); + when(account.getCodeHash()).thenReturn((validCode ? SIMPLE_EOF : INVALID_EOF).getCodeHash()); + when(account.getCode()).thenReturn((validCode ? SIMPLE_EOF : INVALID_EOF).getBytes()); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.updater()).thenReturn(worldUpdater); + + var result = operation.execute(messageFrame, EOF_EVM); + + assertThat(result.getGasCost()).isEqualTo(chargedGas); + assertThat(result.getHaltReason()).isEqualTo(haltReason); + + MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst(); + assertThat(childFrame.getRemainingGas()).isEqualTo(childGas); + + MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast(); + assertThat(parentFrame.getStackItem(0)).isEqualTo(stackItem); + } + + @Test + void overflowTest() { + final ExtStaticCallOperation operation = + new ExtStaticCallOperation(new PragueEOFGasCalculator()); + final var messageFrame = + new TestMessageFrameBuilder() + .initialGas(400000) + .code(SIMPLE_EOF) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .build(); + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + when(account.getBalance()).thenReturn(Wei.ZERO); + when(account.getCodeHash()).thenReturn(SIMPLE_EOF.getCodeHash()); + when(account.getCode()).thenReturn(SIMPLE_EOF.getBytes()); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.updater()).thenReturn(worldUpdater); + while (messageFrame.getDepth() < 1024) { + messageFrame.getMessageFrameStack().add(messageFrame); + } + + var result = operation.execute(messageFrame, EOF_EVM); + + assertThat(result.getGasCost()).isEqualTo(100); + assertThat(result.getHaltReason()).isNull(); + + MessageFrame childFrame = messageFrame.getMessageFrameStack().getFirst(); + assertThat(childFrame.getRemainingGas()).isEqualTo(400000L); + + MessageFrame parentFrame = messageFrame.getMessageFrameStack().getLast(); + assertThat(parentFrame.getStackItem(0)) + .isEqualTo(AbstractExtCallOperation.EOF1_EXCEPTION_STACK_ITEM); + } + + @Test + void legacyTest() { + final ExtStaticCallOperation operation = + new ExtStaticCallOperation(new PragueEOFGasCalculator()); + + final var messageFrame = + new TestMessageFrameBuilder() + .initialGas(400000) + .code(LEGACY_CODE) + .pushStackItem(CONTRACT_ADDRESS) // canary for non-returning + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(CONTRACT_ADDRESS) + .worldUpdater(worldUpdater) + .build(); + messageFrame.warmUpAddress(CONTRACT_ADDRESS); + when(account.getBalance()).thenReturn(Wei.ZERO); + when(account.getCodeHash()).thenReturn(SIMPLE_EOF.getCodeHash()); + when(account.getCode()).thenReturn(SIMPLE_EOF.getBytes()); + when(worldUpdater.get(any())).thenReturn(account); + when(worldUpdater.getAccount(any())).thenReturn(account); + when(worldUpdater.updater()).thenReturn(worldUpdater); + + var result = operation.execute(messageFrame, EOF_EVM); + + assertThat(result.getGasCost()).isZero(); + assertThat(result.getHaltReason()).isEqualTo(ExceptionalHaltReason.INVALID_OPERATION); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/JumpFOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/JumpFOperationTest.java new file mode 100644 index 00000000000..4611faa0f87 --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/JumpFOperationTest.java @@ -0,0 +1,61 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.testutils.OperationsTestUtils.mockCode; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.code.CodeSection; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; + +class JumpFOperationTest { + + @Test + void jumpFHappyPath() { + final GasCalculator gasCalculator = mock(GasCalculator.class); + final Code mockCode = mockCode("00" + "b2" + "0001" + "00"); + + final CodeSection codeSection = new CodeSection(0, 1, 2, 3, 0); + when(mockCode.getCodeSection(1)).thenReturn(codeSection); + when(mockCode.getCodeSection(2)).thenReturn(codeSection); + + MessageFrame messageFrame = + new TestMessageFrameBuilder() + .code(mockCode) + .pc(1) + .section(2) + .initialGas(10L) + .pushStackItem(Bytes.EMPTY) + .build(); + + JumpFOperation jumpF = new JumpFOperation(gasCalculator); + Operation.OperationResult jumpFResult = jumpF.execute(messageFrame, null); + + assertThat(jumpFResult.getHaltReason()).isNull(); + assertThat(jumpFResult.getPcIncrement()).isEqualTo(1); + assertThat(messageFrame.getSection()).isEqualTo(1); + assertThat(messageFrame.getPC()).isEqualTo(-1); + assertThat(messageFrame.returnStackSize()).isZero(); + assertThat(messageFrame.peekReturnStack()).isNull(); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/JumpOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/JumpOperationTest.java similarity index 84% rename from evm/src/test/java/org/hyperledger/besu/evm/operations/JumpOperationTest.java rename to evm/src/test/java/org/hyperledger/besu/evm/operation/JumpOperationTest.java index 37b5998309f..7b9fab338e0 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/JumpOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/JumpOperationTest.java @@ -12,23 +12,19 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.evm.operations; +package org.hyperledger.besu.evm.operation; import static org.assertj.core.api.Assertions.assertThat; import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.EVM; -import org.hyperledger.besu.evm.EvmSpecVersion; -import org.hyperledger.besu.evm.code.CodeFactory; +import org.hyperledger.besu.evm.MainnetEVMs; import org.hyperledger.besu.evm.frame.BlockValues; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.IstanbulGasCalculator; import org.hyperledger.besu.evm.internal.EvmConfiguration; -import org.hyperledger.besu.evm.operation.JumpDestOperation; -import org.hyperledger.besu.evm.operation.JumpOperation; import org.hyperledger.besu.evm.operation.Operation.OperationResult; -import org.hyperledger.besu.evm.operation.OperationRegistry; import org.hyperledger.besu.evm.testutils.FakeBlockValues; import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; @@ -46,7 +42,8 @@ class JumpOperationTest { private static final int CURRENT_PC = 1; - private Address address; + private final Address address = + Address.fromHexString("0xc0dec0dec0dec0dec0dec0dec0dec0dec0dec0de"); private EVM evm; private TestMessageFrameBuilder createMessageFrameBuilder(final long initialGas) { @@ -59,12 +56,7 @@ private TestMessageFrameBuilder createMessageFrameBuilder(final long initialGas) @BeforeEach void init() { - address = Address.fromHexString("0x18675309"); - - final OperationRegistry registry = new OperationRegistry(); - registry.put(new JumpOperation(gasCalculator)); - registry.put(new JumpDestOperation(gasCalculator)); - evm = new EVM(registry, gasCalculator, EvmConfiguration.DEFAULT, EvmSpecVersion.PARIS); + evm = MainnetEVMs.pragueEOF(EvmConfiguration.DEFAULT); } @Test @@ -74,7 +66,7 @@ void shouldJumpWhenLocationIsJumpDest() { final MessageFrame frame = createMessageFrameBuilder(10_000L) .pushStackItem(UInt256.fromHexString("0x03")) - .code(CodeFactory.createCode(jumpBytes, 0, false)) + .code(evm.getCodeUncached(jumpBytes)) .build(); frame.setPC(CURRENT_PC); @@ -89,7 +81,7 @@ void shouldJumpWhenLocationIsJumpDestAndAtEndOfCode() { final MessageFrame frame = createMessageFrameBuilder(10_000L) .pushStackItem(UInt256.fromHexString("0x03")) - .code(CodeFactory.createCode(jumpBytes, 0, false)) + .code(evm.getCodeUncached(jumpBytes)) .build(); frame.setPC(CURRENT_PC); @@ -104,7 +96,7 @@ void shouldHaltWithInvalidJumDestinationWhenLocationIsOutsideOfCodeRange() { final MessageFrame frameDestinationGreaterThanCodeSize = createMessageFrameBuilder(100L) .pushStackItem(UInt256.fromHexString("0xFFFFFFFF")) - .code(CodeFactory.createCode(jumpBytes, 0, false)) + .code(evm.getCodeUncached(jumpBytes)) .build(); frameDestinationGreaterThanCodeSize.setPC(CURRENT_PC); @@ -114,7 +106,7 @@ void shouldHaltWithInvalidJumDestinationWhenLocationIsOutsideOfCodeRange() { final MessageFrame frameDestinationEqualsToCodeSize = createMessageFrameBuilder(100L) .pushStackItem(UInt256.fromHexString("0x04")) - .code(CodeFactory.createCode(badJump, 0, false)) + .code(evm.getCodeUncached(badJump)) .build(); frameDestinationEqualsToCodeSize.setPC(CURRENT_PC); @@ -132,7 +124,7 @@ void longContractsValidate() { final MessageFrame longContract = createMessageFrameBuilder(100L) .pushStackItem(UInt256.fromHexString("0x12c")) - .code(CodeFactory.createCode(longCode, 0, false)) + .code(evm.getCodeUncached(longCode)) .build(); longContract.setPC(255); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/LondonSStoreOperationGasCostTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/LondonSStoreOperationGasCostTest.java similarity index 92% rename from evm/src/test/java/org/hyperledger/besu/evm/operations/LondonSStoreOperationGasCostTest.java rename to evm/src/test/java/org/hyperledger/besu/evm/operation/LondonSStoreOperationGasCostTest.java index 1065d5cbd66..b4865df5d23 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/LondonSStoreOperationGasCostTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/LondonSStoreOperationGasCostTest.java @@ -12,7 +12,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.evm.operations; +package org.hyperledger.besu.evm.operation; import static org.assertj.core.api.Assertions.assertThat; @@ -24,6 +24,7 @@ import org.apache.tuweni.units.bigints.UInt256; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -76,4 +77,11 @@ void shouldCalculateGasAccordingToEip3529( assertThat(frame.getRemainingGas()).isEqualTo(gasLimit - (expectedGasUsed + 2100)); assertThat(frame.getGasRefund()).isEqualTo(expectedGasRefund); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/MCopyOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/MCopyOperationTest.java similarity index 94% rename from evm/src/test/java/org/hyperledger/besu/evm/operations/MCopyOperationTest.java rename to evm/src/test/java/org/hyperledger/besu/evm/operation/MCopyOperationTest.java index bdae1238d5c..6bf5a8afd21 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/MCopyOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/MCopyOperationTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,21 +12,20 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.evm.operations; +package org.hyperledger.besu.evm.operation; import static org.assertj.core.api.Assertions.assertThat; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator; -import org.hyperledger.besu.evm.operation.MCopyOperation; -import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; import java.util.Arrays; import java.util.Collection; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -134,4 +133,11 @@ void testMCopy( assertThat(frame.readMemory(0, expected.size())).isEqualTo(expected); assertThat(result.getGasCost()).isEqualTo(gasCost); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/PrevRanDaoOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/PrevRanDaoOperationTest.java similarity index 92% rename from evm/src/test/java/org/hyperledger/besu/evm/operations/PrevRanDaoOperationTest.java rename to evm/src/test/java/org/hyperledger/besu/evm/operation/PrevRanDaoOperationTest.java index 8030cacc855..28340920a07 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/PrevRanDaoOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/PrevRanDaoOperationTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.evm.operations; +package org.hyperledger.besu.evm.operation; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -23,8 +23,6 @@ import org.hyperledger.besu.evm.frame.BlockValues; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.LondonGasCalculator; -import org.hyperledger.besu.evm.operation.Operation; -import org.hyperledger.besu.evm.operation.PrevRanDaoOperation; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/Push0OperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/Push0OperationTest.java similarity index 92% rename from evm/src/test/java/org/hyperledger/besu/evm/operations/Push0OperationTest.java rename to evm/src/test/java/org/hyperledger/besu/evm/operation/Push0OperationTest.java index 439f4144f07..d7440b07961 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/Push0OperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/Push0OperationTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.evm.operations; +package org.hyperledger.besu.evm.operation; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -24,9 +24,7 @@ import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.BerlinGasCalculator; import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.operation.Operation; import org.hyperledger.besu.evm.operation.Operation.OperationResult; -import org.hyperledger.besu.evm.operation.Push0Operation; import org.hyperledger.besu.evm.testutils.FakeBlockValues; import java.util.Optional; diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/RelativeJumpOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/RelativeJumpOperationTest.java similarity index 80% rename from evm/src/test/java/org/hyperledger/besu/evm/operations/RelativeJumpOperationTest.java rename to evm/src/test/java/org/hyperledger/besu/evm/operation/RelativeJumpOperationTest.java index e576b196978..7ca5eb04caa 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/RelativeJumpOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/RelativeJumpOperationTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,11 +11,11 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ -package org.hyperledger.besu.evm.operations; +package org.hyperledger.besu.evm.operation; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.testutils.OperationsTestUtils.mockCode; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -23,10 +23,6 @@ import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.internal.Words; -import org.hyperledger.besu.evm.operation.Operation; -import org.hyperledger.besu.evm.operation.RelativeJumpIfOperation; -import org.hyperledger.besu.evm.operation.RelativeJumpOperation; -import org.hyperledger.besu.evm.operation.RelativeJumpVectorOperation; import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; import org.apache.tuweni.bytes.Bytes; @@ -42,13 +38,11 @@ class RelativeJumpOperationTest { void rjumpOperation(final int jumpLength) { final GasCalculator gasCalculator = mock(GasCalculator.class); final MessageFrame messageFrame = mock(MessageFrame.class, Mockito.RETURNS_DEEP_STUBS); - final Code mockCode = mock(Code.class); final String twosComplementJump = String.format("%08x", jumpLength).substring(4); final int rjumpOperationIndex = 3; - final Bytes code = Bytes.fromHexString("00".repeat(3) + "5c" + twosComplementJump); + final Code mockCode = mockCode("00".repeat(3) + "5c" + twosComplementJump); when(messageFrame.getCode()).thenReturn(mockCode); - when(mockCode.getBytes()).thenReturn(code); when(messageFrame.getRemainingGas()).thenReturn(3L); when(messageFrame.getPC()).thenReturn(rjumpOperationIndex); @@ -56,15 +50,14 @@ void rjumpOperation(final int jumpLength) { Operation.OperationResult rjumpResult = rjump.execute(messageFrame, null); assertThat(rjumpResult.getPcIncrement()) - .isEqualTo(code.size() - rjumpOperationIndex + jumpLength); + .isEqualTo(mockCode.getBytes().size() - rjumpOperationIndex + jumpLength); } @Test void rjumpiOperation() { final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); final int rjumpOperationIndex = 3; - final Bytes code = Bytes.fromHexString("00".repeat(rjumpOperationIndex) + "5d0004"); + final Code mockCode = mockCode("00".repeat(rjumpOperationIndex) + "5d0004"); MessageFrame messageFrame = new TestMessageFrameBuilder() @@ -73,7 +66,6 @@ void rjumpiOperation() { .initialGas(5L) .pushStackItem(Bytes.EMPTY) .build(); - when(mockCode.getBytes()).thenReturn(code); RelativeJumpIfOperation rjumpi = new RelativeJumpIfOperation(gasCalculator); Operation.OperationResult rjumpResult = rjumpi.execute(messageFrame, null); @@ -84,9 +76,8 @@ void rjumpiOperation() { @Test void rjumpiHitOperation() { final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); final int rjumpOperationIndex = 3; - final Bytes code = Bytes.fromHexString("00".repeat(rjumpOperationIndex) + "5dfffc00"); + final Code mockCode = mockCode("00".repeat(rjumpOperationIndex) + "5dfffc00"); MessageFrame messageFrame = new TestMessageFrameBuilder() @@ -95,7 +86,6 @@ void rjumpiHitOperation() { .initialGas(5L) .pushStackItem(Words.intBytes(1)) .build(); - when(mockCode.getBytes()).thenReturn(code); RelativeJumpIfOperation rjumpi = new RelativeJumpIfOperation(gasCalculator); Operation.OperationResult rjumpResult = rjumpi.execute(messageFrame, null); @@ -106,14 +96,13 @@ void rjumpiHitOperation() { @Test void rjumpvOperation() { final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); final int rjumpOperationIndex = 3; final int jumpVectorSize = 1; final int jumpLength = 4; - final Bytes code = - Bytes.fromHexString( + final Code mockCode = + mockCode( "00".repeat(rjumpOperationIndex) - + String.format("5e%02x%04x", jumpVectorSize, jumpLength)); + + String.format("e2%02x%04x", jumpVectorSize - 1, jumpLength)); MessageFrame messageFrame = new TestMessageFrameBuilder() @@ -122,7 +111,6 @@ void rjumpvOperation() { .initialGas(5L) .pushStackItem(Bytes.of(jumpVectorSize)) .build(); - when(mockCode.getBytes()).thenReturn(code); RelativeJumpVectorOperation rjumpv = new RelativeJumpVectorOperation(gasCalculator); Operation.OperationResult rjumpResult = rjumpv.execute(messageFrame, null); @@ -157,17 +145,15 @@ void rjumpvOperation() { }) void rjumpvOverflowOperation(final String stackValue) { final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); final int rjumpOperationIndex = 3; final int jumpVectorSize = 255; final int jumpLength = 400; - final Bytes code = - Bytes.fromHexString( + final Code mockCode = + mockCode( "00".repeat(rjumpOperationIndex) - + String.format("5e%02x", jumpVectorSize) + + String.format("e2%02x", jumpVectorSize - 1) + String.format("%04x", jumpLength).repeat(jumpVectorSize)); - when(mockCode.getBytes()).thenReturn(code); RelativeJumpVectorOperation rjumpv = new RelativeJumpVectorOperation(gasCalculator); MessageFrame messageFrame = new TestMessageFrameBuilder() @@ -186,17 +172,15 @@ void rjumpvOverflowOperation(final String stackValue) { @ValueSource(strings = {"0x7f", "0xf5", "0x5f", "0xfe"}) void rjumpvIndexOperation(final String stackValue) { final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); final int rjumpOperationIndex = 3; final int jumpVectorSize = 255; final int jumpLength = 400; - final Bytes code = - Bytes.fromHexString( + final Code mockCode = + mockCode( "00".repeat(rjumpOperationIndex) - + String.format("5e%02x", jumpVectorSize) + + String.format("e2%02x", jumpVectorSize - 1) + String.format("%04x", jumpLength).repeat(jumpVectorSize)); - when(mockCode.getBytes()).thenReturn(code); RelativeJumpVectorOperation rjumpv = new RelativeJumpVectorOperation(gasCalculator); MessageFrame messageFrame = new TestMessageFrameBuilder() @@ -214,11 +198,10 @@ void rjumpvIndexOperation(final String stackValue) { @Test void rjumpvHitOperation() { final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); final int rjumpOperationIndex = 3; final int jumpVectorSize = 2; - final Bytes code = - Bytes.fromHexString("00".repeat(rjumpOperationIndex) + "5e" + "02" + "1234" + "5678"); + final Code mockCode = + mockCode("00".repeat(rjumpOperationIndex) + "e2" + "01" + "1234" + "5678"); MessageFrame messageFrame = new TestMessageFrameBuilder() @@ -227,7 +210,6 @@ void rjumpvHitOperation() { .initialGas(5L) .pushStackItem(Bytes.of(jumpVectorSize - 1)) .build(); - when(mockCode.getBytes()).thenReturn(code); RelativeJumpVectorOperation rjumpv = new RelativeJumpVectorOperation(gasCalculator); Operation.OperationResult rjumpResult = rjumpv.execute(messageFrame, null); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operation/RetFOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/RetFOperationTest.java new file mode 100644 index 00000000000..f2a0947086b --- /dev/null +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/RetFOperationTest.java @@ -0,0 +1,63 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.evm.operation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.testutils.OperationsTestUtils.mockCode; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.code.CodeSection; +import org.hyperledger.besu.evm.frame.MessageFrame; +import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.internal.ReturnStack; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; + +class RetFOperationTest { + + @Test + void retFHappyPath() { + final GasCalculator gasCalculator = mock(GasCalculator.class); + final Code mockCode = mockCode("00" + "b1" + "00"); + + final CodeSection codeSection = new CodeSection(0, 1, 2, 3, 0); + when(mockCode.getCodeSection(1)).thenReturn(codeSection); + + MessageFrame messageFrame = + new TestMessageFrameBuilder() + .code(mockCode) + .pc(1) + .section(1) + .initialGas(10L) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .pushStackItem(Bytes.EMPTY) + .build(); + messageFrame.pushReturnStackItem(new ReturnStack.ReturnStackItem(2, 3)); + + RetFOperation retF = new RetFOperation(gasCalculator); + Operation.OperationResult retFResult = retF.execute(messageFrame, null); + + assertThat(retFResult.getHaltReason()).isNull(); + assertThat(retFResult.getPcIncrement()).isEqualTo(1); + assertThat(messageFrame.getSection()).isEqualTo(2); + assertThat(messageFrame.getPC()).isEqualTo(3); + assertThat(messageFrame.returnStackSize()).isZero(); + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/RevertOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/RevertOperationTest.java similarity index 95% rename from evm/src/test/java/org/hyperledger/besu/evm/operations/RevertOperationTest.java rename to evm/src/test/java/org/hyperledger/besu/evm/operation/RevertOperationTest.java index 9ffc422d609..d5094494a3c 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/RevertOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/RevertOperationTest.java @@ -12,7 +12,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.evm.operations; +package org.hyperledger.besu.evm.operation; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.anyLong; @@ -20,7 +20,6 @@ import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator; -import org.hyperledger.besu.evm.operation.RevertOperation; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/SStoreOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/SStoreOperationTest.java similarity index 93% rename from evm/src/test/java/org/hyperledger/besu/evm/operations/SStoreOperationTest.java rename to evm/src/test/java/org/hyperledger/besu/evm/operation/SStoreOperationTest.java index e2f14dbdb2a..6f3c7fd21f6 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/SStoreOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/SStoreOperationTest.java @@ -12,7 +12,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.evm.operations; +package org.hyperledger.besu.evm.operation; import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.INSUFFICIENT_GAS; @@ -25,7 +25,6 @@ import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.operation.Operation.OperationResult; -import org.hyperledger.besu.evm.operation.SStoreOperation; import org.hyperledger.besu.evm.testutils.FakeBlockValues; import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; import org.hyperledger.besu.evm.toy.ToyWorld; @@ -34,6 +33,7 @@ import java.util.List; import org.apache.tuweni.units.bigints.UInt256; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -88,4 +88,11 @@ void storeOperation( final OperationResult result = operation.execute(frame, null); assertThat(result.getHaltReason()).isEqualTo(expectedHalt); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/SarOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/SarOperationTest.java similarity index 96% rename from evm/src/test/java/org/hyperledger/besu/evm/operations/SarOperationTest.java rename to evm/src/test/java/org/hyperledger/besu/evm/operation/SarOperationTest.java index 460a7294f69..c7d7cad00dc 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/SarOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/SarOperationTest.java @@ -12,8 +12,9 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.evm.operations; +package org.hyperledger.besu.evm.operation; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -21,12 +22,12 @@ import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.gascalculator.SpuriousDragonGasCalculator; -import org.hyperledger.besu.evm.operation.SarOperation; import java.util.List; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -176,4 +177,11 @@ void shiftOperation(final String number, final String shift, final String expect operation.execute(frame, null); verify(frame).pushStackItem(Bytes.fromHexString(expectedResult)); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/SelfDestructOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/SelfDestructOperationTest.java similarity index 92% rename from evm/src/test/java/org/hyperledger/besu/evm/operations/SelfDestructOperationTest.java rename to evm/src/test/java/org/hyperledger/besu/evm/operation/SelfDestructOperationTest.java index 913ffd92bc9..d3a3db754b0 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/SelfDestructOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/SelfDestructOperationTest.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.evm.operations; +package org.hyperledger.besu.evm.operation; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.atLeast; @@ -24,17 +24,17 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.MainnetEVMs; import org.hyperledger.besu.evm.account.MutableAccount; -import org.hyperledger.besu.evm.code.CodeFactory; import org.hyperledger.besu.evm.frame.BlockValues; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.internal.Words; -import org.hyperledger.besu.evm.operation.Operation; -import org.hyperledger.besu.evm.operation.SelfDestructOperation; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -55,7 +55,7 @@ public class SelfDestructOperationTest { @Mock private WorldUpdater worldUpdater; @Mock private MutableAccount accountOriginator; @Mock private MutableAccount accountBeneficiary; - @Mock private EVM evm; + private final EVM evm = MainnetEVMs.pragueEOF(EvmConfiguration.DEFAULT); private final SelfDestructOperation frontierOperation = new SelfDestructOperation(new ConstantinopleGasCalculator()); @@ -79,7 +79,7 @@ void checkContractDeletionCommon( .sender(beneficiaryAddress) .value(Wei.ZERO) .apparentValue(Wei.ZERO) - .code(CodeFactory.createCode(SELFDESTRUCT_CODE, 0, true)) + .code(evm.getCodeUncached(SELFDESTRUCT_CODE)) .completer(__ -> {}) .address(originatorAddress) .blockHashLookup(n -> Hash.hash(Words.longBytes(n))) @@ -175,4 +175,11 @@ void checkContractDeletionEIP6780( assertThat(messageFrame.getCreates()).doesNotContain(orignatorAddress); } } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/ShlOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/ShlOperationTest.java similarity index 94% rename from evm/src/test/java/org/hyperledger/besu/evm/operations/ShlOperationTest.java rename to evm/src/test/java/org/hyperledger/besu/evm/operation/ShlOperationTest.java index 7ee283877af..a4d0b8f3104 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/ShlOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/ShlOperationTest.java @@ -12,8 +12,9 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.evm.operations; +package org.hyperledger.besu.evm.operation; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -21,13 +22,13 @@ import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.gascalculator.SpuriousDragonGasCalculator; -import org.hyperledger.besu.evm.operation.ShlOperation; import java.util.Arrays; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -113,4 +114,11 @@ void shiftOperation(final String number, final String shift, final String expect operation.execute(frame, null); verify(frame).pushStackItem(Bytes.fromHexString(expectedResult)); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/ShrOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/ShrOperationTest.java similarity index 94% rename from evm/src/test/java/org/hyperledger/besu/evm/operations/ShrOperationTest.java rename to evm/src/test/java/org/hyperledger/besu/evm/operation/ShrOperationTest.java index 16e5ca76319..5acc7466a2e 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/ShrOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/ShrOperationTest.java @@ -12,8 +12,9 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.evm.operations; +package org.hyperledger.besu.evm.operation; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -21,13 +22,13 @@ import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.gascalculator.SpuriousDragonGasCalculator; -import org.hyperledger.besu.evm.operation.ShrOperation; import java.util.Arrays; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -124,4 +125,11 @@ void shiftOperation(final String number, final String shift, final String expect operation.execute(frame, null); verify(frame).pushStackItem(Bytes.fromHexString(expectedResult)); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/TStoreOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operation/TStoreOperationTest.java similarity index 99% rename from evm/src/test/java/org/hyperledger/besu/evm/operations/TStoreOperationTest.java rename to evm/src/test/java/org/hyperledger/besu/evm/operation/TStoreOperationTest.java index fed95ec0820..c7c3470825a 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/TStoreOperationTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/operation/TStoreOperationTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ -package org.hyperledger.besu.evm.operations; +package org.hyperledger.besu.evm.operation; import static org.assertj.core.api.Assertions.assertThat; import static org.hyperledger.besu.evm.frame.ExceptionalHaltReason.INSUFFICIENT_GAS; @@ -26,8 +26,6 @@ import org.hyperledger.besu.evm.gascalculator.GasCalculator; import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.operation.Operation.OperationResult; -import org.hyperledger.besu.evm.operation.TLoadOperation; -import org.hyperledger.besu.evm.operation.TStoreOperation; import org.hyperledger.besu.evm.testutils.ByteCodeBuilder; import org.hyperledger.besu.evm.testutils.FakeBlockValues; import org.hyperledger.besu.evm.testutils.TestCodeExecutor; diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/CallFOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/CallFOperationTest.java deleted file mode 100644 index c5ea477db80..00000000000 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/CallFOperationTest.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright contributors to Hyperledger Besu - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.evm.operations; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.evm.Code; -import org.hyperledger.besu.evm.code.CodeSection; -import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.ReturnStack; -import org.hyperledger.besu.evm.operation.CallFOperation; -import org.hyperledger.besu.evm.operation.Operation; -import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; - -import org.apache.tuweni.bytes.Bytes; -import org.junit.jupiter.api.Test; - -class CallFOperationTest { - - @Test - void callFHappyPath() { - final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); - final Bytes code = Bytes.fromHexString("00" + "b0" + "0001" + "00"); - when(mockCode.getBytes()).thenReturn(code); - - final CodeSection codeSection = new CodeSection(0, 1, 2, 3, 0); - when(mockCode.getCodeSection(1)).thenReturn(codeSection); - - MessageFrame messageFrame = - new TestMessageFrameBuilder() - .code(mockCode) - .pc(1) - .initialGas(10L) - .pushStackItem(Bytes.EMPTY) - .pushStackItem(Bytes.EMPTY) - .build(); - - CallFOperation callF = new CallFOperation(gasCalculator); - Operation.OperationResult callfResult = callF.execute(messageFrame, null); - - assertThat(callfResult.getHaltReason()).isNull(); - assertThat(callfResult.getPcIncrement()).isEqualTo(1); - assertThat(messageFrame.getSection()).isEqualTo(1); - assertThat(messageFrame.getPC()).isEqualTo(-1); - assertThat(messageFrame.returnStackSize()).isEqualTo(2); - assertThat(messageFrame.peekReturnStack()).isEqualTo(new ReturnStack.ReturnStackItem(0, 3, 1)); - } - - @Test - void callFMissingCodeSection() { - final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); - final Bytes code = Bytes.fromHexString("00" + "b0" + "03ff" + "00"); - when(mockCode.getBytes()).thenReturn(code); - - final CodeSection codeSection = new CodeSection(0, 1, 2, 3, 0); - when(mockCode.getCodeSection(1)).thenReturn(codeSection); - - MessageFrame messageFrame = - new TestMessageFrameBuilder() - .code(mockCode) - .pc(1) - .initialGas(10L) - .pushStackItem(Bytes.EMPTY) - .pushStackItem(Bytes.EMPTY) - .build(); - - CallFOperation callF = new CallFOperation(gasCalculator); - Operation.OperationResult callfResult = callF.execute(messageFrame, null); - - assertThat(callfResult.getHaltReason()).isEqualTo(ExceptionalHaltReason.CODE_SECTION_MISSING); - assertThat(callfResult.getPcIncrement()).isEqualTo(1); - assertThat(messageFrame.getSection()).isZero(); - assertThat(messageFrame.getPC()).isEqualTo(1); - assertThat(messageFrame.returnStackSize()).isEqualTo(1); - assertThat(messageFrame.peekReturnStack()).isEqualTo(new ReturnStack.ReturnStackItem(0, 0, 0)); - } - - @Test - void callFTooMuchStack() { - final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); - final Bytes code = Bytes.fromHexString("00" + "b0" + "0001" + "00"); - when(mockCode.getBytes()).thenReturn(code); - - final CodeSection codeSection = new CodeSection(0, 1, 2, 1023, 0); - when(mockCode.getCodeSection(1)).thenReturn(codeSection); - - MessageFrame messageFrame = - new TestMessageFrameBuilder() - .code(mockCode) - .pc(1) - .initialGas(10L) - .pushStackItem(Bytes.EMPTY) - .pushStackItem(Bytes.EMPTY) - .build(); - - CallFOperation callF = new CallFOperation(gasCalculator); - Operation.OperationResult callfResult = callF.execute(messageFrame, null); - - assertThat(callfResult.getHaltReason()).isEqualTo(ExceptionalHaltReason.TOO_MANY_STACK_ITEMS); - assertThat(callfResult.getPcIncrement()).isEqualTo(1); - assertThat(messageFrame.getSection()).isZero(); - assertThat(messageFrame.getPC()).isEqualTo(1); - assertThat(messageFrame.returnStackSize()).isEqualTo(1); - assertThat(messageFrame.peekReturnStack()).isEqualTo(new ReturnStack.ReturnStackItem(0, 0, 0)); - } - - @Test - void callFTooFewStack() { - final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); - final Bytes code = Bytes.fromHexString("00" + "b0" + "0001" + "00"); - when(mockCode.getBytes()).thenReturn(code); - - final CodeSection codeSection = new CodeSection(0, 5, 2, 5, 0); - when(mockCode.getCodeSection(1)).thenReturn(codeSection); - - MessageFrame messageFrame = - new TestMessageFrameBuilder() - .code(mockCode) - .pc(1) - .initialGas(10L) - .pushStackItem(Bytes.EMPTY) - .pushStackItem(Bytes.EMPTY) - .build(); - - CallFOperation callF = new CallFOperation(gasCalculator); - Operation.OperationResult callfResult = callF.execute(messageFrame, null); - - assertThat(callfResult.getHaltReason()) - .isEqualTo(ExceptionalHaltReason.TOO_FEW_INPUTS_FOR_CODE_SECTION); - assertThat(callfResult.getPcIncrement()).isEqualTo(1); - assertThat(messageFrame.getSection()).isZero(); - assertThat(messageFrame.getPC()).isEqualTo(1); - assertThat(messageFrame.returnStackSize()).isEqualTo(1); - assertThat(messageFrame.peekReturnStack()).isEqualTo(new ReturnStack.ReturnStackItem(0, 0, 0)); - } -} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/ExtCodeHashOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/ExtCodeHashOperationTest.java deleted file mode 100644 index a7ca5eefe67..00000000000 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/ExtCodeHashOperationTest.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.evm.operations; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.hyperledger.besu.datatypes.Address; -import org.hyperledger.besu.datatypes.Hash; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.evm.account.MutableAccount; -import org.hyperledger.besu.evm.frame.BlockValues; -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.gascalculator.ConstantinopleGasCalculator; -import org.hyperledger.besu.evm.gascalculator.IstanbulGasCalculator; -import org.hyperledger.besu.evm.internal.Words; -import org.hyperledger.besu.evm.operation.ExtCodeHashOperation; -import org.hyperledger.besu.evm.operation.Operation.OperationResult; -import org.hyperledger.besu.evm.testutils.FakeBlockValues; -import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; -import org.hyperledger.besu.evm.toy.ToyWorld; -import org.hyperledger.besu.evm.worldstate.WorldUpdater; - -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.units.bigints.UInt256; -import org.junit.jupiter.api.Test; - -class ExtCodeHashOperationTest { - - private static final Address REQUESTED_ADDRESS = Address.fromHexString("0x22222222"); - - private final ToyWorld toyWorld = new ToyWorld(); - private final WorldUpdater worldStateUpdater = toyWorld.updater(); - - private final ExtCodeHashOperation operation = - new ExtCodeHashOperation(new ConstantinopleGasCalculator()); - private final ExtCodeHashOperation operationIstanbul = - new ExtCodeHashOperation(new IstanbulGasCalculator()); - - @Test - void shouldCharge400Gas() { - final OperationResult result = operation.execute(createMessageFrame(REQUESTED_ADDRESS), null); - assertThat(result.getGasCost()).isEqualTo(400L); - } - - @Test - void istanbulShouldCharge700Gas() { - final OperationResult result = - operationIstanbul.execute(createMessageFrame(REQUESTED_ADDRESS), null); - assertThat(result.getGasCost()).isEqualTo(700L); - } - - @Test - void shouldReturnZeroWhenAccountDoesNotExist() { - final Bytes result = executeOperation(REQUESTED_ADDRESS); - assertThat(result.trimLeadingZeros()).isEqualTo(Bytes.EMPTY); - } - - @Test - void shouldReturnHashOfEmptyDataWhenAccountExistsButDoesNotHaveCode() { - worldStateUpdater.getOrCreate(REQUESTED_ADDRESS).setBalance(Wei.of(1)); - assertThat(executeOperation(REQUESTED_ADDRESS)).isEqualTo(Hash.EMPTY); - } - - @Test - void shouldReturnZeroWhenAccountExistsButIsEmpty() { - worldStateUpdater.getOrCreate(REQUESTED_ADDRESS); - assertThat(executeOperation(REQUESTED_ADDRESS).trimLeadingZeros()).isEqualTo(Bytes.EMPTY); - } - - @Test - void shouldReturnZeroWhenPrecompiledContractHasNoBalance() { - assertThat(executeOperation(Address.ECREC).trimLeadingZeros()).isEqualTo(Bytes.EMPTY); - } - - @Test - void shouldReturnEmptyCodeHashWhenPrecompileHasBalance() { - // Sending money to a precompile causes it to exist in the world state archive. - worldStateUpdater.getOrCreate(Address.ECREC).setBalance(Wei.of(10)); - assertThat(executeOperation(Address.ECREC)).isEqualTo(Hash.EMPTY); - } - - @Test - void shouldGetHashOfAccountCodeWhenCodeIsPresent() { - final Bytes code = Bytes.fromHexString("0xabcdef"); - final MutableAccount account = worldStateUpdater.getOrCreate(REQUESTED_ADDRESS); - account.setCode(code); - assertThat(executeOperation(REQUESTED_ADDRESS)).isEqualTo(Hash.hash(code)); - } - - @Test - void shouldZeroOutLeftMostBitsToGetAddress() { - // If EXTCODEHASH of A is X, then EXTCODEHASH of A + 2**160 is X. - final Bytes code = Bytes.fromHexString("0xabcdef"); - final MutableAccount account = worldStateUpdater.getOrCreate(REQUESTED_ADDRESS); - account.setCode(code); - final UInt256 value = - UInt256.fromBytes(Words.fromAddress(REQUESTED_ADDRESS)) - .add(UInt256.valueOf(2).pow(UInt256.valueOf(160))); - final MessageFrame frame = createMessageFrame(value); - operation.execute(frame, null); - assertThat(frame.getStackItem(0)).isEqualTo(Hash.hash(code)); - } - - private Bytes executeOperation(final Address requestedAddress) { - final MessageFrame frame = createMessageFrame(requestedAddress); - operation.execute(frame, null); - return frame.getStackItem(0); - } - - private MessageFrame createMessageFrame(final Address requestedAddress) { - final UInt256 stackItem = Words.fromAddress(requestedAddress); - return createMessageFrame(stackItem); - } - - private MessageFrame createMessageFrame(final UInt256 stackItem) { - final BlockValues blockValues = new FakeBlockValues(1337); - final MessageFrame frame = - new TestMessageFrameBuilder() - .worldUpdater(worldStateUpdater) - .blockValues(blockValues) - .build(); - - frame.pushStackItem(stackItem); - return frame; - } -} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/JumpFOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/JumpFOperationTest.java deleted file mode 100644 index a6157e52a84..00000000000 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/JumpFOperationTest.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright contributors to Hyperledger Besu - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.evm.operations; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.evm.Code; -import org.hyperledger.besu.evm.code.CodeSection; -import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.ReturnStack; -import org.hyperledger.besu.evm.operation.JumpFOperation; -import org.hyperledger.besu.evm.operation.Operation; -import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; - -import org.apache.tuweni.bytes.Bytes; -import org.junit.jupiter.api.Test; - -class JumpFOperationTest { - - @Test - void jumpFHappyPath() { - final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); - final Bytes code = Bytes.fromHexString("00" + "b2" + "0001" + "00"); - when(mockCode.getBytes()).thenReturn(code); - - final CodeSection codeSection = new CodeSection(0, 1, 2, 3, 0); - when(mockCode.getCodeSection(1)).thenReturn(codeSection); - when(mockCode.getCodeSection(2)).thenReturn(codeSection); - - MessageFrame messageFrame = - new TestMessageFrameBuilder() - .code(mockCode) - .pc(1) - .section(2) - .initialGas(10L) - .pushStackItem(Bytes.EMPTY) - .build(); - - JumpFOperation jumpF = new JumpFOperation(gasCalculator); - Operation.OperationResult jumpFResult = jumpF.execute(messageFrame, null); - - assertThat(jumpFResult.getHaltReason()).isNull(); - assertThat(jumpFResult.getPcIncrement()).isEqualTo(1); - assertThat(messageFrame.getSection()).isEqualTo(1); - assertThat(messageFrame.getPC()).isEqualTo(-1); - assertThat(messageFrame.returnStackSize()).isEqualTo(1); - assertThat(messageFrame.peekReturnStack()).isEqualTo(new ReturnStack.ReturnStackItem(0, 0, 0)); - } - - @Test - void jumpFMissingCodeSection() { - final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); - final Bytes code = Bytes.fromHexString("00" + "b2" + "03ff" + "00"); - when(mockCode.getBytes()).thenReturn(code); - - final CodeSection codeSection = new CodeSection(0, 1, 2, 3, 0); - when(mockCode.getCodeSection(1)).thenReturn(codeSection); - - MessageFrame messageFrame = - new TestMessageFrameBuilder() - .code(mockCode) - .pc(1) - .initialGas(10L) - .pushStackItem(Bytes.EMPTY) - .pushStackItem(Bytes.EMPTY) - .build(); - - JumpFOperation jumpF = new JumpFOperation(gasCalculator); - Operation.OperationResult jumpFResult = jumpF.execute(messageFrame, null); - - assertThat(jumpFResult.getHaltReason()).isEqualTo(ExceptionalHaltReason.CODE_SECTION_MISSING); - assertThat(jumpFResult.getPcIncrement()).isEqualTo(1); - assertThat(messageFrame.getSection()).isZero(); - assertThat(messageFrame.getPC()).isEqualTo(1); - assertThat(messageFrame.returnStackSize()).isEqualTo(1); - assertThat(messageFrame.peekReturnStack()).isEqualTo(new ReturnStack.ReturnStackItem(0, 0, 0)); - } - - @Test - void jumpFTooMuchStack() { - final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); - final Bytes code = Bytes.fromHexString("00" + "b2" + "0001" + "00"); - when(mockCode.getBytes()).thenReturn(code); - - final CodeSection codeSection1 = new CodeSection(0, 1, 2, 3, 0); - when(mockCode.getCodeSection(1)).thenReturn(codeSection1); - final CodeSection codeSection2 = new CodeSection(0, 2, 2, 3, 0); - when(mockCode.getCodeSection(2)).thenReturn(codeSection2); - - MessageFrame messageFrame = - new TestMessageFrameBuilder() - .code(mockCode) - .pc(1) - .section(2) - .initialGas(10L) - .pushStackItem(Bytes.EMPTY) - .pushStackItem(Bytes.EMPTY) - .build(); - - JumpFOperation jumpF = new JumpFOperation(gasCalculator); - Operation.OperationResult jumpFResult = jumpF.execute(messageFrame, null); - - assertThat(jumpFResult.getHaltReason()).isEqualTo(ExceptionalHaltReason.JUMPF_STACK_MISMATCH); - assertThat(jumpFResult.getPcIncrement()).isEqualTo(1); - assertThat(messageFrame.getSection()).isEqualTo(2); - assertThat(messageFrame.getPC()).isEqualTo(1); - assertThat(messageFrame.returnStackSize()).isEqualTo(1); - assertThat(messageFrame.peekReturnStack()).isEqualTo(new ReturnStack.ReturnStackItem(0, 0, 0)); - } -} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/operations/RetFOperationTest.java b/evm/src/test/java/org/hyperledger/besu/evm/operations/RetFOperationTest.java deleted file mode 100644 index fb375902260..00000000000 --- a/evm/src/test/java/org/hyperledger/besu/evm/operations/RetFOperationTest.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright contributors to Hyperledger Besu - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.evm.operations; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.evm.Code; -import org.hyperledger.besu.evm.code.CodeSection; -import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; -import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.gascalculator.GasCalculator; -import org.hyperledger.besu.evm.internal.ReturnStack; -import org.hyperledger.besu.evm.operation.Operation; -import org.hyperledger.besu.evm.operation.RetFOperation; -import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; - -import org.apache.tuweni.bytes.Bytes; -import org.junit.jupiter.api.Test; - -class RetFOperationTest { - - @Test - void retFHappyPath() { - final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); - final Bytes code = Bytes.fromHexString("00" + "b1" + "00"); - when(mockCode.getBytes()).thenReturn(code); - - final CodeSection codeSection = new CodeSection(0, 1, 2, 3, 0); - when(mockCode.getCodeSection(1)).thenReturn(codeSection); - - MessageFrame messageFrame = - new TestMessageFrameBuilder() - .code(mockCode) - .pc(1) - .section(1) - .initialGas(10L) - .pushStackItem(Bytes.EMPTY) - .pushStackItem(Bytes.EMPTY) - .pushStackItem(Bytes.EMPTY) - .build(); - messageFrame.pushReturnStackItem(new ReturnStack.ReturnStackItem(2, 3, 1)); - - RetFOperation retF = new RetFOperation(gasCalculator); - Operation.OperationResult retFResult = retF.execute(messageFrame, null); - - assertThat(retFResult.getHaltReason()).isNull(); - assertThat(retFResult.getPcIncrement()).isEqualTo(1); - assertThat(messageFrame.getSection()).isEqualTo(2); - assertThat(messageFrame.getPC()).isEqualTo(3); - assertThat(messageFrame.returnStackSize()).isEqualTo(1); - } - - @Test - void retFFinalReturn() { - final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); - final Bytes code = Bytes.fromHexString("00" + "b1" + "00"); - when(mockCode.getBytes()).thenReturn(code); - - final CodeSection codeSection = new CodeSection(0, 1, 2, 3, 0); - when(mockCode.getCodeSection(1)).thenReturn(codeSection); - - MessageFrame messageFrame = - new TestMessageFrameBuilder() - .code(mockCode) - .pc(1) - .section(1) - .initialGas(10L) - .pushStackItem(Bytes.EMPTY) - .pushStackItem(Bytes.EMPTY) - .build(); - - RetFOperation retF = new RetFOperation(gasCalculator); - Operation.OperationResult retFResult = retF.execute(messageFrame, null); - - assertThat(retFResult.getHaltReason()).isNull(); - assertThat(retFResult.getPcIncrement()).isEqualTo(1); - assertThat(messageFrame.getState()).isEqualTo(MessageFrame.State.CODE_SUCCESS); - assertThat(messageFrame.getOutputData()).isEqualTo(Bytes.EMPTY); - } - - @Test - void retFIncorrectOutput() { - final GasCalculator gasCalculator = mock(GasCalculator.class); - final Code mockCode = mock(Code.class); - final Bytes code = Bytes.fromHexString("00" + "b1" + "00"); - when(mockCode.getBytes()).thenReturn(code); - - final CodeSection codeSection = new CodeSection(0, 1, 2, 3, 0); - when(mockCode.getCodeSection(1)).thenReturn(codeSection); - - MessageFrame messageFrame = - new TestMessageFrameBuilder() - .code(mockCode) - .pc(1) - .section(1) - .initialGas(10L) - .pushStackItem(Bytes.EMPTY) - .pushStackItem(Bytes.EMPTY) - .pushStackItem(Bytes.EMPTY) - .build(); - - RetFOperation retF = new RetFOperation(gasCalculator); - Operation.OperationResult retFResult = retF.execute(messageFrame, null); - - assertThat(retFResult.getHaltReason()) - .isEqualTo(ExceptionalHaltReason.INCORRECT_CODE_SECTION_RETURN_OUTPUTS); - assertThat(retFResult.getPcIncrement()).isEqualTo(1); - assertThat(messageFrame.getSection()).isEqualTo(1); - assertThat(messageFrame.getPC()).isEqualTo(1); - assertThat(messageFrame.returnStackSize()).isZero(); - assertThat(messageFrame.peekReturnStack()).isNull(); - } -} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/precompile/AltBN128PairingPrecompiledContractTest.java b/evm/src/test/java/org/hyperledger/besu/evm/precompile/AltBN128PairingPrecompiledContractTest.java index af78f975b77..5b7b470a8a7 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/precompile/AltBN128PairingPrecompiledContractTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/precompile/AltBN128PairingPrecompiledContractTest.java @@ -22,6 +22,8 @@ import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -36,8 +38,14 @@ class AltBN128PairingPrecompiledContractTest { private final AltBN128PairingPrecompiledContract istanbulContract = AltBN128PairingPrecompiledContract.istanbul(gasCalculator); - @Test - void compute_validPoints() { + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void compute_validPoints(final boolean useNative) { + if (useNative) { + AbstractAltBnPrecompiledContract.maybeEnableNative(); + } else { + AbstractAltBnPrecompiledContract.disableNative(); + } final Bytes input = validPointBytes(); final Bytes result = byzantiumContract.computePrecompile(input, messageFrame).getOutput(); assertThat(result).isEqualTo(AltBN128PairingPrecompiledContract.TRUE); @@ -80,8 +88,14 @@ Bytes validPointBytes() { return Bytes.concatenate(g1Point0, g2Point0, g1Point1, g2Point1); } - @Test - void compute_invalidPointsOutsideSubgroupG2() { + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void compute_invalidPointsOutsideSubgroupG2(final boolean useNative) { + if (useNative) { + AbstractAltBnPrecompiledContract.maybeEnableNative(); + } else { + AbstractAltBnPrecompiledContract.disableNative(); + } final Bytes g1Point0 = Bytes.concatenate( Bytes.fromHexString( @@ -122,11 +136,13 @@ void compute_invalidPointsOutsideSubgroupG2() { @Test void gasPrice_byzantium() { + // gas calculation is java only assertThat(byzantiumContract.gasRequirement(validPointBytes())).isEqualTo(260_000L); } @Test void gasPrice_istanbul() { + // gas calculation is java only assertThat(istanbulContract.gasRequirement(validPointBytes())).isEqualTo(113_000L); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLAKE2BFPrecompileContractTest.java b/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLAKE2BFPrecompileContractTest.java index c449f3e0827..742edc85486 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLAKE2BFPrecompileContractTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLAKE2BFPrecompileContractTest.java @@ -17,10 +17,12 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; +import org.hyperledger.besu.crypto.Blake2bfMessageDigest.Blake2bfDigest; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.PetersburgGasCalculator; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -66,9 +68,24 @@ static Arguments[] parameters() { @ParameterizedTest @MethodSource("parameters") - void shouldRunFCompression( + void shouldRunFCompressionNative( final String inputString, final String expectedResult, final long expectedGasUsed) { + Blake2bfDigest.maybeEnableNative(); + testFCompression(inputString, expectedResult, expectedGasUsed); + } + + @ParameterizedTest + @MethodSource("parameters") + void shouldRunFCompressionJava( + final String inputString, final String expectedResult, final long expectedGasUsed) { + Blake2bfDigest.disableNative(); + + testFCompression(inputString, expectedResult, expectedGasUsed); + } + + private void testFCompression( + final String inputString, final String expectedResult, final long expectedGasUsed) { final Bytes input = Bytes.fromHexString(inputString); final Bytes expectedComputation = expectedResult == null ? null : Bytes.fromHexString(expectedResult); @@ -76,4 +93,11 @@ void shouldRunFCompression( .isEqualTo(expectedComputation); assertThat(contract.gasRequirement(input)).isEqualTo(expectedGasUsed); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12G1AddPrecompiledContractTest.java b/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12G1AddPrecompiledContractTest.java index 293976e7f8b..2ad792a410e 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12G1AddPrecompiledContractTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12G1AddPrecompiledContractTest.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.precompile; @@ -28,6 +27,7 @@ import com.google.common.io.CharStreams; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -72,7 +72,7 @@ void shouldCalculate( if (actualComputation == null) { final ArgumentCaptor revertReason = ArgumentCaptor.forClass(Bytes.class); Mockito.verify(messageFrame).setRevertReason(revertReason.capture()); - assertThat(new String(revertReason.getValue().toArrayUnsafe(), UTF_8)).isEqualTo(notes); + assertThat(new String(revertReason.getValue().toArrayUnsafe(), UTF_8)).contains(notes); assertThat(expectedComputation.size()).isZero(); } else { @@ -80,4 +80,11 @@ void shouldCalculate( assertThat(contract.gasRequirement(input)).isEqualTo(Long.parseLong(expectedGasUsed)); } } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12G1MulPrecompiledContractTest.java b/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12G1MulPrecompiledContractTest.java index bb5a2e1cb80..bf0f8a14818 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12G1MulPrecompiledContractTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12G1MulPrecompiledContractTest.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.precompile; @@ -29,6 +28,7 @@ import com.google.common.io.CharStreams; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -72,7 +72,7 @@ void shouldCalculate( if (actualComputation == null) { final ArgumentCaptor revertReason = ArgumentCaptor.forClass(Bytes.class); verify(messageFrame).setRevertReason(revertReason.capture()); - assertThat(new String(revertReason.getValue().toArrayUnsafe(), UTF_8)).isEqualTo(notes); + assertThat(new String(revertReason.getValue().toArrayUnsafe(), UTF_8)).contains(notes); assertThat(expectedComputation.size()).isZero(); } else { @@ -80,4 +80,11 @@ void shouldCalculate( assertThat(contract.gasRequirement(input)).isEqualTo(Long.parseLong(expectedGasUsed)); } } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12G1MultiExpPrecompiledContractTest.java b/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12G1MultiExpPrecompiledContractTest.java index b4d21661f11..27d45b1dd2a 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12G1MultiExpPrecompiledContractTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12G1MultiExpPrecompiledContractTest.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.precompile; @@ -29,6 +28,7 @@ import com.google.common.io.CharStreams; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -73,7 +73,7 @@ void shouldCalculate( if (actualComputation == null) { final ArgumentCaptor revertReason = ArgumentCaptor.forClass(Bytes.class); verify(messageFrame).setRevertReason(revertReason.capture()); - assertThat(new String(revertReason.getValue().toArrayUnsafe(), UTF_8)).isEqualTo(notes); + assertThat(new String(revertReason.getValue().toArrayUnsafe(), UTF_8)).contains(notes); assertThat(expectedComputation.size()).isZero(); } else { @@ -81,4 +81,11 @@ void shouldCalculate( assertThat(contract.gasRequirement(input)).isEqualTo(Long.parseLong(expectedGasUsed)); } } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12G2AddPrecompiledContractTest.java b/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12G2AddPrecompiledContractTest.java index b2865c57ebf..e98422f785c 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12G2AddPrecompiledContractTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12G2AddPrecompiledContractTest.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.precompile; @@ -29,6 +28,7 @@ import com.google.common.io.CharStreams; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -72,7 +72,7 @@ void shouldCalculate( if (actualComputation == null) { final ArgumentCaptor revertReason = ArgumentCaptor.forClass(Bytes.class); verify(messageFrame).setRevertReason(revertReason.capture()); - assertThat(new String(revertReason.getValue().toArrayUnsafe(), UTF_8)).isEqualTo(notes); + assertThat(new String(revertReason.getValue().toArrayUnsafe(), UTF_8)).contains(notes); assertThat(expectedComputation.size()).isZero(); } else { @@ -80,4 +80,11 @@ void shouldCalculate( assertThat(contract.gasRequirement(input)).isEqualTo(Long.parseLong(expectedGasUsed)); } } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12G2MulPrecompiledContractTest.java b/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12G2MulPrecompiledContractTest.java index b33cfb73cea..32af4677477 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12G2MulPrecompiledContractTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12G2MulPrecompiledContractTest.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.precompile; @@ -29,6 +28,7 @@ import com.google.common.io.CharStreams; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -72,7 +72,7 @@ void shouldCalculate( if (actualComputation == null) { final ArgumentCaptor revertReason = ArgumentCaptor.forClass(Bytes.class); verify(messageFrame).setRevertReason(revertReason.capture()); - assertThat(new String(revertReason.getValue().toArrayUnsafe(), UTF_8)).isEqualTo(notes); + assertThat(new String(revertReason.getValue().toArrayUnsafe(), UTF_8)).contains(notes); assertThat(expectedComputation.size()).isZero(); } else { @@ -80,4 +80,11 @@ void shouldCalculate( assertThat(contract.gasRequirement(input)).isEqualTo(Long.parseLong(expectedGasUsed)); } } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12G2MultiExpPrecompiledContractTest.java b/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12G2MultiExpPrecompiledContractTest.java index 21f9084d570..27ca15076a4 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12G2MultiExpPrecompiledContractTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12G2MultiExpPrecompiledContractTest.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.precompile; @@ -28,6 +27,7 @@ import com.google.common.io.CharStreams; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -72,7 +72,7 @@ void shouldCalculate( if (actualComputation == null) { final ArgumentCaptor revertReason = ArgumentCaptor.forClass(Bytes.class); verify(messageFrame).setRevertReason(revertReason.capture()); - assertThat(new String(revertReason.getValue().toArrayUnsafe(), UTF_8)).isEqualTo(notes); + assertThat(new String(revertReason.getValue().toArrayUnsafe(), UTF_8)).contains(notes); assertThat(expectedComputation.size()).isZero(); } else { @@ -80,4 +80,11 @@ void shouldCalculate( assertThat(contract.gasRequirement(input)).isEqualTo(Long.parseLong(expectedGasUsed)); } } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12MapFp2ToG2PrecompiledContractTest.java b/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12MapFp2ToG2PrecompiledContractTest.java index c90e2a1eb41..afe2826aec5 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12MapFp2ToG2PrecompiledContractTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12MapFp2ToG2PrecompiledContractTest.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.precompile; @@ -29,6 +28,7 @@ import com.google.common.io.CharStreams; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -73,7 +73,7 @@ void shouldCalculate( if (actualComputation == null) { final ArgumentCaptor revertReason = ArgumentCaptor.forClass(Bytes.class); verify(messageFrame).setRevertReason(revertReason.capture()); - assertThat(new String(revertReason.getValue().toArrayUnsafe(), UTF_8)).isEqualTo(notes); + assertThat(new String(revertReason.getValue().toArrayUnsafe(), UTF_8)).contains(notes); assertThat(expectedComputation.size()).isZero(); } else { @@ -81,4 +81,11 @@ void shouldCalculate( assertThat(contract.gasRequirement(input)).isEqualTo(Long.parseLong(expectedGasUsed)); } } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12MapFpToG1PrecompiledContractTest.java b/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12MapFpToG1PrecompiledContractTest.java index 6f269b7880f..836e295dd54 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12MapFpToG1PrecompiledContractTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12MapFpToG1PrecompiledContractTest.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.precompile; @@ -29,6 +28,7 @@ import com.google.common.io.CharStreams; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -73,7 +73,7 @@ void shouldCalculate( if (actualComputation == null) { final ArgumentCaptor revertReason = ArgumentCaptor.forClass(Bytes.class); verify(messageFrame).setRevertReason(revertReason.capture()); - assertThat(new String(revertReason.getValue().toArrayUnsafe(), UTF_8)).isEqualTo(notes); + assertThat(new String(revertReason.getValue().toArrayUnsafe(), UTF_8)).contains(notes); assertThat(expectedComputation.size()).isZero(); } else { @@ -81,4 +81,11 @@ void shouldCalculate( assertThat(contract.gasRequirement(input)).isEqualTo(Long.parseLong(expectedGasUsed)); } } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12PairingPrecompiledContractTest.java b/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12PairingPrecompiledContractTest.java index a1e3b49fc25..134bc12741e 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12PairingPrecompiledContractTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/precompile/BLS12PairingPrecompiledContractTest.java @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.precompile; @@ -30,6 +29,7 @@ import com.google.common.collect.Streams; import com.google.common.io.CharStreams; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -82,7 +82,7 @@ void shouldCalculate( if (actualComputation == null) { final ArgumentCaptor revertReason = ArgumentCaptor.forClass(Bytes.class); verify(messageFrame).setRevertReason(revertReason.capture()); - assertThat(new String(revertReason.getValue().toArrayUnsafe(), UTF_8)).isEqualTo(notes); + assertThat(new String(revertReason.getValue().toArrayUnsafe(), UTF_8)).contains(notes); assertThat(expectedComputation.size()).isZero(); } else { @@ -90,4 +90,11 @@ void shouldCalculate( assertThat(contract.gasRequirement(input)).isEqualTo(Long.parseLong(expectedGasUsed)); } } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/precompile/Benchmarks.java b/evm/src/test/java/org/hyperledger/besu/evm/precompile/Benchmarks.java index e25701d6d0d..458f0c95587 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/precompile/Benchmarks.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/precompile/Benchmarks.java @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.evm.precompile; import static java.nio.charset.StandardCharsets.UTF_8; diff --git a/evm/src/test/java/org/hyperledger/besu/evm/precompile/ECRECPrecompiledContractTest.java b/evm/src/test/java/org/hyperledger/besu/evm/precompile/ECRECPrecompiledContractTest.java index bc4026ae64b..7d2c52efca9 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/precompile/ECRECPrecompiledContractTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/precompile/ECRECPrecompiledContractTest.java @@ -22,6 +22,7 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -348,11 +349,28 @@ static Arguments[] parameters() { @ParameterizedTest @MethodSource("parameters") - void shouldRecoverAddress(final String inputString, final String expectedResult) { + void shouldRecoverAddressNative(final String inputString, final String expectedResult) { + contract.signatureAlgorithm.maybeEnableNative(); + final Bytes input = Bytes.fromHexString(inputString); + final Bytes expected = + expectedResult == null ? Bytes.EMPTY : Bytes32.fromHexString(expectedResult); + assertThat(contract.computePrecompile(input, messageFrame).getOutput()).isEqualTo(expected); + } + @ParameterizedTest + @MethodSource("parameters") + void shouldRecoverAddressJava(final String inputString, final String expectedResult) { + contract.signatureAlgorithm.disableNative(); final Bytes input = Bytes.fromHexString(inputString); final Bytes expected = expectedResult == null ? Bytes.EMPTY : Bytes32.fromHexString(expectedResult); assertThat(contract.computePrecompile(input, messageFrame).getOutput()).isEqualTo(expected); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/precompile/KZGPointEvalPrecompileContractTest.java b/evm/src/test/java/org/hyperledger/besu/evm/precompile/KZGPointEvalPrecompileContractTest.java index bc531cb0c30..bbf9b545afb 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/precompile/KZGPointEvalPrecompileContractTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/precompile/KZGPointEvalPrecompileContractTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -35,6 +35,7 @@ import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -44,7 +45,7 @@ public class KZGPointEvalPrecompileContractTest { @BeforeAll public static void init() { - KZGPointEvalPrecompiledContract.init("mainnet"); + KZGPointEvalPrecompiledContract.init(); contract = new KZGPointEvalPrecompiledContract(); } @@ -55,7 +56,7 @@ public static void tearDown() { @ParameterizedTest(name = "{index}") @MethodSource("getPointEvaluationPrecompileTestVectors") - public void testComputePrecompile(final PrecompileTestParameters parameters) { + void testComputePrecompile(final PrecompileTestParameters parameters) { when(toRun.getVersionedHashes()).thenReturn(Optional.of(List.of(parameters.versionedHash))); PrecompiledContract.PrecompileContractResult result = contract.computePrecompile(parameters.input, toRun); @@ -91,4 +92,11 @@ public static List getPointEvaluationPrecompileTestVec record PrecompileTestParameters( Bytes input, boolean valid, Bytes returnValue, VersionedHash versionedHash) {} + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/precompile/MODEXPPrecompiledContractTest.java b/evm/src/test/java/org/hyperledger/besu/evm/precompile/MODEXPPrecompiledContractTest.java index ad2c80a41f5..61e0f63cfb5 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/precompile/MODEXPPrecompiledContractTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/precompile/MODEXPPrecompiledContractTest.java @@ -22,6 +22,7 @@ import org.hyperledger.besu.evm.gascalculator.ByzantiumGasCalculator; import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -154,12 +155,27 @@ static Arguments[] parameters() { @ParameterizedTest @MethodSource("parameters") - void testPrecompiledContract( + void testPrecompiledContractNative( final String inputString, final String precompiledResult, final Long eip198Gas, final Long eip2565Gas) { + BigIntegerModularExponentiationPrecompiledContract.maybeEnableNative(); + testComputation(inputString, precompiledResult); + } + @ParameterizedTest + @MethodSource("parameters") + void testPrecompiledContractJava( + final String inputString, + final String precompiledResult, + final Long eip198Gas, + final Long eip2565Gas) { + BigIntegerModularExponentiationPrecompiledContract.disableNative(); + testComputation(inputString, precompiledResult); + } + + private void testComputation(final String inputString, final String precompiledResult) { assumeThat(precompiledResult).isNotNull(); final Bytes input = Bytes.fromHexString(inputString); final Bytes expected = Bytes.fromHexString(precompiledResult); @@ -181,4 +197,11 @@ void testGasPrice( assertThat(byzantiumContract.gasRequirement(input)).isEqualTo(eip198Gas); assertThat(berlinContract.gasRequirement(input)).isEqualTo(eip2565Gas); } + + @Test + void dryRunDetector() { + assertThat(true) + .withFailMessage("This test is here so gradle --dry-run executes this class") + .isTrue(); + } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessorTest.java b/evm/src/test/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessorTest.java index 016f3f6da37..6af3026f1f8 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessorTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/processor/AbstractMessageProcessorTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/evm/src/test/java/org/hyperledger/besu/evm/processor/ContractCreationProcessorTest.java b/evm/src/test/java/org/hyperledger/besu/evm/processor/ContractCreationProcessorTest.java index 975cd0e5fa1..3420df88dcd 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/processor/ContractCreationProcessorTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/processor/ContractCreationProcessorTest.java @@ -15,20 +15,20 @@ package org.hyperledger.besu.evm.processor; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.evm.EOFTestConstants.EOF_CREATE_CONTRACT; +import static org.hyperledger.besu.evm.EOFTestConstants.INNER_CONTRACT; import static org.hyperledger.besu.evm.frame.MessageFrame.State.COMPLETED_SUCCESS; import static org.hyperledger.besu.evm.frame.MessageFrame.State.EXCEPTIONAL_HALT; -import static org.mockito.Mockito.when; import org.hyperledger.besu.evm.EVM; import org.hyperledger.besu.evm.EvmSpecVersion; -import org.hyperledger.besu.evm.code.CodeFactory; -import org.hyperledger.besu.evm.contractvalidation.CachedInvalidCodeRule; +import org.hyperledger.besu.evm.MainnetEVMs; import org.hyperledger.besu.evm.contractvalidation.EOFValidationCodeRule; import org.hyperledger.besu.evm.contractvalidation.MaxCodeSizeRule; import org.hyperledger.besu.evm.contractvalidation.PrefixCodeRule; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; import org.hyperledger.besu.evm.tracing.OperationTracer; @@ -38,7 +38,6 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @Nested @@ -46,8 +45,7 @@ class ContractCreationProcessorTest extends AbstractMessageProcessorTest { - @Mock GasCalculator gasCalculator; - @Mock EVM evm; + EVM evm = MainnetEVMs.pragueEOF(EvmConfiguration.DEFAULT); private ContractCreationProcessor processor; @@ -55,18 +53,12 @@ class ContractCreationProcessorTest void shouldThrowAnExceptionWhenCodeContractFormatInvalidPreEOF() { processor = new ContractCreationProcessor( - gasCalculator, - evm, - true, - Collections.singletonList(PrefixCodeRule.of()), - 1, - Collections.emptyList()); + evm, true, Collections.singletonList(PrefixCodeRule.of()), 1, Collections.emptyList()); final Bytes contractCode = Bytes.fromHexString("EF01010101010101"); final MessageFrame messageFrame = new TestMessageFrameBuilder().build(); messageFrame.setOutputData(contractCode); - messageFrame.setGasRemaining(100L); + messageFrame.setGasRemaining(10600L); - when(gasCalculator.codeDepositGasCost(contractCode.size())).thenReturn(10L); processor.codeSuccess(messageFrame, OperationTracer.NO_TRACING); assertThat(messageFrame.getState()).isEqualTo(EXCEPTIONAL_HALT); assertThat(messageFrame.getExceptionalHaltReason()) @@ -77,18 +69,12 @@ void shouldThrowAnExceptionWhenCodeContractFormatInvalidPreEOF() { void shouldNotThrowAnExceptionWhenCodeContractIsValid() { processor = new ContractCreationProcessor( - gasCalculator, - evm, - true, - Collections.singletonList(PrefixCodeRule.of()), - 1, - Collections.emptyList()); + evm, true, Collections.singletonList(PrefixCodeRule.of()), 1, Collections.emptyList()); final Bytes contractCode = Bytes.fromHexString("0101010101010101"); final MessageFrame messageFrame = new TestMessageFrameBuilder().build(); messageFrame.setOutputData(contractCode); - messageFrame.setGasRemaining(100L); + messageFrame.setGasRemaining(10600L); - when(gasCalculator.codeDepositGasCost(contractCode.size())).thenReturn(10L); processor.codeSuccess(messageFrame, OperationTracer.NO_TRACING); assertThat(messageFrame.getState()).isEqualTo(COMPLETED_SUCCESS); } @@ -97,13 +83,12 @@ void shouldNotThrowAnExceptionWhenCodeContractIsValid() { void shouldNotThrowAnExceptionWhenPrefixCodeRuleNotAdded() { processor = new ContractCreationProcessor( - gasCalculator, evm, true, Collections.emptyList(), 1, Collections.emptyList()); + evm, true, Collections.emptyList(), 1, Collections.emptyList()); final Bytes contractCode = Bytes.fromHexString("0F01010101010101"); final MessageFrame messageFrame = new TestMessageFrameBuilder().build(); messageFrame.setOutputData(contractCode); - messageFrame.setGasRemaining(100L); + messageFrame.setGasRemaining(10600L); - when(gasCalculator.codeDepositGasCost(contractCode.size())).thenReturn(10L); processor.codeSuccess(messageFrame, OperationTracer.NO_TRACING); assertThat(messageFrame.getState()).isEqualTo(COMPLETED_SUCCESS); } @@ -112,18 +97,16 @@ void shouldNotThrowAnExceptionWhenPrefixCodeRuleNotAdded() { void shouldThrowAnExceptionWhenCodeContractFormatInvalidPostEOF() { processor = new ContractCreationProcessor( - gasCalculator, evm, true, - Collections.singletonList(EOFValidationCodeRule.of(1, false)), + Collections.singletonList(EOFValidationCodeRule.from(evm)), 1, Collections.emptyList()); final Bytes contractCode = Bytes.fromHexString("EF00010101010101"); final MessageFrame messageFrame = new TestMessageFrameBuilder().build(); messageFrame.setOutputData(contractCode); - messageFrame.setGasRemaining(100L); + messageFrame.setGasRemaining(10600L); - when(gasCalculator.codeDepositGasCost(contractCode.size())).thenReturn(10L); processor.codeSuccess(messageFrame, OperationTracer.NO_TRACING); assertThat(messageFrame.getState()).isEqualTo(EXCEPTIONAL_HALT); assertThat(messageFrame.getExceptionalHaltReason()) @@ -131,21 +114,19 @@ void shouldThrowAnExceptionWhenCodeContractFormatInvalidPostEOF() { } @Test - void eofValidationShouldAllowLegacyCode() { + void eofValidationShouldAllowLegacyDeployFromLegacyInit() { processor = new ContractCreationProcessor( - gasCalculator, evm, true, - Collections.singletonList(EOFValidationCodeRule.of(1, false)), + Collections.singletonList(EOFValidationCodeRule.from(evm)), 1, Collections.emptyList()); final Bytes contractCode = Bytes.fromHexString("0101010101010101"); final MessageFrame messageFrame = new TestMessageFrameBuilder().build(); messageFrame.setOutputData(contractCode); - messageFrame.setGasRemaining(100L); + messageFrame.setGasRemaining(10600L); - when(gasCalculator.codeDepositGasCost(contractCode.size())).thenReturn(10L); processor.codeSuccess(messageFrame, OperationTracer.NO_TRACING); assertThat(messageFrame.getState()).isEqualTo(COMPLETED_SUCCESS); } @@ -154,131 +135,109 @@ void eofValidationShouldAllowLegacyCode() { void eofValidationShouldAllowEOFCode() { processor = new ContractCreationProcessor( - gasCalculator, evm, true, - Collections.singletonList(EOFValidationCodeRule.of(1, false)), + Collections.singletonList(EOFValidationCodeRule.from(evm)), 1, Collections.emptyList()); - final Bytes contractCode = - Bytes.fromHexString( - "0xEF000101000C020003000b000200080300000000000002020100020100000260016002e30001e30002e401e460005360106000f3"); - final MessageFrame messageFrame = new TestMessageFrameBuilder().build(); - messageFrame.setOutputData(contractCode); - messageFrame.setGasRemaining(100L); + final MessageFrame messageFrame = + new TestMessageFrameBuilder().code(evm.getCodeUncached(EOF_CREATE_CONTRACT)).build(); + messageFrame.setOutputData(INNER_CONTRACT); + messageFrame.setGasRemaining(10600L); - when(gasCalculator.codeDepositGasCost(contractCode.size())).thenReturn(10L); processor.codeSuccess(messageFrame, OperationTracer.NO_TRACING); assertThat(messageFrame.getState()).isEqualTo(COMPLETED_SUCCESS); } @Test - void eofValidationShouldPreventLegacyCodeDeployment() { + void prefixValidationShouldPreventEOFCode() { processor = new ContractCreationProcessor( - gasCalculator, - evm, - true, - Collections.singletonList(EOFValidationCodeRule.of(1, false)), - 1, - Collections.emptyList()); - final Bytes contractCode = Bytes.fromHexString("6030602001"); - final Bytes initCode = - Bytes.fromHexString( - "0xEF000101000C020003000b000200080300000000000002020100020100000260016002e30001e30002e401e460005360106000f3"); - final MessageFrame messageFrame = - new TestMessageFrameBuilder().code(CodeFactory.createCode(initCode, 1, true)).build(); - messageFrame.setOutputData(contractCode); - messageFrame.setGasRemaining(100L); + evm, true, Collections.singletonList(PrefixCodeRule.of()), 1, Collections.emptyList()); + final MessageFrame messageFrame = new TestMessageFrameBuilder().build(); + messageFrame.setOutputData(INNER_CONTRACT); + messageFrame.setGasRemaining(10600L); - when(gasCalculator.codeDepositGasCost(contractCode.size())).thenReturn(10L); processor.codeSuccess(messageFrame, OperationTracer.NO_TRACING); assertThat(messageFrame.getState()).isEqualTo(EXCEPTIONAL_HALT); } @Test - void eofValidationPreventsInvalidEOFCode() { + void eofValidationShouldPreventLegacyDeployFromEOFInit() { processor = new ContractCreationProcessor( - gasCalculator, evm, true, - Collections.singletonList(EOFValidationCodeRule.of(1, false)), + Collections.singletonList(EOFValidationCodeRule.from(evm)), 1, Collections.emptyList()); - final Bytes contractCode = - Bytes.fromHexString( - "0xEF000101000C020003000b000200080300000000000000020100020100000260016002b00001b00002b101b160005360106000f3"); - final MessageFrame messageFrame = new TestMessageFrameBuilder().build(); + final Bytes contractCode = Bytes.fromHexString("6030602001"); + final Bytes initCode = EOF_CREATE_CONTRACT; + final MessageFrame messageFrame = + new TestMessageFrameBuilder().code(evm.getCodeForCreation(initCode)).build(); messageFrame.setOutputData(contractCode); - messageFrame.setGasRemaining(100L); + messageFrame.setGasRemaining(10600L); - when(gasCalculator.codeDepositGasCost(contractCode.size())).thenReturn(10L); processor.codeSuccess(messageFrame, OperationTracer.NO_TRACING); assertThat(messageFrame.getState()).isEqualTo(EXCEPTIONAL_HALT); } @Test - void shouldThrowAnExceptionWhenCodeContractTooLarge() { + void eofValidationPreventsEOFDeployFromLegacyInit() { processor = new ContractCreationProcessor( - gasCalculator, evm, true, - Collections.singletonList(MaxCodeSizeRule.of(24 * 1024)), + Collections.singletonList(EOFValidationCodeRule.from(evm)), 1, Collections.emptyList()); - final Bytes contractCode = Bytes.fromHexString("00".repeat(24 * 1024 + 1)); + final Bytes contractCode = EOF_CREATE_CONTRACT; final MessageFrame messageFrame = new TestMessageFrameBuilder().build(); messageFrame.setOutputData(contractCode); - messageFrame.setGasRemaining(100L); + messageFrame.setGasRemaining(10600L); - when(gasCalculator.codeDepositGasCost(contractCode.size())).thenReturn(10L); processor.codeSuccess(messageFrame, OperationTracer.NO_TRACING); assertThat(messageFrame.getState()).isEqualTo(EXCEPTIONAL_HALT); - assertThat(messageFrame.getExceptionalHaltReason()) - .contains(ExceptionalHaltReason.CODE_TOO_LARGE); } @Test - void shouldThrowAnExceptionWhenDeployingInvalidContract() { - EvmSpecVersion evmSpecVersion = EvmSpecVersion.FUTURE_EIPS; + void shouldThrowAnExceptionWhenCodeContractTooLarge() { processor = new ContractCreationProcessor( - gasCalculator, evm, true, - Collections.singletonList(CachedInvalidCodeRule.of(evmSpecVersion)), + Collections.singletonList( + MaxCodeSizeRule.from(EvmSpecVersion.SPURIOUS_DRAGON, EvmConfiguration.DEFAULT)), 1, Collections.emptyList()); - final Bytes contractCreateCode = Bytes.fromHexString("0x67ef0001010001006060005260086018f3"); - final MessageFrame messageFrame = - new TestMessageFrameBuilder() - .code( - CodeFactory.createCode(contractCreateCode, evmSpecVersion.getMaxEofVersion(), true)) - .build(); - messageFrame.setOutputData(Bytes.fromHexString("0xef00010100010060")); + final Bytes contractCode = + Bytes.fromHexString("00".repeat(EvmSpecVersion.SPURIOUS_DRAGON.getMaxCodeSize() + 1)); + final MessageFrame messageFrame = new TestMessageFrameBuilder().build(); + messageFrame.setOutputData(contractCode); + messageFrame.setGasRemaining(10_000_000L); processor.codeSuccess(messageFrame, OperationTracer.NO_TRACING); assertThat(messageFrame.getState()).isEqualTo(EXCEPTIONAL_HALT); + assertThat(messageFrame.getExceptionalHaltReason()) + .contains(ExceptionalHaltReason.CODE_TOO_LARGE); } @Test void shouldNotThrowAnExceptionWhenCodeContractTooLarge() { processor = new ContractCreationProcessor( - gasCalculator, evm, true, - Collections.singletonList(MaxCodeSizeRule.of(24 * 1024)), + Collections.singletonList( + MaxCodeSizeRule.from(EvmSpecVersion.SPURIOUS_DRAGON, EvmConfiguration.DEFAULT)), 1, Collections.emptyList()); - final Bytes contractCode = Bytes.fromHexString("00".repeat(24 * 1024)); + final Bytes contractCode = + Bytes.fromHexString("00".repeat(EvmSpecVersion.SPURIOUS_DRAGON.getMaxCodeSize())); final MessageFrame messageFrame = new TestMessageFrameBuilder().build(); messageFrame.setOutputData(contractCode); - messageFrame.setGasRemaining(100L); + messageFrame.setGasRemaining(5_000_000L); - when(gasCalculator.codeDepositGasCost(contractCode.size())).thenReturn(10L); processor.codeSuccess(messageFrame, OperationTracer.NO_TRACING); assertThat(messageFrame.getState()).isEqualTo(COMPLETED_SUCCESS); } @@ -287,13 +246,12 @@ void shouldNotThrowAnExceptionWhenCodeContractTooLarge() { void shouldNotThrowAnExceptionWhenCodeSizeRuleNotAdded() { processor = new ContractCreationProcessor( - gasCalculator, evm, true, Collections.emptyList(), 1, Collections.emptyList()); + evm, true, Collections.emptyList(), 1, Collections.emptyList()); final Bytes contractCode = Bytes.fromHexString("00".repeat(24 * 1024 + 1)); final MessageFrame messageFrame = new TestMessageFrameBuilder().build(); messageFrame.setOutputData(contractCode); - messageFrame.setGasRemaining(100L); + messageFrame.setGasRemaining(5_000_000L); - when(gasCalculator.codeDepositGasCost(contractCode.size())).thenReturn(10L); processor.codeSuccess(messageFrame, OperationTracer.NO_TRACING); assertThat(messageFrame.getState()).isEqualTo(COMPLETED_SUCCESS); } @@ -301,6 +259,6 @@ void shouldNotThrowAnExceptionWhenCodeSizeRuleNotAdded() { @Override protected ContractCreationProcessor getAbstractMessageProcessor() { return new ContractCreationProcessor( - gasCalculator, evm, true, Collections.emptyList(), 1, Collections.emptyList()); + evm, true, Collections.emptyList(), 1, Collections.emptyList()); } } diff --git a/evm/src/test/java/org/hyperledger/besu/evm/processor/MessageCallProcessorTest.java b/evm/src/test/java/org/hyperledger/besu/evm/processor/MessageCallProcessorTest.java index 9d593c97b38..d17744c8580 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/processor/MessageCallProcessorTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/processor/MessageCallProcessorTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,10 +14,23 @@ */ package org.hyperledger.besu.evm.processor; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.precompile.PrecompileContractRegistry; +import org.hyperledger.besu.evm.precompile.PrecompiledContract; +import org.hyperledger.besu.evm.testutils.TestMessageFrameBuilder; +import org.hyperledger.besu.evm.toy.ToyWorld; import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -28,9 +41,62 @@ class MessageCallProcessorTest extends AbstractMessageProcessorTest + readBigEndianI16(invocationOnMock.getArgument(0), codeBytes.toArrayUnsafe())); + when(mockCode.readBigEndianU16(anyInt())) + .thenAnswer( + invocationOnMock -> + readBigEndianU16(invocationOnMock.getArgument(0), codeBytes.toArrayUnsafe())); + when(mockCode.readU8(anyInt())) + .thenAnswer( + invocationOnMock -> + codeBytes.toArrayUnsafe()[(int) invocationOnMock.getArgument(0)] & 0xff); + return mockCode; + } +} diff --git a/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestMessageFrameBuilder.java b/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestMessageFrameBuilder.java index f6b2abd69d9..801fcae8e2d 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestMessageFrameBuilder.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/testutils/TestMessageFrameBuilder.java @@ -24,13 +24,13 @@ import org.hyperledger.besu.evm.frame.BlockValues; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.internal.Words; +import org.hyperledger.besu.evm.operation.BlockHashOperation.BlockHashLookup; import org.hyperledger.besu.evm.toy.ToyWorld; import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.util.ArrayList; import java.util.List; import java.util.Optional; -import java.util.function.Function; import org.apache.tuweni.bytes.Bytes; @@ -54,8 +54,9 @@ public class TestMessageFrameBuilder { private int pc = 0; private int section = 0; private final List stackItems = new ArrayList<>(); - private Optional> blockHashLookup = Optional.empty(); + private Optional blockHashLookup = Optional.empty(); private Bytes memory = Bytes.EMPTY; + private boolean isStatic = false; public TestMessageFrameBuilder worldUpdater(final WorldUpdater worldUpdater) { this.worldUpdater = Optional.of(worldUpdater); @@ -102,7 +103,7 @@ public TestMessageFrameBuilder value(final Wei value) { return this; } - TestMessageFrameBuilder inputData(final Bytes inputData) { + public TestMessageFrameBuilder inputData(final Bytes inputData) { this.inputData = inputData; return this; } @@ -132,7 +133,7 @@ public TestMessageFrameBuilder pushStackItem(final Bytes item) { return this; } - public TestMessageFrameBuilder blockHashLookup(final Function blockHashLookup) { + public TestMessageFrameBuilder blockHashLookup(final BlockHashLookup blockHashLookup) { this.blockHashLookup = Optional.of(blockHashLookup); return this; } @@ -142,6 +143,11 @@ public TestMessageFrameBuilder memory(final Bytes memory) { return this; } + public TestMessageFrameBuilder isStatic(final boolean isStatic) { + this.isStatic = isStatic; + return this; + } + public MessageFrame build() { final MessageFrame frame = MessageFrame.builder() @@ -163,6 +169,7 @@ public MessageFrame build() { .miningBeneficiary(Address.ZERO) .blockHashLookup(blockHashLookup.orElse(number -> Hash.hash(Words.longBytes(number)))) .maxStackSize(maxStackSize) + .isStatic(isStatic) .build(); frame.setPC(pc); frame.setSection(section); diff --git a/evm/src/test/java/org/hyperledger/besu/evm/toy/EvmToy.java b/evm/src/test/java/org/hyperledger/besu/evm/toy/EvmToy.java index 58957758315..851afab807f 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/toy/EvmToy.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/toy/EvmToy.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.toy; diff --git a/evm/src/test/java/org/hyperledger/besu/evm/toy/EvmToyCommand.java b/evm/src/test/java/org/hyperledger/besu/evm/toy/EvmToyCommand.java index 54060c9f341..fcf26c3c569 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/toy/EvmToyCommand.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/toy/EvmToyCommand.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.toy; @@ -191,12 +190,11 @@ public void run() { .blockValues(new ToyBlockValues()) .completer(c -> {}) .miningBeneficiary(Address.ZERO) - .blockHashLookup(h -> null) + .blockHashLookup(n -> null) .build(); final MessageCallProcessor mcp = new MessageCallProcessor(evm, precompileContractRegistry); - final ContractCreationProcessor ccp = - new ContractCreationProcessor(evm.getGasCalculator(), evm, false, List.of(), 0); + final ContractCreationProcessor ccp = new ContractCreationProcessor(evm, false, List.of(), 0); stopwatch.start(); Deque messageFrameStack = initialMessageFrame.getMessageFrameStack(); while (!messageFrameStack.isEmpty()) { diff --git a/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyAccount.java b/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyAccount.java index 64e2b7ae07e..4dd3018a552 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyAccount.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyAccount.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.toy; @@ -164,6 +163,20 @@ public Map getUpdatedStorage() { return storage; } + /** + * Does this account have any storage slots that are set to non-zero values? + * + * @return true if the account has no storage values set to non-zero values. False if any storage + * is set. + */ + @Override + public boolean isStorageEmpty() { + if (storage.isEmpty()) { + return parent == null || parent.isStorageEmpty(); + } + return false; + } + @Override public void becomeImmutable() { immutable = true; diff --git a/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyBlockValues.java b/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyBlockValues.java index 300014d3878..9f347084d85 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyBlockValues.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyBlockValues.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.toy; diff --git a/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyWorld.java b/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyWorld.java index 06aa833bcd2..e1e9c4c3ad3 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyWorld.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/toy/ToyWorld.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.evm.toy; diff --git a/evm/src/test/java/org/hyperledger/besu/evm/tracing/ExtendedOperationTracerTest.java b/evm/src/test/java/org/hyperledger/besu/evm/tracing/ExtendedOperationTracerTest.java index 9b57bd938f4..bd043f933b5 100644 --- a/evm/src/test/java/org/hyperledger/besu/evm/tracing/ExtendedOperationTracerTest.java +++ b/evm/src/test/java/org/hyperledger/besu/evm/tracing/ExtendedOperationTracerTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -16,14 +16,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.when; import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.MainnetEVMs; import org.hyperledger.besu.evm.account.MutableAccount; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; -import org.hyperledger.besu.evm.gascalculator.GasCalculator; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.evm.processor.ContractCreationProcessor; import org.hyperledger.besu.evm.worldstate.WorldUpdater; @@ -40,8 +40,6 @@ @ExtendWith(MockitoExtension.class) class ExtendedOperationTracerTest { - @Mock GasCalculator gasCalculator; - @Mock EVM evm; @Mock MessageFrame frame; @Mock WorldUpdater worldUpdater; @Mock MutableAccount mutableAccount; @@ -49,7 +47,6 @@ class ExtendedOperationTracerTest { @BeforeEach void setUp() { when(frame.getOutputData()).thenReturn(Bytes.EMPTY); - when(gasCalculator.codeDepositGasCost(anyInt())).thenReturn(0L); when(frame.getRemainingGas()).thenReturn(1L); when(frame.getWorldUpdater()).thenReturn(worldUpdater); @@ -58,8 +55,9 @@ void setUp() { @Test void shouldCallTraceAccountCreationResultIfIsExtendedTracing() { + EVM evm = MainnetEVMs.pragueEOF(EvmConfiguration.DEFAULT); final ContractCreationProcessor contractCreationProcessor = - new ContractCreationProcessor(gasCalculator, evm, false, Collections.emptyList(), 0); + new ContractCreationProcessor(evm, false, Collections.emptyList(), 0); final ExtendedOperationTracer tracer = new ExtendedOperationTracer(); contractCreationProcessor.codeSuccess(frame, tracer); @@ -71,8 +69,9 @@ void shouldCallTraceAccountCreationResultIfIsExtendedTracing() { @Test void shouldNotCallTraceAccountCreationResultIfIsNotExtendedTracing() { + EVM evm = MainnetEVMs.pragueEOF(EvmConfiguration.DEFAULT); final ContractCreationProcessor contractCreationProcessor = - new ContractCreationProcessor(gasCalculator, evm, false, Collections.emptyList(), 0); + new ContractCreationProcessor(evm, false, Collections.emptyList(), 0); final DefaultOperationTracer tracer = new DefaultOperationTracer(); contractCreationProcessor.codeSuccess(frame, tracer); diff --git a/evm/src/test/resources/org/hyperledger/besu/evm/precompile/fp2_to_g2.csv b/evm/src/test/resources/org/hyperledger/besu/evm/precompile/fp2_to_g2.csv index 745f94a70e3..7b2ed8fbdd4 100644 --- a/evm/src/test/resources/org/hyperledger/besu/evm/precompile/fp2_to_g2.csv +++ b/evm/src/test/resources/org/hyperledger/besu/evm/precompile/fp2_to_g2.csv @@ -1,103 +1,103 @@ input,result,gas,notes -0000000000000000000000000000000014406e5bfb9209256a3820879a29ac2f62d6aca82324bf3ae2aa7d3c54792043bd8c791fccdb080c1a52dc68b8b69350000000000000000000000000000000000e885bb33996e12f07da69073e2c0cc880bc8eff26d2a724299eb12d54f4bcf26f4748bb020e80a7e3794a7b0e47a641,000000000000000000000000000000000d029393d3a13ff5b26fe52bd8953768946c5510f9441f1136f1e938957882db6adbd7504177ee49281ecccba596f2bf000000000000000000000000000000001993f668fb1ae603aefbb1323000033fcb3b65d8ed3bf09c84c61e27704b745f540299a1872cd697ae45a5afd780f1d600000000000000000000000000000000079cb41060ef7a128d286c9ef8638689a49ca19da8672ea5c47b6ba6dbde193ee835d3b87a76a689966037c07159c10d0000000000000000000000000000000017c688ae9a8b59a7069c27f2d58dd2196cb414f4fb89da8510518a1142ab19d158badd1c3bad03408fafb1669903cd6c,110000, -000000000000000000000000000000000ba1b6d79150bdc368a14157ebfe8b5f691cf657a6bbe30e79b6654691136577d2ef1b36bfb232e3336e7e4c9352a8ed000000000000000000000000000000000f12847f7787f439575031bcdb1f03cfb79f942f3a9709306e4bd5afc73d3f78fd1c1fef913f503c8cbab58453fb7df2,000000000000000000000000000000000a2bca68ca23f3f03c678140d87465b5b336dbd50926d1219fcc0def162280765fe1093c117d52483d3d8cdc7ab76529000000000000000000000000000000000fe83e3a958d6038569da6132bfa19f0e3dae3bee0d8a60e7cc33e4d7084a9e8c32fe31ec6e617277e2e450699eba1f80000000000000000000000000000000005602683f0ef231cc0b7c8c695765d7933f4efa7503ed9f2aa3c774284eabcdd32fd287b6a3539c9749f2e15b58f5cd50000000000000000000000000000000000b4f17de0db6e9d081723b613b23864c1eeae91b7cbda40ecd24823022aee7fc4068adc41947b97e17009fad9d0d4de,110000, -000000000000000000000000000000001632336631a3c666159b6e5e1fb62ffa21488e571cffb7bc3d75d55a837f242e789a75f0f583ce2b3a969c64c2b46de200000000000000000000000000000000184f1db9ac0fdd6b5ac0307e203d0b4237a50554eb7af37bb1894d9769609c96c8437e9d6d3679ebd5f979eb04035799,00000000000000000000000000000000184af3f8a359dd35dddd3dfcc6f5b55ed327907ed573378289209569244e3c9c02bdf278eb567186f8b64de380c115360000000000000000000000000000000012f5ba8e520c4730ac1fb75dabbfdc0181855e5ba2968a8c0ba36a47ab86ac45d19aa3d55f15a601e120be1f75eefe240000000000000000000000000000000004e313db704b103c2c1e3a58f8e95a470e7199081eb086e9524583131714c4a3db551fd51a3f2314a19a658e7b1765380000000000000000000000000000000004040eab7416a1703b0d103120506f1de2b26b0f48c7a0ea63dca4d9ad1c478ae03b5d7bfd51f4cd6f8cea26212c4edf,110000, -000000000000000000000000000000000732f171d8f6e283dd40a0324dae42ef0209c4caa0bd8ce2b12b206b6a9704f2c6015c918c79f0625fa791051b05c55c000000000000000000000000000000001139e8d932fc0ab10d6d4f6874c757c545b15be27cdb88056ed7c690aa6d924226d83e66b3e2484b2fc3dcd14418ee60,0000000000000000000000000000000017fc341e495bf4ef5da4c159a28320aca97ca28fe3a0441242cf506b0f89bb52f5b5d8c6e038d229ffe67d00151912f00000000000000000000000000000000007666300b7be3d904ae3d19019f7be5cf5ba6161b969c1a78aff639a24387d8fdcc4d0e3cd81ba6f063ebf2d859370f20000000000000000000000000000000007cc705dbfb5c0418beb1cfbd864fa0631bd60eccfdb16b5d55b6ef3558e2ec87dac3b45294dcf04a064d6d1eba5a6eb00000000000000000000000000000000052cb9c982e6b05c1d2ab4eed1d8082f96426b55615ebc6a53bdc320ccad0aad044395ed641b3176b554f19e62d46b73,110000, -0000000000000000000000000000000019a9630cce5181fd0ad80677ed5ad8cd8bce3f284cd529175902b78ad4915f0df56f0d8b37c87c9ddb23d0342005f1570000000000000000000000000000000002cdd00b7662569c9f74553a7d0585312a776c8638e54ad016f8d9d25df98651789470b12ce2626fb3ad1373744387ac,0000000000000000000000000000000015ad9155037e03898cb3b706f7105e39d413ff3a5abb65812b8d21d003cab8fbb607d3938ccd6a774bc8debfa30f42760000000000000000000000000000000019d6382bb2d78180a8998a0536d67412d00ec0ef65f4cbce01340b8d6e781c0ff790296f8cada28966b147c69e02f366000000000000000000000000000000001290c2c205b748069d0875a89ca74a3b05ad8218ed46a1570696932302983c090d96e17e0b828a666fdfc3b72cd348bc000000000000000000000000000000000114f2f7ffaa9f90b547e86c863a5d3585819a78b095848dfa39576a10874a905488687b73e613f3d426510f5d1d1ce1,110000, -000000000000000000000000000000000e63c4d12a38837354bbcdf4f844e5dfe727ebe292016748007d162e74c1f849787767f7e77fc57a42783fe0b06c24c80000000000000000000000000000000008d879e4891a891f2e7d27eb95aef70d5b785b796620ec43dfbb6ae550b4effb9f24210dc20f401d54420445e21cfdd3,0000000000000000000000000000000012084a53cde353a46af17cd2fb02c477e47b874d8ff58025b5015837759032ff98013dc5bf01253bb964f035183c9071000000000000000000000000000000001659272ab7e3a070a5c7b25a5d3402f7371ed67e58cac8438df41c39c1acd95ac5886b030384bf537d7c4bb8ddb2c538000000000000000000000000000000000852ddcc37a09a0a8f62dfbd1ba5064c1f6afacc9a279a4d998bed643eec5a0d96d6bad95701a04f52c83e8f87f48d5d00000000000000000000000000000000097a399370875398028d42bde8cf4e9641730af7a2971e2f59c95938120603a239c65030ded4323c955f7fd24bebf31b,110000, -00000000000000000000000000000000028d6de947a3958af5b53578b0ceacc7ef89d36526d8f3b6fbe787af69fed2c85cad3001643b81c575a741c4566e617e00000000000000000000000000000000182b56202f0494bd8baf5c03969288a1288b8ed8e6c7f49ec9f7493ee3369eeb42fa8f5fb7b243fb2bcee6be244f02be,0000000000000000000000000000000006f8191123f1e8f6a05e4e663fa763c8a0ade5de3c7cd38ec1c82e1c85f123ab51fffcebd677afec8e9adecd8d11263d0000000000000000000000000000000004fcd825bc55d044eb70e0bdd5ea2ac58ec1487e903b431c57a640c756265a382581b8450fb15dc649cf22a8539088220000000000000000000000000000000015259f83d76490bb868bb88c2a2c3e07a326bd3e97fc2f552adf85722a360a443d720c328076e35224328e09494746e0000000000000000000000000000000000f76b0b960a1343b4267f5aff44901fd6796a778b1a87666b95b773edd0e7ffb6656d4f0cc3b9b38bc6c0ed20cfce153,110000, -0000000000000000000000000000000016adb5935f32bafcccb81cf4d177dd8826013d85e11a4aad66e3aa596e1183aeb9d68eb8cf5b716a8a9445ea81b40d7a0000000000000000000000000000000018bee24b0c97af8aec210f15bbb6acbb76168dabe16e669d5558d8d32f00fdf5146471922fa98a28f238974d327996a3,0000000000000000000000000000000018bf5f93dbc2c37479b819f8edccd687c4d3c4dd04f8c73762fd89d0c003674e3b2ed749d23e775f925279b3112689f80000000000000000000000000000000008a033b197aa8ea2213dbd7ed478d98c25dc6e9f91b9924f3c14124da26a67bb196926e02da89b746f2a67b14ad226070000000000000000000000000000000006f7824bdc9c53212609512858278f79d9b094165ff178e3da8776e24311bebbd9deb29f366d4c7693a15c34df118403000000000000000000000000000000000edde25fc24b9ec58b3c317aa3ae48dd5fecdf6397ed9636ea042722d264db0b1a89a15a1e16e892755730ef52796527,110000, -00000000000000000000000000000000114285411713eafd395ee43bf1728f52d17ac512b9d0cddd38c904a9a3a1b30283a3918cd2cc3da6a7d6b4ff923cbb6e0000000000000000000000000000000018a067f91f94b2904c5bb6900f427ec4e93374b5079c84707feabeabde20b5e49801f1f3c7504dd27da94d5e754df4ad,0000000000000000000000000000000002d28025f4b798083aec3ca9a91a051ce27a374b115c944932026b4fe0dcf68b335d5e47212f800c241c2d42fd219635000000000000000000000000000000001742fb6ef8e9a5a7572b0d3fa4ae8ae56c9c6f4daa20d0b88212c40511c6f6b5ee98314a2d1cbe4bbbec907495a1ade8000000000000000000000000000000000d700a511a58c1b8f11153669cb21d88512dfdacbabe38e402431b4f7ba374b5f9a88614da2d56799d39324e9d19e27a000000000000000000000000000000000c6068bc7a43d614b8f1132b13e04f66d2fb5ac0c5bc8501b754a0bcf4f382db92b0994c4999e104c9d1111ef91d5edc,110000, -000000000000000000000000000000000dafa9fa843879038fd1566c319c24119989090c5fd34f6514e57f633d3709f0aa9954dfb289843a6990588e337b63e6000000000000000000000000000000001742a98dd7d3671c2c64aa71023a0040e936fd726c062d520626113bed471e53ff3e85737e5abf9ee8821bae53135f20,000000000000000000000000000000001350c68434a9b02392e60540a3985bae8daf9a170b30336ac73afae6f892c7ae8f5f1cadfb2780d6e5961ebf91cd69ee0000000000000000000000000000000000c20bd286fc1886b9b28dfa40d1a27395cf76a8b73946849ea0a7b5e12530de13c16acef8fe2a2c247ea65ca023eed70000000000000000000000000000000002d8ffd0235fb60fa573662034d46260e0c96396537b2a9d486dd03bdd13c5a1efd2d3cb9849ed11c4376b665f378226000000000000000000000000000000000d90ca1b73a6a9566832f9f19d8530a3b12f22bef853fc44088559b923ca108cebf4291e0d7de8f25c7429d455f5ae46,110000, -0000000000000000000000000000000019cda532e5d94f3b193b3f286a038637a736c2b87b804efd4779359db5bd95320e06d6d28da3c229ae48ffc02303fab10000000000000000000000000000000018df89e4a545bfb825bcce2f4c25f2416a72e32633b3dead5205c8b7d69c78f119d0e940e5bde9ae1cf91574e5d6c175,0000000000000000000000000000000013f223602e8d12c3bb51cd393f6f59beb5c55fe80c3fc8fb0bc90eca533d9b7981563a30ebd727ab6cf0111fa2d3099d000000000000000000000000000000000962b0585c681894cb701f17ec06c0c240899db574c02d82d85ed4dabd4b8654c29b84c71d2921986fc2abc542a3ed9f0000000000000000000000000000000000f0e79245e645a6e3fb88b9103ede3e6ecdd7e45d61b5755d7a8d100d80719746af58bb23d3068cee7389b2acf17f8b0000000000000000000000000000000017fa0aac84c58283f34b9bf713cde98c175b38e92503c08205350822d778f3dd5bed8051e185c495831a628aa89335c7,110000, -0000000000000000000000000000000008ad60829ff001404da40923806e496640a90c5c258a41ef5912f8a1a20eab84ce43b2b5aa4aa7dc4d8b281591d235020000000000000000000000000000000000f13dfef4b3b83aa7f9525eae9913e10502e77c03c55a7aa2de083dc5102c098b6f8e36cb5247b827e30fbcded9e2d3,000000000000000000000000000000001062c97c214b86518660c5e1c33a4e48923ae89ab7d8bc5c798e631de16fc1f104aa957d3e7915aee8551e24aaafc8e6000000000000000000000000000000000e42b785f17f25b87a0dc558a8d57b19d8f41767c3b4fd70c147e95443aff2d9a743003da41d578a2b56d7dc748cf59500000000000000000000000000000000111fd38cd2f5f681bb37f6239a5eea820ce3f01023c685f8e7e244fe9aa9dcbd18f0e50705faa5d8d66b28af9f371c630000000000000000000000000000000004726d3e452f6fcb180ce1d50bbee3a23f7949b635a058f12de1cf5abda19c042168feea53211dbed0bfca489a020930,110000, -0000000000000000000000000000000010468e5421a72ec85b63f7f3070a949223105763868111424fd151c8365eb0307dbc9cbc92e5dfb296d06ddfb58d99000000000000000000000000000000000008149ce856d489050ea834452bc66f7f3478c2056969354dca8652f3d0a349e40fae0c4c57ff0f5e022aa93c61f8c844,000000000000000000000000000000001211bb8d3bf65b60efc7237ffecddb4e7e2f0dd36e2a704dfc9f4972897addff1a57182f8e0a0ac08c9af2c98eaa4c560000000000000000000000000000000007e9877280aad45a3b1453b6771ab509e4f53937cc6da73d3add50aff94869b27f49218fb479fe19a6176b9aadd36e35000000000000000000000000000000000ff915801695a281f6642751be77155a813847ae0237d77d2edf836aebac02b659b98d49842d4d10e82d9d146e63a3da000000000000000000000000000000000fae1c8c01a2dd94f17c660353d158ff6f3eed4e6375f1e414ade9d6fd040a48e3ff0d558c882e92e74bd6ef4ab06168,110000, -0000000000000000000000000000000006295de7bfec61f06a56fe09afbb74be968329e88ba2e87afffe9ea9bf646ff5b4a03d6088e87644958ced95eceeea08000000000000000000000000000000001443e61dbf14b6c6ed99e1917ecfbe5a4a23ab9bdd3bb089fbba76d795d715d9d2e3c7d8db0b7a9434ad691b68bad3b2,000000000000000000000000000000000dd00d9f31cb5148048125668286c1790cb7294e740df978ac0bdaa6e1c4ba139a04f5770b194c9bcfb123d9b40b6acb00000000000000000000000000000000085d5f4cb831720fa13cef25464a1ba7af33abcc4079d2c5736a219ad9649ebb5dbb8687a2d3952390866587d7088f72000000000000000000000000000000000de377d773e40e1c76e218b969297d15f7819c525ce39aee5114e8405bd7361116682cf9d673574d415a7016b23b567d0000000000000000000000000000000018db26c2097f72b8788ef5aad2d7aa400627e224924afea1ac7c7a6b5cff4a55255e218572614519a536eaaf0f65533c,110000, -000000000000000000000000000000000b14b12ecaa94f9656be54772be9b22a2495d4ff873b0bb971c27ab1d8b940c84cabcf921f6f75e93942c38cddeb87500000000000000000000000000000000019eca0daafbfdcd3b56be863dceb21e624b22c0d376fb92ba606456ce3825981713b88e40b7fd801e915f97d5c29ba75,000000000000000000000000000000001853b4c4e6fcdbed29c5d3aa4a9f6d447adc512f66a32fdef06c6ad316c42eb3ca47ffe6f21318ad610d0a68673d7bc300000000000000000000000000000000123d15c37fa8b1a95229e28500c9a767e6286b780138dcff2714bf1f8242f39bebb7d86e2811551914719ca90fb5615f000000000000000000000000000000000537498c2ec64b2ba58aa0a858b69990cac544d5cac29abdf6a42ae9c04061f83580b79c2a6104ebc55939d9a2bc5ae2000000000000000000000000000000000b348c19aad3b67c690512f372d995555ee38bffcdaf33bb827160d6929d2ce598523880f6136f11e1d6482a654cb016,110000, -00000000000000000000000000000000104a452343a4098e9bf07380a8e52050259da95f5fc88f31511a08090bda85f0a08d49cef95bd26c7181aa3eb0be122200000000000000000000000000000000012400aaec3d2f4a1a8cf3f28fd396133c3999c074a565c110354472ae29479b9b62ab67128521c2c6ec4869811ba760,000000000000000000000000000000000994e7b6ccafc996f672c42ab491105ffe1482e65aeb456de2213b531889773ad4d5e6ea1687d6a1f13e74878766f11e000000000000000000000000000000000b89030486a1d622c97970ee7da6189ac341b9cafbb4081463f579ab8b4b049c6e6c8b63157455770a79108424a14f24000000000000000000000000000000000ded43800a991f8c37282d803a39941d3bfbfbdc56dbf7500ef3d16750b27dcb1ad93f89714395fd3dffe318c1771375000000000000000000000000000000001994144b032e1f8c4d688754eef82cdba0018ac47030fcb77e8fd920e0b0336255d2cc8376c03e1074f91269cd2519d1,110000, -00000000000000000000000000000000093e04bfcbd77bc6bafeb77f02d0f794a20b155435ee3af1d667c025e7645d9387abe0ef281386339f461352da93fbe2000000000000000000000000000000000481ffec570d4e155ec10e0cc58effe7a5651795d604cfda6cdbf011676772fdce2c25227e7d5a1a26748d15b1668091,00000000000000000000000000000000195d99406baadc7d8740962cbbf4bc1f22b08eafb52f3cb3c588b6cb3cd89d16cb7b8d388563289f5b5ea466128525c80000000000000000000000000000000004809f70463633595dd763d658354df4f9b409911e1a0328fdaf486d76ffb410d7c6cfcc2d48fd6757d5c2a4834f81fd000000000000000000000000000000000654f8475562098a2cb27ce224674a383283cde35173e1c16b141998b641ac9ee663d766f045451a7f6d600973f0ec520000000000000000000000000000000013bac451a44982c7b1aaac7522dab598cb79b9a3dab77f4d5a4c1c97c154451499979af1f86ced8ce2099bccd400420d,110000, -0000000000000000000000000000000013a3c5dd40f7d7fbba7563331917fe19a093d5d25ae7993200c39460e0c46d839e3958b672b4ed195300f398137faa18000000000000000000000000000000000255bc4d313fbd61a270dce8c851f1fa09e6ac5dff9b9e8dfc8a236a1d44548cb079023ee9b8f0f5756b39e44489c3f1,0000000000000000000000000000000016ea88d0bce32981f489438df1bc14e7ade7a45d449ee1ac1a041c1204460cf53ae5c0e111914d8af9e6b3b7fa394484000000000000000000000000000000000db571ca6a55bc8285421553a373048f7877ecb9683d52acf07d48e1026795993e4e7177490921bc6fe1e63d69c2de3c0000000000000000000000000000000011602919de1df6cc0dd36a59c84ebb8e209056534e336f5074c9ae5323f8a03b123dc6354cf85301d838b16518ab64390000000000000000000000000000000004407d30fbd632fd493055bd4d8cbed337767a2ac534411a3eabec570ba41d2ad28ef37512a7da3611ad60b6536b3f07,110000, -000000000000000000000000000000000ab7b4dec955de92224b234c2d8bb2e3881806c2d36a9a21036e9412f0a8d3946027cbb65b5dd9c975e01b3f235b883f000000000000000000000000000000000ffbb55002d9e926b3d8e7d963ece82c14afaca8b4d8415df8f964a39db606ac99f9e442ff69f7ddbbc4ae563b836192,000000000000000000000000000000000c1e7b188697aa9a053f14e2d907f2c61a59e0b0c72f9cce30faf81dc714a50113500ca9bc3af6657a5d214f52c90616000000000000000000000000000000001544c35d712eaf79d8dd5a22fbab72f8a6843728898412a7f305b205f8a50e03c6c462b87b3ac165e9e6428e0a44a74a00000000000000000000000000000000029ebafd90a1a887669fd0ace762a66bca2bf0a216333b0ac97dedb6bff3dda2bca1e3d0ed5fa9081c2887fe6a8e24cf000000000000000000000000000000000e1a01ca93ed268e0291a937483f7f8e252c91f9bd8bde55271b0c97fcbbb9219009514217dd8bd7e0267f44e9927a93,110000, -00000000000000000000000000000000103469c08562f6f72152db58b48811b0098b68af8de00e652bd5a67246459664cc8c54e15705d702d51e3f1d8ff76a7700000000000000000000000000000000059b326dd567fb2f8a6ae87f41fb22b3edc25122138a5f6732edb48ed7fa1949eda6144297f54faf406d873a016a1510,0000000000000000000000000000000004e8ad9838e7e269cddf0ae5c8f0f57e7467e0b6f2b9e37e7c4bcae965e9582dc46c9c50aa01f5dc761bf2f1ad311eec0000000000000000000000000000000011b1438ccc668900914578c3ec6e1334d0823861c892608817498fe2e538deec73e0034a6e8ba9790f63fdd95af3714a0000000000000000000000000000000005b4c88196425d3ecd22bfc0cb1a95488493f85bb74f50315f0ffcdd57ad2de23c137cd6d2f6f6dca8af2e3f7bb0539c0000000000000000000000000000000017066344a0f345ecf6a2ba66c37ccbce26a3f551524f74636d4c4812bf5adfabffb0645b898b10c332e94e5f2ae2d1c2,110000, -000000000000000000000000000000000bd594d2f5e1472f85bfd550df3eb948085781459eb3037fab34186ad9a0204a0767c8fba571af858a054dc231931b8000000000000000000000000000000000087b8398406c1e707fe87a16118e2448d6a5f4fd1d6c9d7174c4d8a4314fc7b2c21f04178533480976dd20e28b278ad5,0000000000000000000000000000000010d393bf893d589c578df58f4d0098ad3cd10d3a1d0f112f51b132a369e68c0284a6b70a5673383ae24a27a9043b16cf0000000000000000000000000000000003402afb77b187b45906d9cce348976ed88c758d75b9962a53352a6c3ee37751a9928097c0d68c6f8a315def4ca875200000000000000000000000000000000019b98631e53a3ffda3fb9165ef7236dad5c0c8d57c3315617cbd3ce77430bd89b9e1d88a019042cae0075594514a5e67000000000000000000000000000000001783bf1c9b0ec44c9191dab01ef5bda0cb2f533dbcd3aeac2b7c6720dbc8e3f770a215ec8ea2035129711ce4b448ba87,110000, -000000000000000000000000000000000673dface7041c3d7503ce4a50af946d344ad48327b515740b45276403d91bf1ef9deba79c8ffa0126be990b62bf3072000000000000000000000000000000000adb42b7eb0f6759a04da7b933bbc2b6aedde47da8571d6fa32268c606dbafcbc810844017eb6377493a12d76ca56c03,00000000000000000000000000000000086ac901098212acd091d9c4d42a1318c3b343480f1130d6e52128d61df9e19fb61ef1ff35de0ef60062cd99202910ff0000000000000000000000000000000019109b7292f1a420f09a56dce9694cb4944808a2ce9f1964cbb6ffd14a710c35abe81300090ffcd9e95f33e0de9f879a0000000000000000000000000000000012660c4e114a215390c6f6eabc4bd6e3d062ee28d0c87e24351c7d43195253cb7b5bcfed2b4abb2fdeb3ac04ee228997000000000000000000000000000000000e56d35a7e40a86ffd2088c81488265ecc4468d6cf02d563c91611cdf8b4333cf66ef50b993fe651b1792d2b242cff94,110000, -000000000000000000000000000000000f554e52c4a6c5a94fd09c617f57e8f87af57e73ceaee8997fc62c8ddcb2f875ee805e6594a0fb72738abd3cd4748ddb000000000000000000000000000000001876dd03316ff007a2efb4c5f452d8418edacc2881b20e8340895f6fc768d14fd89bd9db3dcfb53fa98a1e96055fa83e,00000000000000000000000000000000071d3e796fb15d63c2d5cf68f59f11792b0b580b85c8839a02fad96664f14735ede2edfd5ba5b64045b366904f54ab600000000000000000000000000000000013fd1ea38d32772458622731b9e2d9d749f2b747443f7e47ef5e041531b56f86d1775d42a548b2bb201228f49ec9f46800000000000000000000000000000000099c2bd996c8c5ee37de971e8b75a0bdd4f69299778ee3d216973c9dbba97c7a93e40b209d390024bc4b5e82560a1a83000000000000000000000000000000000c4922ed9af845467440b78efa3a53ba904f29adf66e8ac437c8bb6624b5e5ba0772a5639b45fe167b1fb9283747c50f,110000, -000000000000000000000000000000000e8b2369fc2c584d78d52037b109aecc87dea0eefc2da46948b5535ad19c9abdb31aee66739f4852a2d3c51f2e7f74e900000000000000000000000000000000168b2d3e4b67390cb8ba5e48a7a823db08edee7d8eff41b88cd653cec1fc0df7a55303d3c91e92a2dc8ebdb327b225fe,000000000000000000000000000000000e413d72fdc3db6fc79ef26ae8b37fe5c4356a80b3598513b5173b3406ffb54708b8794dae158060a1accbe956a39ff30000000000000000000000000000000019ba9dfa74fd241a55a3b47c9f37c6ebd1e8b51f46197881abb64b7f57c0e2d8f18edee35bb9da03702c0dc5cc8749f700000000000000000000000000000000183525156fbc80cc67d6cd15fd2ddf7fb0528656ec1d31b4c275ef101dbb635424abbff1154a3ee04346ac53148fb1f70000000000000000000000000000000011da0dcd666d01180902d8a7fd7d2fbb39f9c7587540451045956108a8579d7c116385a81627dad9d4cb8cfe68927b6d,110000, -0000000000000000000000000000000016cf7b1a9ebafbd20c078948fc974bcca9b8069edc1ca5e8f364f8ca2a52e56e1a424ea6bcc4240f46dc7f262760bf480000000000000000000000000000000011a6a67d4501a8d9b3ab985be59ffc41e79c453bb5548299abff3b83ba9ff951025a68fe6a8ad3eef3c02d39fca8f909,000000000000000000000000000000001932acb1fd0708edf13c293007a035991bdfbfe0089b61c261258e8c5c10d82a5318b2af221b372f0f3f43c391421582000000000000000000000000000000000973650743f0ec8e2acca33f2ef230ee7a05635d14099cdce913ad8678458ec0dde5c5a941097af2ee0c8ffb937d09fd000000000000000000000000000000000bdaf319044101ee9aa27b3accd36a5ecaf8b80deda4548377ddeb97283537be3f7199ad3c190ed23cdb44abb8786a080000000000000000000000000000000006c448827e3fe4f274bfa55a66bc76c5b01e29ac6a8dbebd801855ba4e93bcbd03292ccf804f07f21481260c135b827b,110000, -0000000000000000000000000000000010e53fe9fa94ca622cfa370129c1619b2426bd9d50f4b5eb8a3f681479128dbe92adde15477ad8a4463b08f1a02a62d50000000000000000000000000000000014d10a90709789b25369f0376f39b16860aee1ddc3a4340542abff0077a4af8da946cc29fb6afd9930b872ea98749be5,0000000000000000000000000000000004aee050b0ea07118d76f835218b77b39854f5ababc4e2a29d7c8cc7c18a69c30bb22437049a051d049c8a84f7868ad40000000000000000000000000000000003b1b809d5046054924c3814d26fd5fbdc59e03e5505813bab73bc212b0f5bc0d3fc34478311c5e1ac70fd16a01c52800000000000000000000000000000000002249a026af0b49f4659eca2c23dc790fb36a7b2996188828a17d5852003f1420f11699062932835cfe6543d454521e30000000000000000000000000000000008217aea2221f8748cd81cd37777605a95a63aba36a6ddad72c1e1ac57b24d79ff9d9c4ed71a6e3ac8a378129d5475ad,110000, -00000000000000000000000000000000194612afb777e39d0308a290bf823fe706487c3473412d1410dcb2c0016a70706e70e3a009c0bd61e755b1e4c65bcad0000000000000000000000000000000000ade016d06179faa8d44a9ee2542058bb81724d6af2954c0c09a897703d364ec25e62a3a917c5cecce5c96a7cfba924a,000000000000000000000000000000001274f676bcc05e54fa4b0cce234870ba97a0b1626543d6a9f09afebd5a752769000df404e4d434ebfd561f8335f36d0d0000000000000000000000000000000002877c9438fa319dd1a00f381834e8f3d3cdebf4e1f7690cb82559a2e978bedfd2455be020d0353aa56d435c0174b5b10000000000000000000000000000000009487cc9c7a09be901673cb1bd9a51f45e5d2ed30c90cbdd3e2b294c8f866f68da55533b78152e9ef6de30c345fde5b7000000000000000000000000000000000a3a8d4aabdb260203898655745cb695e6dc90c6e7bf0248784f8aa2340390fd5d8f1c6a98eb1990eb97c2a7f103e3fe,110000, -0000000000000000000000000000000005aaeba19cb0baff9a8e46b901f15735a0c1f45116fe1f41c22fbe1aba22c0a7678bd4799db5cd9141f3112877e2c5f80000000000000000000000000000000003f54664746a5bc6f64021e2f18d8c175d96b1c8ce895809c0e6fcfbe896b3e8c1ac7f7556b9ef953371bb143bfbdafa,000000000000000000000000000000000ef415dfc1e47f39e9632ed21c9c2bfcc1959299710dcd7935a757e3756a42c8f6c627c720fd62f9c486a8e88a64c76d00000000000000000000000000000000088079108fe7d9ac93590c045be0d41396f3204d83793c4e862c5360ddb3268a63f704a9d14323943fc85874cdadaff1000000000000000000000000000000000cce908e8dbb7ec35820f2db5ae1174e0f675b21ae416fc89a7f242df3ee98764022744842999f65132229156d2627370000000000000000000000000000000011e0e2f8513d0a71b48599139a9a29c8eca090c5b02292baba58e07b1d3898fe158cdeb3bbe8edb4a805e695e896984a,110000, -0000000000000000000000000000000010ca243fcabbdb219c5b30092d9d4595a4b8ad1cbed267229eb79a99aef9c5df03d8f24b71db77a5a76917c2fd960ffe00000000000000000000000000000000135d8d92f075c219f8012ce6aebc8e48443b2f33382479a4ca8db0a4f92041d5b6b1e5818b7a3de77a5d30be0e461d13,0000000000000000000000000000000007c6f133647745c312695439f1d8c251e941bad6e988cfe324ec7c959a9e0fb50618984429ff1841d4286922a26873170000000000000000000000000000000008edb220f77ed17fa1f4757a42ec66ad808c1acc25c4b9311be4c09703d547f648d9dd7c8109ffa89d01a35c69ec2685000000000000000000000000000000001595cc05b04f557ed569b19d64c09f4d82e6617437571fddd72a672d07ad94bfbaaed906b3a7e3db519159ec8d0a8c4400000000000000000000000000000000041157d4f40bfcef680af0143ccdd0c4bdd25e598a470dae844d887c398bc498edad715fd7383421fc78758cc9b00326,110000, -0000000000000000000000000000000013e042ccfe0cbb7fa3b045a1fa1a86f199ae91721aaed488b96cc4f6de1899402f81842da2ab55c5bfa63f5b19ddce7300000000000000000000000000000000063cee89d1981f27a4f4d4f23c4d1229fd3333fc8f371ebd85c588e751307ccc75d71d151f7481ecba1ef0cffbfdea5b,000000000000000000000000000000000f983607a6d8a5c3b8a577cbd5d81ad2ae936e714199e3f4095cf280b8fd6d3699acf4d2ef251a571dd1ef4ba6d838bc00000000000000000000000000000000048c12f8b95f9537e56479b1bc43a121e4edfb6477fcb090a5ea60c5f4d01071776dd0264b0250902448f62800f4d2ea000000000000000000000000000000001644ba272d7003d0077991ccb4569638de0dcc48fd2e8e9a41cee1d2200aee1a849f2d620f60beeb06b08c31cd4eeacc0000000000000000000000000000000018892d773f7e48247215484ca0c8d996833c43a5291b0380c97607c86f4ab2784e692673a1da012ac4fec2713d156a49,110000, -000000000000000000000000000000000e07265d2762e8e398c83efe1c43452d91b90b7a4271c09ff693c83745a6c01b73561ffe3da9300c8e7e1602dbaab0bc000000000000000000000000000000000375579c16a167fd9f9f61d5177705f157aa0df3451971029a9444432db119fb33b8c07de33fc822eab46ed4ae47cf82,000000000000000000000000000000000a06ea8e644d2d762520ad956d41ac2086a588450bc34f6d070b86fdfd73cd0734341a751d823935a009b7517770f86e00000000000000000000000000000000140ef0d6a0482537da7db8d775ac3c4a93b16c15fbe4602b5b1843ce757aada5f7776a74151d0bcf760f7284d4ffe56c000000000000000000000000000000000873c90f56a2b99da2f0a1528b8e376a5912f9cd81a159379ad70b7c10e6ebb7fea0a90d65543d968a34ebd539372e89000000000000000000000000000000000b05ff57079386e4e18e73cbff5f7b0efa329ef7355f083e8be258922203240dbb8926f7d11c22ab4c16d1df4bcbb600,110000, -000000000000000000000000000000000aaa37576af2101d090139f562edc2a6e7169b0150af831d053a3a87a3a5518889a51871e02deb3ec154ccbe9dda46df00000000000000000000000000000000158edaeb58b99d9442d608bc8e6024365e9a81e0aa23bbbd466c9ccc8d29415352a153e1f852666505ef097122592ecb,000000000000000000000000000000000e9d6f9e83a2584f2cdacc4711085bd251e060f8c87ff7538ce474d663c6f23361c88971c9da589586e754ed69699c820000000000000000000000000000000003fa90cc1dd81b815704e15c0448bd0e8e8d0cd7ad51237a25d4b8a0f78f532b18ec30a108930b7407b7486aad9824de0000000000000000000000000000000000cb97bce1f75b1df5a4b52745014eb632d2d2230e52a9767e3dfd76754e98252ca81ce274b92a2947f6a65fedbaa3e400000000000000000000000000000000090edabb37f411fae1764792083c8c7412fb470833a9f7399fb312c58687d4afbdc622ecf9d74cdfa3ea87382adcdd5f,110000, -0000000000000000000000000000000012bfaf34a8111a01d213f9a9fc90846335cda978b3df23de99cb7b764cf5db1a816f66adad1319aa7e25c0ab89e7de740000000000000000000000000000000000fed118654a128735fd39ffd3b381ad2d71479054b6bccc04dd58fbeed9b255ce2b925e2141a96a12edc3a19188d1f5,000000000000000000000000000000000cd234fcc729a4206233e46875a557027cb52c96322386b56d6e50d95dd9d23b6f8936ddc6f8475b1076a855c1ae23510000000000000000000000000000000010a774120f607bf9ad2d7bc498536cc9d35cefe384f88a2439a75f1a4f6a9e4b4253daff0d2c91b5915ee0e9a99b4582000000000000000000000000000000001496e7181495114abc0314f580c16038a04a8dab43b5564d518dba5f5e48112ce9daca4b16b6ad51c3af54ec9ce915d20000000000000000000000000000000002c61691a96a2120663c726d7fba3ed37524b58c92a024c15fccc659d1d2cdce077ba233a0d4419a6f237ee4e09abf52,110000, -000000000000000000000000000000000b693fe53cbcd6f8d8c98900be1f9c85966cc644f0a900c70826c6573ee801ce7863a0b170ce0ef168fb1f0ea484b276000000000000000000000000000000000c6bd688fb883f3097f8b6fd6fd0bc5acef9341f21d62a0706fb3625a70459c45a5200ee36a3802d4bb4912030bfcfc7,00000000000000000000000000000000011cd454f16209b0b7040c744291f2df465ebc786946ce3cde77fe4d4bcc4b60a51573c45b8bb2d209da69107613764b0000000000000000000000000000000018a026f29fc2f81e82015ef8610b4396f2e3514ab1a213356953804d585c5cd6a3c5cffbf70d63d9dfca50129021f0e60000000000000000000000000000000015bdcc8c139e636b05ba7376c1ced4a183eb465df53b1996f4ddc8cbf42cdff4ae2bbc2d24831a8ec8b1134cff4444ee0000000000000000000000000000000017671fc3995babcd2c0a1d2a71c417fea84e29df67fa1096fe6d3ec77c45b64fb8da6ed08a57726ab314fb860899961d,110000, -000000000000000000000000000000000ba7f82549ebfdc7f4959dc67cebde4720d76d5d4742af730d45d614133f0a7b0ae7b61ba5b914a997d9dde83b77b031000000000000000000000000000000000b4acd8c203ebd8e3ce12b10cc791b9a4183440309f24bbd60cb2991712c792ecac64d3f878cbe407fa8ca0d09548acb,00000000000000000000000000000000156d8823c37c81d8f03c0b2e61a2342aab6e6c9db36cadc9eb741e085de711e9fda08ca78f21753c4fdd8cec059b6c2800000000000000000000000000000000064d4fc2584c78f1e92f808d4457070b0470eb8de9d558885bba8b03efd8d8e195e4923d8e3382481a0ecee905371ae10000000000000000000000000000000008f1dc4d2ba12e7e3e1b0ef3855df4dbf29468bc99d5cb29fa3058a535af2ba038396bccaa238bba6d538498565c2809000000000000000000000000000000000fc9839b6ee876f7846b5086d487360b8faf133b6f5bd2dbc92a7fe2261b91b15aef8d90c227cd5f8ec05e32d807e022,110000, -00000000000000000000000000000000145f6f774d943a1bb753d5d4876b1a88a4021cb6a6607c0efb07eef2f90ba2a90a6e9dc94586de35f6047332553ce7b5000000000000000000000000000000000b892f1c8d001c8aeddf845c3845c51f2e06c3c77e543e9721d797951b6211a869da97325b569e0de35cf3beda853ac2,000000000000000000000000000000000d40f1c25dd57e36ed305276d4505cb250d2d9da0d5b954fe5e396b2c17a5399613243216586cedb19340e80f898873800000000000000000000000000000000063367c4a622fc925319fc6d119d8592f40f126ae05eed86ee5e4f6707b1d234c747e698c40f292dcb82ac5fe74ea80c00000000000000000000000000000000199ddbb5d4b6cd0fb9225a72c53f4596cf2597de63da56f4a9a18be8321a982de17367b0f3d794fa799657dd8ca10c5f000000000000000000000000000000000f1ed84e4fd958547d40cd2dbf16e2da4cb6d0d02763441067221890ae27ea1f689c26c900b695464ededf083667146d,110000, -000000000000000000000000000000001878e791993186ab76f785b2c6b0fe08588b048007c66fc00c695b55bd17b37bdba71f34ddf75ac441a0c2687711b2990000000000000000000000000000000016598f630f72a0e1f39678e1d0ec6530c4795d7565c5d026fea2389ec0ceb51b434b532466fbb1c92c1c958041283baf,000000000000000000000000000000000ee446310185ce76e31c13e4ca6c43166d971d9b9c539c7d0e8dd8ebbbdd9249922cb674bf6ad6840c203a5e208911fc00000000000000000000000000000000037344752896cff03bc39a9d09757a83c15fbd90f8bc1d8d58dca9b23bc00fa2b0f3f0bd7c9ed857d285825d40afde450000000000000000000000000000000003ef77f0220d1caa7538ecaef1ae2924ac1a180f11004034fc118aeac464fe1ce684b5fc90dae3370e3f79619889f3d7000000000000000000000000000000000fdfa434e7bedec071a1a333088d06299f55735f085a1e907a1c71c312bbb8d27ffa7de7ac69d421ebd675c4afd37594,110000, -00000000000000000000000000000000134725b4d43cb87d2e4d3c43ca98b8df257acfa612ccd61dc0aa1ca749f20bd42c38d933d39f8c3c1a14dd8fec43329200000000000000000000000000000000070ad61a7f5ff9f0b4e7483f5d56b0f315b5f6545b194565ebcf8f0b8d78519ec113af6d70550888be4d661a8403a036,0000000000000000000000000000000000ac465de3832452edcead434729be73be90785158617b5ec3ad53b12653e43721eda7de6742dc51d4d4bb58a291999f00000000000000000000000000000000147c39a5c162afa1f8eef400cfa1bdbe5436bc59d93973f50384022962f828ac934a4f88ab7c3d505b0bc3bb002f5efe00000000000000000000000000000000141bcdad53845a7eb2ec08189a55445059dad24ae5d39fedce869791aa28459f05a6cdf9575676cc6f3dd7d6faf077240000000000000000000000000000000010e9f539a9ced860661472f53147d0347927f065ec09bc32e00c5bc157b07f8b41b05aa4e0eedd1f73c7a287b2d0e5ab,110000, -00000000000000000000000000000000179bc843fecfe713f6e3ccdc8ca0f48759459b675c8b96f5403e1f6da92c2d60449638f564ce179373bce473669965d700000000000000000000000000000000082bd89b49aa62c94ecd4244b3077421569c71efccc62aed3d4bd492bdfe57c0d2cced568df5992a196a7b71bcbe5e3e,0000000000000000000000000000000016479eca30f48bfdaba4c8afca63ddbf59fe3367b2d3c17d15a5869dd2956fc67ebde964530926598cdcb62cfc993d32000000000000000000000000000000000650b4fd24ffbb953ccdb1b112799149d29e2377ee233b9ac97f4db432da63c98b8aad751f6060d04fe1f9262b75fca50000000000000000000000000000000004568dc0b9b430596f2fa59291ea6f923d552683ab9ab93000788145cd7c468c5576efd981c9ecee2ee0c16eca1ecdbe00000000000000000000000000000000154af1490463930d6b8261aa1d066eeda6d65b742cb53c65348e5cd766d86982a1489ad191d1b126233f193d24823b9c,110000, -000000000000000000000000000000000fb118c86e974734fc434c3bcb783e4a7f9251d9fcfb9f4419529354c8a7a3d9f2215de2d1b9f0927b185c5b4db838b60000000000000000000000000000000004da0ce78f3068bebd0a59bc2e41e7ade737375f07d6c9ce962be022856c569a33e8bd6ae60c4bb1b53b3ffc2dcc2aee,0000000000000000000000000000000000df692ca763a74877352af3609c8cdbc184eb71bd35fd86334cb88543637b40b3adbb5802dcd7b88f4d722b566aba7700000000000000000000000000000000181495e709d1617f2d912f43487ad3920ac5f8e47395ec4b58bcf0b2d986c674a0c7838830a039bfb5bb59cd2fee2f5c000000000000000000000000000000000d20b482dd8aad583bd5d08ba9c61b3e954f022d48f9f4f62ddc9f5015ac71dab7d206b1d8b885d5e605519bd33d93a20000000000000000000000000000000010d3deccb9364ee386eb35c7117bab373a76d024627b8a031f96465d5f75b029fa992e29ad4a170c4473cd1df585429b,110000, -0000000000000000000000000000000001f43b86ec24ad40552dc4874a632b4ff4663eeefe1a8c613a19a798a0ebe321a3d543e2df28277944a941b4586ac770000000000000000000000000000000000baaca6bc34feac790807b5eb5fd173c86c12803b76b50be59b2707df765bd10eb467effe34f8dc3e1e79df8a54fde38,000000000000000000000000000000000a007c914ed40c7f2719fc70def0d4752cbaa775cedae9365c5afb61a5e1a2854f9e1ce19af9fc85bfbfd2c33f5bf095000000000000000000000000000000000d85b0d173c25c2915fee429d2468a9eae01ba43c0f1a661f2ef83c1acd726865c00c40ccbc3aae306f93074e5e7858e000000000000000000000000000000000b3df302ec532c8100c121c9a3455392c713ec60de1f9572b040b0966f8ffb888e8cd768dcf6d63d4835a52d13a730c0000000000000000000000000000000001123c43dda8717d03fbc02fa53c4b1c9a931db6b274162cfb02ef5eec602bd8161dedc37c7f6217c8e82236f06e49e2e,110000, -0000000000000000000000000000000005e4751707f3ea7bc7a74d80eff27a0d65cea0c3d2e793425e79cdb0c41e6ad0cfcdbb4de604637c41dbaf30a1e816e60000000000000000000000000000000008f69021794d93826f8207b96d49214b46dfb1778603634a9f5194e92481465702a8be1bc49a7bb57527fe6f963ae04d,0000000000000000000000000000000016d8d9b1b59a22fd830f88b9850576488f75672a87ccb766e52da77f187a8e66071130c7e71f86675f8379b2a8802c4b000000000000000000000000000000000aa4ca84aa23f01ec536ffa25c4b7a6c822f588bc75a4a72ed9237c0588ab892c8474a0f23afc7ff0dbc3b08f8e35b60000000000000000000000000000000001425e759e2537d9e5f0f356ff1d38128eff3a771fa661a839f7a8d0f548347438574ef7d592cd4273ef9b7269c9c5d7f0000000000000000000000000000000012cf1c67d1ce244ae22eec0bf4a400a0f356b9dd075d87a6e61941933872d7c0e42c1d238b2c1704d2cdb2df75169f39,110000, -00000000000000000000000000000000116988a869cf552b2440e16569d8b6e30c6b15430855c4d6bbf80683c5497291bac7999c1f8f08f494fcb4a989451c3b000000000000000000000000000000000e26058d72875fd3d852aa4139f71d35e1edb58242a4939da7986645117d027d20baf85770fc909d537524244da59ce7,0000000000000000000000000000000017f6e2743cb30fb93816d0dc802c24509315363c3652b0244e1395cb9200efb4d7b9fa7642e8d165d28a00740f1a83be000000000000000000000000000000001483644fffd3989ac98cea71843e87b8e446a3d497630419afe99b3f1729a831fa6a49bf763b0c410cfc5390ac4ac1db0000000000000000000000000000000018ad20ae5012266d771b2c86f891f498c2e90a7df19561be240319edc1fbfb316948fb3f8a6b0e3720676b076eb372e10000000000000000000000000000000012f404211899d8fc1221ab5b82db9042ad37e63348871e5ac6cdbddacda0a564888f89d22712069b6096b58c5935edd2,110000, -00000000000000000000000000000000078c6cf89561533810b583a88149586b29da5228ced10a75257b2587904217f63499d8b9ad2d536617247e12f8d1657d0000000000000000000000000000000005b016ede9d892fbd7aea4e8ed0f1eab70713557311481735a91308fabf76fe71e44a06dc23ea66ac5d831e982f401b1,000000000000000000000000000000000d4d78f992f12aefb0e3a6b18fbe2411108327a9befe4a822618fecca4def3169972b4f1fb254cc4656a676529d554ad00000000000000000000000000000000145ef33250240a5c9434d4b2cf2404d9e7cc51b55e482ebc6a8aed85caa21ed00623b3cb2d76ce2d96b2f346d395dfc40000000000000000000000000000000011af2ee2514c58078da335c0273cd18b98d1ac6f0e67890677403f71b0e06863fc72611c0cfba39ac894ae500edbdbae00000000000000000000000000000000186863e7c24cbeb45f7a66b5dddc9b57c7e22c5139aa6bdb82e77cd8182bb8d2fb7bddd7d3516b5422f92e08d02606b5,110000, -0000000000000000000000000000000007160f36f0e5c4ccbcc7900c6504cd86fd6fd700bfa79af69841e4a6127eaad467ccc93c66baf7d767c3fdb1f31c527a00000000000000000000000000000000043fe62b0b9be76a375f3be0d6ec891d5bf5f2982cb2390125ff8d5db57b6b18c5616c526102e4f615963d601d13f122,0000000000000000000000000000000002af4a301e90c71eb375110e7fe23f8f05e2ede86b1a9b240e8d1d4d70e96f1dc3640fca7ebbcde9918deb91f3592de600000000000000000000000000000000058b5f36cfb6b0adb14b397dee4c3769c7446426eb5719aef4965cde2dcb70e6f2fa60101a5f03517c0040093453d092000000000000000000000000000000000f77b560469cd42c5cf3458ae13020c6678af3cddf9bc559372d12bc5d6b930795e1eb09f27cfdb8215f39fb2a11b30c0000000000000000000000000000000003308985946c742af7bd7d29abc2517ff1d225607b5f11fc66695cefabd8f25e294ebdb7339949d6bc4d98db19533966,110000, -000000000000000000000000000000000b9590b1d0d292d9967d759060a551f4e8e4c1c0066a9a3c0be515085847fa26b77462e3bae9e2621f28e01f897df0be0000000000000000000000000000000006ee7c459bb4da96e87eb1d39bd7368de5f60104f85b7b4bcdd7761ce08d48babe1bf5e765282779803bfa972d0e668f,00000000000000000000000000000000093c936d57135b25900bd5dd55cd579aa8b85b9c1b5e8dac6196c4450b624734d9bfc3fda499cedf2e877d79f2da650b000000000000000000000000000000001832306d3ac1c1c61bdaa73c9b6e9c2ccb484c3baa1de6a217a2884c72b72618e864f75fcc2dfaca358181ecbd3347980000000000000000000000000000000002b2e5ff1ee02657fa88c7d6f23cd4c0465152a9daad8479b4b68c97930acb22e4e2eb0011ec4062b8ec46991a7cc630000000000000000000000000000000000712543547e9d24cc78d1c2e3fbe0b51222185f4c6e513256d1ee066ba50beee20321bfd60462e2587c375a0e9395715,110000, -00000000000000000000000000000000044612b42a2baa9d3e1d187b2a4e048773b4851bbd7d4025e0f7f61abee703b5a563397da4515c7379397dcde698228a00000000000000000000000000000000014cbff1000bc0f9b394b18e81124dc81f80e291e841dae6e96e0c86a6f618b9f6aa6103e0e7582e5136319a4dac92fb,000000000000000000000000000000000f52e2f8dff9a93b2985d5c2b8b980e4869af53ce55aa48bc1c9295e557e3b5ff78896e5e6342c2d535d18b11950bf390000000000000000000000000000000013d36cf2805d350c5b748e639d20e592deb4c5bcde99a94fb539dc56d48a862151b925314f21dce4c9130b32e44f54060000000000000000000000000000000017728f485d881b861f626c9de8b3df7d807b266de6cf8dfcba262f40a6248fb5e6506d11e88f460f0b5f1a1907ae5f3e000000000000000000000000000000000c0ab998f63f861c82106dc3ed5ea11a16e98139e8686f8442047a1cf9ac48c3d34b5129263767830144e9a13d4a1f44,110000, -0000000000000000000000000000000013da827dd718d3736cfcec53f034d34bce253bc91f7cfd6cd2666819bdebbfc43a9363f82bf4b580a7739b5dda9c94360000000000000000000000000000000010e94039f37d218ad393e88a226dd324a37e8d5352dedf6d84fa2ed2cab2f874ccc5ce94599950f91b8dd6d6c8b84aba,0000000000000000000000000000000003463d887c4d0aaa21acaa308d77f2c7e13d10157efa9ec3fb1586a8db5ff1a9e807c91c86afc4df34c9fcf06e8561d700000000000000000000000000000000128a81efb9f30ed811ea3163c71b6a46ba2cbdbd3a9f93cb8d0f518747cc860431c6e93bdcdf36d00f83838965da4b50000000000000000000000000000000001777802b7c41111b38da3fd8092c280b4925827b2c1592f779a4ddca71f8268858855c413fd5c0057a652155261d75ba000000000000000000000000000000000c88b522d6dc2000cfbb7052e141ddfe15c6cd7fddc970edc4afc36fc59e7f8e31415706a8121e8e84348be0b50d0d88,110000, -00000000000000000000000000000000010416da7cfbed2768c77b80957053030d49d535b21a8a3297ab257dee0463c91b87a9e571b86bd874522149d9af0c2900000000000000000000000000000000197ef97f6d02a51b80e6f5629e88a3c60399bcc4a358ab103dac3a55a5877482558abed922585a1ce3228ffb507679b4,0000000000000000000000000000000014be96cfc0dbe09155ac8d8233b71ed584153e279b2b2be88471eb653aa4913fd2c33947547c61f7fd8bedbb552a8b1b00000000000000000000000000000000146b9a0011260e2646920894cf405bdebb101db12da7849b30868655fb5f972113cdf2fc322cc246d3dbd9f20b98fe2f00000000000000000000000000000000104bc20e104da5173dcff3e195f80960819a0d64e922bb484c2739c4b7c22535f7faeb1c85188aa853277740b389eac90000000000000000000000000000000019f5aec599f9ec286aefe48eedca3f929ac6c758c231182b92dc965d6ac1f3db53d93f57d733ca8425a5dde070b0dfa8,110000, -000000000000000000000000000000000025f1ac90f5b0748d57d8f7a928be875c5712801f70af0d057546228c1bf83d3a207884c0d66d0b5dbcaa736bfe0aa10000000000000000000000000000000017f66b472b36717ee0902d685c808bb5f190bbcb2c51d067f1cbec64669f10199a5868d7181dcec0498fcc71f5acaf79,0000000000000000000000000000000004ca0149527817b4df0f08acabd4e8c6329c0d1bd9f2e8211cbea25d69b84009ef158c770f948fd67e4609ccadc938680000000000000000000000000000000004101b351e2a9d34042291f38a289d8575872104bcf76f60bf888c60cca5101c34c247da30f7a8db4f0cf2f32abd302c00000000000000000000000000000000167e668de3207ddc60b8a5d5d246bf2f63ceae3bcbc4309e73eebf4d4234c2785bb13e4d5d8fff9c5f205e4fb942a2f6000000000000000000000000000000000491b965ed005065abdac53e3065781f2fd23f6159debc64f01c9f62073c651da33c05ed84617efcb5ffe08ce05e3b2c,110000, -0000000000000000000000000000000003f2dd27e3f0ab503a8752c0802ee14c655271e8cfbc734905b4331fb4e70cdfe291ff71053fbaf91680b1dd108f458f000000000000000000000000000000000c62014b7694a3e81370761e0adcc32430547a1bbe33746637e7762dc24f8d04b4bb955f17ca901659482c622d777642,000000000000000000000000000000001541320fb6f8a8c3c67278a7ad05ae7927d3555ad562bc8addb54c6693c51fb1c7355d2e74ff10f6bc3eb182d8f5b88b00000000000000000000000000000000172b65b110935b116ee683c8680ef0a660afdee43b9b8fce08ef3a70b352f8710c06b820348c338fb903a165cc5376da000000000000000000000000000000000df529b0e274e2e8993dd89ffef487aff23d31f502a19dd7d383de08fc77f1308a59ac5bf7cc899e81d377b2422187850000000000000000000000000000000010b40c9063d174b358637ab710d15c80d9230a1b3a056cfac4d583ad8c5b79c3d9bf22a1b0a4e0f629cd09ff7586f886,110000, -0000000000000000000000000000000014d1491a45b4b0914a6cb2e4dc7de9d0962f5c175cd571057cae1e17d2c943954d119690ea14f5815f858d277a9ad828000000000000000000000000000000001650771e0f7b33d235f229b7d49a7a5a0f00f78e5f4abaa70f39ec452370198a8532b5873e41f17c449f9c565e6adea5,000000000000000000000000000000000978ff68d94d33703488298658cf2c1b6034d3d8d21c175d71a0545bc2f99eaaf131f061f3e4f55622668e686e691f53000000000000000000000000000000001124804b252f8187178435761897d00c43cf67b588ca69f97c20b0ffad3ed94acc2c0f85f900713dd6ee9f38e5ca94490000000000000000000000000000000010ca2a8ce71b9a096c132c4a060a17365475b6556d4fc6284266ae787e217b3ceaa3a32bdf751375eaf6ab49800132fd000000000000000000000000000000000a43b435b116d9480497f6b2e1bb377550cb1a7ad59e4214bffacd517afc6b7bf91112fe57b17a02a86876ea07361bca,110000, -000000000000000000000000000000000aeb244909654b3e1df7cbeccf297223be57c2f514474edf0740dff48dcd5898b6e49eb65c787aa56ef79778249f4e07000000000000000000000000000000001007c89a66dab07f54313db8682f9e829baea229b030b4514d9c93686747207939c50a198e83ac2cf50315e02642a24f,000000000000000000000000000000000c3d87b1b78fab65cfc853304c682b39b6ec2b4ed005e9108f69daee5aecbd586c9818c37cdee865ba53eab9302320ce00000000000000000000000000000000062a7203cd2fd04a957cac8b6b6bb51e635ed7165c547ace10f93a32b7f37747a2e63d5767d966684409a6c748d4ee6c000000000000000000000000000000000526b44af8157dd68725aa8743684e020c1e385af7413c9dcebb320568663d18b6f29edea26f2628358852b794ffcc8e00000000000000000000000000000000098126f486ff55c21f64421e85b09a1b54f42d3499dc0e198db6f3bf7dd8476cad97c02b5b366e5ea20d8f83cc223f7c,110000, -000000000000000000000000000000000398d86b5206bae4ceef0bcc6335b1f6bf5d17863ef3a5e8463aaa69d9f73f8227263964659d4b770d6d9813f9399b9d00000000000000000000000000000000096bd18be1176e16a0d80e60f7d7ec9d3b6162f683440e3cde70082a73605da3783c8a058bf76d7e25056f5cd95c31ed,000000000000000000000000000000000f3e76e7d1cadfaad08d16457b02d89c40c157225eec7916d306faca8dbda008f41792888c647dff1acb4d4ba3b43c4900000000000000000000000000000000132bf730456e2afe745a58cdee689e37223292bf682d5b7dafa7df99e40d385559d0b3161bdda0bf5173c43ee46412dd00000000000000000000000000000000141b36ff6890e35db0054358bc0731b3aa0efac1a247a51daeff3515746456216975f44769174a4be41c109d35e4be33000000000000000000000000000000000ca401ee1addff8fe87b600e057ae34ba297886f92c5be8a8c00b360ada71831e31bc4ea1c309c7da31cb28d1011ecad,110000, -0000000000000000000000000000000004ca5cb60c32edfa385baa911ccb7fd1f383824c22b945944b0f3f7011db8c123efd8fa70e4fe699d40c6716021f0151000000000000000000000000000000001339adb0dd8d83574c2008f0a7ed001b0808d2fb639b5e57e1d293884247d5c66c948ecc60caeea7bf440a3a44ed296d,0000000000000000000000000000000009d0af77517b654ad97de3ee1dbf69ec1eee901facd0f8c39b4af393d0e63957292a7529b461f7fa58909acad32ba3a2000000000000000000000000000000000fda17cd878ec0f8c294daec1bd1d56c63e875b002a81c9c41146dbb564bab6e4eae2717c9fd718af1ba816a1526e8fa0000000000000000000000000000000017563b7ff22b50b6d9e24b1e0d89ca5c72e68d4d3cc24cce36856191111d087c3dfb392070462dc7850ef5a1422931c600000000000000000000000000000000020001fcff638504055ba35230b360e6d3cb5777b959c194d6f9b038b58d3ead0b82b28bb215378abd85d357b85ea260,110000, -00000000000000000000000000000000089211892a61202b1ad3a85aab9f08f8d028f3e3deb16c1de4d62c1a403fa63c6dbbdf8cec37f0a9d6f346b1c7ee179d0000000000000000000000000000000012a9fc2070b326f4d7e64804b3a2e977f4bb36b6a4afcf27252af757d8535e8172a99dc909fad5a3ff8df23d6d6c5948,0000000000000000000000000000000000d51c77c2443f00d965c0d7ec9b5a8a8003c2a77b0ffce3e47bcb55420e8690a9c2ba9235b62a4b351d79d216a3aad40000000000000000000000000000000013cd46e3ee6cbb3bfb771ee30b5f5faf0a64a9efa1f8fc57024c83ad07a9b25e513f211ea604cfdf319dc42bf4c067d300000000000000000000000000000000009fbe1fffc67220067c948e0c80de23795e045fbe8031c9010eaa69356ffd8e5741cfe12731ec13aa236630f1b1dab4000000000000000000000000000000000e5ecdf808d10d47f041e4b078e79b32520ce9623b50059a3bd8b59daebf9103c31425659ecbaebfb2384d1c2f1b400d,110000, -000000000000000000000000000000000b37365748fdb21fcb46f94edf86c586f17d0e042c4683e68c6cb83e7c0ed2c30ed260c15af2c9dce77bb705debfa7590000000000000000000000000000000010d7c02c6c1ba3cf6ac09a05dfe043905e1a7eb32b67f2e8a5dfe82eaca66ef46cce43aaadeff58ca85345dd0d3bf3cb,000000000000000000000000000000000f3e4d2559261829c0f4816f8b571170de1f74d75d74997cba56fdad42932db73504691f9e001f5b4604705a8c1a38e40000000000000000000000000000000018c72136bc7d3050ee693270668e706ebf70f990e447ecc6153a10625cccc9deaf5ae82d2a656b1376bf33b1c1fdc2c9000000000000000000000000000000001754f2725bfa76e92a74ad5b520ec2aa82a1f86e8623a054ebba489adfc9e71d1f14d4692ff9fdd8acc3d768b67e1b7000000000000000000000000000000000096f1373434a8822569cba0679dbd2abf619bd9a8c73e54e078688d4e2615d45431ac8cf3da5e15a83fe77d14b339e49,110000, -000000000000000000000000000000000aeee59421c8ee65f8070b9d036e6bacb39dd2537d02960a3a57da4f0985cc7b27784d60fc1613f5a83c34d2250395c1000000000000000000000000000000001715ddcbaed0a05b38b1c820724405a713cc0215a4c497892f00746c0f9af28b440a3686178d9bfcd41944a224311306,0000000000000000000000000000000018d515b8c99f541c7dd448c3564c1909b84517b662d6a2d1176d3bf5e70abc0a2995c73ae3f1614bfed2f64229e173e80000000000000000000000000000000012126ab671420933cc4fa9206311200cc5241ca3eec54f5d97a426a72642bdde32a65c79735446779cd1744d112d544100000000000000000000000000000000190d836312ffb0d6bf493f4c942263922659abec46ac4de639efc311753148b445509f808c2fd813729b1bd96e0e663f0000000000000000000000000000000006494f9a451460ac658ec17710bef79d59b6e0fca049804c0954c5fc472bbef520f75d34408ccc62cf2da3deeb79acc2,110000, -000000000000000000000000000000000ca4b3e1a8351057ba4a2ffaf0cdf1c3c0717ccfe26433f6c40e2cc29e32ed884f63d25979580fb555a5a86c9147bcb00000000000000000000000000000000010c1db593af38aa14ca9dd588f54b219ff1fc9edd25b3d16c595662ffa7939879244326b14d978e0dfdd25e37776964c,00000000000000000000000000000000173fa567aa952bfaa9a60b8232a185475cbb36761ebef49ea5fce900a06043d0e2c1b6024e40eadc9f4bf04b077201450000000000000000000000000000000010fdc32ff84f79fe39351cee1ed6b67dbcf2956020e2518d5bb5b367b61f86f1bce36f75516d9551d74cc3a567e6c2be0000000000000000000000000000000007abdff8a8967eccc4de6b4ce142173841c0e8399f5a67dcf0f7b5e5b4133391b44bf4d41d3ae3426839b19aa4c5d40c000000000000000000000000000000000c99f160062566418c09f10eb80f005f2c8c12825435f354f1d65bec0322e9b8ee968c009a84ba792a7ee7334b32bb3d,110000, -0000000000000000000000000000000017cd94e7e672f0dba9a3c1db742d87cb18b9401e8311c4badc24f811a8f40c27942da6485807701c1a12da58076c756b0000000000000000000000000000000012f6de4ac9883e78f9d658cede4c70b44bac6b4c9734cbf24298ddf0df0cf54164aca245d8e313be4aca66ba3cab5d70,0000000000000000000000000000000019dc92f1da66d0855ebc8e7a2ddec623a2f843a97c7385364a631671be7ee3387a0f98940b5a51c8d9e23eb27e3133b00000000000000000000000000000000008493903c5c68b2847869b8c3b0fa9b8ba15bf1f11a40a29e6e82942e2910901044254cc8e8c3c3bf56e1f1b6dab7e86000000000000000000000000000000000bd3c1e302a191094059a6493e59a11ab05a49faf333f36f7680ec9b1043e59dfd7f0fabe9f334b97cd638dbb8bb664b00000000000000000000000000000000141c9b07ff33b6ab55b320dda6be54320082f0057c446236cf3d3a51e674c26a5241f2c702d9989adbae9045942eeab6,110000, -0000000000000000000000000000000001b2843d9852feae3145b242cd0999877c07785bc72cc2626f388dca32edfb112bb90f9aefd6953eb15a0babe748573d000000000000000000000000000000000a69bfe809a67ee853cb96b5a7a72798748cda56936e5664d509001544539730f57a7541ecd2396c9225818b9dbfa3c6,000000000000000000000000000000000d0922466c358cfd756727e134b5e64d211244587e4eea036f0959e78570dce3ee264c703cc356cde20637c7560369340000000000000000000000000000000011a66d618f79fb662ac2b2d3b50750a5567e36d7092dfcc72d8f340c04df75ecc0ce4a01b410ea775dc548b8dc66c3d8000000000000000000000000000000000cc49cf4be5e2df6b43054092afa2d6acd66f5a43ef0667f6a2d660beb7fec70558ce02d7acbcd090df91fe833326718000000000000000000000000000000001270b0519db083f903a3dbe0b1b1bd5ce0b0059ea2c2c50335dd80b4bf154fc23a3de1ea753b0e279145254d8e5bd045,110000, -0000000000000000000000000000000002479a989dbf27141bd9f467447218dfa6ef60781a7231f089d5f1f1d8dca2ce9606a75c09f63f37f9cc1ee61dceb32500000000000000000000000000000000037c2f1b96170f6847138232bac663e4940bca602717c877f58ff7f5259778246085d499ec6bbeaade18f738df333cc7,0000000000000000000000000000000007826398b4ec35ab58ba9fda5c15ada2a41d3854677172ef6a4a54087b64d0f73fc875ad62236eb7fdcbd94f14c8895b0000000000000000000000000000000016b14fa92de5f6e43988829ea2f851746efd6680b0ea1283264f803c8ffbe85a343bdd42225caefd1b94b8b311d2f4950000000000000000000000000000000018797093ff82bc10e6db60b1da50b9a60da01d67673e9bee8c7af2bfa2d57f409f7b06f53944938e5c73b049c2d3c6500000000000000000000000000000000000c66dcc3d30f35c21b8a9369c8f6de28af404e8b30d3c9a7f09c461b0272ba6d5a29e716012536dbeac1d9672af8427,110000, -000000000000000000000000000000000e6fcc48312831b910e52aebbf19869b3b32f05db8310b68905bb244ab784df5732db2e72183de5d231d803d92aacff9000000000000000000000000000000000f61f9e52fe3afc2a6bf12e420cebf83bc55a449d6a167779e5b6ba3281c51d790a045643aa75f2516eaf6ae2a816ac4,00000000000000000000000000000000191aacce60a1a83f2c453fe196bbe5839a3a1178b147580435f7de8a2b0b4f65b3e280ac7a67570aba0fdbce6c11ad9700000000000000000000000000000000075ddd6b256f53a6ae6758a5158508540aa99b78ca069378f0ae3f5621ec24b9acff1f9b61d378334a63682a33fb0561000000000000000000000000000000000b06e11c9f858446fcc90c69d05cc26c33bafed0feda19adbd838c9c24bbf567b673110a1b248d0ee97fc682e561298e0000000000000000000000000000000018c75dc203493e12e1523af50f85ed648130ce5d3e9757f713850c867cc95c7acbb66c9733dc4f53d6a0e64bfaad5832,110000, -0000000000000000000000000000000018efc6d366d79a09b7d56c81c17c2eec2ef7395fdb5991f41273329cdcf4537d342bddd83c3994a40d5c18f6afa054c600000000000000000000000000000000127021ce28627a9d6a492720f728acef3b38c12b951f70a932c7fc0ce3f5b6c80783351cec55d7d1bc4ab964bb4913b2,0000000000000000000000000000000012931f51430bea6e96f8ec456ce6b3c9e058b0bd3bbfbfe8b6e84fd6110c3bbbe0001018064e8981797f9c93713a0e4400000000000000000000000000000000196b6093dd2276098853ef2bfac84f0cad06b67a12484e98915dcc756310b818d8136954de1b602eb825ab29a143cf4b0000000000000000000000000000000008284beaa877b25374571dccb218c401cd905b351dd96700853f01920e409d11c4e440e90dc175cdf0fa807cb9d1e93a00000000000000000000000000000000063c6c238485c291fbb60bd2824154a9e23dea374292966d271ae94875391b7ceeee813e3fb9504223bb86f0ea3b6cb4,110000, -000000000000000000000000000000000a0277228ab4e880c12f957a6fcdfe49e2155083f3f93d3f00c68622415cd1f5bae183b7df9e08328a8139392772cdc6000000000000000000000000000000000de0ab426e56029790a5ff72f34da97e11c028dc5d31e448c49ede004102804d2bcc36d509640247a4c8bfdf5104a781,0000000000000000000000000000000000f7bd0705cc4ea96ca38314cb85963044164b83a506ffeaea6e5eb8f7c4967cab1f1658f33b5435191427aaf9605bbb0000000000000000000000000000000007a93e2a5c118aff6ceaf2370ddad52a82854946ae595d384ee0b2b4935a574ba758736d84b0ae792f998ec6a707dfbe00000000000000000000000000000000090936add00fe5c7556610b28ecb4466ffc37b95b5cab43e072a585920b3cbe70faad01ef75d1dcb4f7d00d900bd99600000000000000000000000000000000006ae82539c68b7af3143e23229fe320924472c2b3e15a2e27e94cba674d30f083dce94706da094435c53285a43f89e56,110000, -00000000000000000000000000000000170b243c5aa49a0134bf3d6494cc1e55a1c6ebefc6117eca3b343a48ef0a2b077c443ec5b9201b198df015a38e66b7910000000000000000000000000000000019a8ac8a3be1d45318801bb0a654963b312540d24aafec46bb7661cebeec27b0b345275fd53c041d02b1ebfa27fc3854,00000000000000000000000000000000024c1b869fc13191b71d7159a07e869f1b13c11c73231b82e9bd0a7b4c32d7b376fb73d54f7231dd4974713179f235140000000000000000000000000000000012b9f95af661e8452aa5026302a7c28695307f75e9e4e32365caf378ed394fcecc831a3c47b443172188f4d18338fa75000000000000000000000000000000000f52675fb4d112d1d39ff953a253b22dfa0b73d972e756ea7fb673bf87aa992883c5baf32be6f50f880b03dcb740f06c0000000000000000000000000000000008b57726e17c873e12834dc291cff6bd95307f50e7b1d0caebd8c1eeb6eff4acc0520b135bc8e35a257133b7dc640db2,110000, -0000000000000000000000000000000000fbbd5a10eeb2f358f2b167f1985d4084c4b12accb1520d780ef1c52f6fa80e97aaf190e7a7b241ef96fe8289fc0a9600000000000000000000000000000000155687114e7aa786ba27aeada830fc705aed069c4e3a07e88d7f33923319f416ff3caf6533cbb36e5bbb1b93a191bfd0,00000000000000000000000000000000061938df3365bf910884ccbd74d3cea7c30416bddc1a9b65e7723c15d89aa657da36a45fe10ed50bfa0c2769bb98aa2b0000000000000000000000000000000007b3981054255715826cf8f247210521ac681305aad3928b69804117fc143c5101383eab7017127c8452a79003a857d60000000000000000000000000000000004c745113480fd87212ed3ff30ba43c8716b32e62c1f0091bde53bd4a8fa8fe6bbcf0904144f4791ed1bf12dffa1f17a000000000000000000000000000000001237ba297c7f69e5e240846a12d86c8276a9a6ceb4af977edadc7ebfba3ad3f4ecc0b875da0ea578c83fc3b91f9f31a5,110000, -00000000000000000000000000000000115edef357ccc3432226d9bad796a42b1a278d9c8adfdddc5a0f8a36d32ea1787183877f8b7dfab71424cdd10b63441a0000000000000000000000000000000014b369ce61abe60d942346e049644b95a0fda96316446b2fe7ee427641af52fdd2a654bf125ff6c8c7f3dec98f6cbfb9,000000000000000000000000000000000a0cc3e328b4cfd01afe53dbf971ad78fc74d951050d76210e4c84438109622f0531747e762e185e3d7ecb9faa7c3255000000000000000000000000000000000622ad6092caa727d069b8921f4124d5996f3019705a908ef95d23092c5bb148873a22c227aa25ebee361d4184cc38a10000000000000000000000000000000002938d2ff50cffaab8c056c2844c50013f5bcdbb4f91b3f823836edabb39ba17ed1b8b5862301efad04bd2f5d5bf599b00000000000000000000000000000000072e96136afebbf8c06a37cf9b20c85ef8cb3f7f99d5c71b05a187c193711e5b76f52863c7ef080a1b64b2120ab2ed84,110000, -000000000000000000000000000000000d22b7b36ac66b10adb4570f8e7521ed76de2df2a7b94b2d0b9ee4514cdff6fa7c74854d16e7e70f054a91df93c7ebaf0000000000000000000000000000000016867c9cba66dd9f1d0332d31c4e46f8e393eeeeb19af7e6e01effb29ad999b3086b599ee4b371de557d4fafd5da3794,00000000000000000000000000000000142ceeefa9fceb903b25d4dc68f6755833d7529752db0f125f7f65f2b7aeea8c90e599ac409576e82f7b9d6f83c43aa0000000000000000000000000000000001664acd89b482aed04ef40bd4d1ff9f39c80d7738771e2b3ca731af01aa230d865869cb05d83992e94ad99549fd0b8550000000000000000000000000000000013d6ace9b492c014d9a7504b5abe442e3bba13b1ada454aa53177990ec44f616e091f1382d36db87b7e794c11570a9bf00000000000000000000000000000000081b7a8a2906435f8a9242f573225ea62c5429e903bebda9fe9973a18ed2682185d72aaa6584b9848d1cc45ac907dd27,110000, -000000000000000000000000000000000db9258e1257e659e60bf8569ea90c8247a53a1d1eb958481623447a38d0f1f1686c3e40c8f15bd06cf5be9c02485452000000000000000000000000000000000517c87f3df032ff08d960f524939e66f7fa69b4168b0f2507baf7d7231a70dc5690a02d317b26f219365ac3255bee78,000000000000000000000000000000001182e4230f0c360c07913349f89f8436c01841c9615348a0d7057336c7483342024b0369ae52f39d4582f9885f552b5d000000000000000000000000000000000d15433ed130163a85f8ba87468c906aba88ef8610fcc1a8d6b3308cda29907acca351fd7fb19799184f1ad91c751b5e00000000000000000000000000000000111089005c4c5370863b0ea6b629197a865f978f71becb741f50f9b4e49b13162ca63c29aa26287faa9c923f57f4ad4c000000000000000000000000000000000dce405ed2a79ad433123105ad01a26ee85d1ba4e5f3b4e0339fea787058c06e9a6b10f5ec8f6eeb85b211e18b6ea076,110000, -0000000000000000000000000000000000b6573c743989fc8613d4ea09c2e500ce965b50cf0c8975ff703116464082efff4b42828c8337809f6938d7cdd3f66e000000000000000000000000000000000896d316629c80ce6e5f240535863b9e064728665c0815f39b21675c236f6995e7dfff1e4aec9ad05861e2122469ea56,000000000000000000000000000000001694cb615d2994a903a13645ad44a63395320f286503902b6009e7c795dc8f024260e0c45bedd864edc9fcb9d1ca6bc1000000000000000000000000000000000f20538af015bd6d213f90fb1a1ebde4d9e2ab2defaf80d791a1f70af2ca7ea1598d43e9eef1cc982f468cf15d223c9d00000000000000000000000000000000046c62bec4c6876a67f5fe68107d677db8fa4d59ac0cb7afe6e706864c6e94744bedac6b34a68e8ebf89c231307b86d3000000000000000000000000000000001839f3b8a6dd8fe8028247670fe5b491bb43ea8fda53116dca87f97da96573a5e701a703fb5fa7bca457ef88a827e061,110000, -0000000000000000000000000000000011fd2ccf6883b78fe19cfe7beded503cdbe1cd5dc9ee452aa6b329d2237c2529df6774334b132cfeaa616f20335f88680000000000000000000000000000000009eacceef036ec500e7676f54af703016fac93d69ed19c8943b64ffed2db498b19cd255a0a3437b568eade0f497c7b17,0000000000000000000000000000000009d8725eb8757828a94969ebf40545a62835686897d4504a66484a3078b2f15e39fe918d8dc01bc7560dcb005a7a0dbb000000000000000000000000000000000954a6cc9b2dedca1cf280f72fd0625184b8f83b78ee1ffcaf0f9178ce97900d759e4a74b914c3ddc32f84c3f4c3a8d60000000000000000000000000000000014121b83d2a06390ce7359e570e1593d5ff097cb0e44c38bc74171fbd8a8da0dfffcc2bcb95fb2d80a55933f696a86cb0000000000000000000000000000000016f71d24256de70618a02b0f016c6f31a21d2cc42855886ba30176584a028c2e12367be19b834bf41356cdab21223314,110000, -0000000000000000000000000000000004a851380536054f6b69ef7581b57dfd753d1e6201069bd1218ae5766aada087b4b08f65d43b3ce0215640e8d34633310000000000000000000000000000000013579671b64f2d9a2c3ac2737cf95c2148acce3dcecb3db6d019730010c50d1c0504ba4ed42d93771ba296b0b07487d7,000000000000000000000000000000000cd47f0982904ccaf4f3cdaa37091a08e67a5f04af09033b864631300bb6c2aacbad105eca6ddf68a643976fb555d3d80000000000000000000000000000000012332ddb0e91f0ef9e085f21634c6d69576e60d3d24732a0c91a560906791f60f79d09ac0ebf448bd39f047b1dd428450000000000000000000000000000000000a756a869b3cbc5624f0e08019170beda35fd2642a79108b284a503942f8267b75868636302e5a12b4f1505331b15f9000000000000000000000000000000000f60724f6c8200edff41f3299ca003e9ea03b97b01a3e8c63763bdf67b9f7677331a7144915312458c40d041be97b3c8,110000, -00000000000000000000000000000000021dc1dedded9b0dd90afa9ab7fa8f9c33930fe4ae68185ea4cce9ed97ce4cc9ff93f96377b11f8d42b02e759a10b06200000000000000000000000000000000034c963fda3bb80043d6d7887661ad59b3c31c88c958b451a8e11684770c132205db6655ad7cbd604ecc3225b0c128b0,00000000000000000000000000000000095cd509e53f10b1ee18b2120e2d18f0905a202a992a9c62480beb6588275fc8b5b151e6abf17a12b6d9cd03a8b37a59000000000000000000000000000000001723bf1a3d79935eb4b39f7feaa1e05cd8f3e7a32e2c406625053d8d8fde33eefec231ee00adb00b0acac16a83dc77fb0000000000000000000000000000000004af528e886dad3f9fa7232605936bc22a6a22622828367791920ec9d31cdb2f290e37f5fc79efaeaf96c86b3f6e39220000000000000000000000000000000015bada14a84fdb09b77397cd2e27836f9f88854924af0cafc6f9125d32be848c8325a3eee1a26de8be8eb80b601f1ad5,110000, -0000000000000000000000000000000003e8d1be04f8dbe5c7e1c7553cde8355ae16d26c819dea92fb543cbd9fe9e726359e1e4be0483a7720343f34c6a3fb9200000000000000000000000000000000062bc5fdae812802bdea09e4130c3d9bf80c7518138b116a4b6a302c155b97226a6ccc8a3ace18744e7adece08781f72,000000000000000000000000000000000d8f14042f36bb377655b63dbc37c50e0eb5775d4e4399972a6758cdfa9751cb4b733745ed1a47fe5f2cc434efc5af81000000000000000000000000000000001384016829d028f823e6d062898c042a461bca13ae4627c983d9b5c9e8b4ffff7eb25daa1c52b39e309b9c1e7e4f2e920000000000000000000000000000000004f7904d491a0c2018b1361a9cfec4fc829e607402859fd9b9ded60adcee51e9b522d302f9064130a4eed1327f49bb4f000000000000000000000000000000000ef4fe949fca569b31fc57ae7d0166ea53318c5712311076e052c2967144116f5490fdf56f26adf64aa01beb4f6cd214,110000, -00000000000000000000000000000000014b922157b19ed9debd9ae95cd9435f7980b8d4ea33fd29f99d5e7fb1a96f6d99ae13f7f86239c4bc286c3927d3522a000000000000000000000000000000000f6d4badf78d9115d93783a59ec9576fcfd87a2c29e1c506b6f7e313e71723811a36d64b37650fb6f7b460105a7e13f1,000000000000000000000000000000000f20b3a6505784681331208b573d3a241706692db71b5daf4e9c80adb1fa9bb87023d7ba7f9c65158653c735dee9dfdd000000000000000000000000000000000f7f357407ca6cc5c5fae4b84509d71b2f4de9af226cb4038b4820c0541d4999b7396608efd2f322a00a768129f9800400000000000000000000000000000000138dcc1b9d978adb5eee6356980cec5d18cfbfbf18cf6fd14f4119a563f473f5027af06342e84ea858223ed63d1a16af00000000000000000000000000000000012b63f0d2e8ea361d55aa617a99e066b5feef3af1930b83d2a48b527e0ef304ceadf7cba1415db80c54fdcbbcf66d14,110000, -0000000000000000000000000000000005a54ee5e3dc05c38ade7a42c71baf5a1467938f97c0cdf0742441cd339f22542b1ca6cd215d385b3fd6ba74ec996a4d00000000000000000000000000000000051c6f0ce621e8e27e5017628690fb68f0fea27d67726a0a77b0caf9f524936e123ff096168ff2079b9990c07fa80354,0000000000000000000000000000000015ff2aa94f802d8f9c60ddcb43aee598239cf3ab7f90f8289a487b673f6065f8d9bc92bd4cd28df4a7b0d3bb78fad243000000000000000000000000000000000884b5d4ca3c8abea737cfca05878528890b6cee9bbac0bf027df5d4e0add431829caddf4c1e001818581ce08686eeed0000000000000000000000000000000019b91a7738fde9760240b335457955e963030848e85717858f22dc33ba5a4721156cfdd7341aa86d10d268e2fc9a1d26000000000000000000000000000000000af85e60161795906f3cf705f5e8cb8c15083a90836eac78445c6bc27ffbfc8c2df3009b436989b46b271dd8d1dbc282,110000, -00000000000000000000000000000000094e958d9b7dac39fa4f0143a333b2ccee09046cd23e6a1c0712470a3c2e61d2f8b85aeca37350f71d7ec75aea2b3b6b00000000000000000000000000000000080743cdb5e359e8b8ad3485d53ea286669ad66d841945296edf80dde77b20a158e2c1529dfc33a1fbecf177d75a0c69,0000000000000000000000000000000001bd1fe6a6c373cfdc2bfd488b0c942492b77d55b2560824edef3a91c711ee336bc1366690be40949d04edd39ad48a7500000000000000000000000000000000161476946a5687113c74a34284f49b0658e323fae57aba88b039eae584d6ef28adca669fb083a2fe8f0ef664eb5b957d0000000000000000000000000000000007aead870ae09a04cf9c9fa49d0888f7010782cdc5a0ade4c1340ff15d99cb39b7412d66d4147b95601fcf5a39c39bca00000000000000000000000000000000095cce83dbfec12973e27627bfb2d93fa9a027a2c2af4259a0879d6bda055d74559fc93fb3b4f6b0088f702af29a7643,110000, -000000000000000000000000000000000dec04526dbf7666d2c29db5de1ef0b3da85380a171d871a57ae3df364d2754fceabf9d4d2a3da1ecd94e377abc78430000000000000000000000000000000000d19875fe988ffbd0cf1e9bfefc7019579962ffa3a585ee232615e4a5fce0a07bce0537b203ea00010a90ec05d5b8de7,00000000000000000000000000000000133cdf684c3ff1cdaf07ff787b57a66c215eef06acc2aec4d726a086480e7b2a5dead2cb357d99e298df32d4c6f5029b0000000000000000000000000000000019cd65b830fb17880f40e104ed63a7d49b0fbad8eead7502f18f1b9f85f3f6ba6c275b8a242effc61a7a5d770a4fdaa700000000000000000000000000000000039aeacd163862e476b17a22c76042d7896a04f158489ae71afdd35d27106a3ec276baf5c08e3eed4b3f0a79c3c458d200000000000000000000000000000000125a9bd770c1fea2155a581211bd71d55eb1966645cc892a05d32cf1e4e5b23278ea2fb1336bba7f2c887debe4a93b52,110000, -00000000000000000000000000000000016dd03f0df71b183e42cc29c665f18d57637b65f05df85aed9a0f7b8aa37f7913f6606c10f24a7a8c570f485905583a00000000000000000000000000000000161e62d8be678a114fd4a99a6caeb9481f5eaef145571152fe8a6ed77a34c06a1b6ff66044102d19a94abcaaeb254e73,0000000000000000000000000000000007843268081f61ad2b3f6653336a99086381bb4da4c23b7d59b9c7827f2d4c196d136508c8a1f3d2f939e8c9799b95e10000000000000000000000000000000000e2c57ad95f762115d8230320810a4ea9978e26ca17decd6af4c112789608967a92fafe3fb3e79539d75d1c0bae97740000000000000000000000000000000010951c9839db9dd6ca5ef95bd1b1b9cf60bfd97cf88129fca23b24f19c9d5c71486dffb762e92f23d2a9e9d462556f620000000000000000000000000000000013d35c17b3763fc5db46ac8c44aef996f3f876c49f5278b7c97e844f23ac49f2d50b3af080322d30ead873af7b4257e1,110000, -00000000000000000000000000000000036efffcb0c6f42109bf9b8b7421e32fa3f855373345341e6000eccaca135ef3b2e1c0151bddbd46ae92185acb847d74000000000000000000000000000000000edbd7a40f3e688eaff5e29800159b8d799df07e81f65d59011e84329b4689a28a15ce11537fb560df705be26bf14b1e,0000000000000000000000000000000001aa1919a50b5bad62b839d672d5a11ad345fcc61f75eccc42990e113deb8a486423d1b27e7c81536d8a5799986b9408000000000000000000000000000000001879295d2f7bb3923ec61c063ee4f96d7d7cf7786259e2f4cbc3ccffe7e114af264b3527a5e06dcfad50ec1e2a9c1ae0000000000000000000000000000000001042632662e406c95f3fd44a6d956e526907147e7e6d4219c1c4b28a31e479974d00d4ad6e683f6a834d3d4a20830f4b000000000000000000000000000000000a29ea98ec25e7827bcb349ccdb2a57926809f3cce44d5ff6cd636460278c8103b0db78fa580e9edd4ecd0bdb21018ff,110000, -000000000000000000000000000000000974c7d17cbf91947ad435b30ad2b639671a43d67da6a4edc7f8bdc11fe817d4d42f687dd642a2be89c81bc36e8df592000000000000000000000000000000000efeeb85860877abdabae35672a77ca9d2cf0ed18ed209fb905b591a841c900ed06d2c32c56bed5f7efd469d369b05b8,000000000000000000000000000000000c67498c6751cc27d871b8711c4739398c501a5bfb688d7e1a73dc7db5c47c3e28b633078cb83745bf5b0d5d2dde3ce2000000000000000000000000000000000c205c03305422bd44082715b90e0a0ec178003d6f5e14a0d13bb0f2c38f2270816b884b4870b75db44ab080f88a35e2000000000000000000000000000000000257f378935772d326710ec6efeb22f8c9b6b549c8a4c0205b75740047d750d73da4e71aaa8ff33b9bd8ab7621b08e62000000000000000000000000000000000c386a15f09c849be9f449a59e1332a1e7f16a9394c8de198c01399a05b0f963921c4c57d49916407ae0d202af8da32a,110000, -0000000000000000000000000000000015333364f4d0d173ef35e447fc189b9d65ef71b9fc4ecba25fb6c8c1bfe8467f26bb9c55ef10bb34125d714b94aa1df1000000000000000000000000000000000cbba9d8ac191032f03c0746f13108962130c9e2c01d47f01174a4c4d3daa7631268f7dcc08dfda317bd249fb6e73e8a,000000000000000000000000000000000864da537fd94a9ff1bdae733f01e145dc97a894733d0811cd67c2648ba61d0b187241f9ec69d8c011f514894a05a608000000000000000000000000000000000a53ea4ff9c0ff71541ee21127a33daff2b39e74301946a86e51dc7834717e7d8784cf92fa5845bc0613b6b869003f58000000000000000000000000000000000582f5a1fcef3067dfcdfabc6af33871114538abcb02fcad761cb496020c7b423fc52f0075916f160fbe03574df97ea4000000000000000000000000000000001244ede8ba0dc09aacdc5d9f886e59bf963a25885dbbe2c3d1f611bfae82debc556ec4c94f0606492c7b8c7bf976ec34,110000, -000000000000000000000000000000000781e980c167c982c2fc8d0baa3907bc5499eafca675ae20a10b25063c9088fd06f6769df505e5900bcaf99e266c052c00000000000000000000000000000000183c12798438ea92db75d5bf76cf29d320fab3653e4131989205f2817aebcb1b13f161864c084fd13a11459d7d5ccd92,0000000000000000000000000000000016c334aec0e19934665596f0ae37eb398f1d6f0d0c9f08189f1ccc219230395124a9da03858bdba13ec5366da54228af000000000000000000000000000000000b156ea34ae7b5c252dd90997f1c693773a463c26935a69bcc0599b95bde9e6aa31649c48b6ee4ec1f0a56b19273a5170000000000000000000000000000000014b2d69e02418844effcbc0d564b2721deae2872cd1f27f61d544fc0ebd5cadc77c6777ec944ef0500db181a5443618e0000000000000000000000000000000004f0d48a25c1eb81233f385af17ab6abf554e1285b669eeb5e884c64d5815fd5fa1350bb361997cf2e317f7c5e9cd19a,110000, -000000000000000000000000000000000879133a3c0e50c90abf1a6ac75bbeca1121c865ef39e9224ddb160eb725e0850a34aaf9014e42174966773e40e4c99a0000000000000000000000000000000004c66f8f5bd462cb27e9f5e1d93e322bd97582b9e81d07b2877103420262e4cfe6d0e3bc07f9f160701fd754793eae33,0000000000000000000000000000000003c0d6b721cee4e5fdc6a02095674a58075f81b1d28163f81d5b258c82634297009e6bfc8193969e23e196cf7a99ad6c0000000000000000000000000000000013229818411c8e55e50a63df6983150c1d5ead828711131d9c81841850ed76e4712954d3225eb6d7fffd3cb9924f7497000000000000000000000000000000000f42d6e4d5a28dbfda87c806cb0b1bbabb745e63e655c3c6be50411da4dcdc745ae50f71d56e88db8454d40375e325810000000000000000000000000000000000f663ab791b48f76d358e66e8cd8fa40848dff2bbec758ce1d7b3fe02d1f6b3f123cef644d4fd86d6a77b8155feae58,110000, -000000000000000000000000000000000a7e855324ef471b8fefb31967cec84d57953407ba555b672fa59364b303030cb02b6c77933cc63fcd1b8c107b263554000000000000000000000000000000000b50c3f7cebdcf538820113acdb017fcd5d41a4fd679af8dfde7b0c330e3576ca79d09eedc724a93a3f5c90d141e7524,00000000000000000000000000000000197865f685e78a8842fa79ddc728d507e9f31b31666d1952a46f6422c97c83fba3087be70e3bb588260556014523f74000000000000000000000000000000000131f5d85ad3beaabd129d5a5675d90ea911ebd02cddb5ddc7a8be28c33061430d684d123d5c516785d21ebf756c99195000000000000000000000000000000000c7a14948f3aa29f845e5ca9877db9f0477af376eaeb45324c21e6f99e738aeec96b89af4df942bffbabbf50172d8e5b000000000000000000000000000000000ed4aea3cb585b0d36972f9ad6943172ca7375b44d1d6e80e0bf97a0b25d74deca4d35ce865c8747f1c7a2771a37c667,110000, -000000000000000000000000000000001706830efca18d3e75ea0f1ca8af23a816017ceeb045694cdbad6d3d9aa5a9ddb123f5097a226a217166de3a82038393000000000000000000000000000000000402132ac383a2fcb17fe73398273ef0c2f2d0d6edabc78f31080d3ecbf7c249ffeef28bb8b37a6ef2a7d726c070dc41,000000000000000000000000000000000a795c2affaaecab6cd2cfd6c8fab6e35cdd646e9cfa7b5e02400ef4abf839a69924ea80152eca7810a5041d1bf58ee800000000000000000000000000000000121426bb945d6f6b385c98a5247b7dadaebd3375dd8b2bff7aa77fddfbe603de89e77baf0e8f36a924c707c53d29a1450000000000000000000000000000000007a6fcb486634186f001c8b99874f0a07a37f1ff4b30599d2f570f1bb4ff290b816547f6ce8b3c1ed33e57630a1d57ab000000000000000000000000000000000fa65924a8f17414eb7dcc54f2a4134568484e91533dd21fd33cbcc37a920f2804516a64f1986e9d887ca189179d07c8,110000, -00000000000000000000000000000000024beda2b950efcee233435f0c748e33aa928f54ff29d3db217d7e32b1aac5f4ed11705da4fb8fd38481382486e4aef7000000000000000000000000000000000c85283ad6e35a72d07b74775df1a4660113d50b51426451f454a575adf9cbf9d7be3f521649f6c367c4f4c74b67ff6b,00000000000000000000000000000000049d9ac43e31faa3d02f8255d207b82e4b27e8a9a61ba45fc4f9ad8048e5f89b58d25d98253aabe29334e0dc09d1cd6b000000000000000000000000000000001544f90a0baea38b48d89bcb337cf5a80faaa79334733b7e6126f55358a7e498aeb61419065b9434cab9d10fe8e7fd9f00000000000000000000000000000000139bdd668462a1b5d3ef1299d47aa91ed141ccbeba5b08a8ee31b023aa78c16514a97ba08abf5c8bb1abbd85b3fe87350000000000000000000000000000000005c7dbb8a22403a96aee634cfc67ee6f1069cd61a1e1831e8faa9d7e1aa5e4f7623f51f2e5b739f7fcf3b4ba77c82ff1,110000, -000000000000000000000000000000000cb18f477abe58af92116101c3f52ad4f6074ed92a08c3adcc6660b555df9cff09dd8b34e032ed81e868a62bda50378d0000000000000000000000000000000013c4ab1558dc250c3b5d0f0fae3db62b8df969bb41e9ecc24c10e1e51cb399f1368bed7375a9b9ad9c7653c868eecfe3,000000000000000000000000000000000b8b8bf2b25c2386e5f3be4bdb387d8005cf055e68ab9a5606f17dbedc4fbd7a11314fd646d08bbd6e394485d4f56f5f00000000000000000000000000000000173a45d766682f82ec2d69aed1d80ede2477c276ddaa8fb97f5f4d0515b2c2e370c615cd81c1e361f95db855c9b1b6e200000000000000000000000000000000115868a9187a0465a9309054e865ef224ec3c88a5eafbcc25f9a912ee3b19084757a90b72a4038ba71b10f59fe2f93100000000000000000000000000000000006c5476eb8aa1a471d289af52c7d1df55f6bb1ad53d7eaba6bdc2a97fcb24ec480f9d8e12079d366f2213194c861f016,110000, -000000000000000000000000000000000188f650fdc51b970d16c0637ad5e97aade93c7f1398751439484ec6cc56613814908e51cfa7f68da9d980bb9dac47a400000000000000000000000000000000081834f86f1310135a2cb03265f37d9b7c9459bb149bc54b5a61319a7cde36c6a2a0fb49f8f1fb9d80f07b84f799119f,0000000000000000000000000000000016e8fea4d09831146fc35bcad28e441f2c02e4d17838e04dc7cf909b2133297a13f07ee927722f3d78e36721d6848e3400000000000000000000000000000000114dee8b3a47269e9ada05ee015a874d1cbdfff4acdf5310642f829efd08f78dd6110e1c7a514e7d76aff52046f4ed140000000000000000000000000000000017b9d23f7a865a3ca61197d841fd9195805a9e883d79dc7d36e82f504e6689ade0e84c70a5c5c516fac3e3c643942e160000000000000000000000000000000001ab82b2a0986dec3211507b8adca351829b0a13f25e281f98f54d9e0e32280ea4c638dcb74280eb747a0d9af43b6b74,110000, -0000000000000000000000000000000006f66eb49f95f51ec90df86254580f0ae57862bdd8b5f2759ace63c5f14f8c5a762207f744bb82a8962f8c4fa410dfdb0000000000000000000000000000000004e02a80628c30ce336eab890fa37a227f77357a60be72cb87cc2b7442d2163d395fdc59350624ca1322bfe8619a2efd,0000000000000000000000000000000006bc2ae646a603a1f4524b445cdeb99914e4ed19cd0676d511764b828bfe126e81cad2cb566655f04de1a302c14d70bc00000000000000000000000000000000023bd509aabfa41385e90cd4b1cbbfa45d066c4defab56993aaa386dc5b7707b1a3a7d444b8bd295a30d0b8f4bdc572e0000000000000000000000000000000006f82e60e18cc958375cce6f465db461ff46ed9d15cfcc01a3aff455d54c77ebba5a654c2ec788b6ed8ac53c39defdd3000000000000000000000000000000000896fbe6492c4c297f8b6d60295a7f2565734d69eea67b2675211a203fec043f0d181b1348bea425a068b7bc12676ed0,110000, -000000000000000000000000000000001451bcd19495cea3a19393b77760f688fbf17b107dc131c88cbb503eee2a804e2978d6e8a4720d144083d28be73371d70000000000000000000000000000000017db715e8680a0e82e18b513f2c9c7ea136cefe8add71aac6baba146e3e33a498d025c7e0808ced306c915eb02900c61,0000000000000000000000000000000008604a06a198c3e11458de920176842221667d024f9c155892485a37ff56252be1dc629a6fd580fa41f5e598a23f3651000000000000000000000000000000000e008eed25eafeaa67f27e89e1f81b469724a4b00f08dc4ae672aa1587b19dc615330e3fce0fbd98d7526bc2c4afe69e0000000000000000000000000000000015bc1e4ea5ae2a7fde6d5e5c3e58f6ff5df5bcb125ab402f10edd09087bde39fa27dfcdce7d04fd18ce399729e155fae0000000000000000000000000000000006684e9be8bf9fa4badda842a1d8840f0820d9a797e482c64f4004a18cd63986f19abfc93f6bf068d38eb1e491cabbe6,110000, -0000000000000000000000000000000013a6e129d4dd4aa93cff5489ee879763e2a2231939e609d2d72f07e630b37d09f3057a36fd5cdfc9c81675c996f8ba0f000000000000000000000000000000000e8d7ad082e8f9a718fc2ea712853ed9ab4e8b1a8ca9988f77c70fc759f1fe2d4bd73696e539f130be13b2862efbdf77,000000000000000000000000000000000f15c3d0b40735babb2e38a2471773faa16b2fa307c3a573ef4cfa5a5559574b2d26cf88b19dee204b77f6e11a1b927c000000000000000000000000000000000d224445f3d31d381bb29c4fdc8130174f5bcb957f451c92f4a652cc3d2b5df985017133a944849b5228a88f99bec771000000000000000000000000000000001338b48bc1fa229f251bcd4828654baec9d149f090b19596ad3b444eacc7bc583f97d9cfc40d5611fdcf89cc9a88e33b000000000000000000000000000000000c30dd2aa51f6577d57175edb3ccc1b324717bc195eb0073c1dff4e5b0d77cf5e41ec233527b3936994e86303f91b172,110000, -0000000000000000000000000000000003379bc10acda5ed1014e2bba1e30cf83b72fe69259eb35476a031b8a898e0183bc32ee853a85fb3d738424208fc880900000000000000000000000000000000175a2e5a44ed62744fbbab9581ea7283470bff12436dfc414ad80b4070f895de3786022cbaed55bdbbc4f68db7460548,000000000000000000000000000000001735e1f2fe905839fd6534c95b95322f8cc86a4c482f1ad7691b9b9bb8f55015b4faaa1f243786aa33b5874817cd09c80000000000000000000000000000000013f1a27931ac513145f2601e009cf637ba4bdb18a7604f89534fa3ec8488f5b6eab9963c5d753fdd34cbe7d2f8eb8a5900000000000000000000000000000000092d8f800e7a4bf6f9a25ddd7f64fc403db53b1695ae59c15f229458f347a8e7c2ebc415af2d3849282b670c5cf6f8600000000000000000000000000000000019d22d694e559c55db63521e7b60a1a2342c3cce868d70951e5ed32ec0f5efaeab0e78b21359110f6e769776b745938a,110000, -000000000000000000000000000000000b384a9db472c38a5d246da56059b7630f002b5f4663abce7c5f6e896222a1ca1ac02883a1ec95a4ef09bcfab7d0652a000000000000000000000000000000000de09ef45aafa56936e446e18ef9ff97ca8b81c295d35cf7b72416ebd80586d0fc479d86c23295ac54a23489af045ebc,000000000000000000000000000000000d7dc499e5213120b3ccc173c83d3c15dde9e13ef57238cad84889243b35c8e69eea2ac7ef7560051dcd7402b46b733e00000000000000000000000000000000063ad31c17eb17d39cb4b33e45a0b0e951becc11b685b10cb45cff268b6dca40b780f7e1532be91903372c413a11b5be00000000000000000000000000000000140da959456cbd34e041409350d6106ff65ce6dd2ac3149f04959b16eb83dd0456ca11e5990daf4a1e5c23d3f30a6c4b00000000000000000000000000000000195d07ab127d49baf89fcf5eea1f5e4cffea1a577a5c864c0e637fbdfa10182adc1d5d4ebb871949300193e45ae0fbdd,110000, -0000000000000000000000000000000014df33e7d3ef2c339b958fee667097ccf146556261f7db4b0b0a3c29897b73a0ca249866cff1461488012bc16df43b0d00000000000000000000000000000000099dda253a43b8cfac580306267d9dfeb2c129ac1818fee43c6df5e582f5fa726ba73e1a2ef6a9e011a387c393529678,0000000000000000000000000000000013ec1ef25b303fe2f10a0bbe9bd77a4b2a055e176c2870c99e63b4baf2b313a835459263351dfbc25c22ea32946d8956000000000000000000000000000000000cb1c3292a2e0c9b1c1ff43cbf7595f39c00fd413b54782681fe75a6f5f231d13912f8d598dd8aaae8159de083dccd8e0000000000000000000000000000000005385f2d4bb6d94d67b2a3bacd3aae31da282707672252c0ab1a12fc85d8e9b9eb75454eb145937542099b860f9d6dce000000000000000000000000000000000e59506f7733a38a7e1da4ea5958de4755b52a9307ba2e5813131b33b86f0e401f97594d9674ff1667068a1ec3c9b145,110000, -0000000000000000000000000000000011c89c8d7e83155a308b2e517a23f05a4a55353332292b44b0a891b6f730fd126bd0b97eb87f0fbdb6c82779791d022f000000000000000000000000000000000da6f02450955bf26e236ec63aaf80a018ac34fd8784bb24a22a1fc5e8bd686244a923009a10cb38b1422534d0997afd,000000000000000000000000000000000f4392a41fb3e58dea97b97fd22e2fe6436c3f9bbcd944585a76a5f1a8f98ea4ee21639208d765b6c3a7d08f8cd3f3f00000000000000000000000000000000002c3d62794996dbb881b665eece98926f41a42c21539125fda6070d9f69e29e0557c886b42e4bcd97b14134d6e9d1d710000000000000000000000000000000004b93f315822aa1be8250c2e736727d390ae3a862c4c7dda452817f70f01c73e6f344df1b0f05f03bd574edecc70902e000000000000000000000000000000000731403981fd6243d00c23d0a42a759016f7907548847743f18421f51b1e72cea92f0c5580328babd4ae3e15bc9c56de,110000, -0000000000000000000000000000000015bb227b5c9ccfb8390edcd158b04a69a88a3b99a10ae90e548182751a16448df25493061afde2c9790a0e75e6f409a20000000000000000000000000000000001d7b609155bf3939192eee9642032e6fb10f57d53916674c60211a37b4a7662759899a9569e2dc730febd23f747a7a3,000000000000000000000000000000000b35c6294b70336217eb9334ff1f1bde9d892d109e947de7f4f5681b3830ed00ad1b89ccd7cbad88ce1586449140697d00000000000000000000000000000000032691e5f4597c06496e9e37907041ddcadd18ca8ce64a8b400b1e2e8d63acce5533231edb66b69807fa2dc026c1d2be000000000000000000000000000000000773ccd132cb215cd98aa17d7fc432e0577b08d8faaa35199000d46fdeeb954e8652566384fa0cc5bcd1724942f7075b00000000000000000000000000000000112e951db3694944fc82fb980547cd8b7f2e5ec6fd2051b6aff2573797bd6a28437848ea0627054af1960ad1de0981e5,110000, -00000000000000000000000000000000017599d71686e817cf58b78dd7586d5b359999b32b0dec2d67e33fb6388411418ecfaa2670a2cc9dce3dadaed0fb3364000000000000000000000000000000001773995b540be9ffbfd276a92c0494e4eae296d094f9f7eca975cf4f73ae05e92bd64ea71ac47bba534044f4072a6591,0000000000000000000000000000000018f2eace212eacabd44ff01d886543410ef72b4d27f8d25cb080dbe4b1d4b2b4e57e4dd40723d15789d9b5104b088d9b00000000000000000000000000000000098e9e9b302876ce85ba486609fd028f357314149ce8b530778e6de586ab057fe59648d8c8ae80fe619c4c605b90784a0000000000000000000000000000000016d20a8ca43d37518c8a0f47566ba61a7aade9ea2cdd4a0907ff0ed862c6b7c64815d50397eebec262a05c6010cfaa790000000000000000000000000000000005a70c2fce25acdc4a95fc2bdedb007d71f24b0b5714fa14910ef590215d25442e91a66b6bfea5f7777f0c6d202eff32,110000, -000000000000000000000000000000000f470603a402bc134db1b389fd187460f9eb2dd001a2e99f730af386508c62f0e911d831a2562da84bce11d39f2ff13f000000000000000000000000000000000d8c45f4ab20642d0cba9764126e0818b7d731a6ba29ed234d9d6309a5e8ddfbd85193f1fa8b7cfeed3d31b23b904ee9,0000000000000000000000000000000012e74d5a0c005a86ca148e9eff8e34a00bfa8b6e6aadf633d65cd09bb29917e0ceb0d5c9d9650c162d7fe4aa274526850000000000000000000000000000000005f09101a2088712619f9c096403b66855a12f9016c55aef6047372fba933f02d9d59db1a86df7be57978021e245782100000000000000000000000000000000136975b37fe400d1d217a2b496c1552b39be4e9e71dd7ad482f5f0836d271d02959fdb698dda3d0530587fb86e0db1dd0000000000000000000000000000000000bad0aabd9309e92e2dd752f4dd73be07c0de2c5ddd57916b9ffa065d7440d03d44e7c042075cda694414a9fb639bb7,110000, +0000000000000000000000000000000014406e5bfb9209256a3820879a29ac2f62d6aca82324bf3ae2aa7d3c54792043bd8c791fccdb080c1a52dc68b8b69350000000000000000000000000000000000e885bb33996e12f07da69073e2c0cc880bc8eff26d2a724299eb12d54f4bcf26f4748bb020e80a7e3794a7b0e47a641,000000000000000000000000000000000d029393d3a13ff5b26fe52bd8953768946c5510f9441f1136f1e938957882db6adbd7504177ee49281ecccba596f2bf000000000000000000000000000000001993f668fb1ae603aefbb1323000033fcb3b65d8ed3bf09c84c61e27704b745f540299a1872cd697ae45a5afd780f1d600000000000000000000000000000000079cb41060ef7a128d286c9ef8638689a49ca19da8672ea5c47b6ba6dbde193ee835d3b87a76a689966037c07159c10d0000000000000000000000000000000017c688ae9a8b59a7069c27f2d58dd2196cb414f4fb89da8510518a1142ab19d158badd1c3bad03408fafb1669903cd6c,75000, +000000000000000000000000000000000ba1b6d79150bdc368a14157ebfe8b5f691cf657a6bbe30e79b6654691136577d2ef1b36bfb232e3336e7e4c9352a8ed000000000000000000000000000000000f12847f7787f439575031bcdb1f03cfb79f942f3a9709306e4bd5afc73d3f78fd1c1fef913f503c8cbab58453fb7df2,000000000000000000000000000000000a2bca68ca23f3f03c678140d87465b5b336dbd50926d1219fcc0def162280765fe1093c117d52483d3d8cdc7ab76529000000000000000000000000000000000fe83e3a958d6038569da6132bfa19f0e3dae3bee0d8a60e7cc33e4d7084a9e8c32fe31ec6e617277e2e450699eba1f80000000000000000000000000000000005602683f0ef231cc0b7c8c695765d7933f4efa7503ed9f2aa3c774284eabcdd32fd287b6a3539c9749f2e15b58f5cd50000000000000000000000000000000000b4f17de0db6e9d081723b613b23864c1eeae91b7cbda40ecd24823022aee7fc4068adc41947b97e17009fad9d0d4de,75000, +000000000000000000000000000000001632336631a3c666159b6e5e1fb62ffa21488e571cffb7bc3d75d55a837f242e789a75f0f583ce2b3a969c64c2b46de200000000000000000000000000000000184f1db9ac0fdd6b5ac0307e203d0b4237a50554eb7af37bb1894d9769609c96c8437e9d6d3679ebd5f979eb04035799,00000000000000000000000000000000184af3f8a359dd35dddd3dfcc6f5b55ed327907ed573378289209569244e3c9c02bdf278eb567186f8b64de380c115360000000000000000000000000000000012f5ba8e520c4730ac1fb75dabbfdc0181855e5ba2968a8c0ba36a47ab86ac45d19aa3d55f15a601e120be1f75eefe240000000000000000000000000000000004e313db704b103c2c1e3a58f8e95a470e7199081eb086e9524583131714c4a3db551fd51a3f2314a19a658e7b1765380000000000000000000000000000000004040eab7416a1703b0d103120506f1de2b26b0f48c7a0ea63dca4d9ad1c478ae03b5d7bfd51f4cd6f8cea26212c4edf,75000, +000000000000000000000000000000000732f171d8f6e283dd40a0324dae42ef0209c4caa0bd8ce2b12b206b6a9704f2c6015c918c79f0625fa791051b05c55c000000000000000000000000000000001139e8d932fc0ab10d6d4f6874c757c545b15be27cdb88056ed7c690aa6d924226d83e66b3e2484b2fc3dcd14418ee60,0000000000000000000000000000000017fc341e495bf4ef5da4c159a28320aca97ca28fe3a0441242cf506b0f89bb52f5b5d8c6e038d229ffe67d00151912f00000000000000000000000000000000007666300b7be3d904ae3d19019f7be5cf5ba6161b969c1a78aff639a24387d8fdcc4d0e3cd81ba6f063ebf2d859370f20000000000000000000000000000000007cc705dbfb5c0418beb1cfbd864fa0631bd60eccfdb16b5d55b6ef3558e2ec87dac3b45294dcf04a064d6d1eba5a6eb00000000000000000000000000000000052cb9c982e6b05c1d2ab4eed1d8082f96426b55615ebc6a53bdc320ccad0aad044395ed641b3176b554f19e62d46b73,75000, +0000000000000000000000000000000019a9630cce5181fd0ad80677ed5ad8cd8bce3f284cd529175902b78ad4915f0df56f0d8b37c87c9ddb23d0342005f1570000000000000000000000000000000002cdd00b7662569c9f74553a7d0585312a776c8638e54ad016f8d9d25df98651789470b12ce2626fb3ad1373744387ac,0000000000000000000000000000000015ad9155037e03898cb3b706f7105e39d413ff3a5abb65812b8d21d003cab8fbb607d3938ccd6a774bc8debfa30f42760000000000000000000000000000000019d6382bb2d78180a8998a0536d67412d00ec0ef65f4cbce01340b8d6e781c0ff790296f8cada28966b147c69e02f366000000000000000000000000000000001290c2c205b748069d0875a89ca74a3b05ad8218ed46a1570696932302983c090d96e17e0b828a666fdfc3b72cd348bc000000000000000000000000000000000114f2f7ffaa9f90b547e86c863a5d3585819a78b095848dfa39576a10874a905488687b73e613f3d426510f5d1d1ce1,75000, +000000000000000000000000000000000e63c4d12a38837354bbcdf4f844e5dfe727ebe292016748007d162e74c1f849787767f7e77fc57a42783fe0b06c24c80000000000000000000000000000000008d879e4891a891f2e7d27eb95aef70d5b785b796620ec43dfbb6ae550b4effb9f24210dc20f401d54420445e21cfdd3,0000000000000000000000000000000012084a53cde353a46af17cd2fb02c477e47b874d8ff58025b5015837759032ff98013dc5bf01253bb964f035183c9071000000000000000000000000000000001659272ab7e3a070a5c7b25a5d3402f7371ed67e58cac8438df41c39c1acd95ac5886b030384bf537d7c4bb8ddb2c538000000000000000000000000000000000852ddcc37a09a0a8f62dfbd1ba5064c1f6afacc9a279a4d998bed643eec5a0d96d6bad95701a04f52c83e8f87f48d5d00000000000000000000000000000000097a399370875398028d42bde8cf4e9641730af7a2971e2f59c95938120603a239c65030ded4323c955f7fd24bebf31b,75000, +00000000000000000000000000000000028d6de947a3958af5b53578b0ceacc7ef89d36526d8f3b6fbe787af69fed2c85cad3001643b81c575a741c4566e617e00000000000000000000000000000000182b56202f0494bd8baf5c03969288a1288b8ed8e6c7f49ec9f7493ee3369eeb42fa8f5fb7b243fb2bcee6be244f02be,0000000000000000000000000000000006f8191123f1e8f6a05e4e663fa763c8a0ade5de3c7cd38ec1c82e1c85f123ab51fffcebd677afec8e9adecd8d11263d0000000000000000000000000000000004fcd825bc55d044eb70e0bdd5ea2ac58ec1487e903b431c57a640c756265a382581b8450fb15dc649cf22a8539088220000000000000000000000000000000015259f83d76490bb868bb88c2a2c3e07a326bd3e97fc2f552adf85722a360a443d720c328076e35224328e09494746e0000000000000000000000000000000000f76b0b960a1343b4267f5aff44901fd6796a778b1a87666b95b773edd0e7ffb6656d4f0cc3b9b38bc6c0ed20cfce153,75000, +0000000000000000000000000000000016adb5935f32bafcccb81cf4d177dd8826013d85e11a4aad66e3aa596e1183aeb9d68eb8cf5b716a8a9445ea81b40d7a0000000000000000000000000000000018bee24b0c97af8aec210f15bbb6acbb76168dabe16e669d5558d8d32f00fdf5146471922fa98a28f238974d327996a3,0000000000000000000000000000000018bf5f93dbc2c37479b819f8edccd687c4d3c4dd04f8c73762fd89d0c003674e3b2ed749d23e775f925279b3112689f80000000000000000000000000000000008a033b197aa8ea2213dbd7ed478d98c25dc6e9f91b9924f3c14124da26a67bb196926e02da89b746f2a67b14ad226070000000000000000000000000000000006f7824bdc9c53212609512858278f79d9b094165ff178e3da8776e24311bebbd9deb29f366d4c7693a15c34df118403000000000000000000000000000000000edde25fc24b9ec58b3c317aa3ae48dd5fecdf6397ed9636ea042722d264db0b1a89a15a1e16e892755730ef52796527,75000, +00000000000000000000000000000000114285411713eafd395ee43bf1728f52d17ac512b9d0cddd38c904a9a3a1b30283a3918cd2cc3da6a7d6b4ff923cbb6e0000000000000000000000000000000018a067f91f94b2904c5bb6900f427ec4e93374b5079c84707feabeabde20b5e49801f1f3c7504dd27da94d5e754df4ad,0000000000000000000000000000000002d28025f4b798083aec3ca9a91a051ce27a374b115c944932026b4fe0dcf68b335d5e47212f800c241c2d42fd219635000000000000000000000000000000001742fb6ef8e9a5a7572b0d3fa4ae8ae56c9c6f4daa20d0b88212c40511c6f6b5ee98314a2d1cbe4bbbec907495a1ade8000000000000000000000000000000000d700a511a58c1b8f11153669cb21d88512dfdacbabe38e402431b4f7ba374b5f9a88614da2d56799d39324e9d19e27a000000000000000000000000000000000c6068bc7a43d614b8f1132b13e04f66d2fb5ac0c5bc8501b754a0bcf4f382db92b0994c4999e104c9d1111ef91d5edc,75000, +000000000000000000000000000000000dafa9fa843879038fd1566c319c24119989090c5fd34f6514e57f633d3709f0aa9954dfb289843a6990588e337b63e6000000000000000000000000000000001742a98dd7d3671c2c64aa71023a0040e936fd726c062d520626113bed471e53ff3e85737e5abf9ee8821bae53135f20,000000000000000000000000000000001350c68434a9b02392e60540a3985bae8daf9a170b30336ac73afae6f892c7ae8f5f1cadfb2780d6e5961ebf91cd69ee0000000000000000000000000000000000c20bd286fc1886b9b28dfa40d1a27395cf76a8b73946849ea0a7b5e12530de13c16acef8fe2a2c247ea65ca023eed70000000000000000000000000000000002d8ffd0235fb60fa573662034d46260e0c96396537b2a9d486dd03bdd13c5a1efd2d3cb9849ed11c4376b665f378226000000000000000000000000000000000d90ca1b73a6a9566832f9f19d8530a3b12f22bef853fc44088559b923ca108cebf4291e0d7de8f25c7429d455f5ae46,75000, +0000000000000000000000000000000019cda532e5d94f3b193b3f286a038637a736c2b87b804efd4779359db5bd95320e06d6d28da3c229ae48ffc02303fab10000000000000000000000000000000018df89e4a545bfb825bcce2f4c25f2416a72e32633b3dead5205c8b7d69c78f119d0e940e5bde9ae1cf91574e5d6c175,0000000000000000000000000000000013f223602e8d12c3bb51cd393f6f59beb5c55fe80c3fc8fb0bc90eca533d9b7981563a30ebd727ab6cf0111fa2d3099d000000000000000000000000000000000962b0585c681894cb701f17ec06c0c240899db574c02d82d85ed4dabd4b8654c29b84c71d2921986fc2abc542a3ed9f0000000000000000000000000000000000f0e79245e645a6e3fb88b9103ede3e6ecdd7e45d61b5755d7a8d100d80719746af58bb23d3068cee7389b2acf17f8b0000000000000000000000000000000017fa0aac84c58283f34b9bf713cde98c175b38e92503c08205350822d778f3dd5bed8051e185c495831a628aa89335c7,75000, +0000000000000000000000000000000008ad60829ff001404da40923806e496640a90c5c258a41ef5912f8a1a20eab84ce43b2b5aa4aa7dc4d8b281591d235020000000000000000000000000000000000f13dfef4b3b83aa7f9525eae9913e10502e77c03c55a7aa2de083dc5102c098b6f8e36cb5247b827e30fbcded9e2d3,000000000000000000000000000000001062c97c214b86518660c5e1c33a4e48923ae89ab7d8bc5c798e631de16fc1f104aa957d3e7915aee8551e24aaafc8e6000000000000000000000000000000000e42b785f17f25b87a0dc558a8d57b19d8f41767c3b4fd70c147e95443aff2d9a743003da41d578a2b56d7dc748cf59500000000000000000000000000000000111fd38cd2f5f681bb37f6239a5eea820ce3f01023c685f8e7e244fe9aa9dcbd18f0e50705faa5d8d66b28af9f371c630000000000000000000000000000000004726d3e452f6fcb180ce1d50bbee3a23f7949b635a058f12de1cf5abda19c042168feea53211dbed0bfca489a020930,75000, +0000000000000000000000000000000010468e5421a72ec85b63f7f3070a949223105763868111424fd151c8365eb0307dbc9cbc92e5dfb296d06ddfb58d99000000000000000000000000000000000008149ce856d489050ea834452bc66f7f3478c2056969354dca8652f3d0a349e40fae0c4c57ff0f5e022aa93c61f8c844,000000000000000000000000000000001211bb8d3bf65b60efc7237ffecddb4e7e2f0dd36e2a704dfc9f4972897addff1a57182f8e0a0ac08c9af2c98eaa4c560000000000000000000000000000000007e9877280aad45a3b1453b6771ab509e4f53937cc6da73d3add50aff94869b27f49218fb479fe19a6176b9aadd36e35000000000000000000000000000000000ff915801695a281f6642751be77155a813847ae0237d77d2edf836aebac02b659b98d49842d4d10e82d9d146e63a3da000000000000000000000000000000000fae1c8c01a2dd94f17c660353d158ff6f3eed4e6375f1e414ade9d6fd040a48e3ff0d558c882e92e74bd6ef4ab06168,75000, +0000000000000000000000000000000006295de7bfec61f06a56fe09afbb74be968329e88ba2e87afffe9ea9bf646ff5b4a03d6088e87644958ced95eceeea08000000000000000000000000000000001443e61dbf14b6c6ed99e1917ecfbe5a4a23ab9bdd3bb089fbba76d795d715d9d2e3c7d8db0b7a9434ad691b68bad3b2,000000000000000000000000000000000dd00d9f31cb5148048125668286c1790cb7294e740df978ac0bdaa6e1c4ba139a04f5770b194c9bcfb123d9b40b6acb00000000000000000000000000000000085d5f4cb831720fa13cef25464a1ba7af33abcc4079d2c5736a219ad9649ebb5dbb8687a2d3952390866587d7088f72000000000000000000000000000000000de377d773e40e1c76e218b969297d15f7819c525ce39aee5114e8405bd7361116682cf9d673574d415a7016b23b567d0000000000000000000000000000000018db26c2097f72b8788ef5aad2d7aa400627e224924afea1ac7c7a6b5cff4a55255e218572614519a536eaaf0f65533c,75000, +000000000000000000000000000000000b14b12ecaa94f9656be54772be9b22a2495d4ff873b0bb971c27ab1d8b940c84cabcf921f6f75e93942c38cddeb87500000000000000000000000000000000019eca0daafbfdcd3b56be863dceb21e624b22c0d376fb92ba606456ce3825981713b88e40b7fd801e915f97d5c29ba75,000000000000000000000000000000001853b4c4e6fcdbed29c5d3aa4a9f6d447adc512f66a32fdef06c6ad316c42eb3ca47ffe6f21318ad610d0a68673d7bc300000000000000000000000000000000123d15c37fa8b1a95229e28500c9a767e6286b780138dcff2714bf1f8242f39bebb7d86e2811551914719ca90fb5615f000000000000000000000000000000000537498c2ec64b2ba58aa0a858b69990cac544d5cac29abdf6a42ae9c04061f83580b79c2a6104ebc55939d9a2bc5ae2000000000000000000000000000000000b348c19aad3b67c690512f372d995555ee38bffcdaf33bb827160d6929d2ce598523880f6136f11e1d6482a654cb016,75000, +00000000000000000000000000000000104a452343a4098e9bf07380a8e52050259da95f5fc88f31511a08090bda85f0a08d49cef95bd26c7181aa3eb0be122200000000000000000000000000000000012400aaec3d2f4a1a8cf3f28fd396133c3999c074a565c110354472ae29479b9b62ab67128521c2c6ec4869811ba760,000000000000000000000000000000000994e7b6ccafc996f672c42ab491105ffe1482e65aeb456de2213b531889773ad4d5e6ea1687d6a1f13e74878766f11e000000000000000000000000000000000b89030486a1d622c97970ee7da6189ac341b9cafbb4081463f579ab8b4b049c6e6c8b63157455770a79108424a14f24000000000000000000000000000000000ded43800a991f8c37282d803a39941d3bfbfbdc56dbf7500ef3d16750b27dcb1ad93f89714395fd3dffe318c1771375000000000000000000000000000000001994144b032e1f8c4d688754eef82cdba0018ac47030fcb77e8fd920e0b0336255d2cc8376c03e1074f91269cd2519d1,75000, +00000000000000000000000000000000093e04bfcbd77bc6bafeb77f02d0f794a20b155435ee3af1d667c025e7645d9387abe0ef281386339f461352da93fbe2000000000000000000000000000000000481ffec570d4e155ec10e0cc58effe7a5651795d604cfda6cdbf011676772fdce2c25227e7d5a1a26748d15b1668091,00000000000000000000000000000000195d99406baadc7d8740962cbbf4bc1f22b08eafb52f3cb3c588b6cb3cd89d16cb7b8d388563289f5b5ea466128525c80000000000000000000000000000000004809f70463633595dd763d658354df4f9b409911e1a0328fdaf486d76ffb410d7c6cfcc2d48fd6757d5c2a4834f81fd000000000000000000000000000000000654f8475562098a2cb27ce224674a383283cde35173e1c16b141998b641ac9ee663d766f045451a7f6d600973f0ec520000000000000000000000000000000013bac451a44982c7b1aaac7522dab598cb79b9a3dab77f4d5a4c1c97c154451499979af1f86ced8ce2099bccd400420d,75000, +0000000000000000000000000000000013a3c5dd40f7d7fbba7563331917fe19a093d5d25ae7993200c39460e0c46d839e3958b672b4ed195300f398137faa18000000000000000000000000000000000255bc4d313fbd61a270dce8c851f1fa09e6ac5dff9b9e8dfc8a236a1d44548cb079023ee9b8f0f5756b39e44489c3f1,0000000000000000000000000000000016ea88d0bce32981f489438df1bc14e7ade7a45d449ee1ac1a041c1204460cf53ae5c0e111914d8af9e6b3b7fa394484000000000000000000000000000000000db571ca6a55bc8285421553a373048f7877ecb9683d52acf07d48e1026795993e4e7177490921bc6fe1e63d69c2de3c0000000000000000000000000000000011602919de1df6cc0dd36a59c84ebb8e209056534e336f5074c9ae5323f8a03b123dc6354cf85301d838b16518ab64390000000000000000000000000000000004407d30fbd632fd493055bd4d8cbed337767a2ac534411a3eabec570ba41d2ad28ef37512a7da3611ad60b6536b3f07,75000, +000000000000000000000000000000000ab7b4dec955de92224b234c2d8bb2e3881806c2d36a9a21036e9412f0a8d3946027cbb65b5dd9c975e01b3f235b883f000000000000000000000000000000000ffbb55002d9e926b3d8e7d963ece82c14afaca8b4d8415df8f964a39db606ac99f9e442ff69f7ddbbc4ae563b836192,000000000000000000000000000000000c1e7b188697aa9a053f14e2d907f2c61a59e0b0c72f9cce30faf81dc714a50113500ca9bc3af6657a5d214f52c90616000000000000000000000000000000001544c35d712eaf79d8dd5a22fbab72f8a6843728898412a7f305b205f8a50e03c6c462b87b3ac165e9e6428e0a44a74a00000000000000000000000000000000029ebafd90a1a887669fd0ace762a66bca2bf0a216333b0ac97dedb6bff3dda2bca1e3d0ed5fa9081c2887fe6a8e24cf000000000000000000000000000000000e1a01ca93ed268e0291a937483f7f8e252c91f9bd8bde55271b0c97fcbbb9219009514217dd8bd7e0267f44e9927a93,75000, +00000000000000000000000000000000103469c08562f6f72152db58b48811b0098b68af8de00e652bd5a67246459664cc8c54e15705d702d51e3f1d8ff76a7700000000000000000000000000000000059b326dd567fb2f8a6ae87f41fb22b3edc25122138a5f6732edb48ed7fa1949eda6144297f54faf406d873a016a1510,0000000000000000000000000000000004e8ad9838e7e269cddf0ae5c8f0f57e7467e0b6f2b9e37e7c4bcae965e9582dc46c9c50aa01f5dc761bf2f1ad311eec0000000000000000000000000000000011b1438ccc668900914578c3ec6e1334d0823861c892608817498fe2e538deec73e0034a6e8ba9790f63fdd95af3714a0000000000000000000000000000000005b4c88196425d3ecd22bfc0cb1a95488493f85bb74f50315f0ffcdd57ad2de23c137cd6d2f6f6dca8af2e3f7bb0539c0000000000000000000000000000000017066344a0f345ecf6a2ba66c37ccbce26a3f551524f74636d4c4812bf5adfabffb0645b898b10c332e94e5f2ae2d1c2,75000, +000000000000000000000000000000000bd594d2f5e1472f85bfd550df3eb948085781459eb3037fab34186ad9a0204a0767c8fba571af858a054dc231931b8000000000000000000000000000000000087b8398406c1e707fe87a16118e2448d6a5f4fd1d6c9d7174c4d8a4314fc7b2c21f04178533480976dd20e28b278ad5,0000000000000000000000000000000010d393bf893d589c578df58f4d0098ad3cd10d3a1d0f112f51b132a369e68c0284a6b70a5673383ae24a27a9043b16cf0000000000000000000000000000000003402afb77b187b45906d9cce348976ed88c758d75b9962a53352a6c3ee37751a9928097c0d68c6f8a315def4ca875200000000000000000000000000000000019b98631e53a3ffda3fb9165ef7236dad5c0c8d57c3315617cbd3ce77430bd89b9e1d88a019042cae0075594514a5e67000000000000000000000000000000001783bf1c9b0ec44c9191dab01ef5bda0cb2f533dbcd3aeac2b7c6720dbc8e3f770a215ec8ea2035129711ce4b448ba87,75000, +000000000000000000000000000000000673dface7041c3d7503ce4a50af946d344ad48327b515740b45276403d91bf1ef9deba79c8ffa0126be990b62bf3072000000000000000000000000000000000adb42b7eb0f6759a04da7b933bbc2b6aedde47da8571d6fa32268c606dbafcbc810844017eb6377493a12d76ca56c03,00000000000000000000000000000000086ac901098212acd091d9c4d42a1318c3b343480f1130d6e52128d61df9e19fb61ef1ff35de0ef60062cd99202910ff0000000000000000000000000000000019109b7292f1a420f09a56dce9694cb4944808a2ce9f1964cbb6ffd14a710c35abe81300090ffcd9e95f33e0de9f879a0000000000000000000000000000000012660c4e114a215390c6f6eabc4bd6e3d062ee28d0c87e24351c7d43195253cb7b5bcfed2b4abb2fdeb3ac04ee228997000000000000000000000000000000000e56d35a7e40a86ffd2088c81488265ecc4468d6cf02d563c91611cdf8b4333cf66ef50b993fe651b1792d2b242cff94,75000, +000000000000000000000000000000000f554e52c4a6c5a94fd09c617f57e8f87af57e73ceaee8997fc62c8ddcb2f875ee805e6594a0fb72738abd3cd4748ddb000000000000000000000000000000001876dd03316ff007a2efb4c5f452d8418edacc2881b20e8340895f6fc768d14fd89bd9db3dcfb53fa98a1e96055fa83e,00000000000000000000000000000000071d3e796fb15d63c2d5cf68f59f11792b0b580b85c8839a02fad96664f14735ede2edfd5ba5b64045b366904f54ab600000000000000000000000000000000013fd1ea38d32772458622731b9e2d9d749f2b747443f7e47ef5e041531b56f86d1775d42a548b2bb201228f49ec9f46800000000000000000000000000000000099c2bd996c8c5ee37de971e8b75a0bdd4f69299778ee3d216973c9dbba97c7a93e40b209d390024bc4b5e82560a1a83000000000000000000000000000000000c4922ed9af845467440b78efa3a53ba904f29adf66e8ac437c8bb6624b5e5ba0772a5639b45fe167b1fb9283747c50f,75000, +000000000000000000000000000000000e8b2369fc2c584d78d52037b109aecc87dea0eefc2da46948b5535ad19c9abdb31aee66739f4852a2d3c51f2e7f74e900000000000000000000000000000000168b2d3e4b67390cb8ba5e48a7a823db08edee7d8eff41b88cd653cec1fc0df7a55303d3c91e92a2dc8ebdb327b225fe,000000000000000000000000000000000e413d72fdc3db6fc79ef26ae8b37fe5c4356a80b3598513b5173b3406ffb54708b8794dae158060a1accbe956a39ff30000000000000000000000000000000019ba9dfa74fd241a55a3b47c9f37c6ebd1e8b51f46197881abb64b7f57c0e2d8f18edee35bb9da03702c0dc5cc8749f700000000000000000000000000000000183525156fbc80cc67d6cd15fd2ddf7fb0528656ec1d31b4c275ef101dbb635424abbff1154a3ee04346ac53148fb1f70000000000000000000000000000000011da0dcd666d01180902d8a7fd7d2fbb39f9c7587540451045956108a8579d7c116385a81627dad9d4cb8cfe68927b6d,75000, +0000000000000000000000000000000016cf7b1a9ebafbd20c078948fc974bcca9b8069edc1ca5e8f364f8ca2a52e56e1a424ea6bcc4240f46dc7f262760bf480000000000000000000000000000000011a6a67d4501a8d9b3ab985be59ffc41e79c453bb5548299abff3b83ba9ff951025a68fe6a8ad3eef3c02d39fca8f909,000000000000000000000000000000001932acb1fd0708edf13c293007a035991bdfbfe0089b61c261258e8c5c10d82a5318b2af221b372f0f3f43c391421582000000000000000000000000000000000973650743f0ec8e2acca33f2ef230ee7a05635d14099cdce913ad8678458ec0dde5c5a941097af2ee0c8ffb937d09fd000000000000000000000000000000000bdaf319044101ee9aa27b3accd36a5ecaf8b80deda4548377ddeb97283537be3f7199ad3c190ed23cdb44abb8786a080000000000000000000000000000000006c448827e3fe4f274bfa55a66bc76c5b01e29ac6a8dbebd801855ba4e93bcbd03292ccf804f07f21481260c135b827b,75000, +0000000000000000000000000000000010e53fe9fa94ca622cfa370129c1619b2426bd9d50f4b5eb8a3f681479128dbe92adde15477ad8a4463b08f1a02a62d50000000000000000000000000000000014d10a90709789b25369f0376f39b16860aee1ddc3a4340542abff0077a4af8da946cc29fb6afd9930b872ea98749be5,0000000000000000000000000000000004aee050b0ea07118d76f835218b77b39854f5ababc4e2a29d7c8cc7c18a69c30bb22437049a051d049c8a84f7868ad40000000000000000000000000000000003b1b809d5046054924c3814d26fd5fbdc59e03e5505813bab73bc212b0f5bc0d3fc34478311c5e1ac70fd16a01c52800000000000000000000000000000000002249a026af0b49f4659eca2c23dc790fb36a7b2996188828a17d5852003f1420f11699062932835cfe6543d454521e30000000000000000000000000000000008217aea2221f8748cd81cd37777605a95a63aba36a6ddad72c1e1ac57b24d79ff9d9c4ed71a6e3ac8a378129d5475ad,75000, +00000000000000000000000000000000194612afb777e39d0308a290bf823fe706487c3473412d1410dcb2c0016a70706e70e3a009c0bd61e755b1e4c65bcad0000000000000000000000000000000000ade016d06179faa8d44a9ee2542058bb81724d6af2954c0c09a897703d364ec25e62a3a917c5cecce5c96a7cfba924a,000000000000000000000000000000001274f676bcc05e54fa4b0cce234870ba97a0b1626543d6a9f09afebd5a752769000df404e4d434ebfd561f8335f36d0d0000000000000000000000000000000002877c9438fa319dd1a00f381834e8f3d3cdebf4e1f7690cb82559a2e978bedfd2455be020d0353aa56d435c0174b5b10000000000000000000000000000000009487cc9c7a09be901673cb1bd9a51f45e5d2ed30c90cbdd3e2b294c8f866f68da55533b78152e9ef6de30c345fde5b7000000000000000000000000000000000a3a8d4aabdb260203898655745cb695e6dc90c6e7bf0248784f8aa2340390fd5d8f1c6a98eb1990eb97c2a7f103e3fe,75000, +0000000000000000000000000000000005aaeba19cb0baff9a8e46b901f15735a0c1f45116fe1f41c22fbe1aba22c0a7678bd4799db5cd9141f3112877e2c5f80000000000000000000000000000000003f54664746a5bc6f64021e2f18d8c175d96b1c8ce895809c0e6fcfbe896b3e8c1ac7f7556b9ef953371bb143bfbdafa,000000000000000000000000000000000ef415dfc1e47f39e9632ed21c9c2bfcc1959299710dcd7935a757e3756a42c8f6c627c720fd62f9c486a8e88a64c76d00000000000000000000000000000000088079108fe7d9ac93590c045be0d41396f3204d83793c4e862c5360ddb3268a63f704a9d14323943fc85874cdadaff1000000000000000000000000000000000cce908e8dbb7ec35820f2db5ae1174e0f675b21ae416fc89a7f242df3ee98764022744842999f65132229156d2627370000000000000000000000000000000011e0e2f8513d0a71b48599139a9a29c8eca090c5b02292baba58e07b1d3898fe158cdeb3bbe8edb4a805e695e896984a,75000, +0000000000000000000000000000000010ca243fcabbdb219c5b30092d9d4595a4b8ad1cbed267229eb79a99aef9c5df03d8f24b71db77a5a76917c2fd960ffe00000000000000000000000000000000135d8d92f075c219f8012ce6aebc8e48443b2f33382479a4ca8db0a4f92041d5b6b1e5818b7a3de77a5d30be0e461d13,0000000000000000000000000000000007c6f133647745c312695439f1d8c251e941bad6e988cfe324ec7c959a9e0fb50618984429ff1841d4286922a26873170000000000000000000000000000000008edb220f77ed17fa1f4757a42ec66ad808c1acc25c4b9311be4c09703d547f648d9dd7c8109ffa89d01a35c69ec2685000000000000000000000000000000001595cc05b04f557ed569b19d64c09f4d82e6617437571fddd72a672d07ad94bfbaaed906b3a7e3db519159ec8d0a8c4400000000000000000000000000000000041157d4f40bfcef680af0143ccdd0c4bdd25e598a470dae844d887c398bc498edad715fd7383421fc78758cc9b00326,75000, +0000000000000000000000000000000013e042ccfe0cbb7fa3b045a1fa1a86f199ae91721aaed488b96cc4f6de1899402f81842da2ab55c5bfa63f5b19ddce7300000000000000000000000000000000063cee89d1981f27a4f4d4f23c4d1229fd3333fc8f371ebd85c588e751307ccc75d71d151f7481ecba1ef0cffbfdea5b,000000000000000000000000000000000f983607a6d8a5c3b8a577cbd5d81ad2ae936e714199e3f4095cf280b8fd6d3699acf4d2ef251a571dd1ef4ba6d838bc00000000000000000000000000000000048c12f8b95f9537e56479b1bc43a121e4edfb6477fcb090a5ea60c5f4d01071776dd0264b0250902448f62800f4d2ea000000000000000000000000000000001644ba272d7003d0077991ccb4569638de0dcc48fd2e8e9a41cee1d2200aee1a849f2d620f60beeb06b08c31cd4eeacc0000000000000000000000000000000018892d773f7e48247215484ca0c8d996833c43a5291b0380c97607c86f4ab2784e692673a1da012ac4fec2713d156a49,75000, +000000000000000000000000000000000e07265d2762e8e398c83efe1c43452d91b90b7a4271c09ff693c83745a6c01b73561ffe3da9300c8e7e1602dbaab0bc000000000000000000000000000000000375579c16a167fd9f9f61d5177705f157aa0df3451971029a9444432db119fb33b8c07de33fc822eab46ed4ae47cf82,000000000000000000000000000000000a06ea8e644d2d762520ad956d41ac2086a588450bc34f6d070b86fdfd73cd0734341a751d823935a009b7517770f86e00000000000000000000000000000000140ef0d6a0482537da7db8d775ac3c4a93b16c15fbe4602b5b1843ce757aada5f7776a74151d0bcf760f7284d4ffe56c000000000000000000000000000000000873c90f56a2b99da2f0a1528b8e376a5912f9cd81a159379ad70b7c10e6ebb7fea0a90d65543d968a34ebd539372e89000000000000000000000000000000000b05ff57079386e4e18e73cbff5f7b0efa329ef7355f083e8be258922203240dbb8926f7d11c22ab4c16d1df4bcbb600,75000, +000000000000000000000000000000000aaa37576af2101d090139f562edc2a6e7169b0150af831d053a3a87a3a5518889a51871e02deb3ec154ccbe9dda46df00000000000000000000000000000000158edaeb58b99d9442d608bc8e6024365e9a81e0aa23bbbd466c9ccc8d29415352a153e1f852666505ef097122592ecb,000000000000000000000000000000000e9d6f9e83a2584f2cdacc4711085bd251e060f8c87ff7538ce474d663c6f23361c88971c9da589586e754ed69699c820000000000000000000000000000000003fa90cc1dd81b815704e15c0448bd0e8e8d0cd7ad51237a25d4b8a0f78f532b18ec30a108930b7407b7486aad9824de0000000000000000000000000000000000cb97bce1f75b1df5a4b52745014eb632d2d2230e52a9767e3dfd76754e98252ca81ce274b92a2947f6a65fedbaa3e400000000000000000000000000000000090edabb37f411fae1764792083c8c7412fb470833a9f7399fb312c58687d4afbdc622ecf9d74cdfa3ea87382adcdd5f,75000, +0000000000000000000000000000000012bfaf34a8111a01d213f9a9fc90846335cda978b3df23de99cb7b764cf5db1a816f66adad1319aa7e25c0ab89e7de740000000000000000000000000000000000fed118654a128735fd39ffd3b381ad2d71479054b6bccc04dd58fbeed9b255ce2b925e2141a96a12edc3a19188d1f5,000000000000000000000000000000000cd234fcc729a4206233e46875a557027cb52c96322386b56d6e50d95dd9d23b6f8936ddc6f8475b1076a855c1ae23510000000000000000000000000000000010a774120f607bf9ad2d7bc498536cc9d35cefe384f88a2439a75f1a4f6a9e4b4253daff0d2c91b5915ee0e9a99b4582000000000000000000000000000000001496e7181495114abc0314f580c16038a04a8dab43b5564d518dba5f5e48112ce9daca4b16b6ad51c3af54ec9ce915d20000000000000000000000000000000002c61691a96a2120663c726d7fba3ed37524b58c92a024c15fccc659d1d2cdce077ba233a0d4419a6f237ee4e09abf52,75000, +000000000000000000000000000000000b693fe53cbcd6f8d8c98900be1f9c85966cc644f0a900c70826c6573ee801ce7863a0b170ce0ef168fb1f0ea484b276000000000000000000000000000000000c6bd688fb883f3097f8b6fd6fd0bc5acef9341f21d62a0706fb3625a70459c45a5200ee36a3802d4bb4912030bfcfc7,00000000000000000000000000000000011cd454f16209b0b7040c744291f2df465ebc786946ce3cde77fe4d4bcc4b60a51573c45b8bb2d209da69107613764b0000000000000000000000000000000018a026f29fc2f81e82015ef8610b4396f2e3514ab1a213356953804d585c5cd6a3c5cffbf70d63d9dfca50129021f0e60000000000000000000000000000000015bdcc8c139e636b05ba7376c1ced4a183eb465df53b1996f4ddc8cbf42cdff4ae2bbc2d24831a8ec8b1134cff4444ee0000000000000000000000000000000017671fc3995babcd2c0a1d2a71c417fea84e29df67fa1096fe6d3ec77c45b64fb8da6ed08a57726ab314fb860899961d,75000, +000000000000000000000000000000000ba7f82549ebfdc7f4959dc67cebde4720d76d5d4742af730d45d614133f0a7b0ae7b61ba5b914a997d9dde83b77b031000000000000000000000000000000000b4acd8c203ebd8e3ce12b10cc791b9a4183440309f24bbd60cb2991712c792ecac64d3f878cbe407fa8ca0d09548acb,00000000000000000000000000000000156d8823c37c81d8f03c0b2e61a2342aab6e6c9db36cadc9eb741e085de711e9fda08ca78f21753c4fdd8cec059b6c2800000000000000000000000000000000064d4fc2584c78f1e92f808d4457070b0470eb8de9d558885bba8b03efd8d8e195e4923d8e3382481a0ecee905371ae10000000000000000000000000000000008f1dc4d2ba12e7e3e1b0ef3855df4dbf29468bc99d5cb29fa3058a535af2ba038396bccaa238bba6d538498565c2809000000000000000000000000000000000fc9839b6ee876f7846b5086d487360b8faf133b6f5bd2dbc92a7fe2261b91b15aef8d90c227cd5f8ec05e32d807e022,75000, +00000000000000000000000000000000145f6f774d943a1bb753d5d4876b1a88a4021cb6a6607c0efb07eef2f90ba2a90a6e9dc94586de35f6047332553ce7b5000000000000000000000000000000000b892f1c8d001c8aeddf845c3845c51f2e06c3c77e543e9721d797951b6211a869da97325b569e0de35cf3beda853ac2,000000000000000000000000000000000d40f1c25dd57e36ed305276d4505cb250d2d9da0d5b954fe5e396b2c17a5399613243216586cedb19340e80f898873800000000000000000000000000000000063367c4a622fc925319fc6d119d8592f40f126ae05eed86ee5e4f6707b1d234c747e698c40f292dcb82ac5fe74ea80c00000000000000000000000000000000199ddbb5d4b6cd0fb9225a72c53f4596cf2597de63da56f4a9a18be8321a982de17367b0f3d794fa799657dd8ca10c5f000000000000000000000000000000000f1ed84e4fd958547d40cd2dbf16e2da4cb6d0d02763441067221890ae27ea1f689c26c900b695464ededf083667146d,75000, +000000000000000000000000000000001878e791993186ab76f785b2c6b0fe08588b048007c66fc00c695b55bd17b37bdba71f34ddf75ac441a0c2687711b2990000000000000000000000000000000016598f630f72a0e1f39678e1d0ec6530c4795d7565c5d026fea2389ec0ceb51b434b532466fbb1c92c1c958041283baf,000000000000000000000000000000000ee446310185ce76e31c13e4ca6c43166d971d9b9c539c7d0e8dd8ebbbdd9249922cb674bf6ad6840c203a5e208911fc00000000000000000000000000000000037344752896cff03bc39a9d09757a83c15fbd90f8bc1d8d58dca9b23bc00fa2b0f3f0bd7c9ed857d285825d40afde450000000000000000000000000000000003ef77f0220d1caa7538ecaef1ae2924ac1a180f11004034fc118aeac464fe1ce684b5fc90dae3370e3f79619889f3d7000000000000000000000000000000000fdfa434e7bedec071a1a333088d06299f55735f085a1e907a1c71c312bbb8d27ffa7de7ac69d421ebd675c4afd37594,75000, +00000000000000000000000000000000134725b4d43cb87d2e4d3c43ca98b8df257acfa612ccd61dc0aa1ca749f20bd42c38d933d39f8c3c1a14dd8fec43329200000000000000000000000000000000070ad61a7f5ff9f0b4e7483f5d56b0f315b5f6545b194565ebcf8f0b8d78519ec113af6d70550888be4d661a8403a036,0000000000000000000000000000000000ac465de3832452edcead434729be73be90785158617b5ec3ad53b12653e43721eda7de6742dc51d4d4bb58a291999f00000000000000000000000000000000147c39a5c162afa1f8eef400cfa1bdbe5436bc59d93973f50384022962f828ac934a4f88ab7c3d505b0bc3bb002f5efe00000000000000000000000000000000141bcdad53845a7eb2ec08189a55445059dad24ae5d39fedce869791aa28459f05a6cdf9575676cc6f3dd7d6faf077240000000000000000000000000000000010e9f539a9ced860661472f53147d0347927f065ec09bc32e00c5bc157b07f8b41b05aa4e0eedd1f73c7a287b2d0e5ab,75000, +00000000000000000000000000000000179bc843fecfe713f6e3ccdc8ca0f48759459b675c8b96f5403e1f6da92c2d60449638f564ce179373bce473669965d700000000000000000000000000000000082bd89b49aa62c94ecd4244b3077421569c71efccc62aed3d4bd492bdfe57c0d2cced568df5992a196a7b71bcbe5e3e,0000000000000000000000000000000016479eca30f48bfdaba4c8afca63ddbf59fe3367b2d3c17d15a5869dd2956fc67ebde964530926598cdcb62cfc993d32000000000000000000000000000000000650b4fd24ffbb953ccdb1b112799149d29e2377ee233b9ac97f4db432da63c98b8aad751f6060d04fe1f9262b75fca50000000000000000000000000000000004568dc0b9b430596f2fa59291ea6f923d552683ab9ab93000788145cd7c468c5576efd981c9ecee2ee0c16eca1ecdbe00000000000000000000000000000000154af1490463930d6b8261aa1d066eeda6d65b742cb53c65348e5cd766d86982a1489ad191d1b126233f193d24823b9c,75000, +000000000000000000000000000000000fb118c86e974734fc434c3bcb783e4a7f9251d9fcfb9f4419529354c8a7a3d9f2215de2d1b9f0927b185c5b4db838b60000000000000000000000000000000004da0ce78f3068bebd0a59bc2e41e7ade737375f07d6c9ce962be022856c569a33e8bd6ae60c4bb1b53b3ffc2dcc2aee,0000000000000000000000000000000000df692ca763a74877352af3609c8cdbc184eb71bd35fd86334cb88543637b40b3adbb5802dcd7b88f4d722b566aba7700000000000000000000000000000000181495e709d1617f2d912f43487ad3920ac5f8e47395ec4b58bcf0b2d986c674a0c7838830a039bfb5bb59cd2fee2f5c000000000000000000000000000000000d20b482dd8aad583bd5d08ba9c61b3e954f022d48f9f4f62ddc9f5015ac71dab7d206b1d8b885d5e605519bd33d93a20000000000000000000000000000000010d3deccb9364ee386eb35c7117bab373a76d024627b8a031f96465d5f75b029fa992e29ad4a170c4473cd1df585429b,75000, +0000000000000000000000000000000001f43b86ec24ad40552dc4874a632b4ff4663eeefe1a8c613a19a798a0ebe321a3d543e2df28277944a941b4586ac770000000000000000000000000000000000baaca6bc34feac790807b5eb5fd173c86c12803b76b50be59b2707df765bd10eb467effe34f8dc3e1e79df8a54fde38,000000000000000000000000000000000a007c914ed40c7f2719fc70def0d4752cbaa775cedae9365c5afb61a5e1a2854f9e1ce19af9fc85bfbfd2c33f5bf095000000000000000000000000000000000d85b0d173c25c2915fee429d2468a9eae01ba43c0f1a661f2ef83c1acd726865c00c40ccbc3aae306f93074e5e7858e000000000000000000000000000000000b3df302ec532c8100c121c9a3455392c713ec60de1f9572b040b0966f8ffb888e8cd768dcf6d63d4835a52d13a730c0000000000000000000000000000000001123c43dda8717d03fbc02fa53c4b1c9a931db6b274162cfb02ef5eec602bd8161dedc37c7f6217c8e82236f06e49e2e,75000, +0000000000000000000000000000000005e4751707f3ea7bc7a74d80eff27a0d65cea0c3d2e793425e79cdb0c41e6ad0cfcdbb4de604637c41dbaf30a1e816e60000000000000000000000000000000008f69021794d93826f8207b96d49214b46dfb1778603634a9f5194e92481465702a8be1bc49a7bb57527fe6f963ae04d,0000000000000000000000000000000016d8d9b1b59a22fd830f88b9850576488f75672a87ccb766e52da77f187a8e66071130c7e71f86675f8379b2a8802c4b000000000000000000000000000000000aa4ca84aa23f01ec536ffa25c4b7a6c822f588bc75a4a72ed9237c0588ab892c8474a0f23afc7ff0dbc3b08f8e35b60000000000000000000000000000000001425e759e2537d9e5f0f356ff1d38128eff3a771fa661a839f7a8d0f548347438574ef7d592cd4273ef9b7269c9c5d7f0000000000000000000000000000000012cf1c67d1ce244ae22eec0bf4a400a0f356b9dd075d87a6e61941933872d7c0e42c1d238b2c1704d2cdb2df75169f39,75000, +00000000000000000000000000000000116988a869cf552b2440e16569d8b6e30c6b15430855c4d6bbf80683c5497291bac7999c1f8f08f494fcb4a989451c3b000000000000000000000000000000000e26058d72875fd3d852aa4139f71d35e1edb58242a4939da7986645117d027d20baf85770fc909d537524244da59ce7,0000000000000000000000000000000017f6e2743cb30fb93816d0dc802c24509315363c3652b0244e1395cb9200efb4d7b9fa7642e8d165d28a00740f1a83be000000000000000000000000000000001483644fffd3989ac98cea71843e87b8e446a3d497630419afe99b3f1729a831fa6a49bf763b0c410cfc5390ac4ac1db0000000000000000000000000000000018ad20ae5012266d771b2c86f891f498c2e90a7df19561be240319edc1fbfb316948fb3f8a6b0e3720676b076eb372e10000000000000000000000000000000012f404211899d8fc1221ab5b82db9042ad37e63348871e5ac6cdbddacda0a564888f89d22712069b6096b58c5935edd2,75000, +00000000000000000000000000000000078c6cf89561533810b583a88149586b29da5228ced10a75257b2587904217f63499d8b9ad2d536617247e12f8d1657d0000000000000000000000000000000005b016ede9d892fbd7aea4e8ed0f1eab70713557311481735a91308fabf76fe71e44a06dc23ea66ac5d831e982f401b1,000000000000000000000000000000000d4d78f992f12aefb0e3a6b18fbe2411108327a9befe4a822618fecca4def3169972b4f1fb254cc4656a676529d554ad00000000000000000000000000000000145ef33250240a5c9434d4b2cf2404d9e7cc51b55e482ebc6a8aed85caa21ed00623b3cb2d76ce2d96b2f346d395dfc40000000000000000000000000000000011af2ee2514c58078da335c0273cd18b98d1ac6f0e67890677403f71b0e06863fc72611c0cfba39ac894ae500edbdbae00000000000000000000000000000000186863e7c24cbeb45f7a66b5dddc9b57c7e22c5139aa6bdb82e77cd8182bb8d2fb7bddd7d3516b5422f92e08d02606b5,75000, +0000000000000000000000000000000007160f36f0e5c4ccbcc7900c6504cd86fd6fd700bfa79af69841e4a6127eaad467ccc93c66baf7d767c3fdb1f31c527a00000000000000000000000000000000043fe62b0b9be76a375f3be0d6ec891d5bf5f2982cb2390125ff8d5db57b6b18c5616c526102e4f615963d601d13f122,0000000000000000000000000000000002af4a301e90c71eb375110e7fe23f8f05e2ede86b1a9b240e8d1d4d70e96f1dc3640fca7ebbcde9918deb91f3592de600000000000000000000000000000000058b5f36cfb6b0adb14b397dee4c3769c7446426eb5719aef4965cde2dcb70e6f2fa60101a5f03517c0040093453d092000000000000000000000000000000000f77b560469cd42c5cf3458ae13020c6678af3cddf9bc559372d12bc5d6b930795e1eb09f27cfdb8215f39fb2a11b30c0000000000000000000000000000000003308985946c742af7bd7d29abc2517ff1d225607b5f11fc66695cefabd8f25e294ebdb7339949d6bc4d98db19533966,75000, +000000000000000000000000000000000b9590b1d0d292d9967d759060a551f4e8e4c1c0066a9a3c0be515085847fa26b77462e3bae9e2621f28e01f897df0be0000000000000000000000000000000006ee7c459bb4da96e87eb1d39bd7368de5f60104f85b7b4bcdd7761ce08d48babe1bf5e765282779803bfa972d0e668f,00000000000000000000000000000000093c936d57135b25900bd5dd55cd579aa8b85b9c1b5e8dac6196c4450b624734d9bfc3fda499cedf2e877d79f2da650b000000000000000000000000000000001832306d3ac1c1c61bdaa73c9b6e9c2ccb484c3baa1de6a217a2884c72b72618e864f75fcc2dfaca358181ecbd3347980000000000000000000000000000000002b2e5ff1ee02657fa88c7d6f23cd4c0465152a9daad8479b4b68c97930acb22e4e2eb0011ec4062b8ec46991a7cc630000000000000000000000000000000000712543547e9d24cc78d1c2e3fbe0b51222185f4c6e513256d1ee066ba50beee20321bfd60462e2587c375a0e9395715,75000, +00000000000000000000000000000000044612b42a2baa9d3e1d187b2a4e048773b4851bbd7d4025e0f7f61abee703b5a563397da4515c7379397dcde698228a00000000000000000000000000000000014cbff1000bc0f9b394b18e81124dc81f80e291e841dae6e96e0c86a6f618b9f6aa6103e0e7582e5136319a4dac92fb,000000000000000000000000000000000f52e2f8dff9a93b2985d5c2b8b980e4869af53ce55aa48bc1c9295e557e3b5ff78896e5e6342c2d535d18b11950bf390000000000000000000000000000000013d36cf2805d350c5b748e639d20e592deb4c5bcde99a94fb539dc56d48a862151b925314f21dce4c9130b32e44f54060000000000000000000000000000000017728f485d881b861f626c9de8b3df7d807b266de6cf8dfcba262f40a6248fb5e6506d11e88f460f0b5f1a1907ae5f3e000000000000000000000000000000000c0ab998f63f861c82106dc3ed5ea11a16e98139e8686f8442047a1cf9ac48c3d34b5129263767830144e9a13d4a1f44,75000, +0000000000000000000000000000000013da827dd718d3736cfcec53f034d34bce253bc91f7cfd6cd2666819bdebbfc43a9363f82bf4b580a7739b5dda9c94360000000000000000000000000000000010e94039f37d218ad393e88a226dd324a37e8d5352dedf6d84fa2ed2cab2f874ccc5ce94599950f91b8dd6d6c8b84aba,0000000000000000000000000000000003463d887c4d0aaa21acaa308d77f2c7e13d10157efa9ec3fb1586a8db5ff1a9e807c91c86afc4df34c9fcf06e8561d700000000000000000000000000000000128a81efb9f30ed811ea3163c71b6a46ba2cbdbd3a9f93cb8d0f518747cc860431c6e93bdcdf36d00f83838965da4b50000000000000000000000000000000001777802b7c41111b38da3fd8092c280b4925827b2c1592f779a4ddca71f8268858855c413fd5c0057a652155261d75ba000000000000000000000000000000000c88b522d6dc2000cfbb7052e141ddfe15c6cd7fddc970edc4afc36fc59e7f8e31415706a8121e8e84348be0b50d0d88,75000, +00000000000000000000000000000000010416da7cfbed2768c77b80957053030d49d535b21a8a3297ab257dee0463c91b87a9e571b86bd874522149d9af0c2900000000000000000000000000000000197ef97f6d02a51b80e6f5629e88a3c60399bcc4a358ab103dac3a55a5877482558abed922585a1ce3228ffb507679b4,0000000000000000000000000000000014be96cfc0dbe09155ac8d8233b71ed584153e279b2b2be88471eb653aa4913fd2c33947547c61f7fd8bedbb552a8b1b00000000000000000000000000000000146b9a0011260e2646920894cf405bdebb101db12da7849b30868655fb5f972113cdf2fc322cc246d3dbd9f20b98fe2f00000000000000000000000000000000104bc20e104da5173dcff3e195f80960819a0d64e922bb484c2739c4b7c22535f7faeb1c85188aa853277740b389eac90000000000000000000000000000000019f5aec599f9ec286aefe48eedca3f929ac6c758c231182b92dc965d6ac1f3db53d93f57d733ca8425a5dde070b0dfa8,75000, +000000000000000000000000000000000025f1ac90f5b0748d57d8f7a928be875c5712801f70af0d057546228c1bf83d3a207884c0d66d0b5dbcaa736bfe0aa10000000000000000000000000000000017f66b472b36717ee0902d685c808bb5f190bbcb2c51d067f1cbec64669f10199a5868d7181dcec0498fcc71f5acaf79,0000000000000000000000000000000004ca0149527817b4df0f08acabd4e8c6329c0d1bd9f2e8211cbea25d69b84009ef158c770f948fd67e4609ccadc938680000000000000000000000000000000004101b351e2a9d34042291f38a289d8575872104bcf76f60bf888c60cca5101c34c247da30f7a8db4f0cf2f32abd302c00000000000000000000000000000000167e668de3207ddc60b8a5d5d246bf2f63ceae3bcbc4309e73eebf4d4234c2785bb13e4d5d8fff9c5f205e4fb942a2f6000000000000000000000000000000000491b965ed005065abdac53e3065781f2fd23f6159debc64f01c9f62073c651da33c05ed84617efcb5ffe08ce05e3b2c,75000, +0000000000000000000000000000000003f2dd27e3f0ab503a8752c0802ee14c655271e8cfbc734905b4331fb4e70cdfe291ff71053fbaf91680b1dd108f458f000000000000000000000000000000000c62014b7694a3e81370761e0adcc32430547a1bbe33746637e7762dc24f8d04b4bb955f17ca901659482c622d777642,000000000000000000000000000000001541320fb6f8a8c3c67278a7ad05ae7927d3555ad562bc8addb54c6693c51fb1c7355d2e74ff10f6bc3eb182d8f5b88b00000000000000000000000000000000172b65b110935b116ee683c8680ef0a660afdee43b9b8fce08ef3a70b352f8710c06b820348c338fb903a165cc5376da000000000000000000000000000000000df529b0e274e2e8993dd89ffef487aff23d31f502a19dd7d383de08fc77f1308a59ac5bf7cc899e81d377b2422187850000000000000000000000000000000010b40c9063d174b358637ab710d15c80d9230a1b3a056cfac4d583ad8c5b79c3d9bf22a1b0a4e0f629cd09ff7586f886,75000, +0000000000000000000000000000000014d1491a45b4b0914a6cb2e4dc7de9d0962f5c175cd571057cae1e17d2c943954d119690ea14f5815f858d277a9ad828000000000000000000000000000000001650771e0f7b33d235f229b7d49a7a5a0f00f78e5f4abaa70f39ec452370198a8532b5873e41f17c449f9c565e6adea5,000000000000000000000000000000000978ff68d94d33703488298658cf2c1b6034d3d8d21c175d71a0545bc2f99eaaf131f061f3e4f55622668e686e691f53000000000000000000000000000000001124804b252f8187178435761897d00c43cf67b588ca69f97c20b0ffad3ed94acc2c0f85f900713dd6ee9f38e5ca94490000000000000000000000000000000010ca2a8ce71b9a096c132c4a060a17365475b6556d4fc6284266ae787e217b3ceaa3a32bdf751375eaf6ab49800132fd000000000000000000000000000000000a43b435b116d9480497f6b2e1bb377550cb1a7ad59e4214bffacd517afc6b7bf91112fe57b17a02a86876ea07361bca,75000, +000000000000000000000000000000000aeb244909654b3e1df7cbeccf297223be57c2f514474edf0740dff48dcd5898b6e49eb65c787aa56ef79778249f4e07000000000000000000000000000000001007c89a66dab07f54313db8682f9e829baea229b030b4514d9c93686747207939c50a198e83ac2cf50315e02642a24f,000000000000000000000000000000000c3d87b1b78fab65cfc853304c682b39b6ec2b4ed005e9108f69daee5aecbd586c9818c37cdee865ba53eab9302320ce00000000000000000000000000000000062a7203cd2fd04a957cac8b6b6bb51e635ed7165c547ace10f93a32b7f37747a2e63d5767d966684409a6c748d4ee6c000000000000000000000000000000000526b44af8157dd68725aa8743684e020c1e385af7413c9dcebb320568663d18b6f29edea26f2628358852b794ffcc8e00000000000000000000000000000000098126f486ff55c21f64421e85b09a1b54f42d3499dc0e198db6f3bf7dd8476cad97c02b5b366e5ea20d8f83cc223f7c,75000, +000000000000000000000000000000000398d86b5206bae4ceef0bcc6335b1f6bf5d17863ef3a5e8463aaa69d9f73f8227263964659d4b770d6d9813f9399b9d00000000000000000000000000000000096bd18be1176e16a0d80e60f7d7ec9d3b6162f683440e3cde70082a73605da3783c8a058bf76d7e25056f5cd95c31ed,000000000000000000000000000000000f3e76e7d1cadfaad08d16457b02d89c40c157225eec7916d306faca8dbda008f41792888c647dff1acb4d4ba3b43c4900000000000000000000000000000000132bf730456e2afe745a58cdee689e37223292bf682d5b7dafa7df99e40d385559d0b3161bdda0bf5173c43ee46412dd00000000000000000000000000000000141b36ff6890e35db0054358bc0731b3aa0efac1a247a51daeff3515746456216975f44769174a4be41c109d35e4be33000000000000000000000000000000000ca401ee1addff8fe87b600e057ae34ba297886f92c5be8a8c00b360ada71831e31bc4ea1c309c7da31cb28d1011ecad,75000, +0000000000000000000000000000000004ca5cb60c32edfa385baa911ccb7fd1f383824c22b945944b0f3f7011db8c123efd8fa70e4fe699d40c6716021f0151000000000000000000000000000000001339adb0dd8d83574c2008f0a7ed001b0808d2fb639b5e57e1d293884247d5c66c948ecc60caeea7bf440a3a44ed296d,0000000000000000000000000000000009d0af77517b654ad97de3ee1dbf69ec1eee901facd0f8c39b4af393d0e63957292a7529b461f7fa58909acad32ba3a2000000000000000000000000000000000fda17cd878ec0f8c294daec1bd1d56c63e875b002a81c9c41146dbb564bab6e4eae2717c9fd718af1ba816a1526e8fa0000000000000000000000000000000017563b7ff22b50b6d9e24b1e0d89ca5c72e68d4d3cc24cce36856191111d087c3dfb392070462dc7850ef5a1422931c600000000000000000000000000000000020001fcff638504055ba35230b360e6d3cb5777b959c194d6f9b038b58d3ead0b82b28bb215378abd85d357b85ea260,75000, +00000000000000000000000000000000089211892a61202b1ad3a85aab9f08f8d028f3e3deb16c1de4d62c1a403fa63c6dbbdf8cec37f0a9d6f346b1c7ee179d0000000000000000000000000000000012a9fc2070b326f4d7e64804b3a2e977f4bb36b6a4afcf27252af757d8535e8172a99dc909fad5a3ff8df23d6d6c5948,0000000000000000000000000000000000d51c77c2443f00d965c0d7ec9b5a8a8003c2a77b0ffce3e47bcb55420e8690a9c2ba9235b62a4b351d79d216a3aad40000000000000000000000000000000013cd46e3ee6cbb3bfb771ee30b5f5faf0a64a9efa1f8fc57024c83ad07a9b25e513f211ea604cfdf319dc42bf4c067d300000000000000000000000000000000009fbe1fffc67220067c948e0c80de23795e045fbe8031c9010eaa69356ffd8e5741cfe12731ec13aa236630f1b1dab4000000000000000000000000000000000e5ecdf808d10d47f041e4b078e79b32520ce9623b50059a3bd8b59daebf9103c31425659ecbaebfb2384d1c2f1b400d,75000, +000000000000000000000000000000000b37365748fdb21fcb46f94edf86c586f17d0e042c4683e68c6cb83e7c0ed2c30ed260c15af2c9dce77bb705debfa7590000000000000000000000000000000010d7c02c6c1ba3cf6ac09a05dfe043905e1a7eb32b67f2e8a5dfe82eaca66ef46cce43aaadeff58ca85345dd0d3bf3cb,000000000000000000000000000000000f3e4d2559261829c0f4816f8b571170de1f74d75d74997cba56fdad42932db73504691f9e001f5b4604705a8c1a38e40000000000000000000000000000000018c72136bc7d3050ee693270668e706ebf70f990e447ecc6153a10625cccc9deaf5ae82d2a656b1376bf33b1c1fdc2c9000000000000000000000000000000001754f2725bfa76e92a74ad5b520ec2aa82a1f86e8623a054ebba489adfc9e71d1f14d4692ff9fdd8acc3d768b67e1b7000000000000000000000000000000000096f1373434a8822569cba0679dbd2abf619bd9a8c73e54e078688d4e2615d45431ac8cf3da5e15a83fe77d14b339e49,75000, +000000000000000000000000000000000aeee59421c8ee65f8070b9d036e6bacb39dd2537d02960a3a57da4f0985cc7b27784d60fc1613f5a83c34d2250395c1000000000000000000000000000000001715ddcbaed0a05b38b1c820724405a713cc0215a4c497892f00746c0f9af28b440a3686178d9bfcd41944a224311306,0000000000000000000000000000000018d515b8c99f541c7dd448c3564c1909b84517b662d6a2d1176d3bf5e70abc0a2995c73ae3f1614bfed2f64229e173e80000000000000000000000000000000012126ab671420933cc4fa9206311200cc5241ca3eec54f5d97a426a72642bdde32a65c79735446779cd1744d112d544100000000000000000000000000000000190d836312ffb0d6bf493f4c942263922659abec46ac4de639efc311753148b445509f808c2fd813729b1bd96e0e663f0000000000000000000000000000000006494f9a451460ac658ec17710bef79d59b6e0fca049804c0954c5fc472bbef520f75d34408ccc62cf2da3deeb79acc2,75000, +000000000000000000000000000000000ca4b3e1a8351057ba4a2ffaf0cdf1c3c0717ccfe26433f6c40e2cc29e32ed884f63d25979580fb555a5a86c9147bcb00000000000000000000000000000000010c1db593af38aa14ca9dd588f54b219ff1fc9edd25b3d16c595662ffa7939879244326b14d978e0dfdd25e37776964c,00000000000000000000000000000000173fa567aa952bfaa9a60b8232a185475cbb36761ebef49ea5fce900a06043d0e2c1b6024e40eadc9f4bf04b077201450000000000000000000000000000000010fdc32ff84f79fe39351cee1ed6b67dbcf2956020e2518d5bb5b367b61f86f1bce36f75516d9551d74cc3a567e6c2be0000000000000000000000000000000007abdff8a8967eccc4de6b4ce142173841c0e8399f5a67dcf0f7b5e5b4133391b44bf4d41d3ae3426839b19aa4c5d40c000000000000000000000000000000000c99f160062566418c09f10eb80f005f2c8c12825435f354f1d65bec0322e9b8ee968c009a84ba792a7ee7334b32bb3d,75000, +0000000000000000000000000000000017cd94e7e672f0dba9a3c1db742d87cb18b9401e8311c4badc24f811a8f40c27942da6485807701c1a12da58076c756b0000000000000000000000000000000012f6de4ac9883e78f9d658cede4c70b44bac6b4c9734cbf24298ddf0df0cf54164aca245d8e313be4aca66ba3cab5d70,0000000000000000000000000000000019dc92f1da66d0855ebc8e7a2ddec623a2f843a97c7385364a631671be7ee3387a0f98940b5a51c8d9e23eb27e3133b00000000000000000000000000000000008493903c5c68b2847869b8c3b0fa9b8ba15bf1f11a40a29e6e82942e2910901044254cc8e8c3c3bf56e1f1b6dab7e86000000000000000000000000000000000bd3c1e302a191094059a6493e59a11ab05a49faf333f36f7680ec9b1043e59dfd7f0fabe9f334b97cd638dbb8bb664b00000000000000000000000000000000141c9b07ff33b6ab55b320dda6be54320082f0057c446236cf3d3a51e674c26a5241f2c702d9989adbae9045942eeab6,75000, +0000000000000000000000000000000001b2843d9852feae3145b242cd0999877c07785bc72cc2626f388dca32edfb112bb90f9aefd6953eb15a0babe748573d000000000000000000000000000000000a69bfe809a67ee853cb96b5a7a72798748cda56936e5664d509001544539730f57a7541ecd2396c9225818b9dbfa3c6,000000000000000000000000000000000d0922466c358cfd756727e134b5e64d211244587e4eea036f0959e78570dce3ee264c703cc356cde20637c7560369340000000000000000000000000000000011a66d618f79fb662ac2b2d3b50750a5567e36d7092dfcc72d8f340c04df75ecc0ce4a01b410ea775dc548b8dc66c3d8000000000000000000000000000000000cc49cf4be5e2df6b43054092afa2d6acd66f5a43ef0667f6a2d660beb7fec70558ce02d7acbcd090df91fe833326718000000000000000000000000000000001270b0519db083f903a3dbe0b1b1bd5ce0b0059ea2c2c50335dd80b4bf154fc23a3de1ea753b0e279145254d8e5bd045,75000, +0000000000000000000000000000000002479a989dbf27141bd9f467447218dfa6ef60781a7231f089d5f1f1d8dca2ce9606a75c09f63f37f9cc1ee61dceb32500000000000000000000000000000000037c2f1b96170f6847138232bac663e4940bca602717c877f58ff7f5259778246085d499ec6bbeaade18f738df333cc7,0000000000000000000000000000000007826398b4ec35ab58ba9fda5c15ada2a41d3854677172ef6a4a54087b64d0f73fc875ad62236eb7fdcbd94f14c8895b0000000000000000000000000000000016b14fa92de5f6e43988829ea2f851746efd6680b0ea1283264f803c8ffbe85a343bdd42225caefd1b94b8b311d2f4950000000000000000000000000000000018797093ff82bc10e6db60b1da50b9a60da01d67673e9bee8c7af2bfa2d57f409f7b06f53944938e5c73b049c2d3c6500000000000000000000000000000000000c66dcc3d30f35c21b8a9369c8f6de28af404e8b30d3c9a7f09c461b0272ba6d5a29e716012536dbeac1d9672af8427,75000, +000000000000000000000000000000000e6fcc48312831b910e52aebbf19869b3b32f05db8310b68905bb244ab784df5732db2e72183de5d231d803d92aacff9000000000000000000000000000000000f61f9e52fe3afc2a6bf12e420cebf83bc55a449d6a167779e5b6ba3281c51d790a045643aa75f2516eaf6ae2a816ac4,00000000000000000000000000000000191aacce60a1a83f2c453fe196bbe5839a3a1178b147580435f7de8a2b0b4f65b3e280ac7a67570aba0fdbce6c11ad9700000000000000000000000000000000075ddd6b256f53a6ae6758a5158508540aa99b78ca069378f0ae3f5621ec24b9acff1f9b61d378334a63682a33fb0561000000000000000000000000000000000b06e11c9f858446fcc90c69d05cc26c33bafed0feda19adbd838c9c24bbf567b673110a1b248d0ee97fc682e561298e0000000000000000000000000000000018c75dc203493e12e1523af50f85ed648130ce5d3e9757f713850c867cc95c7acbb66c9733dc4f53d6a0e64bfaad5832,75000, +0000000000000000000000000000000018efc6d366d79a09b7d56c81c17c2eec2ef7395fdb5991f41273329cdcf4537d342bddd83c3994a40d5c18f6afa054c600000000000000000000000000000000127021ce28627a9d6a492720f728acef3b38c12b951f70a932c7fc0ce3f5b6c80783351cec55d7d1bc4ab964bb4913b2,0000000000000000000000000000000012931f51430bea6e96f8ec456ce6b3c9e058b0bd3bbfbfe8b6e84fd6110c3bbbe0001018064e8981797f9c93713a0e4400000000000000000000000000000000196b6093dd2276098853ef2bfac84f0cad06b67a12484e98915dcc756310b818d8136954de1b602eb825ab29a143cf4b0000000000000000000000000000000008284beaa877b25374571dccb218c401cd905b351dd96700853f01920e409d11c4e440e90dc175cdf0fa807cb9d1e93a00000000000000000000000000000000063c6c238485c291fbb60bd2824154a9e23dea374292966d271ae94875391b7ceeee813e3fb9504223bb86f0ea3b6cb4,75000, +000000000000000000000000000000000a0277228ab4e880c12f957a6fcdfe49e2155083f3f93d3f00c68622415cd1f5bae183b7df9e08328a8139392772cdc6000000000000000000000000000000000de0ab426e56029790a5ff72f34da97e11c028dc5d31e448c49ede004102804d2bcc36d509640247a4c8bfdf5104a781,0000000000000000000000000000000000f7bd0705cc4ea96ca38314cb85963044164b83a506ffeaea6e5eb8f7c4967cab1f1658f33b5435191427aaf9605bbb0000000000000000000000000000000007a93e2a5c118aff6ceaf2370ddad52a82854946ae595d384ee0b2b4935a574ba758736d84b0ae792f998ec6a707dfbe00000000000000000000000000000000090936add00fe5c7556610b28ecb4466ffc37b95b5cab43e072a585920b3cbe70faad01ef75d1dcb4f7d00d900bd99600000000000000000000000000000000006ae82539c68b7af3143e23229fe320924472c2b3e15a2e27e94cba674d30f083dce94706da094435c53285a43f89e56,75000, +00000000000000000000000000000000170b243c5aa49a0134bf3d6494cc1e55a1c6ebefc6117eca3b343a48ef0a2b077c443ec5b9201b198df015a38e66b7910000000000000000000000000000000019a8ac8a3be1d45318801bb0a654963b312540d24aafec46bb7661cebeec27b0b345275fd53c041d02b1ebfa27fc3854,00000000000000000000000000000000024c1b869fc13191b71d7159a07e869f1b13c11c73231b82e9bd0a7b4c32d7b376fb73d54f7231dd4974713179f235140000000000000000000000000000000012b9f95af661e8452aa5026302a7c28695307f75e9e4e32365caf378ed394fcecc831a3c47b443172188f4d18338fa75000000000000000000000000000000000f52675fb4d112d1d39ff953a253b22dfa0b73d972e756ea7fb673bf87aa992883c5baf32be6f50f880b03dcb740f06c0000000000000000000000000000000008b57726e17c873e12834dc291cff6bd95307f50e7b1d0caebd8c1eeb6eff4acc0520b135bc8e35a257133b7dc640db2,75000, +0000000000000000000000000000000000fbbd5a10eeb2f358f2b167f1985d4084c4b12accb1520d780ef1c52f6fa80e97aaf190e7a7b241ef96fe8289fc0a9600000000000000000000000000000000155687114e7aa786ba27aeada830fc705aed069c4e3a07e88d7f33923319f416ff3caf6533cbb36e5bbb1b93a191bfd0,00000000000000000000000000000000061938df3365bf910884ccbd74d3cea7c30416bddc1a9b65e7723c15d89aa657da36a45fe10ed50bfa0c2769bb98aa2b0000000000000000000000000000000007b3981054255715826cf8f247210521ac681305aad3928b69804117fc143c5101383eab7017127c8452a79003a857d60000000000000000000000000000000004c745113480fd87212ed3ff30ba43c8716b32e62c1f0091bde53bd4a8fa8fe6bbcf0904144f4791ed1bf12dffa1f17a000000000000000000000000000000001237ba297c7f69e5e240846a12d86c8276a9a6ceb4af977edadc7ebfba3ad3f4ecc0b875da0ea578c83fc3b91f9f31a5,75000, +00000000000000000000000000000000115edef357ccc3432226d9bad796a42b1a278d9c8adfdddc5a0f8a36d32ea1787183877f8b7dfab71424cdd10b63441a0000000000000000000000000000000014b369ce61abe60d942346e049644b95a0fda96316446b2fe7ee427641af52fdd2a654bf125ff6c8c7f3dec98f6cbfb9,000000000000000000000000000000000a0cc3e328b4cfd01afe53dbf971ad78fc74d951050d76210e4c84438109622f0531747e762e185e3d7ecb9faa7c3255000000000000000000000000000000000622ad6092caa727d069b8921f4124d5996f3019705a908ef95d23092c5bb148873a22c227aa25ebee361d4184cc38a10000000000000000000000000000000002938d2ff50cffaab8c056c2844c50013f5bcdbb4f91b3f823836edabb39ba17ed1b8b5862301efad04bd2f5d5bf599b00000000000000000000000000000000072e96136afebbf8c06a37cf9b20c85ef8cb3f7f99d5c71b05a187c193711e5b76f52863c7ef080a1b64b2120ab2ed84,75000, +000000000000000000000000000000000d22b7b36ac66b10adb4570f8e7521ed76de2df2a7b94b2d0b9ee4514cdff6fa7c74854d16e7e70f054a91df93c7ebaf0000000000000000000000000000000016867c9cba66dd9f1d0332d31c4e46f8e393eeeeb19af7e6e01effb29ad999b3086b599ee4b371de557d4fafd5da3794,00000000000000000000000000000000142ceeefa9fceb903b25d4dc68f6755833d7529752db0f125f7f65f2b7aeea8c90e599ac409576e82f7b9d6f83c43aa0000000000000000000000000000000001664acd89b482aed04ef40bd4d1ff9f39c80d7738771e2b3ca731af01aa230d865869cb05d83992e94ad99549fd0b8550000000000000000000000000000000013d6ace9b492c014d9a7504b5abe442e3bba13b1ada454aa53177990ec44f616e091f1382d36db87b7e794c11570a9bf00000000000000000000000000000000081b7a8a2906435f8a9242f573225ea62c5429e903bebda9fe9973a18ed2682185d72aaa6584b9848d1cc45ac907dd27,75000, +000000000000000000000000000000000db9258e1257e659e60bf8569ea90c8247a53a1d1eb958481623447a38d0f1f1686c3e40c8f15bd06cf5be9c02485452000000000000000000000000000000000517c87f3df032ff08d960f524939e66f7fa69b4168b0f2507baf7d7231a70dc5690a02d317b26f219365ac3255bee78,000000000000000000000000000000001182e4230f0c360c07913349f89f8436c01841c9615348a0d7057336c7483342024b0369ae52f39d4582f9885f552b5d000000000000000000000000000000000d15433ed130163a85f8ba87468c906aba88ef8610fcc1a8d6b3308cda29907acca351fd7fb19799184f1ad91c751b5e00000000000000000000000000000000111089005c4c5370863b0ea6b629197a865f978f71becb741f50f9b4e49b13162ca63c29aa26287faa9c923f57f4ad4c000000000000000000000000000000000dce405ed2a79ad433123105ad01a26ee85d1ba4e5f3b4e0339fea787058c06e9a6b10f5ec8f6eeb85b211e18b6ea076,75000, +0000000000000000000000000000000000b6573c743989fc8613d4ea09c2e500ce965b50cf0c8975ff703116464082efff4b42828c8337809f6938d7cdd3f66e000000000000000000000000000000000896d316629c80ce6e5f240535863b9e064728665c0815f39b21675c236f6995e7dfff1e4aec9ad05861e2122469ea56,000000000000000000000000000000001694cb615d2994a903a13645ad44a63395320f286503902b6009e7c795dc8f024260e0c45bedd864edc9fcb9d1ca6bc1000000000000000000000000000000000f20538af015bd6d213f90fb1a1ebde4d9e2ab2defaf80d791a1f70af2ca7ea1598d43e9eef1cc982f468cf15d223c9d00000000000000000000000000000000046c62bec4c6876a67f5fe68107d677db8fa4d59ac0cb7afe6e706864c6e94744bedac6b34a68e8ebf89c231307b86d3000000000000000000000000000000001839f3b8a6dd8fe8028247670fe5b491bb43ea8fda53116dca87f97da96573a5e701a703fb5fa7bca457ef88a827e061,75000, +0000000000000000000000000000000011fd2ccf6883b78fe19cfe7beded503cdbe1cd5dc9ee452aa6b329d2237c2529df6774334b132cfeaa616f20335f88680000000000000000000000000000000009eacceef036ec500e7676f54af703016fac93d69ed19c8943b64ffed2db498b19cd255a0a3437b568eade0f497c7b17,0000000000000000000000000000000009d8725eb8757828a94969ebf40545a62835686897d4504a66484a3078b2f15e39fe918d8dc01bc7560dcb005a7a0dbb000000000000000000000000000000000954a6cc9b2dedca1cf280f72fd0625184b8f83b78ee1ffcaf0f9178ce97900d759e4a74b914c3ddc32f84c3f4c3a8d60000000000000000000000000000000014121b83d2a06390ce7359e570e1593d5ff097cb0e44c38bc74171fbd8a8da0dfffcc2bcb95fb2d80a55933f696a86cb0000000000000000000000000000000016f71d24256de70618a02b0f016c6f31a21d2cc42855886ba30176584a028c2e12367be19b834bf41356cdab21223314,75000, +0000000000000000000000000000000004a851380536054f6b69ef7581b57dfd753d1e6201069bd1218ae5766aada087b4b08f65d43b3ce0215640e8d34633310000000000000000000000000000000013579671b64f2d9a2c3ac2737cf95c2148acce3dcecb3db6d019730010c50d1c0504ba4ed42d93771ba296b0b07487d7,000000000000000000000000000000000cd47f0982904ccaf4f3cdaa37091a08e67a5f04af09033b864631300bb6c2aacbad105eca6ddf68a643976fb555d3d80000000000000000000000000000000012332ddb0e91f0ef9e085f21634c6d69576e60d3d24732a0c91a560906791f60f79d09ac0ebf448bd39f047b1dd428450000000000000000000000000000000000a756a869b3cbc5624f0e08019170beda35fd2642a79108b284a503942f8267b75868636302e5a12b4f1505331b15f9000000000000000000000000000000000f60724f6c8200edff41f3299ca003e9ea03b97b01a3e8c63763bdf67b9f7677331a7144915312458c40d041be97b3c8,75000, +00000000000000000000000000000000021dc1dedded9b0dd90afa9ab7fa8f9c33930fe4ae68185ea4cce9ed97ce4cc9ff93f96377b11f8d42b02e759a10b06200000000000000000000000000000000034c963fda3bb80043d6d7887661ad59b3c31c88c958b451a8e11684770c132205db6655ad7cbd604ecc3225b0c128b0,00000000000000000000000000000000095cd509e53f10b1ee18b2120e2d18f0905a202a992a9c62480beb6588275fc8b5b151e6abf17a12b6d9cd03a8b37a59000000000000000000000000000000001723bf1a3d79935eb4b39f7feaa1e05cd8f3e7a32e2c406625053d8d8fde33eefec231ee00adb00b0acac16a83dc77fb0000000000000000000000000000000004af528e886dad3f9fa7232605936bc22a6a22622828367791920ec9d31cdb2f290e37f5fc79efaeaf96c86b3f6e39220000000000000000000000000000000015bada14a84fdb09b77397cd2e27836f9f88854924af0cafc6f9125d32be848c8325a3eee1a26de8be8eb80b601f1ad5,75000, +0000000000000000000000000000000003e8d1be04f8dbe5c7e1c7553cde8355ae16d26c819dea92fb543cbd9fe9e726359e1e4be0483a7720343f34c6a3fb9200000000000000000000000000000000062bc5fdae812802bdea09e4130c3d9bf80c7518138b116a4b6a302c155b97226a6ccc8a3ace18744e7adece08781f72,000000000000000000000000000000000d8f14042f36bb377655b63dbc37c50e0eb5775d4e4399972a6758cdfa9751cb4b733745ed1a47fe5f2cc434efc5af81000000000000000000000000000000001384016829d028f823e6d062898c042a461bca13ae4627c983d9b5c9e8b4ffff7eb25daa1c52b39e309b9c1e7e4f2e920000000000000000000000000000000004f7904d491a0c2018b1361a9cfec4fc829e607402859fd9b9ded60adcee51e9b522d302f9064130a4eed1327f49bb4f000000000000000000000000000000000ef4fe949fca569b31fc57ae7d0166ea53318c5712311076e052c2967144116f5490fdf56f26adf64aa01beb4f6cd214,75000, +00000000000000000000000000000000014b922157b19ed9debd9ae95cd9435f7980b8d4ea33fd29f99d5e7fb1a96f6d99ae13f7f86239c4bc286c3927d3522a000000000000000000000000000000000f6d4badf78d9115d93783a59ec9576fcfd87a2c29e1c506b6f7e313e71723811a36d64b37650fb6f7b460105a7e13f1,000000000000000000000000000000000f20b3a6505784681331208b573d3a241706692db71b5daf4e9c80adb1fa9bb87023d7ba7f9c65158653c735dee9dfdd000000000000000000000000000000000f7f357407ca6cc5c5fae4b84509d71b2f4de9af226cb4038b4820c0541d4999b7396608efd2f322a00a768129f9800400000000000000000000000000000000138dcc1b9d978adb5eee6356980cec5d18cfbfbf18cf6fd14f4119a563f473f5027af06342e84ea858223ed63d1a16af00000000000000000000000000000000012b63f0d2e8ea361d55aa617a99e066b5feef3af1930b83d2a48b527e0ef304ceadf7cba1415db80c54fdcbbcf66d14,75000, +0000000000000000000000000000000005a54ee5e3dc05c38ade7a42c71baf5a1467938f97c0cdf0742441cd339f22542b1ca6cd215d385b3fd6ba74ec996a4d00000000000000000000000000000000051c6f0ce621e8e27e5017628690fb68f0fea27d67726a0a77b0caf9f524936e123ff096168ff2079b9990c07fa80354,0000000000000000000000000000000015ff2aa94f802d8f9c60ddcb43aee598239cf3ab7f90f8289a487b673f6065f8d9bc92bd4cd28df4a7b0d3bb78fad243000000000000000000000000000000000884b5d4ca3c8abea737cfca05878528890b6cee9bbac0bf027df5d4e0add431829caddf4c1e001818581ce08686eeed0000000000000000000000000000000019b91a7738fde9760240b335457955e963030848e85717858f22dc33ba5a4721156cfdd7341aa86d10d268e2fc9a1d26000000000000000000000000000000000af85e60161795906f3cf705f5e8cb8c15083a90836eac78445c6bc27ffbfc8c2df3009b436989b46b271dd8d1dbc282,75000, +00000000000000000000000000000000094e958d9b7dac39fa4f0143a333b2ccee09046cd23e6a1c0712470a3c2e61d2f8b85aeca37350f71d7ec75aea2b3b6b00000000000000000000000000000000080743cdb5e359e8b8ad3485d53ea286669ad66d841945296edf80dde77b20a158e2c1529dfc33a1fbecf177d75a0c69,0000000000000000000000000000000001bd1fe6a6c373cfdc2bfd488b0c942492b77d55b2560824edef3a91c711ee336bc1366690be40949d04edd39ad48a7500000000000000000000000000000000161476946a5687113c74a34284f49b0658e323fae57aba88b039eae584d6ef28adca669fb083a2fe8f0ef664eb5b957d0000000000000000000000000000000007aead870ae09a04cf9c9fa49d0888f7010782cdc5a0ade4c1340ff15d99cb39b7412d66d4147b95601fcf5a39c39bca00000000000000000000000000000000095cce83dbfec12973e27627bfb2d93fa9a027a2c2af4259a0879d6bda055d74559fc93fb3b4f6b0088f702af29a7643,75000, +000000000000000000000000000000000dec04526dbf7666d2c29db5de1ef0b3da85380a171d871a57ae3df364d2754fceabf9d4d2a3da1ecd94e377abc78430000000000000000000000000000000000d19875fe988ffbd0cf1e9bfefc7019579962ffa3a585ee232615e4a5fce0a07bce0537b203ea00010a90ec05d5b8de7,00000000000000000000000000000000133cdf684c3ff1cdaf07ff787b57a66c215eef06acc2aec4d726a086480e7b2a5dead2cb357d99e298df32d4c6f5029b0000000000000000000000000000000019cd65b830fb17880f40e104ed63a7d49b0fbad8eead7502f18f1b9f85f3f6ba6c275b8a242effc61a7a5d770a4fdaa700000000000000000000000000000000039aeacd163862e476b17a22c76042d7896a04f158489ae71afdd35d27106a3ec276baf5c08e3eed4b3f0a79c3c458d200000000000000000000000000000000125a9bd770c1fea2155a581211bd71d55eb1966645cc892a05d32cf1e4e5b23278ea2fb1336bba7f2c887debe4a93b52,75000, +00000000000000000000000000000000016dd03f0df71b183e42cc29c665f18d57637b65f05df85aed9a0f7b8aa37f7913f6606c10f24a7a8c570f485905583a00000000000000000000000000000000161e62d8be678a114fd4a99a6caeb9481f5eaef145571152fe8a6ed77a34c06a1b6ff66044102d19a94abcaaeb254e73,0000000000000000000000000000000007843268081f61ad2b3f6653336a99086381bb4da4c23b7d59b9c7827f2d4c196d136508c8a1f3d2f939e8c9799b95e10000000000000000000000000000000000e2c57ad95f762115d8230320810a4ea9978e26ca17decd6af4c112789608967a92fafe3fb3e79539d75d1c0bae97740000000000000000000000000000000010951c9839db9dd6ca5ef95bd1b1b9cf60bfd97cf88129fca23b24f19c9d5c71486dffb762e92f23d2a9e9d462556f620000000000000000000000000000000013d35c17b3763fc5db46ac8c44aef996f3f876c49f5278b7c97e844f23ac49f2d50b3af080322d30ead873af7b4257e1,75000, +00000000000000000000000000000000036efffcb0c6f42109bf9b8b7421e32fa3f855373345341e6000eccaca135ef3b2e1c0151bddbd46ae92185acb847d74000000000000000000000000000000000edbd7a40f3e688eaff5e29800159b8d799df07e81f65d59011e84329b4689a28a15ce11537fb560df705be26bf14b1e,0000000000000000000000000000000001aa1919a50b5bad62b839d672d5a11ad345fcc61f75eccc42990e113deb8a486423d1b27e7c81536d8a5799986b9408000000000000000000000000000000001879295d2f7bb3923ec61c063ee4f96d7d7cf7786259e2f4cbc3ccffe7e114af264b3527a5e06dcfad50ec1e2a9c1ae0000000000000000000000000000000001042632662e406c95f3fd44a6d956e526907147e7e6d4219c1c4b28a31e479974d00d4ad6e683f6a834d3d4a20830f4b000000000000000000000000000000000a29ea98ec25e7827bcb349ccdb2a57926809f3cce44d5ff6cd636460278c8103b0db78fa580e9edd4ecd0bdb21018ff,75000, +000000000000000000000000000000000974c7d17cbf91947ad435b30ad2b639671a43d67da6a4edc7f8bdc11fe817d4d42f687dd642a2be89c81bc36e8df592000000000000000000000000000000000efeeb85860877abdabae35672a77ca9d2cf0ed18ed209fb905b591a841c900ed06d2c32c56bed5f7efd469d369b05b8,000000000000000000000000000000000c67498c6751cc27d871b8711c4739398c501a5bfb688d7e1a73dc7db5c47c3e28b633078cb83745bf5b0d5d2dde3ce2000000000000000000000000000000000c205c03305422bd44082715b90e0a0ec178003d6f5e14a0d13bb0f2c38f2270816b884b4870b75db44ab080f88a35e2000000000000000000000000000000000257f378935772d326710ec6efeb22f8c9b6b549c8a4c0205b75740047d750d73da4e71aaa8ff33b9bd8ab7621b08e62000000000000000000000000000000000c386a15f09c849be9f449a59e1332a1e7f16a9394c8de198c01399a05b0f963921c4c57d49916407ae0d202af8da32a,75000, +0000000000000000000000000000000015333364f4d0d173ef35e447fc189b9d65ef71b9fc4ecba25fb6c8c1bfe8467f26bb9c55ef10bb34125d714b94aa1df1000000000000000000000000000000000cbba9d8ac191032f03c0746f13108962130c9e2c01d47f01174a4c4d3daa7631268f7dcc08dfda317bd249fb6e73e8a,000000000000000000000000000000000864da537fd94a9ff1bdae733f01e145dc97a894733d0811cd67c2648ba61d0b187241f9ec69d8c011f514894a05a608000000000000000000000000000000000a53ea4ff9c0ff71541ee21127a33daff2b39e74301946a86e51dc7834717e7d8784cf92fa5845bc0613b6b869003f58000000000000000000000000000000000582f5a1fcef3067dfcdfabc6af33871114538abcb02fcad761cb496020c7b423fc52f0075916f160fbe03574df97ea4000000000000000000000000000000001244ede8ba0dc09aacdc5d9f886e59bf963a25885dbbe2c3d1f611bfae82debc556ec4c94f0606492c7b8c7bf976ec34,75000, +000000000000000000000000000000000781e980c167c982c2fc8d0baa3907bc5499eafca675ae20a10b25063c9088fd06f6769df505e5900bcaf99e266c052c00000000000000000000000000000000183c12798438ea92db75d5bf76cf29d320fab3653e4131989205f2817aebcb1b13f161864c084fd13a11459d7d5ccd92,0000000000000000000000000000000016c334aec0e19934665596f0ae37eb398f1d6f0d0c9f08189f1ccc219230395124a9da03858bdba13ec5366da54228af000000000000000000000000000000000b156ea34ae7b5c252dd90997f1c693773a463c26935a69bcc0599b95bde9e6aa31649c48b6ee4ec1f0a56b19273a5170000000000000000000000000000000014b2d69e02418844effcbc0d564b2721deae2872cd1f27f61d544fc0ebd5cadc77c6777ec944ef0500db181a5443618e0000000000000000000000000000000004f0d48a25c1eb81233f385af17ab6abf554e1285b669eeb5e884c64d5815fd5fa1350bb361997cf2e317f7c5e9cd19a,75000, +000000000000000000000000000000000879133a3c0e50c90abf1a6ac75bbeca1121c865ef39e9224ddb160eb725e0850a34aaf9014e42174966773e40e4c99a0000000000000000000000000000000004c66f8f5bd462cb27e9f5e1d93e322bd97582b9e81d07b2877103420262e4cfe6d0e3bc07f9f160701fd754793eae33,0000000000000000000000000000000003c0d6b721cee4e5fdc6a02095674a58075f81b1d28163f81d5b258c82634297009e6bfc8193969e23e196cf7a99ad6c0000000000000000000000000000000013229818411c8e55e50a63df6983150c1d5ead828711131d9c81841850ed76e4712954d3225eb6d7fffd3cb9924f7497000000000000000000000000000000000f42d6e4d5a28dbfda87c806cb0b1bbabb745e63e655c3c6be50411da4dcdc745ae50f71d56e88db8454d40375e325810000000000000000000000000000000000f663ab791b48f76d358e66e8cd8fa40848dff2bbec758ce1d7b3fe02d1f6b3f123cef644d4fd86d6a77b8155feae58,75000, +000000000000000000000000000000000a7e855324ef471b8fefb31967cec84d57953407ba555b672fa59364b303030cb02b6c77933cc63fcd1b8c107b263554000000000000000000000000000000000b50c3f7cebdcf538820113acdb017fcd5d41a4fd679af8dfde7b0c330e3576ca79d09eedc724a93a3f5c90d141e7524,00000000000000000000000000000000197865f685e78a8842fa79ddc728d507e9f31b31666d1952a46f6422c97c83fba3087be70e3bb588260556014523f74000000000000000000000000000000000131f5d85ad3beaabd129d5a5675d90ea911ebd02cddb5ddc7a8be28c33061430d684d123d5c516785d21ebf756c99195000000000000000000000000000000000c7a14948f3aa29f845e5ca9877db9f0477af376eaeb45324c21e6f99e738aeec96b89af4df942bffbabbf50172d8e5b000000000000000000000000000000000ed4aea3cb585b0d36972f9ad6943172ca7375b44d1d6e80e0bf97a0b25d74deca4d35ce865c8747f1c7a2771a37c667,75000, +000000000000000000000000000000001706830efca18d3e75ea0f1ca8af23a816017ceeb045694cdbad6d3d9aa5a9ddb123f5097a226a217166de3a82038393000000000000000000000000000000000402132ac383a2fcb17fe73398273ef0c2f2d0d6edabc78f31080d3ecbf7c249ffeef28bb8b37a6ef2a7d726c070dc41,000000000000000000000000000000000a795c2affaaecab6cd2cfd6c8fab6e35cdd646e9cfa7b5e02400ef4abf839a69924ea80152eca7810a5041d1bf58ee800000000000000000000000000000000121426bb945d6f6b385c98a5247b7dadaebd3375dd8b2bff7aa77fddfbe603de89e77baf0e8f36a924c707c53d29a1450000000000000000000000000000000007a6fcb486634186f001c8b99874f0a07a37f1ff4b30599d2f570f1bb4ff290b816547f6ce8b3c1ed33e57630a1d57ab000000000000000000000000000000000fa65924a8f17414eb7dcc54f2a4134568484e91533dd21fd33cbcc37a920f2804516a64f1986e9d887ca189179d07c8,75000, +00000000000000000000000000000000024beda2b950efcee233435f0c748e33aa928f54ff29d3db217d7e32b1aac5f4ed11705da4fb8fd38481382486e4aef7000000000000000000000000000000000c85283ad6e35a72d07b74775df1a4660113d50b51426451f454a575adf9cbf9d7be3f521649f6c367c4f4c74b67ff6b,00000000000000000000000000000000049d9ac43e31faa3d02f8255d207b82e4b27e8a9a61ba45fc4f9ad8048e5f89b58d25d98253aabe29334e0dc09d1cd6b000000000000000000000000000000001544f90a0baea38b48d89bcb337cf5a80faaa79334733b7e6126f55358a7e498aeb61419065b9434cab9d10fe8e7fd9f00000000000000000000000000000000139bdd668462a1b5d3ef1299d47aa91ed141ccbeba5b08a8ee31b023aa78c16514a97ba08abf5c8bb1abbd85b3fe87350000000000000000000000000000000005c7dbb8a22403a96aee634cfc67ee6f1069cd61a1e1831e8faa9d7e1aa5e4f7623f51f2e5b739f7fcf3b4ba77c82ff1,75000, +000000000000000000000000000000000cb18f477abe58af92116101c3f52ad4f6074ed92a08c3adcc6660b555df9cff09dd8b34e032ed81e868a62bda50378d0000000000000000000000000000000013c4ab1558dc250c3b5d0f0fae3db62b8df969bb41e9ecc24c10e1e51cb399f1368bed7375a9b9ad9c7653c868eecfe3,000000000000000000000000000000000b8b8bf2b25c2386e5f3be4bdb387d8005cf055e68ab9a5606f17dbedc4fbd7a11314fd646d08bbd6e394485d4f56f5f00000000000000000000000000000000173a45d766682f82ec2d69aed1d80ede2477c276ddaa8fb97f5f4d0515b2c2e370c615cd81c1e361f95db855c9b1b6e200000000000000000000000000000000115868a9187a0465a9309054e865ef224ec3c88a5eafbcc25f9a912ee3b19084757a90b72a4038ba71b10f59fe2f93100000000000000000000000000000000006c5476eb8aa1a471d289af52c7d1df55f6bb1ad53d7eaba6bdc2a97fcb24ec480f9d8e12079d366f2213194c861f016,75000, +000000000000000000000000000000000188f650fdc51b970d16c0637ad5e97aade93c7f1398751439484ec6cc56613814908e51cfa7f68da9d980bb9dac47a400000000000000000000000000000000081834f86f1310135a2cb03265f37d9b7c9459bb149bc54b5a61319a7cde36c6a2a0fb49f8f1fb9d80f07b84f799119f,0000000000000000000000000000000016e8fea4d09831146fc35bcad28e441f2c02e4d17838e04dc7cf909b2133297a13f07ee927722f3d78e36721d6848e3400000000000000000000000000000000114dee8b3a47269e9ada05ee015a874d1cbdfff4acdf5310642f829efd08f78dd6110e1c7a514e7d76aff52046f4ed140000000000000000000000000000000017b9d23f7a865a3ca61197d841fd9195805a9e883d79dc7d36e82f504e6689ade0e84c70a5c5c516fac3e3c643942e160000000000000000000000000000000001ab82b2a0986dec3211507b8adca351829b0a13f25e281f98f54d9e0e32280ea4c638dcb74280eb747a0d9af43b6b74,75000, +0000000000000000000000000000000006f66eb49f95f51ec90df86254580f0ae57862bdd8b5f2759ace63c5f14f8c5a762207f744bb82a8962f8c4fa410dfdb0000000000000000000000000000000004e02a80628c30ce336eab890fa37a227f77357a60be72cb87cc2b7442d2163d395fdc59350624ca1322bfe8619a2efd,0000000000000000000000000000000006bc2ae646a603a1f4524b445cdeb99914e4ed19cd0676d511764b828bfe126e81cad2cb566655f04de1a302c14d70bc00000000000000000000000000000000023bd509aabfa41385e90cd4b1cbbfa45d066c4defab56993aaa386dc5b7707b1a3a7d444b8bd295a30d0b8f4bdc572e0000000000000000000000000000000006f82e60e18cc958375cce6f465db461ff46ed9d15cfcc01a3aff455d54c77ebba5a654c2ec788b6ed8ac53c39defdd3000000000000000000000000000000000896fbe6492c4c297f8b6d60295a7f2565734d69eea67b2675211a203fec043f0d181b1348bea425a068b7bc12676ed0,75000, +000000000000000000000000000000001451bcd19495cea3a19393b77760f688fbf17b107dc131c88cbb503eee2a804e2978d6e8a4720d144083d28be73371d70000000000000000000000000000000017db715e8680a0e82e18b513f2c9c7ea136cefe8add71aac6baba146e3e33a498d025c7e0808ced306c915eb02900c61,0000000000000000000000000000000008604a06a198c3e11458de920176842221667d024f9c155892485a37ff56252be1dc629a6fd580fa41f5e598a23f3651000000000000000000000000000000000e008eed25eafeaa67f27e89e1f81b469724a4b00f08dc4ae672aa1587b19dc615330e3fce0fbd98d7526bc2c4afe69e0000000000000000000000000000000015bc1e4ea5ae2a7fde6d5e5c3e58f6ff5df5bcb125ab402f10edd09087bde39fa27dfcdce7d04fd18ce399729e155fae0000000000000000000000000000000006684e9be8bf9fa4badda842a1d8840f0820d9a797e482c64f4004a18cd63986f19abfc93f6bf068d38eb1e491cabbe6,75000, +0000000000000000000000000000000013a6e129d4dd4aa93cff5489ee879763e2a2231939e609d2d72f07e630b37d09f3057a36fd5cdfc9c81675c996f8ba0f000000000000000000000000000000000e8d7ad082e8f9a718fc2ea712853ed9ab4e8b1a8ca9988f77c70fc759f1fe2d4bd73696e539f130be13b2862efbdf77,000000000000000000000000000000000f15c3d0b40735babb2e38a2471773faa16b2fa307c3a573ef4cfa5a5559574b2d26cf88b19dee204b77f6e11a1b927c000000000000000000000000000000000d224445f3d31d381bb29c4fdc8130174f5bcb957f451c92f4a652cc3d2b5df985017133a944849b5228a88f99bec771000000000000000000000000000000001338b48bc1fa229f251bcd4828654baec9d149f090b19596ad3b444eacc7bc583f97d9cfc40d5611fdcf89cc9a88e33b000000000000000000000000000000000c30dd2aa51f6577d57175edb3ccc1b324717bc195eb0073c1dff4e5b0d77cf5e41ec233527b3936994e86303f91b172,75000, +0000000000000000000000000000000003379bc10acda5ed1014e2bba1e30cf83b72fe69259eb35476a031b8a898e0183bc32ee853a85fb3d738424208fc880900000000000000000000000000000000175a2e5a44ed62744fbbab9581ea7283470bff12436dfc414ad80b4070f895de3786022cbaed55bdbbc4f68db7460548,000000000000000000000000000000001735e1f2fe905839fd6534c95b95322f8cc86a4c482f1ad7691b9b9bb8f55015b4faaa1f243786aa33b5874817cd09c80000000000000000000000000000000013f1a27931ac513145f2601e009cf637ba4bdb18a7604f89534fa3ec8488f5b6eab9963c5d753fdd34cbe7d2f8eb8a5900000000000000000000000000000000092d8f800e7a4bf6f9a25ddd7f64fc403db53b1695ae59c15f229458f347a8e7c2ebc415af2d3849282b670c5cf6f8600000000000000000000000000000000019d22d694e559c55db63521e7b60a1a2342c3cce868d70951e5ed32ec0f5efaeab0e78b21359110f6e769776b745938a,75000, +000000000000000000000000000000000b384a9db472c38a5d246da56059b7630f002b5f4663abce7c5f6e896222a1ca1ac02883a1ec95a4ef09bcfab7d0652a000000000000000000000000000000000de09ef45aafa56936e446e18ef9ff97ca8b81c295d35cf7b72416ebd80586d0fc479d86c23295ac54a23489af045ebc,000000000000000000000000000000000d7dc499e5213120b3ccc173c83d3c15dde9e13ef57238cad84889243b35c8e69eea2ac7ef7560051dcd7402b46b733e00000000000000000000000000000000063ad31c17eb17d39cb4b33e45a0b0e951becc11b685b10cb45cff268b6dca40b780f7e1532be91903372c413a11b5be00000000000000000000000000000000140da959456cbd34e041409350d6106ff65ce6dd2ac3149f04959b16eb83dd0456ca11e5990daf4a1e5c23d3f30a6c4b00000000000000000000000000000000195d07ab127d49baf89fcf5eea1f5e4cffea1a577a5c864c0e637fbdfa10182adc1d5d4ebb871949300193e45ae0fbdd,75000, +0000000000000000000000000000000014df33e7d3ef2c339b958fee667097ccf146556261f7db4b0b0a3c29897b73a0ca249866cff1461488012bc16df43b0d00000000000000000000000000000000099dda253a43b8cfac580306267d9dfeb2c129ac1818fee43c6df5e582f5fa726ba73e1a2ef6a9e011a387c393529678,0000000000000000000000000000000013ec1ef25b303fe2f10a0bbe9bd77a4b2a055e176c2870c99e63b4baf2b313a835459263351dfbc25c22ea32946d8956000000000000000000000000000000000cb1c3292a2e0c9b1c1ff43cbf7595f39c00fd413b54782681fe75a6f5f231d13912f8d598dd8aaae8159de083dccd8e0000000000000000000000000000000005385f2d4bb6d94d67b2a3bacd3aae31da282707672252c0ab1a12fc85d8e9b9eb75454eb145937542099b860f9d6dce000000000000000000000000000000000e59506f7733a38a7e1da4ea5958de4755b52a9307ba2e5813131b33b86f0e401f97594d9674ff1667068a1ec3c9b145,75000, +0000000000000000000000000000000011c89c8d7e83155a308b2e517a23f05a4a55353332292b44b0a891b6f730fd126bd0b97eb87f0fbdb6c82779791d022f000000000000000000000000000000000da6f02450955bf26e236ec63aaf80a018ac34fd8784bb24a22a1fc5e8bd686244a923009a10cb38b1422534d0997afd,000000000000000000000000000000000f4392a41fb3e58dea97b97fd22e2fe6436c3f9bbcd944585a76a5f1a8f98ea4ee21639208d765b6c3a7d08f8cd3f3f00000000000000000000000000000000002c3d62794996dbb881b665eece98926f41a42c21539125fda6070d9f69e29e0557c886b42e4bcd97b14134d6e9d1d710000000000000000000000000000000004b93f315822aa1be8250c2e736727d390ae3a862c4c7dda452817f70f01c73e6f344df1b0f05f03bd574edecc70902e000000000000000000000000000000000731403981fd6243d00c23d0a42a759016f7907548847743f18421f51b1e72cea92f0c5580328babd4ae3e15bc9c56de,75000, +0000000000000000000000000000000015bb227b5c9ccfb8390edcd158b04a69a88a3b99a10ae90e548182751a16448df25493061afde2c9790a0e75e6f409a20000000000000000000000000000000001d7b609155bf3939192eee9642032e6fb10f57d53916674c60211a37b4a7662759899a9569e2dc730febd23f747a7a3,000000000000000000000000000000000b35c6294b70336217eb9334ff1f1bde9d892d109e947de7f4f5681b3830ed00ad1b89ccd7cbad88ce1586449140697d00000000000000000000000000000000032691e5f4597c06496e9e37907041ddcadd18ca8ce64a8b400b1e2e8d63acce5533231edb66b69807fa2dc026c1d2be000000000000000000000000000000000773ccd132cb215cd98aa17d7fc432e0577b08d8faaa35199000d46fdeeb954e8652566384fa0cc5bcd1724942f7075b00000000000000000000000000000000112e951db3694944fc82fb980547cd8b7f2e5ec6fd2051b6aff2573797bd6a28437848ea0627054af1960ad1de0981e5,75000, +00000000000000000000000000000000017599d71686e817cf58b78dd7586d5b359999b32b0dec2d67e33fb6388411418ecfaa2670a2cc9dce3dadaed0fb3364000000000000000000000000000000001773995b540be9ffbfd276a92c0494e4eae296d094f9f7eca975cf4f73ae05e92bd64ea71ac47bba534044f4072a6591,0000000000000000000000000000000018f2eace212eacabd44ff01d886543410ef72b4d27f8d25cb080dbe4b1d4b2b4e57e4dd40723d15789d9b5104b088d9b00000000000000000000000000000000098e9e9b302876ce85ba486609fd028f357314149ce8b530778e6de586ab057fe59648d8c8ae80fe619c4c605b90784a0000000000000000000000000000000016d20a8ca43d37518c8a0f47566ba61a7aade9ea2cdd4a0907ff0ed862c6b7c64815d50397eebec262a05c6010cfaa790000000000000000000000000000000005a70c2fce25acdc4a95fc2bdedb007d71f24b0b5714fa14910ef590215d25442e91a66b6bfea5f7777f0c6d202eff32,75000, +000000000000000000000000000000000f470603a402bc134db1b389fd187460f9eb2dd001a2e99f730af386508c62f0e911d831a2562da84bce11d39f2ff13f000000000000000000000000000000000d8c45f4ab20642d0cba9764126e0818b7d731a6ba29ed234d9d6309a5e8ddfbd85193f1fa8b7cfeed3d31b23b904ee9,0000000000000000000000000000000012e74d5a0c005a86ca148e9eff8e34a00bfa8b6e6aadf633d65cd09bb29917e0ceb0d5c9d9650c162d7fe4aa274526850000000000000000000000000000000005f09101a2088712619f9c096403b66855a12f9016c55aef6047372fba933f02d9d59db1a86df7be57978021e245782100000000000000000000000000000000136975b37fe400d1d217a2b496c1552b39be4e9e71dd7ad482f5f0836d271d02959fdb698dda3d0530587fb86e0db1dd0000000000000000000000000000000000bad0aabd9309e92e2dd752f4dd73be07c0de2c5ddd57916b9ffa065d7440d03d44e7c042075cda694414a9fb639bb7,75000, 00000000000000000000000000000014406e5bfb9209256a3820879a29ac2f62d6aca82324bf3ae2aa7d3c54792043bd8c791fccdb080c1a52dc68b8b69350000000000000000000000000000000000e885bb33996e12f07da69073e2c0cc880bc8eff26d2a724299eb12d54f4bcf26f4748bb020e80a7e3794a7b0e47a641,,,invalid input parameters, invalid input length for Fp2 to G2 to curve mapping 000000000000000000000000000000000014406e5bfb9209256a3820879a29ac2f62d6aca82324bf3ae2aa7d3c54792043bd8c791fccdb080c1a52dc68b8b69350000000000000000000000000000000000e885bb33996e12f07da69073e2c0cc880bc8eff26d2a724299eb12d54f4bcf26f4748bb020e80a7e3794a7b0e47a641,,,invalid input parameters, invalid input length for Fp2 to G2 to curve mapping diff --git a/evm/src/test/resources/org/hyperledger/besu/evm/precompile/g1_add.csv b/evm/src/test/resources/org/hyperledger/besu/evm/precompile/g1_add.csv index c1422c3c048..b6714c2fcd8 100644 --- a/evm/src/test/resources/org/hyperledger/besu/evm/precompile/g1_add.csv +++ b/evm/src/test/resources/org/hyperledger/besu/evm/precompile/g1_add.csv @@ -1,105 +1,105 @@ input,result,gas,notes -0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee000000000000000000000000000000000001101098f5c39893765766af4512a0c74e1bb89bc7e6fdf14e3e7337d257cc0f94658179d83320b99f31ff94cd2bac0000000000000000000000000000000003e1a9f9f44ca2cdab4f43a1a3ee3470fdf90b2fc228eb3b709fcd72f014838ac82a6d797aeefed9a0804b22ed1ce8f7,000000000000000000000000000000001466e1373ae4a7e7ba885c5f0c3ccfa48cdb50661646ac6b779952f466ac9fc92730dcaed9be831cd1f8c4fefffd5209000000000000000000000000000000000c1fb750d2285d4ca0378e1e8cdbf6044151867c34a711b73ae818aee6dbe9e886f53d7928cc6ed9c851e0422f609b11,600, -00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed2000000000000000000000000000000000441e7f7f96198e4c23bd5eb16f1a7f045dbc8c53219ab2bcea91d3a027e2dfe659feac64905f8b9add7e4bfc91bec2b0000000000000000000000000000000005fc51bb1b40c87cd4292d4b66f8ca5ce4ef9abd2b69d4464b4879064203bda7c9fc3f896a3844ebc713f7bb20951d95,0000000000000000000000000000000016b8ab56b45a9294466809b8e858c1ad15ad0d52cfcb62f8f5753dc94cee1de6efaaebce10701e3ec2ecaa9551024ea600000000000000000000000000000000124571eec37c0b1361023188d66ec17c1ec230d31b515e0e81e599ec19e40c8a7c8cdea9735bc3d8b4e37ca7e5dd71f6,600, -0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f00000000000000000000000000000000114c3f11ba0b47551fa28f09f148936d6b290dc9f2d0534a83c32b0b849ab921ce6bcaa4ff3c917707798d9c74f2084f00000000000000000000000000000000149dc028207fb04a7795d94ea65e21f9952e445000eb954531ee519efde6901675d3d2446614d243efb77a9cfe0ca3ae,0000000000000000000000000000000002ce7a08719448494857102da464bc65a47c95c77819af325055a23ac50b626df4732daf63feb9a663d71b7c9b8f2c510000000000000000000000000000000016117e87e9b55bd4bd5763d69d5240d30745e014b9aef87c498f9a9e3286ec4d5927df7cd5a2e54ac4179e78645acf27,600, -0000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e4000000000000000000000000000000000c3d564ac1fe12f18f528c3750583ab6af8973bff3eded7bb4778c32805d9b17846cc7c687af0f46bc87de7748ab72980000000000000000000000000000000002f164c131cbd5afc85692c246157d38dc4bbb2959d2edfa6daf0a8b17c7a898aad53b400e8bdc2b29bf6688ee863db7,0000000000000000000000000000000015510826f50b88fa369caf062ecdf8b03a67e660a35b219b44437a5583b5a9adf76991dce7bff9afc50257f847299504000000000000000000000000000000000a83e879895a1b47dbd6cd25ce8b719e7490cfe021614f7539e841fc2f9c09f071e386676de60b6579aa4bf6d37b13dd,600, -0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d30000000000000000000000000000000019fe3a64361fea14936ff0b3e630471494d0c0b9423e6a004184a2965221c18849b5ed0eb2708a587323d8d6c6735a90000000000000000000000000000000000340823d314703e5efeb0a65c23069199d7dfff8793aaacb98cdcd6177fc8e61ab3294c57bf13b4406266715752ef3e6,00000000000000000000000000000000010b1c96d3910f56b0bf54da5ae8c7ab674a07f8143b61fed660e7309e626dc73eaa2b11886cdb82e2b6735e7802cc860000000000000000000000000000000002dabbbedd72872c2c012e7e893d2f3df1834c43873315488d814ddd6bfcca6758a18aa6bd02a0f3aed962cb51f0a222,600, -000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f90000000000000000000000000000000001461565b03a86df363d1854b4af74879115dffabeddfa879e2c8db9aa414fb291a076c3bdf0beee82d9c094ea8dc381a000000000000000000000000000000000e19d51ab619ee2daf25ea5bfa51eb217eabcfe0b5cb0358fd2fa105fd7cb0f5203816b990df6fda4e0e8d541be9bcf6,000000000000000000000000000000000cb40d0bf86a627d3973f1e7846484ffd0bc4943b42a54ff9527c285fed3c056b947a9b6115824cabafe13cd1af8181c00000000000000000000000000000000076255fc12f1a9dbd232025815238baaa6a3977fd87594e8d1606caec0d37b916e1e43ee2d2953d75a40a7ba416df237,600, -000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1b0000000000000000000000000000000019cabba3e09ad34cc3d125e0eb41b527aa48a4562c2b7637467b2dbc71c373897d50eed1bc75b2bde8904ece5626d6e400000000000000000000000000000000056b0746f820cff527358c86479dc924a10b9f7cae24cd495625a4159c8b71a8c3ad1a15ebf22d3561cd4b74e8a6e48b,000000000000000000000000000000000e115e0b61c1f1b25cc10a7b3bd21cf696b1433a0c366c2e1bca3c26b09482c6eced8c8ecfa69ce6b9b3b4419779262e00000000000000000000000000000000077b85daf61b9f947e81633e3bc64e697bc6c1d873f2c21e5c4c3a11302d4d5ef4c3ff5519564729aaf2a50a3c9f1196,600, -0000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442a0000000000000000000000000000000011f649ee35ff8114060fc5e4df9ac828293f6212a9857ca31cb3e9ce49aa1212154a9808f1e763bc989b6d5ba7cf09390000000000000000000000000000000019af81eca7452f58c1a6e99fab50dc0d5eeebc7712153e717a14a31cffdfd0a923dbd585e652704a174905605a2e8b9d,000000000000000000000000000000000013e37a8950a659265b285c6fb56930fb77759d9d40298acac2714b97b83ec7692a7d1c4ccb83f074384db9eedd809c0000000000000000000000000000000003215d524d6419214568ba42a31502f2a58a97d0139c66908e9d71755f5a7666567aafe30ea84d89308f06768f28a648,600, -0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795000000000000000000000000000000000d713e148769fac2efd380886f8566c6d4662dd38317bb7e68744c4339efaedbab88435ce3dc289afaa7ecb37df37a5300000000000000000000000000000000129d9cd031b31c77a4e68093dcdbb585feba786207aa115d9cf120fe4f19ca31a0dca9c692bd0f53721d60a55c333129,00000000000000000000000000000000029405b9615e14bdac8b5666bbc5f3843d4bca17c97bed66d164f1b58d2a148f0f506d645d665a40e60d53fe29375ed400000000000000000000000000000000162761f1712814e474beb2289cc50519253d680699b530c2a6477f727ccc75a19681b82e490f441f91a3c611eeb0e9e2,600, -0000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a1900000000000000000000000000000000006d92bcb599edca426ff4ceeb154ebf133c2dea210c7db0441f74bd37c8d239149c8b5056ace0bfefb1db04b42664f530000000000000000000000000000000008522fc155eef6d5746283808091f91b427f2a96ac248850f9e3d7aadd14848101c965663fd4a63aea1153d71918435a,000000000000000000000000000000000cfaa8df9437c0b6f344a0c8dcbc7529a07aec0d7632ace89af6796b6b960b014f78dd10e987a993fb8a95cc909822ec0000000000000000000000000000000007475f115f6eb35f78ba9a2b71a44ccb6bbc1e980b8cd369c5c469565f3fb798bc907353cf47f524ba715deaedf379cb,600, -0000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac9430000000000000000000000000000000016380d03b7c5cc3301ffcb2cf7c28c9bde54fc22ba2b36ec293739d8eb674678c8e6461e34c1704747817c8f8341499a000000000000000000000000000000000ec6667aa5c6a769a64c180d277a341926376c39376480dc69fcad9a8d3b540238eb39d05aaa8e3ca15fc2c3ab696047,0000000000000000000000000000000011541d798b4b5069e2541fa5410dad03fd02784332e72658c7b0fa96c586142a967addc11a7a82bfcee33bd5d07066b900000000000000000000000000000000195b3fcb94ab7beb908208283b4e5d19c0af90fca4c76268f3c703859dea7d038aca976927f48839ebc7310869c724aa,600, -000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e00000000000000000000000000000000065eb0770ab40199658bf87db6c6b52cd8c6c843a3e40dd60433d4d79971ff31296c9e00a5d553df7c81ade533379f4b0000000000000000000000000000000017a6f6137ddd90c15cf5e415f040260e15287d8d2254c6bfee88938caec9e5a048ff34f10607d1345ba1f09f30441ef4,0000000000000000000000000000000006b0853b3d41fc2d7b27da0bb2d6eb76be32530b59f8f537d227a6eb78364c7c0760447494a8bba69ef4b256dbef750200000000000000000000000000000000166e55ba2d20d94da474d4a085c14245147705e252e2a76ae696c7e37d75cde6a77fea738cef045182d5e628924dc0bb,600, -0000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276b0000000000000000000000000000000006a3f7eb0e42567210cc1ba5e6f8c42d02f1eef325b6483fef49ba186f59ab69ca2284715b736086d2a0a1f0ea224b40000000000000000000000000000000000bc08427fda31a6cfbe657a8c71c73894a33700e93e411d42f1471160c403b939b535070b68d60a4dc50e47493da63dc,000000000000000000000000000000000c35d4cd5d43e9cf52c15d46fef521666a1e1ab9f0b4a77b8e78882e9fab40f3f988597f202c5bd176c011a56a1887d4000000000000000000000000000000000ae2b5c24928a00c02daddf03fade45344f250dcf4c12eda06c39645b4d56147cb239d95b06fd719d4dc20fe332a6fce,600, -00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a578768100000000000000000000000000000000068e79aea45b7199ec4b6f26e01e88ec76533743639ce76df66937fff9e7de3edf6700d227f10f43e073afcc63e2eddc00000000000000000000000000000000039c0b6d9e9681401aeb57a94cedc0709a0eff423ace9253eb00ae75e21cabeb626b52ef4368e6a4592aed9689c6fca4,0000000000000000000000000000000013bad27dafa20f03863454c30bd5ae6b202c9c7310875da302d4693fc1c2b78cca502b1ff851b183c4b2564c5d3eb4dc0000000000000000000000000000000000552b322b3d672704382b5d8b214c225b4f7868f9c5ae0766b7cdb181f97ed90a4892235915ffbc0daf3e14ec98a606,600, -0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f7680000000000000000000000000000000000adac9bb98bb6f35a8f941dbff39dfd307b6a4d5756ccae103c814564e3d3993a8866ff91581ccdd7686c1dce0b19f700000000000000000000000000000000083d235e0579032ca47f65b6ae007ce8ffd2f1a890ce3bc45ebd0df6673ad530d2f42125d543cb0c51ba0c28345729d8,000000000000000000000000000000000b5513e42f5217490f395a8cb3673a4fc35142575f770af75ecf7a4fcd97eee215c4298fc4feab51915137cbdb814839000000000000000000000000000000000e9d4db04b233b0b12a7ff620faefef906aeb2b15481ce1609dad50eb6a7d0c09a850375599c501296219fb7b288e305,600, -00000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f42000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931d000000000000000000000000000000000d5bb4fa8b494c0adf4b695477d4a05f0ce48f7f971ef53952f685e9fb69dc8db1603e4a58292ddab7129bb5911d6cea0000000000000000000000000000000004a568c556641f0e0a2f44124b77ba70e4e560d7e030f1a21eff41eeec0d3c437b43488c535cdabf19a70acc777bacca,000000000000000000000000000000000c27ef4ebf37fd629370508f4cd062b74faa355b305d2ee60c7f4d67dd741363f18a7bbd368cdb17e848f372a5e33a6f0000000000000000000000000000000000ed833df28988944115502f554636e0b436cccf845341e21191e82d5b662482f32c24df492da4c605a0f9e0f8b00604,600, -000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80000000000000000000000000000000000874389c02d4cf1c61bc54c4c24def11dfbe7880bc998a95e70063009451ee8226fec4b278aade3a7cea55659459f1d500000000000000000000000000000000091ee883cb9ea2c933f6645f0f4c535a826d95b6da6847b4fe2349342bd4bd496e0dd546df7a7a17a4b9fb8349e5064f000000000000000000000000000000000902d7e72242a5e6b068ca82d0cb71dc0f51335dbd302941045319f9a06777518b56a6e0b0b0c9fd8f1edf6b114ad331,00000000000000000000000000000000122cce99f623944dfebffcdf6b0a0a3696162f35053e5952dddc2537421c60da9fe931579d1c4fc2e31082b6c25f96b500000000000000000000000000000000011366ffa91dc0b7da8b7c1839ea84d49299310f5c1ca244012eed0dd363dbcf4ad5813b8e3fb49361ef05ea8cb18ffe,600, -00000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a67000000000000000000000000000000000013a80ede40df002b72f6b33b1f0e3862d505efbe0721dce495d18920d542c98cdd2daf5164dbd1a2fee917ba943debe0000000000000000000000000000000000d3d4f11bc79b8425b77d25698b7e151d360ebb22c3a6afdb227de72fe432dcd6f0276b4fd3f1fcc2da5b59865053930000000000000000000000000000000015ac432071dc23148765f198ed7ea2234662745a96032c215cd9d7cf0ad8dafb8d52f209983fe98aaa2243ecc2073f1b,000000000000000000000000000000000113ccf11264ff04448f8c58b279a6a49acb386750c2051eab2c90fa8b8e03d7c5b9e87eccf36b4b3f79446b80be7b1d0000000000000000000000000000000004358a1fabfe803f4c787a671196b593981a837ee78587225fb21d5a883b98a15b912862763b94d18b971cb7e37dbcf0,600, -0000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e880000000000000000000000000000000008d7489c2d78f17b2b9b1d535f21588d8761b8fb323b08fa9af8a60f39b26e98af76aa883522f21e083c8a14c2e7edb600000000000000000000000000000000034f725766897ed76394145da2f02c92c66794a51fd5ae07bd7cc60c013d7a48ebf1b07faf669dfed74d82d07e48d1150000000000000000000000000000000018f4926a3d0f740988da25379199ecb849250239ad7efcfef7ffaa43bc1373166c0448cc30dcdbd75ceb71f76f883ea7,00000000000000000000000000000000167336aeeb9e447348156936849d518faee314c291c84d732fa3c1bd3951559230d94230e37a08e28e689e9d1fef05770000000000000000000000000000000005366535f7a68996e066ab80c55bb372a15fb0ed6634585b88fe7cafbf818fbfebbf6f6ddd9ca0ff72137594a1e84b35,600, -0000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000aba6a0b58b49f7c6c2802afd2a5ed1320bf062c7b93135f3c0ed7a1d7b1ee27b2b986cde732a60fa585ca6ab7cc154b00000000000000000000000000000000079e5a154cf84190b6c735bc8cd968559182166568649b813732e4fb4c5c428c8b38e8265d4ef04990c49aa1381f51c8000000000000000000000000000000000ae08e682ef92b4986a5ac5d4f094ad0919c826a97efe8d8120a96877766eae5828803804a0cae67df9822fd18622aae,000000000000000000000000000000000a3d66cf87b1ce8c5683d71a6de4bf829d094041240f56d9071aa84ff189a06940e8e1935127e23a970c78ca73c28bf6000000000000000000000000000000000b2adda87740873c0c59e3ebde44d33834773f0fe69e2f5e7ede99c4f928978a5caaede7262e45fd22136a394b3f7858,600, -0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b0900000000000000000000000000000000082543b58a13354d0cce5dc3fb1d91d1de6d5927290b2ff51e4e48f40cdf2d490730843b53a92865140153888d73d4af0000000000000000000000000000000008cefd0fd289d6964a962051c2c2ad98dab178612663548370dd5f007c5264fece368468d3ca8318a381b443c68c4cc7000000000000000000000000000000000708d118d44c1cb5609667fd51df9e58cacce8b65565ef20ad1649a3e1b9453e4fb37af67c95387de008d4c2114e5b95,0000000000000000000000000000000004b2311897264fe08972d62872d3679225d9880a16f2f3d7dd59412226e5e3f4f2aa8a69d283a2dc5b93e022293f0ee1000000000000000000000000000000000f03e18cef3f9a86e6b842272f2c7ee48d0ad23bfc7f1d5a9a796d88e5d5ac31326db5fe90de8f0690c70ae6e0155039,600, -00000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000097ec91c728ae2d290489909bbee1a30048a7fa90bcfd96fe1d9297545867cbfee0939f20f1791329460a4fe1ac719290000000000000000000000000000000008e5afc16d909eb9d8bdaaf229ad291f34f7baf5247bbd4cc938278f1349adb4b0f0aacd14799c01d0ca2ed38c937d600000000000000000000000000000000006cf972c64e20403c82fee901c90eaa5547460d57cce2565fd091ff9bc55e24584595c9182298f148882d6949c36c9d5,000000000000000000000000000000000caf46f480ae2ea8e700f7913c505d5150c4629c9137e917357d2a4ba8a7a1c63b8f6e2978293755952fbed7f0ad8d6d0000000000000000000000000000000002e62e715b72eebbc7c366a2390318f73e69203a9533e72340aab568f65105129ffc9889a8bc00a692494d93688c7ec0,600, -000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed302500000000000000000000000000000000130f139ca118869de846d1d938521647b7d27a95b127bbc53578c7b66d88d541adb525e7028a147bf332607bd760deac0000000000000000000000000000000013a6439e0ec0fabe93f6c772e102b96b1f692971d7181c386f7f8a360daca6e5f99772e1a736f1e72a17148d90b08efe0000000000000000000000000000000010f27477f3171dcf74498e940fc324596ef5ec6792be590028c2963385d84ef8c4bbb12c6eb3f06b1afb6809a2cb0358,000000000000000000000000000000000dea57d1fc19f994e6bdda9478a400b0ada23aed167bfe7a16ef79b6aa020403a04d554303c0b2a9c5a38f85cf6f3800000000000000000000000000000000000b8d76ccd41ba81a835775185bbf1d6bf94b031d94d5c78b3b97beb24cf246b0c25c4c309e2c06ae9896ed800169eeee,600, -0000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e70580000000000000000000000000000000010e4280374c532ed0df44ac0bac82572f839afcfb8b696eea617d5bd1261288dfa90a7190200687d470992fb4827ff320000000000000000000000000000000005728a219d128bc0a1f851f228e2bf604a72400c393cfb0d3484456b6b28a2c5061198656f0e106bbe257d849be159040000000000000000000000000000000011f6d08baa91fb2c8b36191d5b2318e355f8964cc8112838394ba1ded84b075de58d90452601dcfc9aa8a275cfec695d,0000000000000000000000000000000012e6d6c518c15cfd3020181ff3f829e29140b3b507b99251cc7f31795128adec817750296bce413bac18b9a80f69ca5000000000000000000000000000000000131ee9b748f6f1eb790adeb9edd0e79d89a9908368f5a6bb82ee0c913061cdfffe75d9ba411a49aa3f9194ee6d4d08a9,600, -0000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000ebdef273e2288c784c061bef6a45cd49b0306ac1e9faab263c6ff73dea4627189c8f10a823253d86a8752769cc4f8f200000000000000000000000000000000171696781ba195f330241584e42fb112adf9b8437b54ad17d410892b45c7d334e8734e25862604d1b679097590b8ab0a000000000000000000000000000000001879328fdf0d1fb79afd920e0b0a386828be5b8e0e6024dfeea800ffcb5c65f9044061af26d639d4dcc27bcb5ba1481a,00000000000000000000000000000000111c416d5bd018a77f3317e3fbf4b03d8e19658f2b810dc9c17863310dfb09e1c4ffdbb7c98951d357f1c3d93c5d0745000000000000000000000000000000000af0a252bff336d5eb3a406778557ef67d91776a9c788be9a76cff7727f519a70fc7809f1a50a58d29185cb9722624fd,600, -000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b10000000000000000000000000000000011efaeec0b1a4057b1e0053263afe40158790229c5bfb08062c90a252f59eca36085ab35e4cbc70483d29880c5c2f8c2000000000000000000000000000000000231b0d6189a4faad082ce4a69398c1734fcf35d222b7bce22b14571033a1066b049ae3cd3bd6c8cec5bec743955cdd600000000000000000000000000000000037375237fb71536564ea693ab316ae11722aadd7cab12b17b926c8a31bd13c4565619e8c894bffb960e632896856bbe,000000000000000000000000000000000d2b9c677417f4e9b38af6393718f55a27dbd23c730796c50472bc476ebf52172559b10f6ceb81e644ec2d0a41b3bb01000000000000000000000000000000001697f241ff6eceb05d9ada4be7d7078ecbbffa64dd4fb43ead0692eef270cb7cc31513ee4bf38a1b1154fe008a8b836a,600, -00000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e2728390000000000000000000000000000000006dc2ccb10213d3f6c3f10856888cb2bf6f1c7fcb2a17d6e63596c29281682cafd4c72696ecd6af3cce31c440144ebd10000000000000000000000000000000015653d1c5184736cdc78838be953390d12b307d268b394136b917b0462d5e31b8f1b9d96cce8f7a1203c2cae93db6a4000000000000000000000000000000000060efeece033ac711d500c1156e4b6dce3243156170c94bc948fd7beae7b28a31463a44872ca22ca49dc5d4d4dd27d1c,0000000000000000000000000000000003996050756117eeab27a5e4fa9acdde2a1161d6fbfff2601a1c7329f900e93a29f55a8073f85be8f7c2a4d0323e95cc00000000000000000000000000000000010b195a132c1cba2f1a6a73f2507baa079e9b5cb8894ea78bebc16d4151ee56fe562b16e2741f3ab1e8640cdad83180,600, -000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b0000000000000000000000000000000006d38cc6cc1a950a18e92e16287f201af4c014aba1a17929dd407d0440924ce5f08fad8fe0c50f7f733b285bf282acfc0000000000000000000000000000000018adb42928304cbc310a229306a205e7c21cdb31b9e5daf0ff6bb9437acee80cd8cf02b35dab823155d60f8a83fde5cc0000000000000000000000000000000018b57460c81cab43235be79c8c90dcda40fafcaf69e4e767133aee56308a6df07eac71275597dd8ed6607ffb9151ed9a,0000000000000000000000000000000003c7a7ee3d1b73cf1f0213404363bf3c0de4425ab97d679ed51448e877b7537400f148f14eba588ed241fea34e56d465000000000000000000000000000000000c581b5070e6bb8582b7ee2cd312dfeb5aaf0b0da95cf5a22a505ffba21fc204e26a5e17311d1f47113653ff13349f57,600, -0000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000fb24d3d4063fd054cd5b7288498f107114ff323226aca58d3336444fc79c010db15094ceda6eb99770c168d459f0da00000000000000000000000000000000001da65df8574a864ab454e5f2fa929405501bb73c3162a600979a1145586079361c89839cc0c5a07f1135c94bf059f9c0000000000000000000000000000000002560df402c0550662a2c4c463ad428ab6e60297fbc42a6484107e397ae016b58494d1c46ac4952027aa8c0896c50be3,000000000000000000000000000000000d7a539b679e5858271a6f9cf20108410eb5d5d2b1a905e09a8aa20318efbe9175450385d78389f08f836f5634f7a2f0000000000000000000000000000000000fb624e5f6c4c814b7d73eb63b70237c5de7d90d19ac81cac776d86171a8d307d3cc8c56da14f444fe8cf329ab7e63dd,600, -0000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe976000000000000000000000000000000000824e1631f054b666893784b1e7edb44b9a53596f718a6e5ba606dc1020cb6e269e9edf828de1768df0dd8ab8440e0530000000000000000000000000000000005311c11f4d0bb8542f3b60247c1441656608e5ac5c363f4d62127cecb88800a771767cf23a0e7c45f698ffa5015061f0000000000000000000000000000000018f7f1d23c8b0566a6a1fcb58d3a5c6fd422573840eb04660c3c6ba65762ed1becc756ac6300e9ce4f5bfb962e963419,0000000000000000000000000000000000849bbc7b0226b18abbcb4c9a9e78dca2f5f75a2cbb983bd95ff3a95b427b1a01fd909ce36384c49eb88ffb8ff77bb000000000000000000000000000000000087d8d28d92305b5313ca533a6b47f454ddce1c2d0fa3574b255128ef0b145fa4158beb07e4f0d50d6b7b90ea8a8ea8a,600, -00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000c2ffe6be05eccd9170b6c181966bb8c1c3ed10e763613112238cabb41370e2a5bb5fef967f4f8f2af944dbef09d265e000000000000000000000000000000000c8e293f730253128399e5c39ab18c3f040b6cd9df10d794a28d2a428a9256ea1a71cf53022bd1be11f501805e0ddda40000000000000000000000000000000003e60c2291be46900930f710969f79f27e76cf710efefc243236428db2fed93719edeeb64ada0edf6346a0411f2a4cb8,00000000000000000000000000000000191084201608f706ea1f7c51dd5b593dda87b15d2c594b52829db66ce3beab6b30899d1d285bdb9590335949ceda5f050000000000000000000000000000000000d3460622c7f1d849658a20a7ae7b05e5afae1f01e871cad52ef632cc831b0529a3066f7b81248a7728d231e51fc4ad,600, -0000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad6242000000000000000000000000000000000353d6521a17474856ad69582ce225f27d60f5a8319bea8cefded2c3f6b862d76fe633c77ed8ccdf99d2b10430253fc80000000000000000000000000000000013267db8fdf8f488a2806fead5cffdcbb7b1b4b7681a2b67d322cd7f5985c65d088c70cdc2638e679ed678cae3cc63c80000000000000000000000000000000007757233ad6d38d488c3d9d8252b41e4ab7ee54e4ef4bbf171402df57c14f9977dd3583c6c8f9b5171b368d61f082447,000000000000000000000000000000000c06fef6639ab7dceb44dc648ca6a7d614739e40e6486ee9fc01ecc55af580d98abc026c630a95878da7b6d5701d755c0000000000000000000000000000000007c9a7f2bc7fa1f65c9e3a1e463eb4e3283e47bb5490938edb12abf6c8f5a9b56d8ce7a81a60df67db8c399a9a1df1d4,600, -00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff17000000000000000000000000000000001975bc52669187f27a86096ae6bf2d60178706105d15bce8fe782759f14e449bc97cb1570e87eec5f12214a9ae0e0170000000000000000000000000000000000ca6106d6e6487a3b6f00fc2af769d21cb3b83b5dc03db19e4824fc28fd9b3d9f7a986e79f05c02b3a914ff26c7a78d6,0000000000000000000000000000000002fbf4fba68ae416b42a99f3b26916dea464d662cebce55f4545481e5ab92d3c40f3e189504b54db4c9cd51ecdd60e8d0000000000000000000000000000000008e81e094c6d4ded718ef63c5edfacb2d258f48ccfa37562950c607299bb2dca18e680a620dff8c72dedc89b4e9d4759,600, -0000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d3f00000000000000000000000000000000109f6168a719add6ea1a14f9dc95345e325d6b0e56da2f4ecff8408536446894069fa61e81bdaebfc96b13b402fad865000000000000000000000000000000001806aa27c576f4c4fa8a6db49d577cd8f257a8450e89b061cbc7773c0b5434f06bacf12b479abf6847f537c4cbefcb46,0000000000000000000000000000000014e0bd4397b90a3f96240daf835d5fb05da28a64538f4bf42d9e7925a571f831c6e663910aa37dcc265ddd7938d83045000000000000000000000000000000001695d405d4f8ba385ebf4ad25fb3f34c65977217e90d6e5ed5085b3e5b0b143194f82e6c25766d28ad6c63114ca9dcdf,600, -00000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9af0000000000000000000000000000000019d3623a7866933e2d73214ceb2e56097a1b047db5943c3ecb846890aa02250126e90fc76a729a952cef895bd154cc7d000000000000000000000000000000000e87c376bbd695a356ef72226ac7ef6a550d99e9693d8485770a686e568ae28c038ee201d3f2ea38362046236ade91cd,000000000000000000000000000000000ffeab47985bd9b3e10ce27c6636bbda336dcf540cd37eccc3faec2adff2d97dd126633bd83a7d3c8c73c3623bdf0ba2000000000000000000000000000000001992eca4b1e924b360d57ca98b543ab496a8b55bd288d23f03bcc1b22f6bc76d95b12f47c3e305812097253c73b876dd,600, -000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c300000000000000000000000000000000163aaecf83d6c77a5d7417e73f5cf9d71a6aedfd194b2f3b53c608d06a228190f4f79ac57b029d77504c72744df4ecc0000000000000000000000000000000000416e6f9ca188d16daa2c28acd6a594f8fcb990eaa26e60ca2a34dfcad7ad76c425b241acedf674d48d298d0df0f824d,000000000000000000000000000000001812bcb26fa05e0ab5176e703699ab16f5ef8917a33a9626ae6ff20f2a6f4a9d5e2afe3a11f57061cbaa992e1f30477f000000000000000000000000000000000680acf0b632cb48017cb80baa93753d030aa4b49957178d8a10d1d1a27bbdc89ac6811a91868b2c181c5c0b9b6caf86,600, -0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8db000000000000000000000000000000000aba7362eee717d03ef2d4f0fef2763822115fcc8fb9e2e8243683b6c1cde799ebc78f23812e557de2cc38e2b4a2e56700000000000000000000000000000000170833db69b3f067cf5c4c4690857e6711c9e3fcad91ca7cd045e9d2f38c7b31236960e8718f5dd4c8bfb4de76c6c9b9,00000000000000000000000000000000196ffe76a4b726fa8dd720cc1cd04c040724cb18ec10915e312eaa90d124100b08f0ce3a7fc888f46914319a3d7581f4000000000000000000000000000000000e2612357059ca6dbb64efb98ef19370560c9e83e2aad7ab2d9015e2444fe4d8c796b5577584aac9f63258beb5ae863c,600, -00000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da8257721808000000000000000000000000000000000a98ae36c690f2e3be8100f43678be5a1064390e210328dd23f61f5a496b87398db2798580edeabc6273fb9537fa12880000000000000000000000000000000009aedf77bb969592c6552ae0121a1c74de78ba222b6cd08623c7a34708a12763b5ff7969cf761ccd25adc1b65da0f02d,00000000000000000000000000000000072334ec8349fc38b99d6dea0b4259c03cd96c1438c90ef0da6321df2495892de031a53c23838ca2b260774fa09b5461000000000000000000000000000000000e4535767c2477c4f87c087540c836eeffcd0c45960841f9c3561a8a5f8e61ab98b183b11192b8e7ea1c9c7717336243,600, -000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e00000000000000000000000000000000015c3c056ec904ce865d073f8f70ef2d4b5adb5b9238deaa5e167d32f45cad4901aa6d87efa2338c633e7853ce4c19185000000000000000000000000000000000a15f1aa6e662f21d7127351a1655821c943c4cf590e3c9e60c9ab968b4a835f87fb8d87eee6331ee4e194e5f1ea91f4,000000000000000000000000000000000140fb6dcf872d0a3bff3e32a0cb4a7fb7e60ee4fb476bb120c4ce068e169d72e1c167d7fda321280d5855983d5a9af800000000000000000000000000000000108f54a4ec3ba26dd614f4d94c5c82652583906986158ad40ffea54c17703fa4b0bd7806633e1c0318d06e8dc7d41cde,600, -0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dcb000000000000000000000000000000000307841cb33e0f188103a83334a828fa864cea09c264d5f4343246f64ab244add4610c9ccd64c001816e5074fe84013f000000000000000000000000000000000e15bbeb6fff7f1435097828f5d64c448bbc800f31a5b7428436dcffd68abc92682f2b01744d7c60540e0cd1b57ab5d4,000000000000000000000000000000000a1b50660ed9120fff1e5c4abb401e4691a09f41780ca188cea4b1c2d77002f08ce28eb1caa41ee3fe73169e3651bb7f00000000000000000000000000000000125439ac3b45c698a98063ab911364bd3c6dd2a69435d00d6edf89fc5566b33038e960a125e5e52141abb605587942fe,600, -000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6300000000000000000000000000000000013866438b089d39de5a3ca2a624d72c241a54cbdcf5b2a67ebdd2db8373b112a814e74662bd52e37748ffbfc21782a5000000000000000000000000000000000d55454a22d5c2ef82611ef9cb6533e2f08668577764afc5bb9b7dfe32abd5d333147774fb1001dd24889775de57d305,000000000000000000000000000000000037b4e8846b423335711ac12f91e2419de772216509d6b9deb9c27fd1c1ee5851b3e032bf3bcac3dd8e93f3dce8a91b00000000000000000000000000000000113a1bf4be1103e858c3be282effafd5e2384f4d1073350f7073b0a415ecf9e7a3bfb55c951c0b2c25c6bab35454ecf0,600, -00000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f180320000000000000000000000000000000017440fd557df23286da15f9a96bb88cfbc79589b1c157af13baf02c65227dc0a5bdec6f2f300083ff91dae395ed8cb75000000000000000000000000000000000ad09b4290842cc599d346110fdb39ededbb1d651568579564e274465f07b8f77eeaf00fece0c10db69c2125de8ab394,0000000000000000000000000000000007c158b4e21566742f7e4e39a672bd383e27864505acef4ef8c26f8b0a9db418f9c088b555b8e9eb25acf9859b1207b40000000000000000000000000000000016e06a1ace89f992d582af0de7662ef91c0a98f574306f6f6d0d8d5e80166638d2deef70105cce2e9b20faa9d6315510,600, -000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f0000000000000000000000000000000000d7ccc3a4efdfe1a92a88e453933b8216016091f1b9d575faf18a5b3abf90daf077813167a3f4acce7359472dee544bb00000000000000000000000000000000128008c075ab176100e755cbb8de5b9ff0e9a78114f862d26ed030d9c1d1dea1c21ec8ae4d82a84d3ff5ae4c1cd6f339,000000000000000000000000000000000b84f9de79c748e37797c629cb78b86b4b736b199f161b30147b5dacf6eabe0b54afce40d5dacfe9a8ee8da5ef5b49de0000000000000000000000000000000010277ad094bb9a3b96379b1366dd90125b51a21ebeb4f776a81d9d9c1f37ab58c32a884a26fa32c83783ed0eef42b820,600, -000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5e00000000000000000000000000000000008da4a93d5ffcdaa0adc736a59f0c187ae3bf11ecb5e9e6f6aedea976a47757739042200b4c4593c2dd5db555425531000000000000000000000000000000000a6fdb2d4160c6c35223daa6fa10d0b1073de07fe4f2eba28e65ed049ff8d8852ed0538b30759fe7a0d944009ddf9a6f,000000000000000000000000000000000d740bd1effd8674250618af0358ad0b83bbc787f0264af9c2ada72fa5431be909e82155da1de0211f46fb307e9949f0000000000000000000000000000000000ddf62c91d587a14b64feef07da52c081b40fbbf9a0f2eae8b66022e0850fc94de6a467e7e4f580c7f2c806f6c6ed8cf,600, -000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d790000000000000000000000000000000003258d7931a1d72ab6344c7e96c0dbd435a7909fe68cc679c08ca9b62f7a6a04863082cbcfdbe9a736625d895e4f3bdb0000000000000000000000000000000009ee3e470e2b2cebc955ba3444b7e478f887138e36c13bd68490689122627269ea5e7ce22dd9c69792394a24187103d6,000000000000000000000000000000000af674691f5d87655f0066188fac5013f31b4169a0181d3feb7ac3beae0d9a3429d4125f099ee344f644a2de8b941f9f00000000000000000000000000000000042a9603b8e4a6c37d59ede3a1398f5f80c5298da66de575a204ee28811d9f7c7c0dd40cef3769bd72a2156b9eb620c8,600, -000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e820000000000000000000000000000000001833807f1ced52399305419450355499a63411837ee61ad681559d59561db18511eb1e8ad3161e7fe30016b560d18b8f00000000000000000000000000000000198b11b31586e17964a4a4ccdee85703163d2106481833e71f26327a589bafb43578d08d87f6cb19c7a04b4ca92392bf,000000000000000000000000000000001081c3359a0fadfe7850ce878182859e3dd77028772da7bcac9f6451ac6455739c22627889673db626bbea70aa3648d50000000000000000000000000000000000f4e8766f976fa49a0b05ef3f06f56d92fe6452ff05c3fac455f9c16efadf1b81a44d2921bed73511dda81d6fc7478e,600, -000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b811580000000000000000000000000000000007dc719ae9e3f1e11d3ed4747a546a7b973ccb1967adb1b3066645a8bde9632bcfa3530e768f088ddbc022b169e67cbf000000000000000000000000000000000bbf9cf884b19c84045da1cead7dcd9fdbf39d764ff1ad60d83ed1e4fd0ce0554f0fb618203952cf02a7c4ba466c66b8,000000000000000000000000000000000f60d66fd1ed5eb04f9619d6458c522cc49f5ace111aff2b61903b112559972f80ac615591463abf2b944c4f99d4c03e000000000000000000000000000000000001a1abfa869be2cda6bd7e05454a8735e1b638db7e1b3715708539c2d14ade53069c7e68b36d3b08cff80837028b7d,600, -0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00ccd0000000000000000000000000000000014b78c66c4acecdd913ba73cc4ab573c64b404a9494d29d4a2ba02393d9b8fdaba47bb7e76d32586df3a00e03ae2896700000000000000000000000000000000025c371cd8b72592a45dc521336a891202c5f96954812b1095ba2ea6bb11aad7b6941a44d68fe9b44e4e5fd06bd541d4,0000000000000000000000000000000015b164c854a2277658f5d08e04887d896a082c6c20895c8809ed4b349da8492d6fa0333ace6059a1f0d37e92ae9bad30000000000000000000000000000000001510d176ddba09ab60bb452188c2705ef154f449bed26abf0255897673a625637b5761355b17676748f67844a61d4e9f,600, -0000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe900000000000000000000000000000000104ee0990ba4194916f670f44e254200971b67a18ed45b25c17be49df66e4f9b934bac8c1552ecc25bdaa3af55952076000000000000000000000000000000000591094d9d89afe025ca1832d7f3e60444f83e72403a434b42216b6c4213980d29e4ef0c64ae497006de550c1faa9425,0000000000000000000000000000000006db0cc24ffec8aa11aecc43e9b76a418daac51d51f3de437090c1bcaabace19f7f8b5ceb6277d6b32b7f3b239a90c4700000000000000000000000000000000069e01f60ca7468c6b9a247c79d18cf3d88bf5d1d62c76abf9237408edeba05dea744205ac5b501920f519bb847bb711,600, -00000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193a0000000000000000000000000000000004840d028d0c0f056aeb37b7a8505325081e9822ef26046f2da72f2155c20987dd51f4b5577c5395e24288b71d2ce5140000000000000000000000000000000015f231a233e997633c1d6492e0df358fb658ae29d0f53928c8a0578484c899a699178ca3223772210063aa08991c3fff,000000000000000000000000000000000fa72bf2d7d564cc4982b9f2cdca743d2ac14f0f1be4218dbafb8b93a9277e55273487a5d2857fd3f731ac4ee469a6a1000000000000000000000000000000000fce44f886453c6ca5ebde9af41d2be92d1126e9897d72978a179dd7eebeed6242b6e9718604ab0c9369529a0426a575,600, -000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece1738220000000000000000000000000000000004877b97faa1d05d61ab65001110bf190d442cabcd6d4d1b9c1f0e513309aebd278f84a80354dfdef875769d00ec2c7500000000000000000000000000000000187066cccb5008bc2ffd0bcd1b227a5a0fe0cd4984316ba3cfd5113c4632a04c56cbda8d48993bd0dd50e9b7ce2b7ee9,0000000000000000000000000000000019ecd38afacc6b281b2515270157328e18039d51574bae0f7e0ef16c3f6da89f55ddee9e3bbb450ad51fe11edfd9f18d00000000000000000000000000000000088a5e292761bbf7a914a9f723de099035e91bd3c1fe9cd50728a4ceaa4fd3953683f30aa8e70ba0eb23919092aa9e22,600, -0000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10c000000000000000000000000000000001881f5aba0603b0a256e03e5dc507598dd63682ce80a29e0fa141b2afdadf6168e98221e4ee45d378cee0416baaadc49000000000000000000000000000000000070d255101319dd3a0f8ca3a0856188428c09de15475d6b70d70a405e45ab379a5b1f2e55f84bd7fe5dd12aeedce670,0000000000000000000000000000000011ccd455d5e3eba94567a17bcd777559b4ff1afa66fd6f05f99c69937404290a2f1c83cfd6c2c25886ebff4934332c0e0000000000000000000000000000000010920aa3d5974df25530610ef466adce3d51fd6a508d4b1111739c586dfd7ba9040836e075fd812fe111d92f25b67f51,600, -0000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119bf000000000000000000000000000000000b53e5339f25bcd31afd091362874b5042c0b762ed7425341331630addbc4dccc299936e1acdf89823c36867d46c6f28000000000000000000000000000000000fc3c6b522268511dd52826dd1aee707413d925ee51aeb0e5d69c0e3eb697fabbc14783b5007e240cc0c53c299a40ada,00000000000000000000000000000000060773b9b8f3babdba3db27089b7be3e6e287a635dbae19576039d34ae18a0e6413278bfa280570f6329ae05cdb693fd00000000000000000000000000000000075fb9527f99a8c8db41e67baaf1deafffd2c134badb1b3478a26b5501b31dca858fad6f0d52f412d5631ecfa72eece4,600, -0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b2000000000000000000000000000000001693f4ebab3fed548784264196fb01cf55311399f47cdad74a9543bda5d1ca682a00ee04bb0b3954d5a0f00ceef97a750000000000000000000000000000000017f4019c23bd68e84d889857c417b17aa96c780fec3c1ed6ca75100cc70c97a8bb8272ad4c6de896d76dc2a1b09c7a61,000000000000000000000000000000000a3ea8afdc83794f18f9a9427bcd60a355196925d38fdf74ab09d4a08279647b2da6f1fbe30948a785497d6c6dddc2a9000000000000000000000000000000001263c88f1ca3e574cafac21641432d45ee01e1b05eba95716565922abe28c7f0fb004c255afcbfa10cf7959bbe6b00d7,600, -00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0b0000000000000000000000000000000005d5602e05499a435effff3812744b582b0cd7c68f1c88faa3c268515c8b14f3c041b8ae322fe526b2406e7c25d84e61000000000000000000000000000000001038eaf49e74e19111e4456ebba01dc4d22c7e23a303d5dec821da832e90a1b07b1a6b8034137f1bfdcddeb58053a170,0000000000000000000000000000000019258ea5023ce73343dcd201ec9be68ec1ee1cb4e5b9964309d801c2bc523343c8ebc4f8393a403c7881e5928f29db14000000000000000000000000000000001423bf52daefb432162ce2bd9ef78b256ff3b24d0a84766b87119489fd56ecf6156b2884c8a7e1220e493469723cd7f8,600, -0000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751a0000000000000000000000000000000002626f28d421d9d1c28f5e1eb5a51ada9610dbdd62cd33c4078d2fdfc18dbd092e2847cf705ba5fcd8c1a60c1cc34a3b0000000000000000000000000000000001f7b8cfdb7e406c920f5fdecae45fb4be736f209480ccb455f972c6b1a1aebdd5ba116903c46ded72ce37cd8836e871,00000000000000000000000000000000081d674f5b9c7c64673c39fe33f4f3d77271e826dcb4dfd2591062e47c931237e8539ef9c886c9e112eccc50da4f63fd00000000000000000000000000000000141b700695839110ed4ced5f8a3f4fd64a8086805358ab4a5abd2705592e616cd95ff01271212ca9014dcb68d8157ba0,600, -0000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df91000000000000000000000000000000000259e307eacb1bc45a13811b02a7aeaaf4dc2bb405dcd88069bb6ec1c08a78905516169bd3440a36921764df0ef3a85b000000000000000000000000000000001263372b675124f6cc19ca16842ba069c5697dbf57730875fe72c864a81189d7d16fe126b5d24953a0524f96dbac5183,000000000000000000000000000000001908aa3a640817e31a4213156fbd4fd39ab39eb931091670a0e06399def71a689e67286f90d38ce9f97cb85f6488d9c8000000000000000000000000000000000764e46b6b82aa2f8862d28e9d543a751a9de855645377b9633cc098c2110ec6ed4fd30f0044ea5868c93f950f6cfd24,600, -000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c04000000000000000000000000000000000a138203c916cb8425663db3bbff37f239a5745be885784b8e035a4f40c47954c48873f6d5aa06d579e213282fe789fa0000000000000000000000000000000016897b8adbc3a3a0dccd809f7311ba1f84f76e218c58af243c0aa29a1bb150ed719191d1ced802d4372e717c1c97570a,0000000000000000000000000000000004ad79769fd10081ebaaed9e2131de5d8738d9ef143b6d0fa6e106bd82cfd53bbc9fab08c422aa03d03896a0fb2460d0000000000000000000000000000000000bb79356c2d477dfbcb1b0e417df7cb79affbe151c1f03fa60b1372d7d82fd53b2160afdd88be1bf0e9dc99596366055,600, -000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e29460000000000000000000000000000000019f60f2cf585bdbc36947f760a15fa16c54cf46435cc5707def410202a3f4fa61b577ab2481e058b0345982d3e3d1666000000000000000000000000000000000a70b7bbc55e1f3e11e9eb7efd79d4e396742de48d911ddff8dd0a7cf10422423d5e68021948e1448e92c2e07c194776,000000000000000000000000000000000a87e7e115ccdf3c2c1a2716491d449c3f8329e73d264088f4af444d43cf05f8be0410da273ce7eeb32969830195b7e70000000000000000000000000000000010a973d6e4bd85105bf311eb0dcfdc0a5d38dba1c099206b60f2e2df4791fd58846bf19d83769506e1561212920b4895,600, -000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d318400000000000000000000000000000000109bd6e0636a7f96ffe2ce8e109171efaacfcd60189c7050259ddedd15dd257e11f2585bbd84e4a3f4d8fc5fbc0289cf0000000000000000000000000000000019b420d778da53aed81b48f2c9b9eb399e771edd5e124a41577452b409ca2503e2798cd25d791f489352fc7b7268ae23,00000000000000000000000000000000162bd29f2de10002c1c446bd9583e89751fb91703ad564e7951d41673e28d214729aa9b4b9875c397989df197c912d5f0000000000000000000000000000000004d393181871c93714afab6c33c16f68ec391fbfcad606ac65cc1d070949c099e21f710e2fe0dd4e4f50f99ea2167a7e,600, -000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae920000000000000000000000000000000012bb529b45ad7875784b62a7281d025002f15e7f86cc33555e7472df60da2cb15d37c8bf628142818c0711ee9047fb4d000000000000000000000000000000000baa801623312d95e2b51ce86373fea516007e468f265d974c2327c1779830db180bed6dbe8a64f0959aad26eaafb8d9,0000000000000000000000000000000010c4b328d264893099d89ba81b0765d0642bf36b0ac043be090c7b4f7987d21a906228c3c208c4ec5123d577efb0771f0000000000000000000000000000000016d08ce3bf755da7d4bae5f4b06b37845c17a717329c547e941be93325a04e9a5095d3f6e6c6f9ec3b1a740f59d88919,600, -0000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b1700000000000000000000000000000000002c9e50f37ff0db2676637be8a6275fce7948ae700df1e9e6a0861a8af942b6032cca2c3be8b8d95d4b4b36171b4b0d400000000000000000000000000000000050f1a9b2416bbda35bac9c8fdd4a91c12e7ee8e035973f79bd35e418fd88fa603761e2b36736c13f1d7a582984bd15e,000000000000000000000000000000000f798f8d5c21cbce7e9cfcbb708c3800bf5c22773ec5b44590cdbb6f720ccddf05a9f5d5e6a51f704f7c295c291df29f000000000000000000000000000000001483903fde5a968dba6924dfac3933cd39f757e2f89120f4ca9d03aaaf9e18252bdb5c5d3939471666b8a42aeb31b4ed,600, -00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ec000000000000000000000000000000000332cdc97c1611c043dac5fd0014cfeaee4879fee3f1ad36cddf43d76162108e2dc71f181407171da0ceec4165bcd9760000000000000000000000000000000015b96a13732a726bad5860446a8f7e3f40458e865229bd924181aa671d16b2df2171669a3faa3977f0ee27920a2c5270,0000000000000000000000000000000001c762175f885a8d7cb0be11866bd370c97fb50d4277ab15b5531dacd08da0145e037d82be3a46a4ee4116305b807de6000000000000000000000000000000000bb6c4065723eaf84d432c9fde8ce05f80de7fe3baed26cf9d1662939baac9320da69c7fe956acdd085f725178fe1b97,600, -0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c50000000000000000000000000000000003ebca978ea429eedad3a2c782816929724fc7529fbf78ea5738f2ca049aab56c1773f625df2698433d55db7f5fc8ca2000000000000000000000000000000000d2477f57b21ed471a40566f99b7c2d84ce6b82eaf83a6c87a7c21f3242959c8423d4113b7fd8449277b363303bb17b0,00000000000000000000000000000000071dc0f985703bd8335093779de651b524c02faca5fc967766abd3f6f59176d2046d7a14d18c0b757b8c9802e44ebcd300000000000000000000000000000000154e5cb66be8979ee276e8e0f240557e3f7dc074c497293af589256652da21d66a6e6b00ca5bfa6f89963fbd5bc6cf48,600, -000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679000000000000000000000000000000001461afe277bf0e1754c12a8aabbe60262758941281f23496c2eeb714f8c01fd3793faf15139ae173be6c3ff5d534d2bc00000000000000000000000000000000148ad14901be55baa302fa166e5d81cc741d67a98a7052618d77294c12aea56e2d04b7e497662debc714096c433e844e,0000000000000000000000000000000012c4dd169f55dfb5634bc4866f7cbd110648b5392ace6042b5f64aba3278f24085227521b7834864f00d01ec9998dd6800000000000000000000000000000000102d7a495850195424677853da01d70caeb6c0af5270bcfffbc2d4252c0f3680518cd8d2a0a6dbbbc7b52923a5b26562,600, -000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c0000000000000000000000000000000002218b4498c91e0fe66417fe835e03c2896d858a10338e92a461c9d76bcecd66df209771ae02c7dcace119596018f83c000000000000000000000000000000001990233c0bae1c21ba9b0e18e09b03aeb3680539c2b2ef8c9a95a3e94cf6e7c344730bf7a499d0f9f1b77345926fef2d,0000000000000000000000000000000010c50bd0f5169ebd65ee1f9cd2341fa18dd5254b33d2f7da0c644327677fe99b5d655dd5bfdb705b50d4df9cfce33d1400000000000000000000000000000000088e47ffbbc80c69ec3c5f2abe644a483f62df3e7c17aa2ff025553d1aaf3c884a44506eff069f4c41d622df84bbafa1,600, -000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f7000000000000000000000000000000000160e0f540d64a3cedba9cf1e97b727be716bbfa97fbf980686c86e086833dc7a3028758be237de7be488e1c1c368fe100000000000000000000000000000000108250b265bd78f5e52f14ef11515d80af71e4d201389693a5c3ef202cf9d974628421d73666ead30481547582f7abaf,00000000000000000000000000000000168af33c85ae6e650375ed29b91218198edd9135683f6a1428211acdcbf16bdf86f0a95575e47ee0969587a10fa9f3c90000000000000000000000000000000012d9f5d692c870b3da951b6d07797c186a8ddc89b9f08a1c0b8f0f119f10ca0b155e8df5424cf48900ad3bf09ce6872a,600, -0000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d20000000000000000000000000000000002fa19b32a825608ab46b5c681c16ae23ebefd804bb06079059e3f2c7686fe1a74c9406f8581d29ff78f39221d995bfd000000000000000000000000000000000b41ea8a18c64de43301320eaf52d923a1f1d36812c92c6e8b34420eff031e05a037eed47b9fe701fd6a03eb045f2ca7,000000000000000000000000000000000b99587f721a490b503a973591b2bb76152919269d80347aeba85d2912b864a3f67b868c34aee834ecc8cd82ac1373db0000000000000000000000000000000007767bb0ca3047eee40b83bf14d444e63d98e9fc6c4121bdf04ea7148bcfaf3819b70dcebd9a941134e5c649da8f8d80,600, -0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531f0000000000000000000000000000000002a540b681a6113a54249c0bbb47faf7c79e8da746260f71fbf83e60f18c17e5d6c8a7474badafee646fe74217a86ca4000000000000000000000000000000000fe2db7736129b35dc4958ffd0de7115359857fb9480b03a751c4fceb9ae1b2b05855398badffc517ae52c67f6394e2a,000000000000000000000000000000000bc719a8397a035fc3587d32d7ef4b4cfd63d4a5619ab78301d59659208f86df9e247e5d12650acc51a3bca3827063a900000000000000000000000000000000150d5519380a65b1909b0d84da374484675d99b00b254d03e423e634a012b286e3fe074e9b0a7bb24ff52d327249a01b,600, -00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e787900000000000000000000000000000000019d917eb431ce0c066f80742fe7b48f5e008cffa55ee5d02a2a585cc7a105a32bbf47bdff44f8a855ade38184a8279e0000000000000000000000000000000012ee762e29d91a4fc70bc7a2fb296a1dcdd05c90368286cca352b3d5fffc76e3b838e14ea005773c461075beddf414d8,0000000000000000000000000000000008197403ab10f32d873974c937ef4c27fbdb0f505c4df8ac96504705d4851cf951fb0263335e477063884527b21edf160000000000000000000000000000000005396f1affa20ca8530b519a4d5d400969f0c8c8731ecc0944e8086388e89a7ff7c16d9a2a90780972c4762b88a0f0af,600, -000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a000000000000000000000000000000000d280fe0b8297311751de20adf5e2d9e97f0c1bfe0cd430514cfddbafd5cdcb8c61bd8af4176cc3394f51f2de64b152400000000000000000000000000000000039f511e890187f28c7a0b2bd695ae665e89b0544c325a44b9109da52cc6908d81e1a27163a353ab275d683860c2e007,0000000000000000000000000000000002baea63055f72646189bdd133153dd83026f95afad5ce2cffbee3f74c8d47d5480094b2b58b0936c78aa33cd9a8f72f0000000000000000000000000000000013e600456a2d76f5a760059e0ba987b881c6bc10d6161f388d7a9d8b2031921054edfec46afbd80b1364d8e8f6a5a7a2,600, -0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f50000000000000000000000000000000015bad24d12b5d68558e961a17dbc3e1686e1b918e6192ebe6f3f71c925177e61d0162e018ac81126099effa0cadfa185000000000000000000000000000000000de73182569184b3d79dcfa8c27f46ec7a31fe8a3fd73fe26eec37a088461192bdbcf4d4b37b33b6177d6fde015d1631,000000000000000000000000000000000ced641c930387432d512861eefbf2d6131017154f99a0d3d24da880dfd2aaae91c2d9634053fab8b85fc11a7884d30600000000000000000000000000000000122071c0e87fae5031c850dccc4777c3ec9d8463bbc4ed84364d4261bc9d38f696a4320d53eea926a75ed9fcc9789a07,600, -000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b256730000000000000000000000000000000015cdf7dafedce64aba34e1f18c57b28f297629c07ee96b732029b545cf5ea6afdf926daa6a48d1250c67aa2a8b797d370000000000000000000000000000000004867352f86267dbe8e32806e4ed02f1487e036051068f8e06d02e8dea6d3773b422e065d2db27c89ea69246d0185351,000000000000000000000000000000000e2c633351d627a075acd1e373bec96ba41b047f0307201f4b7c9978c1a72243d0b18113604cc421b8f66d76ec9b1360000000000000000000000000000000000844e258d602bf9aaa35ce46c4c91c80dd9337053d8ab22c1163a0571fcd1488a2ef57476e2b66dd9c26963b28284d11,600, -000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc300000000000000000000000000000000077eb801bcde78e9dd73b58d2429a907ea0f5600a8005093d471be373bba23ea70bf828c766ccced6a46db84b440053f00000000000000000000000000000000101af9df2939089d72e42fe2dc3de3e32be8f4526a2263ebd872d0080ed4a152107bb3d2f56176bf72d5ae8bd0c30a3f,0000000000000000000000000000000010205c6be10a5fc5390b0e5ae47a8a822c8e9a7a96f113d081cde477ec0de7bf0e8385e61780b2335e4297edb35bcc6d000000000000000000000000000000001796af180463ed70cf330791c8201ee3f0fe52993f64819291bda33017285fcc3a515669b3d48a411276c849fa021f6f,600, -00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a0000000000000000000000000000000019b09bb7dddd11c5d0e304dac120b920601dd3a3505e478c88850cc701c17eb02aa7bfb20e4017a62fc4fb544d4f9e8f00000000000000000000000000000000048ad536cf89576d4cce83ef065bc16c47f1a28ae27bd71d30d8f2177a9c6f8b2ed0cdf872ead71bc5a1252bccb4a7e0,000000000000000000000000000000000fb047098a1996a625cd19021f81ea79895e038756878d8772aaee9b6bbb66930e474dcc04579ad58f4877b742a890900000000000000000000000000000000017da74a4caefc55794a36eda7938371f42265cc1f2d87d41883152db82873daeb59642e8e663afddd4f24536a1f52b3f,600, -000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa30000000000000000000000000000000005f84f9afa2a4a80ea1be03770cb26ac94bec65cf9cb3412a07683df41bb267c2b561b744b34779635218527484633e30000000000000000000000000000000013ce1d1764961d1b0dff236c1f64eabec2ce5a8526edf6b0bccb9ea412e5a91880db24510435cf297fcc1b774b318b65,000000000000000000000000000000000f4ca788dc52b7c8c0cb3419ab62c26db9fb434321fc6830837333c2bb53b9f31138eecccc3c33461297f99a810e24ad0000000000000000000000000000000006785d4f9cdf42264c00fdc4452883b9050eb56e2f6e46c7b8fc8d937dfe4d3ad5072d969a47c4811b36d3887256d0b9,600, -0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c8000000000000000000000000000000000f0dd7a15dfc39dc2df47cf09761498b0b363157d8443356e768567f5a6d5913c2a67f12d93df2dcf50756bb686836b100000000000000000000000000000000055914dbda5b115222e738d94fbd430440c99bcc6d2c6cf7225c77756ffadf765b2d83447d395e876b5f6134563ed914,000000000000000000000000000000000ac0f0f62202d09cede55ca77b7344b46fd831b41015eb357cac07f0fa49c2564c2e9d5c591630226677446a9100757c000000000000000000000000000000000ca21d0128ef933fc1a48c1b4967f56912513e63a416d86ad40c0a4590b2edf88e4e8a286338b8b176d8b341ea480277,600, -000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb211000000000000000000000000000000000a6ff5f01a97c0f3c89ac0a460861dc9040f00693bfae22d81ea9a46b6c570436f0688ed0deef5cdcc5e2142f195b5c000000000000000000000000000000000193a17880edffe5b2ebedf0dc25e479cac3b136db9b6b24009ea0a9ca526d6dd9714d10d64c999d4334baa081b9f2fbe,000000000000000000000000000000000b728d4ae4b45fae9a9e242524e95e44f175356726da50f46236f690eec17fdd5edce5df1253383378dc8f9c1fee98ae00000000000000000000000000000000131d28a5eab968c45ddc86b82f220dcdeab7c009c7c61986ee4e55045c024e1bcbe76a4e35000b5699ccec5858ba427e,600, -000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a000000000000000000000000000000000b35fcf625cde78fba1b70904acb97d7eb449d968e8013855d44292e9c3b0df3cfbcace6f292ec3c7717e25490bb4c67000000000000000000000000000000000af57abd87df55034c32dbe68bd1c0b47139fc2c3a8887b7c151e57b57c9002070337c8dcb2ce2687f9f007d48dd68c1,00000000000000000000000000000000178a19966b5b0fa70c138be7f5ea51d5399c7b8dcc5171cbef82ecb1451aeccbd1ed29170a27f404ebf6daa2ec99bd69000000000000000000000000000000000b1b748494806175030f6b5e2977c58982bd6ec6662d69237f0521351653c772a40035f2504ac8949fb448a901379fd6,600, -0000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b00000000000000000000000000000000177a51fcc81580ccb7a8873fa93eaf860ca8fedde13cdf3eb53f11e66a1c1e934b82ee9251f711c5c479f33a22770c47000000000000000000000000000000000a0edc9a58f4bb414aa0aeec7bfa6076fb62bdbaee987192c18855adf4e813e7103b943e1dddc24754acfa90600a5750,0000000000000000000000000000000019195049a2d457709e284c84c72a211224efc4d7d46d25c9a537eea94149b06506df02a2a4e0a6428263e9605eaaacb500000000000000000000000000000000061139f9a70ce7cd87ed3a701163bde247382295f557b47a3a0a880d2780f015e8ac753eb3243f9ad138f92c3a2257c5,600, -0000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d000000000000000000000000000000001552982822e0b64a6204b27da0e192873bb5bd2997784ff0b6ed53801b402501a665c17f0a379fd946ab1adfae43c6af000000000000000000000000000000000938359655fe135dd2a390f83e27273feb68387ba94f2b6f7c15389f8272d64231ebe9c8271de90ff2358d935359ba85,00000000000000000000000000000000168f958a40e85341d90012e134976d1a5839e807948410cc0c81a50961552c052bb784c50da4c734f6aa583777c22b28000000000000000000000000000000000d26998bac6ec11bc5fcf6fe7262c984d6500cd5b21af979048b940e20054f8d759f8a011f3e09d01d10f9cf8ab150e1,600, -00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d50000000000000000000000000000000000d94885dcc21b0b98821b6861a4d094e9eb5d5adcf7ca4275c5b759abbf9a9910f3b38073183d54a0569ecbbc1e9826400000000000000000000000000000000034a54b4bbb3f128608a866f5f5c554cf6ad7899f6650ca663a5bd5f1a3e4471e35a2440644c0e4e0a56080936b46d12,000000000000000000000000000000000d4734ab1bbcf9e30cf142a7aa9e8cde1b3c88d92397b8d7d48c7a7402561feee58a810abf67776e1890489efe7f8ec20000000000000000000000000000000005be9e4af0c0c183c43601339f162345f7c013f5941167cd925057e91c4641e19091a20123a36f2e803142833c0bc1ef,600, -00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000014f16cbb17e7f63284d8a75968a4c8fc8ee7f37233ed656d696477c507c23e7c7eaf54001f44c93deb14c298aa6f94c00000000000000000000000000000000169bde83e861889c50b2138c76531a5866235d515a6fee4da7aaf8e8b903f2848a9fe7bbd55eac7f1c58ce3a88e7249d,000000000000000000000000000000001400f774b2d932c6b990da6e1b3493685e8f51d429e0c53e9af1b4a2d3876781b790bca4a1bc28ce0240ea21be24a2350000000000000000000000000000000004993fcf5723b7e02095d4ba73ff3194bbe36027bc9099b57084c91c7e7d50b76331bfb06d3c678d3e401bc3f7fcc577,600, -000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000009acc4b4678b4b645fde47d1b75a5dda8caf6696ad2bf312dd5c12d7f3ab50b95152f5fe59842650c8a1a785f345c3ab000000000000000000000000000000000b672989004fe54f4d645e40cd29a21418151134fd2b90a68185040ceff141ced7f7ece1fdd9137c32589fa04b105a0e,000000000000000000000000000000000fcb0ab180a69b0a230d9dba98099fdce4969f82fc7e7ad93352a7c8dd448bb0ba9c7d62f53d5dc80506bc36190d9bc700000000000000000000000000000000047b7306f4a53c21d42993c50f2365486d02dac495f2dee4f8971a4af308396fce6c90f3cfde857bf7a2c6bf5d0d8aa7,600, -0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f6000000000000000000000000000000000198e12ade128447a240e03e024183c401d605cab1ed81f0f5bb7bc4c7cc9c889a2a01f59c0e37a0767a927719e5a95d000000000000000000000000000000001946e39fee9b76ce552108b339b9b24d11e43d3275ac19d2d4bc745c409bdc3f7c473a60c4d3a4d2cc3b598ae0d66880,00000000000000000000000000000000050b45f896fa40099cda8b1f20ab88644915c16f926589cd709e00149b12922347fa7122175424cd44e8875f217b9ad7000000000000000000000000000000001122b7e9b1509efe5616368b14085bdd36fb7adb85cd5a7f23e327548986f5298c045a602b6ee1265d53a4432a4a3c0e,600, -00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac47388620000000000000000000000000000000009c48aa2681b3005b24075bb3a122ac100cbaca872f761f4398edaba9dd9da6d04d4a4925028297dfe5f77c2b0b5c821000000000000000000000000000000000ea95c646fb68aa458e69c267a6ca640a6a24d40bdca0161246e4521d13c46facfc1ac86dfc0a804cfa6665cebeec822,0000000000000000000000000000000005325a499aec678ada9eb673d366fe0475e885d5188e2fb687a96949e8f782852fba962197976b868ec083c512bfb66b000000000000000000000000000000000c4d6fcacc8d82401882bee355b37930d83e3cea2e4a7bc133e65a3e0af919b25fc3f30c333873da9406845ce42dbb87,600, -000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae80000000000000000000000000000000008e8799a6cc0339e94e861692c81eee53e8a7b326523d5344b416bfbce04290585ef56018834cfd93d234bfa2943369f000000000000000000000000000000000fa1b01aab0878adad693ec769fb68640931c355b3802c51d4a3772300be5b16ceecdc8328a229b3b9f3639170db96f8,000000000000000000000000000000000685ec14da61c48bcb697966aca9e27601db43f0fb1f32e026fb33738eecfbb7012aa1ca3acf36a21fa846730245add70000000000000000000000000000000003fc52a1c3342b12271bbc178545bb20e96e8f1fde673e51f3d27ab5cb42e60aca49c6077e0f687be59b2d25cda9718e,600, -0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000bb3a76287fb98fe668cb0a5de603c768340ee6b7f9f686a22da3a86926d8734d2c565c41f94f08fa3ef0e665f4ccb520000000000000000000000000000000016c02dbfb307c96d5b9c144672fe62f3e9cd78991844f246945ee484cbdef2a4c1b001a017cafb3acc57b35f7c08dc44,00000000000000000000000000000000021796fd6ef624eed7049b8a5c50415cc86104b2367f2966eb3a9f5b7c4833b9470ef558457426f87756d526d94d8dfe000000000000000000000000000000000f492dca3f0a89102b503d7a7d5b197946348e195954d23b8ab9ab7704b3bccecaa2123b8386662f95cd4cfdbbb7a64d,600, -0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f00000000000000000000000000000000127420ff97df415e336cf3e24c39c161fad630c45c7ccef80f1831c4f5ed54da12f2c49a161e72bc70285fa0498e46d00000000000000000000000000000000013e605c21014f72364f8bff392ce64a10078ea537237fa282d5dd252ba1677b84b8c15d7925e54a4ab36f1feb13d3064,000000000000000000000000000000000ae916770455b0a63717e81802f5a7fcfbcc3e260b7adeca02a61a520c338d495eea29c4f070fd6efc1b8d23eb285e4c00000000000000000000000000000000134784e092744df573ba78f7d6f3cf1ed19491a0fc7ddfa02d3ca043bcf102fd40c33ac44b03a947308e3cc7af41c2df,600, -000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab40000000000000000000000000000000016f41e8b098839944adc12481e5f965657a4faedd4f4cdea51a9597a6a0356989e791a686d3d2ee6232ab93683259c6b000000000000000000000000000000000d27b4a56b2cc2216e61eb41061f9a586a704652704906f7fe0eab869ba00d34205ea66f7a02d337d08b916598494e52,0000000000000000000000000000000012842c9d7f4309f6e40124a071d317f5597de419db0d5a8e5324a517f7b61dfdeea2fb4503ad7cdd8deb8aaa5c412554000000000000000000000000000000000ace4d9f98ee6e8a4416ef14d64f26dc49e102e69eced46ef829a352e58e8c1a7e1f083e3f4fc07f24ccd1685dedf215,600, -0000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000019e7c8d182e3b674dfa21539613f7de5d4872d4f4732307a5c6d95ada7e81a01bc25bda34e0b46634e0b0b32cd47e8ec0000000000000000000000000000000008149237de73ab46d5c20dfd85b07f593c0caf2e2e364335450e3ebb478a9f6b9ac0af89174dffd92eda2783a5271f01,000000000000000000000000000000000875289fdaead079a283aafe4de7035c88662642b6bba389b17583f8e3b5801dada6e46bd897af961997665e6ed4a55700000000000000000000000000000000050a6b9c1db35865df0a042d27a042ff4b8d3bec2fba6a3a28a71c5a574620dc05cda0e70932ce9b8966e4592220c147,600, -000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a6000000000000000000000000000000000c0f33f2d76366af661d6fa58a8b5aab207d35ce03899e495f7ddccedf201d9816f270468b207413a2ca70380c798fc60000000000000000000000000000000002a7dc7e2b163e65cadf93b5d682982288c8f36d08b1db8e0b1cb40cd3c7231f3f1672da42b4679f35db2076a8de5b42,0000000000000000000000000000000019ea92820dcd442358db359146797aa82beff6154946b1ea14dccae05e8252b776b817dc044a20764e3514cd22799c0b000000000000000000000000000000000ed929fef2cb11e8b6b9b5d52bfde82080eda747f0c82f33b9cb87019476f0c128e6b918a4486172dee2884ba538ae5d,600, -000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000118fb45274a6b0ca9fe2654821e3b30caa46444f7c64b1921cf16dfd56a43916947d4fb6968d718a59a30ed38d65ce3000000000000000000000000000000000110e8e73e640bbea6927cd770baaf887c8e0e0c58260bca489c39b6dd7a24ab8c0c0a2495133d8ff8c7afb9790b37faa,0000000000000000000000000000000009452bd0a167683e30c673ffd4e750c66a81edf309a8d2d6dd915c358b30b0ffc001c4165b1b17bf157a0f966bfd91d00000000000000000000000000000000015df0b1ee359dd3e35a7b2c33edbb8e92b18804ae3359a369c6a529f5561298e6be9a3498c9477f33353124af7e91968,600, -0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000005dcb54cdf9635db275540c16307fc9f07b4ca5cd91e3977e4b95b58e8103e40ed9fa74752b2a43d95b6acb6f5fcbf440000000000000000000000000000000007ef8457752a47864ef2698176a53990e4822421ecf83b2716251e3ce69151ab2767d4a6611a0a6e0e40a57164ffb94e,0000000000000000000000000000000011f1ac702a06699dd64b63ebdd8b5381578f63b603c63c3a47413fe764af239ab7024712320f3ea3daefa6bd3cd3dfe9000000000000000000000000000000000918bb83a22b4fc66247e007c17155c4c2ec6326131c10fe04a5f9b82ddeca3d21c7c397a70a3949fda4d766540c85ff,600, -0000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c35850000000000000000000000000000000006d3335e092616363e94436bb68be89667c706564ba687f4a3494fcf7da62fd9ad8ae68cb76524926c261983711a14ad000000000000000000000000000000000f085a3d013592c402a380e2e8d9019864a775e7b8e8b94603c8cc1eb1def1e91075fd5675f76534397e2a7d76c2331e,000000000000000000000000000000000344951ccb5e60d1838f7793fcf8b765f5f252b69e1cfdb4bd3c20692c8ffa01afbda6950974a65f6ac74afb9da5942e0000000000000000000000000000000014f5f0e6b99a04d1c5c2adf96c53dd41f8c01aab8db4f0e6d7fc5eab27f6c03c429632db4e1c21467c09d8a54066a4d3,600, -000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b0000000000000000000000000000000019e2ed6e9757e2339d013078fac91c966045f7a1416a56135d75e603c2021a8bebf4acbf6c0d5ba911f66510e9a7ad1a0000000000000000000000000000000008b8585444ffb3bd4fb6ee23e8128142aa72fd574a506151a0eea8979cbd694e03897caba63771b0490d46063bc5bb57,000000000000000000000000000000000a449fb0da911c544887b24860bc5fcaaf054041cc80f16bbb44c796520bee454d0d06f84fd5aa179a44fd4fac9f144a000000000000000000000000000000000fca81401349089caaef9156a86c64271c77235c9efd136dcfad9894450b076cb3dd1a05bfa1e62ef904435eee5d2250,600, -000000000000000000000000000000000b767f399e4ebea34fd6b6b7f32a77f4a36841a12fc79e68910a963175d28cb634eeb8dc6e0533c662223c36b728cce2000000000000000000000000000000000cb3827fd6ac2c84f24f64789adac53439b4eba89409e12fbca0917faa6b7109aa831d16ca03191a124738228095ed65000000000000000000000000000000000f4a256b4288386545957a3ba28278c0ce69a8a412febfed1f952ca13e673822bacb6b7751ea75893b680ea363aab66400000000000000000000000000000000152379d006e74798199f83b0c6c22a98440ef653d7f0a8c5e3026bcdabec8be59a3cc291ba05860bd0639c5c5f5bee26,000000000000000000000000000000000c427721953e139d4f12ad2a3f8f91a4caa49875a87001b619c8a6e909a7da8ddd9dd026bf56d5f85d49fd17527106a800000000000000000000000000000000018add2816914ef51a289e707ba0224fcf0b7bcfa4001487e90dbdce53f1b596e1f5872de32fcee6f63bce4484ccbef7,600, -00000000000000000000000000000000150b75e9e9c03ada40b607f3d648bd6c40269aba3a1a992986dc005c9fde80bb1605266add0819641a0ca702d67bceed00000000000000000000000000000000083b43df032654f2dce90c8049ae4872a39f9cd860f08512930f43898e0f1e5625a5620818788797f3ca68134bc27d220000000000000000000000000000000012dae9aee13ed6ad52fe664bf7d2d0a1f134f0951d0d7ce5184e223bde164f6860967f9aaaa44fa6654d77d026c52d2a000000000000000000000000000000000f71889d64ec2f7da7319994883eb8bd1c753e6cdd3495036b630c35f07118a1bc10568c411ecbdf468a9cdaa9b4811b,000000000000000000000000000000000275b8efb3a3e43e2a24d0cda238154520f0a2b265f168bfc502b9cd4a07b930756961ae7e4fe3f01a5473d36ce3356200000000000000000000000000000000113403d5a968f01ba127dd8ef6c8d7b783a10d039a6b69c617032eba7122e9297f3ce2360c829ae64fdc9794695bf173,600, -000000000000000000000000000000000cba419694214e95a3605a9b748854d16c8e6e1ee151c907487d8189acfac1361b790a5e78f43593152027295adf8df400000000000000000000000000000000110813ff6e0ddf3427e2a514d3f0bfbadcaf9dbf039e0f93fb9643d1e62bc2469fe84cd9ff0d585bdd1037255bbe54850000000000000000000000000000000004e9dd69012ab596b5d3f1f8e4593b448685fcec4ab3394008178b137b762ddf9150cbb8dbb74c8af45bd8baab9a6c4f000000000000000000000000000000001132b66a2127885774062732127951f051c9c3c9b5aba02406e3f3cd4ecfe2dbf6614ebaca3bfe9efbe4f6e5b15ba0f5,000000000000000000000000000000000594c808954bb930bd038806500c9e3fd6460a83554e945baeeec2354a3805f046c76aea62c249080f16ae8e70f8fa6b00000000000000000000000000000000046924a32fb3f2df9a52615e45eeea2fa3ac0e2ccd38458194ada6b4d993ecdc0f441e41d0ea37599254a06aef68b9ae,600, -000000000000000000000000000000000106df8eba767e90cce0eabdaacc24d8e226c6865012ef8cb1460de5a319d443fdc6b4f4e58fb668943e0528b1809da10000000000000000000000000000000019789f464c95c179af18704c0b67b881991880f75ee7b03b9feafa3eafcd0f7d30a17fdd9cf439ff7fe683adca2083b50000000000000000000000000000000017a81b957a12adf474a2913e8636f169ea9cd10be62c16b88f95f5caf661f158a032a9f7d249fdf2765caa1564bed0570000000000000000000000000000000017fbf2abc62dc2678b65d509e19c9c9c5d961c72565649a078da8dff98be6236ef314e9ff8022f639ff565353345c230,00000000000000000000000000000000002c8bc5f39b2c9fea01372429e92a9c945fad152da67174f4e478fdead734d50f6e2da867c235f1f2f11bdfee67d2a7000000000000000000000000000000000c1dd27aad9f5d48c4824da3071daedf0c7a0e2a0b0ed39c50c9d25e61334a9c96765e049542ccaa00e0eccb316eec08,600, +0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee000000000000000000000000000000000001101098f5c39893765766af4512a0c74e1bb89bc7e6fdf14e3e7337d257cc0f94658179d83320b99f31ff94cd2bac0000000000000000000000000000000003e1a9f9f44ca2cdab4f43a1a3ee3470fdf90b2fc228eb3b709fcd72f014838ac82a6d797aeefed9a0804b22ed1ce8f7,000000000000000000000000000000001466e1373ae4a7e7ba885c5f0c3ccfa48cdb50661646ac6b779952f466ac9fc92730dcaed9be831cd1f8c4fefffd5209000000000000000000000000000000000c1fb750d2285d4ca0378e1e8cdbf6044151867c34a711b73ae818aee6dbe9e886f53d7928cc6ed9c851e0422f609b11,500, +00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed2000000000000000000000000000000000441e7f7f96198e4c23bd5eb16f1a7f045dbc8c53219ab2bcea91d3a027e2dfe659feac64905f8b9add7e4bfc91bec2b0000000000000000000000000000000005fc51bb1b40c87cd4292d4b66f8ca5ce4ef9abd2b69d4464b4879064203bda7c9fc3f896a3844ebc713f7bb20951d95,0000000000000000000000000000000016b8ab56b45a9294466809b8e858c1ad15ad0d52cfcb62f8f5753dc94cee1de6efaaebce10701e3ec2ecaa9551024ea600000000000000000000000000000000124571eec37c0b1361023188d66ec17c1ec230d31b515e0e81e599ec19e40c8a7c8cdea9735bc3d8b4e37ca7e5dd71f6,500, +0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f00000000000000000000000000000000114c3f11ba0b47551fa28f09f148936d6b290dc9f2d0534a83c32b0b849ab921ce6bcaa4ff3c917707798d9c74f2084f00000000000000000000000000000000149dc028207fb04a7795d94ea65e21f9952e445000eb954531ee519efde6901675d3d2446614d243efb77a9cfe0ca3ae,0000000000000000000000000000000002ce7a08719448494857102da464bc65a47c95c77819af325055a23ac50b626df4732daf63feb9a663d71b7c9b8f2c510000000000000000000000000000000016117e87e9b55bd4bd5763d69d5240d30745e014b9aef87c498f9a9e3286ec4d5927df7cd5a2e54ac4179e78645acf27,500, +0000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e4000000000000000000000000000000000c3d564ac1fe12f18f528c3750583ab6af8973bff3eded7bb4778c32805d9b17846cc7c687af0f46bc87de7748ab72980000000000000000000000000000000002f164c131cbd5afc85692c246157d38dc4bbb2959d2edfa6daf0a8b17c7a898aad53b400e8bdc2b29bf6688ee863db7,0000000000000000000000000000000015510826f50b88fa369caf062ecdf8b03a67e660a35b219b44437a5583b5a9adf76991dce7bff9afc50257f847299504000000000000000000000000000000000a83e879895a1b47dbd6cd25ce8b719e7490cfe021614f7539e841fc2f9c09f071e386676de60b6579aa4bf6d37b13dd,500, +0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d30000000000000000000000000000000019fe3a64361fea14936ff0b3e630471494d0c0b9423e6a004184a2965221c18849b5ed0eb2708a587323d8d6c6735a90000000000000000000000000000000000340823d314703e5efeb0a65c23069199d7dfff8793aaacb98cdcd6177fc8e61ab3294c57bf13b4406266715752ef3e6,00000000000000000000000000000000010b1c96d3910f56b0bf54da5ae8c7ab674a07f8143b61fed660e7309e626dc73eaa2b11886cdb82e2b6735e7802cc860000000000000000000000000000000002dabbbedd72872c2c012e7e893d2f3df1834c43873315488d814ddd6bfcca6758a18aa6bd02a0f3aed962cb51f0a222,500, +000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f90000000000000000000000000000000001461565b03a86df363d1854b4af74879115dffabeddfa879e2c8db9aa414fb291a076c3bdf0beee82d9c094ea8dc381a000000000000000000000000000000000e19d51ab619ee2daf25ea5bfa51eb217eabcfe0b5cb0358fd2fa105fd7cb0f5203816b990df6fda4e0e8d541be9bcf6,000000000000000000000000000000000cb40d0bf86a627d3973f1e7846484ffd0bc4943b42a54ff9527c285fed3c056b947a9b6115824cabafe13cd1af8181c00000000000000000000000000000000076255fc12f1a9dbd232025815238baaa6a3977fd87594e8d1606caec0d37b916e1e43ee2d2953d75a40a7ba416df237,500, +000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1b0000000000000000000000000000000019cabba3e09ad34cc3d125e0eb41b527aa48a4562c2b7637467b2dbc71c373897d50eed1bc75b2bde8904ece5626d6e400000000000000000000000000000000056b0746f820cff527358c86479dc924a10b9f7cae24cd495625a4159c8b71a8c3ad1a15ebf22d3561cd4b74e8a6e48b,000000000000000000000000000000000e115e0b61c1f1b25cc10a7b3bd21cf696b1433a0c366c2e1bca3c26b09482c6eced8c8ecfa69ce6b9b3b4419779262e00000000000000000000000000000000077b85daf61b9f947e81633e3bc64e697bc6c1d873f2c21e5c4c3a11302d4d5ef4c3ff5519564729aaf2a50a3c9f1196,500, +0000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442a0000000000000000000000000000000011f649ee35ff8114060fc5e4df9ac828293f6212a9857ca31cb3e9ce49aa1212154a9808f1e763bc989b6d5ba7cf09390000000000000000000000000000000019af81eca7452f58c1a6e99fab50dc0d5eeebc7712153e717a14a31cffdfd0a923dbd585e652704a174905605a2e8b9d,000000000000000000000000000000000013e37a8950a659265b285c6fb56930fb77759d9d40298acac2714b97b83ec7692a7d1c4ccb83f074384db9eedd809c0000000000000000000000000000000003215d524d6419214568ba42a31502f2a58a97d0139c66908e9d71755f5a7666567aafe30ea84d89308f06768f28a648,500, +0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795000000000000000000000000000000000d713e148769fac2efd380886f8566c6d4662dd38317bb7e68744c4339efaedbab88435ce3dc289afaa7ecb37df37a5300000000000000000000000000000000129d9cd031b31c77a4e68093dcdbb585feba786207aa115d9cf120fe4f19ca31a0dca9c692bd0f53721d60a55c333129,00000000000000000000000000000000029405b9615e14bdac8b5666bbc5f3843d4bca17c97bed66d164f1b58d2a148f0f506d645d665a40e60d53fe29375ed400000000000000000000000000000000162761f1712814e474beb2289cc50519253d680699b530c2a6477f727ccc75a19681b82e490f441f91a3c611eeb0e9e2,500, +0000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a1900000000000000000000000000000000006d92bcb599edca426ff4ceeb154ebf133c2dea210c7db0441f74bd37c8d239149c8b5056ace0bfefb1db04b42664f530000000000000000000000000000000008522fc155eef6d5746283808091f91b427f2a96ac248850f9e3d7aadd14848101c965663fd4a63aea1153d71918435a,000000000000000000000000000000000cfaa8df9437c0b6f344a0c8dcbc7529a07aec0d7632ace89af6796b6b960b014f78dd10e987a993fb8a95cc909822ec0000000000000000000000000000000007475f115f6eb35f78ba9a2b71a44ccb6bbc1e980b8cd369c5c469565f3fb798bc907353cf47f524ba715deaedf379cb,500, +0000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac9430000000000000000000000000000000016380d03b7c5cc3301ffcb2cf7c28c9bde54fc22ba2b36ec293739d8eb674678c8e6461e34c1704747817c8f8341499a000000000000000000000000000000000ec6667aa5c6a769a64c180d277a341926376c39376480dc69fcad9a8d3b540238eb39d05aaa8e3ca15fc2c3ab696047,0000000000000000000000000000000011541d798b4b5069e2541fa5410dad03fd02784332e72658c7b0fa96c586142a967addc11a7a82bfcee33bd5d07066b900000000000000000000000000000000195b3fcb94ab7beb908208283b4e5d19c0af90fca4c76268f3c703859dea7d038aca976927f48839ebc7310869c724aa,500, +000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e00000000000000000000000000000000065eb0770ab40199658bf87db6c6b52cd8c6c843a3e40dd60433d4d79971ff31296c9e00a5d553df7c81ade533379f4b0000000000000000000000000000000017a6f6137ddd90c15cf5e415f040260e15287d8d2254c6bfee88938caec9e5a048ff34f10607d1345ba1f09f30441ef4,0000000000000000000000000000000006b0853b3d41fc2d7b27da0bb2d6eb76be32530b59f8f537d227a6eb78364c7c0760447494a8bba69ef4b256dbef750200000000000000000000000000000000166e55ba2d20d94da474d4a085c14245147705e252e2a76ae696c7e37d75cde6a77fea738cef045182d5e628924dc0bb,500, +0000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276b0000000000000000000000000000000006a3f7eb0e42567210cc1ba5e6f8c42d02f1eef325b6483fef49ba186f59ab69ca2284715b736086d2a0a1f0ea224b40000000000000000000000000000000000bc08427fda31a6cfbe657a8c71c73894a33700e93e411d42f1471160c403b939b535070b68d60a4dc50e47493da63dc,000000000000000000000000000000000c35d4cd5d43e9cf52c15d46fef521666a1e1ab9f0b4a77b8e78882e9fab40f3f988597f202c5bd176c011a56a1887d4000000000000000000000000000000000ae2b5c24928a00c02daddf03fade45344f250dcf4c12eda06c39645b4d56147cb239d95b06fd719d4dc20fe332a6fce,500, +00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a578768100000000000000000000000000000000068e79aea45b7199ec4b6f26e01e88ec76533743639ce76df66937fff9e7de3edf6700d227f10f43e073afcc63e2eddc00000000000000000000000000000000039c0b6d9e9681401aeb57a94cedc0709a0eff423ace9253eb00ae75e21cabeb626b52ef4368e6a4592aed9689c6fca4,0000000000000000000000000000000013bad27dafa20f03863454c30bd5ae6b202c9c7310875da302d4693fc1c2b78cca502b1ff851b183c4b2564c5d3eb4dc0000000000000000000000000000000000552b322b3d672704382b5d8b214c225b4f7868f9c5ae0766b7cdb181f97ed90a4892235915ffbc0daf3e14ec98a606,500, +0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f7680000000000000000000000000000000000adac9bb98bb6f35a8f941dbff39dfd307b6a4d5756ccae103c814564e3d3993a8866ff91581ccdd7686c1dce0b19f700000000000000000000000000000000083d235e0579032ca47f65b6ae007ce8ffd2f1a890ce3bc45ebd0df6673ad530d2f42125d543cb0c51ba0c28345729d8,000000000000000000000000000000000b5513e42f5217490f395a8cb3673a4fc35142575f770af75ecf7a4fcd97eee215c4298fc4feab51915137cbdb814839000000000000000000000000000000000e9d4db04b233b0b12a7ff620faefef906aeb2b15481ce1609dad50eb6a7d0c09a850375599c501296219fb7b288e305,500, +00000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f42000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931d000000000000000000000000000000000d5bb4fa8b494c0adf4b695477d4a05f0ce48f7f971ef53952f685e9fb69dc8db1603e4a58292ddab7129bb5911d6cea0000000000000000000000000000000004a568c556641f0e0a2f44124b77ba70e4e560d7e030f1a21eff41eeec0d3c437b43488c535cdabf19a70acc777bacca,000000000000000000000000000000000c27ef4ebf37fd629370508f4cd062b74faa355b305d2ee60c7f4d67dd741363f18a7bbd368cdb17e848f372a5e33a6f0000000000000000000000000000000000ed833df28988944115502f554636e0b436cccf845341e21191e82d5b662482f32c24df492da4c605a0f9e0f8b00604,500, +000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80000000000000000000000000000000000874389c02d4cf1c61bc54c4c24def11dfbe7880bc998a95e70063009451ee8226fec4b278aade3a7cea55659459f1d500000000000000000000000000000000091ee883cb9ea2c933f6645f0f4c535a826d95b6da6847b4fe2349342bd4bd496e0dd546df7a7a17a4b9fb8349e5064f000000000000000000000000000000000902d7e72242a5e6b068ca82d0cb71dc0f51335dbd302941045319f9a06777518b56a6e0b0b0c9fd8f1edf6b114ad331,00000000000000000000000000000000122cce99f623944dfebffcdf6b0a0a3696162f35053e5952dddc2537421c60da9fe931579d1c4fc2e31082b6c25f96b500000000000000000000000000000000011366ffa91dc0b7da8b7c1839ea84d49299310f5c1ca244012eed0dd363dbcf4ad5813b8e3fb49361ef05ea8cb18ffe,500, +00000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a67000000000000000000000000000000000013a80ede40df002b72f6b33b1f0e3862d505efbe0721dce495d18920d542c98cdd2daf5164dbd1a2fee917ba943debe0000000000000000000000000000000000d3d4f11bc79b8425b77d25698b7e151d360ebb22c3a6afdb227de72fe432dcd6f0276b4fd3f1fcc2da5b59865053930000000000000000000000000000000015ac432071dc23148765f198ed7ea2234662745a96032c215cd9d7cf0ad8dafb8d52f209983fe98aaa2243ecc2073f1b,000000000000000000000000000000000113ccf11264ff04448f8c58b279a6a49acb386750c2051eab2c90fa8b8e03d7c5b9e87eccf36b4b3f79446b80be7b1d0000000000000000000000000000000004358a1fabfe803f4c787a671196b593981a837ee78587225fb21d5a883b98a15b912862763b94d18b971cb7e37dbcf0,500, +0000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e880000000000000000000000000000000008d7489c2d78f17b2b9b1d535f21588d8761b8fb323b08fa9af8a60f39b26e98af76aa883522f21e083c8a14c2e7edb600000000000000000000000000000000034f725766897ed76394145da2f02c92c66794a51fd5ae07bd7cc60c013d7a48ebf1b07faf669dfed74d82d07e48d1150000000000000000000000000000000018f4926a3d0f740988da25379199ecb849250239ad7efcfef7ffaa43bc1373166c0448cc30dcdbd75ceb71f76f883ea7,00000000000000000000000000000000167336aeeb9e447348156936849d518faee314c291c84d732fa3c1bd3951559230d94230e37a08e28e689e9d1fef05770000000000000000000000000000000005366535f7a68996e066ab80c55bb372a15fb0ed6634585b88fe7cafbf818fbfebbf6f6ddd9ca0ff72137594a1e84b35,500, +0000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000aba6a0b58b49f7c6c2802afd2a5ed1320bf062c7b93135f3c0ed7a1d7b1ee27b2b986cde732a60fa585ca6ab7cc154b00000000000000000000000000000000079e5a154cf84190b6c735bc8cd968559182166568649b813732e4fb4c5c428c8b38e8265d4ef04990c49aa1381f51c8000000000000000000000000000000000ae08e682ef92b4986a5ac5d4f094ad0919c826a97efe8d8120a96877766eae5828803804a0cae67df9822fd18622aae,000000000000000000000000000000000a3d66cf87b1ce8c5683d71a6de4bf829d094041240f56d9071aa84ff189a06940e8e1935127e23a970c78ca73c28bf6000000000000000000000000000000000b2adda87740873c0c59e3ebde44d33834773f0fe69e2f5e7ede99c4f928978a5caaede7262e45fd22136a394b3f7858,500, +0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b0900000000000000000000000000000000082543b58a13354d0cce5dc3fb1d91d1de6d5927290b2ff51e4e48f40cdf2d490730843b53a92865140153888d73d4af0000000000000000000000000000000008cefd0fd289d6964a962051c2c2ad98dab178612663548370dd5f007c5264fece368468d3ca8318a381b443c68c4cc7000000000000000000000000000000000708d118d44c1cb5609667fd51df9e58cacce8b65565ef20ad1649a3e1b9453e4fb37af67c95387de008d4c2114e5b95,0000000000000000000000000000000004b2311897264fe08972d62872d3679225d9880a16f2f3d7dd59412226e5e3f4f2aa8a69d283a2dc5b93e022293f0ee1000000000000000000000000000000000f03e18cef3f9a86e6b842272f2c7ee48d0ad23bfc7f1d5a9a796d88e5d5ac31326db5fe90de8f0690c70ae6e0155039,500, +00000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000097ec91c728ae2d290489909bbee1a30048a7fa90bcfd96fe1d9297545867cbfee0939f20f1791329460a4fe1ac719290000000000000000000000000000000008e5afc16d909eb9d8bdaaf229ad291f34f7baf5247bbd4cc938278f1349adb4b0f0aacd14799c01d0ca2ed38c937d600000000000000000000000000000000006cf972c64e20403c82fee901c90eaa5547460d57cce2565fd091ff9bc55e24584595c9182298f148882d6949c36c9d5,000000000000000000000000000000000caf46f480ae2ea8e700f7913c505d5150c4629c9137e917357d2a4ba8a7a1c63b8f6e2978293755952fbed7f0ad8d6d0000000000000000000000000000000002e62e715b72eebbc7c366a2390318f73e69203a9533e72340aab568f65105129ffc9889a8bc00a692494d93688c7ec0,500, +000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed302500000000000000000000000000000000130f139ca118869de846d1d938521647b7d27a95b127bbc53578c7b66d88d541adb525e7028a147bf332607bd760deac0000000000000000000000000000000013a6439e0ec0fabe93f6c772e102b96b1f692971d7181c386f7f8a360daca6e5f99772e1a736f1e72a17148d90b08efe0000000000000000000000000000000010f27477f3171dcf74498e940fc324596ef5ec6792be590028c2963385d84ef8c4bbb12c6eb3f06b1afb6809a2cb0358,000000000000000000000000000000000dea57d1fc19f994e6bdda9478a400b0ada23aed167bfe7a16ef79b6aa020403a04d554303c0b2a9c5a38f85cf6f3800000000000000000000000000000000000b8d76ccd41ba81a835775185bbf1d6bf94b031d94d5c78b3b97beb24cf246b0c25c4c309e2c06ae9896ed800169eeee,500, +0000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e70580000000000000000000000000000000010e4280374c532ed0df44ac0bac82572f839afcfb8b696eea617d5bd1261288dfa90a7190200687d470992fb4827ff320000000000000000000000000000000005728a219d128bc0a1f851f228e2bf604a72400c393cfb0d3484456b6b28a2c5061198656f0e106bbe257d849be159040000000000000000000000000000000011f6d08baa91fb2c8b36191d5b2318e355f8964cc8112838394ba1ded84b075de58d90452601dcfc9aa8a275cfec695d,0000000000000000000000000000000012e6d6c518c15cfd3020181ff3f829e29140b3b507b99251cc7f31795128adec817750296bce413bac18b9a80f69ca5000000000000000000000000000000000131ee9b748f6f1eb790adeb9edd0e79d89a9908368f5a6bb82ee0c913061cdfffe75d9ba411a49aa3f9194ee6d4d08a9,500, +0000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000ebdef273e2288c784c061bef6a45cd49b0306ac1e9faab263c6ff73dea4627189c8f10a823253d86a8752769cc4f8f200000000000000000000000000000000171696781ba195f330241584e42fb112adf9b8437b54ad17d410892b45c7d334e8734e25862604d1b679097590b8ab0a000000000000000000000000000000001879328fdf0d1fb79afd920e0b0a386828be5b8e0e6024dfeea800ffcb5c65f9044061af26d639d4dcc27bcb5ba1481a,00000000000000000000000000000000111c416d5bd018a77f3317e3fbf4b03d8e19658f2b810dc9c17863310dfb09e1c4ffdbb7c98951d357f1c3d93c5d0745000000000000000000000000000000000af0a252bff336d5eb3a406778557ef67d91776a9c788be9a76cff7727f519a70fc7809f1a50a58d29185cb9722624fd,500, +000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b10000000000000000000000000000000011efaeec0b1a4057b1e0053263afe40158790229c5bfb08062c90a252f59eca36085ab35e4cbc70483d29880c5c2f8c2000000000000000000000000000000000231b0d6189a4faad082ce4a69398c1734fcf35d222b7bce22b14571033a1066b049ae3cd3bd6c8cec5bec743955cdd600000000000000000000000000000000037375237fb71536564ea693ab316ae11722aadd7cab12b17b926c8a31bd13c4565619e8c894bffb960e632896856bbe,000000000000000000000000000000000d2b9c677417f4e9b38af6393718f55a27dbd23c730796c50472bc476ebf52172559b10f6ceb81e644ec2d0a41b3bb01000000000000000000000000000000001697f241ff6eceb05d9ada4be7d7078ecbbffa64dd4fb43ead0692eef270cb7cc31513ee4bf38a1b1154fe008a8b836a,500, +00000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e2728390000000000000000000000000000000006dc2ccb10213d3f6c3f10856888cb2bf6f1c7fcb2a17d6e63596c29281682cafd4c72696ecd6af3cce31c440144ebd10000000000000000000000000000000015653d1c5184736cdc78838be953390d12b307d268b394136b917b0462d5e31b8f1b9d96cce8f7a1203c2cae93db6a4000000000000000000000000000000000060efeece033ac711d500c1156e4b6dce3243156170c94bc948fd7beae7b28a31463a44872ca22ca49dc5d4d4dd27d1c,0000000000000000000000000000000003996050756117eeab27a5e4fa9acdde2a1161d6fbfff2601a1c7329f900e93a29f55a8073f85be8f7c2a4d0323e95cc00000000000000000000000000000000010b195a132c1cba2f1a6a73f2507baa079e9b5cb8894ea78bebc16d4151ee56fe562b16e2741f3ab1e8640cdad83180,500, +000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b0000000000000000000000000000000006d38cc6cc1a950a18e92e16287f201af4c014aba1a17929dd407d0440924ce5f08fad8fe0c50f7f733b285bf282acfc0000000000000000000000000000000018adb42928304cbc310a229306a205e7c21cdb31b9e5daf0ff6bb9437acee80cd8cf02b35dab823155d60f8a83fde5cc0000000000000000000000000000000018b57460c81cab43235be79c8c90dcda40fafcaf69e4e767133aee56308a6df07eac71275597dd8ed6607ffb9151ed9a,0000000000000000000000000000000003c7a7ee3d1b73cf1f0213404363bf3c0de4425ab97d679ed51448e877b7537400f148f14eba588ed241fea34e56d465000000000000000000000000000000000c581b5070e6bb8582b7ee2cd312dfeb5aaf0b0da95cf5a22a505ffba21fc204e26a5e17311d1f47113653ff13349f57,500, +0000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000fb24d3d4063fd054cd5b7288498f107114ff323226aca58d3336444fc79c010db15094ceda6eb99770c168d459f0da00000000000000000000000000000000001da65df8574a864ab454e5f2fa929405501bb73c3162a600979a1145586079361c89839cc0c5a07f1135c94bf059f9c0000000000000000000000000000000002560df402c0550662a2c4c463ad428ab6e60297fbc42a6484107e397ae016b58494d1c46ac4952027aa8c0896c50be3,000000000000000000000000000000000d7a539b679e5858271a6f9cf20108410eb5d5d2b1a905e09a8aa20318efbe9175450385d78389f08f836f5634f7a2f0000000000000000000000000000000000fb624e5f6c4c814b7d73eb63b70237c5de7d90d19ac81cac776d86171a8d307d3cc8c56da14f444fe8cf329ab7e63dd,500, +0000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe976000000000000000000000000000000000824e1631f054b666893784b1e7edb44b9a53596f718a6e5ba606dc1020cb6e269e9edf828de1768df0dd8ab8440e0530000000000000000000000000000000005311c11f4d0bb8542f3b60247c1441656608e5ac5c363f4d62127cecb88800a771767cf23a0e7c45f698ffa5015061f0000000000000000000000000000000018f7f1d23c8b0566a6a1fcb58d3a5c6fd422573840eb04660c3c6ba65762ed1becc756ac6300e9ce4f5bfb962e963419,0000000000000000000000000000000000849bbc7b0226b18abbcb4c9a9e78dca2f5f75a2cbb983bd95ff3a95b427b1a01fd909ce36384c49eb88ffb8ff77bb000000000000000000000000000000000087d8d28d92305b5313ca533a6b47f454ddce1c2d0fa3574b255128ef0b145fa4158beb07e4f0d50d6b7b90ea8a8ea8a,500, +00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000c2ffe6be05eccd9170b6c181966bb8c1c3ed10e763613112238cabb41370e2a5bb5fef967f4f8f2af944dbef09d265e000000000000000000000000000000000c8e293f730253128399e5c39ab18c3f040b6cd9df10d794a28d2a428a9256ea1a71cf53022bd1be11f501805e0ddda40000000000000000000000000000000003e60c2291be46900930f710969f79f27e76cf710efefc243236428db2fed93719edeeb64ada0edf6346a0411f2a4cb8,00000000000000000000000000000000191084201608f706ea1f7c51dd5b593dda87b15d2c594b52829db66ce3beab6b30899d1d285bdb9590335949ceda5f050000000000000000000000000000000000d3460622c7f1d849658a20a7ae7b05e5afae1f01e871cad52ef632cc831b0529a3066f7b81248a7728d231e51fc4ad,500, +0000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad6242000000000000000000000000000000000353d6521a17474856ad69582ce225f27d60f5a8319bea8cefded2c3f6b862d76fe633c77ed8ccdf99d2b10430253fc80000000000000000000000000000000013267db8fdf8f488a2806fead5cffdcbb7b1b4b7681a2b67d322cd7f5985c65d088c70cdc2638e679ed678cae3cc63c80000000000000000000000000000000007757233ad6d38d488c3d9d8252b41e4ab7ee54e4ef4bbf171402df57c14f9977dd3583c6c8f9b5171b368d61f082447,000000000000000000000000000000000c06fef6639ab7dceb44dc648ca6a7d614739e40e6486ee9fc01ecc55af580d98abc026c630a95878da7b6d5701d755c0000000000000000000000000000000007c9a7f2bc7fa1f65c9e3a1e463eb4e3283e47bb5490938edb12abf6c8f5a9b56d8ce7a81a60df67db8c399a9a1df1d4,500, +00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff17000000000000000000000000000000001975bc52669187f27a86096ae6bf2d60178706105d15bce8fe782759f14e449bc97cb1570e87eec5f12214a9ae0e0170000000000000000000000000000000000ca6106d6e6487a3b6f00fc2af769d21cb3b83b5dc03db19e4824fc28fd9b3d9f7a986e79f05c02b3a914ff26c7a78d6,0000000000000000000000000000000002fbf4fba68ae416b42a99f3b26916dea464d662cebce55f4545481e5ab92d3c40f3e189504b54db4c9cd51ecdd60e8d0000000000000000000000000000000008e81e094c6d4ded718ef63c5edfacb2d258f48ccfa37562950c607299bb2dca18e680a620dff8c72dedc89b4e9d4759,500, +0000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d3f00000000000000000000000000000000109f6168a719add6ea1a14f9dc95345e325d6b0e56da2f4ecff8408536446894069fa61e81bdaebfc96b13b402fad865000000000000000000000000000000001806aa27c576f4c4fa8a6db49d577cd8f257a8450e89b061cbc7773c0b5434f06bacf12b479abf6847f537c4cbefcb46,0000000000000000000000000000000014e0bd4397b90a3f96240daf835d5fb05da28a64538f4bf42d9e7925a571f831c6e663910aa37dcc265ddd7938d83045000000000000000000000000000000001695d405d4f8ba385ebf4ad25fb3f34c65977217e90d6e5ed5085b3e5b0b143194f82e6c25766d28ad6c63114ca9dcdf,500, +00000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9af0000000000000000000000000000000019d3623a7866933e2d73214ceb2e56097a1b047db5943c3ecb846890aa02250126e90fc76a729a952cef895bd154cc7d000000000000000000000000000000000e87c376bbd695a356ef72226ac7ef6a550d99e9693d8485770a686e568ae28c038ee201d3f2ea38362046236ade91cd,000000000000000000000000000000000ffeab47985bd9b3e10ce27c6636bbda336dcf540cd37eccc3faec2adff2d97dd126633bd83a7d3c8c73c3623bdf0ba2000000000000000000000000000000001992eca4b1e924b360d57ca98b543ab496a8b55bd288d23f03bcc1b22f6bc76d95b12f47c3e305812097253c73b876dd,500, +000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c300000000000000000000000000000000163aaecf83d6c77a5d7417e73f5cf9d71a6aedfd194b2f3b53c608d06a228190f4f79ac57b029d77504c72744df4ecc0000000000000000000000000000000000416e6f9ca188d16daa2c28acd6a594f8fcb990eaa26e60ca2a34dfcad7ad76c425b241acedf674d48d298d0df0f824d,000000000000000000000000000000001812bcb26fa05e0ab5176e703699ab16f5ef8917a33a9626ae6ff20f2a6f4a9d5e2afe3a11f57061cbaa992e1f30477f000000000000000000000000000000000680acf0b632cb48017cb80baa93753d030aa4b49957178d8a10d1d1a27bbdc89ac6811a91868b2c181c5c0b9b6caf86,500, +0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8db000000000000000000000000000000000aba7362eee717d03ef2d4f0fef2763822115fcc8fb9e2e8243683b6c1cde799ebc78f23812e557de2cc38e2b4a2e56700000000000000000000000000000000170833db69b3f067cf5c4c4690857e6711c9e3fcad91ca7cd045e9d2f38c7b31236960e8718f5dd4c8bfb4de76c6c9b9,00000000000000000000000000000000196ffe76a4b726fa8dd720cc1cd04c040724cb18ec10915e312eaa90d124100b08f0ce3a7fc888f46914319a3d7581f4000000000000000000000000000000000e2612357059ca6dbb64efb98ef19370560c9e83e2aad7ab2d9015e2444fe4d8c796b5577584aac9f63258beb5ae863c,500, +00000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da8257721808000000000000000000000000000000000a98ae36c690f2e3be8100f43678be5a1064390e210328dd23f61f5a496b87398db2798580edeabc6273fb9537fa12880000000000000000000000000000000009aedf77bb969592c6552ae0121a1c74de78ba222b6cd08623c7a34708a12763b5ff7969cf761ccd25adc1b65da0f02d,00000000000000000000000000000000072334ec8349fc38b99d6dea0b4259c03cd96c1438c90ef0da6321df2495892de031a53c23838ca2b260774fa09b5461000000000000000000000000000000000e4535767c2477c4f87c087540c836eeffcd0c45960841f9c3561a8a5f8e61ab98b183b11192b8e7ea1c9c7717336243,500, +000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e00000000000000000000000000000000015c3c056ec904ce865d073f8f70ef2d4b5adb5b9238deaa5e167d32f45cad4901aa6d87efa2338c633e7853ce4c19185000000000000000000000000000000000a15f1aa6e662f21d7127351a1655821c943c4cf590e3c9e60c9ab968b4a835f87fb8d87eee6331ee4e194e5f1ea91f4,000000000000000000000000000000000140fb6dcf872d0a3bff3e32a0cb4a7fb7e60ee4fb476bb120c4ce068e169d72e1c167d7fda321280d5855983d5a9af800000000000000000000000000000000108f54a4ec3ba26dd614f4d94c5c82652583906986158ad40ffea54c17703fa4b0bd7806633e1c0318d06e8dc7d41cde,500, +0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dcb000000000000000000000000000000000307841cb33e0f188103a83334a828fa864cea09c264d5f4343246f64ab244add4610c9ccd64c001816e5074fe84013f000000000000000000000000000000000e15bbeb6fff7f1435097828f5d64c448bbc800f31a5b7428436dcffd68abc92682f2b01744d7c60540e0cd1b57ab5d4,000000000000000000000000000000000a1b50660ed9120fff1e5c4abb401e4691a09f41780ca188cea4b1c2d77002f08ce28eb1caa41ee3fe73169e3651bb7f00000000000000000000000000000000125439ac3b45c698a98063ab911364bd3c6dd2a69435d00d6edf89fc5566b33038e960a125e5e52141abb605587942fe,500, +000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6300000000000000000000000000000000013866438b089d39de5a3ca2a624d72c241a54cbdcf5b2a67ebdd2db8373b112a814e74662bd52e37748ffbfc21782a5000000000000000000000000000000000d55454a22d5c2ef82611ef9cb6533e2f08668577764afc5bb9b7dfe32abd5d333147774fb1001dd24889775de57d305,000000000000000000000000000000000037b4e8846b423335711ac12f91e2419de772216509d6b9deb9c27fd1c1ee5851b3e032bf3bcac3dd8e93f3dce8a91b00000000000000000000000000000000113a1bf4be1103e858c3be282effafd5e2384f4d1073350f7073b0a415ecf9e7a3bfb55c951c0b2c25c6bab35454ecf0,500, +00000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f180320000000000000000000000000000000017440fd557df23286da15f9a96bb88cfbc79589b1c157af13baf02c65227dc0a5bdec6f2f300083ff91dae395ed8cb75000000000000000000000000000000000ad09b4290842cc599d346110fdb39ededbb1d651568579564e274465f07b8f77eeaf00fece0c10db69c2125de8ab394,0000000000000000000000000000000007c158b4e21566742f7e4e39a672bd383e27864505acef4ef8c26f8b0a9db418f9c088b555b8e9eb25acf9859b1207b40000000000000000000000000000000016e06a1ace89f992d582af0de7662ef91c0a98f574306f6f6d0d8d5e80166638d2deef70105cce2e9b20faa9d6315510,500, +000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f0000000000000000000000000000000000d7ccc3a4efdfe1a92a88e453933b8216016091f1b9d575faf18a5b3abf90daf077813167a3f4acce7359472dee544bb00000000000000000000000000000000128008c075ab176100e755cbb8de5b9ff0e9a78114f862d26ed030d9c1d1dea1c21ec8ae4d82a84d3ff5ae4c1cd6f339,000000000000000000000000000000000b84f9de79c748e37797c629cb78b86b4b736b199f161b30147b5dacf6eabe0b54afce40d5dacfe9a8ee8da5ef5b49de0000000000000000000000000000000010277ad094bb9a3b96379b1366dd90125b51a21ebeb4f776a81d9d9c1f37ab58c32a884a26fa32c83783ed0eef42b820,500, +000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5e00000000000000000000000000000000008da4a93d5ffcdaa0adc736a59f0c187ae3bf11ecb5e9e6f6aedea976a47757739042200b4c4593c2dd5db555425531000000000000000000000000000000000a6fdb2d4160c6c35223daa6fa10d0b1073de07fe4f2eba28e65ed049ff8d8852ed0538b30759fe7a0d944009ddf9a6f,000000000000000000000000000000000d740bd1effd8674250618af0358ad0b83bbc787f0264af9c2ada72fa5431be909e82155da1de0211f46fb307e9949f0000000000000000000000000000000000ddf62c91d587a14b64feef07da52c081b40fbbf9a0f2eae8b66022e0850fc94de6a467e7e4f580c7f2c806f6c6ed8cf,500, +000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d790000000000000000000000000000000003258d7931a1d72ab6344c7e96c0dbd435a7909fe68cc679c08ca9b62f7a6a04863082cbcfdbe9a736625d895e4f3bdb0000000000000000000000000000000009ee3e470e2b2cebc955ba3444b7e478f887138e36c13bd68490689122627269ea5e7ce22dd9c69792394a24187103d6,000000000000000000000000000000000af674691f5d87655f0066188fac5013f31b4169a0181d3feb7ac3beae0d9a3429d4125f099ee344f644a2de8b941f9f00000000000000000000000000000000042a9603b8e4a6c37d59ede3a1398f5f80c5298da66de575a204ee28811d9f7c7c0dd40cef3769bd72a2156b9eb620c8,500, +000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e820000000000000000000000000000000001833807f1ced52399305419450355499a63411837ee61ad681559d59561db18511eb1e8ad3161e7fe30016b560d18b8f00000000000000000000000000000000198b11b31586e17964a4a4ccdee85703163d2106481833e71f26327a589bafb43578d08d87f6cb19c7a04b4ca92392bf,000000000000000000000000000000001081c3359a0fadfe7850ce878182859e3dd77028772da7bcac9f6451ac6455739c22627889673db626bbea70aa3648d50000000000000000000000000000000000f4e8766f976fa49a0b05ef3f06f56d92fe6452ff05c3fac455f9c16efadf1b81a44d2921bed73511dda81d6fc7478e,500, +000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b811580000000000000000000000000000000007dc719ae9e3f1e11d3ed4747a546a7b973ccb1967adb1b3066645a8bde9632bcfa3530e768f088ddbc022b169e67cbf000000000000000000000000000000000bbf9cf884b19c84045da1cead7dcd9fdbf39d764ff1ad60d83ed1e4fd0ce0554f0fb618203952cf02a7c4ba466c66b8,000000000000000000000000000000000f60d66fd1ed5eb04f9619d6458c522cc49f5ace111aff2b61903b112559972f80ac615591463abf2b944c4f99d4c03e000000000000000000000000000000000001a1abfa869be2cda6bd7e05454a8735e1b638db7e1b3715708539c2d14ade53069c7e68b36d3b08cff80837028b7d,500, +0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00ccd0000000000000000000000000000000014b78c66c4acecdd913ba73cc4ab573c64b404a9494d29d4a2ba02393d9b8fdaba47bb7e76d32586df3a00e03ae2896700000000000000000000000000000000025c371cd8b72592a45dc521336a891202c5f96954812b1095ba2ea6bb11aad7b6941a44d68fe9b44e4e5fd06bd541d4,0000000000000000000000000000000015b164c854a2277658f5d08e04887d896a082c6c20895c8809ed4b349da8492d6fa0333ace6059a1f0d37e92ae9bad30000000000000000000000000000000001510d176ddba09ab60bb452188c2705ef154f449bed26abf0255897673a625637b5761355b17676748f67844a61d4e9f,500, +0000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe900000000000000000000000000000000104ee0990ba4194916f670f44e254200971b67a18ed45b25c17be49df66e4f9b934bac8c1552ecc25bdaa3af55952076000000000000000000000000000000000591094d9d89afe025ca1832d7f3e60444f83e72403a434b42216b6c4213980d29e4ef0c64ae497006de550c1faa9425,0000000000000000000000000000000006db0cc24ffec8aa11aecc43e9b76a418daac51d51f3de437090c1bcaabace19f7f8b5ceb6277d6b32b7f3b239a90c4700000000000000000000000000000000069e01f60ca7468c6b9a247c79d18cf3d88bf5d1d62c76abf9237408edeba05dea744205ac5b501920f519bb847bb711,500, +00000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193a0000000000000000000000000000000004840d028d0c0f056aeb37b7a8505325081e9822ef26046f2da72f2155c20987dd51f4b5577c5395e24288b71d2ce5140000000000000000000000000000000015f231a233e997633c1d6492e0df358fb658ae29d0f53928c8a0578484c899a699178ca3223772210063aa08991c3fff,000000000000000000000000000000000fa72bf2d7d564cc4982b9f2cdca743d2ac14f0f1be4218dbafb8b93a9277e55273487a5d2857fd3f731ac4ee469a6a1000000000000000000000000000000000fce44f886453c6ca5ebde9af41d2be92d1126e9897d72978a179dd7eebeed6242b6e9718604ab0c9369529a0426a575,500, +000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece1738220000000000000000000000000000000004877b97faa1d05d61ab65001110bf190d442cabcd6d4d1b9c1f0e513309aebd278f84a80354dfdef875769d00ec2c7500000000000000000000000000000000187066cccb5008bc2ffd0bcd1b227a5a0fe0cd4984316ba3cfd5113c4632a04c56cbda8d48993bd0dd50e9b7ce2b7ee9,0000000000000000000000000000000019ecd38afacc6b281b2515270157328e18039d51574bae0f7e0ef16c3f6da89f55ddee9e3bbb450ad51fe11edfd9f18d00000000000000000000000000000000088a5e292761bbf7a914a9f723de099035e91bd3c1fe9cd50728a4ceaa4fd3953683f30aa8e70ba0eb23919092aa9e22,500, +0000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10c000000000000000000000000000000001881f5aba0603b0a256e03e5dc507598dd63682ce80a29e0fa141b2afdadf6168e98221e4ee45d378cee0416baaadc49000000000000000000000000000000000070d255101319dd3a0f8ca3a0856188428c09de15475d6b70d70a405e45ab379a5b1f2e55f84bd7fe5dd12aeedce670,0000000000000000000000000000000011ccd455d5e3eba94567a17bcd777559b4ff1afa66fd6f05f99c69937404290a2f1c83cfd6c2c25886ebff4934332c0e0000000000000000000000000000000010920aa3d5974df25530610ef466adce3d51fd6a508d4b1111739c586dfd7ba9040836e075fd812fe111d92f25b67f51,500, +0000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119bf000000000000000000000000000000000b53e5339f25bcd31afd091362874b5042c0b762ed7425341331630addbc4dccc299936e1acdf89823c36867d46c6f28000000000000000000000000000000000fc3c6b522268511dd52826dd1aee707413d925ee51aeb0e5d69c0e3eb697fabbc14783b5007e240cc0c53c299a40ada,00000000000000000000000000000000060773b9b8f3babdba3db27089b7be3e6e287a635dbae19576039d34ae18a0e6413278bfa280570f6329ae05cdb693fd00000000000000000000000000000000075fb9527f99a8c8db41e67baaf1deafffd2c134badb1b3478a26b5501b31dca858fad6f0d52f412d5631ecfa72eece4,500, +0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b2000000000000000000000000000000001693f4ebab3fed548784264196fb01cf55311399f47cdad74a9543bda5d1ca682a00ee04bb0b3954d5a0f00ceef97a750000000000000000000000000000000017f4019c23bd68e84d889857c417b17aa96c780fec3c1ed6ca75100cc70c97a8bb8272ad4c6de896d76dc2a1b09c7a61,000000000000000000000000000000000a3ea8afdc83794f18f9a9427bcd60a355196925d38fdf74ab09d4a08279647b2da6f1fbe30948a785497d6c6dddc2a9000000000000000000000000000000001263c88f1ca3e574cafac21641432d45ee01e1b05eba95716565922abe28c7f0fb004c255afcbfa10cf7959bbe6b00d7,500, +00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0b0000000000000000000000000000000005d5602e05499a435effff3812744b582b0cd7c68f1c88faa3c268515c8b14f3c041b8ae322fe526b2406e7c25d84e61000000000000000000000000000000001038eaf49e74e19111e4456ebba01dc4d22c7e23a303d5dec821da832e90a1b07b1a6b8034137f1bfdcddeb58053a170,0000000000000000000000000000000019258ea5023ce73343dcd201ec9be68ec1ee1cb4e5b9964309d801c2bc523343c8ebc4f8393a403c7881e5928f29db14000000000000000000000000000000001423bf52daefb432162ce2bd9ef78b256ff3b24d0a84766b87119489fd56ecf6156b2884c8a7e1220e493469723cd7f8,500, +0000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751a0000000000000000000000000000000002626f28d421d9d1c28f5e1eb5a51ada9610dbdd62cd33c4078d2fdfc18dbd092e2847cf705ba5fcd8c1a60c1cc34a3b0000000000000000000000000000000001f7b8cfdb7e406c920f5fdecae45fb4be736f209480ccb455f972c6b1a1aebdd5ba116903c46ded72ce37cd8836e871,00000000000000000000000000000000081d674f5b9c7c64673c39fe33f4f3d77271e826dcb4dfd2591062e47c931237e8539ef9c886c9e112eccc50da4f63fd00000000000000000000000000000000141b700695839110ed4ced5f8a3f4fd64a8086805358ab4a5abd2705592e616cd95ff01271212ca9014dcb68d8157ba0,500, +0000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df91000000000000000000000000000000000259e307eacb1bc45a13811b02a7aeaaf4dc2bb405dcd88069bb6ec1c08a78905516169bd3440a36921764df0ef3a85b000000000000000000000000000000001263372b675124f6cc19ca16842ba069c5697dbf57730875fe72c864a81189d7d16fe126b5d24953a0524f96dbac5183,000000000000000000000000000000001908aa3a640817e31a4213156fbd4fd39ab39eb931091670a0e06399def71a689e67286f90d38ce9f97cb85f6488d9c8000000000000000000000000000000000764e46b6b82aa2f8862d28e9d543a751a9de855645377b9633cc098c2110ec6ed4fd30f0044ea5868c93f950f6cfd24,500, +000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c04000000000000000000000000000000000a138203c916cb8425663db3bbff37f239a5745be885784b8e035a4f40c47954c48873f6d5aa06d579e213282fe789fa0000000000000000000000000000000016897b8adbc3a3a0dccd809f7311ba1f84f76e218c58af243c0aa29a1bb150ed719191d1ced802d4372e717c1c97570a,0000000000000000000000000000000004ad79769fd10081ebaaed9e2131de5d8738d9ef143b6d0fa6e106bd82cfd53bbc9fab08c422aa03d03896a0fb2460d0000000000000000000000000000000000bb79356c2d477dfbcb1b0e417df7cb79affbe151c1f03fa60b1372d7d82fd53b2160afdd88be1bf0e9dc99596366055,500, +000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e29460000000000000000000000000000000019f60f2cf585bdbc36947f760a15fa16c54cf46435cc5707def410202a3f4fa61b577ab2481e058b0345982d3e3d1666000000000000000000000000000000000a70b7bbc55e1f3e11e9eb7efd79d4e396742de48d911ddff8dd0a7cf10422423d5e68021948e1448e92c2e07c194776,000000000000000000000000000000000a87e7e115ccdf3c2c1a2716491d449c3f8329e73d264088f4af444d43cf05f8be0410da273ce7eeb32969830195b7e70000000000000000000000000000000010a973d6e4bd85105bf311eb0dcfdc0a5d38dba1c099206b60f2e2df4791fd58846bf19d83769506e1561212920b4895,500, +000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d318400000000000000000000000000000000109bd6e0636a7f96ffe2ce8e109171efaacfcd60189c7050259ddedd15dd257e11f2585bbd84e4a3f4d8fc5fbc0289cf0000000000000000000000000000000019b420d778da53aed81b48f2c9b9eb399e771edd5e124a41577452b409ca2503e2798cd25d791f489352fc7b7268ae23,00000000000000000000000000000000162bd29f2de10002c1c446bd9583e89751fb91703ad564e7951d41673e28d214729aa9b4b9875c397989df197c912d5f0000000000000000000000000000000004d393181871c93714afab6c33c16f68ec391fbfcad606ac65cc1d070949c099e21f710e2fe0dd4e4f50f99ea2167a7e,500, +000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae920000000000000000000000000000000012bb529b45ad7875784b62a7281d025002f15e7f86cc33555e7472df60da2cb15d37c8bf628142818c0711ee9047fb4d000000000000000000000000000000000baa801623312d95e2b51ce86373fea516007e468f265d974c2327c1779830db180bed6dbe8a64f0959aad26eaafb8d9,0000000000000000000000000000000010c4b328d264893099d89ba81b0765d0642bf36b0ac043be090c7b4f7987d21a906228c3c208c4ec5123d577efb0771f0000000000000000000000000000000016d08ce3bf755da7d4bae5f4b06b37845c17a717329c547e941be93325a04e9a5095d3f6e6c6f9ec3b1a740f59d88919,500, +0000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b1700000000000000000000000000000000002c9e50f37ff0db2676637be8a6275fce7948ae700df1e9e6a0861a8af942b6032cca2c3be8b8d95d4b4b36171b4b0d400000000000000000000000000000000050f1a9b2416bbda35bac9c8fdd4a91c12e7ee8e035973f79bd35e418fd88fa603761e2b36736c13f1d7a582984bd15e,000000000000000000000000000000000f798f8d5c21cbce7e9cfcbb708c3800bf5c22773ec5b44590cdbb6f720ccddf05a9f5d5e6a51f704f7c295c291df29f000000000000000000000000000000001483903fde5a968dba6924dfac3933cd39f757e2f89120f4ca9d03aaaf9e18252bdb5c5d3939471666b8a42aeb31b4ed,500, +00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ec000000000000000000000000000000000332cdc97c1611c043dac5fd0014cfeaee4879fee3f1ad36cddf43d76162108e2dc71f181407171da0ceec4165bcd9760000000000000000000000000000000015b96a13732a726bad5860446a8f7e3f40458e865229bd924181aa671d16b2df2171669a3faa3977f0ee27920a2c5270,0000000000000000000000000000000001c762175f885a8d7cb0be11866bd370c97fb50d4277ab15b5531dacd08da0145e037d82be3a46a4ee4116305b807de6000000000000000000000000000000000bb6c4065723eaf84d432c9fde8ce05f80de7fe3baed26cf9d1662939baac9320da69c7fe956acdd085f725178fe1b97,500, +0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c50000000000000000000000000000000003ebca978ea429eedad3a2c782816929724fc7529fbf78ea5738f2ca049aab56c1773f625df2698433d55db7f5fc8ca2000000000000000000000000000000000d2477f57b21ed471a40566f99b7c2d84ce6b82eaf83a6c87a7c21f3242959c8423d4113b7fd8449277b363303bb17b0,00000000000000000000000000000000071dc0f985703bd8335093779de651b524c02faca5fc967766abd3f6f59176d2046d7a14d18c0b757b8c9802e44ebcd300000000000000000000000000000000154e5cb66be8979ee276e8e0f240557e3f7dc074c497293af589256652da21d66a6e6b00ca5bfa6f89963fbd5bc6cf48,500, +000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679000000000000000000000000000000001461afe277bf0e1754c12a8aabbe60262758941281f23496c2eeb714f8c01fd3793faf15139ae173be6c3ff5d534d2bc00000000000000000000000000000000148ad14901be55baa302fa166e5d81cc741d67a98a7052618d77294c12aea56e2d04b7e497662debc714096c433e844e,0000000000000000000000000000000012c4dd169f55dfb5634bc4866f7cbd110648b5392ace6042b5f64aba3278f24085227521b7834864f00d01ec9998dd6800000000000000000000000000000000102d7a495850195424677853da01d70caeb6c0af5270bcfffbc2d4252c0f3680518cd8d2a0a6dbbbc7b52923a5b26562,500, +000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c0000000000000000000000000000000002218b4498c91e0fe66417fe835e03c2896d858a10338e92a461c9d76bcecd66df209771ae02c7dcace119596018f83c000000000000000000000000000000001990233c0bae1c21ba9b0e18e09b03aeb3680539c2b2ef8c9a95a3e94cf6e7c344730bf7a499d0f9f1b77345926fef2d,0000000000000000000000000000000010c50bd0f5169ebd65ee1f9cd2341fa18dd5254b33d2f7da0c644327677fe99b5d655dd5bfdb705b50d4df9cfce33d1400000000000000000000000000000000088e47ffbbc80c69ec3c5f2abe644a483f62df3e7c17aa2ff025553d1aaf3c884a44506eff069f4c41d622df84bbafa1,500, +000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f7000000000000000000000000000000000160e0f540d64a3cedba9cf1e97b727be716bbfa97fbf980686c86e086833dc7a3028758be237de7be488e1c1c368fe100000000000000000000000000000000108250b265bd78f5e52f14ef11515d80af71e4d201389693a5c3ef202cf9d974628421d73666ead30481547582f7abaf,00000000000000000000000000000000168af33c85ae6e650375ed29b91218198edd9135683f6a1428211acdcbf16bdf86f0a95575e47ee0969587a10fa9f3c90000000000000000000000000000000012d9f5d692c870b3da951b6d07797c186a8ddc89b9f08a1c0b8f0f119f10ca0b155e8df5424cf48900ad3bf09ce6872a,500, +0000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d20000000000000000000000000000000002fa19b32a825608ab46b5c681c16ae23ebefd804bb06079059e3f2c7686fe1a74c9406f8581d29ff78f39221d995bfd000000000000000000000000000000000b41ea8a18c64de43301320eaf52d923a1f1d36812c92c6e8b34420eff031e05a037eed47b9fe701fd6a03eb045f2ca7,000000000000000000000000000000000b99587f721a490b503a973591b2bb76152919269d80347aeba85d2912b864a3f67b868c34aee834ecc8cd82ac1373db0000000000000000000000000000000007767bb0ca3047eee40b83bf14d444e63d98e9fc6c4121bdf04ea7148bcfaf3819b70dcebd9a941134e5c649da8f8d80,500, +0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531f0000000000000000000000000000000002a540b681a6113a54249c0bbb47faf7c79e8da746260f71fbf83e60f18c17e5d6c8a7474badafee646fe74217a86ca4000000000000000000000000000000000fe2db7736129b35dc4958ffd0de7115359857fb9480b03a751c4fceb9ae1b2b05855398badffc517ae52c67f6394e2a,000000000000000000000000000000000bc719a8397a035fc3587d32d7ef4b4cfd63d4a5619ab78301d59659208f86df9e247e5d12650acc51a3bca3827063a900000000000000000000000000000000150d5519380a65b1909b0d84da374484675d99b00b254d03e423e634a012b286e3fe074e9b0a7bb24ff52d327249a01b,500, +00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e787900000000000000000000000000000000019d917eb431ce0c066f80742fe7b48f5e008cffa55ee5d02a2a585cc7a105a32bbf47bdff44f8a855ade38184a8279e0000000000000000000000000000000012ee762e29d91a4fc70bc7a2fb296a1dcdd05c90368286cca352b3d5fffc76e3b838e14ea005773c461075beddf414d8,0000000000000000000000000000000008197403ab10f32d873974c937ef4c27fbdb0f505c4df8ac96504705d4851cf951fb0263335e477063884527b21edf160000000000000000000000000000000005396f1affa20ca8530b519a4d5d400969f0c8c8731ecc0944e8086388e89a7ff7c16d9a2a90780972c4762b88a0f0af,500, +000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a000000000000000000000000000000000d280fe0b8297311751de20adf5e2d9e97f0c1bfe0cd430514cfddbafd5cdcb8c61bd8af4176cc3394f51f2de64b152400000000000000000000000000000000039f511e890187f28c7a0b2bd695ae665e89b0544c325a44b9109da52cc6908d81e1a27163a353ab275d683860c2e007,0000000000000000000000000000000002baea63055f72646189bdd133153dd83026f95afad5ce2cffbee3f74c8d47d5480094b2b58b0936c78aa33cd9a8f72f0000000000000000000000000000000013e600456a2d76f5a760059e0ba987b881c6bc10d6161f388d7a9d8b2031921054edfec46afbd80b1364d8e8f6a5a7a2,500, +0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f50000000000000000000000000000000015bad24d12b5d68558e961a17dbc3e1686e1b918e6192ebe6f3f71c925177e61d0162e018ac81126099effa0cadfa185000000000000000000000000000000000de73182569184b3d79dcfa8c27f46ec7a31fe8a3fd73fe26eec37a088461192bdbcf4d4b37b33b6177d6fde015d1631,000000000000000000000000000000000ced641c930387432d512861eefbf2d6131017154f99a0d3d24da880dfd2aaae91c2d9634053fab8b85fc11a7884d30600000000000000000000000000000000122071c0e87fae5031c850dccc4777c3ec9d8463bbc4ed84364d4261bc9d38f696a4320d53eea926a75ed9fcc9789a07,500, +000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b256730000000000000000000000000000000015cdf7dafedce64aba34e1f18c57b28f297629c07ee96b732029b545cf5ea6afdf926daa6a48d1250c67aa2a8b797d370000000000000000000000000000000004867352f86267dbe8e32806e4ed02f1487e036051068f8e06d02e8dea6d3773b422e065d2db27c89ea69246d0185351,000000000000000000000000000000000e2c633351d627a075acd1e373bec96ba41b047f0307201f4b7c9978c1a72243d0b18113604cc421b8f66d76ec9b1360000000000000000000000000000000000844e258d602bf9aaa35ce46c4c91c80dd9337053d8ab22c1163a0571fcd1488a2ef57476e2b66dd9c26963b28284d11,500, +000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc300000000000000000000000000000000077eb801bcde78e9dd73b58d2429a907ea0f5600a8005093d471be373bba23ea70bf828c766ccced6a46db84b440053f00000000000000000000000000000000101af9df2939089d72e42fe2dc3de3e32be8f4526a2263ebd872d0080ed4a152107bb3d2f56176bf72d5ae8bd0c30a3f,0000000000000000000000000000000010205c6be10a5fc5390b0e5ae47a8a822c8e9a7a96f113d081cde477ec0de7bf0e8385e61780b2335e4297edb35bcc6d000000000000000000000000000000001796af180463ed70cf330791c8201ee3f0fe52993f64819291bda33017285fcc3a515669b3d48a411276c849fa021f6f,500, +00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a0000000000000000000000000000000019b09bb7dddd11c5d0e304dac120b920601dd3a3505e478c88850cc701c17eb02aa7bfb20e4017a62fc4fb544d4f9e8f00000000000000000000000000000000048ad536cf89576d4cce83ef065bc16c47f1a28ae27bd71d30d8f2177a9c6f8b2ed0cdf872ead71bc5a1252bccb4a7e0,000000000000000000000000000000000fb047098a1996a625cd19021f81ea79895e038756878d8772aaee9b6bbb66930e474dcc04579ad58f4877b742a890900000000000000000000000000000000017da74a4caefc55794a36eda7938371f42265cc1f2d87d41883152db82873daeb59642e8e663afddd4f24536a1f52b3f,500, +000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa30000000000000000000000000000000005f84f9afa2a4a80ea1be03770cb26ac94bec65cf9cb3412a07683df41bb267c2b561b744b34779635218527484633e30000000000000000000000000000000013ce1d1764961d1b0dff236c1f64eabec2ce5a8526edf6b0bccb9ea412e5a91880db24510435cf297fcc1b774b318b65,000000000000000000000000000000000f4ca788dc52b7c8c0cb3419ab62c26db9fb434321fc6830837333c2bb53b9f31138eecccc3c33461297f99a810e24ad0000000000000000000000000000000006785d4f9cdf42264c00fdc4452883b9050eb56e2f6e46c7b8fc8d937dfe4d3ad5072d969a47c4811b36d3887256d0b9,500, +0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c8000000000000000000000000000000000f0dd7a15dfc39dc2df47cf09761498b0b363157d8443356e768567f5a6d5913c2a67f12d93df2dcf50756bb686836b100000000000000000000000000000000055914dbda5b115222e738d94fbd430440c99bcc6d2c6cf7225c77756ffadf765b2d83447d395e876b5f6134563ed914,000000000000000000000000000000000ac0f0f62202d09cede55ca77b7344b46fd831b41015eb357cac07f0fa49c2564c2e9d5c591630226677446a9100757c000000000000000000000000000000000ca21d0128ef933fc1a48c1b4967f56912513e63a416d86ad40c0a4590b2edf88e4e8a286338b8b176d8b341ea480277,500, +000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb211000000000000000000000000000000000a6ff5f01a97c0f3c89ac0a460861dc9040f00693bfae22d81ea9a46b6c570436f0688ed0deef5cdcc5e2142f195b5c000000000000000000000000000000000193a17880edffe5b2ebedf0dc25e479cac3b136db9b6b24009ea0a9ca526d6dd9714d10d64c999d4334baa081b9f2fbe,000000000000000000000000000000000b728d4ae4b45fae9a9e242524e95e44f175356726da50f46236f690eec17fdd5edce5df1253383378dc8f9c1fee98ae00000000000000000000000000000000131d28a5eab968c45ddc86b82f220dcdeab7c009c7c61986ee4e55045c024e1bcbe76a4e35000b5699ccec5858ba427e,500, +000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a000000000000000000000000000000000b35fcf625cde78fba1b70904acb97d7eb449d968e8013855d44292e9c3b0df3cfbcace6f292ec3c7717e25490bb4c67000000000000000000000000000000000af57abd87df55034c32dbe68bd1c0b47139fc2c3a8887b7c151e57b57c9002070337c8dcb2ce2687f9f007d48dd68c1,00000000000000000000000000000000178a19966b5b0fa70c138be7f5ea51d5399c7b8dcc5171cbef82ecb1451aeccbd1ed29170a27f404ebf6daa2ec99bd69000000000000000000000000000000000b1b748494806175030f6b5e2977c58982bd6ec6662d69237f0521351653c772a40035f2504ac8949fb448a901379fd6,500, +0000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b00000000000000000000000000000000177a51fcc81580ccb7a8873fa93eaf860ca8fedde13cdf3eb53f11e66a1c1e934b82ee9251f711c5c479f33a22770c47000000000000000000000000000000000a0edc9a58f4bb414aa0aeec7bfa6076fb62bdbaee987192c18855adf4e813e7103b943e1dddc24754acfa90600a5750,0000000000000000000000000000000019195049a2d457709e284c84c72a211224efc4d7d46d25c9a537eea94149b06506df02a2a4e0a6428263e9605eaaacb500000000000000000000000000000000061139f9a70ce7cd87ed3a701163bde247382295f557b47a3a0a880d2780f015e8ac753eb3243f9ad138f92c3a2257c5,500, +0000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d000000000000000000000000000000001552982822e0b64a6204b27da0e192873bb5bd2997784ff0b6ed53801b402501a665c17f0a379fd946ab1adfae43c6af000000000000000000000000000000000938359655fe135dd2a390f83e27273feb68387ba94f2b6f7c15389f8272d64231ebe9c8271de90ff2358d935359ba85,00000000000000000000000000000000168f958a40e85341d90012e134976d1a5839e807948410cc0c81a50961552c052bb784c50da4c734f6aa583777c22b28000000000000000000000000000000000d26998bac6ec11bc5fcf6fe7262c984d6500cd5b21af979048b940e20054f8d759f8a011f3e09d01d10f9cf8ab150e1,500, +00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d50000000000000000000000000000000000d94885dcc21b0b98821b6861a4d094e9eb5d5adcf7ca4275c5b759abbf9a9910f3b38073183d54a0569ecbbc1e9826400000000000000000000000000000000034a54b4bbb3f128608a866f5f5c554cf6ad7899f6650ca663a5bd5f1a3e4471e35a2440644c0e4e0a56080936b46d12,000000000000000000000000000000000d4734ab1bbcf9e30cf142a7aa9e8cde1b3c88d92397b8d7d48c7a7402561feee58a810abf67776e1890489efe7f8ec20000000000000000000000000000000005be9e4af0c0c183c43601339f162345f7c013f5941167cd925057e91c4641e19091a20123a36f2e803142833c0bc1ef,500, +00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000014f16cbb17e7f63284d8a75968a4c8fc8ee7f37233ed656d696477c507c23e7c7eaf54001f44c93deb14c298aa6f94c00000000000000000000000000000000169bde83e861889c50b2138c76531a5866235d515a6fee4da7aaf8e8b903f2848a9fe7bbd55eac7f1c58ce3a88e7249d,000000000000000000000000000000001400f774b2d932c6b990da6e1b3493685e8f51d429e0c53e9af1b4a2d3876781b790bca4a1bc28ce0240ea21be24a2350000000000000000000000000000000004993fcf5723b7e02095d4ba73ff3194bbe36027bc9099b57084c91c7e7d50b76331bfb06d3c678d3e401bc3f7fcc577,500, +000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000009acc4b4678b4b645fde47d1b75a5dda8caf6696ad2bf312dd5c12d7f3ab50b95152f5fe59842650c8a1a785f345c3ab000000000000000000000000000000000b672989004fe54f4d645e40cd29a21418151134fd2b90a68185040ceff141ced7f7ece1fdd9137c32589fa04b105a0e,000000000000000000000000000000000fcb0ab180a69b0a230d9dba98099fdce4969f82fc7e7ad93352a7c8dd448bb0ba9c7d62f53d5dc80506bc36190d9bc700000000000000000000000000000000047b7306f4a53c21d42993c50f2365486d02dac495f2dee4f8971a4af308396fce6c90f3cfde857bf7a2c6bf5d0d8aa7,500, +0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f6000000000000000000000000000000000198e12ade128447a240e03e024183c401d605cab1ed81f0f5bb7bc4c7cc9c889a2a01f59c0e37a0767a927719e5a95d000000000000000000000000000000001946e39fee9b76ce552108b339b9b24d11e43d3275ac19d2d4bc745c409bdc3f7c473a60c4d3a4d2cc3b598ae0d66880,00000000000000000000000000000000050b45f896fa40099cda8b1f20ab88644915c16f926589cd709e00149b12922347fa7122175424cd44e8875f217b9ad7000000000000000000000000000000001122b7e9b1509efe5616368b14085bdd36fb7adb85cd5a7f23e327548986f5298c045a602b6ee1265d53a4432a4a3c0e,500, +00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac47388620000000000000000000000000000000009c48aa2681b3005b24075bb3a122ac100cbaca872f761f4398edaba9dd9da6d04d4a4925028297dfe5f77c2b0b5c821000000000000000000000000000000000ea95c646fb68aa458e69c267a6ca640a6a24d40bdca0161246e4521d13c46facfc1ac86dfc0a804cfa6665cebeec822,0000000000000000000000000000000005325a499aec678ada9eb673d366fe0475e885d5188e2fb687a96949e8f782852fba962197976b868ec083c512bfb66b000000000000000000000000000000000c4d6fcacc8d82401882bee355b37930d83e3cea2e4a7bc133e65a3e0af919b25fc3f30c333873da9406845ce42dbb87,500, +000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae80000000000000000000000000000000008e8799a6cc0339e94e861692c81eee53e8a7b326523d5344b416bfbce04290585ef56018834cfd93d234bfa2943369f000000000000000000000000000000000fa1b01aab0878adad693ec769fb68640931c355b3802c51d4a3772300be5b16ceecdc8328a229b3b9f3639170db96f8,000000000000000000000000000000000685ec14da61c48bcb697966aca9e27601db43f0fb1f32e026fb33738eecfbb7012aa1ca3acf36a21fa846730245add70000000000000000000000000000000003fc52a1c3342b12271bbc178545bb20e96e8f1fde673e51f3d27ab5cb42e60aca49c6077e0f687be59b2d25cda9718e,500, +0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000bb3a76287fb98fe668cb0a5de603c768340ee6b7f9f686a22da3a86926d8734d2c565c41f94f08fa3ef0e665f4ccb520000000000000000000000000000000016c02dbfb307c96d5b9c144672fe62f3e9cd78991844f246945ee484cbdef2a4c1b001a017cafb3acc57b35f7c08dc44,00000000000000000000000000000000021796fd6ef624eed7049b8a5c50415cc86104b2367f2966eb3a9f5b7c4833b9470ef558457426f87756d526d94d8dfe000000000000000000000000000000000f492dca3f0a89102b503d7a7d5b197946348e195954d23b8ab9ab7704b3bccecaa2123b8386662f95cd4cfdbbb7a64d,500, +0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f00000000000000000000000000000000127420ff97df415e336cf3e24c39c161fad630c45c7ccef80f1831c4f5ed54da12f2c49a161e72bc70285fa0498e46d00000000000000000000000000000000013e605c21014f72364f8bff392ce64a10078ea537237fa282d5dd252ba1677b84b8c15d7925e54a4ab36f1feb13d3064,000000000000000000000000000000000ae916770455b0a63717e81802f5a7fcfbcc3e260b7adeca02a61a520c338d495eea29c4f070fd6efc1b8d23eb285e4c00000000000000000000000000000000134784e092744df573ba78f7d6f3cf1ed19491a0fc7ddfa02d3ca043bcf102fd40c33ac44b03a947308e3cc7af41c2df,500, +000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab40000000000000000000000000000000016f41e8b098839944adc12481e5f965657a4faedd4f4cdea51a9597a6a0356989e791a686d3d2ee6232ab93683259c6b000000000000000000000000000000000d27b4a56b2cc2216e61eb41061f9a586a704652704906f7fe0eab869ba00d34205ea66f7a02d337d08b916598494e52,0000000000000000000000000000000012842c9d7f4309f6e40124a071d317f5597de419db0d5a8e5324a517f7b61dfdeea2fb4503ad7cdd8deb8aaa5c412554000000000000000000000000000000000ace4d9f98ee6e8a4416ef14d64f26dc49e102e69eced46ef829a352e58e8c1a7e1f083e3f4fc07f24ccd1685dedf215,500, +0000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000019e7c8d182e3b674dfa21539613f7de5d4872d4f4732307a5c6d95ada7e81a01bc25bda34e0b46634e0b0b32cd47e8ec0000000000000000000000000000000008149237de73ab46d5c20dfd85b07f593c0caf2e2e364335450e3ebb478a9f6b9ac0af89174dffd92eda2783a5271f01,000000000000000000000000000000000875289fdaead079a283aafe4de7035c88662642b6bba389b17583f8e3b5801dada6e46bd897af961997665e6ed4a55700000000000000000000000000000000050a6b9c1db35865df0a042d27a042ff4b8d3bec2fba6a3a28a71c5a574620dc05cda0e70932ce9b8966e4592220c147,500, +000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a6000000000000000000000000000000000c0f33f2d76366af661d6fa58a8b5aab207d35ce03899e495f7ddccedf201d9816f270468b207413a2ca70380c798fc60000000000000000000000000000000002a7dc7e2b163e65cadf93b5d682982288c8f36d08b1db8e0b1cb40cd3c7231f3f1672da42b4679f35db2076a8de5b42,0000000000000000000000000000000019ea92820dcd442358db359146797aa82beff6154946b1ea14dccae05e8252b776b817dc044a20764e3514cd22799c0b000000000000000000000000000000000ed929fef2cb11e8b6b9b5d52bfde82080eda747f0c82f33b9cb87019476f0c128e6b918a4486172dee2884ba538ae5d,500, +000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000118fb45274a6b0ca9fe2654821e3b30caa46444f7c64b1921cf16dfd56a43916947d4fb6968d718a59a30ed38d65ce3000000000000000000000000000000000110e8e73e640bbea6927cd770baaf887c8e0e0c58260bca489c39b6dd7a24ab8c0c0a2495133d8ff8c7afb9790b37faa,0000000000000000000000000000000009452bd0a167683e30c673ffd4e750c66a81edf309a8d2d6dd915c358b30b0ffc001c4165b1b17bf157a0f966bfd91d00000000000000000000000000000000015df0b1ee359dd3e35a7b2c33edbb8e92b18804ae3359a369c6a529f5561298e6be9a3498c9477f33353124af7e91968,500, +0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000005dcb54cdf9635db275540c16307fc9f07b4ca5cd91e3977e4b95b58e8103e40ed9fa74752b2a43d95b6acb6f5fcbf440000000000000000000000000000000007ef8457752a47864ef2698176a53990e4822421ecf83b2716251e3ce69151ab2767d4a6611a0a6e0e40a57164ffb94e,0000000000000000000000000000000011f1ac702a06699dd64b63ebdd8b5381578f63b603c63c3a47413fe764af239ab7024712320f3ea3daefa6bd3cd3dfe9000000000000000000000000000000000918bb83a22b4fc66247e007c17155c4c2ec6326131c10fe04a5f9b82ddeca3d21c7c397a70a3949fda4d766540c85ff,500, +0000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c35850000000000000000000000000000000006d3335e092616363e94436bb68be89667c706564ba687f4a3494fcf7da62fd9ad8ae68cb76524926c261983711a14ad000000000000000000000000000000000f085a3d013592c402a380e2e8d9019864a775e7b8e8b94603c8cc1eb1def1e91075fd5675f76534397e2a7d76c2331e,000000000000000000000000000000000344951ccb5e60d1838f7793fcf8b765f5f252b69e1cfdb4bd3c20692c8ffa01afbda6950974a65f6ac74afb9da5942e0000000000000000000000000000000014f5f0e6b99a04d1c5c2adf96c53dd41f8c01aab8db4f0e6d7fc5eab27f6c03c429632db4e1c21467c09d8a54066a4d3,500, +000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b0000000000000000000000000000000019e2ed6e9757e2339d013078fac91c966045f7a1416a56135d75e603c2021a8bebf4acbf6c0d5ba911f66510e9a7ad1a0000000000000000000000000000000008b8585444ffb3bd4fb6ee23e8128142aa72fd574a506151a0eea8979cbd694e03897caba63771b0490d46063bc5bb57,000000000000000000000000000000000a449fb0da911c544887b24860bc5fcaaf054041cc80f16bbb44c796520bee454d0d06f84fd5aa179a44fd4fac9f144a000000000000000000000000000000000fca81401349089caaef9156a86c64271c77235c9efd136dcfad9894450b076cb3dd1a05bfa1e62ef904435eee5d2250,500, +000000000000000000000000000000000b767f399e4ebea34fd6b6b7f32a77f4a36841a12fc79e68910a963175d28cb634eeb8dc6e0533c662223c36b728cce2000000000000000000000000000000000cb3827fd6ac2c84f24f64789adac53439b4eba89409e12fbca0917faa6b7109aa831d16ca03191a124738228095ed65000000000000000000000000000000000f4a256b4288386545957a3ba28278c0ce69a8a412febfed1f952ca13e673822bacb6b7751ea75893b680ea363aab66400000000000000000000000000000000152379d006e74798199f83b0c6c22a98440ef653d7f0a8c5e3026bcdabec8be59a3cc291ba05860bd0639c5c5f5bee26,000000000000000000000000000000000c427721953e139d4f12ad2a3f8f91a4caa49875a87001b619c8a6e909a7da8ddd9dd026bf56d5f85d49fd17527106a800000000000000000000000000000000018add2816914ef51a289e707ba0224fcf0b7bcfa4001487e90dbdce53f1b596e1f5872de32fcee6f63bce4484ccbef7,500, +00000000000000000000000000000000150b75e9e9c03ada40b607f3d648bd6c40269aba3a1a992986dc005c9fde80bb1605266add0819641a0ca702d67bceed00000000000000000000000000000000083b43df032654f2dce90c8049ae4872a39f9cd860f08512930f43898e0f1e5625a5620818788797f3ca68134bc27d220000000000000000000000000000000012dae9aee13ed6ad52fe664bf7d2d0a1f134f0951d0d7ce5184e223bde164f6860967f9aaaa44fa6654d77d026c52d2a000000000000000000000000000000000f71889d64ec2f7da7319994883eb8bd1c753e6cdd3495036b630c35f07118a1bc10568c411ecbdf468a9cdaa9b4811b,000000000000000000000000000000000275b8efb3a3e43e2a24d0cda238154520f0a2b265f168bfc502b9cd4a07b930756961ae7e4fe3f01a5473d36ce3356200000000000000000000000000000000113403d5a968f01ba127dd8ef6c8d7b783a10d039a6b69c617032eba7122e9297f3ce2360c829ae64fdc9794695bf173,500, +000000000000000000000000000000000cba419694214e95a3605a9b748854d16c8e6e1ee151c907487d8189acfac1361b790a5e78f43593152027295adf8df400000000000000000000000000000000110813ff6e0ddf3427e2a514d3f0bfbadcaf9dbf039e0f93fb9643d1e62bc2469fe84cd9ff0d585bdd1037255bbe54850000000000000000000000000000000004e9dd69012ab596b5d3f1f8e4593b448685fcec4ab3394008178b137b762ddf9150cbb8dbb74c8af45bd8baab9a6c4f000000000000000000000000000000001132b66a2127885774062732127951f051c9c3c9b5aba02406e3f3cd4ecfe2dbf6614ebaca3bfe9efbe4f6e5b15ba0f5,000000000000000000000000000000000594c808954bb930bd038806500c9e3fd6460a83554e945baeeec2354a3805f046c76aea62c249080f16ae8e70f8fa6b00000000000000000000000000000000046924a32fb3f2df9a52615e45eeea2fa3ac0e2ccd38458194ada6b4d993ecdc0f441e41d0ea37599254a06aef68b9ae,500, +000000000000000000000000000000000106df8eba767e90cce0eabdaacc24d8e226c6865012ef8cb1460de5a319d443fdc6b4f4e58fb668943e0528b1809da10000000000000000000000000000000019789f464c95c179af18704c0b67b881991880f75ee7b03b9feafa3eafcd0f7d30a17fdd9cf439ff7fe683adca2083b50000000000000000000000000000000017a81b957a12adf474a2913e8636f169ea9cd10be62c16b88f95f5caf661f158a032a9f7d249fdf2765caa1564bed0570000000000000000000000000000000017fbf2abc62dc2678b65d509e19c9c9c5d961c72565649a078da8dff98be6236ef314e9ff8022f639ff565353345c230,00000000000000000000000000000000002c8bc5f39b2c9fea01372429e92a9c945fad152da67174f4e478fdead734d50f6e2da867c235f1f2f11bdfee67d2a7000000000000000000000000000000000c1dd27aad9f5d48c4824da3071daedf0c7a0e2a0b0ed39c50c9d25e61334a9c96765e049542ccaa00e0eccb316eec08,500, 000000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee000000000000000000000000000000000001101098f5c39893765766af4512a0c74e1bb89bc7e6fdf14e3e7337d257cc0f94658179d83320b99f31ff94cd2bac0000000000000000000000000000000003e1a9f9f44ca2cdab4f43a1a3ee3470fdf90b2fc228eb3b709fcd72f014838ac82a6d797aeefed9a0804b22ed1ce8f7,,,invalid input parameters, invalid input length for G1 addition 00000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee000000000000000000000000000000000001101098f5c39893765766af4512a0c74e1bb89bc7e6fdf14e3e7337d257cc0f94658179d83320b99f31ff94cd2bac0000000000000000000000000000000003e1a9f9f44ca2cdab4f43a1a3ee3470fdf90b2fc228eb3b709fcd72f014838ac82a6d797aeefed9a0804b22ed1ce8f7,,,invalid input parameters, invalid input length for G1 addition -0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f570000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee000000000000000000000000000000000001101098f5c39893765766af4512a0c74e1bb89bc7e6fdf14e3e7337d257cc0f94658179d83320b99f31ff94cd2bac0000000000000000000000000000000003e1a9f9f44ca2cdab4f43a1a3ee3470fdf90b2fc228eb3b709fcd72f014838ac82a6d797aeefed9a0804b22ed1ce8f7,,,invalid input parameters, Point 0 is not on curve, file src/public_interface/eip2537/mod.rs, line 49 -0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee000000000000000000000000000000000001101098f5c39893765766af4512a0c74e1bb89bc7e6fdf14e3e7337d257cc0f94658179d83320b99f31ff94cd2bad0000000000000000000000000000000003e1a9f9f44ca2cdab4f43a1a3ee3470fdf90b2fc228eb3b709fcd72f014838ac82a6d797aeefed9a0804b22ed1ce8f7,,,invalid input parameters, Point 1 is not on curve, file src/public_interface/eip2537/mod.rs, line 54 +0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f570000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee000000000000000000000000000000000001101098f5c39893765766af4512a0c74e1bb89bc7e6fdf14e3e7337d257cc0f94658179d83320b99f31ff94cd2bac0000000000000000000000000000000003e1a9f9f44ca2cdab4f43a1a3ee3470fdf90b2fc228eb3b709fcd72f014838ac82a6d797aeefed9a0804b22ed1ce8f7,,,invalid point: point is not on curve +0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee000000000000000000000000000000000001101098f5c39893765766af4512a0c74e1bb89bc7e6fdf14e3e7337d257cc0f94658179d83320b99f31ff94cd2bad0000000000000000000000000000000003e1a9f9f44ca2cdab4f43a1a3ee3470fdf90b2fc228eb3b709fcd72f014838ac82a6d797aeefed9a0804b22ed1ce8f7,,,invalid point: point is not on curve diff --git a/evm/src/test/resources/org/hyperledger/besu/evm/precompile/g1_mul.csv b/evm/src/test/resources/org/hyperledger/besu/evm/precompile/g1_mul.csv index 3a0c612fcb5..3eb702f1143 100644 --- a/evm/src/test/resources/org/hyperledger/besu/evm/precompile/g1_mul.csv +++ b/evm/src/test/resources/org/hyperledger/besu/evm/precompile/g1_mul.csv @@ -101,4 +101,5 @@ input,result,gas,notes 000000000000000000000000000000000106df8eba767e90cce0eabdaacc24d8e226c6865012ef8cb1460de5a319d443fdc6b4f4e58fb668943e0528b1809da10000000000000000000000000000000019789f464c95c179af18704c0b67b881991880f75ee7b03b9feafa3eafcd0f7d30a17fdd9cf439ff7fe683adca2083b57cf23dee8d95d94046678f3bdb4b0ea3d4e3a1a2f07f582e2a98ad6eb7562cbf,000000000000000000000000000000000bf700422a382546a74376b0292f3a49ceff5597f0d2b726b1ff099bcda7ba92238a21db12eff5c314a29dd2387bec850000000000000000000000000000000005e22e3c772f3634b1ccf4e311241977eb20e7269540ef22d379de26ab80c58461dfa3b67848e0d584fb11de1917949a,12000, 00000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992feeb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e,,,invalid input parameters, invalid input length for G1 multiplication 000000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992feeb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e,,,invalid input parameters, invalid input length for G1 multiplication -0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f570000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992feeb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e,,,invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 79 +0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f570000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992feeb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e,,,invalid point: point is not on curve +000000000000000000000000000000000123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef00000000000000000000000000000000193fb7cedb32b2c3adc06ec11a96bc0d661869316f5e4a577a9f7c179593987beb4fb2ee424dbb2f5dd891e228b46c4a0000000000000000000000000000000000000000000000000000000000000002,,,invalid point: subgroup check failed diff --git a/evm/src/test/resources/org/hyperledger/besu/evm/precompile/g1_multiexp.csv b/evm/src/test/resources/org/hyperledger/besu/evm/precompile/g1_multiexp.csv index 5e4b444696c..20ed990e613 100644 --- a/evm/src/test/resources/org/hyperledger/besu/evm/precompile/g1_multiexp.csv +++ b/evm/src/test/resources/org/hyperledger/besu/evm/precompile/g1_multiexp.csv @@ -101,7 +101,8 @@ input,result,gas,notes 0000000000000000000000000000000004acd4cb6bcfed3219c3aee9368feeb58d77a7ec81d19bea11402015f4bd0ee2d7afd86fa7ae9dd320910ca28eb6d98f0000000000000000000000000000000009fe1b0094c0c2ae80a3c5accfed5d212ce39f867aa2150b781c193a0053aecb04d06e005fbfa0a24595e5968d024be18a71abe11a893fce872f6b8a020b6d84241df03eb934b50cbf3571df4800a8330000000000000000000000000000000018cf9bf39549c35e94211b4e2d0a0157d73e1ce8a17cd724eb33c38281dac07e12eec61b27b440b220c4f21915a73a52000000000000000000000000000000000fca6d956989db84dcfe58b0310fc21b5bdc82a32838c8d9cae912d683dd9c67f68e15b3fbf9d7b430ba239c8904fdd2bbf28e5bca314391550d3a0fce50b1220965860e72c8c3865a2d4c599d31d3f1000000000000000000000000000000001897956bc232fd5a9b0ed1b533bebef8ddd9e97002513eec71d67ce1086ba8473f2c013af7d8ac548290453d9f71bd5a000000000000000000000000000000000796da5c8ac165d416c8fa36d84e11bcaa80c1bbfe18efde4b4b2c71d6d00fa24f3d51eac312cad9e854f094dcb6ec7458b208a6845aeb2bf31999042c59b7b130a7ce5297e88023953b1aef63616fe4000000000000000000000000000000000302240769257e92899da03fcc4abe1ad3944b74c3046e790e4e950f2958426b5fdc691401a1c8a531f42185d382fe5b000000000000000000000000000000000053750b58b6d2fbacae94e22b397261e541eb4abf4715b3f528dbfc3388122918b1b4b506f2fef89ea936efdef0105b3b53b6cf9e0ce1661c4960283be790abf956c2d6433529b8f3a32b92b227aebe000000000000000000000000000000000168a635a14f61734372f4bdd2fd564d77afa8588e1828d88c4c90bb50f57473b2c20585dc0e93726b84e73c61f29ef1000000000000000000000000000000000e6e92355e59304ad35b1dbfbb98db803d5fadabdef4fb1b2a54080ec9a33a7147ebb4d5219acabd949337bebbffa793b049228435ade4c4c565e65f39f13a84c747c312afcdaff352560b9fb3cfebcc000000000000000000000000000000001797bf2ac9b490cd43a346fdc64bfb22301a0a0e371bb4df8ec02342b4fcc99af43b4735665c6b1386fa04a3dc5406e3000000000000000000000000000000000fcc20f4aec04b7896ddfd86f58c2e1e9dc6f863ec3b477572c073c0f4fb07ee8dc0d5a843321446445b6e7846fbc5d556197f5ad17062d2ecbdc8887bcdd32e5ed4c48cefd9e14d622a0b800d9703300000000000000000000000000000000013ddb8ff149222a5a0a997c0b89aeee36a6ff2540de3cba8bfe6a2a64fb505f13ad956a3882082ab85bfbe72f3a3a6b600000000000000000000000000000000102c1a1085f60cd5326966a2dda0872290e1658002ff3ed95c47cc0345565076bdecdeab7082bcfb439cf7f3e445faaf721d9d7fe10104cafcad71307e785321ab87b2b69593535caecbf0e166cfda5b00000000000000000000000000000000189515e637d404ce6db58d24774609cf946074aa22066d808dc022824a26b381bf09148005c61156a976154b025d71c90000000000000000000000000000000009102e313c4517cdd3d07a66e0013eeafc996c21fbf5f0f3e7d232ad5adb781cce1657bd5750193cfc0357ff55bd012a461531ecb61365908019c1e8074a4c322df2b356eea3f3eea9aa1e0e1fc5525e0000000000000000000000000000000002e166e475ff083faad64667b683e546b2358f945b8656f9c2f3f6e87a40dc3fc087dd94874bec1c4bd5929b7c96024a00000000000000000000000000000000022bb4ba4be638d8c14a16c94522c41cd3b3ad917daa454f820b8fa35e5a48c676266feece6986e8fe920b2a5e43e4b3569c1c1ae2d18bbe36ed50db1bf30957802b09a982fbed49d4968815552e010d0000000000000000000000000000000004947bd8ea8cc3b116fb7320c573fff0f107913c18cfdba2e7e9a4c8715e334a431156f384548508df8950d681163aee0000000000000000000000000000000001e9e7494c295248184503344b8ac7bfcff41a4561de03d78691ac47980f14aa47c1eaa3cca80103f0f2ba14a2842aea2061d33b2f7e786effbd2e93101a56ba1bb62c1a773a08b72ca82f5183bea35b0000000000000000000000000000000004789b01538cfc54cad0e99538e874d13eaa7f07199af29d460927c3e622c74e0bb4185afa12c53446f56033348c332f00000000000000000000000000000000154291a8bdefbc91445ef1fe123f326b8aad652c8c54502920d4dfa912c2f42d784fbc5a16d08468d2d6ee56e7e8eaa24129b150752d2d5551a622231ab067931678454aaeb23f76168219406f0d50ee00000000000000000000000000000000029048f227fe8d1b7247a82cfd3e1b4b60cdce6b52de42c4b96641bf8fc5ba9b077e33bd4c4fce9a51b63a6a2451b427000000000000000000000000000000000c83518e1b7700d68966d592cb2e3295a2db5226eb6fef972c8a84721d1e49a30e4a8ee3494ed4bbcd2a6877e1ba597d366c32d5d3c132f32a6ac3cfe1dabb649c59ae224338f747ad98b193e83467290000000000000000000000000000000003e96431aae4330d3d204093b7af21343ace4f1960de951eeaebea51e778b1fee43ecddc46667d096edbc5ff4735586400000000000000000000000000000000183a282f4b0513be661b1b38eb5f02b51aadc591745e0bd5d2d4e5545739e26470a9ec20d78ec284268d9c54c8e4f7b6d997516cac28a3968ac6946b5bffaace0856a52e38fdcca11ddfa16cf5a568f5000000000000000000000000000000000904c85edd36dfa18ddb4e1809607708142f3c0861570f2bc8fff14c462675661f2111c10a01557fb21f7f38957bdd840000000000000000000000000000000012a3a37f34ebb23d4c9268ec9e1d53aed4747aaace497695e6ea8fdbdedd58031cb479003e8bec0d14aa1d062fa30f2ce881ec65fdc2f58e46d3ee45a06d0c5ac844ee5b62872c7ba21f6b48621a337100000000000000000000000000000000148532bffbbf8bb1688f6448854214b4273b9d5adf132aa9142c1605d1882879678b6cc70638713b9438532d427f447c0000000000000000000000000000000010971ee30d83719e10e91aad3f1f201fe35ba1a057531b1905bca3a8391a3786cd077ee0f104305eafb3c94f4546da9edcd9b95e49473277a665ca0f9a8309df9ed6ee4f25d803aa967fb8f688273e65000000000000000000000000000000000f73574aa5a06ea569de88e48fcb96e822039af296684933c1b417dde95e08d2ac9c6ad4d525b0734e24807ee99ba88a000000000000000000000000000000000523deae09e75121a6d89b45161f69f0733a9e43d88d8527a03cca8cc126aeb7a680cfaf291554403723e20440b79437334582482a9038ab906880e43a4a9d39e73b6c63604eba0c8f6399eb5c288638,000000000000000000000000000000000db91871e4cd84b3b58216b6c2e77a9521f3080182e78d3f66fe33f313013f06aec2dc5a6d483f35fadebde873bff9490000000000000000000000000000000003b9685de062b09b9e277ad5cd664d28af59064448af2b1b2b2357df6fc88e3ee7e0ac837100e0b7593944e8db43ab0f,64128, 00000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992feeb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed24d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be10000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e44c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d38964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b89000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f90787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c944000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1baaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e10000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442adac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795bb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd1080000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a190fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f81876720000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac943b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e3b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c760000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276bdd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a57876817010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f76894c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a65905400000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f42000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931db3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746,,,invalid input parameters, invalid input length for G1 multiplication 000000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992feeb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed24d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be10000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e44c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d38964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b89000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f90787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c944000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1baaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e10000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442adac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795bb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd1080000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a190fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f81876720000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac943b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e3b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c760000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276bdd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a57876817010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f76894c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a65905400000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f42000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931db3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746,,,invalid input parameters, invalid input length for G1 multiplication -,,,invalid input parameters, Invalid number of pairs -0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f570000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992feeb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed24d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be10000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e44c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d38964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b89000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f90787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c944000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1baaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e10000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442adac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795bb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd1080000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a190fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f81876720000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac943b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e3b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c760000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276bdd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a57876817010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f76894c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a65905400000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f42000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931db3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746,,,invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 113 -0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992feeb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed24d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be10000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e44c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d38964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b89000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f90787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c944000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1baaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e10000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442adac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795bb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd1080000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a190fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f81876720000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac943b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e3b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c760000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276bdd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a57876817010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f76894c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a65905400000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f43000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931db3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746,,,invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 113 -0x00000000000000000000000000000000075321084f4251d524581318d27b73a8de0b156f502d8f410b3b4529435bf9a14f609da9f63ac8f338fbbdf65ebf270e000000000000000000000000000000001925396f4b5c00ac49765e17c601bd392a8a96d8d5a65e2693093e6d5699610ad699d04e083f8b248c808619d01c33bce9478f1e5d80ad0ed31dc0bd5234bc41957cb74b63ce59654181c13344a5a3ae00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d7d0832bc939e73d01bbb419c98abf35cfd0224f72f6d215de379bad89aedfd65000000000000000000000000000000000d3423a536fe9de27b8da74c6f82fa3fae76945d0c8e50b8de889aa0d533ed1d5b757e918d0eca5b6a8a3b34f9f1de1a000000000000000000000000000000000ae42b8bac10e922cb19169f5d91d50c249e3d81dce761cffb7cd652bde04ca17dcd0ad185fa03b722079b69ad4f4cbc3c2130dfc2b39a0e20abc3f883b4e329496dbb8bc79ea7f30c909140c4b3cd0a000000000000000000000000000000000fdb0c5e83ecf0344a69900d1a59a21bd32f4210bbb0194be6355d18433a5449a81fd2c195e353d844070ed47c88bb500000000000000000000000000000000004c27bb460768252e5d07a952ab5d204e479ff74489933ba64f80ea18799fb937dd6956112872024795eba15f48e60073590f744e04374b92ddc43d0ca3068973b5b48c48a70939fd7d047ede75fb8430000000000000000000000000000000017ff6f3ca925d18ab2afe8c39ac69975769fe0fb6980c3c1c4c678deb01ec4b6e1f5b212a1fd5983e06a372be394c52e00000000000000000000000000000000131e1351e7a7f5fcfb87dffe794934356b36ba49c7d35b99281577ac078c990576a4db6d2be8a45c562a8fc1ec0620e37fd535426244e27c586fe8e87eaae37dfd851601e0a2b001d86f094a63c234f5000000000000000000000000000000000ee908a40185717063897efe710f3f1e929dcd53db503c5e3f0d08bf0c0edf2d68a5958fefd6c0099e9c755dc9407e78000000000000000000000000000000000aa3806ad71189cc119e6d88284d476033386ea4be2f7885d5587e0d991ad88590de58c782ca0bebade6b4b111f3c4ed54e3b4eb4126abb9629e0e5144fdf73716c8654ac8ce827a18f432b5f340e53400000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d9c5fb237ac8e79e271008eab06ddc9ab45ebaba255fae6912577d0cf1428545f00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d0fcca884c7c01ba517c241e44478b35196d441ab3a76b7f023e08100b45d77ff00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7dd7f494e1b8378362ed31b0287c1b5f6912a677abc0fc392234818d4242e0239e00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7dba8eae358d75a3dbff9e62624ef6b1c641a1fc6a31c662f13a39d2fab938b519000000000000000000000000000000000f299ebde6e6c7b4d403b10b04832dfbf65b8ef9bcfebfa5352ee3df4f18e43798a0633845822a44a4fc7ec2ba1d13770000000000000000000000000000000003c886084611cb8c95fdb4473bc00d4877dd3ca10ba40765110400c1200d0b1f4cb9b1433e26d358f78d93da63ede6eea44e84f65a823eda9c7903fd4178c9e734996be8f291949341ff2e3b09ce7605000000000000000000000000000000001812e8d91771d93bef2f087058dd26f0c1ae487de050dcab1510e6ec444473f6815d8dd9565c18e777cfce1c836a4a0b00000000000000000000000000000000041fac4c713ba9727b0d5d539ecd45524285a2c179e7620dab88bad649f1b9b40dd422ab2d40c4945bc27dcb6e0a7a96129a1ee5c960052ac3051e4e663ff4f3c83b039dac18f25d4412a07dd9b164f900000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d5ec8616e3f6fab8a437053de12816f4ca18c1b4d42131c4800904f0c551d8d3a00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d0dc25e17fb0ea75c07be8123108c626000191fb5106f3e1921401acb96dcd710000000000000000000000000000000000f59f9d995d77fa65c22d5d84f277f3ccba50eabb8c404f1f9d70a39cd289b37978bfaceea2b987712507d88fae2a8f3000000000000000000000000000000000a4d6759c576fd8f54a8182a8d1191e5c010e38e33c013603b480cece6dec5cd6892972be8ee1c13500c8a601eee5dd352c4962db75fb059c2fe525270edbe9374add7df7b53f79977c9e51fa5e8952b00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d7cbed522a1daafa00bc82138e08f1318b629d6409bade3d0dbd1431dfa6d60f4000000000000000000000000000000000809adeabe91b3d8173fcfb1ccb80dfb5a5ce48160d915652ff193e6c79eca0b6d9bdcc484a37723db4fcd3c4463ea9f00000000000000000000000000000000130e0fe4001d5995012202bbfa42584ed476ee0decb95122a9ed61364059d5021f8ef232df6276f93ac38cce8d70ffba145ab6abb85976ae17d459441500228e246aa214cd015460aaef6d8e5e02373200000000000000000000000000000000083908343f7329e4e738aa6de69b4f1e275d41243cfb30a6b9e00f032c0459fd005c43efe5a358ef6c0118b1528f3848000000000000000000000000000000000d39c55cf915d530de551ba3bf9aa147fa24f83d51656aeb63866c38b4f206079ac4f89c7f91ae3c37de09d37713f75dd98cf9f26a404e252383a4402c17de293857da5b7b8fabd9f4c59e5bcef4c0eb00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d5a526d3ecc90ba9a2fe62b0673397d4af02440f44551a333fdbca4e2ea6ac0d300000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7de9f0448dd229d025e58f7afb4912ac9878029b29002fb92d4259f815a32916400000000000000000000000000000000005bab5aaedd0f007d0c3e29870e29c93d34f43c26ad57281c03bf273a5e3264fb898059addba45c54a7fbfcecf83e95600000000000000000000000000000000150fb89103ccbd4976372929d3069a0cff886103b8e942e9911f12fd3809003dd5b1aead983c0a3f705b327c73a7aace8e0fc676aeb9d7bb302be47a4efc9e5a6217eb557d6c0d8f37ebe15cd80c170b00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d66539fea2e9ea8285538029a644c759190f21bab23f0e98effbe59b6b4af2c5100000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7dd6683f094390667fff0a92fef389fd9ef0a77280f90f41660bbeda517219e3ef000000000000000000000000000000000a7c61050509b59909bf23a56952fded8adfa4acd2f58b5bbbc0253872dd4d542d27ee23a511de762f21f81c5593ba75000000000000000000000000000000001264e5a654cb3d9ec931f9db10cdd851360958c360e53eea634d6eae8d9922e04e7f87b4cfe99932feff5b3926f6e1efafcce5a813bc622c601c3b4cb300cd413b8b0564793556adf1189e42722ff810000000000000000000000000000000000fa6770b293f6b74109947469ac2becdd55879467c8cbbcd0451805c3202e0b6978e43c421ae07cffba10dc37cafdb3f00000000000000000000000000000000060dfe71eac6b8772bcaa2520d343d56754a7e10368637a506decee4ad549ec43d8c4966b436e0bd2c1e270fc466d3a63ee43425efb56b18114a8fc59a63da39e30b7610d02ec5075cc391db59b875e600000000000000000000000000000000000f6e95654c7f61ba0dd368000312c5ba02feb2074c84bb7790daafb3b6c2a3aab5620d43fe3818d7ad92905992b644000000000000000000000000000000000afe6028eb4c49a8bc331ef714f1e0677d7623e3e36b4b3c451e7253ea6ebe065749e6f2e2bf03718ccb067411e2c7fefc3649d0fd8f7fbdd48ab6f6fd11d5fc593426464c32f09f3515948519d99d7300000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7dd824e57eb10a192bacde897180beb87c5559e6df2512be8df9cf043ad24b1e8c00000000000000000000000000000000119ca5f3fde1801e2cef526cd4e06b1230bb4b7dbeb43d34ba059d9bc394e95fc94d9be60ea972375d66b31c4f0482140000000000000000000000000000000007f10bfcb3ced451329af6fde9e153845fca20c6f5abb30a8ae2543880baf3efe6b31b3e7001700332bfc3e1616738b7a8a514daaaf533057c54b86200a1f8d5cbdacf8e3c2d9eaa921250b6387f45a100000000000000000000000000000000180ea28fad486a20615b934640aa883e70ee60c47c6148796691c58c64d03cb80b3802256756ef9217473837bd7bccc0000000000000000000000000000000000977ddcea657c79715e6d4ae801ab6e63a7c2688c0852e229b503dc951bc9611f36a858450614204ec44472914435643604cad22d4e29aab314523cdc14b691e509f260423d28e6f176eee3b659a164100000000000000000000000000000000195c0894c937d13b865ebbd935e4c609eafd492d57115df5f14df86b3381f7f9a111a2122d79d80980ad201c138464f5000000000000000000000000000000000471746c7c57fa94c23cd5978db9c638cca6617d27c445cda89f8298c4cb998317b8bca49f4de36e1e0ce24947021dbf7b1893649115755749a9f858ffd8484d7dbcb04bd2a3ff91a3fba86fb44ab69c00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7db7930da64294e5949772522fa980479cd419f5014412f53a082e2b35f537ac0800000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d61abf3d5e8d8df951747925b485dfd8cbdbba2fac2ef5f72a6311f79e89d406e00000000000000000000000000000000056f92baa9ef5e1e434fb70193f924706489968123ed61b8c9938c695852ef681709553c56d979f0925bdf1a8390a8d6000000000000000000000000000000000841faa9c8e27b1e94f2a5c14988806d62d7f35de74ba9a7db0062dd444a19b1a893ef6ee2d1ebefe3631d0ee0fcbca35be3179cb22c10450325b3609d9c3366c5200c1936280b3df162c48a213dce68000000000000000000000000000000000a2398e644fb52d53f02a583e67b11e5c7ac5b8e18e96498b9bbe572c3afd4dba8cd978db48501f5cc525e6c13ce92000000000000000000000000000000000017122a21cc3b7551a787fe260c1ed80ea34bec22d3ebb67b86dd0f2f95f50db7f577732c0d0e020e1ebb4295ba9a9d2a17ca658cbd9f48739b0076f97e4b943276f558760ac051f5c67159ad83bc94410000000000000000000000000000000018b172391adc5b6c44edc54c2bd874a01554be69d848b317fe845e62282d82440ad48c4bb645553bc75a701929aa9de0000000000000000000000000000000000753f19cae676565e45b13141d12fbefdebf6ea7bff7da94ace2af135e1aa38a7d63736de76bbea093251eed8755d6e3c3c4edae42b946fe140e511563d1ffb612a0d4fff66941f66007d0ac33c547840000000000000000000000000000000015916c0f96897e0e2b43bd76a98608d0afc276c1764f458f83db94fcf13adb05f440df55f3773bb12a3c25549e57d09d0000000000000000000000000000000016c6bdf76469f51e0b5dc0f9f2b22fe552e0fd0bd7084cf92946fc4d05692ab32690196a408a87681e32237cf172f5eecd8945187d434ffede5a94aeb3fe3442aba582787eca04c0e17d640820a30399000000000000000000000000000000000ca63266caaa56c1ceb84c922afd869885a1f33512fe59e1b367fe3526d1cf47f96df769d2f7708e1b693b528a28c94100000000000000000000000000000000027dcd246ff92089013faa40b4211e0bd0fa5777457668e57c0142561847542308a51d192ac5bf603ad8aa5119c6a5bc0ccbb0d0ed90f9bf2b75879861eb24b1170c9a83f5cc479beb4cb1e8835e77f2000000000000000000000000000000000d91133d005482f27cadbf8eb2d4a90952cee2d8b4b5a49dcd79a8141bd3cc04d4721fb3e62da217a08bee294a879ef50000000000000000000000000000000003bbd0c8be4f936ce1fa7b48f9158405e52a819cc1d4bb1fbd9f309698f8cdc1b2e1a5554eba3ab8d2e92f191274ac8cfe435dd14091302d34191d6c5028c3e62ad016f5a5b6256f3aaf82e9606f21cd00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7dabd30cf71788f338622c21204603041353f1ca6885660be7ea826e00321c262000000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7ddebd81c66b87038940c72fac5d33aaa039525bfd21c79af3e4ef46ac5e1b274500000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d6b382e8936d65c5c6ec4a52a5e22d923ba82072682220a6a30426319584fad480000000000000000000000000000000009edfbf4cad38d597d63663abd67d6a84b5c54dad7b7b147d791c41f9316923859e6daed7e384d9049b0a2203f08607600000000000000000000000000000000168806179a9bcb0bebd363fed37a5b9cc1e8b66d270982c31ea5573b78236f75d36b04efcd2092d7684a18c13de83c512bb927549b1b512c6e6cbe1038bed00f074deda5c63a2367ffc60c5ce7b8afc500000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7dfcfb7d1fdd47912832e10e1f30ec146c120fc68ade3f3ac10604ce548847a83300000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d1fc561e062ab6a6b015fda81de63bdcff89698bd2a7445d0c21c1b8284d8ebdd00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d385829683b3cce18867d07afe8c02723f906a216c7d6d59d5b42518f5999158d00000000000000000000000000000000032d01e694621f0d2821bd2a575424d11cb623e65c1bc2438392ea57f08936e9b98eceedf917f4e51afbdd30dc8c24ef0000000000000000000000000000000010d8d9f87a3c2689ea9ac9afea38a0b1b31d90f0afa71957b596cb89a9bdd414dc2954f1cc747fac377d318769467dedc7839d6bb24f8d23caddf1b6f0895a5a6432fa0843cb313c5ac57e81ff816c6f0000000000000000000000000000000015ccd632042331ff5ea5fd21697810f7c3cfe5eb1d6e7afd9f850e3fd8e5914bce3d548cea90a60ab0ed27e24e72464f0000000000000000000000000000000001d027a23c9de769d155f06b81469ad9d3ea59a93d40388215c2b5c7438fa10e77da03e26f1010dc7a225d179b9298f9852544eb6963fd0dfbede6d8cad90517c2c3dad1ee2f6c49d24d8dd155ac977000000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d5f94eaaa4947d5d054b54792044ca1450f25da28df35db7e61b7d9efe55debac0000000000000000000000000000000015163cbe70b4f6a2d7d90a8e48ac6f8812dfc7c997add0528f17a6f0cf42faaee56b13e1e95d5b8f306696a41f426c49000000000000000000000000000000000f55f240ced8ad609c288edc4b2a888b84b101aa39473c3353163b501cd888e07d42138e5e450f16d4e1ec0414b5d28f3bdf0bb67b4d2551ea7144121412d258383ed6cb4abb93b7b56b127dd384296c0000000000000000000000000000000011db7b6d983443206e5a7962bf7e2113ae463531cc958e84296c96d0c31f4f2264c8392bfb85f164500d6b872e7890340000000000000000000000000000000008d396e251da387cea196f5b7486788b3c03240451ad437b2d306f3b6ecc6cabd7e2f4111f7321b64022d2aad51fda460aef6a4bfefd1af38ae88ca70324b583e2aaebf559301d7505db962ebae2311400000000000000000000000000000000002b319cf695dec2b8da3475991a774e1c1b01421b0ebfd11297673e0eb218448c4271e67d23bd0ee5be867126091a440000000000000000000000000000000008549085ae48aedb3c8a4bcd271a9da39fb74f528af94e588c8a0a6a2e76026f2711aac32601e944d9547876ed23de7e1a85351cd8c1e3175722cbd8f6aa3806e0d9361f09e70dd03771b6b26d2f93de00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d6fcd55de20c3131edb3e85f6aa82f17c5fe5b93f92fa3fd596c833549824ede500000000000000000000000000000000142095eaf85b1017dc314db46100baeb29170a35f429d1563cd7aa6714f6b1dd269ba00be1a82294b0a508f7f038e0d4000000000000000000000000000000001558399b27afcccfa6a89cfd4635a4bd8c87d7ebbb25c28649f24f90897d7fa54c207986dd854f156be69a6eda586aab431309767397001e1e32bd73f685b5f54076732c953974b446a3088f54c9e3240000000000000000000000000000000019ea6e73637995387e7c2cc1a0de3d34953242a8dd3139d8a5886863cf21249e8716a8394f4883a2af632abdd15998a60000000000000000000000000000000010c0c216f747d186300934461ed363744aa30be9e0dbb390d362cd3c71b59728b124a396c6e186ea63aaaeb4d2a454d7d92679e9ce2106708d5206d408ac3650d2046a51eb79eca4dba9416ef0f3527200000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7dfa01f7cf41925a6bbe60b14586586fe0d4ba146c5aa6f02c14d41cd791878554000000000000000000000000000000000e7a795c2e6d4ac210d0fb92e94cd76ff97193ef7bd79446afafa7185df8959389801ac16f846360c8509bd407d28da40000000000000000000000000000000013e260782a41a63ce7c4048109fae191aeb7448fceb79e06c37223de72ded540931ba81ecdcb46423bdc7d564d27ee0aa5138547028a8868888087c4abb44f421757cb5583a6eba82b58504655162463000000000000000000000000000000000ca32c6c4b8746170ef22461dca8893855af15fdabb27a293d2620a20088f3892018e3c10dcf80f67f5227b14a808fb90000000000000000000000000000000001e88d80946624429dfb26f20236fe03680a359f1522a9075d48f27c84de9008bb558a645192bded688197e55b29c1cb5444536040fd8d54b8497d86600a5ed467a4207dead453c71781ce03552fc18500000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d49e2561d0364b0069838bf24bd021d1e4a95cb0bd5aa7a94a506189f4f5ce5b000000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7dcd2d6f115ac393d6d5733cd0f6e6d3f0231cd825bf899902c50c917d0a56aff000000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d59d2bd9bc3e12e5d50c9e4c768c2f8c65ce771a11cfbe46da73d80786e027b58000000000000000000000000000000000fdbd45fd634a2f233ff8ef90d5947977023d9ddfa69f3ce6b1dc6abb17bcdcbe97bc4be5059bfd2ab1aa91d17821024000000000000000000000000000000001435c723d4563d17a9c12090347929487409efb3d29119ccc967a6c5e9cc00e20e537a3e05e385cd8efa555dd3f2b722ff0a56d2dcabac5932c5d959f2f036d9ba5c71cffb7e686d319bdc8d1e83093f000000000000000000000000000000000be1163dbc97bc79f8434c744dc0e2c638c9d524903738ebea3cb86b79438ea2fb0238451da6e962b412be9a0e7611f800000000000000000000000000000000022a59b0eb1ca7d35ae94bcdc64f50b443260f8cf7c8003d99c3ae198a7c2fcbee4653566b485f33357d8eb1a6db1511f7adbee3c265263c30e3662a1bd35fdb466bb3677d6d09efe99cd1a7a468548300000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d6448a9f68973305afe0622c12998037252fc8183296ebd0f29456159b8275e03000000000000000000000000000000001947d6e595915bd7ae4e538306d208a2d312de5de42f0d3bb735916aa741f47ba44834b3965f2aa9b5405c10c2b6b992000000000000000000000000000000000eabf37c5ebf82bf038d3eb58b72a5a584d69ce3e7de1d9668ab5df2abda5797e567321ee43d1aaa15f057048567f7c6f94018c9aa0307288e5126c736b8bc6f270836028ff850de60261bff338b7d7d00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d7037d0654ed51af22b5b324e23ca1065dd8cd3408a675ec204cecdfe687bcd8a0000000000000000000000000000000005d727a52fe6676e8508bd7d3a27c79cc4836d412cd5720063ea0ce5ca245cdc6dbd6fdaa26ff502d3ccc6e27701395e000000000000000000000000000000000ffa9dc48cbbf1ba32bf5de2ad98e8f4f1842f1e219660c00ef9702a72aa1166d46b9731b8c80581a795c0a09693cbeea5616463a00009079c228b8a92059926e3e26170c7bf1514823eaa8bc0e6ffa5000000000000000000000000000000001220a1e08ceb679bb165a3c9be882b6429c36ab83a7324ddc07ef7f2d0a4408d741e84791a33775ad4565cf816a4e9b70000000000000000000000000000000001496ad90c53c27392ee8849601aaa5b2c89700112a324864472c1a8d49804fde78e761adfd9c7755ac2d8d7d215c0b53c1cd4a2a7ab5eca1ddcda8887fb2aef01cb454a97bd39f2e0680915be270b970000000000000000000000000000000012b40bf0cbdc9e374ac4162b638403109a79803a726509f9326b85d3f2b20dd2a09015c30a2bcb1156bf7bb888f010c0000000000000000000000000000000000f3bb973815c39b95fb1398d80ebf1530060e31495a96c736b0235c78c5e041ff69e2564746b07d7a270bd45258e4aa7627af6cb2e8a90102e79d2cb19aaf0b6c18dedfdc736894732c7403036f0324600000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7db4371b02a3681f7f8f53acf22cbf8307d6e5276a5a23ec74fe02cc7aad62e98200000000000000000000000000000000007c2f86b037c3a7b67995d96bf4313ab6c20f472994372b40b78a3dfdba4c7616ead79026738d0ec398364806a57189000000000000000000000000000000000e752c8490fa323e0c74f9ffdffcc9ada27016388144bc95445341a6eaa82fa9a10d59c11bd8dce3b9cb0489ab1a5447ddb739a79265fb03de61859590978af384fd44243aa33806a1c7a3e20f37365500000000000000000000000000000000048afef5dd0565942320a985cf8d555408cae3edc40b7a5456ec86308d5f11db5592fe37e058dc6bed42346b42fc4abe0000000000000000000000000000000005e36fcbaf73026442a94ffeb56f4d05df46b90e3e02c3fbcbe3a32b1878edc7f6a5e57dd9f6ba64097c41e8e7ddaefe24584b142d6180187bc728ed22bf802d01db715ff85b51872991e92ff1d70f500000000000000000000000000000000008e3fe3d42e724fafb0501e4d8f2c55ec35bf60c71f6bd31f3d4b890a0b418e00509b8666af4207c8db44b30dc3828b900000000000000000000000000000000122a0dd7a24cf1e42ac8b8795ba785efd1f03f4753985f4071f17c4b8be7706fda8f900364c1676bef4e20e5797583b129566ac3f767fdce83ea91a295d06984d48763136f977f5e7e4f013ce274923400000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d92af1ecbc9eccb56b3a220398ad3a3885cdfd3f02c9d302add64359bd121cc3e000000000000000000000000000000000e7775462ebb7f1ed737ab730e39ef8bd089222d3d7b4ab904e3583f898739d6a53c1d359d11017892eebe6ed9917253000000000000000000000000000000000d239831ce4acc58fa97e4d4cf56c3961ef313b681ebb9a1e221900e55832989efd1b022d9ec807019dddf73b1a6fdb2a407193f74d08ba9ed5d971f0516e7bbc7f89309dec24a4ea5d2a1f3eccfa5c6000000000000000000000000000000000c5be42126df5197c04f49e618a3fb6b8ff17f90b5ff26db7fd508a0723c4f479645beee629e02c9da60313b19bbfcec000000000000000000000000000000000e7fb0e4699dbc768d51247f5d7030fc698c5875cc9c5b326f3f7a4315c6fdc8e5095e91413dbd2c9b40316645275b3cd8fde96110b12c91bb5a9df196e4ca3c169710a6951e4dafcc2803fea31a43d00000000000000000000000000000000004760b7d9e28a23216b7ad3ac6b95c094e823df1bb05559e1871d9598287ef2f48f2858a1929fabb7ed3d7372a8bc7e6000000000000000000000000000000000f766efb67b69b273f9e1ee0c1f934231072719039e642892623f869b05c3b7af0b18397e76a25cd9f3eefb4f04935a54d02b624a8c2cca31a05cdeda3e09ad819140fec582a88f266291ee6aa1f1ce6000000000000000000000000000000000f734e6b273c6b05e911a4441c3740d13228917fd0916dfcc696930ed1c2e70268ab98d764507e68fd52826bdddd64e2000000000000000000000000000000000c20234fa85fd9eed8b148e79aa833443d47384590788f0700ccdc163b5d50dfaf5fbb901e7802d27a3db86f6aac06e91977c7a5be2688c808528d63a275fa8d18af14d7e90aba7edabfc821f4ea37be0000000000000000000000000000000016f5c2679ac1a70cfeddf38068bb85b6d35de903105a14b3c4b3d00e6f57c8b6c6de7d55f2b697617a4aeb73cbcffc090000000000000000000000000000000014ebec9ccb8e6052a009255080f2e0bc34eff9214e911bd6c4eedb53022a6defd5f0e78c9713c6a2e1f110152ad65b941a5267d9766e7d84ed4693f15ead057ef7396903e8a5f3a55bae74f044368a6300000000000000000000000000000000189b31702e6d365104d661c9c524b2c8d42dff369366d125453496e434f1b9c4f46cf3923afd14b50a4e06e4f73b5f8e0000000000000000000000000000000009eadd06f6cf208c4c70fe079cec8191553cc8a55321ff0a70f818d1cf7b5838292ba5eae5298f9b842577a7cb0ddb4e89246920765fec2a411849831114d529aa308111efdc119e85186b960ea5456600000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7dc088cc74c109957af3548bea8f72505c8ece5c1b690c14dfe958b8faea470aa1000000000000000000000000000000000334aab374f69a9760f58431c6dfa783f71908bb3249f03d05272fcc6b5548dead4791e86d630231133c121ffc10795d0000000000000000000000000000000013b4f68ca1144b9b2723930cf63c5b75fbe7a09997ad6916b933c5713e5c52e918e4a0876e5a3fa282a87335d9c1cf4d6e33049a1bb4429d216991c6aaec7a6601723e52aaeb4fe9dae31b5d3816e93300000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d44f89ffaa24457b265e18cd544a4107dcb6f3d7f3fd39842a3a6f40cb80ef8b800000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d39a1a26daf737fedd3f00aa08e7c55933b70060321ff2d862df8d3f2216433be00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d792f9741bbc394770f02b271ac249e66e056b225118afb58e804ba6a64d69fab00000000000000000000000000000000026ef7ed98b77106cc9e97e418086f844f2fa5db62484336750db6b140101657350dd20a29036f31b3b61a0a6292f1720000000000000000000000000000000007253b6377638195eaf42daaaabc451ac78e0707f14b12523ba5cfbf5ecb146b8fe1361cfdaf76b7b7395fb4b2ec7733fa4e4476364dad37551ac06e221cfb959094aae57733ae80c00d2213a68a841c0000000000000000000000000000000010213a60637cee478d92dc83b85937f51be6878189ec58c491959206024a726045906c47f39c89c68e8109ac8a09124c000000000000000000000000000000000a915b174003ba76da253296fad341af125a7b0d30e541c92cd25c970fb5d45993c516124b8535024c74d970f12fd7c1ced1ad2563403e274800e80f48fecac97498e58356f33ef6fb3ab123ce15f5d700000000000000000000000000000000149451bc330ffe029b198feb8d796fde5341b0691d0ea058d9a29ae1de78740e3906fd8cbcfc135caed16c03ec8b0fb500000000000000000000000000000000147a09c04d930602218988f7a101cb0572e8a41f83c49bfa6b92217d62a7b817146876b8de99f984cdff2f1509762580bb49d60ebba706d3556aa7ada87267ee3220b7f0b8d333aab79d690feba308710000000000000000000000000000000010d8cd8f975822ed51b18ed332fa6b695e0ab0c884413da5a1f77f0a472e06a257323aae9a3a8233dde0f3e1a797dd6a000000000000000000000000000000000f2b776ac6bbb05bd2d18148df50f3cf9ba3bddbc6ea410a537a65e0bf12ced5dfb8fd9a1586289bf9ae79889102d1f9a8d81a8d537651685cad896598a65479445bcab815793de88f399a7e2989c2e400000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7ddd130785a081c481879877abfa85e66b3b39cf81451696e7fe7524755d5bb0ca0000000000000000000000000000000019fc82c97989866d4e50f03c018dda0c6b356851d014355862a243ac1e2201ae248407ea895ea051a87249518eeeef66000000000000000000000000000000000d7c34b2070466e3107979f43191c09bc5e70ae6523fcefaa5227d04eb6e51de5d9025df74e2056e5e976a60a8b8b612f96d861e2c9a2b8b47f7c6f6f6c63ca93eb83bf5d1995006a7c5f03c2fc25d2b000000000000000000000000000000000928e3c3be2939c1da0a388eb4b55a436a698827c7979d34ec6a0ad3ffb9e23f112350c0b8174f1e52b2c53a8de4649100000000000000000000000000000000123a719f66a3f2dcc8ec1b3f130dcaf9b4e1e9124ae6807c744417ee04dc7464bbf1bd7f6076257753061334332e7192eb64c8c2bdbcc472ed8079a295888c1ee902c948ae48d2149d37815537aa538e000000000000000000000000000000000ebc060619caa0db6ea4c47bad6610802128d2be619173223a9d0f84d901f147db4fdeb250249759487bc727ac061c66000000000000000000000000000000000c44ce1e0c8b3f4e2098aaf2efe85cbfb689fe5478641ae53cd5805924ded81d586644c3bc49a389b15d837e2d4fe44a39f9d35b3f3b4037baa3bb79f7d5e522b9c1acd434fdaac61c3f616016b3bd5d00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d91b623b6ee1adbe3728463c29c39379a29809cfd7cd0027835a271a7436f470300000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d4e2e68c5c52d84b713d3f681fe6d06fff398c110215895ebf9deef50b2a338d3000000000000000000000000000000000cd89c9fe1298920067f1b37be297c5567137a07a95c56dadb2c04f3993608ea498e89b684b8262172a1081505d5c60600000000000000000000000000000000073204f3cbeb83a5189fe1df5f501241e2453afa3220c38776590862681a771cd5d6cef2946228c1416eb3c1d840bcca8e2be1afacb2acc764b129c6c62c2c35801b548f424c4bd3717f6302a44b15b000000000000000000000000000000000177d5d25fddee9f1923b4ae21ae91ddbc4e55a22af46eb61b35e0634f7ec15a7d8bd6ca12be9fde52561d207d26406330000000000000000000000000000000011e9fc830a8865caf50c1c20853f38039c3b0aa956a75140ed699cd0e69730c6f7bdd00fe05529cedde1785a39f67d3f60cb52da68c43935c22b6d1b13b3c32b79f8939ffaacfb4938bb1747e3d31f4900000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d91181d5d2444d162419133835091288d910bdc603f405c6120679fae82b6e842000000000000000000000000000000001689b5ec4737d58921a131707b92fb7c5b16e084a0415bc5c1bdb5b9a6260327526da2801932b5775f27cad390e6a6980000000000000000000000000000000004b610f2c453e9cc73f04425487f65660f478eed19ed3f3e60aab068f53f2dfdb5a73b02e525a14afe7423f8850ee9fabd637ad431366f08e955799c79f6ea5aaf9c22faf5400e7dc6c249c0e6494c1f000000000000000000000000000000000c37eec1f1033b86368b0fe1e42d9feaf174a77c4a455aeccda479d8fc937faa3a521afc7a2143285e7496f192670242000000000000000000000000000000000ede805910da61cc6daf7b4d465ee83ddf1ab5097634933a70003112a20361cd9c59bfa77b1bc026e93ba6bcfedded5b6ac13b151ae86bfc484c2bc91d4df9a75ef4a2523b968fd31391f9dd0af789f400000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d6fb50ae4f785f5b5cfdd9aa004f08215d1bb6c1214d5cc5f027c3bc28a812414000000000000000000000000000000001259021c75f2fa3278b16ffaf375146d0ffac5477347ed1641886f703005940cede0bfab44fe49ba20914d580161326f000000000000000000000000000000001029ca636af85218a2a23d4e0b799dfeb713718a258c13df2731c4f352c5629a4d2b996d9b2b3c7adb73ef0a28217ae0042c2d53c35f3fd0005a977237d71ec6da2723a7c9187f1b9f9c86a5d77dc773000000000000000000000000000000000f719b8ee0f2d3c845323b1d4b5df26a6c118f705f62b5cc535ad3a9dfcda6ecee37a0a9cabc52dfcbdee23ee4fa76d3000000000000000000000000000000001550b4ca2ce312e81b6050f7cbb6934f05830c739d3ec8b84cf1f349fd740b13cdcea3f2939bac45bbc71317ee0a3c11cff78aca784067502defb619fce8c8ca407761d3fa78d635b21afed38aefa13000000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d8bd0c10194e4c8a032574824ab15979cd7fd2014dd7fda706b86d37a24a38990000000000000000000000000000000000d72f3900402e43a50414d8aba5dabe6a4ed9a2ce4df7f18018ed3292da6ce11f32d336cafe428a867dc3f75af7e625e0000000000000000000000000000000015c68638b97c79004b2bcabbbc309884bb4429fb7a947db7c0aa54a941574475b164151619c9bf5046a644391842cfd183158c0345a5f28d1577e3d9dc463b8a355602d25577e328ea82f987983e805100000000000000000000000000000000004eb67438c54c72fc064a92653c60b36d93dc4578b251dfd9fef885fbb71e7e17ce48cc286d364a9403125a285714ba000000000000000000000000000000001711f593ed6016095a9d01a7bf1081a2e279e837a35d250153ffc6a12bf2dce0c9819e5d0cedd8b20bae73414538b61aa03169e13ad2198817b924f5bc1df0f1db63f14df64d1ae24b1c43f18eb78e010000000000000000000000000000000016b3e588f20cb6b4fcfc37a53f57b4b446abc8d56314031db09996769e79ea63cf54549f0bd367545e4d17d5b55a300a0000000000000000000000000000000016feb1a66fbc5cbe44fc73e43735063100d1d5b28cb56a46c5c932111c08c4769fdea4a0a76caac26347148f50355aa0aa98c3852a24f571693d36ba7db3ffd984010e4084738f0b8b60fc7b6ac4b9de000000000000000000000000000000000ed8b7e1b18a4da705a76d23df1623052f99741485af9ddc9f501d6bb09d04356dcdd02e78bbfd8384c51b4156d0709200000000000000000000000000000000116ed2039d4a23fd7bdc56506bd4109509c6c189251ec9014cf9f9023ee810e254539c83f18bb7642a1dc6b41bf809c7f93ec9381604290d5311aa3fb14d124f0ba65df55f6ef3ec4a269edd870ff84900000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d3f6f400d9cc7bc131fe4e5549b6ab7d82436bf68b504b970ff712ca4c9c2c97d0000000000000000000000000000000006e236535f811c96ff3f9c072eb9cf5f87c45cba5c7574359fdc6000c40693080e7f61867cd5a3c4a65ee9c3d54a6e03000000000000000000000000000000000738e9c888e7bb7d997c4f981c492fd3a4766e4d4c585b7a03779b249331ac014697c89bc21be212ac499fd6ce0b35969e06051cedf33e1f3ad21f763979ffa200c056792ffc8da1b6f44af6eb227248000000000000000000000000000000000af956bfa4d9b6608adf999c46c2c992f8a3eb697692ea3ee630764b855ff44b350ea6de10a3e5e1f271cac23b235fe800000000000000000000000000000000140bd10fc8fd2e934494c9f7b00fe56028ce31d97345ef9b8b31ce6d297cec92c7d4507b3a6fee0ca39dbc9d451143c61e44b07f50d5e8d1a05c82ac2a481ac5e8aa7822db692a1a3fdea6a0506ad93d0000000000000000000000000000000014f9f27a2cf656c4ed0f94d7c3aee20dc0bc821921bfb045f5b68ed83bf0c7d00a986ea7c45d8d705ecbde13597b0e63000000000000000000000000000000000db7075ecc834f4959cdb7e188fafb405960dbee697a82746f478db752751f450d2ff3bba389104e5b285ae61bdc0c8aad55c357b7f2e245173617f50e12c9e28cabd7b5d4698083b5494bb06ee6b94f000000000000000000000000000000000f3d8fa3abc111f80330ce55ae9318b6fec02b352e6d1d255449eaadd7cb073e98c3c0d8eb927c123e1291e4fe3fd21a0000000000000000000000000000000007444b25c17970b80480504eec11391f135bf9a817c39a17490474401bae058b276da0a3e40524aaa202d20ad9371923f67af135d05146e92f729e3176fdd93e17f5c94fcabc5ea6d93ec2872ba9202200000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7dc430190b829e32a784e3024f0a8724f62d4184898b422e1eaa6a6682a8ffa5ed000000000000000000000000000000000933768ebdc9e9a441da03b7b9a256ad6feb63944389641df09c6a17b73032329246f1d0d6bda314fd240c5edcf763f4000000000000000000000000000000000e27fb7f7d159ec8a2936877c69de1018ef076da27682bf900b67ef6fe042bc4f89d8c209d93f49cf8261350f8e3fa5368baff019bf1f9b24e32cce1bb680db6983d6f2b2e60addfa31d5ad9475238aa00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d494507004a8ae79912a11252b4b2db7e498b534e338450981ced313193d9687c000000000000000000000000000000001197c02653a12f6af36025306b69c89df784bb7894d505621f05d7f1f6a69e852508df91e3d8a370f4e8cc48ab02d1740000000000000000000000000000000004dc5b0344470d1d4f41aa663b33cbbc1c679ff7a30dfe96f99514e9d99b94d7119ec7e93aef68bee8ff883401e7191036da67891846eab25aaa711c5d03b89f0a5377bcf5470bc4ecb1434e6d7fdad400000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7dedace0762debb30741de59f071e6ee17f2d7ab3d390b1c9eb2128326204c6ac3000000000000000000000000000000001579c41346af232eb0717e393790cabff843cd6c50fbb378900009506ea2aa44c9bda06ac22508fd90dd4b1812c5791100000000000000000000000000000000034c6e25a58fbac1a5e023a5f8a820eef2539c8ef3f0dc9b3d9e05044f72eed9cce1957257364dbe961a2368f6f4a2fba1d68cffa664b8a4fc6612f5088c2206666218627b5a3d8cdab267ba61b6e94a000000000000000000000000000000000f6d092d1eafb6707da09d754ebe75c036b634ab71d45c05b02d087eaf9a4653c44cebfea4b6077a7d27c136861745fa00000000000000000000000000000000073be5a3badb56736b4fc10f1da61a512cb01b9938f613c24c3d3f3bf92311fc96b112c729e4c15edcf5929c88d459a61375f570e38bcaaee664681e407e7f987c82053420837154cb5a6728e0ef94b800000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7dcbd879144a71eb72e27370ccfe3a7838e7ccd0d1a3736363d4db8bc93b8823b60000000000000000000000000000000008b35839caed21e951fe50de3cb5a2c1b1666facfc98416258f142b3b60ceacb20bb0d08e3ef36f9d4f5de717412a2bb00000000000000000000000000000000199763b4f8dd148df64a832e2f23565a715a4587c5cf43fc4a9275952ca7d4a7697e395f130424e97f1f86cbfa961be5e666b68b60b59b28fc886334ee9dd129522d30b920bab308529666cf8ff0d76d00000000000000000000000000000000182c820bcb67b1498243b455e29c1334e4a3a59eb6abf2933d8ec05df470bd8e7ee8d332a209f1202290739623aa26f6000000000000000000000000000000000384ad81bda50856eed135161899d096fbebacf59a0d02e7a87366ed0f096e3bbd1eb4fa3281fb8413259a7d2deb162ff6b2722416947db59e49f3b1b7160356e36ebb1963b41ccbda7ea93b62ca483b00000000000000000000000000000000001b26cc520f5c5d0ebac51d8a24106c2da9131df1c53c6b36b81b2cccacf3ee81b55292c06cde3008225d0f8a69d8db00000000000000000000000000000000126cd4e992718f3b1b366b1ee4459a16bac8ed2a814ae9445c30640efefbc7cc01c3d1fa1dcfe2a7c727fb869f46e1b99c567427455c4304a89fbd4fbdeec02d5b1cbfe0404bedd35114252f94b82a6d00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7ddf33607ac4386eda9cbe7a68908739c910922eba2b1cd119e794da621196d3aa00000000000000000000000000000000115c394d97c1aa29dfc3c5c42700dddfd435d30d753ebeb8db7e59c9ee129f8fbd87486f476940c958f127ecb166c7f90000000000000000000000000000000018aa9556e962f8af9d5c4db3e10a1f6c9603345a5e155944702904faf70d972553c0ce6b58df3886ad4de85069fc559b317185cfb9f04c5566bca6bc7dc2043ae8744971331180df35320710e25b404d00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d4e2e31f85a0c9145eae006fb7a37c31d8455ab1819d22ed08633b61826a110f2000000000000000000000000000000000b3c0cdb73a3f823d61c88327100dca8f06dcc5b275178a9fb4c7833adbdf5a6b531e8a20bae515a27542f4c8327832100000000000000000000000000000000110efd582f71bffc5866731c388ee69d793060d254a2b35dd35a1a5894511aa1c5fd58b20335921f4756e192f78fd24d618405deb577678c50402b1ab3054f9d74079022abe527279f459da7e464815700000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d67d8a868ae890084858f063477cf8f8e2463415841530281cb2ee435042ff86400000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d362802543485898cfa1f4e16eec307f6f0d2a0a49755573abefc8805ebf05d61,0x00000000000000000000000000000000198b43bd45d1858e93b542d86515454ac355d15448f3686d53346a73125bc3f356bc458fb1a2039f79bd27802bcfac1100000000000000000000000000000000180dd0333f06df2ca759049c18dee700ffac00c28006813df050d775219fd2ceaeaf971f57268c8d400e55a975723311,269352, \ No newline at end of file +,,,invalid input parameters, invalid number of pairs +0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f570000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992feeb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed24d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be10000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e44c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d38964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b89000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f90787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c944000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1baaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e10000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442adac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795bb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd1080000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a190fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f81876720000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac943b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e3b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c760000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276bdd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a57876817010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f76894c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a65905400000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f42000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931db3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746,,,invalid point: point is not on curve +0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992feeb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed24d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be10000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e44c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d38964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b89000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f90787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c944000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1baaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e10000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442adac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795bb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd1080000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a190fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f81876720000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac943b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e3b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c760000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276bdd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a57876817010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f76894c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a65905400000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f43000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931db3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746,,,invalid point: point is not on curve +00000000000000000000000000000000075321084f4251d524581318d27b73a8de0b156f502d8f410b3b4529435bf9a14f609da9f63ac8f338fbbdf65ebf270e000000000000000000000000000000001925396f4b5c00ac49765e17c601bd392a8a96d8d5a65e2693093e6d5699610ad699d04e083f8b248c808619d01c33bce9478f1e5d80ad0ed31dc0bd5234bc41957cb74b63ce59654181c13344a5a3ae00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d7d0832bc939e73d01bbb419c98abf35cfd0224f72f6d215de379bad89aedfd65000000000000000000000000000000000d3423a536fe9de27b8da74c6f82fa3fae76945d0c8e50b8de889aa0d533ed1d5b757e918d0eca5b6a8a3b34f9f1de1a000000000000000000000000000000000ae42b8bac10e922cb19169f5d91d50c249e3d81dce761cffb7cd652bde04ca17dcd0ad185fa03b722079b69ad4f4cbc3c2130dfc2b39a0e20abc3f883b4e329496dbb8bc79ea7f30c909140c4b3cd0a000000000000000000000000000000000fdb0c5e83ecf0344a69900d1a59a21bd32f4210bbb0194be6355d18433a5449a81fd2c195e353d844070ed47c88bb500000000000000000000000000000000004c27bb460768252e5d07a952ab5d204e479ff74489933ba64f80ea18799fb937dd6956112872024795eba15f48e60073590f744e04374b92ddc43d0ca3068973b5b48c48a70939fd7d047ede75fb8430000000000000000000000000000000017ff6f3ca925d18ab2afe8c39ac69975769fe0fb6980c3c1c4c678deb01ec4b6e1f5b212a1fd5983e06a372be394c52e00000000000000000000000000000000131e1351e7a7f5fcfb87dffe794934356b36ba49c7d35b99281577ac078c990576a4db6d2be8a45c562a8fc1ec0620e37fd535426244e27c586fe8e87eaae37dfd851601e0a2b001d86f094a63c234f5000000000000000000000000000000000ee908a40185717063897efe710f3f1e929dcd53db503c5e3f0d08bf0c0edf2d68a5958fefd6c0099e9c755dc9407e78000000000000000000000000000000000aa3806ad71189cc119e6d88284d476033386ea4be2f7885d5587e0d991ad88590de58c782ca0bebade6b4b111f3c4ed54e3b4eb4126abb9629e0e5144fdf73716c8654ac8ce827a18f432b5f340e53400000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d9c5fb237ac8e79e271008eab06ddc9ab45ebaba255fae6912577d0cf1428545f00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d0fcca884c7c01ba517c241e44478b35196d441ab3a76b7f023e08100b45d77ff00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7dd7f494e1b8378362ed31b0287c1b5f6912a677abc0fc392234818d4242e0239e00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7dba8eae358d75a3dbff9e62624ef6b1c641a1fc6a31c662f13a39d2fab938b519000000000000000000000000000000000f299ebde6e6c7b4d403b10b04832dfbf65b8ef9bcfebfa5352ee3df4f18e43798a0633845822a44a4fc7ec2ba1d13770000000000000000000000000000000003c886084611cb8c95fdb4473bc00d4877dd3ca10ba40765110400c1200d0b1f4cb9b1433e26d358f78d93da63ede6eea44e84f65a823eda9c7903fd4178c9e734996be8f291949341ff2e3b09ce7605000000000000000000000000000000001812e8d91771d93bef2f087058dd26f0c1ae487de050dcab1510e6ec444473f6815d8dd9565c18e777cfce1c836a4a0b00000000000000000000000000000000041fac4c713ba9727b0d5d539ecd45524285a2c179e7620dab88bad649f1b9b40dd422ab2d40c4945bc27dcb6e0a7a96129a1ee5c960052ac3051e4e663ff4f3c83b039dac18f25d4412a07dd9b164f900000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d5ec8616e3f6fab8a437053de12816f4ca18c1b4d42131c4800904f0c551d8d3a00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d0dc25e17fb0ea75c07be8123108c626000191fb5106f3e1921401acb96dcd710000000000000000000000000000000000f59f9d995d77fa65c22d5d84f277f3ccba50eabb8c404f1f9d70a39cd289b37978bfaceea2b987712507d88fae2a8f3000000000000000000000000000000000a4d6759c576fd8f54a8182a8d1191e5c010e38e33c013603b480cece6dec5cd6892972be8ee1c13500c8a601eee5dd352c4962db75fb059c2fe525270edbe9374add7df7b53f79977c9e51fa5e8952b00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d7cbed522a1daafa00bc82138e08f1318b629d6409bade3d0dbd1431dfa6d60f4000000000000000000000000000000000809adeabe91b3d8173fcfb1ccb80dfb5a5ce48160d915652ff193e6c79eca0b6d9bdcc484a37723db4fcd3c4463ea9f00000000000000000000000000000000130e0fe4001d5995012202bbfa42584ed476ee0decb95122a9ed61364059d5021f8ef232df6276f93ac38cce8d70ffba145ab6abb85976ae17d459441500228e246aa214cd015460aaef6d8e5e02373200000000000000000000000000000000083908343f7329e4e738aa6de69b4f1e275d41243cfb30a6b9e00f032c0459fd005c43efe5a358ef6c0118b1528f3848000000000000000000000000000000000d39c55cf915d530de551ba3bf9aa147fa24f83d51656aeb63866c38b4f206079ac4f89c7f91ae3c37de09d37713f75dd98cf9f26a404e252383a4402c17de293857da5b7b8fabd9f4c59e5bcef4c0eb00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d5a526d3ecc90ba9a2fe62b0673397d4af02440f44551a333fdbca4e2ea6ac0d300000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7de9f0448dd229d025e58f7afb4912ac9878029b29002fb92d4259f815a32916400000000000000000000000000000000005bab5aaedd0f007d0c3e29870e29c93d34f43c26ad57281c03bf273a5e3264fb898059addba45c54a7fbfcecf83e95600000000000000000000000000000000150fb89103ccbd4976372929d3069a0cff886103b8e942e9911f12fd3809003dd5b1aead983c0a3f705b327c73a7aace8e0fc676aeb9d7bb302be47a4efc9e5a6217eb557d6c0d8f37ebe15cd80c170b00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d66539fea2e9ea8285538029a644c759190f21bab23f0e98effbe59b6b4af2c5100000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7dd6683f094390667fff0a92fef389fd9ef0a77280f90f41660bbeda517219e3ef000000000000000000000000000000000a7c61050509b59909bf23a56952fded8adfa4acd2f58b5bbbc0253872dd4d542d27ee23a511de762f21f81c5593ba75000000000000000000000000000000001264e5a654cb3d9ec931f9db10cdd851360958c360e53eea634d6eae8d9922e04e7f87b4cfe99932feff5b3926f6e1efafcce5a813bc622c601c3b4cb300cd413b8b0564793556adf1189e42722ff810000000000000000000000000000000000fa6770b293f6b74109947469ac2becdd55879467c8cbbcd0451805c3202e0b6978e43c421ae07cffba10dc37cafdb3f00000000000000000000000000000000060dfe71eac6b8772bcaa2520d343d56754a7e10368637a506decee4ad549ec43d8c4966b436e0bd2c1e270fc466d3a63ee43425efb56b18114a8fc59a63da39e30b7610d02ec5075cc391db59b875e600000000000000000000000000000000000f6e95654c7f61ba0dd368000312c5ba02feb2074c84bb7790daafb3b6c2a3aab5620d43fe3818d7ad92905992b644000000000000000000000000000000000afe6028eb4c49a8bc331ef714f1e0677d7623e3e36b4b3c451e7253ea6ebe065749e6f2e2bf03718ccb067411e2c7fefc3649d0fd8f7fbdd48ab6f6fd11d5fc593426464c32f09f3515948519d99d7300000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7dd824e57eb10a192bacde897180beb87c5559e6df2512be8df9cf043ad24b1e8c00000000000000000000000000000000119ca5f3fde1801e2cef526cd4e06b1230bb4b7dbeb43d34ba059d9bc394e95fc94d9be60ea972375d66b31c4f0482140000000000000000000000000000000007f10bfcb3ced451329af6fde9e153845fca20c6f5abb30a8ae2543880baf3efe6b31b3e7001700332bfc3e1616738b7a8a514daaaf533057c54b86200a1f8d5cbdacf8e3c2d9eaa921250b6387f45a100000000000000000000000000000000180ea28fad486a20615b934640aa883e70ee60c47c6148796691c58c64d03cb80b3802256756ef9217473837bd7bccc0000000000000000000000000000000000977ddcea657c79715e6d4ae801ab6e63a7c2688c0852e229b503dc951bc9611f36a858450614204ec44472914435643604cad22d4e29aab314523cdc14b691e509f260423d28e6f176eee3b659a164100000000000000000000000000000000195c0894c937d13b865ebbd935e4c609eafd492d57115df5f14df86b3381f7f9a111a2122d79d80980ad201c138464f5000000000000000000000000000000000471746c7c57fa94c23cd5978db9c638cca6617d27c445cda89f8298c4cb998317b8bca49f4de36e1e0ce24947021dbf7b1893649115755749a9f858ffd8484d7dbcb04bd2a3ff91a3fba86fb44ab69c00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7db7930da64294e5949772522fa980479cd419f5014412f53a082e2b35f537ac0800000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d61abf3d5e8d8df951747925b485dfd8cbdbba2fac2ef5f72a6311f79e89d406e00000000000000000000000000000000056f92baa9ef5e1e434fb70193f924706489968123ed61b8c9938c695852ef681709553c56d979f0925bdf1a8390a8d6000000000000000000000000000000000841faa9c8e27b1e94f2a5c14988806d62d7f35de74ba9a7db0062dd444a19b1a893ef6ee2d1ebefe3631d0ee0fcbca35be3179cb22c10450325b3609d9c3366c5200c1936280b3df162c48a213dce68000000000000000000000000000000000a2398e644fb52d53f02a583e67b11e5c7ac5b8e18e96498b9bbe572c3afd4dba8cd978db48501f5cc525e6c13ce92000000000000000000000000000000000017122a21cc3b7551a787fe260c1ed80ea34bec22d3ebb67b86dd0f2f95f50db7f577732c0d0e020e1ebb4295ba9a9d2a17ca658cbd9f48739b0076f97e4b943276f558760ac051f5c67159ad83bc94410000000000000000000000000000000018b172391adc5b6c44edc54c2bd874a01554be69d848b317fe845e62282d82440ad48c4bb645553bc75a701929aa9de0000000000000000000000000000000000753f19cae676565e45b13141d12fbefdebf6ea7bff7da94ace2af135e1aa38a7d63736de76bbea093251eed8755d6e3c3c4edae42b946fe140e511563d1ffb612a0d4fff66941f66007d0ac33c547840000000000000000000000000000000015916c0f96897e0e2b43bd76a98608d0afc276c1764f458f83db94fcf13adb05f440df55f3773bb12a3c25549e57d09d0000000000000000000000000000000016c6bdf76469f51e0b5dc0f9f2b22fe552e0fd0bd7084cf92946fc4d05692ab32690196a408a87681e32237cf172f5eecd8945187d434ffede5a94aeb3fe3442aba582787eca04c0e17d640820a30399000000000000000000000000000000000ca63266caaa56c1ceb84c922afd869885a1f33512fe59e1b367fe3526d1cf47f96df769d2f7708e1b693b528a28c94100000000000000000000000000000000027dcd246ff92089013faa40b4211e0bd0fa5777457668e57c0142561847542308a51d192ac5bf603ad8aa5119c6a5bc0ccbb0d0ed90f9bf2b75879861eb24b1170c9a83f5cc479beb4cb1e8835e77f2000000000000000000000000000000000d91133d005482f27cadbf8eb2d4a90952cee2d8b4b5a49dcd79a8141bd3cc04d4721fb3e62da217a08bee294a879ef50000000000000000000000000000000003bbd0c8be4f936ce1fa7b48f9158405e52a819cc1d4bb1fbd9f309698f8cdc1b2e1a5554eba3ab8d2e92f191274ac8cfe435dd14091302d34191d6c5028c3e62ad016f5a5b6256f3aaf82e9606f21cd00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7dabd30cf71788f338622c21204603041353f1ca6885660be7ea826e00321c262000000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7ddebd81c66b87038940c72fac5d33aaa039525bfd21c79af3e4ef46ac5e1b274500000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d6b382e8936d65c5c6ec4a52a5e22d923ba82072682220a6a30426319584fad480000000000000000000000000000000009edfbf4cad38d597d63663abd67d6a84b5c54dad7b7b147d791c41f9316923859e6daed7e384d9049b0a2203f08607600000000000000000000000000000000168806179a9bcb0bebd363fed37a5b9cc1e8b66d270982c31ea5573b78236f75d36b04efcd2092d7684a18c13de83c512bb927549b1b512c6e6cbe1038bed00f074deda5c63a2367ffc60c5ce7b8afc500000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7dfcfb7d1fdd47912832e10e1f30ec146c120fc68ade3f3ac10604ce548847a83300000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d1fc561e062ab6a6b015fda81de63bdcff89698bd2a7445d0c21c1b8284d8ebdd00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d385829683b3cce18867d07afe8c02723f906a216c7d6d59d5b42518f5999158d00000000000000000000000000000000032d01e694621f0d2821bd2a575424d11cb623e65c1bc2438392ea57f08936e9b98eceedf917f4e51afbdd30dc8c24ef0000000000000000000000000000000010d8d9f87a3c2689ea9ac9afea38a0b1b31d90f0afa71957b596cb89a9bdd414dc2954f1cc747fac377d318769467dedc7839d6bb24f8d23caddf1b6f0895a5a6432fa0843cb313c5ac57e81ff816c6f0000000000000000000000000000000015ccd632042331ff5ea5fd21697810f7c3cfe5eb1d6e7afd9f850e3fd8e5914bce3d548cea90a60ab0ed27e24e72464f0000000000000000000000000000000001d027a23c9de769d155f06b81469ad9d3ea59a93d40388215c2b5c7438fa10e77da03e26f1010dc7a225d179b9298f9852544eb6963fd0dfbede6d8cad90517c2c3dad1ee2f6c49d24d8dd155ac977000000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d5f94eaaa4947d5d054b54792044ca1450f25da28df35db7e61b7d9efe55debac0000000000000000000000000000000015163cbe70b4f6a2d7d90a8e48ac6f8812dfc7c997add0528f17a6f0cf42faaee56b13e1e95d5b8f306696a41f426c49000000000000000000000000000000000f55f240ced8ad609c288edc4b2a888b84b101aa39473c3353163b501cd888e07d42138e5e450f16d4e1ec0414b5d28f3bdf0bb67b4d2551ea7144121412d258383ed6cb4abb93b7b56b127dd384296c0000000000000000000000000000000011db7b6d983443206e5a7962bf7e2113ae463531cc958e84296c96d0c31f4f2264c8392bfb85f164500d6b872e7890340000000000000000000000000000000008d396e251da387cea196f5b7486788b3c03240451ad437b2d306f3b6ecc6cabd7e2f4111f7321b64022d2aad51fda460aef6a4bfefd1af38ae88ca70324b583e2aaebf559301d7505db962ebae2311400000000000000000000000000000000002b319cf695dec2b8da3475991a774e1c1b01421b0ebfd11297673e0eb218448c4271e67d23bd0ee5be867126091a440000000000000000000000000000000008549085ae48aedb3c8a4bcd271a9da39fb74f528af94e588c8a0a6a2e76026f2711aac32601e944d9547876ed23de7e1a85351cd8c1e3175722cbd8f6aa3806e0d9361f09e70dd03771b6b26d2f93de00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d6fcd55de20c3131edb3e85f6aa82f17c5fe5b93f92fa3fd596c833549824ede500000000000000000000000000000000142095eaf85b1017dc314db46100baeb29170a35f429d1563cd7aa6714f6b1dd269ba00be1a82294b0a508f7f038e0d4000000000000000000000000000000001558399b27afcccfa6a89cfd4635a4bd8c87d7ebbb25c28649f24f90897d7fa54c207986dd854f156be69a6eda586aab431309767397001e1e32bd73f685b5f54076732c953974b446a3088f54c9e3240000000000000000000000000000000019ea6e73637995387e7c2cc1a0de3d34953242a8dd3139d8a5886863cf21249e8716a8394f4883a2af632abdd15998a60000000000000000000000000000000010c0c216f747d186300934461ed363744aa30be9e0dbb390d362cd3c71b59728b124a396c6e186ea63aaaeb4d2a454d7d92679e9ce2106708d5206d408ac3650d2046a51eb79eca4dba9416ef0f3527200000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7dfa01f7cf41925a6bbe60b14586586fe0d4ba146c5aa6f02c14d41cd791878554000000000000000000000000000000000e7a795c2e6d4ac210d0fb92e94cd76ff97193ef7bd79446afafa7185df8959389801ac16f846360c8509bd407d28da40000000000000000000000000000000013e260782a41a63ce7c4048109fae191aeb7448fceb79e06c37223de72ded540931ba81ecdcb46423bdc7d564d27ee0aa5138547028a8868888087c4abb44f421757cb5583a6eba82b58504655162463000000000000000000000000000000000ca32c6c4b8746170ef22461dca8893855af15fdabb27a293d2620a20088f3892018e3c10dcf80f67f5227b14a808fb90000000000000000000000000000000001e88d80946624429dfb26f20236fe03680a359f1522a9075d48f27c84de9008bb558a645192bded688197e55b29c1cb5444536040fd8d54b8497d86600a5ed467a4207dead453c71781ce03552fc18500000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d49e2561d0364b0069838bf24bd021d1e4a95cb0bd5aa7a94a506189f4f5ce5b000000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7dcd2d6f115ac393d6d5733cd0f6e6d3f0231cd825bf899902c50c917d0a56aff000000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d59d2bd9bc3e12e5d50c9e4c768c2f8c65ce771a11cfbe46da73d80786e027b58000000000000000000000000000000000fdbd45fd634a2f233ff8ef90d5947977023d9ddfa69f3ce6b1dc6abb17bcdcbe97bc4be5059bfd2ab1aa91d17821024000000000000000000000000000000001435c723d4563d17a9c12090347929487409efb3d29119ccc967a6c5e9cc00e20e537a3e05e385cd8efa555dd3f2b722ff0a56d2dcabac5932c5d959f2f036d9ba5c71cffb7e686d319bdc8d1e83093f000000000000000000000000000000000be1163dbc97bc79f8434c744dc0e2c638c9d524903738ebea3cb86b79438ea2fb0238451da6e962b412be9a0e7611f800000000000000000000000000000000022a59b0eb1ca7d35ae94bcdc64f50b443260f8cf7c8003d99c3ae198a7c2fcbee4653566b485f33357d8eb1a6db1511f7adbee3c265263c30e3662a1bd35fdb466bb3677d6d09efe99cd1a7a468548300000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d6448a9f68973305afe0622c12998037252fc8183296ebd0f29456159b8275e03000000000000000000000000000000001947d6e595915bd7ae4e538306d208a2d312de5de42f0d3bb735916aa741f47ba44834b3965f2aa9b5405c10c2b6b992000000000000000000000000000000000eabf37c5ebf82bf038d3eb58b72a5a584d69ce3e7de1d9668ab5df2abda5797e567321ee43d1aaa15f057048567f7c6f94018c9aa0307288e5126c736b8bc6f270836028ff850de60261bff338b7d7d00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d7037d0654ed51af22b5b324e23ca1065dd8cd3408a675ec204cecdfe687bcd8a0000000000000000000000000000000005d727a52fe6676e8508bd7d3a27c79cc4836d412cd5720063ea0ce5ca245cdc6dbd6fdaa26ff502d3ccc6e27701395e000000000000000000000000000000000ffa9dc48cbbf1ba32bf5de2ad98e8f4f1842f1e219660c00ef9702a72aa1166d46b9731b8c80581a795c0a09693cbeea5616463a00009079c228b8a92059926e3e26170c7bf1514823eaa8bc0e6ffa5000000000000000000000000000000001220a1e08ceb679bb165a3c9be882b6429c36ab83a7324ddc07ef7f2d0a4408d741e84791a33775ad4565cf816a4e9b70000000000000000000000000000000001496ad90c53c27392ee8849601aaa5b2c89700112a324864472c1a8d49804fde78e761adfd9c7755ac2d8d7d215c0b53c1cd4a2a7ab5eca1ddcda8887fb2aef01cb454a97bd39f2e0680915be270b970000000000000000000000000000000012b40bf0cbdc9e374ac4162b638403109a79803a726509f9326b85d3f2b20dd2a09015c30a2bcb1156bf7bb888f010c0000000000000000000000000000000000f3bb973815c39b95fb1398d80ebf1530060e31495a96c736b0235c78c5e041ff69e2564746b07d7a270bd45258e4aa7627af6cb2e8a90102e79d2cb19aaf0b6c18dedfdc736894732c7403036f0324600000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7db4371b02a3681f7f8f53acf22cbf8307d6e5276a5a23ec74fe02cc7aad62e98200000000000000000000000000000000007c2f86b037c3a7b67995d96bf4313ab6c20f472994372b40b78a3dfdba4c7616ead79026738d0ec398364806a57189000000000000000000000000000000000e752c8490fa323e0c74f9ffdffcc9ada27016388144bc95445341a6eaa82fa9a10d59c11bd8dce3b9cb0489ab1a5447ddb739a79265fb03de61859590978af384fd44243aa33806a1c7a3e20f37365500000000000000000000000000000000048afef5dd0565942320a985cf8d555408cae3edc40b7a5456ec86308d5f11db5592fe37e058dc6bed42346b42fc4abe0000000000000000000000000000000005e36fcbaf73026442a94ffeb56f4d05df46b90e3e02c3fbcbe3a32b1878edc7f6a5e57dd9f6ba64097c41e8e7ddaefe24584b142d6180187bc728ed22bf802d01db715ff85b51872991e92ff1d70f500000000000000000000000000000000008e3fe3d42e724fafb0501e4d8f2c55ec35bf60c71f6bd31f3d4b890a0b418e00509b8666af4207c8db44b30dc3828b900000000000000000000000000000000122a0dd7a24cf1e42ac8b8795ba785efd1f03f4753985f4071f17c4b8be7706fda8f900364c1676bef4e20e5797583b129566ac3f767fdce83ea91a295d06984d48763136f977f5e7e4f013ce274923400000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d92af1ecbc9eccb56b3a220398ad3a3885cdfd3f02c9d302add64359bd121cc3e000000000000000000000000000000000e7775462ebb7f1ed737ab730e39ef8bd089222d3d7b4ab904e3583f898739d6a53c1d359d11017892eebe6ed9917253000000000000000000000000000000000d239831ce4acc58fa97e4d4cf56c3961ef313b681ebb9a1e221900e55832989efd1b022d9ec807019dddf73b1a6fdb2a407193f74d08ba9ed5d971f0516e7bbc7f89309dec24a4ea5d2a1f3eccfa5c6000000000000000000000000000000000c5be42126df5197c04f49e618a3fb6b8ff17f90b5ff26db7fd508a0723c4f479645beee629e02c9da60313b19bbfcec000000000000000000000000000000000e7fb0e4699dbc768d51247f5d7030fc698c5875cc9c5b326f3f7a4315c6fdc8e5095e91413dbd2c9b40316645275b3cd8fde96110b12c91bb5a9df196e4ca3c169710a6951e4dafcc2803fea31a43d00000000000000000000000000000000004760b7d9e28a23216b7ad3ac6b95c094e823df1bb05559e1871d9598287ef2f48f2858a1929fabb7ed3d7372a8bc7e6000000000000000000000000000000000f766efb67b69b273f9e1ee0c1f934231072719039e642892623f869b05c3b7af0b18397e76a25cd9f3eefb4f04935a54d02b624a8c2cca31a05cdeda3e09ad819140fec582a88f266291ee6aa1f1ce6000000000000000000000000000000000f734e6b273c6b05e911a4441c3740d13228917fd0916dfcc696930ed1c2e70268ab98d764507e68fd52826bdddd64e2000000000000000000000000000000000c20234fa85fd9eed8b148e79aa833443d47384590788f0700ccdc163b5d50dfaf5fbb901e7802d27a3db86f6aac06e91977c7a5be2688c808528d63a275fa8d18af14d7e90aba7edabfc821f4ea37be0000000000000000000000000000000016f5c2679ac1a70cfeddf38068bb85b6d35de903105a14b3c4b3d00e6f57c8b6c6de7d55f2b697617a4aeb73cbcffc090000000000000000000000000000000014ebec9ccb8e6052a009255080f2e0bc34eff9214e911bd6c4eedb53022a6defd5f0e78c9713c6a2e1f110152ad65b941a5267d9766e7d84ed4693f15ead057ef7396903e8a5f3a55bae74f044368a6300000000000000000000000000000000189b31702e6d365104d661c9c524b2c8d42dff369366d125453496e434f1b9c4f46cf3923afd14b50a4e06e4f73b5f8e0000000000000000000000000000000009eadd06f6cf208c4c70fe079cec8191553cc8a55321ff0a70f818d1cf7b5838292ba5eae5298f9b842577a7cb0ddb4e89246920765fec2a411849831114d529aa308111efdc119e85186b960ea5456600000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7dc088cc74c109957af3548bea8f72505c8ece5c1b690c14dfe958b8faea470aa1000000000000000000000000000000000334aab374f69a9760f58431c6dfa783f71908bb3249f03d05272fcc6b5548dead4791e86d630231133c121ffc10795d0000000000000000000000000000000013b4f68ca1144b9b2723930cf63c5b75fbe7a09997ad6916b933c5713e5c52e918e4a0876e5a3fa282a87335d9c1cf4d6e33049a1bb4429d216991c6aaec7a6601723e52aaeb4fe9dae31b5d3816e93300000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d44f89ffaa24457b265e18cd544a4107dcb6f3d7f3fd39842a3a6f40cb80ef8b800000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d39a1a26daf737fedd3f00aa08e7c55933b70060321ff2d862df8d3f2216433be00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d792f9741bbc394770f02b271ac249e66e056b225118afb58e804ba6a64d69fab00000000000000000000000000000000026ef7ed98b77106cc9e97e418086f844f2fa5db62484336750db6b140101657350dd20a29036f31b3b61a0a6292f1720000000000000000000000000000000007253b6377638195eaf42daaaabc451ac78e0707f14b12523ba5cfbf5ecb146b8fe1361cfdaf76b7b7395fb4b2ec7733fa4e4476364dad37551ac06e221cfb959094aae57733ae80c00d2213a68a841c0000000000000000000000000000000010213a60637cee478d92dc83b85937f51be6878189ec58c491959206024a726045906c47f39c89c68e8109ac8a09124c000000000000000000000000000000000a915b174003ba76da253296fad341af125a7b0d30e541c92cd25c970fb5d45993c516124b8535024c74d970f12fd7c1ced1ad2563403e274800e80f48fecac97498e58356f33ef6fb3ab123ce15f5d700000000000000000000000000000000149451bc330ffe029b198feb8d796fde5341b0691d0ea058d9a29ae1de78740e3906fd8cbcfc135caed16c03ec8b0fb500000000000000000000000000000000147a09c04d930602218988f7a101cb0572e8a41f83c49bfa6b92217d62a7b817146876b8de99f984cdff2f1509762580bb49d60ebba706d3556aa7ada87267ee3220b7f0b8d333aab79d690feba308710000000000000000000000000000000010d8cd8f975822ed51b18ed332fa6b695e0ab0c884413da5a1f77f0a472e06a257323aae9a3a8233dde0f3e1a797dd6a000000000000000000000000000000000f2b776ac6bbb05bd2d18148df50f3cf9ba3bddbc6ea410a537a65e0bf12ced5dfb8fd9a1586289bf9ae79889102d1f9a8d81a8d537651685cad896598a65479445bcab815793de88f399a7e2989c2e400000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7ddd130785a081c481879877abfa85e66b3b39cf81451696e7fe7524755d5bb0ca0000000000000000000000000000000019fc82c97989866d4e50f03c018dda0c6b356851d014355862a243ac1e2201ae248407ea895ea051a87249518eeeef66000000000000000000000000000000000d7c34b2070466e3107979f43191c09bc5e70ae6523fcefaa5227d04eb6e51de5d9025df74e2056e5e976a60a8b8b612f96d861e2c9a2b8b47f7c6f6f6c63ca93eb83bf5d1995006a7c5f03c2fc25d2b000000000000000000000000000000000928e3c3be2939c1da0a388eb4b55a436a698827c7979d34ec6a0ad3ffb9e23f112350c0b8174f1e52b2c53a8de4649100000000000000000000000000000000123a719f66a3f2dcc8ec1b3f130dcaf9b4e1e9124ae6807c744417ee04dc7464bbf1bd7f6076257753061334332e7192eb64c8c2bdbcc472ed8079a295888c1ee902c948ae48d2149d37815537aa538e000000000000000000000000000000000ebc060619caa0db6ea4c47bad6610802128d2be619173223a9d0f84d901f147db4fdeb250249759487bc727ac061c66000000000000000000000000000000000c44ce1e0c8b3f4e2098aaf2efe85cbfb689fe5478641ae53cd5805924ded81d586644c3bc49a389b15d837e2d4fe44a39f9d35b3f3b4037baa3bb79f7d5e522b9c1acd434fdaac61c3f616016b3bd5d00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d91b623b6ee1adbe3728463c29c39379a29809cfd7cd0027835a271a7436f470300000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d4e2e68c5c52d84b713d3f681fe6d06fff398c110215895ebf9deef50b2a338d3000000000000000000000000000000000cd89c9fe1298920067f1b37be297c5567137a07a95c56dadb2c04f3993608ea498e89b684b8262172a1081505d5c60600000000000000000000000000000000073204f3cbeb83a5189fe1df5f501241e2453afa3220c38776590862681a771cd5d6cef2946228c1416eb3c1d840bcca8e2be1afacb2acc764b129c6c62c2c35801b548f424c4bd3717f6302a44b15b000000000000000000000000000000000177d5d25fddee9f1923b4ae21ae91ddbc4e55a22af46eb61b35e0634f7ec15a7d8bd6ca12be9fde52561d207d26406330000000000000000000000000000000011e9fc830a8865caf50c1c20853f38039c3b0aa956a75140ed699cd0e69730c6f7bdd00fe05529cedde1785a39f67d3f60cb52da68c43935c22b6d1b13b3c32b79f8939ffaacfb4938bb1747e3d31f4900000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d91181d5d2444d162419133835091288d910bdc603f405c6120679fae82b6e842000000000000000000000000000000001689b5ec4737d58921a131707b92fb7c5b16e084a0415bc5c1bdb5b9a6260327526da2801932b5775f27cad390e6a6980000000000000000000000000000000004b610f2c453e9cc73f04425487f65660f478eed19ed3f3e60aab068f53f2dfdb5a73b02e525a14afe7423f8850ee9fabd637ad431366f08e955799c79f6ea5aaf9c22faf5400e7dc6c249c0e6494c1f000000000000000000000000000000000c37eec1f1033b86368b0fe1e42d9feaf174a77c4a455aeccda479d8fc937faa3a521afc7a2143285e7496f192670242000000000000000000000000000000000ede805910da61cc6daf7b4d465ee83ddf1ab5097634933a70003112a20361cd9c59bfa77b1bc026e93ba6bcfedded5b6ac13b151ae86bfc484c2bc91d4df9a75ef4a2523b968fd31391f9dd0af789f400000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d6fb50ae4f785f5b5cfdd9aa004f08215d1bb6c1214d5cc5f027c3bc28a812414000000000000000000000000000000001259021c75f2fa3278b16ffaf375146d0ffac5477347ed1641886f703005940cede0bfab44fe49ba20914d580161326f000000000000000000000000000000001029ca636af85218a2a23d4e0b799dfeb713718a258c13df2731c4f352c5629a4d2b996d9b2b3c7adb73ef0a28217ae0042c2d53c35f3fd0005a977237d71ec6da2723a7c9187f1b9f9c86a5d77dc773000000000000000000000000000000000f719b8ee0f2d3c845323b1d4b5df26a6c118f705f62b5cc535ad3a9dfcda6ecee37a0a9cabc52dfcbdee23ee4fa76d3000000000000000000000000000000001550b4ca2ce312e81b6050f7cbb6934f05830c739d3ec8b84cf1f349fd740b13cdcea3f2939bac45bbc71317ee0a3c11cff78aca784067502defb619fce8c8ca407761d3fa78d635b21afed38aefa13000000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d8bd0c10194e4c8a032574824ab15979cd7fd2014dd7fda706b86d37a24a38990000000000000000000000000000000000d72f3900402e43a50414d8aba5dabe6a4ed9a2ce4df7f18018ed3292da6ce11f32d336cafe428a867dc3f75af7e625e0000000000000000000000000000000015c68638b97c79004b2bcabbbc309884bb4429fb7a947db7c0aa54a941574475b164151619c9bf5046a644391842cfd183158c0345a5f28d1577e3d9dc463b8a355602d25577e328ea82f987983e805100000000000000000000000000000000004eb67438c54c72fc064a92653c60b36d93dc4578b251dfd9fef885fbb71e7e17ce48cc286d364a9403125a285714ba000000000000000000000000000000001711f593ed6016095a9d01a7bf1081a2e279e837a35d250153ffc6a12bf2dce0c9819e5d0cedd8b20bae73414538b61aa03169e13ad2198817b924f5bc1df0f1db63f14df64d1ae24b1c43f18eb78e010000000000000000000000000000000016b3e588f20cb6b4fcfc37a53f57b4b446abc8d56314031db09996769e79ea63cf54549f0bd367545e4d17d5b55a300a0000000000000000000000000000000016feb1a66fbc5cbe44fc73e43735063100d1d5b28cb56a46c5c932111c08c4769fdea4a0a76caac26347148f50355aa0aa98c3852a24f571693d36ba7db3ffd984010e4084738f0b8b60fc7b6ac4b9de000000000000000000000000000000000ed8b7e1b18a4da705a76d23df1623052f99741485af9ddc9f501d6bb09d04356dcdd02e78bbfd8384c51b4156d0709200000000000000000000000000000000116ed2039d4a23fd7bdc56506bd4109509c6c189251ec9014cf9f9023ee810e254539c83f18bb7642a1dc6b41bf809c7f93ec9381604290d5311aa3fb14d124f0ba65df55f6ef3ec4a269edd870ff84900000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d3f6f400d9cc7bc131fe4e5549b6ab7d82436bf68b504b970ff712ca4c9c2c97d0000000000000000000000000000000006e236535f811c96ff3f9c072eb9cf5f87c45cba5c7574359fdc6000c40693080e7f61867cd5a3c4a65ee9c3d54a6e03000000000000000000000000000000000738e9c888e7bb7d997c4f981c492fd3a4766e4d4c585b7a03779b249331ac014697c89bc21be212ac499fd6ce0b35969e06051cedf33e1f3ad21f763979ffa200c056792ffc8da1b6f44af6eb227248000000000000000000000000000000000af956bfa4d9b6608adf999c46c2c992f8a3eb697692ea3ee630764b855ff44b350ea6de10a3e5e1f271cac23b235fe800000000000000000000000000000000140bd10fc8fd2e934494c9f7b00fe56028ce31d97345ef9b8b31ce6d297cec92c7d4507b3a6fee0ca39dbc9d451143c61e44b07f50d5e8d1a05c82ac2a481ac5e8aa7822db692a1a3fdea6a0506ad93d0000000000000000000000000000000014f9f27a2cf656c4ed0f94d7c3aee20dc0bc821921bfb045f5b68ed83bf0c7d00a986ea7c45d8d705ecbde13597b0e63000000000000000000000000000000000db7075ecc834f4959cdb7e188fafb405960dbee697a82746f478db752751f450d2ff3bba389104e5b285ae61bdc0c8aad55c357b7f2e245173617f50e12c9e28cabd7b5d4698083b5494bb06ee6b94f000000000000000000000000000000000f3d8fa3abc111f80330ce55ae9318b6fec02b352e6d1d255449eaadd7cb073e98c3c0d8eb927c123e1291e4fe3fd21a0000000000000000000000000000000007444b25c17970b80480504eec11391f135bf9a817c39a17490474401bae058b276da0a3e40524aaa202d20ad9371923f67af135d05146e92f729e3176fdd93e17f5c94fcabc5ea6d93ec2872ba9202200000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7dc430190b829e32a784e3024f0a8724f62d4184898b422e1eaa6a6682a8ffa5ed000000000000000000000000000000000933768ebdc9e9a441da03b7b9a256ad6feb63944389641df09c6a17b73032329246f1d0d6bda314fd240c5edcf763f4000000000000000000000000000000000e27fb7f7d159ec8a2936877c69de1018ef076da27682bf900b67ef6fe042bc4f89d8c209d93f49cf8261350f8e3fa5368baff019bf1f9b24e32cce1bb680db6983d6f2b2e60addfa31d5ad9475238aa00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d494507004a8ae79912a11252b4b2db7e498b534e338450981ced313193d9687c000000000000000000000000000000001197c02653a12f6af36025306b69c89df784bb7894d505621f05d7f1f6a69e852508df91e3d8a370f4e8cc48ab02d1740000000000000000000000000000000004dc5b0344470d1d4f41aa663b33cbbc1c679ff7a30dfe96f99514e9d99b94d7119ec7e93aef68bee8ff883401e7191036da67891846eab25aaa711c5d03b89f0a5377bcf5470bc4ecb1434e6d7fdad400000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7dedace0762debb30741de59f071e6ee17f2d7ab3d390b1c9eb2128326204c6ac3000000000000000000000000000000001579c41346af232eb0717e393790cabff843cd6c50fbb378900009506ea2aa44c9bda06ac22508fd90dd4b1812c5791100000000000000000000000000000000034c6e25a58fbac1a5e023a5f8a820eef2539c8ef3f0dc9b3d9e05044f72eed9cce1957257364dbe961a2368f6f4a2fba1d68cffa664b8a4fc6612f5088c2206666218627b5a3d8cdab267ba61b6e94a000000000000000000000000000000000f6d092d1eafb6707da09d754ebe75c036b634ab71d45c05b02d087eaf9a4653c44cebfea4b6077a7d27c136861745fa00000000000000000000000000000000073be5a3badb56736b4fc10f1da61a512cb01b9938f613c24c3d3f3bf92311fc96b112c729e4c15edcf5929c88d459a61375f570e38bcaaee664681e407e7f987c82053420837154cb5a6728e0ef94b800000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7dcbd879144a71eb72e27370ccfe3a7838e7ccd0d1a3736363d4db8bc93b8823b60000000000000000000000000000000008b35839caed21e951fe50de3cb5a2c1b1666facfc98416258f142b3b60ceacb20bb0d08e3ef36f9d4f5de717412a2bb00000000000000000000000000000000199763b4f8dd148df64a832e2f23565a715a4587c5cf43fc4a9275952ca7d4a7697e395f130424e97f1f86cbfa961be5e666b68b60b59b28fc886334ee9dd129522d30b920bab308529666cf8ff0d76d00000000000000000000000000000000182c820bcb67b1498243b455e29c1334e4a3a59eb6abf2933d8ec05df470bd8e7ee8d332a209f1202290739623aa26f6000000000000000000000000000000000384ad81bda50856eed135161899d096fbebacf59a0d02e7a87366ed0f096e3bbd1eb4fa3281fb8413259a7d2deb162ff6b2722416947db59e49f3b1b7160356e36ebb1963b41ccbda7ea93b62ca483b00000000000000000000000000000000001b26cc520f5c5d0ebac51d8a24106c2da9131df1c53c6b36b81b2cccacf3ee81b55292c06cde3008225d0f8a69d8db00000000000000000000000000000000126cd4e992718f3b1b366b1ee4459a16bac8ed2a814ae9445c30640efefbc7cc01c3d1fa1dcfe2a7c727fb869f46e1b99c567427455c4304a89fbd4fbdeec02d5b1cbfe0404bedd35114252f94b82a6d00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7ddf33607ac4386eda9cbe7a68908739c910922eba2b1cd119e794da621196d3aa00000000000000000000000000000000115c394d97c1aa29dfc3c5c42700dddfd435d30d753ebeb8db7e59c9ee129f8fbd87486f476940c958f127ecb166c7f90000000000000000000000000000000018aa9556e962f8af9d5c4db3e10a1f6c9603345a5e155944702904faf70d972553c0ce6b58df3886ad4de85069fc559b317185cfb9f04c5566bca6bc7dc2043ae8744971331180df35320710e25b404d00000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d4e2e31f85a0c9145eae006fb7a37c31d8455ab1819d22ed08633b61826a110f2000000000000000000000000000000000b3c0cdb73a3f823d61c88327100dca8f06dcc5b275178a9fb4c7833adbdf5a6b531e8a20bae515a27542f4c8327832100000000000000000000000000000000110efd582f71bffc5866731c388ee69d793060d254a2b35dd35a1a5894511aa1c5fd58b20335921f4756e192f78fd24d618405deb577678c50402b1ab3054f9d74079022abe527279f459da7e464815700000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d67d8a868ae890084858f063477cf8f8e2463415841530281cb2ee435042ff86400000000000000000000000000000000153753f25d38c68a3b92a486bb896a1b6ca9d4b81620a7a0040386838be7187e8ca73987bb260ee099793009b5cce1f500000000000000000000000000000000063f22c3bedc7ca5ce16db22f44a68a8fa529b2c6aab59c97e4b2353ed1b81da0586dbc0fa49605402da10c5ff7cad7d362802543485898cfa1f4e16eec307f6f0d2a0a49755573abefc8805ebf05d61,0x00000000000000000000000000000000198b43bd45d1858e93b542d86515454ac355d15448f3686d53346a73125bc3f356bc458fb1a2039f79bd27802bcfac1100000000000000000000000000000000180dd0333f06df2ca759049c18dee700ffac00c28006813df050d775219fd2ceaeaf971f57268c8d400e55a975723311,269352, +000000000000000000000000000000000123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef00000000000000000000000000000000193fb7cedb32b2c3adc06ec11a96bc0d661869316f5e4a577a9f7c179593987beb4fb2ee424dbb2f5dd891e228b46c4a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000112b98340eee2777cc3c14163dea3ec97977ac3dc5c70da32e6e87578f44912e902ccef9efe28d4a78b8999dfbca942600000000000000000000000000000000186b28d92356c4dfec4b5201ad099dbdede3781f8998ddf929b4cd7756192185ca7b8f4ef7088f813270ac3d48868a210000000000000000000000000000000000000000000000000000000000000002,,,invalid point: subgroup check failed diff --git a/evm/src/test/resources/org/hyperledger/besu/evm/precompile/g2_add.csv b/evm/src/test/resources/org/hyperledger/besu/evm/precompile/g2_add.csv index f15e2b2f165..3eb44926840 100644 --- a/evm/src/test/resources/org/hyperledger/besu/evm/precompile/g2_add.csv +++ b/evm/src/test/resources/org/hyperledger/besu/evm/precompile/g2_add.csv @@ -1,105 +1,105 @@ input,result,gas,notes -00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2df0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1d9f0000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40,000000000000000000000000000000000a9b880c2c13da05bdeda62ea8f61e5fc2bf0b7aa5cc31eaf512bef7c5073d9e9927084b512e818dbf05eab697ba0661000000000000000000000000000000000b963b527aa3ec36813b108f2294115f732c878ac28551b5490615b436406773b5bb6a3f002be0e54db0bcebe40cb2e2000000000000000000000000000000000bd6e9060b42e36b57d88bc95b8b993da2d9d5acd95b73bad0509c2324212bcf7a94a46901932c0750535d00008a34f7000000000000000000000000000000000a374afd32bc3bb20c22a8864ce0dafe298bda17260b9d1d598a80830400c3fd4e8a8f677630eae5d4aa0a76a434e0ba,4500, -0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d938800000000000000000000000000000000192fa5d8732ff9f38e0b1cf12eadfd2608f0c7a39aced7746837833ae253bb57ef9c0d98a4b69eeb2950901917e99d1e0000000000000000000000000000000009aeb10c372b5ef1010675c6a4762fda33636489c23b581c75220589afbc0cc46249f921eea02dd1b761e036ffdbae220000000000000000000000000000000002d225447600d49f932b9dd3ca1e6959697aa603e74d8666681a2dca8160c3857668ae074440366619eb8920256c4e4a00000000000000000000000000000000174882cdd3551e0ce6178861ff83e195fecbcffd53a67b6f10b4431e423e28a480327febe70276036f60bb9c99cf7633,000000000000000000000000000000001963e94d1501b6038de347037236c18a0a0c8cec677e48fc514e9fc9753a7d8dcf0acc4b3b64572cb571aebbe0b696640000000000000000000000000000000000d9739acc3a60f6dffb26f9b5f1fd114a21f2983deea192663c53e012b9f8e1cabd4942ad039badbd4745ddc0a26a91000000000000000000000000000000000b4206dcdb80d62195febb6773acab25fa2c09a2e4be9416ca019faeb72f1fad1dfdc51e8cea39b371a045b18947d40a00000000000000000000000000000000100758b888fa27e9258ddd5d83409e8aeac576874bc399b33b8bc50d77fce5358cb091d42f9a1b1ed09be3f200959989,4500, -0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc000000000000000000000000000000000a69d6d9f79e19b38e6bf5a245dc820bddbdfe038d50932f76d0e4629d759f8ca6d573fcfc39256305daedf452f9fdf40000000000000000000000000000000015f5949369e58487afcecf8018775d1b0a73e913bf77e13d2e5a843bbbeba7d1978ca27ae8bfc87d30f567dd396b980e00000000000000000000000000000000182198bb38a0353b8db25389e56ab0d8679a1bda008a65dad77e4c95bc6804f6311eb16c761e1a5e2a5f87cfada49fa4000000000000000000000000000000000eb5483959e98c30e71db52615f63521378b156f142d46f3bb285b94aef39d80feacec335b797c5a68dc17ba89d43e0f,00000000000000000000000000000000079e4fc2190d3441fa76c2d925d23b81e353e09e9138fdde51234195e564a32c98aa0d240f051298bf966d17adc2d6fb000000000000000000000000000000000aa327776fa7e15000dd548fcdc3a1cc6f9d0ab33046dd4240a3002962131b738ffed579945a348c795cfcb33682cf3b00000000000000000000000000000000179232ec56602d1ff79861cbfa2edece34b296541483aa65fe0cb493f520b7722cfffbe04294dd054770a38bf75d927b000000000000000000000000000000001826b88a6b411330757bb304a380487a02f7cf421115b84b3f468d11a83dbf304ce7a5661f4f01299d3c7865305a0006,4500, -000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf49000000000000000000000000000000000286f09f931c07507ba4aafb7d43befe0b1d25b27ecc9199b19a9dc20bc7ec0329479ef224e00dece67ec0d61f1ca5ae0000000000000000000000000000000014e6ed154b5552be5c463b730b2134f83e0071dcdadfaa68e6c7c7f6e17dabb7daf06e409177bc4b38cfdb8248157618000000000000000000000000000000000f145e998dc6eb0c2b2be87db62949c7bfa63e8b01c8634248010fd623cfaec5d6c6c193331440957d333bf0c988b7b10000000000000000000000000000000002a1ab3eea343cfdea5779f64b3bddbf0769aded60e54a7507338f044310ba239430663394f110e560594d6042a99f1c,000000000000000000000000000000000f69e3616e7122bf78230461bb1f4b194988adc6149372691d8794d0086fba0870a2255a2c79cc3426e7ba4d032fc2ab00000000000000000000000000000000174752301e05dcd62f7a3ae3357344e64d1c94835b2b742ac24449ee2728d693a0df10c3beaeb45d1b4af4ac2bdbb8b200000000000000000000000000000000051a761a3ceb275ec28a2a269b5ded1d9fd11a617c958e73c07de3a92ac480aa82c7d2a1852d291804e734526277f5740000000000000000000000000000000009bec9045ea89d5d16588e3373cc977f6d975d0e2213b171403a9b2ca460b3b2e1106b474185516d4200655b17a179a1,4500, -000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e000000000000000000000000000000000d1007ca90451229d3780d66d3aed7c9d8fc82e9d45549e8586600e38eb6763f3c466e2f6ba6ba1dafd8f00cc452dda20000000000000000000000000000000001d017d920a262b6d6597bab532f83270f41526409510e80278d1c3595ceabb9ceba8ae32b1817297ff78ea7a0d252e8000000000000000000000000000000000935b7a59d2e51bbb2f9b54ccb06ebee9d189fa82f0e97d10c8020badb3de7fe15731b5895faed8cad92ae76e2e1b649000000000000000000000000000000000792dadd48a20040ad43facedc109747411895180813349d41d0e5b389176bfb15895d41665be8d1afa80835ef818eca,000000000000000000000000000000000c079610e6f8770d65352f911863b6cb4fcb25cacc4a42f75e34e29e977c93244a6241cf3d5bd1040ce7d8987996f87e0000000000000000000000000000000010d08d8f6fa8ee7042c0891ea0c3b9b59a79da52cf3a91627c79d456212e3f6f39e1f69aa0053bbdb4076a3f7d05e5dc00000000000000000000000000000000069047218b0ac1e07650ac8f4a1b9235f68408f543517c4ae3c0ec47c79b468713c704ff3680edc8abd1bbed7a5fa75d00000000000000000000000000000000137737706162e02cfa75ce2154d57c9a3520818cc04626654824769ad92ff7977942f3881a28284ea47c14f353772d0b,4500, -0000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e200000000000000000000000000000000095353ad699b89ac82ca7ef631775b2b3a6e3ed8dd320440cdb929baa428e63cb902a83857cc0e2621470544c69e84aa000000000000000000000000000000000892559ade1060b0eef2cbc1c74de62a7ff076a3621e5f0f159672a549f1201f2ffb3ac12c8b12cb86ae3e386c33e219000000000000000000000000000000000750df4632a7126ddb08658a4001f949b9764d9cc43a9393cc55d8fdbb15d4a1186dd87a6433d111888a7804540ad9fc0000000000000000000000000000000017554bd444665df044b91b0b2614017bbfcd7acc7f8c5a16cea2861235578ce2b27dcced9fba234999fa478cd3f6e42d,0000000000000000000000000000000004dd5dfe38fa70625216ecfec60ea8d38602552726f0fdfb8f392362ce845fe0fda76894d0e456796e08462bb941579f00000000000000000000000000000000195a85cd0685f4053ee539de7e04fccd2380819b291f89cbcd63d5a0015b3214500284a7c6568a71f52bbdbc38be410a00000000000000000000000000000000107c211bad49c7dd8555e30f2500c67e7175eb98a8494f3d5309c65a93cce89572b7b5489428eaf3f0a5c1be323c5352000000000000000000000000000000000c11f978150ac35722679cf79443b3706d288c968116ddedc1f1d0fca8cd746e3c92dc006330be14886c53c41feebbf9,4500, -00000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f300000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46e00000000000000000000000000000000175dadb6ee656ec6aebf8d0e5edaee3f119c74e0ea64e374be9e8ab9fd3d085fceeedf4ed8de676ebe9065d83b0542ad0000000000000000000000000000000005cd6a875329c23e4918976cf997e93e403957acfc999f8159a630d21ab6f1762925c063784237262bedc82402ad81bb0000000000000000000000000000000003274bcb8db35e50164d136c2a98b5a6d2fb5f9767d0ee11c1358bf7ca5ed96d9122f8c1051ba3c658cc89777d03dfa5000000000000000000000000000000000380a240443dff85b6542f75db28b87c39e278cdb8d9627efbbc63b229e6ce783f6fb0114c8e91c2fd6ea71c95bb99a4,000000000000000000000000000000000fb33caed4de22cf341bb3e04d41c0198b064c1d371a24f5cf59595ab4a1edfd379916a40cc405d35f0603b2f8fb987400000000000000000000000000000000131ad6172c20b3a1cc2542db037de1324086fd9cd140ae97987980f260023d91b24504181af6fcbcfa242f48e99559320000000000000000000000000000000004a0404c00789459395f5344544041785d10f2fe74d4bf484966f5e9b6b4c4c8cb113a811a4fa82a1cdf8e3242bb418900000000000000000000000000000000086ba6a914f3f07bdc6750fcf6baf76124a17964bf9eb9a12982e8a28ca04360da3544b69436d5663e4e94bf7189529b,4500, -000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d10000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12000000000000000000000000000000000834cf1b4149d100c41b1bca0495e455002eb6596bddcb94ae48d0c65957e8b313372f8e0d6e57504664b266f38293150000000000000000000000000000000000de2875fbd14760bac4c2cc7d3f239177efe9f7f61f767be420d44f24c9fb863efd60dcd732986db8c5b72470617ea60000000000000000000000000000000000bc9535ebf11c2dcc8c7d3bcd09d7d14035635fccb5fddb7df29ce8855e79f99809781d6ffbbcb33d1227314609abee00000000000000000000000000000000039bbfb4d969d702255e3be7f255a97529a19687ce38cb70637c37894d4102591feef428b0afe8c9ef50310ae3b83091,0000000000000000000000000000000019c8a1a206c0006a3033377abba4c31c55710a094d8c9dcef7560818e90411861ce7d189e2763f8fe69bf75e719e4efe000000000000000000000000000000000cccc6bba8691c210aa0a67d26584a359fab94041d853160abd9669893c0d398c805cc37fa3c33bc5ee5ff915b985c45000000000000000000000000000000000e353c1993c36763acec2a75495560e743d099b565f3de195e011afcacff3d60502801f47695da7dd589af81e772eb7800000000000000000000000000000000100c6123cf08eab6c59d78b414fa504ed10c204851289b0598b40ac31971fa12cfda4ef7cd2d64f9797d4d2b193e0bd2,4500, -0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5b000000000000000000000000000000000fc09c241899fa6e8cc3b31830e9c9f2777d2bc6758260c9f6af5fce56c9dc1a8daedb5bcb7d7669005ccf6bfacf71050000000000000000000000000000000018e95921a76bc37308e2f10afb36a812b622afe19c8db84465ab8b3293c7d371948ee0578dbb025eed7ed60686109aa0000000000000000000000000000000001558cdfbac6ea2c4c1f4b9a2e809b19e9f4ba47b78d2b18185ed8c97c2f9c2990beadc78b85c123b4c3c08d5c5b3bbef000000000000000000000000000000000ea4dfdd12b9a4b9a3172671a6eafed7508af296813ec5700b697d9239ae484bcf7ab630e5b6830d6d95675be5174bb2,0000000000000000000000000000000009fc3870f88288c680b43d63d3bb5305b99fe461e59c07be981b8819fbee0d1fdfae0c037e830fbbabc40cedac7919720000000000000000000000000000000018bdd4903da4d14fa28af4c2cddcb708238cf68673ce77a04a3926c4aaf17d39a831c5401e84dd042d6adf595a1763710000000000000000000000000000000002c398f0e8ad9752f4aded980bc5de2d91118db06818d815c11e818ead47e7065823737db8e304bae32969cab065d1ff00000000000000000000000000000000180642a633c3aa402e5c0b18fcb6fe8c115575b863abda59b5d91997ab01014faefc975d0aee994f98cf37ce79eb95aa,4500, -000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4000000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb815960000000000000000000000000000000000b36d8fb9bd156f618ab8049d41dfe0698218764c0abb10e12fae43c8810b8e2a5201364e2778f6f433b199bb8f9a6800000000000000000000000000000000000707eb15411b63722b4308c0ed4288320078d2463ae659ad4fb3f9ef8124f379df92d64e077403e50727388adb59ac00000000000000000000000000000000158e1249d5b91614924acb23899c6bae408697dec0982c10d0459746499f4e6739afb9d5129568106ed1a1caefeaa9640000000000000000000000000000000019e841562e4aa75321143f8ce1e5ec6158fa5cb8b98c839a486188260c18ee8a7600930f23aa39eac2eb520d6a0fba90,00000000000000000000000000000000199600699a6108599c638df8f965d73b5de4ca74598df281ec95c539de2c7eff9767569692d8e0ad120fcbb3d9335b95000000000000000000000000000000000c42b11e2585ba93521b3c968e9dee07e4f5168c11087d8d750795555a105df70c969bfa79b1ab4e5fc8d81657235d08000000000000000000000000000000001370daa4699daa99e9940fe04f69150e6f752798cbc0e66c91c3bd46149d935c1815f32d7f14b510e16d475044eda9cc0000000000000000000000000000000016c7a00be10de5732795cc3ee2951e58cb9d42f9b05d02fbff1b83fab5d3ad830cb8178092b76172108d7a53afe8c539,4500, -00000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c000000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e97795505419600000000000000000000000000000000186a9661d6fb539e8687ac214301b2d7623caedd76f4055089befba6ef2c96263d810921ad7783d229f82783c9def424000000000000000000000000000000000447f3e20caa1f99fbaccab7bde2bd37fe77cea691ebf2b9499f95bbbb77afe72b7039eb0c05970b61360fcf8ade73730000000000000000000000000000000005e11f828eda86c10a1d7929def547ac06885da278afae59c5d95453caf0a2d8ed186fa7c6d0a7ab6e9142cfa4b338190000000000000000000000000000000003d954e61b6ab71042b19e804efccd4956b56662f27f70a9255cec0c464b86c0e83721ad3785dec62dd4a9dd3d6d5d53,000000000000000000000000000000000669cc8a3acae17f99f805afb9012a38851a9e8d4fd9895a9946c29fc859849c24d7ab7b6278c449cfbc5f1d7ea1fdbd0000000000000000000000000000000007a9095be808d0ebc99bce94e851d2a7cd3e1977b923064ab5bbed2347cf18f3343e60120fa051d12fe27da3146cb423000000000000000000000000000000000f1e7f75887651f67457f6dc064d7c11934035d15fe4dc40bab970160ed1b1aa230a3fb84dc1da08770d847c0216347a000000000000000000000000000000000efbc62ade1678cd70eb38c644038bf19e52b0859f65747068d9f3124762d951e4a6ff05f34b6d14919774f8409adff5,4500, -000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b660000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a90000000000000000000000000000000002b94534aa0ba923bda34cbe92b3cd7a3e263741b120240ff5bdb8b718f094d3867e3fcabeab4a7be39c8f8c4fdd10d900000000000000000000000000000000048711cf6a82534d64d072355cb8fe647808e7e8b2d9ac9ed52eb7fe121647a721dd1234c71ecd163d91701eb7331cac00000000000000000000000000000000141ef2e23a1ecc7ef2ed3ea915492e79cfffe60b5e0de8441e878bd0653843d79c724e3c5ebe2321361df99f8932ddc200000000000000000000000000000000085513b4009f29b3e00a91c2c4be418368560802ba4194cbd2f4fa3d72a55fcae547014434514a8b2a8fe3e0b28d2773,000000000000000000000000000000000e25a38d0ce2aabd2538c95ed463f226e3f29ce7f10e1be27af2d3db741926d557178c4b125af8789b40480d8beec0890000000000000000000000000000000002a94b7c57fe2783d055a537004a3b67e41f5374da0813094f5944fbabf4d27eb576dc8b21ccc15f8339df14ff8785220000000000000000000000000000000008b9efd8abfa4fd71a8eafdba9df38360ef0b0a117c0052528d1c24df5032635eebc7b201439f5de858514666c68cd270000000000000000000000000000000012a2fde51f6f4a98435c325dc3b1ae846bc33a5ffb3b13fbe3fde2f74dec0aa815fa8e42392b3dbf798cf547fdb4db0d,4500, -000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b381731734598360000000000000000000000000000000009143507a24313ee33401955fc46562c9b20c9917df3b40ccbd7ed43b1349d4551cfd98a4976d6fec5fc289460c8d89900000000000000000000000000000000060566b79df5cc975e669da8ca3a7fa91bf3f5c9fb871c3d62f4a3e79dbc341b89d38b588e5414bc385d5e3cbf3ab9310000000000000000000000000000000016bf40b8cc4c01a87aafae0c4439b623a51ba9a383756a550b69d627d6f45209f0d87e4f9be9edff35c986f7b9c49e3f000000000000000000000000000000001842d9172bce51a164fbdbdb108d0faae07e4642f21c80e40ac31e737657472ae3dfe552b65349629c210a068c4afc0e,00000000000000000000000000000000067265782d58b04a2ef3dd419cee506e076e49d1119e28db1df7f0e22cba9bbdabc560084cda50bc8db3915fa9c489a30000000000000000000000000000000012448a61fb2f6fd8e355111b671f0e888304284b72d5688091f2ed00edf7ccb7e5bd8a733a910d6964dde07d393798470000000000000000000000000000000005f687356ff6c634eb46613be8e98540107e706714434faff54510234d4aff42ef7752e154aed63fa8ff905ec0af628f00000000000000000000000000000000180dca84a37c964b30f5cd11a090e54acea102f1b884319f8d1252a37bda005512ffc39dec8e33af0dde0d37993f846f,4500, -000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b000000000000000000000000000000000ab19bbddd661e9db8fe4cb307ecebdc5e03efbb95c5b44716c7075bd60efcfc67de0bfd7c46ad989a613946c90a4c1000000000000000000000000000000000120800e7f344cda816299fa37f603ade06beb3b10907f5af896d6b4e42f7f865b756f14164db84411c56cb2ea81f60be000000000000000000000000000000000f688ddd257e66362af1437b6922d3397a7c3dd6dea6bca8ebd6375e75bf2de40bc287cbf3434388191e56b92949c83b0000000000000000000000000000000005252465784aff8c1c707da58b5808c69583bf852d68f96912bc53f8dae4536b09ccbbd25a49d9e744118992b92b6792,0000000000000000000000000000000012a29d35c9af52f172787c90c5a3e77ed29d66feabf5d7bdd6bfc14dd9a05d402976b84d44647628c908d1816f4e7100000000000000000000000000000000000caf3c372e36de557ecd7eba02e6a79b1b4cff30343119df7a23662c8512095e051ae2dc27e577635c74a260be2b084c0000000000000000000000000000000002ceca293a58bc9beb4ee9a0679eab037f5cf7b326d65c0efeefdbf384ad8e4bc08a3a75a02e6b9cba8963e65d6e76ef0000000000000000000000000000000004631773a6590bc89b49a75bbbe2e732f9466ba259ef7a04ae69b6aa5d5a2621c1918eb213101f6f7eeee4656a7b1472,4500, -0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9300000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd3000000000000000000000000000000000e3165efe00f69aee84ac56d2161f07c017abfaadeaad34f8c96799d68bae0e6f9b557bbf9137e7826f49f29c58d1ef9000000000000000000000000000000000de0dce7ea371ad60f21f2cb61cb582b5072408a7efc91edf05b36a1a3b58fd9e6cf808d75157eedccc8f1c93a8ae07d0000000000000000000000000000000016d911943d80427385ebac1d1b293914a9e4dd9db06c1d6a758192d63c8fc9368e02eae7fb0e3a7859408f215cfa76ca0000000000000000000000000000000007bfdc6afb8acec625e50ecbc08a5cdb7862b795866323679885ba5cba3fd51f181078e03fe35e96e6383c077eed1bf5,0000000000000000000000000000000017f155ed9911ec56d71d63d57556de071ebe89be36e6bc9943ec068a70dd5a6f045dfb9fde5c1e29d52c9fc17579452e000000000000000000000000000000000a60d62ea549edf4b11f62f2321f39d41bf11f3c4f858dc7db85b1dab1b7644e27eeb1d022d6082f59c65155068d2c390000000000000000000000000000000009d309145fad15860e556ec4b4aecb415865954247c2034d5bc96026e4d6f7612af6e2db99f4e462acee2b303134b91b000000000000000000000000000000000114ed157e3d020c5397cba7e10cb864aabb47461f166a6724614e689274ae74c505fb6ebfe3e88da0d6c272a15a0527,4500, -00000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ecf0000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bd000000000000000000000000000000000a68dccbe3452731f075580fe6102b8ee5265007ee19c56d95bcb096a3a6ac444f4145b980f41afcb0a865853b279bc600000000000000000000000000000000164767ea55a9038ac2dd254d8c8a4970dba93dacdf5416aecaa407914719cab165e7a32784b2c41652a86358737d831f000000000000000000000000000000000da9441fbc6578c85fdeca49082c9ebbf183de894d67c65158380ee56132d3cdb44b100d72b6d3b82688defb75d2aa390000000000000000000000000000000017d570e4f6e46550679d5d12c347414da207060f594620e2f8db66df8e0b06c912290b207a268e782d4b45db19a199db,00000000000000000000000000000000118e0c81f9157395578f0fb83b179721de2af3326d13189cb8f43911d8c3268a11fd9702f09f14c115bbdc43d5fbc08b0000000000000000000000000000000016a548df8c87f432c31e4e32c3e5b4d48d6f29fbe391d1181174be9dddee450e7e96bffe8c9f23692ccc080116592944000000000000000000000000000000000eef72a5c698c58f1d2ae9415da256b54d7b1ac37a1d1b88727c0afcfd854a41973c6cb10ecbc3a90050fe3d8d3ce8780000000000000000000000000000000019b16ca8f955dfd21830a3f7fafcc97d7de977bafe1983892988aaedd430d22674d97897d24c1643e99bfa6256df4bf7,4500, -00000000000000000000000000000000000eb3c91515d4a41209a73564741a8ccf901a624df9db22e195a5d02d24b7bc0a12756b15b8d006cb991a7e088eaef1000000000000000000000000000000000704ce8afc808b0161f6f61b22d990d713ae398779e6e74e9b5771daf006ce0bba3a8088edf75156f0e48b92ee8409b00000000000000000000000000000000018fe81e05aff0620f4bdbe4a715e015650497afab62921eba0ab86b649e5a2fd3d54041868928519f537e36448688a0d00000000000000000000000000000000162bd97161201ea3c26f8dd1204a9c6b61b762bdf573cb5d20b6b255f30208ca7d96aa47b46fb8c6bf0922075f1c1ca800000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79e000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e,000000000000000000000000000000000f2bf3f69276d390c9fc2c15e9f5f5d0b3cf9a6eb028c44811b481f376ab60e17d33a04b78348e46eaa94332c5f16ff8000000000000000000000000000000000bedd0437fb3f4baef87e56f33c77fcdff6a5512571cf11fd9605697abd8763315f1fe4bccf04acc6e971d6aeefd9c1500000000000000000000000000000000067c3ff69733baae2fb4ab77cddb7563047c428b40a257a375f8cf8c9d230a6619f7932b86e0836fff0c1c60d2c4dfd900000000000000000000000000000000057526faed8d62aa10e89add5a338320c748ca1f96ba5ceb579efec69d17475571fc4ce6fce3a93398ea88340f0e969d,4500, -00000000000000000000000000000000135aee0e30fbcad798738c10d4aebcdf50c89ce516325f655fe763dce54ffedf94dd74168611e5ae879b5bf5598d62dc000000000000000000000000000000000c728e672cd8b3bf9341bca929c34118b566cd3a80452d7015bee9d5cdc001b1f5c678d4b2cc4f7cac353e7bf326ca1e0000000000000000000000000000000014809aa22e2051e463fba6d49fbb060d0c7f599a0fc5409d34e71f34817e7beb1251810ae6eee1848c60796fb8647dea00000000000000000000000000000000145a4de777d86025d50e12f9a6615ecb9bdd41489992d1b643dd9aa549acbc63b04b0bdfd14b6e45c70f165e9a8c91be0000000000000000000000000000000001c2d8d353d5983f22a5313ddd58fdc0d9c994b2915dbc87a9b65b7b98ff00b62e140a27dc322d42b3ad190c1b3728dd0000000000000000000000000000000010412f3625947b38bb380a6ed059f1677b7a7afcb91517837c563dadd0e285b95740a200ddff6570d4d92bb636b625bb0000000000000000000000000000000015f4f9a480a57bd1b2388532ab045a1ba93d2f6589a3022c585fe06a1d611165c99d70be06251812405c9c37d6e9f7730000000000000000000000000000000001a78e6c5062a6634a56e9853ff5afacb2e7cf31fd0ea5f0d8c8ac6174c88133cf2f63450ec4590544c9a0e37daac1f9,0000000000000000000000000000000004fc19f8fe47e6acd37567016704b07f906e8741fcb196f697e1fc24b0204292693ff424bf1c5e407f5bcba5a3b1ab85000000000000000000000000000000001816f992c3c461fa6d2014ced382a35b0d70e61927d72b4d661434efff3dafe2f4b6cc91bb1a5dbf809f10f3ed7f36de000000000000000000000000000000000dadf7f7223ccedbeffef31c97df7e01f99299da71b589c8828b65715012aa343d7e041dacc57b34a6b5f84523a7938100000000000000000000000000000000167f7e73e22df81bd2a7a6f14e940a401bf414e5d18b3aa610b2a82ca8f46aecb5721d0092b27f8968b2302c37957268,4500, -00000000000000000000000000000000009a58b7116dbd6f550f8ca98071813130ecaa9ea86d5275eebc36860690fa048c9ebeb46600b2b63e847bff3e38ed0d00000000000000000000000000000000113ffc0932c041e0e34b2540c485eb74f5029b339cb60bc88a8a749310f33f330dea137e5f340044fd689264af66696d0000000000000000000000000000000002642da3c2c7b6688aba0b19ab29ac72e35caafa044863c364ea8833fca850289de52c0963bc33d7bba40cb5f568718a000000000000000000000000000000000552d35ca054da2f148c119454f6760607b351f2441921a2be17da2cc10902d71571c5554f132e60df79679428fa07e3000000000000000000000000000000000818e567aea83eaf3142984bb736b443743659626c407987b604a30c79756081fa6ae6beeb2e6c652dbfe9cf62d44e3900000000000000000000000000000000193f0317305fde1046acda2c9491e376aa67244f68ef6495845d049e1293082af91f880be935d9d8ad0e25ad918caae200000000000000000000000000000000109224b8178be58ea4e4a194ca66bef9d14f6fc2c625d25feaa4f32e0f4d72d91024d96839bc96e6a624c5ad6221bd94000000000000000000000000000000000e42decf8a987efaeb4ede37236b637e61249bf6245679be7fd4d633e2d814ed4748b73890ad3c4fcbcfb4960cb67ae7,00000000000000000000000000000000041a5783c748247f05457d30d16f93431e9046a236d5025cc07a27b9f2abaaa556e2df65cf0f0015107253fe94d8b4dd000000000000000000000000000000000193638bf69c7508c4b12808a62e89883c34f97ded6e1b5dcc3f28191e5c7fd901a72a85ae386acccc9865f8144b1bd500000000000000000000000000000000180e8184ab583da58b77b8a4d108a366dff3e3b336ebc5c9153fa815188edc95e7067ef25f7d79526c295d634bc98f5100000000000000000000000000000000125b147100f6df0cede8e22151b3423b1dd364899fdee103c71a44388ff002a367627a2342e15833644bcde61f2ef6b6,4500, -0000000000000000000000000000000018fbbcba3d4b1e548ceaec4a48db62a2420ff29a67af332ee7ea3f902f84e6c375fd33abc33d945c5bca25603979f9a400000000000000000000000000000000072ff416994364bdc6535f36c82212afa822cd94fade69f11eb38dbdcd37c7e22af55fe05e6a826dad822073656eaac10000000000000000000000000000000017bba179b847278a4878b6faeaab3b1f4bd7540d22817cd9aff95557497f8b9d286657b6162c0f89f7820becc637dd550000000000000000000000000000000018e2bfed71aa9b11fefca2f0db8bd9b8c69540267de50bec4fc90a6e9741891465c9761d19282e1100b3707eeb598b31000000000000000000000000000000000ca0d865f8c8ce0a476f7a6edb3ce4bd5e6c3a8d905d8fb5a10e66542f4325a9963c2f8d96f804f4d295f8993b5204df0000000000000000000000000000000005a966f6254f0ef4f93f082a97abe07db56f00c2ade047d2f0027edef6f00a0dfecaa24d50faa778fa29087302211f7e00000000000000000000000000000000121c51da366557c09af1bbd927521da88dfab3e2e9a95b6effb0a968795486f281f0c887e37f51837557b9e3808987130000000000000000000000000000000001a5524975400b1e88f3fff8dd34dadf5d75564cfc0026df31ee9c2c1d48b0f69a48e1e4a48cc4b7db61f023a7915780,00000000000000000000000000000000095fda8adf3981f4468fb82aa0ccf80e55138c922c6422cd8e67f53ee63e7a390bc345469e9211a1f8d810cf4ba27d0a0000000000000000000000000000000015c19b6af21f75e8e53fcefbae1c8d7f97853a8aae5fa62e606cfc92ae71890702ef9dc5609d3ca8fefd415fbd820c04000000000000000000000000000000000007b7e908766d34c5d99cb7cc76d5d5ea83c29ae1d9b83b163741bc9962e293926b1e251b546ce0c1268def728da78100000000000000000000000000000000084fbd6253211f7d66d52b7f14360729d54b2f94c52f2b76e521dc3961c40b4f19944923f64c6425a44eb158a9727a4f,4500, -0000000000000000000000000000000019efd37727dfaedf697fcda7a59847dbda8ca7cdc92f34e68691d682e20ae6545ac104d6660fdb8f64a051e69298eae8000000000000000000000000000000001225ace0fdce456dd888c9672503b68ef77b2d11caf1265a767a6ea14911e3ca03fc153f18dfe9d95e0cc68b7b8a3a8d0000000000000000000000000000000008a6b059c1c4da046cc0b1b5d7f33270aceffa607daf6d0d078c06f940604e1a0b4adf01a4091306e3c7eddcf3d95101000000000000000000000000000000000f79bae5260a2f114ffbb9273f3049d3ebb002500a57ee0a7d157d86957f43f87a2e026fb9892dacaadca5ee04fc8e170000000000000000000000000000000002b51851ef3b44481d13f42e5111fa4fec04be0bf6acc7e59dec3a8c8113e5bb7b604c6dbdc5e8eddc2a1ffb81bc2baf0000000000000000000000000000000018ddb483ae75402852b7f285277ff7308ff78a3364cca8b0e0e1fa9182de275fd55c1e8ec3dbde180379c4280787ba8000000000000000000000000000000000170539890c89a4f91acd59efd413b5d1059f0c8fd8718e8f722e865dd106a4eb02e6fb0cd71b34ebc4b94375b52e4dd60000000000000000000000000000000001c2e9392f5d4b75efc5ff10fe97f37e2671cad7e4710765866e92aec99b0130e6ff1314502d069fb7b5f86bfce4300e,00000000000000000000000000000000121e7f2eb906d0b31b8ce5cc46638428b6ee57a1ee70e4ec3c2bc044230b9b86875abe0862145b442c0e34308efc690f00000000000000000000000000000000139120d0a10b82737561d0b3fda01b6df69d9beb7dbabf3ddda036f9b4c317f3ac1eaf400013fe5ad664bea44a73b336000000000000000000000000000000000a923184b381027d8cb3f82708802b204566b2b8bb6a72767aa396324d8a26b4e0f0cb92fd1914d77a4e9af2f1ec31e3000000000000000000000000000000000409732f2225cb5e5c002bef17512519eb1a18bf6c3d7f834d0c7ac8a38433c88b550b3f443d259313eb1133620ebf0c,4500, -0000000000000000000000000000000016d2b73eeceee17d3bff3aacac9df9ac1c4248d9ea7d6a503a757f7bb22fa6970bb6f5cb5ec154785f7252e1508b382e00000000000000000000000000000000081edc68bbd8db7b10be06ee23d090bd54f9ca07ef24dfed7df7bb05f8cc26e6889dbd40ea203fd5cca5cb588199f9e40000000000000000000000000000000010d3478508619ea9493b4330e2fb9150024cd32dc1378f824788a884a4a30fbf39c630f465557bf0c6d69b4cbecf89f9000000000000000000000000000000000f20c9b134db5d8b7756800c031bf5962fc560ba95d4bd9157b16179f1a37ae08696a2be455ad8d018aead6adcc69b710000000000000000000000000000000011bbc566a10eadf16009c1d2655cfae6adfb0f56f5e55b31dc000414be1b4cee9a0b9f7d9eab4c6829037c327914d5640000000000000000000000000000000009b28329096d8644dfcba6e92477eafff29f7477da4581ce76d1493f03034d7f5d3acaadbe42c76a83ca51db79d456d10000000000000000000000000000000019f75a303fdede5d97f3e521b03ef6b9d7c008d770b59ce3ac38900b340895e008342701ad1b41830b9c010936f4ff1700000000000000000000000000000000161aa1853edbb56fa3bd685c9c6b88e466dfa3c4f194f6774b4d9b1f30b016993bd0d65e8e9d6dea6caa196ff735bd67,0000000000000000000000000000000006a200642d5cece5eaacacb36000b4b897e8d8c661c8282f90495002aa515c7638183cf1e80a0b35e953adb92b6bb845000000000000000000000000000000000e88d4cda34e98df4d727fda79b67961b5b8efb1b125ef2a8eafc481a2cb2fa1530e59a091f31c25cc49d38f545491ff00000000000000000000000000000000082f38c1a1c35981f537547dc3b59331ab8c5e8dd261df58fe6f0c44ef1e65d0cdc1980e1a62f6248f38d0afe91e5627000000000000000000000000000000000eda1002e202e9ee4df5354cb87760d4df32eba1eafdad27cb0636879370a8f93be0bf2a30f15f2fbcd7e52c1bdf6b05,4500, -0000000000000000000000000000000003dce67181d23af9729e9fb0653d7f79c890fba27de42fada93123e112c4a468fa889921192db8047d86e4db77c60266000000000000000000000000000000000869a1e39d42d9bb0cc0568fdad16abbdac3194af893ebd8dd8f8c2c3c855abefa5fc215412168acadc88e658e83f5570000000000000000000000000000000001ef139a75194f3c4b1378c2b66dd304d179460bac0a289405cd8faa3ff66a7b6e54eb7b8742a68150b1e098630135c40000000000000000000000000000000003892b5a645af916be2c6c7fc0bb08fb5f39341d3c68598940554e1be11e1be75af920db0c8710ed13c78edbf683f17d000000000000000000000000000000000ae7289aa9bf20c4a9c807f2b3ac32f0db24e9a0a360c92e5ce4f8253f0e3e7853f771597c8141d705062bef12d4fea80000000000000000000000000000000001d2f610d79110f93145faad2e34f3408316b1dc3a72852e811b324577d9037035e24af25002ddd100cd9283b70ddcad0000000000000000000000000000000012947315d5c0ec670619125eed0de3dd259a008baee4379b82accf2391e70a2bdad264cda04c3bc1b5394a62559fa0ef000000000000000000000000000000001239e687c4d3417c3c9b655035f8d8a649c255f9a8e6f03b785eed0d416a1cd6ef7c8b45563acb4616af24f64dbccac4,000000000000000000000000000000001341cf3316152ae8d57ea2194224f04756690133d2e02d077dc271aa577278e346e0ff66e8a49ff8c983fd34546e1f6f0000000000000000000000000000000016c9093da650643f4b4061e1c6e55da6ebaf9f234bef8325aeecad3863a0a2f53e1cdb2d54aa8b075ce6e6632fb4cd660000000000000000000000000000000011eaf3dee010bf2a16c5fbb1f7aa559cd4d831f087d9dfad4e157a6d2b6495e370d9791cbaaae19339a65726ebfc3b910000000000000000000000000000000008476d793305204be414819fce2ca70754a532682876277bc0586514f2096ba9998ae848c722ead6722d5af9395ff77f,4500, -000000000000000000000000000000000264dd4b477f5db65edad28c7153ed919a863c5c5661e0125c5429b323e055fd69c33142dfc6ed9c87082e2be4675e1f00000000000000000000000000000000046ea088a2ec94d3a1f1f97949f1ebc49690c453d316cc46534fa253b34b30323b6071d147d64bb94e02fb4db07bb0c400000000000000000000000000000000013692a33bb1348486eec40a9e93a4ea3810c7b4d3188cd07e235a2c898aa87ee0d17682fd24f4d978f9fb028fd26e2900000000000000000000000000000000115f8b64c00cd5cd344a7b5edc0ef0bb85a3e8f0f9dfb28f8ffe12db3e0d222c2d45dcdba0fbdc161c5d558bc71aa097000000000000000000000000000000001179ee329771b5913d07818e70f6ce5a58d74ea0b573eaa1bd3d97e45d3eeb27fcc7d37dba127af7a38354cb6ff48f7c000000000000000000000000000000000c898abe6eb76ef99f5143cfb8d840a918bcc9096ce25caa45d0bf5d20814cb01b024f1fd2cbecb6bef65d9456070dd90000000000000000000000000000000008e2a4fd746e86f90484f9b9b7b47b6afe5833762e515ccb276c554f00df88dd9aa0fb792c5f419dda0465cfed838e7c0000000000000000000000000000000012b5e6f7070c0045ade96f548ed6428c5030fa20c6f6f37a42fde9dbb5cd01def0fd8585bf8aeef913e7d42b9ef22efa,0000000000000000000000000000000009792d98ab9b90c2467ad0d070ea44f382ec7ad5290a59d889313c5a55d7b8e837333ad7ecfd97221d405cd6c549dc8e0000000000000000000000000000000002b92dd07b61faec23f48b8a7893dae29509fefd688a978bc2e870d4cd6f963d708a0611b4aa65f5644fbc6ba4c5e66b0000000000000000000000000000000011e46a283946a8e033afbf7c14ce3162a05867809d7de94a090c8cc2cdca8bb79add21f6e2fa8d7f39ea6d26cd37ea850000000000000000000000000000000000fddb7cdf1f1126e7a6780e4892601121b289a386ebce0caf96cd392ddc57c47e3f9284889fd8a18fb330d6c40bdf67,4500, -00000000000000000000000000000000014c83d58d90db4821a0411fab45f83fbc05f7d0d7a67ce75da3ae568978d15f4c1886c6fa6086675c0045efb30d818400000000000000000000000000000000001e68691123451f4c3df6dae62c6a63855ec3597aae33a8a10ee274e902e9aab1460cc9c79726312df0ee0ce90c8d3c00000000000000000000000000000000018a39eb3e3c6c7fb8ee304e55d15e209afe2fe278dda93552a7b9f51fbd778da1502eb6775cbc3f832f8320fa0686240000000000000000000000000000000017c15910fad1ca5749aa82a5a2fa98b0ebb37e92912547fb1741f18c34e0d5fc3a307b928636c25f0320d71cb9d31062000000000000000000000000000000000fe2e61bc8e9085d2b472a6791d4851762d6401fd3e7d3f3ba61620dc70b773f2102df1c9d6f1462144662fb2f15359700000000000000000000000000000000031f160cde626ca11f67613884a977fb5d3248d78ddbf23e50e52c3ba4090268c1f6cd8156fa41d848a482a0ca39eb04000000000000000000000000000000000eb61ba51124be7f3ee9be1488aa83cbd2333aa7e09ae67fef63c890534cb37ca7de3d16046b984e72db21e1f5c57a8a0000000000000000000000000000000006bf6f5d65aa7d19613141018ac8bf5d1e6fe494a9f30da215a2313a0241779006bce33a776aeedae5de5ea6ee5a9b9e,00000000000000000000000000000000054dedc002c5f2da8c6e0a0146bfe5c83200b276b074e6d6f2c397e1208f152d3ea3e8f0da7da62cfd2a028d4c94fe5b0000000000000000000000000000000012ff307f86e266e7a212484a169d3e81df98217c6f715176913b0d383cbe4e790212da7feca0cea66df09d92544fae010000000000000000000000000000000009c211438dcf8ccb664b535e73eff304b92aa2f568aeaeb8e10ec142f92b211bb8147b250dad77d508cfe353667b6f150000000000000000000000000000000009d1734f4ecc88fd56f412f9243c387b9da659faa3fe7295580a6b7519b1980bd074339fa9b0bef44dcdd0cf0c4a629b,4500, -000000000000000000000000000000000fa96d9fe01c18732e8d6454df9bb1f482c4b9add837ce9c354c72d49c2d44ec694674aaf0e6d6a095cab7ebb57ccd9a0000000000000000000000000000000001f8ffe3fb7e9e311e0f6949c07c26a0febb181e37b2268bb5e125fc3a100323740d1ebaa5e635dba3770fdc2ce4ee860000000000000000000000000000000012ac42095fdb677720ab3f14bf0afc55c95b43d28d922a5f8cb0bd841306b978751d24546e3a6474976961d0768f29e9000000000000000000000000000000000baf9804d99039c9fe966a696c64bdacc9673b0906b4deab108d34fbbaa3b0905d50892278570564017b96828c7e1ac900000000000000000000000000000000196044a5cdbc5300ee837dca745a44379070e9297697f5db28df4a37307cc740abed45cc778a3f4e3b8c9890ab6c3c70000000000000000000000000000000001176f5de6a3577ad67863bd3d9152ab9e8184964c6ac276e95946788f5a76394047580077c0971d874a40d510eb0443e00000000000000000000000000000000147dd55dff69213c5760e8d22b700dd7a9c7c33c434a3be95bd5281b97b464fb934a3dff7c23f3e59c5d8d26faa426bf0000000000000000000000000000000019efcf03ddb0934b0f0dba3569809d5b48b863d50d3be4973b504244414e1e1db56adff51d33265ce102b320c552781f,000000000000000000000000000000000896a38ce734c550c178786092292e737d44fa5f503d6d3b66c75e6bb70b59d1db9e8baa1ea3e256e2dfd8a942311e75000000000000000000000000000000001231db96a35229a4c7507b0ec193491446a0b43115c27d18b3715fcd4aea14d4e5c99db5934e73bb0b86f1bb91ee96fa0000000000000000000000000000000000d6f95d5637b29ea889c028dacdcb484d8ccdb243da4d5ff49e5ad82f234d414dc1484e9ed6cba1b5940eaabd3066860000000000000000000000000000000007de052fbb76902e06e1783fa8afcbb54a5069b4c5e9cee78d43da2cf76f24843a740a9eec6fe9b8f9bc4ac9baea77a5,4500, -0000000000000000000000000000000014ce6d88a7c5c782562aa101550f1af487296adebd9dae8252698ba04fbd58b92e2216de6ffd474d5992f97d9f22800d000000000000000000000000000000000ce92a04f5c8a99ca0e93992448222519fc454bda5d1d8638a7bfde968386e4ba0dcd1da59cd81d4c4dca3e584be0275000000000000000000000000000000000cb570796f5c8f7b8aa02e76cb8e870d3365fe4dce5df07ec286a0a821f922b4003d5b69c0f1588206d9544013e268c400000000000000000000000000000000098056a033d9cdae86aac02de3a444471854b909680719154b44d4f55f30087294e39e57643c692d6da725b8592390800000000000000000000000000000000005d8edbabf37a47a539d84393bb2747d0a35a52b80a7c99616c910479306e204e5db1f0fa3fe69f35af3164c7e5726b50000000000000000000000000000000005015082d6975649fbc172035da04f8aeb6d0dd88fdfac3fbd68ec925dc199413ed670488dc6588f9bd34c4ff527f149000000000000000000000000000000001312d53088ca58dfc325772b8dc0e1b20cebf7b2d5b6b4c560759987b44060bf4a59a68d1a5623bbb3cc5b0bc3986b810000000000000000000000000000000012110cd462c6fabf04f67d652639d19640c46f51aadd6c4f9a6dd7806cffb6192d95c198f4c8284151feaa2e2a0dbc1f,00000000000000000000000000000000156914a9137e52abd4579599dea4c0f857eed0457ee1d80635d3a6ccf0c766ba8ab1b6f989711fbdf125c4ff06b597ea000000000000000000000000000000000c60184e8ab32019ce20d2d137130f657c8964406fe4abb26da232c9c5dbfab243837d700c88d6b9ea4b8f0a2f514281000000000000000000000000000000000dc3e6e3acb898552791431859943d0a83fb4ccd62e4ab2a971370a93a99a9dfcdbe4c42535aa063354e0f2cd48308c300000000000000000000000000000000025be02da875d4990d1f0be626ce634c4856ea91f88f636bc27e313e73897c9c13a1e3ae70c1227dfd4fba97f521d6af,4500, -000000000000000000000000000000001214aacb0a5e6b7a40369a83c07fa8cf1786ce7cbde2b5a501d9c1292532df7822d4fde10a31fc0cecce3a7cfe3311850000000000000000000000000000000004f9669d8fe4f884ae93b2505710e6e45b19b7aa5df8cdd811f09e547efc27d21024cba05e2dc9d057055f30ec72d9df000000000000000000000000000000000a852b821b31cd27eca19712a636aa05ef2cd82c36ac1c2ca240edc7d0172b42a72c42d3cba583a5b5129ac1c9486e270000000000000000000000000000000007bd8419e791a5cea04993509e91a980d3ae4987a5b322400b6e4a4f2b636891a1c7ba4de96b53426dd556532403d5a300000000000000000000000000000000117fd5016ddb779a6979d2bffe18032d9a5cdc5a6c7feeaa412381983d49ab894cb067f671163ccbe6225c3d85219db6000000000000000000000000000000000dcf01077dcce35c283bea662f4e4d16f871717eb78e630d9f95a200cc104fe67b0d69d95f6704d9812b46c92b1bc9de00000000000000000000000000000000121f212cd7251697ef6a7e3aa93eb0d7d0157cf1247d4411430c36c7277bf8acfccc4ed8590b5e8d0f760e0e4ed7e95a0000000000000000000000000000000007d22d78b486f575e01e21e1239cbedc4628ba7e01ecf4a3459bd78a9716e2969f26ea3f2449685f60397e1ab2aa7352,0000000000000000000000000000000010124c1c1c10868b570d2969ebc3bf5cd6bfab13ddc93f0fd2b8a1742eb8e04d31063bb81c52b92e253128d4cb4413a60000000000000000000000000000000013f89997cd2ddae00cbf24cb66a92146c553c6fae41cdfaef14d49078729f239ad2661937dd0d4d6ffd7076b03e0aa84000000000000000000000000000000000ba2ecf990cd846c95b35ab60d4f97f5814c8189190df9d521b3dae462f2d44db006a0daecf6b82c1459006bf82ef7c90000000000000000000000000000000016dc129b83cca5b3c699628d081306c5fa61faf9dda5e92894931714037628fb829c595bf64d4a7fa295f136ae244601,4500, -0000000000000000000000000000000005ef88bf38b2f998dec7302cde829076e6cf69df23aa0bf6bbb39fc0d3d8b5eafba74efb928b1de0eeb3d86ec82612300000000000000000000000000000000011f47e9583997b19c36616e4bf78d6ddd6d67937f493986250ff02aef6e6e7ff074559af2f20a5bf1d67158e4a199cdb000000000000000000000000000000000007777c8eb259a836e6459b7bdb642f878d869fdcb31b105d01f280938ef5377f2775874c099dcd394abe70f17d595b000000000000000000000000000000001607379d1cd34e2d0ed765a339b21433e9aa489609b92414c6b5a05d796085269c288d739717def9db3502e055086016000000000000000000000000000000000224cbea61c5136987d8dbc8deafa78ae002255c031bb54335bcf99e56a57768aa127506fca1761e8b835e67e88bb4dd0000000000000000000000000000000018cbf072b544df760c051d394ff68ad2dd5a8c731377fa2a5f61e61481ad5b42645704a2d083c7d45ed4774e5448141e000000000000000000000000000000000740b8b7d7bce78a51809713656c94cf98de72887676050f65f74c57cbe574278dd3634c44e057ea95babcc3d230e3c40000000000000000000000000000000006696058a191c7012a4ee7c973c2005ac51af02a85cbb60e3164809a583b4431dda2b59e1c9ceeb652b3ac7021d116a6,000000000000000000000000000000000a66f36f2437db57473bd8b7670994f1cfeb8b43c0ceae358e63a5e4e52b737fce6b3d24cc4de593bcd44c63f2c5935900000000000000000000000000000000070b7ad970f03a38c8a31452cf11422159cd3331d746031781a5861e26f54efbaba63dcb1db8bab997eada9c3dac39cc000000000000000000000000000000000ba4a9d7350adca1ae64e722df11baeea77c5fb75c5b52c8c46b9d863a70bfed1ec47888e907213f4ed4dcaedd37f20f0000000000000000000000000000000008a64244f1870a1dbcc4bd4d5c9eb5cd5225713dc73aa22bc46b1cea36c88a66f85251a8a9ba7279c88bd5dd37a06f7b,4500, -000000000000000000000000000000000d6e3068c082b68312141aa68f1540ea1415e93e7f1762b6f06ff408a9995542da1c727a13355c19f8f418a44de1a95d000000000000000000000000000000000dcfcf2ab12b1a0e521ab402aaa4d32ff649a5a97892eb6ad98487c3c73c35601c313b8130ad12e9098d16eed3bcc2e00000000000000000000000000000000013777b1eefa4af03dc44e4e054eb7a3a980a9c55644900b80346be84b970e1754d1f4ab771adc9249e4accf88a23fb400000000000000000000000000000000002f53b231f1209c6f8b52f99a78bc2147c951ac89b341495f4a60a6572985ce2bc823625099ec214bc9ceedb2deea3ff000000000000000000000000000000001522e0a4ccd607f117fc6fc8f9abcd704e9850d96adb95d9bfaab210b76bfb2c5dc75163b922bd7a886541250bc1d8630000000000000000000000000000000018a6e4327d633108a292a51abed43e95230e951e4476dc385ceea9c72ed528bf3e06c42d10cefbd4aa75b134936e4747000000000000000000000000000000001198587188e793ad2ec2fa0fa1d0da9b61ed48444fe6722e523aeac270f17f73f56b1e726ab811bb54a6e42e506d70a20000000000000000000000000000000004bedd94182e0f16c71223ac3d68ab327d28ee0ccdcd2c2db07faf69e1babe3fbf3ba09c28b146eca7ab047b59294703,00000000000000000000000000000000079f89f2defd1f97efe0ba1db28523abc88cdf66efd39918a600a07c5ed5b72ab9d3354a172735e7749b5f6814a48f4f0000000000000000000000000000000009e361b8609be8057e5b3c99eaa1727fdac17edc59239af17f55d72c8b8daa89726f4ae240c742ec4b02fbd89d45c46400000000000000000000000000000000121b475a2ab50357ce80fe01fc461195029de20f61474b0773d80434253adfc268a775e1a0e3b7df5e85d1ff8c5008960000000000000000000000000000000019a76aef4e04136b1ad0d03586a3d8608ac4573715f18d5fd6907d03e5fec7c5659e15c19fd87f242da972b651dff5fa,4500, -00000000000000000000000000000000161c595d151a765c7dee03c9210414cdffab84b9078b4b98f9df09be5ec299b8f6322c692214f00ede97958f235c352b00000000000000000000000000000000106883e0937cb869e579b513bde8f61020fcf26be38f8b98eae3885cedec2e028970415fc653cf10e64727b7f6232e06000000000000000000000000000000000f351a82b733af31af453904874b7ca6252957a1ab51ec7f7b6fff85bbf3331f870a7e72a81594a9930859237e7a154d0000000000000000000000000000000012fcf20d1750901f2cfed64fd362f010ee64fafe9ddab406cc352b65829b929881a50514d53247d1cca7d6995d0bc9b200000000000000000000000000000000148b7dfc21521d79ff817c7a0305f1048851e283be13c07d5c04d28b571d48172838399ba539529e8d037ffd1f7295580000000000000000000000000000000003015abea326c15098f5205a8b2d3cd74d72dac59d60671ca6ef8c9c714ea61ffdacd46d1024b5b4f7e6b3b569fabaf20000000000000000000000000000000011f0c512fe7dc2dd8abdc1d22c2ecd2e7d1b84f8950ab90fc93bf54badf7bb9a9bad8c355d52a5efb110dca891e4cc3d0000000000000000000000000000000019774010814d1d94caf3ecda3ef4f5c5986e966eaf187c32a8a5a4a59452af0849690cf71338193f2d8435819160bcfb,000000000000000000000000000000000383ab7a17cc57e239e874af3f1aaabba0e64625b848676712f05f56132dbbd1cadfabeb3fe1f461daba3f1720057ddd00000000000000000000000000000000096967e9b3747f1b8e344535eaa0c51e70bc77412bfaa2a7ce76f11f570c9febb8f4227316866a416a50436d098e6f9a000000000000000000000000000000001079452b7519a7b090d668d54c266335b1cdd1080ed867dd17a2476b11c2617da829bf740e51cb7dfd60d73ed02c0c6700000000000000000000000000000000015fc3a972e05cbd9014882cfe6f2f16d0291c403bf28b05056ac625e4f71dfb1295c85d73145ef554614e6eb2d5bf02,4500, -000000000000000000000000000000000047f92d6306bed1cb840f58fd57b5b71a5df7f86dbfa55a36636cb495e08715cd57f2f3e7cd99a1efc28b1d684de1cb0000000000000000000000000000000000f4eb02d687a1a6105b4dbd740e2c7924689d558e6cbfee768dd303cc8dd0fd887f5eec24b54feccf00f473ca3f54ad000000000000000000000000000000000edad68c4d536912816cf6ef039c3dd0535dc52189583270b3b038e2c67b213d943bf384ce69c4a9dc526d7ef309f25a0000000000000000000000000000000006ff4a6b5129ef026d1d5704bf7fc0b474de92b5cf39722f165e73f4e7612d6d3bb40743e4b7b42d0dad5d5d6a2d4881000000000000000000000000000000000805892f21889cab3cfe62226eaff6a8d3586d4396692b379efc7e90b0eaad4c9afbdf0f56b30f0c07ae0bc4013343b30000000000000000000000000000000007853f0e75c8dee034c2444299da58c98f22de367a90550dbc635fb52c9a8f61ccc100f70f10208944e48d09507fdce100000000000000000000000000000000064afd6b3ef7ff7ec34f1fa330877b42958a46a7698c6d21adf73bfdfcab7793b312e21e5988652e655f2d42edb8a673000000000000000000000000000000000ea8a2217c3dbcc0f6e562de9cb2f334c896577d0b3a7108d96b1aba2d705dbf531e870d4023cec2c053345501324233,0000000000000000000000000000000013f8cdab447ef9be450b87f941c96d4e93d5efd811d80c6a910965728f7dc496dec132f3fbeee5d1e84ed7c24ca9c2a8000000000000000000000000000000001537d5caa13ddfac93f0f86729c743d9a68175a78c730528b581fb54b1f4d020473b3b766e3882a485ce5d02ab381c33000000000000000000000000000000000b370903684ede24f3df80e3834ed414a765cdbad98f20c49bef8663a82a468d3911d6bbcdc021e22c252e83a857e55800000000000000000000000000000000100cc8d05f071904753776c6092a38db84c5de751bf93216131a0f9a50bf78a722344a14b3be2a9207568d1f669d208d,4500, -0000000000000000000000000000000017b32e613cb38b41dcdf3c8bb9187d731546977fbffd79fa7f66e3d6aaf9e1af6eca2fcdc260c8f90818d7148ba2f4960000000000000000000000000000000007e4d26606a47c874c20e8480a9f5815e5b577bccd783b775d10309eeb3d2102c7a0abc3324679e44362f09e7a4ada67000000000000000000000000000000000cb6f12ac8b49cfa36b957591293c87b21af0a949c55a28a90ab0fce88fb5cb7645e20ab2edd284f0ad1377dd95ac10e0000000000000000000000000000000014c96b5dcbd3150eeaea5c2bc27750cf88b30a91933a3233a4d1d9b357a80cc20d135e43a344e718dff5c79045c31f860000000000000000000000000000000011798ea9c137acf6ef9483b489c0273d4f69296959922a352b079857953263372b8d339115f0576cfabedc185abf2086000000000000000000000000000000001498b1412f52b07a0e4f91cbf5e1852ea38fc111613523f1e61b97ebf1fd7fd2cdf36d7f73f1e33719c0b63d7bf66b8f0000000000000000000000000000000004c56d3ee9931f7582d7eebeb598d1be208e3b333ab976dc7bb271969fa1d6caf8f467eb7cbee4af5d30e5c66d00a4e2000000000000000000000000000000000de29857dae126c0acbe966da6f50342837ef5dd9994ad929d75814f6f33f77e5b33690945bf6e980031ddd90ebc76ce,0000000000000000000000000000000003c5498b8c2d4765a270254dc927c6edf02acf0759540ddad951ea8c097bddb949ea0bf19942accd615bef21e8572dff0000000000000000000000000000000004c17bb648909bdddab4dd86560cb6b341e96f58c515ce471281f226181bded16b358b56d72e363f9ec491b8a9dcd92c000000000000000000000000000000001828973958204f8ab8cd13f5af5f3529f368a149bfe931a8002b61a61895457fbcb0cc6874631bb55799c884b998d8b9000000000000000000000000000000000f61460bf61bbf3ce38917850bfd3cece1e3955ce29d200c6f8aa89076c70919c02668678edc0bcf94efc9e9ff6a650e,4500, -0000000000000000000000000000000001ca1141ba9542c56de8991b313c6ae42fcecb6751b0b81b8cb21ed70d5008f7ffe831766b89880a7fa6dfdb09a2cda3000000000000000000000000000000000e6766b17db165bba564ac63ab88d3f8f5eded07a40b48644e60d3223d30458e7dabe404cab8d6f9fe135712ef0b1a43000000000000000000000000000000000dda3e6c87382fa762510e5cac721fd2b654f002f5b9a3767a8c6d651ccc582e80e3f68d6913cda30f9f51ebcfc7c98600000000000000000000000000000000059a7dac5bb6b504f2bd603d486700fe22c14f25254537b2c9079c2b45d36c7ce56854c5699cc7649b533194f51a9045000000000000000000000000000000001755d8a095e087ca66f8a118e0d2c7d5e4d8427dda8fe3049080f4aff12a8746f8c2679c310f4be0d94c5bef0414a7a600000000000000000000000000000000069c84c6419ed5c0441975ee8410065a56c65f07a4b545ff596b657dc4620c7405fd4d092b281e272773d2281a6359a8000000000000000000000000000000000e751ccbd475fe7eda1c62df626c1d37e8ae6853cc9b2109beef3e8c6f26d41a5e4e0a91bbc3371c7ab6ba780b5db41600000000000000000000000000000000184097644c9b44d543ebc0934825610590cc9f8b17ed08e9c06592bf85591d2702b18cf48a70b378926057e541eb8ac5,0000000000000000000000000000000002c6104b3494fdef86d53f87bea68d313188c0908b935fb3b9f636ccd401c6e9cbd33bfcdd437e1a0150d0e4b9c3a881000000000000000000000000000000000bdc88396f807d1ba8d4d6e284d008b5e40445ce32c23a0178824fdbb6db3c5aede7687eaa2f12249125cded57052ad2000000000000000000000000000000000c7004365c1d3027997b55bd258dfc61ae07a762666fba2a14aa2ca116673fc03a6f694c069f53cd915fef6d37513101000000000000000000000000000000000ec17688d8f53e2c92502091c859cef4fe9a57ae984cb1e72686bf1f0656b10246293cae4b96214a38dc76cf2709bd59,4500, -00000000000000000000000000000000090f4b85961ce97cf7f99c342d3627105d790f611e19721a43d8a0febd67ae393d77a02b999108efb56f0397dac22703000000000000000000000000000000001112f23595d1613c47486eadc37f9b1ac3b3c3973b3fe964d3b67c3996fe2eacd9df5c287b0cea8e9475d146fabcf9e70000000000000000000000000000000018f46f7ba3c9af34c1025c2d460f0be966e68944928dbd55cc7fe00e5def598d80b0e3801e48a74963c974ab4727a52100000000000000000000000000000000096845338d5cd2ac44e097607d6a1a05c241eda1941991ae9edbba965d9029032c46da7218b5b2338e6c58898bc4a820000000000000000000000000000000000213e5d2d46523203ae07f36fdeb6c304fb86f552fb9adb566711c31262629efb0b1561585f85d2ac7be174682229bd8000000000000000000000000000000000b3336b5a4f7c0d16db9615e77bcdd55b7cb5b5c1591d835f34f5c1f1468e3cef954608667fb97a32e4595f43b845612000000000000000000000000000000001869606dde1688e5ae9f1c466c5897fce7794f3735234b5af1ad3617f0688529499bbdc9f0b911840a3d99fd9c49150d00000000000000000000000000000000001bfd33df4a6059608ada794e03d7456e78317145eb4d5677c00d482ac4cf470053d33583cf602feb67b6f972c99739,000000000000000000000000000000000a44e6a48ea0a95667f607ee66290cb0094c964baed779bd6656941db28e30a7e9effe49a617be9ab376af4f535cc28f000000000000000000000000000000001933b87310bf5fa60b1abcd13bb7ac3f2ec0a278f6a0a70c953a2905ac1d3bc5a70cf1da885af45d1c7680bb4f7ff74c000000000000000000000000000000000597ce9f1bf7efacdcb0250427d0341e142226aaea060983175ea149912c5c4f3019fe87be6d87d186a8f562fc3059eb00000000000000000000000000000000198b5a891722a237a5e23e3004798c8d3f069af3267152508e283b4549fc5e8388330343f80e606eba30af51c99c7020,4500, -000000000000000000000000000000000aafe45ea7cb8b450a51263eebc28c1ded662972bee512e24fddaf64f43b74b66032523b3b104a4e9f6b62394436c6710000000000000000000000000000000015cb27e1fedfba2d1679f78a388f90b22bbf3e7d090f0ba972fa8e72f6e31c446f628fff929953712ef6e425d16eba5c000000000000000000000000000000000df9931893cae713042bf722db6ce394b6f346587278a154c271d8511e690417eb6dc47efbcebb7c2fb9e77f1de9fde800000000000000000000000000000000106ffa395ef170c99bb5742428ae88fa4fd7a94476985c099e3b700b7403d083281fb71a19640c6bc2321e27bcb33fe20000000000000000000000000000000004ac6e6077d4eddd0e23f30cfd64b7aa1525c85424224e70c15d7535e02aea7a312ef24ba2dcf70b926acb851da2530c0000000000000000000000000000000006ad07d3e8f45cedfb4279913bf0a29e37604810463d6020b4fa8c8c4977d69cffaa33e1149706f04eb237194dcafa520000000000000000000000000000000002c536dd2f05f4a7eaa33fd884262b22a2ab2a88e7b63cb08ebb67fc0f143da7d6b18dd394c424161f7cf703acdc82f50000000000000000000000000000000002d1d9ff74e20ea9b03c478784f57e7a58a21ca2b1e552319f33305f367f5ae4daf8138505f953db4f86c0ec1d96d5f0,00000000000000000000000000000000047c2ccda315b9c013e87bc9168b3b8dd6d463403f1cefd824fa9f93a99f4c4f98fac5f97e4237f76b1ec91042f99bd600000000000000000000000000000000036861fd0a69cbc851741475905441b51af12c5b2aaee6ce9a27a01a43db810be9c7d6fa401406e98e327703404b83a5000000000000000000000000000000000310cbdf53f6cf8d87e2d178869bee4359a8dd666986d869761a79963680a33ea3ecefd40a1e558acae5ded2ca04447300000000000000000000000000000000108bbb28c73ed7e76a51a78e4d15a2c88c25e05c7127ae89d4347cda00be231b5e70e0b0562caddd4a7083efa4516722,4500, -0000000000000000000000000000000010b1f8b1c492a56936da905b8738affba6bd29ae5fffd40ba6b31325181d3b489a81b23dcb69f6e71bd29bfb388e5a8f00000000000000000000000000000000116a115303b4774da59844e457844232d088062d920db67b2a8450a194be7e5340ebd4d106454fd9a03c8f50dbb1e119000000000000000000000000000000000eb521edd61b38006cffc43ab72d395d669dec196846fa4d6d43521da6c2fc3bf0994ce7556a3cffec7751b3bc5703ff00000000000000000000000000000000073cea36eccaa1c78deefb6029903c2b6598301bdefa9759719c3b590fcc5a6a4d3d4d19f552b33f4a3126a6e6a84486000000000000000000000000000000001913ce14bcd1d7bbb47f8efd92d7ffd155ed1990a1dbf1ee7d5e6d592a92bcbec6e865199362950afd6c8fc49b3e10a400000000000000000000000000000000020df729079e76cf06f84e3355e683e093dafad38c2ba92cf7a9faa0515f2f44d814f971046ea20116cc4b0014d7ec350000000000000000000000000000000018db123e05404eea8707f9356f417c3966312b9e41765a6fd8449879ddc4c9850c38434481b235a5bc35db1b8ee86d43000000000000000000000000000000000b4162715717e9065a3849a9294cfe39b351e57ab5a6790f3e725ad9fbf0e4b9d6a3554e872af9c37df33bb896dada5c,00000000000000000000000000000000137d23ed3fa0d7e5928af8d1f4bdfdef08e0b4c0f3bf6f51ed28960ce9805eb8fb254233bb18cbfecbadba95e112fdb80000000000000000000000000000000018615147d7a8cce1dfed6de25cf2fb52f54a243bed4913e20e66673f47ecddad9c5e4ff9653f522180de4b90ddb3ad17000000000000000000000000000000001521f12116b13f785b5211aaf438aa6668bbfa318cf0ed6d91aae963f6f00d32cc5f25d3a02bd902ccc25f847ee2db830000000000000000000000000000000014263b23396f4facdacf13c79864157823db724350bc640abf8fb6d62663cec1069eef9db56817660510e2417b51c616,4500, -000000000000000000000000000000000e3925fa085db73c1e67b29ae90f8773f83be5ec684402e8e2360ffee8a8368911e584843e42b0d470de78591df6ea6300000000000000000000000000000000075c7efdeeb16609b4a47ea442af4d75238fb7534fd96cb236a7886809d6adc2b62c8ff72bdb041bc51c1a71b68219e300000000000000000000000000000000088b4eb0dd185e51b737d797334590e982b7b0a5f109fc7d0524b2465c2c0457964eba5a6d2d4d99fb628f21f15a776c000000000000000000000000000000000fc79f6b38f3356972669290eeadcd992a22bc1191606b663a1e148aa58db3938f0fc65e536bc5811c50d9c7f03d3e370000000000000000000000000000000008be924b49e05c45419e328340f1cbcdd3350bacf832a372417d8331c942df200493a3f7f2e46ad2cdaf3544cfd8cd8600000000000000000000000000000000028cd100457f4e930fc0f55996a6b588c5361816bb853d1f522806e5ec1c455eb200343476feeb07ca77e961fc2adc1f000000000000000000000000000000000f6adad0a3bab3610165be2fadb1b020f25488a0af3d418b7d7cf1165812e17aefcbc23308ebcd31d22ba4ca5773dd87000000000000000000000000000000001657ff792e3d89d5d35767bd0cc788411b0420665a5e0704f4d2399b9d9a5ad3c027ee030fdf495e5a6e2a4c69d05712,000000000000000000000000000000000038f9df6c14f84b8ef8045010c8973e5c2f8d2e37268f6a674298de7b15cae82361ebbfaa00ea1cb2653c5d00886b45000000000000000000000000000000001376f7e2d5621aa9d6f7ce45ed11de7e0e1095ebeea976f78eb83189c6852ee199840c14059c233bc3d40efbeeb5eb36000000000000000000000000000000000c7b0e53adf4f0fc5172f903e3fc479539348241edc3e277f30ae6b4fc419aadcfb73a8f8a09a1ae1dd885a6250de0040000000000000000000000000000000007a00b57ecc8b056436ecacd7e0fd346b906b15042e9a700f54f8c3b1d251c566e0c55bd34f7a9e30f1566b7f2ab16dd,4500, -000000000000000000000000000000000b87c47605fc060a8e3677e84ce9d14b9309360a13c80d040c625fbf0108f829300cc1fca409a0f9c96311cd4a9a21e60000000000000000000000000000000014c4088f1e7935cf6a1d2475b84497ce6a250ee2c0c991fe51a2f2836388a354824b02d9cf215328dfce3f546713e21100000000000000000000000000000000120e59be3ecf35674eac6cdc559599b273f13f28a529770fa156f8e519734c451eefb35023639f32049cd19ea0d945a3000000000000000000000000000000000f97755b62a8cb8f861ea02c77819f0b58181aecf612d92180ba9b475f0b4888b922c57f6a1c619dd5514620a1cfd9e2000000000000000000000000000000000a5048d860b997a9fb352e58284ebbc026622d9be73de79b2807a0c9b431f41f379c255a2db0dd67413c18217cb21b7200000000000000000000000000000000045a701a3f46ca801c02a5419c836b2ab3d74ebd6f4fd1e7dddb1965b49c9a278f6e89950e7c35ebc6724569d34e364c0000000000000000000000000000000004cb55008ccb5b2b8ece69fac7283f5a9ef9e622e2a0e42bed5bdd77faa550882643afc1759b1a327c4f2277e13a3d4f000000000000000000000000000000001690dee40c6c824dc2588fc47dbf93f68ac250b9357e1112db72ded905ed7b101b5f877bdc42d56afb5b6202403a91c4,0000000000000000000000000000000012662e19e41bfacc0c792f5183596bc7f1986f9bea72c626e187d72111b6ef3f36f5afeeb640cfda99b7044c0d0b846900000000000000000000000000000000050ba08e1b9fe95dc67e6ee1ce60664b291c80fdb59729cdea75dfd18f22fb88f837b439fd119c46c996787d3008194b0000000000000000000000000000000004ea0f488fece967675abdd3c42f8fec25b547cfc45d42fba14bbc55ad7e1a75296a679113d0671cef0aec0c2165f4a0000000000000000000000000000000000f617f51800b09150a7560505079c785ab45cea4705992fc0325edaf4ceb30e1f0bec35a31898db5f810685e55634076,4500, -0000000000000000000000000000000005860cfb6be6720118623d2d8ba05e686df22744b948421dd3cc1b1691e00d9b5d00d00195b4acf7a7b043f764f3f1c70000000000000000000000000000000012632a3313dd611e8d969bddd556c2d79ff387603462ac78ded3a842981697bdac34ee6f1f4744ed2ff16100874ac24000000000000000000000000000000000112b94c317586e343acadeca611c485c3ea172bc10dd39158c1e678007130062a921b53826d7be6286963ff822f1066c00000000000000000000000000000000040de8c0dadd2a6c2a7ea0fa43e1a5f2f5a6be3fcb0de6875d8cef1ee2daad87125d12f6869c4dd3d931b296f1df2fb300000000000000000000000000000000153cec9690a6420a10e5a5a8ca46fd9d9f90e2a139886a07b375eeecce9083a5f5418e6baf64ef0f34176e432bc5343a000000000000000000000000000000000d87c1f37f83ae78a51af9c420e2584a64337d2d2dd8dc3b64f252c521901924e5eec1d9899594db5e64c93c7a01ef020000000000000000000000000000000017078538092ace26cc88b94360871fc9a6bb9992172158ef3a16467919955083accf8d55d48c7ec462a743dbbca7b448000000000000000000000000000000000289b703157a02fc1d687a5aa595495be8bbb3eb0d70554728255a44b7820e0ee82d984d5493c800f1d9d8ca0c9381dc,0000000000000000000000000000000019c774e968049bde2188e844c3413203bfe2c4355edc8cbc2cf6f977c34c0a42a206194e6eecba3c97b24558048f3aa700000000000000000000000000000000081ccf6f111575a946341759b9faa13f3608998fbf4ea3b547804737e30fc7e33495caaf2aa328b19bd48315c5c7f9e2000000000000000000000000000000000a4098536041cfb808176c7cd8e980eda613a2b390e8d63d607caaac26db02fccad6d87412b90cb4b3e186bf9ccd31be000000000000000000000000000000000d3c784c6587b9f786c06099a62aa639f40535b512ac2440912f04dfcd1cb5851b7378f381fcdf02d4e58312eb7e442f,4500, -0000000000000000000000000000000006fcd2c4fe848e9462ba1112baad39031c210952adbdd06293a622ffe2d1c6e4fcc8773ec8913717018b97bcb9a554fd00000000000000000000000000000000130a97442f3273b7b35464545e7351faf71ead9b8996c63889a45945ed82bba29bff5014776c6185219a5234d8475c92000000000000000000000000000000000491d571bac5487b866022a0714be11b38bfb296233845cc434a50be1d35f516b8c6b046fe3d0a8f4f95ac20eddea01b0000000000000000000000000000000017e34b04e6fdf152c848f2432b7bd84b3dba3915f06eb77efb8035750aca9d89e92e1d1bc4871105c440d639e8d8b05500000000000000000000000000000000057f975064a29ba6ad20d6e6d97a15bd314d6cd419948d974a16923d52b38b9203f95937a0a0493a693099e4fa17ea540000000000000000000000000000000014396ce4abfc32945a6b2b0eb4896a6b19a041d4eae320ba18507ec3828964e56719fffaa47e57ea4a2e3bd1a149b6b600000000000000000000000000000000048b3e4ba3e2d1e0dbf5955101cf038dc22e87b0855a57b631ef119d1bd19d56c38a1d72376284c8598e866b6dba37530000000000000000000000000000000007c0b98cda33be53cf4ef29d0500ff5e7a3c2df6f83dfc1c36211d7f9c696b77dfa6571169cf7935d2fb5a6463cceac6,0000000000000000000000000000000016fc7c743c5ba747640a6494fb3c30caad5a1e9719a1994d0ca73bd1645fec118a2887acc8876d105102241c10274cd300000000000000000000000000000000058a42a0095a7388fba7ce71dbef4ecfd2018c3fcdde14afd2be26588de4689d8de757e1e3ff22645fb8c17aa60265850000000000000000000000000000000010bb622f649e346834b95e82f93ae83c71c0a65df7842c4ba88df7f6eccb0217ca9377167a6d14777e0474c24821f8d70000000000000000000000000000000010c180c685ea3d0146eb82c007fec3efd129880f18f838f1cd2f80181f5a4884d6b5cc8247430fb0c1701a57f9d1d485,4500, -000000000000000000000000000000000f1b8df4e8fdfe32eaf227f5af9f2befc85073468f10b81d32d0e126fe2b0cc8e8adb8afcac73213b6ed95e8e843b97c00000000000000000000000000000000004e3fb435ae0fb2d8bd091f250aefe5922b353a64e16abd75627737f3bc56639f8b40652cae69c73ff1969925b0afdf000000000000000000000000000000001003aed7cfb00efce49d6b1a8eba27df87479a4d37bd7fda6121549483b669a1a761204b0dd28262bf27e5c8e180540f00000000000000000000000000000000114fbca7caf782b3296d0b26b4c362bf50acaecb8bc5726b2c99f904ec3d092d5d40991d0d30c8e79fddaa45f04a75d3000000000000000000000000000000000b6069a2c375471d34029d2a776e56b86b0210c35d3eb530bf116205b70995e4929fc90349a7db057168dbe6c39857970000000000000000000000000000000014251a0a154731f73513b99d830f70b6fc4bcf05d11f52d2cbe9795ee8ffc5a5f717ad25770b8ecad6d0e9f8066e0cba000000000000000000000000000000001172684b21c4dfe02a55e13b57bbf105c954daec849d4c6df5276b02872c004fdf09d24f4eef366bc82eb72fe91bf70d000000000000000000000000000000001151aeb9441c5a8fabe80867b5c791420645241eae1400bbcc064d75bedd39de2ef585138fe9f65725efa1b1e5888d03,0000000000000000000000000000000019419b635c3742cecffee02ee7e2b1f18ee9ff15e647ca0abc4398ddc421ae7e0444e3c1ec377def9e832d8e64fd40e2000000000000000000000000000000000d9b4abfdaf3b4c7bf00fa07579befa10a3418d8fa0f3a9c31e59ae48b0de50fc8e6d583aaa4d0fe6048bdd1a9c60eb60000000000000000000000000000000003c96d57034ec97c4abef1c2c81f4d4b0f4b6eb1e9dc5464bcab28572555b9b874df80325941501c3766fd7e06bfe7360000000000000000000000000000000002dbb3d72385b562ddcb9a80400ab3770f00d22b880cce2fce1641042b9da669b22b2fbc97617648c25ab644e661e2fe,4500, -0000000000000000000000000000000017faf481fd4cb0c373d21d7caad40e93d9a86e62d26136892fbcc6f6e48205543aff00c45e82fdd1d3e0e733de91e7000000000000000000000000000000000012e14fcb9ad4d9d15347cf004745ed4bd92097eeeb41c4cbcb728a234616363589d8f5ad4cbb61d31a8aa27627723c7e000000000000000000000000000000001513dad1ff27e053902e779e35d04cab648939317830144ea775c435a4b55e13fa2fef03a1256abf5c187487c25a774f00000000000000000000000000000000139da29de8587c7d0ca9237c37a116387385e9cea453b9e2003a37ede7aa0a3f4c1df55255897f5975b662be33622dbc00000000000000000000000000000000161b70d0f384e589d8117938602f3d696f941c24e3c1ca5a9be090b670456c9df315d6fde52daed55c9d8335928a7a3c00000000000000000000000000000000186bb9e6f5ba70dd2c66a641d3b711844977939904c59946d4e9f49ac2d8c00890a43ccb20d4a62bfff63ce4a0a44e8e000000000000000000000000000000001995b9d697bded656236430e78726f0f6ef963db9a5a24d455c12db38aeab0f8629e5dc2d04920156f2a057d69613096000000000000000000000000000000001119b13caf82c18fadcb65c9c166914bfd822534bb9def3feae6c9e572c97c84e97fab3b345cf59358436a404075493d,000000000000000000000000000000000d32b00154a5fe75c576c098419744ac36b911ee800f94bd598ff9b6adcaa39c836bc158c5d6af72c9e715a242d0fe710000000000000000000000000000000006e057c13885d6c05f5d92061fdc4d532f10d31d472c371e71367fef7c5fdd3741e665321d1119b895660fba3770431b000000000000000000000000000000000bfe695c3364e15479741e974f838649e789a76d073e552aaa60981fbc6d185eb7b297fd59e51535965214a02f5cd67e0000000000000000000000000000000014f0a27412248e3163e5f82fed02a25d953b336b0201692f08a3e8e9a9d223b736c70c1a39826a0888fb02a314e223fd,4500, -000000000000000000000000000000000c118b147ee3489f30c6ecc0256a314ab674110588e8b69ca6d265fc270c3e5b767817f861140cca5d7c6be4012d1ffe0000000000000000000000000000000014800790654726959fd876b035bade0da744fb36ee5b304f228663a531345120267c55ac19fd66022752010e5bea7cb30000000000000000000000000000000000193ab7ac2f151750356b6e178557460c9c2672b1736d19a20e3fa28082479ca60021aa68edf2524f1aa826ee70b65a0000000000000000000000000000000015cee9ac55ab45abbc57d0ea6ec9ee49f6c59f6b94f99589dbc08ee877d3a261ad77f5473fedd72ed7206647eeafb6ea0000000000000000000000000000000017d1ffcad218efd8b09c68eba34dbbc30b0a62ae250368ee37e5f6fd40479b8580563416afdbd92c0622c341331e20a30000000000000000000000000000000009f0eb3805ed78aa3952a0a437966258ed38cb72912756253a7a2f9113f0dd9a4e187062b0423e0587d93e904d88f50d0000000000000000000000000000000001bca57e985906695e14882f2aaeef75de5009e8717eb59962e978aa11e9d0a4d9a9e203df774cb1e993b1c6ecd6048c000000000000000000000000000000000695b11cc32740c91546eb7d554ca8b1f3afc942ad977345031be8b94b78b57a87ab049ca2d3676e039efccbf24d0c47,000000000000000000000000000000001566022247ce012b7de92c8495876b4de91c36448f4f7e00f6e154185d38a735e701dda989ae9e37d332a5e60af5d06b00000000000000000000000000000000065aa42560df7990df2098827a55ceaabf3ec592c53d2f20e5dddc1481ee64381accbc8e58601428d33589b3af78a4b70000000000000000000000000000000002d9b0cf8bfd1adf76bca80ca351a4340f02434090518807e07ed76440497042f13a0cd7a9c30086872d6f145808fb290000000000000000000000000000000015daaa131431e3e78a6221091640811fcf88c835ac975a041a7ab50bc1d06b80e6a3c9ae77d2390fd14cc9bb009b47cc,4500, -000000000000000000000000000000000ef203fab794a0ef29eb2ebf00076134e5932e27c99d6d445695b9df2afe7563602e318caf5d44724a21790ca0ab0d180000000000000000000000000000000013b9b1b1d3e98b61b0f1a0ef3a1a4ceed57b6c01849a4ad66a86332b3d27022cfccadd3567e6709d2de5b23b23dba43f000000000000000000000000000000000c1fbace49684f4be32ef6178ac3a95ea3f50b11494340fb73dc5391d50bcacafb3bf0f2631fea9c4ec47327d644489500000000000000000000000000000000040f82812855aa3e3aaba826d5810c1049cf44e86e44e23cc6da437971b529d2f2676c73e1fb9da52640c981fbd710be000000000000000000000000000000000546a0cb9d9f1ef9ec4a1e576fa0047557a56c0217baed8691c4085b88c84a0e12d44043aab8671393d02c4a764407ee00000000000000000000000000000000131884c1386980a181353548da9602db70ab495a661e76235c4b0a32b54acb0dfd8846e17bebd731e8041c4aebb8776600000000000000000000000000000000135b3db43511dbd8b3bd5a91880d6da1a2bd1383000e0d6f0a521bf88a5836a3b5f7cb9c0c02aa861a1c2d339f3c11f20000000000000000000000000000000000e1337271bd3302a1cab762161ccfbf2a18b7800e6efe58cf897d4adbfe4cb3bf14f4b59307fffc548179bda70c18bf,000000000000000000000000000000001290bff629c93d992ad2cc709317c48980b0e56a32fe239258c7aec75e4523e0bc0b81319e100d10568a44847869a8d000000000000000000000000000000000055d9098e08eabdf2b883df35efebec9f6afb16d651ebaca1067e2129146268664ec51c8a4f28f13a250f3e9883053780000000000000000000000000000000002424dab6f0d18ea8bdded2a72bcf87c13307d27d53e8ec35e91eeab97fcf3398135fd436c530c609fd47a3508472bad000000000000000000000000000000000b25d0db1e28b98d4f9d3c77c0b71489c51186105d93be7fc2cf8c72b8abd8959340114635e705e698b0f257855ea4bc,4500, -00000000000000000000000000000000060d7a718dd02b147c265f71eb136d1f31781b12a41866b4f86d7374b93dd10058c192cc0fba928373b1526e1a5d7d7f000000000000000000000000000000000cf29275373c0573ef22bf87919faf5444847203c7dc6d2e18986152cc294be04a5b1a4b0536797158113a15276c4fc6000000000000000000000000000000001016d5b9d4d200d7b4b7cc3836b85d6697fe14db350badba9978c7b56983dd1a7e572640ee0372b0a4e2079ff4c1abf2000000000000000000000000000000000f2768d104d895473ddf8c6b3cd0e7c22458d0037eca6365c766879a07c95037ee0de00d32c974d767080935abbe0be100000000000000000000000000000000113dc3354146ca79eb103b31b61fe8bc6f33dcb9c59a7c39d989bd9411c1afce4239034f84e6b00a084be061c73e69c0000000000000000000000000000000000ae33bf68f24978c7ea9fc58d8d76047ec45d01fdbc880e6a5ba02a22a49a3a8253afe0678ecfa6013f4849da3401df70000000000000000000000000000000012c5b00376a1dd31378ec44f2dc8e321e17185d903cfc5c15345a01c33f2f151b21b938d31816550594a7a1e7216c5b00000000000000000000000000000000013d79f825c44775c68e90932d0496a5cae53f04a1edb19f8abeb5948a3dd325dfec4a8b6f58c7fbca9cf3c09b909d8b2,000000000000000000000000000000000cb2998b4e634bc83b5585b0683b7b561f260eefb826719bdc3c95e8ae51f8f7b442d75d69e0f9228dacde2ce80ef4e60000000000000000000000000000000014d30d1c02122143868ea01b454a4f33432d875f8ba66e6bb1e02fc161bb5f9298e673339a9183a15759f8b94b519cad000000000000000000000000000000001068bf3c768e8c9e9058805050394ea820b5f60bea6d271f8e1fb665d3b7931ab0cc03dff4cbd24577b2c254a956e8200000000000000000000000000000000008b7f4148bd1f4926d2a84497b60a48701057ea08855bb9a2f838d2464e66360a59d058d9072f1416023cc72045af558,4500, -0000000000000000000000000000000017b9ca4349fecaa43ce911c0b256680edb8a0906ef5460fc4d2004579336df1e19560fe960a7a7cd74bb6e8272e08960000000000000000000000000000000000d5b96dae738db59cc67a51c61bec6deaeefaaa51e3259243fa4b142ef59676231229ae386ce699fbe18c4c00bf9d49400000000000000000000000000000000111b79f4b68dad16550a13334d09dc38336a75a5da23a17b5064e2d591aa3dab4c2e982a9f730a7633070504663a24610000000000000000000000000000000018f6d3616a7eaf17c805a88c9710039644d01b61aefebf76717ddcda6f4bb34aa15702de1e92bdb27b27f3409638da900000000000000000000000000000000006ccaf6c08f831be9c99a97714f5257a985cc2a29b5f5c81bc8d794dd0d8d1a41eb5413bed654c0140dbacfd0dda9e1800000000000000000000000000000000144e9cf91580800dfaa47c98ff7d002a576be76d9e44ae1f8335a3f733e1162af0636372e143174d872c7ea89f4c743900000000000000000000000000000000101e143b838c8a3f5f80fb1412081091b875230f1e2f9cf374d4bcd595392f6daa9552dbb6d5834e27b1b3dafe061ed300000000000000000000000000000000072463400b3e875395a1cdd31d73d51396e34347cd86d9f6f43f42253b3cdb24b89ed7434b1522af95ba1ee2d29ed1bb,000000000000000000000000000000000a7843a1d67360b8a6976aeda2e4e98f1ea229a4d84b947dcf5ed8215173d5cf783920a7714f5b048778df30f01a0bed00000000000000000000000000000000035663ceafda9e5bfe934cff725b36b258f12afe749f907a560a06da4abf8380853f8de31adf14d62cdb310d8740e29b000000000000000000000000000000000f210d576aa5d4cdf5aefd8e55be099c422debc217ddf0151b8801f7d16456c97d1e134b40e6d71d296ee2518e50af9d000000000000000000000000000000000219efb35c68540c6bb0ef224e68dae6f7d48425c2908440072f5f63eec3c8e750b559c73e33464d0b5cdabb50fc4d3d,4500, -000000000000000000000000000000000aeb5c087644595d0912879f61959d2731ff55260c682ed2bc5fc55c13964ef7c1f70aeb55876d2264d558c31371ca69000000000000000000000000000000000e173848f4570525b03a2b2c86f4dcdb8b28dd6d18c1354cad31028eb1b8b44432c2346edaace093e3954c7fa6d338a4000000000000000000000000000000001949b0902506d111ef6318edcd7a58ca4d69f5804a028aee73c3786cb2db168c6a73b77194f7a021ae6ae43ac78ade340000000000000000000000000000000017c5e28ba6103d97e2f3d3611c0c78f06406e0da8a49ae29c7d460b52f75136920784cd500aa3593858b877697eb8424000000000000000000000000000000001354146aa546754e10ada6e0fe98f04f5f3a3f8a8350d0295e02b8e9c80735b04c3061412e08ddb13c80ac36e5638e540000000000000000000000000000000012ab26513534b4dc1b71eec46b73199c4157ba9369e66fbe4d2d8f62237fc7c6fad31854ebd878f989b8c5cf35c7cfe0000000000000000000000000000000000eb731bc99cdadf7f2280385c7e17d72d34bcbdbdc725d5bc94e841036115e8cb95df08084221696f9be479821fbdd7400000000000000000000000000000000143ba7d3f66445249d9a81a6949f24ff40e7c4d270fa044a8b80200a4369b07806c5497a0ef9e9dbb87b9e63694623ee,000000000000000000000000000000000ce704e650605f747cbc0bc76e82de8569ba7b3d897eac2bf5f79aba17ef4c989731e959c0bc0b7988000a9b0aef39430000000000000000000000000000000003cd3f3d978d6c85d98812ea0e3d21149bf4151ad1bef966ced124ad62dc7cde55f16e8d08bb1ad54d3a23bb73795d8f0000000000000000000000000000000019d37a20fcf6244c2898b271535e3b8f279eaac5d8fb1ba142096da383488eba28a21d038d7a9d3f9e8a008d6d3ee1d20000000000000000000000000000000001ba9c1720a4ef07ec752efa1ddb629505b3586af415c916fb0ed2953cd8943d9343268f438db860f0bced3e690a66b0,4500, -000000000000000000000000000000000d4f09acd5f362e0a516d4c13c5e2f504d9bd49fdfb6d8b7a7ab35a02c391c8112b03270d5d9eefe9b659dd27601d18f000000000000000000000000000000000fd489cb75945f3b5ebb1c0e326d59602934c8f78fe9294a8877e7aeb95de5addde0cb7ab53674df8b2cfbb036b30b9900000000000000000000000000000000055dbc4eca768714e098bbe9c71cf54b40f51c26e95808ee79225a87fb6fa1415178db47f02d856fea56a752d185f86b000000000000000000000000000000001239b7640f416eb6e921fe47f7501d504fadc190d9cf4e89ae2b717276739a2f4ee9f637c35e23c480df029fd8d247c70000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000068edef3169c58920509ed4e7069229bd8038a45d2ce5773451cc18b396d2838c9539ecb52298a27eebd714afacb907c0000000000000000000000000000000004c5346765a62f2d2e700aadccf747acb3322c250435ce2cf358c08f1e286427cabace052327c4b30135c8482c5c0eb9,00000000000000000000000000000000160d8b4bef36fc3d09af09dcc8357067c22e421f3811deea66faec42a2f00fa4aceca8725cf99062613126a9fd7bf7210000000000000000000000000000000004e8691a42c8f3ce0e7c0470446689e9d2b3cf57d55fad7387d624857f977cb9c6864c87bb4b6a2c17538478ac5fb5960000000000000000000000000000000015e20f6baef033efbd38081d5a10eeb3c67d89ebe5cd652110b778313c9e86cffb45231616d5b67e9ec8b7be15980aa9000000000000000000000000000000000af75dc221050256015fecc2bd8113b42afc9c624e5d28d7ff8312af499e34a603d66a4304f263729b440b6266538316,4500, -000000000000000000000000000000000f20a07526a082e88630a0256d134a8a5e8ada07b1cead39ee838dcbb30904e9016107fcbdf1f8ba182308dbe0b043d20000000000000000000000000000000014fb7732f67abf60c03ac902577532d0acadb5f3db0d6397a42ba693526ad74f2c61a0195bdc9704aaaf12e65aa6d88b000000000000000000000000000000000018cec4fb81c85d304588d11f8b9c51f5a053df11463e5812a1b2e6c7144522ba36bb91adf219892d0007cee470032e000000000000000000000000000000000b8e52d958a12a9037e8be9bc0d5045cade2d6ea05c6e68462b3a30b5d4ea34e5fbad173761e4e216b2e6958c8983b28000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000f6fdc4e5dceb555c9eb4c912fedbfb3cb1b842345f73ded02cfaf8d397c4378809721094aa4a4113a368e0787effeb500000000000000000000000000000000143ac06258c579c11c05569669a2a10babc63ecc86f85c91791d8ea48af700a2067c5f13d2700b8d5cf59bcca8fbf7c6,0000000000000000000000000000000013edd8f016f6af49e9bc461ca14c438a32eaa3d1270a5acec99a666aba3f0a7e7eccea81720971cf4432bfa94cd18392000000000000000000000000000000000dbea5617e44c82da828844a5a4a1426d43422fd0158204a99f53cf9821f82f0bb0130a2123297a6941f695e172d9c5e0000000000000000000000000000000005f65a445e9f2d57dff2b210209f9faeb1c8b446454de4724d990aab20bd68362dd7ceb5b95de361c129855abba83f7e000000000000000000000000000000001219ecae79d62d3039e642369353993b1ece049331f06be256f06b01a1c3b0c617221c8d8f0bf4b6a0abe1191a3ee8e2,4500, -000000000000000000000000000000001468cb35a60898ed129f30c261b8431df6a154c250ec16d85a22f8717593b2c21853d123da86d977a7938c5ed74ef23500000000000000000000000000000000011f4e28e31b5f9e6877192a5e632d8c1ed7ca0c42e6e9902ca68f1c2de0f648c6064436012c5c7b14bb8d1078e02f2c000000000000000000000000000000000b25114b2697ca7eb1e6effdd1054893a188fd382d387ec098f846c1137a9b9baad01653b963a0b0bf3cb50c3ce3563d000000000000000000000000000000000c1d241cb03e642c1752b1e1886472477c19a2801ec032dc220c3243952f882094119bb92b621b654b766bc900d2d4f7000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc500000000000000000000000000000000192f3e8ae2588f9223de77f5e872115f1edec96d6a0f403a47879410c2562e79853c9a706e423b83fbf3154234edb6f80000000000000000000000000000000015084258d58fd1a07bbdb2e90df5a56ae15a787037eff4fe55f660e45f04820c6fc8982303b5e82074cf0cdcbde61307,00000000000000000000000000000000158da32df45fe3e9102010bfd7faf3fde936bb8e52f68262ef479ee825a0d7169ff753aa042883a5403103a9bdafd2be000000000000000000000000000000001800a5776a47f52d2af08144364a6cd7442a0e2fc214a2d8d285a29bb7bd3a0293e89f0a1856223a527100d0abf12899000000000000000000000000000000000a6079d18ff3367c47fa61a57a967b782f3529bee93f452ecebd4f5c404b3e1769c100da9b8aee4258b5191ae1dad9a90000000000000000000000000000000011d3188a927e8f13aecf7f8637be6ddbbce309393a94fef77923c286244f8531d3e137e031d8c1af829891425afd53a3,4500, -000000000000000000000000000000000c80d4474390fa791ea5f2f16b41506d8ae13ee0993c8d31a07712687298ee7978a724999500c42400d2f788a5a36067000000000000000000000000000000000592705cc5a8875750a4e6ceb42aa3bef5593eda9e8212702a2e08ea70277a2a66526bc5237be33c8449301544da35e60000000000000000000000000000000000facabfbd15284c6433f17b0e6035d4fdd84d3ad2dd30a27d52809652ff6e7a684d7724697919100567ad0c3e1a26320000000000000000000000000000000006a0fc4e2af69ce15a356656f5d182a2cf213d76a6047a05a1a3375909d245f5316b91333d2141c0817438f0d87bb52d000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f000000000000000000000000000000000477b55bd7fff14e0d1807bfc21edb9481be01c12abb1460d78b1aafe42953730167e32e694c2ddfb0d442e8cea57d460000000000000000000000000000000004b884c6ea36f189dbc3c0e9cf88f08baf5d868579998f63b752e61fcce3cf2c901bb9b51959d3597c4ef53cff41fc26,0000000000000000000000000000000019294d87be784f0f8fa29de80d45a697bcb694b32f3f6d7641d4b08d8a7ebdad0ef78ba5ccafd6b7f240e1cbde019c51000000000000000000000000000000000645f7851644e1e7e255d0b3dca769b987ec3ff2c9eda42cab65dc39be2f9858c31f307d59f6a2caf9dd932d873d2b08000000000000000000000000000000000e8e93f39ce05a11d40f3b52262980c79ecc52939dd02b94df3e5034a57061d040b0c8894189f4626f37bee485712dd00000000000000000000000000000000001e0b7c9c3d7456b2c0ad842083e9ce2a00da91cb1aaba371ff4b9370f0f2c08f4b53b8e5a3030c99b2957cbe5f9e967,4500, -0000000000000000000000000000000003f629618e1fc3018bb836301ccdc59022f0a25cc9c5de6e4c31fa08feea525c83256235e4ec8364e77e5df478f5f62c000000000000000000000000000000001120d6af221ba6f4351bbee4c2c664a769adb17872646df2c408f70c99ea991ffced4eab50fa98be1bb9426915f125930000000000000000000000000000000015cd16b028ce3d58b10aeb84b783475d894ab3f0cfdf7104ebb4f3417a038107128f07518dce548271061cb8c97e88af0000000000000000000000000000000018379875b68bc26107f9a068e5034f29dc2ae7e8830f8e9ecddc53fe7991206646cda33d37b31a47a977b46be58d761800000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac800000000000000000000000000000000183b7b917aaaa94f0ea9959273ed4701102346be2a9d72531bd18fef908ecb0579a6ac10ed42a91f1147fc3a05b2e81900000000000000000000000000000000070983b1582a97d9797782e4f960a298aaa8ec509720495acdbf176d8ecb9ec9e041c2b5ed6b7dfb46fdeaae3fb34150,00000000000000000000000000000000040f355021ba50c9a3b2b4267668ac8d76dd88991be984ab5bab9c96faed6dcc6e8eac78ed29cd6f7d687dd55cc5d5b70000000000000000000000000000000017853cf0a39332e3c7d75b08b2940d693ac7cfdac46719787c22b55a2ab1036d6f95b68075f1c585942843aa486f17bf0000000000000000000000000000000008696feb333417a7262e8976d1546b6d0a9d5970095485b18efcdee8993b16f42e6dbfdd08d30c45fe4af6a5e203de07000000000000000000000000000000000ec26926720243124ca505c0e04923f3cf5eeca2abfdaf4388960b87c6c1713fc54cdd1c825e2ea359cc67b3bebfa2f9,4500, -00000000000000000000000000000000036570783711b381830e35878fbeb187b84884a9a0e88c38e84124515b470e6ac18157e1499026b27f4f731a961eaf330000000000000000000000000000000008382838c18d56c046a8db495babf8d14c915622d7917ebe10cf7da7ecb65f174cddb9e70d0262ada961b396c5511b410000000000000000000000000000000015f63ce982aa581dad5c71fc79251b7f6336c4e78a4a0f4cb6f87167cabd31cbec987d7af4f11dc6d693a0b0774864130000000000000000000000000000000015c001372fe0530a3f50fb8b30e75ff4b264d673e0448211d082c7a9018f583b4d01790019874596c59c68768cfa3e69000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b0000000000000000000000000000000010f7bf9f6711ba723bb71a004a90109ee22be6643d56d410da18103ef44a1b3d50f10c4b94222c7f05fd3c28acbdc8ee00000000000000000000000000000000007af41f09e6d0adcb1935d6a93ea1f6156fa0157a63f265a3a7ceffe82f6635b8511e7e8f21e8f3be7a73513ff597b1,000000000000000000000000000000000f3dd56c416db1c06fd27e18fb852c9e1662fed42005e253230a7a8f7c3e0b8ce637666e1d20952c219cd2068d6865f1000000000000000000000000000000000aff045afcbefcdcb5255805a86e8af3de881e5482188c487d15ad1b799cf551c1d48c7665028b05ceb2e82e15ea4ae5000000000000000000000000000000000e0e6ed04926aed1f8c6a4e13227bf2a99d9d6d349a9c86214373be693db702a0011b4423defdb7d842bcb6f722c70b100000000000000000000000000000000148b1af285c65b12eef498f1c9e57a673e7a3803088c56e32aaae13dad3977dda8d3e27809094f8d8ed607239610a1a6,4500, -00000000000000000000000000000000074d78cdd35ea17a3013e2301fe9f80f2d20d270a25fdead37eed7697a52d152612543781763e6035fa5452ab12cce25000000000000000000000000000000000e572236e1c203a1c0f99e6ec978458c1a143a6a650eee27cfbe406bb2858fe5f30222f468d119703c2f442bc644ff3000000000000000000000000000000000125384343fe132e16a9fc15efe1b3a9e47289e0afc4b44d492e33a6216edbc96d66c1ca66944a8296e7695f27f414c5b00000000000000000000000000000000084c2cbf0d7c932c3098ded7c70d4411eed882feb0f79e0f7f1c31f5fccb6d53fb57de179c3ba5754bc5e532c3784df10000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a0000000000000000000000000000000003a6cc74cc398f38d535b4341faa37c968daf2009c3f05ace1f938b33bbe4002d81d18d30c2c856b21afe7a22b83c37a000000000000000000000000000000000452d1b2da6392f9df1bfd35e4575c565333703b2f83f56e0a88a0c8195968c5321296b07f6750584e23597304a5472e,000000000000000000000000000000001220b3da7e7d03823458bcdcee82db56957e5aec335e9b543ebb0f3cf4fe3cf6ecacb6198c886b9abbdaa42f528b4963000000000000000000000000000000000138233b166547e9e9ee9d11048e2d2579b2b111af5cab372d36159c4c45e28d836d733a1265e8833da64f461c0a32cd00000000000000000000000000000000005f860a0c72034f1a928501d9f549e5c2a9dc72670272fbf35a0b301025c0fc751d55ef6fc2c5bf7ff42df7693f3dca0000000000000000000000000000000012c73105adf97bc0dfec1f56153c57c6fdb9d68341f4397b72f5b6c667873ff7ed5cc841451b391e33290cec256395c7,4500, -0000000000000000000000000000000004d46066439c3ac559cce863c58316883651023990180470d2efd06e443a7caf3a514b54f15ce6e850d32779215bcf4a0000000000000000000000000000000019ce904b6c9c3de59f7d5017f60f1978d60c564f94a0f1964c24c876d1139a7ffbeb6d0d4884bbfaf5f2f189af6904a50000000000000000000000000000000015f1989719e69be95f25dda9358fb98aae2819e0deb7e2d291e2c01e85ba26a9da421896c6b6e2ed20f609b533154694000000000000000000000000000000000b287cfcf1dd7c6d735c1358dff15393ddd6c82e7a33c5d8005c4234cdf823c76a4725fd74cad74b3ec51df67f09af0f0000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf780000000000000000000000000000000007a9cf48dbe005c5c59b2c731cf4117e5fadc9cb2cd8f486f1ed58b2909092ee8f36d88b8f719db94715641b418ab4240000000000000000000000000000000004ba40d4766b91bf8da1cc2526f62791a1b5f6fc24ffc54b522dd30cde2d29a6a6f81e8429d518710843d43705f3b4e6,00000000000000000000000000000000014933a0923416428b5fe5be7120bf399ab62ca091b07d03da3fd2ff080b9c411c3cda3bfef40c8450ae31c412dc5feb000000000000000000000000000000000214229a73780d4f260364649e9eb2ed751ad3f687a832a3738ca2cc81a3acf12757651e88c4bcd79239bc0b0c40e5a6000000000000000000000000000000000548f20fa375e578084e085ee71df5f8ddaec1db03a1415938d9521b5d9c914b5295835fc07263cdbf49d7802551156a00000000000000000000000000000000063ecd9efe55229a76fc848728e940183c23bf47363cb34c5a49837e6df8a5f0dc29d7108cd10ea08e82ccf017d246d1,4500, -00000000000000000000000000000000006b37e2226957d639fcb0bcd6c20b3c7b8372e7347a14b970e01c67c1859fa97c754ce588d0f835ecc053549d963ab4000000000000000000000000000000000c6a5fae8be3a32e3f70a4202a1ab6d97183964b9f7b9a084c49922cd9e0e952b0bb66c5580f0e0c417e079493bcdb4e0000000000000000000000000000000017b6132f11adc0d5d693ae7f3a0f89f5779708083eba23e03b0c9265e4e60624e1fb6940e8ee49d31618fa6389b1b50b0000000000000000000000000000000000a45c5f6df71359648aecb6434bad1619c39f10e279a02b3cc9725d0256bcd126843fc9ed29cbe02a32cbbe79774a330000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c7952820000000000000000000000000000000008ceb842a17953578013ceee519a28ef1b37f73e13564def5ffe08a64dc53aa680784e26138176c89269477ee003d16700000000000000000000000000000000159791b6f2c26ed611ca40bfbd2059c15cfec9d073a84254ad9b509ef786d62d17fdc67ab13092cf0b7b3482866f4c32,0000000000000000000000000000000008a71a08d2c4e2ba3d8774dcb42d3e96c7f72d36fb3b880a4049b078d8257a7a9a51b0b34c093568baf4aa6de70e709d000000000000000000000000000000000daf83b5ad4b91b557982fc4b9b7dbed2998aa39fc4658ba671f5f27b3888dfec7602949cf626c9e6ef21171acb185600000000000000000000000000000000013a7ffca291d9ba8790ca0462c54c147aa22e03a2413b756f27583155932aee65060924e46db321b3fd6f22ff7f54041000000000000000000000000000000000289d7de10285285279aee024e52476fa6fca85550f7af183a161e395d72e1339b629c64127f96bc85858d80e73dcbe1,4500, -000000000000000000000000000000000ffed009c78ba9af8cd33af7b7697ae4dff863bb92365055baedd2299b7f5b5e8abb84ed434f7223c3e309ca53c08aca0000000000000000000000000000000003b2370c837dd6291818efe7c9af62dd51295c418739ecc509d42c92e2c97d12a9fa582946e176e8153fc9a273140b2f0000000000000000000000000000000001e63438e8b4a0462cfdff64a281ab4a7f48d51b51325817139f8ee683484f8695f1defc0c3efcca81d5fbff06cf9c54000000000000000000000000000000000192fc391cdc1ed6ddbd317f2f366f2ce25ba27b8c0f09c733e7bc0c0697544399a3a4f1186d139a8f6399ffa88e89a6000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf000000000000000000000000000000001272c63693873e1dabe2c2739310f627d3d9b5bcaa615402c3849ffd8dfe72b40fea4a068064655f2c8f46f074e6518d0000000000000000000000000000000000161a8e5e1de10938e5bce241ae73d76173022127822d744b23e656095c28f2f8d142ceb48b72a1dbc36b6143f8af95,000000000000000000000000000000000a4ed8d613cfe4f5dbda1d0c6812d0edee45ffc2667323c3828f8ce4ab55c119e92a82f2c3d06afe3adaa4aaccc18f8d000000000000000000000000000000000fe10c5e185f3f8ba81c93754132d76e05eb3543d8aaa8a2d0c98833ce5fa9e2b84420d6e3412e005cf89d11f5400a510000000000000000000000000000000004ac5f8cc614e3833b3b6dd9eee9ac29501002ba9054554314a4c516bfc8cec870995e811f7892811346574f3c58b2ec000000000000000000000000000000000a6bed54d8ed4ccb09211ae7773c604edc6ce51a05c9acc94e8167026906d387af681fb33a40e72e85cb076e072db7d9,4500, -00000000000000000000000000000000002e105e0eaa418d58019a849b89accf665a94ffb0bdf308a11b99b521de7af8ddb150c0e3b2e9c54cf5456b6105bc81000000000000000000000000000000000691a3b3986fbe1c0ea22329364454f37f645d6abe9310e883b9191ce512347e074e18e28b88c2adcc76190a549b80b40000000000000000000000000000000003f3a37a763c8d0d99a3fe36923843a22cb0fa18ced48493b2510fc99afe5b7699bbaa6c2ecdad8aaf72969354f121a1000000000000000000000000000000000f4bbae00205f54eb10c83d928d908fbae342b76050e33c51b6e282e02b3c1f132a4728dee4ea95455c25fdfc112f254000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a220000000000000000000000000000000018a236ea02b1971d6e193a6eb92e1298956679d86864042fb6a0c36dd91c0e385944d779dedd0149fa8a1b3d6a07949d00000000000000000000000000000000048eac7d116b5a7906bce070e2b51ee7c4c493f1415abdb6fd2d35676036d3b741d14b7135419645a6906018e9d3f150,0000000000000000000000000000000004d145ad2575313a922667b897052063139eef8c61dd375eb055c4a5c52cfbed35391a85df915e1eea50d000b9b6bb5700000000000000000000000000000000071cc73c16a234e99faba9b04fafaca1a943f2bdbb68dcae0a1742acfca1f90c5f69464aba42be6c18be31f79ce30791000000000000000000000000000000000bf725a2f4d7d33c66fefeefce13fb5649a68a93fb7086c943a7bd5663b5788a5ceaad7fd2a219ade832dfb3c0022a5a000000000000000000000000000000000fef4a2610610afef43da2161b86b25a8f6e30ed90053d57f5ee0a10effcdd2af769d32ef6843804b2b6590f95eccb4c,4500, -0000000000000000000000000000000009a3e98fe4a98582ce9f274965f376cb45e8583775dbadf626cb1327c1f8a25b293b97e7f8f31ff72ba7e8e769ff25ef0000000000000000000000000000000018e4785ccb76c4897087c8a4242ddc744c6a0a53a4a844254153c23d6f16d4ddb945252d13f93101613f4eb0b1e2b8320000000000000000000000000000000011b81d344eac04d3471b1edde5e51f31f97bea3396580839fa094db58cf6bee371bbdc045fb60c3ee5c6cd5d3f6d3c4700000000000000000000000000000000073476bc5b1d52ff4ca89c3afc099417f473543fab6e59cf9de8a19705dc4bf2a210b1e6de4dfbde035c312be0c70c5600000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c95384000000000000000000000000000000000003c96c6f20d7ac31ee7ca77d11e8d25ea78cdf13e5f4d317752320e059e19196f14c15b5a18ca712f3a7cc6f09be6d4000000000000000000000000000000000ebd71f61fcddf1652675f577bbaeec26b892dd954965b057ffb431d6e37cc5425a2a42a0059482c2bd75adb2a120b0b,00000000000000000000000000000000151ec7c35a67b878420e198ee7bf359d0668ab61ba1a0bc2e5e57b1b7b18838a015464f9910b659fb7d1e10af2801d86000000000000000000000000000000000511536f34067fe931c6e829e22443eb838f0c938eeef6f839eb322d72e2011dd1c33c504dd044e3cd721065d7075b520000000000000000000000000000000010c486f846242024f9bf40d805c8e33ecf1b44cfaa04455d5584db7ebc32c0d29e8742c61886d4ebae93f22c518ea87300000000000000000000000000000000072e184c836a853fd1153eabb1b645bd35ef72eefde4a52db169acdf2d8d68499398599cb4002994c6f4936de1da75ef,4500, -000000000000000000000000000000000c414b95b298b9c673001173ba7e5ee3e03926f28068481cfa0b469ab556f8fceba9fd0a815180ae0b82c265fd4c6b7e00000000000000000000000000000000054a242c1cc1a9c710bc23305d09c2d613ee8eb3840b37943bfe83f9c1db456ab4436ad319fcdd8684db129d76c95320000000000000000000000000000000001683711c0c7f02e67374f190eed1ce6559479d6d199f43fb5b0ce7df7774a5cb21c86b3b3498855d9b69c5763acd8c4300000000000000000000000000000000062f87085dfec847af518bd71c078f994b090c3b27c6eaad79772ab58afa43993db52fb08649a32629d61c3db12c87310000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e4000000000000000000000000000000000174ffb89d7715866562d9882acb81ce40758644ca3e0decd546c8f5c349b24fce88214956e7540fac36bcfc105cf34a0000000000000000000000000000000003e06c5f607ccf1e2991828034fcdf91106295e7174b4dca21926169451ee58e737d535af45073e2378206e03c81c421,000000000000000000000000000000000642f215b772d17a3aa45ee3aee607321c02b4f7a7df3884259a25ce78c73e9536d46333fa388e506fdc79c708bfd9de00000000000000000000000000000000145864ce36521fdb641761be541a27bbd3f4797b923a870148bef1d5b4b0d463c0a7c8ef07954dad464510d836105e05000000000000000000000000000000000ca038e667fe68111b583dfaa95f88d3b9e46c0798abccd1476071435067e6c0e2fa81d25db6e1175e60efa1705538b9000000000000000000000000000000000cf1cb1b155e4ea47077c42a1a99c3f11f8b27516a808b5e73498ee12363652bb46eab7e55de93513cc2d6272f26a537,4500, -00000000000000000000000000000000083eea9b5b2d5ac5f7ef51ca889a4317322d098a408a741827fb3419eb12a51c07c788c2798cb37635e224e99bbc894c000000000000000000000000000000001312ec00f4b3a4305700b44b3f215779a9a8bfcf5b5d3a7f237a33c5484099ec9bc5c8537fae768e2c0ec62168f383d6000000000000000000000000000000000cf1d5d05d11e1d07074dd34211d0f00eae1df4dc550c55bd2fdafaffa1ad36abd5da30c5d3a5aa2845b1d95a5cb571e0000000000000000000000000000000015223baa9f2ea4b04fdb05b05bf3a94dcabc5e64189aeee39c380de9a34fe6b4253f5795f70bbe51b80e1aec1eab71960000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000b15d92f2301075ab0e3215aa72cf9b130bc8e1bcd9fa36375c4b9d7da430ae3e2b24f417336d8729f44542ee7f561d300000000000000000000000000000000197d90090501e8cdea28eb7963231f1a7b5f716cc3a086acb6e7626600d6544132cac943e8d5cefb5daf0a2f8d400629,00000000000000000000000000000000128c909854a20ccf9e8e396b617b36f233909a5f6c3524c93cc659d22afe0e7058a438a5ee4345bed914288c64802e29000000000000000000000000000000000239fc43718cd27855ee5450cc9be5be5d9bca8188c22601242a1bb4269ca0fe62ad5e12b2c65558cd3dfc89ea31205f000000000000000000000000000000000a0aec9527febbd35bf041a901b0b35e5e0d48a2d6d733bb557d0767798369a7ccf2f1c278710eb764f721821f9aeea300000000000000000000000000000000194931bad52daa16a648ccf1ba9a4768e5e2900fee4f9bf46ae07d1aa605aabbfe96684f5d2233c0b254cb4ad5517775,4500, -0000000000000000000000000000000011a960cf1978aa2ce1731b857fd91d2f59d4b8d7c6871ef6f4f85aeff549a2f397949d11a4793926fe7be37f3a83d11c0000000000000000000000000000000001954f056834d6e3b16043ef1acd0a47a353300257446e9a1db7e58bd0d7c4bc9ceb3db51ae01cfed9de99621e96934c0000000000000000000000000000000002e2fe460e71b65595ed93a0010e5ccd1a2c16fc4e0d345e7226c947f29720d2f3f54282f79cec086d3fb1999b9629b300000000000000000000000000000000060dd8a7ccb613f1521168a8a322aef9f84d9708a893f704f4fc9a19e2493f25620a47e0fff1bc1e212e65e92873b4f20000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf2300000000000000000000000000000000005c35f3372f1ec9845bd04ea722fbed2be1388abf59e622dd3dafb4b3af49bc5fba9e20235e7e58973fedf4b8b720691000000000000000000000000000000001111d18d621070509805d306a31c109701288fd55d4c0644349deb080c6591b6e852b4f7e009b80019513de7f2fce17d,00000000000000000000000000000000189ee5ac642bfd0b612058f96e63acb1feb6b4dce125bf0ea1e56e846775af1a8b0864d4ece6bd96c3b5dbb04e2f6c33000000000000000000000000000000000073d57ab79314e38267ee8015de3156f2c1d5dfcb6655a150b9ab4a3bc9eeddf7b37b3681c49611e02abb012770b3f5000000000000000000000000000000000cfa1363275c7bc5bbb9bb7c03e7bb7f6d6d365e39fccbe62cfe0bb93280527c9ea99079fdf9871abed035b62079856b0000000000000000000000000000000010048e4e96f26710d254110650de36460be2a8302badfc2da8b26147da498e4620e79b4329033fc3f3a9c99b1e12aad4,4500, -000000000000000000000000000000001472caba61c2f1fe4b1d0912b114c25de103ef4351668f22f3a158d7a347539a7b6656044bd490f036ca3e29dbdded370000000000000000000000000000000015f8cdf7786410b409f218164063c99e77d8f72f03882a6c9430ec725ae574547d3ea3cf30c3ad2c9c3febe6c30b1272000000000000000000000000000000000ccbbed85c2809433fbcf22d6490457dab800b21cb4de414c7dd1804a0bdeb7142f8ffbb2de921c2c9eabee6a6351026000000000000000000000000000000000a404f42c48e3ca408d3f92079b99805004da928f128206d8904ecd7fcb14121c7d9a9e7fb69accaff921315ef3d5372000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000000e7e532ee4b892af39f8a3db7a05cc77a6eb0b3d977c17076bac4a52d5ba003a0ac1f902a4257791a45370eb88426a70000000000000000000000000000000016a556050e4905fa74b5061e3874f05cc7a6c5b049bd3bb7c34adef5a77c393239a600542a4401c3e61978ee6515a30e,0000000000000000000000000000000005889133be5f447013d779f2b9b0033667c5af87e1c8a16d239ca3ed238920004d87e00119ded46658026c26988ee63a000000000000000000000000000000000d4ed8fd88f7e1394f2b5a65588bf1c461a292acafdb77703c2790ef249f2de695524293c826252c94967a3ea4a3a28500000000000000000000000000000000001b5ff0aa278c7e87a89d4748aef13b516c49b7dc9f7cd5e0448dc6fd860a7a8af7183a198eebe6c7dd549fef806db00000000000000000000000000000000003c9e40ed44427cc3cf886ca2db341ae31f015c542b857f6702d25cb5036e3e6abeb8d4bf9a0e203281ab85ad89ce0da,4500, -000000000000000000000000000000000b52f05365c4df20a7290aee71a7e030615d1a2a971167884d835c24e756a0faf6ed0552341c561446c7fd3d5e887d830000000000000000000000000000000018718ef172c045cbf0bb132059754b62414097eef640a781db6ad521af5a24d78c622d9402033fa939f70aad0510a1ac0000000000000000000000000000000017e969e44b4910304b350b5d442bb6a0b71e1f226cb4603cc8b4dd48614622f3f4e1ddecb1894046649d40f261d94e030000000000000000000000000000000004dacaeb9e05b9d60ce56c17312a092cb988bff426b8a718cdff860186935507a06eddbc4a1a29e4ef88db83fc4b6e77000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d2900000000000000000000000000000000179c34ba9578d5ff90272a2c7f756794670a047f79a53215da69937152bad0f86576945b12176d3e13cac38d26335c51000000000000000000000000000000000dcc715907e4e17824e24c1f513c09597965941e3ed0aaad6d0c59029b54fb039d716a998c9c418110bd49c5e365507f,00000000000000000000000000000000093b692a68536b16913ef38c3bba7b19ba94a6af1c36a2e54b8ac1754a29c29882107cde142deb95365af00f2d1f537e000000000000000000000000000000001035e70852f38f860a1a04f33081e84f3ed17d83ad894a6800e7b8b9259067b755fe7e08d4c1b297c6d53064ab8209590000000000000000000000000000000013d38db0d8575131865bd7acb6cbe994812bdd8bc7f51b810bc382a6eb379d442c47be20a2c8e751fb08ccce8fea68690000000000000000000000000000000000bd114951193e3bd58cd0025e0b0c807ea073b1c1f7bb04a2a00771b6442e70ea20e1124572ef5b74d2bd87c93c82f5,4500, -0000000000000000000000000000000019829d5799eed5a081042e4646d46fb6bead6d3b9893a4240867b25ed6af6a3e154514f244466d80e3b9311e060bbd7100000000000000000000000000000000156157a654db2813cb9c1b4da0a3ee192fad076bb2767020fc5fc00e967c1a35a367ffa375703e1181b3705ace9dd28000000000000000000000000000000000093385a6a9dd0ab996df54b23f47f4a49b3f379e11bc8331016ecee6161fcddd22f6d49fbb21f098873f1e17424dedca000000000000000000000000000000000d5b5b0f2ce81e755b4030b33fe3a8bdee38c2c60ed3b4a88bffb9207cb762c0a5c699ff424c000ab080d763abc5438d0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c853000000000000000000000000000000001643567a0f22b90fefee96c8e2f5851623384c2c68bce9589cdf64c933d494a8d805edce2fd18a6db80f4819391dd1f9000000000000000000000000000000000e4e40ab1969bf9f00ee3b984947ae95bf7b9579bdaeeee926638f9566f8ab26debb4c8d4009535cb6422b2c2ab7282d,0000000000000000000000000000000006db1eef1f614613ada8383e63d631484015224902ca38f58ee384a70af0a0575b0e7063675d2dd997ed8a140e2598470000000000000000000000000000000010d7b833f050f18ff4e3a8d0df227a9494dad9cbde88f68802b23e87387622a5333dfb7bcdcbfe2d4d137cb532ef4a150000000000000000000000000000000000c9c40ba972ee0be2823625a23345fe352d701cc8bf9a153d5a55c205ef1b7e5544d0a7f65aaa24bde8d77cb4c31ab3000000000000000000000000000000000402f170c4c3ebb9b1e7d64765b66ba9b8d45b2ea9fe9517626f38e00a11d180e1f8872bf80f6322bdf3a8dd90732ae9,4500, -0000000000000000000000000000000003af8c25bdbd0dc1cc344d55366f15555709a74e1f0d8d7050cb6b487759db6200401b7868fca3c2ad26e6362a30e6250000000000000000000000000000000013f8b6ffe30f9a133fafe64461d305cc6b2cf5aededf68ba396d4e00df651531c750a3d94dd77bc5c6713b939b18fa19000000000000000000000000000000000dde97855d7728f409d873b83b6879b45ace5b73f317687fbf478e594a959ce21d4d751db646ceb20432e8311e67404f000000000000000000000000000000000fea997323cf29710cf0e3d44ce682e039d6cbda155e43c94dc8cefc5e94000de4b9525123b9615b5f1019a46ef37ad300000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000a27fe0a29c761ce29a731ead969b1db3ae9ef4c05493cc370a128d97ef956c55d9a500991b3e7bf9600383633778ebb000000000000000000000000000000000dbb997ef4970a472bfcf03e959acb90bb13671a3d27c91698975a407856505e93837f46afc965363f21c35a3d194ec0,0000000000000000000000000000000002dccab673b26be02d2c645c82a2c73290f0eb053e07d4f81d4d315d9483e57c58b65cfabeb0172934b9fbb52ad519210000000000000000000000000000000011c34a27c850fe319fe89399e7680064caf6dcbad171c3a23c45b9883ee06ccc3482b2b81e5777759ff81b16bcc1b0f500000000000000000000000000000000119adca3e2b052c045124f021fceb03c979e6eec0a270c7f4ab13674e461839a4d3a10fd48da4e9ae750a238a2649ace000000000000000000000000000000000fb5210677e1096cb5448bcda16646d6dd29ff8a0765c5aa51d83fc952a5ab8063aa96e97f33abf701cb8688c989c363,4500, -000000000000000000000000000000000cdf60e3bb018407eab162822468255bcffd54cad9127054bd1c30705a4ebf1afc7f539cca6ba4cd070b44410ec751150000000000000000000000000000000009a2e3e5993b6a7007dedbbd21737a8c0aef3ecd4607953c4a24bb3fed97ccae01ae1cec024443f300b570a66e9ac3bf0000000000000000000000000000000008a21fed19e9ec2a741ade7767b0c9f39b79c3fbe34aadc9eb3043583768d893bf927d26231759290c7dd9c4f158d5a10000000000000000000000000000000018eef4ff88d63149d2632c9db586a4af0606644b16c82fbb0a3b869f1ff924c59acc8efbfde7bc604497ff68939cdd0800000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000eb3f5d24d1a4f520032534f6f81a6806c54df33cbd10c30203423aa4f33620b474cda321e924802b636daaeb34400470000000000000000000000000000000016f004f1dfbf140de042e4f57303928a576d9064f2da5b3ad392331f5c43327c7d2a6fd57456d5ef58b54a3e5ec27508,00000000000000000000000000000000056489b2248ba672501069ab6742016cc8ab2af50a119239bbd3c0a4b9b56e014402b78bf62b2b37bf4645c3bd3d95b800000000000000000000000000000000046956432001feaba6d230da27a72e8db5c8eb3d52f00616f87b55c951217095f337a302562cda789e5714c4391ac27000000000000000000000000000000000172c2a583c9563fe02d43b2b767c4ee4e3990fbabe4ac536d64cfcf059f0e38672876289bc86915b6344eb398fbc4ddb0000000000000000000000000000000008915b0edade80caee9b386e4a560ff4b9dce33946ee992649466315786e139e3ce241ebbdfa7ee28fad7e6214e65666,4500, -000000000000000000000000000000000f5d47911596c46c0c08cac5f5e7f6d0609874da4ac1bd4e0e59c393273a5fe31a756c7cfff2a01d19e79d209d7c6d3e000000000000000000000000000000001010f864eb6624132d4436d18db7f5b34727060dc426c109886be88031e3c155490cb3fb09e1fbccb7912875477c6d840000000000000000000000000000000005cfbf1c2ae1b80a8c7cfb2cefedd907b0552794f4fda101ca1a723b18de8cbce30eb54287e1847cee3f416cd8b45f2c00000000000000000000000000000000084fa63781f7eba9c7e911ae5866d485bc7e90603541c55d1ffad8b3cf7547fd57fb24b14002560e58410b828513e1090000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d0000000000000000000000000000000002a36d5efd3381c35ff4f361cd813a96c3e5185141c5985073b45d1319c5f392442b7aa6a253b7eb22d1b5052812be00000000000000000000000000000000000f745dd17966b6befa7f740ea360241162505d6269226ffda90546863d0fff124d8fea13c763cfb69c2f8f12b81d431f,0000000000000000000000000000000005b81843ef3f98c6a6686f1fbd26f77248497ec3d41aff4be5968d13ba86f86309b0ec4792d74220ad8ef147bdee9aa90000000000000000000000000000000019825376b243f3e374b6e9e7e51e0c969bc72b39cde1dfa09187a3c7c5c2c752ee16fa5a4c8fcf94464287419b3a3845000000000000000000000000000000001308cc0c77219034a9fc3018f1d668a41e6959476aaaa5461ec73d7155c6a68fb08e1fdf8140e18270cd338c266a83f4000000000000000000000000000000000fee2a6e245e3bb570c3b605f7ad805bcd68e9a1f2bb2282f92e2a2e83b69e275b21b923f33a65defa8c4224934aa588,4500, -00000000000000000000000000000000124870cfa469136c638e0cbf15802f2699aacb66d7e4c2965c6759dbca4b7e47941ad9ec37a84db1afeeeaa65a7418e4000000000000000000000000000000000d4503049a6a53536bdf41dd832a6ecf3f10554887da7e389cf940394e1d88db94369b7947436546eb6c6e82c48dfb9900000000000000000000000000000000053f9a6e1f05b67cf553073358009a172e2ab8b43572a974da1f3de85a29103b13d7e67b2a359297172d27dba5c61439000000000000000000000000000000000abc29f50ddc1c113c73700b9b9796890cbf48818ba981fdab2db27ef1c58f4c2e4595b99eae397d40990ce2f6c9317c000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000000397dbdcc3edf976e8c507f5e70299da8c7765772115bf8edf7dc9024050c2ed98746c2bf7dd4400ab1fb89af991e43f00000000000000000000000000000000139bd5f917f59e2cb6c41c59024c12cdaf95285f3947b80267f36e3bd2701f9548b561c49003fc5ddeee3fe7bc8f5b5b,00000000000000000000000000000000166414455bcd0e8e40397f4cafa9628d1a092beaef62d35211cf49779ba98df5c1d692f650c1fcf0893a9d4ae1926b1c0000000000000000000000000000000003dd898d0725ee899b913042da8566a1379aeb4dd5f0222ac784205b4e74f32858ae490f981801b166a01fb96266dbeb0000000000000000000000000000000019f0fe4f12b113b337361b977aff7cc7dce50bf37c2609b9f311ce340d30225de178999b73345ef49625518e52aa4d7800000000000000000000000000000000090bc07c6270901d706a8d28d512b07fd0e03013d94d4e43eafbee59677998bfb7c2a58aa93571fb49c35518b6331bca,4500, -0000000000000000000000000000000007d2aae9794b7a7de97f7146c0ee8415e09e56fd42535bce6773cadd6f7ac09c4eafe2e926cb7014377e54c703eaa9dd00000000000000000000000000000000172a4a33ccf99eb0473b2c44d30bd53159afae0c7706ad128bccf6258974d5e5761f9be43e618cdbd96027aede7fd5860000000000000000000000000000000012601bce2171c6e4c2968a3efdf1491285f9e4ab37cf973ab5c8e224ad5b40e1b6459ac89090c73deb8fc79fec7fb8e200000000000000000000000000000000112a6443116e6f98ab348e57daa3971b5fa506e40515e1611fbed3e7dd64c5c1e991e0d2539a70eb93e3da0f573d6b22000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b00000000000000000000000000000000197ed996d62fc0628d8ea4adee487df31c794e05e7c327aaa140c6be0109031bb763c5f84bc35a0597dc61e93d23a9bf000000000000000000000000000000001056c1f3c6ae36be26430d142d34b0e807685c79935496414e004cb85900d85a18454bde9c0f2650f19db35eb3dd468d,0000000000000000000000000000000019ce0f31d9ebaed0ea1d12d4e232bd3ad48373fa465af44f1c8015102b624d2f8330d1323fb2fec524e83de0f6699ad7000000000000000000000000000000000915d65fef96562ea3b76f3152aa1b8e445ef50fa66dc487ad0c04cfd7a33b5ee48aed919eb81fe83b1f4dca59b4990d000000000000000000000000000000000e4731ec887261f29475523f7dfc5d21cbbc1b883439701a33cd58bd24f5d447267707c2b60ea38b04510be7dd10d72b00000000000000000000000000000000146a679d7a81aac5952645b2635f24b96393529ab9571ecc1078c4c20a77e59acc4591b9f45df00428250c5e31b1a8e9,4500, -000000000000000000000000000000000030372914b83644fa4db1958831e9335c72ab7a811fb337696221a3290e4c54bc10c2225f8fdc3a9f62632ba2f1594500000000000000000000000000000000114205926609470b6022d24046a1997c048e6d2cf6043397892c967692161c0ceedf409bf5e1199a64eabb1ff8de23640000000000000000000000000000000017cdecbe73779855b7b94920d4bc8ad057ce51c5481a5579650df8a5bbc421030d2ac44568217c4dbb13d7c639760236000000000000000000000000000000000f194fa814bfa7396697bd812d9449d06fc61b580d7a86429fdd1ad376e21ceca139356d7d13964c3c684563675711c60000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f0000000000000000000000000000000008789ffe0a8676c6a56742a30a48e5e65b88aafd71859d704fb9f69e5e274ccb6942bc51ad36c5671406052aacf19df9000000000000000000000000000000000c7607f4fc69a25aff00a54369f213c4587404644358da4abf26d151dfa4905ba9731dcfb12e2a3f2c551cacd0f4e47f,0000000000000000000000000000000016790155e57f7103d9e325a1f3a64c0b8a1875365eaa0c01c515538b64bd8265e8392e755a2f7314c37ec09026f13d290000000000000000000000000000000007bfe690fc4ab166b29de35e341e8faec4bc3c2d4ea2d42c9f4166c0d748b92b743ba646c86ff9e570612c75bcd522a9000000000000000000000000000000000c11b9ccf990162b772099fdb4266716b11dcf46c5abd12d03caf222c571e2a9e28cfb47e11db05162967ad4b430930e0000000000000000000000000000000000bafe02785607bae144d9ef5391fef02b9f2fd5dcd436e2506bd40866d8726eb83c223e09c00f3b8895181c6710912f,4500, -0000000000000000000000000000000015d4ae1521acf897344c3a76261754ff99742585af4a0ee86dc473a88fd408091404df1da9d8bb291db68bc9c07d6b2b0000000000000000000000000000000008ce160213875c661163990f3f7ac219ea295db5e828354864517ea8689ec15d35c6df78ff14cb276e0c97ffd7fbc09a00000000000000000000000000000000038a3ee211e777d6d6b7ca6c7a0d2130f1a071c030eebec412c3a0f14c3584e7c5cf15de254a8f141a8210a90249ee5a0000000000000000000000000000000019f7ec6b2fcd8b3190ab37a6e843340d3f3fc092f5772a042edbd5bdc967b96e8a1dc9e435b8463496aa1301f87d0e5a00000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000b060965391bfd4afe3271c6ddb91eecb8c7a60451c469d63bb178b1361617000f589c33c35b5deda2f072c6edf2eb370000000000000000000000000000000011c8c988379cd2b82cb8ebd81c3e14d2c01c09dde5690b97623c0876c7554f52ccbaa33d17fb0f0cf331cc85749340cd,000000000000000000000000000000000965966a8a463de1f3bc49d9873668e87f54d95612231458dc8b885681cee8e2835482b4bfc476153c41b206f427cbb400000000000000000000000000000000183639fa14dd74c33e8696496a3ee269160f88e5daca4fdc468724d9b6af8e7d0706867cdb1bcc608029b89b94c531a800000000000000000000000000000000026257fc32efaf241c7712b0a7e9f881763d8fa0711a452d9b71ea25e973bffd88433cba768f1e5b3ea15bdae9cb9428000000000000000000000000000000001527afbb6594dc0f472673606fb8f4797fc855bde4d308ac1acdaa26f19a70f80f2d2bbf3498b53b887b79fd6273231d,4500, -000000000000000000000000000000000fa7f8fbfa1d4ef5f001a451c55ed261dee344025e599884b29d086e15665867932120d33bee579d5eb1b7e6c7299f310000000000000000000000000000000001f06356f793350b17b47a623059a068800ca1eab6089c7c146182990063e8e23bbf40d95a42bf6e976224b680b75bfd0000000000000000000000000000000008807f6606d2302450bfd8b38fd4147b851ff59762c1ff48f9442c4d7b77a32c5e023821eb47fca839a27fde60e5f61d000000000000000000000000000000000c5b92f1ca9c20d4b6b11d794a5853824cff20d9267a20a7aaa4bed8bfdc728c4d4d50feb8f0b569757b97f473138db100000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000f05a111b41a54e0ca78c3a1fff3b80bee7c1505a06b9a4faf36a73b87121d2952cc4f4c4e0dcb6633cad12b0caffc620000000000000000000000000000000018daa0f9a2bb347517eee63463b9d6a5e850446e8a94d0986f2921bf81a9f7541e8fee9d7bbb6d9181021af945fce3e3,000000000000000000000000000000000018123e82a5572e6b6c62d5db07448838df9db7f7d15dac1adba1fd924892c8bb3c417354e838f706564a9ac282c2ac0000000000000000000000000000000016613fc38997d39b2761aed3485de4d7c273e8392e434185605e968ed942b9d4712cd0d538ed5ed1317870d0cafcae27000000000000000000000000000000000354365566b6e43f8b7f4b94a6343146f35ba3abf61a204e9c976b1ad1a90d4d493494c957def69ff270371c1c8d953100000000000000000000000000000000066adbadf1b69dd16cf19349c82e362be4a3768551599b81a4853ca524a24326e6c9dcc38b5a60ed6fdeb3cc4e7973bc,4500, -0000000000000000000000000000000001191410ec6c5ff628bd25d35965f5e9fa7f3c3d8c0a9a1ee7ae37437a97c25e221110d892e2c7a0e9c8e386774eadb80000000000000000000000000000000003be30c25a18cdab139277232d8888f6d13112c9556895af8030f1893114d5845d895df9afe3c6f9ff7ffb1919adea9200000000000000000000000000000000197f6b4e38be0358a3f1722664c61e62587ecf5467f8aadc3a236b47682a75cb76bafb18a5c556b321d5da49cd4bfd4e0000000000000000000000000000000002e4ebf7f22d929b7421a600e67fa2e64a59edd87a2e2eb9dce1f06d3c793f1a812bcdd510e654d44fb4c1de8c64ba9f000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a9035050000000000000000000000000000000017d9e9e2008501981068cb0403e73c270d99defd468cc9dc2d5bbc57750a4a58236f8f7a8df4f8b607095b6a80e7de49000000000000000000000000000000000ebddf4fc74f25be3c358b72a20d1c093f980adfc943b898266592f691e11413c60151a0085d6c9aec8c2d329abbac0d,0000000000000000000000000000000018ba8af47c5cfa552374cb1b25ada1ac785381f2da0501f86c9e7b11cd4417e64095a5c4bdc2480ee10d215ae2296063000000000000000000000000000000000a2e09eff98280f6a9863d8b8faf8871b44650496eac1aaf90fc2b256f88e937101407d722c95fa76846776d4e6bf0dd0000000000000000000000000000000003824f5bf25fa4aec5a9e044703e5564122bec11da155c01ba8ab8344265516c1063983235863d826f68bac455327c65000000000000000000000000000000000ea72f8c6768736800b141b477610e37477d926acaffaa1951a5bfebb042c94c065e984a8812430153d529dbf07ce2bc,4500, -0000000000000000000000000000000011c6f1dbccde640f63ad7d40089779d01075e26269421b4ce12fa5341f58ee9110f17d08dc1052426f2d00da2dd70b4f000000000000000000000000000000000740b147bcdf06705971c113a5cc12fb37345dd59f2cbb5ff500ce2b347fc5a8199cb3007a871670d5093f28979cfade00000000000000000000000000000000046563ea98b5e85b3c42222d5e0d8481e6aefaf077a1b99f2b4eefb397ec846aa3659aacda569054c9c8b9b69750272b000000000000000000000000000000000812d887943506d68e3525ced9b979354539b7b14003a3169e0084c26326b92be67346920c9a99ef0f9638e8991296fe00000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000001fdc4256cc997934a65c68ab9767b09c7aad14b5765dbeedb72ab2429231cb333ab9f9143414359376d76857e8972d9000000000000000000000000000000001362f417875259b47cfd9e4c5feda52b949dcbf5b8178318428fd3e70c384020e58f515b9a24af5597cfa037d42491c6,0000000000000000000000000000000009f1339cff0b58b00a871add058929ffebdc58cd1bd8a9c2c965c63e1843945b28138008cca8bf7b7cc9afb69a11767100000000000000000000000000000000011f65b337710a4043e1fa58bb41d80d505e2aee434b6978129c80fa1b124db89e61617e89bc0e596507566f4a484e9f0000000000000000000000000000000017560f768496ed583b3522c4a013f8b96073197e5b53e9041db6dc935a266111e21d8c54fa33b7bda944a573f6e1f07d000000000000000000000000000000000168a0742af91f42058e6501e122b6fc50dc966c2f5981372704694544aaa68fba2b6483752fa2464526d5072f84d8dd,4500, -0000000000000000000000000000000004c8078fe8567013e8d05a546934026cdeee7d485e30d739407db16fefaef53ed7bff0f9adaaf064aff014ac919d91c600000000000000000000000000000000107cc17f485af7f22e07cf14c5cad6368323f720511fc9dda677b360567f769e47a77f61274927ef9b7be48a77357ec40000000000000000000000000000000001487f0880a6cbdac33ca35b9b65e4ead9d8c2e9180c993bdb2052060325aff8c62668c643f0cd9b4bb1f06a3dc74285000000000000000000000000000000000d4b2d062e31fabe8d2a329dbd6417673a519f455739d140246f2b3e43e20f390088c08e545bf0419d796ac71aebb519000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000010fb029e35b3f6e156b8751415f180ee3960cd3bb6ba9b8e456715ec70b1ba1410b8bfb77998f744d3f462533b59e26c000000000000000000000000000000001472654d9aa210a41d74e3661e05a9eb6b292719b46aa65f94b6abd514bf05f679dae89d21008245d79a381b0d7f51be,0000000000000000000000000000000005daf8338637bddeba63c788d78faa622e014efb84d3ac1d655d15af06317fe31d1782b2990354bd507632844cc87f2700000000000000000000000000000000185550250e2d9eec798e8b8c483dc37e2a917b304a6036e8ee518a0738d6bf946d99f6b7ee352b1a259aa894d53a8e1300000000000000000000000000000000105a4865d66ed4bc4f51dc52ffcf284615593d573b6beac490c3ee8e08ab83a529c8dd062d762d1d70b9b3290b6e8bd50000000000000000000000000000000014f598e5d0e40090f29aec1ecaccbebbf2a2d6889bbb9439798924db41b70c0cacdcf1e8ff6906f61943e9a8a1ae4fb5,4500, -000000000000000000000000000000000811e9b0acfc10830c074c5a4d9f4d9382461eb523a61dda0b77f1c43b285fc5c1ef3a1fafd923addc9a6e904505a255000000000000000000000000000000001113102d015dbb509f0b8d0d0ebb4d3711c4f0e1e3d55fb0af247dd24be4fec9d6fe3ad73fbdcfe206891bcebefee4dd000000000000000000000000000000000085aae9e58fb97b96ca3c089acab7bdbd0c3adae141bf61075f5c13145b0d07113f1075dfb959bc7c2d3d3b3a06ab2a000000000000000000000000000000000bb5eac8125807c10270d94e5bcf278241d6fa82f68e41b5529b28aebc88870af55881db526f7bd221a8c4c0b29a1b7d00000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d7000000000000000000000000000000000520cfc8c536a1d4e685c4eacbc2000d70abd72e1bf8ce3839d79f5cfa069ed31aafb15542f23b8d1af678bab05a2d410000000000000000000000000000000017cfffda12d21c98b79ac31c5bb696783afb7d69c2bedf0fb070cf7714959db14957a4763564b65b7ed214d7b48d399c,0000000000000000000000000000000006b63929ce97554659ae731d60d11abe858383e39a67007877f68233cba8179777c0dfe511fc730448da3f1c4347f85c0000000000000000000000000000000016d4df414c287b0871c69f9745a9ae68ea3a1ff41ecd17d87623338bb8750bf12be52caa81537bacee06cebb86f894890000000000000000000000000000000007ad72c98e2428b90bead3616f1b31b26e978cd3f9b6b759ad53056098c18932c48ba78d3da112d7a738d7a9ba21d84e0000000000000000000000000000000010dfcfc53d0458296686fd7e0555593e0378d2cb176d456abebfd8322012bc9b408bb180d4237679985457e689131705,4500, -000000000000000000000000000000001335276775545fbb4c701beb57cb34312108c9f1d46b4aa4b09a16faf0e648b4e80848bf5e75ed8730715f0107afc9820000000000000000000000000000000006ffff8736bab41b4ee5681b741a81fc870e648001027161144254d04c678e4f954e9f191bd8b26201aec681cbf0654b00000000000000000000000000000000026ede90d14fa0885baad21f9631bae058573251cbef5757bb8cfad061f3bdc78834fa5862dea19a2236c014b0f1652e0000000000000000000000000000000009844d0cf7f6f3401145d8d720defa577ca46b49e04e39c4c139ec6811a574e7dd5ce3acd00d1ce9496f10dd15c6d94600000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000007f786ea1cc7cd69ae1061d6b914278dfc7ebe8a714aa8cd04323860314c3b4b36054169dd5c6c60e67bfa3902d216f50000000000000000000000000000000019675b09a4de34af3c6e79452b57b31b6d499200e996008a9e7d1c910ca0ad2a352dc39cb3fd7333182476095b7aeec3,0000000000000000000000000000000009b166f124b5b85875834b5b0c088ab79a2dcf262240b284f57722e78b6eb56a192cd32544c1bb93ef492fe6d7a6216b00000000000000000000000000000000189b9792982b51b13cc3fc1691e0569b6c8d998168d3a3376e63ca60de4b30a84ce8d04fb265bdcf73f158d8e316bdda0000000000000000000000000000000005b99948b635750040b5b59568f0e8bacbfd512db2ae52c5032cd23eac18ad58d83b8f78cd26ae979ce2abeae8e1f3c3000000000000000000000000000000000d0b6561a49c358101b30f714563bfefc72e0febea857b1ce78cfeb9508b0108c2089c9b35cd694bc8c0ea8afc8d047e,4500, -0000000000000000000000000000000010192b925fca096682acf138833b12d96bf97c9a2e69e4266eaaae1785b9008f36082e23e2d42341427edce24449935f000000000000000000000000000000000d5b24a94adadbf542aa663114096bc670e1b6c99f3b661f55de121922452534faed7f68d6b431fcf6f3e379d7acf6b6000000000000000000000000000000000acdbcae49206b749d8c0d21017a33e689ebe26804d1fe7c863a2ea4210c3559805dcf73685702bc56e644b4e02614a9000000000000000000000000000000000092309d684fcdf44bfa321d473060dc2d8a8c66c51419894a3fbadbf1b56179c31dff25403b970d543f1dd0e19e56cf0000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000fbe421858e4109c51de57b77da4f9c4c1f950099532d9e30e2f7a8b8b4fb9f708cde1a497050d0944e089978b15321e0000000000000000000000000000000019f48a0bf0f27df65ba766a65e831a0801a4ebcd1995a6002a803f88aead1503b7c39fde8ef5c4672020307241958a88,000000000000000000000000000000000bbb59d3e6b0b4d86ffc89bbfcf543a5b8ff922f1999a1e06c501a734b19dabd54632132c865c53e5287f69f06942a58000000000000000000000000000000000a3bb94431530879a7fb46b317d4f3d65b5a790739b396c78521a20e1cfad9c44248c9576be11c70970a49a1914ceffd00000000000000000000000000000000198df068ac5d3cfb9bd6896ab64495f4b9933a72872679ac3a46764478f043e9fddf17a7ef85fb72a8dc1a722804198400000000000000000000000000000000155c1a9db0c90634a6d214e996b13252bd4db3a4ab84ca7456ac3e7899e6fa096904a90f1150026307a1cac8de00c6df,4500, -0000000000000000000000000000000014441b14765eee30e8131a7ef62c3b59370f2f6f0dda20fb2a3654fa09492bf695de1d1a8f250bfde3c7d2ed805ffaeb0000000000000000000000000000000019d813f8be2519e89d42a9fd3fef09d44a996d6a4713a9c224bee10f0ebb196370d6231fad810edf9cb4c875f08357890000000000000000000000000000000001a5abea13e909bbefdb51ddc699614366f271b2f6490ac8efcca7759833f3feae11057ab1b9ea32311e7b6ea6de110c0000000000000000000000000000000003ac2bf3c5486ca176e34ec5212165cbe04fc9e8c375e3e999a31fe014eb824ea3f2d06b9cf8b86ce3a76960cf2eb4d70000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff148,0000000000000000000000000000000010684ea0303f0e76b60eb96c470e1f0466f1f2b073bbedc1a0c0df1d2f6c66d77cb90ef9bfa4fef6a6a9eff8f5c66f9b0000000000000000000000000000000010e7ced79bbf01ae9f65d26894c73a905514296f19561ab4d00c0cde31737d01e7b4e8b8e6050054a7a17e8acb74d49d00000000000000000000000000000000174f771a98e262825ff2db7571f5f5475007d2f73a2c265f24e2929671bd173596b8b163abd46b868a644dd464dcc7cc0000000000000000000000000000000001cbffc9bb3195672ea2d998b169f853d3d4b4e147379329b1bbe69ce76d08ad78f87fdd876af227a050c31884fda084,4500, -000000000000000000000000000000000598e111dcfeaaae66d1522be2a21131350577253a3f33bdd74a04b0bfba2940e73b62fefa8f0c34c4aa91b633f6bdfd0000000000000000000000000000000017fefff7d94afbeceb33714e9b5480c3a2f3eabf9d7f6e8507ae54cb65f69b21cd7d04d23f24e3a272c589f572b91864000000000000000000000000000000001652e3f5a99ba8dfbcd1f90de955ef527947642054be603c1b84b24bebb579b78e2a0be426ec21d32783a0e55f0178dc000000000000000000000000000000000a6c9ec91e8bc86ab198416cbc76239f0ac0b903f40310ee1f2066b01b08191538ca913c2736f53f23ef37fea13d527500000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f,000000000000000000000000000000000fa306f630d06c801e0203525c75fd6065bd12bcb3c4d45c7e02b597f85a53fae1e65a969feedca75068433547e4632d0000000000000000000000000000000004b1bdbc29f19f6484ea4648c70eaa47cf5bb07bbc255bb72dcf68a7b661de433dafb682d51321369cd3372288b2b9c400000000000000000000000000000000136671654b24e1ff2e8223ba747ded51f5c826b6e2c0f02e2865fc35d15045f41952835800406f60f966d1f241914726000000000000000000000000000000001007b5e8ed7f0d25091dd959d89732e9df02561a829ce013f5ad1adb8d6d828a8ce87b52d39fda1b5dc2b581ca420e22,4500, -00000000000000000000000000000000072e022c168461905f798e87425f2eebb517e473cef98c255d0fe434863ef5811920af65bc946b29d489b5dee1066c56000000000000000000000000000000000e7a9872caa82d191f6014c845e1b3ee4ea1ee89852b546a2c85ddbfa3c1d4ce99002e3d7732ccb8cfbd57d550285ab400000000000000000000000000000000144be65db373f6401d76e0ee64e51076b861e8fca596dd6a7f3b5735c23b0cd13248404fa0969ecaa701663a1032f48a0000000000000000000000000000000014c9e9c5cffc4518889f7742440053678ff1d9fb1a1a103d0c1f762b10655bd5849ce98f4bc5eae80bdd9e767aae452300000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf,000000000000000000000000000000000fb74d9ad4de11df81c48d10b9a14fde8353ac47dc902b4420be4c086332be480552e26fc42b7c0f30e34f740bf9a4e6000000000000000000000000000000000612a7e23bbb525f91084b122dd4cfce4074c9e6eedaa7cddb58a14e0b1eccc2f08296baea3eb3e003e576fab7c557ea0000000000000000000000000000000016dea145df47a2c5262893c273c6158ee14d44c3740981c161624a6e9ebb982a52c1eab6160c3849f2bf3821d953f4c3000000000000000000000000000000000e920661772b8b737f1a663badead0e89aec4cbb86e6dece5d4db8a673e75b844bfe81662dff671658cb8386c16a7f3c,4500, -000000000000000000000000000000000948d0f0c20715f8658e1f2b4f9d32d851e584287225a2f47735a1f4c241b07f8d7c5dd8c13bcdf84e97d49817d4d88a0000000000000000000000000000000013c064548cb756b48600dd535af8eb5b9138f984bac0391df2e90a204fcb6c36017df910031864d802a2ff719856b336000000000000000000000000000000000000b7eeb7c9a01be88e573f196c2a531635baecbc8cff9af385455af3757301436686596ec7fe3618af26953c49f7450000000000000000000000000000000001332f4dbd5461ab9e2c8b3c19c6ff407a071018c92d2c17c1d1d481c24565276c0f55eee8692016c1fd76d70f44627c0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77,0000000000000000000000000000000015930559743b21acaf390b557fb960d3021f3cde80630d8867a063d445f860c8a01037057de1929be16d879416b12a6c000000000000000000000000000000000c6074c54c83f717700f61c5b6bfc641502121b59b196a1f8c5f2945e5db1bca0d7a94fdae96bfeeb6204c8c3f4d048a000000000000000000000000000000000b3a78454479c0990e4c65e4f831606c7eeeaef0faa86596350c9e43e84ae959a0f32c8d03d1f631d9b2ecd046efcda6000000000000000000000000000000000aff797d7572f20b06bac75bcf8cef879df11599ba7f8b86eaa28692d1239cff22841b66e28662309e81a6a599e79ddb,4500, -000000000000000000000000000000000d3ee70610b5029a28e586f0f3e65bb19a263db3438710fcb8073e1b25f83db50eb5bbb9d75cb20952a225023f747baa000000000000000000000000000000000682f7d5cf9d182b20ee88683f3915e8c9b03074a373e573aa57232de4e997bf155acf680e365aa0988989dfad102b2e00000000000000000000000000000000143962963e230a9154dc328f9583f5be6923a3b10ee7b1d0cd5f5cbff13913d8ff78ca315be7387900a50b94449884c0000000000000000000000000000000000f4f934b42452d41cc20d7b1ec547bcbcbcc10f215364ccf2b864db23a09d06e94c7a87165dcb691f4975323486757ad0000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee41,000000000000000000000000000000000351bad2f1fd9adc84280515c2d9e538b69dd63ac93514987ecace75d6bc4585199b742eae0d357d587924333721a1d90000000000000000000000000000000003e495b544aaf19a6415d5558170b8686968dc922367c5c8c212fa1f2785535fe0e71498b98b9a39c8b1f2384956170a000000000000000000000000000000000c7040f34872eea5f98ddc78737dd01fdafe75081cf66ad5c7c900674fa90257105b4f4fc59103dd5b92727a072ae462000000000000000000000000000000001312bdd27ef038d4a89b12c86281975bb34b435d42642fe0732709baf55e9a0ecc0ede8a4775a33e880aa2e1fa7b7ed3,4500, -0000000000000000000000000000000005f0fd4080e26971ab16d33aeae04220ae23781da3179e38190082f1d167514bd73bc8ef976a2f333570e9f56a6c05e6000000000000000000000000000000000e159905d29b52ba61575c3a263093017783e1028b3701ccf060c165ba33a765b5265a9b1681c1759bfe2c9c401275e9000000000000000000000000000000000c5ac0bc29a49a7c37d772954da850e6b5e301e230552be9a94017d770ebe2cf4dcfaf104633623e024aef6db57892900000000000000000000000000000000002228e7f42a9409acab49cca82cacf306f6c6c29fd9f7e2ed12fef2d16383cdb7bb2b39ad598b301072c615232db1fa800000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f,000000000000000000000000000000000d521781f60198341d116fa5cd9e2b5c2fe51f91f6c8318f351df007c96086f6c3baa5cd2b9b4f442305695dd9b01ac70000000000000000000000000000000013454fc15b1d182bc98d75947547b3bbebef6d5e2d38ed7c67d76eee8da89ea2be19280af4760282fa7576412d5f2107000000000000000000000000000000000d866015c84de74c24dde252542d0d3823f435203c71cda140af235d88f3f4b736e9d75ec32c09ab73bf74083e76866e00000000000000000000000000000000147dfb5f53a9cc61b6788c911dd8649c09cfffbbba368c1872a31cfe3bd6d6427d7b00163d39f8e0b81fc4c40dc60b87,4500, -00000000000000000000000000000000180569ce03e4a0155285e733adb18fbca71225507a7adf01cb8e8648891525305e92087f58378f4fd8455d5632ad660e0000000000000000000000000000000011ab84e42f10154e306a568d7cf7bc381000f0add0500cb508f695a3b283ea69d140aa0ad48fce2d2d6fcafe60761078000000000000000000000000000000001136c3016474d6f475609606e8d0269fcdab9fd3188a512681cbc41eedeadfa3b3d9355e5b4503e8b5c3665e49fdf3ab0000000000000000000000000000000003f56cba1b9cb4302099b16b09c2602dfab80d1151685ef78e5054cd454b319adf8b5998053a5b9fddcffa020595e3bf000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c,00000000000000000000000000000000059fffdf2d79b4a297f6912e3035cf0b07db9372f3485150e00d60bbe2e7d86f45b5c2ef062dd92c7e8b1e2be5e9bd140000000000000000000000000000000016acdc57e7231b020268373ddc8b8a7318ead02a8c7181165ab045208409373eaf57ace9a6db1fdedcaa477c7a0ff6f40000000000000000000000000000000012fe630f7de8ef5a129b99faff2de080849bf3b59aae1af042c29b1cc49c8825a4f28c4ccffedc6d568f306416b5bb90000000000000000000000000000000000d86ab3e49ffdc7c2485ecbd00256af83e7f3f064d212ea91245d86ca75e3c7f28b42fa9496a5ccc0514cffc60c9fb83,4500, -0000000000000000000000000000000004d79dab9eef873f3415d66172bab7166ce0c71f322529bdeffa915c1b0d3fcd645c91dd3450ba61593ffecb95edb91e000000000000000000000000000000000d611a207d3222bba199fa083d0459675cb5fa00839fb4c9034ad868fc1e79d653c18651771431d6fb6b6b5ce8cf6f7a000000000000000000000000000000000ce802ecb106a4f0ca4efdcc058dd0e29deb6a5d30a2c15c8eda896bcdd3ac19053c10105328d239b26c5ddbdb3a95fc0000000000000000000000000000000001073e142621ecbeff6f81453660362545751f992ffeec3a83477fed3e6215a709ffe0d17b65d3369f8f3913bf000e84000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb1,0000000000000000000000000000000012ba9a8fcb69d15eff147f663a5d7927b6f3f79330eb9ee625e0100b146597554debfcf97a3afb51387a73554522ed0e000000000000000000000000000000000a63a990d6454d4db6d58642eb3489f79e517fbbcabc06f2eaa00c4b6f9a07aae97991f169d90af3461b7a62db276e00000000000000000000000000000000000a95203a1628a6ae2551df832f7ab94ffcdbf985e4c9744e244214c8e8b8079af05a9321d1e49b7240c2bdeeb7b783280000000000000000000000000000000001ec747203be73526d3f943e0af814dbede34020144bf247eef9a6ac2cfc83ef63f18a73d3baae18bfd8d5e83d0519de,4500, -000000000000000000000000000000000bd84f04b3858b1138b1b429c7216d5d1b1e99c1e0fec26440d59b1ad79788c2d5583122c2ad769fcaa6d10d816a1f1e000000000000000000000000000000000387977ed1ce5da51dca230531bba53d17d3de5d593ec576cabfe6463d5164d7153025dbd4cb3525c4145c4f6b85fc76000000000000000000000000000000000a19c943a90fec6921367a2edc5bc38a5c59839cdb650766a2d2d068242463dd4460bd1d0e7a7fb0e3d2104704b8b3730000000000000000000000000000000011d99d44b200feebe00bd42809e3f67a23cce88a07165416cbfaf4db14420f99e54d62db4280d2c99ca0bc3dc41eddbe0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc,000000000000000000000000000000000eefda9046a950c232c6244a79c33e7135d0896bc57839a4f971030220e3ca8196cd0ad75269f3cb5586a384dcd17f9f00000000000000000000000000000000195ce623693996f5ce9e45b4e285adb969e6771e6b0701fb5c95715523c8cb93aa641583821a3b360ad6f4ea1aedcc9f000000000000000000000000000000001553a4d0f965d26fbaba56294591935bed63c84abfedbb9d5c61f3d43484ea71600935fe3c8b6b137d7a9074d907e86c000000000000000000000000000000001673c42c88e4acf8ca38680694b80458f988403a4bd667468506452303000d13649c4f610b738a94ff88b65053731c08,4500, -0000000000000000000000000000000006a186aa584a466a860849c78e4922889c95a4ac6f39c99029fbb422c43d699a8baa51aa4ef51ff99557babeb3e9506800000000000000000000000000000000065fb15b5a0923bdb52dbefc7e9f1a898e32f17d610bac829235446fc5e1913fffc8176e0fbd33091505761f1d06d8920000000000000000000000000000000008bd358698fd073f660ed608462cfcef1da9a59b10905f1d98c4fe66958e56802814906430c10fc25a4d351d91f91cb0000000000000000000000000000000000a53638b1b6c6eeff468e099446300ca7c7bd899c6494682d14fdabfa9cead0bb37a0325d99e7d0ba6341cfa1d257ba800000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed,0000000000000000000000000000000007145ce58cbe48405392edda6022ba8942df055ab582ac402e7c9a0a951cc6a38cd147903f042273e736f30849996cd10000000000000000000000000000000011b457ba464ce818a34a11afc3c0007908091fb528836691e6eccaa9a23ea90cdc746769c4b7ec73efb1f2878413c3b70000000000000000000000000000000019ca519fa6a91cb7e83704daa9b92da9bb70b003f9e9bfe9f323430bfec9b19b01005aa9fcd19d5b1ac59dbdab0c0d84000000000000000000000000000000000ae356f5e5de0d7662bab8d947662bf87d792a3438ed477cf6ed4b27c935b1dd76a5aac446d4dc36db544d4aea40b505,4500, -000000000000000000000000000000001070b98c6348a67e996626ec2752f45e4c007e9c9668459a777c03fab633c10236a1c5be99f3fd950542d5648ef9e88400000000000000000000000000000000073a564401cb1a3a53334c0a55da261814d27b86ebf40b02a76b20973ba2db92e42c138ca7790261c2d70401c984bf470000000000000000000000000000000004212d8a9e4b01f5c6814a88561c2c6143eea61327b031a2e0e4bd056c12dd7098fdfe4d1511bb441ad42b55b584a7bc0000000000000000000000000000000005c5d23824b0fe05eb962194550681c57c1566b315efa8ebc90b3593d7d86ad18328baab8118c9f47eccc0757588591c0000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c5,00000000000000000000000000000000135c42c10ef97279e3d152b18cbb8dac11ca8c805dd1d80818851424f592e7522589ec7df6748b5c72d0808399e629cc00000000000000000000000000000000083ddf3843434937e05ba9e101096371fd8fb34f226bcd517716200003ab9855f7aea94980c57a6b933494cc57afc562000000000000000000000000000000000be9215d936a49538442189c9a0bd3be07d4b0b1d14aa45afcdebc1fde17d33b66f7dc36da1ea5411549577f5a1967ff00000000000000000000000000000000176a4a4962c4af75a712e5093ec2cd5cb5c0433aa0657809dffbc0bc02b1ce303ac084f39a5721d482d41412d391317c,4500, -000000000000000000000000000000000b1b3053774ad5515a20bd4c556d2b3ba95fe74fd0c955069c7f933dfd718ede90ac295f5a675f1c29dcd9701978353700000000000000000000000000000000145746ce88686021a0635bf6f0aa2f77c48bdb364cf4ffa804a57f95bd69d24eead05fbee24021c1ef57e1c7c7b894b00000000000000000000000000000000010ec4795a0762b86f3b83de1198698af67fd1b1be3ddef48f35cf82bc96d886fbb4c75064f51a9cfc5f61630c95d0ad1000000000000000000000000000000001465e31f58892466b8ae4b76a239d9f8d1ecb1834886344013cd1df0be13591798868d224d38213a6d75b02a1fde0ff200000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de,000000000000000000000000000000000bcd916c5888735aa593466e6ab908a05af528f34a7901fb60feb1f51737c73612436c192dfdecf927019724ab2a9b7900000000000000000000000000000000187d4ccf6c22381d0c40c9d7820ff8efe6298c6dad0caa25402412661737cb482dba2719c3a50ec08cd022230952dfc600000000000000000000000000000000164510d4f2cf1e14e039561f1baf82bea678d0065e378d5bb7443fa782e6ab2a3bf7e4ea125d6415a8277c60f5346468000000000000000000000000000000000281f2e28b73eca4db9966456b75de9ae3830c74ac928fc4c36b4aeaaffd47ee587d948f68056df2826ca2775415a53a,4500, -000000000000000000000000000000000f39e731e6ddb7496448c912ae314e833d28208252c7f8e27bcf7eeaf1da6e2310538b4ef0d55401c6552e91fd70691600000000000000000000000000000000069d3612f924961f827497028737000513548ad8e104acee28f014e730d4752a583cb9a893e6169b71966a1c4a4ad2dc00000000000000000000000000000000090899907edcbd336bd4fdad0dd67c578ced4481a25b864b32aef920842689a2c23265277a6e1d4a1dc1b5047a9f79a000000000000000000000000000000000055ba64e2502baf68e46c759fca30247a080464eda2b32e7cfe539e545d6aac6dafb731c2c45749e50513979cecbeb5400000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157,000000000000000000000000000000000cceccfefe04f94e0b67b29b5df8007930665006cb5a59504c3656b8c0bfb52324cdf50fa2722ce15b0ded0efa7fc85f000000000000000000000000000000000cdf34c330c0125f524f0711197639f8aca3e7c435f8c5ea30b78e9622c4bb72a7e584980cb4c3c6ecdd0689daf36b6a0000000000000000000000000000000004b1505d7fb65f6c06ef23aef85b16f3d991218187c5782fb635ba805da463cec9cfdd670c53d680c603adb827a4460a000000000000000000000000000000001104af6bef6482ae64b3b6b39664ec06c39bc18fa91b7b4e5bfcd444c827bab30ef548b28ef5487582d88fbc6d7983cd,4500, -00000000000000000000000000000000042f1c8b9fe81cdcabea047d0998a1354ce09d62a14f1d0e9d188e2f35f2e1845c2b090c5e157595b33108c67e6c184c0000000000000000000000000000000018e69d3564d4ccc0306e1e6b227b0f961aa9afcad59d4ee1737f980dc876609c59a4c6a3506f987467beba0764b857000000000000000000000000000000000012ce5883156588cfe0f4838f819f985b09f1eab40a5ea8e30fc5d70d029a01a4537641248f4c21dd203909e0170737c80000000000000000000000000000000002888eb9778a4045feb5899dda258657b9f41345731ba630fbbf186b3be4b58ffc7f48abb65b693b573a73f85440a7a70000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc0,000000000000000000000000000000000e1ef3003fe3181f690224cbc7008856e1251430ce3cff56a1965c89a892604398f5101d1bec7ff1590b0cc3d23b854600000000000000000000000000000000185b4d4b5fd8313c31542bd1bac034046ddc705b41a034a00570181503a6ea4c2d808bba0478900064270fadf3d655920000000000000000000000000000000005bed63ab9898b89f92027c04ba256569e6285c851753e12760129c98899bcbab34b62172906a1ea4cb056d4d0a5717c000000000000000000000000000000000961129a3e212c7412018d7407d7ad16412feba8c138f4f6ba69daa1a25c6b23f3466bfde6f5f0d09ab67248a2abdc68,4500, -00000000000000000000000000000000051982b46a819c74105cb36da871fb2415328a1531d155856f6551bd043eca62ddb61f24af429edda830fda31e22cd340000000000000000000000000000000006449e5bcdb5619aac542f6633ee3e06a4fd56a3e1ce4034efc608131ff6ead70ca63e70f494f519d5c577ae7119c8c200000000000000000000000000000000153f4f5dddd5801fbf7f88a735b9170d24d5b63861d50cde9644579dcff277cdb0d5fbfc3b3b819a1172de05afb9135b0000000000000000000000000000000010fdea84983fe6c08cdc4b4ccd462bae2ba791ab5209363b10b3ef342c9a5e92184e9d8be1419e3d88402bc05bad5fa2000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc01204,0000000000000000000000000000000001504c47ab0c410b32d5f1fe3d3996dbf1b21c5ef5aa3a2862a9d561b419f818f0b32b8e931c65fffc393ce7beec70ee000000000000000000000000000000000217e9fddd2551a171a13183ae3aba6bc5ce99e8f3587b92a7cffc738b478d8293b8c71989cabf9a55c5f5077249345d0000000000000000000000000000000003874de865d93650a95af4e153fe557c45bfdc4837bd6e209b8f05ad12b8fdee6432675cd92fd739b7e98e56e7ef16b60000000000000000000000000000000011303c0c7ec1f434cdf07c110da5f0bcd85935c3a0ce9fdf5546ca61edbc2d478562dbd9aa45a5f8d96e033feac2fdd6,4500, -0000000000000000000000000000000009b011f793d9a939d916d058ffe91b58138820a646cc450389b3074ae3715d06ddec1075afecda71c65c7ca085210c740000000000000000000000000000000003d4d20f4b93c1e90a0a06bd534d8b4fd64e4c4aba77ae42cf4c5b2bd95f8b02ec4069ea246ff46404e6c9eac632fbac00000000000000000000000000000000051e88c3adfd4d6a02d3f03812362a6cfba3a6c69b9aeef75b51106cc7f1750293d61e31f0ea29b5d7aa56debb6d2aff00000000000000000000000000000000086d9c4ea6769cdf49ffbbf7351023b4aea640e8c90f9291222fd0b5984bca4d481bf7e10df921406a34804e6a09f99d000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6,00000000000000000000000000000000101ed22b16502de0d83303134a97db17ce956faedf47256a9ac86004bcd3ed112a71328a58f98a85977a7f22eb1352c3000000000000000000000000000000000e841a88d10493f301af54c5fe07a31ef90de106a6c87d5631b6967fd017f561a56176a5f3544dbb34b9f94040ebd2770000000000000000000000000000000001bde3c0076f26973651cedd3da97c7eda24451bda856026d1e22d3b65c66a3fcbfbf506b4b664b5fc06fca2d712d8a8000000000000000000000000000000000ce553ee3b7d5389798cdc5af8569aaf477b5b74ca1138454dc61badcf3ecf5e0ee8457e374b5735d0b8408b04fdbcdd,4500, -0000000000000000000000000000000010d48bf523f3909cf90aa58a9517ef5421f1212accd5e8a0f830aeb15a587e215ca9c340bb846b1d0474e43840b2af79000000000000000000000000000000000cc1a3976caf97b9d59f448f6d9f413eef8904f360c0cf912fe942b38d7fcc637a17038973a133608ae769d3e389b18a00000000000000000000000000000000069a6122c6f0ec68834b7617c755a7eb33a80a25acf95859da5ff03316447182f122d20d993b04e79b6fe859b7adf5a8000000000000000000000000000000000058c6f8c297524319bae6722e0a957d1ba0f75ee3a8aaf06148641c67925d15780e419a38ed7e07410e82769da74f2d00000000000000000000000000000000030dfbb89bbe5c14a7a55e68edc4fc38eaee9fb539a6b2f941264c7dc295da5712b0af0f2bbcdb74f785dc9ba038b0aa00000000000000000000000000000000132b4e02fda605a69251a4a6289c47536f9735dd90908ed1fb619b3ab808b3a1f1ca3fcc8f4b35c9864ae311c15747f80000000000000000000000000000000005858ece0bb09e55e012450551025ad2a6d93a15d29619433742851a62d987e7f8bfa6c6faed76493a27060ef5f51805000000000000000000000000000000000dd6b393e6d1b8d546e3f5ce69bc1737399e6ababc628f25734030e10d82b5e9370edfb5da15566d80e23d2fbf8aad5f,00000000000000000000000000000000182f90f5d3ce3f5ff2d91430376144583247def83b3e83524094d57c0f1be98b1c4946964deccc25fc303d6450edfbac000000000000000000000000000000001844806f711735c5ca18ca48e559a9e327b87b91d22a5ef161da7874668130e21a9499728fbc2c88366bdb59f8ced0cf000000000000000000000000000000000815e7cff14b4ceaf26d1cda5c267f432fad294b6baa239b65d886ffb039321f9e24330ae738a35298c6d1ec1ce1c95f000000000000000000000000000000001188a4a2f0920ddeccde1a47a0636aa7c404fd77fb9c828e4fdb5406df80ee6c258c2d4a89dae5e2a2b05210df9100d7,4500, -00000000000000000000000000000000156ca5e80be8c8c03a5506ce9abd22a9d4958c372678c0caf6f1329898507dfcb1f06a9464cf080bc6881fa5b7df1ebe00000000000000000000000000000000088174d486b4086b931010da298a399e15b60a113e08f571e096d3a4e94b57b3a684711318796eeca9319119b201abb30000000000000000000000000000000000b96ff68505c088cc03a1c2dc363b05bc8544728a12b29569bed137780523123eb17e68f4632383c252d81bca0c5ca9000000000000000000000000000000000486fc6e5224c5fad56234c41856e60bee4a6c1046f673bf7d5c1bbb603b141fc91074da5f9d3d41b796a2ebcebd9e740000000000000000000000000000000017032b16be8656cf23bfe0abc8c9e6aade223fa9bea6fe25f95a025da79cea6adf38536eae3859b25ad1af1756b639cd0000000000000000000000000000000010975ed27cefbb43bafad0fd14c87ada8e84525e1d199fdf1e77caa0b718214b33e547a42a040ee3bfd51621a20d22fd00000000000000000000000000000000133d29aa41f92de37523d281eebfe91103f017e5fb390f6bad9a2a4419fa4702bfa04847edbca1da96eb1ad563a92c8a00000000000000000000000000000000014af850de7e800ebee4be1a33c7e3b30aa94106db7defa148568ca3c8d82edc97ab5769ac40162d3728687cdac201a5,000000000000000000000000000000000cf42f2ccff2e0cdda7e5f1d7652680650b4afa523c8f9a554ec18b905c837a189fff73982cbccf903ea492ea902b87f000000000000000000000000000000000d38219770f669557cdb623f2476b5f3f7478422b016123bf86a17bf75848548d1a1ce96a292637b8d52481321d80fbe00000000000000000000000000000000170d8722b824e3291b570ba8e4f9279c1dccdefb95cb5b7a94d27ad8a93513737f12d18ef3153c4e12b530bc457af34100000000000000000000000000000000021aee9e5f578328caee3177a4e08303c3b5533e288dcb75f94992db3520a6da16f4201e60367240b29c48d175942cef,4500, -00000000000000000000000000000000121fe97c62e068988ebff21d8129d52aa903afdbb62862c7fd99564d9ad72182ab1f3a1100223ae486cd76f6938e123f000000000000000000000000000000000968ddedb04f52140160061828b5f88dfd09aaf37df625ee6f66b9500d6608df31c7edf86296eccf8f9918b051a5e4df000000000000000000000000000000000b7491cb8f6252e3861d7160feb0afdd736d27886863ec0909a7cc711a9b71aace18b17a00a2999dd57ca1a74f148516000000000000000000000000000000000fdb280093ef45b12b694ca3390a865ee18e4c04b231e2c98cc28706d4cefaf4e654582ee03f34ecf1dfa9674489d55300000000000000000000000000000000185aefe71f24281e5b03dd41e6d6d45fbc8975beb175118de7568bff0a9ccf917e9df97dc26bca16e8da06b0e9a8e7bb000000000000000000000000000000000015b326d401b827fdf556e4a24a3dd6c8036b1c849751b5ae3c3728cad88f931b06e3a345523a723481193f7afeb67800000000000000000000000000000000054ca16b4c87293002c31e64ad303e8f040e11de8b45c5fb9aca9dbec59b29dfda8532a8ef5ae6a92ac8ea90ee4303e0000000000000000000000000000000000b65a233a7731366cf24c801724265215a8626b1290d86c60bf1e74b021b0b44d7d6552f936fac7b5e60cf1feaa1d82f,0000000000000000000000000000000010d1b2f595166929347e06c1debefead06334f554dc31f320cb844abdb1810b5f7c4b933ff8072dc03d303f4a6d0d09b0000000000000000000000000000000013ab41dfca0a7cb0c58c2c19e02f675a94d9e73312cfe2999dbac34e6a80bff9472506b48690f24ad3171ad495f445420000000000000000000000000000000015bfd0db53fd4da538caa3aee7a90a669cb84460365696ee79b190d09a6d4c3f08965de7fff4efeae435db52b97d213b000000000000000000000000000000000182ffc4304b911b47b092ab678edd63ed5f5e8a9069daf9247f3bf9c0dd149cc9992728a13b0a236fc9b37714b35882,4500, -0000000000000000000000000000000010d001a09cf5dc3276482185f26ef3f75d28cd6d2667eb08a7fe06c03b99f3b6c4d82390739b6867a314291cc642a8b2000000000000000000000000000000000587846a460b1f37c2e7f491f9a097b4e86e1943d9cd0999313f65627b3907f09b5d5ac1be376a313a959dd136f7e9b3000000000000000000000000000000000af439695556e86b102926d3b40e3e54cc84464e120de3b4e3c5541a6a5bca44151fb0594009663764c1824518b13f020000000000000000000000000000000003bfd9418c1e57269e222152d321b83ae090f216cb422956dd1fcc464f68526cb4a05cdaefc7bbe6e81d4ffe27d64db400000000000000000000000000000000085dd8bfc00ba517dc8d7ddb49d711d35bd36f9fe3843689019e779624a032d2f023533b8184b73042d1a1953d2885e50000000000000000000000000000000009ba8d5d36e6efe02097a3206bbed68529f0cb9875ab81deafd886d9243bfec8b403d2abe713a2ec929b93305dd2da220000000000000000000000000000000007f8f90ebb2771136a92023901ca85e87fb7c8b1a40f88ae564a124bdd0ff0bc27ea98612a817e2c871fb4bcea3bb06600000000000000000000000000000000152de417d02f1d14e5899201db8fd5db8ecb40ea8d415dcdedce8ac70c28d851db68e9aef94506a50ec28145547a2d68,0000000000000000000000000000000017555399f979745302f08210de5311a6401b6b181100b3bc6b6d450f0f62079d2f02d7badcb164f50dfc46a975cbd6720000000000000000000000000000000014aea86c06e4c1fbf0711a8cfced2544c7624abc7ae7906cd992bdf575a702540c45c2117e221446ba09960cbc9048ac0000000000000000000000000000000002fac56960c4989a84e02ce36e8970c2e847ee45579d31ca77f042bf96505af574af822da084ae64b22ff876610ba9a5000000000000000000000000000000000a481cfea2aef8975c80a297ce5a185dacd25649d41f8466d3c63d786e3c264a8e4ccab5ef6b80ab1260e86ab6d5b3f3,4500, +00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2df0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1d9f0000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40,000000000000000000000000000000000a9b880c2c13da05bdeda62ea8f61e5fc2bf0b7aa5cc31eaf512bef7c5073d9e9927084b512e818dbf05eab697ba0661000000000000000000000000000000000b963b527aa3ec36813b108f2294115f732c878ac28551b5490615b436406773b5bb6a3f002be0e54db0bcebe40cb2e2000000000000000000000000000000000bd6e9060b42e36b57d88bc95b8b993da2d9d5acd95b73bad0509c2324212bcf7a94a46901932c0750535d00008a34f7000000000000000000000000000000000a374afd32bc3bb20c22a8864ce0dafe298bda17260b9d1d598a80830400c3fd4e8a8f677630eae5d4aa0a76a434e0ba,800, +0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d938800000000000000000000000000000000192fa5d8732ff9f38e0b1cf12eadfd2608f0c7a39aced7746837833ae253bb57ef9c0d98a4b69eeb2950901917e99d1e0000000000000000000000000000000009aeb10c372b5ef1010675c6a4762fda33636489c23b581c75220589afbc0cc46249f921eea02dd1b761e036ffdbae220000000000000000000000000000000002d225447600d49f932b9dd3ca1e6959697aa603e74d8666681a2dca8160c3857668ae074440366619eb8920256c4e4a00000000000000000000000000000000174882cdd3551e0ce6178861ff83e195fecbcffd53a67b6f10b4431e423e28a480327febe70276036f60bb9c99cf7633,000000000000000000000000000000001963e94d1501b6038de347037236c18a0a0c8cec677e48fc514e9fc9753a7d8dcf0acc4b3b64572cb571aebbe0b696640000000000000000000000000000000000d9739acc3a60f6dffb26f9b5f1fd114a21f2983deea192663c53e012b9f8e1cabd4942ad039badbd4745ddc0a26a91000000000000000000000000000000000b4206dcdb80d62195febb6773acab25fa2c09a2e4be9416ca019faeb72f1fad1dfdc51e8cea39b371a045b18947d40a00000000000000000000000000000000100758b888fa27e9258ddd5d83409e8aeac576874bc399b33b8bc50d77fce5358cb091d42f9a1b1ed09be3f200959989,800, +0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc000000000000000000000000000000000a69d6d9f79e19b38e6bf5a245dc820bddbdfe038d50932f76d0e4629d759f8ca6d573fcfc39256305daedf452f9fdf40000000000000000000000000000000015f5949369e58487afcecf8018775d1b0a73e913bf77e13d2e5a843bbbeba7d1978ca27ae8bfc87d30f567dd396b980e00000000000000000000000000000000182198bb38a0353b8db25389e56ab0d8679a1bda008a65dad77e4c95bc6804f6311eb16c761e1a5e2a5f87cfada49fa4000000000000000000000000000000000eb5483959e98c30e71db52615f63521378b156f142d46f3bb285b94aef39d80feacec335b797c5a68dc17ba89d43e0f,00000000000000000000000000000000079e4fc2190d3441fa76c2d925d23b81e353e09e9138fdde51234195e564a32c98aa0d240f051298bf966d17adc2d6fb000000000000000000000000000000000aa327776fa7e15000dd548fcdc3a1cc6f9d0ab33046dd4240a3002962131b738ffed579945a348c795cfcb33682cf3b00000000000000000000000000000000179232ec56602d1ff79861cbfa2edece34b296541483aa65fe0cb493f520b7722cfffbe04294dd054770a38bf75d927b000000000000000000000000000000001826b88a6b411330757bb304a380487a02f7cf421115b84b3f468d11a83dbf304ce7a5661f4f01299d3c7865305a0006,800, +000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf49000000000000000000000000000000000286f09f931c07507ba4aafb7d43befe0b1d25b27ecc9199b19a9dc20bc7ec0329479ef224e00dece67ec0d61f1ca5ae0000000000000000000000000000000014e6ed154b5552be5c463b730b2134f83e0071dcdadfaa68e6c7c7f6e17dabb7daf06e409177bc4b38cfdb8248157618000000000000000000000000000000000f145e998dc6eb0c2b2be87db62949c7bfa63e8b01c8634248010fd623cfaec5d6c6c193331440957d333bf0c988b7b10000000000000000000000000000000002a1ab3eea343cfdea5779f64b3bddbf0769aded60e54a7507338f044310ba239430663394f110e560594d6042a99f1c,000000000000000000000000000000000f69e3616e7122bf78230461bb1f4b194988adc6149372691d8794d0086fba0870a2255a2c79cc3426e7ba4d032fc2ab00000000000000000000000000000000174752301e05dcd62f7a3ae3357344e64d1c94835b2b742ac24449ee2728d693a0df10c3beaeb45d1b4af4ac2bdbb8b200000000000000000000000000000000051a761a3ceb275ec28a2a269b5ded1d9fd11a617c958e73c07de3a92ac480aa82c7d2a1852d291804e734526277f5740000000000000000000000000000000009bec9045ea89d5d16588e3373cc977f6d975d0e2213b171403a9b2ca460b3b2e1106b474185516d4200655b17a179a1,800, +000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e000000000000000000000000000000000d1007ca90451229d3780d66d3aed7c9d8fc82e9d45549e8586600e38eb6763f3c466e2f6ba6ba1dafd8f00cc452dda20000000000000000000000000000000001d017d920a262b6d6597bab532f83270f41526409510e80278d1c3595ceabb9ceba8ae32b1817297ff78ea7a0d252e8000000000000000000000000000000000935b7a59d2e51bbb2f9b54ccb06ebee9d189fa82f0e97d10c8020badb3de7fe15731b5895faed8cad92ae76e2e1b649000000000000000000000000000000000792dadd48a20040ad43facedc109747411895180813349d41d0e5b389176bfb15895d41665be8d1afa80835ef818eca,000000000000000000000000000000000c079610e6f8770d65352f911863b6cb4fcb25cacc4a42f75e34e29e977c93244a6241cf3d5bd1040ce7d8987996f87e0000000000000000000000000000000010d08d8f6fa8ee7042c0891ea0c3b9b59a79da52cf3a91627c79d456212e3f6f39e1f69aa0053bbdb4076a3f7d05e5dc00000000000000000000000000000000069047218b0ac1e07650ac8f4a1b9235f68408f543517c4ae3c0ec47c79b468713c704ff3680edc8abd1bbed7a5fa75d00000000000000000000000000000000137737706162e02cfa75ce2154d57c9a3520818cc04626654824769ad92ff7977942f3881a28284ea47c14f353772d0b,800, +0000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e200000000000000000000000000000000095353ad699b89ac82ca7ef631775b2b3a6e3ed8dd320440cdb929baa428e63cb902a83857cc0e2621470544c69e84aa000000000000000000000000000000000892559ade1060b0eef2cbc1c74de62a7ff076a3621e5f0f159672a549f1201f2ffb3ac12c8b12cb86ae3e386c33e219000000000000000000000000000000000750df4632a7126ddb08658a4001f949b9764d9cc43a9393cc55d8fdbb15d4a1186dd87a6433d111888a7804540ad9fc0000000000000000000000000000000017554bd444665df044b91b0b2614017bbfcd7acc7f8c5a16cea2861235578ce2b27dcced9fba234999fa478cd3f6e42d,0000000000000000000000000000000004dd5dfe38fa70625216ecfec60ea8d38602552726f0fdfb8f392362ce845fe0fda76894d0e456796e08462bb941579f00000000000000000000000000000000195a85cd0685f4053ee539de7e04fccd2380819b291f89cbcd63d5a0015b3214500284a7c6568a71f52bbdbc38be410a00000000000000000000000000000000107c211bad49c7dd8555e30f2500c67e7175eb98a8494f3d5309c65a93cce89572b7b5489428eaf3f0a5c1be323c5352000000000000000000000000000000000c11f978150ac35722679cf79443b3706d288c968116ddedc1f1d0fca8cd746e3c92dc006330be14886c53c41feebbf9,800, +00000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f300000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46e00000000000000000000000000000000175dadb6ee656ec6aebf8d0e5edaee3f119c74e0ea64e374be9e8ab9fd3d085fceeedf4ed8de676ebe9065d83b0542ad0000000000000000000000000000000005cd6a875329c23e4918976cf997e93e403957acfc999f8159a630d21ab6f1762925c063784237262bedc82402ad81bb0000000000000000000000000000000003274bcb8db35e50164d136c2a98b5a6d2fb5f9767d0ee11c1358bf7ca5ed96d9122f8c1051ba3c658cc89777d03dfa5000000000000000000000000000000000380a240443dff85b6542f75db28b87c39e278cdb8d9627efbbc63b229e6ce783f6fb0114c8e91c2fd6ea71c95bb99a4,000000000000000000000000000000000fb33caed4de22cf341bb3e04d41c0198b064c1d371a24f5cf59595ab4a1edfd379916a40cc405d35f0603b2f8fb987400000000000000000000000000000000131ad6172c20b3a1cc2542db037de1324086fd9cd140ae97987980f260023d91b24504181af6fcbcfa242f48e99559320000000000000000000000000000000004a0404c00789459395f5344544041785d10f2fe74d4bf484966f5e9b6b4c4c8cb113a811a4fa82a1cdf8e3242bb418900000000000000000000000000000000086ba6a914f3f07bdc6750fcf6baf76124a17964bf9eb9a12982e8a28ca04360da3544b69436d5663e4e94bf7189529b,800, +000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d10000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12000000000000000000000000000000000834cf1b4149d100c41b1bca0495e455002eb6596bddcb94ae48d0c65957e8b313372f8e0d6e57504664b266f38293150000000000000000000000000000000000de2875fbd14760bac4c2cc7d3f239177efe9f7f61f767be420d44f24c9fb863efd60dcd732986db8c5b72470617ea60000000000000000000000000000000000bc9535ebf11c2dcc8c7d3bcd09d7d14035635fccb5fddb7df29ce8855e79f99809781d6ffbbcb33d1227314609abee00000000000000000000000000000000039bbfb4d969d702255e3be7f255a97529a19687ce38cb70637c37894d4102591feef428b0afe8c9ef50310ae3b83091,0000000000000000000000000000000019c8a1a206c0006a3033377abba4c31c55710a094d8c9dcef7560818e90411861ce7d189e2763f8fe69bf75e719e4efe000000000000000000000000000000000cccc6bba8691c210aa0a67d26584a359fab94041d853160abd9669893c0d398c805cc37fa3c33bc5ee5ff915b985c45000000000000000000000000000000000e353c1993c36763acec2a75495560e743d099b565f3de195e011afcacff3d60502801f47695da7dd589af81e772eb7800000000000000000000000000000000100c6123cf08eab6c59d78b414fa504ed10c204851289b0598b40ac31971fa12cfda4ef7cd2d64f9797d4d2b193e0bd2,800, +0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5b000000000000000000000000000000000fc09c241899fa6e8cc3b31830e9c9f2777d2bc6758260c9f6af5fce56c9dc1a8daedb5bcb7d7669005ccf6bfacf71050000000000000000000000000000000018e95921a76bc37308e2f10afb36a812b622afe19c8db84465ab8b3293c7d371948ee0578dbb025eed7ed60686109aa0000000000000000000000000000000001558cdfbac6ea2c4c1f4b9a2e809b19e9f4ba47b78d2b18185ed8c97c2f9c2990beadc78b85c123b4c3c08d5c5b3bbef000000000000000000000000000000000ea4dfdd12b9a4b9a3172671a6eafed7508af296813ec5700b697d9239ae484bcf7ab630e5b6830d6d95675be5174bb2,0000000000000000000000000000000009fc3870f88288c680b43d63d3bb5305b99fe461e59c07be981b8819fbee0d1fdfae0c037e830fbbabc40cedac7919720000000000000000000000000000000018bdd4903da4d14fa28af4c2cddcb708238cf68673ce77a04a3926c4aaf17d39a831c5401e84dd042d6adf595a1763710000000000000000000000000000000002c398f0e8ad9752f4aded980bc5de2d91118db06818d815c11e818ead47e7065823737db8e304bae32969cab065d1ff00000000000000000000000000000000180642a633c3aa402e5c0b18fcb6fe8c115575b863abda59b5d91997ab01014faefc975d0aee994f98cf37ce79eb95aa,800, +000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4000000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb815960000000000000000000000000000000000b36d8fb9bd156f618ab8049d41dfe0698218764c0abb10e12fae43c8810b8e2a5201364e2778f6f433b199bb8f9a6800000000000000000000000000000000000707eb15411b63722b4308c0ed4288320078d2463ae659ad4fb3f9ef8124f379df92d64e077403e50727388adb59ac00000000000000000000000000000000158e1249d5b91614924acb23899c6bae408697dec0982c10d0459746499f4e6739afb9d5129568106ed1a1caefeaa9640000000000000000000000000000000019e841562e4aa75321143f8ce1e5ec6158fa5cb8b98c839a486188260c18ee8a7600930f23aa39eac2eb520d6a0fba90,00000000000000000000000000000000199600699a6108599c638df8f965d73b5de4ca74598df281ec95c539de2c7eff9767569692d8e0ad120fcbb3d9335b95000000000000000000000000000000000c42b11e2585ba93521b3c968e9dee07e4f5168c11087d8d750795555a105df70c969bfa79b1ab4e5fc8d81657235d08000000000000000000000000000000001370daa4699daa99e9940fe04f69150e6f752798cbc0e66c91c3bd46149d935c1815f32d7f14b510e16d475044eda9cc0000000000000000000000000000000016c7a00be10de5732795cc3ee2951e58cb9d42f9b05d02fbff1b83fab5d3ad830cb8178092b76172108d7a53afe8c539,800, +00000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c000000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e97795505419600000000000000000000000000000000186a9661d6fb539e8687ac214301b2d7623caedd76f4055089befba6ef2c96263d810921ad7783d229f82783c9def424000000000000000000000000000000000447f3e20caa1f99fbaccab7bde2bd37fe77cea691ebf2b9499f95bbbb77afe72b7039eb0c05970b61360fcf8ade73730000000000000000000000000000000005e11f828eda86c10a1d7929def547ac06885da278afae59c5d95453caf0a2d8ed186fa7c6d0a7ab6e9142cfa4b338190000000000000000000000000000000003d954e61b6ab71042b19e804efccd4956b56662f27f70a9255cec0c464b86c0e83721ad3785dec62dd4a9dd3d6d5d53,000000000000000000000000000000000669cc8a3acae17f99f805afb9012a38851a9e8d4fd9895a9946c29fc859849c24d7ab7b6278c449cfbc5f1d7ea1fdbd0000000000000000000000000000000007a9095be808d0ebc99bce94e851d2a7cd3e1977b923064ab5bbed2347cf18f3343e60120fa051d12fe27da3146cb423000000000000000000000000000000000f1e7f75887651f67457f6dc064d7c11934035d15fe4dc40bab970160ed1b1aa230a3fb84dc1da08770d847c0216347a000000000000000000000000000000000efbc62ade1678cd70eb38c644038bf19e52b0859f65747068d9f3124762d951e4a6ff05f34b6d14919774f8409adff5,800, +000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b660000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a90000000000000000000000000000000002b94534aa0ba923bda34cbe92b3cd7a3e263741b120240ff5bdb8b718f094d3867e3fcabeab4a7be39c8f8c4fdd10d900000000000000000000000000000000048711cf6a82534d64d072355cb8fe647808e7e8b2d9ac9ed52eb7fe121647a721dd1234c71ecd163d91701eb7331cac00000000000000000000000000000000141ef2e23a1ecc7ef2ed3ea915492e79cfffe60b5e0de8441e878bd0653843d79c724e3c5ebe2321361df99f8932ddc200000000000000000000000000000000085513b4009f29b3e00a91c2c4be418368560802ba4194cbd2f4fa3d72a55fcae547014434514a8b2a8fe3e0b28d2773,000000000000000000000000000000000e25a38d0ce2aabd2538c95ed463f226e3f29ce7f10e1be27af2d3db741926d557178c4b125af8789b40480d8beec0890000000000000000000000000000000002a94b7c57fe2783d055a537004a3b67e41f5374da0813094f5944fbabf4d27eb576dc8b21ccc15f8339df14ff8785220000000000000000000000000000000008b9efd8abfa4fd71a8eafdba9df38360ef0b0a117c0052528d1c24df5032635eebc7b201439f5de858514666c68cd270000000000000000000000000000000012a2fde51f6f4a98435c325dc3b1ae846bc33a5ffb3b13fbe3fde2f74dec0aa815fa8e42392b3dbf798cf547fdb4db0d,800, +000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b381731734598360000000000000000000000000000000009143507a24313ee33401955fc46562c9b20c9917df3b40ccbd7ed43b1349d4551cfd98a4976d6fec5fc289460c8d89900000000000000000000000000000000060566b79df5cc975e669da8ca3a7fa91bf3f5c9fb871c3d62f4a3e79dbc341b89d38b588e5414bc385d5e3cbf3ab9310000000000000000000000000000000016bf40b8cc4c01a87aafae0c4439b623a51ba9a383756a550b69d627d6f45209f0d87e4f9be9edff35c986f7b9c49e3f000000000000000000000000000000001842d9172bce51a164fbdbdb108d0faae07e4642f21c80e40ac31e737657472ae3dfe552b65349629c210a068c4afc0e,00000000000000000000000000000000067265782d58b04a2ef3dd419cee506e076e49d1119e28db1df7f0e22cba9bbdabc560084cda50bc8db3915fa9c489a30000000000000000000000000000000012448a61fb2f6fd8e355111b671f0e888304284b72d5688091f2ed00edf7ccb7e5bd8a733a910d6964dde07d393798470000000000000000000000000000000005f687356ff6c634eb46613be8e98540107e706714434faff54510234d4aff42ef7752e154aed63fa8ff905ec0af628f00000000000000000000000000000000180dca84a37c964b30f5cd11a090e54acea102f1b884319f8d1252a37bda005512ffc39dec8e33af0dde0d37993f846f,800, +000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b000000000000000000000000000000000ab19bbddd661e9db8fe4cb307ecebdc5e03efbb95c5b44716c7075bd60efcfc67de0bfd7c46ad989a613946c90a4c1000000000000000000000000000000000120800e7f344cda816299fa37f603ade06beb3b10907f5af896d6b4e42f7f865b756f14164db84411c56cb2ea81f60be000000000000000000000000000000000f688ddd257e66362af1437b6922d3397a7c3dd6dea6bca8ebd6375e75bf2de40bc287cbf3434388191e56b92949c83b0000000000000000000000000000000005252465784aff8c1c707da58b5808c69583bf852d68f96912bc53f8dae4536b09ccbbd25a49d9e744118992b92b6792,0000000000000000000000000000000012a29d35c9af52f172787c90c5a3e77ed29d66feabf5d7bdd6bfc14dd9a05d402976b84d44647628c908d1816f4e7100000000000000000000000000000000000caf3c372e36de557ecd7eba02e6a79b1b4cff30343119df7a23662c8512095e051ae2dc27e577635c74a260be2b084c0000000000000000000000000000000002ceca293a58bc9beb4ee9a0679eab037f5cf7b326d65c0efeefdbf384ad8e4bc08a3a75a02e6b9cba8963e65d6e76ef0000000000000000000000000000000004631773a6590bc89b49a75bbbe2e732f9466ba259ef7a04ae69b6aa5d5a2621c1918eb213101f6f7eeee4656a7b1472,800, +0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9300000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd3000000000000000000000000000000000e3165efe00f69aee84ac56d2161f07c017abfaadeaad34f8c96799d68bae0e6f9b557bbf9137e7826f49f29c58d1ef9000000000000000000000000000000000de0dce7ea371ad60f21f2cb61cb582b5072408a7efc91edf05b36a1a3b58fd9e6cf808d75157eedccc8f1c93a8ae07d0000000000000000000000000000000016d911943d80427385ebac1d1b293914a9e4dd9db06c1d6a758192d63c8fc9368e02eae7fb0e3a7859408f215cfa76ca0000000000000000000000000000000007bfdc6afb8acec625e50ecbc08a5cdb7862b795866323679885ba5cba3fd51f181078e03fe35e96e6383c077eed1bf5,0000000000000000000000000000000017f155ed9911ec56d71d63d57556de071ebe89be36e6bc9943ec068a70dd5a6f045dfb9fde5c1e29d52c9fc17579452e000000000000000000000000000000000a60d62ea549edf4b11f62f2321f39d41bf11f3c4f858dc7db85b1dab1b7644e27eeb1d022d6082f59c65155068d2c390000000000000000000000000000000009d309145fad15860e556ec4b4aecb415865954247c2034d5bc96026e4d6f7612af6e2db99f4e462acee2b303134b91b000000000000000000000000000000000114ed157e3d020c5397cba7e10cb864aabb47461f166a6724614e689274ae74c505fb6ebfe3e88da0d6c272a15a0527,800, +00000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ecf0000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bd000000000000000000000000000000000a68dccbe3452731f075580fe6102b8ee5265007ee19c56d95bcb096a3a6ac444f4145b980f41afcb0a865853b279bc600000000000000000000000000000000164767ea55a9038ac2dd254d8c8a4970dba93dacdf5416aecaa407914719cab165e7a32784b2c41652a86358737d831f000000000000000000000000000000000da9441fbc6578c85fdeca49082c9ebbf183de894d67c65158380ee56132d3cdb44b100d72b6d3b82688defb75d2aa390000000000000000000000000000000017d570e4f6e46550679d5d12c347414da207060f594620e2f8db66df8e0b06c912290b207a268e782d4b45db19a199db,00000000000000000000000000000000118e0c81f9157395578f0fb83b179721de2af3326d13189cb8f43911d8c3268a11fd9702f09f14c115bbdc43d5fbc08b0000000000000000000000000000000016a548df8c87f432c31e4e32c3e5b4d48d6f29fbe391d1181174be9dddee450e7e96bffe8c9f23692ccc080116592944000000000000000000000000000000000eef72a5c698c58f1d2ae9415da256b54d7b1ac37a1d1b88727c0afcfd854a41973c6cb10ecbc3a90050fe3d8d3ce8780000000000000000000000000000000019b16ca8f955dfd21830a3f7fafcc97d7de977bafe1983892988aaedd430d22674d97897d24c1643e99bfa6256df4bf7,800, +00000000000000000000000000000000000eb3c91515d4a41209a73564741a8ccf901a624df9db22e195a5d02d24b7bc0a12756b15b8d006cb991a7e088eaef1000000000000000000000000000000000704ce8afc808b0161f6f61b22d990d713ae398779e6e74e9b5771daf006ce0bba3a8088edf75156f0e48b92ee8409b00000000000000000000000000000000018fe81e05aff0620f4bdbe4a715e015650497afab62921eba0ab86b649e5a2fd3d54041868928519f537e36448688a0d00000000000000000000000000000000162bd97161201ea3c26f8dd1204a9c6b61b762bdf573cb5d20b6b255f30208ca7d96aa47b46fb8c6bf0922075f1c1ca800000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79e000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e,000000000000000000000000000000000f2bf3f69276d390c9fc2c15e9f5f5d0b3cf9a6eb028c44811b481f376ab60e17d33a04b78348e46eaa94332c5f16ff8000000000000000000000000000000000bedd0437fb3f4baef87e56f33c77fcdff6a5512571cf11fd9605697abd8763315f1fe4bccf04acc6e971d6aeefd9c1500000000000000000000000000000000067c3ff69733baae2fb4ab77cddb7563047c428b40a257a375f8cf8c9d230a6619f7932b86e0836fff0c1c60d2c4dfd900000000000000000000000000000000057526faed8d62aa10e89add5a338320c748ca1f96ba5ceb579efec69d17475571fc4ce6fce3a93398ea88340f0e969d,800, +00000000000000000000000000000000135aee0e30fbcad798738c10d4aebcdf50c89ce516325f655fe763dce54ffedf94dd74168611e5ae879b5bf5598d62dc000000000000000000000000000000000c728e672cd8b3bf9341bca929c34118b566cd3a80452d7015bee9d5cdc001b1f5c678d4b2cc4f7cac353e7bf326ca1e0000000000000000000000000000000014809aa22e2051e463fba6d49fbb060d0c7f599a0fc5409d34e71f34817e7beb1251810ae6eee1848c60796fb8647dea00000000000000000000000000000000145a4de777d86025d50e12f9a6615ecb9bdd41489992d1b643dd9aa549acbc63b04b0bdfd14b6e45c70f165e9a8c91be0000000000000000000000000000000001c2d8d353d5983f22a5313ddd58fdc0d9c994b2915dbc87a9b65b7b98ff00b62e140a27dc322d42b3ad190c1b3728dd0000000000000000000000000000000010412f3625947b38bb380a6ed059f1677b7a7afcb91517837c563dadd0e285b95740a200ddff6570d4d92bb636b625bb0000000000000000000000000000000015f4f9a480a57bd1b2388532ab045a1ba93d2f6589a3022c585fe06a1d611165c99d70be06251812405c9c37d6e9f7730000000000000000000000000000000001a78e6c5062a6634a56e9853ff5afacb2e7cf31fd0ea5f0d8c8ac6174c88133cf2f63450ec4590544c9a0e37daac1f9,0000000000000000000000000000000004fc19f8fe47e6acd37567016704b07f906e8741fcb196f697e1fc24b0204292693ff424bf1c5e407f5bcba5a3b1ab85000000000000000000000000000000001816f992c3c461fa6d2014ced382a35b0d70e61927d72b4d661434efff3dafe2f4b6cc91bb1a5dbf809f10f3ed7f36de000000000000000000000000000000000dadf7f7223ccedbeffef31c97df7e01f99299da71b589c8828b65715012aa343d7e041dacc57b34a6b5f84523a7938100000000000000000000000000000000167f7e73e22df81bd2a7a6f14e940a401bf414e5d18b3aa610b2a82ca8f46aecb5721d0092b27f8968b2302c37957268,800, +00000000000000000000000000000000009a58b7116dbd6f550f8ca98071813130ecaa9ea86d5275eebc36860690fa048c9ebeb46600b2b63e847bff3e38ed0d00000000000000000000000000000000113ffc0932c041e0e34b2540c485eb74f5029b339cb60bc88a8a749310f33f330dea137e5f340044fd689264af66696d0000000000000000000000000000000002642da3c2c7b6688aba0b19ab29ac72e35caafa044863c364ea8833fca850289de52c0963bc33d7bba40cb5f568718a000000000000000000000000000000000552d35ca054da2f148c119454f6760607b351f2441921a2be17da2cc10902d71571c5554f132e60df79679428fa07e3000000000000000000000000000000000818e567aea83eaf3142984bb736b443743659626c407987b604a30c79756081fa6ae6beeb2e6c652dbfe9cf62d44e3900000000000000000000000000000000193f0317305fde1046acda2c9491e376aa67244f68ef6495845d049e1293082af91f880be935d9d8ad0e25ad918caae200000000000000000000000000000000109224b8178be58ea4e4a194ca66bef9d14f6fc2c625d25feaa4f32e0f4d72d91024d96839bc96e6a624c5ad6221bd94000000000000000000000000000000000e42decf8a987efaeb4ede37236b637e61249bf6245679be7fd4d633e2d814ed4748b73890ad3c4fcbcfb4960cb67ae7,00000000000000000000000000000000041a5783c748247f05457d30d16f93431e9046a236d5025cc07a27b9f2abaaa556e2df65cf0f0015107253fe94d8b4dd000000000000000000000000000000000193638bf69c7508c4b12808a62e89883c34f97ded6e1b5dcc3f28191e5c7fd901a72a85ae386acccc9865f8144b1bd500000000000000000000000000000000180e8184ab583da58b77b8a4d108a366dff3e3b336ebc5c9153fa815188edc95e7067ef25f7d79526c295d634bc98f5100000000000000000000000000000000125b147100f6df0cede8e22151b3423b1dd364899fdee103c71a44388ff002a367627a2342e15833644bcde61f2ef6b6,800, +0000000000000000000000000000000018fbbcba3d4b1e548ceaec4a48db62a2420ff29a67af332ee7ea3f902f84e6c375fd33abc33d945c5bca25603979f9a400000000000000000000000000000000072ff416994364bdc6535f36c82212afa822cd94fade69f11eb38dbdcd37c7e22af55fe05e6a826dad822073656eaac10000000000000000000000000000000017bba179b847278a4878b6faeaab3b1f4bd7540d22817cd9aff95557497f8b9d286657b6162c0f89f7820becc637dd550000000000000000000000000000000018e2bfed71aa9b11fefca2f0db8bd9b8c69540267de50bec4fc90a6e9741891465c9761d19282e1100b3707eeb598b31000000000000000000000000000000000ca0d865f8c8ce0a476f7a6edb3ce4bd5e6c3a8d905d8fb5a10e66542f4325a9963c2f8d96f804f4d295f8993b5204df0000000000000000000000000000000005a966f6254f0ef4f93f082a97abe07db56f00c2ade047d2f0027edef6f00a0dfecaa24d50faa778fa29087302211f7e00000000000000000000000000000000121c51da366557c09af1bbd927521da88dfab3e2e9a95b6effb0a968795486f281f0c887e37f51837557b9e3808987130000000000000000000000000000000001a5524975400b1e88f3fff8dd34dadf5d75564cfc0026df31ee9c2c1d48b0f69a48e1e4a48cc4b7db61f023a7915780,00000000000000000000000000000000095fda8adf3981f4468fb82aa0ccf80e55138c922c6422cd8e67f53ee63e7a390bc345469e9211a1f8d810cf4ba27d0a0000000000000000000000000000000015c19b6af21f75e8e53fcefbae1c8d7f97853a8aae5fa62e606cfc92ae71890702ef9dc5609d3ca8fefd415fbd820c04000000000000000000000000000000000007b7e908766d34c5d99cb7cc76d5d5ea83c29ae1d9b83b163741bc9962e293926b1e251b546ce0c1268def728da78100000000000000000000000000000000084fbd6253211f7d66d52b7f14360729d54b2f94c52f2b76e521dc3961c40b4f19944923f64c6425a44eb158a9727a4f,800, +0000000000000000000000000000000019efd37727dfaedf697fcda7a59847dbda8ca7cdc92f34e68691d682e20ae6545ac104d6660fdb8f64a051e69298eae8000000000000000000000000000000001225ace0fdce456dd888c9672503b68ef77b2d11caf1265a767a6ea14911e3ca03fc153f18dfe9d95e0cc68b7b8a3a8d0000000000000000000000000000000008a6b059c1c4da046cc0b1b5d7f33270aceffa607daf6d0d078c06f940604e1a0b4adf01a4091306e3c7eddcf3d95101000000000000000000000000000000000f79bae5260a2f114ffbb9273f3049d3ebb002500a57ee0a7d157d86957f43f87a2e026fb9892dacaadca5ee04fc8e170000000000000000000000000000000002b51851ef3b44481d13f42e5111fa4fec04be0bf6acc7e59dec3a8c8113e5bb7b604c6dbdc5e8eddc2a1ffb81bc2baf0000000000000000000000000000000018ddb483ae75402852b7f285277ff7308ff78a3364cca8b0e0e1fa9182de275fd55c1e8ec3dbde180379c4280787ba8000000000000000000000000000000000170539890c89a4f91acd59efd413b5d1059f0c8fd8718e8f722e865dd106a4eb02e6fb0cd71b34ebc4b94375b52e4dd60000000000000000000000000000000001c2e9392f5d4b75efc5ff10fe97f37e2671cad7e4710765866e92aec99b0130e6ff1314502d069fb7b5f86bfce4300e,00000000000000000000000000000000121e7f2eb906d0b31b8ce5cc46638428b6ee57a1ee70e4ec3c2bc044230b9b86875abe0862145b442c0e34308efc690f00000000000000000000000000000000139120d0a10b82737561d0b3fda01b6df69d9beb7dbabf3ddda036f9b4c317f3ac1eaf400013fe5ad664bea44a73b336000000000000000000000000000000000a923184b381027d8cb3f82708802b204566b2b8bb6a72767aa396324d8a26b4e0f0cb92fd1914d77a4e9af2f1ec31e3000000000000000000000000000000000409732f2225cb5e5c002bef17512519eb1a18bf6c3d7f834d0c7ac8a38433c88b550b3f443d259313eb1133620ebf0c,800, +0000000000000000000000000000000016d2b73eeceee17d3bff3aacac9df9ac1c4248d9ea7d6a503a757f7bb22fa6970bb6f5cb5ec154785f7252e1508b382e00000000000000000000000000000000081edc68bbd8db7b10be06ee23d090bd54f9ca07ef24dfed7df7bb05f8cc26e6889dbd40ea203fd5cca5cb588199f9e40000000000000000000000000000000010d3478508619ea9493b4330e2fb9150024cd32dc1378f824788a884a4a30fbf39c630f465557bf0c6d69b4cbecf89f9000000000000000000000000000000000f20c9b134db5d8b7756800c031bf5962fc560ba95d4bd9157b16179f1a37ae08696a2be455ad8d018aead6adcc69b710000000000000000000000000000000011bbc566a10eadf16009c1d2655cfae6adfb0f56f5e55b31dc000414be1b4cee9a0b9f7d9eab4c6829037c327914d5640000000000000000000000000000000009b28329096d8644dfcba6e92477eafff29f7477da4581ce76d1493f03034d7f5d3acaadbe42c76a83ca51db79d456d10000000000000000000000000000000019f75a303fdede5d97f3e521b03ef6b9d7c008d770b59ce3ac38900b340895e008342701ad1b41830b9c010936f4ff1700000000000000000000000000000000161aa1853edbb56fa3bd685c9c6b88e466dfa3c4f194f6774b4d9b1f30b016993bd0d65e8e9d6dea6caa196ff735bd67,0000000000000000000000000000000006a200642d5cece5eaacacb36000b4b897e8d8c661c8282f90495002aa515c7638183cf1e80a0b35e953adb92b6bb845000000000000000000000000000000000e88d4cda34e98df4d727fda79b67961b5b8efb1b125ef2a8eafc481a2cb2fa1530e59a091f31c25cc49d38f545491ff00000000000000000000000000000000082f38c1a1c35981f537547dc3b59331ab8c5e8dd261df58fe6f0c44ef1e65d0cdc1980e1a62f6248f38d0afe91e5627000000000000000000000000000000000eda1002e202e9ee4df5354cb87760d4df32eba1eafdad27cb0636879370a8f93be0bf2a30f15f2fbcd7e52c1bdf6b05,800, +0000000000000000000000000000000003dce67181d23af9729e9fb0653d7f79c890fba27de42fada93123e112c4a468fa889921192db8047d86e4db77c60266000000000000000000000000000000000869a1e39d42d9bb0cc0568fdad16abbdac3194af893ebd8dd8f8c2c3c855abefa5fc215412168acadc88e658e83f5570000000000000000000000000000000001ef139a75194f3c4b1378c2b66dd304d179460bac0a289405cd8faa3ff66a7b6e54eb7b8742a68150b1e098630135c40000000000000000000000000000000003892b5a645af916be2c6c7fc0bb08fb5f39341d3c68598940554e1be11e1be75af920db0c8710ed13c78edbf683f17d000000000000000000000000000000000ae7289aa9bf20c4a9c807f2b3ac32f0db24e9a0a360c92e5ce4f8253f0e3e7853f771597c8141d705062bef12d4fea80000000000000000000000000000000001d2f610d79110f93145faad2e34f3408316b1dc3a72852e811b324577d9037035e24af25002ddd100cd9283b70ddcad0000000000000000000000000000000012947315d5c0ec670619125eed0de3dd259a008baee4379b82accf2391e70a2bdad264cda04c3bc1b5394a62559fa0ef000000000000000000000000000000001239e687c4d3417c3c9b655035f8d8a649c255f9a8e6f03b785eed0d416a1cd6ef7c8b45563acb4616af24f64dbccac4,000000000000000000000000000000001341cf3316152ae8d57ea2194224f04756690133d2e02d077dc271aa577278e346e0ff66e8a49ff8c983fd34546e1f6f0000000000000000000000000000000016c9093da650643f4b4061e1c6e55da6ebaf9f234bef8325aeecad3863a0a2f53e1cdb2d54aa8b075ce6e6632fb4cd660000000000000000000000000000000011eaf3dee010bf2a16c5fbb1f7aa559cd4d831f087d9dfad4e157a6d2b6495e370d9791cbaaae19339a65726ebfc3b910000000000000000000000000000000008476d793305204be414819fce2ca70754a532682876277bc0586514f2096ba9998ae848c722ead6722d5af9395ff77f,800, +000000000000000000000000000000000264dd4b477f5db65edad28c7153ed919a863c5c5661e0125c5429b323e055fd69c33142dfc6ed9c87082e2be4675e1f00000000000000000000000000000000046ea088a2ec94d3a1f1f97949f1ebc49690c453d316cc46534fa253b34b30323b6071d147d64bb94e02fb4db07bb0c400000000000000000000000000000000013692a33bb1348486eec40a9e93a4ea3810c7b4d3188cd07e235a2c898aa87ee0d17682fd24f4d978f9fb028fd26e2900000000000000000000000000000000115f8b64c00cd5cd344a7b5edc0ef0bb85a3e8f0f9dfb28f8ffe12db3e0d222c2d45dcdba0fbdc161c5d558bc71aa097000000000000000000000000000000001179ee329771b5913d07818e70f6ce5a58d74ea0b573eaa1bd3d97e45d3eeb27fcc7d37dba127af7a38354cb6ff48f7c000000000000000000000000000000000c898abe6eb76ef99f5143cfb8d840a918bcc9096ce25caa45d0bf5d20814cb01b024f1fd2cbecb6bef65d9456070dd90000000000000000000000000000000008e2a4fd746e86f90484f9b9b7b47b6afe5833762e515ccb276c554f00df88dd9aa0fb792c5f419dda0465cfed838e7c0000000000000000000000000000000012b5e6f7070c0045ade96f548ed6428c5030fa20c6f6f37a42fde9dbb5cd01def0fd8585bf8aeef913e7d42b9ef22efa,0000000000000000000000000000000009792d98ab9b90c2467ad0d070ea44f382ec7ad5290a59d889313c5a55d7b8e837333ad7ecfd97221d405cd6c549dc8e0000000000000000000000000000000002b92dd07b61faec23f48b8a7893dae29509fefd688a978bc2e870d4cd6f963d708a0611b4aa65f5644fbc6ba4c5e66b0000000000000000000000000000000011e46a283946a8e033afbf7c14ce3162a05867809d7de94a090c8cc2cdca8bb79add21f6e2fa8d7f39ea6d26cd37ea850000000000000000000000000000000000fddb7cdf1f1126e7a6780e4892601121b289a386ebce0caf96cd392ddc57c47e3f9284889fd8a18fb330d6c40bdf67,800, +00000000000000000000000000000000014c83d58d90db4821a0411fab45f83fbc05f7d0d7a67ce75da3ae568978d15f4c1886c6fa6086675c0045efb30d818400000000000000000000000000000000001e68691123451f4c3df6dae62c6a63855ec3597aae33a8a10ee274e902e9aab1460cc9c79726312df0ee0ce90c8d3c00000000000000000000000000000000018a39eb3e3c6c7fb8ee304e55d15e209afe2fe278dda93552a7b9f51fbd778da1502eb6775cbc3f832f8320fa0686240000000000000000000000000000000017c15910fad1ca5749aa82a5a2fa98b0ebb37e92912547fb1741f18c34e0d5fc3a307b928636c25f0320d71cb9d31062000000000000000000000000000000000fe2e61bc8e9085d2b472a6791d4851762d6401fd3e7d3f3ba61620dc70b773f2102df1c9d6f1462144662fb2f15359700000000000000000000000000000000031f160cde626ca11f67613884a977fb5d3248d78ddbf23e50e52c3ba4090268c1f6cd8156fa41d848a482a0ca39eb04000000000000000000000000000000000eb61ba51124be7f3ee9be1488aa83cbd2333aa7e09ae67fef63c890534cb37ca7de3d16046b984e72db21e1f5c57a8a0000000000000000000000000000000006bf6f5d65aa7d19613141018ac8bf5d1e6fe494a9f30da215a2313a0241779006bce33a776aeedae5de5ea6ee5a9b9e,00000000000000000000000000000000054dedc002c5f2da8c6e0a0146bfe5c83200b276b074e6d6f2c397e1208f152d3ea3e8f0da7da62cfd2a028d4c94fe5b0000000000000000000000000000000012ff307f86e266e7a212484a169d3e81df98217c6f715176913b0d383cbe4e790212da7feca0cea66df09d92544fae010000000000000000000000000000000009c211438dcf8ccb664b535e73eff304b92aa2f568aeaeb8e10ec142f92b211bb8147b250dad77d508cfe353667b6f150000000000000000000000000000000009d1734f4ecc88fd56f412f9243c387b9da659faa3fe7295580a6b7519b1980bd074339fa9b0bef44dcdd0cf0c4a629b,800, +000000000000000000000000000000000fa96d9fe01c18732e8d6454df9bb1f482c4b9add837ce9c354c72d49c2d44ec694674aaf0e6d6a095cab7ebb57ccd9a0000000000000000000000000000000001f8ffe3fb7e9e311e0f6949c07c26a0febb181e37b2268bb5e125fc3a100323740d1ebaa5e635dba3770fdc2ce4ee860000000000000000000000000000000012ac42095fdb677720ab3f14bf0afc55c95b43d28d922a5f8cb0bd841306b978751d24546e3a6474976961d0768f29e9000000000000000000000000000000000baf9804d99039c9fe966a696c64bdacc9673b0906b4deab108d34fbbaa3b0905d50892278570564017b96828c7e1ac900000000000000000000000000000000196044a5cdbc5300ee837dca745a44379070e9297697f5db28df4a37307cc740abed45cc778a3f4e3b8c9890ab6c3c70000000000000000000000000000000001176f5de6a3577ad67863bd3d9152ab9e8184964c6ac276e95946788f5a76394047580077c0971d874a40d510eb0443e00000000000000000000000000000000147dd55dff69213c5760e8d22b700dd7a9c7c33c434a3be95bd5281b97b464fb934a3dff7c23f3e59c5d8d26faa426bf0000000000000000000000000000000019efcf03ddb0934b0f0dba3569809d5b48b863d50d3be4973b504244414e1e1db56adff51d33265ce102b320c552781f,000000000000000000000000000000000896a38ce734c550c178786092292e737d44fa5f503d6d3b66c75e6bb70b59d1db9e8baa1ea3e256e2dfd8a942311e75000000000000000000000000000000001231db96a35229a4c7507b0ec193491446a0b43115c27d18b3715fcd4aea14d4e5c99db5934e73bb0b86f1bb91ee96fa0000000000000000000000000000000000d6f95d5637b29ea889c028dacdcb484d8ccdb243da4d5ff49e5ad82f234d414dc1484e9ed6cba1b5940eaabd3066860000000000000000000000000000000007de052fbb76902e06e1783fa8afcbb54a5069b4c5e9cee78d43da2cf76f24843a740a9eec6fe9b8f9bc4ac9baea77a5,800, +0000000000000000000000000000000014ce6d88a7c5c782562aa101550f1af487296adebd9dae8252698ba04fbd58b92e2216de6ffd474d5992f97d9f22800d000000000000000000000000000000000ce92a04f5c8a99ca0e93992448222519fc454bda5d1d8638a7bfde968386e4ba0dcd1da59cd81d4c4dca3e584be0275000000000000000000000000000000000cb570796f5c8f7b8aa02e76cb8e870d3365fe4dce5df07ec286a0a821f922b4003d5b69c0f1588206d9544013e268c400000000000000000000000000000000098056a033d9cdae86aac02de3a444471854b909680719154b44d4f55f30087294e39e57643c692d6da725b8592390800000000000000000000000000000000005d8edbabf37a47a539d84393bb2747d0a35a52b80a7c99616c910479306e204e5db1f0fa3fe69f35af3164c7e5726b50000000000000000000000000000000005015082d6975649fbc172035da04f8aeb6d0dd88fdfac3fbd68ec925dc199413ed670488dc6588f9bd34c4ff527f149000000000000000000000000000000001312d53088ca58dfc325772b8dc0e1b20cebf7b2d5b6b4c560759987b44060bf4a59a68d1a5623bbb3cc5b0bc3986b810000000000000000000000000000000012110cd462c6fabf04f67d652639d19640c46f51aadd6c4f9a6dd7806cffb6192d95c198f4c8284151feaa2e2a0dbc1f,00000000000000000000000000000000156914a9137e52abd4579599dea4c0f857eed0457ee1d80635d3a6ccf0c766ba8ab1b6f989711fbdf125c4ff06b597ea000000000000000000000000000000000c60184e8ab32019ce20d2d137130f657c8964406fe4abb26da232c9c5dbfab243837d700c88d6b9ea4b8f0a2f514281000000000000000000000000000000000dc3e6e3acb898552791431859943d0a83fb4ccd62e4ab2a971370a93a99a9dfcdbe4c42535aa063354e0f2cd48308c300000000000000000000000000000000025be02da875d4990d1f0be626ce634c4856ea91f88f636bc27e313e73897c9c13a1e3ae70c1227dfd4fba97f521d6af,800, +000000000000000000000000000000001214aacb0a5e6b7a40369a83c07fa8cf1786ce7cbde2b5a501d9c1292532df7822d4fde10a31fc0cecce3a7cfe3311850000000000000000000000000000000004f9669d8fe4f884ae93b2505710e6e45b19b7aa5df8cdd811f09e547efc27d21024cba05e2dc9d057055f30ec72d9df000000000000000000000000000000000a852b821b31cd27eca19712a636aa05ef2cd82c36ac1c2ca240edc7d0172b42a72c42d3cba583a5b5129ac1c9486e270000000000000000000000000000000007bd8419e791a5cea04993509e91a980d3ae4987a5b322400b6e4a4f2b636891a1c7ba4de96b53426dd556532403d5a300000000000000000000000000000000117fd5016ddb779a6979d2bffe18032d9a5cdc5a6c7feeaa412381983d49ab894cb067f671163ccbe6225c3d85219db6000000000000000000000000000000000dcf01077dcce35c283bea662f4e4d16f871717eb78e630d9f95a200cc104fe67b0d69d95f6704d9812b46c92b1bc9de00000000000000000000000000000000121f212cd7251697ef6a7e3aa93eb0d7d0157cf1247d4411430c36c7277bf8acfccc4ed8590b5e8d0f760e0e4ed7e95a0000000000000000000000000000000007d22d78b486f575e01e21e1239cbedc4628ba7e01ecf4a3459bd78a9716e2969f26ea3f2449685f60397e1ab2aa7352,0000000000000000000000000000000010124c1c1c10868b570d2969ebc3bf5cd6bfab13ddc93f0fd2b8a1742eb8e04d31063bb81c52b92e253128d4cb4413a60000000000000000000000000000000013f89997cd2ddae00cbf24cb66a92146c553c6fae41cdfaef14d49078729f239ad2661937dd0d4d6ffd7076b03e0aa84000000000000000000000000000000000ba2ecf990cd846c95b35ab60d4f97f5814c8189190df9d521b3dae462f2d44db006a0daecf6b82c1459006bf82ef7c90000000000000000000000000000000016dc129b83cca5b3c699628d081306c5fa61faf9dda5e92894931714037628fb829c595bf64d4a7fa295f136ae244601,800, +0000000000000000000000000000000005ef88bf38b2f998dec7302cde829076e6cf69df23aa0bf6bbb39fc0d3d8b5eafba74efb928b1de0eeb3d86ec82612300000000000000000000000000000000011f47e9583997b19c36616e4bf78d6ddd6d67937f493986250ff02aef6e6e7ff074559af2f20a5bf1d67158e4a199cdb000000000000000000000000000000000007777c8eb259a836e6459b7bdb642f878d869fdcb31b105d01f280938ef5377f2775874c099dcd394abe70f17d595b000000000000000000000000000000001607379d1cd34e2d0ed765a339b21433e9aa489609b92414c6b5a05d796085269c288d739717def9db3502e055086016000000000000000000000000000000000224cbea61c5136987d8dbc8deafa78ae002255c031bb54335bcf99e56a57768aa127506fca1761e8b835e67e88bb4dd0000000000000000000000000000000018cbf072b544df760c051d394ff68ad2dd5a8c731377fa2a5f61e61481ad5b42645704a2d083c7d45ed4774e5448141e000000000000000000000000000000000740b8b7d7bce78a51809713656c94cf98de72887676050f65f74c57cbe574278dd3634c44e057ea95babcc3d230e3c40000000000000000000000000000000006696058a191c7012a4ee7c973c2005ac51af02a85cbb60e3164809a583b4431dda2b59e1c9ceeb652b3ac7021d116a6,000000000000000000000000000000000a66f36f2437db57473bd8b7670994f1cfeb8b43c0ceae358e63a5e4e52b737fce6b3d24cc4de593bcd44c63f2c5935900000000000000000000000000000000070b7ad970f03a38c8a31452cf11422159cd3331d746031781a5861e26f54efbaba63dcb1db8bab997eada9c3dac39cc000000000000000000000000000000000ba4a9d7350adca1ae64e722df11baeea77c5fb75c5b52c8c46b9d863a70bfed1ec47888e907213f4ed4dcaedd37f20f0000000000000000000000000000000008a64244f1870a1dbcc4bd4d5c9eb5cd5225713dc73aa22bc46b1cea36c88a66f85251a8a9ba7279c88bd5dd37a06f7b,800, +000000000000000000000000000000000d6e3068c082b68312141aa68f1540ea1415e93e7f1762b6f06ff408a9995542da1c727a13355c19f8f418a44de1a95d000000000000000000000000000000000dcfcf2ab12b1a0e521ab402aaa4d32ff649a5a97892eb6ad98487c3c73c35601c313b8130ad12e9098d16eed3bcc2e00000000000000000000000000000000013777b1eefa4af03dc44e4e054eb7a3a980a9c55644900b80346be84b970e1754d1f4ab771adc9249e4accf88a23fb400000000000000000000000000000000002f53b231f1209c6f8b52f99a78bc2147c951ac89b341495f4a60a6572985ce2bc823625099ec214bc9ceedb2deea3ff000000000000000000000000000000001522e0a4ccd607f117fc6fc8f9abcd704e9850d96adb95d9bfaab210b76bfb2c5dc75163b922bd7a886541250bc1d8630000000000000000000000000000000018a6e4327d633108a292a51abed43e95230e951e4476dc385ceea9c72ed528bf3e06c42d10cefbd4aa75b134936e4747000000000000000000000000000000001198587188e793ad2ec2fa0fa1d0da9b61ed48444fe6722e523aeac270f17f73f56b1e726ab811bb54a6e42e506d70a20000000000000000000000000000000004bedd94182e0f16c71223ac3d68ab327d28ee0ccdcd2c2db07faf69e1babe3fbf3ba09c28b146eca7ab047b59294703,00000000000000000000000000000000079f89f2defd1f97efe0ba1db28523abc88cdf66efd39918a600a07c5ed5b72ab9d3354a172735e7749b5f6814a48f4f0000000000000000000000000000000009e361b8609be8057e5b3c99eaa1727fdac17edc59239af17f55d72c8b8daa89726f4ae240c742ec4b02fbd89d45c46400000000000000000000000000000000121b475a2ab50357ce80fe01fc461195029de20f61474b0773d80434253adfc268a775e1a0e3b7df5e85d1ff8c5008960000000000000000000000000000000019a76aef4e04136b1ad0d03586a3d8608ac4573715f18d5fd6907d03e5fec7c5659e15c19fd87f242da972b651dff5fa,800, +00000000000000000000000000000000161c595d151a765c7dee03c9210414cdffab84b9078b4b98f9df09be5ec299b8f6322c692214f00ede97958f235c352b00000000000000000000000000000000106883e0937cb869e579b513bde8f61020fcf26be38f8b98eae3885cedec2e028970415fc653cf10e64727b7f6232e06000000000000000000000000000000000f351a82b733af31af453904874b7ca6252957a1ab51ec7f7b6fff85bbf3331f870a7e72a81594a9930859237e7a154d0000000000000000000000000000000012fcf20d1750901f2cfed64fd362f010ee64fafe9ddab406cc352b65829b929881a50514d53247d1cca7d6995d0bc9b200000000000000000000000000000000148b7dfc21521d79ff817c7a0305f1048851e283be13c07d5c04d28b571d48172838399ba539529e8d037ffd1f7295580000000000000000000000000000000003015abea326c15098f5205a8b2d3cd74d72dac59d60671ca6ef8c9c714ea61ffdacd46d1024b5b4f7e6b3b569fabaf20000000000000000000000000000000011f0c512fe7dc2dd8abdc1d22c2ecd2e7d1b84f8950ab90fc93bf54badf7bb9a9bad8c355d52a5efb110dca891e4cc3d0000000000000000000000000000000019774010814d1d94caf3ecda3ef4f5c5986e966eaf187c32a8a5a4a59452af0849690cf71338193f2d8435819160bcfb,000000000000000000000000000000000383ab7a17cc57e239e874af3f1aaabba0e64625b848676712f05f56132dbbd1cadfabeb3fe1f461daba3f1720057ddd00000000000000000000000000000000096967e9b3747f1b8e344535eaa0c51e70bc77412bfaa2a7ce76f11f570c9febb8f4227316866a416a50436d098e6f9a000000000000000000000000000000001079452b7519a7b090d668d54c266335b1cdd1080ed867dd17a2476b11c2617da829bf740e51cb7dfd60d73ed02c0c6700000000000000000000000000000000015fc3a972e05cbd9014882cfe6f2f16d0291c403bf28b05056ac625e4f71dfb1295c85d73145ef554614e6eb2d5bf02,800, +000000000000000000000000000000000047f92d6306bed1cb840f58fd57b5b71a5df7f86dbfa55a36636cb495e08715cd57f2f3e7cd99a1efc28b1d684de1cb0000000000000000000000000000000000f4eb02d687a1a6105b4dbd740e2c7924689d558e6cbfee768dd303cc8dd0fd887f5eec24b54feccf00f473ca3f54ad000000000000000000000000000000000edad68c4d536912816cf6ef039c3dd0535dc52189583270b3b038e2c67b213d943bf384ce69c4a9dc526d7ef309f25a0000000000000000000000000000000006ff4a6b5129ef026d1d5704bf7fc0b474de92b5cf39722f165e73f4e7612d6d3bb40743e4b7b42d0dad5d5d6a2d4881000000000000000000000000000000000805892f21889cab3cfe62226eaff6a8d3586d4396692b379efc7e90b0eaad4c9afbdf0f56b30f0c07ae0bc4013343b30000000000000000000000000000000007853f0e75c8dee034c2444299da58c98f22de367a90550dbc635fb52c9a8f61ccc100f70f10208944e48d09507fdce100000000000000000000000000000000064afd6b3ef7ff7ec34f1fa330877b42958a46a7698c6d21adf73bfdfcab7793b312e21e5988652e655f2d42edb8a673000000000000000000000000000000000ea8a2217c3dbcc0f6e562de9cb2f334c896577d0b3a7108d96b1aba2d705dbf531e870d4023cec2c053345501324233,0000000000000000000000000000000013f8cdab447ef9be450b87f941c96d4e93d5efd811d80c6a910965728f7dc496dec132f3fbeee5d1e84ed7c24ca9c2a8000000000000000000000000000000001537d5caa13ddfac93f0f86729c743d9a68175a78c730528b581fb54b1f4d020473b3b766e3882a485ce5d02ab381c33000000000000000000000000000000000b370903684ede24f3df80e3834ed414a765cdbad98f20c49bef8663a82a468d3911d6bbcdc021e22c252e83a857e55800000000000000000000000000000000100cc8d05f071904753776c6092a38db84c5de751bf93216131a0f9a50bf78a722344a14b3be2a9207568d1f669d208d,800, +0000000000000000000000000000000017b32e613cb38b41dcdf3c8bb9187d731546977fbffd79fa7f66e3d6aaf9e1af6eca2fcdc260c8f90818d7148ba2f4960000000000000000000000000000000007e4d26606a47c874c20e8480a9f5815e5b577bccd783b775d10309eeb3d2102c7a0abc3324679e44362f09e7a4ada67000000000000000000000000000000000cb6f12ac8b49cfa36b957591293c87b21af0a949c55a28a90ab0fce88fb5cb7645e20ab2edd284f0ad1377dd95ac10e0000000000000000000000000000000014c96b5dcbd3150eeaea5c2bc27750cf88b30a91933a3233a4d1d9b357a80cc20d135e43a344e718dff5c79045c31f860000000000000000000000000000000011798ea9c137acf6ef9483b489c0273d4f69296959922a352b079857953263372b8d339115f0576cfabedc185abf2086000000000000000000000000000000001498b1412f52b07a0e4f91cbf5e1852ea38fc111613523f1e61b97ebf1fd7fd2cdf36d7f73f1e33719c0b63d7bf66b8f0000000000000000000000000000000004c56d3ee9931f7582d7eebeb598d1be208e3b333ab976dc7bb271969fa1d6caf8f467eb7cbee4af5d30e5c66d00a4e2000000000000000000000000000000000de29857dae126c0acbe966da6f50342837ef5dd9994ad929d75814f6f33f77e5b33690945bf6e980031ddd90ebc76ce,0000000000000000000000000000000003c5498b8c2d4765a270254dc927c6edf02acf0759540ddad951ea8c097bddb949ea0bf19942accd615bef21e8572dff0000000000000000000000000000000004c17bb648909bdddab4dd86560cb6b341e96f58c515ce471281f226181bded16b358b56d72e363f9ec491b8a9dcd92c000000000000000000000000000000001828973958204f8ab8cd13f5af5f3529f368a149bfe931a8002b61a61895457fbcb0cc6874631bb55799c884b998d8b9000000000000000000000000000000000f61460bf61bbf3ce38917850bfd3cece1e3955ce29d200c6f8aa89076c70919c02668678edc0bcf94efc9e9ff6a650e,800, +0000000000000000000000000000000001ca1141ba9542c56de8991b313c6ae42fcecb6751b0b81b8cb21ed70d5008f7ffe831766b89880a7fa6dfdb09a2cda3000000000000000000000000000000000e6766b17db165bba564ac63ab88d3f8f5eded07a40b48644e60d3223d30458e7dabe404cab8d6f9fe135712ef0b1a43000000000000000000000000000000000dda3e6c87382fa762510e5cac721fd2b654f002f5b9a3767a8c6d651ccc582e80e3f68d6913cda30f9f51ebcfc7c98600000000000000000000000000000000059a7dac5bb6b504f2bd603d486700fe22c14f25254537b2c9079c2b45d36c7ce56854c5699cc7649b533194f51a9045000000000000000000000000000000001755d8a095e087ca66f8a118e0d2c7d5e4d8427dda8fe3049080f4aff12a8746f8c2679c310f4be0d94c5bef0414a7a600000000000000000000000000000000069c84c6419ed5c0441975ee8410065a56c65f07a4b545ff596b657dc4620c7405fd4d092b281e272773d2281a6359a8000000000000000000000000000000000e751ccbd475fe7eda1c62df626c1d37e8ae6853cc9b2109beef3e8c6f26d41a5e4e0a91bbc3371c7ab6ba780b5db41600000000000000000000000000000000184097644c9b44d543ebc0934825610590cc9f8b17ed08e9c06592bf85591d2702b18cf48a70b378926057e541eb8ac5,0000000000000000000000000000000002c6104b3494fdef86d53f87bea68d313188c0908b935fb3b9f636ccd401c6e9cbd33bfcdd437e1a0150d0e4b9c3a881000000000000000000000000000000000bdc88396f807d1ba8d4d6e284d008b5e40445ce32c23a0178824fdbb6db3c5aede7687eaa2f12249125cded57052ad2000000000000000000000000000000000c7004365c1d3027997b55bd258dfc61ae07a762666fba2a14aa2ca116673fc03a6f694c069f53cd915fef6d37513101000000000000000000000000000000000ec17688d8f53e2c92502091c859cef4fe9a57ae984cb1e72686bf1f0656b10246293cae4b96214a38dc76cf2709bd59,800, +00000000000000000000000000000000090f4b85961ce97cf7f99c342d3627105d790f611e19721a43d8a0febd67ae393d77a02b999108efb56f0397dac22703000000000000000000000000000000001112f23595d1613c47486eadc37f9b1ac3b3c3973b3fe964d3b67c3996fe2eacd9df5c287b0cea8e9475d146fabcf9e70000000000000000000000000000000018f46f7ba3c9af34c1025c2d460f0be966e68944928dbd55cc7fe00e5def598d80b0e3801e48a74963c974ab4727a52100000000000000000000000000000000096845338d5cd2ac44e097607d6a1a05c241eda1941991ae9edbba965d9029032c46da7218b5b2338e6c58898bc4a820000000000000000000000000000000000213e5d2d46523203ae07f36fdeb6c304fb86f552fb9adb566711c31262629efb0b1561585f85d2ac7be174682229bd8000000000000000000000000000000000b3336b5a4f7c0d16db9615e77bcdd55b7cb5b5c1591d835f34f5c1f1468e3cef954608667fb97a32e4595f43b845612000000000000000000000000000000001869606dde1688e5ae9f1c466c5897fce7794f3735234b5af1ad3617f0688529499bbdc9f0b911840a3d99fd9c49150d00000000000000000000000000000000001bfd33df4a6059608ada794e03d7456e78317145eb4d5677c00d482ac4cf470053d33583cf602feb67b6f972c99739,000000000000000000000000000000000a44e6a48ea0a95667f607ee66290cb0094c964baed779bd6656941db28e30a7e9effe49a617be9ab376af4f535cc28f000000000000000000000000000000001933b87310bf5fa60b1abcd13bb7ac3f2ec0a278f6a0a70c953a2905ac1d3bc5a70cf1da885af45d1c7680bb4f7ff74c000000000000000000000000000000000597ce9f1bf7efacdcb0250427d0341e142226aaea060983175ea149912c5c4f3019fe87be6d87d186a8f562fc3059eb00000000000000000000000000000000198b5a891722a237a5e23e3004798c8d3f069af3267152508e283b4549fc5e8388330343f80e606eba30af51c99c7020,800, +000000000000000000000000000000000aafe45ea7cb8b450a51263eebc28c1ded662972bee512e24fddaf64f43b74b66032523b3b104a4e9f6b62394436c6710000000000000000000000000000000015cb27e1fedfba2d1679f78a388f90b22bbf3e7d090f0ba972fa8e72f6e31c446f628fff929953712ef6e425d16eba5c000000000000000000000000000000000df9931893cae713042bf722db6ce394b6f346587278a154c271d8511e690417eb6dc47efbcebb7c2fb9e77f1de9fde800000000000000000000000000000000106ffa395ef170c99bb5742428ae88fa4fd7a94476985c099e3b700b7403d083281fb71a19640c6bc2321e27bcb33fe20000000000000000000000000000000004ac6e6077d4eddd0e23f30cfd64b7aa1525c85424224e70c15d7535e02aea7a312ef24ba2dcf70b926acb851da2530c0000000000000000000000000000000006ad07d3e8f45cedfb4279913bf0a29e37604810463d6020b4fa8c8c4977d69cffaa33e1149706f04eb237194dcafa520000000000000000000000000000000002c536dd2f05f4a7eaa33fd884262b22a2ab2a88e7b63cb08ebb67fc0f143da7d6b18dd394c424161f7cf703acdc82f50000000000000000000000000000000002d1d9ff74e20ea9b03c478784f57e7a58a21ca2b1e552319f33305f367f5ae4daf8138505f953db4f86c0ec1d96d5f0,00000000000000000000000000000000047c2ccda315b9c013e87bc9168b3b8dd6d463403f1cefd824fa9f93a99f4c4f98fac5f97e4237f76b1ec91042f99bd600000000000000000000000000000000036861fd0a69cbc851741475905441b51af12c5b2aaee6ce9a27a01a43db810be9c7d6fa401406e98e327703404b83a5000000000000000000000000000000000310cbdf53f6cf8d87e2d178869bee4359a8dd666986d869761a79963680a33ea3ecefd40a1e558acae5ded2ca04447300000000000000000000000000000000108bbb28c73ed7e76a51a78e4d15a2c88c25e05c7127ae89d4347cda00be231b5e70e0b0562caddd4a7083efa4516722,800, +0000000000000000000000000000000010b1f8b1c492a56936da905b8738affba6bd29ae5fffd40ba6b31325181d3b489a81b23dcb69f6e71bd29bfb388e5a8f00000000000000000000000000000000116a115303b4774da59844e457844232d088062d920db67b2a8450a194be7e5340ebd4d106454fd9a03c8f50dbb1e119000000000000000000000000000000000eb521edd61b38006cffc43ab72d395d669dec196846fa4d6d43521da6c2fc3bf0994ce7556a3cffec7751b3bc5703ff00000000000000000000000000000000073cea36eccaa1c78deefb6029903c2b6598301bdefa9759719c3b590fcc5a6a4d3d4d19f552b33f4a3126a6e6a84486000000000000000000000000000000001913ce14bcd1d7bbb47f8efd92d7ffd155ed1990a1dbf1ee7d5e6d592a92bcbec6e865199362950afd6c8fc49b3e10a400000000000000000000000000000000020df729079e76cf06f84e3355e683e093dafad38c2ba92cf7a9faa0515f2f44d814f971046ea20116cc4b0014d7ec350000000000000000000000000000000018db123e05404eea8707f9356f417c3966312b9e41765a6fd8449879ddc4c9850c38434481b235a5bc35db1b8ee86d43000000000000000000000000000000000b4162715717e9065a3849a9294cfe39b351e57ab5a6790f3e725ad9fbf0e4b9d6a3554e872af9c37df33bb896dada5c,00000000000000000000000000000000137d23ed3fa0d7e5928af8d1f4bdfdef08e0b4c0f3bf6f51ed28960ce9805eb8fb254233bb18cbfecbadba95e112fdb80000000000000000000000000000000018615147d7a8cce1dfed6de25cf2fb52f54a243bed4913e20e66673f47ecddad9c5e4ff9653f522180de4b90ddb3ad17000000000000000000000000000000001521f12116b13f785b5211aaf438aa6668bbfa318cf0ed6d91aae963f6f00d32cc5f25d3a02bd902ccc25f847ee2db830000000000000000000000000000000014263b23396f4facdacf13c79864157823db724350bc640abf8fb6d62663cec1069eef9db56817660510e2417b51c616,800, +000000000000000000000000000000000e3925fa085db73c1e67b29ae90f8773f83be5ec684402e8e2360ffee8a8368911e584843e42b0d470de78591df6ea6300000000000000000000000000000000075c7efdeeb16609b4a47ea442af4d75238fb7534fd96cb236a7886809d6adc2b62c8ff72bdb041bc51c1a71b68219e300000000000000000000000000000000088b4eb0dd185e51b737d797334590e982b7b0a5f109fc7d0524b2465c2c0457964eba5a6d2d4d99fb628f21f15a776c000000000000000000000000000000000fc79f6b38f3356972669290eeadcd992a22bc1191606b663a1e148aa58db3938f0fc65e536bc5811c50d9c7f03d3e370000000000000000000000000000000008be924b49e05c45419e328340f1cbcdd3350bacf832a372417d8331c942df200493a3f7f2e46ad2cdaf3544cfd8cd8600000000000000000000000000000000028cd100457f4e930fc0f55996a6b588c5361816bb853d1f522806e5ec1c455eb200343476feeb07ca77e961fc2adc1f000000000000000000000000000000000f6adad0a3bab3610165be2fadb1b020f25488a0af3d418b7d7cf1165812e17aefcbc23308ebcd31d22ba4ca5773dd87000000000000000000000000000000001657ff792e3d89d5d35767bd0cc788411b0420665a5e0704f4d2399b9d9a5ad3c027ee030fdf495e5a6e2a4c69d05712,000000000000000000000000000000000038f9df6c14f84b8ef8045010c8973e5c2f8d2e37268f6a674298de7b15cae82361ebbfaa00ea1cb2653c5d00886b45000000000000000000000000000000001376f7e2d5621aa9d6f7ce45ed11de7e0e1095ebeea976f78eb83189c6852ee199840c14059c233bc3d40efbeeb5eb36000000000000000000000000000000000c7b0e53adf4f0fc5172f903e3fc479539348241edc3e277f30ae6b4fc419aadcfb73a8f8a09a1ae1dd885a6250de0040000000000000000000000000000000007a00b57ecc8b056436ecacd7e0fd346b906b15042e9a700f54f8c3b1d251c566e0c55bd34f7a9e30f1566b7f2ab16dd,800, +000000000000000000000000000000000b87c47605fc060a8e3677e84ce9d14b9309360a13c80d040c625fbf0108f829300cc1fca409a0f9c96311cd4a9a21e60000000000000000000000000000000014c4088f1e7935cf6a1d2475b84497ce6a250ee2c0c991fe51a2f2836388a354824b02d9cf215328dfce3f546713e21100000000000000000000000000000000120e59be3ecf35674eac6cdc559599b273f13f28a529770fa156f8e519734c451eefb35023639f32049cd19ea0d945a3000000000000000000000000000000000f97755b62a8cb8f861ea02c77819f0b58181aecf612d92180ba9b475f0b4888b922c57f6a1c619dd5514620a1cfd9e2000000000000000000000000000000000a5048d860b997a9fb352e58284ebbc026622d9be73de79b2807a0c9b431f41f379c255a2db0dd67413c18217cb21b7200000000000000000000000000000000045a701a3f46ca801c02a5419c836b2ab3d74ebd6f4fd1e7dddb1965b49c9a278f6e89950e7c35ebc6724569d34e364c0000000000000000000000000000000004cb55008ccb5b2b8ece69fac7283f5a9ef9e622e2a0e42bed5bdd77faa550882643afc1759b1a327c4f2277e13a3d4f000000000000000000000000000000001690dee40c6c824dc2588fc47dbf93f68ac250b9357e1112db72ded905ed7b101b5f877bdc42d56afb5b6202403a91c4,0000000000000000000000000000000012662e19e41bfacc0c792f5183596bc7f1986f9bea72c626e187d72111b6ef3f36f5afeeb640cfda99b7044c0d0b846900000000000000000000000000000000050ba08e1b9fe95dc67e6ee1ce60664b291c80fdb59729cdea75dfd18f22fb88f837b439fd119c46c996787d3008194b0000000000000000000000000000000004ea0f488fece967675abdd3c42f8fec25b547cfc45d42fba14bbc55ad7e1a75296a679113d0671cef0aec0c2165f4a0000000000000000000000000000000000f617f51800b09150a7560505079c785ab45cea4705992fc0325edaf4ceb30e1f0bec35a31898db5f810685e55634076,800, +0000000000000000000000000000000005860cfb6be6720118623d2d8ba05e686df22744b948421dd3cc1b1691e00d9b5d00d00195b4acf7a7b043f764f3f1c70000000000000000000000000000000012632a3313dd611e8d969bddd556c2d79ff387603462ac78ded3a842981697bdac34ee6f1f4744ed2ff16100874ac24000000000000000000000000000000000112b94c317586e343acadeca611c485c3ea172bc10dd39158c1e678007130062a921b53826d7be6286963ff822f1066c00000000000000000000000000000000040de8c0dadd2a6c2a7ea0fa43e1a5f2f5a6be3fcb0de6875d8cef1ee2daad87125d12f6869c4dd3d931b296f1df2fb300000000000000000000000000000000153cec9690a6420a10e5a5a8ca46fd9d9f90e2a139886a07b375eeecce9083a5f5418e6baf64ef0f34176e432bc5343a000000000000000000000000000000000d87c1f37f83ae78a51af9c420e2584a64337d2d2dd8dc3b64f252c521901924e5eec1d9899594db5e64c93c7a01ef020000000000000000000000000000000017078538092ace26cc88b94360871fc9a6bb9992172158ef3a16467919955083accf8d55d48c7ec462a743dbbca7b448000000000000000000000000000000000289b703157a02fc1d687a5aa595495be8bbb3eb0d70554728255a44b7820e0ee82d984d5493c800f1d9d8ca0c9381dc,0000000000000000000000000000000019c774e968049bde2188e844c3413203bfe2c4355edc8cbc2cf6f977c34c0a42a206194e6eecba3c97b24558048f3aa700000000000000000000000000000000081ccf6f111575a946341759b9faa13f3608998fbf4ea3b547804737e30fc7e33495caaf2aa328b19bd48315c5c7f9e2000000000000000000000000000000000a4098536041cfb808176c7cd8e980eda613a2b390e8d63d607caaac26db02fccad6d87412b90cb4b3e186bf9ccd31be000000000000000000000000000000000d3c784c6587b9f786c06099a62aa639f40535b512ac2440912f04dfcd1cb5851b7378f381fcdf02d4e58312eb7e442f,800, +0000000000000000000000000000000006fcd2c4fe848e9462ba1112baad39031c210952adbdd06293a622ffe2d1c6e4fcc8773ec8913717018b97bcb9a554fd00000000000000000000000000000000130a97442f3273b7b35464545e7351faf71ead9b8996c63889a45945ed82bba29bff5014776c6185219a5234d8475c92000000000000000000000000000000000491d571bac5487b866022a0714be11b38bfb296233845cc434a50be1d35f516b8c6b046fe3d0a8f4f95ac20eddea01b0000000000000000000000000000000017e34b04e6fdf152c848f2432b7bd84b3dba3915f06eb77efb8035750aca9d89e92e1d1bc4871105c440d639e8d8b05500000000000000000000000000000000057f975064a29ba6ad20d6e6d97a15bd314d6cd419948d974a16923d52b38b9203f95937a0a0493a693099e4fa17ea540000000000000000000000000000000014396ce4abfc32945a6b2b0eb4896a6b19a041d4eae320ba18507ec3828964e56719fffaa47e57ea4a2e3bd1a149b6b600000000000000000000000000000000048b3e4ba3e2d1e0dbf5955101cf038dc22e87b0855a57b631ef119d1bd19d56c38a1d72376284c8598e866b6dba37530000000000000000000000000000000007c0b98cda33be53cf4ef29d0500ff5e7a3c2df6f83dfc1c36211d7f9c696b77dfa6571169cf7935d2fb5a6463cceac6,0000000000000000000000000000000016fc7c743c5ba747640a6494fb3c30caad5a1e9719a1994d0ca73bd1645fec118a2887acc8876d105102241c10274cd300000000000000000000000000000000058a42a0095a7388fba7ce71dbef4ecfd2018c3fcdde14afd2be26588de4689d8de757e1e3ff22645fb8c17aa60265850000000000000000000000000000000010bb622f649e346834b95e82f93ae83c71c0a65df7842c4ba88df7f6eccb0217ca9377167a6d14777e0474c24821f8d70000000000000000000000000000000010c180c685ea3d0146eb82c007fec3efd129880f18f838f1cd2f80181f5a4884d6b5cc8247430fb0c1701a57f9d1d485,800, +000000000000000000000000000000000f1b8df4e8fdfe32eaf227f5af9f2befc85073468f10b81d32d0e126fe2b0cc8e8adb8afcac73213b6ed95e8e843b97c00000000000000000000000000000000004e3fb435ae0fb2d8bd091f250aefe5922b353a64e16abd75627737f3bc56639f8b40652cae69c73ff1969925b0afdf000000000000000000000000000000001003aed7cfb00efce49d6b1a8eba27df87479a4d37bd7fda6121549483b669a1a761204b0dd28262bf27e5c8e180540f00000000000000000000000000000000114fbca7caf782b3296d0b26b4c362bf50acaecb8bc5726b2c99f904ec3d092d5d40991d0d30c8e79fddaa45f04a75d3000000000000000000000000000000000b6069a2c375471d34029d2a776e56b86b0210c35d3eb530bf116205b70995e4929fc90349a7db057168dbe6c39857970000000000000000000000000000000014251a0a154731f73513b99d830f70b6fc4bcf05d11f52d2cbe9795ee8ffc5a5f717ad25770b8ecad6d0e9f8066e0cba000000000000000000000000000000001172684b21c4dfe02a55e13b57bbf105c954daec849d4c6df5276b02872c004fdf09d24f4eef366bc82eb72fe91bf70d000000000000000000000000000000001151aeb9441c5a8fabe80867b5c791420645241eae1400bbcc064d75bedd39de2ef585138fe9f65725efa1b1e5888d03,0000000000000000000000000000000019419b635c3742cecffee02ee7e2b1f18ee9ff15e647ca0abc4398ddc421ae7e0444e3c1ec377def9e832d8e64fd40e2000000000000000000000000000000000d9b4abfdaf3b4c7bf00fa07579befa10a3418d8fa0f3a9c31e59ae48b0de50fc8e6d583aaa4d0fe6048bdd1a9c60eb60000000000000000000000000000000003c96d57034ec97c4abef1c2c81f4d4b0f4b6eb1e9dc5464bcab28572555b9b874df80325941501c3766fd7e06bfe7360000000000000000000000000000000002dbb3d72385b562ddcb9a80400ab3770f00d22b880cce2fce1641042b9da669b22b2fbc97617648c25ab644e661e2fe,800, +0000000000000000000000000000000017faf481fd4cb0c373d21d7caad40e93d9a86e62d26136892fbcc6f6e48205543aff00c45e82fdd1d3e0e733de91e7000000000000000000000000000000000012e14fcb9ad4d9d15347cf004745ed4bd92097eeeb41c4cbcb728a234616363589d8f5ad4cbb61d31a8aa27627723c7e000000000000000000000000000000001513dad1ff27e053902e779e35d04cab648939317830144ea775c435a4b55e13fa2fef03a1256abf5c187487c25a774f00000000000000000000000000000000139da29de8587c7d0ca9237c37a116387385e9cea453b9e2003a37ede7aa0a3f4c1df55255897f5975b662be33622dbc00000000000000000000000000000000161b70d0f384e589d8117938602f3d696f941c24e3c1ca5a9be090b670456c9df315d6fde52daed55c9d8335928a7a3c00000000000000000000000000000000186bb9e6f5ba70dd2c66a641d3b711844977939904c59946d4e9f49ac2d8c00890a43ccb20d4a62bfff63ce4a0a44e8e000000000000000000000000000000001995b9d697bded656236430e78726f0f6ef963db9a5a24d455c12db38aeab0f8629e5dc2d04920156f2a057d69613096000000000000000000000000000000001119b13caf82c18fadcb65c9c166914bfd822534bb9def3feae6c9e572c97c84e97fab3b345cf59358436a404075493d,000000000000000000000000000000000d32b00154a5fe75c576c098419744ac36b911ee800f94bd598ff9b6adcaa39c836bc158c5d6af72c9e715a242d0fe710000000000000000000000000000000006e057c13885d6c05f5d92061fdc4d532f10d31d472c371e71367fef7c5fdd3741e665321d1119b895660fba3770431b000000000000000000000000000000000bfe695c3364e15479741e974f838649e789a76d073e552aaa60981fbc6d185eb7b297fd59e51535965214a02f5cd67e0000000000000000000000000000000014f0a27412248e3163e5f82fed02a25d953b336b0201692f08a3e8e9a9d223b736c70c1a39826a0888fb02a314e223fd,800, +000000000000000000000000000000000c118b147ee3489f30c6ecc0256a314ab674110588e8b69ca6d265fc270c3e5b767817f861140cca5d7c6be4012d1ffe0000000000000000000000000000000014800790654726959fd876b035bade0da744fb36ee5b304f228663a531345120267c55ac19fd66022752010e5bea7cb30000000000000000000000000000000000193ab7ac2f151750356b6e178557460c9c2672b1736d19a20e3fa28082479ca60021aa68edf2524f1aa826ee70b65a0000000000000000000000000000000015cee9ac55ab45abbc57d0ea6ec9ee49f6c59f6b94f99589dbc08ee877d3a261ad77f5473fedd72ed7206647eeafb6ea0000000000000000000000000000000017d1ffcad218efd8b09c68eba34dbbc30b0a62ae250368ee37e5f6fd40479b8580563416afdbd92c0622c341331e20a30000000000000000000000000000000009f0eb3805ed78aa3952a0a437966258ed38cb72912756253a7a2f9113f0dd9a4e187062b0423e0587d93e904d88f50d0000000000000000000000000000000001bca57e985906695e14882f2aaeef75de5009e8717eb59962e978aa11e9d0a4d9a9e203df774cb1e993b1c6ecd6048c000000000000000000000000000000000695b11cc32740c91546eb7d554ca8b1f3afc942ad977345031be8b94b78b57a87ab049ca2d3676e039efccbf24d0c47,000000000000000000000000000000001566022247ce012b7de92c8495876b4de91c36448f4f7e00f6e154185d38a735e701dda989ae9e37d332a5e60af5d06b00000000000000000000000000000000065aa42560df7990df2098827a55ceaabf3ec592c53d2f20e5dddc1481ee64381accbc8e58601428d33589b3af78a4b70000000000000000000000000000000002d9b0cf8bfd1adf76bca80ca351a4340f02434090518807e07ed76440497042f13a0cd7a9c30086872d6f145808fb290000000000000000000000000000000015daaa131431e3e78a6221091640811fcf88c835ac975a041a7ab50bc1d06b80e6a3c9ae77d2390fd14cc9bb009b47cc,800, +000000000000000000000000000000000ef203fab794a0ef29eb2ebf00076134e5932e27c99d6d445695b9df2afe7563602e318caf5d44724a21790ca0ab0d180000000000000000000000000000000013b9b1b1d3e98b61b0f1a0ef3a1a4ceed57b6c01849a4ad66a86332b3d27022cfccadd3567e6709d2de5b23b23dba43f000000000000000000000000000000000c1fbace49684f4be32ef6178ac3a95ea3f50b11494340fb73dc5391d50bcacafb3bf0f2631fea9c4ec47327d644489500000000000000000000000000000000040f82812855aa3e3aaba826d5810c1049cf44e86e44e23cc6da437971b529d2f2676c73e1fb9da52640c981fbd710be000000000000000000000000000000000546a0cb9d9f1ef9ec4a1e576fa0047557a56c0217baed8691c4085b88c84a0e12d44043aab8671393d02c4a764407ee00000000000000000000000000000000131884c1386980a181353548da9602db70ab495a661e76235c4b0a32b54acb0dfd8846e17bebd731e8041c4aebb8776600000000000000000000000000000000135b3db43511dbd8b3bd5a91880d6da1a2bd1383000e0d6f0a521bf88a5836a3b5f7cb9c0c02aa861a1c2d339f3c11f20000000000000000000000000000000000e1337271bd3302a1cab762161ccfbf2a18b7800e6efe58cf897d4adbfe4cb3bf14f4b59307fffc548179bda70c18bf,000000000000000000000000000000001290bff629c93d992ad2cc709317c48980b0e56a32fe239258c7aec75e4523e0bc0b81319e100d10568a44847869a8d000000000000000000000000000000000055d9098e08eabdf2b883df35efebec9f6afb16d651ebaca1067e2129146268664ec51c8a4f28f13a250f3e9883053780000000000000000000000000000000002424dab6f0d18ea8bdded2a72bcf87c13307d27d53e8ec35e91eeab97fcf3398135fd436c530c609fd47a3508472bad000000000000000000000000000000000b25d0db1e28b98d4f9d3c77c0b71489c51186105d93be7fc2cf8c72b8abd8959340114635e705e698b0f257855ea4bc,800, +00000000000000000000000000000000060d7a718dd02b147c265f71eb136d1f31781b12a41866b4f86d7374b93dd10058c192cc0fba928373b1526e1a5d7d7f000000000000000000000000000000000cf29275373c0573ef22bf87919faf5444847203c7dc6d2e18986152cc294be04a5b1a4b0536797158113a15276c4fc6000000000000000000000000000000001016d5b9d4d200d7b4b7cc3836b85d6697fe14db350badba9978c7b56983dd1a7e572640ee0372b0a4e2079ff4c1abf2000000000000000000000000000000000f2768d104d895473ddf8c6b3cd0e7c22458d0037eca6365c766879a07c95037ee0de00d32c974d767080935abbe0be100000000000000000000000000000000113dc3354146ca79eb103b31b61fe8bc6f33dcb9c59a7c39d989bd9411c1afce4239034f84e6b00a084be061c73e69c0000000000000000000000000000000000ae33bf68f24978c7ea9fc58d8d76047ec45d01fdbc880e6a5ba02a22a49a3a8253afe0678ecfa6013f4849da3401df70000000000000000000000000000000012c5b00376a1dd31378ec44f2dc8e321e17185d903cfc5c15345a01c33f2f151b21b938d31816550594a7a1e7216c5b00000000000000000000000000000000013d79f825c44775c68e90932d0496a5cae53f04a1edb19f8abeb5948a3dd325dfec4a8b6f58c7fbca9cf3c09b909d8b2,000000000000000000000000000000000cb2998b4e634bc83b5585b0683b7b561f260eefb826719bdc3c95e8ae51f8f7b442d75d69e0f9228dacde2ce80ef4e60000000000000000000000000000000014d30d1c02122143868ea01b454a4f33432d875f8ba66e6bb1e02fc161bb5f9298e673339a9183a15759f8b94b519cad000000000000000000000000000000001068bf3c768e8c9e9058805050394ea820b5f60bea6d271f8e1fb665d3b7931ab0cc03dff4cbd24577b2c254a956e8200000000000000000000000000000000008b7f4148bd1f4926d2a84497b60a48701057ea08855bb9a2f838d2464e66360a59d058d9072f1416023cc72045af558,800, +0000000000000000000000000000000017b9ca4349fecaa43ce911c0b256680edb8a0906ef5460fc4d2004579336df1e19560fe960a7a7cd74bb6e8272e08960000000000000000000000000000000000d5b96dae738db59cc67a51c61bec6deaeefaaa51e3259243fa4b142ef59676231229ae386ce699fbe18c4c00bf9d49400000000000000000000000000000000111b79f4b68dad16550a13334d09dc38336a75a5da23a17b5064e2d591aa3dab4c2e982a9f730a7633070504663a24610000000000000000000000000000000018f6d3616a7eaf17c805a88c9710039644d01b61aefebf76717ddcda6f4bb34aa15702de1e92bdb27b27f3409638da900000000000000000000000000000000006ccaf6c08f831be9c99a97714f5257a985cc2a29b5f5c81bc8d794dd0d8d1a41eb5413bed654c0140dbacfd0dda9e1800000000000000000000000000000000144e9cf91580800dfaa47c98ff7d002a576be76d9e44ae1f8335a3f733e1162af0636372e143174d872c7ea89f4c743900000000000000000000000000000000101e143b838c8a3f5f80fb1412081091b875230f1e2f9cf374d4bcd595392f6daa9552dbb6d5834e27b1b3dafe061ed300000000000000000000000000000000072463400b3e875395a1cdd31d73d51396e34347cd86d9f6f43f42253b3cdb24b89ed7434b1522af95ba1ee2d29ed1bb,000000000000000000000000000000000a7843a1d67360b8a6976aeda2e4e98f1ea229a4d84b947dcf5ed8215173d5cf783920a7714f5b048778df30f01a0bed00000000000000000000000000000000035663ceafda9e5bfe934cff725b36b258f12afe749f907a560a06da4abf8380853f8de31adf14d62cdb310d8740e29b000000000000000000000000000000000f210d576aa5d4cdf5aefd8e55be099c422debc217ddf0151b8801f7d16456c97d1e134b40e6d71d296ee2518e50af9d000000000000000000000000000000000219efb35c68540c6bb0ef224e68dae6f7d48425c2908440072f5f63eec3c8e750b559c73e33464d0b5cdabb50fc4d3d,800, +000000000000000000000000000000000aeb5c087644595d0912879f61959d2731ff55260c682ed2bc5fc55c13964ef7c1f70aeb55876d2264d558c31371ca69000000000000000000000000000000000e173848f4570525b03a2b2c86f4dcdb8b28dd6d18c1354cad31028eb1b8b44432c2346edaace093e3954c7fa6d338a4000000000000000000000000000000001949b0902506d111ef6318edcd7a58ca4d69f5804a028aee73c3786cb2db168c6a73b77194f7a021ae6ae43ac78ade340000000000000000000000000000000017c5e28ba6103d97e2f3d3611c0c78f06406e0da8a49ae29c7d460b52f75136920784cd500aa3593858b877697eb8424000000000000000000000000000000001354146aa546754e10ada6e0fe98f04f5f3a3f8a8350d0295e02b8e9c80735b04c3061412e08ddb13c80ac36e5638e540000000000000000000000000000000012ab26513534b4dc1b71eec46b73199c4157ba9369e66fbe4d2d8f62237fc7c6fad31854ebd878f989b8c5cf35c7cfe0000000000000000000000000000000000eb731bc99cdadf7f2280385c7e17d72d34bcbdbdc725d5bc94e841036115e8cb95df08084221696f9be479821fbdd7400000000000000000000000000000000143ba7d3f66445249d9a81a6949f24ff40e7c4d270fa044a8b80200a4369b07806c5497a0ef9e9dbb87b9e63694623ee,000000000000000000000000000000000ce704e650605f747cbc0bc76e82de8569ba7b3d897eac2bf5f79aba17ef4c989731e959c0bc0b7988000a9b0aef39430000000000000000000000000000000003cd3f3d978d6c85d98812ea0e3d21149bf4151ad1bef966ced124ad62dc7cde55f16e8d08bb1ad54d3a23bb73795d8f0000000000000000000000000000000019d37a20fcf6244c2898b271535e3b8f279eaac5d8fb1ba142096da383488eba28a21d038d7a9d3f9e8a008d6d3ee1d20000000000000000000000000000000001ba9c1720a4ef07ec752efa1ddb629505b3586af415c916fb0ed2953cd8943d9343268f438db860f0bced3e690a66b0,800, +000000000000000000000000000000000d4f09acd5f362e0a516d4c13c5e2f504d9bd49fdfb6d8b7a7ab35a02c391c8112b03270d5d9eefe9b659dd27601d18f000000000000000000000000000000000fd489cb75945f3b5ebb1c0e326d59602934c8f78fe9294a8877e7aeb95de5addde0cb7ab53674df8b2cfbb036b30b9900000000000000000000000000000000055dbc4eca768714e098bbe9c71cf54b40f51c26e95808ee79225a87fb6fa1415178db47f02d856fea56a752d185f86b000000000000000000000000000000001239b7640f416eb6e921fe47f7501d504fadc190d9cf4e89ae2b717276739a2f4ee9f637c35e23c480df029fd8d247c70000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000068edef3169c58920509ed4e7069229bd8038a45d2ce5773451cc18b396d2838c9539ecb52298a27eebd714afacb907c0000000000000000000000000000000004c5346765a62f2d2e700aadccf747acb3322c250435ce2cf358c08f1e286427cabace052327c4b30135c8482c5c0eb9,00000000000000000000000000000000160d8b4bef36fc3d09af09dcc8357067c22e421f3811deea66faec42a2f00fa4aceca8725cf99062613126a9fd7bf7210000000000000000000000000000000004e8691a42c8f3ce0e7c0470446689e9d2b3cf57d55fad7387d624857f977cb9c6864c87bb4b6a2c17538478ac5fb5960000000000000000000000000000000015e20f6baef033efbd38081d5a10eeb3c67d89ebe5cd652110b778313c9e86cffb45231616d5b67e9ec8b7be15980aa9000000000000000000000000000000000af75dc221050256015fecc2bd8113b42afc9c624e5d28d7ff8312af499e34a603d66a4304f263729b440b6266538316,800, +000000000000000000000000000000000f20a07526a082e88630a0256d134a8a5e8ada07b1cead39ee838dcbb30904e9016107fcbdf1f8ba182308dbe0b043d20000000000000000000000000000000014fb7732f67abf60c03ac902577532d0acadb5f3db0d6397a42ba693526ad74f2c61a0195bdc9704aaaf12e65aa6d88b000000000000000000000000000000000018cec4fb81c85d304588d11f8b9c51f5a053df11463e5812a1b2e6c7144522ba36bb91adf219892d0007cee470032e000000000000000000000000000000000b8e52d958a12a9037e8be9bc0d5045cade2d6ea05c6e68462b3a30b5d4ea34e5fbad173761e4e216b2e6958c8983b28000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000f6fdc4e5dceb555c9eb4c912fedbfb3cb1b842345f73ded02cfaf8d397c4378809721094aa4a4113a368e0787effeb500000000000000000000000000000000143ac06258c579c11c05569669a2a10babc63ecc86f85c91791d8ea48af700a2067c5f13d2700b8d5cf59bcca8fbf7c6,0000000000000000000000000000000013edd8f016f6af49e9bc461ca14c438a32eaa3d1270a5acec99a666aba3f0a7e7eccea81720971cf4432bfa94cd18392000000000000000000000000000000000dbea5617e44c82da828844a5a4a1426d43422fd0158204a99f53cf9821f82f0bb0130a2123297a6941f695e172d9c5e0000000000000000000000000000000005f65a445e9f2d57dff2b210209f9faeb1c8b446454de4724d990aab20bd68362dd7ceb5b95de361c129855abba83f7e000000000000000000000000000000001219ecae79d62d3039e642369353993b1ece049331f06be256f06b01a1c3b0c617221c8d8f0bf4b6a0abe1191a3ee8e2,800, +000000000000000000000000000000001468cb35a60898ed129f30c261b8431df6a154c250ec16d85a22f8717593b2c21853d123da86d977a7938c5ed74ef23500000000000000000000000000000000011f4e28e31b5f9e6877192a5e632d8c1ed7ca0c42e6e9902ca68f1c2de0f648c6064436012c5c7b14bb8d1078e02f2c000000000000000000000000000000000b25114b2697ca7eb1e6effdd1054893a188fd382d387ec098f846c1137a9b9baad01653b963a0b0bf3cb50c3ce3563d000000000000000000000000000000000c1d241cb03e642c1752b1e1886472477c19a2801ec032dc220c3243952f882094119bb92b621b654b766bc900d2d4f7000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc500000000000000000000000000000000192f3e8ae2588f9223de77f5e872115f1edec96d6a0f403a47879410c2562e79853c9a706e423b83fbf3154234edb6f80000000000000000000000000000000015084258d58fd1a07bbdb2e90df5a56ae15a787037eff4fe55f660e45f04820c6fc8982303b5e82074cf0cdcbde61307,00000000000000000000000000000000158da32df45fe3e9102010bfd7faf3fde936bb8e52f68262ef479ee825a0d7169ff753aa042883a5403103a9bdafd2be000000000000000000000000000000001800a5776a47f52d2af08144364a6cd7442a0e2fc214a2d8d285a29bb7bd3a0293e89f0a1856223a527100d0abf12899000000000000000000000000000000000a6079d18ff3367c47fa61a57a967b782f3529bee93f452ecebd4f5c404b3e1769c100da9b8aee4258b5191ae1dad9a90000000000000000000000000000000011d3188a927e8f13aecf7f8637be6ddbbce309393a94fef77923c286244f8531d3e137e031d8c1af829891425afd53a3,800, +000000000000000000000000000000000c80d4474390fa791ea5f2f16b41506d8ae13ee0993c8d31a07712687298ee7978a724999500c42400d2f788a5a36067000000000000000000000000000000000592705cc5a8875750a4e6ceb42aa3bef5593eda9e8212702a2e08ea70277a2a66526bc5237be33c8449301544da35e60000000000000000000000000000000000facabfbd15284c6433f17b0e6035d4fdd84d3ad2dd30a27d52809652ff6e7a684d7724697919100567ad0c3e1a26320000000000000000000000000000000006a0fc4e2af69ce15a356656f5d182a2cf213d76a6047a05a1a3375909d245f5316b91333d2141c0817438f0d87bb52d000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f000000000000000000000000000000000477b55bd7fff14e0d1807bfc21edb9481be01c12abb1460d78b1aafe42953730167e32e694c2ddfb0d442e8cea57d460000000000000000000000000000000004b884c6ea36f189dbc3c0e9cf88f08baf5d868579998f63b752e61fcce3cf2c901bb9b51959d3597c4ef53cff41fc26,0000000000000000000000000000000019294d87be784f0f8fa29de80d45a697bcb694b32f3f6d7641d4b08d8a7ebdad0ef78ba5ccafd6b7f240e1cbde019c51000000000000000000000000000000000645f7851644e1e7e255d0b3dca769b987ec3ff2c9eda42cab65dc39be2f9858c31f307d59f6a2caf9dd932d873d2b08000000000000000000000000000000000e8e93f39ce05a11d40f3b52262980c79ecc52939dd02b94df3e5034a57061d040b0c8894189f4626f37bee485712dd00000000000000000000000000000000001e0b7c9c3d7456b2c0ad842083e9ce2a00da91cb1aaba371ff4b9370f0f2c08f4b53b8e5a3030c99b2957cbe5f9e967,800, +0000000000000000000000000000000003f629618e1fc3018bb836301ccdc59022f0a25cc9c5de6e4c31fa08feea525c83256235e4ec8364e77e5df478f5f62c000000000000000000000000000000001120d6af221ba6f4351bbee4c2c664a769adb17872646df2c408f70c99ea991ffced4eab50fa98be1bb9426915f125930000000000000000000000000000000015cd16b028ce3d58b10aeb84b783475d894ab3f0cfdf7104ebb4f3417a038107128f07518dce548271061cb8c97e88af0000000000000000000000000000000018379875b68bc26107f9a068e5034f29dc2ae7e8830f8e9ecddc53fe7991206646cda33d37b31a47a977b46be58d761800000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac800000000000000000000000000000000183b7b917aaaa94f0ea9959273ed4701102346be2a9d72531bd18fef908ecb0579a6ac10ed42a91f1147fc3a05b2e81900000000000000000000000000000000070983b1582a97d9797782e4f960a298aaa8ec509720495acdbf176d8ecb9ec9e041c2b5ed6b7dfb46fdeaae3fb34150,00000000000000000000000000000000040f355021ba50c9a3b2b4267668ac8d76dd88991be984ab5bab9c96faed6dcc6e8eac78ed29cd6f7d687dd55cc5d5b70000000000000000000000000000000017853cf0a39332e3c7d75b08b2940d693ac7cfdac46719787c22b55a2ab1036d6f95b68075f1c585942843aa486f17bf0000000000000000000000000000000008696feb333417a7262e8976d1546b6d0a9d5970095485b18efcdee8993b16f42e6dbfdd08d30c45fe4af6a5e203de07000000000000000000000000000000000ec26926720243124ca505c0e04923f3cf5eeca2abfdaf4388960b87c6c1713fc54cdd1c825e2ea359cc67b3bebfa2f9,800, +00000000000000000000000000000000036570783711b381830e35878fbeb187b84884a9a0e88c38e84124515b470e6ac18157e1499026b27f4f731a961eaf330000000000000000000000000000000008382838c18d56c046a8db495babf8d14c915622d7917ebe10cf7da7ecb65f174cddb9e70d0262ada961b396c5511b410000000000000000000000000000000015f63ce982aa581dad5c71fc79251b7f6336c4e78a4a0f4cb6f87167cabd31cbec987d7af4f11dc6d693a0b0774864130000000000000000000000000000000015c001372fe0530a3f50fb8b30e75ff4b264d673e0448211d082c7a9018f583b4d01790019874596c59c68768cfa3e69000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b0000000000000000000000000000000010f7bf9f6711ba723bb71a004a90109ee22be6643d56d410da18103ef44a1b3d50f10c4b94222c7f05fd3c28acbdc8ee00000000000000000000000000000000007af41f09e6d0adcb1935d6a93ea1f6156fa0157a63f265a3a7ceffe82f6635b8511e7e8f21e8f3be7a73513ff597b1,000000000000000000000000000000000f3dd56c416db1c06fd27e18fb852c9e1662fed42005e253230a7a8f7c3e0b8ce637666e1d20952c219cd2068d6865f1000000000000000000000000000000000aff045afcbefcdcb5255805a86e8af3de881e5482188c487d15ad1b799cf551c1d48c7665028b05ceb2e82e15ea4ae5000000000000000000000000000000000e0e6ed04926aed1f8c6a4e13227bf2a99d9d6d349a9c86214373be693db702a0011b4423defdb7d842bcb6f722c70b100000000000000000000000000000000148b1af285c65b12eef498f1c9e57a673e7a3803088c56e32aaae13dad3977dda8d3e27809094f8d8ed607239610a1a6,800, +00000000000000000000000000000000074d78cdd35ea17a3013e2301fe9f80f2d20d270a25fdead37eed7697a52d152612543781763e6035fa5452ab12cce25000000000000000000000000000000000e572236e1c203a1c0f99e6ec978458c1a143a6a650eee27cfbe406bb2858fe5f30222f468d119703c2f442bc644ff3000000000000000000000000000000000125384343fe132e16a9fc15efe1b3a9e47289e0afc4b44d492e33a6216edbc96d66c1ca66944a8296e7695f27f414c5b00000000000000000000000000000000084c2cbf0d7c932c3098ded7c70d4411eed882feb0f79e0f7f1c31f5fccb6d53fb57de179c3ba5754bc5e532c3784df10000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a0000000000000000000000000000000003a6cc74cc398f38d535b4341faa37c968daf2009c3f05ace1f938b33bbe4002d81d18d30c2c856b21afe7a22b83c37a000000000000000000000000000000000452d1b2da6392f9df1bfd35e4575c565333703b2f83f56e0a88a0c8195968c5321296b07f6750584e23597304a5472e,000000000000000000000000000000001220b3da7e7d03823458bcdcee82db56957e5aec335e9b543ebb0f3cf4fe3cf6ecacb6198c886b9abbdaa42f528b4963000000000000000000000000000000000138233b166547e9e9ee9d11048e2d2579b2b111af5cab372d36159c4c45e28d836d733a1265e8833da64f461c0a32cd00000000000000000000000000000000005f860a0c72034f1a928501d9f549e5c2a9dc72670272fbf35a0b301025c0fc751d55ef6fc2c5bf7ff42df7693f3dca0000000000000000000000000000000012c73105adf97bc0dfec1f56153c57c6fdb9d68341f4397b72f5b6c667873ff7ed5cc841451b391e33290cec256395c7,800, +0000000000000000000000000000000004d46066439c3ac559cce863c58316883651023990180470d2efd06e443a7caf3a514b54f15ce6e850d32779215bcf4a0000000000000000000000000000000019ce904b6c9c3de59f7d5017f60f1978d60c564f94a0f1964c24c876d1139a7ffbeb6d0d4884bbfaf5f2f189af6904a50000000000000000000000000000000015f1989719e69be95f25dda9358fb98aae2819e0deb7e2d291e2c01e85ba26a9da421896c6b6e2ed20f609b533154694000000000000000000000000000000000b287cfcf1dd7c6d735c1358dff15393ddd6c82e7a33c5d8005c4234cdf823c76a4725fd74cad74b3ec51df67f09af0f0000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf780000000000000000000000000000000007a9cf48dbe005c5c59b2c731cf4117e5fadc9cb2cd8f486f1ed58b2909092ee8f36d88b8f719db94715641b418ab4240000000000000000000000000000000004ba40d4766b91bf8da1cc2526f62791a1b5f6fc24ffc54b522dd30cde2d29a6a6f81e8429d518710843d43705f3b4e6,00000000000000000000000000000000014933a0923416428b5fe5be7120bf399ab62ca091b07d03da3fd2ff080b9c411c3cda3bfef40c8450ae31c412dc5feb000000000000000000000000000000000214229a73780d4f260364649e9eb2ed751ad3f687a832a3738ca2cc81a3acf12757651e88c4bcd79239bc0b0c40e5a6000000000000000000000000000000000548f20fa375e578084e085ee71df5f8ddaec1db03a1415938d9521b5d9c914b5295835fc07263cdbf49d7802551156a00000000000000000000000000000000063ecd9efe55229a76fc848728e940183c23bf47363cb34c5a49837e6df8a5f0dc29d7108cd10ea08e82ccf017d246d1,800, +00000000000000000000000000000000006b37e2226957d639fcb0bcd6c20b3c7b8372e7347a14b970e01c67c1859fa97c754ce588d0f835ecc053549d963ab4000000000000000000000000000000000c6a5fae8be3a32e3f70a4202a1ab6d97183964b9f7b9a084c49922cd9e0e952b0bb66c5580f0e0c417e079493bcdb4e0000000000000000000000000000000017b6132f11adc0d5d693ae7f3a0f89f5779708083eba23e03b0c9265e4e60624e1fb6940e8ee49d31618fa6389b1b50b0000000000000000000000000000000000a45c5f6df71359648aecb6434bad1619c39f10e279a02b3cc9725d0256bcd126843fc9ed29cbe02a32cbbe79774a330000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c7952820000000000000000000000000000000008ceb842a17953578013ceee519a28ef1b37f73e13564def5ffe08a64dc53aa680784e26138176c89269477ee003d16700000000000000000000000000000000159791b6f2c26ed611ca40bfbd2059c15cfec9d073a84254ad9b509ef786d62d17fdc67ab13092cf0b7b3482866f4c32,0000000000000000000000000000000008a71a08d2c4e2ba3d8774dcb42d3e96c7f72d36fb3b880a4049b078d8257a7a9a51b0b34c093568baf4aa6de70e709d000000000000000000000000000000000daf83b5ad4b91b557982fc4b9b7dbed2998aa39fc4658ba671f5f27b3888dfec7602949cf626c9e6ef21171acb185600000000000000000000000000000000013a7ffca291d9ba8790ca0462c54c147aa22e03a2413b756f27583155932aee65060924e46db321b3fd6f22ff7f54041000000000000000000000000000000000289d7de10285285279aee024e52476fa6fca85550f7af183a161e395d72e1339b629c64127f96bc85858d80e73dcbe1,800, +000000000000000000000000000000000ffed009c78ba9af8cd33af7b7697ae4dff863bb92365055baedd2299b7f5b5e8abb84ed434f7223c3e309ca53c08aca0000000000000000000000000000000003b2370c837dd6291818efe7c9af62dd51295c418739ecc509d42c92e2c97d12a9fa582946e176e8153fc9a273140b2f0000000000000000000000000000000001e63438e8b4a0462cfdff64a281ab4a7f48d51b51325817139f8ee683484f8695f1defc0c3efcca81d5fbff06cf9c54000000000000000000000000000000000192fc391cdc1ed6ddbd317f2f366f2ce25ba27b8c0f09c733e7bc0c0697544399a3a4f1186d139a8f6399ffa88e89a6000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf000000000000000000000000000000001272c63693873e1dabe2c2739310f627d3d9b5bcaa615402c3849ffd8dfe72b40fea4a068064655f2c8f46f074e6518d0000000000000000000000000000000000161a8e5e1de10938e5bce241ae73d76173022127822d744b23e656095c28f2f8d142ceb48b72a1dbc36b6143f8af95,000000000000000000000000000000000a4ed8d613cfe4f5dbda1d0c6812d0edee45ffc2667323c3828f8ce4ab55c119e92a82f2c3d06afe3adaa4aaccc18f8d000000000000000000000000000000000fe10c5e185f3f8ba81c93754132d76e05eb3543d8aaa8a2d0c98833ce5fa9e2b84420d6e3412e005cf89d11f5400a510000000000000000000000000000000004ac5f8cc614e3833b3b6dd9eee9ac29501002ba9054554314a4c516bfc8cec870995e811f7892811346574f3c58b2ec000000000000000000000000000000000a6bed54d8ed4ccb09211ae7773c604edc6ce51a05c9acc94e8167026906d387af681fb33a40e72e85cb076e072db7d9,800, +00000000000000000000000000000000002e105e0eaa418d58019a849b89accf665a94ffb0bdf308a11b99b521de7af8ddb150c0e3b2e9c54cf5456b6105bc81000000000000000000000000000000000691a3b3986fbe1c0ea22329364454f37f645d6abe9310e883b9191ce512347e074e18e28b88c2adcc76190a549b80b40000000000000000000000000000000003f3a37a763c8d0d99a3fe36923843a22cb0fa18ced48493b2510fc99afe5b7699bbaa6c2ecdad8aaf72969354f121a1000000000000000000000000000000000f4bbae00205f54eb10c83d928d908fbae342b76050e33c51b6e282e02b3c1f132a4728dee4ea95455c25fdfc112f254000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a220000000000000000000000000000000018a236ea02b1971d6e193a6eb92e1298956679d86864042fb6a0c36dd91c0e385944d779dedd0149fa8a1b3d6a07949d00000000000000000000000000000000048eac7d116b5a7906bce070e2b51ee7c4c493f1415abdb6fd2d35676036d3b741d14b7135419645a6906018e9d3f150,0000000000000000000000000000000004d145ad2575313a922667b897052063139eef8c61dd375eb055c4a5c52cfbed35391a85df915e1eea50d000b9b6bb5700000000000000000000000000000000071cc73c16a234e99faba9b04fafaca1a943f2bdbb68dcae0a1742acfca1f90c5f69464aba42be6c18be31f79ce30791000000000000000000000000000000000bf725a2f4d7d33c66fefeefce13fb5649a68a93fb7086c943a7bd5663b5788a5ceaad7fd2a219ade832dfb3c0022a5a000000000000000000000000000000000fef4a2610610afef43da2161b86b25a8f6e30ed90053d57f5ee0a10effcdd2af769d32ef6843804b2b6590f95eccb4c,800, +0000000000000000000000000000000009a3e98fe4a98582ce9f274965f376cb45e8583775dbadf626cb1327c1f8a25b293b97e7f8f31ff72ba7e8e769ff25ef0000000000000000000000000000000018e4785ccb76c4897087c8a4242ddc744c6a0a53a4a844254153c23d6f16d4ddb945252d13f93101613f4eb0b1e2b8320000000000000000000000000000000011b81d344eac04d3471b1edde5e51f31f97bea3396580839fa094db58cf6bee371bbdc045fb60c3ee5c6cd5d3f6d3c4700000000000000000000000000000000073476bc5b1d52ff4ca89c3afc099417f473543fab6e59cf9de8a19705dc4bf2a210b1e6de4dfbde035c312be0c70c5600000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c95384000000000000000000000000000000000003c96c6f20d7ac31ee7ca77d11e8d25ea78cdf13e5f4d317752320e059e19196f14c15b5a18ca712f3a7cc6f09be6d4000000000000000000000000000000000ebd71f61fcddf1652675f577bbaeec26b892dd954965b057ffb431d6e37cc5425a2a42a0059482c2bd75adb2a120b0b,00000000000000000000000000000000151ec7c35a67b878420e198ee7bf359d0668ab61ba1a0bc2e5e57b1b7b18838a015464f9910b659fb7d1e10af2801d86000000000000000000000000000000000511536f34067fe931c6e829e22443eb838f0c938eeef6f839eb322d72e2011dd1c33c504dd044e3cd721065d7075b520000000000000000000000000000000010c486f846242024f9bf40d805c8e33ecf1b44cfaa04455d5584db7ebc32c0d29e8742c61886d4ebae93f22c518ea87300000000000000000000000000000000072e184c836a853fd1153eabb1b645bd35ef72eefde4a52db169acdf2d8d68499398599cb4002994c6f4936de1da75ef,800, +000000000000000000000000000000000c414b95b298b9c673001173ba7e5ee3e03926f28068481cfa0b469ab556f8fceba9fd0a815180ae0b82c265fd4c6b7e00000000000000000000000000000000054a242c1cc1a9c710bc23305d09c2d613ee8eb3840b37943bfe83f9c1db456ab4436ad319fcdd8684db129d76c95320000000000000000000000000000000001683711c0c7f02e67374f190eed1ce6559479d6d199f43fb5b0ce7df7774a5cb21c86b3b3498855d9b69c5763acd8c4300000000000000000000000000000000062f87085dfec847af518bd71c078f994b090c3b27c6eaad79772ab58afa43993db52fb08649a32629d61c3db12c87310000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e4000000000000000000000000000000000174ffb89d7715866562d9882acb81ce40758644ca3e0decd546c8f5c349b24fce88214956e7540fac36bcfc105cf34a0000000000000000000000000000000003e06c5f607ccf1e2991828034fcdf91106295e7174b4dca21926169451ee58e737d535af45073e2378206e03c81c421,000000000000000000000000000000000642f215b772d17a3aa45ee3aee607321c02b4f7a7df3884259a25ce78c73e9536d46333fa388e506fdc79c708bfd9de00000000000000000000000000000000145864ce36521fdb641761be541a27bbd3f4797b923a870148bef1d5b4b0d463c0a7c8ef07954dad464510d836105e05000000000000000000000000000000000ca038e667fe68111b583dfaa95f88d3b9e46c0798abccd1476071435067e6c0e2fa81d25db6e1175e60efa1705538b9000000000000000000000000000000000cf1cb1b155e4ea47077c42a1a99c3f11f8b27516a808b5e73498ee12363652bb46eab7e55de93513cc2d6272f26a537,800, +00000000000000000000000000000000083eea9b5b2d5ac5f7ef51ca889a4317322d098a408a741827fb3419eb12a51c07c788c2798cb37635e224e99bbc894c000000000000000000000000000000001312ec00f4b3a4305700b44b3f215779a9a8bfcf5b5d3a7f237a33c5484099ec9bc5c8537fae768e2c0ec62168f383d6000000000000000000000000000000000cf1d5d05d11e1d07074dd34211d0f00eae1df4dc550c55bd2fdafaffa1ad36abd5da30c5d3a5aa2845b1d95a5cb571e0000000000000000000000000000000015223baa9f2ea4b04fdb05b05bf3a94dcabc5e64189aeee39c380de9a34fe6b4253f5795f70bbe51b80e1aec1eab71960000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000b15d92f2301075ab0e3215aa72cf9b130bc8e1bcd9fa36375c4b9d7da430ae3e2b24f417336d8729f44542ee7f561d300000000000000000000000000000000197d90090501e8cdea28eb7963231f1a7b5f716cc3a086acb6e7626600d6544132cac943e8d5cefb5daf0a2f8d400629,00000000000000000000000000000000128c909854a20ccf9e8e396b617b36f233909a5f6c3524c93cc659d22afe0e7058a438a5ee4345bed914288c64802e29000000000000000000000000000000000239fc43718cd27855ee5450cc9be5be5d9bca8188c22601242a1bb4269ca0fe62ad5e12b2c65558cd3dfc89ea31205f000000000000000000000000000000000a0aec9527febbd35bf041a901b0b35e5e0d48a2d6d733bb557d0767798369a7ccf2f1c278710eb764f721821f9aeea300000000000000000000000000000000194931bad52daa16a648ccf1ba9a4768e5e2900fee4f9bf46ae07d1aa605aabbfe96684f5d2233c0b254cb4ad5517775,800, +0000000000000000000000000000000011a960cf1978aa2ce1731b857fd91d2f59d4b8d7c6871ef6f4f85aeff549a2f397949d11a4793926fe7be37f3a83d11c0000000000000000000000000000000001954f056834d6e3b16043ef1acd0a47a353300257446e9a1db7e58bd0d7c4bc9ceb3db51ae01cfed9de99621e96934c0000000000000000000000000000000002e2fe460e71b65595ed93a0010e5ccd1a2c16fc4e0d345e7226c947f29720d2f3f54282f79cec086d3fb1999b9629b300000000000000000000000000000000060dd8a7ccb613f1521168a8a322aef9f84d9708a893f704f4fc9a19e2493f25620a47e0fff1bc1e212e65e92873b4f20000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf2300000000000000000000000000000000005c35f3372f1ec9845bd04ea722fbed2be1388abf59e622dd3dafb4b3af49bc5fba9e20235e7e58973fedf4b8b720691000000000000000000000000000000001111d18d621070509805d306a31c109701288fd55d4c0644349deb080c6591b6e852b4f7e009b80019513de7f2fce17d,00000000000000000000000000000000189ee5ac642bfd0b612058f96e63acb1feb6b4dce125bf0ea1e56e846775af1a8b0864d4ece6bd96c3b5dbb04e2f6c33000000000000000000000000000000000073d57ab79314e38267ee8015de3156f2c1d5dfcb6655a150b9ab4a3bc9eeddf7b37b3681c49611e02abb012770b3f5000000000000000000000000000000000cfa1363275c7bc5bbb9bb7c03e7bb7f6d6d365e39fccbe62cfe0bb93280527c9ea99079fdf9871abed035b62079856b0000000000000000000000000000000010048e4e96f26710d254110650de36460be2a8302badfc2da8b26147da498e4620e79b4329033fc3f3a9c99b1e12aad4,800, +000000000000000000000000000000001472caba61c2f1fe4b1d0912b114c25de103ef4351668f22f3a158d7a347539a7b6656044bd490f036ca3e29dbdded370000000000000000000000000000000015f8cdf7786410b409f218164063c99e77d8f72f03882a6c9430ec725ae574547d3ea3cf30c3ad2c9c3febe6c30b1272000000000000000000000000000000000ccbbed85c2809433fbcf22d6490457dab800b21cb4de414c7dd1804a0bdeb7142f8ffbb2de921c2c9eabee6a6351026000000000000000000000000000000000a404f42c48e3ca408d3f92079b99805004da928f128206d8904ecd7fcb14121c7d9a9e7fb69accaff921315ef3d5372000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000000e7e532ee4b892af39f8a3db7a05cc77a6eb0b3d977c17076bac4a52d5ba003a0ac1f902a4257791a45370eb88426a70000000000000000000000000000000016a556050e4905fa74b5061e3874f05cc7a6c5b049bd3bb7c34adef5a77c393239a600542a4401c3e61978ee6515a30e,0000000000000000000000000000000005889133be5f447013d779f2b9b0033667c5af87e1c8a16d239ca3ed238920004d87e00119ded46658026c26988ee63a000000000000000000000000000000000d4ed8fd88f7e1394f2b5a65588bf1c461a292acafdb77703c2790ef249f2de695524293c826252c94967a3ea4a3a28500000000000000000000000000000000001b5ff0aa278c7e87a89d4748aef13b516c49b7dc9f7cd5e0448dc6fd860a7a8af7183a198eebe6c7dd549fef806db00000000000000000000000000000000003c9e40ed44427cc3cf886ca2db341ae31f015c542b857f6702d25cb5036e3e6abeb8d4bf9a0e203281ab85ad89ce0da,800, +000000000000000000000000000000000b52f05365c4df20a7290aee71a7e030615d1a2a971167884d835c24e756a0faf6ed0552341c561446c7fd3d5e887d830000000000000000000000000000000018718ef172c045cbf0bb132059754b62414097eef640a781db6ad521af5a24d78c622d9402033fa939f70aad0510a1ac0000000000000000000000000000000017e969e44b4910304b350b5d442bb6a0b71e1f226cb4603cc8b4dd48614622f3f4e1ddecb1894046649d40f261d94e030000000000000000000000000000000004dacaeb9e05b9d60ce56c17312a092cb988bff426b8a718cdff860186935507a06eddbc4a1a29e4ef88db83fc4b6e77000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d2900000000000000000000000000000000179c34ba9578d5ff90272a2c7f756794670a047f79a53215da69937152bad0f86576945b12176d3e13cac38d26335c51000000000000000000000000000000000dcc715907e4e17824e24c1f513c09597965941e3ed0aaad6d0c59029b54fb039d716a998c9c418110bd49c5e365507f,00000000000000000000000000000000093b692a68536b16913ef38c3bba7b19ba94a6af1c36a2e54b8ac1754a29c29882107cde142deb95365af00f2d1f537e000000000000000000000000000000001035e70852f38f860a1a04f33081e84f3ed17d83ad894a6800e7b8b9259067b755fe7e08d4c1b297c6d53064ab8209590000000000000000000000000000000013d38db0d8575131865bd7acb6cbe994812bdd8bc7f51b810bc382a6eb379d442c47be20a2c8e751fb08ccce8fea68690000000000000000000000000000000000bd114951193e3bd58cd0025e0b0c807ea073b1c1f7bb04a2a00771b6442e70ea20e1124572ef5b74d2bd87c93c82f5,800, +0000000000000000000000000000000019829d5799eed5a081042e4646d46fb6bead6d3b9893a4240867b25ed6af6a3e154514f244466d80e3b9311e060bbd7100000000000000000000000000000000156157a654db2813cb9c1b4da0a3ee192fad076bb2767020fc5fc00e967c1a35a367ffa375703e1181b3705ace9dd28000000000000000000000000000000000093385a6a9dd0ab996df54b23f47f4a49b3f379e11bc8331016ecee6161fcddd22f6d49fbb21f098873f1e17424dedca000000000000000000000000000000000d5b5b0f2ce81e755b4030b33fe3a8bdee38c2c60ed3b4a88bffb9207cb762c0a5c699ff424c000ab080d763abc5438d0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c853000000000000000000000000000000001643567a0f22b90fefee96c8e2f5851623384c2c68bce9589cdf64c933d494a8d805edce2fd18a6db80f4819391dd1f9000000000000000000000000000000000e4e40ab1969bf9f00ee3b984947ae95bf7b9579bdaeeee926638f9566f8ab26debb4c8d4009535cb6422b2c2ab7282d,0000000000000000000000000000000006db1eef1f614613ada8383e63d631484015224902ca38f58ee384a70af0a0575b0e7063675d2dd997ed8a140e2598470000000000000000000000000000000010d7b833f050f18ff4e3a8d0df227a9494dad9cbde88f68802b23e87387622a5333dfb7bcdcbfe2d4d137cb532ef4a150000000000000000000000000000000000c9c40ba972ee0be2823625a23345fe352d701cc8bf9a153d5a55c205ef1b7e5544d0a7f65aaa24bde8d77cb4c31ab3000000000000000000000000000000000402f170c4c3ebb9b1e7d64765b66ba9b8d45b2ea9fe9517626f38e00a11d180e1f8872bf80f6322bdf3a8dd90732ae9,800, +0000000000000000000000000000000003af8c25bdbd0dc1cc344d55366f15555709a74e1f0d8d7050cb6b487759db6200401b7868fca3c2ad26e6362a30e6250000000000000000000000000000000013f8b6ffe30f9a133fafe64461d305cc6b2cf5aededf68ba396d4e00df651531c750a3d94dd77bc5c6713b939b18fa19000000000000000000000000000000000dde97855d7728f409d873b83b6879b45ace5b73f317687fbf478e594a959ce21d4d751db646ceb20432e8311e67404f000000000000000000000000000000000fea997323cf29710cf0e3d44ce682e039d6cbda155e43c94dc8cefc5e94000de4b9525123b9615b5f1019a46ef37ad300000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000a27fe0a29c761ce29a731ead969b1db3ae9ef4c05493cc370a128d97ef956c55d9a500991b3e7bf9600383633778ebb000000000000000000000000000000000dbb997ef4970a472bfcf03e959acb90bb13671a3d27c91698975a407856505e93837f46afc965363f21c35a3d194ec0,0000000000000000000000000000000002dccab673b26be02d2c645c82a2c73290f0eb053e07d4f81d4d315d9483e57c58b65cfabeb0172934b9fbb52ad519210000000000000000000000000000000011c34a27c850fe319fe89399e7680064caf6dcbad171c3a23c45b9883ee06ccc3482b2b81e5777759ff81b16bcc1b0f500000000000000000000000000000000119adca3e2b052c045124f021fceb03c979e6eec0a270c7f4ab13674e461839a4d3a10fd48da4e9ae750a238a2649ace000000000000000000000000000000000fb5210677e1096cb5448bcda16646d6dd29ff8a0765c5aa51d83fc952a5ab8063aa96e97f33abf701cb8688c989c363,800, +000000000000000000000000000000000cdf60e3bb018407eab162822468255bcffd54cad9127054bd1c30705a4ebf1afc7f539cca6ba4cd070b44410ec751150000000000000000000000000000000009a2e3e5993b6a7007dedbbd21737a8c0aef3ecd4607953c4a24bb3fed97ccae01ae1cec024443f300b570a66e9ac3bf0000000000000000000000000000000008a21fed19e9ec2a741ade7767b0c9f39b79c3fbe34aadc9eb3043583768d893bf927d26231759290c7dd9c4f158d5a10000000000000000000000000000000018eef4ff88d63149d2632c9db586a4af0606644b16c82fbb0a3b869f1ff924c59acc8efbfde7bc604497ff68939cdd0800000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000eb3f5d24d1a4f520032534f6f81a6806c54df33cbd10c30203423aa4f33620b474cda321e924802b636daaeb34400470000000000000000000000000000000016f004f1dfbf140de042e4f57303928a576d9064f2da5b3ad392331f5c43327c7d2a6fd57456d5ef58b54a3e5ec27508,00000000000000000000000000000000056489b2248ba672501069ab6742016cc8ab2af50a119239bbd3c0a4b9b56e014402b78bf62b2b37bf4645c3bd3d95b800000000000000000000000000000000046956432001feaba6d230da27a72e8db5c8eb3d52f00616f87b55c951217095f337a302562cda789e5714c4391ac27000000000000000000000000000000000172c2a583c9563fe02d43b2b767c4ee4e3990fbabe4ac536d64cfcf059f0e38672876289bc86915b6344eb398fbc4ddb0000000000000000000000000000000008915b0edade80caee9b386e4a560ff4b9dce33946ee992649466315786e139e3ce241ebbdfa7ee28fad7e6214e65666,800, +000000000000000000000000000000000f5d47911596c46c0c08cac5f5e7f6d0609874da4ac1bd4e0e59c393273a5fe31a756c7cfff2a01d19e79d209d7c6d3e000000000000000000000000000000001010f864eb6624132d4436d18db7f5b34727060dc426c109886be88031e3c155490cb3fb09e1fbccb7912875477c6d840000000000000000000000000000000005cfbf1c2ae1b80a8c7cfb2cefedd907b0552794f4fda101ca1a723b18de8cbce30eb54287e1847cee3f416cd8b45f2c00000000000000000000000000000000084fa63781f7eba9c7e911ae5866d485bc7e90603541c55d1ffad8b3cf7547fd57fb24b14002560e58410b828513e1090000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d0000000000000000000000000000000002a36d5efd3381c35ff4f361cd813a96c3e5185141c5985073b45d1319c5f392442b7aa6a253b7eb22d1b5052812be00000000000000000000000000000000000f745dd17966b6befa7f740ea360241162505d6269226ffda90546863d0fff124d8fea13c763cfb69c2f8f12b81d431f,0000000000000000000000000000000005b81843ef3f98c6a6686f1fbd26f77248497ec3d41aff4be5968d13ba86f86309b0ec4792d74220ad8ef147bdee9aa90000000000000000000000000000000019825376b243f3e374b6e9e7e51e0c969bc72b39cde1dfa09187a3c7c5c2c752ee16fa5a4c8fcf94464287419b3a3845000000000000000000000000000000001308cc0c77219034a9fc3018f1d668a41e6959476aaaa5461ec73d7155c6a68fb08e1fdf8140e18270cd338c266a83f4000000000000000000000000000000000fee2a6e245e3bb570c3b605f7ad805bcd68e9a1f2bb2282f92e2a2e83b69e275b21b923f33a65defa8c4224934aa588,800, +00000000000000000000000000000000124870cfa469136c638e0cbf15802f2699aacb66d7e4c2965c6759dbca4b7e47941ad9ec37a84db1afeeeaa65a7418e4000000000000000000000000000000000d4503049a6a53536bdf41dd832a6ecf3f10554887da7e389cf940394e1d88db94369b7947436546eb6c6e82c48dfb9900000000000000000000000000000000053f9a6e1f05b67cf553073358009a172e2ab8b43572a974da1f3de85a29103b13d7e67b2a359297172d27dba5c61439000000000000000000000000000000000abc29f50ddc1c113c73700b9b9796890cbf48818ba981fdab2db27ef1c58f4c2e4595b99eae397d40990ce2f6c9317c000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000000397dbdcc3edf976e8c507f5e70299da8c7765772115bf8edf7dc9024050c2ed98746c2bf7dd4400ab1fb89af991e43f00000000000000000000000000000000139bd5f917f59e2cb6c41c59024c12cdaf95285f3947b80267f36e3bd2701f9548b561c49003fc5ddeee3fe7bc8f5b5b,00000000000000000000000000000000166414455bcd0e8e40397f4cafa9628d1a092beaef62d35211cf49779ba98df5c1d692f650c1fcf0893a9d4ae1926b1c0000000000000000000000000000000003dd898d0725ee899b913042da8566a1379aeb4dd5f0222ac784205b4e74f32858ae490f981801b166a01fb96266dbeb0000000000000000000000000000000019f0fe4f12b113b337361b977aff7cc7dce50bf37c2609b9f311ce340d30225de178999b73345ef49625518e52aa4d7800000000000000000000000000000000090bc07c6270901d706a8d28d512b07fd0e03013d94d4e43eafbee59677998bfb7c2a58aa93571fb49c35518b6331bca,800, +0000000000000000000000000000000007d2aae9794b7a7de97f7146c0ee8415e09e56fd42535bce6773cadd6f7ac09c4eafe2e926cb7014377e54c703eaa9dd00000000000000000000000000000000172a4a33ccf99eb0473b2c44d30bd53159afae0c7706ad128bccf6258974d5e5761f9be43e618cdbd96027aede7fd5860000000000000000000000000000000012601bce2171c6e4c2968a3efdf1491285f9e4ab37cf973ab5c8e224ad5b40e1b6459ac89090c73deb8fc79fec7fb8e200000000000000000000000000000000112a6443116e6f98ab348e57daa3971b5fa506e40515e1611fbed3e7dd64c5c1e991e0d2539a70eb93e3da0f573d6b22000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b00000000000000000000000000000000197ed996d62fc0628d8ea4adee487df31c794e05e7c327aaa140c6be0109031bb763c5f84bc35a0597dc61e93d23a9bf000000000000000000000000000000001056c1f3c6ae36be26430d142d34b0e807685c79935496414e004cb85900d85a18454bde9c0f2650f19db35eb3dd468d,0000000000000000000000000000000019ce0f31d9ebaed0ea1d12d4e232bd3ad48373fa465af44f1c8015102b624d2f8330d1323fb2fec524e83de0f6699ad7000000000000000000000000000000000915d65fef96562ea3b76f3152aa1b8e445ef50fa66dc487ad0c04cfd7a33b5ee48aed919eb81fe83b1f4dca59b4990d000000000000000000000000000000000e4731ec887261f29475523f7dfc5d21cbbc1b883439701a33cd58bd24f5d447267707c2b60ea38b04510be7dd10d72b00000000000000000000000000000000146a679d7a81aac5952645b2635f24b96393529ab9571ecc1078c4c20a77e59acc4591b9f45df00428250c5e31b1a8e9,800, +000000000000000000000000000000000030372914b83644fa4db1958831e9335c72ab7a811fb337696221a3290e4c54bc10c2225f8fdc3a9f62632ba2f1594500000000000000000000000000000000114205926609470b6022d24046a1997c048e6d2cf6043397892c967692161c0ceedf409bf5e1199a64eabb1ff8de23640000000000000000000000000000000017cdecbe73779855b7b94920d4bc8ad057ce51c5481a5579650df8a5bbc421030d2ac44568217c4dbb13d7c639760236000000000000000000000000000000000f194fa814bfa7396697bd812d9449d06fc61b580d7a86429fdd1ad376e21ceca139356d7d13964c3c684563675711c60000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f0000000000000000000000000000000008789ffe0a8676c6a56742a30a48e5e65b88aafd71859d704fb9f69e5e274ccb6942bc51ad36c5671406052aacf19df9000000000000000000000000000000000c7607f4fc69a25aff00a54369f213c4587404644358da4abf26d151dfa4905ba9731dcfb12e2a3f2c551cacd0f4e47f,0000000000000000000000000000000016790155e57f7103d9e325a1f3a64c0b8a1875365eaa0c01c515538b64bd8265e8392e755a2f7314c37ec09026f13d290000000000000000000000000000000007bfe690fc4ab166b29de35e341e8faec4bc3c2d4ea2d42c9f4166c0d748b92b743ba646c86ff9e570612c75bcd522a9000000000000000000000000000000000c11b9ccf990162b772099fdb4266716b11dcf46c5abd12d03caf222c571e2a9e28cfb47e11db05162967ad4b430930e0000000000000000000000000000000000bafe02785607bae144d9ef5391fef02b9f2fd5dcd436e2506bd40866d8726eb83c223e09c00f3b8895181c6710912f,800, +0000000000000000000000000000000015d4ae1521acf897344c3a76261754ff99742585af4a0ee86dc473a88fd408091404df1da9d8bb291db68bc9c07d6b2b0000000000000000000000000000000008ce160213875c661163990f3f7ac219ea295db5e828354864517ea8689ec15d35c6df78ff14cb276e0c97ffd7fbc09a00000000000000000000000000000000038a3ee211e777d6d6b7ca6c7a0d2130f1a071c030eebec412c3a0f14c3584e7c5cf15de254a8f141a8210a90249ee5a0000000000000000000000000000000019f7ec6b2fcd8b3190ab37a6e843340d3f3fc092f5772a042edbd5bdc967b96e8a1dc9e435b8463496aa1301f87d0e5a00000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000b060965391bfd4afe3271c6ddb91eecb8c7a60451c469d63bb178b1361617000f589c33c35b5deda2f072c6edf2eb370000000000000000000000000000000011c8c988379cd2b82cb8ebd81c3e14d2c01c09dde5690b97623c0876c7554f52ccbaa33d17fb0f0cf331cc85749340cd,000000000000000000000000000000000965966a8a463de1f3bc49d9873668e87f54d95612231458dc8b885681cee8e2835482b4bfc476153c41b206f427cbb400000000000000000000000000000000183639fa14dd74c33e8696496a3ee269160f88e5daca4fdc468724d9b6af8e7d0706867cdb1bcc608029b89b94c531a800000000000000000000000000000000026257fc32efaf241c7712b0a7e9f881763d8fa0711a452d9b71ea25e973bffd88433cba768f1e5b3ea15bdae9cb9428000000000000000000000000000000001527afbb6594dc0f472673606fb8f4797fc855bde4d308ac1acdaa26f19a70f80f2d2bbf3498b53b887b79fd6273231d,800, +000000000000000000000000000000000fa7f8fbfa1d4ef5f001a451c55ed261dee344025e599884b29d086e15665867932120d33bee579d5eb1b7e6c7299f310000000000000000000000000000000001f06356f793350b17b47a623059a068800ca1eab6089c7c146182990063e8e23bbf40d95a42bf6e976224b680b75bfd0000000000000000000000000000000008807f6606d2302450bfd8b38fd4147b851ff59762c1ff48f9442c4d7b77a32c5e023821eb47fca839a27fde60e5f61d000000000000000000000000000000000c5b92f1ca9c20d4b6b11d794a5853824cff20d9267a20a7aaa4bed8bfdc728c4d4d50feb8f0b569757b97f473138db100000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000f05a111b41a54e0ca78c3a1fff3b80bee7c1505a06b9a4faf36a73b87121d2952cc4f4c4e0dcb6633cad12b0caffc620000000000000000000000000000000018daa0f9a2bb347517eee63463b9d6a5e850446e8a94d0986f2921bf81a9f7541e8fee9d7bbb6d9181021af945fce3e3,000000000000000000000000000000000018123e82a5572e6b6c62d5db07448838df9db7f7d15dac1adba1fd924892c8bb3c417354e838f706564a9ac282c2ac0000000000000000000000000000000016613fc38997d39b2761aed3485de4d7c273e8392e434185605e968ed942b9d4712cd0d538ed5ed1317870d0cafcae27000000000000000000000000000000000354365566b6e43f8b7f4b94a6343146f35ba3abf61a204e9c976b1ad1a90d4d493494c957def69ff270371c1c8d953100000000000000000000000000000000066adbadf1b69dd16cf19349c82e362be4a3768551599b81a4853ca524a24326e6c9dcc38b5a60ed6fdeb3cc4e7973bc,800, +0000000000000000000000000000000001191410ec6c5ff628bd25d35965f5e9fa7f3c3d8c0a9a1ee7ae37437a97c25e221110d892e2c7a0e9c8e386774eadb80000000000000000000000000000000003be30c25a18cdab139277232d8888f6d13112c9556895af8030f1893114d5845d895df9afe3c6f9ff7ffb1919adea9200000000000000000000000000000000197f6b4e38be0358a3f1722664c61e62587ecf5467f8aadc3a236b47682a75cb76bafb18a5c556b321d5da49cd4bfd4e0000000000000000000000000000000002e4ebf7f22d929b7421a600e67fa2e64a59edd87a2e2eb9dce1f06d3c793f1a812bcdd510e654d44fb4c1de8c64ba9f000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a9035050000000000000000000000000000000017d9e9e2008501981068cb0403e73c270d99defd468cc9dc2d5bbc57750a4a58236f8f7a8df4f8b607095b6a80e7de49000000000000000000000000000000000ebddf4fc74f25be3c358b72a20d1c093f980adfc943b898266592f691e11413c60151a0085d6c9aec8c2d329abbac0d,0000000000000000000000000000000018ba8af47c5cfa552374cb1b25ada1ac785381f2da0501f86c9e7b11cd4417e64095a5c4bdc2480ee10d215ae2296063000000000000000000000000000000000a2e09eff98280f6a9863d8b8faf8871b44650496eac1aaf90fc2b256f88e937101407d722c95fa76846776d4e6bf0dd0000000000000000000000000000000003824f5bf25fa4aec5a9e044703e5564122bec11da155c01ba8ab8344265516c1063983235863d826f68bac455327c65000000000000000000000000000000000ea72f8c6768736800b141b477610e37477d926acaffaa1951a5bfebb042c94c065e984a8812430153d529dbf07ce2bc,800, +0000000000000000000000000000000011c6f1dbccde640f63ad7d40089779d01075e26269421b4ce12fa5341f58ee9110f17d08dc1052426f2d00da2dd70b4f000000000000000000000000000000000740b147bcdf06705971c113a5cc12fb37345dd59f2cbb5ff500ce2b347fc5a8199cb3007a871670d5093f28979cfade00000000000000000000000000000000046563ea98b5e85b3c42222d5e0d8481e6aefaf077a1b99f2b4eefb397ec846aa3659aacda569054c9c8b9b69750272b000000000000000000000000000000000812d887943506d68e3525ced9b979354539b7b14003a3169e0084c26326b92be67346920c9a99ef0f9638e8991296fe00000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000001fdc4256cc997934a65c68ab9767b09c7aad14b5765dbeedb72ab2429231cb333ab9f9143414359376d76857e8972d9000000000000000000000000000000001362f417875259b47cfd9e4c5feda52b949dcbf5b8178318428fd3e70c384020e58f515b9a24af5597cfa037d42491c6,0000000000000000000000000000000009f1339cff0b58b00a871add058929ffebdc58cd1bd8a9c2c965c63e1843945b28138008cca8bf7b7cc9afb69a11767100000000000000000000000000000000011f65b337710a4043e1fa58bb41d80d505e2aee434b6978129c80fa1b124db89e61617e89bc0e596507566f4a484e9f0000000000000000000000000000000017560f768496ed583b3522c4a013f8b96073197e5b53e9041db6dc935a266111e21d8c54fa33b7bda944a573f6e1f07d000000000000000000000000000000000168a0742af91f42058e6501e122b6fc50dc966c2f5981372704694544aaa68fba2b6483752fa2464526d5072f84d8dd,800, +0000000000000000000000000000000004c8078fe8567013e8d05a546934026cdeee7d485e30d739407db16fefaef53ed7bff0f9adaaf064aff014ac919d91c600000000000000000000000000000000107cc17f485af7f22e07cf14c5cad6368323f720511fc9dda677b360567f769e47a77f61274927ef9b7be48a77357ec40000000000000000000000000000000001487f0880a6cbdac33ca35b9b65e4ead9d8c2e9180c993bdb2052060325aff8c62668c643f0cd9b4bb1f06a3dc74285000000000000000000000000000000000d4b2d062e31fabe8d2a329dbd6417673a519f455739d140246f2b3e43e20f390088c08e545bf0419d796ac71aebb519000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000010fb029e35b3f6e156b8751415f180ee3960cd3bb6ba9b8e456715ec70b1ba1410b8bfb77998f744d3f462533b59e26c000000000000000000000000000000001472654d9aa210a41d74e3661e05a9eb6b292719b46aa65f94b6abd514bf05f679dae89d21008245d79a381b0d7f51be,0000000000000000000000000000000005daf8338637bddeba63c788d78faa622e014efb84d3ac1d655d15af06317fe31d1782b2990354bd507632844cc87f2700000000000000000000000000000000185550250e2d9eec798e8b8c483dc37e2a917b304a6036e8ee518a0738d6bf946d99f6b7ee352b1a259aa894d53a8e1300000000000000000000000000000000105a4865d66ed4bc4f51dc52ffcf284615593d573b6beac490c3ee8e08ab83a529c8dd062d762d1d70b9b3290b6e8bd50000000000000000000000000000000014f598e5d0e40090f29aec1ecaccbebbf2a2d6889bbb9439798924db41b70c0cacdcf1e8ff6906f61943e9a8a1ae4fb5,800, +000000000000000000000000000000000811e9b0acfc10830c074c5a4d9f4d9382461eb523a61dda0b77f1c43b285fc5c1ef3a1fafd923addc9a6e904505a255000000000000000000000000000000001113102d015dbb509f0b8d0d0ebb4d3711c4f0e1e3d55fb0af247dd24be4fec9d6fe3ad73fbdcfe206891bcebefee4dd000000000000000000000000000000000085aae9e58fb97b96ca3c089acab7bdbd0c3adae141bf61075f5c13145b0d07113f1075dfb959bc7c2d3d3b3a06ab2a000000000000000000000000000000000bb5eac8125807c10270d94e5bcf278241d6fa82f68e41b5529b28aebc88870af55881db526f7bd221a8c4c0b29a1b7d00000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d7000000000000000000000000000000000520cfc8c536a1d4e685c4eacbc2000d70abd72e1bf8ce3839d79f5cfa069ed31aafb15542f23b8d1af678bab05a2d410000000000000000000000000000000017cfffda12d21c98b79ac31c5bb696783afb7d69c2bedf0fb070cf7714959db14957a4763564b65b7ed214d7b48d399c,0000000000000000000000000000000006b63929ce97554659ae731d60d11abe858383e39a67007877f68233cba8179777c0dfe511fc730448da3f1c4347f85c0000000000000000000000000000000016d4df414c287b0871c69f9745a9ae68ea3a1ff41ecd17d87623338bb8750bf12be52caa81537bacee06cebb86f894890000000000000000000000000000000007ad72c98e2428b90bead3616f1b31b26e978cd3f9b6b759ad53056098c18932c48ba78d3da112d7a738d7a9ba21d84e0000000000000000000000000000000010dfcfc53d0458296686fd7e0555593e0378d2cb176d456abebfd8322012bc9b408bb180d4237679985457e689131705,800, +000000000000000000000000000000001335276775545fbb4c701beb57cb34312108c9f1d46b4aa4b09a16faf0e648b4e80848bf5e75ed8730715f0107afc9820000000000000000000000000000000006ffff8736bab41b4ee5681b741a81fc870e648001027161144254d04c678e4f954e9f191bd8b26201aec681cbf0654b00000000000000000000000000000000026ede90d14fa0885baad21f9631bae058573251cbef5757bb8cfad061f3bdc78834fa5862dea19a2236c014b0f1652e0000000000000000000000000000000009844d0cf7f6f3401145d8d720defa577ca46b49e04e39c4c139ec6811a574e7dd5ce3acd00d1ce9496f10dd15c6d94600000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000007f786ea1cc7cd69ae1061d6b914278dfc7ebe8a714aa8cd04323860314c3b4b36054169dd5c6c60e67bfa3902d216f50000000000000000000000000000000019675b09a4de34af3c6e79452b57b31b6d499200e996008a9e7d1c910ca0ad2a352dc39cb3fd7333182476095b7aeec3,0000000000000000000000000000000009b166f124b5b85875834b5b0c088ab79a2dcf262240b284f57722e78b6eb56a192cd32544c1bb93ef492fe6d7a6216b00000000000000000000000000000000189b9792982b51b13cc3fc1691e0569b6c8d998168d3a3376e63ca60de4b30a84ce8d04fb265bdcf73f158d8e316bdda0000000000000000000000000000000005b99948b635750040b5b59568f0e8bacbfd512db2ae52c5032cd23eac18ad58d83b8f78cd26ae979ce2abeae8e1f3c3000000000000000000000000000000000d0b6561a49c358101b30f714563bfefc72e0febea857b1ce78cfeb9508b0108c2089c9b35cd694bc8c0ea8afc8d047e,800, +0000000000000000000000000000000010192b925fca096682acf138833b12d96bf97c9a2e69e4266eaaae1785b9008f36082e23e2d42341427edce24449935f000000000000000000000000000000000d5b24a94adadbf542aa663114096bc670e1b6c99f3b661f55de121922452534faed7f68d6b431fcf6f3e379d7acf6b6000000000000000000000000000000000acdbcae49206b749d8c0d21017a33e689ebe26804d1fe7c863a2ea4210c3559805dcf73685702bc56e644b4e02614a9000000000000000000000000000000000092309d684fcdf44bfa321d473060dc2d8a8c66c51419894a3fbadbf1b56179c31dff25403b970d543f1dd0e19e56cf0000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000fbe421858e4109c51de57b77da4f9c4c1f950099532d9e30e2f7a8b8b4fb9f708cde1a497050d0944e089978b15321e0000000000000000000000000000000019f48a0bf0f27df65ba766a65e831a0801a4ebcd1995a6002a803f88aead1503b7c39fde8ef5c4672020307241958a88,000000000000000000000000000000000bbb59d3e6b0b4d86ffc89bbfcf543a5b8ff922f1999a1e06c501a734b19dabd54632132c865c53e5287f69f06942a58000000000000000000000000000000000a3bb94431530879a7fb46b317d4f3d65b5a790739b396c78521a20e1cfad9c44248c9576be11c70970a49a1914ceffd00000000000000000000000000000000198df068ac5d3cfb9bd6896ab64495f4b9933a72872679ac3a46764478f043e9fddf17a7ef85fb72a8dc1a722804198400000000000000000000000000000000155c1a9db0c90634a6d214e996b13252bd4db3a4ab84ca7456ac3e7899e6fa096904a90f1150026307a1cac8de00c6df,800, +0000000000000000000000000000000014441b14765eee30e8131a7ef62c3b59370f2f6f0dda20fb2a3654fa09492bf695de1d1a8f250bfde3c7d2ed805ffaeb0000000000000000000000000000000019d813f8be2519e89d42a9fd3fef09d44a996d6a4713a9c224bee10f0ebb196370d6231fad810edf9cb4c875f08357890000000000000000000000000000000001a5abea13e909bbefdb51ddc699614366f271b2f6490ac8efcca7759833f3feae11057ab1b9ea32311e7b6ea6de110c0000000000000000000000000000000003ac2bf3c5486ca176e34ec5212165cbe04fc9e8c375e3e999a31fe014eb824ea3f2d06b9cf8b86ce3a76960cf2eb4d70000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff148,0000000000000000000000000000000010684ea0303f0e76b60eb96c470e1f0466f1f2b073bbedc1a0c0df1d2f6c66d77cb90ef9bfa4fef6a6a9eff8f5c66f9b0000000000000000000000000000000010e7ced79bbf01ae9f65d26894c73a905514296f19561ab4d00c0cde31737d01e7b4e8b8e6050054a7a17e8acb74d49d00000000000000000000000000000000174f771a98e262825ff2db7571f5f5475007d2f73a2c265f24e2929671bd173596b8b163abd46b868a644dd464dcc7cc0000000000000000000000000000000001cbffc9bb3195672ea2d998b169f853d3d4b4e147379329b1bbe69ce76d08ad78f87fdd876af227a050c31884fda084,800, +000000000000000000000000000000000598e111dcfeaaae66d1522be2a21131350577253a3f33bdd74a04b0bfba2940e73b62fefa8f0c34c4aa91b633f6bdfd0000000000000000000000000000000017fefff7d94afbeceb33714e9b5480c3a2f3eabf9d7f6e8507ae54cb65f69b21cd7d04d23f24e3a272c589f572b91864000000000000000000000000000000001652e3f5a99ba8dfbcd1f90de955ef527947642054be603c1b84b24bebb579b78e2a0be426ec21d32783a0e55f0178dc000000000000000000000000000000000a6c9ec91e8bc86ab198416cbc76239f0ac0b903f40310ee1f2066b01b08191538ca913c2736f53f23ef37fea13d527500000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f,000000000000000000000000000000000fa306f630d06c801e0203525c75fd6065bd12bcb3c4d45c7e02b597f85a53fae1e65a969feedca75068433547e4632d0000000000000000000000000000000004b1bdbc29f19f6484ea4648c70eaa47cf5bb07bbc255bb72dcf68a7b661de433dafb682d51321369cd3372288b2b9c400000000000000000000000000000000136671654b24e1ff2e8223ba747ded51f5c826b6e2c0f02e2865fc35d15045f41952835800406f60f966d1f241914726000000000000000000000000000000001007b5e8ed7f0d25091dd959d89732e9df02561a829ce013f5ad1adb8d6d828a8ce87b52d39fda1b5dc2b581ca420e22,800, +00000000000000000000000000000000072e022c168461905f798e87425f2eebb517e473cef98c255d0fe434863ef5811920af65bc946b29d489b5dee1066c56000000000000000000000000000000000e7a9872caa82d191f6014c845e1b3ee4ea1ee89852b546a2c85ddbfa3c1d4ce99002e3d7732ccb8cfbd57d550285ab400000000000000000000000000000000144be65db373f6401d76e0ee64e51076b861e8fca596dd6a7f3b5735c23b0cd13248404fa0969ecaa701663a1032f48a0000000000000000000000000000000014c9e9c5cffc4518889f7742440053678ff1d9fb1a1a103d0c1f762b10655bd5849ce98f4bc5eae80bdd9e767aae452300000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf,000000000000000000000000000000000fb74d9ad4de11df81c48d10b9a14fde8353ac47dc902b4420be4c086332be480552e26fc42b7c0f30e34f740bf9a4e6000000000000000000000000000000000612a7e23bbb525f91084b122dd4cfce4074c9e6eedaa7cddb58a14e0b1eccc2f08296baea3eb3e003e576fab7c557ea0000000000000000000000000000000016dea145df47a2c5262893c273c6158ee14d44c3740981c161624a6e9ebb982a52c1eab6160c3849f2bf3821d953f4c3000000000000000000000000000000000e920661772b8b737f1a663badead0e89aec4cbb86e6dece5d4db8a673e75b844bfe81662dff671658cb8386c16a7f3c,800, +000000000000000000000000000000000948d0f0c20715f8658e1f2b4f9d32d851e584287225a2f47735a1f4c241b07f8d7c5dd8c13bcdf84e97d49817d4d88a0000000000000000000000000000000013c064548cb756b48600dd535af8eb5b9138f984bac0391df2e90a204fcb6c36017df910031864d802a2ff719856b336000000000000000000000000000000000000b7eeb7c9a01be88e573f196c2a531635baecbc8cff9af385455af3757301436686596ec7fe3618af26953c49f7450000000000000000000000000000000001332f4dbd5461ab9e2c8b3c19c6ff407a071018c92d2c17c1d1d481c24565276c0f55eee8692016c1fd76d70f44627c0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77,0000000000000000000000000000000015930559743b21acaf390b557fb960d3021f3cde80630d8867a063d445f860c8a01037057de1929be16d879416b12a6c000000000000000000000000000000000c6074c54c83f717700f61c5b6bfc641502121b59b196a1f8c5f2945e5db1bca0d7a94fdae96bfeeb6204c8c3f4d048a000000000000000000000000000000000b3a78454479c0990e4c65e4f831606c7eeeaef0faa86596350c9e43e84ae959a0f32c8d03d1f631d9b2ecd046efcda6000000000000000000000000000000000aff797d7572f20b06bac75bcf8cef879df11599ba7f8b86eaa28692d1239cff22841b66e28662309e81a6a599e79ddb,800, +000000000000000000000000000000000d3ee70610b5029a28e586f0f3e65bb19a263db3438710fcb8073e1b25f83db50eb5bbb9d75cb20952a225023f747baa000000000000000000000000000000000682f7d5cf9d182b20ee88683f3915e8c9b03074a373e573aa57232de4e997bf155acf680e365aa0988989dfad102b2e00000000000000000000000000000000143962963e230a9154dc328f9583f5be6923a3b10ee7b1d0cd5f5cbff13913d8ff78ca315be7387900a50b94449884c0000000000000000000000000000000000f4f934b42452d41cc20d7b1ec547bcbcbcc10f215364ccf2b864db23a09d06e94c7a87165dcb691f4975323486757ad0000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee41,000000000000000000000000000000000351bad2f1fd9adc84280515c2d9e538b69dd63ac93514987ecace75d6bc4585199b742eae0d357d587924333721a1d90000000000000000000000000000000003e495b544aaf19a6415d5558170b8686968dc922367c5c8c212fa1f2785535fe0e71498b98b9a39c8b1f2384956170a000000000000000000000000000000000c7040f34872eea5f98ddc78737dd01fdafe75081cf66ad5c7c900674fa90257105b4f4fc59103dd5b92727a072ae462000000000000000000000000000000001312bdd27ef038d4a89b12c86281975bb34b435d42642fe0732709baf55e9a0ecc0ede8a4775a33e880aa2e1fa7b7ed3,800, +0000000000000000000000000000000005f0fd4080e26971ab16d33aeae04220ae23781da3179e38190082f1d167514bd73bc8ef976a2f333570e9f56a6c05e6000000000000000000000000000000000e159905d29b52ba61575c3a263093017783e1028b3701ccf060c165ba33a765b5265a9b1681c1759bfe2c9c401275e9000000000000000000000000000000000c5ac0bc29a49a7c37d772954da850e6b5e301e230552be9a94017d770ebe2cf4dcfaf104633623e024aef6db57892900000000000000000000000000000000002228e7f42a9409acab49cca82cacf306f6c6c29fd9f7e2ed12fef2d16383cdb7bb2b39ad598b301072c615232db1fa800000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f,000000000000000000000000000000000d521781f60198341d116fa5cd9e2b5c2fe51f91f6c8318f351df007c96086f6c3baa5cd2b9b4f442305695dd9b01ac70000000000000000000000000000000013454fc15b1d182bc98d75947547b3bbebef6d5e2d38ed7c67d76eee8da89ea2be19280af4760282fa7576412d5f2107000000000000000000000000000000000d866015c84de74c24dde252542d0d3823f435203c71cda140af235d88f3f4b736e9d75ec32c09ab73bf74083e76866e00000000000000000000000000000000147dfb5f53a9cc61b6788c911dd8649c09cfffbbba368c1872a31cfe3bd6d6427d7b00163d39f8e0b81fc4c40dc60b87,800, +00000000000000000000000000000000180569ce03e4a0155285e733adb18fbca71225507a7adf01cb8e8648891525305e92087f58378f4fd8455d5632ad660e0000000000000000000000000000000011ab84e42f10154e306a568d7cf7bc381000f0add0500cb508f695a3b283ea69d140aa0ad48fce2d2d6fcafe60761078000000000000000000000000000000001136c3016474d6f475609606e8d0269fcdab9fd3188a512681cbc41eedeadfa3b3d9355e5b4503e8b5c3665e49fdf3ab0000000000000000000000000000000003f56cba1b9cb4302099b16b09c2602dfab80d1151685ef78e5054cd454b319adf8b5998053a5b9fddcffa020595e3bf000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c,00000000000000000000000000000000059fffdf2d79b4a297f6912e3035cf0b07db9372f3485150e00d60bbe2e7d86f45b5c2ef062dd92c7e8b1e2be5e9bd140000000000000000000000000000000016acdc57e7231b020268373ddc8b8a7318ead02a8c7181165ab045208409373eaf57ace9a6db1fdedcaa477c7a0ff6f40000000000000000000000000000000012fe630f7de8ef5a129b99faff2de080849bf3b59aae1af042c29b1cc49c8825a4f28c4ccffedc6d568f306416b5bb90000000000000000000000000000000000d86ab3e49ffdc7c2485ecbd00256af83e7f3f064d212ea91245d86ca75e3c7f28b42fa9496a5ccc0514cffc60c9fb83,800, +0000000000000000000000000000000004d79dab9eef873f3415d66172bab7166ce0c71f322529bdeffa915c1b0d3fcd645c91dd3450ba61593ffecb95edb91e000000000000000000000000000000000d611a207d3222bba199fa083d0459675cb5fa00839fb4c9034ad868fc1e79d653c18651771431d6fb6b6b5ce8cf6f7a000000000000000000000000000000000ce802ecb106a4f0ca4efdcc058dd0e29deb6a5d30a2c15c8eda896bcdd3ac19053c10105328d239b26c5ddbdb3a95fc0000000000000000000000000000000001073e142621ecbeff6f81453660362545751f992ffeec3a83477fed3e6215a709ffe0d17b65d3369f8f3913bf000e84000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb1,0000000000000000000000000000000012ba9a8fcb69d15eff147f663a5d7927b6f3f79330eb9ee625e0100b146597554debfcf97a3afb51387a73554522ed0e000000000000000000000000000000000a63a990d6454d4db6d58642eb3489f79e517fbbcabc06f2eaa00c4b6f9a07aae97991f169d90af3461b7a62db276e00000000000000000000000000000000000a95203a1628a6ae2551df832f7ab94ffcdbf985e4c9744e244214c8e8b8079af05a9321d1e49b7240c2bdeeb7b783280000000000000000000000000000000001ec747203be73526d3f943e0af814dbede34020144bf247eef9a6ac2cfc83ef63f18a73d3baae18bfd8d5e83d0519de,800, +000000000000000000000000000000000bd84f04b3858b1138b1b429c7216d5d1b1e99c1e0fec26440d59b1ad79788c2d5583122c2ad769fcaa6d10d816a1f1e000000000000000000000000000000000387977ed1ce5da51dca230531bba53d17d3de5d593ec576cabfe6463d5164d7153025dbd4cb3525c4145c4f6b85fc76000000000000000000000000000000000a19c943a90fec6921367a2edc5bc38a5c59839cdb650766a2d2d068242463dd4460bd1d0e7a7fb0e3d2104704b8b3730000000000000000000000000000000011d99d44b200feebe00bd42809e3f67a23cce88a07165416cbfaf4db14420f99e54d62db4280d2c99ca0bc3dc41eddbe0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc,000000000000000000000000000000000eefda9046a950c232c6244a79c33e7135d0896bc57839a4f971030220e3ca8196cd0ad75269f3cb5586a384dcd17f9f00000000000000000000000000000000195ce623693996f5ce9e45b4e285adb969e6771e6b0701fb5c95715523c8cb93aa641583821a3b360ad6f4ea1aedcc9f000000000000000000000000000000001553a4d0f965d26fbaba56294591935bed63c84abfedbb9d5c61f3d43484ea71600935fe3c8b6b137d7a9074d907e86c000000000000000000000000000000001673c42c88e4acf8ca38680694b80458f988403a4bd667468506452303000d13649c4f610b738a94ff88b65053731c08,800, +0000000000000000000000000000000006a186aa584a466a860849c78e4922889c95a4ac6f39c99029fbb422c43d699a8baa51aa4ef51ff99557babeb3e9506800000000000000000000000000000000065fb15b5a0923bdb52dbefc7e9f1a898e32f17d610bac829235446fc5e1913fffc8176e0fbd33091505761f1d06d8920000000000000000000000000000000008bd358698fd073f660ed608462cfcef1da9a59b10905f1d98c4fe66958e56802814906430c10fc25a4d351d91f91cb0000000000000000000000000000000000a53638b1b6c6eeff468e099446300ca7c7bd899c6494682d14fdabfa9cead0bb37a0325d99e7d0ba6341cfa1d257ba800000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed,0000000000000000000000000000000007145ce58cbe48405392edda6022ba8942df055ab582ac402e7c9a0a951cc6a38cd147903f042273e736f30849996cd10000000000000000000000000000000011b457ba464ce818a34a11afc3c0007908091fb528836691e6eccaa9a23ea90cdc746769c4b7ec73efb1f2878413c3b70000000000000000000000000000000019ca519fa6a91cb7e83704daa9b92da9bb70b003f9e9bfe9f323430bfec9b19b01005aa9fcd19d5b1ac59dbdab0c0d84000000000000000000000000000000000ae356f5e5de0d7662bab8d947662bf87d792a3438ed477cf6ed4b27c935b1dd76a5aac446d4dc36db544d4aea40b505,800, +000000000000000000000000000000001070b98c6348a67e996626ec2752f45e4c007e9c9668459a777c03fab633c10236a1c5be99f3fd950542d5648ef9e88400000000000000000000000000000000073a564401cb1a3a53334c0a55da261814d27b86ebf40b02a76b20973ba2db92e42c138ca7790261c2d70401c984bf470000000000000000000000000000000004212d8a9e4b01f5c6814a88561c2c6143eea61327b031a2e0e4bd056c12dd7098fdfe4d1511bb441ad42b55b584a7bc0000000000000000000000000000000005c5d23824b0fe05eb962194550681c57c1566b315efa8ebc90b3593d7d86ad18328baab8118c9f47eccc0757588591c0000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c5,00000000000000000000000000000000135c42c10ef97279e3d152b18cbb8dac11ca8c805dd1d80818851424f592e7522589ec7df6748b5c72d0808399e629cc00000000000000000000000000000000083ddf3843434937e05ba9e101096371fd8fb34f226bcd517716200003ab9855f7aea94980c57a6b933494cc57afc562000000000000000000000000000000000be9215d936a49538442189c9a0bd3be07d4b0b1d14aa45afcdebc1fde17d33b66f7dc36da1ea5411549577f5a1967ff00000000000000000000000000000000176a4a4962c4af75a712e5093ec2cd5cb5c0433aa0657809dffbc0bc02b1ce303ac084f39a5721d482d41412d391317c,800, +000000000000000000000000000000000b1b3053774ad5515a20bd4c556d2b3ba95fe74fd0c955069c7f933dfd718ede90ac295f5a675f1c29dcd9701978353700000000000000000000000000000000145746ce88686021a0635bf6f0aa2f77c48bdb364cf4ffa804a57f95bd69d24eead05fbee24021c1ef57e1c7c7b894b00000000000000000000000000000000010ec4795a0762b86f3b83de1198698af67fd1b1be3ddef48f35cf82bc96d886fbb4c75064f51a9cfc5f61630c95d0ad1000000000000000000000000000000001465e31f58892466b8ae4b76a239d9f8d1ecb1834886344013cd1df0be13591798868d224d38213a6d75b02a1fde0ff200000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de,000000000000000000000000000000000bcd916c5888735aa593466e6ab908a05af528f34a7901fb60feb1f51737c73612436c192dfdecf927019724ab2a9b7900000000000000000000000000000000187d4ccf6c22381d0c40c9d7820ff8efe6298c6dad0caa25402412661737cb482dba2719c3a50ec08cd022230952dfc600000000000000000000000000000000164510d4f2cf1e14e039561f1baf82bea678d0065e378d5bb7443fa782e6ab2a3bf7e4ea125d6415a8277c60f5346468000000000000000000000000000000000281f2e28b73eca4db9966456b75de9ae3830c74ac928fc4c36b4aeaaffd47ee587d948f68056df2826ca2775415a53a,800, +000000000000000000000000000000000f39e731e6ddb7496448c912ae314e833d28208252c7f8e27bcf7eeaf1da6e2310538b4ef0d55401c6552e91fd70691600000000000000000000000000000000069d3612f924961f827497028737000513548ad8e104acee28f014e730d4752a583cb9a893e6169b71966a1c4a4ad2dc00000000000000000000000000000000090899907edcbd336bd4fdad0dd67c578ced4481a25b864b32aef920842689a2c23265277a6e1d4a1dc1b5047a9f79a000000000000000000000000000000000055ba64e2502baf68e46c759fca30247a080464eda2b32e7cfe539e545d6aac6dafb731c2c45749e50513979cecbeb5400000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157,000000000000000000000000000000000cceccfefe04f94e0b67b29b5df8007930665006cb5a59504c3656b8c0bfb52324cdf50fa2722ce15b0ded0efa7fc85f000000000000000000000000000000000cdf34c330c0125f524f0711197639f8aca3e7c435f8c5ea30b78e9622c4bb72a7e584980cb4c3c6ecdd0689daf36b6a0000000000000000000000000000000004b1505d7fb65f6c06ef23aef85b16f3d991218187c5782fb635ba805da463cec9cfdd670c53d680c603adb827a4460a000000000000000000000000000000001104af6bef6482ae64b3b6b39664ec06c39bc18fa91b7b4e5bfcd444c827bab30ef548b28ef5487582d88fbc6d7983cd,800, +00000000000000000000000000000000042f1c8b9fe81cdcabea047d0998a1354ce09d62a14f1d0e9d188e2f35f2e1845c2b090c5e157595b33108c67e6c184c0000000000000000000000000000000018e69d3564d4ccc0306e1e6b227b0f961aa9afcad59d4ee1737f980dc876609c59a4c6a3506f987467beba0764b857000000000000000000000000000000000012ce5883156588cfe0f4838f819f985b09f1eab40a5ea8e30fc5d70d029a01a4537641248f4c21dd203909e0170737c80000000000000000000000000000000002888eb9778a4045feb5899dda258657b9f41345731ba630fbbf186b3be4b58ffc7f48abb65b693b573a73f85440a7a70000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc0,000000000000000000000000000000000e1ef3003fe3181f690224cbc7008856e1251430ce3cff56a1965c89a892604398f5101d1bec7ff1590b0cc3d23b854600000000000000000000000000000000185b4d4b5fd8313c31542bd1bac034046ddc705b41a034a00570181503a6ea4c2d808bba0478900064270fadf3d655920000000000000000000000000000000005bed63ab9898b89f92027c04ba256569e6285c851753e12760129c98899bcbab34b62172906a1ea4cb056d4d0a5717c000000000000000000000000000000000961129a3e212c7412018d7407d7ad16412feba8c138f4f6ba69daa1a25c6b23f3466bfde6f5f0d09ab67248a2abdc68,800, +00000000000000000000000000000000051982b46a819c74105cb36da871fb2415328a1531d155856f6551bd043eca62ddb61f24af429edda830fda31e22cd340000000000000000000000000000000006449e5bcdb5619aac542f6633ee3e06a4fd56a3e1ce4034efc608131ff6ead70ca63e70f494f519d5c577ae7119c8c200000000000000000000000000000000153f4f5dddd5801fbf7f88a735b9170d24d5b63861d50cde9644579dcff277cdb0d5fbfc3b3b819a1172de05afb9135b0000000000000000000000000000000010fdea84983fe6c08cdc4b4ccd462bae2ba791ab5209363b10b3ef342c9a5e92184e9d8be1419e3d88402bc05bad5fa2000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc01204,0000000000000000000000000000000001504c47ab0c410b32d5f1fe3d3996dbf1b21c5ef5aa3a2862a9d561b419f818f0b32b8e931c65fffc393ce7beec70ee000000000000000000000000000000000217e9fddd2551a171a13183ae3aba6bc5ce99e8f3587b92a7cffc738b478d8293b8c71989cabf9a55c5f5077249345d0000000000000000000000000000000003874de865d93650a95af4e153fe557c45bfdc4837bd6e209b8f05ad12b8fdee6432675cd92fd739b7e98e56e7ef16b60000000000000000000000000000000011303c0c7ec1f434cdf07c110da5f0bcd85935c3a0ce9fdf5546ca61edbc2d478562dbd9aa45a5f8d96e033feac2fdd6,800, +0000000000000000000000000000000009b011f793d9a939d916d058ffe91b58138820a646cc450389b3074ae3715d06ddec1075afecda71c65c7ca085210c740000000000000000000000000000000003d4d20f4b93c1e90a0a06bd534d8b4fd64e4c4aba77ae42cf4c5b2bd95f8b02ec4069ea246ff46404e6c9eac632fbac00000000000000000000000000000000051e88c3adfd4d6a02d3f03812362a6cfba3a6c69b9aeef75b51106cc7f1750293d61e31f0ea29b5d7aa56debb6d2aff00000000000000000000000000000000086d9c4ea6769cdf49ffbbf7351023b4aea640e8c90f9291222fd0b5984bca4d481bf7e10df921406a34804e6a09f99d000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6,00000000000000000000000000000000101ed22b16502de0d83303134a97db17ce956faedf47256a9ac86004bcd3ed112a71328a58f98a85977a7f22eb1352c3000000000000000000000000000000000e841a88d10493f301af54c5fe07a31ef90de106a6c87d5631b6967fd017f561a56176a5f3544dbb34b9f94040ebd2770000000000000000000000000000000001bde3c0076f26973651cedd3da97c7eda24451bda856026d1e22d3b65c66a3fcbfbf506b4b664b5fc06fca2d712d8a8000000000000000000000000000000000ce553ee3b7d5389798cdc5af8569aaf477b5b74ca1138454dc61badcf3ecf5e0ee8457e374b5735d0b8408b04fdbcdd,800, +0000000000000000000000000000000010d48bf523f3909cf90aa58a9517ef5421f1212accd5e8a0f830aeb15a587e215ca9c340bb846b1d0474e43840b2af79000000000000000000000000000000000cc1a3976caf97b9d59f448f6d9f413eef8904f360c0cf912fe942b38d7fcc637a17038973a133608ae769d3e389b18a00000000000000000000000000000000069a6122c6f0ec68834b7617c755a7eb33a80a25acf95859da5ff03316447182f122d20d993b04e79b6fe859b7adf5a8000000000000000000000000000000000058c6f8c297524319bae6722e0a957d1ba0f75ee3a8aaf06148641c67925d15780e419a38ed7e07410e82769da74f2d00000000000000000000000000000000030dfbb89bbe5c14a7a55e68edc4fc38eaee9fb539a6b2f941264c7dc295da5712b0af0f2bbcdb74f785dc9ba038b0aa00000000000000000000000000000000132b4e02fda605a69251a4a6289c47536f9735dd90908ed1fb619b3ab808b3a1f1ca3fcc8f4b35c9864ae311c15747f80000000000000000000000000000000005858ece0bb09e55e012450551025ad2a6d93a15d29619433742851a62d987e7f8bfa6c6faed76493a27060ef5f51805000000000000000000000000000000000dd6b393e6d1b8d546e3f5ce69bc1737399e6ababc628f25734030e10d82b5e9370edfb5da15566d80e23d2fbf8aad5f,00000000000000000000000000000000182f90f5d3ce3f5ff2d91430376144583247def83b3e83524094d57c0f1be98b1c4946964deccc25fc303d6450edfbac000000000000000000000000000000001844806f711735c5ca18ca48e559a9e327b87b91d22a5ef161da7874668130e21a9499728fbc2c88366bdb59f8ced0cf000000000000000000000000000000000815e7cff14b4ceaf26d1cda5c267f432fad294b6baa239b65d886ffb039321f9e24330ae738a35298c6d1ec1ce1c95f000000000000000000000000000000001188a4a2f0920ddeccde1a47a0636aa7c404fd77fb9c828e4fdb5406df80ee6c258c2d4a89dae5e2a2b05210df9100d7,800, +00000000000000000000000000000000156ca5e80be8c8c03a5506ce9abd22a9d4958c372678c0caf6f1329898507dfcb1f06a9464cf080bc6881fa5b7df1ebe00000000000000000000000000000000088174d486b4086b931010da298a399e15b60a113e08f571e096d3a4e94b57b3a684711318796eeca9319119b201abb30000000000000000000000000000000000b96ff68505c088cc03a1c2dc363b05bc8544728a12b29569bed137780523123eb17e68f4632383c252d81bca0c5ca9000000000000000000000000000000000486fc6e5224c5fad56234c41856e60bee4a6c1046f673bf7d5c1bbb603b141fc91074da5f9d3d41b796a2ebcebd9e740000000000000000000000000000000017032b16be8656cf23bfe0abc8c9e6aade223fa9bea6fe25f95a025da79cea6adf38536eae3859b25ad1af1756b639cd0000000000000000000000000000000010975ed27cefbb43bafad0fd14c87ada8e84525e1d199fdf1e77caa0b718214b33e547a42a040ee3bfd51621a20d22fd00000000000000000000000000000000133d29aa41f92de37523d281eebfe91103f017e5fb390f6bad9a2a4419fa4702bfa04847edbca1da96eb1ad563a92c8a00000000000000000000000000000000014af850de7e800ebee4be1a33c7e3b30aa94106db7defa148568ca3c8d82edc97ab5769ac40162d3728687cdac201a5,000000000000000000000000000000000cf42f2ccff2e0cdda7e5f1d7652680650b4afa523c8f9a554ec18b905c837a189fff73982cbccf903ea492ea902b87f000000000000000000000000000000000d38219770f669557cdb623f2476b5f3f7478422b016123bf86a17bf75848548d1a1ce96a292637b8d52481321d80fbe00000000000000000000000000000000170d8722b824e3291b570ba8e4f9279c1dccdefb95cb5b7a94d27ad8a93513737f12d18ef3153c4e12b530bc457af34100000000000000000000000000000000021aee9e5f578328caee3177a4e08303c3b5533e288dcb75f94992db3520a6da16f4201e60367240b29c48d175942cef,800, +00000000000000000000000000000000121fe97c62e068988ebff21d8129d52aa903afdbb62862c7fd99564d9ad72182ab1f3a1100223ae486cd76f6938e123f000000000000000000000000000000000968ddedb04f52140160061828b5f88dfd09aaf37df625ee6f66b9500d6608df31c7edf86296eccf8f9918b051a5e4df000000000000000000000000000000000b7491cb8f6252e3861d7160feb0afdd736d27886863ec0909a7cc711a9b71aace18b17a00a2999dd57ca1a74f148516000000000000000000000000000000000fdb280093ef45b12b694ca3390a865ee18e4c04b231e2c98cc28706d4cefaf4e654582ee03f34ecf1dfa9674489d55300000000000000000000000000000000185aefe71f24281e5b03dd41e6d6d45fbc8975beb175118de7568bff0a9ccf917e9df97dc26bca16e8da06b0e9a8e7bb000000000000000000000000000000000015b326d401b827fdf556e4a24a3dd6c8036b1c849751b5ae3c3728cad88f931b06e3a345523a723481193f7afeb67800000000000000000000000000000000054ca16b4c87293002c31e64ad303e8f040e11de8b45c5fb9aca9dbec59b29dfda8532a8ef5ae6a92ac8ea90ee4303e0000000000000000000000000000000000b65a233a7731366cf24c801724265215a8626b1290d86c60bf1e74b021b0b44d7d6552f936fac7b5e60cf1feaa1d82f,0000000000000000000000000000000010d1b2f595166929347e06c1debefead06334f554dc31f320cb844abdb1810b5f7c4b933ff8072dc03d303f4a6d0d09b0000000000000000000000000000000013ab41dfca0a7cb0c58c2c19e02f675a94d9e73312cfe2999dbac34e6a80bff9472506b48690f24ad3171ad495f445420000000000000000000000000000000015bfd0db53fd4da538caa3aee7a90a669cb84460365696ee79b190d09a6d4c3f08965de7fff4efeae435db52b97d213b000000000000000000000000000000000182ffc4304b911b47b092ab678edd63ed5f5e8a9069daf9247f3bf9c0dd149cc9992728a13b0a236fc9b37714b35882,800, +0000000000000000000000000000000010d001a09cf5dc3276482185f26ef3f75d28cd6d2667eb08a7fe06c03b99f3b6c4d82390739b6867a314291cc642a8b2000000000000000000000000000000000587846a460b1f37c2e7f491f9a097b4e86e1943d9cd0999313f65627b3907f09b5d5ac1be376a313a959dd136f7e9b3000000000000000000000000000000000af439695556e86b102926d3b40e3e54cc84464e120de3b4e3c5541a6a5bca44151fb0594009663764c1824518b13f020000000000000000000000000000000003bfd9418c1e57269e222152d321b83ae090f216cb422956dd1fcc464f68526cb4a05cdaefc7bbe6e81d4ffe27d64db400000000000000000000000000000000085dd8bfc00ba517dc8d7ddb49d711d35bd36f9fe3843689019e779624a032d2f023533b8184b73042d1a1953d2885e50000000000000000000000000000000009ba8d5d36e6efe02097a3206bbed68529f0cb9875ab81deafd886d9243bfec8b403d2abe713a2ec929b93305dd2da220000000000000000000000000000000007f8f90ebb2771136a92023901ca85e87fb7c8b1a40f88ae564a124bdd0ff0bc27ea98612a817e2c871fb4bcea3bb06600000000000000000000000000000000152de417d02f1d14e5899201db8fd5db8ecb40ea8d415dcdedce8ac70c28d851db68e9aef94506a50ec28145547a2d68,0000000000000000000000000000000017555399f979745302f08210de5311a6401b6b181100b3bc6b6d450f0f62079d2f02d7badcb164f50dfc46a975cbd6720000000000000000000000000000000014aea86c06e4c1fbf0711a8cfced2544c7624abc7ae7906cd992bdf575a702540c45c2117e221446ba09960cbc9048ac0000000000000000000000000000000002fac56960c4989a84e02ce36e8970c2e847ee45579d31ca77f042bf96505af574af822da084ae64b22ff876610ba9a5000000000000000000000000000000000a481cfea2aef8975c80a297ce5a185dacd25649d41f8466d3c63d786e3c264a8e4ccab5ef6b80ab1260e86ab6d5b3f3,800, 000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2df0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1d9f0000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40,,,invalid input parameters, invalid input length for G2 addition 0000000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2df0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1d9f0000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40,,,invalid input parameters, invalid input length for G2 addition -00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220f0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2df0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1d9f0000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40,,,invalid input parameters, Point 0 is not on curve, file src/public_interface/eip2537/mod.rs, line 146 -00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2df0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1da00000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40,,,invalid input parameters, Point 1 is not on curve, file src/public_interface/eip2537/mod.rs, line 151 +00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220f0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2df0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1d9f0000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40,,,invalid point: point is not on curve +00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2df0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1da00000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40,,,invalid point: point is not on curve diff --git a/evm/src/test/resources/org/hyperledger/besu/evm/precompile/g2_mul.csv b/evm/src/test/resources/org/hyperledger/besu/evm/precompile/g2_mul.csv index 9d3c80c0cf4..7f9ee261bd6 100644 --- a/evm/src/test/resources/org/hyperledger/besu/evm/precompile/g2_mul.csv +++ b/evm/src/test/resources/org/hyperledger/besu/evm/precompile/g2_mul.csv @@ -1,104 +1,105 @@ input,result,gas,notes -00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e,0000000000000000000000000000000006334ba1e361fd94bbd98f44b75ae9ec00ecb4d3467b5528870b1a1fa9a7d04449f12af90bd4c7a1e3f29e717d6d19d3000000000000000000000000000000000bf4cc1626393956915845ea7ca43d30a59c7196fbe309f2d5ee6de7e40c191d29821dd6aae46abecf634b904de8f7490000000000000000000000000000000014aeb09e252cc74610ab956057d4ac5af95cbea8a6baba9e5062643dc037d6841044cb38b22d7dfb978fe0b58f94cc3a0000000000000000000000000000000000fdcd73452fc1ced1c06e6271410a48dea05afbe889a692905e1baab8d72418c62531aab8b74842b51016f0a9cbb93d,55000, -0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d93884d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d,0000000000000000000000000000000010e70bef8eb893377e7ff92168d7acef11c9efab990fbded728b173b94e1d99e471a8357f16625d353287086543551850000000000000000000000000000000014043c1f00221c439e5febd12724a9224bccf0389914461644daf329208e869b1bf149880dccebccd440b1748d15e944000000000000000000000000000000000f7dee1e7d122e410b29a9eb011ee700c2f230cf8f611e196ec66e153c1fc331175532a8f9b060b573bddaa705430c2e000000000000000000000000000000000e1f659470eab7c0741bc8777ac9fc8dcd11a6f1b30ffb4265e96b879e795a4dbf851d1149429dcab95464e89f334627,55000, -0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1,00000000000000000000000000000000119a5147fe9ddca7123f721b5662c1a44b0964c37a214cdf3a4fd34166e3b25210344e65220c38ec84d0e3b5ccc7e46d000000000000000000000000000000001642dad5dacf4295b871fe9b2787f0861f158807b2b6c01c2dce12ab053c9472bd3cb98de5dc33f40053ff45ce5c9af40000000000000000000000000000000005bb5761602b6639f2ecaf79f2d1f853fbdf75f4b3852b90808b858993a83f8a0da8a2ce7072aa91e3b6b3ffd0b3d1e20000000000000000000000000000000000a75143b9551d4ae41fb8bd71fdba7826b994c65904d9189a5ac5130a59cbb9d8dee0e016735565148fc49823d3969e,55000, -000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf494c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a,0000000000000000000000000000000017ebc9446f8c8e17dfeddab9188d0c808565da29c0bdbbc4138a44ca3196c4564853be28286b66660cda36832d6940010000000000000000000000000000000007f29a9583b4ae83d3913dcd72590a3f20f39eb5a6d36663c1ef433058e76550085b9c01bf797d98d0eef45cc22ff8c50000000000000000000000000000000016eeaeb123b12d1913ff1e50f974228c79f2b995609d2e3835c8e1d68773b0cd484df57b86111cdb75de1e19eaf062e500000000000000000000000000000000002f5688c1286aed42309896bd65d1826dc64dda615238fa9043669806968b8e0e1e3e77ef192b7df540aaf0ed282a9a,55000, -000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e8964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b89,00000000000000000000000000000000042d0c1941ae0ed5e8787437ad5e2753bba02185317848e8ec2e425ac954e0efb1bca534725adfe87e8507851ee337af0000000000000000000000000000000002db55ae8126cbe86327aab880381a81205e33a351d172c883b9cc184799866a8db5a6b4321496e05d3ef62d00416d9a0000000000000000000000000000000012c45444403dd62d7be3e7658dd85909204751dd7d085f6edd38c0aa9185d3c32407d8c95bba371b380f788d0dc48e0900000000000000000000000000000000111421c6dd0db595ab731adfb4bc76c84a61197cb023b6f17e7176c443f20a4b6f8cd0a00cfa61e831ed20b3c6a84d98,55000, -0000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e2787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c944,000000000000000000000000000000000ccdb2a0b670f199a9b61198e6a2ce2117075733e6a1568c53ca493dc3674c6ae85be2491d2ed983f52e2c7040824afc0000000000000000000000000000000004f52450d7e041c561c00200d5b142b32f2df2e2156e4f6c15d6c00e185e135037a1ed6be15e2ed920daa00e2f9bc8da000000000000000000000000000000000f39c38c18f03ce6baf1d016cf32d7387269940280f2e8d21db4da33dbd2d24ebb93ae3dff9f79b015eee25813d677c700000000000000000000000000000000189df61f7f1025fa6fdd0a4708ff1d53db7d414019c4828de2520af3d36776062350061c2261e46e746a6475fdeccb2b,55000, -00000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f300000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46eaaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1,000000000000000000000000000000001388a59c57ec8ca5e68b99631abdafca1b71352ac35003a55bbc415b48b8171857adda31123ec86a6ed9e1060d56aa67000000000000000000000000000000001471913b1ab5bcf9336665d3d44232b4e58da70285b7b8eb1dfd7c54442afb28c339f56e6389f89b84db0879e1ee058300000000000000000000000000000000022101b4de40b7180ea17bb36bad0a668a8def3e7361a96fbfabcfc4cdbe6f607ee4ee80d0eb2418b848ad056520092900000000000000000000000000000000103cda694792af5a51e04b6422600a0ea6f50808ca54423cd4f59dfba633daa5afea49c85b900f52e182610efb62fe7d,55000, -000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d10000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12dac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c,000000000000000000000000000000000cf5cb957a752ce9187940f63b13080790348814debf84b91e74fd6e822c2735941d61d50d492439475bb3ea7aa849ec00000000000000000000000000000000012e546ff33dee9875510a68301f46d89e6175f5cd9a6e179fb8599a580e9478fb8d92038982551dd29041d8185c7474000000000000000000000000000000000d52fb57bf2996dbbacdbcb4088df38e77e25598b91bcd5e41eaa27b1398eac150586b142f068d5b498e0ce458d3e8950000000000000000000000000000000012295e1d1039abe7a5fea51a04a34e9e8d44a0f24b8c032680703c119d54274d3bc2e548854021ab027b693e43964314,55000, -0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5bbb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108,0000000000000000000000000000000008e4c57309339400ac9b6b5df16972c272d47cf69ba7baf89afa4f4e72703999c5885253cc35686f6c8d277399da2a390000000000000000000000000000000018ad4e1f105f16b0dbb4eb089c51e709c25e407e54b64346224b1abbe15d62fabb231e36a69eb05a9ba7860f772634200000000000000000000000000000000019994d20a7ecc0f234ccb6b1793fa7d1ece64b3e157c579fb05a8c6cfcdd6f5456ac1f4c1beadb69206988ab543bb8bb000000000000000000000000000000000d435e74bed382442ab83ec90dffb91336137932524bfcf9753fa5ddfe038d0b98a045c8ec9deb53172e5662d3fd67e6,55000, -000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4000000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb81596fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f8187672,000000000000000000000000000000001425890b6c46c5a07a79127de4ddbb751227dca4481ab7c2f601bf22b8f6a149767c73bfbf57ee399c0f2d0b12852a0a0000000000000000000000000000000012cce15f53fdfffb5f71de3567b0c0adea65b9321c85677c574787f7048c1bb5e2dc985b65fbc48115aa129e6000fe4100000000000000000000000000000000041398497f975289fb9fc6ffe671a19fdcd3753c82ffd3b2084574107bf7fadc8de462507f4484c32df39967c3751a480000000000000000000000000000000007514a7f246006e714d4a8cbb4e89d81b951b5c41a05bcf35f61283e888074fb3686fb6ecc1a66e491ea1e1ce0738102,55000, -00000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c000000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e977955054196b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea,000000000000000000000000000000000b24adeb2ca184c9646cb39f45e0cf8711e10bf308ddae06519562b0af3b43be44c2fcb90622726f7446ed690551d30e00000000000000000000000000000000069467c3edc19416067f572c51740ba8e0e7380121ade98e38ce26d907a2bf3a4e82af2bd195b6c3b7c9b29218880531000000000000000000000000000000000eb8c90d0727511be53ffcb6f3b144c07983ed4b76d31ab003e45b37c7bc1066910f5e29f5adad5757af979dd0d8351d0000000000000000000000000000000004760f8d814189dcd893949797a3c4f56f2b60964bba3a4fc741e7ead05eb886787b2502fc64b20363eeba44e65d0ca0,55000, -000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b660000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a93b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76,00000000000000000000000000000000048ea2c854a0df7b10a2147db6eabcb16eba340644f737fc99663d1ef26d8ed688c2baaa7d7699c5f540d7605eb48485000000000000000000000000000000000c959efb835d48d3e7a8ce643764f27c365f6248a88e39092e3a6498f04ed851c55b796dacd62ae73d7edf23aa45fefc00000000000000000000000000000000114337b8caa68cea6f22a25c0ce3b247cadae24c63fb02c6a98a728b54f97b12b1473c8e23f55338326b9575a637bb2e00000000000000000000000000000000033167b0668ec650581815cefab61d13661f4cbc6e01711af0aefb699e1979b551d0031c603ee5f6dd4f716ea7aa4a6e,55000, -000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b38173173459836dd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c,00000000000000000000000000000000142f6b71471f3665ee6269cf598fc3587a62523f9753eec48a2461a2e313e376828cf6d1a9ffc9e64353c8a668718736000000000000000000000000000000000153647cc4a5aeb8ea52f845c415651e167ace9f331c1d73eccbbe20a4014f9e1158c281495206de4b841839438a595500000000000000000000000000000000151d07c3f83217e63b332a6c47e91ef2418e9c658353f8b644f23266f5fbc727562f0935b4d892db947cfbd0757ed61500000000000000000000000000000000035bce4bd2d8261e21476c325cb68e581f20513eb5e0e6a0ddbfd4ac4674bc323590b6f52d0cd50010c13642e7e03daa,55000, -000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b7010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a,0000000000000000000000000000000014e83f87e7f66d8ed880ca46a76a5d3bbbacf2dafe1ee055f04af568738f4c6ddf2a93e1810b616da6f64f25c35a7b5a0000000000000000000000000000000003d14447254b61168d36f92710f95f7100cc8f278b0bc9528da763a18a5386b3f5b83b96f4dc426e4b0fbe755bc986790000000000000000000000000000000017f1a79ed64abfe5e960fda02cf3330e6ef5612c1b8639386959f86c970adb797bf077a468273d37996a65685f75ac30000000000000000000000000000000000d973499a7bf7132541c0976bf2e9bb26a2b6cfa5bda720352fa7a180a6b8fe95befcc13de5a2efe58be934cf7d8e664,55000, -0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9300000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd394c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a659054,0000000000000000000000000000000018bb69dd6db0beb468242265c382de5ac342d465b5f72d4e5a24c67a48272d9a1f3af28e0bd3712e16a854c5d91c616b00000000000000000000000000000000072fbcc86b7dee9c2dc177dbabdbbbddb630c98ac3bf3737fd22f99e2b2b690175d9c5aa4b577f78c545dc6a5d2d03c900000000000000000000000000000000161c4218143ab1f0387f19bccdcd08f9caeb2d1331ca890741799ff1b40533076b6a96a910714176c770b25d2c17715300000000000000000000000000000000063098cd9d1eeb899724b40a2d10ac951ba0277db09aad639957f58541dd391fffadc5d97833bb9666b054e12debfa92,55000, -00000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ecf0000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bdb3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746,000000000000000000000000000000000e43672f1bc25e7e0e64a3fd26cb246bdbd6fb5c9084afdc87c888634916e6a6cc9a351cc67a6ac77ab8e132ed6cbee3000000000000000000000000000000000dee9612527c8ee9c574a4c51f5d3504ccf1d5781b59c78ea15294332c6acfdcc7bc68853e70f1f72524c930e4c3d2eb0000000000000000000000000000000017eba629eb14a0636926275f1c2109318ce8818d8171c69fd371751b6de47bda5b00a0b0e3765d05bab7b8dea9add90900000000000000000000000000000000052f0a4cd9b91695e1e58ead1da1480fef08cecef63896aa51ab16da373b99b3b91767a374645ac5932d9c7fd21d4636,55000, -00000000000000000000000000000000000eb3c91515d4a41209a73564741a8ccf901a624df9db22e195a5d02d24b7bc0a12756b15b8d006cb991a7e088eaef1000000000000000000000000000000000704ce8afc808b0161f6f61b22d990d713ae398779e6e74e9b5771daf006ce0bba3a8088edf75156f0e48b92ee8409b00000000000000000000000000000000018fe81e05aff0620f4bdbe4a715e015650497afab62921eba0ab86b649e5a2fd3d54041868928519f537e36448688a0d00000000000000000000000000000000162bd97161201ea3c26f8dd1204a9c6b61b762bdf573cb5d20b6b255f30208ca7d96aa47b46fb8c6bf0922075f1c1ca807f80a5e502f63375d672379584e11e41d58d2ed58f3e5c3f67d9ea1138493cf,0000000000000000000000000000000019b7ea673dad96c8352870136ea262c9ed105550cb403eb1e64ad598b2145fe1b95e5d61f1b5a6ebec47568c67b68086000000000000000000000000000000000f06ff9bcf2ba284e705b12ef2311f1a9b867ed742ee0737567b5c878547b18394b82c2bb97e16586515728245692cef0000000000000000000000000000000019dfd2d8fc4f2c989c7e1016e147f336174c84d380bab992bf1adbffe96d93d4d2d1d1dacdba3adfaf283b184478229800000000000000000000000000000000068d230422006004cd88ab0dd46a84af3905c7a1d329446cc23c1c5adb401a86a9fa76aaf577f77c2678cd8de8685ed4,55000, -00000000000000000000000000000000135aee0e30fbcad798738c10d4aebcdf50c89ce516325f655fe763dce54ffedf94dd74168611e5ae879b5bf5598d62dc000000000000000000000000000000000c728e672cd8b3bf9341bca929c34118b566cd3a80452d7015bee9d5cdc001b1f5c678d4b2cc4f7cac353e7bf326ca1e0000000000000000000000000000000014809aa22e2051e463fba6d49fbb060d0c7f599a0fc5409d34e71f34817e7beb1251810ae6eee1848c60796fb8647dea00000000000000000000000000000000145a4de777d86025d50e12f9a6615ecb9bdd41489992d1b643dd9aa549acbc63b04b0bdfd14b6e45c70f165e9a8c91bebb169138f94093d5c1c6b253cc001ce8baf78858dae053173fa812d2d1c800da,0000000000000000000000000000000015ffdd83355978ebfc386e13987effac0137ec628fff1667ede29cfcbd05e31cf8323959dd0247c20cf28978dc242c790000000000000000000000000000000016b1f810da2ae3c2ffbb6b83c47ef03eb0f298ff4c304ab0dd7b97207949d62858458d789c86c0cd474c34fa720ad3b70000000000000000000000000000000002a2e1463d5e795e6a25998a848b079363efc7d0337c3803385f4f17f11726b04108adfd87a811d709cbb6750c969526000000000000000000000000000000000289a3f472799c06a84bb1f377a36bad910220e1017884545159fe1b2505e8e7473882fcf324ba0d9125495bcbbc7226,55000, -00000000000000000000000000000000009a58b7116dbd6f550f8ca98071813130ecaa9ea86d5275eebc36860690fa048c9ebeb46600b2b63e847bff3e38ed0d00000000000000000000000000000000113ffc0932c041e0e34b2540c485eb74f5029b339cb60bc88a8a749310f33f330dea137e5f340044fd689264af66696d0000000000000000000000000000000002642da3c2c7b6688aba0b19ab29ac72e35caafa044863c364ea8833fca850289de52c0963bc33d7bba40cb5f568718a000000000000000000000000000000000552d35ca054da2f148c119454f6760607b351f2441921a2be17da2cc10902d71571c5554f132e60df79679428fa07e3e40608bdaf3e7764358a64a920cbb33ab4d571c7b3092e1ae11d9697f82ed833,000000000000000000000000000000000b02ddcfbf391a2d6953261c786945093b09377352473a86cfac6456a811233809434b566b9301eea3105eb86922efcc0000000000000000000000000000000015430deba91113b841303120f0738012d77207e9408474998df5e68d0d61f1a64afb947ff93116ae766ca5325046e263000000000000000000000000000000000ab7094055919f6f707b458cda552f25104d95e4ec8d020ea4c17ac1d7efef5c4c3a769120718f1d5171eb8630a3018200000000000000000000000000000000161e7209f8c98e511a698fbf01735798cb632ae1afe00870654ffa0ba93a549edf4b97d60f03974ab0964cd39298401f,55000, -0000000000000000000000000000000018fbbcba3d4b1e548ceaec4a48db62a2420ff29a67af332ee7ea3f902f84e6c375fd33abc33d945c5bca25603979f9a400000000000000000000000000000000072ff416994364bdc6535f36c82212afa822cd94fade69f11eb38dbdcd37c7e22af55fe05e6a826dad822073656eaac10000000000000000000000000000000017bba179b847278a4878b6faeaab3b1f4bd7540d22817cd9aff95557497f8b9d286657b6162c0f89f7820becc637dd550000000000000000000000000000000018e2bfed71aa9b11fefca2f0db8bd9b8c69540267de50bec4fc90a6e9741891465c9761d19282e1100b3707eeb598b31d411519f2a33b07f65e7d721950e0f0d5161c71a402810e46817627a17c56c0f,0000000000000000000000000000000006cb218607a1f66ce361c89fd20edc3f00421611adc9aa52ec35d45e023174962c863f740ac36c984c2b466cfc4827a900000000000000000000000000000000152b22d46e9660da8b1be4c5b14da613731e750ff7eebaf879f7074bf3c33e1528a2c8479e0178707e3855b49f85f045000000000000000000000000000000000c928cf78cee2c8b9da8215d33d189c5636df1e8e9bdaf143aba7ed40f29490ca2328b4a20cfc56f62e4ce49d9e77f14000000000000000000000000000000001574b7a9c3931933160ad4eb17400b6297210db47bca034bc1b5d17a0cb8c41834636b9123e625e5eb0b01738cd6b9af,55000, -0000000000000000000000000000000019efd37727dfaedf697fcda7a59847dbda8ca7cdc92f34e68691d682e20ae6545ac104d6660fdb8f64a051e69298eae8000000000000000000000000000000001225ace0fdce456dd888c9672503b68ef77b2d11caf1265a767a6ea14911e3ca03fc153f18dfe9d95e0cc68b7b8a3a8d0000000000000000000000000000000008a6b059c1c4da046cc0b1b5d7f33270aceffa607daf6d0d078c06f940604e1a0b4adf01a4091306e3c7eddcf3d95101000000000000000000000000000000000f79bae5260a2f114ffbb9273f3049d3ebb002500a57ee0a7d157d86957f43f87a2e026fb9892dacaadca5ee04fc8e176bb3f9e512311699f110a5e6ae57e0a7d2caaa8f94e41ca71e4af069a93d08cc,0000000000000000000000000000000003e17452a80996203fdc4037db072c452f9eb2dae689c77c88b299d7ba266d111ab2b9c4b24149968d72cd143a34fc4e0000000000000000000000000000000014a057d7a50c9b0f34712ff8008770080bfa671650fef43c82726257da180dfb9672b266d4c54d65fdc677d917e6c5b80000000000000000000000000000000013b452c980bfc4a484637b578be100753aee9dda9487d5ee5c017c689dda838fc673804369328192d780d60a9a3de0f700000000000000000000000000000000103aa86d1807de242a6d4fa4a49be6c91cd757df5808501acfca44940733c6a524b851ac962b99a9be41bfc8d6254478,55000, -0000000000000000000000000000000016d2b73eeceee17d3bff3aacac9df9ac1c4248d9ea7d6a503a757f7bb22fa6970bb6f5cb5ec154785f7252e1508b382e00000000000000000000000000000000081edc68bbd8db7b10be06ee23d090bd54f9ca07ef24dfed7df7bb05f8cc26e6889dbd40ea203fd5cca5cb588199f9e40000000000000000000000000000000010d3478508619ea9493b4330e2fb9150024cd32dc1378f824788a884a4a30fbf39c630f465557bf0c6d69b4cbecf89f9000000000000000000000000000000000f20c9b134db5d8b7756800c031bf5962fc560ba95d4bd9157b16179f1a37ae08696a2be455ad8d018aead6adcc69b712a0c988d97e86dccaeb8bd4e27f9e30fad5d5742202cdde17d800642db633c52,0000000000000000000000000000000007c616472f9ac60f749979c6f870b587425d514395ed07558ed287fccabc77f0c90872f3885d0780bcdfffedd124eb3d0000000000000000000000000000000019531e9c25e84a2a968a85d9f1ab61a372ebc59ba5bb7a2bbb3c0d6e4c9d04061b28fdc719735e97ccd5f7243a58cdc70000000000000000000000000000000007772d3cff12bbee916a6569edce0c6dbc2bd8a794919a4dd7bc37024c8273245210511b8f6da551fe626b7b840833f300000000000000000000000000000000186a3e858a83a7ea1bfdaac65c2df1076059aaa193961559792373886c68acd2f9fca61b166a0ee55084a6ea122ec3e8,55000, -0000000000000000000000000000000003dce67181d23af9729e9fb0653d7f79c890fba27de42fada93123e112c4a468fa889921192db8047d86e4db77c60266000000000000000000000000000000000869a1e39d42d9bb0cc0568fdad16abbdac3194af893ebd8dd8f8c2c3c855abefa5fc215412168acadc88e658e83f5570000000000000000000000000000000001ef139a75194f3c4b1378c2b66dd304d179460bac0a289405cd8faa3ff66a7b6e54eb7b8742a68150b1e098630135c40000000000000000000000000000000003892b5a645af916be2c6c7fc0bb08fb5f39341d3c68598940554e1be11e1be75af920db0c8710ed13c78edbf683f17d0b299c14892e0519b0accfa17e1a758c8aae54794fb61549f1396395c967e1b1,0000000000000000000000000000000008adebaa95d10b9fc0f1a1f0d52dd6741517d2ba23e3f9e7a9221039684ae226ea602dbb50df0efd44b2b5bf7495c0b50000000000000000000000000000000008e276e78ead2473602d37cb9f2f589f9c60514a1fc5c215acf487bf57c935467d29945d3d671b41a8e47c9495dbf5c9000000000000000000000000000000000fab06240cb8cbe9afcc4ebebde50c2881e4bc4d4f2ed09a1065e3620e6344fb3c5f3019250ca4edaeae4902abb7400d0000000000000000000000000000000003fa6c48ead374be1dd45c8417ca8234c15ddefc5039151e6cd7fb27f866e134cef2f59ac9b2ec1b26896eaec9213549,55000, -000000000000000000000000000000000264dd4b477f5db65edad28c7153ed919a863c5c5661e0125c5429b323e055fd69c33142dfc6ed9c87082e2be4675e1f00000000000000000000000000000000046ea088a2ec94d3a1f1f97949f1ebc49690c453d316cc46534fa253b34b30323b6071d147d64bb94e02fb4db07bb0c400000000000000000000000000000000013692a33bb1348486eec40a9e93a4ea3810c7b4d3188cd07e235a2c898aa87ee0d17682fd24f4d978f9fb028fd26e2900000000000000000000000000000000115f8b64c00cd5cd344a7b5edc0ef0bb85a3e8f0f9dfb28f8ffe12db3e0d222c2d45dcdba0fbdc161c5d558bc71aa0977064d43d6802ad4c3794705065f870263fef19b81604839c9dea8648388094e9,000000000000000000000000000000001412bdb48546014adf3c4eac4dbe79ba700f90c8030b063828fb01be5977bd73107533a4e8030c8d9cbdde9bcf10649a00000000000000000000000000000000126d3e1006abfeddd810cb1e12c898cf5f543e414438e600ce4c94cd8dbd1e17c0f3b9831add397feda74362eeace6fb0000000000000000000000000000000005b3159638afa34f219513cbcbc51567b16fd5598b85e6ae0d232021133cec25a6269250df2ab7b5ace726e9e2fbf0b0000000000000000000000000000000000c35bfdd1c10e903da6d41e9afbe65b0cd66addd7893fde41dfda8e543a93938cdeab52cc9bbdbe61f93d651bd1c923d,55000, -00000000000000000000000000000000014c83d58d90db4821a0411fab45f83fbc05f7d0d7a67ce75da3ae568978d15f4c1886c6fa6086675c0045efb30d818400000000000000000000000000000000001e68691123451f4c3df6dae62c6a63855ec3597aae33a8a10ee274e902e9aab1460cc9c79726312df0ee0ce90c8d3c00000000000000000000000000000000018a39eb3e3c6c7fb8ee304e55d15e209afe2fe278dda93552a7b9f51fbd778da1502eb6775cbc3f832f8320fa0686240000000000000000000000000000000017c15910fad1ca5749aa82a5a2fa98b0ebb37e92912547fb1741f18c34e0d5fc3a307b928636c25f0320d71cb9d31062686285a0e22f177fe3adbfc435e9c1786752dcf3c11b723539789b0cdeb0647b,000000000000000000000000000000000bcc781f144bc148687875789fd8c54dd820170984b6f8ae75855f7e45619c1d2ff85c330b7743e447b5fc831dce9277000000000000000000000000000000001409aaf3c94c9a6b5123c82a7f311af7c2f60e9b197d49fb5b010f84faff972151b383a83c106de43454f8097005f6c800000000000000000000000000000000064a91226da8b9cb587030f1f4afb0d422a51e4d55212f26c621abc06fc0c57a473a9be75518a5f4f9a7f8d4aaba69830000000000000000000000000000000002cf239343bb77865ceabfcc1fe34cc9be4a1ebc3a70f16f8b7cb84eed5843524f95673b01466d6cbb0d8d9dc00793e6,55000, -000000000000000000000000000000000fa96d9fe01c18732e8d6454df9bb1f482c4b9add837ce9c354c72d49c2d44ec694674aaf0e6d6a095cab7ebb57ccd9a0000000000000000000000000000000001f8ffe3fb7e9e311e0f6949c07c26a0febb181e37b2268bb5e125fc3a100323740d1ebaa5e635dba3770fdc2ce4ee860000000000000000000000000000000012ac42095fdb677720ab3f14bf0afc55c95b43d28d922a5f8cb0bd841306b978751d24546e3a6474976961d0768f29e9000000000000000000000000000000000baf9804d99039c9fe966a696c64bdacc9673b0906b4deab108d34fbbaa3b0905d50892278570564017b96828c7e1ac93176b6724cf984632daf95c869d56838ab2baef94be3a4bd15df2dd8e49a90a6,0000000000000000000000000000000006bbdabfe104b62d22e78bc8f3446a86cd5f10c4c5a54501140768b55a7e6940b9952c9a90a14d8fdc7c04600195cd6500000000000000000000000000000000172e718c926cd393bf303984518432693c304a2758174dabba303ff4c0289b5bf5376b61e8821abab322d53e88f71d480000000000000000000000000000000000a2f84fbdb5b05107a0a340e81b56ddf6d03c23848448f841dc44f07cbf8a575289cf6d53986f581fddb0f2d07e38d70000000000000000000000000000000005cbc10f143a9a1fe23f670a4c47d385f5c7069d8c46580322d6939122b2d39d185d6a8c2e51e88a1d40fd2e82d08b8f,55000, -0000000000000000000000000000000014ce6d88a7c5c782562aa101550f1af487296adebd9dae8252698ba04fbd58b92e2216de6ffd474d5992f97d9f22800d000000000000000000000000000000000ce92a04f5c8a99ca0e93992448222519fc454bda5d1d8638a7bfde968386e4ba0dcd1da59cd81d4c4dca3e584be0275000000000000000000000000000000000cb570796f5c8f7b8aa02e76cb8e870d3365fe4dce5df07ec286a0a821f922b4003d5b69c0f1588206d9544013e268c400000000000000000000000000000000098056a033d9cdae86aac02de3a444471854b909680719154b44d4f55f30087294e39e57643c692d6da725b859239080d76db3dcb659eaf6c086be6b414a494dea4bd30aef8450ae639f473148c05b36,0000000000000000000000000000000011769e191fe258ffd1922295a9fe877ad5a52fde6e343730f8f5ec6cdcd584f8ed1dbe0f55b5dd81f5f78b7437f02abd000000000000000000000000000000001253689089e9192d10a45342214425de36740c120e49f596d24658941ce2b2ecfb50e879be0125e3d159088f88e234f10000000000000000000000000000000017b642d1b5a953f47fff8f0649263f16f41a0ec0397d5a81571174aeb85431c352e2bf6bafa6894d2e6cdb5eafff16d40000000000000000000000000000000017b3438d0ddbd2ace1e63802013b5bac00d31889dcb2d9653a6f6412d157aad2fc45267322a62129087380bec65ec169,55000, -000000000000000000000000000000001214aacb0a5e6b7a40369a83c07fa8cf1786ce7cbde2b5a501d9c1292532df7822d4fde10a31fc0cecce3a7cfe3311850000000000000000000000000000000004f9669d8fe4f884ae93b2505710e6e45b19b7aa5df8cdd811f09e547efc27d21024cba05e2dc9d057055f30ec72d9df000000000000000000000000000000000a852b821b31cd27eca19712a636aa05ef2cd82c36ac1c2ca240edc7d0172b42a72c42d3cba583a5b5129ac1c9486e270000000000000000000000000000000007bd8419e791a5cea04993509e91a980d3ae4987a5b322400b6e4a4f2b636891a1c7ba4de96b53426dd556532403d5a39915646de2449b3cb78d142b6018f3da7a16769722ec2c7185aedafe2699a8bc,00000000000000000000000000000000089a07bf63b8029e0506393828d8593b94b73c750815552f9a3c74ef7470b5810bc27212ba02ca6fdcd97e1e28a52a1e00000000000000000000000000000000051a93291d4b912f0a594d45c0264a9073663a9ec75e6ee81e13e79383d96e9330bab845fd1e5163e5b28c41c4a854c40000000000000000000000000000000016610bf2b2006207046e489294a132937edbdf95caf508f0df3bf8502e641aab9c44903cde75cff3c1f86873e06cc58c0000000000000000000000000000000005d33669fd8a6256dc55f513bb93cce8bae62a593eb8903cb7d7902a7727efb8fb4bb2e5058441c30b99f146ff5394c3,55000, -0000000000000000000000000000000005ef88bf38b2f998dec7302cde829076e6cf69df23aa0bf6bbb39fc0d3d8b5eafba74efb928b1de0eeb3d86ec82612300000000000000000000000000000000011f47e9583997b19c36616e4bf78d6ddd6d67937f493986250ff02aef6e6e7ff074559af2f20a5bf1d67158e4a199cdb000000000000000000000000000000000007777c8eb259a836e6459b7bdb642f878d869fdcb31b105d01f280938ef5377f2775874c099dcd394abe70f17d595b000000000000000000000000000000001607379d1cd34e2d0ed765a339b21433e9aa489609b92414c6b5a05d796085269c288d739717def9db3502e0550860165061073223f066e35242772385c67aaefb3f7ea7df244d73369db1ea0b208792,0000000000000000000000000000000005aa23543088a9a833d773a71275e73fc3081e13c907b8a04a330df7d6c06618fe69e644e0ee55869e364d3561e40f300000000000000000000000000000000010eef9238d2c520f32243f07161f3e35b15fc949b9401baa1a9c5df7d50b2cb3bdd237747735b235862bb57322fd9d090000000000000000000000000000000012dcc16496c95e39ecfd8f0514b5ab2569d89826d957478cdecd4e827095034e974039b37e767a0f25bf057ed715aeb00000000000000000000000000000000000d0593865fd2172ebf1b94c7511ab7d433a276bf833515146adb6d79b6e09d7c18f4c7f4d3241c14d01a4ad0f31580f,55000, -000000000000000000000000000000000d6e3068c082b68312141aa68f1540ea1415e93e7f1762b6f06ff408a9995542da1c727a13355c19f8f418a44de1a95d000000000000000000000000000000000dcfcf2ab12b1a0e521ab402aaa4d32ff649a5a97892eb6ad98487c3c73c35601c313b8130ad12e9098d16eed3bcc2e00000000000000000000000000000000013777b1eefa4af03dc44e4e054eb7a3a980a9c55644900b80346be84b970e1754d1f4ab771adc9249e4accf88a23fb400000000000000000000000000000000002f53b231f1209c6f8b52f99a78bc2147c951ac89b341495f4a60a6572985ce2bc823625099ec214bc9ceedb2deea3fff396ee22209271ea0bda10fb5e2584e7536e8bb1d00a0dd7b852b0aa653cd86c,0000000000000000000000000000000015785bae0c27680cca2097ab52306207a61ba9903723f574091ef5e57c2e871e076d7f46e6e39f65a01e183e7bd822f000000000000000000000000000000000071110a384248664db46f21d87b455a3ad3c43782c68304ce17f52cc8579fb2e3378995d6eb3b8c97665e5fb7de665fd0000000000000000000000000000000019153a01c2b3c5d481474a71e5c67f27fae3232a0c8f1655ddd4da6b4c79870bfb0b6beb4af8c54aaf7e9251ad41d639000000000000000000000000000000000c58375439a93e0763467c6a11dada3e579ec53a968c9b9c1a446cf3224ea0c89c9ec218a8b78de91fc12f087e722f94,55000, -00000000000000000000000000000000161c595d151a765c7dee03c9210414cdffab84b9078b4b98f9df09be5ec299b8f6322c692214f00ede97958f235c352b00000000000000000000000000000000106883e0937cb869e579b513bde8f61020fcf26be38f8b98eae3885cedec2e028970415fc653cf10e64727b7f6232e06000000000000000000000000000000000f351a82b733af31af453904874b7ca6252957a1ab51ec7f7b6fff85bbf3331f870a7e72a81594a9930859237e7a154d0000000000000000000000000000000012fcf20d1750901f2cfed64fd362f010ee64fafe9ddab406cc352b65829b929881a50514d53247d1cca7d6995d0bc9b2f0d3d4cf46265fc0f69e093181f8b02114e492485696c671b648450c4fcd97aa,0000000000000000000000000000000004c7495c03fc3fb4d0fd4e0e660d6424de9e060eac72eee3608ba95bac294a3a62d246f42dcf3b575ee1cf8e20a9106100000000000000000000000000000000091140aee42a9dc875f87f3ba29beff95138790140f8bb522c6c15281b3545995f9c13b0b73ae691317e674295db6526000000000000000000000000000000000a945a215b2861427e0fbbfc6fea04e79edeaa1eb87df5db8e5e017cf98fde7b8d5a04a1b2129a4aadd2e3924ecc0bb2000000000000000000000000000000000a43f8d3d92a03b7bd4c8a34ce31729ea0b8e6b051c30241dca2db31a02b6e537071a914d8f0876f944dfdb613540c6d,55000, -000000000000000000000000000000000047f92d6306bed1cb840f58fd57b5b71a5df7f86dbfa55a36636cb495e08715cd57f2f3e7cd99a1efc28b1d684de1cb0000000000000000000000000000000000f4eb02d687a1a6105b4dbd740e2c7924689d558e6cbfee768dd303cc8dd0fd887f5eec24b54feccf00f473ca3f54ad000000000000000000000000000000000edad68c4d536912816cf6ef039c3dd0535dc52189583270b3b038e2c67b213d943bf384ce69c4a9dc526d7ef309f25a0000000000000000000000000000000006ff4a6b5129ef026d1d5704bf7fc0b474de92b5cf39722f165e73f4e7612d6d3bb40743e4b7b42d0dad5d5d6a2d4881915b717562844d59623bc582f1a95fc678cf0d39af32560c6c06e3a74023c89c,000000000000000000000000000000001821e14e70e12c7caf2a1ab651eb81dd61c4e1eec9a02fe4124abb865a7029e066f03b62e6ecfcf0fbae5151272b524f00000000000000000000000000000000044ac4a7399d6a67e7ee8cde3f5fe20b0a745462c870926f0ce8554061eba5bd62a8a08c798d8bfe30fba5567d47c7ec00000000000000000000000000000000178b8f061ad9282b3b2057f20c115c91df994ac40aacd05b7669e934bc7d650a0cd88f9fe17d7b766e34bed587ead58200000000000000000000000000000000188311eea279ddcf75f8dd82643ca3efd560ddbe6c8f2696cf7da03e65cc90d97b9f9ce99e29269644d8b881e624cca6,55000, -0000000000000000000000000000000017b32e613cb38b41dcdf3c8bb9187d731546977fbffd79fa7f66e3d6aaf9e1af6eca2fcdc260c8f90818d7148ba2f4960000000000000000000000000000000007e4d26606a47c874c20e8480a9f5815e5b577bccd783b775d10309eeb3d2102c7a0abc3324679e44362f09e7a4ada67000000000000000000000000000000000cb6f12ac8b49cfa36b957591293c87b21af0a949c55a28a90ab0fce88fb5cb7645e20ab2edd284f0ad1377dd95ac10e0000000000000000000000000000000014c96b5dcbd3150eeaea5c2bc27750cf88b30a91933a3233a4d1d9b357a80cc20d135e43a344e718dff5c79045c31f86d5c1c9fa11c36b86430cbb1f3ec10ebbe3787d0f5641d6d7fb96c810eda202dd,0000000000000000000000000000000012496dd3c1278b55bde81f6944c4bdb71869f5e5e21db7b1425ea32fa1dbc8c301e7f5e68cd7629c91650265d1361e690000000000000000000000000000000004a1251591efdbdbeda21eb89165ca61a2e090a73426451b6933d939161364c4064a67a90f859a7713fb6a9c5321d5a200000000000000000000000000000000163bcd07d030fd6ab8a8e0bf39b136dcb34f03925c3fdadf55e94a90bfde0ecde5c51d2f4d06954aa6a96c913f2ab4610000000000000000000000000000000016dc065a852ef9e038d93cc583b4a71db9b96a7e7a819dc530598f1ae256368438f52e4b709f15f56279b9c7f9db8785,55000, -0000000000000000000000000000000001ca1141ba9542c56de8991b313c6ae42fcecb6751b0b81b8cb21ed70d5008f7ffe831766b89880a7fa6dfdb09a2cda3000000000000000000000000000000000e6766b17db165bba564ac63ab88d3f8f5eded07a40b48644e60d3223d30458e7dabe404cab8d6f9fe135712ef0b1a43000000000000000000000000000000000dda3e6c87382fa762510e5cac721fd2b654f002f5b9a3767a8c6d651ccc582e80e3f68d6913cda30f9f51ebcfc7c98600000000000000000000000000000000059a7dac5bb6b504f2bd603d486700fe22c14f25254537b2c9079c2b45d36c7ce56854c5699cc7649b533194f51a9045c00eb20fe7c292f3ad820a074d8b3d8d24506612752d8677c2d6ca24f556cc45,000000000000000000000000000000000a2397fb3a3891d1703eb2112357c5fb8acb070ba9f3a39050be6f05b49b8d2488e94adfbf849c8b4a42e287077e9fff000000000000000000000000000000000cf2c02a97addbc1584091e411f9a07135f1fcf989dfc8ae29155ac90b214ce20dc11a1fc75dfb697694891d934abf0f0000000000000000000000000000000018fd4af647bf0456aff9ef80969613829f8eb837205df552aadca46bc3bf9838e0ff2515d3fe869f80d78e2357091d8b0000000000000000000000000000000003c5671ea4723498359f29d49ebe974099da3dd59d21065a721f7a4f14dc7fb1de3a67a707bfa4bad7058312632c6113,55000, -00000000000000000000000000000000090f4b85961ce97cf7f99c342d3627105d790f611e19721a43d8a0febd67ae393d77a02b999108efb56f0397dac22703000000000000000000000000000000001112f23595d1613c47486eadc37f9b1ac3b3c3973b3fe964d3b67c3996fe2eacd9df5c287b0cea8e9475d146fabcf9e70000000000000000000000000000000018f46f7ba3c9af34c1025c2d460f0be966e68944928dbd55cc7fe00e5def598d80b0e3801e48a74963c974ab4727a52100000000000000000000000000000000096845338d5cd2ac44e097607d6a1a05c241eda1941991ae9edbba965d9029032c46da7218b5b2338e6c58898bc4a820f661d7b30fb11bef70e15b257d7073885468a380862202b2d705a84827644b5b,0000000000000000000000000000000000676bd7ce63d8b58cc1e5399ced9b495baba4cef9503c44760f92d6d9e092d6d5308fa88144491eda6c571a8c308786000000000000000000000000000000000605cebb4c20bc9dff0258f75a825f55f23a32cd0804dce56bf3cf2f19a3504f0345e0f1b839d4d5920aab19b363ae19000000000000000000000000000000001512f95f60a6dc79dd9261c321328ab8e22ff314e7582d8de83aa3bf280805cba8ba6d359a620fa6f0564396a45ca9760000000000000000000000000000000005837474ba78e0700c77141d70af1d8fb95a97cbadc95996faa93c2e81b7c8877d08d5287f83219a24bc0080e630e39a,55000, -000000000000000000000000000000000aafe45ea7cb8b450a51263eebc28c1ded662972bee512e24fddaf64f43b74b66032523b3b104a4e9f6b62394436c6710000000000000000000000000000000015cb27e1fedfba2d1679f78a388f90b22bbf3e7d090f0ba972fa8e72f6e31c446f628fff929953712ef6e425d16eba5c000000000000000000000000000000000df9931893cae713042bf722db6ce394b6f346587278a154c271d8511e690417eb6dc47efbcebb7c2fb9e77f1de9fde800000000000000000000000000000000106ffa395ef170c99bb5742428ae88fa4fd7a94476985c099e3b700b7403d083281fb71a19640c6bc2321e27bcb33fe2346ce87c847376c8967cc18297e6007dcfacb6424e1d273930f38bb0e88fc5ca,0000000000000000000000000000000010b2a9b32e431c11ceb474942bbbd6915a3cff64a74d67570fadeb7447c5abcf1bb35c822d4441565322ebf75e61f64c000000000000000000000000000000000b75a0212232af0a59440482a1f953cc29bcd35272ef407925eccd70c1dc4705dc1e97d2da604996d3c52155d05d77500000000000000000000000000000000018751bc59f5907cbd7f1d503bc5aa266f4109fd3133a1c4c2e58e4a17250a40053b4489da4825b4c368b0f4947baa6240000000000000000000000000000000019b41fa1af9488596b09c587fc33e044d51674eb6087c647d5a762d85e38a587eb5482687d9346a1a701bd3a8bd36a61,55000, -0000000000000000000000000000000010b1f8b1c492a56936da905b8738affba6bd29ae5fffd40ba6b31325181d3b489a81b23dcb69f6e71bd29bfb388e5a8f00000000000000000000000000000000116a115303b4774da59844e457844232d088062d920db67b2a8450a194be7e5340ebd4d106454fd9a03c8f50dbb1e119000000000000000000000000000000000eb521edd61b38006cffc43ab72d395d669dec196846fa4d6d43521da6c2fc3bf0994ce7556a3cffec7751b3bc5703ff00000000000000000000000000000000073cea36eccaa1c78deefb6029903c2b6598301bdefa9759719c3b590fcc5a6a4d3d4d19f552b33f4a3126a6e6a8448639a142c443a666499a880aa1cb9f523411bbc8e5554de099ab485b6c2c2e57cc,00000000000000000000000000000000054836eb7ef9edbe914bc16d1498e0bc3c978bbed2518802c2f8e1c0b59fee482cce0ae8e805c33861d4cd595f6b8bf40000000000000000000000000000000007dda36d55aa7a890aeaecf2528a390c98d9ecfc8a5c78c2a6def30de55b90ae408ab770cf9a9a4663ba601c4f5765a00000000000000000000000000000000007ff7b24c8ed9fca572069e72b1e93978cea87a0fac7ba60f54aa573d881f21b73012b010e9c0fc9324aa7697bae0c4a0000000000000000000000000000000002d9773bf294efe64021e755e4dd2936a5060bbea5688b6369ffa3b94eadcc58cc3986c74ff365301be1e6c785939b69,55000, -000000000000000000000000000000000e3925fa085db73c1e67b29ae90f8773f83be5ec684402e8e2360ffee8a8368911e584843e42b0d470de78591df6ea6300000000000000000000000000000000075c7efdeeb16609b4a47ea442af4d75238fb7534fd96cb236a7886809d6adc2b62c8ff72bdb041bc51c1a71b68219e300000000000000000000000000000000088b4eb0dd185e51b737d797334590e982b7b0a5f109fc7d0524b2465c2c0457964eba5a6d2d4d99fb628f21f15a776c000000000000000000000000000000000fc79f6b38f3356972669290eeadcd992a22bc1191606b663a1e148aa58db3938f0fc65e536bc5811c50d9c7f03d3e372c01b7795c2d16b5bbbb1e107be36cc91b25130888956b0cdd344de9b4659447,000000000000000000000000000000000902c1082ff09bf93b91c9ef5e447bd6832fec9297cdb065f11fc5ee626e6e8834cb5d74775c586609a0394e6114e8820000000000000000000000000000000018e414a40c27430b98246fef556e74dd3dd7adc601e3c05b79f8c29169780a173be9a725df3318d71b6e82abf97930bd000000000000000000000000000000000f924fa88f43c86ec98b34379b9a649c7564ef0dc596c95df19522fd50fb3a37cae031e891a7a7aa6a5e6a9062c3726a0000000000000000000000000000000006bd3340412f64d02d0cb3ac44d1f31cdb1906e56dbfb66d86b60a74cd26c1e241963fcd8bba4109c428db0bb083e81f,55000, -000000000000000000000000000000000b87c47605fc060a8e3677e84ce9d14b9309360a13c80d040c625fbf0108f829300cc1fca409a0f9c96311cd4a9a21e60000000000000000000000000000000014c4088f1e7935cf6a1d2475b84497ce6a250ee2c0c991fe51a2f2836388a354824b02d9cf215328dfce3f546713e21100000000000000000000000000000000120e59be3ecf35674eac6cdc559599b273f13f28a529770fa156f8e519734c451eefb35023639f32049cd19ea0d945a3000000000000000000000000000000000f97755b62a8cb8f861ea02c77819f0b58181aecf612d92180ba9b475f0b4888b922c57f6a1c619dd5514620a1cfd9e2c712943d8795a6104f024b9701c70b09cdee9494755bbab0576e2c7f7c9d4828,0000000000000000000000000000000001415fbd8afeeb5796460a9095f14a8f3f6fe0374d4cc4160f030710a6d4d3a92febcf4dad770de3a3ba1a2efbd858210000000000000000000000000000000015792220c7e53262b56224d230a8a4b32019c77548704ec16da5ce167854305e6cdb9924c248f222d6fe95a8383af7890000000000000000000000000000000001694329d8e0f41256b703a8bb6548f1d9e0749a55c124c9b60361b4cb1daee24fcf272327ba598022a92815764fc8570000000000000000000000000000000003350658842c5b6fc5561a14df27d950a00c5bcc13d6d9d014bfd6dc95ec1a030594625f41d439b90b05275a0ffefdb1,55000, -0000000000000000000000000000000005860cfb6be6720118623d2d8ba05e686df22744b948421dd3cc1b1691e00d9b5d00d00195b4acf7a7b043f764f3f1c70000000000000000000000000000000012632a3313dd611e8d969bddd556c2d79ff387603462ac78ded3a842981697bdac34ee6f1f4744ed2ff16100874ac24000000000000000000000000000000000112b94c317586e343acadeca611c485c3ea172bc10dd39158c1e678007130062a921b53826d7be6286963ff822f1066c00000000000000000000000000000000040de8c0dadd2a6c2a7ea0fa43e1a5f2f5a6be3fcb0de6875d8cef1ee2daad87125d12f6869c4dd3d931b296f1df2fb3d4d77f6246c57d398c57848db8d3f986c475a41a23d424cd3cc2b362c1b99f2a,00000000000000000000000000000000054c6cb26c8b0a9a4700e0b95348e6fb1190c577eba03a44e84fe7744c543321d02c4d8f55c03f984b44ffbd899ac53a000000000000000000000000000000000e7ab8da5d573cb88a78f6a6ad2b307bf867777f79a643b6ec89d9cb208711c85d7d2cf8f8ac69a8b322000fc7866024000000000000000000000000000000000fbc5926b9dcd9e4d1ca1a2b43dab5c98aa20b37aff0868c54441de44eb014e5283010642717fafaa95000f4313e14840000000000000000000000000000000003671ee05bc20bead72f2306203dad55cf20b13d3bb2cca079bf4391411b85ed4df55e1426645d73b6935889d4450c58,55000, -0000000000000000000000000000000006fcd2c4fe848e9462ba1112baad39031c210952adbdd06293a622ffe2d1c6e4fcc8773ec8913717018b97bcb9a554fd00000000000000000000000000000000130a97442f3273b7b35464545e7351faf71ead9b8996c63889a45945ed82bba29bff5014776c6185219a5234d8475c92000000000000000000000000000000000491d571bac5487b866022a0714be11b38bfb296233845cc434a50be1d35f516b8c6b046fe3d0a8f4f95ac20eddea01b0000000000000000000000000000000017e34b04e6fdf152c848f2432b7bd84b3dba3915f06eb77efb8035750aca9d89e92e1d1bc4871105c440d639e8d8b05541776ed9d1029918af4c5113a6110139b8bd7f938caa204373a28ddaa51430eb,0000000000000000000000000000000013fdd394635f42a926a2324b8cb870b5995772ef4e25ebc1da41dc5bf724f747da8d95a28dd703b5ed65ada5555c8b5b00000000000000000000000000000000118fd550962d1de8f1e60c312643ec7cd306f0bbcc932739270595537c8d290ca7e20b962fcde570bd2ed7ea43009fe70000000000000000000000000000000018b25fef4b75fc7649a489d078311dfb6da9909f472de7bd9bee9c3ee353f345c83119269ab797fabdbede41e0fe6169000000000000000000000000000000000b7c2a73741f6944ef4ce8fa20b2900612645c224818b7faccf6597827fa07f7262295f42be5f34a751a6400495f7eaf,55000, -000000000000000000000000000000000f1b8df4e8fdfe32eaf227f5af9f2befc85073468f10b81d32d0e126fe2b0cc8e8adb8afcac73213b6ed95e8e843b97c00000000000000000000000000000000004e3fb435ae0fb2d8bd091f250aefe5922b353a64e16abd75627737f3bc56639f8b40652cae69c73ff1969925b0afdf000000000000000000000000000000001003aed7cfb00efce49d6b1a8eba27df87479a4d37bd7fda6121549483b669a1a761204b0dd28262bf27e5c8e180540f00000000000000000000000000000000114fbca7caf782b3296d0b26b4c362bf50acaecb8bc5726b2c99f904ec3d092d5d40991d0d30c8e79fddaa45f04a75d3fa64411438542922a7bac10806efaa633d31d37c0b223314a8b6221155b9c425,00000000000000000000000000000000177d29de8a81db2e515d4241e5f7e3d35de22bbcf9aaa616b057cbf2dab57ab8d98213cdec82a2034964f3e1def8a4e3000000000000000000000000000000000a0cce8113eecb064a60ee2c470dfae8b3921f8da2c7ad8dc918b355ff44542b007add28a44848fa8d8f8671617431ff0000000000000000000000000000000010470fcc723286327e951e758fd0474de394778d0c1ec5fe6f263dea1957c60f05dc8f9d82b3c6a7d73b3e783f35ade500000000000000000000000000000000098a6ed331f03da7ccc9148f07b19b132152e15d9fdaee5cc092524b33795edf2b458b4e8383c5e29affd3f025094033,55000, -0000000000000000000000000000000017faf481fd4cb0c373d21d7caad40e93d9a86e62d26136892fbcc6f6e48205543aff00c45e82fdd1d3e0e733de91e7000000000000000000000000000000000012e14fcb9ad4d9d15347cf004745ed4bd92097eeeb41c4cbcb728a234616363589d8f5ad4cbb61d31a8aa27627723c7e000000000000000000000000000000001513dad1ff27e053902e779e35d04cab648939317830144ea775c435a4b55e13fa2fef03a1256abf5c187487c25a774f00000000000000000000000000000000139da29de8587c7d0ca9237c37a116387385e9cea453b9e2003a37ede7aa0a3f4c1df55255897f5975b662be33622dbce7002f41c6acab677a0ad023bad2a61b11c1b7221d944018b5ce60bb61e87e96,0000000000000000000000000000000018a1f1a60172a65abc8f2d855ee7510c1e0af9bada084325027bd493ae86ea2c62c15ace7f63562a82cb80ee7095661b000000000000000000000000000000001736b977fb52eb1b466cec3d42df7e89047784f0e8362eb6425e37adb1e84d0438f5a6e82c7b31d59b0959a5f4aaf9310000000000000000000000000000000013ea0f849830f8e48161e840295637d8596b32eb576560289620b797b14bd395d835e8140b69039c904ef1d07a82127b000000000000000000000000000000000d7f58873701c138cb7e18ffc36cd0e47b07d70448ddd9fdc4b947003fb29cba0775916c752d531e527ab744c277e5da,55000, -000000000000000000000000000000000c118b147ee3489f30c6ecc0256a314ab674110588e8b69ca6d265fc270c3e5b767817f861140cca5d7c6be4012d1ffe0000000000000000000000000000000014800790654726959fd876b035bade0da744fb36ee5b304f228663a531345120267c55ac19fd66022752010e5bea7cb30000000000000000000000000000000000193ab7ac2f151750356b6e178557460c9c2672b1736d19a20e3fa28082479ca60021aa68edf2524f1aa826ee70b65a0000000000000000000000000000000015cee9ac55ab45abbc57d0ea6ec9ee49f6c59f6b94f99589dbc08ee877d3a261ad77f5473fedd72ed7206647eeafb6eac26e55f09b787c0542878e4d720027d9ea465f829a4e0164cf618c5d9cde49bc,000000000000000000000000000000000290fb3f38937ce4439ceaa21cf3b31db8a22f9f5ad9db0fd7d38ca978192bc05d41152f8f86ca7b2ee0bb58e125f57f000000000000000000000000000000001775913fc24699bf08f25fb946fc6527178ebb821c654b7bc69f6f86b5168fc42057a5d3bfdc53b3d57fa1ac05f7a0930000000000000000000000000000000017b9043cde8dbf500ad90463250a49f56b35713f2fd9a35d8391fc36c78c083e39674592a98cb857194ef9e73a62a397000000000000000000000000000000000e5e62e39433d443e7d2d32754d2ca2556cf6deea45e5076ac040e3d6de14e9965c53f8c65bd98ae7d17ad3a26f3accb,55000, -000000000000000000000000000000000ef203fab794a0ef29eb2ebf00076134e5932e27c99d6d445695b9df2afe7563602e318caf5d44724a21790ca0ab0d180000000000000000000000000000000013b9b1b1d3e98b61b0f1a0ef3a1a4ceed57b6c01849a4ad66a86332b3d27022cfccadd3567e6709d2de5b23b23dba43f000000000000000000000000000000000c1fbace49684f4be32ef6178ac3a95ea3f50b11494340fb73dc5391d50bcacafb3bf0f2631fea9c4ec47327d644489500000000000000000000000000000000040f82812855aa3e3aaba826d5810c1049cf44e86e44e23cc6da437971b529d2f2676c73e1fb9da52640c981fbd710bebba67cc47e38a129ab1140fbcf0386ddba2feefc919aacdce6059a27a1e2efca,000000000000000000000000000000000d9927347a9ac9b0290e68143fbc6a5f4476604c3fa5ae87e729a03ca055e4c6543f9245a4592e195180d88781e46ac900000000000000000000000000000000175e0ee8de4002b18f32f70f1bfa9e0be87288cddf1c436428c2969884112bef5db19e041cbaeb23596e25cabea3777300000000000000000000000000000000074ed9e981818102b9ba818d478ba27033eb38e3fa19cdeb9f5820e59a64dc451342a160359c54bc8ec7d866b62080ef000000000000000000000000000000000a853930020bf01e20816d3aed242e00792b0d0e78fb15403fc3cc255f0dbd99ea6ae1d59d5978e562be4862b3317324,55000, -00000000000000000000000000000000060d7a718dd02b147c265f71eb136d1f31781b12a41866b4f86d7374b93dd10058c192cc0fba928373b1526e1a5d7d7f000000000000000000000000000000000cf29275373c0573ef22bf87919faf5444847203c7dc6d2e18986152cc294be04a5b1a4b0536797158113a15276c4fc6000000000000000000000000000000001016d5b9d4d200d7b4b7cc3836b85d6697fe14db350badba9978c7b56983dd1a7e572640ee0372b0a4e2079ff4c1abf2000000000000000000000000000000000f2768d104d895473ddf8c6b3cd0e7c22458d0037eca6365c766879a07c95037ee0de00d32c974d767080935abbe0be1705fb566367d9fc142c4194b0525c16672b843aac1160f9056ebb115e80d377a,000000000000000000000000000000000e9c290ba8a22f7bb3f7dfdcc9f5a221a5ce838d4fa85a00473a4dd830bacf583dd91a6a6f78d2ebb54a4c1bb217f793000000000000000000000000000000000dc51b0ae8bda6d28c51016764fc028258171d7c7646393228692aef7f1dda4a83e53553f63d6ba996d4c0a802bc967f0000000000000000000000000000000014ab155029dd35206811be9ca4efbf762a1673367e6b57528f79eb50008ce7c3b49a2d25da0ae68ac4030ab4bcc0daba0000000000000000000000000000000008cd743bb52e7908aa973c8518eaded75fc2858f4edb25fb7f2e09900f0abd3ac87e93cf1068bbe0c7d99619aa7a6b76,55000, -0000000000000000000000000000000017b9ca4349fecaa43ce911c0b256680edb8a0906ef5460fc4d2004579336df1e19560fe960a7a7cd74bb6e8272e08960000000000000000000000000000000000d5b96dae738db59cc67a51c61bec6deaeefaaa51e3259243fa4b142ef59676231229ae386ce699fbe18c4c00bf9d49400000000000000000000000000000000111b79f4b68dad16550a13334d09dc38336a75a5da23a17b5064e2d591aa3dab4c2e982a9f730a7633070504663a24610000000000000000000000000000000018f6d3616a7eaf17c805a88c9710039644d01b61aefebf76717ddcda6f4bb34aa15702de1e92bdb27b27f3409638da90f7bfd990cc4dac62a0d730f56b4eb1c1ad77ca9cd58b089c23c2f6efa00b7fa4,000000000000000000000000000000001746a449993b0684740630f3f0e46eddfa135371e33e8de4dfe553c78845399e63bb3da48798b35df48d27e1f991954400000000000000000000000000000000057e0fb1113968858981c9803166d8b3eacc91bfad320ea0e610fbc5b276da1b46d74fcc54183ba61d1b2fe6743097c90000000000000000000000000000000000b3a178ae3b739cae3e80f3f44db42d8c465a5cfe4943b449d4c3b7f4ad153916c6cf4fdfece14a00b271222c72764300000000000000000000000000000000041c8b293ded0c647f2e4d6f9b35304179b723c3e6e421a5cb103e561d1655b92e74877ce22c99f22a3700c3aba9ebb9,55000, -000000000000000000000000000000000aeb5c087644595d0912879f61959d2731ff55260c682ed2bc5fc55c13964ef7c1f70aeb55876d2264d558c31371ca69000000000000000000000000000000000e173848f4570525b03a2b2c86f4dcdb8b28dd6d18c1354cad31028eb1b8b44432c2346edaace093e3954c7fa6d338a4000000000000000000000000000000001949b0902506d111ef6318edcd7a58ca4d69f5804a028aee73c3786cb2db168c6a73b77194f7a021ae6ae43ac78ade340000000000000000000000000000000017c5e28ba6103d97e2f3d3611c0c78f06406e0da8a49ae29c7d460b52f75136920784cd500aa3593858b877697eb8424807c5a41ae2baa1e10ebee15363d1d4569f731d77a418998108f5dfae0e90556,000000000000000000000000000000001103cc395acf81772955bda38f951a81c5a6a476c0b5e1543616a5a7a7be22dd487ab2a8586524891300adec5225b4020000000000000000000000000000000003479a08e2811ae9aab0301d66ada470935984d7466201f3fb28c610c0b5f67e7305f5ad3514cec5f30b51d0aae775d40000000000000000000000000000000005ea37a6d20c1ad0978da68ded3a5bfcc5ad8fe81e39b525fe7d1f2b2b1ab0be7ada80173b1d0b7fe1e06ab6354e64b10000000000000000000000000000000008f2093151a285dac511df1755e99a652a1cad0af3a019650fbdead1421ba8e84afc9eb0a4fea651f365d72f031a0ca6,55000, -000000000000000000000000000000000d4f09acd5f362e0a516d4c13c5e2f504d9bd49fdfb6d8b7a7ab35a02c391c8112b03270d5d9eefe9b659dd27601d18f000000000000000000000000000000000fd489cb75945f3b5ebb1c0e326d59602934c8f78fe9294a8877e7aeb95de5addde0cb7ab53674df8b2cfbb036b30b9900000000000000000000000000000000055dbc4eca768714e098bbe9c71cf54b40f51c26e95808ee79225a87fb6fa1415178db47f02d856fea56a752d185f86b000000000000000000000000000000001239b7640f416eb6e921fe47f7501d504fadc190d9cf4e89ae2b717276739a2f4ee9f637c35e23c480df029fd8d247c7a7e300bcb3c740fd1f693d4c8915c4c46dcb627f6de6e4847f123623cd23bac7,0000000000000000000000000000000019f79677ea0e011e5c9a892a407646798b05be05337c73135cb771abf101f450bbffd08e125f077f5ea989decc009b9f000000000000000000000000000000000ed15f35966024cf1de2926108151e976dcb0d51b2736b0877d79de81f6fccb9dd299d14855f4e257cae33ab7455b95100000000000000000000000000000000125e2fabb5cc95c0a7890e9ff2b70102a97a03f2d11d915cf4332dd049a467333e12ebb27955c0310ebdfe2afb3173ee0000000000000000000000000000000011718167000f9b749f1615610a30023db4b986364da5bbdc4506c726624a073548a94307b282590cd8a43b4900a1afb2,55000, -000000000000000000000000000000000f20a07526a082e88630a0256d134a8a5e8ada07b1cead39ee838dcbb30904e9016107fcbdf1f8ba182308dbe0b043d20000000000000000000000000000000014fb7732f67abf60c03ac902577532d0acadb5f3db0d6397a42ba693526ad74f2c61a0195bdc9704aaaf12e65aa6d88b000000000000000000000000000000000018cec4fb81c85d304588d11f8b9c51f5a053df11463e5812a1b2e6c7144522ba36bb91adf219892d0007cee470032e000000000000000000000000000000000b8e52d958a12a9037e8be9bc0d5045cade2d6ea05c6e68462b3a30b5d4ea34e5fbad173761e4e216b2e6958c8983b28b473df5e282565a0783d23e65e283a103ebbddb5c884183cceb62fc32d0e9602,0000000000000000000000000000000005af8fd9e79568b46fc42b2c1bac62d115365834e509dab032f66425b7a571a4bd3bf702299d3c5f36c372750b3281f30000000000000000000000000000000018499089f306b3c9f7a645ca2f9aabc4e57c046992fff87e832e21e21875c6adaca050ea8bd7043afec3a36ecf8eafae0000000000000000000000000000000000827fa0f46134e2dff80088129841f0469ec7360fd8b9864e9ed99c5fd3458e6360661ab4c671846681d491b8b823d200000000000000000000000000000000120f829e8d0ffc360a14eabaf52bc653b1e90a36c0a8af806ca745fa306a9739e31435039a377e0748caf5e80c2b0b09,55000, -000000000000000000000000000000001468cb35a60898ed129f30c261b8431df6a154c250ec16d85a22f8717593b2c21853d123da86d977a7938c5ed74ef23500000000000000000000000000000000011f4e28e31b5f9e6877192a5e632d8c1ed7ca0c42e6e9902ca68f1c2de0f648c6064436012c5c7b14bb8d1078e02f2c000000000000000000000000000000000b25114b2697ca7eb1e6effdd1054893a188fd382d387ec098f846c1137a9b9baad01653b963a0b0bf3cb50c3ce3563d000000000000000000000000000000000c1d241cb03e642c1752b1e1886472477c19a2801ec032dc220c3243952f882094119bb92b621b654b766bc900d2d4f7a048ef7cf5d1f6f625ee3aba091147c389ebebc5b8f3d285e16ef4e8afe5c013,000000000000000000000000000000001745500b00e5ebc6f71c779ba0b0f8d6601a065c550ca19de9562455423d2ccb507e659b0dce982faa841267fb1a27d90000000000000000000000000000000009c36b54f12d130868ff9b9b61b714fb1067dc91637c09614c51b5aafa2cbe3ca7dce0f3e366d4200cbf603ad4fd630000000000000000000000000000000000172e543708bb853712d81c000c9f9f2378e628b4d13b074317e95deeae98e11e7f917f91e02a0b18cfe9b25f1b83f16700000000000000000000000000000000189fc572ff6a8c6606ba0cea7da7040898d9ee85a58f12fade8c5a22031ff26c2f9cc612bc6e1b82a0999fa93c6fdfca,55000, -000000000000000000000000000000000c80d4474390fa791ea5f2f16b41506d8ae13ee0993c8d31a07712687298ee7978a724999500c42400d2f788a5a36067000000000000000000000000000000000592705cc5a8875750a4e6ceb42aa3bef5593eda9e8212702a2e08ea70277a2a66526bc5237be33c8449301544da35e60000000000000000000000000000000000facabfbd15284c6433f17b0e6035d4fdd84d3ad2dd30a27d52809652ff6e7a684d7724697919100567ad0c3e1a26320000000000000000000000000000000006a0fc4e2af69ce15a356656f5d182a2cf213d76a6047a05a1a3375909d245f5316b91333d2141c0817438f0d87bb52da9b63c6bf36997118d58600c1e429c105a379b9e8b0de934ab9f433a4fa63dc8,00000000000000000000000000000000013c6f777df97ad3ddab9b7486d54d1bacb3b40ad3035b47a25a66c02e8866955e27a8ee52872c8222ff7466c1310bad0000000000000000000000000000000014a5eb510d7c743e824f4daab21c43db4d6de8ab2e825d13ae0e186aaba828d7b4a2343a11011a8ec4ea82f456e394a70000000000000000000000000000000017a55d3827b78a9c5ea792b705eba7777df74951930791b17ff5b861e98a4488f83007c073c3e904ed4ee328b6f6171c0000000000000000000000000000000019bae02f8d6f1e31dfa09f4feedd5217ade66f6e8248aa98b273574f72aef83d5048534ed38acab9e0eb4c64f4389af4,55000, -0000000000000000000000000000000003f629618e1fc3018bb836301ccdc59022f0a25cc9c5de6e4c31fa08feea525c83256235e4ec8364e77e5df478f5f62c000000000000000000000000000000001120d6af221ba6f4351bbee4c2c664a769adb17872646df2c408f70c99ea991ffced4eab50fa98be1bb9426915f125930000000000000000000000000000000015cd16b028ce3d58b10aeb84b783475d894ab3f0cfdf7104ebb4f3417a038107128f07518dce548271061cb8c97e88af0000000000000000000000000000000018379875b68bc26107f9a068e5034f29dc2ae7e8830f8e9ecddc53fe7991206646cda33d37b31a47a977b46be58d7618f228da17f49667c113d2bc2a2c8a338f80be68496f5145b4be21a5786ca6d46b,0000000000000000000000000000000006490c327790b4c451f93197d7db24211a3b4b5f573a6df409206b4bbfc36bd10d2d0c989889efffd8f4daa4a68b211c00000000000000000000000000000000168f224738db3f07af77494f52ea5e957812a1acd62615f0eaa95c1d363cfceff29be9cf3be5329bb41175a0231ced4f000000000000000000000000000000000321f06b55f7dbfd4900b329c914f9ab9be2794e51e54498e18f83ece5bfd205131fbc254bfbf624d57ec2954b05f6f00000000000000000000000000000000018ec54f3e09bb2a6b112b575f9481bf1c85666133051e9c0ab53369d14eb90e27d2ed02dcda1250d5d539df0d0cda37c,55000, -00000000000000000000000000000000036570783711b381830e35878fbeb187b84884a9a0e88c38e84124515b470e6ac18157e1499026b27f4f731a961eaf330000000000000000000000000000000008382838c18d56c046a8db495babf8d14c915622d7917ebe10cf7da7ecb65f174cddb9e70d0262ada961b396c5511b410000000000000000000000000000000015f63ce982aa581dad5c71fc79251b7f6336c4e78a4a0f4cb6f87167cabd31cbec987d7af4f11dc6d693a0b0774864130000000000000000000000000000000015c001372fe0530a3f50fb8b30e75ff4b264d673e0448211d082c7a9018f583b4d01790019874596c59c68768cfa3e699431e18a462fba704216b516e819fb3392e315b0c92a7411a329cdafeb511244,0000000000000000000000000000000001641b4ad10da5089164809d82ae47f74e27eaebffc2a2ca3c1b924fc69c1ea80ba3da78c78e86957f6a24e7f75dcada0000000000000000000000000000000014e781e4fe79ea1654460f4b0daddaffb29b287efd8168cb20d7ac6c729f684c5f2a7cfa87885accee3a797febc904c200000000000000000000000000000000001c9a44547f0c5b1f4df190285644c5a31df61e3de7da085835ebda917d5e4163f2deea9a83d641a4759fa3108567ad0000000000000000000000000000000014c3d2a79d80687fd6e6aa423257644fa5d0cf641aaf6a7c5675a810767904166fabd9a2ced0727e3badb932e46fd181,55000, -00000000000000000000000000000000074d78cdd35ea17a3013e2301fe9f80f2d20d270a25fdead37eed7697a52d152612543781763e6035fa5452ab12cce25000000000000000000000000000000000e572236e1c203a1c0f99e6ec978458c1a143a6a650eee27cfbe406bb2858fe5f30222f468d119703c2f442bc644ff3000000000000000000000000000000000125384343fe132e16a9fc15efe1b3a9e47289e0afc4b44d492e33a6216edbc96d66c1ca66944a8296e7695f27f414c5b00000000000000000000000000000000084c2cbf0d7c932c3098ded7c70d4411eed882feb0f79e0f7f1c31f5fccb6d53fb57de179c3ba5754bc5e532c3784df12051041bd2f12f6e6e29924139770fe209b7bbdbcd6c0bcabbf5021a7dff2d83,00000000000000000000000000000000129554de7de9a2b73340d94d96f0356a2d1c0524cfb007d76a75f462872e831f45553de05f5b6a1f9eeae37af7f6b4c9000000000000000000000000000000000b1ea2a649ca13a3dc7882f2423036670f68aa05792a8fcd72524420e37381a9ca80dfea701fa5e6da57afa534059617000000000000000000000000000000000b7ff27aba408f9759b5109600cff66c03cdb4bfb3dff64a4838d0516fa46bfcf429fcf9d5cbf74a27f70fdccdb1238c0000000000000000000000000000000005a99aec88967fe775c691d443e2dbd45080eec97e686ee6d7b32e801efe6563315bfafd5c7622d0543519cae4417029,55000, -0000000000000000000000000000000004d46066439c3ac559cce863c58316883651023990180470d2efd06e443a7caf3a514b54f15ce6e850d32779215bcf4a0000000000000000000000000000000019ce904b6c9c3de59f7d5017f60f1978d60c564f94a0f1964c24c876d1139a7ffbeb6d0d4884bbfaf5f2f189af6904a50000000000000000000000000000000015f1989719e69be95f25dda9358fb98aae2819e0deb7e2d291e2c01e85ba26a9da421896c6b6e2ed20f609b533154694000000000000000000000000000000000b287cfcf1dd7c6d735c1358dff15393ddd6c82e7a33c5d8005c4234cdf823c76a4725fd74cad74b3ec51df67f09af0fb96df57a600dc3b5aabff5b1034886d24f6fcf035bcacaaec738deb2cfb8f852,0000000000000000000000000000000007997a499b2194cab634750a189cca6783ff17d866d66f5998603f8639d2242e8039222c65b0d14001167a9b09afb58a0000000000000000000000000000000015050fe6b335884a225efcfea4acd025cfc05e8f5fe9a0e22a0c91b55664c118d79887de91f1ae6cbc081f6f55f0067000000000000000000000000000000000195b23c4c2c087082c30600ff00485d169dbd360643d163f1db363f270cd7d4f177c36b4c291d50da4101e67b229d0de000000000000000000000000000000000df596ba2350ff7d3e75b4cbe5f8d6b2cc0e14b3bd6dc021936e3371ba64031f6266fb1d2951801309f22bfb1c4b27e4,55000, -00000000000000000000000000000000006b37e2226957d639fcb0bcd6c20b3c7b8372e7347a14b970e01c67c1859fa97c754ce588d0f835ecc053549d963ab4000000000000000000000000000000000c6a5fae8be3a32e3f70a4202a1ab6d97183964b9f7b9a084c49922cd9e0e952b0bb66c5580f0e0c417e079493bcdb4e0000000000000000000000000000000017b6132f11adc0d5d693ae7f3a0f89f5779708083eba23e03b0c9265e4e60624e1fb6940e8ee49d31618fa6389b1b50b0000000000000000000000000000000000a45c5f6df71359648aecb6434bad1619c39f10e279a02b3cc9725d0256bcd126843fc9ed29cbe02a32cbbe79774a3378176412b07eb7f423f23ffeaa0ee642590e0b7016bc063f3fffa93e1e35484c,0000000000000000000000000000000001fa243b548f8f5c2e5d7736ca6fa95b74dbfd31f95fd532b94f81a255c73e7c0e000e20f9ca6750cb0dfdcd2c1aea8a00000000000000000000000000000000132a893a2326bf61962e1855331a53667e6279ed7358bc84c4a7c218b6cff1d3f449954f56daea72bc2779c60f1113400000000000000000000000000000000000091dd23c75dd8266f556bf27ba54c95c3ccab06168e4e6d0747239722afb20f3db27454c6db3a88daab0ef10659a66000000000000000000000000000000000d3b2e3fd358aa3dae983e87b5d1fce6d5688e66ced6e3a2c96b8d48041557295d5932af6532c13965d4b383fb252518,55000, -000000000000000000000000000000000ffed009c78ba9af8cd33af7b7697ae4dff863bb92365055baedd2299b7f5b5e8abb84ed434f7223c3e309ca53c08aca0000000000000000000000000000000003b2370c837dd6291818efe7c9af62dd51295c418739ecc509d42c92e2c97d12a9fa582946e176e8153fc9a273140b2f0000000000000000000000000000000001e63438e8b4a0462cfdff64a281ab4a7f48d51b51325817139f8ee683484f8695f1defc0c3efcca81d5fbff06cf9c54000000000000000000000000000000000192fc391cdc1ed6ddbd317f2f366f2ce25ba27b8c0f09c733e7bc0c0697544399a3a4f1186d139a8f6399ffa88e89a69c4b5627d84e153f3a4ecc14ddd6baaf1d62253a0f88d3af51be18d991976da0,0000000000000000000000000000000005095d1becff61df906815842112c6508d6cade4ef5f4b7418f5f01e8c5a383addc1c572237613dfbbb88bcff80e4a44000000000000000000000000000000000bd2561e7bfbda8a48ee038855e37b03fee805689452e9afaf0da4185e0c194e407ce7149b713c689d25f953da36dd1f0000000000000000000000000000000015ba3ae4d4238175425ac5dcbd9e6e9e055b8c1b7752931b524fb546f7bee8723ef2e69351450c6d1ba3c366a22355e20000000000000000000000000000000008c17d77dcfda00a1d75ea0087c58e74263ce5ce4066e979c66397de8e236708831c3a9ca6b35ade8038a28930655eb6,55000, -00000000000000000000000000000000002e105e0eaa418d58019a849b89accf665a94ffb0bdf308a11b99b521de7af8ddb150c0e3b2e9c54cf5456b6105bc81000000000000000000000000000000000691a3b3986fbe1c0ea22329364454f37f645d6abe9310e883b9191ce512347e074e18e28b88c2adcc76190a549b80b40000000000000000000000000000000003f3a37a763c8d0d99a3fe36923843a22cb0fa18ced48493b2510fc99afe5b7699bbaa6c2ecdad8aaf72969354f121a1000000000000000000000000000000000f4bbae00205f54eb10c83d928d908fbae342b76050e33c51b6e282e02b3c1f132a4728dee4ea95455c25fdfc112f2542ed270764791aff081f1dc8051d22b8e18803a7e310393f21bb4a495a445cd45,0000000000000000000000000000000005cabaf39b93d7fe15ef6a7a3031df58219bce702a5a77162551a3d916c22e8ec9af2aa20659e7c4ce5f6382a5f82726000000000000000000000000000000000dcefe1a48d8c239164b54771118f7520ac11a7a6b72d8e17be1cd788cad2f26d3a0d9113e6536426800a744be9f0d4000000000000000000000000000000000199d95a44a4334c87aed273a0184be9602ba443d5b8d34f3495b04e927f4687fb88487f586395c7babb4f218fdbecf8c0000000000000000000000000000000010972032f9cb3e8f45447bdd06df82656fbd3ce38a9f7564c6e5d62ea3596c9b7e0a94046f1c65bf0452ca25b15a885c,55000, -0000000000000000000000000000000009a3e98fe4a98582ce9f274965f376cb45e8583775dbadf626cb1327c1f8a25b293b97e7f8f31ff72ba7e8e769ff25ef0000000000000000000000000000000018e4785ccb76c4897087c8a4242ddc744c6a0a53a4a844254153c23d6f16d4ddb945252d13f93101613f4eb0b1e2b8320000000000000000000000000000000011b81d344eac04d3471b1edde5e51f31f97bea3396580839fa094db58cf6bee371bbdc045fb60c3ee5c6cd5d3f6d3c4700000000000000000000000000000000073476bc5b1d52ff4ca89c3afc099417f473543fab6e59cf9de8a19705dc4bf2a210b1e6de4dfbde035c312be0c70c56fbfb7606b64eef0460b8f33a0be54451fb655ce0b81db89eb7862f392450354f,000000000000000000000000000000000f250b5e47ef616be106a3334e2f516061eec8f7ac69f08f6dfaedecd76fb1c9685ecdac2c3ddd534e3947d007ab177000000000000000000000000000000000073819a6de38303725aa3a9e5a7a9122b4d1e60ee8deb3554b5e06ef5e60d71517c2279c5066af003b32cdf83b7fcdf200000000000000000000000000000000070721107ac6dac198f7ed1a7f84697cbbc3199a220d1aaf82e6f015963bad863f99190f18a482f730254cef753ba22d00000000000000000000000000000000169910eb30b8fe1ad8f84c4a132c6c74a6ff06ed6e792af3baa6619e3c8aa6cc3e6f687299467ec9554f9e91bee77aa8,55000, -000000000000000000000000000000000c414b95b298b9c673001173ba7e5ee3e03926f28068481cfa0b469ab556f8fceba9fd0a815180ae0b82c265fd4c6b7e00000000000000000000000000000000054a242c1cc1a9c710bc23305d09c2d613ee8eb3840b37943bfe83f9c1db456ab4436ad319fcdd8684db129d76c95320000000000000000000000000000000001683711c0c7f02e67374f190eed1ce6559479d6d199f43fb5b0ce7df7774a5cb21c86b3b3498855d9b69c5763acd8c4300000000000000000000000000000000062f87085dfec847af518bd71c078f994b090c3b27c6eaad79772ab58afa43993db52fb08649a32629d61c3db12c87318a29fcc442d0c2446697e94dc47181dca7a314f9073c06aba6dc55aa79978d7d,00000000000000000000000000000000106e892e336b2155909946ab73b980ea761cfe8c48b13ae8a5302eacea08b9cef3e60d5b33c6ec4033218ae5761433dd0000000000000000000000000000000015daeaee59f3b4cc26d3da745661e74db8fe1ea115d50ba49ef5e6151a9ac2f3135f0232235cac7a53e1e8a70eaf0476000000000000000000000000000000000ff494d17c735b934c2c7fb8f413103188fdb116fa8f4d4e43262968ab0fa1bdec23b0d4d8b1c2defe624092de36610d0000000000000000000000000000000008f70b7e9f2d7083774fbce3bff58a1c73fbcbcd9cb049cba71c0c3f0c363517c8956240bcacdfb7934d4c67b1bfdd2b,55000, -00000000000000000000000000000000083eea9b5b2d5ac5f7ef51ca889a4317322d098a408a741827fb3419eb12a51c07c788c2798cb37635e224e99bbc894c000000000000000000000000000000001312ec00f4b3a4305700b44b3f215779a9a8bfcf5b5d3a7f237a33c5484099ec9bc5c8537fae768e2c0ec62168f383d6000000000000000000000000000000000cf1d5d05d11e1d07074dd34211d0f00eae1df4dc550c55bd2fdafaffa1ad36abd5da30c5d3a5aa2845b1d95a5cb571e0000000000000000000000000000000015223baa9f2ea4b04fdb05b05bf3a94dcabc5e64189aeee39c380de9a34fe6b4253f5795f70bbe51b80e1aec1eab7196d5b468797b4af1978983faebe59a28f34956dacf5b7f65d25548bcedb518f45a,00000000000000000000000000000000098f32b35e3b7dc1862ca1ca3c76d009f016c6b91c227f2cebe8f1fe87567d936bf1c54103bec31b3552c077c0242fb40000000000000000000000000000000005380a66d48d348487624a15b63d2ecf6976b5b599901101ea8b1f57736649b4397f6679ecab0ae29573695a921ac475000000000000000000000000000000001710c368f70a2b9cc92ec65c4c2ca35fd63440eb350f488e7c6646f9c42bf680eb62a887d533a91e47988221b46c868200000000000000000000000000000000033c3327da938dbe4630dbe16838229d7d427f3adf18dee6fa26b1c8067838922c1bce78cce08d590ee1acf2baebc7df,55000, -0000000000000000000000000000000011a960cf1978aa2ce1731b857fd91d2f59d4b8d7c6871ef6f4f85aeff549a2f397949d11a4793926fe7be37f3a83d11c0000000000000000000000000000000001954f056834d6e3b16043ef1acd0a47a353300257446e9a1db7e58bd0d7c4bc9ceb3db51ae01cfed9de99621e96934c0000000000000000000000000000000002e2fe460e71b65595ed93a0010e5ccd1a2c16fc4e0d345e7226c947f29720d2f3f54282f79cec086d3fb1999b9629b300000000000000000000000000000000060dd8a7ccb613f1521168a8a322aef9f84d9708a893f704f4fc9a19e2493f25620a47e0fff1bc1e212e65e92873b4f2dbc6afcdd409e5d50d7b655580f1144de77f3efe5d6268032eccab7deaaad997,000000000000000000000000000000000404587c60a4bbd8b5b929ca2ec2a9ff2ba4733f4f2877478a669b238d65ca130cba398899f2910d6de04615f8ffc99f000000000000000000000000000000000940659b3e6de7c3d8de9169a28e68dad433bda78de0991fe4a1d404e5f4babcba9d57c7f3d638aef264642f87c61fc8000000000000000000000000000000001676ce240e1ff70ab03f94f3ba3acd31725ec306ce1fd707e29ec22cf91746216dd998d03ba13a79dedf878fae38d68e00000000000000000000000000000000098a81422511f77191ee15d402614c86f9447ab78a89cc348414108f36857a1929f2b92ced78752ab3604f276861803e,55000, -000000000000000000000000000000001472caba61c2f1fe4b1d0912b114c25de103ef4351668f22f3a158d7a347539a7b6656044bd490f036ca3e29dbdded370000000000000000000000000000000015f8cdf7786410b409f218164063c99e77d8f72f03882a6c9430ec725ae574547d3ea3cf30c3ad2c9c3febe6c30b1272000000000000000000000000000000000ccbbed85c2809433fbcf22d6490457dab800b21cb4de414c7dd1804a0bdeb7142f8ffbb2de921c2c9eabee6a6351026000000000000000000000000000000000a404f42c48e3ca408d3f92079b99805004da928f128206d8904ecd7fcb14121c7d9a9e7fb69accaff921315ef3d5372807347519f114e78f99617f6b147ca833bff7be962c9b1e1f32b5babe6067d7a,0000000000000000000000000000000010a4ba6952d22a51dbb6762a3f9bd09712c2be5a98bf0ef298d7a7e3a9735ab0d3bf39e40b334895c73a36c218ad24b50000000000000000000000000000000002860f38ef61b497bdaf4faeee7b406007981c17246cfa36cee906452ae85e1c1c6385898ebadc3b4ef8887fff25b8240000000000000000000000000000000002dbbca9034fb17c3f37727d44c027cdf47c36f3f628ea9385fc9fc371d23f22d983656caafbf1cd1f8bdeff4ad7669d000000000000000000000000000000000b7e71b65765c4113a7884771952268a9fe10576f745038912e6877c78372cd261220793b888c43accba1646e902fe14,55000, -000000000000000000000000000000000b52f05365c4df20a7290aee71a7e030615d1a2a971167884d835c24e756a0faf6ed0552341c561446c7fd3d5e887d830000000000000000000000000000000018718ef172c045cbf0bb132059754b62414097eef640a781db6ad521af5a24d78c622d9402033fa939f70aad0510a1ac0000000000000000000000000000000017e969e44b4910304b350b5d442bb6a0b71e1f226cb4603cc8b4dd48614622f3f4e1ddecb1894046649d40f261d94e030000000000000000000000000000000004dacaeb9e05b9d60ce56c17312a092cb988bff426b8a718cdff860186935507a06eddbc4a1a29e4ef88db83fc4b6e77830630695c8dabe9aded1b5365bf93770aab7e9ef4140a2bbde2f0a7b109724d,000000000000000000000000000000000e9c1a6d591be4da37fd6dc283b8d899b625ccc96371dd3d7731aca66cd2a978810497171f2aeded64fa2b10e480de2100000000000000000000000000000000006d2ad7287847255002480627782d513eaf1f68a3d583d4762fc156b8eb40deae6969fa8a7d8f8aae923800091386a00000000000000000000000000000000003c7eae0eda08df9b9eee2605a44fbb486e3bf2e409aaa1c8f38c06f969ff1f74338004b01288dce99be26a837e45d3a00000000000000000000000000000000178174d2f569a9392eddd2715ceba8762c5bcc6325217db5e5f970d6fde069d0e48a824e5b6ca017891de175c92f6b29,55000, -0000000000000000000000000000000019829d5799eed5a081042e4646d46fb6bead6d3b9893a4240867b25ed6af6a3e154514f244466d80e3b9311e060bbd7100000000000000000000000000000000156157a654db2813cb9c1b4da0a3ee192fad076bb2767020fc5fc00e967c1a35a367ffa375703e1181b3705ace9dd28000000000000000000000000000000000093385a6a9dd0ab996df54b23f47f4a49b3f379e11bc8331016ecee6161fcddd22f6d49fbb21f098873f1e17424dedca000000000000000000000000000000000d5b5b0f2ce81e755b4030b33fe3a8bdee38c2c60ed3b4a88bffb9207cb762c0a5c699ff424c000ab080d763abc5438d184ef5eceadfd77b3a4092696ec34d0551c88e434567638623740b7d5f9e3616,000000000000000000000000000000000ce12c9010b4c4afbddb459c1b46063a8488277948188b4ec0b739e1cebb5653681d0e43a0d2c6b3f842bfc609bbdee3000000000000000000000000000000001123f60cedddaf4385e63758d64d4facdc443854176ec199ca0df0a9c258517f2512594f2441a4b9a68aa9a2b4a1f4bb0000000000000000000000000000000007cc6f77d181d13bd9736ee23a33b25b0bd969760642ee19004e095ebb8e2b3c0e09321eb15a2f7961803c0fb10b6ffd00000000000000000000000000000000004d8dbf2f0c14b07ebed2b9cb4bc87df78ac8a34ef0b05cbc2c6fb8e8156415399fa52dfb968ef0e6ec697030fb003c,55000, -0000000000000000000000000000000003af8c25bdbd0dc1cc344d55366f15555709a74e1f0d8d7050cb6b487759db6200401b7868fca3c2ad26e6362a30e6250000000000000000000000000000000013f8b6ffe30f9a133fafe64461d305cc6b2cf5aededf68ba396d4e00df651531c750a3d94dd77bc5c6713b939b18fa19000000000000000000000000000000000dde97855d7728f409d873b83b6879b45ace5b73f317687fbf478e594a959ce21d4d751db646ceb20432e8311e67404f000000000000000000000000000000000fea997323cf29710cf0e3d44ce682e039d6cbda155e43c94dc8cefc5e94000de4b9525123b9615b5f1019a46ef37ad3a80d9efab033e920061cee8f8d7ea6023cc05f08340642613628b39e7b7fd0af,00000000000000000000000000000000172805bc715a8cfb2e25c384214f4005aa6d3b809a0ad95322209851ef92151526a9d01a914c4d7f0c120b9bf3837010000000000000000000000000000000000473ceaa092a5ac12f38b4065477672deacc08e553d8e9e6391bac0d9ca50015934cdbc340deb05aca916cf50c7915b30000000000000000000000000000000012e85461fbd26c2d0235acf5c8665750656819bb939e8fae77a8d526ca23443aee395a985cdd4b1eb700311fb87e91a7000000000000000000000000000000000246d45fdd88448c93bedf4799becfc7c80e67abd483f2a0aa41e8bbb3f38cbc900314436364f1db6e1d88595544517a,55000, -000000000000000000000000000000000cdf60e3bb018407eab162822468255bcffd54cad9127054bd1c30705a4ebf1afc7f539cca6ba4cd070b44410ec751150000000000000000000000000000000009a2e3e5993b6a7007dedbbd21737a8c0aef3ecd4607953c4a24bb3fed97ccae01ae1cec024443f300b570a66e9ac3bf0000000000000000000000000000000008a21fed19e9ec2a741ade7767b0c9f39b79c3fbe34aadc9eb3043583768d893bf927d26231759290c7dd9c4f158d5a10000000000000000000000000000000018eef4ff88d63149d2632c9db586a4af0606644b16c82fbb0a3b869f1ff924c59acc8efbfde7bc604497ff68939cdd0845111c860f6f5725f99b225c53b9fe1a70150e7ce922bfe214900aaa2790d145,00000000000000000000000000000000122e1f2081cbde0055fc34d2fe61307bc333b35a1e0772a0cd6fb25338c89824bcf2f066bc7b571b2fb314ca7f45106c00000000000000000000000000000000027ed81b54372d858a6ba2faa65fdc132efbca6ddcd56c3625bd9267cf0ae04f6d342209b995060f584be8d40020669500000000000000000000000000000000002a03427a093a3000a1bed9eba91a82dc2f2fcea1a16a1fb8af29c4988b589abe6a505ec87a82864b3c683beaa6420f00000000000000000000000000000000134bf64871d69a72e42766c2903fb4589b84d7772a62f7d2f8f8d02a914f4d3a278c680c626ef4d69de8aa88b57589a7,55000, -000000000000000000000000000000000f5d47911596c46c0c08cac5f5e7f6d0609874da4ac1bd4e0e59c393273a5fe31a756c7cfff2a01d19e79d209d7c6d3e000000000000000000000000000000001010f864eb6624132d4436d18db7f5b34727060dc426c109886be88031e3c155490cb3fb09e1fbccb7912875477c6d840000000000000000000000000000000005cfbf1c2ae1b80a8c7cfb2cefedd907b0552794f4fda101ca1a723b18de8cbce30eb54287e1847cee3f416cd8b45f2c00000000000000000000000000000000084fa63781f7eba9c7e911ae5866d485bc7e90603541c55d1ffad8b3cf7547fd57fb24b14002560e58410b828513e109c07041840216d60ff445cf53b273a46016c8ecefefb53550f8bafc79966f863a,0000000000000000000000000000000018fa44efeabbd1cc47dd9b1a1195ca921c99c77ed43a44502aad27b6c663f5ce2623382c3ddf208f42e3eea741281f4300000000000000000000000000000000138d11e497e3c5656bc8fc0ae4322a0bfb6fc20e249a47a103b164aa3d9fdbf7df4b1e3b0842b4b12568a31992a151f000000000000000000000000000000000182490d6ae35c1208c0d608984df4988d057f3ce5a25073c77cd5b224a5892768badb1ad5cef8f41d1d2022573098c320000000000000000000000000000000002a6e0523781ccdebb75063dc7ad1a9526f9ff8ea1364bae487914f254c0eebcbb2cfc3715fecb9599bfc2f5feaa62d2,55000, -00000000000000000000000000000000124870cfa469136c638e0cbf15802f2699aacb66d7e4c2965c6759dbca4b7e47941ad9ec37a84db1afeeeaa65a7418e4000000000000000000000000000000000d4503049a6a53536bdf41dd832a6ecf3f10554887da7e389cf940394e1d88db94369b7947436546eb6c6e82c48dfb9900000000000000000000000000000000053f9a6e1f05b67cf553073358009a172e2ab8b43572a974da1f3de85a29103b13d7e67b2a359297172d27dba5c61439000000000000000000000000000000000abc29f50ddc1c113c73700b9b9796890cbf48818ba981fdab2db27ef1c58f4c2e4595b99eae397d40990ce2f6c9317c29b031b82dc8c9f4ea9524793b54207d4e13a548d73297f2aa6241aff57abfd0,000000000000000000000000000000000dc7488491433d5b3924105c01ffed4f30b755d7253d867fda595e7d80197823e56e4d182d5ecc72d8ef1ba9bca15a310000000000000000000000000000000007bfeeadd6fc468ef6340a2b394c155bf50808cb11e89adb0de5499fbdde91760e9531c1deb23050286a15e5910f1d5a000000000000000000000000000000000f096db706b08485fd577f37b7bd232b5a10c3f80c25bcf82f7a3b666c6efaac8e856bfe5f7dafb7457e33eadcb4133d0000000000000000000000000000000004460d1f25159ce6df59efbd7c693355af4634dadeaee2ced68124b2a887698c10e9c4b40c4f4f9c8444acb881ceff65,55000, -0000000000000000000000000000000007d2aae9794b7a7de97f7146c0ee8415e09e56fd42535bce6773cadd6f7ac09c4eafe2e926cb7014377e54c703eaa9dd00000000000000000000000000000000172a4a33ccf99eb0473b2c44d30bd53159afae0c7706ad128bccf6258974d5e5761f9be43e618cdbd96027aede7fd5860000000000000000000000000000000012601bce2171c6e4c2968a3efdf1491285f9e4ab37cf973ab5c8e224ad5b40e1b6459ac89090c73deb8fc79fec7fb8e200000000000000000000000000000000112a6443116e6f98ab348e57daa3971b5fa506e40515e1611fbed3e7dd64c5c1e991e0d2539a70eb93e3da0f573d6b2263d26ae92119c7b06d83d7e2922e06559b1740eae315c6623d3e543c9bf54258,000000000000000000000000000000000f1aa4a7a22c568c41270d24824138bf9ffc763a5356b7c0bc1d051a0a0db12616700d9214972b63eeb2a398d27dc83f00000000000000000000000000000000020d0c2ff8f93db6b415c2a01712034e46bdeb6e665a5177a3877db9f5401d3dccb99907ef843062e394c1428983725a00000000000000000000000000000000088abeb6fc3ead45d5b261b7d684f168ca8f5f163cf338863e6b102dc40e2cd0ede97c47460ad6f560c27e95c8b71ca8000000000000000000000000000000000ca2e5cec212d581c737928512118e2f51a0d74070f40a998b7b06d22b9fc754bb2fa5499308058be9ab81521d057414,55000, -000000000000000000000000000000000030372914b83644fa4db1958831e9335c72ab7a811fb337696221a3290e4c54bc10c2225f8fdc3a9f62632ba2f1594500000000000000000000000000000000114205926609470b6022d24046a1997c048e6d2cf6043397892c967692161c0ceedf409bf5e1199a64eabb1ff8de23640000000000000000000000000000000017cdecbe73779855b7b94920d4bc8ad057ce51c5481a5579650df8a5bbc421030d2ac44568217c4dbb13d7c639760236000000000000000000000000000000000f194fa814bfa7396697bd812d9449d06fc61b580d7a86429fdd1ad376e21ceca139356d7d13964c3c684563675711c67a02c61a7a75342ee7f0745886c0ea2a73c21500aef8078d21d20b7216c2990e,000000000000000000000000000000000cfa23c46881893f6c50d238a83669deb520a7fffab4f912f77df7cca43f6827a1a0ae0b3f36c8f116ecefa33b8bf37a0000000000000000000000000000000014b7e5c18d2f9bfe15b0c1af3bc6e230039a341e135837d123e91cde9cbcda298c66b93f692232c912e5d7d3d6331c430000000000000000000000000000000009c8984999ecd3a4144ccb925d3e5cae5c1662dfbf8871013b1cb2946482fcb075c489c61b8d6261f2574b44da3fc1ce00000000000000000000000000000000196e7feab383211e4825cf98219c63bf9f45a72d66030219cb585d5d25237a01a97f00e122db6a51325022e69e7d8cdb,55000, -0000000000000000000000000000000015d4ae1521acf897344c3a76261754ff99742585af4a0ee86dc473a88fd408091404df1da9d8bb291db68bc9c07d6b2b0000000000000000000000000000000008ce160213875c661163990f3f7ac219ea295db5e828354864517ea8689ec15d35c6df78ff14cb276e0c97ffd7fbc09a00000000000000000000000000000000038a3ee211e777d6d6b7ca6c7a0d2130f1a071c030eebec412c3a0f14c3584e7c5cf15de254a8f141a8210a90249ee5a0000000000000000000000000000000019f7ec6b2fcd8b3190ab37a6e843340d3f3fc092f5772a042edbd5bdc967b96e8a1dc9e435b8463496aa1301f87d0e5a81b0c87102055dc2901826875d5e85a794befd93fccca2b9c0a1f70ef5610d83,00000000000000000000000000000000005c0282830934ea09c9f51b52cb6dee75b874b155c63076dbac2cbbf220863d55557ff1b7d681fa185435df1522f49d000000000000000000000000000000000a1680ebbb185c8e7d8a197a523a7a5e618f97c46670622034d312b3eeef140150e03b00ae3dff8d9f1d872f3d3dca380000000000000000000000000000000019bd2eb4bc25f5aa6bce206f0683dbbbbb002098a118fcfb060c1353a310c2baa1063a782bafcf6ff6bb8edaf6f1597a00000000000000000000000000000000082edf49a0435e0b9f3dc7f207711d66004ae688b18f5b62fd1596899ee8edfaac7da38973d81f12200018fbe8151572,55000, -000000000000000000000000000000000fa7f8fbfa1d4ef5f001a451c55ed261dee344025e599884b29d086e15665867932120d33bee579d5eb1b7e6c7299f310000000000000000000000000000000001f06356f793350b17b47a623059a068800ca1eab6089c7c146182990063e8e23bbf40d95a42bf6e976224b680b75bfd0000000000000000000000000000000008807f6606d2302450bfd8b38fd4147b851ff59762c1ff48f9442c4d7b77a32c5e023821eb47fca839a27fde60e5f61d000000000000000000000000000000000c5b92f1ca9c20d4b6b11d794a5853824cff20d9267a20a7aaa4bed8bfdc728c4d4d50feb8f0b569757b97f473138db1ebf66fce49c6beb12737fe05e3adc0a51ecfa9144ccf6253088dd1a7a483de07,000000000000000000000000000000000b8a715c1c2792a30f7ad752a808b621c34af1fb7f1e3392a36ca9481a019108a21e3ef338a1d05f2f23ac3e2cc42ed500000000000000000000000000000000101375c9de592031c55a7a62189fd3fa3c565abf7c64724796dca3b1c7a6e6834a16ef1c4e2afd6ce2e69487260f0028000000000000000000000000000000000cd385ec8245431d3b1aff88453db7f66a5d7888a5c1e0dd0abe9ac7db752933a343b8be53b7bfffb704768ef0a3dc5c0000000000000000000000000000000015d55c8cddb8715e25fa260d1e1fa672ff76eca7c80d19d00678fb9d08759b810cf266ef0a7e9dd749a576ce07240fa7,55000, -0000000000000000000000000000000001191410ec6c5ff628bd25d35965f5e9fa7f3c3d8c0a9a1ee7ae37437a97c25e221110d892e2c7a0e9c8e386774eadb80000000000000000000000000000000003be30c25a18cdab139277232d8888f6d13112c9556895af8030f1893114d5845d895df9afe3c6f9ff7ffb1919adea9200000000000000000000000000000000197f6b4e38be0358a3f1722664c61e62587ecf5467f8aadc3a236b47682a75cb76bafb18a5c556b321d5da49cd4bfd4e0000000000000000000000000000000002e4ebf7f22d929b7421a600e67fa2e64a59edd87a2e2eb9dce1f06d3c793f1a812bcdd510e654d44fb4c1de8c64ba9f0305523dc79dc4b905e65587fbd095ed57aa42403d2df5dd489db8f50c99e9b6,000000000000000000000000000000001311de31229f1825d0bd2c9d726fd71e05828a20406a4705ea65f441537486338022bac4e552bf3c25e15717bee00ba400000000000000000000000000000000052e082cbe36c854a028a041981fed87d39fb218a88208aa1096e260a3932a1155db7f306c32d133070b0a5bb6d161760000000000000000000000000000000003269d4afd20002873f4305018a4432c1925eea28486d657cb458198ff2df9d304bdfc7455233243b1712d8663591d460000000000000000000000000000000013376fb98929cbe7f7d090d1c9d5c4f6332bbf25470aa03c35a70481931e4bc91c937029a5e11d2a3418eab698361227,55000, -0000000000000000000000000000000011c6f1dbccde640f63ad7d40089779d01075e26269421b4ce12fa5341f58ee9110f17d08dc1052426f2d00da2dd70b4f000000000000000000000000000000000740b147bcdf06705971c113a5cc12fb37345dd59f2cbb5ff500ce2b347fc5a8199cb3007a871670d5093f28979cfade00000000000000000000000000000000046563ea98b5e85b3c42222d5e0d8481e6aefaf077a1b99f2b4eefb397ec846aa3659aacda569054c9c8b9b69750272b000000000000000000000000000000000812d887943506d68e3525ced9b979354539b7b14003a3169e0084c26326b92be67346920c9a99ef0f9638e8991296feac23d04ee3acc757aae6795532ce4c9f34534e506a4d843a26b052a040c79659,00000000000000000000000000000000021166263d1a443d5b2eee9aeca3678ae4c2b44d556a7cb9631d47e4fa3bb05ecb94d6582f4ca0cd787027fb5f2efab60000000000000000000000000000000015335d034d1a0ce78e1246a16e35e0075f73d4a392da1e05c11388084cf80bf31d499e57c48f4be6e72d3abc7b387ec6000000000000000000000000000000000deac4ae1900a4e1814624fb4b8c7a3149fa9cff2ca97f02e7d6765e034a1532a7b8475ef7aef5ebb851063cf4b9e79500000000000000000000000000000000161e3af03f226278a07ff3b08e5788f6c5029b2c8293e7a7e3ae11c4d78676b60dc0208cec6b82e1714d976007fbb389,55000, -0000000000000000000000000000000004c8078fe8567013e8d05a546934026cdeee7d485e30d739407db16fefaef53ed7bff0f9adaaf064aff014ac919d91c600000000000000000000000000000000107cc17f485af7f22e07cf14c5cad6368323f720511fc9dda677b360567f769e47a77f61274927ef9b7be48a77357ec40000000000000000000000000000000001487f0880a6cbdac33ca35b9b65e4ead9d8c2e9180c993bdb2052060325aff8c62668c643f0cd9b4bb1f06a3dc74285000000000000000000000000000000000d4b2d062e31fabe8d2a329dbd6417673a519f455739d140246f2b3e43e20f390088c08e545bf0419d796ac71aebb5198586d7ad8fc3e4fb42981a4415224c0d976ebe1c342e9bc1cd66d35168bae33d,00000000000000000000000000000000120b4434babedbd8ff295a6e2ed5fc7af0548d7e60663110050be797584c0cb638988201ae7707cbedf0c8b3dc5ced85000000000000000000000000000000000d2de0a260bdd241a145e3f68a6de48da4c65107a500e02bfeae6ae7dc428026c7c3e9bdda9a3069d2744705df2eda9b0000000000000000000000000000000018a237906c0e277541c4f00c4c2feba7cb2c9b87709c18b62b7c36d78fc118cfd65c127765e01dc0ae5875b9552bb45300000000000000000000000000000000197485daf54e98e097b6bca24b0738682969256decbf3ebc05f6982e4608829f37e2877937b3f26b88efc3deeb4bfacb,55000, -000000000000000000000000000000000811e9b0acfc10830c074c5a4d9f4d9382461eb523a61dda0b77f1c43b285fc5c1ef3a1fafd923addc9a6e904505a255000000000000000000000000000000001113102d015dbb509f0b8d0d0ebb4d3711c4f0e1e3d55fb0af247dd24be4fec9d6fe3ad73fbdcfe206891bcebefee4dd000000000000000000000000000000000085aae9e58fb97b96ca3c089acab7bdbd0c3adae141bf61075f5c13145b0d07113f1075dfb959bc7c2d3d3b3a06ab2a000000000000000000000000000000000bb5eac8125807c10270d94e5bcf278241d6fa82f68e41b5529b28aebc88870af55881db526f7bd221a8c4c0b29a1b7d6e7db0fbd2a7327c85054b4c0de9727dc0b051058f8bb4ecb1dcc7f825781712,0000000000000000000000000000000005de82540aa67c69b962d292133b09e6593961da8944ce02557141abd19ac471f766b4083db85c67a44b65dad2202488000000000000000000000000000000000cd999bf3cb004074fe9f355cd8dfaa7b9d3439d902fddd2fd0688419b5b7f8c4300ab26b658936a90c0b8e1488249d1000000000000000000000000000000000f97ae779429a5afaf7a3343586eea84a4e76f00a1852ce42a4940babd565bc8d61bf72fca9b123922f1ccfb1db8c06b000000000000000000000000000000000935960fa941c27e74234a07857ee680f53c31047235c6152d1669724bdef37ba642cf4e0dd355443ea470e6430def8d,55000, -000000000000000000000000000000001335276775545fbb4c701beb57cb34312108c9f1d46b4aa4b09a16faf0e648b4e80848bf5e75ed8730715f0107afc9820000000000000000000000000000000006ffff8736bab41b4ee5681b741a81fc870e648001027161144254d04c678e4f954e9f191bd8b26201aec681cbf0654b00000000000000000000000000000000026ede90d14fa0885baad21f9631bae058573251cbef5757bb8cfad061f3bdc78834fa5862dea19a2236c014b0f1652e0000000000000000000000000000000009844d0cf7f6f3401145d8d720defa577ca46b49e04e39c4c139ec6811a574e7dd5ce3acd00d1ce9496f10dd15c6d94685cc8d88273d4aa822f44a447cc22f5a58c420bcfe757a459772825619669a72,0000000000000000000000000000000001b0aba02b0e907c03d2f4003083c824ce60f2f55f70dc6ec7c7f81f3d0ef4bf533b4c94833e36e8aa7aeec18b7255de0000000000000000000000000000000004fc227a6ae303f3006f75193cef7c653e6bddd28fdb843b41c7d39966a701ba8fcf611efa71abf059d7d98833480e69000000000000000000000000000000001077fddd0bf3d5c80eec653916f9095e900cf165315d74a872219285f62b5412536e43c4cdbc120ec5c7753318852dfe000000000000000000000000000000000ccd90e01c1d4a00f0d9e29a88e8134f2cf68162da66bd343645a998730190114a6921c9b048dda58b60b42a133287f2,55000, -0000000000000000000000000000000010192b925fca096682acf138833b12d96bf97c9a2e69e4266eaaae1785b9008f36082e23e2d42341427edce24449935f000000000000000000000000000000000d5b24a94adadbf542aa663114096bc670e1b6c99f3b661f55de121922452534faed7f68d6b431fcf6f3e379d7acf6b6000000000000000000000000000000000acdbcae49206b749d8c0d21017a33e689ebe26804d1fe7c863a2ea4210c3559805dcf73685702bc56e644b4e02614a9000000000000000000000000000000000092309d684fcdf44bfa321d473060dc2d8a8c66c51419894a3fbadbf1b56179c31dff25403b970d543f1dd0e19e56cf5b6e462d809f8bf1a62f276dcb27e42d9aa0ce33fc4e149e87181aca70a4ccc6,00000000000000000000000000000000185520023714580a3f235e24316478b8260565ffabd39670811519066844e131e337bd62ed2069bc6d2305e6638e539700000000000000000000000000000000055fc74cc7cd3fc393d5b5ab2419414effb783ff4da2516e5465a4acc195339c7b5238be4e0744b3d7fdbce46ca7f5dd0000000000000000000000000000000005f584a0311c02d611c15163529130a2fb3dc853083e7225b791ce5ff32d5ef7039c80edfff317ce9ddeef84443b5a51000000000000000000000000000000000f9d5acb355f767cc6286cc09f6df232532f9a0e9e4ed1fe28788abecb200e22066c23f3ac6c49c47071cbb023e70183,55000, -0000000000000000000000000000000014441b14765eee30e8131a7ef62c3b59370f2f6f0dda20fb2a3654fa09492bf695de1d1a8f250bfde3c7d2ed805ffaeb0000000000000000000000000000000019d813f8be2519e89d42a9fd3fef09d44a996d6a4713a9c224bee10f0ebb196370d6231fad810edf9cb4c875f08357890000000000000000000000000000000001a5abea13e909bbefdb51ddc699614366f271b2f6490ac8efcca7759833f3feae11057ab1b9ea32311e7b6ea6de110c0000000000000000000000000000000003ac2bf3c5486ca176e34ec5212165cbe04fc9e8c375e3e999a31fe014eb824ea3f2d06b9cf8b86ce3a76960cf2eb4d7535b53ab5f1c596eb966f57867e021d0f3b099e17bf384479c959794b17d6a4b,000000000000000000000000000000000ceb56d75f3aa1548c50d7780ea1e33c3d069b2f37e7f96be6a8ec03266fa8d0868822afb3b2e54750722266f6032a8000000000000000000000000000000000080f15b7f9f2c22f1afacf558267b5b84f3a6d199fd3349eefa2e46c4f332849c0955d19d4513151dc0f3b566c0058440000000000000000000000000000000013305f8ff6080f7da05c28155c0c2bc1c78d855cdcff0bb2c6b82cd5107d7a070d0830e6705f6832ed5baf75a659c8870000000000000000000000000000000018f4e136859b4ceb230450f9abde0325a4d59db98279d7fbab710305ff53250dae1c8789cccc27586c9b9df5c0c4722e,55000, -000000000000000000000000000000000598e111dcfeaaae66d1522be2a21131350577253a3f33bdd74a04b0bfba2940e73b62fefa8f0c34c4aa91b633f6bdfd0000000000000000000000000000000017fefff7d94afbeceb33714e9b5480c3a2f3eabf9d7f6e8507ae54cb65f69b21cd7d04d23f24e3a272c589f572b91864000000000000000000000000000000001652e3f5a99ba8dfbcd1f90de955ef527947642054be603c1b84b24bebb579b78e2a0be426ec21d32783a0e55f0178dc000000000000000000000000000000000a6c9ec91e8bc86ab198416cbc76239f0ac0b903f40310ee1f2066b01b08191538ca913c2736f53f23ef37fea13d52756e0512ecbc5a1b02ab19bc9bee4d3d9c721278e07b7a6e389c4d6443232a4035,0000000000000000000000000000000002a0214be95f020c70221fb4fb6856af7ce3845a4b607340f85127b52f8a204efcd94a152835860a4ddeef84946671b1000000000000000000000000000000001767777740a9922a91c39a36e2cdfcd544df902b31812ffc88418dab7321f73406ab142055b5bb264c187f2d4f2d6f9d00000000000000000000000000000000026e6941364c74997506df0f9fbe6b2769839e8b7c7293f4e63d13bd7bee90ff779cf82adc2f23c569d1e13826cdb0e4000000000000000000000000000000001618ab2ffd4b823b9c9776baf849641240109b7a4c4e9269f3df69a06f85a777cb4463b456023b7001adac93243c26f5,55000, -00000000000000000000000000000000072e022c168461905f798e87425f2eebb517e473cef98c255d0fe434863ef5811920af65bc946b29d489b5dee1066c56000000000000000000000000000000000e7a9872caa82d191f6014c845e1b3ee4ea1ee89852b546a2c85ddbfa3c1d4ce99002e3d7732ccb8cfbd57d550285ab400000000000000000000000000000000144be65db373f6401d76e0ee64e51076b861e8fca596dd6a7f3b5735c23b0cd13248404fa0969ecaa701663a1032f48a0000000000000000000000000000000014c9e9c5cffc4518889f7742440053678ff1d9fb1a1a103d0c1f762b10655bd5849ce98f4bc5eae80bdd9e767aae4523a79fd15e80b694122dddb01f836460b3eff99e61ea6309d6b395c94fb5a43dff,00000000000000000000000000000000054ce66b9b0b3cff6637d6cfdd788719d4e33516b98402d8fba54725309307711fb576299ba99104d4e7df0deac9ea2500000000000000000000000000000000055e06ff52cda9116a98ad3709f788d39db53844b7db58a57af52848ce1c59ec2a1f083efe79c5994b9291a2d1020fb900000000000000000000000000000000040c9ad63698ec78d06b41bdd6f5eed089b67f106348f9300f822a2d61ea1e5d2ddda0efd1025825c99cb0e243573f7700000000000000000000000000000000195dd00c48186f8d1337ca857aea02c4d199d638133e9cbd2dfc5f633502f656343746ec2a416465c3c0d4e9d53fd097,55000, -000000000000000000000000000000000948d0f0c20715f8658e1f2b4f9d32d851e584287225a2f47735a1f4c241b07f8d7c5dd8c13bcdf84e97d49817d4d88a0000000000000000000000000000000013c064548cb756b48600dd535af8eb5b9138f984bac0391df2e90a204fcb6c36017df910031864d802a2ff719856b336000000000000000000000000000000000000b7eeb7c9a01be88e573f196c2a531635baecbc8cff9af385455af3757301436686596ec7fe3618af26953c49f7450000000000000000000000000000000001332f4dbd5461ab9e2c8b3c19c6ff407a071018c92d2c17c1d1d481c24565276c0f55eee8692016c1fd76d70f44627cbd012914a96253926fdaabec06944ffcdb4637a05e3e78a9bcf1b21b68b9dd9b,000000000000000000000000000000001141b59af8fe6cafdf2e247fcb0ee4642a9b4022b6d71163ec9b6ac2f7d10ee3c5c0173ac686b03cd6a7086b039ec786000000000000000000000000000000000f05ba6973c5d865ac5c037583b65eb4eac826b5a04a7ebed1e10bec6ec7dca93b1c2eba70ee0189fd552d5023f2a87c0000000000000000000000000000000002e54475940985ad2115223c5ea3a4c95890f3e9992e3e1a6df2170ab77143bcc5d29b9dcd1ed3bf16e545e9be21a8640000000000000000000000000000000019acc4705955761518cea482b83e3726dea8d1f66a5f19b06cd7ff95828e15d1b139077e0d274b0e6fb86c027844d97f,55000, -000000000000000000000000000000000d3ee70610b5029a28e586f0f3e65bb19a263db3438710fcb8073e1b25f83db50eb5bbb9d75cb20952a225023f747baa000000000000000000000000000000000682f7d5cf9d182b20ee88683f3915e8c9b03074a373e573aa57232de4e997bf155acf680e365aa0988989dfad102b2e00000000000000000000000000000000143962963e230a9154dc328f9583f5be6923a3b10ee7b1d0cd5f5cbff13913d8ff78ca315be7387900a50b94449884c0000000000000000000000000000000000f4f934b42452d41cc20d7b1ec547bcbcbcc10f215364ccf2b864db23a09d06e94c7a87165dcb691f4975323486757ada300c7e1041d94df0e0201e1135fa6eafc98bd33b2dfbe4c59b546a52538c07d,0000000000000000000000000000000016fb5839fde95111742255b33f040c41dbd0f142d1daa8abc7c63008ba9f63f07381d9d6128240ae9b6cac5befad84e5000000000000000000000000000000000389a11727c356b8f3bdb6a73bc2f6d2d73d33d287365283359521dcac64f17810bd58c0ec5bef4db253bf630bdd9599000000000000000000000000000000000629a8af1bd0c1b1b6d7e447bb779663d7bae8e895e09418bc350e644d7022fa877496f30e2018f5dd1c9683b2715adf000000000000000000000000000000001950185d2574fe0c8277e3f93f59dc5628ec3487911ba9c3194a2f716116ff0bb9a39dde802dcfaa61633ad7657a578f,55000, -0000000000000000000000000000000005f0fd4080e26971ab16d33aeae04220ae23781da3179e38190082f1d167514bd73bc8ef976a2f333570e9f56a6c05e6000000000000000000000000000000000e159905d29b52ba61575c3a263093017783e1028b3701ccf060c165ba33a765b5265a9b1681c1759bfe2c9c401275e9000000000000000000000000000000000c5ac0bc29a49a7c37d772954da850e6b5e301e230552be9a94017d770ebe2cf4dcfaf104633623e024aef6db57892900000000000000000000000000000000002228e7f42a9409acab49cca82cacf306f6c6c29fd9f7e2ed12fef2d16383cdb7bb2b39ad598b301072c615232db1fa833e9cdb10fc117afb17803b61a2bca7de1d190a325639eb23743f51f28294b33,000000000000000000000000000000000024c03edb9b54034eacca4b321d51397348c57f406b074b16a9d6215e03f842380f5358f5c095fcf5bf3cabcbabdc260000000000000000000000000000000014e62dc442135d729f65090475fb408ebae132cdf2c2932582af887ed54696f3cd15b163f11285b99e8d8f809aa2e65d000000000000000000000000000000000438a2c99df216c67d92b99d9ee8cbd0e9751e538074d146767bde9675ae3a05bdae051efcdc6bbddeb1b7a8288370ed0000000000000000000000000000000007c462a8f5720e442e1917bf75fc3c3dafab6c39c80d0b93d81d1db4080f6e199be092b4b025e7b02efce4f30d00299a,55000, -00000000000000000000000000000000180569ce03e4a0155285e733adb18fbca71225507a7adf01cb8e8648891525305e92087f58378f4fd8455d5632ad660e0000000000000000000000000000000011ab84e42f10154e306a568d7cf7bc381000f0add0500cb508f695a3b283ea69d140aa0ad48fce2d2d6fcafe60761078000000000000000000000000000000001136c3016474d6f475609606e8d0269fcdab9fd3188a512681cbc41eedeadfa3b3d9355e5b4503e8b5c3665e49fdf3ab0000000000000000000000000000000003f56cba1b9cb4302099b16b09c2602dfab80d1151685ef78e5054cd454b319adf8b5998053a5b9fddcffa020595e3bfc48b98edd9c229037751d02e58f3d4234d9a3b0ad9ae4947ae14beebb274746f,000000000000000000000000000000000e8137c15436264b5960c27d0c22be7fc5d56a12f43b3832ad0d7f5abddbaaccefd140e2f7c476b99e6fd9b3a52743600000000000000000000000000000000019ee3caa56f0329a2e2acb8907b3edb21f4eee73e312352796b51282e097f9b10af61805d5c222332888737c7f8e227d0000000000000000000000000000000012cb9c610391940fed7882a5cba08eba4226c36eca8a2ed22fb5e752e0a1a5ec556673e47013258b499268f1de77bdf100000000000000000000000000000000031b769f606fa25b81a982db86a1cd442ed738019e7e64728ecf485cddcc17d9dc271146196178740b9f05f56627b061,55000, -0000000000000000000000000000000004d79dab9eef873f3415d66172bab7166ce0c71f322529bdeffa915c1b0d3fcd645c91dd3450ba61593ffecb95edb91e000000000000000000000000000000000d611a207d3222bba199fa083d0459675cb5fa00839fb4c9034ad868fc1e79d653c18651771431d6fb6b6b5ce8cf6f7a000000000000000000000000000000000ce802ecb106a4f0ca4efdcc058dd0e29deb6a5d30a2c15c8eda896bcdd3ac19053c10105328d239b26c5ddbdb3a95fc0000000000000000000000000000000001073e142621ecbeff6f81453660362545751f992ffeec3a83477fed3e6215a709ffe0d17b65d3369f8f3913bf000e844228758d2cf8105f2ef11d83018157a3119a44874dc34d5f0bddb533f50df52c,00000000000000000000000000000000080807a0570b628549629d2eeee39de773badaccefb76e01efaecb0ef0356f535d32c3947f0613bc7d847ef8c8778f02000000000000000000000000000000000e8c091ea30465d204ace72015cbef29645206390fd92ba7c4aa0fecae4ecee53c0b06e1fece99511efd8c7e9cff1a8c000000000000000000000000000000000c881c678c94d80164bb3295acf4341fe6c726ca64a1a015c890450e719b85720f41f80369f99ad3e7e3169ede0113e00000000000000000000000000000000008a2fe01a7100afda40091eb0b2b14cd00b7a4d8bb5cf9d9a3847970a94f2035fec7f292c04c38d7e49890e612830aeb,55000, -000000000000000000000000000000000bd84f04b3858b1138b1b429c7216d5d1b1e99c1e0fec26440d59b1ad79788c2d5583122c2ad769fcaa6d10d816a1f1e000000000000000000000000000000000387977ed1ce5da51dca230531bba53d17d3de5d593ec576cabfe6463d5164d7153025dbd4cb3525c4145c4f6b85fc76000000000000000000000000000000000a19c943a90fec6921367a2edc5bc38a5c59839cdb650766a2d2d068242463dd4460bd1d0e7a7fb0e3d2104704b8b3730000000000000000000000000000000011d99d44b200feebe00bd42809e3f67a23cce88a07165416cbfaf4db14420f99e54d62db4280d2c99ca0bc3dc41eddbea417c96f0cf4355a78513c77cdc676a7b09125802c8045756da867e0025a36f1,000000000000000000000000000000000d17f6d9460566d0543df2666d6ade685565e668521a87fabc58148343085415fee92c32907311c9d04713c34bf7690d00000000000000000000000000000000185da28f07b86885031ff5cda913a85b0e4d07673f456ecf2a9f0fd1b21d99e22442f9b705039252d57380b6a42912050000000000000000000000000000000014a4bde5973ef43691b61b3c0f6c2fdb4bcd6ea88e53e2787a7d93ad6e05ee2e69f2799712520f72b3c577ee278008ec000000000000000000000000000000000d92a565b3d8d0fded054a75198b31c521e3223650cdf762fbf7b851f7ac0fc66b8c86c20b905117585704c23b27e7db,55000, -0000000000000000000000000000000006a186aa584a466a860849c78e4922889c95a4ac6f39c99029fbb422c43d699a8baa51aa4ef51ff99557babeb3e9506800000000000000000000000000000000065fb15b5a0923bdb52dbefc7e9f1a898e32f17d610bac829235446fc5e1913fffc8176e0fbd33091505761f1d06d8920000000000000000000000000000000008bd358698fd073f660ed608462cfcef1da9a59b10905f1d98c4fe66958e56802814906430c10fc25a4d351d91f91cb0000000000000000000000000000000000a53638b1b6c6eeff468e099446300ca7c7bd899c6494682d14fdabfa9cead0bb37a0325d99e7d0ba6341cfa1d257ba846561328b7689b0a89014823537cf9eeaca6ea5c56a3e58d2abfc2ee455dfccb,0000000000000000000000000000000008b1ebd753364a5a0a6296ab48b348f91668525c0d5f7edc4f2d29844592f34a209f9e77f94ebb38ba76bdb3f96063ec000000000000000000000000000000001062e0ff0a67372207052e2520d8b2823764a5075c94011afd6c60288e187ec77e08db01c95dfa195f2409b58c9dc4e5000000000000000000000000000000000cc2b87b613d97a716586f371c457fa869c2b8d1fa1cf4b9e8c34bae23e0544752b997df4711d0712ec11d3a9d96ac2600000000000000000000000000000000140eae891c87c2026f0b1293df2bd8ae2dcb0ab3f8de74676f37c905334ac1f53fe4b75511691dcf108fca51abcd524c,55000, -000000000000000000000000000000001070b98c6348a67e996626ec2752f45e4c007e9c9668459a777c03fab633c10236a1c5be99f3fd950542d5648ef9e88400000000000000000000000000000000073a564401cb1a3a53334c0a55da261814d27b86ebf40b02a76b20973ba2db92e42c138ca7790261c2d70401c984bf470000000000000000000000000000000004212d8a9e4b01f5c6814a88561c2c6143eea61327b031a2e0e4bd056c12dd7098fdfe4d1511bb441ad42b55b584a7bc0000000000000000000000000000000005c5d23824b0fe05eb962194550681c57c1566b315efa8ebc90b3593d7d86ad18328baab8118c9f47eccc0757588591ccf6c3fcd4b9e6b72853934b306a078b1f2fb17879db4a0a93d484abbc2b746cf,000000000000000000000000000000000276a138edecfc9378be4e241d64cbb48bfa6fd4fb1788f8bda870d5ec8b2132fc9ec888ef84c43a50b7de0527def36800000000000000000000000000000000153e90d52c747859f88223555bc8bc4e8b6fc846fe7028de728a4dfa085c6e350f9f1d12b9dca4ca8e07377648544400000000000000000000000000000000000cef00e7217da6df0a6d85f40be69f154300c423e86e54e513b2491e65002e308445238082da69aa9e5e83b5f4fc17dd0000000000000000000000000000000008da1da2a0d1da9d2158b9408dd9b0eaf414d237b8219fa7661e40c1a88eac2f9735d0dd6ad67b85aab85952369e8287,55000, -000000000000000000000000000000000b1b3053774ad5515a20bd4c556d2b3ba95fe74fd0c955069c7f933dfd718ede90ac295f5a675f1c29dcd9701978353700000000000000000000000000000000145746ce88686021a0635bf6f0aa2f77c48bdb364cf4ffa804a57f95bd69d24eead05fbee24021c1ef57e1c7c7b894b00000000000000000000000000000000010ec4795a0762b86f3b83de1198698af67fd1b1be3ddef48f35cf82bc96d886fbb4c75064f51a9cfc5f61630c95d0ad1000000000000000000000000000000001465e31f58892466b8ae4b76a239d9f8d1ecb1834886344013cd1df0be13591798868d224d38213a6d75b02a1fde0ff2f6787b565e8d71be6fdb0c97c4659389c800a2047f668b366214adc716f402d5,000000000000000000000000000000001484993096c210c7bebbc4c0bda24b44a70e982b2528215c0e8578ea55f1181472758caf935aa0a3d6820cdad753e2f90000000000000000000000000000000011802324a6e03c3174bbe7261ecf3812c1a97e1be27269214f232274a3bf82775d47c5fdd70fe1c57155068b296d394200000000000000000000000000000000050f43c874c1cfb5fda81059cb7b4808492632fa20369dcfb611e503ded81a49dacff253e31d7e27ee84bab79e3c5d53000000000000000000000000000000000ef945b6f210fb09bf0ad5bbd4b5a6630f43304ddcb396807c967eb5146741f7432bfdcbd7e5f3d29917781efb62e6ff,55000, -000000000000000000000000000000000f39e731e6ddb7496448c912ae314e833d28208252c7f8e27bcf7eeaf1da6e2310538b4ef0d55401c6552e91fd70691600000000000000000000000000000000069d3612f924961f827497028737000513548ad8e104acee28f014e730d4752a583cb9a893e6169b71966a1c4a4ad2dc00000000000000000000000000000000090899907edcbd336bd4fdad0dd67c578ced4481a25b864b32aef920842689a2c23265277a6e1d4a1dc1b5047a9f79a000000000000000000000000000000000055ba64e2502baf68e46c759fca30247a080464eda2b32e7cfe539e545d6aac6dafb731c2c45749e50513979cecbeb5440ed91f6ceb2ccf87e4106a16227a3cd7b2821b4f3a6e629001f78ba1aa7346e,00000000000000000000000000000000028233bf12e8dbd8510f119be30ea1fc13b755c6ee3ca2a3637a3bf8f73776c9d1fe231b713396ffc579ef9320a05b150000000000000000000000000000000018e7c00b8047d64ca0c5df54486439c5fb3d1414c2f71cf8a3ed591b7c45bf18b37473daeeadcb625eda638885ddb9870000000000000000000000000000000018b89c9b6bf9ece36f1eac08fc35ffc9f7f964a0a9b19d495ae1361fb4bc98aef8770efb47d9961aff694b878d659818000000000000000000000000000000000eb2fda2c29c6761e35ca4c9772bb232ea0d297582af4f50ef76c0b74fefd414b535e356c069f54ef5224225e95be6e7,55000, -00000000000000000000000000000000042f1c8b9fe81cdcabea047d0998a1354ce09d62a14f1d0e9d188e2f35f2e1845c2b090c5e157595b33108c67e6c184c0000000000000000000000000000000018e69d3564d4ccc0306e1e6b227b0f961aa9afcad59d4ee1737f980dc876609c59a4c6a3506f987467beba0764b857000000000000000000000000000000000012ce5883156588cfe0f4838f819f985b09f1eab40a5ea8e30fc5d70d029a01a4537641248f4c21dd203909e0170737c80000000000000000000000000000000002888eb9778a4045feb5899dda258657b9f41345731ba630fbbf186b3be4b58ffc7f48abb65b693b573a73f85440a7a7ae8ddfcdb4748981acb9b2037c017174a140f2457fb0148fe807fd194a9f7be5,000000000000000000000000000000001239935827fb2a269ab064a3ae2bff2555f89bb3a71a47ae815ef755fc1363a89d20326855cfdd0e13f6c85f727bbe120000000000000000000000000000000012fbba047478b5f5b07a582200271a0c331d6f76864f9b6c6ef8ae6b0965eda481eddaf72c7a887b21719164c633d39600000000000000000000000000000000017eb4353b413437244983554a639a9253d105395ff9652504df7700d879cd9a32d5f0824b1eaa532bcf2fea34f8f08800000000000000000000000000000000054ea45475c01ea0557fd143b21c7bdcab6d287bf6bf4f88b6fb06e02ac6fc5ba96f323bb1fda3a1c4d8f42d01d267b2,55000, -00000000000000000000000000000000051982b46a819c74105cb36da871fb2415328a1531d155856f6551bd043eca62ddb61f24af429edda830fda31e22cd340000000000000000000000000000000006449e5bcdb5619aac542f6633ee3e06a4fd56a3e1ce4034efc608131ff6ead70ca63e70f494f519d5c577ae7119c8c200000000000000000000000000000000153f4f5dddd5801fbf7f88a735b9170d24d5b63861d50cde9644579dcff277cdb0d5fbfc3b3b819a1172de05afb9135b0000000000000000000000000000000010fdea84983fe6c08cdc4b4ccd462bae2ba791ab5209363b10b3ef342c9a5e92184e9d8be1419e3d88402bc05bad5fa21268803aeb58a2d57fc797358fb456d5cf96afecb1ee0d2b90782aa0d652b8c0,0000000000000000000000000000000015a145e379b7ecf4566a039b753f91e8ad75d9e9c9a20725ce34a900eb9a1bdf66cabee2100208d7792a963d1fb8c02f0000000000000000000000000000000007f0ca14fc4e34bbdf5008d632dd112c7368e037ce019b7c4ec412000ac02302c85ae64f9ab495361fa5b620e46420aa0000000000000000000000000000000017c00a08bba18426dda40e773d79733030b5b3b199a62436ed06b773fd1f10688e8af00e8a223cdf242bd1ebbedbf634000000000000000000000000000000000a17365cd9f7655793682b72e342227048da0cff88f6ace33ddab548ba126017e4b7f7439373a893e3b5803e662814b8,55000, -0000000000000000000000000000000009b011f793d9a939d916d058ffe91b58138820a646cc450389b3074ae3715d06ddec1075afecda71c65c7ca085210c740000000000000000000000000000000003d4d20f4b93c1e90a0a06bd534d8b4fd64e4c4aba77ae42cf4c5b2bd95f8b02ec4069ea246ff46404e6c9eac632fbac00000000000000000000000000000000051e88c3adfd4d6a02d3f03812362a6cfba3a6c69b9aeef75b51106cc7f1750293d61e31f0ea29b5d7aa56debb6d2aff00000000000000000000000000000000086d9c4ea6769cdf49ffbbf7351023b4aea640e8c90f9291222fd0b5984bca4d481bf7e10df921406a34804e6a09f99df9a8a4e5c65973b785c1e2637937de239bb0fde34b786dceea66f6bb12eb4169,000000000000000000000000000000000081b4dc78b74250a82da9d803876add659411cfb467860b2ac6f0f68929d6377deb71d6acc9ea8fc8c1286b8f92056e0000000000000000000000000000000002c5fde71346a255ee9dc896f654eb2e0c66f4cb4c51541d2bbccf2463ecf0085a22b9d2bdc5bef39d80c4477824f116000000000000000000000000000000000ebda0cd8bf6ac7e86a1bdbe44ed1e15f8ffa1fff92afd67fb564306882f35037b61cf0d93f278f15149c04a2e83041f000000000000000000000000000000000fc38aa811f5ec015f10a99bf175f1479d4983c9d2180a5e3da88b4e9b62ef50560ff0a6c2fb7bda4c46c54551f8390e,55000, -0000000000000000000000000000000010d48bf523f3909cf90aa58a9517ef5421f1212accd5e8a0f830aeb15a587e215ca9c340bb846b1d0474e43840b2af79000000000000000000000000000000000cc1a3976caf97b9d59f448f6d9f413eef8904f360c0cf912fe942b38d7fcc637a17038973a133608ae769d3e389b18a00000000000000000000000000000000069a6122c6f0ec68834b7617c755a7eb33a80a25acf95859da5ff03316447182f122d20d993b04e79b6fe859b7adf5a8000000000000000000000000000000000058c6f8c297524319bae6722e0a957d1ba0f75ee3a8aaf06148641c67925d15780e419a38ed7e07410e82769da74f2d070e7e2ae2751a1f71962726a31f77553c2da38f4fecda435b6e5459d5e833b4,0000000000000000000000000000000007b46fcfb2cd8efe32754306ff2f503d7434168c1c3cbd7c80470cc5a5c8bda10a80bfc0129da349724d2d6431c5ac90000000000000000000000000000000000e1078f4f4ca993d90accbfc036219507bd22d00930ffcfe1227780c00914fcff845698b2541510daf59cc83d8b947e7000000000000000000000000000000000b7c6d9951570e685d3a71b19a38f5485f974f85fe8cd4b4c196d33a18750b278b6d374483d81dc3e15c9b8b9b5dfdd6000000000000000000000000000000001003a239ea4a2f213f0f646bdb62cbe4f98cfaf7298d8b2e0eaa07bf3f939e779caab5ffa0033467c5b297166df657d7,55000, -00000000000000000000000000000000156ca5e80be8c8c03a5506ce9abd22a9d4958c372678c0caf6f1329898507dfcb1f06a9464cf080bc6881fa5b7df1ebe00000000000000000000000000000000088174d486b4086b931010da298a399e15b60a113e08f571e096d3a4e94b57b3a684711318796eeca9319119b201abb30000000000000000000000000000000000b96ff68505c088cc03a1c2dc363b05bc8544728a12b29569bed137780523123eb17e68f4632383c252d81bca0c5ca9000000000000000000000000000000000486fc6e5224c5fad56234c41856e60bee4a6c1046f673bf7d5c1bbb603b141fc91074da5f9d3d41b796a2ebcebd9e74d16aa883a20307f5436354bab32b4633e83178f33626af3edb14f82724b8e125,0000000000000000000000000000000000ea29b1e059560fec21c3692d4e632a45c88a807c953fa23dbedb271b049d7fc717333b498ed12573a896f872e795dc000000000000000000000000000000000de0d10c47df92010a6635e3403dd6e91a1bf35bfcae82c1008998e86aa2d18a6cfd3f2f1207fde3bb39b723ec4d3ca60000000000000000000000000000000005e2aef9cd37430b15e5e76b2c7870630d255f630c12e865caefe308a39833e00319406746dbb2af3ed32135e91eed49000000000000000000000000000000000c229fad41b0d27ad7b5db33188fa70b97f22e323e429ef65fcf98f5339e908c31df8859b863356e0fc90538c5c49cf2,55000, -00000000000000000000000000000000121fe97c62e068988ebff21d8129d52aa903afdbb62862c7fd99564d9ad72182ab1f3a1100223ae486cd76f6938e123f000000000000000000000000000000000968ddedb04f52140160061828b5f88dfd09aaf37df625ee6f66b9500d6608df31c7edf86296eccf8f9918b051a5e4df000000000000000000000000000000000b7491cb8f6252e3861d7160feb0afdd736d27886863ec0909a7cc711a9b71aace18b17a00a2999dd57ca1a74f148516000000000000000000000000000000000fdb280093ef45b12b694ca3390a865ee18e4c04b231e2c98cc28706d4cefaf4e654582ee03f34ecf1dfa9674489d553041390a2209b80f7c64d14965cc2f515d5fbdf37953f75c4a0203bf0d9fb674b,000000000000000000000000000000000444a00cfd258bd46f659b09eef17be9929008d3d1c65e46cdc762eeaa2f0b52abfd636e6094e21983fad8171194c71a00000000000000000000000000000000090833e68614be5bf298e04e44527480cb35128bbdecae15eb95d6931a718f66869ddb68352130b4dd8a921ab3f26d080000000000000000000000000000000000994015b1b55340c3839d48320d178b2ffaa0bbff038f7aa63d4dff41a217582fae9613bc537fdeac8d0670c0cf479a000000000000000000000000000000000fc486e2a1680c10ca28d4c3bb22dbccc9572036512645bf868e7693ae4591569c973f9ea26342a573e23a06c2fb4b70,55000, -0000000000000000000000000000000010d001a09cf5dc3276482185f26ef3f75d28cd6d2667eb08a7fe06c03b99f3b6c4d82390739b6867a314291cc642a8b2000000000000000000000000000000000587846a460b1f37c2e7f491f9a097b4e86e1943d9cd0999313f65627b3907f09b5d5ac1be376a313a959dd136f7e9b3000000000000000000000000000000000af439695556e86b102926d3b40e3e54cc84464e120de3b4e3c5541a6a5bca44151fb0594009663764c1824518b13f020000000000000000000000000000000003bfd9418c1e57269e222152d321b83ae090f216cb422956dd1fcc464f68526cb4a05cdaefc7bbe6e81d4ffe27d64db47cf23dee8d95d94046678f3bdb4b0ea3d4e3a1a2f07f582e2a98ad6eb7562cbf,000000000000000000000000000000001375bd5ee66c330796bd8381a26cefa3f40f8cc8de42d4d59a7adbcd3852e6d632422e6ad9a06a6e497b23b17b1df87500000000000000000000000000000000165d8e7be17ecae9bf51a773da705aea42536d0fa3a2206267da50451f5104ee241811dd0e6710a80c38df77b126c009000000000000000000000000000000001559572407aff34969f83c394d2b095a7ae9f53a8e6c923910f256bb87b6ec076fa6acb85465102fd24d34031f88f7510000000000000000000000000000000015ff9ba89b55ef75f63732dec1e64106d7a912a6657fcc970dd011a03b5364117cca46d6cbafbc0c5049db10fa83fe6d,55000, -000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e,,,invalid input parameters, invalid input length for G1 multiplication -0000000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e,,,invalid input parameters, invalid input length for G1 multiplication -00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220f0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e,,,invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 176 +00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e,0000000000000000000000000000000006334ba1e361fd94bbd98f44b75ae9ec00ecb4d3467b5528870b1a1fa9a7d04449f12af90bd4c7a1e3f29e717d6d19d3000000000000000000000000000000000bf4cc1626393956915845ea7ca43d30a59c7196fbe309f2d5ee6de7e40c191d29821dd6aae46abecf634b904de8f7490000000000000000000000000000000014aeb09e252cc74610ab956057d4ac5af95cbea8a6baba9e5062643dc037d6841044cb38b22d7dfb978fe0b58f94cc3a0000000000000000000000000000000000fdcd73452fc1ced1c06e6271410a48dea05afbe889a692905e1baab8d72418c62531aab8b74842b51016f0a9cbb93d,45000, +0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d93884d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d,0000000000000000000000000000000010e70bef8eb893377e7ff92168d7acef11c9efab990fbded728b173b94e1d99e471a8357f16625d353287086543551850000000000000000000000000000000014043c1f00221c439e5febd12724a9224bccf0389914461644daf329208e869b1bf149880dccebccd440b1748d15e944000000000000000000000000000000000f7dee1e7d122e410b29a9eb011ee700c2f230cf8f611e196ec66e153c1fc331175532a8f9b060b573bddaa705430c2e000000000000000000000000000000000e1f659470eab7c0741bc8777ac9fc8dcd11a6f1b30ffb4265e96b879e795a4dbf851d1149429dcab95464e89f334627,45000, +0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1,00000000000000000000000000000000119a5147fe9ddca7123f721b5662c1a44b0964c37a214cdf3a4fd34166e3b25210344e65220c38ec84d0e3b5ccc7e46d000000000000000000000000000000001642dad5dacf4295b871fe9b2787f0861f158807b2b6c01c2dce12ab053c9472bd3cb98de5dc33f40053ff45ce5c9af40000000000000000000000000000000005bb5761602b6639f2ecaf79f2d1f853fbdf75f4b3852b90808b858993a83f8a0da8a2ce7072aa91e3b6b3ffd0b3d1e20000000000000000000000000000000000a75143b9551d4ae41fb8bd71fdba7826b994c65904d9189a5ac5130a59cbb9d8dee0e016735565148fc49823d3969e,45000, +000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf494c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a,0000000000000000000000000000000017ebc9446f8c8e17dfeddab9188d0c808565da29c0bdbbc4138a44ca3196c4564853be28286b66660cda36832d6940010000000000000000000000000000000007f29a9583b4ae83d3913dcd72590a3f20f39eb5a6d36663c1ef433058e76550085b9c01bf797d98d0eef45cc22ff8c50000000000000000000000000000000016eeaeb123b12d1913ff1e50f974228c79f2b995609d2e3835c8e1d68773b0cd484df57b86111cdb75de1e19eaf062e500000000000000000000000000000000002f5688c1286aed42309896bd65d1826dc64dda615238fa9043669806968b8e0e1e3e77ef192b7df540aaf0ed282a9a,45000, +000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e8964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b89,00000000000000000000000000000000042d0c1941ae0ed5e8787437ad5e2753bba02185317848e8ec2e425ac954e0efb1bca534725adfe87e8507851ee337af0000000000000000000000000000000002db55ae8126cbe86327aab880381a81205e33a351d172c883b9cc184799866a8db5a6b4321496e05d3ef62d00416d9a0000000000000000000000000000000012c45444403dd62d7be3e7658dd85909204751dd7d085f6edd38c0aa9185d3c32407d8c95bba371b380f788d0dc48e0900000000000000000000000000000000111421c6dd0db595ab731adfb4bc76c84a61197cb023b6f17e7176c443f20a4b6f8cd0a00cfa61e831ed20b3c6a84d98,45000, +0000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e2787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c944,000000000000000000000000000000000ccdb2a0b670f199a9b61198e6a2ce2117075733e6a1568c53ca493dc3674c6ae85be2491d2ed983f52e2c7040824afc0000000000000000000000000000000004f52450d7e041c561c00200d5b142b32f2df2e2156e4f6c15d6c00e185e135037a1ed6be15e2ed920daa00e2f9bc8da000000000000000000000000000000000f39c38c18f03ce6baf1d016cf32d7387269940280f2e8d21db4da33dbd2d24ebb93ae3dff9f79b015eee25813d677c700000000000000000000000000000000189df61f7f1025fa6fdd0a4708ff1d53db7d414019c4828de2520af3d36776062350061c2261e46e746a6475fdeccb2b,45000, +00000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f300000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46eaaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1,000000000000000000000000000000001388a59c57ec8ca5e68b99631abdafca1b71352ac35003a55bbc415b48b8171857adda31123ec86a6ed9e1060d56aa67000000000000000000000000000000001471913b1ab5bcf9336665d3d44232b4e58da70285b7b8eb1dfd7c54442afb28c339f56e6389f89b84db0879e1ee058300000000000000000000000000000000022101b4de40b7180ea17bb36bad0a668a8def3e7361a96fbfabcfc4cdbe6f607ee4ee80d0eb2418b848ad056520092900000000000000000000000000000000103cda694792af5a51e04b6422600a0ea6f50808ca54423cd4f59dfba633daa5afea49c85b900f52e182610efb62fe7d,45000, +000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d10000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12dac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c,000000000000000000000000000000000cf5cb957a752ce9187940f63b13080790348814debf84b91e74fd6e822c2735941d61d50d492439475bb3ea7aa849ec00000000000000000000000000000000012e546ff33dee9875510a68301f46d89e6175f5cd9a6e179fb8599a580e9478fb8d92038982551dd29041d8185c7474000000000000000000000000000000000d52fb57bf2996dbbacdbcb4088df38e77e25598b91bcd5e41eaa27b1398eac150586b142f068d5b498e0ce458d3e8950000000000000000000000000000000012295e1d1039abe7a5fea51a04a34e9e8d44a0f24b8c032680703c119d54274d3bc2e548854021ab027b693e43964314,45000, +0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5bbb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108,0000000000000000000000000000000008e4c57309339400ac9b6b5df16972c272d47cf69ba7baf89afa4f4e72703999c5885253cc35686f6c8d277399da2a390000000000000000000000000000000018ad4e1f105f16b0dbb4eb089c51e709c25e407e54b64346224b1abbe15d62fabb231e36a69eb05a9ba7860f772634200000000000000000000000000000000019994d20a7ecc0f234ccb6b1793fa7d1ece64b3e157c579fb05a8c6cfcdd6f5456ac1f4c1beadb69206988ab543bb8bb000000000000000000000000000000000d435e74bed382442ab83ec90dffb91336137932524bfcf9753fa5ddfe038d0b98a045c8ec9deb53172e5662d3fd67e6,45000, +000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4000000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb81596fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f8187672,000000000000000000000000000000001425890b6c46c5a07a79127de4ddbb751227dca4481ab7c2f601bf22b8f6a149767c73bfbf57ee399c0f2d0b12852a0a0000000000000000000000000000000012cce15f53fdfffb5f71de3567b0c0adea65b9321c85677c574787f7048c1bb5e2dc985b65fbc48115aa129e6000fe4100000000000000000000000000000000041398497f975289fb9fc6ffe671a19fdcd3753c82ffd3b2084574107bf7fadc8de462507f4484c32df39967c3751a480000000000000000000000000000000007514a7f246006e714d4a8cbb4e89d81b951b5c41a05bcf35f61283e888074fb3686fb6ecc1a66e491ea1e1ce0738102,45000, +00000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c000000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e977955054196b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea,000000000000000000000000000000000b24adeb2ca184c9646cb39f45e0cf8711e10bf308ddae06519562b0af3b43be44c2fcb90622726f7446ed690551d30e00000000000000000000000000000000069467c3edc19416067f572c51740ba8e0e7380121ade98e38ce26d907a2bf3a4e82af2bd195b6c3b7c9b29218880531000000000000000000000000000000000eb8c90d0727511be53ffcb6f3b144c07983ed4b76d31ab003e45b37c7bc1066910f5e29f5adad5757af979dd0d8351d0000000000000000000000000000000004760f8d814189dcd893949797a3c4f56f2b60964bba3a4fc741e7ead05eb886787b2502fc64b20363eeba44e65d0ca0,45000, +000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b660000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a93b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76,00000000000000000000000000000000048ea2c854a0df7b10a2147db6eabcb16eba340644f737fc99663d1ef26d8ed688c2baaa7d7699c5f540d7605eb48485000000000000000000000000000000000c959efb835d48d3e7a8ce643764f27c365f6248a88e39092e3a6498f04ed851c55b796dacd62ae73d7edf23aa45fefc00000000000000000000000000000000114337b8caa68cea6f22a25c0ce3b247cadae24c63fb02c6a98a728b54f97b12b1473c8e23f55338326b9575a637bb2e00000000000000000000000000000000033167b0668ec650581815cefab61d13661f4cbc6e01711af0aefb699e1979b551d0031c603ee5f6dd4f716ea7aa4a6e,45000, +000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b38173173459836dd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c,00000000000000000000000000000000142f6b71471f3665ee6269cf598fc3587a62523f9753eec48a2461a2e313e376828cf6d1a9ffc9e64353c8a668718736000000000000000000000000000000000153647cc4a5aeb8ea52f845c415651e167ace9f331c1d73eccbbe20a4014f9e1158c281495206de4b841839438a595500000000000000000000000000000000151d07c3f83217e63b332a6c47e91ef2418e9c658353f8b644f23266f5fbc727562f0935b4d892db947cfbd0757ed61500000000000000000000000000000000035bce4bd2d8261e21476c325cb68e581f20513eb5e0e6a0ddbfd4ac4674bc323590b6f52d0cd50010c13642e7e03daa,45000, +000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b7010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a,0000000000000000000000000000000014e83f87e7f66d8ed880ca46a76a5d3bbbacf2dafe1ee055f04af568738f4c6ddf2a93e1810b616da6f64f25c35a7b5a0000000000000000000000000000000003d14447254b61168d36f92710f95f7100cc8f278b0bc9528da763a18a5386b3f5b83b96f4dc426e4b0fbe755bc986790000000000000000000000000000000017f1a79ed64abfe5e960fda02cf3330e6ef5612c1b8639386959f86c970adb797bf077a468273d37996a65685f75ac30000000000000000000000000000000000d973499a7bf7132541c0976bf2e9bb26a2b6cfa5bda720352fa7a180a6b8fe95befcc13de5a2efe58be934cf7d8e664,45000, +0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9300000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd394c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a659054,0000000000000000000000000000000018bb69dd6db0beb468242265c382de5ac342d465b5f72d4e5a24c67a48272d9a1f3af28e0bd3712e16a854c5d91c616b00000000000000000000000000000000072fbcc86b7dee9c2dc177dbabdbbbddb630c98ac3bf3737fd22f99e2b2b690175d9c5aa4b577f78c545dc6a5d2d03c900000000000000000000000000000000161c4218143ab1f0387f19bccdcd08f9caeb2d1331ca890741799ff1b40533076b6a96a910714176c770b25d2c17715300000000000000000000000000000000063098cd9d1eeb899724b40a2d10ac951ba0277db09aad639957f58541dd391fffadc5d97833bb9666b054e12debfa92,45000, +00000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ecf0000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bdb3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746,000000000000000000000000000000000e43672f1bc25e7e0e64a3fd26cb246bdbd6fb5c9084afdc87c888634916e6a6cc9a351cc67a6ac77ab8e132ed6cbee3000000000000000000000000000000000dee9612527c8ee9c574a4c51f5d3504ccf1d5781b59c78ea15294332c6acfdcc7bc68853e70f1f72524c930e4c3d2eb0000000000000000000000000000000017eba629eb14a0636926275f1c2109318ce8818d8171c69fd371751b6de47bda5b00a0b0e3765d05bab7b8dea9add90900000000000000000000000000000000052f0a4cd9b91695e1e58ead1da1480fef08cecef63896aa51ab16da373b99b3b91767a374645ac5932d9c7fd21d4636,45000, +00000000000000000000000000000000000eb3c91515d4a41209a73564741a8ccf901a624df9db22e195a5d02d24b7bc0a12756b15b8d006cb991a7e088eaef1000000000000000000000000000000000704ce8afc808b0161f6f61b22d990d713ae398779e6e74e9b5771daf006ce0bba3a8088edf75156f0e48b92ee8409b00000000000000000000000000000000018fe81e05aff0620f4bdbe4a715e015650497afab62921eba0ab86b649e5a2fd3d54041868928519f537e36448688a0d00000000000000000000000000000000162bd97161201ea3c26f8dd1204a9c6b61b762bdf573cb5d20b6b255f30208ca7d96aa47b46fb8c6bf0922075f1c1ca807f80a5e502f63375d672379584e11e41d58d2ed58f3e5c3f67d9ea1138493cf,0000000000000000000000000000000019b7ea673dad96c8352870136ea262c9ed105550cb403eb1e64ad598b2145fe1b95e5d61f1b5a6ebec47568c67b68086000000000000000000000000000000000f06ff9bcf2ba284e705b12ef2311f1a9b867ed742ee0737567b5c878547b18394b82c2bb97e16586515728245692cef0000000000000000000000000000000019dfd2d8fc4f2c989c7e1016e147f336174c84d380bab992bf1adbffe96d93d4d2d1d1dacdba3adfaf283b184478229800000000000000000000000000000000068d230422006004cd88ab0dd46a84af3905c7a1d329446cc23c1c5adb401a86a9fa76aaf577f77c2678cd8de8685ed4,45000, +00000000000000000000000000000000135aee0e30fbcad798738c10d4aebcdf50c89ce516325f655fe763dce54ffedf94dd74168611e5ae879b5bf5598d62dc000000000000000000000000000000000c728e672cd8b3bf9341bca929c34118b566cd3a80452d7015bee9d5cdc001b1f5c678d4b2cc4f7cac353e7bf326ca1e0000000000000000000000000000000014809aa22e2051e463fba6d49fbb060d0c7f599a0fc5409d34e71f34817e7beb1251810ae6eee1848c60796fb8647dea00000000000000000000000000000000145a4de777d86025d50e12f9a6615ecb9bdd41489992d1b643dd9aa549acbc63b04b0bdfd14b6e45c70f165e9a8c91bebb169138f94093d5c1c6b253cc001ce8baf78858dae053173fa812d2d1c800da,0000000000000000000000000000000015ffdd83355978ebfc386e13987effac0137ec628fff1667ede29cfcbd05e31cf8323959dd0247c20cf28978dc242c790000000000000000000000000000000016b1f810da2ae3c2ffbb6b83c47ef03eb0f298ff4c304ab0dd7b97207949d62858458d789c86c0cd474c34fa720ad3b70000000000000000000000000000000002a2e1463d5e795e6a25998a848b079363efc7d0337c3803385f4f17f11726b04108adfd87a811d709cbb6750c969526000000000000000000000000000000000289a3f472799c06a84bb1f377a36bad910220e1017884545159fe1b2505e8e7473882fcf324ba0d9125495bcbbc7226,45000, +00000000000000000000000000000000009a58b7116dbd6f550f8ca98071813130ecaa9ea86d5275eebc36860690fa048c9ebeb46600b2b63e847bff3e38ed0d00000000000000000000000000000000113ffc0932c041e0e34b2540c485eb74f5029b339cb60bc88a8a749310f33f330dea137e5f340044fd689264af66696d0000000000000000000000000000000002642da3c2c7b6688aba0b19ab29ac72e35caafa044863c364ea8833fca850289de52c0963bc33d7bba40cb5f568718a000000000000000000000000000000000552d35ca054da2f148c119454f6760607b351f2441921a2be17da2cc10902d71571c5554f132e60df79679428fa07e3e40608bdaf3e7764358a64a920cbb33ab4d571c7b3092e1ae11d9697f82ed833,000000000000000000000000000000000b02ddcfbf391a2d6953261c786945093b09377352473a86cfac6456a811233809434b566b9301eea3105eb86922efcc0000000000000000000000000000000015430deba91113b841303120f0738012d77207e9408474998df5e68d0d61f1a64afb947ff93116ae766ca5325046e263000000000000000000000000000000000ab7094055919f6f707b458cda552f25104d95e4ec8d020ea4c17ac1d7efef5c4c3a769120718f1d5171eb8630a3018200000000000000000000000000000000161e7209f8c98e511a698fbf01735798cb632ae1afe00870654ffa0ba93a549edf4b97d60f03974ab0964cd39298401f,45000, +0000000000000000000000000000000018fbbcba3d4b1e548ceaec4a48db62a2420ff29a67af332ee7ea3f902f84e6c375fd33abc33d945c5bca25603979f9a400000000000000000000000000000000072ff416994364bdc6535f36c82212afa822cd94fade69f11eb38dbdcd37c7e22af55fe05e6a826dad822073656eaac10000000000000000000000000000000017bba179b847278a4878b6faeaab3b1f4bd7540d22817cd9aff95557497f8b9d286657b6162c0f89f7820becc637dd550000000000000000000000000000000018e2bfed71aa9b11fefca2f0db8bd9b8c69540267de50bec4fc90a6e9741891465c9761d19282e1100b3707eeb598b31d411519f2a33b07f65e7d721950e0f0d5161c71a402810e46817627a17c56c0f,0000000000000000000000000000000006cb218607a1f66ce361c89fd20edc3f00421611adc9aa52ec35d45e023174962c863f740ac36c984c2b466cfc4827a900000000000000000000000000000000152b22d46e9660da8b1be4c5b14da613731e750ff7eebaf879f7074bf3c33e1528a2c8479e0178707e3855b49f85f045000000000000000000000000000000000c928cf78cee2c8b9da8215d33d189c5636df1e8e9bdaf143aba7ed40f29490ca2328b4a20cfc56f62e4ce49d9e77f14000000000000000000000000000000001574b7a9c3931933160ad4eb17400b6297210db47bca034bc1b5d17a0cb8c41834636b9123e625e5eb0b01738cd6b9af,45000, +0000000000000000000000000000000019efd37727dfaedf697fcda7a59847dbda8ca7cdc92f34e68691d682e20ae6545ac104d6660fdb8f64a051e69298eae8000000000000000000000000000000001225ace0fdce456dd888c9672503b68ef77b2d11caf1265a767a6ea14911e3ca03fc153f18dfe9d95e0cc68b7b8a3a8d0000000000000000000000000000000008a6b059c1c4da046cc0b1b5d7f33270aceffa607daf6d0d078c06f940604e1a0b4adf01a4091306e3c7eddcf3d95101000000000000000000000000000000000f79bae5260a2f114ffbb9273f3049d3ebb002500a57ee0a7d157d86957f43f87a2e026fb9892dacaadca5ee04fc8e176bb3f9e512311699f110a5e6ae57e0a7d2caaa8f94e41ca71e4af069a93d08cc,0000000000000000000000000000000003e17452a80996203fdc4037db072c452f9eb2dae689c77c88b299d7ba266d111ab2b9c4b24149968d72cd143a34fc4e0000000000000000000000000000000014a057d7a50c9b0f34712ff8008770080bfa671650fef43c82726257da180dfb9672b266d4c54d65fdc677d917e6c5b80000000000000000000000000000000013b452c980bfc4a484637b578be100753aee9dda9487d5ee5c017c689dda838fc673804369328192d780d60a9a3de0f700000000000000000000000000000000103aa86d1807de242a6d4fa4a49be6c91cd757df5808501acfca44940733c6a524b851ac962b99a9be41bfc8d6254478,45000, +0000000000000000000000000000000016d2b73eeceee17d3bff3aacac9df9ac1c4248d9ea7d6a503a757f7bb22fa6970bb6f5cb5ec154785f7252e1508b382e00000000000000000000000000000000081edc68bbd8db7b10be06ee23d090bd54f9ca07ef24dfed7df7bb05f8cc26e6889dbd40ea203fd5cca5cb588199f9e40000000000000000000000000000000010d3478508619ea9493b4330e2fb9150024cd32dc1378f824788a884a4a30fbf39c630f465557bf0c6d69b4cbecf89f9000000000000000000000000000000000f20c9b134db5d8b7756800c031bf5962fc560ba95d4bd9157b16179f1a37ae08696a2be455ad8d018aead6adcc69b712a0c988d97e86dccaeb8bd4e27f9e30fad5d5742202cdde17d800642db633c52,0000000000000000000000000000000007c616472f9ac60f749979c6f870b587425d514395ed07558ed287fccabc77f0c90872f3885d0780bcdfffedd124eb3d0000000000000000000000000000000019531e9c25e84a2a968a85d9f1ab61a372ebc59ba5bb7a2bbb3c0d6e4c9d04061b28fdc719735e97ccd5f7243a58cdc70000000000000000000000000000000007772d3cff12bbee916a6569edce0c6dbc2bd8a794919a4dd7bc37024c8273245210511b8f6da551fe626b7b840833f300000000000000000000000000000000186a3e858a83a7ea1bfdaac65c2df1076059aaa193961559792373886c68acd2f9fca61b166a0ee55084a6ea122ec3e8,45000, +0000000000000000000000000000000003dce67181d23af9729e9fb0653d7f79c890fba27de42fada93123e112c4a468fa889921192db8047d86e4db77c60266000000000000000000000000000000000869a1e39d42d9bb0cc0568fdad16abbdac3194af893ebd8dd8f8c2c3c855abefa5fc215412168acadc88e658e83f5570000000000000000000000000000000001ef139a75194f3c4b1378c2b66dd304d179460bac0a289405cd8faa3ff66a7b6e54eb7b8742a68150b1e098630135c40000000000000000000000000000000003892b5a645af916be2c6c7fc0bb08fb5f39341d3c68598940554e1be11e1be75af920db0c8710ed13c78edbf683f17d0b299c14892e0519b0accfa17e1a758c8aae54794fb61549f1396395c967e1b1,0000000000000000000000000000000008adebaa95d10b9fc0f1a1f0d52dd6741517d2ba23e3f9e7a9221039684ae226ea602dbb50df0efd44b2b5bf7495c0b50000000000000000000000000000000008e276e78ead2473602d37cb9f2f589f9c60514a1fc5c215acf487bf57c935467d29945d3d671b41a8e47c9495dbf5c9000000000000000000000000000000000fab06240cb8cbe9afcc4ebebde50c2881e4bc4d4f2ed09a1065e3620e6344fb3c5f3019250ca4edaeae4902abb7400d0000000000000000000000000000000003fa6c48ead374be1dd45c8417ca8234c15ddefc5039151e6cd7fb27f866e134cef2f59ac9b2ec1b26896eaec9213549,45000, +000000000000000000000000000000000264dd4b477f5db65edad28c7153ed919a863c5c5661e0125c5429b323e055fd69c33142dfc6ed9c87082e2be4675e1f00000000000000000000000000000000046ea088a2ec94d3a1f1f97949f1ebc49690c453d316cc46534fa253b34b30323b6071d147d64bb94e02fb4db07bb0c400000000000000000000000000000000013692a33bb1348486eec40a9e93a4ea3810c7b4d3188cd07e235a2c898aa87ee0d17682fd24f4d978f9fb028fd26e2900000000000000000000000000000000115f8b64c00cd5cd344a7b5edc0ef0bb85a3e8f0f9dfb28f8ffe12db3e0d222c2d45dcdba0fbdc161c5d558bc71aa0977064d43d6802ad4c3794705065f870263fef19b81604839c9dea8648388094e9,000000000000000000000000000000001412bdb48546014adf3c4eac4dbe79ba700f90c8030b063828fb01be5977bd73107533a4e8030c8d9cbdde9bcf10649a00000000000000000000000000000000126d3e1006abfeddd810cb1e12c898cf5f543e414438e600ce4c94cd8dbd1e17c0f3b9831add397feda74362eeace6fb0000000000000000000000000000000005b3159638afa34f219513cbcbc51567b16fd5598b85e6ae0d232021133cec25a6269250df2ab7b5ace726e9e2fbf0b0000000000000000000000000000000000c35bfdd1c10e903da6d41e9afbe65b0cd66addd7893fde41dfda8e543a93938cdeab52cc9bbdbe61f93d651bd1c923d,45000, +00000000000000000000000000000000014c83d58d90db4821a0411fab45f83fbc05f7d0d7a67ce75da3ae568978d15f4c1886c6fa6086675c0045efb30d818400000000000000000000000000000000001e68691123451f4c3df6dae62c6a63855ec3597aae33a8a10ee274e902e9aab1460cc9c79726312df0ee0ce90c8d3c00000000000000000000000000000000018a39eb3e3c6c7fb8ee304e55d15e209afe2fe278dda93552a7b9f51fbd778da1502eb6775cbc3f832f8320fa0686240000000000000000000000000000000017c15910fad1ca5749aa82a5a2fa98b0ebb37e92912547fb1741f18c34e0d5fc3a307b928636c25f0320d71cb9d31062686285a0e22f177fe3adbfc435e9c1786752dcf3c11b723539789b0cdeb0647b,000000000000000000000000000000000bcc781f144bc148687875789fd8c54dd820170984b6f8ae75855f7e45619c1d2ff85c330b7743e447b5fc831dce9277000000000000000000000000000000001409aaf3c94c9a6b5123c82a7f311af7c2f60e9b197d49fb5b010f84faff972151b383a83c106de43454f8097005f6c800000000000000000000000000000000064a91226da8b9cb587030f1f4afb0d422a51e4d55212f26c621abc06fc0c57a473a9be75518a5f4f9a7f8d4aaba69830000000000000000000000000000000002cf239343bb77865ceabfcc1fe34cc9be4a1ebc3a70f16f8b7cb84eed5843524f95673b01466d6cbb0d8d9dc00793e6,45000, +000000000000000000000000000000000fa96d9fe01c18732e8d6454df9bb1f482c4b9add837ce9c354c72d49c2d44ec694674aaf0e6d6a095cab7ebb57ccd9a0000000000000000000000000000000001f8ffe3fb7e9e311e0f6949c07c26a0febb181e37b2268bb5e125fc3a100323740d1ebaa5e635dba3770fdc2ce4ee860000000000000000000000000000000012ac42095fdb677720ab3f14bf0afc55c95b43d28d922a5f8cb0bd841306b978751d24546e3a6474976961d0768f29e9000000000000000000000000000000000baf9804d99039c9fe966a696c64bdacc9673b0906b4deab108d34fbbaa3b0905d50892278570564017b96828c7e1ac93176b6724cf984632daf95c869d56838ab2baef94be3a4bd15df2dd8e49a90a6,0000000000000000000000000000000006bbdabfe104b62d22e78bc8f3446a86cd5f10c4c5a54501140768b55a7e6940b9952c9a90a14d8fdc7c04600195cd6500000000000000000000000000000000172e718c926cd393bf303984518432693c304a2758174dabba303ff4c0289b5bf5376b61e8821abab322d53e88f71d480000000000000000000000000000000000a2f84fbdb5b05107a0a340e81b56ddf6d03c23848448f841dc44f07cbf8a575289cf6d53986f581fddb0f2d07e38d70000000000000000000000000000000005cbc10f143a9a1fe23f670a4c47d385f5c7069d8c46580322d6939122b2d39d185d6a8c2e51e88a1d40fd2e82d08b8f,45000, +0000000000000000000000000000000014ce6d88a7c5c782562aa101550f1af487296adebd9dae8252698ba04fbd58b92e2216de6ffd474d5992f97d9f22800d000000000000000000000000000000000ce92a04f5c8a99ca0e93992448222519fc454bda5d1d8638a7bfde968386e4ba0dcd1da59cd81d4c4dca3e584be0275000000000000000000000000000000000cb570796f5c8f7b8aa02e76cb8e870d3365fe4dce5df07ec286a0a821f922b4003d5b69c0f1588206d9544013e268c400000000000000000000000000000000098056a033d9cdae86aac02de3a444471854b909680719154b44d4f55f30087294e39e57643c692d6da725b859239080d76db3dcb659eaf6c086be6b414a494dea4bd30aef8450ae639f473148c05b36,0000000000000000000000000000000011769e191fe258ffd1922295a9fe877ad5a52fde6e343730f8f5ec6cdcd584f8ed1dbe0f55b5dd81f5f78b7437f02abd000000000000000000000000000000001253689089e9192d10a45342214425de36740c120e49f596d24658941ce2b2ecfb50e879be0125e3d159088f88e234f10000000000000000000000000000000017b642d1b5a953f47fff8f0649263f16f41a0ec0397d5a81571174aeb85431c352e2bf6bafa6894d2e6cdb5eafff16d40000000000000000000000000000000017b3438d0ddbd2ace1e63802013b5bac00d31889dcb2d9653a6f6412d157aad2fc45267322a62129087380bec65ec169,45000, +000000000000000000000000000000001214aacb0a5e6b7a40369a83c07fa8cf1786ce7cbde2b5a501d9c1292532df7822d4fde10a31fc0cecce3a7cfe3311850000000000000000000000000000000004f9669d8fe4f884ae93b2505710e6e45b19b7aa5df8cdd811f09e547efc27d21024cba05e2dc9d057055f30ec72d9df000000000000000000000000000000000a852b821b31cd27eca19712a636aa05ef2cd82c36ac1c2ca240edc7d0172b42a72c42d3cba583a5b5129ac1c9486e270000000000000000000000000000000007bd8419e791a5cea04993509e91a980d3ae4987a5b322400b6e4a4f2b636891a1c7ba4de96b53426dd556532403d5a39915646de2449b3cb78d142b6018f3da7a16769722ec2c7185aedafe2699a8bc,00000000000000000000000000000000089a07bf63b8029e0506393828d8593b94b73c750815552f9a3c74ef7470b5810bc27212ba02ca6fdcd97e1e28a52a1e00000000000000000000000000000000051a93291d4b912f0a594d45c0264a9073663a9ec75e6ee81e13e79383d96e9330bab845fd1e5163e5b28c41c4a854c40000000000000000000000000000000016610bf2b2006207046e489294a132937edbdf95caf508f0df3bf8502e641aab9c44903cde75cff3c1f86873e06cc58c0000000000000000000000000000000005d33669fd8a6256dc55f513bb93cce8bae62a593eb8903cb7d7902a7727efb8fb4bb2e5058441c30b99f146ff5394c3,45000, +0000000000000000000000000000000005ef88bf38b2f998dec7302cde829076e6cf69df23aa0bf6bbb39fc0d3d8b5eafba74efb928b1de0eeb3d86ec82612300000000000000000000000000000000011f47e9583997b19c36616e4bf78d6ddd6d67937f493986250ff02aef6e6e7ff074559af2f20a5bf1d67158e4a199cdb000000000000000000000000000000000007777c8eb259a836e6459b7bdb642f878d869fdcb31b105d01f280938ef5377f2775874c099dcd394abe70f17d595b000000000000000000000000000000001607379d1cd34e2d0ed765a339b21433e9aa489609b92414c6b5a05d796085269c288d739717def9db3502e0550860165061073223f066e35242772385c67aaefb3f7ea7df244d73369db1ea0b208792,0000000000000000000000000000000005aa23543088a9a833d773a71275e73fc3081e13c907b8a04a330df7d6c06618fe69e644e0ee55869e364d3561e40f300000000000000000000000000000000010eef9238d2c520f32243f07161f3e35b15fc949b9401baa1a9c5df7d50b2cb3bdd237747735b235862bb57322fd9d090000000000000000000000000000000012dcc16496c95e39ecfd8f0514b5ab2569d89826d957478cdecd4e827095034e974039b37e767a0f25bf057ed715aeb00000000000000000000000000000000000d0593865fd2172ebf1b94c7511ab7d433a276bf833515146adb6d79b6e09d7c18f4c7f4d3241c14d01a4ad0f31580f,45000, +000000000000000000000000000000000d6e3068c082b68312141aa68f1540ea1415e93e7f1762b6f06ff408a9995542da1c727a13355c19f8f418a44de1a95d000000000000000000000000000000000dcfcf2ab12b1a0e521ab402aaa4d32ff649a5a97892eb6ad98487c3c73c35601c313b8130ad12e9098d16eed3bcc2e00000000000000000000000000000000013777b1eefa4af03dc44e4e054eb7a3a980a9c55644900b80346be84b970e1754d1f4ab771adc9249e4accf88a23fb400000000000000000000000000000000002f53b231f1209c6f8b52f99a78bc2147c951ac89b341495f4a60a6572985ce2bc823625099ec214bc9ceedb2deea3fff396ee22209271ea0bda10fb5e2584e7536e8bb1d00a0dd7b852b0aa653cd86c,0000000000000000000000000000000015785bae0c27680cca2097ab52306207a61ba9903723f574091ef5e57c2e871e076d7f46e6e39f65a01e183e7bd822f000000000000000000000000000000000071110a384248664db46f21d87b455a3ad3c43782c68304ce17f52cc8579fb2e3378995d6eb3b8c97665e5fb7de665fd0000000000000000000000000000000019153a01c2b3c5d481474a71e5c67f27fae3232a0c8f1655ddd4da6b4c79870bfb0b6beb4af8c54aaf7e9251ad41d639000000000000000000000000000000000c58375439a93e0763467c6a11dada3e579ec53a968c9b9c1a446cf3224ea0c89c9ec218a8b78de91fc12f087e722f94,45000, +00000000000000000000000000000000161c595d151a765c7dee03c9210414cdffab84b9078b4b98f9df09be5ec299b8f6322c692214f00ede97958f235c352b00000000000000000000000000000000106883e0937cb869e579b513bde8f61020fcf26be38f8b98eae3885cedec2e028970415fc653cf10e64727b7f6232e06000000000000000000000000000000000f351a82b733af31af453904874b7ca6252957a1ab51ec7f7b6fff85bbf3331f870a7e72a81594a9930859237e7a154d0000000000000000000000000000000012fcf20d1750901f2cfed64fd362f010ee64fafe9ddab406cc352b65829b929881a50514d53247d1cca7d6995d0bc9b2f0d3d4cf46265fc0f69e093181f8b02114e492485696c671b648450c4fcd97aa,0000000000000000000000000000000004c7495c03fc3fb4d0fd4e0e660d6424de9e060eac72eee3608ba95bac294a3a62d246f42dcf3b575ee1cf8e20a9106100000000000000000000000000000000091140aee42a9dc875f87f3ba29beff95138790140f8bb522c6c15281b3545995f9c13b0b73ae691317e674295db6526000000000000000000000000000000000a945a215b2861427e0fbbfc6fea04e79edeaa1eb87df5db8e5e017cf98fde7b8d5a04a1b2129a4aadd2e3924ecc0bb2000000000000000000000000000000000a43f8d3d92a03b7bd4c8a34ce31729ea0b8e6b051c30241dca2db31a02b6e537071a914d8f0876f944dfdb613540c6d,45000, +000000000000000000000000000000000047f92d6306bed1cb840f58fd57b5b71a5df7f86dbfa55a36636cb495e08715cd57f2f3e7cd99a1efc28b1d684de1cb0000000000000000000000000000000000f4eb02d687a1a6105b4dbd740e2c7924689d558e6cbfee768dd303cc8dd0fd887f5eec24b54feccf00f473ca3f54ad000000000000000000000000000000000edad68c4d536912816cf6ef039c3dd0535dc52189583270b3b038e2c67b213d943bf384ce69c4a9dc526d7ef309f25a0000000000000000000000000000000006ff4a6b5129ef026d1d5704bf7fc0b474de92b5cf39722f165e73f4e7612d6d3bb40743e4b7b42d0dad5d5d6a2d4881915b717562844d59623bc582f1a95fc678cf0d39af32560c6c06e3a74023c89c,000000000000000000000000000000001821e14e70e12c7caf2a1ab651eb81dd61c4e1eec9a02fe4124abb865a7029e066f03b62e6ecfcf0fbae5151272b524f00000000000000000000000000000000044ac4a7399d6a67e7ee8cde3f5fe20b0a745462c870926f0ce8554061eba5bd62a8a08c798d8bfe30fba5567d47c7ec00000000000000000000000000000000178b8f061ad9282b3b2057f20c115c91df994ac40aacd05b7669e934bc7d650a0cd88f9fe17d7b766e34bed587ead58200000000000000000000000000000000188311eea279ddcf75f8dd82643ca3efd560ddbe6c8f2696cf7da03e65cc90d97b9f9ce99e29269644d8b881e624cca6,45000, +0000000000000000000000000000000017b32e613cb38b41dcdf3c8bb9187d731546977fbffd79fa7f66e3d6aaf9e1af6eca2fcdc260c8f90818d7148ba2f4960000000000000000000000000000000007e4d26606a47c874c20e8480a9f5815e5b577bccd783b775d10309eeb3d2102c7a0abc3324679e44362f09e7a4ada67000000000000000000000000000000000cb6f12ac8b49cfa36b957591293c87b21af0a949c55a28a90ab0fce88fb5cb7645e20ab2edd284f0ad1377dd95ac10e0000000000000000000000000000000014c96b5dcbd3150eeaea5c2bc27750cf88b30a91933a3233a4d1d9b357a80cc20d135e43a344e718dff5c79045c31f86d5c1c9fa11c36b86430cbb1f3ec10ebbe3787d0f5641d6d7fb96c810eda202dd,0000000000000000000000000000000012496dd3c1278b55bde81f6944c4bdb71869f5e5e21db7b1425ea32fa1dbc8c301e7f5e68cd7629c91650265d1361e690000000000000000000000000000000004a1251591efdbdbeda21eb89165ca61a2e090a73426451b6933d939161364c4064a67a90f859a7713fb6a9c5321d5a200000000000000000000000000000000163bcd07d030fd6ab8a8e0bf39b136dcb34f03925c3fdadf55e94a90bfde0ecde5c51d2f4d06954aa6a96c913f2ab4610000000000000000000000000000000016dc065a852ef9e038d93cc583b4a71db9b96a7e7a819dc530598f1ae256368438f52e4b709f15f56279b9c7f9db8785,45000, +0000000000000000000000000000000001ca1141ba9542c56de8991b313c6ae42fcecb6751b0b81b8cb21ed70d5008f7ffe831766b89880a7fa6dfdb09a2cda3000000000000000000000000000000000e6766b17db165bba564ac63ab88d3f8f5eded07a40b48644e60d3223d30458e7dabe404cab8d6f9fe135712ef0b1a43000000000000000000000000000000000dda3e6c87382fa762510e5cac721fd2b654f002f5b9a3767a8c6d651ccc582e80e3f68d6913cda30f9f51ebcfc7c98600000000000000000000000000000000059a7dac5bb6b504f2bd603d486700fe22c14f25254537b2c9079c2b45d36c7ce56854c5699cc7649b533194f51a9045c00eb20fe7c292f3ad820a074d8b3d8d24506612752d8677c2d6ca24f556cc45,000000000000000000000000000000000a2397fb3a3891d1703eb2112357c5fb8acb070ba9f3a39050be6f05b49b8d2488e94adfbf849c8b4a42e287077e9fff000000000000000000000000000000000cf2c02a97addbc1584091e411f9a07135f1fcf989dfc8ae29155ac90b214ce20dc11a1fc75dfb697694891d934abf0f0000000000000000000000000000000018fd4af647bf0456aff9ef80969613829f8eb837205df552aadca46bc3bf9838e0ff2515d3fe869f80d78e2357091d8b0000000000000000000000000000000003c5671ea4723498359f29d49ebe974099da3dd59d21065a721f7a4f14dc7fb1de3a67a707bfa4bad7058312632c6113,45000, +00000000000000000000000000000000090f4b85961ce97cf7f99c342d3627105d790f611e19721a43d8a0febd67ae393d77a02b999108efb56f0397dac22703000000000000000000000000000000001112f23595d1613c47486eadc37f9b1ac3b3c3973b3fe964d3b67c3996fe2eacd9df5c287b0cea8e9475d146fabcf9e70000000000000000000000000000000018f46f7ba3c9af34c1025c2d460f0be966e68944928dbd55cc7fe00e5def598d80b0e3801e48a74963c974ab4727a52100000000000000000000000000000000096845338d5cd2ac44e097607d6a1a05c241eda1941991ae9edbba965d9029032c46da7218b5b2338e6c58898bc4a820f661d7b30fb11bef70e15b257d7073885468a380862202b2d705a84827644b5b,0000000000000000000000000000000000676bd7ce63d8b58cc1e5399ced9b495baba4cef9503c44760f92d6d9e092d6d5308fa88144491eda6c571a8c308786000000000000000000000000000000000605cebb4c20bc9dff0258f75a825f55f23a32cd0804dce56bf3cf2f19a3504f0345e0f1b839d4d5920aab19b363ae19000000000000000000000000000000001512f95f60a6dc79dd9261c321328ab8e22ff314e7582d8de83aa3bf280805cba8ba6d359a620fa6f0564396a45ca9760000000000000000000000000000000005837474ba78e0700c77141d70af1d8fb95a97cbadc95996faa93c2e81b7c8877d08d5287f83219a24bc0080e630e39a,45000, +000000000000000000000000000000000aafe45ea7cb8b450a51263eebc28c1ded662972bee512e24fddaf64f43b74b66032523b3b104a4e9f6b62394436c6710000000000000000000000000000000015cb27e1fedfba2d1679f78a388f90b22bbf3e7d090f0ba972fa8e72f6e31c446f628fff929953712ef6e425d16eba5c000000000000000000000000000000000df9931893cae713042bf722db6ce394b6f346587278a154c271d8511e690417eb6dc47efbcebb7c2fb9e77f1de9fde800000000000000000000000000000000106ffa395ef170c99bb5742428ae88fa4fd7a94476985c099e3b700b7403d083281fb71a19640c6bc2321e27bcb33fe2346ce87c847376c8967cc18297e6007dcfacb6424e1d273930f38bb0e88fc5ca,0000000000000000000000000000000010b2a9b32e431c11ceb474942bbbd6915a3cff64a74d67570fadeb7447c5abcf1bb35c822d4441565322ebf75e61f64c000000000000000000000000000000000b75a0212232af0a59440482a1f953cc29bcd35272ef407925eccd70c1dc4705dc1e97d2da604996d3c52155d05d77500000000000000000000000000000000018751bc59f5907cbd7f1d503bc5aa266f4109fd3133a1c4c2e58e4a17250a40053b4489da4825b4c368b0f4947baa6240000000000000000000000000000000019b41fa1af9488596b09c587fc33e044d51674eb6087c647d5a762d85e38a587eb5482687d9346a1a701bd3a8bd36a61,45000, +0000000000000000000000000000000010b1f8b1c492a56936da905b8738affba6bd29ae5fffd40ba6b31325181d3b489a81b23dcb69f6e71bd29bfb388e5a8f00000000000000000000000000000000116a115303b4774da59844e457844232d088062d920db67b2a8450a194be7e5340ebd4d106454fd9a03c8f50dbb1e119000000000000000000000000000000000eb521edd61b38006cffc43ab72d395d669dec196846fa4d6d43521da6c2fc3bf0994ce7556a3cffec7751b3bc5703ff00000000000000000000000000000000073cea36eccaa1c78deefb6029903c2b6598301bdefa9759719c3b590fcc5a6a4d3d4d19f552b33f4a3126a6e6a8448639a142c443a666499a880aa1cb9f523411bbc8e5554de099ab485b6c2c2e57cc,00000000000000000000000000000000054836eb7ef9edbe914bc16d1498e0bc3c978bbed2518802c2f8e1c0b59fee482cce0ae8e805c33861d4cd595f6b8bf40000000000000000000000000000000007dda36d55aa7a890aeaecf2528a390c98d9ecfc8a5c78c2a6def30de55b90ae408ab770cf9a9a4663ba601c4f5765a00000000000000000000000000000000007ff7b24c8ed9fca572069e72b1e93978cea87a0fac7ba60f54aa573d881f21b73012b010e9c0fc9324aa7697bae0c4a0000000000000000000000000000000002d9773bf294efe64021e755e4dd2936a5060bbea5688b6369ffa3b94eadcc58cc3986c74ff365301be1e6c785939b69,45000, +000000000000000000000000000000000e3925fa085db73c1e67b29ae90f8773f83be5ec684402e8e2360ffee8a8368911e584843e42b0d470de78591df6ea6300000000000000000000000000000000075c7efdeeb16609b4a47ea442af4d75238fb7534fd96cb236a7886809d6adc2b62c8ff72bdb041bc51c1a71b68219e300000000000000000000000000000000088b4eb0dd185e51b737d797334590e982b7b0a5f109fc7d0524b2465c2c0457964eba5a6d2d4d99fb628f21f15a776c000000000000000000000000000000000fc79f6b38f3356972669290eeadcd992a22bc1191606b663a1e148aa58db3938f0fc65e536bc5811c50d9c7f03d3e372c01b7795c2d16b5bbbb1e107be36cc91b25130888956b0cdd344de9b4659447,000000000000000000000000000000000902c1082ff09bf93b91c9ef5e447bd6832fec9297cdb065f11fc5ee626e6e8834cb5d74775c586609a0394e6114e8820000000000000000000000000000000018e414a40c27430b98246fef556e74dd3dd7adc601e3c05b79f8c29169780a173be9a725df3318d71b6e82abf97930bd000000000000000000000000000000000f924fa88f43c86ec98b34379b9a649c7564ef0dc596c95df19522fd50fb3a37cae031e891a7a7aa6a5e6a9062c3726a0000000000000000000000000000000006bd3340412f64d02d0cb3ac44d1f31cdb1906e56dbfb66d86b60a74cd26c1e241963fcd8bba4109c428db0bb083e81f,45000, +000000000000000000000000000000000b87c47605fc060a8e3677e84ce9d14b9309360a13c80d040c625fbf0108f829300cc1fca409a0f9c96311cd4a9a21e60000000000000000000000000000000014c4088f1e7935cf6a1d2475b84497ce6a250ee2c0c991fe51a2f2836388a354824b02d9cf215328dfce3f546713e21100000000000000000000000000000000120e59be3ecf35674eac6cdc559599b273f13f28a529770fa156f8e519734c451eefb35023639f32049cd19ea0d945a3000000000000000000000000000000000f97755b62a8cb8f861ea02c77819f0b58181aecf612d92180ba9b475f0b4888b922c57f6a1c619dd5514620a1cfd9e2c712943d8795a6104f024b9701c70b09cdee9494755bbab0576e2c7f7c9d4828,0000000000000000000000000000000001415fbd8afeeb5796460a9095f14a8f3f6fe0374d4cc4160f030710a6d4d3a92febcf4dad770de3a3ba1a2efbd858210000000000000000000000000000000015792220c7e53262b56224d230a8a4b32019c77548704ec16da5ce167854305e6cdb9924c248f222d6fe95a8383af7890000000000000000000000000000000001694329d8e0f41256b703a8bb6548f1d9e0749a55c124c9b60361b4cb1daee24fcf272327ba598022a92815764fc8570000000000000000000000000000000003350658842c5b6fc5561a14df27d950a00c5bcc13d6d9d014bfd6dc95ec1a030594625f41d439b90b05275a0ffefdb1,45000, +0000000000000000000000000000000005860cfb6be6720118623d2d8ba05e686df22744b948421dd3cc1b1691e00d9b5d00d00195b4acf7a7b043f764f3f1c70000000000000000000000000000000012632a3313dd611e8d969bddd556c2d79ff387603462ac78ded3a842981697bdac34ee6f1f4744ed2ff16100874ac24000000000000000000000000000000000112b94c317586e343acadeca611c485c3ea172bc10dd39158c1e678007130062a921b53826d7be6286963ff822f1066c00000000000000000000000000000000040de8c0dadd2a6c2a7ea0fa43e1a5f2f5a6be3fcb0de6875d8cef1ee2daad87125d12f6869c4dd3d931b296f1df2fb3d4d77f6246c57d398c57848db8d3f986c475a41a23d424cd3cc2b362c1b99f2a,00000000000000000000000000000000054c6cb26c8b0a9a4700e0b95348e6fb1190c577eba03a44e84fe7744c543321d02c4d8f55c03f984b44ffbd899ac53a000000000000000000000000000000000e7ab8da5d573cb88a78f6a6ad2b307bf867777f79a643b6ec89d9cb208711c85d7d2cf8f8ac69a8b322000fc7866024000000000000000000000000000000000fbc5926b9dcd9e4d1ca1a2b43dab5c98aa20b37aff0868c54441de44eb014e5283010642717fafaa95000f4313e14840000000000000000000000000000000003671ee05bc20bead72f2306203dad55cf20b13d3bb2cca079bf4391411b85ed4df55e1426645d73b6935889d4450c58,45000, +0000000000000000000000000000000006fcd2c4fe848e9462ba1112baad39031c210952adbdd06293a622ffe2d1c6e4fcc8773ec8913717018b97bcb9a554fd00000000000000000000000000000000130a97442f3273b7b35464545e7351faf71ead9b8996c63889a45945ed82bba29bff5014776c6185219a5234d8475c92000000000000000000000000000000000491d571bac5487b866022a0714be11b38bfb296233845cc434a50be1d35f516b8c6b046fe3d0a8f4f95ac20eddea01b0000000000000000000000000000000017e34b04e6fdf152c848f2432b7bd84b3dba3915f06eb77efb8035750aca9d89e92e1d1bc4871105c440d639e8d8b05541776ed9d1029918af4c5113a6110139b8bd7f938caa204373a28ddaa51430eb,0000000000000000000000000000000013fdd394635f42a926a2324b8cb870b5995772ef4e25ebc1da41dc5bf724f747da8d95a28dd703b5ed65ada5555c8b5b00000000000000000000000000000000118fd550962d1de8f1e60c312643ec7cd306f0bbcc932739270595537c8d290ca7e20b962fcde570bd2ed7ea43009fe70000000000000000000000000000000018b25fef4b75fc7649a489d078311dfb6da9909f472de7bd9bee9c3ee353f345c83119269ab797fabdbede41e0fe6169000000000000000000000000000000000b7c2a73741f6944ef4ce8fa20b2900612645c224818b7faccf6597827fa07f7262295f42be5f34a751a6400495f7eaf,45000, +000000000000000000000000000000000f1b8df4e8fdfe32eaf227f5af9f2befc85073468f10b81d32d0e126fe2b0cc8e8adb8afcac73213b6ed95e8e843b97c00000000000000000000000000000000004e3fb435ae0fb2d8bd091f250aefe5922b353a64e16abd75627737f3bc56639f8b40652cae69c73ff1969925b0afdf000000000000000000000000000000001003aed7cfb00efce49d6b1a8eba27df87479a4d37bd7fda6121549483b669a1a761204b0dd28262bf27e5c8e180540f00000000000000000000000000000000114fbca7caf782b3296d0b26b4c362bf50acaecb8bc5726b2c99f904ec3d092d5d40991d0d30c8e79fddaa45f04a75d3fa64411438542922a7bac10806efaa633d31d37c0b223314a8b6221155b9c425,00000000000000000000000000000000177d29de8a81db2e515d4241e5f7e3d35de22bbcf9aaa616b057cbf2dab57ab8d98213cdec82a2034964f3e1def8a4e3000000000000000000000000000000000a0cce8113eecb064a60ee2c470dfae8b3921f8da2c7ad8dc918b355ff44542b007add28a44848fa8d8f8671617431ff0000000000000000000000000000000010470fcc723286327e951e758fd0474de394778d0c1ec5fe6f263dea1957c60f05dc8f9d82b3c6a7d73b3e783f35ade500000000000000000000000000000000098a6ed331f03da7ccc9148f07b19b132152e15d9fdaee5cc092524b33795edf2b458b4e8383c5e29affd3f025094033,45000, +0000000000000000000000000000000017faf481fd4cb0c373d21d7caad40e93d9a86e62d26136892fbcc6f6e48205543aff00c45e82fdd1d3e0e733de91e7000000000000000000000000000000000012e14fcb9ad4d9d15347cf004745ed4bd92097eeeb41c4cbcb728a234616363589d8f5ad4cbb61d31a8aa27627723c7e000000000000000000000000000000001513dad1ff27e053902e779e35d04cab648939317830144ea775c435a4b55e13fa2fef03a1256abf5c187487c25a774f00000000000000000000000000000000139da29de8587c7d0ca9237c37a116387385e9cea453b9e2003a37ede7aa0a3f4c1df55255897f5975b662be33622dbce7002f41c6acab677a0ad023bad2a61b11c1b7221d944018b5ce60bb61e87e96,0000000000000000000000000000000018a1f1a60172a65abc8f2d855ee7510c1e0af9bada084325027bd493ae86ea2c62c15ace7f63562a82cb80ee7095661b000000000000000000000000000000001736b977fb52eb1b466cec3d42df7e89047784f0e8362eb6425e37adb1e84d0438f5a6e82c7b31d59b0959a5f4aaf9310000000000000000000000000000000013ea0f849830f8e48161e840295637d8596b32eb576560289620b797b14bd395d835e8140b69039c904ef1d07a82127b000000000000000000000000000000000d7f58873701c138cb7e18ffc36cd0e47b07d70448ddd9fdc4b947003fb29cba0775916c752d531e527ab744c277e5da,45000, +000000000000000000000000000000000c118b147ee3489f30c6ecc0256a314ab674110588e8b69ca6d265fc270c3e5b767817f861140cca5d7c6be4012d1ffe0000000000000000000000000000000014800790654726959fd876b035bade0da744fb36ee5b304f228663a531345120267c55ac19fd66022752010e5bea7cb30000000000000000000000000000000000193ab7ac2f151750356b6e178557460c9c2672b1736d19a20e3fa28082479ca60021aa68edf2524f1aa826ee70b65a0000000000000000000000000000000015cee9ac55ab45abbc57d0ea6ec9ee49f6c59f6b94f99589dbc08ee877d3a261ad77f5473fedd72ed7206647eeafb6eac26e55f09b787c0542878e4d720027d9ea465f829a4e0164cf618c5d9cde49bc,000000000000000000000000000000000290fb3f38937ce4439ceaa21cf3b31db8a22f9f5ad9db0fd7d38ca978192bc05d41152f8f86ca7b2ee0bb58e125f57f000000000000000000000000000000001775913fc24699bf08f25fb946fc6527178ebb821c654b7bc69f6f86b5168fc42057a5d3bfdc53b3d57fa1ac05f7a0930000000000000000000000000000000017b9043cde8dbf500ad90463250a49f56b35713f2fd9a35d8391fc36c78c083e39674592a98cb857194ef9e73a62a397000000000000000000000000000000000e5e62e39433d443e7d2d32754d2ca2556cf6deea45e5076ac040e3d6de14e9965c53f8c65bd98ae7d17ad3a26f3accb,45000, +000000000000000000000000000000000ef203fab794a0ef29eb2ebf00076134e5932e27c99d6d445695b9df2afe7563602e318caf5d44724a21790ca0ab0d180000000000000000000000000000000013b9b1b1d3e98b61b0f1a0ef3a1a4ceed57b6c01849a4ad66a86332b3d27022cfccadd3567e6709d2de5b23b23dba43f000000000000000000000000000000000c1fbace49684f4be32ef6178ac3a95ea3f50b11494340fb73dc5391d50bcacafb3bf0f2631fea9c4ec47327d644489500000000000000000000000000000000040f82812855aa3e3aaba826d5810c1049cf44e86e44e23cc6da437971b529d2f2676c73e1fb9da52640c981fbd710bebba67cc47e38a129ab1140fbcf0386ddba2feefc919aacdce6059a27a1e2efca,000000000000000000000000000000000d9927347a9ac9b0290e68143fbc6a5f4476604c3fa5ae87e729a03ca055e4c6543f9245a4592e195180d88781e46ac900000000000000000000000000000000175e0ee8de4002b18f32f70f1bfa9e0be87288cddf1c436428c2969884112bef5db19e041cbaeb23596e25cabea3777300000000000000000000000000000000074ed9e981818102b9ba818d478ba27033eb38e3fa19cdeb9f5820e59a64dc451342a160359c54bc8ec7d866b62080ef000000000000000000000000000000000a853930020bf01e20816d3aed242e00792b0d0e78fb15403fc3cc255f0dbd99ea6ae1d59d5978e562be4862b3317324,45000, +00000000000000000000000000000000060d7a718dd02b147c265f71eb136d1f31781b12a41866b4f86d7374b93dd10058c192cc0fba928373b1526e1a5d7d7f000000000000000000000000000000000cf29275373c0573ef22bf87919faf5444847203c7dc6d2e18986152cc294be04a5b1a4b0536797158113a15276c4fc6000000000000000000000000000000001016d5b9d4d200d7b4b7cc3836b85d6697fe14db350badba9978c7b56983dd1a7e572640ee0372b0a4e2079ff4c1abf2000000000000000000000000000000000f2768d104d895473ddf8c6b3cd0e7c22458d0037eca6365c766879a07c95037ee0de00d32c974d767080935abbe0be1705fb566367d9fc142c4194b0525c16672b843aac1160f9056ebb115e80d377a,000000000000000000000000000000000e9c290ba8a22f7bb3f7dfdcc9f5a221a5ce838d4fa85a00473a4dd830bacf583dd91a6a6f78d2ebb54a4c1bb217f793000000000000000000000000000000000dc51b0ae8bda6d28c51016764fc028258171d7c7646393228692aef7f1dda4a83e53553f63d6ba996d4c0a802bc967f0000000000000000000000000000000014ab155029dd35206811be9ca4efbf762a1673367e6b57528f79eb50008ce7c3b49a2d25da0ae68ac4030ab4bcc0daba0000000000000000000000000000000008cd743bb52e7908aa973c8518eaded75fc2858f4edb25fb7f2e09900f0abd3ac87e93cf1068bbe0c7d99619aa7a6b76,45000, +0000000000000000000000000000000017b9ca4349fecaa43ce911c0b256680edb8a0906ef5460fc4d2004579336df1e19560fe960a7a7cd74bb6e8272e08960000000000000000000000000000000000d5b96dae738db59cc67a51c61bec6deaeefaaa51e3259243fa4b142ef59676231229ae386ce699fbe18c4c00bf9d49400000000000000000000000000000000111b79f4b68dad16550a13334d09dc38336a75a5da23a17b5064e2d591aa3dab4c2e982a9f730a7633070504663a24610000000000000000000000000000000018f6d3616a7eaf17c805a88c9710039644d01b61aefebf76717ddcda6f4bb34aa15702de1e92bdb27b27f3409638da90f7bfd990cc4dac62a0d730f56b4eb1c1ad77ca9cd58b089c23c2f6efa00b7fa4,000000000000000000000000000000001746a449993b0684740630f3f0e46eddfa135371e33e8de4dfe553c78845399e63bb3da48798b35df48d27e1f991954400000000000000000000000000000000057e0fb1113968858981c9803166d8b3eacc91bfad320ea0e610fbc5b276da1b46d74fcc54183ba61d1b2fe6743097c90000000000000000000000000000000000b3a178ae3b739cae3e80f3f44db42d8c465a5cfe4943b449d4c3b7f4ad153916c6cf4fdfece14a00b271222c72764300000000000000000000000000000000041c8b293ded0c647f2e4d6f9b35304179b723c3e6e421a5cb103e561d1655b92e74877ce22c99f22a3700c3aba9ebb9,45000, +000000000000000000000000000000000aeb5c087644595d0912879f61959d2731ff55260c682ed2bc5fc55c13964ef7c1f70aeb55876d2264d558c31371ca69000000000000000000000000000000000e173848f4570525b03a2b2c86f4dcdb8b28dd6d18c1354cad31028eb1b8b44432c2346edaace093e3954c7fa6d338a4000000000000000000000000000000001949b0902506d111ef6318edcd7a58ca4d69f5804a028aee73c3786cb2db168c6a73b77194f7a021ae6ae43ac78ade340000000000000000000000000000000017c5e28ba6103d97e2f3d3611c0c78f06406e0da8a49ae29c7d460b52f75136920784cd500aa3593858b877697eb8424807c5a41ae2baa1e10ebee15363d1d4569f731d77a418998108f5dfae0e90556,000000000000000000000000000000001103cc395acf81772955bda38f951a81c5a6a476c0b5e1543616a5a7a7be22dd487ab2a8586524891300adec5225b4020000000000000000000000000000000003479a08e2811ae9aab0301d66ada470935984d7466201f3fb28c610c0b5f67e7305f5ad3514cec5f30b51d0aae775d40000000000000000000000000000000005ea37a6d20c1ad0978da68ded3a5bfcc5ad8fe81e39b525fe7d1f2b2b1ab0be7ada80173b1d0b7fe1e06ab6354e64b10000000000000000000000000000000008f2093151a285dac511df1755e99a652a1cad0af3a019650fbdead1421ba8e84afc9eb0a4fea651f365d72f031a0ca6,45000, +000000000000000000000000000000000d4f09acd5f362e0a516d4c13c5e2f504d9bd49fdfb6d8b7a7ab35a02c391c8112b03270d5d9eefe9b659dd27601d18f000000000000000000000000000000000fd489cb75945f3b5ebb1c0e326d59602934c8f78fe9294a8877e7aeb95de5addde0cb7ab53674df8b2cfbb036b30b9900000000000000000000000000000000055dbc4eca768714e098bbe9c71cf54b40f51c26e95808ee79225a87fb6fa1415178db47f02d856fea56a752d185f86b000000000000000000000000000000001239b7640f416eb6e921fe47f7501d504fadc190d9cf4e89ae2b717276739a2f4ee9f637c35e23c480df029fd8d247c7a7e300bcb3c740fd1f693d4c8915c4c46dcb627f6de6e4847f123623cd23bac7,0000000000000000000000000000000019f79677ea0e011e5c9a892a407646798b05be05337c73135cb771abf101f450bbffd08e125f077f5ea989decc009b9f000000000000000000000000000000000ed15f35966024cf1de2926108151e976dcb0d51b2736b0877d79de81f6fccb9dd299d14855f4e257cae33ab7455b95100000000000000000000000000000000125e2fabb5cc95c0a7890e9ff2b70102a97a03f2d11d915cf4332dd049a467333e12ebb27955c0310ebdfe2afb3173ee0000000000000000000000000000000011718167000f9b749f1615610a30023db4b986364da5bbdc4506c726624a073548a94307b282590cd8a43b4900a1afb2,45000, +000000000000000000000000000000000f20a07526a082e88630a0256d134a8a5e8ada07b1cead39ee838dcbb30904e9016107fcbdf1f8ba182308dbe0b043d20000000000000000000000000000000014fb7732f67abf60c03ac902577532d0acadb5f3db0d6397a42ba693526ad74f2c61a0195bdc9704aaaf12e65aa6d88b000000000000000000000000000000000018cec4fb81c85d304588d11f8b9c51f5a053df11463e5812a1b2e6c7144522ba36bb91adf219892d0007cee470032e000000000000000000000000000000000b8e52d958a12a9037e8be9bc0d5045cade2d6ea05c6e68462b3a30b5d4ea34e5fbad173761e4e216b2e6958c8983b28b473df5e282565a0783d23e65e283a103ebbddb5c884183cceb62fc32d0e9602,0000000000000000000000000000000005af8fd9e79568b46fc42b2c1bac62d115365834e509dab032f66425b7a571a4bd3bf702299d3c5f36c372750b3281f30000000000000000000000000000000018499089f306b3c9f7a645ca2f9aabc4e57c046992fff87e832e21e21875c6adaca050ea8bd7043afec3a36ecf8eafae0000000000000000000000000000000000827fa0f46134e2dff80088129841f0469ec7360fd8b9864e9ed99c5fd3458e6360661ab4c671846681d491b8b823d200000000000000000000000000000000120f829e8d0ffc360a14eabaf52bc653b1e90a36c0a8af806ca745fa306a9739e31435039a377e0748caf5e80c2b0b09,45000, +000000000000000000000000000000001468cb35a60898ed129f30c261b8431df6a154c250ec16d85a22f8717593b2c21853d123da86d977a7938c5ed74ef23500000000000000000000000000000000011f4e28e31b5f9e6877192a5e632d8c1ed7ca0c42e6e9902ca68f1c2de0f648c6064436012c5c7b14bb8d1078e02f2c000000000000000000000000000000000b25114b2697ca7eb1e6effdd1054893a188fd382d387ec098f846c1137a9b9baad01653b963a0b0bf3cb50c3ce3563d000000000000000000000000000000000c1d241cb03e642c1752b1e1886472477c19a2801ec032dc220c3243952f882094119bb92b621b654b766bc900d2d4f7a048ef7cf5d1f6f625ee3aba091147c389ebebc5b8f3d285e16ef4e8afe5c013,000000000000000000000000000000001745500b00e5ebc6f71c779ba0b0f8d6601a065c550ca19de9562455423d2ccb507e659b0dce982faa841267fb1a27d90000000000000000000000000000000009c36b54f12d130868ff9b9b61b714fb1067dc91637c09614c51b5aafa2cbe3ca7dce0f3e366d4200cbf603ad4fd630000000000000000000000000000000000172e543708bb853712d81c000c9f9f2378e628b4d13b074317e95deeae98e11e7f917f91e02a0b18cfe9b25f1b83f16700000000000000000000000000000000189fc572ff6a8c6606ba0cea7da7040898d9ee85a58f12fade8c5a22031ff26c2f9cc612bc6e1b82a0999fa93c6fdfca,45000, +000000000000000000000000000000000c80d4474390fa791ea5f2f16b41506d8ae13ee0993c8d31a07712687298ee7978a724999500c42400d2f788a5a36067000000000000000000000000000000000592705cc5a8875750a4e6ceb42aa3bef5593eda9e8212702a2e08ea70277a2a66526bc5237be33c8449301544da35e60000000000000000000000000000000000facabfbd15284c6433f17b0e6035d4fdd84d3ad2dd30a27d52809652ff6e7a684d7724697919100567ad0c3e1a26320000000000000000000000000000000006a0fc4e2af69ce15a356656f5d182a2cf213d76a6047a05a1a3375909d245f5316b91333d2141c0817438f0d87bb52da9b63c6bf36997118d58600c1e429c105a379b9e8b0de934ab9f433a4fa63dc8,00000000000000000000000000000000013c6f777df97ad3ddab9b7486d54d1bacb3b40ad3035b47a25a66c02e8866955e27a8ee52872c8222ff7466c1310bad0000000000000000000000000000000014a5eb510d7c743e824f4daab21c43db4d6de8ab2e825d13ae0e186aaba828d7b4a2343a11011a8ec4ea82f456e394a70000000000000000000000000000000017a55d3827b78a9c5ea792b705eba7777df74951930791b17ff5b861e98a4488f83007c073c3e904ed4ee328b6f6171c0000000000000000000000000000000019bae02f8d6f1e31dfa09f4feedd5217ade66f6e8248aa98b273574f72aef83d5048534ed38acab9e0eb4c64f4389af4,45000, +0000000000000000000000000000000003f629618e1fc3018bb836301ccdc59022f0a25cc9c5de6e4c31fa08feea525c83256235e4ec8364e77e5df478f5f62c000000000000000000000000000000001120d6af221ba6f4351bbee4c2c664a769adb17872646df2c408f70c99ea991ffced4eab50fa98be1bb9426915f125930000000000000000000000000000000015cd16b028ce3d58b10aeb84b783475d894ab3f0cfdf7104ebb4f3417a038107128f07518dce548271061cb8c97e88af0000000000000000000000000000000018379875b68bc26107f9a068e5034f29dc2ae7e8830f8e9ecddc53fe7991206646cda33d37b31a47a977b46be58d7618f228da17f49667c113d2bc2a2c8a338f80be68496f5145b4be21a5786ca6d46b,0000000000000000000000000000000006490c327790b4c451f93197d7db24211a3b4b5f573a6df409206b4bbfc36bd10d2d0c989889efffd8f4daa4a68b211c00000000000000000000000000000000168f224738db3f07af77494f52ea5e957812a1acd62615f0eaa95c1d363cfceff29be9cf3be5329bb41175a0231ced4f000000000000000000000000000000000321f06b55f7dbfd4900b329c914f9ab9be2794e51e54498e18f83ece5bfd205131fbc254bfbf624d57ec2954b05f6f00000000000000000000000000000000018ec54f3e09bb2a6b112b575f9481bf1c85666133051e9c0ab53369d14eb90e27d2ed02dcda1250d5d539df0d0cda37c,45000, +00000000000000000000000000000000036570783711b381830e35878fbeb187b84884a9a0e88c38e84124515b470e6ac18157e1499026b27f4f731a961eaf330000000000000000000000000000000008382838c18d56c046a8db495babf8d14c915622d7917ebe10cf7da7ecb65f174cddb9e70d0262ada961b396c5511b410000000000000000000000000000000015f63ce982aa581dad5c71fc79251b7f6336c4e78a4a0f4cb6f87167cabd31cbec987d7af4f11dc6d693a0b0774864130000000000000000000000000000000015c001372fe0530a3f50fb8b30e75ff4b264d673e0448211d082c7a9018f583b4d01790019874596c59c68768cfa3e699431e18a462fba704216b516e819fb3392e315b0c92a7411a329cdafeb511244,0000000000000000000000000000000001641b4ad10da5089164809d82ae47f74e27eaebffc2a2ca3c1b924fc69c1ea80ba3da78c78e86957f6a24e7f75dcada0000000000000000000000000000000014e781e4fe79ea1654460f4b0daddaffb29b287efd8168cb20d7ac6c729f684c5f2a7cfa87885accee3a797febc904c200000000000000000000000000000000001c9a44547f0c5b1f4df190285644c5a31df61e3de7da085835ebda917d5e4163f2deea9a83d641a4759fa3108567ad0000000000000000000000000000000014c3d2a79d80687fd6e6aa423257644fa5d0cf641aaf6a7c5675a810767904166fabd9a2ced0727e3badb932e46fd181,45000, +00000000000000000000000000000000074d78cdd35ea17a3013e2301fe9f80f2d20d270a25fdead37eed7697a52d152612543781763e6035fa5452ab12cce25000000000000000000000000000000000e572236e1c203a1c0f99e6ec978458c1a143a6a650eee27cfbe406bb2858fe5f30222f468d119703c2f442bc644ff3000000000000000000000000000000000125384343fe132e16a9fc15efe1b3a9e47289e0afc4b44d492e33a6216edbc96d66c1ca66944a8296e7695f27f414c5b00000000000000000000000000000000084c2cbf0d7c932c3098ded7c70d4411eed882feb0f79e0f7f1c31f5fccb6d53fb57de179c3ba5754bc5e532c3784df12051041bd2f12f6e6e29924139770fe209b7bbdbcd6c0bcabbf5021a7dff2d83,00000000000000000000000000000000129554de7de9a2b73340d94d96f0356a2d1c0524cfb007d76a75f462872e831f45553de05f5b6a1f9eeae37af7f6b4c9000000000000000000000000000000000b1ea2a649ca13a3dc7882f2423036670f68aa05792a8fcd72524420e37381a9ca80dfea701fa5e6da57afa534059617000000000000000000000000000000000b7ff27aba408f9759b5109600cff66c03cdb4bfb3dff64a4838d0516fa46bfcf429fcf9d5cbf74a27f70fdccdb1238c0000000000000000000000000000000005a99aec88967fe775c691d443e2dbd45080eec97e686ee6d7b32e801efe6563315bfafd5c7622d0543519cae4417029,45000, +0000000000000000000000000000000004d46066439c3ac559cce863c58316883651023990180470d2efd06e443a7caf3a514b54f15ce6e850d32779215bcf4a0000000000000000000000000000000019ce904b6c9c3de59f7d5017f60f1978d60c564f94a0f1964c24c876d1139a7ffbeb6d0d4884bbfaf5f2f189af6904a50000000000000000000000000000000015f1989719e69be95f25dda9358fb98aae2819e0deb7e2d291e2c01e85ba26a9da421896c6b6e2ed20f609b533154694000000000000000000000000000000000b287cfcf1dd7c6d735c1358dff15393ddd6c82e7a33c5d8005c4234cdf823c76a4725fd74cad74b3ec51df67f09af0fb96df57a600dc3b5aabff5b1034886d24f6fcf035bcacaaec738deb2cfb8f852,0000000000000000000000000000000007997a499b2194cab634750a189cca6783ff17d866d66f5998603f8639d2242e8039222c65b0d14001167a9b09afb58a0000000000000000000000000000000015050fe6b335884a225efcfea4acd025cfc05e8f5fe9a0e22a0c91b55664c118d79887de91f1ae6cbc081f6f55f0067000000000000000000000000000000000195b23c4c2c087082c30600ff00485d169dbd360643d163f1db363f270cd7d4f177c36b4c291d50da4101e67b229d0de000000000000000000000000000000000df596ba2350ff7d3e75b4cbe5f8d6b2cc0e14b3bd6dc021936e3371ba64031f6266fb1d2951801309f22bfb1c4b27e4,45000, +00000000000000000000000000000000006b37e2226957d639fcb0bcd6c20b3c7b8372e7347a14b970e01c67c1859fa97c754ce588d0f835ecc053549d963ab4000000000000000000000000000000000c6a5fae8be3a32e3f70a4202a1ab6d97183964b9f7b9a084c49922cd9e0e952b0bb66c5580f0e0c417e079493bcdb4e0000000000000000000000000000000017b6132f11adc0d5d693ae7f3a0f89f5779708083eba23e03b0c9265e4e60624e1fb6940e8ee49d31618fa6389b1b50b0000000000000000000000000000000000a45c5f6df71359648aecb6434bad1619c39f10e279a02b3cc9725d0256bcd126843fc9ed29cbe02a32cbbe79774a3378176412b07eb7f423f23ffeaa0ee642590e0b7016bc063f3fffa93e1e35484c,0000000000000000000000000000000001fa243b548f8f5c2e5d7736ca6fa95b74dbfd31f95fd532b94f81a255c73e7c0e000e20f9ca6750cb0dfdcd2c1aea8a00000000000000000000000000000000132a893a2326bf61962e1855331a53667e6279ed7358bc84c4a7c218b6cff1d3f449954f56daea72bc2779c60f1113400000000000000000000000000000000000091dd23c75dd8266f556bf27ba54c95c3ccab06168e4e6d0747239722afb20f3db27454c6db3a88daab0ef10659a66000000000000000000000000000000000d3b2e3fd358aa3dae983e87b5d1fce6d5688e66ced6e3a2c96b8d48041557295d5932af6532c13965d4b383fb252518,45000, +000000000000000000000000000000000ffed009c78ba9af8cd33af7b7697ae4dff863bb92365055baedd2299b7f5b5e8abb84ed434f7223c3e309ca53c08aca0000000000000000000000000000000003b2370c837dd6291818efe7c9af62dd51295c418739ecc509d42c92e2c97d12a9fa582946e176e8153fc9a273140b2f0000000000000000000000000000000001e63438e8b4a0462cfdff64a281ab4a7f48d51b51325817139f8ee683484f8695f1defc0c3efcca81d5fbff06cf9c54000000000000000000000000000000000192fc391cdc1ed6ddbd317f2f366f2ce25ba27b8c0f09c733e7bc0c0697544399a3a4f1186d139a8f6399ffa88e89a69c4b5627d84e153f3a4ecc14ddd6baaf1d62253a0f88d3af51be18d991976da0,0000000000000000000000000000000005095d1becff61df906815842112c6508d6cade4ef5f4b7418f5f01e8c5a383addc1c572237613dfbbb88bcff80e4a44000000000000000000000000000000000bd2561e7bfbda8a48ee038855e37b03fee805689452e9afaf0da4185e0c194e407ce7149b713c689d25f953da36dd1f0000000000000000000000000000000015ba3ae4d4238175425ac5dcbd9e6e9e055b8c1b7752931b524fb546f7bee8723ef2e69351450c6d1ba3c366a22355e20000000000000000000000000000000008c17d77dcfda00a1d75ea0087c58e74263ce5ce4066e979c66397de8e236708831c3a9ca6b35ade8038a28930655eb6,45000, +00000000000000000000000000000000002e105e0eaa418d58019a849b89accf665a94ffb0bdf308a11b99b521de7af8ddb150c0e3b2e9c54cf5456b6105bc81000000000000000000000000000000000691a3b3986fbe1c0ea22329364454f37f645d6abe9310e883b9191ce512347e074e18e28b88c2adcc76190a549b80b40000000000000000000000000000000003f3a37a763c8d0d99a3fe36923843a22cb0fa18ced48493b2510fc99afe5b7699bbaa6c2ecdad8aaf72969354f121a1000000000000000000000000000000000f4bbae00205f54eb10c83d928d908fbae342b76050e33c51b6e282e02b3c1f132a4728dee4ea95455c25fdfc112f2542ed270764791aff081f1dc8051d22b8e18803a7e310393f21bb4a495a445cd45,0000000000000000000000000000000005cabaf39b93d7fe15ef6a7a3031df58219bce702a5a77162551a3d916c22e8ec9af2aa20659e7c4ce5f6382a5f82726000000000000000000000000000000000dcefe1a48d8c239164b54771118f7520ac11a7a6b72d8e17be1cd788cad2f26d3a0d9113e6536426800a744be9f0d4000000000000000000000000000000000199d95a44a4334c87aed273a0184be9602ba443d5b8d34f3495b04e927f4687fb88487f586395c7babb4f218fdbecf8c0000000000000000000000000000000010972032f9cb3e8f45447bdd06df82656fbd3ce38a9f7564c6e5d62ea3596c9b7e0a94046f1c65bf0452ca25b15a885c,45000, +0000000000000000000000000000000009a3e98fe4a98582ce9f274965f376cb45e8583775dbadf626cb1327c1f8a25b293b97e7f8f31ff72ba7e8e769ff25ef0000000000000000000000000000000018e4785ccb76c4897087c8a4242ddc744c6a0a53a4a844254153c23d6f16d4ddb945252d13f93101613f4eb0b1e2b8320000000000000000000000000000000011b81d344eac04d3471b1edde5e51f31f97bea3396580839fa094db58cf6bee371bbdc045fb60c3ee5c6cd5d3f6d3c4700000000000000000000000000000000073476bc5b1d52ff4ca89c3afc099417f473543fab6e59cf9de8a19705dc4bf2a210b1e6de4dfbde035c312be0c70c56fbfb7606b64eef0460b8f33a0be54451fb655ce0b81db89eb7862f392450354f,000000000000000000000000000000000f250b5e47ef616be106a3334e2f516061eec8f7ac69f08f6dfaedecd76fb1c9685ecdac2c3ddd534e3947d007ab177000000000000000000000000000000000073819a6de38303725aa3a9e5a7a9122b4d1e60ee8deb3554b5e06ef5e60d71517c2279c5066af003b32cdf83b7fcdf200000000000000000000000000000000070721107ac6dac198f7ed1a7f84697cbbc3199a220d1aaf82e6f015963bad863f99190f18a482f730254cef753ba22d00000000000000000000000000000000169910eb30b8fe1ad8f84c4a132c6c74a6ff06ed6e792af3baa6619e3c8aa6cc3e6f687299467ec9554f9e91bee77aa8,45000, +000000000000000000000000000000000c414b95b298b9c673001173ba7e5ee3e03926f28068481cfa0b469ab556f8fceba9fd0a815180ae0b82c265fd4c6b7e00000000000000000000000000000000054a242c1cc1a9c710bc23305d09c2d613ee8eb3840b37943bfe83f9c1db456ab4436ad319fcdd8684db129d76c95320000000000000000000000000000000001683711c0c7f02e67374f190eed1ce6559479d6d199f43fb5b0ce7df7774a5cb21c86b3b3498855d9b69c5763acd8c4300000000000000000000000000000000062f87085dfec847af518bd71c078f994b090c3b27c6eaad79772ab58afa43993db52fb08649a32629d61c3db12c87318a29fcc442d0c2446697e94dc47181dca7a314f9073c06aba6dc55aa79978d7d,00000000000000000000000000000000106e892e336b2155909946ab73b980ea761cfe8c48b13ae8a5302eacea08b9cef3e60d5b33c6ec4033218ae5761433dd0000000000000000000000000000000015daeaee59f3b4cc26d3da745661e74db8fe1ea115d50ba49ef5e6151a9ac2f3135f0232235cac7a53e1e8a70eaf0476000000000000000000000000000000000ff494d17c735b934c2c7fb8f413103188fdb116fa8f4d4e43262968ab0fa1bdec23b0d4d8b1c2defe624092de36610d0000000000000000000000000000000008f70b7e9f2d7083774fbce3bff58a1c73fbcbcd9cb049cba71c0c3f0c363517c8956240bcacdfb7934d4c67b1bfdd2b,45000, +00000000000000000000000000000000083eea9b5b2d5ac5f7ef51ca889a4317322d098a408a741827fb3419eb12a51c07c788c2798cb37635e224e99bbc894c000000000000000000000000000000001312ec00f4b3a4305700b44b3f215779a9a8bfcf5b5d3a7f237a33c5484099ec9bc5c8537fae768e2c0ec62168f383d6000000000000000000000000000000000cf1d5d05d11e1d07074dd34211d0f00eae1df4dc550c55bd2fdafaffa1ad36abd5da30c5d3a5aa2845b1d95a5cb571e0000000000000000000000000000000015223baa9f2ea4b04fdb05b05bf3a94dcabc5e64189aeee39c380de9a34fe6b4253f5795f70bbe51b80e1aec1eab7196d5b468797b4af1978983faebe59a28f34956dacf5b7f65d25548bcedb518f45a,00000000000000000000000000000000098f32b35e3b7dc1862ca1ca3c76d009f016c6b91c227f2cebe8f1fe87567d936bf1c54103bec31b3552c077c0242fb40000000000000000000000000000000005380a66d48d348487624a15b63d2ecf6976b5b599901101ea8b1f57736649b4397f6679ecab0ae29573695a921ac475000000000000000000000000000000001710c368f70a2b9cc92ec65c4c2ca35fd63440eb350f488e7c6646f9c42bf680eb62a887d533a91e47988221b46c868200000000000000000000000000000000033c3327da938dbe4630dbe16838229d7d427f3adf18dee6fa26b1c8067838922c1bce78cce08d590ee1acf2baebc7df,45000, +0000000000000000000000000000000011a960cf1978aa2ce1731b857fd91d2f59d4b8d7c6871ef6f4f85aeff549a2f397949d11a4793926fe7be37f3a83d11c0000000000000000000000000000000001954f056834d6e3b16043ef1acd0a47a353300257446e9a1db7e58bd0d7c4bc9ceb3db51ae01cfed9de99621e96934c0000000000000000000000000000000002e2fe460e71b65595ed93a0010e5ccd1a2c16fc4e0d345e7226c947f29720d2f3f54282f79cec086d3fb1999b9629b300000000000000000000000000000000060dd8a7ccb613f1521168a8a322aef9f84d9708a893f704f4fc9a19e2493f25620a47e0fff1bc1e212e65e92873b4f2dbc6afcdd409e5d50d7b655580f1144de77f3efe5d6268032eccab7deaaad997,000000000000000000000000000000000404587c60a4bbd8b5b929ca2ec2a9ff2ba4733f4f2877478a669b238d65ca130cba398899f2910d6de04615f8ffc99f000000000000000000000000000000000940659b3e6de7c3d8de9169a28e68dad433bda78de0991fe4a1d404e5f4babcba9d57c7f3d638aef264642f87c61fc8000000000000000000000000000000001676ce240e1ff70ab03f94f3ba3acd31725ec306ce1fd707e29ec22cf91746216dd998d03ba13a79dedf878fae38d68e00000000000000000000000000000000098a81422511f77191ee15d402614c86f9447ab78a89cc348414108f36857a1929f2b92ced78752ab3604f276861803e,45000, +000000000000000000000000000000001472caba61c2f1fe4b1d0912b114c25de103ef4351668f22f3a158d7a347539a7b6656044bd490f036ca3e29dbdded370000000000000000000000000000000015f8cdf7786410b409f218164063c99e77d8f72f03882a6c9430ec725ae574547d3ea3cf30c3ad2c9c3febe6c30b1272000000000000000000000000000000000ccbbed85c2809433fbcf22d6490457dab800b21cb4de414c7dd1804a0bdeb7142f8ffbb2de921c2c9eabee6a6351026000000000000000000000000000000000a404f42c48e3ca408d3f92079b99805004da928f128206d8904ecd7fcb14121c7d9a9e7fb69accaff921315ef3d5372807347519f114e78f99617f6b147ca833bff7be962c9b1e1f32b5babe6067d7a,0000000000000000000000000000000010a4ba6952d22a51dbb6762a3f9bd09712c2be5a98bf0ef298d7a7e3a9735ab0d3bf39e40b334895c73a36c218ad24b50000000000000000000000000000000002860f38ef61b497bdaf4faeee7b406007981c17246cfa36cee906452ae85e1c1c6385898ebadc3b4ef8887fff25b8240000000000000000000000000000000002dbbca9034fb17c3f37727d44c027cdf47c36f3f628ea9385fc9fc371d23f22d983656caafbf1cd1f8bdeff4ad7669d000000000000000000000000000000000b7e71b65765c4113a7884771952268a9fe10576f745038912e6877c78372cd261220793b888c43accba1646e902fe14,45000, +000000000000000000000000000000000b52f05365c4df20a7290aee71a7e030615d1a2a971167884d835c24e756a0faf6ed0552341c561446c7fd3d5e887d830000000000000000000000000000000018718ef172c045cbf0bb132059754b62414097eef640a781db6ad521af5a24d78c622d9402033fa939f70aad0510a1ac0000000000000000000000000000000017e969e44b4910304b350b5d442bb6a0b71e1f226cb4603cc8b4dd48614622f3f4e1ddecb1894046649d40f261d94e030000000000000000000000000000000004dacaeb9e05b9d60ce56c17312a092cb988bff426b8a718cdff860186935507a06eddbc4a1a29e4ef88db83fc4b6e77830630695c8dabe9aded1b5365bf93770aab7e9ef4140a2bbde2f0a7b109724d,000000000000000000000000000000000e9c1a6d591be4da37fd6dc283b8d899b625ccc96371dd3d7731aca66cd2a978810497171f2aeded64fa2b10e480de2100000000000000000000000000000000006d2ad7287847255002480627782d513eaf1f68a3d583d4762fc156b8eb40deae6969fa8a7d8f8aae923800091386a00000000000000000000000000000000003c7eae0eda08df9b9eee2605a44fbb486e3bf2e409aaa1c8f38c06f969ff1f74338004b01288dce99be26a837e45d3a00000000000000000000000000000000178174d2f569a9392eddd2715ceba8762c5bcc6325217db5e5f970d6fde069d0e48a824e5b6ca017891de175c92f6b29,45000, +0000000000000000000000000000000019829d5799eed5a081042e4646d46fb6bead6d3b9893a4240867b25ed6af6a3e154514f244466d80e3b9311e060bbd7100000000000000000000000000000000156157a654db2813cb9c1b4da0a3ee192fad076bb2767020fc5fc00e967c1a35a367ffa375703e1181b3705ace9dd28000000000000000000000000000000000093385a6a9dd0ab996df54b23f47f4a49b3f379e11bc8331016ecee6161fcddd22f6d49fbb21f098873f1e17424dedca000000000000000000000000000000000d5b5b0f2ce81e755b4030b33fe3a8bdee38c2c60ed3b4a88bffb9207cb762c0a5c699ff424c000ab080d763abc5438d184ef5eceadfd77b3a4092696ec34d0551c88e434567638623740b7d5f9e3616,000000000000000000000000000000000ce12c9010b4c4afbddb459c1b46063a8488277948188b4ec0b739e1cebb5653681d0e43a0d2c6b3f842bfc609bbdee3000000000000000000000000000000001123f60cedddaf4385e63758d64d4facdc443854176ec199ca0df0a9c258517f2512594f2441a4b9a68aa9a2b4a1f4bb0000000000000000000000000000000007cc6f77d181d13bd9736ee23a33b25b0bd969760642ee19004e095ebb8e2b3c0e09321eb15a2f7961803c0fb10b6ffd00000000000000000000000000000000004d8dbf2f0c14b07ebed2b9cb4bc87df78ac8a34ef0b05cbc2c6fb8e8156415399fa52dfb968ef0e6ec697030fb003c,45000, +0000000000000000000000000000000003af8c25bdbd0dc1cc344d55366f15555709a74e1f0d8d7050cb6b487759db6200401b7868fca3c2ad26e6362a30e6250000000000000000000000000000000013f8b6ffe30f9a133fafe64461d305cc6b2cf5aededf68ba396d4e00df651531c750a3d94dd77bc5c6713b939b18fa19000000000000000000000000000000000dde97855d7728f409d873b83b6879b45ace5b73f317687fbf478e594a959ce21d4d751db646ceb20432e8311e67404f000000000000000000000000000000000fea997323cf29710cf0e3d44ce682e039d6cbda155e43c94dc8cefc5e94000de4b9525123b9615b5f1019a46ef37ad3a80d9efab033e920061cee8f8d7ea6023cc05f08340642613628b39e7b7fd0af,00000000000000000000000000000000172805bc715a8cfb2e25c384214f4005aa6d3b809a0ad95322209851ef92151526a9d01a914c4d7f0c120b9bf3837010000000000000000000000000000000000473ceaa092a5ac12f38b4065477672deacc08e553d8e9e6391bac0d9ca50015934cdbc340deb05aca916cf50c7915b30000000000000000000000000000000012e85461fbd26c2d0235acf5c8665750656819bb939e8fae77a8d526ca23443aee395a985cdd4b1eb700311fb87e91a7000000000000000000000000000000000246d45fdd88448c93bedf4799becfc7c80e67abd483f2a0aa41e8bbb3f38cbc900314436364f1db6e1d88595544517a,45000, +000000000000000000000000000000000cdf60e3bb018407eab162822468255bcffd54cad9127054bd1c30705a4ebf1afc7f539cca6ba4cd070b44410ec751150000000000000000000000000000000009a2e3e5993b6a7007dedbbd21737a8c0aef3ecd4607953c4a24bb3fed97ccae01ae1cec024443f300b570a66e9ac3bf0000000000000000000000000000000008a21fed19e9ec2a741ade7767b0c9f39b79c3fbe34aadc9eb3043583768d893bf927d26231759290c7dd9c4f158d5a10000000000000000000000000000000018eef4ff88d63149d2632c9db586a4af0606644b16c82fbb0a3b869f1ff924c59acc8efbfde7bc604497ff68939cdd0845111c860f6f5725f99b225c53b9fe1a70150e7ce922bfe214900aaa2790d145,00000000000000000000000000000000122e1f2081cbde0055fc34d2fe61307bc333b35a1e0772a0cd6fb25338c89824bcf2f066bc7b571b2fb314ca7f45106c00000000000000000000000000000000027ed81b54372d858a6ba2faa65fdc132efbca6ddcd56c3625bd9267cf0ae04f6d342209b995060f584be8d40020669500000000000000000000000000000000002a03427a093a3000a1bed9eba91a82dc2f2fcea1a16a1fb8af29c4988b589abe6a505ec87a82864b3c683beaa6420f00000000000000000000000000000000134bf64871d69a72e42766c2903fb4589b84d7772a62f7d2f8f8d02a914f4d3a278c680c626ef4d69de8aa88b57589a7,45000, +000000000000000000000000000000000f5d47911596c46c0c08cac5f5e7f6d0609874da4ac1bd4e0e59c393273a5fe31a756c7cfff2a01d19e79d209d7c6d3e000000000000000000000000000000001010f864eb6624132d4436d18db7f5b34727060dc426c109886be88031e3c155490cb3fb09e1fbccb7912875477c6d840000000000000000000000000000000005cfbf1c2ae1b80a8c7cfb2cefedd907b0552794f4fda101ca1a723b18de8cbce30eb54287e1847cee3f416cd8b45f2c00000000000000000000000000000000084fa63781f7eba9c7e911ae5866d485bc7e90603541c55d1ffad8b3cf7547fd57fb24b14002560e58410b828513e109c07041840216d60ff445cf53b273a46016c8ecefefb53550f8bafc79966f863a,0000000000000000000000000000000018fa44efeabbd1cc47dd9b1a1195ca921c99c77ed43a44502aad27b6c663f5ce2623382c3ddf208f42e3eea741281f4300000000000000000000000000000000138d11e497e3c5656bc8fc0ae4322a0bfb6fc20e249a47a103b164aa3d9fdbf7df4b1e3b0842b4b12568a31992a151f000000000000000000000000000000000182490d6ae35c1208c0d608984df4988d057f3ce5a25073c77cd5b224a5892768badb1ad5cef8f41d1d2022573098c320000000000000000000000000000000002a6e0523781ccdebb75063dc7ad1a9526f9ff8ea1364bae487914f254c0eebcbb2cfc3715fecb9599bfc2f5feaa62d2,45000, +00000000000000000000000000000000124870cfa469136c638e0cbf15802f2699aacb66d7e4c2965c6759dbca4b7e47941ad9ec37a84db1afeeeaa65a7418e4000000000000000000000000000000000d4503049a6a53536bdf41dd832a6ecf3f10554887da7e389cf940394e1d88db94369b7947436546eb6c6e82c48dfb9900000000000000000000000000000000053f9a6e1f05b67cf553073358009a172e2ab8b43572a974da1f3de85a29103b13d7e67b2a359297172d27dba5c61439000000000000000000000000000000000abc29f50ddc1c113c73700b9b9796890cbf48818ba981fdab2db27ef1c58f4c2e4595b99eae397d40990ce2f6c9317c29b031b82dc8c9f4ea9524793b54207d4e13a548d73297f2aa6241aff57abfd0,000000000000000000000000000000000dc7488491433d5b3924105c01ffed4f30b755d7253d867fda595e7d80197823e56e4d182d5ecc72d8ef1ba9bca15a310000000000000000000000000000000007bfeeadd6fc468ef6340a2b394c155bf50808cb11e89adb0de5499fbdde91760e9531c1deb23050286a15e5910f1d5a000000000000000000000000000000000f096db706b08485fd577f37b7bd232b5a10c3f80c25bcf82f7a3b666c6efaac8e856bfe5f7dafb7457e33eadcb4133d0000000000000000000000000000000004460d1f25159ce6df59efbd7c693355af4634dadeaee2ced68124b2a887698c10e9c4b40c4f4f9c8444acb881ceff65,45000, +0000000000000000000000000000000007d2aae9794b7a7de97f7146c0ee8415e09e56fd42535bce6773cadd6f7ac09c4eafe2e926cb7014377e54c703eaa9dd00000000000000000000000000000000172a4a33ccf99eb0473b2c44d30bd53159afae0c7706ad128bccf6258974d5e5761f9be43e618cdbd96027aede7fd5860000000000000000000000000000000012601bce2171c6e4c2968a3efdf1491285f9e4ab37cf973ab5c8e224ad5b40e1b6459ac89090c73deb8fc79fec7fb8e200000000000000000000000000000000112a6443116e6f98ab348e57daa3971b5fa506e40515e1611fbed3e7dd64c5c1e991e0d2539a70eb93e3da0f573d6b2263d26ae92119c7b06d83d7e2922e06559b1740eae315c6623d3e543c9bf54258,000000000000000000000000000000000f1aa4a7a22c568c41270d24824138bf9ffc763a5356b7c0bc1d051a0a0db12616700d9214972b63eeb2a398d27dc83f00000000000000000000000000000000020d0c2ff8f93db6b415c2a01712034e46bdeb6e665a5177a3877db9f5401d3dccb99907ef843062e394c1428983725a00000000000000000000000000000000088abeb6fc3ead45d5b261b7d684f168ca8f5f163cf338863e6b102dc40e2cd0ede97c47460ad6f560c27e95c8b71ca8000000000000000000000000000000000ca2e5cec212d581c737928512118e2f51a0d74070f40a998b7b06d22b9fc754bb2fa5499308058be9ab81521d057414,45000, +000000000000000000000000000000000030372914b83644fa4db1958831e9335c72ab7a811fb337696221a3290e4c54bc10c2225f8fdc3a9f62632ba2f1594500000000000000000000000000000000114205926609470b6022d24046a1997c048e6d2cf6043397892c967692161c0ceedf409bf5e1199a64eabb1ff8de23640000000000000000000000000000000017cdecbe73779855b7b94920d4bc8ad057ce51c5481a5579650df8a5bbc421030d2ac44568217c4dbb13d7c639760236000000000000000000000000000000000f194fa814bfa7396697bd812d9449d06fc61b580d7a86429fdd1ad376e21ceca139356d7d13964c3c684563675711c67a02c61a7a75342ee7f0745886c0ea2a73c21500aef8078d21d20b7216c2990e,000000000000000000000000000000000cfa23c46881893f6c50d238a83669deb520a7fffab4f912f77df7cca43f6827a1a0ae0b3f36c8f116ecefa33b8bf37a0000000000000000000000000000000014b7e5c18d2f9bfe15b0c1af3bc6e230039a341e135837d123e91cde9cbcda298c66b93f692232c912e5d7d3d6331c430000000000000000000000000000000009c8984999ecd3a4144ccb925d3e5cae5c1662dfbf8871013b1cb2946482fcb075c489c61b8d6261f2574b44da3fc1ce00000000000000000000000000000000196e7feab383211e4825cf98219c63bf9f45a72d66030219cb585d5d25237a01a97f00e122db6a51325022e69e7d8cdb,45000, +0000000000000000000000000000000015d4ae1521acf897344c3a76261754ff99742585af4a0ee86dc473a88fd408091404df1da9d8bb291db68bc9c07d6b2b0000000000000000000000000000000008ce160213875c661163990f3f7ac219ea295db5e828354864517ea8689ec15d35c6df78ff14cb276e0c97ffd7fbc09a00000000000000000000000000000000038a3ee211e777d6d6b7ca6c7a0d2130f1a071c030eebec412c3a0f14c3584e7c5cf15de254a8f141a8210a90249ee5a0000000000000000000000000000000019f7ec6b2fcd8b3190ab37a6e843340d3f3fc092f5772a042edbd5bdc967b96e8a1dc9e435b8463496aa1301f87d0e5a81b0c87102055dc2901826875d5e85a794befd93fccca2b9c0a1f70ef5610d83,00000000000000000000000000000000005c0282830934ea09c9f51b52cb6dee75b874b155c63076dbac2cbbf220863d55557ff1b7d681fa185435df1522f49d000000000000000000000000000000000a1680ebbb185c8e7d8a197a523a7a5e618f97c46670622034d312b3eeef140150e03b00ae3dff8d9f1d872f3d3dca380000000000000000000000000000000019bd2eb4bc25f5aa6bce206f0683dbbbbb002098a118fcfb060c1353a310c2baa1063a782bafcf6ff6bb8edaf6f1597a00000000000000000000000000000000082edf49a0435e0b9f3dc7f207711d66004ae688b18f5b62fd1596899ee8edfaac7da38973d81f12200018fbe8151572,45000, +000000000000000000000000000000000fa7f8fbfa1d4ef5f001a451c55ed261dee344025e599884b29d086e15665867932120d33bee579d5eb1b7e6c7299f310000000000000000000000000000000001f06356f793350b17b47a623059a068800ca1eab6089c7c146182990063e8e23bbf40d95a42bf6e976224b680b75bfd0000000000000000000000000000000008807f6606d2302450bfd8b38fd4147b851ff59762c1ff48f9442c4d7b77a32c5e023821eb47fca839a27fde60e5f61d000000000000000000000000000000000c5b92f1ca9c20d4b6b11d794a5853824cff20d9267a20a7aaa4bed8bfdc728c4d4d50feb8f0b569757b97f473138db1ebf66fce49c6beb12737fe05e3adc0a51ecfa9144ccf6253088dd1a7a483de07,000000000000000000000000000000000b8a715c1c2792a30f7ad752a808b621c34af1fb7f1e3392a36ca9481a019108a21e3ef338a1d05f2f23ac3e2cc42ed500000000000000000000000000000000101375c9de592031c55a7a62189fd3fa3c565abf7c64724796dca3b1c7a6e6834a16ef1c4e2afd6ce2e69487260f0028000000000000000000000000000000000cd385ec8245431d3b1aff88453db7f66a5d7888a5c1e0dd0abe9ac7db752933a343b8be53b7bfffb704768ef0a3dc5c0000000000000000000000000000000015d55c8cddb8715e25fa260d1e1fa672ff76eca7c80d19d00678fb9d08759b810cf266ef0a7e9dd749a576ce07240fa7,45000, +0000000000000000000000000000000001191410ec6c5ff628bd25d35965f5e9fa7f3c3d8c0a9a1ee7ae37437a97c25e221110d892e2c7a0e9c8e386774eadb80000000000000000000000000000000003be30c25a18cdab139277232d8888f6d13112c9556895af8030f1893114d5845d895df9afe3c6f9ff7ffb1919adea9200000000000000000000000000000000197f6b4e38be0358a3f1722664c61e62587ecf5467f8aadc3a236b47682a75cb76bafb18a5c556b321d5da49cd4bfd4e0000000000000000000000000000000002e4ebf7f22d929b7421a600e67fa2e64a59edd87a2e2eb9dce1f06d3c793f1a812bcdd510e654d44fb4c1de8c64ba9f0305523dc79dc4b905e65587fbd095ed57aa42403d2df5dd489db8f50c99e9b6,000000000000000000000000000000001311de31229f1825d0bd2c9d726fd71e05828a20406a4705ea65f441537486338022bac4e552bf3c25e15717bee00ba400000000000000000000000000000000052e082cbe36c854a028a041981fed87d39fb218a88208aa1096e260a3932a1155db7f306c32d133070b0a5bb6d161760000000000000000000000000000000003269d4afd20002873f4305018a4432c1925eea28486d657cb458198ff2df9d304bdfc7455233243b1712d8663591d460000000000000000000000000000000013376fb98929cbe7f7d090d1c9d5c4f6332bbf25470aa03c35a70481931e4bc91c937029a5e11d2a3418eab698361227,45000, +0000000000000000000000000000000011c6f1dbccde640f63ad7d40089779d01075e26269421b4ce12fa5341f58ee9110f17d08dc1052426f2d00da2dd70b4f000000000000000000000000000000000740b147bcdf06705971c113a5cc12fb37345dd59f2cbb5ff500ce2b347fc5a8199cb3007a871670d5093f28979cfade00000000000000000000000000000000046563ea98b5e85b3c42222d5e0d8481e6aefaf077a1b99f2b4eefb397ec846aa3659aacda569054c9c8b9b69750272b000000000000000000000000000000000812d887943506d68e3525ced9b979354539b7b14003a3169e0084c26326b92be67346920c9a99ef0f9638e8991296feac23d04ee3acc757aae6795532ce4c9f34534e506a4d843a26b052a040c79659,00000000000000000000000000000000021166263d1a443d5b2eee9aeca3678ae4c2b44d556a7cb9631d47e4fa3bb05ecb94d6582f4ca0cd787027fb5f2efab60000000000000000000000000000000015335d034d1a0ce78e1246a16e35e0075f73d4a392da1e05c11388084cf80bf31d499e57c48f4be6e72d3abc7b387ec6000000000000000000000000000000000deac4ae1900a4e1814624fb4b8c7a3149fa9cff2ca97f02e7d6765e034a1532a7b8475ef7aef5ebb851063cf4b9e79500000000000000000000000000000000161e3af03f226278a07ff3b08e5788f6c5029b2c8293e7a7e3ae11c4d78676b60dc0208cec6b82e1714d976007fbb389,45000, +0000000000000000000000000000000004c8078fe8567013e8d05a546934026cdeee7d485e30d739407db16fefaef53ed7bff0f9adaaf064aff014ac919d91c600000000000000000000000000000000107cc17f485af7f22e07cf14c5cad6368323f720511fc9dda677b360567f769e47a77f61274927ef9b7be48a77357ec40000000000000000000000000000000001487f0880a6cbdac33ca35b9b65e4ead9d8c2e9180c993bdb2052060325aff8c62668c643f0cd9b4bb1f06a3dc74285000000000000000000000000000000000d4b2d062e31fabe8d2a329dbd6417673a519f455739d140246f2b3e43e20f390088c08e545bf0419d796ac71aebb5198586d7ad8fc3e4fb42981a4415224c0d976ebe1c342e9bc1cd66d35168bae33d,00000000000000000000000000000000120b4434babedbd8ff295a6e2ed5fc7af0548d7e60663110050be797584c0cb638988201ae7707cbedf0c8b3dc5ced85000000000000000000000000000000000d2de0a260bdd241a145e3f68a6de48da4c65107a500e02bfeae6ae7dc428026c7c3e9bdda9a3069d2744705df2eda9b0000000000000000000000000000000018a237906c0e277541c4f00c4c2feba7cb2c9b87709c18b62b7c36d78fc118cfd65c127765e01dc0ae5875b9552bb45300000000000000000000000000000000197485daf54e98e097b6bca24b0738682969256decbf3ebc05f6982e4608829f37e2877937b3f26b88efc3deeb4bfacb,45000, +000000000000000000000000000000000811e9b0acfc10830c074c5a4d9f4d9382461eb523a61dda0b77f1c43b285fc5c1ef3a1fafd923addc9a6e904505a255000000000000000000000000000000001113102d015dbb509f0b8d0d0ebb4d3711c4f0e1e3d55fb0af247dd24be4fec9d6fe3ad73fbdcfe206891bcebefee4dd000000000000000000000000000000000085aae9e58fb97b96ca3c089acab7bdbd0c3adae141bf61075f5c13145b0d07113f1075dfb959bc7c2d3d3b3a06ab2a000000000000000000000000000000000bb5eac8125807c10270d94e5bcf278241d6fa82f68e41b5529b28aebc88870af55881db526f7bd221a8c4c0b29a1b7d6e7db0fbd2a7327c85054b4c0de9727dc0b051058f8bb4ecb1dcc7f825781712,0000000000000000000000000000000005de82540aa67c69b962d292133b09e6593961da8944ce02557141abd19ac471f766b4083db85c67a44b65dad2202488000000000000000000000000000000000cd999bf3cb004074fe9f355cd8dfaa7b9d3439d902fddd2fd0688419b5b7f8c4300ab26b658936a90c0b8e1488249d1000000000000000000000000000000000f97ae779429a5afaf7a3343586eea84a4e76f00a1852ce42a4940babd565bc8d61bf72fca9b123922f1ccfb1db8c06b000000000000000000000000000000000935960fa941c27e74234a07857ee680f53c31047235c6152d1669724bdef37ba642cf4e0dd355443ea470e6430def8d,45000, +000000000000000000000000000000001335276775545fbb4c701beb57cb34312108c9f1d46b4aa4b09a16faf0e648b4e80848bf5e75ed8730715f0107afc9820000000000000000000000000000000006ffff8736bab41b4ee5681b741a81fc870e648001027161144254d04c678e4f954e9f191bd8b26201aec681cbf0654b00000000000000000000000000000000026ede90d14fa0885baad21f9631bae058573251cbef5757bb8cfad061f3bdc78834fa5862dea19a2236c014b0f1652e0000000000000000000000000000000009844d0cf7f6f3401145d8d720defa577ca46b49e04e39c4c139ec6811a574e7dd5ce3acd00d1ce9496f10dd15c6d94685cc8d88273d4aa822f44a447cc22f5a58c420bcfe757a459772825619669a72,0000000000000000000000000000000001b0aba02b0e907c03d2f4003083c824ce60f2f55f70dc6ec7c7f81f3d0ef4bf533b4c94833e36e8aa7aeec18b7255de0000000000000000000000000000000004fc227a6ae303f3006f75193cef7c653e6bddd28fdb843b41c7d39966a701ba8fcf611efa71abf059d7d98833480e69000000000000000000000000000000001077fddd0bf3d5c80eec653916f9095e900cf165315d74a872219285f62b5412536e43c4cdbc120ec5c7753318852dfe000000000000000000000000000000000ccd90e01c1d4a00f0d9e29a88e8134f2cf68162da66bd343645a998730190114a6921c9b048dda58b60b42a133287f2,45000, +0000000000000000000000000000000010192b925fca096682acf138833b12d96bf97c9a2e69e4266eaaae1785b9008f36082e23e2d42341427edce24449935f000000000000000000000000000000000d5b24a94adadbf542aa663114096bc670e1b6c99f3b661f55de121922452534faed7f68d6b431fcf6f3e379d7acf6b6000000000000000000000000000000000acdbcae49206b749d8c0d21017a33e689ebe26804d1fe7c863a2ea4210c3559805dcf73685702bc56e644b4e02614a9000000000000000000000000000000000092309d684fcdf44bfa321d473060dc2d8a8c66c51419894a3fbadbf1b56179c31dff25403b970d543f1dd0e19e56cf5b6e462d809f8bf1a62f276dcb27e42d9aa0ce33fc4e149e87181aca70a4ccc6,00000000000000000000000000000000185520023714580a3f235e24316478b8260565ffabd39670811519066844e131e337bd62ed2069bc6d2305e6638e539700000000000000000000000000000000055fc74cc7cd3fc393d5b5ab2419414effb783ff4da2516e5465a4acc195339c7b5238be4e0744b3d7fdbce46ca7f5dd0000000000000000000000000000000005f584a0311c02d611c15163529130a2fb3dc853083e7225b791ce5ff32d5ef7039c80edfff317ce9ddeef84443b5a51000000000000000000000000000000000f9d5acb355f767cc6286cc09f6df232532f9a0e9e4ed1fe28788abecb200e22066c23f3ac6c49c47071cbb023e70183,45000, +0000000000000000000000000000000014441b14765eee30e8131a7ef62c3b59370f2f6f0dda20fb2a3654fa09492bf695de1d1a8f250bfde3c7d2ed805ffaeb0000000000000000000000000000000019d813f8be2519e89d42a9fd3fef09d44a996d6a4713a9c224bee10f0ebb196370d6231fad810edf9cb4c875f08357890000000000000000000000000000000001a5abea13e909bbefdb51ddc699614366f271b2f6490ac8efcca7759833f3feae11057ab1b9ea32311e7b6ea6de110c0000000000000000000000000000000003ac2bf3c5486ca176e34ec5212165cbe04fc9e8c375e3e999a31fe014eb824ea3f2d06b9cf8b86ce3a76960cf2eb4d7535b53ab5f1c596eb966f57867e021d0f3b099e17bf384479c959794b17d6a4b,000000000000000000000000000000000ceb56d75f3aa1548c50d7780ea1e33c3d069b2f37e7f96be6a8ec03266fa8d0868822afb3b2e54750722266f6032a8000000000000000000000000000000000080f15b7f9f2c22f1afacf558267b5b84f3a6d199fd3349eefa2e46c4f332849c0955d19d4513151dc0f3b566c0058440000000000000000000000000000000013305f8ff6080f7da05c28155c0c2bc1c78d855cdcff0bb2c6b82cd5107d7a070d0830e6705f6832ed5baf75a659c8870000000000000000000000000000000018f4e136859b4ceb230450f9abde0325a4d59db98279d7fbab710305ff53250dae1c8789cccc27586c9b9df5c0c4722e,45000, +000000000000000000000000000000000598e111dcfeaaae66d1522be2a21131350577253a3f33bdd74a04b0bfba2940e73b62fefa8f0c34c4aa91b633f6bdfd0000000000000000000000000000000017fefff7d94afbeceb33714e9b5480c3a2f3eabf9d7f6e8507ae54cb65f69b21cd7d04d23f24e3a272c589f572b91864000000000000000000000000000000001652e3f5a99ba8dfbcd1f90de955ef527947642054be603c1b84b24bebb579b78e2a0be426ec21d32783a0e55f0178dc000000000000000000000000000000000a6c9ec91e8bc86ab198416cbc76239f0ac0b903f40310ee1f2066b01b08191538ca913c2736f53f23ef37fea13d52756e0512ecbc5a1b02ab19bc9bee4d3d9c721278e07b7a6e389c4d6443232a4035,0000000000000000000000000000000002a0214be95f020c70221fb4fb6856af7ce3845a4b607340f85127b52f8a204efcd94a152835860a4ddeef84946671b1000000000000000000000000000000001767777740a9922a91c39a36e2cdfcd544df902b31812ffc88418dab7321f73406ab142055b5bb264c187f2d4f2d6f9d00000000000000000000000000000000026e6941364c74997506df0f9fbe6b2769839e8b7c7293f4e63d13bd7bee90ff779cf82adc2f23c569d1e13826cdb0e4000000000000000000000000000000001618ab2ffd4b823b9c9776baf849641240109b7a4c4e9269f3df69a06f85a777cb4463b456023b7001adac93243c26f5,45000, +00000000000000000000000000000000072e022c168461905f798e87425f2eebb517e473cef98c255d0fe434863ef5811920af65bc946b29d489b5dee1066c56000000000000000000000000000000000e7a9872caa82d191f6014c845e1b3ee4ea1ee89852b546a2c85ddbfa3c1d4ce99002e3d7732ccb8cfbd57d550285ab400000000000000000000000000000000144be65db373f6401d76e0ee64e51076b861e8fca596dd6a7f3b5735c23b0cd13248404fa0969ecaa701663a1032f48a0000000000000000000000000000000014c9e9c5cffc4518889f7742440053678ff1d9fb1a1a103d0c1f762b10655bd5849ce98f4bc5eae80bdd9e767aae4523a79fd15e80b694122dddb01f836460b3eff99e61ea6309d6b395c94fb5a43dff,00000000000000000000000000000000054ce66b9b0b3cff6637d6cfdd788719d4e33516b98402d8fba54725309307711fb576299ba99104d4e7df0deac9ea2500000000000000000000000000000000055e06ff52cda9116a98ad3709f788d39db53844b7db58a57af52848ce1c59ec2a1f083efe79c5994b9291a2d1020fb900000000000000000000000000000000040c9ad63698ec78d06b41bdd6f5eed089b67f106348f9300f822a2d61ea1e5d2ddda0efd1025825c99cb0e243573f7700000000000000000000000000000000195dd00c48186f8d1337ca857aea02c4d199d638133e9cbd2dfc5f633502f656343746ec2a416465c3c0d4e9d53fd097,45000, +000000000000000000000000000000000948d0f0c20715f8658e1f2b4f9d32d851e584287225a2f47735a1f4c241b07f8d7c5dd8c13bcdf84e97d49817d4d88a0000000000000000000000000000000013c064548cb756b48600dd535af8eb5b9138f984bac0391df2e90a204fcb6c36017df910031864d802a2ff719856b336000000000000000000000000000000000000b7eeb7c9a01be88e573f196c2a531635baecbc8cff9af385455af3757301436686596ec7fe3618af26953c49f7450000000000000000000000000000000001332f4dbd5461ab9e2c8b3c19c6ff407a071018c92d2c17c1d1d481c24565276c0f55eee8692016c1fd76d70f44627cbd012914a96253926fdaabec06944ffcdb4637a05e3e78a9bcf1b21b68b9dd9b,000000000000000000000000000000001141b59af8fe6cafdf2e247fcb0ee4642a9b4022b6d71163ec9b6ac2f7d10ee3c5c0173ac686b03cd6a7086b039ec786000000000000000000000000000000000f05ba6973c5d865ac5c037583b65eb4eac826b5a04a7ebed1e10bec6ec7dca93b1c2eba70ee0189fd552d5023f2a87c0000000000000000000000000000000002e54475940985ad2115223c5ea3a4c95890f3e9992e3e1a6df2170ab77143bcc5d29b9dcd1ed3bf16e545e9be21a8640000000000000000000000000000000019acc4705955761518cea482b83e3726dea8d1f66a5f19b06cd7ff95828e15d1b139077e0d274b0e6fb86c027844d97f,45000, +000000000000000000000000000000000d3ee70610b5029a28e586f0f3e65bb19a263db3438710fcb8073e1b25f83db50eb5bbb9d75cb20952a225023f747baa000000000000000000000000000000000682f7d5cf9d182b20ee88683f3915e8c9b03074a373e573aa57232de4e997bf155acf680e365aa0988989dfad102b2e00000000000000000000000000000000143962963e230a9154dc328f9583f5be6923a3b10ee7b1d0cd5f5cbff13913d8ff78ca315be7387900a50b94449884c0000000000000000000000000000000000f4f934b42452d41cc20d7b1ec547bcbcbcc10f215364ccf2b864db23a09d06e94c7a87165dcb691f4975323486757ada300c7e1041d94df0e0201e1135fa6eafc98bd33b2dfbe4c59b546a52538c07d,0000000000000000000000000000000016fb5839fde95111742255b33f040c41dbd0f142d1daa8abc7c63008ba9f63f07381d9d6128240ae9b6cac5befad84e5000000000000000000000000000000000389a11727c356b8f3bdb6a73bc2f6d2d73d33d287365283359521dcac64f17810bd58c0ec5bef4db253bf630bdd9599000000000000000000000000000000000629a8af1bd0c1b1b6d7e447bb779663d7bae8e895e09418bc350e644d7022fa877496f30e2018f5dd1c9683b2715adf000000000000000000000000000000001950185d2574fe0c8277e3f93f59dc5628ec3487911ba9c3194a2f716116ff0bb9a39dde802dcfaa61633ad7657a578f,45000, +0000000000000000000000000000000005f0fd4080e26971ab16d33aeae04220ae23781da3179e38190082f1d167514bd73bc8ef976a2f333570e9f56a6c05e6000000000000000000000000000000000e159905d29b52ba61575c3a263093017783e1028b3701ccf060c165ba33a765b5265a9b1681c1759bfe2c9c401275e9000000000000000000000000000000000c5ac0bc29a49a7c37d772954da850e6b5e301e230552be9a94017d770ebe2cf4dcfaf104633623e024aef6db57892900000000000000000000000000000000002228e7f42a9409acab49cca82cacf306f6c6c29fd9f7e2ed12fef2d16383cdb7bb2b39ad598b301072c615232db1fa833e9cdb10fc117afb17803b61a2bca7de1d190a325639eb23743f51f28294b33,000000000000000000000000000000000024c03edb9b54034eacca4b321d51397348c57f406b074b16a9d6215e03f842380f5358f5c095fcf5bf3cabcbabdc260000000000000000000000000000000014e62dc442135d729f65090475fb408ebae132cdf2c2932582af887ed54696f3cd15b163f11285b99e8d8f809aa2e65d000000000000000000000000000000000438a2c99df216c67d92b99d9ee8cbd0e9751e538074d146767bde9675ae3a05bdae051efcdc6bbddeb1b7a8288370ed0000000000000000000000000000000007c462a8f5720e442e1917bf75fc3c3dafab6c39c80d0b93d81d1db4080f6e199be092b4b025e7b02efce4f30d00299a,45000, +00000000000000000000000000000000180569ce03e4a0155285e733adb18fbca71225507a7adf01cb8e8648891525305e92087f58378f4fd8455d5632ad660e0000000000000000000000000000000011ab84e42f10154e306a568d7cf7bc381000f0add0500cb508f695a3b283ea69d140aa0ad48fce2d2d6fcafe60761078000000000000000000000000000000001136c3016474d6f475609606e8d0269fcdab9fd3188a512681cbc41eedeadfa3b3d9355e5b4503e8b5c3665e49fdf3ab0000000000000000000000000000000003f56cba1b9cb4302099b16b09c2602dfab80d1151685ef78e5054cd454b319adf8b5998053a5b9fddcffa020595e3bfc48b98edd9c229037751d02e58f3d4234d9a3b0ad9ae4947ae14beebb274746f,000000000000000000000000000000000e8137c15436264b5960c27d0c22be7fc5d56a12f43b3832ad0d7f5abddbaaccefd140e2f7c476b99e6fd9b3a52743600000000000000000000000000000000019ee3caa56f0329a2e2acb8907b3edb21f4eee73e312352796b51282e097f9b10af61805d5c222332888737c7f8e227d0000000000000000000000000000000012cb9c610391940fed7882a5cba08eba4226c36eca8a2ed22fb5e752e0a1a5ec556673e47013258b499268f1de77bdf100000000000000000000000000000000031b769f606fa25b81a982db86a1cd442ed738019e7e64728ecf485cddcc17d9dc271146196178740b9f05f56627b061,45000, +0000000000000000000000000000000004d79dab9eef873f3415d66172bab7166ce0c71f322529bdeffa915c1b0d3fcd645c91dd3450ba61593ffecb95edb91e000000000000000000000000000000000d611a207d3222bba199fa083d0459675cb5fa00839fb4c9034ad868fc1e79d653c18651771431d6fb6b6b5ce8cf6f7a000000000000000000000000000000000ce802ecb106a4f0ca4efdcc058dd0e29deb6a5d30a2c15c8eda896bcdd3ac19053c10105328d239b26c5ddbdb3a95fc0000000000000000000000000000000001073e142621ecbeff6f81453660362545751f992ffeec3a83477fed3e6215a709ffe0d17b65d3369f8f3913bf000e844228758d2cf8105f2ef11d83018157a3119a44874dc34d5f0bddb533f50df52c,00000000000000000000000000000000080807a0570b628549629d2eeee39de773badaccefb76e01efaecb0ef0356f535d32c3947f0613bc7d847ef8c8778f02000000000000000000000000000000000e8c091ea30465d204ace72015cbef29645206390fd92ba7c4aa0fecae4ecee53c0b06e1fece99511efd8c7e9cff1a8c000000000000000000000000000000000c881c678c94d80164bb3295acf4341fe6c726ca64a1a015c890450e719b85720f41f80369f99ad3e7e3169ede0113e00000000000000000000000000000000008a2fe01a7100afda40091eb0b2b14cd00b7a4d8bb5cf9d9a3847970a94f2035fec7f292c04c38d7e49890e612830aeb,45000, +000000000000000000000000000000000bd84f04b3858b1138b1b429c7216d5d1b1e99c1e0fec26440d59b1ad79788c2d5583122c2ad769fcaa6d10d816a1f1e000000000000000000000000000000000387977ed1ce5da51dca230531bba53d17d3de5d593ec576cabfe6463d5164d7153025dbd4cb3525c4145c4f6b85fc76000000000000000000000000000000000a19c943a90fec6921367a2edc5bc38a5c59839cdb650766a2d2d068242463dd4460bd1d0e7a7fb0e3d2104704b8b3730000000000000000000000000000000011d99d44b200feebe00bd42809e3f67a23cce88a07165416cbfaf4db14420f99e54d62db4280d2c99ca0bc3dc41eddbea417c96f0cf4355a78513c77cdc676a7b09125802c8045756da867e0025a36f1,000000000000000000000000000000000d17f6d9460566d0543df2666d6ade685565e668521a87fabc58148343085415fee92c32907311c9d04713c34bf7690d00000000000000000000000000000000185da28f07b86885031ff5cda913a85b0e4d07673f456ecf2a9f0fd1b21d99e22442f9b705039252d57380b6a42912050000000000000000000000000000000014a4bde5973ef43691b61b3c0f6c2fdb4bcd6ea88e53e2787a7d93ad6e05ee2e69f2799712520f72b3c577ee278008ec000000000000000000000000000000000d92a565b3d8d0fded054a75198b31c521e3223650cdf762fbf7b851f7ac0fc66b8c86c20b905117585704c23b27e7db,45000, +0000000000000000000000000000000006a186aa584a466a860849c78e4922889c95a4ac6f39c99029fbb422c43d699a8baa51aa4ef51ff99557babeb3e9506800000000000000000000000000000000065fb15b5a0923bdb52dbefc7e9f1a898e32f17d610bac829235446fc5e1913fffc8176e0fbd33091505761f1d06d8920000000000000000000000000000000008bd358698fd073f660ed608462cfcef1da9a59b10905f1d98c4fe66958e56802814906430c10fc25a4d351d91f91cb0000000000000000000000000000000000a53638b1b6c6eeff468e099446300ca7c7bd899c6494682d14fdabfa9cead0bb37a0325d99e7d0ba6341cfa1d257ba846561328b7689b0a89014823537cf9eeaca6ea5c56a3e58d2abfc2ee455dfccb,0000000000000000000000000000000008b1ebd753364a5a0a6296ab48b348f91668525c0d5f7edc4f2d29844592f34a209f9e77f94ebb38ba76bdb3f96063ec000000000000000000000000000000001062e0ff0a67372207052e2520d8b2823764a5075c94011afd6c60288e187ec77e08db01c95dfa195f2409b58c9dc4e5000000000000000000000000000000000cc2b87b613d97a716586f371c457fa869c2b8d1fa1cf4b9e8c34bae23e0544752b997df4711d0712ec11d3a9d96ac2600000000000000000000000000000000140eae891c87c2026f0b1293df2bd8ae2dcb0ab3f8de74676f37c905334ac1f53fe4b75511691dcf108fca51abcd524c,45000, +000000000000000000000000000000001070b98c6348a67e996626ec2752f45e4c007e9c9668459a777c03fab633c10236a1c5be99f3fd950542d5648ef9e88400000000000000000000000000000000073a564401cb1a3a53334c0a55da261814d27b86ebf40b02a76b20973ba2db92e42c138ca7790261c2d70401c984bf470000000000000000000000000000000004212d8a9e4b01f5c6814a88561c2c6143eea61327b031a2e0e4bd056c12dd7098fdfe4d1511bb441ad42b55b584a7bc0000000000000000000000000000000005c5d23824b0fe05eb962194550681c57c1566b315efa8ebc90b3593d7d86ad18328baab8118c9f47eccc0757588591ccf6c3fcd4b9e6b72853934b306a078b1f2fb17879db4a0a93d484abbc2b746cf,000000000000000000000000000000000276a138edecfc9378be4e241d64cbb48bfa6fd4fb1788f8bda870d5ec8b2132fc9ec888ef84c43a50b7de0527def36800000000000000000000000000000000153e90d52c747859f88223555bc8bc4e8b6fc846fe7028de728a4dfa085c6e350f9f1d12b9dca4ca8e07377648544400000000000000000000000000000000000cef00e7217da6df0a6d85f40be69f154300c423e86e54e513b2491e65002e308445238082da69aa9e5e83b5f4fc17dd0000000000000000000000000000000008da1da2a0d1da9d2158b9408dd9b0eaf414d237b8219fa7661e40c1a88eac2f9735d0dd6ad67b85aab85952369e8287,45000, +000000000000000000000000000000000b1b3053774ad5515a20bd4c556d2b3ba95fe74fd0c955069c7f933dfd718ede90ac295f5a675f1c29dcd9701978353700000000000000000000000000000000145746ce88686021a0635bf6f0aa2f77c48bdb364cf4ffa804a57f95bd69d24eead05fbee24021c1ef57e1c7c7b894b00000000000000000000000000000000010ec4795a0762b86f3b83de1198698af67fd1b1be3ddef48f35cf82bc96d886fbb4c75064f51a9cfc5f61630c95d0ad1000000000000000000000000000000001465e31f58892466b8ae4b76a239d9f8d1ecb1834886344013cd1df0be13591798868d224d38213a6d75b02a1fde0ff2f6787b565e8d71be6fdb0c97c4659389c800a2047f668b366214adc716f402d5,000000000000000000000000000000001484993096c210c7bebbc4c0bda24b44a70e982b2528215c0e8578ea55f1181472758caf935aa0a3d6820cdad753e2f90000000000000000000000000000000011802324a6e03c3174bbe7261ecf3812c1a97e1be27269214f232274a3bf82775d47c5fdd70fe1c57155068b296d394200000000000000000000000000000000050f43c874c1cfb5fda81059cb7b4808492632fa20369dcfb611e503ded81a49dacff253e31d7e27ee84bab79e3c5d53000000000000000000000000000000000ef945b6f210fb09bf0ad5bbd4b5a6630f43304ddcb396807c967eb5146741f7432bfdcbd7e5f3d29917781efb62e6ff,45000, +000000000000000000000000000000000f39e731e6ddb7496448c912ae314e833d28208252c7f8e27bcf7eeaf1da6e2310538b4ef0d55401c6552e91fd70691600000000000000000000000000000000069d3612f924961f827497028737000513548ad8e104acee28f014e730d4752a583cb9a893e6169b71966a1c4a4ad2dc00000000000000000000000000000000090899907edcbd336bd4fdad0dd67c578ced4481a25b864b32aef920842689a2c23265277a6e1d4a1dc1b5047a9f79a000000000000000000000000000000000055ba64e2502baf68e46c759fca30247a080464eda2b32e7cfe539e545d6aac6dafb731c2c45749e50513979cecbeb5440ed91f6ceb2ccf87e4106a16227a3cd7b2821b4f3a6e629001f78ba1aa7346e,00000000000000000000000000000000028233bf12e8dbd8510f119be30ea1fc13b755c6ee3ca2a3637a3bf8f73776c9d1fe231b713396ffc579ef9320a05b150000000000000000000000000000000018e7c00b8047d64ca0c5df54486439c5fb3d1414c2f71cf8a3ed591b7c45bf18b37473daeeadcb625eda638885ddb9870000000000000000000000000000000018b89c9b6bf9ece36f1eac08fc35ffc9f7f964a0a9b19d495ae1361fb4bc98aef8770efb47d9961aff694b878d659818000000000000000000000000000000000eb2fda2c29c6761e35ca4c9772bb232ea0d297582af4f50ef76c0b74fefd414b535e356c069f54ef5224225e95be6e7,45000, +00000000000000000000000000000000042f1c8b9fe81cdcabea047d0998a1354ce09d62a14f1d0e9d188e2f35f2e1845c2b090c5e157595b33108c67e6c184c0000000000000000000000000000000018e69d3564d4ccc0306e1e6b227b0f961aa9afcad59d4ee1737f980dc876609c59a4c6a3506f987467beba0764b857000000000000000000000000000000000012ce5883156588cfe0f4838f819f985b09f1eab40a5ea8e30fc5d70d029a01a4537641248f4c21dd203909e0170737c80000000000000000000000000000000002888eb9778a4045feb5899dda258657b9f41345731ba630fbbf186b3be4b58ffc7f48abb65b693b573a73f85440a7a7ae8ddfcdb4748981acb9b2037c017174a140f2457fb0148fe807fd194a9f7be5,000000000000000000000000000000001239935827fb2a269ab064a3ae2bff2555f89bb3a71a47ae815ef755fc1363a89d20326855cfdd0e13f6c85f727bbe120000000000000000000000000000000012fbba047478b5f5b07a582200271a0c331d6f76864f9b6c6ef8ae6b0965eda481eddaf72c7a887b21719164c633d39600000000000000000000000000000000017eb4353b413437244983554a639a9253d105395ff9652504df7700d879cd9a32d5f0824b1eaa532bcf2fea34f8f08800000000000000000000000000000000054ea45475c01ea0557fd143b21c7bdcab6d287bf6bf4f88b6fb06e02ac6fc5ba96f323bb1fda3a1c4d8f42d01d267b2,45000, +00000000000000000000000000000000051982b46a819c74105cb36da871fb2415328a1531d155856f6551bd043eca62ddb61f24af429edda830fda31e22cd340000000000000000000000000000000006449e5bcdb5619aac542f6633ee3e06a4fd56a3e1ce4034efc608131ff6ead70ca63e70f494f519d5c577ae7119c8c200000000000000000000000000000000153f4f5dddd5801fbf7f88a735b9170d24d5b63861d50cde9644579dcff277cdb0d5fbfc3b3b819a1172de05afb9135b0000000000000000000000000000000010fdea84983fe6c08cdc4b4ccd462bae2ba791ab5209363b10b3ef342c9a5e92184e9d8be1419e3d88402bc05bad5fa21268803aeb58a2d57fc797358fb456d5cf96afecb1ee0d2b90782aa0d652b8c0,0000000000000000000000000000000015a145e379b7ecf4566a039b753f91e8ad75d9e9c9a20725ce34a900eb9a1bdf66cabee2100208d7792a963d1fb8c02f0000000000000000000000000000000007f0ca14fc4e34bbdf5008d632dd112c7368e037ce019b7c4ec412000ac02302c85ae64f9ab495361fa5b620e46420aa0000000000000000000000000000000017c00a08bba18426dda40e773d79733030b5b3b199a62436ed06b773fd1f10688e8af00e8a223cdf242bd1ebbedbf634000000000000000000000000000000000a17365cd9f7655793682b72e342227048da0cff88f6ace33ddab548ba126017e4b7f7439373a893e3b5803e662814b8,45000, +0000000000000000000000000000000009b011f793d9a939d916d058ffe91b58138820a646cc450389b3074ae3715d06ddec1075afecda71c65c7ca085210c740000000000000000000000000000000003d4d20f4b93c1e90a0a06bd534d8b4fd64e4c4aba77ae42cf4c5b2bd95f8b02ec4069ea246ff46404e6c9eac632fbac00000000000000000000000000000000051e88c3adfd4d6a02d3f03812362a6cfba3a6c69b9aeef75b51106cc7f1750293d61e31f0ea29b5d7aa56debb6d2aff00000000000000000000000000000000086d9c4ea6769cdf49ffbbf7351023b4aea640e8c90f9291222fd0b5984bca4d481bf7e10df921406a34804e6a09f99df9a8a4e5c65973b785c1e2637937de239bb0fde34b786dceea66f6bb12eb4169,000000000000000000000000000000000081b4dc78b74250a82da9d803876add659411cfb467860b2ac6f0f68929d6377deb71d6acc9ea8fc8c1286b8f92056e0000000000000000000000000000000002c5fde71346a255ee9dc896f654eb2e0c66f4cb4c51541d2bbccf2463ecf0085a22b9d2bdc5bef39d80c4477824f116000000000000000000000000000000000ebda0cd8bf6ac7e86a1bdbe44ed1e15f8ffa1fff92afd67fb564306882f35037b61cf0d93f278f15149c04a2e83041f000000000000000000000000000000000fc38aa811f5ec015f10a99bf175f1479d4983c9d2180a5e3da88b4e9b62ef50560ff0a6c2fb7bda4c46c54551f8390e,45000, +0000000000000000000000000000000010d48bf523f3909cf90aa58a9517ef5421f1212accd5e8a0f830aeb15a587e215ca9c340bb846b1d0474e43840b2af79000000000000000000000000000000000cc1a3976caf97b9d59f448f6d9f413eef8904f360c0cf912fe942b38d7fcc637a17038973a133608ae769d3e389b18a00000000000000000000000000000000069a6122c6f0ec68834b7617c755a7eb33a80a25acf95859da5ff03316447182f122d20d993b04e79b6fe859b7adf5a8000000000000000000000000000000000058c6f8c297524319bae6722e0a957d1ba0f75ee3a8aaf06148641c67925d15780e419a38ed7e07410e82769da74f2d070e7e2ae2751a1f71962726a31f77553c2da38f4fecda435b6e5459d5e833b4,0000000000000000000000000000000007b46fcfb2cd8efe32754306ff2f503d7434168c1c3cbd7c80470cc5a5c8bda10a80bfc0129da349724d2d6431c5ac90000000000000000000000000000000000e1078f4f4ca993d90accbfc036219507bd22d00930ffcfe1227780c00914fcff845698b2541510daf59cc83d8b947e7000000000000000000000000000000000b7c6d9951570e685d3a71b19a38f5485f974f85fe8cd4b4c196d33a18750b278b6d374483d81dc3e15c9b8b9b5dfdd6000000000000000000000000000000001003a239ea4a2f213f0f646bdb62cbe4f98cfaf7298d8b2e0eaa07bf3f939e779caab5ffa0033467c5b297166df657d7,45000, +00000000000000000000000000000000156ca5e80be8c8c03a5506ce9abd22a9d4958c372678c0caf6f1329898507dfcb1f06a9464cf080bc6881fa5b7df1ebe00000000000000000000000000000000088174d486b4086b931010da298a399e15b60a113e08f571e096d3a4e94b57b3a684711318796eeca9319119b201abb30000000000000000000000000000000000b96ff68505c088cc03a1c2dc363b05bc8544728a12b29569bed137780523123eb17e68f4632383c252d81bca0c5ca9000000000000000000000000000000000486fc6e5224c5fad56234c41856e60bee4a6c1046f673bf7d5c1bbb603b141fc91074da5f9d3d41b796a2ebcebd9e74d16aa883a20307f5436354bab32b4633e83178f33626af3edb14f82724b8e125,0000000000000000000000000000000000ea29b1e059560fec21c3692d4e632a45c88a807c953fa23dbedb271b049d7fc717333b498ed12573a896f872e795dc000000000000000000000000000000000de0d10c47df92010a6635e3403dd6e91a1bf35bfcae82c1008998e86aa2d18a6cfd3f2f1207fde3bb39b723ec4d3ca60000000000000000000000000000000005e2aef9cd37430b15e5e76b2c7870630d255f630c12e865caefe308a39833e00319406746dbb2af3ed32135e91eed49000000000000000000000000000000000c229fad41b0d27ad7b5db33188fa70b97f22e323e429ef65fcf98f5339e908c31df8859b863356e0fc90538c5c49cf2,45000, +00000000000000000000000000000000121fe97c62e068988ebff21d8129d52aa903afdbb62862c7fd99564d9ad72182ab1f3a1100223ae486cd76f6938e123f000000000000000000000000000000000968ddedb04f52140160061828b5f88dfd09aaf37df625ee6f66b9500d6608df31c7edf86296eccf8f9918b051a5e4df000000000000000000000000000000000b7491cb8f6252e3861d7160feb0afdd736d27886863ec0909a7cc711a9b71aace18b17a00a2999dd57ca1a74f148516000000000000000000000000000000000fdb280093ef45b12b694ca3390a865ee18e4c04b231e2c98cc28706d4cefaf4e654582ee03f34ecf1dfa9674489d553041390a2209b80f7c64d14965cc2f515d5fbdf37953f75c4a0203bf0d9fb674b,000000000000000000000000000000000444a00cfd258bd46f659b09eef17be9929008d3d1c65e46cdc762eeaa2f0b52abfd636e6094e21983fad8171194c71a00000000000000000000000000000000090833e68614be5bf298e04e44527480cb35128bbdecae15eb95d6931a718f66869ddb68352130b4dd8a921ab3f26d080000000000000000000000000000000000994015b1b55340c3839d48320d178b2ffaa0bbff038f7aa63d4dff41a217582fae9613bc537fdeac8d0670c0cf479a000000000000000000000000000000000fc486e2a1680c10ca28d4c3bb22dbccc9572036512645bf868e7693ae4591569c973f9ea26342a573e23a06c2fb4b70,45000, +0000000000000000000000000000000010d001a09cf5dc3276482185f26ef3f75d28cd6d2667eb08a7fe06c03b99f3b6c4d82390739b6867a314291cc642a8b2000000000000000000000000000000000587846a460b1f37c2e7f491f9a097b4e86e1943d9cd0999313f65627b3907f09b5d5ac1be376a313a959dd136f7e9b3000000000000000000000000000000000af439695556e86b102926d3b40e3e54cc84464e120de3b4e3c5541a6a5bca44151fb0594009663764c1824518b13f020000000000000000000000000000000003bfd9418c1e57269e222152d321b83ae090f216cb422956dd1fcc464f68526cb4a05cdaefc7bbe6e81d4ffe27d64db47cf23dee8d95d94046678f3bdb4b0ea3d4e3a1a2f07f582e2a98ad6eb7562cbf,000000000000000000000000000000001375bd5ee66c330796bd8381a26cefa3f40f8cc8de42d4d59a7adbcd3852e6d632422e6ad9a06a6e497b23b17b1df87500000000000000000000000000000000165d8e7be17ecae9bf51a773da705aea42536d0fa3a2206267da50451f5104ee241811dd0e6710a80c38df77b126c009000000000000000000000000000000001559572407aff34969f83c394d2b095a7ae9f53a8e6c923910f256bb87b6ec076fa6acb85465102fd24d34031f88f7510000000000000000000000000000000015ff9ba89b55ef75f63732dec1e64106d7a912a6657fcc970dd011a03b5364117cca46d6cbafbc0c5049db10fa83fe6d,45000, +000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e,,,invalid input parameters, invalid input length for G2 multiplication +0000000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e,,,invalid input parameters, invalid input length for G2 multiplication +00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220f0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e,,,invalid point: point is not on curve +00000000000000000000000000000000197bfd0342bbc8bee2beced2f173e1a87be576379b343e93232d6cef98d84b1d696e5612ff283ce2cfdccb2cfb65fa0c00000000000000000000000000000000184e811f55e6f9d84d77d2f79102fd7ea7422f4759df5bf7f6331d550245e3f1bcf6a30e3b29110d85e0ca16f9f6ae7a000000000000000000000000000000000f10e1eb3c1e53d2ad9cf2d398b2dc22c5842fab0a74b174f691a7e914975da3564d835cd7d2982815b8ac57f507348f000000000000000000000000000000000767d1c453890f1b9110fda82f5815c27281aba3f026ee868e4176a0654feea41a96575e0c4d58a14dbfbcc05b5010b10000000000000000000000000000000000000000000000000000000000000002,,,invalid point: subgroup check failed diff --git a/evm/src/test/resources/org/hyperledger/besu/evm/precompile/g2_multiexp.csv b/evm/src/test/resources/org/hyperledger/besu/evm/precompile/g2_multiexp.csv index 233bd69789b..dfeab857b7d 100644 --- a/evm/src/test/resources/org/hyperledger/besu/evm/precompile/g2_multiexp.csv +++ b/evm/src/test/resources/org/hyperledger/besu/evm/precompile/g2_multiexp.csv @@ -1,106 +1,107 @@ input,result,gas,notes -00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d93884d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf494c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e8964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b890000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e2787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c94400000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f300000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46eaaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d10000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12dac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5bbb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4000000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb81596fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f818767200000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c000000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e977955054196b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b660000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a93b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b38173173459836dd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b7010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9300000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd394c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a65905400000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ecf0000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bdb3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746,00000000000000000000000000000000083ad744b34f6393bc983222b004657494232c5d9fbc978d76e2377a28a34c4528da5d91cbc0977dc953397a6d21eca20000000000000000000000000000000015aec6526e151cf5b8403353517dfb9a162087a698b71f32b266d3c5c936a83975d5567c25b3a5994042ec1379c8e526000000000000000000000000000000000e3647185d1a20efad19f975729908840dc33909a583600f7915025f906aef9c022fd34e618170b11178aaa824ae36b300000000000000000000000000000000159576d1d53f6cd12c39d651697e11798321f17cd287118d7ebeabf68281bc03109ee103ee8ef2ef93c71dd1dcbaf1e0,293920, -00000000000000000000000000000000000eb3c91515d4a41209a73564741a8ccf901a624df9db22e195a5d02d24b7bc0a12756b15b8d006cb991a7e088eaef1000000000000000000000000000000000704ce8afc808b0161f6f61b22d990d713ae398779e6e74e9b5771daf006ce0bba3a8088edf75156f0e48b92ee8409b00000000000000000000000000000000018fe81e05aff0620f4bdbe4a715e015650497afab62921eba0ab86b649e5a2fd3d54041868928519f537e36448688a0d00000000000000000000000000000000162bd97161201ea3c26f8dd1204a9c6b61b762bdf573cb5d20b6b255f30208ca7d96aa47b46fb8c6bf0922075f1c1ca807f80a5e502f63375d672379584e11e41d58d2ed58f3e5c3f67d9ea1138493cf00000000000000000000000000000000135aee0e30fbcad798738c10d4aebcdf50c89ce516325f655fe763dce54ffedf94dd74168611e5ae879b5bf5598d62dc000000000000000000000000000000000c728e672cd8b3bf9341bca929c34118b566cd3a80452d7015bee9d5cdc001b1f5c678d4b2cc4f7cac353e7bf326ca1e0000000000000000000000000000000014809aa22e2051e463fba6d49fbb060d0c7f599a0fc5409d34e71f34817e7beb1251810ae6eee1848c60796fb8647dea00000000000000000000000000000000145a4de777d86025d50e12f9a6615ecb9bdd41489992d1b643dd9aa549acbc63b04b0bdfd14b6e45c70f165e9a8c91bebb169138f94093d5c1c6b253cc001ce8baf78858dae053173fa812d2d1c800da00000000000000000000000000000000009a58b7116dbd6f550f8ca98071813130ecaa9ea86d5275eebc36860690fa048c9ebeb46600b2b63e847bff3e38ed0d00000000000000000000000000000000113ffc0932c041e0e34b2540c485eb74f5029b339cb60bc88a8a749310f33f330dea137e5f340044fd689264af66696d0000000000000000000000000000000002642da3c2c7b6688aba0b19ab29ac72e35caafa044863c364ea8833fca850289de52c0963bc33d7bba40cb5f568718a000000000000000000000000000000000552d35ca054da2f148c119454f6760607b351f2441921a2be17da2cc10902d71571c5554f132e60df79679428fa07e3e40608bdaf3e7764358a64a920cbb33ab4d571c7b3092e1ae11d9697f82ed8330000000000000000000000000000000018fbbcba3d4b1e548ceaec4a48db62a2420ff29a67af332ee7ea3f902f84e6c375fd33abc33d945c5bca25603979f9a400000000000000000000000000000000072ff416994364bdc6535f36c82212afa822cd94fade69f11eb38dbdcd37c7e22af55fe05e6a826dad822073656eaac10000000000000000000000000000000017bba179b847278a4878b6faeaab3b1f4bd7540d22817cd9aff95557497f8b9d286657b6162c0f89f7820becc637dd550000000000000000000000000000000018e2bfed71aa9b11fefca2f0db8bd9b8c69540267de50bec4fc90a6e9741891465c9761d19282e1100b3707eeb598b31d411519f2a33b07f65e7d721950e0f0d5161c71a402810e46817627a17c56c0f0000000000000000000000000000000019efd37727dfaedf697fcda7a59847dbda8ca7cdc92f34e68691d682e20ae6545ac104d6660fdb8f64a051e69298eae8000000000000000000000000000000001225ace0fdce456dd888c9672503b68ef77b2d11caf1265a767a6ea14911e3ca03fc153f18dfe9d95e0cc68b7b8a3a8d0000000000000000000000000000000008a6b059c1c4da046cc0b1b5d7f33270aceffa607daf6d0d078c06f940604e1a0b4adf01a4091306e3c7eddcf3d95101000000000000000000000000000000000f79bae5260a2f114ffbb9273f3049d3ebb002500a57ee0a7d157d86957f43f87a2e026fb9892dacaadca5ee04fc8e176bb3f9e512311699f110a5e6ae57e0a7d2caaa8f94e41ca71e4af069a93d08cc0000000000000000000000000000000016d2b73eeceee17d3bff3aacac9df9ac1c4248d9ea7d6a503a757f7bb22fa6970bb6f5cb5ec154785f7252e1508b382e00000000000000000000000000000000081edc68bbd8db7b10be06ee23d090bd54f9ca07ef24dfed7df7bb05f8cc26e6889dbd40ea203fd5cca5cb588199f9e40000000000000000000000000000000010d3478508619ea9493b4330e2fb9150024cd32dc1378f824788a884a4a30fbf39c630f465557bf0c6d69b4cbecf89f9000000000000000000000000000000000f20c9b134db5d8b7756800c031bf5962fc560ba95d4bd9157b16179f1a37ae08696a2be455ad8d018aead6adcc69b712a0c988d97e86dccaeb8bd4e27f9e30fad5d5742202cdde17d800642db633c520000000000000000000000000000000003dce67181d23af9729e9fb0653d7f79c890fba27de42fada93123e112c4a468fa889921192db8047d86e4db77c60266000000000000000000000000000000000869a1e39d42d9bb0cc0568fdad16abbdac3194af893ebd8dd8f8c2c3c855abefa5fc215412168acadc88e658e83f5570000000000000000000000000000000001ef139a75194f3c4b1378c2b66dd304d179460bac0a289405cd8faa3ff66a7b6e54eb7b8742a68150b1e098630135c40000000000000000000000000000000003892b5a645af916be2c6c7fc0bb08fb5f39341d3c68598940554e1be11e1be75af920db0c8710ed13c78edbf683f17d0b299c14892e0519b0accfa17e1a758c8aae54794fb61549f1396395c967e1b1000000000000000000000000000000000264dd4b477f5db65edad28c7153ed919a863c5c5661e0125c5429b323e055fd69c33142dfc6ed9c87082e2be4675e1f00000000000000000000000000000000046ea088a2ec94d3a1f1f97949f1ebc49690c453d316cc46534fa253b34b30323b6071d147d64bb94e02fb4db07bb0c400000000000000000000000000000000013692a33bb1348486eec40a9e93a4ea3810c7b4d3188cd07e235a2c898aa87ee0d17682fd24f4d978f9fb028fd26e2900000000000000000000000000000000115f8b64c00cd5cd344a7b5edc0ef0bb85a3e8f0f9dfb28f8ffe12db3e0d222c2d45dcdba0fbdc161c5d558bc71aa0977064d43d6802ad4c3794705065f870263fef19b81604839c9dea8648388094e900000000000000000000000000000000014c83d58d90db4821a0411fab45f83fbc05f7d0d7a67ce75da3ae568978d15f4c1886c6fa6086675c0045efb30d818400000000000000000000000000000000001e68691123451f4c3df6dae62c6a63855ec3597aae33a8a10ee274e902e9aab1460cc9c79726312df0ee0ce90c8d3c00000000000000000000000000000000018a39eb3e3c6c7fb8ee304e55d15e209afe2fe278dda93552a7b9f51fbd778da1502eb6775cbc3f832f8320fa0686240000000000000000000000000000000017c15910fad1ca5749aa82a5a2fa98b0ebb37e92912547fb1741f18c34e0d5fc3a307b928636c25f0320d71cb9d31062686285a0e22f177fe3adbfc435e9c1786752dcf3c11b723539789b0cdeb0647b000000000000000000000000000000000fa96d9fe01c18732e8d6454df9bb1f482c4b9add837ce9c354c72d49c2d44ec694674aaf0e6d6a095cab7ebb57ccd9a0000000000000000000000000000000001f8ffe3fb7e9e311e0f6949c07c26a0febb181e37b2268bb5e125fc3a100323740d1ebaa5e635dba3770fdc2ce4ee860000000000000000000000000000000012ac42095fdb677720ab3f14bf0afc55c95b43d28d922a5f8cb0bd841306b978751d24546e3a6474976961d0768f29e9000000000000000000000000000000000baf9804d99039c9fe966a696c64bdacc9673b0906b4deab108d34fbbaa3b0905d50892278570564017b96828c7e1ac93176b6724cf984632daf95c869d56838ab2baef94be3a4bd15df2dd8e49a90a60000000000000000000000000000000014ce6d88a7c5c782562aa101550f1af487296adebd9dae8252698ba04fbd58b92e2216de6ffd474d5992f97d9f22800d000000000000000000000000000000000ce92a04f5c8a99ca0e93992448222519fc454bda5d1d8638a7bfde968386e4ba0dcd1da59cd81d4c4dca3e584be0275000000000000000000000000000000000cb570796f5c8f7b8aa02e76cb8e870d3365fe4dce5df07ec286a0a821f922b4003d5b69c0f1588206d9544013e268c400000000000000000000000000000000098056a033d9cdae86aac02de3a444471854b909680719154b44d4f55f30087294e39e57643c692d6da725b859239080d76db3dcb659eaf6c086be6b414a494dea4bd30aef8450ae639f473148c05b36000000000000000000000000000000001214aacb0a5e6b7a40369a83c07fa8cf1786ce7cbde2b5a501d9c1292532df7822d4fde10a31fc0cecce3a7cfe3311850000000000000000000000000000000004f9669d8fe4f884ae93b2505710e6e45b19b7aa5df8cdd811f09e547efc27d21024cba05e2dc9d057055f30ec72d9df000000000000000000000000000000000a852b821b31cd27eca19712a636aa05ef2cd82c36ac1c2ca240edc7d0172b42a72c42d3cba583a5b5129ac1c9486e270000000000000000000000000000000007bd8419e791a5cea04993509e91a980d3ae4987a5b322400b6e4a4f2b636891a1c7ba4de96b53426dd556532403d5a39915646de2449b3cb78d142b6018f3da7a16769722ec2c7185aedafe2699a8bc0000000000000000000000000000000005ef88bf38b2f998dec7302cde829076e6cf69df23aa0bf6bbb39fc0d3d8b5eafba74efb928b1de0eeb3d86ec82612300000000000000000000000000000000011f47e9583997b19c36616e4bf78d6ddd6d67937f493986250ff02aef6e6e7ff074559af2f20a5bf1d67158e4a199cdb000000000000000000000000000000000007777c8eb259a836e6459b7bdb642f878d869fdcb31b105d01f280938ef5377f2775874c099dcd394abe70f17d595b000000000000000000000000000000001607379d1cd34e2d0ed765a339b21433e9aa489609b92414c6b5a05d796085269c288d739717def9db3502e0550860165061073223f066e35242772385c67aaefb3f7ea7df244d73369db1ea0b208792000000000000000000000000000000000d6e3068c082b68312141aa68f1540ea1415e93e7f1762b6f06ff408a9995542da1c727a13355c19f8f418a44de1a95d000000000000000000000000000000000dcfcf2ab12b1a0e521ab402aaa4d32ff649a5a97892eb6ad98487c3c73c35601c313b8130ad12e9098d16eed3bcc2e00000000000000000000000000000000013777b1eefa4af03dc44e4e054eb7a3a980a9c55644900b80346be84b970e1754d1f4ab771adc9249e4accf88a23fb400000000000000000000000000000000002f53b231f1209c6f8b52f99a78bc2147c951ac89b341495f4a60a6572985ce2bc823625099ec214bc9ceedb2deea3fff396ee22209271ea0bda10fb5e2584e7536e8bb1d00a0dd7b852b0aa653cd86c00000000000000000000000000000000161c595d151a765c7dee03c9210414cdffab84b9078b4b98f9df09be5ec299b8f6322c692214f00ede97958f235c352b00000000000000000000000000000000106883e0937cb869e579b513bde8f61020fcf26be38f8b98eae3885cedec2e028970415fc653cf10e64727b7f6232e06000000000000000000000000000000000f351a82b733af31af453904874b7ca6252957a1ab51ec7f7b6fff85bbf3331f870a7e72a81594a9930859237e7a154d0000000000000000000000000000000012fcf20d1750901f2cfed64fd362f010ee64fafe9ddab406cc352b65829b929881a50514d53247d1cca7d6995d0bc9b2f0d3d4cf46265fc0f69e093181f8b02114e492485696c671b648450c4fcd97aa000000000000000000000000000000000047f92d6306bed1cb840f58fd57b5b71a5df7f86dbfa55a36636cb495e08715cd57f2f3e7cd99a1efc28b1d684de1cb0000000000000000000000000000000000f4eb02d687a1a6105b4dbd740e2c7924689d558e6cbfee768dd303cc8dd0fd887f5eec24b54feccf00f473ca3f54ad000000000000000000000000000000000edad68c4d536912816cf6ef039c3dd0535dc52189583270b3b038e2c67b213d943bf384ce69c4a9dc526d7ef309f25a0000000000000000000000000000000006ff4a6b5129ef026d1d5704bf7fc0b474de92b5cf39722f165e73f4e7612d6d3bb40743e4b7b42d0dad5d5d6a2d4881915b717562844d59623bc582f1a95fc678cf0d39af32560c6c06e3a74023c89c,000000000000000000000000000000000153da66acafe91b6f13cd739ed3342197310e4824e7aef2e3414654c2678b8d09b296c3f928f3cc489893420031ab800000000000000000000000000000000010f501a96b86343a7c8d8c1250577cc9be6ffec81b5175ed07bd14988c5bbf7f2f3e7111df7d941d0cd267ea191d6ac70000000000000000000000000000000015e0d88894f7f83aacb6710f6c03ae60db8844dd3beec160fdb1df746b1f38a5e23def0893a0b39bee47c97af6535fcb000000000000000000000000000000000bcc275115e87f2f88c4afe8bf4faed46e6ad0c0357884356a26120591ba283f06b464c4853217865b1d2301965f2bd4,293920, -0000000000000000000000000000000017b32e613cb38b41dcdf3c8bb9187d731546977fbffd79fa7f66e3d6aaf9e1af6eca2fcdc260c8f90818d7148ba2f4960000000000000000000000000000000007e4d26606a47c874c20e8480a9f5815e5b577bccd783b775d10309eeb3d2102c7a0abc3324679e44362f09e7a4ada67000000000000000000000000000000000cb6f12ac8b49cfa36b957591293c87b21af0a949c55a28a90ab0fce88fb5cb7645e20ab2edd284f0ad1377dd95ac10e0000000000000000000000000000000014c96b5dcbd3150eeaea5c2bc27750cf88b30a91933a3233a4d1d9b357a80cc20d135e43a344e718dff5c79045c31f86d5c1c9fa11c36b86430cbb1f3ec10ebbe3787d0f5641d6d7fb96c810eda202dd0000000000000000000000000000000001ca1141ba9542c56de8991b313c6ae42fcecb6751b0b81b8cb21ed70d5008f7ffe831766b89880a7fa6dfdb09a2cda3000000000000000000000000000000000e6766b17db165bba564ac63ab88d3f8f5eded07a40b48644e60d3223d30458e7dabe404cab8d6f9fe135712ef0b1a43000000000000000000000000000000000dda3e6c87382fa762510e5cac721fd2b654f002f5b9a3767a8c6d651ccc582e80e3f68d6913cda30f9f51ebcfc7c98600000000000000000000000000000000059a7dac5bb6b504f2bd603d486700fe22c14f25254537b2c9079c2b45d36c7ce56854c5699cc7649b533194f51a9045c00eb20fe7c292f3ad820a074d8b3d8d24506612752d8677c2d6ca24f556cc4500000000000000000000000000000000090f4b85961ce97cf7f99c342d3627105d790f611e19721a43d8a0febd67ae393d77a02b999108efb56f0397dac22703000000000000000000000000000000001112f23595d1613c47486eadc37f9b1ac3b3c3973b3fe964d3b67c3996fe2eacd9df5c287b0cea8e9475d146fabcf9e70000000000000000000000000000000018f46f7ba3c9af34c1025c2d460f0be966e68944928dbd55cc7fe00e5def598d80b0e3801e48a74963c974ab4727a52100000000000000000000000000000000096845338d5cd2ac44e097607d6a1a05c241eda1941991ae9edbba965d9029032c46da7218b5b2338e6c58898bc4a820f661d7b30fb11bef70e15b257d7073885468a380862202b2d705a84827644b5b000000000000000000000000000000000aafe45ea7cb8b450a51263eebc28c1ded662972bee512e24fddaf64f43b74b66032523b3b104a4e9f6b62394436c6710000000000000000000000000000000015cb27e1fedfba2d1679f78a388f90b22bbf3e7d090f0ba972fa8e72f6e31c446f628fff929953712ef6e425d16eba5c000000000000000000000000000000000df9931893cae713042bf722db6ce394b6f346587278a154c271d8511e690417eb6dc47efbcebb7c2fb9e77f1de9fde800000000000000000000000000000000106ffa395ef170c99bb5742428ae88fa4fd7a94476985c099e3b700b7403d083281fb71a19640c6bc2321e27bcb33fe2346ce87c847376c8967cc18297e6007dcfacb6424e1d273930f38bb0e88fc5ca0000000000000000000000000000000010b1f8b1c492a56936da905b8738affba6bd29ae5fffd40ba6b31325181d3b489a81b23dcb69f6e71bd29bfb388e5a8f00000000000000000000000000000000116a115303b4774da59844e457844232d088062d920db67b2a8450a194be7e5340ebd4d106454fd9a03c8f50dbb1e119000000000000000000000000000000000eb521edd61b38006cffc43ab72d395d669dec196846fa4d6d43521da6c2fc3bf0994ce7556a3cffec7751b3bc5703ff00000000000000000000000000000000073cea36eccaa1c78deefb6029903c2b6598301bdefa9759719c3b590fcc5a6a4d3d4d19f552b33f4a3126a6e6a8448639a142c443a666499a880aa1cb9f523411bbc8e5554de099ab485b6c2c2e57cc000000000000000000000000000000000e3925fa085db73c1e67b29ae90f8773f83be5ec684402e8e2360ffee8a8368911e584843e42b0d470de78591df6ea6300000000000000000000000000000000075c7efdeeb16609b4a47ea442af4d75238fb7534fd96cb236a7886809d6adc2b62c8ff72bdb041bc51c1a71b68219e300000000000000000000000000000000088b4eb0dd185e51b737d797334590e982b7b0a5f109fc7d0524b2465c2c0457964eba5a6d2d4d99fb628f21f15a776c000000000000000000000000000000000fc79f6b38f3356972669290eeadcd992a22bc1191606b663a1e148aa58db3938f0fc65e536bc5811c50d9c7f03d3e372c01b7795c2d16b5bbbb1e107be36cc91b25130888956b0cdd344de9b4659447000000000000000000000000000000000b87c47605fc060a8e3677e84ce9d14b9309360a13c80d040c625fbf0108f829300cc1fca409a0f9c96311cd4a9a21e60000000000000000000000000000000014c4088f1e7935cf6a1d2475b84497ce6a250ee2c0c991fe51a2f2836388a354824b02d9cf215328dfce3f546713e21100000000000000000000000000000000120e59be3ecf35674eac6cdc559599b273f13f28a529770fa156f8e519734c451eefb35023639f32049cd19ea0d945a3000000000000000000000000000000000f97755b62a8cb8f861ea02c77819f0b58181aecf612d92180ba9b475f0b4888b922c57f6a1c619dd5514620a1cfd9e2c712943d8795a6104f024b9701c70b09cdee9494755bbab0576e2c7f7c9d48280000000000000000000000000000000005860cfb6be6720118623d2d8ba05e686df22744b948421dd3cc1b1691e00d9b5d00d00195b4acf7a7b043f764f3f1c70000000000000000000000000000000012632a3313dd611e8d969bddd556c2d79ff387603462ac78ded3a842981697bdac34ee6f1f4744ed2ff16100874ac24000000000000000000000000000000000112b94c317586e343acadeca611c485c3ea172bc10dd39158c1e678007130062a921b53826d7be6286963ff822f1066c00000000000000000000000000000000040de8c0dadd2a6c2a7ea0fa43e1a5f2f5a6be3fcb0de6875d8cef1ee2daad87125d12f6869c4dd3d931b296f1df2fb3d4d77f6246c57d398c57848db8d3f986c475a41a23d424cd3cc2b362c1b99f2a0000000000000000000000000000000006fcd2c4fe848e9462ba1112baad39031c210952adbdd06293a622ffe2d1c6e4fcc8773ec8913717018b97bcb9a554fd00000000000000000000000000000000130a97442f3273b7b35464545e7351faf71ead9b8996c63889a45945ed82bba29bff5014776c6185219a5234d8475c92000000000000000000000000000000000491d571bac5487b866022a0714be11b38bfb296233845cc434a50be1d35f516b8c6b046fe3d0a8f4f95ac20eddea01b0000000000000000000000000000000017e34b04e6fdf152c848f2432b7bd84b3dba3915f06eb77efb8035750aca9d89e92e1d1bc4871105c440d639e8d8b05541776ed9d1029918af4c5113a6110139b8bd7f938caa204373a28ddaa51430eb000000000000000000000000000000000f1b8df4e8fdfe32eaf227f5af9f2befc85073468f10b81d32d0e126fe2b0cc8e8adb8afcac73213b6ed95e8e843b97c00000000000000000000000000000000004e3fb435ae0fb2d8bd091f250aefe5922b353a64e16abd75627737f3bc56639f8b40652cae69c73ff1969925b0afdf000000000000000000000000000000001003aed7cfb00efce49d6b1a8eba27df87479a4d37bd7fda6121549483b669a1a761204b0dd28262bf27e5c8e180540f00000000000000000000000000000000114fbca7caf782b3296d0b26b4c362bf50acaecb8bc5726b2c99f904ec3d092d5d40991d0d30c8e79fddaa45f04a75d3fa64411438542922a7bac10806efaa633d31d37c0b223314a8b6221155b9c4250000000000000000000000000000000017faf481fd4cb0c373d21d7caad40e93d9a86e62d26136892fbcc6f6e48205543aff00c45e82fdd1d3e0e733de91e7000000000000000000000000000000000012e14fcb9ad4d9d15347cf004745ed4bd92097eeeb41c4cbcb728a234616363589d8f5ad4cbb61d31a8aa27627723c7e000000000000000000000000000000001513dad1ff27e053902e779e35d04cab648939317830144ea775c435a4b55e13fa2fef03a1256abf5c187487c25a774f00000000000000000000000000000000139da29de8587c7d0ca9237c37a116387385e9cea453b9e2003a37ede7aa0a3f4c1df55255897f5975b662be33622dbce7002f41c6acab677a0ad023bad2a61b11c1b7221d944018b5ce60bb61e87e96000000000000000000000000000000000c118b147ee3489f30c6ecc0256a314ab674110588e8b69ca6d265fc270c3e5b767817f861140cca5d7c6be4012d1ffe0000000000000000000000000000000014800790654726959fd876b035bade0da744fb36ee5b304f228663a531345120267c55ac19fd66022752010e5bea7cb30000000000000000000000000000000000193ab7ac2f151750356b6e178557460c9c2672b1736d19a20e3fa28082479ca60021aa68edf2524f1aa826ee70b65a0000000000000000000000000000000015cee9ac55ab45abbc57d0ea6ec9ee49f6c59f6b94f99589dbc08ee877d3a261ad77f5473fedd72ed7206647eeafb6eac26e55f09b787c0542878e4d720027d9ea465f829a4e0164cf618c5d9cde49bc000000000000000000000000000000000ef203fab794a0ef29eb2ebf00076134e5932e27c99d6d445695b9df2afe7563602e318caf5d44724a21790ca0ab0d180000000000000000000000000000000013b9b1b1d3e98b61b0f1a0ef3a1a4ceed57b6c01849a4ad66a86332b3d27022cfccadd3567e6709d2de5b23b23dba43f000000000000000000000000000000000c1fbace49684f4be32ef6178ac3a95ea3f50b11494340fb73dc5391d50bcacafb3bf0f2631fea9c4ec47327d644489500000000000000000000000000000000040f82812855aa3e3aaba826d5810c1049cf44e86e44e23cc6da437971b529d2f2676c73e1fb9da52640c981fbd710bebba67cc47e38a129ab1140fbcf0386ddba2feefc919aacdce6059a27a1e2efca00000000000000000000000000000000060d7a718dd02b147c265f71eb136d1f31781b12a41866b4f86d7374b93dd10058c192cc0fba928373b1526e1a5d7d7f000000000000000000000000000000000cf29275373c0573ef22bf87919faf5444847203c7dc6d2e18986152cc294be04a5b1a4b0536797158113a15276c4fc6000000000000000000000000000000001016d5b9d4d200d7b4b7cc3836b85d6697fe14db350badba9978c7b56983dd1a7e572640ee0372b0a4e2079ff4c1abf2000000000000000000000000000000000f2768d104d895473ddf8c6b3cd0e7c22458d0037eca6365c766879a07c95037ee0de00d32c974d767080935abbe0be1705fb566367d9fc142c4194b0525c16672b843aac1160f9056ebb115e80d377a0000000000000000000000000000000017b9ca4349fecaa43ce911c0b256680edb8a0906ef5460fc4d2004579336df1e19560fe960a7a7cd74bb6e8272e08960000000000000000000000000000000000d5b96dae738db59cc67a51c61bec6deaeefaaa51e3259243fa4b142ef59676231229ae386ce699fbe18c4c00bf9d49400000000000000000000000000000000111b79f4b68dad16550a13334d09dc38336a75a5da23a17b5064e2d591aa3dab4c2e982a9f730a7633070504663a24610000000000000000000000000000000018f6d3616a7eaf17c805a88c9710039644d01b61aefebf76717ddcda6f4bb34aa15702de1e92bdb27b27f3409638da90f7bfd990cc4dac62a0d730f56b4eb1c1ad77ca9cd58b089c23c2f6efa00b7fa4000000000000000000000000000000000aeb5c087644595d0912879f61959d2731ff55260c682ed2bc5fc55c13964ef7c1f70aeb55876d2264d558c31371ca69000000000000000000000000000000000e173848f4570525b03a2b2c86f4dcdb8b28dd6d18c1354cad31028eb1b8b44432c2346edaace093e3954c7fa6d338a4000000000000000000000000000000001949b0902506d111ef6318edcd7a58ca4d69f5804a028aee73c3786cb2db168c6a73b77194f7a021ae6ae43ac78ade340000000000000000000000000000000017c5e28ba6103d97e2f3d3611c0c78f06406e0da8a49ae29c7d460b52f75136920784cd500aa3593858b877697eb8424807c5a41ae2baa1e10ebee15363d1d4569f731d77a418998108f5dfae0e90556,0000000000000000000000000000000013b49054c3957d1e77ba2dc3ef75775bab9f0e9f76b33ff22e244e897b8ab80ee0749c81eceea259e99b5d2a72251e5f0000000000000000000000000000000012e017e4354ef86f73ec51921cbfdd01e3113cff044a049bdd34e36401712420790cf718bd28afa280ad12104c1851ed00000000000000000000000000000000097f28bee5d903e3c6de14e834d5beea5c847c3106742978e586ba7e913f8b631a69c473aa10e19df9795ebfa3ea6a98000000000000000000000000000000001953493daf65b974b549bb98e735da44b543d6fcfd97176fdc7f6f03617d90e6bb952a607fa8e5791df5dc1c9bba2286,293920, -000000000000000000000000000000000d4f09acd5f362e0a516d4c13c5e2f504d9bd49fdfb6d8b7a7ab35a02c391c8112b03270d5d9eefe9b659dd27601d18f000000000000000000000000000000000fd489cb75945f3b5ebb1c0e326d59602934c8f78fe9294a8877e7aeb95de5addde0cb7ab53674df8b2cfbb036b30b9900000000000000000000000000000000055dbc4eca768714e098bbe9c71cf54b40f51c26e95808ee79225a87fb6fa1415178db47f02d856fea56a752d185f86b000000000000000000000000000000001239b7640f416eb6e921fe47f7501d504fadc190d9cf4e89ae2b717276739a2f4ee9f637c35e23c480df029fd8d247c7a7e300bcb3c740fd1f693d4c8915c4c46dcb627f6de6e4847f123623cd23bac7000000000000000000000000000000000f20a07526a082e88630a0256d134a8a5e8ada07b1cead39ee838dcbb30904e9016107fcbdf1f8ba182308dbe0b043d20000000000000000000000000000000014fb7732f67abf60c03ac902577532d0acadb5f3db0d6397a42ba693526ad74f2c61a0195bdc9704aaaf12e65aa6d88b000000000000000000000000000000000018cec4fb81c85d304588d11f8b9c51f5a053df11463e5812a1b2e6c7144522ba36bb91adf219892d0007cee470032e000000000000000000000000000000000b8e52d958a12a9037e8be9bc0d5045cade2d6ea05c6e68462b3a30b5d4ea34e5fbad173761e4e216b2e6958c8983b28b473df5e282565a0783d23e65e283a103ebbddb5c884183cceb62fc32d0e9602000000000000000000000000000000001468cb35a60898ed129f30c261b8431df6a154c250ec16d85a22f8717593b2c21853d123da86d977a7938c5ed74ef23500000000000000000000000000000000011f4e28e31b5f9e6877192a5e632d8c1ed7ca0c42e6e9902ca68f1c2de0f648c6064436012c5c7b14bb8d1078e02f2c000000000000000000000000000000000b25114b2697ca7eb1e6effdd1054893a188fd382d387ec098f846c1137a9b9baad01653b963a0b0bf3cb50c3ce3563d000000000000000000000000000000000c1d241cb03e642c1752b1e1886472477c19a2801ec032dc220c3243952f882094119bb92b621b654b766bc900d2d4f7a048ef7cf5d1f6f625ee3aba091147c389ebebc5b8f3d285e16ef4e8afe5c013000000000000000000000000000000000c80d4474390fa791ea5f2f16b41506d8ae13ee0993c8d31a07712687298ee7978a724999500c42400d2f788a5a36067000000000000000000000000000000000592705cc5a8875750a4e6ceb42aa3bef5593eda9e8212702a2e08ea70277a2a66526bc5237be33c8449301544da35e60000000000000000000000000000000000facabfbd15284c6433f17b0e6035d4fdd84d3ad2dd30a27d52809652ff6e7a684d7724697919100567ad0c3e1a26320000000000000000000000000000000006a0fc4e2af69ce15a356656f5d182a2cf213d76a6047a05a1a3375909d245f5316b91333d2141c0817438f0d87bb52da9b63c6bf36997118d58600c1e429c105a379b9e8b0de934ab9f433a4fa63dc80000000000000000000000000000000003f629618e1fc3018bb836301ccdc59022f0a25cc9c5de6e4c31fa08feea525c83256235e4ec8364e77e5df478f5f62c000000000000000000000000000000001120d6af221ba6f4351bbee4c2c664a769adb17872646df2c408f70c99ea991ffced4eab50fa98be1bb9426915f125930000000000000000000000000000000015cd16b028ce3d58b10aeb84b783475d894ab3f0cfdf7104ebb4f3417a038107128f07518dce548271061cb8c97e88af0000000000000000000000000000000018379875b68bc26107f9a068e5034f29dc2ae7e8830f8e9ecddc53fe7991206646cda33d37b31a47a977b46be58d7618f228da17f49667c113d2bc2a2c8a338f80be68496f5145b4be21a5786ca6d46b00000000000000000000000000000000036570783711b381830e35878fbeb187b84884a9a0e88c38e84124515b470e6ac18157e1499026b27f4f731a961eaf330000000000000000000000000000000008382838c18d56c046a8db495babf8d14c915622d7917ebe10cf7da7ecb65f174cddb9e70d0262ada961b396c5511b410000000000000000000000000000000015f63ce982aa581dad5c71fc79251b7f6336c4e78a4a0f4cb6f87167cabd31cbec987d7af4f11dc6d693a0b0774864130000000000000000000000000000000015c001372fe0530a3f50fb8b30e75ff4b264d673e0448211d082c7a9018f583b4d01790019874596c59c68768cfa3e699431e18a462fba704216b516e819fb3392e315b0c92a7411a329cdafeb51124400000000000000000000000000000000074d78cdd35ea17a3013e2301fe9f80f2d20d270a25fdead37eed7697a52d152612543781763e6035fa5452ab12cce25000000000000000000000000000000000e572236e1c203a1c0f99e6ec978458c1a143a6a650eee27cfbe406bb2858fe5f30222f468d119703c2f442bc644ff3000000000000000000000000000000000125384343fe132e16a9fc15efe1b3a9e47289e0afc4b44d492e33a6216edbc96d66c1ca66944a8296e7695f27f414c5b00000000000000000000000000000000084c2cbf0d7c932c3098ded7c70d4411eed882feb0f79e0f7f1c31f5fccb6d53fb57de179c3ba5754bc5e532c3784df12051041bd2f12f6e6e29924139770fe209b7bbdbcd6c0bcabbf5021a7dff2d830000000000000000000000000000000004d46066439c3ac559cce863c58316883651023990180470d2efd06e443a7caf3a514b54f15ce6e850d32779215bcf4a0000000000000000000000000000000019ce904b6c9c3de59f7d5017f60f1978d60c564f94a0f1964c24c876d1139a7ffbeb6d0d4884bbfaf5f2f189af6904a50000000000000000000000000000000015f1989719e69be95f25dda9358fb98aae2819e0deb7e2d291e2c01e85ba26a9da421896c6b6e2ed20f609b533154694000000000000000000000000000000000b287cfcf1dd7c6d735c1358dff15393ddd6c82e7a33c5d8005c4234cdf823c76a4725fd74cad74b3ec51df67f09af0fb96df57a600dc3b5aabff5b1034886d24f6fcf035bcacaaec738deb2cfb8f85200000000000000000000000000000000006b37e2226957d639fcb0bcd6c20b3c7b8372e7347a14b970e01c67c1859fa97c754ce588d0f835ecc053549d963ab4000000000000000000000000000000000c6a5fae8be3a32e3f70a4202a1ab6d97183964b9f7b9a084c49922cd9e0e952b0bb66c5580f0e0c417e079493bcdb4e0000000000000000000000000000000017b6132f11adc0d5d693ae7f3a0f89f5779708083eba23e03b0c9265e4e60624e1fb6940e8ee49d31618fa6389b1b50b0000000000000000000000000000000000a45c5f6df71359648aecb6434bad1619c39f10e279a02b3cc9725d0256bcd126843fc9ed29cbe02a32cbbe79774a3378176412b07eb7f423f23ffeaa0ee642590e0b7016bc063f3fffa93e1e35484c000000000000000000000000000000000ffed009c78ba9af8cd33af7b7697ae4dff863bb92365055baedd2299b7f5b5e8abb84ed434f7223c3e309ca53c08aca0000000000000000000000000000000003b2370c837dd6291818efe7c9af62dd51295c418739ecc509d42c92e2c97d12a9fa582946e176e8153fc9a273140b2f0000000000000000000000000000000001e63438e8b4a0462cfdff64a281ab4a7f48d51b51325817139f8ee683484f8695f1defc0c3efcca81d5fbff06cf9c54000000000000000000000000000000000192fc391cdc1ed6ddbd317f2f366f2ce25ba27b8c0f09c733e7bc0c0697544399a3a4f1186d139a8f6399ffa88e89a69c4b5627d84e153f3a4ecc14ddd6baaf1d62253a0f88d3af51be18d991976da000000000000000000000000000000000002e105e0eaa418d58019a849b89accf665a94ffb0bdf308a11b99b521de7af8ddb150c0e3b2e9c54cf5456b6105bc81000000000000000000000000000000000691a3b3986fbe1c0ea22329364454f37f645d6abe9310e883b9191ce512347e074e18e28b88c2adcc76190a549b80b40000000000000000000000000000000003f3a37a763c8d0d99a3fe36923843a22cb0fa18ced48493b2510fc99afe5b7699bbaa6c2ecdad8aaf72969354f121a1000000000000000000000000000000000f4bbae00205f54eb10c83d928d908fbae342b76050e33c51b6e282e02b3c1f132a4728dee4ea95455c25fdfc112f2542ed270764791aff081f1dc8051d22b8e18803a7e310393f21bb4a495a445cd450000000000000000000000000000000009a3e98fe4a98582ce9f274965f376cb45e8583775dbadf626cb1327c1f8a25b293b97e7f8f31ff72ba7e8e769ff25ef0000000000000000000000000000000018e4785ccb76c4897087c8a4242ddc744c6a0a53a4a844254153c23d6f16d4ddb945252d13f93101613f4eb0b1e2b8320000000000000000000000000000000011b81d344eac04d3471b1edde5e51f31f97bea3396580839fa094db58cf6bee371bbdc045fb60c3ee5c6cd5d3f6d3c4700000000000000000000000000000000073476bc5b1d52ff4ca89c3afc099417f473543fab6e59cf9de8a19705dc4bf2a210b1e6de4dfbde035c312be0c70c56fbfb7606b64eef0460b8f33a0be54451fb655ce0b81db89eb7862f392450354f000000000000000000000000000000000c414b95b298b9c673001173ba7e5ee3e03926f28068481cfa0b469ab556f8fceba9fd0a815180ae0b82c265fd4c6b7e00000000000000000000000000000000054a242c1cc1a9c710bc23305d09c2d613ee8eb3840b37943bfe83f9c1db456ab4436ad319fcdd8684db129d76c95320000000000000000000000000000000001683711c0c7f02e67374f190eed1ce6559479d6d199f43fb5b0ce7df7774a5cb21c86b3b3498855d9b69c5763acd8c4300000000000000000000000000000000062f87085dfec847af518bd71c078f994b090c3b27c6eaad79772ab58afa43993db52fb08649a32629d61c3db12c87318a29fcc442d0c2446697e94dc47181dca7a314f9073c06aba6dc55aa79978d7d00000000000000000000000000000000083eea9b5b2d5ac5f7ef51ca889a4317322d098a408a741827fb3419eb12a51c07c788c2798cb37635e224e99bbc894c000000000000000000000000000000001312ec00f4b3a4305700b44b3f215779a9a8bfcf5b5d3a7f237a33c5484099ec9bc5c8537fae768e2c0ec62168f383d6000000000000000000000000000000000cf1d5d05d11e1d07074dd34211d0f00eae1df4dc550c55bd2fdafaffa1ad36abd5da30c5d3a5aa2845b1d95a5cb571e0000000000000000000000000000000015223baa9f2ea4b04fdb05b05bf3a94dcabc5e64189aeee39c380de9a34fe6b4253f5795f70bbe51b80e1aec1eab7196d5b468797b4af1978983faebe59a28f34956dacf5b7f65d25548bcedb518f45a0000000000000000000000000000000011a960cf1978aa2ce1731b857fd91d2f59d4b8d7c6871ef6f4f85aeff549a2f397949d11a4793926fe7be37f3a83d11c0000000000000000000000000000000001954f056834d6e3b16043ef1acd0a47a353300257446e9a1db7e58bd0d7c4bc9ceb3db51ae01cfed9de99621e96934c0000000000000000000000000000000002e2fe460e71b65595ed93a0010e5ccd1a2c16fc4e0d345e7226c947f29720d2f3f54282f79cec086d3fb1999b9629b300000000000000000000000000000000060dd8a7ccb613f1521168a8a322aef9f84d9708a893f704f4fc9a19e2493f25620a47e0fff1bc1e212e65e92873b4f2dbc6afcdd409e5d50d7b655580f1144de77f3efe5d6268032eccab7deaaad997000000000000000000000000000000001472caba61c2f1fe4b1d0912b114c25de103ef4351668f22f3a158d7a347539a7b6656044bd490f036ca3e29dbdded370000000000000000000000000000000015f8cdf7786410b409f218164063c99e77d8f72f03882a6c9430ec725ae574547d3ea3cf30c3ad2c9c3febe6c30b1272000000000000000000000000000000000ccbbed85c2809433fbcf22d6490457dab800b21cb4de414c7dd1804a0bdeb7142f8ffbb2de921c2c9eabee6a6351026000000000000000000000000000000000a404f42c48e3ca408d3f92079b99805004da928f128206d8904ecd7fcb14121c7d9a9e7fb69accaff921315ef3d5372807347519f114e78f99617f6b147ca833bff7be962c9b1e1f32b5babe6067d7a,0000000000000000000000000000000000fada9f43b29abe15693d047adc277814cb94694cab3be56b92312ab7666649b8e9d92aad81f8e487be0f74b9ce8c250000000000000000000000000000000007f6891775811a325cd7f548011ad4c705ca0327ea0484d938ce061c913a7ee6978293c3258c4b865d5c2325816c39990000000000000000000000000000000016761f859beb90ea03aa35e954d112da02daa8e76de80297afde9c29cbfe8ef4d42dad535917685a99b2a91b1f952ae50000000000000000000000000000000012a4f24ab88341dfb8a60c19993b8abea96dbd7033d3686c40903728b4fd4da7d07961f2584b51e9e6c05976d555757e,293920, -000000000000000000000000000000000b52f05365c4df20a7290aee71a7e030615d1a2a971167884d835c24e756a0faf6ed0552341c561446c7fd3d5e887d830000000000000000000000000000000018718ef172c045cbf0bb132059754b62414097eef640a781db6ad521af5a24d78c622d9402033fa939f70aad0510a1ac0000000000000000000000000000000017e969e44b4910304b350b5d442bb6a0b71e1f226cb4603cc8b4dd48614622f3f4e1ddecb1894046649d40f261d94e030000000000000000000000000000000004dacaeb9e05b9d60ce56c17312a092cb988bff426b8a718cdff860186935507a06eddbc4a1a29e4ef88db83fc4b6e77830630695c8dabe9aded1b5365bf93770aab7e9ef4140a2bbde2f0a7b109724d0000000000000000000000000000000019829d5799eed5a081042e4646d46fb6bead6d3b9893a4240867b25ed6af6a3e154514f244466d80e3b9311e060bbd7100000000000000000000000000000000156157a654db2813cb9c1b4da0a3ee192fad076bb2767020fc5fc00e967c1a35a367ffa375703e1181b3705ace9dd28000000000000000000000000000000000093385a6a9dd0ab996df54b23f47f4a49b3f379e11bc8331016ecee6161fcddd22f6d49fbb21f098873f1e17424dedca000000000000000000000000000000000d5b5b0f2ce81e755b4030b33fe3a8bdee38c2c60ed3b4a88bffb9207cb762c0a5c699ff424c000ab080d763abc5438d184ef5eceadfd77b3a4092696ec34d0551c88e434567638623740b7d5f9e36160000000000000000000000000000000003af8c25bdbd0dc1cc344d55366f15555709a74e1f0d8d7050cb6b487759db6200401b7868fca3c2ad26e6362a30e6250000000000000000000000000000000013f8b6ffe30f9a133fafe64461d305cc6b2cf5aededf68ba396d4e00df651531c750a3d94dd77bc5c6713b939b18fa19000000000000000000000000000000000dde97855d7728f409d873b83b6879b45ace5b73f317687fbf478e594a959ce21d4d751db646ceb20432e8311e67404f000000000000000000000000000000000fea997323cf29710cf0e3d44ce682e039d6cbda155e43c94dc8cefc5e94000de4b9525123b9615b5f1019a46ef37ad3a80d9efab033e920061cee8f8d7ea6023cc05f08340642613628b39e7b7fd0af000000000000000000000000000000000cdf60e3bb018407eab162822468255bcffd54cad9127054bd1c30705a4ebf1afc7f539cca6ba4cd070b44410ec751150000000000000000000000000000000009a2e3e5993b6a7007dedbbd21737a8c0aef3ecd4607953c4a24bb3fed97ccae01ae1cec024443f300b570a66e9ac3bf0000000000000000000000000000000008a21fed19e9ec2a741ade7767b0c9f39b79c3fbe34aadc9eb3043583768d893bf927d26231759290c7dd9c4f158d5a10000000000000000000000000000000018eef4ff88d63149d2632c9db586a4af0606644b16c82fbb0a3b869f1ff924c59acc8efbfde7bc604497ff68939cdd0845111c860f6f5725f99b225c53b9fe1a70150e7ce922bfe214900aaa2790d145000000000000000000000000000000000f5d47911596c46c0c08cac5f5e7f6d0609874da4ac1bd4e0e59c393273a5fe31a756c7cfff2a01d19e79d209d7c6d3e000000000000000000000000000000001010f864eb6624132d4436d18db7f5b34727060dc426c109886be88031e3c155490cb3fb09e1fbccb7912875477c6d840000000000000000000000000000000005cfbf1c2ae1b80a8c7cfb2cefedd907b0552794f4fda101ca1a723b18de8cbce30eb54287e1847cee3f416cd8b45f2c00000000000000000000000000000000084fa63781f7eba9c7e911ae5866d485bc7e90603541c55d1ffad8b3cf7547fd57fb24b14002560e58410b828513e109c07041840216d60ff445cf53b273a46016c8ecefefb53550f8bafc79966f863a00000000000000000000000000000000124870cfa469136c638e0cbf15802f2699aacb66d7e4c2965c6759dbca4b7e47941ad9ec37a84db1afeeeaa65a7418e4000000000000000000000000000000000d4503049a6a53536bdf41dd832a6ecf3f10554887da7e389cf940394e1d88db94369b7947436546eb6c6e82c48dfb9900000000000000000000000000000000053f9a6e1f05b67cf553073358009a172e2ab8b43572a974da1f3de85a29103b13d7e67b2a359297172d27dba5c61439000000000000000000000000000000000abc29f50ddc1c113c73700b9b9796890cbf48818ba981fdab2db27ef1c58f4c2e4595b99eae397d40990ce2f6c9317c29b031b82dc8c9f4ea9524793b54207d4e13a548d73297f2aa6241aff57abfd00000000000000000000000000000000007d2aae9794b7a7de97f7146c0ee8415e09e56fd42535bce6773cadd6f7ac09c4eafe2e926cb7014377e54c703eaa9dd00000000000000000000000000000000172a4a33ccf99eb0473b2c44d30bd53159afae0c7706ad128bccf6258974d5e5761f9be43e618cdbd96027aede7fd5860000000000000000000000000000000012601bce2171c6e4c2968a3efdf1491285f9e4ab37cf973ab5c8e224ad5b40e1b6459ac89090c73deb8fc79fec7fb8e200000000000000000000000000000000112a6443116e6f98ab348e57daa3971b5fa506e40515e1611fbed3e7dd64c5c1e991e0d2539a70eb93e3da0f573d6b2263d26ae92119c7b06d83d7e2922e06559b1740eae315c6623d3e543c9bf54258000000000000000000000000000000000030372914b83644fa4db1958831e9335c72ab7a811fb337696221a3290e4c54bc10c2225f8fdc3a9f62632ba2f1594500000000000000000000000000000000114205926609470b6022d24046a1997c048e6d2cf6043397892c967692161c0ceedf409bf5e1199a64eabb1ff8de23640000000000000000000000000000000017cdecbe73779855b7b94920d4bc8ad057ce51c5481a5579650df8a5bbc421030d2ac44568217c4dbb13d7c639760236000000000000000000000000000000000f194fa814bfa7396697bd812d9449d06fc61b580d7a86429fdd1ad376e21ceca139356d7d13964c3c684563675711c67a02c61a7a75342ee7f0745886c0ea2a73c21500aef8078d21d20b7216c2990e0000000000000000000000000000000015d4ae1521acf897344c3a76261754ff99742585af4a0ee86dc473a88fd408091404df1da9d8bb291db68bc9c07d6b2b0000000000000000000000000000000008ce160213875c661163990f3f7ac219ea295db5e828354864517ea8689ec15d35c6df78ff14cb276e0c97ffd7fbc09a00000000000000000000000000000000038a3ee211e777d6d6b7ca6c7a0d2130f1a071c030eebec412c3a0f14c3584e7c5cf15de254a8f141a8210a90249ee5a0000000000000000000000000000000019f7ec6b2fcd8b3190ab37a6e843340d3f3fc092f5772a042edbd5bdc967b96e8a1dc9e435b8463496aa1301f87d0e5a81b0c87102055dc2901826875d5e85a794befd93fccca2b9c0a1f70ef5610d83000000000000000000000000000000000fa7f8fbfa1d4ef5f001a451c55ed261dee344025e599884b29d086e15665867932120d33bee579d5eb1b7e6c7299f310000000000000000000000000000000001f06356f793350b17b47a623059a068800ca1eab6089c7c146182990063e8e23bbf40d95a42bf6e976224b680b75bfd0000000000000000000000000000000008807f6606d2302450bfd8b38fd4147b851ff59762c1ff48f9442c4d7b77a32c5e023821eb47fca839a27fde60e5f61d000000000000000000000000000000000c5b92f1ca9c20d4b6b11d794a5853824cff20d9267a20a7aaa4bed8bfdc728c4d4d50feb8f0b569757b97f473138db1ebf66fce49c6beb12737fe05e3adc0a51ecfa9144ccf6253088dd1a7a483de070000000000000000000000000000000001191410ec6c5ff628bd25d35965f5e9fa7f3c3d8c0a9a1ee7ae37437a97c25e221110d892e2c7a0e9c8e386774eadb80000000000000000000000000000000003be30c25a18cdab139277232d8888f6d13112c9556895af8030f1893114d5845d895df9afe3c6f9ff7ffb1919adea9200000000000000000000000000000000197f6b4e38be0358a3f1722664c61e62587ecf5467f8aadc3a236b47682a75cb76bafb18a5c556b321d5da49cd4bfd4e0000000000000000000000000000000002e4ebf7f22d929b7421a600e67fa2e64a59edd87a2e2eb9dce1f06d3c793f1a812bcdd510e654d44fb4c1de8c64ba9f0305523dc79dc4b905e65587fbd095ed57aa42403d2df5dd489db8f50c99e9b60000000000000000000000000000000011c6f1dbccde640f63ad7d40089779d01075e26269421b4ce12fa5341f58ee9110f17d08dc1052426f2d00da2dd70b4f000000000000000000000000000000000740b147bcdf06705971c113a5cc12fb37345dd59f2cbb5ff500ce2b347fc5a8199cb3007a871670d5093f28979cfade00000000000000000000000000000000046563ea98b5e85b3c42222d5e0d8481e6aefaf077a1b99f2b4eefb397ec846aa3659aacda569054c9c8b9b69750272b000000000000000000000000000000000812d887943506d68e3525ced9b979354539b7b14003a3169e0084c26326b92be67346920c9a99ef0f9638e8991296feac23d04ee3acc757aae6795532ce4c9f34534e506a4d843a26b052a040c796590000000000000000000000000000000004c8078fe8567013e8d05a546934026cdeee7d485e30d739407db16fefaef53ed7bff0f9adaaf064aff014ac919d91c600000000000000000000000000000000107cc17f485af7f22e07cf14c5cad6368323f720511fc9dda677b360567f769e47a77f61274927ef9b7be48a77357ec40000000000000000000000000000000001487f0880a6cbdac33ca35b9b65e4ead9d8c2e9180c993bdb2052060325aff8c62668c643f0cd9b4bb1f06a3dc74285000000000000000000000000000000000d4b2d062e31fabe8d2a329dbd6417673a519f455739d140246f2b3e43e20f390088c08e545bf0419d796ac71aebb5198586d7ad8fc3e4fb42981a4415224c0d976ebe1c342e9bc1cd66d35168bae33d000000000000000000000000000000000811e9b0acfc10830c074c5a4d9f4d9382461eb523a61dda0b77f1c43b285fc5c1ef3a1fafd923addc9a6e904505a255000000000000000000000000000000001113102d015dbb509f0b8d0d0ebb4d3711c4f0e1e3d55fb0af247dd24be4fec9d6fe3ad73fbdcfe206891bcebefee4dd000000000000000000000000000000000085aae9e58fb97b96ca3c089acab7bdbd0c3adae141bf61075f5c13145b0d07113f1075dfb959bc7c2d3d3b3a06ab2a000000000000000000000000000000000bb5eac8125807c10270d94e5bcf278241d6fa82f68e41b5529b28aebc88870af55881db526f7bd221a8c4c0b29a1b7d6e7db0fbd2a7327c85054b4c0de9727dc0b051058f8bb4ecb1dcc7f825781712000000000000000000000000000000001335276775545fbb4c701beb57cb34312108c9f1d46b4aa4b09a16faf0e648b4e80848bf5e75ed8730715f0107afc9820000000000000000000000000000000006ffff8736bab41b4ee5681b741a81fc870e648001027161144254d04c678e4f954e9f191bd8b26201aec681cbf0654b00000000000000000000000000000000026ede90d14fa0885baad21f9631bae058573251cbef5757bb8cfad061f3bdc78834fa5862dea19a2236c014b0f1652e0000000000000000000000000000000009844d0cf7f6f3401145d8d720defa577ca46b49e04e39c4c139ec6811a574e7dd5ce3acd00d1ce9496f10dd15c6d94685cc8d88273d4aa822f44a447cc22f5a58c420bcfe757a459772825619669a720000000000000000000000000000000010192b925fca096682acf138833b12d96bf97c9a2e69e4266eaaae1785b9008f36082e23e2d42341427edce24449935f000000000000000000000000000000000d5b24a94adadbf542aa663114096bc670e1b6c99f3b661f55de121922452534faed7f68d6b431fcf6f3e379d7acf6b6000000000000000000000000000000000acdbcae49206b749d8c0d21017a33e689ebe26804d1fe7c863a2ea4210c3559805dcf73685702bc56e644b4e02614a9000000000000000000000000000000000092309d684fcdf44bfa321d473060dc2d8a8c66c51419894a3fbadbf1b56179c31dff25403b970d543f1dd0e19e56cf5b6e462d809f8bf1a62f276dcb27e42d9aa0ce33fc4e149e87181aca70a4ccc6,000000000000000000000000000000000b219032a2461a5fd1e43361c46beeae92e30247acadcdd241692abe81691c295ba38a1f0a2a45ae76b1b95d7d0fdc460000000000000000000000000000000016905f64e581aafe928520adc27c24703e7adeb36dfbb416a159cdb9b9a26c9cef0821ccf52f5ea5253b7c9d78769e9d0000000000000000000000000000000015cfff195b2123aa140f963628c41deaf19dfff44d26a38de4547c3d15edef10fe9f65b1802dc374d7ba8fb62117c8880000000000000000000000000000000018dc725cc8d8919a7414b7866fdc54c4467b0f87cf99fc9b36cd65c0ec526e32649f9c57495657a93487f1f2f5769168,293920, -0000000000000000000000000000000014441b14765eee30e8131a7ef62c3b59370f2f6f0dda20fb2a3654fa09492bf695de1d1a8f250bfde3c7d2ed805ffaeb0000000000000000000000000000000019d813f8be2519e89d42a9fd3fef09d44a996d6a4713a9c224bee10f0ebb196370d6231fad810edf9cb4c875f08357890000000000000000000000000000000001a5abea13e909bbefdb51ddc699614366f271b2f6490ac8efcca7759833f3feae11057ab1b9ea32311e7b6ea6de110c0000000000000000000000000000000003ac2bf3c5486ca176e34ec5212165cbe04fc9e8c375e3e999a31fe014eb824ea3f2d06b9cf8b86ce3a76960cf2eb4d7535b53ab5f1c596eb966f57867e021d0f3b099e17bf384479c959794b17d6a4b000000000000000000000000000000000598e111dcfeaaae66d1522be2a21131350577253a3f33bdd74a04b0bfba2940e73b62fefa8f0c34c4aa91b633f6bdfd0000000000000000000000000000000017fefff7d94afbeceb33714e9b5480c3a2f3eabf9d7f6e8507ae54cb65f69b21cd7d04d23f24e3a272c589f572b91864000000000000000000000000000000001652e3f5a99ba8dfbcd1f90de955ef527947642054be603c1b84b24bebb579b78e2a0be426ec21d32783a0e55f0178dc000000000000000000000000000000000a6c9ec91e8bc86ab198416cbc76239f0ac0b903f40310ee1f2066b01b08191538ca913c2736f53f23ef37fea13d52756e0512ecbc5a1b02ab19bc9bee4d3d9c721278e07b7a6e389c4d6443232a403500000000000000000000000000000000072e022c168461905f798e87425f2eebb517e473cef98c255d0fe434863ef5811920af65bc946b29d489b5dee1066c56000000000000000000000000000000000e7a9872caa82d191f6014c845e1b3ee4ea1ee89852b546a2c85ddbfa3c1d4ce99002e3d7732ccb8cfbd57d550285ab400000000000000000000000000000000144be65db373f6401d76e0ee64e51076b861e8fca596dd6a7f3b5735c23b0cd13248404fa0969ecaa701663a1032f48a0000000000000000000000000000000014c9e9c5cffc4518889f7742440053678ff1d9fb1a1a103d0c1f762b10655bd5849ce98f4bc5eae80bdd9e767aae4523a79fd15e80b694122dddb01f836460b3eff99e61ea6309d6b395c94fb5a43dff000000000000000000000000000000000948d0f0c20715f8658e1f2b4f9d32d851e584287225a2f47735a1f4c241b07f8d7c5dd8c13bcdf84e97d49817d4d88a0000000000000000000000000000000013c064548cb756b48600dd535af8eb5b9138f984bac0391df2e90a204fcb6c36017df910031864d802a2ff719856b336000000000000000000000000000000000000b7eeb7c9a01be88e573f196c2a531635baecbc8cff9af385455af3757301436686596ec7fe3618af26953c49f7450000000000000000000000000000000001332f4dbd5461ab9e2c8b3c19c6ff407a071018c92d2c17c1d1d481c24565276c0f55eee8692016c1fd76d70f44627cbd012914a96253926fdaabec06944ffcdb4637a05e3e78a9bcf1b21b68b9dd9b000000000000000000000000000000000d3ee70610b5029a28e586f0f3e65bb19a263db3438710fcb8073e1b25f83db50eb5bbb9d75cb20952a225023f747baa000000000000000000000000000000000682f7d5cf9d182b20ee88683f3915e8c9b03074a373e573aa57232de4e997bf155acf680e365aa0988989dfad102b2e00000000000000000000000000000000143962963e230a9154dc328f9583f5be6923a3b10ee7b1d0cd5f5cbff13913d8ff78ca315be7387900a50b94449884c0000000000000000000000000000000000f4f934b42452d41cc20d7b1ec547bcbcbcc10f215364ccf2b864db23a09d06e94c7a87165dcb691f4975323486757ada300c7e1041d94df0e0201e1135fa6eafc98bd33b2dfbe4c59b546a52538c07d0000000000000000000000000000000005f0fd4080e26971ab16d33aeae04220ae23781da3179e38190082f1d167514bd73bc8ef976a2f333570e9f56a6c05e6000000000000000000000000000000000e159905d29b52ba61575c3a263093017783e1028b3701ccf060c165ba33a765b5265a9b1681c1759bfe2c9c401275e9000000000000000000000000000000000c5ac0bc29a49a7c37d772954da850e6b5e301e230552be9a94017d770ebe2cf4dcfaf104633623e024aef6db57892900000000000000000000000000000000002228e7f42a9409acab49cca82cacf306f6c6c29fd9f7e2ed12fef2d16383cdb7bb2b39ad598b301072c615232db1fa833e9cdb10fc117afb17803b61a2bca7de1d190a325639eb23743f51f28294b3300000000000000000000000000000000180569ce03e4a0155285e733adb18fbca71225507a7adf01cb8e8648891525305e92087f58378f4fd8455d5632ad660e0000000000000000000000000000000011ab84e42f10154e306a568d7cf7bc381000f0add0500cb508f695a3b283ea69d140aa0ad48fce2d2d6fcafe60761078000000000000000000000000000000001136c3016474d6f475609606e8d0269fcdab9fd3188a512681cbc41eedeadfa3b3d9355e5b4503e8b5c3665e49fdf3ab0000000000000000000000000000000003f56cba1b9cb4302099b16b09c2602dfab80d1151685ef78e5054cd454b319adf8b5998053a5b9fddcffa020595e3bfc48b98edd9c229037751d02e58f3d4234d9a3b0ad9ae4947ae14beebb274746f0000000000000000000000000000000004d79dab9eef873f3415d66172bab7166ce0c71f322529bdeffa915c1b0d3fcd645c91dd3450ba61593ffecb95edb91e000000000000000000000000000000000d611a207d3222bba199fa083d0459675cb5fa00839fb4c9034ad868fc1e79d653c18651771431d6fb6b6b5ce8cf6f7a000000000000000000000000000000000ce802ecb106a4f0ca4efdcc058dd0e29deb6a5d30a2c15c8eda896bcdd3ac19053c10105328d239b26c5ddbdb3a95fc0000000000000000000000000000000001073e142621ecbeff6f81453660362545751f992ffeec3a83477fed3e6215a709ffe0d17b65d3369f8f3913bf000e844228758d2cf8105f2ef11d83018157a3119a44874dc34d5f0bddb533f50df52c000000000000000000000000000000000bd84f04b3858b1138b1b429c7216d5d1b1e99c1e0fec26440d59b1ad79788c2d5583122c2ad769fcaa6d10d816a1f1e000000000000000000000000000000000387977ed1ce5da51dca230531bba53d17d3de5d593ec576cabfe6463d5164d7153025dbd4cb3525c4145c4f6b85fc76000000000000000000000000000000000a19c943a90fec6921367a2edc5bc38a5c59839cdb650766a2d2d068242463dd4460bd1d0e7a7fb0e3d2104704b8b3730000000000000000000000000000000011d99d44b200feebe00bd42809e3f67a23cce88a07165416cbfaf4db14420f99e54d62db4280d2c99ca0bc3dc41eddbea417c96f0cf4355a78513c77cdc676a7b09125802c8045756da867e0025a36f10000000000000000000000000000000006a186aa584a466a860849c78e4922889c95a4ac6f39c99029fbb422c43d699a8baa51aa4ef51ff99557babeb3e9506800000000000000000000000000000000065fb15b5a0923bdb52dbefc7e9f1a898e32f17d610bac829235446fc5e1913fffc8176e0fbd33091505761f1d06d8920000000000000000000000000000000008bd358698fd073f660ed608462cfcef1da9a59b10905f1d98c4fe66958e56802814906430c10fc25a4d351d91f91cb0000000000000000000000000000000000a53638b1b6c6eeff468e099446300ca7c7bd899c6494682d14fdabfa9cead0bb37a0325d99e7d0ba6341cfa1d257ba846561328b7689b0a89014823537cf9eeaca6ea5c56a3e58d2abfc2ee455dfccb000000000000000000000000000000001070b98c6348a67e996626ec2752f45e4c007e9c9668459a777c03fab633c10236a1c5be99f3fd950542d5648ef9e88400000000000000000000000000000000073a564401cb1a3a53334c0a55da261814d27b86ebf40b02a76b20973ba2db92e42c138ca7790261c2d70401c984bf470000000000000000000000000000000004212d8a9e4b01f5c6814a88561c2c6143eea61327b031a2e0e4bd056c12dd7098fdfe4d1511bb441ad42b55b584a7bc0000000000000000000000000000000005c5d23824b0fe05eb962194550681c57c1566b315efa8ebc90b3593d7d86ad18328baab8118c9f47eccc0757588591ccf6c3fcd4b9e6b72853934b306a078b1f2fb17879db4a0a93d484abbc2b746cf000000000000000000000000000000000b1b3053774ad5515a20bd4c556d2b3ba95fe74fd0c955069c7f933dfd718ede90ac295f5a675f1c29dcd9701978353700000000000000000000000000000000145746ce88686021a0635bf6f0aa2f77c48bdb364cf4ffa804a57f95bd69d24eead05fbee24021c1ef57e1c7c7b894b00000000000000000000000000000000010ec4795a0762b86f3b83de1198698af67fd1b1be3ddef48f35cf82bc96d886fbb4c75064f51a9cfc5f61630c95d0ad1000000000000000000000000000000001465e31f58892466b8ae4b76a239d9f8d1ecb1834886344013cd1df0be13591798868d224d38213a6d75b02a1fde0ff2f6787b565e8d71be6fdb0c97c4659389c800a2047f668b366214adc716f402d5000000000000000000000000000000000f39e731e6ddb7496448c912ae314e833d28208252c7f8e27bcf7eeaf1da6e2310538b4ef0d55401c6552e91fd70691600000000000000000000000000000000069d3612f924961f827497028737000513548ad8e104acee28f014e730d4752a583cb9a893e6169b71966a1c4a4ad2dc00000000000000000000000000000000090899907edcbd336bd4fdad0dd67c578ced4481a25b864b32aef920842689a2c23265277a6e1d4a1dc1b5047a9f79a000000000000000000000000000000000055ba64e2502baf68e46c759fca30247a080464eda2b32e7cfe539e545d6aac6dafb731c2c45749e50513979cecbeb5440ed91f6ceb2ccf87e4106a16227a3cd7b2821b4f3a6e629001f78ba1aa7346e00000000000000000000000000000000042f1c8b9fe81cdcabea047d0998a1354ce09d62a14f1d0e9d188e2f35f2e1845c2b090c5e157595b33108c67e6c184c0000000000000000000000000000000018e69d3564d4ccc0306e1e6b227b0f961aa9afcad59d4ee1737f980dc876609c59a4c6a3506f987467beba0764b857000000000000000000000000000000000012ce5883156588cfe0f4838f819f985b09f1eab40a5ea8e30fc5d70d029a01a4537641248f4c21dd203909e0170737c80000000000000000000000000000000002888eb9778a4045feb5899dda258657b9f41345731ba630fbbf186b3be4b58ffc7f48abb65b693b573a73f85440a7a7ae8ddfcdb4748981acb9b2037c017174a140f2457fb0148fe807fd194a9f7be500000000000000000000000000000000051982b46a819c74105cb36da871fb2415328a1531d155856f6551bd043eca62ddb61f24af429edda830fda31e22cd340000000000000000000000000000000006449e5bcdb5619aac542f6633ee3e06a4fd56a3e1ce4034efc608131ff6ead70ca63e70f494f519d5c577ae7119c8c200000000000000000000000000000000153f4f5dddd5801fbf7f88a735b9170d24d5b63861d50cde9644579dcff277cdb0d5fbfc3b3b819a1172de05afb9135b0000000000000000000000000000000010fdea84983fe6c08cdc4b4ccd462bae2ba791ab5209363b10b3ef342c9a5e92184e9d8be1419e3d88402bc05bad5fa21268803aeb58a2d57fc797358fb456d5cf96afecb1ee0d2b90782aa0d652b8c00000000000000000000000000000000009b011f793d9a939d916d058ffe91b58138820a646cc450389b3074ae3715d06ddec1075afecda71c65c7ca085210c740000000000000000000000000000000003d4d20f4b93c1e90a0a06bd534d8b4fd64e4c4aba77ae42cf4c5b2bd95f8b02ec4069ea246ff46404e6c9eac632fbac00000000000000000000000000000000051e88c3adfd4d6a02d3f03812362a6cfba3a6c69b9aeef75b51106cc7f1750293d61e31f0ea29b5d7aa56debb6d2aff00000000000000000000000000000000086d9c4ea6769cdf49ffbbf7351023b4aea640e8c90f9291222fd0b5984bca4d481bf7e10df921406a34804e6a09f99df9a8a4e5c65973b785c1e2637937de239bb0fde34b786dceea66f6bb12eb4169,0000000000000000000000000000000007638fa4e8823dacb40ece440f8f1e57cc5c3851f94357a5325207db92380dd57a7c8709e4d00b670e8af1b77368285a0000000000000000000000000000000005b66a6e6b13ea0eb367a61ffe7c620d9edf5563cb4cc0cdfa68b99d9691cf9a40efd967c1e880238eec313eaf4c92ad0000000000000000000000000000000004f7156c69ea88a71a0af2922d1caca24055d40df058eef02bbf95d864156f62fb0e17d9fccd193840c36ad8449bb4f7000000000000000000000000000000000b8f46fd695c5d96d939d42c65c3b709d32f134710a67909dc4bb43d752521a8d4f0465d0590f30f06ce42bf5f8cac28,293920, -0000000000000000000000000000000010d48bf523f3909cf90aa58a9517ef5421f1212accd5e8a0f830aeb15a587e215ca9c340bb846b1d0474e43840b2af79000000000000000000000000000000000cc1a3976caf97b9d59f448f6d9f413eef8904f360c0cf912fe942b38d7fcc637a17038973a133608ae769d3e389b18a00000000000000000000000000000000069a6122c6f0ec68834b7617c755a7eb33a80a25acf95859da5ff03316447182f122d20d993b04e79b6fe859b7adf5a8000000000000000000000000000000000058c6f8c297524319bae6722e0a957d1ba0f75ee3a8aaf06148641c67925d15780e419a38ed7e07410e82769da74f2d070e7e2ae2751a1f71962726a31f77553c2da38f4fecda435b6e5459d5e833b400000000000000000000000000000000156ca5e80be8c8c03a5506ce9abd22a9d4958c372678c0caf6f1329898507dfcb1f06a9464cf080bc6881fa5b7df1ebe00000000000000000000000000000000088174d486b4086b931010da298a399e15b60a113e08f571e096d3a4e94b57b3a684711318796eeca9319119b201abb30000000000000000000000000000000000b96ff68505c088cc03a1c2dc363b05bc8544728a12b29569bed137780523123eb17e68f4632383c252d81bca0c5ca9000000000000000000000000000000000486fc6e5224c5fad56234c41856e60bee4a6c1046f673bf7d5c1bbb603b141fc91074da5f9d3d41b796a2ebcebd9e74d16aa883a20307f5436354bab32b4633e83178f33626af3edb14f82724b8e12500000000000000000000000000000000121fe97c62e068988ebff21d8129d52aa903afdbb62862c7fd99564d9ad72182ab1f3a1100223ae486cd76f6938e123f000000000000000000000000000000000968ddedb04f52140160061828b5f88dfd09aaf37df625ee6f66b9500d6608df31c7edf86296eccf8f9918b051a5e4df000000000000000000000000000000000b7491cb8f6252e3861d7160feb0afdd736d27886863ec0909a7cc711a9b71aace18b17a00a2999dd57ca1a74f148516000000000000000000000000000000000fdb280093ef45b12b694ca3390a865ee18e4c04b231e2c98cc28706d4cefaf4e654582ee03f34ecf1dfa9674489d553041390a2209b80f7c64d14965cc2f515d5fbdf37953f75c4a0203bf0d9fb674b0000000000000000000000000000000010d001a09cf5dc3276482185f26ef3f75d28cd6d2667eb08a7fe06c03b99f3b6c4d82390739b6867a314291cc642a8b2000000000000000000000000000000000587846a460b1f37c2e7f491f9a097b4e86e1943d9cd0999313f65627b3907f09b5d5ac1be376a313a959dd136f7e9b3000000000000000000000000000000000af439695556e86b102926d3b40e3e54cc84464e120de3b4e3c5541a6a5bca44151fb0594009663764c1824518b13f020000000000000000000000000000000003bfd9418c1e57269e222152d321b83ae090f216cb422956dd1fcc464f68526cb4a05cdaefc7bbe6e81d4ffe27d64db47cf23dee8d95d94046678f3bdb4b0ea3d4e3a1a2f07f582e2a98ad6eb7562cbf00000000000000000000000000000000196f78b64fcc342ba4f4edf34a3080ec950532a5de21a875dd061f09351def5ba3b85745a561e38117a14c20d33a14610000000000000000000000000000000003929c2bc55f323d57dc3529bcf6644e61c941b72b424d69969c1cde7a804d157045bbf6d5b79a3e6686509e11ecdac0000000000000000000000000000000000f6b659818510cde463c52cf00bd99da045c80af4d5cd0e55f9bdd81f34169fe869c519f37a98ff20c56db554469087600000000000000000000000000000000129709e97757724e765f6600c2b1928286efab55ec8d16876a2a3210bf9d31cc5425265d0576a2d5469cbd9a6c8c27c012adc8edb64db5bf0ed6724f3b54140ed6c81ca65ef9d1b38c8bca6a62bfd3c60000000000000000000000000000000009f5f167c9b61a0ef76415fcceff04f3fa57071c2d79f443ef8a7e6049cb1352f650ebd8f358904bb432d42772c29afd000000000000000000000000000000001524a875d73e03c53b92465bafca582479110611bac6a98fc7d76966e9781308a10cb202289c0776cf5c36515733ccf900000000000000000000000000000000002b1acace94a6fe196b217a9aff413fe0bcb55122ce9e344942843e5afba0d5f2cd0bba14c9c8cb9dd1c3e9024918fc0000000000000000000000000000000018e4f85c7663e596182603862adb559635fdf16ba35fbce7278680ea289f871bcf6755d85654b2a37ae77a37e77ba06ed1535bfcd68e8136808edf89967fbbf76b7f58d1a8ac95ebd4944b9e440f20b20000000000000000000000000000000018ee4b4855f866781f38a618c2fe4214c63034620ea5b72361079b0a5c2b2d6fb9ea73fa202db3a2678cf07219cde81100000000000000000000000000000000180870513afef93870ca64e2363fa1aa43a599db97f3b807ada1c25ae331c80b8ead5cd69b6f5a65a083606591de90ff0000000000000000000000000000000010afd546703baa35a9eabaeb45d301bd5be115557bbb4ff2a0e493668ee790e947eeafcaa923f62ca00b8e635994e39b000000000000000000000000000000001089996b218aacde4ccfca4d2f66d79fe161d962baaf2d6696e1a76ea40af4ae7195e8cf9f6417ffd054f20b65ddfb104c576996d90abde581afb58903cde4b9443eeb65e21b0d68c578e04c8f28f3d30000000000000000000000000000000011757ad74a3fb341c8eb6862978ab3fb5e8cfc8fdbda7d82756532a890d61919cce931872ff339843805e00d8c62ec4200000000000000000000000000000000060783a06e93e82cb08e5dc1aa31202ba11676511300e186ae8e45248b7fdec3b7d5b6849f8b79b8f78ad84f36218544000000000000000000000000000000000ecfd8ab18066fe3408fd20f2a4478156e9a19a09b58da76486c9f6a013d861960b6b99bf49cbecfa8c9d01d5615c1bc000000000000000000000000000000000b45709845d35d7b560745375df79fb95df15e85b96cc1b98cc832c74621339c609018d153bff93f2f5493a52b7326073c558cc615b1c61c9a42b8b0ab4668ffcfc9e95bbe958e72e7a5500058e6b0bd0000000000000000000000000000000003f9de90222619216852356052e9819d7c6e8ff91e0c6f1d8cec832770ed9001db4569fbf579ab16964d76ae7d1b89e900000000000000000000000000000000010b7cf8f0d283cc22942ed73c599115763dcfc1ddc98d87979fc3dce2f33ca3531cc2909d94f86736dda2a4e94a4f0c000000000000000000000000000000000b0aa4d947644cbc7df8d1927cdec66a68862e5a806e25554f27cc1a3701f429fc7097497ad0419e21cc403b472c8ea900000000000000000000000000000000146270ecb66e1763437b824f2ae122f72f20eb93fb30474691a0a192ceb932b1dee111fa44954075335ab360d31ee68d61301b4957a468e2817db5914ff102bc96460a2c5c15e78bd42884b1223fa71a000000000000000000000000000000000c977cb8de4b6e2e33d916f74eb4e42f089d22b54b59fac9aab0e4cafc8aa2b0f8c55d7251662b3499ea140e322dbbff00000000000000000000000000000000106944a9c2d2ecd08e109de29095f3460128bb751051a1f079acb58b6a60b0bb5f52e63d47b688f4a382a77c3b039eb5000000000000000000000000000000000d2f8be1c78995d54fbccab61f816b6ec52dd19aee6aeedc0e4bde2898b2d07c2925da0440a38c4c965a823fff10389f00000000000000000000000000000000183b5d15b243cc5d9584842ab1a0a1e01ad87268728d72aa8c0d7ec6e7069063a11fdd1525d2b30b35e4568da7c44c5495cd2686d24a5bdda0bcb118a2c0eb5ccfe411ec452e1beb3adbda7e93ea367c000000000000000000000000000000000f65ad4c21fddadcc49a8f7bc281d2b7901707f51a67122179fe97da46ea5e1bc6e70d68eb4eb6776307510a67e972620000000000000000000000000000000009003dc68cb0cdec4a502436718f066348f1957ae65ecca8d32c5fd776215cb9a098c0ffe56c92d79dd68d251f49f13e00000000000000000000000000000000038ecf0bb98ff2e84b388c58059ba0de0cff3d5881ecf01d668495ce81b76b00323c665ba88309af5552b7950cc8c08f000000000000000000000000000000001924aa0f460659f552458fb469467a2925fcb2420d4fa6249310456853be3d08bd5c37a3f0a9d6e94e434391d20cccedfb81d555d1e2df92cdb487a888fbedad976dce54b5c46a39893edeac21a12d6e00000000000000000000000000000000189c3ee691387fbbcffdb147c880218c3e5c0bf78c44461ac1bd3ecd5d4b85225e46cdb068049607fedfcca14882e289000000000000000000000000000000000260efc08531083db2839d1413c90968e87d79bc1a2c730f0020e40beb92e84b73ef43e80f7c61e1a30c0cee11b3cb370000000000000000000000000000000005c852ca0aae2c575c65ef18b624f50a32c007d299f24a3ec6cacbcef1d6e3bdba9650fd7d639bdc60a3e107ee9c013c000000000000000000000000000000000321c01a9de69d6b89db4ed88dd48261ee28facc5e26511fb2833fa45edfb58051c8c3ce9501e8b4c3cab9c456705889bfeed84bd95fb955d1b1045c059ffd051324dc8966e504164e54f76f02eb1b8600000000000000000000000000000000183d50635b22e4d620130e0d4008e3bfffae5dadd7e34f4496899ca54eb4d9e3e95c54ae1d9664609c58d02ee5eff65500000000000000000000000000000000029e3b4496a379464302b1476a4549db371f5d6721704b1d6bd35e2344d7679f8a61a0c3b12f287fd86fd247f9652cea0000000000000000000000000000000012c6a3793fd23e955708f5aeb4d6efb670d25a38a67813ecc72f899cd5f926ab7ef198bf6d591328383aaf54f756c66b000000000000000000000000000000001914d3e4b6ea96bb91333468fe8f3bb74636e9a4f2ed198e9ff01b49ba02791d5bd63224f6a38538aceb777168bef688e3b308b95f6d496e6d5b910b6aabef8d9f868471653e8254ab4d49d593180d250000000000000000000000000000000007457f2601621a99050d8993244f026b9a62ff7055b325e6f1edd1cf54065785f003cf7c8a4bb1f7bdf14e220e490ada000000000000000000000000000000000928eb76b428dde37546a27f3d77605c293738f448fbdd6d618747b0de04004aa4419cc5601600419c6e1d470c15982e0000000000000000000000000000000008074e9f5473492dd2e536f7b305be4e5c564cfc9218934d03dde6dc5118064ebaa5c26fdd1123a9c31336c37c1234900000000000000000000000000000000002bba1f9b7da6abd2b322c8f11c749b2a284552eab25a77d21b38b028da477a3ffec1901a015e81fe2893576a41e4c0bd4ea92e0e776be341c8444d4040ec121a2847256c5c9bc918adb28618548b0480000000000000000000000000000000003760958eac45397eca1a1d951a80265a728dc3c584f7dae111e7ce04248885321b69b334b00cdb0334a362676c2d32f000000000000000000000000000000001031e4a63129ec40da5fe9dacfe148a67662eaa00e1fd5c30336462371c167348a10e50f4dc18469a1a6b76485f77e12000000000000000000000000000000001412dbf993c557323426b486f18a91d16b4baa2c497b30fb332a710ac901c96d46a577d04ea87afb08258aa6d204a1c9000000000000000000000000000000000da015ca09ac0c3245c090f39852218f46fea62198fba35ebc4a7f14887943c3bd1bbbfbfa300611e45f419b33988e404c07f5188e4c6270a7e9e2f551683c4f9dc943ffc7ec279d15816a7f4910b8d30000000000000000000000000000000015c9121f72e2425cc8aa4c878907628dfe75a903b7f756b9e13728372cba598859d20a92a8297d95e1fbe25fd1cd968300000000000000000000000000000000025a3faebfa53918efa733949f914be08b791794bd4963f0c3fd78df48b14ad214374b08299327575c0731b54eafed76000000000000000000000000000000000771782ecd9980da521618af2f9eb55d91d67b20ba615c7b3cb1a48d483ca405fe99a1cdd17e4dc7aeffce586987d41900000000000000000000000000000000136000da90a76d538f336608ce877be943025b4c8bf15880ea9c1c001c20c954292d362dac9783b7bf66b8d51ddaf0f2a819a0438efd7ec0c1e3eea07ba201af6a832fecec818adbb781ad0c23e81dae,0000000000000000000000000000000014cb24001bd933b1d5866cc3de9f4b8479fe23e4fc26dd210f9d06e7a05449b9f5ac4e2f48fb847599f625824336bf1e00000000000000000000000000000000033fdb2e899427f1cb9757022c5b614f08c64b53583486148b7431311a6f15aea3b968913fd5f3e9b624705351074be600000000000000000000000000000000035420be9c7ae3203d0dec61ecea70e22e62f50368be870e74f9a7349453647a7f61d2a42cec6522164cca0c7081d4de000000000000000000000000000000000fea43388e9f6e31d419c7f9fbb9839b4cec04163a7b401d8f7de73a4560fbfef4e272f1db9c9d5b37693378f139452a,293920, -00000000000000000000000000000000039dc2b60389f6c893c44072f4db23e7df4c2f299d6b70b70784d9370d9ff8e5413872c227074d429db999d30dc9499a000000000000000000000000000000001702273db356abe7a3f91a9fe4bf56584f13de4069a91daa6c0b552089bef60da98d32c615aa5610842dd8a507f9477c00000000000000000000000000000000095285e8c508ff12da79e16e0391dadbe9a823c586a049e729596864c3cae117305c05f009f9e8ac032abaec8a63f8de00000000000000000000000000000000078fc70e926decf7aa4c2e4b395e88f367757dc47a4cedcd5e632c456a4c160393837196af474948ce6ad53f830ce8aeb15af019ea2de662bf187930caea19d0aa07a74b49fa7d1819a539e06e4c69ff000000000000000000000000000000000cc3cb5e7b033cff3e5cb01ba29ce8e9f4a93e836ddea7d417f7b07ba8aa71a0efae2e1d7a8ec70bdff12d84d229245200000000000000000000000000000000019ce3c830505324b9bc7cda1fbb328150d71310f06a8424dba861d67a7bc0428beaaf697646d22cae9e00477cc8066f000000000000000000000000000000000f6ff67efefa5636b104a0351c90fd3e89a32b8a9beb0d123d3d6ae42eb5e8bbc19c7a972e27762daf852259c65fce6f0000000000000000000000000000000018d98c43fe5b13b701749f4a5dc25f0e713d241d573639fcc73429226bb131d448283338a909670066045c65789bf9e7064a6af51c1d499c4c28556ad1255af7467bc750bf2db2842d626647bdb3346100000000000000000000000000000000003cf82958d68429503265dcc7d88a3763cca32baefe3c8d32564cf30e8e6b8255d4a9f6a76bce1da473b50287deda74000000000000000000000000000000000bfa9cde6c06b2a2ff8f877ca90b3827d0aa0408c4ccbed23ad461433dad71017d4dd387f49c5febdeafa17d06ba784e000000000000000000000000000000001770fe70513533d91c83449ea52964cd8b449aa81f71e71995db5b19ceddef18e2919439c80e10086e670be669696e4f00000000000000000000000000000000194c20491c9d5ed827cd9d370b9bbec55e4a7b1c34ddd1d80201e7019d9487a747b4fa57b480dbdd09af73aa4f5fa0e9a3daea5a083af43711fcb09282b66882ae5b5b8e1714e9186f33ac0dfe48b7ca000000000000000000000000000000000a79d9e0ff43249ff54526c5e1cd55a9bce93adf272508871326c933d526602dc9dae5b6f129a0f1c38139ed1c39be5c000000000000000000000000000000001458b554e0387c1ddb9dee9f4e9fba9c81c15807f496442f4b7210267912b9439a19f95dc80a1e09a0e5cfe750f43c8800000000000000000000000000000000012c06b19ed4e8d5d1b9fed56bc5bdaa3bf0112db997e33aa14899d53e1bddd6aa91dce7e9d25473b66b8578d398981f0000000000000000000000000000000015369b2228e728894f2fd7c2d8c41ac3550da4f297de445cc0f0ef7134c478f526987643cb5408a0bbb79f5f983c085ebd682acd154f6e16a583ca4968d28471653375ef79df078b17b2cd9634258dc10000000000000000000000000000000016649a8231407074af5ffa93f9db5a2ddce8785be8ee77149602d6afa24ab30b26d2f74bdb5f7464333924a817e242e50000000000000000000000000000000001b990f5ed0b23e113042ff004236646c6eacacd99d1d73fe0c3d9351ce8d622327e827b2c0556802c5657f8f06062a4000000000000000000000000000000000f002a2a5ca90285f9b2fd429721c2daffcae5fe48c571ebacaf475606f96cc8350ce88a850ed75e5aae59d445249bf00000000000000000000000000000000015157fe1a767dabc185a8dc8fea3cb208fd995ecd9acab762638faa987f8367ff7c1a60b657be6e9461acc9df16381e5562223d3fae1d303a01ee4642fb4cc70f21937ba7fe377260fe82262a8455a7700000000000000000000000000000000073884ffbe6deff99cb4b0ae1c0e91e2f4a8c2c7296339b1d7e117d5d47ab055743d643155680740befb379a1dcab666000000000000000000000000000000001995bdc23991dd4cbd973e915a16691fb860490bb54011384c553dd14afc37fe673d13950c1e7eaa29c324fd9304624c0000000000000000000000000000000012197a19a498cd94ecbb3a409337b04e76e1a52715c40203add20eb80f7eac66f3386242d51bea34ea016d778248836f00000000000000000000000000000000101069ff0af2ac4dc7a5bf7bf7b56d82a310d67cebc41a9abf1e1af489e1acef3e726fe9571b4382777573712663e26caf1d0fdab6185e1c3f9f621ddc169ba92584db0b40b6ace7ed563eee0090629f000000000000000000000000000000000849b88e7ff52d8136a120f924b20b45ea9ae654a0fa037b62f3c275f0661091038a4c1d6ce7d50512e628b6b397c9f6000000000000000000000000000000000e50e82e9b368f2e316d41febab6b0f626d6588b7217b4e28eedbdf50a4abc9039be9e66c97790d12cdedc90873993e2000000000000000000000000000000000bc5d2bdf06fda1e1d1f5c5eaa7988dfdd790bf4d952f5d3a532bb59edf619dafcbc29274fd3661a35a3f15933b1849300000000000000000000000000000000162e5ce45499e620d0977fa26a291a8e75943c4b5a2a80be395ac9b89767ea5a06606d6b75ee4c8a286d2ea5a197baa5e910487c91f3839d5961f02a67f3b357206e406ba207dde969498e40d4a26e880000000000000000000000000000000005c11afc970544b96fc1a4cbb27259e19b5fd588d1be1c8f19eb4f111882292a463c951521388cb8cb743e5a4a1b57cb00000000000000000000000000000000013dc433dadc122376b75fedc923386a7ba5a363678fcf9edf165a50e160dadcc151b6f402648193d9ef960f5e401030000000000000000000000000000000001893af155aca343bc29989ec2b5a583d020a7558c7663accf6f3e40d0a8eb98ac548e933eb8e2d5fe3550927acc2ed4900000000000000000000000000000000043a79bcbaf07bffe6c6890d95c7e74d127446bdea51a0ba3adb164ea39684bb3ac552020ca28b86e34692c9b36f4384396d32c2c9ef685120995d2244756bd45591618597306193422f3b5df4b075d2000000000000000000000000000000000e6946ddc8a9d73e5b140af80cc91b31b9a226a945a9574f0629566f7ee7650730c5ed758cc30442770ed1602b84175c000000000000000000000000000000000da0abb9f5bfcad73b3f24903e9ef887c660447332e5457e4a5764f6628c04d6fe903679b8dc8bb3aaacde410812286a000000000000000000000000000000000656016c01d3405dce9f7d40e47976bc8a84abc370e7e42849dd0bd93ef1da0bc88e428efea43dfea37dd834cf246d69000000000000000000000000000000001939b2c92c8299d7ec1dbeb9f291c5e1c9481e10df10e6ba18ae695a780aec5a185ed4c7e82dc2bb5af87a74552c2ea32087e21d775fbc2c20dda715e46c9c4970394e40e991c78ecc13a2a5d0b0f30f0000000000000000000000000000000000942901572722e5005a9ef5f948c8cd6f557be8d114d2810d3cca29933a94de3c7658e7e28675c2a49f138d9c98c524000000000000000000000000000000001908e8b815e95ec07a90861ce53f545f0cd44aacc47df40c24d6cbc61e7b28fb91cfb1cb3c67b6c5b38c34fcb2ca35710000000000000000000000000000000017bad3616d8e510e325d9166790239c8c817c68ba7fb937fd5fb70a4219265edf6625b52ff26f0a34c0bf481c482b2c600000000000000000000000000000000023ff8a50a9c0e9ee829ec81972386ea012df5e8476d8c342df6b98fa1faa1382ae921c2f1018a918868672450355c44f44043002a94560d725da2ac44f30cc5f14f52dff5671c6689efebd803b1df7a0000000000000000000000000000000014675ab3efd44bffae321791e6fb35a24b9c07405d9985c685795df2db183ee9dadf18c76cf4095e1e0695dc2c08c4c4000000000000000000000000000000000835f2cf09647061ced2bdf4211bdaea408148100f864f47ff76c0c63a43e44e8ddd9e01709b6ad129bd574d71a1a63c000000000000000000000000000000001017eaeaa6eba76923ff27e5848e5f3b09e7b2b9d55b2cb7068f39defa8628d1c8cedcbb0e1cb5810febc4ccea712b7100000000000000000000000000000000054c873449c738383e9fc2f0f74a6334904171fdb704f5ac35a483ba19a8f661187d36fb35014af9ecf88225466c86e48624c83d846ad2e53f3f8ff5ffd3fca8723e6cd431e89ca29a4d662e82004b60000000000000000000000000000000000439ae88636244d5e09607960fb033e4217343899d044b21e61335425b94a5067c941e83e5a77f4b0690e1de037325090000000000000000000000000000000003a67653818cece3ff0390d097f1bfbea9ba954a85710f5c24d1de1893f25f2863991fb9f330e60cad725708e70384b4000000000000000000000000000000000243394c3459a3af236189ec6155418c1916b854a20b980ca1044b48e23b725dab7c60a48e89f642423c805c117e64870000000000000000000000000000000004c8c9fd9f278dfe9f5e24e0f5b42699bb9751b56520827afc2fae8393c690a63f10e92f77c4a10b0c161408da9bf505b2b2a8a42887ca6dff5b5364d88962068496bee79cbe74de0e8a06209feb38320000000000000000000000000000000011ba67024503301ec72bfad101a48708e3521c8a23c6bf2994078690041cf7eb75675cf5f20c8e82d11145e31751a2300000000000000000000000000000000008ace953ed2eaef19595cc7c9fb1806d26cbf1e888075e3985b28f8d93b9c0b4c820c8e8b50fd4e0b23923d428da3efa00000000000000000000000000000000054ee6f7247296e0748d0b52148a97b930e69991a242767d80bd6434d42b0865a64d3ce60953fd2631aef873d8b2acf3000000000000000000000000000000000077748b724301a8bc48efd1cd66086e727e9872e4efdaf55ba90ad1bed7e229a9cfb79013333b50efb46090ac0bdab488ecb5976f63a38d7f3d8c8ec441b705563c5e3d899870ab5d2ff84467fffefb0000000000000000000000000000000005008a1d62dad51132ad38a226e8abd7421392414acda61111c728713a2ece284b04d75c2bc58d355bb1d3061415010200000000000000000000000000000000189725b7fc48b8a648237021e9a2334247f1cf18ca50008b813978db01667ba08f00b23b3aa0e015f549ff2d5e5c535f0000000000000000000000000000000010483cf2310f64cf0baf556cb2f2828a1c15922547bec03cdb182a316aa86b5473f03373cf7e59a9a78f73193c1caf520000000000000000000000000000000007f635394301441bdc57dd1f4f97656f4218ebb139c13a17e12839091e2e81327f3353c56880c608de824a07a17b2bdd951f4960d6614b098249eb9420077ea5ad11e38d1694f4df33719d1127338f44000000000000000000000000000000000daf4090a229a1ce946064cda1c4b19c88100c8785c69f2eeec3aed12065787ab0abd797ceed07617d55a9c70ac3020c0000000000000000000000000000000011d77fc28355f61037cae3a8342bdf8d11e963495ba3b5d67055f790b1fd632b23565cad77a3d9968d364e4e2a553c9d000000000000000000000000000000001038d7e8fedea873c864b79d1cf8045485299a2bd4d26c5ab5c8d4a073e2c3fcb38cb230dc6ab7e8e228cabc6ed97da50000000000000000000000000000000009de9209ed14d62625ffbf770e8c528594aeddcaf1aaeedb4f3ca973e7b9f9f1a40370cc74b154f3bc641665d8e4d96b7056c7d93d8453be369831dc0575df6438db488780d518a53d19b8f5d22d506a000000000000000000000000000000000a6b0dc04591cbbb1b82a059e08b488fd66edca0f2d264c352f81cb6ec45e50f0af16917fa4727ee9888f84b6c888c60000000000000000000000000000000001369ae16bb0743f65cdfc8082dbe0d588cf8aa5406a095c3deefc27eb3ed462dda9dd4921cde6a1d878a805cd144515800000000000000000000000000000000124e08d4de6e831229005663df4e4bd5bb7af56dfb13244c50410e6d0aea420ba19208bf1a774207e0e0170ad3a9b4f60000000000000000000000000000000011b2973743034a2c362281b11a1ac1c89f59ace09f0a53afb0c2ceb061726c7aaefe274f6dc04e5d0dea2b687a00609a8aa982de1583c25307e9e2c8cf2469a0b1076c6be2fbf12caa8584f34988221a,00000000000000000000000000000000136ff52e440da609b6b73aa838f2eb9791221291b7b14d902458aa7aa9e37114c573edbe8cef7a98dd07275a8c3fd650000000000000000000000000000000000ba625eb47be09ac8cd1e2ec9015640f416af0e3e0e79d39ccac600ea08bdae7a2bc9144f13168a8cec03ce66b9daadb00000000000000000000000000000000095c51e81b5881b009b28006286c704ce3b002e4ca50ac8ea8e574d1e9665a5b1efdd60568d4a4a656ca6a2d1750a39900000000000000000000000000000000143c0c4b3b720fcd0b044a6f420961e2b7eb5f9f1b0d200de56ca8b02709d819f47f0a6ea7d6b49c4f30520586a45616,293920, -000000000000000000000000000000000da4cf56fdbaa9004bf8ffa12d5cfb3f296ba5262dab079c91bdbadd6e41ee5f89912bffd5df1643146bce1f0e021b3d00000000000000000000000000000000150227356e48f29443a0ab4536e7a2f86f9e63840e23bbf1b091a59f52c27978bd6a15b29b105132298de45e51134da50000000000000000000000000000000017f5271c97d84f55f8b7ee0d73267bb69cdc7565c470a4b531f9dcd29596eaedf46e61bd79e71e5ade7d000c1c1d81bc000000000000000000000000000000001322812590e6c22bd90511ed72553c1cdb0ba83487b00e3adcb01a9abb438f365ca23fae9ee4a953544253696ddb0bf1a18ca15f0d931619363f5ee56bd7657b2298f228cae8d185c9d062910193e9c40000000000000000000000000000000007c59f94693320b01b56b36f8d1c39fc9e01bad289577738e648771d8940778276cdbfd59f07926e516fcebb70592de0000000000000000000000000000000000aa71d6dcb0b225526eb92b79891ef920634a007b87986fc0f776f85195ad7ec2d84b9bc684add947df8ff42c33b034d000000000000000000000000000000001362cbd6cca3d5c1ec68928be38aca5de1f224e7cd4f5c3ab1c2cd589bbd7c31022d4adc51720bedf2580d2acfa0f06400000000000000000000000000000000162bf0f38e19ddca9aaa370f988be9b35461d2a0f46143e8663f1fa549d0afa1596f029cf2f800b027b90d1eda6ae8a2b54274927eb29fea0cdc464271c918826d5249b2180a52a5020480d1020c9795000000000000000000000000000000000eb12a92fe65f79c646ba508fa615d09d86e582c3337ae16f66cd3bd74a9caa9dc17defb4b4e67ad62f0665c9ad1b6cf00000000000000000000000000000000058b6ce2582c46c0fc108a37e1d2713ff21ec8b1d8c18da0e69f0dfec7f2f327043e174e16d9d64f9ed4d3818a302bea00000000000000000000000000000000068192bd2ebc0a23092bb98c23f5792e179913c4ff1f23eb27296a77e83729803764b8db3b7ba4fe154ca467475eefb2000000000000000000000000000000000482b16e876aa90da6da35e0d7495a04d5b0a1d084c61821f23e1ad63cb1e66ef5975a3cef9ecdf2e696e9d9b50bf9b65849bffc842c21277be88dfae0040c54b072ff526731947cbec0cfe963f2d0dd000000000000000000000000000000000b712fffce3e63362bcc246da566a14139a3d12807ba83ab3520b0aa3aa20cecd5718e2b7e00f24e6fa705315bc2175800000000000000000000000000000000057a66fb12f27e4a5268e56805fe2b61b5ef019b31fcdd861e2b0beecdffe1a3a69e8d193815f97740324aaa40ce34a8000000000000000000000000000000001080a9e1133f37288dbc3835e45b6611fe84ec4790e23e5ff84a2f72bfa2837f55cae9177e5a3a918adde777b7298a9200000000000000000000000000000000142dcaefd73d7f6342e87fff8c6cd161389b6049fa077f35076eadd2b4aa66f3a1819bf8272cac1c28cc02bb6440dc42aeff769da1b62fde321d46c66f8ee7f2129446d805ab7f7bd586268de8f57c4300000000000000000000000000000000034c0f8249d6aefe4cdbf84d151ea9f84add42ade087048bbbf9de4a412cc805dd9b608fdcfa34fa224066b5f06d18630000000000000000000000000000000009e235ce5eb936bae00d3fecead8859e6d909da3d57bbe0a8aefaa5efdc94969a1cb2e12642c0099bca4e7bbf9833469000000000000000000000000000000000b6fbab498c2706f0efdb4effaf79218cf4b652a5205eabeb84f05a060da8cd18c8154a3d37594485ba50a8228f27f6800000000000000000000000000000000130ab70e17dc73f773df99cbe3f978bcd3fcb92a8226a1450239d209cc6969e2cecdc0bf3cbbe9a9c1de072bffbccaa952c9e56cfe957b924c9c0294e1c1f12474331c662c8e86288c97e6a8b8b5b20200000000000000000000000000000000031a2c10e95b841ecfcbddee4b458385e5650dec9a2d1e50216d9fc261a9829eb5fe894e47f171c8fd2f4d5d89771341000000000000000000000000000000001378471c7f770672ee82b70fc87af5ccacdf8995df9ce48aa9fc2f638105a2fdfa48b615970665ae4869f1e2dc7988e8000000000000000000000000000000001969517c503df5560628555a8780138e4c340d9d49d8fac4a8a11c894d283d49fd06aa81e9f0db8f015d9372762dad75000000000000000000000000000000000f5c2d9b7fc33167a6e9b5a5fb8c5d16ca009282edc05cbc8a048b835b16ba33515c226174d6ce5f9836581611ab403bdecec569d223c724d162250ed1d074ed9f4080aaae3f44b77df05292be48ebd90000000000000000000000000000000000a6a32f2006c4b7804e99011d934ac91b1b3fa6f5d02c574cecd6570bde1e998f135449dfc148aaa8fb8757d0a7299b00000000000000000000000000000000198beb461b59f57b85d858b730fcf853d967a1592e5e5787fd81c6a3d9d9b40c1cd7912cae21a47aaf78df5540604cb4000000000000000000000000000000000955701e84721866683b4eaba82c2df8a89bc906fb0a3cde565d314cd7278b0c56936205cc8ada10b03e69b93c48067b0000000000000000000000000000000004740253653a0d6cb15c76e145dc0b1f811bdc964f7d595b6027bb012b42409deaa8da83e6ddc3f0f7b4b237eb62b537915ac9453b831c41becd3c1f412cdf5379e9cd5c80bc6df92ecfc5005356d2aa000000000000000000000000000000000f88e1e30674934bf1062ac619f1834f35f804a958e82121255f8087ae08f10525e740ee53d7514e0ee7c49e324513c700000000000000000000000000000000019d554645696b7beae881ef62297283c5b68ad3fa9a84a47c29cb53449d33d6ee7a5a3cb83b6acb75cd41ac3f52fec40000000000000000000000000000000004b32776966e52e8a72c88a689d6c56833296d384e2059d8f615ccd3616972074987f839b4689d5610a88addcd836d930000000000000000000000000000000000fd4d21b00d81ec993d2350f1fe360576fa983754a7159c2e81024a00931d84e419e8b5231ba8cf8f05a0ee6ccea7e558fa60bc7cff4edde18301af2348faa69ed4f31d437decb7d4fe51142d179e6000000000000000000000000000000000177830cf34186191fa295b7f279bc819d8a53452e2114dbfe709971584ec7a2da7453aae3e64f4b14c261e22314027c3000000000000000000000000000000000ebf2aac35fe070403a4b7a5c2f102c67300bfd68af7863b45185b37ade1bc53d46772062189f348647e74c77caca4a600000000000000000000000000000000128dc7846b2dc5c453ba5fe4675d0c22f4d7089624ede05b0910c34ae623d4671979fd73455b35b61a57c51fe2895adf0000000000000000000000000000000008e33a3c3735be035b550613c712b220595a83c1953b24b3efd38c5913fc23df823e00ae5a1c2ea8a8eebbb93c5c721dc29be0b271d4e22d39e9e06db9e50845515880f30c5bfac80bca39a2d8d61ea0000000000000000000000000000000000a060a957a8da4384e3436110657110653685bb621c32810b6516c690a00c13e37f70185958beb0ed886aae5cdd611a7000000000000000000000000000000000b5afbc85e274049985eac230b2aede7b2df1485c9539a4a4eb6aea406d0f6515ad8bbece7155fb0dfb2123919fb8af9000000000000000000000000000000000afa722987390440a33d5103445dcef42cc4a3c461daa076d56fd38e0b220016ed2bb8e99b9a8da4af96b7da64ba90950000000000000000000000000000000013ea6b8d327191e53bc71fe43fda305a4a0584cad04048afc0480f179955cb27f2ac8791d847036470ffeb47aae36877dc8c2e971a3a4b9909dcc5cc6a0de50286294ee15f441521e0f1d2c3ad3a76e900000000000000000000000000000000032b490f795ac3242b8c7185c9e19f0440ecee3a65263dd4e4c9a431571deb7339bc6e2d73ec43750f6f027bcfd674c400000000000000000000000000000000076ab4ab3e8ed6ea3b882fde5cacb3bd094567288699e11f368c3f60f4283c5bcee7b4c5debeac541ead983f5936d9f80000000000000000000000000000000012aa2060e421f4f4249e83ca0ae1752dfa2b7ca958821841a18f05071a35fb9c1448619bd96f8a7adb2202d3ffda8eb30000000000000000000000000000000008b24f29ee7571f31ff86574e654a5d849acbe92653ae1a1d2baf4c9ca6e67da4937bfda51a70931a6e60d90162efb4f21c9ae0132a4886820115e71e280d33378a04344f635c769fffe91e89fa7ea47000000000000000000000000000000000c8b41e5c47babd6ea113c0ad9f45a75d1ef6bd313b768ac01e6f581ef6630ada623c1a27d4aadf543af4055de7f6b73000000000000000000000000000000000a0f73af06f8f0115bf17f7c5db0a6bdea77a8e3d8fd0b52b0d4e2c558f1331f655dc272c86d98bf166b532ec8e45285000000000000000000000000000000000499b55964186bcc6986e7744c52babf47e274e47a202abf6f816bc748baf846df2b5ced2a5f61fbb0aa2047bbaf82db000000000000000000000000000000000d6c2a9a3fa5d0524f772cca2c7e72a5f2da1a6a1b9550997e7a6cac5b6b6c37693a01d30bebe4b9c742b63bd31487a1e1067c01d5565d0f387516d9721f7f4e5253d5af8353db4a55500e20a95f3c9600000000000000000000000000000000143220e1cd08ffaa6db4795ed4aa35f3b12cce724fcad005367328972f2364f34096e32f1f1cb7a4287ab636d0030322000000000000000000000000000000000f2de47a37a55edbb75ff0bcc446611d690d7f9efdd09ca1ebb6f1d64a330bed420bcc85aed8b95316fcac3aa7d1f2230000000000000000000000000000000016afb044b8b8c64547e000f80b25576aa329a4319dcd4f1bbe15d12e6f3bbdddbb52140e6297c637311ef0c7a31cafab0000000000000000000000000000000019e6803c07fbaa075093f6a69f9dde05ba3d3f58e67389d7f096e56df49f8270008ed422b64fcdadf7cbbc8334037682a23bf766a1e1c068e6e8e4b60391583ac197ade53caf0f8a43c53d1bae9f13e500000000000000000000000000000000134125416c7908cb4454ce6aadb30df46042ef2a6b4b69b19fafcb9ebafe8b5579046725590266cfd10fa26e1b5ff3dc00000000000000000000000000000000073f4147cce24e13b9eefad7c69b457acf126bf278a58a26a7c7c6b482edea6dca9725d7e5e4138b4ec81bc2505ce2e60000000000000000000000000000000006125caac1061cd6c556f4cfc122df8e949622a46ca707b48ef088ee5623df058bada1bc0cce1399f0be1ee86225f13000000000000000000000000000000000146e398c161e29c90c8a4fc44bfd5b3dba6f9e80ead561fa3d91ca5f416e06318dddcfe5147ab5def858fb025a1562352c505d4fd8287a897e01517ddbd7d7ea9d26ae4f58fbca172e5265e2b62858b6000000000000000000000000000000000944942effc77ad02c5ddb052acf86f3a9dc4127dd032181450295464b49ac1dc0047790acb378221fbeebd4c92886820000000000000000000000000000000018e1d201b38d88665696ee6cef11fb19f7daa7f11c5a5ccc73e6b66ac7b89df8437c9f07132ec8b69e13f63424ad694c000000000000000000000000000000001463117fdcf17f28956a42677b3ff431cc17ccbde067b91ecd6fae51e1e24ba8d594ea368d041656022611ad3ed44a6e0000000000000000000000000000000009715cc5add17395b7ddbcb961269fc5d4739d799fe9554b3c9e9f59c895ca5df8ec75bda05cbef3e6a165f7987e78662908006c06ceb9188651c59d434988cb5b51a5a75772ba71875444c65ddf0f4f00000000000000000000000000000000007c07cf1ac9b8b28e3d2f1f4ce22b8ee46e99914ba20c7362c679559a1618a906c6ea65c475ebbeca4947019cb6fbec0000000000000000000000000000000008b29f72cda71e0bc2246ead57b2f758b741b9232d87be75331275a5cd63afc9aa98b0e42c1b82cc258e93c97e596a81000000000000000000000000000000001512548a4bbd537a4d5baf673fb76ea7e35b2977216e7b29a6375e1f92049d7b7d5fd5d8b4ae6191f5592b738e149a5f000000000000000000000000000000000cc9d646428135296919808c6ac10c142e769bf71bc1490196dfdd4e1fc7b84e58155bfdbe77a9e684622ffd83e97ad3e8e8724c80f3527de5f0b2b98ecdf0b8d0471e63c0763a89da8a21a70dbf8399,000000000000000000000000000000000ae9da7d12d0a03cca3b41ad869f762784cacb988eac7ce904ec9ff47824e058e2e211e2285f9fe2aed0b4385949b4540000000000000000000000000000000005b0c873d20f7be1410d39885ce4f79884eb6ae2b2f27510d6f6874dacf2a66c64e56b7aacac61ec88261624936e695700000000000000000000000000000000076c6076175ad748dd68fee64431e5e4ad013797de4528287e7226c3df90233799ed5c8b36848c1a2e1c02591a013d270000000000000000000000000000000001f7f6972121d38ee2d10c621a38448ed12271f7e0e9e4567fe1b5fcb469c7906196fe92c66c37f8c5abc91160fea8ae,293920, -00000000000000000000000000000000139cbf360b10e59c20dd4155af2023d5dfe0681c62351dd541cbed41b6a328aa44b862d1985b132a4d4ca61c95b61ebf0000000000000000000000000000000004af6b5a0f7a02d5c93304be0d31959bb4b1a7a5838dc3f9cf50180c4eaf3d32e68c006263d75f9735c8f0b5f811d3cb000000000000000000000000000000001644937e5ff3b8d2005a2f3b0984917837d44412362a121be481526173a4c61e20d61076aa10b4072b79743c5f7f4c4f0000000000000000000000000000000009bd399b55a59550dd876f35e54a5803058228bd6ab6c9a66e438cae473c63816c96bdf378ad426a236b58b90e737831e14282bc687a00264b4e4678ff238d5205f6b6fcc10040d9b4393e93f76297a8000000000000000000000000000000000f343e5118d7dc3a38e9975a5f40084ee5f2305e45a8aed28ef105f76345d9f5646b4f3924b92978846b4e605b78fdf400000000000000000000000000000000017e61a2ecf9b3403b43f5a10a97cf5088b4f98e5a4513b0912ea7ecef44e6809f10dee60367cf2fe3e903dd68c2a97c00000000000000000000000000000000039f37f414338cab0e12f99b2aa1e3c02cbdee3406d1bd17c359ba899b7cdcff605f894530895aecb469f41628c3da120000000000000000000000000000000001b78bf69f1b7168d735fb4b7b135fe70db79f50e792eedea23f83cee9b48e092536c2ed146c7499cf047c5b4f0a08735307650d6cfc681508fc7b8dcb5291837582eba6588132f46ab8fba674a1f5af000000000000000000000000000000001342346f1b553e29e661c9f6c0a24d8f788db98262d6af86af55d313a37eeabed1183e367ee3d83faa3f284b260e786c000000000000000000000000000000000960c8af3f7e6587cf83baae447491e73cf41e637e1efd730e3acd9793717e57b85530584942e7a030bad3b91a76996300000000000000000000000000000000166daca4ee2cb9516b5178cefef0553115dec8157f6194d24d191cfe6340406071883c89246c0cd5f89bbd5d0f1ee15b00000000000000000000000000000000187f668086b9b6307899d301bdbfec915cf24ac0be10d6897b0677e4f1de6a241f3dfb19225644858be0941530e67d0f7d6a25511ba63f0b7ebd2189cfc4c551083ac92b144e30dd81d27e59dd86e22600000000000000000000000000000000032c3783e701bcb651aef40c91682eda03f9d90f252740612c85a5727f8bcc41a886b328d5ce787031c08ace235ff465000000000000000000000000000000000b0eca06f9fb69ebb46d0af56d3d934b333514d7f31208b4ee2fb92009e6041749028a78246a0adc324034a94503e80d0000000000000000000000000000000019eb24ed35f6c7ae53047814cab14d51ae6cf336d140a17e794d5cf18450b7fac3e6f990e12d340291459197bd353861000000000000000000000000000000001983a596485e657deaedf01614dcd5f4ec515c0050e8068ea02c9833d0b165c0f467107a50da0d8cd43bfcb59db6e710eac8e5cf13de6db37982390c8b6b0474795f479584960748b7ffed881285e2df0000000000000000000000000000000002f1c29ffdf7bf20fb8a13363393d5f1cca5dd9af82888f0102030fdda641abd5532ffaa2669c0c4159a989cef1c5bdb000000000000000000000000000000000bd548079899d49cd368bf5c246aa168fc8c777bb84a7930258502c8424a4b68e1ab20dc9ef39c307e52bcafadb0c8e100000000000000000000000000000000070c18918f037d5fa1aa005e2c80ce6a80b4b24d33ce72a2bd824f9a061af1db236f04d6041314310b31b805b8a674800000000000000000000000000000000014422b173840da655aac6ea4b7a04313d5d0675bcd565258c73039f879176e51ec0c8a9deba9c78c33179a5ba54492012c134652c27da0a0272b0783551ae44db6bf592ff299b48c50c550367d470b5b000000000000000000000000000000000a1be8e39a47dbe0bd19b8108a5bdac582e1d11ef7fe28df1f12da52924e734e1d591e8e33ec20c6d5af5bc8c1161fca000000000000000000000000000000000eaa7a7cec93b8d5eb933103b52a35b3d58214feb8e2de0bba3a0e57e7993a9df0dcf8089142f57f8e0d1d303588ce9d000000000000000000000000000000000089fbfb389ba448eb77722994178ee3cfd15a27be4ed6f4d4ab6ea1a4c10d6ee8424beb17d08190fb18ab8498d4a4fb000000000000000000000000000000000ab02df2eb474735e28c45b915299230ce159816419fe9c99a7da397b7210590705262ee14c2a244f4922c35bcb119338dca9ff432bb483ad726bd20cf96b07ab6f07170a1449f0f1b50ddc6e1a0253800000000000000000000000000000000006508fbef44d36cdc6fb37b6324810ab2a1d94e39abdf09d530df34714168105e23a7d6f7fd9caf31f263b658f16b76000000000000000000000000000000000b5bb1802813f9f8a16991d41275ae6d18532e3dcd2eae091da7256aaddd501855e775b779959fcef2822685725cd43b00000000000000000000000000000000052146ee63ae277911fe491420651a96994a30c7d1b19bab32eded008a125369baed2ec5a963bfd863a83c29bc1afb23000000000000000000000000000000000a180d79335347a8be350a92491760c6bf1fd56604d4d99a1c49bcbe50b2d04b7cdde55b4aea8ddda4bfeb8e79ab6ce4146433a0738ab1b044e059f49a8af8d85546d0e34eaa0edf2b2a6ee466c0def80000000000000000000000000000000015dcdc17a9afbf88b54af22ed2168329bc43ba50d374c0507c790f37f9669d0af167328d50d322a827d45f39724d2b2600000000000000000000000000000000169b83f2567e921a4319fc03b2a7eeefd2aed79914bf608d9e0a54aa71b9cb3e09f1cbfbadaa520c0f77f547fd407ea50000000000000000000000000000000009b7a8ff8388c85a0fe3860f26b09b81b5dc51e00a8961fdba96eb462e1334e9e28a2cdc4be49dd8b96c548c64921718000000000000000000000000000000000243782436fe7cb20a3242a3a21402a43a2c4fcbe77cc7182ee3cc04f4795c269d8a64ddd25e89ba4fc796747b608092de0399ce1ed861c0ebce1d4e811ea0a3d87e21a54ae34e6b5e1284cbb94973680000000000000000000000000000000013ce6856b6df48e4c9e3fc0be0aca5b139e1b874de6ddc148c1c23a846d61e7a531cc889bab99706668a3b69d32b9160000000000000000000000000000000000a459676071c7f3065a6dd7632edd5842db34aeda8fa0e7d7a8ea29f842ebcf2c5fdfa74ee7685caa51481c4f46952240000000000000000000000000000000010c1d9ebf7bed9195cf0bfefad6ba45f1bd19a9a7d340b7c630b9953923efe4907bd75a3da066fe3d49d656f3ed91d2800000000000000000000000000000000039189de73332d5b5a160c296a195cb9d8a736cca23a92948d513da7e4fc46e1ed9c207e86751b3cf1310d8a7284877ec2b034594fa53a0951e2116db1b063345fa42dc8c870e1146f1b00f626dbcfdf00000000000000000000000000000000129821e97c65ad3801c011792f4c099e19919d7d03bf9fcba30b3735586bb7ead7d4f9bd10bc5f0e5cf1dae82d5651ef00000000000000000000000000000000038cfbe45bbdc494988a2dc72dea6a7e36652f5e5a2ecad41b4aeceec05dc4a389e54cd3aab349adbe32e65206eb481b000000000000000000000000000000000bbab53f2be2c471d6e9cbad719a73c00b582d0983e25e1969c0be1faa56b1dfa5b7b55797b3340cf8c7eabc560fac71000000000000000000000000000000000b0db19410e552a2f7889c2204a93c5cfc71c360329e3be3171e88fc7aa1e993a5d089c28b1a8f8fc80d93ba194c63ccc1e6d9c5f8911014f0f540211af5184d96fdfd47c03bf2d7bbbb3bf1a330017b0000000000000000000000000000000019320bb8d29b7b5a7130b87a39e87e271b96656b5a2749f13208520634009c26f9829401d3e21cee5a757782c6bbf9ca0000000000000000000000000000000009b37068d72463e72f3a89b9093c1b09f01770e647b5ff7daa50e0679bb76404cf7729d5575a39f5b9b3b371893967df0000000000000000000000000000000019ff29e41db50c736e12f62d76a28f4ca4f6b0f4f61aee00cc0e9dd4e5a75c0ca965b82698f704c604bb309aa5b457f100000000000000000000000000000000062c352a554dc4bb96b459378c21ec6446e15b868221b2fb745d31dece854bc281bc22827d84ea3b0fecfe5d156712ce6df5a133d3332e1f79f41201f8cb2c8c8d4d1ab0f640c4de6bd6e34884a77aa200000000000000000000000000000000021c52e82b0012537b57fd92fc276e8de842a59355cc15d69a52effcfaa7cc43dbda0c34e1b9af44c2db8e9356b9c71e000000000000000000000000000000000371a6da5dd39092b6108f631a0f4c4401464a109ea1e5d14e262c8a9577e1421d41734d2c3ed73645cc13ef3988e9e90000000000000000000000000000000004054159263ee60f6b1882ad7c376c738c7ed87e6b34dfb4be2fd7aa29ede414c2c6c3ff098c53f22a1c1cd836a6b0600000000000000000000000000000000012d7af6b57c688e1ce90e9f2796b0e525e775fcb6be65f5d2fbe3d1ce1e5d948dcb098c98d495a6e3dd813527b4635258e7219a9d431c597fe9700d43da8b545072f5a27a9f1af99053ac0494087dca1000000000000000000000000000000000e53128fa5392dbae9e40ab1ff0149d5b577d9d30dcb85eb5e4fcdc17c7daf2ff1d6fafd4a1aba88d2e7aeb45a01afc60000000000000000000000000000000012972781f214511e9b78d276767b1b64bfe5b43215c7680c0063b6974f703b209b2929470dbae16f9767a7cba5311fec000000000000000000000000000000000cf6b37c5a60851d03752f68eaeaf37ac67c661f644cf507c5458cb5404d0ce903c92ef66a657b25ce07e5cf5d956929000000000000000000000000000000001835f202705c8b984a4c7a6cd219c718ab27a96671574cf7cb618235d19e9046a15212e0da6233f15f18bbe192df29c38efb8a7a5e48d5f4a011a4aa0dbab22ede62c903414d005d507ea3d77bd47a6c000000000000000000000000000000000d01c6e8e34e646911391b012680f0dd8f4b8d77c10192ac09ce57b6524f0eb8c7f83ff8f26d856e0945d7a909eb790000000000000000000000000000000000070fca42e34dacce0051f9e26c7c0dc328fe652110976df6df77af04202831dd095715af1714b60a99f2177e86a3443d000000000000000000000000000000000063ba43df0155373df59b009a8083b9f62004327b16ad455037487c5b8325e7eaf57a4d05c533e284004be6de79ad1e000000000000000000000000000000000870c2e5a7d26ba54bf0d45ddf0a4c3011152dd12a5e01a80e42bc4dcc784c7ffdb66f9d6d69ac445c1d9aa29586245147f53e2c06664e1daffd7d9b114e12d4190d5d0fa2244d61a13da915c39b8d53000000000000000000000000000000000d84ca02ffb6d3cf6eb27a143ece73d5bf006ff61569f0eab00c5a512c5b46e1fc21e8031d1a578010c9582d75e1faa8000000000000000000000000000000000a41249cf01ecd23d06f6a3bb8573186fe47e5165ec0d447df62bfc236f4c203b4feb8e2a4785648af86646cfb0c4e32000000000000000000000000000000000244fa6caa86fd27e044145557697ea89baf718746711c8dde334a2c5ae3c73d7a0e04fed6289ddfaf26e47a9d26b09e0000000000000000000000000000000017db897060c0a8e3e5d8eca9970407b46dc2c2ca0c004d50a171450852f585268bfa8a379acd01b6d4685e04c0b8c106fb109d9a0a7b62c7c452bdf0a2853c4bf65e5439fdc83aedec8c0bf73a16b55800000000000000000000000000000000071e13963e20eb1dfb671aa4a090973e4a4b7ad3578f8630db8a865847be46c796e6f9e095a9ce558b93d702f8f8572a000000000000000000000000000000000dfc4c89ceaad07e3b4c35d96e8534122ae48421cd4443de478ddf9a8867ffdab279ad745e55c87b731afa7700bbdb110000000000000000000000000000000015dd6b0c26f6821177d0cfebb7f1481a971e7601fb24ea365a0c3127a5b1042eab69446de05b61cb6ac0576752f87aa900000000000000000000000000000000156326c52bc78c82f5cb4aec5de35e3c128c5561dc80da2cb24d68a7e912b1f2dac2078508fdd4ec38769102c082f0f74b0a931b894fbe61115fcf52be51d44afdcb96c94117c75adffcd8729b0a699a,000000000000000000000000000000000b537dc10a6f518122665f7d78326a4728a2889325e5be7da7e25e4752c680fd786cdaadfcc426343a9844efbbce8f2300000000000000000000000000000000085ba3a04aa8cea82b95dd994f5b3bdf0dcf63f13909aca2c2d61e4275a7ea22445c953b927ebc6b0987e98b553469d40000000000000000000000000000000019cec2e9fab640cc88073bd39e46cd571324904b1950fa8f626e2725936d80daacce2487f46ad23fa8af9c6ca0367fdb0000000000000000000000000000000007039a0e11cbb8bd940eaf4a192bb94ff8c6d6c79f775fa67821b5ba411641c09dfe9fac4cf45eb5fae52d2fc4beb6bf,293920, -000000000000000000000000000000000f73a297cd6444809aa11b0756167e71986ab31b52b57d3c0aac5637129b8702ff21ec649541e79644c27f0017c8ae3f0000000000000000000000000000000016f96d6ba02aab604dd918cc799cee61cda4c0164ed9f07d4932fc4ac3eeb92b1e6b40dd7b18cd8d26056b486e57ed290000000000000000000000000000000012156f3ca3aa1e79014dfd92fbb6c785cf0ee449a8920b89ad04355e0fb7c8ea804bbad082b4edc9abd3b24ab0df2b61000000000000000000000000000000000d51b5f62a6e70816d7671bcfc52f11bdac6221a23287286af78605b99ae8bd0c722e485bd0381b958a85f61e05de68368ce22e379ddb8352d12eb597c179c7089c6388542909876c69ee377b14054e7000000000000000000000000000000000acc52d0fca02c3228cd2e5202c4eda297b8227bf4e64308226bc487e5b64738efa4c07a3738397f90251ea9a1a9da29000000000000000000000000000000000b85b853826a28777a5767d5b1966ce12fa8999ceff5d6deab5c947fd19d19de9c103bb920bad615186d132ec22187320000000000000000000000000000000006b5a83827dc7b3580579ab7976a70ee160b712580919b6f5d4e180165e50f5a1698fa7cc63846eb1f5e6df955c3eefe0000000000000000000000000000000006c2957d8adc55931900145388583e5c2d5f6bd784e022702801c38534d2c92c6df9f95d022aa6d800e1e458eb7f313061529338195b665f1b80c4b95b7c3a26a7229884be1f4be9d49e1274a9ec3f810000000000000000000000000000000014e4c5991f9f2ee262019c1344a0843756157dc85aecb15718217a2fbe23fe0843992dcd3953ebe79acd85517acece0e00000000000000000000000000000000076a18fe710aca2875bc102f21782c9649f107684a4edcb0c4538f1a2890a2ae5b46a182d5470e620375327965b6d37700000000000000000000000000000000142a0fb19b28a034d326121458628356561e50cd3a471ee78bade0733597b8b90f647f5199d4b5b1ee6be4e1870bcd310000000000000000000000000000000018f8b5933848813cc2c1a0f079b095d565e7875ba6693eaa10967d496fb47257c9c674f301349dd8f2d22f8857f9d5ca44d740a72e6c8b5632314408022093618321c8c0a8cf2fcd9ebacbe43505a01c000000000000000000000000000000000db331d2b965dbc053b01a61e671d2ee6b04b072b6494e482f48f12221f23e3b1ccebf48046d92b4be2e4283c77f51380000000000000000000000000000000016704f3e1ce14f49df400592ce29627833ed1dbb91ae5f00779eef94fe9ab313c3e7c8da940085034e1a49158043599d000000000000000000000000000000001956d492f5764c6de0b8e9a716766c762620ebd3265a95b47a8ad2c0614c337692108800e22abbe321d77a6cc17f4b880000000000000000000000000000000017149865739d6aed0f2a4c3c71c2d02f8080d9339025b03f89a37a165fe6e5a4cbd489b5fc90bb2cc432e5baab213c8424872a78e340ccb077259aae65d6c448fe6bfb64daf4e2b6ecce2cc9525e35a700000000000000000000000000000000036804da102cce975f980ed5a69e0464241b5de87238f9892c77fc2b6e5ceb00d7a37a45b5520fce5f094f8b9510f49b00000000000000000000000000000000049da8b6c974f2d680a80d2007333f15702f1517d3dc11395662ca1db945c795bf64167840c4df0fda68a69e127b2d590000000000000000000000000000000000e94cc66f1ffb2112e37cbd5b4feb7d65032c2e57260504a42816aeac85648558f6997ef12028655103a8cb9de1297d000000000000000000000000000000000abf7703ddf6995d5c29124ba9a3f890854fe0622d547a4f24d6a60b036ec9e58f7ec2deca5a71e1fce2210cf810e2f901a1d84826bf78f493417a06a800d58dba688800026638316fcf9ae534436fc00000000000000000000000000000000008d22e456c643ce680f5ea14553a9c249a43d4f92d94135dfec85bc58967ec01135507bd8ac3954b5876c5bebcc1179800000000000000000000000000000000022029d4abec7fc9ab3bfddf2f462660bef7449c4093144d9b7d6f9e84f4f1c947855ca6e09bbb3bee4db096978ae0dd0000000000000000000000000000000014beddf6a3fbcd621e2a592e1c87952ed277163ebf390896f7c668944d6e0a026d3df74b0fc877ed560527a80b981d1e000000000000000000000000000000001414af918645ce0d4d1f670333fedf286b01213408019e327d3cb9321f06fae311b598c2f78bb578e85692e6cb787a52c5a3268a8ab5a12214b266aaa4eb562aa05dd19575a7f3ba2d549a25f1900cb800000000000000000000000000000000129f1e25d96b8c879710a81b727b31d27ce9887c245bf908a3768f3606870ca6bfa70dbf5135819d36582d55f230e94c000000000000000000000000000000000e91eaa33e7cacce4e1d6d0fe905c72221b534a72cd51e1de79a25ef0c06ab454a849a241c023b0f82aa07de28e35869000000000000000000000000000000001379e390f2f0f3636312465469b532d876529d58dda8b024b6b81d242af47b5720af4360d5a3172ad80fd9fd8a14ba2d000000000000000000000000000000000775992d5a8ae0640af845fae03dd0b2197699f413f90f6130d21db0dab042324094b36acda26ed86c65821d2d8a29d9e62a7b00d2be967df04ef56121c95c8736efa95e1faa0196e1f4485da82b3c3c000000000000000000000000000000000f5420156358ddbabf31fcc94678866f899e38747e79dba8ae280704c4b199a03eb423ceed18b5cba7e7ce84583c84a0000000000000000000000000000000001127669ef3ba3785a859aa4e942e8fc3181f2703b0ece6ddbee8830d7ffbfe498794f1ca2e67c3ad39ebd33e838dbc5300000000000000000000000000000000138113386846310db8e21fb8bfe40035cd89e51736b491d5f2d3cf5672e6836c25f62eab80f25ab49d16dbb83796aa5d000000000000000000000000000000001711d74ef4995b473239a574fb8ea6edc6eb7a88793a093df4652da240d069c5bf9249b58e9b1e11f7d6619cdc28a5787a883bf845d1ed04e0664d814cf0b49cf8c3e8b8594ae5d2834c753851ed7803000000000000000000000000000000000d32ccc6598af8156f1c5b35e69e7c7f57f9fe18748510605a2a81b4ee09882bf3fb26abf50206cd57c77924ebeda8010000000000000000000000000000000009043d364e0637c60223f9a5db8c50e983746fdf4c9f7986d27f5f4f3a6df487592ea42078f14efcb3eb1b7e81d058eb000000000000000000000000000000000233495c4961e71cffc2abcde4007c0d587687aea905f3ac5758d0f8d9020197adb6f9d7b86a542b8efffb05dce997130000000000000000000000000000000015b084e773e66ab1459825b6e6dba055a96e4dc1d94ac0b640e906e0a9f12d2124a58537c458e6e1b571311b93acc26c0f474e8f4051c4e91124c14895fe9e2516b315d805b79013caf830524fce8880000000000000000000000000000000000e4b859c679a90c03ea4d4b0b3d38211f685db053aede0f7f359f712e1ae808185758546877502d57200da2c2137f37100000000000000000000000000000000173b24ca19436b51aae22838674c41c752536eada3197de6efc98303eceb3e6e8e47ee6679e61e3cb5c8c734c96c98720000000000000000000000000000000005232b8c97a4860a23999d6ed6d173d300ed50b77c7b3ceb4e8407d9d6877a6004e2f76c553bf458b7cfd8d1e6fd364e0000000000000000000000000000000018a115201e3f4eb308c16656b3ca0635e6284169cee3f28101903ce1cab0659c3d83a449918df6e58e8af2e001036b8d9b3a5790750825ab75ab7422f833c671b95c6c58619189db66a6215ce907381c000000000000000000000000000000000131232788aa3038a6b8a055a896af4f8129e3dd3397dfd90ce86b3e09a775e5b5e19f4387f4c02200a36bc2a1e09d98000000000000000000000000000000000eb8cc0455cbaae97dfd05c1246d3d5ee58c286d263184ae342f5c0ef432355a574bb9fb8ec67634f999b6d1419f2b6900000000000000000000000000000000188b8a85a6b255408f074b3cab66b95e0e1a1b5b8965034246dcc196f2bb84aca3a78907409826370bd65cd4c4d0bcf30000000000000000000000000000000009603984f6d9876e9c235621fa817efe45727fd8c4f76abb7b0796ae721701161b39ff7cab4c57850014e7f1750954ab6607a48ba3fa5c033a1ef90260ada14ee50c95e5167bf801ddbd3acb77c3b3880000000000000000000000000000000009003b42c08b5c7d3ee9f6abb96e08e6f537da25cd0cf7eb85a49067746c03566e133b54153380286ef5725db5b41058000000000000000000000000000000000f09b7b754c255e0e3b8435ade64d6960285759495659dfdb9b117806397baf8d3c87e30bee02c9e1b22fa3efcc58f300000000000000000000000000000000003582c08a8de4bbd20ebfa833517a75682618fba2702b6c71a4785f70dbdede4e86ad8e04aae1f50a6bb75842ab74aea000000000000000000000000000000000ec013f22e64a4d4fb6f964e8319feb1ddbcfb71329186545d9b9d7f97d1f6a56c8aad03d20e9c30966ca932e1f2bc67030db724eadd2f487d31dd4354b5c0321a7983aead21759807bd893217c4d40500000000000000000000000000000000025809fb06c8a31f31ca5b4a5c795bc93355c78d9a2a4c1d707e32ff2a71d94cc1bf7b709cd5d6a183cb05fb6b5f360c00000000000000000000000000000000127bd8c9ee6388905ffe59bb0fec0e42b4aa44be74e5961dc2353e474baabfea86c41c6173db413ee28681a6bfd3ccbc00000000000000000000000000000000181f40dd8581b9adb2981dbcae27c7e906138569ff41a833ed3e6ee4fb0baccf2ccbe5b28ae2ff8e08c4f534116b58c40000000000000000000000000000000005cdd822cb47f35f31e0cbc26f6c957d51c6880369af94fd84daa1f1ca95e41e240b910f031585842fd2dfb170d618aa88e71d0be8fd050f6dbb8b2fb3ae2a9e593bef7a5163255aabeb07282e8793e30000000000000000000000000000000004a06984a3916820368076ab8cad6ffffded2cf1e67ac33f539ea8fc7a79580c1969e55b2a2fe3b31de912d6606c20780000000000000000000000000000000008a1152a581b6fad2a23aa8b0b51cbe523e701193207c896d08b99a672dc047498e565a568b79f8f9188767ba95212be0000000000000000000000000000000003539e82e5b88ef660b6593fdfd9591ec23e7109642f4aea0570f1f8f8e00822d2af277632ba74910459535b35ad47120000000000000000000000000000000015d3441f621c7e6922c489e474f80ebeefbef66cc59e4350b6f803e409034b7f498be2dedc97d902590fc1e296fe983c26989184bb87a586b8752733f9ce9ea06422c6a898f0f402cbcf760a7a21c95c000000000000000000000000000000000f775e13276c2e32dfde955009422557f332fb42dd9ccc3246d2b080e3ec44d910aa734478899698a9b04f6fb1a8f922000000000000000000000000000000000460ee4df6dd0184bcdae6d53cb66967c2213fa878a829c3196664f8d594ca6d60bb2a56f93bda3b0d2e6aac0a1a222d000000000000000000000000000000000fc9bf81d4cc80ba4e4df7307f976c2ec1ea2415df3c263cc970583824cd83703aa994daaa6e5c20450da2ba90a242830000000000000000000000000000000011f08ecbda9a192b232e8330ccbccb16a26bcf4791707f2cf52c2e11a8b3993221666563a772d82f4665804275b03b613d1dd9cc44b30a4623a4d14861688cb678bbb8b2f8ae3ba140f60e64c05514b100000000000000000000000000000000027fe7ca0fdf1cab9a52e304e55350195492abecce4289b0f1c02235412bb012803e7eb59e23c665ea86dd4f74c35c440000000000000000000000000000000011301ecfc78ada92885bcba8af75da6cbcb448e0c49511f3ea306f4ab944f5bc114e72f473cdadee2d0e84021905c5300000000000000000000000000000000010eea529fd3162ad7b49638a70f6f2c26a6844251b2c2f9f8ba54cd334914e84e5a1ba9c7b4e7a8b9cff1a909db78bc8000000000000000000000000000000000b8a6235a7310d52fc8050bcc484e6ecf299099e193f91bea9db31fae71fbd14978984a9e6de10939d0fbba96314b0a55639d80f55e24e05e3d943340e324f6738a593a915a6bddb40f01bf12f73daef,000000000000000000000000000000000de312093622aabdc7523cd72f568060f4236c7287d61c3372bf81d9bfebfda2795c3182d508f0268d8f445f6ea0a5f3000000000000000000000000000000000b027f117583406916a8f139d47227bbea28502ed0df91cf0841345435376c944a587c3b4bd60f8ae0be7c7bad1c8199000000000000000000000000000000000e9a7b96136b26b0044b11288d35969c17146241aa529e581a8fcf000c33fcfff2dfe1e55c0fb63f6032d0b6b0cf81180000000000000000000000000000000002a442e740ee390d87ec657fc218b76adad7f6a766cbe8f34f4824ecd1587deb3706af77a95c1d5f8e79eab1dc482c45,293920, -000000000000000000000000000000000f54bcf1637d03854cc2b785e52bde25de7e45308048ed8ec0169069c2124871782bd9d26471014d039c9aa022e1a99d00000000000000000000000000000000106698139b096a5a79d43321ea64adb783011f04e5779625c9f77e5c390b46ef0d249387e978e64529bba2db8d7aef2f000000000000000000000000000000001668d5261a4ba37d79c76f44eae9ce2aa3e216c5fbf6cd2e90c6a73cebd8b59600303afce70de3e83a08c20de4609b100000000000000000000000000000000004b1b122cb55e688f8297913b84d466c6f3d99c09f4b039660238c8bcd0b7f6977851a6ea4b1deb01346db06d75180c142fe1e5b3c0245e5cfaa1ee8dd8ccc4ea8878ce2272d152fd8b24032297ac01800000000000000000000000000000000192a28dbc40d5ceee4d33b5c2778cacf8c3ed7d3227e7ea0d6fbaa7cd4a81134b63415f4f1960656b1fed15023ce3a4400000000000000000000000000000000138f296c45594a930b949756d0ae14dc9a720bb2bd9e93c7895268121a086a9d55c10135962a172c02da1eabfcb8caa20000000000000000000000000000000001605ef8182fa13a09a6b7661472296af2b0fdcfd7b051e7cf1d9e6d7c7f4ad9521d7732733399bfd5d09a088f25d215000000000000000000000000000000001928f2e5d47d7273e035114cbdeabaca724409a56056b4e95a4ca3b2222716b3a5368da3ed406d73f43e9571d1e04902253bdc5565b6ebc219a75ab74dc5ffd304c94e67160389f87111899ac07a71b70000000000000000000000000000000009b35f132a903579d82cae6a321c1ec7fb0281c3e82e9af05c3b2830ecb4a941da5b1637c1bf0fe9a39fcc9ceb0d09d8000000000000000000000000000000000eef9c0846064c866ae07b3709091b8bd48bb6b20f995b44fb49e030b5cb6d78b7f8201704b53697190a5e36e9a4541c000000000000000000000000000000000a98a5d0d5640d6399a3580036f0e5cd693a7cfaa26438a00767d5ffc0777b83c516316d9cd4597cf8601544038f4d9a000000000000000000000000000000000e59541068a62f105a0d26a5f79fa5fa8b41b2211f1fe674d84dd853663962d64a7f70e785b51ac3cc07267c73400fe6acbf64f93f6f85805517ddf0358ecfea1fd58a3666b8dd9d3773a28590fb8a13000000000000000000000000000000000157f58b1c7152a7f931bccd9a79073967ec28855a6d74fb8727f59c5e3728fbf07a5032dccb28eb8d8b24229f2dc1880000000000000000000000000000000019f41bbbb853edc1fe3ee82f901e613107dd4ba1d880284ee95a2c4cfb2220ec1408f8bff14defe59775136bc75b4a1f0000000000000000000000000000000015538789157505a0798aa36fdd171e0bb14bdac75339b35805807c18bf9175d877360748f97a8570754af0e28e89df660000000000000000000000000000000010500aaa99216aa979acd66c5b0cea2a6a973f1cd10c412e823c61cb897bce54d783a6c0acee22cf9052166a4bb5adb8d9d3f97893eb4f14f21f68110f612a444815fbf2f76b8399ba6045c8a44270df000000000000000000000000000000000439729e13e6a9b5baafdaac65783ce79a5972791610a333224e61104d15c746d7cf8350e619f0f72cb73635f6795c5f00000000000000000000000000000000092e3c976a4a5424b09e50e6513a9e1f427356ce161e742be31f0e589e9ff862460d41281f0bb2d27b1837a70a5938fc000000000000000000000000000000000e0e51e92ac3cabfd999cd72b67cfc488e150b11b18f9a31b1c2338fd4f2c58937521b5a107752c342e67666b99fc42500000000000000000000000000000000023d8884aa3f556e98e006960293230ac966ad18f3f715e6ab31a6bf0872c04e6f115fb1608cd87ffb369ff31012a11705fb554531f53b8cef8d93566df80878baa96f92bb54aec19445980b1a1f6c34000000000000000000000000000000000be33bc145611afdbadc636e9d7cb7e3a9c92c32f6944a2b7b5f44c248a0754c174e3286ad307fcdb2ea02a3578aa588000000000000000000000000000000000457de1fa8642d302065319b1d32009c64e7d941fb43d1b3cf455248664b1db516379df87aee05a651c132eab8aaccb5000000000000000000000000000000000a711f3bf1bda60ca49271e8a3143330cf924328d3ac6f7a802c15be1d7413e300f398274f338e6bfd0225cd8ba25fff000000000000000000000000000000000a786c5c7b4f1701e292aaad9b2e47bb883409aae0c44ae813ba48f401f4e2146ea0b1d85f2ce862b6ac9ad3015d4b14d79ba2c485f0aa0e35212fd7fecf970258903bd2427c4c8b97c2c425ee1190990000000000000000000000000000000007d03697e195a6b714fc9785b49e54e219694250cf5fe77553434eeced15422de3985f8c736996c1763d4b9248a7a7e00000000000000000000000000000000015841a70a168d2f356a8ad929e2d1433b782351f4833c51b50f3a1af48a85468c2ec02699550d21bd919203df73abeeb00000000000000000000000000000000170902520080c46faae2bf35de396d56921bd0279fc889f0187adbabb9ae52b849269d8097d5b3f331dd5a817f9b2ff40000000000000000000000000000000016846a000f037eaf5953b7c4b477e441ca4fa738895aa24dfb0ef01a4c8fc21a318d40a9424e151380084578ca413b3344c7017258bb979cc9bb8acbd3a3e62eac7aa152db46cd7398ef07edd031e4f60000000000000000000000000000000001a50509bfb12040c0271b231c566d13510e6ba84448e59685f5bfbf5b008fdc64cd5e9456beabd23ac011b071e3a5fc0000000000000000000000000000000014a964c9faf1752170ca40cff1b9b4fa17f8d2b56a4c4bd7ffabb65798771cd624ba61ee43160e70731fb9b07af8ecc2000000000000000000000000000000001822ceaae7bd0a734f57b67e4834cfb00a6b415459d81c7d380a2e5b5c795eb1b6d63ddffb1131cdfdf0d76852c75a70000000000000000000000000000000000c5a1575b30e5470151ba055f577a0ea49cff869614c50194829e53a3e1a95847fa387a0f45d537cabef3a5925e61c432583e821328ae90a7db16b20525228e8d915bc8d46a642cb0a06dfb64168cf1c0000000000000000000000000000000018cab86a0d70fa30b4df3e05a91eef57f6505cbe4bb7284de56d420ef3bf315be9249eedfae92561c643bac2c92301ee00000000000000000000000000000000098ca598ccdffa9bc9d464d51b46ed8a8f22a87ef408cfa45fa7f78ae2dcb9f861d9d6a571f6fa702a71e783ee3395cb000000000000000000000000000000000c073c0a323c3051c302c0558463a5c030539d74b440fdcb16b42ad5ec097e10c16bd9a651d149dd719fb1fb865420a9000000000000000000000000000000000164e622bfb8ecd5eaf691abad9db38ccc64ff0fa1784d26db8c8fbebc929bc6d4dd471321e01233d55fb4a9661780b5506f22d323a740553d6107e651c192c1dc6e0a0161a82351f125f08c77e53fdb000000000000000000000000000000000fa48147388181e8d0033004118848c50c6425f2e5f91945a17abcff4d11928d298c092d60184e75e67c7ddb9eaa8255000000000000000000000000000000000c535bc54df050c1ba8d858a346d3a644e03fe24873b7dc3e23518d44b06fcb3f52b4be6f11d3b66f0180a0a95dddf680000000000000000000000000000000015e279a2893c205dadc8e1cdebd9c85454cd4b5d7537f984c8f9d451f8316620279357e218fef87339f1728fa317fad5000000000000000000000000000000000316e343ba68c8a762f4c8f2a5c20f16abc4a7a8365556c1625df832219670619b6dc70727e9bd9a64ed491dc22cb9d57f1bc0e1ebff8f935330c35573f9fc3b900606da9cca9a36b425977af47c7ca60000000000000000000000000000000011dc72100cdf676e41f21015fa7c57897da8260609467ffd38c17868a4dcd2bd5d4d72e89cd0db2de83618222ea3b5cd0000000000000000000000000000000007e074f73287faf304f618478566b91c8e191b229ab40743081342e676be09c2523681cf7ca6f7a396f8589a4ae18a6d000000000000000000000000000000000ff753a16c16bf0dd1de9fa9316694214aea6f99b81f66b6bffd58837c00d7f5632ed5f8f4cdf32ec59c29241ed5e28b000000000000000000000000000000000851e26675814612bcfa639fe567633e1960578a0c8d2e6568418f633eebc109e6c8af97e77bb28ddd47c6bba8a7ba724429b85fae16200da6eb8f62e95e027c24aa6ee2a145f6ef225139f29aaca29c0000000000000000000000000000000009eb2f172db0fe9ac0332381d929fa200a97047f6e732570d23fe27f5ea3013fdc52fd0b5ee74a4387af44647b75f956000000000000000000000000000000001355f8e1cf45443855f2d62dba0fe45b2bfc4e0d06aa7aec7e4f7f9c4e25b33d9c46a01c224517bac9a1390a9806ed4f00000000000000000000000000000000179d47a62a5c847f47341b1ba58f2c3b073c5282f925f57efed1fc43db04185955075255e4e4f6c209757ddae59101dd000000000000000000000000000000000ef5f74d4b13754ceb3b468879f1a8befb8bbbdbb143eceabf2dc8e68fe6cc8e1ea4f3eca1b23a1175c9f5f5c4c20d3454a852baf21df9f4ec8d711a48e6ffb36be8c09c8c60eaa090876236b2eae37a0000000000000000000000000000000005b70a4d5b91b85971aef26b1521e12904b7ad224f25e31ec6ef59856cc702043a3eb975bf21dc8e4fc55171a3865bbd0000000000000000000000000000000007cf7c3e75a837545b53ca3e175a275dc6fe42fb88678aad45910d150ea9c6c94eba615429540348bb2ba8efacbb20e60000000000000000000000000000000002eacb469f5f8ee6c9f557a6ddcc854e955c5b9203b4ca5dd2e097d3e021479e13629863eb5ff17db46a17d3b0227f58000000000000000000000000000000000905e66f3a051b304b110a8682169fa749ba0de7763d3af7edc3e40f2d22ce7b6aa00cd06d2c82d74f3a9709d955f44e13814a3c6386b19f7b93c2c4e0eb1568e8bd3f0012a1ae1357b127c33808aa0400000000000000000000000000000000060ac9ce51426d360eff0d911d9f97a86494340bc5c5ba31ef146b55ad3633ec57a700f04b0cb9d4e91e13c2cc5e68a8000000000000000000000000000000000df205ed85e27c25ce27270384d7c3e58c4e0a9f214d74cddfbc7904eb3115e7bf204375df7558c3e65f7a81a942c5160000000000000000000000000000000007a220d42ca8906013479442d7204457b3ff37c9ee70d64f9f6858ba788b7fc13b71d33ad527c6fc673ad8940b0f01cc000000000000000000000000000000000ad481ef549de13b174d82fe88fa57b7e31ecd8999bcdb0c7a8735ab619a13b1e684b9473f0c59c734567cc08c76ecd6aba0fb0440b2461ef64af6ec5f15db381714fce1da6e03ca962cfc94bba26d74000000000000000000000000000000000366f604228e2dff2348a462c56e0043037d1b415ffaf155e72c559d185c6b0a0d125585d060f159a8cdad959af631f5000000000000000000000000000000000f69e829a0995914ac122299d4424b4e2e120fa4913939d2f18f9d1496e7255d00ff0829c20521ef47bb0dee06c28dab000000000000000000000000000000000a3efb4a376281a60f5246d8fc10bc23cbb9cb71037f8f57271a9b01f5e0340a562f9acf0e9a95b8c65ab7a5cd95520a0000000000000000000000000000000004a4ec86e2b04bcb35c7840d85cd1dfaa88e17ffb557ac591640ed8e563cac891793b92e349a7903c6c1f88d26a01c88c01749cac36dbbdba5662687fd1ea5391ef9d0bbd24e05bb5904a20fa6a1e11e000000000000000000000000000000000f5bcc27c243ef65dfbfc0de6d431706ab20d6cf6408ca989a2bc1c52b78ab63de6f58b70bfcaf6878a2746f249b6b160000000000000000000000000000000016a4c9e8ad0634e8afa8606a1a7bd1d8cc0815dfc6906b6e6446e0ceddba4a4a2df979d27cd07b8982a12550bc700fce00000000000000000000000000000000051f8d972362caf0a8a39045bb468112f2e73afa392079f8a4dc4c3a3cbb8dc224c21b6633a5ffbad08796ba2f8df44b000000000000000000000000000000001825aeffda04705ded9c702ba30d24b9fe8eb7cb106ee5d4e4ba029dcb57bc42c74e74e92ef8360cf130590b838645429680fbd6e6c7b1b14b000d3d18bf93242c74662ef108d711d85d8d442e415ffd,000000000000000000000000000000000d0ab61b29ddea1aee0ca4e81b5369f37cf45be383f64ba0b1a5a74b790d7264016ee671959444c94b8e6291c5158ea90000000000000000000000000000000000152bf3709c56b3add8e3396d17abcfebbcfeb230529ea8144d6a120a0a6aa83cb284e40ffb9fd9a96f8a2f7244212400000000000000000000000000000000041f516a7cb2a7137746d028b0739c79ffd8f7535f20ba3728ede32504fe058baaf684cc7677967aa46777818b1fb6630000000000000000000000000000000009f1035729c55cf6ee090983a54d8c0574bf96342901f471a2e5380f11f235a075b0e157c38c456b6eeeaa10b87d3afe,293920, -0000000000000000000000000000000011ada4731ae7df493e405383603a8d79ef77f3fd14fe7b8bd9d2afe068998cb712e84927d5e6ea6e94d7f10270fd193b0000000000000000000000000000000008a14eddf88826dc3be792a0c1f7395efdf91454cec7e26c89f6beda37b194b706dbdde8745129e821b6f4b4ea6118490000000000000000000000000000000011c29513e8a826e6b3eefaa20ad841605d04b813cca282fe02dca0f588b9a579b2195b0b080cb6d12c1a7881008117f8000000000000000000000000000000000689c67d05ca379367fec99439e3806f827218ffaae995bf38dd8e2919fb2e751f426525cc2c6ead3b9aff2e377fc99e1ddff10527bb64de6ee2e3ab4959ebef9e7a6964b7482f9fae396b2b9b0cff9e000000000000000000000000000000000dd683a8e4ad54b1a95826a3000750c6e3cb250ab5d6add63c21b182d736b220d917d4e70044ec7101c3bf8ac620e1dd000000000000000000000000000000000f3e411cc6800b304fda1373ffa60c7718e20bf3e2e5f9784a81b47e398888b366e1f04f48f5aa070a661b5e2148d4fa000000000000000000000000000000000b0f8d0b695e000158ba80881a9256ed9dda5a7f53b550bf3b5c67ab160060fcbf5ce07fe38253ce037abedf4c6f08d1000000000000000000000000000000000bb92d407c457e9ea7b9851770d2743758e162dc9cdff2dd54b8271c046f642729cd2f10576013adac84a46d38623b932943fa2957d267019309f4fe5b6725379d893dcc270ff7f35b3811ad1d8d12b100000000000000000000000000000000023e880685aa69b3480bf2b7f2aed1181e094322da9e79c9263d50a49ba4fca713740bdb55886fc81c81a51045b35139000000000000000000000000000000001707049fb8b7ad278be2949b9eae2e28bde9de1d9eb964eae582541c2d7a8afc4c1489624a0919047a167028b8c77e3c00000000000000000000000000000000062dbb2bfce2f67c32b87ec2fa01ebf7deddfcbeda2fcf0ef094b1be77b7411f657e745350b6d2da16fc83a96f6f20e500000000000000000000000000000000062daeba038c7bc379f56ac371745b91fdfd5b4cbbe50d9619bf1d077f3cde966f81f9b851ebd206f2609a780b6dbd681551a3c2d0391fd8dedade892e8e2171e652d2a9b52f2288451c55f77fac788a000000000000000000000000000000000b553826dd9e2252c9da74c2bf1bf850df3f9c37439859f93df3fbceb7cca4fd949dcaa7fff31c9e06f41e51ae0b30bc00000000000000000000000000000000187810711ea5911a437a62e2ca483983bf2535ff9301a1cfe1b4d41902ef689f8d86f817a2a7c77128e4ce1ef6b037d60000000000000000000000000000000010170cf5f2ce08211cfc41bf54cfaa16584f833f7b97b2f6bc436eecc56ef44463690ea1f5c8c2a8f69d93a25206282b0000000000000000000000000000000001e627a68dbab6b0d05c85e49b966a769461ec38c38fd94992839bd0d46e06410fa7a48d418d65a8285f7852e8af4b318eb2fa94a5c97c28d95008dd1fe60137b34c2e763292d1b86993c02790b8c91f0000000000000000000000000000000011ebe2edc3de58a57aa9ab4d6626d7b93235ed24efc3d75c1ecae376c00beffc5e89ec509d243f693d327f7a4551921f00000000000000000000000000000000088ca2fe0651e4d8f3958454640a58ea1cdd804bfd2700bb1bb8e26ac50f2d7fc8c292f94b0bccef5735c4548025735400000000000000000000000000000000154936de8932279cd39ae803a5d814864953f647a5334bad958222de765250e4bc847e02979689dc9cfe1993486b5750000000000000000000000000000000000c7ce07c9746c6d72dae11e243acbe12dc23423f870f3130b244eef34524d547fe0b2c4b704ecb6b2e6c32f5675ce67ff72ae1def6c988f9242bff0e683b8d2a5c1aecfd6ebb9442131ec5b5b825d0f600000000000000000000000000000000031ea855125d75321a2a86a93e72fb3869dede7531dbcc1cb07ea2a352f3c6cd913275d0d43ccc370f4539f668f205f50000000000000000000000000000000006c4cadb11361f164f5899c6b57c0c6d8af365d902f4575c9d2d14dfd880501ce9ce218544b44bf07f0f04ed68e8f315000000000000000000000000000000000131332638026fd25b1a849c984f9dedd71e64fb52a61968666ba80238673077ac00b9e09817426ceac8c308f475303c000000000000000000000000000000000c7634af796e7aea4d4d83c9972fc822dad951d2473210ad82706ae0aa023ea85c1c467bdda68881094ad2a4f54cb33f331451748146f0564ab0d91b09db87e8a6ba8b14f8329bc041911616195f9fc0000000000000000000000000000000000fcdbf0083065e13deee2020bb6e47cb9e482df3768ce569f1f7c0e1c6083c97d9f08444e67857c2dce40e4a7b8d50cf00000000000000000000000000000000010f246e8ffccc2e752049f638617e122773a6f10220cdcc0603d24f1a94ca7c100f8ee2d9bc7c0a931fa0385eee456f000000000000000000000000000000000f8b68941df75cac3d4b6b3bee43fb357c8f4e56309d8509fdc62620a085d7ee58f52c7dff28525a449cabfd3b7ab3dc00000000000000000000000000000000019f934ef0c7c40786b073d38cb3e4623544cad59cb63440d4a6e76944d491f6b982e3a5e84124996634687d4618418316d298bf591bd927aee24a37c5ba508c3bc121f5150fcd1a70c1f27a79da7d73000000000000000000000000000000000c0208c1f3653fb3a5e2acbbb42f2598b22db1a714d616ee6bb501c3338e80db34d517c7086d43ddc77e0134dc5a4f290000000000000000000000000000000000a528245342e44e36f8e02e7259749e63ecfb38cb0609075e871701f2b3bb0765277b78d28cc3ecb7aa8c9e3b27eaf10000000000000000000000000000000010446583a905864064400f9ef168a122d179d46a058525c9be8a65a5d2ac5e967d51185d4964f81a5571123717210d050000000000000000000000000000000017da91a1d0358271b11a0aa524341ba1ee8c31bed15efc4c9183d60c6e1842ec4383070a09914fda991a63d55efa8f2156be810c3fa86e35bc935fc2b27971c9c41d03c8ab7b6c8869db90b6e0986ef400000000000000000000000000000000176c64efbfc9958b9c8e71b55e9fdf525d4e5a0265ff01ba95bcd5c6093bd063726f8e277d00b138fa4d8c8f80afc4e200000000000000000000000000000000183eaa6c3c605828852ab5e8a9432bcb87411dd18d574cc2491f1a280e7a267ff9ccc80b06c22e95107a72f22ba2fafc0000000000000000000000000000000013319d3a8564ffcd6fc7accdded740127ef205e8299b390d21e96b2609cbb463569c878f36191d43927868b06dcb912b0000000000000000000000000000000000fbde0ad8e89f5458007ef6ba0f01d0aba04217e06745a5571eedaf544443150f59117b56937f533b4974e5d57c41cbaea4445926775a6baffb4dbeb249dfe3b3e0c29f2a579927f540d8f6451553ef000000000000000000000000000000000c044a5116e175ca1d1ae59d400de24e4f47132251b4b3dccdf458623c36b4d3d83abc644a2247ac4d0e3f195d12e7b000000000000000000000000000000000048dff6bf65f158b19b992167ff8adb5c858a154bd68bf0c84e41351bf47a8f870cc735d1be5d9afc62bbcda2fcdb1c20000000000000000000000000000000008c5539746d2610eea22e79b3fe5b33a47fd3bf9991d34c6f9d824a46458480b735c0051d7b4e4909fdb1f2a1a4e4b3a000000000000000000000000000000001936558ac97acd903a29d07c4aea399227ea13fd6dea820813c5519412c157e1a477fcfbab60a787c6b3834eac4522889ee0e58d08779add74b68dd75e82df172b719cb5a772b0bbb34d3401b9f212ea0000000000000000000000000000000017d978d60fc89b0429c1a6424231fe9274cedad5d78d9c4ac5aa2dd5e70e8238a0bb1904bb4b6ee5de5cd1ac514c62a8000000000000000000000000000000000d4ce85a95dbc40f405f4e7ebf9121cdcd22766737c39618ad0fb3e10a6e53be1faceaa96073b2a877ab808483ec9b6f0000000000000000000000000000000016c61599ae4da787fa6db233fc28f5c56f7133d403901800ab5fa19d058fb27ecb34ca2e56ffa7628ed004c9e62092700000000000000000000000000000000001e64e4adfdafbb423b1b9f8973738c690713911f68f658d234e57dc35b9554e0f7ba345dd7920b429a12b9c74775222773d07cb9d20744a2c3ac88082a8d6606acdc892666753793a2b8bb81116cc6d000000000000000000000000000000000908ebe27a1bdf0b9e56325c00ea3814527005793ea97eafec541c01cf2d7c909d2521a5fd475589a31e297cecfd5e7000000000000000000000000000000000017e3c40c60cd369ce5a90f6c4aff14896cf73fe06432e71940bd8086e36c2353d6bf9dd414bcf92889887e2d49fbbf5000000000000000000000000000000000ded856e5b2b139487b3816351584f06582a933af2bd4573a89aab0a41af01ec1cb928a7d8035228302032d399bc7caa000000000000000000000000000000000833b77c5d5c98ad95a144c0f167fd3bd62b03f4ad721561ed1d84c7137dcb19521f781bdd3ddc22afdd52c75146e101f6bb1445e9146b117bd0c95b009fba670a5391874dd314cefc884bdb0a4eba680000000000000000000000000000000005c6f28c5ebd981fff3aacd70eb18f134bffdc8507d1a3aa153e5787b68fba7f4a94c43045d2676aaa992754783ae87800000000000000000000000000000000148ff39e8062bd488accfead42a684f781c4ee579af6204b5b8dabad9022b029139b1f3670fc270710ced9a53253850c000000000000000000000000000000000ff50eca1a92f123e2534b3289f37ffd5d4e05f7678017ac20e35c2deca054dbe376c5529cddb5e58973f5c60914f251000000000000000000000000000000000b58298ba9496fe32891f4c1cff25395ac5a447205cedaadda4dcb929260ee55781916ef5e4e39793fa2831142111226d4158de4e23d793ba77c24a70f0ad07314927fff34361b0d74b25e8922512d7a00000000000000000000000000000000184d156f881f7d10d2f196b7599db85ee826c9c95383978ed68918756f642a2ed1c951503251b0778dcc39598d79fc8a000000000000000000000000000000000952168761380e8fc90a4966e94b8d2b88a784f6e607c99d9af1aa902506f59d6879153339fdb7b8acda178b9bce4ef90000000000000000000000000000000009997621d4e17c76b7798ef2f99d3c0a7519cce278cf718789cd8227b2b1459af7fbbc93078aa0aa361167b1d1c9363600000000000000000000000000000000005369eb3a77d2e26f9907a2d930f39dbb87634346cf10525733aac8ea10eb918d4043d2a05ff8e80b9c69a670e17f15c629ef41d5a2ce49fd81930406f19e760a47074e159ce372dd67e7ea46ad706b0000000000000000000000000000000019bdb390c66f7d28cfaa91bcb34c5c55bf93a9f2345ea396f18ed33ff2221a39cf68c5514fe091f7882e82470efb1fee0000000000000000000000000000000002d0b48d2c0377b0dffca247b7625f9901f86e2161626b4154bc25d6c643a48e9addd260298bedaa80e42caa5b9fc5b10000000000000000000000000000000018a2b0a760652e546eeb42e857ca48f59741eed91822c17692e9c41358b213c82537c9c6898713a13a241cca627a7dc400000000000000000000000000000000079c02f41fca45a56d9d8e305141b4fe8f98d102197e7864065d342e6b07f65b62632e0c12660f37de4d698c0df3d0f3c718651715ab786b4855092ed21be41b499b7824d0bcf68ad31b31ee4cb730d5000000000000000000000000000000000c0448fd4ebe9b5615653336fe0a618fa281b0fd7d72a8f956a5fde84f7d356b6be853bf823436bc0b61a603636db9ef000000000000000000000000000000000dc4f2b4d810c4290e263098576cac393fce137cc901b3be23507cecbda7d86d18022cf8e1a7df4b1298520ae5c9314c000000000000000000000000000000000a39413967b558dd8a6b2bed972687d984fb9abd0662a266680f8c90f1897e2aca1ba37b41d7d3fd47406bc5fa3c5b7f0000000000000000000000000000000000550fcbe5bb75afdd8d5f387798a8e83a8dbb6da4918c24eb2e5d2d8acd3512f6649a4ac9c8d3e6794e6f4f8a87687bc685a2872c4980518fe60c61e2276ef53c007166f7eceb355b4cd533f42c00b7,000000000000000000000000000000001654e242002aafa89c6fdb9e8fe2c197ad2f8aad11868568dd39d68ca35919f94308a80303655bc83fd130de6f9723a900000000000000000000000000000000062b5a064840a5a28b4991ae949f9508586447ad5e8c463593503c0e5857c5233b7ce7ac03e555c2675f2e320e8cee6a0000000000000000000000000000000017d65fbd7caa69629f66be8b201f53baee5ef2957a3c04fe384ae82959105342b52483eba6bcc1442763c677f515f6cf0000000000000000000000000000000002ef8f8ed1114cc9d299e59003c61d62edf8971d65b1b621779bd7b270c4123eb629f56dfa2e2723501588a0caf1847c,293920, -000000000000000000000000000000001392409b92282bccbdaa0268e1173e60911754eb3cdc28a52e93f4d82ec99026f314dfdc59b39a4f988100f9c30cbd1e0000000000000000000000000000000016b3c555d5c196551ba715c6c334a668bcae80f5a17f038038d35dce34843f79968a90e2102f0faa22a93d3240b58d490000000000000000000000000000000002daf83727fdf45dcc1a15adf47de3f8a1724cf4d34116f52106a9e6b22dc24a288e89b940cc57e5a6bb87ee70f680a5000000000000000000000000000000000446009fa3555e4a056a820efa7da52117c15eb105af57985d8e9b33b0b22fde6aef9bad30480c2b8c1246519795f61fc067ecd54e9ef59996493f846ecca63bbd7ec28da586f0b8d41bfdc6d97a35cb00000000000000000000000000000000000372ead514d53007690843484c966361661816e0d3949b868176d7a9bea42064f49113a74f2572a6dca7afa0642fa5000000000000000000000000000000001199d3ea66fad87074e62a0b77d3fb962db17dd948f30c38f5beb0e44e1cd11d9172b878128e9a64a08394f13cd786f60000000000000000000000000000000018b7db157bb326ee2f72d4df2b1e0ddf0a90401ccfca1d4ffd6379c62acf5d6e4176a23ded2f81653038d56d848b4fbb000000000000000000000000000000000a932cc9740812c8bde33b68d94220690e0f55618b7e51d3e3fc29d0cb9a8d42b8f8e1efbba5984c3c1007c9a80fae408b5112baca5e0f2bfb885c5041189612918d203a117d886bcb3b27df7e64d17d0000000000000000000000000000000015798d10386f6d24caed3859875be5fb1a43ac753f725f28da6b3583bd9c0e404d36265c2305d7d194e2ad84bfd2bafe000000000000000000000000000000000ef2ea5f3b6e03e3c9693d6db60019f2efa4ea586bdb7623f03bd035c603e8996ef2ea7cf745aa31f60679ca04f93875000000000000000000000000000000001792a66785a3087a80c4b8652c1e4db8f602cf75c1a6955f480a977f92ea262965dad84061f6045177c831dc4a3bf8400000000000000000000000000000000006ea3862318974d6347639ec0d70afe748f4edf32b9e437fd98f38eaf72168a153cac180c2d67bac8a358e3a4d57a2b32db7ad39ec8129e9e9206bd46cec6a8ad3362ade1beaa97befe148f6c67a9c2b00000000000000000000000000000000000974da7500df70d888d5876e7c61bfffcdf830b49bdd40edf65a2ff476e9add35eaf9451a2166e9781805192ffd7ac000000000000000000000000000000000cb2e7152b5b40758b18caea356dd8e095f400282881207c4b79d10d741756e526be261b98b726d5cefb668dcf73a0a00000000000000000000000000000000014aeebb995d464f4d77bbb72f15d9078936b5ab68eb8022bdd97d050576dbe46e6010eb72250c8ccf2a59138efb38f9d000000000000000000000000000000000cf7162768e8eb50e21d3c0a076c7bac4920c70f334336037fb40e57e0efa91eb025356ac3f0988a6b127408a02eb53fe2400a11d9a67041824b97a96f0ea9da8848e7990373655d76e8bd4eb84df5dc000000000000000000000000000000000b1d6214796b4775c2b50e634a549ed104e6ebc0e032967b17eece6cf88c93aac23059f263faf3c3f38463270320135c0000000000000000000000000000000013ffa3894a36226664ff53ba9256d39c6312303f5cbda6847b4f68c56134b7d731e74bd711014fe374f909a081a7d02a000000000000000000000000000000000ae4590cdcb1367392635d0f8dc6b9557abd16290fd1abca6da354646d8585a7c9432978dc616e5fc38cd71d55f139c200000000000000000000000000000000124a7b5574ef52359b4beabcc56d3286db8c8fe4ca4718f75da28d89a8a95efb878c18b48360dbcb6fb50a9f18f0d559aa2d17c409ade92566ddb3913806723d41067540a36a9c283bdacb273c5b258a00000000000000000000000000000000148ab0e847ecac963f0156da025dbc52e765cd8827fd55ba2969da6775649529226ab13ab8537ad0b89e8f1ebc8648ea000000000000000000000000000000001395b1adb6a56b91c3621a4ac5886a7b13ec00f1c74d5317eb74a766eae655e09e269ec48cdf740abc38f4d6fe52dd0f000000000000000000000000000000000f70f77f07ef2909033665bc05cfeea7df6ed55f2f0b1b87d9f247b6c07c7e22f516840efe68005c3953a2702573a9b400000000000000000000000000000000166a334a711416cab180cc498308487b281711f2d1b832c410ebb4c591af54b154fc8c8d7ac9a49a241f7a3840acbc75e5e3d21862b64e09a0893ece646de60cd66aa483662125ffabc46cc52f1cdefa0000000000000000000000000000000008c19bcbdc2ef26a30dd88f3e35dc7fbb3c81c0224cbcd6b12c90883f3973bd7089636f997e5f213fbdcb79514c551c600000000000000000000000000000000058620cba8ed5b738167e809cf71392aadfe8f384a4cf397d10f674cfa914e9e02bb1518e42f16806214fec52d880f6100000000000000000000000000000000048ac1120d26e4173bb33a58c0ce86329cdbe9df6a6f268c8d5ee4f1d6110f9d81cd50c46256198a2462d50be3e781270000000000000000000000000000000010af13ba791d554720f5075d46d03b55c0c1dccd679cef5a7d439ae868d3ff2780cc3ab151feb72b8b92905a205e630449510ab1b7850badf58cacad67fe47135f6524f0d160f3013e8ff1c881e469e40000000000000000000000000000000005c30a126c94b87c54270d0f23a486c3b36a8b491bbd805ae0d5f2bea818a87ff5aaed2d5e6317b786ab5a23f1cb48da000000000000000000000000000000000eb2d4663eca7f8433f10e84984781a57fffcb8f9535518721521ddfc7a4958778915ea3c57bef399a453b8ebc10befb00000000000000000000000000000000161947f57d97a858e5b3e918dbb22dbf28629e51e81335a9bf105d0fd660ef80087c8d69d8db9841cc69fbb5e7f81487000000000000000000000000000000000c52b6a559928fe4ad984a0569c081f3f71eed3d5b0d3c14d1a23afa45594e0fbd94143348390bee178720fc603145ab713aa69664a8c721cefa7d6dd3fe9f92432b4d350621d5297805fcabb21ff8c600000000000000000000000000000000071aa47d392e1a7787b37c52acedbb4632d5549fc11b79919bab7d22f1bbf1c3a239df622b8824b07f6e35e627283b8500000000000000000000000000000000198e72e05388021919dfc1b2a58ca72bf7655cc6c9b62abe3b45cc782ccfd4a2334780e451b8a6b7c311887036813fe4000000000000000000000000000000000e20cbedbafd96c42612e146debae48c7fab4846b20ad0848c4c42c6aa0603e72f94dfc938ed9e3a9886d221ccbdef70000000000000000000000000000000000c861d1878e63e313e672bebdadd3fdbb691cff5fecbc24da895febce2eef0a3c774a8a9d751498e4fc8e2b71daeb40dc040d8bf0a787346560fa3b100b2dd9adb3f7ee716b8103abdd9609363345ae40000000000000000000000000000000005f7cd2205fa2e17fb9896efe3fbe110e1fa59db1ae5f8d6b5f4510abb4da867933d4fe3caaadc4457dcbb35f1b9c62b00000000000000000000000000000000126f2ef6022a7211fa865c1dbdd5b84d96cddff424b06647acc462408f2d31f34ce898d76e1e124db7c39e08dab0bff6000000000000000000000000000000000987f916ad6f718695f3c40703c59ca93eba38931b45d7c33c64c9f75556f075b744dfff8a5f21489b3db6c3846ba09e0000000000000000000000000000000013011b8c72f3853738e22957f742b05ec428ab0da28901800f787b7c3678449acd0359fee93c40c69623aa4acfc0a81017b811aeac4fb7d91abc655f8a4392176f9060346073c957ef903e25d10935a00000000000000000000000000000000014b88c0586fa18333ab11a79acab8e12c6257f82a4ed16d929768a60a3a5d780a22101c32ea9b0099aa2816f18a0351a000000000000000000000000000000000de0fde69efd2cea7ae08d6d2443883002e0b4e11da253222429f6ecc67ba8d282eee84d7f46e0ad00b039a2c2ad226f000000000000000000000000000000000aedfa0a5a8b7577dcc1094469233f8b07e6fc32af26841894d498d70c6a9a046ad636086def948d21e39833c5b6c5a70000000000000000000000000000000010ec6aa0efba4995582585bb67f997f60741648156324696312d17656baf6aeb3e2db0d1a272912fab2fe81d139e971cbd1f096026159218836a46b9801a4f0c43189324d20220aca777b826eaf2575200000000000000000000000000000000004a847c06abc8ae7ce6e6ff0ab856889dd3e9697a75e3cd4d2af9e06d4c2fc48c0562289348ff52f4d9855ad03d83aa00000000000000000000000000000000075673bc79bafa9a64de6bb0e9dd9fa29cdc9c82e90a7348593eec673cbbf22b1eca436ecf767d45852ed888a3f23949000000000000000000000000000000000f3f8543d1e667404b4564dddba4d7c11d13881fcd8ad774c8eab8fc599f55147c353cd6e163cd7b9d5da55ebc13c2e800000000000000000000000000000000069edec7e7d26962d88a89dfad213daa36046bb2851e5d67adbaa227220f29f83ea67cd3747e6724f148dac28308604cf221dedfc21098ff9a9507e493d0fdb1efa6029fcdab23a016515078c76f7627000000000000000000000000000000000c945e83822896974116663d3e2769f3df5a70d55b8392c1f6966e330951f3cc5688742d4588648a6988b928b9fe00100000000000000000000000000000000003e94b7ff7c71d633ce69bb44d0ba1bfc7c27a5ee618e703aef81a45ad61771a2fa8e3dadddf7c8038f1f65ad7513801000000000000000000000000000000001727d768c1b51066d2af87a9da3e24ea2a75b0f75b8ece70727f9f54ab77d841e7ae01c9c0760f4186d02a28d6f8ddfb0000000000000000000000000000000000a273f9395cd49b646e90fd2526d5c93fd46c7366b715546529c9edf5cb3d274c9947c21a03add3e7b20612636a6745ba5b30d1397bf28100f108b84e05107ddd6cae2e82f1973ce187e8c3a7d02f3e000000000000000000000000000000000c996c16a16879bd3194ac366bbd11b5863123ce6fdabeafe56407600e5d49c92ba68ac1256e1515dc9256de14ac26de0000000000000000000000000000000018c584d8a4f14900b2fee70b50b700199ec2372b731dd1380f42ec7fd3d01f0c9a007554059b85946c1c4f4e2fc504ad00000000000000000000000000000000073d6c7d671762e5398e4c9d57f6b68c3d97dfe0d01783f124256fac236f03b774db58b79cb4d5558e1ebf18bb9e19680000000000000000000000000000000008eb2b95e17fdda916b08ff2819cecd2eb031f41c8299b308339b7d9836382ced75e8eb1514a70356882d3a43227a9bc19aadc83d1db9140af303c0492d2b9bb9e2b53ddb62cd2132bdf8ef62aaed683000000000000000000000000000000001029fc28cd502caf3ea3619f6fd04bf457e6a452b5cad680ec2d4f8222a5ac2daa92b880bda76016973494e605ab28c60000000000000000000000000000000002c672c7571b5d8e99de6e47e0a2eb71c6d9bd12baf2b083e6f88598b32c4644d1486aef582c5936e622058bb141db1700000000000000000000000000000000033cda383a77d5b3adbb0809e834993c56717f81f8c66ad2d97f2b298d5a46f7b29a74d35da09271b7053a05af096393000000000000000000000000000000000132da041c6e3e1d68bbd2223f8531eabde8e180b36b2cd0ed4fca248f255cf3eeccdc5f61e1c581ce54edcfb2b73e0787eb6fc40b00246910626ab66bfbac96ea09242d1d70496466e4d681942050700000000000000000000000000000000009721f22bc49f68d703a4dfccc3bae791caaf0d73892bafa6e9da465ddaf0fb1a069ffdd55306acff2407da64c1c5a0200000000000000000000000000000000056c0a4804a19aeaf1b4fe52064e43de8e5d41a8d77de054e2cfdff078eaf468d123d7317818d1bad1bf3469c0070b680000000000000000000000000000000007f1f318aed043d9ad7bdd53eb6a8c3167240fca75925b04795210700463c93a66ed64851195df1bafbbe4227d7db5ff0000000000000000000000000000000007b8945e258311e7672e842b91b540fec9ef4a79296956a5cba3749c0ad95ed83d7b0b48384ffb3188459e997b86695d3bb5926f36808c0024ea7388998b4cc8c6c48d32917f6456b39d514143c6eded,00000000000000000000000000000000086a1ab4c19c27f70aa422e8292752c50b365d6fe3eba21e8f2ed51f283df0446020834ad27c18b5c7285d1156049bef0000000000000000000000000000000007288f40fde69bd350ce1f4d0f68e645f42de319cc032250b76fe4fa305341e244e5b2366751d5311105e3ccd30e701c0000000000000000000000000000000011d0c487c4eceaeac009b694931f8eafaf8eecd6028f14a4de33d2940bbb747025eecd509564721b50b7186910f81949000000000000000000000000000000000366f0c901fb859b4bae006fbcc9ec7e456eedc7366c899f68090fbd457c37b03ab99ae982872c7888b65c1a056c134c,293920, -00000000000000000000000000000000072f3f03bb09ca30239dd8302b05e0d9dc4e43ea33e865864a82578c35eafcf6868bf0cd9431b92b76f00990b780ffa400000000000000000000000000000000170b76cfb7944ea5ea055aeedaface3e8f0fa4d0ff657fb9d5311f3af6e736da84a5e2bef5188e20f76fb42591267fd9000000000000000000000000000000000d85300009165a8da9cb8e590f7f8d372e4264df150b1551185c80e49dbedaaf872ef69c5763fc3713d0c087c89f21050000000000000000000000000000000003ba59b682174ee61630df95c8e2b1c48ffc8f7f8508c21f3bbe8f7bb3266521fcc06c8f90fe5126d872707872db6d59f44b0204792359895b448bfe6ffaedc14d54a6d72be7a49718c0a933807a399d0000000000000000000000000000000004e8f16480c2f080a13b9f2b66e6480132d76c4ef76e8bac995a8e33280073ed5610865260e154b32f75f527d89620b3000000000000000000000000000000000f9ca48d732a8055d22fbebf3d2bc1e1c9c815c184f594ad2337731709317ea6a205478ba05ee9271d35a19dcad4db5b00000000000000000000000000000000078013b9290284e7ad528a1bb9a2a64b3ef43964c7226ddff8ca16ab17b4a2e8a2a7d921ba924a718587954f586a954800000000000000000000000000000000004aa76bb1122116cc0c04d65265d8652f08b411632a732a9e66d7932801b77c4ad398d582e446968f7f4966e9167894de25977e7426cd5652559626ff8b195ab7ec679de987a6a22a6a0e366759dea000000000000000000000000000000000145de5d101498bfc7c57830eea2931663ca1165ec85b77654c866b04ba6a28bfe710c1aac9876a68cc6ca119708eaf0500000000000000000000000000000000096f9df9d5723e8379f2d09c76a3fd059be47d2c2ed8905d333b2464f72153c5f50b6345980626358839ac691c26c967000000000000000000000000000000001788ffa765c19758da6eb6c38e793190c64d4a7b116576f6827fc090b0f65304988f6a95cf4397f82b7691fc43960ee8000000000000000000000000000000000746e040d7aafdb06a31ba3d7b590dd28f0678badc261a93dc7bd9a605047ec67ba86b2b6dd72637a449872674d6b5982e7ae497b44f531fe203a599622954804c06d5348dc17eb1537e750006584b21000000000000000000000000000000000d8f3cfe1cbd2629f3899313cff16ca3d8f964ec1cc0508341936a7b3b49240db1116b2c3de28f9bc45cdfacdb5fd98c000000000000000000000000000000000fa642ed31293e44211b34bb28bd5b389ae6d0510cdab46c89756f31795506fccbdacafdff21b0127e80557e5ba9afdd000000000000000000000000000000000715a8951cb358b0d8cc63377799a9a61ecc85dac795d726fe60e429d492c9ca843be2a2633c17f830f199335e5d7741000000000000000000000000000000000b88a23fdac7d35fc135b45d7565854bf010a75f072b32c57ca4d0979c111aadd84c71df6792dbdc8e975ecd46a15df2e073adfb5ab96730c53015a4ab6210a35a37b2331ff5123e00798c33e040a913000000000000000000000000000000001171be5820b5a19c045abea399f2b8ab9905d2aa367c6c8c0f84eac132d26150b759a9c029414f1c8f7e4880214446c200000000000000000000000000000000147f0877321f2709183f0b617a7c5ce898db508a3ced4148cc9f7af011fe8040e90885ce817aa956d9f5d19dd968f6220000000000000000000000000000000000acb005c11481b214a17e3cca02c2af266e4c8cd928e3c4e221d866e9f296a2e913bf34c4e051c7503a5e4e7cd7449900000000000000000000000000000000125f45d0af1c010cdf8438bff0f406007853e566fa646df40a581f65496197755eeebaf4f0f77e1e936f399dc4c6c020e6e752d40d411f1ee6e67f48109c9a059226b446601047a2189ab815a3fe13c40000000000000000000000000000000019cce3f872af5cc515ac4cd7825a5318ead5b464d50349909a70b415a8950206974ee0d4203f208d8e6d14690158f5720000000000000000000000000000000002e08e8accede11afe3e2d085f35c08d7d414c26a9caa992d5a090a43c9b0c0cc1471f3693f9d342a973da65189c888b0000000000000000000000000000000008a984ad2ca60c492cff2e95d541d71e33b269b10d3df107c0513dad5af511c51806068da6cc7226df1cf5e5a2fbe707000000000000000000000000000000000fcd3ad75bb0a5c046cf83be3d973bb3685bc717d7b8262fb8205935db6e632472496907f7c965fc6b52042ce69999f9e657fda33cf4ed1aa89dbc19d58fbe3043acb5795dfb8c0cb97620f16f8f24350000000000000000000000000000000014ccaf7594d8ff6157f9439ba63480d3d07f44e62a86caaea510d0ec456cd8c6c4b42cf9e38713213eb4942ed45df2ca0000000000000000000000000000000015c2061c532cda006addd2fd6ebbae458197d55fb336f75ca7decc05dc6d421a65495b71ed11874aaf24a0ec13a7c65000000000000000000000000000000000101f953aed7f23b5b6208032f05b818e0147079b7764aa3134dd9e4a316bbef0309ac378ca3cff3bdeab9ca56cb78e60000000000000000000000000000000000c76a2bc721a4d3ead95af79ec24be9b7624bc80d7debc07e388e52ec621082b9a69f48d157b168af4aa73629697f784c73458e18d6f832f362dec7c49140e6523ead045131a1b719b0c836c1ef13a79000000000000000000000000000000000761832bb5b530b80c668234ab5996bdc225c0c696ea07dcc61c330320404827ada9d58d658e230fcb39a96b339b830e0000000000000000000000000000000001198b85418421d96ebfbf436193b411a3a89c206d006291bd23254ed5fe12ccdad15725a34d962005c0ae60e202bb86000000000000000000000000000000000c1d7ab83b1d2ad57a407e248492773a357c06b83c16c6ce1490e84bc4a3cbae395f160181d2bcca3edc34b764754ab0000000000000000000000000000000000f1e9f0cf96d7671763739b6c37fd442f0e816c49d9c8e001d322397e9d6741dbf8769ef9eb83d08ab024294e279a02838cb0a2b191f538b30187dc730a8c665bbfce8186883500baaa6c3242a0d147400000000000000000000000000000000063049bc3282934e29f3bb3dee432bdad6193a5d2247270e88887cac565f4b986e1b3b2af5387cfca64f0d50bc0ee1640000000000000000000000000000000019f0f05fc7f8bf2f0b8ed375690b53b6dafd0a07c49fa55d36e040798334700a3aafc4995bb90de9c4dc0e077ee18b58000000000000000000000000000000000fbe702d148609dc8feb3ac11c5eac8e32a2f7221aa135cc33a585e9f4c97afa1658d8962fd96e26e0c4c1d5108229ef00000000000000000000000000000000061fe418d3b440e84728091a4996119b515118900f54a6f2da2ad5592f48ebc17bba50b59ecf435de3cb892a123ae9d18a27de64d41d13ab67c1f7b1a7390ab4dbba7d219dfeb31255f9401d5b3c62f80000000000000000000000000000000011e8ecf1e341f0146c59a79a8428bb01d2399d3f87d90d057f63e6cb9837432154d17975f70df175a016735caf85120a0000000000000000000000000000000002a5bd53e4f4c5b9682e1af1f7e09dd305e7342d1688f62885b5e59f173a9fc731cec481559ad693030004a5fbd90a9d000000000000000000000000000000000f9601f95e12bf05c35deb204558d44a60fd630c05f4060b7bd9ff943946e8eab507422afe00a3e7706b8ed013f712c20000000000000000000000000000000003bf6fecc0c7414a69c2b48e2c16e88d988ea8ae9d8b59017ecb89394732a20e4321cb5e4fb071aec7d2736220a4553780030798960729d63db70b8bc3c0030e80d9b8ae766e3330128557e6c34442f6000000000000000000000000000000000549f6464b657eac28f838c6a8bcfcb7a189d6b3b9712e19c1a23503ac209da5f2ad4df83acd505b0231f00eb88515c70000000000000000000000000000000001bf4a46dfdd70542e9d8cd6d6215174cba28f9adbff31c02482ca38205cb4afa2f7fd65ecf57b39e4ee5cee320e33800000000000000000000000000000000012d04a693d565f96566b7c313c47d272fef0ecc828493b0841d58f6bf690a77cb72824a656442e288460ecca7cf05504000000000000000000000000000000000b33eefd5df8b098e6505cbe655a483ab5c6e417a4ed55420beab95e8614c8538dca9296a7848d6aa0495a173df6d0b80d32b6969af54dd345f42320ea96def3c6f4dfd4e22a82686b7a3c57a0df5250000000000000000000000000000000000fdd9702ed88aa857254c3ba50b484bfc324e583659c57055e4b09eb1662af2f70b547a1eec139193a0d3c75b565d3b200000000000000000000000000000000193df0fbc5f24065008b5e98c4c4bf9f1e743a6ee60c3700ae4a9108639e540384eaf1f9d7a60b8b6a5d79e1f34949f50000000000000000000000000000000001022f8a254d17e448cadfad35b7a54dd2fb319c8f9ba219874bd8280a5077301ff4332d731a75646cd93bbf31331154000000000000000000000000000000000ca1eb350844ddd0a65a4ad56e1a96821de2c6633a4a45be976577c223e367853e2b1ecf2cc40b8595ba5591ae8e40f3969848f1b8b36bd28967b762168edb451322e2f0c4b99b7f9112c9a66093fb3f0000000000000000000000000000000001f9cda056a0f8803be581634562e975223b5311f4752b189cb6bd6df1ca5e3824bbd2889b9b93da59e4f08d482734240000000000000000000000000000000009f43c25de25c5d76ee1a03691aa434de6a063bb3a1133b045797a279346fc938dd2636abf0c4bbcb528c9c28d3105c40000000000000000000000000000000012afc29245da8bcd3c0d96c4ee61617cd9ecf42a47c2ee822003af26aeb4e4de8e432ffb6b2d8241090b814401a8676100000000000000000000000000000000053edfd98742dc70d510f1836fcffa6a3ba9ffd4904c7f5559b48e49dd21071401362d0b39bc0d786b7ee2e84a76af0d957ee08a513c5e22bbec04722575a9b4f3a1343db0ae5beef4e66fbbe1ac90440000000000000000000000000000000001dc3f016ea1a74ae50c21c1955ca1eb4a911026a1e72b316c7bbdc708caef63f0c1efecbecce8901d65bbfcaae429da0000000000000000000000000000000016ce9301888808323c9baf6402d7073fb85ebcd389334cc69d7947e345748ee44b2d6aab3ef818beb21b54a19ae4f5b5000000000000000000000000000000000c49817753eb6459cdb4bc737d3710b5f044bc544c8d92c8ef138ec9d83889664267e1a5691f4bc3fa235ecca2a973a500000000000000000000000000000000074a8450e35f1da18e6de05960e21b7059ece8972c36f000bba9e24488730a44ce3ce200c437e06703addb3b442a790a8e0cf0f590f77d13819001916d2c58a654d0b9d3c47c842f2d649cb2570dc0d5000000000000000000000000000000000bc1f2e9af093ae8235c93af098e692e697ea0ab4c8f53019a6e950f7072b56d5eef6b3237710f1dd1cd1970668d06d0000000000000000000000000000000000d9a63f7a13ff9755c6a3832e3c4c852919514523092367fab7886cac317e564d57fb4042ef40e696edce868e697c45700000000000000000000000000000000129a30657466460db13575dca367105c27d631eead330319b084adfac591f5b3b94988925d778e6d4645d1d2816baad00000000000000000000000000000000005ad64d6e761a9a301589547929f4952ccbfead278cbf6658255a075966340f185d5f356679fb02ff2197468ed7de19a71a8c2a479dec43d644ec4113142e666bcefd6d729d4faccbc147effa836ddab00000000000000000000000000000000077d1e5b35c224e2cdc849c02e800c0b80d1c19f3d74d9eec34c40f56bbdb9e2b5d2ef274991dca843755f91a50826fd0000000000000000000000000000000014f3b653e0df0c608b75dee3496a7af04a828e6fc5604f16ed49c39686ec757e96adb0a667853006a8331c3d63ae4ec2000000000000000000000000000000000aae011375b337940f2a53d9091d3581e8197e79251b19c7fba01de987721a9d6fa694b7978f0abf877f46ec26147c98000000000000000000000000000000000aaffbd468a2eb86a3cff59e2e9b7ab88286d2bdd19c2e789b1a68810f0cdc76171a2661ab54e81b17643ff0275eafd72d2d59a7f138327a20263d6338d2a92fa5a2f741daefe9aa81d06f20a6fe3641,0000000000000000000000000000000010a2434fd3150f6b9b491d3a51226bdd457504077ef2ed5a11ceaa8284900d1b84039a34d5239a863809369bf20a704c0000000000000000000000000000000007934f34fd50a98225fe6578d3f34ae5e5ef5e104bb9cb398b2ca4f09048ec39cf52e7fdbac48d45212e9e4c1dcc6e120000000000000000000000000000000013ee70f1b52cb8b07ad957a7565b3e3c56306392cf7b5aa29047b23e5b41fb3239ac3611bcb16ba7c7ffc4213e3d9cc800000000000000000000000000000000035840f8ecf56359dc3239945720ad08702b4ea8d0fa9bea3bfb234431df4618e960a1eea87da72ba4d9443f14bb87a3,293920, -000000000000000000000000000000000d04bb9b75bc8078ccfa85e27d32e137ff5f05f9241b19ea835bba2fffc9255a4a3028c0caf9c32d3d27666e1394fe820000000000000000000000000000000013f59c3d8aaee34230cd7715a32e4a45487b9b16ce68d178f95461229a4d0fbe7d31edc7208a7338eed08e65847f8f29000000000000000000000000000000000d63ca2bafaa54e93ea54846b26f88b4c6749953f9cd00c670914cca279b794c1fb5e2664fce44b8c04f01c68698a8b9000000000000000000000000000000000b5188b4b7ef78d3662baa01b1813b4a0b0f855e11397584a460d56f594f11ff2e5d708a23a8e64d0ab337c7076872527740a826d524fdb7969776bede5ada468a0115229152907cb2b050760c18c8e20000000000000000000000000000000019bae57568c879cd743f7def43b6b994f29782c6a0c74734f35b97042a916da00daaea34f321481e6cc4749e23297c1c000000000000000000000000000000001853fd11d4688b027146a07edea647502e80750de4e5e2d105faad3f71ccc90badcc750f76f1b02db3bc0a1a635b2bbb000000000000000000000000000000000b1e45b90e6a7032179236f13f01ab664c32ee5728414ac0d6b9d79510e8c5bd0f5b62e6c59c1a3c88998bf45636cbab000000000000000000000000000000000ed16c2f88b5b8d29d7e01633e2876322caeb740251b034e5e898919f836ae73f0296c62253a0329ee8f71fdb5cac3a1d226f56bf3935ea95d976fde5790ba0584e5bbc78b37279aed8e50389899b9e9000000000000000000000000000000001455764f99e5eb0e0371e89f88bfee1c43224b9b5202746bd151f72336285556acc5ff36bd8ff87378249e82214cc5e500000000000000000000000000000000007fcee74e5335d96714e4d1a7c6f5c211b1a460efa283e0d0578c6c1f56dbd252198eebf0625362973c40d95fd890d3000000000000000000000000000000000ede26cf87e604507230ad996788e85799cc07245cf7191a6c3cecf0bfd5747b3a277cfbe41252808df6da19f005de9a000000000000000000000000000000001855991a4dd78dfc6088e6a43a64b56c8d86a0278b899bc8a1979a40a287979dee567217b006ca71374156a96b79c176c133e1989ac82e4d1c9852a6c7156a34b05784a58231d59e3cc875ac5834d5c8000000000000000000000000000000000cd032a7dfed029af020bfa249e6adccaaf5bcd2ccf33736281c4fce9c6e2b2e87fa828cc20301269d8e0579ffb866a1000000000000000000000000000000000765c4d6c4062cfbf7e24f9772dcd812f7e707f2b0ccf9043faf10018326834934df121924abb74d736b0da47554794a000000000000000000000000000000001540fa51e4580ff73e58def90a6f19557dec3c8306e2317ba0c25ece3eb4f8c39beb57741b3c4b9b8554fd2597743ce6000000000000000000000000000000000d875c822d0ce50dd638254cd4aad5dea1443813689a940d72cfa5db9309b171299ca3d69b137dfd37f0b7538a0852750fdae1b53f6442c4378774a981c90d282d5f8793feb2334470c873491e41740f0000000000000000000000000000000011c230689175cc672c25f3c56ef4eaf2bc5766ce424f6c596b40ab24fdbfa56a955205419c149058dffa4d86a48ad35d00000000000000000000000000000000078d493ce3a8038134541ae5f2a82b5e0590218a499dfd78c7a9c06b92307003fb62d6414d6c04b22f2877c3de0b65ca0000000000000000000000000000000001d53c22a622c5d91df934783f8c0cb7e370043ecaf99a0554987e6c5120a0e5f4ede023a9ad988d30d945a2132ba5770000000000000000000000000000000015b1f36a00fee95e13443c9f6e67935a840cedc7c3fb7833ece8e180991909922f59d4f4ecbbf23f16bf5ee7f0b5851b70f1de7cc5e6a2cf7dd4b6e60ada67ca47e7b9417bb5f599048fb0c9b2abf33d0000000000000000000000000000000014adff1607236910597a951ae169a7f56d6a3b4e0f44ac63a247716bbbf61feff7865d075f79e4108cda6c0731fdcfef000000000000000000000000000000000d740b13885c268da876898b77914bf4a002beef5bd2a3edefbf366e45ebcdf593ec6d9ab21e983fcae9a0832986182d0000000000000000000000000000000002a0827e812e983898351d9f03f660317d41669b0fa378e5c7667b73df299ddc4a32a529ca887a53245d7e1f946623b3000000000000000000000000000000000bf09a2de1a8ccf24a8a65dda72adcb96535ea7235de87f05d27341738b0b4ab16afbc5b37c97e255118dea9bf180ec2ca82cffdf59b742a736ae9a6d36f7840c46c20c126ec054f47ad52a22948d7210000000000000000000000000000000015fbbf7e8c26e2f41be32daee2c81390b9bc4413aabb053e3a88bc6117377bc16011e81ed167370b72f84f0e77c2b8680000000000000000000000000000000013d48a27d06ff00048b19879493a5f8ca52b7154be2fcb468b9de9edd1395750434b0e95ae6dd941e84fd6d8918455bc0000000000000000000000000000000012fd2bc91286dd46d68d87a3f8793db997ee684dec6b2de1c4202e5e7eb0e4a8a21222e3dcf80e1ae4a3a92474107d330000000000000000000000000000000004d8b71978c9025dabb3d1b1b3c7f4f13f166514b8b356fd064842269a36c6f1c07f150c03510af7d0913103afda4a68fad69492cab4ec7eb89ed37f1e7fe898ff49ffac4ef2aeb75d9c6b544109a08f0000000000000000000000000000000007d679ac21bd4634b415ef8e0e3670a8a1d673f6a4f7f3786b92d55458af980b035e4dab165a3b773ff3469fdd9d5135000000000000000000000000000000000fdb82db6e1096e73322050f828ba41b3012496a4fc4cb481f11fee338243aae20b205ee06887e28f6ba6dad00445f9d0000000000000000000000000000000017e6894b48f60b3d9b4184d58ab9554851e285a1d445b4d97cb1a7ed5a984ade8b0f62ab11ca75fdb280cc0e526108ca000000000000000000000000000000000c03b61690cdd9a4c6c83d03749db72c8946c21a944fb292866cf3a2dd1bf3dcd95743227709740ce8124319d0a540555af71c9baaf54967683f8553f72abf789da465041ee5a92c9ce1ad562c91c4d7000000000000000000000000000000000289f850c4834153f36bfc4855f89e9437a172c35a856117f8b841e5ad4ef973d3aa33fa73d8dbba4b9b2101708006bd000000000000000000000000000000000700025f22c0460613c05f8941f8a79a4319325c37c2b8f099cd910df5c0c27121a9de0e40adc7ba0fda61ea637b47d600000000000000000000000000000000069e17e00d4d726e8eaca8235c88967a7c093c70e5a46b1863ad097acbe233554048838a0a486a72cbed7001c83a27db00000000000000000000000000000000016ce4afb84c1a9e0216f23bcd2dda0bbada6a4acca78e1e0d765a5290f6f4929f6d0eeaf1306fed3c9766ca7c7268acc7effc9a7fe773a420ca430c58bb94e7baf26b9a97b618a15e7a18b31e5914f10000000000000000000000000000000018ca46a89dadcd3b54f60fdf9a7b97c95b9e0668ed9329bbe4121e588a1ba773c9d086dc35b699d65487f428c00ad8c30000000000000000000000000000000003ada6835a93310d0ada01bd7fd6778bd07e718d1ce05aee2b4990bf32322fa94ca898a531ec6e3b8cd7ae3bdc77e0b70000000000000000000000000000000004a8abd2b9f7449213e63ecdb435e5e13fe2aaa31a2c38673a6adb5e96f4dd383dacab391787f6c17579c78a1cefa5450000000000000000000000000000000002a8768d98ccda80149a767e9b5a3b0bbbc0ab4b5f696522c8f1c664f1d27f2f0a6690531672ba2070355c0e77095dc02d5a3d0370f4a58c21016d208609f1d3e7cdf43abdb85199bfc67dd12f589b8a00000000000000000000000000000000048fb58924bd5952d3bd7b1cd57a1dae6c1034df3a420c1151737f88760e4b0e78fa3f891a0dc32fcb50f89e67b0f08300000000000000000000000000000000073e9723c80eae7685db774d3e2bced53a52f24504fc3aff98e2becf8d59c6e83373ed024ec1ca50101d2d613abd286e0000000000000000000000000000000003b64c8e9a1341bc6a444a871843b3add7dbf04bd1810e1d6da7d31c7c2b7a264c362ac9a366dc8d93bcd9392c6056f000000000000000000000000000000000064462d424e54f50e9849a2bba1b0caae966a8618fda0f8965b1a841dd2173872a44a18ace1e2aecc8e3546a9558d7013549b86ed3fb880269be22b9cb8be6f24385bb5e24bba81bce9fd5b72ce2ab71000000000000000000000000000000000c40c8da9281a8b43478c28b2fe59a3cbad0a818e2077d40cfe44624dc2e46f72d4489cccf63eb8460d02f895e78edf5000000000000000000000000000000000735d768f6ac999a47c88bc2f3375f01052259dc69011480e468d8963ea8eda74726c4ef32c8feba52878eaf5c0147730000000000000000000000000000000010adb3ad214b17b963586a10701934727edf05fcbdc94d98255632647d73536decd0c91363840e1b55f29f7d32f650410000000000000000000000000000000019349045e6fd25960c03336888679cb53409027f35a1f211b40d24ebf724866c085a978ffa3a91d989da1a7902bca018c8f6dd56906fa13144dc87c31b53186b0683cad220ab2de89d2fb515bb269cbc000000000000000000000000000000000a5d2dcc05e218b0633e0a965b6d69a3c6c1c7837e1fff7ff75cc9ee93a112f8e34cbc95bd9dd8fe6ed22f2e9221aa110000000000000000000000000000000017d2e5d2c0578b1ec26b57c3305b209c979bba6925756892f031a7462ec44e8a4a2527e6aa2fc13bae91dcacb8c7a30f000000000000000000000000000000000d437edb45ace50700db548db68b9e8376b3039fa00cb98dd00cd197c14d0f92c8a3945127c43b10b34bef7894fa43410000000000000000000000000000000010d5a2e442a2eb35aa85fdaecf094c1e1f307dc9bcc540693d7206cc4e0d050ab900f17fbdd0754b59bd2aae705c60149ec934eddc44729d05f193ac927fbcb022288ffb2bc7d4f46d1bfcc7efacef940000000000000000000000000000000016c36464b426c3066aead1aaaf65ca637e93279e8ccc9d838b9b3ff1aa7b896f36de506efc2b0864763cb6ecca4926f30000000000000000000000000000000006d88d5764fc854ed7d7cf1c0e210496ce347bd887da2a149a09679469e98c453d85115afdd2fc4987b64a88c4a6f0a200000000000000000000000000000000053edcc0ca4c205423ee6a7031939379e552bd2d2657f8f25370c9f0ea0a947e77f18b5f218f98d12d720667844f3795000000000000000000000000000000001292909190854cee4499faa602af99dc49d1354a71278b439e983bd89e6c504fa5fcaaafb6ea26dbeba9850bcdfc1f69bd211ec887635ca841c4608fd00bdc0f5fd0f6365dcdfd7d6f4c36f4b25b5b1b000000000000000000000000000000000997e79a7549ada9ee0233b3bf9289df3ff797595f4b5eb2e7dda6977ca981c1c4a2b91b924812b95418f1b1d9d0cb830000000000000000000000000000000000256b830e80f238e8494387429d727a91cf5d323ea87f7dc143058c05e11858796adcdc677429d1db4dc2415cf23808000000000000000000000000000000000cab529c6b86beacc57c874f07108d1df7d98fbd59fce44c48afe9eb2dff823f4869b620bbafc121b4ead2cf244974de0000000000000000000000000000000002774906c1a0acd87de224a9450617db37f8f36a0a192f5daa2774eff0b73aa79b4804342999df761f8572974c697c6010bce61d4e35770e7737636c0f9a664eefa948662d3d22d1f1708fa48d3043de0000000000000000000000000000000012abd02540073017011e186586023adfca36fae454350b2015a796b7991eece65b63964fcdf581b4b51dbd7ddd506ec3000000000000000000000000000000000ccd3f2d9280908d4b30e924e4a862810a92e1a880cb56e842a94a2a5120956e8713f548ca279d66d06ab23e4976e54e0000000000000000000000000000000000c052ed00fde2cab515694d8c004de910e62d07c462345ffcfbd3904a0171b970bc58d99c5833059315283004f3390e00000000000000000000000000000000008fc4860366074ec0c7aed2c6ffae7c93ae0a81067edd8911b4c53393ebc0f23243823aa7aa2b2e987cb510f6e0a55a65c86930c1d142985bf85ce70bbad170947e850e5c6ac7803fc45980dd37a57d,0000000000000000000000000000000006ced307065868b6d082bd205bfbaea3b0a8cfdccf831bf154563b5a942154622b0d7689819b337479480d19aedd85e4000000000000000000000000000000000c0f04fbb26cf85c2c22763f3e78fe255d8d1f45ea47232ab58f5b785ad9f2458b0b28f3cdc25c4dfcb47d59957ae10700000000000000000000000000000000120e38740eebbc3eeea9beea483e70d6a9c30a5abd61b86e5f94bf65ffb40fb92c8d246edbeca425ace175f79c9c8afd000000000000000000000000000000000d5a503a26e50f9be34c2e64e4a80402ca6e17f09db1b334a9c1f6318f3e7e63b3847a7ca38ae6aa7c96ff94bf5de842,293920, -00000000000000000000000000000000036480931a5a585ea54b6dbb01759eb1d86804e3f03326188c71f859613722e662c453096431171a49eecf8653f14d470000000000000000000000000000000015fcd6a30b9d59a90d8595ca1758eed7d6810d2916638dc2cb637aa09b16b5ba4920df7d21fc0b923453a6c7d32f056b0000000000000000000000000000000019aa4d8e98808c2fc1273d383e836876b087ad5a7d01743bded01314bc62ced94052d75d312a18839c1b33faa9e2e5160000000000000000000000000000000015747ce0f1171c0d0ff1fee9dbb2e5673b9db0b0c3618cc8bda474f378db58ea42184f907593f3d6fc2fa215cabb7b2308e559e394a9c1ff07a45bb3e022f9c212eea4ee5b77db1c5b93ce72c0512b79000000000000000000000000000000000222640c1d64948daac3ff93e86ecc96bcf9c93559266529a37ef1372a81952431673d69f1220e07b8aa0a4f3164c83b000000000000000000000000000000000db593156078821cd0ce0270e8a444d0d204dce0583774496620bd4752839f3451e505aeb3db568048739c7e71d279b40000000000000000000000000000000019932ad2c7e857c2dd51f7846534050b9243e388260cd47a91444fa050a9154eca88ab4d29a37def16d4a11d35683f2f0000000000000000000000000000000004d15ec653a72256ac6b616e9870b0acc7d46286893c0eec523dc27bbcf5fe596204cbf83ce71c2690af67b3616794225e55826db8d12169a31ca27beec80554954f522b56f7994c62bdb527c2438d5d00000000000000000000000000000000180622bfa9a1c452f343ed21a3e9c6fdf76589cebfb9a3f0a53782a3e7c9d066294e10699c386b5d0525003289f0ec580000000000000000000000000000000006615ff63c856302dba6d4e25d1070fe873e0c4950ee5ba8bbbd4b94ceeb181f1ee450acfd22f21010b88f0b88375777000000000000000000000000000000000cfd3940b5eeefa92d775792affa34371d13f3098ede3007e06510344ac8483debadd5a2baebafb5ddcb45a9449768b200000000000000000000000000000000145be0107a1e3acecc89a116668f9887579ed7a72abed3f4236930edd3f18974465c99ada86c4980c88768824216170f1362e8e39ec661cb3c5af64e0001cc94701194344a7404f1ecf7df0d5633eff9000000000000000000000000000000000820e74e6d0333b6b36590ebae78960d019065f1681ce68a2a01a2522496c840c668575a57f9fd0f50b87f928a41b0de000000000000000000000000000000000dee60d90e96019cf2bb552d016419e92dd358ff97039a61838b0a89ccbbd537f2b435cd11f7b6e75a4ec6675964e7fd0000000000000000000000000000000002ca767de9fbf8af7c73d41a07e1c0e38e3fc971472e11928b65393a27354b2d732012dc57f498f94c0b933565a7493200000000000000000000000000000000134fe97b24e153f0e9a27d3fe7b89999c6a19e353325e0746ead013198b8e00ca6472fcbd2a112aecb9ddf671aaedd9174d3d66cde7c4c8a4499708a0c6f7c4da458eb970b6ca87e23601c702365b6de00000000000000000000000000000000031a9c29323196ef31030ba73827d228e56fd5209eeef0803a189e0c0e5b186ca1f342483eeac99e1e1b12cf490856460000000000000000000000000000000010deea45a01370602bf57a1f81413e8d3b337d7a1a33f9525e4ff7003454d1da2cfb1a9b42c4a654320f91fe7d04b6200000000000000000000000000000000002bafb7b7452a173a3971c2ba1768061a043307d2c32767056f18c1bf8b066176937876a87055e54675876bc1b2d2fc3000000000000000000000000000000000b5c77dba3b4136a7efaa8c2e28f39e88afbf26a7313b52ad6e390da4d948209d96e39aa08eb52200dfb890d7e88b46a389e0d43f2006449fe2de506dcdba4cd0e6077e2228f7d8b6ec9d8a4129c494f0000000000000000000000000000000018bd1ea5ee8e39c43d442e9c6fd22706e582cd80051f18334c4db2ea91ab019f54bc0074c8f0e52e50367197a797e7520000000000000000000000000000000005c0bcd1b047fdbdff25b138248bf4da4c013beff7dd3030c348d6b2b8724a147cbc44d570db5c4b273c94d0b99bc2290000000000000000000000000000000018e033935c20be5940863f7e9e39fcbdc29ba031e58c10beea90cc48e9da9988fdbf108bcbd87948058f386928f81fa800000000000000000000000000000000107d179204db7b288315e8aed7b92ebfe53b7ad2366d5d7944b3df68d9d9faad023e477213f85214047645bc05fd4cde5f8dc332cb31e43bc2e551356cb8d1533c6e567d34622667e7e4e3ddef352f03000000000000000000000000000000000a7b364fbd3bac7e2f2e7ee501db2d248bd73a76c2a12a3e51718b56ca9a8ded14b83b8cf0b5bd46f0c26896a65fdb15000000000000000000000000000000000eafea7128fe20ddf740a6396bf18ff5f2652a0317ea9b6e934927c3ee95b59c7dcd51f7c895b3989d40ae5f78ca508f000000000000000000000000000000000bdce57be904236a8df532c2c0072165b5cbd4103e9061fcfc0a45a67e4b25d11b9f816f63fc0eac4d6d3e10d2764c4a0000000000000000000000000000000012419f94ddbd8275054f8f89fdc27a74afca2eef314393236fca65705354e5cc0a470818999c96b5087997813823e9be0dc7052044251fd360538fa6d5dec9fcee53faf2f07de5d8df212d04f968a0b60000000000000000000000000000000011e4010d0cd7855a92cd5d4954ad735363c0c2ab00053db5e078f34e772969d8c492892329cb95ea8893b4b7ff7aaa5e0000000000000000000000000000000013badc54d90a19b84d76b30fef8e3ad2cb268204fdaa50ae951b63e48aec9cc6d585751dd48e4a8d4659b835f38f8da8000000000000000000000000000000000460728f686b9b15cc19ef135af71312e174860284c3f0e7a84cf85a5c934e2bb6cadee8e482d88afe788a796605f79d0000000000000000000000000000000019a50c06ba307d83452a30fbd862270652cf5c7a09b150fcea858a8102ce3b1e9ec13b6abfb323d63d2c4edf209c7cafc579dd4f361fed9084d9c66a3ec4c6af5293710ba5299df3abc4cbaf5802b5360000000000000000000000000000000009faa74f66ec0384f0458893c0026f73688c764e8df9ce056a88a2ed0b84ed8f88d1b683443a3269a3db838f8aeb808a000000000000000000000000000000000949c4be2708c1aac86aff39290ab6a8e0f332e7a098bbd64227a175473d9dfe136e07548b282f69a94a15e2c32dada10000000000000000000000000000000014f2c7c7da781e2f50803e3a948381c3c439b127949f79824df1e5722c206efccd6c0ec5dd75ef63d8b1fa301c83356900000000000000000000000000000000176753460d241f38aff41bafdad51688ab0dc9a5fb3643977c7b9d282ad4532fcca1e725715227780ec28bf1c32bbc1d69f0f3c3f516ae34fbecf45f4636c22acffbee765952b332c0f3d8cadb9c93f10000000000000000000000000000000011982264c8c078518cd0adb05034761224e9063654904e06fb5e5a6eeb1f45e4ff3da661f1232693b79336215dcc0cc40000000000000000000000000000000010c96c872160d2de03a16e85f2828d0cf2dd16a3389effacce46b5b5eecfea1042a77de653da5a1c0380a84c435723fd000000000000000000000000000000000a4ad2d9956bd407c555b26c192c6bf59bf89e40d9c6f9c90780bba313a39db71a73e7633397d47a3f58f61c81edee77000000000000000000000000000000000a7f912530d27a7bf74e01d8e48890cc66f72d14950554991ed1edfc504062ff6bd3cb6941bb398df9fde3cefd33fc0676618f1954730111e572937cf0c9f7b3298a11d18cd890cb419f732c766bc6210000000000000000000000000000000015bc12aa9ecf417fa5bace8d9e5dc4a418555eeddde1da8b624bf7d6e1873ec4a257d5f6dfc058a8d9b02528e699abb70000000000000000000000000000000015b41567f8c780f83342449f27094bc20a839602ae482de14b92e40017e7acac8857db48a2d27f1f1a625883b6e5255e000000000000000000000000000000000cbe79ac0718555fd8fdc38b68eec8be83b32499d2654be44888e45a2d610b0e81ae12fd56550524ad85b5a632db32ce00000000000000000000000000000000069f46b5baf4357d8010869685b3828c0dbf6e2338598c9b42dfecf0b22d803f95fca716115f74c77778d414cbcbd881fbb9f2400ed1dec7ea63d2b26bb3e9c2acf70117e3026626f6f88a07876177880000000000000000000000000000000017ada4038189c544902167be958e43ee133730e5cd329e572dae2d853b694f5ff8032bd9ab41cddd11c51e8284970f810000000000000000000000000000000013eef75e6d28deec945ddff33128c199fa52565288d63677c824b8d56a6c29eb98d34c5834e84865be35d40c1c59a40c000000000000000000000000000000000e2fb4f9c7ba6bdac1d4ff5055be609abef7fecd7923a753a704da537c0ff41951552420bd78d14cf972dc84fa3f5dd9000000000000000000000000000000000805376b814b8a59435310d49a43081dd7ea36dc7dcb40d38068ae9085b3ea9a3b2249234234cacc76724d8ef84a2eaca0170d7b7604b8951a95d49b6697e2d0cd2a41c3671d8f96e936cca911dd516d0000000000000000000000000000000002288860f2d671c84c5239313b7f6b82e31c3976e6d310e15d3bfe1c566e2ab5d86ae6ed0df02530f9f7893ba419f1870000000000000000000000000000000017365bc096e260f8dd7b189fabe10eb66923783b41fff70a149251576b3b465c13230dd0af13cde562751dacd8298335000000000000000000000000000000000fa8eb9c818df27181b45a74b333ab481dc7212e417c4e12634816f9e177064f9e1101deff26156d26bc6574db9617080000000000000000000000000000000009379598bf02222e1ec37a721b9ea31a3adc33524c6a41bc58da06caa3da3bd730659f0a80f793a0fcb9c07b43ca929c2c2afc06f19e627e9ec0edf1083823d30ac569346040965e1c92e0c15011c90b00000000000000000000000000000000136870e08ff5fabf36410629ce5c23470eafbe73a7dceb633df5c1492e39445b86ce15c22bf4c421cfd0adc6518e78c30000000000000000000000000000000010aefa3cdf1225da09b796430d096807a83eb2fd5a58db3a4bfc5e500dcfcd472fea3077f0c059620f4ff708f37c95a90000000000000000000000000000000019ee2c62ff860338af623c535979ed31c42c0d0b2f82cd56c153e80e6d92bec9ce39bc8e8f285d1efd1c1e969521dbb50000000000000000000000000000000008ed69eb0a16c8a35d507bc3a50bfc97e18143fef611263715aacf5400cb1aa285b6d2ebf2ec219d2fec477360875a03141d0ff346e46a20c2498a74f910e9bb2d5d8530afc7ba47c3525861c9e8c5920000000000000000000000000000000014abc4eec64f2611197d0c1322c3248eadb725049379e64682f2b3d7f83f7bcea11358d88f52711b3020924b6ddd84790000000000000000000000000000000009fd78c5d1d2043d83be30a88f046f5b633c6dbb11bab25fa3037bd250b6b9d9394327aae25d1939f777fea9f3df46960000000000000000000000000000000010f413640aaa16a95afba98660f9e1b03a8f3e0a7a3d7f2b971f71b5e3d09016ac2b410f97d20471f48621d5a363e9e6000000000000000000000000000000000154b5df93298a5a14a6157819e38db33ae7f2d11dfd13f7f2a92b2fd9b053fbd25f10a8c45db3026f6f583bd56eee0f1d688a1aca2a837e0a353039294a9988a7111ac134a6a8a68e4f881e7486025c000000000000000000000000000000000f1893df99adeff5e4042c4c5e8557e53f7c34efcb2a7953d5347f81d2f4a75ca0273a3845f54e795ac1c1f8ae7240dc0000000000000000000000000000000004856b05d58898be6aba07fcffe487dd895144c7ac8fa8bb1a37c61e73bcd062ff541d510e24c5bf005c8351d3ddf61c00000000000000000000000000000000178b22c2c698dbc4929b119474a741ef44d6275fff5ba058d9debe9475e71398e464aa14a6712c5deeb5010d1c7758ba0000000000000000000000000000000005ad09389c35c45f349e6dcaf1cdb3b63648b3df427ea0c2a371f45634635f9253957ba6987df4aca6cba4cd472308a31b59c33ff02791031e7a9424c781ff17a209d132af06f5b825df363fbd902cd4,000000000000000000000000000000001090d83d501373cf07c75effb1c85852019b39eb0d77226823aa3c1054d4e408e82fbf0f4420a30144c611fbb856748c00000000000000000000000000000000120a1e3795f6d5c4ed5b886256c611bdd209677f8324b7091cdd7cab11788b1c0f780e8b4c38b84d7c2ea528123d4783000000000000000000000000000000000d250df34d906ed421eec2a78c2ff4ed4eedb717358d7ca879d58ff5b4d2d72521082dba6ac5d10859125e32c2c8b490000000000000000000000000000000000476adaed9d80cb1545be505496222dba1f0ea85d38d5bece0663461e0e9d47abbefe95303c926db008d08b8aa162e27,293920, -000000000000000000000000000000000e1a9066289392b0b0b8f0986366c2975463c9cbe7a74f2eafcb3b8b6d4ee3226ea5886aaae374341bc76b53b165e22a000000000000000000000000000000001557cd01b61b5f2361f6b558a87c67f2778e11c21734b7ed25f72a88cc62cbed396d583de4c2190ae6bbfd096c33bf73000000000000000000000000000000000eab68305118d7e7076043719ac1e13ecda4497df2cf392d6aae4b7753f114d30aae3e8535742947636901feac4b620a0000000000000000000000000000000002cfe5014446556b82d60adf874cef25e58eabd035deb4717c93bf0361f37a4a67aab70b95627326bd97f111efeed57f58fef5bc887b7caf72f2a533fe1455ae523841bd49b4adf16cfe87edc6f573eb000000000000000000000000000000000c8fa30f6055357f6b697f2115203428b8005ad03286d2b3c805bf3d4dbb461c30e6ee8b0973ef41f884b91e857c53500000000000000000000000000000000005e1c785feb4c4fb7e960233d431d51a4fe471f10321251d018a950374d2a686d52ee8cdd855a29e770bdc1bc565f471000000000000000000000000000000001158d31faab483832d39f5431a5d8aeb952d6a63b82ec019f235b5b2e5580df8cd91b46cd53d4a90b9db354b38c5a1710000000000000000000000000000000004a389b09be6fb7ffd14d7f3359b17991e93d92a1c0b9a89faceaf71f5ce77a1875aaeb7a0ec3b2dfb363c47dfc9875273b243b83d44158a66eb6d31e7c4ae1f4b3ddbba81b2cf9a654ca7c4ea2147ad0000000000000000000000000000000010587118c5f90b545ee707466ea2c5f378e6795c260235cdf9876aed8bd753aac592ee05e23882ee77f4a13bff97f5940000000000000000000000000000000000a0344aed244b90c4fb9ac337edb01429e09f951062b06025a5212300f5471a95f28e09bbc715417a6d98423b518c3a00000000000000000000000000000000128457cf374e5b8864b8241f476da093f48553d609a5f30c0f0f235ecf7127231237b6c8802f2904a8304c7c237842620000000000000000000000000000000004d55ff04eb09b33ebfe90f2a0966a1b59cc224215c0359a4ff0c09e60f9fe7ad8342868184d8cfcaa1d8c28328864241ea87af09f6e62111c48993c408efd3db9ebe218ac68f61a461ad9ec1306873d0000000000000000000000000000000019e6992c3da47715bf379a668a15668508e7ad27bac647490be8e82759b9b79c996735aa1bfdc3cef217750e4ed36fce000000000000000000000000000000000828f782c5bd4f2de3570a4930db2c020f75f93adc98aa0e48449d29c7a3b0d5c349963d956bab7f985ba6ffe59c90ec00000000000000000000000000000000062c7a730d286e895c57b75907713ebf1d20650b5e621f270f1d22a2ca480d022346def4102a62eebe867210e4b6122e000000000000000000000000000000000d6c29462ad449ee6cd122e3dc00d56dd5caf17a2510e5305aecfe85626cf73adb401ec2192eb693158650893fa67412a691b9635e38a46e2469811405ef6325ae7ef88a67c1d1c5b05806da329f27e000000000000000000000000000000000098de9ab41c289a05ba5a774eafe27d91aa8272fe9f81fadefba9a0cc0e31de20f808ff454a8647c44f5aa632742af9e000000000000000000000000000000000c96019bd5cdd62df1642656f0832ac8ff6aab86f671e18c1c7023dc16b8ff54a8e3e446b19682a23b73ccb90da2fdf0000000000000000000000000000000000178e3b4366b2517d4c19fb40551be6979d46319d7040682241b046f10ab88d269dfc097ae02952d46e69cb1cf159da50000000000000000000000000000000008341bfe1e2fb999f0c3f4e79523c720edd332401f9dfdb8dddba8d1342c2c1fb20ae2fd9dda92c7bde5a0c95ad971f80d9a35f474325d0f065442805cab3beae4a186b252ebae54a567dec6695588f1000000000000000000000000000000001004d60af8c21f7c62fcba1c5c41b94fc77f64b89abcd23a218f0da8f47d2ae6879ddcde52f3e6feeae2dc7b2720577d000000000000000000000000000000000b8e8a7da87aa62ca852e2984b0f12b85052fdd03883f01f4496df0835d1cafa48818b5ff1e3cb0e9ecd66054540a0d40000000000000000000000000000000009c16854580ad8191e3e80a0afa8da759a8b2bfa7e0d556418b5c96d97e88a12fb75a91cd68c2f4336c3ed7ac99199fe00000000000000000000000000000000195ce9c562c460c7e715908991ea8b017b81561b45133427f63cdfbe8f65202bdc8e8958ab0977b3a244cfa32fb35f37c20e998acda67d406a238f16bc2b3066a6d69d2436577b8900a180e6a71b0a01000000000000000000000000000000000107292f77666064b7d80d73ea8f3b623170ef79ccc7c228b8366675a422a0cb8491586a2e4ab1a067c31396cd670a8900000000000000000000000000000000126f8136dd61d61b2a9c0f4af3ed44a3cec3ccdedc74821f341d200601a7bf0a17079c824de6cfe28467e843d0c74d2a000000000000000000000000000000000bcec8afcc7ee56b36d6d08b51f61454c8fb15ec5baee1117ed55af8fc85f68674250334f79b0fce632e75623dd173210000000000000000000000000000000016624d64660b63b70ed197f6a675911b02b0bc6f880348faa6ce4727af74127c509ce8535d8dc8db5ae2d71aa497e0756fb773cde356e2edac3afd2bf703b59161162dc1e915873ecf606dfc0e6efec5000000000000000000000000000000000f57747c20e1b3923c7e1d8bd7d877736cccc0e0829837a086d62d48cb54f323d90b57ca3339fe4b256df529bff11363000000000000000000000000000000001940327a1b319dc4212e7a553d3f49904660722c89636f6a38604d96771fa0fc71f57674b7aa710db4275822c2b89903000000000000000000000000000000001956b81bcf961d16e50c053ca07ae67cb8597138f34a9dad4d82e0e8d23a7e08b751682d588f229311bc63f9598ef448000000000000000000000000000000000208981064443e8c72987945e399b45b74e529a0bb75e99b7d6744728e5c182a6b0a10e449147bcb0b0cbe70edcdd845bffc1a58dd06752a2a77abab835d089599b4781ae51ab998ff3c5b68329068bf0000000000000000000000000000000018c35ca3a63053fec853e8fda5920b560f1be28431f2f4b08789c7a202336c8905a5ffffbf69ae4427f267b1e13288d60000000000000000000000000000000019de96be76bd93886cc486c2671b5b0d731b568638b1b830a52dd4c481b9a1fbe2b3cef14b46e25f1188ddb3c158da6e000000000000000000000000000000001813ab16a11c79eb3d3d47ae7d9a7c05401ee91eb1183266d23077ec4c0c8f3ac7188eece06876025dc3fe271d65d4ba0000000000000000000000000000000004d2a416dc874e956fd6d29a3fb96195019f4136561b4c127541ac171b5a6b229746af6d6e535a8017e64ce06709e52e57f35cfd74f62fa39f919400f4d692855a4b4e9f91920e4306ebb2e772a484f4000000000000000000000000000000000623b7a8a1c24dcc603f01589e6679c74c4ed3452894e536a4cea69e99047092acc877dd0bb395b0cb693cb1702a64a00000000000000000000000000000000013de9dc75e42f12e905d729a52f25bb1a4125f5edb435734649281bdfd41083716d0797b0a80d842c2503d09cc61162a0000000000000000000000000000000006453c06f56dbaabd4530160bcd5312b8a148dbe19fdf9f1e44b7b047a73ee9ef9d981116d00269942ef73537885eb7a00000000000000000000000000000000075376135ff3acaecc0eeea32f8dc15add57e8f0297d053ffaa0fb0a8fc4418c5b142f96b6b9ce9eee2f949c960aed682d1f3709700634653374fba5a94d69163ef616a72a63d462afd9f01c9ddba84000000000000000000000000000000000120d088fc12210c1f5f6cc3d1091563f9a37d4d0e0d2c305b479f4d7e893c4d5c8170eb164e34e4843a21c9eb193d11d00000000000000000000000000000000159de80db3b1f0ffc5fa8c93e1bd54cf8ae19cbc9018a5dfed86179cdbc976c1c312212080ab221806bbe142d496e7a7000000000000000000000000000000001103abb75a78220218cde4bc4c59ddb5fb647ff808754dda200bdf586ee9c47a09e03762bb726b085928ddcc998af3ee000000000000000000000000000000000bff4bea17eae0f2ff3e7f99bfa91e6ae8aea28f6f3fb6080eb644861defdefc26befbb7874f612edac0cecf70dfb275614ed9a08dfd406df00719d5eeacfb0a96413b608974fd0aa1d4c6176b968dc00000000000000000000000000000000012dde607a2d4452c6c060054c8adb6307743edea3ccb6ac34c275717f177f0e454d9e33d4391208198cae39d7eb6f6c00000000000000000000000000000000014cb4d8bc98060ee68a8ddbc44b83db5cb6d09f09b0d608357629251c35e44383e97058d0d68fe2df3bc47424a5dda03000000000000000000000000000000000c14fbb6c844fbf896fbd3cb3464a83aa4c6e9a7f0450ad96a07527df6f1eeeaf587f60a990bd6abe7aeaf5eb46f362d0000000000000000000000000000000001d9468774318ea711b79f16303ce86288cee312af296f1c9f607ef5f97c7d1cb48a7218775c8aef00c227ccb586286e7c1dd2e5e5f630fb1d07e8934dd3ab029917e7775e401c0bcf7e1fd83aef728400000000000000000000000000000000181e7f8d0ec7a4a7858bc96b61484c24dbb9dfeb3746fd3a231a8e442369e3e83516ee6043b1c06e7e2043dc86f6c75e00000000000000000000000000000000184c1d667c0ece59f18fd2eeafc66f1ed530b7d5f4560a6c886429caa13255c63dea01c3e357e3408af58a39420a8b28000000000000000000000000000000000a8475ea694cf607246a1c50064cf90cbe50ad5cf8006934a1fdf1621ba38d20e70860a2b5aecc05acc60943224cadb60000000000000000000000000000000008afa03c2df8e83fb64523c57d0daa7cfbb7af6a4bf2960ebc64515a61a659b2c37ee661050cd538fa00cb34746a371b64e9d16cb61f2bcdef30cf544d97e078fccb999b96a1da0eeaa0bf232f01995f0000000000000000000000000000000008b33a297c8f86f1e9d7166f9e905283c8e1581e582b879caf48585d0bca3608fe46d8d9f6e7c90855aee9d92283d7a40000000000000000000000000000000016962410d6b4b6f91437617e84bfaaba49de0369b8748d2e2dacb63b421e0d7de4514e7fd3e0dcbcfba8baa4915610d0000000000000000000000000000000000efdab72953b870d0e113efa7c183d99aefc100ce59791aabc72423aff70a5b74c577c06ca94bfd6a7722199b4bc22660000000000000000000000000000000013b18e31700987dfa4344384f9b41e72afe92c39bc961333cad3e7d0a5efd3842a5e849cff5655c4673f720fd0127dca35bca9082d66c06761f702dd439faa4957caa70ce0343268787f41a2f4bc0cbf0000000000000000000000000000000008b86f70c8d8b03b0e9a8975776d7fb0d08f95eded0a0124551d363c2df57124e0e89bd45ddd1cc75c258a4ae2f87916000000000000000000000000000000001120eef9eaff7c308b629deafb060d2c12b20b57562007fa810a2191d99fabe9c7d3c364caec1724665ef556de66b57e0000000000000000000000000000000007698bbef6dcea67a2c643342ab2a0f830c329fb6244d4a98512daa8a3c9d808cd2acc0cebbe3da920053ad73eb7cdc7000000000000000000000000000000001155b6beb28fd88d252c6b407bb9f55d22103257287ce77353bea580c90173b5c3d49080b319ea28817d67c52bead96f7980eac6c8db86ef83748d10b210835e53baf8cc9f607915df272b6e28ac6b2800000000000000000000000000000000142b28509d72f9e3be9ee916827fc1a8dfc4ef7ae2b72eebad5db605fdb2dfa4492b50cc3e472df1b52baa6e2b0eff5500000000000000000000000000000000134d6821088ce4a8b42383d5a43a32bb0cdc96c85f304a2601292670633d5e231b9dc479d199829a9ba9f39c162318d5000000000000000000000000000000000636da344fcb0fe50ff3e22f8591418f64cfc722b2860b4a5047f973f42e4cefb93c2f8eb8a14b4d150758ecbf3cf712000000000000000000000000000000000e6fd06d5dca702cc9f199f7583add86c82f7b530d4dfb9faec36dbb669cf7c1cd1260c7e4f3026824eeb5b979e9fdaea256ebae4b204b3888d7bd244bbff26431ab5890098870f13800bb3be3e842ca,000000000000000000000000000000001684f447f8929ec0187811f66e985f0014eba46eaa87de2d4ac2347d10c0550e4044ec7792d9f315c50081dc2097ebdb000000000000000000000000000000000ee0c46efe930bc98f39dee8cc6a792744e84de4fadec035d25ee8ba82e1c53264d0885a1fb05b2b8dc9c6a1846c28320000000000000000000000000000000003a5ef98843099235a2ad9522c9cfce1908bef77b45794e7df9eb38a4854460031829e947a118e8160365fbec3725b85000000000000000000000000000000000dd205e195abef6a4cfa7da66f022a418235e1a1b2fefa6bd3ddf8a3851d8ca8c27652bf87ac644cd189ae55e3cc7808,293920, -00000000000000000000000000000000064698182f90c20ed6f6da7248cea32a291f901876a900d344ce4dc1b07822b480519cb8d891b5ee4f33b4efd90742cb000000000000000000000000000000000e7e9d2e79ec4b07015baf69a283f6a4abc8d7c1699f3356fdad6ea9b1c70e41e73bc14e500634d73749f9900eeb65f5000000000000000000000000000000000002ddbf40619ea5123c100e2d6553213e37883fb34f0f0f2124795dd971892d5c9051cd4aa78b9d20f196301ca9bb4d0000000000000000000000000000000017a07b32fbffdbf7a80f0437eac1ec5fff5a68f3b053482f034064992158b604bc34489dfd41a24ffba806ccb871fff15805f2e8013007c4f6d8abf441728eda8d742ea2f1df545f85d092f50ca8275c0000000000000000000000000000000018b4de9c04fde8b708408efb3aa7f24b5f7bcec14e7d06fd5a5b36bab528e5adc0bbb1e378a5ff6fcbc95aea530ffc6a0000000000000000000000000000000010da98267770a47e5ed14ffb3dbcf537dd14ae5eb79522c772a7a2833be214690db0b4e86621de1842d88018fc0f348400000000000000000000000000000000135548e2eec9ae7c3d23618d8286db13a5a628fee04fb6ec9da980f3a46838899cf965c1cc6f562e71d5b5c7428cabc8000000000000000000000000000000001669fcee7804df9b7bef32e2ffeaf285e8501842efe87c9e827fce872dffbf92255d3c3a2fb5c382ab7aec0bba1ae0e5502d777b25f3112ba2264022e2f28dfb6e5d5239ba097e9d815928be44b6a62a0000000000000000000000000000000010ed20c069bb300a27571adabd239e70b767af90b91c4d0e93d88278a6da47b7c12fcfaf62ac0a7b9966968cc9f3770b0000000000000000000000000000000017273eddc25cf41f2d7734a3866711e83d4f2823ee6a036942799f837d5ceff10dd6022ea25e3c1e28c7b14ed8f4e7c5000000000000000000000000000000000f201f314f66f6b2c6e1365c0fac7b187d31bc45b5edaef5243b5488e26581dee24de4a5fe493bee44165cc31d8d72ef0000000000000000000000000000000009dfbdd86633edfacad6b78d292141a1e653a1bfd8c48a96b2f6bf8271ed6033c0511628caf2ef258eb64cc8b63d8e5be7d64b471cca34ab0c91f61ff26719c7186dfcdef13895d37ead407873736a740000000000000000000000000000000005c4a4a5ffcb4a39c8809821ff275360ff937070cb97a791cc9ec45f429256a6d2d6127248b6ab0b6c71c30c4fe84ff20000000000000000000000000000000019fa60f481c5be953c9c7dc86903a89af0ca2b4205be3a00d793d6de7103852e147ebc7d983c6d6e8cd99e681241ad440000000000000000000000000000000015b3b2eeb0f81ff8a2624e2ff2396bc69feffeef62b1b6a1e73ca4b9e60506c2950fdd23a37cf56387b8794449d3237f0000000000000000000000000000000017021a69ceba3446dad9fcfd8cbe5b89b61372f57d43a8d2e2c8f4534bef6b91408409dfda9438f24526f7e6bf1f4240e5723630020fdb48e44adda735943c91ad7d1e12f3c32d823833eacfcc8b02ba0000000000000000000000000000000007c8f07f22a3412fb4638cb704751959cda4e42e4612edaf5b1f22c8f9ea314508353445114bab6c07ccbb4b0d0bfa6b00000000000000000000000000000000062d087155c8722d0102c8e5084f95f5f58ed626d48197297d21d2108ee05f70f16d595ef73e8e1207a3c0b013fe16710000000000000000000000000000000003b6652934f3acd4c91c6c521c2476bcd2594a939ff2e7ebcbb0f451fcf0a656a518dbd4f36f165f9b2f58054e9f778f000000000000000000000000000000000bbf21158227e0ad5461de9ad8bd580f9e65327dd4e23f1ad55618f6b0aec45aa6076fa88557953ad15d385a074bc7d96e9e37bd811b76133c12268d325ebbd6656e7ed718cd777458867dc98b1b3bc50000000000000000000000000000000019e336d4d342f110eeeba9773b8e351f26bb56361c77fbf12fd9fc218fd075ae38b95f4a8a5ef830fc2cd92558b1711e000000000000000000000000000000000a112725046ca3b6cc43207e6b36f38d96ff98dfe3444d67ee3f4b0208f3b8543768dc9989f936637d7819e7dc5740fd000000000000000000000000000000000527682076572d8cca15e47a2faf62b129baad29afed22d32ea47983a8d0b138653c1353bfc6fbf9fdbec2efe36700f90000000000000000000000000000000007e3c5aff373b5154ae66f978fcd66d09cbebc7e0c96b4a4cf23c4fa5f2fa655410c7f1ce597a3f5f155017720f7c50f7d46516db284a3938e672ad3c6bd40313d77c5d643ffcc59e3f55ad983cdc0ed000000000000000000000000000000001865c265ed4606ed16056c0b28f953119751d7272bb33b9865eed312ba23b32d01733ad5446cea5873c2bbe37fdfce7e0000000000000000000000000000000007018aca1e7ac211921cab1cc6bb18874d2f39f00d916b8f3d46a088a378f3c9b49ab8a296d0aa21608f11b144a0c687000000000000000000000000000000000210561c0bbe5a9f4b2237e5bdf88bcd73326d395277deb2a883526978df90792993e6ee520c9d5ec0a6f7ef5c6b3542000000000000000000000000000000000cdd344124b7b5da556f64ac5d651a6f9b74427fd712007310d720f3236724e2284aab812d739a87f3a1bfe8737dcee7586cf63c5e52b44aaa79cdda6dd6fa92c6fce11d867b2ff5a04c9e44e0b3930000000000000000000000000000000000024494aab30849df790185a4f939954b724c387c9a366fbe833b628577654174f705d05e7d7dbcd29b8873aecd55df0b000000000000000000000000000000000863054fe3e4838d2caec7103e3d0453e86a17fff0dfdb84dd819f31756032e9e97b7be89b636e5e0b642718f6da217b0000000000000000000000000000000015c8bb4fcb6d9cf941b722136d8d76d847fd6d5c643f4c0049c9746e76e49726fd463ce7899f4df66d04e5d48e523e6a000000000000000000000000000000000f101bea4e1bf610d2782ede91da95eb2b0be9ce60485465b9e94cbb9530b416c4394862f0ba7ee8067bb48e94c07c53efaac96bc5f686d6b952e7082236622b737fda0dd3900bec71654bdebc8ba2e40000000000000000000000000000000002dd11f4dacf3d9c46579182df1c1c45a364a8dc1eb7aa7d54d0141306f1c23bed85235783a22b8e6dc4adc35f9193ab0000000000000000000000000000000010d1c642fce533039e98712bdfcda86eaa62d2d69b861ec4fd835488732fcea414cfb6f3f8414152f9d5398c73a74fd2000000000000000000000000000000000c6759b75b1e3fe86c00fa124d09c5b7438ad61fd1bb71695743ed7793f39b7a0fc99b055201ac1e3aa07ccec61b24a80000000000000000000000000000000017580c9341789484fb31386eccc9c344539a09f1c4421dd124b1a0ce61f2d0528942f7fe8df67c6b2bbf782996def47b39d6045573dafd09ab2a0d8ab6e97b0ade43bd79d820749ecf19cf7d99792ca8000000000000000000000000000000000d9c48a111c8c74bce8cd78d127999531e46a411b2f0be3507226766bc8abd088638a237674ac62e0fb7dd4a86d09b79000000000000000000000000000000000073675bb81e2bfe6adb5cd929e0b7280f5d60b3dee7f797d65ffbefc2c2944a9c7207648bb096f13292ff4440c3f03f00000000000000000000000000000000024d2e0d5ba1a804520c72331fa23a2a326d461177fa527473240dda130f4ef893870e893e1dbf7c5dbb0178dcd29b3b0000000000000000000000000000000002a4c9487485ec33f8fb347d246ab0d41b883bec30d2a5e88cccafa676569f25ffd8341cdf6c09f68afae442a574f3334c4a2ff4ce4b633ec8fe0bfea42ccc329b7d3fbce96c26989b3c7a391c9e806a000000000000000000000000000000000c1965a745e42853b4d54739b2dc507d68d80b330360a4020e4412ba5422daaae313fb9597c98575c66ccf351e62a527000000000000000000000000000000000844439e6f08a411e61d37b5b2b07921049432e1833e839b00d6cc11227dfc8770ad9ca06037043668fe7ce3bf3ce84200000000000000000000000000000000152ad6fabde2e0310c978404a5244209a9363cab1f3ac9f71339cdad6d40c84f8e5a8a196283b581d0209ce90e1e3c6c0000000000000000000000000000000010eb6af62c7dba122b0e24e8326dc906370bcb4ba791c47630f05f657a228c20e010c065b93537ec84fa14a756b199789af09ef1f27cb83189e4e13f3801c08d3a2adc8b5f88717954ee84499defc0c40000000000000000000000000000000001febb2cf2d664e4a277cbf08fc1fbacd05db415a12329f7be551ed56d67f0b5dcc917d1b02951657bff3a26bd8c178d000000000000000000000000000000000018af160555292b2f7ce27112c1d60038b564f5427d62604387de97dcf48e4473107f91936b5e8008065a1537f7ca340000000000000000000000000000000016bbad2a7f5451098294a7cab2fe10d206741a99b128dde5eade581d02ca849bab3662fc3400fbe055dd93a418aecf0b000000000000000000000000000000000b1e9586cc1b357da6e58621ce09288e62a79517144f6c6b867359251baad6d40217578d49c1501f23206b125282bdf4c72c1dc1efefb775a1bda754ff17389a6b6b6bb25e22697847d24a117eb8974b000000000000000000000000000000000b88892250c848e7bc7bb7e42cfe1048a1f61dc546929211846f49501ad8c7c8817f5b5b99ed092d5a2236d59d9c8eaf0000000000000000000000000000000011680c6549f6b7d9d187a6409d40cc26554df654083f1e8a47dde826149d68da756adfb1b65bbd219f79a10d8454e881000000000000000000000000000000000f9596121dad98bf7acb3fd65fe7e0bdc8924e2390341c11d9cc9cbb0517f988ff79a5e1d60bd89449b5f042f0d0b0c30000000000000000000000000000000008982832ef53bafc23ea817be378532b95b5872217093e7c7c2f4512d03a9c9a6dbb7950563a520781c7ae213fc82897b4a0c7c2e611a24c722975ae882dcb4b45e6f6b41cfc87e8c766beefd5b10bfd000000000000000000000000000000000ea5bc2f8bc2b4088d1fed7090ba389577b11a3ee0775cb3f0657ab5b07a6709d3a18fa5fc33554dea235c60baae4bb100000000000000000000000000000000196b6259b06a4c91a0bb0adecea134c8609cf983c2c87158a69c9de3b6768510fc56543a84d1266dda78d90c3b0516ac000000000000000000000000000000000d0222d8ef278cd0d85dc8765fa7c4256394a5ef61f91301af6c7422b4cb17889224c75ccecd2df3ddc9bac98b493863000000000000000000000000000000000548809ce26cd498816ef1222d062b1ebb7313a07e99e3aad1431f984e9b8ecfd43357ea57da7e0c6c011c5d5400f7ba986d48aa5b00fc16c36dcad061d10937b55ec4deee63cc2841b7ebab84f910d2000000000000000000000000000000000b95455351fbce6f73de0345a195f91bf96abee361908cea6c4dcde72048a13a9a23991a75b9c988ba0afd9491d15696000000000000000000000000000000000305f29b05fed06ffab484cb065d4852eb323fda8c9b7c0a78843bd7143effa95cbe5e50c1a0c3a9675bb5381709b6550000000000000000000000000000000016ebcb25f1b8e8d7a8f7131455ed2be084bdcce40034e7ef24a47fc29e447f912c20c7c9910e025aab975cd2c8cf1a96000000000000000000000000000000000d84a5de7a5fd8592f6cc2bc7c3d93c06e26185787856c922d95eeee345ddfb7cbbb60b6d992c5ea4dfb33101f2ef1dc979d4df836daac0960fbbb8919d2f90c3457cc987153def711d6e8a12fb14363000000000000000000000000000000001377d654f80e933c4598aba1f637d1e37d66a96680c3a89a762f412e187817ec08f0ae897b08206a73f1a423b742261900000000000000000000000000000000014b71954b9bc22ac22cb2d7d7f373c3238c923205b223cce6c219175df2bb6d7258ae46d6cdb019311bd386275499fb000000000000000000000000000000000a08ef83b67bc972a67b9174d0e5b1536af882d505d03464c9a97f68061aa319d612de9db84e1e7b12fc3015fc2973b20000000000000000000000000000000005f716d0ffc30005e4a744092704a9e29f58fb06bf7d8d6fdbb95a4c0eeb5c39452cf662721ea3e0bcc67f25931a109425ae495ba75cdd0bfe200ee24d813e1aa93c100ce861c9ed7fa5537e11778990,000000000000000000000000000000000c53f0ca8901f4751be4a478088b30dce70b9ecc382455049df9ce108eb0a8d2696bb325fe9ebfd7d967ab5b9b2c2bd800000000000000000000000000000000033460babd2984a5d8b7002409349972f518364e92648927e223d7a3b648e952482c06cc713bdc29ab83f2646e9398510000000000000000000000000000000007cb9dfe603dc070151cc477ec5bb5a2a949062e8442399597c5eff8f1decff538cd0aef1384256dec73746e63a6c66c0000000000000000000000000000000016b56ee9b21c533b9c464178d14ba5c92a90e6a54c3ed319f487c2082b1ce1d0ff81131a5fb3dd7d13e0fc1d9ad9e4a1,293920, -00000000000000000000000000000000104e0b91821c59290be48b97936458af89078b176b5585ca9a79070c7050309b01df4b0bcd84f137f58304d90599212f0000000000000000000000000000000013b00ece925fd17a8effc43e21d982553ab2764b13defaae5e5419cb9a23ca7436cfc44088c2aded63785e4f07b6e186000000000000000000000000000000000267cdd42febf0706675b60af8c0953582ced84dd5ae870815654cffa46eb14b747fb8fbb3b014e59c929da49c6908050000000000000000000000000000000011c5384d7c3e0f4fd66ba4b4c2ab60f6f78f9930e1fed233263dad25294814d9e2aaba6388ee9f924e2a323693b6e43bbb2a329761a3d6a2e4d9d63d7bbf7fc6fd321ec0344cc4d7d1b6565c475ee9670000000000000000000000000000000018158ad70994584e6f2443b8b96c1e4772a00fa0bf74865c76000eae470eb02cff627579126cc465046d4e088782557b000000000000000000000000000000000d72979d455733756a0849baa8afd79e18960f3f6dc9676c33d1663961617831f3266015cb998fff28b78300c87c2a73000000000000000000000000000000000056192c20cbcbde6099256a8f40c78a32d3fd212fe9c511951c7523a3559f60662e070f5b5e5f87b1686be0bf6cc890000000000000000000000000000000000c7b7e8ab7486012d95af5b2474ce15db612bfe1508852b8d99f4402d0e4f075ba056c19df3caa3a93bb4db89443096143cbc3dd7ec63ac63618a9e5da1f9c3fb952c6fc6972dfec6caf1a415a0aa79e00000000000000000000000000000000005a2741902dab47e8d38992180a9670faf56d1849dbeaa75b2b4ded93ee5494184c8658232e9131a8b08ac9b5460bd400000000000000000000000000000000189077d5130b3a4d7d4c3074633fb12739f95b8b6ccb082dfa61d845a389e6ca7aff835fa0f194dc349e1584b3141507000000000000000000000000000000000f226324f242cbc5f616c4a897f82bc5503ab1963ca38f30070c7c9916ef6bef5caa7e2e26b3f9fe68a1d59f19a9831d000000000000000000000000000000000a999bdfa10e4838ca69694272b0187f7d0198d6db0fd85eae688424fb09baa165c623dc6da567fe034d7cf9f9a0087e733a3a84eddaf3af8c5009646a899f6ae8cf233f535e360e29e2952088ebd7b6000000000000000000000000000000000fe85d976befdae8fd0ad33a4404415304afad1c5698b91bdc15abb4f268807c906410a6ca827320f5271c8fd4c8d6fe000000000000000000000000000000000cbff7963daa20c1d20717bcd47b872b3ecd5f38de1a467ef50936f13d6aebd978116a736cb6c5d676c6a9525bb0b7fc000000000000000000000000000000000c3d20ba17a21bbfe873d88e9221571f1bae7f02f35b8e677c9c42907673d765150c737f0011fdbaf4faa883b0dbf0280000000000000000000000000000000013482c68a5e1084faf12e8aec92cd9f0692b173556ac8ac3c7519beb4bd75f847f41ab9432421c631b14c885c001dce25112b5912aa3cba657d8de3dc8138fec92b391d5f988b82e19f16fe52fafea71000000000000000000000000000000000f9091a0df2c989e12a844c447287b704803d1532a3ecbcc890e6f6a885a54b969c53323c105b3d14d12f2cf766b8ac8000000000000000000000000000000000e54f3a9def8b3a9f972726e606195849584b7197ab70a28cf5644cde15e70bb6e3044042b649825adaf5e37c2d5e614000000000000000000000000000000000cae412d8a3ee3c5af38d7a65bdf2440d9cc2d6348dce0791f4a7e71ac483d7487b6c789be0a401777de3f57ec65de820000000000000000000000000000000014df09fd2ff406707004f6afa366d06bcf8bf18f5fc4b444b07c98b3f358247c6056a6337f5b53c35db45904797fb4455683e0b33b5463bc71283f0625269b2b33ead69c1eb7b23a996c31c514d06937000000000000000000000000000000000a8aa422e1d58fccc84615f9ca4a4743cf5efe3a1066c9819f05042100bb8784fcceffc8b3a739f549b42f34d62629e7000000000000000000000000000000000c737cf78b10e82fc0cc9823891f1a5f1e9229d61e8f369c589512d01e5180246db46e4f09e811464c6e1ad930226d390000000000000000000000000000000016017354434899e2285da6ff4b27fbaab633d962197d2ff4fa5f688c4a85e1817434cbef13a6b018df4e359d7b9ab7cf0000000000000000000000000000000001433c364428ac69ce4f5678aadfed4e6d076241519310686de01572da5cf78af4a98b3502519beb0dcf04b748d08cac5bcc597c5ed7f79173942a0250e618c93cd0917b37b5f354d63a2c02a576080c0000000000000000000000000000000001f8b803f3f76aee9825a9a960cd2f9e8aa931568b32be6169036683b4e6d8c4abba6bb73b137c7c6d6b6ea92f2023ab000000000000000000000000000000000fe9edeab60bb55990ad2c85c8fc9341e81de54324652c08c615a745813f08153bab3849dbeffcf4073f087f7c0cf0f6000000000000000000000000000000001955289b1210fa31542bd89f95188d60751b32e8d54f1d4d280975850e57db7b151b872bd431c528c22fb89c9b8784af00000000000000000000000000000000079c8a56c72adb9fc9baa503db394635abb10264dd43c60f2c82d041d43240321ac1028688d92c4696395d8840d52f15f2613a8e50fbc6683ecdd7c7fd38b4caa8e5dc9778909fc8680a58b16ebf40da0000000000000000000000000000000000b0fd79e62c6129fa115d821b8f2a58a4564f5ccbb14088f59d5e6a17a64e803f32bf8e5a415aac4d6491612d95ee8f00000000000000000000000000000000008d837b6c70468e1e10f6b979b7c0694d65942aac48b5baa829c191579186314ea35fe440e6d843fded02b95f9816890000000000000000000000000000000015a05bbc4607b113b37dc0b4b8add23736e0f1bb1e48aabc15500fa6941b17153918d256b6442687a432dd9ca9a198c70000000000000000000000000000000003546953d97306266bdd359d4daa939e05c0466691de59d2dbe3584e2ebfd9a9e1516cdc9cb643c5d31731835dfb07c657a747bc919991ef9b7b10388bf3f301fd910f807ccd31e322be46580a71b7c60000000000000000000000000000000009a4366299290c3c6651b22865fb22cc972a05ca5981f5682574851e41096d531e375e981c4e1b1cbfebbc70a41bb6ad00000000000000000000000000000000001e6fe2097fca2afb8385a3100dbd5ee1b7ae972e06ef9f5e34eb9fbdc65455e1c822299e06a9dd5a3f71a0c1efd44a0000000000000000000000000000000005ad2ffa8861848c46722a7924ece68580fe44e03157c982b7133361e974b59dab7b75358fe498fcde9f68b5b99f23e0000000000000000000000000000000000adac33e0b7e6740c980a4f297917fc4fc13f53a71909f2eecd0067656c6f82c3b371cc638509151bf937f8257aa415d86ba09829f4bbb383e2e131d554c42edf1065022975655c07df2b3445a3e6cbb000000000000000000000000000000001462d509503d2c33829c3fb5380199b79b970c2ae7f944e54a6d0f0deab3571976916cfc311ea6ce6128c467665fbbd10000000000000000000000000000000017f6fe356cb0dd5bddd489c26669f0f365260bb48a5f862e9bfb778a7ff5392938b905759718d050f7d93f107236cc75000000000000000000000000000000000d9b3ca93c5133cabf3d3daa565bc6b51e63b7e37f68f3bcc43b9b3ee7db15f8bb33052eb7e332ae3e9ffafb17cb77d60000000000000000000000000000000017d6b898d9799385990c9dcc3f72ed93333486b98349ef106a230a71d768b75cf56cd946f5952075bc41f26dca9c83c003fd5e91f590fbe171aa3f006617b20ad645626c970c2351e048b2ac3773213600000000000000000000000000000000158e5e008796c10f6050826c29523864d06e68977cdc95d281a8606924aeed0b475ab152bec5bfca8e0ec53691b307f50000000000000000000000000000000006fe8e75328c067546eaba93f4be2b15513bae4a3458112c3ffa457d15c23636816fb469f071889380f31870d713e949000000000000000000000000000000000b9b21cd58f8742ed094e9b770182f6f3f855204d869e53c02d0c242a133e957c53c9fabc827d6379b39541170be313000000000000000000000000000000000014eaae1f0789f0b1e8ad3b452b4ed3ff87bed49ffedd13c8c35c35668c33537b63050c06a5bf3d88d516cddac13b4c935ee16785c004dd2a01920c52d3244e2160fec2d17a519974d4331527cc627910000000000000000000000000000000019f976b3584ffc188424614fd287eb79f060c55e9b3dd2f3eb99760a7cb5b70e2b62a0895b05e7cce2e390853fed61b3000000000000000000000000000000001117181241fead3865eba4804ec2c14f571aef5351d5bce29399113d007cd4e9c262af1c77daf9183346153e562864b2000000000000000000000000000000000f823f71035a4870be2ef20bc94e97d74d18c0a1be9895fb27c54df1f663df6f9e6e45ea5fe4502143a84c05e517b02b00000000000000000000000000000000141250f392fabd4566e0cd3a472a4b2971a432a3a5e1d9c924866c7a9516322bfa691e9dccdd5ef14c561bca6dd70ba204a6d6e29336015d99e107cd312e300bd54f815c785f6008c47c99fa008452700000000000000000000000000000000014d6827b9bc782863491bc7c544263f58dc04c18e08a87ca2fbb5799c4aa70bc039416a85dbba67dd83bcc27b70748670000000000000000000000000000000016c2816e93ea9d4bd6e42a9720cb89d637d88e00074da3300c6409be98a03403e9ac15f83167cdeb13800ad174ac47f10000000000000000000000000000000002aebc0116a62f93a6e86c7fce86745618e08f4aa9cebca7b520e9176bcdf1521cb2bf7eca7f7af9487fdc82dce76bb50000000000000000000000000000000010684e3254207c4ccdd49e4775198df981afcf7d9f89b894e204c5dd84ef42b89fe3e2f6b9278470e6cde4d3f4abb3b003f9cd3873dc6243748e16e4806f8eaa339edcfdbf4408a8e41a3df80c9816210000000000000000000000000000000010ab1d5494509060c9784b4744a0572a9466d6c374524a6d338ea12ac5ad89519217c462c3487e398325439311bea86400000000000000000000000000000000197568cb53ce03f00aeb04278f355da862be757366dad14ca6d30b3a537df9855a1196010773768a91cb4bb664a34f0f0000000000000000000000000000000001fee249315794d30eaf929f44b99e07927194c6015ff34a4530698d7d68239240c9cc48530d52ea06218a826a655cce000000000000000000000000000000000645b5d701bf3422228576467120935f014c754dd68bb3555b50aff5ca04001a26298982c97a64469aeac3432784efca34135a2e7853c74725bdaee1ceadead7b4c7d729650df6544bd525c05c94234200000000000000000000000000000000113e17730f8dd7258157085c30cd9d1950a26c848b55e3a8a55865eb567edecfb09f32ba27fb3e2096ea00c30f31ced8000000000000000000000000000000000076db9ccf8df9530b64cd43ef7b496d1f432885062406028901bbfc5882fd12533f84eb12aa2ce8b7adf9dd980db0870000000000000000000000000000000015e487de49f1e494ce9907cf0ed31fb0a159c5290538ad969b2c8a504986dc9cccf7c74a61f622154e928aa2dd689c0800000000000000000000000000000000195e887083a98fe3f50a9ff4b342e004398cdfee55c4b02a4db0f65a77d3c0b142a45201674726c96d5f79f8604d61860033fdcb731830951dc3c4b33f06310eca51762cb7279039b3d7d9ace93c5f2a000000000000000000000000000000000d80c7e50973205585b20a068c64957cf4572eea40e32ffa8b759c38c6ad6f4468421f2fd6a6f5da1b0d008f625b3e6600000000000000000000000000000000009242dc1de055aea82b3b917f88b6232c550c3aff41241a7e54caab4c234d29b5d8138968846f7c754d73ab3b4e7913000000000000000000000000000000001188c31a9d8359d737576f4ce7a7900314aca0eb3b51baeccfdc9245bffec49143a11b3331f9126b01de0c307aa4e44400000000000000000000000000000000104ef4835124fa6b30dd551653aca25db5a544af6782cd0b1e7d26178253e0e33cda77428fc1dbcfe6114a758cab5c814c8112ebfe12bf44e84796e8b0cd03a93d2164d6edf1f06a5c520330a177da87,000000000000000000000000000000000e79d18633c18ac818786bba87d09c9bb1571e179d8769f8fb82e2e2b7a6a8695c1f4f06deebcb84524e8facdcb49d0500000000000000000000000000000000149d0231fb030a1bec170decd307c10e72cf1cca55c8a1b67aa94ce61e4c7d2ddfd0b8e71598e1abb054355dbcac1528000000000000000000000000000000000090f5be784dbafb0a8aab1516c773720341de6176017e0fb43a275d60de54c1189144956d4876d989232b362b90851c0000000000000000000000000000000019dba28eaa6706361f285b3abebef68f764204c74ee93ea011db01c19591ddc6f98799fb3026c3c223effe4489a7c676,293920, -0000000000000000000000000000000018a6a982acce5693e632901f3136eded40071e8c7caa7887f302c32621c5bcf9478991ca519978b52f8f69415c0d070b0000000000000000000000000000000013420ab920c8ecad5b2f9aaf9b0074c2386b0b08c81923558770d4c4a6b206a865af8322e9755706cd5e595bf0ffe564000000000000000000000000000000000c0e5bf5465d564e3ce86d6b742ca687448e6952439b1ff44b86ee6461464e07f8039e8ae7a301c6caee7eb99e38fab10000000000000000000000000000000015eb8751b750af62f57971e88b436658758bd5712f98861fa07328d2b11e8725fb55a2a00252e0be06b0c73aac0f7b8cdbb32a4fd8b9dc58a382a7e436e23f49a134915372553eee8c605436221acc80000000000000000000000000000000001328927910ab502e573188271108706152f562b1d5f6ec074f8f9ec5eaecc6cd5e8284a060b65d26463d22c8290ea4ca0000000000000000000000000000000005a1fcc348122350981dd5090c865a2aeb851ba8b6e0443c32f48b157ba673ae5652a70390888b3458afe6fe975321700000000000000000000000000000000019edc749a9799c8d3df75d4024791943a8fa02ba0cac90b6819f0bc42687b044457bc7cc6073506e8fc19af37f224624000000000000000000000000000000000fff20fb2b554b63758963c1583b996ad450cfbd5ca9952e38f38a8994809096086ed86311f7d73a0a5898ac261ce09e57df9664d3e17d9d46a886efde4e37e38859893113558843bc019699eeed8ec00000000000000000000000000000000002a7005dd32bddf1031f27c2ab999604c048a37c39734db48a30baa86c61ef626cf82084651ae9ba8a265333060a408d000000000000000000000000000000000421bf913a25108b8f520b2becc6f8064029dc046d0d5effbef31f0af59eee71cfce83fec8dda7983d50c6d5cbc8329a0000000000000000000000000000000016c75708f1dbfbeae3b06e5e9a7fb676c27100b99deece14d979b32a9c3cde6e9e96c8560a00aafbe6e7decc84e7e2780000000000000000000000000000000000ce23c27b5128bcffa424fd1d181d21b06b77bd6549ca5eba9a28cf18bb9a979270f6a5807c640dde57a0cd4f3af8cbe2b433b7a95c26e598002cc00b7904816d59baaba79bae7c6a7c26dcc48a487e000000000000000000000000000000000690c7ab321c0c93b5ae4ed77843ff4030e4ffb504c685d28573e98836e8e56dc19d662ae9f496a346bf2a8be5396741000000000000000000000000000000000fbbe3861a8d202b10801cdd606b50db0ad6ec7b923b90ae81ff5443676c3399e249e9efeb47b72d2b0a54cb0594686500000000000000000000000000000000148a27016968f0258e5eafe0a8182c22091873a5a58b27aa2160674584e06d5b2f46fc57a00617af18d0688df75294cb000000000000000000000000000000000148449d00b3d1b5b43b08a0c6e909a2d9c66920b60224a2c6a2521f0bad35b99e3bff8be0effb2f7f34438662d7a4882897583b53567bcfdbc63ae3e864a9cda24bb732694a6b27415c5212c7f45a9400000000000000000000000000000000026b55509b81befaf6baa682a3e92a0ab423fdaa84d2897613fd31acd9e1590f81581ba0ba87d68af76b01c36093e183000000000000000000000000000000000c675e190570bc5173b8f508d5bd2768c83e7f56a08cddbc636792dd75386939942827617c4aff8628a74b74195adea20000000000000000000000000000000014f59f38ae9e77f3a76478ecd47f32200567bad11f191d303cf15d7801ae7b5a3286095fc8726acc9818914b27a776bb000000000000000000000000000000000da89fe9493b2d9d46596d80162f5831d4fd8cbb83b46e84e95d5d684eb927022ee62ebc3519442007fdc543701f97bd2f7ff17e54d759eb9c51e16cf6f12d645bf2d091427416b4edbe1dd21947b4d900000000000000000000000000000000170e52a240a7ccf2d57ae92ea8dabe62ca4b458a5da42319ae89cad22ebf13541b0daccafa1b1d3cfcffe81b500c4cf400000000000000000000000000000000174879425f3bfd40fb74a88e3dc578e45b0e0eaad94da009e4076dc42d234d78248ec3a035666dd6de235f87e1a47bcb0000000000000000000000000000000005aee47acc3260d11fe0ca16050a29f92763b3cf8ac78da52b3b2b3e26d8ce7b6ccc187fcd81695aa456e9b94a84269b0000000000000000000000000000000005eb297abf35b51d57474b4989dd8f793005bf8e82e49859c41b786ae39217b2321299829198bda4aaa261a2723d43d6ce0a097efee666c22d1dd0ae8c8e11283aae781e1deadceb3ebbcbc5e5280a61000000000000000000000000000000000e49e94cfa35d8ade2b76865cc8be04737d00b48b195078c8085cbe782232a544cdb548373bd8ad0282674ba5c96fe0700000000000000000000000000000000047d59661f095c41bcc27da5f260f13a3fce334bba216b45df548894bdebc691fe779ccd63d99a9872973ab165a90c01000000000000000000000000000000000772e9a9c22bc7352fdf74915bc464de99ecd96420ef1af6e8bd5a05d73fff89c78e28eb340d4967e906f28afe1320490000000000000000000000000000000018bccff27bf9d7cb2159b9f2d1faabbf8591b53ca8e67e661d9f44f6dba6296e3e46ac32c50128bb5fb076cb8f214e277b2baa349884b54b542e3993210ef002f70c6467c7d512801f0003da789c00580000000000000000000000000000000002d947e728a3b376de520bf78e56452930de42544241180906719a24d72df65f8250402ccaf14d69935b1ecbb0b4d34c000000000000000000000000000000000d5614ec77a9f31915dddb3e4bb533db001702891a45f0bbac49e73d9c19a235a00442b52d452d77018f883706a616f1000000000000000000000000000000000dfc6a73a8e36b7b2d0614b1c6f7bf1ae284ed740c768f08416c0c09a601fadf3e4d7b17a93601b1803d19a04ccd570b0000000000000000000000000000000010d6a8e4eca2e818d6dff13faf0fae44a7fb90be436a9ef3aab05515a35cebfbd53e9af866cde1745f0e2c3b045486dd2b94d087c3ea101649ed57ff308dd3ae0d25a1ad8884763cea1b0b7c56a3834e000000000000000000000000000000000d6c5a6fe9b4d4580f8e1d89f0510bf5dd04e113d6ae5db04af2553bc0eb3a32fb881300f638fb33f7c4bfaa10b063660000000000000000000000000000000013e001b08191707ad98e21b3e0830286c6f3bf587b971dd4ce39e55f06db427676626a5c31c4a67a996a5725ec8f402c0000000000000000000000000000000012f86ed85113ed1abe9dd3826423911e63df0dfb51ad3d1e0e0318ae95991a6a11150176cec77f9c83268a322cb7e934000000000000000000000000000000000dda719cd2cf1aa769f94c21af20ab076b8f024e0a4903e38ddaca21b6bcd6f00baf7e1ed23259f135eb8bcf9c3f97c44f8c35b920a35b71dcf8d15a8a826e5a7c2a2c4f1ac2c2e3a6d100363e7f541800000000000000000000000000000000195ccfb9038bf9e637b88c83c552ffbd562357792513b15f703bffbd373ebaed715a6772fa7e6e5678c2e6422811dae1000000000000000000000000000000000c5a110f31d71b12cc42974003ba39d99dfd91769c2e93393449083a9b84d31473e3a7dff7ca40164e6e7215b03f44ef0000000000000000000000000000000006233b2dcfed96559b565928a494f2a50c2c375b3d7c60ee6b286c538f4fd5ca6f8b2a61654fd04d679bb3e05b9bcb03000000000000000000000000000000000d42233b7b5ad809c735c89c455ba1e8fbd623e1602bc729c01d362368666e4f90e7b076e32468041f3f5665c6fddb0d0ae6101fac82c10267770e74a0ee16b5be6eae2d455d742303a3c624d52aa726000000000000000000000000000000000f6d53de4f8b20de19b2fcbe8a6b8b8ec4bb801bce7363f89b133532ca7ce4925312e23c618a0182d158037c0d0bf07e0000000000000000000000000000000006ce094e24eb14b9bb1b4a1838d8b6da5f53b5c5799ab8dc8934b488cbabf698b99abeb016259a4e1b0f626d27f2c950000000000000000000000000000000000874aec7c8ac360e3980a6e2cbf3f7468f1df7a8d9158f8bdbb0f387d19f3b05326a081129576251ec41a926f670e58f000000000000000000000000000000001711c9b2ed7e2f789b29073f180e46d0c373d6e75c587ece67b8aaca1e9d9b43a96d04dfdcd42f943eca48e240b72ba8002fb31d0372e7730499b26d617b53ea04821c6eae922326d755a0df31b559ae000000000000000000000000000000000e8ddf88269aebf190bf9bd7a8276de92ff6039e479e42a490fe4ef00f646b049eb8ec4b8e073caa000bfcd86ee8724a000000000000000000000000000000000a9623655c0121ea0575de714e53c9e304fa3309f00828ba0e786112781a38bd458cd67864ab17929448171b5937c1d900000000000000000000000000000000198fccc4a333322599697e904e9096240b9c54f89ee6db97475beead62ebf730da1a179409133698ef13abe1310689270000000000000000000000000000000017b059ac08a3fcebde5888bec4d7cc2c70b147b3b1483fd001330637ff1c036faebf292801204bf2ba49350795708dedaa846e68337f4e9c99dde506a3af792732342e3b836376d4816557fc1fc9b916000000000000000000000000000000000a36274f33b4dc09e03a5ad648af0913e5ee95af83df8b4f2a158456aedf0a0528f9b4832b11162dd67e4d22b26e9f940000000000000000000000000000000008ce96d8bc0aaf2dea732dea188870d398b1f3c266b9bf019e1046cca05002416c910e02e998a1604a17c333c65c99a0000000000000000000000000000000000c1a0e4a80bb0331a94ed14570053f941a0438794e6f19d976cc62b3806a565697720ea03c2531004f13453991bd99bf00000000000000000000000000000000184bdae93abbe4d931a6a51ec85bc330d6181da2d34f2cc530e56b6803515ba87f5719fd6fce6a1a8bf1ee5a968bbfbedf9035283f1afc294ee68b2668870aa45e483d208483d9e967b11990cb55d8600000000000000000000000000000000016c3782daa55312a7cfa02c3be73ed75f4b726df5592351fffae19121b5cba73f427d35d5a2df7c63e2a5c68bf57f3800000000000000000000000000000000018b608343616eff759d512c97257f2103cb0909afb4c24a1cc9d8204274b7c9ed51bc762a6280e223a6116a9b23d1f1e000000000000000000000000000000000c687c11a879ec285180cbae3d2e4219df4614e238d4cbdff148ce5a8d21647c489ade3bf6f738052f149fdbc76c8bf6000000000000000000000000000000000936b34fea3a2633b9aa32244329891e332745876d05f95e4efdef859b23ceab4869db562555e5c8edce87a6fd075ae54005df80aa522e889e7720a9f2e44e6e7e19c3160ea282ec87a4b446d7b1c45f0000000000000000000000000000000000d4636a5e13bb59878319af6bb7c98e5d247c2c9cc970b9cec98027de2d4a8ad12d50906fe302c3d055c499a3742ee30000000000000000000000000000000002b0214bb1ee887a7ff10d458fe35208573456f685ee2fb93bb470762c9e27595cb00f2eae7574c8467e417c63c2a960000000000000000000000000000000001710d130f91861230562cd7ab87984ef45916af8e1168fb17b9765183d9d3f9b2c81c649687842de495a757471e28067000000000000000000000000000000000dd15fe505b1364f134ee77e5e3c1a497a20849b6ec7e201813677a1569a9f5a9edbe3df4c36bdcf9ada139b20e048ec893c9daec43032946a9e892dce960e07d29b304000378145148b9a24afd151570000000000000000000000000000000009a48d7c55d24ba49f890791d0f6a8a5ae08a19177575dc0d734fa37b52c3adc45b31b5e485a5d4a5533470c3549f5f900000000000000000000000000000000090f680c6fc1f0588add04ee03bf821868b1ce588e3ebe384dae657ba7885ef74da0bdc98d9d9594a9b979d5b50b93df000000000000000000000000000000000314f6aae1e99dbe3ea9ef85db7e1693a30869f48e05cdb073bf8e14865a671e75abb875d1b41f13d4eb74fc802299c70000000000000000000000000000000013c698b76dd68d1b9ab41672c2b07cb9a63168497d1144b51509b602c5acd71ca6cd049616d949214d95ab7a906a8f8bf685e6bb7713f8fe202c05dfd18003eff261456026a5185ee9e68aa821fe7c5b,000000000000000000000000000000001747f6d3154e0717435fa023754f115ce2a2b3241b62525cb2833473d84a8ccf4c95e3ea030f2b8b0ccc61124095ac86000000000000000000000000000000001827ed7d84a61c21268857036e91c732b304f609f285cdc4736c951fd8954b10267a8505f25d8be666792358632058b400000000000000000000000000000000121ac61f59051e6e89a7c1e2fb4df4b3a5b7773f46495a99e55348454e1d9d42254e5e11b841a1654ff9c80b157389c70000000000000000000000000000000001bc60cd06879980bc6ef2ca109d31f12cac28ebe4d2a934076d720b12f430e1bc4d4260f40045cc7a862726521a69dc,293920, -00000000000000000000000000000000012a6984f0f8967c5ae6b13569a62095b5fe61ec607daff1845961bdd827c00fd56ef864802673dd21d90560fef6cbea00000000000000000000000000000000085ececa080d0f4c996d46c80a1fbad2ac9cff8b3e324aabb67182d79f941927050f025b633fd5119f30bb29b8e4b6f2000000000000000000000000000000000987518a5edfd5ae2616fc60000e117a4f1dd1db68195c3fb68d8cc639e4200945b2864d41ad86fb3e11c504fc1f9766000000000000000000000000000000000310939c7e11b93e5773cfd36fa70020c85396e525840742f994110e20019769abcd339db6881291639c193b987b68ae94b3c88e51af5822177b85978526036a426c9ca1077d594618ebb8fac4cdfc89000000000000000000000000000000000ec6922dfc74009c3750ce2540558c7c1e05cb45a5d651b96427c615d8fc563219215a0ee431c0a4827e40b26c4f8d3900000000000000000000000000000000040a4189d002a0e1ec600e71303575e82414e6400f06b9abf57151a28835d454f56421a6dc4049902bfb94dc0e9967ee000000000000000000000000000000000dfefc7c163c34cc004e9d97d812b2717d4736d0d1c722b6bf1a29676a32c8b46878d05a2d137cb7fff5fed8c0f02474000000000000000000000000000000000e3f0c9cbc778693c8ba88af8306d45477493ed6be1bdd9c81c65341239eb510fc948142cc30b73f570819b38f13e20f6e456b39f4efe6581657f5c701c696fde8acb59e856943f15cdd639c1fa68ed70000000000000000000000000000000013705ca4ecca16559713df65b376c7c5825b4f63d001ebbfce9cd1b592af5f2ddb38ac7c5ce3c5f7af4f39f909887e8b00000000000000000000000000000000179efff38ea1044e91ccad467cd2b49438079ccb4d0fc692e79e0bc374abe064fb9979c4a1f4b92c15cd1b042b501d5f000000000000000000000000000000000b6fda2dbf6339af225515681184843f1a9bcd72f7b1389f186f8d0e048ac16e20967c28e087cf09d7bcac597a85398d000000000000000000000000000000001946fca8c816e1e11187aabc40dd2436533d537ce4639eb2d08630ed2ce402c1806b6c2b3e04a960408fd4d2049849bae5d306f46a31c14de7b2940104d0a4424ebaff805a81f1c4a910566057c81604000000000000000000000000000000001802064095d029d3897725eeb93ed6e3b090390769026120aab6977d0de264a262aa312c5777ba322c9eac29e5396fc6000000000000000000000000000000001410f17820941e6a67b1b4993496cdcf0d4fa2d4fda3d43ee985f2606b1408aa9c9ce412c80c90a0c876cb5ecb76878c000000000000000000000000000000001514e9b2c65ca86713447f2d5bb8395fe8552e059829afc68bc43ba9267ef41ec6d69d06e7407a731bcca77ed5d9716f00000000000000000000000000000000025b5bb18cad46179fab15b2ccef17858f9259a90ea4548852b8c6fca69f0ecdf0b175669bacff1625a7143e762514194ff6d13bb0967945ff3b6fbbc104296805e4fedc3c25bb55b75cc997834de6b700000000000000000000000000000000146eaf5da57b6ac788f8caeb4b2ebf7c8999e03dd839977046ca834fffa7e57cd949e3fd44999a007b5dcf3c8621ba2f000000000000000000000000000000000d859632d3424ffe4227ae14856e05c4e750545cf276c97aa9ec03ebde334144eea670dc68e92b61fc775e477a2154040000000000000000000000000000000010b44279c0c80886e52fde5e71726422da2f9457ff86b21426d80356fad95d5ff3a7491002364d9de5ca99c2500f344d000000000000000000000000000000000851b769a691f0ebb53ee3693833881fed8dc6d9e5f1dfeaf4ab1aa7ad54e2fcac246b70d81110451ed78044a98d1547de4fb2dea292b76d8130e6aa8aff5edf0097de935b252d42a777d4d9b8615ef100000000000000000000000000000000131c9a76109929fc977a0a6eda0a7c71cfc744f5f3654e2221ce84c70787598e24c5d8049f92a7c4d78fdb869cbdd1ed00000000000000000000000000000000049872d2c7d472e090d2975daa64fd96f33e7f934e739633b1d7fcd5e771673ed8820752a0d5c8b0c6933318293a4f27000000000000000000000000000000000dd68fbb592a3957ef893180dd758f75978042add36c91b7bf87c4493b0baa875e1854fbc09e6856688cc241b76ab5a20000000000000000000000000000000006143699816cad8ab7583a72b6064fadb6caeb51c8625ddbf7b2911426cf438534da1bdd13e22cd545495c486c9733f7bac5c50a3a8a37111114c22839c88ce4072940c06f0d8b6d53fed155d0399ed70000000000000000000000000000000006c14301984607d569ad1bd774135e7c9e328be1fe54c3b543276bd06bc0bfff11f299a5eb43b5218c3605011d0ea6d80000000000000000000000000000000012f0a848022f95f4884380a5b8e3637a41e3c399a8d2765aada85dcf4b7c2b559122f792850430681a58ca153be2768a0000000000000000000000000000000016b4cb233e1bd59b7b362c64620eaaa5029c173a05e2278774ad6ed746c70a2f6e76c237182f5d9d790966ae69da5d44000000000000000000000000000000000c277d54a7a72c8528188f6cf29d934cc66471607e5e30d493cd11be6b203bdf734aaf37b686cd7101e8599b69446991c3f37387bad1af3a896a7e66a80dfce2df1709fa252b6fbe4334d02bdced432900000000000000000000000000000000169a3928266375dd5793b7504727f939ef0ed52d69e569b1b75a0e094698b37bc70472578beaeebfd0c3df4bce6177810000000000000000000000000000000008936d470dbb86db1567bb2fe7c09971c6d12b07208d9b1b403c20fbdc05ef8984dd576457fc6989470e40ebfe4ceed30000000000000000000000000000000009cdec9d80f2bf3ebfa9a3316e4250741d0d089245df2fd3c9bba4bac1c2dadfe212682166a0962f78c4bf25b618da900000000000000000000000000000000016521411286cabf3fa2c8f72ca62ca311738fbe63717fd12916a4c9e6af9b05d1f5d65cf60e84d9fc5f7b7645fe9bad570fbf5da3959a49fab7e97b3df3f2a38d16d714dd798a1f04ec2cbf84fce76910000000000000000000000000000000006a827f6149a320a74d9d8c1ae8861c1cb963b3eff899710eda642dae6ed4dbc247a22131758d9f843c62710ce083208000000000000000000000000000000000c83a9fd96bcfd4adcfc6d5a47e84108bd763366e91bf06a7431c6c3a107cbe5647da99ee6c1e57c376d366b21a923df000000000000000000000000000000001604d5c0364afb5503b0e1d52226988d7f7f043ce95e7c0a09d7f96e24a58f089156f0e6d19022138170c1b4b7dd33560000000000000000000000000000000019a11c86f78ce462f46e0462052cc3d342596b329fb62a282a59bbd64c345bd266922b1540e40aac147681754643c2e3e538bcefab5d8d0be5fc143e632e86fc065af3f2f621f293b914980abfd6a0c70000000000000000000000000000000015635de295c16841bf44c73639f047f735175e8906301746837838d124bf0d2a1ebaee142393ce9a0d58107c7cb036e90000000000000000000000000000000004fbbd4252fb901d0737d1bf4da62010c06d690a9584c7631ef5d36f1d8c37486a83f2a1e2db21f05c993fd117c662e8000000000000000000000000000000000f4cfcec1545a08e0e0298753ebcef5f61bfdb7c1b9af71cb4c2f783e4fa3948945d357e8302d99aca96df0cb0fc01a3000000000000000000000000000000000f543dad6d4b797f6fe0b00215a5f70f6340ac6bf7cb0bdfc5bc7698dbf0647e4098413dd19ca7af01685edaaa190c6e30b921d8cd2ca46aa6f3e0dc6ff08d77972fb0a248bd39e90a1e9f32be9e892a000000000000000000000000000000000ed552e94021d0912a0e7563462570cb572b189569eb847bd12ebf976d22343b9ad04d400ae98fa184b10ff36720f12700000000000000000000000000000000178727c3e6ff33be9894ef26347b104023ea0bcf79c1a33afc26ac0ee9879344964fada757118829214cfcdbbc0c5a30000000000000000000000000000000000b0a6a575afe5b0c1e287815612fdd3838ab39e8ee7795855837588614715f6687910c42217ad52c1b8721a9e1c908dd0000000000000000000000000000000018cdbf244c78cae1993400ae164b42c09dab4d8e3707a69e25ffa8d0b96b8270c022c0375f933f16f45c9274132a0a633a5ccd9436b15d4d04a8ee9894c116190062c4e7cfabb047b585f3aa1eeb460500000000000000000000000000000000070636611f903f55cc9499481bc3415a6de62d5e6bf8bfa82a8ce665f85bcf01690118441961ff46ff701e361db208500000000000000000000000000000000013d22dff8f6f86f659ad17ef91d90a70c180538f03e10de20c445d22e637015d51a311a3daaed90712d04c9a3d992d12000000000000000000000000000000000db3535057db95fc262f8adfd7f08f3237fde5f0e2aab589d4ddcd9c23aadc437e13644dd3b3534dcb17936a7c610cf200000000000000000000000000000000044c177d4484c07fb04d1dd477b188a2c157973cf26075001d14d2b07ebb9dbf8e495dc23b32a2419621e1c129b08c5ac7a5bf2cfedd7048be7ac7d2ff19d4f8bf0a94295ebdc5e792393e0e4bc27d56000000000000000000000000000000000e10fd069f2f5fddaa0112e70ae89d1ecf034defb24e2923731a7c0068780177c186fde92a3c254a1cbdd255111a4b7c0000000000000000000000000000000018363e01e86e2e922ba435651ad892bf9288be14b54dda821c397ae6167f9478c8132e92b1c2cb0c4037a4e020f08291000000000000000000000000000000000301b5ad2d5c35ebdcc7e7cd1ebf0405cf204d6f5e30ae6f46d20534eb6d7013682c5ae1bba76d2811124ebded0d2a590000000000000000000000000000000015fb3a8afad778031d04e094cbde5f02dcc89ad7b7d452c6c8f41be336a4c8b26e75cfc685b8776cbe5a487f09c304083563651d5f5729a0ffca6b383d884823aa3b0215fa057bffd8142199a16e4ffe000000000000000000000000000000000a7880b00f6a3e959ff1bd207fa503eff6e7279e701e37b40735e2bc8bf49e355e92edcaf23aa3654bb26fbfb07b5fb100000000000000000000000000000000113d9b792f4e3dcd958664a8778dc4b177c430d8db9da7805595e40293ef2c0a40f7a843bfa70ec134ed89a453f9da50000000000000000000000000000000000d7f92148dca4a9c96c47a0eb284f1834cf3d141be7c0d9a7a060af6e28e45620d8255e465e9a0d8f78b2ffe17d6b04e0000000000000000000000000000000004e7917a8f3070c656d324c9a816236842fbd6147d326652667e7bca0666d214233ed136dd9464c4ac619d46c28e2393833323c3a668541ceba18375531c3781dd98525b49dafce4c4b3188c90f3f4b500000000000000000000000000000000160cb05390b54151f6b154b396bb400a91fa83d77fabdf31fba349d1bf3b5dfb6476ad4d714af2a2963e41b077bffcb90000000000000000000000000000000012885f7ec8e780cbaa90a465b5706cf07d45bda7755ae3477c79adbd7956b926e0ef5303fc13f0b97349ff8b754dab500000000000000000000000000000000009ad7509e9e7f5018ae3d1280e881ec12129cbf825cb6606459211ed7b358a97cbe430e94dd9f5e4f6b74fb7287f862e0000000000000000000000000000000014d5d2ac2dbc3d5a061f4e52dbfa68e1eb1d3c818ba26686a3171e310c63cfeb188030b83407070019dc5c42dd079413d422e21fbffa7d55270eca9c96bbefa29dd915aca266071673e970daa0ca9c050000000000000000000000000000000008ee93fc610712411634079be0bd96c3969b48955fe5478b7a31c3ba7639c18291034167eb62e6b15c16b0dd5145edf500000000000000000000000000000000158cb1731b71905d7b958c5407f090a2c8a9319017719da143a3f4f3fb3982abb83b8dfe14facb014321b4f5edb5e41d000000000000000000000000000000000a9f98f775f06055ac1f137cbc1f95f4afa0d1c4935f536ba2e0569d874d9d76b7b86f71afcea07e2e785c7a6ee1c84400000000000000000000000000000000072f8988dd1ab0fa8037d3620068b34848c65e20dfc90612d123b6f9dbcf9d9d699d5ea73739d31ad54c22116365ab983ba7ea9ffda87131452b24a9efcdc91d1262d0d7550e5a6b787eace3577159b0,00000000000000000000000000000000161203d8db1381722644f87b04f47e4be2ea2bb105ea0e67678bc8d29f8a8a3247f8c05e057af8f98032faa93d896aaa000000000000000000000000000000000d3af4842627a095a2dca99b52d802b2ef8c8f3d09873ffe39d224333fceae84bf74780956904df6c1dcf5ba31be218d0000000000000000000000000000000001c79fae014e55e5d0239645d618593bfd5aef665b3e636dac5d191a8b88949d207cf0ae9822ce8e1997de302b386b8800000000000000000000000000000000136314cc68b372b06e7771d82b7ce7bfd0e9fd306787e07629f831c7aee853bed80172121949a940bc59c4a0b76f0819,293920, -00000000000000000000000000000000099434adf799099f2e6e2fda4c905e1543893462133ba216aace25836db37b3dd5bd80af1a8c31c7fee56b5ecf9a0acb0000000000000000000000000000000008a6890e5bcacc13e116e3fe2d772ff49839803e4f81d6b088ecb7835b1ed44f2bfa04de1d46dd352346cdee71774e37000000000000000000000000000000000e94fe40225e863b7bdfab4cdc0c1c8d1399554ebbfa3f2c95ddeda74b3dff03d5cc78e295accdc9f02f3f89b4953de3000000000000000000000000000000000b23f2912fdc7a5fd1de69c1f479228f8ffc9f97c40845808cf17a6fd8131ea60285640d32bcd64c9be71d419aae82fb16aa2cadacb129598aa459bb2e6b7fb26d1bcb7a49617b6ef8e57018c3db1f510000000000000000000000000000000004c6f5aaac90132b2d0c6a4e70354ed2e724df7c3e6298eb9ae4ea92e3c7981944c89140c52e893ef2edb2773ab36bcc00000000000000000000000000000000021e813378be9ec30395b917ded5a0424fc7eab0abfdcd2328f725bbd6a1dace0a5aadebe40e10470df0c09b3f4b68440000000000000000000000000000000014e3fee16a833f8c543860ca438d763f764f488463601741a2331fa90efce9f6d54ee0fb7978460a1ab838039d398918000000000000000000000000000000000dec8bb882fe6028a4155e6e2bf48ffd314b5519dc4560f8f7410209214c4a8e37b2b36facc53f4db11ee63ff11f9f228c02014d5392d30863a12102d1c9315839b5611dccfdb489207f9186625138500000000000000000000000000000000002d107029bea087a2d53b6b371aae06c695fa85631450f4ad92c8948b09ed568b28948f80f1455cd22e2ad44697290b00000000000000000000000000000000002fab10cdd8bf17a633c8b3ee8ed2ce783f64bf978c384fb7dbd7e4f0da50b65eb9530365d982bcc17ab91a29eabc065000000000000000000000000000000001369237fb3241ac291a868e6f4610a5103d93aa915e954f18bcf348ece1560a12451723b96ad5fe162a6107dabe1c986000000000000000000000000000000000cb70b7064a2f94efc86060431ba4dea38bc64822efa73c76f3a4500ad23c452c8f2e72713b066a45bfa49559d14a719d960ff678e1b46ada4f866adf354ba8c1514df10ebe7d88d2c8de117ef5ea2490000000000000000000000000000000005ebb9c8202cba234851cf5e060a4114c6fee0632f37e0c52aeb852637f362ce64403347d336c32617cc59f23cc7c93e000000000000000000000000000000001126827b6a0a8adb698854c0089276861e3cccfee420512f0966df78ea0d9c55e85a0536f14ad40e649b8fe4384c836c000000000000000000000000000000000998549680649b294d506c529ade746aeb087f75d62a246b7abfb69397ed67f0f2ccb4811219b35aa894b2f87e3fcddb000000000000000000000000000000001027b604f877ade32df8de6162251acf2751a9bd770c21f22dc819a4f5515bb276a246ad667fe7881965f0b083d1f76304753af76295f72295645243ffc87ffc2110c9d8dfd20b464760ad965d7a97940000000000000000000000000000000005d1484bad44069b16d1ef4e9ca1db70ec6cd82eca645c2fbd4029ab4ca33d79780ebc144d8774d82518c1fefaab38530000000000000000000000000000000019abc7063361ed64a5750b70bd59283e6a61d55d49d8c2ea2f1be8ea425f040d3865c399a66c253bf38355360f06cdd40000000000000000000000000000000010a97b13b3b579ab5f7fd9801d9e4fc40f3b2b2acb9f21bfcdc6b6a3168720fd0abc2f77ccad01be6a6e268fddf3759c0000000000000000000000000000000004126b5454050d761047e5da23c0b2f9370996589c04f255a1ce8ef37a3a7c8078788a0125e4aa86aafce8df33f322d3d1b8760cc40d093912fb073c5012f910ae90f0a979cfe6d81c603adbb98289030000000000000000000000000000000017aa7a3f1ebbdec6abe12abea12ef50a3daabbf96a5f2ebfb517885f0b7aba1e927c682b15521529cb9e1f87c59be99e0000000000000000000000000000000016e23f7effbb9dd34ec1f6974115e7f0d23cc4553d86e6d61a0c98f47d09510e06b3f987c5bcf4bc30e20ae9684da74e000000000000000000000000000000000f3905dd4f99cfcfa6152db53106b4d1f6e24518a822da9388d8ca1dd654a4b8315697328571691f105d1abe9aad3dae0000000000000000000000000000000006bfd10d33df9326a55b35aa6d2bc3e831d4c3b5959aaa35613156e5e19343b74e34ed2670c43ba1a45cd3d91f055c9aab79d640b042664b23667d6c60ef9a5d59de72aee57a78d75752b350ce56d8da0000000000000000000000000000000016ca071d741363e7c3297355e49cfbdcf03d419813ed7b329cb2b2a26fc6a46cc52149ca3e9ca3ccd7284cfed97b985d0000000000000000000000000000000018da360fdee88e806ea1a61c01e86687f8e5359730c36c876ad2acb0297bbc1ae13d790d1edaafdaed65db9dac02a74d0000000000000000000000000000000005a46e4572f667b46aee36b8d377c249de25e797b31b822474aa647ee68cc7d40b083fd0a1d938e2b8d85508004c73f40000000000000000000000000000000011701bf88d4287c98996ea561c1ab2f29a5da9138338c7c7539a5fc8355efab6f58e240df4b0e0cb7f01df74bc8010501d1a2965e995bd4380d4ec52fe8e65e7fd99b1ca9f4f0c656adf7051c4b9a99a000000000000000000000000000000000576e79e507d250eb4040197064b8898b0142b3a2551875935f91f22705bfec6da156c7858fbf77028d4a00957553bea0000000000000000000000000000000015d39a325181d6d1a809b1236f4a1ba66a9bfa6c448470425aa5c8ef9fd00b5481c51e8752088dad62e928b3180408df000000000000000000000000000000000aafabc2f68a4933c7d734660e422ba154e37dd90114272e948f79db4ca51d5ca75d504cf74f2dd0479871d69a08386f000000000000000000000000000000000b017c731f63bbaa8fd0b0d9c17140060429f515d2e85a938d10f6529deeae4818c29b9a628802d0ffbbff720339b7bf2cfbf2abd851d2c1f55c56d4f8b11b196c020c2584cb03764580d410d66784d400000000000000000000000000000000028c4dacba5f33ba66368c19491f4baa6aea4f309afafcc8f464f2886b1d05b6397142d02f0295fd50825819621673a1000000000000000000000000000000000849e1b630e8db8ef039f280f8d401957f807ca90479745b68c3db1b5ce3a02fe2c099ddf9c387d7ed76ba75d6a9be9700000000000000000000000000000000013b43fabc3d4df82058db215a69776ed5dfd4c773d7a013dba3b4ef5cf65e25f79d7f76a06ca99132d6fd1fdadb59d400000000000000000000000000000000072cde8eb3d3e1a7f7e4a9eedb8e56f5e103db6de6ccf833f818f02a0706b2043d4ba0d5473bbb6472e8aeb28364e1d8214edaf16742762baa58a3d22d5bb2305cb03a1326adc68adcd268428f82a1e00000000000000000000000000000000007a33b95f42cb1d1ddeff3a199ccfd9a5d47c9fcb89dc09b5b3f59dde2b47d24ff29931920b76ecf6deacd70e83576970000000000000000000000000000000014c0a63e0152f06cfc32e6034b7829f9d9d09aca0a6ef821dc61ae8d99b77d76c1b2fafb2a14938a82ec72c4041ebd9f000000000000000000000000000000001433135cd913b05b3f58b2e9c1a3bbb951d2cf6c92fddb21bd5e1d9c44e464d5fe98f0791044d56e50b81a83ef6cb271000000000000000000000000000000000be12ce3bc47bf69a13762343b5e39c2a2f285896e5d1b73c55203cae2f32cccbb4f7b8230b2026a0c8b2f63db5e5bedc1f38916d6bdd5d379967dcd058ebce5887ef2bccd5fb7c2bcd758e374a195e2000000000000000000000000000000001494984d478784b2ab3ba27464109f99172033fcd5780a48fbd5a2144354157f6fca2d70b15b0081dfd306ab4239cecc00000000000000000000000000000000078aebc22025af53c6542abe56cf72ce5eb11d3f19212a0f7442d0a0df907c8aabe0ec01d1245ca237a691e685011bb8000000000000000000000000000000000415a1804a46f4595014ef29b12d99b89600aab1d98352437ab8342abf479bb2215bc687532e75f140918b3d030ad4520000000000000000000000000000000015e7b0dae7e3e80eee3c7a9ed4c739288ac2192f7d80b2c8cf9934cea5719081803b207623c771051d7694e705744dbf1cb8c8303157f23987f8a2d206f3add697b9d0a303393008429e93cd35711f74000000000000000000000000000000001470f82372e197a21aaf46cb2bd3c0b77c3428bf2ba073311e75eb65471a8164753ff1d989560f1ce477952bb6555200000000000000000000000000000000001645b5e5b4bcb5f6d34ac841e3a80f09a86a5edcb7f2a7e7bf549b022c0073e01be82e4c9e5c8e8de76ba367595639af000000000000000000000000000000000b43f6572553154e2530fb448d5bf20c3a182cc190149d3b1d75b60e45baa048f44884500fd02c434f9f7eac01dbe4170000000000000000000000000000000014adef5a52d76a267f87d9a8b5e9f570e7775ca4f6a55a5afbf80baea311b1866fa0689271799a654eddcfe36a6bb64c61ca9ab9c3df673b7ff8be098cdadd8354c17becdf82e7e99ce264174653007a000000000000000000000000000000000345a2ffa21eb06fa1d76fd81b1239147688093c6a44a40cae37f2af26add812884bed3e8b4643675b1a45320c64f7a8000000000000000000000000000000000c58eeb5ffdf886d6319ead9e6e190300ceb91d58abfb79c0a322de3987eee73ab82092eea8e1249e83ab67e33b303e1000000000000000000000000000000000763a3fba513b6731fb501aab39a4697f3e4de89125c6884f9782bfb73e6e062f17d34555a04a8e2959ee4e1a2ee284100000000000000000000000000000000024180dde2d23cd88cd29c8142d32435d0db57b8ce8e309701fdb963533c1cdc2595e3bfc01d8c0d08d594e096afb34a681a0861df30946911d789a5da1f5b89c38fa1a8c0407b608122a18be05955da00000000000000000000000000000000022d2e7502c4d9587df7ecdbafcbb813b1812d76655cb7f9f57418d5ac83d4f60b84a0ab5b53a5eee3c3954aa9fc70cb00000000000000000000000000000000083212aa1316561a079cb8d027bc8f89161fc828d050c8837a24fca6f7f94b6dbf10d6032fed895a427f07827deaf3cb00000000000000000000000000000000021552b99dc02a051ea3af1b1bbd0a7ef64088c3aef4a58b18a29ca05e1f442f8ea2c8fdb3642ee94c5df501ff6898f40000000000000000000000000000000001015a7987d329cd1eb5f991c270643a05b8e1bc35467130e9f53c5d96fc3c8336a00c060dfa2d3165358b51b6a521e56f0798b448ea0d10c84e2a8896f153b1ac3b84c5fed6a4ba6c932260bf01d34e000000000000000000000000000000000c19c3b9d7c7f520968d8531966cccbe6f0c3fa0938480ca3591b7489febdabd56a70ae55cc309e04d7acb3de6f41a3d0000000000000000000000000000000002ddc64023f0de2730d3affb695927eaba50ecb91cdf1f369a511a8cc8dae8913ada2d8f27a65e75deb9b8b648e4e2e00000000000000000000000000000000000311ef260debf2310fc31fb8ecc802200e11400909eba24b14d9500ff47c1c36ec540eb970c9262dac947b0c2053d6200000000000000000000000000000000199c19645375dea7602b74301adcfd9af259e1c7c20f377fd10d56b719f7a6e0e57d780c976124e0675c2a54aae3e0f5a8b7de8f34053facf1338b54cfbe38dad73121a0429663f484277af9a230abe600000000000000000000000000000000123fce6b793de0ce2d31f2c7c4218fb20f9db68946a7d57914174ea773d6e6fe1fbb1de141c742e0a8154fa1d81a91f70000000000000000000000000000000019f75536e004a61c6d7f466bfa06ad0c9375a1028eb7746406e7c71e551dba249b5c6284f635fe26989aeea69075b3fa0000000000000000000000000000000013088eab16ec77c7ce7e84236337e395690169a4ed7e44e23d233d36d5d25e6afde794cca2bee88fe749851a71aabe24000000000000000000000000000000000e627130da43a6ede3bd6f2fcdf008c8f5c7b7b1fa56cd3b367d3096317948bda115d732346e73b731d1921a1da6aaa18823cdb73dd076ad95679a9d7b11145c12a81b825477f799300d1fd761417c2b,000000000000000000000000000000000e3b85a3d6628e097a3d962f3c9aa37e3c5be43faf2a12cd9830ab33c4a904eda23027735bba563d38ae5ae8b302864b000000000000000000000000000000000c92be62cb091056d981ab8872292017cc22ae4eeb0cee03a60cb5d57d37b264fbed2d752ae9dfd76c0bdde1f1dd10500000000000000000000000000000000019e172b23249a17924b890cda6a65287039d0c32b2c0833409816cb21ceb73ac95928234ccf566287400a2ed7d9de771000000000000000000000000000000001276e206235392fdf65e4ea6210d22eb7afd7783caa0777ff0af719cc1822579d5b82fb4c30f07dffe7d68c649b9e2fd,293920, -0000000000000000000000000000000000a3d974770eda8c742e5d380482d36fabe18901b0217485141c3841003aeac4459ee28b280550e4643d0f50862bf2e2000000000000000000000000000000000369c2bf3beae4e8761f6c06d9bf5261bbedb177e609c81c9bd81ed0a17573b6e10e7f0512e06109cacc3d483918ed9400000000000000000000000000000000030253d0a050986f49c77ee20ea8e3e07de3ba72c39ffda877bcfe569eeb29598588f5a7cedd9e2e34491a059ac4e707000000000000000000000000000000000ce201f07353bf82ec894ec66c7012d17f3c7968b28b45e88f091510e1646380f902c1c5b036084f9497e9a91476dc2c9f2e54f21b7f2116c30d6e444ca82fe800435cbbd72a98a6d22bac92039c54070000000000000000000000000000000018f493dadbcd93df2c614af310e5aec4fac9e502843b8ca8c3de739315d9e9a380f826e2470c96bffa8789133f458d0a000000000000000000000000000000001768f8c3da107b9ac30a12b99f2f3a0f21483c0be334377733cee6024d85af91b03c7ea1c548b42e7a7869141816917a00000000000000000000000000000000076cfc99c16c270d2f6e34aff84832f9ee6493ab757b6361cc921823fe9c30f1c9b1664b650548dba767616bec0fd5d80000000000000000000000000000000006c5f580c9556ed31847b1a3527ac0b5b5f15b9c9197d3cff061c1cf72dc5c96cb5fe98535a4dca8c4e20c8c02158466c8cecea241dd6a924c9b9cc3d390fbf40ab897208ce9d3e4a148b2c30c25e7eb0000000000000000000000000000000010e2d7eb4e874a9c72a98e4c36701a9fa11051b683ac8ab9ac20d14929d72ff7b92a9048a11bde92dc2696467fcb48e6000000000000000000000000000000000eb29e621e9d0af8f661eb1ba90b307eb542dd84a486568f85e19055bf7b8f0a76d34acf276897a01349eff2c36e4b43000000000000000000000000000000000b5f890f22658b207dea2d721d90a8f5991ea2c5ca06b8d1b293f60959ed424dbd7052e010e594a5ee0feda1e93bcef4000000000000000000000000000000000082cdd4d8452078e8b853f196dd76505ece5e98df3e6a8bbb21f422755af23c5ab261accea48d8e4193d6c884773cf6e428fab2c596f23bc3c9e9855b74295f52caf73cb7371c93c65370583f7fef4c00000000000000000000000000000000077501a457d5f0946d25a4c5eede1b7fd80d90d502bca47d8cc40ab2f9a6d827d7323e7d4035f3d32b02401141f0a89d000000000000000000000000000000000985410246c1db01b42728ea46758906883586cba5618b66c21a3cf58cb98e7c6e0dfbabc5505d1d11ca9d047fb6d25f000000000000000000000000000000001775f4008f688882d02355b6eaa1ab108f239890f71238b36c76228cf2f575cd15f15293a62a72f6ad0ff17d7e8ae79f0000000000000000000000000000000004b6967a5ee180d8b92e95c5ef26baa56d8509e1acc710237083d19269c1c5a1f2d1680e85f0bf040747be1d301300b0f7d3d755410f77a0e4b2fad0f184fa9312b559785fb04c6020432465799ebe22000000000000000000000000000000000fee170589e8a3d3fdd93b536347af5002e59e8ef2ac8327a7e9f382560ee9bc36b3f636a3f99fba8be7b5ea3dfbcfc600000000000000000000000000000000032380cb6c043e3f9ef7169da12df1c6529d776b049c7061df660df841840933e514eb7ea3152ddac38daa2c52d66191000000000000000000000000000000000620ebccfd931eb70ec688110975ea24b7ee0f9937841aa1b7bf4f45af88b732b76a26299f0fe48259fdf08abefb4314000000000000000000000000000000000dee6bb8c198363fa4107996331aac07216b82208242c73736be31e14e4e04d97a56a1c22479dd94997acb0d32abd3b0557b05efdd02ac9d8e1453c82a321d798f3106bd18764140faede610ae01fa80000000000000000000000000000000000eb60e98d6cb4e4b3e58271d47261d05be892eebb9a37f6831ff19d0bf2fc235e655f0eb9b01494868bc082c58ed82d40000000000000000000000000000000007254a64a0d94340bcc2b0142faab2d73e8189dbaf52ad0c3a9206e802193168b8eb03cb18b0e4f1cc95b98b943910db0000000000000000000000000000000001e0051fafaf454072051d2aa9512ba2367778aa1617cecf6a7f989d69c7627c9070c349d363f56711f172d43f5730cf000000000000000000000000000000000f4141c8a45448fecce09908ddb42f7b5f6b5bb53b9e1ede0417bee327644af5c98470e8b5364642fc7435f84be1ab443313884abc4d430c06ae843d263f2efc1bba35f6cc270de05551e1f86096bb7500000000000000000000000000000000049c28e0bc677ccf54f4cb46e953a057ffad624752332fb9ee5295438fd5bd61abd2199a0bb729bb7678cf3077e32ec10000000000000000000000000000000007138a996356ca3f5d63bb5a36dfe901254459ed515e18ec8d91fa747a691b40a19878d9a6f1dc74e4f18374a399d38f000000000000000000000000000000000a621b36a3cf04e6a5cb699fe4ff7fb8b3361207186848e81972fdaecf667ceb35f413bd68772f7c1f77c1d3f43a3d610000000000000000000000000000000010becda5a06f3f077218d4387158e4a1ca5e0ef24d4ed304723ed5dd96da7cc9325f7e4ae16d9d6c348577697aa6017b8faea236e782a8fbe27ab15f051ed007a61e25247f1f259b9300974f521f30c800000000000000000000000000000000163ee307e0d0c3b61ade05a022ce2bf315d820ee8ece60f93d63a150e02be843a2eb2240a4882c29be2c7403932c348e0000000000000000000000000000000001fc8e9ca23e8dc8457df8f255db3b434f52cddaf05819dba7df1c5bfed438f756c8b57442197af18bf83fe9ee2b765200000000000000000000000000000000109cbe5279ccb592bd0b33b1a482df53459c48cd0913549739b784ba7ad32872377c2e3924c4d45064b0cc4764220513000000000000000000000000000000000d789795d556a37a375d83120a022f57e26da6e6f9aa3e40e1f32ed04b50fafc4d40d0b9b20a26e4d230dd789e20823013994f5645c6ce83741e48ae472674921bb2d9b8abb7d04ddbbb85a3f2f7f090000000000000000000000000000000000960654bd6e6a6b2f7d87c3c4d6e3fe6c684a50b62f7acf82a67075139a614c056a41cd49769960e229cf07468fc2dcb000000000000000000000000000000001727f2dbcc8d889127060de0079207eed1e094259b59a20fa42ab2783bfd176da00e61a65709dcd60402398fadf30710000000000000000000000000000000000c17805a01e64c320601e0ef521b6573e9c2eb354157cf0412e5c2b13f826759310907c4b77164f5899958cd30f78c030000000000000000000000000000000010fb286ce797c0429ad3385c709259b55cc962ae02c814e537e5261e897b7ee1b7c660273ec908110f997b166c14f5c181eda24db328588e8c670ab70431ddeebb0749b431bc1bfbd992c91f35d59b180000000000000000000000000000000015d96a0f988f4951206aeda63af85910db49ab817c83e218ec74cbbf5f34f81279d8a3f2fd1f3000f73b8c5550af3fd600000000000000000000000000000000186d2eca1cac226227d8981324859126864b84e8dac563b4d92357591c2416c93989cfd9e1ab6ad257dfeb168d829a09000000000000000000000000000000000a8a7247a3b09583cd2d4949721160573f1f88221e6eae833128914555a594f21a3fb2bfe3b1f01f3dee90f7772dc97d00000000000000000000000000000000132361ac1950756549c957c174cab9ef586eb2057a4eb22f49252cae032975f56eb0cb7ea70810afaf5716afde5b88015bf25b5070829e3d5a66ad24ba9930f3ad64767c51e432b51bdbe2fab470688d000000000000000000000000000000001328e22bb83331adb09dbed0a8c58040a3564fcae0ec85794f26c077de69cc0a7555f011e028879cb3aafac4dbecab33000000000000000000000000000000000a93db348adb3886802bab1e993f5d7275360a5b0466845055d5274e44716f3e1d03a6e1796ed4de4c157dc8a2d92c39000000000000000000000000000000000dc0879a8e9556b7d9b6d5dffce5e648f835f10acad3afca7a73b0fdd5d5babaa74a1ca80aa4f6880d9b015501e218a20000000000000000000000000000000003f7ae8207de4a179ae48cffc8c6e926455e46ef9e109c08be3ae7401bd36e0876642ae9ac4fd75a74c67ffb7790e265a9535c082e11b366cda0000d8ed0f92ee30fd2c4364c163a718518321c5e85d2000000000000000000000000000000001078f43093602a2dacf9b5dd7ec41d47bff02e0dd27a996b58c73febca06e3d977c2fbd73f63508243696ab5d8b97b980000000000000000000000000000000001841869086e850ad97b3122fa51c437113d2bca14deaef5715c354d3845f6829f6aebe668844352d5af3509c0d8da7800000000000000000000000000000000047c42e83194143b9e977fa1babf80d455fc86cf6cb491ef8306a1c32bbf8c868e11bb3308dd5f65fc2942b3e49ff5c50000000000000000000000000000000000872ce87ecd22b39b14c9036e971a562d51c5122bb10939cdfd1945dd1445ac9f5de06b70931aa5c86cd0fda51b89952c4cb49adce0292e259e92b229bf7965864a945de86eda3ce0bc9f1a6dc8b7b200000000000000000000000000000000157820de2a134081eb47b1800ec72630348583d77d512b4c6a8c8e581810471a2f57a8eb6b0af87a91960424009ff124000000000000000000000000000000000378cf11b0a2848b06412aa754ddbee5660730001db073724caf902d4b4894959f035a8838e28554b0efc2388f2b4f27000000000000000000000000000000001301d15f290dd11c3f8e53407195e02dbf8f13e4fe25fe38e84740753b5a0032f8dd07df3ce46ba424f6772b3aa66f4f000000000000000000000000000000000d166040d457187232f8f38f2beb1e0e0864105595764022c282867346166e46eb789786a7ec7c00b0446207e9ac1ec05e927f57aa85b2df54b4bddaa041d43766c8929c8b9146d723806ee0cf042275000000000000000000000000000000000793797c5bce4b1cc3bcd751c5ae1d293477af96a0e7c6bd392ab4410f806a53088cafeed51754ee7e60e61dc200ccb00000000000000000000000000000000019d595730af1f3039e37494b86a638a528d8bd24c429e3f8bc97076c7463e7f2618e23bd3f300bc7e7a4674f14f8295d0000000000000000000000000000000008e245c7590888fd8dd58f93332b81f48b6e3acd3cfcf5f3b28df654eae1172f52ef5a121707aa9cb111b0b402d1bfa6000000000000000000000000000000000a7c6403659e1a0c2dc7cc2e9b57a452bf553e96388676f4bf4a6e26b3ca2d3cb82006850d8340dacd65aaa0d20e6fba606ee8a5fdd9890b8017f6c432a45517d65328f13f3a2bb42d7115c02929db7a00000000000000000000000000000000054c37e8acadcec8a795619647d4cf1081a0592de02bef916f847936a1736e74cc3b7ee018717495def8b4ef1d098fc9000000000000000000000000000000000291d89d152b414fb5e7139d6d0bdc7b5b9de1fc44b49f895ae08718b631f7652bb4a895fa11149b9a9db30c344108ed00000000000000000000000000000000107b30992ced35e4ba874e436bed5d88aadf0a0c944ca3eb8319539017bdd652feb7483ab6c705aa17e845723b2cb46a000000000000000000000000000000000895dd8e04114fde4a4cf19925004a72f617f2ff146dd650a2cdbeb12977dd2b34ea7d655dee16ad9560b144b81212f5c1a77ccb4b32a762d60b37827ad6c3448c33af6af861c131adb5920ba3c2b85100000000000000000000000000000000005cea2e036a8ce057e4dbe2d9d786eb759c2a75934580480f78d2e228c3150a0a1d8c95ac5013aae3ab6e35f524d37b0000000000000000000000000000000000e18c18884209f9e4fb17431248a5f8d29c616a58af16e949f4317c2e117b80d531a39800dc70f6b161b98ba040a8af0000000000000000000000000000000007c42ce885d1bae906128589b72f2e6c18e4eeacb78c853e923e6eb785c073b6490b2f6b3dff2276916d96770ad5019800000000000000000000000000000000132d809c37c341eb0304ec933a6b11bf9ac0d2a13ead818ab6ee03ccc94160b405066381dcdb13b6ee3f5dca48ee10ef47cde609c38eabf457cdbd1e0c5366bf523dd5801d66a0282bc187d80417f455,0000000000000000000000000000000009406918e2dd6f06f4782ed110e29516a911f47133ad8adc58f5780de916a8973ad60e05ba931d66de7545a92f388c20000000000000000000000000000000000041cbd52cad2a5f4c8353c7153b5711ec23fa8bfa2f34f5e1a16d8a14cfd47c237766880debb992a05ba9ed0353beea0000000000000000000000000000000017d4211c827379b310956371129011a92d62d11f0ee5b0cbad9eea2d3f2a95d364717713fd0c544747338725adf27248000000000000000000000000000000000a61903fb81064614c9c6894c7f3954aace7611cedf6bab8e751f0c203bcab827d296016947c071d7b6ccc742e28ee9f,293920, -0000000000000000000000000000000007f90813f8c3eabcef04dc1bc9bbafe1dafe220e2db24e4b714aab2b164d7ec9df3e6a3f903e8b7b96df2ad8297381d2000000000000000000000000000000000e34371e51c4c952a0f38c4aaa5fc2324971ade310af2f36ed511fc5fd7a602a551ef77775fcd0f1fccc718710239561000000000000000000000000000000000787edf7a6ed6b50afcd7c0d3876d8919273428bc49833e3503f650e48e788b15cd82eab2672f612025d796bb62d72bb0000000000000000000000000000000006b49e631ace4f72c959919df5d64c537537ccaa3d1890ea9a1d70f9eacbaaa2ec361edf2d4880c9810976c6073028bc3c79fe6374bf8f91bf7851ff935a124b54fdb5db498d2d37939fcd43bb93d29a000000000000000000000000000000000cb63d7eef2d6614d1f629756b3a619a221033207d1621e4ce4791db4248500649b91ff07cd2f1f06eae3a9be5b6af080000000000000000000000000000000019aafbe56da1569959019033e8cc785c9b98bba6b069603969e7ff1150f023706b461913ea7949306a44c3b7d199e86e0000000000000000000000000000000005cdc3a7004f7a7f79ffbf4c4ba7c5dc30ecc62f270a5c231406fa63d82fc64f45e94779cac851ff8443040fd3b2ea6200000000000000000000000000000000040f30dc98e8668194c9278b189e0c0f7b76a4c686ce26a4a96b93190938f07c5b813670e206eb6b5da29624a1b6314ba59fcd2baa47621ebd90c5cd12b89f2a533ae86d537fbb61a14b1a80982c9257000000000000000000000000000000000a5a1bc231f803ae272e497f812ebb663c2ce8b43a366717fc6349264823ca93e29e30762c1a366d8680f81838907f59000000000000000000000000000000000a88fd59ee380449d632d7e1b926210d984d5298fa807570a63a63828cfa55c6e2f01b7745848281795dae36e562181b00000000000000000000000000000000025ad34537909e07beaaff09f22e91e76d93c668d1b45cf6845ab8ba0129e417b758e85a7100a31a9037e307f454bd370000000000000000000000000000000013590106126231b1c616a5dd7aa7ed6946aacdacec963b507907950d6ea11cf1f5b59f819a43eeebaf51a1faa7daa8e719ef9fdfc5f0c4ac41255eb172d485317c124211498a8b9a74c0bfda15b986c5000000000000000000000000000000000938d43b9747c926c3e2dfaca2d6f1e6d61d5a621ae08c66a5baf33d9241771509689f9ea7d75af607d76b66faa8fbc2000000000000000000000000000000001889a48a74966b9748f4a6128dc3d75a69499db1ba1bc9aa3a9428f0efa898b5f78a9e2dae942d3794ab3d1157a1d305000000000000000000000000000000001129c9bf343f476541980b85229c5c25289ca62173e29b75de431b572c8f01f64ec1aa4625dff9e7df535194c7f4e6e7000000000000000000000000000000000fe95c71f703dcc71cf409b332f66fd69c330758d41832236a510ec4bd9a28c4732434d4c3f97445e6301e3070153dbbb8ba028831f429d027319a92fc0f30def8b97a43da456ddc79443d9f8df72cc10000000000000000000000000000000007649efeb3e0bee49b9adb13f8e5d7db1c06d7fde08a3f3082194153bf4b3615aff1450e47fae88ac93f55a389a319da0000000000000000000000000000000008334731582fb1b6125d7ee1da0124fe88f0c70a0a3f6188636976c31ba6a72beed927fe598386f328e4ae534729a57c0000000000000000000000000000000010b57d80fce5cdc90bc93b3bc7a1affadd19fb00aeec2ca9a6287bf4e40fb74616986a44f2f7d945f58501a965f37f3000000000000000000000000000000000180dcae46ee41bccd422b3cc2b34cad26f6816dd08ba51b2f12835e7439ae2d46933de28ac04bbcad68a188e7e90ee8dedf8a6d86471f58c69c1a5e7518c69c34165e72ce84fbe0b7f69d9c2717e5d4d000000000000000000000000000000000b419b675ccee2509daf66e5da4031b08792e1181140b30489ae21f7925305d8cdd8a104580ae5938586d6b8e74f750f0000000000000000000000000000000012e070ab7118991a20b27f1a87fba1f5815665d76269f0d3d460a6b701e57ffdb4fed2c53fa63a3121c74f67e770f31100000000000000000000000000000000124218ca85f235eac3471e0acdabf73f79afdd4bbc159c1e34c641b97f03735e4c3430264f2d94f640486488dd1067380000000000000000000000000000000011c24f4fa1862779f22a628edf9d3cebe0f7593964b642f889201ae85e8fa01e00e48355053f5a7c6d920dcf6a7ee1d60dbaac3f5e25ca3d1d50ebb31258ec4450feca1e02c84672ef15c49b4de2cebd000000000000000000000000000000000266bf0d9d5a4fc713dc0fcc6ea6edae0b326e22cd97bc49c48a7ba398fc87d7a0c7141ba24d80df454de66c2b5a55fb000000000000000000000000000000000aa8f95c7cd61733b0a260149d6608a73d6c1f989afa8cb2aa4098e1fb5a66b4ad5a5c1c4d901aa79812385fd507f02e000000000000000000000000000000000a6b4929df13e1fe7f0a0cf699a7fbfaa97d7527cc3ea1f728ba59def2e75fcf3490199bd42e93b7d47985a307add07c000000000000000000000000000000001719321981d2085ba31c9fb131d6b79c7df5d10d6ad0b5015454329697860121e781093fdde1f19e897dd6f2c272f87a109ccbb8fcd4d4651b84f4708799d84ad0a717aedaf5a76d2970a7b93bd23d37000000000000000000000000000000000431002c9926aa7d2b06412f544a868a7d48fb5f077dfd098febeeafc28b876c434daec809e5cbf50ff2395ae7e456560000000000000000000000000000000005a15f713b6eafb09495cfb1c89e9421515a07a99ca0f208883f11c430ffe6f2592dbc41bcee5db36385a26f67cd26bb0000000000000000000000000000000008dd30fdd7767486844967c5da0803b52282178287b8ef28e14f07b487132fea3a82d86d414b4d0a25b3dc538be11b500000000000000000000000000000000002dcee67e2d17b3106dcb9f4117456a037ae1996e8f7a09b179baab1ee8345c6d01eae554d3f40da86bd79a04702fbf76326fded2b8a3fbf7637bc25bd201d20e3d4d724806cfa678ee039a39c24e86a000000000000000000000000000000001629fcc374e99fa8303a715fb5077f266b13367bbc0098b5463d3298c0892f83127d6b7f751446575b88858bc742586c000000000000000000000000000000001100783c10618752d25c235e1e76dc64db94adce05651fb8df0a5ee7c299d35b1319f7009b857892ddf9e90c91f7d23b0000000000000000000000000000000000ab6996e4935131becd5df288dacfad1e69b41e200ca7dc841ecc180a81b9d2ca14fc8a76a4e7bd6f924bb9f473de62000000000000000000000000000000000ae9b22f8dff29e5e0a2ec5b5641f53fb5e1ca03130b49d0c26696ca4b439a9d998d9a364ac9cc5ec52df699318cffeae005efa8ee75dec8a013029292976e107a507ec09e3c34fb4baf2979fb759f1d0000000000000000000000000000000019c557ae1c12ff8a7c00b7c9e4bc3d65c92753549c193311a38a84bccfc090052a2219461a9691affe2d67ea4357cdeb000000000000000000000000000000000cd35c5dd126bd4b90dd671f29953c5a49a14b6b3fe946991416edf235c3eb3d574613d27b05cd879518fa7dda3ed39a000000000000000000000000000000000224392063b0825fd332bbede23588c1912e7670a013a99da5507f650dc4284431698a5b4e8c180269af8bb30e4fc8450000000000000000000000000000000002ab8d3250d4bb8ceecc8ca2003f91420d0ef8a7dbc2361e5e7fbfcb59471a4c525856bf796a2c2608d219d215cf83fe3917f8baf17f71222166cb9b6c4beb2e57d0d054cba3f7fd3a28cd3dc4b409490000000000000000000000000000000000911417908c2bfe4f63a388f699b31b47df1ea0ec289ee3f96ffd0c71f3deade00d1841aa56b4bebc2adcd3068adf920000000000000000000000000000000005467c7e58e82089fa285c28ea22c759c7806d86fbdcdcc8e09e847d6330922a61bc331ae3b5acce777b7809ca98213f0000000000000000000000000000000010f376fb47933b1f701dd81cebaebb2d8d8f5510a26fb3e9e156ac5ecf2b943c5fa2812d52da542e6c335abad8ecce3c000000000000000000000000000000000dcbf467432acfa4eb9ba11a7cdf02f9110f44ac371128ff8f1f98fc70e4554f057a4608180bfa54d99fd2da010594f6f0f73e1b62561f5b0fbc409e6534ad9e37d1c0724b35cdd3f94bf6489e500fbf00000000000000000000000000000000179aaa7119f6fb986714c03b6db16f25eca7172d24cbdd318bebb633bf08920f9e2a8136c94e3ec7c19e57ab51531b3f0000000000000000000000000000000005937c484213ab5b2ca8ed1c5c90e8d2a2f1bac044b88c04b301ff2fdbe67dc4ea42779d919ad510cabfa2ccd178cd9f00000000000000000000000000000000183cc23fd64514ead63f55d375a07af7cf2a56aca64a887dcc542f8a396468a6abc776170a5d4b4bbcd4dbac285e7ffe000000000000000000000000000000000ce12228dec2f84219904d9ac7923f122a99803a9b34749ca68ba385c178811685c19a492aca2e1123ee82a8a9cb90fc3ea24fb6447f2493c78a267daa158eabb70c1b60af8175d0d4594c99122cb4420000000000000000000000000000000009612bf9130e17110f8b15aa6f3317071daf3433bf6d008c383bd5c2fdc7ca03f25ff4cdb483de3c84c0ef9e579f38c6000000000000000000000000000000000c40172540a7e20eeedfe02c37aabac07165cbf04830f20fa76fe8b05c826e7762c9f7567a0fb972212bf736e627948a000000000000000000000000000000000f49e5b1929ad3ed5c07670c471710baa24e8478a50f72a5b7bbc23a66cff91d30a3d68961fbc2e6e8003d08196f325c0000000000000000000000000000000004ba098f915ba9e934384682648ed8d4e1cbaae60d596655fcd9c05f4b049ba0d278730dba5ce3fd4892531a3153bb955ed307c01d9e29a0571de07c62d5fcfc80749f02b8dbaaee9f69dc9263e99188000000000000000000000000000000000449b15ecec6d6fe5cd32437b54218f62527157aa6344c635fcec8f8305c8b6e44c93105984e0832536237606f07792e0000000000000000000000000000000011e40e8aaf75f5ff8e4040f725ac27693d7b24805a2539ff54b3a6e90c048875ea9609fb8fb3d8de63ca1118876c172400000000000000000000000000000000006ef2a24445f728b53cbf01e5b076acfa7761a84d8261cf1a1b99cc32f330f32fa5ded83d5cd51cc284207adb2451ee000000000000000000000000000000000977966380e772670447b15ad9917035273eb71a21c37607a761aaec808909fcfed50679769aee1573d73cd241de6624877f31ddcb55d961bf9bc09903bd927451390922d647d589302855141cf5cef500000000000000000000000000000000074e475c0ff1a51a24be3c964c45c41f767f890dec82712d92a965be504fee43fcc6c0684b2b17c5b294a3eb7ceff1cb000000000000000000000000000000000597b7dd287f3fb27e35a9e4e1718b6b1a4addf9e95e93aeaa25aa34023669368b794a08fdb178d9bcda2738534d1962000000000000000000000000000000000a492d648393bfa317165ccb552e045fefce5b3444d5ff770f43a08a68efefe7fce1216114ed1495cd00f832538198180000000000000000000000000000000003d85cea8063828ff025ba599bdf1efe0412ed5ce06ad5faa841c6400e4eeb6aea1470d48f4e66fc768d7e7bfebedb37145c1442ab82241f56c27dec2cd4dbfa9fc3cf1ab72bc521ab32a82346f8f6070000000000000000000000000000000008ecc3dd40da2a7a348b4817d9c84242f2f07c5d0ef810dc08311e9d4090d6d96d68b6c725ee6c24de076c71754bc4b50000000000000000000000000000000018fb3a1dc4e0dd9227fba310236a6db7953f0b716fa995b928a2a8de38edb97eca09fe2ab385037dfdcda2ee577e677900000000000000000000000000000000062fce7fe7810273a80760d9f4b3be9e7c821f38ed3e075210d3aac6aa7a763e3cda56465f88b34540b408ac850742080000000000000000000000000000000006fa94466cc47990a80ae6a310ea765590a0e646b5988925f03cc7e30f04fc0a8044b403212290b2fc46c77e84a9028dde4d1470f6cbce027465b4dc2a3deaca14e34218910aa76cb45d47139b31df88,000000000000000000000000000000000f41bad0a932e28096e51482c646dbdf294aa7b91e0ec258670e7674864765c76989a936fb440bfbf4268a49f450d3230000000000000000000000000000000018282b76521db98f589b1c14e603b6f5d27af357553bca761189a38a944a11c66480f7ddd89d17e4aeddc8d78a2b3a0d00000000000000000000000000000000007efc4a90dd97f1312047ac78a3163dc014c42a44c7054daeefd5b72cd0488832cb6396e02ccff09e4171d790954fcd000000000000000000000000000000000e790fe8323fffc96705a42ca071532d5359641ff7cf8714789c9c578717a054c811cdb581df8b6a43729c6c3e3255ab,293920, -000000000000000000000000000000001304e0ce6a4baa6e0545fdb314523fc91f73eee157249b94f284ba7390b12b23b1c849c45a563ac82b62a2c48aec24e1000000000000000000000000000000000a2d0e9e222db70d49d1e85f587d35bdf5e8328aad14343d296f95b152a79c83a4858cafc350a5df1ad0194c49bb929400000000000000000000000000000000199efb09b34d0699eb4bc1c57fef9cc5d98453bf522c504fe7897e22bd0596a3a6c310eb351e15e3f6609b074b240f7d0000000000000000000000000000000016b69f12ce30ad1a65150094e29d4cd82fbce5dc343517ba9e5d89245ec083c44af9a3dad2169f713d3b01fdf70d20642576b42e0728db912a78eec2b7b4c96575e341e86a7592a07a660c11e0044839000000000000000000000000000000000b3ce4ac12861052c602e71906a7c9f3e2186bd2b6eaaf222d8e80b48baee537065ce78372ed936e6728b9642ba1fdb9000000000000000000000000000000000e8186561d23515bc58c77769c93db76dc9c62bb715b283cbfb71462451120a6ded736cd8a292a6799fbad7617d9aa84000000000000000000000000000000000368a6dbc7daaab0a786257c813b1a25c97468732c27cc759fb921cbc3c9a37a46d7dd0298771c447d36ef0a10579ff5000000000000000000000000000000001348d5e34cbe54e3a6b357c4e651acb82d2dc40ef9ed8bb89f0cdf0882ec6a737998f4e4dd61e296d101cbaabccdc3e779f9205ef0e3a85199c60ad9267349fdc7b6fba4cb765ab21750eb3dcfc48d8b0000000000000000000000000000000004ebb53c462239a78bf13f29856ddc4d78645c457a656f3cccec9d3c032ec19c26488f39e0f5bf0d38424f9e3a9bcc870000000000000000000000000000000002fe1949365831f7c38b1cd6cf2e22345c4ce40cd73def77889c214d1077d70e39578e8be4fe5998f59d47cca7917280000000000000000000000000000000001152f2df1512013a42ac056b75802bc35c1883efb345cefda8276c594b061a0b0f4a49d8bafa6fe251658ee76b2493cc00000000000000000000000000000000094f90cb386f7933b2ffcdba5e46e09cbd7d537c12bc223e76d3a88ce9063a7b3574d3306365d65dd4c6505f1dceea53300679b7be7c71224247e8034f5d30a63f8707d92d843a703f0fa93160f6571500000000000000000000000000000000169d9469c53e55768c9312680ee82ee581727e28cdb1d6fcdca25d0c03f3da2ad6572039f12c90b09cdb843bc506e07200000000000000000000000000000000174528257f6d3542f754ecbe97eeeea7d196ee4dd01852f6cbad87fbeb4dd7d3799588f17aad129a15549bb787468772000000000000000000000000000000000c9ab635bdaca1c488538c0830453ec6ab3b2b62447c03ff6ffd2712bf62e02a63c76c79d41644ea412e733128685c45000000000000000000000000000000000172ef0fda359bab149c8c04f583f4ace4d1b148426e993996d278f79ed2c6d3933d6cc5fb62ec4869aadc773d3084ca0454b01910548432a0f706818a98151e38ff9e854f1faa95ad41a7239b5cc4910000000000000000000000000000000017060fa73b58957d12b3996d67b7baa8b7f0943ad52e80e5c4f8830d33dc74c0a39e08594b647945b402299ca861f7b10000000000000000000000000000000001efdc7f783f9977392e2797a3e0bed222d5b661d056aa0c7e04a493bb9b18048bf72aded134941ece78d63df0a0868d0000000000000000000000000000000011355198320af05f2121939e6489f31e9e13b3cbb2cb30c9e675854cb8ec038f80aa2f4b6f995774b36f5f1b6a84298f00000000000000000000000000000000172e18c490d0cd5ba2449362c0ab296212dbe69ac25515d0f91941d300051320f067f946dcaf999554f55f1f616adc0f3685617371b27ba8898ce7f30776d817ff09ef68a9d6721d4a923ed244ae82060000000000000000000000000000000005854f4dba62d1dbbf3ae16f70792f1bb39f111309b454a6400d2916e619d4f70764ecfda7eae5c28cf1d178ad53fe6d000000000000000000000000000000000ed0bad1f5d69a0e621d137746a9ecc764931ab89f24ca827e0340ddc03571ed697f63e79cc58b946e8462099ce4b1d70000000000000000000000000000000011de76edd1cc2f9ba06b98593a24a7a011f2701b451ea3ccd04361ddb678e06d91a676e3f11b62c68cfc05242cb8a859000000000000000000000000000000000599726b5f5b93d414f9310383ed9414e4675d644f83ebaa63dceb2bddc7dcfcbc17c7aaaccd0ee32b0875952554b4e660cb5aa2a0cd1e8c3fdc06a3a1f6f9b6d52a8cc2e98c85b8e258f72d03efc25400000000000000000000000000000000031110347cbea2756b5fdd549d6c0b8f4036f5718d41dcd6c854a91c9df29bd464774be479d0efcb8a3f82cc7441a6c8000000000000000000000000000000000e24a52dccfdda3689c87395e45dbd46156676d9eb2cc09dab22ef7ff0acf5ea243ff117c82b147994d65aee8605b2fb000000000000000000000000000000000e0cd6ea0bffc591c13c48bca0782fecf8e128b0b842aecb06f803a223d32cc350db869b7a77f8e31b05f36bddd587ea00000000000000000000000000000000042ff4ab4596d610638ad23eea904a82701cdf61f9e2dc5832a70e11e717711a2d0e72f32f74706d385a9567426b4713addb1fe778c84242953db87d2307b40eeb776f17767c3a4311b5d2ffd738f151000000000000000000000000000000001517efd853800946aa37868b525e58fb488bb69755ccec806afca2d21bd3a30ba46c39cdf694ad0ca92841760437c3c1000000000000000000000000000000000e5591c339e88544660380d6362f4119c5596f910d4ceb96ccd4c4d9672efc50805b6fedffa0a48d126aae69b241d3640000000000000000000000000000000010ea5babb0de734641f63eed2eba6124377b5c55e429987917c0bd109d7904766a10b0d2dd123413816d0fbabe25050b0000000000000000000000000000000000efc89ee2ffa56193129062ca55a3350bf50e8fc7d586fae3636a70e3577987fb0f8674d383def4b41225e490d3d81528416b4b4e965a5f024723fbad6ef2f65a1381e70201e26ccb40188dc3d0fae8000000000000000000000000000000000dae4277d62e3f3dfffb80818a5ba5c371a48d73b92d69a168ebab897ae8be206fdf776e9f955136d7f7f7b2903040270000000000000000000000000000000010ca635ee2e49cd6c951d75ffddd11557432726d26564239c611b139329a28812afe21f094c0585675f4f233233743050000000000000000000000000000000012378b2ec31119e508fd9ae0ccc4c2603b6820283284a278fe16864e5a18cf7992d850c1d6ebd1253103c219bd95ca4c0000000000000000000000000000000018cac4f0660240045214034cfd8a7e40bf0aa12f97a23c4e27db0e05bb25f4d755276a91a4e882a0be63437a522943ab78077a51f88236dba6d16d7fd681c631510106b0eb7448df456eb9ce758e74cb0000000000000000000000000000000002fd5571c818322d207d58fe0a898a045a26c95c2490765dc9ac663a0de78ef5fbd05b20ea96dc5388d5b2ccf13a5e320000000000000000000000000000000006ff29ccb768da45061ba4e01c90459ededa5e79513917401e7e37151095ccd4656aeb9cb7c083cf27b69377295934cc000000000000000000000000000000000414d34eac47430495be735eb5c4b1a68372abeb43658f27613a9c8b78f17d9074174a8deeeebb1f9cda5d6198bdf89d0000000000000000000000000000000010b11bf63b8c39c1370e8fdbfdcd149fea88eaf1c0a94a51bdd061e4c41abc626a448030bf9ba880032e9f1642caabae871716e790e1a0120fd26d169b8ffe3fcc0d03683dcdba7d2f953f05444076ce00000000000000000000000000000000023eaa08a44eebae674434b013ae9992c75690a3d0de53e4b05d1c0dff249feb24a12432bcb5defe25ee4e44a56b27eb000000000000000000000000000000000f146ac27e685cca04afe8fc58fe853825f5b0009e8831eb0d0121decec23b25bf8521da2fab1508a3ad8254865fbee70000000000000000000000000000000004af1a525d3c33e0b1629cbdb90c56a88d70a28037c87db81c59bcbc811c8f0b98aa9dd574436c9f600c0e8e2d194c0400000000000000000000000000000000170efb5e0e69e46a21ec3b972265bc04b9d5ee926254f61c0e18fed013922e00f1897cf69889576bb5d54810486e7f2776ed0a27553db6ac6d3959ff4c9bc5807fb7d4f0a56095ed2bbe31dbfa41827700000000000000000000000000000000111c832a96329d6db203fc8b6bb5b7db01521529c91c74d9cd71dc78d067b36cb7eabf1af80129a7a3f44b719235927400000000000000000000000000000000097339c17816795238629d4ca6c243a14e9e227e9bfc30370dbb9e1475f6d03020dc35559675121792436bacdf9eac4a000000000000000000000000000000000805870a1efd1fc34c9b576b77418ee8c0d36aa9caf9994a051e1d55b49275f34cdb55edc74ffc267c5776c8d0e113ed0000000000000000000000000000000001513afdfc2f000e3b725fcd0428fe72ab2413ff2aa91b44458a5249c9a160ee27bca01d2fc2e230f4a80454769961af95ce72b30d989889c8779c4056e441bbcd93629efc2877d36d27f670711e21c4000000000000000000000000000000000485b3b1f812b4a28ac87d16f86d8d634e85d49d6dc460646e1224de662e906002c44a1a269c3bc011fd22afeb2d58df0000000000000000000000000000000013ba0752444a794cd00c99eceae51e61c382d0abb26e5e0e595d59321447400e8a8f7d97390bd217fb50bc22cef34b2300000000000000000000000000000000184515a36024d0bf71d9fa4cc5165363ff94ee9f8579bca653ebc0620a9d3146fba70a2f4a9f6bd3777101de0d32e327000000000000000000000000000000000e041422088c0343f7704e726d65ccc4216c4a1bde3668108983643663cf0249e992f9acde2dd8ff478dd26cd8d9434d06d220f64de05bdd6e1140c1e409fdc13f43bd31cd94e633be38ecf22ebd77db0000000000000000000000000000000005bbb0c55fdbc59992c83fc0ff03f677e58b6de6f8649141d88963ebfead9383d692015a7b765b727eacb6de250351ad00000000000000000000000000000000183057eca610b8e07fffb60d21bf2eb87981e6e881bba04ceff420ca38228fce2f94d40a993e2aef09e209f3990dd14a000000000000000000000000000000001231bc55242bea6b589cedd1d82621fb71c606ca9306b268379dbf83ddb1420dea228ffc05cd8b67c38206f3f006ef18000000000000000000000000000000000f2c943e7a8b0ee00fc4e4ba912b94f68f504d2783babb90a3781b666b31bd161af2f97a77813eab9ebba76040b04155257da8ac7d23c5ed965d8bfc76a642a36ea6ec4c45baf6882021372e8643f09800000000000000000000000000000000054bd97b9cc979006f734ec433e215a4e8afe468e69173384bc895e10ead3749d991ff8ff203abff30bf5cc0d2fc8c6c00000000000000000000000000000000066b73a98d5f5ae140a5784c5594892c849aa7f2db3b5798643f755743d401ca745d810fad5f4a33e5b3cf0fd7d96f7b00000000000000000000000000000000007caea93ff5cc6ffc033717220a215ac4ed7283945ae77e62320a0bde13f2153dc8dd401297cd124b4c67a4f3839dfc00000000000000000000000000000000094568035ffff439e3d3201466f3a1d43414e3f6455627c5479c8b7c55130ccaa5007ace7ef6a2b3e2e5a4c9543dad9163d017ba8c7ed138b1bc70141abc5cdc3afbccd8b1db5a6b5f775efa62b8dbc30000000000000000000000000000000015eeef8bcbfac04112931e186f6fd48b7a8ea891ab364ce8266c5fd15f072f08fb3655e324795df182a5ed1c917a5db000000000000000000000000000000000028916fcb3b30a7f95321a0998e544f9f4f578be7a9f866cf72d6b8baccd93f8935f105ed26aceebb3f9c96073a8be180000000000000000000000000000000012b11f356a7e32f3d9281a8999363aca0ae5c1a058724cefb51583e5f217257d47ca76d21e54ab62260796b95f9d3ad0000000000000000000000000000000000d83c75c36cc8dea4aab47823edd26b4492da39b93a15fa454aed4175f28a025ad2c576ef2d76a66e666bedae95cef1a7a16e23e37ecffd514d47199cff249415a6d366fdfaa82450f0744520258955c,00000000000000000000000000000000059443f363ef0c65973d36469ac651eec6e52485a07a6d28112f4d0711802d182b7e6fc56d4f1aae51fe1c549247d885000000000000000000000000000000000d22118a6f1cd06ee14c63f0e005076bfb061bb85ed184b5444c08ed9dc35f77217b6daafeac89a973f2c73f00e0d3c800000000000000000000000000000000180430caa9917cbb40e3ada2de8d685b4daa99639669a643b8f5cf9a4a55d6162e9fd7f5d4989a1a6588feb0273669b90000000000000000000000000000000015d01fba1192f0f1acf8fb32fe790f0448c6563cf8ef5505d9378fa2fdd38bd99ba938077f71bb8eaa91a99e787e840b,293920, -0000000000000000000000000000000012d948b5268524659e29cd407dbbe8f529e608193ab9452f936b2f6fc0b81d3a63a0e929329e2d89b5475dc2d73ebd8a000000000000000000000000000000001219e20a081837f4d4e33bdffda08a946bb9cd876e42e2f561ebfd18ec439e0104b43de61f47b8b7a0c346c33e632be60000000000000000000000000000000000a135c72c45f254cc1c260af803e14cd0f89c2ac3029629a86b05acd3440465aafa4cf84e69551ae772bb55802a90ef00000000000000000000000000000000052750c3a99974f9044531dee9129110b99572cf283b61e6606f1137a87de7344bf01d2ac2f8a1db8d815b6d9e7511fa26a9bd0a71fd58edf81459152782733536e960d27e35f9f84d00da256bdc118c00000000000000000000000000000000136b2f21aba94bbc8e5235951b1b186fd4ad221e6ecbea5c7279cc8ee8b01edecedddf48cca47624ee9b155a4c167f140000000000000000000000000000000019852d2bc9c8abc92503f3e7eec9fb20df108c23643ba8a2fe16c2cf085bb4ac079d3f065a1241067daaf401b662288b00000000000000000000000000000000018bf1a4e74ac9507b97a990f3a41cbae3f32e263e9937a8a62679bee93296ee5cd25110833eb5d136425bae0e9dcb8100000000000000000000000000000000096ae4bfaaf4f18d3e987d9f287fdd3dc9b497cc84867e757da52bd5f58688403e1c9cb432a2eb87e239879d52990ab5f1e168ab93674bd7f2bf73318a48ef17ef4464fbefd39f77c17ebfdb24d679b60000000000000000000000000000000016ebc2ee18515354b7af5d924c895ffd5556ad088560f89c59a4ceec229279d4075f732b884a6ef2bb2eddc11d27572500000000000000000000000000000000110282084ab6f3e76eeb9e5e8c56749992913c2404b003df9c2d01d72751f879538d23f612c8faabbccff45185f4c6a40000000000000000000000000000000017476677ebf052d13f60ac0ec5e572c398f1a478d60ce92a3de88a74a28688d786d30b1ea8008409e45697db0adc628c000000000000000000000000000000000a5e4239d938bfc7c05f3b3a850ebd5f7784eee7aca48c861eb4bdb1ce6321fc9c6bba997e143aba13a42f69ea14937397fb0d947d71a1b032070a12588b85065c19affd0db53e466f194f04f58dba2e000000000000000000000000000000000b6e16f2a6cb821abc43c447da207cc3013f2f750c844f42f0fdf47160a38501bf502073bbeb565122bb3de61b3a5ab800000000000000000000000000000000040f5f3aab5d416e9a084fa298814f894ba599315fe10af20f836e624680582413b4a54623cda8ae2663ee094e4db775000000000000000000000000000000000d32ac715a094813c7b46ce2e932365bfd62ec5e584e047b0c56ed6eca3c58268ae01be31b833be7ba5c2588ebb9859d000000000000000000000000000000000850b9044f129e51658a02cfa49d40a2b09239823cba4d8fe423fa1b4815750811daf745e7e02b317a7318aad0734ddc640f850bad2f22049f2f8aaf3ee57564fb38a847e428e252f003eaac465f7d670000000000000000000000000000000010c703e31f2d488812a387596c797d8d414e406bd82f238cea50a459d842502e11220ad82fce5dd36635792ff5770bc50000000000000000000000000000000010c11caa640708850e1dddd48bae22961a45029971d823b53030979b7d8ef2eaf2ed055436105697c5b0b31b1a9d0a7a0000000000000000000000000000000006b98568b2b7f0aada97310f7e14084a14bffe580ec65bc8fe5d19c6213c45dc1b8e1da5c6c1b8555729f6c781575278000000000000000000000000000000000f2c506f3e41c28a748656d1dfd87e812b3ba21815637e497a30eca4fc5de18257846f12b67919dd2d739477cf5ed0ae8bf91051da5bce0a51bcba6f4e1b3c9063743646f4e75e3e5a8cbc84e8112af400000000000000000000000000000000102b6d561172adc9316b3ec11f05e66e7affb1bdc70a364faffa57aa5938c2ac08863be8fe79ce3f627558fcb2ab1230000000000000000000000000000000000c5e72c271a1ee186d443a96d53f0ba0ce226c76aff2a7c3215c2110f96cb3301bd586f509edc45cd20e662756897b78000000000000000000000000000000000d546fbf485bb283a04fa05aa962ae8d77ec4d26f749d83b208f77247778e32a9a2f1483bd84488806e27b13eabf41d30000000000000000000000000000000005a42c6ce8d43d122bbf984e9777f5d1c15057f27e70fef44b97c2c6e7e2e303fcbad643027b7ff3167916f21a723ea98da771e0e827a52a2f7e79e0e5d93ebae04c1ed78cab87d4353f24ffc52099b3000000000000000000000000000000001788323aafb95f8761f87f771fa05a8e49be71e397849daef5877a7f486af13fa651be7a93bdd9465df7be4ff65825fd0000000000000000000000000000000014b7a56f3f7c12e39be76b3872c1ee648f62f9cb6a1842d869e00a5dc2ac8cb4ecd96ec2483d5eade5b0f9113133bb050000000000000000000000000000000009a30623632b757ae8d03ced0c1fdd1877718f8d84f34ebb42426284f73bb7e8abc31a5e5ded57a02d08adaa90abfb2600000000000000000000000000000000020b47acafefce7f617081e22b2bfc566acec6d2cad5063a79cf33e02cc8931bb698b72184a11fab73e0bb0aaec76c61d6cff707bff10fd53ffeff8e9400966d8ffba6d4ad6a8e7e456df10f8f5ebed2000000000000000000000000000000000d1190466f0e8f03d2cac4a5e63a13d7c6d0cac9f2065295e2de818773199d731f8cb7b2be5f6ef0a246401b345a2d560000000000000000000000000000000007d9c5d187494df79c25b6292527b0d6d8c50b6467bf76a1a1316556e48159a3b5dbdbd9fb0bb901d857f61f423d15db0000000000000000000000000000000013e4401fe76e3f1ef73bd244189cdc81fcc152f71449c11aab24c4fa1d123c5aa8c68a2d10fe88c1c6631778dc0bcd420000000000000000000000000000000004ccffb4296883b8690b2f3fe17e4e9ab24390084ac917ed28fa1e04b9758373abd348290d24c915dfcaf0649ddf5a87e00831cce307cb44e8dbd5edf24f1535b837277160d2cf6daa4e862e57fe73b10000000000000000000000000000000000f4baa5e531ae462b95362292d5366daa89f2fb2707c58568c094c58578e84a8d253fe1de26b917b84635c0aac3a63300000000000000000000000000000000109057e5c5451eb9f85b95aa5ed2615d2faccd0539b1e4481923e04cbdbd2ea9290969022cfa508d3fd050549c74940d0000000000000000000000000000000001c3e147ad9c31927207f2344fedd541316f4010e3de194f924c4a1450a221285b76ff1894f8b1670731007f44965100000000000000000000000000000000000909cdf5c56dc177daa1f3fd7cc31d79a4f6dfcd462c07812cdf629426b75bdaa297b9d7e67aefdbb58175a21e29edada8168d56385722f339a5b27fc25a88034d348e3d533ff4dc99d28536c1c09a770000000000000000000000000000000009b4c6bd1c460d2e93febfe523c1d54d6bf6af50838e7a10b732c1be8748a0752a517e7103d0ffa4507b086626fbfa8a0000000000000000000000000000000015bf2c13891dfa8dba35b5da1235563d4ee1dac33e89006f5c9fcf06f2fef7b31ca845bcaa8ac608046e8b01c8a61fd2000000000000000000000000000000001898dfd6a0618df821474b90542f261c1febbf2e566978b0fafca44f6dadc57202f88366b19d2c955e4291ac21beab520000000000000000000000000000000019287e1ac6b3eaf412e58511b40d87558e7cbf90dc8af2f5d33825b40fd2f2425d0be3a05d0a49076f4114350dcc601eb929ae82ded73a4876c041d2e52fa811882fb8e22690a27cb4ad3ca05169bbf0000000000000000000000000000000000c0993401c024d32cecc0d86d4cc52c200e59acb34fee2ae052837f467905e736a1118260ee12a963ca2df6e1a6c9d0a000000000000000000000000000000000103f78f0e7c9a5628a66efa91f150a87e67623ded2560aef278a8caab017fdcf181981952b450c67e3b4d3f362822a80000000000000000000000000000000000df01ff335f23652f1c34480d23c62d705572321c0e7fe92556e033dd3cf5b78a3d554585403a7f3c71744c20d17579000000000000000000000000000000000a0e2c9e2e34e5cb36e96b29231f702abb127a011c7ea3e21d59e5c55f745a02039a68d59ce8e29afac0752d1939106936999c516d4acdfbcd488d39e3073db9db6cdd0c0fd1d29d58294ace6d2d199f000000000000000000000000000000000eabff0e6ed9dc358881796441c48e722ea171f26011ab898c5a06758f61a629ae21d5a2595a22dc9855fd2e516b30fe0000000000000000000000000000000002732155a7a2791078dedfedfd3381281554c389bf9b5baa47593153a2acfd22a08557d7a1d49be298e416051b9137dd00000000000000000000000000000000116faa2e2a261e6a3e4de6ad80d75ee05aebae47872e2eed9cd91aafb94a706de673a05f1b86c0b0131cf148a90b2b7900000000000000000000000000000000009a04c09c2a4fce22d237bbe930392dfbbe5c82d480abefbb3be876015e2f5889a0922df6d00d4e94be0e9fb8d2f4a1fd0bc405e3970dc2bbd7dfe0c54b7c64543fc241000adeef4f7aa2f1dd2506770000000000000000000000000000000002a6402848507062e5c5d63b1207a1a41d3b941d21792391f2feff95035f1b4625541770fa5e0f87585cfca670976533000000000000000000000000000000000904095ce640605c957715e378ed733ddf1f94d3beb63543a50c8922ab9f8092755fcc65e2a1ed9232c8cddcb5816371000000000000000000000000000000000ec62b911b08d3e8618880c3784685b2c6cbb07a4aa4e348ab72e4f918152622ddd7748bfcd79f35675cb956d11fcd650000000000000000000000000000000013f651e9104d48a081cef2ae0648816b2b4b5f644a791514e94a8e3dd3001099c27d1f9860337ced1b177b4ad7cd5866c36afa3c8581df069292d53b8ce3e35ca136a0b3f95a894958105fde9c77e39d0000000000000000000000000000000016334abef2a21b9c1926b2086075471bc2d2d2f66b963a41623af91fd2fd50f254c008fa3bad6b53658c2486edcc94aa000000000000000000000000000000001063002a5d17aab2bbb5da49e8bde63a1f3c4dcbc8800f9487f47c6d707109c86d3cf7f9171643418b195e50d7483af4000000000000000000000000000000001213004f31fdd0b0df5d8e3677c4f48624691e2534c02881c6cc6875b9abaee56ed5739c2acd66cb1b10553ba066ef1a000000000000000000000000000000000fb7659081cfcf8beaed9c1daf9e92702977c37a54376597d897082a25f9882f1ae14e7724c0aeb9e002dee708c6b4eb0f0a2bd678c5858be2a49ca54de8716fdeec84e1935b8f44545c740417efa7e400000000000000000000000000000000078f06bdfcbc7c0cc491fdc8069314c8a395983f9a2e5c2d1bec360f36e365da377885f897d8d711e33270e3ef9dc4d80000000000000000000000000000000007d43394d5175e020b3a5d768b60ec763d60cb1bb37c0343930fa82e92fb1becde0a178c4565df320824bdadd54ecabb0000000000000000000000000000000012f9fc96355721c35a6f5439065d89cfca5345622b3f38041b41c036b9bc6bcc980498ddc7bcf807e1b97831c099505300000000000000000000000000000000105307b482467b881a59eda1434e31dffdea531603fd3c460aa8d4f58d32668228bfa585bbba2dae7346141af59190e2c8e420db340ef2c1b5c6a71645e303eee95cd93228770b639287b14b6a5c59ba000000000000000000000000000000001576521fb3be8c3178549969e54bb17b0a3546ac4aacb470e935359e36bea4f43dacc06c151a527f441ab9616e07f7b90000000000000000000000000000000018dff940a21768ee9b9450fee7259663bb29af645bda2acb4d43f4e9d631e0127073f2db04293266e6fd6fd3d005e3f0000000000000000000000000000000000ca6a977016c1ebf52827a5ad52e5efcf7517ccc3ff40df8141f6335fb6c77c3fb8f6b0dcdba2596ded7c3838577e28000000000000000000000000000000000150cc33b55586fac30d316cad6580cee0a070900fe7d540167560b79f4cf9690a5e02cfce9946cf67a95dedc9a7d9aa35398541eb5a03271e2ab5ec2aeb2da80e634f63a050c25de98ad13e9d63d09bc,000000000000000000000000000000000adf84ea7681c442731be8db9508a050d4689c42af8f7472491655356a86fd9a0afd91435bdbaee98db3b1d8f26203fe00000000000000000000000000000000090a7dadc0a14df481e44e2005c9ddc6e026ce2afaba7badd18492bd3a338dffc349b4a339f56221eb795314252d53640000000000000000000000000000000007390fbc06077cd167f53a16f347eaf50ce8c9d111afeabf3a672687441b80e66a66ba3fdb28e2ca8282f3ae3dc81be80000000000000000000000000000000001998f98e85285a428a2860c22a00d0b317854da4190dcb4dcd182ac249e13c51f5d5df5db6a0fd61d01232cbcacd5a1,293920, -000000000000000000000000000000000c868a2cce65692f83eedbfeef6f9823ae9382fa5ed23395ff2444807e72403d4f6ac861ecd3a51db841889fe22a033700000000000000000000000000000000111c9aa53da85a63ce1870b963415f0d5f812e061aa6bff57425038d1b65fff57a78bdb963bf2450001525a93011a28e0000000000000000000000000000000011770810c16367d075c695981dfa69b072b82b034f8ac371f26bb157f9f9d667aa555a5c6baca69d08f421cd569faec2000000000000000000000000000000000df6146b29bc8226dccfc95a325d791b30cba8ff2495434d75622b170a634ec7995c5b4c689c73582ca861dd21d8e1e49f99387baca30b9cf63ad10c445daa142fcae1ab3c0a366a068bb5efc9abb3a9000000000000000000000000000000000fb30aac6502ecdd3544f1879bf1b3f4c19fb897de6c3a7cbf08f36244aa8e9dea8aaf781f7509d3ece16ca144a601e40000000000000000000000000000000012304be931a1d7440d67740f50b1a281468b412e8b6c54c62b993ec609012c7056fc7e62405c7530e8f5136cacb5926f00000000000000000000000000000000182320f5d9211c08f3ba5d40ccca45cb0060a6d362b4422084617b9d8212e94a9b878294ac176b8f0e959bc124a753310000000000000000000000000000000010be6678910072ed9f932ab01a2d72f7374a2cc82bbd86a6006a495272aa89fd655e6719ab8b3a0643d002021f7b7ebb4283a1773995bbc97a6df107082fed4ba40e2d30c5472a25a7643ca9e78b8b8b000000000000000000000000000000000f1ffed9514ee81e9b3fef4162c8f4980fe0429e57bbc224a9c9976cef7d26ab61ea7b0cd42eda30da97e3f8f5ab5f0600000000000000000000000000000000035b9b349b531d85361a4618a172b510dbc924df671b3fa707b474d0d8b17d30dc8ed208d66be91dcb7632d2f05ce31d00000000000000000000000000000000010030dcf6695d44ad3236032e47f7aa25b9f55869f5207e7ac8641db8c01f5b59627dd3442a1834b8b1fc595e47cdcb000000000000000000000000000000000f91ad5c923572a75d32962567e7b1b0eb84a91d485c968b5aebf8b3a772c2f94e47bc1d5b333fe43574308a78e768ac7f4202d670fc3b48eaa92e925f48821d2ae057d90c5f184edcce9ea900ab51a6000000000000000000000000000000000ae11c60537bbcfa46a08cbc219122ed66fd0d42f90e68243c32010eb99942554c349c021f0e3635bb50f7ca3d106a3f0000000000000000000000000000000019a61254aaa5b51b4d354f444706ebb0bc3edb87ec2d83e830ffe0282bcaa3278e947d053d6678549a098129bace43da000000000000000000000000000000001100f48a07456f01e16bcc833ae0a2835c964e9b0aa850574dfd8b4a7f06d03059e9b4df8931740ce0621ec7eb31218400000000000000000000000000000000003072392a824c386859735e2d203c9d52c19796ccf8538bda3b1436b2f6815bc86d05287f29fd0bb0569a81a57f0c22a76cd8d292a7053c449cb98f13cf768c6e37da9d702af28c16dceacfaf9cdef5000000000000000000000000000000000392760f98883f9cf6c0f0a324b9a645cbae12b780896f6a3eee918c44a815daed156248d6afb25901521b323f6baa240000000000000000000000000000000006375c6629f30b7a36785269d691772afe1b95d6e1bfaaba9459c31086c2697e4ce77d148fe2ea166cc330373583f4730000000000000000000000000000000000aa8e338df7eac5a7b070a69d3ed1553a0c52fcd894c2bc8d1b8cf6ed38983c6c392a9a045ffe8ff40b39d18e7c87c9000000000000000000000000000000000cbc73b589cba1bd47161282642fe6f51f2b3edcdcad6020bdaef369d3f2c11ea9cafb9a7fdccfb89bbbe13560d42d1d97b7bf8acdfbb148814afee1df79aea17261dad6f78772111a6dcb021d8c79d0000000000000000000000000000000000e71692cc2342d1e93e0ce72be69013023d012dd2294249dfd69e1d610e2236ee2cdef22446f1996bd3309825989930700000000000000000000000000000000013a1bbd3237dcbe44e05234f7e41982f4fd951d3741a3e90345418af1c922d35edf776a27bfbeaf7a15658db67164bc000000000000000000000000000000001197a2ee5c2541e19b5368c97abf51fde3dd0b922c3d701d7d84552c9f47b38ca09a8aef8240abfdcb03292ade1ff04c0000000000000000000000000000000010ca3c22ff8a47b1c683a58086ed9d831a5c25b6ce5a1971989974b4760cc9e83a1bc8d819825989751405b242eba379efdbd5953bc33bfba09fe7b3ee22c46c3a86f557e4b5f272853e67fd95a0f9b0000000000000000000000000000000001306f8047ba1a3417e7993bba0dfee9077eabfc275af91d0b882a53199874e0777d8dfd29767186d922d49087fff38b20000000000000000000000000000000005371b760380a6d287e129b329e735413447969eb9048def44f5c5987a64323d2a5c81484c40b20206832b86a4af9c4d000000000000000000000000000000001552eeae620c42d0bc4593d7c8e2c8fb4d6dbfcdde68d57158a7dfe837a1870a73b45a97b02abdea174a475a7061331400000000000000000000000000000000033a6dec61540a5cd5773b76847dc5016b309c5a027639598f51ae5b1067b3f7a02f5ea11b0e1be77a3ac236cba15c929a331bb218b99fd38451483a10e8add23c9641b975af3897670884efef90d4520000000000000000000000000000000012ad5ff49459fd3a7940a69e2a78919876e9b3a4f0c142499e7b5dbcadb5c2b5d79c5dea972f0f0acdfd10ac53bcdd92000000000000000000000000000000000ec1be9cb379bf1e24bd5429a4a91857bc3ad45095d15bc5537c2ba39407e9f2edc5fbf711ef4287a73ea466d4f53c3800000000000000000000000000000000173605df66aaf51810793db1cf2021de6a7645ae84a5d439ee035b917d037d9f9ff072b5dfe8b9ac69feab60fe2d70bb000000000000000000000000000000000d0bd336825381ae1e18ca37bf6160ae32b653ec9f9dad159006e92c24b661f22b5629ba323e9e06ccc5887a962ec23fe9301dc826bfe2988cf93c29ca9f01421b75ba63c5ed2cee1599122012ada36e000000000000000000000000000000000f5e593c6588add92cac2c9467247fc6d900f20b4d3216c258f88f3334eecaccbf3eacda227e2da46cf520e5102a9cdd000000000000000000000000000000000458177ad6c190222e53e054546413c13216286d414e3509b7dc794dc0704afd26bae93ff630c6157d05d46d805a04470000000000000000000000000000000015df8a7720d389e6112707e37694afac2f97282676a89964deabefddbb3a0f1cbc885d4c875b945b8303c1ed2c0f46b8000000000000000000000000000000000e3c7f1af7cf5923dccfc1d25bd86088706a3a44f5fa7f97171228e8f2a2b18e9631b2a63bd5a75ee0bb83fcc91a45c30a1cb530e8b828542fa4114de6aa936bd2be5ef3a9b7a0e20e475022381d62d40000000000000000000000000000000017823fc8a56e6e5cb9924037ad6ad1b43237894a877572dfe3d3cdc1120fe83e01de112b55f7f334dcb5c6247c210613000000000000000000000000000000000daa01f90cd14d82d4fc40b60b463089fc6c0e567fa46bae69184d0e3cc5acdb1d759e3291e2781fe0b65c734ddde28700000000000000000000000000000000164e742b123c19e52e2d7a6727689181f323990a3f3238072f7cfd7fc0f55b7be4274c0df194d85060a81f3744d3978b0000000000000000000000000000000007c03a1678b6e91c1bfc66ce8fd419cea13c7cda3213856ad21823b06db94538153a15d43a9d4270edf77b9a5ed490e6cf2f0c33bd044e8c4468b4b7e137ae294c178e7b6c9f19878331fb93220db2cb000000000000000000000000000000001865bc91e645e2e24c3efa3afab8b0e278dcf16b29831f75b3eef0b342479e997b9c5f8ccf67c789c830609b3cc425400000000000000000000000000000000018dda7857f919a6a49f6bb465c27342c8fab6afe6350c43b98e91a3105276f3ac27268454e9a9c6dafeb2218ddc7d3cc000000000000000000000000000000000b098258ff8b185a5c59b46150954d52db5a5f68bc7975234491406131e4f1286ce79156dd1290aafe688f936ad34e31000000000000000000000000000000000b294e9ce904fb9e243d0790147b6070b10ff611a06e3f639aacb744154d02016ac08f6769732d4f6944ce9257680d49e5f460dacc592bb947ff6f1c15b8464824aa5c957a645a763138ac1581ac5768000000000000000000000000000000000e541a22a7a36adc06e445f42497596e1017a1d99de85bb945a195cb3cf0c14d39eb7a2aa994cf234eed77f6307cf6410000000000000000000000000000000002de753e41a16565e5ab1b61debdad54950e9930e04badc6e356f10711d7688befc6827040356c0f0a8ce4f8d7121b3a000000000000000000000000000000000f2202e34ca164f1a6c0afbe179b714b303d87ef14534fe3f4230180f709dc63af17f04487264b3dee6b24ec4d0a423f00000000000000000000000000000000004044d9e3b3a77d6a309780c870a65e05e1ac531c5420f6ed0056f5e728e2b83a968ca90d579db50c2dd395f7e40beaf26a9736f728e16d7b8ce0cc59e2ccc848c181459fff4321982c08e9cac5794600000000000000000000000000000000166d7692fd30dcd06b9f01ba2101870ed347840509b3242f7cecf91fbed91abc24b08b08cc39c508e6499a2f8bc3637700000000000000000000000000000000076ce6dcbc77812b4d5b44a50edba5a082cc36dc24a5cc348283a4ce1518198b56134c9807ef850edc9e36e9a282b9ff000000000000000000000000000000001261d9412245abd7ba3fc1597f34179e54766c49306725d42588545e14f4e450ee1c7af913ad7225275c57680c23aa6300000000000000000000000000000000096602b4eee053998555ce522c060d5e04c7961eeaab0145d38c9b13362624f54fcc8d0b77f2bbaf8c312a3279f06e4eccf0a9be4775d65bbfc894f8ca66fa6f69d4249ea7f6b076fe193f2805e64f940000000000000000000000000000000012be34c18145aac51a1494f4052edbeff14c2812ff494cb78198cd7d9db9e951aea80490c55c4ed926f6a96a2c337c880000000000000000000000000000000000536e46a63ec5ac0f2f4eaaad6df98322c6a981cf2fc8ef253269cef20a76ba1ad089c24cba4ad4680dc4192d66595d0000000000000000000000000000000005363b9acb66ee95713b63dad076529805c0dd8921c738e205e7b1d0410a3ecca0870aeb2e64cf45270d49b473371ddd0000000000000000000000000000000016749b2b09d889b883b6fdaf518345d4cf097a728b833e92c4d21b5c41c8d5cfc0758e895b60ad101a74bbb6be6ca0c5fc6bfb37cbfb10a1ffdfcb91d9a52883cb9a606f4ffa8849a6e07386dc9bb34000000000000000000000000000000000067a684b55fdeea39a29252b355700a4810f083909cf2c07a80b362ac1b4d58f5900c68d266f7ad81ea278c0931bc1ec0000000000000000000000000000000001b1f78d194d77cfb4a2116ce9e29438dbf38c52733b0295198159d7cadb2584d86a75c24aedeb36234a0becf9d38a870000000000000000000000000000000011fced2244cd959872a25c0c7bb4af6151d99e1aac079c606db4987b9ba111261d4a16e7d82362b865324824445a946f0000000000000000000000000000000002659e7016ad615ed80ea1ae020903431b470bc0341f8e0918de9b8d2e933dd9f2d9123e9e9d20bfb05d49f71c3c454cd94959e16f6d780628694075ba5aa1a476d89d8fffcf4b4ab7e6343c011fee920000000000000000000000000000000008f3c5de8c94a98dc5ad7846c53980384f997d1657f7349ad9b51376d41f4b21861d212fb6428bcf2347d8774f44156d00000000000000000000000000000000110b245b1e788da41dcbf60a3ac4987c1925696dfca85d450107f654fa1230adb9436d60c9e742dfb4e453ec4944c56c0000000000000000000000000000000011043b975e01df36a36307ba9234a18b97aadb9da509513b13e4f3c80432b0cc5e69a3bbb3cbab8df41bbcc92cdbf60200000000000000000000000000000000120aebda10c52a67d23842e2bd9a897cf38c58fcd11e4e8c5614db5e409a7c03111feebfe2f1212ae753497dc59d6ae9122f3a5e940ee7e5038421619daffb8a6f433605f37e78d863f814b51b2ec4e2,00000000000000000000000000000000021067690e6e001e3d0d01449a7257348c4ef68e66dd47b9014d7687d44749be1f33e6be95df6a204169ab7103dc2d3c00000000000000000000000000000000062efa0c36462ab0734128dab5da8635705bd1e1b540817c5805ed9417f176723eea92425db539e8763b1c79b9923e9700000000000000000000000000000000176c9af1970f026bcfa87e6f85a20ed498c28c6982e21bc050cdc29c0f0af832ed4424082e4862d985b78519cfa75b820000000000000000000000000000000018718b0d0fbdf4783cd0b01524ab153b891fbf08cad60241a3f3163d2c3496c36afdc6de62ab3c9a037f88ee408ce5f6,293920, -0000000000000000000000000000000018adf92d8da050c76118a3a3b2ee43955ae8b14ddc8ed64f5672f40de475f7e0ba6ff60c4b6ca3e863d7914e6de2cc330000000000000000000000000000000013d1e19011a1ea90389480d14fa608985d895e05edd9c28fb34646f70fd7bdb7857fa785b1e3c8a2997da6c3b5337ccf0000000000000000000000000000000015764827d9838c2b011660230ef9805af388fd997cc229c939bc5f4213d517dd837328c45b0b8ee1d6508cb70629b7bb000000000000000000000000000000000d58fa30a2d095ee8d946e50a027ac4cfdd557b3fd9c82dbf1536ddc0f42491a176ecbdb026306e6ebf1bb182a4e8199b3908c739d505a1d6fa85a6dfb7a155202710b45861f1a8a7ac7bb3274a180cb000000000000000000000000000000000cacfc8d0bc6f9db737c8a316043a6b52fd5946937467afc09ddd14e509a89f2445065ac8a8c56454d529d67793edb0400000000000000000000000000000000148b1b941f159d93170fed949d5f53bdd2603d78a49443ac0e2353130ff914376e018c3db3d12b807d105f2d50eded8c000000000000000000000000000000001382a3e98cfd072807214479900a8602bd666cac7f19be0443ba1354bfc05666f40384e9ccac314b5d0a2bec1c90ef0c000000000000000000000000000000000c12c2222f67a5adba78f2c0be5be95ed743e835857f4204cf47b67fa2eac45cd5985fd82c7a3904944e7b84737374b17e0e27a8a416eb38c989a66b84f037a5a24ef3358e20cd553f037a0a2461d31000000000000000000000000000000000197ff997d6c5efa3d7de8e16f26082bf13a2401d6df5f5c33c6614c36105f347e40216c907bdad9c1df6ebbd44f41c3f000000000000000000000000000000000f27a0bf92329730d776a83583177993b2b354a212a9c004f9f8892a750c477b8d1e68c13127f03b1629bc8392d06f5b0000000000000000000000000000000011b239cc6914a321385d907527b85713a0d842f5be80752f4c5758586dc1de944b6e4578bbe324f16838115e9c866bca0000000000000000000000000000000000cf93c5b48cd9de51ccaa45124217cabf466d07d6fdf4a7bb810443339ec4af5b74931bd07eb9fd31c284c05f3f539e0a3cbab01c34856b892aacdabe63d0a0c241ebc137a88c83ad22cf38997b211b00000000000000000000000000000000137b12f731ec925dc51e20a9c90323d14e1497e16b3a4b5651135054ef0e58e9b18167da15220b9a4f7d81e9a7648fc20000000000000000000000000000000000b2d3ac534e1e5b2c9ff4092c2d8dc5efd99121de7df953e5426eb33934ef07e41b196eca50f5a04a936881a05f2b2a0000000000000000000000000000000004feae2377d950717695606844a4873ed7b5f6703d7a63dc8b960b99b68efbba710c2db0f1371acbee314875b97ca054000000000000000000000000000000000f49ce3061e7254dc1bc8af3636a05e098cb96d81fb31e25da97c6266adf3c41a74d46ff32f4fbdb4cb7e4a3f69e827bb386bebe0e49b7f07b0ac61b15306c2515a1ad6fd76a1825dd29a60e845c0e4a00000000000000000000000000000000064ae3fd67250f2c6332e1e149ec09946147e12e0d871403e559b49aab68190a1454b3ae924727b6dcf6e1ab327c9d7c000000000000000000000000000000001131f91c7a0e1854bba3958b36083c27904cfbdb8b8cb3fe68cf578bd1cb6f7c6eff91d98e4b99086926c5d4272cc1f200000000000000000000000000000000071c6a92a8d460ff72d172c204c8a69d6b6752b8c1f731ec63f7f394c0c3a2a1bc15e865172f693f523c11cd4ab1f88e000000000000000000000000000000001193876df7f4a1cc9b337a41c9faebac2f209b9070bd75294c2a88d3091a1e55b51fad482fd2aee8f90458eeb7e981fb8902a82d33993a10c56b2fa3333cabf1c5d47a9c78354d58f70ce4807cf2062800000000000000000000000000000000025c20ed5572dd1c9a098f241d2965d8739878ddc57c017632afcf6e54964894adbd6d30f62f316c9c3ec7a08268afc70000000000000000000000000000000013bc3e930f4fd5766db8f04e1ebfaab2b67f620119c39d687c68619b3564f3e8b74666c9f8bed6c1f080a9e23e9c0f22000000000000000000000000000000000973a3cf19312f90843f1f013b05484064032557807ca67b2ded4a27fdac12d6cd0e1416c8998cc8635ce10046adfbb900000000000000000000000000000000108903617c78fc608eaf007aa13861c970557f2693b24e8a278920897be9694570ae6e6c7749c3eab84d5fa3af5164b1426a4e2317fee033a226a91a52a5830f9ac2cf5f329feb6bdb382438b8a39f2a0000000000000000000000000000000005695975c140fa14998e5916268bde2135cda80a45414fa85193fd6e13c6b5a6486898f590d76175d8ec2629c923e33600000000000000000000000000000000033f58b1cf67e51e9ad817b31919530cfdb5db5ca4a537d9b006b63399da49b2a5077bf5c3b3b4fb10b2478f466542540000000000000000000000000000000015c532e40ec04d9143e308895b2e7e3d3daee093a5840e1e76ab528fcfa5be57d9796ffd58ad5ab7df6f88aaf34706f2000000000000000000000000000000000b55747d1e8b66e2b2fea67229f2b7b17d58ef547ca841bea8db5b53fafaa18390f11b8170c41a5dd29331917fa2e348de0390c05fb0dc9b4a3f76b51cf952a11b909ce13f9abc9fed6a349b8efa98ad00000000000000000000000000000000001ee5ebf73bb40a5c0822350853bb5aeead3262380dc274faba6b04e58e7fb9d5a4ace109ffa5011e73e3d89ee6fd77000000000000000000000000000000001427659e5ab1f8b47edddd27c613b578890d4c66c835c0cf8e8daf19d0ae842f0bba5bc83ed7248adcd75cea5d222a270000000000000000000000000000000001d4560185690ac05e56c2d629d599bceee3ed2919c29e3d1ac54e80ae99b5eb2f93bab865e8c1eef7206f96b2bf4eb20000000000000000000000000000000016ecd3589e3703e5b0ef53790130d5074d2bc0fd5839d9c6ff905746a77e393f73edf53b98b99d9c87a1fee1086aa8657431db9e576643f93505b5b25836218759e736c0d650a5221a652338b0073eb600000000000000000000000000000000163850016261f34de2b831a0a8dd3f224adaa3cc279cdb40e0ae976bbf736dec26c55a6c79cb1c623870b62ea216274b000000000000000000000000000000000a79af5c054cd08608d4be1705058ef7b4ec38a8727560d960f0325d0ef915c049a89e76956d0296bcb6c96333c3470c000000000000000000000000000000000ca89379e558c7308edd25bf06dc05db857204e9351299ab66bf050c8f051341a6c15a02864c679f07373038de3fe87c000000000000000000000000000000001929f42ee5d9dbfd1f6656f61e6243ebf0eb491762b7f3608db3f3e9abf565ab1524f770cd2ade334885d7479342c92c6745a32591e359efa41e9ea93a016d2eedf1da112cddbf31818e8d687b36af2e00000000000000000000000000000000193b6cf7300e47ecd21a05a71b13a8de45418d3f67931789ce6111b8633b9f44063ca13ba8c8a598ee0725caaa3f277a0000000000000000000000000000000016884d982e2ec0fa7e59fb34ae8708d0bf4abfc260837ef4432e8e04474e504b85450db8af8e6809413c90268801fb3b000000000000000000000000000000000fb48a8331f278845979beb8cd21060355566af215ba44029455a03d0c016daf0f6b7c5773d1a99e893e76b4411a53c70000000000000000000000000000000007056e30143058eaea89a3065e1de768d49860b170d4c364a28d38475f90711fba62c1787adda90dd2d347da72680f4eed37a5f4bfca6b77ff9e4f7e03bfed52ecf02a8f84ed3da6da2787a4ee81ad9b000000000000000000000000000000000501fa9af88e28d4f0c0590a2624239bf1724ac7174b0f1d5fd7527cff1de9971d6aaf28ba4005e88e181daffee6b20f0000000000000000000000000000000007af5e30b5aa9ad206645ace12cb2b36cc1c6068e604184ca8bfaac5a4ca327f7c43a74d43417918da7df84e3bffd282000000000000000000000000000000000bfc0538d52f277d54749ed0b69697b4c60ef0c5483d21dda76533e15efedc9e2b2ef07618457d64bae8ef922c0b41f600000000000000000000000000000000048935cd352e999bffa613e3be0a9f9a063d5b5eb46cb5056e41ba214e87f871f216ff41ee297aaaf2994a7b6433f58d81633dd6e729bc17ddc596cb1f17dc6f0e50c052a0b8c5a4c83900d918a9eb560000000000000000000000000000000016ab1e8b6f41891e0b65f14397c0887b27ff27e7463333e0938a7a1a181dec603056afbefdb23b41bbfb2c05807289b8000000000000000000000000000000000980d0ea9ad5c87bbe1aefb708061f85faae1e1e3b01c55bd577631e5bea2b5ffaf5e2478f5a8df89447fb8a73559729000000000000000000000000000000000784d0c5fa243bf0125cb2c83a4040715197e99d507d71a3bd9ca396074cfda652c1ad0dd95c3cfae369e68d3431ee7c000000000000000000000000000000000e533bb33e6d269dfdeedf7d17c3e0c19f694d151e8eef801c326cbcbc463a42558f58cbc330bdff0d8d91e2974eb4cfc6b019d29219b57404baa955f66cf1b2ee6571ad5b80d471ff6db569e32a1a5000000000000000000000000000000000050f005b00f371a7308b5d7d7f67f7c00bf15acc518942607f32686feab5eb503391f964eb7ca711aa6c7b4e494d7eba000000000000000000000000000000000e2ee5092170ea3da0b1397023b2386c65ec8b090484353f2e5d64694aaeb8d5410ae22c92662fcfa21566d70173ef36000000000000000000000000000000001549723160fc7b8f5ef9a84bd1803f18b76698aa7a663d9c107c9ff6c6d02894edc80fd00d436f3a942c05593c5464ad000000000000000000000000000000001032f49e3527cc1f1355c65edb21220c6afc88919ff67ba99c65645cd3b8ca6662dd0146f6a90d92558b3f54815a361d6a76411ce02b4dfc84ddf62ed26508a2dfa5edb5a98a6a20dd69e8b8e7ad2f5900000000000000000000000000000000170b317e49f1304570a3a3e6bef78fcf8537a451ebcfef5afe3eac4aa1aa87dbf95d0f870fd3372d37efc9e663621cf7000000000000000000000000000000000269ae0677d71b2537078e96d2593482e4d41b6d1d2cbec755f307735faaf79c01fa27f1103cdfae1a9bdcb665f592c9000000000000000000000000000000000b115d5a9fb9fd9361d0573a8d68c5193f02edc1cf3fecf004c6603f118f28ff394220f6a9e1051a5d9d4b417290b7f800000000000000000000000000000000107b45614b18c2513f8c42a0032cf0f3f300157b39d2969ef7b126f17a9b5e8e9ecc5a61a2ed4db92134b0797f6a0ea35906098e4ad7e4eb2e996075c7cd660fbc399bc942f9080404b9d0758c4ae14c0000000000000000000000000000000003de39b056f8f0248b138437db1536b7bfee29af00c37fcd14c25c88f0f051eaa07c763d94c8ce497696311736c0b7140000000000000000000000000000000002b52981e828f8dc1cd371e6821d001e1f96d57a865a3c0a255298c43d52741b18fc60903d1a5ef6227061dcb243096c0000000000000000000000000000000016b5335f0f9516f52f2ed45fe723ded427206ba96af0879958f1f22795485b2867e953de3d9b3a9eed2c37f26838e1540000000000000000000000000000000004c860058c7ea2e6e4eb2a65c1dfc20b3070f89ff58ab99bb51a4eb9e7f0642f7b32d1d9f27c668a36a9e053a8d585f394ef8c281a9be3766fe784ae017d93f608dc2cb97cbb7dd3e3814b5ade845d370000000000000000000000000000000019cbbc125ca1b89330c21ef5b42fe0dc1e795271ce4a9ecabff04eec9029f756f180520f0e7b84be2e9fa4af395536ab000000000000000000000000000000001630cf0c4f3282689a3e01b5c8f9be3803f60238bbe9fecbb0d9e8e49f4ec9f6123c44840acb8cf55f8f6bd15579e6830000000000000000000000000000000012afb848bc0ade8f0c25c6c342bb651a7481be065a48944bbedbc14c095af8a4a048fd1e776126e2128f904afbcb17ff000000000000000000000000000000000dbc984f9ff907ce5553bb11a458deaaee0efea49d6816ed7abf1dee7b70cb18cc669d4808e75678bb898359c7ebedbe6feced33019b3b66d335f2118cd22b2952cdf9757fb3a0cff55b7c4f245fb438,000000000000000000000000000000000be6dee62b8c85e36a216d16c5477a7c58f03b992277af83d9b53b3b2169414b72bcb4a97e3667482e888738ff17c94900000000000000000000000000000000067337c69c37ef6f0ae59fddb84c46a2afe7fe047ddb57b3b80437609f1a21fa5a73420fa5b44704ca1cac6c7a99d9320000000000000000000000000000000017fe6f37d2410159e533374ff3812714dcd07610d75a53a5d502cf2f51e750c48858db1e109f6aaf724292c1402382f1000000000000000000000000000000000b8ecfe1f5f5d95777b0fe5d94fe81b82656e6e5a62b7591788baccd251d93e4bbc6857cc87cfe6b4ed470c33631ae22,293920, -00000000000000000000000000000000126d4a9ae3550e31185aac9011e3f086517cf79a279326c264f51bee6615dbcc730d78055489b5602e91b08f96d23882000000000000000000000000000000000aeff5fc04fd06c26af8b048fb2d0d493525ba5c2bde30664e7371812d529ec7dbd584c056b05fe02179b7eefbbc45fe0000000000000000000000000000000017c6538d2801947cbb646d4ec8b70b1e24453f7a984db7ba73e3a5dcf595bdbad9703f2d846ab02491e5e3a5bcee0762000000000000000000000000000000000badf551dbedcefbe7c303a5c8a52151b5460caa22004028893af4d8a3fac30cb1da1e986f9124acd5db7a634657dbd0cb5e7df372d346fd13faa90b0d6961372ce2f32ec379e5e50e7ed8a13942cd9d000000000000000000000000000000000bed71c7d878e7ecccd8233e3e604e564cba0b1ce75f726f846f3a6e2f3b4f5b12a28b8638be647f5c33226edc2bc7fe000000000000000000000000000000001914c20aabaf1f6f82063223053809622ad82a3a54668bd600db1aafba22aeee5c8a07584e263c91cb0fc5fb809da63d00000000000000000000000000000000056d9cd8f79a90d16b36bde77e546f8b3064ba7dd0fde78d6bc538bd6ce12a4f32860205d5d396bab3d70deaaaccf9450000000000000000000000000000000012f7e420708b66132157a80753678de292998cb6c4f00244d3c47a6077b3401132b73c7f52369aa2a6a90892f7be4ed913a5fa1674c20c97d08608d200f3f7611010e6a25a790853ed4ba0c5aacf111b000000000000000000000000000000000339aa1471eddee8cc0a4e4db5a29c3e4e92cfbabe023995a79624614aca522cd459dfacc0cab346b1cedac347e1df100000000000000000000000000000000016cc4ee8cb72fe09e65616fbe9bea1a0077114ca841ae335f1f9eb5a0b129a4bdc77cc6dae8727d74fe21f0d870a43f2000000000000000000000000000000000098a21da6e983228ebbed0ec3704c9d2521e935506c0567e3bbf9b9c379ce6d33c3d0dd8f5e013b431f740964db634b000000000000000000000000000000000a7a38abe8e282544ec6c8740dce8559fd264393d0a5c9af9813b2430bdb92b3150eacb6732b9cc278d0d0e622b263ecace10870acf190b373c19ce615e20e5cb96d3c6be3ec155f2b29825f8476b7740000000000000000000000000000000019ed305bfe8d8bfcc20794832b3c117715b6a658c0bfeb629e5989f265cbb456e857e53d168932589e4ed2806db7c4b4000000000000000000000000000000000e2ffda25fc316a38f556b35a7a3acb1a2bfbc1f9469a1b6427ed1f216e113a379932b0547f5370be1017a1fa0266cfa000000000000000000000000000000000ebc493c9a79b8ba58f48b90b9d287c74f505dcb484eabda79ada987d63a4df04d671d4c4ae4b32f8ad5db6a1b80f37f0000000000000000000000000000000019fc715d26c0c7a0c291ad8319e2e8f2920c63b4d4ed3f0e2f376aeddd4f7bd9269175ac8d0f421b001e2e48634f3f238d9e38d9383f09cf0f8a8077f1d1dba091ff0abdf7e77c3b65c2df48d6c6f536000000000000000000000000000000001285ff533da833a3daae7d815b1b86feb6f20b7592af8b0eb76240f390ea48b69a75547b040e7282b71779f450d3510c000000000000000000000000000000000813d38fa21c1f3c87b9c97ac03e6aeb8fa23e0340a0dff4e3892c774595648743d0b8980a7bd21648ce9b16a245ac3400000000000000000000000000000000020a69dbfb736c64e4cbc800aa415729b24ec05e901f2c7ba38e49a21c3851dc03bd4f7ec829d4326fe6c13867069a07000000000000000000000000000000000d518f3944053c8f74c0aea1d054d89106312880de4479b3dfb45b00945ff8bb58b12f9a489fa9fcd87194a71475d0a1abeffecf9b404c6bb2e2d0c78fbb8609a38e3d3187587c3848e8f9781b7e9f440000000000000000000000000000000018c82052cd483eee7aaa421c2b998ab0b4b32326dadba03c1d923726697d3940b40d5109ba34de09439e833ebc19daca000000000000000000000000000000000e4feddc3eeb3fd1eff8316d5b0cba554714713e8a605a55909889970ea2c8c58bb6c568024709def73b29a5a76563c100000000000000000000000000000000098da4cd0281a16e2e3e542ebb92269c8208a3d373394b0af92dc8a2676f9f0b6e85fda9161e32558e0569cfc7b1f3df000000000000000000000000000000000b7b54b51821fc037f02167d2e640f8dbfd1472407278b4bdf47b958da39f28c64569c3199846c293bf60e86aa45f205adfe53846c0038203d8b8df0cb636aec7d4ed7f78b0b0c1734be448bace08f340000000000000000000000000000000003058abd4e3d49c86ffac9c95b1f07b66a22c42654dc4a2e3b07b87c22024a8bb0ee084a558ac22cc9fa286861fd77ff000000000000000000000000000000000fc9a89ee26c323df22add487a6bb278ca3f4c9a91eba4e067d5abc9dd3afededb4f98263e10083cc7ea224f28d3bbe100000000000000000000000000000000058eb015f1e14da860215d59165e12feb8d1317f652eeb76b3f08b38ed943c94e632dbf8145233dc93755e44e027553e0000000000000000000000000000000010897d5c2b481f9937d830b333e7649931e801a6bbffb7d9a3ee28ab1e27889691a9f0b9616a8437c3cda942bf07282206e9d4e41b628be51690b86aa8938db066c052f3adff774d35eee1e332312d3f0000000000000000000000000000000013b88963296d8c8197cafe160846ee11365b7a991b35cf5613dc57714aa48307f4dd9c6ff9704b29905c18a41a48010e0000000000000000000000000000000016a97fff65fca5ff282a818deb8100104308b8d9dfacddcae32fc2b6082331b44fa70580018930fe1ab9d9c1b13a59a20000000000000000000000000000000019cd2038acd84c2db1f0fa1b7eccc5f7ae3da803cb72c4a1e8390d49e0adff1d88a85696d9daaebce9c6b8a2f861fb36000000000000000000000000000000001271338587f06847770c72dfb3d9a657d05f8c7a012bec77a7d40a98cb1637ae99281c82668486119608b01feb25e6dab3d349b1546a8c235d60c41408c969a0fd42425f8b5ddc1fa5102d2821bde2c600000000000000000000000000000000173ed7c70f4683102cc6a276d192a8f3b189197d5ea5dc813c7d0162a1649e906f76a1c9a1cb1ace6e4d937934b72338000000000000000000000000000000000936d260b789b1a2a9d04388caab364049395be61d320aef66ce50f052eb462faaa2017731518675bb0e4a2f050e4f7900000000000000000000000000000000070bd1254cf4b209ecb40afe248f2e53c390636625460439952ca2977be021d93fbec264c31ced2a810e8a5e54d750230000000000000000000000000000000016ddc3312f8ed359792bd213d086a0ff1540e3e5a2dedf6c450fb96a9b6d1edff9bde31fbc04de382cf44694a631178229b83950e79750e9827ed92856e4d1e1b5f0b47c6bbf3611a1fef8f2fc47659c000000000000000000000000000000000aa4bc6e1a3e6c3c45a29db74b27af27b61856e2cf385ce0e5094ad53db4d31c4af45b5b234c66a21bf15018c13ece8000000000000000000000000000000000188affc993bf6c99103029c1e406bb1a693e4f1dc650907809ba3de1471d41095dc1866578962c72538ca85d09fcd22d000000000000000000000000000000000e487a7151916694b980e62b64ba49ffc54aaccfa0b0fbc5c14fa4a50d1bfda55698df5cd8570c07030f145c49a4ba9000000000000000000000000000000000084a05dced107d29a0fd4cf817ab67017ca33018d5c7302167d08c64c45c5c455fb5c907f21c39b8a86d037a126df4e76b5ac07fb4a184dfed685b93d2265cebd02a3296a3b0416cc6a115242079752e000000000000000000000000000000000ea7060a07dacd84287007a05b494bf19a03e5a759b0ba67624c54cac3562c0ca3fa6e444206614d00d6d6684b86bcb5000000000000000000000000000000000eb2f332f4481276f931d2192c1a9f6d7585e85f248a8ac95aed398cb61bda05230bf8b9c041c6f78be3b34668a9c1a0000000000000000000000000000000000faa038219f844e379d8cce55cb8f0fe2b55548a0a0e1e37e25ba4f432e6b1a6451b8f081c171490bf055f81cbfe5f8600000000000000000000000000000000037c70d4e8befff257c4bc98a4726a961f3e2e68e7e02f9f2c94aa8f5fc67a1da44d41394dfe376a6c04240e4cd5825f3a7a25ad9f02bf51fd73550ccde12374d9b151f2f6fe535bfaa43efc391f789700000000000000000000000000000000100a24d21c0ddb20d76b6d9fe642da5ac1de28afd642ab5c08574206b8b64d1fd822d295476bbdf2ca7e9267138034dd0000000000000000000000000000000000aa7e4f2f77acfe8b4c8f3fabd56b17415ee9bb182bca1db15c399479ec60382f980067b9d4c4ef7556d621259ae9110000000000000000000000000000000012f7a7f91a988fa661c661013736f0ec92b40f571ac15a47067bb847b09ba128d1dcaf8049b941a51cacece5db4e1eb40000000000000000000000000000000007528b0ea66b6ab8d5d318f5e4d1c0e9a4f504057dbb0397b614a1adb160032127f2ac35a1a98da70f023cd343a35ffd47944c8c814f143f746175ba0b2d75e2ae73730a265d869763f0e986c088bfcd0000000000000000000000000000000015d72b8d4e71cc092c2875de80f3d12e003804d980a4b1dd13cff34e9336397c4533b6ae3a03beb2f09312a605947a270000000000000000000000000000000005976027a98f7b0caf4cc7d0d71440d3e4fffb1ff65fbf32dc890b275b646f2a32600a6215d6b2f999eaec8e58cb6d5c00000000000000000000000000000000111583b7734be53a7d4d090486070cd3d9622156c52871ec79c83ca024880684eada56a36b58cfc3490e65de41e10579000000000000000000000000000000000fb670b553c2ed4c81962b149efd4b0c77edf6ee70eba88300cf264dda98190e550540fb9fb95748599bca3abadd752030f33b187df3516866f259ff959d57fa9c53323d5c851fdabb96e5ea470518ac0000000000000000000000000000000003900e7cc0a8e891dc4dfc45f08d97e73ccbe2021a560a92c493aacd9c0614ad100294b5d7ebd634ffe4e5ea301a26170000000000000000000000000000000011ccc136127189728a7036e85d233fd150d5483963c48074f9d8ff83a0791c950da380e717f2bd0bff8fc115e9e886290000000000000000000000000000000007d3e76bd1f22679d228b4ee50a60cf1bd1fdaa171372cfa34bf4136a091abf7e5ef3c6b3446fd41d5de68b563fc7ff3000000000000000000000000000000001107f636d9187155357bea75c943dafcfba2394a9300054026b46d6f9db31eacc06d1f64c2b139af297dc4783026d98f4da8401050f30459e026a207ca631f0684a10813c64ee86dbdf06b7b29cd9786000000000000000000000000000000000e3a4101f6af3cf0d5d5aa5a0ebc26852dc69f91c06e96c5f1c7f8e4528c3dd92cb6f629620136ec356f0657fd9ebc6a0000000000000000000000000000000008d34dc3e1fa8bc22258e23b504d442a11938370325c101f1cfa52f313724e0894be722646195fd078c1a49720cde8c900000000000000000000000000000000163730996c79787e7ab89030de2c26e26188187762fa128ba4378a398ebd906dc56d99cf228591f394396248665c196600000000000000000000000000000000008f0a8b3d003b6727834228798950fb7a3cb6b931bced4540693445a007b474f7459ede17f87158e932e4c9c094ab904d940555d48649f30026f70450b2caf2b8f7148b28bfd4349458ae89c323512e000000000000000000000000000000000cc2d30f7d3869abfc34719f40b0ddaf00f52bcee7ec09a16de51785d55531fa7fe3ca1544d7103b9caf7105d60d9e930000000000000000000000000000000002ebd8af0bd3f82dc9dca585feaa83071534b2bc2b3d2aadbe0d01d759ade77ecec3b3f7b72f82087365a14dc205add80000000000000000000000000000000011aa3734a4b9168d3c46944cd726bcb203b94b25a97437a6aaace9c84da708bb073ee10585f28bc41e0601567863c193000000000000000000000000000000000ceb4ae5a8b506d31e77e2a43f3af8ba9459b887a927ca5287edbc2ba7c7cbba85a6e4d35c099b7ec7bf7eb2814cc38ae140e30424d2cccc91be1fd3a62d9ee49c9d64fa062d9350b3fa567ec21bb06b,00000000000000000000000000000000192eb406b52075513584ae3c6093fb534270d716c79961d0a3c4bbc44096a2e8d28228363e2c8da54857945f1b983569000000000000000000000000000000000ee0d95748b13b531821ddd71a15fc529a2ce2c99a66f14e28f97478c3c2d524cb7c4cd7e71a1027030765554b8f50f7000000000000000000000000000000000610ab3e064532ce261aa2ba4f78721ac4f78661cc13fa09ccc279267e6f703f1bda17265a5eccb0061ce24d31e000ec000000000000000000000000000000001966a334b16e64e4dbd66119af97bd2b8d6afec0eb1b8207f437c00ab134ff369b3b3c1bf51b871a7fe8ad1ce93dca4e,293920, -0000000000000000000000000000000004c22bd94b82ed3b106532a58a0253daf51f579b9d746c624bbc6b58603942eb139c1b576241ca8fab5bf1c457112bd80000000000000000000000000000000010c6f7551d758d1128add57b110227296e060074e4cb934132368f079a794770ff406fc7717867df0f461f5c9fe56960000000000000000000000000000000000048f88afaf6eee5039b76c0c5b4b49671f6fd04f38bdee1b1c8f347a9dd4e6aef387b742c8f9a8aa387ab4d01fe4267000000000000000000000000000000000e7be987d0411dd7138e47ac00f9f07c4737d93aac501edd16362ea5a633c9071a6bf542d4db540d75edecdedc3a8f0ca57b2c351a7946a20cbae1fd789ecc5f77376b09e911749831e9b5680185b15300000000000000000000000000000000056a29b523b0cf85ab04b0a496e078dba5529cb9699e567ca42f9ee3e3f07b61ae29b0ce17cad23131375f624a366157000000000000000000000000000000000acb91d1f057c7aec1f7561614a95f8db2252cc879bbc2595a5f607d8b0ecd6e6e3ec19849eacfca62d870b049ce84910000000000000000000000000000000010d9459e07178af8e125c2f66de699cfafb5f87a63454e24d0ed88b6c804a9ff204f146ecf4d6db62234ace0a944acb20000000000000000000000000000000007256a68e23b43a3b6475b3cf209ec108bac13631ca448cc860672c65c1760a8299fe941ed5bcbbbcf63a683e86806ae8fbff9f8ac4ad10718d46a857ba28f182263bf2d13c8b6a00902af737dea56160000000000000000000000000000000003e33b840426a6bbe15b23fceba829bda9a5ab89d37e60133874f61bf1b10e05d460bb5d228cb178cfae2a5f41035d32000000000000000000000000000000000a9c5460c6443364d9f9440d101d92a0037343789ca0aab6dffcc2bf81e1aed312299a21556d16e55b1398334d9061f00000000000000000000000000000000015db251708253f7de13a5eeae5aa76fec415ecee1ffd88d882580da5da8d9f96c6ff90d920b329096a103dd71e7cfa580000000000000000000000000000000014c3a004cb6ab8465e05d965dc720b37084d98de424b160062f225dd0b67a8e62ae11a3c7bacaa129a568f3a243357ebb061de16f4f609c6947733b58c6444fa9549721fd9a2459652e8e4b8c69b5d61000000000000000000000000000000000c8fecac8bee21d916cc47b96a66b7a522ef4fea76fcc86ec490ff44b46fc01ac0446e3885e36ae7ab62a409ccffcca60000000000000000000000000000000011676ccef54bb27ab7db0b5ec025a9d1f29217030f3686e71564fa011d9fb598f44a8bed3da8fa7fcd10d01e3f66d86500000000000000000000000000000000093aecb91956215980854c6f19120777983a160e16026560c8076bdc4372f53065f9fee0f5830ea192aa5637590a745100000000000000000000000000000000035d773ef15d8d99b600a6a575eefd661aacb49d6540639223a454594570d0f00ba37340b63a2c8a0d4e53ee7dc2dd91355ed5b57b28451ad98fbacd5ae87551b7304e4ef5cf7b7dc443a66432406f9a0000000000000000000000000000000007b2891e9cea2a464742c7f962deb1566c9d4f9e4e7cbee1912a72c5b064211c39801bf42bd888bc239e6b4ba71d700300000000000000000000000000000000169cf5e706dff2945145d5ac14bd5fc8f7e7c3e5f7ce733c865e1882d236926c71853efbea26e13efe4eb0d0e7ed5db6000000000000000000000000000000000de9ee19c4bc2fac36debd4c91317e54f57e761866b134ba9a0e84a8d268b11674110ee8f91aa8a6b80eabee2e5e75ae0000000000000000000000000000000016d91408a670e4ee43ab8e21cc341596709113950d22bdf5073cd90f520667699e94f64f76290f1bebfecfd80a9e051430b6eeb01874ff4b0fb07dc9f23d8e45455c1480eba7fb3033942214e85a7720000000000000000000000000000000001982744a15e8163a6f2ee681bf27a68996682216037d67d91993fbbe040e16ea21a9cb600fc6a40e7289185393544c3f000000000000000000000000000000001131d7dd5a5b96ac1f4c4aa210afe7af8d371cc16d32289aad38c93afcc1d3be53716f82e9d14ce6b1c833f7f5871ad00000000000000000000000000000000009adedaf19fb8823ec55b803c9509ad98217730bfc6424c8b69a071e99d026492e7c8c4a06509491a3bbe5893988c357000000000000000000000000000000000cc60733a783c7df76541daddef2245e6d2b694b94649b13c21aaffdce124c1cec3fd8ed5a5d4d4eff3115ac933e5df989a697a0e8d2cf512edd2a3c3df354eb30a3eaf697779dd9270234b367c2b5ff000000000000000000000000000000000b366a80247a8e3797f1c711aebd60c99ec7caffda34514a3716154e900f2387c46f87f81af036a383e3f9234bd1b50e0000000000000000000000000000000004608b7cea13d08724a2cac691e61255ea7472537f7ff59894d511af7fd99ad72f0a7406271576300a7d1d56aea17bdb00000000000000000000000000000000141abedc914d3d1ed587162acbfddde60f7dbc1ee5e07fdb5f3515b87d1a29024c9e19f24e4c0e3979bd938aa4e798270000000000000000000000000000000010e72c6c0510495dd2c4ecaf13c1c6404654e1be369d1ca485c76d8c2304d60d69b90c2e171f18bf55668232e747825820b72463d54ac1d8f1b3f56f0f98861768b05d5174cf1883dd8eb0410420d56200000000000000000000000000000000081d5a229481fd297363e8e217bf1f94a00f54eb6e8a3f95f4de30081bb2b9edd82d53cf287e37b459afabcb73fea1d1000000000000000000000000000000000ab55f52ff7dc578ae8267fe3fa09bdb8174dc30bb835cab9851dbee7a1aeba82e83e07d5e79aafb34643d9fc9a0d1c100000000000000000000000000000000195245c7a762776bc1e81d7111e3b814088f1e0e7d686c3ee3e500cd0a7ad4015851563a1b8b592e491e00078187c66e000000000000000000000000000000001850c1e8edb0d6dab973a9975833cffee8b5243654bc4ebe64972e423799283707f9ad343bfa86548cd2acbe04ede5da3de7997113708f9d092836c2b0b59abf710d8401baea6de73ee0689436f035fe00000000000000000000000000000000000007e9191fa9057cd7df8fb83d497ad774735c242bce9bd34cfd21d3f8f2a8e37d1f38b592a61ac8a8d22a4287fc5b0000000000000000000000000000000010e36db1460fa65ea229402f558397c6fc57e9c8a4b0b9e85d9ba938196bfeffc951587353cb7c7d84479f60c087e3660000000000000000000000000000000004d86938bebb850fea82acd336c3900b241757dd937f831dd909ce548325955f103dd57611c0b75bf71412a6ac3d6ed30000000000000000000000000000000013990c82583007b693c1d6271c1e5820d7274c4a729da21a76eccbf7abab1f2bdd6c5d26e78d51476ecf154e4fecd1b87fc3d0560432dbb721f8a0610f0db31dfdfea8cd5ebe8da3fe3b8ac5358dd4400000000000000000000000000000000009104610d5887fb7cf6a866584cae30cfeb00e1241083b017ccb82ddc9d72fdc0d2b1d227c22ff6d8497495f44828efc0000000000000000000000000000000002235f959b071f21fd63282fdbb46b1dec27cc193f3e9988def691c73dddd789b6a1adb977a68e2661fb41d62280f229000000000000000000000000000000000ccd46984208f183f0b70c9152c01fdb8ac078ad1d85f41e3a24819da321d9dd9321a8d70103282abe6d8b981447f202000000000000000000000000000000001711057042a54ca76b0c3e7f36f2fd49e339b76cbd2e053d93ec2838848d359865fdbbeb9e75e408b4b316d60ce2741ef0b271f02031a126f8632e30d8b17cc5b57de7b8b873e0971ff392d4246a40f400000000000000000000000000000000001481684941fea0f66c78faa40aeb4b5254bf78c44df7e37b191c095ff12fc94248acf01d2aac5637e9536e73a82c9f0000000000000000000000000000000016b72eff2830f49b24b1e1317c95143cda8bc11b9dc4a91ff22a24e0bc1a244c7215ab1040fcfbc292ab236ac73cbd3d0000000000000000000000000000000013535421771fdad616171f7348cdf32bea7486bf4d836b8b95c69b71ea9915c099e256287aa119af53cf6320ad86664f0000000000000000000000000000000019ba0f36dc556fcf09f0a4a6cee53de485d03d846af7afb792d16220551fb5a42a4261f936b008babc096e6f8f68b63af8b5c136aa5e2d670edcfb5bee9ff6095d85a332ad55763fe1e5e8babd145c070000000000000000000000000000000014b2da0add872d6e61253d6022559f668bf192b4aafe0acfbbf341ada55b404d42b2b31182c1ad50c73673494ea5b7d40000000000000000000000000000000018b76b74e9e6cda8466a354ff66baeb935b5645cf9eca81f4b7342f7914c9bf35c57be402458c09781e66a89cba6e67e0000000000000000000000000000000019bc8c1f32ce934b7ccae6d8ca39a263939585d8f94414c3880fc7bb5a0a27d728708e7ebc42c5a935f769adcfc083f6000000000000000000000000000000001636b62bbbe34bec06253887b78ad5b3ccda1bc5d8baafe450f2d1a8e07334ca79a40c5c4a50b58aaed96408749e6f68285193e7c10646a4601787edfad3d76e19d5b013a0a954873d92bd5293d325820000000000000000000000000000000013c0fd7a8441b6eb2dabfe8c152aa480015f81139c46440741f3da1c50d18c17526c47e8b8c2fbcfaefabbad5f8a0b000000000000000000000000000000000009da839802e7c6759a87eeae5a05146e1d226dd828d4ef6d908b4a0431008f352539f3abcd3e4c532a3d8204e350a8510000000000000000000000000000000014709634973e4554d2379e439d099e9be8bc7ef031b6ea36a7a85d2ff5090b0e0de7cc1c6b6a004465edcf868ef5fd5b00000000000000000000000000000000146779393d82bde1eaa6205e69907a0536c782fa7fc6e11e5e62ad5468f4422b3688f2ff4da2af396741ca5e0f97de3835bb2175fff61894ccbb69d90375df627e925f1ac430a349e75580dd39546e44000000000000000000000000000000000ddb7d0380370830803a7eda2e9b694af71381990f182b5d1223992abb5afe9531bbef8b9dba239f411fc422210fdc930000000000000000000000000000000018b685009d012d72193043d09f8968f9a41ce2fed598a20536fe54cb26db1733214add38f73148e754e632f6d78f524d000000000000000000000000000000000b967a7b4ed1bcd9f3da16584b08e0c28d967cebe7a07069abfb3bbce94d26b6d95d8a807879b24fb1f5ea00091d6dc300000000000000000000000000000000039349785fdb7d38707d8136e9a8f650c4491c50d7425388b75fe30da56147992c3d662f22131ba7173b2550e613477fa25856e5fb9547c48d41783bf2cd13493a1fd71e56b9c7e62af84a1f6cdae1c8000000000000000000000000000000000455d7799cc1c2af1e219b23e8683113fec126bad1dd7a441c5d113b064b552ccb1e7314dfed1b11f42a18acace706e50000000000000000000000000000000014d2400aa3e2270714b656bd755c4bba55866d6e313f619e10f94de6d82b5343ae9a9483dc10c1a72a5a21e619a20a8b000000000000000000000000000000000a6caa6cf8609d23b7873c908e5321d064a9c107b5492d296d04f92c308ee705229dfecb1f908bca0024ca56bc125126000000000000000000000000000000000b31c384423c84316f65e03ba9e01a8f626236f76e4df4b8ce2fa053c1c1e6a9b8f0afbc253db8c9c5e2ce9f9dcf05c71155c0b9c4185025310e8020eb52abb6f2f1780da15e4ba81f3c9a88ed1b4a6400000000000000000000000000000000097938bb53db8d0aeca3f2bc180039a5dc5269748e9cf065cd88e59b30733d527e54cdfa224e9690581e8c7f0881241b0000000000000000000000000000000002d52d97d4dd415fb18348f4de78c65e2933fc45d5e5e1d8f0f0ca1cd52885704ab12609b91d6d2d1ce13eecc7fa0c2d0000000000000000000000000000000018b926a37a8e0ad836846d06c03a9b84db795fdfe5f15d1fd3e0f8fef1b2825b29ee3a503ffb2f75765cca49c2b3d4cd00000000000000000000000000000000073bac093e958a3a09543e060c81b35b6598521a8685629f77200cdc73b372588e66c247097e7c03492c0943bfac4d6bc5610b2707ce84ce67e82d5c0e5f5cd2c90925aefc1e39468ca86475012df045,000000000000000000000000000000000f79110c74f0e983f3d3618869af1d9b96dadba61f1d596294ef8a9412f946fa26cf63483528a57299dae48f85ada81e000000000000000000000000000000000e1a9cea3af1debcf7d6ef6f7b8566b5bb52d5548d4caf85925109228d7c9b50d65a1b24f089631e75a694f8e8dcaf040000000000000000000000000000000010efc1081f079e841eaa5a65cd7c945d4f37acc92c4ace9ae6c69a9a95d8cf569d604376b1c7e63558d022da90d269fd0000000000000000000000000000000010b7f55ffac8d57c89b664c36c20b2988a493de32f5a956c91b16ff67cb806298a59adcde12ead42d598b6ca3e1b94da,293920, -0000000000000000000000000000000017b139e5dddd53433362c49403838b3e2ecdd850a8df12d4dfacc0bb98f79d40966d62dfd0da1e721e7c0f298457d590000000000000000000000000000000000fa35e9c2e37bee1020ed99516174408ba2cf443fed115fe3a964ed86b5e5369e40291dbfbab477e339003ac85eb7405000000000000000000000000000000000e8fb87794860237066ed1b7ae7c2a783c48c52c2267f3e7295d1f17598b96232954e1eb6d6e80e716628f1db8afe48600000000000000000000000000000000083521e3a6d6e3f99570b747498520db5c89092b0077519c8421f9f41772c7a6e177c9cdca52f89a26c6036cadfafa8b32fac970e52778cc90396a5ba92ab98e26499eb1ff17d4bc4c1f78b64887d3f1000000000000000000000000000000000b1415e1dc2d4c1f5619b40e616d258867493d8624857e41d007f82ba8dc53f7ebb36d06f8348b94eedb794899e97df80000000000000000000000000000000001c01656fa47d62b4372361b80ea61501cfda47da5534e3e2aaa27b1e3c4de0bee0aa322e60c476fd4345340e5c00e130000000000000000000000000000000010caa407d9d265721d55f01dcfca52bde851ebd918e8fc4c752a41875940709c64599f36fae5e3ac7f211e1f67890d1c000000000000000000000000000000000b54a86474dd5f410290e4b4ac738fbba5e88c6debec17e38a52090b17ef371dc8feb0573e76c4b61d7688547a89f6a36583bac9672a77f2fe62bea4364aacf62d5e10eb3a757fa0595a81f76543e8630000000000000000000000000000000001649c78147fefa91100738e50034424244d22d8e1bb6a2bf471e4c9b29694a5c9476f4b129912bb09fece53aa87deeb00000000000000000000000000000000117a3e040c1f54b96c2435891a45fb9dd95774b5a55cfb306c22517e4ea72172332d893047f7eaa665fcc58dd21781f400000000000000000000000000000000105e8d80d46e6bab2bb9ce0525cbfc82e8b3320ee4a8b9c0086e21cf2b5895cb35abffedd1b5a9eef21f62a0a1dc48e8000000000000000000000000000000001437ee33abadc8ef6bfeca16c3edcf05480c3dd97db06e396e10d5180472f50074f43f9a031a04dcd11d803462fefadc5a8e1d77c9e42a187054c938a8a5b4bafa834021b727036ed3941b1c1deb9d030000000000000000000000000000000003b51b10efb54dbc2973e001f0bb634e36f689264484eb128de2882d6600a43ad548bc7d1def4541f0ed88a1fb37f3270000000000000000000000000000000009dd80dbfe6663ab04656856f192002593df9ef7f792dfa81f6a51c658c4c9ce5586a5edaffefd507f51ccb7e8c8101500000000000000000000000000000000144160d5ca6b2ad626e6a3424ff5139adadd3319940afa9bff7dc409ac1fc3775d5413ef4612b27fd22c02c1fe57bb86000000000000000000000000000000000e375ff490a626dd1d933a5c751c88cbd61803986fa8dc089ccbdeaa0a922758afbcdc30d29268fe0a34b7b79d0f76c139c02150e4e89b25563985c7802c0c43d00c721d521b54e767c1f509f584bf2b000000000000000000000000000000000997ade20fe9c0d3eb79e61a66a5c272d02af668b0f3c8201a1ea071737f3d2ee3b0764f859480e95be75ab8845b407f0000000000000000000000000000000003215194b6a363d31ece09b18700479e6093fa3472a23ef0133e3dce60a3d56b6fa984b900162c4ad56a6899aacf35c3000000000000000000000000000000001647647bbc399f40124c43510469cf613732d0919e22b478b2603d7553927584cd4b3a407e3ec6387c4a93e9e5373178000000000000000000000000000000000bacc8afdd70e927e21521b3f62264ad4f22adbc872439ff851d3d169a1c79a0d02bca2aabaa0b9941ab1c71d092fac12196ec0e9d2f572856217521fcc5e2869f16d5ec5fe76f7d350698f55ff0c565000000000000000000000000000000000b0c5981bf6ef5b85bbc504fb0196ba442fe87302346688165aa7df8cf2642548760e11daf5b3fe2e37b43379afbfd4a000000000000000000000000000000001086828b9560aaee5e28bcb50db8153c40e632b18c61ed4105bb7f472b6a69ddd8a2836f6605102931ee66b2f07e441f000000000000000000000000000000000f4d7aa3d1a281af6f8afb3d886774f4e4a64490232f63dbe16e3b8c4f626e9d07f7c668d09cadab3c92d6fe852427af000000000000000000000000000000000d92ea3318779b532cd81c9be44b1abb179a8411319a6f8fbd7e3f158bc970917d3e0b25f3f3f6c8e0764011f9bab0398df5017c9c35604f061a7095d976d08bb3570ef8fb518cb606cd39a3060157ab0000000000000000000000000000000000dbd83910f304d0fb2b6d8619c3a308c719f6454a357d9ced03b2882a50692c06cda7f4331f54eb293ed5aa079121fb00000000000000000000000000000000019c33ec829367dfd2610ccef9842ffaa5e4f35809657c22134fb09b024e07949d8370ba8ba1e9149060e9bd3babc19c000000000000000000000000000000000ac468b42925d2daacb8574d40064d393caa643f08767d20e72ac0fad1447a64d8743523312f3a91a118d3e51e1f52d7000000000000000000000000000000000202d1971fef2938cfd10bef5900b91cc4811939f66f1f5578a8ae0eacb2538d2a51c1e025449e1637b5173ab7fa3b6f7b82e7e565f8a521d1a9d0ecafc029f76b70042e1ec36c20e3789b49c7e50ef00000000000000000000000000000000008b6709123b9bd501360fa463dd08076c59177dc0e8035c49fa2f541eef3831e4c584c5a9410c68999dddda6c86fd9d5000000000000000000000000000000000fb94eb34355c636dca909cfa71f52471217b9bc241cd3e98907d4a5c7eb67d5bc9cdb0c73c1369d7950a014fe6069fc0000000000000000000000000000000002e2ee515a5dc96a664bb1f862f21a8d3b7f903fb87f6dac41c3541f3d83633f351ba8dc4661607d24b912dd1ab097da0000000000000000000000000000000008bee545e00e3fc283185a85511e09fd0253e191f52d5c0b440b10228041800c013db3c9322a835e4927c0ae0b21bc1e8260c1b7a249ba215f0dc127a41876f858b20f4422140bb7695c8f98e4c474d00000000000000000000000000000000006ba635e74538748c29aa7c5690a0530f2b1970554598a432d4ea6d2713a4d26786b6e80f67b2f39e218b19323654ea200000000000000000000000000000000133ca9e5e0d4a8200d3522d8e87dec3c72edc1cf16b7305af4abd466aa7a0e30159388d34c36ea030450ef45b7940ec20000000000000000000000000000000004724239afc773688ea92296bae8845f20793c05807a18d6f35f03bef295da06f8ac9dff438b720dbea7ea93f3ea9c4500000000000000000000000000000000149c12922fd69e1960274a8b91384e929fb354936c020911495e6e3c49faf16899ec0c6e87713ee2f0149bf808ac8abfcd68d2b074d038ee0d9887168dc16805ed55df26329a4c0e062c2124a6e5066700000000000000000000000000000000148a4fe6ca67b6c785d5d8a784d5e68fcd2bd08294ca37f296b6426433b805507b554eb9f0fadfa9d293e8cdb8547d4c0000000000000000000000000000000003700600c2b7bfea54801ac95ff7a2c069bace31ceadab2947a0641462089fb43f0b9697acc005a23007a923ffe97360000000000000000000000000000000001705a769ce3c9a7a91283e4068c602d85808980d6fb457345a5f9b2499ff8fb3ec8383049b9b7cae96bd2ac6106a07fd00000000000000000000000000000000052b1f4e8a48a5eb2b2580614c656393819b4f0ffea874be899e4964c7e32d54757f2d48ca7b50e47e8bf6d6ab8ee7572a40c2e796148ed1c539b0584b90cb386844fdcde5d3766cbfb1d1b58626fcd10000000000000000000000000000000012ff8ba50d587765e68f95d276e364c8c40c00b55abc929f9ec240985269eb096dd3cef5826cf6269ecf54bc67773510000000000000000000000000000000000959492d74cb34c8c9ca4a21ddee97df99c8a6e627db3ef72200f39e0402d56f0a9709596189c80aa3aa50793e0f1a68000000000000000000000000000000000f7e5dbe884597054d6dc5e80bf4d0d333025bddebc1fdb1d61482cf15bcb4c8a95ea29cdd0925b5b816cc0bb307387200000000000000000000000000000000194e940c041d71f43ffaa51fbb31eb63c23559069b42dbf8777f35eddf14edbc3f7762c7b354174a584507ad714948234a1e176fb26983e549aefff9aeb220f50e071222073422dc2c44abd85528ee2800000000000000000000000000000000101a8e54d1fc2357df60b0ef8872b729295218f29ff63f7a7b6a70b3ecdbfc6809eaa8dc1f62a664b9987e8e86154c6c0000000000000000000000000000000015b5ddd012b42e1a600d738e05b551d91e7fcf3cb36018ceda9b689b92022224990c11a6fa0b421d5610b7e59b7463c30000000000000000000000000000000016130be17fceab55387d43179cd943c85ce1ff1881c07c937b2cc0645ec9ebaf0e10718ec7fe0d720f49bed2b8caf15b0000000000000000000000000000000017d73650680856bc11619e6acc139e137f0a06476f5f8979b5ba7fb8123d85916915da60d1f2e8c84197eef518b350c2a62e07bb97ca3805ba2d30f39f44e70a7b2917889c26b84bac8f9739bdf764090000000000000000000000000000000007d26bf37a97d532ec93a3eac00d9d39b064ecd172ebd5e18228b1601eb7a2c272aff9d88d63781b4a587c2c8582eec4000000000000000000000000000000000108000e850bfbfb02d7acef97592e15ca721334eb51197511b0eb2bd3bb647fc8f07713487b0a0bedbafb106992de4b000000000000000000000000000000001868c0b2ba732731f7536851f8005e8bae7b16545b39190251eb2bf93dedbf0803a42ec24cebd151998b690c38c0346c0000000000000000000000000000000016faafe909a1f926333b12f5463231a71058aec31d73893687d3169c4c3588436f6178447eed307b642490199c507d63a14278fe7a08174660c08323de272b2110047a1d1d8bd0e3c7d76dde030e00a6000000000000000000000000000000000331338cbaeb8e304fbb9257bb80aff5d3e043d07dbc476dec2795347e4c25248caad06ad14f56183d2b6276c49ff98700000000000000000000000000000000167e9578304a1162de73914b02791468e14faa2e0f161aa57818b8a169b5933dfcab787ec0f4b23737011163dcaa02750000000000000000000000000000000010aadfd5cc781e73c31f2fb64e7981b2e28614aa18dc7b2d96d2bb4ed8c2ee9089d6ebe0cf85479b272cb049e934739900000000000000000000000000000000128d7ea54f338064cd2f041f42a1a1e77d8b9be4ee55f568786a36f87f965d8142207e518798061eb3e32fe3b0f1541d1f516ab5b36a59e6300a54d17363ffebba35fa0c64cadb21e541af5078545b400000000000000000000000000000000004539f22654b3182d4fda5ab8d4bce6f1268d4e402b6c29a4cdff3b5abe0618d33db55ccd1ff12b27b2cb0196ac53e0600000000000000000000000000000000177e80ab6aa8512cc9e4d65b06b2bd76e33bef9038cdc1ab97fbb9d896ae2ad884ea16407490653dbe972b14e9c30c0b000000000000000000000000000000000c280a4431e41df6515979a694ce292f220278178f7f36e23c8a4cb2b8a7ebc520901ebe34c72a26b2c8a60aa1a155100000000000000000000000000000000006a0b80538a6c8093f3655905af1c59c235567d22192758c28dad1b715045189a412e4c1edc26e1d8ac95a584277709b3bcdb23f9568e409271b5f907fd64b0cd81939a52a6db38fd8d95de76213f7b5000000000000000000000000000000000eb091007672a212dc4937b314576963d7561657cf1103820ce9bc34e4d46c24f4891a4a4ada648f8cdd2c30f670b86200000000000000000000000000000000166389a37e6e3c02317d68d54f29cc98d1d1df5853940555161d71df791cd92c483eaad87dc0e765b12408d6ac344f31000000000000000000000000000000000affd0d5734cbc27b192c0c0e464db48d3d76799d2c6a493b172127ef2df6ea18a33898828effeeaceb7a203e35ca41800000000000000000000000000000000155708b9756752c9b44048c91d71970fd2cf2a4cae6b0baec00629c81387c8261150e78f856093d81e816be6403f1ee91b716b02b3e94600867e019be166f4532d264e0aa65d723dc0e117aded59245d,0000000000000000000000000000000007ceeb14945414d96088a7900c1120ff182b2a93b09943c2fd1dc2b0b223f684b0d4c0b1b5803502582f2daf16d81d2d0000000000000000000000000000000008df450fb25534fdc456a8f41cc143a84729ccb082aaa2243c8f37e34a6670f5195750f8547444c49f7a898aa8567d980000000000000000000000000000000008c12d360078d5645b0e095c90d4fd37eb20f0ebbc6fa93fa5beda7e7c78eecc06e0d839268e2c303422ab1769402e0b0000000000000000000000000000000002bd594a21153d7c458b9f804050d05caf2d90bbf9d18def79eb8148b7f89e3a3ac21f84b87fd13c39df5b91cf73460d,293920, -0000000000000000000000000000000003e06e2dcfbd695e9bda0baee1276ceab637fd1fbe2d2d6458c923c35b00edc7edf4f9e797aea59ff8cfceada0615a02000000000000000000000000000000000a04a2ed5e42fac7f064b43d64151a6c517ecf22dbc7563a3e9f35f555a9992fe45cf6a728ba94607df7c96f7e0a334b00000000000000000000000000000000090fac97f9f524168bc930d26ea1627ceaf187398d6bfc5a019c8467d75cd31a41c7eb9fda35fc85bd92b4cfca92dbff000000000000000000000000000000000f37b91dc935c28668c27d38328a511148c1739b65f2816dc53e42a8f059c9b2be7417a6f97c9a2597b1a0f06b7afc65bcfdf0495e49dbb8a8f9a0dc517351f39a6d823dcd42715f329dc78400bd74fc00000000000000000000000000000000090b834a587521729426d5b134c6058bf7999f4d4bcc0812e8d8b3ebb050961321b5e93356e87171a6f12160749394ee000000000000000000000000000000000cd5148c7eeac4aaea4288b38a02b5a901a6e2805e2b1695ed98ed86cfa0d259d87b65bf3cc9d00b8548100a60a371d200000000000000000000000000000000026db1079b85411dea0b9fca383956af50b938a465f35347605c01f3b72b297630ee2fb5252da20ee0d8ba5071974ed70000000000000000000000000000000012ae26c193e02d7ae4a7a01181551085dec9fbcac811c45d5cef19abf736ca2514e1259811970af5913891abe22a75ecf095238bcee61ec1317c0f98ad4f8f9b39c5940cf37a8a3a676787d9dda99438000000000000000000000000000000000ed5d8a609aa4f3c65a89b8dbc9334bd3cec6c7763bff298acd6c260e4d3bec0088e15c5d82618571d13b74a2031eff1000000000000000000000000000000000c28f92f018e6f822912b6eccfab37432ab0ab9acab751f848401791bd2f16e32ac6d97948bd8a0bed2ddc1917f0db3b0000000000000000000000000000000014083be2539d914883172cdc70950512dfe7be8893b1ecf085d837c2e9ba7f03656c5a0e15373e04d300869620eb66d00000000000000000000000000000000002561b77cc2658c54d29f8d1988dd7448f59c80c02ee9256404d8ef5536ee50104cbc11b6ee1ab9ccbf0ca55e53c52aae45a6d64cac817cd479a501c77b6720c6777c6026dbee471b490fee9f242a6700000000000000000000000000000000000fff05aea33a9d1e8f7b227c80ae87c9e7589ba2804904b7d8386b24b0e5324e718f29531251969a972870a30c310630000000000000000000000000000000016ecb8f27a369df13e122c981e7ae37882b36d5492fccbc86d606aa1198f3e4ee7bb7ad0555e11949e6c1783d8f4cda100000000000000000000000000000000187f425b675cb12719a01ee3b78ea73d88f70805f72d6cabef6372ccb9d99008bdd7da54f155454c4c59f041deec86f800000000000000000000000000000000151c272d5cb67b3f801e103ee901deb4b3d3bef76ee4e1b2ce1b5e663ed292845ba012c732d38f9209f82e77f1f73cf354868215022673de608cb43a3cb74ef2073ffff34c54fbb43f19b22a02bcc2ad000000000000000000000000000000001791bd59815309f2aeb7b07df8afd89a288eb6f19c7e613f394353ac5398267e1388c97b17d83104446e57f94581a79c00000000000000000000000000000000154cc72ada5a9c99dea06ebec143a14271cf332b57c631725ab30e2d308d6b688ca08a79efb6fce632cb1216ac3d077e0000000000000000000000000000000012b4c6fe8c17274ef57539563a736c2f83c4cb473e9d075a976e18e193255057340f45de373c7d6e3fe5e08ad0dd97d20000000000000000000000000000000005aef16e11bd4e7787bd5ab4427276ecdf9c6c134b9fdb2ec39e87ae4a5b3b674b5ceee29bcdf804ebd7e83960d8d7ef7068c3ba82e52fce0223a9f28c1d42681c7863c94797d1786c1adbc3e6d10dbb0000000000000000000000000000000008e57f905fa202c7640500746b590791cf9d0f160a77e5eaa5a30280e513e8e801c4b6b04cc3f80d9403388571d180ba000000000000000000000000000000000da3c128ae234bc27824062832ac10aa9cd4978f37855a8b4cde3822f5b485fddb9a475a9805e795519d7f138a8199cf0000000000000000000000000000000000ec11b7e07710161fc557a56e04337f71aaa1a0f070cd84525965e53a1fe445c91ac07c618ec349997890ae893c165d000000000000000000000000000000000406b0eafbb8782d11f5dae2f6214282252af9ae9ebc5c17a81d4ddded40f05d0b534d14019bcb6cf4e49c4c182b90f00042b8005283c7b91ef4b3ff7e20a91349c8c3d1301c9b54b901e8348a7d186e000000000000000000000000000000000b1d456e66671dfa72ef3a56523eb939146226111fdbbeb697983928aebd5f50b0518db841a3d48912a7a780785c1f180000000000000000000000000000000007a15b2253496b78d270dd55b80bff90583a95283a89d40f6df71fadce56d103f0d365fe79256fa4f93b2d2bf4c06a2e0000000000000000000000000000000010829223166d38fd2c3041dd5643c9784da366a2ea8cbb3abdffb5fe43e975318c86de0ac9ec77c0126ee75bd209f7300000000000000000000000000000000004b124018e83e1e5e77bad42eb831798d450f8ff4a79c9b14f67f080047c491fbba45db79b2cf6015188f9fa6329e8be0a3eb64ce8fe140d94956b0685f91a5462dba1a90093e803dc617559a66d20da0000000000000000000000000000000011119be42b90c7857079a51695dd5be08e59374b0d1c7e12d0ffe870202e1f0c62bff84c9691679a82e610e788b7b5e1000000000000000000000000000000000c7a64524c5dd1bf10d16da7f15b39d05c9ee1620d4dcae79c60316a1f522b238e7934d1be897a441d0c8e621b67d44c0000000000000000000000000000000013045613a090d05d07310865d977c8e0bb1caa713b2249d6676e7cfd6f4e3ba8e667deabf9fdf7fd527685f7d251b178000000000000000000000000000000000dfee7f8259701b5726b6439a7ce77b92245499906502c7dfb384e29cafea61f3b1f21fcd7888231569ebf29d3035a61ec88ed0eac8d0f2f618530e91cdb9ea36b8d56c1001a6792a09e11ff65fc02aa0000000000000000000000000000000006d77669207bb2d064824cb56fc786c631936d30db630be3c08e18d7e95b1c26e2d4e7b2eddc2f946fba6e99acb2198a00000000000000000000000000000000168bd8f291f8bcdf8b5e9fa915f7f24856a62803bbbeb9bc38384149008d4e3129338035061631f1fbaceeccfaeef4a700000000000000000000000000000000146bf2dedc262557dec2b4545c94a37434e20e4900b1693e8fa9bda9a94dbd07e0a3bee5f3bedfa42148791f4951db7500000000000000000000000000000000138467700fd5088c76af2f77fea4b746f98701fc0578571997b0ac2fc343354ddc8b2dc57d5298dd4daf767573d8bd3d5f03e53ff983fe4886a3dfc03a353fb77927d7a0d1998a1c55ca7421a4bdac6f000000000000000000000000000000001536da0df7c91687339fc93608eb404c5f46adf4b9122b99b1e5cee0012e27ddf30934d8f669bd39091f8673aa3b3c490000000000000000000000000000000002deaa8f9349e7c551e39751b1454a00f8f7896d63110e8e42607e8023ae3070c4abc9885ed54ee37a82f6e5c68451e900000000000000000000000000000000079a62eb17f7b07d4117956d3dab5d16a7f90e98948d5c3caa124fcf755c73f060a90d002cf880f5246a87342717b4dd0000000000000000000000000000000001246f0f3ec2af7c0250ae14cc67b5a1d42309f06c6f47b89178ff7534c47e8413a26a43f27454c0f946c66634563d41cc1b04dc356bd348211ccc4c50d12cb382660a4f9526539c2a0c52b021ed216500000000000000000000000000000000046e4a08785de985c66c7417f9262d363b9acee07e250999a4a7124f101ec4d82e3e4b2b0d9736471329fd61d0cff13b0000000000000000000000000000000017bf1e20ac181780ced62a18c78b378fc0dad157cf30d6026680560b681f5755183bd30b4e454764c08edb93297590b5000000000000000000000000000000000a57cbe93254bb0796eafc0a57330e38bfca37f8b94c4d21ba656e5616239e1e18ba6d632c0129d30291736fe37a4ac90000000000000000000000000000000007f31df7dbe9abe15f4024d8f6bed93c92ff5bfbd7835e08e870eb1bc4a6f62b3809b922c6d5a7350e2e5a978c80a67397b584ee05c27d45390aba36772ed49d571837567e95f1fd3ba3fc1ba5916727000000000000000000000000000000001577abdf6e915c9c3b3fa50a4601709cd629397f2f91784528e4cdbb140065fc2a6ee3830983dcfd49a928e78cf530aa000000000000000000000000000000000d6f98df9e41009837cbb05bc3e3340d38e56a448fe396bd48acf03f061e7489d1402b36a84b3c56eb859437e9c406f1000000000000000000000000000000001912afae5361c3d8c6141755deeef26d1fadf6b0036b9d05b2e0c4d50f42328741f0423ac772fc66dbc922bd4a837ac40000000000000000000000000000000000616661f049b5c784ba05334b2931509e1e033bd203fe17f04cfe12e80e73eb7075beac9d379fc1c457bea1b6adf365752542cd551cafc5d50852526ba0a23d274317e1e4a6e75c0d19319e5853b8b6000000000000000000000000000000000f98fed7e4d67a513c746d2fb188597a605165d5d299072aad6d621e077845f93804d575a5796bfa726f529dbd90e014000000000000000000000000000000000adb2d0b6c02e4e8fcab11c7c8819e87f73aab673ff9dbc5c50fee751bc7a6a8d386c8f9fa830b5545f94a73ce6e1f1f000000000000000000000000000000000f08e05ac40655cf59ee3ea9f10fc900315c6f06ffd3b80853560559f580ecdd65aba5ba660c729e0bb9576eee3703710000000000000000000000000000000009da46469f4b8fcd8d2b016e96f6e6582fb01c75407c36c7f87b4a1cd8f08ad06e962a0ec2138ed6fabaa1cb0115f97e2f76a0fa585828f79553fbf3baac6a2776b782de66dedd6b734f9342e734ee3000000000000000000000000000000000047b45ad2ad4f7b5b72194f98b98b2150b5d73a9df2aeb2377beed9a1275a882fa2d849037ddb56af632489f892a48a7000000000000000000000000000000000e1b0d9b52c0c5324067857ba4701f5f20eec165be418871fc0f0adbc3a0bbdce5a33277a33b79013109b81e006c621400000000000000000000000000000000179c471e01e340d8e6fc0f737ec09f0180bd2dd2a86d0817f753d1e9a9f8cb18178e9de68c596dc6a824e6c3c151d8b80000000000000000000000000000000019405c1e571a9b200ff2949aa74647dae59d92a8669d4876ba23f1b4a12a1f9412412503c68acbd619cae3ff056bd346f638e6a70917c89811851109296a7225f9c7c5b3d7fe6d6ba6c7d1ee77db4458000000000000000000000000000000000ca8566b9bd088c471fd33fb7b1bf760ee12cc8b0cfa9ad92b45012cafef5c0772d9bd3bd9b266d6c3e3890c8f00057300000000000000000000000000000000055789839e786ecee7fb7d10f3876359fcc1bd6f2c5cf25c8337aff7fdeec9b43ffbe932cc4936bb708571a59e4339990000000000000000000000000000000013cf827bd57d8179d105f34c147665a072714ccbc114aa4e878d04ce66ca78bdabdc4867b3968c75dead147257197c6a0000000000000000000000000000000014a8dc5ac1858442ca627eaa194e1ba64091b5f9ace551338d770c92fb49ee12449dc200c8c35d70f9e0652b4d9b90da1c4ac944341dc68fee586d221db2a8167e833f18f012afa7c3844def6dfb26bc000000000000000000000000000000001124ea2b97a6d73c81387a51e814b9bdc951a773db2a32d50691be60f1d397cd4aadd9b06e4f49c32b12254e9f824fe80000000000000000000000000000000014cb365e9780feeeff3548f34a56548302ae0dc73402c40317fc819969ee9c4ea2a181381b94f82dd97a236671b456a000000000000000000000000000000000064b769c4b785d45472038aeeebd3ba9b28b3132d72023640ab2d7512cc6e31296c5330be5653ad6902e4e15e57e2c3e0000000000000000000000000000000014c7bfb1f142d69c17f73e23011aee0063a97a99d982d25ff72791a65c7a68941a80fc216cea8a49f3df2d0748b1f95db0eedaee9347b10ab7b346fbc16c10cc9db486f561f88b756c269ebbba23a7f4,000000000000000000000000000000000fb1227806c750e0eec0b865daaaf51afb74a88589d1c035c60dc1913f05c8ab18de24903ea876fda27b97a5eaa2fd7c0000000000000000000000000000000019903e1341f0285658164f9273b5c958060bf836264502b9dc298f75d4104d7a43b8d5dc0bb934a506ce1273ba839d830000000000000000000000000000000006e791347b54057195189e8b9f10fd42d170f37d455c0af5e92cc6a12e2c23990253be6855f4be6c84a708852c19a6f90000000000000000000000000000000005b72c361dca430fb2414b9d5a326cef8b77cfe5310153d6994dc1f8b9e74e8fbb43079e21956f428ed8aa48d6897e32,293920, -00000000000000000000000000000000052acff88605f33a0cf1201e8101c95ca0befd443c087265821a7d954917a026d41ab24d29bdfd972bb52ff4ad6de14c000000000000000000000000000000000e134b2aac3f6270e43afd994302426796b1e362031638fe0263c0ec212b828a30d8321af34ef7bf260652150cf2293b000000000000000000000000000000000d6628f675008099e9a75e1294803e86417ab22670d36316798680289ae61a26821693f2f9efc8468721a1097c3bceb20000000000000000000000000000000006d66ffad1a2e0f39488fd3f6e0214c9407528c8bfb8d1ebe6d75596a3e3cc844d00fdf46ce7ff6cd6d67732874a24a484adc8cfd2e42abc2f0e0d7e9c4b378f73731905760bfeeef01c94f8d5c3cacd000000000000000000000000000000001160bf0f7f2915cfc64e12a5a91b7e2aac78d4c2ce362e7677dd0e9c0172b37fd1b52222a13c65819b87593ee32a9ba6000000000000000000000000000000000c8be2cbbd302b31b1ab6dcbeb57b4ad428447bca9159fdfd007f5375218d121673a010f2c7fdf83fb45883458fb068e000000000000000000000000000000000363d3d492e6e6901756bac13b5c32d55aabbedde878115aa41b57d27b49a0f017a61fc90b13a20e009989374b82f5dd0000000000000000000000000000000009302fc26e6d750ff9441d7471903cc296b320128de71f86c4eacc80ce0725e8eea6acd2af056abde2f61e0a349f9bb5bbd5d4a15998d733326ce23cced86ec5d5b410c29ee98a4de19f2662c3933dd1000000000000000000000000000000000b12aca17efc103cad500b3d773e43cb24df6be651963c0f30bca489f1dd7911ffc7204fcaa4511e161c6f75da4a5ff600000000000000000000000000000000179a36e9292d3f78a5fecbec1175f001bd4ac0ff3610f596aacdba29a12ea4844885a7c465e66d3883c7fc99d4a7e06a0000000000000000000000000000000016bfd0758b31f54f90eb8562bb693c45a92a297a3d302279c9e3cb8263efc0f31579a3af8e8f5a091d9a6a36776f445d00000000000000000000000000000000020f6c66fa554a5cb610ab05d194e7583e6798a113b8fff336c986f7358bb9fa6a7aab0b04be9b5c44a6fcfdd21999e83717aadf16301a9c8741d65c86ad7f849101e30b7b1a344643b100a8582a6ad10000000000000000000000000000000004bf40c1d2d3574ad7fe128ee376364591b6f647f939b0b556ac3fdb5a92873f17c007e926b8a39a97c814728f355bfa000000000000000000000000000000000b8669e10e0a538a421b717287455620b82574b96ed09f64db444ec73a67a3227503e1b4fd6869314214071399eeae0b0000000000000000000000000000000006ddea4adb703d7205b6d2af436b41b4bde3a8c5dbed9dd161c9b3b466ebf06beced64fca25c3bbb97f232315daa5565000000000000000000000000000000000d97248a25ddf0ebd0200c6abbcac9ecd9775cfc5ec8da91634e77488bb592e5ff277a9607fe721990f403dd73f746e622788b3597da7b9b106203dd0ea97527aa8f5149754bbb0c10bb6eca8a46d94000000000000000000000000000000000135bc4f28663a6d7d995f6b876ffb8e6ef1d2d0f232388aa5f390c57e8c48cb84d370ebc4bc267eae4466a019c9ed56e0000000000000000000000000000000008b6a9d13dd9d7014df6acb59f80b335a751fa2ba4dce63467aed18f68358f5cb743718112b3cb2d0b5add34bb6989000000000000000000000000000000000013a5389dba4da195f34fbe798b254403f0bc5632ed98bd6017ef24fff33640ae493c1bb7a77a0d3c97649230e455eb51000000000000000000000000000000000a69803a4cc237ddfebc51df2d90fa1ad03359f9635ac1646bc942546575d1558f5f2c3010f6e2207849ee697be41d093c21276fc1371060c226424eb9886de6897b15b075fc5a51aab4710e9dddd384000000000000000000000000000000001939c2431f8ac4ab19d2735f122c0424af2ef18c0028e155611237e86648bf1d74fcba3008f5c6aa30feb5d4a14a3f3f00000000000000000000000000000000174473eedb54aafc522973244ec2feb3f7e95e50a1e996d1100c8da4fa59428c280f76e9e7364906662c4d2802235aa5000000000000000000000000000000001021d15f8ae2f62dfd3862944bf3be88d86d8113f4be22544ae5e925d450044279c5bfa1bfca44cd5934b42a27096b510000000000000000000000000000000015e0f20efae92e1fe8dea2222ce808a7de9e9e861c333db139f8ac11d7c4fa9ae6e49f51095f6e16bc738dc6d094b4cfccbce4e92cf377f67244995badc72db0b80fe37c9b7d443595156fa41abea17a0000000000000000000000000000000012d70691721f5787ea2e2a652f9c65edaf763637f95c285a62d32dded18579b7257493e01eda19631d00ecdd4e27a9ff0000000000000000000000000000000014da9ef6076e646e7d5b52d1803d3a6d897664851c6de2a9b330e48695737e05f0369224c3eb357bf557625bb94e6ea2000000000000000000000000000000001554f68124a91be5b9f325394db23ed5db8f6c46eb46cb50e57947bae00819b151afbf4ab4949290ad41625499f42dc00000000000000000000000000000000009fc0d459e28cd1239d227e1d2f7d530b9d14ce5638cd308569300a791c997a51dd5a98aad703239a23cfe7cef7f47f6ff79345f31c107841ae388f6cf116d10bc696aec4933de56bb9affe7e20c649f000000000000000000000000000000000452580d6a37a07038ce3564a12c1c7391fdb002cf27a6df7e194b38f3c12a3026f2a8acfe5e634cf89140da256d0a420000000000000000000000000000000004b73c9a4f9d41b8b84e53de538e4b15198f50247e75c274c14f136d7d91dce4a62c5346bf11a105f035e29ccac3dbb70000000000000000000000000000000008a8a3b2705a82b551f8913853f682253e7f1f68c8e42f349337f4f1eaa5103f59430af0c4a124b6a739bf88298c5f6f0000000000000000000000000000000012f4220609899e8610809bb3a4da46e0688c285ba2e8750b4bf44a849cf15fbf5c016e8e8f9372239bb562e7f38916e921cf773387d5351aeab99971eaa3b207fa6a318ad60f1c3e16b7f68251f9c910000000000000000000000000000000001884558e709635c046bd6ea8872bda936ba4d5ebcf7a0208cd0a4ee08b69f36dd2e136ce655ddfd89a5b1cf8e48f5ef7000000000000000000000000000000001357e2dd9fb603e5190d7b7ee105668bca2ed23ec6a248aa71aa430c2b2755747b8dfa3b147eb51ea644bf0354a61ba000000000000000000000000000000000009b0b0a76c6980e62e4893157b85f59345e1ac81e1aad1e48acec44c4803e2a9080f0d193fb799e0277ae6f1058839e0000000000000000000000000000000014c984ae4ef5d9d319fc89895f34a7db02747f57b206b0b30e8c9757d4b47419e6c0c8378fdec5aba364936a3b1922ca2d69cfed6bb2d33fedcbd215dd4e9632a3cf86a4b2716406305f6a85e6090a050000000000000000000000000000000003e1bbb872db172a1fa615155f81aa75ee9760f8602e4135ef9f1640b7f9d54bda911a220d211dc4bb767bc2b5e6e23e0000000000000000000000000000000008464f23cf693b1d4545b6ce4aecdc8fd182cfb288c5ddb1f78ca578e9b16342c8178d886cbb6b8086c0fd1318c4ae09000000000000000000000000000000000af574c4d0fd86087e23daf6d9ce98765e1e445ef9152dbd68152fa4833ada0be440de4abfe7c07dbd4ee67f1a4aec9a000000000000000000000000000000000a8227b982f9286b03c4d49766687622206213d88cde007360df9b4ca5916c44ce44dbe6443577998b4b0d527d22593379cabae288f8a9a8cd54523c20825b8fb07886bbf0ba0c5c807956f268af4fa10000000000000000000000000000000012e31070a501a7df7be43dc23e23dafa32ebfbc10ffb4c53f5d36bab2af69db5a05ad64b9ed116560e40b71f9217189b0000000000000000000000000000000011cbcd38ec3c6a6d49df6a8d6e1029a0412b42bd3fe8b42ed625adeb5a2f631e97bfad302de82ae34f715962b5ba0289000000000000000000000000000000001019b1b619fde9fb885d3c5f03a4373358107af7509754ce1ab2deb67df536d05e07ca7d60d927c15b549502750054f90000000000000000000000000000000018f1768b7140484105cf3ad2daa7c565e18eaba834db3f6bdfc9ee37445f2d6f7dc2b4c986b7efd5373224d2c92aa5a81973977d8e8c592f9063c5a14a658990f9c3405643089eb58324cd3f05b5b5e4000000000000000000000000000000001847b14146cfa2e1700f368f414b6a66ccaa02ca2a90b40a8e2be2ee4eb66af77ba563d7507de63362fb18426b6149610000000000000000000000000000000005c028d2b344ccb6400b53134bd179028b8774000ace89369bc655bb9dcd1643aaeec830407ee941df5432ba27987e8f000000000000000000000000000000000c4a680e2157dbdb53ae761209d505b4cf6b18fef5aff1c5009ab41295e0ce2ca23bd7a4f983fb9d085e1d0dbc75ffe40000000000000000000000000000000013c0cc77a5d771f1df99d1530e65ba782604c1ecf67d08572609de9f18405b9b817c2643226cdc7c9ad35beebf87dab0a610bfd375a7b8d0b034c17c8fa27d4366b06c681131fa7daaeeeb08e25c2ca60000000000000000000000000000000009f32f2f83c21875963818872d243cc8c70b75234f53490eccffbf060cb3b9c53545c1c32025b271514f500b20b00ec10000000000000000000000000000000002491b571087a9e89dbdd039ccd2c37d5d8d25587495b2d7b0066e9dcca02d44b2c134b0128a9a1527396729f069df83000000000000000000000000000000000264e9c47f72b639597de8f26a42ca7d77324f8c0db705986fc3b40dfb46f47764b69c70037a68d76a5de49a278779a100000000000000000000000000000000090614b3bb302ed9fb78b8756524fb78d54a4390b27136087181342571f994b1a93faee28256d765a8ff4f448cc357c199ffe1dc2d7526338462860501d75380a5ed9d53e675125342afb6652a97437b0000000000000000000000000000000012c716ddf17fca0d974e8d6003d99aa90f06b201fd141c74d8fdf1167030d14dc732917d3c6f736c68fbde9df50c098a0000000000000000000000000000000000261ef2b47de8e1576aecc6e19ececf80ddc1f4e28b2ff27953a65199f65a6211db7326632cfe04d543895c727ef8b600000000000000000000000000000000044fd6b9b4a1bacb8b7d4c53c106b025ae78f17c3baebbccca4e18cfbdbcbf8b3ef88ed5bd9bb36d9aea9e24f4117e760000000000000000000000000000000007721612515fd075811ee804314acec9d389900c7ef883e866f71fba00c49d5c4dcc7a2b8e2366f5a93f4577926ed171fdd97465982b58e69993711a6a64134bc4e76b88ba1948af91ba3339e9b9d3e900000000000000000000000000000000122581659ab1712afc23c23c2986394de8e155bcf722e944ec05e7e42e05acc366d9a7abf2136b5dc68a8dcfd4a640bf000000000000000000000000000000000188842cf4ef54cf77c145acb685d3187cd9c842ba6705bfed846ace83dc4400c45120fc1d6a633ea879840d3d0c902f0000000000000000000000000000000005c8966862ed4458a753155ffe2c64655779860149641ee5511a46ec576798fdb5cd9521528df77bfebcdaae2f94b865000000000000000000000000000000000cc10d888d2b7a97666de99ac14a501b7e2171f074d30d947efd67d85226c312a7977cf923ddbc88c533f08a99f2045f786a2a3974c84752b32f29707805c71992d5d473f4b7bc1f0757d126607a1c07000000000000000000000000000000000e5af1420546c1a5a0e0c2bd9241bb7c7a26dd52f4f358fc868bea457a60bd4f6bc5b60b27069fb4f6760813a91ada740000000000000000000000000000000017426a65d239b1d9505bef2b476799c394fcc7bfdca36a1ee5a600351334dadc238b64cf8a667a25d4880a31b73c53a9000000000000000000000000000000000f151587944aad17429b51b1c16193c1e1c93cb412538d1475473666c997e012ce618eb841c4e9e064a08ab83d7fa60e0000000000000000000000000000000015c2e049c532db585807319c23ec077a51f288fcffb2cb6528d3697221e8542e3fc85d18b079ea1b217fae30858a36f285d33a7fbe6ac6eb42eb932dfbbca2f771ffad5e80fde686e5df9d34e9f83ad6,000000000000000000000000000000000c9be91da9bd8774f18efa3ae9248e4b03d11c49b377c372613b7e745882b2b23c49d518672e58eabd4d9b510a25d8fa0000000000000000000000000000000019687b9eaf5d68b0e795cd57055a74e44efb3e997cb038b7f1cbf08ca70e80a1655cdb04402c542a92ae4e435c22d0b90000000000000000000000000000000010aa1514402ce348d1d61b8d38b53017cd3977a84dc14445db64799cfe822b56a0adbfc5332093ce7ea1f0f438bf15590000000000000000000000000000000019ade30ba0faffcaede95aa272be042aef090f89d9ca25cb825846c4bf9e4c1dc575f8968c88ada51fac71f26fb01517,293920, -000000000000000000000000000000000a1346771f8ba25fc44323d5290068e46b3f756de6d97aa934d511979a1486bc32173575639a7e54aea8eeb60f32a8c3000000000000000000000000000000001958ae7fce87db47a65a03402313b99f659ae02e8b62db3525d48dc9075aacc5e5abb50156e704701f3ceb18747e0431000000000000000000000000000000000f98778311e28b4081aa76a3f9546b94c29d86fe8e66b905265d74ee21928dc3ac463049f70d355d8caee5b59d65e07300000000000000000000000000000000185cc233ce72770ae26406476c1779858523e7c940d69adf2750695cb12440440686b6b918f4adb3b14aee9aceb6422119582dfd9cb80d44c17c5f62360e62f6736d186194f0f8483e34d8d18d832d37000000000000000000000000000000000ae2f565a44c8e07f2a136368798a44926cffd3c3a6d4c2fbf91763c20d2bd959271343b80eccec4d59a84394c7a3ce70000000000000000000000000000000009481a5fb276c938801133adf10dde3e7da2087d0bcecd3c9435b7de544271eb3b07a69efe7e168869e727868f24b0d90000000000000000000000000000000011774e0197866b1c8b3428d353d2c9f6326a77ab30d5595e2402a0486f03ca6ebb1e8dd335a60a772dfdd9a3dbdd3eeb0000000000000000000000000000000011ed2480d79f73a67a2adaa6da3ae4f1e1c28feaf0e4cb9aafac658901960129e40f6415ec80a31d72004899326f714bac0bd9b8746fd02aa70d8b8a2b5d3be46baecf9449d8cd3d620cf9efb3c615d1000000000000000000000000000000000a73b0d8c31af2deed481faec54095875639233bb09f31b1c7c745cb54778d1c8bd0a230e963ddd2ec8d178d31fc14740000000000000000000000000000000015a889b16be93d0b6dced01f5e2278ffde1cef0576d0b04b49996cc5252854f879e04b1ffeb90e222f4b9d5fa350767c000000000000000000000000000000000b53dc4d72e90330ffad17012bc7dd2e497cf8aa6ec73bf25c10427e23fd28137631249eabe9d0308c956dc7a9e92047000000000000000000000000000000000930cdc5d04ed2d1eb62937d9f72fdd733c07a5a0e392fd5216100216b1a2e3cde7053bf766f046cc470d92bebaf6290069d889881d5bb87dd65a9a02a7fe239bdb55ee54a6310bc987e7c5772404d7d00000000000000000000000000000000131c4e590400b69b3657f7c67272b1e3491983997993ee87c94043001d78e605965abf3c1a8c8c39cc08d5a5ef05520000000000000000000000000000000000124f71c136dbb032504da910958e8a7949f1dc5c061f21d50e439e01e67919891633b3bb84fa8a54c69b632f78560ca70000000000000000000000000000000014a4b1a05f1060853f4294e669a20b91f939793a6eada6dbc84fda8ab11509b256d8b785b252a3795f1d2b99a51df05d000000000000000000000000000000000be2489f1f91d7adff356236859679c46b6bf8c1b375e8bc8bd1e97830b5ac223ffbbda60ecda168bacc2c0b90ed25d3be658348e299bbf2438a0c013f86eeeb69a013b8004a4996189472f3372b326c00000000000000000000000000000000111ebb796e8770d5a69e724a8d3ca62ef1f13778baf4ba12bf462211d35e325ff8e455c85237a73a3046e531f2e2255e0000000000000000000000000000000004308b76b06067e0a07bda143341220809b481b40b78edb2e24e83aa0f003d209198825b5fa9bfd92597e27a4054d3ee000000000000000000000000000000000de74485713f5c95653e98b96aeefb79b59911a610c2a848a807653c19d50394fdb52178947c779134d24b6d396ca36800000000000000000000000000000000069f47a71ad765591f6335b962e7c2d87b556801e1e6c25b449edc83432612fefd405c952397a704e9aa5a924769ad4e9b9d0ec92ae7df3f52a95747659f8fa3ca2cd01e8d7ef6de384111246886bafb000000000000000000000000000000000a3f89408ee43c0ba6a7c9c479327ebab426d430e3ff212c65da6364b16195619d27eee83d701a2ec50bd4b7acfaa06300000000000000000000000000000000092715831af983f740ca2c673e7c9c47727d64165c59fce19dc3fbbdd0b6a7be66288ea1f033ebb5ae2b38b3762edaee00000000000000000000000000000000071ca6fa9e546d4bce965b2bd0f0fb97e6833f05cedcf66d43ec88aba411dc4d6db9f1591de22f493f49a1dab1a2701e0000000000000000000000000000000018f89932ec032fc28775d34d588169a1435bf4ad7e2ee11c9d6934dae31324ddb96b3ef88f95d1bb2e52c3c8d9c01516d2ffdf1237b4e03c219806f2dea745c94bf08924e1b9f11deeedf0db19da6f3f0000000000000000000000000000000011b5cc382164fc21c9a72cd85acf61c2a78d00a16a2dff938f0b36bfb3bb7075845a1616001ab53271a9a257a38312cc00000000000000000000000000000000139ba2f27e545d45027a0b11253532e28fa691170e08608472ce3b3f9a3e9398c5ee76953b1a1d01a5e79f194c32d1f5000000000000000000000000000000000d875f44829555cec695f3f4a28078b0a6f168bb0985793d003443b75a141936f3c7c633518890e0f7238416d46573cf000000000000000000000000000000000675420ed817ecd24bc5172d3e7df60ac4281b24ba91e8b5ca8bd6a8321f5c7312a6ba043fbcdc467c8a5c957590a692cca0751c9534cee7f14d11b7c8ccbb2c537a799df59f850bb125c6362d72e9c400000000000000000000000000000000107bde844286cd3958cc7a1314127322251699b51d8af8e6b57495497f21a84e05612b1569b54fc5639a75e9f9deef750000000000000000000000000000000002355b1a60e24e4879448437d2c1b12e58f02d7eba88583e96e9634f7e2c8c6886132ef0488918f665ae3f7b6977c7c4000000000000000000000000000000000fed531e437b70bc4a19ad63c61ccaab49afc50fad1f156b1c8ecba0e1b703f8aea61882c6327d4d8fdd072df9c4e73500000000000000000000000000000000182177409579ad53786539514753c696c8757b8c4d9b8360392f24b591e43ec20e84c0abe468061a9e5e879c5c81314217f890a1120daca4a1bc1bc0fa7529f0a87b5fd6ec385f12b270bc0f1a5281b40000000000000000000000000000000001fb25395089228772d6000025cb0356eb510c964bf7d0c12d47a6608fc18cc448e44880eb5ba8475cbe6418fc9d8fee000000000000000000000000000000000f3b9de9980e5afaebc59c56e02fd75fdad13013842ac035f8d5569a46cc67f0cee461a939aa5a3d8fec2966294207930000000000000000000000000000000009a223ac0edb164845eb8397e0cae4363fb2c8c996c3c5d722cb50be56cc3789c732763cfd4b61470886dc991be39f57000000000000000000000000000000001909f17b229eb351dfe8317a8273d846edf14ad5ee0ebe8cc2b595ebfed19b73983035e19ebaee3d05b1dea35968586961ca18257d9d989ec13d4f158b18ec17d59344f4558b6dae6c0aa0c2f37affb500000000000000000000000000000000081fa9eb8ca7d9db52380e4c408e6d5d668471bafbafd62ba9023fa08f6d300a45295b583677824c29ddc3254439cadc000000000000000000000000000000000e2e613043b1566674f791dca9d860a49a75dfa24dce3fe18f544a9b24ec5266a64e77386b672c93fc4d079eb8e76a01000000000000000000000000000000000f471b86ac5783d720e7d73e8871474c8665e8a109aba27c1172ca24217eefb0f66c53232df1672dc0af6ddf9640e10d0000000000000000000000000000000010667cb22a6a818fa7c729e40a7e70e1f31b0ecd568b54a4d352d5c9df8cf1072ebf2ef1e612efd96bddcbeedd8566430fc004ed8a135ad97cdd1bc4d0c3ccd15e65031ad7e3cc13ef2c260958bc43be000000000000000000000000000000000a0ed87b01f27f26380c6285e82bf2f12ef3016c7e7f3a13041d465825664573db47be6cf099cea615e21f6a5d759b6a0000000000000000000000000000000007afb2a1bd50fa0fd3174d70f1c8d5c229627a496bc9bb89d4f52d47b1862e14d704dddd80045e58d00336e898a996eb000000000000000000000000000000001698f30f824ee5cb71b3f2451953c371987433d2eda570f2a13262ff9e5e529e316b06ef6aadffc152803b076f22db9f0000000000000000000000000000000009eb1d5f3da7cfe9b40a70e1b3c3dae36436e8d068a79dcaa283905614676645c99a5a165630ad46b70bd6be8b1f21a8d8cfaa1037e2c81c6973b221dc7badf25ebe3fb4b42bbdef1124265df2c7ccc40000000000000000000000000000000005c4390b8f37cc3fb9f248470b505a5d9502d44e4a4459d1f56452cd9aec89d114f1402fa45935930fa00888a4860a9900000000000000000000000000000000163b0ca84b5cca4f124bfb5a13a4a3efa677a84dc89b6a61e69d0aad34fade528614e549a7b2326d1f6016bd0d35465a000000000000000000000000000000000bf450dc8af483a9f993a29cb47d5362c9f5ef38afc2fba8040e14514eb834fec6520a413fce5868aa9a2c7c3ff6617a000000000000000000000000000000001063619f384102949fa1f8353f0aaa5031234d736c54103df6ef6fcd0df02a19c3aef471f0413a1e19febed6395459a0c25ecc5d37659ebb0c9e21ea2f8fddc518e3d8faa99627b21faf105445f69d7d000000000000000000000000000000000e35db3017963d3a9d62b7e7fbfa13ce4f5fb46a90c1285ddc0fa481d9379b95a77e8cdd4aab5c33059bfcdcd82473fb0000000000000000000000000000000004fa27c663c8d21f041d15cb199d31cfcb96a56cd673b730dd111bf03cd954cc33799456674ed4d58e8e0dfa826a6b26000000000000000000000000000000000e0df4e7f943db5b5c27bafc7e1ce099b2caa64642bcd6336ef926352682fbe81a1945b266cba7eab52b16f4aa63eb8500000000000000000000000000000000020167756b8c68f535c4691b1249ca1ccf0a539f7274623ada824d0ba789ef44ebb20ec1ba51d46c0a42da78653d287e26cbb32382902d9b1963779070d749cbc4df1e7605f840819f2c04aaf89c732f00000000000000000000000000000000178037c6b5fd1c6c396d8aaadb712863557feb744d2cb9165ae5c36376d2c066f7b1648e083f81c2c96da6562e0b3c20000000000000000000000000000000000b805b4e1cd5d45d8b6ed9d4f604ac0b40f336b8123f7281df43a6e803f8688bd8087fc4d5fbae695d06efb0fa35e18400000000000000000000000000000000000a947562dde45f613ee1d15614940a2edfc770d733a60374f8e9188675d4cf973a5c1081c11fe5a1d93bbe85e6f47800000000000000000000000000000000059473d80c82c6ca06b4aa71d072f4751b3b053b53ffcfb4a84906ddfc36ec5918668a62f07054af1b241bdd4485edba699aa549077a80ff8732b5fc9df148a90f405bccc14bf7305266836566b7a98b0000000000000000000000000000000008b9d0916a9f5689b8fdac84bec3a49d0224dbadca6329ecc156da633e1332bcc6735ca3ecb228c22032dcb7b2f372d3000000000000000000000000000000000cac0c264add10bdc1217384a7379f65b93cf822418f7e4e2b48eeac45f068a61f805cedfb1665dda06e04cb726d245c000000000000000000000000000000001578e98a40a64da59154b1c3d757d8f1f8cdc500482c7b7d65b9997576f745442fbac654c19331977bd210df440372970000000000000000000000000000000015ef69f82e85c81d28893d94927068f14c6516eb7d09898d5d055cbb7a9b55c6d7f686f067ab164160e6d6a8f91ea19d40e2de1a2901f1380a383a741d79fbb0a041da5d7bfb92edab74cd483edf95230000000000000000000000000000000000a6a27b498285085139b8dd0c37b700997134337e696c84b5e0cf70ea3991cfb40ca3a3098a3b3a2fa31e91aac78eb2000000000000000000000000000000000bbd7ebf4301c5eabd4f448b89f1b227415cede3247a1c8dc56a02247efaa99dc78cf370f644ffc06cd2158fa25197dc0000000000000000000000000000000004535a402540474d53c084d4fb6d9e12dba6716ee13286ed758aedc1ef911b55c572640180a54cbc084ff57ceae8a4b4000000000000000000000000000000000759de2a9e0f3c04b4f629a682dbcadb2140e5b935845cb55bd267e230e08c6e8cc5426057473aa03ea2196203bbf6dc062b323592118868d547e83b731d15ba2c7bdb1ee4fdf73600c2584f1db0b45d,00000000000000000000000000000000134c29cc5c33c10f04b6c09b5db71b10304028d06ad6acd4f4b39b16823288085a84a0380a1549f04b3dc692cb8216d3000000000000000000000000000000000a0a9379d63527ab9b5f9c00be4acd54e5fd683a0a2f37c85ba570171c705eaadfb0f4e4be1a8836c9de86dff46138300000000000000000000000000000000006ce78f135dda5af34a0e069d7ef13fd589cec5a6128512bdae7f45f28b09c6e4b3cf638628c9f4783097cc00082aeea00000000000000000000000000000000141e710ce7a979dd1772150d0cb2d5b269d5cda50d1bf7bd0cd827b24f9cd8c1e2775f495cfec0428519627b7fede464,293920, -0000000000000000000000000000000018aba8353cc470b287a163fdb9b8b4cc46071543ee8261f928a7b856287946637d9b36b728a54e1df5f185a47f1556060000000000000000000000000000000001129541b2e3b2e1a553995b603dc3eee44a5ea440e687739ee9e1339dd79bd96c67231ac753d483e0ca96b27054997b000000000000000000000000000000000e1cdf3591aadeb56dbd80890ff7d5639a64847cec771a19c769df7da732a6d3179d3a89ce0052bd7c982af0304408120000000000000000000000000000000000f5f5f0ebfd2b632e15381ccbabfa88eb774f2c61801381ca73e6970965ecd54f5f3a9af7c152186af8fb75ffb5bc25764ab6f4c43630d5e79e8c474d76d8973a7b7bd1c7f1a985333cf1a6be5ccff2000000000000000000000000000000000e527e40c311edc5dcdbb4d0b70497eaee14533aa8ec57dc7cbd7d839fe6c6ae62b1fd0be2346a038de687d5cf5394d60000000000000000000000000000000005f9fc63027dbee5e0d55cd6c57daf5df7af0d138393a2dcdc71ef9aeeb204ea347f7d574e71f2ffdd37d8f05dc7979f000000000000000000000000000000000f8788034c9f1c9c2018a52326c046cdba8997199732152963819b663c6e58e9d6a0065289e2e25a97ce5627505900f20000000000000000000000000000000002a747bb3bcccdc6ea0af1bf1d0ce55de3f41b93060361b30c76063346b606322a76ed7eb260219c83aea0806ac7d8583280f1b1e78d2339f64b5b2f2bd77aa24623b79fe2c9debab4212f4ff564983b0000000000000000000000000000000002148c043e065132e978e89f018a5b728d278c95c9cd1a6f276bd13f0cb708422a62fa22f7b377adf33055fcb09a6a8100000000000000000000000000000000024c4c721a0574e53118bdcc3fd41f73176bc8264d2ff39673210525166bb3513016b5c9af67a47a7032b74a62effcef000000000000000000000000000000000797dfa8cad94896916b7d55fbbb3eb0eeb74f70231205388d0dda69dd8abb436c22addd22c1e3689093739af957b65200000000000000000000000000000000010dd2ea2d45528de8bf1b5c5dc3267fe8951e48ff5987e67ec52d58635521cf1905f1688894e3e23a659764880b2301d4d27ff9d03ab9120ac2adfeb36b070015f0e90782255ddc9111704c5fb11177000000000000000000000000000000000eecc0a4edd3cc3f70d3e0e43ba56b04cfb3f1ac23c657048a94318e622217572b0f929c73f545d6f5f5613920c0580200000000000000000000000000000000137a098ea8d3aed32c197a2d244a2e18753045b55cfe16874f79c728c664b7f23b10476f20dfffb2f80417c26dff4f860000000000000000000000000000000004a7789b02d7d95a2ce0c7bac39d5b057509200393450a47fd9d087a353f866921aa11185550537b98f3073650d9a1370000000000000000000000000000000006ed63730bae06403baf705da0e30c6c00739799eea4a312d06b8d7dc35cb43a4f1e941a69e55ddd7ab8ce42d8629fdcc66d5291311c7cdd1f33e5365ec0689608b3569427a8f6a9cd0b94b671472e66000000000000000000000000000000000ee7ddbf43f17f722dae84d34d26add8c1d732918b8c75c6b295f2f584075cea0c655911410b32c06868c1acf36aeaaf0000000000000000000000000000000018775682555d9f5a576cf9462170910bcdd083671ae2e4c8c6fa99a702548f1ce9afe90e681b00d194322b1a2a3be7ef000000000000000000000000000000000f3935bbbf58b91fe8176f3e25ad3fdeffdd6b369ae70b704d4e54d4fe32fe5987e73aa5aa975e958497340274577cf3000000000000000000000000000000000c088bc439d638d86aba6bb1e6e9f7540ac2da3b96080aed455edd1fbabfc141e26f125cc3a9cf72070a24f298dcc3ef4b718a5129659250640e333f4567043ca749063e63d87efd86a9995adfd3b8450000000000000000000000000000000018d8a47d1a13b9b8fb5a1625f9616ba120d5c677bcc996f694b7e15d251fc4bc938b0a7cb5b70f22b8e9f5b416c513210000000000000000000000000000000003d0646458bbee7ccab27f858b8ab0af0cf583da12a40ca5a954d7eaf97c897d379129a63d8131036f29c30c6e644149000000000000000000000000000000000d5466b50975c5a2dad96e4e24339eafc8c85c2497a6f19e12d96603596498654cabea6995a92c91b8319ce06f18d56d00000000000000000000000000000000191a96d62139f8219b9e4369a783400d045d72ab2dd83fd229e08a4ca73de59a11a5add86c739cb3bab4adc5e9f79685708891f45d7bee38fe382820260061e212c6cb9a8572b4d1854f3ab09409b05a00000000000000000000000000000000032eb1f7846b563e98fca0cd44ede4909b6e16a893f5ef01eaccbd7d8aa11710606bbbd0ee6480f7cdbdc9ffe66c3a9c000000000000000000000000000000000c31bb6fb537cfcbffe815d86ebfae1f5053ceb756818ede8a58cd84cb34d0eacc70ab9095f9db1691e4fb4bb816d570000000000000000000000000000000000a8fa1dc2f28277a4bf8fd9665d4b5c3baf1352d89890d4af94a3657cdac7fd72558da1e65cbc5bfac142f0e817be74f0000000000000000000000000000000005ff65c22ff0abfb33518791823c5f2202ea5f7258c0a507ab84460335ffc2cc8d7c7f670752a7647d6a6487ca0c9adb85ac0f94f300b004c7f20aafcfd9129d6c2590749504a3f08c4cc708fa30100300000000000000000000000000000000190379b7629f74bfb88096dc9ffcdecebae0d653410f032a35a811a09022679c9be19f3790af95c3205a396819e068e1000000000000000000000000000000000b6f114fc277ae8f0b5374dd349985bf955dff7fcb0095e0e1e137fb539814be78c924074bbab54f29dfb42f3e7df24a0000000000000000000000000000000002d86b0507c147142d03d3461bfea4c3af7e57a6edbb372387de24a27cfe27c44ee4b9571325a1b3f5e83eef450f2fc6000000000000000000000000000000000ac3b226d5e13c36c3a8ef0c8896d9af55bcc0cb67ac1cee57a5c6519617ec77af9af60ed44e0a8284a2d59816ebf848fdbb634bc0f99c5795f3c4d6a0efcda7f71427f1eaa1c5411caa6cb05ee3147800000000000000000000000000000000079cd4511e953e4d1b3f4f3bbbc66a62772018e809779fa39aaeeffac737cda9a6116293848f967577f03017f33231d2000000000000000000000000000000000ce3cf48be423a2fc0188b94f2a22579872e9ba140798e560ad107f63ab2b8c601831f89d06a4bb8e7a758cf836ddfb3000000000000000000000000000000000a6a90f735f215a79216fc4e7daffbf74775f38824952af72ac38c38a77a277483e34bc95031679494d76f109c0aecc4000000000000000000000000000000000d55fbce780d885cf817cd2126e7acf115ae9c72843af23c376f3a5d4307d1eefaa0f4691e7c09b5da1707aeaa5b675af5e4695c01849259fb969183de385ef30c2403e081067c2d9b6b5522c73fcf200000000000000000000000000000000008924efbdb46b9324bfb79b922ba8b7d83f5e5e4b3b736105e5339805838171801bcf17208f3dfe5c7475d4e45b6ad970000000000000000000000000000000007bc0096fd23f0c93f0dde8a3974ea3105574e031202f6316d5940c85164c6d6bb5b86078a0c68dc822c0fd1b3dc8cc10000000000000000000000000000000017276b3208b347388a5657b10e3c8e4a187b376e42352f76ee3ee88873217b6b8185022c93097cc116abdecf3cc64467000000000000000000000000000000001915ff932acbdeb52f07b664bcc47c3a5b096c6cec32da4d7044326dfe84358e49539fe50782538a901b99428446b0f50ea6fd588db5efc5fb2248634cca683d39d610886b59eb3077fa9612c368d7690000000000000000000000000000000009e295d229b543a17db1cc85c846111b7097bd169d19b410de78f8da9684e664922eae77c64b0db430aeb422016cfe7d000000000000000000000000000000000e29aab30a1da56b8590e9df67171cc1b9c847696b51147cc57ed6c3b55819cfa0992c67e15e4ca6de2573c9e16231c10000000000000000000000000000000007cc9990c6722645e320dd16a4be8adaab41f958f769ba0d22e235549a7457778cb9b14aa6ea5caa9e0bd43f8d04cacc000000000000000000000000000000000b2dab5cf37ae8e76b71dd8748c86e8823142792445fa0b140de31957d35bb7267e3d94e0dc92f4342d9f8560c5d9d86dc2060a3421c5a8336c80983c9a160345901a496c3a74fc5248fca081d09953900000000000000000000000000000000128e2aa795f8479da3ea2a4efd12aa90a6fb019d4da89fd372e6848ff7ee17da689d766c9e49c88c962eb4f682c56fff0000000000000000000000000000000000fd68bb80d6b2200297aacae1174275f864669e962d85c9105032d7a352fea548e9fa0629a6749c789fa0827a40190700000000000000000000000000000000175bc3918dcc972fb728f1d8cc30ce9887efc6e0b254d8d22af87f95cd4182129d494c43d11b028c4b9849f5520a4fc00000000000000000000000000000000007c5363f507a01c0b6935fee0413345bceaf1336cdd20f69060bdba2e411521a61a549e6159b2e006ffa16e3bd77e998e27e4afc3e6d59d0f5871b35eb83b46cf15da6c326e88dd8edf84031f58e23f90000000000000000000000000000000000efcd782b89fee74ebc037160c6653ccc104260b5f8989545b40d51ead6ad6ce6252e1232281c813e3c883af86e68ca000000000000000000000000000000000b68ed21f76ce131c089dc454dc48ef948cc7c6d5fd87d647db954c9eeab2f7f76ccc51a1cff8612e89bceed16ca03ba000000000000000000000000000000000cd776670d5171610046fa294fecefb42f9bb4d71baed4af65a09018b09ad9341789abc23c9feb85adf96b4203b0c0a0000000000000000000000000000000000ec4ca0091a28b73c9adbe7120f2bf1a84a62ebba1e86b1948389b1a1966c1de4c632a5e245ba634b53cb932f5847f6ecc7efff04f143e2d038de153861da5e04016a7eb17fbe6365de13069d088b1a100000000000000000000000000000000022f319bb5167c2b945a69a438f712df8975a0e262438ea687e2b0d824e2d1d14bff1065f50fd6ae92494f6f3aa9472b00000000000000000000000000000000198ce9e4ddb6b423788dbea82d75513f43cb43ecf1b27c8788f041248f01808644f60fd823e5862cd7afb4f7e8b6b6a100000000000000000000000000000000119dc1be1bbb7e678319db73055ccb88ef7efcf6119f8a9c43c69247ff264879a627f653a10a950e0dbe69155ebca4f1000000000000000000000000000000000692a0ef5a75d42524e3fd52ae073b0f2ddf6378f18a5dcef05af4868a899b93c7f1d2691883e5c85f97052ef1f4177d09a2c3dbb4ee4f485dc60dfbd94a358a7c62204c021f2d7b140187ee9ffdc4ce00000000000000000000000000000000102c92272571b73a7df754728d7293fd8050d9dd2b8605c3f7722e6de541b7fc6a81b01c1cf15e5241ee4ee1f81ab39d000000000000000000000000000000000af1cd6f23bbd3e9ef75eed6d6d99a7cdd24574881b3609e45c4adbf82e08259d14701fcc5b6338ecf52166aecca003700000000000000000000000000000000026a1a4c3eb54de2ba4509dc806db9efc7e26247d501cb59c525b8dd15d03b91abafa9ba5816c22e1f8ca159cda34bd500000000000000000000000000000000170b510ec227fe8534a2cbb0f405756491c4f6832df552bd23980ab0946725371b3c24fa8b93a38bdcd47e1026e1d2a0d9b15c065497392e4b477a556ad620d44e671137cfd570d53590b7528f8ff680000000000000000000000000000000001423d1707e49d2215f639df75ee0e13bc724efc7d099259179260ba0f17157c4efc4276844bfdc46c61ac2185f64beca0000000000000000000000000000000019ad06d215d3c819311938f89609ea7cc63fadaa11bcc86cf5f26370a966eaed1aca312c18176674b5aaca3ed8ca876e0000000000000000000000000000000013bf3f13e87f3ce29f0524094e2ab8e39679566add32e779256006dc92ce09f60d5bb9cf0452b90ece71a5f6981d77f300000000000000000000000000000000112e4901efca14686c30a883ecdafdc389303f4cf46345e229885c76d900b0aa084a957076009ce22ee36d4e285d410c9e2a72eff2ec29a65b417767e7090b73c2fb530de6c8f4e4ba30543946423b12,0000000000000000000000000000000016d1fce53fc4cf40acb0347c0983dda46383e4828c16985459ac89a2ce8d3c2a26cd9acfaa2ec899cc63b4c6bc351f560000000000000000000000000000000019c9626363b511a79f297dc79c5a3b7a2e5127fe49a2fac5bc43a4376f170404f044f9f84b82cd09a306012fc81e3bdb00000000000000000000000000000000062e324f3d7c5bd39808b762a5b009cb30bec14a9591477959339bf2de9ef27eb42a0eddb95aa5fdca9bb9d89b278cc20000000000000000000000000000000000f05225a4d3bf910b0ac0103594a90684ffc0c09e2c21744032e30470d5727be3c27621dc2377e9845ad78be67b856a,293920, -000000000000000000000000000000000c64577b78ff306186dc44131cf8bd946a68e65502c4af9613154a7e3ffea4fe4e59cac4a500894b470a74e66db1834e000000000000000000000000000000000b4311c295bd30174f17b9ab3544f1496b9655302a4b6026a113b1aca31b555ce7b2d812bf8fafb6b075f67cdc59b77f0000000000000000000000000000000012d7dc3db10ae6b4e3e99c655aadb71124a0bdcfa6e30ec13c7c977d39f83aba4979a1f45881813a3a68e149a40120a90000000000000000000000000000000001b958c47cfecd619c05a2c54315585d15fe377beff889602ecba6ea3b29d51f6480f09a0a8490e4c754045f0bfdc3eb7b9aa7e0bfaf135ff24720773ccd1e0a46fab6d678d91a14de137061e145fb9d00000000000000000000000000000000010db66653f2ca73e55d85254f62e8b558584d546913e4b8f530968088d90cd5c4bc164fdb77325fe0bb2fb1a5683896000000000000000000000000000000000a1af9bf84f0b351c8e8c082a79c7ccae768bd2ed40eede38666aab1901a6eab0cff258f440c8c8eb4854a824b9aab4b000000000000000000000000000000000444fa654afb57f1b01d10be08a330f23667af58e3a40c630a2e01c1809125b3ff8f76e8a39418688a023293ff1a45e90000000000000000000000000000000002ebb117ea107a3598b888dcbd5918dd0ca307b21d65843c6c28b3efab89d0e276e5d92283bbb345b674d4a300595425c6733c9bb7bd195622b96c4181e8c8837d1912fbadf77d6029f7fc44d793b48000000000000000000000000000000000105818a11efaeab4801d4fa7558b09bd56294be7f58e3e54fab1f4f7c243ceaf08c99a14c3128ccfd866b6f93caf989800000000000000000000000000000000091ca092e5f83a04e872e621e71d54dd6886d088718f60ed5b62d3e4c165d3ff2cea1e50fcb94fff251df0f5ee453cfc000000000000000000000000000000000b42051a1ef52f87536b9bca459fa7070ca17bf825950b13b3bbe9db183ef722f62af6c05c174c65d59b722e1d2f5b0e0000000000000000000000000000000002fdb4a5480418e43aea28e5041af6ad55a6c226e1eea1a0449a99b5a937375285feecabea95c2652da5113dc17d8ef4410bb66334c677397d04f59eade9630463065cd7da3c9d50580c7d66bbaf487d000000000000000000000000000000000d8f93b589678d4e93bdf8da7834bc8edab648ead731b7f5f0cc299488f07262877ee9bb1174ccc106204dcd3f1f416800000000000000000000000000000000160f740ffca48d3a10c43e591cf46c129507f10e65d76a031fded2930d6c2dca4c79d7813f63e4ff71aee09d672173680000000000000000000000000000000013c768a4889315faa3976c8e43b4d466ea594bd94773f270a198f2571ba9662d10435d1e541a492055c333eece9bb29a0000000000000000000000000000000003dcbcc9e6a0cd5741d77da88fbbc269202e8f944a5df5dc4f9145758654934d5e1eedd596325080382458961ed3d21ed97a16fc5b2c70829b15f73615286eba334de1f520b5f0f6a83d2578399cc0b3000000000000000000000000000000001344fb37c1d7dcab01a4bf6fa50c6bb7606f7db22b85a3888ffcc2e9f229f196881cd7c82160730727e49b9e6fea04320000000000000000000000000000000010c7b15a6355d3152eaada7a606031f28809f278a1d0e04d264b563185ac7d9e351295191a6a90ffc9c6dd33995265db0000000000000000000000000000000014438086226a061a1bd557dac24d9333e14cdfa3a7bb00ded4a450e8889a3028b174bf38ae1347e6aad19ebc1cf5ff7800000000000000000000000000000000105165703c4592cc4f1f489d78426a56434dc77327c13221b582dc25306f4c5bfe596f3e47abcb741ab553fa14cad374bdbac08202bbe5df1229e99c76c1727f7789e0f8c2002f0a2c195bdfc00acb36000000000000000000000000000000000ad8b55a198a5e788bb54c32112761ccba9863cba16d95ec9e30181376e7eccaa2741660f2c5f708300be058e566ae27000000000000000000000000000000000b9bbca7db413964d2ec113cdee2d7a7bcdb712d285655f6b2897dcac61456ba4d08e25e8c28627231824829bd7d13800000000000000000000000000000000001ae49c10675256651e3e038a2150d85993fa6f2a97b9bc02c693ed97ad52af34015176258b3b2546b81010a4381d85c000000000000000000000000000000000c8f9668a0a497420acff5207a07cf780e0b2ba78083eb0ed8eef76beea996210541bae2e64d825000f307d54cbe3b2b43da827b812ec6ac23b00208cbad0f2e8b3a32434aa61dde029683c34c1ab1900000000000000000000000000000000012990a66c132a31d60d182f729b52d9b57d9d1eb1988b6f0b4d9fa2847f26983078ef9bbfd0e888e14bf7d1f92d09e54000000000000000000000000000000000585215ffc2673a197bf9cc6c6424547886abc6ef5c6edfeab2ef0c42034a4a458fc7155c35c84a8e9e9d89fbd4aa25c00000000000000000000000000000000118fb4fe0d3498dd2b55e62272e95a1203f9fd22314921d3e044f1b162376aaa7e8154a4e2184b66451aba98729330c0000000000000000000000000000000000364b9032ab9cd9f599979c8a93acbdb831707f1f84fdc92844b58bc7e4d72472ca5b09c51b1b04271ed9f0e391552463c7a8f7bf434ce5e63ac9365448da8663745f66689b4b04968f9b8b1b6805893000000000000000000000000000000000ddf9e4e302169e195f4f88fed06e0c93fd1b542abbfeea5da5d47c662ad9a16b8f4aed7874354fb9008d073920e1e7e000000000000000000000000000000000043fd1a4b781f25e8747ecb3eec45ce90760e0d5dd701e8193a7e726684ccb8ff21f0219ba15e9e777d339a3d87a1ee000000000000000000000000000000001117d2ca429048056084e4847c7169b4c8ddaefe1d48079949f9f14e4d74f0e0b38a95d0f17389f61da7b2a6d8cabd1c0000000000000000000000000000000007adfc7d87b1c533b4439f6b30d38c694b65b3b321f3eec13cd7d923f1d5f117005be6c3ea901a9479d79fc553e34e6c51f2e2bcfa6ebf84d3ad83c57257b9032e5d62a8663ed5d77afce00f33382bc600000000000000000000000000000000115a81aebee0329b174c01458f8714b13ea3fb2dbfb051b27b29b940652f27e01a84e522626d12be80da7e1039e2baf6000000000000000000000000000000000d9e37d2e5e7160db30acf5593d1c282541a0d4ac0482f0759fef8704b9ec3ab1e3ed248e37c6be285e890ef1a520d0b000000000000000000000000000000000c198a22c2f590df2902c8dc2bb1ee427b33e9687767666140f9d3b51d73fef18a259d43d86fb3559b1ab0abacf547a70000000000000000000000000000000017e705af54ab76145a79e747167a4fec6ec3a16f3ceef86b1ddd1be144e616ea7d686bbccbd1c5c258e4546405be023d6d8b15ec8908bfe008414757c0c7f79b3079f9db86d91ac3ec8f38ae2c94d48b0000000000000000000000000000000007c4c31287ae0b3bb90475f84abdda36610f887aae311d8e97bf97bbdbdfb11d38c7de331cc9dd022926678e5180c0770000000000000000000000000000000017f4afe28adc4b24d16b9cd97aacd171c2104b13b079c643d664a7c924151a401c352832c4967c0e5cecec5f1d1dae290000000000000000000000000000000005a8aa8a3a91461e0ba256e44af56875f8d07e24628e738ffc057249d8380417884f40c84e76dc6ce5816ffc05c0d686000000000000000000000000000000000f84bb7385a6936b519e881a708541570a31a9d7897ab8b348a350adb0d30522567fb917c9b6db661b6f53f98b5e68aaf4723e85076d48389c3fb5a5df16b6bc6f7a69ca701632b1159677bd8a6f7bb1000000000000000000000000000000000a8726ea352582ed52ab4e440102963891f059cf5a3f4901615733ad560a99930efd8692f3c30256d58e5cfc4f0803bf0000000000000000000000000000000016a623dfeae872639d99e3b8209748642f20af796325630596b6ab6146783bd4f2247c7ae38d53ba9a3fc0cdd6f29087000000000000000000000000000000000e40709656e569e4fe90eb85d8761c6ce44a4272793ccb7635ce75d67d97464e8dcd8c32bd4ac9a36fcce706006915b20000000000000000000000000000000019e64802756896206a9600f2d888f3d307ebf958492b1b5153c06a11231e2d7d2f1786368447f380093a29117cc25da9a632938a6df169fb64daa55d2f874ef7629d5be41dfa0d50827c082333f0fca00000000000000000000000000000000019c7409cda6084edc6e559da9b361c96cf207f1e2cd63cabc9b86c5bcb07a59b43e9c6ae3e90a61c872f168ab89cb2c9000000000000000000000000000000001101bb63a452b766a085fb788937f6b751417dd8d905ee50ab5bf96cdbb9d7b68c1735460a71eaf9e9bf662734f495c20000000000000000000000000000000014a103871fe523cd01053a992eb9884ce83c6023bd6a8c2cd9ca60b8780118c88502c6980904f2d2bf9ccc9fb597d535000000000000000000000000000000001929f25d52ee6b9a44333237c732a63ce2abc80c5510bd67faad1d7adac96eac5449823f3a52ed18bb90b93d9640d0d1283a4da7f71bde54d4b7e28b2b23e2eb05d8b025e77e15810625d71faca6d6e50000000000000000000000000000000015b0a46692f57ccd2b7f53040dd75f30af0196aa3c5499049eb172b4d927f96a59c42a129117d6162a1bb31d2e8734a4000000000000000000000000000000001366dde2d9070a2c057744fffe78effdc328b122e356a6aadb10c3fd2e8badc0ff70bc6d18293b3c52428e2ba78766600000000000000000000000000000000016fd48b067b949ed75bae3e4db29b5785bf672bd01032a925d653f8a605998e1eff6c77ec39dcfccd417f1e0a9defa820000000000000000000000000000000004cf22bd706dbb1cf8b97187ed97636380871402b3ba9de58f174bf50a7a0b528749762c3f55f5f835a276e43b46e669d402b71c1fc5c3f3a4ed9edc73457a27ea427f83a784796e01b7a1451b3305b0000000000000000000000000000000000ff424ae9372af46de34210bb0bd670eb173bd49076df5caca4bc4293e742121267a20506f931a4ae77cc36fcbc8df4d0000000000000000000000000000000015a6815b47966fb84aad5de62e6d4280f9135e129f33fd01e667f4d6e1bf7204317fa7741f3cff3682e251437927131c000000000000000000000000000000000639dca43483b79ba8043130e508e91fe3f43bc362fd1dbb135a2eb8f3b94d5cc4af70f1101c790545a0eaf2408706e1000000000000000000000000000000000045f0a04a642bb6e4db34fbffc8adb19a24648554f36ca371fb1a851384a4516a57f1850f7d6be59ff67029ec4002de310bc47acb3aba7eaa490ec104ed9b2985f65c7347f69fdc67b76f6f43846a99000000000000000000000000000000000e796fd500cb1a25b834baf7335641f34ccf04ccf60f82367f0e5c8c7fce8e3030e7b916752bac8e3adc01cbf4b319ac00000000000000000000000000000000142e8bbac9cae69ba3dca48aec045e0c4d7028f73c254433f921b7240761c661cf8e774a21da249f7758234cf7607fbe00000000000000000000000000000000045a3d80767d116e89bab0e9de812ffe7ffdbc41b61f5f17ad16be5bdc9968e34f46b937c5f94f8197e21b358f44b5240000000000000000000000000000000006978b93018bfdbaef0d40f1278e831a1fc50b44fff39b7c93820a284d90b699981b1f422f751a33094ae7b5cedbbb2691b88ce9888e5dcfef70d6f960a456dbabc792571f2a98746b7d833e7fab98500000000000000000000000000000000003c3561f5d255cf1f83cb5f4df8e3b8d5655d965826d56867ae66da631f8e7d489f733f5824c36652ab00586d9c593be0000000000000000000000000000000010b3adb0017e2cea1b71680ca33aee368429880759660dce2d3cdf57b6cd7339bd8853e5efafb9a5aec3f7e22da676c2000000000000000000000000000000000cdf976e4c65edb79ff15178f6ec5bf0a77a30d97b799e433f216a2fe3eedb10bc6ecbee2974167128773cff43f1922c000000000000000000000000000000001599b60ee70d927849764880830b2e7355daf95eefef39ef61569a2b83b2bcced4dfb28047a1e5350cc87ef3cd5cf1d93e82cc1261ac3864266379b4d518e25c05bc492a8946b38b8a64acf47aeec4b8,00000000000000000000000000000000123af49ac2981e14a490a4a6792f21343497b562266a47487cf6c650769c810852e357445bc68f3460e7822e8cd1e3f000000000000000000000000000000000143e79853e4bf6d031e1116dac2c6eca4991b8a1f822fac1d352d2cf1b88df239d82df886f0b24b3e7f305286cc1257e000000000000000000000000000000000b621056a9de2d83c946b1e2c7a07f9beb8e053202407323e412c0d8f60123cfd5818067f57034fe1b1b5a3d1bb285a50000000000000000000000000000000001642fdff2c52d58d38201cf44c31e00df47ea1439e9896b5ac5e9372482f4ffcc698be46c2d375d57a72fc677a9fc8f,293920, -0000000000000000000000000000000011f78204afa05c3717d3908dc7e4356ef96c426ef380b6abba3a5616d9ee01addeec3369967ed42e030c4b8ff9939c4e00000000000000000000000000000000175a19c86e7eff0f4e809a5105503ed223fe327ee4617f7f51257426fe408373899f39014821292a75e4cc4eb9f7f31e00000000000000000000000000000000052130dd3cd17840385db424802d869d7eac781365be25ba401b7b0e4025353c8dbf59e5997b5aac74c252192061299d000000000000000000000000000000000457f4fc7ac5d7d4fa07e8ed125df4c4e950e6ea926be9c04b6df3c3699a10e99af7ea8546b8ac70c3003468a75c821ca2a1148f1ba719b2da92c418fd137efe21a44dd4cce013ab36e57d97dfed92990000000000000000000000000000000005182a3af2b52102e09214d048a1a29d1e10b7ace6afbc4e6b1ebf16790be372dfb6d65cb8fe08c3dbbed8c5435a59a3000000000000000000000000000000000f2a1261463c09a88edac443ec3cea8aaa19659e8b7ec2e8a403dcffb1e50ebb3d07217a9ab057d8d097c075609c13900000000000000000000000000000000007d6525fea8fbb685fcf89bd772d48c406aff7377429fb199f27c3c3337f11f8e24c4d81c9026b469600d11e8cce51be0000000000000000000000000000000004b6d6102debaec16c34fecfeb444e7ba573b13b83ec375f14d2c541a0d1fa528ed6599a0eff4f8ca527c5baa579f762fec5d6167d7777169348cf81ad3eab5153f8f2f18fb5935c5ee5e3a759f9b5af000000000000000000000000000000000ba1f2b2c3f1c57afa0ab647d32af5d036ef18069a4abc9795dd9927ea274a718b67373230e337cb9374ef73b5e2303900000000000000000000000000000000016458ff2f5d600af9d2983f535c965a2a8aee48c0d95095bee642bb7bbab8bc87e3e7c3b52a787c53f0d1e00cb4ff32000000000000000000000000000000000d11324e812cd4fa65d20cf58f88a9bc9407657834d7a92f80bfd32c7085ffa2f9f78d7e18c1405a03de939bd0dbb06b00000000000000000000000000000000144a0f4d50bcb16942d22a12d28bf34d2b4c51512a3f11c130f1566aacbdb63ec3984df5569f41ef621f50d138883d0dda609e1c8fa42a993ff355a70d44dfeebc71a801daa36acd437daec5d7b645d10000000000000000000000000000000000d7b138fe445d7b7e130134db653022ebb389075ffb62ff9faa544cd0fdb9e78e313d0b1cb19bd812421d38d1e9996d00000000000000000000000000000000084411aa2719b729a1e299fe8a710f767009060f1b2becf2aaa92efdeea8c91239aa5d2504c6e7ad2e3f39d89ef00c1e00000000000000000000000000000000017e86dc0146c9bbfa5ea1e48f49918167dda13b31ba73311fd5cfdc12845b95b9e90972a9a4d36203be8c5920f8de5600000000000000000000000000000000150e4b6fa9cd9a609241d1de8a637c6ab25207bccf8e5eff4a97ed633b67826135172880b118037649407a3e1b1a0661bc5f7f5d096247ababa51852724ce9ddcc6acc7ab6180beaa1cda97dba94b4ea000000000000000000000000000000000ad7430b7778248d63a06e26119e5600ae97071fe8827b24440587e8bf6887b646f342741af69d20e243c9b45d7dcf24000000000000000000000000000000001230cba1a5a66197875240fe00c59b796ba1db5ea5653cc76bf43d6adce0db3a109168593beb39bb45688c1d124b9eb300000000000000000000000000000000144652474c58413cadf9b31715152052b7618e7093e931367a7ed0340e66d84c0471b6ec178e1730cf10e749e01815780000000000000000000000000000000009abdd0210f25d12146f2911a60035867f59cc341b35c73bbdf8f7a5a90d0bb6566c6ba0e868a3d62d3557436190f3f63222b41a59f9551e91572ae00582e1e41989ff5f8e2cd1ee1a78f55c2b28ecb40000000000000000000000000000000018ced3cd0c169693368fcf9c3dfc49fe248f0b9b5511e9407b8634d8ed7b54ae2dc4ac6ffde8b3dea70ca86ddc89449c0000000000000000000000000000000002f6b227e699dccf7ab1e0b1cba4cf9f05c4dcaf9fee6cd94bbb79f42bc9598fa23eb2c653a7654db73feb511b24829f0000000000000000000000000000000019785959766eb8b00ac2600d87240f2876e049725680f4504f59db6642ff8f82d4e1b856929643906c3be7807a2443180000000000000000000000000000000018285acdf25a475b37ee4da872debba4297fc8731eede6b22be3b0dff12117634de44b84a18042852ef419c3ae18a46b7431e5c1fe5f8d38c759bc48e8207695a3cdf07d4c1fd02f1009088539085da10000000000000000000000000000000019c7950b01e15669cc1f96fd94957535f32132ff6a5ae788f6f660024c332593942bd3e9603f862756edd4f3ab17b20b000000000000000000000000000000000bf3a6bbe10ad91d687a135f4863ba0332e9b04271d437a6a4770056e6b1ca34319dc895f9186482bbbc815aead03392000000000000000000000000000000000a3ef4d4f7a15da04a91ff079cc40040993a90e9ea21f53e31f7dede52dd513a97ece780374c5f3aa8c8b2e525ee31d10000000000000000000000000000000017749fc7761b06432632ac686d93484f08407504e58b04b3890cc2101f15d21f46ec0dc1e9028c8ef8df10f9ae929887d474e755f6ce9045baaed65c80f5a686547089e8cdf4ad2b7c2ce7c255cb5c730000000000000000000000000000000005a36af876edfdf26175c185c3ef005530e02474232ea659f5cf251c5de5721f1b44a25714967d283525632789331d2900000000000000000000000000000000130a6f5edf94736477143b1efc316f131b36d9658c484821be08e7f5b9c93f60cf34042858664db0ff0240addad8782f0000000000000000000000000000000004fedf49e6d49c074dcca96c01607da2105d8053861b4c677a69cff0f82e66a2a63f32f3d9fac8e6c844a1f77055bf31000000000000000000000000000000001528541de3a9d4a216c0e60c31d2b7c7cb91b839fc31307cc70f18e9b87b92bf5b9a9dc4eaacdec6e6bf7791e547d8a2976c8775b0eaa1e4aa384d222efc476305c7ea2d625cf5c67ea4368d7a9fccd10000000000000000000000000000000013faf7b2b8514f77021d8927a3b63bb7c57785e581f40ca82882341c13a9daf062a26b668844e58291366ea6ae2f179f0000000000000000000000000000000009060f9e1047f15f175fe95cb0914f4941bcaf071f24e856eae6f36263c812689a9217da277613c10c8e254a0933c80800000000000000000000000000000000154619e4ae3901789ed3ecdfc76069d8026a3e2cf142a144e8b58482233380690e378de6b81af0ed9b6536da1cc2a30b00000000000000000000000000000000040c1bce922503699e1fd5ac67725f11d7f9bb6903ff9204412f65355be69d73cd7330a3f7bfcacaa9b078ba6b9a9f839db274233c46caaa9c99690fd00fcbfa4eaaad7c41f8ae84313448c787165f6500000000000000000000000000000000103d91916d537379d6d8717b17ac5b7e9fedd98c24890b51c027cc086458259767d989b3ff9d6adad72bf977e4d378f400000000000000000000000000000000159c01ee371622378339518217dfc0570178aecc938b4a008dee1a6661ffa605c0f1472c107558ea791e0959d7dc1c70000000000000000000000000000000000ea3e10cbc3a55ef2dc7bde7a2e80666557e9e8fd9ce77e2e92c2c70777afe43c23072e263e1def56cae4b6d3772db96000000000000000000000000000000000cf1db638331c47f9080c04117ddab4ba79950563810d50e04af819f14ae0981f6e1e94a635fc90226c8d7beef0844354ac9f9ed46ae5aca33af9ba1c0fa5a2138d4ca02b962fd1d02b4636114ce19970000000000000000000000000000000000095c82c58182ae9a1ba14421c2966d687f7225ccd192b24097f997b471d13b46a048202712cb2d8b1be0ff40755dcc0000000000000000000000000000000017410aca05935a06942f673d1937a593423cbbd226f6707c5922306d28a60396baa08a941122dd4c583331c9481a734f0000000000000000000000000000000002c1d3a1262ce8aae42a6ed10d8020c31a468127e1a59d57d2d409ca9d14143d9fd21353b260edd8b387840580698846000000000000000000000000000000001512e29256b6b9f5a7ba4f79dde2c915b162e4881856258ac2050f02868842381518da4ed824243692b131710d7201f8ab300ee55e90ac046dbd772da788dacddf72c559d9378b39507987a9774301b0000000000000000000000000000000000ff83bf1d50fe35bb3d1bcb07b02d78a1b44d2e0c6bf82c600feec3897fae8b93c0ef05006c1322af0a732392dad86e8000000000000000000000000000000000d70c4957cb3615027cb950e4224d41849b9ff1b435ce936384fe17c4d7bc2883fdbba5123ca0c0c010651500557e1be0000000000000000000000000000000008b16fe9af45fa913aa7e5d01b5b58f946004eaaeeeb493759a5bc2b192d2dd71af24ecc5c6838b5e267ec2dfcf5c17d00000000000000000000000000000000038ca027c985af3cf60cda13e770fbe4919d3a5b413763c8ad155cb4903312822366eb986f2ec9e0804594ad4894e468275b22db781d5e8fd07f36788bc1219c4b4a13554c28d92a381adae111b265730000000000000000000000000000000003a313d6d41f1ebd6b98b2061a2d85943e52d89e4b8680611d41ea182385e154da24248faa1563e6ad79172f91a8763f00000000000000000000000000000000038d9388fe9169710e1a205ecfd03f674b47ba2275794469dbd5f193a55e00765c8eed026363b41afda417bdd8910ea60000000000000000000000000000000007a75f53d9b8e5eef19ef6f5fe8ce5d5308a1a7d02e0bf46f91a1e0cb22555752d82d8471c123269050fd8f35a272f600000000000000000000000000000000011f313127a036403652fb2f83c5122fd12c362ecba2251bd6c357a964dd758eb2a2c3053dc668b9a4bc071898d45cd46ec69b95dccdbf193d9ee4c51615c0b7be5ac6bed3f2559f0cb2755c634839ce7000000000000000000000000000000000a43335eb6ff3bf2daeeb1eaf44c2782eeb517e82e55203a247b7a396e26fdf85f93695753c52c68819b58c95f361820000000000000000000000000000000000c240b7896b3dd0c318dc9ffcaa001d20bff288def3ce42752d660fd705e1544e292a5a0aa3a9a80ae91cb47cb938989000000000000000000000000000000000e5195bcc4ee8b149a769322165b6a3157ee7d04546643390adc812b6296675dbd31168b268df869a6722a7c8f51c79d00000000000000000000000000000000004af7dc8a5c552f00d55b996d193a9571173ea829eba8fadfa7becc2f4149ee7c6c4d2c8c7b1970df33cc56e450657331e2bf1816a84c190eaa850ecfe1a9376123e0d0732d90ac3537668f8f18b9f70000000000000000000000000000000007860c3403607d4e13f738357e18bbcc4df10fad4aa25776f84d3c2758624a83aee0996146ba17a812384e1d67a7c54f0000000000000000000000000000000002169148d86b1f7a0ef75d9bd19b6d7cd66da4293fcf33fed9241544dc2564d980161a6bd959f3b43569312bff7a23cb0000000000000000000000000000000001897c121cbf5e82424cc50078ca7143a0c670f1217a9180cd2a4700e06aa895cf84c0af94b7c04bfce047a7d1f8443100000000000000000000000000000000040c1a0c4257f90bd83fece3c9372842a148132d2dffa956729e741ce996d229aacb04387d51a72630329230020b2235f4087feda4bd8205d96cd0bf6eee44c27a6669d7ae8e16c731849cfbb2324e1e00000000000000000000000000000000058c001ee1343c6cde55bbdc4c538f5d14b0e8c199fb822f080ad96ee764bd1908f92260ce60cd521919f223301ba1220000000000000000000000000000000000f8943c35e7fb8b58963719f1b9820153e0831cf81dd208176af7527781ceabcf6ed2e2276cbf374e0525952bace0c80000000000000000000000000000000003b43ea8c32a13c014b05326f7b4ad5b5fc1bb2367866a69373ba91402f4b45409c6d034898e8b0ec3b93c2878d59b72000000000000000000000000000000000101c371ab4d57ed2cf17dcb731117b1986bffc586529fc1edc630de1c6f4fdff1e10b0895907bb81d2ccd3eaa96c04a67b81583fcdc9afe5f35974dc9b6310ee8e1c92031a49c08b05555fc0d33517f,0000000000000000000000000000000007152d538d0f750901466c1ea34a16e7b0e1296a2a3740568812587affa5c0c76ca2055804e24f3415a403f06a717c0e00000000000000000000000000000000119c0c282d22a01524d87eb850789c4816e7dafdb2782b57c32409b1016615beeee2067443835466283543773cc8b427000000000000000000000000000000000d68137c3df081a519747c044950c3231ef82295eea5b7040843668195d4549c8ece4a91447e0ec89530bc51277535fb0000000000000000000000000000000000d81a4fa2d32ada3e08a7bd4471d45a6afd2cfad5bbfa3d378b1df2e0749f9b05b465be61cc9d1a0f4abd56dce03dbc,293920, -0000000000000000000000000000000004b153d6554c9879359052717457179d8318f8127eb73edc1d6ac2efb1ea9643c4357cc813d1df49730b77f995d6d449000000000000000000000000000000001533a450737b4bf8edada15446cf21ebf82aa7bec7943025dccc4784e1070fbce430699cf3a37a36a3ece692ce87639c0000000000000000000000000000000017b8afc300bd70a3221120f6fbc37a8e6158c48b476f00992c6f41808036765071bd0a76f7c641443b97ba523153947e0000000000000000000000000000000012f686b4759a3d5db2325508f148bfd6217e027fe261d3ed7b8fd0526036044dfb563e1c4399ec266e140ca372120e289f3c65c2c25c6c37aa45b1104745cb8ec511a165ffdb7e304f5478aa3add4d7e00000000000000000000000000000000061c8288b7bf2856c075a176d1086fc49f0359ca3e7c1aaf5f151e6462916a4e1b69b6decb18823759b620f7593079c0000000000000000000000000000000000877af18cfa0d029e7c9a5833b346c7fdec06e54d9641d3953d3cae0e8912bac7c990f8864c2adb6e576442c634865b6000000000000000000000000000000000331490f42993de3ce7cdd53afb4b310f25881710a23f601ed95961bad4e9cbf57d3077803908a91b65fc32d1a84130c0000000000000000000000000000000005aec079da804fc572bb8eb867acb93a24ffb6611eba920d2ce799c4c80cd8e73b3cefe989885167ab685365529b4f2a8fd50c46bade91a13d6dc5a06ee62e5e89e0ae7ee885e5516ca6c2dacc36f6f3000000000000000000000000000000000ecc7abf4f6f9692cf3a118cd01abaab4754c90d1a59468d402bd699992800c2994f47b2094878604bf7825f125133e4000000000000000000000000000000000662396427cc596458e880bb8a43dbe046deb85601e3c64556990de36e8637e8ea3b142a8195762910a83609ca311c3a00000000000000000000000000000000198b9c52be68d073910f5b26bdeada9e9b308e4541561a8ffb21fd8e69ec9d93b01ec966fba65be27ee53d4857a43e120000000000000000000000000000000005201975985cac810248e333ca714cbcac0ede46bb915d8c857837c80c805898d0f9ca0940819878a26d269faa02cb86128db1a106328916ca5d63c0b5722642febed26f00a89430d52ec3eae25a019b0000000000000000000000000000000004e61ad4818ea3c98ed3c0d247798a1a6ca6bcb35a1cb60bda9394272ec092c385661ab93a42af439f1b55ee8b9c0cd4000000000000000000000000000000000ee0b71ebf39e4009bdb310fe3b555cdc860abd47a67bf481ab36b5ed0c00bdca8082abfb75691d45e10c2f2d777be19000000000000000000000000000000000e9582e3b5bd580f3ca7ca1f58e39379918f2d04b82b418a91e133117a9703f7df4aad30d48a47e29aeaccf5b8e33559000000000000000000000000000000000113b4c068fd040cd3300a2d1ef658955b014e571e7c77594edd31968037c1fff241da88e7a88669a569462564e28cd7d45665afb6a864893e389511a0f7b2df74c9e45a86fb69f6bd79854e3a88c206000000000000000000000000000000000d8b0bf633072f19db61ea263a1dbeddb326738396caf1196e31e2cbe99a68e8c70f8db13cfdfa4fc4494e54c1ef28210000000000000000000000000000000009ceaf2a0c63604afb8a903195933fd1ada0e5314255be3d74a95679c7a7845785e22d2c0c206f3afd62110ac9810c2e0000000000000000000000000000000003a135b405f46ae3f5cbdd63f4964cdc5014c9f3405c2062ba17423dcd22b8f2011638d520ce0ec7bb0cb5b03e8ec01700000000000000000000000000000000066eafafe1cea67aa6de267c767f49d4a3fd44c28d45a920fe9b3cebdeded883d8960f5e9fa4cc179246918942b1428d28f5fd09c2c1819adf8e6d0e0f4e4681babff46757edeff3839e9691888c13220000000000000000000000000000000017e37a2f1c892fdc58ac3f72cc5a5e2b7c0c87333afb06de89f7a84b1267695bcd452925fb2f15f8b7b20aaa85a6b5650000000000000000000000000000000015b2919343962337a41b54076d6735a093190e1965faa33eee800f5eaa43c35f349aaf93f19977b6fcd19360b27edd6d00000000000000000000000000000000161afb1494482f953007038557c847e2cbf84c57c5f5b806e3b0178e71e3238305f733943bea7ad6f2bc290778638e6a000000000000000000000000000000000c27a2170fa584863697292e626e2539aa15f3c8eee65cbf1f1b7ced6297248d059fdaeb9c955437a51cb016d1ee97c3e6e61390ef88f20591570ec1fe71c3ed769ee8e234c7cc7303a4cdc065717736000000000000000000000000000000000313a30edffaf864d0f1c6bdafd7d1e563cef434d45e71489e9f9e4cc6700e44991a99220f53f0cf5e7de5f6e4098bf20000000000000000000000000000000010429081ebd2ed6fa07de6ab0b7bd559a26a43df99fca0a2252411b4554dc69821ccf3df1b05114da84a616ccee0a9c800000000000000000000000000000000131a31442f80da4621f7691664e9f8b467988fa039bd086a2d64f9810878b557614c27745b2e821016f648ec36ee797d000000000000000000000000000000001160cef9f5e4d022baeacdf10b3bf9d7ed5e50627a99e29df1be3667cb872b2af333f803bf426314369b490c2eca642aa83c5af2f9d10c06552ea7d1749cbfa7574b238433c1c0e4788efd0cafeffa57000000000000000000000000000000000b20ec53bc643bdfda1e3947b3773d748cce1992e2ae27c6b7460d90d48e08eb9240879a5a7d3dc3189f486706438dbe000000000000000000000000000000001024bae4a7f71d3d2fb8246e82d95664c4ee8bca4a380c293ea084f749911f984aa4c6f266ecfff69c4f57e20c0660ca000000000000000000000000000000000b58472d81a9f16d2fe7af87170ca0c8c51dada62a4b2a713cae053a0066fd268283a785ccf269e05d8873cd686d2f4a0000000000000000000000000000000016b68177bce92fedfbd90cdb752bc332f46fde6673911c016fb9cff4912d79d3267bf629c33097cf8979f2b913c0936d4bcc88d85a5a8a29dfad37ba97ab3a5defde4ec356146db8d10f33bfb36ddd37000000000000000000000000000000001030d5791bd2a27469d242c62403883ca167303d907839e608acd827b4118b752840a4eca0acbe5df0b447d6651e517800000000000000000000000000000000106d65f922581237f779ba3e66608729dfddb2c487bc927f34e5e39707f2c8a82e8c96af68e3257c7a9876a05a1b01d800000000000000000000000000000000115bec40b8fa914305b1d5a85b65f0811517d36839494ba69c929fc2422f7e8b85d78df4e1687ab0087287eff29c65430000000000000000000000000000000018d78a75ec057cfcf179fa2ffa7dba79cabf6525dabd69ab95b23dc8f293aa077e46e562caf447dd0913ac9dd60ec76b29d5d818e62c9791c320e01a3164e142d9804e9caa7f96b4c3b76baff38ee2e600000000000000000000000000000000023f7736d6de94b08d9e9efb6f32f8c17cafb1e1b9b1f3db6e58df72a451c3225d11f4304eb0d702b07a7966f95a11fb0000000000000000000000000000000007b3204f258c873a6fcb48d0b36c98ed5f99b424cf4f92a028174e0e93db2af549648ea95fa8c7bbb42b2a10eeceaf8500000000000000000000000000000000165d6e769b7df91374dfb44e18d43e03ae12ee10d8a618a20f67332cf96492ff514eb7de06ea53096e823770c686c32700000000000000000000000000000000012e69ca1e106411165c06ca15988362de583c4a05425e2f4aba4c14cef6d8d04c52c87b4fb26b1557801f55b02ee8ba971c8aad41e401ab6c49dccba28ef26acf4961978e94e633b72c581ac03621e4000000000000000000000000000000000e8e6bf1c8837c31446959242285e9b85978a5349e1f0b3447e380a7bcd6bce758bc6192cb880f9c09d6ad4a0ee36eea00000000000000000000000000000000199b361ad0b435d7a66b46a43d06e5898376a6c260b68c965f7b186fc75d2f321bf883646e7551eaba03181907d3aad1000000000000000000000000000000000a76e3f399f31cddc4dd4bc22187a68fba31fe2371291ab515d22730d320ae4240911c755750f687c7d26aed09da4210000000000000000000000000000000000cbc8dbc004b9253ba91b2238c92bbf7883360c7ce39f6e15592a8668654950a3fc5a94cfa97f5ddc60add40c32a3630659ff910eea5280dc5c24c542516053637a5dbea576a94a22acefc902e56568e0000000000000000000000000000000005448b623604262a9cf1a9a292c36738960e132bcf0ec8e61a510008c2ae0b51b31da25f2bcf0d7c0d4ce15b1d7179fc000000000000000000000000000000000b61df56ef891bac07a873571f68fe43f79438a31038cf8ff97393ba44cc47408e5a6d64e9ebddf0195bf914f141e668000000000000000000000000000000000d196ced22ddf11132bbebf6c85bb3006a194cebca975d74992ecfcbac546f0f25a39ed5d6100768c1f1a791f3604d12000000000000000000000000000000000f727cb947849d2d7b046218f084283e5513e8582229085f9f98fca522879543429cb8ab435aa3dbf01b68ed258d82c112ff32d44eb442a711250875d86a401d0dccc95e5ee39bec71738fd812d487c6000000000000000000000000000000001044bcb16b3384a1f350cbd62bae568c96932a364c16b34d91ab9b1035ddee93a02920ab4dbde2c6f254031909dc3a450000000000000000000000000000000004a29aae48210289e5f588aede0756ddf60724b8ac54de5d9159ea834d5da98b7a9d09a6f37bcaeaabc559dbdde58b6800000000000000000000000000000000112ca953b5ba652c715fd20e3b85c5bdfeaa7d577aa49aa4656d142c9c2afa3d8aee151338f59a199f3c0c3f6a430d6a0000000000000000000000000000000001ebc7a17da7809f9e744cf7f13fc437de34d3472f022493f58bb979e2282368f989ca0982098a7c377498f1d8d32583666b820fae2459b98f9bff20275a3c96ddcaf98a78f3f5fa4a2c0a26cea79352000000000000000000000000000000000e7c3d6bef4b1723479ab6724cf7858c221993357b194e5055db96b8168f8d78f72aaa4a2046be17ae9a7eb00695ec510000000000000000000000000000000015e85e85cec08133b86738e1f7a738de455930ffd5073997a1f1692c28044ac00b634b90eb24938cab56e286ca0dfaa400000000000000000000000000000000164646a4767ed69f9280f96be9a7f988d17c187162554239797436a0bf4c4ffa7e4f8387c3d2406a7528c021f56081df00000000000000000000000000000000197b1080bf3ac3ef7bd6123a55f20f1002f366d4efea9e14ed92fd2ef147e2b5d9251a302a85172235438bb2d35943a740a9181633a146d7f307ca7606cd45b8e721c46b955a6989d421baafd8e40139000000000000000000000000000000000db7cfdfd58a6ce9dbbcd5d65cbf22b5e1a81acc70f1c85651ba962d61fbd7ad83e5524fb9aa019c6bd75dade96f7d4e0000000000000000000000000000000011e269a390fd15ab1d52d38de78ec97eb6202604fab02c4598ecebc7635ac91ee564e751275a485fb43b933678f11fd8000000000000000000000000000000000b8597a00d2401664405dd1fc7d69786353c86cd4699af981fe869f266f9087b00df22a46ac34883173bead870798f650000000000000000000000000000000009117a49b3f2a8a850a0179b558319bdd19a5f1f4a45af0ccad0890e63b222d028536e9bb612093cb3f1068d262af90d662ac80797c633d8b9c8907acc2960ebdcb5bdad82d9fceb4411d5173b7411fd0000000000000000000000000000000002e1311abb9df5e4d76959276b6f725f13728844f8c7dfe5e25469cb95c6937a822282b3baa38817e24a6219601132bb0000000000000000000000000000000012820e6ddc50e19a8f98c15013ecc38901a4ef8ec2b79b85c7f7913da24404afa1c79045f1318cdf271028126f9420a5000000000000000000000000000000001794e653c5673e51a3ace631c1a1265dba07fb74235506b2149d42b90eb16afc26ec0ddc54d03f7ba2dd6a2503971fee00000000000000000000000000000000112479bdabb9dd057b325563c666910c01ef66adf47aa32f5a41bc9cb8234750985c266fcc329ea3704e2b8d9b15bfeb59401af15d9b83e2ad68cc8e2ad1508391516ba0b26fcc5ec6eda8b318a374b6,00000000000000000000000000000000168c90045dcccef35cfe8eb642924ec2629db886366fd9ebc082019690d103627865f0dc39ffdd2167111f68d8d03c89000000000000000000000000000000000b6f0928a32672983664ad15252b3f145afaa04f11d5f43a6169a2fbdc0b0a04902a183b25e38987c45579ac6d11011f00000000000000000000000000000000195c4d796989630f85df4594eb8353d44bcee76d82b73ff7a57069466337b49b875b3c1418d22d79716ffded7e704a6c00000000000000000000000000000000032db644ff8ca6a3b1ac7bc51ff783ce0cdb7bee8b2c21dcfd3adb56a3e210390756211f22feb3dd4f706e13e5cc163a,293920, -000000000000000000000000000000000c675cb5e90e45300619be91c752a5831ec47b4143c28330422cb57139882e776c1c5f000d6032cd87c16ab3b1c08ee0000000000000000000000000000000000aeb4e78724d46a55e5f101564bc05e0a1be1d80a781ce8a19607322e82c7ee59db9f53ec34c70bef0766a5b965f54b1000000000000000000000000000000000933e8d7c2420cc553afb1c88b5f64c7a39f78272b34b5611972dd5ced3f639ae2ed2aaba470abe829be6ca6d666ddaf000000000000000000000000000000000ac0a9b46323ccabf4b2024e3a5b4717cd8de9ed7de8a78e33a38037f802651a4b43380a746890d93289d547d94b61bb9c351c585d1920b8cfb89a5bcd72fe041b17f7bd091ba505b287778b0be4e87c000000000000000000000000000000000196597114fbaefb8108c185a85d0fff0f6bffecf056902b22d61cc70b49a747bb35638f5b663830a8d2ee15df9fd5a1000000000000000000000000000000000616ed44a5fe69da994e2ada03a1e09065964223333229f5f30ba7a452830848f599ee21810a95e3659cca495897bb710000000000000000000000000000000012d0631e524ee9d3c776c79137499f8c9fb752ca93e92497d89973033d60971da23f672f140c1a753b4d00d08a00babc00000000000000000000000000000000111159e95d131c8cbe8df75853fe9b3f24013daa083e57c5b716e77f6fd3872dcfe0156382c9d2778fe886621be19973ec42da11e95cebbeed0ebaecd31be24801fdec8b81f4046fea52f553c4e7910b000000000000000000000000000000000a7d253487591fbed97381b3a430404b87aac04073e5931ee0bfd9ea6e0d38a41090c6dc7f6a591336fc58a97a3bea8d000000000000000000000000000000000647c67f1816ae6fec39033c3169eb1ea89e5e20e755cfdea33572d6397e7e87635c7439eda4912361a32de313893206000000000000000000000000000000000e0cbd54634d070aa3c7a503df1171a5cc435d050def17395707bdf7a61cfd539348ee5a4c29c7845cbf0e5df0531f530000000000000000000000000000000007d006601dc1e092a616eb470be35b5d32742dc6a2a4d71cda8865f071dbba9d8a3a8cb10b486253b1633e4590e716dddfdd8996780460757702e34ad98f5f64a8c1e0bc8851d6c97f02749b8f77cd03000000000000000000000000000000000c502a19770a892b2fa1ba59900a36c0ed054a8bfa0c4e32bf471b90d0da9edca6c06b133c8f12e233b104262a81dbe00000000000000000000000000000000011801f159086d07833a691182595a42645513d316c084b2841445c4a63c6bbb402664a9a9a100e8d6436337ecbf398bb000000000000000000000000000000000f2b9bfd8ef6286bc41e9f47ffdd3efe437aad553c9da02b3c22ca04b5578d634c0543a07bea966bedf345562218c2190000000000000000000000000000000010be5ffe0cc9f580c74e027aad09c213189fa4b7aa92160ce813b8d398b2e2803294e1a730cf5c891cf1546c6bc91414f256ff23b38b3b986a62074c5a3e05e86ead9431fcdeb67512f6d502fcefe3c300000000000000000000000000000000132cd5220c125759a18c31313592eda774247f97b5134111b01ef28dad5c3ba4d3f13d1af9076d663f7e217258a6fdaf000000000000000000000000000000000f06a5b03daaf8f92f9a302f06413044ca0dcf2be81d9cf016120312fbd41b273650fbb542d419595fd2815a809c4b960000000000000000000000000000000001b11acf12cf46e40554a1d6a833566cec1b2750f3f72ef77496477831d5933f477d59463ba19c03dfbbbd02fcbb680b000000000000000000000000000000000b2aaf91827ba923c8a1c2fa1d6fb92384c9f48f8f77273056b94245114d1f3cf66fdcab330673ceb2e9dad6c1aed0d4c01b3c8bb0acb17198bde9adce3b0f7ed4cd8615f837aee928524b0984c99d0e00000000000000000000000000000000051858339be99d1271152bb390e9a2ec0c0760b7686804ba072c46db3cfc4472404a9f87d868a28f2aef16c9e989d6e90000000000000000000000000000000019a33f21d0bb8303f540bf26816f145360bd1e9a8229dbbe7981f1cb5b099e814f2691fadbeeed8e4c4b772bbd27e60600000000000000000000000000000000073eeb49aa7e601732dc0888ae6b0f5e8dde3d97b818155221f5ab8c599eda75b25c86f15ceecafdfb9ee4abe3419e10000000000000000000000000000000001507073b97d494de26e70f18bd1723d931cd2a88903ab6da2aac3b80fea78ce75caaa9b99375780d759fc4a1683950bd458f882b63c99ada33d8215111a6df21c8f7424eb2fe9f429256201d099413c10000000000000000000000000000000013b5422deb0e80bec71309d03fdba007eed33c3ce0fc6d4f9a0d063136b3b85a6fce90ee59956a9b91e1caa519f813e8000000000000000000000000000000000829a11eb50f3bb1a47b72cfeec9d1f63e02b9f7b2592174c481ea7b72a121645ecb36b3d1964b082bc6c7efb4483a180000000000000000000000000000000003d3aab53814f55fa97285af2dc6d32cfcf5a08032d2c15ac83ea036603e08a53e0d2b8d93a25dd969937c113e78064a000000000000000000000000000000000c938a68688138149cda64f168ac1466c401196eaaa44a464d9e345c422948767ad1e25d1ce4cc5996ac5d5dab61516b804d7a35e5731b111a6904e0998d90ce86cf612914152fe3d2fca0201a41166a0000000000000000000000000000000001ab96f0b60213855fe221fdbe2fb22da6bd6cad8bab8ecb747c9528d3511976236ffefb34afc462abfde13a99503cb900000000000000000000000000000000182fb121778cb002be3f90e2d6837a406edbb609bfae8fe59837aea6f5f6131a10791f92188958b57059b7b9a9d3a24500000000000000000000000000000000159cac269098d223ee6d145a4489f05875b6a546767c023dbea62b3cfba9f8518c9f4d2594d00ceac325f3d8ef551369000000000000000000000000000000000c0d2e4e7aaedec7e53bfebe8f7fe5115720e58768469b6673cee3473b08fb8cd1ebd0514689ef65d78d008889e3ed296f1629a801db6bb4066588ed79f75223120728c3a57f7129d88f7f877149223300000000000000000000000000000000079c40bd7fd2ce0f48806dd2e88850ba988e5adb0cc5120977da8110b07da264318fa034c0c213590a2616f0ebe40f21000000000000000000000000000000000905f41389be39361fbfe7641394d30870a079f230dacef89149fdcf81a4d1e0e10b9fa1c0c3ecadced9aaa19fa9dcc800000000000000000000000000000000192f50e08e497f902403df40a504a1b4b82f1957572a9ab7ef97f5ab93c6fb876d8b08f318244cba95ad5200fc2a6e34000000000000000000000000000000000be7ef45a14871dbb344a69c4036af4f994a22ef14540377d1144a92978a23c2d678cca47cbc18e8c036714112d11f7cfe80ddbcaeb784e24975b9a42801c89bdfb842cbde5fbc0c3d70c0632cfcdab80000000000000000000000000000000018d7410f0105ff03cc4ddd87a6e0b65ede4abd4609db5ae53720851c90255757e63c6482de4651eb1d3669b1e1a2f8d9000000000000000000000000000000000d4223be106693a672da890b64d2653135119983639f7052eb32051c34113022080ce2355a93a2f64a75d8e0578b2f95000000000000000000000000000000000764780391249d0c987270bd181a44f6260ef82eb00c06585db7ef09e8b069e46c4e0e659a081ab0fda491534b71b0ba000000000000000000000000000000000a8546031e6466ae43643462b7617703a63841d6d4cb0c09ce63b2fbe2c2ba7cc35367191d0313717b1daa665bcb54551aeff13de7bcc4bc2ac1b37e28ce466805757dda29c9c743eaea9da33f47f4fd000000000000000000000000000000001922491dee4e0f29a1dc090c9b48fe8e6d70c3441e532021985932005b22cedaeea7d9ce1796808d756b740ec63f8ca80000000000000000000000000000000005b34dae0e630be6a59ccae17b44eab4e7f10be2ee700bea15f9771a724f0979798617e129540901a8aa023630a446f800000000000000000000000000000000095bdf612289258b31cc79188566ceeef6fd66858b4dc060864d378cbbb69f951e9c6bfb3d1384014507ff29f9446f410000000000000000000000000000000019f06f11a833c06c1c9227255e3a1d74172e73b06675c547844065dbb909ad66bbc150ba396fa1ba22b7183c0fe80e96c4984739882bd2f882e12660815b96d2af7812d7ae87f5be034b88e9e04fa2890000000000000000000000000000000003de8082f828ec51e23c864a16147546ff60b5fa71897ff4c120556af5c6616bde96b6e53fa673cd1f8af503070bfacd00000000000000000000000000000000093013f75b6a19b5433b3b5ff044384ddfa258420c80fe81e0424e3102cbf9e550a946e56ba9746423ef745e33da51e7000000000000000000000000000000001227cfc3e9a8d6a71738c514c05766ed4f1f4605198f5a3ad8309c0a49499e4ecd34ba1ba7677d6d90203e54d7611807000000000000000000000000000000000a635221d514e58170ef299eb7f5b679050ee24c589cc7e348b2905a3cd1b7bcf2010cfe168f5aa60f4bfe15e59b4436e7f33141d383a1a927b7645656ff7a5795901a997e27003c5672ae4fbab4aecf0000000000000000000000000000000012ff0494d308d3e7321ad4c4000e9dcd19552d5e4bce8504760f066e2fb2509279b01f1568e3c3f6216bd5328cbf72db000000000000000000000000000000000038c6e8f0fab30b5c8e4323c1fd29527845c29e1a26c70b8e5284f7ca55fb55ad4ad5389b5280927b98907132f26b76000000000000000000000000000000000aef946b9b9e9fcabb36507c1cf441df2f5ccd71ef9281dafa5e25bf07d69556e4143ab402dfb38aa756bb6ee009a6890000000000000000000000000000000015f69bc7b0a6f2cb64fd0897b421e339fcc8637efced8bf33f5aed809a38b49a2e6376d18b1bff0ef70df1b7187ad048fba4674313a9727aa4b733832a0e06666d3e38184836edf786317de9dd055cbf0000000000000000000000000000000009e8450887137cf45b04184b3c6fedac6676cad416a7646e9980dc99a6d6b62164dbdfae7cc20edaacb84432627e6e550000000000000000000000000000000002acbd87ddca9dc775da01ae026f1c60f1cb5974ce40caef80cb0d2eb7839777c1f61eae0472c7568ec9d0ebb2ec7dd20000000000000000000000000000000017c295c458a9dd995d848e3ba585f8dcdec4185a953e4b8e3ca760eb3e815e39a8ff60416e1e6f974cf7e7b086ee4baf0000000000000000000000000000000003cd8725e1cadfbd80585bf5a19e086abd631d6787403edb4bbc785d1a81f6108f451ff642f4df17dfcf94dd6107352bdc0c4d0e34d8a16b3bfb51ffc9b3c353817e8e357c608b5075c173204963606e000000000000000000000000000000000b3cc99db523b3647937b694fc23281a74010079351b2c7d1ae4cc9167917f06c06e627c4ec44af6b09f2886ddf309b800000000000000000000000000000000001e2681dd123994627adc92e6ddd3ffb006521d8bb03040fe1989e4f709e4797d143cd0bb749de33c8109933c709e970000000000000000000000000000000017df13f532bc9894be932e72c609c0386d32390dee95dda45821bedbc1067043d46007b39b6ade871bd36d39a17dd04d00000000000000000000000000000000162db4d1e956fa5b5f9ef244dbc0c6d27718eca7dcc512d1d7b97bbfd2bd00cce7941d1b9a170da6341891773a729e9ae4e31f5b6629463311b9d3c8333c33c5b2e79761ffff9863acd9d636e1a9586a000000000000000000000000000000000f0e4b606ba0a175bf57d4478aa286640ce4b5507f9f9e354fd96c45443333f6889a93012d663d78956bbfa7c645bb9d000000000000000000000000000000000d85dc4d733f0498fcb10e1e814eb61245203d6c1a46181e5a388fda2680640a1271a68d645f8fb179c0dc3107fb788500000000000000000000000000000000185b02140f6314cb62bd7977042ffaaec41ba8788d356047488004d609ae680c2f0cdc94e59a3cf90b6651298b6a81d000000000000000000000000000000000038ce717d08d367a9f882f2241ae4cc0e8a31418498bf68d05805db2e162d053a10dcff85403dc473598089a78dec27e03f256e58f60307ac1888a1b0b14b56c7435213e271eecc79b4a6f88d102be4c,0000000000000000000000000000000004cb919a72e67c31b3409c28dca1d57833a5066c240d2889f5bbdd5540ab2a49484c2462b25da197ec8d93dc8f26ea83000000000000000000000000000000000e1ac1dfcfe22ed7ac52c701a7221b542ce72bf59d62cc49f95f8ba64c05060671098d40c83947dd1952494833a19b55000000000000000000000000000000001331f6ed8ea5ec9b9e1a14997c2c9bc9af2ca305b313e2bc5c5bd35308b7b451a362f8ad61d636dbf77d1b2388702d8f00000000000000000000000000000000186b85e656e45cb5ac9a2a2009353e45749b53dcdcdad4f378431a0e4a317652301f834617e14dfac9836c3c11512aca,293920, -000000000000000000000000000000000e6292b4d3031fcdeabe62921f0c562606b1ec6139b9c43938971d7851da4945cf69f39652425396ed1b2e70e65b9f55000000000000000000000000000000000e94bc63f3b8944ea6bd7bab811c013fd61303aa7713619faab85a271308bb220e2a94b26f5c7e4136a3d2761dffea610000000000000000000000000000000012313ef65ba41f8e0a57e9b810c13d23241e8430c6ab967a1a9bf5bd6308e89c135e00e789a5610694d146840fbd877300000000000000000000000000000000165ce83af7edc9e701eb57b332597305fedf4b939f3a13a95a0bb3d119c2a9204a4991388f7fb344ec8f15d32cab0eb5eb850f01feb55bb99e4accee0aea8fe6ed0bd29b2ca942ffe09456733aff10ea0000000000000000000000000000000005a88477765bbc8290b7eb137e6de78e62bbd929ca511cf0aa701f926440f21d33bcc6ac8f2ca5de57ee8116c685ba38000000000000000000000000000000000738074a9365c707190f882780b27dbe96179224103392b86c628b601e33b092a03e24a89bb6d1d1024862a9df6fce8d00000000000000000000000000000000188c713945046771bf852155ba412b4222173b6dec8320ffd1c59e9b36943c2c18b0dd3bd551b7b1367dde3e8031201a0000000000000000000000000000000017222294bacd664ec37e9b214407e5325eebe9753b430589de2eea13360783be52a479e2b0e9c5dc4907dd5f06a7fa822b373fd7e5806d227ca1f49ab0a7f8be2b5950906c8974de9f2cb4e13ed20a9a000000000000000000000000000000000c97299d7e18f41e538b91b75e962c3ce4e068202271b40469c58cfc477d7820e90a0e91d647e8ef5fc0cb822daefd29000000000000000000000000000000000bd1e11a3646c499a240bad708f97a49acaeb653aa5bafdcaba41c1c9d32d32c516c94a3db8816e0a43d1b1eceac7243000000000000000000000000000000001223ecf82c4622653ce84460c39afe8a967cbd87a2d75cbee1609161837c15b522480c4731c9e6de9c5c392ef1db18e10000000000000000000000000000000016c5e98d3d17c723548427868e3e6d7ef4bca339e41acef19e0710459bd4732de4a556b22cbb49b823c4ee656fa354f1babde7f3fdf9fba868b5eac61337be0d73517ac3f06c39b4eaceeb27ab6311db000000000000000000000000000000001125735092842142cb5387f1ef8fb69a3535e1f0ccce59661183d3104ec1ef79dd87a7fb36159bc67bd73ad403b46c1500000000000000000000000000000000162caf579539574199d56f4e756f1532c66278a55b4f67f4f4090368260f46023543a8a18d49e8c5783cb65f93d750480000000000000000000000000000000003accbc87996a220a625e36d5cdf05d8c16fb353068ad819f94ba8223cdf6436f8d822719153bdba620a07c5dd955fe5000000000000000000000000000000000b53c8a4b62466c998327e0c5ad65818ea383650bf0977d98a8a94fa9653fba276f7781af9f5a4e99052ee3ae65c283d5ba1635cf82b25b2d7e466717f5716c33f5f3e826bdedf19dbc1d95ff0c8052e000000000000000000000000000000001264608a59c0ee9a26568cdcea8801cc8cf6616773bcb0971234b2d987563270c7b2291fa035c8f2069ac99e16c68fc0000000000000000000000000000000000e839d8d982d6663ca4552527f4fcab6ad5e0a444e7b5921055c774871601d342a151133ae15bc76c023b7ea643182ba0000000000000000000000000000000012ffd0696b7e29b305412fb840c596b66b77ac2eed936fdbe0562541e4de6b3166a9991dbdfa0f79b78b4b86f11291de000000000000000000000000000000001777ece357f82d7303aa816237a0dbd3a1398574f4061dd2fbf6b32af38a65abf5ec9bc53bb8ede932db9cfa0842d53a1a0a832e5bbdf897553c1aed35fab43aa3f4510c1782115e14e5d56229de2dff0000000000000000000000000000000002b41743325db9550c3a84af80bc20c54b8b0b685d7f84d05d14dcabed2f450b91675aa8c5c650eb81151bcfbf1603b4000000000000000000000000000000000f3d3e69d475fe1d4259f18f193cd84a90b91589a6502588106f0a577d1c1dc4b2feeec20a4fc30b3e403d6ca9e03894000000000000000000000000000000000c10e2bd1335363fe958eb50981b99bfbadfd1c414830857b5257bc8fa6e26b50989d9adb5b3a2fa610b3151f8754309000000000000000000000000000000000008c825371319f4ebd684f76b567c4e9a389dce96068c101568dc8cafcc10896e3c20202b591a344d9a1c1be02310be9b75e0582e9ad7aa4a02ed5ffa22e55570c9f20e6a24e2186e8a2a2f838fa45300000000000000000000000000000000101d3f92fe64af93468229608007f50e3406719572acf265fb8b2a7051525a9cb67cf2e46fc8e098cf081e73f3b20c770000000000000000000000000000000017b1422f8208c2521e3896820b22a65bb2a9b47d7fdcd2ce57196123c1ce43c1db6d00f236d7582795d00ef33ad6d585000000000000000000000000000000000e261500a9c64f5ae107d6ccb57fa9151f5321ef4e80f0e271515f1eaaa5e3714c59bf97b39acca41b15d90c0505ba9a000000000000000000000000000000000c08c955b6df18444ce3726711d29c2088721fa0aa6e317c52a05f73ec7171ef8bd61047174c74afa1dea804c68a28e33b7252f8f3cc6341d490c5c4464bb36e012f1b05057f405aa907ebb2c983f646000000000000000000000000000000000985cdfb3934e0484805a1965984028d6c459654a3eea6ef66e867dfc737e1bbcd92e31020d5a4ddb7f8091cae2371f8000000000000000000000000000000001998c5682209153a261bf981e16bf1f7a6f8e5e566c1b0f975253ea62439e5b36c5e5060751f21941edf0d348bafd18a000000000000000000000000000000000c8822c1d6412bc45fea05faef33c65d5a6dd13aacf1279b9cfda2a2ee34df3146d45e3434ce8e5f242e9cf7d3ac27180000000000000000000000000000000019191b51d6664a3047aeb5590df2939b2cbb115ded70fafc2de4c2e8c2a955a957375314081a8838bf89d5a140b7b915f10427f6e461e7b63b781e116a4d5136ddc79ff86b71fa754f00c797c035412b00000000000000000000000000000000156fcfffbf01ff3c8a97e7bd99e59327d38c6f7f1083d068ae158d1901808b3c9ac96f95c2bcbdf5f74b36dd8ce58d7d0000000000000000000000000000000014c64256d1cce124c01fa727482caf8ccf007e4ae00e5277d984f31a11ce584e7633565c61d47bc8accdf7c28bb266b200000000000000000000000000000000052dc9f7fce4859c852d3d9e1e77bb7887ffd35d4d550726632acab3d4303ecf8b3ec7f4114dbd590ac20d748570899f0000000000000000000000000000000017abd1e5dad7ee06116a8131c05c9b48defaa92efc636ee34a2970d701c02b6be0345a58cd8749e582ebd105c02f10a06440c89f8b10ce15806938b7ad65ece194d2fa3cc8d7d5591bc1d52d010896af0000000000000000000000000000000018ce0fb077dfefd57f7943d432e12dc9bf92dfaa30f8341397ff8906b1abdf0c02b599edf85ba1e5bb6287aadc72d7a50000000000000000000000000000000019e5e9e3b0632ec10a26b7c1ec40248a9a8b230806c38aa24e47489a8aee5abb5450f6e5679e3f13c6ec7a79560689050000000000000000000000000000000006e257a74f45142817ea8044f403e98c99db8355d626c59e1d11c6859eb0dd1dc8af389f07562259c1f85411be6cbfe2000000000000000000000000000000000f463e345b004b1364894c6e8ab5d35bfbdf6b7544a576ed6b5c5398ca2074f67e2d80af1ff5b721fc126d3afadff1ef43f1bb26469b778edd10127e634fed4d749e25b41d8eba86eff2c068c33e714f00000000000000000000000000000000174231581338fc8c461c981d4949d18f5b753d27184ffb41568f11e178a271bfc69f8c73f2daed0fdbe5bdc7fdf8ef56000000000000000000000000000000001532474399d6a73501801e5f3fbfc6f13bdaff7a3ea7634568fe82745752ee15af23b16809be18788d295e044e29c05a000000000000000000000000000000000912eaef94ab1f3b3257b26c5e8bbe3f99eaceb8c7ae8da577ef98e24f3308abe6e6005ff674a2af01b4242f8ff87108000000000000000000000000000000001925cd635d0ce770f4925a3117721e96c316dd96708b096901ee04ce02e7b357428e4364cd488eeedf76352a26cc1d10a40251ec7a7e9f7cc29948b122010d9745752df3f4a9c67427a8b58122ad4e7e0000000000000000000000000000000005c4a7f26ef0416f34750badcbbb3bce075606435ee7f69b3589e21e37491f0b4a7a98c825ec222848f5e29618828258000000000000000000000000000000000381c5f6511c9f06ea1a76ca84adab4a26a3cde13e0825b3d81899d6ad3191628894d0f57787f854aeb9e4c57fd15d32000000000000000000000000000000000bd706a5b5ef0d4ee1b679a0af90c217ddf9242b7c39523c39657962952dc14e5e07d02154e05693bad08bfb24a2b19a0000000000000000000000000000000009f28a84aa5bd39eeb09f13fc8770fa7e2e053b6f5d7e6021da77f48b9c3807ad917ac671de88b28dd343c2847c5e8eee03e5eb477506c397bc1a5204b30872085a36b65b7a8df3e0e187f3022736329000000000000000000000000000000000a8ff1b15ddcc3684b4d4ecfb53473497feb8a04660350ab84e5719fdb0618d61acbb555174b0900b32341154eb7bec9000000000000000000000000000000001464d21df798c0242ac6aaaf3c579eb66eb8cd53eb1e5ab2727298ca61ea8ca4c7cf815bf5c9f94c2b76bf659a4e2da50000000000000000000000000000000003a25752a4360c84e9353b7f1ce74d5106cbd637ec5ecb03dd0752660fe5c7622fe2d0475a4db98f785307c6961f14b000000000000000000000000000000000163601a86f02900d214ff8fbd041934189503438c557138b6ebaca8ce3c109af50ac28074223fc81d6476a3a99559ac565cb04110bbfcdf00616c2826e253f61cf955756e94dffcbb6001f59ae4a93c100000000000000000000000000000000189597e6d618a20ecf9a87cc70b3e0eee69ffa4dba75056ebae93cfc3c2ebb368532b17d9f6c06f09e44d9f101397b2d00000000000000000000000000000000086ba610e490588e9385c8b6944c2bad1eb03058e927fb2f9740dbefb779bdf669a51af88b45985e8345b8cb168c13ec000000000000000000000000000000000db8b9cdd4a9bcfc9f7de144da0b33981e4dd53744cd260c4bf045d643a4ef5f25aa19edab7be0c7f8f5ab74a4b7f1820000000000000000000000000000000010198384a646807b16e2ed9186aed99ca3197b05964dd0348086f446d3ebb847907624f4e02f71a1e866d17a125e07e93ce1bb7cf7d7a55f0624bf5c4c35327b178923d88be748a9b079720c27b500e6000000000000000000000000000000000a293f07dc3f0da0da4bee671951175a4480a719d44cad3d627878ad2f17596f0dfbd6f43acc7a1f9857c5d1f453e5d5000000000000000000000000000000000be6382cc7a00d590f2aada3b4b75f01f8538caad2ade90227ec71e5661ae353e68789807a13f28b23b17dc0dafc19b70000000000000000000000000000000015a9ad5a6f1a511ffe1891ce260ce442996fe4d8515ca593e3e869cab9b18af57956b1daa43aec98a0281143b0c319fb000000000000000000000000000000001807a4ddb73a9aee58b54bab2b878bea8429cdc91384c8fa533a8c9d15c966350e892bdfce16d37a4048a763cbf25d71e2b4c64b363efef0c5525b0337bf407879755f060af451075f4345dea7e681a30000000000000000000000000000000015aa6b865796f88ffe770bf25612ad27942213131c566a446dc149fcc70a018230f1cc8b20461ba2c55300fd27930bb0000000000000000000000000000000000c39c4f229b23c0f65ed720d655121eab50f695864959a2aa49771b848730494d14597eb85ba35743f64eda897f95917000000000000000000000000000000000ad44cafa754f06e45dfab801998c40e5a9f56e4add5c8add1d7ed9e05d12459f2efe3f3367cbcd161f524c714f7782b000000000000000000000000000000001437b1f1a1399ce2a860f7c6517b14a2db264b2602c1c57b8eb04e165205842b483497e98e6b6f8a62e25ab8b0e722f04c85e47ebe2c26e0aa25661d3353b5d88c632182aaecb35303d8d47f01308a0d,00000000000000000000000000000000077b81fa5997de07738e1b34d8e17ef4a9bde516577a4290253cc759ceaae745e10a457204b9ed0069942e0b487d106e0000000000000000000000000000000015e79be67a752a46dd0605e9d07d738c5732b2b605457ce056deaa1f7093b0bdc91b4c81c4c5462a51bc713a7fbb86c3000000000000000000000000000000000cfd2e6043263bda2b97798e1a7dcb24c63aa7197f2749f92524329e97f93dcb56429d82c1d63c067d7ceb38e6c65b5a00000000000000000000000000000000026f352d2f93e6407c59d58742dbd91ced464a3746dc1ad9059e6bb9c146dc1e74235bd54b1d90bb3399865cd3102f3a,293920, -000000000000000000000000000000001387fb972f997ed0cb97a5ccdaa759dcc3c2c7f4f15e5cc4fe74685e42cba75e778772d795847b45f274d32cd4960de600000000000000000000000000000000150b1ad31a3d434c1cbef877fde2e105d4a047dc34e3889d21544c2143e7b41b8e0024443a774bd1e09438293860a43f00000000000000000000000000000000065033cee91f5c4d429a074be3d2a8b001892455a11dc708ea73c0082bedb1cb8e8b567a6ea68a1296ad2b80e4b5b08f0000000000000000000000000000000001991ff6fb57e8cbf9d228f1a99697f785261ebce9d3c1f592389fc860b8d7a069896dd48debb8cbe0c43175cd2ecfff5bc589e7d89994400c511789cbcaea19b077e0b02d625e549bc6f2673ce40128000000000000000000000000000000000a0fa2d39d868737b9a0526296335256ab4894cc58ffd80bc6334e80d1314bdf017c8226b41ea135f6adefd07650ca1c0000000000000000000000000000000004334f7985211061dedc794ee8931ded12acd39d7e6a6ef44a749118d19ce8204d07935fe62fb2a8ea4f68f99d7c5f5d0000000000000000000000000000000018850a3fc8c851a06781511faaded1ce0752e7ef66da82c2464eccdf78c32fae306da3cfedaf76dad371cfbe012f2bee000000000000000000000000000000001296ca0b0e368429b122537b096fac77d6367988956a7f6cf70c7193b7033ce42fa0cccb8b84b9c78b16a68fd5f4c14c2c3d2a0cba111642a6354c117d494be805cad5b5c486bc47906a2d37a9cd9f850000000000000000000000000000000019deb7de7fa5254fdf5ef34fa616651ec70548187fb0bfae9f512e0bfe1f662783f06a9a99e434ced84229deddab9d240000000000000000000000000000000009c199ef916e6f6fe0677ab07beeff221a5687fa8da3ed3ad99a950b7f27159f857d1b561006bfffab551d240b764fb300000000000000000000000000000000148a211fb58b38072cf7c417c70d3ef92e9cbe22b31b2b626198add01dbe1ccfec32d333abf42140b9316312ac48aaa2000000000000000000000000000000000b551b57045365d842133e46814d5d0084248904960f8d2fb28e9623660bcee658582928703f86261cd70e95cd20cf3a530ff74626657262fb49460b2c6981155871f2eb5562581a74f968233c3cbe3d00000000000000000000000000000000185959a297a8f434cb9529a1f7bf9009fc1af3d09efb0a9dce1b9e7d30699da64e4b1d32cdb05b068621db092c1eb59c00000000000000000000000000000000106ef21e9031d108364e93ae4b5d21b0d6d78c2e86e0f8a7af27ed3d38dba0192954e8c716665333e5dcf21387d3f2b1000000000000000000000000000000000185d21efd7d613c409b6ddaa66eed70c235440974b2a9154f3711e3969061461f8824b4547c65e9db09ce875512ca2b0000000000000000000000000000000001aa46b22451afb12962bec5c6309feeb4acefdf3c98c1ea14275409b7111aacf7c92a8e024d01d4dcbfb1c91fc445a1d182ac912b005e90ab81d4f2a906da8309a69576a8afaa160fad2540ec049913000000000000000000000000000000000557370d81bc3da4c50980106b8e65ca2edc757a475194cef201c9edc0f50363cbebcf2750acab0b67e1020daf5660e7000000000000000000000000000000000462f1c1379be9bfed97a1a83a00428de63eadb6360393ba162af3762a99d7eae8549d0cee218e469e4997ada7b35cc00000000000000000000000000000000008aa5ead309fc703f6de980dd43c294530cc2b38b94d5281e9cd9b0d09f82f747a7107b700f1437f3abe36c01bcfed1b0000000000000000000000000000000014110a19d574f26e11e2163a981c3388c04854c5693e9033a474f1020d5f980666d84c60370950734c46663e194bf0ec42a002a460b51429e25f85ec4abaa580ac1a14315b1627bd52349b7b81a641d60000000000000000000000000000000015beff8cb3c79098bc73dc1ea4b240a4e0d094b3dbbd51592df6adc9c9847beb436ec83df6c55666e296fa843298446a000000000000000000000000000000000943aca2a6e57e9897ec764ee2911d9ff0a59d9e903c70a8494340cef2143895e79d3e6c03af2d6461ca199dfbd0ca0d000000000000000000000000000000000b812ba87c4989af07af44f3dfa87de119fea28ad598cb8e52247cf41bb8bd384c0d8913fc82e4cc2878065e797cc581000000000000000000000000000000000410ed148d1e354653f9d9d17c50026957fb03fec64964f2bee5eeea966b430e77f7b3538d9f4700a673fa07d0daac6b7a650dd3765032ac139d1b54ec7a5457c9e3caefa6af45d198433e5949d149ad000000000000000000000000000000000de0a9bbd63c59767938b555c7f9284d0885ca23019818c213a7d4f1594b028965da871cc5818240d155c05c69e4e25400000000000000000000000000000000079dee5649cc67700e9338799a9810d352a5c68098d0676e42e00bac31f37513944dcf47408288cb7f1cba121506a10500000000000000000000000000000000101a650e84352aaf3817b400da0aff40907aae3d2fcf16739f8ee8d5bfc62c2a0dd518201701932728a41134ea3f6278000000000000000000000000000000000f1f9dcc0b55d0ed327f667cebc052c4b6116fde5e3076dd6e447c3214d4c8847885be9547f95f341c42e7c7fa7e2c71bbedc44d54349cff199befba9531dd4120a51e2b830a3e356e68cff31bbe365b00000000000000000000000000000000148f706b4c93e739324e5db40d42025535cd33a32bb3f211add618c0e2022068384a5612da67150746896a2813a664e80000000000000000000000000000000007204ebcef495ca8232078fbf1539a4b46e89506a09dc008da457dee2792acafb6baac4f6cef2de15cbeb48bfd12bfd6000000000000000000000000000000000bf8900e48a4a56b653b1e02c3b9a7d81c2045dbf6297f1ac2acd69d1bf9e06480ea917e3a616243c3a30235abbc426f0000000000000000000000000000000005ebe0ddf4cd1aee76d0b3d03eab754664c8b36fb20ab1060900909e0e0a4abdb45bf74a0b1d40fece9bf73360f580bcbef3956ac71bfe97029b8e3f85923c2fdf9cf1ea6582b68d5a4eabc6b044c80d0000000000000000000000000000000007824d1c48bb2cc0f406e356f6e52b66392f6203f49dca7ce03ae6302ce3e8055d071cd812f97481acc654b318d6cae2000000000000000000000000000000000ae89f9eb1abe452efb7ca48f8f939d835f9a79e05211ed9f4abee06b93e34b17d920ddbca3d8bf18b96c3705c1a064500000000000000000000000000000000119ac787a7f3e9b7ee34070aac1a769430eaa8cc838f1752b573ac7f3c02a9f490de9600c856a55448598b149f5392e300000000000000000000000000000000193a3655a80e6e0b1278730600fd4f645d54947d193484131176b890ac197702333ea847317568230ad8af1280864096392f5b4291fbb18a93248e830b08fadbaad6434040c02b45cade73b77f22c2bc0000000000000000000000000000000012f66629836f0f57bdfd9bdeb2c9b7d6d5dc55c586e15d76aaa04aef06722bc8ca156fd1295b3063d738a85b3e8746d900000000000000000000000000000000097825c5db7289b1b9e640d19ecaaa81ee59e5b9884713f6d312604d8ac367634a264c316d73a9cf63358c8fb15f8c5700000000000000000000000000000000181133d027b97d8e2bef308a93b7ea2a35824dc7d01a3ed2f404fbe12ba3b3e51d94ec86cadf3da7dc9ecbaa23b411cc000000000000000000000000000000000a28a609d0bb015e375e74c087ce426dd3c20fbd8b374d3817c626faa81469cfd11a2a4e418a44f4d7ca621d0564bc4920a96f963375d7a294b584f2da699a6a00eb5781f46830987346cf4fe922a2f6000000000000000000000000000000000feca6f7e3cb286090fa3df9c5ebd10c06192fe14af58d46b827acf48fbd462f3f76d9d20670803946028437410ea52800000000000000000000000000000000183dc7085483bd05c27691c25588e33296fb610bddaac253af5b2262db38091650c1c3185d71a69d1a63770f95f381d7000000000000000000000000000000000189f9b9ea528bc2377ca3354fccf440fee059f5732dfdac320fb58541e74e444dbdcdc008c7b47681c05502f0b302f5000000000000000000000000000000000906162085e0e299a07e41b9d62668d4810b97d4be317bf376da537de7adb06de011f5f40af834593761b774771a80e4115cb4646c8996239f4fdda8c27a335361f0a19550d6eb0225c008408c47258800000000000000000000000000000000030cc52d7901d0360d10f344cecc8325412788cc30a912d5de3fa9bdab18db44efea235c5d34bab526f3b8ecee2cbb8d000000000000000000000000000000000cda35f561c19ebd85a445ce8bb1618b446c7013c07606ce58e0b5627a5c9e7cb200e2b8ee12a0564730279e75b469b500000000000000000000000000000000055ad0655a96f6dab5a432e7d2fef57a6a11113070444089df23b4b911e0994b90aaaaa2c62d06756f4704fa218f7c350000000000000000000000000000000011d22438d7c162d34802a664c254abaae07659902e1f1bfc2bdffa6c17eb11bff5276474cc3cec9507e28685f1c21bb0c8a8d98c93c392aefb64ce0c7ea455ba14c48bfbad0e3dc38d43abbc3276caab0000000000000000000000000000000001d04065373ce5d1ce47e00476f07708bb028040edb9ae7e8e00e2c6c460e1ab8b730ff510a25a3c8114c1753b7bf1ca00000000000000000000000000000000001c87217f150694a84a4e5aba8d188ebf7224e76b078dcaba4a91de6b4ab317966ce1a9267a5a27ce556c3386b086620000000000000000000000000000000003c8422590826e0999e7ae3ecba84edaed20fd7f1eba02b9daf1c46c2aec74d5fe63319047d37f5115f243ae0ddd4ffb00000000000000000000000000000000136ae093c3bd55ddaffc2494f3ba8176947cdf2f1ae408e7e786b23b6a65ba8c4131c83cd890386ba531b8637b3b042c8221622734dc6ccf6c7b84b387a3dfecafe187dab70ba373b4416ce3c505bef2000000000000000000000000000000000d09b92a559b8efe5224184fb4f43779d0b8c8f23587f4f74e2fc6fb1f94e8d2e0d591eb0702cf51a9eb402e79b46a0a0000000000000000000000000000000014ec2e4702f1ee1074cd1ad29791cf4903357e62570d16ac80c5e8ff73b255ee03a5ba070091cb2f984b2139de06a97d000000000000000000000000000000000d22fceaa48193756ce7331952a2d9a8057b67bede729e07cf8422bfc79f9ed2aeb99a9227af256deee9f8a6f227faba0000000000000000000000000000000015d9322c3a5a7ca404259c4cc7cb93dc3d46dd8dd9475756d2ce6fea527642f9230c7e94a804ecb0b4adec7963fa9cdcd3d1f427a25f5df025fa71244cb92dda9391d65b04756c41de0f67ea072c375d000000000000000000000000000000000e16fee11affc6714c7fc8fc5e7cce44d8afe645861dd2f0b8e58aa93d4f0de9b7e73020a1537bfbb0e2c8327c4aae03000000000000000000000000000000000b7745a4aaa8ab4593daa61e375d55f9043fbc7385ff229889fca514562168a4e769c5eeef4d564b41cff28b4efdb7bf0000000000000000000000000000000017f6c5b1fb00746b50ee4c7c743ae57fae2742617e5565241d012a0ef6067d9ce59be749a99886ce9836b648525d2e92000000000000000000000000000000000a3be81720e80f6aa0570c89613c78efe95d87ccb374e7f77065800590bc71d23ae097516ae1e97b498cd233221cf717b55c943fd9b11f2fb8a89f6c08a6eabe9434062354d845f1ac740e6043443f8b0000000000000000000000000000000008080a7d91caaf2470f9632575b43990a9523219d75994f1944979ed5b650be1e3c93eceeafb0875f66a40651f4c6dfc0000000000000000000000000000000007a19c4a6340e39230a33b12fe63e47bb0d1378420ec9e439f216699e512e4d70571a1670eaa6b60a5c899ac63360a250000000000000000000000000000000016898d22b2c123003480e3a01965a72de94cdfa39b20898c49e451dcf6a4727a1ebd629172aa1a1aa6897916cea192b4000000000000000000000000000000001217a373c78de9d3005690023b9e56bbed3073f13ca2408a27a3480578d8013fb9d3ee5cda95c3cdd091a5cc68d928da7b0c1d54e51b8572256aeb72bb032a5011a3e8ec5ad7e8b6e0397b9f6fc64c9f,0000000000000000000000000000000005829c932c80baa420602bf841ad9bb24fa25c61f33f5d88693207b81271c94eef54bb524aa830fdad8caf8c082bd4990000000000000000000000000000000000b8d184316c2471ec6875641ea83de4f9b7227041922415b38b07a0704d01f2585ec2701bb4ae0bf6a0c0522efc0c630000000000000000000000000000000001dd81e075620914254b38ca5a7287eb56f2f31f6f8fe02fa51488d45c7f4609bcf49972d0ae5ded76eed5a4c096939d0000000000000000000000000000000008067feba36999b58342ac54e48b0fe28245f8ac2498b60093082822d19854df5c3168dcd55ccb6b2cb397b77e712333,293920, -0000000000000000000000000000000016ed84856b9f41be9bc6c025a9b79e2968e2ee6bbc27608093256c541096e2c9eda1159e6dcdaefe783aa59d52f28ee90000000000000000000000000000000014aabafdfe8c7369f93d5472a9c6c4d426e4b02c943488be993d04ed24aef5477f6d455f82b4af78381b8bd16f42b56f000000000000000000000000000000000af34789c6c923103633e5b1b9fb447b671ab05265c16488ca7224e49db21973487a5d3de4de40b9d8a97ac9b1966619000000000000000000000000000000001123a6601c5351a586f27f8264d4227f5e1df868a03e0c3df5c148cb523cdd178f96fbe52464fdab210564dfc22b29536f082a5ffb8baa38ffd684a4a70114343a1e723bfcbfeb57d0a85ad5e592d7410000000000000000000000000000000011b82d78cd9b53b8e7e5c14a7371f34f08546896bd59d1e7d8be15d21742180aacdd01b0d08da2cb24873ce75e166bd500000000000000000000000000000000161ae0d724085a6e801edf73443cca87995c2d6b37e962db5719f4c480cb830e379fa778fd2f29e75173e1c31daccaee000000000000000000000000000000000a2c2b89d00b7d19f2b0530889905c30cecbd4ed0b56ca82208d666e7576c32a6e90cf867ad87f19e4fd367a10c449a2000000000000000000000000000000000b65c0226743b573dad7ff25bf1885e3dec686cfd5da2862ab300fde4fc8fa9b587d0f2d11ebe1f6a6770bcaf2588f8f5160286a6d23c30595809dab6ee6523d7d235114d1b295087e024b4f6ffc80e50000000000000000000000000000000012d4f299998aa897db9e3194244fdd1dfb95225e3271383b5cc296bbc51c4e1af52e849d8244f82421cd198158918d8900000000000000000000000000000000110638a2f7cdb7104de8fffe29be32610063bc656e13168921501e1614f282bdc9fccff4eb3c479a42b240a2c8014864000000000000000000000000000000000b0adbcbaedbedd376efd20a417bcce562b87b7449cac1e90d44eb05930e6f558b35ef755457305da012a231b5675bc2000000000000000000000000000000000db6fa926c7e02f633730569732fd9239bbacf2042599e79a4bee76619872901c6f4ec4d4fbf3f84143a0d17b167130ebbca29b94b6583d46753473143d13a7aadb0b18d6d35d7423b8a004991fa1ce500000000000000000000000000000000166578f3087772545c0f47fe0b3efe32874d26463e4f262be65a3bb6b0fad7d0f779808f69362f3fe63c72f24ed03d70000000000000000000000000000000000a8e61e8193228fa1825cf14e94f68a5eecece9afb48b44871c5ad62510ee1fc4e9c60d5f2529b8685e6aa13ec91979b0000000000000000000000000000000008d25d81bc4bc92508c8cade33c305c11d71a06bd46f184b05dc406f0939f0e0967b02f15b4f7f6984c9fba0644ca8e800000000000000000000000000000000113660a7d2152346500a1578641aad4dac2919ce63d01d8ffa6dad72f524c888fc2e9d2876859859e47d8e884f170f86607c80069dab2a16e39370de32df20534aca46565cf573159a93c64f1f0c4a1a00000000000000000000000000000000160529ff217934c85cbaa8b347151539e252dbb502c015e8e45c128df2b8a737866737d5cf0eca6f76e4a16790cd02a200000000000000000000000000000000127f7b0e4f9351836db9c204386a199293955471dbcd7b4ee9186f0434b46dcacd1edc02fb46b4c377c4e62cec10cd6700000000000000000000000000000000094abac17b11600d7447f7ad0f21d98c14e439c4a4a6572b00c90e14d9fc54e85045d0576f74b054d384179afc0a70c80000000000000000000000000000000017165c32410a498add8e1dd55ae43f94be234ba3859fc6b4816d7436746add313f42b1fb49e0cb6c4b7341f0acd09db841c1f256e866d218b3ec20c132446945177d518573ae3f0e739ebcc8821bfbc700000000000000000000000000000000060e503ee1c5d3eae4bc0eb30fd86303a5c48c10cc7b4736d17b8774c78a8c97ee05b40d366b2cc9bc7781b1e4a192f200000000000000000000000000000000034e7012414edfc6a8f7b2c6049236b6fb77eb94b05d55b218851fc1e553514e6ad388fac08a24c33bea63ddabdfd8720000000000000000000000000000000004c832477a90683d417a00a698b69c643d6dbf82f5afbb83eb3946f8098d80de6f2d457c0a06d0051315f06e93b5e13b00000000000000000000000000000000048c3339996948974f2bac14d8a6b8430897644ec8e9cff9eb369557003aa2827a4f3fc3444c4df73663ebc9325ff317c72a47e2267010c532d676ee3c3ebfb2be2b7569f6f7a22f76733d7773ed383c00000000000000000000000000000000082466944ee7c62788b6fa77816094ea623d03c7aa2af249cfbfbf78eed26a76cff8c23c2295aac7ee1ef8dc84630003000000000000000000000000000000000a8f88adecc3f50d8eb329492f2c031e722f36627cb3b21415781156ef44954c5b8529ceed5978a37ae1248909d38b5d000000000000000000000000000000000e08f628aa014152b50a85bb6eb947d53c596d82c0d03594ed3b64c486b8630c880adf43fb1575b02e4eb8174a04034c000000000000000000000000000000000776844f28958d3e12a5c163dbd039e50df44b1c6215429381790175a609a339621475a5b9a06c3276c9177d2dd2b576c52f48e84a68d99124e678dabaf376c956dbe9603974283a9efc7c27e830e9590000000000000000000000000000000004477f153c0510d8e50bfdc2db69182c05d5ae9b94bb1880de239733e380e03d50001378432312b24b5bf0952c38396c0000000000000000000000000000000016663990dbe529a5658f2b3044bbd390ad430adaeffbd5306f758d86bd5422391bfa1d21e88c63300faad55e6a2d1d3200000000000000000000000000000000188f701658558033ce2c41101a611f74ad6d3cd075c195476bd2cd59a1a9dcfe937020737250fe418b4de435f8b3a0380000000000000000000000000000000013f8d3625309767841603329f56686a99e196d697802cfcf31f8b48f9c76f77a321276a0158a22b94e91d6907f6ff451e4fe662495bffd8ace4c1ddb39e612b361bf90a0f1bdf6c7fde2bcf63df1bbd2000000000000000000000000000000000f184d22f3c0431b031ee0ee7ae9598ffb511a2a56f5c9f15c9a4b0c53af2a10d22a311805786e303e234239326dd74b000000000000000000000000000000001062725b8c576e79e314f6a56ef9c41f05a65d7d0d57d8414e2ae9cb1a520b16ede7e418d3a9413c9c1660dd7508d5860000000000000000000000000000000012ef02fbe96f9a191804b6c4a0b65b6024e3e2b1f8cff986f5a950cde9a32ad50d4f7a72804b2d18b93250a63a7ae97800000000000000000000000000000000000b3b0333d61fc46653a7172f5a813d13ff5a48056f9689c78c4b18b8aa3afaeb7cec305d98dd600786351338a2185a651e67e96f64b80f4978fdc1cac90be538774e34c2f619f8b8e60cd2aa20f2690000000000000000000000000000000010c91e1dda48dc528f618f01abbe01db1a7b6dcb0d47b83c7b7db3331f7156f7b2d0f081458241467b0078935a7b4a4c0000000000000000000000000000000006f87f782979d2adc02e65b56a4906e50430cb4e0913636e9aa0364535c9d7ecd3b9433358e00caa8e90e84b7705bdfe0000000000000000000000000000000004635089c7706cfdb5a22ef643d1a9a5021847646ef01ea559d1b655299b65cd76a73b04149adbac612e7aa756cf30060000000000000000000000000000000002d83d82bc9fd66c558e00547a8c25633899584c9b855195c00eb3c8742d22c601982f244a03f8e0c5c21caee24405481a6ecd3db89a7f07344b5728efffd35a11f7380c740669f746fdf565905a1ca0000000000000000000000000000000000848f10eeba8ef9c7fd0e679767f6b6a2392922092916da8f13573661f84ec97c65717e55c65526cedd59dc1e096f0840000000000000000000000000000000013781974518487de12661bedfca5fe72205c51cab461b5757ff14f319d081e7845cf8e099892ea85470039713e8e48cd0000000000000000000000000000000004cc1a27d1aa88484fed40ceef72e6bd201e5ee276b5ec27624286dee112ece767b37c6f1f7846d71cc0f4042f04dc170000000000000000000000000000000004f7335d6a1463976d9fd86e2baa45d08ec65059b14449ebe4aae99971c5666cdc6e40cf0510ae99dbce97ae8b4598067db5ef4c1c174c2e5ffe5555f54f4e845c463bb5105381fb39eddc01103b1bf70000000000000000000000000000000003c1b1e0848bbe37e62f1ebacef1a574400d5048f1e09d935af2052da29140dc4074175e4d6ceb7c2c071331b2f3d1d3000000000000000000000000000000000e1c84d6b20553ddc5ab09049ec488ea2839c5818e31455a7b231cd0455e2945aefcbdc6c1979821a80bb4f77d46e91e00000000000000000000000000000000199ebb31e8800395a9c2e103c9340444c97004186929b52de33cb8d9396e7ab8d5af3fe6035d4463701ea41e341f577300000000000000000000000000000000081b3882bfdf83e67d2dc42b211069a4e93c0f173263f9f20579128391e7f2de70335df949b9c0e9b834b6e574f2f8cc14018f14c50d40d3324952ec22ed247c13c4cf10eacd32c3671757bd12b041e60000000000000000000000000000000018aa45c6b3898a5fa618f87f9a08a7234c1b94fbe38e2297a1f9c7a2e9de0ed83023deebd56560b1928c012c14dd7a860000000000000000000000000000000009ab80da6c519aee8aa1fa68c35bd0fac78b55f88d861e8fcd445f629054325d63cc4241f61e5596dad0d54c94511e4c00000000000000000000000000000000105f8253f37f5538a2c25587fd33ea61fdc744a7cdf4ff23a55e2c66a39040d4de5eeacb7e11c0d2a483d59e7c3186aa000000000000000000000000000000000f6b10cd6522a1e34c87c702f58a07858cb753d67da9625155bd433020775351a9ec4ff879f91a43f63be1c969afe675ed4a28dc3acaf2220ba56d026b292a7d017bcbe358dedc57556cf638517bbb14000000000000000000000000000000001618dd5de43a6bcde91a6a03fcd88fe59d1c8c51d3d85cd44a1920dabd2608a0b17a987b76eb8f5b20c7f1dc0abb383e00000000000000000000000000000000198034b7ab8fb8ff267a52a9423da95bc587eef8684f18639df5db44e50bae7fdea5c5e5ef37ff14937f86cc948a34e500000000000000000000000000000000106d1f017da463176bdf55e3ada78ce70da4486be42dd0095e3a8a0f6e59ed503324565b717b45ee38d90dd3ad13c10600000000000000000000000000000000112d425765fa2fc28486b95e49db63346188fc5a6bd0b7dffa4430dc82703eb44d98d726edfa4a275aa5db5028d01ef530fb17a38b7d0888eb02394eed26406bce9e92779251bdbcb432153a550c0850000000000000000000000000000000001326581ac1a1a960db1ff2e8b89b1debaae46d1e2d0aa6ffc6c7398f207abb699ac59186ae7222b5cae3abe64cb61c93000000000000000000000000000000000218753594c63ebe5fe503aab4dbe1e944b24138948542c7c43d92ccfeba5854b7bf1bbcf8078d85fb0b8701b8b092fa000000000000000000000000000000000c3ce8c17f75e78a8c9980e9fe125290d377a32ac46411876ef011e169e86e1458ac5e71cb4a446f6c640cceb8d5617300000000000000000000000000000000176966eac1e20586ad2a03b4a1598b4db1d7c66be70b1b22833e4afe0e0b3783572f791ddcd4eb70a88f4acc28b6fc7a980b5873a5d0f78c3b8582581349498fa997fe3c6b1abe0edaed594253359d8700000000000000000000000000000000099ac8430fa411e74082cf3282f9a456d3826a7df4f91ecf621e645a1abc057e1bcfaf9ee73f149bc447cf4230f2f6c90000000000000000000000000000000004e93d7fedc9e2d7423c9e111b4674a2bd83de28dcbbcc54ce4b324c96318a11603fc9ea385f1c02364ab1f6b5458481000000000000000000000000000000000bbb29d70fba5b12fadb02a24bfe3f6a5362c71fe5f964dcd0e01442781d0462a873501029192858027d612a8572e9d30000000000000000000000000000000010daa9960005562ca2d18eaf4b4bf081f194fa824cc77515c81b2c836627f21b732448f367e2cc1830ad0fa4ceb928e1619f5719c320320a3c45dcd6207575e0d8527c458c56d3decf1d12ead8a985a1,0000000000000000000000000000000002a61fead6801f41f2f27603cf34cfb4b917f2f85cba1f9c684995227653c9dde559e1e8497234fba9b2e4c119cbd9ec000000000000000000000000000000000085f73b8e835a10bcb9312931eb08d916d2b93a1da843fa2f4530cdb26e93b5dc95a555dbe8e50ca465b162661ce1d3000000000000000000000000000000001442fff9019b5476c217ff725ad95c92c17b42471ed7bcc121e8a0506755ec279d4e56d770a322d56f56bc6a6b0a41160000000000000000000000000000000017e7710c4639d51c4a79c5a2791101b52742df202b1827192872f168bd21020bd068160a587fc501782c1301c231a0d3,293920, -000000000000000000000000000000001213b5d5704c454845824994769c8b300676e75bafdeb95202001161aede276ab7967ea7362d38e97ca1484cf9c342fd0000000000000000000000000000000008c7c1fa04bebe5a1fb8678370563db63e7a10b30747c2ddeb4aabd4fc0ec93220d578b8110c6bfe8a3a6ea2820f0db8000000000000000000000000000000000c4061a295120a00de52300ee625ac46566464e6702489467316e8c182ca2168052c50b5962ff47285866c17d213fc8400000000000000000000000000000000086c153169a9ed1aba10a6cbebff4911b37907d6398c441ad47da17988d512d822ee36f5217355b93c9d6dd8dcbc8e0b119d33d32affaadbf6c2b6243bb7b344a971270b488cf887334fcb15de2818cc0000000000000000000000000000000017929edde8f9940826ed739bc9f59099ce76e85950698ab0140784647023f96afa064aa4a49b9728f496515a0a807e5900000000000000000000000000000000198d98f430384c1e7fa9e2403d9c3d2f81873fb7b204378cec95b97e674e10a1a43af97db0488209904469989ce80a0a000000000000000000000000000000000afc9b5138999bcef35613e38bff4f81cf532e00346f5205405470b2424622826c746ddf0369c7bdf77467dcea5cff290000000000000000000000000000000019ccc05724b3e9966bf918f01312c80e8422b697be89365b6ca00eb31b0bd08fae942e90a75bf9da1b3d264e416060f1f1d832b355d7e0ac3653431528ad0a8f6819daaa19292a00c910ff0ff39f46d5000000000000000000000000000000001568e52c2760d895874527d1ac8597730578176bdcfc67aaf69ccda253f6616230811dac59bc27cc1e57b94b5743cb3f000000000000000000000000000000000a4ddeb8b56f105ed5f47a538052f3d38a23c0ceaa2dea241554e6508f82f47d32415ffeeafe5ae5664c936b78e07648000000000000000000000000000000000b3b335a390aa0090bfd6467d6cd02eea1ced347cdce3c9ed85dd46e38e9f2ae9642392c2875a27618ba8f2c555d5b190000000000000000000000000000000012baa4b29d116eec749353b7658af70d4d216189133db707e56068c8483af43ba86583862e6b39df13b88058536861b9e6dcfa50f6129544835b5a4568954264ea68d9e2e5d4370ee31026997a3fbfe90000000000000000000000000000000001888b83ca28c244a6178377b4ee6844dc916e28c3f56312ecc0e29d08e6254dcda39a36ccdc317d1908303db3c028dd000000000000000000000000000000000f4b73d9316fee42d60f8de402a7d07765508b84d8f2c1be1f3f9e802ed7b0c6c5fece3db95d5287225026e73de98ea4000000000000000000000000000000000f1b48122191e1bc421881de831293a80566b9a7f2c9836f7718afb69592d59d2a714cfddf88945b94fac7a50b743eee000000000000000000000000000000000f1c6b052dbd03795433d7ad122473f109484d50245021c8727d252145e7db7dadc015265d1547f9c748409d74f5aa33f7822767391d3b2331e8e1b81c659c6e0262f7355063decedabac9797a84f0f400000000000000000000000000000000011e8613c3a771a177b4b85f0c6f97a53fd7900cc23566aecbf115058d2863189c21be36dd5dd736f6d0ffbe88182b400000000000000000000000000000000017b2c4e8d8aad0a12fd7130789188bb63a08f2b243c8f7700599dd33d7e176f70f2b1818e56540ab3fa507878d96a46e000000000000000000000000000000000e2b5ad5ed3578dfdffa414a4a2142846b1232cd2de468725283e3f92b536d8ede74bacc236993f6f68a16fc6a7828d3000000000000000000000000000000000fdbf06ae4cfedc462f5913bba9bba2b5c86ecd0e298bf27a21317fe74af6ab15014c62cbfb617356548cf808599caf4b1ba1cd6a4a6c433624dec63547119c0d492e3f38afb04e5153d82e400631aef000000000000000000000000000000000b48aebc6525620b99cd83979658a35afa233d17849bd0dcabffcf3b550f875a386b6c0b4ddacf18a23843629072c0150000000000000000000000000000000010432e5abf862d3be10ac5677b9f296ccdcedf1480e45de631b6bfec42f20edf62034f7205f659f11fe5a6aa9d882c7a00000000000000000000000000000000011702a3590e7aedd6948bb94bcc874e0b8d77a18126ed4ba3753dc98953ff941495486c14c6d801c71fca3564ded9910000000000000000000000000000000009faa427c0a7da26c92b451c61f5b5e8804fe032a4cfa014397e430882cbfcff81bb22f9c15a8747ef455773c1ef65b0a41e184bcaa0721caa4114d6393ae2251fed84aef57c7927a170145308bb136700000000000000000000000000000000061a1ee841251bad461f89c52196bebb1cb4463298e88abd62cccd21bbd325ddb33d1306ffedb2734be76c18d80c8dfe000000000000000000000000000000000d05a5ce6372ce34b0bf4b19d8e05aab74abc1cedcc35a2d1d4db38813d1e5c1375d63ca0e8bbf29c510a4319d2aec27000000000000000000000000000000000dfc57aa8de28745b8d28db3769ab5ea26b5115d3e59e51ff19af8ba37efacdccf763ce682cfdc77685705781c3924870000000000000000000000000000000018c17d87411c4f8e0ca51b3eb4c3765d3846e0d1b75574f8e511b2f3e8c5ff53bf7618959ce18dfd9e4c6285e88f094f63cb451d8eb3565274793925a1869ca5a25fb19639449c71a761809f785568de000000000000000000000000000000000a0642094b89dc9c6c7c11c1e57ed542982bd246112884969d424a3e091ec4fd73dd40a5ac116f6c68216fd1e733cdc7000000000000000000000000000000000788c7a63eecd1cbc26ee6b14b09d0a3b7a17a848fc0551d50cb7497bc98287da2d9b898260eb678a8a0f446eef5c6670000000000000000000000000000000017a1298f90022ddff3fbbcca180e3f4da8760218dba595a067287a2473a6e10b93dcd54154cb64b6c078b083b42cd09000000000000000000000000000000000116e999b808dcaea0566c0fbef1807e160612dce91756b2cbcf4883b04a90320a0759bba21b41e6f4d8449b52e52f9a96a2f94d55f784ebfc6b6260327372217d6a5b9637ea5f9afc1a65f99c221c29f00000000000000000000000000000000064c95bc9c0e2be48849a349f16713791c37310f71b5d0613cf0706febeea3a56a0f0f1ac6b504524eba801e8b759f2900000000000000000000000000000000007088d2f41fc7e1147b92a2ee7062b9bec194d3a47eb9985ac1ceeef57e1a006571e7247a13dc95afcf9905be57e2a7000000000000000000000000000000000e6a0770f4315acd9e410fe58395ab8b20a08240a6948b762dfbbad3414bfca0ced4ec9da982bc9b8798b60dde78a96c000000000000000000000000000000000a70b53a6d71c83971167afe329ffacdd417bd7b228766851c3b43701a439f253a8659312db7e83a398142fe19332b527d889a3362f551b88e63463b7f0cc334fab3fdd302b630e419e362ec1eaaeec000000000000000000000000000000000002486eaf9b743d3aa6a1f3e1174c5f213bbf3e3cc0558d63ce40e3c03e1c2f6e8508248bb649aae1bc92f3eb8118a2000000000000000000000000000000000042b03959b40eb0641d39117f7af50dc7ff048697a57b80723aaca164e2dbc647ffe78fea0a6a4c07671f7db6d5b2dfa000000000000000000000000000000000e141eab29f52b9bd0ee44861f154ec1bd30abd715935a7958a19007e789a41cdb0f4b9cf7b3fac0b0d4d77637b510d00000000000000000000000000000000002cc2eaf89cb7a04d425d878a30b5e2e9858ae0b2a2ab28fb28a6db0c7283ad861bb6a92067e969e5721b43466e857db8bdd400ad873cd6ec546bff698171942d536b94e69dfef4bbf316a471d4b45cd000000000000000000000000000000000e0f7595e4c136b4d8bbd1eeb021df7dd2bcf1d9f98e4fa293f7edab635e019af87c138275fefacd806213177af40eca0000000000000000000000000000000005dc209d6c86f1871637998c10490a70371f9e00a68d1363dfaeb62046473dfb4bbd3b18b943439f75c45a1ee7f264a90000000000000000000000000000000003d215567d1e8f504a72658d48fa51374ac77234552c17db4033af780133d8516bb0769678ecb50b8b9eb950c2dd73e80000000000000000000000000000000004d780849b731012e1e5732d5f6d32c659a95c3e1c8f5ef4841fe82afc6f0aa309b1e02dc2554a4a4ee781be2be2149f63b496a64cfd15410192aee9912f869deea5a08eebd6b160667e12fdf23c44510000000000000000000000000000000007ecfb753be501d9f9b7ae7ceaabaa4fcb7b690ee04fa1a711a15dcf67e4422adef64a0f8118f93e67f24a2d1a2bcb36000000000000000000000000000000000a459e403d85972f7132641c05bb842416a7135009ff46b617bf0918e65cbbf33f76b98c10d901936e589bdf5de31ea4000000000000000000000000000000000bc6ec31a3ff92b4fae07cb73ad7bfa8423044048337b0ab9add09bf10fdf190a5f7996d157483d29fb29a681ed585520000000000000000000000000000000004c622e2bc606fefc8bd83c4a32f7353123205a6d3716b581c2c71360e5200ab069f60c256dfcb04b466c53cd61fc94470de38cb4627f53509eadb0918e562c6fa68a4cbdfa9f7578a8aaa8182f5315000000000000000000000000000000000125688e44f593c5f585765f30e9fee5e4f15247cf33ac78ed1744453385f49ac61128e23b1569ea33d74b207a5e72e930000000000000000000000000000000009d77360ea37298fe971569230159967012c4991255fb5337ca6d58cecc3cd44a024a9a044ac98a894cc97dea161844e00000000000000000000000000000000056b2dd9569f0698c732367cfb217af90a3d6dc15e2555ce0aa845616e4067a7fefb304f6525b539555a0a685f0ec5f20000000000000000000000000000000009acb138abacac351e03f7589d4bf29cbd331e93bf538578ca9466b759ea070931c786d35f74fad42261e2df431fd00316732c583e8049a5de38642cebab774d90d5f87601e3599ffc9af692ba532e620000000000000000000000000000000013515b0022ea946a8e679b9c0eac6cd67dbc4efc820f0b3d8984f12b7d154c0632a8d7207747284d49c498c79b6bb5c60000000000000000000000000000000004d6765ec6aa8744225c1e652ccddccc91fff7fa8182931c8648b3d8bd33b2177a9af03b2906da02bf117bea59aed3040000000000000000000000000000000006f1d858c4b223552f0aee466cff35d14b3ac6da35b8f482417e8f597514b065be315aec6662ea5c7784d3a9e2184090000000000000000000000000000000000345eaf0d72b9c11fe72261a2fddea318a8dab92a67ccb9438c11e61fd298a333cc42084d4ca127e09792e346cfe0f004a037e7562adfbad6b1ac48b8e4b6f277a788ea2f4416ed2900ed2791f09bc2400000000000000000000000000000000029ad10ed6d6d5bb591771cbd597a3a0b841c2347c89027126bfd1efee2ac403933beb99d08721232ab9b7354fcf9aa800000000000000000000000000000000198400d4e026c2463a07ba5a3974c869ed8ceb1f029bfc7f41b23dd7076cf4a83b17c27ad6506c852cd2cf7c4987f93100000000000000000000000000000000152bbf74cefb77fae8e825443e4ce09b4e223242187f563a236695294d0a5f540f0b29d6f93a54cf0a77900e936e61e000000000000000000000000000000000079f4759eaf044a80417345a1b4029f8d4cfc7e00fc625e815cb7daba2243a97d21e42b42ec968dc8647158fbe467088fa878f6a2e18b88d6badc5b42775e92c17974f3a18817b7769d44ceecac46b89000000000000000000000000000000000cf3148d0c30774104a097562cc83456d5d18643d5f7ad58aedd9327bf8e9450feee50ee893442b1cde87acb02b62045000000000000000000000000000000000011d4037dcc15d0c50337d71816a2b77428b8ddd530bc3b3c8550606229f88286ae94ba03578cbb5bbaf118916dddc90000000000000000000000000000000016160c8ec4e2fb780748aac279bc248b2e2f1092262f86d368d2f06a78ebcd27e929930c8f2be124e9d92dff5c6c6f42000000000000000000000000000000001980375281735390f48ddac9d00d4c6ee7312ed0797333a26a1684e09c9575e57bcecfc4a31b8d9597a8ecc703835e22c4f1a7d2b66e6202c957a649384cb277dbba769afd60708b457613f0f3372515,0000000000000000000000000000000019ff32d2901b7732df1a924eb3c099a9d36bf36cb32ab322f46a72d99d81c7942d0f2193a4aeb55cf079a2cc1707c7aa000000000000000000000000000000000193561d0433e1031fc51829504ca70e92e19bead2e0bad655aaffb6b41f5f75d18f04a162db7714f3f23da891ea91af000000000000000000000000000000000d010c36acbfb38d9dc2df6e6e21bd75deba5708fb1012eab23d06d78b1244d4daae38aa4f803d12441d91adfbaece7a000000000000000000000000000000001459ebfe65c3b2c9b2684042bd71201869db1a0248c740a54fbdafcf18fcdbcc7b677af43abe803362b462369237690c,293920, -0000000000000000000000000000000005b9860b565fc64146020647d1902e2a2d2fb2002b54bf5e21b6601124edf14d6e8836f938843fbd8c02ed8530953dac00000000000000000000000000000000104938181f16f16318d223febec3be3877bc57067fc23729d1f5552099125558cb21ee0eddc32ae0b0cc3555219eedba000000000000000000000000000000000211f809b624c4992a43e78a978ea32accf9e61fccef6bcd05155e52adbf4853340dfacebec9fa87e5417c045da25f9a0000000000000000000000000000000019bfe94a18da9ab4ea744389c17870ac96218d02178bf2ad502f166a3a1da8c14e3fc52038021503cd24042cde8f306d0241da9d8505208b4d3ba2521a42f28df7d06fc212e9e84931cbd30ae3ba2e150000000000000000000000000000000004fbb396eac2a1de9953febed9fb6e158a3b5a366f783d2105b562e8143031d7a1ef039e3fdcdb675b3d3aa4f4dcbe4f00000000000000000000000000000000155e23b5b70f1ce34fc229ad5c8bdfde7fb5dc0eca19596c658c1f8c38716a0a7b5ff59ff19a7a67e12760fa90eeafcd0000000000000000000000000000000002cc82cf87e7ac05be236104c1e668b5573674d9bd741f2d91d05c8a11af1f72aaa1dc20c73953fea38e6e069d2a43de000000000000000000000000000000000a7d1dcab00db0e7c0a239511d630526fb120defcd9453fbb57ee328f974a98721274144e48d22558edf25595b8ff4cc6fecab1334668102e47f1e7139059c1d715a1851a026a1580f93c2daa3f08a270000000000000000000000000000000010c9293b3c58d646a95c620a0e0a7a0a55cf43b4abaa0de1d5570fabca8d97c91afd67bd45aa234273715457da5a2894000000000000000000000000000000001454f8682f3736847cdb3f784a098f7c9e488629efc3820d49b36a2e928bbf736dcb3e1b30187c2c0090fff290dbf97f000000000000000000000000000000000a0fe3c635a81f20258db4f1031589afc8c7fd07f2fe1e5cfc8f3c40d08a958a3dbec537c51be2de99b849e006870b6c000000000000000000000000000000000728876e3fdc42273e8d71953de61dd5c03e7c31ab6ec56fc03cdf55c8f0aa4b4e5c8ed88c23c28568be0d864df026af4e2023c64a3b51cc3d82e262f83260ed4a5e9e3238b85077852fd501b52aceed000000000000000000000000000000000c9ba542189ec1828c397ace9639cf2ebbd1613356d8fb26d3c40dd00af1f43f5bbb25032561aeebba7b874bf39cb0d500000000000000000000000000000000175aa6e94a9e42cf809f48f51c48d60e74d61388dd217b55f3f63612c4565357581e5c39751a65afc3b7488caf5151720000000000000000000000000000000014c880d35d1d31793145803182584a8da003b0ee3c29c978b64bbfe4e1da82910a4539587ba350d393e1bf3169c5e4c70000000000000000000000000000000002a063b3fcd77180de632deca1ba89ec4ceeefefe9883ed9e7e06301a268bdf377c3a6e30859e5a39419e449dd27ebf5dc0a88f0aeb2b082dea6b50d591018330c2276830ed554840c10772403561ed700000000000000000000000000000000069edfb8a83760e09726f6d1c117d4bb3e499084b65e1e830ab30daf1625c37f851ac122f9f5c795912b5b6f7907ffe1000000000000000000000000000000000eb6e9b55869f65ecdf3ac46d0ee596d07c573f88724bcd802b4429392b9a56730a217a03286deb5103f70aba7a9bc46000000000000000000000000000000000e2803e1a646bd70c51806b676591b328cb20359aadc8e79d59e7c31e1ce2f1473b0b19f7a34f23aae09678b11b37432000000000000000000000000000000000b3c9fb5a39a6c40343259e12ff4fe5058f25619d145922e1d80c3f5d105a7495dd9a4da329a2e78afc31a87b2c5d5e2f68c9e76d9d8914f14007c968a31089041e67312c6a3e5d30e65efa55894ba740000000000000000000000000000000019da372143e30307a71c7b96ae0703301ed723814a35e270ef6a6b0c57144f494df1d3fa0ac369f59f3daf534070c9120000000000000000000000000000000006521d89d810c7542108de26bbc888482a3bcec8cb9b542db42d5d4af30d6c339a5b4e959da4f98dd6ef8075554f4017000000000000000000000000000000001387d9c684a0fbf615e7023c0f3ff47f4d2c5a9f748f0261656a09b23066c745420df0eb180c9716d6d0743aae7689a10000000000000000000000000000000014271b9d0b21cc69072333a6c03493321b9d9028149d24964a3773bcbe5045875c457aee11ab0682c2bdd44f098f363d80eb90c6cc25b3a48d93b94b698eff513da37210ba79d22d76a270aa97fd51070000000000000000000000000000000001dd881f3d2063adcc5638b4b3813a30e75fd308de3c9f42e5382fdbf097d5796ee9e03cb44752515b2459f131f58bb90000000000000000000000000000000010f491f4594dab938115343edb47b0087d1cd1bc12ef908e150ecbdb3a54d8dd51ab24a0e10c585f235ed99fbd3172270000000000000000000000000000000019d1665d452ce7fb6bb6da9782a55dcf12a1d9abdfd50435b8f2a1bc5b323c004fad35ff7e9aedfd414a9b68fb1eb1860000000000000000000000000000000013828087beeeb85e43e8540fbdf97af189878f5ddc1eb35c95aa06a26923330f3b8a2b43f835186865d6f5f6afdb2b9b067bfd893b12c79e13659ee9b5f22de71d806a85410c9a23dc43363915a606b100000000000000000000000000000000014964f3576b97c00a8c5f4372e2501944a1e4374a3c30e11376ea62e09d52d40d428887833bc2f06279b859c00c98a60000000000000000000000000000000019ed533a3bf469ce5b3e4e9035af177efe9e4f8b0a0e5dc9721dde49a7fc66fa31c8b1c8d5bcda1bf75a532bd2be356d00000000000000000000000000000000064ec4ed48d63ab62373adb7898bc904d246bf2b3790c3cd850524e50ec38e7fb4a364344a6a1dbd26f2ad2d0fccaa1600000000000000000000000000000000134aa3c6b72d39bedd8f9c619d206a295cbc05c611147d38aa7304e995089ce34ab1fa13c2d6c6807a88797dea20214b34abb11f7ed6d73fb81ce2777acd6bbe8839112c527ef4ad88b094cabdb4742a000000000000000000000000000000000a2a4c8b457d0d2554a2d439fd3b74b18843386aaa00d1b89a1c2d8ff7192cdd1d3a888994376bb7ddda4d16bfcaae3200000000000000000000000000000000155a7dc763caf6f0fe1ba9537c0f75d3e455c2d1c749dbb4aa7242b40a9740fe9e8e88af6017e8f743a9e4c5dc6ebc23000000000000000000000000000000000a693da3aee178e2f0489af77f671c734423032f30c0b7b48debd3b71e65dab7db12ab1e0e72d3ef686d6c1922aebbf700000000000000000000000000000000109c3476016884386d6206c94073c628375a02c8fcd3041e06b8b413508188a1d26ec5ddf84a77d059e9a039dc5470d08d6693acb1eb73f6ed1bb4f74f1062f720a7f2c0ecf2b5a944ff89feb2688e19000000000000000000000000000000000a07f457f5dee69e9ee746dd67f982914a2182b5cb2609d273d4122d57a32c195270c956361d78eb65449cf5e13907ba0000000000000000000000000000000011f149ce84c2a11ff818be3ff0f86c1b38a9555e169a8cf791c79828207b7aa89c84e8012a0c5d8cce4e89d758b90e22000000000000000000000000000000000d636e5b027e41809d7ec8bbbfd4bf641a56599a63a7678569404ec8d45c3b88c1d2969e6101528d4edf1ee9d8e793320000000000000000000000000000000011878eefa5ee49be83ea1f7a9cdcd4997ccc59a9669778b3f006429e1a22d3b2a051924f371a228856523e3a09bab59b29ca1b157e6a2b5b88d7467e851282491ed30382ba217b82ea5cc9ca0c6986930000000000000000000000000000000019e9a1950f663b258474b24c334bd256d3aedcd26dc971a745857bf1fe007da0aa00777db5c3e5d21294e99862bf8ea1000000000000000000000000000000000329a12fa0add36f259e401442bbe6e5f9139e4a46d5d091a2110d2561b5629211a1c1996f20d19327d1782340e7ce4200000000000000000000000000000000032782c94c6e45a88425438324f3a24ebf37f0be213424b1be52c878985633950a022f57f8d64af1470486aa3744f3f7000000000000000000000000000000000631556d52fdcee3529023cf20d46ea09ab3c642a7f4eca2878e4af88801d21b80b829c9aec9e73317252639c148676c40bb53575662fa0b726469da01c39df389efde3936d2eee18d7035704130ad6d0000000000000000000000000000000009eb122c61ec44afb56b64929040058a804311e0e97d3fa513a162748091304233480bbc883f6fb66080b563b308a24a0000000000000000000000000000000007d1d810fb8788b9f0cd04235771d7adbbdf8c6e67e8538b2c6f0f278755cc5e57ad720515ca558412ae1fe2cd40b74d000000000000000000000000000000000955496bdcbca8716245a130fe6eef44d13280b2d56f15bfc772f8ca66a52ca0a742e6bc273c28cfc858a3269f59beab0000000000000000000000000000000000b27aaa0d94633912c96f00ecc021773e5cf5e164e20c7a7222a58b0465e7baec4e67fb56ffe564c7a2904f36c265e61574a30a575138c44881c1c126be214c6b68335d7338875b8a398196f27510d70000000000000000000000000000000012e0572f5c84f6082dd05705a3fae738920ffff840c21e444f0ed002df16394afdc21c249b6f1837389c48719539f4c5000000000000000000000000000000000c26bb3ab52e3bddc219dc223daf472247547544e3a9ccc31123b82000b17ef325148935621edd36ced4e702ade1ee3e000000000000000000000000000000000c13a8f02dc3f209e9abf3d316fa843be9c4dd98ce1ca2edecf757bf2bb498750f6d96c28abd45d9c6cf5b8b6334b63600000000000000000000000000000000157a50d9034024dfc7b0f0db4ea0f45323d76c81bc844844ff9bdd0c13f2059066ec3060210aaba61bc074afd7ccaa286dd51553c4119255b31cb0aaad7391694f7dd29420420b513699747bee819a99000000000000000000000000000000001054edb092a7053eebc542f690e03139f2e25a0098c665741e8711c8a6b9582af47e467f74fff9aeea098b7732be72d400000000000000000000000000000000084f919e219de15e7f9ee122383c772415741e5b86be6ee7d2193a4f6be5c9cc9b2fe5e8beba26cd768bf2ea1b6ebffb0000000000000000000000000000000001822b4e8fae5bddbb36f5c226216471862af238be770d33c4fc1ec2777350db2f42e33a7ba468c317a128e8446ceff300000000000000000000000000000000130f704596ddb28ec6e335d9527707a75c97298407ff3fe17d3cba0cde4c21bfcfd1ae46272018c1db768c036f215182d88f049ab3ee2b01af449abce08ca14ea3b065f06a8665ae3510b4c04f42308200000000000000000000000000000000194dde06f8c54de9ab0ad72ba0de2241fef32fba30fd6f5e83fb7750bc120d51c461d75e495cee0d1e85f0f39aa9d3620000000000000000000000000000000010646496be02c658c82dc68eab86a4f784cf64494bd8441f884e8ff384cbb6ff3a4bf5126bbacaa556aafd652397a8a800000000000000000000000000000000109807bd4b6613acb3eb7d386e84166219e52e841c41185a269cc7cfc5f34e9ef5cd1fea29877749e0cef93a3b44eb1600000000000000000000000000000000020a388c668c9339e7aab15d03108317dea97720dd27a94cd3bb59b372b268d1a7d7d7409780bc4912c3f95acd42a57619d6e227185c538b122858ad5ae594720fa7f743f5805552152a213ebea64aeb00000000000000000000000000000000161506c4a2d57c852fe8c3dce63ec6673f05f99c1e032c8e591239616ef4469c4240482ce5985fdfd4a80f54dfa7024f000000000000000000000000000000000486c5b106393e544852c143c5ac4a882c79870363858b2c910ef4041d8803876cc55ef59cd6a41869bf5247f0db2c0a0000000000000000000000000000000000fe765ebf3c4edd3035c7bedd4aec918426898339d7aa004fd74bbf0e3236deeb7d2bbba56c31fd447816e301100a66000000000000000000000000000000001917c9cf16032e22cdd3f87f098a532a33c9fec560a88f9d4232f96cbe0fe945fbae6bcfdd2095cafe6e0b21071d6ec53f53123f01c4d0d4c18dd72ea87ebb5fcb559df255773fa0165f1432c229deb6,0000000000000000000000000000000015a88bcfa39f444cd66d0d7e15c4040561154c59b832c5ca895f8f8077659487581681cc8f13be136a35b4a573551ad00000000000000000000000000000000009fb6b87eba1edb3d1d23e566977eac68e8f1a28386fdca9d484c7e341c1b210390787418e2f2dff7a228e1cf10962d6000000000000000000000000000000000978de870dcd8d094072897707313b9f1a18d525e60a7cba2b2a395ffcc9d0f97f84e0784df36247d6c98824aaf3ec82000000000000000000000000000000000fbc6832c324d40f104bf82c8cda941212105131c26f630af1d3f7040ef43c6eb4486766b75a81433e46966f79953647,293920, -0000000000000000000000000000000007517a941bec38d0e84d21508d8bdd6778a853d9fb4c5e953bcfe3c8fba3732ca0b7f6cb5c363f33d7718b1b1a68f8e800000000000000000000000000000000150c0d975481422ddec2a58a55b3d917b6b7c0510e75442c81ee856e267d7efa09641c6b79fb9e699c6b473cccde7f4d0000000000000000000000000000000009d37bf938ac30fae1cb3ffaa971ff3746ee4090d4bf8b11dff7710b3f2e4cc686813890e03643fd56fc99e847ae5e940000000000000000000000000000000010fabc4048e0fadad73d0481e290c81884f4578cfb66e0a83324739652ccf273b62204f126696a2fc6469ede39e00a9fcdf2bbbad52a3f5c0b3959d46a8c42f6f5250a93c3dcc636712a4a2baed289c900000000000000000000000000000000089b167ce7fa997ca0ad3f8bbbb358824cb41f525bf60352d5df99402af62cc6d768113d2c1ebc7fe8190c5f732fbfff0000000000000000000000000000000013dcd35865e27bf98f1f6508b32c7e9a989d528df0626228087bda0d8b456af3ea2f4be6732edf1bd8cfd0ab9576197a000000000000000000000000000000000333b0612d7068986d21e1cb67a1c7af423e98cb14aace2ce02f84d32a38f97bcdac465f2b22e5fafa6aaf0d40380e4a0000000000000000000000000000000010de7ba4f50b6654fdecc4fc6c41289bec50cff1be18be9d5c9d1f906ae843189bb43f144aad4d2a2cdebbc2697c974918adf5d8fbdf81f8e4bf2f622e2d481fb2dea06a1aaa289ce4f6e783eb9a98dd000000000000000000000000000000000fdddfceb29fd79c31b138ae8e41507f324abd5e3750da14f4f86176126a06380d53dd5f7efd00e7f94bc1370ac9816a000000000000000000000000000000000d8371c602e393a4be250583c299d069270a344953f7f07a5fe27f8617cbd3ebc91f423dc176b272339bb3bd8a9a348200000000000000000000000000000000193a260a417c9c46da0aaf139e3bbfbaa9f248943048396d95716b3be0b8a148a3f0ebcf7d6f9a318b16d2d850ec2f5c000000000000000000000000000000000be4d0f2bf6d746b930034eea8a19d73377617645c29153b6ae6d3ca6fb35a704b6a0bb658282cf93555c998f6fd054a650e995b73b63d068fd54f596cd9669fc3061f0c1da77ae7d0f5bec012f9ba69000000000000000000000000000000000731c0a5d076d6addb15c1e5d3143d41371f4835d77756418bee452d2f03b1e603230c59f87905fb67d5eccce65a45d20000000000000000000000000000000013bd198c023009190c65686468523eccb57c5fe7b159a1c5ba30c662a275fb24d69338ec9c023ee6a10a8ec9dc7968210000000000000000000000000000000018fb369923ee655839c7d996e264133c49f102978f18261faf2f8eef376eb0bdcb5af375ada2bc783e50df16737f78dc0000000000000000000000000000000009ab8e16e1d0b406adbb37e950bc3820ec13c882ec4483528ebac836726ba202bcf796e84abb3c16dbf6d1131b3854cc3350d4f13e25052b1162dad3ace76e5cda71680fdc89460d8fa88c0d157df426000000000000000000000000000000001401029d7cf8e7d2690a27c01b32008e273b5a33842dcf52d84f77dfa4b2a1fb290f56eb4ccddbb420b27e06a7a3a3b4000000000000000000000000000000000c464c6fdba702f2fbf4232b34d615e66dbb5bdf80233f695e9103272111a06a79f8972d1034176859d0e29400f5a9c10000000000000000000000000000000006cc97a29f4e694f0cdbb099278fa94140b40147f4f911de96a762f2bd28233598a892899a6329cc3cb854b56076787a000000000000000000000000000000000625811cad7c740758388f330c4a56ef30429ea4cdb9a00e2cd1b7f310184a2e6ba36ebdca57c87cebd5232f52c34d92283f0256766690c88df6cf7c968b9a96121d26d19672ce9adc84b699476a32db000000000000000000000000000000000d0a16b5d48eb062c71b91d74a0d25eca0d4bd7082de25199f33a9d3d598d137fbee2ac36e8f877c157be7438ebabd74000000000000000000000000000000000bf93533bf677050d9a77a5dbdbf7cf084b5d934d55318256712ec361693738d48ef27536476fdc93dd8e81f13d67a8e000000000000000000000000000000000696fbd8841e60300602aa5528391aa8b196d8c186d6124c842a0124a8d8dcbba637502f330c980b2f5a900be8e04d020000000000000000000000000000000017b0c51e699d2252f35619520af71775f9dd8c57c2ef146adeb72640bec2ca02a59680153e5c9f66bd513bd8559b9d66145cdeae7fd3f7455dfd2ea9a064c135f0a0a36990ea34929e292e4cdfa0f472000000000000000000000000000000000eee94b5148ccbb3642e582cf0a517b72e6ea019676a13b1484982de7f4be0346b7ed22979ba7303f6367294a3eb2716000000000000000000000000000000001502bb3964f6b3e862279e15fb105073e876c4e48c55c42f3737dc9efed82b10fe8e39438ccd39c933f5ac3c6768497e0000000000000000000000000000000016cd8c4b3be55474aef7081cb969b75ef5e7cca9bd0f9627928fe9931c6f869a9a49d0ae2cfb8346116eb3ced25d4a8e0000000000000000000000000000000012456eceaf32cbb6514e6211136475a750889caea18ff4f9d5ed7b378e6d1d265721a646715aee6b9f2098e954a88289d9cdaa979ab08b454dcb961e3cc4bb18f706bed93a72a9c1e105cd69c0b691af0000000000000000000000000000000007b5633f4a7dfe62f11065d44581f5060210f8e572d960eb85ffd0a903d8b989ce10449fc90b7e5646784a9f6df28699000000000000000000000000000000001710f252cb35d88f6bd151ed596f2d6455f050c5e25add394dbaf60fc036016ae07a5a8ed494b95875c02df3c523186a000000000000000000000000000000000bee19779dc6430ebee993f82a054fbd42e5b7265090017e5b2d2f1469bc96a5a188adf471d576a416f6a841081043df00000000000000000000000000000000038f9fb4159e4e6f596a17ddf45a00a9e4aede63b062af5eda045efacd3977e8dfd61c307834c08bb4c284638696e92ef262f9f7a26353193939bfbbdc50ee35614a3c099776f08b21e0c4c97521eb8e00000000000000000000000000000000197687895f22c4a639bcf2f494dd9e5a034610b0297528235f1d806cf032f5a86c5248a83ed6b12f0de27f5c6e6f49420000000000000000000000000000000011ccd5dd6d6ce553ade9b31503a9e6a6119ae329178706f051581e3cf0ee9d6fb527b340bab8c79fad1cd451c7edb4330000000000000000000000000000000011e9f051aacd69c8bfd2f0ecb566e6d38eabc43f276ba7a1b8e8ab093917dd1c672c61d6dac4651026823b9938d3601f000000000000000000000000000000001362c3b2e6fd9b3618df26ed28f96530c1915f0a4ecb647658d1ae4ccf4c000f3bd1797696c9ac5c5000dbe58dba8de44f0d2915e82c9a69f9e9af64a2c5cacf61ead492bf69912a35ad6a316f9914a8000000000000000000000000000000001819d13cf4522a9362bbeb0bbbb0a498c3f34da1c9e3b2c54d08f7c8acd9ee756983fe80405579effb79d673407390ef000000000000000000000000000000000f870e5978f4a6e3b655fb2a05541ac0673e7b10136adaf28be4dfc9022d4cc8a60e17d125dfe53fbe10c644ff37e02a0000000000000000000000000000000010207ef774cddd10db2bca0a051ceb12900c407ee265dea4615553c193d7475b5ba3198b7e0160740e4fd015dca33e1d0000000000000000000000000000000017937be546e06fd2eab4c969a029534c02fb770646d43edeb5e6c8bc0c2b5f35576c375bf860fd1087ce099d4377d24e25ed3f13198b69604c08b414562f67a67aa8dd4a7bd3c066874182d21ed9004d000000000000000000000000000000000db02fcda340fb27a3fd7da468c5cbed9c8dce8471843a8ddadae43dbec9957a0479aa52855d7a6dca99e7922432365c00000000000000000000000000000000163503d24f9af34058cb5afd8e9d5aaf29e141c8521eaac282f138466e834f0daa9ce14e0590b501680d5b47f866aa8c000000000000000000000000000000000fc9175e6d20afd9d194907f2eb311bf8134aeb96da72f6423610612f2ed20a074c113fe8bb632d9ad74b2f6e7e2417d000000000000000000000000000000000b4621f5e4465629648b62b7f2b77afe6470f9706f9bee5b3ccfa66c596842cbae26badc689f7f623360cb7fc1d416b84ae188cc115e9d492be64abefa4bd4c93b61dd42a7d213e1100b8108611a61630000000000000000000000000000000003c77c7efdab9a9e71283b034ef581a31faee417febfa99be3c18e8ab724c140be684ce719bc5a9ac5d3855ddbf3651a0000000000000000000000000000000011889b02b4a1150fc2b7191a95c5ee767f3c9b82a3a53591018242fa8685ee3b3542526dfdc00695a6cf046033b8eb760000000000000000000000000000000016d7463159c4e3cb635f24bfb944bc518369e894218bc49d7b7f0ea99240259f7ee2b4c26c6083dbc4559ffcfbd392bd0000000000000000000000000000000010a85df6294fd6406ca651f15494153e9802f0068bfa149e87fe4b1cc3071ba74940a21dfd55a8a77e7e2a193468a3d2eede725a693277356ce71ffd7814a77fcc30eeb3a2b8863fb91ca03da1cbe37a0000000000000000000000000000000016beec57d3049c382fc039ac96b890412c5e8075afcab599fb877f8639747a587e82241d9a8059a0bb45ad49959777d0000000000000000000000000000000000a70fba1b061dcf587f133035a3aaafcdace3b1e771d71887ae914919e5f52a99d9933307ec15b5f0a1623b9592824500000000000000000000000000000000005064161136c04f9f50e42a5cee5dce3fa0ce1dc0655b3785a852cb9741927f6c9b357ac1010d7212533d1593c83dba70000000000000000000000000000000000d50b992bc0eee37a15cfd32eda2c591fc4c4203ef84232d1a1e7a9888005bc00755d76b9d0345bb01ffa7525f2aa1e9d0618f898594b23ee3754fe390d6bdfa7d47fe749d6819e306935c1eab6b0460000000000000000000000000000000007617e60d8f67344ce6d2fb65cfd5b423a1fd091626da837dc8a51d6ffdeda9712864e8f30e45ae8df917e0e4625e59a00000000000000000000000000000000077c4aad14f870ba24703397ff0b33af2e50b026f3e0f13f3ec1aebc9ea3af98cc65ab56cce4045538ae6e5f410196f10000000000000000000000000000000004a31d0eff18afa87f9a53098cfd5d21e913c7519cb171f83d0b73abbf3e893a3ccd5aebb9f2bbdd3b0eb0326d37fd1b000000000000000000000000000000000393052e6dff65e01e79254af757f12eb1931e0b386f8cf0fa0782269f962ebc5d9bde46f5a4ad3806e88330aca59ff01e1c9420cfa91026286d7f986b538c13e8c97893995485308c69f053927f962200000000000000000000000000000000033aa108d252e9107f29cc7da79585d4525ff2a35d31479a099c7c011a9c4414d7bc5f8498f8a204134b2d14c5fcde5100000000000000000000000000000000121214465992bdefb970d420face6db75d531e67314a021d2877643ddf738fbe57625d286bde7f40efc1d329a2e85b6e0000000000000000000000000000000017e14f6cdd916b1fc949be8ba3ef9ae6cc16d64da4dd498b5458ea0c14eb7aab8f970f030aab26397110331da11a232d000000000000000000000000000000000c56ccda2a5cca61025253407e72967c767f0e7f2aa0b97d4e4a09420dcb882ff35039ae504a9c62b3f9e7bb0c2e7bbbe5095ed9a9181aee392888e3194ebf9c4a6d87b503f4668bb6cc0d290880a44f0000000000000000000000000000000010fb3396b0674b9285cc5d5a4e7a41ac002f2b43332c20a56f428d1e19e1d1bb6f886d3bf03f7b0fc509e52d75965e15000000000000000000000000000000001196b7c253c50da10815bdfd7930a69608187fc3ac5fbcfeb35b95754d3017a094afcdaea867c2f08346717dfce7bce8000000000000000000000000000000001021f178c53b7d7d2041a6419203d12ee162f27999dd8f79baa15c37a7401e7a6df6aa4192a310cc1a23bdb0b427d63c000000000000000000000000000000000953c75910165f11112583476574f3987495d33e5b1a5c650a2b30692592a442d9de36da49255b0c01a7bacaecc9b81adcece8ee33d3bf3188574e94a889794077035ee92473b7266d92a3c018771b4c,0000000000000000000000000000000014da1d424c936453600a4acbd3666c6188493d4da8b34d6bc508aab07e59e3680a9e3488e69d42a724c9486d70ed4fd000000000000000000000000000000000048c637348fb9a4c631a82ded1fa08d693cfa2cdd6cdffb8bffee63d1bb2ee8676512a1a8d375e7ab942b6d6bdda45c80000000000000000000000000000000000443264e7dfca91f17251c33cf72c56b045902b4db2eb10d1fd856f79b4130afa6f29f3283af7d3b8b2a9d8dd63718a000000000000000000000000000000000fb386f875190ac7a49d4742edb387f72c1ae0366ca5c71d5b7e385c11442941ce0fb9fe2014fc624fe93ab86ebc7aff,293920, -0000000000000000000000000000000016a539a21320574fc25ffbc0ff10c821d6ad20674413eaeda6f4a31f9a028e21cbb3b224c225a2e3bc3dc221cec084cf00000000000000000000000000000000104e44989e2fba9ddce8e309f5d3fa3129f679d6456ed11137149b50adf8b22c1a148d47154450853e6797aba2b006850000000000000000000000000000000008b33b8cfc992efdf7d733803a6d08a4102e27fc4960ebe6ebdb7949c4ff5af76e55002d93a4f7204eff5f2dc4e37ef10000000000000000000000000000000017c35411c571c302c746a9b79cae892e988d50b4660564660de960ee09b3937b6f5b61fe37d09f1c02528f554210744aaddc845ad867f1e2664ef0e78bced8ff6904c5836e7c63ea3a9c858fd7b710b60000000000000000000000000000000009cd32594094d4744f59690cf8d7fd260b5ffb2a22945d938c035151861507ecaac9ea553e7b44fc4b3beb03b33783540000000000000000000000000000000006f4de33731b9b13b9cb395798769e54a0679d272c2d5175455e10c790debabae4ee02b6df08975efe806da9c4a208b20000000000000000000000000000000011859798a8383b7f994a1535bc0a96a114b90644d19921f0eec774ed58dbaa899dd3736cd1f4a4ff9bfacbc7370091d7000000000000000000000000000000000376c25b0f70427d4974c4fd1539d40996b6847fbb67822fa01cfd541cd3a3f8a9f3fe9f7ddcc3ce920a6ecb27dafca0c78cfc6a30cea34d3d3505f4f897631f67ba77913734f6c2895d871fd6d5581c0000000000000000000000000000000003a178f91a135d59dbd65eacebab293a3817d30e734c247f56a08812aa540a5c80e3f9908d86ad787bab27fbddd21517000000000000000000000000000000000672b3544dd2b91a626f37dbb389aff073777164e3e20dc572b18a2e5223bd323094e41bdbe2dec9bada227efb37dd22000000000000000000000000000000000f40f2d279c66f22bf0fedd129e02c96d8906f9f1ec19f5a5c1cbd5beb10942a066dd391b69920a0a697138f627a1b180000000000000000000000000000000016ef3caad858d323b752e5c437ee2043c8f691ca0f1862e80857f7cc478a689df97bde5b1d1350892c1adb03c5d2373ba1e40df9e1f7c84633cb3dc2223296887de7281ea66c5e1f2d5816334f7b280a000000000000000000000000000000001276e133fc5e708a3265646ef0a0122048ef95d7fb46f78b8dca57dabae0164ca986bdc74e581604ff31165f9f28dca50000000000000000000000000000000008a77611be0502d2ea7fbcf73774fbaec68eba36038e2f34f79caf07f2e4b7444efc49a4e85f88af585fd28a041f26c800000000000000000000000000000000181ab176e391190b1cae2e9b4105ca14cc82d15890b0ec127d8cdb46f30b704a089ac69e76f5b50575ed66176950e1120000000000000000000000000000000004031ce77fe9ee319b8db8f220ef4480c81568b3f6e4043c8710b559d25ad69dd38dda48b2e11d5aead18db0d1cc09b98810b9ce0020904dc1903338089c30e616ed0be03741572ce46946883874f4ea000000000000000000000000000000000f26e6d71e206c88dc81b8b8a5c05ee84a9f185e7b7f155253aa39104b5de5be7bb6cb6662df4f8e63b37fd1682721f20000000000000000000000000000000010058d13637c8da2e91c8cda7dc2cf1734a2f14b12b798e5c563ef9ef3624255a6e1c7550c37b547c35c55dc736a17ce0000000000000000000000000000000019ed470bd514f8bda8fdcd9c64f7626efdde0102907bd31551b1d1972aa14e1d361e1d58b17948909a669fa4d99cf3200000000000000000000000000000000013277afe1891807e269c22c9aa1598c12081809d888e0eb2513ca3f81308700893f74f176858ceed9c7955dcc0d8fc6893e7702da2ff9f3f14586a9ae80c8713743d61b915a7c379c1faa1b151406a9a00000000000000000000000000000000083664daa965c4173d6028e047794703a16e52ae459d3db0534d13c72d749d603edd668b9ce500677715e45216367c63000000000000000000000000000000000f4e87a65f4720cbfde7868eaadb34ec1916925ffd84e5407defbda0c39e1c7afcbc90855b275d528e7b63fd3707bd4a0000000000000000000000000000000004c9f689abe0d2dd3d927bad4b39ab44f6704014ef9a1dcd1966777129e1c72515b43c1b92ee60e9611245454683588b000000000000000000000000000000000ecc57b08b45037e62498135643cf077f01d216b5106551daab391446ce7bb37d40f41378c830081bb6a326f0105c2c4eca54e365faa35d2c9be259b53a1157b180a373813382f47c9154af18a2d83270000000000000000000000000000000012b84341bbad1eaf7fc8ebe56f67598821017365b6f3b4cc1f2355f868e8d55f9c0bed2943ada202a7d85cc884d8e6a20000000000000000000000000000000017693721988f73d77f7a41db108e428b0ba781ea88eab463693ec352cc13d394101b9a2792e0f30c77bebaa395a4776700000000000000000000000000000000093245e2919523cd57a0abd2e8a9c5cbe774bee957f26d3cb502b9c8c06483b850b031461dc2cb033d399651724f4fe4000000000000000000000000000000001530f7dbf6a0fbdc8b4f7a4d298b7824c15035428cb8df834907e25c64b8985186bb13f397b7b99ea7014ae65c428b12abe2079ecb3618de3accdf291d9479bec32bca1f9fe87b00b64a12d735f5b9a5000000000000000000000000000000000f323f01f2a63bc6eb1b565594ded14043c4ea5d1f0fbf20f39299052617c334e6126afd4273738aeb153c3561348b8a000000000000000000000000000000001525d1e1fa65f1b674feef74f6c81c82c3eeb709e597aedabbfc2b3262271b31d93818613ecdeb49c5d3a6a64f17a5d90000000000000000000000000000000010458c15bf46947a237dd1c61882b1561121f64890681bae5db6fbd24ef6c34b7fcb826eeee1fa328d9ef4d859faf238000000000000000000000000000000000e1f29275fe1805d02e069082d5e9a7acf69be17013e6c4c351277408d49383fe06f00137e777ba4aa49c29c25c6c0ddc541a44756ebda14aea95f1a1d05e7366dc0285305116b907fc89e777ce45f79000000000000000000000000000000000efb7373e11694b966d0182a9b01d1e52ec1e89cb18275921294e2d36333460b1e49fd420f1ab781b000d1491ccb0b11000000000000000000000000000000000cafcdc2c58fb3fad713ce1a38deadba8636c384243f9971e3930b961efaf303cac4eef1e8e4662636ff91eff1bf52a80000000000000000000000000000000007ea7441e1b2b0f1e42bd511c060b646c2d00bb3e6507beb5d17ab93ff68515b02f82c2dd43ce035ff660ddb0c104a77000000000000000000000000000000000bd04b88caf9dbd0ef5f89d12e72aa47d64212332b0ed871b7eb96b16295cf4810f6f20cc85fd4d1ce72119f80697c1b37d521d31de52681f1d9bbf64a12f9bc8fe0ac61aaef14d7e8d048ff09e6578b000000000000000000000000000000000c3d2d978e23a690e8422fd54f36fbee1f642611b6c3b2c2413844066159bdcd3703d1a392b030446af04b654f8f73b7000000000000000000000000000000000ae652fcdbd8e467ee9b447e61fcb811f8b6aa48840476c92daec3285785a06a81c1705fc2896c0843ab48eb92555b9300000000000000000000000000000000007088e6441cb85aeffcb4a9a0c81ebfc54a61f35c542be3870c2bb94d7081353322d4745747b0dfc3e5db07f9e48c560000000000000000000000000000000006c11f3e0941ea3bde0dd3a562dbbdad433f0b1e99ba34879e86f7951ddfb29b9e04ca62d54d7552a74e8cf1c3da3e704904a876d4ac1341e88fc4808508c89c19dd74aa8fb1dd7673cbc2d28e7d920e000000000000000000000000000000000c665f4417d0163820ac96c83cc2f09b1b3c000023d827e2690aad7357ff59e278832b992703f5f0016051ce0a4510cb0000000000000000000000000000000012f4b6688300b253fe868b3790f6d2f4fc16d81a49ff7a2edf821de16dc992d79482d66e443e0abb5da43df69f8d648d0000000000000000000000000000000009e033750a118d998b136cd671d0e760e3a617f1d6a994db8f6dfc391619f408720cc57fe550785306184b0c824705620000000000000000000000000000000018cbacd471e528535e22f714a841f110fb0484826e30f97842d65072b2790dadf0bd7b28df96bec531fbed1f3f93486b68911b04d8155f90c7c5c0cb519ee6ff14c0ae27ece0374f30fa148235e8cb49000000000000000000000000000000000c42b6fd52cc52034b04078a6565af2b43948695851393596e05f37f297dfaaea931a33f5b4c25980c093f8a742c0020000000000000000000000000000000000fdc7aa20e63743dd6ab32c82d2d6992b29779ec06eebd452c17d844159e90a7f3221f3e0e6b5805dc0f42dc3836d90f0000000000000000000000000000000003a2342a1bd528d701c2a6c72708a16df632f4e4b6cdb3ccc224b58b57af30b44556cc968ba3c0396a5e3f11568a73710000000000000000000000000000000019ccf76462668905c5687b7612a0bdfd4aac70f291d8b772e84fd5d4bcb591556317426471242fb5f44fd695c7d49279481e894ecd52a252cc76547513e2cf0a5cc6b20c3dc9c64c7f34f29a488258ef000000000000000000000000000000000c8fd4a171c5fbf584f567a1c10b20628e7e0d5d796eac4a9dd2376f8d488da25b9219c7c70709999b5553f8bba915ae0000000000000000000000000000000005d791c907984f2aaebf903a0ace52147745295f0c5e85964999a8fc74b64c8871dce358f26ed1b4af6c6f7f18e8f4c500000000000000000000000000000000110a453bbba72ac171876e0f6b4acd5b178816301e02586a143c2bcbfffcdbf593655408b9aaa4141b2a210599f452ed000000000000000000000000000000001025d5065f9801fcc1c1ebebdf67923b967ce985b5ca27ab5db8af7057fda23561a46b84fac5e793dd9af692c4d56cde72780ab3c48c8a102469799ba2f70d2fd9d324cf558a8c8b49e2ecdb71ae1c9b00000000000000000000000000000000023e5ea1909032676cdb79111a33da7ed788d2affbf4029b932eed843268f355dc92905db283d6617fbb530da3d704dd000000000000000000000000000000000b46f07de520aa17d597586cb0a6894a356757941ff9bdc2976f620e1bf1eec1dd9801d6baa2d7efbb3cc7073412ce8e0000000000000000000000000000000010022940611f418de9f9210b1be919d7506aca468fe5853675fe159d3e58685bcff6cbc2c1cb9e7d45a7bf305fca0eaf000000000000000000000000000000001888b5b0dd1648d9a27345f570a1278238957de1bd30c195d554750ea4b119e98b3989b912c4fad531de416c1533467f84ae1de8aaf498bd2d91bd828bc64e56482b225322b86529da703f47289c65670000000000000000000000000000000011dcc334a5037719256e514b2c3b0f36396d8cedcd77f33545842c686fa0f35558c397562a7e245f8cc412c776a2b3930000000000000000000000000000000006efd32c6afc56a07c813fe19e71f0248666c87e1df7e79b7afbd70178929e5660e85cea35d1c6f42b4c627a94ae0d150000000000000000000000000000000005a5fc2010798c793c1b407a577da0bf0e04b0478f19b7d0cfeff8e4e4fe2d581461831db165cfd17146c49a732c41460000000000000000000000000000000011dfe3b62eb87b039113152af74ae74137cba1762d4ae62d3cb0746272d1c42d3cb4a8fccd845a519fd0650a23a897a13256548db55ee9de70ebf6fa347d81bc50494b937ab1c3079977234a34cbfcfd00000000000000000000000000000000110e73e44734b7ab63f021727b75e735702f1acfa6669e0dc27111794ebee371734764bb165132af3a7e02f3605456480000000000000000000000000000000005fbcac7c7334cd0e6468feedebe077b80390833eaa4c28af80d29e75d692a10cf13058526fa5e5ab0fb635335ac8f220000000000000000000000000000000013f537ecc28685aba2cd60d0e3e787bc8104a3373177cb93107b63d39919c583ad3ad7a42e322249d7605ef035fe1af40000000000000000000000000000000014791f94aff42bfca13ab328a3e47b06f7da52e13436ad477cf55e53b54108d3aa531f0a5d73ae5ed7108d5cca1ecf7a575ae146524544307ee51e58a654d7324983a87e1b37d46cea1a4ec34114b44b,000000000000000000000000000000000bab02defb32b7938372d656feaebfb5431de1484361542c02519d20c6a500f0b0b112c331fe6f4eac3ec7f6ae4167e50000000000000000000000000000000000796b38c67df1361115bbf3a4afad2651664ef55b1ed02d3172f024f90a003fc3631753d7142aafffc64c6f6f57bf7800000000000000000000000000000000080d91637a93a9025e8691a400254af37cfde67eff7d3037d428596a808a01d9bda8025b7246fb00785cd1068b2752d400000000000000000000000000000000182a97624249f0c6d24672f04e2c93eff63fbe76cc11ace0f7193facd0655cc1e1ccb2d89d9547bc352a395efeb95afb,293920, -00000000000000000000000000000000115b14c4eb9cc78eafedd2072be4555a3db9e61b5fe0139bf3e40a92cc37b4936c68576fee5692a80e4a9aef05a9b7a80000000000000000000000000000000019c828ea555a3c8d28cf0981e98609361b5bafa8b62e860d121c0f6d0f0dcef544784e8a5fa6a9f1d1a68b30e8e8a6f8000000000000000000000000000000000a2ef5146d2658609fd4eb98fcb5d42f6c6aac4fe53597128bbba3ba3539042ee5824f381c41dc76e2c6e4dbe0665657000000000000000000000000000000001807a12ae5f289ecde8ca0a913647d44209b13fae9dd6aa8fb4365a3beeb81652ec17cf92f6784c9ca5a077824ff6dc31129275f3ab3125db33d43b36c7de0ad60a6e9cb4457aa03275caea9635f0b0700000000000000000000000000000000186bfd109ea2369818ae2f466953eddfa763960caeee9d6f1ecbf6a3f854163342c26b56d3844bfedd8f227070f75546000000000000000000000000000000001877077daa2ea074b2868e86faf14efc6ed35a64161a77aec54624d9cb916c45de81b40acc3797c6e3338fcd7a42bb0d00000000000000000000000000000000054be1650d9bb6cae6a1ed08879668e4aa4cd139c8b07ce21d40fb1cf37f11de730ff13814a02d2d6d6df5eec4afe8470000000000000000000000000000000001612b5b7c613cb66d4134aa867d985682f6a544147e7865732887d4fbb191a9f5bdc27bfbefd397f38cb101a2d68b192dbcfd8680258eee451db60f3f42f02f388f87440d00badb0a725964042515c90000000000000000000000000000000015e2b23aa42f1e6a07b0a31dd4acc27e35ce1fb3333c3f330f2d88f112375cf24e6dd5afc4d245531e4e84f1f82230ea00000000000000000000000000000000193649f3b7efb346e0c1f7bc05b0910311270cd44b5803fa16e06655d6239f609363344bb7c16c2105e20709fb5ff0400000000000000000000000000000000002a6ee30841f471dd2ef13888ba03c9cb93c85cfd0f1d0a3927205e3f57fe291bba7eccbd2352f25cc4047097fbb63860000000000000000000000000000000005482d4a47d6e381f755c4756a761f0310d0d981523afcf288e47a1a643d6be62ac6410521e0f25828f469af6150f41e5a6f194abeb6b7c1c561aa820bba658f0277870e2a32f972f9d18ca361929b01000000000000000000000000000000000178ca460993d503e496633fe5230d895bfcdd0696d817a23ae94e529bb145a0861e4448d3bd48c55907be762192a8c4000000000000000000000000000000000015d2db77105a8ed6eadc05d3d1f26a54b3d1b812a58ab49a889f5b7fcf5ec08c2eea6ad09484fda29cc007037a1f6c000000000000000000000000000000000fd5628d61cd0835fe49fcbd2f17058f23d0ffaca4474923a2c0706d9333d9881125efa2fda56234a82158da3eddb5b70000000000000000000000000000000010ce4a0bcada5f92cc8898dbf5c108c0897322eb6a467662be569d9ed0f6e2c808e214b83969ebb86c84d38f67d20754579450b7aa155a3ab61e47e337ddbcd17b197de2dbb76008cfaa09d3fc806be4000000000000000000000000000000000fc4ed0ca43d5cb172deef02704579187a480cc977737070b8ed2dce48d3f3141619f37e985c220a2840ac01dc5667f900000000000000000000000000000000047a15e96760affa4e537a45aefaaab1e0e18052f63514a9f6544136c87b7cb4a5c8dfc0d9544518adc7932ce9cff5f1000000000000000000000000000000000c9be55c06f81e87de58a5c1df8d16174cf4115f81091937d98dad6c4780a9b8dae1081f8961fadc4f994ef62927664700000000000000000000000000000000165f9b1a8f23831a91be8077b18563c7648e54caf30895983cc26580241ca7c86b9b30408a9b27776286ed9f07bd8ecd4be94f96ec4a3d4e028644c63b2577a9ef849b403acc55e42432c3063a918d160000000000000000000000000000000006edc0d62ec31b14e87b2ccff3a21a7c8d38c3ba0ec48bbb8df27fb1acf58e1a87c4458dc2b770172460adfb9a9bd50b000000000000000000000000000000000ae80063df8d41d45fc43f3aa0881364ab5fcb9ac526ee22d3870f2edb0aa379e9d81780b0ab08a4cf308d819338deee000000000000000000000000000000000e0898453feebb51d9a1cd2bda36a307ff2eebf44dc8f4c694831218c42b51f723ffe521b356ad4e5f0dbbca9af9ab47000000000000000000000000000000000fd186dbc046dd02217cec3c7894972f71e5f00e00a40fb1521659a33e079b7a1f60b026d9055a50ae18aae5757ab8490983e6618e9e4208cfbaf647592e42b2d30de9e74e4943fb2bb49109a66302aa0000000000000000000000000000000012134b433877e0a7858e6c3b95b2a1dcfb0548b290b68c209642dadf550db1c636598ac43d101b13c2d8d5ed9602a73800000000000000000000000000000000102b5de123c449a078f6f06935c9537efc791ed8e5475ffc2d9e1d098c814abe56d4b7fc6501c315edf7e64a431c5183000000000000000000000000000000000bce703ba78f45a1c59c69429d3dda18243ba2413c5eab46d469f504da975c434eda451c85357738d6c7054755d5cec1000000000000000000000000000000000387724937bfd817a65c0e0411678cdc78df26ebd4a814d92b023710558701163349b56b80e6bb68a4401f2662a0525506615e300a924ab962e0b7fd0b044cae9516d96de603ee80695718c27d7fba0c0000000000000000000000000000000005abed9305bb79a0ef1cc70e7fc2eae35a8580cd3d1ffad73d3bdae541ad546b8f74b4ca76f8f374e31dbdaf1bd14be9000000000000000000000000000000000199b29da8d161ab3061a18debc8b7400415caf029ced47131e27d81a0f7f79b6ae5e570f34a4c74fb85fea1411bd6ea000000000000000000000000000000000b8a7c42f5289d20b1a55a42d53d49510d3871b6efbe560bb4d87029b85b930f787c3a42e225006ad62c68d5f96c2f8a000000000000000000000000000000000e74aad2b29a210cc316181863e71a1dce8866a088a072ad5972af57b813a2e968a5b16b294273acd6e81e9a5be2961dd77d3e9e64e00b9356cceb62209ad48fc89e69e2214aad2edeba1812272736390000000000000000000000000000000001587e32753adc85c98cf1322115772b0e282ef4e6a75944fc86091e81aad076508e3d727f4df0e30924fff6b67c312e000000000000000000000000000000000ae96d3a1b79985e56f80df8ac4d9792229ca580b156dbbe71a9db470447fa4dfa19fc8a8a2e2f0fae28a24b7d6153d100000000000000000000000000000000114101ad0d29ddfd2fc436d2a270711c444c8c257785f4b4c549e9c795f6dd9834d3744995d2188c0c968752a7f68892000000000000000000000000000000000d30d9cc1e2273af745dd47a596a2202ca4fb655f9f9beeb0a87631e2461f29206163fd921761fde69654cb02e23505c41f75c89ec973f65b11786e186f4d42ee2e85c40f29745d9f428af08a39d5681000000000000000000000000000000001611787ba658b64467b4a28e55ea24a3b230836af6c2a7072231045ee4ee38e02302a62688d6f988f76cb5e50eba40080000000000000000000000000000000008badcd59d6d30f26ca674753ae9257a853dbcf49a5641999634a9a35a97096b6096b7b058360bec2f9476a51eb0d781000000000000000000000000000000000d30154440d8bb5fa6538953a96ba404658817be8047fa7a3a86493f02399543220758e649948b804f2daf84fb86f7580000000000000000000000000000000014fbdd62f761fa675e4cbcb61083a910bcfbd1f8e37f1fb1915f60929b047c970b87be0730ddc20f9716ed8c9bea7f19c70cfb76a04d1a9e0d937292e5553ef371e20d5d3dd33611edc0da178e2e4a1600000000000000000000000000000000143d1f811644e3a51c735b708cb2f8a2a90311f9971c90b9ec8e45bdd6488638b6851dbc882205263887b4dd5dfb4e120000000000000000000000000000000015692a6b06e3bd3100e149c6be3cbf1566fb24531eb29036fc48f85d5da83316a38af4e714a17552024c1ca4a5e39d9d00000000000000000000000000000000172b9c88ed9a1fc2d5a7f147d034fa243d420b129343ff92b79bc4d836e380e5a7e388069e9af9026485e9d3f41a7aa300000000000000000000000000000000012e8453dc64f72653c4e9b3f6f43fdd01b896c642d21604f992dc5591f2cadf71de4099e1075a4ca4b7539f84dd5a908db878b7f5fe817599add432ecf262f19d80ac834bb0a0f983728f6e2c189c880000000000000000000000000000000003e9b6d23809781f50c0033e53d245dfebbba9e0c4d9f676ae61b80fb6e774509f62fad854fd9ea841d9905d48d943a30000000000000000000000000000000016a1ba62bc684bb1848b0ccba59597b19973b56fd9b1d9d06352de44aa79c6bf65409dafb54f859d4a7c32e188bbe19d000000000000000000000000000000000e782741a4b16c5838a8f6e542135221ab3c6ad180c85c08742992ddf0239388e273735eae76c656e61614da386ce2640000000000000000000000000000000001cf6752e88990c221af94e18744790c30aa6a158b10a1f6a56c2ee3c3f0fdb2fa7213f16764ac9e9f4f65e99e715ca170751fe88ad289c91dfcd3c3c61ce1e33f4146f03fc0dc77cde9b32b51c75fc0000000000000000000000000000000000810b0175d781256053c3c9188cee4f55620a6624bfbd2f4d2e70ee68a105bc7b60bafdb76794a048e9f25da976390d4000000000000000000000000000000000716095f8fd72d9350ca62ca3ec34d2228cb563d4e89b19b152787d42fbb750435aa6233d0a97196a9324319837be14f00000000000000000000000000000000178e939d87c37d4a2f49e1e5596945879f2f0f64419e3dfe2afa06bd58098e1ba57a9b60c32cd6527481ab3b325ca827000000000000000000000000000000000aed480a1da482e40ae610a9522f0a18399b0130202f9ca79e3573987f5f7ae30724feddb52fdd05817a96f7937aaf7984bf139cc0b6ac94697b7dc278063a74e478d47528da3f84f55fb0adfd851d09000000000000000000000000000000000133adb236d9eec3544fc91852278abe37a1da0f32a84477c0d93927d64af613b7452a5f64ddec7447779f42873cb157000000000000000000000000000000000f6bd940b51b7ec5a0d92ac77a55c296215e970e9a499793864dd69c3a8d583403e95c08b719b5d8eb0c37a8476d3b960000000000000000000000000000000007d4444062ae06e65b45c6105af53c487f6b275ecdb36f87ec7b71d5861a1bdd6d735e9a1fc5dfb476ab8c13a98b570a000000000000000000000000000000000e043cdc87c67157b5ea3e5ab1b243aef479b23861f8cd823bced140ee03dd1f8bc6cebb4bde4683ac3340823f4d55b8d19d9496e7ebca44354d5c6e1f6b8211eb06ca23a6444c307f92f5bc6dcc2dbe0000000000000000000000000000000018c35112c27caa6bfe9cf8ae55f51755ed349ee7e7141c99069dea07c21a6d8634778a91f4dc3d17da04966a9eaccab5000000000000000000000000000000001800c8a9b146dba27050ce63e78895bee2016255c59acc34fd5e6cb926c16a8fcd2e8a579fa02559b3c571cb08011bea0000000000000000000000000000000014afab23fd4ea54b1ef576a12a2a62d42b493612ef466483ee8c4e62908486c038598e72dbd9256166960db73259def8000000000000000000000000000000000899a99ae8b10da4bbffb6590d79aa33bb2adb2444a11627f05622c732b70f90cbd2779362349aede5b591e84b53a8a06940e3509e1fb090fa787fdf633a74380cd5de722678826224641e46a6e920df000000000000000000000000000000000567d6458d1a3e012c63adc8b9dcf32254c98c0b7021ec6a8d579dad47d501715d2e42a0837def225515d663e663c4f000000000000000000000000000000000178daae121366ce025c1dc2d3e72068fd40ba9d54b2b3724f7a2071a59d4f17d4766a82364540bc31a46398c66d0e7da00000000000000000000000000000000147b2851311913ea53662082acfba785d21915cf00cd154b1b495246e109ac37c3fb6c63aabc4fe71a0d37c81a40148d0000000000000000000000000000000000122b7b1a81888aee37fdd6c23d31c38e79f28945cd1798cee3f4d674e923fc68311eda8ce45a561abf9c5f0bfeb4297b27d21c1d6e06d9fba7b61fb87d364a7a6252c70b8ace2d3679ed87ce0fcf7e,000000000000000000000000000000000f5b941cda417cce69a30c1ba4a82cca71cb4b953d06d8e545c1b792ae22738dc006627da02b4344bb8be93a5a0dcf07000000000000000000000000000000000eebf4ac30fe0ffb905f81577466889666f801d4d6efe0fb8a663fbf1cbe76b2167243edfc6cde3f49d97d3040a9507400000000000000000000000000000000007ae6a99b86dc7ea95801776589472547ffc7a623009a592403a9710ca365510d85bbf20fa4519ca0e0ca208bf86a670000000000000000000000000000000004b5abf778c72bcc5b887855c582c042a4cfff489b0548785e4c1b735b19159be8a3f4cecf34c769a34cdefa722ba783,293920, -0000000000000000000000000000000017b47384e6302b140118d0a9247aeae2091607ebb413ffa232223bb42d16753b2ae48e5ad0265e33616b25f0c4234be600000000000000000000000000000000167be566292b835a42ac7c099d80e8a0b5d4ff91d842d4ff6026876aa1570ef9641e9c0cbd44d8578f6a758717bad6f10000000000000000000000000000000014f692d195979abd9c55ac132d0def925d4e158fe946fa7b0a010c475d60171a0951d4b68ae3c463bf1136600a1ddaed000000000000000000000000000000000ddba1f4236c5200aa52f8cb7e15fac1f20cc66dc65ed180745a3eb8308f2c851ed6c1e27e1507d3f902ce672d6f8d24facfcdf87c6ca0506b070abff83ce7812181c31729cc534497aa8dabe243351300000000000000000000000000000000023d08e255b244cffe911e43b9b48408f9fb3562edc2c27f405bb657731c885a58392ebbde9fc80cccee2404cc8547ec00000000000000000000000000000000088ef289adaf206afd2b72c93049fca2cf9292bf6471133c64ac4f42015b97bb9a23f6c34653e0218fd0abdefa56bcc60000000000000000000000000000000015cb78c1440f74b17125c547fe7a37611f01b83b91a351664c696e0f647bd2db3ffead880b96a327780026d74c9abca30000000000000000000000000000000004d1a63607b9a5c9ec31168d85fbbee77cea0ae93e98c8c1dde14d0baa72f91042b2b7ca489958344916ce79bcf286456546fa692d9cd61895526282193c90148099e2afa54f7903a5431f932bd5fa06000000000000000000000000000000000ea6cb7ff6a7f4ec38ba11e9945eb406dbb8517585fef6cdd64edc970efba244b071fa162f7c8e184acbf71c5d1e12160000000000000000000000000000000001ab80c0dced33cac8a6a085efce71dcd7021f6255684bb631cf5c1716021bece57b900b819e6eb6f5b755b74c677b6c0000000000000000000000000000000005465fdd51352cbcd8b804cd509526c3b6232976b8278cec3b7db7da14b77f78898c6240c30943d1418462cb7a5abf8f0000000000000000000000000000000006b6caa6a0d5f2d671b10217c0ce5b3962b0c3edb4f2918497c316ebbdbe1a15c803d7fc3413907346f0e7d03920005aa9c1460c1cbb2a552e3452d5c5535868ee9c2952ec3fdb52dd826c16ae3d00bc00000000000000000000000000000000170db23154805a04013052a388e14b5da00e65b35b8ce2dd967213a74735dcbfd28782cef1ffe9d384be3ecadd101e9300000000000000000000000000000000082dea309092976408a379f1dbed9d8cf91f768e2921e49ece458859c80a1d9efd4d5e588470bd669c777d16f9d2e7de000000000000000000000000000000000adba8ef34e197689228e6c4e13be75b3d4732872c99b865ce7733b7a42034d6d4d7520ef7ab712f60f1ff87bc4d9d8d0000000000000000000000000000000005df0788ec39430fcd0625f8e030d917d8e7c251ee6e3b0e79fc6fa5f6fac2ad736c818bd833e58ec61cfdff52c9c6ee2c36204b6a005a64819b06804eb94c311d78977b557e7acfa82e147b4d6ec62f0000000000000000000000000000000000922d8b5db6e415aa3acbd0d6065db1b492c92313260019ef1bda0fa091c4bf091de95846af1edb34516b1abf7d278e0000000000000000000000000000000019af4ecc4f278315ed90d67cf4d22ed6fc9af5c0d0ca654f6a74a3c4bc98588bf5347b4536f36ca8b4750c18464f9b7b00000000000000000000000000000000021eaceb11638bda8b4293991983f11cc60c1daa2287f4b4a6066374bac82d117ac3ea4ec73afc4372d254bfc433b8c3000000000000000000000000000000001037fe26a10305cc5dc11a65edc705be5a0082656cad53e63038ee57a79e16075df54331233229a129483c34d6dd92ec9160c5a553479a10996704c3eda8e57be88eaaf5d1efc8371e7e10d7d106e4810000000000000000000000000000000011e63dc251a5a1e2ec83741682d90588b6b185365b33dba45458b1f56324a4900b04d61af155a0edb0bdc2971b7aaa210000000000000000000000000000000002dc1bd5448a2ebb9a02509af8777616ba9657bd3be65519233f0187df77c49fc931bbd3ec0ad5856b2ec0dfde476a870000000000000000000000000000000019f0cf8baf100451313711bbb0a0fa318c14224933897e74fb727b585cc8620b7d741c9ca2f0d3cbe14a8749aa48ef3e0000000000000000000000000000000018448fa9e05f87d4991ae1c248413edc9a8c3ee789c9c005e691bfc9003191ff469e26db9e42e5758fac79309a62942c5e5a50e5dbabb7a56897935683f80a5b16dbef3c23461e241fbdfceea38e3ee200000000000000000000000000000000109b71c19cd36ef3078bbae25ce6d0e8f7b58e129407fe68ab09aa747bfb3e90c04ab804fa6b7a223c172146fdb14683000000000000000000000000000000000d297750ba112da88beb84b8bbf74ed134b59fc9496da3045aa6dbcd97c68425fd68b75508de113733602a5565f4c8a600000000000000000000000000000000149b8ba6e05b66d07b353f46ace4e583bb61ed18fdbcea0e941b8d9805d3168040186d1c961add494f98e4e7fe68824d0000000000000000000000000000000013a6877bd46557d23b9aaf371ee5a101227d7938c64503b04b39cc6cb4e8ddedcf5cb6865439c9f8b1bfebb807ce52e24a95b293daa2761cc456b9667517f499c4d9eb9eb1d82237e7a7819b5d44f7a200000000000000000000000000000000073f440c2704fae6c86aca3cee34591ec03c362c2c5153a5e82c7bcdece2af0c58a3484b448c8bf4da851800ead959df00000000000000000000000000000000075a2c26372b482a2420bd3c9952fdbf9e5fea906dc8a4deb9691f8745372805bacd68a4838a3fefc381a2ce946ed1780000000000000000000000000000000017575b016435782cd09901afd2ea6773b11f5a983bdd19d14668d75362f95d055b76e5bf6966b1bd7bfdfbe9a939e4b60000000000000000000000000000000001569d74258298fac89d0d91a9945780f4c08d7af7b942d06255ae590db6e8509c908c16bd2c2bb634279debb72f489b5e22ef32d111261dfcb5a2e8d23c8d920f013bd9602bbef45e6d4e0909abdef20000000000000000000000000000000017180e36b925e2ce23c46813d96b919ca181481efb5d1666c4a4e9c8031abdd9521eb8228c4e3f16de0b33da4c73588e00000000000000000000000000000000138965bff7c573546d80ef7efb3d45e87ed20f59adb0cd7ae148d09a97da7feaf1b0ef2455ca19381762768a7d82f486000000000000000000000000000000000360bd29c3f07c5b560e2ac226112a628839da9db18b052991eb2d9c54541c1b5ade9b3c2d7f446ad50050531228120f0000000000000000000000000000000007105978bcf13bbe2bf5c8f7d165998c3ad99b6a2794c90f5b61fb7bf2472d307df8fc9f4afe7ae1e40e7f0eee8ef9466e687c0ac8fab70de2416642afa1553bb38183d2914050602874491057f78786000000000000000000000000000000000f4434c5180ad10cd45dca62b8da790cdb912c255c0f33950f7039e3885b38fa9e9297c7b0a875380545839d8c4d4ada000000000000000000000000000000000d0dd1429e512884ac209f788b5832d31649a78a8966d3348a93f841be23c8e4e42d6ff0d6c27e8f43daf495c9582935000000000000000000000000000000001307377f55dfed30ac1a406671af1895218a01d063b025d25bdbc53f5f9d535e4cd8053c09b2cebb25d3a08365ab8ccb0000000000000000000000000000000004f5c06f505ed15aa7661249b7edd71855bbf47237e049aa951e1ea3ff88f98591518bac975ac628e417892f8e9e5523428f1a27ea15135f044643dc36a3f9c2b4446a3136bb11f696b0a430a7454b3f00000000000000000000000000000000083336fa0b79691b4875ed27b2bbd2d2586992940356f6ae5ddd2021c5ddd87f07f0a5c1e8d8a2654b99182cc2233e84000000000000000000000000000000001880f3824f7cef95ae5743de2e17191848d8d30f0469f455461c6559ebc75a7afbc86dfa3ee17f5470f74018ec335edd0000000000000000000000000000000007c2b26353e86223e5dbd4ed6d59f1170b9cc9dc600fdfbc6c73b96f2c667a82128b1ae5af0542b11a7d1efae87c75610000000000000000000000000000000002427b7eeb497a20cf15c10513cadc9ea612f3ae94e2ae833d281734e7b5d1d50e240659ac01da7864a95b4cdcf88744ae21ad8a6c9d75b51133e81ec34d66ca70a52529c5c3a2307b0e8d6f1c5e7d97000000000000000000000000000000000e72845430ebfb84f8e3cd3dd418f6dc528bf521aca4f9dbd798ed903ef0ea3cf21dd1409aa3759351be32b21d8e8cbd000000000000000000000000000000001457ad87f0957006192dff7d99815c35adb3635815e5d157542b9f52f1e9f8c0143a21a3be4dc1aea3a895689f4a316f0000000000000000000000000000000007e8544b1037ece2e5a9ea387e0f43b72e895e9c2ca4d205f12bf6df0b35ae62a4d62756221d6fff65b928b7358f48b00000000000000000000000000000000012c5c3167f6ef118c4044c0aafc85a337d305437d694a7bd6fb406dabb7364d9e90d74a8b327aba971421a5b3dd5d06988a23b118179ee2c34ad030993a2d2d70375311b95254c44254a32508abcb612000000000000000000000000000000001995d7cb79da7b6c5a0c8ccc5ba075d8d6d8ed3cfca85e8ecdd2b589986fa58c4cd4f045983e9184d79173678d618f310000000000000000000000000000000000f9f7f6bcff0f6fd621f3f8fcfebac132b3f0d52a34af33bb9830bd714d2982f3cc6674ad6ca668131a5062e5589df90000000000000000000000000000000017699b298a46829020e0299ab89ab6411af0a602dffb0e149053ff40ccaec71a908da02c8e611723cd06c16a8e5c0f2d000000000000000000000000000000000523b287383c1e47a6f31d397359941fe0bb8167aa11604ff8569969eb5ccddf4c4f432d2b6fe6f39204020e850d4f2b30eac099ededf0087275d1af828bbf79ef7fb0e77179a068f2ebfe4c749a98c90000000000000000000000000000000004760120239593cae5bdec813735ccc99a88129c707686cf43efbd48fb08d8da3086879a6042bf118879fcccce0736bc00000000000000000000000000000000105b8191431f701b365c66680cb4eb267681ee4da17ba55d47cf26d21ba1c0c3eeeabcafcc79dd87b6457bcc91e9fec600000000000000000000000000000000126ab502f66e732aabe02fdb2f7a665a9a43f6b4ff21c22fa976e7e434b08b606e9cf0f02459fd85f5a80a332fb3a62e000000000000000000000000000000000b2ef01adea6c00250f2f14c98ec6d6083c45019f3d166419e3a137667324f80c34b6b72e991daf72e2eaf9985d0f9287e8dcbf708682225fe3f71b7a687da23de5ed188e40585be0553358012132577000000000000000000000000000000000ff22a0db4f1b1679bde5853a7c2932501f191f4a9f25eed968a796219cef028e26070851a9036a05a04abd73bd6bd4e00000000000000000000000000000000097e9310749f52a4b645190069f4d52315f0eb2ff9cbbcd31f1781a68b2664bbbf27166e6e74fc2be2e5b1eb3f3d77a00000000000000000000000000000000015ca218d7d128095bd4f4b4f7bcf7666e92b905e551dd22745bc743ad0783b6ac44b841f87d3deac44617a7c9a341c55000000000000000000000000000000000a1cb723a4c378e5db2775f4dde9a6887ee3313401a64130a78b90d65dda3a5d9c8bcbc1a0d78c310c869a7fc4889954532cd42a9b698a2c2d22b1a620a7ec60daa9d1eb8ac36894603be7bb9b5e37be0000000000000000000000000000000018b30cc461a4e1fbefe209a709a21ae201bc6094b2d15f0d6dee5a55dd84ef56b62ab1b6bd513b27c84c638291f4205a0000000000000000000000000000000008a6f2082d6d510b280a270c09044ad31fb18b851ad2b38859138c9c2e4870fba6b607f682a798bf21a13bff116014d200000000000000000000000000000000150ef352d494a97d0a7ffe44903aba1611c8d81fa2788c0f42a6db48a71101e12f07318da5ceb1f0af3aa10cd4c26341000000000000000000000000000000000ffdf3b133cc926684e4624531569bfa09b1658e29ad9c3efbd5e9d18353ffbbfbf23a2ad80ccee88f8fa597416d47173ccd5e19892765e549a63238e664c732af781fddea558a117cb927bc4a1aceb5,00000000000000000000000000000000134f45e5409998e657923ca76ce92b7d2acc932308e0694bb22f121f8324d16bfce86f96c67111c8080289eada4b4fb40000000000000000000000000000000008d9063b7845ffc8400c0b7585e819043884f92e28f7e3ffa47a39e808cdbb034ef4230b6e19bebf083e939b6b686b0b000000000000000000000000000000000e95f8fcd6b5bcc9e00a580a99627d92fa7486ff5ea587df5dded24d1b0bb76d339f6765a5a2058a8e227f633ce36e91000000000000000000000000000000000393041eb33f2c63df3f40d8ea1e1a9eaa9eb0a46151294845e542054d503ef69b40b0b676b0e4f3e08f4d26c36a5d4b,293920, -00000000000000000000000000000000042a220276f12fb4e81c40ecef3a0d86634b4d1c0acfdc463df4e7501289f0be385e03808d44be053e6ac98f403a8a930000000000000000000000000000000004e3dc92e155aeebdfccaaa1d24f49efc8b02e4d9ba8500a5b953a96e0fdd58519bbf1c279750eb8b98616e6bb9a3f6d00000000000000000000000000000000086bc212a83b09c7361540349767896058d5567d4342c607ae9c07fe5f123d9aaac95ded6cdff0825edb5acbce3e2b6d00000000000000000000000000000000062598cd6d5680a155b6349cd51d636c1350746d17fe0fff5195f164ba2fa51cf52f8662f43d555f7be6bb8bcacca39448da17551b2369b723bf932173a9167663f8389d2461b763d6a061df78d7ff1c00000000000000000000000000000000193e2c749e5bbe87dd5c306d822740969cd69ad6e156c323d217c08b18bb3f97c85aad63aed1e3a455ffa1b6d2a670340000000000000000000000000000000012a3dce63a88ae32a746e3812833569959021e1dd9518621793308f8f11d04829b2c3d0e0ec39fc48dfb8285b6852746000000000000000000000000000000001235488d01c380e91872fc57cbf618c3531a6bbf6cba9dddb9f07168cd459c9e866e44e9e5336369cffd8cc3a36cddc00000000000000000000000000000000010d85f85d6242b63f8421e92f1c37f64d33fed67e0cd3dc4b2b2cb4a7fc7637f9e049fc959720eae6d1f452159d48b78def52379c8b9e7c24d971c3456b85b51a63ab03761ec66c8dfac1018833e05940000000000000000000000000000000010889825c752d0ae8445a1d0f3510135b9672c30a781788698f637c2f535e35788d76492edce8ea091223d016b5cc141000000000000000000000000000000000577175035c86c022e634ccc9a5beb96a36aa068cdc36e5a4fc2028d5dd099c5296d30a916d3b720f2e051e7d72e4d490000000000000000000000000000000017b46e49ba08a0abb9394479d693d8097a140094d0ef1d1ba7761fb601a686b0b2b4d49abc2e393a99c5cb760299992b000000000000000000000000000000000820f8e52c1b09986a70bff04909b044f671c3933de43a6bdfcbe3712310274ed880d7adc4947490c7de095ea651e578b2225be6985b9c8fa59a2890da56427612a4334937761e24a33d37f0f951a794000000000000000000000000000000001776b92f683069fdc006904fef8e91f716d9f6bc46306b042228088545f0e11a41b40b60722d4f0483250391febc0ed90000000000000000000000000000000010d5052ef2504115e9d9d4ca81c7080c0868cbed605dc7673f7f94f5959c793c96aa5334175e58499102ff76f974ced80000000000000000000000000000000011d1e719bb69d842df4fc23e8dc4393067d00f6fa8ee42d89b462a546414b91f68dda5378fa093b3ffa764b5fc63b1aa00000000000000000000000000000000099d0784a200e5d2d38773912cf1a49e813c871ede8c50da03abff58ec1943d2adefe66bd2feed1c57f5a80253e091d0a64ce8ad619276bc7a00cb49faf6cc84b917ae6b37654363f5719a727a220291000000000000000000000000000000000d7287adbe0bc3cbb35ab8bfe69064faa83e3e64d73a0c64d960949e10070081a99c35d1dacea5a3b9bf312745acd6e600000000000000000000000000000000034f1995eb8631e080378b22a51ace902ebc9da4589c89ab557b6aaf685fcc74ec1fcf95f6b9a31b7a45cfc5a1610c640000000000000000000000000000000009f56712a46c0fbc199c12d5eb7abd60e660e2c6d437007c34954c6234a0496ad0adf68cf759f8ea30980c9a78175e1a00000000000000000000000000000000073fae1cb78c776188190a4d7223f7cdce9a36488193dc06898919ef4d5136099c3185d352028760c753e9eebb52ac240b891d638d7e76e0dcb230b1f9a7c3b35b35193c43a6c86f345f5a5bc9c708f500000000000000000000000000000000019944fc459fb601bddf10a3a7eb55f34713d396c3208a10089b8f21f4bf0a5e87e95ccf73e0bd90474d3e043f37a72300000000000000000000000000000000158445bd2b6d396a390a0bd5e26256587f980eae84d7a592b2b4d788c452d312b854427185a770084e1b4c7898962e50000000000000000000000000000000000ba44a1b912645354da7d8d9c694b1d5a9ff2d642fad31975171deef3adb0f8d92b2d3a8bef6ecbe0b8e90470b3938d00000000000000000000000000000000012a040a72ab035684bfdf57bf473ff59cd1ee01ec949dcc6066e5c8afad775ed55123859cdd74c7016a092bade7f991b571175eb91888222085fc2dfe0f4401ed6a1fc5df86c0c6b8e44fba6454305bf000000000000000000000000000000000317ca8fdec8c7c56fa3812157f9ca8e9bbf91013dfc7052c0795a04a1b4649b2147d9cb1a61f2c114a705e5133729920000000000000000000000000000000012b893d50fe5ea2eb528d1a04bc8596b10d4714a0dc38bdd5f0a275c07c846970106c3f7b5686685f5c809e93c57e2ff0000000000000000000000000000000014f018a0d13c4c494f4a6b7e836f0f2f46c4b7975d91adb93616a0555910f53574add03b905000f8492465c9b5488c1300000000000000000000000000000000146eb4ef1103b525dfb5c31bcb98e550245732fa252a814824258093a2397d1489df8ca0228d4f5df0a00d473d1566c454c9e7f7ca14c66b8431e25e6eddb4f20507d03bf124eb607957ca2f43a0c17b0000000000000000000000000000000010e9962dc19aae8e92abf32fb9c8eac44d77f587159af4e3b3a080748322715a958d953d3c057999839a47dcc840076a000000000000000000000000000000000ccafa9761e654ba54a46afff51384f1c6331264082e23f94fffd6c31a1b1b568a391eac79417657f40ce2fc9a154a670000000000000000000000000000000007276b111c94130b2608827156021815faf2be29ae42c454f3e2f95de98d2f5b98cea3eb18335a8fa00e5464f8089cf300000000000000000000000000000000053550896e867e237086098f4493caa2520e8c97a05e14d0ab7012d37b7fbbd42a90accbf0fe2ac99e78ccf0be5c9c58000579e1ad83015c8f02a9db5c38d0220368a80b309ee45bb952cac824817b6b0000000000000000000000000000000016b5bca8537059362147911da9e69ad3ecd3b4a7c43ee7d6d809f46c74c16bc7d69bfb5d7c727b4d5d8a356a0458b59e0000000000000000000000000000000010f3a7eefeb3033a733af7d20c3c5caff4c409305de8d71e08cb9cefbdfacda41bb975c92c5e5f2952c3c1e2bc6ca8cc00000000000000000000000000000000148f5b2bd65b71184ba6974678f709c5f9e3f1a020e3d4bedfa5f5f66478adac47f06ca2626c4a759b5eae09756cfe49000000000000000000000000000000001301306d1259059b5567154ef6e4779fedf98c29ea967ce34b78147c5730f202e1c12d5b5094219bf85fa62834329b45909a45c8b78350e3ca21697e9f56d5fc8fc2a01817b78a7f5daeda487768ed1e000000000000000000000000000000001741f739459f5d462fc9ab55c68101a5a3f2741c05b4c3eec6959b2aa5e12493a19d1b33a9aa89337add642458089eb6000000000000000000000000000000000300d8b7988522706c0690da52d0a67ae41344e43cfa05d22feb91eb8635bdb970810e993e0ebf8fd63ab8fe3e048d660000000000000000000000000000000007c003cfba125692b88feba85e7288bf61bb25e04b1462f7a39b4198737010224ce4b73a320c81b1f70119af34d381d1000000000000000000000000000000000a4870c9de67517f4353de23af21fcfadcfce55365ced33a61a19e5de52f98721b17c6eb382970e7c4acd81b80a7bd2f6d4e2277da617f0ad530b6209df6264e1288122b1b4d92da04fe334be17bd8320000000000000000000000000000000002eef52fc72d5aa0456c13808ba548cb765e11cd0bfd0599544793f57c8a27ee90880e6577af1b76b3fe32c4e71f4104000000000000000000000000000000000ea99a4f6772f8114cfb3ae9dc20f11a34880a86088511e5b7fe521d50470148b43f866eb5bf4f67c523266bb55117050000000000000000000000000000000004bd802b889e6d18df7dbd65f39a908cf5889e14be51b5ebd423ccb63e4e5b35e429eb0d4f384b811b47975143ea2ef60000000000000000000000000000000018dded357c546d709beffff2da0c08e8059c720023234c7b53d0ae85750b3e166cde7faf340697b546b8dd7c13b1ce7bdcba6bed6b8c42240c01df5fa0ea81dd96168c6d98ee9d5d4653edfa5172eb28000000000000000000000000000000001405ef521bcc60c55f8551fb2e2aa7b10117b2f96c03e8535e5bff48ae197b7e5fe69a40eecd25a67f430ca02edcc9d2000000000000000000000000000000000477d85a7dfffcc5a2a1048205362ec42b268e5fbd27ee7c8d4ca77b5c9db84dba482bc4b164f92db2c15cc518b3d32800000000000000000000000000000000060988548ede00aad3682fe827d1e993ed1cf118bec7cbe6f69bc160f030bf87c299d40047a4fb5ee27dc2814649a4580000000000000000000000000000000006b9e0579f82fcb8bc149e40b1199f5897ea48ae5eb58abd2002c923efd0f5275d24a579bd904e49b7447c4a03e3fbe423d168e01657e5c2da89a27e513bcbc6620b8c6082bd39880619bfe2b3a7317d0000000000000000000000000000000003cde2bfc5a865cac624d9018c37c1b5746b5394597d79c171b25f84d5fdbc76bb90ba5cf9db14b3b8e62ff91cfd79520000000000000000000000000000000017596885262075e45db62ca68ee5b99d12223bd476e36ed4ddbf5cd56a0c6e9db5d79e7f95b96b1bc323d7c9fc5447d800000000000000000000000000000000018333858871dd41cddb7ad2f179f1f341b2ef20bfc7a1d3cb235e3a1a181e0da7251911886f0788e0f868e16520c5a200000000000000000000000000000000098ce44092980cb14e89faa7efc2906051c9a51cf7b2757dbffa49fafa3a9ba145f809f1212c27aa620bf062e839f83c2a76fafc5e8e33852bbeb7ab8229305be84f5474427e0c6d2ed35c7bfe99faa1000000000000000000000000000000001180d554fd523a51e0decb92e0134c6064a17dd3aa7b11d590b9b6022f76763b1e20562da21e836e65374efafd78b77e000000000000000000000000000000000488686f793dde899a3f4936f07f9eda7918450966ca85b4715d6fee978d9d091bae1b5d2d04943365c076a849b3359c0000000000000000000000000000000014661fb2d305ec9e63d63e9951d0f081aeba99972b094c922d2797a1100759cfe150812821411205f563e22f01ef29c50000000000000000000000000000000013dd681200608466853cd3bfd20f146a6383151931079654962684d6c6fc3bd6900bb049483c1ca6d2819da456f67e3be3c7e4e95167faed1391e269785918a207490c6d186bf2537c02e52e414d564e0000000000000000000000000000000016c8c7a2a1a76ec05770f2d6c8df35003104c034c76323fedd49663daa759caf2f4fefbe8d44b3abf1dadfec2a06cb45000000000000000000000000000000000837305004aba2e322ae29e8f0109f1c756a44b21c72733019e63ff9886a639464090770d12d35553f0002ad028332370000000000000000000000000000000005c8f82ca2d4f6785e2d76ca3a3d1ac67aedf78e9ac833c52cfda6289e6f5d7a83befbeaf753abce12376889caec312f0000000000000000000000000000000013595cdc9181ca70845c613663367ff774f073774688dc58edfd0c58de5ae12df5acd04a673b645371940d7f7e1601045d335e3d96a9b25be7f3916e92fffd75abeef5b91a1ec577ced52a96f6a9b10c0000000000000000000000000000000010f1b8b39ea8ffcb6a96bacd1c00b413c93d3f8da64dcf9257a7cf0264831be23ca63ab8d3d1cea21ed8d83ecaa3a0c70000000000000000000000000000000017a9030fbee573cb71330007900723f85e9e82530283f713f72e68c1d9a5ff9552d0da469a4f38b66e30df1514f922a40000000000000000000000000000000018b9020986a49213d4f3b4b052cf2fb65f82b9bc2051f20b399f2784b984ccfa2752ca576d352c7d65ab218bb8d5df870000000000000000000000000000000015a375a3711f5e9f85ad7266b2d307cac09ace9ea36e149dde5e0d5acdbac3f62e1cecba8be51d88f2143c3070eddaf0fa563a70780837ffcf9a84456f0b4f6eda0d60cce6a8538ba10960eaf17362fc,000000000000000000000000000000000b668f602b9f56182b74be413d36b03d2266d7165386a7f1f0d25d336d06d2bc5659e80e54dc71f153883292df1cd8940000000000000000000000000000000013151d305bba39734538fe9a2717392bcd134ef1f8c1879740c8cce981a2d70c94b23f1a71a0596e7ead55a55eb186c80000000000000000000000000000000000e5e7c268f93d8a9d3ce45db2a95be906483aefa3831ed2ab2aa357eca0ca231927c0e5031daa200784cba33b96e51d0000000000000000000000000000000011d57d9a9123123f9fb56e990626346e5c76bbd1a4b3349c3f7bc44f05a899f9c4dddd67ce5a788f85b4fb172385faef,293920, -0000000000000000000000000000000006a7946b50749147573991c91f13cdef4e9558be37736e3d990c5750d31ba9711721b88eec529ea4b1dec1c935fafa9a00000000000000000000000000000000078d8a565b7f58b915c220882a73b6aaf100f2d54cce2524cc3a80d9b526c3058903668d17427a617ea045c3322ec502000000000000000000000000000000000733c6562cdcb28d740c64f50ee9d204af4ecc8de2c1719fb73c20f2580fcf01e1e494faf4386764e03920a4162049fb000000000000000000000000000000000421365fa828affe963d145d318065933d4d865f2a3d24469ab0db66dd09a574945f8a8b622d079a7ce1c6fb6c795a8f6e2ee781da12b984e7a08730a60f50c41cdd7c7c8b3f1f30f721923ddc34fb79000000000000000000000000000000000a4fdc68bda287bd819ebb0a296212ddd19cc76b042e134f1637c894ad64bcd8431392c9791f2eaaf94f6c8d189846760000000000000000000000000000000009d974fcb46fe81d81d62b24b805ab5108c9450e162454c3260ecd0d5356b7c263be5f78f6214cc7254e461166910d23000000000000000000000000000000001081fe3579cb4d8a7e7d43ca8cdda28e1f9ea8df83c6069f4162a2a0e68e0d5876b283193649018e754c5c8fef101f53000000000000000000000000000000000ca4faaddc4d14a6648e3515a8b9028190c17f771c7de086fe4624a3008d7e6e374c967f303d9511b9da1a95409e3cb3d51e0b65be993ddd2892ab8f47eab78352b177be4b3fb68f47c65f5c59fa866000000000000000000000000000000000117318e376f2c130e5bca89b3d700fe76e9603adb22a5ef353bb3b5a8f641c85deb4640fbaccf94e025a59fbf2a41370000000000000000000000000000000000433428497ed89a43ba07d816df224809a827194ca899924c3844650a3800952cda8db82f2f8e513994ed9893fac747400000000000000000000000000000000064889f1cb7d6ab216fcceef7c4abf89be14ef93be2d39bbba2b74d06999dec5ae1941b507709d093b28e030700cf866000000000000000000000000000000000957fcb8658497802e78b8250373f77acc4ec47fa2c87e78adbb2daef70240da640a7945895940f76bbb80bd36b4ba24fed4dd284df98923bfc3c41496d6e64a10815a8c474275e0cdbc9ed26e92b0ae0000000000000000000000000000000013f9771c105462fb6b975b0b2fd20d0accdd2d95d879c8019b08db394cd785ed9f151d0eb1adeaa63bbc2686d1172b0f0000000000000000000000000000000002062a5f2db0a01114a1c6e8c739f80f598f4e905952101a244853078298eb443be6839b59d4f0c7745b739cc89ad8220000000000000000000000000000000015b5485439f1b94fbb3a8f5ac6197f0dc0577863f39c44b34d4c5437b6a82a704dec17529654b3037a9ee1ebf14c8d8300000000000000000000000000000000154d750e2a660205812d428cfe79aef4e1059f4e231024a665889d112af37e6e17e04cd7c926b6240bf2f616a1f572dd7c36ec97c1eafc8a20a772fb7887d75568047ea32458b9ce74ad9ca058129949000000000000000000000000000000000231223930956bd2d36a89a0a0a47aa46b4763919455ad3a3581439d25a82c176569698fd5ca2b9429793ebb16c98e50000000000000000000000000000000000b5dd675af51c18d2dc76e3103da4409f6e8c1cc719a622d4a33aaae3f23e529c78b63c55b67fa999bdcc7095a4ece300000000000000000000000000000000010c971be55cd02e4c97031d3b25acfbf722e47e5179beb26eafaa72d4bd5f47cf280a99e0c3c4cdac05bf1572d01fcfc0000000000000000000000000000000002c1370919e6445994df1e25ff4a79c8cd8805f12e5d8c781e58f04dff68a97424b35d162d875ca2b3f805b4cd6d1fa641b2c0354d2f7d92b05043f193b718d78b457ae76e19069c8e1c2b77d7999d6500000000000000000000000000000000169938b4d3c859f97a0627bd1a83fde725eafb7ab77b22cae06d2a776569236d834702081e78d61386999c938c0259b900000000000000000000000000000000091e922f00828488e324f9fea652ce1edff83d9f479e843ed4bffee27805a5025e7a150719b354b5e61f381ebd24d4ea000000000000000000000000000000000334ba8044d7d47795b59eb089502808a7ab8f18e3d5e1cc49acdb5020b3973fd21d6d82557afd748dad88e45a7623730000000000000000000000000000000002299bf949ef249b5057c103ecd149444635b4f636a2fd0d073484404c1ff4ef71820260ea6529bee6f5b07f2ba94de35615370a76bb0a5f64d61b97bdb045b9669f6a0b7644b101d21a50483d8b04dc00000000000000000000000000000000076ab7838db87727fd653a3b561a2a5594518f296284bc24a7d215b1fbc0a6492d425078fd98f92a414dfcb3c92cc1d000000000000000000000000000000000022b71fb467dbd6d9b130763350bd06f52d20ff2cbd46cdea5e8b1525fd73bfd08f5ff171f9fa28050e9a3b296d3e9e00000000000000000000000000000000007e917cef0195fc589317d4a71c14022867dbc0db26c653052e2e382d0dbebe67a0f582bc0a27dd1dfb4703c545d0da30000000000000000000000000000000005b1d8651b86a403ad993c5cea4b6b82a0f8a9f8a59d4b94f10e68e9538a559efdde2007736aa9d04f585851a89af88fbcc38cfd3c6bdd32ed1d583f2bd14e175d61448c627f195559b751aab1ecf7cb000000000000000000000000000000000653c5f5b2d97239821d173036929dc716e78d835a80af55868dcc3e218bcebdc2a052d31f6a573572d13f3bbb14f241000000000000000000000000000000000cdbdc3cf52239a6d4bdadc273b00924de8730c03ea82bd20ec1f04375daa4497fff3a1726269a736706355e72be83870000000000000000000000000000000008e0285b177fcd768d3519062177fa1314c4370f872eaf10f3e0dc94e716dc6a67894d887f40104552336cbb5ed614f20000000000000000000000000000000000638db8269ea4c2fecd5b45955609ef6a1c2c6faf6ee5a8d777e0b38f16d1acab2da7fe7b6f6ebb315ccb345835a21d94c41471a2e4edf0f688c2f032036d41ef5f8a966871dd099dcdbced8b37e1c40000000000000000000000000000000005b4f74cd099eafa6ae59e7105873d4a46e8e5985faf2d26ca564125dca93b1c48187ed7afa02cde8b52df878e1aa618000000000000000000000000000000000cda7f9eeadda16ef757ee8a98be147d374d3a1d40790d20a1ae42c9ed38e4fe22be76ec4f807cf93fff5c6efdb50d1c00000000000000000000000000000000121219b0b0d236a89a857c02249cc04c22299d041d95296dd235b3639416337f5be4a2ebe92a50d192fb748d5d4dca0300000000000000000000000000000000112545a4677ca7d60645cb8bd98689c4aa85a68bb62dc68c0affca5a17ecf0a08fb9b91589d08712b5af4aadf31caad2dd297b192f1c907914ef949fd27a5ea5659aab659b83239c4433f7a4e24529f2000000000000000000000000000000001342460712b73ca0ef07d953c32d280a3441e108abdc2d133265160608986481df3563c5dca20f209ce078b13b49707e0000000000000000000000000000000003580a5b4a7f6d6e066ad9073f7105f6cc1ff35ef5e79a0aba7f48ff2b732c7aec72cc9c5f9233fc9c267d8aa37ac17c000000000000000000000000000000000bb7f32db8a4e341cd9f8dd3b5677dc650cef675f0923bf2e5c8b84c33d447daacbf68631c2388eac5698495e1ad5a3a0000000000000000000000000000000015bf9cd1aa585eda2910128f2b452569abc1c94bc8bd308ee92b6c7315a56fc92d6cba03334bc36c137c14eb1f198b07d30fdb174a3f5c06b78cbaee5b6e7a4c90551083d78c5164de6bb45ee5de23c100000000000000000000000000000000091bca266255d692cdfd10929802d79b474706d160033495decd11cb0758136ec3ae7fd4bb99081e44dd7f25224e009c0000000000000000000000000000000001fbba1ba796416ac22c92f3741e3b268d89fbf0307edf0f25c7c12b5cd230c41582ba69465686ffead9f8363dc0c297000000000000000000000000000000001139590315fc4d81e3e747a53e63ad856635050367ffc143c1422e324d5fe9e4fb90631ff8bba764a87b8077b571aa0a000000000000000000000000000000000dcbba28afd445a57db762d08338a26980b4efbd11668e4050d18234ce35a909d6b563a5d3e8e72892514431fabf0147aafc42f7fe6854866cb954367fa65c8072bd1b60173a2d45077421d6e25f2bb3000000000000000000000000000000001322b1f1388a9dd2853829bda1a5120250ed08f07c84fa398e59fa2577454f38f0a76a1e8db897bf15b4b50ff52a847c0000000000000000000000000000000017020d7de1dd424de53992c168d924c42f26231d184ea3cd9cfe64ad9c82ad067540b2d9ab18b0fd28477ac792a80c4a0000000000000000000000000000000000fabc0769b95e6feedc2165bd6d324b7d16247b79eebc1f09d849792255136538e628bd6ad9b86af7bcdfdd991fc31000000000000000000000000000000000144f39f792bf5585f4b49dcd3fcdbb61cc7ef471e08af4c15cfebb855f0ac8d5fd057c9486e53e8e1ee4f66bd5e943ad106da5f98d5e7cd9f4a1c8d6e50ea2236c2abdf1e08a0eca54555a59bcadbc6a000000000000000000000000000000000c27ac29db98fe3038fe5f537d5ca6faa240602abe11c6f530d9b18d763d6dda3fb25f9538d316e6527c114405ae54f00000000000000000000000000000000017ecc872183413d8065a99a2d1a73b70150e2c1fca2c13a731a39b52aebc6db79772e91f115a63f7b23e5fa231df697a0000000000000000000000000000000016b9715ce820b619274202b52d7e7bee9a17aaeb06c2ecab8bc77c670bd4c714789e4478178d94d2aad57e7bb0b7a4040000000000000000000000000000000002d0723a3386248d8597d2b63289300de6a16011a38985170a1652ff81ea70a78459b3ef252cc5ed26ff1ef1ecaf6a42c971deeba2f757970bcd4f5548a2767bd6c43e63f4c5fc4b157ef060a1f45aae000000000000000000000000000000000eb1ddb7306d8d2858fb57dac71f67473b813f37f02d73b17f375be86028176cc1dd84347f183cb7d427b861be34c3d70000000000000000000000000000000009a8811ec77eb21f2b33a591f2fe6d7b74b40c5045ceeee275912aeec664838f332bb49bedcd958ede0af0d0232e76ed00000000000000000000000000000000156e28ee3c40c6f18c6059e06ac8f7b39fa23e5962f640ef3afce13c169346a4c8e5c2bcdac8fa15921a4740cc5a0f2300000000000000000000000000000000084371522a6ebb1925c8fad3f20277c34e657aa71abf8ed7d323a10c14cbbb1a9e0e54bace32eb845e6709c1c58afc34a5262a021977dd79ab96606eb24a7c5ed650300dd68bc79f4b8378f58c6eed49000000000000000000000000000000000be2ef9ef38a5dcb42ad31b1415c8eceae625850db4306a26a0598d4a567936d75b701c81793fa7b42d158df2dcb0d5d000000000000000000000000000000000851b82b59fc15b89e33fb618c56d11a07116ea35850583a07066ed97b8a864f3766c0cf921d007a6cb43931ad4fcf8e000000000000000000000000000000000ea8bdfa3c5f000d7cb1b5cd69537e4104daa15ffcec06f40a91b972d8011e5fccfa911c55a07383cce6760c145c39e4000000000000000000000000000000000652a4165602978004ef702103ef18e8fe7decab1522a76486c742d29103e3bdf6dda2d3cd64ff1b5d5a76f4823bd363083b3720c20044fa41712039b6e9e776197391ef393c0935a0e9990fbc1b7a4600000000000000000000000000000000015ce5b43e1fd950b77e2baccae8c99b82f38bce09989fdc5d402420e7931a38b7fdac5a254b0cb9bd8fbb488d02493b00000000000000000000000000000000018c5b3ff46a04ed114bbf56399738e5d594ef8dd1d5e2e8dc23a0097893be3da4fa4662686a6dac04418fd2d344e36c000000000000000000000000000000000efa3e970a5cd0c7bdef6a2df3be9be18cce63c10c331a18d628bbeef30488ef73d866f3c8804acb3bd375542e99eae6000000000000000000000000000000000e966d9e2f2d47df5d661a89fafb6d4518fa1544ab7a56716df511cbcca99098f944a981c9da569cf95debb455842006d6f846581848f5dbb9e8d220b881d0327c4f3f5d4b79fb2c4dcbdb9bcf44b02d,000000000000000000000000000000000ef06b515addb951b24e5d61f6e6eededf5f93f9f17455e1b563f187f73394457b3b7c1b90ed454218f8782d2bc848be00000000000000000000000000000000167398608a87490fd17506166bf54636aa4dd6d3e8c4d42995bcb0262268eaf2a6d828b295434f45e3e53703aa67cdcf000000000000000000000000000000001602ec6519e4987a052f97eb222f505e241d99602c08ea9c41bc95796675ebf6a819aa0bf87319f29dfe47f45f3c8c7a0000000000000000000000000000000002ad4291ece7ea0fcc9f4440e88eef693b8dd53060ec847bd27d74cf71218eb6210a71895ff1f1f4537a901090f14de5,293920, -00000000000000000000000000000000129cc9cf82aa30671e969148f058a0b8d275bcbb1c8da66e52998d9555dfaa075e2fcf39fc18f305940fbc972cc5c0c0000000000000000000000000000000001252482c1419ad72229a00d90f1c09d464e896f47db91e9680efe36822e99e1ca7a2b5ba8b17b5929fdbeff8993bb18e000000000000000000000000000000001287d5054bf5db038ec2f7b7a3b79848fcc8ca42f9e19d5e21d36d2256f97e0edc2608d19c17d3714024e1e9e86ae264000000000000000000000000000000000f6262669e30a5db67550cdce8a4611b9501d02cd4e950aeedd1c87c4f0f63c74f35c802dafcf91c988a154dd690103c67c44f7c8513472b51f96d526422bac628aad4c40c521cd7cf9e86eaf92838fd0000000000000000000000000000000019cccd010df3e668b1c3ec053e76c46e45d01a4fe02eb074e296df2a48e0e4eb647b06c40cf64a0048a8fcc2b0cfa6100000000000000000000000000000000018e07bc50657f3bbf736c38518933e91af29e3bafd776243296cca3a1d975116e8b428b050045a61069adb23baa22d3800000000000000000000000000000000154f51badda1b828346986264b01fb1be4c7e9b570ab63a5eb15cabe9412a2f9bbc6d5111c638ff5118a4f6d08ed055200000000000000000000000000000000064d4e607a8994c0bb65770a14a14ed1b68c766ac2aed45a44c0f7c7cd4c3ecdfa077206812cf9b24e35021060a3668d2d6f95d4b6216e4226f78e4fa5011c9becf98fe45a17dfd740fdd0ef36d8ba94000000000000000000000000000000000b9bae5f720d9bd3271a71d751e4c35c39ad30fa8a67846107ff769c455e42465b2a39dfa32861634d5e323878f56f4f000000000000000000000000000000000a1077046cf5c7a66452cec2193ee21c1ace50dfd79f707c9297737f13806dc05e9e1cc5d1fb4c87b250ebea5a4ca6c40000000000000000000000000000000013e1fbe1a9b5f2ccff51120590ac0cb00cab502726b43a6fc12459e27bad4aa41537d6f3cc94a81a170998768f6a0fc6000000000000000000000000000000000a02c551ecec1c7a415256caaec1b5485a42f9ca8d897cf26546ace1f2bc8c2d10a353b8b84495b8dab5e3c60881185a58c25d36216b811ee42d0ba629ab7a0f9ce7edd7234620c28e37bb3df3f042e700000000000000000000000000000000021b9ab3ad614816d7006efe688e1ae8cd99b0c4437d4363e557642a7cfda2000db6503b32db36b6d1ffe40d967c5efd000000000000000000000000000000000b7fb0ddd9eb2be9cdffaf8f8c593a9296c4c7deeb1c714c11863d71dab1e6fd309b75c41e25de3cb6089726b43427f0000000000000000000000000000000001277065ea9d208777d0fb7a6726e11c8330f0b3ed3c6716acd559aab19b2fdafceca8126c9facc43b9d80534c07035a20000000000000000000000000000000015e8c12065d601dd5ede75bcfceb7300bf6f9fbcdf68d2f093b7654d80d3e565135d64137dc401d691a45fe052f58a6850a5c6bb6b87fbe5ebfb0d182d425ee173973c6f2085c556b0fe60219b9f3c32000000000000000000000000000000000484c4f9652427d0649c33e93d666dcb15bc56669c00980c53357ecee874bcdbaa016236df65a4339dfbd44e4eb0823c0000000000000000000000000000000013836a7275c29c989891c94e756cee9d6c54a8f634fa570655ee44b7c1e34137edc33323dc0d1f3a0218039fd6f7013d0000000000000000000000000000000002e88c7d5fd87e97a0de1be95021821942a8004115fb4fdd9ad26b7e0fd171f9c7e6f962eb179bdb95ef960cf9396372000000000000000000000000000000001636e351a0ed1a260ffe0d1355e6da288792fc97a7310b040cb9fcd5c550d85d90572154d58a9847dd5a8a06456bb2e43b4bdeaf6643ed159f4a3e23c33ac486b33e1edbc5a097a47a6c2c753e5299d20000000000000000000000000000000007579785c14fa012cb5d6c116d34dcc15dfc908a29e90de3bbfb8c9b44e0b4258644440d7c78d751a007c10f98053bd10000000000000000000000000000000009f023538822ceba0883a0e3454121dafe8e5e61d4754b54e6417c989efa998334641d458591b3076b615937de065cfd00000000000000000000000000000000130fe7f2d5e0ffefa67ad3378690c53a6e68de5504f3691de0df3a24c309619bd3a345bc2bec4dcfb4b77255cbfe09980000000000000000000000000000000015bf85ed997eef4d97a81f1d75825bb4409cf86b8c8e5f4368cf1e4c803f9e1e23a2a96f7b0a08e5cff55a78761ebce21d18596bc392dd0b71e1216bbb20a0e5e2559a46789c36a146cb78c5aa8e3921000000000000000000000000000000000a95597e4402bbd17c20dda088f0134d42f14443bd519b3511b28fd8d395a0e50758386498388ea6ad0e7634587336c1000000000000000000000000000000000079f348d3de505875c5192f795cd77e2f7385ed447b06f2dbee18e85c832386b201cb3eeb21aac3f258d2c4b0434d48000000000000000000000000000000001895b1891a08ea42eb1f68698abc20394ffd66bf0c32979668950bfec5cfc8425314eec2ac17ba25f29133a8becc9f5e00000000000000000000000000000000146160336d881b24c6258a3a86c08d346900680324632b6d5d4582ee0865a7e5f2d01677e5e49c5a4179f8382e49d1566fb3669c0789ba6a5b00f14c14fe2edd15d37a742c9e36cae9ac010e632d75a40000000000000000000000000000000013cadc6c394efb2f93e00f3976dde34efe75adff34bfb6f5e1a150b79bb5baf6bf29fa149581fda48faa68653cb61e300000000000000000000000000000000005fd25362d87f9581a202b186d2786d2859faa9966a1ceae747dd7a48749abd424eb9813e44caada0e456ee8bd12e99c0000000000000000000000000000000018e6b279e2b545acf1da29dc0504caa5982522546f83d4d3389e1fd6cb5328d4a167926a00ccaca402b3a3cdc67d757400000000000000000000000000000000089a9992a36b476fee21abd50977dfee01d7c91b24b3e26d7c15b2301352069dad920f0ea93a3e477a48029eef35605f06c2988dd6b8e9aa116eea4e1f63dacf100019844d37d163c047567e8e1188620000000000000000000000000000000005200b78dba7e423bc23e87c5937b464e97405f6461d05bd9d1d0fbf8f3c8e64a39081f9e43b4ec416198dc44db897e7000000000000000000000000000000000bdba1ed07c4a570359863a1098a73830818b3fa5b222316a3e0692a4ec65e59ca6b4bf5f72f8c1384e73e807d272d6e00000000000000000000000000000000073fa3eef473707b6aff37fb6f829f0fdb7ae808e85ebba4d4924a185c3656eb2856896307b671080347cebc32e958bf00000000000000000000000000000000076b56330f07cfc0ab34e98e2fa0ce4702b296a00f6ffee07c3ab523fadc048a047ccea7a9003c090951e4ef698d14e5fbf8322f706b1972f73fe4e22a3dad29c4ede09163561b2810cfc3eb2ffbc7ab0000000000000000000000000000000007252747c8275f87b21bbac4071c1826d166d14e6205095e5299315d6b6a85aed995f9ba59a2163ce2c51a8e60eaaeeb000000000000000000000000000000000460a000fe29cca24dec469ba5fb729edf3e443bb032d488cc99102a614a5251915267db003dbf395132d815ba78f262000000000000000000000000000000000161c01cb4d0942faf2303c108604babbd4cabf5d3d30c13d7db9428a445c7f72d96a7405e22e4e451058a94e20068720000000000000000000000000000000010ccf8a8ec4e6515b20e07057fb8cbecc5defb87480f3e32a1bcd0cfe239e00daf6a390c4815ef6b85be1f07a4c4bfbc4a46618381ba6b991b2edfdeafa67aef1cfea066fbffdba24db25385963326bf000000000000000000000000000000000e6cf781162502d2a758d0f96946bab887591b7c9ec9f67a1b0b962e74ee514e84c14bf67ae3c0a9ea2a3e472b7ef59c0000000000000000000000000000000001542b4e97f1e8a64ffd51ca43137b0660f897f6b3d5c6fee598fc4dd03932c3658ea55e1e9e73376e51df278ddd3a3f0000000000000000000000000000000016dae882ba240343e752eac68122424320d1acb1fbc4bd26c3983dd91325f25e1b1f06213e0e06c142997a13fbeca597000000000000000000000000000000001138b71c95d4de320f02e68dae9bb0de3e5b317cb596532c5cc18ca588cc8566c21551d7d55d685591126b9d9e466455cd05fce871e4ff11e7a4e834061c65a0aab7bfa8a0128d460a493337c6e63ebf000000000000000000000000000000000904f6a09f3a5f5baac902c702b059835737c06f62c2ffe9101bac32f854e4d72f74031f5410a5941612b1aaacbb50920000000000000000000000000000000012f39e7022150b2be12cdd621ae23525581405021b21cb9e55972724a22b1aeb2e15b135ceade132d3310e050e607f65000000000000000000000000000000000a92b1daaf23524904d74c3f149fcd2c98e3a4c257113533e7cc59c4656b785aacbf0ba6b9df0dc17cf7c25f1ca698c5000000000000000000000000000000000a20a5d7c0aeb16ff498f46bf05e512784d120b9c3c8b2877411852d7da3abde9e83a6d00213bf69ed88bcbb051a486daba9e37ae0dbb733af820743d8e307fc02a3ce9b40032b16d0e9466903de9caa00000000000000000000000000000000153918807d7da07ec7014154f00a558ebe0d5fb48fba4c16488d61a826a1eec28e3828d6744300c04207e8ff1cb61211000000000000000000000000000000000a755480457896c5a3fde35658e73fae821151c43fb92e9ffedcb05fabad37cb68aa24e029fc33a2518398d723c4859100000000000000000000000000000000148798bdc5b14b90aefc38946db93be1754f15d78762f38971b1e64a53fda92b96b0a70ca2548baec882887ae7f636910000000000000000000000000000000012299fc413dbaa77cf8867e331bc0602c4fb32fe44a150217de9e6391374a9ed83781034e5775c4933e13cdfffd25a9e6ef151662cba4952416eaadebfe5e0fa0ca1d31380e1540c2d5e0181af9e317c000000000000000000000000000000000fdfcbcce1603198fa344487d2d4838b3ff23fc0a73a76222707d9f8623f0b87dfc816be8717b0b12667bee460ef40f70000000000000000000000000000000015036dff68139419db619912e2d19b7d2a2d637fbb8bffcd941aefe2eb4d24c1f7dc32f4f53d4cfce67785e7c328d6c4000000000000000000000000000000000fd575be9bf54128a9a1cbd366339c993ced315a840d60f8b77e035352bf705c01a9def713e8cae3001dc1062cc0723b0000000000000000000000000000000004015ed456125cf0f46fe0093b81ff9315d955d470ad756a9303f548819f339e137305c58e6f4d8db3c8bbfab90718d4f0a3851bd52ca52919dfd21efa6efc56f6dd5060ad969360b1a731e8f38f0f5d0000000000000000000000000000000016d31e68cfdc5823970c8c2ebc53c3d4517792c44e90c10f920a819e72e4a6966c59a691b905c8b0b612065c56d86ca40000000000000000000000000000000005096d516e416fdc0df552c2688c74f1c067a3e5e7fd782479bfa468096e6ee3e601bc23d2e38ae7500325765483250600000000000000000000000000000000092c994e9dae287bb6450607a4263bcf6267f0f66ba3e63436292af7f6bc8e4ba794a12792b6af49ef59b5fe50ff6d3400000000000000000000000000000000175a645988f33612e969e1d91b2c30e47ec655ad655d89cd8dac151c3bd194cd5a8c28b498b1cc2f2966b7fc37cfc8c532b41960417047a2258b6e9e228f3cc1130b296cafbb75f58731a81fcfe8c83a0000000000000000000000000000000008d09ee15c80facf7e32b15418fefbf7e80400acf37f2a1bc6ced88b1591bcb8f86b45b544646c5fafa71b5b103b927400000000000000000000000000000000060865ba68ed8fb3d0a05779c278352b22d4244edb7add23d985a2836d2772dbffc3c82c3134916e9b0900c9db6ead8f000000000000000000000000000000000dce53bf8aca1ed44bee47096dd988689c1e32e1e65a5f8dbabab7c4edba866132ee2c036aba5648d0dafa9a26405fe30000000000000000000000000000000003319995785be720860bbf48692d1507185d898187993865648ded74d3aaca45df939c6dd986db42a51bd13579a55b8f71a6f7f091a6a21dbfffcec2eecaa22d05252b60bf91b56811a833dde3fcfde6,0000000000000000000000000000000010643af30c3cdefc30144c5d7cab17c9c54adccb3294ae79fe5c69376011c159be1e43940640bf5d9012ccdbc997e2090000000000000000000000000000000002a22b08904ea9ca99103a01caad745dc2afb7b6d23e666770e81a97031de921f9d4d1c04fa941c433b8cd9cafced3a10000000000000000000000000000000010808e5518eb6cd61eec8820b9f279dba2423b1a3677e21fe3a0ca2ad49fbab2995de1c5adc9ac867de79e3b40ffddf30000000000000000000000000000000003ce1270644d71e0055345c7463d72dc119495bfa04a818dd398d944ca46deb0aee8c7936557754fa18225522fb79564,293920, -00000000000000000000000000000000059234bb6d1b66985b79e6a2c6cd158d37fc50ccd6e50ad2fe3b6a02ae2ad35b8b2be92139265957ea698441e780532100000000000000000000000000000000066adc7083a7f3dd75a8e431a36632dfcecc2527f261e961335bbe8fc8329d992880736bb41fffe41484f68c38fda61a0000000000000000000000000000000006aa0794c27d3f60debbee292c28b430c159cb2874b9467312cb857a8777058580f8a2d3b9bf4b8b732edb185cca6ac10000000000000000000000000000000000d81f222ed6acdf29437adf26d2b785535cd6d61b329df98be04114c3ae68bc6854e275792fd48de3eedf6ba7f3849a2e56b63fc6ba87cf021c2f92baec248756ddae0a4f070df840db281283a4c9b20000000000000000000000000000000013dab8066757b8bcce2c9965e600c31b792463623cd5561f7f6d55c5a52c22efcbe48b8684fc2dca87e2945765bf565600000000000000000000000000000000198a0594b5e606b18201fe2706bddffe7bee6c583147513333230715d18295714055b984cd3ff8d1127f9420863e3a67000000000000000000000000000000000ef77ae1e991daea1fe8338cf236ba959b22df4b24f00c6c01483b6956c609b805ab89712f80892bc0160fa3775907890000000000000000000000000000000004d30f5a866a100dfe469d4d0c47872245c4cdcfb18d3ffa0de422691044b52d2b9335682dcbf67aafa9275712ae3f5512a50af55f47fdaf176d4885e4910c54428c8ef433ea0cb1d009ea77783559470000000000000000000000000000000008010bd5fb5e222618bf4f919c203dadf9a7b7597bf90e16772020614481a0963a8e8b1bd244661bd33e0d147be7663c0000000000000000000000000000000007e21f548efb869a28d6fe39b999ab7fedae9cd6cfa531fe608476ef30c8703951839476811838608dac1aaf9cd87eed0000000000000000000000000000000006cc674c464f80cacb2156fc1eb680938cb38cc166a99f72daa50f9d2c40f10ff07e447d7bd5e59b6b22f0dc407dd8df0000000000000000000000000000000010b160c58ea82bc3904302b1b4fe83d1883efe3c8f52c4e05a3d8681e604eabb1b7f533e61c51e9a172987383506e7b189a012158b3c18e19471fca6d5aba5fd004a337d49ddef5db177da187c8cf1c8000000000000000000000000000000000d4f8372d1138489913654a7567735be1eacf8c7fd497c2216bf77a94f483bd4a7daa2c8232581d6815af9898a7569b80000000000000000000000000000000013676a1f72cc2ed80fa24f70fe1c52aad9ac13ad6cad1f519649fda6ea3787d86ea164d9ac86de96436e9db4fb4aa44f0000000000000000000000000000000003f7644f7ddc9276ac36aea5c36451f3d5d6e4c508932b16d5677977108f04deace5e8cf0b3b3dee88c328b6030f3567000000000000000000000000000000001953cf03effb7de9e62937572850e9fdeecb74fb4aa655de1abdc6e065920e6d2e51ff28880f33341443b5e6652eff4827dd109f6df1d9851dae28bcb9e552c6b1e1b2dfb331aa955d3d0b6c4862253d000000000000000000000000000000000c76a5bcbd2a61172fdd53b351d143bd30d460e398c9d4b7094a604ab2c0d46d6112bd8a5483c9935f0bf6d84df04b9500000000000000000000000000000000116b15825b780c49fb24617dca620e939e2528e10c49f34971736c82cd35fd3965088595deb86eaca3d3239c6c78a84f000000000000000000000000000000000a0cdaa541dd96fefd46b303b88f1dd4d24257b910a596817f1d946873cfd60ae58a88aa687ba573832331e8fc158db50000000000000000000000000000000016259f7285de159a2c6d6d8687ed348ab97e8cf329ea5de49b6d708b6da5b806bd012ca3641c50f479d85921e20fbaefca96785c1ab66cc5c8e434f59cc1ddf76bd78b6fe660f7cf74cfb79d7f2c7f84000000000000000000000000000000000797e815a98d362e1d7e2ac1fdcb477ccdec8ecdf340d7bded36856ce30e92b661669b38ecbcfb0896b2fd75df9b734a0000000000000000000000000000000017916c559db6b4b28b798c2027e2c70ba1b940212df8a1649b9f6087120660d698bea81258e2007edd4aa7d0d535bccb00000000000000000000000000000000167170a76db0783a8c3228f8246502b15d342b019fb44a46b514f4ba2de3ac66e435941adc3d91874371561870ba87150000000000000000000000000000000010097a585eb9264ea96904d8534820be185d8d9e4b1616439a926c0ff8ceb6f2bc082e5712454690c9c05b8018a996235aabd1fba36142bd768339e091b15b7f5b4ea609b57947a7187c498bd9833c2900000000000000000000000000000000025eff57e1f37903056835d1b4133ce064c86947f35859817b2cccf1a5c3923ccca766b3e0affd20a4a6df62a45c31000000000000000000000000000000000011158fce4ade070629162b2b6cf1924696f1f7776f3d623cfa3d54c66fc17fa0299c6650b709a1472262fc0abe8d9557000000000000000000000000000000001828a65fb90dcebe25413566deacf0677a3993b39d68854b264fe7807097fbd3106ac618545d3a6a42e197c65f0d2a7100000000000000000000000000000000045eb8164b6ec874467286dc3626fad3c01be61f6a8a88e5f88797978463db648a9b8a1e1a2589364ef2879cb5f75423fbe608fefa5472c7d1611abfa402d2eddb1e54542f45d4012db8ac6d1e5016100000000000000000000000000000000011847bdf2f67b40aac3426716391da488a8f0462b68bd35a8c1c762591e2f426f48f979a646a094bce16bc99cef7fcfc00000000000000000000000000000000092d61e408120b1549fc8d2174572eb7ed3f679327cb89754f326fa72fbff79e98cf5ad9c94c14dd86135e9aacc98b98000000000000000000000000000000001440e2f4ee2ba254a780a31b02babca093a38e5a1ac09ff388080b6c60918ae5b26e1c0888ea0976527ba103b257d02d0000000000000000000000000000000019797e49808b756128866fae0d6aa7e755a1d6f07f7e6a877bee150fe9adf0cfe612350c5a0e31d68cbeed226fa56f2a28d57066cce439d8d0385f647ed5e9b29e8fd0528c1ed8455f37dcd81f4b62240000000000000000000000000000000016d723a64ee06a7a631509c6e64b1c8bbe199952da532dd92194147bce9942cb4a76f2358e6c7d263916fa36e2c0c09d0000000000000000000000000000000003d04ce655cad1d63748f6eaa9912d6474a34820986835f60c812aee9980d3ebc18d6fd856a6de9546be024b2e95126a000000000000000000000000000000000ea840bd7f76f8e944f95146cdc9692d97e6a2d7d16d4a7f054f81888472da4d60ae5faccb72d3a05781b399536ccb1e00000000000000000000000000000000155a1c43c39e9dfc6d96e01c981662900fadf1a46aa1c2fdb70bb34e94dcbe86c4f255e259c771ea8ede50b388ceb2f61208d8d328014a6b2c8b2b9edc70589cdd63d38f4e70abb41cff1b7693bf9a290000000000000000000000000000000008f189d97f7d82aad87fb71d090a5c99d94079c0b74beb3dc860d440c0f46727fc49104d671bdcbe5b9551552e18afc60000000000000000000000000000000010c4edfb64b8932a617c316820cd27d3f6ffa89b471949362762af8e10d25265b84ca2aafd3b14f33c39a4b533da60d60000000000000000000000000000000017ef3bb919b087fb6745bbf115e2929394fbc9c89f65e7d591f15da93ab785aa6828ebb6ced99d3506810647d28ed814000000000000000000000000000000001591d8213ab349017cc93f1fbe6aca6765dd33ac1f468621e2c79e30aa73bd7606a0e5ba1d97ff03e0029dbc8ab1c5f4d3a2044ed4f938c17684413625bdd281f685abea2e375bece77c03d697c82cc20000000000000000000000000000000019b3a2df3a9571b066eb451e34d8a38c0d90b6e365862bcd92ae76195956c21c59441f0cd03cc69abdf4ba069759b87f00000000000000000000000000000000082537ef7f4bba5f32db4443abc8eabceef643b0878ef83860d75ba508369a3b459cad96f1cfd872df99548f656b0f9b000000000000000000000000000000000b2fda5ba0c405c9481edd598181ed8a59a8a18462508af8c5d66988a7a58a5c9635d93b5e0ee310bd35e0091fcd4986000000000000000000000000000000000af7e15e0052576f82e36e7e2b614dd835a290e05f2ed9dad7f508b4c04e8d437e7e937a7f4c88b5e66b06e0beffc4df7fd81e27a577b5e79929614c069d6d52146a6183822d25cf1ef84d8afcc1f6b40000000000000000000000000000000017a1d5add5601010d138263b4793149a02e8f4f7cfaafb69fde7b843a51cf5f0634e26b6e5e3315420d44b0fd205230d0000000000000000000000000000000013ea863ebe1b1cfcf4164d78dbe8fc809d2b82ef3e5a2589ca1357e48dddf2696e910a90301ed910fae77a1e462a5b1000000000000000000000000000000000012b40d9f25dc5a61454ddf1fa9c38e87eee60e55938b411bff9cf2161ebd7d3fc930131a198e7e97dc90cc245164e7000000000000000000000000000000000054f19ed8e2682caeda10c252f11706e7f3b65c81e7ae0a617469babc5f3268fe5c0ce2e85d44fb6731e8ac132b97c3ac5d47ce35d4ede84a83c9860322f582ec00c872b4b035d5d340981fc29884f13000000000000000000000000000000000ef0378945ae4683666099be36de3e60b5bae9c3137b702e5e4e35afd5c1e81d033c3d6b1debf5bf36bdfc4e3af37759000000000000000000000000000000000c37074af84ff596ff2c7ff963d96968464d6c8d88b69af64ae883457d02ee9ec80720661f39019230a6531a0f2952bb000000000000000000000000000000000454e8aaa2830f07d86eac7aca1d7589fb06aed646146a1b90f4959b5caed73131ab231313b50c15213f89566ed87a3600000000000000000000000000000000143516cd7a1b8da41226cb828887a0b3314cf4f87c207d1d84e9c49f0f7e548ab99e635bd126d49fd2e4dcea98f3adf784ae256d47de2d49b1e755cb0e972f3b614f3e7ba779c65ce175ca3811021a7f0000000000000000000000000000000019384e15a8754c6d85bd298ed550a26b51b714745bf2980b4920d6e73f59e657d85d3e86baa9bcf7e971233daff99d02000000000000000000000000000000000229d233d605a1a9f060605ae366a263594d8fa2b7797358ffe4c62431b9718d155d24d80bf5af1c806f447b92fcfbab000000000000000000000000000000000bbfb66cc0c7bcf251141c540f712fe9a359d1ed36d228379a1f3791991cccb7dfe1a10d40667ca062cccd55c9e6b08d00000000000000000000000000000000150a4d7a003cb81423604c13d0c5175183ab5f459b96842939f5c4cfbb9196db4667bb4382d2d5c92b70800adf384569a09d0136d4dbb3abfabcac55db48b1ce302067f413283fc1a21744f1c16ef7b50000000000000000000000000000000016352fb8e2751f126fd0f889f2a62a85b95c50d6bda7704112e4487dc94417218b0daa1dd6b998662af2582c44b011c90000000000000000000000000000000016bf4c60eeaca103c90643fe0969c2c261e9697ddbc02279f0d5afb5c905a984ab2396db93555cc2dd5682a1525446d00000000000000000000000000000000014be742feb1215cbdcde21e974c74e23c7bbc2cbfaaace28cf1d4f2b5a77dde2f3910aea74bc200277e6fe0475208057000000000000000000000000000000000bf98dd3e3a8b13e487d8b1a35615b0c6b0f514f9b8da7d6402586f113974c8dc9561db797a96f4f8040c1765518d175650a6fba1a5eace6b455ee780ff266c324f49801832640856a80098f0eed0b7b000000000000000000000000000000000362935e552dd01b5fc5a15a76faae937d7ad086b0a67e9cd3558287274106623deb85b6410bb4e64c424d44335f3b1e00000000000000000000000000000000096f23a54cf57aa3306df0a0a4f45aecb9b09bfe83878d551a59c53e18efc5a9f177cb7fdaec1648f66cdfaebb15c61d00000000000000000000000000000000135271fbe0cc0987e82f3430eefa8e3cdcc1be4a441393bb3fac0b8e8f78dc47ba2b833d9dca4277bd60befdf33275cf000000000000000000000000000000000dc1b7512fa5f9d4ea3f4229d947f43d7dc46b7770aadbd7351b6d48d525d0144183f2c84293c63c68d5262851401ae0282cb1f8f6d6dd81e7c49176503a76837a96d7f2b084d29d11dd9c6548cf0a57,0000000000000000000000000000000001c11610b63eeaf9e00552a230bfee290ea49bf9c93cfea1b6f684c9b5a07f341b718a0070534e0da9e6ab1239d800830000000000000000000000000000000017e8107113714ebb1743c34d83be3acde096bfb6cf140e943ecd0831ecfcd097f58d25a45005db61551a01d9da46de10000000000000000000000000000000000c2eff6c7c25885c514aadecb8f0465a0fb4385eadffa082e8d4f497b10df2395be5e7760a87bc26772dd78701146b730000000000000000000000000000000011ad4e20f5c1518c72f75d67a897f30100dbb83365ef7729c3501c6f266d6002edcab8c8bc1f449c30ec3624cda13809,293920, -0000000000000000000000000000000010a26eda028649789d9e01232884e4e5a6b8e0b17169b9d64393e2568b09ac4d3e61a5996108655c24e76abe9e3fb7380000000000000000000000000000000015e4e36bcad524f8aac7f909fbb884e879caa735f80fe9890d7874be919ee727beb2a074984c047dac1d02b8712afa3a0000000000000000000000000000000012458946f1e853a7a45861a92d4ce707e5aebbf69edfe69190c0bb130141a10869e2a73e06785b568248d5b1f647e63e0000000000000000000000000000000004d8061f25edb5a510a2db9e1df850518156138c78ace50f4c9ce47734a0b14352f5283083a232602a070c3ce94c7bd93d7f8fbaa4225f3008649eebf42315785ccda2b9ce922170e606876881825cb90000000000000000000000000000000000baa40ea518227b007b9714ae6eb5a4e92883dc75e6328caa780bb2ffee7573dcd7e9ac47821ac449187569986bd2980000000000000000000000000000000009d43d61f070ae308c5c285915600dd9c17b7de63cfeee6fe33c9ba857b3c72e057bcb4d4ac2b492797e7d785997c18800000000000000000000000000000000185215a7fefb96b3ff9229cc3239c3ce5202a97e275ea9b1541d7bc0a2931d7e3b01942febb45c6e96e66e3605744afa00000000000000000000000000000000103ae58b8066dd62c46c14c593c768fda91b90e4840b5560c974ce69b86bd6d2c13f689b72cf9619e57c9dc8e3d3fb15e71e6cb3d4e19f4a70a4465df6eec6326f558ee1cb99aa540ad2a73c363a133900000000000000000000000000000000075585f862c0e0e031efe12f31e159f2a8b89825ce80fdf65906474f0155f397fdc666292f6a7384cab790f071335c49000000000000000000000000000000000eba3d37a5cae738ab99ed9475c2c7fbb88ff54edb8490017162dbb16c8225102158a266fd4ba7570ee6d5ff6cf3f5d400000000000000000000000000000000135a0b0a38c036919f8389eb7bdc505a375fd75d513eecf0cac134645d60fb6030a437ac6a0fbbd167b7a77a927b3b0e000000000000000000000000000000001688fabd4ad751598ca036ec5ef6d7b314980dce7d8652163e89fad01b233af64defbcf352743ec478af42587f58177dbdb2b3c3b8e91540dc2724537526fd8c0d4b85d2cc20323d71fa5a4f61b3f12a00000000000000000000000000000000062a74a9ba0e2e8d95fca478be8d18fc716243b1faf7365a55387fd7188021f53bbe780e973e7d16c9db236faff176cc000000000000000000000000000000000f949be3fcf9b38995624570fcc9e7df9964d038eca189336ec39d9e0bd05148ff7df0b48436a2cf6e249e52248ee8a40000000000000000000000000000000007472e7c366419a0cab844522c46356acdf6a12cffae941fae3d3b78e7a83f0446c945bdf7b247abccaeeaafec49026c0000000000000000000000000000000006a564e6860b97feff368fc9a349282112e591a7a6987fd10a2d4de8ae4384ce229b9db9a93445f727eeec55a6fe5a9def0c8574167a3bd3b794f057ed01865ea69c38023dbddb0afdc55dd9523ebab7000000000000000000000000000000000c073d2885eb125d3e7db48127178bea2c5bb0f09eec7081f15bc6fe6cba156914fe1b1fea6cf14a21a328d831523ec300000000000000000000000000000000010d93564b2facde13d29dac198c5f5fa314a0398f30c6fb7fc9575bc83d4e97edcc1c1d34f78728729442777718f54600000000000000000000000000000000136a4ffaacf0b4a607c677ed343c1ad41a1eca49c7c48fe73ab2f74084a07cff18f07f54a7f8ea1bfb7fa3667863bdc8000000000000000000000000000000000fb0c007a907ecdff7bfe2242097caf0c5001124d112689a74544fe4fd85be9771632e7267a1cc7e9f66d7e4bb4c954c3ccc75501428d3be8bb469ed0f2df7dec10e1d205e11a907cc30c4a76eee3cc000000000000000000000000000000000032bb9f20fdb19f578fac3008396f5dd0a70860f77f8ae7771fc6253569d47b72751cd56bd373dbc5eadf55b99578861000000000000000000000000000000000c4a4bfb5ca6f9c1bd69d7377c6da405afc3128338dfddd9aea19aec5e1e0f547e3febd28445af5e27469c87c4ac15280000000000000000000000000000000003b551547af253d07625028db4b9a8da2a857bc925620c5d561bbcd3e063eb460d9407cd4d4813800551e5d0d23a2ea40000000000000000000000000000000006d5c69a251e9a042c66bd4ee92d4f3cd4e79704b1b215c15b319e09cae0d798eb201be24f407340dbcefcf2cb87da5ae5e403f555fbc800f1342275f18a73dbb679bd31873ee87617090912a52d6a55000000000000000000000000000000000a5802e388f7605bbacd0bb65ba96689e223379214fd7a92de9a313f55d66cc71ffc9ab3f9979b75edf55647ad3b6c94000000000000000000000000000000000f86f968b5c20a81f18074803e1ec55ebd73bc87451c48d5bb61604ebae46538dcc9d21cce062abc07b4b9e89c85bf60000000000000000000000000000000000f9fceddfa8fb5bd76fb7c8986372c32ab9fae3c26e9fedae892bb55178fa2f3432e6eab5043496dcebef46b20bf5824000000000000000000000000000000000dcf7a118881aea4e6a0e4e305910d4e4a5f3d0a8800f52659ac26f122bd63c8aa2c5583f1121275adc9af1800a007fc97ea57a38598204c15bf65e7270a175460510848540ca4004286f3ca09eb59260000000000000000000000000000000003ee0ba2b1de438abe66769124b97a951ce18aedc8d9ed005628aeebd90efd316e7a3c60cb5a103d6f72e7a40ed8f44000000000000000000000000000000000119597c99a7a16d8d35937ea15539089741363153ef898d6bb177d9a9b6c5bb4b79728155eacc5d82571f398ac6c32a200000000000000000000000000000000116184ac845a28c4f96641ec19a07e1f8326bd45e2106148f40277ae6fcf200d64e326915cf5c927222def8deccd4ff8000000000000000000000000000000000f890258e70b973c0d69492b2e7d10ccb3997798503c0943af4255c13b3856ca4007b18cb9d638d5d9cca71c368cdfccc54dd8cbe68d5151e4428d35ec2d5b5cc7f5e455207c0788a695c2d7fff6735200000000000000000000000000000000171035755bd519af04efdd477d407267c5a8108bd32dd6d3f1b9555f15f37ce7598c096fb5301873809f0c000457a4a2000000000000000000000000000000000bd35595246a8337a426c50c02299f297036f710b0979c7f981c6909e835c0d9556cf64e2676baf952a787e10d604f210000000000000000000000000000000006600ff240aaa026941290f49ae8968e72293ae7c2af0df1b4ebb9373199b95fc91feedd2782ce819440286aeb2388c50000000000000000000000000000000015b2bbffac097c27944143cfb22e38ff8e50e79f2336e64c8496b0b25892834efb18a765e26f1408df1d64f4b9b78fb947ee5651c127d7c8ef65ec68fcd97d1dc228bffb5bf1278aed3eef8115a5ae72000000000000000000000000000000001064bd04edf96a3c76d2ace669ff72ee5edd87d32592213cb5a6a4a482154c1723bc19c7c530d164c31626dbf758d43f00000000000000000000000000000000176ac06390e3629bdfa282bf825c0bca9bc4e0b8fd90fcf2d4ee456d5bcb3ac2882d8406d2fd59faf10c8327b1962124000000000000000000000000000000000b58fbe4e14ee0af03d9aac4131abfaaba43c7cd92d530802516cb67343b382a6d2af9399d93b43d6e05f7ec827d5ae20000000000000000000000000000000000bfd241e3180cd5ce9de831b24ca50db23685bea7e008be0c6ead11abee338618728968c25a8e5a916cef8aa516667214ab6a1d0d3f87e7c9df0c14b6fd2f9d0cd755d5fce5f40bdc8174790901549b00000000000000000000000000000000183ccf0ddeb8573923694decc02b8f02162037156a8f6523ed178c13113d094521c3d9257febcfbd8f15acfe3d5d5c27000000000000000000000000000000000cf716097aabb07979ee435cf57ae36a3034283eeec0771bea24c9a1a15ea106201af8606d3fc28ad8ffbea2cf274458000000000000000000000000000000000b962565763c4cc155b2d9ea104e754e5fb4745303240688fee7e2256fbda82dfb515a51096be5ba0b111637b1a25438000000000000000000000000000000000df04aea745b9df2df0e34153269958d3640c1596fdff3fba696801c96371420a3619c5ace9210af7e0de4f408b09a7729b12cff5a72f27e15032844fae50e3cabbe31a69568bc4b5cfa884f62e7e204000000000000000000000000000000000e6be3275371e533a676f8d075bb2ab8b0216642ecde13425bce4ffa8ac51cb1b4c5c789d82387f5355c27f18da556400000000000000000000000000000000009fa3a3df5195203f967322cee54a15d1e0096922b6b881bb3bce54587fdb82931c0b87de7a9dd1a21b4389a34d161ba0000000000000000000000000000000014dd5455deaa5ea4f9b5a6241c2e8b2230fabff9e1ac08b359f029f4c7838201cb88a92a5b696ed47819e4866512fff300000000000000000000000000000000181085d630d1e24ebf79bfafa134c08c0e75626dd400ce500392adf4462028bc714ca07b28b8b8f15c9cf2934a299c3092c1b10d980826351c3d193a0f54a7dd78a3995efb02fe5b4525fca8791b1c4f0000000000000000000000000000000013b60e3be9d7d43eb42f7cc2c0a7efc81c175b696e82b034c87d1238db2798d9ad6534b86992653d86755b4f00cf989d0000000000000000000000000000000009dbb325624e698c76b9d697e4f7f03e502ae1cd43b49a0957fc067858e20e8c7ede3577f336eeccee58cad53eb727560000000000000000000000000000000007f2f50be2c6fbc500ea347cd14ca195af08b835814ca515d14dd2f6078eb6def2b9475c2ce370780acf394065032d0400000000000000000000000000000000109803d612b9e27be5725f162d061b9428f363493c17eb39c097032039387d96d0939a06466470ab62ff507ff762fba78f715f35fc967837facb515ebff3df502223c29e7089fe6d2e9120bd3ecfcd120000000000000000000000000000000008a9fcb462412c1065dc7c3623ba5a980e6f86cc813b5d8eca6b1b8a302ee4176cebc233411f2c9ff171332c66a0d46e00000000000000000000000000000000058d2e7ee02bbd4896b5bcaac0f2b09c16d1664209710945c1f7f1a53e24496d7eace99488debb32afe10d7fea442cb800000000000000000000000000000000084d7600bcb68d5e375457078672fa07ba2c87c8ec5f9eb7b61a0232988b197aff052e7125b33c6657729ce8a1c668e2000000000000000000000000000000000a07c42468c7c65fcc984bbfc2f05bf452daf17d57e669ee5992ce67517e1c93b5f7f4c9434d40f3b9bbdb3446ddb982a9e49fcb12c0b1e9bcdbda52e9852ee0e98fa0d43f7476b3d65ef5370c9460a3000000000000000000000000000000000ec380d15e0efd71958978b1f9298ced4cc3322e472d03830ebbaf2a4601c8371e6bc1cad047b0e1e429ecf6fc628208000000000000000000000000000000000b278fcc53b7527545ae1340c24158ff662683919717c220e7d2838a853fcc84ce3915f105a932872ca7f64b7cf096ba000000000000000000000000000000001520798dcd146c0b39ee727e8276fd998de0157a68587c2fde56cd82a9779b6ffbf745ec151210d1e9143856f24f01d600000000000000000000000000000000175d53b992d750b34f9daa39aec918a0ebb2f539db8057eff1409492c90f79a00f14a4c53445c028bef5d6372c9f80c680b0d6316c5d62d41fb0399256c5c46ebe2a12eaad835d2c7177bb7325e21d3b000000000000000000000000000000000fb3863bc7b468f1a0ab0e4701ea392bd820ec5cc2d7d86b58949002f24c972f51f0f82400fadebef13b750884b35f9e0000000000000000000000000000000008fca1b30d4e01991811679f261d11723086753e816239c8c7ebb60ce9ac0ea207011a69cdc29e3336e8f589b71bdfde0000000000000000000000000000000010696ff9d78b48743abdc6c1f4b44b4c960aa516623a24da515206d95e65286e453a8f275d98aaa09fefea29e71b5643000000000000000000000000000000000fb4b5eb18b6f6f8ee7dc734e8bdb625a403dcac6d0cae363e5a7f3a834c8eed5f01fbc4dc752e228c41f3f9d992bbe01b96434f34fa3e00ee0cfe548a2d2ca29a848cf1c52f940685caa9a227e32a61,00000000000000000000000000000000165baa8b143e3734169986e68a848739ca05330786012de260148cfd0810ffd5659210855f19ca92566ea0d6c48086ec000000000000000000000000000000001225672112e0476418288f381165292a9aabd009b0d9e44d9f8f00469b2c56698f5f985ab6292c9dbcf73bcf610080a20000000000000000000000000000000005418cba24a43fc7edaf2fe77422a0b2e8b38a45415e13654c6176c8f7cf6bb2b80401534154cd3b23e977af589eda9e00000000000000000000000000000000067126ad59105621cb0931ab8f386570b54977563ffd69c2231c56e7961f6df2c5d7b114e0b1ea176cbfc1d657127286,293920, -00000000000000000000000000000000139cfd67c3365c5b4422063d7901108c9f33e233bf6413ba2e5b2ad62d188cb50dbd3dac0f298aef7c1d621249d4b0c50000000000000000000000000000000012fcc0d5d09cb3d86895f76ac3d3e9fa9b2495110b0276e7a039d7d2fc2e48fee646fe331c1d8e6f019898ddb43dd09b00000000000000000000000000000000159356eb3ed0d4f146dc929aa6c77057be5ffbb064432d3fc35d346f19f6c1f8552c7079e27f3188bcf29941375e62c9000000000000000000000000000000000fbd4e9a57aaaec40ef9bce8b76b529bd2261d373f05fd69af58d1f23c089497473e44e937b2617a92942af1a99d031f10e0acc22c43080ab9cea11a60866feedd57664bbe6c3f0366beff177f66318500000000000000000000000000000000022ce2d2bee57f7567e9b52ae8e913c79e3b2dad381802ccad317b525be0b503bdfa92722eb0c21fdaa31fce2421ae300000000000000000000000000000000001177074350288dff9dd85dbee758fee1400cebc173793198a96c0be3bb810d352720e94b9bdcc6f5a8951b3a86b2a0e000000000000000000000000000000000179e21de58ff76427f5ed7c8ca3058d0e5e81e436280aecc75a3d989d1cf11d41734de22bda74cf0dff175ac789532b0000000000000000000000000000000016abe94a49f071fcd5e24b5f3a837fe3fe7c7dc53416f59d0469d71f144f71ade4569bac3aaa202a8479c794bd251645cab0c230c354cbf1a3c13c23a36ae5f2d5d084d7aaeb427c580cb6b9bfd9df600000000000000000000000000000000018dee638031a3c9b1198cd4a4f267cdd66849e2b80e3d670897d9e058bbe772936d827eaa4e78283d42ecd25eb4b22e200000000000000000000000000000000009c04ef31cfda7c086a31341434a1698c1132fb5916d359a523b98d05d57bb38a1e2e2bb779d4762f9d4ec24fbf2564000000000000000000000000000000000a788450652e0bfae66889c66b0dab8d1972a626facb690f8e4ebfefc7e1a7b2b58f6eed02c1f10a74b140a49b6c5de50000000000000000000000000000000009e48b52f2b0548dab1c0d260144ad2e66a22e0f1781f94071b5a3a08311d11dcad6963b4339fa63bd82b4ff0dabe685290608899cce4b3d25f57519cc881eb748e9ee7e27f7b21d69f5d8ab3650c3e8000000000000000000000000000000001319607058637d4b796020cca79d62af5862b1c186f32d99c0ff53a830888f297ac4389582f9fd010534d824522e6fe3000000000000000000000000000000000608ca0b4806f17b59a805a3f9f75e7a33ac0791e05050d4eb19f2d4c845fa4e4c738c3309e24a4524b6bfe716949ab5000000000000000000000000000000000a6a6201ec077e113995acb81d4d07d0c4a085d367ed740d26c4a0c04ddf28697c1cf5e648b25148888617ba77ced5e50000000000000000000000000000000003eaff54800dfc8eb3ce647ec4ae8c1aab6a87d4853a1ea061a5e6367d8ebc94243837d4752a1933f7eee0ec1ffe68c8b71debbd9f3be5d6e65e837bd78605d5653fe63025c320cf49c035ae66d8ff5700000000000000000000000000000000122822c91bfc4f761b65f4066a94c0eb1f53133a1355c019f04003e84edc5095523b2ce87ff24bb42425ce979743ce31000000000000000000000000000000001928bc315800ae9936e5b763bf29b19a9aeb71268cb47706494598e0ea057f9dbdda6733d9ea165acade87bd89b3ec12000000000000000000000000000000000a87c1ee17bcd7d348ed1a5022bbc7438bfad06172584dd8e3b51db4b3b09645290382ba991df37db0ce562c950c0e6600000000000000000000000000000000127c80da591c3ff8d300bbdbe27e0aa21b5edc1c1fd8a5da27f58a4dec3971b3c4f9631bde244a7072d9c19f1c0a46be250f62ee2c2972e751b36d95a578efd2fa5e0a2c1e29475a3cee48a28080cb0b0000000000000000000000000000000004bcd0a0321c3c7e6161cd53254353905c27d965f57c9783c3fa7cd5c55a5820116415ce45491d5d1ccef6017ea4608c0000000000000000000000000000000013a30e19c43a1f466c0c3ebb5cf1b57c44434892b18a7fde18a2a29b09a5b4d13d26cef871d689d9855a73a43d22119a00000000000000000000000000000000066d6b3c9a949049413300ec0398d605277911d7be327b1d816cf25543d1b2d7c31d912f426021e612b56ca288b462450000000000000000000000000000000008549f4dfdf018073cc4e32ac930397659ae7a59ef42ca4f864b26e4635c2b7669186a107e9e91c35f04674d2be46051ad08c3d2c36085212542427c1760c72f22838be5286402ef87403f816f4fec950000000000000000000000000000000015900fb486bd2c066cea98e51d30424681fc3347a1cfaeeab65989d1adba104a362837bee51b8b953ebb520feb49aa6c00000000000000000000000000000000198ccab1f94fa910f755936e357a92d358e00cf406894b46adcfc301918c4fd7cf7200a1ea515343d577d920680c83640000000000000000000000000000000018d9380a8568adb92f8f9f67c315f2a837d542b32aa82d9bbf5db6dfea27260738bd0a03683a9988c6c3370563e7bb8f000000000000000000000000000000000528ad42f23c4e21a687f2303f495e962b0a90713d6ef3abbdce38ed166ffea9c132e50c5b002b2ddbbd4933e9a1aedf6ffa16b6fc4cc9509a2b8d8434fa0f4f38b4cb4eb1bf7f545f9f43b9190cad890000000000000000000000000000000017eb2587aef34b03943a170d91d99aa16ceb2a36df3068663382ff4c135083c998743f9145a2fd5dd4ce3bb8b64cf3fe000000000000000000000000000000001256fb29c7482e5469d64183e3e848e5bf32f9c495cc495c3f8cd8e46f71c3f9880f875cfe429677615a6803f849952500000000000000000000000000000000146e2f329f86ddf5b0b17c37aa2905122f457c2c812782bdc15e132468af48c49b715e3080da504d59414ceb367596f100000000000000000000000000000000022a8e385972592430e76bd952a700df8d35b32deaf06c60173d0048d6ea22dad95cc62300bc1a60c6452c41b32b504a1271d29abc5f972809461a1afa5eb186dff5e28f20311a1d8416f8d54fc4b2d90000000000000000000000000000000009c80b3191783d235814fc86653bf2f9a32cb7938111408087b6ab5bafc480583e7a2a32c6bee0ee4aa867ad5dbbf77a000000000000000000000000000000000a09af60eed6c47a6c2615cbfe62025530b35727b42fd812032671ca1eece6694aaae259b05906faf7fbb54362ea890900000000000000000000000000000000055c5f0818f41e5d73e8cd5f70fa77cf477cad8dca2a88b8970a3a25c8f38382268e439642518f1974c5b470cbf29699000000000000000000000000000000000834e44669043aed8ad47cccaaa7476ad830e38fc1def66aa7e8207e889ac0fa1a931eb1e90aa6e1cd694bb95056c3e63ce55b3b32ad29dca1a0c99771fc8f7179851995d5eac804458edede9b8dbcd000000000000000000000000000000000190f8da34caaf472ea9b0f41851f808bba402b9be4baa5d02d1bcb2f66acc3172abe78a49a653cd24dea402dfb972f670000000000000000000000000000000019931343d0e59f0f0a060bcbbeea92fc4670db510c017fd94e0650ace68c2925c627f373d8e755813c199b79c70369f20000000000000000000000000000000013ee811cbc036d2786d8ec0339627d6134b10517c8858f6c6db19a9319636459ebaa217649825ffba32a224175267de90000000000000000000000000000000011039d587f3323ea9d3c50027c427fbcbbf7e097533d8a5f7a61520f3eb548c399e401df0f51884395ad6a338c0a3500c6fa7aeb016b3e3f599846af83f426b9ab85b6857f901c49554d03d27a390f5c0000000000000000000000000000000011d5791e9bc632eb63bff86aa433e6df463a84570b779c913f67e77fcfefb6af48f3df2174096a511ac35eff64e0e5f3000000000000000000000000000000000282716505907931bc93748ba1729777b959d65aec5a78c9f829ae6f2a94a022116715a8c2a653a832a62625473a0cd1000000000000000000000000000000000f694a16ce7a69f0261a0ae19478003dcb61bf93a2ff39f940fc4718a38b9f4b6ab13527c5b438d22499ba29c0b5461700000000000000000000000000000000031eab53440757e4065804896e9e811d459665598546796d67472054fa60e5da8685d8e847eae342e44730056757c6287275a8d16c02389795d54ebdcb70a39fa885320d00cd4e5aa15967916e46c61500000000000000000000000000000000138862ee422bc0f38ce3e27ed3c1b71f71a03d61cc474d989b0cc824efc512ef173ef17bbfb2090997eb9435f4d23e0d000000000000000000000000000000000fabf1fac2ffa25d9c8cbd49b3db5dfdbee52adb947ebc1a3423c9fa2f9d3d29329b60ce0c1c739c7fc6d5a5d3b9e96400000000000000000000000000000000090d92e8763d4df49b8121a50affcecfcd632923b5fede480a3ee79128781f3f49b592d8f65d30adfc75d8a1922c41b0000000000000000000000000000000000074456b341565b13ee3862bd87b72f9d01754c7715751738c5b33ee85e3d8a6f731d7292bb485b5fb59bbf3ddf9b0d0dbec9767ed2dbde21fd8f315ed6292b5b0b1bb6daf2b62665c34daed00a679cb0000000000000000000000000000000007b85110889fed72b3654a8632625835cc041ff0a827f3e1b86c090d816d98cb3b4be66b6e573b3dc05b1998f2772f0e00000000000000000000000000000000160524507679ee021f4307e5a9fdaf01459cbb9a3fb9dc8be5599431e2a8bef38bf8a05d601580085da503dfcf57aab7000000000000000000000000000000000f98e2e7ae9cef2b1d954b7f26fa1755258112c496605c3c77408786d4b210e51c76f10870f558296993e0ddcec3d76e00000000000000000000000000000000068841825f5f5d8f622c1d43bfe090d11c6996688589c3d644ff5da47b94c0638128878d51dcf6d43637781f0ab21a68ff634fd89223733f407c242e52f034691036c7ca69f30e6cd444c561de9ebdaf0000000000000000000000000000000013ec97016dc3d6a3cf41edcc18f88f58b1b88cb2616bc2a8f96af3e7774ec1aaefe86a86135a20ab7592c874a33a8e1b000000000000000000000000000000000021dc7e4be6462d64ba6c09c2d326ca0164305dbf5ca1981f265a1e50f1a646748ce66ae07297230325937faf60709e00000000000000000000000000000000121bda2855503ef11b043301cf331a0fda6e5914e5ca657890ffba2542d908f8fb02c2c93cb4ac4fe5bb92eea757ca7b000000000000000000000000000000000386fdda56c778a7552dce451a6ade55cd24bf9eaeb837ebef898e2e868d05eb5edfe97bfa8eff8ab7cbfaca3c918910461d349e9711fa701b92b62dd3e3569d1203b6a35ac8600367a4df9a9484bdb0000000000000000000000000000000000763746ba87e8bb547180b0bf18699ff74f11154a06cd77a76cc9c264db7c48286fc52e3ef2d30ca914cdcc5c4ed46ad0000000000000000000000000000000018037afcabd273413eb4a712f5d1888249dc987a6fdb8befb92c02660604bd11deb33f283b37f88880cf1be2b2e71f1c0000000000000000000000000000000008ecca3d1652be4764720ef13a6ed6164a3ae89d160cc8c2c8c37bcbaa52db0fc0de84fbe2a19b93b8100556fce0fc80000000000000000000000000000000000c5727babfbc5c36c1d57b9f69c5b41823882e0196e9e0a89d5f4380c4257818d90b1fa6d782e774f2424209bf2e6b5fcc110fd7a6ae46ef78c0e26183e707eb5e0a2944e3afc09e435d56e91584b93d00000000000000000000000000000000142d41630fb9db2f9630e4d5f9c13069242fbcaf1dd02f93224174567c3f944fa02b9791a409d9236d89df6ad785e8ed0000000000000000000000000000000002fb5fa0b3a7cef16e5638f217bb946085fba870836c618a7db9b4394da9144850572daccbff8208f14c8082aaf1ef6f000000000000000000000000000000000a6be9b4a6a9b96d2096eb3a95780f11be1e13bcb6e625517191822403935c52cd40481bce2e782c42b11321cff2cb7f0000000000000000000000000000000019e2d94e35d608a50b5c8b371044f6410dd6c1988ec7a677016d4b52cc3f21b82fbaa7db897f7107d81a177c31f8e52467de5b9bee26b26b28f81d96e880a3f07dd04eb56c15314f1a789436e01adcda,000000000000000000000000000000000a6f3fcd812e3878cccc6967d49b104599fdaa80cb5dee7298c3fdc80477d277f2c68f1c941f6e03441eb176c222a448000000000000000000000000000000000a4007cc5586d677e7945dc8a5872b4839d5b256999166e7fe8efe4d56895f93be4659f43aaf68c6070babb6d3328168000000000000000000000000000000000cef5304a1077c8f31d72e6f1f91ef5a021d8ba64719b4527225b34e615af388d9b1391f65511eac209ff5e86244039f000000000000000000000000000000000c856e7847ea0b4a8334d124417b45a8689d5d9f113b99ebbe3af3f9aae1cefb236d751c40488a861a8f0e0326b42c4c,293920, -000000000000000000000000000000001805009a7fc3d1705936696191c163a07ea992cbb4bec66884a2d58ac3fc0e16b6e0d2292caccb3541f39b7fd6098100000000000000000000000000000000000f3bcfcb0c400d3d06184563204bdee465de167c7d17bea2e2150fe12eb9bc3285f5693b222fcd224181f8d193b7d95f00000000000000000000000000000000028d60b7fc3790aac7f6b3ec32c4be626a2c64c6348fb8a1f39e58ee56b81469e04886ed9be1388958550c02ca9a75b9000000000000000000000000000000000b60ed8052e43e99d3c10a4b97ac3197ee3cc04ad857c5cf4d8ea1df2671084d02fb683f28f5d499910351354d5e6288624ab43047c02e30ba2ec671511d06f869bf736a9866192c5f2eea6c065acea40000000000000000000000000000000002ddb1a9a88e3a0697540cb008bceb075e87e2331f6e9b68f8ffec48752d93cfda5fee121155ad2a142c0ec42808fbc200000000000000000000000000000000144b694018840835fa9c50fdf62c2e32261a8350d2ef074dcf7d016af982316a0c6f9e5d15d29d3a54d8d25aac5534940000000000000000000000000000000010a3765089ada75e9eb61328756ab9ca7b8362cf86cc82af3cf43f390a0745954f28da72a6ea4eb904a040596795639100000000000000000000000000000000056b51dbefab453012b35fb6e06af06ee92e4e84e92a9967b379af760fdca4a3f10f938684a646fd70a2188721c92e98edfdf850c0d3e3903404fe3e0f523cd230cabc45946c4fcb6d0e5e05e388c23500000000000000000000000000000000169effb324d60b71dc7ba975e3d5f18700b34cb9017f482f64be37c4df01fb66ee9eb5870e43649225c9a88a0d499b890000000000000000000000000000000016c7ad9c5f7b65a9423f642d87621a5192d7548e1099d774a99a34dd4ec9623aa1168b9adab092b3cf450f369bcb627600000000000000000000000000000000123b35bbcd791ce0d00148cdb3d35ba39054a7126ca5ad3351fef1437461379ef639896b271276a9561b46e270f7501400000000000000000000000000000000161fca2deb729fc55f1102fb75ff466319f18510fc66d6cf95a8256118fca618682f00318b0a5297be873a2f7af1915afeb34852ce0f3b5730962023418ad6cb860716dcb526dc53e8ab6a74a6a3910b00000000000000000000000000000000073ad8c2f713288313185c3b2455ade93d58e70d5df6b8dfaac8eccd990fca6843778fe42cc8aa6f34ee44aefb49397100000000000000000000000000000000012eb9cf288a366adc58d40c9ea5f2cb5dcc5b04108e3822266ff20eed71f56bd74f1a2727f20d55917adf20b6c4d6a1000000000000000000000000000000001463db177fe5c0dcb899797f89da963731dd4e9e8b2eb77b465b98415dc95f6d5569df51bd2b08a13838f4cca4b62fcc0000000000000000000000000000000009c0bbadad98361209f36eb23a9eeff98f6eafc7d5327fddb6bf43898a2be704520a005b84c5b45c6a68bb7c98d65d6dcf25e64093bd92a8fb394511215a3fa674db86d7329ac5ea70ec77d24d4ac58e0000000000000000000000000000000013c63973ce6549ca3dfe8ea8e3bcd6b0bd88f7c73730834d9ffe2076cd4345090d0364d161ae8998af1048d102f22e5d00000000000000000000000000000000060cd24eea4177c9a5c37038d4cb62aeb709218fa8e64b9084e002f53a0c4c411825812c20df282345bc4a6aabfff6a100000000000000000000000000000000106ea864dd52933be02c1a79cbaf6dc81ae9a2d619bb368c4abc36226104f3b74fadfab906e36d4852a6412315223bdd00000000000000000000000000000000192e45153e4942c88bcce76098fa51782a81b53abddb4c07bd79a2391be68858e2d278969b9fe75bc652d02fe4db1a130b40db4f9e5c27a3208899f4f536880b97f4c69e7d889c0726d87c3fa27e097500000000000000000000000000000000101ca1625e9d4a51e08f5eb81387b361f6445eb307d9bc92acd29d62735d4e5078b1a9b36b94e4ea0a314703a85ac4cd000000000000000000000000000000000f134c460c6d931396a0aa397558975ee973e642f1c4a32a3d397051fe250daf4215ff5ac4b2863d570c87f0e32c8cb800000000000000000000000000000000008eeb127a38104351298ad77481c32bf51bc5d3910b03da0cc34062dd2a8766adba6891cb9fc579672276666e1242730000000000000000000000000000000010c896ecd4bdc1ce010da81a51dac96409079853635e57e5c3a5733956a5f5a9c3ea6838849e286ce0405dd54d7e32d6730bc7f68d8d371d0bc51d95f8a5899249b8db5cba0d21fd88ba6f86d8691659000000000000000000000000000000000be489a1c71246adaa1c1dd6d2ddfae9523fd1d58d00d4f189f56d08632dccc694e63b371db6922a7f3faa05afbf487500000000000000000000000000000000174212b6840a797f0fe9e209b41f55aa5dbf169a2e2ecf05de48c44e608f6cd6d98ff5269e5412defb431caadc8a09c3000000000000000000000000000000000f4501715c0c511703f6236caa82479b3368de430f2c2d95b39193537be0b990fec1ed8e4d94634ee6233cfa359b043d000000000000000000000000000000000f3b4712f95005004d99fd739affc532d2c4c45970316c1a43f76fa9b57f6676c709e8791c276237b92750f5bdc94492ef06360717cfcab15be966cba2836b97deeedd20a52f88c73e2a583b64c8e5f00000000000000000000000000000000003abd36736fec3e8b89863670666365b169d8510090a89007c7ff3a82fc62ed371544013a1444fedc4358e92ceec62470000000000000000000000000000000008229855468fc63f4024938cd6f41c6e6a5653319cb83f38ab7efb9e9d281166261e7c854bfc08f55a0a9ca47e54dd42000000000000000000000000000000000463ccacb341fc5874f6ba2d44efb5cd24e9409b2ce7f43e9d39466288dc833a45988261f45d34332f416a68c5d10ce80000000000000000000000000000000002baa086177394203a04ce1b46415983399e60986531967b690b1a13cf8ae039b56f0a00bf9aff357d51ac57f8fac8b282b7d8b8b9345bf13d0e113b662141f5ebfc5888a5ef8ea06f7d5d137324ebef000000000000000000000000000000000b25a203268100df0510e4155c594a144dbdefbb0ac95e02bb4b3799aee4e738ef4c52f03c6937cdfa7275c28f130778000000000000000000000000000000000c432347a2534e86e90ca346a7b8b40f45075727847fa3ae2f2e297baa14aca88ac6e08342f0d248a92e2c272841fddf00000000000000000000000000000000057ec8099e1e30329762ccf0641b45e1a226f7b66b80644fd551d6fb1f2136afb8e8ab5c6905ffc7c24e67d7f21863e4000000000000000000000000000000000a9e472aa993bea05961affd6782efe8f50d746928efb8fbd328fb50a254db861c90db8df7faa7da8266ceb47fa1a13a2396fe15751bca2c4a651445cef236a865269849908df53551802dd378b892cc00000000000000000000000000000000025484652f18e2b32e2bbe79916c8bad42902db5528fc45993e04daeca008f3c2ff38fe4b48c292f70a7dc57654233400000000000000000000000000000000008e403f472b60a6046fd190544a1d6b249dc97cbd8641c62613f4de0e0fa9f5456d843ece4ac2b9f4ffa2c0278e61829000000000000000000000000000000000824e0b9b03198597fa54252b3df9690df678e9c6d82301848939dc55ab25a7751bcc2b99786cd31960ee7030bf68ac80000000000000000000000000000000018d1d8c7f2b20f0ba66db616322e48ac8f1d6f4205f228ee8ee6cd13d1f64be9af338c11f511859baabea3e15d165fc09a5897c9596223ca4d6628ca1f793a000aa21a739a37faa28637692b754148f80000000000000000000000000000000002845c4255819ec6e97abddf4c9db7d91658dd1d55328ab0565144b377e20ca0743d93fddf68acc985ceb7f7431e30b0000000000000000000000000000000001577a5691f2425e65ffd59071c2bb167ad05a8fe23c11c7f7464764442ebb2f7a75a8d02594d4426c1ff022f7a6e19360000000000000000000000000000000012c6ffefcd3964362f1373348404d04d1849e98ffbef7b5ed5704d74b9550869e30a4df26e74b5304b85c7503f7487f1000000000000000000000000000000000faf3dc42113f27ac27aae36725221d04fb1ab46b59e16277be0758b8fad706fa237c0c7627771d8e8d3ad610f63619bf20a2973faf886556e5329363bd9b9c96424fcf2e953df90bfd011ec07bc66eb00000000000000000000000000000000044de166200ec06bcb88720e57b84cd8f9534d1fe303a26aca08cc35104ffd7e81a6473c08b28037118dd8a61d090e910000000000000000000000000000000000f4325ebaafc67945de2418c81f5da92da4e67866ab5965eff0f392cc527fc34ba4e7e16b91c26aa370b27eb6a07f6b000000000000000000000000000000000e1d77ccc1c196cf1cdf0dabbee4829d56e937372e9f5613e261ca07e19b3fcf10f7a45c490b98b5a64b955eab5c4f2a0000000000000000000000000000000004ba2e81f901b0da1ead004c76d43278d372456c0c0a8c6752597823d44994177734ed3f355aaa22f325ea36b7c9eba1f4ddb773155a27badba330ae5d26096f350e9ca2811feb227c4eee09d2baf32f000000000000000000000000000000000c115e270ffd6f2cb9bbb2a62e04c3bf7be9d7db783d292bed272c297773b39e9e51c75e5c79a6606ff7d0bb9ddd040a000000000000000000000000000000000a57b637126b16b23bdaa6a7cf2346f33778cebdc0c9943eb2985ba5c4114674cd596ecdb6959791139c36c22148ab8300000000000000000000000000000000177c7ed16c29d99d3d98c6facca9cb5ffe72e6aa63959dbb51d9382f0fa49b02a1652a398eb223e093516ebf134448c4000000000000000000000000000000000d6bd518678828f582fbb3b1bef725e66f442c4d3e6325fa571e13db492300d03c0188399a2ef9d5687a76e647873c0f52e4030b5a4bfa767ae20cdea7f464dd2dba51c9c698556d24b8f3d4d1afc82e00000000000000000000000000000000085d4f90336987f99d250067c2331e7de8f09a80d71fef0570ecfd99e409c1f405058bd3461c9f8ac5ccda406db89bca0000000000000000000000000000000015f310660ca6a0c06b458d0b840a5c1c476d5175d9ff6dce6334466d363d319939572a2b00662247be1ed0f4e6676f8b0000000000000000000000000000000011e9352c0f81bd3857806db678bceb2150848f2224ddfc43fb0c733f0689ab4fffde50d5ce04d54055d27d7702e5d2d40000000000000000000000000000000005d835d04dcf4199130d6a16e86cb97f4ccff58c496594b83524dcd88f5570212f06b744379288f2a737c7a82e897cedd32e0429e7934faa526475c5c7fb977c3030ed74e145eba21af2d2cc8461580f000000000000000000000000000000000f7c4e621c37bd3068a972b9d4211abf9026e438ac7f8cb341516f7e6aa4d8bfb3536389e9155029ce9e8d5d376eec1c0000000000000000000000000000000012a46cab2624797513f2acaefa26fb22c4bf29188881690c350593fd1949cbc243c9d1d7d27d9d76aaccd347359a45660000000000000000000000000000000002dc383d4f9b75907f74bace1769bb5bb1b27a597c9548310f2b5f90098596fcce6b5fe0c72bc8be9037fbf31050d74e000000000000000000000000000000001900deff7ddc62ac302c941e1d2a28a4bd2351edd7700042ea4c4a48145ef91688666d8d7de503913ea259f0b58809f21f700d651c67ca5b8d95fad1a8e412befdf691b074956bb8092938bda2ad26940000000000000000000000000000000018ac8048d58f7b1a9407d3101824e3640eb20633f8ffdcc97d43d1b25329a2a1e91added42801c03635ec904e627eb690000000000000000000000000000000000b499fbdbe2ed41dfd6c454796e1ba57021f355a4de8f60964c78dc685e2ffe9c90f5a1f6c9677514ae4a9c95c8d6450000000000000000000000000000000009d10e5e2bb69ea6fd820778f75a2a60627802a49128c3f999d8c1cc2ba56ed18acef354a2e06fbbdfa7e7a4ade7529a00000000000000000000000000000000082839d66a18763656c2ef7196a1d83bd162e1f109b54c5a6095cc7c436e8a4888c4001696958270f54f61b81b00b32d83052a3bd7a13bb1ccc22b9519c7ab12d2dec67924fd9f15f96069de22e7b692,000000000000000000000000000000001463ac5e269d286961036db48ae33fb868a28b0dd828c3a66592ff9dc115303bdf3ab78a8e1f5df68ed1f3b4c6c3f2440000000000000000000000000000000012c64ca0ac10ab616fc733f75fe6181814e9c204f9e4eb79487ba49e3a9746b9b7916a1d768f2ec573a4c4e226365f48000000000000000000000000000000000a06b5b745dd92adbe1f4cf30c79ce0c48428b3e3b05af1585c4ca12eb2e763ffff46b55a060913e1f77fc9b0b085c9f0000000000000000000000000000000006271931ce9c8b9cabdc932297f3c87128a5af25a9f77e71ea4e588f1e88686638e89a8e212c92f6472692be2e05fa5e,293920, -00000000000000000000000000000000001bf5b74c1ac89d4bab4663943e19128619731e315d2d7b39675f7c43493b338020190a72cc7a6edf0b8838886a7fe6000000000000000000000000000000000713f413bab7919cd57c2de3349394121d6bace3c10df0e41a0ab895433d225b05cdb1587deb93ae6e56ec26a29c39f4000000000000000000000000000000000dfb11c9c0bab7e4d1ee39941d5f6b932ab473567be2329c94bb0b146c46fc1c2cda25dbef8ff9b0066bd4ca3b6da67a0000000000000000000000000000000014e399169243bd619be7f2120b2cae5d19b2f04185aebc7d948007c4d3345a9f45249273b6290c2e86448648868ac552c40774f67a651ad70f17393b386e9ea9e81682ffd78db7fbc17cc5084f3c7052000000000000000000000000000000000a67bca1f8a0b386b2a67158e80262f025b225535a294394584118f9a701e31e91b2c7eb8fc7e28538966b967c139dc400000000000000000000000000000000185e8aaeec9b9abb9f0d6f34e2480e9abc30208eb1c6e023d4986d544b356a387c323c9edb5c52f5a2f0bd59cca7df98000000000000000000000000000000001877ce1ca6e8b30df86de688d950755f2708fd6f933c07ae45fad1b3e43337f1a8454ca5d2a80940e8fee98fffe953a700000000000000000000000000000000117a0ac9d27292f967ff5bff2ebed5d2ddd9f453d6aeadd9106eb52b53447974561b621fdc1d973c055f1cdf824c367bccf1e36e063a5fdd4b735dc18bf07703b80c6b72f987c05641612d7ce73562c00000000000000000000000000000000009fc4e9e816ff495dbfd4f745106fc90c023d95bc64b809801d02dc7cead905177ede5016f537243660e4b7f54a02ea200000000000000000000000000000000180aceb6e9851a11a1e34502897299e7db3e09f4970337612634fd9848d1de2bb3de8ede690ca051a75add5810ff777600000000000000000000000000000000199f3c43d429fe8f73e20f81ea00c4e78294eaaa29fd67563664381db3cee2186b387b880089cf96fb99c2e22c95449d00000000000000000000000000000000040b20ec4e685f104be188d0f15a79f27cf34dd01f813275f6019a9ffac56e234b6c967c80745294d9fa46e0083cdd907ea75dd2f54fa6413ba77f10a11e12abea3a4b947116e1e7c9334a0a37c3963100000000000000000000000000000000189fba635109ca215bf3a09c3e44ad65f7eaf653e0929aed39042e3b9c8b1132c5fe7cfafddfdd0646514aa1f9e7e1c0000000000000000000000000000000000c28f598c80ac262ec7a0e0d1c867e01ef26f182c5df9ea7f88fdf8bcf3a5d2f06128526b1ce72cead8ab4286a0b8d030000000000000000000000000000000008051be3328df43b79dc9040ef0a0263d474acc0edc023f300cdf7c13088d1bb21b5f37ed81b38dcf8718bf6441605f8000000000000000000000000000000000d2d474723c6c246dc59e683be147b1a6bd6e7d3cf12aff7b636802a99954e7a13c9ea429b19833a985ca5649b1a998f6855c61bb7d72b022c16290c6d3ca9c1255cede8e0b827b43e40fbf01840397800000000000000000000000000000000058bf424fd68aac77c42a046f78a55729e6b5b3fcaf436d0d98354b426a95904b55cdffdd9a8892c9f56f170ca8811a600000000000000000000000000000000142c1ded08928fd155b89bcfaf9c8194f4569b4cdeb3bc7286f4dd79e822f5db497768220533b71be8c71d121e557020000000000000000000000000000000000a9c753686534bfcc295eba0a617f86d7f9e78d3fe6d52f26cede97a5b1f107210a757a2d89361645856b7b20e89185a000000000000000000000000000000000f745541841cc4b5352f659c2b7cfa8d51b07f91b0cb8c787b4492bb4b94ea27117695416e2806e57c38d7e565b9eac67fa8503101f392a6c6c27300b6992af3fcc48d47f73db67615a44de883770d4f0000000000000000000000000000000004445d4464b51d6b12f164a49ee3b610f11738d60cfa6e02f8c33b168d9d5db90e6cc558cd12c56069571567d91183a30000000000000000000000000000000009e4b96c2b533a16803a36f8d1f179313b7adbe6c4b90716855474ffb2fbe087df3fc0b4ef14cda7d958efc5c92574ac00000000000000000000000000000000104dff7c859eec61a0ff8e0d831bf9667226d5bdbe298400b4f9e3159a64b1bbc7cb9f4ff9604e3ced40bb0de0455ce300000000000000000000000000000000134bc2461459ed6f0d96aca02b62e3110c2009e1ba7d3258656e9cf97c2a1685faf1f61733ce6ac3af7ef4d73d0b43b1dd947617bcb7ca1c8fda0d49e6d950a84d60230bc2411d42ac32e3651f48524b00000000000000000000000000000000104e5709f8edd71f50eac1770ff1c2b21f5ee8cf5a310fd1201109d1b73cab69913bcfa2d27a8ba16d974e9841586ebd0000000000000000000000000000000003a4bedc6277c61825f6ea1f438c058a1afd494c384689a8479195646888eecc7953b8b8aec849fb5f19a20071261336000000000000000000000000000000000856ee8eafb9b3d25fde7e38da4acec624d1444337b87b0b1a660bf497ff37929b1ef9aed8e1fb0ffc6cacd8f0d1a1a00000000000000000000000000000000011b52192c88264df56de3d7b14372443e25183bb816ea1c0346f15a1f324527ef8531e27aac3112e2a497a0eff0d5485b4cbbc6d537ed2b69c2c32c84f3cea3d2db180b64861859368e98aca32bceea6000000000000000000000000000000000a696c83010719161b6624aa7756e6e84980518416554ac045a93b63c2561a68ca2ff2fd5b6d2d667822ae4e3b3a2ba2000000000000000000000000000000000fb8fdab4f177b0dee52bb5ba615b1d548130deb87b14d05d427984ec148a7a94efc4674804b3660d0f7aae2b49f7b1e0000000000000000000000000000000004914c0359c8e23a7e431e517cb83e5735cb2876e8b53ad45abf1e9eda06e736378ce03ff75002374d47f1bd45b08e8900000000000000000000000000000000139abe340c2d773cc45cfc75c47ff31b2dcdce27ada3e6d6c0823f37e4e693ca30342fe41eb96dde464d14668eb72c5e457bcb8c44a2d9d1facb39ba7ec8ede5d5962b3256d9fc2e68a1ee5a733ccbd100000000000000000000000000000000180345fc01e3fa349c45b1a7fdccde5f9ee70d7d65510e8b4bce654f2541fae7641ad86f9bbc1f02e93e94422433f8b40000000000000000000000000000000006cfe7026cd423be189c5ade8de197aecbc9aefd4cdbbd2aeacda816247ad59ae06a5c49b0e29bf1140f400d46845191000000000000000000000000000000000cc4f240a317ae9ce75b44fae87c92fe9b6de10e1191cdebdcc37ac200957683849d8a957216676db1af51fa0a2a1136000000000000000000000000000000000ba84d595661e5d9bdf9d268a3cc575fbb6b0d469b58b3e43f80694c78f4e9e501c4a4f9c42ee4518ed7189a1c36ca0c19f254dbf75f1c42046343b0060e71302bf6c94ca2fb8aec74fe7a47a3c9c3ff000000000000000000000000000000000fdf7e2372b01b5d926a18ddd06b4573248c02d7debf944312dc06f76ba08a7be460c451d296b71e9e81cf0956b974b80000000000000000000000000000000018326d0e1bfb4a62ab6f772b47ed7188035a62141e6b2eccf53a299028902a172771e8e46c0b1ac4833ab12045922b3600000000000000000000000000000000072107574145c6afdfc7d618f2dba2b8bb01d92007dafd476e4ca62e6053e5e9f2e34243ec2dd16ffdbe3488b925a0f000000000000000000000000000000000070e8491a835ae96087013b0f8da267a7ca5b0a600d71b8c76fee35f41d8b5c1ad82c5170b0e8d1cacfc7b7b13938e96f08cf27a47d89ae6e2ffb27870d613b9ae586857e4ea00670944a2883ba325af0000000000000000000000000000000018f4da37ff63f66d68c875def8c758d9a5adcdc408f0c12b3a60ee4a285e6702b1d5b9326c61f443dc71ae83c7bd21e80000000000000000000000000000000013a665e430141cff62c25577798473a645d20321490bae7689de6ea223a434c7d3b16ad004b24a82e2c62879b2408cf90000000000000000000000000000000011b0108562f53bd47d9f8ada54166854bf758ef3769ca1c3b7b006fec8707107fef0b6c7e59feb727646b74c27ec699600000000000000000000000000000000028799b52107d8965066e2f629b30c0edb490a0f4d0b6cdfff89a9f7763afbe6217bd42c2059042397b6c0443465fdc050aa333bb6b44086fe6211e89cb70b8467eccc228c09aaa1d589cfc24771a11b000000000000000000000000000000000c42cb42e389f32926ef09584516249ae332641b573ed29bc0884feda08d35c1bdc6c3d4a69fa15105de95010c6cc24600000000000000000000000000000000006c57fbf93c7959c562e0f3ef59966c1640c706fd18a6b539dfd711b0ad79643642038954bc866d42d1c04be375b95a00000000000000000000000000000000039ca3ad23b71693e02af36a4abe6ccd0dd4f4aa709f74d900b9fd015a2eaed55bdc2bc0749c995783a7615971e8a1f50000000000000000000000000000000009a08596b29da34466c8a7f46b805f1b6f2e48bbba614d728562981d3d4884de9a3c1980d398eadcf69e90c851d48526d9f7f74a5ccbd01afd985d3259739023cd012cd67fba3a4ab5597e94d8fad43400000000000000000000000000000000123dde5bb9b7ca11da9e08a9489cf07d147492be8041a5ad0b70715147e21d6017a58af23c47d77885a7830cfbbe5e0d0000000000000000000000000000000001527cec3c393d03e74ee8a7b1d6a8b6398945cd284b59a93fade9839863f0af591c287e89b3b45e6048f2f9b518208e0000000000000000000000000000000017ac3a2d9458bbd5f38d584b0fe4b35f3a452e22161564a7582465d2068b3ba4dc5e1e24a996596b1fb553d641996a4e000000000000000000000000000000000ee5ed5610a78dee181750e35a8ab91c001446f04124930c2ed85de74c6167009af45a6cbc3c59c4915334d7853ee12f85c00be7e66e318bed8e66cc41e7fd0593004bbca20f0dbc28efe4441acfc9ae0000000000000000000000000000000014d60c1d436e4486f35ec85bf2655ba6b752a36c86fd9088c0ce46363e75abd636052f876986fa0f4a59152998c0e4a800000000000000000000000000000000083328e38373f1de1049deaba78f568db818b1dc38d981ae92b968134d369ccc399bc3bd55c841755beb484cbbd60f4b000000000000000000000000000000001788850a5508d81df9af1f087356bf8e63b3c8a4e209403c4de7b3adda07684a08f9de6f1f8fd8dd4b2bb9b75be329cf000000000000000000000000000000001506a37d222173f0098f56b7c443e04ffe08b376e1563344e7bf22b1c9df0a1292f70ba51cbe554843fb93a7f535a4aabacef63d90ad11bbdf0c5fa2db2838c238ad3049a3f47b7f67361825efbc6526000000000000000000000000000000000d5f153952defdea9309269bc996a7714deab12e7644f8f8344140fe53034de538aae6c3af7b06687684edcd2c5dd19e0000000000000000000000000000000002da67345153c87ca65012b8703acbe777900953abaedca4770fd893275948d150ca3d6694d58bbbc9e62904448a8d2c0000000000000000000000000000000006e8c95d22f01fd9d56178d754f0892f46166282a27e6b02826478cd39119636e811c03fd835c714a59bd2f7da5ce5e1000000000000000000000000000000000b5ab6233d8dff50648d89cd65793640c06ea784d00aff329e882ae04fb466506cce3fb6c381b4eacef8b5305953f7b6473fa3d16e6431da14b8639d4fe316692db087a167a2c4f07307e770bb9e35ae000000000000000000000000000000000595edc440a5c94506a79f3b3fee818256d7c4185be40c1953b46765b2f925ed16a476b07a267570c727592dfc4a0d8d00000000000000000000000000000000079ad05473fca57f26fd068ed659e4aa4919847dd96e683e7d4b3a731cc9ae0562a693abeea4fd550e644b43b553118500000000000000000000000000000000176a9751dbfe727a442797551254cf904862c4d590892e019a54b72f6a5a124d268777b82e19d557690ccfb81cbe949d00000000000000000000000000000000164ab74c150cd151b70fdd7d63d0404214fc9cdafba3bc642aa798b1c301c287ff6d05ee7b3a3ce997072b8189d54aa62774741f87af1d6942dc4ed79b70b2d706f3db6b6d083eef0475334ef1e2410a,0000000000000000000000000000000017d73e29f1d555a10272043ac0900e80883c185ff7d087ee7f5a3b762213e658a42d1b4fdd435d1acb9d5587fa7e8243000000000000000000000000000000000ddc440795d0e4308577fe8439d43418641538711972c9744dfc8a4c206c193aa17958404bc387c7c2fa30bc678937f7000000000000000000000000000000000d7e43c0f99adcb02db99974e7615b4ca0de72117792ea515bb04c4bc8680a3fdb0afcf6a3bdfe16bf54c1d7336aa185000000000000000000000000000000000bcec1d7fc9f2210be80e90631810987801fdf60890ce197db041b6a62682fd7e181c6110956c5f5e9c196049e39100f,293920, -0000000000000000000000000000000015425cbb7075a97dfa9409d7b014127396056dee6d4bb63ea285309fd91280fb691f9cb9572b544b332324f6cb3b1276000000000000000000000000000000000c5b9634e6748d5819396051322d9b7e0377554613a7fd8dc0c71cfb7886dc0ac29add7265af84087a9df5ae3799ae30000000000000000000000000000000000534226ad7324ed5600b5438b659c7b1e96f27ee1d77163f2d3073418f7ded5c613ca4b1a686764ecc43ce3388e0c32600000000000000000000000000000000198267e2bd474dc0415f47f5c87a11fe0945a91cc0bfc37d504ac53f9b9b0d087cd5dbc9b03972be03d4b3f9d2123945d10ffdd3797ad13e65a1115cab6529d0f87b91eb41d6265e694eed8f02667214000000000000000000000000000000000389a084d95445af6e0afaef21d3676794e45986b9520035111ccdbac4ddc1b23974a686a900616f878f3a06eec90db500000000000000000000000000000000064c75d1129753b5f399c1a5166a0f6a8f427d65ec2fd84d0c7339218e0a396681797bab68b33653ffb9820a6005fa7500000000000000000000000000000000147e199e8c08b9af38cb457b623d0fff32242b11e695f2adc0f136c5596db313b03c2466fb58e37c94704152e5c8f9dd000000000000000000000000000000000e8fe5436baf3470a19891b85d15486d1269e1b13098d837b0a510e71b0e6260700ea85f0bc6476217cc73615370cf003e5da5568a9427e0cbd7973a34c147ac2f3577d06f68280caecf8588ebf1591a000000000000000000000000000000000a39a2032858a57ccbfb940741f4ae21b318a56d5567cc0088ed52dddf1e0d5de60bd2da9b675212a9a28ec17fca7c0600000000000000000000000000000000039e2a4bb1b417f8a94b02cad60a3e1c4c4bc5a86a23def7cfaecbfd97d89a5104e0cd13870c9fbd010dfec3ad9b1df9000000000000000000000000000000000bc29c5623f9f18ec2af5bc651a65d89554705a349923ee15a9bfb82c114246b404a1dc1c24d65c8749e7c9cf62d963a0000000000000000000000000000000001496d76f7b8583a64c1627151589af876a2f5e7677611ea15f14606538f6052c56e9fc3ed145c313acea69a51547fb6145b5f1f156f3c823cc129568e7602694107608c1f9545edaa897df58d27b18f0000000000000000000000000000000015f83b2f998691e504aa740b4db38f5b0236ece3bc1ca933b79999d55b737bfec51e590c2127d57625a9b7c2960c06280000000000000000000000000000000001b7b117f5d722e320b7e90307ac1423aec5e30c29602d314bac9e5272ad3990d31999bf3f516ac78b2be0e16c0375d8000000000000000000000000000000000fa7992cd7fb679eb5f9f9a9febe9c3cf41a717c8f6fffbab5748572098407174f09457e13468165f1c7275d52f6c84b000000000000000000000000000000000737e95f62aacd12f8aebc288c5cfe052f34c4d16e7b44df4497d9a713b77485fb0efc09aef11c7b86eec4d0cfd9b03ecf6760be82cefac2843265be5fc0fd6d308c1ed06fc684c4693de25372f09ed000000000000000000000000000000000004d48d72ad4e77954ec6a5a62299f0472bc52b556cf3857019f8efdd694758f13029f9d6832ed672cc210f32033da8d0000000000000000000000000000000009b2394755d0319741d131b012ba0ece7e2044def20ae73fe73bcc276af9d807ad75be79202963f9a5c512a6ca53197800000000000000000000000000000000128f856fc4790d9fa68cd2a3c152d675453dd81dd64f0ab084c6dabce456f78c2bab0e7f315439b34f86e8fa61a33ffd00000000000000000000000000000000173dbb908ed617ffffb6aeb212cfe6c03f7ee51c84134fde67de2ad9561a897e28a0efa66257ae0c21ebcee3fe4fa68cd9fca4d166149ac9e6159ce95a06f790a96243662373637f0c6a59764b77b45e000000000000000000000000000000000bb7b84476d4b17f4ada0b6f50d34dfaecd611356862895c8d2fee6707c4aedbf565560d4207e43c179c5cd33cbb739000000000000000000000000000000000112d8b10c775218d318090dfcef55a903953f7466c50417125ec0b2c20a24fb50bd172331c0377d4f47aec99bd87a3fc000000000000000000000000000000000cf4e4b3c600053f45f350c8860e47621f50f3849872a91ab115f71a2b04657991217e2f0844b296d3a6bc33ee66e6a80000000000000000000000000000000008f625da164bc9d96be3e78df63bd1633a2951dbea0b98e359c6317abe6ac5799c4bb00bbc2c5d02048539e753019a6241733039312347a0c9d760c1bb9a1209a34a02b359a9c52a57eddced1575867000000000000000000000000000000000028db057ab9421eefd1fd481c91153b5c1ceb0f2dacb0097298cac986f036572c6ab0c8709325b3bc25bd494bb46c55400000000000000000000000000000000024be09301c9be4f726fbf7796e8336c50897e8534614c25f65c37bcfc6e724d530c2782bf483668fd08e91ad09484af00000000000000000000000000000000037bfdaa11660111ce0a9c3e18b5da74c004cb44882b1aea4173e18d3a17f04fefa3b319afaf4af9dbf3d4b9ddb2c3a00000000000000000000000000000000008f2138bf621237a286229fe762968a224358b030f6c20db58043c13727b516097b42d47781bd0f0df2b155197ca3946b21b18d883ef62084ce4bd353d7434d7e220e9cf6bd0e8d0bed1ad0a4ad94c7e000000000000000000000000000000000b4e2b058d6e77cf95be093375233e5c9c8ee0cb2a3aa93172c08faea111df81b9721a506180b7b45bdde4b58b0b7368000000000000000000000000000000000f7025cc33424a7c11eef47baef888535d938d50c0f40eb83ae86791834770e5dd95b30aebdd2c13eda3447d5730ce3b00000000000000000000000000000000088270ef05480ef8aac5c284358d8e06c3482c26279734b8513000019924cefeb396ae79f5d9bd863bdd9b22e3ac3c54000000000000000000000000000000000df75afafb138fb06bfd905c87035bc5d18c45a29267c3965131083d7e0112e10556d7693d424172a53e8d3120f0cf2aeafb6aa11296facbc13936bd2ba09a2cf9bbd9dab6ec8cc5f73d78c90b471a3000000000000000000000000000000000122fdd3c83c01c7cbe71f54d783181860e7dcf8406e3966e910f4d0ccddae3a245d6b1f94b1182d1917fd63960cd75d400000000000000000000000000000000043592e5797cc1409d6d42dacad628448799b24320acbda83f6ea9d232968efd021058f540e3bd73a7f95761efbb5fc400000000000000000000000000000000025b5a8577ec1064b5c557415a50e84c2302df97eb65860f979e5b1e261f47c0f305461681beb07e521cf03f0e21fd030000000000000000000000000000000017e86f3ffe72bcb71d46661a1537918d52e886e362d78ed756140a6b5083a4eebb5280b9eeb8a25251dec43a5cf509b13d39a61323c07f9f4656a6c5e6ba139da8175ebfb8a641de50cfa2290884662900000000000000000000000000000000122f26b4561d1f79a70bd0e401f25d50891c0fa0320579ef21aeed7c191fe1c75403a09260c3872cf74b798eb1587ebe00000000000000000000000000000000039a261d9f48b9eab6e89046f333ac328cea287993166057e9b99fa8a7d7eb3e7c34ecbb353b7427b235084f47f45d1100000000000000000000000000000000015d5e297317684bd0169c795d9dcd209452d024ef9a450c41beb0f6c7e6dc5fa0f3ae24c7cf2d7eef97bdc51788188d000000000000000000000000000000001487564f0e9d3e0d2d30ec9930a00f10093e29f2f195344f567960be323ca21231efd8528108dbee4d5ae4de3930ddedf6374d0849a4471eca96c5e715b10505c4c49664f341d04705fc688c8479cda4000000000000000000000000000000001965ac3a520c1ac39b86832ecbe226ae0474b76659076ccbb550a0daf41c40d424ceda084dd991f22cc53779085828430000000000000000000000000000000002e970a4248823049bb4339d21583fdce9540ec103d6e9530b89e39ea875b1c333f7f5f859be39baad34b374055baa770000000000000000000000000000000003460eafb3e54ec03fd5cc1d460e1359b97f5543e6231d61614c1225ab7545fae079ac8e65668b83d022031a7a54746b000000000000000000000000000000000321394863e7c70df3934d874613b7c9d6c331e59a599be593c82edb7a26eff9bee8e4befbf122240d2deb2d527bd38c0b7cb52b99abe10d1367f8d3def38221c18657a1114ceaa1c0673ab13a6e10870000000000000000000000000000000001a5eebe200ec041476457f8585cb4ccdda936cca4977d7701c44e0d4fc5d9c206682a23348013a055117028c16914400000000000000000000000000000000003519bd1dea70245e521988336eb41870599a877380c0a9eb19301f9b2caf963eb559070e23eaeefa4de0173bb1fbd8a00000000000000000000000000000000125707f5a8e26b28968dab97ef4654c315b0a118c20935e38a5a526d9ac0a0e18355d8c9f3f58c082de98691957e2d5e0000000000000000000000000000000010b58dd683f73a16d8bd5557b35b7003a761bdf7d90ef576de8acd420bc74f5219fe7f9d35667feeb3ddf1d568b56bf1f49b1fa80a321d4d100069b2c4b94cbda255d8e9f1a7f14ddf4762b76e4a386f00000000000000000000000000000000018267d8b83ca59d4efce7ee3d73f7b984f09556ea4fa5cff5997a1eeeaeb8bdc9185176d77ad0f4d86f2e429f4015350000000000000000000000000000000014114344d6b7c976cdaf2418d7f72c120c2fddcc65c3ead067482e7073e2a3a239af19f862ad247e3181b13f5236d1040000000000000000000000000000000015db961a093b248e83deea0ceeebfc3dd57c7cf8b48cd627c5c566a4f9bea30ff0ef9cab9287a0f520a72b02d9092a0c0000000000000000000000000000000015159439fbfb91d1e24af611563aee3eb498fde666a1014a9f645037995d72dca0ed5569da7ecd084208b7c228e8a2b2ad3625b0839cc1ab8c9798b2e9706ba6d7aa623f3c0ce0985bccb2ee5c05a313000000000000000000000000000000000e1780b32a7b17464cf514efc4bdb02283af396ffcf6d1ae023e07fae02becdcc3c467f89f8edc9173a71aad27b200da000000000000000000000000000000000c3e7fd95dd823338bdf3d82fd46c265a3f794d4065d83873b1aca66da5f80c5962c9dcf537fc315d024d8cab7bed89d000000000000000000000000000000000e4eb722080e24f54fac7eed4b94e7b1eedb081c3edd7aaf5433d00829929d8bdef940aedbdd7dfb0376b3ad5544d9cf00000000000000000000000000000000158c1ff057f7ffe6492097e339cc4ce56bbefd39658ad55e08d5407619d1cbea7c83b977a1583ee48897a5e9c0d9ce3e150e53fb45ba8ce5ca917010f26451220be51141fe21cfc1cc06a5557e8e7afc00000000000000000000000000000000138e8bc8cfaecba9fd1322a3c1682c9fc1286d78e5b6718da00acc69f811fe9f94c9f0dc9d80e9002c0022c6dfcf156a00000000000000000000000000000000021da679a068b2f5f473ceed588f07adc7f485003f7d2286a18c07b09b835881f4ab94c7d4ec742c33a7cf01801116fe0000000000000000000000000000000018a62c2f4a02b73f5a91f503b53332304afc9cd8769f236259789277599a203b8b304b38993835a87d7cc970ad514d2400000000000000000000000000000000179396865f859386df7c1b8fa84c4ee71c14daf695fc0841c293618e6f8c87fb56b924f3f91a273b969e8635d7f90985d69ec73df67feb970f1c7a3880ee84d948eab4d8672a6c1481d61efc6cd710020000000000000000000000000000000004a8cb437297722c0c1a9471ff083ce60ec40c908af4ebb570c87133df705e725e3209152bcff26a0d6e4602030610d3000000000000000000000000000000001832e55a9e703d727156e4677ef4f82b86c6764123c3ed1dd94ae3b46d7eed459114993968eaf8e21cf24c59d042f41d000000000000000000000000000000000f606d5ee57b188636334ad60057cec4008ace88f14ea06324edaecb26da627670b44b6ac57b9fa2717d03096010785300000000000000000000000000000000145bf70f90a9d98f56ed38b3506556a48a1340ca6161806d055d7a1382eed54e294564de7fdbf525b0012de3d25ab5c838f8acba4782dfbc02a14d4b1d7b2b0a582f9bd75642169707a475b1a7d2d7e0,0000000000000000000000000000000018ca453b9d832f029ac8c7c70df846be97b530e6e42de3ba6943a7d0dc00296942f88eba6a9cc3352900ff124efaf7d90000000000000000000000000000000002e4514102aa3f772f2659ae9f1e2a91c7fb749ea590a3cea2c1a2e0f7236f71e182374cf7ebd2fa086dd921c29013910000000000000000000000000000000007c025696cdbf403494c5fc7f9a10ad0c549f84d1e06c5c4bb22f7a039486909c540776224bcdaaeb3880ae9d745dbe5000000000000000000000000000000000b5b5b70fae8b3953ee6661a0f4a1be25596839482d78710e584d3bcd93dff2b0bf4c8b20974744667e25fd8353cec0a,293920, -000000000000000000000000000000001265e90c564693db716f17d1a8815a8449e43b5a2d5446ca65160d864718cdfd413d5aa024e7581421c7222c29eb452b00000000000000000000000000000000133a6558baa53a2b8d239198e1dcd81af1ee46d55137177be467a99edf282edcd47b7861a3c822f9bd0df2e86aeb5dc2000000000000000000000000000000000d8287564bcedb1e57c3d74b0d484a9b475ce3f5b0322bda0e980de8891e2e8663abda99744b58032b8d7d3adddbac9500000000000000000000000000000000013cc35410d7fe07eac96abd2b35ff656e17b6b1eba2bd1d75ce5c87c5e76755ef9c2cce70f05cdec15d1bc44bf902d4cacfb05e5d10c41b06a487e9f8afa38759eeb55f0a5bc8640164bbb081c1fd2a00000000000000000000000000000000193f0cd6b4051cfd89f358cf6643528f0f042ae30ba3627d297b4fa2c2936426a9c1b65145b8192f65dfaad1f2fbc358000000000000000000000000000000000a92ca8943e64a391aa39126f093f2b530f556c1e3ea1b55bef1c264909dc93d260eec6420fb7a4e4a45f932d57951500000000000000000000000000000000005c7dc5832f744089d5fe034bc93e0bcca042ddd1b221cdd5958be86214831906ddbf82508dd91dccee467fd1625dd740000000000000000000000000000000011b11b3d24f44bcafbcb9baf62cef3f18b56ded696b73577375dae8108dcfb663d437e4cd9e44b7e6bf49741e058f8cb9a0b88d946231cc484550a87a548719f0a543c0698411f230a966cf602dc4de300000000000000000000000000000000073872ce0d74ea368df132897617aa8f941b67cf3fb395ca6c2f5bb2c551f17d68b0c6ef11e742206d6559796f06426c00000000000000000000000000000000156cc28eece7bed943c8410a44af112edd8576807e25701093eac0c9726f93da68a19c1d7b294f3ae6c84e32e7c2d5ba00000000000000000000000000000000050fe5987d5fa678be3d34c50fa6c5296f883e65ac3201c333b97ec0de00dee6187d2790c357a3f8822a174a534539a900000000000000000000000000000000177fee6e2d3909c0536acdbbdfc716f6ca19b6bfee7920a78ac9725c85114c69cd13152467e72270e35006b3c6caee8c74e3b5ff944bbbbf808f1f469a3380ee7dc37ebecdd8fcdbbd2f2561e0dcd68e000000000000000000000000000000000dd147bec9e0d1727c9d7597dea4a5b6b15c0a603dd1b586835580468148a502289fcc38194b2fccdcd8fdf0d8ec1904000000000000000000000000000000000186501fa4f3a20e80bf297e8ef1885b7d157617701839a3b524d61f35b2eb843ff0af13e253bbdef653a83e07a5871e000000000000000000000000000000000023eda2ed9d34aa253c8bf2f3b66b3c0c2551cc0e74f43dde2e429d9dea113a62572d245b44708bed79d662d9cba487000000000000000000000000000000001041cdaeb244803556e9b20db95f2a66830cbe47a68aea262865da50ab15ba658116657625318fe46fef393eeb6f3e2ec23064970a4ae4ae648a79edb193d98208418d3489e9b5b8517ebe99cc32b4d7000000000000000000000000000000000c27b1feeeb38068ee52b0fa440af2e3bcfd16601c8af983d259f2d15316b513ac3e89069bc141f02b934f2e474253ba00000000000000000000000000000000183f966cdb28f344ccae4cfda63ba6a6f29d00ab942ae7db7572cc09305e4f80c11305527b8ba38c40aae5f23165cf9400000000000000000000000000000000049cf59bbd6c26ab3e25b3cb94878271c73c0b4436573d612311feceed0f1668f4d79aad92360c1c97d60b540239ae630000000000000000000000000000000015f35eb8e4c40cb1297f7128d99b109ca75944c1943abe9158813432145a4a2a5663b55dbabfa48bfd9dd01907e1e8d3972fb60ccab83b6ce042c09ead82fea3d2cb891e21ddc5af7b5d8e334d5a3264000000000000000000000000000000000e5d9a671862733804f517dc9cae2190ef0005f26394e3161fbe771b9a486368871f4b1f10f405e45048362f437238260000000000000000000000000000000008100c6f96ae7af5fc86d9d91fbbefcc1bf5873dacaba9c3adf1b2833dd529d87f303a55e5d4098153377effd0f8114500000000000000000000000000000000010e4863a9b037d4ae6dff827a34be04c7f1627670b40e5cafb1fbca2fbf56af9ea6b24548db58e3119db64553d18cf200000000000000000000000000000000036a298ad5e8b32041a18e3f6c5847eaef20a5b63ddece41bd7dc4c4a54deb9c6d7002e6621aa01d78d64ec9991f68fbdb68c389b94c82f006fdc637696d8085b24897177d2992f504d4bcf5ff04d173000000000000000000000000000000000f62c0bad83c41887bf1ebd2644cef0577d793c2f3d67cbe43974f460a4afaf2e412fbf9ec97404e5e882ca0b23bd1a400000000000000000000000000000000191562ec9ace63ad2aae1f7fa977b9e0606e1da9775a978b2caafada4f6b3d9104562f2055fe037cd06df6093123a08e00000000000000000000000000000000156702c3feef1baf5ba202a25b9dfd5c1fc620e837501b0c5bcb85ec8b6e3e92bad1fc842bd1a0dac363e4bdf0fac87c0000000000000000000000000000000013a4b7e869ed9bdbf9671a5d8ca9145a2e97b6885d2a93b33f378e649e0e576be65bfe849119381057337315363bab2f4510c100005f2306f4b474d3843b4a79d04f0171afc5c66df70f631b0481dd330000000000000000000000000000000000a4b273438168494f0db235f535bf31893bb70f4119dc4741aa3c5e63e93b9a8bc001faaca10e37f36e130ef53853900000000000000000000000000000000010936551b148e16249dd934fcc83dee55279495c2a70d46dfc45945a69549657c3dd7cce00d8136e28d64b0c800344cd00000000000000000000000000000000115c053ac0b68573c3abd5f047b8fcd897e3d514945c5fe6efebf1921563d0079eadf32f7428ecb703d9163bc7811ebf00000000000000000000000000000000162e86af01daf552589b62be849e6176d74fa5da9b214a5cf2285802dbc44f346eaee5cc3d93a085740f74cf7e1b17e1dc682a2be4d67852d119795988c52230d8273648cc176ddc012a4b4da5a8636b000000000000000000000000000000000d77cb5045f7d4578621c76bf5b3db076661c72174508279280de3e92f0aa57057ab50180f0f908561a87d412636d964000000000000000000000000000000001853f9cdccf5e6e4b87231b153ea5257f52ff10dcb24cbaaaa95426d0231dbb355f9c47475d125ec1079b9bf26b23b560000000000000000000000000000000000fab825e06c2329a19de853a05c4bc65f16fa047eadba8e79607bb31b84ed6541b00f7f14b15687d67cb4cae0ef9c600000000000000000000000000000000005deaebb5f31a62fc0bc1af13da63d0af3c716df8c9bf00f1e831af5882b88974c49e8d35db2545747c85ac35156bb668af6b200fc8e6a57a954226d9a0254c8bcbbc55fd6c3db5cf8532323d4c50b4b0000000000000000000000000000000016faa5e91048badedcb33e83684d2670051c82b7a1d0ead0e28f4dddccb141a8ed1fa7606e4b6a3a893c55344263eb4400000000000000000000000000000000019b2c8758abe5d339afade4ad0c1d44d651f185f8a0030b81b136d5972510b353d43cef616ce04827d56255419831a400000000000000000000000000000000124b1e87f343a890fd690e384cd156da57f4f0fc5b1ca99c73bb0571332ec4c12d3ebe955e3ae792efadc1d5c0c67a410000000000000000000000000000000014cef10e4a9a41bf117aacd2fca5f1364a46b0c4aa0723a369fc6ede09dc76dcd8cb67fdf87ac49bd4bd9981a2e589647e2036f73e8cd5e42ad86914e192dd969465aed0c3b752986b84a0c2444c90b80000000000000000000000000000000002862fd5f38154dd452f65de0d3c1d54403cdd2a397ef416fb92e570913c543d3368a95fa114fcf48c3bb4b68895ba33000000000000000000000000000000000e7185443e5dbb656fcb9ed100949f8f7052ee2cdcba4f5c687a65a1b45bf66ede5c60b0c04845b9a870e004f8af8450000000000000000000000000000000001817be6d13cf2a67225b2eaf073e9f1614f3bd32cf5572766ace4a91f6b6be56f498b989f1c3dd3dbc9a819c029431dc0000000000000000000000000000000001cf41fe428b088a17b8ea93a653677705d5c024db530b8300752c6b100f2abe4c46dfc24afdaa2b3d53cd8ce0df1b6a70cd5c1545e76027c389645da1089fa88f675b5b6ef9217b584d7202b797f8520000000000000000000000000000000002eed272430ca3176988272e6157a18df7151bbfed5b90979752a02619ef467af8083208dcc9c7d926490b1283baa21f000000000000000000000000000000000a644f6137bde232c3a909b742d30bba096ef88b711ef100144276d0944487f9ebe8331483978a47c07d3a42c441310900000000000000000000000000000000042c67cdc10efa8301ae95d6d4f21cf152f04b235bad2dc5a61724cba64083f690b3158676ee6ef10f52dcc7061f7c7d0000000000000000000000000000000007018d0aed5abb744cb998f84140331fb2cef8d9e09c76176def48a85370c6247c2ac6fc726eea891b2041ad5edca7f0244041bcfc21ede8023ad80b6d4af4b2777c0204ca5f61854e6da34ff5e1145f00000000000000000000000000000000141c0edc966b7c845d4e68272c6a71f8ffb7fd8d56b7cabcd556a98422f830d7a81d123d701ce1479e84047328ac1f3100000000000000000000000000000000105c1164d721b6dfb05b6b69955b2f25db0e9fdb58600a3229dd516076087aaec05b837ade68bd2a19917eee7b9a22bb000000000000000000000000000000000da3dd97e693948fd6955ae52d493b3a2d2896dd4ad00a0b549d4d392e81593472e4f9435a8b7977f3d58e324c5b9af800000000000000000000000000000000068c531ddb26a2299cc584b5bbfb0235fd774a2447134c06e7de8b94993804958bbf1ee80728cc6db647e8a244462372ad7572da641373708bef008057aa5af1cc76ccb882bacc50a77b37d7047b1bf3000000000000000000000000000000001881432f4742dbe41bf774930413c98d49a781a48d6c64ee1a18f3076bc6c0e1214f92d5bc84ac65ee1c586c437d697300000000000000000000000000000000067e0a95f3eb826f3efeedc1882ecfa30b8b96c92f626aa324f4044ee74531fbfd50a221b1b0e0182d759d149d51427d00000000000000000000000000000000173f5be7098b756ea84f030e374973feb4f8811118ea6673db1db75ec6909303e571ec5a1d55a6bddf32fc80480cf103000000000000000000000000000000000f28540976a6ddb277df5951fe58e7310861af837cf31fe31c24f7b979f72ef1549372e7ea1ced15b655d24293dade7854b51c78093cafcb57c4c1f172d08257c379a9caeb5b5478cacb4887119a08c600000000000000000000000000000000188f296e218719bb9cabefd4f33d5728a1d280bc59c3d826a0f3b5338f92e6544a4cf36f1a493458e0adb246c01a415a0000000000000000000000000000000007dc8e4222c7ba78190a8e72ec7e6980e2581f51a8d6c41669b6fc9e16d50a2bf4d422af73398e76b2f39705eaf8a6da000000000000000000000000000000000b25a44523323301cc01b50d58726768c2cf61e691203dd34a0ce8d58fe4f72c1c33abfb2a56e0425fa9b7e2fe48e870000000000000000000000000000000000c6f11ea269d9061d2f462ac37401def1b2b28c47b84344d04d1f026add3237d99a586e3fcbae347a4ecb5646c8c569fae3bbf55186a89740af4da6c073d8c0e331542a2c972a49dd3bf65261dda6e49000000000000000000000000000000000c41a02e937f8cacc0be5d9f2d9fff0d6d4302fd252f32145974206463854b3a7d09b3b147cdf2d7536e970dc13613ab0000000000000000000000000000000005f9367f4e31f7e4d6e21664ac13d55f501f5368c1ca77fc439db60e1846861e6c4c3c44909469f88e02cd973499992300000000000000000000000000000000131fe6df7fff97f132bfcba1d2599a862c1feb514a05b4b7b0bccf49e00aaad043edae9346bf726e2eee498dbadf2067000000000000000000000000000000000e59044f0950a741da3881282697f4a1a522b026e493f6009227da4c0a963de622d5e421c30e0023f4118c9a036274f859b43915b15c509ab8930979312dea2ec9cfa9f679b004ee526aa5dbb25759a4,00000000000000000000000000000000144433ad3afca0a9581e7e87220a4944e26ef2eef6b887ce77d2a2559ced058e7349b36efa66c492cc75b014b3448ef9000000000000000000000000000000000267b90e45d7001edae01fb198d16dd37c43cadcd2ca87bd7cd1f0f65a95148144f5ddfe75d344eb4573c1376aa2728600000000000000000000000000000000050ade28b09b0394b08d128c089808021e4c65dac49d9fb45efb93792a4faf210230b650fc3ce810fb8d11947e9af5060000000000000000000000000000000003b1d7dd7c6d944d16724fd1bbfe0f53b6b50a70e133dc5998c82b51f817f489bfe1e0c361be36fa41f5af7c1577f2ea,293920, -000000000000000000000000000000000a081f037738b0d812da43a907e7c624e331108ffb72104d82725b9c14dec8449f5ba0e8c1a3f1379cad2c3e7aa99f70000000000000000000000000000000000937fb5d8b3c258b7b28555fb59620f114816f0fad46818a5f100bf7dc3332a03d285eda18e31e4047cb2606bc53b20c000000000000000000000000000000001574e355b7570043bf36ecd52f9c4d9ff556146d81a1e9d088444805db9b3b678fb55774865ad34d21022afea2c154590000000000000000000000000000000009f70a5cc658cdab280ed65e13aaa319049b9534a222217a08168047ee2491f25a9d2620c7343a6426bc54a0700bdb4fa53d5989b63ee5f157cc44c684ccc7cb4c74338b12fbfb534ea33db341fa6b460000000000000000000000000000000015a76e89c8938b8a27e4857aaae8c942371b6979605adf774827e9438ef739428fc53b65d32e4e152cbc6a4de42b8bf30000000000000000000000000000000019494030ae0507eeff20b69b4913596c1b9ea6927157945c8295e273707013ef1f2cd08c058f6b469a6c99ad73acc28700000000000000000000000000000000122ea7ac21a27ca7c4b00207538bf561f688429999332c45de7545046acbd6d9e96d31f5f6a00595eeb212918a28d2920000000000000000000000000000000018b023e7da67cb8d9159746bf700f9e151fa60ba8f5a28b3739de005822929cd28c49b9dbb4ca8a10729dd24771730ff4d840680013af06920dd06bacc0ce95cf0cf79e8ccc0b10027f2d28c1d0049980000000000000000000000000000000007811c759634904765029e955c3deca648fba6a9da6433b50a6d2086a59e65811d52d41ed8ff2e9bd63a4c0828bc702c00000000000000000000000000000000182c86cddf5e20697462c829f41c7b49e7976880311b01ed4d12d7174340799f19db0f295263a2617182bfd1b49e0d1b0000000000000000000000000000000011824bc20bd1b27876b4f48aa8fe3063f826b6b2c3dd777fb8999a25d9139f218f6f288955274884ce96ef2dc6d34d120000000000000000000000000000000000dd310d5e141e4eb13380db828caf74f62878959b6b2df998bebf9306965f723fcd4dae7c25bf2f79ece3e8e9b92de61b67d661ebc9008669bb4e5cffef81a32baabd71667a72f1d202ced823f09c740000000000000000000000000000000005667d8c4f8dc3f4aa0021d1026a1d0dd0bc3576c49339262e84d20198fffe33a389d28ab1d782e9d19af761a2f097b40000000000000000000000000000000002803d5ad6393d7072e149f1f2ebf70cd8961ba3bbefd648916a8ac5a5eb893b71bb6015e201dc241537ad5890024239000000000000000000000000000000000122e1d0e0859b04143f23c4d2d2ffec09ca2ce5eaa9429dd0c047032d180bcdb10c106071d9f9701c006e5eb8ef88130000000000000000000000000000000008347a7bdb3b4f381b58ed3a128134c09563b345380ec948943e738347de5b5737540b57c28d00b9d060c60942446617ee495199ebdebda02179432d42d5d9c76eead4d4993cd09a93d46cac997716a5000000000000000000000000000000000b26aaa46a279c482fb395ddb84d5b4c9c70102c336cd565ca9eecf62cb96f59f634adf46af748826590fe65beea752b0000000000000000000000000000000012cc63256a9f73f450e86ee38c54ea78baa5bf87d3bc01320f7fbd85bf11e19f75d787b9b12b8f2c7634368a9023de880000000000000000000000000000000006392fe611835f6fd50229725d71d435f704f78cabd1b5569e1c5a89d4b11f911f0e34ec034369f972a80eb407938b97000000000000000000000000000000000f4ff2d6a991fde9093000d7bd9cecb289383d259346d83bc9bf5389d4c39c82a0e1d7deb84b90ef370e0a19fce28d2b3e038e473d6f965751ebc5f69eea6f37be88cf001de0c4e4b700823d8326f17500000000000000000000000000000000193752c40fa0f466f7c8bd26658f133d0283d2ac3b02eadd27b3e9681329307f91a1512fbc53e537f9e1025a3d68a7ca000000000000000000000000000000001106d751c9e1637f00e51e0be856405e6b69421d81bb30b9b8718cbc9cfdc36c80d2848bab0d5246da84f10b478fe48e000000000000000000000000000000000827a83f28678c4e39c4963e95c2404a70691885788e5457e149c0c45d4e8c74eef55223ed15cd75fad9f7209a6ecaee00000000000000000000000000000000072667f02b781c8e0a75d0ed8f3d55e668ddcc8c61937c80653e240c3a744c961055c782ca41b15211c0f1e1ba800bf5ab2af2590309c9b9177e4f6f0fa06339fa720cf1c9fc7c001785d7145a3c9030000000000000000000000000000000001419629aaf0baf779feca264d0d9846b987506125b0049ebc8b307c4e3ffe00da1284a94a012bfd60456a4a937b2e0e000000000000000000000000000000000119a801bd0a5a1c1b25cebbbcccc7d2bed9baa4995483f4ae94121a8c6cd0c3f90a26234f51590d66cc38b8bef9020d3000000000000000000000000000000001125bd15fd9814ddd15be0997a6961b6f1c05ce7944514371f10c8e5bde271c4b936d6537d91ebed740fbefe6b281a0d000000000000000000000000000000000982a2904a524b1fafc50d540506b8fb07c3b4978310bf3cf53ce570b1b05e746981bcfc06d59a78d170573b09347f3fc9551f12084ad7d4ce346f841fef785d644821b5c2d3c8db3145fc26e65666bc000000000000000000000000000000000b1da333e508ec6b0329747fef35cb926d922091d4a45eab7cb5358f20496c66e17e46874ed9600cf4252432c29aeb07000000000000000000000000000000000c757daad8f3ed7dfd64782548eedfe904f7ef3bcc11eefc4781fb37159d07825a4c9f3fdf9cb3d8f3944277bf25f88c0000000000000000000000000000000011160e21503d6fd61a2ca0212a7d48317186f259a987a17cc3eb04a6d9251736e4a66b739a8f3095684b7d91ce6f79730000000000000000000000000000000007440ec0f9197352a3148f9bb3d3dba9b1d5add903e48b50ef3f6879859b22ea0e31b46ea4ce566930d8853520abdd14ef5823541696ecb88d0c71e00a15282c40d4826220a202be09c47fd6891b93ba00000000000000000000000000000000070ffa4d522df8b9f62aaf36132bb1b857e177280a7b6d3af6bfc79b73ad3848241df18ca7f8993ae3d67005ead9264d000000000000000000000000000000000e32b65bf035bcb11f86c60a334622d2367797d0226761b58a7db8c7324fc4bb498a558eec509c2326fbd0e7bb8d3d19000000000000000000000000000000000dd291a760393c6e962818986727e5ca5d46544dc47eb49dd828c6f74caf0599e88c4293881714c425b0697944faa861000000000000000000000000000000000f7ead0be081467f3371ab92c249cea73dedfefcb6aa16a162c06e30605e104844c3dd194b4a89ad5230f596bef64f19e32d695dd02323d40ac1eb9452cc53376ef941237563b1ee380c9824a565008d000000000000000000000000000000000ca545b53836899e507880329799e4c1a1acc17275f5d71d87b9e41ccd7a090da854f9936254448c988ec772a813bb6e0000000000000000000000000000000016c9b03fd01394560497d6a03add63c034f96744d96a13a4ec92d28719018d1eba1465e4332e53f37f2aec4d93d4ab7f0000000000000000000000000000000007019f5201dce326d5a6a1ebecf3fe50e22335593bc9d3e62256351c591f0a1a577d916055d79c0b4abe191b6b8011fe0000000000000000000000000000000017acbe72fe30c386e463f3e9b35a474b902f6712b30af88ef340e6fc6ec0fe2e606c7e26432c2a4de33a12e35ce41868f5e23ff8acf88d18e53bb31476f10fef288e20e818431f9f0d2ffe1265e8ea8200000000000000000000000000000000057f856ae648279f2b6dd17584e1388e4dfdc9e870db48ee6ef5f58389ccd4ba17e074b79ae12b728c59e2f91bac5709000000000000000000000000000000000e0f39f4beddbf05fd700458448067b52c11e963b22603f10d697d6b6286b1449b1663e032bf7bea48f2051d8ded923f000000000000000000000000000000000022cfadc1dc399ef5f12afe1349d9274cd595a9ab6ef7ffdd68f8bd2d170a4a783ce0a7303878d809a16bb8073d79860000000000000000000000000000000007e301565124eb66d59a70897f2ac356e7b0c1bfd4e3b57e508ba0cb5c9c881f9de86b91fd5133aa2977c8e81138d66971927817449ba5f053d0ed1e567b53b1179c6b62a554c8be6764d7ce203f74e4000000000000000000000000000000000edf3fdbfb03bc07871079aa4aade538a97e1619b54d0692a7f5f73d7fbc8abbf680ea3a99325e03c0501ef174deedd1000000000000000000000000000000000b8c1b5d3c926d7da6e0583f67d981af5286a04429e857b0aa4b1120604f9c8c93f04e763da169137416dc9ec4839a910000000000000000000000000000000006ca2aa4c7109f043da9cd90bc801404685db802eb8bc925d9d098e7af3d9f95ca490790b2b1c77995c050aaebb935db0000000000000000000000000000000001f40a2090b63f94f93e8b61b5ba1ac62a37548342ad81a9bd99ce8339435a7d7477c3b9cee9b531a1ecdc85a72041555ce5d6f0e44a20d0a0e2f1cc523455b001dbeef772d84b2599daec66b285027f00000000000000000000000000000000021464dded318cfa86db1e4329f302bbeca7095d910c4260799cd2a60ebb20e60152868e67a48b86f44000f267d11c33000000000000000000000000000000000ae45fa46fc8e043c3df99bc0d87ffc5867208fde0eaeda782230341a8624b101346f35fa24e1dd67ab200f5d6fbc8a7000000000000000000000000000000000795b9afedbb128a46c1eb25c52a71375903adf7d3520535372d9af5023dadb1dfefdcc0cb546e9d218890123252946d000000000000000000000000000000001852511855bb368cec51c54d95b430259f05dba6bae53b5c42d69f31371c30cb611037fbd81393a896cbdb6240114549d37f7bca1a59f65982294755ddf8af7f1c953b6e482fee854e0d89e9b269e0e900000000000000000000000000000000113b883c6bc41b0673145bfeccda414af45efe5710f436977712e7227f38911cbae851dbe03928f38e310033458eed72000000000000000000000000000000000853e32773ef1f95a3936aacbca50cdd5eed3d08dc467d7ee834487e445fbdaeddb0df394bd0c91fdb06d2883c4dadd60000000000000000000000000000000013a7f9cdebb2ec37fad172d31a717f4b538a8ee74432c5a5e6410460eaaa3b5f24d223b76bde4277097e93087b7136330000000000000000000000000000000003d6f141b56e1e2e400fe821524017cd972678a7d64f660c313e6a8910b72b5ac04328d45945077aa2946931c8dbd11706d0535e3728b9e358d9ea82df4f1137db7a02f79c0cd0dd672e24092bf7f6b40000000000000000000000000000000016adbeb3530f6b451d870b2d8292a01143986cd9890c79a64764383575771b8608ea61beb2de87bc034d3b8a085958be000000000000000000000000000000001125d7cf83239e4341c286fe0c8739e7013b234814b26a079ffbffa329ee4705da81fd12f34f49d821690a11b8f83c5e0000000000000000000000000000000005873dc5c0baf0f3297d884ac7b652c749abd0405b96ba60fe396efa179a79fa55be76924b0690c9a528c605ad4f9e120000000000000000000000000000000000fceec23f479c72e0fea0d10d3394d7121bf1673250cf1ebe72eca60af82f232fbee342e2c8705434394d4e519fbb40f56d6810620e8da932c202628c2fa9f0a9f3fda3aa07c262924aa51685d2c9af0000000000000000000000000000000005ec966cfa28e105f3496f977a2f046fb206a190fce1a6062df0fa1946f274cde9f6fa8a71089af8cc2fbc2b60746cf40000000000000000000000000000000013c77ab66fa92a2411391d366a331a40accd120db1c6a656bdd92858826fcbded296293c13ee189ea3f34635de56732c00000000000000000000000000000000162795b6feaf6a63e6ea2d34f2bff2a4985ad26463b8fac69f8525eb0a005bd377fe7ff4aae820d361592d2d88f98f5c00000000000000000000000000000000044c9d5d3bc0d99693f5a0605ed467cca8b5dc7c7093294d14015b59bfd8ac6bd479b73ed52fd30d8bd891ed971912c571e7f672ad398f5c02c989b475d12ce86e6e242d36784308e56178f2a6a1517c,000000000000000000000000000000000c3bed2f51a60f9afa6655853ec2f0e9d46bdc1277bfedffc468d9f36cfc7ad9e70365fecc84a5a40d863dcaadabf22a0000000000000000000000000000000008c5894a4f93b02fa1deda8b556798fb7d71f53046ccc305588bfc00b68bdfc34b3f0bf154ce7cb50c9536ad45e65f300000000000000000000000000000000003699501ebb9698e98dc998fcdac54dff895457d2e4e0a0e2d65d275b5798dc016e921bf1f65fec0f284a563aee66ca70000000000000000000000000000000010389c73de7f6d860c972c1f09dd24137c898e92935c45c10565ef3da3406cf521647ef80688f6e799eef4879ca9a6e8,293920, -00000000000000000000000000000000114b9c33bd09899c684e81a5a4e620eefa4e620c01c391a4df5caa75be462ec7ab027a9ae2c31d6643c48e3d75b6ced6000000000000000000000000000000001925084d2a1f537329e23c77b8a820c385ec5e12e4a145888882ec611e99b05b789d79bcab48326db4424309c24d1688000000000000000000000000000000000a1dc78c25cd16211a38bd0c70d24c84da1b83adb219e1b9c06fe6a6669d6e0281a155b4cec32d32751fff653aeef1990000000000000000000000000000000001daa74f19cce1086a87232464ba903938465da5e3e1f9ddc05a4b4dc13f1026e1b07af7254d515d2ad6960ea62dca1f77f9a79850b2fd5a281b22f52de085f12bd34e56808496e1c1388804f534d2da0000000000000000000000000000000018810adf0cc793c21726e9a27b7c558aa16b81af73f22629c478d293208a107fbfed4511d9cbcc25fbc2826bf004e7dc000000000000000000000000000000000356b25cbc7cf65107438125c930dff24b7786cbd7eb744d7f27967619d5cc02799451ac8814782eaf9aa331e6f8dbe7000000000000000000000000000000001164ab32ddbeb11c2c8baf7f311ffb01bcc367395bc7ecbe5642d344a8e879c74a554b3f9e4b6ed7db4ea0f872cf96740000000000000000000000000000000017704b1dfb111807d1f5d90c370a5b2968008a5ee9fd72262b6543c93fa168285c04931198f5195f1abca648722ebdc5630c1fdad9338fa5236f817bada168a737dd3685b327fb59d8a37329920af4cb0000000000000000000000000000000000a336a04a8fd8e18dd9a582da897016983d9beb0fdbcea6c88b7c0640620be52bff32afbe700599e3c08669c457b760000000000000000000000000000000001765fe4faeeb13fc2c007682c031ea7ff2899090e16a9a11959c5c3ae7881a1dd2c6d2b7f5f708a92349a2b0de4b92d5000000000000000000000000000000000e7c57db660133ebeadc2cb2054ab4ed16355466932685d4d11038e1e1f47b0349b68bc4e918dd48ef8e1c5d7cc53f7800000000000000000000000000000000169b629ddd7add588b91d9866a750570dec58662e43409031a5e25f1b2913c5c5a7a7cf666953c99835431f091ab1b140969599bed4899c3c47e1d4081027203c73233536cc6e45aaa78a4f1150a51620000000000000000000000000000000017d03e9855f3bbee719a15208ae24324ebf1879972ac134b027c9e03444a5736863bc55604158e81b38c7fd78ba4bee7000000000000000000000000000000000468f7c5478cc0faab7098dbcc455bf18525b56272c2d02cc1febc1825579a613edc6b455764ffc71c903a0704224a4c00000000000000000000000000000000067104ba5366e7e11bd4d516565d9cdd93d4390f2af3c1ef2ea3b1e84ee8e5c0e0fd8ac11ec9d2553e4cc13b277d473e0000000000000000000000000000000012e10495ba15b29c669cb9683b2fc7a45fe7ddba743b4a39677fbf85aa738480eb9da967eee69b02ef14137e102e240eddd438de35651328de7183dd38820ea2983488ba31d401094e59cacfcd1d031900000000000000000000000000000000078f8c17427847ddaa1665d206866231a5f36d3a7b4e8fa13910161566163006b5aa5d9696f423d0c44195de65326f21000000000000000000000000000000001613c465b65940f43c61b5e3c93313ae49d92728518d9cdfc57b49d6924479b70e281e724e04fa5f165b5999f1c1ed3100000000000000000000000000000000031741b6830c16d730619457d42767a51037fb4118e00bfd6cfcd8baea35ae76a5159bf1f4639fc2951f0b57446110e70000000000000000000000000000000011a618ffbafe4bad0a435d04084233495e5f7fbeaeb66d0d49a8177f562329b52a5ed4fdc680b791f273a7b0d3d4b349191f2b2cc76d848e456d07c84c0826a8861981dc84bdc671bc9b5882d387a41a00000000000000000000000000000000043c09eea638e524661c60ae3704fd1c18c46443ae134a0ab7b9a98cd398377febd9026c28b3e1e50de98766aaf0083600000000000000000000000000000000105918aa1476cf52f91b9ddb7c23ac18af3bd5269dbafc369713687010720affed6b12af9414cecd521cf0c7f5416c350000000000000000000000000000000019ab4a3eca904a15782f560bbbc8819dc09275f1f6d7c3b8e98aa0a96ec33dcb528284636b0f42ad0d503489d17161ff000000000000000000000000000000000a2abada18e79c548d5829991a65491ebcfe0e1a2c89a1e05f06a0ecd197797c5ffea0ae90b61f54c6b3fc844e0eb3ddaa76094782d0c06f2080d699b81aa04a60891046e0053d2fa757c7029df8f848000000000000000000000000000000000d457cb2c77acc8ba4b19ade0c724a2b6b0966ecfbbec8cbea745439b9bb7f3dde2febf9fcd6c5e6139fd7175e57b1720000000000000000000000000000000003154466283addb0d0b5d86a9633f8300960cbe8bf6a1405a3a040472542e9da63fd4f79a43d641a47c2b69a31298d3c0000000000000000000000000000000006599794823797f8ccea9daf0459b9d26e0d207f5fb95383c6b61eba38516b272e8ae6ddff2a9fa791e69c0eb25f3e470000000000000000000000000000000018be316bbe0416ad7deced1486d4e31490f5dc7e379c17542b7d3e9dc77bbae9c992e657c884db320cd51c2141a4abd2049a751a406657dacceb3721461417571a0104e11c1e00805becf71ee77eadf10000000000000000000000000000000007ba1ec5293d169b88ca4d2d92eacd51f0b8cffdb403632ea8ffdebc37f3997baf736771231335d12717cb45b51be31a0000000000000000000000000000000013505cc24222fb2ba9e25f5f3497653462f5b10bdd0dc88f9b16d5643a99ddd4a7749dfa6b566f41cd2da7c2b1ae93d2000000000000000000000000000000001465fdced698ca76d5faaa7e4faf1260cd5c4fd2939b16d3593e3588c92de3d003540ec989be9632fdba4ecae889ef180000000000000000000000000000000013a20cecd5e8f161ac70e40b8e9ca4c23e2b267690a3abea941c293b03acbbe4fc68a1e7b6d35b79ac46f65edde73a3e0502d56084d1be7179fb735e233978a5a3c2756d780cc0ea6a8aa92b1d1f7c4f000000000000000000000000000000001936436783f02f3a5307bfc0bd8c0a00ed8013508a440d040ed4f45b37a4e89986102964a328e93fabde6d9dc7ca424900000000000000000000000000000000000f16408b869303181b4b4877b554353b26a7b4750b711f3c41cc4b6682b2113cc772cf9bfcd0cf60e59ef29a5d0814000000000000000000000000000000000d5880e2ef94663ead736687ee725f7ce98fdc594230c1ac9e8345d39754bd616e261076aa5362776a6026129bff105c0000000000000000000000000000000006865ce3cdb5081e86535beb990d95ec3d75f67c7e881306607e4876c42714d627f8d548849aece4382d1c8f2b693bdc9787a6720b8db1b4f0e1d535833ed20b519a0e4d2e9fef75022aafef523713750000000000000000000000000000000016d941b6a0dc023fa2699c836b74e16c31b4cd51538f73fbb271d163519d4de1cb0f6ec2f8efde22c74ffb532c576b16000000000000000000000000000000000d10a7bfe9541a7b22d455f1b68cfe2422a83a070d93476aa0844670f02aecb36e9f41b9d66e8e9d0d67c0ba85c99f44000000000000000000000000000000000d7873f96d45fa8c9ba9cb4913a7b01c8e38876b6bb2a05506d23df0491bcffb42983ef663db85bc3cf755f476291a79000000000000000000000000000000000c22fdb83f9991c85b3577d1ed5a171f28460d79dbc6167b0c30b200235c512f999066eb1fa449115aab55128f8f2dde10b47b662e8cc8dd005bdc81dc6d98d0eb98f86b46c0c8f24481af9120e84a820000000000000000000000000000000010faf9cb9d0fcb487c9e86a2d2123105baa8691d82ebae8f5bb7d5ae7b7d8154837120eea86dfcd35ea5482a7ebf7f8a0000000000000000000000000000000014e40640eb6e8e38651a2eac05165f6cf5e0178b3711f34828766ff9db951e1348f0cdc652a78840dc24ada8b1c835c600000000000000000000000000000000129db7482ec62873591018a8399a8c5e4bf00e8bd9dd78dfa3d0b4cd1d93ce5ec7531e56d58b7a1cb3e58f062f6895ee000000000000000000000000000000000d8db3b54b6e71497faed107b31f5e44f328780cf01c62cb5ca00f99f10385ebb22a367cc89505640d1106a9ceec98c4072460e3c5349c8fec9944dc99762625262e84c70f10d0a92077a351335127470000000000000000000000000000000011ae9bc3ce04df2add17e57f260a72f88f19a1e44b0b074cccb7fd547035038d19e5f2228db46843343a69823decda370000000000000000000000000000000015ea64b6147ef76212bb5223d6d5ab9ca866799365683720866d8ce1117f60bd552a8e9981c095894258ca3c1bb5150500000000000000000000000000000000173bd5cb455b80b78951b15180fa7f8fb4725c1a12e5c53df1b9b31b45a29083e66c7116741d9aa93448c81b5e6014610000000000000000000000000000000007eba059855ab058c2066c643ef5268c864d09ec9962537d65a1686322c374eb5ab8eba4c4260ad0919dc18b4289a694f3177c4d865caebf1ef6565bc85e0b0bd51365a6f321e26b97cce887bc3f44d6000000000000000000000000000000001598471460ae082c2e2568602c99923193c913b9e803cbb7a4503ceff369e8c4bb3a19ad245c08192e12a2e9b3e75c4e0000000000000000000000000000000013b289bec9d97c529382388f7037749c10a64f915746d23d8f37e15db9dcb173b3a6d00bf45e67b8c70959472148321d00000000000000000000000000000000094a99f9b031a51b7d54f7b8865621b204c85d23fd66fe8ce007f0b852f8b5b895010745b2fc469abb670e38fbc41e50000000000000000000000000000000000e36daddab2134f65696ede36c50f90f9a1c56165e09243cd56fd3d9902d3c78cd85e7028f6dd466f6a8655da62ecefd393654ef7ad8687c8878c55a8240ae9df04805d3e2f194e960d5e498ae3ca17700000000000000000000000000000000050a818ce247367e8b57673d205d6bff8c650bcab7bf794dd32494669eff865fd4e05d7b4d35eb579eb475a3a0320ff80000000000000000000000000000000017ae5d612bdd46e1351dd1367c08c16ceb002a29832eba75e48d4c82e364f17c58525ee653a0940955b874da6a5bcfcf000000000000000000000000000000000eb2075367b42a0b3dfa30799ce1ab327eb583316d15b8cae21b716e6c7fd8cab96c67bc39e353f5e842e74995356c070000000000000000000000000000000018ca4b533da1baab37f05afc3ae0afe976e4f4530401d2f97176f5c73de3eaa75b8a34e8c6c0543ca0a08aeed28e478bdb9f942124a381b150f00a59e4579d0a2b7b728f62715633288fd03d01dd12dd000000000000000000000000000000000b3f4bfec920018663bb39c5520491da5c538f82138f03390c768e088bbb2880287196af937f1f70e215edd49d1872ea000000000000000000000000000000000037e7607a60cf235d8e4ecbe69d378dc02f0a8e40b7f23745e15a73fdcfc971cc8707d55a8c5b91d9a5f42c2f49c455000000000000000000000000000000000467df75c2703ccff1a01fa5bdebde210b61b5f3fa33e76e55be5dc953f4758c3a2c499cbd42b256ff5a2005949d9bbf00000000000000000000000000000000010d574c69050ce9e909dc23a76e9a2106870e8d8ce2a0e30d42cbfeea56ce3167535a9af1d453d4d8e6a450eff870638e6eb65778a328cf899f66581ac7a4a89e0e824c15573bc68c02cdaad89cdf24000000000000000000000000000000000907fb825f247c85d93fca36dcede9c22a409fa82fcf540593e8247c17875a1385fe009f0ff43853c404f6c96e2809ce0000000000000000000000000000000012bff10bd4162207870f6363342f2541804adc6a4e3f7b8be51d361be34def7a85fb39357c85a4e8df670fe39233bed00000000000000000000000000000000014f7e61ccd52bbf6d050c9d506751e03c8771b320872179a9f0161ac5736edc13bc133bda6239abba1ae09bd6c16f0c3000000000000000000000000000000000ca78624563584f8929d72668da70218a2da12b42c4b894108e6b103201372554fdd6b3bbbf2d94a9d0cf4053eb07d460940e3620c59504062e4e98b5d4c8cbccdb017c47a094d06253743c29465731c,000000000000000000000000000000000de8e87899b294575392d523ff6e153a7c2038302ac74574bfae7fb222558f2b4c9556be1bc2757b83ebc180ae710187000000000000000000000000000000001881c7688debe3ff795788c90397c3fe3d6d9f56da9221778d7b12f5a54d8c0a00e1a8d4bb9c0b1d578dff862516b5dc0000000000000000000000000000000014cdfdffbb956a20d8521ccdb214adab14975d22ffbac107b2c15f42e97bb823c6a3945a5b299d3226e2044e64f8d1ed000000000000000000000000000000000eb769b301cb7c0c976623badda4db8ccb18dc7322472b5fdb969393d5d82b3ce94bfa59dae06ece424bfcb88e24207a,293920, -00000000000000000000000000000000164227fbb787b2d47ceea93faf1cf7890f48107ffae3628192235aa57658d9a2861db13fec0e58c347571c2ab0cd11ea0000000000000000000000000000000015478417b6758826b1d6fe0c562d43451e289dd50de31ef365ec70faf961ebb65b510c4788b6c7da2dda9cf56d3c8a74000000000000000000000000000000000f9e50d802ca8cbf80caec6489fbb24a2761db1245d9f7e820e6747bdd0855902ff211c427c00157ed9b1bffdf39eea900000000000000000000000000000000128f69ef5dbea5f80dbb9558a25f133b9ad77492250e0654f8fa5b55266f2fd26826a5c373afcd74990ebf768d6d8fd20f2f697ef6783390724e04b81d0e18dde6533eea9f72c1e10bc72c7b954659660000000000000000000000000000000005f7cfb31492dacae51caf4036d99d917fa13b0d2353bbce4e6547ea744b3a49b162deac2f107149ebc2f79e74828f720000000000000000000000000000000015ed4627efa9b318cbb52f518b734327f5d1cfbb097adc6184c5034620504181a298ac7e52759586dae2e107f121a9b600000000000000000000000000000000023e832638849599d9d7854d3ae18648e67e8938ebf606a7c86c3a7ea21cab8d4dd5d9cda5c482e05d351ea3ccd854710000000000000000000000000000000001849665396bc36d0301f4c9adbce81fd2f2d0c7f89925487d91a25c6bd0730ce31678694a319666cf42162608ef15a834680b934e67bd7518f0d6a3a809dc7faf845eb71d0247291d61053d5cbe0ba20000000000000000000000000000000012c9b607e29e35f260f3c4617b4217d5dbc6953eaeffaaa903710195e080d593972e7794897eb176aae3539401a483b10000000000000000000000000000000019cdae8d1d9035d1fc4b4db09e7da3c20d3b8777523155d407cc6565a71a6c951eca609d328ddbb165c2b5a3e6b081da0000000000000000000000000000000009c4629b67c1c50e5fcf316136bc645e9e62ffadac8495c084f97e32b0a3990b3b1019261f78de576ff7ffc89e36e2af00000000000000000000000000000000070a49e8892c5b523f5914e2341dde63127b694eef556de6dcff603da109a53b342363d9a854dda3d2833e25afd5b57eefc024dbceb522c02b88810ada9a814bfd085fb63d570663a64bc0658e5ad0220000000000000000000000000000000018d3c9259f70312c803dd6bac6488541f92482f7eb61ead71fa42bd5e2cca9338218d62835051bd308799beeed3b422b0000000000000000000000000000000005e0da6859601b6ada82b1826a455a846f8b4e54d9f22c3c639835a8a89e17ea2d76e2f49fb151f519de3e9adb78f0590000000000000000000000000000000010113d2fdc1e8ce0027b651cee6f9f6832b531d843db3ef7bf209aa00018715c1c42c68a82c53247a267929ea3c9363f000000000000000000000000000000000e7d1152af6448aca78aa7983013395f0dfc298848d86def6f017780e9cb144bbb21540a14a4d47b61d7a9b8c62376fc2c136f00c97a515076f6a0b63faf7e378f2cf04f8a90ac942fd70e25e683cbe70000000000000000000000000000000014125c81d4d7a8ea18004d798311f0d80c41c8e3a08366f686145e867192bbb13244f9f77217559cae72a150faba12a6000000000000000000000000000000000fdcaaf79c0607ebe9c8ca309d29d32284f3567a18dbbd23da9d96bad7269395ec2445d153711df4c883e8e7f7b02ab2000000000000000000000000000000000d34dd6636ef18b14f011fbeb62d33ec4358166f96f38a54c36b8797b51c1bedafa43d9f51fa4afcc2acc0cdd991997f00000000000000000000000000000000017337fab49d545caba55b763c23ce9bb3d3cc475f5ca37a15322e94c37825fc800cc7ee67bdcac66f9b5c22b03bf6558b033f2270ad2416d03dedd4bafb78ddc598810768fafd349a42438923ddfc930000000000000000000000000000000013434d32deb96edafc9a0e855281970b7c748c92b3472b34cc758dc3c17c4e6fdcf3190c910fa54a0259ef8bec75a3b300000000000000000000000000000000137df92ec14dd2fc02c0ec15a4e63547492154b4d4809e25f3ebbf24fe84255babfd6949770ba61637cc67e8ff299a2b0000000000000000000000000000000012fb20ef106e8cf3c79173e15dcdddb216c25a4de6797e411fd11d5632aef1304b36f8135c915c8c38caa2d778788f060000000000000000000000000000000014ef5cbe5711a815b9ff845e9201745f4117149b54ea3c6d1606060a192d513aa8ffe73425e37a42537773796b6fac8f202d0d506bbcd56c92bfc6fbab36bc96716de1af02aa166e7db2e2a0a4c19cd7000000000000000000000000000000000b1581a5def94e95e565bfd402cb84f2f21c181639c047d8f91044da84bb7854f5cb4eb3a6cdeb66569d99410ca3ec6c000000000000000000000000000000000d8029828f4ca245cafa7f396c25592ef08f6768e1a5b806450be6ca5b548cfb212d8c4787c3f15fe922f466dbe518c0000000000000000000000000000000000f51e01a044b6da437e3850349476437e4ff8b94fa190387099b17e6462040918cb2eba3b10d6044ff2123242005bd6f000000000000000000000000000000000991201229a856f88348381e1f2e282f0487e7daf1e5a4ac3854e66fa3d1303e3c20eb9eca605859e7d46dcfdd7615cc8329762dde1c4c91043a740a8b9639e83e809f749fc8c4853966cb2ea520620a00000000000000000000000000000000011f1bff5df413ade311b0bc3b46c4ecb11e386b886b71226987f14bc1a3a4b986412c2bfe8a4618ad5d70afacf4a3b4000000000000000000000000000000001972f49fa8b36d11d9c9d4ed6197261506b892ce6dfa932b87e686cb197560dfb8718aa413c38ee1bb771a5618c17224000000000000000000000000000000000e563bd240f5e18b518a792750c00aa5dfbea1f79b80a71369238ef15df9885d341d6901fb9168a2e74249f036e9a688000000000000000000000000000000000670e59ebf6e30b458ea505075840ed5348563efd536c31003d8d0bafdacfec7ba1ed401c616a3bab431a0fa71bb6188ea46572fdb37fe282203172c147715bf0a16e02a62bc79f33cbfe36703c95a7300000000000000000000000000000000071319574a93739586eda876ffd3be5d982e6fa04f5667873dfabfab83ddf603513394e0dbb9f418e725b02d2dc7b876000000000000000000000000000000000c6a8e0261da2ab499bf9a639a6e261e8c479f3f2b2d12992b41a3267e034c25373d4da4645626e6343e867466bf3626000000000000000000000000000000000045a0312dd5fccdd19edb65e24d5ba50e44689a9748ed9ec208320bd9eddf8d606b9340cd34ebf983e69a65c242fed900000000000000000000000000000000090b3dbebc7dd49e9f764e99c43b5915b67bdebd00d22c80e36e08873e5c5186bcd082dbce94f4f230b237d60cab7107b9e49472b9b74cefe5a951febe595b0020c43fd54150445fcdc4292c5ffe65f60000000000000000000000000000000007b04063dc315025b8545cef11be6b601fb4ae02597d75979b4946f3872764ffdbfd309f5ab3b36fe47b810f8320c1b40000000000000000000000000000000009361927d02192433a8d3c3d7871d76c6d88361774913067d16b68625aaa60f5a4ca19b6fd4140a5a11f92dec57d783e0000000000000000000000000000000012501f19b73fc6ddb4d194895e5cc2b89ca84defb7ae94f3170f25417965102fc195f38dfb7a2d88aa4b24e4a2fcaa4300000000000000000000000000000000141d0a0be60c32247f6cb0e0114251ac68c90fd43651d58c3108c728601ad6efc27c27a331a2f086d55aed54b3585fd1b6bfa1ec877010aeab030b96e80d2e27b45a93c6a99e2aeb3ccef22527c6e47200000000000000000000000000000000043f74a82ebfbbcf4abf3fd02eaa4483108a3446c9cf041bc67f5078d1774308ddcb3f918d7999d1e2c0876177cab6790000000000000000000000000000000000da7d4fa72dabb314ad8f68b61fcfa38627d1d7719bc07767f596671c58cca16e005d36e42413d03da3c643eb46b1eb0000000000000000000000000000000019f3f8f1a4008f9db1b604373d3566ae7c14a9147f80597a31839b83f0f8dcdfd829f7fa933fef3499b671867c3121fc0000000000000000000000000000000018bba4bfcf7629fcfa47935e36462cef4fa3751c7affa2ee2cb2fe3e3532d46ca1d247393ea190fb3f48077270d6a8b22810705458845232e851b33fdbcaab01966b8ed53b455873a966c1d6b89363890000000000000000000000000000000005a1e0e3a023f67aa7ab0109814f130a05c8c739036b98c70c8a8ddc1828d2cc4e2fcd16de4ef038a7373d15c78e81f10000000000000000000000000000000019e2bb467409b3dfae0b06244b4140de7f75cb105ab897d1ffb999c6b53bf3b60a3d11354815621c5d9f07962a237ffe0000000000000000000000000000000012e745499d5ed626b4762b57923bbfae7f1209408e7ecb8813a545c4ece0ec7c48a4015e0e264b47fa08fa82c39d3a110000000000000000000000000000000008acfd3c2a2e17be41a70ebbd1ca2cff2eda8a359e0969a389ab0a6fa51db5601b386dd035b26232be08d704a02033a7175fa4954e56dabfd1808f93d2686e0b4fd285bcb78b80d15e10e63ea8c7b646000000000000000000000000000000000fb464af51161f9c2758acc09d16754d4d8ac52a37baf2fb6ccd3bca3058bd3cd204de6c8a0bfcce8822f16ecfcd0601000000000000000000000000000000001819075eaa6d9e3f0568ecc2e507370f938a65169cea1ecc40c9cb4d02c83d7964254602e3d041ba0f93c24369fdf3940000000000000000000000000000000016c179832739a8129d2ef184f4d1231d24bc8d4093670a63d73771983152ec322b6a8c954565d61c2af76c4f6ef5e8a2000000000000000000000000000000000f6623578a4fa45614f4b74768adf65a753a35dacc84af005fa4d7328d733a09f12f709a7bb7f89060f60d4fac85780ae7dda7e5373d0e0afc3da1507416f47ea8b467a5b6c2fbde484aec8777ab7559000000000000000000000000000000000189724a2a0723e7727d224ced126e4288f4743f6855b035722f2aa36cf2f0a6fc23f6835c25222b670c15248884451b0000000000000000000000000000000009a57d85140f31ca58e38b4a99c4ef103f0a4af0d5546d416134fa8adce6ecca6588c3c56ba06b2f59015acc1a081099000000000000000000000000000000000dfc67b7644851c3e928ea33aaa0f745a18983edb7488b148736e81ec0c62345c11e3f0dfce729d893dce27ea249860e000000000000000000000000000000001712009a81e06a85a225a46fac056b139c8da05e6b72074ee4079316e490a06f51c62241e380909b86239d867d631be16aa731f9393d2bb32adf04f19884dd1a5e7aa36e46408b847222a153da95aea5000000000000000000000000000000000976746ae4d9325d5e8300b57ce99650f28055b5e020700ee5f124fa76ef3bdb9923101c3a1f46b6985b8203b4e8c60600000000000000000000000000000000057310c3b6cff6c849938f533b401b0cbe10b6ff3736c79a968009b2c0b90708b6b9a98b8e594cce09c579a64ead846d000000000000000000000000000000000d39511e47f33e310332178b8a0210e76e4d4c7408ff5c2374f5e7bde8335525e03897cb3e2bdfe59bb76b21cc6411df0000000000000000000000000000000010c46a621b7fb2e7ceab8943b3371475d3d6f132fb658b8c6bf299888711f1b344ebd4a5793ffe6a7a7eec8c66c80303985f367919b0f3c667b1c1cacedeb0be1f9cb175c899992ef55f14e9b7aa6ad10000000000000000000000000000000011ffff38891ee56cb1fc062d02f6c9993100f991a556445b5ee1b1b0d56d8e64bc6eea4d7f69a6b6dc55ce7d8b4ba300000000000000000000000000000000000d6cdd95d1ab2a11ab424d7aa596cc7e5de025c57217da0da143887d7dccd6fda0addae7c2fd9e0996bdd0d23128e807000000000000000000000000000000000499b3e69214fdb4db7dbecd619ef9c6b5c8343c808e4953f593cc89adba02b5cbc56a5e7a3046c6023c5cf305e54e85000000000000000000000000000000000d267e21606c16479065e47da8e3c058cb59f55a1316a87117a73dbb067ec26f406eba6a40b30ecb00f506bfd3c32f4da3041cc52c6f1bf62dee4c61b1c5e35b72ebff7e8e89b05353388b551eb10010,000000000000000000000000000000000650fe9f3cb3620e0bf1654a7f1dee503b79fe2218739bad608dba9f1e5330f325b4fb7c340f118eb10dd0776fbfe63c000000000000000000000000000000000bcbf1c6a684dea5ad6c1a540b3525cbc64c7c431f37213bc8b08c8d8915a331c07bc899d3a2ea72a9a4bb2c539cf56b0000000000000000000000000000000008fca1c364333f558c7284afa1be486e84bb035b049a2108b0df99395149de83549de153a784e4df2b0134317c85292b0000000000000000000000000000000002784cc1d11667bbd0759bca35a16a1baf49a21765c6c2c3bcdd4fc9697ef20f1274be5caa0f820d37e843bc38c68957,293920, -00000000000000000000000000000000051646993c3aba532988d7baa07eaabeb8366853436b8b19c0fe3e14ed45fdc65448d749adf745291ab5ee62d4e824880000000000000000000000000000000002cec01290d8e51ccf751183dcad20bac20b8231804a2b6f87f886aacb61d31b14f2335629e97af0ae0546a17a4cca49000000000000000000000000000000000762afa7b94ed580fd07d5141a8e1299c6ec439bbfc6c1a4d695d9aba4ab5d6dec93dc4de47096d72e5ad87d879eea190000000000000000000000000000000014769208ce8a9682c8e0340f68a0290a7782c2b04e3c13027f0b23966eada2ffb2156f6e20539738535fa0ef097f78d6709a2e80dd96eb12edc481e3d58893bd0d789a499d5289072d58c2ea80b036cb000000000000000000000000000000000acc4e3ccc3574285c19d2545839d1da9db6770b078aa399262b7c91a7c41fb4c83fe7dd0aad19f4e3eb2b56273f664f0000000000000000000000000000000017851c99881677b89956fcdf1b8c5ca5dd0997d810f3fb89f7378dbf7964926cfde315f8722531d6d715b4932179eeb40000000000000000000000000000000005e374a4c7118a76e59cdaadebb1c4e635b4dd18665010249f3bc78d559455d27d547856573e264c98ba39f6f3abea69000000000000000000000000000000000a532979cfd5263c774f629027f7624799dd0f9d6a77f675d790a85fccccad6e93c00ff2e5536b8e9a92443af14611e69ff35bc510c86a9e72c3e9c6b49d2abca546f7a62330156ec09c6fe6847a400e00000000000000000000000000000000056f109801b7a4a36fcadbee7219c06ac74e4a3f7b81616076c33ba2a71d7ca0776b596fb25d29992fa26d416272a4b4000000000000000000000000000000000c02d7e6ec50b778a7ff36fbe5751ba32beb1c2024b17bd99b46239e6dd5a708d2fc689e8e8924902e0d80287cdbd6e90000000000000000000000000000000016f18df97f48aba4d1b64e71eb894904d02ee7f6ba425e58f38a08542319e2498cb0dada8dbbb81bb398c9c924ae44270000000000000000000000000000000017dce98b335f536909ce01647aaabb918942ba2468d9a07c5516cfd347e1baa02029d39de1b2602932630e4819f2f00f391dd27628d0808d4a0773509737597230d7849418540e1fe4498fd70d39d16c00000000000000000000000000000000005b23d6f76b8bd4f334e91771383856794d1dc65b365fbc0c94f21fff049761d7379f0d512c42ce13f878e0661712d100000000000000000000000000000000009dcf70c16f524ff540f132b35074cec6ed7dcc1f319432a0dd09b3ded0778ec9ad0f05d67ecf3ebb7947951fc4b25d000000000000000000000000000000001075fb15240d532a9543dc59cb0098cbd03da77c3bf85a0ef8be1560958f8ab57d3777fab5836ba98d67c721a4a8cd460000000000000000000000000000000003511525fcf6fe224eb87b13999d2548b6b8bb8069fd354f298a025b04a33f48be72d8e82a99b9aa34ce5ccdc1f1a59c94f11b10e4c45f15d811e3db4b947ee6414e262965d7b5c23a731b019e63d5130000000000000000000000000000000019039c69d52a66330d2d8572a1308bd88159f0383c041ee7605d0aa86f1d0fe3e884d0a2ad9c72405149b5fd204ec3db000000000000000000000000000000000942163eca08672af3827dbd876b9c1adeefcd5ae74a2768fb55f1e8b342aefbf76bc6546853a2b33e26fa866e60a4e9000000000000000000000000000000000c60c6bd103ba5bb5323b5107373cd8d706038bf5ec2b367a43bab72411523bea35985b974c756184c346626ab2622d30000000000000000000000000000000016c4a2fc8a9b3c54f65cd150c80a3bf70ae8dbacdcd37128514b4a881239023e427f0b0c8984ce219207c458bb380da970f7a0ee05cfc3f63d46a3151c20da53604628bac70d7b521b3be65d7b2abedf0000000000000000000000000000000003e3df9a8ce220be05f15904a3321a6805ab68bbd539479be56b2a870c3d61234e9cda8190bdc89f48e7f0dd9374e1d800000000000000000000000000000000040446db3ec43e3e67dce62efd741a4157e8ea2597a143f7d6273b66c7045daf31f72397b4b9d374328520893157c1f1000000000000000000000000000000000c3a7dde5b02df5f7c1e750a9ee5314a580cc6ed53d326a9157b507ebd6c2da314c37a7f1837f7fcff7e8754ab603b7b0000000000000000000000000000000005e617ca4eced853f8f2e9fdefef810c97eb27d5c8bd06c5b4ea50c03761c01e8adddfe27d2d72eed8cb25ea7514a4aabd991eb5e8ac8ad7cbf8fe64a5889b715a2409305f2366b278adcd2144d7be8c00000000000000000000000000000000104ccaee210aa8196010a6478702a54cb7ba49c80a98ecbf5c0920408ff8b4a7568212bfbf3561b6a7790520bb73bd42000000000000000000000000000000000870ddd51dcc76c8a97ac4b4f23819df48dc8a8798df0450d7a45d273f830c908541dcaab7b066bcd668b289c846ea000000000000000000000000000000000012fdae32b020a346ad5edc3bab360fb5ba55004ef3dfe5f437e841b5dd7284ddb3880051956c8068e49a3fd165143ac50000000000000000000000000000000019081bf768dae314fbecec408d687df5b6ecb32ec24b41f9febd583c05693f80345e6b9d81322ddc72616c1cc39a86811a9caeccc2a2058c2f5a271c09036d73320f9bcb31b7296a796ef94ca4599757000000000000000000000000000000001316b5ce5bcc168d76d2c862230ce604d02cd3d242c51c250bc6b6fe5c380c9e83fe7041049f2272481ab38f44648f4700000000000000000000000000000000079acfc2b9629da9c9f3394874e64aa00527de21e726f02db180f86cc0b9a97138c2c567832e287635721ca40469e00c000000000000000000000000000000000e11807dcd4ac69fdcea71e3e6a93dafc27afedf12c2998dbbb2e4f33e37ea736df73af791eae69bff84f3bb212bab47000000000000000000000000000000000e834a34fb63d9df68d683a26d79ecf8ff67066586e5f760d4468ad196c66d4ebf8605ebfbb7bde201f47b35cfde3a5d8ed4eec02c2af286ae19ad5f05642587cb9ad93196756d269c783a11f23393bd000000000000000000000000000000000990f115519d2125d47b925b613edc3303110e9040fa705211e0d772edb2e0f7f88ce521d1738a5f65c9d158e9d360c2000000000000000000000000000000000bb951a16decf9be8381d0c88726b53d90bb32cd8aeff962d48e43863e4eab1839bd80d7434c7eb808bbc0e32e92a4290000000000000000000000000000000013dbd5bdb7caaecc42ffd81f14be0ff3d8fa228ff121ed4f2f3ad5961fbce617d7cbc8133fd49e03caa62f7d1567541b00000000000000000000000000000000195fd9b85e19d0e3e1c93bab0380cad6f6f3bdbdcbf5c6ec32b7de7972421d0065cf0b265f6250c02eada67e95284bce26f20eee9bd019f9e0f5c794e22e770128737198b5f5dbaf5b7d18040443a0bc0000000000000000000000000000000009ca977266277bdeb985750df47353a6b81c5f0c473eb3369d25a01df67610bebf66a6de5727a465131404025e90441a00000000000000000000000000000000054410a13287ecf4aa18f543916fcd65b15cd5d54617433217b0a2b91a79fea764b511b3b270de3e8985e8f6a2fd8c380000000000000000000000000000000009a9802a03a7c9fb63c1eb13972cd42ea2df614a0972b914c4015c2e8630af319d12fc8108b4c88db9508a9a77d9e57d00000000000000000000000000000000094d83483bca296b20b7bee124f538ae9c659a84541f5c9d9fd22e98251d2b48051ac55ebe07bcc9d2e9109f526d60a6c470a66cd3428a44a7d095ef410126257175597a333cd36ce6c9822d1ee9bb380000000000000000000000000000000003f2d93ddb6d5983fd5521c1d1726addf662af0945aee54788855037f47a013d2fe595231792a05e1259c5e5a8c553a900000000000000000000000000000000004f4f4e7df5dee975fb440b5a217c27d9d1eb83a5ae280a2b147896f6bb864abe04459c17ef56d784d3c4a0b7ad3f3900000000000000000000000000000000069da36057aaa89cda458af4ee27fd9ec969c8f7612cbb153da0e010d67bfdddadb2941cfbdba8c43019a9f1aaf9c296000000000000000000000000000000001545b8325a80176ea148a3d9301debd7046f33a1b419b4ed01916a3d0a072037fd617d96e0bad32b208983ac3be7dda4e53fa8fb708204e619c221b8ecee14fdbcb1f94731ac2c858787ab33906c9269000000000000000000000000000000001536a81b203df2640bbe7e695b5fde186021d21685f24c25966cf11dde554d49bcefca64f16697509a9ca86e58b75eff0000000000000000000000000000000014348a2bd4907cf081f2f7bc944a98d3fac671abde029995377df190f7f60319b8de1698b99be39c821328e32a449c760000000000000000000000000000000000e18d4da3823addb2a6cef8336c83f99f390e23d7129365d57035d4363aac7e9c4da9f8000f086f7d2206666f990dac000000000000000000000000000000000d6ba54e2af9afa57ff4536a35e9b61c8d8fb3d431b653a0c66a2a4b8f11d9b5c45389f894d64485233d4183895921f3abf8de43c54ed59b936e1d55032eab5c9d9e04e83e4696d969c24167b4239f62000000000000000000000000000000000d88d5719e07e2332c54ba41f330c7763d2b2b7c4140d19b8b0972fae6ef902415de5f2abcc2342fce24d3ed8ffe156300000000000000000000000000000000163aa2c768eca58194fb76822deffc37cefe04ceb70aba38a51f507be7cd64c0755abdc2e49e7db234cd5d68575c2d7a000000000000000000000000000000000e443d9953468b8cea4eca4f5968e214888e2b95bc20ece39483ac551d4e180c0b0a41c4668c8ddaf761a0ac03fbcad3000000000000000000000000000000000691930530ce86a1354d73cb21ee32d968e6d89b12e5a09a7991c7d27dec302348af7f49c3e0de91e1a1838aa11651e795f59041329b6c3e6aef01d3410836852f79cc436fcf23199e0985c56f65c4f0000000000000000000000000000000000d7c6f9d4aa794f34596bb9af4d62363462d9804898ebd7c7db7544be1f46b4bde488ec59004adaa0cbe40aef525ce3f000000000000000000000000000000001094629b1428c4c284b7a64d0623e10ca0c4d395bccbfaad89d1a737a3887c10b714541f2681c33e674c3b99a36b7a450000000000000000000000000000000000d6812fad9c5ea365a64ebd3150238349d88b76d041ccaa7e637fdfa6c715d9d6dc3d3315cb95fd6919fe419d028783000000000000000000000000000000000eee5cb772ce02fe2a4883008f17570aebb902ad7c40b4024a5b24ff75b3aaa2b54ace6fb4601b1c62837a20204194dd740e4a207ab5dd4a0621fd65697f5d30b8ee1440a5f5c5e74a0dbc6b6391c1b0000000000000000000000000000000001026d21e075fb8921dd849c98252a565d39ca9f5a62a825e7e3e77ab5be6620e76e45047e51350c48d9a4cf98a1222a9000000000000000000000000000000000f6459a8287bb2da77404a515dd7a35f46a4aa49ef72cd2cdefbc5e5242872df5f7b7aeae6848d59afa1dd142ae7caca0000000000000000000000000000000011e3545151d4e0b034b950cd2f1a3fc2d29e9d53250ade2482b7ea6075dacf7e8e777afa1e8e612b45028205235265970000000000000000000000000000000017a869d75144ece603c04d39cb56a487895cc882fec613f40f6a66601bdbbbb7748ec755553257d654d1558b1104a981f49a3f82d25c6e0d69207e6dff010d56f0d99b28fd986c5711878dcb6665b1f50000000000000000000000000000000011602a23c9b5cc091a700114e5d3557bd4857c4fc44cb8628ef327ddeeb728927347438f123e2011f9cfda9b6dfc42e4000000000000000000000000000000000c4fad264ca95827e9cbb9783e36cb0b683fcc33038d47bc7ab6b65998770325588e5b910e811cf7d61fce13c3378d6700000000000000000000000000000000009b4711aa67e84434cabc289a78fae48ea86641a162d48b79bbcbfd56237705dd2d1e9ba3a18d737eec29eb8e940e58000000000000000000000000000000001160fc9e2a488ad9385140bb62ab48ee613c2284208cf2f92912e1b973ff81a5d3de338d9aa6881cbe437907890258fc8390fa1b452f887ef3afc7129ad8ceb9a8397f7625c2b249d7442566814ae0a9,000000000000000000000000000000000cd0d8c746ecc8d92fcf2232793282d7e0e17e0ec27ee851487eb7788f590db3487296061075f36c24f67cd4c4bbf36f0000000000000000000000000000000010c5e1d05070c27f19c228813051c6a830254542eb71469664c842695b219670dba8ddff858e2d147019847866f01084000000000000000000000000000000001799ca7d8f2637da761622b793a3ed3317d50b902a1cabefdfc776b0d0ef88b707b8a5c36786d5ede3d8a381de4e069d00000000000000000000000000000000129881a3b56e0014bf1dac4775f509f309c33406f2cf22df9a0ccd15c87ea48a868d4437303923127bf580b8d6ed0a8f,293920, -00000000000000000000000000000000000d087c1b98f8c67c1bbc4389f21d9dab02faf46ee4223c609e7b9eb399132ae168bc12847c580f58edbb9255dca3b000000000000000000000000000000000065ded24bda39d2b830639fa511bce8dc770eb95e349d6874ce63b3355d23c1da3ee9771ad44e57c6c661b7453076fe7000000000000000000000000000000000fa3b2ef40a7c3d41f0c3a5f86afec252c6ce89bd1bf1f2192026e22fa256365360589c788753033658b1ba151797feb00000000000000000000000000000000105040ff4dc2bc435c2a82e1174e2ee0b94043d69074f01e8ed013da8c431f33c94a438a93b06774411780cdb72abbc8414ca9894bc15e6bca798544138689b2471f8171a5dc48eccfa36c83af142b7d00000000000000000000000000000000129c8c1db08ccd0dadd59b04df67a91fb6547d97ce23e59aa57cd3d38458e6baaa67285800809856e7e264d812e584390000000000000000000000000000000004a0be934248b4e142fc51745233b6d0ab2c46f53a8f9d4c84981e5eacff146ee6227de289c713e4ce24a4341572c9d70000000000000000000000000000000005916d14a8592af57a40418b10376e8e20f70929d2ba568c1fb70e343a1dfcf3e63c791cb639bec49c50aebd2f816fdf0000000000000000000000000000000018682c66a461a69b11d7c32f7aca07749e05a23fc46547bac121752aef64e9bb98a274d15a14faa93af8f284790acb9b99eac8ce85a1bc70c725a2f04aea3749d75d22c0df7c0755a5e76ab4d82ef9420000000000000000000000000000000001552053742eb89ae3d0b95be919c84e53919c898ada92d3eaf05605a19ac910091fc08a65e9764f3108877c837d478c00000000000000000000000000000000118e5d22f6df0e6bc7447177ce06659f94315478385372046b649fa6d39fefeeb492e6623e0160bc47233f4d3143e326000000000000000000000000000000000dd02c30cfdea5abd3550a9f28b546d82d5b3043f012de622d892062945847748ba820555fb811fb3382791ec43ce1f700000000000000000000000000000000050373898b396d9a641e2f2ed832c7619515fd9070852b891b4ce0b5bb5ea8b5e24248297d53e9db7cb946e76c4433fa49b25140d7967b0438e49f59a6b04b75bc8745b84d7350605be548c6b4b3aeee0000000000000000000000000000000006b465f4b9d60a3a14e119c54a7c35172bd648c86a7cf331e80ba849fc87b9dcd48410e3c9a07b634e83fc7dd71e5b9f000000000000000000000000000000000283ad9c77f549042f79c47b8a69e72164f0ee77aee50c20519d2b89029c63ea86dde2744cd21eb5d37e896c3abbdf56000000000000000000000000000000001668b08a87787928afe92d941240e503da07b646a34cf82ed09d4c2f4d479aa24358c8475eebd9bcfaa6bae17c430cfd00000000000000000000000000000000150e5b28bd901f7a2a9af44bfd6b78cc84900dc05e334de306f9a45f1e67708adddf4dcede8150a39670054f97a643436e30a51d55a1ac94089d0f3217c3a2182da6b02ce70ce7dd8e2d4e938bfefa9d00000000000000000000000000000000060d75764a92e30e80e7c1a6df1482585f4de901bbc36dd9d8978a76c12c739f85a9ba16741d0b19ed480fe2dc331e5b000000000000000000000000000000000024fd15c9e5b8872d2e9dae9ae96102bfb0e31d15e92a24316818862dd8ca7a6fef271d499fed5e0db6dfebc4c72e0200000000000000000000000000000000058cda551e1fcd701c6a3880b276a2f7536a26aa366a6425a1c42cf31eec678551f489a27f23ed5dbc76f19b0fbfae43000000000000000000000000000000001152e2cfdb584295563af8120c523a9f4c01cf72da64fcbe0a90a284d693a3089f299bc760166be062cf9f8efb6a951ad3da3db6492ff36102747d9d663bc6e9cf8f75b1cf77044989c7af3f11d66ae700000000000000000000000000000000116fc24e980b2e7ad6bf17bcd7c4f06e654bbf766ea0238a66d738bf3c2d41c8c63bd52f81553cca5fea91f5f9b74a2c0000000000000000000000000000000001078f19ecf785a5e0d3e764b7d6ea47b2d077b5eb222f4e6a9451f134ff0d77a0b9a3b53caf599705d131e3b17b6ca9000000000000000000000000000000000e44c07f00a1f198583a8ffca43da45d8e54e1f2a85bee7afff6c1c733b5d0b5712961c4b6d344869a8e4de3b34218e000000000000000000000000000000000083c78b3568cdf808b75d9ee2b03b98cd516bb16ca8cc35757f53f12119747bf6b5b0605bdffb2f079cbc69e99ee0bad6de8753f3df8be42b6d6ab578096426f852de4ff545d2e4ac12c3943b044b43800000000000000000000000000000000087ded6945bd6fae7a0aebb1ea68d3cd34588035531a6cb00fcf1b83e06f7ec21cd3486580165c1364027b43e238e34d00000000000000000000000000000000005a2fe8a9871273bb60cc7ebef44a361300a1033f3f0230a731f5723fca124ec9d305cfde45802482a45942154398cd00000000000000000000000000000000121eb94a41f9e133adf082ef651272c178d780a1c31ba8797f60a208ad36b4c703c9b6c08be845f8844dd14d6406734d000000000000000000000000000000000e5e3da7c91ab4cca1c9286020aab9795e64e667d55a5a700241f9589aa3519639f168d040a0027ac057f334a9f740aba28f7ef4b12c5097a15fa6394a4dcc3ceed6cf3c6240ec2ac949bc21a9f6447f00000000000000000000000000000000041f9117b426938acb40c905bbcba443c043bb55cf9b876edfa2ca051b6354124f0fa54d6a88ea172c3f5c10c6d921b3000000000000000000000000000000001828dc0b9533274db6afc802b2fadaacf57f28126094b6b9038ed5f6bbae0112c873fe5eed15bc49b970461abc2f5c3200000000000000000000000000000000107df6da02f106ae47718959aeba7b4fb4a8f0e2651560e2f2266a62566e13a5af86430b8800543f5eb6b1e96be79c69000000000000000000000000000000001628fd4a598813133de75cd7c96ff3711b6bc826806b96d07e5a89cd549592f0f51c84aa9ee0642cffae5630ca1ebae1a3d0eff3368b10d00566f35391bf43c9d204a4444b7eb91017f1b2d8a762d90c000000000000000000000000000000000e8fff44163cd9c2a4e148eef3cbbee19ab8f648da1a8d438be27d2b0bcab393fb7d49e096d9a7abed3d8f82c11c4e03000000000000000000000000000000001274335d8bde3d14924f8d7ba18fea82bbc85427892f18fb741c8ecc5f2d6d7bee74c68058164c55db3cb8da8597bfe40000000000000000000000000000000010c7fc728c094e47569f0e75446c399d20a1239b511e34d8d6193dd32df607dfaa4377a1825b3892a9f74ff4efa0d9df00000000000000000000000000000000067d904122a6581b5d5a60acfe8156dcb6c10ed083840e506487b5dd9117927663e0ad883fb91b4914778ae082de0a7eb90d76e660389e570bef756e9785e39b9748aecd7a34556bac8399aa5564d12d000000000000000000000000000000000a909706e3ce45c86f2c30de5e820c8c9eefef207e530fd504511827f5e6422714d3f4224afa6bbba22ffca533d647390000000000000000000000000000000013ff61472ddc0d70207692648087c283763ede668ae380b0b9d6ae6593498b0adc9d4e4fcc73b5cce250e7563f7577de000000000000000000000000000000000a81db69eca785373c4dcbafd8635b23a9f41265e91152f309fb2945622937e65b5c17656abf8aff042a1fd1e5e50341000000000000000000000000000000000c66269c3ccd9e91766d1a640789bde6de752d08ffe3b2955df8dad3d2a0b6cea9013af235cbfbccee8271a7242e310614f18dae096e4de75de3da284a5755efe51e912e180020a20adf1f5de43cb51800000000000000000000000000000000181f3f4a16696980bd0eb9bd10ff1084ffe90bcb65f12f505b25f0a26dc1d4e16987d486b2c0b117fd6f2e356b83a5250000000000000000000000000000000010d7be6788da3ec56c87acee68ea8a03e7d467f816060207bb163dfcf8a4e7721651bf2bb23d5bc390d50fb1ee6625a900000000000000000000000000000000196c1ac817493f51d9ca891b55fa65ad5192df83cdb63eb1a634ad54e2d627f7feaa68780418f5354e6cc09cdf2f6c5800000000000000000000000000000000190f36690b8d36f2e295b9625f23afef9d9babe87c1ba0303f60c6d44ec952ba6bf8356469cff9d952f8e26bdb86ca06e32d4645ce0172000fd74f30937261de89753caa716dd03a8b3269747f2349a1000000000000000000000000000000000f77df606f0611856c449c58393f4ee7a6225a5bee667382a48f59dfc747736a895d598f90ab26002dd0ed3a5a8f5a200000000000000000000000000000000012aa50d0ec440884fc6c2f7a0e8db8a5e79160f0c482209ae1a1aca2b9dfedfec6d6ea09252a373ea57905130220a4820000000000000000000000000000000004773f46165cdb19cae49cc42663316df39586c62be5b827535f138e1fca8dcf62ba42ab60ac6dcec85e8496f32b9eda0000000000000000000000000000000010c91923c2c7b3eb2cd9aaf0455c0eb035e38e5352d218b07ea23f50040ea58fd548b373c1bee9113d3d44fcb25f6ba08c8722e3e929ba21f1ed6c51fe5ad4940fb13d63e0293893135d0da5e6e0389300000000000000000000000000000000044b95fd5f0e049abfdc2adc699646afa5b0f64464779efacce85a5279477697090615933069992bf30036c6ac70dfe50000000000000000000000000000000002778e7dacc5566354c24ea1144613a5ce8a38eb56d53d230ca145ce83d5ed88596afe243df22cba10f423e64a7c103a0000000000000000000000000000000017e87cd2752d8674c373c557ab2b922e02620a070aacf6f5b3d3d07ca35d89ed2666da7246b800717c0e4763dc35f5f6000000000000000000000000000000000a3ed312e5f309eafaed486629d953970cb73f839bf30f506c2f393df4c283f299d6c643ae6c229430d919e8aeae8bd839bef6ccc893f6eed62e68f5f2a07812f2d3066b89653431e7e39e8596bc3652000000000000000000000000000000001082a0edac6267151c8ef11fac7614b74cf58b39b72fb71e4d66467ed4fb3264b177c691e569230f2a13a64b4a48c6fc000000000000000000000000000000000073a8d5f96ee580741bee1f82cacb6139d962fec34c44c648c8fcd0322796429bbaef083a11b4c8fa376d4c00cd79c00000000000000000000000000000000008d41e51dc2822e0f14b992511de799fe4db3783a05ddc1026a53faa89af000075ba5aa830ceb7551e51f0fff144c1360000000000000000000000000000000006bc4bf0bdf350af417160d06e8aebf2dde02c9b50be39b0c4dcb3a045f9e04f1f041f6de10328e287df6121247dd4e9c395ba8f2553e3eced8a42b221a710a5cd2a5ffe5834d3084dc260ae0f51698e000000000000000000000000000000000802e7b71127a15a279a629e89f194b51d19c4f329efd8ecf9fe69d340dd06068c8467da6ab39be25c194077d3ce2428000000000000000000000000000000000250172c787afe866b428748be8359d8e0bad161832abc108c850362c5839237483fb38678d77c94696260508907726a000000000000000000000000000000000d46223c1666f314f9a1e32a94f83d8150755d71252e19af91a3b460ab0ade2db2364d8c6217cb422095f0d9a1ed648a0000000000000000000000000000000002fc2849014717d1c07935efe601325e1842ed333897222f6de322dac8b50bf4d9859eed8880a34676af0d0e3277639053ef5568a766b6c39854ba059f3130b75d7fd870bfac2b00b626e2d71c4968e10000000000000000000000000000000004151d78d65b0c9eb26822e20d90ace8fac209a1f08f62ce722ae3effd7fcc476f4c0179e71b09fc181db96fb2ea4eec0000000000000000000000000000000013d17ef429483be98411947ca0771ce671fc38e27bd0aa4abcfd5ddf1af9e138404d86f4c2ed74702f80a573638d92f500000000000000000000000000000000178f2a7eb43b9f88acfa892b5868d7f7c5787a399c1c566de39ecedbfe88357fd5256ec57e1ba12e9784382c14331756000000000000000000000000000000000253a391373974beef746c4397654a30a68992fe9163f9518ff0ed9b7be37b858ac60c95259ab894bb6acfd123333b7fbadefc3880ca8dcff10b8b763f7d15f88965c2261b72ba879e3540a90c59effa,000000000000000000000000000000000710bfc39e92b0b9d15ee9bdb4959daa3a78f66aeae29eaeb50a0aa0460f3ff703c86eec8903011b4b61a0dea725ab08000000000000000000000000000000000856fe7a074d37786237cc14ff1bc53c735ee8133b231dd3fc63dfa0dbd1979304bcc7b55cd1bb66fd7529e15d15db5800000000000000000000000000000000014757f1fbfd4fa7935ebfe65e150519d6eb4f4831890df4b236dda98804b79862fb6699b587c3e568fd6de1e582409900000000000000000000000000000000000f7b54e4961dab9e94b1c4b897177dfa74be9937694a38207ddc9d6290dae1d5e122cfe4c8c31d853db3783999a7f0,293920, -0000000000000000000000000000000003bfd2535c6d8ffb44670bd02b5aa6f050f5cfae7266fc3225865bc3f34320820eaeaa952f80da51671f6d97b3df9d4f00000000000000000000000000000000026c1adc0ffc3fef9ccf018ff9a647ef5c69c5133fb4a6566cdcbd3180d9ee784f34d667edb1dd54ae292253b45576b4000000000000000000000000000000000ee90fb541becf96b4728f1859aee5ae74e30ba9193b90569b66b0e1d087eb81f30c21774298cb06e7dbee8f8aafb1930000000000000000000000000000000000a4361867bca952446f64c273735764e141eef43d156d6cbb6e68dbf3bc940e935f7bf3a77b57fca4bbc74bda2f26532c1a5abbddc02f453519563d6b6e05959d8de5feb493f7c58ea5e548cfec2df60000000000000000000000000000000004bdef85b0da28e0531734016e5953256c75c3620937736cf65de5f05b8beff294677668047a3b74f0f135b846a95bd6000000000000000000000000000000000b754df2aef855b4a0eb6f6aa03115ee8f38a31fc852381deef2b59bf23e2c885ae166030ccadd5673bacc35482f81e9000000000000000000000000000000000f1d760ac6dfb65b39c999211d4e4c3623c3fb8ea59cdcf926249a07285a8e4da1890327fed20ff07f12359f6d9035980000000000000000000000000000000009f2698239c8b452748126ffd83abec768edffb83dfa3dc7943fd499c8980e2d9aad76dc38b336a4a63eccf5c4150ce0b406eb0c097237556228f3a48b7e770c1707fd583b91b4b6b70f398b7dbb0d3c000000000000000000000000000000000cd724c51fd56528dfa688df46f71bbfc9144ff98958b559fca8fd05eda01c38c28630ee19579012b9913a393264cd90000000000000000000000000000000000aa1e55f2b6d9385ec6a9cbafcdbad157f7ebc06b2e30e2380ac54e71db5259cb919e17042d6ba6e045f1358aef276ea0000000000000000000000000000000010181ce9ffe235b6b271d570b3c2d6e1be60c53b4a98ef5e8d7d00b463e5bbc9d8d96dda881e58746090983d6f8edd35000000000000000000000000000000000333deb8b14f499319ad675f482fecd80f9a69ba369425decd441cd2ff5c3c77f11075f61bb1d90d0be850ff657d6b7cccc30cf1db4c6be6dbc5830ee37b5782c6dad215334163a9d9e4deb962186f80000000000000000000000000000000001581a5440fe892ee6eece5fc2227fe072dfbc440e0620a1e5fb505ff0b16d9e6033d83c83576b4b6ff87a807dc81b88400000000000000000000000000000000099b070a0d7497f33c1c478ac424d5564fa645d836a3d572d98782f08713d8e425b571433fee928475688db2b3a9a04c0000000000000000000000000000000011e1cbaa09a6361aff9e199e21bc52e98dfacc49ed83e732d4b4f2503b3bfdf85d029dead4412b6f3d7ea447e20d669b0000000000000000000000000000000005503e151d620e9a5a142e4f7940ed88375e7efc1109214141c191e9f38a32a40d3a92d6094584e763e0cf13cbb54bcc99461c0f12019b344a7f322900b64fe81e0d8a052c0ff5e977f58753b1b6edc60000000000000000000000000000000007c780f119bbccfd658f3f1b69ce9c56b1f5269bded713b6827d97d32b2a6deadcc02c410138d984d977527f3609cc2c00000000000000000000000000000000095aebacfa33928a916ca7b0ceac699c71620781b35cb2f3b254bdbd1544b728a2ec1fb35416ed7a8a3a630bc07ff8720000000000000000000000000000000012194abf7e411f4961b6f8a1e2ad052c27624ded863d7a9132d9c7ecd3b4074ef0060cd86adb73056323f4227ba5fa9e0000000000000000000000000000000002fde2be9ac1e8265f258a09eec85a70112ef1eadc3a91429c9206555933e2b89aaf7493fb833e33e5d61be28a12a1c2338ef9fa825e47b46483ed8fd2df64bc7b56da8aecbae704b7eff2e7d426f27d000000000000000000000000000000001586c65405e810e1d5b59304bb4555ca43c04a593671ec64d5ed2d2e626b1f8a89f48a4b21d38fb49909b8c614209a460000000000000000000000000000000014528cdf994e774b8fd54090cb45b68098c1ad9a351bc1f36a9393f3b4364f5beaf58fff6e5f8b21a85b67bc427c0e920000000000000000000000000000000000b48d8713aee51d80c79109fb8b4e0c6e32e25a7ca24dd3e7700f8f3195730375208b241b2c722af3c2295a1704cbb3000000000000000000000000000000001913cf6328429cf2966a48117dc74db0d45be7800f93cfbebf597fb48a8bdcae4fae2df7835f9536481f67261755da2a1dd6656a34f3b12e5568b9c348fbf4ecf50d65a89e63ec0936591f01e6cc7a4a0000000000000000000000000000000017e45a481449f167fd579accc896ac65aff6f1f7392df47d006b404de3cb7ebf6cb59d0913438f3a51e55a0ae3d446c9000000000000000000000000000000000cf4b7db343bea29af6e244a71880538b41b826bfd1d06a21512d00ce58f5d7500ab1ed77b446b1e3782df736bf3dbb6000000000000000000000000000000000525d08e134779ca7614784818876514e14b65e799b7832f61a63601fc491c8b9cb25430547f961cc1c22100170a2065000000000000000000000000000000000450cc2156c4716d0343f32aca82fd2d0712389b1aa984b31d51edc2aa0545c88ff52e470b15eb6b2c22e30f79864dc85202f32528e795e0fbe6deb4ef6e45efc70019520b01fa1d71d5505e42faa69a0000000000000000000000000000000004147c105ee8b4db68482b9d7f6a716ea1474b6c62efc41b9444ed1ef9e92e2b7010a1c1ecc59038ac37b385074a6bce0000000000000000000000000000000018a600a85c5c38be835d2e91a35cce4b59e5f5ac3b735fc007bf5498062beca9befc9c8ead58f9f21f6e08266b149d800000000000000000000000000000000012a476fcb81ab66e3101de2364cb609b17e06eabdff5246bf736eb9d5c87fddd404e8867578262f07a05731b04069164000000000000000000000000000000000c54a888678c28766ad17a18507e4bf5dc57dd394eb6e9b69abaf15e645cf4779bf6ccf4314d2756584647cf27af089ba2b39f2b893be03ab4da77ed518ef35b2e24278d707a20b67ab4d1e5972f9722000000000000000000000000000000000e809152c44cebdd8b40f0d22d57c3b31f29700e0cbc3e69f660bf7270e59093d84bf7ac358be7e45e799a75cf9c13df000000000000000000000000000000000c6c61f98bd4e3b7095fc7f1196baa98139087df00fae2a795e76544ca47e453f75929cab07c11cd3595de6ecbbbaff000000000000000000000000000000000171c70446c19fec3c152741925c8db28ab0d140720cb6a6c45e9bc66c012a421d12271889ea43fe1524944ff572fe6850000000000000000000000000000000006e4baa09b4660c69cace151e60320b771e56e7460b01442bfcf26823c17779034ac241b9365dbbfade770d2056eeecd892eb7c361f05e114a645caffce9437b7b43fa01dd66c1e75b30f3abd0209bcf000000000000000000000000000000001917a23350e94963e3a7488ac1dafefe9ab11856d405eff39d655e31ba808f02954b63e822613d3c6e5f358be04be4a4000000000000000000000000000000001620211b06288c16aa02f4404192e9f57a048e900f0ec5db9b478475f13b142f924c6de720031b3fc12cf869b422af470000000000000000000000000000000011e8ded9ad57e46713e7ac0044ee4edec12689cdfb98838a74adf1a35244e3d9a4a34c81323b089c10422abf26b044e70000000000000000000000000000000006f85c7478cec590fe3355a8d6e9557c5be084c161e090c72f1281be4ee56f36aa1e3c9c844eb45d9e295c15c4cd903efdafc3f57d6116163f1da9e70ea645243c5911cc4ad4a969a57c46c6b5c73acf000000000000000000000000000000000d555d9f23de97318dafb257cf444952bdd3e844e9ed5ce193c10b76f5179f0c6851f93af1553b128f34d3a7e75339f3000000000000000000000000000000000132704571a12a58f629dab48f1a3956392b40f801c2b3757c15f7be46ef1d9115d89920c460c0e2bb062b3cc1aaed7400000000000000000000000000000000152829eaef900fd2f19d6fdbb8f7eb3b02df35d218b494d075219b69016256e572eb7f555f6fbdbe17c59a666d190055000000000000000000000000000000000fe5c67c949b7c89a867301528f0ab24b04d31d6f18f575c475ab5a6098f7187eef20a9ed6e810684da9afd8de96ded6660a77b2be50eb72fd108644d913b9253209972fdec2d107213ba47357c96e9e00000000000000000000000000000000128bf3cbb5208d84dff719ced229921a889c9a4d02f5a508187662f03852531fb8be1f4c2aa9ef01de7720c352dbd19d00000000000000000000000000000000158d89a44b8fcf9ca8c96a8e516e130ae8af19ed71c2b8487ae300c3cdb546e248728bc58fd9cfef21107e0dabf44fc20000000000000000000000000000000012b70b42c8af4551267a94a795fe18e8d054291225438adaa33fe2edafa87742fc3709abcc7bada5d26e3a14649cb47f0000000000000000000000000000000015a853160b7666ea7d64aacd931314497ac7068a4b8bfe3a7deed85df2bb8dba277716a9d1ee50c56b2970016ada509d1ca575cca348dee9adfe68f8a78d39bb998205da2a5285c12141a77ee7af840900000000000000000000000000000000087c7bf08e085e19f0cb301d2e36478357e835620b1cde6e132c237ff6fc63e6fc16a8753550d50fb93a0a1741302cf9000000000000000000000000000000000615299ccefe4da879e5f4b01d6b6ef8358bb59ed8a2b365ec72003c16486d3266243db81f48855d81b6a25440bb861a0000000000000000000000000000000001498fd20640f39dbc03a474f4514e5e283256ac19468077af1c9ddaa40759dcf93afe256de1e49be6469fa106394193000000000000000000000000000000000cba50fc4919a29be2f4e74c261487dbf855db1856e8d5d008cc3f4ee5eb3babfdfaff878adae49b96db99d424bc4dab2e1e4537f855eb478274992cba4e3f50fd9e944f6246cd52dd1517b55bd7f71f000000000000000000000000000000001369dd82ed013474581ca1ab2d2133341d7c1d52065060d72b8317e899e79e9077bcefe6c76c3c7f67e54f76dd3c246c000000000000000000000000000000000405aa84d3ceb02bf8eae989a9cd65afa15451443af6f3cf5e70f5cd7bb8d413c57ac3893a7e8b888ae93a92dcfa2b20000000000000000000000000000000000378d003988f3c6c16d3b12ef47a4a49e2d3d2c7c67e384bcd510939581770aed92e06291ed3b7c742769f0d1ef740c100000000000000000000000000000000048bfa6550711a17d52f48377821baae6f3de6ad99ccfeb8302466047dfddee8005240cdc65b3ab11ed85b11f128624957f9a729aa01c8bf0271052202a077913a9e0c87201a367845f9b271c130e95d0000000000000000000000000000000013370ab697da0ff0a0efa8ebc7589b465374c983c13daee7b5451e8b299933eb5a4d255ffe4aa46782ae0916fd3990230000000000000000000000000000000002ee77be6e0b6fd260ad660a96100bf3259329faf2ff9796102928e70cd52c2bda8d0d1da1d484d7b023d3d59725d12b0000000000000000000000000000000014482fee88e02e61b847c08e61d7ae6fca2d993bbb69bf1653138150d5d7fed09cd5cd4097cb4b6368ea8023383477cf0000000000000000000000000000000009d0380d0d6fa39c9e242b9a67336d86445551658bc29fbd594239a76d7741ba388450caa244fb186afc36d35c8740e93017593cf311989ed8fedff72bb1f14f72cfe5bb7446ace5274d8ded54c1372f000000000000000000000000000000001537d4a47247af8f60f77d309666056c412ce089f3f011457e894f74fa4ad5168baafd36ed3294f5f61cc9cd8f87554500000000000000000000000000000000119e43382a846c8945e58dc7723a0f24b24d9cd487d436a156156a6da97795cf3f4ce382d21435695949b5137a2bf1d3000000000000000000000000000000000be5fd015998bd6043f124048c82e4d848e1b8c87442d0021390cba41c294de17648a47dacc06268606ba73cc95ae6e70000000000000000000000000000000000e05a3dbbf3da8320c40d51ac44c6380d56ecb460b0e7094819aa6af4d7c70d1541d4bc1fc5afd453b165f3d48d09a708bbe9e7a307e380c238ec1f8e2010a95fff8b03923ecd9b012f99e56c77a5cd,000000000000000000000000000000000b00b5c14685ddd17ee99c74598e6bfae5bb1c103f8ebfaec3a620ba57312f3093f9ad5eac820d81096dfece90e72ef8000000000000000000000000000000000dd81552160d449cd787ac27c76685ea0dc993a9fcf8ab182f1ff5d8a484a47c14c1c1a785285b44336c7f6fc0732a0c0000000000000000000000000000000003008b6d97a12868554d294faa26e2ebe2920add650f841adfbf0ee89af72fc4da5dc23b45b7ff191a58c17971b50ae50000000000000000000000000000000013f438d927f35b04bee8fc55693d5c97229c8548ff9de39fae6e26c26f89623d3b0c810b9be8dcf0445910e8eac5c58b,293920, -000000000000000000000000000000000e1f825b71cd9edcb231c178e160e37bea70108b369afb248edc7c6a59114c22e843fb5541e0f26c77a2b589ea88fb3d000000000000000000000000000000000d65d777e91920b17400955a4afecf82f67cd13f3e7c5d9c2076c4a4d8f7f26383d22d9977dfd0987f219a625c8a621200000000000000000000000000000000045716092850318c343f0dc5337df1a72f8c74dd729831d12103b46127c9180fb50cece34986a94fee6119e72d16a55e00000000000000000000000000000000083fac698ce800786719d1f6063c87d9f728da03cea2545b4ad8831f6c24bfff73e80f2c2fff1532f6d1fea60e7d438ccc5e9d01f6ea67dc3f943d57d9d8b5791d823592f7fae6719804c1ca097e651d00000000000000000000000000000000171d60b76698d4d3f14b4eacbdce9fa66b8c3cc7ecfb989439330fbf0d051d95f3007c389113346e614f5ec8cd170a2100000000000000000000000000000000151a96beb250bdeca3cdad1b07322040bf1cf2105dfa854bb24fa76c8abc25ef4fb924ff995da641244f9daccff2ff970000000000000000000000000000000007e5818778a8331cdcd1432b46abf1efcdf7e4aa8907fd42d5e7d14b57dbfc48125246b57587755ee1571be8b52d2c57000000000000000000000000000000000693eb562e22fa8ca4a655b76e43b50fe487ca1d65cc3867eaf793e50496f0b4658bd92199104c2ab92e4ac53c44db6f57b8fcb85e4dbc1969805d814e75b2b80f5cd1e5562bfc1e28becf731aadfc58000000000000000000000000000000001059d23ec6e472937d80829256db506d2d2deb37d4b750a980568cd5b0db085358a4d610d59009b64db1a9225f9f6f5300000000000000000000000000000000053d9ffc47672f1058856aa08e51aebd469111dcd129ed542454d6401e7893323f8a9c63641f499cd8617c7389518f8e0000000000000000000000000000000002b9b30a5e37b18af4bb02ae8cdd56f6a87820716ea1522a174a0d99c3716295ad0ff2daf663697cb56bc6053c9dba610000000000000000000000000000000019d3230c0bdc228fa0cfd5e0d8bb88be959e70e59d931d9f9e3683d5e65d8ba0d121fcea329b23c5905b80dac34de33b03edc53ced9ec5d7f302216fd30a81c3554a3fd04994f62b5e3da74c8b71bb870000000000000000000000000000000015a619addc75f425596f9a51c6cf2259087cf32afe9b1f07e346a2f4e1f8caa001dc10098d1287b89837f426d073982d000000000000000000000000000000001660598fcd3ab6a55423138ee72a4ca7b57277f6ce140f9f992dd9934bcda78513516df0d309a0e8ea151b2742dceddd0000000000000000000000000000000004cce7d84e0763fbbb54376833ddd7408afe3f741bc2b7e42fef3789a005134cc5540981a15a9f256e0e541ac58ff3b10000000000000000000000000000000019c20a0064f89d37548e06d63d8ff4fbf3584d5bcc2fc2757339b7c89db6d5da76d43b31da7364259187ed602e79bf4f976568ab779e335b8dc67a64f15b947e69cd3896ff393f51fbd3c3e3a157543f000000000000000000000000000000000d7ec5a27ac44daeebab7658011624c441e45924cce97d5bda354f1daf9362f5bce2ddf57151fa07f78740a7db170e8300000000000000000000000000000000121ee325f4252ae5cdd3e3495f36492d68d9dbe13249039d1185760e6e48a789744b2a9946a3d6478a64b378f76b0de300000000000000000000000000000000014c6c5b98c1e214f78b82f1b3be4c32c5013934b1231fec942b5591d3f0440bf63b1505cfbb7a8fa78a85ba58fd4aa90000000000000000000000000000000016aaea3bd0ae91b9d18ff89a40ae27b68d74f3a227383138ed737d59c19ac578da03df83f04c8d962cb9d6f84a15302f3aa5eeded490a17b1cfa66d409811741643b7beacf312b9d6c8e7e7e63579c8300000000000000000000000000000000188e5aed425a768f89f5ce09b2cc909b28c6a0165787c8e3750fca8e8162128ecf62ef0ff853d206d23bc076335008e70000000000000000000000000000000001cfd330da0d1b5b92b6533cf5a8b6b70bd93daec4373f28d669f5e970a947fd813ab1d1272b61afbd2748922b87c8c300000000000000000000000000000000002aec750fd085c99c3b9c3af62b6deddd85e49eba0293e6e8160b26a3945af546a760b8f8f85120d6a51d22313cd33800000000000000000000000000000000162a109abce2edef753ca6351aaa9cecdeac20919681c672dbb183b5b26649e885ff081b9d3687f802dbe20fda43462af9f1f9313bf966ea3b8f09bbe9dcb66a7f9e3e94e2024c49a72ccbbe03fe4662000000000000000000000000000000000f7ad6a1dd9f8cf52bef02ae1e82b0d20dcacfaa5c169a485bf8becec8b51373fae851ca29e64385f0b7024eb0bcf9270000000000000000000000000000000010412a7a710f842fe836414e2729d0ff2e145709d8f7b5e3964af3e0ae267ac53dac3db1e6d2b7f7671ec34b18c844a10000000000000000000000000000000002d3b96fab0e3b8fe44e316fcc5e35f06dab83f2c531a777e162f7521cdd5767ad0b6f877f876f73d2ff663d9b71f462000000000000000000000000000000000c09a98bf623e82a4d2d4b63fb867fab5d3bb1f85a0669c4c11cebaeb357c0717a0f246a9ce4064b7351dcf1e77cdbd393be64fc3763d06111961bb208a2b858aa1ff181781dda630ca41f0d45ef2a9000000000000000000000000000000000114270d35ebff55c0341776086d893513595aca3b200ab98c8b586029b19a360a04f2e77e90d382174296443ab8531d10000000000000000000000000000000008b88849c3cda9a23d37ec9f4700904edb24be95fbbe6d9e20ced0d52208b597d44bb9269830a1ac5cda35d0c0a03c9e000000000000000000000000000000001144466b13427c10ad7679567067dc47c671107064fbb9bad287924c9bdee653c395dc2654caa5b3013ade932fddd5e50000000000000000000000000000000008e14e3cff3bb57f0d87680a0c09d745c7272bd3c216ff9fde7c03df2caffc27e0bfd9f99912855c156a787200752c125d2a2b6008a3b4a4cb3a8c28864214c7fbe154fedab1f9ff8c96eab6a5f28fd30000000000000000000000000000000015cda76d42de9fa86f900a5180ff016155f31b9276c617ef664202848d2efd2876d412402516c0c3d26d49f71d894acf000000000000000000000000000000001307fa2b963fc19583b7e4ef2e9dddbe93e2505e8f4f00ec52db26ab411002136c1f646b1cda71e19480c767906a6d03000000000000000000000000000000000ba87b08173c841a2bfbe424584d4685c39bdd0f83f278f9fbafa8111102aa3acfad5aabbe032c7123631fb8b454255b0000000000000000000000000000000016c525c1dc247fdf34344168b7cc245579585fdbdd6fd783cbe60b727cd11ee97b87a86647f78dda207c98e65c2ee7e6854e742ef7c76ad438cbf30c30103741f57ebbcdca4d6c4f14e554dd1ed81b24000000000000000000000000000000000403887fd4429f44f8da7f17ca072f867e88ac046922ebe3e1e6c4f9d8e174399e7648aca924a557dbf7b29c540db33f000000000000000000000000000000000522324700fb6b2c43eb5b39e0da94cb60e234369543f530ea47f4aa510ec0fd79cdf4dd3ae046e21d78b9c0e35107900000000000000000000000000000000015e946b90984257ffe3814dcc3ef065fed1504f0790f3564c8bfad4e97cffdb61c0d73bb0b1dbe78c4266c773abd56b500000000000000000000000000000000078f604630074ebedbd836c463f3879cd5d4a2c947da0e47740ec369112f4fedd787ae59bea69aab61b91f05d92061036f4f00b2494a32844e01d0827ca78b06f5eb40b6769f36a04f20eea229c305f9000000000000000000000000000000000f722bfebd55f75f3bbd0a55492499c3a3f637ead0e54270042fcc88853df5bc5f11a3677efa26d31c28368e00c8713700000000000000000000000000000000182618bc8a4b3f6556d79848f90efd6883df90806a8358cb6852bde465a27a70644ac5d5040d4f64ec355763f1a384990000000000000000000000000000000015f717739a1cbb2eab30e7b1bd9b25f57ad56f36016b59128ea1f2089f2d1dd0128b455b1b0e9e3b320f68a38a1bdfac000000000000000000000000000000000b855788d6b6a7748aa923dea3163fe525a7b43f4619c1eff3f9219ec3d98ceaf34b97bfd19aa6f91f7fcff728728978191e47a0b0c72bd17319063abde7df51498cf0c980c46946bf80ae4c9864e2e200000000000000000000000000000000120048ace47bc1ab3fdc07713b91a9223fe0fffdcbeaabc8a61351d756f936e18177f672c5a4db7b9dc29bad16bb7c4c00000000000000000000000000000000101275492a6e843306f2927b6ab540d7a5ee925bdab40103b4ddd885e444e6a6ec2d6e99c061284a1967797d8a2e9e700000000000000000000000000000000002c12f17a5dd2c56aed0d308367f37510f83c94a4482e5f632161dd0517dc2d4f46a90bbc13034c63dbd04fe4c616e320000000000000000000000000000000000e4b9089155ce2178f26b058f4bfef57b73aafb83b0b78138a01890a167709f79100a1e4d797c5849473eb3486cffa4b7baf8816db56c0a602cfb4caa9089136ebde05722ad4838671e45ada5c716f20000000000000000000000000000000018180eee7e72b6a4bc2e60555236da335fe05fcbe2b3cca4937e73a550aeae6274122ba84ace78eff84d323b4196f58400000000000000000000000000000000147659347e0fac7a16c92950ea5fd115416072f339d7de3cc0f00ef369f5122ff050d8515effacc825c807f7e19650e10000000000000000000000000000000017bdbcae7f63052af9a7d8bd71dc98b6eca7ecf5eee7632959fe56ed51278099690c534ec33be4ace4612b0f516794aa000000000000000000000000000000000d6fa233be4d6d783bf973cca3740cbaf0f719827d7f9310f38d1dd9d1c1f125cdfca6d12fbf6a8e8104f79bf30b00647d9ac1699117bb9b8b90e2fb709eff4ea0f7882bdf6acc6885c9458703cbfb3500000000000000000000000000000000082f3beaafa575e86be53b4fe7b93835b00759f921933402282e5bb0e643a812e0e4b676ad51ff2c6f5332d777641cc3000000000000000000000000000000001760b87bc4d2c13122fd7acc6d629c9f9db9bc9a2c49634aaf33e258ceb3106bc2755b227c6660a1df1d92c60067cf5a0000000000000000000000000000000016a819d7109c9a12199eb98537a730908a693767cdb35a69b4c7329761939afea766f0b91ae405e273227330761a53dc0000000000000000000000000000000009d14d7138440349e83f5ded46d18b886ef3cd63e0e5bfa0a8b50985142b21a4733813ec347e40cabe28e6ec1e068c24a22b6c1a24eff71f0fc64b6aee8d3d2dd0827756f5c959f68f1216c2dea58503000000000000000000000000000000001676d7f489219b56c198f8494e156fc0672ae28dab20021b7a6018436c7c0f107efd2493ddc2a1cfb3ad490ef146348300000000000000000000000000000000001106e89fc098ce7bd8bead5d7f6432bc54501370ae6544f34cfd996b3b610f9cfc7ad366751ae1211b848aad7d93d30000000000000000000000000000000011f8f0bd037365b5427e76d57b018c1c644034b28d06c8f68c59bff45eb4a2c4d761d066d96c13f7e73dfd80c81704a0000000000000000000000000000000000fc826b5957613f35bfa36d3ce088dfbbd06c8f2e88056a22a9f35db561e06fa0378ccff29ba8b81cc12c7a504f8c704c0431e6877166686239f014008959332d9d009a801be6a1e68c2de52ee264bfc0000000000000000000000000000000014f0f64acb0d9638a68278099abf5b5da3aa087792bef15192cfe3689b69b7ec1aefbfa14e659358b5410d98d2eedac50000000000000000000000000000000015ca79c92e98cf8314a2f6319520e1eb7d4656ca6e51278710cefd9c768a25691fc58e983aaf858d3c8d0ed73e2beec300000000000000000000000000000000007a5192f1dc906693568291f163e9632c53e1f418a87cd25656064adffbf31863680468f3ea451fbd22ac990dc870b3000000000000000000000000000000000131d2e3f6956da8941e8340259b8a15aee9fc6f23573f9a348ee9a51bbca1308dc54e7b4675357e3a9c5971be3a5c16af833a784d22b99537236fb07ab7b59918783e35b89fc9692d7f76a03e024c94,00000000000000000000000000000000163da4bf7e159e05eec90318a8ddad4a59fb02d7ae2fe18795d38c3ccaf797188fa16577e6a421ccfb12ba1ed573c4e6000000000000000000000000000000001256654eef3352b09e0027277aec042519d99eb2567fce2cfa50a0c251c12d3593604739128171bfc13b3bfd1ce8f9e8000000000000000000000000000000000b8a46123bc863bed525f97166bcb77504eeeb66d2db207eb8342a3d18f7f5a99910fae3e6423c6e84e437a2c4b24363000000000000000000000000000000000b73cf08023c8572f48c132add67dda7a15def638a01b198361b9d21a4634ba76ceed9819b37c12e24f148d255483856,293920, -0000000000000000000000000000000003113ba8b216d933fe0c81f23f75942c0065d21d8f009d73b1f698281408874e33dd2750fd4298367b81827cf6fdad34000000000000000000000000000000000b8a921e840fc665a786d826f83ca5a9c8f00e7c802bce5473a7d1ebf63e8bb6cf5c4b426153508d874064d1f1dade09000000000000000000000000000000001492ab584088d23d3b0d1283904f9a8f29f9efe47950c6e9ffb9db2123f3f9820b906d672fc7f97f0bd38b8fc0ef44520000000000000000000000000000000010d321c2538f92aad4631af44ae39e63dc06becd2460f0cee0e526328d167fd6cfbcf4edfaafe32d13b5fe66c009533bb16c1bc60e1a9be9a82c93b7e0311e7516a57d687d8907599918df77b3b6caf3000000000000000000000000000000000ae75d01481a51294003041afc4802326ab878a3a75eafcda43cf873cc65e300d28aa986fb82a2d1d649e5be00f956820000000000000000000000000000000017640eeef8982250f88a4d187dcabfcc9adc3ee9194dbc3c04c741690fce5bc7cb07cd0b7c3497191d9ed8558fd0d24c0000000000000000000000000000000007527fd8dacb81b8d1abc746688db6a47211fa71556155d38361921c4bdb2a9e9921a3a540bcf55c6dd751b84c04a1040000000000000000000000000000000008de9109ba354d7426a5313d66cd747a54df347f0f86a3c0f99e9e4b68fc79641fcf98ab39fa23ef6f1a781c48f53f76cf301dfca76a83c70552c9cbc9c80cb20f0d82a70a9d887b04b150fa0764ce2e0000000000000000000000000000000017331b8367f07756e789f7edce4d22f6886656fed78ddacef6987a2751dd3d5d49011a050e7b2a3e11fc8d90266c9d710000000000000000000000000000000016959c303e11f23392f95c1402d1d1ad7f38343c711e96f18d03f832f76e3e81de789a6eaff797ae51079b13571334d40000000000000000000000000000000004266fd13db1ca80196a91263c79d1583b717fb61fd9ce5113e4cd94c59e605152b244e10e364b468c5a561c6fa9715800000000000000000000000000000000026f67cb263be83f3163856f091e9346651c29d4634e242da53b22eb6e66018d235b0f30f8833310dff9f3020e5bd3811cfb94c4e029a2126a9cf5561c677687f52059e4b7f8b7e7e73e5b1dd7f42129000000000000000000000000000000000114d8babd11c81ca2b8a7e193afbe0a8fce426b83996bae6f77201870e51c9355c319dd86b985272f73e0804c0f53700000000000000000000000000000000016f5ea7610891d0e72975816c08e6e25a75c7c42500655f26efdfb384241bbc825358a21caff347d00c8b2391501d15400000000000000000000000000000000199c8c74a79ee90c3606906bbb8cc163c214259e4d0127cee3283bcd9c1ebe4090ca7d7b180201910d3f6f51566d3bdc00000000000000000000000000000000032c785165ad4c1a2846e15318bd7cf5b42ce8b675cb18fcc4232e28701f225f1ea384b276e7a38b2c9e2e8b112f1911d8386fe6f4303959e58165b422e98c4813b1bad7808594473e4e66df09698cf0000000000000000000000000000000000842c65006caed9b53add048a2eea89e1b4584e1deb4365e3dcf8b9ecb02f337bccbe5d6929ef8c20461847f171fd4d600000000000000000000000000000000100dc23e6c1c6f6756419a9bad3133bba052f408a424c5239b8528ad4429a2bce64b72f1463625f7599ce43865581e9600000000000000000000000000000000125b4d71333274a16e52829ad5eaaecdda5c206063473dedec5a8ff4424def70e6f650926948dd2158b403f985a3421b0000000000000000000000000000000006a031e3c002702837e4ad28250b85cd94d42cf7b0d765b980fab95edded7636d13bdef1be63e66682c4e297d0cb2b0302e1c432f3b55ae87ab815647f196be3e138b2f6e9fe7acb9459650246187eb90000000000000000000000000000000003f7091a25da7d5afe6fa6b254604a1abe7a0c6ea11cc1a4167f5f648aa973d888383bc7e987b620d23e688868d318360000000000000000000000000000000016637f888efc3e057227cbefecb3037aebf8e330c3a794e51d691e3bc064237b98351beb746868aef977a83d1fe163ac00000000000000000000000000000000126d2884487984f851d1bd7d61bdb803321f263918e88e0677831563bacc9f5207358d1e9c76a5a25a66f0294f459e3900000000000000000000000000000000125c61b387a4462fa3bb2f06a4cfbd7df082d20cb23ee974aece2ec9a3b0c084d13a7ea83725a05d9f31b8033d2888ef9b0cc0ac499dffd627f5d19b87817dcd67e87561d5244a4b5698265f8c5b767e0000000000000000000000000000000006cf2bc7c691c4f8a64d0aa1ca3760d715b3188a2dd299ab09c723315acba8b0b4bbee819ba06cc564f0c875a63a415b000000000000000000000000000000000bded3d695e471f30f9d723f55826eda112eb0e3fbfb9a377cfa07d6233ed84108b92a79bb491a2971e9afdf83db8e9a0000000000000000000000000000000009b0e9928cb267508d4f9444c6ac3dc6f64f49a70c82c0bcaf4022e97854e5d9ec2612a2cd4d67642dc0451583bcb24d00000000000000000000000000000000009347dcfebe93a2f7674ad02ac48794e7cbffb04dd85b0c8c192fc85cfb9cef40fd11def6f63ae9a923960424eac6a02f3875f81fd39c9b3ec74eb269903dba4173d8eb0e41a196d3131252207ffa040000000000000000000000000000000013e8215c7bbdca445555c9fa0ae44e1905703334bade3294fc047ec262b9e4903880d52851967339eeadd666200b25ae0000000000000000000000000000000003b0bf4498103ac03601a8594b154b59a2a93d663f98ff8dbd2c85a1902e572a9456c629a12651aa87a1262102e1c770000000000000000000000000000000000e8bfd7d3fa0f773e6bcfd0d43a5c436862d1cb6a4ed715093c6782cd94699090c4bde597f65768e963fd0f8644e09b300000000000000000000000000000000064dab4d0d0c6b94c58b067337f2fac7d0d922cc822562b6bc941a794d96aac5ddb83d1d5844440d21d0a72a69303b8b2d8d4341822dba68c6fd58cfebd07b134c1d0c4e32ff63f7d59addff4df1ec3200000000000000000000000000000000098dd9a20f84fc26e78993a9de4d519aa2f8d343fbee501af945e5943e88425d29beb7ae54481b04175a07bf69b260a30000000000000000000000000000000007ef43e7a56e4e7d532420e152ce566d9055eadb4ef13d5698c49da905a4977fa8a7d3f51c8f5275582e1647984be61e0000000000000000000000000000000003755ee4432ea90f2197c7cc2e191dbbf7950c52a2c1b723f26d2aaf7a38c1b97efa29a312fed599f1199cf186400adc00000000000000000000000000000000150edc463f0a55fc70c2ffdd1f73a3abbdae459eb16adf79e96d18849ca638e6f41c6805b73755968be5cb110d81faa4efa3dab1d7cdf949bd938ca6ac371f953b3bbef1aec7ae76bda37db4c940b3d8000000000000000000000000000000000f7149602cbb3e5f2c5f8edfe59fc0fb8e1f03f89ea192bfe3990d87ccd28d4a80d7cd3003a8cfd669e1b6ff7e3cc5890000000000000000000000000000000006ffbc965bd06de07d8c0a9db8db5ab82d5f11afa1ad8eb92ed4453489f5899cc8c46ff02743956bed81229f64cf6efc00000000000000000000000000000000164cd3271ace4809eadeb1c0f769094272f3b66968690339bdb5da92e920cdc80c9d577ae4fa5b6426a5a6f46fba80bd00000000000000000000000000000000098f0a14a511ff424847d2b4d1b80a049b1f05ecd40af96b7a81def54486e4969011c122ca7dca3444029daeae2ecfc79848d3c53632dc461619c8c522013b83550ef3dc7fda197ba37c9cfe4340f5a50000000000000000000000000000000018409c0d0f37f4932cca87e24eb4d55e75dc98f938420ce036d43689fbdbbd839dc608b21d12a8af1d0a780aeac6617400000000000000000000000000000000109f2294669422a4946f926b1f106c2887893a042e3bf900559429c7fa484da4909216c8dcf826871534981021256741000000000000000000000000000000000a1ded19846e603b958d0bdcc9b554beca784b017d2a35ba117890fd0dbf729428bcd9823c7a378706220377c82a215c0000000000000000000000000000000000eafc89e30e4fc0544497e27674ec5b37ec0849fb382e608e09d0c1c94cb78bcb96ef4ea48e374aad1038881706fbcfcbfd192e917f2e0c4d6253c4e4755f30812149d1ce1ee4ae5540faf1dbfbc13a000000000000000000000000000000000e02cb3e099792ae7508321ce7afa323fd499de90c4006621ef5ce1054d0c934ae058a97ff8aeae0c88709c4d8ed0adf000000000000000000000000000000000e19318f5890320f17d5243adb4683a97e3e9763102c4fc93e3c3e3d24f4f61e0500be916c249dab00094b4ab048fe99000000000000000000000000000000000989faafcf6156472368b282313e076613cfe7ff135eb131b49e58932cbfafecf6585009d1f17ff8941d7f871be23e9e000000000000000000000000000000001167419d097ae8b96993b2e67da79b658adde1e12e43c71f27835845c7077f385612158d3e59fe2cb32b9418463e672679eaf11b3a30c7771ce63cec214416d798de20432670280d997b2f0631007d63000000000000000000000000000000001579b7d03d3d2c8a280e8ca113bcc98afa6a2705a5d228d92807a85cd5a1ee97510f632293a478c3fe0bd383f4b69cdd00000000000000000000000000000000107cc2e6bd02251bfd565b4b848adaa84babe9d4f083e827ceae6bacd9c9c221f0dbbef53278175bf27ebfe5949fcf8c00000000000000000000000000000000018d187c566690e4edd8d8abe5e0a448e352f622c96680378051228b6d081a4914aa51383326aedf45e351612ad6c5d000000000000000000000000000000000197427117a52f82aa6e931ecb0c5ffeec7f73ee8f44c5816935d26c06cc8285200ff9240d98cc244708e00669460f98b43077447b67f65e16a8aeb3564b2d13822e478dfb4a82a15a1c8fb7cc8170cc90000000000000000000000000000000019bd947df5a437a7f1ca2340bec628f2783cc1760dbc4a97ae10093aedd9f64e25ba79d9f4ce678f4fec91a3b1eef2d7000000000000000000000000000000000770e0c39988c9d8eca076464a3e10e274b06b1d2f6230e6dbd8dd59dd9c062f8958c6870c44ff196341bb9f65b8db38000000000000000000000000000000000a1833ef19e2b8e31577e5cd26e0a7fa46a5d25355d8b3dc0605f53714a60423556f3bcf17649745695f68f26570de0b000000000000000000000000000000000f449aed4120f3bef05506f2463f4546c7ea67b9e9110d3942dc256400d063dcc571305b1d4cd2bc3f18cf25319286e8eb64479b496c17d0587f6f26c62752881b6a9228643e8c43f21c441eeb643107000000000000000000000000000000000c1f9688ea64165f894e85b21761a9b2bfce891070103119ae71ff7acd164a57b0e054319631180c22f19eab8607f5b40000000000000000000000000000000005ba18dafcd3552af464acd469b133896e90c9ccd7e3bfc6e05db883f3c6aa1cc4610ec47f6354f6a7cff4385c56d2b3000000000000000000000000000000000fefbd9d78f48683b378d2d6311bf7ffaccaf7aa73a0bb4ce019a0c1d2e1673e52c724bf3a782729ec23d258043efff5000000000000000000000000000000000ea47ebbe3e858c5fcbf5b0cc9017d6ea23bda36e235d2aecbec827fdd2e4b042d1108d5f645b6dcdd786304e6bbf81b52b42f75aebdad1bf433917c025800c4f4e985cc077db3ba36f7484f95764e89000000000000000000000000000000000a313e1bf72d9a176bbad609631192c779e94c293463507edcd1c38bee8f33cfe6104d7169457ad5ffd9f045fce1cadf000000000000000000000000000000000af8db18938c51742b351fffddd74bf1137092ecb50a7e749391bacc9c1a19c7b9cf235b52ed577e7855d4ec1fadd940000000000000000000000000000000000febaa128de79274ef11d3e6378809d5b319796c653604723693c335eda175014b645604271429e3d449e756c85bcf6f0000000000000000000000000000000006adb29cc4ba053fea56d07225d2f7735651c0046f5cbe4a350dcc20431ed9457651d46a5d23d946959cadfc5500b7eae83106e9ea63791eb192e7a035bee27bd049b3a37f080076146eeeea6a769384,0000000000000000000000000000000019a5b588aff8853adcfa959afc5135807d00196a75acb3536ad4fc9a9df3803d919a2de7cbe9ff762913225577ebdbf6000000000000000000000000000000000ac8bde939ba2f164795804d96dfa8d3a1c4d9e4eafb000cfccd956c24f4d594b30bbf961917f625c86270cbe164cc5b0000000000000000000000000000000002de09fdf52aec0b91bbe99fe2eb9043b19975c6fd503815264ce030dd5e5444f0f4275ac9a07a49de775335d52ea3c40000000000000000000000000000000012457bb55876c482e5b907c765b476dfe6ebfe8e588cb7f630e58f78942bfca57e6c0d5d7b0ce80e48960e297863d212,293920, -0000000000000000000000000000000008f3f1f04fb80a23d348e3e25dac1d732265fd4a71ab8dad3718d268e49c79578e8e1ad1720e70357439e57df0791d64000000000000000000000000000000000fa4c15c76e395fa706a55d1909ede2163274a68b3e7afb8d2e0bb176f60c06f5a921c9ace35bd311bd79ae86340ba5000000000000000000000000000000000173633369e00c8c5528bd5ccf95c6af8b012e5a31941c134ad4541099c7c33c5ffd29a5a31e18be720f7ae85132cd6cf000000000000000000000000000000000800f5eaf7c8b1dd2787305ecc637a0bba8eac807a7b449410e48aed3dae2b4645b8459fcdd477fd92fa5ac6291b800ea4d710d2f632e3ed0ef69fa0219e16ba30d3efee99386f1a5c921f4548ebf64b000000000000000000000000000000000ea8057b2d609ac2130b21e0b4a41f0aca20ee7751f55d816ea42cfa4612b67c3c556b01b0bb1c5912a74c50a420407f0000000000000000000000000000000007fbccf8ce8d1a92756fe80b15c7d9342af4e166d3c1c7e35ea2fac34851cfd983633270c877224749365720fbcea54a0000000000000000000000000000000000885e173b73118721d28fd26f3a9c562bfbb878ce71091d7ae4b37c1f2625777d67955a2b7458af71077db7557171f2000000000000000000000000000000001754edbfc3f2af94c92e6754d6bb096bbc4b39bb1128dc6bba8b4d4d9fac6649598be90b06b9d5db44c4e77c0cd1537cbd9ae4597aaf582857b40096360ced0f044ea172551c6f9fe3a15e0ce290b56b0000000000000000000000000000000008a1a751b5f9a08e2bf5b2a58f62f0af6b8773f88e50f658ed220c0134e83c7031a288eb50a8a35016d2362b431d809d000000000000000000000000000000000d7f04d4a6c36cb3d105dc3915cd5d57f56692132681b3abca4b00e078c700931848e34ea1b7ec663f3886ff766fef41000000000000000000000000000000000a06c3ac81d6d0466e1ef21115150d04c8bd6dc3e4078e46eab213203c3226bb0c6500ae4fda591d6b8a791de598edb90000000000000000000000000000000014d849ddba2fa79b6a7107efeb46e9b6231d65384c69ed183acfb922d55b790d4fc7546afadc190b76f7da00103ef565efbcb4bad99b419820eec388f6f62ac8d87866d7beae6d431dfa48d4243b4a4b0000000000000000000000000000000014dfcb5fdb38cf09c1ecb284dd4f2de0c3d70f90d7c167a442d84e9a29bf43be62cd319b2dafdb6ead2c6596443a00090000000000000000000000000000000006220fc05c53f48e7e4104422b0660ab67fd88a695a201366de570f0ac0ad30421d5e37a1575e6b5ba35f45b441b297200000000000000000000000000000000077cb8ec1cb83c4974f6452ce0de630afc82e283eeb55d3b7e9969bb44bcf0404deae617393f82ac228b836c3cb6f95a000000000000000000000000000000000e2bdf539eb45a125112836008effd104e881aca397457004fbda4a40d152817801bd259434481f0509ab1838cdd1fd060d89acf5b49fd1f70fc54756c4bc1972cd8818e36efc37b422ba6a9318fa134000000000000000000000000000000000a09843630131cc6feeeee8aa8214408235655e4733badd6fe20c5cf1e45f6a61a5216e0cde937799437962706d3bfe2000000000000000000000000000000000ff518501614ed4a199ca9e9aad4e8efb8e9cffa9b4fa683093a49cef4669198a7893db998d5777f2cc8f4bb130c84360000000000000000000000000000000010ea66fb5224f4508ec100cdb611be133c4895a8de1b4c475b097494ff0f1ecdc1bf8fe467c630233cac2ddc07935fcf0000000000000000000000000000000009d22c0a45c82b0a19beb94eda0b93cbbe1f2e5f2d61279e1e1c93ba073cb766f5637195e6964a4814e588e44bb03f03386af376b9b393dde994da419d1f7aab60023546647f7b069ede939386bd6ee80000000000000000000000000000000015ca795fc7f0d169ba8abdafb1dee80b67e7dc616e824959f84c61284d6b2e0e8b9f99b414f5bd96d0e59b66ee706fd800000000000000000000000000000000042f473d1fa228961aad526efd003461935954abaae347dd6c9bc7fcd68b5f5138e57ab2a160cb19d1983089b58b51ab00000000000000000000000000000000188eb160cb968b4b048ce14bb72be27c228df1a6c014fa7dbec09a30aed8c71e8da59d3d5f8073b6a7d70d94c0e59dda000000000000000000000000000000000d467e6b05f033f3923667a82d5b489a5c90c99c5f68078aec700fc67a83d9bb4c09f3f00b9fc2cfd62bb098f885fe295ffca78eea65c00e1128f8dcfc96b39af1c4472b461ba5262307003bc972023d0000000000000000000000000000000003bec45d94f3073b2ca54d6332d36fdb8f5c801d9f70ccf6e3666b66ee06c0fdfd741f74cde1997aa205fb0318c9c4760000000000000000000000000000000014009b777b660264eedb35ec2e13ea586aa9438c47b3fbfd095ea3d8688a89c85bb4052bbd3edd450c19acea6372d0070000000000000000000000000000000017f26d3cfcb40fd6b4f3f1acb6d47a9b54c232aee484c7a8992a3d1accea794dc384fccefb0418d43e1fa7b399bdacaa00000000000000000000000000000000153c6cafbff3c53114c96d8caeee2880dc063d7db5edf5f14157117387f368c76b739553542bf6a9bc4ace3694de885a92837b4314e63ef5a153ea2ec4bd373cc3cecfa3e892c3a12aaac8ddcaf5905c0000000000000000000000000000000005d2481438c03493efc9f1e8e9ae6ab05b7430f7fb82e108aada0e886b14d769969d54b17b31e5bbb63d40836748f541000000000000000000000000000000000971deac599b2161a4baf1178feb81fd4798ad5cb063b1a0cbee7cc33b8fcec6c3f43d1d46d9ed45555187db636af99e000000000000000000000000000000000222acaf8df647744859e04104a5fcd546949feff6244e192a9031fc838f368aa465a3799779c637ef0087183f30731d000000000000000000000000000000000b8e8f1889816f89401b070db687aae47f7264c9be192a8d6e485ee71a5a688070d57ad8928d09d9a4925f1050e2c69e127ef2309c699a3602b0d86a070baef0eef90f539aac3cb6ff42cb19f284bd99000000000000000000000000000000000b8a5b0dd422469a8d6d7603e9f3179f443ef3fab0016afd94e93e2ea9e84b332da4b59f23a5257b99460efdf7d2aca7000000000000000000000000000000000c28e7068769c3a79bb8d92c3b89eca5d6eb42e3e18c2a7154f43a671f8670f878c4b110990c2e2b163ba4d1155319fe0000000000000000000000000000000001804302246fd07d86f4bb23f610af38deba8e324cdedbe5e61cf0941281cda8fb5dc211fbc0ce6fddf30aefa9563a0500000000000000000000000000000000015813fe0d6bbcfdc8e7e40b6141db21e1b490d846ffe82eeb3edcd9a024315193259612155b0179a4971e205738af74ba0f9a93c2fe35877ddccee5da39ce5ae60a6a19e72481319e3b3fa2eac614890000000000000000000000000000000011ac1ea4dad0f650fe0844ac3ab9434ebac6eb70a5f77c8f9c892cb4cb06639a15c63a9b820ef8f7a720040ae5b9e49500000000000000000000000000000000117da7999552e7886a25a939ada0944cdb15b5c468e9d1c3bf5b6af920e470bd648d24f3cb7f91e670f57a52cd65f7b3000000000000000000000000000000000a24147ef5f2b8ad888899c1db8df0a601eca9d76f1b934b1627e7eef3efe542f51205b96b5c00916688579ece81336900000000000000000000000000000000151863d964b12287ae4278c905341124985410f1ad6a72bd5c62230b7d8b0cddbea0c62cb2a7147afb5bfb03348be53363da2f227d636f10e814e360c2156e686e26ce3401dfd15f47c4ed277d05353f0000000000000000000000000000000001d32ea5faa6303c530790146df7cd5cdee93c0933b4cbc1c2b8030bf0a8d2600dba1907df1756152625cfccf8cc7fa90000000000000000000000000000000017b05f549751d090f42ce8a3ac5d959cf988ecdc485f51734d52c40a3e22a097917345978209fa74a0a05be0a66e5c6d000000000000000000000000000000001481fab7750380626b174602d9fcbc97555c516f4410193d2849443cf25ec22840e4fd00b225f98d81b38619e8844ce90000000000000000000000000000000001d56434066551c5bfbaf8c9007874abe57a6f78de9355a297bc117f2bc5e6e3f44b248037f400f7caf83fece0c00ba0ef79e3b6ce752d140c3dfb2007a08221d989038c921efff3bc4e150a6155a33e000000000000000000000000000000001667f1400973598ad3f56c2e49dcb5b556cc38ee3e5801ac4943f3c4554205d8fa69831e582a084aae1ef584feb0a1880000000000000000000000000000000003f0bb26ea548e498f05a5bbda8b8e536613f10e7165607ab77565b193f794664c8ab0a5ae2368d7483b77bc1173d14500000000000000000000000000000000176d8d294b4d975629c6a89bd6d45f9c3924a621259ab43d33a3d5aa1f423b68e3cef96dc103494bbb9036436c170f5600000000000000000000000000000000002f8ed87c584e69de59cdde02b6de9816c31a6efbebafb6ad9cecaf266f5bb9c8880f062dbc9235c91c668bae5051f4bc08091af8b8c6ea5c26f1a7d795132521350d774042d3a8c0523e86fdd23a3f00000000000000000000000000000000085fee95b859c52e44fcb2900a9aa590b1a5c2f981a388d6ad7b81ffbfe033f648c4a84e2119cb0484e178ebd3e220d100000000000000000000000000000000171e6ca074aa97981d2c2ab000a8bd12cbd5f5d574cb83158a6ed734e8f9b7aa4b74aaa43b7aae31b3f4fd3d82fd30ea00000000000000000000000000000000004fe6099a52fb491a0624a8d787d95617f6c64d16d20d1b3769f60d4721f7af66d7e3e905b3e08b2946ef7bff4806ed0000000000000000000000000000000004d3d1a56af91377ae6b00e192ad64fce6dd43a37592fa8706c9344b3d96b1f930e03be85a5ead3007f9016255d2df7570363101b87d685aa7314f6569fca0775bc6aaffabe136e6c336e8fa43dedb8a00000000000000000000000000000000155830eff04ec2f4dfca4f73403e408a68830bc031555433fd38ab3ce1035b5f882bcd6032aba69ecc43625546b4a3a8000000000000000000000000000000000ed5b698b1ae23769cf5b6dc2e39f8500fd8a881eb43452d67c6b84ef9f0b3c7d81db1909b646e92412acc7365923a940000000000000000000000000000000009f28ec2f949cddee9bbe2fac12c2c029f4e472afa1ea56d0edfeacdeb9f43a4a43b79ccdfbe8957b4cc16bbcac1857d000000000000000000000000000000001474b435131301db9e232ddf54569ba99bc476200ceefc15e4aaaf1a574c1de8bd2d63c8280e23127a7a036acae223b1997ff3852cd97c3a65bce9083ff66197fd5c70894641195514d556102f091e8800000000000000000000000000000000168475854829d47356d9a8dc13a94e8d169771ea0070d9ef45e666d5378dd676d603c2eb57a3cda072c11e0926b02d650000000000000000000000000000000008b493a9f4c19831341782fe6285db2f7e8250d72952351ddcfcae6f22a2ec0935e29d396ba32f72dfa4067d0e7ce7cb000000000000000000000000000000000d9e72e22f2a1522babc5f2e8dc7857ee690f60f7843ffe15a080d56bf63db86f124cac039cbfa16fc8ace4d6268a1180000000000000000000000000000000008f3db1f6c0e5e7b3bb27abd34bd877cc3c373c681a3abc88eaa91636924ee477ba5032801dda091dbc51936a90c84685ff95dfa306f91196849d752569b35812e1db7946708cd06df9db9ee75447bc30000000000000000000000000000000004e34bff7e9e3ede02df950aa0e8c5f4c5f85cd3be89d211e957a7de95b8e321cc11400c3dd5b2ba0d1a3008462cebe7000000000000000000000000000000000fc1047097f01fd2079e6357ed379ba39107ec41ed6c6dc17fa6248d52be2b1cc2593c9735a6cb48e6d6e0434028f755000000000000000000000000000000001896fc5e990aeb416cf21ccc73f02c41d019d0a2679bd533d0811b7c16ad3ad3a6988170fb2db030b5fa7c3e4df5acf4000000000000000000000000000000000b70e14ce1b54d7913b9f3782b2b8ff249967a6b871dfac7f54f959954febb2783cf20e20d1710e5526ef8aeafecb3d603c4308f0467520343825a91c0421f9c9c9d06957fa2fc051970f14085339e26,0000000000000000000000000000000008056d4dfcb593c10a877cc8a4accbf58f360256b76876ed2b33a07be3110f8e295ef459dd6fb10d12bd02a8276351f50000000000000000000000000000000005686da1a0da89074c6b13fe9913f5cd49e0ecfea46e06493510625f1393ba4cc2e13f023fbc7ec2e130bf9a4f7483ef0000000000000000000000000000000010cd660001f65876db5b2cb1a56d85171d4cbf037f3bfb0e01bf4430c479237cde5b6cce5839a4fb22b406846e757868000000000000000000000000000000000809d7711211d37df76cd1cf71001cbf02c76df67c83e4eccea3e05b11d196b5d52ad7c3d0a00d9f0ef5b018717fc3eb,293920, -0000000000000000000000000000000005d6cf50e3add0e5ac3016b394ec363d6145ed66ef56b07bcd33c90600e83b4277558695222062e02d1e2b0693858e73000000000000000000000000000000000de8caaa810d4ac39258e3d1656bf7f2fb7853a5963ecb989346abe90d5d35d3662f6e283cec7bc386a6a8638ac395ed000000000000000000000000000000001849ef86eec16b0612f214c5ed52c0d50a90bd65b623402879f2654fc578ab680d49af9afdeff546702304597a20f1fc00000000000000000000000000000000168707730c4e74eaa4e85e48e7239b9ba3e8cb74c24b7126a685da0fcc963b9f9180e252adf7d8c521deb1a2ce0099582849fab097a4f71bdfcfaf435994a0c6ac3671a4a9ed0402010be83ff95228fd0000000000000000000000000000000007d4fed2fbd9e9dd19e0af5c52637b2cd337e0bfbcef0384f182a56189a7e7304b9d2144266ffa79044be90cb7ede1b6000000000000000000000000000000000baabe8c23a10cfe85494c693d1b09fc8e43ef5f233052d5b6294dae14b4ff9e5ec240a1c00a16a9ddc27cf7b53bcc7c0000000000000000000000000000000001c595f193229da9acff04ef67ca444b0cec75db5b2c1921502e37eebdd2bb43ef47290fc6f1980abc75ef4c50034df00000000000000000000000000000000010fe7f3110ed3a240366ad7ba31d56ab993468dae2dc1b667a46c7759baa37b865d02834e14280a2ccc062af5bb2b7d6e6558521e301eabf09e80a509b46cf8ec118ee8586f4e46a7313a96dc37ba69900000000000000000000000000000000150350d8a771c79268606d6a5e1c147dc9d92e63fdc60b20be688bd52eac697aa5d90fe1b7b91321b2af87c47ac0d5060000000000000000000000000000000000fba8f4da448b8f2bbd99014bee2f9c581f2a974bb0b54f41a84a7fb359e9dbf88ba59a705504140284d486241e94e80000000000000000000000000000000003bb92d6a603bd93f8e987071a7385de68d10cfbde389eaf01ba6480caf1ad8aea03c84d1889b7d5b5c5f72e62a2d75a00000000000000000000000000000000193342a9f15109367030724946342e564507b26971caf75190e0b209e429a948d8b21ca16041a01010b68222db66a16b8f2f7c525fc0f353700fa823a5d32a93189699206c5ba5ed271a158ebb47674b000000000000000000000000000000000bc4a46eea57231cc64758560e3032a8ad8f1907b3cebd7a8faeb98c4216cb8a0c8fee09929ecefc4bee7955f4e799ba0000000000000000000000000000000009f9486257ae3f94a2ca48eb203e2ef44ecf866ddec7824e1a4bb3b89b320c38b3c46de8202480628c53c415433383a3000000000000000000000000000000000d8e2b5d0825b11344d16dbb2cc614c6b84eb1cb43f70d70e272123867b731775b429aacde611318b2700aa567a84c7a0000000000000000000000000000000007f720929287a70873e9f2f2031b66693eaa6e604668219aa5aff3f50e720b34c5fa3f5c66eced5c3e86e8b34a81b984c7e8adc0f0a042a32c733b5c3356cf4a7d648be51c1d78534ca65dd43a0c13e4000000000000000000000000000000001537ed68e203e56f31498efa314322694ebd74cd1dcc3145d534299fbdadd4256f20b9f74b895931a60753bde6ff9030000000000000000000000000000000000935c6ae847aa7f47bf427988665e5e18a32aa869e196cf9d5bac1349c650219a8d20e01bd8d49bc7e4bb8d464aee84300000000000000000000000000000000013e0661d7254428861cc3ed47c3fc9daae8b86db35d1c64f8ced3bc18a89202825f13163ff94ac0ebf046a0a99727e200000000000000000000000000000000039a6b0b2cb91e460d50eaf9600c29fd4f82a81c283ba4fbd9a7d103efdaeb1e82947f5cc1a7a1112ae6344c51119201650081a6720845a20164ef7c06ce1e73286a32dd64efbe57fe46765008dc9dd500000000000000000000000000000000071a6b0267806f2b9e0ba493960fe0e43f135c739a54c8daf5ef9ee348a281f19876f80c0dcea59dfe9457b49809c12a0000000000000000000000000000000009ac83690c30a4afd78f94b2493674668da4efc84007d2a08fc78bba271ed1f43e2a9e5909149bf0811c44dbe07c52f9000000000000000000000000000000000f5d523612fdb2e7dcf5da56720057dff6b0b80707cf5924d146c0c072edc0635c73fb04256e06c7c9355cfe77a7af0700000000000000000000000000000000168431fc569869ebba9b4a72371e3df232545b5fb95778baf3d9105930d9a89b4cb7eba430e9162a5589c7465e54ca3ac067d18b95591f7f14261f95513e1990f5a4f6908f94a015a93fe379726d5120000000000000000000000000000000000ce836522b983fe3ef6a502a0de4c599fad8a36a60d914218d5d2cc4d56d69eed8d27b2d50899639d1a0ea9dc7597f900000000000000000000000000000000014110ac048ac4c20e53f2214df8c06d77f0b3150077d027691cacd3715d4630a387d5819ef58eb1bce2e8669be330a3100000000000000000000000000000000178e5cb42f56df2f1b255a028a00df96c02eab0a79aa0ff3e9772fbe3eb62174728259b3a15e356e6d9666eb65fd6b7e00000000000000000000000000000000045197f136649b61d6e0e7b9a56674e769e2d26716ee7a63fd2b83b767a9ae96694e9cf81375d0377a1b27ea6dffaebbb448bb01a1963bf74e0fbf99329005af8e932074358d855ff43c213e02bf26bd0000000000000000000000000000000016a6a58301c243b0c59d6934bd926d6440b87b49f004f411ab0fdd924480175052f63f594c18007359055dc776e7f2d300000000000000000000000000000000176db4845cad46a13d9dd0f4077cd22b3458f64084c7325e9885f8ca341ce3ccd4f634f41efd6a70f16e1f0c9ae103a900000000000000000000000000000000068ba68f652c4f072a64d56618f93a1e148274b1b835433be878c06e11f65ff45b7cba0f67fbe80327abace68396da7e00000000000000000000000000000000047a699487964c98453207c98cc91c980c1ed37dc26e17748e6ee88e5f4c0ce424d87c82ca6db2264dc8aa9e437a5f25441fc4cb1ea8f86af8839aa40c35c0706f3a159b4bc902347009f744b73cee35000000000000000000000000000000000bf7e4a9751d4e3baa7ce9906f4378764e5384136944f6d3f3074dce66ed017759783c64fc381f0dd7512d6f6e55b4aa00000000000000000000000000000000006ae2a4fda156818cb5ea6120edf7ea39370eeecc3f306890f47a6dcfaffccbb69fd21f33fe491b7065838b277ad2b2000000000000000000000000000000000d3ce00c2f5febfeb232dbbb74fb0405bab86474d1d9c545c93b65c7892bdd58aa56225641074ec9b428efd9063085d00000000000000000000000000000000002552a8c1848fbefd6b039d6c4bd47c34dc34ab307163c4f6d337946f1d1b41aff2f7e37f5fd94012f0ebd21f97d18a83020a1ab853ef2018976e43cce2724105a2526b28d23b0226c49ff3d4a03d40c00000000000000000000000000000000105320cccd67b6ea78e96e66425a10a6911d2d348fac3231af583146273609fcd7fd27a19d4614fbdf05bcca0f92b927000000000000000000000000000000001204229ee1f66fb5a5dcc4ee978327e35d703ea310901be9c100af824e39d24a028ef8fce42370e5d734df02a26c145e000000000000000000000000000000000dd21f31f116681c1810bc36141cc18096cc113faee7db2c189abb7a746e398e272fa0cc61286aea0a5ec4008c8d03b60000000000000000000000000000000007911297718e98588844b9022c825bc4b37f2af30e1fc2d9cfb58b4500dffc8e9949afddd051e971fe78d4e1e7ad1b4a82702398b8c95c3a8cd163a8a3cb2a7a04030ef99404c325115e9a9312e8c1bf000000000000000000000000000000000760787190048e6ec8bc3bfc368f010e2f8aadd53164693a62b0d7207575bb2597bcec4bb382c57fe9053e90fe2f7159000000000000000000000000000000000ec525abbf13da64a8093c5d3fb800440f4c1fe798bcc71eb97bf2e0aa9e8be4b08afd2313f9143260058132d2607141000000000000000000000000000000000aa12c902084eb843daf7b351989bbab7a86acb62eb54eff0c7599bacaf44653c9fbf53f47f6ca72d22ea1671842eca800000000000000000000000000000000082f330d9a693f2bb9386fe5274aa79ac73a17688821f3c705120fb2aa76903627786a8614053f21a93e0aeb555de64e338468a325384a9367c90bd0450816a22849b845aadaf187c27b3f09800e791b0000000000000000000000000000000002ce7f08b8d5052d8bd07090744ca067700eaa1db61dad3e5086661850337bcab485c15fdd36c309a9e5169fd2a2b55e00000000000000000000000000000000073fa834cb4dc4ae120e738059749bfbd86b9e64fd71b1d372dcec8474f3341137ce8cb97a38955e9081f9bd5e07ab830000000000000000000000000000000001568df6806d8c3cfc9231802ebe5edc5d505198747a0adc24d0ac59f28d32b7b379d1f2c6b8352389057c7465692ded0000000000000000000000000000000004fb4b08a4fbfe197e924be3f7213a769a2bcd24109ae69a32a197b6212c5f50dbe8f46f5ab6044a4c779cd3e09d13bdd29136cbc4764346e7ae1af92fe64560f453821f96f32a42a2006b6edee75021000000000000000000000000000000000c07ff656904a47b0c7bf77540abc47cc6eee3e76b6ff0983151de9468ce3a860c427f3d5d489d096264159ab0567cd20000000000000000000000000000000008cce094ae1d9fff246a0e76cd67dbf9808c94554372fc4aed4879487ef240e45047dc201dd8bbccb613feb9c4623a0b0000000000000000000000000000000008a25297940a1bca1267fdce450b0cf43105eb4a21ab14562116039bc8379b1a3f58a7c117e9ba735bdec40f772465300000000000000000000000000000000000ae17a9b1fc3b0b7803ef48cb26643e8e78ef133f94bff5f87739182e662e2641e72383efab1f3ec58fa20fc816d56c675a59418f1462247d3bddda5937553e96d854b5df64a68145a193b2b1a7eb250000000000000000000000000000000002357e5a04b0dbd7f9a1709bce9b7afa12b10c7274b440b4dc3bf51a801d483804b1b4b9a096c3205a0e2aa7c0100c6e0000000000000000000000000000000002ff20af67f126c80293e44bb3c9ac74a94586a2de4146588c7ee8503530398eabc30f7e89322727739618087fa55de50000000000000000000000000000000013c6d06ce509fd557946479f2768f62474e6db04b2c92c5cfa86c023f79d05a387bd4c9aa618888476d4ecc93ba0995e00000000000000000000000000000000000fa477870c952f7506b879b17fb0a1c31771ee832ce0ab21a513fdd91b7a2a78a03d297c55558b834e255462b15520544a345719b40f973398a6fdaa2044037cacd7f6c361921c62053cd51f2e5ff700000000000000000000000000000000181336b8fdc03c02e23cd06ac975855caa2bbc1fe78a2fc7a9d0963c90a1f1f9330d50b88bf2526db6132d336ea5b8e6000000000000000000000000000000000f2d94d3fde2c0f67dae5a6ac12f713ccce2621303762e01961843eb9924d1d3c732b4c977d8cd0e5668adcd7dbf7dcc0000000000000000000000000000000005ac9ecab11c3368c75b0d396889dc34bd43ccf550d817c1dcdc7143c15d5c0e241add37328a7bd8556fde87d75d67fb000000000000000000000000000000000184704eeebead43f85b32d7f3efb9b9469f3ae10b73a2f034bd33e6e66da0bc36597d8e29ef5585443a655e24ffb68fbb38b4cd72eb18c3ac87860aa58b4b439712562f742f112b5d769415e9c19d0a00000000000000000000000000000000046751743f8f747e378738c265c1df3a368cd9570a2bd7636991045974c34039161fb0eddc6b813003e0908915b402170000000000000000000000000000000003341bea6cb81fc5e7baefd386a518d17a6f752c0e1ace5a9580a1b1649f5501c7b4639ba0cdbc33808d78b025a31f190000000000000000000000000000000016e3b9e8e189df73574a00a721440379589a7a6df09eca9a790e04c729400323b2110f63d547d83664c35227bd15b5760000000000000000000000000000000005ebd94e4640344e99e7e0f1619c6288665c985b90d99921ee61bbfce921265c4881a7e1034bcd840a665bae44467f5a94a849f6fb5a53bd5957e53ade1baee05702185b4d0fbb7c1cc0f46cb75614fc,000000000000000000000000000000000d993522760839abc960e99d62dca1021b52ddc8147929c4a064ec72570ffb3793205598cefab8490446453fb6da231600000000000000000000000000000000105db1e83fdff735d06d34574f962e70d84e2c1ceef4d8a8f14c2673633d7dbc7b97ba6dce9013f06fcfb134ffa2ef98000000000000000000000000000000000363be663cb0d36b8eb076df283b075ab9e568e460be804f197c51cf7ef611d8783ced304407d4c2540f1a4a04c18467000000000000000000000000000000000ab2c00473a2267682ecb356422aeafc893fab96a3bd27ae58d9b0786624c8fde446cf68bf8a003d9449702e345b1ace,293920, -0000000000000000000000000000000000a575d896b06c5ebd7459a70b9321cd0de082dce7dc0ce7e39581751d01b7db810bca80f39f521df0bf70ef642bd66a000000000000000000000000000000000ab497a9590deef40f6fdc0d4db2ae7b6ad9ab59f112a5a0671b48581f1f2b6a71602c73784ca6c0effce66a0a9c6500000000000000000000000000000000000af3812439e44981c91633f73d1a92298ca1ed426c98cfbdb50643cee36affd5fd02886349aa608f4b8a27452a51a96500000000000000000000000000000000013126db8b642d33dd988b745b07084ef86a228767f7e8bd45aac830dbce4136ca5febca5fda9644d3292203e27439d9f5b9d270fe31c772e9a0bb409d9f08a07887f045f906f30e2653d293b6c2c277000000000000000000000000000000000cc12f75fe5e6d6f082f9977dcce64c7858f3b6378112e7e083caf0c4b33b5811d62a1130c595937983905fbde8db1fe000000000000000000000000000000000308b803bcaf4f63affaea0206aa9f4770c21b4d191890602bb4151b80fdb42af0cd9f8dd2b1a3adfe28d0e49712d2290000000000000000000000000000000019f83af5cbee858fcbc9bca0f499222849b9e80dde7ac79b7c46785a484fecf274e0d4326469eca647cb223068a183d8000000000000000000000000000000000d0a8334171571bc63054c032299824523bd2476b1150a67eb17b84bba01d8a65295624202c3874e0302159951734702dcbf4fe86140c50618598be9185830bc1da11429162afe0528f00eb6698ec088000000000000000000000000000000000141cc01094391887f46391bd49fdedbaaf524cfc94d741cc7c8cf081dd7c425d81ea3e407be48127550012e39d2b0580000000000000000000000000000000014db31972eb242d6c2912b418ddf416fd7911f13aede9194559b05d1c9e12056deaa1e56c155cdbc231b39f4f9aa91ea0000000000000000000000000000000007b361beb6c156b5c8b92b489e6d6c05e32a4376d20ac3e1a54c94e678c88480779bb789c3e1ff7a021aa6d872c98551000000000000000000000000000000000215d270f2d3c5c5b9fa99a873fdc337f4edad6889f7a55556d8ccb5ee86b592453b74a720ef6a907bc342710cfd9cf91d7fb7121ef0baa85046567014620e1adfb9e8b3bc95adccbf2e0b0ea8f37c670000000000000000000000000000000017f5d31987655f8eaf046d6ea4025444924befa51c319b2bcb02dcdfde4d80a1c48049514e0b580e4bb59dd2fe40bb22000000000000000000000000000000000141ab771c08ad7c592725630aca0b2564de1ed8759eb3afb10a4bf451eb21d25e8d917f49bd5f7a06894baafdebbe790000000000000000000000000000000012dd82703c939cc5e7dd5bc3b924d744f0ef1a95fd0b9e57617e822e3fdda05b2e5a9959ec48cba0da40079da2253cc7000000000000000000000000000000000c53ff34d875fec4c7095af324d15921cd775873a3ba67740b2c123d6d482263b1cf93585dc810d19c68965cdbd9e102310d3b0535e78d803b477e5dc26c71bb18acfe66bd5ba5892d924d265afd6a16000000000000000000000000000000000a6514331035d42f58abf98b805f159921d8c4c935f88bb5493c580a6ce14a65e243424b41b3a9188e26a7f0c912a378000000000000000000000000000000001351e48b2d3f619887f4e83823dcd9dc15afb2800169ab78a2cd5ebdf25dcb6310f1051894bd2b549e509c55f5286f600000000000000000000000000000000007900972b84b6a76b2e686fa5757e98b8395bfc99da86eca122ce209afb39e8f3b07603cad92623774ed54d637e350d30000000000000000000000000000000002c68c42b3924b89a67764990478e48fc17aad4b5543bd38bcfee34fa1cae7535671f3b885852aecac53a30f28b0d4aa2fc9417e65cb76aa0093a7deb3d38c111c68f461a4aac57d8f09189f94407ee8000000000000000000000000000000000152d2c0e798d85e4dbf35dab808dd29d724e9b6c7ca7f53ffddfe1aef5976f2d3079eb1d3099e91b37d9fad7f1af5750000000000000000000000000000000015059423ee4e7201aa65e39116a2a49ba715b15e4b9547d18a0efd355de6f5a0159bc9047508bd3649407758d62887f0000000000000000000000000000000000e5a823fdc69f3928b22c542388f982f8131a978b08dde80d44e51d9eaed2ac4a1d5fa7392be6c7edfa33e833da4832c00000000000000000000000000000000044285f4e4ce526f96f9f512c5be754e0b0953744dcc04807ec6f041ba5c6fb9d5d395e93317064d50e61aae26810df0aa0b2d714aff175a0be2ba9e675a2be8936c42f15e304a146622a95dd6b3e3ef0000000000000000000000000000000019c457e369dbfaa130ee79bd33ca70d00a3797b6cf62126baec0c5d7c3fdcf5ba7f41195276dc412b6862b71560aeb77000000000000000000000000000000001206f67dee6521ede85573bbd5784d675fea42da16010544857d4e2d81b720b6f85f646fa23540880b44a6cde9a39f5d00000000000000000000000000000000142018ecd7c7acd4f4ae288e1c6a66594f1c7f31bdb9bade2b4dc4c6455cdc685b716382c54d67373831a19100185e850000000000000000000000000000000013b0b57463a3e4cbe063c0d4f4e998cbeb132a41c2877106ee60e83d4ef7d339a5432d30a3c149a42dfb1da9d61f34030227c3510ed6e4c7f84b11ddd2d6caa55e0e79ed59e1cc0cb325d55b5d145aa80000000000000000000000000000000008a463003900194e45fc2610fb461fde538b17c4fd516919000d423f5a1b582342ab9ec20d8eb6fda8fffc6a898e46420000000000000000000000000000000010eef0f7bf73e35dd75fb924bd9759c09aded9cce46b05e5d3c5eb3e93e5d5032ecc459e2220aa529d2f773c4b8b8c180000000000000000000000000000000002a0247f82a25468ee74da555218cdbb6405871f7097c24e89db3f3eab59b91ce48ac06e8eab2c049346436c846226a3000000000000000000000000000000001895b58a50c025e46a2cd0c59d5437f6eee75fac949adb7ee12d455c96206a33ec9ac17d5088fb773618fec131981ab6ad930000a9f82e082d408999b396aca2b0e435a66faba1d95e10fa0abc0625cc000000000000000000000000000000000cb0f13b0680c2f7de522a59f4e46fe1d4af3a64cd3ab97a2523ad3c3dc42f5e6760e06cf48e4db22ee64c5ed8273dd90000000000000000000000000000000016517038ecd2799d787c5b6ee93079c93f78de4a96449bc82699ddd6eebcedaa1d02981ab47c529652cc21663f1a665100000000000000000000000000000000067ae1dc093d4aa2ddd8b7127dc60745ce9c462a066106b099a7a07525597c72e4920bf64c2ea8a3fef3de51c703de8b0000000000000000000000000000000016374f51023e2448eee7c64115d85794996fadf4f76fd4266c45093c266f35be09e861d07ff194f3d15e310385705f0e1a6799cab8964c7b79b80e76be237ef49c2bdef5c99a38ea873af6e9d49790ec0000000000000000000000000000000017479396aeac06bd624a47e75b066d6daf5a37dbe515650cdf3e16be21e7d3a1f52a695c1c06382589eb7fc869c7d9250000000000000000000000000000000015c31ff36ed4eaec4d3927e62c111d062236e19fe6514236e6e3f7ff05ee96e3e4c084fcafcd21049a81faa1f84b7e7c000000000000000000000000000000000341b440e6c6273515fa7940d2f77018169bf6362b70a7b0cd6d66cd332ccc30e3ac48f7581edf47ebd137253a9c1369000000000000000000000000000000000cf424de046252efea9320b32b79bdab58e0e04f2916b4e8ef475da7b8ab85d8d5fc793a45ec6e6c035b6331a895d3efb206dbfd70e4b24bcc09ad35ce7b3aa62d17f18347f2bc5f15730202909c93770000000000000000000000000000000007c9111a85a6acb851e9cbdadf182096b720913ba3fb357dc2cbf2b8e796e9a8044b6df3ccadb740c73a16c3780c640b00000000000000000000000000000000059543a955c84a197d23cac22e15d82363c881026e41c57ee924da2a8c044f3021b29918d1db7926ddc2fc7a662ee7ab000000000000000000000000000000001355d8bcbea65a50c9b6ab59881e48e8e5f5592cee6aa69d5d01b033a84057cb6e74d911769bd2ab5f9722328aa204640000000000000000000000000000000011232571c95d0cbadf8e70454c851974efa4b326370249238db159a1224cc6d34eaad690e1840ad887a875b667ac1f193a607a7301bb7dc5b9c82d956ebb0bc54568d0654d725d4d5f13ceb6231e862e00000000000000000000000000000000088b7cbecf91721e01e5e4a08ea3b261febb58cdae3056d9316c3840b3e5720a289739568bec7b899f4b1f4f5372013b00000000000000000000000000000000001f8835d4b0e3b957e46b718b6bcd81acdb50ab85f10bb70c6343a23970efbe72bef89dbcb24d66e6a6be3eb55665a200000000000000000000000000000000046500afd292a31bb5a4a9bd7b5bd0fe608bb1265351edea69162e61f1623cf58e34e8e1a8ec58ca166e8203c86f84c00000000000000000000000000000000005d6cc367ff9c88fc8b6c35383f147b4f9e3eb21268a5a7405794441d449b3e1b44c8f66e30783e5f6c3567adf0d80171231e0fbbc2d98bfd1039a889acac471110d568b0a24ddf5eb3501adcbaac6fa0000000000000000000000000000000015bab57412cc5c7ee0147b0d2511b7836a14a82df06b4eb2b1baab102840ed04cad81da6e920ee000751e0727091c1460000000000000000000000000000000002f725e61e82980e6164cae7a2e30a36dd7245402f4933697607640d53fab2d5db57698be33a0c9b5dda14aa846db7c90000000000000000000000000000000007fdc589448887f6986efd817c63954d350511401333cb0df89214317dec0a82b06259ae9263f260fc7f21f98ad2630f000000000000000000000000000000001324e3bb46a1c69fc550fa8f2ae2d0ea74bc2d7159bed03c13a9d232233449e271ad1c3922dac5d84aae52606f77dcc0393c5c10d4bc4cd1567bca6960051f818e5c53704ce44dc4582767fef1092a870000000000000000000000000000000010adc26d73007e3b1cc58684fbdd7d197550658b4c66c702e9cd0f4e481f23a26c94c6798cdd9763110eefdca3d802050000000000000000000000000000000009138258ad1bdf6f9cdfb943fa32b42c4f1d834be536ed365d00126227c78b0df2776610fe5cf66a937cca3e0b088861000000000000000000000000000000001991db3a35bd2cd72377cd459502a84315422bed92890af906fefcc0acc4515fe7cacee1e4f360ba24efb23292482b8f000000000000000000000000000000000d10dfb682ae7a78b23b37b081efba32ff2011fcdae7b0f8a794a6ec33d71f5d6055f93e3b68a37086ab190d7d9bd7aed412195e347b680430c4528987859a1552ba8383cdc075c437ef19db6eff6e1a00000000000000000000000000000000182795b905320ee69281de833f37e040a3295e23be05ea7ae4563bd49d8b1fb02e95782c5c19645244633951cc29c5c900000000000000000000000000000000053368ee1412723b5c6465ee5ebddcfc00812e0e12e940f8485f44bce475c8897b324eaf7e66c0351ce9a6c92758c337000000000000000000000000000000000279f26c1e76e5f5d0fe1240c0956cd6025f6520ec303feb383b69525ebb6b2f199808a578a91368c3881a4044f37be50000000000000000000000000000000000ba4012c24dfe1038ec4b4565e1b321bbfc174cb197f0b0914bf1c126bdac9f423845f6742129670b7f3dfeaaa62df45b6701bc11c1ef3c9389710e4dd090e3db481c5400ecb91655c20694207a71f10000000000000000000000000000000016c27a3a950fc4857fc775441947f7ac02af9b3df6422874507b11f7b005c61d7d6a4a115d3759fcbd64633a8ad95611000000000000000000000000000000000e92954034df4f15450c32be31d4e146c4b0014a2b81e2afe755df79aa962afb05ca4d03577f15980fc6d8a34f2cc50200000000000000000000000000000000032db3e3c3617c16ceb1c8fae83e806744ca40cffb56bf9b79997cf48c55e5fea89db43b368cd922cd7ce30dd3984d82000000000000000000000000000000000d153fadc3854be49b2376ffcf4e5a46b9dfb4f54e580986767db13127e2d4d10e465f1ca932d79ca90f1971ddc0993dab45b07c059738ead9709bf36ab20b09fd3368f7aa12c6d9f3acf3f145c83fa5,000000000000000000000000000000000e1968e131e25d3f911284c369eb63aaf264ee82f4d9bd978b7d02470feab68ae82aed8509ffba875798a292f012c9180000000000000000000000000000000011488365570d9bff018ce6aa15e3d7e078368f09873ed5e0b827d1c25ef369d6571899c8df38a3df3135d0db814c87a700000000000000000000000000000000161973f4949bd72b9f010f017398778e0d7f0c8f77e12a87db1851af516b4540e3f4df314381f295c4d167fd9ac091a6000000000000000000000000000000000ae16f0a4a597159195aa47862585839485615095e538b745c1081ca73f202115a438d279dfa45bd3aef8d4043ec67c6,293920, -00000000000000000000000000000000046eea8e5af344dc8600ba7e506e923f6c356f7ecb3b78bb3805c4561e808c1f570e122c4fc5a1fbe433b48ce0c15d510000000000000000000000000000000006f1ab405a46c825e104bc963d2b2f573f0d345bd2b08a952d8793c0297dce267a754b802ded4db478399cfa88e7e255000000000000000000000000000000000a5fc4a09019ac9649c07b623d2cbcd9f0cbb89d28c01b170b62544d8da8ba3f236ca3172ac754175a3db85d9b846cfd000000000000000000000000000000000f7580110db2549742f69bbc2850e4ab35a6e415bcd1b06220b9b009c1f4c99152289eedbcba2aa653f38f6b8460386b3ca13f8540eaf45ffdab5182883d32a1d35e7cd956092221cc40232efde6cd1e00000000000000000000000000000000026907ccf4d501265cfe67bc1c0b06840e9dd94a614c873d676b5416457d98a1dd744322887f1f1f86176b11a27d2830000000000000000000000000000000000cb08e541a5b32fdf51acb28ec64d3ea542c7bd75179fa3f74e9588156815bda9d027dcf5597d714aa001b2dd8a9553c00000000000000000000000000000000103ac1c03c16706d5936f216a6445577c96acd3a00a3d8a9c2c66e6ce568dd84a4c4db187a5fbde24e6ce60e037f53a90000000000000000000000000000000001da5cedccc02d0f8d1dd7e4d81c3ec47d432e81e941ea1452b112eaf40748a6634957c90f32fb0385dc5d642bf65acdb3c8b045ef559b76005875bce09a66b36f295070a73ec8dc66c86bca51fa5d4d000000000000000000000000000000000a0b8dd68918b58ca6b113e938f8a00b2595351777aaf32dfbf703ef3884f02c798f1b5bb78cfac32f196c1fe88aecaa00000000000000000000000000000000121a4104e374566f8d582f75a3c9b70f09628f116b7ab22679ee13a1691b0b0bdb0d737833fb606c746fafee5859f1ee0000000000000000000000000000000000b8bc89d718572ebdd6e3100769f2571cabdd79ef5ca9a4b9bbaa432b1a4dd752f9af9d2a9b1f1f32d76d4ec2d1636500000000000000000000000000000000129f1d760a12eb1a75fec1d2ca438189c933e87095b9fbf9a0371d64eb205d8f0932fde9ee2ab9f36f8b6e5d4b5dd31021953ea264f74bf64378a339461bff41c5193e17913c67be7e2a249c9737b825000000000000000000000000000000001499e5481ceeefcd2ff672df24e8987fb60872ed106c496178d71c68e9078409a80016e1f9727ed0d5922c93e821dcc80000000000000000000000000000000007bfb606c005c7da6b4ce2d974f9fdb2e3710c8f51f18257ced7663cc341ff81fe2e46308a2b62b13408965949a6f08800000000000000000000000000000000003fbd951e860e3a4724b667427fd9916ca4ba511a0dcac7b1125b14d8a4f4da82ddc0b0edab8ea50e911b0fcb5c200a000000000000000000000000000000000b43195a5f0263307e85408ae4eb046e06ddb1295a490ac4e0e654324de53d0dd023b8cc159d86b861dfcfdf7ebeee4a505655d72f1128ac0204539f0d823f076cb3a57a7e74e896b5019c9161d6486a000000000000000000000000000000000743bed2c17bea1ebddf750da504fe120f457cd3b1754c9413757cc48f7aef07eb4fa0572cb853cb72d68427e875456000000000000000000000000000000000102ddfe3dee27186a9484f74b3cb3aa366a79f0d2e36063af6e484f6a459e9168d7a4a6969bb720ec694a52db7ab34b40000000000000000000000000000000009bdf5b86aba4845adf9187ccf9c74b1fcabaa05764e41fcce4b38356b4a0ace8e7b16abfc7f7b96b785ad47fbf8e90f000000000000000000000000000000001934fa903b71d234c4341b2f49f8177334142e7c401553dad38e66a2c157fcdf7637165058955b7798a59051846dfb8cc4c861cde3f445e3a78d1498d98b2b947056cf578652e19be88da4a786af196f000000000000000000000000000000000ddde953f59b8591a83b0cdfce780ec23d052037c26d60cef36522d0f984f907315d7b41c8be9a9632f2b88e0ce950ce000000000000000000000000000000000b8d7bdd94a994901a434e6ea5d03ea45dcdb859e560833d8ea0bd9d20c7db9c16b2427eac27d8f1eb640b7d28a530fe0000000000000000000000000000000017b5b3a3097a74d9c1f1b23783723235b6148023b6b060234dd9e2f6fd05e38668167136c999d91249963e224f9bbcbd00000000000000000000000000000000133da0c217c31ca052800315aa8a3b934fc1f179e6247801904bcea1e28dec0b65632ab2690bcca3606bb1461aeb147b99762c5189cf154e24238e4b157caa1d8759002f69b289cfbf3f24f5dabf20bb0000000000000000000000000000000012778a6fe79b1f2b768432df036543cade95504bb7735ff547969faaa8db84e3588046a074838c9a551a4fb48f4a66140000000000000000000000000000000013288a3413d7e7edebd118463d5eea9f9ae2e10f51965480f9b5c244b05775d04079a1dc75ba0885aaa9e2e4bae1ac750000000000000000000000000000000005b766ad112b8d69f1a28079688942ea146f8f31616611909f539a57c58ec5e857da9fce415d683c1c6dcb5e74da9d17000000000000000000000000000000000907e5c3c83d3f12a68d6bf812e310f5a04f1417094301fab7d4f41007b9d01fc1bfbf739dceddef756417367ed5b1d0298b5f6b43074b8f0807086b03f5028709209310474c35c7ee232eec8579147c00000000000000000000000000000000090be6ce5ed09e45a6fd9ea3a9223fe43a835141c1c29d6b386e085846869f9c5798b80c3bddec8bc15171906dd417dc0000000000000000000000000000000019bdf67eb16f2708ca55fd20af8deca66e2ae270b2f2f9736fcf49dbdf7cee034cc956f6fb799f0e87c12f283a11448e00000000000000000000000000000000124a69c723cbd366d52919a72dfceb7e4cd9ca5b5cef1784bfad3f125b11d810328ea1c849602536af500261aa684f5b000000000000000000000000000000000bbf05318ffd81495efa4f4c271c8b1c669041a6446501788f49b8739a934f09de9d976fe7300b0ae861be567d35c992177bfb0218ecd8cdbc6dd9484e74e41be6971ec2911bacc8b53b9b4b8c70e5730000000000000000000000000000000010833a3e7329ad40c1a8cef296b015f6ac6542c612038ce00f13a99f673783cb7eeb14796485c168d21cc169065d051c000000000000000000000000000000000d3b1416b23453b893c92a6c7850cdc0e4a395459140391b1dce11055da10fb68f318c5561e1c12d991a28f3f544a5230000000000000000000000000000000014721dc58eada80f2d0574fb4e2c1c94c45fbd90c2d2fd666fd618a96f4736a5ecf34cab34fcbdcb19b6cf7b44098922000000000000000000000000000000001905d34029bf84617a956d1edae090853dc1b622f560c5289251447ab6bcea5700bdd80d6ffb2dc12fdf3b0267e74543cac52219796226385aebf9e85f5f179362d4149c33582a97b7d2aeb05a8e6a99000000000000000000000000000000000b4d380f4f4eb976e6121b933be8418c536f85994491b0b93695d50473615e41547ead326bab795d4d59524a61d607cb00000000000000000000000000000000104b7f4058c9b355d38908d715c311a53169b42d2434de0876f1c4ffce1c39603c4876b33fe3076528be15fe42849d3e0000000000000000000000000000000017e2fd647e7739366ebb606e8a326daa5c03cd2b726cc4cec7747cb3468419f1907126d7cba98bbbc659478ce3afee7700000000000000000000000000000000183be0a976dbb3b5385b544c194e111729c7a8d5aa98eba3fe1c0a5b69b5fe6e5d0164e96398cbc61eba5b86d91b3c94e03afb2efea85fcd035cb4ba09977b2e1c84a0d98edf88e9f8d2c4f116d0f50300000000000000000000000000000000023bc7eab817fcb9982cdac242cb6cc0ee1779bcefaecf144dbe57d5ae2b2ebfe9088f39f416a56de4b4dc04d4bbce7a000000000000000000000000000000001318e728c271746905788dd8f5ab22a3a10edce3fa063438e54ebadba22c29e461b2ed78a95a8f26a65b47022291b8df0000000000000000000000000000000010aab000b9c5de56623f18861b343ffa80da5ed4ae0d7767b7ed791bf3dd507fe7286447b6a07ea0fa12c19f2e4d8e8d000000000000000000000000000000000770e2909b5795a08d98dc66389655b1718e70b93c5bc6d805c3945cb5fc0092a5b390e6497b550988c28c58b6e016a3804dec43760dab29c161b8f4bddc52379a17f3168f684267cfbbc3505e32d5f1000000000000000000000000000000001259a4e36f5bce7d5f97184948d57fccd458cf7f2ae0c9e174f537bece01d744fef544447959cb73a678fe2c378ce3c900000000000000000000000000000000131aa575b2b94232e06879fa1f6f145a0bf5dd12456b698f731a72bc587e6def5054b3b2afb6dbbfc34fa5249dc673860000000000000000000000000000000011d64b923596c316b097a0752043efad8b61fbe068c58bec7a6766d9bc90ed965b3419dde3b96679426f72184adb8931000000000000000000000000000000001653af784cbad5a804e3f72716bb51e0c733014d587952c47395f953828566cbd7da811a3da1d48681998d569db00a7bed2d3daf616df3f0061f58c925e9dfbbf6e9cbfd4b0b3896a596919fb3d243db00000000000000000000000000000000077a9ab830f7683b7fb46676df09f72d773b65286c5f5ea86623306e5de51e63851c18d192c4c3b20af582bb7f017ff70000000000000000000000000000000016dc185f4158e249939541d35ae8230fd749988b9174c40c40b8c932aec625a7e94beaef9a07f492445d4675a01b7453000000000000000000000000000000000c107a895bfb45d33136db6251c76dc0461a235fa5d1ba7a5d216bfebe15691261b46c9816315c146becc328acb6b8c7000000000000000000000000000000001151cba240678efe61e3a36e169e314b3610e9d4df6650507f53ccf635d8f1277a80d86baa85a2d4c7e2af73934a7299e16797ed90581fd8c3cef1f30abaed10997f13461374ea649b29101959fd506400000000000000000000000000000000090a1ee6c611980e0421b72a122cb39257dc38d1e74ee41b809ad76e440fe307cf45e79afddd8d40b94382d48cdd4c450000000000000000000000000000000010f2e6e610eec7b7c2b95c1510af1af342ac19fd3b01dddf81b8961ead2cc57a8eca36c2f5747238eded5914e484c52e000000000000000000000000000000000acce0789cfff975b09d687ef79535c536f3b799157d3ff731915ea5b323ddd9f6f4750dc8e00a879d4e516bce8cb3e40000000000000000000000000000000008d8203dd13aee7363f6b10a9e1ae9b713bbc8b8fb2c56f05fa71e8d69ea571384d150e8fd01e855b1b0054fe7967a052f9f29432638c033ca84422b12ca80ac4ae85fa30ff56c913c5737aeb2c84d04000000000000000000000000000000000b332430c518d7dcd120b346440e5b6b48900b5c3656d84840823a96e5bf002816d583a989898cad9e09ba978ebc58a40000000000000000000000000000000004197b43877b833de7f69cc1a43ad8d6d3544cd10d42336d4b19a187f31337a37b10cbf48e72b77e4d8e1a1da68e5e4c0000000000000000000000000000000008887d5dd08f45034584f40a2a68254baf2104f9d6a4c2637ef79c5ff2503c246f7adc36559758a0c07533b66c3637d40000000000000000000000000000000009343819dec1d4569683de4596621c19785d5ed14ba13e57d94b1b1a108aa62cc8c55c58dfa18c06883ce50cc1364b95e6f1e5df7ff90c4a4fb9a071c0caf3a3997569538ab9837ed41d9d0a8d7305370000000000000000000000000000000003fc7f9a0804e7f1664f8cd3ca67b70ba128529a611c24214fd09674072a6b8d652ccd37bf5d4611424688213a41cb3100000000000000000000000000000000137a869cd7bde696035bd9353662e0d37d2aa0731ae55357df3bc43536b9210f360324cbb3670362cf9ef607b1919bca00000000000000000000000000000000045d9d39c04e257fcd912c54e57c86d2d4304e6a7cb95a83d2bff07964d0a5dd8b4e42bdb91a8b245e512395e6749f1f00000000000000000000000000000000120e5e4b04b8a744757812fc331e7c98b35624faa1cbabfc1470e4c0804248bfb0c53a484107a677a7d3f0d2b533e7530cf3283195707c30880e50ff5ef605b561c3c3c354fbe8108f93b36f212f9ef5,0000000000000000000000000000000002bed414afe9c7a630441e7b163280be10e502cf877e94b6521d14baca0087c5dcdfa39ff4a51c8376d99855e1e6f36a000000000000000000000000000000000dcd54727a7729408e682c6e213005687ed51fa7935c522312793fc58cdb273eec9c61cd8b056a26619fc8dc006b066800000000000000000000000000000000137286f4086763e6ccd5ee82d3bda712b26814a17c6a71006a3e6dbdd919e469bd0e744bcdb2074679e78a1e7d56ee7d0000000000000000000000000000000012d75de1310199c0e556d61d6c0395b406afba0f13bfb37486c05d66b446809e8b1a024e8fd2c55f1b82cf2aed99a5e1,293920, -0000000000000000000000000000000008b83142b22f6d6496cad0dea23c71355e7c5d98659580b5ee6e97eaccb9fbe523f7e3b925abbca3a38f67426f3fb35f00000000000000000000000000000000035f655a1b2d22ea21cf0081e78d7140bad08c4e66dd45230a113ff3b7a77e39f0f1a72991f85e2b00ff58b27d5cb54900000000000000000000000000000000105d04e38243ef1ad2f734a3c97e91506c5a7c5d95e9b8771b7fded8908f1be933a81a5769044b633d501c0df7b5d7fd000000000000000000000000000000000e670ae4af94d0df34a7f2d7cfbfcefa6eebcf2a6b2dc5b82068b023fe02ce8a279e1bb96d905ad4f2ffbd8214e47d702063b046a71c2674e35466657a85d8e02253b42517b033619e31a536659172120000000000000000000000000000000009051f1e636309016c5433cc7eb019c7dbb75b3a4a5b27f6927de08fdd9577e8eb9e12919157ed35bfd6607be7fc4de5000000000000000000000000000000001953b7a33695ede6d0792eba85567aa5052b8a58c1bdc94ee82b5001893c6b996d3e8f7af8b8effd6cf50656d8b85554000000000000000000000000000000000a2f769f00679b610bbe212c2f8045e7579a96dc6bff80899eb7715aabb1afe79421ad5000f2c7b85d4e0904e335ddfa000000000000000000000000000000000ec962a3d00fac14d05774adc49bbabaf46ae78325083c0020587fb85eb234387aaf6506f503fa988df8e9ecafb4a59992fa325cd07502c6576dfb93ee952fedb304022656597bf3bb03a2bbc471b32a0000000000000000000000000000000006823056a4da801cae430fb9e3a8663fc8f46bb6c180b743b7f9c7c7e3287f3feb1aad4be0e98409c74ff58004f8732e0000000000000000000000000000000015f7a3f692d55252fa5af5ec952f581b796d54089f13971fce2ef9062173664816dd9f37174294ed78681d8c8c5a9cd800000000000000000000000000000000154743c76f7de590a31cb96d46a0ec0fa88008b7d6684bd8f6fdaec70722afff7b6e88c1f0fb048714fb1072d30780e60000000000000000000000000000000006f3191946d0e7c1307a1a0d1ea9a26db195ec98ad88f9b8f08a03a3d48bbff1fa53ffc920f7db5ebd4c65911392bb834484e688799c3f0a3bbe00cec7322fba6245570685cd7df0d744473b72f03df8000000000000000000000000000000000355018079cd02dfcca15fbd2934a8e47c5ee89e679663488499ddd4abdaba7679fb1c9d2102317cf2798c47aff1ceec000000000000000000000000000000000c417d489a224fbba9999300eb65a23749194bf5302fdfaa33ff7daeb8d896e387e56600233038d5c5eb59f644a99b6a000000000000000000000000000000000f5a62e9d711293d4373bec1bc2637802938eb789c828939e6c42f10062ec171ac6110261165bd179206d649713f6fe3000000000000000000000000000000000b11f9fd0ef8dcac2e21ef09846ffe9f5a624ec246e31393b39082a47354fc9523dbd247f0059b6cc740d7a387b137f0fae2ef61a024e4d8c4ae277f6b1d834193df655ffb315da67afa4ee4ddcb7fbd000000000000000000000000000000000fbb5521cdb9c3a69d58e5c9cd7e4a50bf5469bda2603f5119f3209669eb3e374d700f851b0c7ac5ee3cc9de79e6a7ec00000000000000000000000000000000131ccc37581e64f6f9fdf675b9b63ceb67d9d5844bf512166f39b5bb09d8e031437c06b0ca01caae7ad6d8c9bbb9fd67000000000000000000000000000000000531cb0557fa18ef054dbff2e7e994f1af08aaea7557602a26fd6ff539ab3c0a73f1fe841177012dabed4a1223ffb5a7000000000000000000000000000000000a180e7a345d2b635be92888934608e8b6c17384c48c560f4cb9809ff995f8e70d83cd4cf0e96c458fc414e1275d2a993168a1007abd42bc398e317484149f2fa61174243fd1748deec6cc75e7c720a200000000000000000000000000000000125c83184f63dee35ffd2c0c7dad9010cd6a9735675099f24b465554ab3db727ee76b5b7ea603ead78795d33e37689a400000000000000000000000000000000141bdf7e270dcd356993327cdb5dabe38a5c5a9b53470d9a4aafc041c46fe8bc841089e337469bddab5d4f7fd3d6ccbd000000000000000000000000000000000f9613f6d05f38e3073f14d0c2557101a4864a7d6d0b5a2b931d0613f020adb99a1ab2037a39fea6e99fcfb47929827a00000000000000000000000000000000192d812e05a17d22c60b78c53fabcc55a0eef3656f8e84132faf16686ee18ab4d35767db9a384d42f392c40c7b0fe1c0f1525bba87baee35023d0976b4a2d87524ba74158f000e5501c6d06aed04adda000000000000000000000000000000000b6e1960e82586de19ffcf29a8c5f16cf2fcf5286bf42febef832767919abddc655a0d1bfa240cac8fdfaed5a1e8f389000000000000000000000000000000000fc1598454caf04414f1930f711d762f0d72f5cdc7a4053c92b916c742b00dd0f107aee111976c1b1218c4577deeb006000000000000000000000000000000000455d6e9e9bb848e0868c9d725edca1f50b279d0acef8c597927eda72763e3702f46b216919ac36b080b4865249fd961000000000000000000000000000000000174463cc7804796b4a6d8ff28d2e8cfd8361b2e38f368de30166cf3c20c474ea0a1e8d94749fc3e6468924a7d1369e62d3d7c014416f33724acaa46412348d350f93d334588d39c77dc0b8ffcb4cb1d00000000000000000000000000000000144e4b615ddb871bae85484c308423adceb5de387d0c7ffffdd2211b4ea28788eba9bfae96ffc46781e6d6343e2f501b000000000000000000000000000000000046e39cf43fd707ddc4b7ce9a8a22a2aa1e55aa63cae1eb23082f7b4b5dce49f32d2ff887b5108b40f98062c02d5613000000000000000000000000000000000b75b5460db2baca86528569b47209b5ac24930e2545cc6aa08c401a87ef2c4e233de537e5a857e533d0ba0981b24d7c000000000000000000000000000000000018f53b83072fe7daab226c831a89da63a0930ea86e301c97e639d0ee1609e298e2789d1a347bdb4afcd355fffd16d053bfbb1670b7045b6df689871d5d012dc93e8be65faa4a98a51db8501a4b7677000000000000000000000000000000000185b296e9c7209a9abcc3194b46be9a545666527ec9b0634a3e3be579447cb52330174c19e40e1667124552392a7a0c00000000000000000000000000000000158a053c788e5b914fcdcf1aebb4e21cc8bbfbcc20c4d692256b2ae48149f6644e1578f98d58b3e73d9768d0e7df643b000000000000000000000000000000001318ff4150bebd8fa612f4e84f89151d5c56c272969bc1f31a3c1fcbd8ded0e298914e98e1ca48248e9023cd12db0fd300000000000000000000000000000000076555254f382707fdb7419772a4978808a7409f59d1dbb8c9e648372e19c44573f5ce1888a2b570a83afc20e698ee44f944ee8d294d189226a6cff17456e2201d17d4dfcb78f58f8501870377a6e431000000000000000000000000000000000f4395e3f2e301ee3e18df3c23cdd142716c7fcfc23caed924f0561795948b0bfbed948a6f7c415ca615ee0ba4d5145c00000000000000000000000000000000176ad308c7fe8c3a1aa350fa82b8f8ec638f77bc703afe1042a6da22e5385cd8473ad789247f205214c9980532b12c7100000000000000000000000000000000092b0ec86c511992c66f320ad46c9d6d7c82df118a9ab2ce1f2c5611ff4e5cdc9193a39c3fc95f18ddf96e139688b00f000000000000000000000000000000000b4f671e334b7f22bd8d89d8c4eb8a52b04bbd4dd1259cc9caa1872093736680618930f3a469b3af4a00cb6e44b573f27de53613b7a31583ccb214726482b770029c0ed42f9528fa74da7d2d1dd915e100000000000000000000000000000000123b64561ebfe085238220eb1428b3a203acb01846d1e4428f3759db6cff4ed3c1b9d436706f28b77e3b92e2e39ecb41000000000000000000000000000000000ccdf1973693e4b43b6133563986f6c96e2b924895c813f8acdd0f39585e4ee95ef26c0d9d51d6ef88bb62305e51594d000000000000000000000000000000000f51693bd44b12188131ca84801bfee0ca853640c0a8d5b20123c97b369c98299ac04beeb27d75946cc6f45f8a07b5fd000000000000000000000000000000000804c6597810d2c75de94484873a67eae258fcc9577bafa778e13d4814ce099a5684b1cc94e0df5a59acc7b19328fb8bb0a9750cdfe0910c544668bc9b11ecdedf1b757ff69b61fcc838c502c2911bbc0000000000000000000000000000000009b02eea05c78a24adfb0187defb6810116e21894d8782605c1d590f8bdc10723bf71a1e5e5004b181504ac2deb142cb0000000000000000000000000000000015882389195128e20e50ec4f8d278e8b8791e362341be93c475064d640e1f8bb1c92a6c777d666f8644d471409bb9aa90000000000000000000000000000000000d89295f845f989e0fbc6e86e97400b08e39b2968fe6c9a141d1e92ec9c838a3d8e1ada5e44bb08189a5d514ebfc2f5000000000000000000000000000000000dea05d8e6ab50b8f8dd9632337948a60568724d5a03c7914e4a03e2af572dd8153effef1a7d5c2cb27765ef2c17bc5b4aadecb1111ff43894123648eea9e57685dcb7a25553233a374479c24f2f8899000000000000000000000000000000000bacd14447ede6af0e92e19b54c4f5b6ebfb94207efec3e9f385a4c84a7d670514ecbc28ab686b383e239ae7f9bd673d000000000000000000000000000000001698bc92d146049174b843dac8c5dadcee12d1d503b2d0e46ee68139dd43d3aa797fd5bd06e2b214cc9ae3647c98394a0000000000000000000000000000000018d20cf6c84446cadfa1a26192a04e16d2b2a053705a89abc51bfbfa35c2b03cd58021ad95a35364ae1e2da5d233208300000000000000000000000000000000113268e360006294fa0203ce58cbfd05d05fb625e1f9474c96c89c0ec1ea80fe834030592c2f1c182ef8a3d5c32caf71adde66cf749daf69a30f41ca00d251f7f1e93b0e7f916a1ba6b994d946b12ca0000000000000000000000000000000001727b6bfa9c601fe84a65c54f556887c4538cb5383a288156fec87420ae7f15da395886e1ac0e10b8fbbae8bf040f4ba0000000000000000000000000000000012127cdf02ada71f28ed036a417971b87fe443b8c65b7739795dc7067082cbc9f06f7bf10c709969281cd072490c06fb00000000000000000000000000000000134f1fa1d277d01e2811c118cf10e2de6324e2ba14efcf717a03c1a10dca0862ebde0f6328839da63d7d85f573e8501f000000000000000000000000000000000d20a036b715d18ac9e2dbe009dd0063a4b13b3ec6fd060a64c4ad2b98e05e069060179530410d154caa575d504c63b7b2f9b44c73a1a6dfba6462e1202166b63727f45dc3b8b3b73b5d06459a1beec20000000000000000000000000000000000bd5375e7f98d3972b93420a39fd6c31da86d0d9349ac3774bbef15c2240437cc0761b2f1245e805d2538cbca6f778600000000000000000000000000000000100232139641c8cd5bdaa75b77e1e1c8e33b3f9554e2ae00ec6315b82cc00a6a70d576d744e68938a299ee2b451558250000000000000000000000000000000004224691faacb007bde3e37db6c7486aa5d3b4259a24c8b7653238e7522604ef4ffc1eb3cecf719a1b7f52ff00c34399000000000000000000000000000000001156ceaccfe0396374c6dec5adb39f14b6f08a32b88ef7499756f5cc324a9f1553bf5dc106a97469f2c49be5d563e1100cdc89e668f7cbd53a2ef6597a48b93d7d9a78950d4f821f3935edf72649e0000000000000000000000000000000000010a549108e77f0ddeacdc795517ccdcb357f909264457cab22fac2b982d10064756d66d0e48af02a59f58eeb1e8ba14b000000000000000000000000000000000c68703ef1c1e93c78faebc5f7ccc69e39046fe8af92e12469e9fd6baee62a2e8cc06fbbb3def81ae5cc57f488fd9c9100000000000000000000000000000000064ffb6aeeed432629242c3843f8cbea5bf7fe78585763926c5c45dc3cb4d1c79b3715506d7cda18c531ef890b22a1f7000000000000000000000000000000000e0eeb69f28a552cc6563f5fdc9919423c4358a2b70ccd56b048c22111454f67107513cda2a5aa0efd2af25dc74a1c47e23b377ed80bc90a4645df09e825509eebf57f59d7a2aa1b9121ace80926ccf7,000000000000000000000000000000000b1913c672760f98fc6d4e96ad1ef200f99add6f233b17291036e187ac6692ab0a29a4083dcf86a532dd06efb3d9b8c6000000000000000000000000000000000323b703abed59a9824f34d97851546a5e78441accea4e3a933b9270f92a9dd1aa056858ebd1739659018a0ca13b96e0000000000000000000000000000000001603cb3ed75c09ae5da6b94eea6017dac0c40b17d9aa8b65b78f2ba17de051bf3f21109d9afb214d260a09391f5526c10000000000000000000000000000000019f3bcdb8f16d9a2bd11e3f9491266780aa9110f447e19f12f7a2d62dc4f2c7b5fa18e13792007f5f361e20656c8ffdb,293920, -000000000000000000000000000000000b7d06c16c77a57b5ed74d829ad6acd665e73d20f1d9285ebba67b60c0d1571e1b02cabe5dea44499ce6d40a83288aac0000000000000000000000000000000007e6ae768ee3d149c7130022f9883ed51e4fcf68c91327ac1fe85aec8083aa61a37e9afc25d3352e144aaf888f264ab20000000000000000000000000000000016f2423478e0388e8495a898c23d63a0929a2ee7cf848034e4c1adad3460c7070caf47631eb930678d3c85aaba044dae000000000000000000000000000000001587e63cdf50d6e0b6b3d7652ad0a0a2497e70259d73432831781641d3a76db4ac7cff1bef165fd8ba29200d7320e43475888762fd1de395fa80b6d8a751f280eca568de2947e953feac343fd6bd592d000000000000000000000000000000001181bebe3256dd6ed754b8a1d77ac07e9a341789b3f4a4988599c7c60a36f1e95d3e3cec52c54c0f0abe312ac358c28700000000000000000000000000000000189d224b2904bd45cd1e8fa72570a1e35c94680d03d30292892462664f9d7aca3cc45ecc0773e66a10248df28ba9a9a1000000000000000000000000000000000f654f4c8b02a891e14fccbd5a96228afaaf79ed8306c7c1267715bc934e5f2568ea06de2bcdc2a55ef708689d90108c000000000000000000000000000000000c0a413f16e1aab8b91a87e7027f067ffe7de65097da37d67f604a184c7e7a7af6fe59ced8c03fa32ab036862868b35018ce7941da132adec1eee8d81facdb5012a15ddfe0cd6751ebbf66ce6c495043000000000000000000000000000000000dc972d55b7e68f97191d988ae7be5f5301bce5c654b323d4c17bf6e070f7227c0789ee38af3ccc07b04f0793090c6130000000000000000000000000000000016288c405bb42b4e71d12fd0a798cfccc7d33aba0500f939f5fedbd0e071166169d3072befcc5549cc6963b6dacbef4100000000000000000000000000000000171ea4f6607d6efc875cd9cff203bc62eb83bdc05c07f702143c23ab2770f50f42738f748e6bb3bb5d6f51f40fea1d910000000000000000000000000000000000fb729cc9716bf2e9e30a598ee7c4281163b287422ab66b414da85b0b960102991c24cd023791e4241bda5b0f6ddd3424a0497c642dce3937d883ee25b0ea577c729366c7d67e2e5ff1ccde3b19a5dc0000000000000000000000000000000005720bcbc598c4eda697406dbb390c2aaf4bc22c794b4b664e9b85b7c2079b90f7678e69496a4a5cd3b46580b90a7a30000000000000000000000000000000001159788c3edf619cc5e6f77c4aeb4860764d46afac4cdce54cade63155040c631eed65c2fa11b9cdff14847950cddc2e000000000000000000000000000000000d61bf02587e2c61544ae8a98b4c742c26a3d6ca49c6ae1b19a9d69c7f8eca43cefd555c973145566f8332902217cec3000000000000000000000000000000000cc0da96623432a2c170f07a3aad2844c1c2aab9d1bb5d2183928c818e681c66cb3767be372be4ae65fa40bf5483258ce4e0ad0d478ccf5381470a3fc9b882639dde4a0147c7c82e50bb93431b07a1350000000000000000000000000000000016efffb5d4ecbd01567c1e6099c0f06644d4312c599579b4cb780fccc8a425f3d1263a5f0c957dda4508202a354162f600000000000000000000000000000000115686a37624ffa8272ec7dedb7a632ac568245918ed91be6c9116e0fde290c26b5291e5f57ba6a779750685b0f126ba000000000000000000000000000000001852662b92fb49b2f0359255db8a7a2d20bd37705b7994cef1eb8e318aed70fc37bb7af9fc0c32ab3efa8c0afad640570000000000000000000000000000000017a691c08724ccf0e668f2f4eeda692e9ac21385fea243dc62c37ca73421eaf51c3a60771da3fb3e3cb578de37d2d45d38573db9346a3c8de41af54048cc51a0edcb86f36372859d0d794f7560c8525b0000000000000000000000000000000006fe4276e8f2e23127853eb929ee4e0c6ec4a190d46ac222dceb995c2e1d8fc9f822806a877e6cf87cf579cb2862c25c00000000000000000000000000000000044dc671bcd516cf03ad98ccc55266688856a4e4e5a59d6a6bb93e9ca77c596b5ecd2db6f3cc6377a0086c53ceed5487000000000000000000000000000000000c3ca83688d20519423b2b5547afcccbfaaa088a90523272c7cdc9a9b0397340350f2a5ced2a8153d69c81cd79732bce00000000000000000000000000000000069916c468f22bad174522d7fb94b4b7d2a664743b4689daa5423f470014152387a29425642b50f9e50fb679ddafdafa02257ed12262d00e78bde574a9ebd2664484a446c03fe8cbb855bf92e56bc1630000000000000000000000000000000001fd452b8685b0806545e09783947551bc5f6446c9d15d079a8968a448a6fd6f8b7e91974b71a4b2c50954be299c4885000000000000000000000000000000000f28bdab0b0fd3e05d08ee2c51f1bc0d30380e3a7aa18d6e04b47018d6a8d2b35a8f06df3866ccb95ffbd9c5333ca94c00000000000000000000000000000000035f3aa1cff72df0bb10f1a6f8414aa2ad0289cd15f56d84061a7cc70562f1f12304c402c388e48dd3f34082aaf79eef00000000000000000000000000000000034730e3ad7a3373b97279a00dc7a053aadd088557e0da61b9aa132c5b402fd9aef73cc45dc1cb7f2076cb2ff27ae2fc76b9d21a3da0359a377c11a6d0a18bce7ea81d4128dc1e7618e6c35b9149d5c80000000000000000000000000000000009c91d800cb1d52501520b3625dd4c20173684bad8742c7ac4b35c0ce028556b6529de5cb7541e3c146b13f58ccae57800000000000000000000000000000000124259d345bf2f8c16215be4b6b7922f4e2d6b32f91c4b1c4f1d4974918fa9e6fcf10e46f0c0b55e2a7210d1a5336eed00000000000000000000000000000000072e6231244ed14aa0f5de06e2f953371995a567684b00e459113380c1434a8faaab8b28a0280336ae35bf1f90f1d4d10000000000000000000000000000000010289a63e0e5f1f35b7af22137e117a85df27874ba15df39b7c26801c169667a3afe9a14663d7ac0c2956f4eb70cf11fc9cd895d5d1ae0ae704e240c10d8ed4a01b319598d7885f7c3fffcd9b491f5fd000000000000000000000000000000000d0f22a9bcda47ffcd034618c15daebad8160f9ab6b3148f1cacb32e713df2ef19f706f14131f8ab1181b7ef7598e3e4000000000000000000000000000000001680314cd79fec583c8bc0842e1750b1318f94aa7700c6662aabd4c592ca61ad51a6876b686ac3fe3f508cb40192c31c000000000000000000000000000000000a172bd8e49637fd9eb611b590c68bda707931e403db35cde1c10bb74c389ed725aab54dcd7048285352c56c8bc5fd920000000000000000000000000000000012589683ff3f85ecb006c5c435ca7bfd9d5a6fd06eb625bcbcb18577cdef610d912e783f3986c965710269b1ff79ba972467604875028997efdf5139180a8d530a1e9f18c62ddac7753cc370bf25254b0000000000000000000000000000000009720c2b3a0658a4aba8e76e196a558bd155ff550b3e41bb5b43e7c5946bad803b1de64e342956a11627e7f24f69fef7000000000000000000000000000000000decf2262e8369d6a2b1ce07fdd257abe1c7610084ae2f347640c0cdb98c7cfa732dc609c18b7b6a51b47ebe4b07a586000000000000000000000000000000000e8a0158702ff6d6c3a7ed9fbc774bc329681130840d86ca3f26cf6642cb49e5f14ad95fff1c94151457b1d5a142bb5900000000000000000000000000000000035ae66137629e95539e09ee99b001d5b9a6ede79727d7deedcbeb5acf081cd05ad469ab06c265a5224fd5236db160b62f47637b64d28fb4facc31d5bed34b22e7b270431f54a689cd0fabd205e001ae000000000000000000000000000000000413d82d0b02ca706f0266051445c04f3ac594ad82e2f1fb4e8e0cf23a6c1087c29383238ad3677f170e99259e2fe93e00000000000000000000000000000000070af21f84895c0193f0b8174cb20b11f45c845a8d782b1f58182b149362e1368ba076ba702185fc54b5da94c3172f5500000000000000000000000000000000182e124ca29d66f9f6c370f6065f60928b6a8f445a74800d59209219add6cab0d1b79702c31d60e61cf56874a4eb6717000000000000000000000000000000000b94b733f76067a102cce9659292f31f3df2cf2770e3a83c1524536e29d0a84ea5c4883cb4e849830384dc7e157d8715474c3ac61d4fbece967fbd0599c9a92c3fe0e53899861f044308b0ea8df137880000000000000000000000000000000004b2feedd5badbbdff6fd0f33a4bee17b38cc8967fc72206246c349e1017ed0407fe08e0cd9208fa9e4e21eca4cfbc2a000000000000000000000000000000000df0d74d5cc17ea94457c0ee26ef24071700a0fd6bfc762e3ec69b8f1c096887f679e312f07cce8340686eb2716c9a96000000000000000000000000000000001878edbfff2efc5af64aa9a23589a52d63749b7ab2970f256874fe0cc15091c4511050b0a243d421dc6536f19b5977cb0000000000000000000000000000000015951da3b20494a266e4d014d0ec70fef4586c8656baf536a0ea9a48dfa041624e8154989a2fb106189217ca979ddbe8eaf9da65e0e1752a982601e9a070a7cc77d5007eb641fffbb78d2a1b02dcffec000000000000000000000000000000000657fdf40c829719db134acd6c2a9ff904681b1869f28512cbe2a64d93e5b62114a73bdc5260ad9a1f24a3ff191b7a3e0000000000000000000000000000000004e77bf63eb9c4741028dffd0591b4f525d533b455d35e51cd86c7884d63419a162b145752bde188d2a622251c087f870000000000000000000000000000000016cf02af01fa6750b4d862f0cdd5a87a79da7c3fbedb0fa356ef2e7419e25b3a2bc8cbfa97463d463d0ab349efaa3f2b000000000000000000000000000000000ea4468fe6a85d36ae990d0ba959ae050756805c4c769c829de475a2990ef1c46de20d5b466543978faae0f6045023e85158bfe535fbc342e31f32ab4723c0d9fe95a2c64cc4e59bd41d13a08ac811780000000000000000000000000000000018d42a2df8ca475be6bdc468a82c313599239b974ec3d27e8b8c534aa4d6b85d4ee9aceb15c38b3bade2bb1706a2c2cc000000000000000000000000000000000124d5dc60527faf48f5e9574308f8a328b410de1cb49f2cc6f76b8a1f2707f2d1a94bcbca0a97bc38f24545a8013b250000000000000000000000000000000018b690b3d1e3b22946a91ace004e1d8f92eb5beb284eb05b52ac5ba003d7bc387540d33d088a02711522e3aef7f74f4300000000000000000000000000000000103080d8bb379d961da06bc4c148cb5b056ae115b3a0e33f0a8c99a7fb7b7ceda35d3902e0733156d354dd0240e4bcabd66f5a8f37a7c6a32088897abfaf5a98bd4722c411bf4b52f0f5b5648e89df29000000000000000000000000000000000f4d068354cb5b51e5a86163978386533f8f9b6e388c5e75f7d9ff5e1ab6d1637717d251f2b723b7d683e26a274d610c00000000000000000000000000000000001ec5a0d408c55f247d62ffef172ef26e45c41029f1d04e36f0dbb4fe8af414b0f7fe7ec0cfda66a2855b58592486fc0000000000000000000000000000000000cb1b68045076f457746621cd415d743701bf3ecae8d52dd5582c3e0bfb38e6cf2651a5ebdf521afb1ec5b8066444210000000000000000000000000000000010f5672f813470378fa806abdff90edeb0239b00d85ff23a3fc6798779f46d6b43071d66f7742897a4e53ebf6c7dae719acdd24190589ae7823a42e8b59598eca12bf13b97aa9a0eec17f5f79a01e8df000000000000000000000000000000001422fbaf1bc2908be5900968af61ffa7b3af46e7250e4663ff321f42e2db057bcfb2106c433a9eef8fe20f7138b71d280000000000000000000000000000000002176e68cdb0ada2d7baea437bec8754ea293d14afb85a811f7a5d740d645a53e511b5605445b110174ceb5e6720e736000000000000000000000000000000000a69e992b6f4f7eaad2682cf9ac2e58faee9b3341e852543c2aafbff390ae067a641b2b5693319618fde413fdc64d6c10000000000000000000000000000000009440317af8f5c753b5de4648b06212256a39b7fb03678f1913b0a3d402a50e74e2da5d29c211cdf0b292c132759c36d0291be87a213b0a24c92df5ce42381ca378dc4b9aeb4cb9b6918263bea827bf8,000000000000000000000000000000000fa31d16d9625200c13a415fd61b7552646c62fb8db307e92c1ac3d2acc92336765a1db42407ab0f774ccf01291b9ee800000000000000000000000000000000156a77678873dcbe4832b9fc7f516eabc1a10f4a6576cfb15765cdf999a771a6a6d41021897dd783e9beb2db722a6fa2000000000000000000000000000000000ee4599a6ca9642cb4cf38f5f2af23271cc8c5bc6e2cf6bad572b618bff9f8677837104b93ca8942843fd5db5c30dcdf00000000000000000000000000000000138986714a4053618e66a85835d105f4aa2ef38ad18e34b2ee7ae30a4282f7e543c80c94bd12c244506e7fcba25f4c1b,293920, -00000000000000000000000000000000083c515ef8509b12ab85ad7d0a816d986bcdefc14778efcb3bf7c2ab61991849f279ae6a9f5342880837c0d0f4a4eba700000000000000000000000000000000020cf5196b5d567fc429cb9ced7b55e4925e18c914caae216a736886a8d886c4bdf6d704bbd0ceebdc1975ef530c665a000000000000000000000000000000000f3d0a217c224434604d63cef559eed3864d2da62ac00d49fab8c2c6e22c688496adc30c8d591e21bc0be404b62083c20000000000000000000000000000000003d0bf7f25bab0bf2c768b44e10a6022650f7d5b7d568d502b9d0b28209ee69b1d952ed848572d3e966e8771c20becc4b14c6a38cc998df3583228080ea10f215a6e6a4b02ddb6d43e8f459d494a1ec1000000000000000000000000000000000cc4c4b7eb7e358d4133b65e635fc13b8a92229706a6dc5867171a60a99a8e343045a794c368f1133ae6cd2788c3a7db0000000000000000000000000000000019508aa39fda9c3efced287d2571db97045f8b7b0c7a9c9d51796aa8017fc0e5abb8fc994700dd5c9f755edb518e096600000000000000000000000000000000049f68b0ac142715cfb385161ee70e453f0e24e2e93f3f96c3d69447f3a28b180fe76989427b2e392c7ff939011e04ab0000000000000000000000000000000004903c0f8e0757dfd3f5edb4f54a0e292df15ff70757df7b0b04c99f590a3dd13c6ce7bbabf3e14daf9f3ec60e2379aafee8614394c8109338432ec72f2d9babba06f1e7b826b0f2558c3247c923b23500000000000000000000000000000000041128064ac768664f076116247e0f8a00adaaa824cd6fff33bf524d0c76e61203408ac13b294aa41f5c462cd42d3cec0000000000000000000000000000000005e150c27979ff1cbe307511816be900648957624caed1f08d88347061cd783179c615258fcf3619bc4bfa53d2513c610000000000000000000000000000000009d2b3d97d29386b93d7af014ea8f1cfe2c1db5a9aa0c17e8430b0fcde974a4e7b8b42ef041e9a7b1a8aecb97cefb52e000000000000000000000000000000000d86096ebd88b2cdaf5cda1e9ca6b7f12ed5def629354b0570eb084bc7139cf20bb8ebe4438f87937b8b554e2201344c28728d06cd90050e44a827b44f14ea35e83c9b58ce4c3a7a45aed6f31c94fb960000000000000000000000000000000018d677cd67e96b10b671d2ed9234d7708042ddfe6fb804d2e9371a80ad167004f9d6b92d26b3d3af34ab7caa0e03964e000000000000000000000000000000000e34a6c85187d328eb33c2d5b2ca96b5210d47a779ab810dcc380dcb7e6b3c334ac8fccd7354aa9108136e4f6dd4ea0a0000000000000000000000000000000000ab8f7274ee3fce1511c58661625c766ffb0ac68bdb835a948b09b7510bb573d49000000e3d3cea772bd71d79681e1800000000000000000000000000000000135ca42f2103905748a1c416d82170f7d24b49ff3f859d6cb7493cf89bbae0217529a9edc835be1f9890ce105877af630fda665c40d1da93b1f132070e0b7c8c2c0ea0e66993b5a3d7419a33d118d25f0000000000000000000000000000000007884edaacca499491580c8c7194c0d60ac6eba95f7a81f63742451c8ed21a223ca545d5cc1e648b9d2dd05016b4fea20000000000000000000000000000000014c78d5d1a93760096bf6da73bb41631e94d6a1b251ed0be7bda93e4c50568420bd4d49e4a46e5be4bb204cdb6b0ad5000000000000000000000000000000000128a860c23a183c5bdd18b4a1853cb53475f1a893420bdf3271cc4a65a827eba6b92e1f9e8ac0d10c73edec5160c640b000000000000000000000000000000000ac14b2170042ee6561c34f77fca40e1bd2d40d01798417dd954905135ed9b7772e5689e6d4e543d44a4563da8c3ca40c14f014117a74f21e0b698a257ae8e3d6091ba76bff7912abb6bd94d41886d0500000000000000000000000000000000144df2e76821c19167f60630f50c939b66867a82c2a5f807e943676c876aeaa2aef2126bef7fc431f0c7b39e648542fe0000000000000000000000000000000005e463627bb2d22c25520c27c05cdc75e1f2ee3b91e8088399ee42ad13ca217284596e5404b4370995f71fdbf1c1c7860000000000000000000000000000000012323010d6aba1bc6b1d6e7f7e8c7bbc0838564b279d5ae6279f7f7d3cb5d96273e27e7096e9a8540463ad16deb3780e0000000000000000000000000000000019102ac6bb33bd1c5a158a584ce32308b6ee5679dd6d2acdcfa4b9c54674fecad7489d1e39c05b1ded88e4ea93620724d81a1239ad2c945f1c560fd1674ac7e87d49aa41a1f4a5bfffeab1147c0ef7c6000000000000000000000000000000000faf210330693663c8a1d1fef78e211ed2542f7ffeddca3e19be3ba77ef211da1b8bb5abcfc96b692d74f8c7df40b0ce00000000000000000000000000000000134153a252fd8ec5d9aec08ba09a94c4416f95ff6f4ccce59bd400474c836af5bfd941f03384ca4bd5c56fbe81d96ea2000000000000000000000000000000000b4532ff1ceab2a3a177cb83a75c16a833a2ff28df447def351134ec4fcd608b2b75b1f8035ba7d40a737087f3e8c1c100000000000000000000000000000000127e3ed13384b69819b34ef8705fe9a66dd01b275f1f74c2c724420546b39c70cb7a8295a6c1ec4075ead4e3312b8b603a02689cfd2c353fc1b4d3913f5a43745fffe6a87a7c223ec3b25b321584a75c000000000000000000000000000000001351d0d5d531a63a5f56aaf1d7906b7ad2bfb4e9d823e2659bed4e05e7edc9179a7bbf13405ab5cf410b25c7d476c342000000000000000000000000000000000f0ec96128e058e8bfb6e0df1331887245dee87c4f9721fc7f1d20c20a2feea7a7078a4946803ac093477707598d59b70000000000000000000000000000000009399034e4aed13cbf197d8c4753285effa72fc53493ca316db11b39d5527b009aec6350d579f9dee22cd6d4cabd88ad000000000000000000000000000000000002f41ed0dcfa2437cad7b12a94501266d670ed6956196c438241aeb90474d17214eec5d5217090d28892d95f4e40055af95ab3fd062088ffbef6ed887fd39aa1d527fe7633b876187ae12e736fcf2f000000000000000000000000000000000ae208978a751f8921c6067ebab4190ac8d3608dbdf50222eec59460095b8ab2abadd97616c240edd0a9c53dd006e38c000000000000000000000000000000000905224b317a1e64d8af075b6db9de46ca4481458ad6bceaf726ba0f63e81e2a0322e79e70a5a82034abf00d47fccc300000000000000000000000000000000007173c3359f0c2e315d11d646a76e6f500c0922401e4bf9f4ccf2f0801a567fa653f287fdbfb878ba0d9ee12e25396ef000000000000000000000000000000000161d4cc71621e5df13d121c77105af195c2adff5fc6b656b0fc1dd6eb2518f474444d8bc526ae16387f23a4ab3f342f6541c6cf8217c2a95792900e8fc39581b177a57ca00162c57131ea4fb80a4c60000000000000000000000000000000000266af9991c393d3b55f9e0f22b0967d47dbc5b0c97947125e220c4bf9f4bc58d32ebc7bfb02b2e329c933ce41d0d8c00000000000000000000000000000000004cf5748aae8dbc1e4778dc85da575de2b6d9d346f5dc5ccbfd82513166384111f5e5f2f1c2f7ae367a22146d1fac027000000000000000000000000000000000095dbe68521b2cf51283a8cfea1f20eb7ae37e6e945c5f879ba4834d20918b74981f9e0eff4543a79ff4eb36d84a9c60000000000000000000000000000000007953cad14379ffd4309cef1ed6a2dbb73a93db0bd3a256753402e525bb62b10aaf22b662bb2c704865690af995e7d284b7c3f3c4ed10bced85f36fd6dac2646c65d3c810e6d2d116c38aa5e10b29c2d0000000000000000000000000000000010e99f318111baeb1b4611847fdaea7cbd5e3ae532af667ad2498fb2e97b1eee0297e2811c7ae854b882f616da7733fd000000000000000000000000000000000e56cea75b4c4e4c669a492a6723fd60e351a66dc5c34c46469dc36cb04d2c23cfd4aeaa23d0e9e83d5b78a1b77696ed0000000000000000000000000000000018f838d6a582a52a508cbd6bbbb9cf515e091deb7a640e141dea4018af6593c001dc43a8fe4819a7877d9ecf53d5752000000000000000000000000000000000119aaa2ebcdb6379f7ae972cb709990a3e8254f1025cef308281bf7057295e3099d1f3127f76bd2f9ce0a03ae0de8e8d7e33f394e96d17efa30d34f57eecc45d7b4ca150a31b8d0484578151d6e65c2b0000000000000000000000000000000008f837c478e874b857f1c939a26a02e13061d50728c10939ffcf5e862cb177993e204590699a28cabc7593056617d433000000000000000000000000000000000432d9e66dc78bb58ab98771e7e8b5fe51835f286b488e2df6c1991fd36c3c537f2ce30abf24f9d4fb13941189972e39000000000000000000000000000000000b202de3708984f44f7d05ccd9e574a2a93a285d5ca262017346580be273c58f13165437dc90d1d4103d3b9eaac536ce000000000000000000000000000000001873e1251d9ae9448de8e7ccb7ca59a21bcc0d07a2819d140c06ec33cbba559ba90647494a7ecdec8b609b58cf7995cbfde92a31e571ec03e509ac8a70ed5788869854eef0bf578efe6c5e6468315553000000000000000000000000000000000084e07b6576c73aaf43c0ef9c5666dc988ed93d1a106b71e4882fc0cfb5e710b91e5d5eff57327f5678f662f4a451d50000000000000000000000000000000008a29751f1653236a48adb5fbc59059c7137d36139574c6af97314bfbcc22f77a4c5162092762a26b5da7887b94f2da6000000000000000000000000000000000a4fd84c4d58cb9e18aeee180fb05f07c3e1d7ed8d09940182e9b4738744fa6faf600b6f720441e0ad6391a4d502ac040000000000000000000000000000000018b356be2aebca82c54988ab2a2ec58751ce7a815f3dd58a2218a638753d4734d38b74ca0e00bbc8681768f5d1a02b646f7de01ad0f7b4dcaee1123bb80a71d3bc1e63ca577a12b14ae2a11d8c0fde46000000000000000000000000000000000de0f22cf05620a5d4bdcf50ae179f23a9c089fd6eaeb14eca937d9e2480f1782a1c67df76e06191a9b87514daa8bbce000000000000000000000000000000001981cd1f260e7d96e55533b8e29867f37af507b4a58abd69e0ad6af2a55228ab1c82fc2de52deb7b7b7deae2fe621e10000000000000000000000000000000000d22a7a567ec8826391ee711768e612c403e3c16e20947ca5861185c24728b6c7e7756debb333e7acb53d86032d5748900000000000000000000000000000000016fad52e1e86b9e092955cefdf93a10f30db896fb519fd2ca12571d8dc8aa352cf4f8092e0e973d0b0c66df78433251e2c69d21d40813ee40a718f0ead36b51f3a50e9e4e4b2de8acd33add62bfc1d20000000000000000000000000000000000484bb2452158bca93dfeeedb40745bc5d9a9ad49afa20e6c29fc9ed1a8fde33ce508cc252ddd05fc486f8ef78738ac0000000000000000000000000000000003c2d6ff6f292b0f0e505fdfdd2940e72bf8c2837da4ec9c74fb593fe3318a9b9a8592524bb5d40f6c38ad871ab7b6150000000000000000000000000000000015f888ae2722713e1b5b02803a5b48d53116c1a4bb1191c9da77ded8c6ab49f1620b0f7c7867957d84503cfd3dca1be7000000000000000000000000000000000fd96baa382cceadc252eaf000d47d8c1e2085e9f274dd9dbb571bf85bba612836e1da2453fd914135842e2750796b54762d89025196aec4f87da2fcc5a9188b4dc7b1c014dd1d705223bf9fe1e7a7d1000000000000000000000000000000001820de289f62058920ac3d4bc60da023ac29c431ee429a10066f305d2b1a333ffaa906404af977cfd3212b53e66726b500000000000000000000000000000000094e448db84421e25cd03be3867125cedc7f77f286f404524757f3c1a9cfa28ab6771293da490a4d75852f515dfe1a6700000000000000000000000000000000097dec124970bc63d8f62f9133157d412f5ad3fd5eebb444568cf0fe2825d6ef6577ad302842f35570c9977638c6a827000000000000000000000000000000000490bdaabf4db27dce906cfacf3160c0fe25959df4af89301cbe6eeb29f72e4c55bb467841ba7d0750a59a32fc8b03d0ffb9f3e1d43aece3af1f59319a8228cd81e668b1e250d03350958dcac9e23843,00000000000000000000000000000000193358b283147ed5848559d4d1533734822b0248dd17b2effa80920a853b70e7fb683b08aad6ad4dbb91f964ad1b3bb6000000000000000000000000000000000649be60ba72734db4cc307a2fd8be57857f60660d0c496c0dad73794296552e17cb2eabb3537ce677edaac1c6997341000000000000000000000000000000000f91ce27345e86003c99d133eca50710c0722cb35af2ce442ebd74b46d659e0118be9bebf32111c258e4cb4ab795a2cf000000000000000000000000000000000d76ad65233522b1e079fcfef4dfa80f163682d7984d5062680a5dd4cbccd5044de4705013c6bce2140f7950032f90ec,293920, -0000000000000000000000000000000013fe4afb94d08ae311b7442de7291a11e733d8e555f2da6f72bf99da780a8f8d357cbf3d8959f6aeaca7bf3f5b5bd10500000000000000000000000000000000025af713b18cbdb5a960371c2dd0317f4bfd0182f4bfd6b88d588b56fadc1a0398412e7e0a786c326aca8779ae384243000000000000000000000000000000000581c277053c15df8eec05c34267f62e63faeefa2d124c2b4b84d2a739ce5484641ce955fbecb901d1e8ca816690189b0000000000000000000000000000000005355dd304b9b60498a3fb1f08e1ba0c98db327365ca9a0365a7f1e5cb56aec43b7fd2b4aa104eac7b1c30b6f53cd422be285a119dc8cb32b1a0c5380af736114a32e9d1ca870abdf278dfa84444f70e0000000000000000000000000000000016b5b3a6fdeffe5b9a0244a333ada4444a2e03771f94433832a4617be696e467b4e88ed80b174809dde4242bbb51248b0000000000000000000000000000000003dee846c5b84f89734016e547c63c02e4be07dbbecc86f811e2d8d3245f91205bfc055882565371db532240da1a845900000000000000000000000000000000194d53bbfa962def4da2a9bc7129fb6242a3922fe26cc4e603528ff31393a31d03dfc3463704250ea2ffa973ad175153000000000000000000000000000000000333768faee332d7468119b9e0469bbc7bc98a482562ff2fd9aeb6d9c67daac9c3da1db41c9e12224a2eff2feee51778bc0535bd504d7b9658e459c2e79b86bf4e718baa82b8d6e624fba0eb141c7260000000000000000000000000000000001910ded86d79f9b043bb79cc4049e0652c13d0fb8db2f070d695124d7a42cc3a2238282fc8a424fcd8d9ecdab4bb6fad000000000000000000000000000000000dc8d6caf97416928d2d58466219f054c6f28f49b2bc04d8a80cd46a308bc95aaca3a8df1914ab0c7da341862fdf47400000000000000000000000000000000004380ca7b1f7ef96295589f78a1683a51bce4b2afe50bd6076ccf5d07d35e6cb2ec7f74fa35097b2c0b9fff3f4797c1100000000000000000000000000000000054f492d7442b1c0d1293277d95efe822faa7d8881b9afde20db58d6267e049b90d0c8828a6c12540f4ba1e7c9ace6d84f3fa09243c01748954d84f4deeb460f3ef78f9c34296c6a092952bc463d7284000000000000000000000000000000000bba4761eda87a304a80180c2447a1d5a52f743015ea7c728e70d6a5defe3139c80696f842da3f06586be8d506ca4bc90000000000000000000000000000000019ea930d5733f4a1ace9fa0139d412d65b2886b659770e388894592de0694d38876fcd86d14580f9b92518d5496fd44c0000000000000000000000000000000002bf5d9a36d641d1259c1b30397aeb071b88844c4cf17e3de0984129d7b4d67865157ee2f682e7cf9d968fc07ce43618000000000000000000000000000000000f9a4f29868654abafc7ba935aa22d3d010023ef5112683a037a6c69b9e89374b256b8e1329eb5ad306d9f2063c22c335d84733ccc41f71a11d61852fa336df566109c5538c2c5f5cf2af961e93797fd00000000000000000000000000000000004f194f21373f09f8cb4984169890ad3855e814a4768c84e9fc97dfc181c60114aae534a27d3eb225b2125131c754ee000000000000000000000000000000000e6f88880e9645e35806d193f5d16799d63e2f9edd8ae28df54d19875c61857b0a34819a70ba3e9c31f00b5826b0cdc200000000000000000000000000000000193293c6cfae9ae4b24519fb23469e2f8dc4eda8524ee0b00c7141587b07c8a26a29841d41cafbd24bfbea2034a9c18e0000000000000000000000000000000017433efadfe9873dea9a68177af3d5dec4a13dcf4a710422d52020d4d145e2523ec0b48acc533a1ac7068c08ae6aa28bfeeb95c32362014caedf2a9e066a775e2db0d1322edc86759faa99bd70c05b580000000000000000000000000000000011dc003f7542f6822cb872117fa658638dee2a15429aaa9dd576a7e895bc0a2160bc120558a32aab9e646354233a1afd000000000000000000000000000000000fe9ed8ba572ef7d1176176a31fa92a5ff3dc38b0183ea1e22618e3b3214ee78c53074d4c60b5056901c6f046f8210070000000000000000000000000000000006ef1c20c3bd88bd6787598dcfca52da4e5e0e7c7643af983c709b916e71fd15475da30d763ddba0899b182cbc070ca20000000000000000000000000000000001a38a2e54a44ade572ecde076038f5244f266cd99532024a377829a64c20fb2cfe1633367c74b5990febb08e776bc34edee2ea28b93b2daf4ff927991769a9c69ba16490b5676074e64f5e91fa994a60000000000000000000000000000000011ce7b2cba037e5f3ff19b36371d34e287eec807178dad4118c6d43aba68623e182aedbf911a2ae5cf3d0e690ec3ba790000000000000000000000000000000017a617453f391e6e2437d56ee831ba895084f60d1a5f342e19a242b9661c703219d90a157e1b55f005f5059c15c179dd000000000000000000000000000000000746ab134c7f4bc19583a4ea4991c7cec3f651a60582b40c17b2d18cf6e252d93d2f3c2a1a3399be70512ec9eab251de000000000000000000000000000000000698daf214f2de44ebfaa36379862bd9ffb40987dfc8e632f14738c93c8e5c3fc7be9fa9100fb5f7440311cab34fe1897a07e50c1fbf1b388e9264c762798c31fe76761508d070f06adc63130df07641000000000000000000000000000000000e4ac65ce62180ac602ad68098ee31cb747886e95a183e4f819d54af99850d70496e6952076084dc7bc2d3f7a273383100000000000000000000000000000000182c718fc9e5cc961426258e82594a5cafc36270af0eb50646d161fcc192c30d40d06647e14a282421638b31f378de940000000000000000000000000000000002bf448ebd27cb6270e1b87087796ca6534ff51ba0962f3290ee1d06dc18ed39fb736ec95632b483f44d3a9d0e45d1d50000000000000000000000000000000018b956acc1300e60b22bb936b2b52e2ae82e256f15f1415263157965179855137715c321d3765c5227dacb63ba2d6225f0056903b4508cffb6334bb5f645cb553a8cc61ea6765283f933686f172f8360000000000000000000000000000000000f5372651ffb40bf853f6f8396a7c7483c401b89b67e098ea888fde8d19e7552a006a127af1f3311203434126ffad85800000000000000000000000000000000050d7e89b21c7484cc5831885422fe7aa8e898df85cf7a3a275370623eb9660611610cdb829d3935f0d0955e0ac97506000000000000000000000000000000000f83a3f79f1dd110bdb8521e18a64490d567210801d77fa3c0c6e5cbc7285840da325cab7ab08494c8d516511eb189dd000000000000000000000000000000000f72904131be66380c5a18af4857ada7c15e88572197e100de1cfcc9fdb4306e446f2f330fefcccb41b676f24e3e0bf88031f363c8b0062b34d48f4c2e5bdba884005e52f77ac04c2f29dc7ef10fac0c0000000000000000000000000000000009ba6bbf102d390638ceb9259205a1856def2b3a4b5209eb3e4e54074347f71b6c06b70764fe85c8dfc9074067b8d00d000000000000000000000000000000000339c30631229eabc1230240942bdbcfa6e18f23bfbf88b7b8a8fa92f18e35d2f7336f0b819e875ac643b43e6d931e68000000000000000000000000000000000600cfeda6033ff51c3bf9182d22abbfbeb6db46c0fbe15ba82e72fee483744ba5a57ab2eab6f35927b4ba6d2b150063000000000000000000000000000000001530bba4db8a60bb6b7a05f72dbcd23044011d75221d114b839aaa9535400874472f94c849597174322291b5cfec4974cb146e27a9d36dc698e1982afc945af9500fc5aeba719d06d0c4e4eb245034c6000000000000000000000000000000000c636ac98557e22897fd101dc6c54d87060f460b4cf2c5a88ea14641e2a8a9395492fc5a946eebbba36dbe38f6f5c0c60000000000000000000000000000000007fe3a557aa93f2e9aef4ffc55d39a9172475e6595fd57409df3a7fe3d11558c4d3dea3396ee62f61190add83b85813d0000000000000000000000000000000015b04e0daf4a10541623e7523ac5fbe57dfff9ac17afaf4293c493c1982f3395980ec63046cb1d424c6dec91899202c10000000000000000000000000000000019617b191e9e493751b0a02511a18757330bde56722a72a29a399ace983db7114f84795e2b70bc9d670cc0095220454ed983f98fe5112a55c23591bf4e259d072f893944741d9941a00f907749e3c9990000000000000000000000000000000017472b8c1cb3ec528400649fe7c39e3908b16ed69b42d967e4d225b694544e8bc7ce5bec87019db5539f1de39dc6807a0000000000000000000000000000000012b1c4884c37037a94f84c15061df5ca6c05c5a35ad9b37e3ab8e8297c9000e715fd2bdc3f2b485e86c415bf656392a10000000000000000000000000000000002c21af2933029f04b344be76e18ce499def4a0671a97dd9b6a108d0fb23852fcdc56f882be0319978952ef04a207a6a0000000000000000000000000000000015eb31e80fb162d5fa392fada8d43648ef54d4f9ebcb0e9652dd501f55a8875a16a148d42e283ea8bb2c5a38bfcc8843a62f99ac46f986f2f29f0ad3da0310f061e691955c711850a2816ad7464614a70000000000000000000000000000000015e68e011ed063a9fd9cc8a806d8e3561e4f449526ccb6e5ce983ebc4fc49d61d26dad7db64f56ad5ab0b54fbdb76e61000000000000000000000000000000001617d7387fedcdd772a34b267a44315212d21b798c0fe1e7a9ed3caafb678910d9c9c3bd1fff4a3c8e339d0c90a865b8000000000000000000000000000000000e2b3c9b9cc10f41c4c0129d34c62d526aea47c77ded91a5ca3afa0da1801bba81def3ca66a978ebb2d1f3227ea82a9700000000000000000000000000000000096b6caf7b6f29e91bea370f91c2576c188b08b95f9df6c7df995fc9879c11cdbe2af86809468d472fcac8a89716d1d87ee01b0c9c6a6ca1fdac35d89c803bee3595f03d9d200affc5292d8a7c6720b80000000000000000000000000000000016daa86ec04f57c72395d96b6ea5d6ba7cf2d9d4a50eb90f7121545f17c1ee16216f4086481d91e59fc5ed8542baeb7e0000000000000000000000000000000017a783d60be67206241e0bcad20e371d86d47d88ba1293b73f32999b0a1646967e5d031a5b28517f035168d7c7d7927800000000000000000000000000000000058f24fbe4e9befd8abe364c961f0ca4d9083260234a939bf6103a3e8f10a8381a9e3d74af7c13f159e5c7dcf456df00000000000000000000000000000000000485c9448fe3a069eb024ec43aaf563a98da09c02c294da2a94a98a95430e25b062e8ff886fb5fca240fba1abf7cee60297fc700698c56877be6764f48a836d210bb33e99b5735da9837882269af9b45000000000000000000000000000000001230577527a0fde2e8e66b8c4d17594bdab8be1339866819c8890c600b35889d1e3a749fe15fd8182001e30e6420ca6d000000000000000000000000000000000ce03cccfa87229fa8d560884d8c7963276d79ae9873a23d550b4555cc4bda35a242dd2e70cc730b70cdf898609b3d8400000000000000000000000000000000174aab1f142fbb7a45bcdffd64c2d38b99c8919baf9651aa430bcd39613d7565196c18f0f4ee6fe05f5c40ddbcd4a67a0000000000000000000000000000000011dd23f59ca2a033ee5dfa50afb0c7ddeaec6d4f50e1866cca3f061fa03594216f005bc65b2c97ed1109c305e16222671b7ac02db15cebb8af459290c35eb5a86cf98b86d8336764c6bdda6698b49b640000000000000000000000000000000014e1cdf4f10b11f47c15d0b6b7dfccb6081d05d116c8149989cce4f1c53dfcd2d0b7443677b03d037710eba813f6f597000000000000000000000000000000000c8415c7d5508010e0db1878ca663d359525b290b2f02c61436e945145a7a4e1b3ff4e27ea1b2c8d3adbe737d8291b14000000000000000000000000000000000e424ece68003cbfaf65a54dba51e7b0942cc53b2fa9794b4deb6aef1dc1ba1719cba285f9a1a59e71a881eebffe2eb9000000000000000000000000000000001404f9a3146b7201b09c5fd678fdbf2111c48130e82cc95012e5aec1df7e64a3b3c727afee4f603e620925686e126c0f5d1a3f78a2c2ab7b85cee68ee670f50a176e988a341303afb7722917f442fab6,000000000000000000000000000000000e9f6bedba1f6e2a3ff33e0e4b18fbf8e77558bf42e89023df6338b03a648c591486c63c2ecc8ecbbce23b3ff9a7ae6e0000000000000000000000000000000013d2526d83b4495b5af645d5a1af7bd40bd0ebff125e0fa14f10d1c08511dc29643dcfbd25ca0bee5705a56b26c558730000000000000000000000000000000003fa442ab532094d47f1a9111c87deacb15d80ca6e76bfb5f9b9a209bfe196643351d778b0c6d6b274b4799f733abacf000000000000000000000000000000001278d51523d5d9aefc0d3783e745da54f74a88620f2161090a398defdebf82d13d5b5a21a5cd466352ab8685b034fa89,293920, -000000000000000000000000000000000a497e74635fde8caaa5c9dfe666b1b40732e58b93a72d39c8a60c1f4b262e1f18f62229a30fb8257bf895352ac4d249000000000000000000000000000000000c1b2fcbd7f78d85c73ae55f67110b575750bec353e55761de0ff09a9f8a2d916c336655d8f6a78dfbae13fded5a9c36000000000000000000000000000000000173893333d998dd32cc3e82fd7ad8ce77003192ad2bfa1b1d2b43f9466898313276b922f9fbd8e83e86b67acfd9ad780000000000000000000000000000000004ed01b702bbafc73dc1e6846bc944be297ff08d1dfef397603294c7fe11668cd0670d386a8fa0f0f02c52d47f54a11b34aaf86eb77ce03f1d8eacab84d5ff98a565fd33a9a2c40f2a19d7c041a7e2a6000000000000000000000000000000000b5ec74a2150dcf5ebe09f39234c4dfec623318889d92b0bc1f197a69650bc48d28a1112306be763176b691c6915dc7c00000000000000000000000000000000028db19af73ffdd0111dabf9c7d6879cc7389320a249f108b41be8b1d4c259d5889dbcbb48b30a288e26cd9926682d1900000000000000000000000000000000172fe526c62f9cae49e6d3284170e6339d5af256441590cae9507c61f987eb495d340500cb761896163cb8ec631434690000000000000000000000000000000013bbfcf9cd3167b47b48af5f5ed7e6d45a5fa38192756c9e140eb89a85c75602814f767c57108cfa2f726e71f31548f808ab2065f1d2278caece0939cbbab4bcbe3eacdc80cfae6e4500a5195883de0000000000000000000000000000000000052d7a0f93142b36489cfa21d76c0eb96904a3ddd946a53b8a6730036d88d30336fd8aae3ab29ebf62a48c6e849ca66200000000000000000000000000000000198350abe8cc91bd675f26516d771422c128d5dc0af844c6c1af07bf04a1d3ad9654cbddf2de5b7828d1446c45e7828b00000000000000000000000000000000198f35692d5face8dda4b464ff48d650145242852fe189748783b1a2e48806294368ae0a99481bfe739fb4962f3b86a4000000000000000000000000000000000e3cf2e018a7e0acfee25bc3a82cb282cb377bbd72ce3044dd20e109d948f68720c27aea3d4663ee45b2de6f178a00ac58c69b55bac97a633f3ed7816e77e2a26cccc029f7e7429c86145ca4645eb41500000000000000000000000000000000150e6b03a3052d043da6514bf4ee09baf1a35b2a909473db33ea0bd4c6af7d7aee9a8366c1d08d2adc5998635eb0dfb0000000000000000000000000000000001370c2976b0d36fcb955e797087e6ccffc851d2450cd63833d6cbf52e1fccbbbbf9dc695ee45c7df01c2828051bcd79700000000000000000000000000000000048b5fad2fe0af7ccdf675328d8ff5e63b564d8436d04c55b23b6ab7d2aedbd25d614d1780963fcd03d569bed2085bae00000000000000000000000000000000141f94b4e7ba542707d0c3cb69f8dd79e499602952be2374cead840dc669c5ac57089c5fd60c44291703b872098fa2daae7faf23e841bd53683521cb3cf215577fa51f0f751714b6aafe5c740f66208c000000000000000000000000000000000eec51e0ddb8cf9914304e7766a7418e2854ca71367c1d2b3875c12b7dc5c7cc2fbc136037bb7ff72458027104ed3f270000000000000000000000000000000009fe5e8d1918f9b5865a8b97c2c2cfc8bd750a0ccbe2942070827a09d8e41ca795a86b2262b10462795f833c73e788ec000000000000000000000000000000000b95c9146f3f560ad880ca905b5f297e48905680b4613e91f393f72ddb042f6a6201628fb5f75fc23f2298cde66a6df5000000000000000000000000000000000a29a8fba7644ac96d77ee73a93dae23b03d81a57f6cd8cb4594b23571cc1f658f163081ae50d72e09c6513d1cd2c8bf72022cdd6d942158bad47a53a9b0c3be910a41036874975724a5cdd22c012871000000000000000000000000000000001807dd8d2bb40a642fef693739b1df12fc787db0f031306f31970d0f59f0c97c0894afc34b9a9913726a20dcb7d5191200000000000000000000000000000000096fe8bb5e911c1ed9985ac08d864c7020367f4259a0d074973a26cc421a44e8034a7007f6d1639285cf8acb8b2d64a60000000000000000000000000000000014026d43eceb26b9ab5bdd4139d4f94349b273e43f27737f9ad26d23454cdb1d35ea793d21f057359d28328a82d5290b0000000000000000000000000000000003dda2a84bd1f92524a8ede9f5e81f0f64b41b24510f4e0b8146496a776d5b509968f188c12c2d66cf755e5000cddb3b800ae0b956e38bc34cce55bb7e88f1370a30fc8ed0e3f1126c68c30792a2cabc00000000000000000000000000000000011246ad07713d1916c662679ab757c053e33def437d7a976533f0ce80ff6ffc259489c26524ea96898c3747c4127539000000000000000000000000000000000acf66265811a57e47a4c98b40b12a37c6f439550b18215fcf856c167b7218397d7d559f852fb45077945a5074f460be0000000000000000000000000000000009badf2799f1c43a2e3859123aca91e894f86d6298a06a9127249100ba270f2bdc79cf511691bf2d7faa45ffa17490eb00000000000000000000000000000000069438b1d53efcc4277ea7b41cbd28a19f80b5380136f62121e766bd2845e13d5cb40b2f15d508414876ddde491a3830a57c3322133d6ffac661c888995e7cb067ca1309f3e9178a266f1a410a79c01300000000000000000000000000000000112c4cc34da9e83207b5ea8a9251ac5f004546596f2294b3fd51b77ad8d8e98239d53ec4f527c7280801233175500b1b0000000000000000000000000000000011dd8627748c9a2b08524f88e560cd3944bfd1fa17e1d6e2e9cd025b04f2e3ed35125197136afa2848d24fb5fd19508900000000000000000000000000000000093219f9ffbfdaa60c5965b45a5d5bd923eb5d3971542ac147de3f591a5fbe31b30704a0061a524e2ddd05a45dfcb6a10000000000000000000000000000000006407dffb5580790e250a72dfe68a488431f61f45ec9df279217b8800f0ac1ab585d84e486487d5688735fe5aae75bacebe67f3d067b0d011abb31588d1b2fa9fdf8a56bc46b1a0196e926d4ec73040500000000000000000000000000000000107ede23f8e4f273ac2647fc251008905966dde32339c023f1da3c4d35d483a55b54f4157a303e68e1dd7fa3f3b14c8d000000000000000000000000000000001739327f282812fbcbeccb12e40df049284562d8986b8d4559787e1d5247eb6c83d6b838d099f36d8d0e32da2a7999a10000000000000000000000000000000005e5b6b2baede3ceae776da5adf075c1d774e83d6129ccfe7e835862686bb4064b187cc0be0cbfed37e5cc039f3a3fb6000000000000000000000000000000000249554dcfa53f73ef8f08daabf20c55301f75c8ce095cd794061c55e195221602a54ba54260980bcdb35685e41d0f4ffa1d6d0d1876a67337d66c596fbcd7eb22ee308e4a5f66cedff584f1441be6a700000000000000000000000000000000048b7fc5a71787231f1c7ed2134be528fc8d8f77102bda806ccbadf4f9bed79ee94b43c0fd3e5b1d776fe73d786872d1000000000000000000000000000000000152a1f005a64e16949d7249c3b391d5c1e0ded4893d0ce926cc666f0f88b64e8dd6ec4f92ddda18127ec24cad7e40b40000000000000000000000000000000013a2e1e7958a53307adf3beb32a88b7c493df0e37e074c9105da3c09bbaa01fed092fce2b1800790c6e8af3d30ec5a81000000000000000000000000000000000e2d405806764c75122c1b5e410673b28759f26af7489cfa6f35c6c0dd16c508af045009853f3329cda4a67948232bcef0c4ac919efdf3d0e649126da7f8ca3daa30b6ca6f3be6854c0f447a63cf2110000000000000000000000000000000000a71d61dbb3ae37230a2dceb54061d5f8c1ce645e20ec39785c229cf79aefe238959b2745e3b50e4b3c20c7a8e2ae27f0000000000000000000000000000000010e82b8dd5faed6bbd5755c4e5a88edbb3511d3f4442d1e44b82cf72a6414bf6558d29e8907b07f71c00f537637605bb000000000000000000000000000000000d8c93f1984b742b5a02777b706970215c7d8eeeb7377cc26c3af9005648c2eaea7f7a3177b6e049b132ef6bb4b188da0000000000000000000000000000000000ff082a252082499d70eaeba6d5514fc8d641404b48b2ecb256eeb40d9c6b68ad5af58556c9dcfc5667621c549b8ee760d8bf380bc2223efc779a747c0a36f8c2b18c3e821e96163bae14b18f3739f9000000000000000000000000000000000f4cf354b8de6dd2231448bb235af3c84daac2db49abed345da6ded50eae93982a4f2c27b07ce725a062b07fdd9058fe00000000000000000000000000000000076cf19408f0f0379c7e65a6675b9856782990986f5c6d7002e9c9c74b95ab875924bd7ad5e4812844f6d1f530e58deb0000000000000000000000000000000007acffe32f96f5e56557965e3db8dce87eb7140d93608cc003bf4a43fb261bb7360c576da0b7c4dccdbdd9cc53b5c5f8000000000000000000000000000000000eba1c668fd9323d42d6a82d9f075cec2d278cc57122e25ccd72cf8b5a569552cc6b0e9f88d23b9b7af18f3bfa0cc820006c3a7b5ae971e4b0ec34a1007a02cf8c55f067115ba00c5967f70a7dcef9d60000000000000000000000000000000006157cb6e2dfa2733d4c489ec0334f0303ff1ad410f329cb59f99a5fa3ed2cf84eb7d2f231078ba5db0954badb58425f0000000000000000000000000000000003dfee394f4c140e2cad61e8675b26f91244880d9a0b6798d6111090dc9d080563db5c89b7293dcaadc74ea5849a08aa0000000000000000000000000000000001aa1e0683014d5b6f99f469a0b7beefaf05a7ac0298bd1a3e2da409f6cf856f70bc067610fd705a851cd70054df9562000000000000000000000000000000001571b129f69f3a6717272ff75351fa053f46294f68ba3f859208d6c91ba5eb9a0f2133a5e139d04e38c7f7aa303451768f29e330b48230de23e0393bf1614cd26685cafb899db5a164497955d3e98be4000000000000000000000000000000000c4e84b7c8e46daea67c8090b27dc28b7867b89b92f56232bfd8ecd9968b865a057957292e79c6dc08162f9e91e6a4b2000000000000000000000000000000000b8d1eadcf3f1de6ee608a4a0ebb7defeeaf4e251bf07717a6a8e50c07223ca32a2ef290f26d0de14b1942e02acba39a000000000000000000000000000000000e901b546a4d3c68e4432f376c97f42ecf0724777956c4ffb1e6ca4fda562e57be788ecfa45ba3afadb439c2ea546ff30000000000000000000000000000000007ffe01da4fbda9fe5d47c3bedb4b92fdd71ad73fa272b071a7a7d1cdce7743a535da7dfe05a43d03368eb97fff54b2d861ffae8f62572938925593f7271a56e0f559b56bf97c454c38547a2185e2ce70000000000000000000000000000000008da0fe413e31ca68f84032f23bdd5399e01eb3b5ae47033c6834a39645d7b5cc2ec937067b91ac6d83035a86fa841f9000000000000000000000000000000000b950b982323f747782d9065dddca5332940058a604829e31560a6bf9b03ec72b09cfb87a1cd244ec694c7cf192c37ac000000000000000000000000000000000f4afddd25eac15d2248c71d76c9aa27323f75141820efeef1ab4f5003141053f138d9a7d1a901961d0f2c210ade27ed000000000000000000000000000000000217b1800c53d53459b00b8e463df1882b2cbafe85043f08093a5414e58ea7fd4dd933c601acfd7c154d0e4ce187468a2dd907071c2d39fe710215d174452459cc31d36007a1b5570a27ca2e42c8be55000000000000000000000000000000000046aed1acd19201553bb6a88fd6a6c0525ed44822d2a4ed3bca48a0a2b75e76cfcdced8f342b81ce03ffa72e667b3bf0000000000000000000000000000000009a5adbac43cca3402db016a2138342fae89285ab1fa16d7acaa9c3ee2b4e3df2641f7392355996bef7b1578ce1ef119000000000000000000000000000000000c8ebbcbdf2ac3fbb553a2e589f4b7c259a1621b83b14fd1927f92d9f6cb27e82507d7943ff5930f0c14b9fc38c9857900000000000000000000000000000000105b729f678db31d04ceae0aa37f9cb0b0319c4da9a1a4702a11bfe3a5f2f1f2af09b9cbd5ded5a930e2e65f4279a31699893c06db2dab559f2c374df4298707dc1815e55034dce920ae7b1df2ec8d23,000000000000000000000000000000000708e9b926f2536731b02b6b75305c549da58e312d9c53701a993624697af2f3469af34dd4634467f8c98a0f721cd9c00000000000000000000000000000000019185b84fc0511a048e3c39bc10334c91dc1052d323a31c8bf325479a2fa3e4228f8260c0e725c2b89d5a0319e6fbed70000000000000000000000000000000013c7c441d5cca81b48d43e908d6a3bf8b5057cf19e4884227cefa9b235103b46edbe01bada06bb9b620ebbd016d537630000000000000000000000000000000000431182c8a1eed66073956fe5798a894be396403c072e766cdc262b719d1779f960f4aebf61c1bcd4d005d3c7413e52,293920, -00000000000000000000000000000000199f555aa5c651183f52470e36cde438422f41c9b2d1947510665254b74ba0bb9cdc6e6a1283b0c8f58d8f009eec46900000000000000000000000000000000018f1d8f22f43b4649300aa23ac92a2e8f17e7e3853b912bbc8e90588125c371084cb224c2d54dcecb4946ff6db53cd02000000000000000000000000000000000efed0bcc83a52f0faf9e260815da8d4e5286396081268485aab052a96af8eea0112be6cce1486b10b60551ad6c810780000000000000000000000000000000013a3b1ca3b9b7d50083c10d36997f5f521d4426af8d2905aa5d074ff37e218a0c96c74387485c2dae24c0842b7a74cf0d8555388bcc6791802ddb6c0f4cde44f45ac0b5d7ecd918bc34fb9fdedb65b94000000000000000000000000000000000efc5a5c506e94ad2754e235e2da866d9c46342f14d518f12510c93f13a619f6bfefec50c146d6d6170f190497eff229000000000000000000000000000000000fb91f34356005f38c9804250549554cfe67ce195d5e218e4e1b1a4fb904257bdb68d6dfb013e8e85fb5a4cbdbf0f21a000000000000000000000000000000000f09903db4c41fe3f11c6f0cdb7c31a131033e30f52cb66ba10c2e7da1ed8a225ef280d313630121701f9a490e8a0f5c0000000000000000000000000000000003484f7e8f7d67ce40b4cccef110bc255d91f61a4e1968a9ad37e25058eeaf39e9f1ff89c9b2e515388a7c1b49a84a2c33e5999498978d14c9de06f7beb2fd870f6f16dc42125fa496606e65c7466c0f000000000000000000000000000000000444215c3d4a7d62201ea1b69890e2ab90b5f5c6ff56fdc9908634c7489e785521b8dcd7ed409cf09c585cae8414a3250000000000000000000000000000000002d70674251a0c9ba76b8bf3b70547da77cde5592da9204954abd6d8aec82799cc0fa4fcd42139357043fc867b3d0e0d0000000000000000000000000000000018c57fafbad2351a3da695f8b523443e8c763dd7ab875caaa6a494a498cc40b1c0d44488e2dc80d1f0bce00a2c90c67000000000000000000000000000000000125d5a87ee3f558b5e1e7664b0cb95c195bcebd5e43b930fb47d15eee4fd50b3fdd0a401c9bb011c326acc77645440137894a51dcfe5a8fa4da1745a696c870b353fb03a31238b8744840a78084bde480000000000000000000000000000000018790123ce8b3b72d626493a16936c47770a9b06ca45b17c6fa5c7759f088cf98de8ce7b3b5d6082e9e42b39acf76f79000000000000000000000000000000000fea86cad8b40f315d8378550f6d3d831149339a8e8dafa77295859ddd2417e8f5c0ae2baad25fcfe00de14f45a537170000000000000000000000000000000014ad78bb2bce966d52b1fe1a273bc07f2f24b354465edef6dbb1e0123c7c3d7550983b3793ff1c7db846e88eddbf33c4000000000000000000000000000000000c0daa6fba40ec59f6b34d413130df5d9137297d1b7b71b83114a6570fef8e7f83d6f5689527164782f92da4b1ea12e8fb6a294589c816e18859cec34262df6490a2af6acc7daa3de861198c5bcf4b13000000000000000000000000000000001186b7c78952e5c32a9393eab07ad4532471595bc2c5d8137c61dd7fe6b6ca3aaba82dc205a559bdc15421a001b7270d0000000000000000000000000000000012d56b6fcec3d6511d2d723601cb8c9faabdcdd12efdd0e2bfd7c9292f2c3bd7f39c6e9aa53e6955727f88ad69c5b4f10000000000000000000000000000000006a5e56e4a42b04c03619c78232104f1f1f39e755058a19354eb230f2f09bf486b2586817aa6b88f27b884957ea0226600000000000000000000000000000000118c8521dd4866df907ecb252d9ce7a489f17d0f240d054a5dbff6c35895ef20b205236aa6e5be6f0825f9df87878ab783c4a3460caa35fc0e7342dd2da5c7b6aae818eeaf5a2cbf4794387180b95dfa00000000000000000000000000000000092809d18926c20456857826491f55cec17803e9e7d43f22faf4da18ede3bda15e3319539017ab20ed1de2bff490a33f0000000000000000000000000000000018d736b967eca64234f4e0018e5d6c902608e265037d9b8ba42dcc923b84ac62599e153e1c7d00e552ecc5aac57d1a5d000000000000000000000000000000001804aee99219354d4a5c46328f0658a417c85c6bc89af6db29a4911c4b0cad5638fac5ca61cc997fef3450cfb4a6c666000000000000000000000000000000000bf99dc4a400adda5bc89762e9011dae8ada23b284e52e2d49f75f1c75247f6282c95a36f7a72f896ea308131215404bd2b65c1580bb46e3a4cd9d9c4eb7dc998168c66982448abf3a4e08cd12f612b1000000000000000000000000000000000604f8bde85c0b26894e0de155cf896c911bca47533362a0b59ccdad0dd64108d33af8262d3ca2ca399306723f2482a8000000000000000000000000000000000ec10d3777aa54cd0cfd84b4062092ca3ac840a24e8e8aaad5f4c275e4d45091f838ae522efb1b2a0fa42229157297d300000000000000000000000000000000132cc70638d02186116773b31ec0e571a55c1cd78ec055fc647ab09cf4d3c543e0552d559b3daa4e99cef031e583e61500000000000000000000000000000000194a6a32a269692906b64feef9e4e8cd204e560b98db8c66380758d2123babae871273b4c571a1570a317c13a51d0fe9120892aded230949b83bfb2dbac054b83a9dbb852bd0ad85dd1d7f715852306f0000000000000000000000000000000016d05912dfff44912bf34f242ac85eb55bbb8a21625d45496c76d057f518352528c6632d6e8adbbccdd5983d13c26953000000000000000000000000000000000b10aa1402c15fd601ce605ade8f25531ea8f95cf592bf4ed86c4a3aa847dc8aa2369655ce5348da30a897fa8d71ffd800000000000000000000000000000000183f5a2f40da0a0f4598c6b9ea7b99f8cda1d85cec0e6da5365d7eaad1e9a3167bd647e5e654985f395ea72257f61e5d0000000000000000000000000000000014e615e2d5072c1b536ffa607f3a826ce297800b0da329fff397b6327800ecdc879e91f1e3ebc26c18e188e1ca66bfd66af9777a58539e5aa8b1fce0994e0e1cdb5877d93ed4db715c5aaf74d6a8bb1a000000000000000000000000000000000f3cd275d72a637bcce855e2e20727c6e5a1f15bc8d799231d3a7f61311d4cd2f58cf38448675aee9910c1a3d0b576210000000000000000000000000000000019efca445312f568727948c803d06b8d4e2c5289015740f2626fedbc0047d344aead06ef521ff7e139312fa41d1c107200000000000000000000000000000000141384e1c9f79e38bbb0bc1025c079741b93f56e150df58cf9a61ec27c2877c4188866fa197242965e3feb47a78c68380000000000000000000000000000000010638286faa6c45cf028e8e3d200edcb348560e2e35902927391401b3155240b62a40784db88e02b874e128e3a2132b5f37e2ed8e96921a0f9bff8b43d432b382d7b59938e269c381351ea49b8c1ba2b000000000000000000000000000000000c7fc4216767ed298206bc142862c138d78726e2d39afa18fe5732616c73a965d95cd2032d4b2f5a4d562be48ba6885a000000000000000000000000000000000928bbbd76b87f58ecc850e1aa4a2be11b15a81786aa7ca8cf0f6cc342db87b66c435f009f88ad97b747400fbcc651e10000000000000000000000000000000019f5ae9f06f2bc27a39bafacc7f3745fcdf8c78c9ae8a3c066ffd704aa4117eba773691ae43387b93e86d2e2de3688700000000000000000000000000000000014360a7ed73c05ef5fe651321f7e839c920bbc1896636143b88357cbf76e15da839bc7e1f1e629768d447c9d313cec8e23f4a77a2c34a370a9b59ab1cfad77212e433464d0195f0d2fd20c69141389f50000000000000000000000000000000000b9d955f9d28f9485d0bc4a961f0acbf09ee5fef38ccd81a2c73cf87a461ff1bf28d4dd1e0db3ea522299af67bff93b000000000000000000000000000000000889061e71866001b0760f68e20c7c0c033d782e6e6752f11502a0e8b6b70277a985dd13dd83424d1e5cdb9eb96a01c0000000000000000000000000000000000e05a26686667f44de2bef53c36c82f1fdda13dd3f7f8fe1fb026273dc4dfad18241d732ccb757e2b46ed8317dc69fad00000000000000000000000000000000038b55685b02231905dd9a62a709c0f015cf5650b3fa469462b3e9d06e3af8092d998c8e08ee61db1fd5583b0809a38996c59b0bc6dbf66f42cfee34413cc4cbdae7a61e232757c75474818591764d6f0000000000000000000000000000000006649a8eabb25fb7793344a0b29325a88294343f6c69612ee9d9002154a49791f6cd7b37b2bec69fa8ce11722e9f8a03000000000000000000000000000000000e10f2f3de16fce9b9817085f0130e1839d9aae949170ec16834732a9b12f589a2b00f17d2fd3416ddd020b7421ca20500000000000000000000000000000000016b51112b3c7c42a8c2a0fa7f286ec05cd07b6cea5675bf1132de99cf42b450b3c2a8f02ec821529a14a2a0fac3a751000000000000000000000000000000000f471ec8b65bde22e003500d1d422dd0d163abb424dd261fac588333755cc5124acde328085d8df852c61e024155564781c180924f1d982bf4b6a2bb1cac590cdfe84198fdecd87364e163dd988f9b1c000000000000000000000000000000000ec162d22b6516c309efb6a4577c5631a5807bebddc5fd1be5446e4a64785d49eed80eba2e89cfefe484ecb8d50440a600000000000000000000000000000000070c252caf6c56018af6b281b829a4fb8dbab850ba0446d233dcd4d87bebac00e3e5070bd41898dd561526498b153199000000000000000000000000000000000a0d76d1205c1f520d82c85bac4473ea7cf5f68022d95b1f04d06062197973001234d86921e70a94e478eea85264f14a0000000000000000000000000000000014c6a07f0d568f2103ccf8f61278e916458820bcb61fd91479b0dee874fe36c063a34bcb14ee434b68681d297637b5bfe44748b9eb1f44b5fb143cc8deaad23047bc5ecb8059705e7905c37625d5e2d3000000000000000000000000000000000aabac129385d145243c3a1f357ccc963ff14867ad039827488128ac639dc62fba82ace66f889b47d8eac39802bc1af900000000000000000000000000000000062bbbe8c72cd6f8626484bac159b7e28c6c8c3261edc6a05a30c308cc9e56db17eb58f62ab755f04a5c87e58c04c7550000000000000000000000000000000011a4a439d18501142350229778f67bbe0c9b948229dcecf70a8b09d1df6c54801a111c603301da2377d4198d09dd51e70000000000000000000000000000000017de3d9bc6fc5f415d04ecec013a635fa200699c496f4d0bdb5cea7d446274dddd0a7f6b06058fde43fc4f1457361558ae04d7723b7c9cb0574ba744bfed8f8a347ab740bdab99136aa71a6d635d0d98000000000000000000000000000000000c86590a02fb5c9568af4e69611f09980cb5a7e040c94ecdbe64e40005783fd3305a5657a5c6bebca7d20ee123a872b4000000000000000000000000000000000bc873a9bc694171d2606f4efa409897e03198a61b1bb16ae90f0d12345d2650d93c46e0c22b717e2f0504b8983515990000000000000000000000000000000001df9160ac3bc54c0121a9c69e9065f4266202f755c961bcb8641d13720b82ebd73eb3804ba44769fb2d75144442f1c400000000000000000000000000000000045e9c8ed2fe1e5c9a2a5bda75dd60f6bb5dcd0a805f68c1f662a5960b025ff29c8e21857d2a61bcd65c747d2a2da8ef6a794685a342ff25dd706e4df725e3466889d8f08a27ed2f32523b117f01a84e000000000000000000000000000000000f94df8d267339bb4f51b21014ca6d685f7657d0f0bca189e53cf19e0e5e05bfad773c0553daafd80c86f302b1907ba5000000000000000000000000000000000d92905addc028a1dfdad50e909c77662e10e4689e7c8a4a0174a3e1c746b361665b65e17fce02b6c067a5b8d7a6a6f500000000000000000000000000000000183444f0665790c48bd3c07545115a11f82463a092774234e7b33aac1094761f213235895e5e61ac1b0a15603bffe2140000000000000000000000000000000003cc2cbbf181fb023a5f6088d8a9793b17984b3dddc8c3ef1a9f82f8f436002610df60b2d35be212da9945bc8108c0bced3f23c51953e46d400802dde46c374178ef379d5c1b04d25449891f0d5623e5,0000000000000000000000000000000011f85691799cb76213068ef4f997af66c349bf707295b969d85fe637d4eabf54f3f29e739152aba5027c1b55317a27210000000000000000000000000000000019627f9570f07f44f326b5b3ee19bc477e92d813be2865e00da93135645e02e6fe5507ac4d50085b02149667794609fd0000000000000000000000000000000018fdc97bf0f88b2348b436d70ac4e28b5ee5ba21e21e94808b8b9e401c0c7d688974fe203ebda0b23abe38018876f4930000000000000000000000000000000019e28c9c936ea5a0b3b41871c3afaaabd53a93902e44a96dcb7651bce7e6143d81cb695fea8b94aa32c09ec030dd9ac4,293920, -000000000000000000000000000000000703481cf48efe78fe8dad34184edd1765a1d01846de74a45b43d4721bf1af116c229f969868b0e6e851f22bdfb0451300000000000000000000000000000000063d316d495b1e82380c5b73bd61ce7f2159e7714c50e374e8a91dd56731dbe03a3378bf8afeccaba5fda73b4c2dd166000000000000000000000000000000001012cb2f6578065c93aeb673f447ce95fb42927ef9d12e07968ec04b6a604d785944620043dee5de4de33d59e67d64f20000000000000000000000000000000018cc7cfc360801ecc420d77ee171fb3eac3be0cf26b3f36a6cfb7c6adae7bb74c18071daed8fc56b8fa639ea138267928c8e071da1ae8f615631759cf33fdb876ab289a6bcfa6fba2693a58f8601dfd10000000000000000000000000000000011e0dfc437a65c6fe37bb9e554b5138f68a3c52816807bdf7d98f13cfaf86b37e9669f4e0db1b7865d910a309f16cc200000000000000000000000000000000006f2323e01591a7db1d3c7fa1a2ce4540cbe0396cc55baa3a3e13650a6f6b926a7cde0eebb45d359edd52137152fe360000000000000000000000000000000000066bfec8df4ab5f5f5eb369b34e8e22fe32abfc00ac58b68f2d3841248fe5843d6d29ad012249fb9ee851e40b940dc2000000000000000000000000000000000f4ea977d9249bc05dafb682a863ed17f7fba0a06c4a13cdf5a836748664183272eed96bc4109bc5beff61c5469e221f8371fff9230243d2e6cb6bdc4cd97260a8cf0362d18b9ba8df512d2a6f5563dc000000000000000000000000000000000fa3e3e77112774fd6d6b560ff88cc92ef8d009675d0ed65705398ce727cfe786684da50bcdfaffae97d19bdaddd81c00000000000000000000000000000000019e98284b8b9f53faf3b73902cc322dd80fc330dcaff2a7fceb55db6a4b0f7f667297f5e4650c797ee337985dc6b54310000000000000000000000000000000004e30acf2ba66d842575c8679caec607fd090f0aa2350464f3b6eef22e2b9a1d9d5fabb0f3909f1c19f6b8f27c53b040000000000000000000000000000000000ad76b86e32f84ad74bac68909da0c271571606e071b13bd92e387a8a16a1c4002c5a5e94ecaa1e8d2d6e051e19a45c763016c9a9cfbf336ebda090d3f2a1a1b265787e1917f0148f82a9c0b66b21dc100000000000000000000000000000000019bd07479b234bba974ca2f39b317d5f4be33afef66c1d69e53c44cb5e44c679775ba141f82486424110d186561777f00000000000000000000000000000000130002de0d453abe9052a5f70a9d55de74939d1c8e6ad5871a669a867861b1359322eb98539f4a21597d806aeca62d18000000000000000000000000000000000b2f0c649fdb37216c10762f510c3bb4c789dbd29c4f9a8ff39f74ed1a96609c60473a50f5ce3f6535e4af0f2f0a150c000000000000000000000000000000000893b9af710787361a32fbd19c380161c9a214a1bcf3761563424b8546f6068ba650d9caff3e42be63ebf4b6afa2de516c9f679167d5fbb29250834c9f65d3025606e2af20aedec309718f95ba01e90c00000000000000000000000000000000019805c0de5e232632228e2772dc79712e3d863bd6fe56932b29ee99870d2ce5eaf90c73632d1dcddc093e9b6b5b0f1d000000000000000000000000000000000405d77f4b3c44f99a956ef375879e62df033aa408127e0fee013b74675a8c7d999c6abd30f459693086bfdb326d67af00000000000000000000000000000000110f2c231998aca3d76e40055a05feb37eba76cdd10106719f2300f57906424d7eb6d9f85115b78b7371ee60e26d02b5000000000000000000000000000000000593a4721a67caa7cbbe1566611a1d48532c68adcdbb67f362c9ec21e08aaddf6b5e09a9a96df9a89bc25f11665f3a36aaa3300f5a2fafab132f5f4662c1d288210e7502ca2472d060aeea6f2eab2d7100000000000000000000000000000000151758f1921743d116f1c4adfc09cb68b3ff911329e2f6d6bcd04beb9c109568c796f328e1f04381a995fe89aebbc49c000000000000000000000000000000001388c73b1db46bdbe70540c99db46b730e157a23afea97648d73f9d5f7e8b073ed665eed9e9e2500152c87715f1c4d4c000000000000000000000000000000000284ad228867ed14ade5a327ed951ca50c87f0a669e59b7a75d17feb54bc5d685245448a912590179db1e84f1eed1e5b0000000000000000000000000000000017d3da7c167733dd88f1c39315e47cc80c3310cc431989d4cc50ddb22e9fa481c5dc02d94dbf806c4c8da16ba5b24905f6608f7c036c8fdc335601ac55e869215eb4e626f52bae813d45b827df2afd490000000000000000000000000000000016064871cb68f748939a839800afbb018fd5836914a2b76c51818e764628a76817c7ea329e6b2f9de653c8162a2a2e0c00000000000000000000000000000000082fa03cda4c617a780caaecd7c859c5251b56b61f70fb3ea8c05b4c11c030adb8a96d715c1325ef3dce9b20e8065b6700000000000000000000000000000000174a245baedb7e1bf1368212620b850151be41ebb00c977d85da499223c207ab6f1a1d94a51aa9e90d07764ec3615b3a000000000000000000000000000000000df5b81cf4b008480775ff3d7644f546a60382e92a98b03deaa4a20f831e69e14a893ffa731c4ae9ee237d747149a9080cd68c59b1371c7063dee5732182961be90b95247511a5b564d7eee8d2c7c64700000000000000000000000000000000019d36b8dae5e1083e687743f7494b7f9dd0923024df81e2f83c78743e227ffce588a16630201b9909daa6c9207b5f430000000000000000000000000000000015659059cfee7850e1cf0e49abeef2fe5837cd128742e62de20dc734f1bba343aee1c9f1a59d920a0519995561891fdc00000000000000000000000000000000102b7221257c40d9adabd0db3ec9f6348487187ea1110773fcb2ac5ce210dfed167a4d15e605e9d9e666fd092147a1c7000000000000000000000000000000001402ff9770d27d2d82efa6abe4a181e3c1d944e97a06f670d9e46b24f9900fb4a838b32e17482f25be9b6f3240870c02ea52329555d9b79eb1fd6d186df80b25245ba9225553f402cfa6037592f0b10f0000000000000000000000000000000001745ea52686f87a39fa42ddb5b0f69368db3757394fa7a1a93eb20c398c26415c8a7edeec7334df5b15345d6174126b0000000000000000000000000000000012b580e6fd228f087c7584cd95826e56d1c074cf16c35286c45d2067a362529d241c1e24fd22cc9727d423551de1a1f700000000000000000000000000000000104b46c42a706c61610f8c0434894c7cb9ef878cd0234f8aec0825cbb8297bed3de349e7f6037dd19a159103ca7753390000000000000000000000000000000010b781b3cbe6f415af15e37be7c60dc6703e6e79618cb3d8d9a5ea3b17c00822aef1eddacad66a646c009dac887bb070caf39f2a517d432d1653c37fd9a6c4a8a811107dae428f4b2af3b12e4b6acea30000000000000000000000000000000004b172c360fca555e65860c7a294960f506b562e012ddebad5803bc3f4b93159c16cedb73f339def9cd1beaa0912c93c000000000000000000000000000000000242e37775a042ccf59e99da667c67fc49e80e54a1b438a74fe306d668059ab4dc7d9e457adb45e1f91b3e6bef0a130f00000000000000000000000000000000186eb83ce3abe66b8760dcc0d375eb783d175b0b2f36cc08793d8a86cf76b7618b826f50c6b02ed586394abe4efec2f1000000000000000000000000000000000bf780324df1cc5de325a796f1fde367eb52dac76c0632915dfcaf01f5acd6ae890dbfc2e505bafeba7fed8fd63018c2ff0bad6dae80d5f47dd8c208fef0f3046cf1040112d18c596eeb934762977cdc000000000000000000000000000000001231b52c8a081add6e5c250caeb9467335933c2ed66826e4ab44561eda9259acf926f22ad0df8e8756aa51279d12bc9600000000000000000000000000000000051c46bb04d3e035d324de681c772e4561cecc6a5bc4ef0a0cea56618e09b3f39f5085e208229e50164bcdcd4abdefd2000000000000000000000000000000000ad7ee610398935a02c3a7139185409d7fd4681ebb74a239e15d1c092ea913016d3f585d8224cb1d109ac111660a94aa000000000000000000000000000000000903bb16efb052b99e9c46f3478b4acf800a173b35b0079d7728fc25c9415c8b05ad520f31e6a3c867245f64355cbc080d0c40e5d422685c5c83716380eed82392ae1dc6074a7edb5759fa34a61db2d0000000000000000000000000000000001788efb21597aaac29b7bcb9ad6cecb89267c757cfcd8893c32fb13c0f3e1af7fcccb9573dcffe8d9220292b7861cac90000000000000000000000000000000015f85d3686148ad62d7fecb71920981117cb8759ab249d0ceb45f9e4687914536a1eb16ccd0e185d1352a8d2b4a8ee7a0000000000000000000000000000000015d8ed94c0415ee0f7c9854841bac5821253bb2ed4d86a61f494cbfbd61614983e4279fb17802ca68aba4a0302ec1d8a000000000000000000000000000000000f950a4c8aa18f4605e1252c367dba1e170ad00376a8560c2fccfa7d5487b0d1d5885cec16a0a17d81b5a584d473853f7e93a16a443d5f981a02f0b6866536dadd276abc0998bedd76b168ebc8e31b82000000000000000000000000000000000da25ed9154121205ab6843f603a38a6892887d2725f16ff87a5218586c6139188f46da5a42b5e05982468e8115713ce0000000000000000000000000000000013c13ffbed4a60bcb8659013b022012ef3a4400f506d65aff7ffb1bd5a9a5e030a298e417cc1ec8ee7ebc06455dbe61b00000000000000000000000000000000132d83bd141c434326d4772de7f8772c30a6456de7adee7de66a04bece4c0d20bae5526c8eca5af5ef2eebd72c90d54d00000000000000000000000000000000131355c5e359081dc86e0b15c8aedb4f2016b41e8428051f5132258eaf4392fdb63a91452dc56aca20b7ad3263ebc8c92a1d13a64c03585715908744481c79f340b5bdcdd88d685ab8b91722ee7ab7190000000000000000000000000000000012dbe1327162e4176b4988cec23df0c1b0075d0dc51ea8afbbf98f00891511d9023cf7538c5705d59b6d6ddcc90b101d00000000000000000000000000000000036c12c7f7627b6d6fcba9a303248c38d784a3d1d0ff02e550565efbab68c5116e9a88faaaf09bc72bcc3358e9dad0ee000000000000000000000000000000001578ffb68cf12dc9a5ae6fb5d822324cec9e3f576ce08d45e24fec9203d36a6461c5b8ea6ac50233e8893b07ea6e71e00000000000000000000000000000000015cdb43c82b20b8ab270b942b9e625ada9283962a7ce95eae156aa4355e1123ff87ddb1cc85b2a94bf36102ccbec33fb2bc6979fa2e386abec058683c6d74de31af3cac21283cd5e4244d7edd94da9600000000000000000000000000000000017041e16975850e6445c7b4896955eb5eab383ad3c3031aef04e8fdfb65a6d52c9e647330bfbb0f0eab630c9f9ef7a12000000000000000000000000000000000b62757ccfb913ac4264692053f766e142697f598a3fe26e998119b63a3abc7fee03db32a8af36aa21181fe9ea89d12c0000000000000000000000000000000006bbb842a889d7ff3c1eb5e0b16e3a921a11d28a251c488a8a17a29edd93672fd15974a7e972a34c47283c583cf2d29b000000000000000000000000000000000e94e685fb1751f8720b8af79aec7b245ae8daa195f11f485f2c0c5dd68cf39eef848a402ce2342a6b3398cc7879c6010f1937936cc3766184e47f39acfe5af4497e8edf77ab34083135a9ced61d25ed00000000000000000000000000000000100d3fee47ae6c8c7981c8cc615870924fbcb34c2ed817d6862e2e6d0b4612222a4c8332c7d51b58ec59df6832139e1d0000000000000000000000000000000017270fa71c34ec84043ef64c5dfd61614b5b3bd99204f9f70994d71498219818a5f16843c67c668b06aa5ad3a6ba8a0a00000000000000000000000000000000057948c0ebd14664bf33fb282e200fa0e641764a353e8347586465dab0c79ca2caffbdc2c6d60b2d7c8cb6b088bd16fc0000000000000000000000000000000012747eb070f2de18f517648395109bc08b4af3f04d98e23eb6b516199b4eefc5df7d57baec736987139c7b03b573941f639a8b60a1849c71688a11e612b315439161717f525b5deabbce75808470166e,00000000000000000000000000000000128c6c0283ea35c10330502d6aa849a909df6b8dd927a24f08072135b8e21e40c495c42e742160363772569189d73ef40000000000000000000000000000000016d78dba1e0feeab46f8cd38681a9c2f6490ecc3a6e79b31caead256611d133090a4eaed9691a87b66dd1c2ee50d5f470000000000000000000000000000000016de93e176c950975abcbc692384996315a98065db6d6a6214472e5a338e291b36abbcdea1b8be6162fe578acd669abf000000000000000000000000000000000d7155e239e9b15ab64a538b0a0bd53936df4ebdc3ec9b0b1d494e4df780bd014425759e9743c9b262cf48cda01e945a,293920, -000000000000000000000000000000000a653e0c24eee1cdf8e3652809de0cd159f2c541981a4f43936e7d41c0f97ffe2f1e1e0d1032f0970023f1d27241a16a0000000000000000000000000000000012d1d8d2f96db0e5f97be096c961e3b90ef3d88492fb756894979d2e8104791a5b9a43888043ce9e543691f15d2fdb650000000000000000000000000000000006ffb94dc3c2d07830498260ebe4641b2cb64df61cebfffaf2d4ab5b6ba92cd75de209e8d7915ee744c4db5352ff239d0000000000000000000000000000000011f25722cf9db77ef8adb9caa250175e12412e6350b494395a86c31e1f5dee6c89cc6603f1dfd08a70344cdc44aa0c2df3efcda934ec9d2ab05f25d618e5a483e830d0452a88e980589fcd7cfc39e5d80000000000000000000000000000000006177a74e3551770e7d906222590108bae7b97a5dd3bdd2344fc12e7005f2c1a188ab9dffe68f5ffb0cc36294106f15800000000000000000000000000000000041b140c46868767119a6ebb58562570732198854c92bcc070f2a8d9be91282a70c5ab99e75cc9e5064ed628aa5c59de000000000000000000000000000000000f318ee33fccf455e46add44922bb6e99afd4354bbc79d7550f8d12d3de4f75e5ddf4e62624b116f91aaa80a148adaf9000000000000000000000000000000000fe012bf88e152eb62c0c906dccba469abe591687573a59d3debe747b7d895e4b0755f16e67fa9193a2fd338c04d243a4507a696cc57c0bc49fb4d1686752c71c9c816d7d09bd66910b23810d475aa02000000000000000000000000000000000b26c6e0106d4efbacf2dd0d15df17209b1306f388f493c096429c031bc4a6a535b64cb02b400433f948fd6004df2fa200000000000000000000000000000000061853cf1a32fdf4c370cd413754ea584d3722a08d58575075a7371e57a7bdef95386ed72f91c4893377f6b551dd6b1d000000000000000000000000000000000ebf17e60718c8563a1029ba035dbbba75e7191b4339d5d33f64bb35f34866081f26f4815e01b02e8330e7b7e9c428cd0000000000000000000000000000000008ce40f92efb5c5be48c814018fbbe45f1be45f5b607a6600cecd50d8f791de7d91939ab61204c2a1337c3f21b2c9d26518c1259f23de4cecd5e3a40abef5662b497ebaf16240f40ecd651d2ba50af0700000000000000000000000000000000123ef52cc44f36326b33234ab3348893bc722bac3674e43385b201f372fe4ea3569d69d4d561e26f8ea903e017d7376a0000000000000000000000000000000005b1707ef61ff9acb9e8b4dd6922daaaa2d8a7558cb55b1b9b96eb6d57c23f50a7955763c9b5ef04f52b09be8d55f4b50000000000000000000000000000000015b6e35d14da61e7a7fcbcb0dddaf0071d8d2d89f7179f44851947a2b9b0535d6fa86b5cae9713a73bbed909a4c6deaa0000000000000000000000000000000013463e135b1fd460cf042dcd0226e229d60cc2beccd8a1832df241e65a644159722a14297c0033eb499e5890f0caff1e5561616c195ccc1345421d8a6efec48f0a4dc8e89ee89599839efaf95c386551000000000000000000000000000000000fbdf4a533d355e232723fbc97352fc5d7d3d199934883a61a9ea116830bdf9e40d423256225d9a3458134332ef6e817000000000000000000000000000000001195f0ad227941c5e383c48f546be34762d158e6cee585650b6ee987f7b98e802f678abac6646832b30b6e12e90948cb000000000000000000000000000000001820d5fbb5a62140c6e8cd105a70fc2f1ed84e254c839deadae5eadbb75e1c33a07ad12ee92900f55478e91958a3147a0000000000000000000000000000000013849bdcae33fad27f16e91c6d46b9678a00491e3d70a8db905db4b1d2c6f02a29392b5b77c1472052d6f4d49f14a16737c77734125181c72454bb2d37c3725cf1f9b6d6f42b721bca469fec154b3e2600000000000000000000000000000000188fe1e394b567d71099fa13b5c8a5891636d83b6b8a08f410b080658a0663deaae4dca1afe8b9023b5e8e573c752c92000000000000000000000000000000000f66c65dab8e1b2912fd5285a4c87821888532f5107075cdfedacc4d7f75c6a74b4828d0b4c3a2c0ed94576654a7047d0000000000000000000000000000000016af44a6df79c8c9b6f1d8aeca24e024c454d7b94c9ed386858dd35c4158cddcad1207f9fc3ac9e3b748c2314f875dac000000000000000000000000000000000315e5e4f78e9fcb93aac78025e95b8bf82ce4c840cf565e0a868b0aac22950d62f7becbf8039a16ca3ea66a7498327d981483aa66e04351f4340fd2b461165b9a9983e91c148da78d3c8e0c69e77de4000000000000000000000000000000000f9a61dd1b3034b8cd7408b0a44c8d02f4fe0e87778d5d34f5e884ccc9e2d51eca6b6060b46b66843e8247b3c794e19d0000000000000000000000000000000005c47fa7799a0fffcafbbe4694dfe8d0f47b60f712d6319e9a56ac459a636460e700e2af80f9c688208978aec7c413af000000000000000000000000000000000ab1c55fe2207865ecf12e372a341c776d24c08dba10702fce1cd2c01eda314852d81d0ccf1c3423c2a12e8960677f060000000000000000000000000000000014f8a1964aa3240d788ea40bb51abc50fae2736a34120ca9585fb2d5bba4e5cfa201c83be1e00ecd1c46fcb2ebb4eb809913da6f756005ca8ab900ab686484483af07df768209a16d807f8b88b9334d30000000000000000000000000000000006441fcaf5e68b10e7e511a95e56b9613453ec6468bb126c5eb12f204c9681c69b5c296320f92a6fbb0b848f8ab5fcd1000000000000000000000000000000000141de16aeca0a2f991e9fca4b6ce8fbab3d66ee3ee4dffb0124384a7d4ba51864a53e005fd34516c92ecab33165944a0000000000000000000000000000000008543656b5495bdb726109cd98fa18e405648fa88cbe2e5fea5380b7d0ecb207f0343dc7888b9945e55156977336226b000000000000000000000000000000000b53d4e392f304225b1ef363a3528daca1d3a6ad64ee99d58491863ea432a29cde5edd4f390de45a567cf32112ca5929188fb33fb359f21bc5bdfc85d39676c2ca0a1e619bf8a8e8de62da8818bd6cfe0000000000000000000000000000000002e0c55a43078df575efb2c99b27c5632dd1c08bf28b6c0558081a78de58e4258d1b57d94ec6fa157add04aee06e7b6e0000000000000000000000000000000006d3f4f0791431a56fb386f4bb8e6744cd19b10bd0f2e65e927371ab488d3735e3b83400ddb25ef9d740a8620821b0ab0000000000000000000000000000000011e9cdfec8a8f8eba0de6809485911711149ca0ebd0cecc033e2e5ddfc195fa7de671a686edd2f56e5f7da7328dfbec000000000000000000000000000000000171f188afd5d9568cc5648aefb65cd715c0293344b9aceac1031f10b4a1e4b9fa2ab11114bd58f28aaa58c10ee0eeac65525ab4c4468a2ec0beecdb7fb072f28260ebb3d9da1a4c274b2c11a087e814a000000000000000000000000000000001651d9bddf61e5e54f86609c2479513ae84b000ad7defd840d9619a8361922dde81c999d0e95d8a3044c46fe0360c2030000000000000000000000000000000014a68c248808e826a3bb50f3c1c1438483cbb9da8dd67a0c9633a47f733e6aa7deb4a13aaebcd50de6e8e8f00000424a0000000000000000000000000000000010c8a94b9e0ec9965f6c8bd0c4279102ab682a14fc3c22e9640d68f240ccecfead9a2c6e69f7c8ed369cce7e2da50d5000000000000000000000000000000000181493e8137fcfae203e1b45189fb828dc9eb56887c89aaf9aad0380fffada423f0ab48ed068ba4e67a2b01a16abbfe55ab5a55a5cfc49cf6c36b5718e108f8d006bf7fa1ec3dc7a7f9c02a2d1e3fc57000000000000000000000000000000000e3e33fa4d85a35e8707419ca6d4fb6a61ee6b07ce152adfbaf6b5f1d7ccc253b59f91e4545848b3570bfaa804ad9767000000000000000000000000000000000c923a4de074dce3ccc94698bf6445af5847c0e6f22f225c589f744ec83ed0810913af2a6d04bd55200ffc738b31b01200000000000000000000000000000000186961ed1c6039476eb6f13bf1b5f6627b3b017ece57a4a5f33db8ef12347fd507398a421932d3d2a1d009f65d06e42c0000000000000000000000000000000011e10ae0139f95a2f1144810894fb98f6e5e86ce67877b949a2a7134c446dfe53c23dfbfd12919b24975f26eafa249216ce7aa7dcd01c1b7059ad3cc0ebf5d19ceaae633160a968c33aac5dc6adb942800000000000000000000000000000000029265ecf3c81aab289c98d9cdb917749ceef56e2e4d59de2d6c83907f394ddd1cce9d093a20206c2c1c215493c41c49000000000000000000000000000000000986ad139381e4dbabd6beba179600e1c782f436f84a7bd58cdd96a22269f1d937f88f25059214fe2a781ac519aa621d0000000000000000000000000000000019e296d5b17f78b3ffbdaa2ef5228fa9dd65abdf6b2c5b0f99a708c4721797b3b156b8df98a5a879f17f095548555da7000000000000000000000000000000000349677d4719445d5525cd65e2338463d232eb75721ca51c48fe52d0fbd299ddbd6cbc12546f056bf212d5700c3c4100854bce63dcdc0cf408b43690abbbbdacda5f3ebd9d9e462f89f9f50a9f7bd44b0000000000000000000000000000000016f5d5eb3fc3ff178843a7d21d3dd628bda120321ae44206d88f07ac001651428e0da95d3f0676e1bbb969a300406ce000000000000000000000000000000000029121c539ef1d7b9888497a362fda2f8402adf10a1bee11b53cf3dfcc6f99d5026bc386f86a2eecd0c276494878104f000000000000000000000000000000001320a402922f2a0bb287464854be6782046dd9dae4c0cd94efcb8ad8e0f37b7889bc97a3c8b4d3b3670a6924c8ee23ec00000000000000000000000000000000101fa8bb2c90b755bfba9cd7a98790b7bea2ede4c806fbd9f2006d10cf87c44172d4ba46ea40fb75afbbaa2abc3b6e9d7603824b834a83c1c408243b51cd2c2d31e2ee763d69e2ad6d369bb6aa2396fd0000000000000000000000000000000003285cb099b04b6acd333c7ac76c839b6c09388792d5fa1f2af0821e49dfbf40a06803c4cca92512bb76d073129a48a00000000000000000000000000000000005b2fdbb25381b3b67814bf6cc0a4cc17271416d16ee369b723b1711d968c355b755183f0bce519709723250515ba32a0000000000000000000000000000000002c7062ba4f642b95e028a364b0698b801f48af3c336fa09d13d83ec6cff10d210b55b23cad1d999889c83df7d1ab7e10000000000000000000000000000000012cdfdc10bf46097083294259754453e084010f7ee928cf540d44c80aa4f601247223a318700bc24114e7603922d15ae923c86e91c48582f19409b962be361da5936db02b6862eefc288f9a32d5f54760000000000000000000000000000000000669d760352e34a407aef8e141fcaa9468257b12ec08ec218f49f0769f3acd5068c6dc9d251a1b2af02a2d091f8ad0000000000000000000000000000000000064a7b4026ee3115cb730e56c4b9bf3e1527dd0f0ac6015f43d30a2f3d8d8c2659cf50247e70ca3c93d7e0a404d9faaa000000000000000000000000000000000979ca2e81663ed61486c1f841c19d83549388d798da72feda82283406d4964bc9991f876a6032382c35b605441ee7da0000000000000000000000000000000008d92cf77b44c516c243f3e6a8a8d3f9d3d7405820ab972338f700de1dd9a66d33b4a70540a30f630aa81fe1cb5bf057e1b3071b561a80aaaadb5cc24b348a2b6012340d3aebcca7e2f56983a8a13bf900000000000000000000000000000000198831a40fec54a210a63f5e00b132bb1eca6408335b85a75e28be6a111beea3b99d9f2fe5091ab0eba0f082c201c14d000000000000000000000000000000000fe457f8d215f390000efbb7fe7193ba02a2ef78e9bff6539995f01604fdca9fa3c010276afb90215890f5a5df3ae21500000000000000000000000000000000076771823180422495d89c301443a9d1fa141716e5e27205b8cb6b461a3ded7e6f196c3976cd6ad56b2e6ebb6b3a70860000000000000000000000000000000007f666efc677f6f767828e1291bde0ba0ca445ddb2d69d5d2fa090ca49e697ce4e00f55d2b706454be6d68f012d76efbb6863b755d3dee61328a60f585531c436663bbeab9afaffac49b6f0b57614eaa,000000000000000000000000000000000e1268a5e2f654c4038647a910e6cb4bab1d6ca3562ad4a9ac18444c8b8a3fdfbd35acf37f9203041fd587a5175ce86d0000000000000000000000000000000005e701a8ddd15ecb0231b7625f7868c81467c140b2617e60440a9b348a192e5248b1b3c087545cfb6d173fafe48d32f600000000000000000000000000000000071327f52b1325bb664d32c513fb12afb96dd8da23dd91bc4c3e8ae5f97d6bf673a5d03bb8bdeb6da3144dedac200dbd000000000000000000000000000000001852b86d3ef45aaeb691f454a345ed61104cecf0f444e4d841f2bc0402ea1291ef056eddb3fc3929ef4623f31016c3b5,293920, -000000000000000000000000000000000fcd3f253d018ef09a7c6e8f36662ab4190867a197e0c42a0b425dfb5fe61d57596ada28dde0b093676ce15d03406d20000000000000000000000000000000000df00598337060d603607f3b8dd16f277ce1882a2e9ced48e1944662323efc29b33c807653f31583a5d2198426019ba70000000000000000000000000000000009876c81a76986435d34c6d44d51cf1016c19ceed2432ef1e68decd64da2e31e42372c1a41a514b0eac0ac103ab6f43800000000000000000000000000000000121cf298ff8f610c64ca4a887c52cbe940333506ef2afecffe266b5b585ff737395e2c64adc23b5bd232250e67c7a62613ca0cfc742607bee58988df361d7cd5d809ba4fddb209c898cd555369fff566000000000000000000000000000000001885d5cdc3e0e0c8cffa7519e6914e5d85962d07633970c4174ae4587853f13970a1f5d7ccba97458b9b5046847ad29800000000000000000000000000000000105b7c0ba96d5ce32d7447351ded3e3f491a0e741e921447b91f22a23b64c2d749055a0593e5b47f0ff7815e1a4c9943000000000000000000000000000000000cb88fc10c94642ae7e1d7275bbfd51a2d40e9b29f3d51a1ceda577beeb131eae4b17418f9f358d47b4b9c9ca4960a3b00000000000000000000000000000000131a3e080b1d4e936d97d255b07b09a6210b5fe6900da87b5cc595a72de2b6ddb01809e2dc63ad460a2926dd8d3b3b2ebcca8ab454fbc576a2b910f140b23c23b14301c19e1f47989d78eeecf279862a00000000000000000000000000000000066b31c0bc4b3b9fe420dc095d551903a2859556d86e210c96480f1d31d449d85ea292e2432babdb71c151c7b215cd6b0000000000000000000000000000000019d79a60793957745077f9233aee7a4f096515eefa7c49473f09bbc73fa0ee13a2a30a08bd7f3bc1d5c412d671fc37ff00000000000000000000000000000000006882160e4fa8ae2c2d48ae389d8f023e2775adb7a815edeba13728b8f6b343c45788c8e9116445e9989e01eb43e1500000000000000000000000000000000000ce53ab2d81ebaf4a85b3e12a6175ad7fb6cfbae207a69a0fe2195ab916fcb582b097f09d9fc565b837925f68855c4b59f82ceeb6160d3256228d7a41fb3caa6f305b23142ab979e728356a13309e27000000000000000000000000000000000a30d335c035afe459dc262fb1bd24dc0bafbc08fae0bed47e4e204280eb96595fada9c4332df1218748921bfb1274c7000000000000000000000000000000000e37eb189560211d6fe56faa3b6e710878a21907fdc1a9f8becabca290c24b8831e28ebb48d06bd822300fd09b4d103100000000000000000000000000000000104842b88b9df6a7b8243494eb11eb62c89d1ccbde9f55fe221c2366d6bc9149178f177628c6fe7c7661318640295e570000000000000000000000000000000011df8599d72b85ade11261076e02c036be5dfa3b6fab4ff72ed7413a879c0a0742be6c36a32d0829a4e3171b0341c6a3995f7d2038ad02deddca34399e5b5653fa471d998c52bd52241840cdb9202b2c0000000000000000000000000000000019f6634435be45b099cc739fe5c2dfa01f61fd2d466d5ea464053e2d5acf2e0e9448b1bb7770b5ad426f8a872c5764400000000000000000000000000000000002bbd52efecb10b3bb6f8bd04a5751042d8598cc34e2837184cea2b5953ec125dee871d1f2f57ebc84849e3a7ee5abe2000000000000000000000000000000001962b716342df9c13c21d89ab5b8c4c0ca191440fa709627e0f240a7ba518f4c95adfc5973b6ed0af591bb54bd00937f00000000000000000000000000000000089eec676276c52bfbb2593ef0362c12a5f3c1a0566d5aa862f5f5ba1580f4dadb36c15fdcf0c3910ee14487ff146c8997b67e68bfe2d7fc256e6aa610dd91dc1b02c64186d24702ad8fa9f715b582a5000000000000000000000000000000001556d081a489eba4fbb0c20e22b8cab432a9f6ff459ab9b0e7ceacbbd46c8e24a2ee70151b019a1b4bfe47d934afede30000000000000000000000000000000008fdd7391113e8d9865ef48b60acf921b17c50744e6ad62fa24abaae54836b3d59a7441371bdfdcdb251d252a43aed7b000000000000000000000000000000000cc66cdb1fe32beb91b05922f3920060e7a95467381d62f2f036e6268af4128c9516780ea53e873993744ce932b901f100000000000000000000000000000000151f94dec958859ecaeb810c4b1cc7a707d0e1671cd4a1e3c811910bc8b95c6c944167dd280c7fed22f92ce7650beef998115b9f84e3ed6947bd6f0e3c65361cf360a65bc059515da852a72ec5cd178100000000000000000000000000000000004f88568c7ede48d7476175f1d2e7ded4312c24934f0d47794705621f8aa8a5072b86cc41e187f4aeeb49bff17a4c9d000000000000000000000000000000000ca6c579e86a68b4041150fbbc36da744d359028993681c34e66c537eb8a0a0d55aeb9b8da7fecb844104dabeb507805000000000000000000000000000000000fec63c57d3d3ca98cd1735b2f59217e163ca53b07b4fabc4415b98377d87e75f0fcc9b51c99a57ff61ca8d0016a206d000000000000000000000000000000000940e9f93f3ccbe74c7be93236a2c440b213a014ed51cb57fa053495c3d6f6c8edc08ba8e10be26e5faa898162d67fe327370e1037b709015e0bf178a41ac55774a813368e11ef7a764eb48abe75dbf500000000000000000000000000000000055e4dd9da22201b5eb64e3b9eff2eab614c48450424491a85c18e05f50659b88e862490edd11ff980b06696b60c35b00000000000000000000000000000000018fab38f58d3d541666bc29b9e94cb3940f1794b2aa851d079b9aaa1cf742b07cd6dc7c985c7e4d7d3fe683bb15d618e000000000000000000000000000000000534de5e1c1181e951b437fd17993e995fd4aa2f6b28fc3612cd4db615de742e12d66c03b9ced538c1c7cde27752c190000000000000000000000000000000000aa8580f1da71f2ae9ec26f3b6466813a40ba5bd3f89ed0d42695d420032540194617fcc2f13e36219fc0cc3886a69c36bf5fb297948e0ddc60ba26e49ef2892ca008e64a22ff2bb21ff70c56112f710000000000000000000000000000000001804ed7677fa3842bdc3eba708bf4fb7f7d4eaf2f1a46193c861595f64196398622df4358b9526f33663138b24fef1310000000000000000000000000000000011fdd7e1d0c5adfbbbaa69ce63c7c54525091289e4dfdfb3de772a8d5a958581cc23933deadcb8856540e2d0dc564dbc0000000000000000000000000000000013fcf17235506fb194e3adaab881c7aba4b87e5aef739e0547b858410e3cdbff0dab1980b1b30a7d03d617179ae545c900000000000000000000000000000000004eed0ca479cc458231ff969ebdd4e33732953e9f5610d78d4753b99c5f8cf73c742387b8e71b9be074fcc67acd71cf6b488b6b63cb8bf34efeedd9f95dff4d3d8c067c0d807bd1e20bd267748275d0000000000000000000000000000000001082b7796d35e387df689bcdda6e0316d343dc907822d1a873adea050374962b164ed27cea0e1b834997f8274e4c5438000000000000000000000000000000000b1905979a90c7a61f4ee2cf3a9f4d6ed4c724c9e216981b8ec34fb9b528018d237771ad620020efc2c3cb104df667cd000000000000000000000000000000000752663e72390108288ef4de3c3ea409c74e7051505b12083c41a2e8937eaadbd8cd61f96f7991722226fdd02dd8d252000000000000000000000000000000000f8e4eb7a3c78b8040a115c42b5d2fc69405f8334e948b8553f444dfef29bf3920892da431cd8394cf61f24e356e95694f661845e91de1c09f581c7612a25bfa0889f77c2add31b493b37d20bcce11070000000000000000000000000000000010884516bb9916084709351ed8768c6105fa451e08d5acb233511254ddbf4e72baf9c43b56b4d7dd129a38f5b34ee5f0000000000000000000000000000000000228fc5fffef746419cc69abb17cdc63ded44892b8c5d02f0c72bc8506a61d15a74ec4ea0e1d78f555ddec07f418539500000000000000000000000000000000048a4192c204b7441e871076d91d4f610c347c2d71cf495ffcb2e2ab808a8c1a549eae96e657d756d9a3b94db2892a2f0000000000000000000000000000000017a94d2472df89104ed96e24d166f922bb852b5ad80f80188fce65b08d39cc3ecf94991c6bec5dc12f9337e7c087db2f8b3bf8d5e529912b1b6e445f592a6d151c6f5d01d3b021a31a2669df4ce02aa3000000000000000000000000000000000f6293fb0e19ec85f43a1a02df9f59ad4fb0e49b16a216ce097b8ec59e781fdf176360d8492e8b77674ae2c0ddb1da70000000000000000000000000000000000e354d09aad68fce6cde40c787ba1e4488999d5b9f3fec25c9994b56bcccaaa746c958bd16ba271485f461b0d4e983200000000000000000000000000000000014fca0851b0bfdf2c69fb346f23b46135d2b7914bb49e297a0c1304d8c2851ff6bd0a0bb364938dd44680fe86cfe12e300000000000000000000000000000000164e23a53103dfa332e5ae09c7c898b95773c20f019d8b794a6b49594040e2e090db6a8047c943885dca95188e89a63b30e1c8f222019b877e66df0b6201b5bfc5b6c10aae340c55e74410a536ffb9b200000000000000000000000000000000146d37241ce4f71017e4423dd0bf907a12c1364ae9fc6dfe535c25e5e99e03ce157cbba2675829b396a69f92668107280000000000000000000000000000000000d5a992f5357615f436d95fa516212812f6811dd1f1921ba4129e84e3d487b6c97520995d8a65f6771dbba9d150c7ab0000000000000000000000000000000007b01f86574a9cb7eb3b9a19b6040055a5c11b13e7071078d16b9ad71f714ed28ad25db9511964b156ee34db22385cdf00000000000000000000000000000000154c29c6e2b21a75b14159b183e625c98a04be1850b22d314225e94b313619f641ead73130c1d6feb85abd8c9e172f6323a258d66f2296fa1c71065cf23c994eb8c6c35d35120d16790fec791ad215fe00000000000000000000000000000000075be2703b8416fa07a7cb6ae8841dcab1e36b0ea24231dba617a2fed3bebf8d952d31f68c149dd17eed136fe37b01880000000000000000000000000000000001156563f1401b731cc23c4be59e69b0e6a0827df4889cd9ef9e11310f679c1603a0d9c9679c29b8dab75ae51f49bfe3000000000000000000000000000000000663faacfaa92fbc095a5dd6b1f2dd141e248f84eff1716ee71bdffd4d28ef1f4c88828e3457e8ebf0daba1416d2d6070000000000000000000000000000000018f2871f5897aad9ff6ac45a9c0e78be8f312f07af5f1dab2bc4705558070abf367f1782af896288a7754da82bf1a5141ef4055b85f37b548dac2b64608d99ca293548bebe1e24355393520c34eda60a0000000000000000000000000000000001618a284286899f501f46c4761c93b68bc8ab3157144e4013e242e1678cba20a2d978ab53b4b43145dd6062748df541000000000000000000000000000000000c25da737368775e41ddcd9c64cf99a824afacb1d404f1ef46ec7fe4ffd89673648c5207551914e6e0d12c57e7d7682c00000000000000000000000000000000097ff49c4872e2da1f6c24fd6dd4667f0bef4eb30fc197d13e8b66adc425e39841dea011d79e4d775106a19ea1978f4c00000000000000000000000000000000147426b7d9b0bdc2be051d8f6cc4249014e1bbc2369bc32eca94684483f50ced2c07be6a320effddcc1ed5cae455fc92212529248c51c95b5b26961f27e6d44ef1c2b9233bb2ed32c3eee79ca6c6eb750000000000000000000000000000000000cf68f7ab056c4689af95b361ee3e3b1c1c48f18b5aa655cce1a2be217010814b3f07dedf6f9a7b835cb13e2afd7136000000000000000000000000000000000dd6d0fb94048dab34410dba4e682f020ed54a655099fbb6f6e94a31511960f0447d7e94143eea88195291b225d11246000000000000000000000000000000001864c6ad3f2f794239a179647d68734e23b3520b79952bda20acf2f5afe1b76bc18e35b852d35a5cf3b02a3ce86f640700000000000000000000000000000000015ea24562d7bc59d813b77b2a4943f9e98842b5a41c0c7026077a02ddfd3d5fecf352d4399f507fb12ada4ac495ddece9888dd839d9b8c236394c44d358f452a4588ae65d24ffe2bd345fc745de9d37,00000000000000000000000000000000080f0e50f90e001a442965ba7900997fcc89246742590b99add6d90428d3b61516664654bc8fb423f701e85a342a668100000000000000000000000000000000003fa9e84ddd754047649b7cfcf5bd78852abb298b3bbe6575c4c7dbc2e7595499a9f42f379a2463aa74f29e5c73a9040000000000000000000000000000000009e72d3c418726f6400b8cd8b9b649005f3b25ade40cd6f77a0c3cbdbef461e917d4453c9e07ded45301d21df4ec44db0000000000000000000000000000000015a06cac223217602ccfba4f4586cb184994bf08b324bf977dbb3884c394aed0622da7dcf5712970554d73b18e2733c5,293920, -0000000000000000000000000000000007340f432a5cd5aff1a1d98c6ea1c94be24de2d15a4e112925586c30979e23a5db93643308d3299e370b1f26bdd09eac00000000000000000000000000000000155027caae88381a60af71b2fa770e58efccfbb7642f5ef6b1591bf77e415eb117ab564aff8d9ebcd576f813b793ad2c000000000000000000000000000000000f604238d1b28f010ce8e45f2fe61d3ea20b902a4debbabcd54ce0ecd44a9540fe2bfe847178656fef0a5fd7e6d012b3000000000000000000000000000000000d7f503ede395dfa5682aadedc98bfe28d3fbfb52f42ecabc9eebc0e0a6616d3671604709f28255f50b62bee641d2711f812322dc2a7d5faa9e4877638faf8492d84e0f4c4c65ca3aadcb7eafed2106400000000000000000000000000000000176e1f9eac4dab0d253c0ff41b7600437b53a5ac5278d544a9620648e0bc4dc56aff0bda973fd1338f77fa174d8b13b90000000000000000000000000000000012919a18343cc166e2dfb92ff07bbf838779ef0479985bb85b3b82f9d0632b3f7a19d387f725a21729a77c58dd4d1d1d0000000000000000000000000000000017eb269ed75fe0403021ce70505bb60a711c91c551931605bb2a0773fafa07aeb47cdda382c0aa64f40f5e6e0e6bc77d000000000000000000000000000000000bed8ca999a4691646124a140fcc17dec02b74bd28b599c807abcaaff13bff65aff3892897652cd33b4ba5e4cc0198a9c1f6d538c5b4ae15c84581f8fd4c61160ed395816557fde197e1a013ba41ba0f000000000000000000000000000000001344d6902f5fdbb59a4c975847db0191beac284eb17cd92360e59f42cd7796cf2aa282bbd4cb074c4ee10b489ee3f2f60000000000000000000000000000000002158eb3429d0532792532fcceecc404e95a879be68b3685ae94016ca3762438b3320553ab6d5fbda3a0b615a04d996f00000000000000000000000000000000118f6fd8f60edf7088a0b4b49338bfcfc9c38be230460d7516f317b27c07600f504c8cc87acb0c95515c3acdc1b125ce0000000000000000000000000000000014eb422d44ec6931ac9860a6a017a907e8ed76de91bb7557e818dbacb19fb51457a1f45cca91f1d1d75a3567a3375b5cf2f6a4713eb692f7667fba2a3dc35363c3ba163519d95757daddefae11a95853000000000000000000000000000000000f2c72c53fdb1b0cd13a1f20407c64c46e4a0e461778b0e2d48c4f20be7c655c639b38f758fa9199b8395f706df10e7a0000000000000000000000000000000016e6c75cdfbc20c5dbc2dbd1caa66be92911264d407ce3c689ef3ae1dca44dffacb4c0d8a78ac959e47ac5c454f607bc0000000000000000000000000000000011c5d80d52e864b0a46fb48488f497fb85f51ac040c77b1d01336860b972858c0a6e59914112f6cd6c1612c604d26f56000000000000000000000000000000001136aa7eb63d6f85d665d0539975a9a51a9a3f5bd8731910c32130b1ec8b07c39eb42e4f61e7d22bed933d9fce1810581022e50c3fe7b2a65aab79de6d9e47c457d197e145592dd0611b1dc39941513b000000000000000000000000000000001306612f5119d33f177b8804443d14d04c8e059e28f63aa10ac6a1b25975327f378d5d24f0236e05849f07e99af93ae20000000000000000000000000000000017340f8887292264d498f84fce4af83573aa6cf1d57d99d364f2b84e1734fa4f9a1e07ddc81a2135ad5f5e0ed2989585000000000000000000000000000000000f65073250019ea69339379aacbeae7520c1ae10c8912ff827b702bdab2e15404cfc939389587364d811054b7d9f2b350000000000000000000000000000000019742f83ba0c9d36aa1d595fcedc3cdfa6c6f08579e66b8956fb32ac03530114ed4266738c57175e7a10313c8dd42deab80011c7a4aa905d4db6d4f6ae46eac9eb8bb18613d4ac5e5567990d7e8fdd96000000000000000000000000000000000b2513f906db531d052e8e6f1cb8d7d3c41c7ec3158b370268d1de204ed8fe7618b64ae35029d1718153b5bdb8439dd90000000000000000000000000000000001664c367a2d4170f463c90351cb321608e2a49fca6f3258bf10d32c39747084cf9d2c38d5241888aaad97985cb09a450000000000000000000000000000000014de15b86461cda9f1be69f43a9ceadfe7b7d1548a206f3237d93c7c01ee554c4245fb73827ed0ab72b99a62215faeae000000000000000000000000000000000b25e458522be9fbdde4554b1a0d9af157aeb7d3ec1f89185b193c0429125dafa554d7a531ef9502d443a26112b940b8f397789685a736375ead2312874174795586e12b230669a90d072fa636128c7d0000000000000000000000000000000006862c0b0e3d7bc4507bea1df82080745aff21b7549b372085776be2f88aedd4cff00ab8258aa21e63340963bd0d937b0000000000000000000000000000000017199c5ec3a2dbc1f1e8d74648cf8da247e35cb07df22629b3845274d29e473819a31bc344f2a2bd6c790530cfcc0126000000000000000000000000000000000e7fd1ff41d86a02014229c5085c886988dfaddcb60f5c7c81063e8289aba846337d61bdde57e276fe6c65bdfb48751f0000000000000000000000000000000010efa6aaf7650edb0c74d30125e36cb67cffd1c7f57932d92ab4aaf36f8d9245d7c75dc2b3bc8f3f328589b16e26230e28e325fea39d61269c576626984f85ea43cd683b08c3ce111aac0005adda39c5000000000000000000000000000000000935de4b16f5f9c0accee77b5820cf36c24aad9953d40a2409b7e6040f09f85da7d2252843f9f8005316146caae539800000000000000000000000000000000008a8c542111951b32bb0b50f7631f8938d22e298193edffefa3e0f5c861ac8205ea9b865f9420ad74cd22b37c5cb56200000000000000000000000000000000012ddd660879a1f52ae6284e14f2ae6ea381ff3f321458cb76bfa566b04ae19f3793468d0aab652a82671be74332a3b7a0000000000000000000000000000000005eb148c35732f7ababc73861b71fe4ea5e25bcdd675e975fadd0a9e0fc54e175b2e39dcf0323f4a9802a68baecd25df3cfd9bc41303803a0b4edd121b818a126bece309dfee4133aa5314cb8a91d08d000000000000000000000000000000000bc351eebfd3f3c332268055af1655c8729cea44eaae803607198cf747280adc0d3dedba137828834af3e7179ccff4c3000000000000000000000000000000000d8a6cca17e1c6ceace7c0ab1333ba76ed6c3b114bf99ff80127c6a17eb0585bf6fcce871deb7385e9a8896a21c065ba0000000000000000000000000000000013222db97e31e28946adecda10c9ccc9aa9fce33e0aca51d6483d2f0c5bc3f33994ad516215f8333e22167164ef5459500000000000000000000000000000000144d3707b1898d35c65ae2c89b1570971a9494e8bd23df835f565059554eb7b5cb66a6eec890058316aef43d6c6ff55c8e08fed30e422868f37c422d1efdcc93912d55b0a731479af863dca4705e0c5000000000000000000000000000000000138da93a9a4948d41a6fc6d057a217faf5efad863b45ae8eab311360c033362213edb0ff90bad6c95f60b8e1131336e6000000000000000000000000000000000f41766d9b57b3210d315a2b8f90aabe591c1de6037ec79c0d72a283f0ac3094436bb97b82b7ad12ff4f471a41227bb50000000000000000000000000000000009aa4f5b674782b7adce6bf75ad676480f96a58d68dd7ef8d1fa488cfab794f06e7754e9315430189eed265913db8b300000000000000000000000000000000004e2a4a48f02079c0ed50c1daa91b1216af481a982c7aa64d8ba90449ed886cdeddd0cc08f1f8764f7f8c5988fe677f5674ecdf795b48d62f0db0f9cce057fe570d15c78f2eb7a77b66e4895a45804880000000000000000000000000000000019c927bbffd96aeb9342666e1974d30f9dc215e8eca41c24244c63c106331ddad20d64c79faf8c5baa45cd30b561e167000000000000000000000000000000000523f063de96c9b77bfe5c5045a007e155b45dbe68c5f1162884f1d942bb385bd34c2a37e5e67e6dae4a23d600d75d1f000000000000000000000000000000000c221006f5bfc8baf43826258d0588d7c0fc345d68de1add1693bb897959c2cfdbb9c165e82c0c787529cd7be85afbc50000000000000000000000000000000004218e3d52b42a4504611929f94024326f38e78bba2aba105db3ffb4a51f8906b060ce2302e22ded60714d652a234c1f288fc80d07393f629ef2732879332a253b49d26ca7b2bef7cc49ee40530b2b3400000000000000000000000000000000189e5063a36b0edd736bcd9f997f4b08c62d33b27560e2e2b7b40039e7c63b75757f23746e70a330110d975ca683941300000000000000000000000000000000013393485ae494b1f1467cac9a8840c695d619aa1a78c40674038c053f264c1e20481f2005abc7f0545346f5a982d05e0000000000000000000000000000000003f2be501504f4d37e12acdc54b3280671ca0762a063fd3bc04473ed5a051cae3767044c002b7ed1abe88b2143af08750000000000000000000000000000000009d5952af88514996336e1ff19409e3e4eb3079f6dea22f9738f4a331ce842b151e0b842b68cddc10a711afa6d3242b256e69f4ce8fbd8f86f546fd6d129f9760edce7c5e178dffaf987bf565e9bb7e9000000000000000000000000000000000a79444c673e630f46bbc5a9e06e8c023978a78e3c58d72910a04c3733ad873c0d0de61448076b2fd3764cc17d86d94f00000000000000000000000000000000110cfd215d67d4a091578203855fa0e85feb4dfd0076fbfad20bd092fb91b528a4117850955f5fb6568fc5844e17bbfc0000000000000000000000000000000012ece0577512182c50dbb4a485256e705410108d9ba9c8d57780d49e2e25a0f89ed1fe917797b902aafcb8f7d98fe931000000000000000000000000000000000217cf1dffac7ae162181d43ef12e3e88da4840f1573d7ffa271f64d8d54861099be37b644e96e650dc613975d8a00a4ab40e86212189e6f5925df810141c132eab20c123166cd8d3c6f40f5dcf1b1cd0000000000000000000000000000000010bec428b2865aa7c077c168dc28dc549481c6f8367a5b84cbbad661b0225cf0fda3e840d96c4e4efc36c20d48f23d5d000000000000000000000000000000000ded3a1e9e2eded0a11211a217f9355070361f0a5887a7e19c74edc8768000311cb9dd8513977ecfb45416cda0908cca000000000000000000000000000000000b99ffddc79e825f0b73f2d0229d66e51624d854d00bdee5aa7a884dcafa1888963e2a2149db0f6e40ce3c67941a391000000000000000000000000000000000147618970c71965684bdf0d6cbe1de189bd23bddb2b861c9636efdcb7a96dff27bb1ac70485b562e78485a1e8e56531cb96a5b6129c58113bca713e6905c026c0bfdb6d679c203cbe2b256b0a49ecece0000000000000000000000000000000001a402aba8fb28dd37f1be11fca037baa99a6b57188ccab66208a50bb6967dcacd1943cca73e34f6b2e2f72407103a73000000000000000000000000000000000c0bd64d043fa4e3ea566cb84f9139091891231ff500b67e5fd451805f79003f6303352a4f0c236063d60d9088fae88c0000000000000000000000000000000002861fa7d0222711ffcadac86e7b9e7b494f5561c22544bd0876fb6e1b2e680d0f7074c2800312cb233de2412ccbbc8600000000000000000000000000000000015945f0c83e738a17cb1283d08d63ecf12a7272bc62812006ed78254bfc45ca7c42306cb79bb16ed17bea600a4d62b5d9d8147c4453cdeed971242d316e350abead3dd08e93ee54738a4a5aed23affb0000000000000000000000000000000002268793f6872f7715d802c0d96f3b3d850249d8e70aaa97f19793d2c92e7cef384aaac603eb51525c7ceccdd0211fc40000000000000000000000000000000002507d680a2db16746810e966d1ba5547ac98d08c8402aed0859203e6dae0cbd87a9ddcc05119c1ca08fca2fd733882200000000000000000000000000000000192426b6438b2abc7386599afbe09081ed4908fbeb807a65bcb7c6676aa76e5e0c2c87612cd109cb124c73b9c8e0591a0000000000000000000000000000000017f125a2ef5246e7a19e1b2741b31b9224511ffefe63ccfffaef1b7949e88af573e267d6c7617ea97bbaee6d50eef67e1ba8e52986d3bb0421eb53b18ca8c21b9f7e631f16b99ec56748baeb541b32e5,0000000000000000000000000000000018c2f533f464f9768308a56209711cf9b6653e8d38591d782ae2374905f99f75c0d42c63af4b534056c28599a9da874400000000000000000000000000000000071d4d708f00875545f381e164f77183e14faab599e472b2857c15091254ddaf5c2e9df809875336f92ebcf5b7628da500000000000000000000000000000000099b207cf6ed022289c27393c32d0b83aed6c7b57323e746374c1a8e2ade071a5293168e72f7aab82f6c2e39b97b03830000000000000000000000000000000005dada01b4dfb6a52d998210a67ccedc11d6aca2405e0836280e2f7c8fd7c8dd271c815a2e9ea1dba6f1ab0d6e89d756,293920, -00000000000000000000000000000000195d3f440857011bf9b764ff270b8ba1d9d13daf48933e49c12ea20d335b58bcbef1353d9698a7e795b4370ee385f12b000000000000000000000000000000000716c151efc6e611b5b15c749eaf02816a86e267428750741b167404a21116f2025d0d07c447b9c7bee8edcc2c7b76d30000000000000000000000000000000012ba0bf62b35327111d09b402db2b75b2e835cbe581638af2fdd6d06034774533e6501be3de84e7075e4184e11fd81a8000000000000000000000000000000000329b14859d004c146047b03870371f53936e078ddc69294ff1fd6f42cf2a354a921e5f2e5c125c454e20af97dcf769e7d39b55aadd47afa3cd35cb85a89e729ca236ada965b99f64ab302a84952babd00000000000000000000000000000000042286dd205ac86fdec3fee779059e2ad59adb62505f7b78606c128244b031c53dc40ebc2f5afdba348892d5ef4c10e7000000000000000000000000000000000f960010d4818846b3a0291c6fe1aa53bf0eafbc0e0968e3ee82324452a7c1a8041c06b4db9cd36a07c119c9fd2f9038000000000000000000000000000000001876da0dca72869708b8ff9ea0b74ad6be25ba82ccc76660246413a04344f2b72e5a7f6fddb58e9dc0bfaa6b33a5fadf000000000000000000000000000000001538ad1673f117493d998941d9356fb9907f70c279bde8ae8813b9c7b371344456f8e67cf02bf3401ee06d55604cadf9c41ece17a6d8b4a22994227b37a9d73e17a88859683afd5d226e113246e70cb1000000000000000000000000000000000d91319b4a5e047ffc8a68e10c34b2b90e7f3f08f9e3ec53ed12bba5f66c168c20c6583ba2016f0137caed834845f7470000000000000000000000000000000018d5542919674d2fc32430175405d806ae4abe3e1236df2188bf4c9ddf66c0974036e28414890212ff8dad244d11e3c700000000000000000000000000000000160b128f1ffeb97edf0e62dff85e3f90fa48567ab777a7937a2c0e4659df180fae4565107c2236a5f2808e42a03a4ab40000000000000000000000000000000003ee74d214ec491331fb9db8243e75570daba9feb587671496cea4b480d80ee162c6294c082203534bee450c384f645e69700dfa3b6e5fba735d1fec3b3adc90719ec301c406ac40673f4e5677da3227000000000000000000000000000000001951afa33800a366944c43bb42b8c5c8beb9ea2e1cead8b84e0f94af51e4a156d9454c0f08d1b13c692c41cc480fdefb00000000000000000000000000000000077f4543fadad6f2f8ae8d5d98f64965bf9626971e7efef5221cb4d95d51b8764324cf4a11d0ff5330d58df70cb79d92000000000000000000000000000000000417251cd0c1b32505377e51bb30ac8a8a3c059644b9ddb5a058b3c6e1110e1c71ee19f549b15090144dcf4668d0d50100000000000000000000000000000000052133be345adc562238c4ecbaf76ca4159fc11ff563ab393317b03065ab668e7df401831baf7027f0577f5791b1ca3019e8eed297661c06c92075629e163e80a08835254f7af8c0f179400be114ba7b00000000000000000000000000000000067bd52b7a3193d31a4f1ffb76432c8d4108442616f17056d310fbfee2ffaade9437e2bdb8425cf83233f0c632efc1170000000000000000000000000000000011b045d6eebe1bc8218b696b5e81e78db78eadf1b5d987060c1bdd73aa65666f77e1d6bb6f3d939d64cb3e6bda08994c0000000000000000000000000000000016eb5ea5067413b72632f5300efbe0d01a284b2a59b68d0333c269da9302bf0f0cdc923acb27e51bbbbc1d4086e6b06a000000000000000000000000000000000ff37b8812963d9efaa1e6deb5cfd34eec70620fdb65808739295a819e03ebcc8f501b8194d0b3c72717fc922b785194199ca6fb7f6df8a2e72971c5738ad75d84935e922587acf3a6b6debf3c37bb5e00000000000000000000000000000000149b5e0df255281c1b518427094cc0903fe89eac9a6dcdc379b8ca30f3696d89824c201601fc4b0795a3c859a82893170000000000000000000000000000000016ee9e7d957f439d078f3c5da98d114a1b5bc4da9c17e117e1f540dcbf83a349bba94def4b87b63247f190e3b5813cb00000000000000000000000000000000005d4f56bea105be4bf1fcaf4f25df30f85968d59e60b1c438c28ea0f480851f5ab9c05a7ca6677e6f12c7dd3ed67c2e0000000000000000000000000000000000dc0e87ca5a8b339b485ff3da2b9854a07e9663c43344dfb5ecf3ea055eadf67405c43013e15367fbaa55f1bd8e222f98159c6b98bce6ed31c30957280d8f7820e9376093d1ec9ac68ce0777d02b084b000000000000000000000000000000000b0575fe2adc9ad66209cb2191efc2946672e4e81b96d50493d2125d9c83165f0c4d3f714539eecef9de0706cc20da9b000000000000000000000000000000001511649f0cb6b86111d2830812231ad37df5500d7ce1086241591dc3cf40b30f1c53dda3133b2f7fff253c94d5eb98720000000000000000000000000000000005b15e4e32f4f4e46c1560792a9973f6ad63f5176694734f379375f16a08c162a4a820385d3ea6c191bd87fea4f5c8cb00000000000000000000000000000000089218403fef08dcc6e679b49a74557dafed3278d41ff36a9801db091b91de0d46d779a40574fa4a3f2baaa1a14be098ef1bc580e0b52b10b049f07d5115a60ba96d14a39e48ddee3c219f11c3b2a82a0000000000000000000000000000000001c35a3fdea92b28c9ab4bd9ea592b998853a73be844b9dcb500ed6704bbf3ca4ed4216dc24b50254b6ca75c4ca3e7fc000000000000000000000000000000001815292d2a365dd7f41ecf3f9a89e040bab717241cefb3155a097eb9885d64fa55f5de7023f2ecfd33f483ff304666520000000000000000000000000000000013df522c72805b890aef97864ec6769f569504fca2d6a6beae97f80dc92643f8014daf3dafc0040dd7b985c0d9b2c462000000000000000000000000000000001155ad4373a8304fa6301cf48b4ace135d6a0c08cb06d624f42f88073e43612ced3cc37235422171b43af2b4ebbd5662d06f6ed682c56611fd060ed2b3b1dc48974769ed6dc504ca3e0b9f68b77e63c5000000000000000000000000000000000bb9afedf7417ca31beb96486b024af13c06007585d785efd1e78444daa9bc3c03e1d64b560e8d6a18ccf77a8c3c8d05000000000000000000000000000000001652d3adcf1612e487a9ca198801afb9ec30267148502684c2b91c05ebf6c48e2ce33f9c0a986daab81d5359ec1b503c000000000000000000000000000000000baf3d34bf4a78e3b9dfa637c6392c7f4d7ad0ec315d10748784b5b60221bd9da0f4b75c57c139ac2db329e270d559de0000000000000000000000000000000000c30e553fa2324d552bdbc7d2dc86531340c4894495ee9a38b64f5bb6f92314021a2a00c4bcd8837e55a0ae2676a9b761d7b314ae9d9e78f628ec5a207d12e2dcb690688d256fe46e0affdfcc9775ae00000000000000000000000000000000159a1e4e87c35aaaeacdf21efbf8ed99fd6a2ddd7e990c12407b1417edaf185b8f1df9bafbddfaf3d581b5d97d7718300000000000000000000000000000000012239ef7b1e1009c81098aa4aaad8ee9e003530db5afd49867aec47f46d5e29d44b5e62d80d9e832937a299633e863c80000000000000000000000000000000016af6f74392461a9294d9f848508651ca5c0cb50494ee7c6a334bd770580b924a17beb7824b489e7e101ccd50aa0d5cf000000000000000000000000000000001912a0f54ba4fbecaa55c150ae93455e1db6b238c032fa7992bc8456f183c09b6005dd6398a77ab91cf547919ce7485b03a0c47621401fc20d2c78f7e30814de9a6f838d4328a5b5be628b833c31a6fd000000000000000000000000000000000cf1cf7a09a12f51d10059425042ef8e140718ff11d2f17897a0156034f73ed29496d93b8695cdf609280d319c9bb742000000000000000000000000000000000b2c4d26fa1eb72eed1a24f27229d2675e0c6f91e3a4eba7d34b0fc1bf5a9b4eb49c3492d9586669abaf25a656e1f95d0000000000000000000000000000000012c5c83a03087b2449b71e9037591fa265d710ff6d869bfa18ac37cbdcc93024f673128db3dbad9e3517501af12f2540000000000000000000000000000000000ffe5824245e43953e3d0adcd5fdc1a97ffc87f8c5473fdb0fed57000fd126a9925ba7415c698248c51c1f3e12b270d5e4ac6a5e740e073c5ef8af389e70c2cb8ee8c4c04c2ab4c48c579e83e181005b00000000000000000000000000000000036aa888e40882b2d6ac71d66c88543e32b4a0a7c959eec560e3d26114d8aeca63fd87dcbb3171622c989a6c7a204ac60000000000000000000000000000000006a5e552e6d2dc95ab8636a8be16bc79572b47860bb88934bf04c195ec01fd71eb91e45f24c58bc2812ed5fa10c8dd7d0000000000000000000000000000000015fa3ffcbd4e562a4bc29975cf8c1eedf442e37374fc87128e6f68bcdf6e996f6f054e0b8c608e651753de96655b2c100000000000000000000000000000000019bba7c0b170dfc1f8fdbf7a2e09ca0c4027a6aa6930d15dc2772a0f20e5e56f0d11644094dc866595f801ba5552e6c4c1e20d8003fec60f68c03942185fed934ebc197c2863174442d1a1c8d1424d31000000000000000000000000000000000341f46ec06a8def4f044328bcdaa308798469c767d10e5db34b0ffb6f550421c67c6fab7b63cbc7504e55847cee419e0000000000000000000000000000000006952e5f791c37dfebcfe69cdef196dff66563b29e94927e3ab34365773b93e72251a63af4ff294af88d45fe0899a2c3000000000000000000000000000000000874dfe75b31450e99dea063c090e32d24fbff9b681b64a9dca5f967f82003005b003d17eb869bd3b37d4a412bcb28fb0000000000000000000000000000000014203b69e8af4e25232777f503d5e82d6121256fafdff1b037f65d5aaad0f09ce882151d6bb4705328400f00089dcc7a7713ea72a2ee99442232472ab3dea9307a02fa1279129d994af5588af4fe7af4000000000000000000000000000000001403fa3f418107e0bf7f3f4bfcf621812d32b1b744ab5a4c37b5cf946a5e5dabd675c2b70bd355590a9883436c5e32dd00000000000000000000000000000000069e006f168bed4439fb46db9ba4f279f72ed608c12a05eed172608693f42cb1f04aaa54191f4b0b35f967bf03d0e63b0000000000000000000000000000000003f9ce029f6fe605802de64701ccdf52bf4aa299400a6e1c36f5a1f9173bc11a38e7628f123fdcae01d2b260f77c577c0000000000000000000000000000000009c9732809f60635115cb479c80457c6cd8dad092111d663c0cda0da1fa71c9bd6795ad013d2efaa4599c8ac5c88e5f26f128420cf6ab4616a05b287191105f25c7212f2c39c3230fa56bc27cd06ebfd00000000000000000000000000000000115e08d8e4dff7adcfe46a416625be0ac26ea2d7900f5fed497809a6d46e7faa5b47c52ab3bbeb9fb16d82b549707ed6000000000000000000000000000000000dd1b31446e44f64ea5046dca5174ae854f6bb5d95886fb95aa136d432f1a8c03ef1a5f9320f89c82f764049a7f678a40000000000000000000000000000000014879783c07e6986cd393fa1e0ca8a7e23b2c9efa595229fc0b6a11b9c232ba33e92962a1087fe2ba0532d7b541827900000000000000000000000000000000013dc6e2bdb2801333e7f914b99f30b40125fa1ebd49b141d88a8c090b15ec3250a13812a19c3c0751a4e5ed100a6f0ba12bacb3419c34369dbfd1c968334f76bc50885028758a975cc812a04e6feabd6000000000000000000000000000000000a2cceef36ec78dc702b6731dbaf8cea1dc2b41fee1b235673c6941729bc5631e69ff37900479391a4d10b300fbf3eb40000000000000000000000000000000002f4881fd626f4ac434bc1e59716e5e5ee14dcb9adca4d639ebc9d86e323d274ad8ec0a4b1e6ff92e1fe7928d48924b000000000000000000000000000000000174cac80e7bc63989f58759e123513b611e9849b44d43a362f2eb84421ad008f3ae9e9f0f233e49fc8e10c1824ba948200000000000000000000000000000000143641099c8a6c8153dc8ce74debe795dd6c4487e8234f164f9f8dcdea6a53619c04a8fac215421f985557b5b956c20a5b00f26af6f59620c7130a6d12cf2091b5f52a6b638484fc1f242dc1773be256,0000000000000000000000000000000009807ffe8fa881b235b1181d2d3f147dbe21042524fb0c0b4c90fb122d160c7b895034ab32e20324dfca564ca6e3183c0000000000000000000000000000000010f6da88525da3e86ee56cd5514a436e3ce4128e437a876be130a70c42444a05ac269326c84dca532ca2e546860027c00000000000000000000000000000000011396a7317918841ba171ea46bbddc9bb5a08db7c82b90008c6982b4b79a4dafc151081bbdb7b9fb79784e603e15eb9e00000000000000000000000000000000070b8580f303b83c643a484dd031b780ff4ca2ec805d8c538a0b0c791cc7f8163654f5e5a41776a8681500a6690e24a4,293920, -000000000000000000000000000000000f38906bd058e4d32403fc3d39fa57bf49c0da65ef42fb129332b91c184185de4f9f0bfe8908a44833ff4ac4d65b88180000000000000000000000000000000014ea6fffa6dc462463c15feace841697698bc521f608ed0d16be5097bf42aefcd1f73182f37b6279f989e9668a8076d1000000000000000000000000000000000f56d296323177ce53c6977fb60e445278e59ed1cf92e3f68c570eb7a9e5f8afbec5e2ef64674bbb54d7016c829f72750000000000000000000000000000000001b29012ff3460cbe4a07bdc65885718f217cf177866823a7cbeae18bda67f65913ea20bb69e0ffb31bd82f19862113dacc5a8ec806f2f273120457865582b08697904a2c6510bfe9ea21eaf682fa4fd000000000000000000000000000000000a4126bff91ada057ceb9a75d577120c7ac8c9ba62151602414364cf88a3e12dfac90b5590db3e40c16163177ad4e7520000000000000000000000000000000004a3768d326c4ebcd5ffed89341e8d04f89e674f3f2aded3205a7193e11c20115b3c4d595b959d6e39a03d76f6b5925b0000000000000000000000000000000006e0ae4a9c45bb69c3a1c65e26e4869f2eb18fefe584e4598ba99c0044e8d911145a5db3f57194ceb6201e7eab9a81b20000000000000000000000000000000005be2ba6b147f3f2052c4877c90ca364427c6721ab64dd35e89f14f3179564d8812b9013e3e3db22f69afd739229682b98c15a259b4dbb8c300a39f0af558a9827112f6b4c5eae3d43bbfe057eb113cf0000000000000000000000000000000004c36cf955fc81bdba4ea8d2ecf934adaa57fa4073199f77bd0428d3ef80a7d7102179d4a44ef0de887bcb3ae915408900000000000000000000000000000000138bd3ec7a1b6fb65d1df6bc1d2ada35aa52b06729c10b5d45b9bb7cbbdf41677942b99eb9c2d32e3e73da7d5f9cfed40000000000000000000000000000000000b0291ca10245e2f7a963fa07ec62b15f6bf9e7a5a7839840ebcbe538dfecaf2114c7864a16564a5b3c85c15d97fb7a000000000000000000000000000000000b436e912b8a71cf8050d10d59017eca6e494e5440f02d2816924ac9cc2034bedb1cce6eff5c42f3dc57a74cf1b51cc0a0e68bdc97fd642581f7e62ecf134df2c05570713c96fa733d3db96ace88f0f0000000000000000000000000000000000c105ac7475ed9517a0b07f25a030a5616952d817f3893181e352907c7cf4ec9f5f3006e37b1da97e9cae4a1213584e20000000000000000000000000000000002c112c18268934823d5946d2322d0faec497d8e18736da91d2af744d90f74136c49370a4b43952152c62820d25e52ee000000000000000000000000000000000fe2818a397d70543e752e7022f12bab10f1b1289cee61a0230d545296ec872e34d8df6edf7ce9980f3c153e6e51d96d000000000000000000000000000000000f479e6a52bfaab3a31aa9a461adbec8a390daa8eb6273f9e425eeed764a6dbad44d12778bb888aa5808df272edde401e5512cac411cd103fcd7497fdf47d1221999bcecdba30467f06ec356483484fe00000000000000000000000000000000016106cb42ffc41d5b23bc5b06001473bdfe556d375fac6a0cb0a12494e9c02ca2dd6133356846e1759a2c485faf5e890000000000000000000000000000000003cec25b0f5d1db0ead5319d6dd15517657d1fec442facda4335ae0bbeff606fa9caa6a4c00445001180aaeef895d7fd0000000000000000000000000000000016ce3573fbe27a8d23b3ebd22aec989d61fbd0e41a519c5e2f1d650f2ad73adcfc8c840fb12bce83b722a0cc69164e21000000000000000000000000000000001434d13d44fd8dcf776c2a045734dff7c09ded31c9e3a4b5e765cf26fbfea4cbb4ac15c06599012a7f2cd572bfafd78ba32f6861298bcfd4668653544b4551d7357d64f733365a5f08ebf297a09fd4ca0000000000000000000000000000000019923ffba0d08ebf1bd43393142d61022430356081c18e37804172082c7ace987ece2594f4852e84604a77235c7795e000000000000000000000000000000000123acf9e1a86846ae27d5fc0358afa34fe9d6b68232c9ebf2d47cc169779c4bd24f225ad30886fdf68166adfd9898abf000000000000000000000000000000000a6061d4cef29d1e3535d54a2e36373e2c16f91543f53e1aca94c4abdabc663049673f2327ea8bb574244d7f5c99e981000000000000000000000000000000000b1f3e1d43575a74584ec7a3280f8b7196f9b99b5e911ed33ba6bde1188c82d906f0f8e6fc2b285fefa0ce59116e449524301fc5c3ab842d7f6a278fcd32249f1daf86a31dd254ab9a21941fffca98a1000000000000000000000000000000000373d36dd0fac76a0fc46ba5da279ca3be5a1f8d799570004e429256787110d4fb746f65a8527d0ba681a81b9980bd5c00000000000000000000000000000000057933c2b3e482ae026159211c4742264f7e890efbaeb6e14f3bf66c80923289af095dc97b751a117e181ef917d049b000000000000000000000000000000000068816ad2369bb57b3430c657284858d3736c327284e7410b61ed444786bcb34a66db9c16aca583aa9722aa8d7975b440000000000000000000000000000000007fcd7dbc062d28f6ef906f6a455337e517e1d6e6c02c7c0b2b2685b79f56ca3436c1bfa0ab96e4a5eb0c2e2c321c0dc17a920aef58100de67c482ae1fabf7ec87cf3447bde1e19d9aaff825695706740000000000000000000000000000000007bb0ab060cc12002e043724c0fd0c8bad30e08b65ba9f2fe5d09d18cac4bb2d50e29ee14590ca7bfc505f3ee3d4f93d000000000000000000000000000000000e680653d29eb5d90f21802f543eac3102a1de6d2a5bc943a53dd9b80bdcaa6951ced2eae5e2a25448b40468f1923ebc000000000000000000000000000000000b7494b494019e3ef36d5c620ac56483fc6b1c8fe5c6f67537b19f56ef01db327812095fdf805d3dfe678a3ed8bb6226000000000000000000000000000000000291e5b98ecaf7aef0374647d28fb9f8785a64d9165de407d062403047da14d4ecd19fad8575070b278608e16b71d387d76d5eebc3d099448ce4a8ea6dec047b0f062c6361ddb9e95ec898442423a31800000000000000000000000000000000186536e3ae3edd9cc6bc24fda6589ed26e72e06121e97e1ead65b200fa0578c6e53d1154dc7b14e7eccc3a53237685060000000000000000000000000000000012fefaf6c76ae7197b99571e41a19b14846fc4499e8e964ff750e7c3ffef6ab3dc19eeb42c5f6ba44a573bca7a15166b000000000000000000000000000000000a135db813a44a21174cea3a0b34fb49f273877203ccb66bce44b2b58794818d8bc1df27544ecbf780823467e2e4ee6b0000000000000000000000000000000009b08f70cdf4e349e1a73935de9fb2ad9f4feb8cf5f835be78383fda2af94d81af253ebce08cef825764151d5713ae60cd4cc1453dec7ae335db989886fc0964ee73e12bab69ce1f1458d1416471176a0000000000000000000000000000000007976df2d47c14374e554401c4d3330bbf6f1e6b8fafcea1e1974af61e8ebf493dc0473d34b30b0b1cbee082550d85c200000000000000000000000000000000177cd64db8334dccb17fb207e467e5b09e891b05df7658d9b439e3cb72bf3e0a70e84f96fb5e448f33c003c279cb38d800000000000000000000000000000000094d739a02b8ea6ff8113019597f41df4728b270770edc5e68b1f5c32775f0c706e3f31c0a82059c1ee150b89097376a0000000000000000000000000000000006ed888aa4bdbee94ec67500e30d654071774fe22464dd5b900fdc17b445754293504b10d044aac8fa0c289f0b2d9dce6d207c08e51d64a9a47f5353faac77fbb184e1123d38e39bbada85534cbcd3150000000000000000000000000000000014a16b856b04ac4b687c79f2b4e1dd6d45db25b382e0ba6687afac648c9b6384cdcfa89812f1a726bb4d1c22ebaa6668000000000000000000000000000000000764088e337df6db30ce8aa23aefd91d9e35be911c9e89ac62a1e06c3d06e28efac256490400fac4490f595cd03c127e000000000000000000000000000000000894856fa1c8488fce182a9c7749f7953e6a73879b6e743fdb8c780275447122f512806fa83d5ad528f8f61598ed01d20000000000000000000000000000000002b33bfd09e0ff452c3336bde08df0102162488bc83c27052447a1e5d16c9c68bc529f96ee3787a26d2009f22a1246342e1910b704d39b6a64cc7a44e44ba3e8b7e64ddfa90dfa6b5ef571f9ff7d7f0b00000000000000000000000000000000133e2d092352d3ecef5b67a09c2be268fcd4fe1f7360a8ce3ef5f33bf689242961a140d9c8afcc1e2fab3ad4e3dba49d00000000000000000000000000000000101eb285f0c462a22406846d82ca6a278520b65132d2008b124f6647a642c221b0c3bbd4a0abe8af7417e7aefb81b5b20000000000000000000000000000000010958cbc317f1186aab69ac24be87647b8013b678b0eabc6270167bdc9c0cefbaf4d9a34dc41524b709f1b881e6bfa34000000000000000000000000000000000d92c47257fd0c4d6baa4c81efe65852840479b9bfda5cc06b253f167069ca7367924c0c67d6497a1e9abcce7d0ce9502eda0eb154d5f9b0e25a828c6f77541701004cd0293c61ae4d36aa3038d0f1840000000000000000000000000000000014ad0f935ba129b47ecaad63b9dda44e7ef7933f182a0f5226141c8f0ede026ca2f11db7f4924b5c582461688dad6359000000000000000000000000000000001453716381f13bf6ebf8fff2ed7bcb90f7beb44269008af5880a355dd03de5c84c14f5aaf69fda043b422aab0c694784000000000000000000000000000000000e983c9e9b799eccfdb56444d31948067d46adf275d7f39a70aaa8bfd0fe1b83632c23d87f4e993c8191901e9a607217000000000000000000000000000000000267c8b8c5e09b59277736caad12ec6986f206d1c1f48023356d8bc877a594c8bbd98981cec6382bf9bdb9a5fa38275ecaf6dcd51a851eb200c7f5fc3e106ac5ffc432f756b942b1b9a5dde31cb2a3760000000000000000000000000000000002e28c245e71a7f6206427ee512f3250612785ce29b369682fbf767d06ac08f91de8ac9f82951574cce46cee1aa757720000000000000000000000000000000019b0dc35eacd961e0ca7d54a0e37c4ace37eb0200d5489316f3371412717c57c8f17c1379721f4dd67b3fde24f50d4cd0000000000000000000000000000000013b9741f7a32e5e5b1ae5400e32dd6fcc1fd43b68df54ade57c934720b1289a51deae77b1726e1955b6430f37928e2bf000000000000000000000000000000000693980b347ed7ee6cd93f565c87efb36fb304d7e9ae24e2b9f902bfc962b6c7fbab93287147f5ac892db2a709c9ab42106d4a893a68b7fcb8be96faedef65181c239dc2cd752c85ae7800ca84fc2dfd000000000000000000000000000000000ad6b7cfc6cefa5783093b7d700360b354d0698d27ecefb7d5928ac5bd6c299e4001474d205cf3b85a32c600ddaf1a360000000000000000000000000000000017172c3d5acf59b70b340fc703e9b7801aeb4857ffbe7a9d5daa0f32ad80d1c0ef2f0b3b7d1fd83a757c076872425fc7000000000000000000000000000000001291f55fa7d14b14c578d57178cc707cabcdc4bfb444cecabda271cbfba2ab361947d045ed46d9edbd215fa4c8164e56000000000000000000000000000000000f64ed6c989eec5222239d888d08dfd638a0e35eff2266410dab0498941fcd1683654064107fb7e53b8c02fbe98a25622b9e1cfbf140f4a3b1d06be656ad6ee5169a9cfa7cbe6efbf8173843d406acd30000000000000000000000000000000001d25b5bfcedc6d7ff7e9fcf729f858759936235d23ad45b14dfd0229bf3e50fc68799d19ef019b36728285bf7ecd0b4000000000000000000000000000000000326e300ba07935e0233a03ac891f18dc7b5a9ad9a28264136228e9e23e8f2aa31b7f5e5f3cb3354984f57a868a5d00c000000000000000000000000000000000dc92060e3403df3a92b15ba3e437ef0c403fcfc9c3545e544a78874e5d9b5e63b9ba6060c29022fe2594c2e6fbb6a840000000000000000000000000000000006a01e85f59dc45b1501309a350137d71147c30fb70da6b7637a9b1dd884aeb7e554215474784ecd3bef18d15d2c0524dbc68f77d40330ad5b8cfcda42edf57899454571c6c6465c4107e662a269aeb5,000000000000000000000000000000000b7fc0b44723ff0d1cb7c43e470d4d432fc4bbc7f9d98ddb3d91434a5574956fdf15f898e579236426ea44677998665d00000000000000000000000000000000176586b6f157e408138391e3767d0c1c8457857f4cfae571267ed64ac86ff8a4b61a28b406e1caecffaae6a399f4ec9c000000000000000000000000000000000a420992f850db20d4f7d2ddff33e4dc79bc0c39caee533920c5d03d1c2619d8ced769ac09f664c0921829bd7edb446b0000000000000000000000000000000017e4000f4d03a6707174c3adb74966896bcc0eaabf4ff83cce92a666fbd13b59efa2c767442062b6c8b1a3abd604f0ac,293920, -0000000000000000000000000000000016ac5146ffc26d1f0c33645931bddfb84756e1c7b03f4838467d1b6701ee9478ae03c910b6b4f5c14135487bd2c14bf8000000000000000000000000000000000e1d082f16e4d5c5f0b6fbe5178aef6f781a6fb165f775cf0cd4dd55f2b498a79edf373007113dcdf6b977a87e1fded2000000000000000000000000000000000bb94be280df1aed761651c0292f88037d172b675bae1933ec12323b947023b437d080aac9e196fe5e06e5c4137284d200000000000000000000000000000000064190f9725bfd5d56c31aef7fd1590c975e2ce549fa6d5c71b254851149e0a0dab0b9de3acfc2d64231c79bc0ebaa37ebb3c942d3a1a15cee806fdb0fc3635483743a5b0ee9c40a48700bad5da53ae70000000000000000000000000000000012199d02d3f1bd8c4005878c3302e6a731ea69d69accdd690b4667e847b079563d32e18eb7a440b8005ca936da5e73cf00000000000000000000000000000000125b0dbdb0058639513b007a84d3a3e6302f5d846f22f99a55181f097e200981d9013c00d688a11eb976120f1a5da64200000000000000000000000000000000081e723506635433528fe4a40fe4ecb8a9c3d8cd701c043c0418d149951651e21632cd85f03db33b89efadd69e009ebe000000000000000000000000000000000956af2e67f8ae676abc783c4ec9f85c50ea130410cee8216fe036cd0521b8ea38966288afe7d35c28b30f7ca5c6edc0c193d751c4f24f4808621979f07f03b2eabba75f08bb49682b9df2da7a85a7730000000000000000000000000000000003e11a4e9dfe82cb495e9e698b16c257ea3f4ecb24749751e7334e0f31fbd6677545e4bf9ff78a82853560f7e7ba2ae2000000000000000000000000000000000caea2c527cb3aeae427e92fc364365b1f55e7128a544009be2ff7a5236d1cf8ffd5a5cefc87820bff5bdf1c6bfa165d00000000000000000000000000000000064a3186774da8bb5d013debf46ccb0d894592c414f32de6f77da47f4d42b0c8a13a2ba4f14b9883d564fd8ff6a4c90200000000000000000000000000000000072f6c48b6a05039e3a4dfc6b73501d6d4ca7e840b119da9c074bd4cd2adf4f2c6e9e6325ebf6f97c3f0b00e6b9bfac6dee4eef524f133183b4af95e4445f3ee5084b96c32e284ebebc5b87f3d76150b0000000000000000000000000000000019ddf708ab31f6f6f725f0e4f65d11248d3a79af30927a6f2673901fef9819b189502cb952bd4742d2b8e84acebb5196000000000000000000000000000000000d928535c47eafa5da4ce4f91467fc31aff8b86b850e4582a597b334491b14da71763f9aedb15ed32856382069c094ce0000000000000000000000000000000004d6b3545d067aa0768cda9dc3cca0f58eb546345b96f7d6b9355d47770e00286d962a6b3a64ca2ce22fdb4834a4bb6e000000000000000000000000000000000f4ef9366d342b309076299816c1ed9b424b68886a5c69e21e785f97cb0f99ae3a99ff6b5244dab817094449048a7552da514f21c8eab0edb2405e673297bb595edc21027890ad680f1663fd960ce478000000000000000000000000000000000236c5b4c57ee4facec5d4ff37a478c505217af66e029c3382613442c58875c75cb423789f6703ff3c1c0d80991c9e3a0000000000000000000000000000000016c052de3336002f362d9b0cb386b800860527e0fe81a1a6df0ccde31f3265e6246191b3febd1ea48e9391c44593ab0700000000000000000000000000000000078dcb04ca93c676a9a924e59f924d9d3af872849bc30ca633d4025aecd981ba12e626337635ea77886a45f4da84104f00000000000000000000000000000000027df6394b195222bb8357bd684088e3e2a398f0fb0cb812ca5dcdcd1fa1279cfd03db62e0f8b2800d4b8b48238931656aeac9a669c962817c01069cffbd948d9d8ce764e92859f31fdaf85f5aefab77000000000000000000000000000000000485ce58b387083172102145fdb3e26c6ffca8b35af0e1d84ce9cbd89055be083bddd3da56443924049a056fdb2ef092000000000000000000000000000000000d998b234a69d584c78ed054b1322ceb33f73cafb5b23c1703a9fd609edcabd44f1a642802b9c0b6fde6a6828b50c1200000000000000000000000000000000019235ff13567bd007d77e4dfab139cd57dbb309a3cf6a6198a548c4e6915778094ddf2b05a91f5478169757bf5a56cb300000000000000000000000000000000110f6ea19a7f62bc3e78f4c5c1c6d3efdf1a7f563576e758218b2c363fff8ad8fab0e72431619e4ebc93d2d739fc786c40273bda92c9b1b677edd905d76d75875e5b77841befb2bcaf1fca7674dffd5a0000000000000000000000000000000001d45da76e3016c00fe65bb50f7067e4f06364ad8348184831c4932ea0e0f3a170ab5147e4670ee1b16924105b6fdb6f000000000000000000000000000000000b3468206db0613369b2b0750c98da65b660fc07c30cab4e459c311dab683b6b313b99ec0fbe92ba07f8aab43a12a2c9000000000000000000000000000000000f58a57c449a41105837d5e2419a34201cc921ec77408d6c0c7a2eb227be98ec1f6f6eb9fc088daa0d4c78928a1eacda000000000000000000000000000000000ba53b872dcb9fcabf35e673b467523ea77accfc1b38a5f92d7b9d269c28aa00d00b08d70eae6ed4d2e82bdb06008f9ab77e16276f9464fa2063230d6c1a4152553536c610062f18565c030e80b5cb540000000000000000000000000000000002b82e2b582b247271543117b939fd17ba8bdd617a223873296f7bd75de4790f0d5d8fe523792bc7fb4764d3739669d80000000000000000000000000000000006eb554347efc5f2ee79949bafc012e6d9964ce19459b3867865709d903fe3d11bc617f30f6279a9e62ea104565953600000000000000000000000000000000006a543fe5cfbae629fd3256575e3eb4e0b65864aad6c7f359e169038bf090ed9bd92fef32fe1ac20b2a8c90fbb6081690000000000000000000000000000000013ee42b0693b2f3b9b977fbae5c856e9e4c5e70120b5c29e0a9f898f6d04b7fe351e17b02716a44febcf0a00a9cdd9220be15b654ce22ae4e32987babc4863ffe2bd8a459d0f01f68fe84a75326889900000000000000000000000000000000001ae7368f84e354e5758554aa9c72ab4b00a644cfb9a4ecba38dc72227d297749bbc98c8f5d6149143b31442359d8013000000000000000000000000000000000abf087f77c79cb8c69e4289fae87b2ed483442daec3851a5ba32c43e342be29433b2deac6dbfa7a787547a7361ed0a00000000000000000000000000000000000fa01cff7aea64b649951a8d85fef0bd475f31e47c706b96ee2753df9987508b5e5456cc49e88ec3aad720a2535f6940000000000000000000000000000000018874d020e2eec0e286dce324b91f15b2a4f293d32956b27524f478983f0e0c5b43df802b60f4f001753f12d449cd821c8f1fe94bce21966427380b6d357a3599e9db03a7694159335ffba26fe29e4650000000000000000000000000000000018f7d19362e2cba91023455e115cd90f02aeafcb026349393ca4105e270ab1cf589621b40965fdc9795f66ea0f6a053100000000000000000000000000000000170ce0eb304e0e1047617b709c834b67a8989212e5bf1cbd5a33242be94bb141d5366e636c01a229943bead9a7baf43900000000000000000000000000000000077a17356b3b31faf90f709042938b9e901817f7379b7bd486d18e47d22b0430ba70fb3006e9afa67d7dac71ffaf152400000000000000000000000000000000064aca92c41561e195fa8239800c97d5242ff0f8ce76b0d119063e2ffa09c26e01d23d5728765a59bb9587e885450ad1c6d34471ed00035a484f97f4e8123d40ca23b017b94df65540a5551b905e57b3000000000000000000000000000000000876a57dc24ad58416f910ee3ce220630a1297e6bc691c908e6cc16f975b146872d71661bbb869361623c61670627eb0000000000000000000000000000000000760fc65097d215ab9aeb3d5a5153977e1e399e2cc0b0cb9befb0266d98ac13512a0eadaba4e051bf56794621c551ec60000000000000000000000000000000003c8e205e53075a96c14ec26345c75881a0d67c7ce0d62d73c83dc353cd7b555cde52ffc5659ab0db2179a899f0fd694000000000000000000000000000000000d7e8a7fe6b751f7f478698f4f0d30cd0a435a2295a958cabedf4668769819b4cbd4e8b7721eeb5ced3f913156abcaaff3abd467168bf5e57f71017b5779bdd400dbf416f34f105fe747ea2f8cf4a21000000000000000000000000000000000180546f697349adb2918129f4d0a979bb114d1b58e5baa6cc221a09d7083469bfaa61f80f1e3a6ccde0da54b24d59db70000000000000000000000000000000004074338380e3d7c0facbbc71d83e78b53191af9ba13ba0cba6015bf4f28e4b0b52ffb34c7867a335848f57b5ce5ef5200000000000000000000000000000000148a800ec38cfc2386497d9aacb4327d5953a6612cd4067ac13fb977046688e80032125d4b0e7cb49913e489796a50ea00000000000000000000000000000000132438d18d942e6dd3f69d117abf83c2fa18418e5145cc43b3cb8d18c873935e41279a9e13596f2863be7aeae9b73d172809801eb18d38a61ef8a80f13086d6b1f85ba751cdb8d17fbb9ad5f8d0f835c0000000000000000000000000000000018b3102ce91af86cd10162d3a43e488a0d7b7807dfb9624c3cae76f342e86f8ef1200444a57e2ed7f819828357a6dfe80000000000000000000000000000000017137b470f3c8d1a03e7252e18f4466c9ff809408cbb2043d6b226ae2746d890b267ce3255114b2e073eb66e93c55eb200000000000000000000000000000000054dc1c981c9166d0bd3a54064c33f15ab856b240770ed44adaf9f32d4429babcd0baf2c5b8a1ff80728e9c63e806cd3000000000000000000000000000000001897595f836342ab54bc2e1b72f433bfe3b5bc989727de48575abe89386aaad9b1549af3ca55f39feec14355b29dc9e33521c9cf035b094d754db994fce3161842a9509ec8288699680c0ac7761eac68000000000000000000000000000000000467f1a3093c72aba4c2d9e8171057cf88146eb32f38db0761a5ab2027f2213c89e12c67a338b4b342a73384109988d2000000000000000000000000000000000ab26c871d140c9c4e0512afe9fb576409ffdcb95417f8c6cdc0d964011dfb1e745045766bbbc08ff7dbd6935934bba300000000000000000000000000000000183488902b886200e63465098be87a905810b2e8ebe0364316da798e423dbb267743a0d2e3d93303623fb17df0e74ce30000000000000000000000000000000012c7e79f9ba36cc47762139d191e6625c850a03d5b6e0648032d1669575704c91e48a9ae432bb3553ec66e86e082de689c8c2998d141b9cd3a82507b6dd97e8d32e9e759169c575eb484e9a1559427da0000000000000000000000000000000012ef4988956e026a79e5e904ad3d7ca56793321d62cad46de3cbde8570be5f0ac86d386216152b37053741fe342de7c60000000000000000000000000000000014ff7804312754d23b251a42aea65207695d4df65cac4f87fc96cb920843c022f24cd27731224db751cfb621886249540000000000000000000000000000000006ea693105a1b2afc79dbf75504c256c519f927ea0d79ddd1997a49638a67151dc81b84473208e8078cf71d456f2de0c00000000000000000000000000000000122d367c147c91517679432d3c7b56f2d529d70040109f803b89a04fd8540a6c565354ae420e1bd4ad4ff61427332629dc83c1ea9e4f4fc12a7190e6c71c4f35d1a676d39e30fe688a05820dd989664000000000000000000000000000000000156e7f8f1412cec315eb76f10c92143157313b8eda0677a6c0236de5fd27e5660ec3eb7369f1604082c59e1aa5f94dd900000000000000000000000000000000018ca9f505a88ed2bf595fa9b55d2356748770af16b35bd5db448990b7d41c3aac53aa490791f7ac09d2f5a087f938f70000000000000000000000000000000017c76ca9ddfcc26b028928364ee35829c6e57fda40773a6bc0c259a1b3cdea715c664d7bd0340192aaf7dec7ad20a2ed00000000000000000000000000000000082a255966c4f9d0ad6bd3d88b136cb2cfca09ed6ae378c914c28ff3338a2cd466cafd839f3fff4a30b33ee56e684f4e00be1b9098f1873ce155a66899877c7b48ddda363ae1d2353cb3816f1ab15ef0,00000000000000000000000000000000075c71e21ce327a97024c8ab5fcbef4fff76260a4f8c8489167166c4a85d25096c617cceef73097a4bb956be3eae8b780000000000000000000000000000000016270f3ac86c0ec43b9472499c4d845eab488a34ad9e2148c72cbb1db13623c5dbbc8327c47ce596521bd1f54f119a660000000000000000000000000000000007ad4914ceda9fbc161121c818bd05953836a581dcdc78bebcd82ef548671c899581681c908a337618a445f77c6b7cf400000000000000000000000000000000173f401cb78024e844adcc88fcf0e52d32de134f6300216ea0da7747752ae3ddf4d181b8d266b53d1b699921f9871425,293920, -0000000000000000000000000000000017f40eea638a3d4fec417701c936d61c5b233c9d6b3e94ba9addd0fa0b20adf9f8e07c6b629977445c7750acfd18001c0000000000000000000000000000000005f1ca1ca9cf67c33e3ff174a65adfde2db62e74edf30b5b0156d6b9dc86dd619ad8863c055096685d611ac015ba884f0000000000000000000000000000000001683dc67710880b8af76b464291c17fb0ee4eff3f648ac0772f4a777025c8cda0342d8f5aae3123da7fac57b965685900000000000000000000000000000000143d919ce2cf00838b10fc65374e770bec3db8ecb17d2db08b6a10ac38657bb109f54c1b3040b661c3914ded6f7eb80fa9cbdaa0ddbf854861eac6621255b2102e5343666c735d0384049d5680d105d4000000000000000000000000000000000616299341f2921adf083d1190c212a7941bc0d9fee50b05b265f2e8c339fc3dd9f94607631f485e394f5a7d71ae73d00000000000000000000000000000000006b2f12e22369e8aff45b6c05a2bb72a706dc46a5d1393aaa9e5a7931ccff33a5df2967189114c3fc5dbf69d080e39dc000000000000000000000000000000000981e1b119d04343e075a80dfc189000b4cfb4e321575817aece6009e6b3a6233d1409e8e584f0ac9caaed1f43e40d7d0000000000000000000000000000000001ce4693e8c14032c35497e0f9a586a4541d8a1a68ad014b0850753a04215be2bb60cd7c2fa9be4f4f09a562d7b29f3892073d958260a55b70b580f3ee27c42553b5b858b66f6928fe74b4de91c34bda0000000000000000000000000000000012d634764207dd7a0201703f855365f7750291c810ff292b3e8dee682d7d8eebd6d6f3b3dc8b0c9e25bd2860e031311b0000000000000000000000000000000000eb0859d79fcdef546026fcd380f5c936e64a5665d73f56d92c03dfb50c534a00c857c86ec43275ce69cccc0b53137f000000000000000000000000000000000131bf000fd117ef722b33a1cebd28899fb012e1113f767d0ed46fdad82a32e4327b883fbe29abba1bb7ba3ecc1cbab2000000000000000000000000000000000e24ef1e44029366ae1daf06524d8beacb2b99f60f419cc2ec1a49013b79fb7a4781dbd37785f32ec67c0a28d61a3cea2117f11d78dfead915a94f11fa7e904a96204ddf1835d3501639b83cd5f716f500000000000000000000000000000000067b6eda41cb8da47a424a02a142e2b98b9c69e7023cf616040993f41507798882194229cb6572806e82e9e5eb837b37000000000000000000000000000000000e38693cddf130d3645fd60ade780db84fa700e5bfb74ebea49cc95ab001bde442f363b4e4c61f683b3e67f1ec8c2af80000000000000000000000000000000006d593005cbccc55c5e336e19aded70da65a7fe42b6a85070e491a4ae54e18ac213556a91d5d62786b6d4d1305525a76000000000000000000000000000000000ff86216f5388114dc06deffa7b52a273b22fe0bc8d50804b491fac83e13915c2dd1b8c2779a46b5c313c4e1c05eb2979087caa1e89e48f05bad1d720477199410941a6105f911d589e1f94a851e0715000000000000000000000000000000000262cf4727703fb227bd7fce6cd3f25c1897011ab892e79fa47446711d6867ca82b9b95f129f7ca24dcb60ac75173d4700000000000000000000000000000000136b5a304807e029d0a77b2ed505ee5c920248242f0f95aa07e9bc2e13d35f6f67451d028dc19d26095b55cdc2fae4fc000000000000000000000000000000000b511b2e19da7bfeb183f0aec91bc7db3e7c913f1c282e12d5d2f422a49e7fa78a5f35656dc9c980324717a5ad386dc30000000000000000000000000000000012eae443aae59fdf907bcfe3ee4366e252bb57e268fd569d742456f348429f009f67bf92f9dadd401104ccd2549cecc8255603b470c056b3dfb3acae0dd45bcb3d014765a5181760336deeabff3f00be0000000000000000000000000000000016a827938d8b98e3446445ce23f48a7ba0d634de826dd1ee3c71467eb57bd4c24e0d1b4190f47bd841183baceaa2593e0000000000000000000000000000000011d360e0c18b45ace82eaee902475127d8f18aa4a2ec2081a453f1c85ffe3c59c0f7016f966574a7c51bc14f1935568400000000000000000000000000000000186b5d452c6dcc1ddb4f47b07e01b6d64644f6d01cba8498c3059cc494a68bd25eef35cae05885b9f2689683e65161410000000000000000000000000000000000ff826e5a62affbfd6d2062bd329fcb561f287046870b8be461767759cb0d5f1ac904ecd1f136c5ccd784bc11088233e0eab0e2486316956291feb44de6389b20f8bafe9cc890d86d27a598bab0f3c4000000000000000000000000000000001010e75c52ed0acebe30fc588961c849b7b6298bb8d859f9a9401737c467921c5e3cda101cd4e38e4318233d12b6c7b9000000000000000000000000000000001884db518fbe4d621403ce00521878c0d419d8cf476a1dfda59b7d3c7af2bd91058bbbf54ac0c5cf9a217beb78e3f98e0000000000000000000000000000000001272cf0ad917738bba052e88baf88347d60f63f5b875d604cf0531c1ba7d43e868bc70a682b7274067106f611f08ae60000000000000000000000000000000006e3236f6a66bd37af4be230d4edda6eaaed661f206ca4852d3004b5f358f184d80be6af81c62e5bc8c88e7a1072fe21fb9436456262e5149d02b33a1078e198bbb681699b3f485625784df444bfff670000000000000000000000000000000004fd1e2fd0d28db08224fa7e880abb8c48dfd0e488df4d2ae5f6649f448193acbe943baf22af4b12fd763e3e4ddaa08d0000000000000000000000000000000008df68f276f356ade28500eeae3b755c9af9b5acac5f5f60827b5b2044b2405129b00e5271baf9a80847d3b720026b3a0000000000000000000000000000000005e683d1556f513e6d093704405f312687c3b9e2de3b2840fff32e88186c89b18d1ac558d960b1196594730a9bc107480000000000000000000000000000000018161f8d23c394d10ba576fb0ceee530ebb95a670f2589d84c0646f693086ecb7ed80e556f3ed9434d7fa488430ccf430e2724d3501e3d79b85266fd83a2a6156eeb48e749a61676a1c92ab9bdd6b8990000000000000000000000000000000017860708943449c2227c0f50cf1274652dd32e999d5f9b1a8d672feedde15e9f1af484a7b9462a62dd745bb6d3c7295a00000000000000000000000000000000064f8cb707494f82ffb6374641817a466af65f5c7d83cc2964e6cb8efd021e0c40934a3ffbb0d91bf8a7a616dbe8d220000000000000000000000000000000000eb37cc9d56fa0dbf050b557aaeec76f9f6d0a6c448ea298af78004e41ecd8a1df8fe8640e77cb76b593ee17658326ff00000000000000000000000000000000092ab597967544fda640b145edcb3ae6c3f027c2111dbc282ebdd48eb93287ae4729cb30e45c1c8999b3a45b099dbf0ca49344fe6ea9274a103f323f3d9381e91ae48233dd579944e12afdeaf854000f00000000000000000000000000000000124fa4d48ffc5732fb21d465b559e995891fef98370a1eb73c9264988f75caa93fc134fde7f93c794582ba5cbf6bc685000000000000000000000000000000000b71d012abc1558e49831f053757518643ae04f79234fa92023db9c5483bbd872d24eb87a78960f12930094c4f8fb70e000000000000000000000000000000000651cf0016efea086d98e5bda8e1959e20e4947e302eeb021d196897cffde3e2c28f783521b2a28b8de1ad1a131f5e67000000000000000000000000000000000555ff8a930cc11d320afc3e0635a6f93da1487a5764d56636be4e5803d740a73d84666f6141ed5ee6b778a463823fbeb44aeaf3ba8b03e7ef7201415de7365365b828f2c1a38d09153e51432d35b9a7000000000000000000000000000000000974e769869719f0ee30895df837cff50d47382461c557abc4b8806b04776f401b76a5e630a6ccbd3484980d03ff58d300000000000000000000000000000000098157f0190e6bacbf34c20310f6471166750ea1b235e46a5fae313f90dddc799f21548088322910bb0fd7e41beb23450000000000000000000000000000000007f00d7d18719db9d91e2c32f51083b42c4fcb43c38087f86879ad6bc99600d4c395586187d26d041ff49dbbe517fca2000000000000000000000000000000000510cea4a7463bc5882d0cc25fa967a0b02072627bd57f9a5863fe5255953732846d4907fa301789bf02af9c1b25211c53961d33104649cbfccecc7eaf33b7a2a486c77dca363ffc9fbc9ce4e8c1adff000000000000000000000000000000000bf264c0b7bf68c595b89453ebbd7fe2e64f4ae2c7268ad51f4578c35d48040277f3dac9021997af02e492039348efaa00000000000000000000000000000000083a4fea41cb1e02e5002259f5f7b335c81e15cca93cbc884dc1b08ee981c55f2dd3c0db1a35ac9907435edd7f0ba625000000000000000000000000000000001468e508a02ed7b61f752ac38313345338d2b2d018f719f391c0f3fa1dd1602d9476f3d8829720d17021a459a2732e96000000000000000000000000000000000629edb2530c38ead8717b289c08036c12630cd8c9ae875111749ed893b8cbce40bcaeaf13df4044147bb665ecc2319ea04e97c20b42dc265271740f27f1a833bc5b324bcb843a8f9f8a68231c663d57000000000000000000000000000000001635830ebf227be126e13c634a84f3649d498e0999ad2dc73b9c7360db120dc2216addfe18c00676ed185efa1e789d8c000000000000000000000000000000000471e3cfca449bde0ba2b1e2a5b63d53badcb34da3251313190a35daf694d70ba385976d1f875242386fc74ae0173d18000000000000000000000000000000000986cf3f1eef587bcc70f66f25c60f353e6b15bd105fde9254487e9b522159658d0fc6b6a8a3ea38c27865f1ea4d76490000000000000000000000000000000015a2eccb9c10bc273cb712ee04bef01a11e486bc6a4d220a0f653582af6ba1bac0b5108250626ddf126f16f4015c9d2cb688426bbe9ae054acb6c1fdd4195f8a113727f5617642a5b3c0c65566e22527000000000000000000000000000000001213cbd035615f09189171b3e22630d72df2df93fa8c14427bb00c34f5b55bc8d1b1a59404bed6549b582537a397eaab00000000000000000000000000000000161072d8ebec2841f0f34cb38a3e1b2094a597640a34178ee951e5c993646ecfc3a4c0dd753e7e76f3a6da5a091f9f7100000000000000000000000000000000077e9c95b6c6f726902392c3a16b5cc71cd9d4cec58c00eadca6091e45bc095e53006ce8ac8827565e867531013821950000000000000000000000000000000018cdf909bd9f38e57ee24c0f51a5f9f703eb3d190dfbf75be00969e9e8f8fee331cf32d93c3a956d12f374f8752c2c79cf365a86a8d08db5cd95f239a2f3d22279556975ecc3baae0b774b0323dbb1b6000000000000000000000000000000000cbc27995eaeef2bef14919d48a008a0b0467856f8a6659d6e68e47a2d9d41d217c5913aa1d67911325dbd4fc38e36eb0000000000000000000000000000000010639740654bad5c4ec93f2496f4dc54a7642bc92ed03372ad4edc5fedcdfcf37158d3f02279d4e15078e9d5a7f8b5df000000000000000000000000000000000155ff4d6dfa031b0cc2f57df41c1e1b1c81bf5a5cc1e3aa93920e93c2e2e7a71b56ac410a87855400025badf6dae8e60000000000000000000000000000000018e637da048e7e84b9d1654113978fb148a54d86e1d011d7f5a86cd4f1e5bc15abc5b67d00129f53c0c021cf933f399c528715199c9f47fd6337b6b0e807e230b1397885fded024431c70e453f55f3650000000000000000000000000000000015d8f6e47b8f07b3e07ae0952a7c8f79519ce2828e3e26b284b8f2fae7432b337de17089b5c32f0081ec6c6916f2f53f0000000000000000000000000000000010ecfcdb02cff772db667266cb3f99f1dc28004ffcadca7a9c64b3b5853c09b7793ca0aadb155257bd64fa7bccb390450000000000000000000000000000000011096a52f3272955947304ba037e8b3fce6b2f07f2352c08d5932f4d2306ca511a74dc044d0f0e1e260ff40b0fac5e0e00000000000000000000000000000000130facbe0c1c6d077e9dcab647a44b049a1aba3df500bf27d1c268f71a59635e702c9ee1bdd68fbfcff7ae5b3e6bd83bc32e8643f38f8177b788b8c2bdc25b668308d914fce35c6f9023a769334a51d1,000000000000000000000000000000000b47d58802579e662f34908a4060becd40434e4934ff58790df2a69a759223ca29f42e658ab475cb92bd9c46566811c7000000000000000000000000000000000091d3a4c58a669d3bf0377abfe28d1817168b2a86375928d95df3459c83334669a59aba95ab2b9957d5ded0bd8925910000000000000000000000000000000005aa9c3fe0067338675099ee32f93bc8a5e9ead94b120dfa391651da40cf1ef5ff79d193b0b14b5926f10660aca6c11500000000000000000000000000000000058200992b111461f4d737533301734a5c3731c9f2e7b55e18887ebff4d5b74dbbfd23773606f54cd6a930b85b89aabd,293920, -000000000000000000000000000000000bc3eba5594666cec699a47fa13ee74722a80b4e5a1b28553de37c3163f2fdb8b801e35a19f6d99cd207d1642c0d4ad400000000000000000000000000000000104b334afeab36961824611f0fb58bcac55c9d36604ba73e9c8809085c019bd967cae7147c766df6565ddfcc0cd5fc9700000000000000000000000000000000026c3a6a4ace8638bf8ba7434b59c3aebd4bb274cbdcb898ec6a547b215f32d10395f3bb85a9eaecff5ef6d5ef2b50e000000000000000000000000000000000065bc419f9496b5e81ce72a13fc1bdce4738c2e3faacd80676be31db65ba3e7941ea75e370b6d6c0e7b2cdcce80a2fa14f8bfa3d47ed33a05fe3da738797f18ca5d5b8658055de5a9f85bafe6078f7fe000000000000000000000000000000000e7f1f5ead0f212439b4c47599581982712d2e6ba056f36cb04033ff5eebd81b5b41b874a78aeaa98562899418ab04c900000000000000000000000000000000095e45da9a4b2578cedd13af71e289d0067ecca1f09c014a294e0b250d1e8243ff98a9761030ac855a9d897cfe9fafdd00000000000000000000000000000000030b44b150d1337a3ed6a77f7b6332d7c8103da1aef0d445ff7467b4863e4c830fb782a81d01a6bf97e8d52bf333e78d0000000000000000000000000000000013bb76800375a45b847a96ef6edff3fc3c30e3d45bb4afe04230107f6a1802794e1dc23431797bc5e79e0d5ac6357eee4b0d302be94d437b8055586aa77ec1fe616e30552b4d7d3471ea219c148be069000000000000000000000000000000000602e0bd3d34415ddd517a73acaed5750dcfd68b633d51003edf79a169ad7a3ca2541d7a131c317c957a9597a753b5080000000000000000000000000000000007a964539081fff51e0ec24bb71257f6a1c513fb0047aad84b80180b22133246a1f62958ead75e4b2a68f973d17f1230000000000000000000000000000000000f48fe0f5b5a95e48bde4d8be1b2f352d748c1201b064bc88309a709b285f81260d4535d3e1dc7f1d6ee23ead35abd9f00000000000000000000000000000000135b480fc8a72248f7a4898fffc6c18b7f2f5b1af5cc3846610c299c8da252fb71190d9f761e037c6b47595bab6a03e56765d7f1079b142e513e604d577e7caf82cacae59fb98c6f8990522372dc906f0000000000000000000000000000000004773cd2884e52c2551c1ea0abb78fa17caacfffe13f18b75588484b8bfe781e623075bdf827fc82c6ed7e1d4d62081d0000000000000000000000000000000007e6023fc0e409bfc7d0b7ca65fa0e8d88bf1b4c202a8d1f0e1c3479b0963a646d16795fc5a128a54e624357050fed4000000000000000000000000000000000039f6eaaf99bcc9f4d8fb994a040af0d29c37960e9015d4e48466a9e554da30975c5534e76a1f08a55ed8ce7375b70100000000000000000000000000000000003d2b097d4afdde83a01cf2b4f9d12f77c8e92a8cadc225d40f974ccf385ae65bed1972a365d55e24231d58abed4395a2eeee02d9309af8c74c78e21836f4e6a7a6df5406617e2b4e9d300e37d8a2bfa00000000000000000000000000000000047b8c550310ae246e43b513d39e507f1dace7bcb543a49ae0854a397f62c408ae3632c94d172669ef3e013781796ecf000000000000000000000000000000001592914e260afaca80c0a240426c2828239ad5e256a707530f49cd65e9da2e4bb14a7d6d5978f52c04130a0d434cf4ca0000000000000000000000000000000006c0b8448ad87350db130373778d414deb738d3be97fba25c816826f59e3e926f44956c2e2056b7d769278cf56cf6fe0000000000000000000000000000000000a42d716fd83071bfa014a9b7af6c164d494f0347aed953bd2c1c97ade087a8bbea9f53c507fc0b22d520f28cc5d480cf8449caedd55f0a08825cc1a9e985201c8a7a54d1c4dd96f0ac54214743941810000000000000000000000000000000018026c9f6c86219d0be88955ce0afc3cb637b1c3a531aa2722c56816d368688181ef2fedf1525daec6d9b1651b71f27c000000000000000000000000000000000b40b15bb0621209bf9e33ebc27a7502d90fd3af62a1bb8f54a874a14c105df34ae34a43fc3805c1e4817ba30c048ac7000000000000000000000000000000000465262367e30ccc24632d39bf3af9cb160e97049d855176f665a185c138d5c529d11e53e56c65506e3e30be7b48c6730000000000000000000000000000000009485991319a311052d883b45911be12cf7648b5ca104ffe77594472f7047c803b8e9fb753b98645e630b9913bbc947e28ec5f9dc48931da70ba0cfa7251953e24c4c95cd019e00ac6fda095c1302a01000000000000000000000000000000000fcc0aca0d873cb8733ff7e2ea02b3736b737821af2db06ee6508e161f6159f9d944372c513a03cc4c9e30a707dca0930000000000000000000000000000000015c3774f4e0b30c9532beaa2f7f9b777f8d46bfd3888d6835f4a5a046153a98062efb17f78807fa17b3a995ce720c0b900000000000000000000000000000000083d48e01d2fb58244861a74a1261063f7d20b412c8a44f9945fbe373cb4b9a7ffd4c4ba4054ece0abddb6c14c013ceb00000000000000000000000000000000133c4976454b7be427c4c2ed437bc2e882854d2ddce42d2f97cd3fab1fcf60c3272aaa123a0cbecce1a774946bb7a8a0dc6046b43e6982f11f39412cbdef14f8e330d37fbe6dfa9ddf3656b86f4f60e7000000000000000000000000000000000f6ae7de1dba3b3030b208f61d182013231c4666f134b007b52d36bceb6f3cd77577be7b11abc097cf9618d351d61e270000000000000000000000000000000005803904e3e640e51900805f930638ddd8b86cc1bd50cbd32a142e10d85044cc52ff769bf1b40dcfb7269c913d00b01e000000000000000000000000000000000e6997b1f8bb649c56de5c4bf9968d19712abd22fb7dabee19e0aebd1b13adcd3e8b202975b4edc917d93adf087fb539000000000000000000000000000000000a32384fe03280962c5f575b47192e5ef3111fbbb0a01bda2db1e9733471f11eac0a37df8ae1a891de311770c482c06b0adf4625ec80149b7810767c985c2aa0187987b3649cab8c59a892404ff2aeb2000000000000000000000000000000000531fad86551ac6dee15fbd62cb13f38d8d5c89d23a031b9977f110efcf16501534757bc5b93f0250ff02d6cfdf2009a000000000000000000000000000000000e6d78343049a68514271fc785de053ed7f50a7774b87f264c42e03e6f8f86285477f8cc57ae066ef0fde237c8d1ddb30000000000000000000000000000000013e313484da4d6b85634c5306444bdbe45d7db823616d72821eb64a2bb5f352a4f7e4273fb6557039fa563ce1b091bea0000000000000000000000000000000009a40a984be66c3442fc8946cc42eca722187dd819be9ab34a9c3b4b0de7de3d5f126c175fe84c51a6f09e18623214f9345fd17367ecb06b29d764b22dc1e262ba1a339b6f0e0c77384245e3d41cda970000000000000000000000000000000008a76db551280cd43d4608e9fc629a021675bfdf9bc5a021546b92f3734acff1e97928850716b94d15b7dbcc4a1e0aee0000000000000000000000000000000000b2262872c268782e8f27ee8fefe0827d45131555e755c0a65a7c8b4185269bd621412b653348d7c1111d681f38d946000000000000000000000000000000000dabcf0f847045e01ef70ceaa32455f4c962e4657b840f97a1cff7cf5073cbf4ca8ea75a4887076f155e27e8d7406c95000000000000000000000000000000000a9c0ed94170eddfc485d9f1a770a8b493d4a59bd7156d6cd4b95b55bffa1b597ae9d6fbe529dc0833634d75906a4aba5ce5e62dd15958e6298cdf4a4e899e53644a48494d04fa6d1f73f2dbd645817c00000000000000000000000000000000170ac69c2bf9b48715f445524cab902b18ce6dea7b258481cc59986ae61c8fcb6708b1457be299a6e2f6f34dfd936fdb00000000000000000000000000000000107e855593b6f3bd2982a65167ecead47039065c9ae6e1bf963f81d441f0ebb411eec4b3ed1cff73044f68a4c114806a00000000000000000000000000000000063b470d158ebb4828e875c3dd0ca29a4fd2cd2af356233885a871cb5b77402090f29709c6d6a78f612c8ca4df2f4119000000000000000000000000000000000db75a60fa0b425b8cd2c955e21846ce3c407cb3f96c472cb412498143cc60212de0dfd0bf4de53ae3b345232180b4ad853396021d32530351deec5c266a65519471dce2087485781f33a1423755ef38000000000000000000000000000000000389e79154f627463a7966252deab10b5e809b0c2a9e90989c56d4076b834e2081ddae1c02a9e01b71d96b772766fc680000000000000000000000000000000009109473c7aa614334fde410951a69ac45967f7550890e01b05279b6dff394775dac51d583ae0aa82edda18ecc5e66240000000000000000000000000000000019dd51ec6783c1618a7f12298e38cc75d4fa32fc31438f67eb15419a2f0e9d4b5f70ea59b69e531c868475cada519569000000000000000000000000000000001121c7a6cbbb54d5e30a11a73c158237dedac46385aa15d93592a30fb64fcd94a674cc77afd21a611f704734337905596dfc62eb59bb84b3b6599bf3ce7af229096a8fd5925d4743a5ea386a26c9a6d000000000000000000000000000000000178670fb06f5eb8a4f182913f46f66147deb3f9f634d620ed55da2ccc88895e75f76f55b979e1ba3c3db29710050c7bd0000000000000000000000000000000011adec68ef139716ee081db7122e911ec5a6e1fd7f681a96a713dddc2b742b6e7cf7485b8f45e7ebdec8b1174c02eaf100000000000000000000000000000000089dac9a47cbdfead8536d6cfe8b94d316123bd92ddf30091e16711ff4651c4e2d8dcaf6c72bc159d7de9fd832c6f5be000000000000000000000000000000000c40b871930f0c6826a943a229112f8bf9a3b7d7e07139e1a7d99f97601b6ca8cf3638e0265743dd732cee17fadf996721d35ee6d29ee4816b91d1664b5957767b4b8066775b37c3b3d08729c949d6e5000000000000000000000000000000001040c4cd3c28a752295b115fd80c8ef0e538e1a3906e0d326e46585d633140bd6b8231f50d50c8e7a9018a625c4bdc530000000000000000000000000000000008b966d9433bfc3bede4ddb005cd0c256a168437c31b8ecc83e6fefa6f4b1f2bfd057c78f82bb76279b74a2f7de493b5000000000000000000000000000000000c0f75db7a17e4b712666b16c31b10bb935e7127eb9a0e59e35ec54814a9de9012210ff1862aef5f765d4f7f673c4962000000000000000000000000000000001015e63589a8b56aa643a79c5a433dcd8f4933a10edc9921bcaa7098af435f7879a40868e25d1ca6f7852800df29c2eb3d283067bac390f556891a531dfacfc4795358229bc9a651c0aa71d601bdd56d000000000000000000000000000000000fab22ab380043b01d312004057488ffc958168f8fe4d9c86af622030121e14a46c4308d711d5fa9a414b9ef75d51ba300000000000000000000000000000000047c738fe5272e695f421ed463ce0d6308e05c23b6bd0973df9b55ca96d89c0771a45d53b4d17f30d8cf08edbf94490c0000000000000000000000000000000017bcb3ed735e5a302f76002ae82f4ac74889fa0e966f0fb611fa6a6a09440bc923f447eb6aebe47eef917753b7427efe000000000000000000000000000000000b189d5b64578eb53ad850c826082265e506ab620a9ab9684cc2a53718f26befc35e9431af012306a6190f144a9632bf873724ba35e4e8b731db36f5067aeafd33f2e966977bd0962fd57cd5ccbfe87b00000000000000000000000000000000049fff545ac239696c995eacc560580a0328af07376f5ec819902e30d5e7e40d5fe07295c4ccf54d5c06134370373c1b000000000000000000000000000000000bff448d5ab544a8cae0cacd216a6b6d48f0abe1b4bc946d95c1a8c4ae44bf049c3b572675a5e20c1b4188fa27a867a70000000000000000000000000000000011dbc52baa00712f66def2fa8fc77bcb07431d3285774e2517dcca65e611f07aac265856cdef0c1637def44c382230fe00000000000000000000000000000000090af0898dd578123c65d1f818c3f33866e4acea19aeafbb31bd8da029ed1daa2d7ab3b22147eb32a09021f7a78fdf2acc5934c02b63797010cc8474e90fa5dc88d73dbe5f9be605bf335057fba47ea3,000000000000000000000000000000000d52fcbe9f1776477a9d2149ca55e0651fe9d098a67209ce2e7d772d4901ff2c70be432b53dc94886651865a81ba8c620000000000000000000000000000000006b54871379e2be969f86c72cda9acab9bc99f73de987f17ab8b25c63c55ffa2cff61b87e8c30d9f712afb62a2b9cfcb0000000000000000000000000000000005652612b19c38650d1babd4772722ae2c560e2914f2e246725cea86dbe1275a981a592eb55077ee4b7c6090e84d2ed3000000000000000000000000000000000ee37a6d42ce69aa67cdcacb19efc230c6c34969a2e081ac77e8f9d45128a6e8fff923c7647a0f168fee18342bc6d845,293920, -000000000000000000000000000000000f114e56d10dba7945d125fe1ab7871d9510771548d8388a2aec8a481de92572645b73631f9a60285c3eebcacb3bc0f5000000000000000000000000000000000667d3f31955df11e4e7896a1856fbd4e573f1cfc906b3953b5806a5d01dcdb96009d9f148156a3828e822435f722c5e000000000000000000000000000000000d7740ae776eb4766999f5671315c8965ccc84ff71757e361fbbb55babeefb96265c97df8892acdd6a9166641f656e62000000000000000000000000000000000166529d1a76ad784557384cb971728dba298baacc2f2a39ee36516bc7a761e9a7c29e385cf5784efb9f6e60e998b01e864a1ee754f6b0a86923e5501d54e6f47d7ab9e1483294ce98be13b7db52937100000000000000000000000000000000133e0b08430d9318d98bcf58b3d8f51c7b717fab56fe25f434bf521f830c7d4247d87d3df910490be2ad38adaa8eec26000000000000000000000000000000000e15afaee4f1ce6290ddfbc13cb887e540efc3fd8150dfbf3a5e7c759ccb8f334ba26953c7bbc43b5234b857159f6722000000000000000000000000000000000e4cc685524d42ea5e435afec7b3d7d025e93ea06407a28c246a39dee8ae77514a0bb2d5031f7367d658027299762bea0000000000000000000000000000000001b231237f7b0538d51adfa4ff92bf313507996cf5255f191875970ed4d946cffa8620b44045f4bcfd8f89baadd331fd93064d187f7d21b8b0a7092943de13b96c2e1ac9b578637a6e64331e2d40f53900000000000000000000000000000000084128f1848b2b244e4812eccba01287b9d07e85450459c8c42b01180bdd843058d9926f39e2fb5f610651a00233e31f000000000000000000000000000000000055ee70765f2cccac966dc08abd4bba0d004b379a2c6bf188f300f5d413f84e77ca1d462219bfb820d7f585b914a52f0000000000000000000000000000000002dd8f1d1cd85a5e6ac793f7e1e3cff887204aa4a5fed92f2088c06eae95842ab2c04d30d56f4b0fcfe61379e8e7c6940000000000000000000000000000000013e318f8b6f4165a8096c76ada440154901de42d69c38e66d9df4ffe5476666ecf7068e7163f29f04972682c43f3b0fd5e676b40c09f80be5d9398a9ec20cb811cf6819a130445203d792a4d34fc3e950000000000000000000000000000000003415c8bab713aa18d3f0d54e0101ba36793e6e9dd3471f8eed9a15e00d8312732a9ce88b5f0c30207aed92eb173ac680000000000000000000000000000000008a7e145e9576be8ba2fd980fb1735a2b73d1bf5f3e108878b721b6ed8378b5e0f03ecac179a6d148541096ba483b40200000000000000000000000000000000029e5554752db8bb87d58275268f24ccfcf3e0923744d57473d54a72e2cccb847eaa8f3bf638833a934c43930fbf30990000000000000000000000000000000000e0f2ead2697110a132c4ce1643b97fc652dd0660deadaa4e0c45e7ebfa64cb6a6fbbaac7c4e2b725beeadf6881ae5893f63a87972dd11f5239c35ce269e4b9239e3ae906ab117f1f045d3acfd16ca00000000000000000000000000000000014325fcc087aa108f152b42759cbc02cfa24e7e7cb995c78ccaa9a283ec2029c08cd747d599e0685d365ee99eeafca880000000000000000000000000000000011da603d3a1128329af19e596ebeaa4bad034c59581e9fa2e42a0260032f84654bf5ce22ee32c34eed7515d7fb0fade0000000000000000000000000000000000189cdb5b934cc1ec7ea0cf4b8158a1416712bb59c1650e6d244de33bebfffd3691b499b3ff8255b1b513deba709f7d3000000000000000000000000000000000e7ab2b279d0d5933df25d8fc4faeb8ca907e7bb8588e618b92737fcb6959380abc205118d2e3fc128b89a2ead5ca906145e3456d5ca6aa5910430e5a19567c327b757377aef98c4f46fe9a1f52cdc5e000000000000000000000000000000000895b6777e677732c74cfa82d5348c4c8ddd63ce10347836f5140b9a64dfe631804ea3be8e20bd4438f5e7fa14a121d80000000000000000000000000000000002422cc4781f007f732239ff9eedc126777d6ca0f0365dd90bab6b68c9e3d02ce726726a6d30d7d51a1f0b45aec1854100000000000000000000000000000000048af8a79663aefaff77a934f0af3a09ba02077c13a794ddb88e5c679ce348b3ab0fa217954ce1422f4e212d1383ebdc000000000000000000000000000000001190fec6c510b0b16e1505f737b25dc2401e9fc2c95bca92aa5d6e93b284b766bfed93a80b137e5fcb339983a86acd41ce27de5d3a5ef941d058a458f3ad2a386f1d66945789e51fa330fd65da4cd5080000000000000000000000000000000014fbf4d005f43563fb7408d1f20f672c8983120c66462ba9156b64a287e66960fecb41ca129b6b14466a5a0de91b81c50000000000000000000000000000000004fb283724950174d60f64af7bc8a7d059431332c8f17769df33f6607d72633aae3a8d595cb8d5af3f8909297844b3a0000000000000000000000000000000000e187476a19280ad9f33a55c50f37f765e343f92938e247ec9fe099c7f3df65e24af14885539bfcf3efe3bde9f2700ce000000000000000000000000000000000f086e6b9e845fe3b0c5100f82bc8aeaed166bed9fa4d34bc03ed86342a997101c508a4c096c4f67cb5791cc1a1fdb8187bf5c4624e86aaead712987f313e5db8f2fe6787fc33481ed6e5c4d3e96d5be0000000000000000000000000000000018dbe48c54347635d4b6bc17ff5ba390a73925f1b180d2c516eafc0936aa9bddaf7317cc0c211fb2a7f7bb096369a45d0000000000000000000000000000000015544c177a4b8018ed60c2639b43236957c2d995fb0f32523654584b0bf052e0930366a93406e1ec5c6d2edb955e811d000000000000000000000000000000000802d2cdbc5e15b25c77ded4bdba087f1d5760e6ebf9549a37f3314b1e88d3d6f58da9d8c6e9ef85028a271b83dd6242000000000000000000000000000000001577bfeaf213ca8b0983cb178e9634dd18f74baf02f6ca31b2e3b287d80a32d4cf11afc71df09ca5bb0bc8e60fc7ffa968cfa3fd0692c9ce56538bf70e77e2a47534d9472ac702c53f2dbe68217d53df0000000000000000000000000000000007c059044ce0c15bc527b19ce85cade8b1d5a9cc6dd304ce9a3c461e631e17c4feec52a0ab5cfab6a2270c75f73df86e00000000000000000000000000000000076344286cedc8c180e3bd762f12ac08f0ecc51293b9f9b8e7c0056ceba1bbb6fab4ee39cf559fdbd601db6c3d201199000000000000000000000000000000000bf6e708d0a4fd85c7566804e19f21f7a00bcc3bd7135f6639ad30aafef2ed1e72c84c8995b0e59738c2bf1e4040621b0000000000000000000000000000000018ff3d0ade15b690b6e306adaa5c10796b78ed7f8a984f637271cccfd39fd17c1e8288a11b051ca94de2a9bd04fa96d7a36b13ef742bfe88882a4e635b5fdbd9b079e1adf3423dd4962835c68c9617c500000000000000000000000000000000025cb808922f6deb0bed979b80a675d9324cf25c53de373534d771afd919a182af9aa1dc26a2d0284887121bf4d6b6470000000000000000000000000000000018970aa4f456c1b203817322df2e222516bce67ff9ace069599061c6229596e506c0286171f3551302e45b7d3b69a39f000000000000000000000000000000000a57d0da60f03fd4a5664546f9809c771ab6188aca5102c31f26b09950cadc26b0275417ddd9c4f4cf29794b739733cf0000000000000000000000000000000004ebf2bd93d7921d8bd97ee71cadf91145e064a33651da2604ed6fc8e08b1b8305005f12fd4e6b68b7b6a3b5cf123b1324c54daa7de8446e5a26cdbd6741cc90bfd26c544fdf221d47d509c978723c3b000000000000000000000000000000000c8ff29d0333e3f38fd8af91ecdca49e54ea5dced71b60d693b1bbade99ae668e4f994f7a5417a08a8ddafa410d437f300000000000000000000000000000000078ac1d0898a9e6cae29fe6b50e435e5f543d0ee233346728c46d659c4338295f27b42fc4b2851ad5035feab2bea8871000000000000000000000000000000000b3a566d2ef4467f21c27e4a3dec99a26c304b32ba1fcce8276a8518383a7de44de5b4011ba738dbb8761e67e36115560000000000000000000000000000000015a0aab8c3d51fc3fc8aa35dcd07f8a08188976883f9d3ccc87ee148525f2115ca46726a2e3c550167c169977b216d6217ff7a416011549f144a3a65238d62395f4f76afc09496902c064b27739c6d0a00000000000000000000000000000000115589e8e1440edcfe72c008f6e9cdf13fb7baaf70aee16166e7f32f4651db784f4c5cac15d91ee13001169fa777f0d00000000000000000000000000000000000f86710678b01c8f648bab2289e8f90648d9470cb13d5145ade526696d22508a4a59164290586c2c000dfc55b4a20350000000000000000000000000000000019b300961b40b0d9fe6e292e9357d04f0483ab3a8cc6f8f522153c51d22de8e96a812adf720d13ff7d05d1e68264638a000000000000000000000000000000000a80b61ab051ce413ec838167fce393f88c8a25f403bdf07cb60391fb15306a5271a7042d36f7c46b5978106a7b5293c4615de9bd7aebf1acedd9d40fddda34e4a85bc253c5e92c20d984f6c4cec533c000000000000000000000000000000000567c33d22805319418cb1ea7eca6205a6c44f1f881c03e37bf3c66a1baa5153473cc73b8c25d497b0b0057ceb0395960000000000000000000000000000000014d7a2bfeea6a746e709f6108eb32581ba38a617e4450b3567c77a992988d91f4da31b209286f8e9fd0d7b8628aa6c4e000000000000000000000000000000000ae6c9fbf0e06f2e38e91699cd21596ba90f92f6022a4f3c7c8a6557b7e1331283bd4d7a7d31d77d9d7cf70a2945ea1600000000000000000000000000000000066b8132c73e1da8ae7fec9169770a188b686f223fd0306441356040bc9070f34a47fe1bb8c94de9fd7606c18b1d2b1dd38f1a0417a5a366dd2d8f5ce229afb6f34c1b663ad6eb1d9ff12f38412f00f7000000000000000000000000000000001460040d0a19c37fb0736ebdac0324d8a38c94a73fc5f602b7ea5b7255be9d4b6ffc22fea5043d948420e9ae3476f56a000000000000000000000000000000000b37c0078ab8babcefa8874c6cd1c5184d713b976852d087ed84337073fab3054899859d0fac2f4351bb75ee0e534fa70000000000000000000000000000000004150f3b98e6166d9d6b0388342042dd8eff9b8e1239f479330b64c5b316f98fc7bb401b737efb87e1f6663ca4efa26700000000000000000000000000000000043e6131c1ff621fd6f8caf0939487a927550343e24425ada33cf622de757e6e75c9affff9f04373a954557181641617364da9c6b07aada98107447afbb189626180c5eef31f7f2cf26d5d76ab0c74590000000000000000000000000000000009fa1754bbc957d2a8317a2eed859457073571379cc7c6d65bc6a0b5829f8142db77654eb98a2bb0cfa5223a27d756cd000000000000000000000000000000000cfe8b8fbbff7507d3d74f4f550b4c85e19b8929d3728a462e12b4008c79014103153c69ed8dc6b743e1b6fb4720bad00000000000000000000000000000000017ca0c08c320c12502a1dbc841425694bde68b7806eddbb40702e58ed26c7e112f9a821a6c67afed174f51896ec2287300000000000000000000000000000000014d08df9cf825b07a387642ac9959e8cd15ea8e752231a3047fa30816acb1ecb79f1755484af9a98b993f50128c2bf5031aa8d860e3b598ad0c4e9f93f26d153f8a8d8d0dd614ba868ed055c517532f000000000000000000000000000000000273b64e867a9111e257c9b32484655e4d7e676ec50f174d9ebc9fc4262c037b176ada941dd8c1abf645e275dde04f4a0000000000000000000000000000000008a63b9604e96a5034d92e3790411f3112c2c7cdaa056f9f1bdfc0b164c37fc9f58dbb566337132cd1626f9ca2618f800000000000000000000000000000000006a661167c9fb6c26bfe0a3902f309fa683fd22729bfcb433756182e7e1a406bf44ae1d13ef0228534881daa339394e400000000000000000000000000000000193c6c5ec200d225c43c6e37cfd15e16e49b7d87e5515bb7b4c918903966f4f6ae0d42af6b98f6efdedc9b0301fa1c0f290c467c4827c9252b82ff523633ba116c52d15df9cd4e3121ff0e9f754ced5f,000000000000000000000000000000001403c7e3059135ebcf5e752011fdfaf66e348135314f3f4239b066e1c6192ffcaf89bad4228fcc2be19a64f4f5386f5e000000000000000000000000000000000aadbd8d0e53d5b409f7fa508089337bcf36212a3f613b37a95757793dd6b0ca99d1b3578ad8020d46e29c9c4197ea070000000000000000000000000000000019e43bb32f92ed187fc32d9dbe24a486e38316a3cec0fd7f7c19b313af43a10fd63738b78e609e04a083de6761d53a90000000000000000000000000000000001490da7d36ff16304b27f6e57412975497e9f3a6d35cb162464bcf69fe141d34ae27a33afc75a2802eb120e90d4897bb,293920, -00000000000000000000000000000000038ee0c2c409d8832437ea450ed705589c82791b8319fd0ba6fb4d302d3c5b73ea0521a0253716e5810f03fca2e9dc720000000000000000000000000000000018c9d748aa685bf6e11e6e4b6ad2290ceff59c8837a088b41a08983fb2c5ef077adb0730b298c5df9aa02a820a19a4bd00000000000000000000000000000000015d248426e362ad2489c0c6a567d80b22d54d6a79e198198a771fae4c4e97eb317da9feba8eaafc9460ef45b1a5e5690000000000000000000000000000000005a2342412801cb37911a04d7ee3b1e5d3dce2a06e0658d59f2ddcaa9ba32804a1ddbe8f4d00f4436aad1346ed1ea5344aaa57782608de34c6334ce5039c67767f6da7b315dcfc772f03aaf3dd1e67b90000000000000000000000000000000019d49748f05458cb9b316e433b0d341e23bb5aaa724b824bd147596761c11efe8f4940eae09e302e563e14e96b814f4a0000000000000000000000000000000018011e7ee4988da168adbcf81cd14a9232edacc06bbfef0fc78dc0f96b5ac86ea67be8661442b5ef60e3889f3137182200000000000000000000000000000000175a2ae3bdade6551b23656c16884ba0fd4247df4ba7471cf81022d7e224b23490db153c8289f95467ddf9671f8b6cf90000000000000000000000000000000013c58c0f55c46bced98faf3865e3b6a836252f252e97b6d2a799b574dc569f09ce33082880a4d0c3b8a2c7c0d4c30eae22c1cde67b0e8ec7217c6ec72f36d8a1e73794297819de9ef6f1e52acbd3ec4a000000000000000000000000000000000ee45d5689a8ea6132d5ace000699a157c1cea3c0c98b38d504153d64fcaf1702ac7a1cb0889539d6b15489fef415aef000000000000000000000000000000000b320e0cdedbdc1fc5733488e6d2aece6386a030adc36b0a69dc3809827319947049f3861c2edc859797d30a3689322b00000000000000000000000000000000194096079b3a1d6ab1080dc71bf6d5734bc7b5e7f30bbb0f9b95c9495a6bc4adf76e198fc66accbbbaac215a8932d8c5000000000000000000000000000000000ec07be0cfa9b3d3a64c016471d9e6d25228b46dcaca6e197be00b9ca5087162c35f1d6326a3cf83f568cb06da8c5220895341f4363b688c4e9660fb0cd17f6c111a5c92e732205fab0d0da0175f6832000000000000000000000000000000000a7f3a3fcf2e7b0ada6d4fce179bdf229454002f1271a39d5e99daae72da549c6ccfc7c574f35bb9784100675c30b1120000000000000000000000000000000000fad14ab095fa09bea919ada313727e7aa5aa06a1cc7746d006e3eaf70f79c5e4001a8a8de03540b45e0598b22710e00000000000000000000000000000000015345ade62c5691690c181da09d8f39c1ead42046987b8c7c975d40690a286a816f8cca519731d0ca23349c54b30d8570000000000000000000000000000000019f0a32361bb6ecd8b1d87c2e15d31c0e0cf995eac9facd5eca123c0799c465f156b0142d98e0f315e9b3595974a7b824c5718fed7503c5e2a97fd6ab0294d6c42b1d35067e9d5ec1077176a4bd3126f0000000000000000000000000000000017af46e78904915e348734d2450fc6e1938bcf002989f855082e3b4ff3366d81ee8d28293609c3c3b11568668b1305f80000000000000000000000000000000018b0b3859763c2654fc00792a5193b7317fa5051bcfd15ea42be2fda0f43adf322219f34e54b2446ef73a4562151f9a70000000000000000000000000000000015c23509a1b324c649ff878d004ab5f253d041670ef172ec4dabec7a525d5ddb8f9f62f383e3f71b0e9c98532e247d560000000000000000000000000000000003a38564a55fdbe05b047281fa153f736edbf48c901749005473255333590f967171a6fc88751eaf57a5335bbfb6ebe86d055ad484f5054e8bd0d073cd556deba05418ef1235d08ecbf8717b550933fa00000000000000000000000000000000100322c4a92c136437714a6586c82a6842027ee218bf1fdfffaf95ce47c9c8b6c8f61115b092dff81ff2e645d0a7a4340000000000000000000000000000000013a91ed8629acb5e770683015c3c248255d673d4b2e6c96334d1c80326d1a8b4b655c81175e4a914a45fb37c1f178bd10000000000000000000000000000000019075c2eea3f64f42be82fdb8f83f2c68c08e858702a0225d869143c0b017b76a7a40d809116ffbdff6700b288f5ca3b000000000000000000000000000000000598ee9ba9d56400b59c7f5977aef1e179855a37179fbfe97b95f19137b6034568e5c7f616943b4aca804272955d42334cccbb062c27a67ae2783ab65a47ce166330cfced1f11b85f87483e0250b138400000000000000000000000000000000025a526b137aaab5ac1b5f8179a18b06feb7c905b4a843cd55e31b7464c2b6d432b569e9bfc3222511c18255102aba5b00000000000000000000000000000000090c20c9f78a242e52daa339d5cc1c3f35aff7ab802a3e4366597db8b6ca43d30fa0fe8d9484e49fa4fd0bf5509f19e6000000000000000000000000000000000e928b2173e32e5fc9c373a2a6f126e1a3a472c01a5e87677be0d29907022b9a7dbec3340cfc89e67377ce472c2d5d4c00000000000000000000000000000000147b4eaa2dcee39b918b7cdf24483b29466120677e5d42b51353a9b2fa207bd911d9b391142a13a212d0ab38adcbe10796111cb1181f048f51349aa2953bba2af50f7b7b5d2328d435bd63a7df5cfe5c00000000000000000000000000000000007790cde9ff8af2d7597d33909f00963eafa228817de1ebf4233ef0831202700b99641318186aec80ac913a1b1143eb0000000000000000000000000000000009d42ea1386d8b019dcd26068ab156f399c35b7d492722a20da0c915f7abe44ba688d9486f4bbb44268542c5a49168930000000000000000000000000000000010611f233bc1c4af0a14e1d1b945c91c077ec3dda592e2f852e2de41e09331664e1a92f9a0b7416c50327bc943a17b9e00000000000000000000000000000000048614243262dd070a754f40652b96a03326fc51273dddabed85df0654890ff38e0da7abb8190e4ebefdd6f78a5fec509d7f0c0c7e927bed3fb930fe2d0109f58678969ac8e14fabdf4ccdd0823f706d0000000000000000000000000000000008451d24fdc873c61db44e57372d43c35a2a8098255f9aad3a6b244913b86bff6444042e391685b1244f009c5ccde935000000000000000000000000000000001177c2da9972a2b96afaf866f97dc149482fbaaa93e194803c09c8334c2c7025e08cad4f7898959a57b07a545ecf76ad0000000000000000000000000000000016f40426cbd1f0f4ca5ae1dfa4c3960a6fbd51a1b5b24ff5d03fb9911e908406a0ecf4f20a78a280d24dc9bdd1c0799b00000000000000000000000000000000194a8c55f549da1842cc3173f3eb7bfd70df26b43a3059a3590992e34fb19b2caac4149f64d442965e166225b9013e2b11ce517fad2609f2ab8d44ae6263623a7903b2cbec683570949a96fad78fc6d3000000000000000000000000000000000a97664c1d7624cae0e969c728a84130fe260581305435ff8ec701cdc51a73977f58c891ecee637eb6b7c972069ebbb80000000000000000000000000000000003f4ed6a9e9f4229f0fb35394bbc10da9adbf4985d4453da64eb312ec88cb15bdc189a3b5df1af3107a36fc001ec92ad000000000000000000000000000000000ac552c5f6170a70563fcdca8e0c6a7c6135af2f9d5ae6f60a2c459d1be4cf76ebcdf9bcd891db8a1e2fc905a23a97b4000000000000000000000000000000001734a46c99e776d1ed4b807f5b313562e0989ad5c67dbcb961c134f8b7b7601c23308839569dc224bdf7c370c4498303b17d28cbcb9efde6d9cdc4c9cda385ce598ac8468d4fc94cc8e98ca3bfadf440000000000000000000000000000000000a523182c886671435ccc75cbc78293274802c6142465acb31a1809e43b1d656ed9c808068de167b1ab126ed0f73a4490000000000000000000000000000000007c4616080b5a002fea3589d54c7510884a3ece705d27dee315851746b1ee748e8a08d3516d8c6afe1c0482b960a9c62000000000000000000000000000000000dd1bd9b4b9c140aeb97887a0266bfb5696813fea034b78bb7d0cf1cca15b5bb0ed92a97841c8d8cc614f7721b8b7e040000000000000000000000000000000012a41a8941b6f0e4c87f8188718f9bc75305d41d6f4441eb9682473340fce0bbb463e1b922d3af8daea32b8a8ac9c3b4a9516e93416bc7b0f3c5ef5da6112abb73fc285a14093ed19d8eddf241169119000000000000000000000000000000001763ab2b361681955735ae00b69f26e06469391af993c8dc6f2e1dffb52ca01e49d58d6e2249e7433ccfb5ddaf8fead40000000000000000000000000000000003858f3bb01b2393aa4d4d7889bdeb0bb9bcde0dcb9b39c4ffe0fcd0b865baaff75b676c715be275929ff4303c416e0800000000000000000000000000000000086d64bd1302b0b3a620b87ac29cac3d9e606513ec8b47898cd852bf552c1364291aaa842616b92c8936e076e59451bd000000000000000000000000000000000967c9f59c15ed02c9b2da6e76fb0bf3d445ba849010afb7f9c994b1ef6a05ad577570d4adad043796eb90e51537ce5187fed462636eb57506f870ed1c8f66e211758327f4c19bf909a6419312c58945000000000000000000000000000000000e6b0da7b406bcac2dbb90fbf430fda6442cc2860ce633ab84404dfbb426949d55ecd72992da1a2e8e1ce229b599232c000000000000000000000000000000000fbe3a345ffc8fb85cedc4b8dedf9d952c41b4ff6f1c7ff4cf91b2276621969d905aa9aae5fc89bc516f96b9bd1bb3c10000000000000000000000000000000018c2a7fcc35099c41bb851ff66abb047e2af9cf4fa9fc45f030124ea2c7efd26e594abbfc7a7f258c8081a3a80d15105000000000000000000000000000000000a27cd33c2121c9c542e27b52a13275ef7e81dc0c6ece883b65e71d2bc3e7246f95aef7c6b41eace382a1400568cf298c373d64034c78482d6673c6906553151887c8aa28ab2930659671b8cb98a595700000000000000000000000000000000158bd8e6198d22b52efb7f3b945668666e1190a4a8e70307ba5c1b737316a8f8568092f219f683c0f53f56f25745d4e600000000000000000000000000000000097e64e4553371c81a9bf553ddd9719f59b329284eca0d76f023d603c29a034d123ab777cf173c5f2bbc66412d69d4ce000000000000000000000000000000001298cd5501e136a06ad4fcf87a75c0c7b96c73e844863b74bf6aa581a0ea98c2b1f608c668743a3e37ad5ca2074af9340000000000000000000000000000000017ff9f1336d7f2152f17daddde9d3e1679cab8120ed2c0288b0908d4e2099a08c9bc6f79425f004ea3ac4d684abff6dcf29c901f9769a42610958a8cd53eaacd9e5c4656106fab536052518b4989911700000000000000000000000000000000115baaab8f0331894da531ab557bb454e2003010ba1dc1d96e3d983d49b1312585c6d4c43d85dc074b23b2fb28c8a1d6000000000000000000000000000000000db1621b721c8a54ece26a355b190af5f3e1dc1b43e0827a1912ace651cbad4b980e77a4c3566aa809157229b234c808000000000000000000000000000000000c594e0ed3f7ee55886e251deef9732aea3de11f094ec53907a843b755add8fa5d00779a66621e615ba7772ee821c4030000000000000000000000000000000004e80aeff6c4b85188903b4d2dcac4f94f7cb4285a38f94b0becb556d83dce8735d1db5810b409d45a8dd1b9a6dde29c125c12599e84b7e648aab52cd68fcca7f1a5f56c854f3c36e0445ab7e2df2b740000000000000000000000000000000000371a74468ce2ad90e19b7fe3f57159dffb1b0422b32ad693b2fe6c45c5d371b97a90054095da887019d25c1ee8197800000000000000000000000000000000010575e1ec9a3e609ca086ef8bca679c4548482d9e0da2e51878158ac8e5b29d824c31ad7ff642041e748efc50c2514e000000000000000000000000000000000ef36130380f1e84b2f462b5f970abb8535431b79813015261015c6d7e74f038b47504de01794840d93fbbb4b386e17500000000000000000000000000000000018419e85fc2d75f007d1e0e02c1975332e03d42c3b41c50c3538c3625e702161cdcf8913babd2995aea7566ff15abf2bb9a1d051e33a617c25e17b7ca8ae6b02f16c759cae0df7fbd403372eb2407f6,00000000000000000000000000000000125406a942ae0119575453beb4c093d2696d3bea7bc031d7a586439197f848e1d5a82b925b4e96138a3460eecf198ffa000000000000000000000000000000000befcee6bd1412c54674a3d519dd2813b87b18f2ab3375a731197e9f539f8f8fff634f15647e7fea3c65b93594343c2000000000000000000000000000000000011e4d432ee6babd502a9cbbb5cf4839dc6da6176b6bb0ba51d99a3587465f5f3f83f4d4cf2c7e6187de93b859ca61d800000000000000000000000000000000168509010b867aa198fc294a5879ce14a51503c1d0e8fbc02ec08cf62afbd357ceac24b633bd0fa99f83dda92e10724b,293920, -00000000000000000000000000000000070a0d060c6e9bad0d1bb15417578daaa8b7a5c984c4947dba10fd874d93fd1e3994337c87799d78a674087678d9168f00000000000000000000000000000000128985b69d5d6ea0ad0b19eba7c2b430f5242a7e89626c66fb83b58ca7cb65a479de4b2fca6886cf55b8cfb52394102a000000000000000000000000000000000bb0bced708571662af042d18956b5b7d797b61aba70823618682287deebe69bf1f9a94ca4059e0570e25a39e60b9a8b00000000000000000000000000000000193f0793324dc78c40f356dde030b632feeb1609a1bd75ce88f0d313a0864dbf1f5e92826870866ab9b3c98cd1c12aa508c35887835bf4497d673936f40ed44145c5d5009fae16eb0f3ee9168831abf7000000000000000000000000000000000a61a310f90a5ffde617b78f784b2e699cd77e7c3e7c483a2ccb768f94d68e59a2a4521410c22ef6f21ba589ec3abdcc000000000000000000000000000000000e6568c83e0f7e459b27a28e5bf954983c5dee478a009c244da16041e710ddc67479cdb3da6f47e7203fedb8f765b2490000000000000000000000000000000001c5cf6b948b85a1c426fe932cd87605f1fbf6c932756eb1bfb43beaf012bec4612d8dd0840efd4cba3f5394beb65112000000000000000000000000000000000e02d5bc20c40d7cc2165a21ab37c6e4eb71322c01a43f2085f93b5b02bcabcd668dab90323db0f9288737d757997631a0154f7f8d52319c9e5cd59052e91b84640efe83ac814d95370e46aff4334cf400000000000000000000000000000000165287d72eca1ecda5fe16a555245b0a34a04beaf9177466bfd88bbc675442d206e70f7a2063b6ed0e15e9406232f5ea0000000000000000000000000000000004c0608bd7e01e65a15716b0c505111a3abb0abac3efb846e05e8db59c063950dcee052f04d1c4e9e492bc6740fafe6d000000000000000000000000000000000de897f7ebaf9089f7e198ee41e1efd7d84fbec7327799b9293a489965cd36159442eb0dc1f79f6b1f122f592b013bb30000000000000000000000000000000009774586dc359e5d20486f00dcea6ff93948c5a8b74058645d1048fe46ae3330dd56d85204d328f43f15e674020f353ec252ac28ea29b5459cd2ae5bce4bf08a102280c093b9962cafb481016a212709000000000000000000000000000000000438ee51a560aa419ad6ae45e1014c38b7c43f1f6a512bccc2d4f10a35838369b71799fab4b6a754fd938c1a1b874fc0000000000000000000000000000000000c1491c85965c0b74d08f5866ca727fd30bf641a6ada0ab8363ff01916c37d10b1b7eccff79b396c587d9beca2c826c0000000000000000000000000000000001452f254ceae9626443265ba31a1a750a425f2a7789e69cde16b70eb319c744a6221e74a9e2881c6bafea161d29638df0000000000000000000000000000000011bd6a1bbded174e9cb95d74492f7b07a755339a6c40f2a1a76debccc0f3a32c7017ca4e6679fb2c038c751f19986f526d3bb5ee3410dfad575b0fbe71ac5df2048f74b52e777fe0955d6e244d434f3b00000000000000000000000000000000139157c34aaf70cbfaa82be655281b085e37d6406df4cf8e291b221394e91d9e3cf04d431f15436064d0bfc8cbe13701000000000000000000000000000000000353fcf6e587e71e59d8f05d4085961d37b1f62694dd5c7f40efb5875b90459dd66c4d2d6c01a40834307ae9e82c2e08000000000000000000000000000000000a4975c9872fd167d0ff4cc80a6ce179b1e6e1eb21c8de80321451b1deffe68d8a13db26218f14935b64af25d63644c10000000000000000000000000000000001e8a2824f21cda745a24844ac0336994fb18e30608ac61201a932c0a5a58f1acd56cbd9353bfab4944efcf2859ad5915c30684c596976bf46384e6afb2bad6f821c4a62338d7a6eb204ed75070b1973000000000000000000000000000000000537d7a9d7d9dc451cba4d50630caed32e182cbbd95212577b8c2855c327530e447a4f3d73c7d63fa3ad5111254c9ed90000000000000000000000000000000006984b32955fac4ad3c0d181c81b98534ebaddc316d51a40baa1028bacd6a93a20d4bd6cad6a0f8cf7ade96bcd4d68dd000000000000000000000000000000000720c392a663884ad4d8daeb7279ac41717ea602108c76519da13a45a77d2acafee842828f5ccfcd786bf7ea88afd01600000000000000000000000000000000081f1d3e37ebaacc11671bfe1670ed65ece2aee0e3b5d746a8d618b44bd4b7dea905eb8e958bc026a092b2bd5a7b87cb11009058bb8e23b0a4294b5cae63aff10265e729d3601d85dd7f1e8063ce260a00000000000000000000000000000000005af33731879a574f39dca99c5c1b9517eda13121221be77a0c1bac82fbf29b37889c15a9d32531a3f6bf9137ce82dc000000000000000000000000000000000c62939f00d70a07a85804cd97fd34b9764565bdba225cdd7549729ceb9735bf4d09a80ec3055c483e1e24b66c41e403000000000000000000000000000000000e415677988c9d4656e59f77c608926c83028f91bf4c0634120b5f774ba07180b98141ffdf727cf9d0fc7a4cb52f4393000000000000000000000000000000000c9c37eaca857151a0c4a49b079f2f061e6a8ebb77e11eb32b29227529562f8dc8e2646e25469491eec5a07b11943f203e5489447bb9a5b661bcff2d9a4153a5aad975abdec380301b6d4ce019bf2cdf00000000000000000000000000000000015113f8f9100cd18427ff48038e1070fd835fce6c0812b7bafa679ac733c80bef56492ec3ca08c1117bd0edf19cb26f000000000000000000000000000000000789cd90c0be1de5d0b359c030d4b9d8aef93951e26870e37c375b9e7879cf277971a05babd319a3a6ac53f00f3254e40000000000000000000000000000000019b1cb91c9a1b1ee49c3837339778806bf0c093f171c92c9931ad43e35fc61cc08dafaf55b7b9e0f49dac28a12bcf92d00000000000000000000000000000000066c7864631333226f191e313436453e59f48f91d42e68874fa4da45eeda1f6f7f6342204e64e124d5ecd861f02ef4f00444d520ee01d87407747a4ac37abb7bd4e4c4f1735ca7458cc2e4dcb1d6297c00000000000000000000000000000000129d887d694be0ef2f84c343a9aebd0a2aaf19a4e78586470351ffaf0b1309593363bd9c6e7fe39a6e59445d935414ef000000000000000000000000000000000596d7061c2399b6a9be7d4d495e58c0377b18db1e45cf3eb431d10cb8b15ae42548a86a26086d57b1a71cb5857d7917000000000000000000000000000000000cce7181fc87dfe1bb493043279a5d93cb2d980eed38dab2ace8c9fb335c2890447434d80df6e7c95729933ada7b9d8f000000000000000000000000000000000f0e1274ff70bc6d3f1d0d5b251ae528ed94aa3a1b9bbdb260892bfaa6213892071b8a6407abe26105b2f81df90569492035cab8f8120ea8e91389707a290db4ee69875d7429c6857e74e8bd40dc7360000000000000000000000000000000001192050735b114c19eb2bb9aa01f04d1fd9bed4df877113a14f7fbc9c31acc10db3ed0e0d15d8433e7408bc237c985b9000000000000000000000000000000000a8a66cda780790311b56836fe69479c7b94dbc6c82ed5886887dbb539a40390ebb2683c04078ed105e639a2ed8732a1000000000000000000000000000000001678ddff677b99011c73e0c9875b5b2ba063170f4d565d261b4c6d3263ccce0334b5bbb7ee08692568037fa96782e48b000000000000000000000000000000000ae15f79ad7f790f8ceaf7709f4b5da71642da0c1f7c442eeaeb165c7dacd8a4892fdfc8447a03a7c56e12513499e43c4bec711286827f0941ffbb451a8eba871239341a60e3aaef23487175c9d2e8260000000000000000000000000000000007fcb5ea5358074d06b64c5f46454e682dd9ac2127374c83f3ac5ad46bc5fd2fff7c5a80ffc669a1c159ee8c9a01bd37000000000000000000000000000000001010ada1bd493d6282ac2d3582480f50074a02fdf412c63e93c5857974626ff464150c20bdf23a87692bfe69a075eeb300000000000000000000000000000000086bb5664a8738f02af5517aec4c6db47653a6d76bd4b5e37ba4d8b27a7819e82e6a4c7ba4f8377e06a5878e7c0bffbc000000000000000000000000000000000be1463ab76e468e47e1711c158dc9bb10d1278f5cc676cff937f60ba457061bacdad7b8d3286f40219963b147cce4bd369d91a4d575d4c142b98a53115a792ec50a290608ad316465487762e83f3a86000000000000000000000000000000000c3329d1e1c76b0bcc7ca3766b2cc5ec8169690f45e0ea3e37b7173bfd6c884921c7523ff25391a85b47d5de395ca63b00000000000000000000000000000000081ff066c008d5a4c893a636d24e9752c6a06666dcbf80082167610e73a32d70aae3e58c88ffaa27f05260b86b11f72a000000000000000000000000000000001178e88c652d257888cda1c0b65ee2c0636184194fef9e6ae3791a85417c43a31fe75893773ff3e7b4d4cda9eafa8de40000000000000000000000000000000019657ec4604ab5e8812237a28e5ff320a0d728c60c541142ffd87fec2c703665638e5eebc33e308d5582cd043d08d788ee472561535a7710db521976cef0c92a4ed89861ecb397cbcfafa477756e8e120000000000000000000000000000000010789200f69d8acc70f108145804b62b521a30a04176c449f52bedff5975ad7b273aaf4a32f8461ced8e92b2229e2cef000000000000000000000000000000001178c36174cdb783b5b09d419ae4a154512bf9ce07368521d1576b2f1bf39f98be29bf533bad16ba9d96aae621612aa70000000000000000000000000000000002580f2115d1814667b6178b6bffca6a4d992eb66e9601c0d21e32a5f3b69e3f85e1205c877b2dc2696a0e872c5bbc6c0000000000000000000000000000000002c94d7ff016d57bd5f589971344c6499577bc2234e18e6c8dfd7d27a205442a4236ac54fe279d1bbca76467530140b42cfdcb8240f183abec526344e8ceca6a007c35b757928803f854225d3a6ca36100000000000000000000000000000000108b6fef7396ef71b46339d421726f83b08320599d66da18234011720d2b524d24075a255d2771f1ae904958c50a9046000000000000000000000000000000000723d5045b65c0887da1bb01d874714ac86d21441119a93a1d5758957215f399f5ef1cbc00558db01b295bf0cc988cab000000000000000000000000000000000994914a3df9d3094dab0c0c41a45315dce5968a99e6171fc609ac9e50bee5ccac771efaa04067467e95709bd924973f000000000000000000000000000000000ac746602f804f52e9a485c30412adf92eb9af3f6daa8f23b974339a0ffa6f5aa1b70a80a9f19cde2a69a4b7251ecf5d60659743dc1977a698371cc302b7579b6d7d13632a31b47df369365fb02aff790000000000000000000000000000000000a2ffeaff148dc5f70fcf53e7e8d7b6100cd6e7df5b3fa4aa33bced243f15b4f77f48d25f74366a693404b6ed7d3075000000000000000000000000000000000f3e1b34ac8fde4caedf3d8c3e24db02de3f91487db300f09c779e7e4e96ae55229288abd946abcc3a8adaf18a0c89e000000000000000000000000000000000166a68c5191dd7f9d44eade2ef1a9b522dc062bba9c55e2ff03aef400e5d2765a12816b4ba51e10bc21e06113c8ddc5100000000000000000000000000000000109c00de20f7e827375c1841348e684fdb248fad116e9643dbda8be2bd06b71db264e9f2c40dec2092e7d518540a6d82652a5d4fdf6d6703c857fc7b10a741b95fbce91fe823d827cc7203be3b3bce0a0000000000000000000000000000000014ddb61173359514226c150a3343576b04fb1b06fabd8fe2f921fb3b90baf5513447c107f6d2f96c8b03274bfe451dca0000000000000000000000000000000001d1064860f6c4d62a282147308e80ceb0c5dd62f39b3232a231b1b287e497df31cbc5a3905a7687eb2f24447e50a395000000000000000000000000000000000859611bb3962955f92bff861e03d07bab7fe1f69e90c6bc7928be8d1758c9194ff7a52b16472d04564607b742543eaf0000000000000000000000000000000008a3e8396901a205a071aad06ba9812207171f33775eb358de4232826a5f0ff50ec3e137b1344b583849e8a5b424b46676a30abda185e7d280804952fc0c074ad907fea2aa54da4c3190895270169b20,0000000000000000000000000000000008c9db83241e7f3ae6c2eac8fdcff5f2d35318e24c3b4130e9bb7048a3b84a52fa3f222a8190121d2a5b8835bf911bb200000000000000000000000000000000002db79cbcbabf41bd8c715e024f4687bc0d058d76b8dbe58ffdb80918212ab6e9b35256fde583c0fe903c34a4c41ba70000000000000000000000000000000019f37d05f5c9e65c6f004e1aef03ff0e1899f0739c9cc4e9038e18f9d45678388454d144495b2cd993eb3691bf3e96f5000000000000000000000000000000000d8e0d7715ed71291729bf480f5fee7ae04264015732677488472bedc0dbacf8b35eef7adcce196e3bba9cac0991be81,293920, -00000000000000000000000000000000064a134260b753af73df3764ab662e3b1bd624c8f3248e9bcf7676d8fb0825ab85ea33387d4641c81fb8ba3757e0870a000000000000000000000000000000000d67eff1936a395cd3f808ed7fc89f8b6a227c4849a6941d4bf762af6e41ae41c8114aeccc2565ba01fd902df530df1e000000000000000000000000000000000110ca2339832e7a9468844b94b3ced0c9216654bef1c8a5cf66385a99d5d452f978bbb7fe15fb477f56753488fc909b00000000000000000000000000000000173210b548d1b98b926539049996713f53108cd2911105235c1d5258360d5620d330951db67219ffaa304a67fd6219f39f4db766964c7855daea58d1205fe8da572aef06e0ca64912cec7c87bcb2f51f000000000000000000000000000000000f7c3795ac3d511f93a3d85e65261e4c09cd316787f74ced6e472a3993b7b5b0ce5a7c91d99559a8e0791f712cb4e1700000000000000000000000000000000018eacb2c5fa9221881c6311256a69c7616748deb3235c61cc11412860450151a25e3d6a220bb23e0b3e3325044fba68300000000000000000000000000000000121827286873ad31f58cb3889fd01cb7d0f91ff1c241295f6ef2dd0e8aa8638b63a7e6061efc2e7ca1d3579b4868f0460000000000000000000000000000000003a57315175d70880b2b53c67d61831ab066b08d7ac68637364ab1c1f3efad96d42a3cf5189c45012c1f73a1b97bdb4c1deebc727d98bdec47b5a1fc48916dca1e42345ff5474a5fd6cab0ae99e9f10800000000000000000000000000000000180648e5d0bf727101417f515cb9578bdde3e9f6c4176d516454ea7c32c1712610cc8bbed303bd1afd48f580ec11b77c000000000000000000000000000000000d6ffa9b85d69b67abb77f5c8bd776eae82d1cb055d2dcdea31ac66b1825014ec7f7a2aea320ef9f6897c9aac8c0706900000000000000000000000000000000073214fedbade28cc60ecfa4e1fe2fbc05f3d71528aca315312d50214f680956bb9e0fc12783843b00b3f4f0f52efe2700000000000000000000000000000000128f87e7da7b53f28944aeb26ef0f6c99d84038af51a1d242501ec84b5a6a8593ef1a0f6b523478d9fa12e36c2fdbe694b964d74259c216c1eccd7f2b52ffa5fcf151d47bd69bd2768e6466b32eb4fe50000000000000000000000000000000001443980d7450af1e19949fb328776cb7238a9b26240cddc565aa9d52c5592083b1533e8103dc07eac80e4bd830f209f000000000000000000000000000000000afdbea7f1cec534c03d3269d50017372f7ccbcba9f096fdb2754af4d6b4956decbab2b0afb69f97a03beeb20b4ccc31000000000000000000000000000000000a83dfa3197dc65097601457a97d0df7710e001e90657b150e289515609f13997b454167a7589ef218893309460139f300000000000000000000000000000000029c362244510c342358130f877de947acad5a379295f3149d5c713274316e06a169501f889e4b9cbf86f10b9521c1bb124ceb1dbc8004a4b1f8b422d394b0480bca7c0f38aafd8f06ba090a98a1d3c60000000000000000000000000000000010a83f13a185c70ca3f724dd84efcfa3ec463d7c05360056f8b5304864b20025b0a82c9d542ba08b645e2334f176472d000000000000000000000000000000000848a6a18bcf64d083e118190805d68f7ffea8b5a66e0807b9cd3733d31ffa5cc25dbfa6ada604646dcd8dfa622e08a30000000000000000000000000000000009962205c0ba43e5101fc3d5353f429a57a97bcb84baa0942a7e7facdfb0d032b9307aed8bd2ac9094a2e5b1460db7140000000000000000000000000000000019b1012661a10d31a4a73d0cb31f7eec0e7be729a42baf560c1e90a9124fe8d5fe31ecbb6d4954dba7d943a7af773eaa5a2bf15b2ed08b33056a0733c920741f86730dcda9c06aa0e3c135a844cef916000000000000000000000000000000000e7f02c1d2ceae60f314f51374b338c329f2eaa82553c3fc1643c7f1910ca24e277f3d658f552a47f780d4d9e0ac5e030000000000000000000000000000000014b6b56afc4afed5199191ec13dbeedd797f14ed493c25658a9658f031ac8d43de12e6a8c4b1671c9e5ef78da1a55e2600000000000000000000000000000000194d8a50618ff55ba3fa5602d41cbbeadc01a348ad1484c5e9aee5fb7241fcd9018f436e3c6c6dc64beaa241513a6c8300000000000000000000000000000000052681eac4bd59e160b67ebb27582a6d3ad5286d652787a0e160026607acfbfc5b9f38b9b171375079d052cb242b87fe8c3c919f31d72ab414f91938089430bbbeaa53ad7a73224fd3f204b80fa1ab87000000000000000000000000000000000d96ce83d917204e674ad9f5e5728651f5f23df25236b0fe769be48adf482ed8c36ad9c9abb6efa3719bd35324bd700800000000000000000000000000000000107f55ab0e5b60dbcc0632c345a9e93818014d7657b264031709275744e1c6722ec63aa209e655878a57704ca6cb3bc10000000000000000000000000000000018d97fba324431fa28b8845d94f62fc9eacc0253134b923908f06889d375405b51610ac21a75bdfb27e3533dd4debc22000000000000000000000000000000001667856804a5471238ffd64bf3bf266ce3a2351ebc68265674bc86ce6faa8dd50a3dfa00c647fb4265951b3a9607ab99f749063165c6db0eb038cb9f1a573de25bf377e1fee94f31df5987f7b2450aff000000000000000000000000000000000fde2fd0349e7a47a9b6858014d551aea569ef9802629bd9520e303ef0487c9d2d399682ac16ce6fa03adb6f4b478fa5000000000000000000000000000000001858ae58920dd0abd8ad94d2f9f946c53e050fe89c61f62fccad37e17f8723a4fbecb6b1be1e3cb853f045d0dca8e53e00000000000000000000000000000000093615a7f9d12e92c90706a47abe9620c4db41e95e42e478949745d6b73e021422e40b969e9e34263778c8a4d4907445000000000000000000000000000000001006ae7963b1e1c4d8c2c85175aca958758fb380019825b09ca3f728b5356254ae4fc670aa29812320b921b48a069df622d292cbcb836843acdd5a3fb404024174cd5c1cef632d1b9b6a73f2c5f705a3000000000000000000000000000000000ac407b75ea77789748e7607b5d6edb1d891875aeef2802715ddc393818fc8cbe82cde9f96377e3ac60107ddcda7e6610000000000000000000000000000000006e63e49356c38b816736d1d7c360ceaaba875c53c98ec68cb825962531855dc6410a125b914b0ad99f6f4327f5450890000000000000000000000000000000018ffb4ac95b8ffde112c8bdbf07a1c97b1d30a42dd4a97c82617698617ceb169e8702437ff6082a2ae387b462cd86256000000000000000000000000000000000497c4b3788c4d6c9b4cd8b3d3569ac4b4332b2f76c5f03f112e089bb79d33152b2469f7ad3eadb8b954775aab73f47de816dd1bfe025685f2eff0856f9c162d73a58fdeae0dfbeb5ce076e9f9ec1a700000000000000000000000000000000003e16f2f5a2fe15fa02b6217aed7dc688dd2670c09c02791cafeccfceb7d99ce826bccf213f6a7c6064687519f9283de00000000000000000000000000000000095e6638ac74815dc451b3ec85a6a8cc18643b541e8be99052ff6dad39c971f2e8bee976ab2ed5e1cdacf92816249ded000000000000000000000000000000000f2703c08b1d707fb6de215de80b53ffbf2ac48f3dd059d2a952b1031189248fad27beec5c8591ac93625a08e3420f0200000000000000000000000000000000024ae36412ba6f2fdeb0777b892f1ed7bab0527879d93f7b71b62f437f5c1ad1f04a5a7380ae5990a455f11870c7208304f117d41a011d36f55d0cb53d4f98de3b1a6cb55dc8a76b29d393bc21826ea0000000000000000000000000000000000f7ab1908c6d4b152835f950b604b55fdda7eb55c6b90c05e98626ba7cd014683bd3e219fd0d5983e9dcfaaa5d389e560000000000000000000000000000000010b285c2884dbdd540d6dfeca704e00839337f12d2267f6a3fc731fa0f724cde19e268782b4b9c2e11ec3aef9a72a6ed0000000000000000000000000000000014a40cc55570e8f45369bd9dc622e05f03989bce6a98a0d87f4fa7add67eee3e2ad9a297615dde05e64203e86153ec230000000000000000000000000000000007f2b6a092adc595e4857e821579801301396321d4a20bccb3296a031d74a62bd79ea4ea094d2e545943138d2fc930fb6b6f5ee0549b28a1bb317cb020ae0e031dbc381075772ff582718fa49db486d200000000000000000000000000000000108834a685455dc0be10aaf54607a06100673140b012ef23a16d3df204a81dd8505d62ca3e0278a2581abc59e0fbc421000000000000000000000000000000000bca7130de9896e8d6858022f24308af7ca66fb4c91f38b30f717c5491996ef4cdb01f4d38a730f9ba9ca5af5ad1de7700000000000000000000000000000000007d60ded107a06114afaf741dc8826f9e14bac6014eba26089c4e31a73b0f30c4b6e22533ac0db7e73621cecf753590000000000000000000000000000000000b538213a703f7a0bbcffb4aa8ce25ba2a538bf599d3c0251f5e8acddfd596c9912d4cf9a1bd8d3ec070713328ca992205edf9812adf95c9844b2da06f75d96e742c0620d1cb0d47dfd9b68d0bb76128000000000000000000000000000000000cdf0b9bc829cd8537918d665e5bf344d309678d01ee80c71a6d6efb45ee8a7beca35bb5ee046e0a3fac76e1771520ff00000000000000000000000000000000014e5be9dca2f8ee4da18e5ec9c4caa891dd78acc47f553af584308c72988435b85ad21b14abf8421bdb9e25164d568f000000000000000000000000000000000accdde22a1c479e47a17b8da6f1d2b7f780ac278c68a68090e5402977d897bd734f5af8164118d613f480c1f65e5d8e00000000000000000000000000000000029614458afdf6b572bea02a0af987d178c43650ca1c80a297b1d31e259aabd3e2a2c8e4b2c044466924dd6e5e3483e6f64a71e4e7652860038df67c99d97b1e5a063370e65217531253419bf2e6365b0000000000000000000000000000000004e45cc43d4d10ed878e18df156062c799a687b8e6beedad9fa6f66ad855cd053af6918e234ff9a43561da7e67f3dee10000000000000000000000000000000009c9ae47a76c199c93c38e7213c8d6c030cfca709714c703839b9ae9b65207e83486f9c8c16373e2b37756f3fd4355fd0000000000000000000000000000000001594ce9c2e229491b22317452938115747515ce62a0d49f4dd12667f5b3e7b541b3775c9b1363cc185a539b9f7596330000000000000000000000000000000016bf68e05e32168c69ad67331d7bc88a6d130fe8aed3e42eddfeb1d92add266eb69487b246a3ca961ea6ac0a35f8da78059bebd962501b8381b67c22055ba01667d916932713d7ca427cd80d8f76b41900000000000000000000000000000000080d165c57354f87008eb97610d4a596f180e48ed3190779591a0f7e07278f8d2fa6cd21d1b10e6347f11bd9731fdfed0000000000000000000000000000000008d5a1e66ec76743ca366be80fd1cbd5efc9112dbcfa84ce6c44e8df03140ca5f07d4bafc6c6ce5f2f190ede55fe8718000000000000000000000000000000000d0e1d2e5ef384a4fb314fdce54ab7895f895b3bc669acffd48e92c6320024d4f371f42071fceea550c8cf68615b00960000000000000000000000000000000010beae4ffbb68cf6e5d0683dc0629411ee14563f84788d50b1c8755b0b06092cc0f0ef7b55a39d51945b5178e374f8e047b3448b9b404e184f7ff20466aef3dbd4e08375673ca31fdb303c88243fface00000000000000000000000000000000161486d422462460923bd98834f0cc270982087697747fe40eb9153a7923d48eda191e4e7a75964f18f1df9365901a360000000000000000000000000000000017ab168a4ec81c8db4a74d529670fe6332b3870004f696f3a143cd1a62abd747d94afac9485e5dc19b0f4262dd379c990000000000000000000000000000000001e9cc85f03039ea53253f0fa2420012171fe39ed8696ddfbed57b80b73476171e59631388d75fe43aafde52aa14a64100000000000000000000000000000000109a5d5449002f4bdca44c0bd141175d5ca1cee449302f0314fcb5f282f022a7a3cef77f4e9fb515107e797726ff51d767d9d30b38b252a0661c12dc69127ac380f3f756144801633e99bc2ffa2f463c,000000000000000000000000000000000aaa5de171664fcb45439b17a024806ff7e07d02294e0592ca74752a5b66f3365d1b49d6893b3bac3b8b0d10d026e48d000000000000000000000000000000000418354ce1820ecf848321a07ce22117303e5a15169a9cbfd141fb4797de8871d84d577e86270a9cbfe31c088ceed0250000000000000000000000000000000016884caa03ea641e0660a790975d77c5bb03568f873800d0559b69e3e0afcc10ddf031bb5c25c46f136f0791bbd3cc8f0000000000000000000000000000000002bdf659df76cbaaec030448e8f4bbd6b424037a8dfd7c4b8ccaa2224b0852c168f49c6f45c04f23abc85b8df21953ce,293920, -000000000000000000000000000000000062bad6816308f1c8c6941980caf71929a4006083dd29827902ffc92ebd9b14f1ef662f3a0125b1e74dabd039f9106400000000000000000000000000000000118e4ae76e2c321a5b89eb19b58f58f44e80dcbc7bd6d619579da40e1156aab32fe81df8eeb1bd047f96d65aed8b3b6a000000000000000000000000000000000c8c93e1beeb4efe52a96e5d5612338721e3e487c13c18b02475f9ccd8fafc2c95101aed291951f2031bee5216dba26f0000000000000000000000000000000016fba44e9aa39a12ae27e3c36de1f14e3f37ffb0ceaf5fed2a0d9815eab02c5aae91b254812a8f3a2e3654cec01a341caaea75e63204e177d404898aa51555767f813c3f3ed283405ed1ee829b04c85c0000000000000000000000000000000013716488daf8586719c52fcec80d35f17d4c595b66c7f2138244f3c8cea69b819778bfb50e49ca1d092e57c51674fca00000000000000000000000000000000019cee25c4731bf48602ceab23b5fc4f764993443e3622107b4c33b29c23d1b5916380431b7ecd94a0ce99811fe6dadba000000000000000000000000000000000562b28b245b7c1ee531a320fa0f4e12d7c171c7e3932ffda6cfebb123fa7f5993e5ed5e7b7d295405e5031b339994bf00000000000000000000000000000000180c4a8158a26d34123c870bc694382352a8e4de712b650d3e45e6baa16d6950ec15d3a4e032c1d1ae8fea18faa6f3d8db48a90ddcd791e6a9debfabcb1c71c88e7ad98f9e739ee752b381b28d7656f20000000000000000000000000000000008472d40e0505d6b8b92500e8e9711112048611fcdcca2377481ae86a7f6da1571f179183301e2194a42dac3873a3ba5000000000000000000000000000000000e2c5b61c050a8a12298f76b5f15383e72b90b001fa26889b67a24bb374b63c1e00979b05450e44ed63e72042af6d46e000000000000000000000000000000000e8723eace9c7a72b3e6097afc9bcadde61462e2ee03fcd5ad1b1c0dcf39f437f80530c2a1c5e6ecdaac14e8715f02e30000000000000000000000000000000002e21e0f451d035a5257fb09e9ed17b27f0994e6d85ddaf8d33153628adb194c97db17656351c029be4d3125bd29dc22ad1795823d3834496b0a1c2c07431f9d76071db77834005fa1228393ad4ce3f40000000000000000000000000000000000dce49634595869d7858e95a301bcff8112eb73dca8a22042137456d6d4887998a541489ff09f8e006176e6beee4e300000000000000000000000000000000010835f7336dc49e62706da4ef21d8e3173629b16742c317c1b397d4f17ced40a56520ea63557d7ac7f251568f4eb3a220000000000000000000000000000000017446ebe659a4510a362ee3b406b636bea8f381503e51ac21031c7cc92acd23046d62c2f32cda01b680c0f107142ae7d0000000000000000000000000000000006ef82deabd8983ebe4255d8e06f4a1b3585c057b2a1ca3c3e1cf04b582b65792e9980e3a1735a8ad58b053b16ca03d036d56e38fe63e573b02203be04ef9e1a044e1754eb2db50c6f9804abc4a40f46000000000000000000000000000000000cd8e7422ee179a0499178c3848cc4fbc87fc25c8c882f036a03cd9d3f273f7f2bf71bd3c9cf5e30c42b1ee6e90b36fb0000000000000000000000000000000005005a471d77a35e922b6d6a45b13a90947c2b31d8e7a2e4b6388265b039ce23ed958495dbf904186bef60fd547b941c0000000000000000000000000000000006c337380065eb8a5f63cb20fc61a9eec4ccf0e23c4e0f231a5bc4d765271b9c5697bbde692b4828ae22ea12423ad932000000000000000000000000000000000f7a0080cbe72a6e6473f66ed729f58683a80815a1748e52f7b67a6bf2846b7df8e7dd8599f87fe63706e9823bfe00d21a6b36f4674ab19202037d59fd8e14369e5d3d71acc3c76985b813d81ca6e24a000000000000000000000000000000000c94834474ac91547546d7d179b2091e33c8812c1b582ff186e69b63011177283a74b549aa342a7f3882ee82ad8ecc03000000000000000000000000000000000d72c4308e9ae695acedb9413445bf6a40d59ca78bd4f74ddbc1bcd8508cfb521bfcca99c98dad8022d3d1ccdd98bca9000000000000000000000000000000001487d006830d00d84a567c5d031019035443fae4791a05253f91249b32a4b3e7b3ce7eae885b8caeaea411a90b3445e0000000000000000000000000000000000d94f17aa100503f605732a48e4f55c394a8df1421a3d7c78bc85f4cb7a53744eadcf76e1620fc54204b123d6071cd3bad85286877fa7e5a9a61dba9df5ce35083beca7c2f5ecad13d226fa32b9720e900000000000000000000000000000000101cfa8d9c7522277f2bb4bae6c09e8b93a876c749c91c61784feeb105be61c2479375abdaa81deafc2fe754ed6cd9da00000000000000000000000000000000089ebbdd489ff670a70218f5aaca78d4e7ade483c7f20de4a84d39217be8f560fbf7bbe36f3f8b8361ba16d17ce609d200000000000000000000000000000000094f094372b2315fabc219099200e7b9e2f3a2f6fef2ede6f83c82f44792da03aaad06b8cd06dc3f140746bee2a45706000000000000000000000000000000000cde6cf9a3a7018b2b1c0c26b5850820080c7e4b56e615d577a78565431c93de78348d2851d5ad9f120ddaa9ff3da31b8fa5387c5712832b52c9c72e10c6f69e9c1c5b278aa379140e75e404c4f50a2c00000000000000000000000000000000059bb8e5dc5f0cd31cf674ea78b80b67b8a8a753e51284a2ab37d3f29459250d904e70ed00481b73556970a7f5424e5900000000000000000000000000000000043c6a53c413bfa2f4bb14ef296afd97ce801a37fe63d11a842f8d66160794c1a651d70f4c836af2c73cb1bc58c706460000000000000000000000000000000003e7b67da1513656f7b08fc5a77682477349ac57e53687c82b6d98772b5f929a2b06b0c7e14481d522aa94fa3a6e1cde00000000000000000000000000000000109e07928216eaea36fbb20a38711e73fdc26e18a6967b54f308b10116a5c8af0c8411406ef6ab1050b61c23bb746b0a3023298162ebe7f4ae6aee45a8a6ba602c3942a8bd6b35636fc6b85596a582e000000000000000000000000000000000166f26d3d26cd48e498578900a8c830ce9b80f162c4b430749651b945d9f60ae6a26306ad7711a1f9d3428946074912d00000000000000000000000000000000165f1bc59c9c36d12754097ea83e9a63fb4ae5d1b93a1b9239a6f338cddf4a9b30415d58076852288c6a467ce9b6b9eb00000000000000000000000000000000198e73619cb93fa6a2bc700cd400519d11a7d3d6d945ffac9754a6faf37da8596b49b7a3a4f2cd899ec9c84f1e79b7ed000000000000000000000000000000000a4740820d60034d37bb85e3e622783852779d36d6e61f81a7eabcd094993dd7d81900277550bb4299d550d2805466aa8ff2430d2f82c6d5e7424836ecea15af0ba2d0bd6498e65c65b6cd281a7b8f28000000000000000000000000000000001714857b0ee07b94ea928ff57aae9fe003c0c85d8564456955d14fc8d4ae14a7c9bc303983af3e2999c6db2d000ea51d0000000000000000000000000000000016512cb60aa372cf5098ad514291d8168ed31bd755861dbd9ef020252c01379d343a9c058839cdec8d14f2fb9da0db80000000000000000000000000000000000af74d8ac711b6590e7041e80ca40dd4db659e42b950bdd68c56d676de654c1a47867bfe6483dfe1971eb7c1d1a70bd10000000000000000000000000000000019e56ca1ef3fffa9e131fc5bc93100577b062cf9b2acd234c79e5e54aa799a389f30002b4bd683edec5fb100f1800d66415eea22058493dbf6ac248fd2ad8b4734ebe33761f2177089a3feda396001c00000000000000000000000000000000019d1d1e1e2dd4ab86df81a8246c902a573d1fd1598050663342e411a1d1b3c8849473c689afcc8e0ce5e51a9dc9c3b6200000000000000000000000000000000190d7c923bdd6336fe3e0509563b2eb6067354d8807f66e6052e97d5997464b9f07f29f3022f78779a5c4ac155a703ce00000000000000000000000000000000128591bb699c18a7b9e6e4e894654853f6a68233dfe8c744b42e057711b8d0efb3a98bab6aaa40ae7675d9200a8427d600000000000000000000000000000000045e0560e0936b16d1e055d3d3f4e0fb42d129546abddebeb78e871d1442f4796d939929d354b0326b95e50fd5208fa9ff79e3ef5d32a751b713180be37d44ae55c59c5a8121c132c5098ff972d8a97400000000000000000000000000000000092373dfd7d4375d6bcffa415e5b36a31499e881a80be32400105a6d56b34d64f4fed09f12640a43289a710f034b71e6000000000000000000000000000000000fa75d6510b3b58a32635a7a6cb4b9255aa7af46905cafc893f29b7866e12565765bcde498dbe87df3d1dd53ab5628320000000000000000000000000000000010dfd3456cb6a8bc853b390380a13f045ab43abd289fd05e7f98839477dea1fb1fbe38ca4f5bdd6691446ac0219e453000000000000000000000000000000000112567397f3fda84db6042817a99aeccd0c46a11fd3ba44e2600deafaaab7014dba98cdcadf81b97272fb7f275ee8a4e039bc7274a3ab172285d853d368da0950203a48ef61b3c7564644762279c1ff30000000000000000000000000000000007b397f093e69874d2bd3592489d93c80d0191b157e71d08a6ebe73063f77e7c5e084a24b34da2aa6354b1815a694185000000000000000000000000000000000fcede3a39dd5f905d072dafdb6f56d85726f6f362f91f079fcd47a8c1d3bdcf199d64edf17e3db1dfc96a3e59f69bfe0000000000000000000000000000000010cfa13c84e750d8af8bbb88bd6d16adf3bc7b532447c2e6accb359a5576be08c1b25f336047fb8e01a4d7f9080d0392000000000000000000000000000000000ca0e88b5c2035bcd3a65e8bf1aa219cf428b6f80617040ae02a0ed41559804844df373ac61a85899bec83e5a6243ed42c47d0b1fd24c1c66a3cb0deb7d51ea19f0fc492f637ed5d4d03e102cbdd055500000000000000000000000000000000021f3b793680e0e3127fa53034e9fcf286f5279cd167ac1e8ba051c440aa265ec6d28fcc2f6d3bad126180efd4503fe900000000000000000000000000000000182b429f27996ee070ed27e7015bd70191b814bd02ca6558a9be81d6898161aa525197c1672ae75da92729f2fae9fa3c000000000000000000000000000000000a20b3922e07da4ef6696de85754eabf1f58f7f5d37accb6cde4f62066e789bc64bc8ad6ac827b8c955acc858b03d053000000000000000000000000000000000814faebd3b60fa1a8fb86b3cb57d36b9c85d4b28e97a2251e6bc1fed1ccb18f17664321f38f3723cf8b09a2161c6aeaab4aca860ae4bc20d33808533c9a70108b153bc4b2256003ad4bbc11dc92898500000000000000000000000000000000159f9d329f929a65e41c7a0d4c05e11db61ca7d6d82f8b92a780bac66568694656f4c845a730861fde9a313fa49bdf0e000000000000000000000000000000000d556bdc8dc959b00f74209dff27023c5521d387a40bf20ae2a98f3f55318eddd347bf1e9d856f43a4b5fcd26c3567ad0000000000000000000000000000000009b4b0cedf477ef1e0f99627bdd7a7afeb9e29afbac553a516fab479913b23a9be5e0b38994215a9e23849bb664201ee0000000000000000000000000000000010899f4dc55ac5d1f56a7b8d55ce7f6a5e0a8647bf1ef6e9050f00c5fcac9f679f138018b9aa611be73d3bdc0af2056e297500a2747f9a68b2d8d9ca5b0390369d919897c53d422cb76c5a283c38669e000000000000000000000000000000000226c8a6b27437972ce29c2ed7e5cca4b6691e3a5dbbe713b5d309ff2f4cbb95e8f1571314444d65ff5fbc3281f9354f000000000000000000000000000000000282a49d0c560d873676967700c1062013a2d4beee96a09af7e14436fda4e3d2a32ab8ee4e591decec39a811ddff130400000000000000000000000000000000167bfe499f1f4609e67134e12ad91aadc37bdabd0055ecf7f96162c39a02a86e62a7b3d39f514f63edd82d04beb1958a00000000000000000000000000000000191673ea5470e4704e361f5ead1c56371d6aee3035d92d9e1b96fd119c4f877cde6451411e441fb45aa9fcb90fe4c66ba87ca4cf226c212c80f3db5e4e781ad7391fb73b1124d01cf893169d1c50ca99,000000000000000000000000000000001488532d83fddf0bfd69b32f965790b3fe4cd9f64e8d17e78189c346518c91e69db2f0b742cdd5804b3db3777dd931230000000000000000000000000000000016205c470c6371d73b012a14d519bf214ff10de458605097da1b798977bd938727c5be19a10f4f492f301d2ab6c38ed000000000000000000000000000000000142cc08f61d3c9bd4c7bfd0b7a0b8693af6120898fcaff49a7fb5abdaf1d15bf70eb033d6ff09a75995547e6856c595f00000000000000000000000000000000164b2807e19135ca3b66bac9aceb371165c930ae063f3cb5a06efb8985a1e0c39023d8f01df517713796083e8c2cceb7,293920, -00000000000000000000000000000000023bec14deefcc20a90e439bc16912e90191dc7142234b1870e4e8d70c56f695d5cd30a68930ff9b007bdcae8ca90d870000000000000000000000000000000000053a6e226f3bd82150e08ec3690f36616d5ab745b36a9990baac7ad3429a41bc60c7f7000ceda4cc9298b10043639e000000000000000000000000000000000b81b331589ac332093928faa60d6819d3b5559d32d37d2cc13c78aafa1cc34e32d317695c1c4b4979baa1865ced90150000000000000000000000000000000010dbac5e52f9a046ab88aa36b3c5f6952720e174bf8f1732e886e66e5803aab63642185aa24ea08c991edaf8375bcadd9abfe7e05e8a210604355a77f64386a01323407d9f25397769cc6dd141bc6643000000000000000000000000000000001875ef3f90df03d49ce6cede2c791b4d8503b75acff2dcb1c7c88026394dfe11481da72de4ff58ee9a98e75577b6398c000000000000000000000000000000000c8ee603d1404e64ea3ff08c70b3dbffd318736ae95f9a96ca07ddaa449818e6c5a17b2970f572f53c90be893e5c323b000000000000000000000000000000000f31af63c68481f527092b261d29d5c2daa95873b68899c28ac7753d95a64f455ebabedfe6e72246e494cc5fa2a9bd040000000000000000000000000000000009fd06bc51d4dc51de9fad6d1eb763809cdb5ccdba8e0427859d878904bdf295983b318f311856728078e7cbbecb0c5b64be08e7c2fd15ac0116ca941b85615c6deb38fe85e2c0fd22997e394b8a67690000000000000000000000000000000003ce75ecf6b605ce73f4e215b1aad4799f91e624daf0deae3a273968490bdbdbd0250686ee91a1c24c2e2f2b6024fa49000000000000000000000000000000000e4d9b65d71b7593310fb5145677d170663c0ca29636f7b7c50ec1988bd2d2f1c41d542d4cd8fa23fad94bd6a84aef5b000000000000000000000000000000000fa4accea53a6362651f6c6ad2a68d20b5f549f8eb961718e0c14cd05249a121e442a6a588eafc83d6a43d8baa66882400000000000000000000000000000000121e325406767852620ddc45677495fe3e0851fd3c70922896a3e92033347d2fe8d07f4db8f26b8127ec39d619d596030c391dff1c0c303c77b2a1fff24f50250dc793338f7d7f8f1d54bf7d87ab37da0000000000000000000000000000000003a0ac3ac37932b71672b9c48bdbd368d64c11f57ccb952f633bcd10ec19134c65fb2cbad655d773a90cbec2d9232b3b0000000000000000000000000000000007553c470bd8f38a48490dadea29df81ad901ecaaf1eab35b1f497bb58acce77b883e03e78702930dda72e2277139a2b00000000000000000000000000000000044973913824b3326b72e62ccbabd8c9f1b5dc81b423d0dca37b6f33972d993a681c326730717036bc6f0286da9177430000000000000000000000000000000017b0407d2864cfb39dbb0a5fa8deb4ed4a690a4042153e829f51c56bd0f2953a440d8305a318e6d6f67970d473753021a2d728e013e5fc3e1ca24c105a0c268cbb4f152a97e318f3aae33186ea6bc93a000000000000000000000000000000000b7478dda7053590ed013b7c23431a21626e748c3843e2332bde0bd3890ecea95b6104bac420a8be5f3dd9b075203616000000000000000000000000000000000e6dea641181cf796f62b196652f952ee2a26ba998cce1cfe9d65ae49198d10badffa561e2bd818eb2a7f350c122fa820000000000000000000000000000000003c79917ad5a9c7f046b34e5491ed015695aecb00760f3009dde4cfbf88ad1c03e44117fcb6cdbd5ecaa8df8760a3da100000000000000000000000000000000034e22ddbdeb9dea46c71ca2144ffcc8356c1a525c5ada69a6d5e5c1786aaaf0cf532e31a2f78371e04a72e8222ed4c7e8da0c8da19dc441f53c54551579fec5d820ce2e3599824b24b7c5bf1847c5890000000000000000000000000000000017964112272360a38d3bddf89da922ab50be076bf71a094fc8afde109d3817cc2db633e6408f5716b76d70e30ae00c0d0000000000000000000000000000000009bed28bbf43846ab97b92aab9ce094b077bbc59db648dbb469f21842058ef20318a1a8c18045b3de555bd8c76132ff0000000000000000000000000000000001297110789c7aecb0fec577f6f4a4de14608d9aa26a8de68289adea7f6b53b766b840d315152ea346f8c10b2d2729e730000000000000000000000000000000002b551c6a7846b96c6895e55ec435397af70eb435dc1c562ac71a44c36936c2c6d3e6a1e3545513516513391aedaf9ca76e90965adfc2fe52e4341895e6b6154fd7a097e052b59e4935c8267a6f0e63800000000000000000000000000000000003d463ee4d177d78849fdecba52b7e83ca90d54177ed39e82b4e80c17994a6a2bfd9c46edc0ddb256f8955428f30eca0000000000000000000000000000000011dd976dfeb8ecb7d7f5cd10c235131709fb16d8a827e83d7084266c2504cd1f5276ae3333bc7fbb4ebab48c0d97a9930000000000000000000000000000000005fd19477fffc246f5991603b48085d95256b273631bcfc16f19c6980a3ba01ac098061faa149b475bfce37d586464b800000000000000000000000000000000103ac3dd682aee109dd7fbf60b50c28cf7e37642f05b424773a06f6cfaf7e9fb01d5074ade97ef6cb0ace2e1fe07d54c7f3f352c7b7a9e2eb6c87edfc99e2df3148966760168f6abb13ee482f223a01d0000000000000000000000000000000003208ce7f51a96dee053cbaa66fbdb921c2c3b42ead78b39b4f1df7ab49f05cb88d0f4ac18de5839749416eba5535d4b0000000000000000000000000000000001ff7f9db52aaa0fddc8e96a67b99353b92d7032f59d200bf69da3b446d08435d2ddaeb93584d3b68a1934566187922b0000000000000000000000000000000005f05ccfa5704652cecfb42979c538823fb9d11a00222a963d00f1a4b9a040a0222dcf45baad40c6574d85e5617dbbea0000000000000000000000000000000018637b8c3ef111f6ad4538464c250d780e7f081802bdf720f4c925154f4667c5d50cdbc4dbb7d0b2747b97d2ba2280bfd35c4286f19a9fe8117e37132ce4ce76e28afee25ecca2f66de3cd5e1c83235f000000000000000000000000000000000eb400becfa5521b824a4288885fe46642c31576238e94f95e9b4bcbf62845ee9d9ee122f87d36fbe668f0e605fa2ce00000000000000000000000000000000003c8cbdeea0d09590e1719ddffa0a116723f0fe85585583f3f271ead66fbc2107873181915cc41eed3ec6e2c5669e9d3000000000000000000000000000000000e61c0768561517405952c6462f1c5df95be272251d8a7060624b62f9be310cef64436eb2c4c04e8352d7b75fea1756200000000000000000000000000000000036cd74a8efa8a1fce7587f07d5c2a6c4b7ef161b0faae037c9bbe63bd0c92b83e514c8c1bae4a5d9866c0889b1b914f3c2b40b7968a39fe8e4f24acc25b6c727887c3c44cc89cf62eb14a78ae47e8680000000000000000000000000000000013019d0fc8b93da2c79e473d713d94af33eaffda65a7a49d0cbae9f5259b8323e6f29b83da9608ba7d6ec004fb0710eb000000000000000000000000000000001505d30bf8f7c51994d896d91e8e2259782e2b49bda834015477f18c29e64da4d31f8b96edd080267b77a9539afca06a000000000000000000000000000000000eba929531615d9c0f59c4b33c1fc34b81e9c77cd8c6887099d850b3e39326d7caee1feeb101222f22bea1e9853d06ea0000000000000000000000000000000019d88f62cae047ddf2cefe497495f890d9ab8499e56f72488af65095e992427bf821f63555a67b0afb00d6fb441080a010325465403dbd4898beb740884cc325923ec3e1d7483540377d8bbd02c11382000000000000000000000000000000000b7c8f3d0c56b3b7d96c0a24fea3394551a186f87acbbbbce41d1313b23762945bae2e911725da4211614b456b508c0500000000000000000000000000000000125316f64bdd0c5bcd26a0e5bcfc3139045b3a44c8a8dd1cebbfaeb83b963c5a5abd4a5961465cff261c0e49189278d800000000000000000000000000000000095a327f488b901fe7dcc9f9ce6f4f25876bb09b053b64e9f4de9506a0fb95fc0cd443473c2cc5436750581d39b8e51f0000000000000000000000000000000015d406b31c791ae2d25ce462304c0bcf341686d7967c9dbb6734bc28b02123b1730d0a673fa8071dd90950d9411a2b3909545b90dbe35b0d5764bc72d45717e0c3aca6aa77c73178fa8a3ee9fec9cdb3000000000000000000000000000000000c7029af9422246d0a30784431d6bf9eca09481589438fe9a6d2fe1d5e526ec3d176a3d550204aadb85353d99bfe3ce50000000000000000000000000000000014a0dcb26c40693ad19a1edccda05055a27ca24544e933d01dfb964571071f94c94233f81e1ead0925d24e6d3df2c21500000000000000000000000000000000147a55ebd83c746128ba9c7ac57be125ca5c95f80f891e2c5893caa779484bdc1f9c3b3ccc4223b2343ba939251f7fdc00000000000000000000000000000000125622a040d8b157432ad81b8a83a9b1f0920b92680bbb65050b4862b89017b3bfaf81a3402ccb383265ba7200ce677feef0f8014102664a300ea9a30fdc7afeae3cc338fd45cd421a1bfea98e304c810000000000000000000000000000000013b394fd7a0f3d94e5fe4cf5cce3627d425ec848912395565b3e61ffe89e56be799c4779d3b9a0222ecc6538ca3346e40000000000000000000000000000000014ac1a87b333caed0f557fa5692d1138a8c1e92d1f9acdc9f357e2a46f27513dea42f367b046d389dc831610be4fbcf40000000000000000000000000000000011fa243a0aa8b0c01c7636387d60021afe6efc223b7deb69d030651c369643188b9dd5e08d6d031d71dd11eca1e825ac0000000000000000000000000000000015bf8fd7fe438407db7f1b0b586b2c285777c5b6dbef9e45b46cc0a50dc831f32a70e7d4316d4869bc769ff6de58ac30c8f1e08cdd72ed200253211e3b9947cb2a5fa24079b6920b4a4d3f1fd78146e80000000000000000000000000000000005ea57c269c9d43d3f17a83df04c95ea7e7bd85aad1dc2dd285ccdbd52bfe707a1d2476417e848ab119e62fea30520af000000000000000000000000000000000b99768ffbe95e315b244bf996cf34f8ac356664adda5aa7f4ff8d513b2eb5934b8ffe0fd9af94bc9b934e0a8bbd51ba0000000000000000000000000000000003b02c259df189370dd2700c5cccfc8b212a4b332a083adf9771503f5bd0c9ef040590320fe4a86c555a4ea87531268100000000000000000000000000000000003ebb1e610bd055d037a410cce3ae06aa654950aee0210ed0ee79f7a332be7342e308347d7b17a146a8b4c623029e08a7e25b1a60b6c6080ccf1bfdc37aabbc2bf92079d9356844f7f12867b3e2b2800000000000000000000000000000000015c4da691b5e6242af870e06b29bcde467b4644f01080eca60a28c7f941590192be30e6a4270a36dc8959b80235600aa00000000000000000000000000000000080f3d3d5c35ee24179f51ad854a37ac4ff867a2736a0e3e8f3312ac98c7016beea6ffe2bad1dd4842d6ec77995ff97600000000000000000000000000000000130c29dc633aaefc831b0bccb13fde1212fdce8cdd17beaaf1d06e74ef5b1b69bcc219c8d63f054690af1b6dc7c0d647000000000000000000000000000000000767290aaa1ed4c1dfa5603d976df0715b417599445ca577ded7d99e685118bbec71443fe1d9a65e0f23436353df152cdcb456eaad2b7c71ca32277206c1a1dbfa7e0e84950cbf14aadd455fb58e398a00000000000000000000000000000000133e997857f47f8d6278b8ad86f4692ba0dec9da336f2726704db593af368dda7aefc0b218ce1674f415e0d9e2dee5c60000000000000000000000000000000018db87da1272bd386f7d8b5245dc2de30e82739723b680dedd36f4ac4cf5042bcbada1e1bb307ba444431d73a4248f9c0000000000000000000000000000000006580be3e67c7a615408aaf9c95c0956678af0e2b1f536f1e69588193387f8a05b03d5e1060ca60c4fec9eaf3e72d39900000000000000000000000000000000050bd9879ef9eea147678f552cedacaee84562e6561b3b7338fa8f9d514099291c3f2a3723fdb22c88f1c9243d411ccba6e7b19245341fdfc5927cdae57f59de5f3fc8c37f8653e5aaca87db682034ce,000000000000000000000000000000000d8f69d90c871c08ae09e7b3e62e36514fd056c41fb596fec2fc9ce8509ab4f6675d7e85aa6b4b3197f5ab781f6f2e490000000000000000000000000000000011c4bd3cd156c34065e408efcaa5e13ad23d114458b71c2a6345f4aaf82af76cd4362db7ba9ee7e1e92ce72e242f570a000000000000000000000000000000000712dbbf20e9b24d20511d01717a3783608386408a258c2261fcdad5fbcab36c6bd21473c3d93ef8518975256c65a945000000000000000000000000000000000d13747be82153aea8076fd7813ecd7f60a214c31e88e25b14dee5cdb9336599e40b136d9ae6deb85606d35406b2675d,293920, -0000000000000000000000000000000017da08f2faa32570d95b9efd2d2fe358faec1ffe304750dca1dc3a273be3427c70904d58864f76afa19b0fe33ab1535f0000000000000000000000000000000017de677b713202f23baecef2b0618da140af624e56b876f2d7a20cd437c3868ea00ff6cd9c8908c1ef323ad294edd9670000000000000000000000000000000011d50aad957c54868aed6d848b2e67094b129282cc2df56c41d6ffe976d02ee83a592c33370d3715588a074db503b3e8000000000000000000000000000000000b8aeb019d120959b21627c1dcfdfb67ade22a948fe433172994d4a34084ac9e1c11333a9c663c87acf50962e21c728e92898d9cbad829a5346c0925c15b585de18869adfe796e46cbd56828540571b70000000000000000000000000000000001312ebeee36fff8152324a3ed24c37eee50b3099619a33c7a6316470ae722548b4b9e0f0640453caf53f374dba504830000000000000000000000000000000005ea81d2e5d9edeb3ed6c200b75beb731c31ad666e6e37db72ffd0265378bffc2724047c7c0c6e3f1598345fd390e9270000000000000000000000000000000017617a836beb12e637c5bbadd4fbf1ca2f5cc3280814ff5cbb5890b31cf2d2faee9e3ea8134af97ad4feace50aa194140000000000000000000000000000000002606deb5d57dce5b3d2e5f7ccec3ad036992beae238673641ad6042479ec3cf83bcc0fd03b7dacb9b4bb6c181ea9cc8c193fe87634fb0bdaa1700466881b557c470a62464e8521be311a95dff65eca6000000000000000000000000000000001203ef36896bfad2a2841689a964328fe4ce3d83798671630d0c8876e67ceda03d99555aac46d984f1d3bc38ffc134c50000000000000000000000000000000013e7461c256c8ff9144b17f8cc2e270aa94b64be62588280baca2ae6b6efc4d32b3800eb84da62561e0e96d5f0387a3f0000000000000000000000000000000009454b6a810647350cf0b364eb1c2b719670af45bdba9d7d1a534e23d4e810c3ef4d9318532e46fd104a83bb10159a30000000000000000000000000000000001034546c4288f642daeccf5b56beed2ca2d946bb4391d056df9c6fd6771048903fa330ec16d59d05540cd715333c4bc73dd9c99a5aea019436e3c91030d03ebefbf6ea6ac69222f1870fadae32f55ae6000000000000000000000000000000000d7782404dc6721f52648fc6969db33a9aa209f8baf5faa9678437c76c9e1635fa6d22d94aedefc90112223bb81ce33f0000000000000000000000000000000001e442e548d3045d1589817d0b57dfcd66fc64ff978186f784bd576faf57607170d49364a72189328c9837c9a2d8b0a0000000000000000000000000000000000da2b207bb7720aeca2e6ea02b65076770b960d4b7a96ed941a7f409757b952031a472384298acc3948bdc485088501c00000000000000000000000000000000048f85bc05ed78c692138f27c3541ced11b6b0ec158b43d133c3450a905416682fbb8c83dea06a06d294c48289ddb829e74ab390c3f73c62eb1435226e9b4f9b921ea1918a61a614b9bdbe9eebd1cd790000000000000000000000000000000017134f787c920bc15cf2228a186dfa1d10194087f28b6dd8f03e1c86226928f0eb1c27020a5cc74d94b50c4b4e36b8020000000000000000000000000000000012fa1fdcbaa81c4cc1e37447cae51beb29e55bb19b91e2b575afa3754589ee0151cd9e83573edaaefd341f381d34f4f8000000000000000000000000000000000ecafd00cc87a773a13909512466ed11288c842716e1ca5c37a4d9a4cd7585136c86f32140fdf02e2997a6e19e3d76a200000000000000000000000000000000104cf007ea863dbd473d7dbab6f55e74062b18986e9bc09bcfdc9c23e4bff8683f73aa998a5cce59ded10499d18a0ecc4dee3e2bfae3820f611c30df232c1d9c6bf58d40b3530858c79f840720d78d72000000000000000000000000000000000ffffc98e55f4ba9a642c40678d625690464bea39d085dbc9c99b4c36ea8bff5154eae3c315e1dec29aa669840accf290000000000000000000000000000000000a3df9595167048c52b8170596d4127968194aef7fbaea4594a27c6af05c54bb772928a7749d74311038d1c115e91b2000000000000000000000000000000000b317a3abd808e94a7197e0d3b2515a147774f78d0cd7d36e1156da28a26e33bfa76d75c6e3ae346f9ace050c9911cc6000000000000000000000000000000000fb5fbcc2f74fc30ae7e32143f219db7dfe5db6ecb09cedad8f087b6df56bf9693c8b7d78aace064e7c31785f6869541795fc8e20dd30622876a94afce1c1a76e3b689d6848903c21103cfce6a8a95680000000000000000000000000000000011e4b907a72f34af899a6c4de211af5fbe0265e5bf24d406798de53ecea273d5df4f4953d13fd7c9dc3bb0f0c143e3e4000000000000000000000000000000001623de5e87b6e1ee920e1b7d979fb9c431c12abb47b93876f9ddfaf28a7b673c18be634f96b813f7e0574c55b628a8790000000000000000000000000000000018ba994b02dad759ee79301b42ea20d7545844c0ea4bff2f95dc9420194cc4196fff12cc09bc0cef03cb7ba868c273700000000000000000000000000000000004b3527c8d148bd9e6006bd298ff8d7fe320748dd3f6d23449e874fc0c2f58d933c1e038a74f60fb6032cce41a3dbf5725b49f325e76733eb3c1a2cee5467157b2ee80987abae43d2c4b93e5157f083800000000000000000000000000000000129641af11fa92056236ef135843b2189d46d870381261d5781a5fd6f2c5cc1861ebb2e801f19f3adf2216609a9e196f0000000000000000000000000000000007b4007c55e47f6bf3aa420ad75fd191ffe0fe824fd30c3f1961a8168922476fdb3869822704999b044feead470e3b8f00000000000000000000000000000000174209113e2d8c363b04f49487176dc6d9eb4ecc0b22daa7ecaa5548d038b3b7c23ebda4f1b6845425cee13493385302000000000000000000000000000000000a58c80a02b7f93db01d2f8e0005839625e6c4f121f3d69115f435526a7f7cb53177caab4db86273bc2d2f0474235f31df49b30dd6aff459f64906eb1a9c9b2067d4f1b75057874b2fee17923bcb906e000000000000000000000000000000001738a03b46a8ca3f3d1f4f4447497c59f114005400f06813b24ff462ebc6f27c1c3c788b5f83f65958cadb34fddd08f40000000000000000000000000000000004dcfff2bc9ca0282016f38df484655cce7b872b1ff047351ae6b903e05f457d7fefae93104f9dfb549980394dfad2760000000000000000000000000000000017cd89434225dba07be137a73892faf0258b3fb19e6c8cec412fcda912c0613f2a925ad50ae485187020a371ff2dbc59000000000000000000000000000000000f1f9f87d3401e7b3b59331a89d9535adc973f869b81bfd8892a37117d8597ebab2800c966e623469792f4ae2a8eb232959e0a33b1fa12e0ba960761b09921b81746b8df23e808a8de09e7f5cbe2bf41000000000000000000000000000000000bdcb1d2a782541ff7884dde4167ba060fbd4b117944ae69aa2ff685b9bd7d475f45adce0c9f92695b4f4ecdd48cb9b50000000000000000000000000000000012a55432678043888bb9e7e47efb17700b3e702e389d0f58dd454224a02da3f190b2fef4c9d3e2074c7bef813fb56fb0000000000000000000000000000000000efa51ba64f1e7a1a269dc083179a222afac916778a967098582f55a41394bff3747f8d024261959f6d399f44a40d0fe000000000000000000000000000000000845dd0974c5789a85c3cb09ea441f2c433f0606928ee1b177eb851530d6e6b620b4fdcaffb8f75623435dff99b3ad9526ca68383528f6a871c237ae5214b49c18c4f3e2f3ef5dfba39e69eb181143d700000000000000000000000000000000180beba92bdb95c7803fca0407e29929ee64e03d61cad96ea0e6c469c5a888cc5ca5eb20983b3418a8da6596a5f1b2ba000000000000000000000000000000001322f7356eb3069fe20063f4be22c44426162dc8fc117e4e382bc4e33bdf3d971ef662fffc1d58ce187c33a43a4c853e000000000000000000000000000000001601a0aadaba846f11ba5c9f48e13bda1007ffdc1b8bbc9e85e83e569e9ee17a1e9e780a50ce617e6c780b8155675f2100000000000000000000000000000000105b2c213aa43ead42d9cfdf1d6c0559c25b4b86af43d4493bd75b76986d0d4f1d9b3bf9e3922b5c08a37a1629cab7d8f1f95a9d1d4e8e7d0f17a954177253709d988c3a77c77d35b8bf70294bb358c20000000000000000000000000000000017bc70346765b7160a0a5e556805c7944304acbecde06cadba474c51f05f22445c3d943674cc8215f973cdf11b9ea2e9000000000000000000000000000000000bfdbe202619a1d95359941c249b25462d3ecf09fabb878943a8a37cb9eb94abd7e6399f8d82f90ffcf904f4466cc5b1000000000000000000000000000000000f048db8530a288fef10a5ef9bb3cdd9f3d3b0ef4824609efad96bdf52d7c3b10ef628fa04f8b6513485e55f653f4b990000000000000000000000000000000004ec35f59287eadb1738bb50b0e2ad9d280bedfdb0a201e72594bfc4322ade0b7ffd6b532ebc7796cfc71f88a194bef4b481f986998d863c98e55a7661136a8f19d7d4c57f6036cd642ae16c82cdcfb30000000000000000000000000000000014424c77af7ace8ebf66f556cf219919712d96d24438466ad620221ce1ae9b2cd75b9c526e25df7fbf3c9250583757f500000000000000000000000000000000198aa00723781714152b3494b76ea3ee043b363b3fa81806cdf7e440b4cea907f226a3c038fb95c932710dc9aad4c9dd000000000000000000000000000000001360e4c775f6fa5e987231dce25ec67f61429ca9fd8160c3074383c30a8c0d7ff068b1d1215b2c0cc87129d9c9aecbc9000000000000000000000000000000001280ee6160800c4b0f82d5c2775238b4b223d8a0ac9a8f8013f138d554ba31c9fedb30e0eb5c330da17f5785b2717422ad872848d72367467094675a819f9aa6107183aa0c8685d5d84c27b3aaab33c1000000000000000000000000000000000f1f84251204d9f9328f79a45d15b311984df0715579633a82b5a9f680f6645cbe748b0fa64b9ce1e696e20a5645d6d300000000000000000000000000000000156901506e502a09917f76d825614824dfbc34d019ed53c2ec5395b51512da512b27541bc53331444eac2f618ffd5357000000000000000000000000000000000ea8736a97a33112bea9d07b729e973e3a942422f1d2b24c30e96637b535ccfc10cb5930bb59ed90bef604453df8772100000000000000000000000000000000187378477f60e3eaa225e89d8532bd95babd4a5c51729cca800d364b61575704992639dc5035138664e8e074ed0820033c2c60541fe17fa8e71d58184a055fa8b1dd0bfd16ac2baa912b4472c6056122000000000000000000000000000000000e5281c1c9210269a7f5ccd02cd5a7d3648b56d9ca6a4ee50beadf151c2601e0291fe7f1b89b694500e6c636d4e445c4000000000000000000000000000000000d5d5399f49697e46013558dfff544383b25f3b60681ba5fa2c5e6edfd3924267d0992abe65cbd5109ba8a1c6eadc7e30000000000000000000000000000000012a2104aa92871dd8e41ae1ae6dc18ceb7d0f361a5a4fc67936454b8866b8aec1602dd596459cccf6d9e1319ec3299d4000000000000000000000000000000000268795f6f9892f5b476c3a534673538647300203a51a8ff60b530094608b5fdf16297f02ab7ba41d6fe556885f064a4ff07c19ad4f10ab47e73b6698f9febf3f28087614759e082e6e717588c1caff7000000000000000000000000000000000a5585961328c52e0fefff16e66e3367e34339dac1a20cbc5e89b78804b8bc265e6e3fec1da6a62cd8a46be2f08a6d960000000000000000000000000000000016fbbd698784beec5a636332c0b20fdcb68fd3015cc6d18b541346a5e6af76613e6fcb14c888a2b8133c0f4132fc079300000000000000000000000000000000041805e0adf2a32153b89d1131226cf0ebd77cde3116a168e792ae8b88ba2edcb1fe7275658a384251b805d282ee039c00000000000000000000000000000000024213e4a8504cbae4875617b9b78473e7842ff72415ceacfaaf2e8b415f9f7e411989bada8101be72f9295dfbddfa3f240c881fdbfc414d3e85ead1cdf166ed6929d0b2ccbc35f0811473757b6b41af,0000000000000000000000000000000003c4f051d528166f256d9356aa9cb885db5680c51990d9474a948848888fb82a9b86daa7a2273725ac8ec564ebbf15db00000000000000000000000000000000010a6c4c7067f511ca8f1b66bf9ffcbb275c7575540909262f7c4332c3d75b2f6d2f3ad2848c0d455410afb1cd60c835000000000000000000000000000000000ee5e582554b3930c5670d4e3542bf32e8b871849d7859eafc077bb2b533e936d462f614057f9fc09c4010afab501c1f0000000000000000000000000000000017fdbcaa065d301adb94a60dd20dbae71512d369fc82c556ea0dff66843be768be942e060752591c6eb0718985d8e313,293920, -000000000000000000000000000000001353960aff58d45691c5378a0676a8e837260f5819cbbac9cd75c8cc4c6f1e17b9dc9843eabc0b1dfb27ff7631e4e52d000000000000000000000000000000000d6279a43d3526c035e88b0b640b04d42ea573ed07323aaac1d9d5570a8be64782682892415ba2be5cbb13f56e3a44db000000000000000000000000000000001250fd14fd003f88eb6e0e80e9f2ebe204475fc6c06cd10fa45608a17b7039afe0326474ff80c357f86c2825cdf7a16d00000000000000000000000000000000186cd91cfc8ae625e302946f2b393ea67e1107c0bfe938f5f36d28879fa0c0780c847aac77d0310d43211152c1d5f5d314d5455ff1717bdd545f4daa37e145121e7bd9636d7a2b65633e5ca5a63f2d98000000000000000000000000000000000e55a98e8b1e59600e86cabb5e8db8ee622009b1618ff0df3e93fb55b80985bf2a8ed060aeaba53773274d4186934f75000000000000000000000000000000000bb7215fc43f465f51fc8265477fc8c79493966f040e02f0eacc4ebcb3414b84fd94ded822bd24dd5ad5720f12bb8313000000000000000000000000000000000b23328e15cda8a576ea352b5dd7ce382ec781deca6c23f646e42f0cf63e28669539579ea51e3c0afebbb58e1e8e3243000000000000000000000000000000001716019236169bdb4af7bf7d7ce0aeee7900b74023acbb16f6965c2abcf28917bf88d0f9d5bc26a81710496f7821fd4682cd8da62bd901355a60b37ca14ce65d427bcf9551203cae7c346a49b4fa8626000000000000000000000000000000001718a4d6f5e78524d8df23d2c589abf04e3567d2176539a30b9f73c6251de573caa60c2881f3da99c48d48e9aacd7402000000000000000000000000000000000ce0e35721379077e6eb3b572f7f7718bbf775b116521c14acbd3ff19549c75d50bf70ce84326cbc3f9e5e53605d8ecd0000000000000000000000000000000007cb3305ef0d2cd7de4dceaf25d2eff44d4f437e065f6b244bf1b0611c891626eafc4b759d55b45d76e94b85852df1de0000000000000000000000000000000011cb56d2ed32a46bd951836f8e0f92d3824a4cddf011eecf1e2d92d81bff407a04abdfcffd60ccecda6e9443b328d51eea2c7fc2050e9c1ebd05d15f197b4b1be61c6820c8d27ade57d85109d7f982490000000000000000000000000000000011ba705da23100f853882dd166d81ee1d7621550d156b14f7c2123e2681887ec3724626061db68b2c63987325b27d6230000000000000000000000000000000014271414fe078a80587269398afd127ce34c8dc2a4851f76613b81dc99d766d75c703949c1093b04d66a301a79d89bc30000000000000000000000000000000011b7935ff284b0f812b5da5b28ed338dc4c21ebbc7fee04db834732b11fd76092db0e8d80368255b0f1205129081e9af00000000000000000000000000000000104ff0ad2e3db08d3b4890b2e54f29e456e627cefc3a4f07c1109b764dae4142480e3e5312ada43fec9ba96ce587e8a4e3bf7e661d54796c71437354d7d3182770f10ab450827512a423d3dc82d5b43d000000000000000000000000000000000c60749ef36d63960022f3127d0ab4e12acf05ba1e1a136dec89be388b9d7144c1d78c04df658727763dbaa9725bd8b90000000000000000000000000000000019932b1c205a765bc9de0cc136999deb153222a9dd9e9ec3660fb6daef56242d08791d440888e69ca0da2bbe0fcb7d79000000000000000000000000000000001764790d12f5ff79ee4f2c9fadd5dfb1cf47db70b9e86018bbdbffd1be18df193c7dfa71533afa381053a77e02719c6400000000000000000000000000000000044b2b0211cbb407281ab2abc4725c2cd791b313bab8779954a2461ce445cdae60d4a9efad9f90f80e66b1438514e0f0d3a364e7b217dfd649d1e08f76393372d8768bb0fc85c79ef4652417ef1637fc00000000000000000000000000000000175cf9e7eead650e7ae4fd657bc288b6b6392773bf1bbea48e17172a5019637fbb2bc0a3d0d1e3b8054564935c908db200000000000000000000000000000000136da2a625cf72403d0861b9cd947cdad12b1f1e6cdefc4aab6756536425285a7953a1b892df40ec12ac3430fec889cd000000000000000000000000000000000c2d10c6d71cff4e1deba1984bfd17166571e64659ac91b64c343cdf587c29d52a2266c00a57c01feddb1df6439d21d1000000000000000000000000000000000384a782fb31278f49c840bb8f0552ac2734ef36bb3d115be7df20333aa747c92db990f7e879399235d122fdba0eed76eef7b05d5c725ed31269ae9c56dc7ae35048af39ab114319680d4af69be7e7c3000000000000000000000000000000000a9a821cc63e7c9857b0f39f7444a1e00a422f7cd5d0575c26bc5c6b98313abfde51e3f6d5f4c817193bdf391344e5ba0000000000000000000000000000000010daa8c7194a75cea757b6ae4eee85006eda459ff2cf155b1b5f19c3ad341972f72e28b781c4878e8919c7e5abe9a1d5000000000000000000000000000000001154d5d5764aa2b8818a9dc5dce30ba2197a86d0bdc7dee3e600462e295cc3a69dfbf8db34acf138e7a1f16b62a45717000000000000000000000000000000000b4243a09b05a958d78ba8ae25fd3fa85d520b95e56f1dff44e556b221a075f8dd3370313886d9dbfc56a75697454d72acecaee3dd4dc11e341b3dd0073842d90f641d4dd467a6596f337a6147bd30a9000000000000000000000000000000001820f953fd22b71ce00bbe9e9b78fcf5fb28bcb925f6b5dbf5711e00470ed7fd2f38d7291d40514ab4258807f29150270000000000000000000000000000000007b737b56a2ba33f76bcf66c0b26fb44d5f79879273f6ab21ecbfe6a5744da289464ca2b46c55edaadfe3210b907f3f7000000000000000000000000000000001735d1b39c5369bbf886c5063a96dd12b85e56fd9d8ff9d84520918e1dfeccb62bbbe1c2ab440ccecd0fe66f6ec55853000000000000000000000000000000000e591b7709bf00bb2a87e9edb95720de19adc41a42378cf9ebb930c6d3f5993a1d7b6320040d5c69908685d978be8f980cba585b847bec40515a257cb839c7e5d677d17b7313c258e83d630e65cfb5d2000000000000000000000000000000001732ac410b2a7d10110bbf7709dc6fdc91ce742f8cb9b2c3ba37ba5f0934f8622c675753a26d04a176e24a630d090d81000000000000000000000000000000001111a52da6aca10cf40127fa8ab7683505305e0d474eed28a5e1735ee6877aa00c1bd598420876f2154b814660f3fe7600000000000000000000000000000000098c6d19c2ff42c2c57a4924693325de1a91135e3474ec699b70439d034469e72e844a5511e23dff3948a66cc2a2165300000000000000000000000000000000175fb79e5e54963cdbb133f38dccea2d1abc3cdf005c17e8f2de6dba9b9dbdeff7719983aa9ddb602f0cf966fdd430e0b8cd305c650d2e1cfa91ef0aca9dd0d785d7570d6fb67e61fb9b6817116a05440000000000000000000000000000000004e88468d35d72dba6b3e4b9ca216b75b5d20c447064a48bee6a6ddf994b1e22fd6ee8abd60c627622daffcda219645a0000000000000000000000000000000015eb2ae16e3310b4c4ff557f0615519c13f29109d9863418fdfbe6309b5bac4463456df8ebb0b6d9022e294cc16265ea000000000000000000000000000000001288ffe0ffdb96708558d914bc412758770d048c4d50523e2b134f8468d11a57da97e42bea303ab7137e2d26c0b3b8f30000000000000000000000000000000003ce563b63c50b09a80b71a1a82995238a9de31aaf189c6d29307924b6f0990854507b7dc1644f689c5abcf931dd5a3c825e5f9d81273f306a065fd064ae24bc2c5ce8dbff6b22128753663a218da8a30000000000000000000000000000000009e39ce653485caf699ae1d1d9cf2b8c5ea85b80ea042279e57f0beb81056159e49f73d67e7b1f9ece9f9ece7dcd2cf50000000000000000000000000000000008d6492cc335660c54e4a34b29b337b5800f1ef992d124524c799c04c852ccd3cfc01bf39515cb8b96151753147e8c49000000000000000000000000000000000ca779d87aaa3a6552f9f1a10b0d2e635be90022326db04e6072f326b919ee55d4124b9268f55751dc0f18172bd327ae00000000000000000000000000000000112eea543d6609d0acfaeb7be98be609f03304f50c3814ee8a010283146e6b5dbf170c7314598cac06efb9ced1ac2930307ff9660ad0c24cbb139486638a2556687f88fb93a290a1d174bf87d780b3fd0000000000000000000000000000000006624dd7f6eb043da41a36a15752f370eeb3cb2e6bd88b337b370fe0660c5ba8fe64f62e112f91d2524e9324f3a049fb000000000000000000000000000000000415b964484c9246385cf95461ab955ed0390e20209ed405d84fa8c8af9fa7ab39ce89049691a63c61b12bbf6aa2a4e80000000000000000000000000000000014411d7b2db7c9ee78ea14c6a315df3d90827b511db2e2423d660176384d8f8afd284879b22f5aeed73afb2eca4be52200000000000000000000000000000000105bfb471340e76f28901edbdbfe2ba246a8824b501ae2d4a73cffd2690181347c1e6530804614e88e2bb13a8edef8f4bfa8ee3b44c70ba2512c00a1aaecede2180b08ac3ac8c550d70407f0c12e027d0000000000000000000000000000000002b17f4b0b0231be229d87f075998435560ce9046a8b0e8f15e3a9f07cd52f3316f6d8c00d6a872362e7066715cf990e0000000000000000000000000000000003110eb232154f8a06834e2ddd33c0207ea552f439a6127b652bc261158209a00654e50341d333cd1b206a915fe0691d0000000000000000000000000000000007940e209c8934c185e4392f12fc0afe3d234dd1ef3f92df18d76be8fc42bdcdd6d1ea8d5bb6f07b3f3caecbeb5ef27f00000000000000000000000000000000012ec903a8442f68c03300ab02ddd08ec935d97bec9050d26a5e276584592df3ab87d596f90768d2c0918099b28963be58aa85b50e5f4ffe375599cbb912f41d35acbb85a324880148f9b9003c4265bd0000000000000000000000000000000010fdc16bff0fea02b325c672fe06297e0669094e2710d0baf3838f3e234c3f776bb3fd41b967c9ebbc72a6bc6eca70850000000000000000000000000000000009d64ce322e39d5b2d0872760a61a831877c450b1cfac6cacec52d4070b0f179dce90afbdefdaa8466f6a6e2e83ee8da000000000000000000000000000000000cddca46f3b24e05b76e61b4584bc716ca7036afdd914731a61347e453a26d07549e9808e553ee056bd47e53c75eac8f000000000000000000000000000000000451cccaebe1a188d3eaadd40090ca594f071c8b6d0e0d82f5b2d43fa784f8437e4226104c4cfdb24ece1ed75375aa616810c6cd59b14ef4f6a4c2702cc53c65b3dc84988372c1195980417c583fd7ff0000000000000000000000000000000005832ad778dca8dfcfbe741dcf311024d76341d5920b6830cb75893a112c9d86719583d1dfa7287281fb73fe21650c3500000000000000000000000000000000044feb86b4816e45ffb98e9a670fcb039fd9d8844a2c7ff9b7752f20e619195fe6ab1148f30afa393936d3605fa4c8da0000000000000000000000000000000018db9365370a8c703364ba6d9c48b3512da46cc603a43c3fb91c0a8ee59777d7cf9ac646c3e4274bd950d7de92ebce840000000000000000000000000000000017bd82310e251701cafbf8c4dc5b9e6c88085b0df287b6dde7887e1f64f2d9487a25b31abe07aec7d99a75baa5983195c5ebc09190ba3df49d8ea55cfd18370b9d443f9d9084cf84f2236ef4723d2d470000000000000000000000000000000002c1df194f01dcb503dcc8a283f059b82d141274c8f37cdb6441aa33f84f16dd288d566752a93ca23d26ef5834c0658c000000000000000000000000000000001700fa4459dd4e609453284f4f7dab479342675a87c1cb42b601908296557f39256f1597ed3b9ec38ad0a40a2c728f0d00000000000000000000000000000000135ed4f475eb99397cf204f971215a0303316a3ed8b62b303b4bf756ff753410b7fe263c4e97fd4c4b399c319ff3ad98000000000000000000000000000000000a487e179bf1b73627af9d7d2b43bc0e43127a8fbfeaea7ce958ddd53ecb27741eda187745e3917f1cbb60adf0286f5413a56b176fc835b7e825c817d432b9ec6d51b0a66483dfbf12166ee979b664cc,000000000000000000000000000000001327c57e16f03fbf652bbacd16cf574113860eb87b8f2f6e498dc5dcc4f2fa63859d922d88ccd6683d503d0962db5336000000000000000000000000000000000cb06948c539cbf686f6936b6a1ebef2e148d98c531da36272e0334afca5c2b16a52da542a0fdbc3bf764eb877f5778a0000000000000000000000000000000003acddfb5bc4fd5579d3f592977365840be4d3cff96434e5ff4f01ea798e4401930a1f5d91f8de3ff98504dce398c2ef000000000000000000000000000000000a5a332805f704613eb085d6639f99667d0d9247cae34eabcfa399eed551f24c5d5cb05d6458530ae270b1be682e71f4,293920, -000000000000000000000000000000000b1b06a76e5bdcb6c1c2f1952b49e1820a9d8557229fbff8740269a0b819b91cfd0de20db0afd76a2cb0fbc5fac12ec5000000000000000000000000000000000347654df2084082efd32cba2b270f66b0ed30fa8713b27949fc9d270ee8eaa9b9a7896d7a52dfd8faa3e0cd112a24e0000000000000000000000000000000000bf5b7a2c0c8bc223ab334bd1df5d9fd4bc0c635379ed2b32da13f6178e07217bb88a4bc2eae0b975f2e566f657d23aa0000000000000000000000000000000017042f8585a07304995853270b1b03bb08484104f7498a12bc865f2a0e37e662fc4b0331b94ee5690efe74056567000bdedf65658ec3cca48fd48e844337159af090c5d1f5e9d713ac6d0fe1e1f193d2000000000000000000000000000000000fcbc73d0628537eae417f8efc67af0a4c9c375d82406086bdff669911fe1307576333c389f189f49677cbbfe2ee98730000000000000000000000000000000019d552b85b1445660ca49518d202afdc67b0eb5be02c8d3482dc1b12e5d40a4ff95a49ce47809e4d6644d04aeb67b3c2000000000000000000000000000000000ed536c0f19f592180291bbce59a72ce5e516199dcbd4fbba736cae2edbe3cfb860ead0325dcc8f8d9be1ac126dc6cda000000000000000000000000000000000f5d4f0c0ae3e76b1c41edbbebcf1ff17c7cefd41e7ef8f75dfc10170834d05820149d5f721a8c6460cd0181571fca97db65ad6bcd6f485eefebda0badfc64e9e7dfe7e911f3ccf4f4fb9528dfebdae6000000000000000000000000000000000d6207f6684f8d2f083c963551bbf0a674ba40e691a34ebe6164ff80ba9bab2cc23024a896d7b906fb74c95016a9adfb00000000000000000000000000000000145855e7d610b50cde39db8995b127145d68fc9bea3f075f65b7793acbb14bbb313a1a39bd96fbea6641baae02612b000000000000000000000000000000000005b533ee83cf72f0e4d9c9ddcc6b91f4364e50a106becf766987c490d559d0f733839ecc706bbc9c2c75b243814068a3000000000000000000000000000000000cd8fba13b9ba7557c7577da183bf50810fb14eec7380e3b3d4f2fed62bb36f2b5ff288736bed0578fb6f47fb6d22ac86e0fa09884a7ff4c801ea0722cf6bfa58a46fc3d36058e8c395ea8fe56d9fca4000000000000000000000000000000000fd6a466f2eb12f6337ae9f9b847ac1481820013142af1a474229c5f5f5e1c0bb2d9678c19c7a3a1aa22cfc7b5052e0e0000000000000000000000000000000002a0340f5a0caf5c66719f7d546972bb4b89147989280542787d281901ff036b7c69d41418c21c43127c0158593aa5cb000000000000000000000000000000000deeee37ef96f26a4907e1a8a8f3f030dc09102799bd0c6dbeb1d208a0c86a423d0da6313e0be03c026da5614a6a576b0000000000000000000000000000000007220475449add59b3cc6570701528dcbdedacb9a3d39674ad4aef4d94114f24d2bff32f40b25af97ba883905ea6838a27a3377d7b9ff3aee2ce1194a22d7115b09a9fd53fcfa5e7f76bd9fdd35559610000000000000000000000000000000009d7023ebb73df81455f74cb2708c14ccecacd49521a0cf67ecb6edc8756e286ede59eed54d89eee5f77f178ea8fdee900000000000000000000000000000000002ad48fc3192634e7b01604678473e286afb0efe67a4377bb885d38b59ea00202241fb28c93232ce7c9a3dabb136a53000000000000000000000000000000001934664f2bfffb254f0415d6769f4e2ac710ee88cd822bf5da5df3a2541f887e4155dbb7e8056efb2a0370d6f9173e3b0000000000000000000000000000000019df518e1ebafe95adf683279729a3298fc8d7eb39c9a3dfe4b6665153f970e243e50dfb16fb87b3be54192f69766659446a62ef5760c995cb3cd0984d607c232c1eb0df5516a501ce448a189a3134d8000000000000000000000000000000001870048d360f397877321904563d35bfd0817ce464e0078e9605a4744e2723f49f9cb21dd3d6f37f1f9aff5a6a99bc530000000000000000000000000000000000e29dd0da13ac451d013d4a38408827cb0e739772e1f250d31e4192ddc13d651ab576ed6b8f4ee44e928fa663244999000000000000000000000000000000001646183099579322e0115ab0b3bd6c814e216ae6b2b80206354925565b7bcd97bc12668b7f3530a95409456ac99bf01200000000000000000000000000000000092f6f594ad0d92c9c64f78c819c44320e6bb5dc1dc8fbe58acc7ce3c101e49a74ae6d50b1a668a3b7436dc445e3da345f0c1a7c2dd281f7d2f006497f99f65d6a1e22f1d9aacb08724b3576aa19e19f0000000000000000000000000000000000428ff447de18dcc11b2c5c679bc2efd125464f589013c6964ea6cab33d9b7cbcce3a5d6177bf43114ee256f23fefa10000000000000000000000000000000000d1ded695e88dae6dfa702375959831f4bda688fc0faa289dcfb90a07f3a7963f2c9070958561909a2051a852cc15e1000000000000000000000000000000000c39bf1d11fc5693167890246c81133faee93a8639f459429757965e0b62e372153ce53c61f2c539247dbe7747b27d1c000000000000000000000000000000000e84ecb6dd9cbd4133c22350f07a976ae13dcbe4c6ae09ccb023f2118fa2dec68c20ba2266f9b571bbe30dde97480e0a94c1476ae0a62c502aa096a371e30ca885dc13fc417e3dc9bc00bcdf516764100000000000000000000000000000000015e040fc8753f06ed1112cc06e2cb7142a4fc984834f01faae718c17cde782d5953547857ca9aeee1c4a7d91df060d330000000000000000000000000000000006789ac15d719a7159b650b757f7d3cf58fca02d3b8f3685478ad5e5b1dca0508dea7a8203ece97c7c6d32b2f194458d000000000000000000000000000000001824d75634043cac3fd17ff0bb141daf7010f70b5941d8f75f1ae076713afaa7e0a0a25fc71038baf1b1255d64c914c6000000000000000000000000000000000a2f71bf85af6392a8a070596e30225bec9e3dc12c70e8df7c545bd6bbcee56799db2c9a8d2504c4f90ecf6a5e18abc9b677bc9f1f7572f808e969aa50efc519192ab8653c71090e5cf8cdeb1a3544dd0000000000000000000000000000000008bd859ff1f22d682f86e1a0e3bdf3a332ae78d64814720687a3de44c9bdd7506d2696b4daf81a94d33f64983967fdc2000000000000000000000000000000000d7b4b958e0087f8edf18a4370ff98700764c126808d5c52afd3e71ee326c766c1e5712dfa351cf5b3c518e52133ce780000000000000000000000000000000013a145331bdd9c93e63edbabb9f6c541a7c4dccb1705f07eb353a0407074a76022a8e5f5f2535b41ecf6474649e257bf000000000000000000000000000000000a12e461b7439bff0dddb560dba21ec53ce88f71fd3dc10723f3d8742ed63a1ab725f7e9619ca1ccb729564dfbdb1be7f5ca580a25a5c87015f57f7c23cc51a0beb5926c84d44659e45512da51aa0cf4000000000000000000000000000000001430a8184c5055008a06ea22ca9c997d1a24ddce7e374937c32ed1e487c80537b238a589b5e50b86fa194666bd3410e80000000000000000000000000000000005c78c94f457bdda242deab79524bd2beac82bb1cb427dcb2872b56d1f46d11fc9d69ba132004958fabc5da7d6d103fc000000000000000000000000000000000e985e8ca038b5dadc9fcaf22699e75cad9d2effa47fe7d4c579ee056b1e34ccc540372111a665041062fc6c39e05d170000000000000000000000000000000018c865243534fbde740de0ffbdeab0d38ee878c20f5d84c0226d1f2b14ed3359f5b5b909808b6b3789bfcab3be75c4cdfa1cc45c35e266a82899d8ea0c9c1f96f96140eace41a8758a87975b088f0231000000000000000000000000000000000c5b10541ec34dc0a8b8e42d9d6fd6f4f71e1fe56b5afa323f4ade35c0170b5e224a66771326d9edbddf2bd38c6c68ce0000000000000000000000000000000019cf33c19936f7489a1bbc095d0f5c6ddc1f43bccf7e8d1b30fb8e8cd1ef747b483b9a8e9faf21cba7cb17fbee887ad70000000000000000000000000000000010e83916faa7bc9de9feb8a7f34ac6f2aced06a771b662cbce846107245edb9c07632782300e838957788a8d88c8253c00000000000000000000000000000000066127bed5ac9f2871500fdd68a03ade57c35449d4b4186b9fac7c89e91b4ebf2f2a02e94d0b578aaf60b32017f147a493d2908aa9266844eb265c2b1c17f8357a5ff039836ba83c837909f6a9d0bc03000000000000000000000000000000000cb5a734a28b44f04d39ffae049fe8b63b138411661ca6dba00c72cadd47b50ad4b71e858e817561682d6ca378ebbe870000000000000000000000000000000000baf4d689baa09aaf763ae7e142b801223c8ff58f2b541ee4c44ab2460fb8f6dfc1e9f61a8d73aeb92d7d08c281cf410000000000000000000000000000000008a0c736f19bd0005c9d25f88565b1355e53fa3403021577de536712ec986567184f4dd626127ee80dd03cdf9044b2ba00000000000000000000000000000000063ffb7a3b4e057a9ffe233296c11fb462136fc4b187be6f9e36f9e6d335a3d673ef8b9ae6f60c146a075a1789f389cf3b94325aad8a2c80971a781bf6f6bebad63ee37405ab7e903fb7094beef14d06000000000000000000000000000000000c33d89595d039722222b9b9ee7ff1a0dae896a8de97f202d3aca00bd81d0169f14676efc4b051bbd339dce862d8b60b000000000000000000000000000000001109a24dc6f70bea47e040b24df395bf561cf5f1ee79e90c9b0480fff0795677483a85e6f2e9ded4f36ca849ff39d6f60000000000000000000000000000000009c7878f3a4e4e3149b72149a7da91bf527c4d7c94b15ba80b02e0e50b02a2c482ecae9f458a881c87e669986514f6d70000000000000000000000000000000004284448e42187c128578b801f76d421fc508cfee9360a7203a91d6f9cc7ccb6ed3211fc5df9e15f14aea98bc298b2f95143a8e734824840346078aec03d6760564870c5ee2b2dc13f8a39ac452be9f5000000000000000000000000000000000271ec1a3f8e3364ba8e101b49c0bb17e2b7c7f27a4aa4d4db5c07203195050f30c1a05d33c524a84b1a2f0ce31a587200000000000000000000000000000000082ce9d1da5d7f192c537b2bd617b36b65f88b308fe1ff85e47c64b62dc62324458493d1cd1da9f5fe308d27545fb6510000000000000000000000000000000000b30356b59eb04258096d0c3f357fb04471583cfe6a060de5279bf2cff4413678c1716ba87d0b6de6b6e79a96ec26030000000000000000000000000000000003c02470a14211fef14d754f6f71efb33a06a76e099093a5b9512f907ff819e1e0e15f14995febe48852007bb5c380bd0dbee37fea759c2a58cf360c654f85298e8ff44b3f900e8229c3f838345d053b00000000000000000000000000000000172df3290c3c5044d590eea59980d02e02d4fc6fe7948168492362de8f0a85df0c3d09d8cd8b206cc4d1608311ef4c130000000000000000000000000000000010e4d14065315a0d9e48204e47955ee9652b08318251a7836f32e6fc015d4856444172de44b3b88efa1b54dad346e9b1000000000000000000000000000000001549b9c85cb2fc2c7495d7ef6aa1452e58937baf58717037069e6bc6d72ced3a163f800991cd26510e71aa64c44f66170000000000000000000000000000000007814c2f1734fcc8cbf9fcba06b936c86d0452a2370f8c9480b97105e42f9babfe0869cecda7e15500e9d8d868290201b92f9db82d0976f4c379622c4028002ede2ab17f647bca3bbfb159045cdb342b0000000000000000000000000000000014f849e9749a5ff6b7b10daac7f5934be5f783d49c8593367c4243664e01b1d3552e878802d7dfee823e0122e9fd46f90000000000000000000000000000000000d0b32d7904dbf08269ca3c6ae3fe582501f55e32337ae361fe4a58dada560db54205e56a399aed33bce8758a05ebcb000000000000000000000000000000000cb21440baba44c3cc6943c8cfa2fe544a652f06423d3de06c2ff734ebbb544da07ba8982b3009b6c4857b73ceca570100000000000000000000000000000000174ef591975fdaa0e3cb05bbb4140abcb38f685ce4de77c95e2cec1911985557b77d9229940b8c9157ccf9fb553e8e0d98df4ba50cd5cb5a02d5f50b3ba23e5f5b0172a46cc315a0a94fed05551a68af,0000000000000000000000000000000006da1222c7ae02843ff289931fcfcb315f621972f45e4fb4160b8bf48cd8102d50fb53d2c699afd36892d91f5e608784000000000000000000000000000000000523048c5de2d0139965c976d8c3328666e99c249104712719e992334442245e955cd6b14a1e3d666220617d78edcc630000000000000000000000000000000009f669d4e7d89fa8d999d8d5a6323da9445583344276bd6a29494a91174aeeb29132926a893d5a0eeee9c3048ebc0dd200000000000000000000000000000000099ee1c33d6f09a8d063393d2a8debeaba93027e31f7b23c5170b6747f56bd6e6494de966dc280dd67a38d39ae35a336,293920, -000000000000000000000000000000000b46cd281a09b85d977e88cb2251cc61cf13181522cf1c344b114783c4fa22d08f2d008faea3dfee8ea7c56aa35ee39a0000000000000000000000000000000012b530bd34f5b31161355436c7dc444d41d634b0ac3292c0096558618005fe82d189e4b208a48240dfdb4d50ad44dc620000000000000000000000000000000014d334e7835d4bcee2698ca4f643f9692f28d8090ebb8ed8f67c60639c64eb116067e5e8a6d04a0c88c765c608d57ef1000000000000000000000000000000000578cb4e243b4b20c99afefcdc0e0a9e308ab4bec6d655a8c4981a711515c807067472a6fca30060d776213b8ed98d74e49662df81f6bd455ee554704ff0c7d5a589d91e017d7ab4c500d36378c17c8900000000000000000000000000000000046ad1212696acdbb686543d284d7cf2e1e8e8c10af76c6ba51d55f060c006dbab25d3a789c71c428f5bdde9aafbf6d5000000000000000000000000000000000a6a880d52fed6a45bdc61d9ee78d8fe472e76ccbe155bddd0e2a967f4d116bb9f2dd4c62cc6f7224b835c8060213ecd000000000000000000000000000000000786544589eda15edc433edcbaa46d4953da72473f04169ea64dc114b99f0a58181d41dce1fcaf7f3109f66aef02e53900000000000000000000000000000000030759c3bdeafc94fc8fc0b03ddcd96869459bf54ace74582aa06c179323ef076aef89c09ce8e7bf9109ab2e8c8fb0be79eb26c79d78ab84c4d7e48e56889a601fda17901037a74fd79355e7536f3953000000000000000000000000000000000e6addfe0db96a7377fcab1fb92183fd7d7f13ec003fdfe0740bcc8cf03d8cc602d5d808b4bc874f34944a65b249997a0000000000000000000000000000000014a4337107e716113d8ba0fc7f75e85edd1c132e2b3dadb3f9cdec1440f261513646525314b5c0de6fd372472aafe877000000000000000000000000000000000d472ee0484ed831f8ddf7ad86faef5443df8b943c6fd4c3f94c8d52d9eed6fbb53107170a60f25be52219ca4816788f00000000000000000000000000000000035d06ffc452c65a31f80c3f8a0c1e2d15e32d993ec06c50499bc0fb8f669acd3d2182ba23d942489ea922baf61dd49cd2918ddc2bfb7f7cb3d7e74b43b9a972c5b51ac20ea83f41d5b51034b5714c0b000000000000000000000000000000000ef1f5f6b3041939557368d613279043d1aceaf5fee3ed90b3b756ad409d700fb41e62b3758c8c2d325db7a37f339c610000000000000000000000000000000004d66040a8e055399bacb6a1e762b698afbfabf789caeb957fb7a3dccb01d7dff5414e90f5a14961c4e980b298f834ec0000000000000000000000000000000006efe9e66078000c26d375e87ffaca643aae9cd3f8337f5718e0e268b74f4b7838f7661dc0ce60f557e162a21ff467160000000000000000000000000000000014ab782a3b2c06af7e9c2f28f1604cbfa8a676a874853bf38195780751d306936cefd1cc38c2192cb756e28793d2abb3e9a8159fd7915c15db69355514d9dd26c66fbd14af969ee576401b1b782fc6d300000000000000000000000000000000057270788a199a894b37a526a26bc4d293780d365a6b66247e7417884d543dd752ef7c89f2f4b38f4b51e6f9d86b45ad0000000000000000000000000000000000b59fedd6798487ec09d226a7406b27f04f7983075b4659ca6a78c6bb8aa83828fafdc6488518e2cba6fa4193de938c000000000000000000000000000000001105c18d92b4192833302814ee9b176831e57fb64b703ab3c2d3f440ab302c8fdf7ddc81933d3b1adaad16038dd6dc1f00000000000000000000000000000000020509b08e6ed980df29da649051c7095edcd4eed4ce95cd797da430cd09062a110bae21b6f73daff2053fc0289041fac818ce6e33e581595e83cf8d33a62edc26ed38c22f20c6949a94e2652bb954cc0000000000000000000000000000000007be348ccf6a76827d3b9b33e7a89378c133c9b226e47dcb205ee061423ee6e1b838bc262a7befae7c15aa385ced00bf000000000000000000000000000000000689787c19192ad55b9c6c260a5ec3aa203ef71f0b746eebf10f82526c4fadaa8570936d7049c1a46e7f3cdc455a63a6000000000000000000000000000000000306965b09678d481aa4c754d56a0bb4565f16f7523cd0b404fbd39dfc3b6ed483f5239fa30f13aa3e87918ca039d5ee0000000000000000000000000000000000a2586143f9610a96eb0ef86593988770db5ed49663eab72f8c368b9388bdfbcd02fc6bee09f4fe055813d140ca0fa89ab338e94b31d22947dbeb20fce3150127249d2db6107d95bdd032eb24c49645000000000000000000000000000000000018f46dfdde786a88e582ff6addbecb4f58e12c2625e3d6440f2e5b5781beaa95cad6f63b7d132e84700e7bd344fe3200000000000000000000000000000000185a4fc339a95a50551d53c18bb0dc3b74e9c164729c2b0d919392f7aad2be3ebae3b8f676ab81ea05233b3039918ab50000000000000000000000000000000015395b020a9d0bb336066c1347dd91c557b6ae7b8817cd8a2cba9e5bb149ca3401d661227c26d52a9be234faea894c8a00000000000000000000000000000000103d9d7e33a0767554e13b57dc756981488a3c7dfcc026ea84b35b0af21193e301226cb5a4760962707d19a95841be9296acb797236dbd0316fdd355f07b9b45c9bc626f73105e87c376af4d7dc075d30000000000000000000000000000000018359aad8af59cdda484232b885d1b14956ec04b5584684b13a64d97b8310c283e5d66637dd75de405f5f4bc65a6879a000000000000000000000000000000000849fd55e4f3d4dfc643dfede6356826eef21290b84f7e8e226deabbc84273d95f7be5479e9656dc907ec367a7ebf8f60000000000000000000000000000000006ee01b54eb7834b4de53f821ad46f467cadffce6df09751b728d0952bfe615253d7ad173892a52c6181810a815bd90600000000000000000000000000000000161472d45b56dd9fd276fc607f2eef84c5c843ea05799e732d7eb6dce96c632335949e1b3a06815e410e919f4cdc3fb360bc12a8b34e717b2c410d026660c14182250d7c66f8f88dd4cc94e550421caf00000000000000000000000000000000107ef91cf3a3068c4e5644676f7bc7c5f9ecc361524bf3fe2ebfc606f22f8f83b38c0d4bae89f3cdff6119cc27fedf820000000000000000000000000000000006a7f7cad2fa9db8824e4e30da7158f7737d2536554b904ed835c37add0341c07c5220db0f9801da2587a456300c7b75000000000000000000000000000000000f6dc3adda42dbccb1d1e3fea8918f5572e8b26ba3011429e754edd28559b731853761d33777f4e767094f80e63d417700000000000000000000000000000000107d93537a79173ba9367732fa3a28113ec37e053cdf31ce6970dedfa8a9b4cd55238289be9a6f40319e3dfedd132f95537f0f732fee8b882d254a81704d2310c05dde186232f3cffc05401fa98302150000000000000000000000000000000019dc19a1663bb05ebfc0b7cc23ea9e07376de413f77e15a685a3f11fc19bf0ddf38d5671e2a5e6e31624cbcd47a19cf60000000000000000000000000000000019e78aae57f327fbe8ce794afc22bddde08ff9bc9ad3527601cb1fd5dc0b8ed8fdf3b210f86760954b48bf61d74162220000000000000000000000000000000013954a533bf871e99f4a7d81a8b9931c480ce7fc47260c3708c590ade42e6b7bb887d4d24aa18642d010a8170cf85d34000000000000000000000000000000000a561d3f64ba31a6d45ffcf1bcac95f8f665133a1e962e31351ec78e369042bd3afb0c43d12b3087168c1142107241f31a22bc0bec2501a505cc8e00a24792bb380ed451ab6f56fde07ace8b6c9348a20000000000000000000000000000000007149094366e29537b0ad7239ce04bf49f253e4b746b9fe440dbf9b425bfff21064fce66e286e08c87dd83e22a3b499b00000000000000000000000000000000045ead132e0d03c842656cfc82a45c8b4a3b0cee7a5d071c5f235791ff7b5ead071b2c529b446a15aa8837aafc11222d00000000000000000000000000000000013159458f2123698ad4e7d41da47ad7d5083b928839e346a32f2307ee69f643ca11335d50e47d328b0079f1873cc7e800000000000000000000000000000000167edcf807ee723ba70e352367705448047c6b5223fe703381af6bb103cbb24da739ed005b14fab5699fbae6574505a7c7b10c801fb9d929432cbbe994b404d3baa5633628f396d20d047fe2c2ac2914000000000000000000000000000000000feb6f6f85903b3c8e4d6ec2ff234775f12727fdf7c35eade09c9773b004270f659b00248338f0b749d6715778f1f4d90000000000000000000000000000000003300794df19b9e472e8b869a2762c07a9251cdb96b508dfecdbd62fc3c3843b37118d216a64519bc3bdb71e40f9bd700000000000000000000000000000000005fa144135a5d6cf1c73055750ab6582b4c6d368566172b75902b1fc7a6f5de2a251ca7efc7ac6cc6c0bded14df02b700000000000000000000000000000000004239a7bfdefbe78116a588810328024b1bcebaf8f28f09387dcab66dcf2b02c94002df09d12db369fef9dd960783c0b84f2f3f31d9869799ed8bfc2cb129dbbeeb096d771730ae2863c4ddece66158d00000000000000000000000000000000007c8a24005575a3098c12ffa65095bfe227ee59e5e978a7ccab7a9a72391fea61690648c102ce24af723945bbcafece0000000000000000000000000000000000323d57bec7dfbb4614c8c3b286860fbadbf71901fa006149053ea614dafd56b1f3d6a86fa55bf1cbdfe8af4ff08dec000000000000000000000000000000001180b2b0b9c4c12f6d06eec07bbf6f5a220722015fe5365d1c4ca9e58ac9c8f67964d8230152d7a2220575c756bdf8b0000000000000000000000000000000001969a364c447f07d0820586bade587ccc816e50696aa0c5ea4f1daf6cd577769a890b44caa013d93e7f21f5ea269aa85c62206fadb762c23bf77f69f69bd492674bb92edb39248ad2a432f819304e6ea0000000000000000000000000000000008a51c01c3bbed13d42a4da626a8b89e2811db1d83d7de3332b36881ad14a5c8668ece4f5ed2b71204810457aa3d75cb000000000000000000000000000000000658a56aaf627e3f776d3f03caa2c00425bf197c6fa20c92f563f48260109a8f935d0d1638f5039486ce0c0100834fcd00000000000000000000000000000000126d1964f2d964c290cd7364e175ca4a855149e5c4ba488829a436b09ee5e21f6c964e439739f15317873088726bd51f000000000000000000000000000000001803186f88833393bd853970ca4fe414a43b7a619ded1f9c830444b4d43a94e9146146e2284d690436b395bf1e3fad15a6f950de53d07fda75ab43f73982c2684edb06317568df15b8712dff2ef782830000000000000000000000000000000002dc3756c7f4bb47559cd720a3acf4159290d7413e0498877d1fe321cbcb7cdda90b6c8b4ef8e27b2642b82ab9b3174d000000000000000000000000000000000c7490f1ccfdd91aa37a3044d265cb0612bfd9c065c370adb813b2d96f02d44041e79921d1b8935dcdb8c83ea4460ef30000000000000000000000000000000007beb34bfb9ba9b6fb590c7e830400888095d1958b252d187c184de91f165e12599d66345341292fdcb662deadcded030000000000000000000000000000000001ce203d58bebe1eb5b7cbc6038f75b2f7534bce9f50e7e4c91d6cc5ac1bb68d9fd8ce99206c5ec92bcabb71672c6ac195a373fab5176d124f783a36eb2346dddd5c4eba9e24e4c0cdc4f925e2e24cc9000000000000000000000000000000000765acace3e238e51bdaa08c0f6d737c9de55b5ce9ac3523335f0d35bfab6f4e7e2944b8aa4ee031ae9d39d4db96e9ac000000000000000000000000000000000b0fd488a6f9e92c4bdb5e82b52a0035f9a0aed7f69ec65303632017669f34d11552f849326e4dd204d58f50f3ad124800000000000000000000000000000000033991f66588b5e39eb78c7cbf62a74bbde2fa1b7c96164cb58040f0887c485b372e0ef4def9d38da9c6f5c4df2d59a700000000000000000000000000000000187d41fa7905739078d2c2f8775394f830d20352a9d91e97568c6929412f356009239bc9e1da3a8c766e89d09893b5b5319d855218eee020f9cf8e4c0b6004902f0b16eedba8a1c911476af34f65dd40,000000000000000000000000000000000dedf92894c567ee656051a7f02384edc7206152af6d3c5f662ca02559a3cc349c6b034c6fadceeccf652a396dbec6c900000000000000000000000000000000089deb173bda620678247a7218408594efff7ab0cebbf627b93ed37e553cf944e09232b92afe2f5f31d29bb9ae442c26000000000000000000000000000000000178bc39b2ca8b032d3cde53d2da3f8797026d78c76c51381b377c79992f027cf55ba4e182773c99c99ea6293a948e5c00000000000000000000000000000000195d9cb91537e77e7a4be4370b982b6d36190342ef6ebc2250a9cc8ef6ef45211736ce1f41da899178c1adcc4927a9ba,293920, -0000000000000000000000000000000004638ececd7f626a069b1bc3ad9d0f7cc71e5f0c1b11711fbfee1f81466b574f7a11de8161e55eb574ab367f56b9d6480000000000000000000000000000000013ef4f403f139771afe7e97815d3b3777818a3054d02125d3a25138e504c8c2c6696061572322aa19ace9ffd8e3ff308000000000000000000000000000000001910f776582f5acbaea626d2378e9da133b63afa087f25c2cfbcd1e7b34f6a237f2e9adccc303f9d5efe22496ee2ab75000000000000000000000000000000001963bd62098614c4ca2fc2a9e2d679c2b74bf9d322d34377cc63c3b8e7f8a4eb7d6d440d081e044606402fb3f51b0cee2a397c2f19a8c4e66df0e062f179be2f45a0c5e104588222a1a78447512f299b000000000000000000000000000000001935ecdf4f21cc6b510321b4ed2663e339954cd7399b9d67f1d9e2ea7fb9bfff8531f83ab59d3f0546393dc289ab2dd7000000000000000000000000000000000f390b86fb4cd4c1a072a83e1d1198a57a650fa6e94b69d983b693c592bc0c8fcd9a46c6883adacc4c7e2546dcd079fe00000000000000000000000000000000136beae11ea54ae26a8d69015ea7793675625b2013dbeb081a5ed877832849d67ac709b81fcc4fb322b262ec3776c0c00000000000000000000000000000000011f1df574f63f679b6464df463b58948fd337a4b3f159229ba0313cc040303345e75a3a2b0ed0dedfbefa89d8331d074f193d5a575c80a3e7599923bf5a8ba8a48e8f98322d1d8eb1da42e446d518c1b0000000000000000000000000000000001510378fdcde4027999edc99d49bfb46423ceb0d740829e310f8a381a7632fd0d6b6aa3533c2702a5ca76d386ac0145000000000000000000000000000000000bae237bfcc061552ca07eb14300cf557c974611885aa6894f7933f7dc7a0a1cc3b5587bbe1ea5fa604e3cee1db5f7c9000000000000000000000000000000001743207a1814990c798dc3de272a02b1b194a485bf09faea382dedd957861dc15cbf981f9906cda50cce2899785b9a6300000000000000000000000000000000106f902004c66c80437392e92cad73bed6b73010bdc7b6a75de4c01f0b6fbe5b8d1f47378279bfa42b3af05120854ced07f2013742ddf2d35448feb80b6b7aaf2925d3975ce28ed2b1ac789886ae26e400000000000000000000000000000000123362e41268f7821fcd2294b032c6a51c6d80506eb052ab6132267fa248a1a60c3e4eb2e75a1674bee1c9d46d82b9180000000000000000000000000000000006296670461ca67081cd76528446867e1a4905f88742d0ed8d1f7baf86e0a5e5ee86c8b0eeef07c14dd821dee0143bea00000000000000000000000000000000058bff9544e4e02c063158a52a68c93c7544e8157d37159dbb99b51b09e3d8f5b307bfe63a10fa409e20a35219ac244a000000000000000000000000000000000b135edfbf53187004d0977db94eeabf426ad7bea84ad76c6ac771fa186a073d430af76d717070e3c4057a7a2da095984e637a80a4eb1b2caba68b6828aa18f956c62baa7c5e9e591a15156c5abb605000000000000000000000000000000000133d3a223112dc5665e78fba8fc8d040d133858d984e66d2382d5e629f9369dd127e93c7a4da77fad98a0520ebedfeba000000000000000000000000000000000e88515db391bcdeeed2a9f64d27387af0391bf832164fba79100b560d8150debbd703c140dae3ba9b1ec35c1f45670600000000000000000000000000000000042583722c69a19f413392c6a2b75c8ca969be85eb951056d7e1d94e046dba49c346d5774009b8463a40b0576ccc1a6e000000000000000000000000000000000ee61a9eb6ad497c57405a44d798868e22b4fd5b8c480e9938cfdd3f1817eaaf331a9988368680158c59c2801add0a7a27671631f9afd9d2e86f263f5c17c3c11c7f6e43efb6d75cb2cb8250094f228900000000000000000000000000000000020352de9b4e8ea1acc8589bb22e23dfd0ae3a80de9e21bdb3f6391dd05a012e635de9e1f5f450bf4aab05728c054f8a000000000000000000000000000000001733593b94ec800bb59ed97dacbefda5ad882a8023346bdff8f471c5613c67247e27d72cb4ed8cdaa0f236018dd2128a0000000000000000000000000000000011f272a3b25bc519fc3e229211b846042031e22fbed22ecd0d1a4ef1d05feacf105772d71157e3d7293575aca257cd5f00000000000000000000000000000000153b4b4d7d65f7bd13d20fee4812f04706c96cd1a0d27b7e139c47299805e0ac86e8941aa38d90331c78a61c2dd56aa3c2decb1f482f3eb48e7f52b89f6452b659812ef79bb42fb25f03aa9969faf9bc00000000000000000000000000000000143e1f6dd9397f0e89a46c6ec995bf6c87ec8a72b309f050dc5b3134e00e2a16327767cb0573ca5ea9776215a5815df500000000000000000000000000000000186cb3af2cdb4562bf2d0c180079547cfb345cc3943fd7f9203fabbdc1547079cb9ed854f9b1a47f513e318cd409df83000000000000000000000000000000000c8c9197fa5a1e66b371a653c5d18c01fed8d17a8aa92d89b2cbd954b9fd2931fa61abb6676e4851dc9481732c6195610000000000000000000000000000000009026b259e840cb5264f6aad6ebbb09661f5b6d980389817309aef99e4e0cb228d3a7a06e6c25bdb1aeafe5acdc44441911eb1de54fa8ccb746336b681504fd08f995c864a8dae2aa866862f81f0e7850000000000000000000000000000000007bceb74ba86c07d0fca20e4febd3b12b1fe9f786c9a5da0531550244f40261d7ce728498fbfcfe16cc235db6ed42e11000000000000000000000000000000000883104ffcc0d040d70bda04dcf67c1197c39e200d4d9daf5f3c185638a13dffe3dcac94fce4175187dad867e8d2b78c000000000000000000000000000000001404e48e86f199486db7d40076cc8dc4e2aa2c1b6d4bed8f027512e2c71817905b26ff4f0551f9c08a2a7a27b2075b6c000000000000000000000000000000000b789a6addb98ea43c0f9e85831a75b8ee1977936c17929fb45d4c06b4f1ec33b9b41e32b52cde542c9e4b64d27c686cfd0a61dbcb0c657e824cbcf4670a31a95ecbd47a9b93812cd5124f3ac9450c1b000000000000000000000000000000000654e7f3985bf90dd1e3169382690fdc0f804eb6384ce407a060f539804fe6e0451094abaa0dad611c15d3ee52f31a92000000000000000000000000000000000deeec957d58a2246ff8f7b7448f5198647576c16c1717369ad155ae36d5a6bdb42c8d6a1f0a095891fb0890b6203f950000000000000000000000000000000013a01a6ca4c296f59cfa4a5f5399d28af76ffcb8b218c861d5e6dc603e140f730f632028c8da46c823d87bff5ca703280000000000000000000000000000000003698f659e86b96613ca74a480c81e749bce4b74324976c1d241a0911d078926fb2adfcc3f901a7a015a02f525ddbb808118e9c70cc5def8e7d258e05273937c514131f39e0cc9fd2a3620dbffc7ce3c0000000000000000000000000000000016ce72e1798ffd84b52ac664a184c6cf5ce1ca2aa263c9d056355cf610517e9c7bf7f057c342f6e3ae801b84c2082c0f0000000000000000000000000000000010992af1438eec10881b5e2e3fa3b1e91b6b5313ea58dcc0cd2159f8ba6ce5912d81b38956929620e04b3596f6835a6f0000000000000000000000000000000014315dbebd532d0c835e8e85a02c0814574cf040a20c18d06573718223c8ea15b7ea69f0cf342dd09037258398ba4bef00000000000000000000000000000000136d13a83e72525b2d4af54d14d5e21d8bd9bed18543836b02ae0a7e51d433c93aa1943e85f978a8a9ff4454d8c5d120c445931b79e2b826aca02d1bfbb00c2dfb6d30ac2ef97a4ded18243b1afce7730000000000000000000000000000000002c1bf7dc75006c2941b89a2de52fdcdd1b4fbda5b14fe3fc165915b90fd9d93cdb8105898ef59d5b374707f0afaccd600000000000000000000000000000000049a16efcf81de84e443666bf990f6aad2145f9c9c2c61a752e256e8f447dfb27b462e4553544971807f909a666af12f0000000000000000000000000000000000aa4702fb69d791ef958826753d3f74f61c7a591ab94bb6c1bd5d82d94c5877121ecfc1e769d0d16ebe491b775ad96e0000000000000000000000000000000008cd7f2562eca6c53a37382fcdb04be53998f45c2241bfebac3d1fb08d8e1d4df3182f2bd63861d0de72d58072356ccc982ae6de98df906922e660d461009ba6c04cc6497f3645a66385c775b21b210b000000000000000000000000000000000a6b30c4ddb692ae33c903693cdba00ba48efa48e90b9cec9dc747004e57a8d5a05b5522634fc0de306d38c28390dbf4000000000000000000000000000000000601341b3c4057767a910bf30dd16324ad7abcb55b7e98e73584f26d7f87d8a8d24ff2113c12ceb3077bf65e0912b2360000000000000000000000000000000019dc9c50f613470abdb5c763c0272e88e34ed38e617d6757f4e70d05b8ec9f67a023b4ec1363e7e60e38cff64e18f0510000000000000000000000000000000013fb1858f7efeec5fd03d9f7f4513e3e9103c340eee7bfe48ed3cd3dd073b96add9450a17f12e161f1d44669b1b2f813000674ac5d09c6c599173bbe9a43726c120c3a60a96d43954727a2f33ac4320d000000000000000000000000000000000d6c135bfe0fc7af93571a69b7c37ba691f051d69582cf159cbeb0bd59b48342172a82a3eec2e3d440805934e1574f2a0000000000000000000000000000000001b04e56cb3bb221caedd3582943f89a33b955f624f9e473941f1dd987f2898339142a654d11d87bf8bd2fd0fe0d4c1a000000000000000000000000000000000f185fd420b761a1e38d542558b0beffba916f369b37296fdd8878a7c3d2ac9d3ab1d8e45ad799f0d81bb439b5e5058100000000000000000000000000000000002d10ce460c414fa1094ef2b7de8f1ce024b6d086d10067be0fed4e45dc25c8e50bef361d39a2743be1e1ea4fb7e2ef773f8e9637886d795b75e7ecaee512005c1780e7ab17b9f20ae9232595478bb2000000000000000000000000000000001972ea36bae504d7047639ce6e0e6c3b16afe89fa3d6c6b33c910c8d4b70782d8165912e5bfbe8bd84f78f9f23f7f956000000000000000000000000000000000ae55c4fc1c01f1bbdb060191e8551a7ba5ebd3dbadd138202090d7dc6765fd1ef5fe8204ae76a8bcfa03ee5985a35280000000000000000000000000000000018ebed295805e0fc14f1c7b0e6ee12ca48cb7177c1d367a613e0d6cfaaac5128fefce0e8f38d4e2f11ae0d327be466a400000000000000000000000000000000157068d89fe48e77e0f62e3b5b0293423f35c5f4ffb9e0577f5aa49e91cea6bd312a0e65ec08af9c1f53de6499409c8d759d0bab12ac790cc3a16e88f1a108e670681f117d4fc7d01f8c5a2d6ca7fe8e00000000000000000000000000000000120e4a8935c08032dbfc19a43e2a770b12b05cf1dc229e12f683f0d7f604bc13666bf318fcc38038b618ef83c9448b870000000000000000000000000000000004e3f851be46bd85f37c8b1d84507f4ed63ea76bc305cc26a6f4cfa2135d5affcd3b319d9f57619e21c964c6246fc3f600000000000000000000000000000000138733f352029373b19e1c40d5958a04257e2b344770e1bbb8f377bcfb1c7225ae7a8b0b0e57795ec06a08e13c90d7de00000000000000000000000000000000093e85783c556a017829e28bc42b607b1035890fb9743bf0e279df4dd8a695c1dd07a76a213087c3a8a7e614b29b7a1ecce865074a8a41f8a3f40228046c5be68bdb50ced10bb73ac8472f0525302938000000000000000000000000000000000be1ae00f9ba0a2e57f94728508e0029b1bafd52c91ce718ba41790a3541117d1a9f846d68440978cdef016c3b9ae422000000000000000000000000000000001947683154204c9fc93e3aeac17b417453a24d01804e8acbf6f67947f5962dce875f49d05e6ae65384602828784f852c000000000000000000000000000000000859dc1c00b49cd1292cdc65c6aa4b11e27637b949c7db508930c557ee3ce00f98f9cd3dd0f6d73a646d176a91d75c070000000000000000000000000000000015a7a6984b5f42aadebba1e1f4682aaf1a2d01c9ce2afab7fed2269373467787bb1361b493dcdd862180e9159ec2ad5785e2f9597c9b687150864e90ab042f4f012a54d92cf9d2ece09e4a081ec9773f,00000000000000000000000000000000047cc33d9decfd059724bbb014fb9cd95de187e2dd29cf4a9bf9ad06d415e2cacb5a4e49266c13e0c38f2563d1a21d6a0000000000000000000000000000000011c79d93fa870d541e79ad4037c20b85d3cec053587f9df60dc11e7dc2727d79059f75bef76474c09fe61ed10b317cad0000000000000000000000000000000003df3f0db20c5ffea4dc9f4d9333d76141447bba50da18e521e89aae1e63750c71b392570d79e597967dfc9952e903c60000000000000000000000000000000014e83ea645b1019ac2dfafe370f616af0c5baeabe217ac1f9ecf78877717475b25911b339557422526a8163434439923,293920, -000000000000000000000000000000000cd1d25f285c2073175ecad5bba4987cc52012eadc7b19dbaa20fa82d7a6cfb8a52f33469b6308d921eb4b3b23f7022d000000000000000000000000000000001707b67a23d9212d30c06f26f0040c38389b185057e80236d2c828a8d9ade4f72eee1d6eccd78e4f4d71e2c28ee9539e0000000000000000000000000000000008e5c04effd14d915b9afc2083afa2b6d4008cfa0e47144a41982d8b5a8e77922a2609384e2c5d18c871ae24a7d505b7000000000000000000000000000000000f414acb056fff2cd6d9b408adb6eb7f34c8f66a66ee93945a3381d46c2d181613047ca6d4067614c190da444223cab685431a1df7678e49ee049b75ea968ca255ef456dd58cce57b64edffac1ac223c0000000000000000000000000000000008ea841aced2d0b8dd688947648a8ff18d0f6f03f63ee1c331f126dd4fc0da3d386535156b80902bdc1f65add7769cd70000000000000000000000000000000017a32ad2763d99c38c954f62466e78c0332e48875e15afbbe9c78376f1bab12346c73a573738353e2162d3928091dede0000000000000000000000000000000010ea738884dbfe5bc35d031bde9aa4109b1fca529502e236aebb5ad0bf71dd2f3db250d924415b0bfca56519f8ca5d290000000000000000000000000000000013699e29cc1871f51a469898be8b3c732b5cf7860286e655e65bd8176832804d17b48d0ff85eb023360db78162581297b6ccbc0b600f11f1b89061d94c6fbdc9b1d389244fb29a5d140dab8842d44eaa00000000000000000000000000000000004d504e62b2825651381ae862fd33407309851d5291591cd0f541fd092800d709ede00a9134e65ee752eaceb0a344b50000000000000000000000000000000016481efba290c37aa4ecbf940c76ee5df199b0c1f90fddebd2db28120bb5a14dd9f4a067b6d4889aeb683cca0f6ab337000000000000000000000000000000001400c89942cc63417ca4cb05c9d81dda3251e5611c27fc7727c3e803170672f103bff26f7216a0b646533aac3171488d0000000000000000000000000000000019889641be9db08880543ff476b9d4c72167092548ba49a3f3ace4518d3874f4f7993ae7b8cec90f092f144ec9d66c1a54dfe31190469897c30ac3736ab166220dd3702df5bc897835347713d03a8d04000000000000000000000000000000001927fe80adc6dbb581349c603103ad8831e645d9275af8669939b83829182cc6e2a30df2fdeda6d3aa2e2a6126e00ba3000000000000000000000000000000000b6d7934d5ca1098a85a0c60acca075220105e221b802b1be97c2967820bffc2937fc3278ed0f26905c60d44d5fd8dc000000000000000000000000000000000057acc1379f23c0d1d37427d400eb1b0a89f3736c83d3ffd797ae279e01e2acddd84082f13f3c8b8f1bf7c275702a9c700000000000000000000000000000000038dbcd7e08d34c553850a52336991a7d48968e98057e930790d78b5c6368eb2fe01571b60c4aefb653ec04122953d56eff1ceff9e5184dd9fea44da4f07529823dc9b100f776cef6f6881120f7de11a00000000000000000000000000000000014052031b88af979b7edb06c99c2e46bd9df2c862c7e1b71321754841fad67fc3242b51141e49ad86b61344aec913f40000000000000000000000000000000014806a86d078ee9bdde99257b67f50dc2ddf9bbf01dde931742ee6f739aa986cfdca06cd32d23d86f2c14c3b09033d29000000000000000000000000000000000e0561e795d35ceb8bd9e3b276406ec1f697a38ada25d1dbe08715a28bdd3d6ce6e0aac01f7dfc7c2b403850ab213b4700000000000000000000000000000000146a65209b09487e00e356e3b29952280ebc6a06343c4ce14efa0c6281bc2482698bd02295bc35125275ff5f5bb867dfb273e4c6266c1f5cf022902fe1310d2191af91c47995486342bc61cd361eab8500000000000000000000000000000000021592cd7f4cf9cab3be53561c889c9ee865961aad51339f6393dd6a0b7dcc8a7c48b753c947b15cd3add01abd3d76d8000000000000000000000000000000000f9e1a80bad58055a8577700c177889c4d702de04343c1202eaed9485a76493158547b20bcb552b66c42a0c86df809ed0000000000000000000000000000000013908dcff1945cf06f038e3caac9a7fbb3a6466ca18627e93468a875759a2b5599a96834ff21fcd6bfbba82b79536b9a0000000000000000000000000000000001b6354665109c5a64613c3bd7d805b3a34098708f3d41c7b77db031ac6fa0b2d4e2f2f70c84ac78687b0c0f9bf334771342b5cd4ad3179f406941ef6ea15d0aecdf9f6d96dc334c39b7dca89d256d4f0000000000000000000000000000000019394063202186e141dcebce7b8f0f267ba6057a0f993bb1cbe22a5bc528323823bfd1597a87017d478186a18f09a47800000000000000000000000000000000148437bcc43d432d70b47dadac8e738616c97d38d0f84dc132599626612f7bba74988bb23ae47ac15e6f70c059d607ed00000000000000000000000000000000180851594710f4bb5be7ae0104a383081c50f59e4e048614660ab5a4e2661e171510f5b112d8cf97a6af27d56d137c860000000000000000000000000000000000599f3f82f29b493ffe9ee3a8363b9a599a5ef3c9c5c680d4e003f4ac5a7de0562cba8e2a4c6da7d07cbe86c3f7bfb85b36620f65ed84fc0bb344b4b73f4eba4b1680a47b28b47f6d10f9ee82398125000000000000000000000000000000000cfdce7997601afbe484901893a1b5fc0b83e8d238d41d2f889a58fd4d884df1c667a000b53b587df2c42ad46aa2c3e0000000000000000000000000000000000c50bf3e06400cb10494cd09bd89f3c96ff49c9f74dd5325f9489ed6be13b59bd7b0b2351411ac854d430405b8a2a3de0000000000000000000000000000000001db313a34ca4073e4fa2287e234ac32bc579742de22e5218f7873b922f5804894826e6054925a394f125fce850f33ef000000000000000000000000000000000e0627a66d286e8d4d3654b32fc5f552a7ca12f0bd47eb6dee0dde22ee48165247c067a0f4c3d422bf3562d38a3c0cf1249ca9bcf879a770b0a054422a6ea97ae795118ae45532c1523c842696de6d170000000000000000000000000000000005285ba39f5bd981fce2fdf853706d70992acab2dc6d4c4198144fab397392a60d631056b580d0d98f3f350414ce554e0000000000000000000000000000000013bddbc1180f155872376fcdfaff2fb12d3d9645b81bd1475a5323ea855cea820ed7eb693791caa9bd3fa5c66036439700000000000000000000000000000000125644d32df397def58dff875d7e3f14166e765ed49a3991f45b38d74db3985fc7f5052058d85594c8b97afcf850e11b000000000000000000000000000000000fca4662eb1b39f576ee820385fba88ddd2fc01fcfb9d9f874453ad725cd5defb357be028fae97ce71bc5ac26d11c1bac014a0aa616e809b674390b4553bf2d9bf325e73d3a935eba94488dddee4e8950000000000000000000000000000000015b97d7c74c8ec102083b41d7ce5490466e1c0e261b5ea5c756d3f9ae79dd2d8ec6eb5075cfb76dfcf7bfdd80442f7d10000000000000000000000000000000016812f845faf96b8b69ac7a6af3c8947aa25877199e3c12552527706a17b768bbea259ea61ea82c4624a96cbcdf4040d00000000000000000000000000000000123ad55e5cb5ac5bdd3ca0a5afa7c3f8e4b98ad91a205f073fb546fe799ffc57b3c1c3a6209547ffc6ef05fd24be6f5d00000000000000000000000000000000017719f31946aedabe0e9d88ef3f90eb6ceda884f5e3d2ece368373785b2d8bf0f9677731803b25accfcb6cb716e0aa4ab722a1c20f068b6955a44073914c418a082345796912ca634e79983a24ec4bb0000000000000000000000000000000000497e3480d58027c780f47cc35a121ee0cd76c4e84d9a2f9002c04a1c286be990167a0138049ad70467132818f48ec9000000000000000000000000000000000ec0ddf938553105400f70989140ca322d996f48ffb1b35641ca36a6ba9ac1daac1603c100822f80cf62ec3bfb442158000000000000000000000000000000000a0b6ebee28a792df46d2f727af812c15fc91a471e0d8e34b25b26048f3b9606d8375b5b268c40fb04ef8f098e1d03340000000000000000000000000000000017843dd19bfabbd0cfa41fb58e70a8900397d17ccea783087ece90962560f5cf090e8d9eaa873a6a6ebac45219ea97a68b314f83cc3ad501caa44b4c3ca8cf68c70ff6920f445d3a7ada212b6a19ba3e000000000000000000000000000000000b27c82d71f7e4aab9a68596669596df3f62071e921e131ba4d9e59d8d81d370e077e93a4a6a43e059661227f40b38c800000000000000000000000000000000093004917ceb2fb4a1b33960ff74943d520f86e83aa02b9a6c85e4b9a489e9331863cd30cb6ad6f099d03289b4ada5520000000000000000000000000000000016f04e35186c7deaf730708e1678089bf3e73c1164bca24bf8f70c4f6cccd5bbb34bbb5dc313ee428aec4ac9c638a01a00000000000000000000000000000000011052348cec9dc3e85e01abcca5a652461f08a9f5d72b3fc27140a6a571137f0065ed7ccf9ec8cebe314ad9a214d5ed94ffab83099c69845cc87727d459ae300a5725ec53176424ab0ec8bd3f80eaff0000000000000000000000000000000007083dfb0738d58ba8933a1f60283e5da8bf90af5aae4053ca573ee7223d3b80e4bdc30b4a831ce6af9f52f393e9742600000000000000000000000000000000130c627b7d3a527c94cfeba9f514e75eac047e1b6088c082229a8c95d0765a0898ce1e45694ca2c7935bb8e41e44e8b10000000000000000000000000000000009610645b074e652a08f2b89dbe594afa3009d795ef211f7c036a56274b1e1bd69a035c4f356b6b21f69b9cec2bf7c32000000000000000000000000000000001020f3cdef468af700269aa1e9d928e71b8c521f23586c9b0155314f0073da7de04ca41ececd5edcc052af72c05f0e4bb1d80be637e2abd98d0433150e14b629d98fc0918c7dfc179204669ab465e90300000000000000000000000000000000123540047f0768b0af841aa4aeceaf3dac31ea832daed86c8cbd1d33ed0282c6f697d5881f9022af032e90ff82efb6bc000000000000000000000000000000000113daffbe413075f5f4f6fb42f37b6e9d5e5822aa24d6f865792f63e6078584246bcf8b17117385db1d6233974f6ed9000000000000000000000000000000001067b46fd221b6995d25d4bf0adb088e0554d858d4e5d9d6b59e1ae2a7d57188d559b0208918a8944aedd62b1ebd4f4700000000000000000000000000000000087dae77e483d5c0baa37b9b96dad5ca92b5869fa253bffad24dd8747446f7ce60858b52438e58233210d86f470f765fe670a57ce4dcfa680e60ef33ba99c437e4fdb160ea1012de36f4b59613a6af8500000000000000000000000000000000039d09a094d655c139cb9714aa258d9548473162548048b0f07c9317a41a7e5dbaa5aca156992c8a509d4071d9ae4394000000000000000000000000000000000f0273a38b1b9d006efa43c15a53f026587a676912d0275968608519e97994ea9c6a147e377f68b1738ebeaa178f9c1000000000000000000000000000000000132cd92417578d2e46884f1c1a1080b1916c8c8404d2533a4de02bf8575c80ce7e8097c2ddd1f95737355521c0ec21ce0000000000000000000000000000000019adbf09a268a3ed8eff936d25fbe8af2874e44d2580c7941dc14fb89c5da963b468a7088c4a763eac89f4d15deaaf5e54a999fdf391d3944318c54680e69b58ce3778683b6f2c607d64450ed32c6d89000000000000000000000000000000000756dced467ea32c3c425590b7690a45e250e464ac6927ad3f5d2d8d2826961b8dd7572db609375c8d06cc3b9bc3a157000000000000000000000000000000000b79b4eecbaf1d0f8a89f9ef8fc144b3aff38148ae260da7c20e9dd3866d946585df7ed12c8b7005e7b0e1387c9db41d000000000000000000000000000000000afc403b008b70e19f17b1ef37c9c396577a585b6c34b23d09621b891efc00ef9460c3f4b5f3e851ef63620dc06c824300000000000000000000000000000000024e06f3f3b18c026a166c41f75d7bcf699480f5b6811463c27606c9ec1232fd249a46235b7f5b5a2ed3b53231b333150563ae7b444cca7ebaba36b8d59aaa5c0e6c1454a4a2907866247e752e640a7d,0000000000000000000000000000000004f2480d0b7e84a996965b76055f6879aab5bab26656e4e5ca7220adc17f14b5946047484327bbc5952d9f2ffa5f92af0000000000000000000000000000000002f7260c55c500b54df4391a55eb4adefa7d19bcbec82a107fc0d222c898b97360a679a02ab3023ce2ebdcffd125ddf30000000000000000000000000000000002cddfa94c8f6b3f29c2fe018b1f8679d0e98c8c2656f86f327b9cbcba574cc52643ab423b458994a03347460deef6570000000000000000000000000000000014eb4c84f71ef5935e707a11a92ba34780677ac7eb198e160585ad92aa5c1ea21e3a77be940fe344e7e170680e2a8d53,293920, -000000000000000000000000000000000c2c4c039047d297049afd0e8f0375bd4294d628d3a22078d93b684b737e8c4b6ad3ee544ecbeaad6b3c75d8d217f3a20000000000000000000000000000000004c77a2c0943c6f997ce2e785461f8ec253c47273ded4e1af43ae882766ef8c168e66d831abc2b3b3a0849bbc210cbd40000000000000000000000000000000004456a6c267a5cc6b7d9a9f573270855186a1b621cfdc465fe71ddb4d614565d9d36b13985b31396587919226843c6230000000000000000000000000000000009487cdd8a0cf7f40e9087fd3121cb480730f4302339d25fa12128033239662ed65579a59b837bf1bc5fa87db15b15335b59d128b5ac47106b4114cf225dceb621d177141ef5783943f40a54ad94e990000000000000000000000000000000000ba43205e8392168824f77bac344d60c1a9a0b14ab55538c3bfea4a64984cf381a2f61c64f1ba1bcfd8a7973e43f6e80000000000000000000000000000000000e95e5ac415c3e3e7c9feb6e7a2af3e8189afca06ae1fe54bbeb31783810860921ab3c76a475fb227b3c8299e3f1caf00000000000000000000000000000000001e3cb2106a23e77a126013087751c4d2a419a51beedc3a33faa6c933bedb3d34ee9c6450c583642426edb352e04da98000000000000000000000000000000000ab5af4c98aca1fc3fa55355351d12f3bc639662bf8b5b772152988d676b00ef39f767237a2fa3be936e83d1dd77da86a057c0405e24b7373f67197b2109b633a02589711b6a92ff49ca024f893d7ecc0000000000000000000000000000000012f3d927316ba638bd6294f7dd2f3f166d20285ee1662ae4dc145835704a17127078343d26042a5c397bfef31754186200000000000000000000000000000000162893d6252361c340057bcac31986458b8b55a8a4283f5a06ce1730098f9838dad1bca264374e7261bb9d08c177c1820000000000000000000000000000000017264aead0ec41a079827296f3d32c12adfca7cb6c674070d54087438d57b6ccca4822b2337263e60075d469b4ce0ccc000000000000000000000000000000000480cae035bd3bf1b4a4a766bcd5f188833e9587e1aff0e1f10e36ebbf2f3ae76bc0946e7c336efc3ee00bf42e7efbb9677b05905180182427efeb848b2ba2eafbabc7519ab33db14de0746afb607191000000000000000000000000000000000d13375356b1518e37a13b43b7d192eb74bd69636f91c570c41a741a8763c03caf8d13c7364f57c867a4a3983e88060f000000000000000000000000000000000f6f78dffb404faab88ac7373e0c765209c0af80514d438e18393bfcfeb60d9a5e13158d399f43162033571ee4a75dbc0000000000000000000000000000000010c379860638ccf3b6cb8479aa38881b0004197e3e367a1d5ef7c7fcf075689d283b87022e2825b5c789ac6a448467320000000000000000000000000000000002dc392872cf2fcd8e196f10c1ded175300070e4e38aa58c89c81e1aa5faa08d770a5ad90a8295a890551f9329a13cee53e7f69582f4c106ee5bfccba1d5f557736c1b75b6e3777cfde47d552e6bdcac0000000000000000000000000000000010383a21acda7c8f3f3be980bff2d57fa0a5b2dc424164dd2ce53c0b20ca399d6227913b7b550462180b01c231e4813200000000000000000000000000000000078aec90354721f0a31e1377b3652bcb1f388ab36f1866c955f3ea8dfe6ac2c25bc4cea14f54aee71595c2c1bd2dc4910000000000000000000000000000000007dfeec77213d952c183452b98ad936e8854608d950c0c1451262cdc7d6de5aed0db07a8d74b3e8f674967cb4839c4d00000000000000000000000000000000015c09e4ed2ea76d10d196f7a733ccc272b94dc436d6bb5fafad2fae4a96372c2c6f1325d1554746814ae292d8e6b1e3634c87bfb629b817e7ab97def7400b0a83e47af8d628787ff814733fdf34ba8d500000000000000000000000000000000138656fa091cc6613b1fcff04a3efb4f9c393985b2c78fa838eecbbbb8b6dafd88d9c72441f9bc735649480b5187acac000000000000000000000000000000000a35cec4819ca3321917cea5aa589db8cf61882fd1135031dc41a8207a8e71d326312799291b160a646148c382ed086b0000000000000000000000000000000005b6e4c02c9c54630c96271073513cac3a42d47a7272f62a21c7ad4c85c19b60b70d04719626cf4273f6c5691719931700000000000000000000000000000000166a20da734a47d7e28cce8f0c2d679fa6c738a7a1ca9089dc67ba2b1c92a83b024b8991f131e7e8802a617153de4554bebb60069acf431e1671e3d00e4da0d70fa11ed4099b21d45a2b811f99dd9cca000000000000000000000000000000000a4432a544deda931b1f62759320ada2963062e722bc1b748c9bd0d026ffae10f228be36ea0ab076358924f4c06b6feb0000000000000000000000000000000000e955b1b1b28d2044b6be371c58bc85097c77145b239e913bb0729757518c465d9e69338066f7496aa6a2038ea604f900000000000000000000000000000000017ca2a7d52c3a82ab8abf9fc1bc187389b6e4904e161541008e5b3ba0981870e01060d1272a6d59bfcfb294c942403f000000000000000000000000000000001870649a50e0978185551f213eefd9305d33e92b3f8c39752b6ebe18ae86ad97f92acef05971dceee3b3729becea18168b1ee2765e762f1b8c2451270cd5a755758fd733d7922a537aa9f1fd7d0c95960000000000000000000000000000000013713effa20d5039ced751ebafe1516f062f11ee05ffad37281cfee9d7a49ab14c065709832f6674bfbf2c9f379bc9c9000000000000000000000000000000000295f7ef148430209b48c292b024474f05036edfdee082c56aea05a62f1fba3ee7a540955423f78614c8385da8ef60040000000000000000000000000000000007408c97321b6d7c27e5e442a9e35b054e743c34d845874aeb1ccf4e903ca7803ed7fb1288327865f9e0ff0a388e92b400000000000000000000000000000000081808d03722a2d48846a693059c2662dee614f181dc406825544d30a6adc0f9d84a712eff80bddd4a27a036e4bf7359d5009fd559714d5692de5500ec8cae9c04ae1ab1c7c6e08c8738ef22da19ceca000000000000000000000000000000000880b646a674723c15b240ff56d2031e5db724251b1402a68df8b26261ffc9fb60a81abf165c6832137dc7a7293142d200000000000000000000000000000000172354b62bfb8d388b5a984411414738302725a508e8abeacdcb46454371d5e9cf762028fb65921d5c3dd8c67d42a981000000000000000000000000000000000a1af459bc3122dcef78359e468f4094d609ae3da09ca5aa6efb71a7494dafa2373a3906bac1f324d98b3eaa982a27d500000000000000000000000000000000092ac3b47253c7f090df076914cdc08a715faf153e8e365392b4859fca1db14d3f7fb998c97de9ad99b7d0b357252f086330c755ef708d8eb129475785f24be9e7836385ac918c60ad36e80e2f3281b80000000000000000000000000000000003b23eff722c078a781771d8b75d519e7a062ca3e4252ecca877845920158fb20d79a9ce449d9087426b113da0091826000000000000000000000000000000000c9026e8d3fee6282492393db504a2c41db19d8fbb83260624b05ba4107d6cb2c90d645a3c16862b27cc3fcce9bf89840000000000000000000000000000000018b8648d0a42285d474f809519696df9e1ad5c35d8e848ad74fbee37425aee8844a8be8cb4d3331670ee294ddb9a290200000000000000000000000000000000068cad37ee8578f4b502ac2ef4371a10e5432e57fe20d0cb074dc427831872113d3514a0b199d813b796b8357fa2a3dbc2431888d05cae840dde4c26911db1893659fdc345d5433556d9bf75e51fe3740000000000000000000000000000000013200f0aea4c60937be47213b6149b0ae76767f3559e0519f774af4a5d9431e2dd7ea74b42cc3ceb28ccf0d2f01116f30000000000000000000000000000000001c5bff08fd16ecb68f21289a3e7b9a2ec5da1357d604710a18e78ab780f8ef0343d5d9ee7f7988a009329b17e498beb00000000000000000000000000000000125453772eb9d1335ce4dbcc8f2ab8426fe89a0e49fec51d4e96718a38570aa82dbef452368141be2df260fb131c50b2000000000000000000000000000000000432cdd445519775b9914a986a0941cc829b4a15cd361df9ae7129547b24f7a6a15cd8fe9393fa1551db2d761a208b8ec9a72369cda74e5c86c6559cbc4f4db1b3ab24c5150c7decea862ede3c02c505000000000000000000000000000000000396cb6d7b44f92b716ed02985d351b4e8cd1bbb95f239e4f29d7379428470be395e2faeb8e3a910007aaa490d3c336d0000000000000000000000000000000000ad0c0623fdf50c2b504777554dbab3cde1b9705e976561873d7c22b81f49c7654a7c76e558fad1518ed73a0d3c3570000000000000000000000000000000001241d5bed68e02a2ddeb3ccbe109a161abe81edd7affb72182c5163851211c4763e6aecf766053b61ce575de893985f800000000000000000000000000000000183696d2a48feef6088f4e9f75a5055e8c54b3813658b593958490ddd4245ac495a8ff966861b20f26047f07fa8609a0c2f50989b04fc29c4c4a0090fb11e860c15f89a66f3bb8281e4678ba63ff3f9a000000000000000000000000000000000fe0ce41aa9e7cb2bcb4e01721b7b1d99fca4e9b7c4df09bec00bd346fc57c25118ba70d5333b7f3eef2659c64520a470000000000000000000000000000000005c932e09c62b7ddaf3f5c420c60740befa7cdff5bb812e0f089c45098d71b57004b7a207f0cdd34daaa3282cf6e9f7e000000000000000000000000000000001874200ead9776c1ecd6a54a57e5d0f9577910a4b3afb9b051622f658fe3ef6cc5070af60e7ef910562720e9716158d6000000000000000000000000000000000c2c657e58e400a67e59deee8c28234ff4688e781a2f6f2f0d0b186a5e4012695a522dfa0770cfd543f55939a05e20b09fc9abf1c76ff11ab538f46ce768ba597eb5f2f63073ec67e8de10aa1d666720000000000000000000000000000000000f0b561e5860321249b9ff434c604d26c3275824fc4ab9c1ce5c5858605ddaafae83ae27e523bf6006932f6c7f33d0a7000000000000000000000000000000000b47aab85bbd909599aa85c5eda363b67790ac6729fd8b1f4f53f66dd796cf2fa3496407b1bfaea4dc8eae53519054e70000000000000000000000000000000000cab1ebd23bc05c53bc9e8481c469eac3ee1b140af545bebed10a8fe50698d2ed883219881929207c0addf2f687198d0000000000000000000000000000000007742de55b799950e6f786f4eef45d0fb67e0475272ad68a183135b70047abab6c2ed51ede16c39be7b986df334e9e75d4167723682bc0e7476797b3be5e14b8de3e4e23b4ca33c50a2096cda8766dd7000000000000000000000000000000000923861332988bc843a65ec5dd4637f9dca8a15e71b82c780fe60d768213d118d8948ab554e30bb9253e900a9b7d87f200000000000000000000000000000000132b1faef49e7966a05783ba526e71134bfb577b13116548352da37e91e617d7c72ed2645e672ebbc517e079247dfb0e00000000000000000000000000000000000a46a8893a194ebfe077afd05fb25d4680f1e4991a3ec29475fa5651d086d20b38136155a65a4c70af31de5a78af59000000000000000000000000000000001344eb957594028b4228cbdb8efb03cc7cf49ec43b2ca5481eae1df6f2df3d5be9a7c4e4e78f8c39be546e29a83c92f49644c3727f78dd12811092190d5d10adcd5b9fc581dd783c97d4e7b5130f309a0000000000000000000000000000000012d7111303563a6358e5ce9155d7a153b5781062c2f6b919efc67ddfb4c61ef03be8828ca6339397b84763a5f8a7e8330000000000000000000000000000000010a2a0ea9973728d3fb1b5906ee84b2635c687c11398ebf605cad30216df3b7b4e3ee1653d4b323a690e6ba614ebec30000000000000000000000000000000000b93d5de37b892d4de9407a820c73ecfd6cd9fa565db82e7e8c14c8406823f705ff0adf6bd6add5ddc5f72c91e52e840000000000000000000000000000000000dcb320ceba5436df8f099c5a77f34376c96d830f5e8ab80667d156d89f6bf8998c148ef9a53847ed395871ab86f6d280df9846c84354ab7f947caca7800e12e38d8e6651527e6831f4d8b1bd66c4f3d,000000000000000000000000000000000ff3e299e6b9fc488d6db991cf9d226d330846e87c4a5cd3e5d4ac955bc2c3c2d1ee5ff230098b48f594d256495f489800000000000000000000000000000000097fdb8fc95d64c7070d8c71e0fd2a933d1e1ad3fefa230f079bc5743828317cd1b0c6d4253ba9c3c6ec6b12d53afa700000000000000000000000000000000002448bbb179d82eaa453cd5452752b9a083b52694cc65c5d9b47f72eff118d7aa06b0fba18eafe93d2937d7399c001af0000000000000000000000000000000009dec4c0aff2a46b07855c87e08832811633709ddfbbc30dbb7e78b3de43bd95e383baa6ff4c58188f0c7643e0ea5a40,293920, -0000000000000000000000000000000008afabec8a9985cbbc6246825785654c1d2eb7da5a01f76c4af4d0096b9baed3c33dbe492d14a6f9e762f06eb3d198f800000000000000000000000000000000027c592315dee4bcc892acc6f41a6eff5219c308253f7cd715d0e4a32c03c6d0d0e8568e146e9e799ac3025486c77fc30000000000000000000000000000000015b4ee27a3aa518a1ec1b447bb8f9128301c85b7176296d68dad3339b1dee78715b2f031a7fb6ba376145c97ceafeef60000000000000000000000000000000004b7e30ec7cc024ced863ce511cef3cabe954a4e5843dd636d776645a44225a36ed7e153ab5bf5d18f23c6444751875c8a71abe11a893fce872f6b8a020b6d84241df03eb934b50cbf3571df4800a83300000000000000000000000000000000119949d36d8d8e2bc1c26ded5f5fb01225a980a28b934ed3862480dc9297a3758e0f08ccaab3a09b5e5c0e4215e3246c0000000000000000000000000000000004a82dc22316ee6af39d937b662d1f1f2dc855c2ca8f33ec3274d833e87d594633fc7fab247911e0f46564397910d6ce00000000000000000000000000000000196900a09d8504ed960d41f4a8a2cde2e5dac61b008d3f6eb47e86d7b2ce6fcdc0f85157e3ab1571094d9fdaa75d0d500000000000000000000000000000000010c52ef9407eb4ec57844aebbcc3ea5000b1940d035dcc2a873327affaaabdd79e3560cbd29c63ce04f6279056d6eed1bbf28e5bca314391550d3a0fce50b1220965860e72c8c3865a2d4c599d31d3f1000000000000000000000000000000000e43655ae05dc6cfa93113dc26cea895d1c5bc73f20454c7b441dbc5ac80035b290514b13b31b41931ea5336d8d9a6a7000000000000000000000000000000001199a873958c63147e6b82625dfea15ce90dd41ceb4e315f67221eb874ef32c6a2953412e7e981659c72239a7a72bfe6000000000000000000000000000000001845af5936b4d7487ffe59137ba2f86daea3770cf37fd560969ee48243389941a1072205e049ddaa06c0ac56b7edc8930000000000000000000000000000000003cc831177f24614f93a118b896434105f05a277051a852fb9973a775fc54f779c2a1f3d64c457e5231dc22d6aef606b58b208a6845aeb2bf31999042c59b7b130a7ce5297e88023953b1aef63616fe400000000000000000000000000000000005e63584bc85ba58615985f6a466afe05268e545e0062cd7214e0b6fc8b87537c745b754cd9a1144948bc88b3c43acd000000000000000000000000000000000635b6a49090ccede3ed2ef203f0ed164783e3df4d9a7d93319515cb9230bd841b61a097f39e30175793b3e934d8e426000000000000000000000000000000001861e65f47a9da1584c45bc79a66045d86bc1709c2d1cf6cd2930a9fcc8c4efaa6536b5015be8d54789e8f574f93f9f70000000000000000000000000000000009290ce63d55eb436794acf11be9d896f03e7608a1bc8528f61ec9473f054bc9fbbda1072440e58e2f6ba080a01180173b53b6cf9e0ce1661c4960283be790abf956c2d6433529b8f3a32b92b227aebe0000000000000000000000000000000018feed9500bff884d2bb58554da2180c68267b6d3a45c2c7cee4c3f8524252d3faaa5eff971bf40123587e669fe66bbb000000000000000000000000000000001441bd3b58b4a4a87c2459f873c0692f5977b775af984bab46dd76cb9f775d2faebcb77b2854c9f1faa33f6c5de61c6a00000000000000000000000000000000123a890c3362c77e5b5cf9846d9c9e43fb3242d5a831e640ad080993fa0547854c8d11cc22f7f7b426528bf1154d2300000000000000000000000000000000000ff4a59ea98d13cfd353ae61e18d3c7018688f755561e6a1da5f09acc4277e8d49645087115acc64f992ea778a11f39bb049228435ade4c4c565e65f39f13a84c747c312afcdaff352560b9fb3cfebcc0000000000000000000000000000000006b019d005141e82393a2ca04469d1f6fd7b9456001ffef4c34eff6b2e91df58e99fd07944f52b108bd41ab6c4d6bbf200000000000000000000000000000000109ae87042029856befff0c916db5437e1e058a96f2970d8816b3becc93a1a50d6d336d5451303715f3e272147a36caa0000000000000000000000000000000000fc381b8dc9dc02d34db13e34732a10d0dfcf676c224a05a3bffd888b0af7c415b38af0b6afe6b464ffca42947c6ee5000000000000000000000000000000000087040d09c39ccd06c9ecc360fa02147a32e8036ad6e4b6bdf5b3883722a4e5a887dd022d53706d2585fe558696be6656197f5ad17062d2ecbdc8887bcdd32e5ed4c48cefd9e14d622a0b800d970330000000000000000000000000000000000e35c27b29df0fa9298bb9ab6a38b3450782223e2115d79152f9baa924d762d583b3ebe88e42f33028814ec78e5b319d00000000000000000000000000000000190c65667627a16f0af0ac7f23af0803bca810f3986b906b7b4f126d98473d52badf45e90e2e45bb390242fa8c40135100000000000000000000000000000000103f0283a5673c16bcc0f74f259c2eb077061947da04e467dfebf62aa005491e32b85cb73418b624a30dbaa01672921e000000000000000000000000000000000465466955c908607191faf15f0768dce42488c488eb4a065977f21ac7484766bc0abf23961ea2ba46dcc04956abf6c7721d9d7fe10104cafcad71307e785321ab87b2b69593535caecbf0e166cfda5b00000000000000000000000000000000082346e352e845a54cd4267f93b85b2c8623d4650e00c1c56082b73ee31f63588d2c117d3cdecc0378fbbf8956b082040000000000000000000000000000000001a7f43c2bb19cb32345c43c950536f8e85815b86364f278f6ec8169eca80917c2b8fc08d59b20cf55f25dc468e7bd7f00000000000000000000000000000000085a5cb020df10f9b4c7afc01b1d11700579dec1e85e766507def2e6cf5b714174f7be9cce3b18533a5ebfeec2b4e481000000000000000000000000000000001836d7506d1cc984fb777b8ee935d6f5b110644f59e96ff44d8329336d59a3e1d2b53a05d35e97f634baa4fdc11a6cd8461531ecb61365908019c1e8074a4c322df2b356eea3f3eea9aa1e0e1fc5525e000000000000000000000000000000000c1c59828ec6257a02679cff0bee0d665d449d2a158bc6d877e84cc0fe2161c297dde09b778d5e1249c515833e483004000000000000000000000000000000000f5e82589bfb7781e4110f1486752b00cbdf96cdf4191d75053c6d6d646e1c989add011361031a11559e156d64139fbf0000000000000000000000000000000015053afa7fb2b4e4b70f3c8a570fef8288fdc22dd951b6ba8a40b6087b9ab04ede21f0ddfa84d6d18914041bcf244c110000000000000000000000000000000003f399800cba51ab35624d866831ab6506392cb3acf549787153ffaf08cc451acea46c7a612821dd96c45f8b75133d88569c1c1ae2d18bbe36ed50db1bf30957802b09a982fbed49d4968815552e010d000000000000000000000000000000000e26242c8f73116079369ef4265f624abd4377e4e3485c28197663de9de9f5618c3b6ee602ff6bebd1c242aef7295b2200000000000000000000000000000000066ceb3ea6067220bd28fa1164237782859d27c1d3087a42b4d09bcc343611e4ed2be014a27f5b394c67643dc00f57cf00000000000000000000000000000000157f9d30de52110ea7a2a35ddfe67d9fad7223c5e3307e797dd0df3621520a421958a2835205e3c4777923f47d47e5310000000000000000000000000000000016ebb41beb85b9489a6d5482f8a3330a5c5c5e5718e8efb8b67362f9d8e9c313e9e563275ba38c207c5bf3d89c406ea62061d33b2f7e786effbd2e93101a56ba1bb62c1a773a08b72ca82f5183bea35b0000000000000000000000000000000005d1c9109b5b7409f94ae3f7dd9e8ae4908a9b378fea4ea284cbd33d1e59b605577b63892aaa8ec14d415f34e22fec520000000000000000000000000000000005afed05e62599f20f7eca019f41d770c630cf6359cb5601464be821691fba5205c16e7b580e6881047214f938e5104b00000000000000000000000000000000105637a2aa4725d8e080dec3b731a111ea4c94b79f898dfd51f645501ef0c8d68ea8e80fde28ff96e927e44306ebbb1d00000000000000000000000000000000080cfeea754474ceb37973234d5dc3269f8ca99bd862d4d2d1a602321fc709945a3209e5ff2cc962cfa6d03017c9a1354129b150752d2d5551a622231ab067931678454aaeb23f76168219406f0d50ee00000000000000000000000000000000137762ea5c80033aaf17570451b15a062feedde810f11ebdbe9a79a3275dc12613e0505835c122bd5f9afea7dba84203000000000000000000000000000000000d89c04e45e60769a63fcd73df2a138c457bb549195f2c4eebb3be1ea46149f286756795be8328b5b886f497d8167b34000000000000000000000000000000000be43d515083c8c10f467618685a43d4d5f6457204bacd278445943a9f44f7189b561a0e1bc59d2757fcfab2e3f93a4a0000000000000000000000000000000011a52583227c6dcdc1784d3633fd584612a9f3bbc1922477396dcd5af84413e5e9382a34a71b3a72491ea09fab2fc6bf366c32d5d3c132f32a6ac3cfe1dabb649c59ae224338f747ad98b193e8346729000000000000000000000000000000000073acefe33525dd2d5204cce72371ed82c7e4b58d1b4e7f4b4994f9c58b02d9d6206fefb3552446b6b355e860ace43c0000000000000000000000000000000007344eaeaae71e17930e769e02bcb4f44ddf3d040ffa0b081f25901cc125a37a58a6a5d13e7b0ba493802ccdaa054e29000000000000000000000000000000000a65fec6ad29ec3eee9ddc7ded2297f49d03ff18a255f1e6d29d2a67c20713f319d79d513af0c58ae3cddfd1f6240ff50000000000000000000000000000000019d5f00d9e2b271f4e9ac779a096386f08ae124f77fb8183405d48ea7f16e685805442dc67a392aefc643ea95b4f1fcfd997516cac28a3968ac6946b5bffaace0856a52e38fdcca11ddfa16cf5a568f50000000000000000000000000000000018230bf1a873aa04855af1426da30f1b3ef4b64eec613b9f660222e3827b325c318baea031b463c7e9f775165d22ec8f00000000000000000000000000000000017faafa1294fac53e1de8cae9601acc62d76a5f01a39ce49d65f3f5d2cd5cca33eb90bb4116b3ea36f912ae2b81b6cf000000000000000000000000000000000fc3ef5ea59849a87fcd45500989f1744cb5570ee88e34a952cec32cea2eb5900b64d8d0d04ef5c51e8fdcccd46412490000000000000000000000000000000001c53aa8aaae8422fa4fddc86cacdefa89c37592c8e67e472a23627514623a90901a619af79e93561a0dc65215837274e881ec65fdc2f58e46d3ee45a06d0c5ac844ee5b62872c7ba21f6b48621a3371000000000000000000000000000000000e3db6885c2db9244548e11b8c49b73f85e4104b413f54308497262fdff1957495859830114528a22c45d39a554ba82700000000000000000000000000000000181b1bfe2d9a1c563e73356d73f4ed3e7061a79c610bc97c911ab1a0213d123c9f83ed6706e862087a796ce14c5cf53d0000000000000000000000000000000013f5fdceddce771588869b945bd6025e5ce485fe78a362356720b474b83998f27e535cfd8d33ee51cfc68e5d514f915c0000000000000000000000000000000007e8fd7ba457a3cefd50c641847425cf2262deb1d6945a0bd740eadf38dcaa616edc48c3912508d663349f089b8b56fadcd9b95e49473277a665ca0f9a8309df9ed6ee4f25d803aa967fb8f688273e650000000000000000000000000000000004b20b0408da7b704694b47607928a655077015f2174fe01bac9a0b3a61dae087b0b593f58d2947d8d84f75bbfb327c900000000000000000000000000000000106d623b2007c5d7128e03e540325ba763e992a651e2e5c78936f82ee2ff72d89a1a914345486cd0a04440c75beb190b000000000000000000000000000000001847348e5ef429cfdf1ba4d265d8c5ebcbec3d5dd4611ba36e2754fbd3d327273bf2eb7b7ba4b3888d059dc87f034739000000000000000000000000000000000bcb0a9dfe5189bc965e9721407b4cb3ed4171510aa4d4e5d5f0823a1c2827643e1278f9c0ee960c54ef8f6c208eee7b334582482a9038ab906880e43a4a9d39e73b6c63604eba0c8f6399eb5c288638,000000000000000000000000000000001205b70b29ee04212589f8a70a71e004f517d3354e714c1b4fe42cf93faf1a8ed40dbc1b5089ddb53bb052c9cb74c0e8000000000000000000000000000000000f619082734dd9de653b61cf2fb927199f228637db70797bd2a21fdd48b6ecd4c4f712097037534024880a436fdd63680000000000000000000000000000000000592eca560be6ae256abe1796f7ec394a8085c127437f6590c8d41501a482c61456392cb320b9e801044dcba7802df9000000000000000000000000000000000a6d20b8009708ca01a274aed6dece732c5eed5aae5e4c2f3793b5fa1f8cb8c95037ce387bda2e7476e9c493507c7fbc,293920, -000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d93884d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf494c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e8964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b890000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e2787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c94400000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f300000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46eaaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d10000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12dac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5bbb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4000000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb81596fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f818767200000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c000000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e977955054196b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b660000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a93b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b38173173459836dd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b7010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9300000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd394c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a65905400000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ecf0000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bdb3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746,,,invalid input parameters, invalid input length for G1 multiplication -0000000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d93884d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf494c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e8964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b890000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e2787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c94400000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f300000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46eaaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d10000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12dac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5bbb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4000000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb81596fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f818767200000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c000000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e977955054196b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b660000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a93b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b38173173459836dd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b7010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9300000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd394c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a65905400000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ecf0000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bdb3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746,,,invalid input parameters, invalid input length for G1 multiplication -,,,invalid input parameters, Invalid number of pairs -00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220f0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d93884d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf494c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e8964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b890000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e2787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c94400000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f300000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46eaaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d10000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12dac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5bbb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4000000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb81596fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f818767200000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c000000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e977955054196b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b660000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a93b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b38173173459836dd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b7010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9300000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd394c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a65905400000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ecf0000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bdb3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746,,,invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 210 -00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d93884d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf494c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e8964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b890000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e2787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c94400000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f300000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46eaaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d10000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12dac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5bbb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4000000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb81596fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f818767200000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c000000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e977955054196b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b660000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a93b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b38173173459836dd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b7010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9300000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd394c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a65905400000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ed00000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bdb3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746,,,invalid input parameters, Point is not on curve, file src/public_interface/eip2537/mod.rs, line 210 +00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d93884d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf494c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e8964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b890000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e2787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c94400000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f300000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46eaaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d10000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12dac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5bbb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4000000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb81596fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f818767200000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c000000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e977955054196b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b660000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a93b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b38173173459836dd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b7010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9300000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd394c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a65905400000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ecf0000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bdb3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746,00000000000000000000000000000000083ad744b34f6393bc983222b004657494232c5d9fbc978d76e2377a28a34c4528da5d91cbc0977dc953397a6d21eca20000000000000000000000000000000015aec6526e151cf5b8403353517dfb9a162087a698b71f32b266d3c5c936a83975d5567c25b3a5994042ec1379c8e526000000000000000000000000000000000e3647185d1a20efad19f975729908840dc33909a583600f7915025f906aef9c022fd34e618170b11178aaa824ae36b300000000000000000000000000000000159576d1d53f6cd12c39d651697e11798321f17cd287118d7ebeabf68281bc03109ee103ee8ef2ef93c71dd1dcbaf1e0,240480, +00000000000000000000000000000000000eb3c91515d4a41209a73564741a8ccf901a624df9db22e195a5d02d24b7bc0a12756b15b8d006cb991a7e088eaef1000000000000000000000000000000000704ce8afc808b0161f6f61b22d990d713ae398779e6e74e9b5771daf006ce0bba3a8088edf75156f0e48b92ee8409b00000000000000000000000000000000018fe81e05aff0620f4bdbe4a715e015650497afab62921eba0ab86b649e5a2fd3d54041868928519f537e36448688a0d00000000000000000000000000000000162bd97161201ea3c26f8dd1204a9c6b61b762bdf573cb5d20b6b255f30208ca7d96aa47b46fb8c6bf0922075f1c1ca807f80a5e502f63375d672379584e11e41d58d2ed58f3e5c3f67d9ea1138493cf00000000000000000000000000000000135aee0e30fbcad798738c10d4aebcdf50c89ce516325f655fe763dce54ffedf94dd74168611e5ae879b5bf5598d62dc000000000000000000000000000000000c728e672cd8b3bf9341bca929c34118b566cd3a80452d7015bee9d5cdc001b1f5c678d4b2cc4f7cac353e7bf326ca1e0000000000000000000000000000000014809aa22e2051e463fba6d49fbb060d0c7f599a0fc5409d34e71f34817e7beb1251810ae6eee1848c60796fb8647dea00000000000000000000000000000000145a4de777d86025d50e12f9a6615ecb9bdd41489992d1b643dd9aa549acbc63b04b0bdfd14b6e45c70f165e9a8c91bebb169138f94093d5c1c6b253cc001ce8baf78858dae053173fa812d2d1c800da00000000000000000000000000000000009a58b7116dbd6f550f8ca98071813130ecaa9ea86d5275eebc36860690fa048c9ebeb46600b2b63e847bff3e38ed0d00000000000000000000000000000000113ffc0932c041e0e34b2540c485eb74f5029b339cb60bc88a8a749310f33f330dea137e5f340044fd689264af66696d0000000000000000000000000000000002642da3c2c7b6688aba0b19ab29ac72e35caafa044863c364ea8833fca850289de52c0963bc33d7bba40cb5f568718a000000000000000000000000000000000552d35ca054da2f148c119454f6760607b351f2441921a2be17da2cc10902d71571c5554f132e60df79679428fa07e3e40608bdaf3e7764358a64a920cbb33ab4d571c7b3092e1ae11d9697f82ed8330000000000000000000000000000000018fbbcba3d4b1e548ceaec4a48db62a2420ff29a67af332ee7ea3f902f84e6c375fd33abc33d945c5bca25603979f9a400000000000000000000000000000000072ff416994364bdc6535f36c82212afa822cd94fade69f11eb38dbdcd37c7e22af55fe05e6a826dad822073656eaac10000000000000000000000000000000017bba179b847278a4878b6faeaab3b1f4bd7540d22817cd9aff95557497f8b9d286657b6162c0f89f7820becc637dd550000000000000000000000000000000018e2bfed71aa9b11fefca2f0db8bd9b8c69540267de50bec4fc90a6e9741891465c9761d19282e1100b3707eeb598b31d411519f2a33b07f65e7d721950e0f0d5161c71a402810e46817627a17c56c0f0000000000000000000000000000000019efd37727dfaedf697fcda7a59847dbda8ca7cdc92f34e68691d682e20ae6545ac104d6660fdb8f64a051e69298eae8000000000000000000000000000000001225ace0fdce456dd888c9672503b68ef77b2d11caf1265a767a6ea14911e3ca03fc153f18dfe9d95e0cc68b7b8a3a8d0000000000000000000000000000000008a6b059c1c4da046cc0b1b5d7f33270aceffa607daf6d0d078c06f940604e1a0b4adf01a4091306e3c7eddcf3d95101000000000000000000000000000000000f79bae5260a2f114ffbb9273f3049d3ebb002500a57ee0a7d157d86957f43f87a2e026fb9892dacaadca5ee04fc8e176bb3f9e512311699f110a5e6ae57e0a7d2caaa8f94e41ca71e4af069a93d08cc0000000000000000000000000000000016d2b73eeceee17d3bff3aacac9df9ac1c4248d9ea7d6a503a757f7bb22fa6970bb6f5cb5ec154785f7252e1508b382e00000000000000000000000000000000081edc68bbd8db7b10be06ee23d090bd54f9ca07ef24dfed7df7bb05f8cc26e6889dbd40ea203fd5cca5cb588199f9e40000000000000000000000000000000010d3478508619ea9493b4330e2fb9150024cd32dc1378f824788a884a4a30fbf39c630f465557bf0c6d69b4cbecf89f9000000000000000000000000000000000f20c9b134db5d8b7756800c031bf5962fc560ba95d4bd9157b16179f1a37ae08696a2be455ad8d018aead6adcc69b712a0c988d97e86dccaeb8bd4e27f9e30fad5d5742202cdde17d800642db633c520000000000000000000000000000000003dce67181d23af9729e9fb0653d7f79c890fba27de42fada93123e112c4a468fa889921192db8047d86e4db77c60266000000000000000000000000000000000869a1e39d42d9bb0cc0568fdad16abbdac3194af893ebd8dd8f8c2c3c855abefa5fc215412168acadc88e658e83f5570000000000000000000000000000000001ef139a75194f3c4b1378c2b66dd304d179460bac0a289405cd8faa3ff66a7b6e54eb7b8742a68150b1e098630135c40000000000000000000000000000000003892b5a645af916be2c6c7fc0bb08fb5f39341d3c68598940554e1be11e1be75af920db0c8710ed13c78edbf683f17d0b299c14892e0519b0accfa17e1a758c8aae54794fb61549f1396395c967e1b1000000000000000000000000000000000264dd4b477f5db65edad28c7153ed919a863c5c5661e0125c5429b323e055fd69c33142dfc6ed9c87082e2be4675e1f00000000000000000000000000000000046ea088a2ec94d3a1f1f97949f1ebc49690c453d316cc46534fa253b34b30323b6071d147d64bb94e02fb4db07bb0c400000000000000000000000000000000013692a33bb1348486eec40a9e93a4ea3810c7b4d3188cd07e235a2c898aa87ee0d17682fd24f4d978f9fb028fd26e2900000000000000000000000000000000115f8b64c00cd5cd344a7b5edc0ef0bb85a3e8f0f9dfb28f8ffe12db3e0d222c2d45dcdba0fbdc161c5d558bc71aa0977064d43d6802ad4c3794705065f870263fef19b81604839c9dea8648388094e900000000000000000000000000000000014c83d58d90db4821a0411fab45f83fbc05f7d0d7a67ce75da3ae568978d15f4c1886c6fa6086675c0045efb30d818400000000000000000000000000000000001e68691123451f4c3df6dae62c6a63855ec3597aae33a8a10ee274e902e9aab1460cc9c79726312df0ee0ce90c8d3c00000000000000000000000000000000018a39eb3e3c6c7fb8ee304e55d15e209afe2fe278dda93552a7b9f51fbd778da1502eb6775cbc3f832f8320fa0686240000000000000000000000000000000017c15910fad1ca5749aa82a5a2fa98b0ebb37e92912547fb1741f18c34e0d5fc3a307b928636c25f0320d71cb9d31062686285a0e22f177fe3adbfc435e9c1786752dcf3c11b723539789b0cdeb0647b000000000000000000000000000000000fa96d9fe01c18732e8d6454df9bb1f482c4b9add837ce9c354c72d49c2d44ec694674aaf0e6d6a095cab7ebb57ccd9a0000000000000000000000000000000001f8ffe3fb7e9e311e0f6949c07c26a0febb181e37b2268bb5e125fc3a100323740d1ebaa5e635dba3770fdc2ce4ee860000000000000000000000000000000012ac42095fdb677720ab3f14bf0afc55c95b43d28d922a5f8cb0bd841306b978751d24546e3a6474976961d0768f29e9000000000000000000000000000000000baf9804d99039c9fe966a696c64bdacc9673b0906b4deab108d34fbbaa3b0905d50892278570564017b96828c7e1ac93176b6724cf984632daf95c869d56838ab2baef94be3a4bd15df2dd8e49a90a60000000000000000000000000000000014ce6d88a7c5c782562aa101550f1af487296adebd9dae8252698ba04fbd58b92e2216de6ffd474d5992f97d9f22800d000000000000000000000000000000000ce92a04f5c8a99ca0e93992448222519fc454bda5d1d8638a7bfde968386e4ba0dcd1da59cd81d4c4dca3e584be0275000000000000000000000000000000000cb570796f5c8f7b8aa02e76cb8e870d3365fe4dce5df07ec286a0a821f922b4003d5b69c0f1588206d9544013e268c400000000000000000000000000000000098056a033d9cdae86aac02de3a444471854b909680719154b44d4f55f30087294e39e57643c692d6da725b859239080d76db3dcb659eaf6c086be6b414a494dea4bd30aef8450ae639f473148c05b36000000000000000000000000000000001214aacb0a5e6b7a40369a83c07fa8cf1786ce7cbde2b5a501d9c1292532df7822d4fde10a31fc0cecce3a7cfe3311850000000000000000000000000000000004f9669d8fe4f884ae93b2505710e6e45b19b7aa5df8cdd811f09e547efc27d21024cba05e2dc9d057055f30ec72d9df000000000000000000000000000000000a852b821b31cd27eca19712a636aa05ef2cd82c36ac1c2ca240edc7d0172b42a72c42d3cba583a5b5129ac1c9486e270000000000000000000000000000000007bd8419e791a5cea04993509e91a980d3ae4987a5b322400b6e4a4f2b636891a1c7ba4de96b53426dd556532403d5a39915646de2449b3cb78d142b6018f3da7a16769722ec2c7185aedafe2699a8bc0000000000000000000000000000000005ef88bf38b2f998dec7302cde829076e6cf69df23aa0bf6bbb39fc0d3d8b5eafba74efb928b1de0eeb3d86ec82612300000000000000000000000000000000011f47e9583997b19c36616e4bf78d6ddd6d67937f493986250ff02aef6e6e7ff074559af2f20a5bf1d67158e4a199cdb000000000000000000000000000000000007777c8eb259a836e6459b7bdb642f878d869fdcb31b105d01f280938ef5377f2775874c099dcd394abe70f17d595b000000000000000000000000000000001607379d1cd34e2d0ed765a339b21433e9aa489609b92414c6b5a05d796085269c288d739717def9db3502e0550860165061073223f066e35242772385c67aaefb3f7ea7df244d73369db1ea0b208792000000000000000000000000000000000d6e3068c082b68312141aa68f1540ea1415e93e7f1762b6f06ff408a9995542da1c727a13355c19f8f418a44de1a95d000000000000000000000000000000000dcfcf2ab12b1a0e521ab402aaa4d32ff649a5a97892eb6ad98487c3c73c35601c313b8130ad12e9098d16eed3bcc2e00000000000000000000000000000000013777b1eefa4af03dc44e4e054eb7a3a980a9c55644900b80346be84b970e1754d1f4ab771adc9249e4accf88a23fb400000000000000000000000000000000002f53b231f1209c6f8b52f99a78bc2147c951ac89b341495f4a60a6572985ce2bc823625099ec214bc9ceedb2deea3fff396ee22209271ea0bda10fb5e2584e7536e8bb1d00a0dd7b852b0aa653cd86c00000000000000000000000000000000161c595d151a765c7dee03c9210414cdffab84b9078b4b98f9df09be5ec299b8f6322c692214f00ede97958f235c352b00000000000000000000000000000000106883e0937cb869e579b513bde8f61020fcf26be38f8b98eae3885cedec2e028970415fc653cf10e64727b7f6232e06000000000000000000000000000000000f351a82b733af31af453904874b7ca6252957a1ab51ec7f7b6fff85bbf3331f870a7e72a81594a9930859237e7a154d0000000000000000000000000000000012fcf20d1750901f2cfed64fd362f010ee64fafe9ddab406cc352b65829b929881a50514d53247d1cca7d6995d0bc9b2f0d3d4cf46265fc0f69e093181f8b02114e492485696c671b648450c4fcd97aa000000000000000000000000000000000047f92d6306bed1cb840f58fd57b5b71a5df7f86dbfa55a36636cb495e08715cd57f2f3e7cd99a1efc28b1d684de1cb0000000000000000000000000000000000f4eb02d687a1a6105b4dbd740e2c7924689d558e6cbfee768dd303cc8dd0fd887f5eec24b54feccf00f473ca3f54ad000000000000000000000000000000000edad68c4d536912816cf6ef039c3dd0535dc52189583270b3b038e2c67b213d943bf384ce69c4a9dc526d7ef309f25a0000000000000000000000000000000006ff4a6b5129ef026d1d5704bf7fc0b474de92b5cf39722f165e73f4e7612d6d3bb40743e4b7b42d0dad5d5d6a2d4881915b717562844d59623bc582f1a95fc678cf0d39af32560c6c06e3a74023c89c,000000000000000000000000000000000153da66acafe91b6f13cd739ed3342197310e4824e7aef2e3414654c2678b8d09b296c3f928f3cc489893420031ab800000000000000000000000000000000010f501a96b86343a7c8d8c1250577cc9be6ffec81b5175ed07bd14988c5bbf7f2f3e7111df7d941d0cd267ea191d6ac70000000000000000000000000000000015e0d88894f7f83aacb6710f6c03ae60db8844dd3beec160fdb1df746b1f38a5e23def0893a0b39bee47c97af6535fcb000000000000000000000000000000000bcc275115e87f2f88c4afe8bf4faed46e6ad0c0357884356a26120591ba283f06b464c4853217865b1d2301965f2bd4,240480, +0000000000000000000000000000000017b32e613cb38b41dcdf3c8bb9187d731546977fbffd79fa7f66e3d6aaf9e1af6eca2fcdc260c8f90818d7148ba2f4960000000000000000000000000000000007e4d26606a47c874c20e8480a9f5815e5b577bccd783b775d10309eeb3d2102c7a0abc3324679e44362f09e7a4ada67000000000000000000000000000000000cb6f12ac8b49cfa36b957591293c87b21af0a949c55a28a90ab0fce88fb5cb7645e20ab2edd284f0ad1377dd95ac10e0000000000000000000000000000000014c96b5dcbd3150eeaea5c2bc27750cf88b30a91933a3233a4d1d9b357a80cc20d135e43a344e718dff5c79045c31f86d5c1c9fa11c36b86430cbb1f3ec10ebbe3787d0f5641d6d7fb96c810eda202dd0000000000000000000000000000000001ca1141ba9542c56de8991b313c6ae42fcecb6751b0b81b8cb21ed70d5008f7ffe831766b89880a7fa6dfdb09a2cda3000000000000000000000000000000000e6766b17db165bba564ac63ab88d3f8f5eded07a40b48644e60d3223d30458e7dabe404cab8d6f9fe135712ef0b1a43000000000000000000000000000000000dda3e6c87382fa762510e5cac721fd2b654f002f5b9a3767a8c6d651ccc582e80e3f68d6913cda30f9f51ebcfc7c98600000000000000000000000000000000059a7dac5bb6b504f2bd603d486700fe22c14f25254537b2c9079c2b45d36c7ce56854c5699cc7649b533194f51a9045c00eb20fe7c292f3ad820a074d8b3d8d24506612752d8677c2d6ca24f556cc4500000000000000000000000000000000090f4b85961ce97cf7f99c342d3627105d790f611e19721a43d8a0febd67ae393d77a02b999108efb56f0397dac22703000000000000000000000000000000001112f23595d1613c47486eadc37f9b1ac3b3c3973b3fe964d3b67c3996fe2eacd9df5c287b0cea8e9475d146fabcf9e70000000000000000000000000000000018f46f7ba3c9af34c1025c2d460f0be966e68944928dbd55cc7fe00e5def598d80b0e3801e48a74963c974ab4727a52100000000000000000000000000000000096845338d5cd2ac44e097607d6a1a05c241eda1941991ae9edbba965d9029032c46da7218b5b2338e6c58898bc4a820f661d7b30fb11bef70e15b257d7073885468a380862202b2d705a84827644b5b000000000000000000000000000000000aafe45ea7cb8b450a51263eebc28c1ded662972bee512e24fddaf64f43b74b66032523b3b104a4e9f6b62394436c6710000000000000000000000000000000015cb27e1fedfba2d1679f78a388f90b22bbf3e7d090f0ba972fa8e72f6e31c446f628fff929953712ef6e425d16eba5c000000000000000000000000000000000df9931893cae713042bf722db6ce394b6f346587278a154c271d8511e690417eb6dc47efbcebb7c2fb9e77f1de9fde800000000000000000000000000000000106ffa395ef170c99bb5742428ae88fa4fd7a94476985c099e3b700b7403d083281fb71a19640c6bc2321e27bcb33fe2346ce87c847376c8967cc18297e6007dcfacb6424e1d273930f38bb0e88fc5ca0000000000000000000000000000000010b1f8b1c492a56936da905b8738affba6bd29ae5fffd40ba6b31325181d3b489a81b23dcb69f6e71bd29bfb388e5a8f00000000000000000000000000000000116a115303b4774da59844e457844232d088062d920db67b2a8450a194be7e5340ebd4d106454fd9a03c8f50dbb1e119000000000000000000000000000000000eb521edd61b38006cffc43ab72d395d669dec196846fa4d6d43521da6c2fc3bf0994ce7556a3cffec7751b3bc5703ff00000000000000000000000000000000073cea36eccaa1c78deefb6029903c2b6598301bdefa9759719c3b590fcc5a6a4d3d4d19f552b33f4a3126a6e6a8448639a142c443a666499a880aa1cb9f523411bbc8e5554de099ab485b6c2c2e57cc000000000000000000000000000000000e3925fa085db73c1e67b29ae90f8773f83be5ec684402e8e2360ffee8a8368911e584843e42b0d470de78591df6ea6300000000000000000000000000000000075c7efdeeb16609b4a47ea442af4d75238fb7534fd96cb236a7886809d6adc2b62c8ff72bdb041bc51c1a71b68219e300000000000000000000000000000000088b4eb0dd185e51b737d797334590e982b7b0a5f109fc7d0524b2465c2c0457964eba5a6d2d4d99fb628f21f15a776c000000000000000000000000000000000fc79f6b38f3356972669290eeadcd992a22bc1191606b663a1e148aa58db3938f0fc65e536bc5811c50d9c7f03d3e372c01b7795c2d16b5bbbb1e107be36cc91b25130888956b0cdd344de9b4659447000000000000000000000000000000000b87c47605fc060a8e3677e84ce9d14b9309360a13c80d040c625fbf0108f829300cc1fca409a0f9c96311cd4a9a21e60000000000000000000000000000000014c4088f1e7935cf6a1d2475b84497ce6a250ee2c0c991fe51a2f2836388a354824b02d9cf215328dfce3f546713e21100000000000000000000000000000000120e59be3ecf35674eac6cdc559599b273f13f28a529770fa156f8e519734c451eefb35023639f32049cd19ea0d945a3000000000000000000000000000000000f97755b62a8cb8f861ea02c77819f0b58181aecf612d92180ba9b475f0b4888b922c57f6a1c619dd5514620a1cfd9e2c712943d8795a6104f024b9701c70b09cdee9494755bbab0576e2c7f7c9d48280000000000000000000000000000000005860cfb6be6720118623d2d8ba05e686df22744b948421dd3cc1b1691e00d9b5d00d00195b4acf7a7b043f764f3f1c70000000000000000000000000000000012632a3313dd611e8d969bddd556c2d79ff387603462ac78ded3a842981697bdac34ee6f1f4744ed2ff16100874ac24000000000000000000000000000000000112b94c317586e343acadeca611c485c3ea172bc10dd39158c1e678007130062a921b53826d7be6286963ff822f1066c00000000000000000000000000000000040de8c0dadd2a6c2a7ea0fa43e1a5f2f5a6be3fcb0de6875d8cef1ee2daad87125d12f6869c4dd3d931b296f1df2fb3d4d77f6246c57d398c57848db8d3f986c475a41a23d424cd3cc2b362c1b99f2a0000000000000000000000000000000006fcd2c4fe848e9462ba1112baad39031c210952adbdd06293a622ffe2d1c6e4fcc8773ec8913717018b97bcb9a554fd00000000000000000000000000000000130a97442f3273b7b35464545e7351faf71ead9b8996c63889a45945ed82bba29bff5014776c6185219a5234d8475c92000000000000000000000000000000000491d571bac5487b866022a0714be11b38bfb296233845cc434a50be1d35f516b8c6b046fe3d0a8f4f95ac20eddea01b0000000000000000000000000000000017e34b04e6fdf152c848f2432b7bd84b3dba3915f06eb77efb8035750aca9d89e92e1d1bc4871105c440d639e8d8b05541776ed9d1029918af4c5113a6110139b8bd7f938caa204373a28ddaa51430eb000000000000000000000000000000000f1b8df4e8fdfe32eaf227f5af9f2befc85073468f10b81d32d0e126fe2b0cc8e8adb8afcac73213b6ed95e8e843b97c00000000000000000000000000000000004e3fb435ae0fb2d8bd091f250aefe5922b353a64e16abd75627737f3bc56639f8b40652cae69c73ff1969925b0afdf000000000000000000000000000000001003aed7cfb00efce49d6b1a8eba27df87479a4d37bd7fda6121549483b669a1a761204b0dd28262bf27e5c8e180540f00000000000000000000000000000000114fbca7caf782b3296d0b26b4c362bf50acaecb8bc5726b2c99f904ec3d092d5d40991d0d30c8e79fddaa45f04a75d3fa64411438542922a7bac10806efaa633d31d37c0b223314a8b6221155b9c4250000000000000000000000000000000017faf481fd4cb0c373d21d7caad40e93d9a86e62d26136892fbcc6f6e48205543aff00c45e82fdd1d3e0e733de91e7000000000000000000000000000000000012e14fcb9ad4d9d15347cf004745ed4bd92097eeeb41c4cbcb728a234616363589d8f5ad4cbb61d31a8aa27627723c7e000000000000000000000000000000001513dad1ff27e053902e779e35d04cab648939317830144ea775c435a4b55e13fa2fef03a1256abf5c187487c25a774f00000000000000000000000000000000139da29de8587c7d0ca9237c37a116387385e9cea453b9e2003a37ede7aa0a3f4c1df55255897f5975b662be33622dbce7002f41c6acab677a0ad023bad2a61b11c1b7221d944018b5ce60bb61e87e96000000000000000000000000000000000c118b147ee3489f30c6ecc0256a314ab674110588e8b69ca6d265fc270c3e5b767817f861140cca5d7c6be4012d1ffe0000000000000000000000000000000014800790654726959fd876b035bade0da744fb36ee5b304f228663a531345120267c55ac19fd66022752010e5bea7cb30000000000000000000000000000000000193ab7ac2f151750356b6e178557460c9c2672b1736d19a20e3fa28082479ca60021aa68edf2524f1aa826ee70b65a0000000000000000000000000000000015cee9ac55ab45abbc57d0ea6ec9ee49f6c59f6b94f99589dbc08ee877d3a261ad77f5473fedd72ed7206647eeafb6eac26e55f09b787c0542878e4d720027d9ea465f829a4e0164cf618c5d9cde49bc000000000000000000000000000000000ef203fab794a0ef29eb2ebf00076134e5932e27c99d6d445695b9df2afe7563602e318caf5d44724a21790ca0ab0d180000000000000000000000000000000013b9b1b1d3e98b61b0f1a0ef3a1a4ceed57b6c01849a4ad66a86332b3d27022cfccadd3567e6709d2de5b23b23dba43f000000000000000000000000000000000c1fbace49684f4be32ef6178ac3a95ea3f50b11494340fb73dc5391d50bcacafb3bf0f2631fea9c4ec47327d644489500000000000000000000000000000000040f82812855aa3e3aaba826d5810c1049cf44e86e44e23cc6da437971b529d2f2676c73e1fb9da52640c981fbd710bebba67cc47e38a129ab1140fbcf0386ddba2feefc919aacdce6059a27a1e2efca00000000000000000000000000000000060d7a718dd02b147c265f71eb136d1f31781b12a41866b4f86d7374b93dd10058c192cc0fba928373b1526e1a5d7d7f000000000000000000000000000000000cf29275373c0573ef22bf87919faf5444847203c7dc6d2e18986152cc294be04a5b1a4b0536797158113a15276c4fc6000000000000000000000000000000001016d5b9d4d200d7b4b7cc3836b85d6697fe14db350badba9978c7b56983dd1a7e572640ee0372b0a4e2079ff4c1abf2000000000000000000000000000000000f2768d104d895473ddf8c6b3cd0e7c22458d0037eca6365c766879a07c95037ee0de00d32c974d767080935abbe0be1705fb566367d9fc142c4194b0525c16672b843aac1160f9056ebb115e80d377a0000000000000000000000000000000017b9ca4349fecaa43ce911c0b256680edb8a0906ef5460fc4d2004579336df1e19560fe960a7a7cd74bb6e8272e08960000000000000000000000000000000000d5b96dae738db59cc67a51c61bec6deaeefaaa51e3259243fa4b142ef59676231229ae386ce699fbe18c4c00bf9d49400000000000000000000000000000000111b79f4b68dad16550a13334d09dc38336a75a5da23a17b5064e2d591aa3dab4c2e982a9f730a7633070504663a24610000000000000000000000000000000018f6d3616a7eaf17c805a88c9710039644d01b61aefebf76717ddcda6f4bb34aa15702de1e92bdb27b27f3409638da90f7bfd990cc4dac62a0d730f56b4eb1c1ad77ca9cd58b089c23c2f6efa00b7fa4000000000000000000000000000000000aeb5c087644595d0912879f61959d2731ff55260c682ed2bc5fc55c13964ef7c1f70aeb55876d2264d558c31371ca69000000000000000000000000000000000e173848f4570525b03a2b2c86f4dcdb8b28dd6d18c1354cad31028eb1b8b44432c2346edaace093e3954c7fa6d338a4000000000000000000000000000000001949b0902506d111ef6318edcd7a58ca4d69f5804a028aee73c3786cb2db168c6a73b77194f7a021ae6ae43ac78ade340000000000000000000000000000000017c5e28ba6103d97e2f3d3611c0c78f06406e0da8a49ae29c7d460b52f75136920784cd500aa3593858b877697eb8424807c5a41ae2baa1e10ebee15363d1d4569f731d77a418998108f5dfae0e90556,0000000000000000000000000000000013b49054c3957d1e77ba2dc3ef75775bab9f0e9f76b33ff22e244e897b8ab80ee0749c81eceea259e99b5d2a72251e5f0000000000000000000000000000000012e017e4354ef86f73ec51921cbfdd01e3113cff044a049bdd34e36401712420790cf718bd28afa280ad12104c1851ed00000000000000000000000000000000097f28bee5d903e3c6de14e834d5beea5c847c3106742978e586ba7e913f8b631a69c473aa10e19df9795ebfa3ea6a98000000000000000000000000000000001953493daf65b974b549bb98e735da44b543d6fcfd97176fdc7f6f03617d90e6bb952a607fa8e5791df5dc1c9bba2286,240480, +000000000000000000000000000000000d4f09acd5f362e0a516d4c13c5e2f504d9bd49fdfb6d8b7a7ab35a02c391c8112b03270d5d9eefe9b659dd27601d18f000000000000000000000000000000000fd489cb75945f3b5ebb1c0e326d59602934c8f78fe9294a8877e7aeb95de5addde0cb7ab53674df8b2cfbb036b30b9900000000000000000000000000000000055dbc4eca768714e098bbe9c71cf54b40f51c26e95808ee79225a87fb6fa1415178db47f02d856fea56a752d185f86b000000000000000000000000000000001239b7640f416eb6e921fe47f7501d504fadc190d9cf4e89ae2b717276739a2f4ee9f637c35e23c480df029fd8d247c7a7e300bcb3c740fd1f693d4c8915c4c46dcb627f6de6e4847f123623cd23bac7000000000000000000000000000000000f20a07526a082e88630a0256d134a8a5e8ada07b1cead39ee838dcbb30904e9016107fcbdf1f8ba182308dbe0b043d20000000000000000000000000000000014fb7732f67abf60c03ac902577532d0acadb5f3db0d6397a42ba693526ad74f2c61a0195bdc9704aaaf12e65aa6d88b000000000000000000000000000000000018cec4fb81c85d304588d11f8b9c51f5a053df11463e5812a1b2e6c7144522ba36bb91adf219892d0007cee470032e000000000000000000000000000000000b8e52d958a12a9037e8be9bc0d5045cade2d6ea05c6e68462b3a30b5d4ea34e5fbad173761e4e216b2e6958c8983b28b473df5e282565a0783d23e65e283a103ebbddb5c884183cceb62fc32d0e9602000000000000000000000000000000001468cb35a60898ed129f30c261b8431df6a154c250ec16d85a22f8717593b2c21853d123da86d977a7938c5ed74ef23500000000000000000000000000000000011f4e28e31b5f9e6877192a5e632d8c1ed7ca0c42e6e9902ca68f1c2de0f648c6064436012c5c7b14bb8d1078e02f2c000000000000000000000000000000000b25114b2697ca7eb1e6effdd1054893a188fd382d387ec098f846c1137a9b9baad01653b963a0b0bf3cb50c3ce3563d000000000000000000000000000000000c1d241cb03e642c1752b1e1886472477c19a2801ec032dc220c3243952f882094119bb92b621b654b766bc900d2d4f7a048ef7cf5d1f6f625ee3aba091147c389ebebc5b8f3d285e16ef4e8afe5c013000000000000000000000000000000000c80d4474390fa791ea5f2f16b41506d8ae13ee0993c8d31a07712687298ee7978a724999500c42400d2f788a5a36067000000000000000000000000000000000592705cc5a8875750a4e6ceb42aa3bef5593eda9e8212702a2e08ea70277a2a66526bc5237be33c8449301544da35e60000000000000000000000000000000000facabfbd15284c6433f17b0e6035d4fdd84d3ad2dd30a27d52809652ff6e7a684d7724697919100567ad0c3e1a26320000000000000000000000000000000006a0fc4e2af69ce15a356656f5d182a2cf213d76a6047a05a1a3375909d245f5316b91333d2141c0817438f0d87bb52da9b63c6bf36997118d58600c1e429c105a379b9e8b0de934ab9f433a4fa63dc80000000000000000000000000000000003f629618e1fc3018bb836301ccdc59022f0a25cc9c5de6e4c31fa08feea525c83256235e4ec8364e77e5df478f5f62c000000000000000000000000000000001120d6af221ba6f4351bbee4c2c664a769adb17872646df2c408f70c99ea991ffced4eab50fa98be1bb9426915f125930000000000000000000000000000000015cd16b028ce3d58b10aeb84b783475d894ab3f0cfdf7104ebb4f3417a038107128f07518dce548271061cb8c97e88af0000000000000000000000000000000018379875b68bc26107f9a068e5034f29dc2ae7e8830f8e9ecddc53fe7991206646cda33d37b31a47a977b46be58d7618f228da17f49667c113d2bc2a2c8a338f80be68496f5145b4be21a5786ca6d46b00000000000000000000000000000000036570783711b381830e35878fbeb187b84884a9a0e88c38e84124515b470e6ac18157e1499026b27f4f731a961eaf330000000000000000000000000000000008382838c18d56c046a8db495babf8d14c915622d7917ebe10cf7da7ecb65f174cddb9e70d0262ada961b396c5511b410000000000000000000000000000000015f63ce982aa581dad5c71fc79251b7f6336c4e78a4a0f4cb6f87167cabd31cbec987d7af4f11dc6d693a0b0774864130000000000000000000000000000000015c001372fe0530a3f50fb8b30e75ff4b264d673e0448211d082c7a9018f583b4d01790019874596c59c68768cfa3e699431e18a462fba704216b516e819fb3392e315b0c92a7411a329cdafeb51124400000000000000000000000000000000074d78cdd35ea17a3013e2301fe9f80f2d20d270a25fdead37eed7697a52d152612543781763e6035fa5452ab12cce25000000000000000000000000000000000e572236e1c203a1c0f99e6ec978458c1a143a6a650eee27cfbe406bb2858fe5f30222f468d119703c2f442bc644ff3000000000000000000000000000000000125384343fe132e16a9fc15efe1b3a9e47289e0afc4b44d492e33a6216edbc96d66c1ca66944a8296e7695f27f414c5b00000000000000000000000000000000084c2cbf0d7c932c3098ded7c70d4411eed882feb0f79e0f7f1c31f5fccb6d53fb57de179c3ba5754bc5e532c3784df12051041bd2f12f6e6e29924139770fe209b7bbdbcd6c0bcabbf5021a7dff2d830000000000000000000000000000000004d46066439c3ac559cce863c58316883651023990180470d2efd06e443a7caf3a514b54f15ce6e850d32779215bcf4a0000000000000000000000000000000019ce904b6c9c3de59f7d5017f60f1978d60c564f94a0f1964c24c876d1139a7ffbeb6d0d4884bbfaf5f2f189af6904a50000000000000000000000000000000015f1989719e69be95f25dda9358fb98aae2819e0deb7e2d291e2c01e85ba26a9da421896c6b6e2ed20f609b533154694000000000000000000000000000000000b287cfcf1dd7c6d735c1358dff15393ddd6c82e7a33c5d8005c4234cdf823c76a4725fd74cad74b3ec51df67f09af0fb96df57a600dc3b5aabff5b1034886d24f6fcf035bcacaaec738deb2cfb8f85200000000000000000000000000000000006b37e2226957d639fcb0bcd6c20b3c7b8372e7347a14b970e01c67c1859fa97c754ce588d0f835ecc053549d963ab4000000000000000000000000000000000c6a5fae8be3a32e3f70a4202a1ab6d97183964b9f7b9a084c49922cd9e0e952b0bb66c5580f0e0c417e079493bcdb4e0000000000000000000000000000000017b6132f11adc0d5d693ae7f3a0f89f5779708083eba23e03b0c9265e4e60624e1fb6940e8ee49d31618fa6389b1b50b0000000000000000000000000000000000a45c5f6df71359648aecb6434bad1619c39f10e279a02b3cc9725d0256bcd126843fc9ed29cbe02a32cbbe79774a3378176412b07eb7f423f23ffeaa0ee642590e0b7016bc063f3fffa93e1e35484c000000000000000000000000000000000ffed009c78ba9af8cd33af7b7697ae4dff863bb92365055baedd2299b7f5b5e8abb84ed434f7223c3e309ca53c08aca0000000000000000000000000000000003b2370c837dd6291818efe7c9af62dd51295c418739ecc509d42c92e2c97d12a9fa582946e176e8153fc9a273140b2f0000000000000000000000000000000001e63438e8b4a0462cfdff64a281ab4a7f48d51b51325817139f8ee683484f8695f1defc0c3efcca81d5fbff06cf9c54000000000000000000000000000000000192fc391cdc1ed6ddbd317f2f366f2ce25ba27b8c0f09c733e7bc0c0697544399a3a4f1186d139a8f6399ffa88e89a69c4b5627d84e153f3a4ecc14ddd6baaf1d62253a0f88d3af51be18d991976da000000000000000000000000000000000002e105e0eaa418d58019a849b89accf665a94ffb0bdf308a11b99b521de7af8ddb150c0e3b2e9c54cf5456b6105bc81000000000000000000000000000000000691a3b3986fbe1c0ea22329364454f37f645d6abe9310e883b9191ce512347e074e18e28b88c2adcc76190a549b80b40000000000000000000000000000000003f3a37a763c8d0d99a3fe36923843a22cb0fa18ced48493b2510fc99afe5b7699bbaa6c2ecdad8aaf72969354f121a1000000000000000000000000000000000f4bbae00205f54eb10c83d928d908fbae342b76050e33c51b6e282e02b3c1f132a4728dee4ea95455c25fdfc112f2542ed270764791aff081f1dc8051d22b8e18803a7e310393f21bb4a495a445cd450000000000000000000000000000000009a3e98fe4a98582ce9f274965f376cb45e8583775dbadf626cb1327c1f8a25b293b97e7f8f31ff72ba7e8e769ff25ef0000000000000000000000000000000018e4785ccb76c4897087c8a4242ddc744c6a0a53a4a844254153c23d6f16d4ddb945252d13f93101613f4eb0b1e2b8320000000000000000000000000000000011b81d344eac04d3471b1edde5e51f31f97bea3396580839fa094db58cf6bee371bbdc045fb60c3ee5c6cd5d3f6d3c4700000000000000000000000000000000073476bc5b1d52ff4ca89c3afc099417f473543fab6e59cf9de8a19705dc4bf2a210b1e6de4dfbde035c312be0c70c56fbfb7606b64eef0460b8f33a0be54451fb655ce0b81db89eb7862f392450354f000000000000000000000000000000000c414b95b298b9c673001173ba7e5ee3e03926f28068481cfa0b469ab556f8fceba9fd0a815180ae0b82c265fd4c6b7e00000000000000000000000000000000054a242c1cc1a9c710bc23305d09c2d613ee8eb3840b37943bfe83f9c1db456ab4436ad319fcdd8684db129d76c95320000000000000000000000000000000001683711c0c7f02e67374f190eed1ce6559479d6d199f43fb5b0ce7df7774a5cb21c86b3b3498855d9b69c5763acd8c4300000000000000000000000000000000062f87085dfec847af518bd71c078f994b090c3b27c6eaad79772ab58afa43993db52fb08649a32629d61c3db12c87318a29fcc442d0c2446697e94dc47181dca7a314f9073c06aba6dc55aa79978d7d00000000000000000000000000000000083eea9b5b2d5ac5f7ef51ca889a4317322d098a408a741827fb3419eb12a51c07c788c2798cb37635e224e99bbc894c000000000000000000000000000000001312ec00f4b3a4305700b44b3f215779a9a8bfcf5b5d3a7f237a33c5484099ec9bc5c8537fae768e2c0ec62168f383d6000000000000000000000000000000000cf1d5d05d11e1d07074dd34211d0f00eae1df4dc550c55bd2fdafaffa1ad36abd5da30c5d3a5aa2845b1d95a5cb571e0000000000000000000000000000000015223baa9f2ea4b04fdb05b05bf3a94dcabc5e64189aeee39c380de9a34fe6b4253f5795f70bbe51b80e1aec1eab7196d5b468797b4af1978983faebe59a28f34956dacf5b7f65d25548bcedb518f45a0000000000000000000000000000000011a960cf1978aa2ce1731b857fd91d2f59d4b8d7c6871ef6f4f85aeff549a2f397949d11a4793926fe7be37f3a83d11c0000000000000000000000000000000001954f056834d6e3b16043ef1acd0a47a353300257446e9a1db7e58bd0d7c4bc9ceb3db51ae01cfed9de99621e96934c0000000000000000000000000000000002e2fe460e71b65595ed93a0010e5ccd1a2c16fc4e0d345e7226c947f29720d2f3f54282f79cec086d3fb1999b9629b300000000000000000000000000000000060dd8a7ccb613f1521168a8a322aef9f84d9708a893f704f4fc9a19e2493f25620a47e0fff1bc1e212e65e92873b4f2dbc6afcdd409e5d50d7b655580f1144de77f3efe5d6268032eccab7deaaad997000000000000000000000000000000001472caba61c2f1fe4b1d0912b114c25de103ef4351668f22f3a158d7a347539a7b6656044bd490f036ca3e29dbdded370000000000000000000000000000000015f8cdf7786410b409f218164063c99e77d8f72f03882a6c9430ec725ae574547d3ea3cf30c3ad2c9c3febe6c30b1272000000000000000000000000000000000ccbbed85c2809433fbcf22d6490457dab800b21cb4de414c7dd1804a0bdeb7142f8ffbb2de921c2c9eabee6a6351026000000000000000000000000000000000a404f42c48e3ca408d3f92079b99805004da928f128206d8904ecd7fcb14121c7d9a9e7fb69accaff921315ef3d5372807347519f114e78f99617f6b147ca833bff7be962c9b1e1f32b5babe6067d7a,0000000000000000000000000000000000fada9f43b29abe15693d047adc277814cb94694cab3be56b92312ab7666649b8e9d92aad81f8e487be0f74b9ce8c250000000000000000000000000000000007f6891775811a325cd7f548011ad4c705ca0327ea0484d938ce061c913a7ee6978293c3258c4b865d5c2325816c39990000000000000000000000000000000016761f859beb90ea03aa35e954d112da02daa8e76de80297afde9c29cbfe8ef4d42dad535917685a99b2a91b1f952ae50000000000000000000000000000000012a4f24ab88341dfb8a60c19993b8abea96dbd7033d3686c40903728b4fd4da7d07961f2584b51e9e6c05976d555757e,240480, +000000000000000000000000000000000b52f05365c4df20a7290aee71a7e030615d1a2a971167884d835c24e756a0faf6ed0552341c561446c7fd3d5e887d830000000000000000000000000000000018718ef172c045cbf0bb132059754b62414097eef640a781db6ad521af5a24d78c622d9402033fa939f70aad0510a1ac0000000000000000000000000000000017e969e44b4910304b350b5d442bb6a0b71e1f226cb4603cc8b4dd48614622f3f4e1ddecb1894046649d40f261d94e030000000000000000000000000000000004dacaeb9e05b9d60ce56c17312a092cb988bff426b8a718cdff860186935507a06eddbc4a1a29e4ef88db83fc4b6e77830630695c8dabe9aded1b5365bf93770aab7e9ef4140a2bbde2f0a7b109724d0000000000000000000000000000000019829d5799eed5a081042e4646d46fb6bead6d3b9893a4240867b25ed6af6a3e154514f244466d80e3b9311e060bbd7100000000000000000000000000000000156157a654db2813cb9c1b4da0a3ee192fad076bb2767020fc5fc00e967c1a35a367ffa375703e1181b3705ace9dd28000000000000000000000000000000000093385a6a9dd0ab996df54b23f47f4a49b3f379e11bc8331016ecee6161fcddd22f6d49fbb21f098873f1e17424dedca000000000000000000000000000000000d5b5b0f2ce81e755b4030b33fe3a8bdee38c2c60ed3b4a88bffb9207cb762c0a5c699ff424c000ab080d763abc5438d184ef5eceadfd77b3a4092696ec34d0551c88e434567638623740b7d5f9e36160000000000000000000000000000000003af8c25bdbd0dc1cc344d55366f15555709a74e1f0d8d7050cb6b487759db6200401b7868fca3c2ad26e6362a30e6250000000000000000000000000000000013f8b6ffe30f9a133fafe64461d305cc6b2cf5aededf68ba396d4e00df651531c750a3d94dd77bc5c6713b939b18fa19000000000000000000000000000000000dde97855d7728f409d873b83b6879b45ace5b73f317687fbf478e594a959ce21d4d751db646ceb20432e8311e67404f000000000000000000000000000000000fea997323cf29710cf0e3d44ce682e039d6cbda155e43c94dc8cefc5e94000de4b9525123b9615b5f1019a46ef37ad3a80d9efab033e920061cee8f8d7ea6023cc05f08340642613628b39e7b7fd0af000000000000000000000000000000000cdf60e3bb018407eab162822468255bcffd54cad9127054bd1c30705a4ebf1afc7f539cca6ba4cd070b44410ec751150000000000000000000000000000000009a2e3e5993b6a7007dedbbd21737a8c0aef3ecd4607953c4a24bb3fed97ccae01ae1cec024443f300b570a66e9ac3bf0000000000000000000000000000000008a21fed19e9ec2a741ade7767b0c9f39b79c3fbe34aadc9eb3043583768d893bf927d26231759290c7dd9c4f158d5a10000000000000000000000000000000018eef4ff88d63149d2632c9db586a4af0606644b16c82fbb0a3b869f1ff924c59acc8efbfde7bc604497ff68939cdd0845111c860f6f5725f99b225c53b9fe1a70150e7ce922bfe214900aaa2790d145000000000000000000000000000000000f5d47911596c46c0c08cac5f5e7f6d0609874da4ac1bd4e0e59c393273a5fe31a756c7cfff2a01d19e79d209d7c6d3e000000000000000000000000000000001010f864eb6624132d4436d18db7f5b34727060dc426c109886be88031e3c155490cb3fb09e1fbccb7912875477c6d840000000000000000000000000000000005cfbf1c2ae1b80a8c7cfb2cefedd907b0552794f4fda101ca1a723b18de8cbce30eb54287e1847cee3f416cd8b45f2c00000000000000000000000000000000084fa63781f7eba9c7e911ae5866d485bc7e90603541c55d1ffad8b3cf7547fd57fb24b14002560e58410b828513e109c07041840216d60ff445cf53b273a46016c8ecefefb53550f8bafc79966f863a00000000000000000000000000000000124870cfa469136c638e0cbf15802f2699aacb66d7e4c2965c6759dbca4b7e47941ad9ec37a84db1afeeeaa65a7418e4000000000000000000000000000000000d4503049a6a53536bdf41dd832a6ecf3f10554887da7e389cf940394e1d88db94369b7947436546eb6c6e82c48dfb9900000000000000000000000000000000053f9a6e1f05b67cf553073358009a172e2ab8b43572a974da1f3de85a29103b13d7e67b2a359297172d27dba5c61439000000000000000000000000000000000abc29f50ddc1c113c73700b9b9796890cbf48818ba981fdab2db27ef1c58f4c2e4595b99eae397d40990ce2f6c9317c29b031b82dc8c9f4ea9524793b54207d4e13a548d73297f2aa6241aff57abfd00000000000000000000000000000000007d2aae9794b7a7de97f7146c0ee8415e09e56fd42535bce6773cadd6f7ac09c4eafe2e926cb7014377e54c703eaa9dd00000000000000000000000000000000172a4a33ccf99eb0473b2c44d30bd53159afae0c7706ad128bccf6258974d5e5761f9be43e618cdbd96027aede7fd5860000000000000000000000000000000012601bce2171c6e4c2968a3efdf1491285f9e4ab37cf973ab5c8e224ad5b40e1b6459ac89090c73deb8fc79fec7fb8e200000000000000000000000000000000112a6443116e6f98ab348e57daa3971b5fa506e40515e1611fbed3e7dd64c5c1e991e0d2539a70eb93e3da0f573d6b2263d26ae92119c7b06d83d7e2922e06559b1740eae315c6623d3e543c9bf54258000000000000000000000000000000000030372914b83644fa4db1958831e9335c72ab7a811fb337696221a3290e4c54bc10c2225f8fdc3a9f62632ba2f1594500000000000000000000000000000000114205926609470b6022d24046a1997c048e6d2cf6043397892c967692161c0ceedf409bf5e1199a64eabb1ff8de23640000000000000000000000000000000017cdecbe73779855b7b94920d4bc8ad057ce51c5481a5579650df8a5bbc421030d2ac44568217c4dbb13d7c639760236000000000000000000000000000000000f194fa814bfa7396697bd812d9449d06fc61b580d7a86429fdd1ad376e21ceca139356d7d13964c3c684563675711c67a02c61a7a75342ee7f0745886c0ea2a73c21500aef8078d21d20b7216c2990e0000000000000000000000000000000015d4ae1521acf897344c3a76261754ff99742585af4a0ee86dc473a88fd408091404df1da9d8bb291db68bc9c07d6b2b0000000000000000000000000000000008ce160213875c661163990f3f7ac219ea295db5e828354864517ea8689ec15d35c6df78ff14cb276e0c97ffd7fbc09a00000000000000000000000000000000038a3ee211e777d6d6b7ca6c7a0d2130f1a071c030eebec412c3a0f14c3584e7c5cf15de254a8f141a8210a90249ee5a0000000000000000000000000000000019f7ec6b2fcd8b3190ab37a6e843340d3f3fc092f5772a042edbd5bdc967b96e8a1dc9e435b8463496aa1301f87d0e5a81b0c87102055dc2901826875d5e85a794befd93fccca2b9c0a1f70ef5610d83000000000000000000000000000000000fa7f8fbfa1d4ef5f001a451c55ed261dee344025e599884b29d086e15665867932120d33bee579d5eb1b7e6c7299f310000000000000000000000000000000001f06356f793350b17b47a623059a068800ca1eab6089c7c146182990063e8e23bbf40d95a42bf6e976224b680b75bfd0000000000000000000000000000000008807f6606d2302450bfd8b38fd4147b851ff59762c1ff48f9442c4d7b77a32c5e023821eb47fca839a27fde60e5f61d000000000000000000000000000000000c5b92f1ca9c20d4b6b11d794a5853824cff20d9267a20a7aaa4bed8bfdc728c4d4d50feb8f0b569757b97f473138db1ebf66fce49c6beb12737fe05e3adc0a51ecfa9144ccf6253088dd1a7a483de070000000000000000000000000000000001191410ec6c5ff628bd25d35965f5e9fa7f3c3d8c0a9a1ee7ae37437a97c25e221110d892e2c7a0e9c8e386774eadb80000000000000000000000000000000003be30c25a18cdab139277232d8888f6d13112c9556895af8030f1893114d5845d895df9afe3c6f9ff7ffb1919adea9200000000000000000000000000000000197f6b4e38be0358a3f1722664c61e62587ecf5467f8aadc3a236b47682a75cb76bafb18a5c556b321d5da49cd4bfd4e0000000000000000000000000000000002e4ebf7f22d929b7421a600e67fa2e64a59edd87a2e2eb9dce1f06d3c793f1a812bcdd510e654d44fb4c1de8c64ba9f0305523dc79dc4b905e65587fbd095ed57aa42403d2df5dd489db8f50c99e9b60000000000000000000000000000000011c6f1dbccde640f63ad7d40089779d01075e26269421b4ce12fa5341f58ee9110f17d08dc1052426f2d00da2dd70b4f000000000000000000000000000000000740b147bcdf06705971c113a5cc12fb37345dd59f2cbb5ff500ce2b347fc5a8199cb3007a871670d5093f28979cfade00000000000000000000000000000000046563ea98b5e85b3c42222d5e0d8481e6aefaf077a1b99f2b4eefb397ec846aa3659aacda569054c9c8b9b69750272b000000000000000000000000000000000812d887943506d68e3525ced9b979354539b7b14003a3169e0084c26326b92be67346920c9a99ef0f9638e8991296feac23d04ee3acc757aae6795532ce4c9f34534e506a4d843a26b052a040c796590000000000000000000000000000000004c8078fe8567013e8d05a546934026cdeee7d485e30d739407db16fefaef53ed7bff0f9adaaf064aff014ac919d91c600000000000000000000000000000000107cc17f485af7f22e07cf14c5cad6368323f720511fc9dda677b360567f769e47a77f61274927ef9b7be48a77357ec40000000000000000000000000000000001487f0880a6cbdac33ca35b9b65e4ead9d8c2e9180c993bdb2052060325aff8c62668c643f0cd9b4bb1f06a3dc74285000000000000000000000000000000000d4b2d062e31fabe8d2a329dbd6417673a519f455739d140246f2b3e43e20f390088c08e545bf0419d796ac71aebb5198586d7ad8fc3e4fb42981a4415224c0d976ebe1c342e9bc1cd66d35168bae33d000000000000000000000000000000000811e9b0acfc10830c074c5a4d9f4d9382461eb523a61dda0b77f1c43b285fc5c1ef3a1fafd923addc9a6e904505a255000000000000000000000000000000001113102d015dbb509f0b8d0d0ebb4d3711c4f0e1e3d55fb0af247dd24be4fec9d6fe3ad73fbdcfe206891bcebefee4dd000000000000000000000000000000000085aae9e58fb97b96ca3c089acab7bdbd0c3adae141bf61075f5c13145b0d07113f1075dfb959bc7c2d3d3b3a06ab2a000000000000000000000000000000000bb5eac8125807c10270d94e5bcf278241d6fa82f68e41b5529b28aebc88870af55881db526f7bd221a8c4c0b29a1b7d6e7db0fbd2a7327c85054b4c0de9727dc0b051058f8bb4ecb1dcc7f825781712000000000000000000000000000000001335276775545fbb4c701beb57cb34312108c9f1d46b4aa4b09a16faf0e648b4e80848bf5e75ed8730715f0107afc9820000000000000000000000000000000006ffff8736bab41b4ee5681b741a81fc870e648001027161144254d04c678e4f954e9f191bd8b26201aec681cbf0654b00000000000000000000000000000000026ede90d14fa0885baad21f9631bae058573251cbef5757bb8cfad061f3bdc78834fa5862dea19a2236c014b0f1652e0000000000000000000000000000000009844d0cf7f6f3401145d8d720defa577ca46b49e04e39c4c139ec6811a574e7dd5ce3acd00d1ce9496f10dd15c6d94685cc8d88273d4aa822f44a447cc22f5a58c420bcfe757a459772825619669a720000000000000000000000000000000010192b925fca096682acf138833b12d96bf97c9a2e69e4266eaaae1785b9008f36082e23e2d42341427edce24449935f000000000000000000000000000000000d5b24a94adadbf542aa663114096bc670e1b6c99f3b661f55de121922452534faed7f68d6b431fcf6f3e379d7acf6b6000000000000000000000000000000000acdbcae49206b749d8c0d21017a33e689ebe26804d1fe7c863a2ea4210c3559805dcf73685702bc56e644b4e02614a9000000000000000000000000000000000092309d684fcdf44bfa321d473060dc2d8a8c66c51419894a3fbadbf1b56179c31dff25403b970d543f1dd0e19e56cf5b6e462d809f8bf1a62f276dcb27e42d9aa0ce33fc4e149e87181aca70a4ccc6,000000000000000000000000000000000b219032a2461a5fd1e43361c46beeae92e30247acadcdd241692abe81691c295ba38a1f0a2a45ae76b1b95d7d0fdc460000000000000000000000000000000016905f64e581aafe928520adc27c24703e7adeb36dfbb416a159cdb9b9a26c9cef0821ccf52f5ea5253b7c9d78769e9d0000000000000000000000000000000015cfff195b2123aa140f963628c41deaf19dfff44d26a38de4547c3d15edef10fe9f65b1802dc374d7ba8fb62117c8880000000000000000000000000000000018dc725cc8d8919a7414b7866fdc54c4467b0f87cf99fc9b36cd65c0ec526e32649f9c57495657a93487f1f2f5769168,240480, +0000000000000000000000000000000014441b14765eee30e8131a7ef62c3b59370f2f6f0dda20fb2a3654fa09492bf695de1d1a8f250bfde3c7d2ed805ffaeb0000000000000000000000000000000019d813f8be2519e89d42a9fd3fef09d44a996d6a4713a9c224bee10f0ebb196370d6231fad810edf9cb4c875f08357890000000000000000000000000000000001a5abea13e909bbefdb51ddc699614366f271b2f6490ac8efcca7759833f3feae11057ab1b9ea32311e7b6ea6de110c0000000000000000000000000000000003ac2bf3c5486ca176e34ec5212165cbe04fc9e8c375e3e999a31fe014eb824ea3f2d06b9cf8b86ce3a76960cf2eb4d7535b53ab5f1c596eb966f57867e021d0f3b099e17bf384479c959794b17d6a4b000000000000000000000000000000000598e111dcfeaaae66d1522be2a21131350577253a3f33bdd74a04b0bfba2940e73b62fefa8f0c34c4aa91b633f6bdfd0000000000000000000000000000000017fefff7d94afbeceb33714e9b5480c3a2f3eabf9d7f6e8507ae54cb65f69b21cd7d04d23f24e3a272c589f572b91864000000000000000000000000000000001652e3f5a99ba8dfbcd1f90de955ef527947642054be603c1b84b24bebb579b78e2a0be426ec21d32783a0e55f0178dc000000000000000000000000000000000a6c9ec91e8bc86ab198416cbc76239f0ac0b903f40310ee1f2066b01b08191538ca913c2736f53f23ef37fea13d52756e0512ecbc5a1b02ab19bc9bee4d3d9c721278e07b7a6e389c4d6443232a403500000000000000000000000000000000072e022c168461905f798e87425f2eebb517e473cef98c255d0fe434863ef5811920af65bc946b29d489b5dee1066c56000000000000000000000000000000000e7a9872caa82d191f6014c845e1b3ee4ea1ee89852b546a2c85ddbfa3c1d4ce99002e3d7732ccb8cfbd57d550285ab400000000000000000000000000000000144be65db373f6401d76e0ee64e51076b861e8fca596dd6a7f3b5735c23b0cd13248404fa0969ecaa701663a1032f48a0000000000000000000000000000000014c9e9c5cffc4518889f7742440053678ff1d9fb1a1a103d0c1f762b10655bd5849ce98f4bc5eae80bdd9e767aae4523a79fd15e80b694122dddb01f836460b3eff99e61ea6309d6b395c94fb5a43dff000000000000000000000000000000000948d0f0c20715f8658e1f2b4f9d32d851e584287225a2f47735a1f4c241b07f8d7c5dd8c13bcdf84e97d49817d4d88a0000000000000000000000000000000013c064548cb756b48600dd535af8eb5b9138f984bac0391df2e90a204fcb6c36017df910031864d802a2ff719856b336000000000000000000000000000000000000b7eeb7c9a01be88e573f196c2a531635baecbc8cff9af385455af3757301436686596ec7fe3618af26953c49f7450000000000000000000000000000000001332f4dbd5461ab9e2c8b3c19c6ff407a071018c92d2c17c1d1d481c24565276c0f55eee8692016c1fd76d70f44627cbd012914a96253926fdaabec06944ffcdb4637a05e3e78a9bcf1b21b68b9dd9b000000000000000000000000000000000d3ee70610b5029a28e586f0f3e65bb19a263db3438710fcb8073e1b25f83db50eb5bbb9d75cb20952a225023f747baa000000000000000000000000000000000682f7d5cf9d182b20ee88683f3915e8c9b03074a373e573aa57232de4e997bf155acf680e365aa0988989dfad102b2e00000000000000000000000000000000143962963e230a9154dc328f9583f5be6923a3b10ee7b1d0cd5f5cbff13913d8ff78ca315be7387900a50b94449884c0000000000000000000000000000000000f4f934b42452d41cc20d7b1ec547bcbcbcc10f215364ccf2b864db23a09d06e94c7a87165dcb691f4975323486757ada300c7e1041d94df0e0201e1135fa6eafc98bd33b2dfbe4c59b546a52538c07d0000000000000000000000000000000005f0fd4080e26971ab16d33aeae04220ae23781da3179e38190082f1d167514bd73bc8ef976a2f333570e9f56a6c05e6000000000000000000000000000000000e159905d29b52ba61575c3a263093017783e1028b3701ccf060c165ba33a765b5265a9b1681c1759bfe2c9c401275e9000000000000000000000000000000000c5ac0bc29a49a7c37d772954da850e6b5e301e230552be9a94017d770ebe2cf4dcfaf104633623e024aef6db57892900000000000000000000000000000000002228e7f42a9409acab49cca82cacf306f6c6c29fd9f7e2ed12fef2d16383cdb7bb2b39ad598b301072c615232db1fa833e9cdb10fc117afb17803b61a2bca7de1d190a325639eb23743f51f28294b3300000000000000000000000000000000180569ce03e4a0155285e733adb18fbca71225507a7adf01cb8e8648891525305e92087f58378f4fd8455d5632ad660e0000000000000000000000000000000011ab84e42f10154e306a568d7cf7bc381000f0add0500cb508f695a3b283ea69d140aa0ad48fce2d2d6fcafe60761078000000000000000000000000000000001136c3016474d6f475609606e8d0269fcdab9fd3188a512681cbc41eedeadfa3b3d9355e5b4503e8b5c3665e49fdf3ab0000000000000000000000000000000003f56cba1b9cb4302099b16b09c2602dfab80d1151685ef78e5054cd454b319adf8b5998053a5b9fddcffa020595e3bfc48b98edd9c229037751d02e58f3d4234d9a3b0ad9ae4947ae14beebb274746f0000000000000000000000000000000004d79dab9eef873f3415d66172bab7166ce0c71f322529bdeffa915c1b0d3fcd645c91dd3450ba61593ffecb95edb91e000000000000000000000000000000000d611a207d3222bba199fa083d0459675cb5fa00839fb4c9034ad868fc1e79d653c18651771431d6fb6b6b5ce8cf6f7a000000000000000000000000000000000ce802ecb106a4f0ca4efdcc058dd0e29deb6a5d30a2c15c8eda896bcdd3ac19053c10105328d239b26c5ddbdb3a95fc0000000000000000000000000000000001073e142621ecbeff6f81453660362545751f992ffeec3a83477fed3e6215a709ffe0d17b65d3369f8f3913bf000e844228758d2cf8105f2ef11d83018157a3119a44874dc34d5f0bddb533f50df52c000000000000000000000000000000000bd84f04b3858b1138b1b429c7216d5d1b1e99c1e0fec26440d59b1ad79788c2d5583122c2ad769fcaa6d10d816a1f1e000000000000000000000000000000000387977ed1ce5da51dca230531bba53d17d3de5d593ec576cabfe6463d5164d7153025dbd4cb3525c4145c4f6b85fc76000000000000000000000000000000000a19c943a90fec6921367a2edc5bc38a5c59839cdb650766a2d2d068242463dd4460bd1d0e7a7fb0e3d2104704b8b3730000000000000000000000000000000011d99d44b200feebe00bd42809e3f67a23cce88a07165416cbfaf4db14420f99e54d62db4280d2c99ca0bc3dc41eddbea417c96f0cf4355a78513c77cdc676a7b09125802c8045756da867e0025a36f10000000000000000000000000000000006a186aa584a466a860849c78e4922889c95a4ac6f39c99029fbb422c43d699a8baa51aa4ef51ff99557babeb3e9506800000000000000000000000000000000065fb15b5a0923bdb52dbefc7e9f1a898e32f17d610bac829235446fc5e1913fffc8176e0fbd33091505761f1d06d8920000000000000000000000000000000008bd358698fd073f660ed608462cfcef1da9a59b10905f1d98c4fe66958e56802814906430c10fc25a4d351d91f91cb0000000000000000000000000000000000a53638b1b6c6eeff468e099446300ca7c7bd899c6494682d14fdabfa9cead0bb37a0325d99e7d0ba6341cfa1d257ba846561328b7689b0a89014823537cf9eeaca6ea5c56a3e58d2abfc2ee455dfccb000000000000000000000000000000001070b98c6348a67e996626ec2752f45e4c007e9c9668459a777c03fab633c10236a1c5be99f3fd950542d5648ef9e88400000000000000000000000000000000073a564401cb1a3a53334c0a55da261814d27b86ebf40b02a76b20973ba2db92e42c138ca7790261c2d70401c984bf470000000000000000000000000000000004212d8a9e4b01f5c6814a88561c2c6143eea61327b031a2e0e4bd056c12dd7098fdfe4d1511bb441ad42b55b584a7bc0000000000000000000000000000000005c5d23824b0fe05eb962194550681c57c1566b315efa8ebc90b3593d7d86ad18328baab8118c9f47eccc0757588591ccf6c3fcd4b9e6b72853934b306a078b1f2fb17879db4a0a93d484abbc2b746cf000000000000000000000000000000000b1b3053774ad5515a20bd4c556d2b3ba95fe74fd0c955069c7f933dfd718ede90ac295f5a675f1c29dcd9701978353700000000000000000000000000000000145746ce88686021a0635bf6f0aa2f77c48bdb364cf4ffa804a57f95bd69d24eead05fbee24021c1ef57e1c7c7b894b00000000000000000000000000000000010ec4795a0762b86f3b83de1198698af67fd1b1be3ddef48f35cf82bc96d886fbb4c75064f51a9cfc5f61630c95d0ad1000000000000000000000000000000001465e31f58892466b8ae4b76a239d9f8d1ecb1834886344013cd1df0be13591798868d224d38213a6d75b02a1fde0ff2f6787b565e8d71be6fdb0c97c4659389c800a2047f668b366214adc716f402d5000000000000000000000000000000000f39e731e6ddb7496448c912ae314e833d28208252c7f8e27bcf7eeaf1da6e2310538b4ef0d55401c6552e91fd70691600000000000000000000000000000000069d3612f924961f827497028737000513548ad8e104acee28f014e730d4752a583cb9a893e6169b71966a1c4a4ad2dc00000000000000000000000000000000090899907edcbd336bd4fdad0dd67c578ced4481a25b864b32aef920842689a2c23265277a6e1d4a1dc1b5047a9f79a000000000000000000000000000000000055ba64e2502baf68e46c759fca30247a080464eda2b32e7cfe539e545d6aac6dafb731c2c45749e50513979cecbeb5440ed91f6ceb2ccf87e4106a16227a3cd7b2821b4f3a6e629001f78ba1aa7346e00000000000000000000000000000000042f1c8b9fe81cdcabea047d0998a1354ce09d62a14f1d0e9d188e2f35f2e1845c2b090c5e157595b33108c67e6c184c0000000000000000000000000000000018e69d3564d4ccc0306e1e6b227b0f961aa9afcad59d4ee1737f980dc876609c59a4c6a3506f987467beba0764b857000000000000000000000000000000000012ce5883156588cfe0f4838f819f985b09f1eab40a5ea8e30fc5d70d029a01a4537641248f4c21dd203909e0170737c80000000000000000000000000000000002888eb9778a4045feb5899dda258657b9f41345731ba630fbbf186b3be4b58ffc7f48abb65b693b573a73f85440a7a7ae8ddfcdb4748981acb9b2037c017174a140f2457fb0148fe807fd194a9f7be500000000000000000000000000000000051982b46a819c74105cb36da871fb2415328a1531d155856f6551bd043eca62ddb61f24af429edda830fda31e22cd340000000000000000000000000000000006449e5bcdb5619aac542f6633ee3e06a4fd56a3e1ce4034efc608131ff6ead70ca63e70f494f519d5c577ae7119c8c200000000000000000000000000000000153f4f5dddd5801fbf7f88a735b9170d24d5b63861d50cde9644579dcff277cdb0d5fbfc3b3b819a1172de05afb9135b0000000000000000000000000000000010fdea84983fe6c08cdc4b4ccd462bae2ba791ab5209363b10b3ef342c9a5e92184e9d8be1419e3d88402bc05bad5fa21268803aeb58a2d57fc797358fb456d5cf96afecb1ee0d2b90782aa0d652b8c00000000000000000000000000000000009b011f793d9a939d916d058ffe91b58138820a646cc450389b3074ae3715d06ddec1075afecda71c65c7ca085210c740000000000000000000000000000000003d4d20f4b93c1e90a0a06bd534d8b4fd64e4c4aba77ae42cf4c5b2bd95f8b02ec4069ea246ff46404e6c9eac632fbac00000000000000000000000000000000051e88c3adfd4d6a02d3f03812362a6cfba3a6c69b9aeef75b51106cc7f1750293d61e31f0ea29b5d7aa56debb6d2aff00000000000000000000000000000000086d9c4ea6769cdf49ffbbf7351023b4aea640e8c90f9291222fd0b5984bca4d481bf7e10df921406a34804e6a09f99df9a8a4e5c65973b785c1e2637937de239bb0fde34b786dceea66f6bb12eb4169,0000000000000000000000000000000007638fa4e8823dacb40ece440f8f1e57cc5c3851f94357a5325207db92380dd57a7c8709e4d00b670e8af1b77368285a0000000000000000000000000000000005b66a6e6b13ea0eb367a61ffe7c620d9edf5563cb4cc0cdfa68b99d9691cf9a40efd967c1e880238eec313eaf4c92ad0000000000000000000000000000000004f7156c69ea88a71a0af2922d1caca24055d40df058eef02bbf95d864156f62fb0e17d9fccd193840c36ad8449bb4f7000000000000000000000000000000000b8f46fd695c5d96d939d42c65c3b709d32f134710a67909dc4bb43d752521a8d4f0465d0590f30f06ce42bf5f8cac28,240480, +0000000000000000000000000000000010d48bf523f3909cf90aa58a9517ef5421f1212accd5e8a0f830aeb15a587e215ca9c340bb846b1d0474e43840b2af79000000000000000000000000000000000cc1a3976caf97b9d59f448f6d9f413eef8904f360c0cf912fe942b38d7fcc637a17038973a133608ae769d3e389b18a00000000000000000000000000000000069a6122c6f0ec68834b7617c755a7eb33a80a25acf95859da5ff03316447182f122d20d993b04e79b6fe859b7adf5a8000000000000000000000000000000000058c6f8c297524319bae6722e0a957d1ba0f75ee3a8aaf06148641c67925d15780e419a38ed7e07410e82769da74f2d070e7e2ae2751a1f71962726a31f77553c2da38f4fecda435b6e5459d5e833b400000000000000000000000000000000156ca5e80be8c8c03a5506ce9abd22a9d4958c372678c0caf6f1329898507dfcb1f06a9464cf080bc6881fa5b7df1ebe00000000000000000000000000000000088174d486b4086b931010da298a399e15b60a113e08f571e096d3a4e94b57b3a684711318796eeca9319119b201abb30000000000000000000000000000000000b96ff68505c088cc03a1c2dc363b05bc8544728a12b29569bed137780523123eb17e68f4632383c252d81bca0c5ca9000000000000000000000000000000000486fc6e5224c5fad56234c41856e60bee4a6c1046f673bf7d5c1bbb603b141fc91074da5f9d3d41b796a2ebcebd9e74d16aa883a20307f5436354bab32b4633e83178f33626af3edb14f82724b8e12500000000000000000000000000000000121fe97c62e068988ebff21d8129d52aa903afdbb62862c7fd99564d9ad72182ab1f3a1100223ae486cd76f6938e123f000000000000000000000000000000000968ddedb04f52140160061828b5f88dfd09aaf37df625ee6f66b9500d6608df31c7edf86296eccf8f9918b051a5e4df000000000000000000000000000000000b7491cb8f6252e3861d7160feb0afdd736d27886863ec0909a7cc711a9b71aace18b17a00a2999dd57ca1a74f148516000000000000000000000000000000000fdb280093ef45b12b694ca3390a865ee18e4c04b231e2c98cc28706d4cefaf4e654582ee03f34ecf1dfa9674489d553041390a2209b80f7c64d14965cc2f515d5fbdf37953f75c4a0203bf0d9fb674b0000000000000000000000000000000010d001a09cf5dc3276482185f26ef3f75d28cd6d2667eb08a7fe06c03b99f3b6c4d82390739b6867a314291cc642a8b2000000000000000000000000000000000587846a460b1f37c2e7f491f9a097b4e86e1943d9cd0999313f65627b3907f09b5d5ac1be376a313a959dd136f7e9b3000000000000000000000000000000000af439695556e86b102926d3b40e3e54cc84464e120de3b4e3c5541a6a5bca44151fb0594009663764c1824518b13f020000000000000000000000000000000003bfd9418c1e57269e222152d321b83ae090f216cb422956dd1fcc464f68526cb4a05cdaefc7bbe6e81d4ffe27d64db47cf23dee8d95d94046678f3bdb4b0ea3d4e3a1a2f07f582e2a98ad6eb7562cbf00000000000000000000000000000000196f78b64fcc342ba4f4edf34a3080ec950532a5de21a875dd061f09351def5ba3b85745a561e38117a14c20d33a14610000000000000000000000000000000003929c2bc55f323d57dc3529bcf6644e61c941b72b424d69969c1cde7a804d157045bbf6d5b79a3e6686509e11ecdac0000000000000000000000000000000000f6b659818510cde463c52cf00bd99da045c80af4d5cd0e55f9bdd81f34169fe869c519f37a98ff20c56db554469087600000000000000000000000000000000129709e97757724e765f6600c2b1928286efab55ec8d16876a2a3210bf9d31cc5425265d0576a2d5469cbd9a6c8c27c012adc8edb64db5bf0ed6724f3b54140ed6c81ca65ef9d1b38c8bca6a62bfd3c60000000000000000000000000000000009f5f167c9b61a0ef76415fcceff04f3fa57071c2d79f443ef8a7e6049cb1352f650ebd8f358904bb432d42772c29afd000000000000000000000000000000001524a875d73e03c53b92465bafca582479110611bac6a98fc7d76966e9781308a10cb202289c0776cf5c36515733ccf900000000000000000000000000000000002b1acace94a6fe196b217a9aff413fe0bcb55122ce9e344942843e5afba0d5f2cd0bba14c9c8cb9dd1c3e9024918fc0000000000000000000000000000000018e4f85c7663e596182603862adb559635fdf16ba35fbce7278680ea289f871bcf6755d85654b2a37ae77a37e77ba06ed1535bfcd68e8136808edf89967fbbf76b7f58d1a8ac95ebd4944b9e440f20b20000000000000000000000000000000018ee4b4855f866781f38a618c2fe4214c63034620ea5b72361079b0a5c2b2d6fb9ea73fa202db3a2678cf07219cde81100000000000000000000000000000000180870513afef93870ca64e2363fa1aa43a599db97f3b807ada1c25ae331c80b8ead5cd69b6f5a65a083606591de90ff0000000000000000000000000000000010afd546703baa35a9eabaeb45d301bd5be115557bbb4ff2a0e493668ee790e947eeafcaa923f62ca00b8e635994e39b000000000000000000000000000000001089996b218aacde4ccfca4d2f66d79fe161d962baaf2d6696e1a76ea40af4ae7195e8cf9f6417ffd054f20b65ddfb104c576996d90abde581afb58903cde4b9443eeb65e21b0d68c578e04c8f28f3d30000000000000000000000000000000011757ad74a3fb341c8eb6862978ab3fb5e8cfc8fdbda7d82756532a890d61919cce931872ff339843805e00d8c62ec4200000000000000000000000000000000060783a06e93e82cb08e5dc1aa31202ba11676511300e186ae8e45248b7fdec3b7d5b6849f8b79b8f78ad84f36218544000000000000000000000000000000000ecfd8ab18066fe3408fd20f2a4478156e9a19a09b58da76486c9f6a013d861960b6b99bf49cbecfa8c9d01d5615c1bc000000000000000000000000000000000b45709845d35d7b560745375df79fb95df15e85b96cc1b98cc832c74621339c609018d153bff93f2f5493a52b7326073c558cc615b1c61c9a42b8b0ab4668ffcfc9e95bbe958e72e7a5500058e6b0bd0000000000000000000000000000000003f9de90222619216852356052e9819d7c6e8ff91e0c6f1d8cec832770ed9001db4569fbf579ab16964d76ae7d1b89e900000000000000000000000000000000010b7cf8f0d283cc22942ed73c599115763dcfc1ddc98d87979fc3dce2f33ca3531cc2909d94f86736dda2a4e94a4f0c000000000000000000000000000000000b0aa4d947644cbc7df8d1927cdec66a68862e5a806e25554f27cc1a3701f429fc7097497ad0419e21cc403b472c8ea900000000000000000000000000000000146270ecb66e1763437b824f2ae122f72f20eb93fb30474691a0a192ceb932b1dee111fa44954075335ab360d31ee68d61301b4957a468e2817db5914ff102bc96460a2c5c15e78bd42884b1223fa71a000000000000000000000000000000000c977cb8de4b6e2e33d916f74eb4e42f089d22b54b59fac9aab0e4cafc8aa2b0f8c55d7251662b3499ea140e322dbbff00000000000000000000000000000000106944a9c2d2ecd08e109de29095f3460128bb751051a1f079acb58b6a60b0bb5f52e63d47b688f4a382a77c3b039eb5000000000000000000000000000000000d2f8be1c78995d54fbccab61f816b6ec52dd19aee6aeedc0e4bde2898b2d07c2925da0440a38c4c965a823fff10389f00000000000000000000000000000000183b5d15b243cc5d9584842ab1a0a1e01ad87268728d72aa8c0d7ec6e7069063a11fdd1525d2b30b35e4568da7c44c5495cd2686d24a5bdda0bcb118a2c0eb5ccfe411ec452e1beb3adbda7e93ea367c000000000000000000000000000000000f65ad4c21fddadcc49a8f7bc281d2b7901707f51a67122179fe97da46ea5e1bc6e70d68eb4eb6776307510a67e972620000000000000000000000000000000009003dc68cb0cdec4a502436718f066348f1957ae65ecca8d32c5fd776215cb9a098c0ffe56c92d79dd68d251f49f13e00000000000000000000000000000000038ecf0bb98ff2e84b388c58059ba0de0cff3d5881ecf01d668495ce81b76b00323c665ba88309af5552b7950cc8c08f000000000000000000000000000000001924aa0f460659f552458fb469467a2925fcb2420d4fa6249310456853be3d08bd5c37a3f0a9d6e94e434391d20cccedfb81d555d1e2df92cdb487a888fbedad976dce54b5c46a39893edeac21a12d6e00000000000000000000000000000000189c3ee691387fbbcffdb147c880218c3e5c0bf78c44461ac1bd3ecd5d4b85225e46cdb068049607fedfcca14882e289000000000000000000000000000000000260efc08531083db2839d1413c90968e87d79bc1a2c730f0020e40beb92e84b73ef43e80f7c61e1a30c0cee11b3cb370000000000000000000000000000000005c852ca0aae2c575c65ef18b624f50a32c007d299f24a3ec6cacbcef1d6e3bdba9650fd7d639bdc60a3e107ee9c013c000000000000000000000000000000000321c01a9de69d6b89db4ed88dd48261ee28facc5e26511fb2833fa45edfb58051c8c3ce9501e8b4c3cab9c456705889bfeed84bd95fb955d1b1045c059ffd051324dc8966e504164e54f76f02eb1b8600000000000000000000000000000000183d50635b22e4d620130e0d4008e3bfffae5dadd7e34f4496899ca54eb4d9e3e95c54ae1d9664609c58d02ee5eff65500000000000000000000000000000000029e3b4496a379464302b1476a4549db371f5d6721704b1d6bd35e2344d7679f8a61a0c3b12f287fd86fd247f9652cea0000000000000000000000000000000012c6a3793fd23e955708f5aeb4d6efb670d25a38a67813ecc72f899cd5f926ab7ef198bf6d591328383aaf54f756c66b000000000000000000000000000000001914d3e4b6ea96bb91333468fe8f3bb74636e9a4f2ed198e9ff01b49ba02791d5bd63224f6a38538aceb777168bef688e3b308b95f6d496e6d5b910b6aabef8d9f868471653e8254ab4d49d593180d250000000000000000000000000000000007457f2601621a99050d8993244f026b9a62ff7055b325e6f1edd1cf54065785f003cf7c8a4bb1f7bdf14e220e490ada000000000000000000000000000000000928eb76b428dde37546a27f3d77605c293738f448fbdd6d618747b0de04004aa4419cc5601600419c6e1d470c15982e0000000000000000000000000000000008074e9f5473492dd2e536f7b305be4e5c564cfc9218934d03dde6dc5118064ebaa5c26fdd1123a9c31336c37c1234900000000000000000000000000000000002bba1f9b7da6abd2b322c8f11c749b2a284552eab25a77d21b38b028da477a3ffec1901a015e81fe2893576a41e4c0bd4ea92e0e776be341c8444d4040ec121a2847256c5c9bc918adb28618548b0480000000000000000000000000000000003760958eac45397eca1a1d951a80265a728dc3c584f7dae111e7ce04248885321b69b334b00cdb0334a362676c2d32f000000000000000000000000000000001031e4a63129ec40da5fe9dacfe148a67662eaa00e1fd5c30336462371c167348a10e50f4dc18469a1a6b76485f77e12000000000000000000000000000000001412dbf993c557323426b486f18a91d16b4baa2c497b30fb332a710ac901c96d46a577d04ea87afb08258aa6d204a1c9000000000000000000000000000000000da015ca09ac0c3245c090f39852218f46fea62198fba35ebc4a7f14887943c3bd1bbbfbfa300611e45f419b33988e404c07f5188e4c6270a7e9e2f551683c4f9dc943ffc7ec279d15816a7f4910b8d30000000000000000000000000000000015c9121f72e2425cc8aa4c878907628dfe75a903b7f756b9e13728372cba598859d20a92a8297d95e1fbe25fd1cd968300000000000000000000000000000000025a3faebfa53918efa733949f914be08b791794bd4963f0c3fd78df48b14ad214374b08299327575c0731b54eafed76000000000000000000000000000000000771782ecd9980da521618af2f9eb55d91d67b20ba615c7b3cb1a48d483ca405fe99a1cdd17e4dc7aeffce586987d41900000000000000000000000000000000136000da90a76d538f336608ce877be943025b4c8bf15880ea9c1c001c20c954292d362dac9783b7bf66b8d51ddaf0f2a819a0438efd7ec0c1e3eea07ba201af6a832fecec818adbb781ad0c23e81dae,0000000000000000000000000000000014cb24001bd933b1d5866cc3de9f4b8479fe23e4fc26dd210f9d06e7a05449b9f5ac4e2f48fb847599f625824336bf1e00000000000000000000000000000000033fdb2e899427f1cb9757022c5b614f08c64b53583486148b7431311a6f15aea3b968913fd5f3e9b624705351074be600000000000000000000000000000000035420be9c7ae3203d0dec61ecea70e22e62f50368be870e74f9a7349453647a7f61d2a42cec6522164cca0c7081d4de000000000000000000000000000000000fea43388e9f6e31d419c7f9fbb9839b4cec04163a7b401d8f7de73a4560fbfef4e272f1db9c9d5b37693378f139452a,240480, +00000000000000000000000000000000039dc2b60389f6c893c44072f4db23e7df4c2f299d6b70b70784d9370d9ff8e5413872c227074d429db999d30dc9499a000000000000000000000000000000001702273db356abe7a3f91a9fe4bf56584f13de4069a91daa6c0b552089bef60da98d32c615aa5610842dd8a507f9477c00000000000000000000000000000000095285e8c508ff12da79e16e0391dadbe9a823c586a049e729596864c3cae117305c05f009f9e8ac032abaec8a63f8de00000000000000000000000000000000078fc70e926decf7aa4c2e4b395e88f367757dc47a4cedcd5e632c456a4c160393837196af474948ce6ad53f830ce8aeb15af019ea2de662bf187930caea19d0aa07a74b49fa7d1819a539e06e4c69ff000000000000000000000000000000000cc3cb5e7b033cff3e5cb01ba29ce8e9f4a93e836ddea7d417f7b07ba8aa71a0efae2e1d7a8ec70bdff12d84d229245200000000000000000000000000000000019ce3c830505324b9bc7cda1fbb328150d71310f06a8424dba861d67a7bc0428beaaf697646d22cae9e00477cc8066f000000000000000000000000000000000f6ff67efefa5636b104a0351c90fd3e89a32b8a9beb0d123d3d6ae42eb5e8bbc19c7a972e27762daf852259c65fce6f0000000000000000000000000000000018d98c43fe5b13b701749f4a5dc25f0e713d241d573639fcc73429226bb131d448283338a909670066045c65789bf9e7064a6af51c1d499c4c28556ad1255af7467bc750bf2db2842d626647bdb3346100000000000000000000000000000000003cf82958d68429503265dcc7d88a3763cca32baefe3c8d32564cf30e8e6b8255d4a9f6a76bce1da473b50287deda74000000000000000000000000000000000bfa9cde6c06b2a2ff8f877ca90b3827d0aa0408c4ccbed23ad461433dad71017d4dd387f49c5febdeafa17d06ba784e000000000000000000000000000000001770fe70513533d91c83449ea52964cd8b449aa81f71e71995db5b19ceddef18e2919439c80e10086e670be669696e4f00000000000000000000000000000000194c20491c9d5ed827cd9d370b9bbec55e4a7b1c34ddd1d80201e7019d9487a747b4fa57b480dbdd09af73aa4f5fa0e9a3daea5a083af43711fcb09282b66882ae5b5b8e1714e9186f33ac0dfe48b7ca000000000000000000000000000000000a79d9e0ff43249ff54526c5e1cd55a9bce93adf272508871326c933d526602dc9dae5b6f129a0f1c38139ed1c39be5c000000000000000000000000000000001458b554e0387c1ddb9dee9f4e9fba9c81c15807f496442f4b7210267912b9439a19f95dc80a1e09a0e5cfe750f43c8800000000000000000000000000000000012c06b19ed4e8d5d1b9fed56bc5bdaa3bf0112db997e33aa14899d53e1bddd6aa91dce7e9d25473b66b8578d398981f0000000000000000000000000000000015369b2228e728894f2fd7c2d8c41ac3550da4f297de445cc0f0ef7134c478f526987643cb5408a0bbb79f5f983c085ebd682acd154f6e16a583ca4968d28471653375ef79df078b17b2cd9634258dc10000000000000000000000000000000016649a8231407074af5ffa93f9db5a2ddce8785be8ee77149602d6afa24ab30b26d2f74bdb5f7464333924a817e242e50000000000000000000000000000000001b990f5ed0b23e113042ff004236646c6eacacd99d1d73fe0c3d9351ce8d622327e827b2c0556802c5657f8f06062a4000000000000000000000000000000000f002a2a5ca90285f9b2fd429721c2daffcae5fe48c571ebacaf475606f96cc8350ce88a850ed75e5aae59d445249bf00000000000000000000000000000000015157fe1a767dabc185a8dc8fea3cb208fd995ecd9acab762638faa987f8367ff7c1a60b657be6e9461acc9df16381e5562223d3fae1d303a01ee4642fb4cc70f21937ba7fe377260fe82262a8455a7700000000000000000000000000000000073884ffbe6deff99cb4b0ae1c0e91e2f4a8c2c7296339b1d7e117d5d47ab055743d643155680740befb379a1dcab666000000000000000000000000000000001995bdc23991dd4cbd973e915a16691fb860490bb54011384c553dd14afc37fe673d13950c1e7eaa29c324fd9304624c0000000000000000000000000000000012197a19a498cd94ecbb3a409337b04e76e1a52715c40203add20eb80f7eac66f3386242d51bea34ea016d778248836f00000000000000000000000000000000101069ff0af2ac4dc7a5bf7bf7b56d82a310d67cebc41a9abf1e1af489e1acef3e726fe9571b4382777573712663e26caf1d0fdab6185e1c3f9f621ddc169ba92584db0b40b6ace7ed563eee0090629f000000000000000000000000000000000849b88e7ff52d8136a120f924b20b45ea9ae654a0fa037b62f3c275f0661091038a4c1d6ce7d50512e628b6b397c9f6000000000000000000000000000000000e50e82e9b368f2e316d41febab6b0f626d6588b7217b4e28eedbdf50a4abc9039be9e66c97790d12cdedc90873993e2000000000000000000000000000000000bc5d2bdf06fda1e1d1f5c5eaa7988dfdd790bf4d952f5d3a532bb59edf619dafcbc29274fd3661a35a3f15933b1849300000000000000000000000000000000162e5ce45499e620d0977fa26a291a8e75943c4b5a2a80be395ac9b89767ea5a06606d6b75ee4c8a286d2ea5a197baa5e910487c91f3839d5961f02a67f3b357206e406ba207dde969498e40d4a26e880000000000000000000000000000000005c11afc970544b96fc1a4cbb27259e19b5fd588d1be1c8f19eb4f111882292a463c951521388cb8cb743e5a4a1b57cb00000000000000000000000000000000013dc433dadc122376b75fedc923386a7ba5a363678fcf9edf165a50e160dadcc151b6f402648193d9ef960f5e401030000000000000000000000000000000001893af155aca343bc29989ec2b5a583d020a7558c7663accf6f3e40d0a8eb98ac548e933eb8e2d5fe3550927acc2ed4900000000000000000000000000000000043a79bcbaf07bffe6c6890d95c7e74d127446bdea51a0ba3adb164ea39684bb3ac552020ca28b86e34692c9b36f4384396d32c2c9ef685120995d2244756bd45591618597306193422f3b5df4b075d2000000000000000000000000000000000e6946ddc8a9d73e5b140af80cc91b31b9a226a945a9574f0629566f7ee7650730c5ed758cc30442770ed1602b84175c000000000000000000000000000000000da0abb9f5bfcad73b3f24903e9ef887c660447332e5457e4a5764f6628c04d6fe903679b8dc8bb3aaacde410812286a000000000000000000000000000000000656016c01d3405dce9f7d40e47976bc8a84abc370e7e42849dd0bd93ef1da0bc88e428efea43dfea37dd834cf246d69000000000000000000000000000000001939b2c92c8299d7ec1dbeb9f291c5e1c9481e10df10e6ba18ae695a780aec5a185ed4c7e82dc2bb5af87a74552c2ea32087e21d775fbc2c20dda715e46c9c4970394e40e991c78ecc13a2a5d0b0f30f0000000000000000000000000000000000942901572722e5005a9ef5f948c8cd6f557be8d114d2810d3cca29933a94de3c7658e7e28675c2a49f138d9c98c524000000000000000000000000000000001908e8b815e95ec07a90861ce53f545f0cd44aacc47df40c24d6cbc61e7b28fb91cfb1cb3c67b6c5b38c34fcb2ca35710000000000000000000000000000000017bad3616d8e510e325d9166790239c8c817c68ba7fb937fd5fb70a4219265edf6625b52ff26f0a34c0bf481c482b2c600000000000000000000000000000000023ff8a50a9c0e9ee829ec81972386ea012df5e8476d8c342df6b98fa1faa1382ae921c2f1018a918868672450355c44f44043002a94560d725da2ac44f30cc5f14f52dff5671c6689efebd803b1df7a0000000000000000000000000000000014675ab3efd44bffae321791e6fb35a24b9c07405d9985c685795df2db183ee9dadf18c76cf4095e1e0695dc2c08c4c4000000000000000000000000000000000835f2cf09647061ced2bdf4211bdaea408148100f864f47ff76c0c63a43e44e8ddd9e01709b6ad129bd574d71a1a63c000000000000000000000000000000001017eaeaa6eba76923ff27e5848e5f3b09e7b2b9d55b2cb7068f39defa8628d1c8cedcbb0e1cb5810febc4ccea712b7100000000000000000000000000000000054c873449c738383e9fc2f0f74a6334904171fdb704f5ac35a483ba19a8f661187d36fb35014af9ecf88225466c86e48624c83d846ad2e53f3f8ff5ffd3fca8723e6cd431e89ca29a4d662e82004b60000000000000000000000000000000000439ae88636244d5e09607960fb033e4217343899d044b21e61335425b94a5067c941e83e5a77f4b0690e1de037325090000000000000000000000000000000003a67653818cece3ff0390d097f1bfbea9ba954a85710f5c24d1de1893f25f2863991fb9f330e60cad725708e70384b4000000000000000000000000000000000243394c3459a3af236189ec6155418c1916b854a20b980ca1044b48e23b725dab7c60a48e89f642423c805c117e64870000000000000000000000000000000004c8c9fd9f278dfe9f5e24e0f5b42699bb9751b56520827afc2fae8393c690a63f10e92f77c4a10b0c161408da9bf505b2b2a8a42887ca6dff5b5364d88962068496bee79cbe74de0e8a06209feb38320000000000000000000000000000000011ba67024503301ec72bfad101a48708e3521c8a23c6bf2994078690041cf7eb75675cf5f20c8e82d11145e31751a2300000000000000000000000000000000008ace953ed2eaef19595cc7c9fb1806d26cbf1e888075e3985b28f8d93b9c0b4c820c8e8b50fd4e0b23923d428da3efa00000000000000000000000000000000054ee6f7247296e0748d0b52148a97b930e69991a242767d80bd6434d42b0865a64d3ce60953fd2631aef873d8b2acf3000000000000000000000000000000000077748b724301a8bc48efd1cd66086e727e9872e4efdaf55ba90ad1bed7e229a9cfb79013333b50efb46090ac0bdab488ecb5976f63a38d7f3d8c8ec441b705563c5e3d899870ab5d2ff84467fffefb0000000000000000000000000000000005008a1d62dad51132ad38a226e8abd7421392414acda61111c728713a2ece284b04d75c2bc58d355bb1d3061415010200000000000000000000000000000000189725b7fc48b8a648237021e9a2334247f1cf18ca50008b813978db01667ba08f00b23b3aa0e015f549ff2d5e5c535f0000000000000000000000000000000010483cf2310f64cf0baf556cb2f2828a1c15922547bec03cdb182a316aa86b5473f03373cf7e59a9a78f73193c1caf520000000000000000000000000000000007f635394301441bdc57dd1f4f97656f4218ebb139c13a17e12839091e2e81327f3353c56880c608de824a07a17b2bdd951f4960d6614b098249eb9420077ea5ad11e38d1694f4df33719d1127338f44000000000000000000000000000000000daf4090a229a1ce946064cda1c4b19c88100c8785c69f2eeec3aed12065787ab0abd797ceed07617d55a9c70ac3020c0000000000000000000000000000000011d77fc28355f61037cae3a8342bdf8d11e963495ba3b5d67055f790b1fd632b23565cad77a3d9968d364e4e2a553c9d000000000000000000000000000000001038d7e8fedea873c864b79d1cf8045485299a2bd4d26c5ab5c8d4a073e2c3fcb38cb230dc6ab7e8e228cabc6ed97da50000000000000000000000000000000009de9209ed14d62625ffbf770e8c528594aeddcaf1aaeedb4f3ca973e7b9f9f1a40370cc74b154f3bc641665d8e4d96b7056c7d93d8453be369831dc0575df6438db488780d518a53d19b8f5d22d506a000000000000000000000000000000000a6b0dc04591cbbb1b82a059e08b488fd66edca0f2d264c352f81cb6ec45e50f0af16917fa4727ee9888f84b6c888c60000000000000000000000000000000001369ae16bb0743f65cdfc8082dbe0d588cf8aa5406a095c3deefc27eb3ed462dda9dd4921cde6a1d878a805cd144515800000000000000000000000000000000124e08d4de6e831229005663df4e4bd5bb7af56dfb13244c50410e6d0aea420ba19208bf1a774207e0e0170ad3a9b4f60000000000000000000000000000000011b2973743034a2c362281b11a1ac1c89f59ace09f0a53afb0c2ceb061726c7aaefe274f6dc04e5d0dea2b687a00609a8aa982de1583c25307e9e2c8cf2469a0b1076c6be2fbf12caa8584f34988221a,00000000000000000000000000000000136ff52e440da609b6b73aa838f2eb9791221291b7b14d902458aa7aa9e37114c573edbe8cef7a98dd07275a8c3fd650000000000000000000000000000000000ba625eb47be09ac8cd1e2ec9015640f416af0e3e0e79d39ccac600ea08bdae7a2bc9144f13168a8cec03ce66b9daadb00000000000000000000000000000000095c51e81b5881b009b28006286c704ce3b002e4ca50ac8ea8e574d1e9665a5b1efdd60568d4a4a656ca6a2d1750a39900000000000000000000000000000000143c0c4b3b720fcd0b044a6f420961e2b7eb5f9f1b0d200de56ca8b02709d819f47f0a6ea7d6b49c4f30520586a45616,240480, +000000000000000000000000000000000da4cf56fdbaa9004bf8ffa12d5cfb3f296ba5262dab079c91bdbadd6e41ee5f89912bffd5df1643146bce1f0e021b3d00000000000000000000000000000000150227356e48f29443a0ab4536e7a2f86f9e63840e23bbf1b091a59f52c27978bd6a15b29b105132298de45e51134da50000000000000000000000000000000017f5271c97d84f55f8b7ee0d73267bb69cdc7565c470a4b531f9dcd29596eaedf46e61bd79e71e5ade7d000c1c1d81bc000000000000000000000000000000001322812590e6c22bd90511ed72553c1cdb0ba83487b00e3adcb01a9abb438f365ca23fae9ee4a953544253696ddb0bf1a18ca15f0d931619363f5ee56bd7657b2298f228cae8d185c9d062910193e9c40000000000000000000000000000000007c59f94693320b01b56b36f8d1c39fc9e01bad289577738e648771d8940778276cdbfd59f07926e516fcebb70592de0000000000000000000000000000000000aa71d6dcb0b225526eb92b79891ef920634a007b87986fc0f776f85195ad7ec2d84b9bc684add947df8ff42c33b034d000000000000000000000000000000001362cbd6cca3d5c1ec68928be38aca5de1f224e7cd4f5c3ab1c2cd589bbd7c31022d4adc51720bedf2580d2acfa0f06400000000000000000000000000000000162bf0f38e19ddca9aaa370f988be9b35461d2a0f46143e8663f1fa549d0afa1596f029cf2f800b027b90d1eda6ae8a2b54274927eb29fea0cdc464271c918826d5249b2180a52a5020480d1020c9795000000000000000000000000000000000eb12a92fe65f79c646ba508fa615d09d86e582c3337ae16f66cd3bd74a9caa9dc17defb4b4e67ad62f0665c9ad1b6cf00000000000000000000000000000000058b6ce2582c46c0fc108a37e1d2713ff21ec8b1d8c18da0e69f0dfec7f2f327043e174e16d9d64f9ed4d3818a302bea00000000000000000000000000000000068192bd2ebc0a23092bb98c23f5792e179913c4ff1f23eb27296a77e83729803764b8db3b7ba4fe154ca467475eefb2000000000000000000000000000000000482b16e876aa90da6da35e0d7495a04d5b0a1d084c61821f23e1ad63cb1e66ef5975a3cef9ecdf2e696e9d9b50bf9b65849bffc842c21277be88dfae0040c54b072ff526731947cbec0cfe963f2d0dd000000000000000000000000000000000b712fffce3e63362bcc246da566a14139a3d12807ba83ab3520b0aa3aa20cecd5718e2b7e00f24e6fa705315bc2175800000000000000000000000000000000057a66fb12f27e4a5268e56805fe2b61b5ef019b31fcdd861e2b0beecdffe1a3a69e8d193815f97740324aaa40ce34a8000000000000000000000000000000001080a9e1133f37288dbc3835e45b6611fe84ec4790e23e5ff84a2f72bfa2837f55cae9177e5a3a918adde777b7298a9200000000000000000000000000000000142dcaefd73d7f6342e87fff8c6cd161389b6049fa077f35076eadd2b4aa66f3a1819bf8272cac1c28cc02bb6440dc42aeff769da1b62fde321d46c66f8ee7f2129446d805ab7f7bd586268de8f57c4300000000000000000000000000000000034c0f8249d6aefe4cdbf84d151ea9f84add42ade087048bbbf9de4a412cc805dd9b608fdcfa34fa224066b5f06d18630000000000000000000000000000000009e235ce5eb936bae00d3fecead8859e6d909da3d57bbe0a8aefaa5efdc94969a1cb2e12642c0099bca4e7bbf9833469000000000000000000000000000000000b6fbab498c2706f0efdb4effaf79218cf4b652a5205eabeb84f05a060da8cd18c8154a3d37594485ba50a8228f27f6800000000000000000000000000000000130ab70e17dc73f773df99cbe3f978bcd3fcb92a8226a1450239d209cc6969e2cecdc0bf3cbbe9a9c1de072bffbccaa952c9e56cfe957b924c9c0294e1c1f12474331c662c8e86288c97e6a8b8b5b20200000000000000000000000000000000031a2c10e95b841ecfcbddee4b458385e5650dec9a2d1e50216d9fc261a9829eb5fe894e47f171c8fd2f4d5d89771341000000000000000000000000000000001378471c7f770672ee82b70fc87af5ccacdf8995df9ce48aa9fc2f638105a2fdfa48b615970665ae4869f1e2dc7988e8000000000000000000000000000000001969517c503df5560628555a8780138e4c340d9d49d8fac4a8a11c894d283d49fd06aa81e9f0db8f015d9372762dad75000000000000000000000000000000000f5c2d9b7fc33167a6e9b5a5fb8c5d16ca009282edc05cbc8a048b835b16ba33515c226174d6ce5f9836581611ab403bdecec569d223c724d162250ed1d074ed9f4080aaae3f44b77df05292be48ebd90000000000000000000000000000000000a6a32f2006c4b7804e99011d934ac91b1b3fa6f5d02c574cecd6570bde1e998f135449dfc148aaa8fb8757d0a7299b00000000000000000000000000000000198beb461b59f57b85d858b730fcf853d967a1592e5e5787fd81c6a3d9d9b40c1cd7912cae21a47aaf78df5540604cb4000000000000000000000000000000000955701e84721866683b4eaba82c2df8a89bc906fb0a3cde565d314cd7278b0c56936205cc8ada10b03e69b93c48067b0000000000000000000000000000000004740253653a0d6cb15c76e145dc0b1f811bdc964f7d595b6027bb012b42409deaa8da83e6ddc3f0f7b4b237eb62b537915ac9453b831c41becd3c1f412cdf5379e9cd5c80bc6df92ecfc5005356d2aa000000000000000000000000000000000f88e1e30674934bf1062ac619f1834f35f804a958e82121255f8087ae08f10525e740ee53d7514e0ee7c49e324513c700000000000000000000000000000000019d554645696b7beae881ef62297283c5b68ad3fa9a84a47c29cb53449d33d6ee7a5a3cb83b6acb75cd41ac3f52fec40000000000000000000000000000000004b32776966e52e8a72c88a689d6c56833296d384e2059d8f615ccd3616972074987f839b4689d5610a88addcd836d930000000000000000000000000000000000fd4d21b00d81ec993d2350f1fe360576fa983754a7159c2e81024a00931d84e419e8b5231ba8cf8f05a0ee6ccea7e558fa60bc7cff4edde18301af2348faa69ed4f31d437decb7d4fe51142d179e6000000000000000000000000000000000177830cf34186191fa295b7f279bc819d8a53452e2114dbfe709971584ec7a2da7453aae3e64f4b14c261e22314027c3000000000000000000000000000000000ebf2aac35fe070403a4b7a5c2f102c67300bfd68af7863b45185b37ade1bc53d46772062189f348647e74c77caca4a600000000000000000000000000000000128dc7846b2dc5c453ba5fe4675d0c22f4d7089624ede05b0910c34ae623d4671979fd73455b35b61a57c51fe2895adf0000000000000000000000000000000008e33a3c3735be035b550613c712b220595a83c1953b24b3efd38c5913fc23df823e00ae5a1c2ea8a8eebbb93c5c721dc29be0b271d4e22d39e9e06db9e50845515880f30c5bfac80bca39a2d8d61ea0000000000000000000000000000000000a060a957a8da4384e3436110657110653685bb621c32810b6516c690a00c13e37f70185958beb0ed886aae5cdd611a7000000000000000000000000000000000b5afbc85e274049985eac230b2aede7b2df1485c9539a4a4eb6aea406d0f6515ad8bbece7155fb0dfb2123919fb8af9000000000000000000000000000000000afa722987390440a33d5103445dcef42cc4a3c461daa076d56fd38e0b220016ed2bb8e99b9a8da4af96b7da64ba90950000000000000000000000000000000013ea6b8d327191e53bc71fe43fda305a4a0584cad04048afc0480f179955cb27f2ac8791d847036470ffeb47aae36877dc8c2e971a3a4b9909dcc5cc6a0de50286294ee15f441521e0f1d2c3ad3a76e900000000000000000000000000000000032b490f795ac3242b8c7185c9e19f0440ecee3a65263dd4e4c9a431571deb7339bc6e2d73ec43750f6f027bcfd674c400000000000000000000000000000000076ab4ab3e8ed6ea3b882fde5cacb3bd094567288699e11f368c3f60f4283c5bcee7b4c5debeac541ead983f5936d9f80000000000000000000000000000000012aa2060e421f4f4249e83ca0ae1752dfa2b7ca958821841a18f05071a35fb9c1448619bd96f8a7adb2202d3ffda8eb30000000000000000000000000000000008b24f29ee7571f31ff86574e654a5d849acbe92653ae1a1d2baf4c9ca6e67da4937bfda51a70931a6e60d90162efb4f21c9ae0132a4886820115e71e280d33378a04344f635c769fffe91e89fa7ea47000000000000000000000000000000000c8b41e5c47babd6ea113c0ad9f45a75d1ef6bd313b768ac01e6f581ef6630ada623c1a27d4aadf543af4055de7f6b73000000000000000000000000000000000a0f73af06f8f0115bf17f7c5db0a6bdea77a8e3d8fd0b52b0d4e2c558f1331f655dc272c86d98bf166b532ec8e45285000000000000000000000000000000000499b55964186bcc6986e7744c52babf47e274e47a202abf6f816bc748baf846df2b5ced2a5f61fbb0aa2047bbaf82db000000000000000000000000000000000d6c2a9a3fa5d0524f772cca2c7e72a5f2da1a6a1b9550997e7a6cac5b6b6c37693a01d30bebe4b9c742b63bd31487a1e1067c01d5565d0f387516d9721f7f4e5253d5af8353db4a55500e20a95f3c9600000000000000000000000000000000143220e1cd08ffaa6db4795ed4aa35f3b12cce724fcad005367328972f2364f34096e32f1f1cb7a4287ab636d0030322000000000000000000000000000000000f2de47a37a55edbb75ff0bcc446611d690d7f9efdd09ca1ebb6f1d64a330bed420bcc85aed8b95316fcac3aa7d1f2230000000000000000000000000000000016afb044b8b8c64547e000f80b25576aa329a4319dcd4f1bbe15d12e6f3bbdddbb52140e6297c637311ef0c7a31cafab0000000000000000000000000000000019e6803c07fbaa075093f6a69f9dde05ba3d3f58e67389d7f096e56df49f8270008ed422b64fcdadf7cbbc8334037682a23bf766a1e1c068e6e8e4b60391583ac197ade53caf0f8a43c53d1bae9f13e500000000000000000000000000000000134125416c7908cb4454ce6aadb30df46042ef2a6b4b69b19fafcb9ebafe8b5579046725590266cfd10fa26e1b5ff3dc00000000000000000000000000000000073f4147cce24e13b9eefad7c69b457acf126bf278a58a26a7c7c6b482edea6dca9725d7e5e4138b4ec81bc2505ce2e60000000000000000000000000000000006125caac1061cd6c556f4cfc122df8e949622a46ca707b48ef088ee5623df058bada1bc0cce1399f0be1ee86225f13000000000000000000000000000000000146e398c161e29c90c8a4fc44bfd5b3dba6f9e80ead561fa3d91ca5f416e06318dddcfe5147ab5def858fb025a1562352c505d4fd8287a897e01517ddbd7d7ea9d26ae4f58fbca172e5265e2b62858b6000000000000000000000000000000000944942effc77ad02c5ddb052acf86f3a9dc4127dd032181450295464b49ac1dc0047790acb378221fbeebd4c92886820000000000000000000000000000000018e1d201b38d88665696ee6cef11fb19f7daa7f11c5a5ccc73e6b66ac7b89df8437c9f07132ec8b69e13f63424ad694c000000000000000000000000000000001463117fdcf17f28956a42677b3ff431cc17ccbde067b91ecd6fae51e1e24ba8d594ea368d041656022611ad3ed44a6e0000000000000000000000000000000009715cc5add17395b7ddbcb961269fc5d4739d799fe9554b3c9e9f59c895ca5df8ec75bda05cbef3e6a165f7987e78662908006c06ceb9188651c59d434988cb5b51a5a75772ba71875444c65ddf0f4f00000000000000000000000000000000007c07cf1ac9b8b28e3d2f1f4ce22b8ee46e99914ba20c7362c679559a1618a906c6ea65c475ebbeca4947019cb6fbec0000000000000000000000000000000008b29f72cda71e0bc2246ead57b2f758b741b9232d87be75331275a5cd63afc9aa98b0e42c1b82cc258e93c97e596a81000000000000000000000000000000001512548a4bbd537a4d5baf673fb76ea7e35b2977216e7b29a6375e1f92049d7b7d5fd5d8b4ae6191f5592b738e149a5f000000000000000000000000000000000cc9d646428135296919808c6ac10c142e769bf71bc1490196dfdd4e1fc7b84e58155bfdbe77a9e684622ffd83e97ad3e8e8724c80f3527de5f0b2b98ecdf0b8d0471e63c0763a89da8a21a70dbf8399,000000000000000000000000000000000ae9da7d12d0a03cca3b41ad869f762784cacb988eac7ce904ec9ff47824e058e2e211e2285f9fe2aed0b4385949b4540000000000000000000000000000000005b0c873d20f7be1410d39885ce4f79884eb6ae2b2f27510d6f6874dacf2a66c64e56b7aacac61ec88261624936e695700000000000000000000000000000000076c6076175ad748dd68fee64431e5e4ad013797de4528287e7226c3df90233799ed5c8b36848c1a2e1c02591a013d270000000000000000000000000000000001f7f6972121d38ee2d10c621a38448ed12271f7e0e9e4567fe1b5fcb469c7906196fe92c66c37f8c5abc91160fea8ae,240480, +00000000000000000000000000000000139cbf360b10e59c20dd4155af2023d5dfe0681c62351dd541cbed41b6a328aa44b862d1985b132a4d4ca61c95b61ebf0000000000000000000000000000000004af6b5a0f7a02d5c93304be0d31959bb4b1a7a5838dc3f9cf50180c4eaf3d32e68c006263d75f9735c8f0b5f811d3cb000000000000000000000000000000001644937e5ff3b8d2005a2f3b0984917837d44412362a121be481526173a4c61e20d61076aa10b4072b79743c5f7f4c4f0000000000000000000000000000000009bd399b55a59550dd876f35e54a5803058228bd6ab6c9a66e438cae473c63816c96bdf378ad426a236b58b90e737831e14282bc687a00264b4e4678ff238d5205f6b6fcc10040d9b4393e93f76297a8000000000000000000000000000000000f343e5118d7dc3a38e9975a5f40084ee5f2305e45a8aed28ef105f76345d9f5646b4f3924b92978846b4e605b78fdf400000000000000000000000000000000017e61a2ecf9b3403b43f5a10a97cf5088b4f98e5a4513b0912ea7ecef44e6809f10dee60367cf2fe3e903dd68c2a97c00000000000000000000000000000000039f37f414338cab0e12f99b2aa1e3c02cbdee3406d1bd17c359ba899b7cdcff605f894530895aecb469f41628c3da120000000000000000000000000000000001b78bf69f1b7168d735fb4b7b135fe70db79f50e792eedea23f83cee9b48e092536c2ed146c7499cf047c5b4f0a08735307650d6cfc681508fc7b8dcb5291837582eba6588132f46ab8fba674a1f5af000000000000000000000000000000001342346f1b553e29e661c9f6c0a24d8f788db98262d6af86af55d313a37eeabed1183e367ee3d83faa3f284b260e786c000000000000000000000000000000000960c8af3f7e6587cf83baae447491e73cf41e637e1efd730e3acd9793717e57b85530584942e7a030bad3b91a76996300000000000000000000000000000000166daca4ee2cb9516b5178cefef0553115dec8157f6194d24d191cfe6340406071883c89246c0cd5f89bbd5d0f1ee15b00000000000000000000000000000000187f668086b9b6307899d301bdbfec915cf24ac0be10d6897b0677e4f1de6a241f3dfb19225644858be0941530e67d0f7d6a25511ba63f0b7ebd2189cfc4c551083ac92b144e30dd81d27e59dd86e22600000000000000000000000000000000032c3783e701bcb651aef40c91682eda03f9d90f252740612c85a5727f8bcc41a886b328d5ce787031c08ace235ff465000000000000000000000000000000000b0eca06f9fb69ebb46d0af56d3d934b333514d7f31208b4ee2fb92009e6041749028a78246a0adc324034a94503e80d0000000000000000000000000000000019eb24ed35f6c7ae53047814cab14d51ae6cf336d140a17e794d5cf18450b7fac3e6f990e12d340291459197bd353861000000000000000000000000000000001983a596485e657deaedf01614dcd5f4ec515c0050e8068ea02c9833d0b165c0f467107a50da0d8cd43bfcb59db6e710eac8e5cf13de6db37982390c8b6b0474795f479584960748b7ffed881285e2df0000000000000000000000000000000002f1c29ffdf7bf20fb8a13363393d5f1cca5dd9af82888f0102030fdda641abd5532ffaa2669c0c4159a989cef1c5bdb000000000000000000000000000000000bd548079899d49cd368bf5c246aa168fc8c777bb84a7930258502c8424a4b68e1ab20dc9ef39c307e52bcafadb0c8e100000000000000000000000000000000070c18918f037d5fa1aa005e2c80ce6a80b4b24d33ce72a2bd824f9a061af1db236f04d6041314310b31b805b8a674800000000000000000000000000000000014422b173840da655aac6ea4b7a04313d5d0675bcd565258c73039f879176e51ec0c8a9deba9c78c33179a5ba54492012c134652c27da0a0272b0783551ae44db6bf592ff299b48c50c550367d470b5b000000000000000000000000000000000a1be8e39a47dbe0bd19b8108a5bdac582e1d11ef7fe28df1f12da52924e734e1d591e8e33ec20c6d5af5bc8c1161fca000000000000000000000000000000000eaa7a7cec93b8d5eb933103b52a35b3d58214feb8e2de0bba3a0e57e7993a9df0dcf8089142f57f8e0d1d303588ce9d000000000000000000000000000000000089fbfb389ba448eb77722994178ee3cfd15a27be4ed6f4d4ab6ea1a4c10d6ee8424beb17d08190fb18ab8498d4a4fb000000000000000000000000000000000ab02df2eb474735e28c45b915299230ce159816419fe9c99a7da397b7210590705262ee14c2a244f4922c35bcb119338dca9ff432bb483ad726bd20cf96b07ab6f07170a1449f0f1b50ddc6e1a0253800000000000000000000000000000000006508fbef44d36cdc6fb37b6324810ab2a1d94e39abdf09d530df34714168105e23a7d6f7fd9caf31f263b658f16b76000000000000000000000000000000000b5bb1802813f9f8a16991d41275ae6d18532e3dcd2eae091da7256aaddd501855e775b779959fcef2822685725cd43b00000000000000000000000000000000052146ee63ae277911fe491420651a96994a30c7d1b19bab32eded008a125369baed2ec5a963bfd863a83c29bc1afb23000000000000000000000000000000000a180d79335347a8be350a92491760c6bf1fd56604d4d99a1c49bcbe50b2d04b7cdde55b4aea8ddda4bfeb8e79ab6ce4146433a0738ab1b044e059f49a8af8d85546d0e34eaa0edf2b2a6ee466c0def80000000000000000000000000000000015dcdc17a9afbf88b54af22ed2168329bc43ba50d374c0507c790f37f9669d0af167328d50d322a827d45f39724d2b2600000000000000000000000000000000169b83f2567e921a4319fc03b2a7eeefd2aed79914bf608d9e0a54aa71b9cb3e09f1cbfbadaa520c0f77f547fd407ea50000000000000000000000000000000009b7a8ff8388c85a0fe3860f26b09b81b5dc51e00a8961fdba96eb462e1334e9e28a2cdc4be49dd8b96c548c64921718000000000000000000000000000000000243782436fe7cb20a3242a3a21402a43a2c4fcbe77cc7182ee3cc04f4795c269d8a64ddd25e89ba4fc796747b608092de0399ce1ed861c0ebce1d4e811ea0a3d87e21a54ae34e6b5e1284cbb94973680000000000000000000000000000000013ce6856b6df48e4c9e3fc0be0aca5b139e1b874de6ddc148c1c23a846d61e7a531cc889bab99706668a3b69d32b9160000000000000000000000000000000000a459676071c7f3065a6dd7632edd5842db34aeda8fa0e7d7a8ea29f842ebcf2c5fdfa74ee7685caa51481c4f46952240000000000000000000000000000000010c1d9ebf7bed9195cf0bfefad6ba45f1bd19a9a7d340b7c630b9953923efe4907bd75a3da066fe3d49d656f3ed91d2800000000000000000000000000000000039189de73332d5b5a160c296a195cb9d8a736cca23a92948d513da7e4fc46e1ed9c207e86751b3cf1310d8a7284877ec2b034594fa53a0951e2116db1b063345fa42dc8c870e1146f1b00f626dbcfdf00000000000000000000000000000000129821e97c65ad3801c011792f4c099e19919d7d03bf9fcba30b3735586bb7ead7d4f9bd10bc5f0e5cf1dae82d5651ef00000000000000000000000000000000038cfbe45bbdc494988a2dc72dea6a7e36652f5e5a2ecad41b4aeceec05dc4a389e54cd3aab349adbe32e65206eb481b000000000000000000000000000000000bbab53f2be2c471d6e9cbad719a73c00b582d0983e25e1969c0be1faa56b1dfa5b7b55797b3340cf8c7eabc560fac71000000000000000000000000000000000b0db19410e552a2f7889c2204a93c5cfc71c360329e3be3171e88fc7aa1e993a5d089c28b1a8f8fc80d93ba194c63ccc1e6d9c5f8911014f0f540211af5184d96fdfd47c03bf2d7bbbb3bf1a330017b0000000000000000000000000000000019320bb8d29b7b5a7130b87a39e87e271b96656b5a2749f13208520634009c26f9829401d3e21cee5a757782c6bbf9ca0000000000000000000000000000000009b37068d72463e72f3a89b9093c1b09f01770e647b5ff7daa50e0679bb76404cf7729d5575a39f5b9b3b371893967df0000000000000000000000000000000019ff29e41db50c736e12f62d76a28f4ca4f6b0f4f61aee00cc0e9dd4e5a75c0ca965b82698f704c604bb309aa5b457f100000000000000000000000000000000062c352a554dc4bb96b459378c21ec6446e15b868221b2fb745d31dece854bc281bc22827d84ea3b0fecfe5d156712ce6df5a133d3332e1f79f41201f8cb2c8c8d4d1ab0f640c4de6bd6e34884a77aa200000000000000000000000000000000021c52e82b0012537b57fd92fc276e8de842a59355cc15d69a52effcfaa7cc43dbda0c34e1b9af44c2db8e9356b9c71e000000000000000000000000000000000371a6da5dd39092b6108f631a0f4c4401464a109ea1e5d14e262c8a9577e1421d41734d2c3ed73645cc13ef3988e9e90000000000000000000000000000000004054159263ee60f6b1882ad7c376c738c7ed87e6b34dfb4be2fd7aa29ede414c2c6c3ff098c53f22a1c1cd836a6b0600000000000000000000000000000000012d7af6b57c688e1ce90e9f2796b0e525e775fcb6be65f5d2fbe3d1ce1e5d948dcb098c98d495a6e3dd813527b4635258e7219a9d431c597fe9700d43da8b545072f5a27a9f1af99053ac0494087dca1000000000000000000000000000000000e53128fa5392dbae9e40ab1ff0149d5b577d9d30dcb85eb5e4fcdc17c7daf2ff1d6fafd4a1aba88d2e7aeb45a01afc60000000000000000000000000000000012972781f214511e9b78d276767b1b64bfe5b43215c7680c0063b6974f703b209b2929470dbae16f9767a7cba5311fec000000000000000000000000000000000cf6b37c5a60851d03752f68eaeaf37ac67c661f644cf507c5458cb5404d0ce903c92ef66a657b25ce07e5cf5d956929000000000000000000000000000000001835f202705c8b984a4c7a6cd219c718ab27a96671574cf7cb618235d19e9046a15212e0da6233f15f18bbe192df29c38efb8a7a5e48d5f4a011a4aa0dbab22ede62c903414d005d507ea3d77bd47a6c000000000000000000000000000000000d01c6e8e34e646911391b012680f0dd8f4b8d77c10192ac09ce57b6524f0eb8c7f83ff8f26d856e0945d7a909eb790000000000000000000000000000000000070fca42e34dacce0051f9e26c7c0dc328fe652110976df6df77af04202831dd095715af1714b60a99f2177e86a3443d000000000000000000000000000000000063ba43df0155373df59b009a8083b9f62004327b16ad455037487c5b8325e7eaf57a4d05c533e284004be6de79ad1e000000000000000000000000000000000870c2e5a7d26ba54bf0d45ddf0a4c3011152dd12a5e01a80e42bc4dcc784c7ffdb66f9d6d69ac445c1d9aa29586245147f53e2c06664e1daffd7d9b114e12d4190d5d0fa2244d61a13da915c39b8d53000000000000000000000000000000000d84ca02ffb6d3cf6eb27a143ece73d5bf006ff61569f0eab00c5a512c5b46e1fc21e8031d1a578010c9582d75e1faa8000000000000000000000000000000000a41249cf01ecd23d06f6a3bb8573186fe47e5165ec0d447df62bfc236f4c203b4feb8e2a4785648af86646cfb0c4e32000000000000000000000000000000000244fa6caa86fd27e044145557697ea89baf718746711c8dde334a2c5ae3c73d7a0e04fed6289ddfaf26e47a9d26b09e0000000000000000000000000000000017db897060c0a8e3e5d8eca9970407b46dc2c2ca0c004d50a171450852f585268bfa8a379acd01b6d4685e04c0b8c106fb109d9a0a7b62c7c452bdf0a2853c4bf65e5439fdc83aedec8c0bf73a16b55800000000000000000000000000000000071e13963e20eb1dfb671aa4a090973e4a4b7ad3578f8630db8a865847be46c796e6f9e095a9ce558b93d702f8f8572a000000000000000000000000000000000dfc4c89ceaad07e3b4c35d96e8534122ae48421cd4443de478ddf9a8867ffdab279ad745e55c87b731afa7700bbdb110000000000000000000000000000000015dd6b0c26f6821177d0cfebb7f1481a971e7601fb24ea365a0c3127a5b1042eab69446de05b61cb6ac0576752f87aa900000000000000000000000000000000156326c52bc78c82f5cb4aec5de35e3c128c5561dc80da2cb24d68a7e912b1f2dac2078508fdd4ec38769102c082f0f74b0a931b894fbe61115fcf52be51d44afdcb96c94117c75adffcd8729b0a699a,000000000000000000000000000000000b537dc10a6f518122665f7d78326a4728a2889325e5be7da7e25e4752c680fd786cdaadfcc426343a9844efbbce8f2300000000000000000000000000000000085ba3a04aa8cea82b95dd994f5b3bdf0dcf63f13909aca2c2d61e4275a7ea22445c953b927ebc6b0987e98b553469d40000000000000000000000000000000019cec2e9fab640cc88073bd39e46cd571324904b1950fa8f626e2725936d80daacce2487f46ad23fa8af9c6ca0367fdb0000000000000000000000000000000007039a0e11cbb8bd940eaf4a192bb94ff8c6d6c79f775fa67821b5ba411641c09dfe9fac4cf45eb5fae52d2fc4beb6bf,240480, +000000000000000000000000000000000f73a297cd6444809aa11b0756167e71986ab31b52b57d3c0aac5637129b8702ff21ec649541e79644c27f0017c8ae3f0000000000000000000000000000000016f96d6ba02aab604dd918cc799cee61cda4c0164ed9f07d4932fc4ac3eeb92b1e6b40dd7b18cd8d26056b486e57ed290000000000000000000000000000000012156f3ca3aa1e79014dfd92fbb6c785cf0ee449a8920b89ad04355e0fb7c8ea804bbad082b4edc9abd3b24ab0df2b61000000000000000000000000000000000d51b5f62a6e70816d7671bcfc52f11bdac6221a23287286af78605b99ae8bd0c722e485bd0381b958a85f61e05de68368ce22e379ddb8352d12eb597c179c7089c6388542909876c69ee377b14054e7000000000000000000000000000000000acc52d0fca02c3228cd2e5202c4eda297b8227bf4e64308226bc487e5b64738efa4c07a3738397f90251ea9a1a9da29000000000000000000000000000000000b85b853826a28777a5767d5b1966ce12fa8999ceff5d6deab5c947fd19d19de9c103bb920bad615186d132ec22187320000000000000000000000000000000006b5a83827dc7b3580579ab7976a70ee160b712580919b6f5d4e180165e50f5a1698fa7cc63846eb1f5e6df955c3eefe0000000000000000000000000000000006c2957d8adc55931900145388583e5c2d5f6bd784e022702801c38534d2c92c6df9f95d022aa6d800e1e458eb7f313061529338195b665f1b80c4b95b7c3a26a7229884be1f4be9d49e1274a9ec3f810000000000000000000000000000000014e4c5991f9f2ee262019c1344a0843756157dc85aecb15718217a2fbe23fe0843992dcd3953ebe79acd85517acece0e00000000000000000000000000000000076a18fe710aca2875bc102f21782c9649f107684a4edcb0c4538f1a2890a2ae5b46a182d5470e620375327965b6d37700000000000000000000000000000000142a0fb19b28a034d326121458628356561e50cd3a471ee78bade0733597b8b90f647f5199d4b5b1ee6be4e1870bcd310000000000000000000000000000000018f8b5933848813cc2c1a0f079b095d565e7875ba6693eaa10967d496fb47257c9c674f301349dd8f2d22f8857f9d5ca44d740a72e6c8b5632314408022093618321c8c0a8cf2fcd9ebacbe43505a01c000000000000000000000000000000000db331d2b965dbc053b01a61e671d2ee6b04b072b6494e482f48f12221f23e3b1ccebf48046d92b4be2e4283c77f51380000000000000000000000000000000016704f3e1ce14f49df400592ce29627833ed1dbb91ae5f00779eef94fe9ab313c3e7c8da940085034e1a49158043599d000000000000000000000000000000001956d492f5764c6de0b8e9a716766c762620ebd3265a95b47a8ad2c0614c337692108800e22abbe321d77a6cc17f4b880000000000000000000000000000000017149865739d6aed0f2a4c3c71c2d02f8080d9339025b03f89a37a165fe6e5a4cbd489b5fc90bb2cc432e5baab213c8424872a78e340ccb077259aae65d6c448fe6bfb64daf4e2b6ecce2cc9525e35a700000000000000000000000000000000036804da102cce975f980ed5a69e0464241b5de87238f9892c77fc2b6e5ceb00d7a37a45b5520fce5f094f8b9510f49b00000000000000000000000000000000049da8b6c974f2d680a80d2007333f15702f1517d3dc11395662ca1db945c795bf64167840c4df0fda68a69e127b2d590000000000000000000000000000000000e94cc66f1ffb2112e37cbd5b4feb7d65032c2e57260504a42816aeac85648558f6997ef12028655103a8cb9de1297d000000000000000000000000000000000abf7703ddf6995d5c29124ba9a3f890854fe0622d547a4f24d6a60b036ec9e58f7ec2deca5a71e1fce2210cf810e2f901a1d84826bf78f493417a06a800d58dba688800026638316fcf9ae534436fc00000000000000000000000000000000008d22e456c643ce680f5ea14553a9c249a43d4f92d94135dfec85bc58967ec01135507bd8ac3954b5876c5bebcc1179800000000000000000000000000000000022029d4abec7fc9ab3bfddf2f462660bef7449c4093144d9b7d6f9e84f4f1c947855ca6e09bbb3bee4db096978ae0dd0000000000000000000000000000000014beddf6a3fbcd621e2a592e1c87952ed277163ebf390896f7c668944d6e0a026d3df74b0fc877ed560527a80b981d1e000000000000000000000000000000001414af918645ce0d4d1f670333fedf286b01213408019e327d3cb9321f06fae311b598c2f78bb578e85692e6cb787a52c5a3268a8ab5a12214b266aaa4eb562aa05dd19575a7f3ba2d549a25f1900cb800000000000000000000000000000000129f1e25d96b8c879710a81b727b31d27ce9887c245bf908a3768f3606870ca6bfa70dbf5135819d36582d55f230e94c000000000000000000000000000000000e91eaa33e7cacce4e1d6d0fe905c72221b534a72cd51e1de79a25ef0c06ab454a849a241c023b0f82aa07de28e35869000000000000000000000000000000001379e390f2f0f3636312465469b532d876529d58dda8b024b6b81d242af47b5720af4360d5a3172ad80fd9fd8a14ba2d000000000000000000000000000000000775992d5a8ae0640af845fae03dd0b2197699f413f90f6130d21db0dab042324094b36acda26ed86c65821d2d8a29d9e62a7b00d2be967df04ef56121c95c8736efa95e1faa0196e1f4485da82b3c3c000000000000000000000000000000000f5420156358ddbabf31fcc94678866f899e38747e79dba8ae280704c4b199a03eb423ceed18b5cba7e7ce84583c84a0000000000000000000000000000000001127669ef3ba3785a859aa4e942e8fc3181f2703b0ece6ddbee8830d7ffbfe498794f1ca2e67c3ad39ebd33e838dbc5300000000000000000000000000000000138113386846310db8e21fb8bfe40035cd89e51736b491d5f2d3cf5672e6836c25f62eab80f25ab49d16dbb83796aa5d000000000000000000000000000000001711d74ef4995b473239a574fb8ea6edc6eb7a88793a093df4652da240d069c5bf9249b58e9b1e11f7d6619cdc28a5787a883bf845d1ed04e0664d814cf0b49cf8c3e8b8594ae5d2834c753851ed7803000000000000000000000000000000000d32ccc6598af8156f1c5b35e69e7c7f57f9fe18748510605a2a81b4ee09882bf3fb26abf50206cd57c77924ebeda8010000000000000000000000000000000009043d364e0637c60223f9a5db8c50e983746fdf4c9f7986d27f5f4f3a6df487592ea42078f14efcb3eb1b7e81d058eb000000000000000000000000000000000233495c4961e71cffc2abcde4007c0d587687aea905f3ac5758d0f8d9020197adb6f9d7b86a542b8efffb05dce997130000000000000000000000000000000015b084e773e66ab1459825b6e6dba055a96e4dc1d94ac0b640e906e0a9f12d2124a58537c458e6e1b571311b93acc26c0f474e8f4051c4e91124c14895fe9e2516b315d805b79013caf830524fce8880000000000000000000000000000000000e4b859c679a90c03ea4d4b0b3d38211f685db053aede0f7f359f712e1ae808185758546877502d57200da2c2137f37100000000000000000000000000000000173b24ca19436b51aae22838674c41c752536eada3197de6efc98303eceb3e6e8e47ee6679e61e3cb5c8c734c96c98720000000000000000000000000000000005232b8c97a4860a23999d6ed6d173d300ed50b77c7b3ceb4e8407d9d6877a6004e2f76c553bf458b7cfd8d1e6fd364e0000000000000000000000000000000018a115201e3f4eb308c16656b3ca0635e6284169cee3f28101903ce1cab0659c3d83a449918df6e58e8af2e001036b8d9b3a5790750825ab75ab7422f833c671b95c6c58619189db66a6215ce907381c000000000000000000000000000000000131232788aa3038a6b8a055a896af4f8129e3dd3397dfd90ce86b3e09a775e5b5e19f4387f4c02200a36bc2a1e09d98000000000000000000000000000000000eb8cc0455cbaae97dfd05c1246d3d5ee58c286d263184ae342f5c0ef432355a574bb9fb8ec67634f999b6d1419f2b6900000000000000000000000000000000188b8a85a6b255408f074b3cab66b95e0e1a1b5b8965034246dcc196f2bb84aca3a78907409826370bd65cd4c4d0bcf30000000000000000000000000000000009603984f6d9876e9c235621fa817efe45727fd8c4f76abb7b0796ae721701161b39ff7cab4c57850014e7f1750954ab6607a48ba3fa5c033a1ef90260ada14ee50c95e5167bf801ddbd3acb77c3b3880000000000000000000000000000000009003b42c08b5c7d3ee9f6abb96e08e6f537da25cd0cf7eb85a49067746c03566e133b54153380286ef5725db5b41058000000000000000000000000000000000f09b7b754c255e0e3b8435ade64d6960285759495659dfdb9b117806397baf8d3c87e30bee02c9e1b22fa3efcc58f300000000000000000000000000000000003582c08a8de4bbd20ebfa833517a75682618fba2702b6c71a4785f70dbdede4e86ad8e04aae1f50a6bb75842ab74aea000000000000000000000000000000000ec013f22e64a4d4fb6f964e8319feb1ddbcfb71329186545d9b9d7f97d1f6a56c8aad03d20e9c30966ca932e1f2bc67030db724eadd2f487d31dd4354b5c0321a7983aead21759807bd893217c4d40500000000000000000000000000000000025809fb06c8a31f31ca5b4a5c795bc93355c78d9a2a4c1d707e32ff2a71d94cc1bf7b709cd5d6a183cb05fb6b5f360c00000000000000000000000000000000127bd8c9ee6388905ffe59bb0fec0e42b4aa44be74e5961dc2353e474baabfea86c41c6173db413ee28681a6bfd3ccbc00000000000000000000000000000000181f40dd8581b9adb2981dbcae27c7e906138569ff41a833ed3e6ee4fb0baccf2ccbe5b28ae2ff8e08c4f534116b58c40000000000000000000000000000000005cdd822cb47f35f31e0cbc26f6c957d51c6880369af94fd84daa1f1ca95e41e240b910f031585842fd2dfb170d618aa88e71d0be8fd050f6dbb8b2fb3ae2a9e593bef7a5163255aabeb07282e8793e30000000000000000000000000000000004a06984a3916820368076ab8cad6ffffded2cf1e67ac33f539ea8fc7a79580c1969e55b2a2fe3b31de912d6606c20780000000000000000000000000000000008a1152a581b6fad2a23aa8b0b51cbe523e701193207c896d08b99a672dc047498e565a568b79f8f9188767ba95212be0000000000000000000000000000000003539e82e5b88ef660b6593fdfd9591ec23e7109642f4aea0570f1f8f8e00822d2af277632ba74910459535b35ad47120000000000000000000000000000000015d3441f621c7e6922c489e474f80ebeefbef66cc59e4350b6f803e409034b7f498be2dedc97d902590fc1e296fe983c26989184bb87a586b8752733f9ce9ea06422c6a898f0f402cbcf760a7a21c95c000000000000000000000000000000000f775e13276c2e32dfde955009422557f332fb42dd9ccc3246d2b080e3ec44d910aa734478899698a9b04f6fb1a8f922000000000000000000000000000000000460ee4df6dd0184bcdae6d53cb66967c2213fa878a829c3196664f8d594ca6d60bb2a56f93bda3b0d2e6aac0a1a222d000000000000000000000000000000000fc9bf81d4cc80ba4e4df7307f976c2ec1ea2415df3c263cc970583824cd83703aa994daaa6e5c20450da2ba90a242830000000000000000000000000000000011f08ecbda9a192b232e8330ccbccb16a26bcf4791707f2cf52c2e11a8b3993221666563a772d82f4665804275b03b613d1dd9cc44b30a4623a4d14861688cb678bbb8b2f8ae3ba140f60e64c05514b100000000000000000000000000000000027fe7ca0fdf1cab9a52e304e55350195492abecce4289b0f1c02235412bb012803e7eb59e23c665ea86dd4f74c35c440000000000000000000000000000000011301ecfc78ada92885bcba8af75da6cbcb448e0c49511f3ea306f4ab944f5bc114e72f473cdadee2d0e84021905c5300000000000000000000000000000000010eea529fd3162ad7b49638a70f6f2c26a6844251b2c2f9f8ba54cd334914e84e5a1ba9c7b4e7a8b9cff1a909db78bc8000000000000000000000000000000000b8a6235a7310d52fc8050bcc484e6ecf299099e193f91bea9db31fae71fbd14978984a9e6de10939d0fbba96314b0a55639d80f55e24e05e3d943340e324f6738a593a915a6bddb40f01bf12f73daef,000000000000000000000000000000000de312093622aabdc7523cd72f568060f4236c7287d61c3372bf81d9bfebfda2795c3182d508f0268d8f445f6ea0a5f3000000000000000000000000000000000b027f117583406916a8f139d47227bbea28502ed0df91cf0841345435376c944a587c3b4bd60f8ae0be7c7bad1c8199000000000000000000000000000000000e9a7b96136b26b0044b11288d35969c17146241aa529e581a8fcf000c33fcfff2dfe1e55c0fb63f6032d0b6b0cf81180000000000000000000000000000000002a442e740ee390d87ec657fc218b76adad7f6a766cbe8f34f4824ecd1587deb3706af77a95c1d5f8e79eab1dc482c45,240480, +000000000000000000000000000000000f54bcf1637d03854cc2b785e52bde25de7e45308048ed8ec0169069c2124871782bd9d26471014d039c9aa022e1a99d00000000000000000000000000000000106698139b096a5a79d43321ea64adb783011f04e5779625c9f77e5c390b46ef0d249387e978e64529bba2db8d7aef2f000000000000000000000000000000001668d5261a4ba37d79c76f44eae9ce2aa3e216c5fbf6cd2e90c6a73cebd8b59600303afce70de3e83a08c20de4609b100000000000000000000000000000000004b1b122cb55e688f8297913b84d466c6f3d99c09f4b039660238c8bcd0b7f6977851a6ea4b1deb01346db06d75180c142fe1e5b3c0245e5cfaa1ee8dd8ccc4ea8878ce2272d152fd8b24032297ac01800000000000000000000000000000000192a28dbc40d5ceee4d33b5c2778cacf8c3ed7d3227e7ea0d6fbaa7cd4a81134b63415f4f1960656b1fed15023ce3a4400000000000000000000000000000000138f296c45594a930b949756d0ae14dc9a720bb2bd9e93c7895268121a086a9d55c10135962a172c02da1eabfcb8caa20000000000000000000000000000000001605ef8182fa13a09a6b7661472296af2b0fdcfd7b051e7cf1d9e6d7c7f4ad9521d7732733399bfd5d09a088f25d215000000000000000000000000000000001928f2e5d47d7273e035114cbdeabaca724409a56056b4e95a4ca3b2222716b3a5368da3ed406d73f43e9571d1e04902253bdc5565b6ebc219a75ab74dc5ffd304c94e67160389f87111899ac07a71b70000000000000000000000000000000009b35f132a903579d82cae6a321c1ec7fb0281c3e82e9af05c3b2830ecb4a941da5b1637c1bf0fe9a39fcc9ceb0d09d8000000000000000000000000000000000eef9c0846064c866ae07b3709091b8bd48bb6b20f995b44fb49e030b5cb6d78b7f8201704b53697190a5e36e9a4541c000000000000000000000000000000000a98a5d0d5640d6399a3580036f0e5cd693a7cfaa26438a00767d5ffc0777b83c516316d9cd4597cf8601544038f4d9a000000000000000000000000000000000e59541068a62f105a0d26a5f79fa5fa8b41b2211f1fe674d84dd853663962d64a7f70e785b51ac3cc07267c73400fe6acbf64f93f6f85805517ddf0358ecfea1fd58a3666b8dd9d3773a28590fb8a13000000000000000000000000000000000157f58b1c7152a7f931bccd9a79073967ec28855a6d74fb8727f59c5e3728fbf07a5032dccb28eb8d8b24229f2dc1880000000000000000000000000000000019f41bbbb853edc1fe3ee82f901e613107dd4ba1d880284ee95a2c4cfb2220ec1408f8bff14defe59775136bc75b4a1f0000000000000000000000000000000015538789157505a0798aa36fdd171e0bb14bdac75339b35805807c18bf9175d877360748f97a8570754af0e28e89df660000000000000000000000000000000010500aaa99216aa979acd66c5b0cea2a6a973f1cd10c412e823c61cb897bce54d783a6c0acee22cf9052166a4bb5adb8d9d3f97893eb4f14f21f68110f612a444815fbf2f76b8399ba6045c8a44270df000000000000000000000000000000000439729e13e6a9b5baafdaac65783ce79a5972791610a333224e61104d15c746d7cf8350e619f0f72cb73635f6795c5f00000000000000000000000000000000092e3c976a4a5424b09e50e6513a9e1f427356ce161e742be31f0e589e9ff862460d41281f0bb2d27b1837a70a5938fc000000000000000000000000000000000e0e51e92ac3cabfd999cd72b67cfc488e150b11b18f9a31b1c2338fd4f2c58937521b5a107752c342e67666b99fc42500000000000000000000000000000000023d8884aa3f556e98e006960293230ac966ad18f3f715e6ab31a6bf0872c04e6f115fb1608cd87ffb369ff31012a11705fb554531f53b8cef8d93566df80878baa96f92bb54aec19445980b1a1f6c34000000000000000000000000000000000be33bc145611afdbadc636e9d7cb7e3a9c92c32f6944a2b7b5f44c248a0754c174e3286ad307fcdb2ea02a3578aa588000000000000000000000000000000000457de1fa8642d302065319b1d32009c64e7d941fb43d1b3cf455248664b1db516379df87aee05a651c132eab8aaccb5000000000000000000000000000000000a711f3bf1bda60ca49271e8a3143330cf924328d3ac6f7a802c15be1d7413e300f398274f338e6bfd0225cd8ba25fff000000000000000000000000000000000a786c5c7b4f1701e292aaad9b2e47bb883409aae0c44ae813ba48f401f4e2146ea0b1d85f2ce862b6ac9ad3015d4b14d79ba2c485f0aa0e35212fd7fecf970258903bd2427c4c8b97c2c425ee1190990000000000000000000000000000000007d03697e195a6b714fc9785b49e54e219694250cf5fe77553434eeced15422de3985f8c736996c1763d4b9248a7a7e00000000000000000000000000000000015841a70a168d2f356a8ad929e2d1433b782351f4833c51b50f3a1af48a85468c2ec02699550d21bd919203df73abeeb00000000000000000000000000000000170902520080c46faae2bf35de396d56921bd0279fc889f0187adbabb9ae52b849269d8097d5b3f331dd5a817f9b2ff40000000000000000000000000000000016846a000f037eaf5953b7c4b477e441ca4fa738895aa24dfb0ef01a4c8fc21a318d40a9424e151380084578ca413b3344c7017258bb979cc9bb8acbd3a3e62eac7aa152db46cd7398ef07edd031e4f60000000000000000000000000000000001a50509bfb12040c0271b231c566d13510e6ba84448e59685f5bfbf5b008fdc64cd5e9456beabd23ac011b071e3a5fc0000000000000000000000000000000014a964c9faf1752170ca40cff1b9b4fa17f8d2b56a4c4bd7ffabb65798771cd624ba61ee43160e70731fb9b07af8ecc2000000000000000000000000000000001822ceaae7bd0a734f57b67e4834cfb00a6b415459d81c7d380a2e5b5c795eb1b6d63ddffb1131cdfdf0d76852c75a70000000000000000000000000000000000c5a1575b30e5470151ba055f577a0ea49cff869614c50194829e53a3e1a95847fa387a0f45d537cabef3a5925e61c432583e821328ae90a7db16b20525228e8d915bc8d46a642cb0a06dfb64168cf1c0000000000000000000000000000000018cab86a0d70fa30b4df3e05a91eef57f6505cbe4bb7284de56d420ef3bf315be9249eedfae92561c643bac2c92301ee00000000000000000000000000000000098ca598ccdffa9bc9d464d51b46ed8a8f22a87ef408cfa45fa7f78ae2dcb9f861d9d6a571f6fa702a71e783ee3395cb000000000000000000000000000000000c073c0a323c3051c302c0558463a5c030539d74b440fdcb16b42ad5ec097e10c16bd9a651d149dd719fb1fb865420a9000000000000000000000000000000000164e622bfb8ecd5eaf691abad9db38ccc64ff0fa1784d26db8c8fbebc929bc6d4dd471321e01233d55fb4a9661780b5506f22d323a740553d6107e651c192c1dc6e0a0161a82351f125f08c77e53fdb000000000000000000000000000000000fa48147388181e8d0033004118848c50c6425f2e5f91945a17abcff4d11928d298c092d60184e75e67c7ddb9eaa8255000000000000000000000000000000000c535bc54df050c1ba8d858a346d3a644e03fe24873b7dc3e23518d44b06fcb3f52b4be6f11d3b66f0180a0a95dddf680000000000000000000000000000000015e279a2893c205dadc8e1cdebd9c85454cd4b5d7537f984c8f9d451f8316620279357e218fef87339f1728fa317fad5000000000000000000000000000000000316e343ba68c8a762f4c8f2a5c20f16abc4a7a8365556c1625df832219670619b6dc70727e9bd9a64ed491dc22cb9d57f1bc0e1ebff8f935330c35573f9fc3b900606da9cca9a36b425977af47c7ca60000000000000000000000000000000011dc72100cdf676e41f21015fa7c57897da8260609467ffd38c17868a4dcd2bd5d4d72e89cd0db2de83618222ea3b5cd0000000000000000000000000000000007e074f73287faf304f618478566b91c8e191b229ab40743081342e676be09c2523681cf7ca6f7a396f8589a4ae18a6d000000000000000000000000000000000ff753a16c16bf0dd1de9fa9316694214aea6f99b81f66b6bffd58837c00d7f5632ed5f8f4cdf32ec59c29241ed5e28b000000000000000000000000000000000851e26675814612bcfa639fe567633e1960578a0c8d2e6568418f633eebc109e6c8af97e77bb28ddd47c6bba8a7ba724429b85fae16200da6eb8f62e95e027c24aa6ee2a145f6ef225139f29aaca29c0000000000000000000000000000000009eb2f172db0fe9ac0332381d929fa200a97047f6e732570d23fe27f5ea3013fdc52fd0b5ee74a4387af44647b75f956000000000000000000000000000000001355f8e1cf45443855f2d62dba0fe45b2bfc4e0d06aa7aec7e4f7f9c4e25b33d9c46a01c224517bac9a1390a9806ed4f00000000000000000000000000000000179d47a62a5c847f47341b1ba58f2c3b073c5282f925f57efed1fc43db04185955075255e4e4f6c209757ddae59101dd000000000000000000000000000000000ef5f74d4b13754ceb3b468879f1a8befb8bbbdbb143eceabf2dc8e68fe6cc8e1ea4f3eca1b23a1175c9f5f5c4c20d3454a852baf21df9f4ec8d711a48e6ffb36be8c09c8c60eaa090876236b2eae37a0000000000000000000000000000000005b70a4d5b91b85971aef26b1521e12904b7ad224f25e31ec6ef59856cc702043a3eb975bf21dc8e4fc55171a3865bbd0000000000000000000000000000000007cf7c3e75a837545b53ca3e175a275dc6fe42fb88678aad45910d150ea9c6c94eba615429540348bb2ba8efacbb20e60000000000000000000000000000000002eacb469f5f8ee6c9f557a6ddcc854e955c5b9203b4ca5dd2e097d3e021479e13629863eb5ff17db46a17d3b0227f58000000000000000000000000000000000905e66f3a051b304b110a8682169fa749ba0de7763d3af7edc3e40f2d22ce7b6aa00cd06d2c82d74f3a9709d955f44e13814a3c6386b19f7b93c2c4e0eb1568e8bd3f0012a1ae1357b127c33808aa0400000000000000000000000000000000060ac9ce51426d360eff0d911d9f97a86494340bc5c5ba31ef146b55ad3633ec57a700f04b0cb9d4e91e13c2cc5e68a8000000000000000000000000000000000df205ed85e27c25ce27270384d7c3e58c4e0a9f214d74cddfbc7904eb3115e7bf204375df7558c3e65f7a81a942c5160000000000000000000000000000000007a220d42ca8906013479442d7204457b3ff37c9ee70d64f9f6858ba788b7fc13b71d33ad527c6fc673ad8940b0f01cc000000000000000000000000000000000ad481ef549de13b174d82fe88fa57b7e31ecd8999bcdb0c7a8735ab619a13b1e684b9473f0c59c734567cc08c76ecd6aba0fb0440b2461ef64af6ec5f15db381714fce1da6e03ca962cfc94bba26d74000000000000000000000000000000000366f604228e2dff2348a462c56e0043037d1b415ffaf155e72c559d185c6b0a0d125585d060f159a8cdad959af631f5000000000000000000000000000000000f69e829a0995914ac122299d4424b4e2e120fa4913939d2f18f9d1496e7255d00ff0829c20521ef47bb0dee06c28dab000000000000000000000000000000000a3efb4a376281a60f5246d8fc10bc23cbb9cb71037f8f57271a9b01f5e0340a562f9acf0e9a95b8c65ab7a5cd95520a0000000000000000000000000000000004a4ec86e2b04bcb35c7840d85cd1dfaa88e17ffb557ac591640ed8e563cac891793b92e349a7903c6c1f88d26a01c88c01749cac36dbbdba5662687fd1ea5391ef9d0bbd24e05bb5904a20fa6a1e11e000000000000000000000000000000000f5bcc27c243ef65dfbfc0de6d431706ab20d6cf6408ca989a2bc1c52b78ab63de6f58b70bfcaf6878a2746f249b6b160000000000000000000000000000000016a4c9e8ad0634e8afa8606a1a7bd1d8cc0815dfc6906b6e6446e0ceddba4a4a2df979d27cd07b8982a12550bc700fce00000000000000000000000000000000051f8d972362caf0a8a39045bb468112f2e73afa392079f8a4dc4c3a3cbb8dc224c21b6633a5ffbad08796ba2f8df44b000000000000000000000000000000001825aeffda04705ded9c702ba30d24b9fe8eb7cb106ee5d4e4ba029dcb57bc42c74e74e92ef8360cf130590b838645429680fbd6e6c7b1b14b000d3d18bf93242c74662ef108d711d85d8d442e415ffd,000000000000000000000000000000000d0ab61b29ddea1aee0ca4e81b5369f37cf45be383f64ba0b1a5a74b790d7264016ee671959444c94b8e6291c5158ea90000000000000000000000000000000000152bf3709c56b3add8e3396d17abcfebbcfeb230529ea8144d6a120a0a6aa83cb284e40ffb9fd9a96f8a2f7244212400000000000000000000000000000000041f516a7cb2a7137746d028b0739c79ffd8f7535f20ba3728ede32504fe058baaf684cc7677967aa46777818b1fb6630000000000000000000000000000000009f1035729c55cf6ee090983a54d8c0574bf96342901f471a2e5380f11f235a075b0e157c38c456b6eeeaa10b87d3afe,240480, +0000000000000000000000000000000011ada4731ae7df493e405383603a8d79ef77f3fd14fe7b8bd9d2afe068998cb712e84927d5e6ea6e94d7f10270fd193b0000000000000000000000000000000008a14eddf88826dc3be792a0c1f7395efdf91454cec7e26c89f6beda37b194b706dbdde8745129e821b6f4b4ea6118490000000000000000000000000000000011c29513e8a826e6b3eefaa20ad841605d04b813cca282fe02dca0f588b9a579b2195b0b080cb6d12c1a7881008117f8000000000000000000000000000000000689c67d05ca379367fec99439e3806f827218ffaae995bf38dd8e2919fb2e751f426525cc2c6ead3b9aff2e377fc99e1ddff10527bb64de6ee2e3ab4959ebef9e7a6964b7482f9fae396b2b9b0cff9e000000000000000000000000000000000dd683a8e4ad54b1a95826a3000750c6e3cb250ab5d6add63c21b182d736b220d917d4e70044ec7101c3bf8ac620e1dd000000000000000000000000000000000f3e411cc6800b304fda1373ffa60c7718e20bf3e2e5f9784a81b47e398888b366e1f04f48f5aa070a661b5e2148d4fa000000000000000000000000000000000b0f8d0b695e000158ba80881a9256ed9dda5a7f53b550bf3b5c67ab160060fcbf5ce07fe38253ce037abedf4c6f08d1000000000000000000000000000000000bb92d407c457e9ea7b9851770d2743758e162dc9cdff2dd54b8271c046f642729cd2f10576013adac84a46d38623b932943fa2957d267019309f4fe5b6725379d893dcc270ff7f35b3811ad1d8d12b100000000000000000000000000000000023e880685aa69b3480bf2b7f2aed1181e094322da9e79c9263d50a49ba4fca713740bdb55886fc81c81a51045b35139000000000000000000000000000000001707049fb8b7ad278be2949b9eae2e28bde9de1d9eb964eae582541c2d7a8afc4c1489624a0919047a167028b8c77e3c00000000000000000000000000000000062dbb2bfce2f67c32b87ec2fa01ebf7deddfcbeda2fcf0ef094b1be77b7411f657e745350b6d2da16fc83a96f6f20e500000000000000000000000000000000062daeba038c7bc379f56ac371745b91fdfd5b4cbbe50d9619bf1d077f3cde966f81f9b851ebd206f2609a780b6dbd681551a3c2d0391fd8dedade892e8e2171e652d2a9b52f2288451c55f77fac788a000000000000000000000000000000000b553826dd9e2252c9da74c2bf1bf850df3f9c37439859f93df3fbceb7cca4fd949dcaa7fff31c9e06f41e51ae0b30bc00000000000000000000000000000000187810711ea5911a437a62e2ca483983bf2535ff9301a1cfe1b4d41902ef689f8d86f817a2a7c77128e4ce1ef6b037d60000000000000000000000000000000010170cf5f2ce08211cfc41bf54cfaa16584f833f7b97b2f6bc436eecc56ef44463690ea1f5c8c2a8f69d93a25206282b0000000000000000000000000000000001e627a68dbab6b0d05c85e49b966a769461ec38c38fd94992839bd0d46e06410fa7a48d418d65a8285f7852e8af4b318eb2fa94a5c97c28d95008dd1fe60137b34c2e763292d1b86993c02790b8c91f0000000000000000000000000000000011ebe2edc3de58a57aa9ab4d6626d7b93235ed24efc3d75c1ecae376c00beffc5e89ec509d243f693d327f7a4551921f00000000000000000000000000000000088ca2fe0651e4d8f3958454640a58ea1cdd804bfd2700bb1bb8e26ac50f2d7fc8c292f94b0bccef5735c4548025735400000000000000000000000000000000154936de8932279cd39ae803a5d814864953f647a5334bad958222de765250e4bc847e02979689dc9cfe1993486b5750000000000000000000000000000000000c7ce07c9746c6d72dae11e243acbe12dc23423f870f3130b244eef34524d547fe0b2c4b704ecb6b2e6c32f5675ce67ff72ae1def6c988f9242bff0e683b8d2a5c1aecfd6ebb9442131ec5b5b825d0f600000000000000000000000000000000031ea855125d75321a2a86a93e72fb3869dede7531dbcc1cb07ea2a352f3c6cd913275d0d43ccc370f4539f668f205f50000000000000000000000000000000006c4cadb11361f164f5899c6b57c0c6d8af365d902f4575c9d2d14dfd880501ce9ce218544b44bf07f0f04ed68e8f315000000000000000000000000000000000131332638026fd25b1a849c984f9dedd71e64fb52a61968666ba80238673077ac00b9e09817426ceac8c308f475303c000000000000000000000000000000000c7634af796e7aea4d4d83c9972fc822dad951d2473210ad82706ae0aa023ea85c1c467bdda68881094ad2a4f54cb33f331451748146f0564ab0d91b09db87e8a6ba8b14f8329bc041911616195f9fc0000000000000000000000000000000000fcdbf0083065e13deee2020bb6e47cb9e482df3768ce569f1f7c0e1c6083c97d9f08444e67857c2dce40e4a7b8d50cf00000000000000000000000000000000010f246e8ffccc2e752049f638617e122773a6f10220cdcc0603d24f1a94ca7c100f8ee2d9bc7c0a931fa0385eee456f000000000000000000000000000000000f8b68941df75cac3d4b6b3bee43fb357c8f4e56309d8509fdc62620a085d7ee58f52c7dff28525a449cabfd3b7ab3dc00000000000000000000000000000000019f934ef0c7c40786b073d38cb3e4623544cad59cb63440d4a6e76944d491f6b982e3a5e84124996634687d4618418316d298bf591bd927aee24a37c5ba508c3bc121f5150fcd1a70c1f27a79da7d73000000000000000000000000000000000c0208c1f3653fb3a5e2acbbb42f2598b22db1a714d616ee6bb501c3338e80db34d517c7086d43ddc77e0134dc5a4f290000000000000000000000000000000000a528245342e44e36f8e02e7259749e63ecfb38cb0609075e871701f2b3bb0765277b78d28cc3ecb7aa8c9e3b27eaf10000000000000000000000000000000010446583a905864064400f9ef168a122d179d46a058525c9be8a65a5d2ac5e967d51185d4964f81a5571123717210d050000000000000000000000000000000017da91a1d0358271b11a0aa524341ba1ee8c31bed15efc4c9183d60c6e1842ec4383070a09914fda991a63d55efa8f2156be810c3fa86e35bc935fc2b27971c9c41d03c8ab7b6c8869db90b6e0986ef400000000000000000000000000000000176c64efbfc9958b9c8e71b55e9fdf525d4e5a0265ff01ba95bcd5c6093bd063726f8e277d00b138fa4d8c8f80afc4e200000000000000000000000000000000183eaa6c3c605828852ab5e8a9432bcb87411dd18d574cc2491f1a280e7a267ff9ccc80b06c22e95107a72f22ba2fafc0000000000000000000000000000000013319d3a8564ffcd6fc7accdded740127ef205e8299b390d21e96b2609cbb463569c878f36191d43927868b06dcb912b0000000000000000000000000000000000fbde0ad8e89f5458007ef6ba0f01d0aba04217e06745a5571eedaf544443150f59117b56937f533b4974e5d57c41cbaea4445926775a6baffb4dbeb249dfe3b3e0c29f2a579927f540d8f6451553ef000000000000000000000000000000000c044a5116e175ca1d1ae59d400de24e4f47132251b4b3dccdf458623c36b4d3d83abc644a2247ac4d0e3f195d12e7b000000000000000000000000000000000048dff6bf65f158b19b992167ff8adb5c858a154bd68bf0c84e41351bf47a8f870cc735d1be5d9afc62bbcda2fcdb1c20000000000000000000000000000000008c5539746d2610eea22e79b3fe5b33a47fd3bf9991d34c6f9d824a46458480b735c0051d7b4e4909fdb1f2a1a4e4b3a000000000000000000000000000000001936558ac97acd903a29d07c4aea399227ea13fd6dea820813c5519412c157e1a477fcfbab60a787c6b3834eac4522889ee0e58d08779add74b68dd75e82df172b719cb5a772b0bbb34d3401b9f212ea0000000000000000000000000000000017d978d60fc89b0429c1a6424231fe9274cedad5d78d9c4ac5aa2dd5e70e8238a0bb1904bb4b6ee5de5cd1ac514c62a8000000000000000000000000000000000d4ce85a95dbc40f405f4e7ebf9121cdcd22766737c39618ad0fb3e10a6e53be1faceaa96073b2a877ab808483ec9b6f0000000000000000000000000000000016c61599ae4da787fa6db233fc28f5c56f7133d403901800ab5fa19d058fb27ecb34ca2e56ffa7628ed004c9e62092700000000000000000000000000000000001e64e4adfdafbb423b1b9f8973738c690713911f68f658d234e57dc35b9554e0f7ba345dd7920b429a12b9c74775222773d07cb9d20744a2c3ac88082a8d6606acdc892666753793a2b8bb81116cc6d000000000000000000000000000000000908ebe27a1bdf0b9e56325c00ea3814527005793ea97eafec541c01cf2d7c909d2521a5fd475589a31e297cecfd5e7000000000000000000000000000000000017e3c40c60cd369ce5a90f6c4aff14896cf73fe06432e71940bd8086e36c2353d6bf9dd414bcf92889887e2d49fbbf5000000000000000000000000000000000ded856e5b2b139487b3816351584f06582a933af2bd4573a89aab0a41af01ec1cb928a7d8035228302032d399bc7caa000000000000000000000000000000000833b77c5d5c98ad95a144c0f167fd3bd62b03f4ad721561ed1d84c7137dcb19521f781bdd3ddc22afdd52c75146e101f6bb1445e9146b117bd0c95b009fba670a5391874dd314cefc884bdb0a4eba680000000000000000000000000000000005c6f28c5ebd981fff3aacd70eb18f134bffdc8507d1a3aa153e5787b68fba7f4a94c43045d2676aaa992754783ae87800000000000000000000000000000000148ff39e8062bd488accfead42a684f781c4ee579af6204b5b8dabad9022b029139b1f3670fc270710ced9a53253850c000000000000000000000000000000000ff50eca1a92f123e2534b3289f37ffd5d4e05f7678017ac20e35c2deca054dbe376c5529cddb5e58973f5c60914f251000000000000000000000000000000000b58298ba9496fe32891f4c1cff25395ac5a447205cedaadda4dcb929260ee55781916ef5e4e39793fa2831142111226d4158de4e23d793ba77c24a70f0ad07314927fff34361b0d74b25e8922512d7a00000000000000000000000000000000184d156f881f7d10d2f196b7599db85ee826c9c95383978ed68918756f642a2ed1c951503251b0778dcc39598d79fc8a000000000000000000000000000000000952168761380e8fc90a4966e94b8d2b88a784f6e607c99d9af1aa902506f59d6879153339fdb7b8acda178b9bce4ef90000000000000000000000000000000009997621d4e17c76b7798ef2f99d3c0a7519cce278cf718789cd8227b2b1459af7fbbc93078aa0aa361167b1d1c9363600000000000000000000000000000000005369eb3a77d2e26f9907a2d930f39dbb87634346cf10525733aac8ea10eb918d4043d2a05ff8e80b9c69a670e17f15c629ef41d5a2ce49fd81930406f19e760a47074e159ce372dd67e7ea46ad706b0000000000000000000000000000000019bdb390c66f7d28cfaa91bcb34c5c55bf93a9f2345ea396f18ed33ff2221a39cf68c5514fe091f7882e82470efb1fee0000000000000000000000000000000002d0b48d2c0377b0dffca247b7625f9901f86e2161626b4154bc25d6c643a48e9addd260298bedaa80e42caa5b9fc5b10000000000000000000000000000000018a2b0a760652e546eeb42e857ca48f59741eed91822c17692e9c41358b213c82537c9c6898713a13a241cca627a7dc400000000000000000000000000000000079c02f41fca45a56d9d8e305141b4fe8f98d102197e7864065d342e6b07f65b62632e0c12660f37de4d698c0df3d0f3c718651715ab786b4855092ed21be41b499b7824d0bcf68ad31b31ee4cb730d5000000000000000000000000000000000c0448fd4ebe9b5615653336fe0a618fa281b0fd7d72a8f956a5fde84f7d356b6be853bf823436bc0b61a603636db9ef000000000000000000000000000000000dc4f2b4d810c4290e263098576cac393fce137cc901b3be23507cecbda7d86d18022cf8e1a7df4b1298520ae5c9314c000000000000000000000000000000000a39413967b558dd8a6b2bed972687d984fb9abd0662a266680f8c90f1897e2aca1ba37b41d7d3fd47406bc5fa3c5b7f0000000000000000000000000000000000550fcbe5bb75afdd8d5f387798a8e83a8dbb6da4918c24eb2e5d2d8acd3512f6649a4ac9c8d3e6794e6f4f8a87687bc685a2872c4980518fe60c61e2276ef53c007166f7eceb355b4cd533f42c00b7,000000000000000000000000000000001654e242002aafa89c6fdb9e8fe2c197ad2f8aad11868568dd39d68ca35919f94308a80303655bc83fd130de6f9723a900000000000000000000000000000000062b5a064840a5a28b4991ae949f9508586447ad5e8c463593503c0e5857c5233b7ce7ac03e555c2675f2e320e8cee6a0000000000000000000000000000000017d65fbd7caa69629f66be8b201f53baee5ef2957a3c04fe384ae82959105342b52483eba6bcc1442763c677f515f6cf0000000000000000000000000000000002ef8f8ed1114cc9d299e59003c61d62edf8971d65b1b621779bd7b270c4123eb629f56dfa2e2723501588a0caf1847c,240480, +000000000000000000000000000000001392409b92282bccbdaa0268e1173e60911754eb3cdc28a52e93f4d82ec99026f314dfdc59b39a4f988100f9c30cbd1e0000000000000000000000000000000016b3c555d5c196551ba715c6c334a668bcae80f5a17f038038d35dce34843f79968a90e2102f0faa22a93d3240b58d490000000000000000000000000000000002daf83727fdf45dcc1a15adf47de3f8a1724cf4d34116f52106a9e6b22dc24a288e89b940cc57e5a6bb87ee70f680a5000000000000000000000000000000000446009fa3555e4a056a820efa7da52117c15eb105af57985d8e9b33b0b22fde6aef9bad30480c2b8c1246519795f61fc067ecd54e9ef59996493f846ecca63bbd7ec28da586f0b8d41bfdc6d97a35cb00000000000000000000000000000000000372ead514d53007690843484c966361661816e0d3949b868176d7a9bea42064f49113a74f2572a6dca7afa0642fa5000000000000000000000000000000001199d3ea66fad87074e62a0b77d3fb962db17dd948f30c38f5beb0e44e1cd11d9172b878128e9a64a08394f13cd786f60000000000000000000000000000000018b7db157bb326ee2f72d4df2b1e0ddf0a90401ccfca1d4ffd6379c62acf5d6e4176a23ded2f81653038d56d848b4fbb000000000000000000000000000000000a932cc9740812c8bde33b68d94220690e0f55618b7e51d3e3fc29d0cb9a8d42b8f8e1efbba5984c3c1007c9a80fae408b5112baca5e0f2bfb885c5041189612918d203a117d886bcb3b27df7e64d17d0000000000000000000000000000000015798d10386f6d24caed3859875be5fb1a43ac753f725f28da6b3583bd9c0e404d36265c2305d7d194e2ad84bfd2bafe000000000000000000000000000000000ef2ea5f3b6e03e3c9693d6db60019f2efa4ea586bdb7623f03bd035c603e8996ef2ea7cf745aa31f60679ca04f93875000000000000000000000000000000001792a66785a3087a80c4b8652c1e4db8f602cf75c1a6955f480a977f92ea262965dad84061f6045177c831dc4a3bf8400000000000000000000000000000000006ea3862318974d6347639ec0d70afe748f4edf32b9e437fd98f38eaf72168a153cac180c2d67bac8a358e3a4d57a2b32db7ad39ec8129e9e9206bd46cec6a8ad3362ade1beaa97befe148f6c67a9c2b00000000000000000000000000000000000974da7500df70d888d5876e7c61bfffcdf830b49bdd40edf65a2ff476e9add35eaf9451a2166e9781805192ffd7ac000000000000000000000000000000000cb2e7152b5b40758b18caea356dd8e095f400282881207c4b79d10d741756e526be261b98b726d5cefb668dcf73a0a00000000000000000000000000000000014aeebb995d464f4d77bbb72f15d9078936b5ab68eb8022bdd97d050576dbe46e6010eb72250c8ccf2a59138efb38f9d000000000000000000000000000000000cf7162768e8eb50e21d3c0a076c7bac4920c70f334336037fb40e57e0efa91eb025356ac3f0988a6b127408a02eb53fe2400a11d9a67041824b97a96f0ea9da8848e7990373655d76e8bd4eb84df5dc000000000000000000000000000000000b1d6214796b4775c2b50e634a549ed104e6ebc0e032967b17eece6cf88c93aac23059f263faf3c3f38463270320135c0000000000000000000000000000000013ffa3894a36226664ff53ba9256d39c6312303f5cbda6847b4f68c56134b7d731e74bd711014fe374f909a081a7d02a000000000000000000000000000000000ae4590cdcb1367392635d0f8dc6b9557abd16290fd1abca6da354646d8585a7c9432978dc616e5fc38cd71d55f139c200000000000000000000000000000000124a7b5574ef52359b4beabcc56d3286db8c8fe4ca4718f75da28d89a8a95efb878c18b48360dbcb6fb50a9f18f0d559aa2d17c409ade92566ddb3913806723d41067540a36a9c283bdacb273c5b258a00000000000000000000000000000000148ab0e847ecac963f0156da025dbc52e765cd8827fd55ba2969da6775649529226ab13ab8537ad0b89e8f1ebc8648ea000000000000000000000000000000001395b1adb6a56b91c3621a4ac5886a7b13ec00f1c74d5317eb74a766eae655e09e269ec48cdf740abc38f4d6fe52dd0f000000000000000000000000000000000f70f77f07ef2909033665bc05cfeea7df6ed55f2f0b1b87d9f247b6c07c7e22f516840efe68005c3953a2702573a9b400000000000000000000000000000000166a334a711416cab180cc498308487b281711f2d1b832c410ebb4c591af54b154fc8c8d7ac9a49a241f7a3840acbc75e5e3d21862b64e09a0893ece646de60cd66aa483662125ffabc46cc52f1cdefa0000000000000000000000000000000008c19bcbdc2ef26a30dd88f3e35dc7fbb3c81c0224cbcd6b12c90883f3973bd7089636f997e5f213fbdcb79514c551c600000000000000000000000000000000058620cba8ed5b738167e809cf71392aadfe8f384a4cf397d10f674cfa914e9e02bb1518e42f16806214fec52d880f6100000000000000000000000000000000048ac1120d26e4173bb33a58c0ce86329cdbe9df6a6f268c8d5ee4f1d6110f9d81cd50c46256198a2462d50be3e781270000000000000000000000000000000010af13ba791d554720f5075d46d03b55c0c1dccd679cef5a7d439ae868d3ff2780cc3ab151feb72b8b92905a205e630449510ab1b7850badf58cacad67fe47135f6524f0d160f3013e8ff1c881e469e40000000000000000000000000000000005c30a126c94b87c54270d0f23a486c3b36a8b491bbd805ae0d5f2bea818a87ff5aaed2d5e6317b786ab5a23f1cb48da000000000000000000000000000000000eb2d4663eca7f8433f10e84984781a57fffcb8f9535518721521ddfc7a4958778915ea3c57bef399a453b8ebc10befb00000000000000000000000000000000161947f57d97a858e5b3e918dbb22dbf28629e51e81335a9bf105d0fd660ef80087c8d69d8db9841cc69fbb5e7f81487000000000000000000000000000000000c52b6a559928fe4ad984a0569c081f3f71eed3d5b0d3c14d1a23afa45594e0fbd94143348390bee178720fc603145ab713aa69664a8c721cefa7d6dd3fe9f92432b4d350621d5297805fcabb21ff8c600000000000000000000000000000000071aa47d392e1a7787b37c52acedbb4632d5549fc11b79919bab7d22f1bbf1c3a239df622b8824b07f6e35e627283b8500000000000000000000000000000000198e72e05388021919dfc1b2a58ca72bf7655cc6c9b62abe3b45cc782ccfd4a2334780e451b8a6b7c311887036813fe4000000000000000000000000000000000e20cbedbafd96c42612e146debae48c7fab4846b20ad0848c4c42c6aa0603e72f94dfc938ed9e3a9886d221ccbdef70000000000000000000000000000000000c861d1878e63e313e672bebdadd3fdbb691cff5fecbc24da895febce2eef0a3c774a8a9d751498e4fc8e2b71daeb40dc040d8bf0a787346560fa3b100b2dd9adb3f7ee716b8103abdd9609363345ae40000000000000000000000000000000005f7cd2205fa2e17fb9896efe3fbe110e1fa59db1ae5f8d6b5f4510abb4da867933d4fe3caaadc4457dcbb35f1b9c62b00000000000000000000000000000000126f2ef6022a7211fa865c1dbdd5b84d96cddff424b06647acc462408f2d31f34ce898d76e1e124db7c39e08dab0bff6000000000000000000000000000000000987f916ad6f718695f3c40703c59ca93eba38931b45d7c33c64c9f75556f075b744dfff8a5f21489b3db6c3846ba09e0000000000000000000000000000000013011b8c72f3853738e22957f742b05ec428ab0da28901800f787b7c3678449acd0359fee93c40c69623aa4acfc0a81017b811aeac4fb7d91abc655f8a4392176f9060346073c957ef903e25d10935a00000000000000000000000000000000014b88c0586fa18333ab11a79acab8e12c6257f82a4ed16d929768a60a3a5d780a22101c32ea9b0099aa2816f18a0351a000000000000000000000000000000000de0fde69efd2cea7ae08d6d2443883002e0b4e11da253222429f6ecc67ba8d282eee84d7f46e0ad00b039a2c2ad226f000000000000000000000000000000000aedfa0a5a8b7577dcc1094469233f8b07e6fc32af26841894d498d70c6a9a046ad636086def948d21e39833c5b6c5a70000000000000000000000000000000010ec6aa0efba4995582585bb67f997f60741648156324696312d17656baf6aeb3e2db0d1a272912fab2fe81d139e971cbd1f096026159218836a46b9801a4f0c43189324d20220aca777b826eaf2575200000000000000000000000000000000004a847c06abc8ae7ce6e6ff0ab856889dd3e9697a75e3cd4d2af9e06d4c2fc48c0562289348ff52f4d9855ad03d83aa00000000000000000000000000000000075673bc79bafa9a64de6bb0e9dd9fa29cdc9c82e90a7348593eec673cbbf22b1eca436ecf767d45852ed888a3f23949000000000000000000000000000000000f3f8543d1e667404b4564dddba4d7c11d13881fcd8ad774c8eab8fc599f55147c353cd6e163cd7b9d5da55ebc13c2e800000000000000000000000000000000069edec7e7d26962d88a89dfad213daa36046bb2851e5d67adbaa227220f29f83ea67cd3747e6724f148dac28308604cf221dedfc21098ff9a9507e493d0fdb1efa6029fcdab23a016515078c76f7627000000000000000000000000000000000c945e83822896974116663d3e2769f3df5a70d55b8392c1f6966e330951f3cc5688742d4588648a6988b928b9fe00100000000000000000000000000000000003e94b7ff7c71d633ce69bb44d0ba1bfc7c27a5ee618e703aef81a45ad61771a2fa8e3dadddf7c8038f1f65ad7513801000000000000000000000000000000001727d768c1b51066d2af87a9da3e24ea2a75b0f75b8ece70727f9f54ab77d841e7ae01c9c0760f4186d02a28d6f8ddfb0000000000000000000000000000000000a273f9395cd49b646e90fd2526d5c93fd46c7366b715546529c9edf5cb3d274c9947c21a03add3e7b20612636a6745ba5b30d1397bf28100f108b84e05107ddd6cae2e82f1973ce187e8c3a7d02f3e000000000000000000000000000000000c996c16a16879bd3194ac366bbd11b5863123ce6fdabeafe56407600e5d49c92ba68ac1256e1515dc9256de14ac26de0000000000000000000000000000000018c584d8a4f14900b2fee70b50b700199ec2372b731dd1380f42ec7fd3d01f0c9a007554059b85946c1c4f4e2fc504ad00000000000000000000000000000000073d6c7d671762e5398e4c9d57f6b68c3d97dfe0d01783f124256fac236f03b774db58b79cb4d5558e1ebf18bb9e19680000000000000000000000000000000008eb2b95e17fdda916b08ff2819cecd2eb031f41c8299b308339b7d9836382ced75e8eb1514a70356882d3a43227a9bc19aadc83d1db9140af303c0492d2b9bb9e2b53ddb62cd2132bdf8ef62aaed683000000000000000000000000000000001029fc28cd502caf3ea3619f6fd04bf457e6a452b5cad680ec2d4f8222a5ac2daa92b880bda76016973494e605ab28c60000000000000000000000000000000002c672c7571b5d8e99de6e47e0a2eb71c6d9bd12baf2b083e6f88598b32c4644d1486aef582c5936e622058bb141db1700000000000000000000000000000000033cda383a77d5b3adbb0809e834993c56717f81f8c66ad2d97f2b298d5a46f7b29a74d35da09271b7053a05af096393000000000000000000000000000000000132da041c6e3e1d68bbd2223f8531eabde8e180b36b2cd0ed4fca248f255cf3eeccdc5f61e1c581ce54edcfb2b73e0787eb6fc40b00246910626ab66bfbac96ea09242d1d70496466e4d681942050700000000000000000000000000000000009721f22bc49f68d703a4dfccc3bae791caaf0d73892bafa6e9da465ddaf0fb1a069ffdd55306acff2407da64c1c5a0200000000000000000000000000000000056c0a4804a19aeaf1b4fe52064e43de8e5d41a8d77de054e2cfdff078eaf468d123d7317818d1bad1bf3469c0070b680000000000000000000000000000000007f1f318aed043d9ad7bdd53eb6a8c3167240fca75925b04795210700463c93a66ed64851195df1bafbbe4227d7db5ff0000000000000000000000000000000007b8945e258311e7672e842b91b540fec9ef4a79296956a5cba3749c0ad95ed83d7b0b48384ffb3188459e997b86695d3bb5926f36808c0024ea7388998b4cc8c6c48d32917f6456b39d514143c6eded,00000000000000000000000000000000086a1ab4c19c27f70aa422e8292752c50b365d6fe3eba21e8f2ed51f283df0446020834ad27c18b5c7285d1156049bef0000000000000000000000000000000007288f40fde69bd350ce1f4d0f68e645f42de319cc032250b76fe4fa305341e244e5b2366751d5311105e3ccd30e701c0000000000000000000000000000000011d0c487c4eceaeac009b694931f8eafaf8eecd6028f14a4de33d2940bbb747025eecd509564721b50b7186910f81949000000000000000000000000000000000366f0c901fb859b4bae006fbcc9ec7e456eedc7366c899f68090fbd457c37b03ab99ae982872c7888b65c1a056c134c,240480, +00000000000000000000000000000000072f3f03bb09ca30239dd8302b05e0d9dc4e43ea33e865864a82578c35eafcf6868bf0cd9431b92b76f00990b780ffa400000000000000000000000000000000170b76cfb7944ea5ea055aeedaface3e8f0fa4d0ff657fb9d5311f3af6e736da84a5e2bef5188e20f76fb42591267fd9000000000000000000000000000000000d85300009165a8da9cb8e590f7f8d372e4264df150b1551185c80e49dbedaaf872ef69c5763fc3713d0c087c89f21050000000000000000000000000000000003ba59b682174ee61630df95c8e2b1c48ffc8f7f8508c21f3bbe8f7bb3266521fcc06c8f90fe5126d872707872db6d59f44b0204792359895b448bfe6ffaedc14d54a6d72be7a49718c0a933807a399d0000000000000000000000000000000004e8f16480c2f080a13b9f2b66e6480132d76c4ef76e8bac995a8e33280073ed5610865260e154b32f75f527d89620b3000000000000000000000000000000000f9ca48d732a8055d22fbebf3d2bc1e1c9c815c184f594ad2337731709317ea6a205478ba05ee9271d35a19dcad4db5b00000000000000000000000000000000078013b9290284e7ad528a1bb9a2a64b3ef43964c7226ddff8ca16ab17b4a2e8a2a7d921ba924a718587954f586a954800000000000000000000000000000000004aa76bb1122116cc0c04d65265d8652f08b411632a732a9e66d7932801b77c4ad398d582e446968f7f4966e9167894de25977e7426cd5652559626ff8b195ab7ec679de987a6a22a6a0e366759dea000000000000000000000000000000000145de5d101498bfc7c57830eea2931663ca1165ec85b77654c866b04ba6a28bfe710c1aac9876a68cc6ca119708eaf0500000000000000000000000000000000096f9df9d5723e8379f2d09c76a3fd059be47d2c2ed8905d333b2464f72153c5f50b6345980626358839ac691c26c967000000000000000000000000000000001788ffa765c19758da6eb6c38e793190c64d4a7b116576f6827fc090b0f65304988f6a95cf4397f82b7691fc43960ee8000000000000000000000000000000000746e040d7aafdb06a31ba3d7b590dd28f0678badc261a93dc7bd9a605047ec67ba86b2b6dd72637a449872674d6b5982e7ae497b44f531fe203a599622954804c06d5348dc17eb1537e750006584b21000000000000000000000000000000000d8f3cfe1cbd2629f3899313cff16ca3d8f964ec1cc0508341936a7b3b49240db1116b2c3de28f9bc45cdfacdb5fd98c000000000000000000000000000000000fa642ed31293e44211b34bb28bd5b389ae6d0510cdab46c89756f31795506fccbdacafdff21b0127e80557e5ba9afdd000000000000000000000000000000000715a8951cb358b0d8cc63377799a9a61ecc85dac795d726fe60e429d492c9ca843be2a2633c17f830f199335e5d7741000000000000000000000000000000000b88a23fdac7d35fc135b45d7565854bf010a75f072b32c57ca4d0979c111aadd84c71df6792dbdc8e975ecd46a15df2e073adfb5ab96730c53015a4ab6210a35a37b2331ff5123e00798c33e040a913000000000000000000000000000000001171be5820b5a19c045abea399f2b8ab9905d2aa367c6c8c0f84eac132d26150b759a9c029414f1c8f7e4880214446c200000000000000000000000000000000147f0877321f2709183f0b617a7c5ce898db508a3ced4148cc9f7af011fe8040e90885ce817aa956d9f5d19dd968f6220000000000000000000000000000000000acb005c11481b214a17e3cca02c2af266e4c8cd928e3c4e221d866e9f296a2e913bf34c4e051c7503a5e4e7cd7449900000000000000000000000000000000125f45d0af1c010cdf8438bff0f406007853e566fa646df40a581f65496197755eeebaf4f0f77e1e936f399dc4c6c020e6e752d40d411f1ee6e67f48109c9a059226b446601047a2189ab815a3fe13c40000000000000000000000000000000019cce3f872af5cc515ac4cd7825a5318ead5b464d50349909a70b415a8950206974ee0d4203f208d8e6d14690158f5720000000000000000000000000000000002e08e8accede11afe3e2d085f35c08d7d414c26a9caa992d5a090a43c9b0c0cc1471f3693f9d342a973da65189c888b0000000000000000000000000000000008a984ad2ca60c492cff2e95d541d71e33b269b10d3df107c0513dad5af511c51806068da6cc7226df1cf5e5a2fbe707000000000000000000000000000000000fcd3ad75bb0a5c046cf83be3d973bb3685bc717d7b8262fb8205935db6e632472496907f7c965fc6b52042ce69999f9e657fda33cf4ed1aa89dbc19d58fbe3043acb5795dfb8c0cb97620f16f8f24350000000000000000000000000000000014ccaf7594d8ff6157f9439ba63480d3d07f44e62a86caaea510d0ec456cd8c6c4b42cf9e38713213eb4942ed45df2ca0000000000000000000000000000000015c2061c532cda006addd2fd6ebbae458197d55fb336f75ca7decc05dc6d421a65495b71ed11874aaf24a0ec13a7c65000000000000000000000000000000000101f953aed7f23b5b6208032f05b818e0147079b7764aa3134dd9e4a316bbef0309ac378ca3cff3bdeab9ca56cb78e60000000000000000000000000000000000c76a2bc721a4d3ead95af79ec24be9b7624bc80d7debc07e388e52ec621082b9a69f48d157b168af4aa73629697f784c73458e18d6f832f362dec7c49140e6523ead045131a1b719b0c836c1ef13a79000000000000000000000000000000000761832bb5b530b80c668234ab5996bdc225c0c696ea07dcc61c330320404827ada9d58d658e230fcb39a96b339b830e0000000000000000000000000000000001198b85418421d96ebfbf436193b411a3a89c206d006291bd23254ed5fe12ccdad15725a34d962005c0ae60e202bb86000000000000000000000000000000000c1d7ab83b1d2ad57a407e248492773a357c06b83c16c6ce1490e84bc4a3cbae395f160181d2bcca3edc34b764754ab0000000000000000000000000000000000f1e9f0cf96d7671763739b6c37fd442f0e816c49d9c8e001d322397e9d6741dbf8769ef9eb83d08ab024294e279a02838cb0a2b191f538b30187dc730a8c665bbfce8186883500baaa6c3242a0d147400000000000000000000000000000000063049bc3282934e29f3bb3dee432bdad6193a5d2247270e88887cac565f4b986e1b3b2af5387cfca64f0d50bc0ee1640000000000000000000000000000000019f0f05fc7f8bf2f0b8ed375690b53b6dafd0a07c49fa55d36e040798334700a3aafc4995bb90de9c4dc0e077ee18b58000000000000000000000000000000000fbe702d148609dc8feb3ac11c5eac8e32a2f7221aa135cc33a585e9f4c97afa1658d8962fd96e26e0c4c1d5108229ef00000000000000000000000000000000061fe418d3b440e84728091a4996119b515118900f54a6f2da2ad5592f48ebc17bba50b59ecf435de3cb892a123ae9d18a27de64d41d13ab67c1f7b1a7390ab4dbba7d219dfeb31255f9401d5b3c62f80000000000000000000000000000000011e8ecf1e341f0146c59a79a8428bb01d2399d3f87d90d057f63e6cb9837432154d17975f70df175a016735caf85120a0000000000000000000000000000000002a5bd53e4f4c5b9682e1af1f7e09dd305e7342d1688f62885b5e59f173a9fc731cec481559ad693030004a5fbd90a9d000000000000000000000000000000000f9601f95e12bf05c35deb204558d44a60fd630c05f4060b7bd9ff943946e8eab507422afe00a3e7706b8ed013f712c20000000000000000000000000000000003bf6fecc0c7414a69c2b48e2c16e88d988ea8ae9d8b59017ecb89394732a20e4321cb5e4fb071aec7d2736220a4553780030798960729d63db70b8bc3c0030e80d9b8ae766e3330128557e6c34442f6000000000000000000000000000000000549f6464b657eac28f838c6a8bcfcb7a189d6b3b9712e19c1a23503ac209da5f2ad4df83acd505b0231f00eb88515c70000000000000000000000000000000001bf4a46dfdd70542e9d8cd6d6215174cba28f9adbff31c02482ca38205cb4afa2f7fd65ecf57b39e4ee5cee320e33800000000000000000000000000000000012d04a693d565f96566b7c313c47d272fef0ecc828493b0841d58f6bf690a77cb72824a656442e288460ecca7cf05504000000000000000000000000000000000b33eefd5df8b098e6505cbe655a483ab5c6e417a4ed55420beab95e8614c8538dca9296a7848d6aa0495a173df6d0b80d32b6969af54dd345f42320ea96def3c6f4dfd4e22a82686b7a3c57a0df5250000000000000000000000000000000000fdd9702ed88aa857254c3ba50b484bfc324e583659c57055e4b09eb1662af2f70b547a1eec139193a0d3c75b565d3b200000000000000000000000000000000193df0fbc5f24065008b5e98c4c4bf9f1e743a6ee60c3700ae4a9108639e540384eaf1f9d7a60b8b6a5d79e1f34949f50000000000000000000000000000000001022f8a254d17e448cadfad35b7a54dd2fb319c8f9ba219874bd8280a5077301ff4332d731a75646cd93bbf31331154000000000000000000000000000000000ca1eb350844ddd0a65a4ad56e1a96821de2c6633a4a45be976577c223e367853e2b1ecf2cc40b8595ba5591ae8e40f3969848f1b8b36bd28967b762168edb451322e2f0c4b99b7f9112c9a66093fb3f0000000000000000000000000000000001f9cda056a0f8803be581634562e975223b5311f4752b189cb6bd6df1ca5e3824bbd2889b9b93da59e4f08d482734240000000000000000000000000000000009f43c25de25c5d76ee1a03691aa434de6a063bb3a1133b045797a279346fc938dd2636abf0c4bbcb528c9c28d3105c40000000000000000000000000000000012afc29245da8bcd3c0d96c4ee61617cd9ecf42a47c2ee822003af26aeb4e4de8e432ffb6b2d8241090b814401a8676100000000000000000000000000000000053edfd98742dc70d510f1836fcffa6a3ba9ffd4904c7f5559b48e49dd21071401362d0b39bc0d786b7ee2e84a76af0d957ee08a513c5e22bbec04722575a9b4f3a1343db0ae5beef4e66fbbe1ac90440000000000000000000000000000000001dc3f016ea1a74ae50c21c1955ca1eb4a911026a1e72b316c7bbdc708caef63f0c1efecbecce8901d65bbfcaae429da0000000000000000000000000000000016ce9301888808323c9baf6402d7073fb85ebcd389334cc69d7947e345748ee44b2d6aab3ef818beb21b54a19ae4f5b5000000000000000000000000000000000c49817753eb6459cdb4bc737d3710b5f044bc544c8d92c8ef138ec9d83889664267e1a5691f4bc3fa235ecca2a973a500000000000000000000000000000000074a8450e35f1da18e6de05960e21b7059ece8972c36f000bba9e24488730a44ce3ce200c437e06703addb3b442a790a8e0cf0f590f77d13819001916d2c58a654d0b9d3c47c842f2d649cb2570dc0d5000000000000000000000000000000000bc1f2e9af093ae8235c93af098e692e697ea0ab4c8f53019a6e950f7072b56d5eef6b3237710f1dd1cd1970668d06d0000000000000000000000000000000000d9a63f7a13ff9755c6a3832e3c4c852919514523092367fab7886cac317e564d57fb4042ef40e696edce868e697c45700000000000000000000000000000000129a30657466460db13575dca367105c27d631eead330319b084adfac591f5b3b94988925d778e6d4645d1d2816baad00000000000000000000000000000000005ad64d6e761a9a301589547929f4952ccbfead278cbf6658255a075966340f185d5f356679fb02ff2197468ed7de19a71a8c2a479dec43d644ec4113142e666bcefd6d729d4faccbc147effa836ddab00000000000000000000000000000000077d1e5b35c224e2cdc849c02e800c0b80d1c19f3d74d9eec34c40f56bbdb9e2b5d2ef274991dca843755f91a50826fd0000000000000000000000000000000014f3b653e0df0c608b75dee3496a7af04a828e6fc5604f16ed49c39686ec757e96adb0a667853006a8331c3d63ae4ec2000000000000000000000000000000000aae011375b337940f2a53d9091d3581e8197e79251b19c7fba01de987721a9d6fa694b7978f0abf877f46ec26147c98000000000000000000000000000000000aaffbd468a2eb86a3cff59e2e9b7ab88286d2bdd19c2e789b1a68810f0cdc76171a2661ab54e81b17643ff0275eafd72d2d59a7f138327a20263d6338d2a92fa5a2f741daefe9aa81d06f20a6fe3641,0000000000000000000000000000000010a2434fd3150f6b9b491d3a51226bdd457504077ef2ed5a11ceaa8284900d1b84039a34d5239a863809369bf20a704c0000000000000000000000000000000007934f34fd50a98225fe6578d3f34ae5e5ef5e104bb9cb398b2ca4f09048ec39cf52e7fdbac48d45212e9e4c1dcc6e120000000000000000000000000000000013ee70f1b52cb8b07ad957a7565b3e3c56306392cf7b5aa29047b23e5b41fb3239ac3611bcb16ba7c7ffc4213e3d9cc800000000000000000000000000000000035840f8ecf56359dc3239945720ad08702b4ea8d0fa9bea3bfb234431df4618e960a1eea87da72ba4d9443f14bb87a3,240480, +000000000000000000000000000000000d04bb9b75bc8078ccfa85e27d32e137ff5f05f9241b19ea835bba2fffc9255a4a3028c0caf9c32d3d27666e1394fe820000000000000000000000000000000013f59c3d8aaee34230cd7715a32e4a45487b9b16ce68d178f95461229a4d0fbe7d31edc7208a7338eed08e65847f8f29000000000000000000000000000000000d63ca2bafaa54e93ea54846b26f88b4c6749953f9cd00c670914cca279b794c1fb5e2664fce44b8c04f01c68698a8b9000000000000000000000000000000000b5188b4b7ef78d3662baa01b1813b4a0b0f855e11397584a460d56f594f11ff2e5d708a23a8e64d0ab337c7076872527740a826d524fdb7969776bede5ada468a0115229152907cb2b050760c18c8e20000000000000000000000000000000019bae57568c879cd743f7def43b6b994f29782c6a0c74734f35b97042a916da00daaea34f321481e6cc4749e23297c1c000000000000000000000000000000001853fd11d4688b027146a07edea647502e80750de4e5e2d105faad3f71ccc90badcc750f76f1b02db3bc0a1a635b2bbb000000000000000000000000000000000b1e45b90e6a7032179236f13f01ab664c32ee5728414ac0d6b9d79510e8c5bd0f5b62e6c59c1a3c88998bf45636cbab000000000000000000000000000000000ed16c2f88b5b8d29d7e01633e2876322caeb740251b034e5e898919f836ae73f0296c62253a0329ee8f71fdb5cac3a1d226f56bf3935ea95d976fde5790ba0584e5bbc78b37279aed8e50389899b9e9000000000000000000000000000000001455764f99e5eb0e0371e89f88bfee1c43224b9b5202746bd151f72336285556acc5ff36bd8ff87378249e82214cc5e500000000000000000000000000000000007fcee74e5335d96714e4d1a7c6f5c211b1a460efa283e0d0578c6c1f56dbd252198eebf0625362973c40d95fd890d3000000000000000000000000000000000ede26cf87e604507230ad996788e85799cc07245cf7191a6c3cecf0bfd5747b3a277cfbe41252808df6da19f005de9a000000000000000000000000000000001855991a4dd78dfc6088e6a43a64b56c8d86a0278b899bc8a1979a40a287979dee567217b006ca71374156a96b79c176c133e1989ac82e4d1c9852a6c7156a34b05784a58231d59e3cc875ac5834d5c8000000000000000000000000000000000cd032a7dfed029af020bfa249e6adccaaf5bcd2ccf33736281c4fce9c6e2b2e87fa828cc20301269d8e0579ffb866a1000000000000000000000000000000000765c4d6c4062cfbf7e24f9772dcd812f7e707f2b0ccf9043faf10018326834934df121924abb74d736b0da47554794a000000000000000000000000000000001540fa51e4580ff73e58def90a6f19557dec3c8306e2317ba0c25ece3eb4f8c39beb57741b3c4b9b8554fd2597743ce6000000000000000000000000000000000d875c822d0ce50dd638254cd4aad5dea1443813689a940d72cfa5db9309b171299ca3d69b137dfd37f0b7538a0852750fdae1b53f6442c4378774a981c90d282d5f8793feb2334470c873491e41740f0000000000000000000000000000000011c230689175cc672c25f3c56ef4eaf2bc5766ce424f6c596b40ab24fdbfa56a955205419c149058dffa4d86a48ad35d00000000000000000000000000000000078d493ce3a8038134541ae5f2a82b5e0590218a499dfd78c7a9c06b92307003fb62d6414d6c04b22f2877c3de0b65ca0000000000000000000000000000000001d53c22a622c5d91df934783f8c0cb7e370043ecaf99a0554987e6c5120a0e5f4ede023a9ad988d30d945a2132ba5770000000000000000000000000000000015b1f36a00fee95e13443c9f6e67935a840cedc7c3fb7833ece8e180991909922f59d4f4ecbbf23f16bf5ee7f0b5851b70f1de7cc5e6a2cf7dd4b6e60ada67ca47e7b9417bb5f599048fb0c9b2abf33d0000000000000000000000000000000014adff1607236910597a951ae169a7f56d6a3b4e0f44ac63a247716bbbf61feff7865d075f79e4108cda6c0731fdcfef000000000000000000000000000000000d740b13885c268da876898b77914bf4a002beef5bd2a3edefbf366e45ebcdf593ec6d9ab21e983fcae9a0832986182d0000000000000000000000000000000002a0827e812e983898351d9f03f660317d41669b0fa378e5c7667b73df299ddc4a32a529ca887a53245d7e1f946623b3000000000000000000000000000000000bf09a2de1a8ccf24a8a65dda72adcb96535ea7235de87f05d27341738b0b4ab16afbc5b37c97e255118dea9bf180ec2ca82cffdf59b742a736ae9a6d36f7840c46c20c126ec054f47ad52a22948d7210000000000000000000000000000000015fbbf7e8c26e2f41be32daee2c81390b9bc4413aabb053e3a88bc6117377bc16011e81ed167370b72f84f0e77c2b8680000000000000000000000000000000013d48a27d06ff00048b19879493a5f8ca52b7154be2fcb468b9de9edd1395750434b0e95ae6dd941e84fd6d8918455bc0000000000000000000000000000000012fd2bc91286dd46d68d87a3f8793db997ee684dec6b2de1c4202e5e7eb0e4a8a21222e3dcf80e1ae4a3a92474107d330000000000000000000000000000000004d8b71978c9025dabb3d1b1b3c7f4f13f166514b8b356fd064842269a36c6f1c07f150c03510af7d0913103afda4a68fad69492cab4ec7eb89ed37f1e7fe898ff49ffac4ef2aeb75d9c6b544109a08f0000000000000000000000000000000007d679ac21bd4634b415ef8e0e3670a8a1d673f6a4f7f3786b92d55458af980b035e4dab165a3b773ff3469fdd9d5135000000000000000000000000000000000fdb82db6e1096e73322050f828ba41b3012496a4fc4cb481f11fee338243aae20b205ee06887e28f6ba6dad00445f9d0000000000000000000000000000000017e6894b48f60b3d9b4184d58ab9554851e285a1d445b4d97cb1a7ed5a984ade8b0f62ab11ca75fdb280cc0e526108ca000000000000000000000000000000000c03b61690cdd9a4c6c83d03749db72c8946c21a944fb292866cf3a2dd1bf3dcd95743227709740ce8124319d0a540555af71c9baaf54967683f8553f72abf789da465041ee5a92c9ce1ad562c91c4d7000000000000000000000000000000000289f850c4834153f36bfc4855f89e9437a172c35a856117f8b841e5ad4ef973d3aa33fa73d8dbba4b9b2101708006bd000000000000000000000000000000000700025f22c0460613c05f8941f8a79a4319325c37c2b8f099cd910df5c0c27121a9de0e40adc7ba0fda61ea637b47d600000000000000000000000000000000069e17e00d4d726e8eaca8235c88967a7c093c70e5a46b1863ad097acbe233554048838a0a486a72cbed7001c83a27db00000000000000000000000000000000016ce4afb84c1a9e0216f23bcd2dda0bbada6a4acca78e1e0d765a5290f6f4929f6d0eeaf1306fed3c9766ca7c7268acc7effc9a7fe773a420ca430c58bb94e7baf26b9a97b618a15e7a18b31e5914f10000000000000000000000000000000018ca46a89dadcd3b54f60fdf9a7b97c95b9e0668ed9329bbe4121e588a1ba773c9d086dc35b699d65487f428c00ad8c30000000000000000000000000000000003ada6835a93310d0ada01bd7fd6778bd07e718d1ce05aee2b4990bf32322fa94ca898a531ec6e3b8cd7ae3bdc77e0b70000000000000000000000000000000004a8abd2b9f7449213e63ecdb435e5e13fe2aaa31a2c38673a6adb5e96f4dd383dacab391787f6c17579c78a1cefa5450000000000000000000000000000000002a8768d98ccda80149a767e9b5a3b0bbbc0ab4b5f696522c8f1c664f1d27f2f0a6690531672ba2070355c0e77095dc02d5a3d0370f4a58c21016d208609f1d3e7cdf43abdb85199bfc67dd12f589b8a00000000000000000000000000000000048fb58924bd5952d3bd7b1cd57a1dae6c1034df3a420c1151737f88760e4b0e78fa3f891a0dc32fcb50f89e67b0f08300000000000000000000000000000000073e9723c80eae7685db774d3e2bced53a52f24504fc3aff98e2becf8d59c6e83373ed024ec1ca50101d2d613abd286e0000000000000000000000000000000003b64c8e9a1341bc6a444a871843b3add7dbf04bd1810e1d6da7d31c7c2b7a264c362ac9a366dc8d93bcd9392c6056f000000000000000000000000000000000064462d424e54f50e9849a2bba1b0caae966a8618fda0f8965b1a841dd2173872a44a18ace1e2aecc8e3546a9558d7013549b86ed3fb880269be22b9cb8be6f24385bb5e24bba81bce9fd5b72ce2ab71000000000000000000000000000000000c40c8da9281a8b43478c28b2fe59a3cbad0a818e2077d40cfe44624dc2e46f72d4489cccf63eb8460d02f895e78edf5000000000000000000000000000000000735d768f6ac999a47c88bc2f3375f01052259dc69011480e468d8963ea8eda74726c4ef32c8feba52878eaf5c0147730000000000000000000000000000000010adb3ad214b17b963586a10701934727edf05fcbdc94d98255632647d73536decd0c91363840e1b55f29f7d32f650410000000000000000000000000000000019349045e6fd25960c03336888679cb53409027f35a1f211b40d24ebf724866c085a978ffa3a91d989da1a7902bca018c8f6dd56906fa13144dc87c31b53186b0683cad220ab2de89d2fb515bb269cbc000000000000000000000000000000000a5d2dcc05e218b0633e0a965b6d69a3c6c1c7837e1fff7ff75cc9ee93a112f8e34cbc95bd9dd8fe6ed22f2e9221aa110000000000000000000000000000000017d2e5d2c0578b1ec26b57c3305b209c979bba6925756892f031a7462ec44e8a4a2527e6aa2fc13bae91dcacb8c7a30f000000000000000000000000000000000d437edb45ace50700db548db68b9e8376b3039fa00cb98dd00cd197c14d0f92c8a3945127c43b10b34bef7894fa43410000000000000000000000000000000010d5a2e442a2eb35aa85fdaecf094c1e1f307dc9bcc540693d7206cc4e0d050ab900f17fbdd0754b59bd2aae705c60149ec934eddc44729d05f193ac927fbcb022288ffb2bc7d4f46d1bfcc7efacef940000000000000000000000000000000016c36464b426c3066aead1aaaf65ca637e93279e8ccc9d838b9b3ff1aa7b896f36de506efc2b0864763cb6ecca4926f30000000000000000000000000000000006d88d5764fc854ed7d7cf1c0e210496ce347bd887da2a149a09679469e98c453d85115afdd2fc4987b64a88c4a6f0a200000000000000000000000000000000053edcc0ca4c205423ee6a7031939379e552bd2d2657f8f25370c9f0ea0a947e77f18b5f218f98d12d720667844f3795000000000000000000000000000000001292909190854cee4499faa602af99dc49d1354a71278b439e983bd89e6c504fa5fcaaafb6ea26dbeba9850bcdfc1f69bd211ec887635ca841c4608fd00bdc0f5fd0f6365dcdfd7d6f4c36f4b25b5b1b000000000000000000000000000000000997e79a7549ada9ee0233b3bf9289df3ff797595f4b5eb2e7dda6977ca981c1c4a2b91b924812b95418f1b1d9d0cb830000000000000000000000000000000000256b830e80f238e8494387429d727a91cf5d323ea87f7dc143058c05e11858796adcdc677429d1db4dc2415cf23808000000000000000000000000000000000cab529c6b86beacc57c874f07108d1df7d98fbd59fce44c48afe9eb2dff823f4869b620bbafc121b4ead2cf244974de0000000000000000000000000000000002774906c1a0acd87de224a9450617db37f8f36a0a192f5daa2774eff0b73aa79b4804342999df761f8572974c697c6010bce61d4e35770e7737636c0f9a664eefa948662d3d22d1f1708fa48d3043de0000000000000000000000000000000012abd02540073017011e186586023adfca36fae454350b2015a796b7991eece65b63964fcdf581b4b51dbd7ddd506ec3000000000000000000000000000000000ccd3f2d9280908d4b30e924e4a862810a92e1a880cb56e842a94a2a5120956e8713f548ca279d66d06ab23e4976e54e0000000000000000000000000000000000c052ed00fde2cab515694d8c004de910e62d07c462345ffcfbd3904a0171b970bc58d99c5833059315283004f3390e00000000000000000000000000000000008fc4860366074ec0c7aed2c6ffae7c93ae0a81067edd8911b4c53393ebc0f23243823aa7aa2b2e987cb510f6e0a55a65c86930c1d142985bf85ce70bbad170947e850e5c6ac7803fc45980dd37a57d,0000000000000000000000000000000006ced307065868b6d082bd205bfbaea3b0a8cfdccf831bf154563b5a942154622b0d7689819b337479480d19aedd85e4000000000000000000000000000000000c0f04fbb26cf85c2c22763f3e78fe255d8d1f45ea47232ab58f5b785ad9f2458b0b28f3cdc25c4dfcb47d59957ae10700000000000000000000000000000000120e38740eebbc3eeea9beea483e70d6a9c30a5abd61b86e5f94bf65ffb40fb92c8d246edbeca425ace175f79c9c8afd000000000000000000000000000000000d5a503a26e50f9be34c2e64e4a80402ca6e17f09db1b334a9c1f6318f3e7e63b3847a7ca38ae6aa7c96ff94bf5de842,240480, +00000000000000000000000000000000036480931a5a585ea54b6dbb01759eb1d86804e3f03326188c71f859613722e662c453096431171a49eecf8653f14d470000000000000000000000000000000015fcd6a30b9d59a90d8595ca1758eed7d6810d2916638dc2cb637aa09b16b5ba4920df7d21fc0b923453a6c7d32f056b0000000000000000000000000000000019aa4d8e98808c2fc1273d383e836876b087ad5a7d01743bded01314bc62ced94052d75d312a18839c1b33faa9e2e5160000000000000000000000000000000015747ce0f1171c0d0ff1fee9dbb2e5673b9db0b0c3618cc8bda474f378db58ea42184f907593f3d6fc2fa215cabb7b2308e559e394a9c1ff07a45bb3e022f9c212eea4ee5b77db1c5b93ce72c0512b79000000000000000000000000000000000222640c1d64948daac3ff93e86ecc96bcf9c93559266529a37ef1372a81952431673d69f1220e07b8aa0a4f3164c83b000000000000000000000000000000000db593156078821cd0ce0270e8a444d0d204dce0583774496620bd4752839f3451e505aeb3db568048739c7e71d279b40000000000000000000000000000000019932ad2c7e857c2dd51f7846534050b9243e388260cd47a91444fa050a9154eca88ab4d29a37def16d4a11d35683f2f0000000000000000000000000000000004d15ec653a72256ac6b616e9870b0acc7d46286893c0eec523dc27bbcf5fe596204cbf83ce71c2690af67b3616794225e55826db8d12169a31ca27beec80554954f522b56f7994c62bdb527c2438d5d00000000000000000000000000000000180622bfa9a1c452f343ed21a3e9c6fdf76589cebfb9a3f0a53782a3e7c9d066294e10699c386b5d0525003289f0ec580000000000000000000000000000000006615ff63c856302dba6d4e25d1070fe873e0c4950ee5ba8bbbd4b94ceeb181f1ee450acfd22f21010b88f0b88375777000000000000000000000000000000000cfd3940b5eeefa92d775792affa34371d13f3098ede3007e06510344ac8483debadd5a2baebafb5ddcb45a9449768b200000000000000000000000000000000145be0107a1e3acecc89a116668f9887579ed7a72abed3f4236930edd3f18974465c99ada86c4980c88768824216170f1362e8e39ec661cb3c5af64e0001cc94701194344a7404f1ecf7df0d5633eff9000000000000000000000000000000000820e74e6d0333b6b36590ebae78960d019065f1681ce68a2a01a2522496c840c668575a57f9fd0f50b87f928a41b0de000000000000000000000000000000000dee60d90e96019cf2bb552d016419e92dd358ff97039a61838b0a89ccbbd537f2b435cd11f7b6e75a4ec6675964e7fd0000000000000000000000000000000002ca767de9fbf8af7c73d41a07e1c0e38e3fc971472e11928b65393a27354b2d732012dc57f498f94c0b933565a7493200000000000000000000000000000000134fe97b24e153f0e9a27d3fe7b89999c6a19e353325e0746ead013198b8e00ca6472fcbd2a112aecb9ddf671aaedd9174d3d66cde7c4c8a4499708a0c6f7c4da458eb970b6ca87e23601c702365b6de00000000000000000000000000000000031a9c29323196ef31030ba73827d228e56fd5209eeef0803a189e0c0e5b186ca1f342483eeac99e1e1b12cf490856460000000000000000000000000000000010deea45a01370602bf57a1f81413e8d3b337d7a1a33f9525e4ff7003454d1da2cfb1a9b42c4a654320f91fe7d04b6200000000000000000000000000000000002bafb7b7452a173a3971c2ba1768061a043307d2c32767056f18c1bf8b066176937876a87055e54675876bc1b2d2fc3000000000000000000000000000000000b5c77dba3b4136a7efaa8c2e28f39e88afbf26a7313b52ad6e390da4d948209d96e39aa08eb52200dfb890d7e88b46a389e0d43f2006449fe2de506dcdba4cd0e6077e2228f7d8b6ec9d8a4129c494f0000000000000000000000000000000018bd1ea5ee8e39c43d442e9c6fd22706e582cd80051f18334c4db2ea91ab019f54bc0074c8f0e52e50367197a797e7520000000000000000000000000000000005c0bcd1b047fdbdff25b138248bf4da4c013beff7dd3030c348d6b2b8724a147cbc44d570db5c4b273c94d0b99bc2290000000000000000000000000000000018e033935c20be5940863f7e9e39fcbdc29ba031e58c10beea90cc48e9da9988fdbf108bcbd87948058f386928f81fa800000000000000000000000000000000107d179204db7b288315e8aed7b92ebfe53b7ad2366d5d7944b3df68d9d9faad023e477213f85214047645bc05fd4cde5f8dc332cb31e43bc2e551356cb8d1533c6e567d34622667e7e4e3ddef352f03000000000000000000000000000000000a7b364fbd3bac7e2f2e7ee501db2d248bd73a76c2a12a3e51718b56ca9a8ded14b83b8cf0b5bd46f0c26896a65fdb15000000000000000000000000000000000eafea7128fe20ddf740a6396bf18ff5f2652a0317ea9b6e934927c3ee95b59c7dcd51f7c895b3989d40ae5f78ca508f000000000000000000000000000000000bdce57be904236a8df532c2c0072165b5cbd4103e9061fcfc0a45a67e4b25d11b9f816f63fc0eac4d6d3e10d2764c4a0000000000000000000000000000000012419f94ddbd8275054f8f89fdc27a74afca2eef314393236fca65705354e5cc0a470818999c96b5087997813823e9be0dc7052044251fd360538fa6d5dec9fcee53faf2f07de5d8df212d04f968a0b60000000000000000000000000000000011e4010d0cd7855a92cd5d4954ad735363c0c2ab00053db5e078f34e772969d8c492892329cb95ea8893b4b7ff7aaa5e0000000000000000000000000000000013badc54d90a19b84d76b30fef8e3ad2cb268204fdaa50ae951b63e48aec9cc6d585751dd48e4a8d4659b835f38f8da8000000000000000000000000000000000460728f686b9b15cc19ef135af71312e174860284c3f0e7a84cf85a5c934e2bb6cadee8e482d88afe788a796605f79d0000000000000000000000000000000019a50c06ba307d83452a30fbd862270652cf5c7a09b150fcea858a8102ce3b1e9ec13b6abfb323d63d2c4edf209c7cafc579dd4f361fed9084d9c66a3ec4c6af5293710ba5299df3abc4cbaf5802b5360000000000000000000000000000000009faa74f66ec0384f0458893c0026f73688c764e8df9ce056a88a2ed0b84ed8f88d1b683443a3269a3db838f8aeb808a000000000000000000000000000000000949c4be2708c1aac86aff39290ab6a8e0f332e7a098bbd64227a175473d9dfe136e07548b282f69a94a15e2c32dada10000000000000000000000000000000014f2c7c7da781e2f50803e3a948381c3c439b127949f79824df1e5722c206efccd6c0ec5dd75ef63d8b1fa301c83356900000000000000000000000000000000176753460d241f38aff41bafdad51688ab0dc9a5fb3643977c7b9d282ad4532fcca1e725715227780ec28bf1c32bbc1d69f0f3c3f516ae34fbecf45f4636c22acffbee765952b332c0f3d8cadb9c93f10000000000000000000000000000000011982264c8c078518cd0adb05034761224e9063654904e06fb5e5a6eeb1f45e4ff3da661f1232693b79336215dcc0cc40000000000000000000000000000000010c96c872160d2de03a16e85f2828d0cf2dd16a3389effacce46b5b5eecfea1042a77de653da5a1c0380a84c435723fd000000000000000000000000000000000a4ad2d9956bd407c555b26c192c6bf59bf89e40d9c6f9c90780bba313a39db71a73e7633397d47a3f58f61c81edee77000000000000000000000000000000000a7f912530d27a7bf74e01d8e48890cc66f72d14950554991ed1edfc504062ff6bd3cb6941bb398df9fde3cefd33fc0676618f1954730111e572937cf0c9f7b3298a11d18cd890cb419f732c766bc6210000000000000000000000000000000015bc12aa9ecf417fa5bace8d9e5dc4a418555eeddde1da8b624bf7d6e1873ec4a257d5f6dfc058a8d9b02528e699abb70000000000000000000000000000000015b41567f8c780f83342449f27094bc20a839602ae482de14b92e40017e7acac8857db48a2d27f1f1a625883b6e5255e000000000000000000000000000000000cbe79ac0718555fd8fdc38b68eec8be83b32499d2654be44888e45a2d610b0e81ae12fd56550524ad85b5a632db32ce00000000000000000000000000000000069f46b5baf4357d8010869685b3828c0dbf6e2338598c9b42dfecf0b22d803f95fca716115f74c77778d414cbcbd881fbb9f2400ed1dec7ea63d2b26bb3e9c2acf70117e3026626f6f88a07876177880000000000000000000000000000000017ada4038189c544902167be958e43ee133730e5cd329e572dae2d853b694f5ff8032bd9ab41cddd11c51e8284970f810000000000000000000000000000000013eef75e6d28deec945ddff33128c199fa52565288d63677c824b8d56a6c29eb98d34c5834e84865be35d40c1c59a40c000000000000000000000000000000000e2fb4f9c7ba6bdac1d4ff5055be609abef7fecd7923a753a704da537c0ff41951552420bd78d14cf972dc84fa3f5dd9000000000000000000000000000000000805376b814b8a59435310d49a43081dd7ea36dc7dcb40d38068ae9085b3ea9a3b2249234234cacc76724d8ef84a2eaca0170d7b7604b8951a95d49b6697e2d0cd2a41c3671d8f96e936cca911dd516d0000000000000000000000000000000002288860f2d671c84c5239313b7f6b82e31c3976e6d310e15d3bfe1c566e2ab5d86ae6ed0df02530f9f7893ba419f1870000000000000000000000000000000017365bc096e260f8dd7b189fabe10eb66923783b41fff70a149251576b3b465c13230dd0af13cde562751dacd8298335000000000000000000000000000000000fa8eb9c818df27181b45a74b333ab481dc7212e417c4e12634816f9e177064f9e1101deff26156d26bc6574db9617080000000000000000000000000000000009379598bf02222e1ec37a721b9ea31a3adc33524c6a41bc58da06caa3da3bd730659f0a80f793a0fcb9c07b43ca929c2c2afc06f19e627e9ec0edf1083823d30ac569346040965e1c92e0c15011c90b00000000000000000000000000000000136870e08ff5fabf36410629ce5c23470eafbe73a7dceb633df5c1492e39445b86ce15c22bf4c421cfd0adc6518e78c30000000000000000000000000000000010aefa3cdf1225da09b796430d096807a83eb2fd5a58db3a4bfc5e500dcfcd472fea3077f0c059620f4ff708f37c95a90000000000000000000000000000000019ee2c62ff860338af623c535979ed31c42c0d0b2f82cd56c153e80e6d92bec9ce39bc8e8f285d1efd1c1e969521dbb50000000000000000000000000000000008ed69eb0a16c8a35d507bc3a50bfc97e18143fef611263715aacf5400cb1aa285b6d2ebf2ec219d2fec477360875a03141d0ff346e46a20c2498a74f910e9bb2d5d8530afc7ba47c3525861c9e8c5920000000000000000000000000000000014abc4eec64f2611197d0c1322c3248eadb725049379e64682f2b3d7f83f7bcea11358d88f52711b3020924b6ddd84790000000000000000000000000000000009fd78c5d1d2043d83be30a88f046f5b633c6dbb11bab25fa3037bd250b6b9d9394327aae25d1939f777fea9f3df46960000000000000000000000000000000010f413640aaa16a95afba98660f9e1b03a8f3e0a7a3d7f2b971f71b5e3d09016ac2b410f97d20471f48621d5a363e9e6000000000000000000000000000000000154b5df93298a5a14a6157819e38db33ae7f2d11dfd13f7f2a92b2fd9b053fbd25f10a8c45db3026f6f583bd56eee0f1d688a1aca2a837e0a353039294a9988a7111ac134a6a8a68e4f881e7486025c000000000000000000000000000000000f1893df99adeff5e4042c4c5e8557e53f7c34efcb2a7953d5347f81d2f4a75ca0273a3845f54e795ac1c1f8ae7240dc0000000000000000000000000000000004856b05d58898be6aba07fcffe487dd895144c7ac8fa8bb1a37c61e73bcd062ff541d510e24c5bf005c8351d3ddf61c00000000000000000000000000000000178b22c2c698dbc4929b119474a741ef44d6275fff5ba058d9debe9475e71398e464aa14a6712c5deeb5010d1c7758ba0000000000000000000000000000000005ad09389c35c45f349e6dcaf1cdb3b63648b3df427ea0c2a371f45634635f9253957ba6987df4aca6cba4cd472308a31b59c33ff02791031e7a9424c781ff17a209d132af06f5b825df363fbd902cd4,000000000000000000000000000000001090d83d501373cf07c75effb1c85852019b39eb0d77226823aa3c1054d4e408e82fbf0f4420a30144c611fbb856748c00000000000000000000000000000000120a1e3795f6d5c4ed5b886256c611bdd209677f8324b7091cdd7cab11788b1c0f780e8b4c38b84d7c2ea528123d4783000000000000000000000000000000000d250df34d906ed421eec2a78c2ff4ed4eedb717358d7ca879d58ff5b4d2d72521082dba6ac5d10859125e32c2c8b490000000000000000000000000000000000476adaed9d80cb1545be505496222dba1f0ea85d38d5bece0663461e0e9d47abbefe95303c926db008d08b8aa162e27,240480, +000000000000000000000000000000000e1a9066289392b0b0b8f0986366c2975463c9cbe7a74f2eafcb3b8b6d4ee3226ea5886aaae374341bc76b53b165e22a000000000000000000000000000000001557cd01b61b5f2361f6b558a87c67f2778e11c21734b7ed25f72a88cc62cbed396d583de4c2190ae6bbfd096c33bf73000000000000000000000000000000000eab68305118d7e7076043719ac1e13ecda4497df2cf392d6aae4b7753f114d30aae3e8535742947636901feac4b620a0000000000000000000000000000000002cfe5014446556b82d60adf874cef25e58eabd035deb4717c93bf0361f37a4a67aab70b95627326bd97f111efeed57f58fef5bc887b7caf72f2a533fe1455ae523841bd49b4adf16cfe87edc6f573eb000000000000000000000000000000000c8fa30f6055357f6b697f2115203428b8005ad03286d2b3c805bf3d4dbb461c30e6ee8b0973ef41f884b91e857c53500000000000000000000000000000000005e1c785feb4c4fb7e960233d431d51a4fe471f10321251d018a950374d2a686d52ee8cdd855a29e770bdc1bc565f471000000000000000000000000000000001158d31faab483832d39f5431a5d8aeb952d6a63b82ec019f235b5b2e5580df8cd91b46cd53d4a90b9db354b38c5a1710000000000000000000000000000000004a389b09be6fb7ffd14d7f3359b17991e93d92a1c0b9a89faceaf71f5ce77a1875aaeb7a0ec3b2dfb363c47dfc9875273b243b83d44158a66eb6d31e7c4ae1f4b3ddbba81b2cf9a654ca7c4ea2147ad0000000000000000000000000000000010587118c5f90b545ee707466ea2c5f378e6795c260235cdf9876aed8bd753aac592ee05e23882ee77f4a13bff97f5940000000000000000000000000000000000a0344aed244b90c4fb9ac337edb01429e09f951062b06025a5212300f5471a95f28e09bbc715417a6d98423b518c3a00000000000000000000000000000000128457cf374e5b8864b8241f476da093f48553d609a5f30c0f0f235ecf7127231237b6c8802f2904a8304c7c237842620000000000000000000000000000000004d55ff04eb09b33ebfe90f2a0966a1b59cc224215c0359a4ff0c09e60f9fe7ad8342868184d8cfcaa1d8c28328864241ea87af09f6e62111c48993c408efd3db9ebe218ac68f61a461ad9ec1306873d0000000000000000000000000000000019e6992c3da47715bf379a668a15668508e7ad27bac647490be8e82759b9b79c996735aa1bfdc3cef217750e4ed36fce000000000000000000000000000000000828f782c5bd4f2de3570a4930db2c020f75f93adc98aa0e48449d29c7a3b0d5c349963d956bab7f985ba6ffe59c90ec00000000000000000000000000000000062c7a730d286e895c57b75907713ebf1d20650b5e621f270f1d22a2ca480d022346def4102a62eebe867210e4b6122e000000000000000000000000000000000d6c29462ad449ee6cd122e3dc00d56dd5caf17a2510e5305aecfe85626cf73adb401ec2192eb693158650893fa67412a691b9635e38a46e2469811405ef6325ae7ef88a67c1d1c5b05806da329f27e000000000000000000000000000000000098de9ab41c289a05ba5a774eafe27d91aa8272fe9f81fadefba9a0cc0e31de20f808ff454a8647c44f5aa632742af9e000000000000000000000000000000000c96019bd5cdd62df1642656f0832ac8ff6aab86f671e18c1c7023dc16b8ff54a8e3e446b19682a23b73ccb90da2fdf0000000000000000000000000000000000178e3b4366b2517d4c19fb40551be6979d46319d7040682241b046f10ab88d269dfc097ae02952d46e69cb1cf159da50000000000000000000000000000000008341bfe1e2fb999f0c3f4e79523c720edd332401f9dfdb8dddba8d1342c2c1fb20ae2fd9dda92c7bde5a0c95ad971f80d9a35f474325d0f065442805cab3beae4a186b252ebae54a567dec6695588f1000000000000000000000000000000001004d60af8c21f7c62fcba1c5c41b94fc77f64b89abcd23a218f0da8f47d2ae6879ddcde52f3e6feeae2dc7b2720577d000000000000000000000000000000000b8e8a7da87aa62ca852e2984b0f12b85052fdd03883f01f4496df0835d1cafa48818b5ff1e3cb0e9ecd66054540a0d40000000000000000000000000000000009c16854580ad8191e3e80a0afa8da759a8b2bfa7e0d556418b5c96d97e88a12fb75a91cd68c2f4336c3ed7ac99199fe00000000000000000000000000000000195ce9c562c460c7e715908991ea8b017b81561b45133427f63cdfbe8f65202bdc8e8958ab0977b3a244cfa32fb35f37c20e998acda67d406a238f16bc2b3066a6d69d2436577b8900a180e6a71b0a01000000000000000000000000000000000107292f77666064b7d80d73ea8f3b623170ef79ccc7c228b8366675a422a0cb8491586a2e4ab1a067c31396cd670a8900000000000000000000000000000000126f8136dd61d61b2a9c0f4af3ed44a3cec3ccdedc74821f341d200601a7bf0a17079c824de6cfe28467e843d0c74d2a000000000000000000000000000000000bcec8afcc7ee56b36d6d08b51f61454c8fb15ec5baee1117ed55af8fc85f68674250334f79b0fce632e75623dd173210000000000000000000000000000000016624d64660b63b70ed197f6a675911b02b0bc6f880348faa6ce4727af74127c509ce8535d8dc8db5ae2d71aa497e0756fb773cde356e2edac3afd2bf703b59161162dc1e915873ecf606dfc0e6efec5000000000000000000000000000000000f57747c20e1b3923c7e1d8bd7d877736cccc0e0829837a086d62d48cb54f323d90b57ca3339fe4b256df529bff11363000000000000000000000000000000001940327a1b319dc4212e7a553d3f49904660722c89636f6a38604d96771fa0fc71f57674b7aa710db4275822c2b89903000000000000000000000000000000001956b81bcf961d16e50c053ca07ae67cb8597138f34a9dad4d82e0e8d23a7e08b751682d588f229311bc63f9598ef448000000000000000000000000000000000208981064443e8c72987945e399b45b74e529a0bb75e99b7d6744728e5c182a6b0a10e449147bcb0b0cbe70edcdd845bffc1a58dd06752a2a77abab835d089599b4781ae51ab998ff3c5b68329068bf0000000000000000000000000000000018c35ca3a63053fec853e8fda5920b560f1be28431f2f4b08789c7a202336c8905a5ffffbf69ae4427f267b1e13288d60000000000000000000000000000000019de96be76bd93886cc486c2671b5b0d731b568638b1b830a52dd4c481b9a1fbe2b3cef14b46e25f1188ddb3c158da6e000000000000000000000000000000001813ab16a11c79eb3d3d47ae7d9a7c05401ee91eb1183266d23077ec4c0c8f3ac7188eece06876025dc3fe271d65d4ba0000000000000000000000000000000004d2a416dc874e956fd6d29a3fb96195019f4136561b4c127541ac171b5a6b229746af6d6e535a8017e64ce06709e52e57f35cfd74f62fa39f919400f4d692855a4b4e9f91920e4306ebb2e772a484f4000000000000000000000000000000000623b7a8a1c24dcc603f01589e6679c74c4ed3452894e536a4cea69e99047092acc877dd0bb395b0cb693cb1702a64a00000000000000000000000000000000013de9dc75e42f12e905d729a52f25bb1a4125f5edb435734649281bdfd41083716d0797b0a80d842c2503d09cc61162a0000000000000000000000000000000006453c06f56dbaabd4530160bcd5312b8a148dbe19fdf9f1e44b7b047a73ee9ef9d981116d00269942ef73537885eb7a00000000000000000000000000000000075376135ff3acaecc0eeea32f8dc15add57e8f0297d053ffaa0fb0a8fc4418c5b142f96b6b9ce9eee2f949c960aed682d1f3709700634653374fba5a94d69163ef616a72a63d462afd9f01c9ddba84000000000000000000000000000000000120d088fc12210c1f5f6cc3d1091563f9a37d4d0e0d2c305b479f4d7e893c4d5c8170eb164e34e4843a21c9eb193d11d00000000000000000000000000000000159de80db3b1f0ffc5fa8c93e1bd54cf8ae19cbc9018a5dfed86179cdbc976c1c312212080ab221806bbe142d496e7a7000000000000000000000000000000001103abb75a78220218cde4bc4c59ddb5fb647ff808754dda200bdf586ee9c47a09e03762bb726b085928ddcc998af3ee000000000000000000000000000000000bff4bea17eae0f2ff3e7f99bfa91e6ae8aea28f6f3fb6080eb644861defdefc26befbb7874f612edac0cecf70dfb275614ed9a08dfd406df00719d5eeacfb0a96413b608974fd0aa1d4c6176b968dc00000000000000000000000000000000012dde607a2d4452c6c060054c8adb6307743edea3ccb6ac34c275717f177f0e454d9e33d4391208198cae39d7eb6f6c00000000000000000000000000000000014cb4d8bc98060ee68a8ddbc44b83db5cb6d09f09b0d608357629251c35e44383e97058d0d68fe2df3bc47424a5dda03000000000000000000000000000000000c14fbb6c844fbf896fbd3cb3464a83aa4c6e9a7f0450ad96a07527df6f1eeeaf587f60a990bd6abe7aeaf5eb46f362d0000000000000000000000000000000001d9468774318ea711b79f16303ce86288cee312af296f1c9f607ef5f97c7d1cb48a7218775c8aef00c227ccb586286e7c1dd2e5e5f630fb1d07e8934dd3ab029917e7775e401c0bcf7e1fd83aef728400000000000000000000000000000000181e7f8d0ec7a4a7858bc96b61484c24dbb9dfeb3746fd3a231a8e442369e3e83516ee6043b1c06e7e2043dc86f6c75e00000000000000000000000000000000184c1d667c0ece59f18fd2eeafc66f1ed530b7d5f4560a6c886429caa13255c63dea01c3e357e3408af58a39420a8b28000000000000000000000000000000000a8475ea694cf607246a1c50064cf90cbe50ad5cf8006934a1fdf1621ba38d20e70860a2b5aecc05acc60943224cadb60000000000000000000000000000000008afa03c2df8e83fb64523c57d0daa7cfbb7af6a4bf2960ebc64515a61a659b2c37ee661050cd538fa00cb34746a371b64e9d16cb61f2bcdef30cf544d97e078fccb999b96a1da0eeaa0bf232f01995f0000000000000000000000000000000008b33a297c8f86f1e9d7166f9e905283c8e1581e582b879caf48585d0bca3608fe46d8d9f6e7c90855aee9d92283d7a40000000000000000000000000000000016962410d6b4b6f91437617e84bfaaba49de0369b8748d2e2dacb63b421e0d7de4514e7fd3e0dcbcfba8baa4915610d0000000000000000000000000000000000efdab72953b870d0e113efa7c183d99aefc100ce59791aabc72423aff70a5b74c577c06ca94bfd6a7722199b4bc22660000000000000000000000000000000013b18e31700987dfa4344384f9b41e72afe92c39bc961333cad3e7d0a5efd3842a5e849cff5655c4673f720fd0127dca35bca9082d66c06761f702dd439faa4957caa70ce0343268787f41a2f4bc0cbf0000000000000000000000000000000008b86f70c8d8b03b0e9a8975776d7fb0d08f95eded0a0124551d363c2df57124e0e89bd45ddd1cc75c258a4ae2f87916000000000000000000000000000000001120eef9eaff7c308b629deafb060d2c12b20b57562007fa810a2191d99fabe9c7d3c364caec1724665ef556de66b57e0000000000000000000000000000000007698bbef6dcea67a2c643342ab2a0f830c329fb6244d4a98512daa8a3c9d808cd2acc0cebbe3da920053ad73eb7cdc7000000000000000000000000000000001155b6beb28fd88d252c6b407bb9f55d22103257287ce77353bea580c90173b5c3d49080b319ea28817d67c52bead96f7980eac6c8db86ef83748d10b210835e53baf8cc9f607915df272b6e28ac6b2800000000000000000000000000000000142b28509d72f9e3be9ee916827fc1a8dfc4ef7ae2b72eebad5db605fdb2dfa4492b50cc3e472df1b52baa6e2b0eff5500000000000000000000000000000000134d6821088ce4a8b42383d5a43a32bb0cdc96c85f304a2601292670633d5e231b9dc479d199829a9ba9f39c162318d5000000000000000000000000000000000636da344fcb0fe50ff3e22f8591418f64cfc722b2860b4a5047f973f42e4cefb93c2f8eb8a14b4d150758ecbf3cf712000000000000000000000000000000000e6fd06d5dca702cc9f199f7583add86c82f7b530d4dfb9faec36dbb669cf7c1cd1260c7e4f3026824eeb5b979e9fdaea256ebae4b204b3888d7bd244bbff26431ab5890098870f13800bb3be3e842ca,000000000000000000000000000000001684f447f8929ec0187811f66e985f0014eba46eaa87de2d4ac2347d10c0550e4044ec7792d9f315c50081dc2097ebdb000000000000000000000000000000000ee0c46efe930bc98f39dee8cc6a792744e84de4fadec035d25ee8ba82e1c53264d0885a1fb05b2b8dc9c6a1846c28320000000000000000000000000000000003a5ef98843099235a2ad9522c9cfce1908bef77b45794e7df9eb38a4854460031829e947a118e8160365fbec3725b85000000000000000000000000000000000dd205e195abef6a4cfa7da66f022a418235e1a1b2fefa6bd3ddf8a3851d8ca8c27652bf87ac644cd189ae55e3cc7808,240480, +00000000000000000000000000000000064698182f90c20ed6f6da7248cea32a291f901876a900d344ce4dc1b07822b480519cb8d891b5ee4f33b4efd90742cb000000000000000000000000000000000e7e9d2e79ec4b07015baf69a283f6a4abc8d7c1699f3356fdad6ea9b1c70e41e73bc14e500634d73749f9900eeb65f5000000000000000000000000000000000002ddbf40619ea5123c100e2d6553213e37883fb34f0f0f2124795dd971892d5c9051cd4aa78b9d20f196301ca9bb4d0000000000000000000000000000000017a07b32fbffdbf7a80f0437eac1ec5fff5a68f3b053482f034064992158b604bc34489dfd41a24ffba806ccb871fff15805f2e8013007c4f6d8abf441728eda8d742ea2f1df545f85d092f50ca8275c0000000000000000000000000000000018b4de9c04fde8b708408efb3aa7f24b5f7bcec14e7d06fd5a5b36bab528e5adc0bbb1e378a5ff6fcbc95aea530ffc6a0000000000000000000000000000000010da98267770a47e5ed14ffb3dbcf537dd14ae5eb79522c772a7a2833be214690db0b4e86621de1842d88018fc0f348400000000000000000000000000000000135548e2eec9ae7c3d23618d8286db13a5a628fee04fb6ec9da980f3a46838899cf965c1cc6f562e71d5b5c7428cabc8000000000000000000000000000000001669fcee7804df9b7bef32e2ffeaf285e8501842efe87c9e827fce872dffbf92255d3c3a2fb5c382ab7aec0bba1ae0e5502d777b25f3112ba2264022e2f28dfb6e5d5239ba097e9d815928be44b6a62a0000000000000000000000000000000010ed20c069bb300a27571adabd239e70b767af90b91c4d0e93d88278a6da47b7c12fcfaf62ac0a7b9966968cc9f3770b0000000000000000000000000000000017273eddc25cf41f2d7734a3866711e83d4f2823ee6a036942799f837d5ceff10dd6022ea25e3c1e28c7b14ed8f4e7c5000000000000000000000000000000000f201f314f66f6b2c6e1365c0fac7b187d31bc45b5edaef5243b5488e26581dee24de4a5fe493bee44165cc31d8d72ef0000000000000000000000000000000009dfbdd86633edfacad6b78d292141a1e653a1bfd8c48a96b2f6bf8271ed6033c0511628caf2ef258eb64cc8b63d8e5be7d64b471cca34ab0c91f61ff26719c7186dfcdef13895d37ead407873736a740000000000000000000000000000000005c4a4a5ffcb4a39c8809821ff275360ff937070cb97a791cc9ec45f429256a6d2d6127248b6ab0b6c71c30c4fe84ff20000000000000000000000000000000019fa60f481c5be953c9c7dc86903a89af0ca2b4205be3a00d793d6de7103852e147ebc7d983c6d6e8cd99e681241ad440000000000000000000000000000000015b3b2eeb0f81ff8a2624e2ff2396bc69feffeef62b1b6a1e73ca4b9e60506c2950fdd23a37cf56387b8794449d3237f0000000000000000000000000000000017021a69ceba3446dad9fcfd8cbe5b89b61372f57d43a8d2e2c8f4534bef6b91408409dfda9438f24526f7e6bf1f4240e5723630020fdb48e44adda735943c91ad7d1e12f3c32d823833eacfcc8b02ba0000000000000000000000000000000007c8f07f22a3412fb4638cb704751959cda4e42e4612edaf5b1f22c8f9ea314508353445114bab6c07ccbb4b0d0bfa6b00000000000000000000000000000000062d087155c8722d0102c8e5084f95f5f58ed626d48197297d21d2108ee05f70f16d595ef73e8e1207a3c0b013fe16710000000000000000000000000000000003b6652934f3acd4c91c6c521c2476bcd2594a939ff2e7ebcbb0f451fcf0a656a518dbd4f36f165f9b2f58054e9f778f000000000000000000000000000000000bbf21158227e0ad5461de9ad8bd580f9e65327dd4e23f1ad55618f6b0aec45aa6076fa88557953ad15d385a074bc7d96e9e37bd811b76133c12268d325ebbd6656e7ed718cd777458867dc98b1b3bc50000000000000000000000000000000019e336d4d342f110eeeba9773b8e351f26bb56361c77fbf12fd9fc218fd075ae38b95f4a8a5ef830fc2cd92558b1711e000000000000000000000000000000000a112725046ca3b6cc43207e6b36f38d96ff98dfe3444d67ee3f4b0208f3b8543768dc9989f936637d7819e7dc5740fd000000000000000000000000000000000527682076572d8cca15e47a2faf62b129baad29afed22d32ea47983a8d0b138653c1353bfc6fbf9fdbec2efe36700f90000000000000000000000000000000007e3c5aff373b5154ae66f978fcd66d09cbebc7e0c96b4a4cf23c4fa5f2fa655410c7f1ce597a3f5f155017720f7c50f7d46516db284a3938e672ad3c6bd40313d77c5d643ffcc59e3f55ad983cdc0ed000000000000000000000000000000001865c265ed4606ed16056c0b28f953119751d7272bb33b9865eed312ba23b32d01733ad5446cea5873c2bbe37fdfce7e0000000000000000000000000000000007018aca1e7ac211921cab1cc6bb18874d2f39f00d916b8f3d46a088a378f3c9b49ab8a296d0aa21608f11b144a0c687000000000000000000000000000000000210561c0bbe5a9f4b2237e5bdf88bcd73326d395277deb2a883526978df90792993e6ee520c9d5ec0a6f7ef5c6b3542000000000000000000000000000000000cdd344124b7b5da556f64ac5d651a6f9b74427fd712007310d720f3236724e2284aab812d739a87f3a1bfe8737dcee7586cf63c5e52b44aaa79cdda6dd6fa92c6fce11d867b2ff5a04c9e44e0b3930000000000000000000000000000000000024494aab30849df790185a4f939954b724c387c9a366fbe833b628577654174f705d05e7d7dbcd29b8873aecd55df0b000000000000000000000000000000000863054fe3e4838d2caec7103e3d0453e86a17fff0dfdb84dd819f31756032e9e97b7be89b636e5e0b642718f6da217b0000000000000000000000000000000015c8bb4fcb6d9cf941b722136d8d76d847fd6d5c643f4c0049c9746e76e49726fd463ce7899f4df66d04e5d48e523e6a000000000000000000000000000000000f101bea4e1bf610d2782ede91da95eb2b0be9ce60485465b9e94cbb9530b416c4394862f0ba7ee8067bb48e94c07c53efaac96bc5f686d6b952e7082236622b737fda0dd3900bec71654bdebc8ba2e40000000000000000000000000000000002dd11f4dacf3d9c46579182df1c1c45a364a8dc1eb7aa7d54d0141306f1c23bed85235783a22b8e6dc4adc35f9193ab0000000000000000000000000000000010d1c642fce533039e98712bdfcda86eaa62d2d69b861ec4fd835488732fcea414cfb6f3f8414152f9d5398c73a74fd2000000000000000000000000000000000c6759b75b1e3fe86c00fa124d09c5b7438ad61fd1bb71695743ed7793f39b7a0fc99b055201ac1e3aa07ccec61b24a80000000000000000000000000000000017580c9341789484fb31386eccc9c344539a09f1c4421dd124b1a0ce61f2d0528942f7fe8df67c6b2bbf782996def47b39d6045573dafd09ab2a0d8ab6e97b0ade43bd79d820749ecf19cf7d99792ca8000000000000000000000000000000000d9c48a111c8c74bce8cd78d127999531e46a411b2f0be3507226766bc8abd088638a237674ac62e0fb7dd4a86d09b79000000000000000000000000000000000073675bb81e2bfe6adb5cd929e0b7280f5d60b3dee7f797d65ffbefc2c2944a9c7207648bb096f13292ff4440c3f03f00000000000000000000000000000000024d2e0d5ba1a804520c72331fa23a2a326d461177fa527473240dda130f4ef893870e893e1dbf7c5dbb0178dcd29b3b0000000000000000000000000000000002a4c9487485ec33f8fb347d246ab0d41b883bec30d2a5e88cccafa676569f25ffd8341cdf6c09f68afae442a574f3334c4a2ff4ce4b633ec8fe0bfea42ccc329b7d3fbce96c26989b3c7a391c9e806a000000000000000000000000000000000c1965a745e42853b4d54739b2dc507d68d80b330360a4020e4412ba5422daaae313fb9597c98575c66ccf351e62a527000000000000000000000000000000000844439e6f08a411e61d37b5b2b07921049432e1833e839b00d6cc11227dfc8770ad9ca06037043668fe7ce3bf3ce84200000000000000000000000000000000152ad6fabde2e0310c978404a5244209a9363cab1f3ac9f71339cdad6d40c84f8e5a8a196283b581d0209ce90e1e3c6c0000000000000000000000000000000010eb6af62c7dba122b0e24e8326dc906370bcb4ba791c47630f05f657a228c20e010c065b93537ec84fa14a756b199789af09ef1f27cb83189e4e13f3801c08d3a2adc8b5f88717954ee84499defc0c40000000000000000000000000000000001febb2cf2d664e4a277cbf08fc1fbacd05db415a12329f7be551ed56d67f0b5dcc917d1b02951657bff3a26bd8c178d000000000000000000000000000000000018af160555292b2f7ce27112c1d60038b564f5427d62604387de97dcf48e4473107f91936b5e8008065a1537f7ca340000000000000000000000000000000016bbad2a7f5451098294a7cab2fe10d206741a99b128dde5eade581d02ca849bab3662fc3400fbe055dd93a418aecf0b000000000000000000000000000000000b1e9586cc1b357da6e58621ce09288e62a79517144f6c6b867359251baad6d40217578d49c1501f23206b125282bdf4c72c1dc1efefb775a1bda754ff17389a6b6b6bb25e22697847d24a117eb8974b000000000000000000000000000000000b88892250c848e7bc7bb7e42cfe1048a1f61dc546929211846f49501ad8c7c8817f5b5b99ed092d5a2236d59d9c8eaf0000000000000000000000000000000011680c6549f6b7d9d187a6409d40cc26554df654083f1e8a47dde826149d68da756adfb1b65bbd219f79a10d8454e881000000000000000000000000000000000f9596121dad98bf7acb3fd65fe7e0bdc8924e2390341c11d9cc9cbb0517f988ff79a5e1d60bd89449b5f042f0d0b0c30000000000000000000000000000000008982832ef53bafc23ea817be378532b95b5872217093e7c7c2f4512d03a9c9a6dbb7950563a520781c7ae213fc82897b4a0c7c2e611a24c722975ae882dcb4b45e6f6b41cfc87e8c766beefd5b10bfd000000000000000000000000000000000ea5bc2f8bc2b4088d1fed7090ba389577b11a3ee0775cb3f0657ab5b07a6709d3a18fa5fc33554dea235c60baae4bb100000000000000000000000000000000196b6259b06a4c91a0bb0adecea134c8609cf983c2c87158a69c9de3b6768510fc56543a84d1266dda78d90c3b0516ac000000000000000000000000000000000d0222d8ef278cd0d85dc8765fa7c4256394a5ef61f91301af6c7422b4cb17889224c75ccecd2df3ddc9bac98b493863000000000000000000000000000000000548809ce26cd498816ef1222d062b1ebb7313a07e99e3aad1431f984e9b8ecfd43357ea57da7e0c6c011c5d5400f7ba986d48aa5b00fc16c36dcad061d10937b55ec4deee63cc2841b7ebab84f910d2000000000000000000000000000000000b95455351fbce6f73de0345a195f91bf96abee361908cea6c4dcde72048a13a9a23991a75b9c988ba0afd9491d15696000000000000000000000000000000000305f29b05fed06ffab484cb065d4852eb323fda8c9b7c0a78843bd7143effa95cbe5e50c1a0c3a9675bb5381709b6550000000000000000000000000000000016ebcb25f1b8e8d7a8f7131455ed2be084bdcce40034e7ef24a47fc29e447f912c20c7c9910e025aab975cd2c8cf1a96000000000000000000000000000000000d84a5de7a5fd8592f6cc2bc7c3d93c06e26185787856c922d95eeee345ddfb7cbbb60b6d992c5ea4dfb33101f2ef1dc979d4df836daac0960fbbb8919d2f90c3457cc987153def711d6e8a12fb14363000000000000000000000000000000001377d654f80e933c4598aba1f637d1e37d66a96680c3a89a762f412e187817ec08f0ae897b08206a73f1a423b742261900000000000000000000000000000000014b71954b9bc22ac22cb2d7d7f373c3238c923205b223cce6c219175df2bb6d7258ae46d6cdb019311bd386275499fb000000000000000000000000000000000a08ef83b67bc972a67b9174d0e5b1536af882d505d03464c9a97f68061aa319d612de9db84e1e7b12fc3015fc2973b20000000000000000000000000000000005f716d0ffc30005e4a744092704a9e29f58fb06bf7d8d6fdbb95a4c0eeb5c39452cf662721ea3e0bcc67f25931a109425ae495ba75cdd0bfe200ee24d813e1aa93c100ce861c9ed7fa5537e11778990,000000000000000000000000000000000c53f0ca8901f4751be4a478088b30dce70b9ecc382455049df9ce108eb0a8d2696bb325fe9ebfd7d967ab5b9b2c2bd800000000000000000000000000000000033460babd2984a5d8b7002409349972f518364e92648927e223d7a3b648e952482c06cc713bdc29ab83f2646e9398510000000000000000000000000000000007cb9dfe603dc070151cc477ec5bb5a2a949062e8442399597c5eff8f1decff538cd0aef1384256dec73746e63a6c66c0000000000000000000000000000000016b56ee9b21c533b9c464178d14ba5c92a90e6a54c3ed319f487c2082b1ce1d0ff81131a5fb3dd7d13e0fc1d9ad9e4a1,240480, +00000000000000000000000000000000104e0b91821c59290be48b97936458af89078b176b5585ca9a79070c7050309b01df4b0bcd84f137f58304d90599212f0000000000000000000000000000000013b00ece925fd17a8effc43e21d982553ab2764b13defaae5e5419cb9a23ca7436cfc44088c2aded63785e4f07b6e186000000000000000000000000000000000267cdd42febf0706675b60af8c0953582ced84dd5ae870815654cffa46eb14b747fb8fbb3b014e59c929da49c6908050000000000000000000000000000000011c5384d7c3e0f4fd66ba4b4c2ab60f6f78f9930e1fed233263dad25294814d9e2aaba6388ee9f924e2a323693b6e43bbb2a329761a3d6a2e4d9d63d7bbf7fc6fd321ec0344cc4d7d1b6565c475ee9670000000000000000000000000000000018158ad70994584e6f2443b8b96c1e4772a00fa0bf74865c76000eae470eb02cff627579126cc465046d4e088782557b000000000000000000000000000000000d72979d455733756a0849baa8afd79e18960f3f6dc9676c33d1663961617831f3266015cb998fff28b78300c87c2a73000000000000000000000000000000000056192c20cbcbde6099256a8f40c78a32d3fd212fe9c511951c7523a3559f60662e070f5b5e5f87b1686be0bf6cc890000000000000000000000000000000000c7b7e8ab7486012d95af5b2474ce15db612bfe1508852b8d99f4402d0e4f075ba056c19df3caa3a93bb4db89443096143cbc3dd7ec63ac63618a9e5da1f9c3fb952c6fc6972dfec6caf1a415a0aa79e00000000000000000000000000000000005a2741902dab47e8d38992180a9670faf56d1849dbeaa75b2b4ded93ee5494184c8658232e9131a8b08ac9b5460bd400000000000000000000000000000000189077d5130b3a4d7d4c3074633fb12739f95b8b6ccb082dfa61d845a389e6ca7aff835fa0f194dc349e1584b3141507000000000000000000000000000000000f226324f242cbc5f616c4a897f82bc5503ab1963ca38f30070c7c9916ef6bef5caa7e2e26b3f9fe68a1d59f19a9831d000000000000000000000000000000000a999bdfa10e4838ca69694272b0187f7d0198d6db0fd85eae688424fb09baa165c623dc6da567fe034d7cf9f9a0087e733a3a84eddaf3af8c5009646a899f6ae8cf233f535e360e29e2952088ebd7b6000000000000000000000000000000000fe85d976befdae8fd0ad33a4404415304afad1c5698b91bdc15abb4f268807c906410a6ca827320f5271c8fd4c8d6fe000000000000000000000000000000000cbff7963daa20c1d20717bcd47b872b3ecd5f38de1a467ef50936f13d6aebd978116a736cb6c5d676c6a9525bb0b7fc000000000000000000000000000000000c3d20ba17a21bbfe873d88e9221571f1bae7f02f35b8e677c9c42907673d765150c737f0011fdbaf4faa883b0dbf0280000000000000000000000000000000013482c68a5e1084faf12e8aec92cd9f0692b173556ac8ac3c7519beb4bd75f847f41ab9432421c631b14c885c001dce25112b5912aa3cba657d8de3dc8138fec92b391d5f988b82e19f16fe52fafea71000000000000000000000000000000000f9091a0df2c989e12a844c447287b704803d1532a3ecbcc890e6f6a885a54b969c53323c105b3d14d12f2cf766b8ac8000000000000000000000000000000000e54f3a9def8b3a9f972726e606195849584b7197ab70a28cf5644cde15e70bb6e3044042b649825adaf5e37c2d5e614000000000000000000000000000000000cae412d8a3ee3c5af38d7a65bdf2440d9cc2d6348dce0791f4a7e71ac483d7487b6c789be0a401777de3f57ec65de820000000000000000000000000000000014df09fd2ff406707004f6afa366d06bcf8bf18f5fc4b444b07c98b3f358247c6056a6337f5b53c35db45904797fb4455683e0b33b5463bc71283f0625269b2b33ead69c1eb7b23a996c31c514d06937000000000000000000000000000000000a8aa422e1d58fccc84615f9ca4a4743cf5efe3a1066c9819f05042100bb8784fcceffc8b3a739f549b42f34d62629e7000000000000000000000000000000000c737cf78b10e82fc0cc9823891f1a5f1e9229d61e8f369c589512d01e5180246db46e4f09e811464c6e1ad930226d390000000000000000000000000000000016017354434899e2285da6ff4b27fbaab633d962197d2ff4fa5f688c4a85e1817434cbef13a6b018df4e359d7b9ab7cf0000000000000000000000000000000001433c364428ac69ce4f5678aadfed4e6d076241519310686de01572da5cf78af4a98b3502519beb0dcf04b748d08cac5bcc597c5ed7f79173942a0250e618c93cd0917b37b5f354d63a2c02a576080c0000000000000000000000000000000001f8b803f3f76aee9825a9a960cd2f9e8aa931568b32be6169036683b4e6d8c4abba6bb73b137c7c6d6b6ea92f2023ab000000000000000000000000000000000fe9edeab60bb55990ad2c85c8fc9341e81de54324652c08c615a745813f08153bab3849dbeffcf4073f087f7c0cf0f6000000000000000000000000000000001955289b1210fa31542bd89f95188d60751b32e8d54f1d4d280975850e57db7b151b872bd431c528c22fb89c9b8784af00000000000000000000000000000000079c8a56c72adb9fc9baa503db394635abb10264dd43c60f2c82d041d43240321ac1028688d92c4696395d8840d52f15f2613a8e50fbc6683ecdd7c7fd38b4caa8e5dc9778909fc8680a58b16ebf40da0000000000000000000000000000000000b0fd79e62c6129fa115d821b8f2a58a4564f5ccbb14088f59d5e6a17a64e803f32bf8e5a415aac4d6491612d95ee8f00000000000000000000000000000000008d837b6c70468e1e10f6b979b7c0694d65942aac48b5baa829c191579186314ea35fe440e6d843fded02b95f9816890000000000000000000000000000000015a05bbc4607b113b37dc0b4b8add23736e0f1bb1e48aabc15500fa6941b17153918d256b6442687a432dd9ca9a198c70000000000000000000000000000000003546953d97306266bdd359d4daa939e05c0466691de59d2dbe3584e2ebfd9a9e1516cdc9cb643c5d31731835dfb07c657a747bc919991ef9b7b10388bf3f301fd910f807ccd31e322be46580a71b7c60000000000000000000000000000000009a4366299290c3c6651b22865fb22cc972a05ca5981f5682574851e41096d531e375e981c4e1b1cbfebbc70a41bb6ad00000000000000000000000000000000001e6fe2097fca2afb8385a3100dbd5ee1b7ae972e06ef9f5e34eb9fbdc65455e1c822299e06a9dd5a3f71a0c1efd44a0000000000000000000000000000000005ad2ffa8861848c46722a7924ece68580fe44e03157c982b7133361e974b59dab7b75358fe498fcde9f68b5b99f23e0000000000000000000000000000000000adac33e0b7e6740c980a4f297917fc4fc13f53a71909f2eecd0067656c6f82c3b371cc638509151bf937f8257aa415d86ba09829f4bbb383e2e131d554c42edf1065022975655c07df2b3445a3e6cbb000000000000000000000000000000001462d509503d2c33829c3fb5380199b79b970c2ae7f944e54a6d0f0deab3571976916cfc311ea6ce6128c467665fbbd10000000000000000000000000000000017f6fe356cb0dd5bddd489c26669f0f365260bb48a5f862e9bfb778a7ff5392938b905759718d050f7d93f107236cc75000000000000000000000000000000000d9b3ca93c5133cabf3d3daa565bc6b51e63b7e37f68f3bcc43b9b3ee7db15f8bb33052eb7e332ae3e9ffafb17cb77d60000000000000000000000000000000017d6b898d9799385990c9dcc3f72ed93333486b98349ef106a230a71d768b75cf56cd946f5952075bc41f26dca9c83c003fd5e91f590fbe171aa3f006617b20ad645626c970c2351e048b2ac3773213600000000000000000000000000000000158e5e008796c10f6050826c29523864d06e68977cdc95d281a8606924aeed0b475ab152bec5bfca8e0ec53691b307f50000000000000000000000000000000006fe8e75328c067546eaba93f4be2b15513bae4a3458112c3ffa457d15c23636816fb469f071889380f31870d713e949000000000000000000000000000000000b9b21cd58f8742ed094e9b770182f6f3f855204d869e53c02d0c242a133e957c53c9fabc827d6379b39541170be313000000000000000000000000000000000014eaae1f0789f0b1e8ad3b452b4ed3ff87bed49ffedd13c8c35c35668c33537b63050c06a5bf3d88d516cddac13b4c935ee16785c004dd2a01920c52d3244e2160fec2d17a519974d4331527cc627910000000000000000000000000000000019f976b3584ffc188424614fd287eb79f060c55e9b3dd2f3eb99760a7cb5b70e2b62a0895b05e7cce2e390853fed61b3000000000000000000000000000000001117181241fead3865eba4804ec2c14f571aef5351d5bce29399113d007cd4e9c262af1c77daf9183346153e562864b2000000000000000000000000000000000f823f71035a4870be2ef20bc94e97d74d18c0a1be9895fb27c54df1f663df6f9e6e45ea5fe4502143a84c05e517b02b00000000000000000000000000000000141250f392fabd4566e0cd3a472a4b2971a432a3a5e1d9c924866c7a9516322bfa691e9dccdd5ef14c561bca6dd70ba204a6d6e29336015d99e107cd312e300bd54f815c785f6008c47c99fa008452700000000000000000000000000000000014d6827b9bc782863491bc7c544263f58dc04c18e08a87ca2fbb5799c4aa70bc039416a85dbba67dd83bcc27b70748670000000000000000000000000000000016c2816e93ea9d4bd6e42a9720cb89d637d88e00074da3300c6409be98a03403e9ac15f83167cdeb13800ad174ac47f10000000000000000000000000000000002aebc0116a62f93a6e86c7fce86745618e08f4aa9cebca7b520e9176bcdf1521cb2bf7eca7f7af9487fdc82dce76bb50000000000000000000000000000000010684e3254207c4ccdd49e4775198df981afcf7d9f89b894e204c5dd84ef42b89fe3e2f6b9278470e6cde4d3f4abb3b003f9cd3873dc6243748e16e4806f8eaa339edcfdbf4408a8e41a3df80c9816210000000000000000000000000000000010ab1d5494509060c9784b4744a0572a9466d6c374524a6d338ea12ac5ad89519217c462c3487e398325439311bea86400000000000000000000000000000000197568cb53ce03f00aeb04278f355da862be757366dad14ca6d30b3a537df9855a1196010773768a91cb4bb664a34f0f0000000000000000000000000000000001fee249315794d30eaf929f44b99e07927194c6015ff34a4530698d7d68239240c9cc48530d52ea06218a826a655cce000000000000000000000000000000000645b5d701bf3422228576467120935f014c754dd68bb3555b50aff5ca04001a26298982c97a64469aeac3432784efca34135a2e7853c74725bdaee1ceadead7b4c7d729650df6544bd525c05c94234200000000000000000000000000000000113e17730f8dd7258157085c30cd9d1950a26c848b55e3a8a55865eb567edecfb09f32ba27fb3e2096ea00c30f31ced8000000000000000000000000000000000076db9ccf8df9530b64cd43ef7b496d1f432885062406028901bbfc5882fd12533f84eb12aa2ce8b7adf9dd980db0870000000000000000000000000000000015e487de49f1e494ce9907cf0ed31fb0a159c5290538ad969b2c8a504986dc9cccf7c74a61f622154e928aa2dd689c0800000000000000000000000000000000195e887083a98fe3f50a9ff4b342e004398cdfee55c4b02a4db0f65a77d3c0b142a45201674726c96d5f79f8604d61860033fdcb731830951dc3c4b33f06310eca51762cb7279039b3d7d9ace93c5f2a000000000000000000000000000000000d80c7e50973205585b20a068c64957cf4572eea40e32ffa8b759c38c6ad6f4468421f2fd6a6f5da1b0d008f625b3e6600000000000000000000000000000000009242dc1de055aea82b3b917f88b6232c550c3aff41241a7e54caab4c234d29b5d8138968846f7c754d73ab3b4e7913000000000000000000000000000000001188c31a9d8359d737576f4ce7a7900314aca0eb3b51baeccfdc9245bffec49143a11b3331f9126b01de0c307aa4e44400000000000000000000000000000000104ef4835124fa6b30dd551653aca25db5a544af6782cd0b1e7d26178253e0e33cda77428fc1dbcfe6114a758cab5c814c8112ebfe12bf44e84796e8b0cd03a93d2164d6edf1f06a5c520330a177da87,000000000000000000000000000000000e79d18633c18ac818786bba87d09c9bb1571e179d8769f8fb82e2e2b7a6a8695c1f4f06deebcb84524e8facdcb49d0500000000000000000000000000000000149d0231fb030a1bec170decd307c10e72cf1cca55c8a1b67aa94ce61e4c7d2ddfd0b8e71598e1abb054355dbcac1528000000000000000000000000000000000090f5be784dbafb0a8aab1516c773720341de6176017e0fb43a275d60de54c1189144956d4876d989232b362b90851c0000000000000000000000000000000019dba28eaa6706361f285b3abebef68f764204c74ee93ea011db01c19591ddc6f98799fb3026c3c223effe4489a7c676,240480, +0000000000000000000000000000000018a6a982acce5693e632901f3136eded40071e8c7caa7887f302c32621c5bcf9478991ca519978b52f8f69415c0d070b0000000000000000000000000000000013420ab920c8ecad5b2f9aaf9b0074c2386b0b08c81923558770d4c4a6b206a865af8322e9755706cd5e595bf0ffe564000000000000000000000000000000000c0e5bf5465d564e3ce86d6b742ca687448e6952439b1ff44b86ee6461464e07f8039e8ae7a301c6caee7eb99e38fab10000000000000000000000000000000015eb8751b750af62f57971e88b436658758bd5712f98861fa07328d2b11e8725fb55a2a00252e0be06b0c73aac0f7b8cdbb32a4fd8b9dc58a382a7e436e23f49a134915372553eee8c605436221acc80000000000000000000000000000000001328927910ab502e573188271108706152f562b1d5f6ec074f8f9ec5eaecc6cd5e8284a060b65d26463d22c8290ea4ca0000000000000000000000000000000005a1fcc348122350981dd5090c865a2aeb851ba8b6e0443c32f48b157ba673ae5652a70390888b3458afe6fe975321700000000000000000000000000000000019edc749a9799c8d3df75d4024791943a8fa02ba0cac90b6819f0bc42687b044457bc7cc6073506e8fc19af37f224624000000000000000000000000000000000fff20fb2b554b63758963c1583b996ad450cfbd5ca9952e38f38a8994809096086ed86311f7d73a0a5898ac261ce09e57df9664d3e17d9d46a886efde4e37e38859893113558843bc019699eeed8ec00000000000000000000000000000000002a7005dd32bddf1031f27c2ab999604c048a37c39734db48a30baa86c61ef626cf82084651ae9ba8a265333060a408d000000000000000000000000000000000421bf913a25108b8f520b2becc6f8064029dc046d0d5effbef31f0af59eee71cfce83fec8dda7983d50c6d5cbc8329a0000000000000000000000000000000016c75708f1dbfbeae3b06e5e9a7fb676c27100b99deece14d979b32a9c3cde6e9e96c8560a00aafbe6e7decc84e7e2780000000000000000000000000000000000ce23c27b5128bcffa424fd1d181d21b06b77bd6549ca5eba9a28cf18bb9a979270f6a5807c640dde57a0cd4f3af8cbe2b433b7a95c26e598002cc00b7904816d59baaba79bae7c6a7c26dcc48a487e000000000000000000000000000000000690c7ab321c0c93b5ae4ed77843ff4030e4ffb504c685d28573e98836e8e56dc19d662ae9f496a346bf2a8be5396741000000000000000000000000000000000fbbe3861a8d202b10801cdd606b50db0ad6ec7b923b90ae81ff5443676c3399e249e9efeb47b72d2b0a54cb0594686500000000000000000000000000000000148a27016968f0258e5eafe0a8182c22091873a5a58b27aa2160674584e06d5b2f46fc57a00617af18d0688df75294cb000000000000000000000000000000000148449d00b3d1b5b43b08a0c6e909a2d9c66920b60224a2c6a2521f0bad35b99e3bff8be0effb2f7f34438662d7a4882897583b53567bcfdbc63ae3e864a9cda24bb732694a6b27415c5212c7f45a9400000000000000000000000000000000026b55509b81befaf6baa682a3e92a0ab423fdaa84d2897613fd31acd9e1590f81581ba0ba87d68af76b01c36093e183000000000000000000000000000000000c675e190570bc5173b8f508d5bd2768c83e7f56a08cddbc636792dd75386939942827617c4aff8628a74b74195adea20000000000000000000000000000000014f59f38ae9e77f3a76478ecd47f32200567bad11f191d303cf15d7801ae7b5a3286095fc8726acc9818914b27a776bb000000000000000000000000000000000da89fe9493b2d9d46596d80162f5831d4fd8cbb83b46e84e95d5d684eb927022ee62ebc3519442007fdc543701f97bd2f7ff17e54d759eb9c51e16cf6f12d645bf2d091427416b4edbe1dd21947b4d900000000000000000000000000000000170e52a240a7ccf2d57ae92ea8dabe62ca4b458a5da42319ae89cad22ebf13541b0daccafa1b1d3cfcffe81b500c4cf400000000000000000000000000000000174879425f3bfd40fb74a88e3dc578e45b0e0eaad94da009e4076dc42d234d78248ec3a035666dd6de235f87e1a47bcb0000000000000000000000000000000005aee47acc3260d11fe0ca16050a29f92763b3cf8ac78da52b3b2b3e26d8ce7b6ccc187fcd81695aa456e9b94a84269b0000000000000000000000000000000005eb297abf35b51d57474b4989dd8f793005bf8e82e49859c41b786ae39217b2321299829198bda4aaa261a2723d43d6ce0a097efee666c22d1dd0ae8c8e11283aae781e1deadceb3ebbcbc5e5280a61000000000000000000000000000000000e49e94cfa35d8ade2b76865cc8be04737d00b48b195078c8085cbe782232a544cdb548373bd8ad0282674ba5c96fe0700000000000000000000000000000000047d59661f095c41bcc27da5f260f13a3fce334bba216b45df548894bdebc691fe779ccd63d99a9872973ab165a90c01000000000000000000000000000000000772e9a9c22bc7352fdf74915bc464de99ecd96420ef1af6e8bd5a05d73fff89c78e28eb340d4967e906f28afe1320490000000000000000000000000000000018bccff27bf9d7cb2159b9f2d1faabbf8591b53ca8e67e661d9f44f6dba6296e3e46ac32c50128bb5fb076cb8f214e277b2baa349884b54b542e3993210ef002f70c6467c7d512801f0003da789c00580000000000000000000000000000000002d947e728a3b376de520bf78e56452930de42544241180906719a24d72df65f8250402ccaf14d69935b1ecbb0b4d34c000000000000000000000000000000000d5614ec77a9f31915dddb3e4bb533db001702891a45f0bbac49e73d9c19a235a00442b52d452d77018f883706a616f1000000000000000000000000000000000dfc6a73a8e36b7b2d0614b1c6f7bf1ae284ed740c768f08416c0c09a601fadf3e4d7b17a93601b1803d19a04ccd570b0000000000000000000000000000000010d6a8e4eca2e818d6dff13faf0fae44a7fb90be436a9ef3aab05515a35cebfbd53e9af866cde1745f0e2c3b045486dd2b94d087c3ea101649ed57ff308dd3ae0d25a1ad8884763cea1b0b7c56a3834e000000000000000000000000000000000d6c5a6fe9b4d4580f8e1d89f0510bf5dd04e113d6ae5db04af2553bc0eb3a32fb881300f638fb33f7c4bfaa10b063660000000000000000000000000000000013e001b08191707ad98e21b3e0830286c6f3bf587b971dd4ce39e55f06db427676626a5c31c4a67a996a5725ec8f402c0000000000000000000000000000000012f86ed85113ed1abe9dd3826423911e63df0dfb51ad3d1e0e0318ae95991a6a11150176cec77f9c83268a322cb7e934000000000000000000000000000000000dda719cd2cf1aa769f94c21af20ab076b8f024e0a4903e38ddaca21b6bcd6f00baf7e1ed23259f135eb8bcf9c3f97c44f8c35b920a35b71dcf8d15a8a826e5a7c2a2c4f1ac2c2e3a6d100363e7f541800000000000000000000000000000000195ccfb9038bf9e637b88c83c552ffbd562357792513b15f703bffbd373ebaed715a6772fa7e6e5678c2e6422811dae1000000000000000000000000000000000c5a110f31d71b12cc42974003ba39d99dfd91769c2e93393449083a9b84d31473e3a7dff7ca40164e6e7215b03f44ef0000000000000000000000000000000006233b2dcfed96559b565928a494f2a50c2c375b3d7c60ee6b286c538f4fd5ca6f8b2a61654fd04d679bb3e05b9bcb03000000000000000000000000000000000d42233b7b5ad809c735c89c455ba1e8fbd623e1602bc729c01d362368666e4f90e7b076e32468041f3f5665c6fddb0d0ae6101fac82c10267770e74a0ee16b5be6eae2d455d742303a3c624d52aa726000000000000000000000000000000000f6d53de4f8b20de19b2fcbe8a6b8b8ec4bb801bce7363f89b133532ca7ce4925312e23c618a0182d158037c0d0bf07e0000000000000000000000000000000006ce094e24eb14b9bb1b4a1838d8b6da5f53b5c5799ab8dc8934b488cbabf698b99abeb016259a4e1b0f626d27f2c950000000000000000000000000000000000874aec7c8ac360e3980a6e2cbf3f7468f1df7a8d9158f8bdbb0f387d19f3b05326a081129576251ec41a926f670e58f000000000000000000000000000000001711c9b2ed7e2f789b29073f180e46d0c373d6e75c587ece67b8aaca1e9d9b43a96d04dfdcd42f943eca48e240b72ba8002fb31d0372e7730499b26d617b53ea04821c6eae922326d755a0df31b559ae000000000000000000000000000000000e8ddf88269aebf190bf9bd7a8276de92ff6039e479e42a490fe4ef00f646b049eb8ec4b8e073caa000bfcd86ee8724a000000000000000000000000000000000a9623655c0121ea0575de714e53c9e304fa3309f00828ba0e786112781a38bd458cd67864ab17929448171b5937c1d900000000000000000000000000000000198fccc4a333322599697e904e9096240b9c54f89ee6db97475beead62ebf730da1a179409133698ef13abe1310689270000000000000000000000000000000017b059ac08a3fcebde5888bec4d7cc2c70b147b3b1483fd001330637ff1c036faebf292801204bf2ba49350795708dedaa846e68337f4e9c99dde506a3af792732342e3b836376d4816557fc1fc9b916000000000000000000000000000000000a36274f33b4dc09e03a5ad648af0913e5ee95af83df8b4f2a158456aedf0a0528f9b4832b11162dd67e4d22b26e9f940000000000000000000000000000000008ce96d8bc0aaf2dea732dea188870d398b1f3c266b9bf019e1046cca05002416c910e02e998a1604a17c333c65c99a0000000000000000000000000000000000c1a0e4a80bb0331a94ed14570053f941a0438794e6f19d976cc62b3806a565697720ea03c2531004f13453991bd99bf00000000000000000000000000000000184bdae93abbe4d931a6a51ec85bc330d6181da2d34f2cc530e56b6803515ba87f5719fd6fce6a1a8bf1ee5a968bbfbedf9035283f1afc294ee68b2668870aa45e483d208483d9e967b11990cb55d8600000000000000000000000000000000016c3782daa55312a7cfa02c3be73ed75f4b726df5592351fffae19121b5cba73f427d35d5a2df7c63e2a5c68bf57f3800000000000000000000000000000000018b608343616eff759d512c97257f2103cb0909afb4c24a1cc9d8204274b7c9ed51bc762a6280e223a6116a9b23d1f1e000000000000000000000000000000000c687c11a879ec285180cbae3d2e4219df4614e238d4cbdff148ce5a8d21647c489ade3bf6f738052f149fdbc76c8bf6000000000000000000000000000000000936b34fea3a2633b9aa32244329891e332745876d05f95e4efdef859b23ceab4869db562555e5c8edce87a6fd075ae54005df80aa522e889e7720a9f2e44e6e7e19c3160ea282ec87a4b446d7b1c45f0000000000000000000000000000000000d4636a5e13bb59878319af6bb7c98e5d247c2c9cc970b9cec98027de2d4a8ad12d50906fe302c3d055c499a3742ee30000000000000000000000000000000002b0214bb1ee887a7ff10d458fe35208573456f685ee2fb93bb470762c9e27595cb00f2eae7574c8467e417c63c2a960000000000000000000000000000000001710d130f91861230562cd7ab87984ef45916af8e1168fb17b9765183d9d3f9b2c81c649687842de495a757471e28067000000000000000000000000000000000dd15fe505b1364f134ee77e5e3c1a497a20849b6ec7e201813677a1569a9f5a9edbe3df4c36bdcf9ada139b20e048ec893c9daec43032946a9e892dce960e07d29b304000378145148b9a24afd151570000000000000000000000000000000009a48d7c55d24ba49f890791d0f6a8a5ae08a19177575dc0d734fa37b52c3adc45b31b5e485a5d4a5533470c3549f5f900000000000000000000000000000000090f680c6fc1f0588add04ee03bf821868b1ce588e3ebe384dae657ba7885ef74da0bdc98d9d9594a9b979d5b50b93df000000000000000000000000000000000314f6aae1e99dbe3ea9ef85db7e1693a30869f48e05cdb073bf8e14865a671e75abb875d1b41f13d4eb74fc802299c70000000000000000000000000000000013c698b76dd68d1b9ab41672c2b07cb9a63168497d1144b51509b602c5acd71ca6cd049616d949214d95ab7a906a8f8bf685e6bb7713f8fe202c05dfd18003eff261456026a5185ee9e68aa821fe7c5b,000000000000000000000000000000001747f6d3154e0717435fa023754f115ce2a2b3241b62525cb2833473d84a8ccf4c95e3ea030f2b8b0ccc61124095ac86000000000000000000000000000000001827ed7d84a61c21268857036e91c732b304f609f285cdc4736c951fd8954b10267a8505f25d8be666792358632058b400000000000000000000000000000000121ac61f59051e6e89a7c1e2fb4df4b3a5b7773f46495a99e55348454e1d9d42254e5e11b841a1654ff9c80b157389c70000000000000000000000000000000001bc60cd06879980bc6ef2ca109d31f12cac28ebe4d2a934076d720b12f430e1bc4d4260f40045cc7a862726521a69dc,240480, +00000000000000000000000000000000012a6984f0f8967c5ae6b13569a62095b5fe61ec607daff1845961bdd827c00fd56ef864802673dd21d90560fef6cbea00000000000000000000000000000000085ececa080d0f4c996d46c80a1fbad2ac9cff8b3e324aabb67182d79f941927050f025b633fd5119f30bb29b8e4b6f2000000000000000000000000000000000987518a5edfd5ae2616fc60000e117a4f1dd1db68195c3fb68d8cc639e4200945b2864d41ad86fb3e11c504fc1f9766000000000000000000000000000000000310939c7e11b93e5773cfd36fa70020c85396e525840742f994110e20019769abcd339db6881291639c193b987b68ae94b3c88e51af5822177b85978526036a426c9ca1077d594618ebb8fac4cdfc89000000000000000000000000000000000ec6922dfc74009c3750ce2540558c7c1e05cb45a5d651b96427c615d8fc563219215a0ee431c0a4827e40b26c4f8d3900000000000000000000000000000000040a4189d002a0e1ec600e71303575e82414e6400f06b9abf57151a28835d454f56421a6dc4049902bfb94dc0e9967ee000000000000000000000000000000000dfefc7c163c34cc004e9d97d812b2717d4736d0d1c722b6bf1a29676a32c8b46878d05a2d137cb7fff5fed8c0f02474000000000000000000000000000000000e3f0c9cbc778693c8ba88af8306d45477493ed6be1bdd9c81c65341239eb510fc948142cc30b73f570819b38f13e20f6e456b39f4efe6581657f5c701c696fde8acb59e856943f15cdd639c1fa68ed70000000000000000000000000000000013705ca4ecca16559713df65b376c7c5825b4f63d001ebbfce9cd1b592af5f2ddb38ac7c5ce3c5f7af4f39f909887e8b00000000000000000000000000000000179efff38ea1044e91ccad467cd2b49438079ccb4d0fc692e79e0bc374abe064fb9979c4a1f4b92c15cd1b042b501d5f000000000000000000000000000000000b6fda2dbf6339af225515681184843f1a9bcd72f7b1389f186f8d0e048ac16e20967c28e087cf09d7bcac597a85398d000000000000000000000000000000001946fca8c816e1e11187aabc40dd2436533d537ce4639eb2d08630ed2ce402c1806b6c2b3e04a960408fd4d2049849bae5d306f46a31c14de7b2940104d0a4424ebaff805a81f1c4a910566057c81604000000000000000000000000000000001802064095d029d3897725eeb93ed6e3b090390769026120aab6977d0de264a262aa312c5777ba322c9eac29e5396fc6000000000000000000000000000000001410f17820941e6a67b1b4993496cdcf0d4fa2d4fda3d43ee985f2606b1408aa9c9ce412c80c90a0c876cb5ecb76878c000000000000000000000000000000001514e9b2c65ca86713447f2d5bb8395fe8552e059829afc68bc43ba9267ef41ec6d69d06e7407a731bcca77ed5d9716f00000000000000000000000000000000025b5bb18cad46179fab15b2ccef17858f9259a90ea4548852b8c6fca69f0ecdf0b175669bacff1625a7143e762514194ff6d13bb0967945ff3b6fbbc104296805e4fedc3c25bb55b75cc997834de6b700000000000000000000000000000000146eaf5da57b6ac788f8caeb4b2ebf7c8999e03dd839977046ca834fffa7e57cd949e3fd44999a007b5dcf3c8621ba2f000000000000000000000000000000000d859632d3424ffe4227ae14856e05c4e750545cf276c97aa9ec03ebde334144eea670dc68e92b61fc775e477a2154040000000000000000000000000000000010b44279c0c80886e52fde5e71726422da2f9457ff86b21426d80356fad95d5ff3a7491002364d9de5ca99c2500f344d000000000000000000000000000000000851b769a691f0ebb53ee3693833881fed8dc6d9e5f1dfeaf4ab1aa7ad54e2fcac246b70d81110451ed78044a98d1547de4fb2dea292b76d8130e6aa8aff5edf0097de935b252d42a777d4d9b8615ef100000000000000000000000000000000131c9a76109929fc977a0a6eda0a7c71cfc744f5f3654e2221ce84c70787598e24c5d8049f92a7c4d78fdb869cbdd1ed00000000000000000000000000000000049872d2c7d472e090d2975daa64fd96f33e7f934e739633b1d7fcd5e771673ed8820752a0d5c8b0c6933318293a4f27000000000000000000000000000000000dd68fbb592a3957ef893180dd758f75978042add36c91b7bf87c4493b0baa875e1854fbc09e6856688cc241b76ab5a20000000000000000000000000000000006143699816cad8ab7583a72b6064fadb6caeb51c8625ddbf7b2911426cf438534da1bdd13e22cd545495c486c9733f7bac5c50a3a8a37111114c22839c88ce4072940c06f0d8b6d53fed155d0399ed70000000000000000000000000000000006c14301984607d569ad1bd774135e7c9e328be1fe54c3b543276bd06bc0bfff11f299a5eb43b5218c3605011d0ea6d80000000000000000000000000000000012f0a848022f95f4884380a5b8e3637a41e3c399a8d2765aada85dcf4b7c2b559122f792850430681a58ca153be2768a0000000000000000000000000000000016b4cb233e1bd59b7b362c64620eaaa5029c173a05e2278774ad6ed746c70a2f6e76c237182f5d9d790966ae69da5d44000000000000000000000000000000000c277d54a7a72c8528188f6cf29d934cc66471607e5e30d493cd11be6b203bdf734aaf37b686cd7101e8599b69446991c3f37387bad1af3a896a7e66a80dfce2df1709fa252b6fbe4334d02bdced432900000000000000000000000000000000169a3928266375dd5793b7504727f939ef0ed52d69e569b1b75a0e094698b37bc70472578beaeebfd0c3df4bce6177810000000000000000000000000000000008936d470dbb86db1567bb2fe7c09971c6d12b07208d9b1b403c20fbdc05ef8984dd576457fc6989470e40ebfe4ceed30000000000000000000000000000000009cdec9d80f2bf3ebfa9a3316e4250741d0d089245df2fd3c9bba4bac1c2dadfe212682166a0962f78c4bf25b618da900000000000000000000000000000000016521411286cabf3fa2c8f72ca62ca311738fbe63717fd12916a4c9e6af9b05d1f5d65cf60e84d9fc5f7b7645fe9bad570fbf5da3959a49fab7e97b3df3f2a38d16d714dd798a1f04ec2cbf84fce76910000000000000000000000000000000006a827f6149a320a74d9d8c1ae8861c1cb963b3eff899710eda642dae6ed4dbc247a22131758d9f843c62710ce083208000000000000000000000000000000000c83a9fd96bcfd4adcfc6d5a47e84108bd763366e91bf06a7431c6c3a107cbe5647da99ee6c1e57c376d366b21a923df000000000000000000000000000000001604d5c0364afb5503b0e1d52226988d7f7f043ce95e7c0a09d7f96e24a58f089156f0e6d19022138170c1b4b7dd33560000000000000000000000000000000019a11c86f78ce462f46e0462052cc3d342596b329fb62a282a59bbd64c345bd266922b1540e40aac147681754643c2e3e538bcefab5d8d0be5fc143e632e86fc065af3f2f621f293b914980abfd6a0c70000000000000000000000000000000015635de295c16841bf44c73639f047f735175e8906301746837838d124bf0d2a1ebaee142393ce9a0d58107c7cb036e90000000000000000000000000000000004fbbd4252fb901d0737d1bf4da62010c06d690a9584c7631ef5d36f1d8c37486a83f2a1e2db21f05c993fd117c662e8000000000000000000000000000000000f4cfcec1545a08e0e0298753ebcef5f61bfdb7c1b9af71cb4c2f783e4fa3948945d357e8302d99aca96df0cb0fc01a3000000000000000000000000000000000f543dad6d4b797f6fe0b00215a5f70f6340ac6bf7cb0bdfc5bc7698dbf0647e4098413dd19ca7af01685edaaa190c6e30b921d8cd2ca46aa6f3e0dc6ff08d77972fb0a248bd39e90a1e9f32be9e892a000000000000000000000000000000000ed552e94021d0912a0e7563462570cb572b189569eb847bd12ebf976d22343b9ad04d400ae98fa184b10ff36720f12700000000000000000000000000000000178727c3e6ff33be9894ef26347b104023ea0bcf79c1a33afc26ac0ee9879344964fada757118829214cfcdbbc0c5a30000000000000000000000000000000000b0a6a575afe5b0c1e287815612fdd3838ab39e8ee7795855837588614715f6687910c42217ad52c1b8721a9e1c908dd0000000000000000000000000000000018cdbf244c78cae1993400ae164b42c09dab4d8e3707a69e25ffa8d0b96b8270c022c0375f933f16f45c9274132a0a633a5ccd9436b15d4d04a8ee9894c116190062c4e7cfabb047b585f3aa1eeb460500000000000000000000000000000000070636611f903f55cc9499481bc3415a6de62d5e6bf8bfa82a8ce665f85bcf01690118441961ff46ff701e361db208500000000000000000000000000000000013d22dff8f6f86f659ad17ef91d90a70c180538f03e10de20c445d22e637015d51a311a3daaed90712d04c9a3d992d12000000000000000000000000000000000db3535057db95fc262f8adfd7f08f3237fde5f0e2aab589d4ddcd9c23aadc437e13644dd3b3534dcb17936a7c610cf200000000000000000000000000000000044c177d4484c07fb04d1dd477b188a2c157973cf26075001d14d2b07ebb9dbf8e495dc23b32a2419621e1c129b08c5ac7a5bf2cfedd7048be7ac7d2ff19d4f8bf0a94295ebdc5e792393e0e4bc27d56000000000000000000000000000000000e10fd069f2f5fddaa0112e70ae89d1ecf034defb24e2923731a7c0068780177c186fde92a3c254a1cbdd255111a4b7c0000000000000000000000000000000018363e01e86e2e922ba435651ad892bf9288be14b54dda821c397ae6167f9478c8132e92b1c2cb0c4037a4e020f08291000000000000000000000000000000000301b5ad2d5c35ebdcc7e7cd1ebf0405cf204d6f5e30ae6f46d20534eb6d7013682c5ae1bba76d2811124ebded0d2a590000000000000000000000000000000015fb3a8afad778031d04e094cbde5f02dcc89ad7b7d452c6c8f41be336a4c8b26e75cfc685b8776cbe5a487f09c304083563651d5f5729a0ffca6b383d884823aa3b0215fa057bffd8142199a16e4ffe000000000000000000000000000000000a7880b00f6a3e959ff1bd207fa503eff6e7279e701e37b40735e2bc8bf49e355e92edcaf23aa3654bb26fbfb07b5fb100000000000000000000000000000000113d9b792f4e3dcd958664a8778dc4b177c430d8db9da7805595e40293ef2c0a40f7a843bfa70ec134ed89a453f9da50000000000000000000000000000000000d7f92148dca4a9c96c47a0eb284f1834cf3d141be7c0d9a7a060af6e28e45620d8255e465e9a0d8f78b2ffe17d6b04e0000000000000000000000000000000004e7917a8f3070c656d324c9a816236842fbd6147d326652667e7bca0666d214233ed136dd9464c4ac619d46c28e2393833323c3a668541ceba18375531c3781dd98525b49dafce4c4b3188c90f3f4b500000000000000000000000000000000160cb05390b54151f6b154b396bb400a91fa83d77fabdf31fba349d1bf3b5dfb6476ad4d714af2a2963e41b077bffcb90000000000000000000000000000000012885f7ec8e780cbaa90a465b5706cf07d45bda7755ae3477c79adbd7956b926e0ef5303fc13f0b97349ff8b754dab500000000000000000000000000000000009ad7509e9e7f5018ae3d1280e881ec12129cbf825cb6606459211ed7b358a97cbe430e94dd9f5e4f6b74fb7287f862e0000000000000000000000000000000014d5d2ac2dbc3d5a061f4e52dbfa68e1eb1d3c818ba26686a3171e310c63cfeb188030b83407070019dc5c42dd079413d422e21fbffa7d55270eca9c96bbefa29dd915aca266071673e970daa0ca9c050000000000000000000000000000000008ee93fc610712411634079be0bd96c3969b48955fe5478b7a31c3ba7639c18291034167eb62e6b15c16b0dd5145edf500000000000000000000000000000000158cb1731b71905d7b958c5407f090a2c8a9319017719da143a3f4f3fb3982abb83b8dfe14facb014321b4f5edb5e41d000000000000000000000000000000000a9f98f775f06055ac1f137cbc1f95f4afa0d1c4935f536ba2e0569d874d9d76b7b86f71afcea07e2e785c7a6ee1c84400000000000000000000000000000000072f8988dd1ab0fa8037d3620068b34848c65e20dfc90612d123b6f9dbcf9d9d699d5ea73739d31ad54c22116365ab983ba7ea9ffda87131452b24a9efcdc91d1262d0d7550e5a6b787eace3577159b0,00000000000000000000000000000000161203d8db1381722644f87b04f47e4be2ea2bb105ea0e67678bc8d29f8a8a3247f8c05e057af8f98032faa93d896aaa000000000000000000000000000000000d3af4842627a095a2dca99b52d802b2ef8c8f3d09873ffe39d224333fceae84bf74780956904df6c1dcf5ba31be218d0000000000000000000000000000000001c79fae014e55e5d0239645d618593bfd5aef665b3e636dac5d191a8b88949d207cf0ae9822ce8e1997de302b386b8800000000000000000000000000000000136314cc68b372b06e7771d82b7ce7bfd0e9fd306787e07629f831c7aee853bed80172121949a940bc59c4a0b76f0819,240480, +00000000000000000000000000000000099434adf799099f2e6e2fda4c905e1543893462133ba216aace25836db37b3dd5bd80af1a8c31c7fee56b5ecf9a0acb0000000000000000000000000000000008a6890e5bcacc13e116e3fe2d772ff49839803e4f81d6b088ecb7835b1ed44f2bfa04de1d46dd352346cdee71774e37000000000000000000000000000000000e94fe40225e863b7bdfab4cdc0c1c8d1399554ebbfa3f2c95ddeda74b3dff03d5cc78e295accdc9f02f3f89b4953de3000000000000000000000000000000000b23f2912fdc7a5fd1de69c1f479228f8ffc9f97c40845808cf17a6fd8131ea60285640d32bcd64c9be71d419aae82fb16aa2cadacb129598aa459bb2e6b7fb26d1bcb7a49617b6ef8e57018c3db1f510000000000000000000000000000000004c6f5aaac90132b2d0c6a4e70354ed2e724df7c3e6298eb9ae4ea92e3c7981944c89140c52e893ef2edb2773ab36bcc00000000000000000000000000000000021e813378be9ec30395b917ded5a0424fc7eab0abfdcd2328f725bbd6a1dace0a5aadebe40e10470df0c09b3f4b68440000000000000000000000000000000014e3fee16a833f8c543860ca438d763f764f488463601741a2331fa90efce9f6d54ee0fb7978460a1ab838039d398918000000000000000000000000000000000dec8bb882fe6028a4155e6e2bf48ffd314b5519dc4560f8f7410209214c4a8e37b2b36facc53f4db11ee63ff11f9f228c02014d5392d30863a12102d1c9315839b5611dccfdb489207f9186625138500000000000000000000000000000000002d107029bea087a2d53b6b371aae06c695fa85631450f4ad92c8948b09ed568b28948f80f1455cd22e2ad44697290b00000000000000000000000000000000002fab10cdd8bf17a633c8b3ee8ed2ce783f64bf978c384fb7dbd7e4f0da50b65eb9530365d982bcc17ab91a29eabc065000000000000000000000000000000001369237fb3241ac291a868e6f4610a5103d93aa915e954f18bcf348ece1560a12451723b96ad5fe162a6107dabe1c986000000000000000000000000000000000cb70b7064a2f94efc86060431ba4dea38bc64822efa73c76f3a4500ad23c452c8f2e72713b066a45bfa49559d14a719d960ff678e1b46ada4f866adf354ba8c1514df10ebe7d88d2c8de117ef5ea2490000000000000000000000000000000005ebb9c8202cba234851cf5e060a4114c6fee0632f37e0c52aeb852637f362ce64403347d336c32617cc59f23cc7c93e000000000000000000000000000000001126827b6a0a8adb698854c0089276861e3cccfee420512f0966df78ea0d9c55e85a0536f14ad40e649b8fe4384c836c000000000000000000000000000000000998549680649b294d506c529ade746aeb087f75d62a246b7abfb69397ed67f0f2ccb4811219b35aa894b2f87e3fcddb000000000000000000000000000000001027b604f877ade32df8de6162251acf2751a9bd770c21f22dc819a4f5515bb276a246ad667fe7881965f0b083d1f76304753af76295f72295645243ffc87ffc2110c9d8dfd20b464760ad965d7a97940000000000000000000000000000000005d1484bad44069b16d1ef4e9ca1db70ec6cd82eca645c2fbd4029ab4ca33d79780ebc144d8774d82518c1fefaab38530000000000000000000000000000000019abc7063361ed64a5750b70bd59283e6a61d55d49d8c2ea2f1be8ea425f040d3865c399a66c253bf38355360f06cdd40000000000000000000000000000000010a97b13b3b579ab5f7fd9801d9e4fc40f3b2b2acb9f21bfcdc6b6a3168720fd0abc2f77ccad01be6a6e268fddf3759c0000000000000000000000000000000004126b5454050d761047e5da23c0b2f9370996589c04f255a1ce8ef37a3a7c8078788a0125e4aa86aafce8df33f322d3d1b8760cc40d093912fb073c5012f910ae90f0a979cfe6d81c603adbb98289030000000000000000000000000000000017aa7a3f1ebbdec6abe12abea12ef50a3daabbf96a5f2ebfb517885f0b7aba1e927c682b15521529cb9e1f87c59be99e0000000000000000000000000000000016e23f7effbb9dd34ec1f6974115e7f0d23cc4553d86e6d61a0c98f47d09510e06b3f987c5bcf4bc30e20ae9684da74e000000000000000000000000000000000f3905dd4f99cfcfa6152db53106b4d1f6e24518a822da9388d8ca1dd654a4b8315697328571691f105d1abe9aad3dae0000000000000000000000000000000006bfd10d33df9326a55b35aa6d2bc3e831d4c3b5959aaa35613156e5e19343b74e34ed2670c43ba1a45cd3d91f055c9aab79d640b042664b23667d6c60ef9a5d59de72aee57a78d75752b350ce56d8da0000000000000000000000000000000016ca071d741363e7c3297355e49cfbdcf03d419813ed7b329cb2b2a26fc6a46cc52149ca3e9ca3ccd7284cfed97b985d0000000000000000000000000000000018da360fdee88e806ea1a61c01e86687f8e5359730c36c876ad2acb0297bbc1ae13d790d1edaafdaed65db9dac02a74d0000000000000000000000000000000005a46e4572f667b46aee36b8d377c249de25e797b31b822474aa647ee68cc7d40b083fd0a1d938e2b8d85508004c73f40000000000000000000000000000000011701bf88d4287c98996ea561c1ab2f29a5da9138338c7c7539a5fc8355efab6f58e240df4b0e0cb7f01df74bc8010501d1a2965e995bd4380d4ec52fe8e65e7fd99b1ca9f4f0c656adf7051c4b9a99a000000000000000000000000000000000576e79e507d250eb4040197064b8898b0142b3a2551875935f91f22705bfec6da156c7858fbf77028d4a00957553bea0000000000000000000000000000000015d39a325181d6d1a809b1236f4a1ba66a9bfa6c448470425aa5c8ef9fd00b5481c51e8752088dad62e928b3180408df000000000000000000000000000000000aafabc2f68a4933c7d734660e422ba154e37dd90114272e948f79db4ca51d5ca75d504cf74f2dd0479871d69a08386f000000000000000000000000000000000b017c731f63bbaa8fd0b0d9c17140060429f515d2e85a938d10f6529deeae4818c29b9a628802d0ffbbff720339b7bf2cfbf2abd851d2c1f55c56d4f8b11b196c020c2584cb03764580d410d66784d400000000000000000000000000000000028c4dacba5f33ba66368c19491f4baa6aea4f309afafcc8f464f2886b1d05b6397142d02f0295fd50825819621673a1000000000000000000000000000000000849e1b630e8db8ef039f280f8d401957f807ca90479745b68c3db1b5ce3a02fe2c099ddf9c387d7ed76ba75d6a9be9700000000000000000000000000000000013b43fabc3d4df82058db215a69776ed5dfd4c773d7a013dba3b4ef5cf65e25f79d7f76a06ca99132d6fd1fdadb59d400000000000000000000000000000000072cde8eb3d3e1a7f7e4a9eedb8e56f5e103db6de6ccf833f818f02a0706b2043d4ba0d5473bbb6472e8aeb28364e1d8214edaf16742762baa58a3d22d5bb2305cb03a1326adc68adcd268428f82a1e00000000000000000000000000000000007a33b95f42cb1d1ddeff3a199ccfd9a5d47c9fcb89dc09b5b3f59dde2b47d24ff29931920b76ecf6deacd70e83576970000000000000000000000000000000014c0a63e0152f06cfc32e6034b7829f9d9d09aca0a6ef821dc61ae8d99b77d76c1b2fafb2a14938a82ec72c4041ebd9f000000000000000000000000000000001433135cd913b05b3f58b2e9c1a3bbb951d2cf6c92fddb21bd5e1d9c44e464d5fe98f0791044d56e50b81a83ef6cb271000000000000000000000000000000000be12ce3bc47bf69a13762343b5e39c2a2f285896e5d1b73c55203cae2f32cccbb4f7b8230b2026a0c8b2f63db5e5bedc1f38916d6bdd5d379967dcd058ebce5887ef2bccd5fb7c2bcd758e374a195e2000000000000000000000000000000001494984d478784b2ab3ba27464109f99172033fcd5780a48fbd5a2144354157f6fca2d70b15b0081dfd306ab4239cecc00000000000000000000000000000000078aebc22025af53c6542abe56cf72ce5eb11d3f19212a0f7442d0a0df907c8aabe0ec01d1245ca237a691e685011bb8000000000000000000000000000000000415a1804a46f4595014ef29b12d99b89600aab1d98352437ab8342abf479bb2215bc687532e75f140918b3d030ad4520000000000000000000000000000000015e7b0dae7e3e80eee3c7a9ed4c739288ac2192f7d80b2c8cf9934cea5719081803b207623c771051d7694e705744dbf1cb8c8303157f23987f8a2d206f3add697b9d0a303393008429e93cd35711f74000000000000000000000000000000001470f82372e197a21aaf46cb2bd3c0b77c3428bf2ba073311e75eb65471a8164753ff1d989560f1ce477952bb6555200000000000000000000000000000000001645b5e5b4bcb5f6d34ac841e3a80f09a86a5edcb7f2a7e7bf549b022c0073e01be82e4c9e5c8e8de76ba367595639af000000000000000000000000000000000b43f6572553154e2530fb448d5bf20c3a182cc190149d3b1d75b60e45baa048f44884500fd02c434f9f7eac01dbe4170000000000000000000000000000000014adef5a52d76a267f87d9a8b5e9f570e7775ca4f6a55a5afbf80baea311b1866fa0689271799a654eddcfe36a6bb64c61ca9ab9c3df673b7ff8be098cdadd8354c17becdf82e7e99ce264174653007a000000000000000000000000000000000345a2ffa21eb06fa1d76fd81b1239147688093c6a44a40cae37f2af26add812884bed3e8b4643675b1a45320c64f7a8000000000000000000000000000000000c58eeb5ffdf886d6319ead9e6e190300ceb91d58abfb79c0a322de3987eee73ab82092eea8e1249e83ab67e33b303e1000000000000000000000000000000000763a3fba513b6731fb501aab39a4697f3e4de89125c6884f9782bfb73e6e062f17d34555a04a8e2959ee4e1a2ee284100000000000000000000000000000000024180dde2d23cd88cd29c8142d32435d0db57b8ce8e309701fdb963533c1cdc2595e3bfc01d8c0d08d594e096afb34a681a0861df30946911d789a5da1f5b89c38fa1a8c0407b608122a18be05955da00000000000000000000000000000000022d2e7502c4d9587df7ecdbafcbb813b1812d76655cb7f9f57418d5ac83d4f60b84a0ab5b53a5eee3c3954aa9fc70cb00000000000000000000000000000000083212aa1316561a079cb8d027bc8f89161fc828d050c8837a24fca6f7f94b6dbf10d6032fed895a427f07827deaf3cb00000000000000000000000000000000021552b99dc02a051ea3af1b1bbd0a7ef64088c3aef4a58b18a29ca05e1f442f8ea2c8fdb3642ee94c5df501ff6898f40000000000000000000000000000000001015a7987d329cd1eb5f991c270643a05b8e1bc35467130e9f53c5d96fc3c8336a00c060dfa2d3165358b51b6a521e56f0798b448ea0d10c84e2a8896f153b1ac3b84c5fed6a4ba6c932260bf01d34e000000000000000000000000000000000c19c3b9d7c7f520968d8531966cccbe6f0c3fa0938480ca3591b7489febdabd56a70ae55cc309e04d7acb3de6f41a3d0000000000000000000000000000000002ddc64023f0de2730d3affb695927eaba50ecb91cdf1f369a511a8cc8dae8913ada2d8f27a65e75deb9b8b648e4e2e00000000000000000000000000000000000311ef260debf2310fc31fb8ecc802200e11400909eba24b14d9500ff47c1c36ec540eb970c9262dac947b0c2053d6200000000000000000000000000000000199c19645375dea7602b74301adcfd9af259e1c7c20f377fd10d56b719f7a6e0e57d780c976124e0675c2a54aae3e0f5a8b7de8f34053facf1338b54cfbe38dad73121a0429663f484277af9a230abe600000000000000000000000000000000123fce6b793de0ce2d31f2c7c4218fb20f9db68946a7d57914174ea773d6e6fe1fbb1de141c742e0a8154fa1d81a91f70000000000000000000000000000000019f75536e004a61c6d7f466bfa06ad0c9375a1028eb7746406e7c71e551dba249b5c6284f635fe26989aeea69075b3fa0000000000000000000000000000000013088eab16ec77c7ce7e84236337e395690169a4ed7e44e23d233d36d5d25e6afde794cca2bee88fe749851a71aabe24000000000000000000000000000000000e627130da43a6ede3bd6f2fcdf008c8f5c7b7b1fa56cd3b367d3096317948bda115d732346e73b731d1921a1da6aaa18823cdb73dd076ad95679a9d7b11145c12a81b825477f799300d1fd761417c2b,000000000000000000000000000000000e3b85a3d6628e097a3d962f3c9aa37e3c5be43faf2a12cd9830ab33c4a904eda23027735bba563d38ae5ae8b302864b000000000000000000000000000000000c92be62cb091056d981ab8872292017cc22ae4eeb0cee03a60cb5d57d37b264fbed2d752ae9dfd76c0bdde1f1dd10500000000000000000000000000000000019e172b23249a17924b890cda6a65287039d0c32b2c0833409816cb21ceb73ac95928234ccf566287400a2ed7d9de771000000000000000000000000000000001276e206235392fdf65e4ea6210d22eb7afd7783caa0777ff0af719cc1822579d5b82fb4c30f07dffe7d68c649b9e2fd,240480, +0000000000000000000000000000000000a3d974770eda8c742e5d380482d36fabe18901b0217485141c3841003aeac4459ee28b280550e4643d0f50862bf2e2000000000000000000000000000000000369c2bf3beae4e8761f6c06d9bf5261bbedb177e609c81c9bd81ed0a17573b6e10e7f0512e06109cacc3d483918ed9400000000000000000000000000000000030253d0a050986f49c77ee20ea8e3e07de3ba72c39ffda877bcfe569eeb29598588f5a7cedd9e2e34491a059ac4e707000000000000000000000000000000000ce201f07353bf82ec894ec66c7012d17f3c7968b28b45e88f091510e1646380f902c1c5b036084f9497e9a91476dc2c9f2e54f21b7f2116c30d6e444ca82fe800435cbbd72a98a6d22bac92039c54070000000000000000000000000000000018f493dadbcd93df2c614af310e5aec4fac9e502843b8ca8c3de739315d9e9a380f826e2470c96bffa8789133f458d0a000000000000000000000000000000001768f8c3da107b9ac30a12b99f2f3a0f21483c0be334377733cee6024d85af91b03c7ea1c548b42e7a7869141816917a00000000000000000000000000000000076cfc99c16c270d2f6e34aff84832f9ee6493ab757b6361cc921823fe9c30f1c9b1664b650548dba767616bec0fd5d80000000000000000000000000000000006c5f580c9556ed31847b1a3527ac0b5b5f15b9c9197d3cff061c1cf72dc5c96cb5fe98535a4dca8c4e20c8c02158466c8cecea241dd6a924c9b9cc3d390fbf40ab897208ce9d3e4a148b2c30c25e7eb0000000000000000000000000000000010e2d7eb4e874a9c72a98e4c36701a9fa11051b683ac8ab9ac20d14929d72ff7b92a9048a11bde92dc2696467fcb48e6000000000000000000000000000000000eb29e621e9d0af8f661eb1ba90b307eb542dd84a486568f85e19055bf7b8f0a76d34acf276897a01349eff2c36e4b43000000000000000000000000000000000b5f890f22658b207dea2d721d90a8f5991ea2c5ca06b8d1b293f60959ed424dbd7052e010e594a5ee0feda1e93bcef4000000000000000000000000000000000082cdd4d8452078e8b853f196dd76505ece5e98df3e6a8bbb21f422755af23c5ab261accea48d8e4193d6c884773cf6e428fab2c596f23bc3c9e9855b74295f52caf73cb7371c93c65370583f7fef4c00000000000000000000000000000000077501a457d5f0946d25a4c5eede1b7fd80d90d502bca47d8cc40ab2f9a6d827d7323e7d4035f3d32b02401141f0a89d000000000000000000000000000000000985410246c1db01b42728ea46758906883586cba5618b66c21a3cf58cb98e7c6e0dfbabc5505d1d11ca9d047fb6d25f000000000000000000000000000000001775f4008f688882d02355b6eaa1ab108f239890f71238b36c76228cf2f575cd15f15293a62a72f6ad0ff17d7e8ae79f0000000000000000000000000000000004b6967a5ee180d8b92e95c5ef26baa56d8509e1acc710237083d19269c1c5a1f2d1680e85f0bf040747be1d301300b0f7d3d755410f77a0e4b2fad0f184fa9312b559785fb04c6020432465799ebe22000000000000000000000000000000000fee170589e8a3d3fdd93b536347af5002e59e8ef2ac8327a7e9f382560ee9bc36b3f636a3f99fba8be7b5ea3dfbcfc600000000000000000000000000000000032380cb6c043e3f9ef7169da12df1c6529d776b049c7061df660df841840933e514eb7ea3152ddac38daa2c52d66191000000000000000000000000000000000620ebccfd931eb70ec688110975ea24b7ee0f9937841aa1b7bf4f45af88b732b76a26299f0fe48259fdf08abefb4314000000000000000000000000000000000dee6bb8c198363fa4107996331aac07216b82208242c73736be31e14e4e04d97a56a1c22479dd94997acb0d32abd3b0557b05efdd02ac9d8e1453c82a321d798f3106bd18764140faede610ae01fa80000000000000000000000000000000000eb60e98d6cb4e4b3e58271d47261d05be892eebb9a37f6831ff19d0bf2fc235e655f0eb9b01494868bc082c58ed82d40000000000000000000000000000000007254a64a0d94340bcc2b0142faab2d73e8189dbaf52ad0c3a9206e802193168b8eb03cb18b0e4f1cc95b98b943910db0000000000000000000000000000000001e0051fafaf454072051d2aa9512ba2367778aa1617cecf6a7f989d69c7627c9070c349d363f56711f172d43f5730cf000000000000000000000000000000000f4141c8a45448fecce09908ddb42f7b5f6b5bb53b9e1ede0417bee327644af5c98470e8b5364642fc7435f84be1ab443313884abc4d430c06ae843d263f2efc1bba35f6cc270de05551e1f86096bb7500000000000000000000000000000000049c28e0bc677ccf54f4cb46e953a057ffad624752332fb9ee5295438fd5bd61abd2199a0bb729bb7678cf3077e32ec10000000000000000000000000000000007138a996356ca3f5d63bb5a36dfe901254459ed515e18ec8d91fa747a691b40a19878d9a6f1dc74e4f18374a399d38f000000000000000000000000000000000a621b36a3cf04e6a5cb699fe4ff7fb8b3361207186848e81972fdaecf667ceb35f413bd68772f7c1f77c1d3f43a3d610000000000000000000000000000000010becda5a06f3f077218d4387158e4a1ca5e0ef24d4ed304723ed5dd96da7cc9325f7e4ae16d9d6c348577697aa6017b8faea236e782a8fbe27ab15f051ed007a61e25247f1f259b9300974f521f30c800000000000000000000000000000000163ee307e0d0c3b61ade05a022ce2bf315d820ee8ece60f93d63a150e02be843a2eb2240a4882c29be2c7403932c348e0000000000000000000000000000000001fc8e9ca23e8dc8457df8f255db3b434f52cddaf05819dba7df1c5bfed438f756c8b57442197af18bf83fe9ee2b765200000000000000000000000000000000109cbe5279ccb592bd0b33b1a482df53459c48cd0913549739b784ba7ad32872377c2e3924c4d45064b0cc4764220513000000000000000000000000000000000d789795d556a37a375d83120a022f57e26da6e6f9aa3e40e1f32ed04b50fafc4d40d0b9b20a26e4d230dd789e20823013994f5645c6ce83741e48ae472674921bb2d9b8abb7d04ddbbb85a3f2f7f090000000000000000000000000000000000960654bd6e6a6b2f7d87c3c4d6e3fe6c684a50b62f7acf82a67075139a614c056a41cd49769960e229cf07468fc2dcb000000000000000000000000000000001727f2dbcc8d889127060de0079207eed1e094259b59a20fa42ab2783bfd176da00e61a65709dcd60402398fadf30710000000000000000000000000000000000c17805a01e64c320601e0ef521b6573e9c2eb354157cf0412e5c2b13f826759310907c4b77164f5899958cd30f78c030000000000000000000000000000000010fb286ce797c0429ad3385c709259b55cc962ae02c814e537e5261e897b7ee1b7c660273ec908110f997b166c14f5c181eda24db328588e8c670ab70431ddeebb0749b431bc1bfbd992c91f35d59b180000000000000000000000000000000015d96a0f988f4951206aeda63af85910db49ab817c83e218ec74cbbf5f34f81279d8a3f2fd1f3000f73b8c5550af3fd600000000000000000000000000000000186d2eca1cac226227d8981324859126864b84e8dac563b4d92357591c2416c93989cfd9e1ab6ad257dfeb168d829a09000000000000000000000000000000000a8a7247a3b09583cd2d4949721160573f1f88221e6eae833128914555a594f21a3fb2bfe3b1f01f3dee90f7772dc97d00000000000000000000000000000000132361ac1950756549c957c174cab9ef586eb2057a4eb22f49252cae032975f56eb0cb7ea70810afaf5716afde5b88015bf25b5070829e3d5a66ad24ba9930f3ad64767c51e432b51bdbe2fab470688d000000000000000000000000000000001328e22bb83331adb09dbed0a8c58040a3564fcae0ec85794f26c077de69cc0a7555f011e028879cb3aafac4dbecab33000000000000000000000000000000000a93db348adb3886802bab1e993f5d7275360a5b0466845055d5274e44716f3e1d03a6e1796ed4de4c157dc8a2d92c39000000000000000000000000000000000dc0879a8e9556b7d9b6d5dffce5e648f835f10acad3afca7a73b0fdd5d5babaa74a1ca80aa4f6880d9b015501e218a20000000000000000000000000000000003f7ae8207de4a179ae48cffc8c6e926455e46ef9e109c08be3ae7401bd36e0876642ae9ac4fd75a74c67ffb7790e265a9535c082e11b366cda0000d8ed0f92ee30fd2c4364c163a718518321c5e85d2000000000000000000000000000000001078f43093602a2dacf9b5dd7ec41d47bff02e0dd27a996b58c73febca06e3d977c2fbd73f63508243696ab5d8b97b980000000000000000000000000000000001841869086e850ad97b3122fa51c437113d2bca14deaef5715c354d3845f6829f6aebe668844352d5af3509c0d8da7800000000000000000000000000000000047c42e83194143b9e977fa1babf80d455fc86cf6cb491ef8306a1c32bbf8c868e11bb3308dd5f65fc2942b3e49ff5c50000000000000000000000000000000000872ce87ecd22b39b14c9036e971a562d51c5122bb10939cdfd1945dd1445ac9f5de06b70931aa5c86cd0fda51b89952c4cb49adce0292e259e92b229bf7965864a945de86eda3ce0bc9f1a6dc8b7b200000000000000000000000000000000157820de2a134081eb47b1800ec72630348583d77d512b4c6a8c8e581810471a2f57a8eb6b0af87a91960424009ff124000000000000000000000000000000000378cf11b0a2848b06412aa754ddbee5660730001db073724caf902d4b4894959f035a8838e28554b0efc2388f2b4f27000000000000000000000000000000001301d15f290dd11c3f8e53407195e02dbf8f13e4fe25fe38e84740753b5a0032f8dd07df3ce46ba424f6772b3aa66f4f000000000000000000000000000000000d166040d457187232f8f38f2beb1e0e0864105595764022c282867346166e46eb789786a7ec7c00b0446207e9ac1ec05e927f57aa85b2df54b4bddaa041d43766c8929c8b9146d723806ee0cf042275000000000000000000000000000000000793797c5bce4b1cc3bcd751c5ae1d293477af96a0e7c6bd392ab4410f806a53088cafeed51754ee7e60e61dc200ccb00000000000000000000000000000000019d595730af1f3039e37494b86a638a528d8bd24c429e3f8bc97076c7463e7f2618e23bd3f300bc7e7a4674f14f8295d0000000000000000000000000000000008e245c7590888fd8dd58f93332b81f48b6e3acd3cfcf5f3b28df654eae1172f52ef5a121707aa9cb111b0b402d1bfa6000000000000000000000000000000000a7c6403659e1a0c2dc7cc2e9b57a452bf553e96388676f4bf4a6e26b3ca2d3cb82006850d8340dacd65aaa0d20e6fba606ee8a5fdd9890b8017f6c432a45517d65328f13f3a2bb42d7115c02929db7a00000000000000000000000000000000054c37e8acadcec8a795619647d4cf1081a0592de02bef916f847936a1736e74cc3b7ee018717495def8b4ef1d098fc9000000000000000000000000000000000291d89d152b414fb5e7139d6d0bdc7b5b9de1fc44b49f895ae08718b631f7652bb4a895fa11149b9a9db30c344108ed00000000000000000000000000000000107b30992ced35e4ba874e436bed5d88aadf0a0c944ca3eb8319539017bdd652feb7483ab6c705aa17e845723b2cb46a000000000000000000000000000000000895dd8e04114fde4a4cf19925004a72f617f2ff146dd650a2cdbeb12977dd2b34ea7d655dee16ad9560b144b81212f5c1a77ccb4b32a762d60b37827ad6c3448c33af6af861c131adb5920ba3c2b85100000000000000000000000000000000005cea2e036a8ce057e4dbe2d9d786eb759c2a75934580480f78d2e228c3150a0a1d8c95ac5013aae3ab6e35f524d37b0000000000000000000000000000000000e18c18884209f9e4fb17431248a5f8d29c616a58af16e949f4317c2e117b80d531a39800dc70f6b161b98ba040a8af0000000000000000000000000000000007c42ce885d1bae906128589b72f2e6c18e4eeacb78c853e923e6eb785c073b6490b2f6b3dff2276916d96770ad5019800000000000000000000000000000000132d809c37c341eb0304ec933a6b11bf9ac0d2a13ead818ab6ee03ccc94160b405066381dcdb13b6ee3f5dca48ee10ef47cde609c38eabf457cdbd1e0c5366bf523dd5801d66a0282bc187d80417f455,0000000000000000000000000000000009406918e2dd6f06f4782ed110e29516a911f47133ad8adc58f5780de916a8973ad60e05ba931d66de7545a92f388c20000000000000000000000000000000000041cbd52cad2a5f4c8353c7153b5711ec23fa8bfa2f34f5e1a16d8a14cfd47c237766880debb992a05ba9ed0353beea0000000000000000000000000000000017d4211c827379b310956371129011a92d62d11f0ee5b0cbad9eea2d3f2a95d364717713fd0c544747338725adf27248000000000000000000000000000000000a61903fb81064614c9c6894c7f3954aace7611cedf6bab8e751f0c203bcab827d296016947c071d7b6ccc742e28ee9f,240480, +0000000000000000000000000000000007f90813f8c3eabcef04dc1bc9bbafe1dafe220e2db24e4b714aab2b164d7ec9df3e6a3f903e8b7b96df2ad8297381d2000000000000000000000000000000000e34371e51c4c952a0f38c4aaa5fc2324971ade310af2f36ed511fc5fd7a602a551ef77775fcd0f1fccc718710239561000000000000000000000000000000000787edf7a6ed6b50afcd7c0d3876d8919273428bc49833e3503f650e48e788b15cd82eab2672f612025d796bb62d72bb0000000000000000000000000000000006b49e631ace4f72c959919df5d64c537537ccaa3d1890ea9a1d70f9eacbaaa2ec361edf2d4880c9810976c6073028bc3c79fe6374bf8f91bf7851ff935a124b54fdb5db498d2d37939fcd43bb93d29a000000000000000000000000000000000cb63d7eef2d6614d1f629756b3a619a221033207d1621e4ce4791db4248500649b91ff07cd2f1f06eae3a9be5b6af080000000000000000000000000000000019aafbe56da1569959019033e8cc785c9b98bba6b069603969e7ff1150f023706b461913ea7949306a44c3b7d199e86e0000000000000000000000000000000005cdc3a7004f7a7f79ffbf4c4ba7c5dc30ecc62f270a5c231406fa63d82fc64f45e94779cac851ff8443040fd3b2ea6200000000000000000000000000000000040f30dc98e8668194c9278b189e0c0f7b76a4c686ce26a4a96b93190938f07c5b813670e206eb6b5da29624a1b6314ba59fcd2baa47621ebd90c5cd12b89f2a533ae86d537fbb61a14b1a80982c9257000000000000000000000000000000000a5a1bc231f803ae272e497f812ebb663c2ce8b43a366717fc6349264823ca93e29e30762c1a366d8680f81838907f59000000000000000000000000000000000a88fd59ee380449d632d7e1b926210d984d5298fa807570a63a63828cfa55c6e2f01b7745848281795dae36e562181b00000000000000000000000000000000025ad34537909e07beaaff09f22e91e76d93c668d1b45cf6845ab8ba0129e417b758e85a7100a31a9037e307f454bd370000000000000000000000000000000013590106126231b1c616a5dd7aa7ed6946aacdacec963b507907950d6ea11cf1f5b59f819a43eeebaf51a1faa7daa8e719ef9fdfc5f0c4ac41255eb172d485317c124211498a8b9a74c0bfda15b986c5000000000000000000000000000000000938d43b9747c926c3e2dfaca2d6f1e6d61d5a621ae08c66a5baf33d9241771509689f9ea7d75af607d76b66faa8fbc2000000000000000000000000000000001889a48a74966b9748f4a6128dc3d75a69499db1ba1bc9aa3a9428f0efa898b5f78a9e2dae942d3794ab3d1157a1d305000000000000000000000000000000001129c9bf343f476541980b85229c5c25289ca62173e29b75de431b572c8f01f64ec1aa4625dff9e7df535194c7f4e6e7000000000000000000000000000000000fe95c71f703dcc71cf409b332f66fd69c330758d41832236a510ec4bd9a28c4732434d4c3f97445e6301e3070153dbbb8ba028831f429d027319a92fc0f30def8b97a43da456ddc79443d9f8df72cc10000000000000000000000000000000007649efeb3e0bee49b9adb13f8e5d7db1c06d7fde08a3f3082194153bf4b3615aff1450e47fae88ac93f55a389a319da0000000000000000000000000000000008334731582fb1b6125d7ee1da0124fe88f0c70a0a3f6188636976c31ba6a72beed927fe598386f328e4ae534729a57c0000000000000000000000000000000010b57d80fce5cdc90bc93b3bc7a1affadd19fb00aeec2ca9a6287bf4e40fb74616986a44f2f7d945f58501a965f37f3000000000000000000000000000000000180dcae46ee41bccd422b3cc2b34cad26f6816dd08ba51b2f12835e7439ae2d46933de28ac04bbcad68a188e7e90ee8dedf8a6d86471f58c69c1a5e7518c69c34165e72ce84fbe0b7f69d9c2717e5d4d000000000000000000000000000000000b419b675ccee2509daf66e5da4031b08792e1181140b30489ae21f7925305d8cdd8a104580ae5938586d6b8e74f750f0000000000000000000000000000000012e070ab7118991a20b27f1a87fba1f5815665d76269f0d3d460a6b701e57ffdb4fed2c53fa63a3121c74f67e770f31100000000000000000000000000000000124218ca85f235eac3471e0acdabf73f79afdd4bbc159c1e34c641b97f03735e4c3430264f2d94f640486488dd1067380000000000000000000000000000000011c24f4fa1862779f22a628edf9d3cebe0f7593964b642f889201ae85e8fa01e00e48355053f5a7c6d920dcf6a7ee1d60dbaac3f5e25ca3d1d50ebb31258ec4450feca1e02c84672ef15c49b4de2cebd000000000000000000000000000000000266bf0d9d5a4fc713dc0fcc6ea6edae0b326e22cd97bc49c48a7ba398fc87d7a0c7141ba24d80df454de66c2b5a55fb000000000000000000000000000000000aa8f95c7cd61733b0a260149d6608a73d6c1f989afa8cb2aa4098e1fb5a66b4ad5a5c1c4d901aa79812385fd507f02e000000000000000000000000000000000a6b4929df13e1fe7f0a0cf699a7fbfaa97d7527cc3ea1f728ba59def2e75fcf3490199bd42e93b7d47985a307add07c000000000000000000000000000000001719321981d2085ba31c9fb131d6b79c7df5d10d6ad0b5015454329697860121e781093fdde1f19e897dd6f2c272f87a109ccbb8fcd4d4651b84f4708799d84ad0a717aedaf5a76d2970a7b93bd23d37000000000000000000000000000000000431002c9926aa7d2b06412f544a868a7d48fb5f077dfd098febeeafc28b876c434daec809e5cbf50ff2395ae7e456560000000000000000000000000000000005a15f713b6eafb09495cfb1c89e9421515a07a99ca0f208883f11c430ffe6f2592dbc41bcee5db36385a26f67cd26bb0000000000000000000000000000000008dd30fdd7767486844967c5da0803b52282178287b8ef28e14f07b487132fea3a82d86d414b4d0a25b3dc538be11b500000000000000000000000000000000002dcee67e2d17b3106dcb9f4117456a037ae1996e8f7a09b179baab1ee8345c6d01eae554d3f40da86bd79a04702fbf76326fded2b8a3fbf7637bc25bd201d20e3d4d724806cfa678ee039a39c24e86a000000000000000000000000000000001629fcc374e99fa8303a715fb5077f266b13367bbc0098b5463d3298c0892f83127d6b7f751446575b88858bc742586c000000000000000000000000000000001100783c10618752d25c235e1e76dc64db94adce05651fb8df0a5ee7c299d35b1319f7009b857892ddf9e90c91f7d23b0000000000000000000000000000000000ab6996e4935131becd5df288dacfad1e69b41e200ca7dc841ecc180a81b9d2ca14fc8a76a4e7bd6f924bb9f473de62000000000000000000000000000000000ae9b22f8dff29e5e0a2ec5b5641f53fb5e1ca03130b49d0c26696ca4b439a9d998d9a364ac9cc5ec52df699318cffeae005efa8ee75dec8a013029292976e107a507ec09e3c34fb4baf2979fb759f1d0000000000000000000000000000000019c557ae1c12ff8a7c00b7c9e4bc3d65c92753549c193311a38a84bccfc090052a2219461a9691affe2d67ea4357cdeb000000000000000000000000000000000cd35c5dd126bd4b90dd671f29953c5a49a14b6b3fe946991416edf235c3eb3d574613d27b05cd879518fa7dda3ed39a000000000000000000000000000000000224392063b0825fd332bbede23588c1912e7670a013a99da5507f650dc4284431698a5b4e8c180269af8bb30e4fc8450000000000000000000000000000000002ab8d3250d4bb8ceecc8ca2003f91420d0ef8a7dbc2361e5e7fbfcb59471a4c525856bf796a2c2608d219d215cf83fe3917f8baf17f71222166cb9b6c4beb2e57d0d054cba3f7fd3a28cd3dc4b409490000000000000000000000000000000000911417908c2bfe4f63a388f699b31b47df1ea0ec289ee3f96ffd0c71f3deade00d1841aa56b4bebc2adcd3068adf920000000000000000000000000000000005467c7e58e82089fa285c28ea22c759c7806d86fbdcdcc8e09e847d6330922a61bc331ae3b5acce777b7809ca98213f0000000000000000000000000000000010f376fb47933b1f701dd81cebaebb2d8d8f5510a26fb3e9e156ac5ecf2b943c5fa2812d52da542e6c335abad8ecce3c000000000000000000000000000000000dcbf467432acfa4eb9ba11a7cdf02f9110f44ac371128ff8f1f98fc70e4554f057a4608180bfa54d99fd2da010594f6f0f73e1b62561f5b0fbc409e6534ad9e37d1c0724b35cdd3f94bf6489e500fbf00000000000000000000000000000000179aaa7119f6fb986714c03b6db16f25eca7172d24cbdd318bebb633bf08920f9e2a8136c94e3ec7c19e57ab51531b3f0000000000000000000000000000000005937c484213ab5b2ca8ed1c5c90e8d2a2f1bac044b88c04b301ff2fdbe67dc4ea42779d919ad510cabfa2ccd178cd9f00000000000000000000000000000000183cc23fd64514ead63f55d375a07af7cf2a56aca64a887dcc542f8a396468a6abc776170a5d4b4bbcd4dbac285e7ffe000000000000000000000000000000000ce12228dec2f84219904d9ac7923f122a99803a9b34749ca68ba385c178811685c19a492aca2e1123ee82a8a9cb90fc3ea24fb6447f2493c78a267daa158eabb70c1b60af8175d0d4594c99122cb4420000000000000000000000000000000009612bf9130e17110f8b15aa6f3317071daf3433bf6d008c383bd5c2fdc7ca03f25ff4cdb483de3c84c0ef9e579f38c6000000000000000000000000000000000c40172540a7e20eeedfe02c37aabac07165cbf04830f20fa76fe8b05c826e7762c9f7567a0fb972212bf736e627948a000000000000000000000000000000000f49e5b1929ad3ed5c07670c471710baa24e8478a50f72a5b7bbc23a66cff91d30a3d68961fbc2e6e8003d08196f325c0000000000000000000000000000000004ba098f915ba9e934384682648ed8d4e1cbaae60d596655fcd9c05f4b049ba0d278730dba5ce3fd4892531a3153bb955ed307c01d9e29a0571de07c62d5fcfc80749f02b8dbaaee9f69dc9263e99188000000000000000000000000000000000449b15ecec6d6fe5cd32437b54218f62527157aa6344c635fcec8f8305c8b6e44c93105984e0832536237606f07792e0000000000000000000000000000000011e40e8aaf75f5ff8e4040f725ac27693d7b24805a2539ff54b3a6e90c048875ea9609fb8fb3d8de63ca1118876c172400000000000000000000000000000000006ef2a24445f728b53cbf01e5b076acfa7761a84d8261cf1a1b99cc32f330f32fa5ded83d5cd51cc284207adb2451ee000000000000000000000000000000000977966380e772670447b15ad9917035273eb71a21c37607a761aaec808909fcfed50679769aee1573d73cd241de6624877f31ddcb55d961bf9bc09903bd927451390922d647d589302855141cf5cef500000000000000000000000000000000074e475c0ff1a51a24be3c964c45c41f767f890dec82712d92a965be504fee43fcc6c0684b2b17c5b294a3eb7ceff1cb000000000000000000000000000000000597b7dd287f3fb27e35a9e4e1718b6b1a4addf9e95e93aeaa25aa34023669368b794a08fdb178d9bcda2738534d1962000000000000000000000000000000000a492d648393bfa317165ccb552e045fefce5b3444d5ff770f43a08a68efefe7fce1216114ed1495cd00f832538198180000000000000000000000000000000003d85cea8063828ff025ba599bdf1efe0412ed5ce06ad5faa841c6400e4eeb6aea1470d48f4e66fc768d7e7bfebedb37145c1442ab82241f56c27dec2cd4dbfa9fc3cf1ab72bc521ab32a82346f8f6070000000000000000000000000000000008ecc3dd40da2a7a348b4817d9c84242f2f07c5d0ef810dc08311e9d4090d6d96d68b6c725ee6c24de076c71754bc4b50000000000000000000000000000000018fb3a1dc4e0dd9227fba310236a6db7953f0b716fa995b928a2a8de38edb97eca09fe2ab385037dfdcda2ee577e677900000000000000000000000000000000062fce7fe7810273a80760d9f4b3be9e7c821f38ed3e075210d3aac6aa7a763e3cda56465f88b34540b408ac850742080000000000000000000000000000000006fa94466cc47990a80ae6a310ea765590a0e646b5988925f03cc7e30f04fc0a8044b403212290b2fc46c77e84a9028dde4d1470f6cbce027465b4dc2a3deaca14e34218910aa76cb45d47139b31df88,000000000000000000000000000000000f41bad0a932e28096e51482c646dbdf294aa7b91e0ec258670e7674864765c76989a936fb440bfbf4268a49f450d3230000000000000000000000000000000018282b76521db98f589b1c14e603b6f5d27af357553bca761189a38a944a11c66480f7ddd89d17e4aeddc8d78a2b3a0d00000000000000000000000000000000007efc4a90dd97f1312047ac78a3163dc014c42a44c7054daeefd5b72cd0488832cb6396e02ccff09e4171d790954fcd000000000000000000000000000000000e790fe8323fffc96705a42ca071532d5359641ff7cf8714789c9c578717a054c811cdb581df8b6a43729c6c3e3255ab,240480, +000000000000000000000000000000001304e0ce6a4baa6e0545fdb314523fc91f73eee157249b94f284ba7390b12b23b1c849c45a563ac82b62a2c48aec24e1000000000000000000000000000000000a2d0e9e222db70d49d1e85f587d35bdf5e8328aad14343d296f95b152a79c83a4858cafc350a5df1ad0194c49bb929400000000000000000000000000000000199efb09b34d0699eb4bc1c57fef9cc5d98453bf522c504fe7897e22bd0596a3a6c310eb351e15e3f6609b074b240f7d0000000000000000000000000000000016b69f12ce30ad1a65150094e29d4cd82fbce5dc343517ba9e5d89245ec083c44af9a3dad2169f713d3b01fdf70d20642576b42e0728db912a78eec2b7b4c96575e341e86a7592a07a660c11e0044839000000000000000000000000000000000b3ce4ac12861052c602e71906a7c9f3e2186bd2b6eaaf222d8e80b48baee537065ce78372ed936e6728b9642ba1fdb9000000000000000000000000000000000e8186561d23515bc58c77769c93db76dc9c62bb715b283cbfb71462451120a6ded736cd8a292a6799fbad7617d9aa84000000000000000000000000000000000368a6dbc7daaab0a786257c813b1a25c97468732c27cc759fb921cbc3c9a37a46d7dd0298771c447d36ef0a10579ff5000000000000000000000000000000001348d5e34cbe54e3a6b357c4e651acb82d2dc40ef9ed8bb89f0cdf0882ec6a737998f4e4dd61e296d101cbaabccdc3e779f9205ef0e3a85199c60ad9267349fdc7b6fba4cb765ab21750eb3dcfc48d8b0000000000000000000000000000000004ebb53c462239a78bf13f29856ddc4d78645c457a656f3cccec9d3c032ec19c26488f39e0f5bf0d38424f9e3a9bcc870000000000000000000000000000000002fe1949365831f7c38b1cd6cf2e22345c4ce40cd73def77889c214d1077d70e39578e8be4fe5998f59d47cca7917280000000000000000000000000000000001152f2df1512013a42ac056b75802bc35c1883efb345cefda8276c594b061a0b0f4a49d8bafa6fe251658ee76b2493cc00000000000000000000000000000000094f90cb386f7933b2ffcdba5e46e09cbd7d537c12bc223e76d3a88ce9063a7b3574d3306365d65dd4c6505f1dceea53300679b7be7c71224247e8034f5d30a63f8707d92d843a703f0fa93160f6571500000000000000000000000000000000169d9469c53e55768c9312680ee82ee581727e28cdb1d6fcdca25d0c03f3da2ad6572039f12c90b09cdb843bc506e07200000000000000000000000000000000174528257f6d3542f754ecbe97eeeea7d196ee4dd01852f6cbad87fbeb4dd7d3799588f17aad129a15549bb787468772000000000000000000000000000000000c9ab635bdaca1c488538c0830453ec6ab3b2b62447c03ff6ffd2712bf62e02a63c76c79d41644ea412e733128685c45000000000000000000000000000000000172ef0fda359bab149c8c04f583f4ace4d1b148426e993996d278f79ed2c6d3933d6cc5fb62ec4869aadc773d3084ca0454b01910548432a0f706818a98151e38ff9e854f1faa95ad41a7239b5cc4910000000000000000000000000000000017060fa73b58957d12b3996d67b7baa8b7f0943ad52e80e5c4f8830d33dc74c0a39e08594b647945b402299ca861f7b10000000000000000000000000000000001efdc7f783f9977392e2797a3e0bed222d5b661d056aa0c7e04a493bb9b18048bf72aded134941ece78d63df0a0868d0000000000000000000000000000000011355198320af05f2121939e6489f31e9e13b3cbb2cb30c9e675854cb8ec038f80aa2f4b6f995774b36f5f1b6a84298f00000000000000000000000000000000172e18c490d0cd5ba2449362c0ab296212dbe69ac25515d0f91941d300051320f067f946dcaf999554f55f1f616adc0f3685617371b27ba8898ce7f30776d817ff09ef68a9d6721d4a923ed244ae82060000000000000000000000000000000005854f4dba62d1dbbf3ae16f70792f1bb39f111309b454a6400d2916e619d4f70764ecfda7eae5c28cf1d178ad53fe6d000000000000000000000000000000000ed0bad1f5d69a0e621d137746a9ecc764931ab89f24ca827e0340ddc03571ed697f63e79cc58b946e8462099ce4b1d70000000000000000000000000000000011de76edd1cc2f9ba06b98593a24a7a011f2701b451ea3ccd04361ddb678e06d91a676e3f11b62c68cfc05242cb8a859000000000000000000000000000000000599726b5f5b93d414f9310383ed9414e4675d644f83ebaa63dceb2bddc7dcfcbc17c7aaaccd0ee32b0875952554b4e660cb5aa2a0cd1e8c3fdc06a3a1f6f9b6d52a8cc2e98c85b8e258f72d03efc25400000000000000000000000000000000031110347cbea2756b5fdd549d6c0b8f4036f5718d41dcd6c854a91c9df29bd464774be479d0efcb8a3f82cc7441a6c8000000000000000000000000000000000e24a52dccfdda3689c87395e45dbd46156676d9eb2cc09dab22ef7ff0acf5ea243ff117c82b147994d65aee8605b2fb000000000000000000000000000000000e0cd6ea0bffc591c13c48bca0782fecf8e128b0b842aecb06f803a223d32cc350db869b7a77f8e31b05f36bddd587ea00000000000000000000000000000000042ff4ab4596d610638ad23eea904a82701cdf61f9e2dc5832a70e11e717711a2d0e72f32f74706d385a9567426b4713addb1fe778c84242953db87d2307b40eeb776f17767c3a4311b5d2ffd738f151000000000000000000000000000000001517efd853800946aa37868b525e58fb488bb69755ccec806afca2d21bd3a30ba46c39cdf694ad0ca92841760437c3c1000000000000000000000000000000000e5591c339e88544660380d6362f4119c5596f910d4ceb96ccd4c4d9672efc50805b6fedffa0a48d126aae69b241d3640000000000000000000000000000000010ea5babb0de734641f63eed2eba6124377b5c55e429987917c0bd109d7904766a10b0d2dd123413816d0fbabe25050b0000000000000000000000000000000000efc89ee2ffa56193129062ca55a3350bf50e8fc7d586fae3636a70e3577987fb0f8674d383def4b41225e490d3d81528416b4b4e965a5f024723fbad6ef2f65a1381e70201e26ccb40188dc3d0fae8000000000000000000000000000000000dae4277d62e3f3dfffb80818a5ba5c371a48d73b92d69a168ebab897ae8be206fdf776e9f955136d7f7f7b2903040270000000000000000000000000000000010ca635ee2e49cd6c951d75ffddd11557432726d26564239c611b139329a28812afe21f094c0585675f4f233233743050000000000000000000000000000000012378b2ec31119e508fd9ae0ccc4c2603b6820283284a278fe16864e5a18cf7992d850c1d6ebd1253103c219bd95ca4c0000000000000000000000000000000018cac4f0660240045214034cfd8a7e40bf0aa12f97a23c4e27db0e05bb25f4d755276a91a4e882a0be63437a522943ab78077a51f88236dba6d16d7fd681c631510106b0eb7448df456eb9ce758e74cb0000000000000000000000000000000002fd5571c818322d207d58fe0a898a045a26c95c2490765dc9ac663a0de78ef5fbd05b20ea96dc5388d5b2ccf13a5e320000000000000000000000000000000006ff29ccb768da45061ba4e01c90459ededa5e79513917401e7e37151095ccd4656aeb9cb7c083cf27b69377295934cc000000000000000000000000000000000414d34eac47430495be735eb5c4b1a68372abeb43658f27613a9c8b78f17d9074174a8deeeebb1f9cda5d6198bdf89d0000000000000000000000000000000010b11bf63b8c39c1370e8fdbfdcd149fea88eaf1c0a94a51bdd061e4c41abc626a448030bf9ba880032e9f1642caabae871716e790e1a0120fd26d169b8ffe3fcc0d03683dcdba7d2f953f05444076ce00000000000000000000000000000000023eaa08a44eebae674434b013ae9992c75690a3d0de53e4b05d1c0dff249feb24a12432bcb5defe25ee4e44a56b27eb000000000000000000000000000000000f146ac27e685cca04afe8fc58fe853825f5b0009e8831eb0d0121decec23b25bf8521da2fab1508a3ad8254865fbee70000000000000000000000000000000004af1a525d3c33e0b1629cbdb90c56a88d70a28037c87db81c59bcbc811c8f0b98aa9dd574436c9f600c0e8e2d194c0400000000000000000000000000000000170efb5e0e69e46a21ec3b972265bc04b9d5ee926254f61c0e18fed013922e00f1897cf69889576bb5d54810486e7f2776ed0a27553db6ac6d3959ff4c9bc5807fb7d4f0a56095ed2bbe31dbfa41827700000000000000000000000000000000111c832a96329d6db203fc8b6bb5b7db01521529c91c74d9cd71dc78d067b36cb7eabf1af80129a7a3f44b719235927400000000000000000000000000000000097339c17816795238629d4ca6c243a14e9e227e9bfc30370dbb9e1475f6d03020dc35559675121792436bacdf9eac4a000000000000000000000000000000000805870a1efd1fc34c9b576b77418ee8c0d36aa9caf9994a051e1d55b49275f34cdb55edc74ffc267c5776c8d0e113ed0000000000000000000000000000000001513afdfc2f000e3b725fcd0428fe72ab2413ff2aa91b44458a5249c9a160ee27bca01d2fc2e230f4a80454769961af95ce72b30d989889c8779c4056e441bbcd93629efc2877d36d27f670711e21c4000000000000000000000000000000000485b3b1f812b4a28ac87d16f86d8d634e85d49d6dc460646e1224de662e906002c44a1a269c3bc011fd22afeb2d58df0000000000000000000000000000000013ba0752444a794cd00c99eceae51e61c382d0abb26e5e0e595d59321447400e8a8f7d97390bd217fb50bc22cef34b2300000000000000000000000000000000184515a36024d0bf71d9fa4cc5165363ff94ee9f8579bca653ebc0620a9d3146fba70a2f4a9f6bd3777101de0d32e327000000000000000000000000000000000e041422088c0343f7704e726d65ccc4216c4a1bde3668108983643663cf0249e992f9acde2dd8ff478dd26cd8d9434d06d220f64de05bdd6e1140c1e409fdc13f43bd31cd94e633be38ecf22ebd77db0000000000000000000000000000000005bbb0c55fdbc59992c83fc0ff03f677e58b6de6f8649141d88963ebfead9383d692015a7b765b727eacb6de250351ad00000000000000000000000000000000183057eca610b8e07fffb60d21bf2eb87981e6e881bba04ceff420ca38228fce2f94d40a993e2aef09e209f3990dd14a000000000000000000000000000000001231bc55242bea6b589cedd1d82621fb71c606ca9306b268379dbf83ddb1420dea228ffc05cd8b67c38206f3f006ef18000000000000000000000000000000000f2c943e7a8b0ee00fc4e4ba912b94f68f504d2783babb90a3781b666b31bd161af2f97a77813eab9ebba76040b04155257da8ac7d23c5ed965d8bfc76a642a36ea6ec4c45baf6882021372e8643f09800000000000000000000000000000000054bd97b9cc979006f734ec433e215a4e8afe468e69173384bc895e10ead3749d991ff8ff203abff30bf5cc0d2fc8c6c00000000000000000000000000000000066b73a98d5f5ae140a5784c5594892c849aa7f2db3b5798643f755743d401ca745d810fad5f4a33e5b3cf0fd7d96f7b00000000000000000000000000000000007caea93ff5cc6ffc033717220a215ac4ed7283945ae77e62320a0bde13f2153dc8dd401297cd124b4c67a4f3839dfc00000000000000000000000000000000094568035ffff439e3d3201466f3a1d43414e3f6455627c5479c8b7c55130ccaa5007ace7ef6a2b3e2e5a4c9543dad9163d017ba8c7ed138b1bc70141abc5cdc3afbccd8b1db5a6b5f775efa62b8dbc30000000000000000000000000000000015eeef8bcbfac04112931e186f6fd48b7a8ea891ab364ce8266c5fd15f072f08fb3655e324795df182a5ed1c917a5db000000000000000000000000000000000028916fcb3b30a7f95321a0998e544f9f4f578be7a9f866cf72d6b8baccd93f8935f105ed26aceebb3f9c96073a8be180000000000000000000000000000000012b11f356a7e32f3d9281a8999363aca0ae5c1a058724cefb51583e5f217257d47ca76d21e54ab62260796b95f9d3ad0000000000000000000000000000000000d83c75c36cc8dea4aab47823edd26b4492da39b93a15fa454aed4175f28a025ad2c576ef2d76a66e666bedae95cef1a7a16e23e37ecffd514d47199cff249415a6d366fdfaa82450f0744520258955c,00000000000000000000000000000000059443f363ef0c65973d36469ac651eec6e52485a07a6d28112f4d0711802d182b7e6fc56d4f1aae51fe1c549247d885000000000000000000000000000000000d22118a6f1cd06ee14c63f0e005076bfb061bb85ed184b5444c08ed9dc35f77217b6daafeac89a973f2c73f00e0d3c800000000000000000000000000000000180430caa9917cbb40e3ada2de8d685b4daa99639669a643b8f5cf9a4a55d6162e9fd7f5d4989a1a6588feb0273669b90000000000000000000000000000000015d01fba1192f0f1acf8fb32fe790f0448c6563cf8ef5505d9378fa2fdd38bd99ba938077f71bb8eaa91a99e787e840b,240480, +0000000000000000000000000000000012d948b5268524659e29cd407dbbe8f529e608193ab9452f936b2f6fc0b81d3a63a0e929329e2d89b5475dc2d73ebd8a000000000000000000000000000000001219e20a081837f4d4e33bdffda08a946bb9cd876e42e2f561ebfd18ec439e0104b43de61f47b8b7a0c346c33e632be60000000000000000000000000000000000a135c72c45f254cc1c260af803e14cd0f89c2ac3029629a86b05acd3440465aafa4cf84e69551ae772bb55802a90ef00000000000000000000000000000000052750c3a99974f9044531dee9129110b99572cf283b61e6606f1137a87de7344bf01d2ac2f8a1db8d815b6d9e7511fa26a9bd0a71fd58edf81459152782733536e960d27e35f9f84d00da256bdc118c00000000000000000000000000000000136b2f21aba94bbc8e5235951b1b186fd4ad221e6ecbea5c7279cc8ee8b01edecedddf48cca47624ee9b155a4c167f140000000000000000000000000000000019852d2bc9c8abc92503f3e7eec9fb20df108c23643ba8a2fe16c2cf085bb4ac079d3f065a1241067daaf401b662288b00000000000000000000000000000000018bf1a4e74ac9507b97a990f3a41cbae3f32e263e9937a8a62679bee93296ee5cd25110833eb5d136425bae0e9dcb8100000000000000000000000000000000096ae4bfaaf4f18d3e987d9f287fdd3dc9b497cc84867e757da52bd5f58688403e1c9cb432a2eb87e239879d52990ab5f1e168ab93674bd7f2bf73318a48ef17ef4464fbefd39f77c17ebfdb24d679b60000000000000000000000000000000016ebc2ee18515354b7af5d924c895ffd5556ad088560f89c59a4ceec229279d4075f732b884a6ef2bb2eddc11d27572500000000000000000000000000000000110282084ab6f3e76eeb9e5e8c56749992913c2404b003df9c2d01d72751f879538d23f612c8faabbccff45185f4c6a40000000000000000000000000000000017476677ebf052d13f60ac0ec5e572c398f1a478d60ce92a3de88a74a28688d786d30b1ea8008409e45697db0adc628c000000000000000000000000000000000a5e4239d938bfc7c05f3b3a850ebd5f7784eee7aca48c861eb4bdb1ce6321fc9c6bba997e143aba13a42f69ea14937397fb0d947d71a1b032070a12588b85065c19affd0db53e466f194f04f58dba2e000000000000000000000000000000000b6e16f2a6cb821abc43c447da207cc3013f2f750c844f42f0fdf47160a38501bf502073bbeb565122bb3de61b3a5ab800000000000000000000000000000000040f5f3aab5d416e9a084fa298814f894ba599315fe10af20f836e624680582413b4a54623cda8ae2663ee094e4db775000000000000000000000000000000000d32ac715a094813c7b46ce2e932365bfd62ec5e584e047b0c56ed6eca3c58268ae01be31b833be7ba5c2588ebb9859d000000000000000000000000000000000850b9044f129e51658a02cfa49d40a2b09239823cba4d8fe423fa1b4815750811daf745e7e02b317a7318aad0734ddc640f850bad2f22049f2f8aaf3ee57564fb38a847e428e252f003eaac465f7d670000000000000000000000000000000010c703e31f2d488812a387596c797d8d414e406bd82f238cea50a459d842502e11220ad82fce5dd36635792ff5770bc50000000000000000000000000000000010c11caa640708850e1dddd48bae22961a45029971d823b53030979b7d8ef2eaf2ed055436105697c5b0b31b1a9d0a7a0000000000000000000000000000000006b98568b2b7f0aada97310f7e14084a14bffe580ec65bc8fe5d19c6213c45dc1b8e1da5c6c1b8555729f6c781575278000000000000000000000000000000000f2c506f3e41c28a748656d1dfd87e812b3ba21815637e497a30eca4fc5de18257846f12b67919dd2d739477cf5ed0ae8bf91051da5bce0a51bcba6f4e1b3c9063743646f4e75e3e5a8cbc84e8112af400000000000000000000000000000000102b6d561172adc9316b3ec11f05e66e7affb1bdc70a364faffa57aa5938c2ac08863be8fe79ce3f627558fcb2ab1230000000000000000000000000000000000c5e72c271a1ee186d443a96d53f0ba0ce226c76aff2a7c3215c2110f96cb3301bd586f509edc45cd20e662756897b78000000000000000000000000000000000d546fbf485bb283a04fa05aa962ae8d77ec4d26f749d83b208f77247778e32a9a2f1483bd84488806e27b13eabf41d30000000000000000000000000000000005a42c6ce8d43d122bbf984e9777f5d1c15057f27e70fef44b97c2c6e7e2e303fcbad643027b7ff3167916f21a723ea98da771e0e827a52a2f7e79e0e5d93ebae04c1ed78cab87d4353f24ffc52099b3000000000000000000000000000000001788323aafb95f8761f87f771fa05a8e49be71e397849daef5877a7f486af13fa651be7a93bdd9465df7be4ff65825fd0000000000000000000000000000000014b7a56f3f7c12e39be76b3872c1ee648f62f9cb6a1842d869e00a5dc2ac8cb4ecd96ec2483d5eade5b0f9113133bb050000000000000000000000000000000009a30623632b757ae8d03ced0c1fdd1877718f8d84f34ebb42426284f73bb7e8abc31a5e5ded57a02d08adaa90abfb2600000000000000000000000000000000020b47acafefce7f617081e22b2bfc566acec6d2cad5063a79cf33e02cc8931bb698b72184a11fab73e0bb0aaec76c61d6cff707bff10fd53ffeff8e9400966d8ffba6d4ad6a8e7e456df10f8f5ebed2000000000000000000000000000000000d1190466f0e8f03d2cac4a5e63a13d7c6d0cac9f2065295e2de818773199d731f8cb7b2be5f6ef0a246401b345a2d560000000000000000000000000000000007d9c5d187494df79c25b6292527b0d6d8c50b6467bf76a1a1316556e48159a3b5dbdbd9fb0bb901d857f61f423d15db0000000000000000000000000000000013e4401fe76e3f1ef73bd244189cdc81fcc152f71449c11aab24c4fa1d123c5aa8c68a2d10fe88c1c6631778dc0bcd420000000000000000000000000000000004ccffb4296883b8690b2f3fe17e4e9ab24390084ac917ed28fa1e04b9758373abd348290d24c915dfcaf0649ddf5a87e00831cce307cb44e8dbd5edf24f1535b837277160d2cf6daa4e862e57fe73b10000000000000000000000000000000000f4baa5e531ae462b95362292d5366daa89f2fb2707c58568c094c58578e84a8d253fe1de26b917b84635c0aac3a63300000000000000000000000000000000109057e5c5451eb9f85b95aa5ed2615d2faccd0539b1e4481923e04cbdbd2ea9290969022cfa508d3fd050549c74940d0000000000000000000000000000000001c3e147ad9c31927207f2344fedd541316f4010e3de194f924c4a1450a221285b76ff1894f8b1670731007f44965100000000000000000000000000000000000909cdf5c56dc177daa1f3fd7cc31d79a4f6dfcd462c07812cdf629426b75bdaa297b9d7e67aefdbb58175a21e29edada8168d56385722f339a5b27fc25a88034d348e3d533ff4dc99d28536c1c09a770000000000000000000000000000000009b4c6bd1c460d2e93febfe523c1d54d6bf6af50838e7a10b732c1be8748a0752a517e7103d0ffa4507b086626fbfa8a0000000000000000000000000000000015bf2c13891dfa8dba35b5da1235563d4ee1dac33e89006f5c9fcf06f2fef7b31ca845bcaa8ac608046e8b01c8a61fd2000000000000000000000000000000001898dfd6a0618df821474b90542f261c1febbf2e566978b0fafca44f6dadc57202f88366b19d2c955e4291ac21beab520000000000000000000000000000000019287e1ac6b3eaf412e58511b40d87558e7cbf90dc8af2f5d33825b40fd2f2425d0be3a05d0a49076f4114350dcc601eb929ae82ded73a4876c041d2e52fa811882fb8e22690a27cb4ad3ca05169bbf0000000000000000000000000000000000c0993401c024d32cecc0d86d4cc52c200e59acb34fee2ae052837f467905e736a1118260ee12a963ca2df6e1a6c9d0a000000000000000000000000000000000103f78f0e7c9a5628a66efa91f150a87e67623ded2560aef278a8caab017fdcf181981952b450c67e3b4d3f362822a80000000000000000000000000000000000df01ff335f23652f1c34480d23c62d705572321c0e7fe92556e033dd3cf5b78a3d554585403a7f3c71744c20d17579000000000000000000000000000000000a0e2c9e2e34e5cb36e96b29231f702abb127a011c7ea3e21d59e5c55f745a02039a68d59ce8e29afac0752d1939106936999c516d4acdfbcd488d39e3073db9db6cdd0c0fd1d29d58294ace6d2d199f000000000000000000000000000000000eabff0e6ed9dc358881796441c48e722ea171f26011ab898c5a06758f61a629ae21d5a2595a22dc9855fd2e516b30fe0000000000000000000000000000000002732155a7a2791078dedfedfd3381281554c389bf9b5baa47593153a2acfd22a08557d7a1d49be298e416051b9137dd00000000000000000000000000000000116faa2e2a261e6a3e4de6ad80d75ee05aebae47872e2eed9cd91aafb94a706de673a05f1b86c0b0131cf148a90b2b7900000000000000000000000000000000009a04c09c2a4fce22d237bbe930392dfbbe5c82d480abefbb3be876015e2f5889a0922df6d00d4e94be0e9fb8d2f4a1fd0bc405e3970dc2bbd7dfe0c54b7c64543fc241000adeef4f7aa2f1dd2506770000000000000000000000000000000002a6402848507062e5c5d63b1207a1a41d3b941d21792391f2feff95035f1b4625541770fa5e0f87585cfca670976533000000000000000000000000000000000904095ce640605c957715e378ed733ddf1f94d3beb63543a50c8922ab9f8092755fcc65e2a1ed9232c8cddcb5816371000000000000000000000000000000000ec62b911b08d3e8618880c3784685b2c6cbb07a4aa4e348ab72e4f918152622ddd7748bfcd79f35675cb956d11fcd650000000000000000000000000000000013f651e9104d48a081cef2ae0648816b2b4b5f644a791514e94a8e3dd3001099c27d1f9860337ced1b177b4ad7cd5866c36afa3c8581df069292d53b8ce3e35ca136a0b3f95a894958105fde9c77e39d0000000000000000000000000000000016334abef2a21b9c1926b2086075471bc2d2d2f66b963a41623af91fd2fd50f254c008fa3bad6b53658c2486edcc94aa000000000000000000000000000000001063002a5d17aab2bbb5da49e8bde63a1f3c4dcbc8800f9487f47c6d707109c86d3cf7f9171643418b195e50d7483af4000000000000000000000000000000001213004f31fdd0b0df5d8e3677c4f48624691e2534c02881c6cc6875b9abaee56ed5739c2acd66cb1b10553ba066ef1a000000000000000000000000000000000fb7659081cfcf8beaed9c1daf9e92702977c37a54376597d897082a25f9882f1ae14e7724c0aeb9e002dee708c6b4eb0f0a2bd678c5858be2a49ca54de8716fdeec84e1935b8f44545c740417efa7e400000000000000000000000000000000078f06bdfcbc7c0cc491fdc8069314c8a395983f9a2e5c2d1bec360f36e365da377885f897d8d711e33270e3ef9dc4d80000000000000000000000000000000007d43394d5175e020b3a5d768b60ec763d60cb1bb37c0343930fa82e92fb1becde0a178c4565df320824bdadd54ecabb0000000000000000000000000000000012f9fc96355721c35a6f5439065d89cfca5345622b3f38041b41c036b9bc6bcc980498ddc7bcf807e1b97831c099505300000000000000000000000000000000105307b482467b881a59eda1434e31dffdea531603fd3c460aa8d4f58d32668228bfa585bbba2dae7346141af59190e2c8e420db340ef2c1b5c6a71645e303eee95cd93228770b639287b14b6a5c59ba000000000000000000000000000000001576521fb3be8c3178549969e54bb17b0a3546ac4aacb470e935359e36bea4f43dacc06c151a527f441ab9616e07f7b90000000000000000000000000000000018dff940a21768ee9b9450fee7259663bb29af645bda2acb4d43f4e9d631e0127073f2db04293266e6fd6fd3d005e3f0000000000000000000000000000000000ca6a977016c1ebf52827a5ad52e5efcf7517ccc3ff40df8141f6335fb6c77c3fb8f6b0dcdba2596ded7c3838577e28000000000000000000000000000000000150cc33b55586fac30d316cad6580cee0a070900fe7d540167560b79f4cf9690a5e02cfce9946cf67a95dedc9a7d9aa35398541eb5a03271e2ab5ec2aeb2da80e634f63a050c25de98ad13e9d63d09bc,000000000000000000000000000000000adf84ea7681c442731be8db9508a050d4689c42af8f7472491655356a86fd9a0afd91435bdbaee98db3b1d8f26203fe00000000000000000000000000000000090a7dadc0a14df481e44e2005c9ddc6e026ce2afaba7badd18492bd3a338dffc349b4a339f56221eb795314252d53640000000000000000000000000000000007390fbc06077cd167f53a16f347eaf50ce8c9d111afeabf3a672687441b80e66a66ba3fdb28e2ca8282f3ae3dc81be80000000000000000000000000000000001998f98e85285a428a2860c22a00d0b317854da4190dcb4dcd182ac249e13c51f5d5df5db6a0fd61d01232cbcacd5a1,240480, +000000000000000000000000000000000c868a2cce65692f83eedbfeef6f9823ae9382fa5ed23395ff2444807e72403d4f6ac861ecd3a51db841889fe22a033700000000000000000000000000000000111c9aa53da85a63ce1870b963415f0d5f812e061aa6bff57425038d1b65fff57a78bdb963bf2450001525a93011a28e0000000000000000000000000000000011770810c16367d075c695981dfa69b072b82b034f8ac371f26bb157f9f9d667aa555a5c6baca69d08f421cd569faec2000000000000000000000000000000000df6146b29bc8226dccfc95a325d791b30cba8ff2495434d75622b170a634ec7995c5b4c689c73582ca861dd21d8e1e49f99387baca30b9cf63ad10c445daa142fcae1ab3c0a366a068bb5efc9abb3a9000000000000000000000000000000000fb30aac6502ecdd3544f1879bf1b3f4c19fb897de6c3a7cbf08f36244aa8e9dea8aaf781f7509d3ece16ca144a601e40000000000000000000000000000000012304be931a1d7440d67740f50b1a281468b412e8b6c54c62b993ec609012c7056fc7e62405c7530e8f5136cacb5926f00000000000000000000000000000000182320f5d9211c08f3ba5d40ccca45cb0060a6d362b4422084617b9d8212e94a9b878294ac176b8f0e959bc124a753310000000000000000000000000000000010be6678910072ed9f932ab01a2d72f7374a2cc82bbd86a6006a495272aa89fd655e6719ab8b3a0643d002021f7b7ebb4283a1773995bbc97a6df107082fed4ba40e2d30c5472a25a7643ca9e78b8b8b000000000000000000000000000000000f1ffed9514ee81e9b3fef4162c8f4980fe0429e57bbc224a9c9976cef7d26ab61ea7b0cd42eda30da97e3f8f5ab5f0600000000000000000000000000000000035b9b349b531d85361a4618a172b510dbc924df671b3fa707b474d0d8b17d30dc8ed208d66be91dcb7632d2f05ce31d00000000000000000000000000000000010030dcf6695d44ad3236032e47f7aa25b9f55869f5207e7ac8641db8c01f5b59627dd3442a1834b8b1fc595e47cdcb000000000000000000000000000000000f91ad5c923572a75d32962567e7b1b0eb84a91d485c968b5aebf8b3a772c2f94e47bc1d5b333fe43574308a78e768ac7f4202d670fc3b48eaa92e925f48821d2ae057d90c5f184edcce9ea900ab51a6000000000000000000000000000000000ae11c60537bbcfa46a08cbc219122ed66fd0d42f90e68243c32010eb99942554c349c021f0e3635bb50f7ca3d106a3f0000000000000000000000000000000019a61254aaa5b51b4d354f444706ebb0bc3edb87ec2d83e830ffe0282bcaa3278e947d053d6678549a098129bace43da000000000000000000000000000000001100f48a07456f01e16bcc833ae0a2835c964e9b0aa850574dfd8b4a7f06d03059e9b4df8931740ce0621ec7eb31218400000000000000000000000000000000003072392a824c386859735e2d203c9d52c19796ccf8538bda3b1436b2f6815bc86d05287f29fd0bb0569a81a57f0c22a76cd8d292a7053c449cb98f13cf768c6e37da9d702af28c16dceacfaf9cdef5000000000000000000000000000000000392760f98883f9cf6c0f0a324b9a645cbae12b780896f6a3eee918c44a815daed156248d6afb25901521b323f6baa240000000000000000000000000000000006375c6629f30b7a36785269d691772afe1b95d6e1bfaaba9459c31086c2697e4ce77d148fe2ea166cc330373583f4730000000000000000000000000000000000aa8e338df7eac5a7b070a69d3ed1553a0c52fcd894c2bc8d1b8cf6ed38983c6c392a9a045ffe8ff40b39d18e7c87c9000000000000000000000000000000000cbc73b589cba1bd47161282642fe6f51f2b3edcdcad6020bdaef369d3f2c11ea9cafb9a7fdccfb89bbbe13560d42d1d97b7bf8acdfbb148814afee1df79aea17261dad6f78772111a6dcb021d8c79d0000000000000000000000000000000000e71692cc2342d1e93e0ce72be69013023d012dd2294249dfd69e1d610e2236ee2cdef22446f1996bd3309825989930700000000000000000000000000000000013a1bbd3237dcbe44e05234f7e41982f4fd951d3741a3e90345418af1c922d35edf776a27bfbeaf7a15658db67164bc000000000000000000000000000000001197a2ee5c2541e19b5368c97abf51fde3dd0b922c3d701d7d84552c9f47b38ca09a8aef8240abfdcb03292ade1ff04c0000000000000000000000000000000010ca3c22ff8a47b1c683a58086ed9d831a5c25b6ce5a1971989974b4760cc9e83a1bc8d819825989751405b242eba379efdbd5953bc33bfba09fe7b3ee22c46c3a86f557e4b5f272853e67fd95a0f9b0000000000000000000000000000000001306f8047ba1a3417e7993bba0dfee9077eabfc275af91d0b882a53199874e0777d8dfd29767186d922d49087fff38b20000000000000000000000000000000005371b760380a6d287e129b329e735413447969eb9048def44f5c5987a64323d2a5c81484c40b20206832b86a4af9c4d000000000000000000000000000000001552eeae620c42d0bc4593d7c8e2c8fb4d6dbfcdde68d57158a7dfe837a1870a73b45a97b02abdea174a475a7061331400000000000000000000000000000000033a6dec61540a5cd5773b76847dc5016b309c5a027639598f51ae5b1067b3f7a02f5ea11b0e1be77a3ac236cba15c929a331bb218b99fd38451483a10e8add23c9641b975af3897670884efef90d4520000000000000000000000000000000012ad5ff49459fd3a7940a69e2a78919876e9b3a4f0c142499e7b5dbcadb5c2b5d79c5dea972f0f0acdfd10ac53bcdd92000000000000000000000000000000000ec1be9cb379bf1e24bd5429a4a91857bc3ad45095d15bc5537c2ba39407e9f2edc5fbf711ef4287a73ea466d4f53c3800000000000000000000000000000000173605df66aaf51810793db1cf2021de6a7645ae84a5d439ee035b917d037d9f9ff072b5dfe8b9ac69feab60fe2d70bb000000000000000000000000000000000d0bd336825381ae1e18ca37bf6160ae32b653ec9f9dad159006e92c24b661f22b5629ba323e9e06ccc5887a962ec23fe9301dc826bfe2988cf93c29ca9f01421b75ba63c5ed2cee1599122012ada36e000000000000000000000000000000000f5e593c6588add92cac2c9467247fc6d900f20b4d3216c258f88f3334eecaccbf3eacda227e2da46cf520e5102a9cdd000000000000000000000000000000000458177ad6c190222e53e054546413c13216286d414e3509b7dc794dc0704afd26bae93ff630c6157d05d46d805a04470000000000000000000000000000000015df8a7720d389e6112707e37694afac2f97282676a89964deabefddbb3a0f1cbc885d4c875b945b8303c1ed2c0f46b8000000000000000000000000000000000e3c7f1af7cf5923dccfc1d25bd86088706a3a44f5fa7f97171228e8f2a2b18e9631b2a63bd5a75ee0bb83fcc91a45c30a1cb530e8b828542fa4114de6aa936bd2be5ef3a9b7a0e20e475022381d62d40000000000000000000000000000000017823fc8a56e6e5cb9924037ad6ad1b43237894a877572dfe3d3cdc1120fe83e01de112b55f7f334dcb5c6247c210613000000000000000000000000000000000daa01f90cd14d82d4fc40b60b463089fc6c0e567fa46bae69184d0e3cc5acdb1d759e3291e2781fe0b65c734ddde28700000000000000000000000000000000164e742b123c19e52e2d7a6727689181f323990a3f3238072f7cfd7fc0f55b7be4274c0df194d85060a81f3744d3978b0000000000000000000000000000000007c03a1678b6e91c1bfc66ce8fd419cea13c7cda3213856ad21823b06db94538153a15d43a9d4270edf77b9a5ed490e6cf2f0c33bd044e8c4468b4b7e137ae294c178e7b6c9f19878331fb93220db2cb000000000000000000000000000000001865bc91e645e2e24c3efa3afab8b0e278dcf16b29831f75b3eef0b342479e997b9c5f8ccf67c789c830609b3cc425400000000000000000000000000000000018dda7857f919a6a49f6bb465c27342c8fab6afe6350c43b98e91a3105276f3ac27268454e9a9c6dafeb2218ddc7d3cc000000000000000000000000000000000b098258ff8b185a5c59b46150954d52db5a5f68bc7975234491406131e4f1286ce79156dd1290aafe688f936ad34e31000000000000000000000000000000000b294e9ce904fb9e243d0790147b6070b10ff611a06e3f639aacb744154d02016ac08f6769732d4f6944ce9257680d49e5f460dacc592bb947ff6f1c15b8464824aa5c957a645a763138ac1581ac5768000000000000000000000000000000000e541a22a7a36adc06e445f42497596e1017a1d99de85bb945a195cb3cf0c14d39eb7a2aa994cf234eed77f6307cf6410000000000000000000000000000000002de753e41a16565e5ab1b61debdad54950e9930e04badc6e356f10711d7688befc6827040356c0f0a8ce4f8d7121b3a000000000000000000000000000000000f2202e34ca164f1a6c0afbe179b714b303d87ef14534fe3f4230180f709dc63af17f04487264b3dee6b24ec4d0a423f00000000000000000000000000000000004044d9e3b3a77d6a309780c870a65e05e1ac531c5420f6ed0056f5e728e2b83a968ca90d579db50c2dd395f7e40beaf26a9736f728e16d7b8ce0cc59e2ccc848c181459fff4321982c08e9cac5794600000000000000000000000000000000166d7692fd30dcd06b9f01ba2101870ed347840509b3242f7cecf91fbed91abc24b08b08cc39c508e6499a2f8bc3637700000000000000000000000000000000076ce6dcbc77812b4d5b44a50edba5a082cc36dc24a5cc348283a4ce1518198b56134c9807ef850edc9e36e9a282b9ff000000000000000000000000000000001261d9412245abd7ba3fc1597f34179e54766c49306725d42588545e14f4e450ee1c7af913ad7225275c57680c23aa6300000000000000000000000000000000096602b4eee053998555ce522c060d5e04c7961eeaab0145d38c9b13362624f54fcc8d0b77f2bbaf8c312a3279f06e4eccf0a9be4775d65bbfc894f8ca66fa6f69d4249ea7f6b076fe193f2805e64f940000000000000000000000000000000012be34c18145aac51a1494f4052edbeff14c2812ff494cb78198cd7d9db9e951aea80490c55c4ed926f6a96a2c337c880000000000000000000000000000000000536e46a63ec5ac0f2f4eaaad6df98322c6a981cf2fc8ef253269cef20a76ba1ad089c24cba4ad4680dc4192d66595d0000000000000000000000000000000005363b9acb66ee95713b63dad076529805c0dd8921c738e205e7b1d0410a3ecca0870aeb2e64cf45270d49b473371ddd0000000000000000000000000000000016749b2b09d889b883b6fdaf518345d4cf097a728b833e92c4d21b5c41c8d5cfc0758e895b60ad101a74bbb6be6ca0c5fc6bfb37cbfb10a1ffdfcb91d9a52883cb9a606f4ffa8849a6e07386dc9bb34000000000000000000000000000000000067a684b55fdeea39a29252b355700a4810f083909cf2c07a80b362ac1b4d58f5900c68d266f7ad81ea278c0931bc1ec0000000000000000000000000000000001b1f78d194d77cfb4a2116ce9e29438dbf38c52733b0295198159d7cadb2584d86a75c24aedeb36234a0becf9d38a870000000000000000000000000000000011fced2244cd959872a25c0c7bb4af6151d99e1aac079c606db4987b9ba111261d4a16e7d82362b865324824445a946f0000000000000000000000000000000002659e7016ad615ed80ea1ae020903431b470bc0341f8e0918de9b8d2e933dd9f2d9123e9e9d20bfb05d49f71c3c454cd94959e16f6d780628694075ba5aa1a476d89d8fffcf4b4ab7e6343c011fee920000000000000000000000000000000008f3c5de8c94a98dc5ad7846c53980384f997d1657f7349ad9b51376d41f4b21861d212fb6428bcf2347d8774f44156d00000000000000000000000000000000110b245b1e788da41dcbf60a3ac4987c1925696dfca85d450107f654fa1230adb9436d60c9e742dfb4e453ec4944c56c0000000000000000000000000000000011043b975e01df36a36307ba9234a18b97aadb9da509513b13e4f3c80432b0cc5e69a3bbb3cbab8df41bbcc92cdbf60200000000000000000000000000000000120aebda10c52a67d23842e2bd9a897cf38c58fcd11e4e8c5614db5e409a7c03111feebfe2f1212ae753497dc59d6ae9122f3a5e940ee7e5038421619daffb8a6f433605f37e78d863f814b51b2ec4e2,00000000000000000000000000000000021067690e6e001e3d0d01449a7257348c4ef68e66dd47b9014d7687d44749be1f33e6be95df6a204169ab7103dc2d3c00000000000000000000000000000000062efa0c36462ab0734128dab5da8635705bd1e1b540817c5805ed9417f176723eea92425db539e8763b1c79b9923e9700000000000000000000000000000000176c9af1970f026bcfa87e6f85a20ed498c28c6982e21bc050cdc29c0f0af832ed4424082e4862d985b78519cfa75b820000000000000000000000000000000018718b0d0fbdf4783cd0b01524ab153b891fbf08cad60241a3f3163d2c3496c36afdc6de62ab3c9a037f88ee408ce5f6,240480, +0000000000000000000000000000000018adf92d8da050c76118a3a3b2ee43955ae8b14ddc8ed64f5672f40de475f7e0ba6ff60c4b6ca3e863d7914e6de2cc330000000000000000000000000000000013d1e19011a1ea90389480d14fa608985d895e05edd9c28fb34646f70fd7bdb7857fa785b1e3c8a2997da6c3b5337ccf0000000000000000000000000000000015764827d9838c2b011660230ef9805af388fd997cc229c939bc5f4213d517dd837328c45b0b8ee1d6508cb70629b7bb000000000000000000000000000000000d58fa30a2d095ee8d946e50a027ac4cfdd557b3fd9c82dbf1536ddc0f42491a176ecbdb026306e6ebf1bb182a4e8199b3908c739d505a1d6fa85a6dfb7a155202710b45861f1a8a7ac7bb3274a180cb000000000000000000000000000000000cacfc8d0bc6f9db737c8a316043a6b52fd5946937467afc09ddd14e509a89f2445065ac8a8c56454d529d67793edb0400000000000000000000000000000000148b1b941f159d93170fed949d5f53bdd2603d78a49443ac0e2353130ff914376e018c3db3d12b807d105f2d50eded8c000000000000000000000000000000001382a3e98cfd072807214479900a8602bd666cac7f19be0443ba1354bfc05666f40384e9ccac314b5d0a2bec1c90ef0c000000000000000000000000000000000c12c2222f67a5adba78f2c0be5be95ed743e835857f4204cf47b67fa2eac45cd5985fd82c7a3904944e7b84737374b17e0e27a8a416eb38c989a66b84f037a5a24ef3358e20cd553f037a0a2461d31000000000000000000000000000000000197ff997d6c5efa3d7de8e16f26082bf13a2401d6df5f5c33c6614c36105f347e40216c907bdad9c1df6ebbd44f41c3f000000000000000000000000000000000f27a0bf92329730d776a83583177993b2b354a212a9c004f9f8892a750c477b8d1e68c13127f03b1629bc8392d06f5b0000000000000000000000000000000011b239cc6914a321385d907527b85713a0d842f5be80752f4c5758586dc1de944b6e4578bbe324f16838115e9c866bca0000000000000000000000000000000000cf93c5b48cd9de51ccaa45124217cabf466d07d6fdf4a7bb810443339ec4af5b74931bd07eb9fd31c284c05f3f539e0a3cbab01c34856b892aacdabe63d0a0c241ebc137a88c83ad22cf38997b211b00000000000000000000000000000000137b12f731ec925dc51e20a9c90323d14e1497e16b3a4b5651135054ef0e58e9b18167da15220b9a4f7d81e9a7648fc20000000000000000000000000000000000b2d3ac534e1e5b2c9ff4092c2d8dc5efd99121de7df953e5426eb33934ef07e41b196eca50f5a04a936881a05f2b2a0000000000000000000000000000000004feae2377d950717695606844a4873ed7b5f6703d7a63dc8b960b99b68efbba710c2db0f1371acbee314875b97ca054000000000000000000000000000000000f49ce3061e7254dc1bc8af3636a05e098cb96d81fb31e25da97c6266adf3c41a74d46ff32f4fbdb4cb7e4a3f69e827bb386bebe0e49b7f07b0ac61b15306c2515a1ad6fd76a1825dd29a60e845c0e4a00000000000000000000000000000000064ae3fd67250f2c6332e1e149ec09946147e12e0d871403e559b49aab68190a1454b3ae924727b6dcf6e1ab327c9d7c000000000000000000000000000000001131f91c7a0e1854bba3958b36083c27904cfbdb8b8cb3fe68cf578bd1cb6f7c6eff91d98e4b99086926c5d4272cc1f200000000000000000000000000000000071c6a92a8d460ff72d172c204c8a69d6b6752b8c1f731ec63f7f394c0c3a2a1bc15e865172f693f523c11cd4ab1f88e000000000000000000000000000000001193876df7f4a1cc9b337a41c9faebac2f209b9070bd75294c2a88d3091a1e55b51fad482fd2aee8f90458eeb7e981fb8902a82d33993a10c56b2fa3333cabf1c5d47a9c78354d58f70ce4807cf2062800000000000000000000000000000000025c20ed5572dd1c9a098f241d2965d8739878ddc57c017632afcf6e54964894adbd6d30f62f316c9c3ec7a08268afc70000000000000000000000000000000013bc3e930f4fd5766db8f04e1ebfaab2b67f620119c39d687c68619b3564f3e8b74666c9f8bed6c1f080a9e23e9c0f22000000000000000000000000000000000973a3cf19312f90843f1f013b05484064032557807ca67b2ded4a27fdac12d6cd0e1416c8998cc8635ce10046adfbb900000000000000000000000000000000108903617c78fc608eaf007aa13861c970557f2693b24e8a278920897be9694570ae6e6c7749c3eab84d5fa3af5164b1426a4e2317fee033a226a91a52a5830f9ac2cf5f329feb6bdb382438b8a39f2a0000000000000000000000000000000005695975c140fa14998e5916268bde2135cda80a45414fa85193fd6e13c6b5a6486898f590d76175d8ec2629c923e33600000000000000000000000000000000033f58b1cf67e51e9ad817b31919530cfdb5db5ca4a537d9b006b63399da49b2a5077bf5c3b3b4fb10b2478f466542540000000000000000000000000000000015c532e40ec04d9143e308895b2e7e3d3daee093a5840e1e76ab528fcfa5be57d9796ffd58ad5ab7df6f88aaf34706f2000000000000000000000000000000000b55747d1e8b66e2b2fea67229f2b7b17d58ef547ca841bea8db5b53fafaa18390f11b8170c41a5dd29331917fa2e348de0390c05fb0dc9b4a3f76b51cf952a11b909ce13f9abc9fed6a349b8efa98ad00000000000000000000000000000000001ee5ebf73bb40a5c0822350853bb5aeead3262380dc274faba6b04e58e7fb9d5a4ace109ffa5011e73e3d89ee6fd77000000000000000000000000000000001427659e5ab1f8b47edddd27c613b578890d4c66c835c0cf8e8daf19d0ae842f0bba5bc83ed7248adcd75cea5d222a270000000000000000000000000000000001d4560185690ac05e56c2d629d599bceee3ed2919c29e3d1ac54e80ae99b5eb2f93bab865e8c1eef7206f96b2bf4eb20000000000000000000000000000000016ecd3589e3703e5b0ef53790130d5074d2bc0fd5839d9c6ff905746a77e393f73edf53b98b99d9c87a1fee1086aa8657431db9e576643f93505b5b25836218759e736c0d650a5221a652338b0073eb600000000000000000000000000000000163850016261f34de2b831a0a8dd3f224adaa3cc279cdb40e0ae976bbf736dec26c55a6c79cb1c623870b62ea216274b000000000000000000000000000000000a79af5c054cd08608d4be1705058ef7b4ec38a8727560d960f0325d0ef915c049a89e76956d0296bcb6c96333c3470c000000000000000000000000000000000ca89379e558c7308edd25bf06dc05db857204e9351299ab66bf050c8f051341a6c15a02864c679f07373038de3fe87c000000000000000000000000000000001929f42ee5d9dbfd1f6656f61e6243ebf0eb491762b7f3608db3f3e9abf565ab1524f770cd2ade334885d7479342c92c6745a32591e359efa41e9ea93a016d2eedf1da112cddbf31818e8d687b36af2e00000000000000000000000000000000193b6cf7300e47ecd21a05a71b13a8de45418d3f67931789ce6111b8633b9f44063ca13ba8c8a598ee0725caaa3f277a0000000000000000000000000000000016884d982e2ec0fa7e59fb34ae8708d0bf4abfc260837ef4432e8e04474e504b85450db8af8e6809413c90268801fb3b000000000000000000000000000000000fb48a8331f278845979beb8cd21060355566af215ba44029455a03d0c016daf0f6b7c5773d1a99e893e76b4411a53c70000000000000000000000000000000007056e30143058eaea89a3065e1de768d49860b170d4c364a28d38475f90711fba62c1787adda90dd2d347da72680f4eed37a5f4bfca6b77ff9e4f7e03bfed52ecf02a8f84ed3da6da2787a4ee81ad9b000000000000000000000000000000000501fa9af88e28d4f0c0590a2624239bf1724ac7174b0f1d5fd7527cff1de9971d6aaf28ba4005e88e181daffee6b20f0000000000000000000000000000000007af5e30b5aa9ad206645ace12cb2b36cc1c6068e604184ca8bfaac5a4ca327f7c43a74d43417918da7df84e3bffd282000000000000000000000000000000000bfc0538d52f277d54749ed0b69697b4c60ef0c5483d21dda76533e15efedc9e2b2ef07618457d64bae8ef922c0b41f600000000000000000000000000000000048935cd352e999bffa613e3be0a9f9a063d5b5eb46cb5056e41ba214e87f871f216ff41ee297aaaf2994a7b6433f58d81633dd6e729bc17ddc596cb1f17dc6f0e50c052a0b8c5a4c83900d918a9eb560000000000000000000000000000000016ab1e8b6f41891e0b65f14397c0887b27ff27e7463333e0938a7a1a181dec603056afbefdb23b41bbfb2c05807289b8000000000000000000000000000000000980d0ea9ad5c87bbe1aefb708061f85faae1e1e3b01c55bd577631e5bea2b5ffaf5e2478f5a8df89447fb8a73559729000000000000000000000000000000000784d0c5fa243bf0125cb2c83a4040715197e99d507d71a3bd9ca396074cfda652c1ad0dd95c3cfae369e68d3431ee7c000000000000000000000000000000000e533bb33e6d269dfdeedf7d17c3e0c19f694d151e8eef801c326cbcbc463a42558f58cbc330bdff0d8d91e2974eb4cfc6b019d29219b57404baa955f66cf1b2ee6571ad5b80d471ff6db569e32a1a5000000000000000000000000000000000050f005b00f371a7308b5d7d7f67f7c00bf15acc518942607f32686feab5eb503391f964eb7ca711aa6c7b4e494d7eba000000000000000000000000000000000e2ee5092170ea3da0b1397023b2386c65ec8b090484353f2e5d64694aaeb8d5410ae22c92662fcfa21566d70173ef36000000000000000000000000000000001549723160fc7b8f5ef9a84bd1803f18b76698aa7a663d9c107c9ff6c6d02894edc80fd00d436f3a942c05593c5464ad000000000000000000000000000000001032f49e3527cc1f1355c65edb21220c6afc88919ff67ba99c65645cd3b8ca6662dd0146f6a90d92558b3f54815a361d6a76411ce02b4dfc84ddf62ed26508a2dfa5edb5a98a6a20dd69e8b8e7ad2f5900000000000000000000000000000000170b317e49f1304570a3a3e6bef78fcf8537a451ebcfef5afe3eac4aa1aa87dbf95d0f870fd3372d37efc9e663621cf7000000000000000000000000000000000269ae0677d71b2537078e96d2593482e4d41b6d1d2cbec755f307735faaf79c01fa27f1103cdfae1a9bdcb665f592c9000000000000000000000000000000000b115d5a9fb9fd9361d0573a8d68c5193f02edc1cf3fecf004c6603f118f28ff394220f6a9e1051a5d9d4b417290b7f800000000000000000000000000000000107b45614b18c2513f8c42a0032cf0f3f300157b39d2969ef7b126f17a9b5e8e9ecc5a61a2ed4db92134b0797f6a0ea35906098e4ad7e4eb2e996075c7cd660fbc399bc942f9080404b9d0758c4ae14c0000000000000000000000000000000003de39b056f8f0248b138437db1536b7bfee29af00c37fcd14c25c88f0f051eaa07c763d94c8ce497696311736c0b7140000000000000000000000000000000002b52981e828f8dc1cd371e6821d001e1f96d57a865a3c0a255298c43d52741b18fc60903d1a5ef6227061dcb243096c0000000000000000000000000000000016b5335f0f9516f52f2ed45fe723ded427206ba96af0879958f1f22795485b2867e953de3d9b3a9eed2c37f26838e1540000000000000000000000000000000004c860058c7ea2e6e4eb2a65c1dfc20b3070f89ff58ab99bb51a4eb9e7f0642f7b32d1d9f27c668a36a9e053a8d585f394ef8c281a9be3766fe784ae017d93f608dc2cb97cbb7dd3e3814b5ade845d370000000000000000000000000000000019cbbc125ca1b89330c21ef5b42fe0dc1e795271ce4a9ecabff04eec9029f756f180520f0e7b84be2e9fa4af395536ab000000000000000000000000000000001630cf0c4f3282689a3e01b5c8f9be3803f60238bbe9fecbb0d9e8e49f4ec9f6123c44840acb8cf55f8f6bd15579e6830000000000000000000000000000000012afb848bc0ade8f0c25c6c342bb651a7481be065a48944bbedbc14c095af8a4a048fd1e776126e2128f904afbcb17ff000000000000000000000000000000000dbc984f9ff907ce5553bb11a458deaaee0efea49d6816ed7abf1dee7b70cb18cc669d4808e75678bb898359c7ebedbe6feced33019b3b66d335f2118cd22b2952cdf9757fb3a0cff55b7c4f245fb438,000000000000000000000000000000000be6dee62b8c85e36a216d16c5477a7c58f03b992277af83d9b53b3b2169414b72bcb4a97e3667482e888738ff17c94900000000000000000000000000000000067337c69c37ef6f0ae59fddb84c46a2afe7fe047ddb57b3b80437609f1a21fa5a73420fa5b44704ca1cac6c7a99d9320000000000000000000000000000000017fe6f37d2410159e533374ff3812714dcd07610d75a53a5d502cf2f51e750c48858db1e109f6aaf724292c1402382f1000000000000000000000000000000000b8ecfe1f5f5d95777b0fe5d94fe81b82656e6e5a62b7591788baccd251d93e4bbc6857cc87cfe6b4ed470c33631ae22,240480, +00000000000000000000000000000000126d4a9ae3550e31185aac9011e3f086517cf79a279326c264f51bee6615dbcc730d78055489b5602e91b08f96d23882000000000000000000000000000000000aeff5fc04fd06c26af8b048fb2d0d493525ba5c2bde30664e7371812d529ec7dbd584c056b05fe02179b7eefbbc45fe0000000000000000000000000000000017c6538d2801947cbb646d4ec8b70b1e24453f7a984db7ba73e3a5dcf595bdbad9703f2d846ab02491e5e3a5bcee0762000000000000000000000000000000000badf551dbedcefbe7c303a5c8a52151b5460caa22004028893af4d8a3fac30cb1da1e986f9124acd5db7a634657dbd0cb5e7df372d346fd13faa90b0d6961372ce2f32ec379e5e50e7ed8a13942cd9d000000000000000000000000000000000bed71c7d878e7ecccd8233e3e604e564cba0b1ce75f726f846f3a6e2f3b4f5b12a28b8638be647f5c33226edc2bc7fe000000000000000000000000000000001914c20aabaf1f6f82063223053809622ad82a3a54668bd600db1aafba22aeee5c8a07584e263c91cb0fc5fb809da63d00000000000000000000000000000000056d9cd8f79a90d16b36bde77e546f8b3064ba7dd0fde78d6bc538bd6ce12a4f32860205d5d396bab3d70deaaaccf9450000000000000000000000000000000012f7e420708b66132157a80753678de292998cb6c4f00244d3c47a6077b3401132b73c7f52369aa2a6a90892f7be4ed913a5fa1674c20c97d08608d200f3f7611010e6a25a790853ed4ba0c5aacf111b000000000000000000000000000000000339aa1471eddee8cc0a4e4db5a29c3e4e92cfbabe023995a79624614aca522cd459dfacc0cab346b1cedac347e1df100000000000000000000000000000000016cc4ee8cb72fe09e65616fbe9bea1a0077114ca841ae335f1f9eb5a0b129a4bdc77cc6dae8727d74fe21f0d870a43f2000000000000000000000000000000000098a21da6e983228ebbed0ec3704c9d2521e935506c0567e3bbf9b9c379ce6d33c3d0dd8f5e013b431f740964db634b000000000000000000000000000000000a7a38abe8e282544ec6c8740dce8559fd264393d0a5c9af9813b2430bdb92b3150eacb6732b9cc278d0d0e622b263ecace10870acf190b373c19ce615e20e5cb96d3c6be3ec155f2b29825f8476b7740000000000000000000000000000000019ed305bfe8d8bfcc20794832b3c117715b6a658c0bfeb629e5989f265cbb456e857e53d168932589e4ed2806db7c4b4000000000000000000000000000000000e2ffda25fc316a38f556b35a7a3acb1a2bfbc1f9469a1b6427ed1f216e113a379932b0547f5370be1017a1fa0266cfa000000000000000000000000000000000ebc493c9a79b8ba58f48b90b9d287c74f505dcb484eabda79ada987d63a4df04d671d4c4ae4b32f8ad5db6a1b80f37f0000000000000000000000000000000019fc715d26c0c7a0c291ad8319e2e8f2920c63b4d4ed3f0e2f376aeddd4f7bd9269175ac8d0f421b001e2e48634f3f238d9e38d9383f09cf0f8a8077f1d1dba091ff0abdf7e77c3b65c2df48d6c6f536000000000000000000000000000000001285ff533da833a3daae7d815b1b86feb6f20b7592af8b0eb76240f390ea48b69a75547b040e7282b71779f450d3510c000000000000000000000000000000000813d38fa21c1f3c87b9c97ac03e6aeb8fa23e0340a0dff4e3892c774595648743d0b8980a7bd21648ce9b16a245ac3400000000000000000000000000000000020a69dbfb736c64e4cbc800aa415729b24ec05e901f2c7ba38e49a21c3851dc03bd4f7ec829d4326fe6c13867069a07000000000000000000000000000000000d518f3944053c8f74c0aea1d054d89106312880de4479b3dfb45b00945ff8bb58b12f9a489fa9fcd87194a71475d0a1abeffecf9b404c6bb2e2d0c78fbb8609a38e3d3187587c3848e8f9781b7e9f440000000000000000000000000000000018c82052cd483eee7aaa421c2b998ab0b4b32326dadba03c1d923726697d3940b40d5109ba34de09439e833ebc19daca000000000000000000000000000000000e4feddc3eeb3fd1eff8316d5b0cba554714713e8a605a55909889970ea2c8c58bb6c568024709def73b29a5a76563c100000000000000000000000000000000098da4cd0281a16e2e3e542ebb92269c8208a3d373394b0af92dc8a2676f9f0b6e85fda9161e32558e0569cfc7b1f3df000000000000000000000000000000000b7b54b51821fc037f02167d2e640f8dbfd1472407278b4bdf47b958da39f28c64569c3199846c293bf60e86aa45f205adfe53846c0038203d8b8df0cb636aec7d4ed7f78b0b0c1734be448bace08f340000000000000000000000000000000003058abd4e3d49c86ffac9c95b1f07b66a22c42654dc4a2e3b07b87c22024a8bb0ee084a558ac22cc9fa286861fd77ff000000000000000000000000000000000fc9a89ee26c323df22add487a6bb278ca3f4c9a91eba4e067d5abc9dd3afededb4f98263e10083cc7ea224f28d3bbe100000000000000000000000000000000058eb015f1e14da860215d59165e12feb8d1317f652eeb76b3f08b38ed943c94e632dbf8145233dc93755e44e027553e0000000000000000000000000000000010897d5c2b481f9937d830b333e7649931e801a6bbffb7d9a3ee28ab1e27889691a9f0b9616a8437c3cda942bf07282206e9d4e41b628be51690b86aa8938db066c052f3adff774d35eee1e332312d3f0000000000000000000000000000000013b88963296d8c8197cafe160846ee11365b7a991b35cf5613dc57714aa48307f4dd9c6ff9704b29905c18a41a48010e0000000000000000000000000000000016a97fff65fca5ff282a818deb8100104308b8d9dfacddcae32fc2b6082331b44fa70580018930fe1ab9d9c1b13a59a20000000000000000000000000000000019cd2038acd84c2db1f0fa1b7eccc5f7ae3da803cb72c4a1e8390d49e0adff1d88a85696d9daaebce9c6b8a2f861fb36000000000000000000000000000000001271338587f06847770c72dfb3d9a657d05f8c7a012bec77a7d40a98cb1637ae99281c82668486119608b01feb25e6dab3d349b1546a8c235d60c41408c969a0fd42425f8b5ddc1fa5102d2821bde2c600000000000000000000000000000000173ed7c70f4683102cc6a276d192a8f3b189197d5ea5dc813c7d0162a1649e906f76a1c9a1cb1ace6e4d937934b72338000000000000000000000000000000000936d260b789b1a2a9d04388caab364049395be61d320aef66ce50f052eb462faaa2017731518675bb0e4a2f050e4f7900000000000000000000000000000000070bd1254cf4b209ecb40afe248f2e53c390636625460439952ca2977be021d93fbec264c31ced2a810e8a5e54d750230000000000000000000000000000000016ddc3312f8ed359792bd213d086a0ff1540e3e5a2dedf6c450fb96a9b6d1edff9bde31fbc04de382cf44694a631178229b83950e79750e9827ed92856e4d1e1b5f0b47c6bbf3611a1fef8f2fc47659c000000000000000000000000000000000aa4bc6e1a3e6c3c45a29db74b27af27b61856e2cf385ce0e5094ad53db4d31c4af45b5b234c66a21bf15018c13ece8000000000000000000000000000000000188affc993bf6c99103029c1e406bb1a693e4f1dc650907809ba3de1471d41095dc1866578962c72538ca85d09fcd22d000000000000000000000000000000000e487a7151916694b980e62b64ba49ffc54aaccfa0b0fbc5c14fa4a50d1bfda55698df5cd8570c07030f145c49a4ba9000000000000000000000000000000000084a05dced107d29a0fd4cf817ab67017ca33018d5c7302167d08c64c45c5c455fb5c907f21c39b8a86d037a126df4e76b5ac07fb4a184dfed685b93d2265cebd02a3296a3b0416cc6a115242079752e000000000000000000000000000000000ea7060a07dacd84287007a05b494bf19a03e5a759b0ba67624c54cac3562c0ca3fa6e444206614d00d6d6684b86bcb5000000000000000000000000000000000eb2f332f4481276f931d2192c1a9f6d7585e85f248a8ac95aed398cb61bda05230bf8b9c041c6f78be3b34668a9c1a0000000000000000000000000000000000faa038219f844e379d8cce55cb8f0fe2b55548a0a0e1e37e25ba4f432e6b1a6451b8f081c171490bf055f81cbfe5f8600000000000000000000000000000000037c70d4e8befff257c4bc98a4726a961f3e2e68e7e02f9f2c94aa8f5fc67a1da44d41394dfe376a6c04240e4cd5825f3a7a25ad9f02bf51fd73550ccde12374d9b151f2f6fe535bfaa43efc391f789700000000000000000000000000000000100a24d21c0ddb20d76b6d9fe642da5ac1de28afd642ab5c08574206b8b64d1fd822d295476bbdf2ca7e9267138034dd0000000000000000000000000000000000aa7e4f2f77acfe8b4c8f3fabd56b17415ee9bb182bca1db15c399479ec60382f980067b9d4c4ef7556d621259ae9110000000000000000000000000000000012f7a7f91a988fa661c661013736f0ec92b40f571ac15a47067bb847b09ba128d1dcaf8049b941a51cacece5db4e1eb40000000000000000000000000000000007528b0ea66b6ab8d5d318f5e4d1c0e9a4f504057dbb0397b614a1adb160032127f2ac35a1a98da70f023cd343a35ffd47944c8c814f143f746175ba0b2d75e2ae73730a265d869763f0e986c088bfcd0000000000000000000000000000000015d72b8d4e71cc092c2875de80f3d12e003804d980a4b1dd13cff34e9336397c4533b6ae3a03beb2f09312a605947a270000000000000000000000000000000005976027a98f7b0caf4cc7d0d71440d3e4fffb1ff65fbf32dc890b275b646f2a32600a6215d6b2f999eaec8e58cb6d5c00000000000000000000000000000000111583b7734be53a7d4d090486070cd3d9622156c52871ec79c83ca024880684eada56a36b58cfc3490e65de41e10579000000000000000000000000000000000fb670b553c2ed4c81962b149efd4b0c77edf6ee70eba88300cf264dda98190e550540fb9fb95748599bca3abadd752030f33b187df3516866f259ff959d57fa9c53323d5c851fdabb96e5ea470518ac0000000000000000000000000000000003900e7cc0a8e891dc4dfc45f08d97e73ccbe2021a560a92c493aacd9c0614ad100294b5d7ebd634ffe4e5ea301a26170000000000000000000000000000000011ccc136127189728a7036e85d233fd150d5483963c48074f9d8ff83a0791c950da380e717f2bd0bff8fc115e9e886290000000000000000000000000000000007d3e76bd1f22679d228b4ee50a60cf1bd1fdaa171372cfa34bf4136a091abf7e5ef3c6b3446fd41d5de68b563fc7ff3000000000000000000000000000000001107f636d9187155357bea75c943dafcfba2394a9300054026b46d6f9db31eacc06d1f64c2b139af297dc4783026d98f4da8401050f30459e026a207ca631f0684a10813c64ee86dbdf06b7b29cd9786000000000000000000000000000000000e3a4101f6af3cf0d5d5aa5a0ebc26852dc69f91c06e96c5f1c7f8e4528c3dd92cb6f629620136ec356f0657fd9ebc6a0000000000000000000000000000000008d34dc3e1fa8bc22258e23b504d442a11938370325c101f1cfa52f313724e0894be722646195fd078c1a49720cde8c900000000000000000000000000000000163730996c79787e7ab89030de2c26e26188187762fa128ba4378a398ebd906dc56d99cf228591f394396248665c196600000000000000000000000000000000008f0a8b3d003b6727834228798950fb7a3cb6b931bced4540693445a007b474f7459ede17f87158e932e4c9c094ab904d940555d48649f30026f70450b2caf2b8f7148b28bfd4349458ae89c323512e000000000000000000000000000000000cc2d30f7d3869abfc34719f40b0ddaf00f52bcee7ec09a16de51785d55531fa7fe3ca1544d7103b9caf7105d60d9e930000000000000000000000000000000002ebd8af0bd3f82dc9dca585feaa83071534b2bc2b3d2aadbe0d01d759ade77ecec3b3f7b72f82087365a14dc205add80000000000000000000000000000000011aa3734a4b9168d3c46944cd726bcb203b94b25a97437a6aaace9c84da708bb073ee10585f28bc41e0601567863c193000000000000000000000000000000000ceb4ae5a8b506d31e77e2a43f3af8ba9459b887a927ca5287edbc2ba7c7cbba85a6e4d35c099b7ec7bf7eb2814cc38ae140e30424d2cccc91be1fd3a62d9ee49c9d64fa062d9350b3fa567ec21bb06b,00000000000000000000000000000000192eb406b52075513584ae3c6093fb534270d716c79961d0a3c4bbc44096a2e8d28228363e2c8da54857945f1b983569000000000000000000000000000000000ee0d95748b13b531821ddd71a15fc529a2ce2c99a66f14e28f97478c3c2d524cb7c4cd7e71a1027030765554b8f50f7000000000000000000000000000000000610ab3e064532ce261aa2ba4f78721ac4f78661cc13fa09ccc279267e6f703f1bda17265a5eccb0061ce24d31e000ec000000000000000000000000000000001966a334b16e64e4dbd66119af97bd2b8d6afec0eb1b8207f437c00ab134ff369b3b3c1bf51b871a7fe8ad1ce93dca4e,240480, +0000000000000000000000000000000004c22bd94b82ed3b106532a58a0253daf51f579b9d746c624bbc6b58603942eb139c1b576241ca8fab5bf1c457112bd80000000000000000000000000000000010c6f7551d758d1128add57b110227296e060074e4cb934132368f079a794770ff406fc7717867df0f461f5c9fe56960000000000000000000000000000000000048f88afaf6eee5039b76c0c5b4b49671f6fd04f38bdee1b1c8f347a9dd4e6aef387b742c8f9a8aa387ab4d01fe4267000000000000000000000000000000000e7be987d0411dd7138e47ac00f9f07c4737d93aac501edd16362ea5a633c9071a6bf542d4db540d75edecdedc3a8f0ca57b2c351a7946a20cbae1fd789ecc5f77376b09e911749831e9b5680185b15300000000000000000000000000000000056a29b523b0cf85ab04b0a496e078dba5529cb9699e567ca42f9ee3e3f07b61ae29b0ce17cad23131375f624a366157000000000000000000000000000000000acb91d1f057c7aec1f7561614a95f8db2252cc879bbc2595a5f607d8b0ecd6e6e3ec19849eacfca62d870b049ce84910000000000000000000000000000000010d9459e07178af8e125c2f66de699cfafb5f87a63454e24d0ed88b6c804a9ff204f146ecf4d6db62234ace0a944acb20000000000000000000000000000000007256a68e23b43a3b6475b3cf209ec108bac13631ca448cc860672c65c1760a8299fe941ed5bcbbbcf63a683e86806ae8fbff9f8ac4ad10718d46a857ba28f182263bf2d13c8b6a00902af737dea56160000000000000000000000000000000003e33b840426a6bbe15b23fceba829bda9a5ab89d37e60133874f61bf1b10e05d460bb5d228cb178cfae2a5f41035d32000000000000000000000000000000000a9c5460c6443364d9f9440d101d92a0037343789ca0aab6dffcc2bf81e1aed312299a21556d16e55b1398334d9061f00000000000000000000000000000000015db251708253f7de13a5eeae5aa76fec415ecee1ffd88d882580da5da8d9f96c6ff90d920b329096a103dd71e7cfa580000000000000000000000000000000014c3a004cb6ab8465e05d965dc720b37084d98de424b160062f225dd0b67a8e62ae11a3c7bacaa129a568f3a243357ebb061de16f4f609c6947733b58c6444fa9549721fd9a2459652e8e4b8c69b5d61000000000000000000000000000000000c8fecac8bee21d916cc47b96a66b7a522ef4fea76fcc86ec490ff44b46fc01ac0446e3885e36ae7ab62a409ccffcca60000000000000000000000000000000011676ccef54bb27ab7db0b5ec025a9d1f29217030f3686e71564fa011d9fb598f44a8bed3da8fa7fcd10d01e3f66d86500000000000000000000000000000000093aecb91956215980854c6f19120777983a160e16026560c8076bdc4372f53065f9fee0f5830ea192aa5637590a745100000000000000000000000000000000035d773ef15d8d99b600a6a575eefd661aacb49d6540639223a454594570d0f00ba37340b63a2c8a0d4e53ee7dc2dd91355ed5b57b28451ad98fbacd5ae87551b7304e4ef5cf7b7dc443a66432406f9a0000000000000000000000000000000007b2891e9cea2a464742c7f962deb1566c9d4f9e4e7cbee1912a72c5b064211c39801bf42bd888bc239e6b4ba71d700300000000000000000000000000000000169cf5e706dff2945145d5ac14bd5fc8f7e7c3e5f7ce733c865e1882d236926c71853efbea26e13efe4eb0d0e7ed5db6000000000000000000000000000000000de9ee19c4bc2fac36debd4c91317e54f57e761866b134ba9a0e84a8d268b11674110ee8f91aa8a6b80eabee2e5e75ae0000000000000000000000000000000016d91408a670e4ee43ab8e21cc341596709113950d22bdf5073cd90f520667699e94f64f76290f1bebfecfd80a9e051430b6eeb01874ff4b0fb07dc9f23d8e45455c1480eba7fb3033942214e85a7720000000000000000000000000000000001982744a15e8163a6f2ee681bf27a68996682216037d67d91993fbbe040e16ea21a9cb600fc6a40e7289185393544c3f000000000000000000000000000000001131d7dd5a5b96ac1f4c4aa210afe7af8d371cc16d32289aad38c93afcc1d3be53716f82e9d14ce6b1c833f7f5871ad00000000000000000000000000000000009adedaf19fb8823ec55b803c9509ad98217730bfc6424c8b69a071e99d026492e7c8c4a06509491a3bbe5893988c357000000000000000000000000000000000cc60733a783c7df76541daddef2245e6d2b694b94649b13c21aaffdce124c1cec3fd8ed5a5d4d4eff3115ac933e5df989a697a0e8d2cf512edd2a3c3df354eb30a3eaf697779dd9270234b367c2b5ff000000000000000000000000000000000b366a80247a8e3797f1c711aebd60c99ec7caffda34514a3716154e900f2387c46f87f81af036a383e3f9234bd1b50e0000000000000000000000000000000004608b7cea13d08724a2cac691e61255ea7472537f7ff59894d511af7fd99ad72f0a7406271576300a7d1d56aea17bdb00000000000000000000000000000000141abedc914d3d1ed587162acbfddde60f7dbc1ee5e07fdb5f3515b87d1a29024c9e19f24e4c0e3979bd938aa4e798270000000000000000000000000000000010e72c6c0510495dd2c4ecaf13c1c6404654e1be369d1ca485c76d8c2304d60d69b90c2e171f18bf55668232e747825820b72463d54ac1d8f1b3f56f0f98861768b05d5174cf1883dd8eb0410420d56200000000000000000000000000000000081d5a229481fd297363e8e217bf1f94a00f54eb6e8a3f95f4de30081bb2b9edd82d53cf287e37b459afabcb73fea1d1000000000000000000000000000000000ab55f52ff7dc578ae8267fe3fa09bdb8174dc30bb835cab9851dbee7a1aeba82e83e07d5e79aafb34643d9fc9a0d1c100000000000000000000000000000000195245c7a762776bc1e81d7111e3b814088f1e0e7d686c3ee3e500cd0a7ad4015851563a1b8b592e491e00078187c66e000000000000000000000000000000001850c1e8edb0d6dab973a9975833cffee8b5243654bc4ebe64972e423799283707f9ad343bfa86548cd2acbe04ede5da3de7997113708f9d092836c2b0b59abf710d8401baea6de73ee0689436f035fe00000000000000000000000000000000000007e9191fa9057cd7df8fb83d497ad774735c242bce9bd34cfd21d3f8f2a8e37d1f38b592a61ac8a8d22a4287fc5b0000000000000000000000000000000010e36db1460fa65ea229402f558397c6fc57e9c8a4b0b9e85d9ba938196bfeffc951587353cb7c7d84479f60c087e3660000000000000000000000000000000004d86938bebb850fea82acd336c3900b241757dd937f831dd909ce548325955f103dd57611c0b75bf71412a6ac3d6ed30000000000000000000000000000000013990c82583007b693c1d6271c1e5820d7274c4a729da21a76eccbf7abab1f2bdd6c5d26e78d51476ecf154e4fecd1b87fc3d0560432dbb721f8a0610f0db31dfdfea8cd5ebe8da3fe3b8ac5358dd4400000000000000000000000000000000009104610d5887fb7cf6a866584cae30cfeb00e1241083b017ccb82ddc9d72fdc0d2b1d227c22ff6d8497495f44828efc0000000000000000000000000000000002235f959b071f21fd63282fdbb46b1dec27cc193f3e9988def691c73dddd789b6a1adb977a68e2661fb41d62280f229000000000000000000000000000000000ccd46984208f183f0b70c9152c01fdb8ac078ad1d85f41e3a24819da321d9dd9321a8d70103282abe6d8b981447f202000000000000000000000000000000001711057042a54ca76b0c3e7f36f2fd49e339b76cbd2e053d93ec2838848d359865fdbbeb9e75e408b4b316d60ce2741ef0b271f02031a126f8632e30d8b17cc5b57de7b8b873e0971ff392d4246a40f400000000000000000000000000000000001481684941fea0f66c78faa40aeb4b5254bf78c44df7e37b191c095ff12fc94248acf01d2aac5637e9536e73a82c9f0000000000000000000000000000000016b72eff2830f49b24b1e1317c95143cda8bc11b9dc4a91ff22a24e0bc1a244c7215ab1040fcfbc292ab236ac73cbd3d0000000000000000000000000000000013535421771fdad616171f7348cdf32bea7486bf4d836b8b95c69b71ea9915c099e256287aa119af53cf6320ad86664f0000000000000000000000000000000019ba0f36dc556fcf09f0a4a6cee53de485d03d846af7afb792d16220551fb5a42a4261f936b008babc096e6f8f68b63af8b5c136aa5e2d670edcfb5bee9ff6095d85a332ad55763fe1e5e8babd145c070000000000000000000000000000000014b2da0add872d6e61253d6022559f668bf192b4aafe0acfbbf341ada55b404d42b2b31182c1ad50c73673494ea5b7d40000000000000000000000000000000018b76b74e9e6cda8466a354ff66baeb935b5645cf9eca81f4b7342f7914c9bf35c57be402458c09781e66a89cba6e67e0000000000000000000000000000000019bc8c1f32ce934b7ccae6d8ca39a263939585d8f94414c3880fc7bb5a0a27d728708e7ebc42c5a935f769adcfc083f6000000000000000000000000000000001636b62bbbe34bec06253887b78ad5b3ccda1bc5d8baafe450f2d1a8e07334ca79a40c5c4a50b58aaed96408749e6f68285193e7c10646a4601787edfad3d76e19d5b013a0a954873d92bd5293d325820000000000000000000000000000000013c0fd7a8441b6eb2dabfe8c152aa480015f81139c46440741f3da1c50d18c17526c47e8b8c2fbcfaefabbad5f8a0b000000000000000000000000000000000009da839802e7c6759a87eeae5a05146e1d226dd828d4ef6d908b4a0431008f352539f3abcd3e4c532a3d8204e350a8510000000000000000000000000000000014709634973e4554d2379e439d099e9be8bc7ef031b6ea36a7a85d2ff5090b0e0de7cc1c6b6a004465edcf868ef5fd5b00000000000000000000000000000000146779393d82bde1eaa6205e69907a0536c782fa7fc6e11e5e62ad5468f4422b3688f2ff4da2af396741ca5e0f97de3835bb2175fff61894ccbb69d90375df627e925f1ac430a349e75580dd39546e44000000000000000000000000000000000ddb7d0380370830803a7eda2e9b694af71381990f182b5d1223992abb5afe9531bbef8b9dba239f411fc422210fdc930000000000000000000000000000000018b685009d012d72193043d09f8968f9a41ce2fed598a20536fe54cb26db1733214add38f73148e754e632f6d78f524d000000000000000000000000000000000b967a7b4ed1bcd9f3da16584b08e0c28d967cebe7a07069abfb3bbce94d26b6d95d8a807879b24fb1f5ea00091d6dc300000000000000000000000000000000039349785fdb7d38707d8136e9a8f650c4491c50d7425388b75fe30da56147992c3d662f22131ba7173b2550e613477fa25856e5fb9547c48d41783bf2cd13493a1fd71e56b9c7e62af84a1f6cdae1c8000000000000000000000000000000000455d7799cc1c2af1e219b23e8683113fec126bad1dd7a441c5d113b064b552ccb1e7314dfed1b11f42a18acace706e50000000000000000000000000000000014d2400aa3e2270714b656bd755c4bba55866d6e313f619e10f94de6d82b5343ae9a9483dc10c1a72a5a21e619a20a8b000000000000000000000000000000000a6caa6cf8609d23b7873c908e5321d064a9c107b5492d296d04f92c308ee705229dfecb1f908bca0024ca56bc125126000000000000000000000000000000000b31c384423c84316f65e03ba9e01a8f626236f76e4df4b8ce2fa053c1c1e6a9b8f0afbc253db8c9c5e2ce9f9dcf05c71155c0b9c4185025310e8020eb52abb6f2f1780da15e4ba81f3c9a88ed1b4a6400000000000000000000000000000000097938bb53db8d0aeca3f2bc180039a5dc5269748e9cf065cd88e59b30733d527e54cdfa224e9690581e8c7f0881241b0000000000000000000000000000000002d52d97d4dd415fb18348f4de78c65e2933fc45d5e5e1d8f0f0ca1cd52885704ab12609b91d6d2d1ce13eecc7fa0c2d0000000000000000000000000000000018b926a37a8e0ad836846d06c03a9b84db795fdfe5f15d1fd3e0f8fef1b2825b29ee3a503ffb2f75765cca49c2b3d4cd00000000000000000000000000000000073bac093e958a3a09543e060c81b35b6598521a8685629f77200cdc73b372588e66c247097e7c03492c0943bfac4d6bc5610b2707ce84ce67e82d5c0e5f5cd2c90925aefc1e39468ca86475012df045,000000000000000000000000000000000f79110c74f0e983f3d3618869af1d9b96dadba61f1d596294ef8a9412f946fa26cf63483528a57299dae48f85ada81e000000000000000000000000000000000e1a9cea3af1debcf7d6ef6f7b8566b5bb52d5548d4caf85925109228d7c9b50d65a1b24f089631e75a694f8e8dcaf040000000000000000000000000000000010efc1081f079e841eaa5a65cd7c945d4f37acc92c4ace9ae6c69a9a95d8cf569d604376b1c7e63558d022da90d269fd0000000000000000000000000000000010b7f55ffac8d57c89b664c36c20b2988a493de32f5a956c91b16ff67cb806298a59adcde12ead42d598b6ca3e1b94da,240480, +0000000000000000000000000000000017b139e5dddd53433362c49403838b3e2ecdd850a8df12d4dfacc0bb98f79d40966d62dfd0da1e721e7c0f298457d590000000000000000000000000000000000fa35e9c2e37bee1020ed99516174408ba2cf443fed115fe3a964ed86b5e5369e40291dbfbab477e339003ac85eb7405000000000000000000000000000000000e8fb87794860237066ed1b7ae7c2a783c48c52c2267f3e7295d1f17598b96232954e1eb6d6e80e716628f1db8afe48600000000000000000000000000000000083521e3a6d6e3f99570b747498520db5c89092b0077519c8421f9f41772c7a6e177c9cdca52f89a26c6036cadfafa8b32fac970e52778cc90396a5ba92ab98e26499eb1ff17d4bc4c1f78b64887d3f1000000000000000000000000000000000b1415e1dc2d4c1f5619b40e616d258867493d8624857e41d007f82ba8dc53f7ebb36d06f8348b94eedb794899e97df80000000000000000000000000000000001c01656fa47d62b4372361b80ea61501cfda47da5534e3e2aaa27b1e3c4de0bee0aa322e60c476fd4345340e5c00e130000000000000000000000000000000010caa407d9d265721d55f01dcfca52bde851ebd918e8fc4c752a41875940709c64599f36fae5e3ac7f211e1f67890d1c000000000000000000000000000000000b54a86474dd5f410290e4b4ac738fbba5e88c6debec17e38a52090b17ef371dc8feb0573e76c4b61d7688547a89f6a36583bac9672a77f2fe62bea4364aacf62d5e10eb3a757fa0595a81f76543e8630000000000000000000000000000000001649c78147fefa91100738e50034424244d22d8e1bb6a2bf471e4c9b29694a5c9476f4b129912bb09fece53aa87deeb00000000000000000000000000000000117a3e040c1f54b96c2435891a45fb9dd95774b5a55cfb306c22517e4ea72172332d893047f7eaa665fcc58dd21781f400000000000000000000000000000000105e8d80d46e6bab2bb9ce0525cbfc82e8b3320ee4a8b9c0086e21cf2b5895cb35abffedd1b5a9eef21f62a0a1dc48e8000000000000000000000000000000001437ee33abadc8ef6bfeca16c3edcf05480c3dd97db06e396e10d5180472f50074f43f9a031a04dcd11d803462fefadc5a8e1d77c9e42a187054c938a8a5b4bafa834021b727036ed3941b1c1deb9d030000000000000000000000000000000003b51b10efb54dbc2973e001f0bb634e36f689264484eb128de2882d6600a43ad548bc7d1def4541f0ed88a1fb37f3270000000000000000000000000000000009dd80dbfe6663ab04656856f192002593df9ef7f792dfa81f6a51c658c4c9ce5586a5edaffefd507f51ccb7e8c8101500000000000000000000000000000000144160d5ca6b2ad626e6a3424ff5139adadd3319940afa9bff7dc409ac1fc3775d5413ef4612b27fd22c02c1fe57bb86000000000000000000000000000000000e375ff490a626dd1d933a5c751c88cbd61803986fa8dc089ccbdeaa0a922758afbcdc30d29268fe0a34b7b79d0f76c139c02150e4e89b25563985c7802c0c43d00c721d521b54e767c1f509f584bf2b000000000000000000000000000000000997ade20fe9c0d3eb79e61a66a5c272d02af668b0f3c8201a1ea071737f3d2ee3b0764f859480e95be75ab8845b407f0000000000000000000000000000000003215194b6a363d31ece09b18700479e6093fa3472a23ef0133e3dce60a3d56b6fa984b900162c4ad56a6899aacf35c3000000000000000000000000000000001647647bbc399f40124c43510469cf613732d0919e22b478b2603d7553927584cd4b3a407e3ec6387c4a93e9e5373178000000000000000000000000000000000bacc8afdd70e927e21521b3f62264ad4f22adbc872439ff851d3d169a1c79a0d02bca2aabaa0b9941ab1c71d092fac12196ec0e9d2f572856217521fcc5e2869f16d5ec5fe76f7d350698f55ff0c565000000000000000000000000000000000b0c5981bf6ef5b85bbc504fb0196ba442fe87302346688165aa7df8cf2642548760e11daf5b3fe2e37b43379afbfd4a000000000000000000000000000000001086828b9560aaee5e28bcb50db8153c40e632b18c61ed4105bb7f472b6a69ddd8a2836f6605102931ee66b2f07e441f000000000000000000000000000000000f4d7aa3d1a281af6f8afb3d886774f4e4a64490232f63dbe16e3b8c4f626e9d07f7c668d09cadab3c92d6fe852427af000000000000000000000000000000000d92ea3318779b532cd81c9be44b1abb179a8411319a6f8fbd7e3f158bc970917d3e0b25f3f3f6c8e0764011f9bab0398df5017c9c35604f061a7095d976d08bb3570ef8fb518cb606cd39a3060157ab0000000000000000000000000000000000dbd83910f304d0fb2b6d8619c3a308c719f6454a357d9ced03b2882a50692c06cda7f4331f54eb293ed5aa079121fb00000000000000000000000000000000019c33ec829367dfd2610ccef9842ffaa5e4f35809657c22134fb09b024e07949d8370ba8ba1e9149060e9bd3babc19c000000000000000000000000000000000ac468b42925d2daacb8574d40064d393caa643f08767d20e72ac0fad1447a64d8743523312f3a91a118d3e51e1f52d7000000000000000000000000000000000202d1971fef2938cfd10bef5900b91cc4811939f66f1f5578a8ae0eacb2538d2a51c1e025449e1637b5173ab7fa3b6f7b82e7e565f8a521d1a9d0ecafc029f76b70042e1ec36c20e3789b49c7e50ef00000000000000000000000000000000008b6709123b9bd501360fa463dd08076c59177dc0e8035c49fa2f541eef3831e4c584c5a9410c68999dddda6c86fd9d5000000000000000000000000000000000fb94eb34355c636dca909cfa71f52471217b9bc241cd3e98907d4a5c7eb67d5bc9cdb0c73c1369d7950a014fe6069fc0000000000000000000000000000000002e2ee515a5dc96a664bb1f862f21a8d3b7f903fb87f6dac41c3541f3d83633f351ba8dc4661607d24b912dd1ab097da0000000000000000000000000000000008bee545e00e3fc283185a85511e09fd0253e191f52d5c0b440b10228041800c013db3c9322a835e4927c0ae0b21bc1e8260c1b7a249ba215f0dc127a41876f858b20f4422140bb7695c8f98e4c474d00000000000000000000000000000000006ba635e74538748c29aa7c5690a0530f2b1970554598a432d4ea6d2713a4d26786b6e80f67b2f39e218b19323654ea200000000000000000000000000000000133ca9e5e0d4a8200d3522d8e87dec3c72edc1cf16b7305af4abd466aa7a0e30159388d34c36ea030450ef45b7940ec20000000000000000000000000000000004724239afc773688ea92296bae8845f20793c05807a18d6f35f03bef295da06f8ac9dff438b720dbea7ea93f3ea9c4500000000000000000000000000000000149c12922fd69e1960274a8b91384e929fb354936c020911495e6e3c49faf16899ec0c6e87713ee2f0149bf808ac8abfcd68d2b074d038ee0d9887168dc16805ed55df26329a4c0e062c2124a6e5066700000000000000000000000000000000148a4fe6ca67b6c785d5d8a784d5e68fcd2bd08294ca37f296b6426433b805507b554eb9f0fadfa9d293e8cdb8547d4c0000000000000000000000000000000003700600c2b7bfea54801ac95ff7a2c069bace31ceadab2947a0641462089fb43f0b9697acc005a23007a923ffe97360000000000000000000000000000000001705a769ce3c9a7a91283e4068c602d85808980d6fb457345a5f9b2499ff8fb3ec8383049b9b7cae96bd2ac6106a07fd00000000000000000000000000000000052b1f4e8a48a5eb2b2580614c656393819b4f0ffea874be899e4964c7e32d54757f2d48ca7b50e47e8bf6d6ab8ee7572a40c2e796148ed1c539b0584b90cb386844fdcde5d3766cbfb1d1b58626fcd10000000000000000000000000000000012ff8ba50d587765e68f95d276e364c8c40c00b55abc929f9ec240985269eb096dd3cef5826cf6269ecf54bc67773510000000000000000000000000000000000959492d74cb34c8c9ca4a21ddee97df99c8a6e627db3ef72200f39e0402d56f0a9709596189c80aa3aa50793e0f1a68000000000000000000000000000000000f7e5dbe884597054d6dc5e80bf4d0d333025bddebc1fdb1d61482cf15bcb4c8a95ea29cdd0925b5b816cc0bb307387200000000000000000000000000000000194e940c041d71f43ffaa51fbb31eb63c23559069b42dbf8777f35eddf14edbc3f7762c7b354174a584507ad714948234a1e176fb26983e549aefff9aeb220f50e071222073422dc2c44abd85528ee2800000000000000000000000000000000101a8e54d1fc2357df60b0ef8872b729295218f29ff63f7a7b6a70b3ecdbfc6809eaa8dc1f62a664b9987e8e86154c6c0000000000000000000000000000000015b5ddd012b42e1a600d738e05b551d91e7fcf3cb36018ceda9b689b92022224990c11a6fa0b421d5610b7e59b7463c30000000000000000000000000000000016130be17fceab55387d43179cd943c85ce1ff1881c07c937b2cc0645ec9ebaf0e10718ec7fe0d720f49bed2b8caf15b0000000000000000000000000000000017d73650680856bc11619e6acc139e137f0a06476f5f8979b5ba7fb8123d85916915da60d1f2e8c84197eef518b350c2a62e07bb97ca3805ba2d30f39f44e70a7b2917889c26b84bac8f9739bdf764090000000000000000000000000000000007d26bf37a97d532ec93a3eac00d9d39b064ecd172ebd5e18228b1601eb7a2c272aff9d88d63781b4a587c2c8582eec4000000000000000000000000000000000108000e850bfbfb02d7acef97592e15ca721334eb51197511b0eb2bd3bb647fc8f07713487b0a0bedbafb106992de4b000000000000000000000000000000001868c0b2ba732731f7536851f8005e8bae7b16545b39190251eb2bf93dedbf0803a42ec24cebd151998b690c38c0346c0000000000000000000000000000000016faafe909a1f926333b12f5463231a71058aec31d73893687d3169c4c3588436f6178447eed307b642490199c507d63a14278fe7a08174660c08323de272b2110047a1d1d8bd0e3c7d76dde030e00a6000000000000000000000000000000000331338cbaeb8e304fbb9257bb80aff5d3e043d07dbc476dec2795347e4c25248caad06ad14f56183d2b6276c49ff98700000000000000000000000000000000167e9578304a1162de73914b02791468e14faa2e0f161aa57818b8a169b5933dfcab787ec0f4b23737011163dcaa02750000000000000000000000000000000010aadfd5cc781e73c31f2fb64e7981b2e28614aa18dc7b2d96d2bb4ed8c2ee9089d6ebe0cf85479b272cb049e934739900000000000000000000000000000000128d7ea54f338064cd2f041f42a1a1e77d8b9be4ee55f568786a36f87f965d8142207e518798061eb3e32fe3b0f1541d1f516ab5b36a59e6300a54d17363ffebba35fa0c64cadb21e541af5078545b400000000000000000000000000000000004539f22654b3182d4fda5ab8d4bce6f1268d4e402b6c29a4cdff3b5abe0618d33db55ccd1ff12b27b2cb0196ac53e0600000000000000000000000000000000177e80ab6aa8512cc9e4d65b06b2bd76e33bef9038cdc1ab97fbb9d896ae2ad884ea16407490653dbe972b14e9c30c0b000000000000000000000000000000000c280a4431e41df6515979a694ce292f220278178f7f36e23c8a4cb2b8a7ebc520901ebe34c72a26b2c8a60aa1a155100000000000000000000000000000000006a0b80538a6c8093f3655905af1c59c235567d22192758c28dad1b715045189a412e4c1edc26e1d8ac95a584277709b3bcdb23f9568e409271b5f907fd64b0cd81939a52a6db38fd8d95de76213f7b5000000000000000000000000000000000eb091007672a212dc4937b314576963d7561657cf1103820ce9bc34e4d46c24f4891a4a4ada648f8cdd2c30f670b86200000000000000000000000000000000166389a37e6e3c02317d68d54f29cc98d1d1df5853940555161d71df791cd92c483eaad87dc0e765b12408d6ac344f31000000000000000000000000000000000affd0d5734cbc27b192c0c0e464db48d3d76799d2c6a493b172127ef2df6ea18a33898828effeeaceb7a203e35ca41800000000000000000000000000000000155708b9756752c9b44048c91d71970fd2cf2a4cae6b0baec00629c81387c8261150e78f856093d81e816be6403f1ee91b716b02b3e94600867e019be166f4532d264e0aa65d723dc0e117aded59245d,0000000000000000000000000000000007ceeb14945414d96088a7900c1120ff182b2a93b09943c2fd1dc2b0b223f684b0d4c0b1b5803502582f2daf16d81d2d0000000000000000000000000000000008df450fb25534fdc456a8f41cc143a84729ccb082aaa2243c8f37e34a6670f5195750f8547444c49f7a898aa8567d980000000000000000000000000000000008c12d360078d5645b0e095c90d4fd37eb20f0ebbc6fa93fa5beda7e7c78eecc06e0d839268e2c303422ab1769402e0b0000000000000000000000000000000002bd594a21153d7c458b9f804050d05caf2d90bbf9d18def79eb8148b7f89e3a3ac21f84b87fd13c39df5b91cf73460d,240480, +0000000000000000000000000000000003e06e2dcfbd695e9bda0baee1276ceab637fd1fbe2d2d6458c923c35b00edc7edf4f9e797aea59ff8cfceada0615a02000000000000000000000000000000000a04a2ed5e42fac7f064b43d64151a6c517ecf22dbc7563a3e9f35f555a9992fe45cf6a728ba94607df7c96f7e0a334b00000000000000000000000000000000090fac97f9f524168bc930d26ea1627ceaf187398d6bfc5a019c8467d75cd31a41c7eb9fda35fc85bd92b4cfca92dbff000000000000000000000000000000000f37b91dc935c28668c27d38328a511148c1739b65f2816dc53e42a8f059c9b2be7417a6f97c9a2597b1a0f06b7afc65bcfdf0495e49dbb8a8f9a0dc517351f39a6d823dcd42715f329dc78400bd74fc00000000000000000000000000000000090b834a587521729426d5b134c6058bf7999f4d4bcc0812e8d8b3ebb050961321b5e93356e87171a6f12160749394ee000000000000000000000000000000000cd5148c7eeac4aaea4288b38a02b5a901a6e2805e2b1695ed98ed86cfa0d259d87b65bf3cc9d00b8548100a60a371d200000000000000000000000000000000026db1079b85411dea0b9fca383956af50b938a465f35347605c01f3b72b297630ee2fb5252da20ee0d8ba5071974ed70000000000000000000000000000000012ae26c193e02d7ae4a7a01181551085dec9fbcac811c45d5cef19abf736ca2514e1259811970af5913891abe22a75ecf095238bcee61ec1317c0f98ad4f8f9b39c5940cf37a8a3a676787d9dda99438000000000000000000000000000000000ed5d8a609aa4f3c65a89b8dbc9334bd3cec6c7763bff298acd6c260e4d3bec0088e15c5d82618571d13b74a2031eff1000000000000000000000000000000000c28f92f018e6f822912b6eccfab37432ab0ab9acab751f848401791bd2f16e32ac6d97948bd8a0bed2ddc1917f0db3b0000000000000000000000000000000014083be2539d914883172cdc70950512dfe7be8893b1ecf085d837c2e9ba7f03656c5a0e15373e04d300869620eb66d00000000000000000000000000000000002561b77cc2658c54d29f8d1988dd7448f59c80c02ee9256404d8ef5536ee50104cbc11b6ee1ab9ccbf0ca55e53c52aae45a6d64cac817cd479a501c77b6720c6777c6026dbee471b490fee9f242a6700000000000000000000000000000000000fff05aea33a9d1e8f7b227c80ae87c9e7589ba2804904b7d8386b24b0e5324e718f29531251969a972870a30c310630000000000000000000000000000000016ecb8f27a369df13e122c981e7ae37882b36d5492fccbc86d606aa1198f3e4ee7bb7ad0555e11949e6c1783d8f4cda100000000000000000000000000000000187f425b675cb12719a01ee3b78ea73d88f70805f72d6cabef6372ccb9d99008bdd7da54f155454c4c59f041deec86f800000000000000000000000000000000151c272d5cb67b3f801e103ee901deb4b3d3bef76ee4e1b2ce1b5e663ed292845ba012c732d38f9209f82e77f1f73cf354868215022673de608cb43a3cb74ef2073ffff34c54fbb43f19b22a02bcc2ad000000000000000000000000000000001791bd59815309f2aeb7b07df8afd89a288eb6f19c7e613f394353ac5398267e1388c97b17d83104446e57f94581a79c00000000000000000000000000000000154cc72ada5a9c99dea06ebec143a14271cf332b57c631725ab30e2d308d6b688ca08a79efb6fce632cb1216ac3d077e0000000000000000000000000000000012b4c6fe8c17274ef57539563a736c2f83c4cb473e9d075a976e18e193255057340f45de373c7d6e3fe5e08ad0dd97d20000000000000000000000000000000005aef16e11bd4e7787bd5ab4427276ecdf9c6c134b9fdb2ec39e87ae4a5b3b674b5ceee29bcdf804ebd7e83960d8d7ef7068c3ba82e52fce0223a9f28c1d42681c7863c94797d1786c1adbc3e6d10dbb0000000000000000000000000000000008e57f905fa202c7640500746b590791cf9d0f160a77e5eaa5a30280e513e8e801c4b6b04cc3f80d9403388571d180ba000000000000000000000000000000000da3c128ae234bc27824062832ac10aa9cd4978f37855a8b4cde3822f5b485fddb9a475a9805e795519d7f138a8199cf0000000000000000000000000000000000ec11b7e07710161fc557a56e04337f71aaa1a0f070cd84525965e53a1fe445c91ac07c618ec349997890ae893c165d000000000000000000000000000000000406b0eafbb8782d11f5dae2f6214282252af9ae9ebc5c17a81d4ddded40f05d0b534d14019bcb6cf4e49c4c182b90f00042b8005283c7b91ef4b3ff7e20a91349c8c3d1301c9b54b901e8348a7d186e000000000000000000000000000000000b1d456e66671dfa72ef3a56523eb939146226111fdbbeb697983928aebd5f50b0518db841a3d48912a7a780785c1f180000000000000000000000000000000007a15b2253496b78d270dd55b80bff90583a95283a89d40f6df71fadce56d103f0d365fe79256fa4f93b2d2bf4c06a2e0000000000000000000000000000000010829223166d38fd2c3041dd5643c9784da366a2ea8cbb3abdffb5fe43e975318c86de0ac9ec77c0126ee75bd209f7300000000000000000000000000000000004b124018e83e1e5e77bad42eb831798d450f8ff4a79c9b14f67f080047c491fbba45db79b2cf6015188f9fa6329e8be0a3eb64ce8fe140d94956b0685f91a5462dba1a90093e803dc617559a66d20da0000000000000000000000000000000011119be42b90c7857079a51695dd5be08e59374b0d1c7e12d0ffe870202e1f0c62bff84c9691679a82e610e788b7b5e1000000000000000000000000000000000c7a64524c5dd1bf10d16da7f15b39d05c9ee1620d4dcae79c60316a1f522b238e7934d1be897a441d0c8e621b67d44c0000000000000000000000000000000013045613a090d05d07310865d977c8e0bb1caa713b2249d6676e7cfd6f4e3ba8e667deabf9fdf7fd527685f7d251b178000000000000000000000000000000000dfee7f8259701b5726b6439a7ce77b92245499906502c7dfb384e29cafea61f3b1f21fcd7888231569ebf29d3035a61ec88ed0eac8d0f2f618530e91cdb9ea36b8d56c1001a6792a09e11ff65fc02aa0000000000000000000000000000000006d77669207bb2d064824cb56fc786c631936d30db630be3c08e18d7e95b1c26e2d4e7b2eddc2f946fba6e99acb2198a00000000000000000000000000000000168bd8f291f8bcdf8b5e9fa915f7f24856a62803bbbeb9bc38384149008d4e3129338035061631f1fbaceeccfaeef4a700000000000000000000000000000000146bf2dedc262557dec2b4545c94a37434e20e4900b1693e8fa9bda9a94dbd07e0a3bee5f3bedfa42148791f4951db7500000000000000000000000000000000138467700fd5088c76af2f77fea4b746f98701fc0578571997b0ac2fc343354ddc8b2dc57d5298dd4daf767573d8bd3d5f03e53ff983fe4886a3dfc03a353fb77927d7a0d1998a1c55ca7421a4bdac6f000000000000000000000000000000001536da0df7c91687339fc93608eb404c5f46adf4b9122b99b1e5cee0012e27ddf30934d8f669bd39091f8673aa3b3c490000000000000000000000000000000002deaa8f9349e7c551e39751b1454a00f8f7896d63110e8e42607e8023ae3070c4abc9885ed54ee37a82f6e5c68451e900000000000000000000000000000000079a62eb17f7b07d4117956d3dab5d16a7f90e98948d5c3caa124fcf755c73f060a90d002cf880f5246a87342717b4dd0000000000000000000000000000000001246f0f3ec2af7c0250ae14cc67b5a1d42309f06c6f47b89178ff7534c47e8413a26a43f27454c0f946c66634563d41cc1b04dc356bd348211ccc4c50d12cb382660a4f9526539c2a0c52b021ed216500000000000000000000000000000000046e4a08785de985c66c7417f9262d363b9acee07e250999a4a7124f101ec4d82e3e4b2b0d9736471329fd61d0cff13b0000000000000000000000000000000017bf1e20ac181780ced62a18c78b378fc0dad157cf30d6026680560b681f5755183bd30b4e454764c08edb93297590b5000000000000000000000000000000000a57cbe93254bb0796eafc0a57330e38bfca37f8b94c4d21ba656e5616239e1e18ba6d632c0129d30291736fe37a4ac90000000000000000000000000000000007f31df7dbe9abe15f4024d8f6bed93c92ff5bfbd7835e08e870eb1bc4a6f62b3809b922c6d5a7350e2e5a978c80a67397b584ee05c27d45390aba36772ed49d571837567e95f1fd3ba3fc1ba5916727000000000000000000000000000000001577abdf6e915c9c3b3fa50a4601709cd629397f2f91784528e4cdbb140065fc2a6ee3830983dcfd49a928e78cf530aa000000000000000000000000000000000d6f98df9e41009837cbb05bc3e3340d38e56a448fe396bd48acf03f061e7489d1402b36a84b3c56eb859437e9c406f1000000000000000000000000000000001912afae5361c3d8c6141755deeef26d1fadf6b0036b9d05b2e0c4d50f42328741f0423ac772fc66dbc922bd4a837ac40000000000000000000000000000000000616661f049b5c784ba05334b2931509e1e033bd203fe17f04cfe12e80e73eb7075beac9d379fc1c457bea1b6adf365752542cd551cafc5d50852526ba0a23d274317e1e4a6e75c0d19319e5853b8b6000000000000000000000000000000000f98fed7e4d67a513c746d2fb188597a605165d5d299072aad6d621e077845f93804d575a5796bfa726f529dbd90e014000000000000000000000000000000000adb2d0b6c02e4e8fcab11c7c8819e87f73aab673ff9dbc5c50fee751bc7a6a8d386c8f9fa830b5545f94a73ce6e1f1f000000000000000000000000000000000f08e05ac40655cf59ee3ea9f10fc900315c6f06ffd3b80853560559f580ecdd65aba5ba660c729e0bb9576eee3703710000000000000000000000000000000009da46469f4b8fcd8d2b016e96f6e6582fb01c75407c36c7f87b4a1cd8f08ad06e962a0ec2138ed6fabaa1cb0115f97e2f76a0fa585828f79553fbf3baac6a2776b782de66dedd6b734f9342e734ee3000000000000000000000000000000000047b45ad2ad4f7b5b72194f98b98b2150b5d73a9df2aeb2377beed9a1275a882fa2d849037ddb56af632489f892a48a7000000000000000000000000000000000e1b0d9b52c0c5324067857ba4701f5f20eec165be418871fc0f0adbc3a0bbdce5a33277a33b79013109b81e006c621400000000000000000000000000000000179c471e01e340d8e6fc0f737ec09f0180bd2dd2a86d0817f753d1e9a9f8cb18178e9de68c596dc6a824e6c3c151d8b80000000000000000000000000000000019405c1e571a9b200ff2949aa74647dae59d92a8669d4876ba23f1b4a12a1f9412412503c68acbd619cae3ff056bd346f638e6a70917c89811851109296a7225f9c7c5b3d7fe6d6ba6c7d1ee77db4458000000000000000000000000000000000ca8566b9bd088c471fd33fb7b1bf760ee12cc8b0cfa9ad92b45012cafef5c0772d9bd3bd9b266d6c3e3890c8f00057300000000000000000000000000000000055789839e786ecee7fb7d10f3876359fcc1bd6f2c5cf25c8337aff7fdeec9b43ffbe932cc4936bb708571a59e4339990000000000000000000000000000000013cf827bd57d8179d105f34c147665a072714ccbc114aa4e878d04ce66ca78bdabdc4867b3968c75dead147257197c6a0000000000000000000000000000000014a8dc5ac1858442ca627eaa194e1ba64091b5f9ace551338d770c92fb49ee12449dc200c8c35d70f9e0652b4d9b90da1c4ac944341dc68fee586d221db2a8167e833f18f012afa7c3844def6dfb26bc000000000000000000000000000000001124ea2b97a6d73c81387a51e814b9bdc951a773db2a32d50691be60f1d397cd4aadd9b06e4f49c32b12254e9f824fe80000000000000000000000000000000014cb365e9780feeeff3548f34a56548302ae0dc73402c40317fc819969ee9c4ea2a181381b94f82dd97a236671b456a000000000000000000000000000000000064b769c4b785d45472038aeeebd3ba9b28b3132d72023640ab2d7512cc6e31296c5330be5653ad6902e4e15e57e2c3e0000000000000000000000000000000014c7bfb1f142d69c17f73e23011aee0063a97a99d982d25ff72791a65c7a68941a80fc216cea8a49f3df2d0748b1f95db0eedaee9347b10ab7b346fbc16c10cc9db486f561f88b756c269ebbba23a7f4,000000000000000000000000000000000fb1227806c750e0eec0b865daaaf51afb74a88589d1c035c60dc1913f05c8ab18de24903ea876fda27b97a5eaa2fd7c0000000000000000000000000000000019903e1341f0285658164f9273b5c958060bf836264502b9dc298f75d4104d7a43b8d5dc0bb934a506ce1273ba839d830000000000000000000000000000000006e791347b54057195189e8b9f10fd42d170f37d455c0af5e92cc6a12e2c23990253be6855f4be6c84a708852c19a6f90000000000000000000000000000000005b72c361dca430fb2414b9d5a326cef8b77cfe5310153d6994dc1f8b9e74e8fbb43079e21956f428ed8aa48d6897e32,240480, +00000000000000000000000000000000052acff88605f33a0cf1201e8101c95ca0befd443c087265821a7d954917a026d41ab24d29bdfd972bb52ff4ad6de14c000000000000000000000000000000000e134b2aac3f6270e43afd994302426796b1e362031638fe0263c0ec212b828a30d8321af34ef7bf260652150cf2293b000000000000000000000000000000000d6628f675008099e9a75e1294803e86417ab22670d36316798680289ae61a26821693f2f9efc8468721a1097c3bceb20000000000000000000000000000000006d66ffad1a2e0f39488fd3f6e0214c9407528c8bfb8d1ebe6d75596a3e3cc844d00fdf46ce7ff6cd6d67732874a24a484adc8cfd2e42abc2f0e0d7e9c4b378f73731905760bfeeef01c94f8d5c3cacd000000000000000000000000000000001160bf0f7f2915cfc64e12a5a91b7e2aac78d4c2ce362e7677dd0e9c0172b37fd1b52222a13c65819b87593ee32a9ba6000000000000000000000000000000000c8be2cbbd302b31b1ab6dcbeb57b4ad428447bca9159fdfd007f5375218d121673a010f2c7fdf83fb45883458fb068e000000000000000000000000000000000363d3d492e6e6901756bac13b5c32d55aabbedde878115aa41b57d27b49a0f017a61fc90b13a20e009989374b82f5dd0000000000000000000000000000000009302fc26e6d750ff9441d7471903cc296b320128de71f86c4eacc80ce0725e8eea6acd2af056abde2f61e0a349f9bb5bbd5d4a15998d733326ce23cced86ec5d5b410c29ee98a4de19f2662c3933dd1000000000000000000000000000000000b12aca17efc103cad500b3d773e43cb24df6be651963c0f30bca489f1dd7911ffc7204fcaa4511e161c6f75da4a5ff600000000000000000000000000000000179a36e9292d3f78a5fecbec1175f001bd4ac0ff3610f596aacdba29a12ea4844885a7c465e66d3883c7fc99d4a7e06a0000000000000000000000000000000016bfd0758b31f54f90eb8562bb693c45a92a297a3d302279c9e3cb8263efc0f31579a3af8e8f5a091d9a6a36776f445d00000000000000000000000000000000020f6c66fa554a5cb610ab05d194e7583e6798a113b8fff336c986f7358bb9fa6a7aab0b04be9b5c44a6fcfdd21999e83717aadf16301a9c8741d65c86ad7f849101e30b7b1a344643b100a8582a6ad10000000000000000000000000000000004bf40c1d2d3574ad7fe128ee376364591b6f647f939b0b556ac3fdb5a92873f17c007e926b8a39a97c814728f355bfa000000000000000000000000000000000b8669e10e0a538a421b717287455620b82574b96ed09f64db444ec73a67a3227503e1b4fd6869314214071399eeae0b0000000000000000000000000000000006ddea4adb703d7205b6d2af436b41b4bde3a8c5dbed9dd161c9b3b466ebf06beced64fca25c3bbb97f232315daa5565000000000000000000000000000000000d97248a25ddf0ebd0200c6abbcac9ecd9775cfc5ec8da91634e77488bb592e5ff277a9607fe721990f403dd73f746e622788b3597da7b9b106203dd0ea97527aa8f5149754bbb0c10bb6eca8a46d94000000000000000000000000000000000135bc4f28663a6d7d995f6b876ffb8e6ef1d2d0f232388aa5f390c57e8c48cb84d370ebc4bc267eae4466a019c9ed56e0000000000000000000000000000000008b6a9d13dd9d7014df6acb59f80b335a751fa2ba4dce63467aed18f68358f5cb743718112b3cb2d0b5add34bb6989000000000000000000000000000000000013a5389dba4da195f34fbe798b254403f0bc5632ed98bd6017ef24fff33640ae493c1bb7a77a0d3c97649230e455eb51000000000000000000000000000000000a69803a4cc237ddfebc51df2d90fa1ad03359f9635ac1646bc942546575d1558f5f2c3010f6e2207849ee697be41d093c21276fc1371060c226424eb9886de6897b15b075fc5a51aab4710e9dddd384000000000000000000000000000000001939c2431f8ac4ab19d2735f122c0424af2ef18c0028e155611237e86648bf1d74fcba3008f5c6aa30feb5d4a14a3f3f00000000000000000000000000000000174473eedb54aafc522973244ec2feb3f7e95e50a1e996d1100c8da4fa59428c280f76e9e7364906662c4d2802235aa5000000000000000000000000000000001021d15f8ae2f62dfd3862944bf3be88d86d8113f4be22544ae5e925d450044279c5bfa1bfca44cd5934b42a27096b510000000000000000000000000000000015e0f20efae92e1fe8dea2222ce808a7de9e9e861c333db139f8ac11d7c4fa9ae6e49f51095f6e16bc738dc6d094b4cfccbce4e92cf377f67244995badc72db0b80fe37c9b7d443595156fa41abea17a0000000000000000000000000000000012d70691721f5787ea2e2a652f9c65edaf763637f95c285a62d32dded18579b7257493e01eda19631d00ecdd4e27a9ff0000000000000000000000000000000014da9ef6076e646e7d5b52d1803d3a6d897664851c6de2a9b330e48695737e05f0369224c3eb357bf557625bb94e6ea2000000000000000000000000000000001554f68124a91be5b9f325394db23ed5db8f6c46eb46cb50e57947bae00819b151afbf4ab4949290ad41625499f42dc00000000000000000000000000000000009fc0d459e28cd1239d227e1d2f7d530b9d14ce5638cd308569300a791c997a51dd5a98aad703239a23cfe7cef7f47f6ff79345f31c107841ae388f6cf116d10bc696aec4933de56bb9affe7e20c649f000000000000000000000000000000000452580d6a37a07038ce3564a12c1c7391fdb002cf27a6df7e194b38f3c12a3026f2a8acfe5e634cf89140da256d0a420000000000000000000000000000000004b73c9a4f9d41b8b84e53de538e4b15198f50247e75c274c14f136d7d91dce4a62c5346bf11a105f035e29ccac3dbb70000000000000000000000000000000008a8a3b2705a82b551f8913853f682253e7f1f68c8e42f349337f4f1eaa5103f59430af0c4a124b6a739bf88298c5f6f0000000000000000000000000000000012f4220609899e8610809bb3a4da46e0688c285ba2e8750b4bf44a849cf15fbf5c016e8e8f9372239bb562e7f38916e921cf773387d5351aeab99971eaa3b207fa6a318ad60f1c3e16b7f68251f9c910000000000000000000000000000000001884558e709635c046bd6ea8872bda936ba4d5ebcf7a0208cd0a4ee08b69f36dd2e136ce655ddfd89a5b1cf8e48f5ef7000000000000000000000000000000001357e2dd9fb603e5190d7b7ee105668bca2ed23ec6a248aa71aa430c2b2755747b8dfa3b147eb51ea644bf0354a61ba000000000000000000000000000000000009b0b0a76c6980e62e4893157b85f59345e1ac81e1aad1e48acec44c4803e2a9080f0d193fb799e0277ae6f1058839e0000000000000000000000000000000014c984ae4ef5d9d319fc89895f34a7db02747f57b206b0b30e8c9757d4b47419e6c0c8378fdec5aba364936a3b1922ca2d69cfed6bb2d33fedcbd215dd4e9632a3cf86a4b2716406305f6a85e6090a050000000000000000000000000000000003e1bbb872db172a1fa615155f81aa75ee9760f8602e4135ef9f1640b7f9d54bda911a220d211dc4bb767bc2b5e6e23e0000000000000000000000000000000008464f23cf693b1d4545b6ce4aecdc8fd182cfb288c5ddb1f78ca578e9b16342c8178d886cbb6b8086c0fd1318c4ae09000000000000000000000000000000000af574c4d0fd86087e23daf6d9ce98765e1e445ef9152dbd68152fa4833ada0be440de4abfe7c07dbd4ee67f1a4aec9a000000000000000000000000000000000a8227b982f9286b03c4d49766687622206213d88cde007360df9b4ca5916c44ce44dbe6443577998b4b0d527d22593379cabae288f8a9a8cd54523c20825b8fb07886bbf0ba0c5c807956f268af4fa10000000000000000000000000000000012e31070a501a7df7be43dc23e23dafa32ebfbc10ffb4c53f5d36bab2af69db5a05ad64b9ed116560e40b71f9217189b0000000000000000000000000000000011cbcd38ec3c6a6d49df6a8d6e1029a0412b42bd3fe8b42ed625adeb5a2f631e97bfad302de82ae34f715962b5ba0289000000000000000000000000000000001019b1b619fde9fb885d3c5f03a4373358107af7509754ce1ab2deb67df536d05e07ca7d60d927c15b549502750054f90000000000000000000000000000000018f1768b7140484105cf3ad2daa7c565e18eaba834db3f6bdfc9ee37445f2d6f7dc2b4c986b7efd5373224d2c92aa5a81973977d8e8c592f9063c5a14a658990f9c3405643089eb58324cd3f05b5b5e4000000000000000000000000000000001847b14146cfa2e1700f368f414b6a66ccaa02ca2a90b40a8e2be2ee4eb66af77ba563d7507de63362fb18426b6149610000000000000000000000000000000005c028d2b344ccb6400b53134bd179028b8774000ace89369bc655bb9dcd1643aaeec830407ee941df5432ba27987e8f000000000000000000000000000000000c4a680e2157dbdb53ae761209d505b4cf6b18fef5aff1c5009ab41295e0ce2ca23bd7a4f983fb9d085e1d0dbc75ffe40000000000000000000000000000000013c0cc77a5d771f1df99d1530e65ba782604c1ecf67d08572609de9f18405b9b817c2643226cdc7c9ad35beebf87dab0a610bfd375a7b8d0b034c17c8fa27d4366b06c681131fa7daaeeeb08e25c2ca60000000000000000000000000000000009f32f2f83c21875963818872d243cc8c70b75234f53490eccffbf060cb3b9c53545c1c32025b271514f500b20b00ec10000000000000000000000000000000002491b571087a9e89dbdd039ccd2c37d5d8d25587495b2d7b0066e9dcca02d44b2c134b0128a9a1527396729f069df83000000000000000000000000000000000264e9c47f72b639597de8f26a42ca7d77324f8c0db705986fc3b40dfb46f47764b69c70037a68d76a5de49a278779a100000000000000000000000000000000090614b3bb302ed9fb78b8756524fb78d54a4390b27136087181342571f994b1a93faee28256d765a8ff4f448cc357c199ffe1dc2d7526338462860501d75380a5ed9d53e675125342afb6652a97437b0000000000000000000000000000000012c716ddf17fca0d974e8d6003d99aa90f06b201fd141c74d8fdf1167030d14dc732917d3c6f736c68fbde9df50c098a0000000000000000000000000000000000261ef2b47de8e1576aecc6e19ececf80ddc1f4e28b2ff27953a65199f65a6211db7326632cfe04d543895c727ef8b600000000000000000000000000000000044fd6b9b4a1bacb8b7d4c53c106b025ae78f17c3baebbccca4e18cfbdbcbf8b3ef88ed5bd9bb36d9aea9e24f4117e760000000000000000000000000000000007721612515fd075811ee804314acec9d389900c7ef883e866f71fba00c49d5c4dcc7a2b8e2366f5a93f4577926ed171fdd97465982b58e69993711a6a64134bc4e76b88ba1948af91ba3339e9b9d3e900000000000000000000000000000000122581659ab1712afc23c23c2986394de8e155bcf722e944ec05e7e42e05acc366d9a7abf2136b5dc68a8dcfd4a640bf000000000000000000000000000000000188842cf4ef54cf77c145acb685d3187cd9c842ba6705bfed846ace83dc4400c45120fc1d6a633ea879840d3d0c902f0000000000000000000000000000000005c8966862ed4458a753155ffe2c64655779860149641ee5511a46ec576798fdb5cd9521528df77bfebcdaae2f94b865000000000000000000000000000000000cc10d888d2b7a97666de99ac14a501b7e2171f074d30d947efd67d85226c312a7977cf923ddbc88c533f08a99f2045f786a2a3974c84752b32f29707805c71992d5d473f4b7bc1f0757d126607a1c07000000000000000000000000000000000e5af1420546c1a5a0e0c2bd9241bb7c7a26dd52f4f358fc868bea457a60bd4f6bc5b60b27069fb4f6760813a91ada740000000000000000000000000000000017426a65d239b1d9505bef2b476799c394fcc7bfdca36a1ee5a600351334dadc238b64cf8a667a25d4880a31b73c53a9000000000000000000000000000000000f151587944aad17429b51b1c16193c1e1c93cb412538d1475473666c997e012ce618eb841c4e9e064a08ab83d7fa60e0000000000000000000000000000000015c2e049c532db585807319c23ec077a51f288fcffb2cb6528d3697221e8542e3fc85d18b079ea1b217fae30858a36f285d33a7fbe6ac6eb42eb932dfbbca2f771ffad5e80fde686e5df9d34e9f83ad6,000000000000000000000000000000000c9be91da9bd8774f18efa3ae9248e4b03d11c49b377c372613b7e745882b2b23c49d518672e58eabd4d9b510a25d8fa0000000000000000000000000000000019687b9eaf5d68b0e795cd57055a74e44efb3e997cb038b7f1cbf08ca70e80a1655cdb04402c542a92ae4e435c22d0b90000000000000000000000000000000010aa1514402ce348d1d61b8d38b53017cd3977a84dc14445db64799cfe822b56a0adbfc5332093ce7ea1f0f438bf15590000000000000000000000000000000019ade30ba0faffcaede95aa272be042aef090f89d9ca25cb825846c4bf9e4c1dc575f8968c88ada51fac71f26fb01517,240480, +000000000000000000000000000000000a1346771f8ba25fc44323d5290068e46b3f756de6d97aa934d511979a1486bc32173575639a7e54aea8eeb60f32a8c3000000000000000000000000000000001958ae7fce87db47a65a03402313b99f659ae02e8b62db3525d48dc9075aacc5e5abb50156e704701f3ceb18747e0431000000000000000000000000000000000f98778311e28b4081aa76a3f9546b94c29d86fe8e66b905265d74ee21928dc3ac463049f70d355d8caee5b59d65e07300000000000000000000000000000000185cc233ce72770ae26406476c1779858523e7c940d69adf2750695cb12440440686b6b918f4adb3b14aee9aceb6422119582dfd9cb80d44c17c5f62360e62f6736d186194f0f8483e34d8d18d832d37000000000000000000000000000000000ae2f565a44c8e07f2a136368798a44926cffd3c3a6d4c2fbf91763c20d2bd959271343b80eccec4d59a84394c7a3ce70000000000000000000000000000000009481a5fb276c938801133adf10dde3e7da2087d0bcecd3c9435b7de544271eb3b07a69efe7e168869e727868f24b0d90000000000000000000000000000000011774e0197866b1c8b3428d353d2c9f6326a77ab30d5595e2402a0486f03ca6ebb1e8dd335a60a772dfdd9a3dbdd3eeb0000000000000000000000000000000011ed2480d79f73a67a2adaa6da3ae4f1e1c28feaf0e4cb9aafac658901960129e40f6415ec80a31d72004899326f714bac0bd9b8746fd02aa70d8b8a2b5d3be46baecf9449d8cd3d620cf9efb3c615d1000000000000000000000000000000000a73b0d8c31af2deed481faec54095875639233bb09f31b1c7c745cb54778d1c8bd0a230e963ddd2ec8d178d31fc14740000000000000000000000000000000015a889b16be93d0b6dced01f5e2278ffde1cef0576d0b04b49996cc5252854f879e04b1ffeb90e222f4b9d5fa350767c000000000000000000000000000000000b53dc4d72e90330ffad17012bc7dd2e497cf8aa6ec73bf25c10427e23fd28137631249eabe9d0308c956dc7a9e92047000000000000000000000000000000000930cdc5d04ed2d1eb62937d9f72fdd733c07a5a0e392fd5216100216b1a2e3cde7053bf766f046cc470d92bebaf6290069d889881d5bb87dd65a9a02a7fe239bdb55ee54a6310bc987e7c5772404d7d00000000000000000000000000000000131c4e590400b69b3657f7c67272b1e3491983997993ee87c94043001d78e605965abf3c1a8c8c39cc08d5a5ef05520000000000000000000000000000000000124f71c136dbb032504da910958e8a7949f1dc5c061f21d50e439e01e67919891633b3bb84fa8a54c69b632f78560ca70000000000000000000000000000000014a4b1a05f1060853f4294e669a20b91f939793a6eada6dbc84fda8ab11509b256d8b785b252a3795f1d2b99a51df05d000000000000000000000000000000000be2489f1f91d7adff356236859679c46b6bf8c1b375e8bc8bd1e97830b5ac223ffbbda60ecda168bacc2c0b90ed25d3be658348e299bbf2438a0c013f86eeeb69a013b8004a4996189472f3372b326c00000000000000000000000000000000111ebb796e8770d5a69e724a8d3ca62ef1f13778baf4ba12bf462211d35e325ff8e455c85237a73a3046e531f2e2255e0000000000000000000000000000000004308b76b06067e0a07bda143341220809b481b40b78edb2e24e83aa0f003d209198825b5fa9bfd92597e27a4054d3ee000000000000000000000000000000000de74485713f5c95653e98b96aeefb79b59911a610c2a848a807653c19d50394fdb52178947c779134d24b6d396ca36800000000000000000000000000000000069f47a71ad765591f6335b962e7c2d87b556801e1e6c25b449edc83432612fefd405c952397a704e9aa5a924769ad4e9b9d0ec92ae7df3f52a95747659f8fa3ca2cd01e8d7ef6de384111246886bafb000000000000000000000000000000000a3f89408ee43c0ba6a7c9c479327ebab426d430e3ff212c65da6364b16195619d27eee83d701a2ec50bd4b7acfaa06300000000000000000000000000000000092715831af983f740ca2c673e7c9c47727d64165c59fce19dc3fbbdd0b6a7be66288ea1f033ebb5ae2b38b3762edaee00000000000000000000000000000000071ca6fa9e546d4bce965b2bd0f0fb97e6833f05cedcf66d43ec88aba411dc4d6db9f1591de22f493f49a1dab1a2701e0000000000000000000000000000000018f89932ec032fc28775d34d588169a1435bf4ad7e2ee11c9d6934dae31324ddb96b3ef88f95d1bb2e52c3c8d9c01516d2ffdf1237b4e03c219806f2dea745c94bf08924e1b9f11deeedf0db19da6f3f0000000000000000000000000000000011b5cc382164fc21c9a72cd85acf61c2a78d00a16a2dff938f0b36bfb3bb7075845a1616001ab53271a9a257a38312cc00000000000000000000000000000000139ba2f27e545d45027a0b11253532e28fa691170e08608472ce3b3f9a3e9398c5ee76953b1a1d01a5e79f194c32d1f5000000000000000000000000000000000d875f44829555cec695f3f4a28078b0a6f168bb0985793d003443b75a141936f3c7c633518890e0f7238416d46573cf000000000000000000000000000000000675420ed817ecd24bc5172d3e7df60ac4281b24ba91e8b5ca8bd6a8321f5c7312a6ba043fbcdc467c8a5c957590a692cca0751c9534cee7f14d11b7c8ccbb2c537a799df59f850bb125c6362d72e9c400000000000000000000000000000000107bde844286cd3958cc7a1314127322251699b51d8af8e6b57495497f21a84e05612b1569b54fc5639a75e9f9deef750000000000000000000000000000000002355b1a60e24e4879448437d2c1b12e58f02d7eba88583e96e9634f7e2c8c6886132ef0488918f665ae3f7b6977c7c4000000000000000000000000000000000fed531e437b70bc4a19ad63c61ccaab49afc50fad1f156b1c8ecba0e1b703f8aea61882c6327d4d8fdd072df9c4e73500000000000000000000000000000000182177409579ad53786539514753c696c8757b8c4d9b8360392f24b591e43ec20e84c0abe468061a9e5e879c5c81314217f890a1120daca4a1bc1bc0fa7529f0a87b5fd6ec385f12b270bc0f1a5281b40000000000000000000000000000000001fb25395089228772d6000025cb0356eb510c964bf7d0c12d47a6608fc18cc448e44880eb5ba8475cbe6418fc9d8fee000000000000000000000000000000000f3b9de9980e5afaebc59c56e02fd75fdad13013842ac035f8d5569a46cc67f0cee461a939aa5a3d8fec2966294207930000000000000000000000000000000009a223ac0edb164845eb8397e0cae4363fb2c8c996c3c5d722cb50be56cc3789c732763cfd4b61470886dc991be39f57000000000000000000000000000000001909f17b229eb351dfe8317a8273d846edf14ad5ee0ebe8cc2b595ebfed19b73983035e19ebaee3d05b1dea35968586961ca18257d9d989ec13d4f158b18ec17d59344f4558b6dae6c0aa0c2f37affb500000000000000000000000000000000081fa9eb8ca7d9db52380e4c408e6d5d668471bafbafd62ba9023fa08f6d300a45295b583677824c29ddc3254439cadc000000000000000000000000000000000e2e613043b1566674f791dca9d860a49a75dfa24dce3fe18f544a9b24ec5266a64e77386b672c93fc4d079eb8e76a01000000000000000000000000000000000f471b86ac5783d720e7d73e8871474c8665e8a109aba27c1172ca24217eefb0f66c53232df1672dc0af6ddf9640e10d0000000000000000000000000000000010667cb22a6a818fa7c729e40a7e70e1f31b0ecd568b54a4d352d5c9df8cf1072ebf2ef1e612efd96bddcbeedd8566430fc004ed8a135ad97cdd1bc4d0c3ccd15e65031ad7e3cc13ef2c260958bc43be000000000000000000000000000000000a0ed87b01f27f26380c6285e82bf2f12ef3016c7e7f3a13041d465825664573db47be6cf099cea615e21f6a5d759b6a0000000000000000000000000000000007afb2a1bd50fa0fd3174d70f1c8d5c229627a496bc9bb89d4f52d47b1862e14d704dddd80045e58d00336e898a996eb000000000000000000000000000000001698f30f824ee5cb71b3f2451953c371987433d2eda570f2a13262ff9e5e529e316b06ef6aadffc152803b076f22db9f0000000000000000000000000000000009eb1d5f3da7cfe9b40a70e1b3c3dae36436e8d068a79dcaa283905614676645c99a5a165630ad46b70bd6be8b1f21a8d8cfaa1037e2c81c6973b221dc7badf25ebe3fb4b42bbdef1124265df2c7ccc40000000000000000000000000000000005c4390b8f37cc3fb9f248470b505a5d9502d44e4a4459d1f56452cd9aec89d114f1402fa45935930fa00888a4860a9900000000000000000000000000000000163b0ca84b5cca4f124bfb5a13a4a3efa677a84dc89b6a61e69d0aad34fade528614e549a7b2326d1f6016bd0d35465a000000000000000000000000000000000bf450dc8af483a9f993a29cb47d5362c9f5ef38afc2fba8040e14514eb834fec6520a413fce5868aa9a2c7c3ff6617a000000000000000000000000000000001063619f384102949fa1f8353f0aaa5031234d736c54103df6ef6fcd0df02a19c3aef471f0413a1e19febed6395459a0c25ecc5d37659ebb0c9e21ea2f8fddc518e3d8faa99627b21faf105445f69d7d000000000000000000000000000000000e35db3017963d3a9d62b7e7fbfa13ce4f5fb46a90c1285ddc0fa481d9379b95a77e8cdd4aab5c33059bfcdcd82473fb0000000000000000000000000000000004fa27c663c8d21f041d15cb199d31cfcb96a56cd673b730dd111bf03cd954cc33799456674ed4d58e8e0dfa826a6b26000000000000000000000000000000000e0df4e7f943db5b5c27bafc7e1ce099b2caa64642bcd6336ef926352682fbe81a1945b266cba7eab52b16f4aa63eb8500000000000000000000000000000000020167756b8c68f535c4691b1249ca1ccf0a539f7274623ada824d0ba789ef44ebb20ec1ba51d46c0a42da78653d287e26cbb32382902d9b1963779070d749cbc4df1e7605f840819f2c04aaf89c732f00000000000000000000000000000000178037c6b5fd1c6c396d8aaadb712863557feb744d2cb9165ae5c36376d2c066f7b1648e083f81c2c96da6562e0b3c20000000000000000000000000000000000b805b4e1cd5d45d8b6ed9d4f604ac0b40f336b8123f7281df43a6e803f8688bd8087fc4d5fbae695d06efb0fa35e18400000000000000000000000000000000000a947562dde45f613ee1d15614940a2edfc770d733a60374f8e9188675d4cf973a5c1081c11fe5a1d93bbe85e6f47800000000000000000000000000000000059473d80c82c6ca06b4aa71d072f4751b3b053b53ffcfb4a84906ddfc36ec5918668a62f07054af1b241bdd4485edba699aa549077a80ff8732b5fc9df148a90f405bccc14bf7305266836566b7a98b0000000000000000000000000000000008b9d0916a9f5689b8fdac84bec3a49d0224dbadca6329ecc156da633e1332bcc6735ca3ecb228c22032dcb7b2f372d3000000000000000000000000000000000cac0c264add10bdc1217384a7379f65b93cf822418f7e4e2b48eeac45f068a61f805cedfb1665dda06e04cb726d245c000000000000000000000000000000001578e98a40a64da59154b1c3d757d8f1f8cdc500482c7b7d65b9997576f745442fbac654c19331977bd210df440372970000000000000000000000000000000015ef69f82e85c81d28893d94927068f14c6516eb7d09898d5d055cbb7a9b55c6d7f686f067ab164160e6d6a8f91ea19d40e2de1a2901f1380a383a741d79fbb0a041da5d7bfb92edab74cd483edf95230000000000000000000000000000000000a6a27b498285085139b8dd0c37b700997134337e696c84b5e0cf70ea3991cfb40ca3a3098a3b3a2fa31e91aac78eb2000000000000000000000000000000000bbd7ebf4301c5eabd4f448b89f1b227415cede3247a1c8dc56a02247efaa99dc78cf370f644ffc06cd2158fa25197dc0000000000000000000000000000000004535a402540474d53c084d4fb6d9e12dba6716ee13286ed758aedc1ef911b55c572640180a54cbc084ff57ceae8a4b4000000000000000000000000000000000759de2a9e0f3c04b4f629a682dbcadb2140e5b935845cb55bd267e230e08c6e8cc5426057473aa03ea2196203bbf6dc062b323592118868d547e83b731d15ba2c7bdb1ee4fdf73600c2584f1db0b45d,00000000000000000000000000000000134c29cc5c33c10f04b6c09b5db71b10304028d06ad6acd4f4b39b16823288085a84a0380a1549f04b3dc692cb8216d3000000000000000000000000000000000a0a9379d63527ab9b5f9c00be4acd54e5fd683a0a2f37c85ba570171c705eaadfb0f4e4be1a8836c9de86dff46138300000000000000000000000000000000006ce78f135dda5af34a0e069d7ef13fd589cec5a6128512bdae7f45f28b09c6e4b3cf638628c9f4783097cc00082aeea00000000000000000000000000000000141e710ce7a979dd1772150d0cb2d5b269d5cda50d1bf7bd0cd827b24f9cd8c1e2775f495cfec0428519627b7fede464,240480, +0000000000000000000000000000000018aba8353cc470b287a163fdb9b8b4cc46071543ee8261f928a7b856287946637d9b36b728a54e1df5f185a47f1556060000000000000000000000000000000001129541b2e3b2e1a553995b603dc3eee44a5ea440e687739ee9e1339dd79bd96c67231ac753d483e0ca96b27054997b000000000000000000000000000000000e1cdf3591aadeb56dbd80890ff7d5639a64847cec771a19c769df7da732a6d3179d3a89ce0052bd7c982af0304408120000000000000000000000000000000000f5f5f0ebfd2b632e15381ccbabfa88eb774f2c61801381ca73e6970965ecd54f5f3a9af7c152186af8fb75ffb5bc25764ab6f4c43630d5e79e8c474d76d8973a7b7bd1c7f1a985333cf1a6be5ccff2000000000000000000000000000000000e527e40c311edc5dcdbb4d0b70497eaee14533aa8ec57dc7cbd7d839fe6c6ae62b1fd0be2346a038de687d5cf5394d60000000000000000000000000000000005f9fc63027dbee5e0d55cd6c57daf5df7af0d138393a2dcdc71ef9aeeb204ea347f7d574e71f2ffdd37d8f05dc7979f000000000000000000000000000000000f8788034c9f1c9c2018a52326c046cdba8997199732152963819b663c6e58e9d6a0065289e2e25a97ce5627505900f20000000000000000000000000000000002a747bb3bcccdc6ea0af1bf1d0ce55de3f41b93060361b30c76063346b606322a76ed7eb260219c83aea0806ac7d8583280f1b1e78d2339f64b5b2f2bd77aa24623b79fe2c9debab4212f4ff564983b0000000000000000000000000000000002148c043e065132e978e89f018a5b728d278c95c9cd1a6f276bd13f0cb708422a62fa22f7b377adf33055fcb09a6a8100000000000000000000000000000000024c4c721a0574e53118bdcc3fd41f73176bc8264d2ff39673210525166bb3513016b5c9af67a47a7032b74a62effcef000000000000000000000000000000000797dfa8cad94896916b7d55fbbb3eb0eeb74f70231205388d0dda69dd8abb436c22addd22c1e3689093739af957b65200000000000000000000000000000000010dd2ea2d45528de8bf1b5c5dc3267fe8951e48ff5987e67ec52d58635521cf1905f1688894e3e23a659764880b2301d4d27ff9d03ab9120ac2adfeb36b070015f0e90782255ddc9111704c5fb11177000000000000000000000000000000000eecc0a4edd3cc3f70d3e0e43ba56b04cfb3f1ac23c657048a94318e622217572b0f929c73f545d6f5f5613920c0580200000000000000000000000000000000137a098ea8d3aed32c197a2d244a2e18753045b55cfe16874f79c728c664b7f23b10476f20dfffb2f80417c26dff4f860000000000000000000000000000000004a7789b02d7d95a2ce0c7bac39d5b057509200393450a47fd9d087a353f866921aa11185550537b98f3073650d9a1370000000000000000000000000000000006ed63730bae06403baf705da0e30c6c00739799eea4a312d06b8d7dc35cb43a4f1e941a69e55ddd7ab8ce42d8629fdcc66d5291311c7cdd1f33e5365ec0689608b3569427a8f6a9cd0b94b671472e66000000000000000000000000000000000ee7ddbf43f17f722dae84d34d26add8c1d732918b8c75c6b295f2f584075cea0c655911410b32c06868c1acf36aeaaf0000000000000000000000000000000018775682555d9f5a576cf9462170910bcdd083671ae2e4c8c6fa99a702548f1ce9afe90e681b00d194322b1a2a3be7ef000000000000000000000000000000000f3935bbbf58b91fe8176f3e25ad3fdeffdd6b369ae70b704d4e54d4fe32fe5987e73aa5aa975e958497340274577cf3000000000000000000000000000000000c088bc439d638d86aba6bb1e6e9f7540ac2da3b96080aed455edd1fbabfc141e26f125cc3a9cf72070a24f298dcc3ef4b718a5129659250640e333f4567043ca749063e63d87efd86a9995adfd3b8450000000000000000000000000000000018d8a47d1a13b9b8fb5a1625f9616ba120d5c677bcc996f694b7e15d251fc4bc938b0a7cb5b70f22b8e9f5b416c513210000000000000000000000000000000003d0646458bbee7ccab27f858b8ab0af0cf583da12a40ca5a954d7eaf97c897d379129a63d8131036f29c30c6e644149000000000000000000000000000000000d5466b50975c5a2dad96e4e24339eafc8c85c2497a6f19e12d96603596498654cabea6995a92c91b8319ce06f18d56d00000000000000000000000000000000191a96d62139f8219b9e4369a783400d045d72ab2dd83fd229e08a4ca73de59a11a5add86c739cb3bab4adc5e9f79685708891f45d7bee38fe382820260061e212c6cb9a8572b4d1854f3ab09409b05a00000000000000000000000000000000032eb1f7846b563e98fca0cd44ede4909b6e16a893f5ef01eaccbd7d8aa11710606bbbd0ee6480f7cdbdc9ffe66c3a9c000000000000000000000000000000000c31bb6fb537cfcbffe815d86ebfae1f5053ceb756818ede8a58cd84cb34d0eacc70ab9095f9db1691e4fb4bb816d570000000000000000000000000000000000a8fa1dc2f28277a4bf8fd9665d4b5c3baf1352d89890d4af94a3657cdac7fd72558da1e65cbc5bfac142f0e817be74f0000000000000000000000000000000005ff65c22ff0abfb33518791823c5f2202ea5f7258c0a507ab84460335ffc2cc8d7c7f670752a7647d6a6487ca0c9adb85ac0f94f300b004c7f20aafcfd9129d6c2590749504a3f08c4cc708fa30100300000000000000000000000000000000190379b7629f74bfb88096dc9ffcdecebae0d653410f032a35a811a09022679c9be19f3790af95c3205a396819e068e1000000000000000000000000000000000b6f114fc277ae8f0b5374dd349985bf955dff7fcb0095e0e1e137fb539814be78c924074bbab54f29dfb42f3e7df24a0000000000000000000000000000000002d86b0507c147142d03d3461bfea4c3af7e57a6edbb372387de24a27cfe27c44ee4b9571325a1b3f5e83eef450f2fc6000000000000000000000000000000000ac3b226d5e13c36c3a8ef0c8896d9af55bcc0cb67ac1cee57a5c6519617ec77af9af60ed44e0a8284a2d59816ebf848fdbb634bc0f99c5795f3c4d6a0efcda7f71427f1eaa1c5411caa6cb05ee3147800000000000000000000000000000000079cd4511e953e4d1b3f4f3bbbc66a62772018e809779fa39aaeeffac737cda9a6116293848f967577f03017f33231d2000000000000000000000000000000000ce3cf48be423a2fc0188b94f2a22579872e9ba140798e560ad107f63ab2b8c601831f89d06a4bb8e7a758cf836ddfb3000000000000000000000000000000000a6a90f735f215a79216fc4e7daffbf74775f38824952af72ac38c38a77a277483e34bc95031679494d76f109c0aecc4000000000000000000000000000000000d55fbce780d885cf817cd2126e7acf115ae9c72843af23c376f3a5d4307d1eefaa0f4691e7c09b5da1707aeaa5b675af5e4695c01849259fb969183de385ef30c2403e081067c2d9b6b5522c73fcf200000000000000000000000000000000008924efbdb46b9324bfb79b922ba8b7d83f5e5e4b3b736105e5339805838171801bcf17208f3dfe5c7475d4e45b6ad970000000000000000000000000000000007bc0096fd23f0c93f0dde8a3974ea3105574e031202f6316d5940c85164c6d6bb5b86078a0c68dc822c0fd1b3dc8cc10000000000000000000000000000000017276b3208b347388a5657b10e3c8e4a187b376e42352f76ee3ee88873217b6b8185022c93097cc116abdecf3cc64467000000000000000000000000000000001915ff932acbdeb52f07b664bcc47c3a5b096c6cec32da4d7044326dfe84358e49539fe50782538a901b99428446b0f50ea6fd588db5efc5fb2248634cca683d39d610886b59eb3077fa9612c368d7690000000000000000000000000000000009e295d229b543a17db1cc85c846111b7097bd169d19b410de78f8da9684e664922eae77c64b0db430aeb422016cfe7d000000000000000000000000000000000e29aab30a1da56b8590e9df67171cc1b9c847696b51147cc57ed6c3b55819cfa0992c67e15e4ca6de2573c9e16231c10000000000000000000000000000000007cc9990c6722645e320dd16a4be8adaab41f958f769ba0d22e235549a7457778cb9b14aa6ea5caa9e0bd43f8d04cacc000000000000000000000000000000000b2dab5cf37ae8e76b71dd8748c86e8823142792445fa0b140de31957d35bb7267e3d94e0dc92f4342d9f8560c5d9d86dc2060a3421c5a8336c80983c9a160345901a496c3a74fc5248fca081d09953900000000000000000000000000000000128e2aa795f8479da3ea2a4efd12aa90a6fb019d4da89fd372e6848ff7ee17da689d766c9e49c88c962eb4f682c56fff0000000000000000000000000000000000fd68bb80d6b2200297aacae1174275f864669e962d85c9105032d7a352fea548e9fa0629a6749c789fa0827a40190700000000000000000000000000000000175bc3918dcc972fb728f1d8cc30ce9887efc6e0b254d8d22af87f95cd4182129d494c43d11b028c4b9849f5520a4fc00000000000000000000000000000000007c5363f507a01c0b6935fee0413345bceaf1336cdd20f69060bdba2e411521a61a549e6159b2e006ffa16e3bd77e998e27e4afc3e6d59d0f5871b35eb83b46cf15da6c326e88dd8edf84031f58e23f90000000000000000000000000000000000efcd782b89fee74ebc037160c6653ccc104260b5f8989545b40d51ead6ad6ce6252e1232281c813e3c883af86e68ca000000000000000000000000000000000b68ed21f76ce131c089dc454dc48ef948cc7c6d5fd87d647db954c9eeab2f7f76ccc51a1cff8612e89bceed16ca03ba000000000000000000000000000000000cd776670d5171610046fa294fecefb42f9bb4d71baed4af65a09018b09ad9341789abc23c9feb85adf96b4203b0c0a0000000000000000000000000000000000ec4ca0091a28b73c9adbe7120f2bf1a84a62ebba1e86b1948389b1a1966c1de4c632a5e245ba634b53cb932f5847f6ecc7efff04f143e2d038de153861da5e04016a7eb17fbe6365de13069d088b1a100000000000000000000000000000000022f319bb5167c2b945a69a438f712df8975a0e262438ea687e2b0d824e2d1d14bff1065f50fd6ae92494f6f3aa9472b00000000000000000000000000000000198ce9e4ddb6b423788dbea82d75513f43cb43ecf1b27c8788f041248f01808644f60fd823e5862cd7afb4f7e8b6b6a100000000000000000000000000000000119dc1be1bbb7e678319db73055ccb88ef7efcf6119f8a9c43c69247ff264879a627f653a10a950e0dbe69155ebca4f1000000000000000000000000000000000692a0ef5a75d42524e3fd52ae073b0f2ddf6378f18a5dcef05af4868a899b93c7f1d2691883e5c85f97052ef1f4177d09a2c3dbb4ee4f485dc60dfbd94a358a7c62204c021f2d7b140187ee9ffdc4ce00000000000000000000000000000000102c92272571b73a7df754728d7293fd8050d9dd2b8605c3f7722e6de541b7fc6a81b01c1cf15e5241ee4ee1f81ab39d000000000000000000000000000000000af1cd6f23bbd3e9ef75eed6d6d99a7cdd24574881b3609e45c4adbf82e08259d14701fcc5b6338ecf52166aecca003700000000000000000000000000000000026a1a4c3eb54de2ba4509dc806db9efc7e26247d501cb59c525b8dd15d03b91abafa9ba5816c22e1f8ca159cda34bd500000000000000000000000000000000170b510ec227fe8534a2cbb0f405756491c4f6832df552bd23980ab0946725371b3c24fa8b93a38bdcd47e1026e1d2a0d9b15c065497392e4b477a556ad620d44e671137cfd570d53590b7528f8ff680000000000000000000000000000000001423d1707e49d2215f639df75ee0e13bc724efc7d099259179260ba0f17157c4efc4276844bfdc46c61ac2185f64beca0000000000000000000000000000000019ad06d215d3c819311938f89609ea7cc63fadaa11bcc86cf5f26370a966eaed1aca312c18176674b5aaca3ed8ca876e0000000000000000000000000000000013bf3f13e87f3ce29f0524094e2ab8e39679566add32e779256006dc92ce09f60d5bb9cf0452b90ece71a5f6981d77f300000000000000000000000000000000112e4901efca14686c30a883ecdafdc389303f4cf46345e229885c76d900b0aa084a957076009ce22ee36d4e285d410c9e2a72eff2ec29a65b417767e7090b73c2fb530de6c8f4e4ba30543946423b12,0000000000000000000000000000000016d1fce53fc4cf40acb0347c0983dda46383e4828c16985459ac89a2ce8d3c2a26cd9acfaa2ec899cc63b4c6bc351f560000000000000000000000000000000019c9626363b511a79f297dc79c5a3b7a2e5127fe49a2fac5bc43a4376f170404f044f9f84b82cd09a306012fc81e3bdb00000000000000000000000000000000062e324f3d7c5bd39808b762a5b009cb30bec14a9591477959339bf2de9ef27eb42a0eddb95aa5fdca9bb9d89b278cc20000000000000000000000000000000000f05225a4d3bf910b0ac0103594a90684ffc0c09e2c21744032e30470d5727be3c27621dc2377e9845ad78be67b856a,240480, +000000000000000000000000000000000c64577b78ff306186dc44131cf8bd946a68e65502c4af9613154a7e3ffea4fe4e59cac4a500894b470a74e66db1834e000000000000000000000000000000000b4311c295bd30174f17b9ab3544f1496b9655302a4b6026a113b1aca31b555ce7b2d812bf8fafb6b075f67cdc59b77f0000000000000000000000000000000012d7dc3db10ae6b4e3e99c655aadb71124a0bdcfa6e30ec13c7c977d39f83aba4979a1f45881813a3a68e149a40120a90000000000000000000000000000000001b958c47cfecd619c05a2c54315585d15fe377beff889602ecba6ea3b29d51f6480f09a0a8490e4c754045f0bfdc3eb7b9aa7e0bfaf135ff24720773ccd1e0a46fab6d678d91a14de137061e145fb9d00000000000000000000000000000000010db66653f2ca73e55d85254f62e8b558584d546913e4b8f530968088d90cd5c4bc164fdb77325fe0bb2fb1a5683896000000000000000000000000000000000a1af9bf84f0b351c8e8c082a79c7ccae768bd2ed40eede38666aab1901a6eab0cff258f440c8c8eb4854a824b9aab4b000000000000000000000000000000000444fa654afb57f1b01d10be08a330f23667af58e3a40c630a2e01c1809125b3ff8f76e8a39418688a023293ff1a45e90000000000000000000000000000000002ebb117ea107a3598b888dcbd5918dd0ca307b21d65843c6c28b3efab89d0e276e5d92283bbb345b674d4a300595425c6733c9bb7bd195622b96c4181e8c8837d1912fbadf77d6029f7fc44d793b48000000000000000000000000000000000105818a11efaeab4801d4fa7558b09bd56294be7f58e3e54fab1f4f7c243ceaf08c99a14c3128ccfd866b6f93caf989800000000000000000000000000000000091ca092e5f83a04e872e621e71d54dd6886d088718f60ed5b62d3e4c165d3ff2cea1e50fcb94fff251df0f5ee453cfc000000000000000000000000000000000b42051a1ef52f87536b9bca459fa7070ca17bf825950b13b3bbe9db183ef722f62af6c05c174c65d59b722e1d2f5b0e0000000000000000000000000000000002fdb4a5480418e43aea28e5041af6ad55a6c226e1eea1a0449a99b5a937375285feecabea95c2652da5113dc17d8ef4410bb66334c677397d04f59eade9630463065cd7da3c9d50580c7d66bbaf487d000000000000000000000000000000000d8f93b589678d4e93bdf8da7834bc8edab648ead731b7f5f0cc299488f07262877ee9bb1174ccc106204dcd3f1f416800000000000000000000000000000000160f740ffca48d3a10c43e591cf46c129507f10e65d76a031fded2930d6c2dca4c79d7813f63e4ff71aee09d672173680000000000000000000000000000000013c768a4889315faa3976c8e43b4d466ea594bd94773f270a198f2571ba9662d10435d1e541a492055c333eece9bb29a0000000000000000000000000000000003dcbcc9e6a0cd5741d77da88fbbc269202e8f944a5df5dc4f9145758654934d5e1eedd596325080382458961ed3d21ed97a16fc5b2c70829b15f73615286eba334de1f520b5f0f6a83d2578399cc0b3000000000000000000000000000000001344fb37c1d7dcab01a4bf6fa50c6bb7606f7db22b85a3888ffcc2e9f229f196881cd7c82160730727e49b9e6fea04320000000000000000000000000000000010c7b15a6355d3152eaada7a606031f28809f278a1d0e04d264b563185ac7d9e351295191a6a90ffc9c6dd33995265db0000000000000000000000000000000014438086226a061a1bd557dac24d9333e14cdfa3a7bb00ded4a450e8889a3028b174bf38ae1347e6aad19ebc1cf5ff7800000000000000000000000000000000105165703c4592cc4f1f489d78426a56434dc77327c13221b582dc25306f4c5bfe596f3e47abcb741ab553fa14cad374bdbac08202bbe5df1229e99c76c1727f7789e0f8c2002f0a2c195bdfc00acb36000000000000000000000000000000000ad8b55a198a5e788bb54c32112761ccba9863cba16d95ec9e30181376e7eccaa2741660f2c5f708300be058e566ae27000000000000000000000000000000000b9bbca7db413964d2ec113cdee2d7a7bcdb712d285655f6b2897dcac61456ba4d08e25e8c28627231824829bd7d13800000000000000000000000000000000001ae49c10675256651e3e038a2150d85993fa6f2a97b9bc02c693ed97ad52af34015176258b3b2546b81010a4381d85c000000000000000000000000000000000c8f9668a0a497420acff5207a07cf780e0b2ba78083eb0ed8eef76beea996210541bae2e64d825000f307d54cbe3b2b43da827b812ec6ac23b00208cbad0f2e8b3a32434aa61dde029683c34c1ab1900000000000000000000000000000000012990a66c132a31d60d182f729b52d9b57d9d1eb1988b6f0b4d9fa2847f26983078ef9bbfd0e888e14bf7d1f92d09e54000000000000000000000000000000000585215ffc2673a197bf9cc6c6424547886abc6ef5c6edfeab2ef0c42034a4a458fc7155c35c84a8e9e9d89fbd4aa25c00000000000000000000000000000000118fb4fe0d3498dd2b55e62272e95a1203f9fd22314921d3e044f1b162376aaa7e8154a4e2184b66451aba98729330c0000000000000000000000000000000000364b9032ab9cd9f599979c8a93acbdb831707f1f84fdc92844b58bc7e4d72472ca5b09c51b1b04271ed9f0e391552463c7a8f7bf434ce5e63ac9365448da8663745f66689b4b04968f9b8b1b6805893000000000000000000000000000000000ddf9e4e302169e195f4f88fed06e0c93fd1b542abbfeea5da5d47c662ad9a16b8f4aed7874354fb9008d073920e1e7e000000000000000000000000000000000043fd1a4b781f25e8747ecb3eec45ce90760e0d5dd701e8193a7e726684ccb8ff21f0219ba15e9e777d339a3d87a1ee000000000000000000000000000000001117d2ca429048056084e4847c7169b4c8ddaefe1d48079949f9f14e4d74f0e0b38a95d0f17389f61da7b2a6d8cabd1c0000000000000000000000000000000007adfc7d87b1c533b4439f6b30d38c694b65b3b321f3eec13cd7d923f1d5f117005be6c3ea901a9479d79fc553e34e6c51f2e2bcfa6ebf84d3ad83c57257b9032e5d62a8663ed5d77afce00f33382bc600000000000000000000000000000000115a81aebee0329b174c01458f8714b13ea3fb2dbfb051b27b29b940652f27e01a84e522626d12be80da7e1039e2baf6000000000000000000000000000000000d9e37d2e5e7160db30acf5593d1c282541a0d4ac0482f0759fef8704b9ec3ab1e3ed248e37c6be285e890ef1a520d0b000000000000000000000000000000000c198a22c2f590df2902c8dc2bb1ee427b33e9687767666140f9d3b51d73fef18a259d43d86fb3559b1ab0abacf547a70000000000000000000000000000000017e705af54ab76145a79e747167a4fec6ec3a16f3ceef86b1ddd1be144e616ea7d686bbccbd1c5c258e4546405be023d6d8b15ec8908bfe008414757c0c7f79b3079f9db86d91ac3ec8f38ae2c94d48b0000000000000000000000000000000007c4c31287ae0b3bb90475f84abdda36610f887aae311d8e97bf97bbdbdfb11d38c7de331cc9dd022926678e5180c0770000000000000000000000000000000017f4afe28adc4b24d16b9cd97aacd171c2104b13b079c643d664a7c924151a401c352832c4967c0e5cecec5f1d1dae290000000000000000000000000000000005a8aa8a3a91461e0ba256e44af56875f8d07e24628e738ffc057249d8380417884f40c84e76dc6ce5816ffc05c0d686000000000000000000000000000000000f84bb7385a6936b519e881a708541570a31a9d7897ab8b348a350adb0d30522567fb917c9b6db661b6f53f98b5e68aaf4723e85076d48389c3fb5a5df16b6bc6f7a69ca701632b1159677bd8a6f7bb1000000000000000000000000000000000a8726ea352582ed52ab4e440102963891f059cf5a3f4901615733ad560a99930efd8692f3c30256d58e5cfc4f0803bf0000000000000000000000000000000016a623dfeae872639d99e3b8209748642f20af796325630596b6ab6146783bd4f2247c7ae38d53ba9a3fc0cdd6f29087000000000000000000000000000000000e40709656e569e4fe90eb85d8761c6ce44a4272793ccb7635ce75d67d97464e8dcd8c32bd4ac9a36fcce706006915b20000000000000000000000000000000019e64802756896206a9600f2d888f3d307ebf958492b1b5153c06a11231e2d7d2f1786368447f380093a29117cc25da9a632938a6df169fb64daa55d2f874ef7629d5be41dfa0d50827c082333f0fca00000000000000000000000000000000019c7409cda6084edc6e559da9b361c96cf207f1e2cd63cabc9b86c5bcb07a59b43e9c6ae3e90a61c872f168ab89cb2c9000000000000000000000000000000001101bb63a452b766a085fb788937f6b751417dd8d905ee50ab5bf96cdbb9d7b68c1735460a71eaf9e9bf662734f495c20000000000000000000000000000000014a103871fe523cd01053a992eb9884ce83c6023bd6a8c2cd9ca60b8780118c88502c6980904f2d2bf9ccc9fb597d535000000000000000000000000000000001929f25d52ee6b9a44333237c732a63ce2abc80c5510bd67faad1d7adac96eac5449823f3a52ed18bb90b93d9640d0d1283a4da7f71bde54d4b7e28b2b23e2eb05d8b025e77e15810625d71faca6d6e50000000000000000000000000000000015b0a46692f57ccd2b7f53040dd75f30af0196aa3c5499049eb172b4d927f96a59c42a129117d6162a1bb31d2e8734a4000000000000000000000000000000001366dde2d9070a2c057744fffe78effdc328b122e356a6aadb10c3fd2e8badc0ff70bc6d18293b3c52428e2ba78766600000000000000000000000000000000016fd48b067b949ed75bae3e4db29b5785bf672bd01032a925d653f8a605998e1eff6c77ec39dcfccd417f1e0a9defa820000000000000000000000000000000004cf22bd706dbb1cf8b97187ed97636380871402b3ba9de58f174bf50a7a0b528749762c3f55f5f835a276e43b46e669d402b71c1fc5c3f3a4ed9edc73457a27ea427f83a784796e01b7a1451b3305b0000000000000000000000000000000000ff424ae9372af46de34210bb0bd670eb173bd49076df5caca4bc4293e742121267a20506f931a4ae77cc36fcbc8df4d0000000000000000000000000000000015a6815b47966fb84aad5de62e6d4280f9135e129f33fd01e667f4d6e1bf7204317fa7741f3cff3682e251437927131c000000000000000000000000000000000639dca43483b79ba8043130e508e91fe3f43bc362fd1dbb135a2eb8f3b94d5cc4af70f1101c790545a0eaf2408706e1000000000000000000000000000000000045f0a04a642bb6e4db34fbffc8adb19a24648554f36ca371fb1a851384a4516a57f1850f7d6be59ff67029ec4002de310bc47acb3aba7eaa490ec104ed9b2985f65c7347f69fdc67b76f6f43846a99000000000000000000000000000000000e796fd500cb1a25b834baf7335641f34ccf04ccf60f82367f0e5c8c7fce8e3030e7b916752bac8e3adc01cbf4b319ac00000000000000000000000000000000142e8bbac9cae69ba3dca48aec045e0c4d7028f73c254433f921b7240761c661cf8e774a21da249f7758234cf7607fbe00000000000000000000000000000000045a3d80767d116e89bab0e9de812ffe7ffdbc41b61f5f17ad16be5bdc9968e34f46b937c5f94f8197e21b358f44b5240000000000000000000000000000000006978b93018bfdbaef0d40f1278e831a1fc50b44fff39b7c93820a284d90b699981b1f422f751a33094ae7b5cedbbb2691b88ce9888e5dcfef70d6f960a456dbabc792571f2a98746b7d833e7fab98500000000000000000000000000000000003c3561f5d255cf1f83cb5f4df8e3b8d5655d965826d56867ae66da631f8e7d489f733f5824c36652ab00586d9c593be0000000000000000000000000000000010b3adb0017e2cea1b71680ca33aee368429880759660dce2d3cdf57b6cd7339bd8853e5efafb9a5aec3f7e22da676c2000000000000000000000000000000000cdf976e4c65edb79ff15178f6ec5bf0a77a30d97b799e433f216a2fe3eedb10bc6ecbee2974167128773cff43f1922c000000000000000000000000000000001599b60ee70d927849764880830b2e7355daf95eefef39ef61569a2b83b2bcced4dfb28047a1e5350cc87ef3cd5cf1d93e82cc1261ac3864266379b4d518e25c05bc492a8946b38b8a64acf47aeec4b8,00000000000000000000000000000000123af49ac2981e14a490a4a6792f21343497b562266a47487cf6c650769c810852e357445bc68f3460e7822e8cd1e3f000000000000000000000000000000000143e79853e4bf6d031e1116dac2c6eca4991b8a1f822fac1d352d2cf1b88df239d82df886f0b24b3e7f305286cc1257e000000000000000000000000000000000b621056a9de2d83c946b1e2c7a07f9beb8e053202407323e412c0d8f60123cfd5818067f57034fe1b1b5a3d1bb285a50000000000000000000000000000000001642fdff2c52d58d38201cf44c31e00df47ea1439e9896b5ac5e9372482f4ffcc698be46c2d375d57a72fc677a9fc8f,240480, +0000000000000000000000000000000011f78204afa05c3717d3908dc7e4356ef96c426ef380b6abba3a5616d9ee01addeec3369967ed42e030c4b8ff9939c4e00000000000000000000000000000000175a19c86e7eff0f4e809a5105503ed223fe327ee4617f7f51257426fe408373899f39014821292a75e4cc4eb9f7f31e00000000000000000000000000000000052130dd3cd17840385db424802d869d7eac781365be25ba401b7b0e4025353c8dbf59e5997b5aac74c252192061299d000000000000000000000000000000000457f4fc7ac5d7d4fa07e8ed125df4c4e950e6ea926be9c04b6df3c3699a10e99af7ea8546b8ac70c3003468a75c821ca2a1148f1ba719b2da92c418fd137efe21a44dd4cce013ab36e57d97dfed92990000000000000000000000000000000005182a3af2b52102e09214d048a1a29d1e10b7ace6afbc4e6b1ebf16790be372dfb6d65cb8fe08c3dbbed8c5435a59a3000000000000000000000000000000000f2a1261463c09a88edac443ec3cea8aaa19659e8b7ec2e8a403dcffb1e50ebb3d07217a9ab057d8d097c075609c13900000000000000000000000000000000007d6525fea8fbb685fcf89bd772d48c406aff7377429fb199f27c3c3337f11f8e24c4d81c9026b469600d11e8cce51be0000000000000000000000000000000004b6d6102debaec16c34fecfeb444e7ba573b13b83ec375f14d2c541a0d1fa528ed6599a0eff4f8ca527c5baa579f762fec5d6167d7777169348cf81ad3eab5153f8f2f18fb5935c5ee5e3a759f9b5af000000000000000000000000000000000ba1f2b2c3f1c57afa0ab647d32af5d036ef18069a4abc9795dd9927ea274a718b67373230e337cb9374ef73b5e2303900000000000000000000000000000000016458ff2f5d600af9d2983f535c965a2a8aee48c0d95095bee642bb7bbab8bc87e3e7c3b52a787c53f0d1e00cb4ff32000000000000000000000000000000000d11324e812cd4fa65d20cf58f88a9bc9407657834d7a92f80bfd32c7085ffa2f9f78d7e18c1405a03de939bd0dbb06b00000000000000000000000000000000144a0f4d50bcb16942d22a12d28bf34d2b4c51512a3f11c130f1566aacbdb63ec3984df5569f41ef621f50d138883d0dda609e1c8fa42a993ff355a70d44dfeebc71a801daa36acd437daec5d7b645d10000000000000000000000000000000000d7b138fe445d7b7e130134db653022ebb389075ffb62ff9faa544cd0fdb9e78e313d0b1cb19bd812421d38d1e9996d00000000000000000000000000000000084411aa2719b729a1e299fe8a710f767009060f1b2becf2aaa92efdeea8c91239aa5d2504c6e7ad2e3f39d89ef00c1e00000000000000000000000000000000017e86dc0146c9bbfa5ea1e48f49918167dda13b31ba73311fd5cfdc12845b95b9e90972a9a4d36203be8c5920f8de5600000000000000000000000000000000150e4b6fa9cd9a609241d1de8a637c6ab25207bccf8e5eff4a97ed633b67826135172880b118037649407a3e1b1a0661bc5f7f5d096247ababa51852724ce9ddcc6acc7ab6180beaa1cda97dba94b4ea000000000000000000000000000000000ad7430b7778248d63a06e26119e5600ae97071fe8827b24440587e8bf6887b646f342741af69d20e243c9b45d7dcf24000000000000000000000000000000001230cba1a5a66197875240fe00c59b796ba1db5ea5653cc76bf43d6adce0db3a109168593beb39bb45688c1d124b9eb300000000000000000000000000000000144652474c58413cadf9b31715152052b7618e7093e931367a7ed0340e66d84c0471b6ec178e1730cf10e749e01815780000000000000000000000000000000009abdd0210f25d12146f2911a60035867f59cc341b35c73bbdf8f7a5a90d0bb6566c6ba0e868a3d62d3557436190f3f63222b41a59f9551e91572ae00582e1e41989ff5f8e2cd1ee1a78f55c2b28ecb40000000000000000000000000000000018ced3cd0c169693368fcf9c3dfc49fe248f0b9b5511e9407b8634d8ed7b54ae2dc4ac6ffde8b3dea70ca86ddc89449c0000000000000000000000000000000002f6b227e699dccf7ab1e0b1cba4cf9f05c4dcaf9fee6cd94bbb79f42bc9598fa23eb2c653a7654db73feb511b24829f0000000000000000000000000000000019785959766eb8b00ac2600d87240f2876e049725680f4504f59db6642ff8f82d4e1b856929643906c3be7807a2443180000000000000000000000000000000018285acdf25a475b37ee4da872debba4297fc8731eede6b22be3b0dff12117634de44b84a18042852ef419c3ae18a46b7431e5c1fe5f8d38c759bc48e8207695a3cdf07d4c1fd02f1009088539085da10000000000000000000000000000000019c7950b01e15669cc1f96fd94957535f32132ff6a5ae788f6f660024c332593942bd3e9603f862756edd4f3ab17b20b000000000000000000000000000000000bf3a6bbe10ad91d687a135f4863ba0332e9b04271d437a6a4770056e6b1ca34319dc895f9186482bbbc815aead03392000000000000000000000000000000000a3ef4d4f7a15da04a91ff079cc40040993a90e9ea21f53e31f7dede52dd513a97ece780374c5f3aa8c8b2e525ee31d10000000000000000000000000000000017749fc7761b06432632ac686d93484f08407504e58b04b3890cc2101f15d21f46ec0dc1e9028c8ef8df10f9ae929887d474e755f6ce9045baaed65c80f5a686547089e8cdf4ad2b7c2ce7c255cb5c730000000000000000000000000000000005a36af876edfdf26175c185c3ef005530e02474232ea659f5cf251c5de5721f1b44a25714967d283525632789331d2900000000000000000000000000000000130a6f5edf94736477143b1efc316f131b36d9658c484821be08e7f5b9c93f60cf34042858664db0ff0240addad8782f0000000000000000000000000000000004fedf49e6d49c074dcca96c01607da2105d8053861b4c677a69cff0f82e66a2a63f32f3d9fac8e6c844a1f77055bf31000000000000000000000000000000001528541de3a9d4a216c0e60c31d2b7c7cb91b839fc31307cc70f18e9b87b92bf5b9a9dc4eaacdec6e6bf7791e547d8a2976c8775b0eaa1e4aa384d222efc476305c7ea2d625cf5c67ea4368d7a9fccd10000000000000000000000000000000013faf7b2b8514f77021d8927a3b63bb7c57785e581f40ca82882341c13a9daf062a26b668844e58291366ea6ae2f179f0000000000000000000000000000000009060f9e1047f15f175fe95cb0914f4941bcaf071f24e856eae6f36263c812689a9217da277613c10c8e254a0933c80800000000000000000000000000000000154619e4ae3901789ed3ecdfc76069d8026a3e2cf142a144e8b58482233380690e378de6b81af0ed9b6536da1cc2a30b00000000000000000000000000000000040c1bce922503699e1fd5ac67725f11d7f9bb6903ff9204412f65355be69d73cd7330a3f7bfcacaa9b078ba6b9a9f839db274233c46caaa9c99690fd00fcbfa4eaaad7c41f8ae84313448c787165f6500000000000000000000000000000000103d91916d537379d6d8717b17ac5b7e9fedd98c24890b51c027cc086458259767d989b3ff9d6adad72bf977e4d378f400000000000000000000000000000000159c01ee371622378339518217dfc0570178aecc938b4a008dee1a6661ffa605c0f1472c107558ea791e0959d7dc1c70000000000000000000000000000000000ea3e10cbc3a55ef2dc7bde7a2e80666557e9e8fd9ce77e2e92c2c70777afe43c23072e263e1def56cae4b6d3772db96000000000000000000000000000000000cf1db638331c47f9080c04117ddab4ba79950563810d50e04af819f14ae0981f6e1e94a635fc90226c8d7beef0844354ac9f9ed46ae5aca33af9ba1c0fa5a2138d4ca02b962fd1d02b4636114ce19970000000000000000000000000000000000095c82c58182ae9a1ba14421c2966d687f7225ccd192b24097f997b471d13b46a048202712cb2d8b1be0ff40755dcc0000000000000000000000000000000017410aca05935a06942f673d1937a593423cbbd226f6707c5922306d28a60396baa08a941122dd4c583331c9481a734f0000000000000000000000000000000002c1d3a1262ce8aae42a6ed10d8020c31a468127e1a59d57d2d409ca9d14143d9fd21353b260edd8b387840580698846000000000000000000000000000000001512e29256b6b9f5a7ba4f79dde2c915b162e4881856258ac2050f02868842381518da4ed824243692b131710d7201f8ab300ee55e90ac046dbd772da788dacddf72c559d9378b39507987a9774301b0000000000000000000000000000000000ff83bf1d50fe35bb3d1bcb07b02d78a1b44d2e0c6bf82c600feec3897fae8b93c0ef05006c1322af0a732392dad86e8000000000000000000000000000000000d70c4957cb3615027cb950e4224d41849b9ff1b435ce936384fe17c4d7bc2883fdbba5123ca0c0c010651500557e1be0000000000000000000000000000000008b16fe9af45fa913aa7e5d01b5b58f946004eaaeeeb493759a5bc2b192d2dd71af24ecc5c6838b5e267ec2dfcf5c17d00000000000000000000000000000000038ca027c985af3cf60cda13e770fbe4919d3a5b413763c8ad155cb4903312822366eb986f2ec9e0804594ad4894e468275b22db781d5e8fd07f36788bc1219c4b4a13554c28d92a381adae111b265730000000000000000000000000000000003a313d6d41f1ebd6b98b2061a2d85943e52d89e4b8680611d41ea182385e154da24248faa1563e6ad79172f91a8763f00000000000000000000000000000000038d9388fe9169710e1a205ecfd03f674b47ba2275794469dbd5f193a55e00765c8eed026363b41afda417bdd8910ea60000000000000000000000000000000007a75f53d9b8e5eef19ef6f5fe8ce5d5308a1a7d02e0bf46f91a1e0cb22555752d82d8471c123269050fd8f35a272f600000000000000000000000000000000011f313127a036403652fb2f83c5122fd12c362ecba2251bd6c357a964dd758eb2a2c3053dc668b9a4bc071898d45cd46ec69b95dccdbf193d9ee4c51615c0b7be5ac6bed3f2559f0cb2755c634839ce7000000000000000000000000000000000a43335eb6ff3bf2daeeb1eaf44c2782eeb517e82e55203a247b7a396e26fdf85f93695753c52c68819b58c95f361820000000000000000000000000000000000c240b7896b3dd0c318dc9ffcaa001d20bff288def3ce42752d660fd705e1544e292a5a0aa3a9a80ae91cb47cb938989000000000000000000000000000000000e5195bcc4ee8b149a769322165b6a3157ee7d04546643390adc812b6296675dbd31168b268df869a6722a7c8f51c79d00000000000000000000000000000000004af7dc8a5c552f00d55b996d193a9571173ea829eba8fadfa7becc2f4149ee7c6c4d2c8c7b1970df33cc56e450657331e2bf1816a84c190eaa850ecfe1a9376123e0d0732d90ac3537668f8f18b9f70000000000000000000000000000000007860c3403607d4e13f738357e18bbcc4df10fad4aa25776f84d3c2758624a83aee0996146ba17a812384e1d67a7c54f0000000000000000000000000000000002169148d86b1f7a0ef75d9bd19b6d7cd66da4293fcf33fed9241544dc2564d980161a6bd959f3b43569312bff7a23cb0000000000000000000000000000000001897c121cbf5e82424cc50078ca7143a0c670f1217a9180cd2a4700e06aa895cf84c0af94b7c04bfce047a7d1f8443100000000000000000000000000000000040c1a0c4257f90bd83fece3c9372842a148132d2dffa956729e741ce996d229aacb04387d51a72630329230020b2235f4087feda4bd8205d96cd0bf6eee44c27a6669d7ae8e16c731849cfbb2324e1e00000000000000000000000000000000058c001ee1343c6cde55bbdc4c538f5d14b0e8c199fb822f080ad96ee764bd1908f92260ce60cd521919f223301ba1220000000000000000000000000000000000f8943c35e7fb8b58963719f1b9820153e0831cf81dd208176af7527781ceabcf6ed2e2276cbf374e0525952bace0c80000000000000000000000000000000003b43ea8c32a13c014b05326f7b4ad5b5fc1bb2367866a69373ba91402f4b45409c6d034898e8b0ec3b93c2878d59b72000000000000000000000000000000000101c371ab4d57ed2cf17dcb731117b1986bffc586529fc1edc630de1c6f4fdff1e10b0895907bb81d2ccd3eaa96c04a67b81583fcdc9afe5f35974dc9b6310ee8e1c92031a49c08b05555fc0d33517f,0000000000000000000000000000000007152d538d0f750901466c1ea34a16e7b0e1296a2a3740568812587affa5c0c76ca2055804e24f3415a403f06a717c0e00000000000000000000000000000000119c0c282d22a01524d87eb850789c4816e7dafdb2782b57c32409b1016615beeee2067443835466283543773cc8b427000000000000000000000000000000000d68137c3df081a519747c044950c3231ef82295eea5b7040843668195d4549c8ece4a91447e0ec89530bc51277535fb0000000000000000000000000000000000d81a4fa2d32ada3e08a7bd4471d45a6afd2cfad5bbfa3d378b1df2e0749f9b05b465be61cc9d1a0f4abd56dce03dbc,240480, +0000000000000000000000000000000004b153d6554c9879359052717457179d8318f8127eb73edc1d6ac2efb1ea9643c4357cc813d1df49730b77f995d6d449000000000000000000000000000000001533a450737b4bf8edada15446cf21ebf82aa7bec7943025dccc4784e1070fbce430699cf3a37a36a3ece692ce87639c0000000000000000000000000000000017b8afc300bd70a3221120f6fbc37a8e6158c48b476f00992c6f41808036765071bd0a76f7c641443b97ba523153947e0000000000000000000000000000000012f686b4759a3d5db2325508f148bfd6217e027fe261d3ed7b8fd0526036044dfb563e1c4399ec266e140ca372120e289f3c65c2c25c6c37aa45b1104745cb8ec511a165ffdb7e304f5478aa3add4d7e00000000000000000000000000000000061c8288b7bf2856c075a176d1086fc49f0359ca3e7c1aaf5f151e6462916a4e1b69b6decb18823759b620f7593079c0000000000000000000000000000000000877af18cfa0d029e7c9a5833b346c7fdec06e54d9641d3953d3cae0e8912bac7c990f8864c2adb6e576442c634865b6000000000000000000000000000000000331490f42993de3ce7cdd53afb4b310f25881710a23f601ed95961bad4e9cbf57d3077803908a91b65fc32d1a84130c0000000000000000000000000000000005aec079da804fc572bb8eb867acb93a24ffb6611eba920d2ce799c4c80cd8e73b3cefe989885167ab685365529b4f2a8fd50c46bade91a13d6dc5a06ee62e5e89e0ae7ee885e5516ca6c2dacc36f6f3000000000000000000000000000000000ecc7abf4f6f9692cf3a118cd01abaab4754c90d1a59468d402bd699992800c2994f47b2094878604bf7825f125133e4000000000000000000000000000000000662396427cc596458e880bb8a43dbe046deb85601e3c64556990de36e8637e8ea3b142a8195762910a83609ca311c3a00000000000000000000000000000000198b9c52be68d073910f5b26bdeada9e9b308e4541561a8ffb21fd8e69ec9d93b01ec966fba65be27ee53d4857a43e120000000000000000000000000000000005201975985cac810248e333ca714cbcac0ede46bb915d8c857837c80c805898d0f9ca0940819878a26d269faa02cb86128db1a106328916ca5d63c0b5722642febed26f00a89430d52ec3eae25a019b0000000000000000000000000000000004e61ad4818ea3c98ed3c0d247798a1a6ca6bcb35a1cb60bda9394272ec092c385661ab93a42af439f1b55ee8b9c0cd4000000000000000000000000000000000ee0b71ebf39e4009bdb310fe3b555cdc860abd47a67bf481ab36b5ed0c00bdca8082abfb75691d45e10c2f2d777be19000000000000000000000000000000000e9582e3b5bd580f3ca7ca1f58e39379918f2d04b82b418a91e133117a9703f7df4aad30d48a47e29aeaccf5b8e33559000000000000000000000000000000000113b4c068fd040cd3300a2d1ef658955b014e571e7c77594edd31968037c1fff241da88e7a88669a569462564e28cd7d45665afb6a864893e389511a0f7b2df74c9e45a86fb69f6bd79854e3a88c206000000000000000000000000000000000d8b0bf633072f19db61ea263a1dbeddb326738396caf1196e31e2cbe99a68e8c70f8db13cfdfa4fc4494e54c1ef28210000000000000000000000000000000009ceaf2a0c63604afb8a903195933fd1ada0e5314255be3d74a95679c7a7845785e22d2c0c206f3afd62110ac9810c2e0000000000000000000000000000000003a135b405f46ae3f5cbdd63f4964cdc5014c9f3405c2062ba17423dcd22b8f2011638d520ce0ec7bb0cb5b03e8ec01700000000000000000000000000000000066eafafe1cea67aa6de267c767f49d4a3fd44c28d45a920fe9b3cebdeded883d8960f5e9fa4cc179246918942b1428d28f5fd09c2c1819adf8e6d0e0f4e4681babff46757edeff3839e9691888c13220000000000000000000000000000000017e37a2f1c892fdc58ac3f72cc5a5e2b7c0c87333afb06de89f7a84b1267695bcd452925fb2f15f8b7b20aaa85a6b5650000000000000000000000000000000015b2919343962337a41b54076d6735a093190e1965faa33eee800f5eaa43c35f349aaf93f19977b6fcd19360b27edd6d00000000000000000000000000000000161afb1494482f953007038557c847e2cbf84c57c5f5b806e3b0178e71e3238305f733943bea7ad6f2bc290778638e6a000000000000000000000000000000000c27a2170fa584863697292e626e2539aa15f3c8eee65cbf1f1b7ced6297248d059fdaeb9c955437a51cb016d1ee97c3e6e61390ef88f20591570ec1fe71c3ed769ee8e234c7cc7303a4cdc065717736000000000000000000000000000000000313a30edffaf864d0f1c6bdafd7d1e563cef434d45e71489e9f9e4cc6700e44991a99220f53f0cf5e7de5f6e4098bf20000000000000000000000000000000010429081ebd2ed6fa07de6ab0b7bd559a26a43df99fca0a2252411b4554dc69821ccf3df1b05114da84a616ccee0a9c800000000000000000000000000000000131a31442f80da4621f7691664e9f8b467988fa039bd086a2d64f9810878b557614c27745b2e821016f648ec36ee797d000000000000000000000000000000001160cef9f5e4d022baeacdf10b3bf9d7ed5e50627a99e29df1be3667cb872b2af333f803bf426314369b490c2eca642aa83c5af2f9d10c06552ea7d1749cbfa7574b238433c1c0e4788efd0cafeffa57000000000000000000000000000000000b20ec53bc643bdfda1e3947b3773d748cce1992e2ae27c6b7460d90d48e08eb9240879a5a7d3dc3189f486706438dbe000000000000000000000000000000001024bae4a7f71d3d2fb8246e82d95664c4ee8bca4a380c293ea084f749911f984aa4c6f266ecfff69c4f57e20c0660ca000000000000000000000000000000000b58472d81a9f16d2fe7af87170ca0c8c51dada62a4b2a713cae053a0066fd268283a785ccf269e05d8873cd686d2f4a0000000000000000000000000000000016b68177bce92fedfbd90cdb752bc332f46fde6673911c016fb9cff4912d79d3267bf629c33097cf8979f2b913c0936d4bcc88d85a5a8a29dfad37ba97ab3a5defde4ec356146db8d10f33bfb36ddd37000000000000000000000000000000001030d5791bd2a27469d242c62403883ca167303d907839e608acd827b4118b752840a4eca0acbe5df0b447d6651e517800000000000000000000000000000000106d65f922581237f779ba3e66608729dfddb2c487bc927f34e5e39707f2c8a82e8c96af68e3257c7a9876a05a1b01d800000000000000000000000000000000115bec40b8fa914305b1d5a85b65f0811517d36839494ba69c929fc2422f7e8b85d78df4e1687ab0087287eff29c65430000000000000000000000000000000018d78a75ec057cfcf179fa2ffa7dba79cabf6525dabd69ab95b23dc8f293aa077e46e562caf447dd0913ac9dd60ec76b29d5d818e62c9791c320e01a3164e142d9804e9caa7f96b4c3b76baff38ee2e600000000000000000000000000000000023f7736d6de94b08d9e9efb6f32f8c17cafb1e1b9b1f3db6e58df72a451c3225d11f4304eb0d702b07a7966f95a11fb0000000000000000000000000000000007b3204f258c873a6fcb48d0b36c98ed5f99b424cf4f92a028174e0e93db2af549648ea95fa8c7bbb42b2a10eeceaf8500000000000000000000000000000000165d6e769b7df91374dfb44e18d43e03ae12ee10d8a618a20f67332cf96492ff514eb7de06ea53096e823770c686c32700000000000000000000000000000000012e69ca1e106411165c06ca15988362de583c4a05425e2f4aba4c14cef6d8d04c52c87b4fb26b1557801f55b02ee8ba971c8aad41e401ab6c49dccba28ef26acf4961978e94e633b72c581ac03621e4000000000000000000000000000000000e8e6bf1c8837c31446959242285e9b85978a5349e1f0b3447e380a7bcd6bce758bc6192cb880f9c09d6ad4a0ee36eea00000000000000000000000000000000199b361ad0b435d7a66b46a43d06e5898376a6c260b68c965f7b186fc75d2f321bf883646e7551eaba03181907d3aad1000000000000000000000000000000000a76e3f399f31cddc4dd4bc22187a68fba31fe2371291ab515d22730d320ae4240911c755750f687c7d26aed09da4210000000000000000000000000000000000cbc8dbc004b9253ba91b2238c92bbf7883360c7ce39f6e15592a8668654950a3fc5a94cfa97f5ddc60add40c32a3630659ff910eea5280dc5c24c542516053637a5dbea576a94a22acefc902e56568e0000000000000000000000000000000005448b623604262a9cf1a9a292c36738960e132bcf0ec8e61a510008c2ae0b51b31da25f2bcf0d7c0d4ce15b1d7179fc000000000000000000000000000000000b61df56ef891bac07a873571f68fe43f79438a31038cf8ff97393ba44cc47408e5a6d64e9ebddf0195bf914f141e668000000000000000000000000000000000d196ced22ddf11132bbebf6c85bb3006a194cebca975d74992ecfcbac546f0f25a39ed5d6100768c1f1a791f3604d12000000000000000000000000000000000f727cb947849d2d7b046218f084283e5513e8582229085f9f98fca522879543429cb8ab435aa3dbf01b68ed258d82c112ff32d44eb442a711250875d86a401d0dccc95e5ee39bec71738fd812d487c6000000000000000000000000000000001044bcb16b3384a1f350cbd62bae568c96932a364c16b34d91ab9b1035ddee93a02920ab4dbde2c6f254031909dc3a450000000000000000000000000000000004a29aae48210289e5f588aede0756ddf60724b8ac54de5d9159ea834d5da98b7a9d09a6f37bcaeaabc559dbdde58b6800000000000000000000000000000000112ca953b5ba652c715fd20e3b85c5bdfeaa7d577aa49aa4656d142c9c2afa3d8aee151338f59a199f3c0c3f6a430d6a0000000000000000000000000000000001ebc7a17da7809f9e744cf7f13fc437de34d3472f022493f58bb979e2282368f989ca0982098a7c377498f1d8d32583666b820fae2459b98f9bff20275a3c96ddcaf98a78f3f5fa4a2c0a26cea79352000000000000000000000000000000000e7c3d6bef4b1723479ab6724cf7858c221993357b194e5055db96b8168f8d78f72aaa4a2046be17ae9a7eb00695ec510000000000000000000000000000000015e85e85cec08133b86738e1f7a738de455930ffd5073997a1f1692c28044ac00b634b90eb24938cab56e286ca0dfaa400000000000000000000000000000000164646a4767ed69f9280f96be9a7f988d17c187162554239797436a0bf4c4ffa7e4f8387c3d2406a7528c021f56081df00000000000000000000000000000000197b1080bf3ac3ef7bd6123a55f20f1002f366d4efea9e14ed92fd2ef147e2b5d9251a302a85172235438bb2d35943a740a9181633a146d7f307ca7606cd45b8e721c46b955a6989d421baafd8e40139000000000000000000000000000000000db7cfdfd58a6ce9dbbcd5d65cbf22b5e1a81acc70f1c85651ba962d61fbd7ad83e5524fb9aa019c6bd75dade96f7d4e0000000000000000000000000000000011e269a390fd15ab1d52d38de78ec97eb6202604fab02c4598ecebc7635ac91ee564e751275a485fb43b933678f11fd8000000000000000000000000000000000b8597a00d2401664405dd1fc7d69786353c86cd4699af981fe869f266f9087b00df22a46ac34883173bead870798f650000000000000000000000000000000009117a49b3f2a8a850a0179b558319bdd19a5f1f4a45af0ccad0890e63b222d028536e9bb612093cb3f1068d262af90d662ac80797c633d8b9c8907acc2960ebdcb5bdad82d9fceb4411d5173b7411fd0000000000000000000000000000000002e1311abb9df5e4d76959276b6f725f13728844f8c7dfe5e25469cb95c6937a822282b3baa38817e24a6219601132bb0000000000000000000000000000000012820e6ddc50e19a8f98c15013ecc38901a4ef8ec2b79b85c7f7913da24404afa1c79045f1318cdf271028126f9420a5000000000000000000000000000000001794e653c5673e51a3ace631c1a1265dba07fb74235506b2149d42b90eb16afc26ec0ddc54d03f7ba2dd6a2503971fee00000000000000000000000000000000112479bdabb9dd057b325563c666910c01ef66adf47aa32f5a41bc9cb8234750985c266fcc329ea3704e2b8d9b15bfeb59401af15d9b83e2ad68cc8e2ad1508391516ba0b26fcc5ec6eda8b318a374b6,00000000000000000000000000000000168c90045dcccef35cfe8eb642924ec2629db886366fd9ebc082019690d103627865f0dc39ffdd2167111f68d8d03c89000000000000000000000000000000000b6f0928a32672983664ad15252b3f145afaa04f11d5f43a6169a2fbdc0b0a04902a183b25e38987c45579ac6d11011f00000000000000000000000000000000195c4d796989630f85df4594eb8353d44bcee76d82b73ff7a57069466337b49b875b3c1418d22d79716ffded7e704a6c00000000000000000000000000000000032db644ff8ca6a3b1ac7bc51ff783ce0cdb7bee8b2c21dcfd3adb56a3e210390756211f22feb3dd4f706e13e5cc163a,240480, +000000000000000000000000000000000c675cb5e90e45300619be91c752a5831ec47b4143c28330422cb57139882e776c1c5f000d6032cd87c16ab3b1c08ee0000000000000000000000000000000000aeb4e78724d46a55e5f101564bc05e0a1be1d80a781ce8a19607322e82c7ee59db9f53ec34c70bef0766a5b965f54b1000000000000000000000000000000000933e8d7c2420cc553afb1c88b5f64c7a39f78272b34b5611972dd5ced3f639ae2ed2aaba470abe829be6ca6d666ddaf000000000000000000000000000000000ac0a9b46323ccabf4b2024e3a5b4717cd8de9ed7de8a78e33a38037f802651a4b43380a746890d93289d547d94b61bb9c351c585d1920b8cfb89a5bcd72fe041b17f7bd091ba505b287778b0be4e87c000000000000000000000000000000000196597114fbaefb8108c185a85d0fff0f6bffecf056902b22d61cc70b49a747bb35638f5b663830a8d2ee15df9fd5a1000000000000000000000000000000000616ed44a5fe69da994e2ada03a1e09065964223333229f5f30ba7a452830848f599ee21810a95e3659cca495897bb710000000000000000000000000000000012d0631e524ee9d3c776c79137499f8c9fb752ca93e92497d89973033d60971da23f672f140c1a753b4d00d08a00babc00000000000000000000000000000000111159e95d131c8cbe8df75853fe9b3f24013daa083e57c5b716e77f6fd3872dcfe0156382c9d2778fe886621be19973ec42da11e95cebbeed0ebaecd31be24801fdec8b81f4046fea52f553c4e7910b000000000000000000000000000000000a7d253487591fbed97381b3a430404b87aac04073e5931ee0bfd9ea6e0d38a41090c6dc7f6a591336fc58a97a3bea8d000000000000000000000000000000000647c67f1816ae6fec39033c3169eb1ea89e5e20e755cfdea33572d6397e7e87635c7439eda4912361a32de313893206000000000000000000000000000000000e0cbd54634d070aa3c7a503df1171a5cc435d050def17395707bdf7a61cfd539348ee5a4c29c7845cbf0e5df0531f530000000000000000000000000000000007d006601dc1e092a616eb470be35b5d32742dc6a2a4d71cda8865f071dbba9d8a3a8cb10b486253b1633e4590e716dddfdd8996780460757702e34ad98f5f64a8c1e0bc8851d6c97f02749b8f77cd03000000000000000000000000000000000c502a19770a892b2fa1ba59900a36c0ed054a8bfa0c4e32bf471b90d0da9edca6c06b133c8f12e233b104262a81dbe00000000000000000000000000000000011801f159086d07833a691182595a42645513d316c084b2841445c4a63c6bbb402664a9a9a100e8d6436337ecbf398bb000000000000000000000000000000000f2b9bfd8ef6286bc41e9f47ffdd3efe437aad553c9da02b3c22ca04b5578d634c0543a07bea966bedf345562218c2190000000000000000000000000000000010be5ffe0cc9f580c74e027aad09c213189fa4b7aa92160ce813b8d398b2e2803294e1a730cf5c891cf1546c6bc91414f256ff23b38b3b986a62074c5a3e05e86ead9431fcdeb67512f6d502fcefe3c300000000000000000000000000000000132cd5220c125759a18c31313592eda774247f97b5134111b01ef28dad5c3ba4d3f13d1af9076d663f7e217258a6fdaf000000000000000000000000000000000f06a5b03daaf8f92f9a302f06413044ca0dcf2be81d9cf016120312fbd41b273650fbb542d419595fd2815a809c4b960000000000000000000000000000000001b11acf12cf46e40554a1d6a833566cec1b2750f3f72ef77496477831d5933f477d59463ba19c03dfbbbd02fcbb680b000000000000000000000000000000000b2aaf91827ba923c8a1c2fa1d6fb92384c9f48f8f77273056b94245114d1f3cf66fdcab330673ceb2e9dad6c1aed0d4c01b3c8bb0acb17198bde9adce3b0f7ed4cd8615f837aee928524b0984c99d0e00000000000000000000000000000000051858339be99d1271152bb390e9a2ec0c0760b7686804ba072c46db3cfc4472404a9f87d868a28f2aef16c9e989d6e90000000000000000000000000000000019a33f21d0bb8303f540bf26816f145360bd1e9a8229dbbe7981f1cb5b099e814f2691fadbeeed8e4c4b772bbd27e60600000000000000000000000000000000073eeb49aa7e601732dc0888ae6b0f5e8dde3d97b818155221f5ab8c599eda75b25c86f15ceecafdfb9ee4abe3419e10000000000000000000000000000000001507073b97d494de26e70f18bd1723d931cd2a88903ab6da2aac3b80fea78ce75caaa9b99375780d759fc4a1683950bd458f882b63c99ada33d8215111a6df21c8f7424eb2fe9f429256201d099413c10000000000000000000000000000000013b5422deb0e80bec71309d03fdba007eed33c3ce0fc6d4f9a0d063136b3b85a6fce90ee59956a9b91e1caa519f813e8000000000000000000000000000000000829a11eb50f3bb1a47b72cfeec9d1f63e02b9f7b2592174c481ea7b72a121645ecb36b3d1964b082bc6c7efb4483a180000000000000000000000000000000003d3aab53814f55fa97285af2dc6d32cfcf5a08032d2c15ac83ea036603e08a53e0d2b8d93a25dd969937c113e78064a000000000000000000000000000000000c938a68688138149cda64f168ac1466c401196eaaa44a464d9e345c422948767ad1e25d1ce4cc5996ac5d5dab61516b804d7a35e5731b111a6904e0998d90ce86cf612914152fe3d2fca0201a41166a0000000000000000000000000000000001ab96f0b60213855fe221fdbe2fb22da6bd6cad8bab8ecb747c9528d3511976236ffefb34afc462abfde13a99503cb900000000000000000000000000000000182fb121778cb002be3f90e2d6837a406edbb609bfae8fe59837aea6f5f6131a10791f92188958b57059b7b9a9d3a24500000000000000000000000000000000159cac269098d223ee6d145a4489f05875b6a546767c023dbea62b3cfba9f8518c9f4d2594d00ceac325f3d8ef551369000000000000000000000000000000000c0d2e4e7aaedec7e53bfebe8f7fe5115720e58768469b6673cee3473b08fb8cd1ebd0514689ef65d78d008889e3ed296f1629a801db6bb4066588ed79f75223120728c3a57f7129d88f7f877149223300000000000000000000000000000000079c40bd7fd2ce0f48806dd2e88850ba988e5adb0cc5120977da8110b07da264318fa034c0c213590a2616f0ebe40f21000000000000000000000000000000000905f41389be39361fbfe7641394d30870a079f230dacef89149fdcf81a4d1e0e10b9fa1c0c3ecadced9aaa19fa9dcc800000000000000000000000000000000192f50e08e497f902403df40a504a1b4b82f1957572a9ab7ef97f5ab93c6fb876d8b08f318244cba95ad5200fc2a6e34000000000000000000000000000000000be7ef45a14871dbb344a69c4036af4f994a22ef14540377d1144a92978a23c2d678cca47cbc18e8c036714112d11f7cfe80ddbcaeb784e24975b9a42801c89bdfb842cbde5fbc0c3d70c0632cfcdab80000000000000000000000000000000018d7410f0105ff03cc4ddd87a6e0b65ede4abd4609db5ae53720851c90255757e63c6482de4651eb1d3669b1e1a2f8d9000000000000000000000000000000000d4223be106693a672da890b64d2653135119983639f7052eb32051c34113022080ce2355a93a2f64a75d8e0578b2f95000000000000000000000000000000000764780391249d0c987270bd181a44f6260ef82eb00c06585db7ef09e8b069e46c4e0e659a081ab0fda491534b71b0ba000000000000000000000000000000000a8546031e6466ae43643462b7617703a63841d6d4cb0c09ce63b2fbe2c2ba7cc35367191d0313717b1daa665bcb54551aeff13de7bcc4bc2ac1b37e28ce466805757dda29c9c743eaea9da33f47f4fd000000000000000000000000000000001922491dee4e0f29a1dc090c9b48fe8e6d70c3441e532021985932005b22cedaeea7d9ce1796808d756b740ec63f8ca80000000000000000000000000000000005b34dae0e630be6a59ccae17b44eab4e7f10be2ee700bea15f9771a724f0979798617e129540901a8aa023630a446f800000000000000000000000000000000095bdf612289258b31cc79188566ceeef6fd66858b4dc060864d378cbbb69f951e9c6bfb3d1384014507ff29f9446f410000000000000000000000000000000019f06f11a833c06c1c9227255e3a1d74172e73b06675c547844065dbb909ad66bbc150ba396fa1ba22b7183c0fe80e96c4984739882bd2f882e12660815b96d2af7812d7ae87f5be034b88e9e04fa2890000000000000000000000000000000003de8082f828ec51e23c864a16147546ff60b5fa71897ff4c120556af5c6616bde96b6e53fa673cd1f8af503070bfacd00000000000000000000000000000000093013f75b6a19b5433b3b5ff044384ddfa258420c80fe81e0424e3102cbf9e550a946e56ba9746423ef745e33da51e7000000000000000000000000000000001227cfc3e9a8d6a71738c514c05766ed4f1f4605198f5a3ad8309c0a49499e4ecd34ba1ba7677d6d90203e54d7611807000000000000000000000000000000000a635221d514e58170ef299eb7f5b679050ee24c589cc7e348b2905a3cd1b7bcf2010cfe168f5aa60f4bfe15e59b4436e7f33141d383a1a927b7645656ff7a5795901a997e27003c5672ae4fbab4aecf0000000000000000000000000000000012ff0494d308d3e7321ad4c4000e9dcd19552d5e4bce8504760f066e2fb2509279b01f1568e3c3f6216bd5328cbf72db000000000000000000000000000000000038c6e8f0fab30b5c8e4323c1fd29527845c29e1a26c70b8e5284f7ca55fb55ad4ad5389b5280927b98907132f26b76000000000000000000000000000000000aef946b9b9e9fcabb36507c1cf441df2f5ccd71ef9281dafa5e25bf07d69556e4143ab402dfb38aa756bb6ee009a6890000000000000000000000000000000015f69bc7b0a6f2cb64fd0897b421e339fcc8637efced8bf33f5aed809a38b49a2e6376d18b1bff0ef70df1b7187ad048fba4674313a9727aa4b733832a0e06666d3e38184836edf786317de9dd055cbf0000000000000000000000000000000009e8450887137cf45b04184b3c6fedac6676cad416a7646e9980dc99a6d6b62164dbdfae7cc20edaacb84432627e6e550000000000000000000000000000000002acbd87ddca9dc775da01ae026f1c60f1cb5974ce40caef80cb0d2eb7839777c1f61eae0472c7568ec9d0ebb2ec7dd20000000000000000000000000000000017c295c458a9dd995d848e3ba585f8dcdec4185a953e4b8e3ca760eb3e815e39a8ff60416e1e6f974cf7e7b086ee4baf0000000000000000000000000000000003cd8725e1cadfbd80585bf5a19e086abd631d6787403edb4bbc785d1a81f6108f451ff642f4df17dfcf94dd6107352bdc0c4d0e34d8a16b3bfb51ffc9b3c353817e8e357c608b5075c173204963606e000000000000000000000000000000000b3cc99db523b3647937b694fc23281a74010079351b2c7d1ae4cc9167917f06c06e627c4ec44af6b09f2886ddf309b800000000000000000000000000000000001e2681dd123994627adc92e6ddd3ffb006521d8bb03040fe1989e4f709e4797d143cd0bb749de33c8109933c709e970000000000000000000000000000000017df13f532bc9894be932e72c609c0386d32390dee95dda45821bedbc1067043d46007b39b6ade871bd36d39a17dd04d00000000000000000000000000000000162db4d1e956fa5b5f9ef244dbc0c6d27718eca7dcc512d1d7b97bbfd2bd00cce7941d1b9a170da6341891773a729e9ae4e31f5b6629463311b9d3c8333c33c5b2e79761ffff9863acd9d636e1a9586a000000000000000000000000000000000f0e4b606ba0a175bf57d4478aa286640ce4b5507f9f9e354fd96c45443333f6889a93012d663d78956bbfa7c645bb9d000000000000000000000000000000000d85dc4d733f0498fcb10e1e814eb61245203d6c1a46181e5a388fda2680640a1271a68d645f8fb179c0dc3107fb788500000000000000000000000000000000185b02140f6314cb62bd7977042ffaaec41ba8788d356047488004d609ae680c2f0cdc94e59a3cf90b6651298b6a81d000000000000000000000000000000000038ce717d08d367a9f882f2241ae4cc0e8a31418498bf68d05805db2e162d053a10dcff85403dc473598089a78dec27e03f256e58f60307ac1888a1b0b14b56c7435213e271eecc79b4a6f88d102be4c,0000000000000000000000000000000004cb919a72e67c31b3409c28dca1d57833a5066c240d2889f5bbdd5540ab2a49484c2462b25da197ec8d93dc8f26ea83000000000000000000000000000000000e1ac1dfcfe22ed7ac52c701a7221b542ce72bf59d62cc49f95f8ba64c05060671098d40c83947dd1952494833a19b55000000000000000000000000000000001331f6ed8ea5ec9b9e1a14997c2c9bc9af2ca305b313e2bc5c5bd35308b7b451a362f8ad61d636dbf77d1b2388702d8f00000000000000000000000000000000186b85e656e45cb5ac9a2a2009353e45749b53dcdcdad4f378431a0e4a317652301f834617e14dfac9836c3c11512aca,240480, +000000000000000000000000000000000e6292b4d3031fcdeabe62921f0c562606b1ec6139b9c43938971d7851da4945cf69f39652425396ed1b2e70e65b9f55000000000000000000000000000000000e94bc63f3b8944ea6bd7bab811c013fd61303aa7713619faab85a271308bb220e2a94b26f5c7e4136a3d2761dffea610000000000000000000000000000000012313ef65ba41f8e0a57e9b810c13d23241e8430c6ab967a1a9bf5bd6308e89c135e00e789a5610694d146840fbd877300000000000000000000000000000000165ce83af7edc9e701eb57b332597305fedf4b939f3a13a95a0bb3d119c2a9204a4991388f7fb344ec8f15d32cab0eb5eb850f01feb55bb99e4accee0aea8fe6ed0bd29b2ca942ffe09456733aff10ea0000000000000000000000000000000005a88477765bbc8290b7eb137e6de78e62bbd929ca511cf0aa701f926440f21d33bcc6ac8f2ca5de57ee8116c685ba38000000000000000000000000000000000738074a9365c707190f882780b27dbe96179224103392b86c628b601e33b092a03e24a89bb6d1d1024862a9df6fce8d00000000000000000000000000000000188c713945046771bf852155ba412b4222173b6dec8320ffd1c59e9b36943c2c18b0dd3bd551b7b1367dde3e8031201a0000000000000000000000000000000017222294bacd664ec37e9b214407e5325eebe9753b430589de2eea13360783be52a479e2b0e9c5dc4907dd5f06a7fa822b373fd7e5806d227ca1f49ab0a7f8be2b5950906c8974de9f2cb4e13ed20a9a000000000000000000000000000000000c97299d7e18f41e538b91b75e962c3ce4e068202271b40469c58cfc477d7820e90a0e91d647e8ef5fc0cb822daefd29000000000000000000000000000000000bd1e11a3646c499a240bad708f97a49acaeb653aa5bafdcaba41c1c9d32d32c516c94a3db8816e0a43d1b1eceac7243000000000000000000000000000000001223ecf82c4622653ce84460c39afe8a967cbd87a2d75cbee1609161837c15b522480c4731c9e6de9c5c392ef1db18e10000000000000000000000000000000016c5e98d3d17c723548427868e3e6d7ef4bca339e41acef19e0710459bd4732de4a556b22cbb49b823c4ee656fa354f1babde7f3fdf9fba868b5eac61337be0d73517ac3f06c39b4eaceeb27ab6311db000000000000000000000000000000001125735092842142cb5387f1ef8fb69a3535e1f0ccce59661183d3104ec1ef79dd87a7fb36159bc67bd73ad403b46c1500000000000000000000000000000000162caf579539574199d56f4e756f1532c66278a55b4f67f4f4090368260f46023543a8a18d49e8c5783cb65f93d750480000000000000000000000000000000003accbc87996a220a625e36d5cdf05d8c16fb353068ad819f94ba8223cdf6436f8d822719153bdba620a07c5dd955fe5000000000000000000000000000000000b53c8a4b62466c998327e0c5ad65818ea383650bf0977d98a8a94fa9653fba276f7781af9f5a4e99052ee3ae65c283d5ba1635cf82b25b2d7e466717f5716c33f5f3e826bdedf19dbc1d95ff0c8052e000000000000000000000000000000001264608a59c0ee9a26568cdcea8801cc8cf6616773bcb0971234b2d987563270c7b2291fa035c8f2069ac99e16c68fc0000000000000000000000000000000000e839d8d982d6663ca4552527f4fcab6ad5e0a444e7b5921055c774871601d342a151133ae15bc76c023b7ea643182ba0000000000000000000000000000000012ffd0696b7e29b305412fb840c596b66b77ac2eed936fdbe0562541e4de6b3166a9991dbdfa0f79b78b4b86f11291de000000000000000000000000000000001777ece357f82d7303aa816237a0dbd3a1398574f4061dd2fbf6b32af38a65abf5ec9bc53bb8ede932db9cfa0842d53a1a0a832e5bbdf897553c1aed35fab43aa3f4510c1782115e14e5d56229de2dff0000000000000000000000000000000002b41743325db9550c3a84af80bc20c54b8b0b685d7f84d05d14dcabed2f450b91675aa8c5c650eb81151bcfbf1603b4000000000000000000000000000000000f3d3e69d475fe1d4259f18f193cd84a90b91589a6502588106f0a577d1c1dc4b2feeec20a4fc30b3e403d6ca9e03894000000000000000000000000000000000c10e2bd1335363fe958eb50981b99bfbadfd1c414830857b5257bc8fa6e26b50989d9adb5b3a2fa610b3151f8754309000000000000000000000000000000000008c825371319f4ebd684f76b567c4e9a389dce96068c101568dc8cafcc10896e3c20202b591a344d9a1c1be02310be9b75e0582e9ad7aa4a02ed5ffa22e55570c9f20e6a24e2186e8a2a2f838fa45300000000000000000000000000000000101d3f92fe64af93468229608007f50e3406719572acf265fb8b2a7051525a9cb67cf2e46fc8e098cf081e73f3b20c770000000000000000000000000000000017b1422f8208c2521e3896820b22a65bb2a9b47d7fdcd2ce57196123c1ce43c1db6d00f236d7582795d00ef33ad6d585000000000000000000000000000000000e261500a9c64f5ae107d6ccb57fa9151f5321ef4e80f0e271515f1eaaa5e3714c59bf97b39acca41b15d90c0505ba9a000000000000000000000000000000000c08c955b6df18444ce3726711d29c2088721fa0aa6e317c52a05f73ec7171ef8bd61047174c74afa1dea804c68a28e33b7252f8f3cc6341d490c5c4464bb36e012f1b05057f405aa907ebb2c983f646000000000000000000000000000000000985cdfb3934e0484805a1965984028d6c459654a3eea6ef66e867dfc737e1bbcd92e31020d5a4ddb7f8091cae2371f8000000000000000000000000000000001998c5682209153a261bf981e16bf1f7a6f8e5e566c1b0f975253ea62439e5b36c5e5060751f21941edf0d348bafd18a000000000000000000000000000000000c8822c1d6412bc45fea05faef33c65d5a6dd13aacf1279b9cfda2a2ee34df3146d45e3434ce8e5f242e9cf7d3ac27180000000000000000000000000000000019191b51d6664a3047aeb5590df2939b2cbb115ded70fafc2de4c2e8c2a955a957375314081a8838bf89d5a140b7b915f10427f6e461e7b63b781e116a4d5136ddc79ff86b71fa754f00c797c035412b00000000000000000000000000000000156fcfffbf01ff3c8a97e7bd99e59327d38c6f7f1083d068ae158d1901808b3c9ac96f95c2bcbdf5f74b36dd8ce58d7d0000000000000000000000000000000014c64256d1cce124c01fa727482caf8ccf007e4ae00e5277d984f31a11ce584e7633565c61d47bc8accdf7c28bb266b200000000000000000000000000000000052dc9f7fce4859c852d3d9e1e77bb7887ffd35d4d550726632acab3d4303ecf8b3ec7f4114dbd590ac20d748570899f0000000000000000000000000000000017abd1e5dad7ee06116a8131c05c9b48defaa92efc636ee34a2970d701c02b6be0345a58cd8749e582ebd105c02f10a06440c89f8b10ce15806938b7ad65ece194d2fa3cc8d7d5591bc1d52d010896af0000000000000000000000000000000018ce0fb077dfefd57f7943d432e12dc9bf92dfaa30f8341397ff8906b1abdf0c02b599edf85ba1e5bb6287aadc72d7a50000000000000000000000000000000019e5e9e3b0632ec10a26b7c1ec40248a9a8b230806c38aa24e47489a8aee5abb5450f6e5679e3f13c6ec7a79560689050000000000000000000000000000000006e257a74f45142817ea8044f403e98c99db8355d626c59e1d11c6859eb0dd1dc8af389f07562259c1f85411be6cbfe2000000000000000000000000000000000f463e345b004b1364894c6e8ab5d35bfbdf6b7544a576ed6b5c5398ca2074f67e2d80af1ff5b721fc126d3afadff1ef43f1bb26469b778edd10127e634fed4d749e25b41d8eba86eff2c068c33e714f00000000000000000000000000000000174231581338fc8c461c981d4949d18f5b753d27184ffb41568f11e178a271bfc69f8c73f2daed0fdbe5bdc7fdf8ef56000000000000000000000000000000001532474399d6a73501801e5f3fbfc6f13bdaff7a3ea7634568fe82745752ee15af23b16809be18788d295e044e29c05a000000000000000000000000000000000912eaef94ab1f3b3257b26c5e8bbe3f99eaceb8c7ae8da577ef98e24f3308abe6e6005ff674a2af01b4242f8ff87108000000000000000000000000000000001925cd635d0ce770f4925a3117721e96c316dd96708b096901ee04ce02e7b357428e4364cd488eeedf76352a26cc1d10a40251ec7a7e9f7cc29948b122010d9745752df3f4a9c67427a8b58122ad4e7e0000000000000000000000000000000005c4a7f26ef0416f34750badcbbb3bce075606435ee7f69b3589e21e37491f0b4a7a98c825ec222848f5e29618828258000000000000000000000000000000000381c5f6511c9f06ea1a76ca84adab4a26a3cde13e0825b3d81899d6ad3191628894d0f57787f854aeb9e4c57fd15d32000000000000000000000000000000000bd706a5b5ef0d4ee1b679a0af90c217ddf9242b7c39523c39657962952dc14e5e07d02154e05693bad08bfb24a2b19a0000000000000000000000000000000009f28a84aa5bd39eeb09f13fc8770fa7e2e053b6f5d7e6021da77f48b9c3807ad917ac671de88b28dd343c2847c5e8eee03e5eb477506c397bc1a5204b30872085a36b65b7a8df3e0e187f3022736329000000000000000000000000000000000a8ff1b15ddcc3684b4d4ecfb53473497feb8a04660350ab84e5719fdb0618d61acbb555174b0900b32341154eb7bec9000000000000000000000000000000001464d21df798c0242ac6aaaf3c579eb66eb8cd53eb1e5ab2727298ca61ea8ca4c7cf815bf5c9f94c2b76bf659a4e2da50000000000000000000000000000000003a25752a4360c84e9353b7f1ce74d5106cbd637ec5ecb03dd0752660fe5c7622fe2d0475a4db98f785307c6961f14b000000000000000000000000000000000163601a86f02900d214ff8fbd041934189503438c557138b6ebaca8ce3c109af50ac28074223fc81d6476a3a99559ac565cb04110bbfcdf00616c2826e253f61cf955756e94dffcbb6001f59ae4a93c100000000000000000000000000000000189597e6d618a20ecf9a87cc70b3e0eee69ffa4dba75056ebae93cfc3c2ebb368532b17d9f6c06f09e44d9f101397b2d00000000000000000000000000000000086ba610e490588e9385c8b6944c2bad1eb03058e927fb2f9740dbefb779bdf669a51af88b45985e8345b8cb168c13ec000000000000000000000000000000000db8b9cdd4a9bcfc9f7de144da0b33981e4dd53744cd260c4bf045d643a4ef5f25aa19edab7be0c7f8f5ab74a4b7f1820000000000000000000000000000000010198384a646807b16e2ed9186aed99ca3197b05964dd0348086f446d3ebb847907624f4e02f71a1e866d17a125e07e93ce1bb7cf7d7a55f0624bf5c4c35327b178923d88be748a9b079720c27b500e6000000000000000000000000000000000a293f07dc3f0da0da4bee671951175a4480a719d44cad3d627878ad2f17596f0dfbd6f43acc7a1f9857c5d1f453e5d5000000000000000000000000000000000be6382cc7a00d590f2aada3b4b75f01f8538caad2ade90227ec71e5661ae353e68789807a13f28b23b17dc0dafc19b70000000000000000000000000000000015a9ad5a6f1a511ffe1891ce260ce442996fe4d8515ca593e3e869cab9b18af57956b1daa43aec98a0281143b0c319fb000000000000000000000000000000001807a4ddb73a9aee58b54bab2b878bea8429cdc91384c8fa533a8c9d15c966350e892bdfce16d37a4048a763cbf25d71e2b4c64b363efef0c5525b0337bf407879755f060af451075f4345dea7e681a30000000000000000000000000000000015aa6b865796f88ffe770bf25612ad27942213131c566a446dc149fcc70a018230f1cc8b20461ba2c55300fd27930bb0000000000000000000000000000000000c39c4f229b23c0f65ed720d655121eab50f695864959a2aa49771b848730494d14597eb85ba35743f64eda897f95917000000000000000000000000000000000ad44cafa754f06e45dfab801998c40e5a9f56e4add5c8add1d7ed9e05d12459f2efe3f3367cbcd161f524c714f7782b000000000000000000000000000000001437b1f1a1399ce2a860f7c6517b14a2db264b2602c1c57b8eb04e165205842b483497e98e6b6f8a62e25ab8b0e722f04c85e47ebe2c26e0aa25661d3353b5d88c632182aaecb35303d8d47f01308a0d,00000000000000000000000000000000077b81fa5997de07738e1b34d8e17ef4a9bde516577a4290253cc759ceaae745e10a457204b9ed0069942e0b487d106e0000000000000000000000000000000015e79be67a752a46dd0605e9d07d738c5732b2b605457ce056deaa1f7093b0bdc91b4c81c4c5462a51bc713a7fbb86c3000000000000000000000000000000000cfd2e6043263bda2b97798e1a7dcb24c63aa7197f2749f92524329e97f93dcb56429d82c1d63c067d7ceb38e6c65b5a00000000000000000000000000000000026f352d2f93e6407c59d58742dbd91ced464a3746dc1ad9059e6bb9c146dc1e74235bd54b1d90bb3399865cd3102f3a,240480, +000000000000000000000000000000001387fb972f997ed0cb97a5ccdaa759dcc3c2c7f4f15e5cc4fe74685e42cba75e778772d795847b45f274d32cd4960de600000000000000000000000000000000150b1ad31a3d434c1cbef877fde2e105d4a047dc34e3889d21544c2143e7b41b8e0024443a774bd1e09438293860a43f00000000000000000000000000000000065033cee91f5c4d429a074be3d2a8b001892455a11dc708ea73c0082bedb1cb8e8b567a6ea68a1296ad2b80e4b5b08f0000000000000000000000000000000001991ff6fb57e8cbf9d228f1a99697f785261ebce9d3c1f592389fc860b8d7a069896dd48debb8cbe0c43175cd2ecfff5bc589e7d89994400c511789cbcaea19b077e0b02d625e549bc6f2673ce40128000000000000000000000000000000000a0fa2d39d868737b9a0526296335256ab4894cc58ffd80bc6334e80d1314bdf017c8226b41ea135f6adefd07650ca1c0000000000000000000000000000000004334f7985211061dedc794ee8931ded12acd39d7e6a6ef44a749118d19ce8204d07935fe62fb2a8ea4f68f99d7c5f5d0000000000000000000000000000000018850a3fc8c851a06781511faaded1ce0752e7ef66da82c2464eccdf78c32fae306da3cfedaf76dad371cfbe012f2bee000000000000000000000000000000001296ca0b0e368429b122537b096fac77d6367988956a7f6cf70c7193b7033ce42fa0cccb8b84b9c78b16a68fd5f4c14c2c3d2a0cba111642a6354c117d494be805cad5b5c486bc47906a2d37a9cd9f850000000000000000000000000000000019deb7de7fa5254fdf5ef34fa616651ec70548187fb0bfae9f512e0bfe1f662783f06a9a99e434ced84229deddab9d240000000000000000000000000000000009c199ef916e6f6fe0677ab07beeff221a5687fa8da3ed3ad99a950b7f27159f857d1b561006bfffab551d240b764fb300000000000000000000000000000000148a211fb58b38072cf7c417c70d3ef92e9cbe22b31b2b626198add01dbe1ccfec32d333abf42140b9316312ac48aaa2000000000000000000000000000000000b551b57045365d842133e46814d5d0084248904960f8d2fb28e9623660bcee658582928703f86261cd70e95cd20cf3a530ff74626657262fb49460b2c6981155871f2eb5562581a74f968233c3cbe3d00000000000000000000000000000000185959a297a8f434cb9529a1f7bf9009fc1af3d09efb0a9dce1b9e7d30699da64e4b1d32cdb05b068621db092c1eb59c00000000000000000000000000000000106ef21e9031d108364e93ae4b5d21b0d6d78c2e86e0f8a7af27ed3d38dba0192954e8c716665333e5dcf21387d3f2b1000000000000000000000000000000000185d21efd7d613c409b6ddaa66eed70c235440974b2a9154f3711e3969061461f8824b4547c65e9db09ce875512ca2b0000000000000000000000000000000001aa46b22451afb12962bec5c6309feeb4acefdf3c98c1ea14275409b7111aacf7c92a8e024d01d4dcbfb1c91fc445a1d182ac912b005e90ab81d4f2a906da8309a69576a8afaa160fad2540ec049913000000000000000000000000000000000557370d81bc3da4c50980106b8e65ca2edc757a475194cef201c9edc0f50363cbebcf2750acab0b67e1020daf5660e7000000000000000000000000000000000462f1c1379be9bfed97a1a83a00428de63eadb6360393ba162af3762a99d7eae8549d0cee218e469e4997ada7b35cc00000000000000000000000000000000008aa5ead309fc703f6de980dd43c294530cc2b38b94d5281e9cd9b0d09f82f747a7107b700f1437f3abe36c01bcfed1b0000000000000000000000000000000014110a19d574f26e11e2163a981c3388c04854c5693e9033a474f1020d5f980666d84c60370950734c46663e194bf0ec42a002a460b51429e25f85ec4abaa580ac1a14315b1627bd52349b7b81a641d60000000000000000000000000000000015beff8cb3c79098bc73dc1ea4b240a4e0d094b3dbbd51592df6adc9c9847beb436ec83df6c55666e296fa843298446a000000000000000000000000000000000943aca2a6e57e9897ec764ee2911d9ff0a59d9e903c70a8494340cef2143895e79d3e6c03af2d6461ca199dfbd0ca0d000000000000000000000000000000000b812ba87c4989af07af44f3dfa87de119fea28ad598cb8e52247cf41bb8bd384c0d8913fc82e4cc2878065e797cc581000000000000000000000000000000000410ed148d1e354653f9d9d17c50026957fb03fec64964f2bee5eeea966b430e77f7b3538d9f4700a673fa07d0daac6b7a650dd3765032ac139d1b54ec7a5457c9e3caefa6af45d198433e5949d149ad000000000000000000000000000000000de0a9bbd63c59767938b555c7f9284d0885ca23019818c213a7d4f1594b028965da871cc5818240d155c05c69e4e25400000000000000000000000000000000079dee5649cc67700e9338799a9810d352a5c68098d0676e42e00bac31f37513944dcf47408288cb7f1cba121506a10500000000000000000000000000000000101a650e84352aaf3817b400da0aff40907aae3d2fcf16739f8ee8d5bfc62c2a0dd518201701932728a41134ea3f6278000000000000000000000000000000000f1f9dcc0b55d0ed327f667cebc052c4b6116fde5e3076dd6e447c3214d4c8847885be9547f95f341c42e7c7fa7e2c71bbedc44d54349cff199befba9531dd4120a51e2b830a3e356e68cff31bbe365b00000000000000000000000000000000148f706b4c93e739324e5db40d42025535cd33a32bb3f211add618c0e2022068384a5612da67150746896a2813a664e80000000000000000000000000000000007204ebcef495ca8232078fbf1539a4b46e89506a09dc008da457dee2792acafb6baac4f6cef2de15cbeb48bfd12bfd6000000000000000000000000000000000bf8900e48a4a56b653b1e02c3b9a7d81c2045dbf6297f1ac2acd69d1bf9e06480ea917e3a616243c3a30235abbc426f0000000000000000000000000000000005ebe0ddf4cd1aee76d0b3d03eab754664c8b36fb20ab1060900909e0e0a4abdb45bf74a0b1d40fece9bf73360f580bcbef3956ac71bfe97029b8e3f85923c2fdf9cf1ea6582b68d5a4eabc6b044c80d0000000000000000000000000000000007824d1c48bb2cc0f406e356f6e52b66392f6203f49dca7ce03ae6302ce3e8055d071cd812f97481acc654b318d6cae2000000000000000000000000000000000ae89f9eb1abe452efb7ca48f8f939d835f9a79e05211ed9f4abee06b93e34b17d920ddbca3d8bf18b96c3705c1a064500000000000000000000000000000000119ac787a7f3e9b7ee34070aac1a769430eaa8cc838f1752b573ac7f3c02a9f490de9600c856a55448598b149f5392e300000000000000000000000000000000193a3655a80e6e0b1278730600fd4f645d54947d193484131176b890ac197702333ea847317568230ad8af1280864096392f5b4291fbb18a93248e830b08fadbaad6434040c02b45cade73b77f22c2bc0000000000000000000000000000000012f66629836f0f57bdfd9bdeb2c9b7d6d5dc55c586e15d76aaa04aef06722bc8ca156fd1295b3063d738a85b3e8746d900000000000000000000000000000000097825c5db7289b1b9e640d19ecaaa81ee59e5b9884713f6d312604d8ac367634a264c316d73a9cf63358c8fb15f8c5700000000000000000000000000000000181133d027b97d8e2bef308a93b7ea2a35824dc7d01a3ed2f404fbe12ba3b3e51d94ec86cadf3da7dc9ecbaa23b411cc000000000000000000000000000000000a28a609d0bb015e375e74c087ce426dd3c20fbd8b374d3817c626faa81469cfd11a2a4e418a44f4d7ca621d0564bc4920a96f963375d7a294b584f2da699a6a00eb5781f46830987346cf4fe922a2f6000000000000000000000000000000000feca6f7e3cb286090fa3df9c5ebd10c06192fe14af58d46b827acf48fbd462f3f76d9d20670803946028437410ea52800000000000000000000000000000000183dc7085483bd05c27691c25588e33296fb610bddaac253af5b2262db38091650c1c3185d71a69d1a63770f95f381d7000000000000000000000000000000000189f9b9ea528bc2377ca3354fccf440fee059f5732dfdac320fb58541e74e444dbdcdc008c7b47681c05502f0b302f5000000000000000000000000000000000906162085e0e299a07e41b9d62668d4810b97d4be317bf376da537de7adb06de011f5f40af834593761b774771a80e4115cb4646c8996239f4fdda8c27a335361f0a19550d6eb0225c008408c47258800000000000000000000000000000000030cc52d7901d0360d10f344cecc8325412788cc30a912d5de3fa9bdab18db44efea235c5d34bab526f3b8ecee2cbb8d000000000000000000000000000000000cda35f561c19ebd85a445ce8bb1618b446c7013c07606ce58e0b5627a5c9e7cb200e2b8ee12a0564730279e75b469b500000000000000000000000000000000055ad0655a96f6dab5a432e7d2fef57a6a11113070444089df23b4b911e0994b90aaaaa2c62d06756f4704fa218f7c350000000000000000000000000000000011d22438d7c162d34802a664c254abaae07659902e1f1bfc2bdffa6c17eb11bff5276474cc3cec9507e28685f1c21bb0c8a8d98c93c392aefb64ce0c7ea455ba14c48bfbad0e3dc38d43abbc3276caab0000000000000000000000000000000001d04065373ce5d1ce47e00476f07708bb028040edb9ae7e8e00e2c6c460e1ab8b730ff510a25a3c8114c1753b7bf1ca00000000000000000000000000000000001c87217f150694a84a4e5aba8d188ebf7224e76b078dcaba4a91de6b4ab317966ce1a9267a5a27ce556c3386b086620000000000000000000000000000000003c8422590826e0999e7ae3ecba84edaed20fd7f1eba02b9daf1c46c2aec74d5fe63319047d37f5115f243ae0ddd4ffb00000000000000000000000000000000136ae093c3bd55ddaffc2494f3ba8176947cdf2f1ae408e7e786b23b6a65ba8c4131c83cd890386ba531b8637b3b042c8221622734dc6ccf6c7b84b387a3dfecafe187dab70ba373b4416ce3c505bef2000000000000000000000000000000000d09b92a559b8efe5224184fb4f43779d0b8c8f23587f4f74e2fc6fb1f94e8d2e0d591eb0702cf51a9eb402e79b46a0a0000000000000000000000000000000014ec2e4702f1ee1074cd1ad29791cf4903357e62570d16ac80c5e8ff73b255ee03a5ba070091cb2f984b2139de06a97d000000000000000000000000000000000d22fceaa48193756ce7331952a2d9a8057b67bede729e07cf8422bfc79f9ed2aeb99a9227af256deee9f8a6f227faba0000000000000000000000000000000015d9322c3a5a7ca404259c4cc7cb93dc3d46dd8dd9475756d2ce6fea527642f9230c7e94a804ecb0b4adec7963fa9cdcd3d1f427a25f5df025fa71244cb92dda9391d65b04756c41de0f67ea072c375d000000000000000000000000000000000e16fee11affc6714c7fc8fc5e7cce44d8afe645861dd2f0b8e58aa93d4f0de9b7e73020a1537bfbb0e2c8327c4aae03000000000000000000000000000000000b7745a4aaa8ab4593daa61e375d55f9043fbc7385ff229889fca514562168a4e769c5eeef4d564b41cff28b4efdb7bf0000000000000000000000000000000017f6c5b1fb00746b50ee4c7c743ae57fae2742617e5565241d012a0ef6067d9ce59be749a99886ce9836b648525d2e92000000000000000000000000000000000a3be81720e80f6aa0570c89613c78efe95d87ccb374e7f77065800590bc71d23ae097516ae1e97b498cd233221cf717b55c943fd9b11f2fb8a89f6c08a6eabe9434062354d845f1ac740e6043443f8b0000000000000000000000000000000008080a7d91caaf2470f9632575b43990a9523219d75994f1944979ed5b650be1e3c93eceeafb0875f66a40651f4c6dfc0000000000000000000000000000000007a19c4a6340e39230a33b12fe63e47bb0d1378420ec9e439f216699e512e4d70571a1670eaa6b60a5c899ac63360a250000000000000000000000000000000016898d22b2c123003480e3a01965a72de94cdfa39b20898c49e451dcf6a4727a1ebd629172aa1a1aa6897916cea192b4000000000000000000000000000000001217a373c78de9d3005690023b9e56bbed3073f13ca2408a27a3480578d8013fb9d3ee5cda95c3cdd091a5cc68d928da7b0c1d54e51b8572256aeb72bb032a5011a3e8ec5ad7e8b6e0397b9f6fc64c9f,0000000000000000000000000000000005829c932c80baa420602bf841ad9bb24fa25c61f33f5d88693207b81271c94eef54bb524aa830fdad8caf8c082bd4990000000000000000000000000000000000b8d184316c2471ec6875641ea83de4f9b7227041922415b38b07a0704d01f2585ec2701bb4ae0bf6a0c0522efc0c630000000000000000000000000000000001dd81e075620914254b38ca5a7287eb56f2f31f6f8fe02fa51488d45c7f4609bcf49972d0ae5ded76eed5a4c096939d0000000000000000000000000000000008067feba36999b58342ac54e48b0fe28245f8ac2498b60093082822d19854df5c3168dcd55ccb6b2cb397b77e712333,240480, +0000000000000000000000000000000016ed84856b9f41be9bc6c025a9b79e2968e2ee6bbc27608093256c541096e2c9eda1159e6dcdaefe783aa59d52f28ee90000000000000000000000000000000014aabafdfe8c7369f93d5472a9c6c4d426e4b02c943488be993d04ed24aef5477f6d455f82b4af78381b8bd16f42b56f000000000000000000000000000000000af34789c6c923103633e5b1b9fb447b671ab05265c16488ca7224e49db21973487a5d3de4de40b9d8a97ac9b1966619000000000000000000000000000000001123a6601c5351a586f27f8264d4227f5e1df868a03e0c3df5c148cb523cdd178f96fbe52464fdab210564dfc22b29536f082a5ffb8baa38ffd684a4a70114343a1e723bfcbfeb57d0a85ad5e592d7410000000000000000000000000000000011b82d78cd9b53b8e7e5c14a7371f34f08546896bd59d1e7d8be15d21742180aacdd01b0d08da2cb24873ce75e166bd500000000000000000000000000000000161ae0d724085a6e801edf73443cca87995c2d6b37e962db5719f4c480cb830e379fa778fd2f29e75173e1c31daccaee000000000000000000000000000000000a2c2b89d00b7d19f2b0530889905c30cecbd4ed0b56ca82208d666e7576c32a6e90cf867ad87f19e4fd367a10c449a2000000000000000000000000000000000b65c0226743b573dad7ff25bf1885e3dec686cfd5da2862ab300fde4fc8fa9b587d0f2d11ebe1f6a6770bcaf2588f8f5160286a6d23c30595809dab6ee6523d7d235114d1b295087e024b4f6ffc80e50000000000000000000000000000000012d4f299998aa897db9e3194244fdd1dfb95225e3271383b5cc296bbc51c4e1af52e849d8244f82421cd198158918d8900000000000000000000000000000000110638a2f7cdb7104de8fffe29be32610063bc656e13168921501e1614f282bdc9fccff4eb3c479a42b240a2c8014864000000000000000000000000000000000b0adbcbaedbedd376efd20a417bcce562b87b7449cac1e90d44eb05930e6f558b35ef755457305da012a231b5675bc2000000000000000000000000000000000db6fa926c7e02f633730569732fd9239bbacf2042599e79a4bee76619872901c6f4ec4d4fbf3f84143a0d17b167130ebbca29b94b6583d46753473143d13a7aadb0b18d6d35d7423b8a004991fa1ce500000000000000000000000000000000166578f3087772545c0f47fe0b3efe32874d26463e4f262be65a3bb6b0fad7d0f779808f69362f3fe63c72f24ed03d70000000000000000000000000000000000a8e61e8193228fa1825cf14e94f68a5eecece9afb48b44871c5ad62510ee1fc4e9c60d5f2529b8685e6aa13ec91979b0000000000000000000000000000000008d25d81bc4bc92508c8cade33c305c11d71a06bd46f184b05dc406f0939f0e0967b02f15b4f7f6984c9fba0644ca8e800000000000000000000000000000000113660a7d2152346500a1578641aad4dac2919ce63d01d8ffa6dad72f524c888fc2e9d2876859859e47d8e884f170f86607c80069dab2a16e39370de32df20534aca46565cf573159a93c64f1f0c4a1a00000000000000000000000000000000160529ff217934c85cbaa8b347151539e252dbb502c015e8e45c128df2b8a737866737d5cf0eca6f76e4a16790cd02a200000000000000000000000000000000127f7b0e4f9351836db9c204386a199293955471dbcd7b4ee9186f0434b46dcacd1edc02fb46b4c377c4e62cec10cd6700000000000000000000000000000000094abac17b11600d7447f7ad0f21d98c14e439c4a4a6572b00c90e14d9fc54e85045d0576f74b054d384179afc0a70c80000000000000000000000000000000017165c32410a498add8e1dd55ae43f94be234ba3859fc6b4816d7436746add313f42b1fb49e0cb6c4b7341f0acd09db841c1f256e866d218b3ec20c132446945177d518573ae3f0e739ebcc8821bfbc700000000000000000000000000000000060e503ee1c5d3eae4bc0eb30fd86303a5c48c10cc7b4736d17b8774c78a8c97ee05b40d366b2cc9bc7781b1e4a192f200000000000000000000000000000000034e7012414edfc6a8f7b2c6049236b6fb77eb94b05d55b218851fc1e553514e6ad388fac08a24c33bea63ddabdfd8720000000000000000000000000000000004c832477a90683d417a00a698b69c643d6dbf82f5afbb83eb3946f8098d80de6f2d457c0a06d0051315f06e93b5e13b00000000000000000000000000000000048c3339996948974f2bac14d8a6b8430897644ec8e9cff9eb369557003aa2827a4f3fc3444c4df73663ebc9325ff317c72a47e2267010c532d676ee3c3ebfb2be2b7569f6f7a22f76733d7773ed383c00000000000000000000000000000000082466944ee7c62788b6fa77816094ea623d03c7aa2af249cfbfbf78eed26a76cff8c23c2295aac7ee1ef8dc84630003000000000000000000000000000000000a8f88adecc3f50d8eb329492f2c031e722f36627cb3b21415781156ef44954c5b8529ceed5978a37ae1248909d38b5d000000000000000000000000000000000e08f628aa014152b50a85bb6eb947d53c596d82c0d03594ed3b64c486b8630c880adf43fb1575b02e4eb8174a04034c000000000000000000000000000000000776844f28958d3e12a5c163dbd039e50df44b1c6215429381790175a609a339621475a5b9a06c3276c9177d2dd2b576c52f48e84a68d99124e678dabaf376c956dbe9603974283a9efc7c27e830e9590000000000000000000000000000000004477f153c0510d8e50bfdc2db69182c05d5ae9b94bb1880de239733e380e03d50001378432312b24b5bf0952c38396c0000000000000000000000000000000016663990dbe529a5658f2b3044bbd390ad430adaeffbd5306f758d86bd5422391bfa1d21e88c63300faad55e6a2d1d3200000000000000000000000000000000188f701658558033ce2c41101a611f74ad6d3cd075c195476bd2cd59a1a9dcfe937020737250fe418b4de435f8b3a0380000000000000000000000000000000013f8d3625309767841603329f56686a99e196d697802cfcf31f8b48f9c76f77a321276a0158a22b94e91d6907f6ff451e4fe662495bffd8ace4c1ddb39e612b361bf90a0f1bdf6c7fde2bcf63df1bbd2000000000000000000000000000000000f184d22f3c0431b031ee0ee7ae9598ffb511a2a56f5c9f15c9a4b0c53af2a10d22a311805786e303e234239326dd74b000000000000000000000000000000001062725b8c576e79e314f6a56ef9c41f05a65d7d0d57d8414e2ae9cb1a520b16ede7e418d3a9413c9c1660dd7508d5860000000000000000000000000000000012ef02fbe96f9a191804b6c4a0b65b6024e3e2b1f8cff986f5a950cde9a32ad50d4f7a72804b2d18b93250a63a7ae97800000000000000000000000000000000000b3b0333d61fc46653a7172f5a813d13ff5a48056f9689c78c4b18b8aa3afaeb7cec305d98dd600786351338a2185a651e67e96f64b80f4978fdc1cac90be538774e34c2f619f8b8e60cd2aa20f2690000000000000000000000000000000010c91e1dda48dc528f618f01abbe01db1a7b6dcb0d47b83c7b7db3331f7156f7b2d0f081458241467b0078935a7b4a4c0000000000000000000000000000000006f87f782979d2adc02e65b56a4906e50430cb4e0913636e9aa0364535c9d7ecd3b9433358e00caa8e90e84b7705bdfe0000000000000000000000000000000004635089c7706cfdb5a22ef643d1a9a5021847646ef01ea559d1b655299b65cd76a73b04149adbac612e7aa756cf30060000000000000000000000000000000002d83d82bc9fd66c558e00547a8c25633899584c9b855195c00eb3c8742d22c601982f244a03f8e0c5c21caee24405481a6ecd3db89a7f07344b5728efffd35a11f7380c740669f746fdf565905a1ca0000000000000000000000000000000000848f10eeba8ef9c7fd0e679767f6b6a2392922092916da8f13573661f84ec97c65717e55c65526cedd59dc1e096f0840000000000000000000000000000000013781974518487de12661bedfca5fe72205c51cab461b5757ff14f319d081e7845cf8e099892ea85470039713e8e48cd0000000000000000000000000000000004cc1a27d1aa88484fed40ceef72e6bd201e5ee276b5ec27624286dee112ece767b37c6f1f7846d71cc0f4042f04dc170000000000000000000000000000000004f7335d6a1463976d9fd86e2baa45d08ec65059b14449ebe4aae99971c5666cdc6e40cf0510ae99dbce97ae8b4598067db5ef4c1c174c2e5ffe5555f54f4e845c463bb5105381fb39eddc01103b1bf70000000000000000000000000000000003c1b1e0848bbe37e62f1ebacef1a574400d5048f1e09d935af2052da29140dc4074175e4d6ceb7c2c071331b2f3d1d3000000000000000000000000000000000e1c84d6b20553ddc5ab09049ec488ea2839c5818e31455a7b231cd0455e2945aefcbdc6c1979821a80bb4f77d46e91e00000000000000000000000000000000199ebb31e8800395a9c2e103c9340444c97004186929b52de33cb8d9396e7ab8d5af3fe6035d4463701ea41e341f577300000000000000000000000000000000081b3882bfdf83e67d2dc42b211069a4e93c0f173263f9f20579128391e7f2de70335df949b9c0e9b834b6e574f2f8cc14018f14c50d40d3324952ec22ed247c13c4cf10eacd32c3671757bd12b041e60000000000000000000000000000000018aa45c6b3898a5fa618f87f9a08a7234c1b94fbe38e2297a1f9c7a2e9de0ed83023deebd56560b1928c012c14dd7a860000000000000000000000000000000009ab80da6c519aee8aa1fa68c35bd0fac78b55f88d861e8fcd445f629054325d63cc4241f61e5596dad0d54c94511e4c00000000000000000000000000000000105f8253f37f5538a2c25587fd33ea61fdc744a7cdf4ff23a55e2c66a39040d4de5eeacb7e11c0d2a483d59e7c3186aa000000000000000000000000000000000f6b10cd6522a1e34c87c702f58a07858cb753d67da9625155bd433020775351a9ec4ff879f91a43f63be1c969afe675ed4a28dc3acaf2220ba56d026b292a7d017bcbe358dedc57556cf638517bbb14000000000000000000000000000000001618dd5de43a6bcde91a6a03fcd88fe59d1c8c51d3d85cd44a1920dabd2608a0b17a987b76eb8f5b20c7f1dc0abb383e00000000000000000000000000000000198034b7ab8fb8ff267a52a9423da95bc587eef8684f18639df5db44e50bae7fdea5c5e5ef37ff14937f86cc948a34e500000000000000000000000000000000106d1f017da463176bdf55e3ada78ce70da4486be42dd0095e3a8a0f6e59ed503324565b717b45ee38d90dd3ad13c10600000000000000000000000000000000112d425765fa2fc28486b95e49db63346188fc5a6bd0b7dffa4430dc82703eb44d98d726edfa4a275aa5db5028d01ef530fb17a38b7d0888eb02394eed26406bce9e92779251bdbcb432153a550c0850000000000000000000000000000000001326581ac1a1a960db1ff2e8b89b1debaae46d1e2d0aa6ffc6c7398f207abb699ac59186ae7222b5cae3abe64cb61c93000000000000000000000000000000000218753594c63ebe5fe503aab4dbe1e944b24138948542c7c43d92ccfeba5854b7bf1bbcf8078d85fb0b8701b8b092fa000000000000000000000000000000000c3ce8c17f75e78a8c9980e9fe125290d377a32ac46411876ef011e169e86e1458ac5e71cb4a446f6c640cceb8d5617300000000000000000000000000000000176966eac1e20586ad2a03b4a1598b4db1d7c66be70b1b22833e4afe0e0b3783572f791ddcd4eb70a88f4acc28b6fc7a980b5873a5d0f78c3b8582581349498fa997fe3c6b1abe0edaed594253359d8700000000000000000000000000000000099ac8430fa411e74082cf3282f9a456d3826a7df4f91ecf621e645a1abc057e1bcfaf9ee73f149bc447cf4230f2f6c90000000000000000000000000000000004e93d7fedc9e2d7423c9e111b4674a2bd83de28dcbbcc54ce4b324c96318a11603fc9ea385f1c02364ab1f6b5458481000000000000000000000000000000000bbb29d70fba5b12fadb02a24bfe3f6a5362c71fe5f964dcd0e01442781d0462a873501029192858027d612a8572e9d30000000000000000000000000000000010daa9960005562ca2d18eaf4b4bf081f194fa824cc77515c81b2c836627f21b732448f367e2cc1830ad0fa4ceb928e1619f5719c320320a3c45dcd6207575e0d8527c458c56d3decf1d12ead8a985a1,0000000000000000000000000000000002a61fead6801f41f2f27603cf34cfb4b917f2f85cba1f9c684995227653c9dde559e1e8497234fba9b2e4c119cbd9ec000000000000000000000000000000000085f73b8e835a10bcb9312931eb08d916d2b93a1da843fa2f4530cdb26e93b5dc95a555dbe8e50ca465b162661ce1d3000000000000000000000000000000001442fff9019b5476c217ff725ad95c92c17b42471ed7bcc121e8a0506755ec279d4e56d770a322d56f56bc6a6b0a41160000000000000000000000000000000017e7710c4639d51c4a79c5a2791101b52742df202b1827192872f168bd21020bd068160a587fc501782c1301c231a0d3,240480, +000000000000000000000000000000001213b5d5704c454845824994769c8b300676e75bafdeb95202001161aede276ab7967ea7362d38e97ca1484cf9c342fd0000000000000000000000000000000008c7c1fa04bebe5a1fb8678370563db63e7a10b30747c2ddeb4aabd4fc0ec93220d578b8110c6bfe8a3a6ea2820f0db8000000000000000000000000000000000c4061a295120a00de52300ee625ac46566464e6702489467316e8c182ca2168052c50b5962ff47285866c17d213fc8400000000000000000000000000000000086c153169a9ed1aba10a6cbebff4911b37907d6398c441ad47da17988d512d822ee36f5217355b93c9d6dd8dcbc8e0b119d33d32affaadbf6c2b6243bb7b344a971270b488cf887334fcb15de2818cc0000000000000000000000000000000017929edde8f9940826ed739bc9f59099ce76e85950698ab0140784647023f96afa064aa4a49b9728f496515a0a807e5900000000000000000000000000000000198d98f430384c1e7fa9e2403d9c3d2f81873fb7b204378cec95b97e674e10a1a43af97db0488209904469989ce80a0a000000000000000000000000000000000afc9b5138999bcef35613e38bff4f81cf532e00346f5205405470b2424622826c746ddf0369c7bdf77467dcea5cff290000000000000000000000000000000019ccc05724b3e9966bf918f01312c80e8422b697be89365b6ca00eb31b0bd08fae942e90a75bf9da1b3d264e416060f1f1d832b355d7e0ac3653431528ad0a8f6819daaa19292a00c910ff0ff39f46d5000000000000000000000000000000001568e52c2760d895874527d1ac8597730578176bdcfc67aaf69ccda253f6616230811dac59bc27cc1e57b94b5743cb3f000000000000000000000000000000000a4ddeb8b56f105ed5f47a538052f3d38a23c0ceaa2dea241554e6508f82f47d32415ffeeafe5ae5664c936b78e07648000000000000000000000000000000000b3b335a390aa0090bfd6467d6cd02eea1ced347cdce3c9ed85dd46e38e9f2ae9642392c2875a27618ba8f2c555d5b190000000000000000000000000000000012baa4b29d116eec749353b7658af70d4d216189133db707e56068c8483af43ba86583862e6b39df13b88058536861b9e6dcfa50f6129544835b5a4568954264ea68d9e2e5d4370ee31026997a3fbfe90000000000000000000000000000000001888b83ca28c244a6178377b4ee6844dc916e28c3f56312ecc0e29d08e6254dcda39a36ccdc317d1908303db3c028dd000000000000000000000000000000000f4b73d9316fee42d60f8de402a7d07765508b84d8f2c1be1f3f9e802ed7b0c6c5fece3db95d5287225026e73de98ea4000000000000000000000000000000000f1b48122191e1bc421881de831293a80566b9a7f2c9836f7718afb69592d59d2a714cfddf88945b94fac7a50b743eee000000000000000000000000000000000f1c6b052dbd03795433d7ad122473f109484d50245021c8727d252145e7db7dadc015265d1547f9c748409d74f5aa33f7822767391d3b2331e8e1b81c659c6e0262f7355063decedabac9797a84f0f400000000000000000000000000000000011e8613c3a771a177b4b85f0c6f97a53fd7900cc23566aecbf115058d2863189c21be36dd5dd736f6d0ffbe88182b400000000000000000000000000000000017b2c4e8d8aad0a12fd7130789188bb63a08f2b243c8f7700599dd33d7e176f70f2b1818e56540ab3fa507878d96a46e000000000000000000000000000000000e2b5ad5ed3578dfdffa414a4a2142846b1232cd2de468725283e3f92b536d8ede74bacc236993f6f68a16fc6a7828d3000000000000000000000000000000000fdbf06ae4cfedc462f5913bba9bba2b5c86ecd0e298bf27a21317fe74af6ab15014c62cbfb617356548cf808599caf4b1ba1cd6a4a6c433624dec63547119c0d492e3f38afb04e5153d82e400631aef000000000000000000000000000000000b48aebc6525620b99cd83979658a35afa233d17849bd0dcabffcf3b550f875a386b6c0b4ddacf18a23843629072c0150000000000000000000000000000000010432e5abf862d3be10ac5677b9f296ccdcedf1480e45de631b6bfec42f20edf62034f7205f659f11fe5a6aa9d882c7a00000000000000000000000000000000011702a3590e7aedd6948bb94bcc874e0b8d77a18126ed4ba3753dc98953ff941495486c14c6d801c71fca3564ded9910000000000000000000000000000000009faa427c0a7da26c92b451c61f5b5e8804fe032a4cfa014397e430882cbfcff81bb22f9c15a8747ef455773c1ef65b0a41e184bcaa0721caa4114d6393ae2251fed84aef57c7927a170145308bb136700000000000000000000000000000000061a1ee841251bad461f89c52196bebb1cb4463298e88abd62cccd21bbd325ddb33d1306ffedb2734be76c18d80c8dfe000000000000000000000000000000000d05a5ce6372ce34b0bf4b19d8e05aab74abc1cedcc35a2d1d4db38813d1e5c1375d63ca0e8bbf29c510a4319d2aec27000000000000000000000000000000000dfc57aa8de28745b8d28db3769ab5ea26b5115d3e59e51ff19af8ba37efacdccf763ce682cfdc77685705781c3924870000000000000000000000000000000018c17d87411c4f8e0ca51b3eb4c3765d3846e0d1b75574f8e511b2f3e8c5ff53bf7618959ce18dfd9e4c6285e88f094f63cb451d8eb3565274793925a1869ca5a25fb19639449c71a761809f785568de000000000000000000000000000000000a0642094b89dc9c6c7c11c1e57ed542982bd246112884969d424a3e091ec4fd73dd40a5ac116f6c68216fd1e733cdc7000000000000000000000000000000000788c7a63eecd1cbc26ee6b14b09d0a3b7a17a848fc0551d50cb7497bc98287da2d9b898260eb678a8a0f446eef5c6670000000000000000000000000000000017a1298f90022ddff3fbbcca180e3f4da8760218dba595a067287a2473a6e10b93dcd54154cb64b6c078b083b42cd09000000000000000000000000000000000116e999b808dcaea0566c0fbef1807e160612dce91756b2cbcf4883b04a90320a0759bba21b41e6f4d8449b52e52f9a96a2f94d55f784ebfc6b6260327372217d6a5b9637ea5f9afc1a65f99c221c29f00000000000000000000000000000000064c95bc9c0e2be48849a349f16713791c37310f71b5d0613cf0706febeea3a56a0f0f1ac6b504524eba801e8b759f2900000000000000000000000000000000007088d2f41fc7e1147b92a2ee7062b9bec194d3a47eb9985ac1ceeef57e1a006571e7247a13dc95afcf9905be57e2a7000000000000000000000000000000000e6a0770f4315acd9e410fe58395ab8b20a08240a6948b762dfbbad3414bfca0ced4ec9da982bc9b8798b60dde78a96c000000000000000000000000000000000a70b53a6d71c83971167afe329ffacdd417bd7b228766851c3b43701a439f253a8659312db7e83a398142fe19332b527d889a3362f551b88e63463b7f0cc334fab3fdd302b630e419e362ec1eaaeec000000000000000000000000000000000002486eaf9b743d3aa6a1f3e1174c5f213bbf3e3cc0558d63ce40e3c03e1c2f6e8508248bb649aae1bc92f3eb8118a2000000000000000000000000000000000042b03959b40eb0641d39117f7af50dc7ff048697a57b80723aaca164e2dbc647ffe78fea0a6a4c07671f7db6d5b2dfa000000000000000000000000000000000e141eab29f52b9bd0ee44861f154ec1bd30abd715935a7958a19007e789a41cdb0f4b9cf7b3fac0b0d4d77637b510d00000000000000000000000000000000002cc2eaf89cb7a04d425d878a30b5e2e9858ae0b2a2ab28fb28a6db0c7283ad861bb6a92067e969e5721b43466e857db8bdd400ad873cd6ec546bff698171942d536b94e69dfef4bbf316a471d4b45cd000000000000000000000000000000000e0f7595e4c136b4d8bbd1eeb021df7dd2bcf1d9f98e4fa293f7edab635e019af87c138275fefacd806213177af40eca0000000000000000000000000000000005dc209d6c86f1871637998c10490a70371f9e00a68d1363dfaeb62046473dfb4bbd3b18b943439f75c45a1ee7f264a90000000000000000000000000000000003d215567d1e8f504a72658d48fa51374ac77234552c17db4033af780133d8516bb0769678ecb50b8b9eb950c2dd73e80000000000000000000000000000000004d780849b731012e1e5732d5f6d32c659a95c3e1c8f5ef4841fe82afc6f0aa309b1e02dc2554a4a4ee781be2be2149f63b496a64cfd15410192aee9912f869deea5a08eebd6b160667e12fdf23c44510000000000000000000000000000000007ecfb753be501d9f9b7ae7ceaabaa4fcb7b690ee04fa1a711a15dcf67e4422adef64a0f8118f93e67f24a2d1a2bcb36000000000000000000000000000000000a459e403d85972f7132641c05bb842416a7135009ff46b617bf0918e65cbbf33f76b98c10d901936e589bdf5de31ea4000000000000000000000000000000000bc6ec31a3ff92b4fae07cb73ad7bfa8423044048337b0ab9add09bf10fdf190a5f7996d157483d29fb29a681ed585520000000000000000000000000000000004c622e2bc606fefc8bd83c4a32f7353123205a6d3716b581c2c71360e5200ab069f60c256dfcb04b466c53cd61fc94470de38cb4627f53509eadb0918e562c6fa68a4cbdfa9f7578a8aaa8182f5315000000000000000000000000000000000125688e44f593c5f585765f30e9fee5e4f15247cf33ac78ed1744453385f49ac61128e23b1569ea33d74b207a5e72e930000000000000000000000000000000009d77360ea37298fe971569230159967012c4991255fb5337ca6d58cecc3cd44a024a9a044ac98a894cc97dea161844e00000000000000000000000000000000056b2dd9569f0698c732367cfb217af90a3d6dc15e2555ce0aa845616e4067a7fefb304f6525b539555a0a685f0ec5f20000000000000000000000000000000009acb138abacac351e03f7589d4bf29cbd331e93bf538578ca9466b759ea070931c786d35f74fad42261e2df431fd00316732c583e8049a5de38642cebab774d90d5f87601e3599ffc9af692ba532e620000000000000000000000000000000013515b0022ea946a8e679b9c0eac6cd67dbc4efc820f0b3d8984f12b7d154c0632a8d7207747284d49c498c79b6bb5c60000000000000000000000000000000004d6765ec6aa8744225c1e652ccddccc91fff7fa8182931c8648b3d8bd33b2177a9af03b2906da02bf117bea59aed3040000000000000000000000000000000006f1d858c4b223552f0aee466cff35d14b3ac6da35b8f482417e8f597514b065be315aec6662ea5c7784d3a9e2184090000000000000000000000000000000000345eaf0d72b9c11fe72261a2fddea318a8dab92a67ccb9438c11e61fd298a333cc42084d4ca127e09792e346cfe0f004a037e7562adfbad6b1ac48b8e4b6f277a788ea2f4416ed2900ed2791f09bc2400000000000000000000000000000000029ad10ed6d6d5bb591771cbd597a3a0b841c2347c89027126bfd1efee2ac403933beb99d08721232ab9b7354fcf9aa800000000000000000000000000000000198400d4e026c2463a07ba5a3974c869ed8ceb1f029bfc7f41b23dd7076cf4a83b17c27ad6506c852cd2cf7c4987f93100000000000000000000000000000000152bbf74cefb77fae8e825443e4ce09b4e223242187f563a236695294d0a5f540f0b29d6f93a54cf0a77900e936e61e000000000000000000000000000000000079f4759eaf044a80417345a1b4029f8d4cfc7e00fc625e815cb7daba2243a97d21e42b42ec968dc8647158fbe467088fa878f6a2e18b88d6badc5b42775e92c17974f3a18817b7769d44ceecac46b89000000000000000000000000000000000cf3148d0c30774104a097562cc83456d5d18643d5f7ad58aedd9327bf8e9450feee50ee893442b1cde87acb02b62045000000000000000000000000000000000011d4037dcc15d0c50337d71816a2b77428b8ddd530bc3b3c8550606229f88286ae94ba03578cbb5bbaf118916dddc90000000000000000000000000000000016160c8ec4e2fb780748aac279bc248b2e2f1092262f86d368d2f06a78ebcd27e929930c8f2be124e9d92dff5c6c6f42000000000000000000000000000000001980375281735390f48ddac9d00d4c6ee7312ed0797333a26a1684e09c9575e57bcecfc4a31b8d9597a8ecc703835e22c4f1a7d2b66e6202c957a649384cb277dbba769afd60708b457613f0f3372515,0000000000000000000000000000000019ff32d2901b7732df1a924eb3c099a9d36bf36cb32ab322f46a72d99d81c7942d0f2193a4aeb55cf079a2cc1707c7aa000000000000000000000000000000000193561d0433e1031fc51829504ca70e92e19bead2e0bad655aaffb6b41f5f75d18f04a162db7714f3f23da891ea91af000000000000000000000000000000000d010c36acbfb38d9dc2df6e6e21bd75deba5708fb1012eab23d06d78b1244d4daae38aa4f803d12441d91adfbaece7a000000000000000000000000000000001459ebfe65c3b2c9b2684042bd71201869db1a0248c740a54fbdafcf18fcdbcc7b677af43abe803362b462369237690c,240480, +0000000000000000000000000000000005b9860b565fc64146020647d1902e2a2d2fb2002b54bf5e21b6601124edf14d6e8836f938843fbd8c02ed8530953dac00000000000000000000000000000000104938181f16f16318d223febec3be3877bc57067fc23729d1f5552099125558cb21ee0eddc32ae0b0cc3555219eedba000000000000000000000000000000000211f809b624c4992a43e78a978ea32accf9e61fccef6bcd05155e52adbf4853340dfacebec9fa87e5417c045da25f9a0000000000000000000000000000000019bfe94a18da9ab4ea744389c17870ac96218d02178bf2ad502f166a3a1da8c14e3fc52038021503cd24042cde8f306d0241da9d8505208b4d3ba2521a42f28df7d06fc212e9e84931cbd30ae3ba2e150000000000000000000000000000000004fbb396eac2a1de9953febed9fb6e158a3b5a366f783d2105b562e8143031d7a1ef039e3fdcdb675b3d3aa4f4dcbe4f00000000000000000000000000000000155e23b5b70f1ce34fc229ad5c8bdfde7fb5dc0eca19596c658c1f8c38716a0a7b5ff59ff19a7a67e12760fa90eeafcd0000000000000000000000000000000002cc82cf87e7ac05be236104c1e668b5573674d9bd741f2d91d05c8a11af1f72aaa1dc20c73953fea38e6e069d2a43de000000000000000000000000000000000a7d1dcab00db0e7c0a239511d630526fb120defcd9453fbb57ee328f974a98721274144e48d22558edf25595b8ff4cc6fecab1334668102e47f1e7139059c1d715a1851a026a1580f93c2daa3f08a270000000000000000000000000000000010c9293b3c58d646a95c620a0e0a7a0a55cf43b4abaa0de1d5570fabca8d97c91afd67bd45aa234273715457da5a2894000000000000000000000000000000001454f8682f3736847cdb3f784a098f7c9e488629efc3820d49b36a2e928bbf736dcb3e1b30187c2c0090fff290dbf97f000000000000000000000000000000000a0fe3c635a81f20258db4f1031589afc8c7fd07f2fe1e5cfc8f3c40d08a958a3dbec537c51be2de99b849e006870b6c000000000000000000000000000000000728876e3fdc42273e8d71953de61dd5c03e7c31ab6ec56fc03cdf55c8f0aa4b4e5c8ed88c23c28568be0d864df026af4e2023c64a3b51cc3d82e262f83260ed4a5e9e3238b85077852fd501b52aceed000000000000000000000000000000000c9ba542189ec1828c397ace9639cf2ebbd1613356d8fb26d3c40dd00af1f43f5bbb25032561aeebba7b874bf39cb0d500000000000000000000000000000000175aa6e94a9e42cf809f48f51c48d60e74d61388dd217b55f3f63612c4565357581e5c39751a65afc3b7488caf5151720000000000000000000000000000000014c880d35d1d31793145803182584a8da003b0ee3c29c978b64bbfe4e1da82910a4539587ba350d393e1bf3169c5e4c70000000000000000000000000000000002a063b3fcd77180de632deca1ba89ec4ceeefefe9883ed9e7e06301a268bdf377c3a6e30859e5a39419e449dd27ebf5dc0a88f0aeb2b082dea6b50d591018330c2276830ed554840c10772403561ed700000000000000000000000000000000069edfb8a83760e09726f6d1c117d4bb3e499084b65e1e830ab30daf1625c37f851ac122f9f5c795912b5b6f7907ffe1000000000000000000000000000000000eb6e9b55869f65ecdf3ac46d0ee596d07c573f88724bcd802b4429392b9a56730a217a03286deb5103f70aba7a9bc46000000000000000000000000000000000e2803e1a646bd70c51806b676591b328cb20359aadc8e79d59e7c31e1ce2f1473b0b19f7a34f23aae09678b11b37432000000000000000000000000000000000b3c9fb5a39a6c40343259e12ff4fe5058f25619d145922e1d80c3f5d105a7495dd9a4da329a2e78afc31a87b2c5d5e2f68c9e76d9d8914f14007c968a31089041e67312c6a3e5d30e65efa55894ba740000000000000000000000000000000019da372143e30307a71c7b96ae0703301ed723814a35e270ef6a6b0c57144f494df1d3fa0ac369f59f3daf534070c9120000000000000000000000000000000006521d89d810c7542108de26bbc888482a3bcec8cb9b542db42d5d4af30d6c339a5b4e959da4f98dd6ef8075554f4017000000000000000000000000000000001387d9c684a0fbf615e7023c0f3ff47f4d2c5a9f748f0261656a09b23066c745420df0eb180c9716d6d0743aae7689a10000000000000000000000000000000014271b9d0b21cc69072333a6c03493321b9d9028149d24964a3773bcbe5045875c457aee11ab0682c2bdd44f098f363d80eb90c6cc25b3a48d93b94b698eff513da37210ba79d22d76a270aa97fd51070000000000000000000000000000000001dd881f3d2063adcc5638b4b3813a30e75fd308de3c9f42e5382fdbf097d5796ee9e03cb44752515b2459f131f58bb90000000000000000000000000000000010f491f4594dab938115343edb47b0087d1cd1bc12ef908e150ecbdb3a54d8dd51ab24a0e10c585f235ed99fbd3172270000000000000000000000000000000019d1665d452ce7fb6bb6da9782a55dcf12a1d9abdfd50435b8f2a1bc5b323c004fad35ff7e9aedfd414a9b68fb1eb1860000000000000000000000000000000013828087beeeb85e43e8540fbdf97af189878f5ddc1eb35c95aa06a26923330f3b8a2b43f835186865d6f5f6afdb2b9b067bfd893b12c79e13659ee9b5f22de71d806a85410c9a23dc43363915a606b100000000000000000000000000000000014964f3576b97c00a8c5f4372e2501944a1e4374a3c30e11376ea62e09d52d40d428887833bc2f06279b859c00c98a60000000000000000000000000000000019ed533a3bf469ce5b3e4e9035af177efe9e4f8b0a0e5dc9721dde49a7fc66fa31c8b1c8d5bcda1bf75a532bd2be356d00000000000000000000000000000000064ec4ed48d63ab62373adb7898bc904d246bf2b3790c3cd850524e50ec38e7fb4a364344a6a1dbd26f2ad2d0fccaa1600000000000000000000000000000000134aa3c6b72d39bedd8f9c619d206a295cbc05c611147d38aa7304e995089ce34ab1fa13c2d6c6807a88797dea20214b34abb11f7ed6d73fb81ce2777acd6bbe8839112c527ef4ad88b094cabdb4742a000000000000000000000000000000000a2a4c8b457d0d2554a2d439fd3b74b18843386aaa00d1b89a1c2d8ff7192cdd1d3a888994376bb7ddda4d16bfcaae3200000000000000000000000000000000155a7dc763caf6f0fe1ba9537c0f75d3e455c2d1c749dbb4aa7242b40a9740fe9e8e88af6017e8f743a9e4c5dc6ebc23000000000000000000000000000000000a693da3aee178e2f0489af77f671c734423032f30c0b7b48debd3b71e65dab7db12ab1e0e72d3ef686d6c1922aebbf700000000000000000000000000000000109c3476016884386d6206c94073c628375a02c8fcd3041e06b8b413508188a1d26ec5ddf84a77d059e9a039dc5470d08d6693acb1eb73f6ed1bb4f74f1062f720a7f2c0ecf2b5a944ff89feb2688e19000000000000000000000000000000000a07f457f5dee69e9ee746dd67f982914a2182b5cb2609d273d4122d57a32c195270c956361d78eb65449cf5e13907ba0000000000000000000000000000000011f149ce84c2a11ff818be3ff0f86c1b38a9555e169a8cf791c79828207b7aa89c84e8012a0c5d8cce4e89d758b90e22000000000000000000000000000000000d636e5b027e41809d7ec8bbbfd4bf641a56599a63a7678569404ec8d45c3b88c1d2969e6101528d4edf1ee9d8e793320000000000000000000000000000000011878eefa5ee49be83ea1f7a9cdcd4997ccc59a9669778b3f006429e1a22d3b2a051924f371a228856523e3a09bab59b29ca1b157e6a2b5b88d7467e851282491ed30382ba217b82ea5cc9ca0c6986930000000000000000000000000000000019e9a1950f663b258474b24c334bd256d3aedcd26dc971a745857bf1fe007da0aa00777db5c3e5d21294e99862bf8ea1000000000000000000000000000000000329a12fa0add36f259e401442bbe6e5f9139e4a46d5d091a2110d2561b5629211a1c1996f20d19327d1782340e7ce4200000000000000000000000000000000032782c94c6e45a88425438324f3a24ebf37f0be213424b1be52c878985633950a022f57f8d64af1470486aa3744f3f7000000000000000000000000000000000631556d52fdcee3529023cf20d46ea09ab3c642a7f4eca2878e4af88801d21b80b829c9aec9e73317252639c148676c40bb53575662fa0b726469da01c39df389efde3936d2eee18d7035704130ad6d0000000000000000000000000000000009eb122c61ec44afb56b64929040058a804311e0e97d3fa513a162748091304233480bbc883f6fb66080b563b308a24a0000000000000000000000000000000007d1d810fb8788b9f0cd04235771d7adbbdf8c6e67e8538b2c6f0f278755cc5e57ad720515ca558412ae1fe2cd40b74d000000000000000000000000000000000955496bdcbca8716245a130fe6eef44d13280b2d56f15bfc772f8ca66a52ca0a742e6bc273c28cfc858a3269f59beab0000000000000000000000000000000000b27aaa0d94633912c96f00ecc021773e5cf5e164e20c7a7222a58b0465e7baec4e67fb56ffe564c7a2904f36c265e61574a30a575138c44881c1c126be214c6b68335d7338875b8a398196f27510d70000000000000000000000000000000012e0572f5c84f6082dd05705a3fae738920ffff840c21e444f0ed002df16394afdc21c249b6f1837389c48719539f4c5000000000000000000000000000000000c26bb3ab52e3bddc219dc223daf472247547544e3a9ccc31123b82000b17ef325148935621edd36ced4e702ade1ee3e000000000000000000000000000000000c13a8f02dc3f209e9abf3d316fa843be9c4dd98ce1ca2edecf757bf2bb498750f6d96c28abd45d9c6cf5b8b6334b63600000000000000000000000000000000157a50d9034024dfc7b0f0db4ea0f45323d76c81bc844844ff9bdd0c13f2059066ec3060210aaba61bc074afd7ccaa286dd51553c4119255b31cb0aaad7391694f7dd29420420b513699747bee819a99000000000000000000000000000000001054edb092a7053eebc542f690e03139f2e25a0098c665741e8711c8a6b9582af47e467f74fff9aeea098b7732be72d400000000000000000000000000000000084f919e219de15e7f9ee122383c772415741e5b86be6ee7d2193a4f6be5c9cc9b2fe5e8beba26cd768bf2ea1b6ebffb0000000000000000000000000000000001822b4e8fae5bddbb36f5c226216471862af238be770d33c4fc1ec2777350db2f42e33a7ba468c317a128e8446ceff300000000000000000000000000000000130f704596ddb28ec6e335d9527707a75c97298407ff3fe17d3cba0cde4c21bfcfd1ae46272018c1db768c036f215182d88f049ab3ee2b01af449abce08ca14ea3b065f06a8665ae3510b4c04f42308200000000000000000000000000000000194dde06f8c54de9ab0ad72ba0de2241fef32fba30fd6f5e83fb7750bc120d51c461d75e495cee0d1e85f0f39aa9d3620000000000000000000000000000000010646496be02c658c82dc68eab86a4f784cf64494bd8441f884e8ff384cbb6ff3a4bf5126bbacaa556aafd652397a8a800000000000000000000000000000000109807bd4b6613acb3eb7d386e84166219e52e841c41185a269cc7cfc5f34e9ef5cd1fea29877749e0cef93a3b44eb1600000000000000000000000000000000020a388c668c9339e7aab15d03108317dea97720dd27a94cd3bb59b372b268d1a7d7d7409780bc4912c3f95acd42a57619d6e227185c538b122858ad5ae594720fa7f743f5805552152a213ebea64aeb00000000000000000000000000000000161506c4a2d57c852fe8c3dce63ec6673f05f99c1e032c8e591239616ef4469c4240482ce5985fdfd4a80f54dfa7024f000000000000000000000000000000000486c5b106393e544852c143c5ac4a882c79870363858b2c910ef4041d8803876cc55ef59cd6a41869bf5247f0db2c0a0000000000000000000000000000000000fe765ebf3c4edd3035c7bedd4aec918426898339d7aa004fd74bbf0e3236deeb7d2bbba56c31fd447816e301100a66000000000000000000000000000000001917c9cf16032e22cdd3f87f098a532a33c9fec560a88f9d4232f96cbe0fe945fbae6bcfdd2095cafe6e0b21071d6ec53f53123f01c4d0d4c18dd72ea87ebb5fcb559df255773fa0165f1432c229deb6,0000000000000000000000000000000015a88bcfa39f444cd66d0d7e15c4040561154c59b832c5ca895f8f8077659487581681cc8f13be136a35b4a573551ad00000000000000000000000000000000009fb6b87eba1edb3d1d23e566977eac68e8f1a28386fdca9d484c7e341c1b210390787418e2f2dff7a228e1cf10962d6000000000000000000000000000000000978de870dcd8d094072897707313b9f1a18d525e60a7cba2b2a395ffcc9d0f97f84e0784df36247d6c98824aaf3ec82000000000000000000000000000000000fbc6832c324d40f104bf82c8cda941212105131c26f630af1d3f7040ef43c6eb4486766b75a81433e46966f79953647,240480, +0000000000000000000000000000000007517a941bec38d0e84d21508d8bdd6778a853d9fb4c5e953bcfe3c8fba3732ca0b7f6cb5c363f33d7718b1b1a68f8e800000000000000000000000000000000150c0d975481422ddec2a58a55b3d917b6b7c0510e75442c81ee856e267d7efa09641c6b79fb9e699c6b473cccde7f4d0000000000000000000000000000000009d37bf938ac30fae1cb3ffaa971ff3746ee4090d4bf8b11dff7710b3f2e4cc686813890e03643fd56fc99e847ae5e940000000000000000000000000000000010fabc4048e0fadad73d0481e290c81884f4578cfb66e0a83324739652ccf273b62204f126696a2fc6469ede39e00a9fcdf2bbbad52a3f5c0b3959d46a8c42f6f5250a93c3dcc636712a4a2baed289c900000000000000000000000000000000089b167ce7fa997ca0ad3f8bbbb358824cb41f525bf60352d5df99402af62cc6d768113d2c1ebc7fe8190c5f732fbfff0000000000000000000000000000000013dcd35865e27bf98f1f6508b32c7e9a989d528df0626228087bda0d8b456af3ea2f4be6732edf1bd8cfd0ab9576197a000000000000000000000000000000000333b0612d7068986d21e1cb67a1c7af423e98cb14aace2ce02f84d32a38f97bcdac465f2b22e5fafa6aaf0d40380e4a0000000000000000000000000000000010de7ba4f50b6654fdecc4fc6c41289bec50cff1be18be9d5c9d1f906ae843189bb43f144aad4d2a2cdebbc2697c974918adf5d8fbdf81f8e4bf2f622e2d481fb2dea06a1aaa289ce4f6e783eb9a98dd000000000000000000000000000000000fdddfceb29fd79c31b138ae8e41507f324abd5e3750da14f4f86176126a06380d53dd5f7efd00e7f94bc1370ac9816a000000000000000000000000000000000d8371c602e393a4be250583c299d069270a344953f7f07a5fe27f8617cbd3ebc91f423dc176b272339bb3bd8a9a348200000000000000000000000000000000193a260a417c9c46da0aaf139e3bbfbaa9f248943048396d95716b3be0b8a148a3f0ebcf7d6f9a318b16d2d850ec2f5c000000000000000000000000000000000be4d0f2bf6d746b930034eea8a19d73377617645c29153b6ae6d3ca6fb35a704b6a0bb658282cf93555c998f6fd054a650e995b73b63d068fd54f596cd9669fc3061f0c1da77ae7d0f5bec012f9ba69000000000000000000000000000000000731c0a5d076d6addb15c1e5d3143d41371f4835d77756418bee452d2f03b1e603230c59f87905fb67d5eccce65a45d20000000000000000000000000000000013bd198c023009190c65686468523eccb57c5fe7b159a1c5ba30c662a275fb24d69338ec9c023ee6a10a8ec9dc7968210000000000000000000000000000000018fb369923ee655839c7d996e264133c49f102978f18261faf2f8eef376eb0bdcb5af375ada2bc783e50df16737f78dc0000000000000000000000000000000009ab8e16e1d0b406adbb37e950bc3820ec13c882ec4483528ebac836726ba202bcf796e84abb3c16dbf6d1131b3854cc3350d4f13e25052b1162dad3ace76e5cda71680fdc89460d8fa88c0d157df426000000000000000000000000000000001401029d7cf8e7d2690a27c01b32008e273b5a33842dcf52d84f77dfa4b2a1fb290f56eb4ccddbb420b27e06a7a3a3b4000000000000000000000000000000000c464c6fdba702f2fbf4232b34d615e66dbb5bdf80233f695e9103272111a06a79f8972d1034176859d0e29400f5a9c10000000000000000000000000000000006cc97a29f4e694f0cdbb099278fa94140b40147f4f911de96a762f2bd28233598a892899a6329cc3cb854b56076787a000000000000000000000000000000000625811cad7c740758388f330c4a56ef30429ea4cdb9a00e2cd1b7f310184a2e6ba36ebdca57c87cebd5232f52c34d92283f0256766690c88df6cf7c968b9a96121d26d19672ce9adc84b699476a32db000000000000000000000000000000000d0a16b5d48eb062c71b91d74a0d25eca0d4bd7082de25199f33a9d3d598d137fbee2ac36e8f877c157be7438ebabd74000000000000000000000000000000000bf93533bf677050d9a77a5dbdbf7cf084b5d934d55318256712ec361693738d48ef27536476fdc93dd8e81f13d67a8e000000000000000000000000000000000696fbd8841e60300602aa5528391aa8b196d8c186d6124c842a0124a8d8dcbba637502f330c980b2f5a900be8e04d020000000000000000000000000000000017b0c51e699d2252f35619520af71775f9dd8c57c2ef146adeb72640bec2ca02a59680153e5c9f66bd513bd8559b9d66145cdeae7fd3f7455dfd2ea9a064c135f0a0a36990ea34929e292e4cdfa0f472000000000000000000000000000000000eee94b5148ccbb3642e582cf0a517b72e6ea019676a13b1484982de7f4be0346b7ed22979ba7303f6367294a3eb2716000000000000000000000000000000001502bb3964f6b3e862279e15fb105073e876c4e48c55c42f3737dc9efed82b10fe8e39438ccd39c933f5ac3c6768497e0000000000000000000000000000000016cd8c4b3be55474aef7081cb969b75ef5e7cca9bd0f9627928fe9931c6f869a9a49d0ae2cfb8346116eb3ced25d4a8e0000000000000000000000000000000012456eceaf32cbb6514e6211136475a750889caea18ff4f9d5ed7b378e6d1d265721a646715aee6b9f2098e954a88289d9cdaa979ab08b454dcb961e3cc4bb18f706bed93a72a9c1e105cd69c0b691af0000000000000000000000000000000007b5633f4a7dfe62f11065d44581f5060210f8e572d960eb85ffd0a903d8b989ce10449fc90b7e5646784a9f6df28699000000000000000000000000000000001710f252cb35d88f6bd151ed596f2d6455f050c5e25add394dbaf60fc036016ae07a5a8ed494b95875c02df3c523186a000000000000000000000000000000000bee19779dc6430ebee993f82a054fbd42e5b7265090017e5b2d2f1469bc96a5a188adf471d576a416f6a841081043df00000000000000000000000000000000038f9fb4159e4e6f596a17ddf45a00a9e4aede63b062af5eda045efacd3977e8dfd61c307834c08bb4c284638696e92ef262f9f7a26353193939bfbbdc50ee35614a3c099776f08b21e0c4c97521eb8e00000000000000000000000000000000197687895f22c4a639bcf2f494dd9e5a034610b0297528235f1d806cf032f5a86c5248a83ed6b12f0de27f5c6e6f49420000000000000000000000000000000011ccd5dd6d6ce553ade9b31503a9e6a6119ae329178706f051581e3cf0ee9d6fb527b340bab8c79fad1cd451c7edb4330000000000000000000000000000000011e9f051aacd69c8bfd2f0ecb566e6d38eabc43f276ba7a1b8e8ab093917dd1c672c61d6dac4651026823b9938d3601f000000000000000000000000000000001362c3b2e6fd9b3618df26ed28f96530c1915f0a4ecb647658d1ae4ccf4c000f3bd1797696c9ac5c5000dbe58dba8de44f0d2915e82c9a69f9e9af64a2c5cacf61ead492bf69912a35ad6a316f9914a8000000000000000000000000000000001819d13cf4522a9362bbeb0bbbb0a498c3f34da1c9e3b2c54d08f7c8acd9ee756983fe80405579effb79d673407390ef000000000000000000000000000000000f870e5978f4a6e3b655fb2a05541ac0673e7b10136adaf28be4dfc9022d4cc8a60e17d125dfe53fbe10c644ff37e02a0000000000000000000000000000000010207ef774cddd10db2bca0a051ceb12900c407ee265dea4615553c193d7475b5ba3198b7e0160740e4fd015dca33e1d0000000000000000000000000000000017937be546e06fd2eab4c969a029534c02fb770646d43edeb5e6c8bc0c2b5f35576c375bf860fd1087ce099d4377d24e25ed3f13198b69604c08b414562f67a67aa8dd4a7bd3c066874182d21ed9004d000000000000000000000000000000000db02fcda340fb27a3fd7da468c5cbed9c8dce8471843a8ddadae43dbec9957a0479aa52855d7a6dca99e7922432365c00000000000000000000000000000000163503d24f9af34058cb5afd8e9d5aaf29e141c8521eaac282f138466e834f0daa9ce14e0590b501680d5b47f866aa8c000000000000000000000000000000000fc9175e6d20afd9d194907f2eb311bf8134aeb96da72f6423610612f2ed20a074c113fe8bb632d9ad74b2f6e7e2417d000000000000000000000000000000000b4621f5e4465629648b62b7f2b77afe6470f9706f9bee5b3ccfa66c596842cbae26badc689f7f623360cb7fc1d416b84ae188cc115e9d492be64abefa4bd4c93b61dd42a7d213e1100b8108611a61630000000000000000000000000000000003c77c7efdab9a9e71283b034ef581a31faee417febfa99be3c18e8ab724c140be684ce719bc5a9ac5d3855ddbf3651a0000000000000000000000000000000011889b02b4a1150fc2b7191a95c5ee767f3c9b82a3a53591018242fa8685ee3b3542526dfdc00695a6cf046033b8eb760000000000000000000000000000000016d7463159c4e3cb635f24bfb944bc518369e894218bc49d7b7f0ea99240259f7ee2b4c26c6083dbc4559ffcfbd392bd0000000000000000000000000000000010a85df6294fd6406ca651f15494153e9802f0068bfa149e87fe4b1cc3071ba74940a21dfd55a8a77e7e2a193468a3d2eede725a693277356ce71ffd7814a77fcc30eeb3a2b8863fb91ca03da1cbe37a0000000000000000000000000000000016beec57d3049c382fc039ac96b890412c5e8075afcab599fb877f8639747a587e82241d9a8059a0bb45ad49959777d0000000000000000000000000000000000a70fba1b061dcf587f133035a3aaafcdace3b1e771d71887ae914919e5f52a99d9933307ec15b5f0a1623b9592824500000000000000000000000000000000005064161136c04f9f50e42a5cee5dce3fa0ce1dc0655b3785a852cb9741927f6c9b357ac1010d7212533d1593c83dba70000000000000000000000000000000000d50b992bc0eee37a15cfd32eda2c591fc4c4203ef84232d1a1e7a9888005bc00755d76b9d0345bb01ffa7525f2aa1e9d0618f898594b23ee3754fe390d6bdfa7d47fe749d6819e306935c1eab6b0460000000000000000000000000000000007617e60d8f67344ce6d2fb65cfd5b423a1fd091626da837dc8a51d6ffdeda9712864e8f30e45ae8df917e0e4625e59a00000000000000000000000000000000077c4aad14f870ba24703397ff0b33af2e50b026f3e0f13f3ec1aebc9ea3af98cc65ab56cce4045538ae6e5f410196f10000000000000000000000000000000004a31d0eff18afa87f9a53098cfd5d21e913c7519cb171f83d0b73abbf3e893a3ccd5aebb9f2bbdd3b0eb0326d37fd1b000000000000000000000000000000000393052e6dff65e01e79254af757f12eb1931e0b386f8cf0fa0782269f962ebc5d9bde46f5a4ad3806e88330aca59ff01e1c9420cfa91026286d7f986b538c13e8c97893995485308c69f053927f962200000000000000000000000000000000033aa108d252e9107f29cc7da79585d4525ff2a35d31479a099c7c011a9c4414d7bc5f8498f8a204134b2d14c5fcde5100000000000000000000000000000000121214465992bdefb970d420face6db75d531e67314a021d2877643ddf738fbe57625d286bde7f40efc1d329a2e85b6e0000000000000000000000000000000017e14f6cdd916b1fc949be8ba3ef9ae6cc16d64da4dd498b5458ea0c14eb7aab8f970f030aab26397110331da11a232d000000000000000000000000000000000c56ccda2a5cca61025253407e72967c767f0e7f2aa0b97d4e4a09420dcb882ff35039ae504a9c62b3f9e7bb0c2e7bbbe5095ed9a9181aee392888e3194ebf9c4a6d87b503f4668bb6cc0d290880a44f0000000000000000000000000000000010fb3396b0674b9285cc5d5a4e7a41ac002f2b43332c20a56f428d1e19e1d1bb6f886d3bf03f7b0fc509e52d75965e15000000000000000000000000000000001196b7c253c50da10815bdfd7930a69608187fc3ac5fbcfeb35b95754d3017a094afcdaea867c2f08346717dfce7bce8000000000000000000000000000000001021f178c53b7d7d2041a6419203d12ee162f27999dd8f79baa15c37a7401e7a6df6aa4192a310cc1a23bdb0b427d63c000000000000000000000000000000000953c75910165f11112583476574f3987495d33e5b1a5c650a2b30692592a442d9de36da49255b0c01a7bacaecc9b81adcece8ee33d3bf3188574e94a889794077035ee92473b7266d92a3c018771b4c,0000000000000000000000000000000014da1d424c936453600a4acbd3666c6188493d4da8b34d6bc508aab07e59e3680a9e3488e69d42a724c9486d70ed4fd000000000000000000000000000000000048c637348fb9a4c631a82ded1fa08d693cfa2cdd6cdffb8bffee63d1bb2ee8676512a1a8d375e7ab942b6d6bdda45c80000000000000000000000000000000000443264e7dfca91f17251c33cf72c56b045902b4db2eb10d1fd856f79b4130afa6f29f3283af7d3b8b2a9d8dd63718a000000000000000000000000000000000fb386f875190ac7a49d4742edb387f72c1ae0366ca5c71d5b7e385c11442941ce0fb9fe2014fc624fe93ab86ebc7aff,240480, +0000000000000000000000000000000016a539a21320574fc25ffbc0ff10c821d6ad20674413eaeda6f4a31f9a028e21cbb3b224c225a2e3bc3dc221cec084cf00000000000000000000000000000000104e44989e2fba9ddce8e309f5d3fa3129f679d6456ed11137149b50adf8b22c1a148d47154450853e6797aba2b006850000000000000000000000000000000008b33b8cfc992efdf7d733803a6d08a4102e27fc4960ebe6ebdb7949c4ff5af76e55002d93a4f7204eff5f2dc4e37ef10000000000000000000000000000000017c35411c571c302c746a9b79cae892e988d50b4660564660de960ee09b3937b6f5b61fe37d09f1c02528f554210744aaddc845ad867f1e2664ef0e78bced8ff6904c5836e7c63ea3a9c858fd7b710b60000000000000000000000000000000009cd32594094d4744f59690cf8d7fd260b5ffb2a22945d938c035151861507ecaac9ea553e7b44fc4b3beb03b33783540000000000000000000000000000000006f4de33731b9b13b9cb395798769e54a0679d272c2d5175455e10c790debabae4ee02b6df08975efe806da9c4a208b20000000000000000000000000000000011859798a8383b7f994a1535bc0a96a114b90644d19921f0eec774ed58dbaa899dd3736cd1f4a4ff9bfacbc7370091d7000000000000000000000000000000000376c25b0f70427d4974c4fd1539d40996b6847fbb67822fa01cfd541cd3a3f8a9f3fe9f7ddcc3ce920a6ecb27dafca0c78cfc6a30cea34d3d3505f4f897631f67ba77913734f6c2895d871fd6d5581c0000000000000000000000000000000003a178f91a135d59dbd65eacebab293a3817d30e734c247f56a08812aa540a5c80e3f9908d86ad787bab27fbddd21517000000000000000000000000000000000672b3544dd2b91a626f37dbb389aff073777164e3e20dc572b18a2e5223bd323094e41bdbe2dec9bada227efb37dd22000000000000000000000000000000000f40f2d279c66f22bf0fedd129e02c96d8906f9f1ec19f5a5c1cbd5beb10942a066dd391b69920a0a697138f627a1b180000000000000000000000000000000016ef3caad858d323b752e5c437ee2043c8f691ca0f1862e80857f7cc478a689df97bde5b1d1350892c1adb03c5d2373ba1e40df9e1f7c84633cb3dc2223296887de7281ea66c5e1f2d5816334f7b280a000000000000000000000000000000001276e133fc5e708a3265646ef0a0122048ef95d7fb46f78b8dca57dabae0164ca986bdc74e581604ff31165f9f28dca50000000000000000000000000000000008a77611be0502d2ea7fbcf73774fbaec68eba36038e2f34f79caf07f2e4b7444efc49a4e85f88af585fd28a041f26c800000000000000000000000000000000181ab176e391190b1cae2e9b4105ca14cc82d15890b0ec127d8cdb46f30b704a089ac69e76f5b50575ed66176950e1120000000000000000000000000000000004031ce77fe9ee319b8db8f220ef4480c81568b3f6e4043c8710b559d25ad69dd38dda48b2e11d5aead18db0d1cc09b98810b9ce0020904dc1903338089c30e616ed0be03741572ce46946883874f4ea000000000000000000000000000000000f26e6d71e206c88dc81b8b8a5c05ee84a9f185e7b7f155253aa39104b5de5be7bb6cb6662df4f8e63b37fd1682721f20000000000000000000000000000000010058d13637c8da2e91c8cda7dc2cf1734a2f14b12b798e5c563ef9ef3624255a6e1c7550c37b547c35c55dc736a17ce0000000000000000000000000000000019ed470bd514f8bda8fdcd9c64f7626efdde0102907bd31551b1d1972aa14e1d361e1d58b17948909a669fa4d99cf3200000000000000000000000000000000013277afe1891807e269c22c9aa1598c12081809d888e0eb2513ca3f81308700893f74f176858ceed9c7955dcc0d8fc6893e7702da2ff9f3f14586a9ae80c8713743d61b915a7c379c1faa1b151406a9a00000000000000000000000000000000083664daa965c4173d6028e047794703a16e52ae459d3db0534d13c72d749d603edd668b9ce500677715e45216367c63000000000000000000000000000000000f4e87a65f4720cbfde7868eaadb34ec1916925ffd84e5407defbda0c39e1c7afcbc90855b275d528e7b63fd3707bd4a0000000000000000000000000000000004c9f689abe0d2dd3d927bad4b39ab44f6704014ef9a1dcd1966777129e1c72515b43c1b92ee60e9611245454683588b000000000000000000000000000000000ecc57b08b45037e62498135643cf077f01d216b5106551daab391446ce7bb37d40f41378c830081bb6a326f0105c2c4eca54e365faa35d2c9be259b53a1157b180a373813382f47c9154af18a2d83270000000000000000000000000000000012b84341bbad1eaf7fc8ebe56f67598821017365b6f3b4cc1f2355f868e8d55f9c0bed2943ada202a7d85cc884d8e6a20000000000000000000000000000000017693721988f73d77f7a41db108e428b0ba781ea88eab463693ec352cc13d394101b9a2792e0f30c77bebaa395a4776700000000000000000000000000000000093245e2919523cd57a0abd2e8a9c5cbe774bee957f26d3cb502b9c8c06483b850b031461dc2cb033d399651724f4fe4000000000000000000000000000000001530f7dbf6a0fbdc8b4f7a4d298b7824c15035428cb8df834907e25c64b8985186bb13f397b7b99ea7014ae65c428b12abe2079ecb3618de3accdf291d9479bec32bca1f9fe87b00b64a12d735f5b9a5000000000000000000000000000000000f323f01f2a63bc6eb1b565594ded14043c4ea5d1f0fbf20f39299052617c334e6126afd4273738aeb153c3561348b8a000000000000000000000000000000001525d1e1fa65f1b674feef74f6c81c82c3eeb709e597aedabbfc2b3262271b31d93818613ecdeb49c5d3a6a64f17a5d90000000000000000000000000000000010458c15bf46947a237dd1c61882b1561121f64890681bae5db6fbd24ef6c34b7fcb826eeee1fa328d9ef4d859faf238000000000000000000000000000000000e1f29275fe1805d02e069082d5e9a7acf69be17013e6c4c351277408d49383fe06f00137e777ba4aa49c29c25c6c0ddc541a44756ebda14aea95f1a1d05e7366dc0285305116b907fc89e777ce45f79000000000000000000000000000000000efb7373e11694b966d0182a9b01d1e52ec1e89cb18275921294e2d36333460b1e49fd420f1ab781b000d1491ccb0b11000000000000000000000000000000000cafcdc2c58fb3fad713ce1a38deadba8636c384243f9971e3930b961efaf303cac4eef1e8e4662636ff91eff1bf52a80000000000000000000000000000000007ea7441e1b2b0f1e42bd511c060b646c2d00bb3e6507beb5d17ab93ff68515b02f82c2dd43ce035ff660ddb0c104a77000000000000000000000000000000000bd04b88caf9dbd0ef5f89d12e72aa47d64212332b0ed871b7eb96b16295cf4810f6f20cc85fd4d1ce72119f80697c1b37d521d31de52681f1d9bbf64a12f9bc8fe0ac61aaef14d7e8d048ff09e6578b000000000000000000000000000000000c3d2d978e23a690e8422fd54f36fbee1f642611b6c3b2c2413844066159bdcd3703d1a392b030446af04b654f8f73b7000000000000000000000000000000000ae652fcdbd8e467ee9b447e61fcb811f8b6aa48840476c92daec3285785a06a81c1705fc2896c0843ab48eb92555b9300000000000000000000000000000000007088e6441cb85aeffcb4a9a0c81ebfc54a61f35c542be3870c2bb94d7081353322d4745747b0dfc3e5db07f9e48c560000000000000000000000000000000006c11f3e0941ea3bde0dd3a562dbbdad433f0b1e99ba34879e86f7951ddfb29b9e04ca62d54d7552a74e8cf1c3da3e704904a876d4ac1341e88fc4808508c89c19dd74aa8fb1dd7673cbc2d28e7d920e000000000000000000000000000000000c665f4417d0163820ac96c83cc2f09b1b3c000023d827e2690aad7357ff59e278832b992703f5f0016051ce0a4510cb0000000000000000000000000000000012f4b6688300b253fe868b3790f6d2f4fc16d81a49ff7a2edf821de16dc992d79482d66e443e0abb5da43df69f8d648d0000000000000000000000000000000009e033750a118d998b136cd671d0e760e3a617f1d6a994db8f6dfc391619f408720cc57fe550785306184b0c824705620000000000000000000000000000000018cbacd471e528535e22f714a841f110fb0484826e30f97842d65072b2790dadf0bd7b28df96bec531fbed1f3f93486b68911b04d8155f90c7c5c0cb519ee6ff14c0ae27ece0374f30fa148235e8cb49000000000000000000000000000000000c42b6fd52cc52034b04078a6565af2b43948695851393596e05f37f297dfaaea931a33f5b4c25980c093f8a742c0020000000000000000000000000000000000fdc7aa20e63743dd6ab32c82d2d6992b29779ec06eebd452c17d844159e90a7f3221f3e0e6b5805dc0f42dc3836d90f0000000000000000000000000000000003a2342a1bd528d701c2a6c72708a16df632f4e4b6cdb3ccc224b58b57af30b44556cc968ba3c0396a5e3f11568a73710000000000000000000000000000000019ccf76462668905c5687b7612a0bdfd4aac70f291d8b772e84fd5d4bcb591556317426471242fb5f44fd695c7d49279481e894ecd52a252cc76547513e2cf0a5cc6b20c3dc9c64c7f34f29a488258ef000000000000000000000000000000000c8fd4a171c5fbf584f567a1c10b20628e7e0d5d796eac4a9dd2376f8d488da25b9219c7c70709999b5553f8bba915ae0000000000000000000000000000000005d791c907984f2aaebf903a0ace52147745295f0c5e85964999a8fc74b64c8871dce358f26ed1b4af6c6f7f18e8f4c500000000000000000000000000000000110a453bbba72ac171876e0f6b4acd5b178816301e02586a143c2bcbfffcdbf593655408b9aaa4141b2a210599f452ed000000000000000000000000000000001025d5065f9801fcc1c1ebebdf67923b967ce985b5ca27ab5db8af7057fda23561a46b84fac5e793dd9af692c4d56cde72780ab3c48c8a102469799ba2f70d2fd9d324cf558a8c8b49e2ecdb71ae1c9b00000000000000000000000000000000023e5ea1909032676cdb79111a33da7ed788d2affbf4029b932eed843268f355dc92905db283d6617fbb530da3d704dd000000000000000000000000000000000b46f07de520aa17d597586cb0a6894a356757941ff9bdc2976f620e1bf1eec1dd9801d6baa2d7efbb3cc7073412ce8e0000000000000000000000000000000010022940611f418de9f9210b1be919d7506aca468fe5853675fe159d3e58685bcff6cbc2c1cb9e7d45a7bf305fca0eaf000000000000000000000000000000001888b5b0dd1648d9a27345f570a1278238957de1bd30c195d554750ea4b119e98b3989b912c4fad531de416c1533467f84ae1de8aaf498bd2d91bd828bc64e56482b225322b86529da703f47289c65670000000000000000000000000000000011dcc334a5037719256e514b2c3b0f36396d8cedcd77f33545842c686fa0f35558c397562a7e245f8cc412c776a2b3930000000000000000000000000000000006efd32c6afc56a07c813fe19e71f0248666c87e1df7e79b7afbd70178929e5660e85cea35d1c6f42b4c627a94ae0d150000000000000000000000000000000005a5fc2010798c793c1b407a577da0bf0e04b0478f19b7d0cfeff8e4e4fe2d581461831db165cfd17146c49a732c41460000000000000000000000000000000011dfe3b62eb87b039113152af74ae74137cba1762d4ae62d3cb0746272d1c42d3cb4a8fccd845a519fd0650a23a897a13256548db55ee9de70ebf6fa347d81bc50494b937ab1c3079977234a34cbfcfd00000000000000000000000000000000110e73e44734b7ab63f021727b75e735702f1acfa6669e0dc27111794ebee371734764bb165132af3a7e02f3605456480000000000000000000000000000000005fbcac7c7334cd0e6468feedebe077b80390833eaa4c28af80d29e75d692a10cf13058526fa5e5ab0fb635335ac8f220000000000000000000000000000000013f537ecc28685aba2cd60d0e3e787bc8104a3373177cb93107b63d39919c583ad3ad7a42e322249d7605ef035fe1af40000000000000000000000000000000014791f94aff42bfca13ab328a3e47b06f7da52e13436ad477cf55e53b54108d3aa531f0a5d73ae5ed7108d5cca1ecf7a575ae146524544307ee51e58a654d7324983a87e1b37d46cea1a4ec34114b44b,000000000000000000000000000000000bab02defb32b7938372d656feaebfb5431de1484361542c02519d20c6a500f0b0b112c331fe6f4eac3ec7f6ae4167e50000000000000000000000000000000000796b38c67df1361115bbf3a4afad2651664ef55b1ed02d3172f024f90a003fc3631753d7142aafffc64c6f6f57bf7800000000000000000000000000000000080d91637a93a9025e8691a400254af37cfde67eff7d3037d428596a808a01d9bda8025b7246fb00785cd1068b2752d400000000000000000000000000000000182a97624249f0c6d24672f04e2c93eff63fbe76cc11ace0f7193facd0655cc1e1ccb2d89d9547bc352a395efeb95afb,240480, +00000000000000000000000000000000115b14c4eb9cc78eafedd2072be4555a3db9e61b5fe0139bf3e40a92cc37b4936c68576fee5692a80e4a9aef05a9b7a80000000000000000000000000000000019c828ea555a3c8d28cf0981e98609361b5bafa8b62e860d121c0f6d0f0dcef544784e8a5fa6a9f1d1a68b30e8e8a6f8000000000000000000000000000000000a2ef5146d2658609fd4eb98fcb5d42f6c6aac4fe53597128bbba3ba3539042ee5824f381c41dc76e2c6e4dbe0665657000000000000000000000000000000001807a12ae5f289ecde8ca0a913647d44209b13fae9dd6aa8fb4365a3beeb81652ec17cf92f6784c9ca5a077824ff6dc31129275f3ab3125db33d43b36c7de0ad60a6e9cb4457aa03275caea9635f0b0700000000000000000000000000000000186bfd109ea2369818ae2f466953eddfa763960caeee9d6f1ecbf6a3f854163342c26b56d3844bfedd8f227070f75546000000000000000000000000000000001877077daa2ea074b2868e86faf14efc6ed35a64161a77aec54624d9cb916c45de81b40acc3797c6e3338fcd7a42bb0d00000000000000000000000000000000054be1650d9bb6cae6a1ed08879668e4aa4cd139c8b07ce21d40fb1cf37f11de730ff13814a02d2d6d6df5eec4afe8470000000000000000000000000000000001612b5b7c613cb66d4134aa867d985682f6a544147e7865732887d4fbb191a9f5bdc27bfbefd397f38cb101a2d68b192dbcfd8680258eee451db60f3f42f02f388f87440d00badb0a725964042515c90000000000000000000000000000000015e2b23aa42f1e6a07b0a31dd4acc27e35ce1fb3333c3f330f2d88f112375cf24e6dd5afc4d245531e4e84f1f82230ea00000000000000000000000000000000193649f3b7efb346e0c1f7bc05b0910311270cd44b5803fa16e06655d6239f609363344bb7c16c2105e20709fb5ff0400000000000000000000000000000000002a6ee30841f471dd2ef13888ba03c9cb93c85cfd0f1d0a3927205e3f57fe291bba7eccbd2352f25cc4047097fbb63860000000000000000000000000000000005482d4a47d6e381f755c4756a761f0310d0d981523afcf288e47a1a643d6be62ac6410521e0f25828f469af6150f41e5a6f194abeb6b7c1c561aa820bba658f0277870e2a32f972f9d18ca361929b01000000000000000000000000000000000178ca460993d503e496633fe5230d895bfcdd0696d817a23ae94e529bb145a0861e4448d3bd48c55907be762192a8c4000000000000000000000000000000000015d2db77105a8ed6eadc05d3d1f26a54b3d1b812a58ab49a889f5b7fcf5ec08c2eea6ad09484fda29cc007037a1f6c000000000000000000000000000000000fd5628d61cd0835fe49fcbd2f17058f23d0ffaca4474923a2c0706d9333d9881125efa2fda56234a82158da3eddb5b70000000000000000000000000000000010ce4a0bcada5f92cc8898dbf5c108c0897322eb6a467662be569d9ed0f6e2c808e214b83969ebb86c84d38f67d20754579450b7aa155a3ab61e47e337ddbcd17b197de2dbb76008cfaa09d3fc806be4000000000000000000000000000000000fc4ed0ca43d5cb172deef02704579187a480cc977737070b8ed2dce48d3f3141619f37e985c220a2840ac01dc5667f900000000000000000000000000000000047a15e96760affa4e537a45aefaaab1e0e18052f63514a9f6544136c87b7cb4a5c8dfc0d9544518adc7932ce9cff5f1000000000000000000000000000000000c9be55c06f81e87de58a5c1df8d16174cf4115f81091937d98dad6c4780a9b8dae1081f8961fadc4f994ef62927664700000000000000000000000000000000165f9b1a8f23831a91be8077b18563c7648e54caf30895983cc26580241ca7c86b9b30408a9b27776286ed9f07bd8ecd4be94f96ec4a3d4e028644c63b2577a9ef849b403acc55e42432c3063a918d160000000000000000000000000000000006edc0d62ec31b14e87b2ccff3a21a7c8d38c3ba0ec48bbb8df27fb1acf58e1a87c4458dc2b770172460adfb9a9bd50b000000000000000000000000000000000ae80063df8d41d45fc43f3aa0881364ab5fcb9ac526ee22d3870f2edb0aa379e9d81780b0ab08a4cf308d819338deee000000000000000000000000000000000e0898453feebb51d9a1cd2bda36a307ff2eebf44dc8f4c694831218c42b51f723ffe521b356ad4e5f0dbbca9af9ab47000000000000000000000000000000000fd186dbc046dd02217cec3c7894972f71e5f00e00a40fb1521659a33e079b7a1f60b026d9055a50ae18aae5757ab8490983e6618e9e4208cfbaf647592e42b2d30de9e74e4943fb2bb49109a66302aa0000000000000000000000000000000012134b433877e0a7858e6c3b95b2a1dcfb0548b290b68c209642dadf550db1c636598ac43d101b13c2d8d5ed9602a73800000000000000000000000000000000102b5de123c449a078f6f06935c9537efc791ed8e5475ffc2d9e1d098c814abe56d4b7fc6501c315edf7e64a431c5183000000000000000000000000000000000bce703ba78f45a1c59c69429d3dda18243ba2413c5eab46d469f504da975c434eda451c85357738d6c7054755d5cec1000000000000000000000000000000000387724937bfd817a65c0e0411678cdc78df26ebd4a814d92b023710558701163349b56b80e6bb68a4401f2662a0525506615e300a924ab962e0b7fd0b044cae9516d96de603ee80695718c27d7fba0c0000000000000000000000000000000005abed9305bb79a0ef1cc70e7fc2eae35a8580cd3d1ffad73d3bdae541ad546b8f74b4ca76f8f374e31dbdaf1bd14be9000000000000000000000000000000000199b29da8d161ab3061a18debc8b7400415caf029ced47131e27d81a0f7f79b6ae5e570f34a4c74fb85fea1411bd6ea000000000000000000000000000000000b8a7c42f5289d20b1a55a42d53d49510d3871b6efbe560bb4d87029b85b930f787c3a42e225006ad62c68d5f96c2f8a000000000000000000000000000000000e74aad2b29a210cc316181863e71a1dce8866a088a072ad5972af57b813a2e968a5b16b294273acd6e81e9a5be2961dd77d3e9e64e00b9356cceb62209ad48fc89e69e2214aad2edeba1812272736390000000000000000000000000000000001587e32753adc85c98cf1322115772b0e282ef4e6a75944fc86091e81aad076508e3d727f4df0e30924fff6b67c312e000000000000000000000000000000000ae96d3a1b79985e56f80df8ac4d9792229ca580b156dbbe71a9db470447fa4dfa19fc8a8a2e2f0fae28a24b7d6153d100000000000000000000000000000000114101ad0d29ddfd2fc436d2a270711c444c8c257785f4b4c549e9c795f6dd9834d3744995d2188c0c968752a7f68892000000000000000000000000000000000d30d9cc1e2273af745dd47a596a2202ca4fb655f9f9beeb0a87631e2461f29206163fd921761fde69654cb02e23505c41f75c89ec973f65b11786e186f4d42ee2e85c40f29745d9f428af08a39d5681000000000000000000000000000000001611787ba658b64467b4a28e55ea24a3b230836af6c2a7072231045ee4ee38e02302a62688d6f988f76cb5e50eba40080000000000000000000000000000000008badcd59d6d30f26ca674753ae9257a853dbcf49a5641999634a9a35a97096b6096b7b058360bec2f9476a51eb0d781000000000000000000000000000000000d30154440d8bb5fa6538953a96ba404658817be8047fa7a3a86493f02399543220758e649948b804f2daf84fb86f7580000000000000000000000000000000014fbdd62f761fa675e4cbcb61083a910bcfbd1f8e37f1fb1915f60929b047c970b87be0730ddc20f9716ed8c9bea7f19c70cfb76a04d1a9e0d937292e5553ef371e20d5d3dd33611edc0da178e2e4a1600000000000000000000000000000000143d1f811644e3a51c735b708cb2f8a2a90311f9971c90b9ec8e45bdd6488638b6851dbc882205263887b4dd5dfb4e120000000000000000000000000000000015692a6b06e3bd3100e149c6be3cbf1566fb24531eb29036fc48f85d5da83316a38af4e714a17552024c1ca4a5e39d9d00000000000000000000000000000000172b9c88ed9a1fc2d5a7f147d034fa243d420b129343ff92b79bc4d836e380e5a7e388069e9af9026485e9d3f41a7aa300000000000000000000000000000000012e8453dc64f72653c4e9b3f6f43fdd01b896c642d21604f992dc5591f2cadf71de4099e1075a4ca4b7539f84dd5a908db878b7f5fe817599add432ecf262f19d80ac834bb0a0f983728f6e2c189c880000000000000000000000000000000003e9b6d23809781f50c0033e53d245dfebbba9e0c4d9f676ae61b80fb6e774509f62fad854fd9ea841d9905d48d943a30000000000000000000000000000000016a1ba62bc684bb1848b0ccba59597b19973b56fd9b1d9d06352de44aa79c6bf65409dafb54f859d4a7c32e188bbe19d000000000000000000000000000000000e782741a4b16c5838a8f6e542135221ab3c6ad180c85c08742992ddf0239388e273735eae76c656e61614da386ce2640000000000000000000000000000000001cf6752e88990c221af94e18744790c30aa6a158b10a1f6a56c2ee3c3f0fdb2fa7213f16764ac9e9f4f65e99e715ca170751fe88ad289c91dfcd3c3c61ce1e33f4146f03fc0dc77cde9b32b51c75fc0000000000000000000000000000000000810b0175d781256053c3c9188cee4f55620a6624bfbd2f4d2e70ee68a105bc7b60bafdb76794a048e9f25da976390d4000000000000000000000000000000000716095f8fd72d9350ca62ca3ec34d2228cb563d4e89b19b152787d42fbb750435aa6233d0a97196a9324319837be14f00000000000000000000000000000000178e939d87c37d4a2f49e1e5596945879f2f0f64419e3dfe2afa06bd58098e1ba57a9b60c32cd6527481ab3b325ca827000000000000000000000000000000000aed480a1da482e40ae610a9522f0a18399b0130202f9ca79e3573987f5f7ae30724feddb52fdd05817a96f7937aaf7984bf139cc0b6ac94697b7dc278063a74e478d47528da3f84f55fb0adfd851d09000000000000000000000000000000000133adb236d9eec3544fc91852278abe37a1da0f32a84477c0d93927d64af613b7452a5f64ddec7447779f42873cb157000000000000000000000000000000000f6bd940b51b7ec5a0d92ac77a55c296215e970e9a499793864dd69c3a8d583403e95c08b719b5d8eb0c37a8476d3b960000000000000000000000000000000007d4444062ae06e65b45c6105af53c487f6b275ecdb36f87ec7b71d5861a1bdd6d735e9a1fc5dfb476ab8c13a98b570a000000000000000000000000000000000e043cdc87c67157b5ea3e5ab1b243aef479b23861f8cd823bced140ee03dd1f8bc6cebb4bde4683ac3340823f4d55b8d19d9496e7ebca44354d5c6e1f6b8211eb06ca23a6444c307f92f5bc6dcc2dbe0000000000000000000000000000000018c35112c27caa6bfe9cf8ae55f51755ed349ee7e7141c99069dea07c21a6d8634778a91f4dc3d17da04966a9eaccab5000000000000000000000000000000001800c8a9b146dba27050ce63e78895bee2016255c59acc34fd5e6cb926c16a8fcd2e8a579fa02559b3c571cb08011bea0000000000000000000000000000000014afab23fd4ea54b1ef576a12a2a62d42b493612ef466483ee8c4e62908486c038598e72dbd9256166960db73259def8000000000000000000000000000000000899a99ae8b10da4bbffb6590d79aa33bb2adb2444a11627f05622c732b70f90cbd2779362349aede5b591e84b53a8a06940e3509e1fb090fa787fdf633a74380cd5de722678826224641e46a6e920df000000000000000000000000000000000567d6458d1a3e012c63adc8b9dcf32254c98c0b7021ec6a8d579dad47d501715d2e42a0837def225515d663e663c4f000000000000000000000000000000000178daae121366ce025c1dc2d3e72068fd40ba9d54b2b3724f7a2071a59d4f17d4766a82364540bc31a46398c66d0e7da00000000000000000000000000000000147b2851311913ea53662082acfba785d21915cf00cd154b1b495246e109ac37c3fb6c63aabc4fe71a0d37c81a40148d0000000000000000000000000000000000122b7b1a81888aee37fdd6c23d31c38e79f28945cd1798cee3f4d674e923fc68311eda8ce45a561abf9c5f0bfeb4297b27d21c1d6e06d9fba7b61fb87d364a7a6252c70b8ace2d3679ed87ce0fcf7e,000000000000000000000000000000000f5b941cda417cce69a30c1ba4a82cca71cb4b953d06d8e545c1b792ae22738dc006627da02b4344bb8be93a5a0dcf07000000000000000000000000000000000eebf4ac30fe0ffb905f81577466889666f801d4d6efe0fb8a663fbf1cbe76b2167243edfc6cde3f49d97d3040a9507400000000000000000000000000000000007ae6a99b86dc7ea95801776589472547ffc7a623009a592403a9710ca365510d85bbf20fa4519ca0e0ca208bf86a670000000000000000000000000000000004b5abf778c72bcc5b887855c582c042a4cfff489b0548785e4c1b735b19159be8a3f4cecf34c769a34cdefa722ba783,240480, +0000000000000000000000000000000017b47384e6302b140118d0a9247aeae2091607ebb413ffa232223bb42d16753b2ae48e5ad0265e33616b25f0c4234be600000000000000000000000000000000167be566292b835a42ac7c099d80e8a0b5d4ff91d842d4ff6026876aa1570ef9641e9c0cbd44d8578f6a758717bad6f10000000000000000000000000000000014f692d195979abd9c55ac132d0def925d4e158fe946fa7b0a010c475d60171a0951d4b68ae3c463bf1136600a1ddaed000000000000000000000000000000000ddba1f4236c5200aa52f8cb7e15fac1f20cc66dc65ed180745a3eb8308f2c851ed6c1e27e1507d3f902ce672d6f8d24facfcdf87c6ca0506b070abff83ce7812181c31729cc534497aa8dabe243351300000000000000000000000000000000023d08e255b244cffe911e43b9b48408f9fb3562edc2c27f405bb657731c885a58392ebbde9fc80cccee2404cc8547ec00000000000000000000000000000000088ef289adaf206afd2b72c93049fca2cf9292bf6471133c64ac4f42015b97bb9a23f6c34653e0218fd0abdefa56bcc60000000000000000000000000000000015cb78c1440f74b17125c547fe7a37611f01b83b91a351664c696e0f647bd2db3ffead880b96a327780026d74c9abca30000000000000000000000000000000004d1a63607b9a5c9ec31168d85fbbee77cea0ae93e98c8c1dde14d0baa72f91042b2b7ca489958344916ce79bcf286456546fa692d9cd61895526282193c90148099e2afa54f7903a5431f932bd5fa06000000000000000000000000000000000ea6cb7ff6a7f4ec38ba11e9945eb406dbb8517585fef6cdd64edc970efba244b071fa162f7c8e184acbf71c5d1e12160000000000000000000000000000000001ab80c0dced33cac8a6a085efce71dcd7021f6255684bb631cf5c1716021bece57b900b819e6eb6f5b755b74c677b6c0000000000000000000000000000000005465fdd51352cbcd8b804cd509526c3b6232976b8278cec3b7db7da14b77f78898c6240c30943d1418462cb7a5abf8f0000000000000000000000000000000006b6caa6a0d5f2d671b10217c0ce5b3962b0c3edb4f2918497c316ebbdbe1a15c803d7fc3413907346f0e7d03920005aa9c1460c1cbb2a552e3452d5c5535868ee9c2952ec3fdb52dd826c16ae3d00bc00000000000000000000000000000000170db23154805a04013052a388e14b5da00e65b35b8ce2dd967213a74735dcbfd28782cef1ffe9d384be3ecadd101e9300000000000000000000000000000000082dea309092976408a379f1dbed9d8cf91f768e2921e49ece458859c80a1d9efd4d5e588470bd669c777d16f9d2e7de000000000000000000000000000000000adba8ef34e197689228e6c4e13be75b3d4732872c99b865ce7733b7a42034d6d4d7520ef7ab712f60f1ff87bc4d9d8d0000000000000000000000000000000005df0788ec39430fcd0625f8e030d917d8e7c251ee6e3b0e79fc6fa5f6fac2ad736c818bd833e58ec61cfdff52c9c6ee2c36204b6a005a64819b06804eb94c311d78977b557e7acfa82e147b4d6ec62f0000000000000000000000000000000000922d8b5db6e415aa3acbd0d6065db1b492c92313260019ef1bda0fa091c4bf091de95846af1edb34516b1abf7d278e0000000000000000000000000000000019af4ecc4f278315ed90d67cf4d22ed6fc9af5c0d0ca654f6a74a3c4bc98588bf5347b4536f36ca8b4750c18464f9b7b00000000000000000000000000000000021eaceb11638bda8b4293991983f11cc60c1daa2287f4b4a6066374bac82d117ac3ea4ec73afc4372d254bfc433b8c3000000000000000000000000000000001037fe26a10305cc5dc11a65edc705be5a0082656cad53e63038ee57a79e16075df54331233229a129483c34d6dd92ec9160c5a553479a10996704c3eda8e57be88eaaf5d1efc8371e7e10d7d106e4810000000000000000000000000000000011e63dc251a5a1e2ec83741682d90588b6b185365b33dba45458b1f56324a4900b04d61af155a0edb0bdc2971b7aaa210000000000000000000000000000000002dc1bd5448a2ebb9a02509af8777616ba9657bd3be65519233f0187df77c49fc931bbd3ec0ad5856b2ec0dfde476a870000000000000000000000000000000019f0cf8baf100451313711bbb0a0fa318c14224933897e74fb727b585cc8620b7d741c9ca2f0d3cbe14a8749aa48ef3e0000000000000000000000000000000018448fa9e05f87d4991ae1c248413edc9a8c3ee789c9c005e691bfc9003191ff469e26db9e42e5758fac79309a62942c5e5a50e5dbabb7a56897935683f80a5b16dbef3c23461e241fbdfceea38e3ee200000000000000000000000000000000109b71c19cd36ef3078bbae25ce6d0e8f7b58e129407fe68ab09aa747bfb3e90c04ab804fa6b7a223c172146fdb14683000000000000000000000000000000000d297750ba112da88beb84b8bbf74ed134b59fc9496da3045aa6dbcd97c68425fd68b75508de113733602a5565f4c8a600000000000000000000000000000000149b8ba6e05b66d07b353f46ace4e583bb61ed18fdbcea0e941b8d9805d3168040186d1c961add494f98e4e7fe68824d0000000000000000000000000000000013a6877bd46557d23b9aaf371ee5a101227d7938c64503b04b39cc6cb4e8ddedcf5cb6865439c9f8b1bfebb807ce52e24a95b293daa2761cc456b9667517f499c4d9eb9eb1d82237e7a7819b5d44f7a200000000000000000000000000000000073f440c2704fae6c86aca3cee34591ec03c362c2c5153a5e82c7bcdece2af0c58a3484b448c8bf4da851800ead959df00000000000000000000000000000000075a2c26372b482a2420bd3c9952fdbf9e5fea906dc8a4deb9691f8745372805bacd68a4838a3fefc381a2ce946ed1780000000000000000000000000000000017575b016435782cd09901afd2ea6773b11f5a983bdd19d14668d75362f95d055b76e5bf6966b1bd7bfdfbe9a939e4b60000000000000000000000000000000001569d74258298fac89d0d91a9945780f4c08d7af7b942d06255ae590db6e8509c908c16bd2c2bb634279debb72f489b5e22ef32d111261dfcb5a2e8d23c8d920f013bd9602bbef45e6d4e0909abdef20000000000000000000000000000000017180e36b925e2ce23c46813d96b919ca181481efb5d1666c4a4e9c8031abdd9521eb8228c4e3f16de0b33da4c73588e00000000000000000000000000000000138965bff7c573546d80ef7efb3d45e87ed20f59adb0cd7ae148d09a97da7feaf1b0ef2455ca19381762768a7d82f486000000000000000000000000000000000360bd29c3f07c5b560e2ac226112a628839da9db18b052991eb2d9c54541c1b5ade9b3c2d7f446ad50050531228120f0000000000000000000000000000000007105978bcf13bbe2bf5c8f7d165998c3ad99b6a2794c90f5b61fb7bf2472d307df8fc9f4afe7ae1e40e7f0eee8ef9466e687c0ac8fab70de2416642afa1553bb38183d2914050602874491057f78786000000000000000000000000000000000f4434c5180ad10cd45dca62b8da790cdb912c255c0f33950f7039e3885b38fa9e9297c7b0a875380545839d8c4d4ada000000000000000000000000000000000d0dd1429e512884ac209f788b5832d31649a78a8966d3348a93f841be23c8e4e42d6ff0d6c27e8f43daf495c9582935000000000000000000000000000000001307377f55dfed30ac1a406671af1895218a01d063b025d25bdbc53f5f9d535e4cd8053c09b2cebb25d3a08365ab8ccb0000000000000000000000000000000004f5c06f505ed15aa7661249b7edd71855bbf47237e049aa951e1ea3ff88f98591518bac975ac628e417892f8e9e5523428f1a27ea15135f044643dc36a3f9c2b4446a3136bb11f696b0a430a7454b3f00000000000000000000000000000000083336fa0b79691b4875ed27b2bbd2d2586992940356f6ae5ddd2021c5ddd87f07f0a5c1e8d8a2654b99182cc2233e84000000000000000000000000000000001880f3824f7cef95ae5743de2e17191848d8d30f0469f455461c6559ebc75a7afbc86dfa3ee17f5470f74018ec335edd0000000000000000000000000000000007c2b26353e86223e5dbd4ed6d59f1170b9cc9dc600fdfbc6c73b96f2c667a82128b1ae5af0542b11a7d1efae87c75610000000000000000000000000000000002427b7eeb497a20cf15c10513cadc9ea612f3ae94e2ae833d281734e7b5d1d50e240659ac01da7864a95b4cdcf88744ae21ad8a6c9d75b51133e81ec34d66ca70a52529c5c3a2307b0e8d6f1c5e7d97000000000000000000000000000000000e72845430ebfb84f8e3cd3dd418f6dc528bf521aca4f9dbd798ed903ef0ea3cf21dd1409aa3759351be32b21d8e8cbd000000000000000000000000000000001457ad87f0957006192dff7d99815c35adb3635815e5d157542b9f52f1e9f8c0143a21a3be4dc1aea3a895689f4a316f0000000000000000000000000000000007e8544b1037ece2e5a9ea387e0f43b72e895e9c2ca4d205f12bf6df0b35ae62a4d62756221d6fff65b928b7358f48b00000000000000000000000000000000012c5c3167f6ef118c4044c0aafc85a337d305437d694a7bd6fb406dabb7364d9e90d74a8b327aba971421a5b3dd5d06988a23b118179ee2c34ad030993a2d2d70375311b95254c44254a32508abcb612000000000000000000000000000000001995d7cb79da7b6c5a0c8ccc5ba075d8d6d8ed3cfca85e8ecdd2b589986fa58c4cd4f045983e9184d79173678d618f310000000000000000000000000000000000f9f7f6bcff0f6fd621f3f8fcfebac132b3f0d52a34af33bb9830bd714d2982f3cc6674ad6ca668131a5062e5589df90000000000000000000000000000000017699b298a46829020e0299ab89ab6411af0a602dffb0e149053ff40ccaec71a908da02c8e611723cd06c16a8e5c0f2d000000000000000000000000000000000523b287383c1e47a6f31d397359941fe0bb8167aa11604ff8569969eb5ccddf4c4f432d2b6fe6f39204020e850d4f2b30eac099ededf0087275d1af828bbf79ef7fb0e77179a068f2ebfe4c749a98c90000000000000000000000000000000004760120239593cae5bdec813735ccc99a88129c707686cf43efbd48fb08d8da3086879a6042bf118879fcccce0736bc00000000000000000000000000000000105b8191431f701b365c66680cb4eb267681ee4da17ba55d47cf26d21ba1c0c3eeeabcafcc79dd87b6457bcc91e9fec600000000000000000000000000000000126ab502f66e732aabe02fdb2f7a665a9a43f6b4ff21c22fa976e7e434b08b606e9cf0f02459fd85f5a80a332fb3a62e000000000000000000000000000000000b2ef01adea6c00250f2f14c98ec6d6083c45019f3d166419e3a137667324f80c34b6b72e991daf72e2eaf9985d0f9287e8dcbf708682225fe3f71b7a687da23de5ed188e40585be0553358012132577000000000000000000000000000000000ff22a0db4f1b1679bde5853a7c2932501f191f4a9f25eed968a796219cef028e26070851a9036a05a04abd73bd6bd4e00000000000000000000000000000000097e9310749f52a4b645190069f4d52315f0eb2ff9cbbcd31f1781a68b2664bbbf27166e6e74fc2be2e5b1eb3f3d77a00000000000000000000000000000000015ca218d7d128095bd4f4b4f7bcf7666e92b905e551dd22745bc743ad0783b6ac44b841f87d3deac44617a7c9a341c55000000000000000000000000000000000a1cb723a4c378e5db2775f4dde9a6887ee3313401a64130a78b90d65dda3a5d9c8bcbc1a0d78c310c869a7fc4889954532cd42a9b698a2c2d22b1a620a7ec60daa9d1eb8ac36894603be7bb9b5e37be0000000000000000000000000000000018b30cc461a4e1fbefe209a709a21ae201bc6094b2d15f0d6dee5a55dd84ef56b62ab1b6bd513b27c84c638291f4205a0000000000000000000000000000000008a6f2082d6d510b280a270c09044ad31fb18b851ad2b38859138c9c2e4870fba6b607f682a798bf21a13bff116014d200000000000000000000000000000000150ef352d494a97d0a7ffe44903aba1611c8d81fa2788c0f42a6db48a71101e12f07318da5ceb1f0af3aa10cd4c26341000000000000000000000000000000000ffdf3b133cc926684e4624531569bfa09b1658e29ad9c3efbd5e9d18353ffbbfbf23a2ad80ccee88f8fa597416d47173ccd5e19892765e549a63238e664c732af781fddea558a117cb927bc4a1aceb5,00000000000000000000000000000000134f45e5409998e657923ca76ce92b7d2acc932308e0694bb22f121f8324d16bfce86f96c67111c8080289eada4b4fb40000000000000000000000000000000008d9063b7845ffc8400c0b7585e819043884f92e28f7e3ffa47a39e808cdbb034ef4230b6e19bebf083e939b6b686b0b000000000000000000000000000000000e95f8fcd6b5bcc9e00a580a99627d92fa7486ff5ea587df5dded24d1b0bb76d339f6765a5a2058a8e227f633ce36e91000000000000000000000000000000000393041eb33f2c63df3f40d8ea1e1a9eaa9eb0a46151294845e542054d503ef69b40b0b676b0e4f3e08f4d26c36a5d4b,240480, +00000000000000000000000000000000042a220276f12fb4e81c40ecef3a0d86634b4d1c0acfdc463df4e7501289f0be385e03808d44be053e6ac98f403a8a930000000000000000000000000000000004e3dc92e155aeebdfccaaa1d24f49efc8b02e4d9ba8500a5b953a96e0fdd58519bbf1c279750eb8b98616e6bb9a3f6d00000000000000000000000000000000086bc212a83b09c7361540349767896058d5567d4342c607ae9c07fe5f123d9aaac95ded6cdff0825edb5acbce3e2b6d00000000000000000000000000000000062598cd6d5680a155b6349cd51d636c1350746d17fe0fff5195f164ba2fa51cf52f8662f43d555f7be6bb8bcacca39448da17551b2369b723bf932173a9167663f8389d2461b763d6a061df78d7ff1c00000000000000000000000000000000193e2c749e5bbe87dd5c306d822740969cd69ad6e156c323d217c08b18bb3f97c85aad63aed1e3a455ffa1b6d2a670340000000000000000000000000000000012a3dce63a88ae32a746e3812833569959021e1dd9518621793308f8f11d04829b2c3d0e0ec39fc48dfb8285b6852746000000000000000000000000000000001235488d01c380e91872fc57cbf618c3531a6bbf6cba9dddb9f07168cd459c9e866e44e9e5336369cffd8cc3a36cddc00000000000000000000000000000000010d85f85d6242b63f8421e92f1c37f64d33fed67e0cd3dc4b2b2cb4a7fc7637f9e049fc959720eae6d1f452159d48b78def52379c8b9e7c24d971c3456b85b51a63ab03761ec66c8dfac1018833e05940000000000000000000000000000000010889825c752d0ae8445a1d0f3510135b9672c30a781788698f637c2f535e35788d76492edce8ea091223d016b5cc141000000000000000000000000000000000577175035c86c022e634ccc9a5beb96a36aa068cdc36e5a4fc2028d5dd099c5296d30a916d3b720f2e051e7d72e4d490000000000000000000000000000000017b46e49ba08a0abb9394479d693d8097a140094d0ef1d1ba7761fb601a686b0b2b4d49abc2e393a99c5cb760299992b000000000000000000000000000000000820f8e52c1b09986a70bff04909b044f671c3933de43a6bdfcbe3712310274ed880d7adc4947490c7de095ea651e578b2225be6985b9c8fa59a2890da56427612a4334937761e24a33d37f0f951a794000000000000000000000000000000001776b92f683069fdc006904fef8e91f716d9f6bc46306b042228088545f0e11a41b40b60722d4f0483250391febc0ed90000000000000000000000000000000010d5052ef2504115e9d9d4ca81c7080c0868cbed605dc7673f7f94f5959c793c96aa5334175e58499102ff76f974ced80000000000000000000000000000000011d1e719bb69d842df4fc23e8dc4393067d00f6fa8ee42d89b462a546414b91f68dda5378fa093b3ffa764b5fc63b1aa00000000000000000000000000000000099d0784a200e5d2d38773912cf1a49e813c871ede8c50da03abff58ec1943d2adefe66bd2feed1c57f5a80253e091d0a64ce8ad619276bc7a00cb49faf6cc84b917ae6b37654363f5719a727a220291000000000000000000000000000000000d7287adbe0bc3cbb35ab8bfe69064faa83e3e64d73a0c64d960949e10070081a99c35d1dacea5a3b9bf312745acd6e600000000000000000000000000000000034f1995eb8631e080378b22a51ace902ebc9da4589c89ab557b6aaf685fcc74ec1fcf95f6b9a31b7a45cfc5a1610c640000000000000000000000000000000009f56712a46c0fbc199c12d5eb7abd60e660e2c6d437007c34954c6234a0496ad0adf68cf759f8ea30980c9a78175e1a00000000000000000000000000000000073fae1cb78c776188190a4d7223f7cdce9a36488193dc06898919ef4d5136099c3185d352028760c753e9eebb52ac240b891d638d7e76e0dcb230b1f9a7c3b35b35193c43a6c86f345f5a5bc9c708f500000000000000000000000000000000019944fc459fb601bddf10a3a7eb55f34713d396c3208a10089b8f21f4bf0a5e87e95ccf73e0bd90474d3e043f37a72300000000000000000000000000000000158445bd2b6d396a390a0bd5e26256587f980eae84d7a592b2b4d788c452d312b854427185a770084e1b4c7898962e50000000000000000000000000000000000ba44a1b912645354da7d8d9c694b1d5a9ff2d642fad31975171deef3adb0f8d92b2d3a8bef6ecbe0b8e90470b3938d00000000000000000000000000000000012a040a72ab035684bfdf57bf473ff59cd1ee01ec949dcc6066e5c8afad775ed55123859cdd74c7016a092bade7f991b571175eb91888222085fc2dfe0f4401ed6a1fc5df86c0c6b8e44fba6454305bf000000000000000000000000000000000317ca8fdec8c7c56fa3812157f9ca8e9bbf91013dfc7052c0795a04a1b4649b2147d9cb1a61f2c114a705e5133729920000000000000000000000000000000012b893d50fe5ea2eb528d1a04bc8596b10d4714a0dc38bdd5f0a275c07c846970106c3f7b5686685f5c809e93c57e2ff0000000000000000000000000000000014f018a0d13c4c494f4a6b7e836f0f2f46c4b7975d91adb93616a0555910f53574add03b905000f8492465c9b5488c1300000000000000000000000000000000146eb4ef1103b525dfb5c31bcb98e550245732fa252a814824258093a2397d1489df8ca0228d4f5df0a00d473d1566c454c9e7f7ca14c66b8431e25e6eddb4f20507d03bf124eb607957ca2f43a0c17b0000000000000000000000000000000010e9962dc19aae8e92abf32fb9c8eac44d77f587159af4e3b3a080748322715a958d953d3c057999839a47dcc840076a000000000000000000000000000000000ccafa9761e654ba54a46afff51384f1c6331264082e23f94fffd6c31a1b1b568a391eac79417657f40ce2fc9a154a670000000000000000000000000000000007276b111c94130b2608827156021815faf2be29ae42c454f3e2f95de98d2f5b98cea3eb18335a8fa00e5464f8089cf300000000000000000000000000000000053550896e867e237086098f4493caa2520e8c97a05e14d0ab7012d37b7fbbd42a90accbf0fe2ac99e78ccf0be5c9c58000579e1ad83015c8f02a9db5c38d0220368a80b309ee45bb952cac824817b6b0000000000000000000000000000000016b5bca8537059362147911da9e69ad3ecd3b4a7c43ee7d6d809f46c74c16bc7d69bfb5d7c727b4d5d8a356a0458b59e0000000000000000000000000000000010f3a7eefeb3033a733af7d20c3c5caff4c409305de8d71e08cb9cefbdfacda41bb975c92c5e5f2952c3c1e2bc6ca8cc00000000000000000000000000000000148f5b2bd65b71184ba6974678f709c5f9e3f1a020e3d4bedfa5f5f66478adac47f06ca2626c4a759b5eae09756cfe49000000000000000000000000000000001301306d1259059b5567154ef6e4779fedf98c29ea967ce34b78147c5730f202e1c12d5b5094219bf85fa62834329b45909a45c8b78350e3ca21697e9f56d5fc8fc2a01817b78a7f5daeda487768ed1e000000000000000000000000000000001741f739459f5d462fc9ab55c68101a5a3f2741c05b4c3eec6959b2aa5e12493a19d1b33a9aa89337add642458089eb6000000000000000000000000000000000300d8b7988522706c0690da52d0a67ae41344e43cfa05d22feb91eb8635bdb970810e993e0ebf8fd63ab8fe3e048d660000000000000000000000000000000007c003cfba125692b88feba85e7288bf61bb25e04b1462f7a39b4198737010224ce4b73a320c81b1f70119af34d381d1000000000000000000000000000000000a4870c9de67517f4353de23af21fcfadcfce55365ced33a61a19e5de52f98721b17c6eb382970e7c4acd81b80a7bd2f6d4e2277da617f0ad530b6209df6264e1288122b1b4d92da04fe334be17bd8320000000000000000000000000000000002eef52fc72d5aa0456c13808ba548cb765e11cd0bfd0599544793f57c8a27ee90880e6577af1b76b3fe32c4e71f4104000000000000000000000000000000000ea99a4f6772f8114cfb3ae9dc20f11a34880a86088511e5b7fe521d50470148b43f866eb5bf4f67c523266bb55117050000000000000000000000000000000004bd802b889e6d18df7dbd65f39a908cf5889e14be51b5ebd423ccb63e4e5b35e429eb0d4f384b811b47975143ea2ef60000000000000000000000000000000018dded357c546d709beffff2da0c08e8059c720023234c7b53d0ae85750b3e166cde7faf340697b546b8dd7c13b1ce7bdcba6bed6b8c42240c01df5fa0ea81dd96168c6d98ee9d5d4653edfa5172eb28000000000000000000000000000000001405ef521bcc60c55f8551fb2e2aa7b10117b2f96c03e8535e5bff48ae197b7e5fe69a40eecd25a67f430ca02edcc9d2000000000000000000000000000000000477d85a7dfffcc5a2a1048205362ec42b268e5fbd27ee7c8d4ca77b5c9db84dba482bc4b164f92db2c15cc518b3d32800000000000000000000000000000000060988548ede00aad3682fe827d1e993ed1cf118bec7cbe6f69bc160f030bf87c299d40047a4fb5ee27dc2814649a4580000000000000000000000000000000006b9e0579f82fcb8bc149e40b1199f5897ea48ae5eb58abd2002c923efd0f5275d24a579bd904e49b7447c4a03e3fbe423d168e01657e5c2da89a27e513bcbc6620b8c6082bd39880619bfe2b3a7317d0000000000000000000000000000000003cde2bfc5a865cac624d9018c37c1b5746b5394597d79c171b25f84d5fdbc76bb90ba5cf9db14b3b8e62ff91cfd79520000000000000000000000000000000017596885262075e45db62ca68ee5b99d12223bd476e36ed4ddbf5cd56a0c6e9db5d79e7f95b96b1bc323d7c9fc5447d800000000000000000000000000000000018333858871dd41cddb7ad2f179f1f341b2ef20bfc7a1d3cb235e3a1a181e0da7251911886f0788e0f868e16520c5a200000000000000000000000000000000098ce44092980cb14e89faa7efc2906051c9a51cf7b2757dbffa49fafa3a9ba145f809f1212c27aa620bf062e839f83c2a76fafc5e8e33852bbeb7ab8229305be84f5474427e0c6d2ed35c7bfe99faa1000000000000000000000000000000001180d554fd523a51e0decb92e0134c6064a17dd3aa7b11d590b9b6022f76763b1e20562da21e836e65374efafd78b77e000000000000000000000000000000000488686f793dde899a3f4936f07f9eda7918450966ca85b4715d6fee978d9d091bae1b5d2d04943365c076a849b3359c0000000000000000000000000000000014661fb2d305ec9e63d63e9951d0f081aeba99972b094c922d2797a1100759cfe150812821411205f563e22f01ef29c50000000000000000000000000000000013dd681200608466853cd3bfd20f146a6383151931079654962684d6c6fc3bd6900bb049483c1ca6d2819da456f67e3be3c7e4e95167faed1391e269785918a207490c6d186bf2537c02e52e414d564e0000000000000000000000000000000016c8c7a2a1a76ec05770f2d6c8df35003104c034c76323fedd49663daa759caf2f4fefbe8d44b3abf1dadfec2a06cb45000000000000000000000000000000000837305004aba2e322ae29e8f0109f1c756a44b21c72733019e63ff9886a639464090770d12d35553f0002ad028332370000000000000000000000000000000005c8f82ca2d4f6785e2d76ca3a3d1ac67aedf78e9ac833c52cfda6289e6f5d7a83befbeaf753abce12376889caec312f0000000000000000000000000000000013595cdc9181ca70845c613663367ff774f073774688dc58edfd0c58de5ae12df5acd04a673b645371940d7f7e1601045d335e3d96a9b25be7f3916e92fffd75abeef5b91a1ec577ced52a96f6a9b10c0000000000000000000000000000000010f1b8b39ea8ffcb6a96bacd1c00b413c93d3f8da64dcf9257a7cf0264831be23ca63ab8d3d1cea21ed8d83ecaa3a0c70000000000000000000000000000000017a9030fbee573cb71330007900723f85e9e82530283f713f72e68c1d9a5ff9552d0da469a4f38b66e30df1514f922a40000000000000000000000000000000018b9020986a49213d4f3b4b052cf2fb65f82b9bc2051f20b399f2784b984ccfa2752ca576d352c7d65ab218bb8d5df870000000000000000000000000000000015a375a3711f5e9f85ad7266b2d307cac09ace9ea36e149dde5e0d5acdbac3f62e1cecba8be51d88f2143c3070eddaf0fa563a70780837ffcf9a84456f0b4f6eda0d60cce6a8538ba10960eaf17362fc,000000000000000000000000000000000b668f602b9f56182b74be413d36b03d2266d7165386a7f1f0d25d336d06d2bc5659e80e54dc71f153883292df1cd8940000000000000000000000000000000013151d305bba39734538fe9a2717392bcd134ef1f8c1879740c8cce981a2d70c94b23f1a71a0596e7ead55a55eb186c80000000000000000000000000000000000e5e7c268f93d8a9d3ce45db2a95be906483aefa3831ed2ab2aa357eca0ca231927c0e5031daa200784cba33b96e51d0000000000000000000000000000000011d57d9a9123123f9fb56e990626346e5c76bbd1a4b3349c3f7bc44f05a899f9c4dddd67ce5a788f85b4fb172385faef,240480, +0000000000000000000000000000000006a7946b50749147573991c91f13cdef4e9558be37736e3d990c5750d31ba9711721b88eec529ea4b1dec1c935fafa9a00000000000000000000000000000000078d8a565b7f58b915c220882a73b6aaf100f2d54cce2524cc3a80d9b526c3058903668d17427a617ea045c3322ec502000000000000000000000000000000000733c6562cdcb28d740c64f50ee9d204af4ecc8de2c1719fb73c20f2580fcf01e1e494faf4386764e03920a4162049fb000000000000000000000000000000000421365fa828affe963d145d318065933d4d865f2a3d24469ab0db66dd09a574945f8a8b622d079a7ce1c6fb6c795a8f6e2ee781da12b984e7a08730a60f50c41cdd7c7c8b3f1f30f721923ddc34fb79000000000000000000000000000000000a4fdc68bda287bd819ebb0a296212ddd19cc76b042e134f1637c894ad64bcd8431392c9791f2eaaf94f6c8d189846760000000000000000000000000000000009d974fcb46fe81d81d62b24b805ab5108c9450e162454c3260ecd0d5356b7c263be5f78f6214cc7254e461166910d23000000000000000000000000000000001081fe3579cb4d8a7e7d43ca8cdda28e1f9ea8df83c6069f4162a2a0e68e0d5876b283193649018e754c5c8fef101f53000000000000000000000000000000000ca4faaddc4d14a6648e3515a8b9028190c17f771c7de086fe4624a3008d7e6e374c967f303d9511b9da1a95409e3cb3d51e0b65be993ddd2892ab8f47eab78352b177be4b3fb68f47c65f5c59fa866000000000000000000000000000000000117318e376f2c130e5bca89b3d700fe76e9603adb22a5ef353bb3b5a8f641c85deb4640fbaccf94e025a59fbf2a41370000000000000000000000000000000000433428497ed89a43ba07d816df224809a827194ca899924c3844650a3800952cda8db82f2f8e513994ed9893fac747400000000000000000000000000000000064889f1cb7d6ab216fcceef7c4abf89be14ef93be2d39bbba2b74d06999dec5ae1941b507709d093b28e030700cf866000000000000000000000000000000000957fcb8658497802e78b8250373f77acc4ec47fa2c87e78adbb2daef70240da640a7945895940f76bbb80bd36b4ba24fed4dd284df98923bfc3c41496d6e64a10815a8c474275e0cdbc9ed26e92b0ae0000000000000000000000000000000013f9771c105462fb6b975b0b2fd20d0accdd2d95d879c8019b08db394cd785ed9f151d0eb1adeaa63bbc2686d1172b0f0000000000000000000000000000000002062a5f2db0a01114a1c6e8c739f80f598f4e905952101a244853078298eb443be6839b59d4f0c7745b739cc89ad8220000000000000000000000000000000015b5485439f1b94fbb3a8f5ac6197f0dc0577863f39c44b34d4c5437b6a82a704dec17529654b3037a9ee1ebf14c8d8300000000000000000000000000000000154d750e2a660205812d428cfe79aef4e1059f4e231024a665889d112af37e6e17e04cd7c926b6240bf2f616a1f572dd7c36ec97c1eafc8a20a772fb7887d75568047ea32458b9ce74ad9ca058129949000000000000000000000000000000000231223930956bd2d36a89a0a0a47aa46b4763919455ad3a3581439d25a82c176569698fd5ca2b9429793ebb16c98e50000000000000000000000000000000000b5dd675af51c18d2dc76e3103da4409f6e8c1cc719a622d4a33aaae3f23e529c78b63c55b67fa999bdcc7095a4ece300000000000000000000000000000000010c971be55cd02e4c97031d3b25acfbf722e47e5179beb26eafaa72d4bd5f47cf280a99e0c3c4cdac05bf1572d01fcfc0000000000000000000000000000000002c1370919e6445994df1e25ff4a79c8cd8805f12e5d8c781e58f04dff68a97424b35d162d875ca2b3f805b4cd6d1fa641b2c0354d2f7d92b05043f193b718d78b457ae76e19069c8e1c2b77d7999d6500000000000000000000000000000000169938b4d3c859f97a0627bd1a83fde725eafb7ab77b22cae06d2a776569236d834702081e78d61386999c938c0259b900000000000000000000000000000000091e922f00828488e324f9fea652ce1edff83d9f479e843ed4bffee27805a5025e7a150719b354b5e61f381ebd24d4ea000000000000000000000000000000000334ba8044d7d47795b59eb089502808a7ab8f18e3d5e1cc49acdb5020b3973fd21d6d82557afd748dad88e45a7623730000000000000000000000000000000002299bf949ef249b5057c103ecd149444635b4f636a2fd0d073484404c1ff4ef71820260ea6529bee6f5b07f2ba94de35615370a76bb0a5f64d61b97bdb045b9669f6a0b7644b101d21a50483d8b04dc00000000000000000000000000000000076ab7838db87727fd653a3b561a2a5594518f296284bc24a7d215b1fbc0a6492d425078fd98f92a414dfcb3c92cc1d000000000000000000000000000000000022b71fb467dbd6d9b130763350bd06f52d20ff2cbd46cdea5e8b1525fd73bfd08f5ff171f9fa28050e9a3b296d3e9e00000000000000000000000000000000007e917cef0195fc589317d4a71c14022867dbc0db26c653052e2e382d0dbebe67a0f582bc0a27dd1dfb4703c545d0da30000000000000000000000000000000005b1d8651b86a403ad993c5cea4b6b82a0f8a9f8a59d4b94f10e68e9538a559efdde2007736aa9d04f585851a89af88fbcc38cfd3c6bdd32ed1d583f2bd14e175d61448c627f195559b751aab1ecf7cb000000000000000000000000000000000653c5f5b2d97239821d173036929dc716e78d835a80af55868dcc3e218bcebdc2a052d31f6a573572d13f3bbb14f241000000000000000000000000000000000cdbdc3cf52239a6d4bdadc273b00924de8730c03ea82bd20ec1f04375daa4497fff3a1726269a736706355e72be83870000000000000000000000000000000008e0285b177fcd768d3519062177fa1314c4370f872eaf10f3e0dc94e716dc6a67894d887f40104552336cbb5ed614f20000000000000000000000000000000000638db8269ea4c2fecd5b45955609ef6a1c2c6faf6ee5a8d777e0b38f16d1acab2da7fe7b6f6ebb315ccb345835a21d94c41471a2e4edf0f688c2f032036d41ef5f8a966871dd099dcdbced8b37e1c40000000000000000000000000000000005b4f74cd099eafa6ae59e7105873d4a46e8e5985faf2d26ca564125dca93b1c48187ed7afa02cde8b52df878e1aa618000000000000000000000000000000000cda7f9eeadda16ef757ee8a98be147d374d3a1d40790d20a1ae42c9ed38e4fe22be76ec4f807cf93fff5c6efdb50d1c00000000000000000000000000000000121219b0b0d236a89a857c02249cc04c22299d041d95296dd235b3639416337f5be4a2ebe92a50d192fb748d5d4dca0300000000000000000000000000000000112545a4677ca7d60645cb8bd98689c4aa85a68bb62dc68c0affca5a17ecf0a08fb9b91589d08712b5af4aadf31caad2dd297b192f1c907914ef949fd27a5ea5659aab659b83239c4433f7a4e24529f2000000000000000000000000000000001342460712b73ca0ef07d953c32d280a3441e108abdc2d133265160608986481df3563c5dca20f209ce078b13b49707e0000000000000000000000000000000003580a5b4a7f6d6e066ad9073f7105f6cc1ff35ef5e79a0aba7f48ff2b732c7aec72cc9c5f9233fc9c267d8aa37ac17c000000000000000000000000000000000bb7f32db8a4e341cd9f8dd3b5677dc650cef675f0923bf2e5c8b84c33d447daacbf68631c2388eac5698495e1ad5a3a0000000000000000000000000000000015bf9cd1aa585eda2910128f2b452569abc1c94bc8bd308ee92b6c7315a56fc92d6cba03334bc36c137c14eb1f198b07d30fdb174a3f5c06b78cbaee5b6e7a4c90551083d78c5164de6bb45ee5de23c100000000000000000000000000000000091bca266255d692cdfd10929802d79b474706d160033495decd11cb0758136ec3ae7fd4bb99081e44dd7f25224e009c0000000000000000000000000000000001fbba1ba796416ac22c92f3741e3b268d89fbf0307edf0f25c7c12b5cd230c41582ba69465686ffead9f8363dc0c297000000000000000000000000000000001139590315fc4d81e3e747a53e63ad856635050367ffc143c1422e324d5fe9e4fb90631ff8bba764a87b8077b571aa0a000000000000000000000000000000000dcbba28afd445a57db762d08338a26980b4efbd11668e4050d18234ce35a909d6b563a5d3e8e72892514431fabf0147aafc42f7fe6854866cb954367fa65c8072bd1b60173a2d45077421d6e25f2bb3000000000000000000000000000000001322b1f1388a9dd2853829bda1a5120250ed08f07c84fa398e59fa2577454f38f0a76a1e8db897bf15b4b50ff52a847c0000000000000000000000000000000017020d7de1dd424de53992c168d924c42f26231d184ea3cd9cfe64ad9c82ad067540b2d9ab18b0fd28477ac792a80c4a0000000000000000000000000000000000fabc0769b95e6feedc2165bd6d324b7d16247b79eebc1f09d849792255136538e628bd6ad9b86af7bcdfdd991fc31000000000000000000000000000000000144f39f792bf5585f4b49dcd3fcdbb61cc7ef471e08af4c15cfebb855f0ac8d5fd057c9486e53e8e1ee4f66bd5e943ad106da5f98d5e7cd9f4a1c8d6e50ea2236c2abdf1e08a0eca54555a59bcadbc6a000000000000000000000000000000000c27ac29db98fe3038fe5f537d5ca6faa240602abe11c6f530d9b18d763d6dda3fb25f9538d316e6527c114405ae54f00000000000000000000000000000000017ecc872183413d8065a99a2d1a73b70150e2c1fca2c13a731a39b52aebc6db79772e91f115a63f7b23e5fa231df697a0000000000000000000000000000000016b9715ce820b619274202b52d7e7bee9a17aaeb06c2ecab8bc77c670bd4c714789e4478178d94d2aad57e7bb0b7a4040000000000000000000000000000000002d0723a3386248d8597d2b63289300de6a16011a38985170a1652ff81ea70a78459b3ef252cc5ed26ff1ef1ecaf6a42c971deeba2f757970bcd4f5548a2767bd6c43e63f4c5fc4b157ef060a1f45aae000000000000000000000000000000000eb1ddb7306d8d2858fb57dac71f67473b813f37f02d73b17f375be86028176cc1dd84347f183cb7d427b861be34c3d70000000000000000000000000000000009a8811ec77eb21f2b33a591f2fe6d7b74b40c5045ceeee275912aeec664838f332bb49bedcd958ede0af0d0232e76ed00000000000000000000000000000000156e28ee3c40c6f18c6059e06ac8f7b39fa23e5962f640ef3afce13c169346a4c8e5c2bcdac8fa15921a4740cc5a0f2300000000000000000000000000000000084371522a6ebb1925c8fad3f20277c34e657aa71abf8ed7d323a10c14cbbb1a9e0e54bace32eb845e6709c1c58afc34a5262a021977dd79ab96606eb24a7c5ed650300dd68bc79f4b8378f58c6eed49000000000000000000000000000000000be2ef9ef38a5dcb42ad31b1415c8eceae625850db4306a26a0598d4a567936d75b701c81793fa7b42d158df2dcb0d5d000000000000000000000000000000000851b82b59fc15b89e33fb618c56d11a07116ea35850583a07066ed97b8a864f3766c0cf921d007a6cb43931ad4fcf8e000000000000000000000000000000000ea8bdfa3c5f000d7cb1b5cd69537e4104daa15ffcec06f40a91b972d8011e5fccfa911c55a07383cce6760c145c39e4000000000000000000000000000000000652a4165602978004ef702103ef18e8fe7decab1522a76486c742d29103e3bdf6dda2d3cd64ff1b5d5a76f4823bd363083b3720c20044fa41712039b6e9e776197391ef393c0935a0e9990fbc1b7a4600000000000000000000000000000000015ce5b43e1fd950b77e2baccae8c99b82f38bce09989fdc5d402420e7931a38b7fdac5a254b0cb9bd8fbb488d02493b00000000000000000000000000000000018c5b3ff46a04ed114bbf56399738e5d594ef8dd1d5e2e8dc23a0097893be3da4fa4662686a6dac04418fd2d344e36c000000000000000000000000000000000efa3e970a5cd0c7bdef6a2df3be9be18cce63c10c331a18d628bbeef30488ef73d866f3c8804acb3bd375542e99eae6000000000000000000000000000000000e966d9e2f2d47df5d661a89fafb6d4518fa1544ab7a56716df511cbcca99098f944a981c9da569cf95debb455842006d6f846581848f5dbb9e8d220b881d0327c4f3f5d4b79fb2c4dcbdb9bcf44b02d,000000000000000000000000000000000ef06b515addb951b24e5d61f6e6eededf5f93f9f17455e1b563f187f73394457b3b7c1b90ed454218f8782d2bc848be00000000000000000000000000000000167398608a87490fd17506166bf54636aa4dd6d3e8c4d42995bcb0262268eaf2a6d828b295434f45e3e53703aa67cdcf000000000000000000000000000000001602ec6519e4987a052f97eb222f505e241d99602c08ea9c41bc95796675ebf6a819aa0bf87319f29dfe47f45f3c8c7a0000000000000000000000000000000002ad4291ece7ea0fcc9f4440e88eef693b8dd53060ec847bd27d74cf71218eb6210a71895ff1f1f4537a901090f14de5,240480, +00000000000000000000000000000000129cc9cf82aa30671e969148f058a0b8d275bcbb1c8da66e52998d9555dfaa075e2fcf39fc18f305940fbc972cc5c0c0000000000000000000000000000000001252482c1419ad72229a00d90f1c09d464e896f47db91e9680efe36822e99e1ca7a2b5ba8b17b5929fdbeff8993bb18e000000000000000000000000000000001287d5054bf5db038ec2f7b7a3b79848fcc8ca42f9e19d5e21d36d2256f97e0edc2608d19c17d3714024e1e9e86ae264000000000000000000000000000000000f6262669e30a5db67550cdce8a4611b9501d02cd4e950aeedd1c87c4f0f63c74f35c802dafcf91c988a154dd690103c67c44f7c8513472b51f96d526422bac628aad4c40c521cd7cf9e86eaf92838fd0000000000000000000000000000000019cccd010df3e668b1c3ec053e76c46e45d01a4fe02eb074e296df2a48e0e4eb647b06c40cf64a0048a8fcc2b0cfa6100000000000000000000000000000000018e07bc50657f3bbf736c38518933e91af29e3bafd776243296cca3a1d975116e8b428b050045a61069adb23baa22d3800000000000000000000000000000000154f51badda1b828346986264b01fb1be4c7e9b570ab63a5eb15cabe9412a2f9bbc6d5111c638ff5118a4f6d08ed055200000000000000000000000000000000064d4e607a8994c0bb65770a14a14ed1b68c766ac2aed45a44c0f7c7cd4c3ecdfa077206812cf9b24e35021060a3668d2d6f95d4b6216e4226f78e4fa5011c9becf98fe45a17dfd740fdd0ef36d8ba94000000000000000000000000000000000b9bae5f720d9bd3271a71d751e4c35c39ad30fa8a67846107ff769c455e42465b2a39dfa32861634d5e323878f56f4f000000000000000000000000000000000a1077046cf5c7a66452cec2193ee21c1ace50dfd79f707c9297737f13806dc05e9e1cc5d1fb4c87b250ebea5a4ca6c40000000000000000000000000000000013e1fbe1a9b5f2ccff51120590ac0cb00cab502726b43a6fc12459e27bad4aa41537d6f3cc94a81a170998768f6a0fc6000000000000000000000000000000000a02c551ecec1c7a415256caaec1b5485a42f9ca8d897cf26546ace1f2bc8c2d10a353b8b84495b8dab5e3c60881185a58c25d36216b811ee42d0ba629ab7a0f9ce7edd7234620c28e37bb3df3f042e700000000000000000000000000000000021b9ab3ad614816d7006efe688e1ae8cd99b0c4437d4363e557642a7cfda2000db6503b32db36b6d1ffe40d967c5efd000000000000000000000000000000000b7fb0ddd9eb2be9cdffaf8f8c593a9296c4c7deeb1c714c11863d71dab1e6fd309b75c41e25de3cb6089726b43427f0000000000000000000000000000000001277065ea9d208777d0fb7a6726e11c8330f0b3ed3c6716acd559aab19b2fdafceca8126c9facc43b9d80534c07035a20000000000000000000000000000000015e8c12065d601dd5ede75bcfceb7300bf6f9fbcdf68d2f093b7654d80d3e565135d64137dc401d691a45fe052f58a6850a5c6bb6b87fbe5ebfb0d182d425ee173973c6f2085c556b0fe60219b9f3c32000000000000000000000000000000000484c4f9652427d0649c33e93d666dcb15bc56669c00980c53357ecee874bcdbaa016236df65a4339dfbd44e4eb0823c0000000000000000000000000000000013836a7275c29c989891c94e756cee9d6c54a8f634fa570655ee44b7c1e34137edc33323dc0d1f3a0218039fd6f7013d0000000000000000000000000000000002e88c7d5fd87e97a0de1be95021821942a8004115fb4fdd9ad26b7e0fd171f9c7e6f962eb179bdb95ef960cf9396372000000000000000000000000000000001636e351a0ed1a260ffe0d1355e6da288792fc97a7310b040cb9fcd5c550d85d90572154d58a9847dd5a8a06456bb2e43b4bdeaf6643ed159f4a3e23c33ac486b33e1edbc5a097a47a6c2c753e5299d20000000000000000000000000000000007579785c14fa012cb5d6c116d34dcc15dfc908a29e90de3bbfb8c9b44e0b4258644440d7c78d751a007c10f98053bd10000000000000000000000000000000009f023538822ceba0883a0e3454121dafe8e5e61d4754b54e6417c989efa998334641d458591b3076b615937de065cfd00000000000000000000000000000000130fe7f2d5e0ffefa67ad3378690c53a6e68de5504f3691de0df3a24c309619bd3a345bc2bec4dcfb4b77255cbfe09980000000000000000000000000000000015bf85ed997eef4d97a81f1d75825bb4409cf86b8c8e5f4368cf1e4c803f9e1e23a2a96f7b0a08e5cff55a78761ebce21d18596bc392dd0b71e1216bbb20a0e5e2559a46789c36a146cb78c5aa8e3921000000000000000000000000000000000a95597e4402bbd17c20dda088f0134d42f14443bd519b3511b28fd8d395a0e50758386498388ea6ad0e7634587336c1000000000000000000000000000000000079f348d3de505875c5192f795cd77e2f7385ed447b06f2dbee18e85c832386b201cb3eeb21aac3f258d2c4b0434d48000000000000000000000000000000001895b1891a08ea42eb1f68698abc20394ffd66bf0c32979668950bfec5cfc8425314eec2ac17ba25f29133a8becc9f5e00000000000000000000000000000000146160336d881b24c6258a3a86c08d346900680324632b6d5d4582ee0865a7e5f2d01677e5e49c5a4179f8382e49d1566fb3669c0789ba6a5b00f14c14fe2edd15d37a742c9e36cae9ac010e632d75a40000000000000000000000000000000013cadc6c394efb2f93e00f3976dde34efe75adff34bfb6f5e1a150b79bb5baf6bf29fa149581fda48faa68653cb61e300000000000000000000000000000000005fd25362d87f9581a202b186d2786d2859faa9966a1ceae747dd7a48749abd424eb9813e44caada0e456ee8bd12e99c0000000000000000000000000000000018e6b279e2b545acf1da29dc0504caa5982522546f83d4d3389e1fd6cb5328d4a167926a00ccaca402b3a3cdc67d757400000000000000000000000000000000089a9992a36b476fee21abd50977dfee01d7c91b24b3e26d7c15b2301352069dad920f0ea93a3e477a48029eef35605f06c2988dd6b8e9aa116eea4e1f63dacf100019844d37d163c047567e8e1188620000000000000000000000000000000005200b78dba7e423bc23e87c5937b464e97405f6461d05bd9d1d0fbf8f3c8e64a39081f9e43b4ec416198dc44db897e7000000000000000000000000000000000bdba1ed07c4a570359863a1098a73830818b3fa5b222316a3e0692a4ec65e59ca6b4bf5f72f8c1384e73e807d272d6e00000000000000000000000000000000073fa3eef473707b6aff37fb6f829f0fdb7ae808e85ebba4d4924a185c3656eb2856896307b671080347cebc32e958bf00000000000000000000000000000000076b56330f07cfc0ab34e98e2fa0ce4702b296a00f6ffee07c3ab523fadc048a047ccea7a9003c090951e4ef698d14e5fbf8322f706b1972f73fe4e22a3dad29c4ede09163561b2810cfc3eb2ffbc7ab0000000000000000000000000000000007252747c8275f87b21bbac4071c1826d166d14e6205095e5299315d6b6a85aed995f9ba59a2163ce2c51a8e60eaaeeb000000000000000000000000000000000460a000fe29cca24dec469ba5fb729edf3e443bb032d488cc99102a614a5251915267db003dbf395132d815ba78f262000000000000000000000000000000000161c01cb4d0942faf2303c108604babbd4cabf5d3d30c13d7db9428a445c7f72d96a7405e22e4e451058a94e20068720000000000000000000000000000000010ccf8a8ec4e6515b20e07057fb8cbecc5defb87480f3e32a1bcd0cfe239e00daf6a390c4815ef6b85be1f07a4c4bfbc4a46618381ba6b991b2edfdeafa67aef1cfea066fbffdba24db25385963326bf000000000000000000000000000000000e6cf781162502d2a758d0f96946bab887591b7c9ec9f67a1b0b962e74ee514e84c14bf67ae3c0a9ea2a3e472b7ef59c0000000000000000000000000000000001542b4e97f1e8a64ffd51ca43137b0660f897f6b3d5c6fee598fc4dd03932c3658ea55e1e9e73376e51df278ddd3a3f0000000000000000000000000000000016dae882ba240343e752eac68122424320d1acb1fbc4bd26c3983dd91325f25e1b1f06213e0e06c142997a13fbeca597000000000000000000000000000000001138b71c95d4de320f02e68dae9bb0de3e5b317cb596532c5cc18ca588cc8566c21551d7d55d685591126b9d9e466455cd05fce871e4ff11e7a4e834061c65a0aab7bfa8a0128d460a493337c6e63ebf000000000000000000000000000000000904f6a09f3a5f5baac902c702b059835737c06f62c2ffe9101bac32f854e4d72f74031f5410a5941612b1aaacbb50920000000000000000000000000000000012f39e7022150b2be12cdd621ae23525581405021b21cb9e55972724a22b1aeb2e15b135ceade132d3310e050e607f65000000000000000000000000000000000a92b1daaf23524904d74c3f149fcd2c98e3a4c257113533e7cc59c4656b785aacbf0ba6b9df0dc17cf7c25f1ca698c5000000000000000000000000000000000a20a5d7c0aeb16ff498f46bf05e512784d120b9c3c8b2877411852d7da3abde9e83a6d00213bf69ed88bcbb051a486daba9e37ae0dbb733af820743d8e307fc02a3ce9b40032b16d0e9466903de9caa00000000000000000000000000000000153918807d7da07ec7014154f00a558ebe0d5fb48fba4c16488d61a826a1eec28e3828d6744300c04207e8ff1cb61211000000000000000000000000000000000a755480457896c5a3fde35658e73fae821151c43fb92e9ffedcb05fabad37cb68aa24e029fc33a2518398d723c4859100000000000000000000000000000000148798bdc5b14b90aefc38946db93be1754f15d78762f38971b1e64a53fda92b96b0a70ca2548baec882887ae7f636910000000000000000000000000000000012299fc413dbaa77cf8867e331bc0602c4fb32fe44a150217de9e6391374a9ed83781034e5775c4933e13cdfffd25a9e6ef151662cba4952416eaadebfe5e0fa0ca1d31380e1540c2d5e0181af9e317c000000000000000000000000000000000fdfcbcce1603198fa344487d2d4838b3ff23fc0a73a76222707d9f8623f0b87dfc816be8717b0b12667bee460ef40f70000000000000000000000000000000015036dff68139419db619912e2d19b7d2a2d637fbb8bffcd941aefe2eb4d24c1f7dc32f4f53d4cfce67785e7c328d6c4000000000000000000000000000000000fd575be9bf54128a9a1cbd366339c993ced315a840d60f8b77e035352bf705c01a9def713e8cae3001dc1062cc0723b0000000000000000000000000000000004015ed456125cf0f46fe0093b81ff9315d955d470ad756a9303f548819f339e137305c58e6f4d8db3c8bbfab90718d4f0a3851bd52ca52919dfd21efa6efc56f6dd5060ad969360b1a731e8f38f0f5d0000000000000000000000000000000016d31e68cfdc5823970c8c2ebc53c3d4517792c44e90c10f920a819e72e4a6966c59a691b905c8b0b612065c56d86ca40000000000000000000000000000000005096d516e416fdc0df552c2688c74f1c067a3e5e7fd782479bfa468096e6ee3e601bc23d2e38ae7500325765483250600000000000000000000000000000000092c994e9dae287bb6450607a4263bcf6267f0f66ba3e63436292af7f6bc8e4ba794a12792b6af49ef59b5fe50ff6d3400000000000000000000000000000000175a645988f33612e969e1d91b2c30e47ec655ad655d89cd8dac151c3bd194cd5a8c28b498b1cc2f2966b7fc37cfc8c532b41960417047a2258b6e9e228f3cc1130b296cafbb75f58731a81fcfe8c83a0000000000000000000000000000000008d09ee15c80facf7e32b15418fefbf7e80400acf37f2a1bc6ced88b1591bcb8f86b45b544646c5fafa71b5b103b927400000000000000000000000000000000060865ba68ed8fb3d0a05779c278352b22d4244edb7add23d985a2836d2772dbffc3c82c3134916e9b0900c9db6ead8f000000000000000000000000000000000dce53bf8aca1ed44bee47096dd988689c1e32e1e65a5f8dbabab7c4edba866132ee2c036aba5648d0dafa9a26405fe30000000000000000000000000000000003319995785be720860bbf48692d1507185d898187993865648ded74d3aaca45df939c6dd986db42a51bd13579a55b8f71a6f7f091a6a21dbfffcec2eecaa22d05252b60bf91b56811a833dde3fcfde6,0000000000000000000000000000000010643af30c3cdefc30144c5d7cab17c9c54adccb3294ae79fe5c69376011c159be1e43940640bf5d9012ccdbc997e2090000000000000000000000000000000002a22b08904ea9ca99103a01caad745dc2afb7b6d23e666770e81a97031de921f9d4d1c04fa941c433b8cd9cafced3a10000000000000000000000000000000010808e5518eb6cd61eec8820b9f279dba2423b1a3677e21fe3a0ca2ad49fbab2995de1c5adc9ac867de79e3b40ffddf30000000000000000000000000000000003ce1270644d71e0055345c7463d72dc119495bfa04a818dd398d944ca46deb0aee8c7936557754fa18225522fb79564,240480, +00000000000000000000000000000000059234bb6d1b66985b79e6a2c6cd158d37fc50ccd6e50ad2fe3b6a02ae2ad35b8b2be92139265957ea698441e780532100000000000000000000000000000000066adc7083a7f3dd75a8e431a36632dfcecc2527f261e961335bbe8fc8329d992880736bb41fffe41484f68c38fda61a0000000000000000000000000000000006aa0794c27d3f60debbee292c28b430c159cb2874b9467312cb857a8777058580f8a2d3b9bf4b8b732edb185cca6ac10000000000000000000000000000000000d81f222ed6acdf29437adf26d2b785535cd6d61b329df98be04114c3ae68bc6854e275792fd48de3eedf6ba7f3849a2e56b63fc6ba87cf021c2f92baec248756ddae0a4f070df840db281283a4c9b20000000000000000000000000000000013dab8066757b8bcce2c9965e600c31b792463623cd5561f7f6d55c5a52c22efcbe48b8684fc2dca87e2945765bf565600000000000000000000000000000000198a0594b5e606b18201fe2706bddffe7bee6c583147513333230715d18295714055b984cd3ff8d1127f9420863e3a67000000000000000000000000000000000ef77ae1e991daea1fe8338cf236ba959b22df4b24f00c6c01483b6956c609b805ab89712f80892bc0160fa3775907890000000000000000000000000000000004d30f5a866a100dfe469d4d0c47872245c4cdcfb18d3ffa0de422691044b52d2b9335682dcbf67aafa9275712ae3f5512a50af55f47fdaf176d4885e4910c54428c8ef433ea0cb1d009ea77783559470000000000000000000000000000000008010bd5fb5e222618bf4f919c203dadf9a7b7597bf90e16772020614481a0963a8e8b1bd244661bd33e0d147be7663c0000000000000000000000000000000007e21f548efb869a28d6fe39b999ab7fedae9cd6cfa531fe608476ef30c8703951839476811838608dac1aaf9cd87eed0000000000000000000000000000000006cc674c464f80cacb2156fc1eb680938cb38cc166a99f72daa50f9d2c40f10ff07e447d7bd5e59b6b22f0dc407dd8df0000000000000000000000000000000010b160c58ea82bc3904302b1b4fe83d1883efe3c8f52c4e05a3d8681e604eabb1b7f533e61c51e9a172987383506e7b189a012158b3c18e19471fca6d5aba5fd004a337d49ddef5db177da187c8cf1c8000000000000000000000000000000000d4f8372d1138489913654a7567735be1eacf8c7fd497c2216bf77a94f483bd4a7daa2c8232581d6815af9898a7569b80000000000000000000000000000000013676a1f72cc2ed80fa24f70fe1c52aad9ac13ad6cad1f519649fda6ea3787d86ea164d9ac86de96436e9db4fb4aa44f0000000000000000000000000000000003f7644f7ddc9276ac36aea5c36451f3d5d6e4c508932b16d5677977108f04deace5e8cf0b3b3dee88c328b6030f3567000000000000000000000000000000001953cf03effb7de9e62937572850e9fdeecb74fb4aa655de1abdc6e065920e6d2e51ff28880f33341443b5e6652eff4827dd109f6df1d9851dae28bcb9e552c6b1e1b2dfb331aa955d3d0b6c4862253d000000000000000000000000000000000c76a5bcbd2a61172fdd53b351d143bd30d460e398c9d4b7094a604ab2c0d46d6112bd8a5483c9935f0bf6d84df04b9500000000000000000000000000000000116b15825b780c49fb24617dca620e939e2528e10c49f34971736c82cd35fd3965088595deb86eaca3d3239c6c78a84f000000000000000000000000000000000a0cdaa541dd96fefd46b303b88f1dd4d24257b910a596817f1d946873cfd60ae58a88aa687ba573832331e8fc158db50000000000000000000000000000000016259f7285de159a2c6d6d8687ed348ab97e8cf329ea5de49b6d708b6da5b806bd012ca3641c50f479d85921e20fbaefca96785c1ab66cc5c8e434f59cc1ddf76bd78b6fe660f7cf74cfb79d7f2c7f84000000000000000000000000000000000797e815a98d362e1d7e2ac1fdcb477ccdec8ecdf340d7bded36856ce30e92b661669b38ecbcfb0896b2fd75df9b734a0000000000000000000000000000000017916c559db6b4b28b798c2027e2c70ba1b940212df8a1649b9f6087120660d698bea81258e2007edd4aa7d0d535bccb00000000000000000000000000000000167170a76db0783a8c3228f8246502b15d342b019fb44a46b514f4ba2de3ac66e435941adc3d91874371561870ba87150000000000000000000000000000000010097a585eb9264ea96904d8534820be185d8d9e4b1616439a926c0ff8ceb6f2bc082e5712454690c9c05b8018a996235aabd1fba36142bd768339e091b15b7f5b4ea609b57947a7187c498bd9833c2900000000000000000000000000000000025eff57e1f37903056835d1b4133ce064c86947f35859817b2cccf1a5c3923ccca766b3e0affd20a4a6df62a45c31000000000000000000000000000000000011158fce4ade070629162b2b6cf1924696f1f7776f3d623cfa3d54c66fc17fa0299c6650b709a1472262fc0abe8d9557000000000000000000000000000000001828a65fb90dcebe25413566deacf0677a3993b39d68854b264fe7807097fbd3106ac618545d3a6a42e197c65f0d2a7100000000000000000000000000000000045eb8164b6ec874467286dc3626fad3c01be61f6a8a88e5f88797978463db648a9b8a1e1a2589364ef2879cb5f75423fbe608fefa5472c7d1611abfa402d2eddb1e54542f45d4012db8ac6d1e5016100000000000000000000000000000000011847bdf2f67b40aac3426716391da488a8f0462b68bd35a8c1c762591e2f426f48f979a646a094bce16bc99cef7fcfc00000000000000000000000000000000092d61e408120b1549fc8d2174572eb7ed3f679327cb89754f326fa72fbff79e98cf5ad9c94c14dd86135e9aacc98b98000000000000000000000000000000001440e2f4ee2ba254a780a31b02babca093a38e5a1ac09ff388080b6c60918ae5b26e1c0888ea0976527ba103b257d02d0000000000000000000000000000000019797e49808b756128866fae0d6aa7e755a1d6f07f7e6a877bee150fe9adf0cfe612350c5a0e31d68cbeed226fa56f2a28d57066cce439d8d0385f647ed5e9b29e8fd0528c1ed8455f37dcd81f4b62240000000000000000000000000000000016d723a64ee06a7a631509c6e64b1c8bbe199952da532dd92194147bce9942cb4a76f2358e6c7d263916fa36e2c0c09d0000000000000000000000000000000003d04ce655cad1d63748f6eaa9912d6474a34820986835f60c812aee9980d3ebc18d6fd856a6de9546be024b2e95126a000000000000000000000000000000000ea840bd7f76f8e944f95146cdc9692d97e6a2d7d16d4a7f054f81888472da4d60ae5faccb72d3a05781b399536ccb1e00000000000000000000000000000000155a1c43c39e9dfc6d96e01c981662900fadf1a46aa1c2fdb70bb34e94dcbe86c4f255e259c771ea8ede50b388ceb2f61208d8d328014a6b2c8b2b9edc70589cdd63d38f4e70abb41cff1b7693bf9a290000000000000000000000000000000008f189d97f7d82aad87fb71d090a5c99d94079c0b74beb3dc860d440c0f46727fc49104d671bdcbe5b9551552e18afc60000000000000000000000000000000010c4edfb64b8932a617c316820cd27d3f6ffa89b471949362762af8e10d25265b84ca2aafd3b14f33c39a4b533da60d60000000000000000000000000000000017ef3bb919b087fb6745bbf115e2929394fbc9c89f65e7d591f15da93ab785aa6828ebb6ced99d3506810647d28ed814000000000000000000000000000000001591d8213ab349017cc93f1fbe6aca6765dd33ac1f468621e2c79e30aa73bd7606a0e5ba1d97ff03e0029dbc8ab1c5f4d3a2044ed4f938c17684413625bdd281f685abea2e375bece77c03d697c82cc20000000000000000000000000000000019b3a2df3a9571b066eb451e34d8a38c0d90b6e365862bcd92ae76195956c21c59441f0cd03cc69abdf4ba069759b87f00000000000000000000000000000000082537ef7f4bba5f32db4443abc8eabceef643b0878ef83860d75ba508369a3b459cad96f1cfd872df99548f656b0f9b000000000000000000000000000000000b2fda5ba0c405c9481edd598181ed8a59a8a18462508af8c5d66988a7a58a5c9635d93b5e0ee310bd35e0091fcd4986000000000000000000000000000000000af7e15e0052576f82e36e7e2b614dd835a290e05f2ed9dad7f508b4c04e8d437e7e937a7f4c88b5e66b06e0beffc4df7fd81e27a577b5e79929614c069d6d52146a6183822d25cf1ef84d8afcc1f6b40000000000000000000000000000000017a1d5add5601010d138263b4793149a02e8f4f7cfaafb69fde7b843a51cf5f0634e26b6e5e3315420d44b0fd205230d0000000000000000000000000000000013ea863ebe1b1cfcf4164d78dbe8fc809d2b82ef3e5a2589ca1357e48dddf2696e910a90301ed910fae77a1e462a5b1000000000000000000000000000000000012b40d9f25dc5a61454ddf1fa9c38e87eee60e55938b411bff9cf2161ebd7d3fc930131a198e7e97dc90cc245164e7000000000000000000000000000000000054f19ed8e2682caeda10c252f11706e7f3b65c81e7ae0a617469babc5f3268fe5c0ce2e85d44fb6731e8ac132b97c3ac5d47ce35d4ede84a83c9860322f582ec00c872b4b035d5d340981fc29884f13000000000000000000000000000000000ef0378945ae4683666099be36de3e60b5bae9c3137b702e5e4e35afd5c1e81d033c3d6b1debf5bf36bdfc4e3af37759000000000000000000000000000000000c37074af84ff596ff2c7ff963d96968464d6c8d88b69af64ae883457d02ee9ec80720661f39019230a6531a0f2952bb000000000000000000000000000000000454e8aaa2830f07d86eac7aca1d7589fb06aed646146a1b90f4959b5caed73131ab231313b50c15213f89566ed87a3600000000000000000000000000000000143516cd7a1b8da41226cb828887a0b3314cf4f87c207d1d84e9c49f0f7e548ab99e635bd126d49fd2e4dcea98f3adf784ae256d47de2d49b1e755cb0e972f3b614f3e7ba779c65ce175ca3811021a7f0000000000000000000000000000000019384e15a8754c6d85bd298ed550a26b51b714745bf2980b4920d6e73f59e657d85d3e86baa9bcf7e971233daff99d02000000000000000000000000000000000229d233d605a1a9f060605ae366a263594d8fa2b7797358ffe4c62431b9718d155d24d80bf5af1c806f447b92fcfbab000000000000000000000000000000000bbfb66cc0c7bcf251141c540f712fe9a359d1ed36d228379a1f3791991cccb7dfe1a10d40667ca062cccd55c9e6b08d00000000000000000000000000000000150a4d7a003cb81423604c13d0c5175183ab5f459b96842939f5c4cfbb9196db4667bb4382d2d5c92b70800adf384569a09d0136d4dbb3abfabcac55db48b1ce302067f413283fc1a21744f1c16ef7b50000000000000000000000000000000016352fb8e2751f126fd0f889f2a62a85b95c50d6bda7704112e4487dc94417218b0daa1dd6b998662af2582c44b011c90000000000000000000000000000000016bf4c60eeaca103c90643fe0969c2c261e9697ddbc02279f0d5afb5c905a984ab2396db93555cc2dd5682a1525446d00000000000000000000000000000000014be742feb1215cbdcde21e974c74e23c7bbc2cbfaaace28cf1d4f2b5a77dde2f3910aea74bc200277e6fe0475208057000000000000000000000000000000000bf98dd3e3a8b13e487d8b1a35615b0c6b0f514f9b8da7d6402586f113974c8dc9561db797a96f4f8040c1765518d175650a6fba1a5eace6b455ee780ff266c324f49801832640856a80098f0eed0b7b000000000000000000000000000000000362935e552dd01b5fc5a15a76faae937d7ad086b0a67e9cd3558287274106623deb85b6410bb4e64c424d44335f3b1e00000000000000000000000000000000096f23a54cf57aa3306df0a0a4f45aecb9b09bfe83878d551a59c53e18efc5a9f177cb7fdaec1648f66cdfaebb15c61d00000000000000000000000000000000135271fbe0cc0987e82f3430eefa8e3cdcc1be4a441393bb3fac0b8e8f78dc47ba2b833d9dca4277bd60befdf33275cf000000000000000000000000000000000dc1b7512fa5f9d4ea3f4229d947f43d7dc46b7770aadbd7351b6d48d525d0144183f2c84293c63c68d5262851401ae0282cb1f8f6d6dd81e7c49176503a76837a96d7f2b084d29d11dd9c6548cf0a57,0000000000000000000000000000000001c11610b63eeaf9e00552a230bfee290ea49bf9c93cfea1b6f684c9b5a07f341b718a0070534e0da9e6ab1239d800830000000000000000000000000000000017e8107113714ebb1743c34d83be3acde096bfb6cf140e943ecd0831ecfcd097f58d25a45005db61551a01d9da46de10000000000000000000000000000000000c2eff6c7c25885c514aadecb8f0465a0fb4385eadffa082e8d4f497b10df2395be5e7760a87bc26772dd78701146b730000000000000000000000000000000011ad4e20f5c1518c72f75d67a897f30100dbb83365ef7729c3501c6f266d6002edcab8c8bc1f449c30ec3624cda13809,240480, +0000000000000000000000000000000010a26eda028649789d9e01232884e4e5a6b8e0b17169b9d64393e2568b09ac4d3e61a5996108655c24e76abe9e3fb7380000000000000000000000000000000015e4e36bcad524f8aac7f909fbb884e879caa735f80fe9890d7874be919ee727beb2a074984c047dac1d02b8712afa3a0000000000000000000000000000000012458946f1e853a7a45861a92d4ce707e5aebbf69edfe69190c0bb130141a10869e2a73e06785b568248d5b1f647e63e0000000000000000000000000000000004d8061f25edb5a510a2db9e1df850518156138c78ace50f4c9ce47734a0b14352f5283083a232602a070c3ce94c7bd93d7f8fbaa4225f3008649eebf42315785ccda2b9ce922170e606876881825cb90000000000000000000000000000000000baa40ea518227b007b9714ae6eb5a4e92883dc75e6328caa780bb2ffee7573dcd7e9ac47821ac449187569986bd2980000000000000000000000000000000009d43d61f070ae308c5c285915600dd9c17b7de63cfeee6fe33c9ba857b3c72e057bcb4d4ac2b492797e7d785997c18800000000000000000000000000000000185215a7fefb96b3ff9229cc3239c3ce5202a97e275ea9b1541d7bc0a2931d7e3b01942febb45c6e96e66e3605744afa00000000000000000000000000000000103ae58b8066dd62c46c14c593c768fda91b90e4840b5560c974ce69b86bd6d2c13f689b72cf9619e57c9dc8e3d3fb15e71e6cb3d4e19f4a70a4465df6eec6326f558ee1cb99aa540ad2a73c363a133900000000000000000000000000000000075585f862c0e0e031efe12f31e159f2a8b89825ce80fdf65906474f0155f397fdc666292f6a7384cab790f071335c49000000000000000000000000000000000eba3d37a5cae738ab99ed9475c2c7fbb88ff54edb8490017162dbb16c8225102158a266fd4ba7570ee6d5ff6cf3f5d400000000000000000000000000000000135a0b0a38c036919f8389eb7bdc505a375fd75d513eecf0cac134645d60fb6030a437ac6a0fbbd167b7a77a927b3b0e000000000000000000000000000000001688fabd4ad751598ca036ec5ef6d7b314980dce7d8652163e89fad01b233af64defbcf352743ec478af42587f58177dbdb2b3c3b8e91540dc2724537526fd8c0d4b85d2cc20323d71fa5a4f61b3f12a00000000000000000000000000000000062a74a9ba0e2e8d95fca478be8d18fc716243b1faf7365a55387fd7188021f53bbe780e973e7d16c9db236faff176cc000000000000000000000000000000000f949be3fcf9b38995624570fcc9e7df9964d038eca189336ec39d9e0bd05148ff7df0b48436a2cf6e249e52248ee8a40000000000000000000000000000000007472e7c366419a0cab844522c46356acdf6a12cffae941fae3d3b78e7a83f0446c945bdf7b247abccaeeaafec49026c0000000000000000000000000000000006a564e6860b97feff368fc9a349282112e591a7a6987fd10a2d4de8ae4384ce229b9db9a93445f727eeec55a6fe5a9def0c8574167a3bd3b794f057ed01865ea69c38023dbddb0afdc55dd9523ebab7000000000000000000000000000000000c073d2885eb125d3e7db48127178bea2c5bb0f09eec7081f15bc6fe6cba156914fe1b1fea6cf14a21a328d831523ec300000000000000000000000000000000010d93564b2facde13d29dac198c5f5fa314a0398f30c6fb7fc9575bc83d4e97edcc1c1d34f78728729442777718f54600000000000000000000000000000000136a4ffaacf0b4a607c677ed343c1ad41a1eca49c7c48fe73ab2f74084a07cff18f07f54a7f8ea1bfb7fa3667863bdc8000000000000000000000000000000000fb0c007a907ecdff7bfe2242097caf0c5001124d112689a74544fe4fd85be9771632e7267a1cc7e9f66d7e4bb4c954c3ccc75501428d3be8bb469ed0f2df7dec10e1d205e11a907cc30c4a76eee3cc000000000000000000000000000000000032bb9f20fdb19f578fac3008396f5dd0a70860f77f8ae7771fc6253569d47b72751cd56bd373dbc5eadf55b99578861000000000000000000000000000000000c4a4bfb5ca6f9c1bd69d7377c6da405afc3128338dfddd9aea19aec5e1e0f547e3febd28445af5e27469c87c4ac15280000000000000000000000000000000003b551547af253d07625028db4b9a8da2a857bc925620c5d561bbcd3e063eb460d9407cd4d4813800551e5d0d23a2ea40000000000000000000000000000000006d5c69a251e9a042c66bd4ee92d4f3cd4e79704b1b215c15b319e09cae0d798eb201be24f407340dbcefcf2cb87da5ae5e403f555fbc800f1342275f18a73dbb679bd31873ee87617090912a52d6a55000000000000000000000000000000000a5802e388f7605bbacd0bb65ba96689e223379214fd7a92de9a313f55d66cc71ffc9ab3f9979b75edf55647ad3b6c94000000000000000000000000000000000f86f968b5c20a81f18074803e1ec55ebd73bc87451c48d5bb61604ebae46538dcc9d21cce062abc07b4b9e89c85bf60000000000000000000000000000000000f9fceddfa8fb5bd76fb7c8986372c32ab9fae3c26e9fedae892bb55178fa2f3432e6eab5043496dcebef46b20bf5824000000000000000000000000000000000dcf7a118881aea4e6a0e4e305910d4e4a5f3d0a8800f52659ac26f122bd63c8aa2c5583f1121275adc9af1800a007fc97ea57a38598204c15bf65e7270a175460510848540ca4004286f3ca09eb59260000000000000000000000000000000003ee0ba2b1de438abe66769124b97a951ce18aedc8d9ed005628aeebd90efd316e7a3c60cb5a103d6f72e7a40ed8f44000000000000000000000000000000000119597c99a7a16d8d35937ea15539089741363153ef898d6bb177d9a9b6c5bb4b79728155eacc5d82571f398ac6c32a200000000000000000000000000000000116184ac845a28c4f96641ec19a07e1f8326bd45e2106148f40277ae6fcf200d64e326915cf5c927222def8deccd4ff8000000000000000000000000000000000f890258e70b973c0d69492b2e7d10ccb3997798503c0943af4255c13b3856ca4007b18cb9d638d5d9cca71c368cdfccc54dd8cbe68d5151e4428d35ec2d5b5cc7f5e455207c0788a695c2d7fff6735200000000000000000000000000000000171035755bd519af04efdd477d407267c5a8108bd32dd6d3f1b9555f15f37ce7598c096fb5301873809f0c000457a4a2000000000000000000000000000000000bd35595246a8337a426c50c02299f297036f710b0979c7f981c6909e835c0d9556cf64e2676baf952a787e10d604f210000000000000000000000000000000006600ff240aaa026941290f49ae8968e72293ae7c2af0df1b4ebb9373199b95fc91feedd2782ce819440286aeb2388c50000000000000000000000000000000015b2bbffac097c27944143cfb22e38ff8e50e79f2336e64c8496b0b25892834efb18a765e26f1408df1d64f4b9b78fb947ee5651c127d7c8ef65ec68fcd97d1dc228bffb5bf1278aed3eef8115a5ae72000000000000000000000000000000001064bd04edf96a3c76d2ace669ff72ee5edd87d32592213cb5a6a4a482154c1723bc19c7c530d164c31626dbf758d43f00000000000000000000000000000000176ac06390e3629bdfa282bf825c0bca9bc4e0b8fd90fcf2d4ee456d5bcb3ac2882d8406d2fd59faf10c8327b1962124000000000000000000000000000000000b58fbe4e14ee0af03d9aac4131abfaaba43c7cd92d530802516cb67343b382a6d2af9399d93b43d6e05f7ec827d5ae20000000000000000000000000000000000bfd241e3180cd5ce9de831b24ca50db23685bea7e008be0c6ead11abee338618728968c25a8e5a916cef8aa516667214ab6a1d0d3f87e7c9df0c14b6fd2f9d0cd755d5fce5f40bdc8174790901549b00000000000000000000000000000000183ccf0ddeb8573923694decc02b8f02162037156a8f6523ed178c13113d094521c3d9257febcfbd8f15acfe3d5d5c27000000000000000000000000000000000cf716097aabb07979ee435cf57ae36a3034283eeec0771bea24c9a1a15ea106201af8606d3fc28ad8ffbea2cf274458000000000000000000000000000000000b962565763c4cc155b2d9ea104e754e5fb4745303240688fee7e2256fbda82dfb515a51096be5ba0b111637b1a25438000000000000000000000000000000000df04aea745b9df2df0e34153269958d3640c1596fdff3fba696801c96371420a3619c5ace9210af7e0de4f408b09a7729b12cff5a72f27e15032844fae50e3cabbe31a69568bc4b5cfa884f62e7e204000000000000000000000000000000000e6be3275371e533a676f8d075bb2ab8b0216642ecde13425bce4ffa8ac51cb1b4c5c789d82387f5355c27f18da556400000000000000000000000000000000009fa3a3df5195203f967322cee54a15d1e0096922b6b881bb3bce54587fdb82931c0b87de7a9dd1a21b4389a34d161ba0000000000000000000000000000000014dd5455deaa5ea4f9b5a6241c2e8b2230fabff9e1ac08b359f029f4c7838201cb88a92a5b696ed47819e4866512fff300000000000000000000000000000000181085d630d1e24ebf79bfafa134c08c0e75626dd400ce500392adf4462028bc714ca07b28b8b8f15c9cf2934a299c3092c1b10d980826351c3d193a0f54a7dd78a3995efb02fe5b4525fca8791b1c4f0000000000000000000000000000000013b60e3be9d7d43eb42f7cc2c0a7efc81c175b696e82b034c87d1238db2798d9ad6534b86992653d86755b4f00cf989d0000000000000000000000000000000009dbb325624e698c76b9d697e4f7f03e502ae1cd43b49a0957fc067858e20e8c7ede3577f336eeccee58cad53eb727560000000000000000000000000000000007f2f50be2c6fbc500ea347cd14ca195af08b835814ca515d14dd2f6078eb6def2b9475c2ce370780acf394065032d0400000000000000000000000000000000109803d612b9e27be5725f162d061b9428f363493c17eb39c097032039387d96d0939a06466470ab62ff507ff762fba78f715f35fc967837facb515ebff3df502223c29e7089fe6d2e9120bd3ecfcd120000000000000000000000000000000008a9fcb462412c1065dc7c3623ba5a980e6f86cc813b5d8eca6b1b8a302ee4176cebc233411f2c9ff171332c66a0d46e00000000000000000000000000000000058d2e7ee02bbd4896b5bcaac0f2b09c16d1664209710945c1f7f1a53e24496d7eace99488debb32afe10d7fea442cb800000000000000000000000000000000084d7600bcb68d5e375457078672fa07ba2c87c8ec5f9eb7b61a0232988b197aff052e7125b33c6657729ce8a1c668e2000000000000000000000000000000000a07c42468c7c65fcc984bbfc2f05bf452daf17d57e669ee5992ce67517e1c93b5f7f4c9434d40f3b9bbdb3446ddb982a9e49fcb12c0b1e9bcdbda52e9852ee0e98fa0d43f7476b3d65ef5370c9460a3000000000000000000000000000000000ec380d15e0efd71958978b1f9298ced4cc3322e472d03830ebbaf2a4601c8371e6bc1cad047b0e1e429ecf6fc628208000000000000000000000000000000000b278fcc53b7527545ae1340c24158ff662683919717c220e7d2838a853fcc84ce3915f105a932872ca7f64b7cf096ba000000000000000000000000000000001520798dcd146c0b39ee727e8276fd998de0157a68587c2fde56cd82a9779b6ffbf745ec151210d1e9143856f24f01d600000000000000000000000000000000175d53b992d750b34f9daa39aec918a0ebb2f539db8057eff1409492c90f79a00f14a4c53445c028bef5d6372c9f80c680b0d6316c5d62d41fb0399256c5c46ebe2a12eaad835d2c7177bb7325e21d3b000000000000000000000000000000000fb3863bc7b468f1a0ab0e4701ea392bd820ec5cc2d7d86b58949002f24c972f51f0f82400fadebef13b750884b35f9e0000000000000000000000000000000008fca1b30d4e01991811679f261d11723086753e816239c8c7ebb60ce9ac0ea207011a69cdc29e3336e8f589b71bdfde0000000000000000000000000000000010696ff9d78b48743abdc6c1f4b44b4c960aa516623a24da515206d95e65286e453a8f275d98aaa09fefea29e71b5643000000000000000000000000000000000fb4b5eb18b6f6f8ee7dc734e8bdb625a403dcac6d0cae363e5a7f3a834c8eed5f01fbc4dc752e228c41f3f9d992bbe01b96434f34fa3e00ee0cfe548a2d2ca29a848cf1c52f940685caa9a227e32a61,00000000000000000000000000000000165baa8b143e3734169986e68a848739ca05330786012de260148cfd0810ffd5659210855f19ca92566ea0d6c48086ec000000000000000000000000000000001225672112e0476418288f381165292a9aabd009b0d9e44d9f8f00469b2c56698f5f985ab6292c9dbcf73bcf610080a20000000000000000000000000000000005418cba24a43fc7edaf2fe77422a0b2e8b38a45415e13654c6176c8f7cf6bb2b80401534154cd3b23e977af589eda9e00000000000000000000000000000000067126ad59105621cb0931ab8f386570b54977563ffd69c2231c56e7961f6df2c5d7b114e0b1ea176cbfc1d657127286,240480, +00000000000000000000000000000000139cfd67c3365c5b4422063d7901108c9f33e233bf6413ba2e5b2ad62d188cb50dbd3dac0f298aef7c1d621249d4b0c50000000000000000000000000000000012fcc0d5d09cb3d86895f76ac3d3e9fa9b2495110b0276e7a039d7d2fc2e48fee646fe331c1d8e6f019898ddb43dd09b00000000000000000000000000000000159356eb3ed0d4f146dc929aa6c77057be5ffbb064432d3fc35d346f19f6c1f8552c7079e27f3188bcf29941375e62c9000000000000000000000000000000000fbd4e9a57aaaec40ef9bce8b76b529bd2261d373f05fd69af58d1f23c089497473e44e937b2617a92942af1a99d031f10e0acc22c43080ab9cea11a60866feedd57664bbe6c3f0366beff177f66318500000000000000000000000000000000022ce2d2bee57f7567e9b52ae8e913c79e3b2dad381802ccad317b525be0b503bdfa92722eb0c21fdaa31fce2421ae300000000000000000000000000000000001177074350288dff9dd85dbee758fee1400cebc173793198a96c0be3bb810d352720e94b9bdcc6f5a8951b3a86b2a0e000000000000000000000000000000000179e21de58ff76427f5ed7c8ca3058d0e5e81e436280aecc75a3d989d1cf11d41734de22bda74cf0dff175ac789532b0000000000000000000000000000000016abe94a49f071fcd5e24b5f3a837fe3fe7c7dc53416f59d0469d71f144f71ade4569bac3aaa202a8479c794bd251645cab0c230c354cbf1a3c13c23a36ae5f2d5d084d7aaeb427c580cb6b9bfd9df600000000000000000000000000000000018dee638031a3c9b1198cd4a4f267cdd66849e2b80e3d670897d9e058bbe772936d827eaa4e78283d42ecd25eb4b22e200000000000000000000000000000000009c04ef31cfda7c086a31341434a1698c1132fb5916d359a523b98d05d57bb38a1e2e2bb779d4762f9d4ec24fbf2564000000000000000000000000000000000a788450652e0bfae66889c66b0dab8d1972a626facb690f8e4ebfefc7e1a7b2b58f6eed02c1f10a74b140a49b6c5de50000000000000000000000000000000009e48b52f2b0548dab1c0d260144ad2e66a22e0f1781f94071b5a3a08311d11dcad6963b4339fa63bd82b4ff0dabe685290608899cce4b3d25f57519cc881eb748e9ee7e27f7b21d69f5d8ab3650c3e8000000000000000000000000000000001319607058637d4b796020cca79d62af5862b1c186f32d99c0ff53a830888f297ac4389582f9fd010534d824522e6fe3000000000000000000000000000000000608ca0b4806f17b59a805a3f9f75e7a33ac0791e05050d4eb19f2d4c845fa4e4c738c3309e24a4524b6bfe716949ab5000000000000000000000000000000000a6a6201ec077e113995acb81d4d07d0c4a085d367ed740d26c4a0c04ddf28697c1cf5e648b25148888617ba77ced5e50000000000000000000000000000000003eaff54800dfc8eb3ce647ec4ae8c1aab6a87d4853a1ea061a5e6367d8ebc94243837d4752a1933f7eee0ec1ffe68c8b71debbd9f3be5d6e65e837bd78605d5653fe63025c320cf49c035ae66d8ff5700000000000000000000000000000000122822c91bfc4f761b65f4066a94c0eb1f53133a1355c019f04003e84edc5095523b2ce87ff24bb42425ce979743ce31000000000000000000000000000000001928bc315800ae9936e5b763bf29b19a9aeb71268cb47706494598e0ea057f9dbdda6733d9ea165acade87bd89b3ec12000000000000000000000000000000000a87c1ee17bcd7d348ed1a5022bbc7438bfad06172584dd8e3b51db4b3b09645290382ba991df37db0ce562c950c0e6600000000000000000000000000000000127c80da591c3ff8d300bbdbe27e0aa21b5edc1c1fd8a5da27f58a4dec3971b3c4f9631bde244a7072d9c19f1c0a46be250f62ee2c2972e751b36d95a578efd2fa5e0a2c1e29475a3cee48a28080cb0b0000000000000000000000000000000004bcd0a0321c3c7e6161cd53254353905c27d965f57c9783c3fa7cd5c55a5820116415ce45491d5d1ccef6017ea4608c0000000000000000000000000000000013a30e19c43a1f466c0c3ebb5cf1b57c44434892b18a7fde18a2a29b09a5b4d13d26cef871d689d9855a73a43d22119a00000000000000000000000000000000066d6b3c9a949049413300ec0398d605277911d7be327b1d816cf25543d1b2d7c31d912f426021e612b56ca288b462450000000000000000000000000000000008549f4dfdf018073cc4e32ac930397659ae7a59ef42ca4f864b26e4635c2b7669186a107e9e91c35f04674d2be46051ad08c3d2c36085212542427c1760c72f22838be5286402ef87403f816f4fec950000000000000000000000000000000015900fb486bd2c066cea98e51d30424681fc3347a1cfaeeab65989d1adba104a362837bee51b8b953ebb520feb49aa6c00000000000000000000000000000000198ccab1f94fa910f755936e357a92d358e00cf406894b46adcfc301918c4fd7cf7200a1ea515343d577d920680c83640000000000000000000000000000000018d9380a8568adb92f8f9f67c315f2a837d542b32aa82d9bbf5db6dfea27260738bd0a03683a9988c6c3370563e7bb8f000000000000000000000000000000000528ad42f23c4e21a687f2303f495e962b0a90713d6ef3abbdce38ed166ffea9c132e50c5b002b2ddbbd4933e9a1aedf6ffa16b6fc4cc9509a2b8d8434fa0f4f38b4cb4eb1bf7f545f9f43b9190cad890000000000000000000000000000000017eb2587aef34b03943a170d91d99aa16ceb2a36df3068663382ff4c135083c998743f9145a2fd5dd4ce3bb8b64cf3fe000000000000000000000000000000001256fb29c7482e5469d64183e3e848e5bf32f9c495cc495c3f8cd8e46f71c3f9880f875cfe429677615a6803f849952500000000000000000000000000000000146e2f329f86ddf5b0b17c37aa2905122f457c2c812782bdc15e132468af48c49b715e3080da504d59414ceb367596f100000000000000000000000000000000022a8e385972592430e76bd952a700df8d35b32deaf06c60173d0048d6ea22dad95cc62300bc1a60c6452c41b32b504a1271d29abc5f972809461a1afa5eb186dff5e28f20311a1d8416f8d54fc4b2d90000000000000000000000000000000009c80b3191783d235814fc86653bf2f9a32cb7938111408087b6ab5bafc480583e7a2a32c6bee0ee4aa867ad5dbbf77a000000000000000000000000000000000a09af60eed6c47a6c2615cbfe62025530b35727b42fd812032671ca1eece6694aaae259b05906faf7fbb54362ea890900000000000000000000000000000000055c5f0818f41e5d73e8cd5f70fa77cf477cad8dca2a88b8970a3a25c8f38382268e439642518f1974c5b470cbf29699000000000000000000000000000000000834e44669043aed8ad47cccaaa7476ad830e38fc1def66aa7e8207e889ac0fa1a931eb1e90aa6e1cd694bb95056c3e63ce55b3b32ad29dca1a0c99771fc8f7179851995d5eac804458edede9b8dbcd000000000000000000000000000000000190f8da34caaf472ea9b0f41851f808bba402b9be4baa5d02d1bcb2f66acc3172abe78a49a653cd24dea402dfb972f670000000000000000000000000000000019931343d0e59f0f0a060bcbbeea92fc4670db510c017fd94e0650ace68c2925c627f373d8e755813c199b79c70369f20000000000000000000000000000000013ee811cbc036d2786d8ec0339627d6134b10517c8858f6c6db19a9319636459ebaa217649825ffba32a224175267de90000000000000000000000000000000011039d587f3323ea9d3c50027c427fbcbbf7e097533d8a5f7a61520f3eb548c399e401df0f51884395ad6a338c0a3500c6fa7aeb016b3e3f599846af83f426b9ab85b6857f901c49554d03d27a390f5c0000000000000000000000000000000011d5791e9bc632eb63bff86aa433e6df463a84570b779c913f67e77fcfefb6af48f3df2174096a511ac35eff64e0e5f3000000000000000000000000000000000282716505907931bc93748ba1729777b959d65aec5a78c9f829ae6f2a94a022116715a8c2a653a832a62625473a0cd1000000000000000000000000000000000f694a16ce7a69f0261a0ae19478003dcb61bf93a2ff39f940fc4718a38b9f4b6ab13527c5b438d22499ba29c0b5461700000000000000000000000000000000031eab53440757e4065804896e9e811d459665598546796d67472054fa60e5da8685d8e847eae342e44730056757c6287275a8d16c02389795d54ebdcb70a39fa885320d00cd4e5aa15967916e46c61500000000000000000000000000000000138862ee422bc0f38ce3e27ed3c1b71f71a03d61cc474d989b0cc824efc512ef173ef17bbfb2090997eb9435f4d23e0d000000000000000000000000000000000fabf1fac2ffa25d9c8cbd49b3db5dfdbee52adb947ebc1a3423c9fa2f9d3d29329b60ce0c1c739c7fc6d5a5d3b9e96400000000000000000000000000000000090d92e8763d4df49b8121a50affcecfcd632923b5fede480a3ee79128781f3f49b592d8f65d30adfc75d8a1922c41b0000000000000000000000000000000000074456b341565b13ee3862bd87b72f9d01754c7715751738c5b33ee85e3d8a6f731d7292bb485b5fb59bbf3ddf9b0d0dbec9767ed2dbde21fd8f315ed6292b5b0b1bb6daf2b62665c34daed00a679cb0000000000000000000000000000000007b85110889fed72b3654a8632625835cc041ff0a827f3e1b86c090d816d98cb3b4be66b6e573b3dc05b1998f2772f0e00000000000000000000000000000000160524507679ee021f4307e5a9fdaf01459cbb9a3fb9dc8be5599431e2a8bef38bf8a05d601580085da503dfcf57aab7000000000000000000000000000000000f98e2e7ae9cef2b1d954b7f26fa1755258112c496605c3c77408786d4b210e51c76f10870f558296993e0ddcec3d76e00000000000000000000000000000000068841825f5f5d8f622c1d43bfe090d11c6996688589c3d644ff5da47b94c0638128878d51dcf6d43637781f0ab21a68ff634fd89223733f407c242e52f034691036c7ca69f30e6cd444c561de9ebdaf0000000000000000000000000000000013ec97016dc3d6a3cf41edcc18f88f58b1b88cb2616bc2a8f96af3e7774ec1aaefe86a86135a20ab7592c874a33a8e1b000000000000000000000000000000000021dc7e4be6462d64ba6c09c2d326ca0164305dbf5ca1981f265a1e50f1a646748ce66ae07297230325937faf60709e00000000000000000000000000000000121bda2855503ef11b043301cf331a0fda6e5914e5ca657890ffba2542d908f8fb02c2c93cb4ac4fe5bb92eea757ca7b000000000000000000000000000000000386fdda56c778a7552dce451a6ade55cd24bf9eaeb837ebef898e2e868d05eb5edfe97bfa8eff8ab7cbfaca3c918910461d349e9711fa701b92b62dd3e3569d1203b6a35ac8600367a4df9a9484bdb0000000000000000000000000000000000763746ba87e8bb547180b0bf18699ff74f11154a06cd77a76cc9c264db7c48286fc52e3ef2d30ca914cdcc5c4ed46ad0000000000000000000000000000000018037afcabd273413eb4a712f5d1888249dc987a6fdb8befb92c02660604bd11deb33f283b37f88880cf1be2b2e71f1c0000000000000000000000000000000008ecca3d1652be4764720ef13a6ed6164a3ae89d160cc8c2c8c37bcbaa52db0fc0de84fbe2a19b93b8100556fce0fc80000000000000000000000000000000000c5727babfbc5c36c1d57b9f69c5b41823882e0196e9e0a89d5f4380c4257818d90b1fa6d782e774f2424209bf2e6b5fcc110fd7a6ae46ef78c0e26183e707eb5e0a2944e3afc09e435d56e91584b93d00000000000000000000000000000000142d41630fb9db2f9630e4d5f9c13069242fbcaf1dd02f93224174567c3f944fa02b9791a409d9236d89df6ad785e8ed0000000000000000000000000000000002fb5fa0b3a7cef16e5638f217bb946085fba870836c618a7db9b4394da9144850572daccbff8208f14c8082aaf1ef6f000000000000000000000000000000000a6be9b4a6a9b96d2096eb3a95780f11be1e13bcb6e625517191822403935c52cd40481bce2e782c42b11321cff2cb7f0000000000000000000000000000000019e2d94e35d608a50b5c8b371044f6410dd6c1988ec7a677016d4b52cc3f21b82fbaa7db897f7107d81a177c31f8e52467de5b9bee26b26b28f81d96e880a3f07dd04eb56c15314f1a789436e01adcda,000000000000000000000000000000000a6f3fcd812e3878cccc6967d49b104599fdaa80cb5dee7298c3fdc80477d277f2c68f1c941f6e03441eb176c222a448000000000000000000000000000000000a4007cc5586d677e7945dc8a5872b4839d5b256999166e7fe8efe4d56895f93be4659f43aaf68c6070babb6d3328168000000000000000000000000000000000cef5304a1077c8f31d72e6f1f91ef5a021d8ba64719b4527225b34e615af388d9b1391f65511eac209ff5e86244039f000000000000000000000000000000000c856e7847ea0b4a8334d124417b45a8689d5d9f113b99ebbe3af3f9aae1cefb236d751c40488a861a8f0e0326b42c4c,240480, +000000000000000000000000000000001805009a7fc3d1705936696191c163a07ea992cbb4bec66884a2d58ac3fc0e16b6e0d2292caccb3541f39b7fd6098100000000000000000000000000000000000f3bcfcb0c400d3d06184563204bdee465de167c7d17bea2e2150fe12eb9bc3285f5693b222fcd224181f8d193b7d95f00000000000000000000000000000000028d60b7fc3790aac7f6b3ec32c4be626a2c64c6348fb8a1f39e58ee56b81469e04886ed9be1388958550c02ca9a75b9000000000000000000000000000000000b60ed8052e43e99d3c10a4b97ac3197ee3cc04ad857c5cf4d8ea1df2671084d02fb683f28f5d499910351354d5e6288624ab43047c02e30ba2ec671511d06f869bf736a9866192c5f2eea6c065acea40000000000000000000000000000000002ddb1a9a88e3a0697540cb008bceb075e87e2331f6e9b68f8ffec48752d93cfda5fee121155ad2a142c0ec42808fbc200000000000000000000000000000000144b694018840835fa9c50fdf62c2e32261a8350d2ef074dcf7d016af982316a0c6f9e5d15d29d3a54d8d25aac5534940000000000000000000000000000000010a3765089ada75e9eb61328756ab9ca7b8362cf86cc82af3cf43f390a0745954f28da72a6ea4eb904a040596795639100000000000000000000000000000000056b51dbefab453012b35fb6e06af06ee92e4e84e92a9967b379af760fdca4a3f10f938684a646fd70a2188721c92e98edfdf850c0d3e3903404fe3e0f523cd230cabc45946c4fcb6d0e5e05e388c23500000000000000000000000000000000169effb324d60b71dc7ba975e3d5f18700b34cb9017f482f64be37c4df01fb66ee9eb5870e43649225c9a88a0d499b890000000000000000000000000000000016c7ad9c5f7b65a9423f642d87621a5192d7548e1099d774a99a34dd4ec9623aa1168b9adab092b3cf450f369bcb627600000000000000000000000000000000123b35bbcd791ce0d00148cdb3d35ba39054a7126ca5ad3351fef1437461379ef639896b271276a9561b46e270f7501400000000000000000000000000000000161fca2deb729fc55f1102fb75ff466319f18510fc66d6cf95a8256118fca618682f00318b0a5297be873a2f7af1915afeb34852ce0f3b5730962023418ad6cb860716dcb526dc53e8ab6a74a6a3910b00000000000000000000000000000000073ad8c2f713288313185c3b2455ade93d58e70d5df6b8dfaac8eccd990fca6843778fe42cc8aa6f34ee44aefb49397100000000000000000000000000000000012eb9cf288a366adc58d40c9ea5f2cb5dcc5b04108e3822266ff20eed71f56bd74f1a2727f20d55917adf20b6c4d6a1000000000000000000000000000000001463db177fe5c0dcb899797f89da963731dd4e9e8b2eb77b465b98415dc95f6d5569df51bd2b08a13838f4cca4b62fcc0000000000000000000000000000000009c0bbadad98361209f36eb23a9eeff98f6eafc7d5327fddb6bf43898a2be704520a005b84c5b45c6a68bb7c98d65d6dcf25e64093bd92a8fb394511215a3fa674db86d7329ac5ea70ec77d24d4ac58e0000000000000000000000000000000013c63973ce6549ca3dfe8ea8e3bcd6b0bd88f7c73730834d9ffe2076cd4345090d0364d161ae8998af1048d102f22e5d00000000000000000000000000000000060cd24eea4177c9a5c37038d4cb62aeb709218fa8e64b9084e002f53a0c4c411825812c20df282345bc4a6aabfff6a100000000000000000000000000000000106ea864dd52933be02c1a79cbaf6dc81ae9a2d619bb368c4abc36226104f3b74fadfab906e36d4852a6412315223bdd00000000000000000000000000000000192e45153e4942c88bcce76098fa51782a81b53abddb4c07bd79a2391be68858e2d278969b9fe75bc652d02fe4db1a130b40db4f9e5c27a3208899f4f536880b97f4c69e7d889c0726d87c3fa27e097500000000000000000000000000000000101ca1625e9d4a51e08f5eb81387b361f6445eb307d9bc92acd29d62735d4e5078b1a9b36b94e4ea0a314703a85ac4cd000000000000000000000000000000000f134c460c6d931396a0aa397558975ee973e642f1c4a32a3d397051fe250daf4215ff5ac4b2863d570c87f0e32c8cb800000000000000000000000000000000008eeb127a38104351298ad77481c32bf51bc5d3910b03da0cc34062dd2a8766adba6891cb9fc579672276666e1242730000000000000000000000000000000010c896ecd4bdc1ce010da81a51dac96409079853635e57e5c3a5733956a5f5a9c3ea6838849e286ce0405dd54d7e32d6730bc7f68d8d371d0bc51d95f8a5899249b8db5cba0d21fd88ba6f86d8691659000000000000000000000000000000000be489a1c71246adaa1c1dd6d2ddfae9523fd1d58d00d4f189f56d08632dccc694e63b371db6922a7f3faa05afbf487500000000000000000000000000000000174212b6840a797f0fe9e209b41f55aa5dbf169a2e2ecf05de48c44e608f6cd6d98ff5269e5412defb431caadc8a09c3000000000000000000000000000000000f4501715c0c511703f6236caa82479b3368de430f2c2d95b39193537be0b990fec1ed8e4d94634ee6233cfa359b043d000000000000000000000000000000000f3b4712f95005004d99fd739affc532d2c4c45970316c1a43f76fa9b57f6676c709e8791c276237b92750f5bdc94492ef06360717cfcab15be966cba2836b97deeedd20a52f88c73e2a583b64c8e5f00000000000000000000000000000000003abd36736fec3e8b89863670666365b169d8510090a89007c7ff3a82fc62ed371544013a1444fedc4358e92ceec62470000000000000000000000000000000008229855468fc63f4024938cd6f41c6e6a5653319cb83f38ab7efb9e9d281166261e7c854bfc08f55a0a9ca47e54dd42000000000000000000000000000000000463ccacb341fc5874f6ba2d44efb5cd24e9409b2ce7f43e9d39466288dc833a45988261f45d34332f416a68c5d10ce80000000000000000000000000000000002baa086177394203a04ce1b46415983399e60986531967b690b1a13cf8ae039b56f0a00bf9aff357d51ac57f8fac8b282b7d8b8b9345bf13d0e113b662141f5ebfc5888a5ef8ea06f7d5d137324ebef000000000000000000000000000000000b25a203268100df0510e4155c594a144dbdefbb0ac95e02bb4b3799aee4e738ef4c52f03c6937cdfa7275c28f130778000000000000000000000000000000000c432347a2534e86e90ca346a7b8b40f45075727847fa3ae2f2e297baa14aca88ac6e08342f0d248a92e2c272841fddf00000000000000000000000000000000057ec8099e1e30329762ccf0641b45e1a226f7b66b80644fd551d6fb1f2136afb8e8ab5c6905ffc7c24e67d7f21863e4000000000000000000000000000000000a9e472aa993bea05961affd6782efe8f50d746928efb8fbd328fb50a254db861c90db8df7faa7da8266ceb47fa1a13a2396fe15751bca2c4a651445cef236a865269849908df53551802dd378b892cc00000000000000000000000000000000025484652f18e2b32e2bbe79916c8bad42902db5528fc45993e04daeca008f3c2ff38fe4b48c292f70a7dc57654233400000000000000000000000000000000008e403f472b60a6046fd190544a1d6b249dc97cbd8641c62613f4de0e0fa9f5456d843ece4ac2b9f4ffa2c0278e61829000000000000000000000000000000000824e0b9b03198597fa54252b3df9690df678e9c6d82301848939dc55ab25a7751bcc2b99786cd31960ee7030bf68ac80000000000000000000000000000000018d1d8c7f2b20f0ba66db616322e48ac8f1d6f4205f228ee8ee6cd13d1f64be9af338c11f511859baabea3e15d165fc09a5897c9596223ca4d6628ca1f793a000aa21a739a37faa28637692b754148f80000000000000000000000000000000002845c4255819ec6e97abddf4c9db7d91658dd1d55328ab0565144b377e20ca0743d93fddf68acc985ceb7f7431e30b0000000000000000000000000000000001577a5691f2425e65ffd59071c2bb167ad05a8fe23c11c7f7464764442ebb2f7a75a8d02594d4426c1ff022f7a6e19360000000000000000000000000000000012c6ffefcd3964362f1373348404d04d1849e98ffbef7b5ed5704d74b9550869e30a4df26e74b5304b85c7503f7487f1000000000000000000000000000000000faf3dc42113f27ac27aae36725221d04fb1ab46b59e16277be0758b8fad706fa237c0c7627771d8e8d3ad610f63619bf20a2973faf886556e5329363bd9b9c96424fcf2e953df90bfd011ec07bc66eb00000000000000000000000000000000044de166200ec06bcb88720e57b84cd8f9534d1fe303a26aca08cc35104ffd7e81a6473c08b28037118dd8a61d090e910000000000000000000000000000000000f4325ebaafc67945de2418c81f5da92da4e67866ab5965eff0f392cc527fc34ba4e7e16b91c26aa370b27eb6a07f6b000000000000000000000000000000000e1d77ccc1c196cf1cdf0dabbee4829d56e937372e9f5613e261ca07e19b3fcf10f7a45c490b98b5a64b955eab5c4f2a0000000000000000000000000000000004ba2e81f901b0da1ead004c76d43278d372456c0c0a8c6752597823d44994177734ed3f355aaa22f325ea36b7c9eba1f4ddb773155a27badba330ae5d26096f350e9ca2811feb227c4eee09d2baf32f000000000000000000000000000000000c115e270ffd6f2cb9bbb2a62e04c3bf7be9d7db783d292bed272c297773b39e9e51c75e5c79a6606ff7d0bb9ddd040a000000000000000000000000000000000a57b637126b16b23bdaa6a7cf2346f33778cebdc0c9943eb2985ba5c4114674cd596ecdb6959791139c36c22148ab8300000000000000000000000000000000177c7ed16c29d99d3d98c6facca9cb5ffe72e6aa63959dbb51d9382f0fa49b02a1652a398eb223e093516ebf134448c4000000000000000000000000000000000d6bd518678828f582fbb3b1bef725e66f442c4d3e6325fa571e13db492300d03c0188399a2ef9d5687a76e647873c0f52e4030b5a4bfa767ae20cdea7f464dd2dba51c9c698556d24b8f3d4d1afc82e00000000000000000000000000000000085d4f90336987f99d250067c2331e7de8f09a80d71fef0570ecfd99e409c1f405058bd3461c9f8ac5ccda406db89bca0000000000000000000000000000000015f310660ca6a0c06b458d0b840a5c1c476d5175d9ff6dce6334466d363d319939572a2b00662247be1ed0f4e6676f8b0000000000000000000000000000000011e9352c0f81bd3857806db678bceb2150848f2224ddfc43fb0c733f0689ab4fffde50d5ce04d54055d27d7702e5d2d40000000000000000000000000000000005d835d04dcf4199130d6a16e86cb97f4ccff58c496594b83524dcd88f5570212f06b744379288f2a737c7a82e897cedd32e0429e7934faa526475c5c7fb977c3030ed74e145eba21af2d2cc8461580f000000000000000000000000000000000f7c4e621c37bd3068a972b9d4211abf9026e438ac7f8cb341516f7e6aa4d8bfb3536389e9155029ce9e8d5d376eec1c0000000000000000000000000000000012a46cab2624797513f2acaefa26fb22c4bf29188881690c350593fd1949cbc243c9d1d7d27d9d76aaccd347359a45660000000000000000000000000000000002dc383d4f9b75907f74bace1769bb5bb1b27a597c9548310f2b5f90098596fcce6b5fe0c72bc8be9037fbf31050d74e000000000000000000000000000000001900deff7ddc62ac302c941e1d2a28a4bd2351edd7700042ea4c4a48145ef91688666d8d7de503913ea259f0b58809f21f700d651c67ca5b8d95fad1a8e412befdf691b074956bb8092938bda2ad26940000000000000000000000000000000018ac8048d58f7b1a9407d3101824e3640eb20633f8ffdcc97d43d1b25329a2a1e91added42801c03635ec904e627eb690000000000000000000000000000000000b499fbdbe2ed41dfd6c454796e1ba57021f355a4de8f60964c78dc685e2ffe9c90f5a1f6c9677514ae4a9c95c8d6450000000000000000000000000000000009d10e5e2bb69ea6fd820778f75a2a60627802a49128c3f999d8c1cc2ba56ed18acef354a2e06fbbdfa7e7a4ade7529a00000000000000000000000000000000082839d66a18763656c2ef7196a1d83bd162e1f109b54c5a6095cc7c436e8a4888c4001696958270f54f61b81b00b32d83052a3bd7a13bb1ccc22b9519c7ab12d2dec67924fd9f15f96069de22e7b692,000000000000000000000000000000001463ac5e269d286961036db48ae33fb868a28b0dd828c3a66592ff9dc115303bdf3ab78a8e1f5df68ed1f3b4c6c3f2440000000000000000000000000000000012c64ca0ac10ab616fc733f75fe6181814e9c204f9e4eb79487ba49e3a9746b9b7916a1d768f2ec573a4c4e226365f48000000000000000000000000000000000a06b5b745dd92adbe1f4cf30c79ce0c48428b3e3b05af1585c4ca12eb2e763ffff46b55a060913e1f77fc9b0b085c9f0000000000000000000000000000000006271931ce9c8b9cabdc932297f3c87128a5af25a9f77e71ea4e588f1e88686638e89a8e212c92f6472692be2e05fa5e,240480, +00000000000000000000000000000000001bf5b74c1ac89d4bab4663943e19128619731e315d2d7b39675f7c43493b338020190a72cc7a6edf0b8838886a7fe6000000000000000000000000000000000713f413bab7919cd57c2de3349394121d6bace3c10df0e41a0ab895433d225b05cdb1587deb93ae6e56ec26a29c39f4000000000000000000000000000000000dfb11c9c0bab7e4d1ee39941d5f6b932ab473567be2329c94bb0b146c46fc1c2cda25dbef8ff9b0066bd4ca3b6da67a0000000000000000000000000000000014e399169243bd619be7f2120b2cae5d19b2f04185aebc7d948007c4d3345a9f45249273b6290c2e86448648868ac552c40774f67a651ad70f17393b386e9ea9e81682ffd78db7fbc17cc5084f3c7052000000000000000000000000000000000a67bca1f8a0b386b2a67158e80262f025b225535a294394584118f9a701e31e91b2c7eb8fc7e28538966b967c139dc400000000000000000000000000000000185e8aaeec9b9abb9f0d6f34e2480e9abc30208eb1c6e023d4986d544b356a387c323c9edb5c52f5a2f0bd59cca7df98000000000000000000000000000000001877ce1ca6e8b30df86de688d950755f2708fd6f933c07ae45fad1b3e43337f1a8454ca5d2a80940e8fee98fffe953a700000000000000000000000000000000117a0ac9d27292f967ff5bff2ebed5d2ddd9f453d6aeadd9106eb52b53447974561b621fdc1d973c055f1cdf824c367bccf1e36e063a5fdd4b735dc18bf07703b80c6b72f987c05641612d7ce73562c00000000000000000000000000000000009fc4e9e816ff495dbfd4f745106fc90c023d95bc64b809801d02dc7cead905177ede5016f537243660e4b7f54a02ea200000000000000000000000000000000180aceb6e9851a11a1e34502897299e7db3e09f4970337612634fd9848d1de2bb3de8ede690ca051a75add5810ff777600000000000000000000000000000000199f3c43d429fe8f73e20f81ea00c4e78294eaaa29fd67563664381db3cee2186b387b880089cf96fb99c2e22c95449d00000000000000000000000000000000040b20ec4e685f104be188d0f15a79f27cf34dd01f813275f6019a9ffac56e234b6c967c80745294d9fa46e0083cdd907ea75dd2f54fa6413ba77f10a11e12abea3a4b947116e1e7c9334a0a37c3963100000000000000000000000000000000189fba635109ca215bf3a09c3e44ad65f7eaf653e0929aed39042e3b9c8b1132c5fe7cfafddfdd0646514aa1f9e7e1c0000000000000000000000000000000000c28f598c80ac262ec7a0e0d1c867e01ef26f182c5df9ea7f88fdf8bcf3a5d2f06128526b1ce72cead8ab4286a0b8d030000000000000000000000000000000008051be3328df43b79dc9040ef0a0263d474acc0edc023f300cdf7c13088d1bb21b5f37ed81b38dcf8718bf6441605f8000000000000000000000000000000000d2d474723c6c246dc59e683be147b1a6bd6e7d3cf12aff7b636802a99954e7a13c9ea429b19833a985ca5649b1a998f6855c61bb7d72b022c16290c6d3ca9c1255cede8e0b827b43e40fbf01840397800000000000000000000000000000000058bf424fd68aac77c42a046f78a55729e6b5b3fcaf436d0d98354b426a95904b55cdffdd9a8892c9f56f170ca8811a600000000000000000000000000000000142c1ded08928fd155b89bcfaf9c8194f4569b4cdeb3bc7286f4dd79e822f5db497768220533b71be8c71d121e557020000000000000000000000000000000000a9c753686534bfcc295eba0a617f86d7f9e78d3fe6d52f26cede97a5b1f107210a757a2d89361645856b7b20e89185a000000000000000000000000000000000f745541841cc4b5352f659c2b7cfa8d51b07f91b0cb8c787b4492bb4b94ea27117695416e2806e57c38d7e565b9eac67fa8503101f392a6c6c27300b6992af3fcc48d47f73db67615a44de883770d4f0000000000000000000000000000000004445d4464b51d6b12f164a49ee3b610f11738d60cfa6e02f8c33b168d9d5db90e6cc558cd12c56069571567d91183a30000000000000000000000000000000009e4b96c2b533a16803a36f8d1f179313b7adbe6c4b90716855474ffb2fbe087df3fc0b4ef14cda7d958efc5c92574ac00000000000000000000000000000000104dff7c859eec61a0ff8e0d831bf9667226d5bdbe298400b4f9e3159a64b1bbc7cb9f4ff9604e3ced40bb0de0455ce300000000000000000000000000000000134bc2461459ed6f0d96aca02b62e3110c2009e1ba7d3258656e9cf97c2a1685faf1f61733ce6ac3af7ef4d73d0b43b1dd947617bcb7ca1c8fda0d49e6d950a84d60230bc2411d42ac32e3651f48524b00000000000000000000000000000000104e5709f8edd71f50eac1770ff1c2b21f5ee8cf5a310fd1201109d1b73cab69913bcfa2d27a8ba16d974e9841586ebd0000000000000000000000000000000003a4bedc6277c61825f6ea1f438c058a1afd494c384689a8479195646888eecc7953b8b8aec849fb5f19a20071261336000000000000000000000000000000000856ee8eafb9b3d25fde7e38da4acec624d1444337b87b0b1a660bf497ff37929b1ef9aed8e1fb0ffc6cacd8f0d1a1a00000000000000000000000000000000011b52192c88264df56de3d7b14372443e25183bb816ea1c0346f15a1f324527ef8531e27aac3112e2a497a0eff0d5485b4cbbc6d537ed2b69c2c32c84f3cea3d2db180b64861859368e98aca32bceea6000000000000000000000000000000000a696c83010719161b6624aa7756e6e84980518416554ac045a93b63c2561a68ca2ff2fd5b6d2d667822ae4e3b3a2ba2000000000000000000000000000000000fb8fdab4f177b0dee52bb5ba615b1d548130deb87b14d05d427984ec148a7a94efc4674804b3660d0f7aae2b49f7b1e0000000000000000000000000000000004914c0359c8e23a7e431e517cb83e5735cb2876e8b53ad45abf1e9eda06e736378ce03ff75002374d47f1bd45b08e8900000000000000000000000000000000139abe340c2d773cc45cfc75c47ff31b2dcdce27ada3e6d6c0823f37e4e693ca30342fe41eb96dde464d14668eb72c5e457bcb8c44a2d9d1facb39ba7ec8ede5d5962b3256d9fc2e68a1ee5a733ccbd100000000000000000000000000000000180345fc01e3fa349c45b1a7fdccde5f9ee70d7d65510e8b4bce654f2541fae7641ad86f9bbc1f02e93e94422433f8b40000000000000000000000000000000006cfe7026cd423be189c5ade8de197aecbc9aefd4cdbbd2aeacda816247ad59ae06a5c49b0e29bf1140f400d46845191000000000000000000000000000000000cc4f240a317ae9ce75b44fae87c92fe9b6de10e1191cdebdcc37ac200957683849d8a957216676db1af51fa0a2a1136000000000000000000000000000000000ba84d595661e5d9bdf9d268a3cc575fbb6b0d469b58b3e43f80694c78f4e9e501c4a4f9c42ee4518ed7189a1c36ca0c19f254dbf75f1c42046343b0060e71302bf6c94ca2fb8aec74fe7a47a3c9c3ff000000000000000000000000000000000fdf7e2372b01b5d926a18ddd06b4573248c02d7debf944312dc06f76ba08a7be460c451d296b71e9e81cf0956b974b80000000000000000000000000000000018326d0e1bfb4a62ab6f772b47ed7188035a62141e6b2eccf53a299028902a172771e8e46c0b1ac4833ab12045922b3600000000000000000000000000000000072107574145c6afdfc7d618f2dba2b8bb01d92007dafd476e4ca62e6053e5e9f2e34243ec2dd16ffdbe3488b925a0f000000000000000000000000000000000070e8491a835ae96087013b0f8da267a7ca5b0a600d71b8c76fee35f41d8b5c1ad82c5170b0e8d1cacfc7b7b13938e96f08cf27a47d89ae6e2ffb27870d613b9ae586857e4ea00670944a2883ba325af0000000000000000000000000000000018f4da37ff63f66d68c875def8c758d9a5adcdc408f0c12b3a60ee4a285e6702b1d5b9326c61f443dc71ae83c7bd21e80000000000000000000000000000000013a665e430141cff62c25577798473a645d20321490bae7689de6ea223a434c7d3b16ad004b24a82e2c62879b2408cf90000000000000000000000000000000011b0108562f53bd47d9f8ada54166854bf758ef3769ca1c3b7b006fec8707107fef0b6c7e59feb727646b74c27ec699600000000000000000000000000000000028799b52107d8965066e2f629b30c0edb490a0f4d0b6cdfff89a9f7763afbe6217bd42c2059042397b6c0443465fdc050aa333bb6b44086fe6211e89cb70b8467eccc228c09aaa1d589cfc24771a11b000000000000000000000000000000000c42cb42e389f32926ef09584516249ae332641b573ed29bc0884feda08d35c1bdc6c3d4a69fa15105de95010c6cc24600000000000000000000000000000000006c57fbf93c7959c562e0f3ef59966c1640c706fd18a6b539dfd711b0ad79643642038954bc866d42d1c04be375b95a00000000000000000000000000000000039ca3ad23b71693e02af36a4abe6ccd0dd4f4aa709f74d900b9fd015a2eaed55bdc2bc0749c995783a7615971e8a1f50000000000000000000000000000000009a08596b29da34466c8a7f46b805f1b6f2e48bbba614d728562981d3d4884de9a3c1980d398eadcf69e90c851d48526d9f7f74a5ccbd01afd985d3259739023cd012cd67fba3a4ab5597e94d8fad43400000000000000000000000000000000123dde5bb9b7ca11da9e08a9489cf07d147492be8041a5ad0b70715147e21d6017a58af23c47d77885a7830cfbbe5e0d0000000000000000000000000000000001527cec3c393d03e74ee8a7b1d6a8b6398945cd284b59a93fade9839863f0af591c287e89b3b45e6048f2f9b518208e0000000000000000000000000000000017ac3a2d9458bbd5f38d584b0fe4b35f3a452e22161564a7582465d2068b3ba4dc5e1e24a996596b1fb553d641996a4e000000000000000000000000000000000ee5ed5610a78dee181750e35a8ab91c001446f04124930c2ed85de74c6167009af45a6cbc3c59c4915334d7853ee12f85c00be7e66e318bed8e66cc41e7fd0593004bbca20f0dbc28efe4441acfc9ae0000000000000000000000000000000014d60c1d436e4486f35ec85bf2655ba6b752a36c86fd9088c0ce46363e75abd636052f876986fa0f4a59152998c0e4a800000000000000000000000000000000083328e38373f1de1049deaba78f568db818b1dc38d981ae92b968134d369ccc399bc3bd55c841755beb484cbbd60f4b000000000000000000000000000000001788850a5508d81df9af1f087356bf8e63b3c8a4e209403c4de7b3adda07684a08f9de6f1f8fd8dd4b2bb9b75be329cf000000000000000000000000000000001506a37d222173f0098f56b7c443e04ffe08b376e1563344e7bf22b1c9df0a1292f70ba51cbe554843fb93a7f535a4aabacef63d90ad11bbdf0c5fa2db2838c238ad3049a3f47b7f67361825efbc6526000000000000000000000000000000000d5f153952defdea9309269bc996a7714deab12e7644f8f8344140fe53034de538aae6c3af7b06687684edcd2c5dd19e0000000000000000000000000000000002da67345153c87ca65012b8703acbe777900953abaedca4770fd893275948d150ca3d6694d58bbbc9e62904448a8d2c0000000000000000000000000000000006e8c95d22f01fd9d56178d754f0892f46166282a27e6b02826478cd39119636e811c03fd835c714a59bd2f7da5ce5e1000000000000000000000000000000000b5ab6233d8dff50648d89cd65793640c06ea784d00aff329e882ae04fb466506cce3fb6c381b4eacef8b5305953f7b6473fa3d16e6431da14b8639d4fe316692db087a167a2c4f07307e770bb9e35ae000000000000000000000000000000000595edc440a5c94506a79f3b3fee818256d7c4185be40c1953b46765b2f925ed16a476b07a267570c727592dfc4a0d8d00000000000000000000000000000000079ad05473fca57f26fd068ed659e4aa4919847dd96e683e7d4b3a731cc9ae0562a693abeea4fd550e644b43b553118500000000000000000000000000000000176a9751dbfe727a442797551254cf904862c4d590892e019a54b72f6a5a124d268777b82e19d557690ccfb81cbe949d00000000000000000000000000000000164ab74c150cd151b70fdd7d63d0404214fc9cdafba3bc642aa798b1c301c287ff6d05ee7b3a3ce997072b8189d54aa62774741f87af1d6942dc4ed79b70b2d706f3db6b6d083eef0475334ef1e2410a,0000000000000000000000000000000017d73e29f1d555a10272043ac0900e80883c185ff7d087ee7f5a3b762213e658a42d1b4fdd435d1acb9d5587fa7e8243000000000000000000000000000000000ddc440795d0e4308577fe8439d43418641538711972c9744dfc8a4c206c193aa17958404bc387c7c2fa30bc678937f7000000000000000000000000000000000d7e43c0f99adcb02db99974e7615b4ca0de72117792ea515bb04c4bc8680a3fdb0afcf6a3bdfe16bf54c1d7336aa185000000000000000000000000000000000bcec1d7fc9f2210be80e90631810987801fdf60890ce197db041b6a62682fd7e181c6110956c5f5e9c196049e39100f,240480, +0000000000000000000000000000000015425cbb7075a97dfa9409d7b014127396056dee6d4bb63ea285309fd91280fb691f9cb9572b544b332324f6cb3b1276000000000000000000000000000000000c5b9634e6748d5819396051322d9b7e0377554613a7fd8dc0c71cfb7886dc0ac29add7265af84087a9df5ae3799ae30000000000000000000000000000000000534226ad7324ed5600b5438b659c7b1e96f27ee1d77163f2d3073418f7ded5c613ca4b1a686764ecc43ce3388e0c32600000000000000000000000000000000198267e2bd474dc0415f47f5c87a11fe0945a91cc0bfc37d504ac53f9b9b0d087cd5dbc9b03972be03d4b3f9d2123945d10ffdd3797ad13e65a1115cab6529d0f87b91eb41d6265e694eed8f02667214000000000000000000000000000000000389a084d95445af6e0afaef21d3676794e45986b9520035111ccdbac4ddc1b23974a686a900616f878f3a06eec90db500000000000000000000000000000000064c75d1129753b5f399c1a5166a0f6a8f427d65ec2fd84d0c7339218e0a396681797bab68b33653ffb9820a6005fa7500000000000000000000000000000000147e199e8c08b9af38cb457b623d0fff32242b11e695f2adc0f136c5596db313b03c2466fb58e37c94704152e5c8f9dd000000000000000000000000000000000e8fe5436baf3470a19891b85d15486d1269e1b13098d837b0a510e71b0e6260700ea85f0bc6476217cc73615370cf003e5da5568a9427e0cbd7973a34c147ac2f3577d06f68280caecf8588ebf1591a000000000000000000000000000000000a39a2032858a57ccbfb940741f4ae21b318a56d5567cc0088ed52dddf1e0d5de60bd2da9b675212a9a28ec17fca7c0600000000000000000000000000000000039e2a4bb1b417f8a94b02cad60a3e1c4c4bc5a86a23def7cfaecbfd97d89a5104e0cd13870c9fbd010dfec3ad9b1df9000000000000000000000000000000000bc29c5623f9f18ec2af5bc651a65d89554705a349923ee15a9bfb82c114246b404a1dc1c24d65c8749e7c9cf62d963a0000000000000000000000000000000001496d76f7b8583a64c1627151589af876a2f5e7677611ea15f14606538f6052c56e9fc3ed145c313acea69a51547fb6145b5f1f156f3c823cc129568e7602694107608c1f9545edaa897df58d27b18f0000000000000000000000000000000015f83b2f998691e504aa740b4db38f5b0236ece3bc1ca933b79999d55b737bfec51e590c2127d57625a9b7c2960c06280000000000000000000000000000000001b7b117f5d722e320b7e90307ac1423aec5e30c29602d314bac9e5272ad3990d31999bf3f516ac78b2be0e16c0375d8000000000000000000000000000000000fa7992cd7fb679eb5f9f9a9febe9c3cf41a717c8f6fffbab5748572098407174f09457e13468165f1c7275d52f6c84b000000000000000000000000000000000737e95f62aacd12f8aebc288c5cfe052f34c4d16e7b44df4497d9a713b77485fb0efc09aef11c7b86eec4d0cfd9b03ecf6760be82cefac2843265be5fc0fd6d308c1ed06fc684c4693de25372f09ed000000000000000000000000000000000004d48d72ad4e77954ec6a5a62299f0472bc52b556cf3857019f8efdd694758f13029f9d6832ed672cc210f32033da8d0000000000000000000000000000000009b2394755d0319741d131b012ba0ece7e2044def20ae73fe73bcc276af9d807ad75be79202963f9a5c512a6ca53197800000000000000000000000000000000128f856fc4790d9fa68cd2a3c152d675453dd81dd64f0ab084c6dabce456f78c2bab0e7f315439b34f86e8fa61a33ffd00000000000000000000000000000000173dbb908ed617ffffb6aeb212cfe6c03f7ee51c84134fde67de2ad9561a897e28a0efa66257ae0c21ebcee3fe4fa68cd9fca4d166149ac9e6159ce95a06f790a96243662373637f0c6a59764b77b45e000000000000000000000000000000000bb7b84476d4b17f4ada0b6f50d34dfaecd611356862895c8d2fee6707c4aedbf565560d4207e43c179c5cd33cbb739000000000000000000000000000000000112d8b10c775218d318090dfcef55a903953f7466c50417125ec0b2c20a24fb50bd172331c0377d4f47aec99bd87a3fc000000000000000000000000000000000cf4e4b3c600053f45f350c8860e47621f50f3849872a91ab115f71a2b04657991217e2f0844b296d3a6bc33ee66e6a80000000000000000000000000000000008f625da164bc9d96be3e78df63bd1633a2951dbea0b98e359c6317abe6ac5799c4bb00bbc2c5d02048539e753019a6241733039312347a0c9d760c1bb9a1209a34a02b359a9c52a57eddced1575867000000000000000000000000000000000028db057ab9421eefd1fd481c91153b5c1ceb0f2dacb0097298cac986f036572c6ab0c8709325b3bc25bd494bb46c55400000000000000000000000000000000024be09301c9be4f726fbf7796e8336c50897e8534614c25f65c37bcfc6e724d530c2782bf483668fd08e91ad09484af00000000000000000000000000000000037bfdaa11660111ce0a9c3e18b5da74c004cb44882b1aea4173e18d3a17f04fefa3b319afaf4af9dbf3d4b9ddb2c3a00000000000000000000000000000000008f2138bf621237a286229fe762968a224358b030f6c20db58043c13727b516097b42d47781bd0f0df2b155197ca3946b21b18d883ef62084ce4bd353d7434d7e220e9cf6bd0e8d0bed1ad0a4ad94c7e000000000000000000000000000000000b4e2b058d6e77cf95be093375233e5c9c8ee0cb2a3aa93172c08faea111df81b9721a506180b7b45bdde4b58b0b7368000000000000000000000000000000000f7025cc33424a7c11eef47baef888535d938d50c0f40eb83ae86791834770e5dd95b30aebdd2c13eda3447d5730ce3b00000000000000000000000000000000088270ef05480ef8aac5c284358d8e06c3482c26279734b8513000019924cefeb396ae79f5d9bd863bdd9b22e3ac3c54000000000000000000000000000000000df75afafb138fb06bfd905c87035bc5d18c45a29267c3965131083d7e0112e10556d7693d424172a53e8d3120f0cf2aeafb6aa11296facbc13936bd2ba09a2cf9bbd9dab6ec8cc5f73d78c90b471a3000000000000000000000000000000000122fdd3c83c01c7cbe71f54d783181860e7dcf8406e3966e910f4d0ccddae3a245d6b1f94b1182d1917fd63960cd75d400000000000000000000000000000000043592e5797cc1409d6d42dacad628448799b24320acbda83f6ea9d232968efd021058f540e3bd73a7f95761efbb5fc400000000000000000000000000000000025b5a8577ec1064b5c557415a50e84c2302df97eb65860f979e5b1e261f47c0f305461681beb07e521cf03f0e21fd030000000000000000000000000000000017e86f3ffe72bcb71d46661a1537918d52e886e362d78ed756140a6b5083a4eebb5280b9eeb8a25251dec43a5cf509b13d39a61323c07f9f4656a6c5e6ba139da8175ebfb8a641de50cfa2290884662900000000000000000000000000000000122f26b4561d1f79a70bd0e401f25d50891c0fa0320579ef21aeed7c191fe1c75403a09260c3872cf74b798eb1587ebe00000000000000000000000000000000039a261d9f48b9eab6e89046f333ac328cea287993166057e9b99fa8a7d7eb3e7c34ecbb353b7427b235084f47f45d1100000000000000000000000000000000015d5e297317684bd0169c795d9dcd209452d024ef9a450c41beb0f6c7e6dc5fa0f3ae24c7cf2d7eef97bdc51788188d000000000000000000000000000000001487564f0e9d3e0d2d30ec9930a00f10093e29f2f195344f567960be323ca21231efd8528108dbee4d5ae4de3930ddedf6374d0849a4471eca96c5e715b10505c4c49664f341d04705fc688c8479cda4000000000000000000000000000000001965ac3a520c1ac39b86832ecbe226ae0474b76659076ccbb550a0daf41c40d424ceda084dd991f22cc53779085828430000000000000000000000000000000002e970a4248823049bb4339d21583fdce9540ec103d6e9530b89e39ea875b1c333f7f5f859be39baad34b374055baa770000000000000000000000000000000003460eafb3e54ec03fd5cc1d460e1359b97f5543e6231d61614c1225ab7545fae079ac8e65668b83d022031a7a54746b000000000000000000000000000000000321394863e7c70df3934d874613b7c9d6c331e59a599be593c82edb7a26eff9bee8e4befbf122240d2deb2d527bd38c0b7cb52b99abe10d1367f8d3def38221c18657a1114ceaa1c0673ab13a6e10870000000000000000000000000000000001a5eebe200ec041476457f8585cb4ccdda936cca4977d7701c44e0d4fc5d9c206682a23348013a055117028c16914400000000000000000000000000000000003519bd1dea70245e521988336eb41870599a877380c0a9eb19301f9b2caf963eb559070e23eaeefa4de0173bb1fbd8a00000000000000000000000000000000125707f5a8e26b28968dab97ef4654c315b0a118c20935e38a5a526d9ac0a0e18355d8c9f3f58c082de98691957e2d5e0000000000000000000000000000000010b58dd683f73a16d8bd5557b35b7003a761bdf7d90ef576de8acd420bc74f5219fe7f9d35667feeb3ddf1d568b56bf1f49b1fa80a321d4d100069b2c4b94cbda255d8e9f1a7f14ddf4762b76e4a386f00000000000000000000000000000000018267d8b83ca59d4efce7ee3d73f7b984f09556ea4fa5cff5997a1eeeaeb8bdc9185176d77ad0f4d86f2e429f4015350000000000000000000000000000000014114344d6b7c976cdaf2418d7f72c120c2fddcc65c3ead067482e7073e2a3a239af19f862ad247e3181b13f5236d1040000000000000000000000000000000015db961a093b248e83deea0ceeebfc3dd57c7cf8b48cd627c5c566a4f9bea30ff0ef9cab9287a0f520a72b02d9092a0c0000000000000000000000000000000015159439fbfb91d1e24af611563aee3eb498fde666a1014a9f645037995d72dca0ed5569da7ecd084208b7c228e8a2b2ad3625b0839cc1ab8c9798b2e9706ba6d7aa623f3c0ce0985bccb2ee5c05a313000000000000000000000000000000000e1780b32a7b17464cf514efc4bdb02283af396ffcf6d1ae023e07fae02becdcc3c467f89f8edc9173a71aad27b200da000000000000000000000000000000000c3e7fd95dd823338bdf3d82fd46c265a3f794d4065d83873b1aca66da5f80c5962c9dcf537fc315d024d8cab7bed89d000000000000000000000000000000000e4eb722080e24f54fac7eed4b94e7b1eedb081c3edd7aaf5433d00829929d8bdef940aedbdd7dfb0376b3ad5544d9cf00000000000000000000000000000000158c1ff057f7ffe6492097e339cc4ce56bbefd39658ad55e08d5407619d1cbea7c83b977a1583ee48897a5e9c0d9ce3e150e53fb45ba8ce5ca917010f26451220be51141fe21cfc1cc06a5557e8e7afc00000000000000000000000000000000138e8bc8cfaecba9fd1322a3c1682c9fc1286d78e5b6718da00acc69f811fe9f94c9f0dc9d80e9002c0022c6dfcf156a00000000000000000000000000000000021da679a068b2f5f473ceed588f07adc7f485003f7d2286a18c07b09b835881f4ab94c7d4ec742c33a7cf01801116fe0000000000000000000000000000000018a62c2f4a02b73f5a91f503b53332304afc9cd8769f236259789277599a203b8b304b38993835a87d7cc970ad514d2400000000000000000000000000000000179396865f859386df7c1b8fa84c4ee71c14daf695fc0841c293618e6f8c87fb56b924f3f91a273b969e8635d7f90985d69ec73df67feb970f1c7a3880ee84d948eab4d8672a6c1481d61efc6cd710020000000000000000000000000000000004a8cb437297722c0c1a9471ff083ce60ec40c908af4ebb570c87133df705e725e3209152bcff26a0d6e4602030610d3000000000000000000000000000000001832e55a9e703d727156e4677ef4f82b86c6764123c3ed1dd94ae3b46d7eed459114993968eaf8e21cf24c59d042f41d000000000000000000000000000000000f606d5ee57b188636334ad60057cec4008ace88f14ea06324edaecb26da627670b44b6ac57b9fa2717d03096010785300000000000000000000000000000000145bf70f90a9d98f56ed38b3506556a48a1340ca6161806d055d7a1382eed54e294564de7fdbf525b0012de3d25ab5c838f8acba4782dfbc02a14d4b1d7b2b0a582f9bd75642169707a475b1a7d2d7e0,0000000000000000000000000000000018ca453b9d832f029ac8c7c70df846be97b530e6e42de3ba6943a7d0dc00296942f88eba6a9cc3352900ff124efaf7d90000000000000000000000000000000002e4514102aa3f772f2659ae9f1e2a91c7fb749ea590a3cea2c1a2e0f7236f71e182374cf7ebd2fa086dd921c29013910000000000000000000000000000000007c025696cdbf403494c5fc7f9a10ad0c549f84d1e06c5c4bb22f7a039486909c540776224bcdaaeb3880ae9d745dbe5000000000000000000000000000000000b5b5b70fae8b3953ee6661a0f4a1be25596839482d78710e584d3bcd93dff2b0bf4c8b20974744667e25fd8353cec0a,240480, +000000000000000000000000000000001265e90c564693db716f17d1a8815a8449e43b5a2d5446ca65160d864718cdfd413d5aa024e7581421c7222c29eb452b00000000000000000000000000000000133a6558baa53a2b8d239198e1dcd81af1ee46d55137177be467a99edf282edcd47b7861a3c822f9bd0df2e86aeb5dc2000000000000000000000000000000000d8287564bcedb1e57c3d74b0d484a9b475ce3f5b0322bda0e980de8891e2e8663abda99744b58032b8d7d3adddbac9500000000000000000000000000000000013cc35410d7fe07eac96abd2b35ff656e17b6b1eba2bd1d75ce5c87c5e76755ef9c2cce70f05cdec15d1bc44bf902d4cacfb05e5d10c41b06a487e9f8afa38759eeb55f0a5bc8640164bbb081c1fd2a00000000000000000000000000000000193f0cd6b4051cfd89f358cf6643528f0f042ae30ba3627d297b4fa2c2936426a9c1b65145b8192f65dfaad1f2fbc358000000000000000000000000000000000a92ca8943e64a391aa39126f093f2b530f556c1e3ea1b55bef1c264909dc93d260eec6420fb7a4e4a45f932d57951500000000000000000000000000000000005c7dc5832f744089d5fe034bc93e0bcca042ddd1b221cdd5958be86214831906ddbf82508dd91dccee467fd1625dd740000000000000000000000000000000011b11b3d24f44bcafbcb9baf62cef3f18b56ded696b73577375dae8108dcfb663d437e4cd9e44b7e6bf49741e058f8cb9a0b88d946231cc484550a87a548719f0a543c0698411f230a966cf602dc4de300000000000000000000000000000000073872ce0d74ea368df132897617aa8f941b67cf3fb395ca6c2f5bb2c551f17d68b0c6ef11e742206d6559796f06426c00000000000000000000000000000000156cc28eece7bed943c8410a44af112edd8576807e25701093eac0c9726f93da68a19c1d7b294f3ae6c84e32e7c2d5ba00000000000000000000000000000000050fe5987d5fa678be3d34c50fa6c5296f883e65ac3201c333b97ec0de00dee6187d2790c357a3f8822a174a534539a900000000000000000000000000000000177fee6e2d3909c0536acdbbdfc716f6ca19b6bfee7920a78ac9725c85114c69cd13152467e72270e35006b3c6caee8c74e3b5ff944bbbbf808f1f469a3380ee7dc37ebecdd8fcdbbd2f2561e0dcd68e000000000000000000000000000000000dd147bec9e0d1727c9d7597dea4a5b6b15c0a603dd1b586835580468148a502289fcc38194b2fccdcd8fdf0d8ec1904000000000000000000000000000000000186501fa4f3a20e80bf297e8ef1885b7d157617701839a3b524d61f35b2eb843ff0af13e253bbdef653a83e07a5871e000000000000000000000000000000000023eda2ed9d34aa253c8bf2f3b66b3c0c2551cc0e74f43dde2e429d9dea113a62572d245b44708bed79d662d9cba487000000000000000000000000000000001041cdaeb244803556e9b20db95f2a66830cbe47a68aea262865da50ab15ba658116657625318fe46fef393eeb6f3e2ec23064970a4ae4ae648a79edb193d98208418d3489e9b5b8517ebe99cc32b4d7000000000000000000000000000000000c27b1feeeb38068ee52b0fa440af2e3bcfd16601c8af983d259f2d15316b513ac3e89069bc141f02b934f2e474253ba00000000000000000000000000000000183f966cdb28f344ccae4cfda63ba6a6f29d00ab942ae7db7572cc09305e4f80c11305527b8ba38c40aae5f23165cf9400000000000000000000000000000000049cf59bbd6c26ab3e25b3cb94878271c73c0b4436573d612311feceed0f1668f4d79aad92360c1c97d60b540239ae630000000000000000000000000000000015f35eb8e4c40cb1297f7128d99b109ca75944c1943abe9158813432145a4a2a5663b55dbabfa48bfd9dd01907e1e8d3972fb60ccab83b6ce042c09ead82fea3d2cb891e21ddc5af7b5d8e334d5a3264000000000000000000000000000000000e5d9a671862733804f517dc9cae2190ef0005f26394e3161fbe771b9a486368871f4b1f10f405e45048362f437238260000000000000000000000000000000008100c6f96ae7af5fc86d9d91fbbefcc1bf5873dacaba9c3adf1b2833dd529d87f303a55e5d4098153377effd0f8114500000000000000000000000000000000010e4863a9b037d4ae6dff827a34be04c7f1627670b40e5cafb1fbca2fbf56af9ea6b24548db58e3119db64553d18cf200000000000000000000000000000000036a298ad5e8b32041a18e3f6c5847eaef20a5b63ddece41bd7dc4c4a54deb9c6d7002e6621aa01d78d64ec9991f68fbdb68c389b94c82f006fdc637696d8085b24897177d2992f504d4bcf5ff04d173000000000000000000000000000000000f62c0bad83c41887bf1ebd2644cef0577d793c2f3d67cbe43974f460a4afaf2e412fbf9ec97404e5e882ca0b23bd1a400000000000000000000000000000000191562ec9ace63ad2aae1f7fa977b9e0606e1da9775a978b2caafada4f6b3d9104562f2055fe037cd06df6093123a08e00000000000000000000000000000000156702c3feef1baf5ba202a25b9dfd5c1fc620e837501b0c5bcb85ec8b6e3e92bad1fc842bd1a0dac363e4bdf0fac87c0000000000000000000000000000000013a4b7e869ed9bdbf9671a5d8ca9145a2e97b6885d2a93b33f378e649e0e576be65bfe849119381057337315363bab2f4510c100005f2306f4b474d3843b4a79d04f0171afc5c66df70f631b0481dd330000000000000000000000000000000000a4b273438168494f0db235f535bf31893bb70f4119dc4741aa3c5e63e93b9a8bc001faaca10e37f36e130ef53853900000000000000000000000000000000010936551b148e16249dd934fcc83dee55279495c2a70d46dfc45945a69549657c3dd7cce00d8136e28d64b0c800344cd00000000000000000000000000000000115c053ac0b68573c3abd5f047b8fcd897e3d514945c5fe6efebf1921563d0079eadf32f7428ecb703d9163bc7811ebf00000000000000000000000000000000162e86af01daf552589b62be849e6176d74fa5da9b214a5cf2285802dbc44f346eaee5cc3d93a085740f74cf7e1b17e1dc682a2be4d67852d119795988c52230d8273648cc176ddc012a4b4da5a8636b000000000000000000000000000000000d77cb5045f7d4578621c76bf5b3db076661c72174508279280de3e92f0aa57057ab50180f0f908561a87d412636d964000000000000000000000000000000001853f9cdccf5e6e4b87231b153ea5257f52ff10dcb24cbaaaa95426d0231dbb355f9c47475d125ec1079b9bf26b23b560000000000000000000000000000000000fab825e06c2329a19de853a05c4bc65f16fa047eadba8e79607bb31b84ed6541b00f7f14b15687d67cb4cae0ef9c600000000000000000000000000000000005deaebb5f31a62fc0bc1af13da63d0af3c716df8c9bf00f1e831af5882b88974c49e8d35db2545747c85ac35156bb668af6b200fc8e6a57a954226d9a0254c8bcbbc55fd6c3db5cf8532323d4c50b4b0000000000000000000000000000000016faa5e91048badedcb33e83684d2670051c82b7a1d0ead0e28f4dddccb141a8ed1fa7606e4b6a3a893c55344263eb4400000000000000000000000000000000019b2c8758abe5d339afade4ad0c1d44d651f185f8a0030b81b136d5972510b353d43cef616ce04827d56255419831a400000000000000000000000000000000124b1e87f343a890fd690e384cd156da57f4f0fc5b1ca99c73bb0571332ec4c12d3ebe955e3ae792efadc1d5c0c67a410000000000000000000000000000000014cef10e4a9a41bf117aacd2fca5f1364a46b0c4aa0723a369fc6ede09dc76dcd8cb67fdf87ac49bd4bd9981a2e589647e2036f73e8cd5e42ad86914e192dd969465aed0c3b752986b84a0c2444c90b80000000000000000000000000000000002862fd5f38154dd452f65de0d3c1d54403cdd2a397ef416fb92e570913c543d3368a95fa114fcf48c3bb4b68895ba33000000000000000000000000000000000e7185443e5dbb656fcb9ed100949f8f7052ee2cdcba4f5c687a65a1b45bf66ede5c60b0c04845b9a870e004f8af8450000000000000000000000000000000001817be6d13cf2a67225b2eaf073e9f1614f3bd32cf5572766ace4a91f6b6be56f498b989f1c3dd3dbc9a819c029431dc0000000000000000000000000000000001cf41fe428b088a17b8ea93a653677705d5c024db530b8300752c6b100f2abe4c46dfc24afdaa2b3d53cd8ce0df1b6a70cd5c1545e76027c389645da1089fa88f675b5b6ef9217b584d7202b797f8520000000000000000000000000000000002eed272430ca3176988272e6157a18df7151bbfed5b90979752a02619ef467af8083208dcc9c7d926490b1283baa21f000000000000000000000000000000000a644f6137bde232c3a909b742d30bba096ef88b711ef100144276d0944487f9ebe8331483978a47c07d3a42c441310900000000000000000000000000000000042c67cdc10efa8301ae95d6d4f21cf152f04b235bad2dc5a61724cba64083f690b3158676ee6ef10f52dcc7061f7c7d0000000000000000000000000000000007018d0aed5abb744cb998f84140331fb2cef8d9e09c76176def48a85370c6247c2ac6fc726eea891b2041ad5edca7f0244041bcfc21ede8023ad80b6d4af4b2777c0204ca5f61854e6da34ff5e1145f00000000000000000000000000000000141c0edc966b7c845d4e68272c6a71f8ffb7fd8d56b7cabcd556a98422f830d7a81d123d701ce1479e84047328ac1f3100000000000000000000000000000000105c1164d721b6dfb05b6b69955b2f25db0e9fdb58600a3229dd516076087aaec05b837ade68bd2a19917eee7b9a22bb000000000000000000000000000000000da3dd97e693948fd6955ae52d493b3a2d2896dd4ad00a0b549d4d392e81593472e4f9435a8b7977f3d58e324c5b9af800000000000000000000000000000000068c531ddb26a2299cc584b5bbfb0235fd774a2447134c06e7de8b94993804958bbf1ee80728cc6db647e8a244462372ad7572da641373708bef008057aa5af1cc76ccb882bacc50a77b37d7047b1bf3000000000000000000000000000000001881432f4742dbe41bf774930413c98d49a781a48d6c64ee1a18f3076bc6c0e1214f92d5bc84ac65ee1c586c437d697300000000000000000000000000000000067e0a95f3eb826f3efeedc1882ecfa30b8b96c92f626aa324f4044ee74531fbfd50a221b1b0e0182d759d149d51427d00000000000000000000000000000000173f5be7098b756ea84f030e374973feb4f8811118ea6673db1db75ec6909303e571ec5a1d55a6bddf32fc80480cf103000000000000000000000000000000000f28540976a6ddb277df5951fe58e7310861af837cf31fe31c24f7b979f72ef1549372e7ea1ced15b655d24293dade7854b51c78093cafcb57c4c1f172d08257c379a9caeb5b5478cacb4887119a08c600000000000000000000000000000000188f296e218719bb9cabefd4f33d5728a1d280bc59c3d826a0f3b5338f92e6544a4cf36f1a493458e0adb246c01a415a0000000000000000000000000000000007dc8e4222c7ba78190a8e72ec7e6980e2581f51a8d6c41669b6fc9e16d50a2bf4d422af73398e76b2f39705eaf8a6da000000000000000000000000000000000b25a44523323301cc01b50d58726768c2cf61e691203dd34a0ce8d58fe4f72c1c33abfb2a56e0425fa9b7e2fe48e870000000000000000000000000000000000c6f11ea269d9061d2f462ac37401def1b2b28c47b84344d04d1f026add3237d99a586e3fcbae347a4ecb5646c8c569fae3bbf55186a89740af4da6c073d8c0e331542a2c972a49dd3bf65261dda6e49000000000000000000000000000000000c41a02e937f8cacc0be5d9f2d9fff0d6d4302fd252f32145974206463854b3a7d09b3b147cdf2d7536e970dc13613ab0000000000000000000000000000000005f9367f4e31f7e4d6e21664ac13d55f501f5368c1ca77fc439db60e1846861e6c4c3c44909469f88e02cd973499992300000000000000000000000000000000131fe6df7fff97f132bfcba1d2599a862c1feb514a05b4b7b0bccf49e00aaad043edae9346bf726e2eee498dbadf2067000000000000000000000000000000000e59044f0950a741da3881282697f4a1a522b026e493f6009227da4c0a963de622d5e421c30e0023f4118c9a036274f859b43915b15c509ab8930979312dea2ec9cfa9f679b004ee526aa5dbb25759a4,00000000000000000000000000000000144433ad3afca0a9581e7e87220a4944e26ef2eef6b887ce77d2a2559ced058e7349b36efa66c492cc75b014b3448ef9000000000000000000000000000000000267b90e45d7001edae01fb198d16dd37c43cadcd2ca87bd7cd1f0f65a95148144f5ddfe75d344eb4573c1376aa2728600000000000000000000000000000000050ade28b09b0394b08d128c089808021e4c65dac49d9fb45efb93792a4faf210230b650fc3ce810fb8d11947e9af5060000000000000000000000000000000003b1d7dd7c6d944d16724fd1bbfe0f53b6b50a70e133dc5998c82b51f817f489bfe1e0c361be36fa41f5af7c1577f2ea,240480, +000000000000000000000000000000000a081f037738b0d812da43a907e7c624e331108ffb72104d82725b9c14dec8449f5ba0e8c1a3f1379cad2c3e7aa99f70000000000000000000000000000000000937fb5d8b3c258b7b28555fb59620f114816f0fad46818a5f100bf7dc3332a03d285eda18e31e4047cb2606bc53b20c000000000000000000000000000000001574e355b7570043bf36ecd52f9c4d9ff556146d81a1e9d088444805db9b3b678fb55774865ad34d21022afea2c154590000000000000000000000000000000009f70a5cc658cdab280ed65e13aaa319049b9534a222217a08168047ee2491f25a9d2620c7343a6426bc54a0700bdb4fa53d5989b63ee5f157cc44c684ccc7cb4c74338b12fbfb534ea33db341fa6b460000000000000000000000000000000015a76e89c8938b8a27e4857aaae8c942371b6979605adf774827e9438ef739428fc53b65d32e4e152cbc6a4de42b8bf30000000000000000000000000000000019494030ae0507eeff20b69b4913596c1b9ea6927157945c8295e273707013ef1f2cd08c058f6b469a6c99ad73acc28700000000000000000000000000000000122ea7ac21a27ca7c4b00207538bf561f688429999332c45de7545046acbd6d9e96d31f5f6a00595eeb212918a28d2920000000000000000000000000000000018b023e7da67cb8d9159746bf700f9e151fa60ba8f5a28b3739de005822929cd28c49b9dbb4ca8a10729dd24771730ff4d840680013af06920dd06bacc0ce95cf0cf79e8ccc0b10027f2d28c1d0049980000000000000000000000000000000007811c759634904765029e955c3deca648fba6a9da6433b50a6d2086a59e65811d52d41ed8ff2e9bd63a4c0828bc702c00000000000000000000000000000000182c86cddf5e20697462c829f41c7b49e7976880311b01ed4d12d7174340799f19db0f295263a2617182bfd1b49e0d1b0000000000000000000000000000000011824bc20bd1b27876b4f48aa8fe3063f826b6b2c3dd777fb8999a25d9139f218f6f288955274884ce96ef2dc6d34d120000000000000000000000000000000000dd310d5e141e4eb13380db828caf74f62878959b6b2df998bebf9306965f723fcd4dae7c25bf2f79ece3e8e9b92de61b67d661ebc9008669bb4e5cffef81a32baabd71667a72f1d202ced823f09c740000000000000000000000000000000005667d8c4f8dc3f4aa0021d1026a1d0dd0bc3576c49339262e84d20198fffe33a389d28ab1d782e9d19af761a2f097b40000000000000000000000000000000002803d5ad6393d7072e149f1f2ebf70cd8961ba3bbefd648916a8ac5a5eb893b71bb6015e201dc241537ad5890024239000000000000000000000000000000000122e1d0e0859b04143f23c4d2d2ffec09ca2ce5eaa9429dd0c047032d180bcdb10c106071d9f9701c006e5eb8ef88130000000000000000000000000000000008347a7bdb3b4f381b58ed3a128134c09563b345380ec948943e738347de5b5737540b57c28d00b9d060c60942446617ee495199ebdebda02179432d42d5d9c76eead4d4993cd09a93d46cac997716a5000000000000000000000000000000000b26aaa46a279c482fb395ddb84d5b4c9c70102c336cd565ca9eecf62cb96f59f634adf46af748826590fe65beea752b0000000000000000000000000000000012cc63256a9f73f450e86ee38c54ea78baa5bf87d3bc01320f7fbd85bf11e19f75d787b9b12b8f2c7634368a9023de880000000000000000000000000000000006392fe611835f6fd50229725d71d435f704f78cabd1b5569e1c5a89d4b11f911f0e34ec034369f972a80eb407938b97000000000000000000000000000000000f4ff2d6a991fde9093000d7bd9cecb289383d259346d83bc9bf5389d4c39c82a0e1d7deb84b90ef370e0a19fce28d2b3e038e473d6f965751ebc5f69eea6f37be88cf001de0c4e4b700823d8326f17500000000000000000000000000000000193752c40fa0f466f7c8bd26658f133d0283d2ac3b02eadd27b3e9681329307f91a1512fbc53e537f9e1025a3d68a7ca000000000000000000000000000000001106d751c9e1637f00e51e0be856405e6b69421d81bb30b9b8718cbc9cfdc36c80d2848bab0d5246da84f10b478fe48e000000000000000000000000000000000827a83f28678c4e39c4963e95c2404a70691885788e5457e149c0c45d4e8c74eef55223ed15cd75fad9f7209a6ecaee00000000000000000000000000000000072667f02b781c8e0a75d0ed8f3d55e668ddcc8c61937c80653e240c3a744c961055c782ca41b15211c0f1e1ba800bf5ab2af2590309c9b9177e4f6f0fa06339fa720cf1c9fc7c001785d7145a3c9030000000000000000000000000000000001419629aaf0baf779feca264d0d9846b987506125b0049ebc8b307c4e3ffe00da1284a94a012bfd60456a4a937b2e0e000000000000000000000000000000000119a801bd0a5a1c1b25cebbbcccc7d2bed9baa4995483f4ae94121a8c6cd0c3f90a26234f51590d66cc38b8bef9020d3000000000000000000000000000000001125bd15fd9814ddd15be0997a6961b6f1c05ce7944514371f10c8e5bde271c4b936d6537d91ebed740fbefe6b281a0d000000000000000000000000000000000982a2904a524b1fafc50d540506b8fb07c3b4978310bf3cf53ce570b1b05e746981bcfc06d59a78d170573b09347f3fc9551f12084ad7d4ce346f841fef785d644821b5c2d3c8db3145fc26e65666bc000000000000000000000000000000000b1da333e508ec6b0329747fef35cb926d922091d4a45eab7cb5358f20496c66e17e46874ed9600cf4252432c29aeb07000000000000000000000000000000000c757daad8f3ed7dfd64782548eedfe904f7ef3bcc11eefc4781fb37159d07825a4c9f3fdf9cb3d8f3944277bf25f88c0000000000000000000000000000000011160e21503d6fd61a2ca0212a7d48317186f259a987a17cc3eb04a6d9251736e4a66b739a8f3095684b7d91ce6f79730000000000000000000000000000000007440ec0f9197352a3148f9bb3d3dba9b1d5add903e48b50ef3f6879859b22ea0e31b46ea4ce566930d8853520abdd14ef5823541696ecb88d0c71e00a15282c40d4826220a202be09c47fd6891b93ba00000000000000000000000000000000070ffa4d522df8b9f62aaf36132bb1b857e177280a7b6d3af6bfc79b73ad3848241df18ca7f8993ae3d67005ead9264d000000000000000000000000000000000e32b65bf035bcb11f86c60a334622d2367797d0226761b58a7db8c7324fc4bb498a558eec509c2326fbd0e7bb8d3d19000000000000000000000000000000000dd291a760393c6e962818986727e5ca5d46544dc47eb49dd828c6f74caf0599e88c4293881714c425b0697944faa861000000000000000000000000000000000f7ead0be081467f3371ab92c249cea73dedfefcb6aa16a162c06e30605e104844c3dd194b4a89ad5230f596bef64f19e32d695dd02323d40ac1eb9452cc53376ef941237563b1ee380c9824a565008d000000000000000000000000000000000ca545b53836899e507880329799e4c1a1acc17275f5d71d87b9e41ccd7a090da854f9936254448c988ec772a813bb6e0000000000000000000000000000000016c9b03fd01394560497d6a03add63c034f96744d96a13a4ec92d28719018d1eba1465e4332e53f37f2aec4d93d4ab7f0000000000000000000000000000000007019f5201dce326d5a6a1ebecf3fe50e22335593bc9d3e62256351c591f0a1a577d916055d79c0b4abe191b6b8011fe0000000000000000000000000000000017acbe72fe30c386e463f3e9b35a474b902f6712b30af88ef340e6fc6ec0fe2e606c7e26432c2a4de33a12e35ce41868f5e23ff8acf88d18e53bb31476f10fef288e20e818431f9f0d2ffe1265e8ea8200000000000000000000000000000000057f856ae648279f2b6dd17584e1388e4dfdc9e870db48ee6ef5f58389ccd4ba17e074b79ae12b728c59e2f91bac5709000000000000000000000000000000000e0f39f4beddbf05fd700458448067b52c11e963b22603f10d697d6b6286b1449b1663e032bf7bea48f2051d8ded923f000000000000000000000000000000000022cfadc1dc399ef5f12afe1349d9274cd595a9ab6ef7ffdd68f8bd2d170a4a783ce0a7303878d809a16bb8073d79860000000000000000000000000000000007e301565124eb66d59a70897f2ac356e7b0c1bfd4e3b57e508ba0cb5c9c881f9de86b91fd5133aa2977c8e81138d66971927817449ba5f053d0ed1e567b53b1179c6b62a554c8be6764d7ce203f74e4000000000000000000000000000000000edf3fdbfb03bc07871079aa4aade538a97e1619b54d0692a7f5f73d7fbc8abbf680ea3a99325e03c0501ef174deedd1000000000000000000000000000000000b8c1b5d3c926d7da6e0583f67d981af5286a04429e857b0aa4b1120604f9c8c93f04e763da169137416dc9ec4839a910000000000000000000000000000000006ca2aa4c7109f043da9cd90bc801404685db802eb8bc925d9d098e7af3d9f95ca490790b2b1c77995c050aaebb935db0000000000000000000000000000000001f40a2090b63f94f93e8b61b5ba1ac62a37548342ad81a9bd99ce8339435a7d7477c3b9cee9b531a1ecdc85a72041555ce5d6f0e44a20d0a0e2f1cc523455b001dbeef772d84b2599daec66b285027f00000000000000000000000000000000021464dded318cfa86db1e4329f302bbeca7095d910c4260799cd2a60ebb20e60152868e67a48b86f44000f267d11c33000000000000000000000000000000000ae45fa46fc8e043c3df99bc0d87ffc5867208fde0eaeda782230341a8624b101346f35fa24e1dd67ab200f5d6fbc8a7000000000000000000000000000000000795b9afedbb128a46c1eb25c52a71375903adf7d3520535372d9af5023dadb1dfefdcc0cb546e9d218890123252946d000000000000000000000000000000001852511855bb368cec51c54d95b430259f05dba6bae53b5c42d69f31371c30cb611037fbd81393a896cbdb6240114549d37f7bca1a59f65982294755ddf8af7f1c953b6e482fee854e0d89e9b269e0e900000000000000000000000000000000113b883c6bc41b0673145bfeccda414af45efe5710f436977712e7227f38911cbae851dbe03928f38e310033458eed72000000000000000000000000000000000853e32773ef1f95a3936aacbca50cdd5eed3d08dc467d7ee834487e445fbdaeddb0df394bd0c91fdb06d2883c4dadd60000000000000000000000000000000013a7f9cdebb2ec37fad172d31a717f4b538a8ee74432c5a5e6410460eaaa3b5f24d223b76bde4277097e93087b7136330000000000000000000000000000000003d6f141b56e1e2e400fe821524017cd972678a7d64f660c313e6a8910b72b5ac04328d45945077aa2946931c8dbd11706d0535e3728b9e358d9ea82df4f1137db7a02f79c0cd0dd672e24092bf7f6b40000000000000000000000000000000016adbeb3530f6b451d870b2d8292a01143986cd9890c79a64764383575771b8608ea61beb2de87bc034d3b8a085958be000000000000000000000000000000001125d7cf83239e4341c286fe0c8739e7013b234814b26a079ffbffa329ee4705da81fd12f34f49d821690a11b8f83c5e0000000000000000000000000000000005873dc5c0baf0f3297d884ac7b652c749abd0405b96ba60fe396efa179a79fa55be76924b0690c9a528c605ad4f9e120000000000000000000000000000000000fceec23f479c72e0fea0d10d3394d7121bf1673250cf1ebe72eca60af82f232fbee342e2c8705434394d4e519fbb40f56d6810620e8da932c202628c2fa9f0a9f3fda3aa07c262924aa51685d2c9af0000000000000000000000000000000005ec966cfa28e105f3496f977a2f046fb206a190fce1a6062df0fa1946f274cde9f6fa8a71089af8cc2fbc2b60746cf40000000000000000000000000000000013c77ab66fa92a2411391d366a331a40accd120db1c6a656bdd92858826fcbded296293c13ee189ea3f34635de56732c00000000000000000000000000000000162795b6feaf6a63e6ea2d34f2bff2a4985ad26463b8fac69f8525eb0a005bd377fe7ff4aae820d361592d2d88f98f5c00000000000000000000000000000000044c9d5d3bc0d99693f5a0605ed467cca8b5dc7c7093294d14015b59bfd8ac6bd479b73ed52fd30d8bd891ed971912c571e7f672ad398f5c02c989b475d12ce86e6e242d36784308e56178f2a6a1517c,000000000000000000000000000000000c3bed2f51a60f9afa6655853ec2f0e9d46bdc1277bfedffc468d9f36cfc7ad9e70365fecc84a5a40d863dcaadabf22a0000000000000000000000000000000008c5894a4f93b02fa1deda8b556798fb7d71f53046ccc305588bfc00b68bdfc34b3f0bf154ce7cb50c9536ad45e65f300000000000000000000000000000000003699501ebb9698e98dc998fcdac54dff895457d2e4e0a0e2d65d275b5798dc016e921bf1f65fec0f284a563aee66ca70000000000000000000000000000000010389c73de7f6d860c972c1f09dd24137c898e92935c45c10565ef3da3406cf521647ef80688f6e799eef4879ca9a6e8,240480, +00000000000000000000000000000000114b9c33bd09899c684e81a5a4e620eefa4e620c01c391a4df5caa75be462ec7ab027a9ae2c31d6643c48e3d75b6ced6000000000000000000000000000000001925084d2a1f537329e23c77b8a820c385ec5e12e4a145888882ec611e99b05b789d79bcab48326db4424309c24d1688000000000000000000000000000000000a1dc78c25cd16211a38bd0c70d24c84da1b83adb219e1b9c06fe6a6669d6e0281a155b4cec32d32751fff653aeef1990000000000000000000000000000000001daa74f19cce1086a87232464ba903938465da5e3e1f9ddc05a4b4dc13f1026e1b07af7254d515d2ad6960ea62dca1f77f9a79850b2fd5a281b22f52de085f12bd34e56808496e1c1388804f534d2da0000000000000000000000000000000018810adf0cc793c21726e9a27b7c558aa16b81af73f22629c478d293208a107fbfed4511d9cbcc25fbc2826bf004e7dc000000000000000000000000000000000356b25cbc7cf65107438125c930dff24b7786cbd7eb744d7f27967619d5cc02799451ac8814782eaf9aa331e6f8dbe7000000000000000000000000000000001164ab32ddbeb11c2c8baf7f311ffb01bcc367395bc7ecbe5642d344a8e879c74a554b3f9e4b6ed7db4ea0f872cf96740000000000000000000000000000000017704b1dfb111807d1f5d90c370a5b2968008a5ee9fd72262b6543c93fa168285c04931198f5195f1abca648722ebdc5630c1fdad9338fa5236f817bada168a737dd3685b327fb59d8a37329920af4cb0000000000000000000000000000000000a336a04a8fd8e18dd9a582da897016983d9beb0fdbcea6c88b7c0640620be52bff32afbe700599e3c08669c457b760000000000000000000000000000000001765fe4faeeb13fc2c007682c031ea7ff2899090e16a9a11959c5c3ae7881a1dd2c6d2b7f5f708a92349a2b0de4b92d5000000000000000000000000000000000e7c57db660133ebeadc2cb2054ab4ed16355466932685d4d11038e1e1f47b0349b68bc4e918dd48ef8e1c5d7cc53f7800000000000000000000000000000000169b629ddd7add588b91d9866a750570dec58662e43409031a5e25f1b2913c5c5a7a7cf666953c99835431f091ab1b140969599bed4899c3c47e1d4081027203c73233536cc6e45aaa78a4f1150a51620000000000000000000000000000000017d03e9855f3bbee719a15208ae24324ebf1879972ac134b027c9e03444a5736863bc55604158e81b38c7fd78ba4bee7000000000000000000000000000000000468f7c5478cc0faab7098dbcc455bf18525b56272c2d02cc1febc1825579a613edc6b455764ffc71c903a0704224a4c00000000000000000000000000000000067104ba5366e7e11bd4d516565d9cdd93d4390f2af3c1ef2ea3b1e84ee8e5c0e0fd8ac11ec9d2553e4cc13b277d473e0000000000000000000000000000000012e10495ba15b29c669cb9683b2fc7a45fe7ddba743b4a39677fbf85aa738480eb9da967eee69b02ef14137e102e240eddd438de35651328de7183dd38820ea2983488ba31d401094e59cacfcd1d031900000000000000000000000000000000078f8c17427847ddaa1665d206866231a5f36d3a7b4e8fa13910161566163006b5aa5d9696f423d0c44195de65326f21000000000000000000000000000000001613c465b65940f43c61b5e3c93313ae49d92728518d9cdfc57b49d6924479b70e281e724e04fa5f165b5999f1c1ed3100000000000000000000000000000000031741b6830c16d730619457d42767a51037fb4118e00bfd6cfcd8baea35ae76a5159bf1f4639fc2951f0b57446110e70000000000000000000000000000000011a618ffbafe4bad0a435d04084233495e5f7fbeaeb66d0d49a8177f562329b52a5ed4fdc680b791f273a7b0d3d4b349191f2b2cc76d848e456d07c84c0826a8861981dc84bdc671bc9b5882d387a41a00000000000000000000000000000000043c09eea638e524661c60ae3704fd1c18c46443ae134a0ab7b9a98cd398377febd9026c28b3e1e50de98766aaf0083600000000000000000000000000000000105918aa1476cf52f91b9ddb7c23ac18af3bd5269dbafc369713687010720affed6b12af9414cecd521cf0c7f5416c350000000000000000000000000000000019ab4a3eca904a15782f560bbbc8819dc09275f1f6d7c3b8e98aa0a96ec33dcb528284636b0f42ad0d503489d17161ff000000000000000000000000000000000a2abada18e79c548d5829991a65491ebcfe0e1a2c89a1e05f06a0ecd197797c5ffea0ae90b61f54c6b3fc844e0eb3ddaa76094782d0c06f2080d699b81aa04a60891046e0053d2fa757c7029df8f848000000000000000000000000000000000d457cb2c77acc8ba4b19ade0c724a2b6b0966ecfbbec8cbea745439b9bb7f3dde2febf9fcd6c5e6139fd7175e57b1720000000000000000000000000000000003154466283addb0d0b5d86a9633f8300960cbe8bf6a1405a3a040472542e9da63fd4f79a43d641a47c2b69a31298d3c0000000000000000000000000000000006599794823797f8ccea9daf0459b9d26e0d207f5fb95383c6b61eba38516b272e8ae6ddff2a9fa791e69c0eb25f3e470000000000000000000000000000000018be316bbe0416ad7deced1486d4e31490f5dc7e379c17542b7d3e9dc77bbae9c992e657c884db320cd51c2141a4abd2049a751a406657dacceb3721461417571a0104e11c1e00805becf71ee77eadf10000000000000000000000000000000007ba1ec5293d169b88ca4d2d92eacd51f0b8cffdb403632ea8ffdebc37f3997baf736771231335d12717cb45b51be31a0000000000000000000000000000000013505cc24222fb2ba9e25f5f3497653462f5b10bdd0dc88f9b16d5643a99ddd4a7749dfa6b566f41cd2da7c2b1ae93d2000000000000000000000000000000001465fdced698ca76d5faaa7e4faf1260cd5c4fd2939b16d3593e3588c92de3d003540ec989be9632fdba4ecae889ef180000000000000000000000000000000013a20cecd5e8f161ac70e40b8e9ca4c23e2b267690a3abea941c293b03acbbe4fc68a1e7b6d35b79ac46f65edde73a3e0502d56084d1be7179fb735e233978a5a3c2756d780cc0ea6a8aa92b1d1f7c4f000000000000000000000000000000001936436783f02f3a5307bfc0bd8c0a00ed8013508a440d040ed4f45b37a4e89986102964a328e93fabde6d9dc7ca424900000000000000000000000000000000000f16408b869303181b4b4877b554353b26a7b4750b711f3c41cc4b6682b2113cc772cf9bfcd0cf60e59ef29a5d0814000000000000000000000000000000000d5880e2ef94663ead736687ee725f7ce98fdc594230c1ac9e8345d39754bd616e261076aa5362776a6026129bff105c0000000000000000000000000000000006865ce3cdb5081e86535beb990d95ec3d75f67c7e881306607e4876c42714d627f8d548849aece4382d1c8f2b693bdc9787a6720b8db1b4f0e1d535833ed20b519a0e4d2e9fef75022aafef523713750000000000000000000000000000000016d941b6a0dc023fa2699c836b74e16c31b4cd51538f73fbb271d163519d4de1cb0f6ec2f8efde22c74ffb532c576b16000000000000000000000000000000000d10a7bfe9541a7b22d455f1b68cfe2422a83a070d93476aa0844670f02aecb36e9f41b9d66e8e9d0d67c0ba85c99f44000000000000000000000000000000000d7873f96d45fa8c9ba9cb4913a7b01c8e38876b6bb2a05506d23df0491bcffb42983ef663db85bc3cf755f476291a79000000000000000000000000000000000c22fdb83f9991c85b3577d1ed5a171f28460d79dbc6167b0c30b200235c512f999066eb1fa449115aab55128f8f2dde10b47b662e8cc8dd005bdc81dc6d98d0eb98f86b46c0c8f24481af9120e84a820000000000000000000000000000000010faf9cb9d0fcb487c9e86a2d2123105baa8691d82ebae8f5bb7d5ae7b7d8154837120eea86dfcd35ea5482a7ebf7f8a0000000000000000000000000000000014e40640eb6e8e38651a2eac05165f6cf5e0178b3711f34828766ff9db951e1348f0cdc652a78840dc24ada8b1c835c600000000000000000000000000000000129db7482ec62873591018a8399a8c5e4bf00e8bd9dd78dfa3d0b4cd1d93ce5ec7531e56d58b7a1cb3e58f062f6895ee000000000000000000000000000000000d8db3b54b6e71497faed107b31f5e44f328780cf01c62cb5ca00f99f10385ebb22a367cc89505640d1106a9ceec98c4072460e3c5349c8fec9944dc99762625262e84c70f10d0a92077a351335127470000000000000000000000000000000011ae9bc3ce04df2add17e57f260a72f88f19a1e44b0b074cccb7fd547035038d19e5f2228db46843343a69823decda370000000000000000000000000000000015ea64b6147ef76212bb5223d6d5ab9ca866799365683720866d8ce1117f60bd552a8e9981c095894258ca3c1bb5150500000000000000000000000000000000173bd5cb455b80b78951b15180fa7f8fb4725c1a12e5c53df1b9b31b45a29083e66c7116741d9aa93448c81b5e6014610000000000000000000000000000000007eba059855ab058c2066c643ef5268c864d09ec9962537d65a1686322c374eb5ab8eba4c4260ad0919dc18b4289a694f3177c4d865caebf1ef6565bc85e0b0bd51365a6f321e26b97cce887bc3f44d6000000000000000000000000000000001598471460ae082c2e2568602c99923193c913b9e803cbb7a4503ceff369e8c4bb3a19ad245c08192e12a2e9b3e75c4e0000000000000000000000000000000013b289bec9d97c529382388f7037749c10a64f915746d23d8f37e15db9dcb173b3a6d00bf45e67b8c70959472148321d00000000000000000000000000000000094a99f9b031a51b7d54f7b8865621b204c85d23fd66fe8ce007f0b852f8b5b895010745b2fc469abb670e38fbc41e50000000000000000000000000000000000e36daddab2134f65696ede36c50f90f9a1c56165e09243cd56fd3d9902d3c78cd85e7028f6dd466f6a8655da62ecefd393654ef7ad8687c8878c55a8240ae9df04805d3e2f194e960d5e498ae3ca17700000000000000000000000000000000050a818ce247367e8b57673d205d6bff8c650bcab7bf794dd32494669eff865fd4e05d7b4d35eb579eb475a3a0320ff80000000000000000000000000000000017ae5d612bdd46e1351dd1367c08c16ceb002a29832eba75e48d4c82e364f17c58525ee653a0940955b874da6a5bcfcf000000000000000000000000000000000eb2075367b42a0b3dfa30799ce1ab327eb583316d15b8cae21b716e6c7fd8cab96c67bc39e353f5e842e74995356c070000000000000000000000000000000018ca4b533da1baab37f05afc3ae0afe976e4f4530401d2f97176f5c73de3eaa75b8a34e8c6c0543ca0a08aeed28e478bdb9f942124a381b150f00a59e4579d0a2b7b728f62715633288fd03d01dd12dd000000000000000000000000000000000b3f4bfec920018663bb39c5520491da5c538f82138f03390c768e088bbb2880287196af937f1f70e215edd49d1872ea000000000000000000000000000000000037e7607a60cf235d8e4ecbe69d378dc02f0a8e40b7f23745e15a73fdcfc971cc8707d55a8c5b91d9a5f42c2f49c455000000000000000000000000000000000467df75c2703ccff1a01fa5bdebde210b61b5f3fa33e76e55be5dc953f4758c3a2c499cbd42b256ff5a2005949d9bbf00000000000000000000000000000000010d574c69050ce9e909dc23a76e9a2106870e8d8ce2a0e30d42cbfeea56ce3167535a9af1d453d4d8e6a450eff870638e6eb65778a328cf899f66581ac7a4a89e0e824c15573bc68c02cdaad89cdf24000000000000000000000000000000000907fb825f247c85d93fca36dcede9c22a409fa82fcf540593e8247c17875a1385fe009f0ff43853c404f6c96e2809ce0000000000000000000000000000000012bff10bd4162207870f6363342f2541804adc6a4e3f7b8be51d361be34def7a85fb39357c85a4e8df670fe39233bed00000000000000000000000000000000014f7e61ccd52bbf6d050c9d506751e03c8771b320872179a9f0161ac5736edc13bc133bda6239abba1ae09bd6c16f0c3000000000000000000000000000000000ca78624563584f8929d72668da70218a2da12b42c4b894108e6b103201372554fdd6b3bbbf2d94a9d0cf4053eb07d460940e3620c59504062e4e98b5d4c8cbccdb017c47a094d06253743c29465731c,000000000000000000000000000000000de8e87899b294575392d523ff6e153a7c2038302ac74574bfae7fb222558f2b4c9556be1bc2757b83ebc180ae710187000000000000000000000000000000001881c7688debe3ff795788c90397c3fe3d6d9f56da9221778d7b12f5a54d8c0a00e1a8d4bb9c0b1d578dff862516b5dc0000000000000000000000000000000014cdfdffbb956a20d8521ccdb214adab14975d22ffbac107b2c15f42e97bb823c6a3945a5b299d3226e2044e64f8d1ed000000000000000000000000000000000eb769b301cb7c0c976623badda4db8ccb18dc7322472b5fdb969393d5d82b3ce94bfa59dae06ece424bfcb88e24207a,240480, +00000000000000000000000000000000164227fbb787b2d47ceea93faf1cf7890f48107ffae3628192235aa57658d9a2861db13fec0e58c347571c2ab0cd11ea0000000000000000000000000000000015478417b6758826b1d6fe0c562d43451e289dd50de31ef365ec70faf961ebb65b510c4788b6c7da2dda9cf56d3c8a74000000000000000000000000000000000f9e50d802ca8cbf80caec6489fbb24a2761db1245d9f7e820e6747bdd0855902ff211c427c00157ed9b1bffdf39eea900000000000000000000000000000000128f69ef5dbea5f80dbb9558a25f133b9ad77492250e0654f8fa5b55266f2fd26826a5c373afcd74990ebf768d6d8fd20f2f697ef6783390724e04b81d0e18dde6533eea9f72c1e10bc72c7b954659660000000000000000000000000000000005f7cfb31492dacae51caf4036d99d917fa13b0d2353bbce4e6547ea744b3a49b162deac2f107149ebc2f79e74828f720000000000000000000000000000000015ed4627efa9b318cbb52f518b734327f5d1cfbb097adc6184c5034620504181a298ac7e52759586dae2e107f121a9b600000000000000000000000000000000023e832638849599d9d7854d3ae18648e67e8938ebf606a7c86c3a7ea21cab8d4dd5d9cda5c482e05d351ea3ccd854710000000000000000000000000000000001849665396bc36d0301f4c9adbce81fd2f2d0c7f89925487d91a25c6bd0730ce31678694a319666cf42162608ef15a834680b934e67bd7518f0d6a3a809dc7faf845eb71d0247291d61053d5cbe0ba20000000000000000000000000000000012c9b607e29e35f260f3c4617b4217d5dbc6953eaeffaaa903710195e080d593972e7794897eb176aae3539401a483b10000000000000000000000000000000019cdae8d1d9035d1fc4b4db09e7da3c20d3b8777523155d407cc6565a71a6c951eca609d328ddbb165c2b5a3e6b081da0000000000000000000000000000000009c4629b67c1c50e5fcf316136bc645e9e62ffadac8495c084f97e32b0a3990b3b1019261f78de576ff7ffc89e36e2af00000000000000000000000000000000070a49e8892c5b523f5914e2341dde63127b694eef556de6dcff603da109a53b342363d9a854dda3d2833e25afd5b57eefc024dbceb522c02b88810ada9a814bfd085fb63d570663a64bc0658e5ad0220000000000000000000000000000000018d3c9259f70312c803dd6bac6488541f92482f7eb61ead71fa42bd5e2cca9338218d62835051bd308799beeed3b422b0000000000000000000000000000000005e0da6859601b6ada82b1826a455a846f8b4e54d9f22c3c639835a8a89e17ea2d76e2f49fb151f519de3e9adb78f0590000000000000000000000000000000010113d2fdc1e8ce0027b651cee6f9f6832b531d843db3ef7bf209aa00018715c1c42c68a82c53247a267929ea3c9363f000000000000000000000000000000000e7d1152af6448aca78aa7983013395f0dfc298848d86def6f017780e9cb144bbb21540a14a4d47b61d7a9b8c62376fc2c136f00c97a515076f6a0b63faf7e378f2cf04f8a90ac942fd70e25e683cbe70000000000000000000000000000000014125c81d4d7a8ea18004d798311f0d80c41c8e3a08366f686145e867192bbb13244f9f77217559cae72a150faba12a6000000000000000000000000000000000fdcaaf79c0607ebe9c8ca309d29d32284f3567a18dbbd23da9d96bad7269395ec2445d153711df4c883e8e7f7b02ab2000000000000000000000000000000000d34dd6636ef18b14f011fbeb62d33ec4358166f96f38a54c36b8797b51c1bedafa43d9f51fa4afcc2acc0cdd991997f00000000000000000000000000000000017337fab49d545caba55b763c23ce9bb3d3cc475f5ca37a15322e94c37825fc800cc7ee67bdcac66f9b5c22b03bf6558b033f2270ad2416d03dedd4bafb78ddc598810768fafd349a42438923ddfc930000000000000000000000000000000013434d32deb96edafc9a0e855281970b7c748c92b3472b34cc758dc3c17c4e6fdcf3190c910fa54a0259ef8bec75a3b300000000000000000000000000000000137df92ec14dd2fc02c0ec15a4e63547492154b4d4809e25f3ebbf24fe84255babfd6949770ba61637cc67e8ff299a2b0000000000000000000000000000000012fb20ef106e8cf3c79173e15dcdddb216c25a4de6797e411fd11d5632aef1304b36f8135c915c8c38caa2d778788f060000000000000000000000000000000014ef5cbe5711a815b9ff845e9201745f4117149b54ea3c6d1606060a192d513aa8ffe73425e37a42537773796b6fac8f202d0d506bbcd56c92bfc6fbab36bc96716de1af02aa166e7db2e2a0a4c19cd7000000000000000000000000000000000b1581a5def94e95e565bfd402cb84f2f21c181639c047d8f91044da84bb7854f5cb4eb3a6cdeb66569d99410ca3ec6c000000000000000000000000000000000d8029828f4ca245cafa7f396c25592ef08f6768e1a5b806450be6ca5b548cfb212d8c4787c3f15fe922f466dbe518c0000000000000000000000000000000000f51e01a044b6da437e3850349476437e4ff8b94fa190387099b17e6462040918cb2eba3b10d6044ff2123242005bd6f000000000000000000000000000000000991201229a856f88348381e1f2e282f0487e7daf1e5a4ac3854e66fa3d1303e3c20eb9eca605859e7d46dcfdd7615cc8329762dde1c4c91043a740a8b9639e83e809f749fc8c4853966cb2ea520620a00000000000000000000000000000000011f1bff5df413ade311b0bc3b46c4ecb11e386b886b71226987f14bc1a3a4b986412c2bfe8a4618ad5d70afacf4a3b4000000000000000000000000000000001972f49fa8b36d11d9c9d4ed6197261506b892ce6dfa932b87e686cb197560dfb8718aa413c38ee1bb771a5618c17224000000000000000000000000000000000e563bd240f5e18b518a792750c00aa5dfbea1f79b80a71369238ef15df9885d341d6901fb9168a2e74249f036e9a688000000000000000000000000000000000670e59ebf6e30b458ea505075840ed5348563efd536c31003d8d0bafdacfec7ba1ed401c616a3bab431a0fa71bb6188ea46572fdb37fe282203172c147715bf0a16e02a62bc79f33cbfe36703c95a7300000000000000000000000000000000071319574a93739586eda876ffd3be5d982e6fa04f5667873dfabfab83ddf603513394e0dbb9f418e725b02d2dc7b876000000000000000000000000000000000c6a8e0261da2ab499bf9a639a6e261e8c479f3f2b2d12992b41a3267e034c25373d4da4645626e6343e867466bf3626000000000000000000000000000000000045a0312dd5fccdd19edb65e24d5ba50e44689a9748ed9ec208320bd9eddf8d606b9340cd34ebf983e69a65c242fed900000000000000000000000000000000090b3dbebc7dd49e9f764e99c43b5915b67bdebd00d22c80e36e08873e5c5186bcd082dbce94f4f230b237d60cab7107b9e49472b9b74cefe5a951febe595b0020c43fd54150445fcdc4292c5ffe65f60000000000000000000000000000000007b04063dc315025b8545cef11be6b601fb4ae02597d75979b4946f3872764ffdbfd309f5ab3b36fe47b810f8320c1b40000000000000000000000000000000009361927d02192433a8d3c3d7871d76c6d88361774913067d16b68625aaa60f5a4ca19b6fd4140a5a11f92dec57d783e0000000000000000000000000000000012501f19b73fc6ddb4d194895e5cc2b89ca84defb7ae94f3170f25417965102fc195f38dfb7a2d88aa4b24e4a2fcaa4300000000000000000000000000000000141d0a0be60c32247f6cb0e0114251ac68c90fd43651d58c3108c728601ad6efc27c27a331a2f086d55aed54b3585fd1b6bfa1ec877010aeab030b96e80d2e27b45a93c6a99e2aeb3ccef22527c6e47200000000000000000000000000000000043f74a82ebfbbcf4abf3fd02eaa4483108a3446c9cf041bc67f5078d1774308ddcb3f918d7999d1e2c0876177cab6790000000000000000000000000000000000da7d4fa72dabb314ad8f68b61fcfa38627d1d7719bc07767f596671c58cca16e005d36e42413d03da3c643eb46b1eb0000000000000000000000000000000019f3f8f1a4008f9db1b604373d3566ae7c14a9147f80597a31839b83f0f8dcdfd829f7fa933fef3499b671867c3121fc0000000000000000000000000000000018bba4bfcf7629fcfa47935e36462cef4fa3751c7affa2ee2cb2fe3e3532d46ca1d247393ea190fb3f48077270d6a8b22810705458845232e851b33fdbcaab01966b8ed53b455873a966c1d6b89363890000000000000000000000000000000005a1e0e3a023f67aa7ab0109814f130a05c8c739036b98c70c8a8ddc1828d2cc4e2fcd16de4ef038a7373d15c78e81f10000000000000000000000000000000019e2bb467409b3dfae0b06244b4140de7f75cb105ab897d1ffb999c6b53bf3b60a3d11354815621c5d9f07962a237ffe0000000000000000000000000000000012e745499d5ed626b4762b57923bbfae7f1209408e7ecb8813a545c4ece0ec7c48a4015e0e264b47fa08fa82c39d3a110000000000000000000000000000000008acfd3c2a2e17be41a70ebbd1ca2cff2eda8a359e0969a389ab0a6fa51db5601b386dd035b26232be08d704a02033a7175fa4954e56dabfd1808f93d2686e0b4fd285bcb78b80d15e10e63ea8c7b646000000000000000000000000000000000fb464af51161f9c2758acc09d16754d4d8ac52a37baf2fb6ccd3bca3058bd3cd204de6c8a0bfcce8822f16ecfcd0601000000000000000000000000000000001819075eaa6d9e3f0568ecc2e507370f938a65169cea1ecc40c9cb4d02c83d7964254602e3d041ba0f93c24369fdf3940000000000000000000000000000000016c179832739a8129d2ef184f4d1231d24bc8d4093670a63d73771983152ec322b6a8c954565d61c2af76c4f6ef5e8a2000000000000000000000000000000000f6623578a4fa45614f4b74768adf65a753a35dacc84af005fa4d7328d733a09f12f709a7bb7f89060f60d4fac85780ae7dda7e5373d0e0afc3da1507416f47ea8b467a5b6c2fbde484aec8777ab7559000000000000000000000000000000000189724a2a0723e7727d224ced126e4288f4743f6855b035722f2aa36cf2f0a6fc23f6835c25222b670c15248884451b0000000000000000000000000000000009a57d85140f31ca58e38b4a99c4ef103f0a4af0d5546d416134fa8adce6ecca6588c3c56ba06b2f59015acc1a081099000000000000000000000000000000000dfc67b7644851c3e928ea33aaa0f745a18983edb7488b148736e81ec0c62345c11e3f0dfce729d893dce27ea249860e000000000000000000000000000000001712009a81e06a85a225a46fac056b139c8da05e6b72074ee4079316e490a06f51c62241e380909b86239d867d631be16aa731f9393d2bb32adf04f19884dd1a5e7aa36e46408b847222a153da95aea5000000000000000000000000000000000976746ae4d9325d5e8300b57ce99650f28055b5e020700ee5f124fa76ef3bdb9923101c3a1f46b6985b8203b4e8c60600000000000000000000000000000000057310c3b6cff6c849938f533b401b0cbe10b6ff3736c79a968009b2c0b90708b6b9a98b8e594cce09c579a64ead846d000000000000000000000000000000000d39511e47f33e310332178b8a0210e76e4d4c7408ff5c2374f5e7bde8335525e03897cb3e2bdfe59bb76b21cc6411df0000000000000000000000000000000010c46a621b7fb2e7ceab8943b3371475d3d6f132fb658b8c6bf299888711f1b344ebd4a5793ffe6a7a7eec8c66c80303985f367919b0f3c667b1c1cacedeb0be1f9cb175c899992ef55f14e9b7aa6ad10000000000000000000000000000000011ffff38891ee56cb1fc062d02f6c9993100f991a556445b5ee1b1b0d56d8e64bc6eea4d7f69a6b6dc55ce7d8b4ba300000000000000000000000000000000000d6cdd95d1ab2a11ab424d7aa596cc7e5de025c57217da0da143887d7dccd6fda0addae7c2fd9e0996bdd0d23128e807000000000000000000000000000000000499b3e69214fdb4db7dbecd619ef9c6b5c8343c808e4953f593cc89adba02b5cbc56a5e7a3046c6023c5cf305e54e85000000000000000000000000000000000d267e21606c16479065e47da8e3c058cb59f55a1316a87117a73dbb067ec26f406eba6a40b30ecb00f506bfd3c32f4da3041cc52c6f1bf62dee4c61b1c5e35b72ebff7e8e89b05353388b551eb10010,000000000000000000000000000000000650fe9f3cb3620e0bf1654a7f1dee503b79fe2218739bad608dba9f1e5330f325b4fb7c340f118eb10dd0776fbfe63c000000000000000000000000000000000bcbf1c6a684dea5ad6c1a540b3525cbc64c7c431f37213bc8b08c8d8915a331c07bc899d3a2ea72a9a4bb2c539cf56b0000000000000000000000000000000008fca1c364333f558c7284afa1be486e84bb035b049a2108b0df99395149de83549de153a784e4df2b0134317c85292b0000000000000000000000000000000002784cc1d11667bbd0759bca35a16a1baf49a21765c6c2c3bcdd4fc9697ef20f1274be5caa0f820d37e843bc38c68957,240480, +00000000000000000000000000000000051646993c3aba532988d7baa07eaabeb8366853436b8b19c0fe3e14ed45fdc65448d749adf745291ab5ee62d4e824880000000000000000000000000000000002cec01290d8e51ccf751183dcad20bac20b8231804a2b6f87f886aacb61d31b14f2335629e97af0ae0546a17a4cca49000000000000000000000000000000000762afa7b94ed580fd07d5141a8e1299c6ec439bbfc6c1a4d695d9aba4ab5d6dec93dc4de47096d72e5ad87d879eea190000000000000000000000000000000014769208ce8a9682c8e0340f68a0290a7782c2b04e3c13027f0b23966eada2ffb2156f6e20539738535fa0ef097f78d6709a2e80dd96eb12edc481e3d58893bd0d789a499d5289072d58c2ea80b036cb000000000000000000000000000000000acc4e3ccc3574285c19d2545839d1da9db6770b078aa399262b7c91a7c41fb4c83fe7dd0aad19f4e3eb2b56273f664f0000000000000000000000000000000017851c99881677b89956fcdf1b8c5ca5dd0997d810f3fb89f7378dbf7964926cfde315f8722531d6d715b4932179eeb40000000000000000000000000000000005e374a4c7118a76e59cdaadebb1c4e635b4dd18665010249f3bc78d559455d27d547856573e264c98ba39f6f3abea69000000000000000000000000000000000a532979cfd5263c774f629027f7624799dd0f9d6a77f675d790a85fccccad6e93c00ff2e5536b8e9a92443af14611e69ff35bc510c86a9e72c3e9c6b49d2abca546f7a62330156ec09c6fe6847a400e00000000000000000000000000000000056f109801b7a4a36fcadbee7219c06ac74e4a3f7b81616076c33ba2a71d7ca0776b596fb25d29992fa26d416272a4b4000000000000000000000000000000000c02d7e6ec50b778a7ff36fbe5751ba32beb1c2024b17bd99b46239e6dd5a708d2fc689e8e8924902e0d80287cdbd6e90000000000000000000000000000000016f18df97f48aba4d1b64e71eb894904d02ee7f6ba425e58f38a08542319e2498cb0dada8dbbb81bb398c9c924ae44270000000000000000000000000000000017dce98b335f536909ce01647aaabb918942ba2468d9a07c5516cfd347e1baa02029d39de1b2602932630e4819f2f00f391dd27628d0808d4a0773509737597230d7849418540e1fe4498fd70d39d16c00000000000000000000000000000000005b23d6f76b8bd4f334e91771383856794d1dc65b365fbc0c94f21fff049761d7379f0d512c42ce13f878e0661712d100000000000000000000000000000000009dcf70c16f524ff540f132b35074cec6ed7dcc1f319432a0dd09b3ded0778ec9ad0f05d67ecf3ebb7947951fc4b25d000000000000000000000000000000001075fb15240d532a9543dc59cb0098cbd03da77c3bf85a0ef8be1560958f8ab57d3777fab5836ba98d67c721a4a8cd460000000000000000000000000000000003511525fcf6fe224eb87b13999d2548b6b8bb8069fd354f298a025b04a33f48be72d8e82a99b9aa34ce5ccdc1f1a59c94f11b10e4c45f15d811e3db4b947ee6414e262965d7b5c23a731b019e63d5130000000000000000000000000000000019039c69d52a66330d2d8572a1308bd88159f0383c041ee7605d0aa86f1d0fe3e884d0a2ad9c72405149b5fd204ec3db000000000000000000000000000000000942163eca08672af3827dbd876b9c1adeefcd5ae74a2768fb55f1e8b342aefbf76bc6546853a2b33e26fa866e60a4e9000000000000000000000000000000000c60c6bd103ba5bb5323b5107373cd8d706038bf5ec2b367a43bab72411523bea35985b974c756184c346626ab2622d30000000000000000000000000000000016c4a2fc8a9b3c54f65cd150c80a3bf70ae8dbacdcd37128514b4a881239023e427f0b0c8984ce219207c458bb380da970f7a0ee05cfc3f63d46a3151c20da53604628bac70d7b521b3be65d7b2abedf0000000000000000000000000000000003e3df9a8ce220be05f15904a3321a6805ab68bbd539479be56b2a870c3d61234e9cda8190bdc89f48e7f0dd9374e1d800000000000000000000000000000000040446db3ec43e3e67dce62efd741a4157e8ea2597a143f7d6273b66c7045daf31f72397b4b9d374328520893157c1f1000000000000000000000000000000000c3a7dde5b02df5f7c1e750a9ee5314a580cc6ed53d326a9157b507ebd6c2da314c37a7f1837f7fcff7e8754ab603b7b0000000000000000000000000000000005e617ca4eced853f8f2e9fdefef810c97eb27d5c8bd06c5b4ea50c03761c01e8adddfe27d2d72eed8cb25ea7514a4aabd991eb5e8ac8ad7cbf8fe64a5889b715a2409305f2366b278adcd2144d7be8c00000000000000000000000000000000104ccaee210aa8196010a6478702a54cb7ba49c80a98ecbf5c0920408ff8b4a7568212bfbf3561b6a7790520bb73bd42000000000000000000000000000000000870ddd51dcc76c8a97ac4b4f23819df48dc8a8798df0450d7a45d273f830c908541dcaab7b066bcd668b289c846ea000000000000000000000000000000000012fdae32b020a346ad5edc3bab360fb5ba55004ef3dfe5f437e841b5dd7284ddb3880051956c8068e49a3fd165143ac50000000000000000000000000000000019081bf768dae314fbecec408d687df5b6ecb32ec24b41f9febd583c05693f80345e6b9d81322ddc72616c1cc39a86811a9caeccc2a2058c2f5a271c09036d73320f9bcb31b7296a796ef94ca4599757000000000000000000000000000000001316b5ce5bcc168d76d2c862230ce604d02cd3d242c51c250bc6b6fe5c380c9e83fe7041049f2272481ab38f44648f4700000000000000000000000000000000079acfc2b9629da9c9f3394874e64aa00527de21e726f02db180f86cc0b9a97138c2c567832e287635721ca40469e00c000000000000000000000000000000000e11807dcd4ac69fdcea71e3e6a93dafc27afedf12c2998dbbb2e4f33e37ea736df73af791eae69bff84f3bb212bab47000000000000000000000000000000000e834a34fb63d9df68d683a26d79ecf8ff67066586e5f760d4468ad196c66d4ebf8605ebfbb7bde201f47b35cfde3a5d8ed4eec02c2af286ae19ad5f05642587cb9ad93196756d269c783a11f23393bd000000000000000000000000000000000990f115519d2125d47b925b613edc3303110e9040fa705211e0d772edb2e0f7f88ce521d1738a5f65c9d158e9d360c2000000000000000000000000000000000bb951a16decf9be8381d0c88726b53d90bb32cd8aeff962d48e43863e4eab1839bd80d7434c7eb808bbc0e32e92a4290000000000000000000000000000000013dbd5bdb7caaecc42ffd81f14be0ff3d8fa228ff121ed4f2f3ad5961fbce617d7cbc8133fd49e03caa62f7d1567541b00000000000000000000000000000000195fd9b85e19d0e3e1c93bab0380cad6f6f3bdbdcbf5c6ec32b7de7972421d0065cf0b265f6250c02eada67e95284bce26f20eee9bd019f9e0f5c794e22e770128737198b5f5dbaf5b7d18040443a0bc0000000000000000000000000000000009ca977266277bdeb985750df47353a6b81c5f0c473eb3369d25a01df67610bebf66a6de5727a465131404025e90441a00000000000000000000000000000000054410a13287ecf4aa18f543916fcd65b15cd5d54617433217b0a2b91a79fea764b511b3b270de3e8985e8f6a2fd8c380000000000000000000000000000000009a9802a03a7c9fb63c1eb13972cd42ea2df614a0972b914c4015c2e8630af319d12fc8108b4c88db9508a9a77d9e57d00000000000000000000000000000000094d83483bca296b20b7bee124f538ae9c659a84541f5c9d9fd22e98251d2b48051ac55ebe07bcc9d2e9109f526d60a6c470a66cd3428a44a7d095ef410126257175597a333cd36ce6c9822d1ee9bb380000000000000000000000000000000003f2d93ddb6d5983fd5521c1d1726addf662af0945aee54788855037f47a013d2fe595231792a05e1259c5e5a8c553a900000000000000000000000000000000004f4f4e7df5dee975fb440b5a217c27d9d1eb83a5ae280a2b147896f6bb864abe04459c17ef56d784d3c4a0b7ad3f3900000000000000000000000000000000069da36057aaa89cda458af4ee27fd9ec969c8f7612cbb153da0e010d67bfdddadb2941cfbdba8c43019a9f1aaf9c296000000000000000000000000000000001545b8325a80176ea148a3d9301debd7046f33a1b419b4ed01916a3d0a072037fd617d96e0bad32b208983ac3be7dda4e53fa8fb708204e619c221b8ecee14fdbcb1f94731ac2c858787ab33906c9269000000000000000000000000000000001536a81b203df2640bbe7e695b5fde186021d21685f24c25966cf11dde554d49bcefca64f16697509a9ca86e58b75eff0000000000000000000000000000000014348a2bd4907cf081f2f7bc944a98d3fac671abde029995377df190f7f60319b8de1698b99be39c821328e32a449c760000000000000000000000000000000000e18d4da3823addb2a6cef8336c83f99f390e23d7129365d57035d4363aac7e9c4da9f8000f086f7d2206666f990dac000000000000000000000000000000000d6ba54e2af9afa57ff4536a35e9b61c8d8fb3d431b653a0c66a2a4b8f11d9b5c45389f894d64485233d4183895921f3abf8de43c54ed59b936e1d55032eab5c9d9e04e83e4696d969c24167b4239f62000000000000000000000000000000000d88d5719e07e2332c54ba41f330c7763d2b2b7c4140d19b8b0972fae6ef902415de5f2abcc2342fce24d3ed8ffe156300000000000000000000000000000000163aa2c768eca58194fb76822deffc37cefe04ceb70aba38a51f507be7cd64c0755abdc2e49e7db234cd5d68575c2d7a000000000000000000000000000000000e443d9953468b8cea4eca4f5968e214888e2b95bc20ece39483ac551d4e180c0b0a41c4668c8ddaf761a0ac03fbcad3000000000000000000000000000000000691930530ce86a1354d73cb21ee32d968e6d89b12e5a09a7991c7d27dec302348af7f49c3e0de91e1a1838aa11651e795f59041329b6c3e6aef01d3410836852f79cc436fcf23199e0985c56f65c4f0000000000000000000000000000000000d7c6f9d4aa794f34596bb9af4d62363462d9804898ebd7c7db7544be1f46b4bde488ec59004adaa0cbe40aef525ce3f000000000000000000000000000000001094629b1428c4c284b7a64d0623e10ca0c4d395bccbfaad89d1a737a3887c10b714541f2681c33e674c3b99a36b7a450000000000000000000000000000000000d6812fad9c5ea365a64ebd3150238349d88b76d041ccaa7e637fdfa6c715d9d6dc3d3315cb95fd6919fe419d028783000000000000000000000000000000000eee5cb772ce02fe2a4883008f17570aebb902ad7c40b4024a5b24ff75b3aaa2b54ace6fb4601b1c62837a20204194dd740e4a207ab5dd4a0621fd65697f5d30b8ee1440a5f5c5e74a0dbc6b6391c1b0000000000000000000000000000000001026d21e075fb8921dd849c98252a565d39ca9f5a62a825e7e3e77ab5be6620e76e45047e51350c48d9a4cf98a1222a9000000000000000000000000000000000f6459a8287bb2da77404a515dd7a35f46a4aa49ef72cd2cdefbc5e5242872df5f7b7aeae6848d59afa1dd142ae7caca0000000000000000000000000000000011e3545151d4e0b034b950cd2f1a3fc2d29e9d53250ade2482b7ea6075dacf7e8e777afa1e8e612b45028205235265970000000000000000000000000000000017a869d75144ece603c04d39cb56a487895cc882fec613f40f6a66601bdbbbb7748ec755553257d654d1558b1104a981f49a3f82d25c6e0d69207e6dff010d56f0d99b28fd986c5711878dcb6665b1f50000000000000000000000000000000011602a23c9b5cc091a700114e5d3557bd4857c4fc44cb8628ef327ddeeb728927347438f123e2011f9cfda9b6dfc42e4000000000000000000000000000000000c4fad264ca95827e9cbb9783e36cb0b683fcc33038d47bc7ab6b65998770325588e5b910e811cf7d61fce13c3378d6700000000000000000000000000000000009b4711aa67e84434cabc289a78fae48ea86641a162d48b79bbcbfd56237705dd2d1e9ba3a18d737eec29eb8e940e58000000000000000000000000000000001160fc9e2a488ad9385140bb62ab48ee613c2284208cf2f92912e1b973ff81a5d3de338d9aa6881cbe437907890258fc8390fa1b452f887ef3afc7129ad8ceb9a8397f7625c2b249d7442566814ae0a9,000000000000000000000000000000000cd0d8c746ecc8d92fcf2232793282d7e0e17e0ec27ee851487eb7788f590db3487296061075f36c24f67cd4c4bbf36f0000000000000000000000000000000010c5e1d05070c27f19c228813051c6a830254542eb71469664c842695b219670dba8ddff858e2d147019847866f01084000000000000000000000000000000001799ca7d8f2637da761622b793a3ed3317d50b902a1cabefdfc776b0d0ef88b707b8a5c36786d5ede3d8a381de4e069d00000000000000000000000000000000129881a3b56e0014bf1dac4775f509f309c33406f2cf22df9a0ccd15c87ea48a868d4437303923127bf580b8d6ed0a8f,240480, +00000000000000000000000000000000000d087c1b98f8c67c1bbc4389f21d9dab02faf46ee4223c609e7b9eb399132ae168bc12847c580f58edbb9255dca3b000000000000000000000000000000000065ded24bda39d2b830639fa511bce8dc770eb95e349d6874ce63b3355d23c1da3ee9771ad44e57c6c661b7453076fe7000000000000000000000000000000000fa3b2ef40a7c3d41f0c3a5f86afec252c6ce89bd1bf1f2192026e22fa256365360589c788753033658b1ba151797feb00000000000000000000000000000000105040ff4dc2bc435c2a82e1174e2ee0b94043d69074f01e8ed013da8c431f33c94a438a93b06774411780cdb72abbc8414ca9894bc15e6bca798544138689b2471f8171a5dc48eccfa36c83af142b7d00000000000000000000000000000000129c8c1db08ccd0dadd59b04df67a91fb6547d97ce23e59aa57cd3d38458e6baaa67285800809856e7e264d812e584390000000000000000000000000000000004a0be934248b4e142fc51745233b6d0ab2c46f53a8f9d4c84981e5eacff146ee6227de289c713e4ce24a4341572c9d70000000000000000000000000000000005916d14a8592af57a40418b10376e8e20f70929d2ba568c1fb70e343a1dfcf3e63c791cb639bec49c50aebd2f816fdf0000000000000000000000000000000018682c66a461a69b11d7c32f7aca07749e05a23fc46547bac121752aef64e9bb98a274d15a14faa93af8f284790acb9b99eac8ce85a1bc70c725a2f04aea3749d75d22c0df7c0755a5e76ab4d82ef9420000000000000000000000000000000001552053742eb89ae3d0b95be919c84e53919c898ada92d3eaf05605a19ac910091fc08a65e9764f3108877c837d478c00000000000000000000000000000000118e5d22f6df0e6bc7447177ce06659f94315478385372046b649fa6d39fefeeb492e6623e0160bc47233f4d3143e326000000000000000000000000000000000dd02c30cfdea5abd3550a9f28b546d82d5b3043f012de622d892062945847748ba820555fb811fb3382791ec43ce1f700000000000000000000000000000000050373898b396d9a641e2f2ed832c7619515fd9070852b891b4ce0b5bb5ea8b5e24248297d53e9db7cb946e76c4433fa49b25140d7967b0438e49f59a6b04b75bc8745b84d7350605be548c6b4b3aeee0000000000000000000000000000000006b465f4b9d60a3a14e119c54a7c35172bd648c86a7cf331e80ba849fc87b9dcd48410e3c9a07b634e83fc7dd71e5b9f000000000000000000000000000000000283ad9c77f549042f79c47b8a69e72164f0ee77aee50c20519d2b89029c63ea86dde2744cd21eb5d37e896c3abbdf56000000000000000000000000000000001668b08a87787928afe92d941240e503da07b646a34cf82ed09d4c2f4d479aa24358c8475eebd9bcfaa6bae17c430cfd00000000000000000000000000000000150e5b28bd901f7a2a9af44bfd6b78cc84900dc05e334de306f9a45f1e67708adddf4dcede8150a39670054f97a643436e30a51d55a1ac94089d0f3217c3a2182da6b02ce70ce7dd8e2d4e938bfefa9d00000000000000000000000000000000060d75764a92e30e80e7c1a6df1482585f4de901bbc36dd9d8978a76c12c739f85a9ba16741d0b19ed480fe2dc331e5b000000000000000000000000000000000024fd15c9e5b8872d2e9dae9ae96102bfb0e31d15e92a24316818862dd8ca7a6fef271d499fed5e0db6dfebc4c72e0200000000000000000000000000000000058cda551e1fcd701c6a3880b276a2f7536a26aa366a6425a1c42cf31eec678551f489a27f23ed5dbc76f19b0fbfae43000000000000000000000000000000001152e2cfdb584295563af8120c523a9f4c01cf72da64fcbe0a90a284d693a3089f299bc760166be062cf9f8efb6a951ad3da3db6492ff36102747d9d663bc6e9cf8f75b1cf77044989c7af3f11d66ae700000000000000000000000000000000116fc24e980b2e7ad6bf17bcd7c4f06e654bbf766ea0238a66d738bf3c2d41c8c63bd52f81553cca5fea91f5f9b74a2c0000000000000000000000000000000001078f19ecf785a5e0d3e764b7d6ea47b2d077b5eb222f4e6a9451f134ff0d77a0b9a3b53caf599705d131e3b17b6ca9000000000000000000000000000000000e44c07f00a1f198583a8ffca43da45d8e54e1f2a85bee7afff6c1c733b5d0b5712961c4b6d344869a8e4de3b34218e000000000000000000000000000000000083c78b3568cdf808b75d9ee2b03b98cd516bb16ca8cc35757f53f12119747bf6b5b0605bdffb2f079cbc69e99ee0bad6de8753f3df8be42b6d6ab578096426f852de4ff545d2e4ac12c3943b044b43800000000000000000000000000000000087ded6945bd6fae7a0aebb1ea68d3cd34588035531a6cb00fcf1b83e06f7ec21cd3486580165c1364027b43e238e34d00000000000000000000000000000000005a2fe8a9871273bb60cc7ebef44a361300a1033f3f0230a731f5723fca124ec9d305cfde45802482a45942154398cd00000000000000000000000000000000121eb94a41f9e133adf082ef651272c178d780a1c31ba8797f60a208ad36b4c703c9b6c08be845f8844dd14d6406734d000000000000000000000000000000000e5e3da7c91ab4cca1c9286020aab9795e64e667d55a5a700241f9589aa3519639f168d040a0027ac057f334a9f740aba28f7ef4b12c5097a15fa6394a4dcc3ceed6cf3c6240ec2ac949bc21a9f6447f00000000000000000000000000000000041f9117b426938acb40c905bbcba443c043bb55cf9b876edfa2ca051b6354124f0fa54d6a88ea172c3f5c10c6d921b3000000000000000000000000000000001828dc0b9533274db6afc802b2fadaacf57f28126094b6b9038ed5f6bbae0112c873fe5eed15bc49b970461abc2f5c3200000000000000000000000000000000107df6da02f106ae47718959aeba7b4fb4a8f0e2651560e2f2266a62566e13a5af86430b8800543f5eb6b1e96be79c69000000000000000000000000000000001628fd4a598813133de75cd7c96ff3711b6bc826806b96d07e5a89cd549592f0f51c84aa9ee0642cffae5630ca1ebae1a3d0eff3368b10d00566f35391bf43c9d204a4444b7eb91017f1b2d8a762d90c000000000000000000000000000000000e8fff44163cd9c2a4e148eef3cbbee19ab8f648da1a8d438be27d2b0bcab393fb7d49e096d9a7abed3d8f82c11c4e03000000000000000000000000000000001274335d8bde3d14924f8d7ba18fea82bbc85427892f18fb741c8ecc5f2d6d7bee74c68058164c55db3cb8da8597bfe40000000000000000000000000000000010c7fc728c094e47569f0e75446c399d20a1239b511e34d8d6193dd32df607dfaa4377a1825b3892a9f74ff4efa0d9df00000000000000000000000000000000067d904122a6581b5d5a60acfe8156dcb6c10ed083840e506487b5dd9117927663e0ad883fb91b4914778ae082de0a7eb90d76e660389e570bef756e9785e39b9748aecd7a34556bac8399aa5564d12d000000000000000000000000000000000a909706e3ce45c86f2c30de5e820c8c9eefef207e530fd504511827f5e6422714d3f4224afa6bbba22ffca533d647390000000000000000000000000000000013ff61472ddc0d70207692648087c283763ede668ae380b0b9d6ae6593498b0adc9d4e4fcc73b5cce250e7563f7577de000000000000000000000000000000000a81db69eca785373c4dcbafd8635b23a9f41265e91152f309fb2945622937e65b5c17656abf8aff042a1fd1e5e50341000000000000000000000000000000000c66269c3ccd9e91766d1a640789bde6de752d08ffe3b2955df8dad3d2a0b6cea9013af235cbfbccee8271a7242e310614f18dae096e4de75de3da284a5755efe51e912e180020a20adf1f5de43cb51800000000000000000000000000000000181f3f4a16696980bd0eb9bd10ff1084ffe90bcb65f12f505b25f0a26dc1d4e16987d486b2c0b117fd6f2e356b83a5250000000000000000000000000000000010d7be6788da3ec56c87acee68ea8a03e7d467f816060207bb163dfcf8a4e7721651bf2bb23d5bc390d50fb1ee6625a900000000000000000000000000000000196c1ac817493f51d9ca891b55fa65ad5192df83cdb63eb1a634ad54e2d627f7feaa68780418f5354e6cc09cdf2f6c5800000000000000000000000000000000190f36690b8d36f2e295b9625f23afef9d9babe87c1ba0303f60c6d44ec952ba6bf8356469cff9d952f8e26bdb86ca06e32d4645ce0172000fd74f30937261de89753caa716dd03a8b3269747f2349a1000000000000000000000000000000000f77df606f0611856c449c58393f4ee7a6225a5bee667382a48f59dfc747736a895d598f90ab26002dd0ed3a5a8f5a200000000000000000000000000000000012aa50d0ec440884fc6c2f7a0e8db8a5e79160f0c482209ae1a1aca2b9dfedfec6d6ea09252a373ea57905130220a4820000000000000000000000000000000004773f46165cdb19cae49cc42663316df39586c62be5b827535f138e1fca8dcf62ba42ab60ac6dcec85e8496f32b9eda0000000000000000000000000000000010c91923c2c7b3eb2cd9aaf0455c0eb035e38e5352d218b07ea23f50040ea58fd548b373c1bee9113d3d44fcb25f6ba08c8722e3e929ba21f1ed6c51fe5ad4940fb13d63e0293893135d0da5e6e0389300000000000000000000000000000000044b95fd5f0e049abfdc2adc699646afa5b0f64464779efacce85a5279477697090615933069992bf30036c6ac70dfe50000000000000000000000000000000002778e7dacc5566354c24ea1144613a5ce8a38eb56d53d230ca145ce83d5ed88596afe243df22cba10f423e64a7c103a0000000000000000000000000000000017e87cd2752d8674c373c557ab2b922e02620a070aacf6f5b3d3d07ca35d89ed2666da7246b800717c0e4763dc35f5f6000000000000000000000000000000000a3ed312e5f309eafaed486629d953970cb73f839bf30f506c2f393df4c283f299d6c643ae6c229430d919e8aeae8bd839bef6ccc893f6eed62e68f5f2a07812f2d3066b89653431e7e39e8596bc3652000000000000000000000000000000001082a0edac6267151c8ef11fac7614b74cf58b39b72fb71e4d66467ed4fb3264b177c691e569230f2a13a64b4a48c6fc000000000000000000000000000000000073a8d5f96ee580741bee1f82cacb6139d962fec34c44c648c8fcd0322796429bbaef083a11b4c8fa376d4c00cd79c00000000000000000000000000000000008d41e51dc2822e0f14b992511de799fe4db3783a05ddc1026a53faa89af000075ba5aa830ceb7551e51f0fff144c1360000000000000000000000000000000006bc4bf0bdf350af417160d06e8aebf2dde02c9b50be39b0c4dcb3a045f9e04f1f041f6de10328e287df6121247dd4e9c395ba8f2553e3eced8a42b221a710a5cd2a5ffe5834d3084dc260ae0f51698e000000000000000000000000000000000802e7b71127a15a279a629e89f194b51d19c4f329efd8ecf9fe69d340dd06068c8467da6ab39be25c194077d3ce2428000000000000000000000000000000000250172c787afe866b428748be8359d8e0bad161832abc108c850362c5839237483fb38678d77c94696260508907726a000000000000000000000000000000000d46223c1666f314f9a1e32a94f83d8150755d71252e19af91a3b460ab0ade2db2364d8c6217cb422095f0d9a1ed648a0000000000000000000000000000000002fc2849014717d1c07935efe601325e1842ed333897222f6de322dac8b50bf4d9859eed8880a34676af0d0e3277639053ef5568a766b6c39854ba059f3130b75d7fd870bfac2b00b626e2d71c4968e10000000000000000000000000000000004151d78d65b0c9eb26822e20d90ace8fac209a1f08f62ce722ae3effd7fcc476f4c0179e71b09fc181db96fb2ea4eec0000000000000000000000000000000013d17ef429483be98411947ca0771ce671fc38e27bd0aa4abcfd5ddf1af9e138404d86f4c2ed74702f80a573638d92f500000000000000000000000000000000178f2a7eb43b9f88acfa892b5868d7f7c5787a399c1c566de39ecedbfe88357fd5256ec57e1ba12e9784382c14331756000000000000000000000000000000000253a391373974beef746c4397654a30a68992fe9163f9518ff0ed9b7be37b858ac60c95259ab894bb6acfd123333b7fbadefc3880ca8dcff10b8b763f7d15f88965c2261b72ba879e3540a90c59effa,000000000000000000000000000000000710bfc39e92b0b9d15ee9bdb4959daa3a78f66aeae29eaeb50a0aa0460f3ff703c86eec8903011b4b61a0dea725ab08000000000000000000000000000000000856fe7a074d37786237cc14ff1bc53c735ee8133b231dd3fc63dfa0dbd1979304bcc7b55cd1bb66fd7529e15d15db5800000000000000000000000000000000014757f1fbfd4fa7935ebfe65e150519d6eb4f4831890df4b236dda98804b79862fb6699b587c3e568fd6de1e582409900000000000000000000000000000000000f7b54e4961dab9e94b1c4b897177dfa74be9937694a38207ddc9d6290dae1d5e122cfe4c8c31d853db3783999a7f0,240480, +0000000000000000000000000000000003bfd2535c6d8ffb44670bd02b5aa6f050f5cfae7266fc3225865bc3f34320820eaeaa952f80da51671f6d97b3df9d4f00000000000000000000000000000000026c1adc0ffc3fef9ccf018ff9a647ef5c69c5133fb4a6566cdcbd3180d9ee784f34d667edb1dd54ae292253b45576b4000000000000000000000000000000000ee90fb541becf96b4728f1859aee5ae74e30ba9193b90569b66b0e1d087eb81f30c21774298cb06e7dbee8f8aafb1930000000000000000000000000000000000a4361867bca952446f64c273735764e141eef43d156d6cbb6e68dbf3bc940e935f7bf3a77b57fca4bbc74bda2f26532c1a5abbddc02f453519563d6b6e05959d8de5feb493f7c58ea5e548cfec2df60000000000000000000000000000000004bdef85b0da28e0531734016e5953256c75c3620937736cf65de5f05b8beff294677668047a3b74f0f135b846a95bd6000000000000000000000000000000000b754df2aef855b4a0eb6f6aa03115ee8f38a31fc852381deef2b59bf23e2c885ae166030ccadd5673bacc35482f81e9000000000000000000000000000000000f1d760ac6dfb65b39c999211d4e4c3623c3fb8ea59cdcf926249a07285a8e4da1890327fed20ff07f12359f6d9035980000000000000000000000000000000009f2698239c8b452748126ffd83abec768edffb83dfa3dc7943fd499c8980e2d9aad76dc38b336a4a63eccf5c4150ce0b406eb0c097237556228f3a48b7e770c1707fd583b91b4b6b70f398b7dbb0d3c000000000000000000000000000000000cd724c51fd56528dfa688df46f71bbfc9144ff98958b559fca8fd05eda01c38c28630ee19579012b9913a393264cd90000000000000000000000000000000000aa1e55f2b6d9385ec6a9cbafcdbad157f7ebc06b2e30e2380ac54e71db5259cb919e17042d6ba6e045f1358aef276ea0000000000000000000000000000000010181ce9ffe235b6b271d570b3c2d6e1be60c53b4a98ef5e8d7d00b463e5bbc9d8d96dda881e58746090983d6f8edd35000000000000000000000000000000000333deb8b14f499319ad675f482fecd80f9a69ba369425decd441cd2ff5c3c77f11075f61bb1d90d0be850ff657d6b7cccc30cf1db4c6be6dbc5830ee37b5782c6dad215334163a9d9e4deb962186f80000000000000000000000000000000001581a5440fe892ee6eece5fc2227fe072dfbc440e0620a1e5fb505ff0b16d9e6033d83c83576b4b6ff87a807dc81b88400000000000000000000000000000000099b070a0d7497f33c1c478ac424d5564fa645d836a3d572d98782f08713d8e425b571433fee928475688db2b3a9a04c0000000000000000000000000000000011e1cbaa09a6361aff9e199e21bc52e98dfacc49ed83e732d4b4f2503b3bfdf85d029dead4412b6f3d7ea447e20d669b0000000000000000000000000000000005503e151d620e9a5a142e4f7940ed88375e7efc1109214141c191e9f38a32a40d3a92d6094584e763e0cf13cbb54bcc99461c0f12019b344a7f322900b64fe81e0d8a052c0ff5e977f58753b1b6edc60000000000000000000000000000000007c780f119bbccfd658f3f1b69ce9c56b1f5269bded713b6827d97d32b2a6deadcc02c410138d984d977527f3609cc2c00000000000000000000000000000000095aebacfa33928a916ca7b0ceac699c71620781b35cb2f3b254bdbd1544b728a2ec1fb35416ed7a8a3a630bc07ff8720000000000000000000000000000000012194abf7e411f4961b6f8a1e2ad052c27624ded863d7a9132d9c7ecd3b4074ef0060cd86adb73056323f4227ba5fa9e0000000000000000000000000000000002fde2be9ac1e8265f258a09eec85a70112ef1eadc3a91429c9206555933e2b89aaf7493fb833e33e5d61be28a12a1c2338ef9fa825e47b46483ed8fd2df64bc7b56da8aecbae704b7eff2e7d426f27d000000000000000000000000000000001586c65405e810e1d5b59304bb4555ca43c04a593671ec64d5ed2d2e626b1f8a89f48a4b21d38fb49909b8c614209a460000000000000000000000000000000014528cdf994e774b8fd54090cb45b68098c1ad9a351bc1f36a9393f3b4364f5beaf58fff6e5f8b21a85b67bc427c0e920000000000000000000000000000000000b48d8713aee51d80c79109fb8b4e0c6e32e25a7ca24dd3e7700f8f3195730375208b241b2c722af3c2295a1704cbb3000000000000000000000000000000001913cf6328429cf2966a48117dc74db0d45be7800f93cfbebf597fb48a8bdcae4fae2df7835f9536481f67261755da2a1dd6656a34f3b12e5568b9c348fbf4ecf50d65a89e63ec0936591f01e6cc7a4a0000000000000000000000000000000017e45a481449f167fd579accc896ac65aff6f1f7392df47d006b404de3cb7ebf6cb59d0913438f3a51e55a0ae3d446c9000000000000000000000000000000000cf4b7db343bea29af6e244a71880538b41b826bfd1d06a21512d00ce58f5d7500ab1ed77b446b1e3782df736bf3dbb6000000000000000000000000000000000525d08e134779ca7614784818876514e14b65e799b7832f61a63601fc491c8b9cb25430547f961cc1c22100170a2065000000000000000000000000000000000450cc2156c4716d0343f32aca82fd2d0712389b1aa984b31d51edc2aa0545c88ff52e470b15eb6b2c22e30f79864dc85202f32528e795e0fbe6deb4ef6e45efc70019520b01fa1d71d5505e42faa69a0000000000000000000000000000000004147c105ee8b4db68482b9d7f6a716ea1474b6c62efc41b9444ed1ef9e92e2b7010a1c1ecc59038ac37b385074a6bce0000000000000000000000000000000018a600a85c5c38be835d2e91a35cce4b59e5f5ac3b735fc007bf5498062beca9befc9c8ead58f9f21f6e08266b149d800000000000000000000000000000000012a476fcb81ab66e3101de2364cb609b17e06eabdff5246bf736eb9d5c87fddd404e8867578262f07a05731b04069164000000000000000000000000000000000c54a888678c28766ad17a18507e4bf5dc57dd394eb6e9b69abaf15e645cf4779bf6ccf4314d2756584647cf27af089ba2b39f2b893be03ab4da77ed518ef35b2e24278d707a20b67ab4d1e5972f9722000000000000000000000000000000000e809152c44cebdd8b40f0d22d57c3b31f29700e0cbc3e69f660bf7270e59093d84bf7ac358be7e45e799a75cf9c13df000000000000000000000000000000000c6c61f98bd4e3b7095fc7f1196baa98139087df00fae2a795e76544ca47e453f75929cab07c11cd3595de6ecbbbaff000000000000000000000000000000000171c70446c19fec3c152741925c8db28ab0d140720cb6a6c45e9bc66c012a421d12271889ea43fe1524944ff572fe6850000000000000000000000000000000006e4baa09b4660c69cace151e60320b771e56e7460b01442bfcf26823c17779034ac241b9365dbbfade770d2056eeecd892eb7c361f05e114a645caffce9437b7b43fa01dd66c1e75b30f3abd0209bcf000000000000000000000000000000001917a23350e94963e3a7488ac1dafefe9ab11856d405eff39d655e31ba808f02954b63e822613d3c6e5f358be04be4a4000000000000000000000000000000001620211b06288c16aa02f4404192e9f57a048e900f0ec5db9b478475f13b142f924c6de720031b3fc12cf869b422af470000000000000000000000000000000011e8ded9ad57e46713e7ac0044ee4edec12689cdfb98838a74adf1a35244e3d9a4a34c81323b089c10422abf26b044e70000000000000000000000000000000006f85c7478cec590fe3355a8d6e9557c5be084c161e090c72f1281be4ee56f36aa1e3c9c844eb45d9e295c15c4cd903efdafc3f57d6116163f1da9e70ea645243c5911cc4ad4a969a57c46c6b5c73acf000000000000000000000000000000000d555d9f23de97318dafb257cf444952bdd3e844e9ed5ce193c10b76f5179f0c6851f93af1553b128f34d3a7e75339f3000000000000000000000000000000000132704571a12a58f629dab48f1a3956392b40f801c2b3757c15f7be46ef1d9115d89920c460c0e2bb062b3cc1aaed7400000000000000000000000000000000152829eaef900fd2f19d6fdbb8f7eb3b02df35d218b494d075219b69016256e572eb7f555f6fbdbe17c59a666d190055000000000000000000000000000000000fe5c67c949b7c89a867301528f0ab24b04d31d6f18f575c475ab5a6098f7187eef20a9ed6e810684da9afd8de96ded6660a77b2be50eb72fd108644d913b9253209972fdec2d107213ba47357c96e9e00000000000000000000000000000000128bf3cbb5208d84dff719ced229921a889c9a4d02f5a508187662f03852531fb8be1f4c2aa9ef01de7720c352dbd19d00000000000000000000000000000000158d89a44b8fcf9ca8c96a8e516e130ae8af19ed71c2b8487ae300c3cdb546e248728bc58fd9cfef21107e0dabf44fc20000000000000000000000000000000012b70b42c8af4551267a94a795fe18e8d054291225438adaa33fe2edafa87742fc3709abcc7bada5d26e3a14649cb47f0000000000000000000000000000000015a853160b7666ea7d64aacd931314497ac7068a4b8bfe3a7deed85df2bb8dba277716a9d1ee50c56b2970016ada509d1ca575cca348dee9adfe68f8a78d39bb998205da2a5285c12141a77ee7af840900000000000000000000000000000000087c7bf08e085e19f0cb301d2e36478357e835620b1cde6e132c237ff6fc63e6fc16a8753550d50fb93a0a1741302cf9000000000000000000000000000000000615299ccefe4da879e5f4b01d6b6ef8358bb59ed8a2b365ec72003c16486d3266243db81f48855d81b6a25440bb861a0000000000000000000000000000000001498fd20640f39dbc03a474f4514e5e283256ac19468077af1c9ddaa40759dcf93afe256de1e49be6469fa106394193000000000000000000000000000000000cba50fc4919a29be2f4e74c261487dbf855db1856e8d5d008cc3f4ee5eb3babfdfaff878adae49b96db99d424bc4dab2e1e4537f855eb478274992cba4e3f50fd9e944f6246cd52dd1517b55bd7f71f000000000000000000000000000000001369dd82ed013474581ca1ab2d2133341d7c1d52065060d72b8317e899e79e9077bcefe6c76c3c7f67e54f76dd3c246c000000000000000000000000000000000405aa84d3ceb02bf8eae989a9cd65afa15451443af6f3cf5e70f5cd7bb8d413c57ac3893a7e8b888ae93a92dcfa2b20000000000000000000000000000000000378d003988f3c6c16d3b12ef47a4a49e2d3d2c7c67e384bcd510939581770aed92e06291ed3b7c742769f0d1ef740c100000000000000000000000000000000048bfa6550711a17d52f48377821baae6f3de6ad99ccfeb8302466047dfddee8005240cdc65b3ab11ed85b11f128624957f9a729aa01c8bf0271052202a077913a9e0c87201a367845f9b271c130e95d0000000000000000000000000000000013370ab697da0ff0a0efa8ebc7589b465374c983c13daee7b5451e8b299933eb5a4d255ffe4aa46782ae0916fd3990230000000000000000000000000000000002ee77be6e0b6fd260ad660a96100bf3259329faf2ff9796102928e70cd52c2bda8d0d1da1d484d7b023d3d59725d12b0000000000000000000000000000000014482fee88e02e61b847c08e61d7ae6fca2d993bbb69bf1653138150d5d7fed09cd5cd4097cb4b6368ea8023383477cf0000000000000000000000000000000009d0380d0d6fa39c9e242b9a67336d86445551658bc29fbd594239a76d7741ba388450caa244fb186afc36d35c8740e93017593cf311989ed8fedff72bb1f14f72cfe5bb7446ace5274d8ded54c1372f000000000000000000000000000000001537d4a47247af8f60f77d309666056c412ce089f3f011457e894f74fa4ad5168baafd36ed3294f5f61cc9cd8f87554500000000000000000000000000000000119e43382a846c8945e58dc7723a0f24b24d9cd487d436a156156a6da97795cf3f4ce382d21435695949b5137a2bf1d3000000000000000000000000000000000be5fd015998bd6043f124048c82e4d848e1b8c87442d0021390cba41c294de17648a47dacc06268606ba73cc95ae6e70000000000000000000000000000000000e05a3dbbf3da8320c40d51ac44c6380d56ecb460b0e7094819aa6af4d7c70d1541d4bc1fc5afd453b165f3d48d09a708bbe9e7a307e380c238ec1f8e2010a95fff8b03923ecd9b012f99e56c77a5cd,000000000000000000000000000000000b00b5c14685ddd17ee99c74598e6bfae5bb1c103f8ebfaec3a620ba57312f3093f9ad5eac820d81096dfece90e72ef8000000000000000000000000000000000dd81552160d449cd787ac27c76685ea0dc993a9fcf8ab182f1ff5d8a484a47c14c1c1a785285b44336c7f6fc0732a0c0000000000000000000000000000000003008b6d97a12868554d294faa26e2ebe2920add650f841adfbf0ee89af72fc4da5dc23b45b7ff191a58c17971b50ae50000000000000000000000000000000013f438d927f35b04bee8fc55693d5c97229c8548ff9de39fae6e26c26f89623d3b0c810b9be8dcf0445910e8eac5c58b,240480, +000000000000000000000000000000000e1f825b71cd9edcb231c178e160e37bea70108b369afb248edc7c6a59114c22e843fb5541e0f26c77a2b589ea88fb3d000000000000000000000000000000000d65d777e91920b17400955a4afecf82f67cd13f3e7c5d9c2076c4a4d8f7f26383d22d9977dfd0987f219a625c8a621200000000000000000000000000000000045716092850318c343f0dc5337df1a72f8c74dd729831d12103b46127c9180fb50cece34986a94fee6119e72d16a55e00000000000000000000000000000000083fac698ce800786719d1f6063c87d9f728da03cea2545b4ad8831f6c24bfff73e80f2c2fff1532f6d1fea60e7d438ccc5e9d01f6ea67dc3f943d57d9d8b5791d823592f7fae6719804c1ca097e651d00000000000000000000000000000000171d60b76698d4d3f14b4eacbdce9fa66b8c3cc7ecfb989439330fbf0d051d95f3007c389113346e614f5ec8cd170a2100000000000000000000000000000000151a96beb250bdeca3cdad1b07322040bf1cf2105dfa854bb24fa76c8abc25ef4fb924ff995da641244f9daccff2ff970000000000000000000000000000000007e5818778a8331cdcd1432b46abf1efcdf7e4aa8907fd42d5e7d14b57dbfc48125246b57587755ee1571be8b52d2c57000000000000000000000000000000000693eb562e22fa8ca4a655b76e43b50fe487ca1d65cc3867eaf793e50496f0b4658bd92199104c2ab92e4ac53c44db6f57b8fcb85e4dbc1969805d814e75b2b80f5cd1e5562bfc1e28becf731aadfc58000000000000000000000000000000001059d23ec6e472937d80829256db506d2d2deb37d4b750a980568cd5b0db085358a4d610d59009b64db1a9225f9f6f5300000000000000000000000000000000053d9ffc47672f1058856aa08e51aebd469111dcd129ed542454d6401e7893323f8a9c63641f499cd8617c7389518f8e0000000000000000000000000000000002b9b30a5e37b18af4bb02ae8cdd56f6a87820716ea1522a174a0d99c3716295ad0ff2daf663697cb56bc6053c9dba610000000000000000000000000000000019d3230c0bdc228fa0cfd5e0d8bb88be959e70e59d931d9f9e3683d5e65d8ba0d121fcea329b23c5905b80dac34de33b03edc53ced9ec5d7f302216fd30a81c3554a3fd04994f62b5e3da74c8b71bb870000000000000000000000000000000015a619addc75f425596f9a51c6cf2259087cf32afe9b1f07e346a2f4e1f8caa001dc10098d1287b89837f426d073982d000000000000000000000000000000001660598fcd3ab6a55423138ee72a4ca7b57277f6ce140f9f992dd9934bcda78513516df0d309a0e8ea151b2742dceddd0000000000000000000000000000000004cce7d84e0763fbbb54376833ddd7408afe3f741bc2b7e42fef3789a005134cc5540981a15a9f256e0e541ac58ff3b10000000000000000000000000000000019c20a0064f89d37548e06d63d8ff4fbf3584d5bcc2fc2757339b7c89db6d5da76d43b31da7364259187ed602e79bf4f976568ab779e335b8dc67a64f15b947e69cd3896ff393f51fbd3c3e3a157543f000000000000000000000000000000000d7ec5a27ac44daeebab7658011624c441e45924cce97d5bda354f1daf9362f5bce2ddf57151fa07f78740a7db170e8300000000000000000000000000000000121ee325f4252ae5cdd3e3495f36492d68d9dbe13249039d1185760e6e48a789744b2a9946a3d6478a64b378f76b0de300000000000000000000000000000000014c6c5b98c1e214f78b82f1b3be4c32c5013934b1231fec942b5591d3f0440bf63b1505cfbb7a8fa78a85ba58fd4aa90000000000000000000000000000000016aaea3bd0ae91b9d18ff89a40ae27b68d74f3a227383138ed737d59c19ac578da03df83f04c8d962cb9d6f84a15302f3aa5eeded490a17b1cfa66d409811741643b7beacf312b9d6c8e7e7e63579c8300000000000000000000000000000000188e5aed425a768f89f5ce09b2cc909b28c6a0165787c8e3750fca8e8162128ecf62ef0ff853d206d23bc076335008e70000000000000000000000000000000001cfd330da0d1b5b92b6533cf5a8b6b70bd93daec4373f28d669f5e970a947fd813ab1d1272b61afbd2748922b87c8c300000000000000000000000000000000002aec750fd085c99c3b9c3af62b6deddd85e49eba0293e6e8160b26a3945af546a760b8f8f85120d6a51d22313cd33800000000000000000000000000000000162a109abce2edef753ca6351aaa9cecdeac20919681c672dbb183b5b26649e885ff081b9d3687f802dbe20fda43462af9f1f9313bf966ea3b8f09bbe9dcb66a7f9e3e94e2024c49a72ccbbe03fe4662000000000000000000000000000000000f7ad6a1dd9f8cf52bef02ae1e82b0d20dcacfaa5c169a485bf8becec8b51373fae851ca29e64385f0b7024eb0bcf9270000000000000000000000000000000010412a7a710f842fe836414e2729d0ff2e145709d8f7b5e3964af3e0ae267ac53dac3db1e6d2b7f7671ec34b18c844a10000000000000000000000000000000002d3b96fab0e3b8fe44e316fcc5e35f06dab83f2c531a777e162f7521cdd5767ad0b6f877f876f73d2ff663d9b71f462000000000000000000000000000000000c09a98bf623e82a4d2d4b63fb867fab5d3bb1f85a0669c4c11cebaeb357c0717a0f246a9ce4064b7351dcf1e77cdbd393be64fc3763d06111961bb208a2b858aa1ff181781dda630ca41f0d45ef2a9000000000000000000000000000000000114270d35ebff55c0341776086d893513595aca3b200ab98c8b586029b19a360a04f2e77e90d382174296443ab8531d10000000000000000000000000000000008b88849c3cda9a23d37ec9f4700904edb24be95fbbe6d9e20ced0d52208b597d44bb9269830a1ac5cda35d0c0a03c9e000000000000000000000000000000001144466b13427c10ad7679567067dc47c671107064fbb9bad287924c9bdee653c395dc2654caa5b3013ade932fddd5e50000000000000000000000000000000008e14e3cff3bb57f0d87680a0c09d745c7272bd3c216ff9fde7c03df2caffc27e0bfd9f99912855c156a787200752c125d2a2b6008a3b4a4cb3a8c28864214c7fbe154fedab1f9ff8c96eab6a5f28fd30000000000000000000000000000000015cda76d42de9fa86f900a5180ff016155f31b9276c617ef664202848d2efd2876d412402516c0c3d26d49f71d894acf000000000000000000000000000000001307fa2b963fc19583b7e4ef2e9dddbe93e2505e8f4f00ec52db26ab411002136c1f646b1cda71e19480c767906a6d03000000000000000000000000000000000ba87b08173c841a2bfbe424584d4685c39bdd0f83f278f9fbafa8111102aa3acfad5aabbe032c7123631fb8b454255b0000000000000000000000000000000016c525c1dc247fdf34344168b7cc245579585fdbdd6fd783cbe60b727cd11ee97b87a86647f78dda207c98e65c2ee7e6854e742ef7c76ad438cbf30c30103741f57ebbcdca4d6c4f14e554dd1ed81b24000000000000000000000000000000000403887fd4429f44f8da7f17ca072f867e88ac046922ebe3e1e6c4f9d8e174399e7648aca924a557dbf7b29c540db33f000000000000000000000000000000000522324700fb6b2c43eb5b39e0da94cb60e234369543f530ea47f4aa510ec0fd79cdf4dd3ae046e21d78b9c0e35107900000000000000000000000000000000015e946b90984257ffe3814dcc3ef065fed1504f0790f3564c8bfad4e97cffdb61c0d73bb0b1dbe78c4266c773abd56b500000000000000000000000000000000078f604630074ebedbd836c463f3879cd5d4a2c947da0e47740ec369112f4fedd787ae59bea69aab61b91f05d92061036f4f00b2494a32844e01d0827ca78b06f5eb40b6769f36a04f20eea229c305f9000000000000000000000000000000000f722bfebd55f75f3bbd0a55492499c3a3f637ead0e54270042fcc88853df5bc5f11a3677efa26d31c28368e00c8713700000000000000000000000000000000182618bc8a4b3f6556d79848f90efd6883df90806a8358cb6852bde465a27a70644ac5d5040d4f64ec355763f1a384990000000000000000000000000000000015f717739a1cbb2eab30e7b1bd9b25f57ad56f36016b59128ea1f2089f2d1dd0128b455b1b0e9e3b320f68a38a1bdfac000000000000000000000000000000000b855788d6b6a7748aa923dea3163fe525a7b43f4619c1eff3f9219ec3d98ceaf34b97bfd19aa6f91f7fcff728728978191e47a0b0c72bd17319063abde7df51498cf0c980c46946bf80ae4c9864e2e200000000000000000000000000000000120048ace47bc1ab3fdc07713b91a9223fe0fffdcbeaabc8a61351d756f936e18177f672c5a4db7b9dc29bad16bb7c4c00000000000000000000000000000000101275492a6e843306f2927b6ab540d7a5ee925bdab40103b4ddd885e444e6a6ec2d6e99c061284a1967797d8a2e9e700000000000000000000000000000000002c12f17a5dd2c56aed0d308367f37510f83c94a4482e5f632161dd0517dc2d4f46a90bbc13034c63dbd04fe4c616e320000000000000000000000000000000000e4b9089155ce2178f26b058f4bfef57b73aafb83b0b78138a01890a167709f79100a1e4d797c5849473eb3486cffa4b7baf8816db56c0a602cfb4caa9089136ebde05722ad4838671e45ada5c716f20000000000000000000000000000000018180eee7e72b6a4bc2e60555236da335fe05fcbe2b3cca4937e73a550aeae6274122ba84ace78eff84d323b4196f58400000000000000000000000000000000147659347e0fac7a16c92950ea5fd115416072f339d7de3cc0f00ef369f5122ff050d8515effacc825c807f7e19650e10000000000000000000000000000000017bdbcae7f63052af9a7d8bd71dc98b6eca7ecf5eee7632959fe56ed51278099690c534ec33be4ace4612b0f516794aa000000000000000000000000000000000d6fa233be4d6d783bf973cca3740cbaf0f719827d7f9310f38d1dd9d1c1f125cdfca6d12fbf6a8e8104f79bf30b00647d9ac1699117bb9b8b90e2fb709eff4ea0f7882bdf6acc6885c9458703cbfb3500000000000000000000000000000000082f3beaafa575e86be53b4fe7b93835b00759f921933402282e5bb0e643a812e0e4b676ad51ff2c6f5332d777641cc3000000000000000000000000000000001760b87bc4d2c13122fd7acc6d629c9f9db9bc9a2c49634aaf33e258ceb3106bc2755b227c6660a1df1d92c60067cf5a0000000000000000000000000000000016a819d7109c9a12199eb98537a730908a693767cdb35a69b4c7329761939afea766f0b91ae405e273227330761a53dc0000000000000000000000000000000009d14d7138440349e83f5ded46d18b886ef3cd63e0e5bfa0a8b50985142b21a4733813ec347e40cabe28e6ec1e068c24a22b6c1a24eff71f0fc64b6aee8d3d2dd0827756f5c959f68f1216c2dea58503000000000000000000000000000000001676d7f489219b56c198f8494e156fc0672ae28dab20021b7a6018436c7c0f107efd2493ddc2a1cfb3ad490ef146348300000000000000000000000000000000001106e89fc098ce7bd8bead5d7f6432bc54501370ae6544f34cfd996b3b610f9cfc7ad366751ae1211b848aad7d93d30000000000000000000000000000000011f8f0bd037365b5427e76d57b018c1c644034b28d06c8f68c59bff45eb4a2c4d761d066d96c13f7e73dfd80c81704a0000000000000000000000000000000000fc826b5957613f35bfa36d3ce088dfbbd06c8f2e88056a22a9f35db561e06fa0378ccff29ba8b81cc12c7a504f8c704c0431e6877166686239f014008959332d9d009a801be6a1e68c2de52ee264bfc0000000000000000000000000000000014f0f64acb0d9638a68278099abf5b5da3aa087792bef15192cfe3689b69b7ec1aefbfa14e659358b5410d98d2eedac50000000000000000000000000000000015ca79c92e98cf8314a2f6319520e1eb7d4656ca6e51278710cefd9c768a25691fc58e983aaf858d3c8d0ed73e2beec300000000000000000000000000000000007a5192f1dc906693568291f163e9632c53e1f418a87cd25656064adffbf31863680468f3ea451fbd22ac990dc870b3000000000000000000000000000000000131d2e3f6956da8941e8340259b8a15aee9fc6f23573f9a348ee9a51bbca1308dc54e7b4675357e3a9c5971be3a5c16af833a784d22b99537236fb07ab7b59918783e35b89fc9692d7f76a03e024c94,00000000000000000000000000000000163da4bf7e159e05eec90318a8ddad4a59fb02d7ae2fe18795d38c3ccaf797188fa16577e6a421ccfb12ba1ed573c4e6000000000000000000000000000000001256654eef3352b09e0027277aec042519d99eb2567fce2cfa50a0c251c12d3593604739128171bfc13b3bfd1ce8f9e8000000000000000000000000000000000b8a46123bc863bed525f97166bcb77504eeeb66d2db207eb8342a3d18f7f5a99910fae3e6423c6e84e437a2c4b24363000000000000000000000000000000000b73cf08023c8572f48c132add67dda7a15def638a01b198361b9d21a4634ba76ceed9819b37c12e24f148d255483856,240480, +0000000000000000000000000000000003113ba8b216d933fe0c81f23f75942c0065d21d8f009d73b1f698281408874e33dd2750fd4298367b81827cf6fdad34000000000000000000000000000000000b8a921e840fc665a786d826f83ca5a9c8f00e7c802bce5473a7d1ebf63e8bb6cf5c4b426153508d874064d1f1dade09000000000000000000000000000000001492ab584088d23d3b0d1283904f9a8f29f9efe47950c6e9ffb9db2123f3f9820b906d672fc7f97f0bd38b8fc0ef44520000000000000000000000000000000010d321c2538f92aad4631af44ae39e63dc06becd2460f0cee0e526328d167fd6cfbcf4edfaafe32d13b5fe66c009533bb16c1bc60e1a9be9a82c93b7e0311e7516a57d687d8907599918df77b3b6caf3000000000000000000000000000000000ae75d01481a51294003041afc4802326ab878a3a75eafcda43cf873cc65e300d28aa986fb82a2d1d649e5be00f956820000000000000000000000000000000017640eeef8982250f88a4d187dcabfcc9adc3ee9194dbc3c04c741690fce5bc7cb07cd0b7c3497191d9ed8558fd0d24c0000000000000000000000000000000007527fd8dacb81b8d1abc746688db6a47211fa71556155d38361921c4bdb2a9e9921a3a540bcf55c6dd751b84c04a1040000000000000000000000000000000008de9109ba354d7426a5313d66cd747a54df347f0f86a3c0f99e9e4b68fc79641fcf98ab39fa23ef6f1a781c48f53f76cf301dfca76a83c70552c9cbc9c80cb20f0d82a70a9d887b04b150fa0764ce2e0000000000000000000000000000000017331b8367f07756e789f7edce4d22f6886656fed78ddacef6987a2751dd3d5d49011a050e7b2a3e11fc8d90266c9d710000000000000000000000000000000016959c303e11f23392f95c1402d1d1ad7f38343c711e96f18d03f832f76e3e81de789a6eaff797ae51079b13571334d40000000000000000000000000000000004266fd13db1ca80196a91263c79d1583b717fb61fd9ce5113e4cd94c59e605152b244e10e364b468c5a561c6fa9715800000000000000000000000000000000026f67cb263be83f3163856f091e9346651c29d4634e242da53b22eb6e66018d235b0f30f8833310dff9f3020e5bd3811cfb94c4e029a2126a9cf5561c677687f52059e4b7f8b7e7e73e5b1dd7f42129000000000000000000000000000000000114d8babd11c81ca2b8a7e193afbe0a8fce426b83996bae6f77201870e51c9355c319dd86b985272f73e0804c0f53700000000000000000000000000000000016f5ea7610891d0e72975816c08e6e25a75c7c42500655f26efdfb384241bbc825358a21caff347d00c8b2391501d15400000000000000000000000000000000199c8c74a79ee90c3606906bbb8cc163c214259e4d0127cee3283bcd9c1ebe4090ca7d7b180201910d3f6f51566d3bdc00000000000000000000000000000000032c785165ad4c1a2846e15318bd7cf5b42ce8b675cb18fcc4232e28701f225f1ea384b276e7a38b2c9e2e8b112f1911d8386fe6f4303959e58165b422e98c4813b1bad7808594473e4e66df09698cf0000000000000000000000000000000000842c65006caed9b53add048a2eea89e1b4584e1deb4365e3dcf8b9ecb02f337bccbe5d6929ef8c20461847f171fd4d600000000000000000000000000000000100dc23e6c1c6f6756419a9bad3133bba052f408a424c5239b8528ad4429a2bce64b72f1463625f7599ce43865581e9600000000000000000000000000000000125b4d71333274a16e52829ad5eaaecdda5c206063473dedec5a8ff4424def70e6f650926948dd2158b403f985a3421b0000000000000000000000000000000006a031e3c002702837e4ad28250b85cd94d42cf7b0d765b980fab95edded7636d13bdef1be63e66682c4e297d0cb2b0302e1c432f3b55ae87ab815647f196be3e138b2f6e9fe7acb9459650246187eb90000000000000000000000000000000003f7091a25da7d5afe6fa6b254604a1abe7a0c6ea11cc1a4167f5f648aa973d888383bc7e987b620d23e688868d318360000000000000000000000000000000016637f888efc3e057227cbefecb3037aebf8e330c3a794e51d691e3bc064237b98351beb746868aef977a83d1fe163ac00000000000000000000000000000000126d2884487984f851d1bd7d61bdb803321f263918e88e0677831563bacc9f5207358d1e9c76a5a25a66f0294f459e3900000000000000000000000000000000125c61b387a4462fa3bb2f06a4cfbd7df082d20cb23ee974aece2ec9a3b0c084d13a7ea83725a05d9f31b8033d2888ef9b0cc0ac499dffd627f5d19b87817dcd67e87561d5244a4b5698265f8c5b767e0000000000000000000000000000000006cf2bc7c691c4f8a64d0aa1ca3760d715b3188a2dd299ab09c723315acba8b0b4bbee819ba06cc564f0c875a63a415b000000000000000000000000000000000bded3d695e471f30f9d723f55826eda112eb0e3fbfb9a377cfa07d6233ed84108b92a79bb491a2971e9afdf83db8e9a0000000000000000000000000000000009b0e9928cb267508d4f9444c6ac3dc6f64f49a70c82c0bcaf4022e97854e5d9ec2612a2cd4d67642dc0451583bcb24d00000000000000000000000000000000009347dcfebe93a2f7674ad02ac48794e7cbffb04dd85b0c8c192fc85cfb9cef40fd11def6f63ae9a923960424eac6a02f3875f81fd39c9b3ec74eb269903dba4173d8eb0e41a196d3131252207ffa040000000000000000000000000000000013e8215c7bbdca445555c9fa0ae44e1905703334bade3294fc047ec262b9e4903880d52851967339eeadd666200b25ae0000000000000000000000000000000003b0bf4498103ac03601a8594b154b59a2a93d663f98ff8dbd2c85a1902e572a9456c629a12651aa87a1262102e1c770000000000000000000000000000000000e8bfd7d3fa0f773e6bcfd0d43a5c436862d1cb6a4ed715093c6782cd94699090c4bde597f65768e963fd0f8644e09b300000000000000000000000000000000064dab4d0d0c6b94c58b067337f2fac7d0d922cc822562b6bc941a794d96aac5ddb83d1d5844440d21d0a72a69303b8b2d8d4341822dba68c6fd58cfebd07b134c1d0c4e32ff63f7d59addff4df1ec3200000000000000000000000000000000098dd9a20f84fc26e78993a9de4d519aa2f8d343fbee501af945e5943e88425d29beb7ae54481b04175a07bf69b260a30000000000000000000000000000000007ef43e7a56e4e7d532420e152ce566d9055eadb4ef13d5698c49da905a4977fa8a7d3f51c8f5275582e1647984be61e0000000000000000000000000000000003755ee4432ea90f2197c7cc2e191dbbf7950c52a2c1b723f26d2aaf7a38c1b97efa29a312fed599f1199cf186400adc00000000000000000000000000000000150edc463f0a55fc70c2ffdd1f73a3abbdae459eb16adf79e96d18849ca638e6f41c6805b73755968be5cb110d81faa4efa3dab1d7cdf949bd938ca6ac371f953b3bbef1aec7ae76bda37db4c940b3d8000000000000000000000000000000000f7149602cbb3e5f2c5f8edfe59fc0fb8e1f03f89ea192bfe3990d87ccd28d4a80d7cd3003a8cfd669e1b6ff7e3cc5890000000000000000000000000000000006ffbc965bd06de07d8c0a9db8db5ab82d5f11afa1ad8eb92ed4453489f5899cc8c46ff02743956bed81229f64cf6efc00000000000000000000000000000000164cd3271ace4809eadeb1c0f769094272f3b66968690339bdb5da92e920cdc80c9d577ae4fa5b6426a5a6f46fba80bd00000000000000000000000000000000098f0a14a511ff424847d2b4d1b80a049b1f05ecd40af96b7a81def54486e4969011c122ca7dca3444029daeae2ecfc79848d3c53632dc461619c8c522013b83550ef3dc7fda197ba37c9cfe4340f5a50000000000000000000000000000000018409c0d0f37f4932cca87e24eb4d55e75dc98f938420ce036d43689fbdbbd839dc608b21d12a8af1d0a780aeac6617400000000000000000000000000000000109f2294669422a4946f926b1f106c2887893a042e3bf900559429c7fa484da4909216c8dcf826871534981021256741000000000000000000000000000000000a1ded19846e603b958d0bdcc9b554beca784b017d2a35ba117890fd0dbf729428bcd9823c7a378706220377c82a215c0000000000000000000000000000000000eafc89e30e4fc0544497e27674ec5b37ec0849fb382e608e09d0c1c94cb78bcb96ef4ea48e374aad1038881706fbcfcbfd192e917f2e0c4d6253c4e4755f30812149d1ce1ee4ae5540faf1dbfbc13a000000000000000000000000000000000e02cb3e099792ae7508321ce7afa323fd499de90c4006621ef5ce1054d0c934ae058a97ff8aeae0c88709c4d8ed0adf000000000000000000000000000000000e19318f5890320f17d5243adb4683a97e3e9763102c4fc93e3c3e3d24f4f61e0500be916c249dab00094b4ab048fe99000000000000000000000000000000000989faafcf6156472368b282313e076613cfe7ff135eb131b49e58932cbfafecf6585009d1f17ff8941d7f871be23e9e000000000000000000000000000000001167419d097ae8b96993b2e67da79b658adde1e12e43c71f27835845c7077f385612158d3e59fe2cb32b9418463e672679eaf11b3a30c7771ce63cec214416d798de20432670280d997b2f0631007d63000000000000000000000000000000001579b7d03d3d2c8a280e8ca113bcc98afa6a2705a5d228d92807a85cd5a1ee97510f632293a478c3fe0bd383f4b69cdd00000000000000000000000000000000107cc2e6bd02251bfd565b4b848adaa84babe9d4f083e827ceae6bacd9c9c221f0dbbef53278175bf27ebfe5949fcf8c00000000000000000000000000000000018d187c566690e4edd8d8abe5e0a448e352f622c96680378051228b6d081a4914aa51383326aedf45e351612ad6c5d000000000000000000000000000000000197427117a52f82aa6e931ecb0c5ffeec7f73ee8f44c5816935d26c06cc8285200ff9240d98cc244708e00669460f98b43077447b67f65e16a8aeb3564b2d13822e478dfb4a82a15a1c8fb7cc8170cc90000000000000000000000000000000019bd947df5a437a7f1ca2340bec628f2783cc1760dbc4a97ae10093aedd9f64e25ba79d9f4ce678f4fec91a3b1eef2d7000000000000000000000000000000000770e0c39988c9d8eca076464a3e10e274b06b1d2f6230e6dbd8dd59dd9c062f8958c6870c44ff196341bb9f65b8db38000000000000000000000000000000000a1833ef19e2b8e31577e5cd26e0a7fa46a5d25355d8b3dc0605f53714a60423556f3bcf17649745695f68f26570de0b000000000000000000000000000000000f449aed4120f3bef05506f2463f4546c7ea67b9e9110d3942dc256400d063dcc571305b1d4cd2bc3f18cf25319286e8eb64479b496c17d0587f6f26c62752881b6a9228643e8c43f21c441eeb643107000000000000000000000000000000000c1f9688ea64165f894e85b21761a9b2bfce891070103119ae71ff7acd164a57b0e054319631180c22f19eab8607f5b40000000000000000000000000000000005ba18dafcd3552af464acd469b133896e90c9ccd7e3bfc6e05db883f3c6aa1cc4610ec47f6354f6a7cff4385c56d2b3000000000000000000000000000000000fefbd9d78f48683b378d2d6311bf7ffaccaf7aa73a0bb4ce019a0c1d2e1673e52c724bf3a782729ec23d258043efff5000000000000000000000000000000000ea47ebbe3e858c5fcbf5b0cc9017d6ea23bda36e235d2aecbec827fdd2e4b042d1108d5f645b6dcdd786304e6bbf81b52b42f75aebdad1bf433917c025800c4f4e985cc077db3ba36f7484f95764e89000000000000000000000000000000000a313e1bf72d9a176bbad609631192c779e94c293463507edcd1c38bee8f33cfe6104d7169457ad5ffd9f045fce1cadf000000000000000000000000000000000af8db18938c51742b351fffddd74bf1137092ecb50a7e749391bacc9c1a19c7b9cf235b52ed577e7855d4ec1fadd940000000000000000000000000000000000febaa128de79274ef11d3e6378809d5b319796c653604723693c335eda175014b645604271429e3d449e756c85bcf6f0000000000000000000000000000000006adb29cc4ba053fea56d07225d2f7735651c0046f5cbe4a350dcc20431ed9457651d46a5d23d946959cadfc5500b7eae83106e9ea63791eb192e7a035bee27bd049b3a37f080076146eeeea6a769384,0000000000000000000000000000000019a5b588aff8853adcfa959afc5135807d00196a75acb3536ad4fc9a9df3803d919a2de7cbe9ff762913225577ebdbf6000000000000000000000000000000000ac8bde939ba2f164795804d96dfa8d3a1c4d9e4eafb000cfccd956c24f4d594b30bbf961917f625c86270cbe164cc5b0000000000000000000000000000000002de09fdf52aec0b91bbe99fe2eb9043b19975c6fd503815264ce030dd5e5444f0f4275ac9a07a49de775335d52ea3c40000000000000000000000000000000012457bb55876c482e5b907c765b476dfe6ebfe8e588cb7f630e58f78942bfca57e6c0d5d7b0ce80e48960e297863d212,240480, +0000000000000000000000000000000008f3f1f04fb80a23d348e3e25dac1d732265fd4a71ab8dad3718d268e49c79578e8e1ad1720e70357439e57df0791d64000000000000000000000000000000000fa4c15c76e395fa706a55d1909ede2163274a68b3e7afb8d2e0bb176f60c06f5a921c9ace35bd311bd79ae86340ba5000000000000000000000000000000000173633369e00c8c5528bd5ccf95c6af8b012e5a31941c134ad4541099c7c33c5ffd29a5a31e18be720f7ae85132cd6cf000000000000000000000000000000000800f5eaf7c8b1dd2787305ecc637a0bba8eac807a7b449410e48aed3dae2b4645b8459fcdd477fd92fa5ac6291b800ea4d710d2f632e3ed0ef69fa0219e16ba30d3efee99386f1a5c921f4548ebf64b000000000000000000000000000000000ea8057b2d609ac2130b21e0b4a41f0aca20ee7751f55d816ea42cfa4612b67c3c556b01b0bb1c5912a74c50a420407f0000000000000000000000000000000007fbccf8ce8d1a92756fe80b15c7d9342af4e166d3c1c7e35ea2fac34851cfd983633270c877224749365720fbcea54a0000000000000000000000000000000000885e173b73118721d28fd26f3a9c562bfbb878ce71091d7ae4b37c1f2625777d67955a2b7458af71077db7557171f2000000000000000000000000000000001754edbfc3f2af94c92e6754d6bb096bbc4b39bb1128dc6bba8b4d4d9fac6649598be90b06b9d5db44c4e77c0cd1537cbd9ae4597aaf582857b40096360ced0f044ea172551c6f9fe3a15e0ce290b56b0000000000000000000000000000000008a1a751b5f9a08e2bf5b2a58f62f0af6b8773f88e50f658ed220c0134e83c7031a288eb50a8a35016d2362b431d809d000000000000000000000000000000000d7f04d4a6c36cb3d105dc3915cd5d57f56692132681b3abca4b00e078c700931848e34ea1b7ec663f3886ff766fef41000000000000000000000000000000000a06c3ac81d6d0466e1ef21115150d04c8bd6dc3e4078e46eab213203c3226bb0c6500ae4fda591d6b8a791de598edb90000000000000000000000000000000014d849ddba2fa79b6a7107efeb46e9b6231d65384c69ed183acfb922d55b790d4fc7546afadc190b76f7da00103ef565efbcb4bad99b419820eec388f6f62ac8d87866d7beae6d431dfa48d4243b4a4b0000000000000000000000000000000014dfcb5fdb38cf09c1ecb284dd4f2de0c3d70f90d7c167a442d84e9a29bf43be62cd319b2dafdb6ead2c6596443a00090000000000000000000000000000000006220fc05c53f48e7e4104422b0660ab67fd88a695a201366de570f0ac0ad30421d5e37a1575e6b5ba35f45b441b297200000000000000000000000000000000077cb8ec1cb83c4974f6452ce0de630afc82e283eeb55d3b7e9969bb44bcf0404deae617393f82ac228b836c3cb6f95a000000000000000000000000000000000e2bdf539eb45a125112836008effd104e881aca397457004fbda4a40d152817801bd259434481f0509ab1838cdd1fd060d89acf5b49fd1f70fc54756c4bc1972cd8818e36efc37b422ba6a9318fa134000000000000000000000000000000000a09843630131cc6feeeee8aa8214408235655e4733badd6fe20c5cf1e45f6a61a5216e0cde937799437962706d3bfe2000000000000000000000000000000000ff518501614ed4a199ca9e9aad4e8efb8e9cffa9b4fa683093a49cef4669198a7893db998d5777f2cc8f4bb130c84360000000000000000000000000000000010ea66fb5224f4508ec100cdb611be133c4895a8de1b4c475b097494ff0f1ecdc1bf8fe467c630233cac2ddc07935fcf0000000000000000000000000000000009d22c0a45c82b0a19beb94eda0b93cbbe1f2e5f2d61279e1e1c93ba073cb766f5637195e6964a4814e588e44bb03f03386af376b9b393dde994da419d1f7aab60023546647f7b069ede939386bd6ee80000000000000000000000000000000015ca795fc7f0d169ba8abdafb1dee80b67e7dc616e824959f84c61284d6b2e0e8b9f99b414f5bd96d0e59b66ee706fd800000000000000000000000000000000042f473d1fa228961aad526efd003461935954abaae347dd6c9bc7fcd68b5f5138e57ab2a160cb19d1983089b58b51ab00000000000000000000000000000000188eb160cb968b4b048ce14bb72be27c228df1a6c014fa7dbec09a30aed8c71e8da59d3d5f8073b6a7d70d94c0e59dda000000000000000000000000000000000d467e6b05f033f3923667a82d5b489a5c90c99c5f68078aec700fc67a83d9bb4c09f3f00b9fc2cfd62bb098f885fe295ffca78eea65c00e1128f8dcfc96b39af1c4472b461ba5262307003bc972023d0000000000000000000000000000000003bec45d94f3073b2ca54d6332d36fdb8f5c801d9f70ccf6e3666b66ee06c0fdfd741f74cde1997aa205fb0318c9c4760000000000000000000000000000000014009b777b660264eedb35ec2e13ea586aa9438c47b3fbfd095ea3d8688a89c85bb4052bbd3edd450c19acea6372d0070000000000000000000000000000000017f26d3cfcb40fd6b4f3f1acb6d47a9b54c232aee484c7a8992a3d1accea794dc384fccefb0418d43e1fa7b399bdacaa00000000000000000000000000000000153c6cafbff3c53114c96d8caeee2880dc063d7db5edf5f14157117387f368c76b739553542bf6a9bc4ace3694de885a92837b4314e63ef5a153ea2ec4bd373cc3cecfa3e892c3a12aaac8ddcaf5905c0000000000000000000000000000000005d2481438c03493efc9f1e8e9ae6ab05b7430f7fb82e108aada0e886b14d769969d54b17b31e5bbb63d40836748f541000000000000000000000000000000000971deac599b2161a4baf1178feb81fd4798ad5cb063b1a0cbee7cc33b8fcec6c3f43d1d46d9ed45555187db636af99e000000000000000000000000000000000222acaf8df647744859e04104a5fcd546949feff6244e192a9031fc838f368aa465a3799779c637ef0087183f30731d000000000000000000000000000000000b8e8f1889816f89401b070db687aae47f7264c9be192a8d6e485ee71a5a688070d57ad8928d09d9a4925f1050e2c69e127ef2309c699a3602b0d86a070baef0eef90f539aac3cb6ff42cb19f284bd99000000000000000000000000000000000b8a5b0dd422469a8d6d7603e9f3179f443ef3fab0016afd94e93e2ea9e84b332da4b59f23a5257b99460efdf7d2aca7000000000000000000000000000000000c28e7068769c3a79bb8d92c3b89eca5d6eb42e3e18c2a7154f43a671f8670f878c4b110990c2e2b163ba4d1155319fe0000000000000000000000000000000001804302246fd07d86f4bb23f610af38deba8e324cdedbe5e61cf0941281cda8fb5dc211fbc0ce6fddf30aefa9563a0500000000000000000000000000000000015813fe0d6bbcfdc8e7e40b6141db21e1b490d846ffe82eeb3edcd9a024315193259612155b0179a4971e205738af74ba0f9a93c2fe35877ddccee5da39ce5ae60a6a19e72481319e3b3fa2eac614890000000000000000000000000000000011ac1ea4dad0f650fe0844ac3ab9434ebac6eb70a5f77c8f9c892cb4cb06639a15c63a9b820ef8f7a720040ae5b9e49500000000000000000000000000000000117da7999552e7886a25a939ada0944cdb15b5c468e9d1c3bf5b6af920e470bd648d24f3cb7f91e670f57a52cd65f7b3000000000000000000000000000000000a24147ef5f2b8ad888899c1db8df0a601eca9d76f1b934b1627e7eef3efe542f51205b96b5c00916688579ece81336900000000000000000000000000000000151863d964b12287ae4278c905341124985410f1ad6a72bd5c62230b7d8b0cddbea0c62cb2a7147afb5bfb03348be53363da2f227d636f10e814e360c2156e686e26ce3401dfd15f47c4ed277d05353f0000000000000000000000000000000001d32ea5faa6303c530790146df7cd5cdee93c0933b4cbc1c2b8030bf0a8d2600dba1907df1756152625cfccf8cc7fa90000000000000000000000000000000017b05f549751d090f42ce8a3ac5d959cf988ecdc485f51734d52c40a3e22a097917345978209fa74a0a05be0a66e5c6d000000000000000000000000000000001481fab7750380626b174602d9fcbc97555c516f4410193d2849443cf25ec22840e4fd00b225f98d81b38619e8844ce90000000000000000000000000000000001d56434066551c5bfbaf8c9007874abe57a6f78de9355a297bc117f2bc5e6e3f44b248037f400f7caf83fece0c00ba0ef79e3b6ce752d140c3dfb2007a08221d989038c921efff3bc4e150a6155a33e000000000000000000000000000000001667f1400973598ad3f56c2e49dcb5b556cc38ee3e5801ac4943f3c4554205d8fa69831e582a084aae1ef584feb0a1880000000000000000000000000000000003f0bb26ea548e498f05a5bbda8b8e536613f10e7165607ab77565b193f794664c8ab0a5ae2368d7483b77bc1173d14500000000000000000000000000000000176d8d294b4d975629c6a89bd6d45f9c3924a621259ab43d33a3d5aa1f423b68e3cef96dc103494bbb9036436c170f5600000000000000000000000000000000002f8ed87c584e69de59cdde02b6de9816c31a6efbebafb6ad9cecaf266f5bb9c8880f062dbc9235c91c668bae5051f4bc08091af8b8c6ea5c26f1a7d795132521350d774042d3a8c0523e86fdd23a3f00000000000000000000000000000000085fee95b859c52e44fcb2900a9aa590b1a5c2f981a388d6ad7b81ffbfe033f648c4a84e2119cb0484e178ebd3e220d100000000000000000000000000000000171e6ca074aa97981d2c2ab000a8bd12cbd5f5d574cb83158a6ed734e8f9b7aa4b74aaa43b7aae31b3f4fd3d82fd30ea00000000000000000000000000000000004fe6099a52fb491a0624a8d787d95617f6c64d16d20d1b3769f60d4721f7af66d7e3e905b3e08b2946ef7bff4806ed0000000000000000000000000000000004d3d1a56af91377ae6b00e192ad64fce6dd43a37592fa8706c9344b3d96b1f930e03be85a5ead3007f9016255d2df7570363101b87d685aa7314f6569fca0775bc6aaffabe136e6c336e8fa43dedb8a00000000000000000000000000000000155830eff04ec2f4dfca4f73403e408a68830bc031555433fd38ab3ce1035b5f882bcd6032aba69ecc43625546b4a3a8000000000000000000000000000000000ed5b698b1ae23769cf5b6dc2e39f8500fd8a881eb43452d67c6b84ef9f0b3c7d81db1909b646e92412acc7365923a940000000000000000000000000000000009f28ec2f949cddee9bbe2fac12c2c029f4e472afa1ea56d0edfeacdeb9f43a4a43b79ccdfbe8957b4cc16bbcac1857d000000000000000000000000000000001474b435131301db9e232ddf54569ba99bc476200ceefc15e4aaaf1a574c1de8bd2d63c8280e23127a7a036acae223b1997ff3852cd97c3a65bce9083ff66197fd5c70894641195514d556102f091e8800000000000000000000000000000000168475854829d47356d9a8dc13a94e8d169771ea0070d9ef45e666d5378dd676d603c2eb57a3cda072c11e0926b02d650000000000000000000000000000000008b493a9f4c19831341782fe6285db2f7e8250d72952351ddcfcae6f22a2ec0935e29d396ba32f72dfa4067d0e7ce7cb000000000000000000000000000000000d9e72e22f2a1522babc5f2e8dc7857ee690f60f7843ffe15a080d56bf63db86f124cac039cbfa16fc8ace4d6268a1180000000000000000000000000000000008f3db1f6c0e5e7b3bb27abd34bd877cc3c373c681a3abc88eaa91636924ee477ba5032801dda091dbc51936a90c84685ff95dfa306f91196849d752569b35812e1db7946708cd06df9db9ee75447bc30000000000000000000000000000000004e34bff7e9e3ede02df950aa0e8c5f4c5f85cd3be89d211e957a7de95b8e321cc11400c3dd5b2ba0d1a3008462cebe7000000000000000000000000000000000fc1047097f01fd2079e6357ed379ba39107ec41ed6c6dc17fa6248d52be2b1cc2593c9735a6cb48e6d6e0434028f755000000000000000000000000000000001896fc5e990aeb416cf21ccc73f02c41d019d0a2679bd533d0811b7c16ad3ad3a6988170fb2db030b5fa7c3e4df5acf4000000000000000000000000000000000b70e14ce1b54d7913b9f3782b2b8ff249967a6b871dfac7f54f959954febb2783cf20e20d1710e5526ef8aeafecb3d603c4308f0467520343825a91c0421f9c9c9d06957fa2fc051970f14085339e26,0000000000000000000000000000000008056d4dfcb593c10a877cc8a4accbf58f360256b76876ed2b33a07be3110f8e295ef459dd6fb10d12bd02a8276351f50000000000000000000000000000000005686da1a0da89074c6b13fe9913f5cd49e0ecfea46e06493510625f1393ba4cc2e13f023fbc7ec2e130bf9a4f7483ef0000000000000000000000000000000010cd660001f65876db5b2cb1a56d85171d4cbf037f3bfb0e01bf4430c479237cde5b6cce5839a4fb22b406846e757868000000000000000000000000000000000809d7711211d37df76cd1cf71001cbf02c76df67c83e4eccea3e05b11d196b5d52ad7c3d0a00d9f0ef5b018717fc3eb,240480, +0000000000000000000000000000000005d6cf50e3add0e5ac3016b394ec363d6145ed66ef56b07bcd33c90600e83b4277558695222062e02d1e2b0693858e73000000000000000000000000000000000de8caaa810d4ac39258e3d1656bf7f2fb7853a5963ecb989346abe90d5d35d3662f6e283cec7bc386a6a8638ac395ed000000000000000000000000000000001849ef86eec16b0612f214c5ed52c0d50a90bd65b623402879f2654fc578ab680d49af9afdeff546702304597a20f1fc00000000000000000000000000000000168707730c4e74eaa4e85e48e7239b9ba3e8cb74c24b7126a685da0fcc963b9f9180e252adf7d8c521deb1a2ce0099582849fab097a4f71bdfcfaf435994a0c6ac3671a4a9ed0402010be83ff95228fd0000000000000000000000000000000007d4fed2fbd9e9dd19e0af5c52637b2cd337e0bfbcef0384f182a56189a7e7304b9d2144266ffa79044be90cb7ede1b6000000000000000000000000000000000baabe8c23a10cfe85494c693d1b09fc8e43ef5f233052d5b6294dae14b4ff9e5ec240a1c00a16a9ddc27cf7b53bcc7c0000000000000000000000000000000001c595f193229da9acff04ef67ca444b0cec75db5b2c1921502e37eebdd2bb43ef47290fc6f1980abc75ef4c50034df00000000000000000000000000000000010fe7f3110ed3a240366ad7ba31d56ab993468dae2dc1b667a46c7759baa37b865d02834e14280a2ccc062af5bb2b7d6e6558521e301eabf09e80a509b46cf8ec118ee8586f4e46a7313a96dc37ba69900000000000000000000000000000000150350d8a771c79268606d6a5e1c147dc9d92e63fdc60b20be688bd52eac697aa5d90fe1b7b91321b2af87c47ac0d5060000000000000000000000000000000000fba8f4da448b8f2bbd99014bee2f9c581f2a974bb0b54f41a84a7fb359e9dbf88ba59a705504140284d486241e94e80000000000000000000000000000000003bb92d6a603bd93f8e987071a7385de68d10cfbde389eaf01ba6480caf1ad8aea03c84d1889b7d5b5c5f72e62a2d75a00000000000000000000000000000000193342a9f15109367030724946342e564507b26971caf75190e0b209e429a948d8b21ca16041a01010b68222db66a16b8f2f7c525fc0f353700fa823a5d32a93189699206c5ba5ed271a158ebb47674b000000000000000000000000000000000bc4a46eea57231cc64758560e3032a8ad8f1907b3cebd7a8faeb98c4216cb8a0c8fee09929ecefc4bee7955f4e799ba0000000000000000000000000000000009f9486257ae3f94a2ca48eb203e2ef44ecf866ddec7824e1a4bb3b89b320c38b3c46de8202480628c53c415433383a3000000000000000000000000000000000d8e2b5d0825b11344d16dbb2cc614c6b84eb1cb43f70d70e272123867b731775b429aacde611318b2700aa567a84c7a0000000000000000000000000000000007f720929287a70873e9f2f2031b66693eaa6e604668219aa5aff3f50e720b34c5fa3f5c66eced5c3e86e8b34a81b984c7e8adc0f0a042a32c733b5c3356cf4a7d648be51c1d78534ca65dd43a0c13e4000000000000000000000000000000001537ed68e203e56f31498efa314322694ebd74cd1dcc3145d534299fbdadd4256f20b9f74b895931a60753bde6ff9030000000000000000000000000000000000935c6ae847aa7f47bf427988665e5e18a32aa869e196cf9d5bac1349c650219a8d20e01bd8d49bc7e4bb8d464aee84300000000000000000000000000000000013e0661d7254428861cc3ed47c3fc9daae8b86db35d1c64f8ced3bc18a89202825f13163ff94ac0ebf046a0a99727e200000000000000000000000000000000039a6b0b2cb91e460d50eaf9600c29fd4f82a81c283ba4fbd9a7d103efdaeb1e82947f5cc1a7a1112ae6344c51119201650081a6720845a20164ef7c06ce1e73286a32dd64efbe57fe46765008dc9dd500000000000000000000000000000000071a6b0267806f2b9e0ba493960fe0e43f135c739a54c8daf5ef9ee348a281f19876f80c0dcea59dfe9457b49809c12a0000000000000000000000000000000009ac83690c30a4afd78f94b2493674668da4efc84007d2a08fc78bba271ed1f43e2a9e5909149bf0811c44dbe07c52f9000000000000000000000000000000000f5d523612fdb2e7dcf5da56720057dff6b0b80707cf5924d146c0c072edc0635c73fb04256e06c7c9355cfe77a7af0700000000000000000000000000000000168431fc569869ebba9b4a72371e3df232545b5fb95778baf3d9105930d9a89b4cb7eba430e9162a5589c7465e54ca3ac067d18b95591f7f14261f95513e1990f5a4f6908f94a015a93fe379726d5120000000000000000000000000000000000ce836522b983fe3ef6a502a0de4c599fad8a36a60d914218d5d2cc4d56d69eed8d27b2d50899639d1a0ea9dc7597f900000000000000000000000000000000014110ac048ac4c20e53f2214df8c06d77f0b3150077d027691cacd3715d4630a387d5819ef58eb1bce2e8669be330a3100000000000000000000000000000000178e5cb42f56df2f1b255a028a00df96c02eab0a79aa0ff3e9772fbe3eb62174728259b3a15e356e6d9666eb65fd6b7e00000000000000000000000000000000045197f136649b61d6e0e7b9a56674e769e2d26716ee7a63fd2b83b767a9ae96694e9cf81375d0377a1b27ea6dffaebbb448bb01a1963bf74e0fbf99329005af8e932074358d855ff43c213e02bf26bd0000000000000000000000000000000016a6a58301c243b0c59d6934bd926d6440b87b49f004f411ab0fdd924480175052f63f594c18007359055dc776e7f2d300000000000000000000000000000000176db4845cad46a13d9dd0f4077cd22b3458f64084c7325e9885f8ca341ce3ccd4f634f41efd6a70f16e1f0c9ae103a900000000000000000000000000000000068ba68f652c4f072a64d56618f93a1e148274b1b835433be878c06e11f65ff45b7cba0f67fbe80327abace68396da7e00000000000000000000000000000000047a699487964c98453207c98cc91c980c1ed37dc26e17748e6ee88e5f4c0ce424d87c82ca6db2264dc8aa9e437a5f25441fc4cb1ea8f86af8839aa40c35c0706f3a159b4bc902347009f744b73cee35000000000000000000000000000000000bf7e4a9751d4e3baa7ce9906f4378764e5384136944f6d3f3074dce66ed017759783c64fc381f0dd7512d6f6e55b4aa00000000000000000000000000000000006ae2a4fda156818cb5ea6120edf7ea39370eeecc3f306890f47a6dcfaffccbb69fd21f33fe491b7065838b277ad2b2000000000000000000000000000000000d3ce00c2f5febfeb232dbbb74fb0405bab86474d1d9c545c93b65c7892bdd58aa56225641074ec9b428efd9063085d00000000000000000000000000000000002552a8c1848fbefd6b039d6c4bd47c34dc34ab307163c4f6d337946f1d1b41aff2f7e37f5fd94012f0ebd21f97d18a83020a1ab853ef2018976e43cce2724105a2526b28d23b0226c49ff3d4a03d40c00000000000000000000000000000000105320cccd67b6ea78e96e66425a10a6911d2d348fac3231af583146273609fcd7fd27a19d4614fbdf05bcca0f92b927000000000000000000000000000000001204229ee1f66fb5a5dcc4ee978327e35d703ea310901be9c100af824e39d24a028ef8fce42370e5d734df02a26c145e000000000000000000000000000000000dd21f31f116681c1810bc36141cc18096cc113faee7db2c189abb7a746e398e272fa0cc61286aea0a5ec4008c8d03b60000000000000000000000000000000007911297718e98588844b9022c825bc4b37f2af30e1fc2d9cfb58b4500dffc8e9949afddd051e971fe78d4e1e7ad1b4a82702398b8c95c3a8cd163a8a3cb2a7a04030ef99404c325115e9a9312e8c1bf000000000000000000000000000000000760787190048e6ec8bc3bfc368f010e2f8aadd53164693a62b0d7207575bb2597bcec4bb382c57fe9053e90fe2f7159000000000000000000000000000000000ec525abbf13da64a8093c5d3fb800440f4c1fe798bcc71eb97bf2e0aa9e8be4b08afd2313f9143260058132d2607141000000000000000000000000000000000aa12c902084eb843daf7b351989bbab7a86acb62eb54eff0c7599bacaf44653c9fbf53f47f6ca72d22ea1671842eca800000000000000000000000000000000082f330d9a693f2bb9386fe5274aa79ac73a17688821f3c705120fb2aa76903627786a8614053f21a93e0aeb555de64e338468a325384a9367c90bd0450816a22849b845aadaf187c27b3f09800e791b0000000000000000000000000000000002ce7f08b8d5052d8bd07090744ca067700eaa1db61dad3e5086661850337bcab485c15fdd36c309a9e5169fd2a2b55e00000000000000000000000000000000073fa834cb4dc4ae120e738059749bfbd86b9e64fd71b1d372dcec8474f3341137ce8cb97a38955e9081f9bd5e07ab830000000000000000000000000000000001568df6806d8c3cfc9231802ebe5edc5d505198747a0adc24d0ac59f28d32b7b379d1f2c6b8352389057c7465692ded0000000000000000000000000000000004fb4b08a4fbfe197e924be3f7213a769a2bcd24109ae69a32a197b6212c5f50dbe8f46f5ab6044a4c779cd3e09d13bdd29136cbc4764346e7ae1af92fe64560f453821f96f32a42a2006b6edee75021000000000000000000000000000000000c07ff656904a47b0c7bf77540abc47cc6eee3e76b6ff0983151de9468ce3a860c427f3d5d489d096264159ab0567cd20000000000000000000000000000000008cce094ae1d9fff246a0e76cd67dbf9808c94554372fc4aed4879487ef240e45047dc201dd8bbccb613feb9c4623a0b0000000000000000000000000000000008a25297940a1bca1267fdce450b0cf43105eb4a21ab14562116039bc8379b1a3f58a7c117e9ba735bdec40f772465300000000000000000000000000000000000ae17a9b1fc3b0b7803ef48cb26643e8e78ef133f94bff5f87739182e662e2641e72383efab1f3ec58fa20fc816d56c675a59418f1462247d3bddda5937553e96d854b5df64a68145a193b2b1a7eb250000000000000000000000000000000002357e5a04b0dbd7f9a1709bce9b7afa12b10c7274b440b4dc3bf51a801d483804b1b4b9a096c3205a0e2aa7c0100c6e0000000000000000000000000000000002ff20af67f126c80293e44bb3c9ac74a94586a2de4146588c7ee8503530398eabc30f7e89322727739618087fa55de50000000000000000000000000000000013c6d06ce509fd557946479f2768f62474e6db04b2c92c5cfa86c023f79d05a387bd4c9aa618888476d4ecc93ba0995e00000000000000000000000000000000000fa477870c952f7506b879b17fb0a1c31771ee832ce0ab21a513fdd91b7a2a78a03d297c55558b834e255462b15520544a345719b40f973398a6fdaa2044037cacd7f6c361921c62053cd51f2e5ff700000000000000000000000000000000181336b8fdc03c02e23cd06ac975855caa2bbc1fe78a2fc7a9d0963c90a1f1f9330d50b88bf2526db6132d336ea5b8e6000000000000000000000000000000000f2d94d3fde2c0f67dae5a6ac12f713ccce2621303762e01961843eb9924d1d3c732b4c977d8cd0e5668adcd7dbf7dcc0000000000000000000000000000000005ac9ecab11c3368c75b0d396889dc34bd43ccf550d817c1dcdc7143c15d5c0e241add37328a7bd8556fde87d75d67fb000000000000000000000000000000000184704eeebead43f85b32d7f3efb9b9469f3ae10b73a2f034bd33e6e66da0bc36597d8e29ef5585443a655e24ffb68fbb38b4cd72eb18c3ac87860aa58b4b439712562f742f112b5d769415e9c19d0a00000000000000000000000000000000046751743f8f747e378738c265c1df3a368cd9570a2bd7636991045974c34039161fb0eddc6b813003e0908915b402170000000000000000000000000000000003341bea6cb81fc5e7baefd386a518d17a6f752c0e1ace5a9580a1b1649f5501c7b4639ba0cdbc33808d78b025a31f190000000000000000000000000000000016e3b9e8e189df73574a00a721440379589a7a6df09eca9a790e04c729400323b2110f63d547d83664c35227bd15b5760000000000000000000000000000000005ebd94e4640344e99e7e0f1619c6288665c985b90d99921ee61bbfce921265c4881a7e1034bcd840a665bae44467f5a94a849f6fb5a53bd5957e53ade1baee05702185b4d0fbb7c1cc0f46cb75614fc,000000000000000000000000000000000d993522760839abc960e99d62dca1021b52ddc8147929c4a064ec72570ffb3793205598cefab8490446453fb6da231600000000000000000000000000000000105db1e83fdff735d06d34574f962e70d84e2c1ceef4d8a8f14c2673633d7dbc7b97ba6dce9013f06fcfb134ffa2ef98000000000000000000000000000000000363be663cb0d36b8eb076df283b075ab9e568e460be804f197c51cf7ef611d8783ced304407d4c2540f1a4a04c18467000000000000000000000000000000000ab2c00473a2267682ecb356422aeafc893fab96a3bd27ae58d9b0786624c8fde446cf68bf8a003d9449702e345b1ace,240480, +0000000000000000000000000000000000a575d896b06c5ebd7459a70b9321cd0de082dce7dc0ce7e39581751d01b7db810bca80f39f521df0bf70ef642bd66a000000000000000000000000000000000ab497a9590deef40f6fdc0d4db2ae7b6ad9ab59f112a5a0671b48581f1f2b6a71602c73784ca6c0effce66a0a9c6500000000000000000000000000000000000af3812439e44981c91633f73d1a92298ca1ed426c98cfbdb50643cee36affd5fd02886349aa608f4b8a27452a51a96500000000000000000000000000000000013126db8b642d33dd988b745b07084ef86a228767f7e8bd45aac830dbce4136ca5febca5fda9644d3292203e27439d9f5b9d270fe31c772e9a0bb409d9f08a07887f045f906f30e2653d293b6c2c277000000000000000000000000000000000cc12f75fe5e6d6f082f9977dcce64c7858f3b6378112e7e083caf0c4b33b5811d62a1130c595937983905fbde8db1fe000000000000000000000000000000000308b803bcaf4f63affaea0206aa9f4770c21b4d191890602bb4151b80fdb42af0cd9f8dd2b1a3adfe28d0e49712d2290000000000000000000000000000000019f83af5cbee858fcbc9bca0f499222849b9e80dde7ac79b7c46785a484fecf274e0d4326469eca647cb223068a183d8000000000000000000000000000000000d0a8334171571bc63054c032299824523bd2476b1150a67eb17b84bba01d8a65295624202c3874e0302159951734702dcbf4fe86140c50618598be9185830bc1da11429162afe0528f00eb6698ec088000000000000000000000000000000000141cc01094391887f46391bd49fdedbaaf524cfc94d741cc7c8cf081dd7c425d81ea3e407be48127550012e39d2b0580000000000000000000000000000000014db31972eb242d6c2912b418ddf416fd7911f13aede9194559b05d1c9e12056deaa1e56c155cdbc231b39f4f9aa91ea0000000000000000000000000000000007b361beb6c156b5c8b92b489e6d6c05e32a4376d20ac3e1a54c94e678c88480779bb789c3e1ff7a021aa6d872c98551000000000000000000000000000000000215d270f2d3c5c5b9fa99a873fdc337f4edad6889f7a55556d8ccb5ee86b592453b74a720ef6a907bc342710cfd9cf91d7fb7121ef0baa85046567014620e1adfb9e8b3bc95adccbf2e0b0ea8f37c670000000000000000000000000000000017f5d31987655f8eaf046d6ea4025444924befa51c319b2bcb02dcdfde4d80a1c48049514e0b580e4bb59dd2fe40bb22000000000000000000000000000000000141ab771c08ad7c592725630aca0b2564de1ed8759eb3afb10a4bf451eb21d25e8d917f49bd5f7a06894baafdebbe790000000000000000000000000000000012dd82703c939cc5e7dd5bc3b924d744f0ef1a95fd0b9e57617e822e3fdda05b2e5a9959ec48cba0da40079da2253cc7000000000000000000000000000000000c53ff34d875fec4c7095af324d15921cd775873a3ba67740b2c123d6d482263b1cf93585dc810d19c68965cdbd9e102310d3b0535e78d803b477e5dc26c71bb18acfe66bd5ba5892d924d265afd6a16000000000000000000000000000000000a6514331035d42f58abf98b805f159921d8c4c935f88bb5493c580a6ce14a65e243424b41b3a9188e26a7f0c912a378000000000000000000000000000000001351e48b2d3f619887f4e83823dcd9dc15afb2800169ab78a2cd5ebdf25dcb6310f1051894bd2b549e509c55f5286f600000000000000000000000000000000007900972b84b6a76b2e686fa5757e98b8395bfc99da86eca122ce209afb39e8f3b07603cad92623774ed54d637e350d30000000000000000000000000000000002c68c42b3924b89a67764990478e48fc17aad4b5543bd38bcfee34fa1cae7535671f3b885852aecac53a30f28b0d4aa2fc9417e65cb76aa0093a7deb3d38c111c68f461a4aac57d8f09189f94407ee8000000000000000000000000000000000152d2c0e798d85e4dbf35dab808dd29d724e9b6c7ca7f53ffddfe1aef5976f2d3079eb1d3099e91b37d9fad7f1af5750000000000000000000000000000000015059423ee4e7201aa65e39116a2a49ba715b15e4b9547d18a0efd355de6f5a0159bc9047508bd3649407758d62887f0000000000000000000000000000000000e5a823fdc69f3928b22c542388f982f8131a978b08dde80d44e51d9eaed2ac4a1d5fa7392be6c7edfa33e833da4832c00000000000000000000000000000000044285f4e4ce526f96f9f512c5be754e0b0953744dcc04807ec6f041ba5c6fb9d5d395e93317064d50e61aae26810df0aa0b2d714aff175a0be2ba9e675a2be8936c42f15e304a146622a95dd6b3e3ef0000000000000000000000000000000019c457e369dbfaa130ee79bd33ca70d00a3797b6cf62126baec0c5d7c3fdcf5ba7f41195276dc412b6862b71560aeb77000000000000000000000000000000001206f67dee6521ede85573bbd5784d675fea42da16010544857d4e2d81b720b6f85f646fa23540880b44a6cde9a39f5d00000000000000000000000000000000142018ecd7c7acd4f4ae288e1c6a66594f1c7f31bdb9bade2b4dc4c6455cdc685b716382c54d67373831a19100185e850000000000000000000000000000000013b0b57463a3e4cbe063c0d4f4e998cbeb132a41c2877106ee60e83d4ef7d339a5432d30a3c149a42dfb1da9d61f34030227c3510ed6e4c7f84b11ddd2d6caa55e0e79ed59e1cc0cb325d55b5d145aa80000000000000000000000000000000008a463003900194e45fc2610fb461fde538b17c4fd516919000d423f5a1b582342ab9ec20d8eb6fda8fffc6a898e46420000000000000000000000000000000010eef0f7bf73e35dd75fb924bd9759c09aded9cce46b05e5d3c5eb3e93e5d5032ecc459e2220aa529d2f773c4b8b8c180000000000000000000000000000000002a0247f82a25468ee74da555218cdbb6405871f7097c24e89db3f3eab59b91ce48ac06e8eab2c049346436c846226a3000000000000000000000000000000001895b58a50c025e46a2cd0c59d5437f6eee75fac949adb7ee12d455c96206a33ec9ac17d5088fb773618fec131981ab6ad930000a9f82e082d408999b396aca2b0e435a66faba1d95e10fa0abc0625cc000000000000000000000000000000000cb0f13b0680c2f7de522a59f4e46fe1d4af3a64cd3ab97a2523ad3c3dc42f5e6760e06cf48e4db22ee64c5ed8273dd90000000000000000000000000000000016517038ecd2799d787c5b6ee93079c93f78de4a96449bc82699ddd6eebcedaa1d02981ab47c529652cc21663f1a665100000000000000000000000000000000067ae1dc093d4aa2ddd8b7127dc60745ce9c462a066106b099a7a07525597c72e4920bf64c2ea8a3fef3de51c703de8b0000000000000000000000000000000016374f51023e2448eee7c64115d85794996fadf4f76fd4266c45093c266f35be09e861d07ff194f3d15e310385705f0e1a6799cab8964c7b79b80e76be237ef49c2bdef5c99a38ea873af6e9d49790ec0000000000000000000000000000000017479396aeac06bd624a47e75b066d6daf5a37dbe515650cdf3e16be21e7d3a1f52a695c1c06382589eb7fc869c7d9250000000000000000000000000000000015c31ff36ed4eaec4d3927e62c111d062236e19fe6514236e6e3f7ff05ee96e3e4c084fcafcd21049a81faa1f84b7e7c000000000000000000000000000000000341b440e6c6273515fa7940d2f77018169bf6362b70a7b0cd6d66cd332ccc30e3ac48f7581edf47ebd137253a9c1369000000000000000000000000000000000cf424de046252efea9320b32b79bdab58e0e04f2916b4e8ef475da7b8ab85d8d5fc793a45ec6e6c035b6331a895d3efb206dbfd70e4b24bcc09ad35ce7b3aa62d17f18347f2bc5f15730202909c93770000000000000000000000000000000007c9111a85a6acb851e9cbdadf182096b720913ba3fb357dc2cbf2b8e796e9a8044b6df3ccadb740c73a16c3780c640b00000000000000000000000000000000059543a955c84a197d23cac22e15d82363c881026e41c57ee924da2a8c044f3021b29918d1db7926ddc2fc7a662ee7ab000000000000000000000000000000001355d8bcbea65a50c9b6ab59881e48e8e5f5592cee6aa69d5d01b033a84057cb6e74d911769bd2ab5f9722328aa204640000000000000000000000000000000011232571c95d0cbadf8e70454c851974efa4b326370249238db159a1224cc6d34eaad690e1840ad887a875b667ac1f193a607a7301bb7dc5b9c82d956ebb0bc54568d0654d725d4d5f13ceb6231e862e00000000000000000000000000000000088b7cbecf91721e01e5e4a08ea3b261febb58cdae3056d9316c3840b3e5720a289739568bec7b899f4b1f4f5372013b00000000000000000000000000000000001f8835d4b0e3b957e46b718b6bcd81acdb50ab85f10bb70c6343a23970efbe72bef89dbcb24d66e6a6be3eb55665a200000000000000000000000000000000046500afd292a31bb5a4a9bd7b5bd0fe608bb1265351edea69162e61f1623cf58e34e8e1a8ec58ca166e8203c86f84c00000000000000000000000000000000005d6cc367ff9c88fc8b6c35383f147b4f9e3eb21268a5a7405794441d449b3e1b44c8f66e30783e5f6c3567adf0d80171231e0fbbc2d98bfd1039a889acac471110d568b0a24ddf5eb3501adcbaac6fa0000000000000000000000000000000015bab57412cc5c7ee0147b0d2511b7836a14a82df06b4eb2b1baab102840ed04cad81da6e920ee000751e0727091c1460000000000000000000000000000000002f725e61e82980e6164cae7a2e30a36dd7245402f4933697607640d53fab2d5db57698be33a0c9b5dda14aa846db7c90000000000000000000000000000000007fdc589448887f6986efd817c63954d350511401333cb0df89214317dec0a82b06259ae9263f260fc7f21f98ad2630f000000000000000000000000000000001324e3bb46a1c69fc550fa8f2ae2d0ea74bc2d7159bed03c13a9d232233449e271ad1c3922dac5d84aae52606f77dcc0393c5c10d4bc4cd1567bca6960051f818e5c53704ce44dc4582767fef1092a870000000000000000000000000000000010adc26d73007e3b1cc58684fbdd7d197550658b4c66c702e9cd0f4e481f23a26c94c6798cdd9763110eefdca3d802050000000000000000000000000000000009138258ad1bdf6f9cdfb943fa32b42c4f1d834be536ed365d00126227c78b0df2776610fe5cf66a937cca3e0b088861000000000000000000000000000000001991db3a35bd2cd72377cd459502a84315422bed92890af906fefcc0acc4515fe7cacee1e4f360ba24efb23292482b8f000000000000000000000000000000000d10dfb682ae7a78b23b37b081efba32ff2011fcdae7b0f8a794a6ec33d71f5d6055f93e3b68a37086ab190d7d9bd7aed412195e347b680430c4528987859a1552ba8383cdc075c437ef19db6eff6e1a00000000000000000000000000000000182795b905320ee69281de833f37e040a3295e23be05ea7ae4563bd49d8b1fb02e95782c5c19645244633951cc29c5c900000000000000000000000000000000053368ee1412723b5c6465ee5ebddcfc00812e0e12e940f8485f44bce475c8897b324eaf7e66c0351ce9a6c92758c337000000000000000000000000000000000279f26c1e76e5f5d0fe1240c0956cd6025f6520ec303feb383b69525ebb6b2f199808a578a91368c3881a4044f37be50000000000000000000000000000000000ba4012c24dfe1038ec4b4565e1b321bbfc174cb197f0b0914bf1c126bdac9f423845f6742129670b7f3dfeaaa62df45b6701bc11c1ef3c9389710e4dd090e3db481c5400ecb91655c20694207a71f10000000000000000000000000000000016c27a3a950fc4857fc775441947f7ac02af9b3df6422874507b11f7b005c61d7d6a4a115d3759fcbd64633a8ad95611000000000000000000000000000000000e92954034df4f15450c32be31d4e146c4b0014a2b81e2afe755df79aa962afb05ca4d03577f15980fc6d8a34f2cc50200000000000000000000000000000000032db3e3c3617c16ceb1c8fae83e806744ca40cffb56bf9b79997cf48c55e5fea89db43b368cd922cd7ce30dd3984d82000000000000000000000000000000000d153fadc3854be49b2376ffcf4e5a46b9dfb4f54e580986767db13127e2d4d10e465f1ca932d79ca90f1971ddc0993dab45b07c059738ead9709bf36ab20b09fd3368f7aa12c6d9f3acf3f145c83fa5,000000000000000000000000000000000e1968e131e25d3f911284c369eb63aaf264ee82f4d9bd978b7d02470feab68ae82aed8509ffba875798a292f012c9180000000000000000000000000000000011488365570d9bff018ce6aa15e3d7e078368f09873ed5e0b827d1c25ef369d6571899c8df38a3df3135d0db814c87a700000000000000000000000000000000161973f4949bd72b9f010f017398778e0d7f0c8f77e12a87db1851af516b4540e3f4df314381f295c4d167fd9ac091a6000000000000000000000000000000000ae16f0a4a597159195aa47862585839485615095e538b745c1081ca73f202115a438d279dfa45bd3aef8d4043ec67c6,240480, +00000000000000000000000000000000046eea8e5af344dc8600ba7e506e923f6c356f7ecb3b78bb3805c4561e808c1f570e122c4fc5a1fbe433b48ce0c15d510000000000000000000000000000000006f1ab405a46c825e104bc963d2b2f573f0d345bd2b08a952d8793c0297dce267a754b802ded4db478399cfa88e7e255000000000000000000000000000000000a5fc4a09019ac9649c07b623d2cbcd9f0cbb89d28c01b170b62544d8da8ba3f236ca3172ac754175a3db85d9b846cfd000000000000000000000000000000000f7580110db2549742f69bbc2850e4ab35a6e415bcd1b06220b9b009c1f4c99152289eedbcba2aa653f38f6b8460386b3ca13f8540eaf45ffdab5182883d32a1d35e7cd956092221cc40232efde6cd1e00000000000000000000000000000000026907ccf4d501265cfe67bc1c0b06840e9dd94a614c873d676b5416457d98a1dd744322887f1f1f86176b11a27d2830000000000000000000000000000000000cb08e541a5b32fdf51acb28ec64d3ea542c7bd75179fa3f74e9588156815bda9d027dcf5597d714aa001b2dd8a9553c00000000000000000000000000000000103ac1c03c16706d5936f216a6445577c96acd3a00a3d8a9c2c66e6ce568dd84a4c4db187a5fbde24e6ce60e037f53a90000000000000000000000000000000001da5cedccc02d0f8d1dd7e4d81c3ec47d432e81e941ea1452b112eaf40748a6634957c90f32fb0385dc5d642bf65acdb3c8b045ef559b76005875bce09a66b36f295070a73ec8dc66c86bca51fa5d4d000000000000000000000000000000000a0b8dd68918b58ca6b113e938f8a00b2595351777aaf32dfbf703ef3884f02c798f1b5bb78cfac32f196c1fe88aecaa00000000000000000000000000000000121a4104e374566f8d582f75a3c9b70f09628f116b7ab22679ee13a1691b0b0bdb0d737833fb606c746fafee5859f1ee0000000000000000000000000000000000b8bc89d718572ebdd6e3100769f2571cabdd79ef5ca9a4b9bbaa432b1a4dd752f9af9d2a9b1f1f32d76d4ec2d1636500000000000000000000000000000000129f1d760a12eb1a75fec1d2ca438189c933e87095b9fbf9a0371d64eb205d8f0932fde9ee2ab9f36f8b6e5d4b5dd31021953ea264f74bf64378a339461bff41c5193e17913c67be7e2a249c9737b825000000000000000000000000000000001499e5481ceeefcd2ff672df24e8987fb60872ed106c496178d71c68e9078409a80016e1f9727ed0d5922c93e821dcc80000000000000000000000000000000007bfb606c005c7da6b4ce2d974f9fdb2e3710c8f51f18257ced7663cc341ff81fe2e46308a2b62b13408965949a6f08800000000000000000000000000000000003fbd951e860e3a4724b667427fd9916ca4ba511a0dcac7b1125b14d8a4f4da82ddc0b0edab8ea50e911b0fcb5c200a000000000000000000000000000000000b43195a5f0263307e85408ae4eb046e06ddb1295a490ac4e0e654324de53d0dd023b8cc159d86b861dfcfdf7ebeee4a505655d72f1128ac0204539f0d823f076cb3a57a7e74e896b5019c9161d6486a000000000000000000000000000000000743bed2c17bea1ebddf750da504fe120f457cd3b1754c9413757cc48f7aef07eb4fa0572cb853cb72d68427e875456000000000000000000000000000000000102ddfe3dee27186a9484f74b3cb3aa366a79f0d2e36063af6e484f6a459e9168d7a4a6969bb720ec694a52db7ab34b40000000000000000000000000000000009bdf5b86aba4845adf9187ccf9c74b1fcabaa05764e41fcce4b38356b4a0ace8e7b16abfc7f7b96b785ad47fbf8e90f000000000000000000000000000000001934fa903b71d234c4341b2f49f8177334142e7c401553dad38e66a2c157fcdf7637165058955b7798a59051846dfb8cc4c861cde3f445e3a78d1498d98b2b947056cf578652e19be88da4a786af196f000000000000000000000000000000000ddde953f59b8591a83b0cdfce780ec23d052037c26d60cef36522d0f984f907315d7b41c8be9a9632f2b88e0ce950ce000000000000000000000000000000000b8d7bdd94a994901a434e6ea5d03ea45dcdb859e560833d8ea0bd9d20c7db9c16b2427eac27d8f1eb640b7d28a530fe0000000000000000000000000000000017b5b3a3097a74d9c1f1b23783723235b6148023b6b060234dd9e2f6fd05e38668167136c999d91249963e224f9bbcbd00000000000000000000000000000000133da0c217c31ca052800315aa8a3b934fc1f179e6247801904bcea1e28dec0b65632ab2690bcca3606bb1461aeb147b99762c5189cf154e24238e4b157caa1d8759002f69b289cfbf3f24f5dabf20bb0000000000000000000000000000000012778a6fe79b1f2b768432df036543cade95504bb7735ff547969faaa8db84e3588046a074838c9a551a4fb48f4a66140000000000000000000000000000000013288a3413d7e7edebd118463d5eea9f9ae2e10f51965480f9b5c244b05775d04079a1dc75ba0885aaa9e2e4bae1ac750000000000000000000000000000000005b766ad112b8d69f1a28079688942ea146f8f31616611909f539a57c58ec5e857da9fce415d683c1c6dcb5e74da9d17000000000000000000000000000000000907e5c3c83d3f12a68d6bf812e310f5a04f1417094301fab7d4f41007b9d01fc1bfbf739dceddef756417367ed5b1d0298b5f6b43074b8f0807086b03f5028709209310474c35c7ee232eec8579147c00000000000000000000000000000000090be6ce5ed09e45a6fd9ea3a9223fe43a835141c1c29d6b386e085846869f9c5798b80c3bddec8bc15171906dd417dc0000000000000000000000000000000019bdf67eb16f2708ca55fd20af8deca66e2ae270b2f2f9736fcf49dbdf7cee034cc956f6fb799f0e87c12f283a11448e00000000000000000000000000000000124a69c723cbd366d52919a72dfceb7e4cd9ca5b5cef1784bfad3f125b11d810328ea1c849602536af500261aa684f5b000000000000000000000000000000000bbf05318ffd81495efa4f4c271c8b1c669041a6446501788f49b8739a934f09de9d976fe7300b0ae861be567d35c992177bfb0218ecd8cdbc6dd9484e74e41be6971ec2911bacc8b53b9b4b8c70e5730000000000000000000000000000000010833a3e7329ad40c1a8cef296b015f6ac6542c612038ce00f13a99f673783cb7eeb14796485c168d21cc169065d051c000000000000000000000000000000000d3b1416b23453b893c92a6c7850cdc0e4a395459140391b1dce11055da10fb68f318c5561e1c12d991a28f3f544a5230000000000000000000000000000000014721dc58eada80f2d0574fb4e2c1c94c45fbd90c2d2fd666fd618a96f4736a5ecf34cab34fcbdcb19b6cf7b44098922000000000000000000000000000000001905d34029bf84617a956d1edae090853dc1b622f560c5289251447ab6bcea5700bdd80d6ffb2dc12fdf3b0267e74543cac52219796226385aebf9e85f5f179362d4149c33582a97b7d2aeb05a8e6a99000000000000000000000000000000000b4d380f4f4eb976e6121b933be8418c536f85994491b0b93695d50473615e41547ead326bab795d4d59524a61d607cb00000000000000000000000000000000104b7f4058c9b355d38908d715c311a53169b42d2434de0876f1c4ffce1c39603c4876b33fe3076528be15fe42849d3e0000000000000000000000000000000017e2fd647e7739366ebb606e8a326daa5c03cd2b726cc4cec7747cb3468419f1907126d7cba98bbbc659478ce3afee7700000000000000000000000000000000183be0a976dbb3b5385b544c194e111729c7a8d5aa98eba3fe1c0a5b69b5fe6e5d0164e96398cbc61eba5b86d91b3c94e03afb2efea85fcd035cb4ba09977b2e1c84a0d98edf88e9f8d2c4f116d0f50300000000000000000000000000000000023bc7eab817fcb9982cdac242cb6cc0ee1779bcefaecf144dbe57d5ae2b2ebfe9088f39f416a56de4b4dc04d4bbce7a000000000000000000000000000000001318e728c271746905788dd8f5ab22a3a10edce3fa063438e54ebadba22c29e461b2ed78a95a8f26a65b47022291b8df0000000000000000000000000000000010aab000b9c5de56623f18861b343ffa80da5ed4ae0d7767b7ed791bf3dd507fe7286447b6a07ea0fa12c19f2e4d8e8d000000000000000000000000000000000770e2909b5795a08d98dc66389655b1718e70b93c5bc6d805c3945cb5fc0092a5b390e6497b550988c28c58b6e016a3804dec43760dab29c161b8f4bddc52379a17f3168f684267cfbbc3505e32d5f1000000000000000000000000000000001259a4e36f5bce7d5f97184948d57fccd458cf7f2ae0c9e174f537bece01d744fef544447959cb73a678fe2c378ce3c900000000000000000000000000000000131aa575b2b94232e06879fa1f6f145a0bf5dd12456b698f731a72bc587e6def5054b3b2afb6dbbfc34fa5249dc673860000000000000000000000000000000011d64b923596c316b097a0752043efad8b61fbe068c58bec7a6766d9bc90ed965b3419dde3b96679426f72184adb8931000000000000000000000000000000001653af784cbad5a804e3f72716bb51e0c733014d587952c47395f953828566cbd7da811a3da1d48681998d569db00a7bed2d3daf616df3f0061f58c925e9dfbbf6e9cbfd4b0b3896a596919fb3d243db00000000000000000000000000000000077a9ab830f7683b7fb46676df09f72d773b65286c5f5ea86623306e5de51e63851c18d192c4c3b20af582bb7f017ff70000000000000000000000000000000016dc185f4158e249939541d35ae8230fd749988b9174c40c40b8c932aec625a7e94beaef9a07f492445d4675a01b7453000000000000000000000000000000000c107a895bfb45d33136db6251c76dc0461a235fa5d1ba7a5d216bfebe15691261b46c9816315c146becc328acb6b8c7000000000000000000000000000000001151cba240678efe61e3a36e169e314b3610e9d4df6650507f53ccf635d8f1277a80d86baa85a2d4c7e2af73934a7299e16797ed90581fd8c3cef1f30abaed10997f13461374ea649b29101959fd506400000000000000000000000000000000090a1ee6c611980e0421b72a122cb39257dc38d1e74ee41b809ad76e440fe307cf45e79afddd8d40b94382d48cdd4c450000000000000000000000000000000010f2e6e610eec7b7c2b95c1510af1af342ac19fd3b01dddf81b8961ead2cc57a8eca36c2f5747238eded5914e484c52e000000000000000000000000000000000acce0789cfff975b09d687ef79535c536f3b799157d3ff731915ea5b323ddd9f6f4750dc8e00a879d4e516bce8cb3e40000000000000000000000000000000008d8203dd13aee7363f6b10a9e1ae9b713bbc8b8fb2c56f05fa71e8d69ea571384d150e8fd01e855b1b0054fe7967a052f9f29432638c033ca84422b12ca80ac4ae85fa30ff56c913c5737aeb2c84d04000000000000000000000000000000000b332430c518d7dcd120b346440e5b6b48900b5c3656d84840823a96e5bf002816d583a989898cad9e09ba978ebc58a40000000000000000000000000000000004197b43877b833de7f69cc1a43ad8d6d3544cd10d42336d4b19a187f31337a37b10cbf48e72b77e4d8e1a1da68e5e4c0000000000000000000000000000000008887d5dd08f45034584f40a2a68254baf2104f9d6a4c2637ef79c5ff2503c246f7adc36559758a0c07533b66c3637d40000000000000000000000000000000009343819dec1d4569683de4596621c19785d5ed14ba13e57d94b1b1a108aa62cc8c55c58dfa18c06883ce50cc1364b95e6f1e5df7ff90c4a4fb9a071c0caf3a3997569538ab9837ed41d9d0a8d7305370000000000000000000000000000000003fc7f9a0804e7f1664f8cd3ca67b70ba128529a611c24214fd09674072a6b8d652ccd37bf5d4611424688213a41cb3100000000000000000000000000000000137a869cd7bde696035bd9353662e0d37d2aa0731ae55357df3bc43536b9210f360324cbb3670362cf9ef607b1919bca00000000000000000000000000000000045d9d39c04e257fcd912c54e57c86d2d4304e6a7cb95a83d2bff07964d0a5dd8b4e42bdb91a8b245e512395e6749f1f00000000000000000000000000000000120e5e4b04b8a744757812fc331e7c98b35624faa1cbabfc1470e4c0804248bfb0c53a484107a677a7d3f0d2b533e7530cf3283195707c30880e50ff5ef605b561c3c3c354fbe8108f93b36f212f9ef5,0000000000000000000000000000000002bed414afe9c7a630441e7b163280be10e502cf877e94b6521d14baca0087c5dcdfa39ff4a51c8376d99855e1e6f36a000000000000000000000000000000000dcd54727a7729408e682c6e213005687ed51fa7935c522312793fc58cdb273eec9c61cd8b056a26619fc8dc006b066800000000000000000000000000000000137286f4086763e6ccd5ee82d3bda712b26814a17c6a71006a3e6dbdd919e469bd0e744bcdb2074679e78a1e7d56ee7d0000000000000000000000000000000012d75de1310199c0e556d61d6c0395b406afba0f13bfb37486c05d66b446809e8b1a024e8fd2c55f1b82cf2aed99a5e1,240480, +0000000000000000000000000000000008b83142b22f6d6496cad0dea23c71355e7c5d98659580b5ee6e97eaccb9fbe523f7e3b925abbca3a38f67426f3fb35f00000000000000000000000000000000035f655a1b2d22ea21cf0081e78d7140bad08c4e66dd45230a113ff3b7a77e39f0f1a72991f85e2b00ff58b27d5cb54900000000000000000000000000000000105d04e38243ef1ad2f734a3c97e91506c5a7c5d95e9b8771b7fded8908f1be933a81a5769044b633d501c0df7b5d7fd000000000000000000000000000000000e670ae4af94d0df34a7f2d7cfbfcefa6eebcf2a6b2dc5b82068b023fe02ce8a279e1bb96d905ad4f2ffbd8214e47d702063b046a71c2674e35466657a85d8e02253b42517b033619e31a536659172120000000000000000000000000000000009051f1e636309016c5433cc7eb019c7dbb75b3a4a5b27f6927de08fdd9577e8eb9e12919157ed35bfd6607be7fc4de5000000000000000000000000000000001953b7a33695ede6d0792eba85567aa5052b8a58c1bdc94ee82b5001893c6b996d3e8f7af8b8effd6cf50656d8b85554000000000000000000000000000000000a2f769f00679b610bbe212c2f8045e7579a96dc6bff80899eb7715aabb1afe79421ad5000f2c7b85d4e0904e335ddfa000000000000000000000000000000000ec962a3d00fac14d05774adc49bbabaf46ae78325083c0020587fb85eb234387aaf6506f503fa988df8e9ecafb4a59992fa325cd07502c6576dfb93ee952fedb304022656597bf3bb03a2bbc471b32a0000000000000000000000000000000006823056a4da801cae430fb9e3a8663fc8f46bb6c180b743b7f9c7c7e3287f3feb1aad4be0e98409c74ff58004f8732e0000000000000000000000000000000015f7a3f692d55252fa5af5ec952f581b796d54089f13971fce2ef9062173664816dd9f37174294ed78681d8c8c5a9cd800000000000000000000000000000000154743c76f7de590a31cb96d46a0ec0fa88008b7d6684bd8f6fdaec70722afff7b6e88c1f0fb048714fb1072d30780e60000000000000000000000000000000006f3191946d0e7c1307a1a0d1ea9a26db195ec98ad88f9b8f08a03a3d48bbff1fa53ffc920f7db5ebd4c65911392bb834484e688799c3f0a3bbe00cec7322fba6245570685cd7df0d744473b72f03df8000000000000000000000000000000000355018079cd02dfcca15fbd2934a8e47c5ee89e679663488499ddd4abdaba7679fb1c9d2102317cf2798c47aff1ceec000000000000000000000000000000000c417d489a224fbba9999300eb65a23749194bf5302fdfaa33ff7daeb8d896e387e56600233038d5c5eb59f644a99b6a000000000000000000000000000000000f5a62e9d711293d4373bec1bc2637802938eb789c828939e6c42f10062ec171ac6110261165bd179206d649713f6fe3000000000000000000000000000000000b11f9fd0ef8dcac2e21ef09846ffe9f5a624ec246e31393b39082a47354fc9523dbd247f0059b6cc740d7a387b137f0fae2ef61a024e4d8c4ae277f6b1d834193df655ffb315da67afa4ee4ddcb7fbd000000000000000000000000000000000fbb5521cdb9c3a69d58e5c9cd7e4a50bf5469bda2603f5119f3209669eb3e374d700f851b0c7ac5ee3cc9de79e6a7ec00000000000000000000000000000000131ccc37581e64f6f9fdf675b9b63ceb67d9d5844bf512166f39b5bb09d8e031437c06b0ca01caae7ad6d8c9bbb9fd67000000000000000000000000000000000531cb0557fa18ef054dbff2e7e994f1af08aaea7557602a26fd6ff539ab3c0a73f1fe841177012dabed4a1223ffb5a7000000000000000000000000000000000a180e7a345d2b635be92888934608e8b6c17384c48c560f4cb9809ff995f8e70d83cd4cf0e96c458fc414e1275d2a993168a1007abd42bc398e317484149f2fa61174243fd1748deec6cc75e7c720a200000000000000000000000000000000125c83184f63dee35ffd2c0c7dad9010cd6a9735675099f24b465554ab3db727ee76b5b7ea603ead78795d33e37689a400000000000000000000000000000000141bdf7e270dcd356993327cdb5dabe38a5c5a9b53470d9a4aafc041c46fe8bc841089e337469bddab5d4f7fd3d6ccbd000000000000000000000000000000000f9613f6d05f38e3073f14d0c2557101a4864a7d6d0b5a2b931d0613f020adb99a1ab2037a39fea6e99fcfb47929827a00000000000000000000000000000000192d812e05a17d22c60b78c53fabcc55a0eef3656f8e84132faf16686ee18ab4d35767db9a384d42f392c40c7b0fe1c0f1525bba87baee35023d0976b4a2d87524ba74158f000e5501c6d06aed04adda000000000000000000000000000000000b6e1960e82586de19ffcf29a8c5f16cf2fcf5286bf42febef832767919abddc655a0d1bfa240cac8fdfaed5a1e8f389000000000000000000000000000000000fc1598454caf04414f1930f711d762f0d72f5cdc7a4053c92b916c742b00dd0f107aee111976c1b1218c4577deeb006000000000000000000000000000000000455d6e9e9bb848e0868c9d725edca1f50b279d0acef8c597927eda72763e3702f46b216919ac36b080b4865249fd961000000000000000000000000000000000174463cc7804796b4a6d8ff28d2e8cfd8361b2e38f368de30166cf3c20c474ea0a1e8d94749fc3e6468924a7d1369e62d3d7c014416f33724acaa46412348d350f93d334588d39c77dc0b8ffcb4cb1d00000000000000000000000000000000144e4b615ddb871bae85484c308423adceb5de387d0c7ffffdd2211b4ea28788eba9bfae96ffc46781e6d6343e2f501b000000000000000000000000000000000046e39cf43fd707ddc4b7ce9a8a22a2aa1e55aa63cae1eb23082f7b4b5dce49f32d2ff887b5108b40f98062c02d5613000000000000000000000000000000000b75b5460db2baca86528569b47209b5ac24930e2545cc6aa08c401a87ef2c4e233de537e5a857e533d0ba0981b24d7c000000000000000000000000000000000018f53b83072fe7daab226c831a89da63a0930ea86e301c97e639d0ee1609e298e2789d1a347bdb4afcd355fffd16d053bfbb1670b7045b6df689871d5d012dc93e8be65faa4a98a51db8501a4b7677000000000000000000000000000000000185b296e9c7209a9abcc3194b46be9a545666527ec9b0634a3e3be579447cb52330174c19e40e1667124552392a7a0c00000000000000000000000000000000158a053c788e5b914fcdcf1aebb4e21cc8bbfbcc20c4d692256b2ae48149f6644e1578f98d58b3e73d9768d0e7df643b000000000000000000000000000000001318ff4150bebd8fa612f4e84f89151d5c56c272969bc1f31a3c1fcbd8ded0e298914e98e1ca48248e9023cd12db0fd300000000000000000000000000000000076555254f382707fdb7419772a4978808a7409f59d1dbb8c9e648372e19c44573f5ce1888a2b570a83afc20e698ee44f944ee8d294d189226a6cff17456e2201d17d4dfcb78f58f8501870377a6e431000000000000000000000000000000000f4395e3f2e301ee3e18df3c23cdd142716c7fcfc23caed924f0561795948b0bfbed948a6f7c415ca615ee0ba4d5145c00000000000000000000000000000000176ad308c7fe8c3a1aa350fa82b8f8ec638f77bc703afe1042a6da22e5385cd8473ad789247f205214c9980532b12c7100000000000000000000000000000000092b0ec86c511992c66f320ad46c9d6d7c82df118a9ab2ce1f2c5611ff4e5cdc9193a39c3fc95f18ddf96e139688b00f000000000000000000000000000000000b4f671e334b7f22bd8d89d8c4eb8a52b04bbd4dd1259cc9caa1872093736680618930f3a469b3af4a00cb6e44b573f27de53613b7a31583ccb214726482b770029c0ed42f9528fa74da7d2d1dd915e100000000000000000000000000000000123b64561ebfe085238220eb1428b3a203acb01846d1e4428f3759db6cff4ed3c1b9d436706f28b77e3b92e2e39ecb41000000000000000000000000000000000ccdf1973693e4b43b6133563986f6c96e2b924895c813f8acdd0f39585e4ee95ef26c0d9d51d6ef88bb62305e51594d000000000000000000000000000000000f51693bd44b12188131ca84801bfee0ca853640c0a8d5b20123c97b369c98299ac04beeb27d75946cc6f45f8a07b5fd000000000000000000000000000000000804c6597810d2c75de94484873a67eae258fcc9577bafa778e13d4814ce099a5684b1cc94e0df5a59acc7b19328fb8bb0a9750cdfe0910c544668bc9b11ecdedf1b757ff69b61fcc838c502c2911bbc0000000000000000000000000000000009b02eea05c78a24adfb0187defb6810116e21894d8782605c1d590f8bdc10723bf71a1e5e5004b181504ac2deb142cb0000000000000000000000000000000015882389195128e20e50ec4f8d278e8b8791e362341be93c475064d640e1f8bb1c92a6c777d666f8644d471409bb9aa90000000000000000000000000000000000d89295f845f989e0fbc6e86e97400b08e39b2968fe6c9a141d1e92ec9c838a3d8e1ada5e44bb08189a5d514ebfc2f5000000000000000000000000000000000dea05d8e6ab50b8f8dd9632337948a60568724d5a03c7914e4a03e2af572dd8153effef1a7d5c2cb27765ef2c17bc5b4aadecb1111ff43894123648eea9e57685dcb7a25553233a374479c24f2f8899000000000000000000000000000000000bacd14447ede6af0e92e19b54c4f5b6ebfb94207efec3e9f385a4c84a7d670514ecbc28ab686b383e239ae7f9bd673d000000000000000000000000000000001698bc92d146049174b843dac8c5dadcee12d1d503b2d0e46ee68139dd43d3aa797fd5bd06e2b214cc9ae3647c98394a0000000000000000000000000000000018d20cf6c84446cadfa1a26192a04e16d2b2a053705a89abc51bfbfa35c2b03cd58021ad95a35364ae1e2da5d233208300000000000000000000000000000000113268e360006294fa0203ce58cbfd05d05fb625e1f9474c96c89c0ec1ea80fe834030592c2f1c182ef8a3d5c32caf71adde66cf749daf69a30f41ca00d251f7f1e93b0e7f916a1ba6b994d946b12ca0000000000000000000000000000000001727b6bfa9c601fe84a65c54f556887c4538cb5383a288156fec87420ae7f15da395886e1ac0e10b8fbbae8bf040f4ba0000000000000000000000000000000012127cdf02ada71f28ed036a417971b87fe443b8c65b7739795dc7067082cbc9f06f7bf10c709969281cd072490c06fb00000000000000000000000000000000134f1fa1d277d01e2811c118cf10e2de6324e2ba14efcf717a03c1a10dca0862ebde0f6328839da63d7d85f573e8501f000000000000000000000000000000000d20a036b715d18ac9e2dbe009dd0063a4b13b3ec6fd060a64c4ad2b98e05e069060179530410d154caa575d504c63b7b2f9b44c73a1a6dfba6462e1202166b63727f45dc3b8b3b73b5d06459a1beec20000000000000000000000000000000000bd5375e7f98d3972b93420a39fd6c31da86d0d9349ac3774bbef15c2240437cc0761b2f1245e805d2538cbca6f778600000000000000000000000000000000100232139641c8cd5bdaa75b77e1e1c8e33b3f9554e2ae00ec6315b82cc00a6a70d576d744e68938a299ee2b451558250000000000000000000000000000000004224691faacb007bde3e37db6c7486aa5d3b4259a24c8b7653238e7522604ef4ffc1eb3cecf719a1b7f52ff00c34399000000000000000000000000000000001156ceaccfe0396374c6dec5adb39f14b6f08a32b88ef7499756f5cc324a9f1553bf5dc106a97469f2c49be5d563e1100cdc89e668f7cbd53a2ef6597a48b93d7d9a78950d4f821f3935edf72649e0000000000000000000000000000000000010a549108e77f0ddeacdc795517ccdcb357f909264457cab22fac2b982d10064756d66d0e48af02a59f58eeb1e8ba14b000000000000000000000000000000000c68703ef1c1e93c78faebc5f7ccc69e39046fe8af92e12469e9fd6baee62a2e8cc06fbbb3def81ae5cc57f488fd9c9100000000000000000000000000000000064ffb6aeeed432629242c3843f8cbea5bf7fe78585763926c5c45dc3cb4d1c79b3715506d7cda18c531ef890b22a1f7000000000000000000000000000000000e0eeb69f28a552cc6563f5fdc9919423c4358a2b70ccd56b048c22111454f67107513cda2a5aa0efd2af25dc74a1c47e23b377ed80bc90a4645df09e825509eebf57f59d7a2aa1b9121ace80926ccf7,000000000000000000000000000000000b1913c672760f98fc6d4e96ad1ef200f99add6f233b17291036e187ac6692ab0a29a4083dcf86a532dd06efb3d9b8c6000000000000000000000000000000000323b703abed59a9824f34d97851546a5e78441accea4e3a933b9270f92a9dd1aa056858ebd1739659018a0ca13b96e0000000000000000000000000000000001603cb3ed75c09ae5da6b94eea6017dac0c40b17d9aa8b65b78f2ba17de051bf3f21109d9afb214d260a09391f5526c10000000000000000000000000000000019f3bcdb8f16d9a2bd11e3f9491266780aa9110f447e19f12f7a2d62dc4f2c7b5fa18e13792007f5f361e20656c8ffdb,240480, +000000000000000000000000000000000b7d06c16c77a57b5ed74d829ad6acd665e73d20f1d9285ebba67b60c0d1571e1b02cabe5dea44499ce6d40a83288aac0000000000000000000000000000000007e6ae768ee3d149c7130022f9883ed51e4fcf68c91327ac1fe85aec8083aa61a37e9afc25d3352e144aaf888f264ab20000000000000000000000000000000016f2423478e0388e8495a898c23d63a0929a2ee7cf848034e4c1adad3460c7070caf47631eb930678d3c85aaba044dae000000000000000000000000000000001587e63cdf50d6e0b6b3d7652ad0a0a2497e70259d73432831781641d3a76db4ac7cff1bef165fd8ba29200d7320e43475888762fd1de395fa80b6d8a751f280eca568de2947e953feac343fd6bd592d000000000000000000000000000000001181bebe3256dd6ed754b8a1d77ac07e9a341789b3f4a4988599c7c60a36f1e95d3e3cec52c54c0f0abe312ac358c28700000000000000000000000000000000189d224b2904bd45cd1e8fa72570a1e35c94680d03d30292892462664f9d7aca3cc45ecc0773e66a10248df28ba9a9a1000000000000000000000000000000000f654f4c8b02a891e14fccbd5a96228afaaf79ed8306c7c1267715bc934e5f2568ea06de2bcdc2a55ef708689d90108c000000000000000000000000000000000c0a413f16e1aab8b91a87e7027f067ffe7de65097da37d67f604a184c7e7a7af6fe59ced8c03fa32ab036862868b35018ce7941da132adec1eee8d81facdb5012a15ddfe0cd6751ebbf66ce6c495043000000000000000000000000000000000dc972d55b7e68f97191d988ae7be5f5301bce5c654b323d4c17bf6e070f7227c0789ee38af3ccc07b04f0793090c6130000000000000000000000000000000016288c405bb42b4e71d12fd0a798cfccc7d33aba0500f939f5fedbd0e071166169d3072befcc5549cc6963b6dacbef4100000000000000000000000000000000171ea4f6607d6efc875cd9cff203bc62eb83bdc05c07f702143c23ab2770f50f42738f748e6bb3bb5d6f51f40fea1d910000000000000000000000000000000000fb729cc9716bf2e9e30a598ee7c4281163b287422ab66b414da85b0b960102991c24cd023791e4241bda5b0f6ddd3424a0497c642dce3937d883ee25b0ea577c729366c7d67e2e5ff1ccde3b19a5dc0000000000000000000000000000000005720bcbc598c4eda697406dbb390c2aaf4bc22c794b4b664e9b85b7c2079b90f7678e69496a4a5cd3b46580b90a7a30000000000000000000000000000000001159788c3edf619cc5e6f77c4aeb4860764d46afac4cdce54cade63155040c631eed65c2fa11b9cdff14847950cddc2e000000000000000000000000000000000d61bf02587e2c61544ae8a98b4c742c26a3d6ca49c6ae1b19a9d69c7f8eca43cefd555c973145566f8332902217cec3000000000000000000000000000000000cc0da96623432a2c170f07a3aad2844c1c2aab9d1bb5d2183928c818e681c66cb3767be372be4ae65fa40bf5483258ce4e0ad0d478ccf5381470a3fc9b882639dde4a0147c7c82e50bb93431b07a1350000000000000000000000000000000016efffb5d4ecbd01567c1e6099c0f06644d4312c599579b4cb780fccc8a425f3d1263a5f0c957dda4508202a354162f600000000000000000000000000000000115686a37624ffa8272ec7dedb7a632ac568245918ed91be6c9116e0fde290c26b5291e5f57ba6a779750685b0f126ba000000000000000000000000000000001852662b92fb49b2f0359255db8a7a2d20bd37705b7994cef1eb8e318aed70fc37bb7af9fc0c32ab3efa8c0afad640570000000000000000000000000000000017a691c08724ccf0e668f2f4eeda692e9ac21385fea243dc62c37ca73421eaf51c3a60771da3fb3e3cb578de37d2d45d38573db9346a3c8de41af54048cc51a0edcb86f36372859d0d794f7560c8525b0000000000000000000000000000000006fe4276e8f2e23127853eb929ee4e0c6ec4a190d46ac222dceb995c2e1d8fc9f822806a877e6cf87cf579cb2862c25c00000000000000000000000000000000044dc671bcd516cf03ad98ccc55266688856a4e4e5a59d6a6bb93e9ca77c596b5ecd2db6f3cc6377a0086c53ceed5487000000000000000000000000000000000c3ca83688d20519423b2b5547afcccbfaaa088a90523272c7cdc9a9b0397340350f2a5ced2a8153d69c81cd79732bce00000000000000000000000000000000069916c468f22bad174522d7fb94b4b7d2a664743b4689daa5423f470014152387a29425642b50f9e50fb679ddafdafa02257ed12262d00e78bde574a9ebd2664484a446c03fe8cbb855bf92e56bc1630000000000000000000000000000000001fd452b8685b0806545e09783947551bc5f6446c9d15d079a8968a448a6fd6f8b7e91974b71a4b2c50954be299c4885000000000000000000000000000000000f28bdab0b0fd3e05d08ee2c51f1bc0d30380e3a7aa18d6e04b47018d6a8d2b35a8f06df3866ccb95ffbd9c5333ca94c00000000000000000000000000000000035f3aa1cff72df0bb10f1a6f8414aa2ad0289cd15f56d84061a7cc70562f1f12304c402c388e48dd3f34082aaf79eef00000000000000000000000000000000034730e3ad7a3373b97279a00dc7a053aadd088557e0da61b9aa132c5b402fd9aef73cc45dc1cb7f2076cb2ff27ae2fc76b9d21a3da0359a377c11a6d0a18bce7ea81d4128dc1e7618e6c35b9149d5c80000000000000000000000000000000009c91d800cb1d52501520b3625dd4c20173684bad8742c7ac4b35c0ce028556b6529de5cb7541e3c146b13f58ccae57800000000000000000000000000000000124259d345bf2f8c16215be4b6b7922f4e2d6b32f91c4b1c4f1d4974918fa9e6fcf10e46f0c0b55e2a7210d1a5336eed00000000000000000000000000000000072e6231244ed14aa0f5de06e2f953371995a567684b00e459113380c1434a8faaab8b28a0280336ae35bf1f90f1d4d10000000000000000000000000000000010289a63e0e5f1f35b7af22137e117a85df27874ba15df39b7c26801c169667a3afe9a14663d7ac0c2956f4eb70cf11fc9cd895d5d1ae0ae704e240c10d8ed4a01b319598d7885f7c3fffcd9b491f5fd000000000000000000000000000000000d0f22a9bcda47ffcd034618c15daebad8160f9ab6b3148f1cacb32e713df2ef19f706f14131f8ab1181b7ef7598e3e4000000000000000000000000000000001680314cd79fec583c8bc0842e1750b1318f94aa7700c6662aabd4c592ca61ad51a6876b686ac3fe3f508cb40192c31c000000000000000000000000000000000a172bd8e49637fd9eb611b590c68bda707931e403db35cde1c10bb74c389ed725aab54dcd7048285352c56c8bc5fd920000000000000000000000000000000012589683ff3f85ecb006c5c435ca7bfd9d5a6fd06eb625bcbcb18577cdef610d912e783f3986c965710269b1ff79ba972467604875028997efdf5139180a8d530a1e9f18c62ddac7753cc370bf25254b0000000000000000000000000000000009720c2b3a0658a4aba8e76e196a558bd155ff550b3e41bb5b43e7c5946bad803b1de64e342956a11627e7f24f69fef7000000000000000000000000000000000decf2262e8369d6a2b1ce07fdd257abe1c7610084ae2f347640c0cdb98c7cfa732dc609c18b7b6a51b47ebe4b07a586000000000000000000000000000000000e8a0158702ff6d6c3a7ed9fbc774bc329681130840d86ca3f26cf6642cb49e5f14ad95fff1c94151457b1d5a142bb5900000000000000000000000000000000035ae66137629e95539e09ee99b001d5b9a6ede79727d7deedcbeb5acf081cd05ad469ab06c265a5224fd5236db160b62f47637b64d28fb4facc31d5bed34b22e7b270431f54a689cd0fabd205e001ae000000000000000000000000000000000413d82d0b02ca706f0266051445c04f3ac594ad82e2f1fb4e8e0cf23a6c1087c29383238ad3677f170e99259e2fe93e00000000000000000000000000000000070af21f84895c0193f0b8174cb20b11f45c845a8d782b1f58182b149362e1368ba076ba702185fc54b5da94c3172f5500000000000000000000000000000000182e124ca29d66f9f6c370f6065f60928b6a8f445a74800d59209219add6cab0d1b79702c31d60e61cf56874a4eb6717000000000000000000000000000000000b94b733f76067a102cce9659292f31f3df2cf2770e3a83c1524536e29d0a84ea5c4883cb4e849830384dc7e157d8715474c3ac61d4fbece967fbd0599c9a92c3fe0e53899861f044308b0ea8df137880000000000000000000000000000000004b2feedd5badbbdff6fd0f33a4bee17b38cc8967fc72206246c349e1017ed0407fe08e0cd9208fa9e4e21eca4cfbc2a000000000000000000000000000000000df0d74d5cc17ea94457c0ee26ef24071700a0fd6bfc762e3ec69b8f1c096887f679e312f07cce8340686eb2716c9a96000000000000000000000000000000001878edbfff2efc5af64aa9a23589a52d63749b7ab2970f256874fe0cc15091c4511050b0a243d421dc6536f19b5977cb0000000000000000000000000000000015951da3b20494a266e4d014d0ec70fef4586c8656baf536a0ea9a48dfa041624e8154989a2fb106189217ca979ddbe8eaf9da65e0e1752a982601e9a070a7cc77d5007eb641fffbb78d2a1b02dcffec000000000000000000000000000000000657fdf40c829719db134acd6c2a9ff904681b1869f28512cbe2a64d93e5b62114a73bdc5260ad9a1f24a3ff191b7a3e0000000000000000000000000000000004e77bf63eb9c4741028dffd0591b4f525d533b455d35e51cd86c7884d63419a162b145752bde188d2a622251c087f870000000000000000000000000000000016cf02af01fa6750b4d862f0cdd5a87a79da7c3fbedb0fa356ef2e7419e25b3a2bc8cbfa97463d463d0ab349efaa3f2b000000000000000000000000000000000ea4468fe6a85d36ae990d0ba959ae050756805c4c769c829de475a2990ef1c46de20d5b466543978faae0f6045023e85158bfe535fbc342e31f32ab4723c0d9fe95a2c64cc4e59bd41d13a08ac811780000000000000000000000000000000018d42a2df8ca475be6bdc468a82c313599239b974ec3d27e8b8c534aa4d6b85d4ee9aceb15c38b3bade2bb1706a2c2cc000000000000000000000000000000000124d5dc60527faf48f5e9574308f8a328b410de1cb49f2cc6f76b8a1f2707f2d1a94bcbca0a97bc38f24545a8013b250000000000000000000000000000000018b690b3d1e3b22946a91ace004e1d8f92eb5beb284eb05b52ac5ba003d7bc387540d33d088a02711522e3aef7f74f4300000000000000000000000000000000103080d8bb379d961da06bc4c148cb5b056ae115b3a0e33f0a8c99a7fb7b7ceda35d3902e0733156d354dd0240e4bcabd66f5a8f37a7c6a32088897abfaf5a98bd4722c411bf4b52f0f5b5648e89df29000000000000000000000000000000000f4d068354cb5b51e5a86163978386533f8f9b6e388c5e75f7d9ff5e1ab6d1637717d251f2b723b7d683e26a274d610c00000000000000000000000000000000001ec5a0d408c55f247d62ffef172ef26e45c41029f1d04e36f0dbb4fe8af414b0f7fe7ec0cfda66a2855b58592486fc0000000000000000000000000000000000cb1b68045076f457746621cd415d743701bf3ecae8d52dd5582c3e0bfb38e6cf2651a5ebdf521afb1ec5b8066444210000000000000000000000000000000010f5672f813470378fa806abdff90edeb0239b00d85ff23a3fc6798779f46d6b43071d66f7742897a4e53ebf6c7dae719acdd24190589ae7823a42e8b59598eca12bf13b97aa9a0eec17f5f79a01e8df000000000000000000000000000000001422fbaf1bc2908be5900968af61ffa7b3af46e7250e4663ff321f42e2db057bcfb2106c433a9eef8fe20f7138b71d280000000000000000000000000000000002176e68cdb0ada2d7baea437bec8754ea293d14afb85a811f7a5d740d645a53e511b5605445b110174ceb5e6720e736000000000000000000000000000000000a69e992b6f4f7eaad2682cf9ac2e58faee9b3341e852543c2aafbff390ae067a641b2b5693319618fde413fdc64d6c10000000000000000000000000000000009440317af8f5c753b5de4648b06212256a39b7fb03678f1913b0a3d402a50e74e2da5d29c211cdf0b292c132759c36d0291be87a213b0a24c92df5ce42381ca378dc4b9aeb4cb9b6918263bea827bf8,000000000000000000000000000000000fa31d16d9625200c13a415fd61b7552646c62fb8db307e92c1ac3d2acc92336765a1db42407ab0f774ccf01291b9ee800000000000000000000000000000000156a77678873dcbe4832b9fc7f516eabc1a10f4a6576cfb15765cdf999a771a6a6d41021897dd783e9beb2db722a6fa2000000000000000000000000000000000ee4599a6ca9642cb4cf38f5f2af23271cc8c5bc6e2cf6bad572b618bff9f8677837104b93ca8942843fd5db5c30dcdf00000000000000000000000000000000138986714a4053618e66a85835d105f4aa2ef38ad18e34b2ee7ae30a4282f7e543c80c94bd12c244506e7fcba25f4c1b,240480, +00000000000000000000000000000000083c515ef8509b12ab85ad7d0a816d986bcdefc14778efcb3bf7c2ab61991849f279ae6a9f5342880837c0d0f4a4eba700000000000000000000000000000000020cf5196b5d567fc429cb9ced7b55e4925e18c914caae216a736886a8d886c4bdf6d704bbd0ceebdc1975ef530c665a000000000000000000000000000000000f3d0a217c224434604d63cef559eed3864d2da62ac00d49fab8c2c6e22c688496adc30c8d591e21bc0be404b62083c20000000000000000000000000000000003d0bf7f25bab0bf2c768b44e10a6022650f7d5b7d568d502b9d0b28209ee69b1d952ed848572d3e966e8771c20becc4b14c6a38cc998df3583228080ea10f215a6e6a4b02ddb6d43e8f459d494a1ec1000000000000000000000000000000000cc4c4b7eb7e358d4133b65e635fc13b8a92229706a6dc5867171a60a99a8e343045a794c368f1133ae6cd2788c3a7db0000000000000000000000000000000019508aa39fda9c3efced287d2571db97045f8b7b0c7a9c9d51796aa8017fc0e5abb8fc994700dd5c9f755edb518e096600000000000000000000000000000000049f68b0ac142715cfb385161ee70e453f0e24e2e93f3f96c3d69447f3a28b180fe76989427b2e392c7ff939011e04ab0000000000000000000000000000000004903c0f8e0757dfd3f5edb4f54a0e292df15ff70757df7b0b04c99f590a3dd13c6ce7bbabf3e14daf9f3ec60e2379aafee8614394c8109338432ec72f2d9babba06f1e7b826b0f2558c3247c923b23500000000000000000000000000000000041128064ac768664f076116247e0f8a00adaaa824cd6fff33bf524d0c76e61203408ac13b294aa41f5c462cd42d3cec0000000000000000000000000000000005e150c27979ff1cbe307511816be900648957624caed1f08d88347061cd783179c615258fcf3619bc4bfa53d2513c610000000000000000000000000000000009d2b3d97d29386b93d7af014ea8f1cfe2c1db5a9aa0c17e8430b0fcde974a4e7b8b42ef041e9a7b1a8aecb97cefb52e000000000000000000000000000000000d86096ebd88b2cdaf5cda1e9ca6b7f12ed5def629354b0570eb084bc7139cf20bb8ebe4438f87937b8b554e2201344c28728d06cd90050e44a827b44f14ea35e83c9b58ce4c3a7a45aed6f31c94fb960000000000000000000000000000000018d677cd67e96b10b671d2ed9234d7708042ddfe6fb804d2e9371a80ad167004f9d6b92d26b3d3af34ab7caa0e03964e000000000000000000000000000000000e34a6c85187d328eb33c2d5b2ca96b5210d47a779ab810dcc380dcb7e6b3c334ac8fccd7354aa9108136e4f6dd4ea0a0000000000000000000000000000000000ab8f7274ee3fce1511c58661625c766ffb0ac68bdb835a948b09b7510bb573d49000000e3d3cea772bd71d79681e1800000000000000000000000000000000135ca42f2103905748a1c416d82170f7d24b49ff3f859d6cb7493cf89bbae0217529a9edc835be1f9890ce105877af630fda665c40d1da93b1f132070e0b7c8c2c0ea0e66993b5a3d7419a33d118d25f0000000000000000000000000000000007884edaacca499491580c8c7194c0d60ac6eba95f7a81f63742451c8ed21a223ca545d5cc1e648b9d2dd05016b4fea20000000000000000000000000000000014c78d5d1a93760096bf6da73bb41631e94d6a1b251ed0be7bda93e4c50568420bd4d49e4a46e5be4bb204cdb6b0ad5000000000000000000000000000000000128a860c23a183c5bdd18b4a1853cb53475f1a893420bdf3271cc4a65a827eba6b92e1f9e8ac0d10c73edec5160c640b000000000000000000000000000000000ac14b2170042ee6561c34f77fca40e1bd2d40d01798417dd954905135ed9b7772e5689e6d4e543d44a4563da8c3ca40c14f014117a74f21e0b698a257ae8e3d6091ba76bff7912abb6bd94d41886d0500000000000000000000000000000000144df2e76821c19167f60630f50c939b66867a82c2a5f807e943676c876aeaa2aef2126bef7fc431f0c7b39e648542fe0000000000000000000000000000000005e463627bb2d22c25520c27c05cdc75e1f2ee3b91e8088399ee42ad13ca217284596e5404b4370995f71fdbf1c1c7860000000000000000000000000000000012323010d6aba1bc6b1d6e7f7e8c7bbc0838564b279d5ae6279f7f7d3cb5d96273e27e7096e9a8540463ad16deb3780e0000000000000000000000000000000019102ac6bb33bd1c5a158a584ce32308b6ee5679dd6d2acdcfa4b9c54674fecad7489d1e39c05b1ded88e4ea93620724d81a1239ad2c945f1c560fd1674ac7e87d49aa41a1f4a5bfffeab1147c0ef7c6000000000000000000000000000000000faf210330693663c8a1d1fef78e211ed2542f7ffeddca3e19be3ba77ef211da1b8bb5abcfc96b692d74f8c7df40b0ce00000000000000000000000000000000134153a252fd8ec5d9aec08ba09a94c4416f95ff6f4ccce59bd400474c836af5bfd941f03384ca4bd5c56fbe81d96ea2000000000000000000000000000000000b4532ff1ceab2a3a177cb83a75c16a833a2ff28df447def351134ec4fcd608b2b75b1f8035ba7d40a737087f3e8c1c100000000000000000000000000000000127e3ed13384b69819b34ef8705fe9a66dd01b275f1f74c2c724420546b39c70cb7a8295a6c1ec4075ead4e3312b8b603a02689cfd2c353fc1b4d3913f5a43745fffe6a87a7c223ec3b25b321584a75c000000000000000000000000000000001351d0d5d531a63a5f56aaf1d7906b7ad2bfb4e9d823e2659bed4e05e7edc9179a7bbf13405ab5cf410b25c7d476c342000000000000000000000000000000000f0ec96128e058e8bfb6e0df1331887245dee87c4f9721fc7f1d20c20a2feea7a7078a4946803ac093477707598d59b70000000000000000000000000000000009399034e4aed13cbf197d8c4753285effa72fc53493ca316db11b39d5527b009aec6350d579f9dee22cd6d4cabd88ad000000000000000000000000000000000002f41ed0dcfa2437cad7b12a94501266d670ed6956196c438241aeb90474d17214eec5d5217090d28892d95f4e40055af95ab3fd062088ffbef6ed887fd39aa1d527fe7633b876187ae12e736fcf2f000000000000000000000000000000000ae208978a751f8921c6067ebab4190ac8d3608dbdf50222eec59460095b8ab2abadd97616c240edd0a9c53dd006e38c000000000000000000000000000000000905224b317a1e64d8af075b6db9de46ca4481458ad6bceaf726ba0f63e81e2a0322e79e70a5a82034abf00d47fccc300000000000000000000000000000000007173c3359f0c2e315d11d646a76e6f500c0922401e4bf9f4ccf2f0801a567fa653f287fdbfb878ba0d9ee12e25396ef000000000000000000000000000000000161d4cc71621e5df13d121c77105af195c2adff5fc6b656b0fc1dd6eb2518f474444d8bc526ae16387f23a4ab3f342f6541c6cf8217c2a95792900e8fc39581b177a57ca00162c57131ea4fb80a4c60000000000000000000000000000000000266af9991c393d3b55f9e0f22b0967d47dbc5b0c97947125e220c4bf9f4bc58d32ebc7bfb02b2e329c933ce41d0d8c00000000000000000000000000000000004cf5748aae8dbc1e4778dc85da575de2b6d9d346f5dc5ccbfd82513166384111f5e5f2f1c2f7ae367a22146d1fac027000000000000000000000000000000000095dbe68521b2cf51283a8cfea1f20eb7ae37e6e945c5f879ba4834d20918b74981f9e0eff4543a79ff4eb36d84a9c60000000000000000000000000000000007953cad14379ffd4309cef1ed6a2dbb73a93db0bd3a256753402e525bb62b10aaf22b662bb2c704865690af995e7d284b7c3f3c4ed10bced85f36fd6dac2646c65d3c810e6d2d116c38aa5e10b29c2d0000000000000000000000000000000010e99f318111baeb1b4611847fdaea7cbd5e3ae532af667ad2498fb2e97b1eee0297e2811c7ae854b882f616da7733fd000000000000000000000000000000000e56cea75b4c4e4c669a492a6723fd60e351a66dc5c34c46469dc36cb04d2c23cfd4aeaa23d0e9e83d5b78a1b77696ed0000000000000000000000000000000018f838d6a582a52a508cbd6bbbb9cf515e091deb7a640e141dea4018af6593c001dc43a8fe4819a7877d9ecf53d5752000000000000000000000000000000000119aaa2ebcdb6379f7ae972cb709990a3e8254f1025cef308281bf7057295e3099d1f3127f76bd2f9ce0a03ae0de8e8d7e33f394e96d17efa30d34f57eecc45d7b4ca150a31b8d0484578151d6e65c2b0000000000000000000000000000000008f837c478e874b857f1c939a26a02e13061d50728c10939ffcf5e862cb177993e204590699a28cabc7593056617d433000000000000000000000000000000000432d9e66dc78bb58ab98771e7e8b5fe51835f286b488e2df6c1991fd36c3c537f2ce30abf24f9d4fb13941189972e39000000000000000000000000000000000b202de3708984f44f7d05ccd9e574a2a93a285d5ca262017346580be273c58f13165437dc90d1d4103d3b9eaac536ce000000000000000000000000000000001873e1251d9ae9448de8e7ccb7ca59a21bcc0d07a2819d140c06ec33cbba559ba90647494a7ecdec8b609b58cf7995cbfde92a31e571ec03e509ac8a70ed5788869854eef0bf578efe6c5e6468315553000000000000000000000000000000000084e07b6576c73aaf43c0ef9c5666dc988ed93d1a106b71e4882fc0cfb5e710b91e5d5eff57327f5678f662f4a451d50000000000000000000000000000000008a29751f1653236a48adb5fbc59059c7137d36139574c6af97314bfbcc22f77a4c5162092762a26b5da7887b94f2da6000000000000000000000000000000000a4fd84c4d58cb9e18aeee180fb05f07c3e1d7ed8d09940182e9b4738744fa6faf600b6f720441e0ad6391a4d502ac040000000000000000000000000000000018b356be2aebca82c54988ab2a2ec58751ce7a815f3dd58a2218a638753d4734d38b74ca0e00bbc8681768f5d1a02b646f7de01ad0f7b4dcaee1123bb80a71d3bc1e63ca577a12b14ae2a11d8c0fde46000000000000000000000000000000000de0f22cf05620a5d4bdcf50ae179f23a9c089fd6eaeb14eca937d9e2480f1782a1c67df76e06191a9b87514daa8bbce000000000000000000000000000000001981cd1f260e7d96e55533b8e29867f37af507b4a58abd69e0ad6af2a55228ab1c82fc2de52deb7b7b7deae2fe621e10000000000000000000000000000000000d22a7a567ec8826391ee711768e612c403e3c16e20947ca5861185c24728b6c7e7756debb333e7acb53d86032d5748900000000000000000000000000000000016fad52e1e86b9e092955cefdf93a10f30db896fb519fd2ca12571d8dc8aa352cf4f8092e0e973d0b0c66df78433251e2c69d21d40813ee40a718f0ead36b51f3a50e9e4e4b2de8acd33add62bfc1d20000000000000000000000000000000000484bb2452158bca93dfeeedb40745bc5d9a9ad49afa20e6c29fc9ed1a8fde33ce508cc252ddd05fc486f8ef78738ac0000000000000000000000000000000003c2d6ff6f292b0f0e505fdfdd2940e72bf8c2837da4ec9c74fb593fe3318a9b9a8592524bb5d40f6c38ad871ab7b6150000000000000000000000000000000015f888ae2722713e1b5b02803a5b48d53116c1a4bb1191c9da77ded8c6ab49f1620b0f7c7867957d84503cfd3dca1be7000000000000000000000000000000000fd96baa382cceadc252eaf000d47d8c1e2085e9f274dd9dbb571bf85bba612836e1da2453fd914135842e2750796b54762d89025196aec4f87da2fcc5a9188b4dc7b1c014dd1d705223bf9fe1e7a7d1000000000000000000000000000000001820de289f62058920ac3d4bc60da023ac29c431ee429a10066f305d2b1a333ffaa906404af977cfd3212b53e66726b500000000000000000000000000000000094e448db84421e25cd03be3867125cedc7f77f286f404524757f3c1a9cfa28ab6771293da490a4d75852f515dfe1a6700000000000000000000000000000000097dec124970bc63d8f62f9133157d412f5ad3fd5eebb444568cf0fe2825d6ef6577ad302842f35570c9977638c6a827000000000000000000000000000000000490bdaabf4db27dce906cfacf3160c0fe25959df4af89301cbe6eeb29f72e4c55bb467841ba7d0750a59a32fc8b03d0ffb9f3e1d43aece3af1f59319a8228cd81e668b1e250d03350958dcac9e23843,00000000000000000000000000000000193358b283147ed5848559d4d1533734822b0248dd17b2effa80920a853b70e7fb683b08aad6ad4dbb91f964ad1b3bb6000000000000000000000000000000000649be60ba72734db4cc307a2fd8be57857f60660d0c496c0dad73794296552e17cb2eabb3537ce677edaac1c6997341000000000000000000000000000000000f91ce27345e86003c99d133eca50710c0722cb35af2ce442ebd74b46d659e0118be9bebf32111c258e4cb4ab795a2cf000000000000000000000000000000000d76ad65233522b1e079fcfef4dfa80f163682d7984d5062680a5dd4cbccd5044de4705013c6bce2140f7950032f90ec,240480, +0000000000000000000000000000000013fe4afb94d08ae311b7442de7291a11e733d8e555f2da6f72bf99da780a8f8d357cbf3d8959f6aeaca7bf3f5b5bd10500000000000000000000000000000000025af713b18cbdb5a960371c2dd0317f4bfd0182f4bfd6b88d588b56fadc1a0398412e7e0a786c326aca8779ae384243000000000000000000000000000000000581c277053c15df8eec05c34267f62e63faeefa2d124c2b4b84d2a739ce5484641ce955fbecb901d1e8ca816690189b0000000000000000000000000000000005355dd304b9b60498a3fb1f08e1ba0c98db327365ca9a0365a7f1e5cb56aec43b7fd2b4aa104eac7b1c30b6f53cd422be285a119dc8cb32b1a0c5380af736114a32e9d1ca870abdf278dfa84444f70e0000000000000000000000000000000016b5b3a6fdeffe5b9a0244a333ada4444a2e03771f94433832a4617be696e467b4e88ed80b174809dde4242bbb51248b0000000000000000000000000000000003dee846c5b84f89734016e547c63c02e4be07dbbecc86f811e2d8d3245f91205bfc055882565371db532240da1a845900000000000000000000000000000000194d53bbfa962def4da2a9bc7129fb6242a3922fe26cc4e603528ff31393a31d03dfc3463704250ea2ffa973ad175153000000000000000000000000000000000333768faee332d7468119b9e0469bbc7bc98a482562ff2fd9aeb6d9c67daac9c3da1db41c9e12224a2eff2feee51778bc0535bd504d7b9658e459c2e79b86bf4e718baa82b8d6e624fba0eb141c7260000000000000000000000000000000001910ded86d79f9b043bb79cc4049e0652c13d0fb8db2f070d695124d7a42cc3a2238282fc8a424fcd8d9ecdab4bb6fad000000000000000000000000000000000dc8d6caf97416928d2d58466219f054c6f28f49b2bc04d8a80cd46a308bc95aaca3a8df1914ab0c7da341862fdf47400000000000000000000000000000000004380ca7b1f7ef96295589f78a1683a51bce4b2afe50bd6076ccf5d07d35e6cb2ec7f74fa35097b2c0b9fff3f4797c1100000000000000000000000000000000054f492d7442b1c0d1293277d95efe822faa7d8881b9afde20db58d6267e049b90d0c8828a6c12540f4ba1e7c9ace6d84f3fa09243c01748954d84f4deeb460f3ef78f9c34296c6a092952bc463d7284000000000000000000000000000000000bba4761eda87a304a80180c2447a1d5a52f743015ea7c728e70d6a5defe3139c80696f842da3f06586be8d506ca4bc90000000000000000000000000000000019ea930d5733f4a1ace9fa0139d412d65b2886b659770e388894592de0694d38876fcd86d14580f9b92518d5496fd44c0000000000000000000000000000000002bf5d9a36d641d1259c1b30397aeb071b88844c4cf17e3de0984129d7b4d67865157ee2f682e7cf9d968fc07ce43618000000000000000000000000000000000f9a4f29868654abafc7ba935aa22d3d010023ef5112683a037a6c69b9e89374b256b8e1329eb5ad306d9f2063c22c335d84733ccc41f71a11d61852fa336df566109c5538c2c5f5cf2af961e93797fd00000000000000000000000000000000004f194f21373f09f8cb4984169890ad3855e814a4768c84e9fc97dfc181c60114aae534a27d3eb225b2125131c754ee000000000000000000000000000000000e6f88880e9645e35806d193f5d16799d63e2f9edd8ae28df54d19875c61857b0a34819a70ba3e9c31f00b5826b0cdc200000000000000000000000000000000193293c6cfae9ae4b24519fb23469e2f8dc4eda8524ee0b00c7141587b07c8a26a29841d41cafbd24bfbea2034a9c18e0000000000000000000000000000000017433efadfe9873dea9a68177af3d5dec4a13dcf4a710422d52020d4d145e2523ec0b48acc533a1ac7068c08ae6aa28bfeeb95c32362014caedf2a9e066a775e2db0d1322edc86759faa99bd70c05b580000000000000000000000000000000011dc003f7542f6822cb872117fa658638dee2a15429aaa9dd576a7e895bc0a2160bc120558a32aab9e646354233a1afd000000000000000000000000000000000fe9ed8ba572ef7d1176176a31fa92a5ff3dc38b0183ea1e22618e3b3214ee78c53074d4c60b5056901c6f046f8210070000000000000000000000000000000006ef1c20c3bd88bd6787598dcfca52da4e5e0e7c7643af983c709b916e71fd15475da30d763ddba0899b182cbc070ca20000000000000000000000000000000001a38a2e54a44ade572ecde076038f5244f266cd99532024a377829a64c20fb2cfe1633367c74b5990febb08e776bc34edee2ea28b93b2daf4ff927991769a9c69ba16490b5676074e64f5e91fa994a60000000000000000000000000000000011ce7b2cba037e5f3ff19b36371d34e287eec807178dad4118c6d43aba68623e182aedbf911a2ae5cf3d0e690ec3ba790000000000000000000000000000000017a617453f391e6e2437d56ee831ba895084f60d1a5f342e19a242b9661c703219d90a157e1b55f005f5059c15c179dd000000000000000000000000000000000746ab134c7f4bc19583a4ea4991c7cec3f651a60582b40c17b2d18cf6e252d93d2f3c2a1a3399be70512ec9eab251de000000000000000000000000000000000698daf214f2de44ebfaa36379862bd9ffb40987dfc8e632f14738c93c8e5c3fc7be9fa9100fb5f7440311cab34fe1897a07e50c1fbf1b388e9264c762798c31fe76761508d070f06adc63130df07641000000000000000000000000000000000e4ac65ce62180ac602ad68098ee31cb747886e95a183e4f819d54af99850d70496e6952076084dc7bc2d3f7a273383100000000000000000000000000000000182c718fc9e5cc961426258e82594a5cafc36270af0eb50646d161fcc192c30d40d06647e14a282421638b31f378de940000000000000000000000000000000002bf448ebd27cb6270e1b87087796ca6534ff51ba0962f3290ee1d06dc18ed39fb736ec95632b483f44d3a9d0e45d1d50000000000000000000000000000000018b956acc1300e60b22bb936b2b52e2ae82e256f15f1415263157965179855137715c321d3765c5227dacb63ba2d6225f0056903b4508cffb6334bb5f645cb553a8cc61ea6765283f933686f172f8360000000000000000000000000000000000f5372651ffb40bf853f6f8396a7c7483c401b89b67e098ea888fde8d19e7552a006a127af1f3311203434126ffad85800000000000000000000000000000000050d7e89b21c7484cc5831885422fe7aa8e898df85cf7a3a275370623eb9660611610cdb829d3935f0d0955e0ac97506000000000000000000000000000000000f83a3f79f1dd110bdb8521e18a64490d567210801d77fa3c0c6e5cbc7285840da325cab7ab08494c8d516511eb189dd000000000000000000000000000000000f72904131be66380c5a18af4857ada7c15e88572197e100de1cfcc9fdb4306e446f2f330fefcccb41b676f24e3e0bf88031f363c8b0062b34d48f4c2e5bdba884005e52f77ac04c2f29dc7ef10fac0c0000000000000000000000000000000009ba6bbf102d390638ceb9259205a1856def2b3a4b5209eb3e4e54074347f71b6c06b70764fe85c8dfc9074067b8d00d000000000000000000000000000000000339c30631229eabc1230240942bdbcfa6e18f23bfbf88b7b8a8fa92f18e35d2f7336f0b819e875ac643b43e6d931e68000000000000000000000000000000000600cfeda6033ff51c3bf9182d22abbfbeb6db46c0fbe15ba82e72fee483744ba5a57ab2eab6f35927b4ba6d2b150063000000000000000000000000000000001530bba4db8a60bb6b7a05f72dbcd23044011d75221d114b839aaa9535400874472f94c849597174322291b5cfec4974cb146e27a9d36dc698e1982afc945af9500fc5aeba719d06d0c4e4eb245034c6000000000000000000000000000000000c636ac98557e22897fd101dc6c54d87060f460b4cf2c5a88ea14641e2a8a9395492fc5a946eebbba36dbe38f6f5c0c60000000000000000000000000000000007fe3a557aa93f2e9aef4ffc55d39a9172475e6595fd57409df3a7fe3d11558c4d3dea3396ee62f61190add83b85813d0000000000000000000000000000000015b04e0daf4a10541623e7523ac5fbe57dfff9ac17afaf4293c493c1982f3395980ec63046cb1d424c6dec91899202c10000000000000000000000000000000019617b191e9e493751b0a02511a18757330bde56722a72a29a399ace983db7114f84795e2b70bc9d670cc0095220454ed983f98fe5112a55c23591bf4e259d072f893944741d9941a00f907749e3c9990000000000000000000000000000000017472b8c1cb3ec528400649fe7c39e3908b16ed69b42d967e4d225b694544e8bc7ce5bec87019db5539f1de39dc6807a0000000000000000000000000000000012b1c4884c37037a94f84c15061df5ca6c05c5a35ad9b37e3ab8e8297c9000e715fd2bdc3f2b485e86c415bf656392a10000000000000000000000000000000002c21af2933029f04b344be76e18ce499def4a0671a97dd9b6a108d0fb23852fcdc56f882be0319978952ef04a207a6a0000000000000000000000000000000015eb31e80fb162d5fa392fada8d43648ef54d4f9ebcb0e9652dd501f55a8875a16a148d42e283ea8bb2c5a38bfcc8843a62f99ac46f986f2f29f0ad3da0310f061e691955c711850a2816ad7464614a70000000000000000000000000000000015e68e011ed063a9fd9cc8a806d8e3561e4f449526ccb6e5ce983ebc4fc49d61d26dad7db64f56ad5ab0b54fbdb76e61000000000000000000000000000000001617d7387fedcdd772a34b267a44315212d21b798c0fe1e7a9ed3caafb678910d9c9c3bd1fff4a3c8e339d0c90a865b8000000000000000000000000000000000e2b3c9b9cc10f41c4c0129d34c62d526aea47c77ded91a5ca3afa0da1801bba81def3ca66a978ebb2d1f3227ea82a9700000000000000000000000000000000096b6caf7b6f29e91bea370f91c2576c188b08b95f9df6c7df995fc9879c11cdbe2af86809468d472fcac8a89716d1d87ee01b0c9c6a6ca1fdac35d89c803bee3595f03d9d200affc5292d8a7c6720b80000000000000000000000000000000016daa86ec04f57c72395d96b6ea5d6ba7cf2d9d4a50eb90f7121545f17c1ee16216f4086481d91e59fc5ed8542baeb7e0000000000000000000000000000000017a783d60be67206241e0bcad20e371d86d47d88ba1293b73f32999b0a1646967e5d031a5b28517f035168d7c7d7927800000000000000000000000000000000058f24fbe4e9befd8abe364c961f0ca4d9083260234a939bf6103a3e8f10a8381a9e3d74af7c13f159e5c7dcf456df00000000000000000000000000000000000485c9448fe3a069eb024ec43aaf563a98da09c02c294da2a94a98a95430e25b062e8ff886fb5fca240fba1abf7cee60297fc700698c56877be6764f48a836d210bb33e99b5735da9837882269af9b45000000000000000000000000000000001230577527a0fde2e8e66b8c4d17594bdab8be1339866819c8890c600b35889d1e3a749fe15fd8182001e30e6420ca6d000000000000000000000000000000000ce03cccfa87229fa8d560884d8c7963276d79ae9873a23d550b4555cc4bda35a242dd2e70cc730b70cdf898609b3d8400000000000000000000000000000000174aab1f142fbb7a45bcdffd64c2d38b99c8919baf9651aa430bcd39613d7565196c18f0f4ee6fe05f5c40ddbcd4a67a0000000000000000000000000000000011dd23f59ca2a033ee5dfa50afb0c7ddeaec6d4f50e1866cca3f061fa03594216f005bc65b2c97ed1109c305e16222671b7ac02db15cebb8af459290c35eb5a86cf98b86d8336764c6bdda6698b49b640000000000000000000000000000000014e1cdf4f10b11f47c15d0b6b7dfccb6081d05d116c8149989cce4f1c53dfcd2d0b7443677b03d037710eba813f6f597000000000000000000000000000000000c8415c7d5508010e0db1878ca663d359525b290b2f02c61436e945145a7a4e1b3ff4e27ea1b2c8d3adbe737d8291b14000000000000000000000000000000000e424ece68003cbfaf65a54dba51e7b0942cc53b2fa9794b4deb6aef1dc1ba1719cba285f9a1a59e71a881eebffe2eb9000000000000000000000000000000001404f9a3146b7201b09c5fd678fdbf2111c48130e82cc95012e5aec1df7e64a3b3c727afee4f603e620925686e126c0f5d1a3f78a2c2ab7b85cee68ee670f50a176e988a341303afb7722917f442fab6,000000000000000000000000000000000e9f6bedba1f6e2a3ff33e0e4b18fbf8e77558bf42e89023df6338b03a648c591486c63c2ecc8ecbbce23b3ff9a7ae6e0000000000000000000000000000000013d2526d83b4495b5af645d5a1af7bd40bd0ebff125e0fa14f10d1c08511dc29643dcfbd25ca0bee5705a56b26c558730000000000000000000000000000000003fa442ab532094d47f1a9111c87deacb15d80ca6e76bfb5f9b9a209bfe196643351d778b0c6d6b274b4799f733abacf000000000000000000000000000000001278d51523d5d9aefc0d3783e745da54f74a88620f2161090a398defdebf82d13d5b5a21a5cd466352ab8685b034fa89,240480, +000000000000000000000000000000000a497e74635fde8caaa5c9dfe666b1b40732e58b93a72d39c8a60c1f4b262e1f18f62229a30fb8257bf895352ac4d249000000000000000000000000000000000c1b2fcbd7f78d85c73ae55f67110b575750bec353e55761de0ff09a9f8a2d916c336655d8f6a78dfbae13fded5a9c36000000000000000000000000000000000173893333d998dd32cc3e82fd7ad8ce77003192ad2bfa1b1d2b43f9466898313276b922f9fbd8e83e86b67acfd9ad780000000000000000000000000000000004ed01b702bbafc73dc1e6846bc944be297ff08d1dfef397603294c7fe11668cd0670d386a8fa0f0f02c52d47f54a11b34aaf86eb77ce03f1d8eacab84d5ff98a565fd33a9a2c40f2a19d7c041a7e2a6000000000000000000000000000000000b5ec74a2150dcf5ebe09f39234c4dfec623318889d92b0bc1f197a69650bc48d28a1112306be763176b691c6915dc7c00000000000000000000000000000000028db19af73ffdd0111dabf9c7d6879cc7389320a249f108b41be8b1d4c259d5889dbcbb48b30a288e26cd9926682d1900000000000000000000000000000000172fe526c62f9cae49e6d3284170e6339d5af256441590cae9507c61f987eb495d340500cb761896163cb8ec631434690000000000000000000000000000000013bbfcf9cd3167b47b48af5f5ed7e6d45a5fa38192756c9e140eb89a85c75602814f767c57108cfa2f726e71f31548f808ab2065f1d2278caece0939cbbab4bcbe3eacdc80cfae6e4500a5195883de0000000000000000000000000000000000052d7a0f93142b36489cfa21d76c0eb96904a3ddd946a53b8a6730036d88d30336fd8aae3ab29ebf62a48c6e849ca66200000000000000000000000000000000198350abe8cc91bd675f26516d771422c128d5dc0af844c6c1af07bf04a1d3ad9654cbddf2de5b7828d1446c45e7828b00000000000000000000000000000000198f35692d5face8dda4b464ff48d650145242852fe189748783b1a2e48806294368ae0a99481bfe739fb4962f3b86a4000000000000000000000000000000000e3cf2e018a7e0acfee25bc3a82cb282cb377bbd72ce3044dd20e109d948f68720c27aea3d4663ee45b2de6f178a00ac58c69b55bac97a633f3ed7816e77e2a26cccc029f7e7429c86145ca4645eb41500000000000000000000000000000000150e6b03a3052d043da6514bf4ee09baf1a35b2a909473db33ea0bd4c6af7d7aee9a8366c1d08d2adc5998635eb0dfb0000000000000000000000000000000001370c2976b0d36fcb955e797087e6ccffc851d2450cd63833d6cbf52e1fccbbbbf9dc695ee45c7df01c2828051bcd79700000000000000000000000000000000048b5fad2fe0af7ccdf675328d8ff5e63b564d8436d04c55b23b6ab7d2aedbd25d614d1780963fcd03d569bed2085bae00000000000000000000000000000000141f94b4e7ba542707d0c3cb69f8dd79e499602952be2374cead840dc669c5ac57089c5fd60c44291703b872098fa2daae7faf23e841bd53683521cb3cf215577fa51f0f751714b6aafe5c740f66208c000000000000000000000000000000000eec51e0ddb8cf9914304e7766a7418e2854ca71367c1d2b3875c12b7dc5c7cc2fbc136037bb7ff72458027104ed3f270000000000000000000000000000000009fe5e8d1918f9b5865a8b97c2c2cfc8bd750a0ccbe2942070827a09d8e41ca795a86b2262b10462795f833c73e788ec000000000000000000000000000000000b95c9146f3f560ad880ca905b5f297e48905680b4613e91f393f72ddb042f6a6201628fb5f75fc23f2298cde66a6df5000000000000000000000000000000000a29a8fba7644ac96d77ee73a93dae23b03d81a57f6cd8cb4594b23571cc1f658f163081ae50d72e09c6513d1cd2c8bf72022cdd6d942158bad47a53a9b0c3be910a41036874975724a5cdd22c012871000000000000000000000000000000001807dd8d2bb40a642fef693739b1df12fc787db0f031306f31970d0f59f0c97c0894afc34b9a9913726a20dcb7d5191200000000000000000000000000000000096fe8bb5e911c1ed9985ac08d864c7020367f4259a0d074973a26cc421a44e8034a7007f6d1639285cf8acb8b2d64a60000000000000000000000000000000014026d43eceb26b9ab5bdd4139d4f94349b273e43f27737f9ad26d23454cdb1d35ea793d21f057359d28328a82d5290b0000000000000000000000000000000003dda2a84bd1f92524a8ede9f5e81f0f64b41b24510f4e0b8146496a776d5b509968f188c12c2d66cf755e5000cddb3b800ae0b956e38bc34cce55bb7e88f1370a30fc8ed0e3f1126c68c30792a2cabc00000000000000000000000000000000011246ad07713d1916c662679ab757c053e33def437d7a976533f0ce80ff6ffc259489c26524ea96898c3747c4127539000000000000000000000000000000000acf66265811a57e47a4c98b40b12a37c6f439550b18215fcf856c167b7218397d7d559f852fb45077945a5074f460be0000000000000000000000000000000009badf2799f1c43a2e3859123aca91e894f86d6298a06a9127249100ba270f2bdc79cf511691bf2d7faa45ffa17490eb00000000000000000000000000000000069438b1d53efcc4277ea7b41cbd28a19f80b5380136f62121e766bd2845e13d5cb40b2f15d508414876ddde491a3830a57c3322133d6ffac661c888995e7cb067ca1309f3e9178a266f1a410a79c01300000000000000000000000000000000112c4cc34da9e83207b5ea8a9251ac5f004546596f2294b3fd51b77ad8d8e98239d53ec4f527c7280801233175500b1b0000000000000000000000000000000011dd8627748c9a2b08524f88e560cd3944bfd1fa17e1d6e2e9cd025b04f2e3ed35125197136afa2848d24fb5fd19508900000000000000000000000000000000093219f9ffbfdaa60c5965b45a5d5bd923eb5d3971542ac147de3f591a5fbe31b30704a0061a524e2ddd05a45dfcb6a10000000000000000000000000000000006407dffb5580790e250a72dfe68a488431f61f45ec9df279217b8800f0ac1ab585d84e486487d5688735fe5aae75bacebe67f3d067b0d011abb31588d1b2fa9fdf8a56bc46b1a0196e926d4ec73040500000000000000000000000000000000107ede23f8e4f273ac2647fc251008905966dde32339c023f1da3c4d35d483a55b54f4157a303e68e1dd7fa3f3b14c8d000000000000000000000000000000001739327f282812fbcbeccb12e40df049284562d8986b8d4559787e1d5247eb6c83d6b838d099f36d8d0e32da2a7999a10000000000000000000000000000000005e5b6b2baede3ceae776da5adf075c1d774e83d6129ccfe7e835862686bb4064b187cc0be0cbfed37e5cc039f3a3fb6000000000000000000000000000000000249554dcfa53f73ef8f08daabf20c55301f75c8ce095cd794061c55e195221602a54ba54260980bcdb35685e41d0f4ffa1d6d0d1876a67337d66c596fbcd7eb22ee308e4a5f66cedff584f1441be6a700000000000000000000000000000000048b7fc5a71787231f1c7ed2134be528fc8d8f77102bda806ccbadf4f9bed79ee94b43c0fd3e5b1d776fe73d786872d1000000000000000000000000000000000152a1f005a64e16949d7249c3b391d5c1e0ded4893d0ce926cc666f0f88b64e8dd6ec4f92ddda18127ec24cad7e40b40000000000000000000000000000000013a2e1e7958a53307adf3beb32a88b7c493df0e37e074c9105da3c09bbaa01fed092fce2b1800790c6e8af3d30ec5a81000000000000000000000000000000000e2d405806764c75122c1b5e410673b28759f26af7489cfa6f35c6c0dd16c508af045009853f3329cda4a67948232bcef0c4ac919efdf3d0e649126da7f8ca3daa30b6ca6f3be6854c0f447a63cf2110000000000000000000000000000000000a71d61dbb3ae37230a2dceb54061d5f8c1ce645e20ec39785c229cf79aefe238959b2745e3b50e4b3c20c7a8e2ae27f0000000000000000000000000000000010e82b8dd5faed6bbd5755c4e5a88edbb3511d3f4442d1e44b82cf72a6414bf6558d29e8907b07f71c00f537637605bb000000000000000000000000000000000d8c93f1984b742b5a02777b706970215c7d8eeeb7377cc26c3af9005648c2eaea7f7a3177b6e049b132ef6bb4b188da0000000000000000000000000000000000ff082a252082499d70eaeba6d5514fc8d641404b48b2ecb256eeb40d9c6b68ad5af58556c9dcfc5667621c549b8ee760d8bf380bc2223efc779a747c0a36f8c2b18c3e821e96163bae14b18f3739f9000000000000000000000000000000000f4cf354b8de6dd2231448bb235af3c84daac2db49abed345da6ded50eae93982a4f2c27b07ce725a062b07fdd9058fe00000000000000000000000000000000076cf19408f0f0379c7e65a6675b9856782990986f5c6d7002e9c9c74b95ab875924bd7ad5e4812844f6d1f530e58deb0000000000000000000000000000000007acffe32f96f5e56557965e3db8dce87eb7140d93608cc003bf4a43fb261bb7360c576da0b7c4dccdbdd9cc53b5c5f8000000000000000000000000000000000eba1c668fd9323d42d6a82d9f075cec2d278cc57122e25ccd72cf8b5a569552cc6b0e9f88d23b9b7af18f3bfa0cc820006c3a7b5ae971e4b0ec34a1007a02cf8c55f067115ba00c5967f70a7dcef9d60000000000000000000000000000000006157cb6e2dfa2733d4c489ec0334f0303ff1ad410f329cb59f99a5fa3ed2cf84eb7d2f231078ba5db0954badb58425f0000000000000000000000000000000003dfee394f4c140e2cad61e8675b26f91244880d9a0b6798d6111090dc9d080563db5c89b7293dcaadc74ea5849a08aa0000000000000000000000000000000001aa1e0683014d5b6f99f469a0b7beefaf05a7ac0298bd1a3e2da409f6cf856f70bc067610fd705a851cd70054df9562000000000000000000000000000000001571b129f69f3a6717272ff75351fa053f46294f68ba3f859208d6c91ba5eb9a0f2133a5e139d04e38c7f7aa303451768f29e330b48230de23e0393bf1614cd26685cafb899db5a164497955d3e98be4000000000000000000000000000000000c4e84b7c8e46daea67c8090b27dc28b7867b89b92f56232bfd8ecd9968b865a057957292e79c6dc08162f9e91e6a4b2000000000000000000000000000000000b8d1eadcf3f1de6ee608a4a0ebb7defeeaf4e251bf07717a6a8e50c07223ca32a2ef290f26d0de14b1942e02acba39a000000000000000000000000000000000e901b546a4d3c68e4432f376c97f42ecf0724777956c4ffb1e6ca4fda562e57be788ecfa45ba3afadb439c2ea546ff30000000000000000000000000000000007ffe01da4fbda9fe5d47c3bedb4b92fdd71ad73fa272b071a7a7d1cdce7743a535da7dfe05a43d03368eb97fff54b2d861ffae8f62572938925593f7271a56e0f559b56bf97c454c38547a2185e2ce70000000000000000000000000000000008da0fe413e31ca68f84032f23bdd5399e01eb3b5ae47033c6834a39645d7b5cc2ec937067b91ac6d83035a86fa841f9000000000000000000000000000000000b950b982323f747782d9065dddca5332940058a604829e31560a6bf9b03ec72b09cfb87a1cd244ec694c7cf192c37ac000000000000000000000000000000000f4afddd25eac15d2248c71d76c9aa27323f75141820efeef1ab4f5003141053f138d9a7d1a901961d0f2c210ade27ed000000000000000000000000000000000217b1800c53d53459b00b8e463df1882b2cbafe85043f08093a5414e58ea7fd4dd933c601acfd7c154d0e4ce187468a2dd907071c2d39fe710215d174452459cc31d36007a1b5570a27ca2e42c8be55000000000000000000000000000000000046aed1acd19201553bb6a88fd6a6c0525ed44822d2a4ed3bca48a0a2b75e76cfcdced8f342b81ce03ffa72e667b3bf0000000000000000000000000000000009a5adbac43cca3402db016a2138342fae89285ab1fa16d7acaa9c3ee2b4e3df2641f7392355996bef7b1578ce1ef119000000000000000000000000000000000c8ebbcbdf2ac3fbb553a2e589f4b7c259a1621b83b14fd1927f92d9f6cb27e82507d7943ff5930f0c14b9fc38c9857900000000000000000000000000000000105b729f678db31d04ceae0aa37f9cb0b0319c4da9a1a4702a11bfe3a5f2f1f2af09b9cbd5ded5a930e2e65f4279a31699893c06db2dab559f2c374df4298707dc1815e55034dce920ae7b1df2ec8d23,000000000000000000000000000000000708e9b926f2536731b02b6b75305c549da58e312d9c53701a993624697af2f3469af34dd4634467f8c98a0f721cd9c00000000000000000000000000000000019185b84fc0511a048e3c39bc10334c91dc1052d323a31c8bf325479a2fa3e4228f8260c0e725c2b89d5a0319e6fbed70000000000000000000000000000000013c7c441d5cca81b48d43e908d6a3bf8b5057cf19e4884227cefa9b235103b46edbe01bada06bb9b620ebbd016d537630000000000000000000000000000000000431182c8a1eed66073956fe5798a894be396403c072e766cdc262b719d1779f960f4aebf61c1bcd4d005d3c7413e52,240480, +00000000000000000000000000000000199f555aa5c651183f52470e36cde438422f41c9b2d1947510665254b74ba0bb9cdc6e6a1283b0c8f58d8f009eec46900000000000000000000000000000000018f1d8f22f43b4649300aa23ac92a2e8f17e7e3853b912bbc8e90588125c371084cb224c2d54dcecb4946ff6db53cd02000000000000000000000000000000000efed0bcc83a52f0faf9e260815da8d4e5286396081268485aab052a96af8eea0112be6cce1486b10b60551ad6c810780000000000000000000000000000000013a3b1ca3b9b7d50083c10d36997f5f521d4426af8d2905aa5d074ff37e218a0c96c74387485c2dae24c0842b7a74cf0d8555388bcc6791802ddb6c0f4cde44f45ac0b5d7ecd918bc34fb9fdedb65b94000000000000000000000000000000000efc5a5c506e94ad2754e235e2da866d9c46342f14d518f12510c93f13a619f6bfefec50c146d6d6170f190497eff229000000000000000000000000000000000fb91f34356005f38c9804250549554cfe67ce195d5e218e4e1b1a4fb904257bdb68d6dfb013e8e85fb5a4cbdbf0f21a000000000000000000000000000000000f09903db4c41fe3f11c6f0cdb7c31a131033e30f52cb66ba10c2e7da1ed8a225ef280d313630121701f9a490e8a0f5c0000000000000000000000000000000003484f7e8f7d67ce40b4cccef110bc255d91f61a4e1968a9ad37e25058eeaf39e9f1ff89c9b2e515388a7c1b49a84a2c33e5999498978d14c9de06f7beb2fd870f6f16dc42125fa496606e65c7466c0f000000000000000000000000000000000444215c3d4a7d62201ea1b69890e2ab90b5f5c6ff56fdc9908634c7489e785521b8dcd7ed409cf09c585cae8414a3250000000000000000000000000000000002d70674251a0c9ba76b8bf3b70547da77cde5592da9204954abd6d8aec82799cc0fa4fcd42139357043fc867b3d0e0d0000000000000000000000000000000018c57fafbad2351a3da695f8b523443e8c763dd7ab875caaa6a494a498cc40b1c0d44488e2dc80d1f0bce00a2c90c67000000000000000000000000000000000125d5a87ee3f558b5e1e7664b0cb95c195bcebd5e43b930fb47d15eee4fd50b3fdd0a401c9bb011c326acc77645440137894a51dcfe5a8fa4da1745a696c870b353fb03a31238b8744840a78084bde480000000000000000000000000000000018790123ce8b3b72d626493a16936c47770a9b06ca45b17c6fa5c7759f088cf98de8ce7b3b5d6082e9e42b39acf76f79000000000000000000000000000000000fea86cad8b40f315d8378550f6d3d831149339a8e8dafa77295859ddd2417e8f5c0ae2baad25fcfe00de14f45a537170000000000000000000000000000000014ad78bb2bce966d52b1fe1a273bc07f2f24b354465edef6dbb1e0123c7c3d7550983b3793ff1c7db846e88eddbf33c4000000000000000000000000000000000c0daa6fba40ec59f6b34d413130df5d9137297d1b7b71b83114a6570fef8e7f83d6f5689527164782f92da4b1ea12e8fb6a294589c816e18859cec34262df6490a2af6acc7daa3de861198c5bcf4b13000000000000000000000000000000001186b7c78952e5c32a9393eab07ad4532471595bc2c5d8137c61dd7fe6b6ca3aaba82dc205a559bdc15421a001b7270d0000000000000000000000000000000012d56b6fcec3d6511d2d723601cb8c9faabdcdd12efdd0e2bfd7c9292f2c3bd7f39c6e9aa53e6955727f88ad69c5b4f10000000000000000000000000000000006a5e56e4a42b04c03619c78232104f1f1f39e755058a19354eb230f2f09bf486b2586817aa6b88f27b884957ea0226600000000000000000000000000000000118c8521dd4866df907ecb252d9ce7a489f17d0f240d054a5dbff6c35895ef20b205236aa6e5be6f0825f9df87878ab783c4a3460caa35fc0e7342dd2da5c7b6aae818eeaf5a2cbf4794387180b95dfa00000000000000000000000000000000092809d18926c20456857826491f55cec17803e9e7d43f22faf4da18ede3bda15e3319539017ab20ed1de2bff490a33f0000000000000000000000000000000018d736b967eca64234f4e0018e5d6c902608e265037d9b8ba42dcc923b84ac62599e153e1c7d00e552ecc5aac57d1a5d000000000000000000000000000000001804aee99219354d4a5c46328f0658a417c85c6bc89af6db29a4911c4b0cad5638fac5ca61cc997fef3450cfb4a6c666000000000000000000000000000000000bf99dc4a400adda5bc89762e9011dae8ada23b284e52e2d49f75f1c75247f6282c95a36f7a72f896ea308131215404bd2b65c1580bb46e3a4cd9d9c4eb7dc998168c66982448abf3a4e08cd12f612b1000000000000000000000000000000000604f8bde85c0b26894e0de155cf896c911bca47533362a0b59ccdad0dd64108d33af8262d3ca2ca399306723f2482a8000000000000000000000000000000000ec10d3777aa54cd0cfd84b4062092ca3ac840a24e8e8aaad5f4c275e4d45091f838ae522efb1b2a0fa42229157297d300000000000000000000000000000000132cc70638d02186116773b31ec0e571a55c1cd78ec055fc647ab09cf4d3c543e0552d559b3daa4e99cef031e583e61500000000000000000000000000000000194a6a32a269692906b64feef9e4e8cd204e560b98db8c66380758d2123babae871273b4c571a1570a317c13a51d0fe9120892aded230949b83bfb2dbac054b83a9dbb852bd0ad85dd1d7f715852306f0000000000000000000000000000000016d05912dfff44912bf34f242ac85eb55bbb8a21625d45496c76d057f518352528c6632d6e8adbbccdd5983d13c26953000000000000000000000000000000000b10aa1402c15fd601ce605ade8f25531ea8f95cf592bf4ed86c4a3aa847dc8aa2369655ce5348da30a897fa8d71ffd800000000000000000000000000000000183f5a2f40da0a0f4598c6b9ea7b99f8cda1d85cec0e6da5365d7eaad1e9a3167bd647e5e654985f395ea72257f61e5d0000000000000000000000000000000014e615e2d5072c1b536ffa607f3a826ce297800b0da329fff397b6327800ecdc879e91f1e3ebc26c18e188e1ca66bfd66af9777a58539e5aa8b1fce0994e0e1cdb5877d93ed4db715c5aaf74d6a8bb1a000000000000000000000000000000000f3cd275d72a637bcce855e2e20727c6e5a1f15bc8d799231d3a7f61311d4cd2f58cf38448675aee9910c1a3d0b576210000000000000000000000000000000019efca445312f568727948c803d06b8d4e2c5289015740f2626fedbc0047d344aead06ef521ff7e139312fa41d1c107200000000000000000000000000000000141384e1c9f79e38bbb0bc1025c079741b93f56e150df58cf9a61ec27c2877c4188866fa197242965e3feb47a78c68380000000000000000000000000000000010638286faa6c45cf028e8e3d200edcb348560e2e35902927391401b3155240b62a40784db88e02b874e128e3a2132b5f37e2ed8e96921a0f9bff8b43d432b382d7b59938e269c381351ea49b8c1ba2b000000000000000000000000000000000c7fc4216767ed298206bc142862c138d78726e2d39afa18fe5732616c73a965d95cd2032d4b2f5a4d562be48ba6885a000000000000000000000000000000000928bbbd76b87f58ecc850e1aa4a2be11b15a81786aa7ca8cf0f6cc342db87b66c435f009f88ad97b747400fbcc651e10000000000000000000000000000000019f5ae9f06f2bc27a39bafacc7f3745fcdf8c78c9ae8a3c066ffd704aa4117eba773691ae43387b93e86d2e2de3688700000000000000000000000000000000014360a7ed73c05ef5fe651321f7e839c920bbc1896636143b88357cbf76e15da839bc7e1f1e629768d447c9d313cec8e23f4a77a2c34a370a9b59ab1cfad77212e433464d0195f0d2fd20c69141389f50000000000000000000000000000000000b9d955f9d28f9485d0bc4a961f0acbf09ee5fef38ccd81a2c73cf87a461ff1bf28d4dd1e0db3ea522299af67bff93b000000000000000000000000000000000889061e71866001b0760f68e20c7c0c033d782e6e6752f11502a0e8b6b70277a985dd13dd83424d1e5cdb9eb96a01c0000000000000000000000000000000000e05a26686667f44de2bef53c36c82f1fdda13dd3f7f8fe1fb026273dc4dfad18241d732ccb757e2b46ed8317dc69fad00000000000000000000000000000000038b55685b02231905dd9a62a709c0f015cf5650b3fa469462b3e9d06e3af8092d998c8e08ee61db1fd5583b0809a38996c59b0bc6dbf66f42cfee34413cc4cbdae7a61e232757c75474818591764d6f0000000000000000000000000000000006649a8eabb25fb7793344a0b29325a88294343f6c69612ee9d9002154a49791f6cd7b37b2bec69fa8ce11722e9f8a03000000000000000000000000000000000e10f2f3de16fce9b9817085f0130e1839d9aae949170ec16834732a9b12f589a2b00f17d2fd3416ddd020b7421ca20500000000000000000000000000000000016b51112b3c7c42a8c2a0fa7f286ec05cd07b6cea5675bf1132de99cf42b450b3c2a8f02ec821529a14a2a0fac3a751000000000000000000000000000000000f471ec8b65bde22e003500d1d422dd0d163abb424dd261fac588333755cc5124acde328085d8df852c61e024155564781c180924f1d982bf4b6a2bb1cac590cdfe84198fdecd87364e163dd988f9b1c000000000000000000000000000000000ec162d22b6516c309efb6a4577c5631a5807bebddc5fd1be5446e4a64785d49eed80eba2e89cfefe484ecb8d50440a600000000000000000000000000000000070c252caf6c56018af6b281b829a4fb8dbab850ba0446d233dcd4d87bebac00e3e5070bd41898dd561526498b153199000000000000000000000000000000000a0d76d1205c1f520d82c85bac4473ea7cf5f68022d95b1f04d06062197973001234d86921e70a94e478eea85264f14a0000000000000000000000000000000014c6a07f0d568f2103ccf8f61278e916458820bcb61fd91479b0dee874fe36c063a34bcb14ee434b68681d297637b5bfe44748b9eb1f44b5fb143cc8deaad23047bc5ecb8059705e7905c37625d5e2d3000000000000000000000000000000000aabac129385d145243c3a1f357ccc963ff14867ad039827488128ac639dc62fba82ace66f889b47d8eac39802bc1af900000000000000000000000000000000062bbbe8c72cd6f8626484bac159b7e28c6c8c3261edc6a05a30c308cc9e56db17eb58f62ab755f04a5c87e58c04c7550000000000000000000000000000000011a4a439d18501142350229778f67bbe0c9b948229dcecf70a8b09d1df6c54801a111c603301da2377d4198d09dd51e70000000000000000000000000000000017de3d9bc6fc5f415d04ecec013a635fa200699c496f4d0bdb5cea7d446274dddd0a7f6b06058fde43fc4f1457361558ae04d7723b7c9cb0574ba744bfed8f8a347ab740bdab99136aa71a6d635d0d98000000000000000000000000000000000c86590a02fb5c9568af4e69611f09980cb5a7e040c94ecdbe64e40005783fd3305a5657a5c6bebca7d20ee123a872b4000000000000000000000000000000000bc873a9bc694171d2606f4efa409897e03198a61b1bb16ae90f0d12345d2650d93c46e0c22b717e2f0504b8983515990000000000000000000000000000000001df9160ac3bc54c0121a9c69e9065f4266202f755c961bcb8641d13720b82ebd73eb3804ba44769fb2d75144442f1c400000000000000000000000000000000045e9c8ed2fe1e5c9a2a5bda75dd60f6bb5dcd0a805f68c1f662a5960b025ff29c8e21857d2a61bcd65c747d2a2da8ef6a794685a342ff25dd706e4df725e3466889d8f08a27ed2f32523b117f01a84e000000000000000000000000000000000f94df8d267339bb4f51b21014ca6d685f7657d0f0bca189e53cf19e0e5e05bfad773c0553daafd80c86f302b1907ba5000000000000000000000000000000000d92905addc028a1dfdad50e909c77662e10e4689e7c8a4a0174a3e1c746b361665b65e17fce02b6c067a5b8d7a6a6f500000000000000000000000000000000183444f0665790c48bd3c07545115a11f82463a092774234e7b33aac1094761f213235895e5e61ac1b0a15603bffe2140000000000000000000000000000000003cc2cbbf181fb023a5f6088d8a9793b17984b3dddc8c3ef1a9f82f8f436002610df60b2d35be212da9945bc8108c0bced3f23c51953e46d400802dde46c374178ef379d5c1b04d25449891f0d5623e5,0000000000000000000000000000000011f85691799cb76213068ef4f997af66c349bf707295b969d85fe637d4eabf54f3f29e739152aba5027c1b55317a27210000000000000000000000000000000019627f9570f07f44f326b5b3ee19bc477e92d813be2865e00da93135645e02e6fe5507ac4d50085b02149667794609fd0000000000000000000000000000000018fdc97bf0f88b2348b436d70ac4e28b5ee5ba21e21e94808b8b9e401c0c7d688974fe203ebda0b23abe38018876f4930000000000000000000000000000000019e28c9c936ea5a0b3b41871c3afaaabd53a93902e44a96dcb7651bce7e6143d81cb695fea8b94aa32c09ec030dd9ac4,240480, +000000000000000000000000000000000703481cf48efe78fe8dad34184edd1765a1d01846de74a45b43d4721bf1af116c229f969868b0e6e851f22bdfb0451300000000000000000000000000000000063d316d495b1e82380c5b73bd61ce7f2159e7714c50e374e8a91dd56731dbe03a3378bf8afeccaba5fda73b4c2dd166000000000000000000000000000000001012cb2f6578065c93aeb673f447ce95fb42927ef9d12e07968ec04b6a604d785944620043dee5de4de33d59e67d64f20000000000000000000000000000000018cc7cfc360801ecc420d77ee171fb3eac3be0cf26b3f36a6cfb7c6adae7bb74c18071daed8fc56b8fa639ea138267928c8e071da1ae8f615631759cf33fdb876ab289a6bcfa6fba2693a58f8601dfd10000000000000000000000000000000011e0dfc437a65c6fe37bb9e554b5138f68a3c52816807bdf7d98f13cfaf86b37e9669f4e0db1b7865d910a309f16cc200000000000000000000000000000000006f2323e01591a7db1d3c7fa1a2ce4540cbe0396cc55baa3a3e13650a6f6b926a7cde0eebb45d359edd52137152fe360000000000000000000000000000000000066bfec8df4ab5f5f5eb369b34e8e22fe32abfc00ac58b68f2d3841248fe5843d6d29ad012249fb9ee851e40b940dc2000000000000000000000000000000000f4ea977d9249bc05dafb682a863ed17f7fba0a06c4a13cdf5a836748664183272eed96bc4109bc5beff61c5469e221f8371fff9230243d2e6cb6bdc4cd97260a8cf0362d18b9ba8df512d2a6f5563dc000000000000000000000000000000000fa3e3e77112774fd6d6b560ff88cc92ef8d009675d0ed65705398ce727cfe786684da50bcdfaffae97d19bdaddd81c00000000000000000000000000000000019e98284b8b9f53faf3b73902cc322dd80fc330dcaff2a7fceb55db6a4b0f7f667297f5e4650c797ee337985dc6b54310000000000000000000000000000000004e30acf2ba66d842575c8679caec607fd090f0aa2350464f3b6eef22e2b9a1d9d5fabb0f3909f1c19f6b8f27c53b040000000000000000000000000000000000ad76b86e32f84ad74bac68909da0c271571606e071b13bd92e387a8a16a1c4002c5a5e94ecaa1e8d2d6e051e19a45c763016c9a9cfbf336ebda090d3f2a1a1b265787e1917f0148f82a9c0b66b21dc100000000000000000000000000000000019bd07479b234bba974ca2f39b317d5f4be33afef66c1d69e53c44cb5e44c679775ba141f82486424110d186561777f00000000000000000000000000000000130002de0d453abe9052a5f70a9d55de74939d1c8e6ad5871a669a867861b1359322eb98539f4a21597d806aeca62d18000000000000000000000000000000000b2f0c649fdb37216c10762f510c3bb4c789dbd29c4f9a8ff39f74ed1a96609c60473a50f5ce3f6535e4af0f2f0a150c000000000000000000000000000000000893b9af710787361a32fbd19c380161c9a214a1bcf3761563424b8546f6068ba650d9caff3e42be63ebf4b6afa2de516c9f679167d5fbb29250834c9f65d3025606e2af20aedec309718f95ba01e90c00000000000000000000000000000000019805c0de5e232632228e2772dc79712e3d863bd6fe56932b29ee99870d2ce5eaf90c73632d1dcddc093e9b6b5b0f1d000000000000000000000000000000000405d77f4b3c44f99a956ef375879e62df033aa408127e0fee013b74675a8c7d999c6abd30f459693086bfdb326d67af00000000000000000000000000000000110f2c231998aca3d76e40055a05feb37eba76cdd10106719f2300f57906424d7eb6d9f85115b78b7371ee60e26d02b5000000000000000000000000000000000593a4721a67caa7cbbe1566611a1d48532c68adcdbb67f362c9ec21e08aaddf6b5e09a9a96df9a89bc25f11665f3a36aaa3300f5a2fafab132f5f4662c1d288210e7502ca2472d060aeea6f2eab2d7100000000000000000000000000000000151758f1921743d116f1c4adfc09cb68b3ff911329e2f6d6bcd04beb9c109568c796f328e1f04381a995fe89aebbc49c000000000000000000000000000000001388c73b1db46bdbe70540c99db46b730e157a23afea97648d73f9d5f7e8b073ed665eed9e9e2500152c87715f1c4d4c000000000000000000000000000000000284ad228867ed14ade5a327ed951ca50c87f0a669e59b7a75d17feb54bc5d685245448a912590179db1e84f1eed1e5b0000000000000000000000000000000017d3da7c167733dd88f1c39315e47cc80c3310cc431989d4cc50ddb22e9fa481c5dc02d94dbf806c4c8da16ba5b24905f6608f7c036c8fdc335601ac55e869215eb4e626f52bae813d45b827df2afd490000000000000000000000000000000016064871cb68f748939a839800afbb018fd5836914a2b76c51818e764628a76817c7ea329e6b2f9de653c8162a2a2e0c00000000000000000000000000000000082fa03cda4c617a780caaecd7c859c5251b56b61f70fb3ea8c05b4c11c030adb8a96d715c1325ef3dce9b20e8065b6700000000000000000000000000000000174a245baedb7e1bf1368212620b850151be41ebb00c977d85da499223c207ab6f1a1d94a51aa9e90d07764ec3615b3a000000000000000000000000000000000df5b81cf4b008480775ff3d7644f546a60382e92a98b03deaa4a20f831e69e14a893ffa731c4ae9ee237d747149a9080cd68c59b1371c7063dee5732182961be90b95247511a5b564d7eee8d2c7c64700000000000000000000000000000000019d36b8dae5e1083e687743f7494b7f9dd0923024df81e2f83c78743e227ffce588a16630201b9909daa6c9207b5f430000000000000000000000000000000015659059cfee7850e1cf0e49abeef2fe5837cd128742e62de20dc734f1bba343aee1c9f1a59d920a0519995561891fdc00000000000000000000000000000000102b7221257c40d9adabd0db3ec9f6348487187ea1110773fcb2ac5ce210dfed167a4d15e605e9d9e666fd092147a1c7000000000000000000000000000000001402ff9770d27d2d82efa6abe4a181e3c1d944e97a06f670d9e46b24f9900fb4a838b32e17482f25be9b6f3240870c02ea52329555d9b79eb1fd6d186df80b25245ba9225553f402cfa6037592f0b10f0000000000000000000000000000000001745ea52686f87a39fa42ddb5b0f69368db3757394fa7a1a93eb20c398c26415c8a7edeec7334df5b15345d6174126b0000000000000000000000000000000012b580e6fd228f087c7584cd95826e56d1c074cf16c35286c45d2067a362529d241c1e24fd22cc9727d423551de1a1f700000000000000000000000000000000104b46c42a706c61610f8c0434894c7cb9ef878cd0234f8aec0825cbb8297bed3de349e7f6037dd19a159103ca7753390000000000000000000000000000000010b781b3cbe6f415af15e37be7c60dc6703e6e79618cb3d8d9a5ea3b17c00822aef1eddacad66a646c009dac887bb070caf39f2a517d432d1653c37fd9a6c4a8a811107dae428f4b2af3b12e4b6acea30000000000000000000000000000000004b172c360fca555e65860c7a294960f506b562e012ddebad5803bc3f4b93159c16cedb73f339def9cd1beaa0912c93c000000000000000000000000000000000242e37775a042ccf59e99da667c67fc49e80e54a1b438a74fe306d668059ab4dc7d9e457adb45e1f91b3e6bef0a130f00000000000000000000000000000000186eb83ce3abe66b8760dcc0d375eb783d175b0b2f36cc08793d8a86cf76b7618b826f50c6b02ed586394abe4efec2f1000000000000000000000000000000000bf780324df1cc5de325a796f1fde367eb52dac76c0632915dfcaf01f5acd6ae890dbfc2e505bafeba7fed8fd63018c2ff0bad6dae80d5f47dd8c208fef0f3046cf1040112d18c596eeb934762977cdc000000000000000000000000000000001231b52c8a081add6e5c250caeb9467335933c2ed66826e4ab44561eda9259acf926f22ad0df8e8756aa51279d12bc9600000000000000000000000000000000051c46bb04d3e035d324de681c772e4561cecc6a5bc4ef0a0cea56618e09b3f39f5085e208229e50164bcdcd4abdefd2000000000000000000000000000000000ad7ee610398935a02c3a7139185409d7fd4681ebb74a239e15d1c092ea913016d3f585d8224cb1d109ac111660a94aa000000000000000000000000000000000903bb16efb052b99e9c46f3478b4acf800a173b35b0079d7728fc25c9415c8b05ad520f31e6a3c867245f64355cbc080d0c40e5d422685c5c83716380eed82392ae1dc6074a7edb5759fa34a61db2d0000000000000000000000000000000001788efb21597aaac29b7bcb9ad6cecb89267c757cfcd8893c32fb13c0f3e1af7fcccb9573dcffe8d9220292b7861cac90000000000000000000000000000000015f85d3686148ad62d7fecb71920981117cb8759ab249d0ceb45f9e4687914536a1eb16ccd0e185d1352a8d2b4a8ee7a0000000000000000000000000000000015d8ed94c0415ee0f7c9854841bac5821253bb2ed4d86a61f494cbfbd61614983e4279fb17802ca68aba4a0302ec1d8a000000000000000000000000000000000f950a4c8aa18f4605e1252c367dba1e170ad00376a8560c2fccfa7d5487b0d1d5885cec16a0a17d81b5a584d473853f7e93a16a443d5f981a02f0b6866536dadd276abc0998bedd76b168ebc8e31b82000000000000000000000000000000000da25ed9154121205ab6843f603a38a6892887d2725f16ff87a5218586c6139188f46da5a42b5e05982468e8115713ce0000000000000000000000000000000013c13ffbed4a60bcb8659013b022012ef3a4400f506d65aff7ffb1bd5a9a5e030a298e417cc1ec8ee7ebc06455dbe61b00000000000000000000000000000000132d83bd141c434326d4772de7f8772c30a6456de7adee7de66a04bece4c0d20bae5526c8eca5af5ef2eebd72c90d54d00000000000000000000000000000000131355c5e359081dc86e0b15c8aedb4f2016b41e8428051f5132258eaf4392fdb63a91452dc56aca20b7ad3263ebc8c92a1d13a64c03585715908744481c79f340b5bdcdd88d685ab8b91722ee7ab7190000000000000000000000000000000012dbe1327162e4176b4988cec23df0c1b0075d0dc51ea8afbbf98f00891511d9023cf7538c5705d59b6d6ddcc90b101d00000000000000000000000000000000036c12c7f7627b6d6fcba9a303248c38d784a3d1d0ff02e550565efbab68c5116e9a88faaaf09bc72bcc3358e9dad0ee000000000000000000000000000000001578ffb68cf12dc9a5ae6fb5d822324cec9e3f576ce08d45e24fec9203d36a6461c5b8ea6ac50233e8893b07ea6e71e00000000000000000000000000000000015cdb43c82b20b8ab270b942b9e625ada9283962a7ce95eae156aa4355e1123ff87ddb1cc85b2a94bf36102ccbec33fb2bc6979fa2e386abec058683c6d74de31af3cac21283cd5e4244d7edd94da9600000000000000000000000000000000017041e16975850e6445c7b4896955eb5eab383ad3c3031aef04e8fdfb65a6d52c9e647330bfbb0f0eab630c9f9ef7a12000000000000000000000000000000000b62757ccfb913ac4264692053f766e142697f598a3fe26e998119b63a3abc7fee03db32a8af36aa21181fe9ea89d12c0000000000000000000000000000000006bbb842a889d7ff3c1eb5e0b16e3a921a11d28a251c488a8a17a29edd93672fd15974a7e972a34c47283c583cf2d29b000000000000000000000000000000000e94e685fb1751f8720b8af79aec7b245ae8daa195f11f485f2c0c5dd68cf39eef848a402ce2342a6b3398cc7879c6010f1937936cc3766184e47f39acfe5af4497e8edf77ab34083135a9ced61d25ed00000000000000000000000000000000100d3fee47ae6c8c7981c8cc615870924fbcb34c2ed817d6862e2e6d0b4612222a4c8332c7d51b58ec59df6832139e1d0000000000000000000000000000000017270fa71c34ec84043ef64c5dfd61614b5b3bd99204f9f70994d71498219818a5f16843c67c668b06aa5ad3a6ba8a0a00000000000000000000000000000000057948c0ebd14664bf33fb282e200fa0e641764a353e8347586465dab0c79ca2caffbdc2c6d60b2d7c8cb6b088bd16fc0000000000000000000000000000000012747eb070f2de18f517648395109bc08b4af3f04d98e23eb6b516199b4eefc5df7d57baec736987139c7b03b573941f639a8b60a1849c71688a11e612b315439161717f525b5deabbce75808470166e,00000000000000000000000000000000128c6c0283ea35c10330502d6aa849a909df6b8dd927a24f08072135b8e21e40c495c42e742160363772569189d73ef40000000000000000000000000000000016d78dba1e0feeab46f8cd38681a9c2f6490ecc3a6e79b31caead256611d133090a4eaed9691a87b66dd1c2ee50d5f470000000000000000000000000000000016de93e176c950975abcbc692384996315a98065db6d6a6214472e5a338e291b36abbcdea1b8be6162fe578acd669abf000000000000000000000000000000000d7155e239e9b15ab64a538b0a0bd53936df4ebdc3ec9b0b1d494e4df780bd014425759e9743c9b262cf48cda01e945a,240480, +000000000000000000000000000000000a653e0c24eee1cdf8e3652809de0cd159f2c541981a4f43936e7d41c0f97ffe2f1e1e0d1032f0970023f1d27241a16a0000000000000000000000000000000012d1d8d2f96db0e5f97be096c961e3b90ef3d88492fb756894979d2e8104791a5b9a43888043ce9e543691f15d2fdb650000000000000000000000000000000006ffb94dc3c2d07830498260ebe4641b2cb64df61cebfffaf2d4ab5b6ba92cd75de209e8d7915ee744c4db5352ff239d0000000000000000000000000000000011f25722cf9db77ef8adb9caa250175e12412e6350b494395a86c31e1f5dee6c89cc6603f1dfd08a70344cdc44aa0c2df3efcda934ec9d2ab05f25d618e5a483e830d0452a88e980589fcd7cfc39e5d80000000000000000000000000000000006177a74e3551770e7d906222590108bae7b97a5dd3bdd2344fc12e7005f2c1a188ab9dffe68f5ffb0cc36294106f15800000000000000000000000000000000041b140c46868767119a6ebb58562570732198854c92bcc070f2a8d9be91282a70c5ab99e75cc9e5064ed628aa5c59de000000000000000000000000000000000f318ee33fccf455e46add44922bb6e99afd4354bbc79d7550f8d12d3de4f75e5ddf4e62624b116f91aaa80a148adaf9000000000000000000000000000000000fe012bf88e152eb62c0c906dccba469abe591687573a59d3debe747b7d895e4b0755f16e67fa9193a2fd338c04d243a4507a696cc57c0bc49fb4d1686752c71c9c816d7d09bd66910b23810d475aa02000000000000000000000000000000000b26c6e0106d4efbacf2dd0d15df17209b1306f388f493c096429c031bc4a6a535b64cb02b400433f948fd6004df2fa200000000000000000000000000000000061853cf1a32fdf4c370cd413754ea584d3722a08d58575075a7371e57a7bdef95386ed72f91c4893377f6b551dd6b1d000000000000000000000000000000000ebf17e60718c8563a1029ba035dbbba75e7191b4339d5d33f64bb35f34866081f26f4815e01b02e8330e7b7e9c428cd0000000000000000000000000000000008ce40f92efb5c5be48c814018fbbe45f1be45f5b607a6600cecd50d8f791de7d91939ab61204c2a1337c3f21b2c9d26518c1259f23de4cecd5e3a40abef5662b497ebaf16240f40ecd651d2ba50af0700000000000000000000000000000000123ef52cc44f36326b33234ab3348893bc722bac3674e43385b201f372fe4ea3569d69d4d561e26f8ea903e017d7376a0000000000000000000000000000000005b1707ef61ff9acb9e8b4dd6922daaaa2d8a7558cb55b1b9b96eb6d57c23f50a7955763c9b5ef04f52b09be8d55f4b50000000000000000000000000000000015b6e35d14da61e7a7fcbcb0dddaf0071d8d2d89f7179f44851947a2b9b0535d6fa86b5cae9713a73bbed909a4c6deaa0000000000000000000000000000000013463e135b1fd460cf042dcd0226e229d60cc2beccd8a1832df241e65a644159722a14297c0033eb499e5890f0caff1e5561616c195ccc1345421d8a6efec48f0a4dc8e89ee89599839efaf95c386551000000000000000000000000000000000fbdf4a533d355e232723fbc97352fc5d7d3d199934883a61a9ea116830bdf9e40d423256225d9a3458134332ef6e817000000000000000000000000000000001195f0ad227941c5e383c48f546be34762d158e6cee585650b6ee987f7b98e802f678abac6646832b30b6e12e90948cb000000000000000000000000000000001820d5fbb5a62140c6e8cd105a70fc2f1ed84e254c839deadae5eadbb75e1c33a07ad12ee92900f55478e91958a3147a0000000000000000000000000000000013849bdcae33fad27f16e91c6d46b9678a00491e3d70a8db905db4b1d2c6f02a29392b5b77c1472052d6f4d49f14a16737c77734125181c72454bb2d37c3725cf1f9b6d6f42b721bca469fec154b3e2600000000000000000000000000000000188fe1e394b567d71099fa13b5c8a5891636d83b6b8a08f410b080658a0663deaae4dca1afe8b9023b5e8e573c752c92000000000000000000000000000000000f66c65dab8e1b2912fd5285a4c87821888532f5107075cdfedacc4d7f75c6a74b4828d0b4c3a2c0ed94576654a7047d0000000000000000000000000000000016af44a6df79c8c9b6f1d8aeca24e024c454d7b94c9ed386858dd35c4158cddcad1207f9fc3ac9e3b748c2314f875dac000000000000000000000000000000000315e5e4f78e9fcb93aac78025e95b8bf82ce4c840cf565e0a868b0aac22950d62f7becbf8039a16ca3ea66a7498327d981483aa66e04351f4340fd2b461165b9a9983e91c148da78d3c8e0c69e77de4000000000000000000000000000000000f9a61dd1b3034b8cd7408b0a44c8d02f4fe0e87778d5d34f5e884ccc9e2d51eca6b6060b46b66843e8247b3c794e19d0000000000000000000000000000000005c47fa7799a0fffcafbbe4694dfe8d0f47b60f712d6319e9a56ac459a636460e700e2af80f9c688208978aec7c413af000000000000000000000000000000000ab1c55fe2207865ecf12e372a341c776d24c08dba10702fce1cd2c01eda314852d81d0ccf1c3423c2a12e8960677f060000000000000000000000000000000014f8a1964aa3240d788ea40bb51abc50fae2736a34120ca9585fb2d5bba4e5cfa201c83be1e00ecd1c46fcb2ebb4eb809913da6f756005ca8ab900ab686484483af07df768209a16d807f8b88b9334d30000000000000000000000000000000006441fcaf5e68b10e7e511a95e56b9613453ec6468bb126c5eb12f204c9681c69b5c296320f92a6fbb0b848f8ab5fcd1000000000000000000000000000000000141de16aeca0a2f991e9fca4b6ce8fbab3d66ee3ee4dffb0124384a7d4ba51864a53e005fd34516c92ecab33165944a0000000000000000000000000000000008543656b5495bdb726109cd98fa18e405648fa88cbe2e5fea5380b7d0ecb207f0343dc7888b9945e55156977336226b000000000000000000000000000000000b53d4e392f304225b1ef363a3528daca1d3a6ad64ee99d58491863ea432a29cde5edd4f390de45a567cf32112ca5929188fb33fb359f21bc5bdfc85d39676c2ca0a1e619bf8a8e8de62da8818bd6cfe0000000000000000000000000000000002e0c55a43078df575efb2c99b27c5632dd1c08bf28b6c0558081a78de58e4258d1b57d94ec6fa157add04aee06e7b6e0000000000000000000000000000000006d3f4f0791431a56fb386f4bb8e6744cd19b10bd0f2e65e927371ab488d3735e3b83400ddb25ef9d740a8620821b0ab0000000000000000000000000000000011e9cdfec8a8f8eba0de6809485911711149ca0ebd0cecc033e2e5ddfc195fa7de671a686edd2f56e5f7da7328dfbec000000000000000000000000000000000171f188afd5d9568cc5648aefb65cd715c0293344b9aceac1031f10b4a1e4b9fa2ab11114bd58f28aaa58c10ee0eeac65525ab4c4468a2ec0beecdb7fb072f28260ebb3d9da1a4c274b2c11a087e814a000000000000000000000000000000001651d9bddf61e5e54f86609c2479513ae84b000ad7defd840d9619a8361922dde81c999d0e95d8a3044c46fe0360c2030000000000000000000000000000000014a68c248808e826a3bb50f3c1c1438483cbb9da8dd67a0c9633a47f733e6aa7deb4a13aaebcd50de6e8e8f00000424a0000000000000000000000000000000010c8a94b9e0ec9965f6c8bd0c4279102ab682a14fc3c22e9640d68f240ccecfead9a2c6e69f7c8ed369cce7e2da50d5000000000000000000000000000000000181493e8137fcfae203e1b45189fb828dc9eb56887c89aaf9aad0380fffada423f0ab48ed068ba4e67a2b01a16abbfe55ab5a55a5cfc49cf6c36b5718e108f8d006bf7fa1ec3dc7a7f9c02a2d1e3fc57000000000000000000000000000000000e3e33fa4d85a35e8707419ca6d4fb6a61ee6b07ce152adfbaf6b5f1d7ccc253b59f91e4545848b3570bfaa804ad9767000000000000000000000000000000000c923a4de074dce3ccc94698bf6445af5847c0e6f22f225c589f744ec83ed0810913af2a6d04bd55200ffc738b31b01200000000000000000000000000000000186961ed1c6039476eb6f13bf1b5f6627b3b017ece57a4a5f33db8ef12347fd507398a421932d3d2a1d009f65d06e42c0000000000000000000000000000000011e10ae0139f95a2f1144810894fb98f6e5e86ce67877b949a2a7134c446dfe53c23dfbfd12919b24975f26eafa249216ce7aa7dcd01c1b7059ad3cc0ebf5d19ceaae633160a968c33aac5dc6adb942800000000000000000000000000000000029265ecf3c81aab289c98d9cdb917749ceef56e2e4d59de2d6c83907f394ddd1cce9d093a20206c2c1c215493c41c49000000000000000000000000000000000986ad139381e4dbabd6beba179600e1c782f436f84a7bd58cdd96a22269f1d937f88f25059214fe2a781ac519aa621d0000000000000000000000000000000019e296d5b17f78b3ffbdaa2ef5228fa9dd65abdf6b2c5b0f99a708c4721797b3b156b8df98a5a879f17f095548555da7000000000000000000000000000000000349677d4719445d5525cd65e2338463d232eb75721ca51c48fe52d0fbd299ddbd6cbc12546f056bf212d5700c3c4100854bce63dcdc0cf408b43690abbbbdacda5f3ebd9d9e462f89f9f50a9f7bd44b0000000000000000000000000000000016f5d5eb3fc3ff178843a7d21d3dd628bda120321ae44206d88f07ac001651428e0da95d3f0676e1bbb969a300406ce000000000000000000000000000000000029121c539ef1d7b9888497a362fda2f8402adf10a1bee11b53cf3dfcc6f99d5026bc386f86a2eecd0c276494878104f000000000000000000000000000000001320a402922f2a0bb287464854be6782046dd9dae4c0cd94efcb8ad8e0f37b7889bc97a3c8b4d3b3670a6924c8ee23ec00000000000000000000000000000000101fa8bb2c90b755bfba9cd7a98790b7bea2ede4c806fbd9f2006d10cf87c44172d4ba46ea40fb75afbbaa2abc3b6e9d7603824b834a83c1c408243b51cd2c2d31e2ee763d69e2ad6d369bb6aa2396fd0000000000000000000000000000000003285cb099b04b6acd333c7ac76c839b6c09388792d5fa1f2af0821e49dfbf40a06803c4cca92512bb76d073129a48a00000000000000000000000000000000005b2fdbb25381b3b67814bf6cc0a4cc17271416d16ee369b723b1711d968c355b755183f0bce519709723250515ba32a0000000000000000000000000000000002c7062ba4f642b95e028a364b0698b801f48af3c336fa09d13d83ec6cff10d210b55b23cad1d999889c83df7d1ab7e10000000000000000000000000000000012cdfdc10bf46097083294259754453e084010f7ee928cf540d44c80aa4f601247223a318700bc24114e7603922d15ae923c86e91c48582f19409b962be361da5936db02b6862eefc288f9a32d5f54760000000000000000000000000000000000669d760352e34a407aef8e141fcaa9468257b12ec08ec218f49f0769f3acd5068c6dc9d251a1b2af02a2d091f8ad0000000000000000000000000000000000064a7b4026ee3115cb730e56c4b9bf3e1527dd0f0ac6015f43d30a2f3d8d8c2659cf50247e70ca3c93d7e0a404d9faaa000000000000000000000000000000000979ca2e81663ed61486c1f841c19d83549388d798da72feda82283406d4964bc9991f876a6032382c35b605441ee7da0000000000000000000000000000000008d92cf77b44c516c243f3e6a8a8d3f9d3d7405820ab972338f700de1dd9a66d33b4a70540a30f630aa81fe1cb5bf057e1b3071b561a80aaaadb5cc24b348a2b6012340d3aebcca7e2f56983a8a13bf900000000000000000000000000000000198831a40fec54a210a63f5e00b132bb1eca6408335b85a75e28be6a111beea3b99d9f2fe5091ab0eba0f082c201c14d000000000000000000000000000000000fe457f8d215f390000efbb7fe7193ba02a2ef78e9bff6539995f01604fdca9fa3c010276afb90215890f5a5df3ae21500000000000000000000000000000000076771823180422495d89c301443a9d1fa141716e5e27205b8cb6b461a3ded7e6f196c3976cd6ad56b2e6ebb6b3a70860000000000000000000000000000000007f666efc677f6f767828e1291bde0ba0ca445ddb2d69d5d2fa090ca49e697ce4e00f55d2b706454be6d68f012d76efbb6863b755d3dee61328a60f585531c436663bbeab9afaffac49b6f0b57614eaa,000000000000000000000000000000000e1268a5e2f654c4038647a910e6cb4bab1d6ca3562ad4a9ac18444c8b8a3fdfbd35acf37f9203041fd587a5175ce86d0000000000000000000000000000000005e701a8ddd15ecb0231b7625f7868c81467c140b2617e60440a9b348a192e5248b1b3c087545cfb6d173fafe48d32f600000000000000000000000000000000071327f52b1325bb664d32c513fb12afb96dd8da23dd91bc4c3e8ae5f97d6bf673a5d03bb8bdeb6da3144dedac200dbd000000000000000000000000000000001852b86d3ef45aaeb691f454a345ed61104cecf0f444e4d841f2bc0402ea1291ef056eddb3fc3929ef4623f31016c3b5,240480, +000000000000000000000000000000000fcd3f253d018ef09a7c6e8f36662ab4190867a197e0c42a0b425dfb5fe61d57596ada28dde0b093676ce15d03406d20000000000000000000000000000000000df00598337060d603607f3b8dd16f277ce1882a2e9ced48e1944662323efc29b33c807653f31583a5d2198426019ba70000000000000000000000000000000009876c81a76986435d34c6d44d51cf1016c19ceed2432ef1e68decd64da2e31e42372c1a41a514b0eac0ac103ab6f43800000000000000000000000000000000121cf298ff8f610c64ca4a887c52cbe940333506ef2afecffe266b5b585ff737395e2c64adc23b5bd232250e67c7a62613ca0cfc742607bee58988df361d7cd5d809ba4fddb209c898cd555369fff566000000000000000000000000000000001885d5cdc3e0e0c8cffa7519e6914e5d85962d07633970c4174ae4587853f13970a1f5d7ccba97458b9b5046847ad29800000000000000000000000000000000105b7c0ba96d5ce32d7447351ded3e3f491a0e741e921447b91f22a23b64c2d749055a0593e5b47f0ff7815e1a4c9943000000000000000000000000000000000cb88fc10c94642ae7e1d7275bbfd51a2d40e9b29f3d51a1ceda577beeb131eae4b17418f9f358d47b4b9c9ca4960a3b00000000000000000000000000000000131a3e080b1d4e936d97d255b07b09a6210b5fe6900da87b5cc595a72de2b6ddb01809e2dc63ad460a2926dd8d3b3b2ebcca8ab454fbc576a2b910f140b23c23b14301c19e1f47989d78eeecf279862a00000000000000000000000000000000066b31c0bc4b3b9fe420dc095d551903a2859556d86e210c96480f1d31d449d85ea292e2432babdb71c151c7b215cd6b0000000000000000000000000000000019d79a60793957745077f9233aee7a4f096515eefa7c49473f09bbc73fa0ee13a2a30a08bd7f3bc1d5c412d671fc37ff00000000000000000000000000000000006882160e4fa8ae2c2d48ae389d8f023e2775adb7a815edeba13728b8f6b343c45788c8e9116445e9989e01eb43e1500000000000000000000000000000000000ce53ab2d81ebaf4a85b3e12a6175ad7fb6cfbae207a69a0fe2195ab916fcb582b097f09d9fc565b837925f68855c4b59f82ceeb6160d3256228d7a41fb3caa6f305b23142ab979e728356a13309e27000000000000000000000000000000000a30d335c035afe459dc262fb1bd24dc0bafbc08fae0bed47e4e204280eb96595fada9c4332df1218748921bfb1274c7000000000000000000000000000000000e37eb189560211d6fe56faa3b6e710878a21907fdc1a9f8becabca290c24b8831e28ebb48d06bd822300fd09b4d103100000000000000000000000000000000104842b88b9df6a7b8243494eb11eb62c89d1ccbde9f55fe221c2366d6bc9149178f177628c6fe7c7661318640295e570000000000000000000000000000000011df8599d72b85ade11261076e02c036be5dfa3b6fab4ff72ed7413a879c0a0742be6c36a32d0829a4e3171b0341c6a3995f7d2038ad02deddca34399e5b5653fa471d998c52bd52241840cdb9202b2c0000000000000000000000000000000019f6634435be45b099cc739fe5c2dfa01f61fd2d466d5ea464053e2d5acf2e0e9448b1bb7770b5ad426f8a872c5764400000000000000000000000000000000002bbd52efecb10b3bb6f8bd04a5751042d8598cc34e2837184cea2b5953ec125dee871d1f2f57ebc84849e3a7ee5abe2000000000000000000000000000000001962b716342df9c13c21d89ab5b8c4c0ca191440fa709627e0f240a7ba518f4c95adfc5973b6ed0af591bb54bd00937f00000000000000000000000000000000089eec676276c52bfbb2593ef0362c12a5f3c1a0566d5aa862f5f5ba1580f4dadb36c15fdcf0c3910ee14487ff146c8997b67e68bfe2d7fc256e6aa610dd91dc1b02c64186d24702ad8fa9f715b582a5000000000000000000000000000000001556d081a489eba4fbb0c20e22b8cab432a9f6ff459ab9b0e7ceacbbd46c8e24a2ee70151b019a1b4bfe47d934afede30000000000000000000000000000000008fdd7391113e8d9865ef48b60acf921b17c50744e6ad62fa24abaae54836b3d59a7441371bdfdcdb251d252a43aed7b000000000000000000000000000000000cc66cdb1fe32beb91b05922f3920060e7a95467381d62f2f036e6268af4128c9516780ea53e873993744ce932b901f100000000000000000000000000000000151f94dec958859ecaeb810c4b1cc7a707d0e1671cd4a1e3c811910bc8b95c6c944167dd280c7fed22f92ce7650beef998115b9f84e3ed6947bd6f0e3c65361cf360a65bc059515da852a72ec5cd178100000000000000000000000000000000004f88568c7ede48d7476175f1d2e7ded4312c24934f0d47794705621f8aa8a5072b86cc41e187f4aeeb49bff17a4c9d000000000000000000000000000000000ca6c579e86a68b4041150fbbc36da744d359028993681c34e66c537eb8a0a0d55aeb9b8da7fecb844104dabeb507805000000000000000000000000000000000fec63c57d3d3ca98cd1735b2f59217e163ca53b07b4fabc4415b98377d87e75f0fcc9b51c99a57ff61ca8d0016a206d000000000000000000000000000000000940e9f93f3ccbe74c7be93236a2c440b213a014ed51cb57fa053495c3d6f6c8edc08ba8e10be26e5faa898162d67fe327370e1037b709015e0bf178a41ac55774a813368e11ef7a764eb48abe75dbf500000000000000000000000000000000055e4dd9da22201b5eb64e3b9eff2eab614c48450424491a85c18e05f50659b88e862490edd11ff980b06696b60c35b00000000000000000000000000000000018fab38f58d3d541666bc29b9e94cb3940f1794b2aa851d079b9aaa1cf742b07cd6dc7c985c7e4d7d3fe683bb15d618e000000000000000000000000000000000534de5e1c1181e951b437fd17993e995fd4aa2f6b28fc3612cd4db615de742e12d66c03b9ced538c1c7cde27752c190000000000000000000000000000000000aa8580f1da71f2ae9ec26f3b6466813a40ba5bd3f89ed0d42695d420032540194617fcc2f13e36219fc0cc3886a69c36bf5fb297948e0ddc60ba26e49ef2892ca008e64a22ff2bb21ff70c56112f710000000000000000000000000000000001804ed7677fa3842bdc3eba708bf4fb7f7d4eaf2f1a46193c861595f64196398622df4358b9526f33663138b24fef1310000000000000000000000000000000011fdd7e1d0c5adfbbbaa69ce63c7c54525091289e4dfdfb3de772a8d5a958581cc23933deadcb8856540e2d0dc564dbc0000000000000000000000000000000013fcf17235506fb194e3adaab881c7aba4b87e5aef739e0547b858410e3cdbff0dab1980b1b30a7d03d617179ae545c900000000000000000000000000000000004eed0ca479cc458231ff969ebdd4e33732953e9f5610d78d4753b99c5f8cf73c742387b8e71b9be074fcc67acd71cf6b488b6b63cb8bf34efeedd9f95dff4d3d8c067c0d807bd1e20bd267748275d0000000000000000000000000000000001082b7796d35e387df689bcdda6e0316d343dc907822d1a873adea050374962b164ed27cea0e1b834997f8274e4c5438000000000000000000000000000000000b1905979a90c7a61f4ee2cf3a9f4d6ed4c724c9e216981b8ec34fb9b528018d237771ad620020efc2c3cb104df667cd000000000000000000000000000000000752663e72390108288ef4de3c3ea409c74e7051505b12083c41a2e8937eaadbd8cd61f96f7991722226fdd02dd8d252000000000000000000000000000000000f8e4eb7a3c78b8040a115c42b5d2fc69405f8334e948b8553f444dfef29bf3920892da431cd8394cf61f24e356e95694f661845e91de1c09f581c7612a25bfa0889f77c2add31b493b37d20bcce11070000000000000000000000000000000010884516bb9916084709351ed8768c6105fa451e08d5acb233511254ddbf4e72baf9c43b56b4d7dd129a38f5b34ee5f0000000000000000000000000000000000228fc5fffef746419cc69abb17cdc63ded44892b8c5d02f0c72bc8506a61d15a74ec4ea0e1d78f555ddec07f418539500000000000000000000000000000000048a4192c204b7441e871076d91d4f610c347c2d71cf495ffcb2e2ab808a8c1a549eae96e657d756d9a3b94db2892a2f0000000000000000000000000000000017a94d2472df89104ed96e24d166f922bb852b5ad80f80188fce65b08d39cc3ecf94991c6bec5dc12f9337e7c087db2f8b3bf8d5e529912b1b6e445f592a6d151c6f5d01d3b021a31a2669df4ce02aa3000000000000000000000000000000000f6293fb0e19ec85f43a1a02df9f59ad4fb0e49b16a216ce097b8ec59e781fdf176360d8492e8b77674ae2c0ddb1da70000000000000000000000000000000000e354d09aad68fce6cde40c787ba1e4488999d5b9f3fec25c9994b56bcccaaa746c958bd16ba271485f461b0d4e983200000000000000000000000000000000014fca0851b0bfdf2c69fb346f23b46135d2b7914bb49e297a0c1304d8c2851ff6bd0a0bb364938dd44680fe86cfe12e300000000000000000000000000000000164e23a53103dfa332e5ae09c7c898b95773c20f019d8b794a6b49594040e2e090db6a8047c943885dca95188e89a63b30e1c8f222019b877e66df0b6201b5bfc5b6c10aae340c55e74410a536ffb9b200000000000000000000000000000000146d37241ce4f71017e4423dd0bf907a12c1364ae9fc6dfe535c25e5e99e03ce157cbba2675829b396a69f92668107280000000000000000000000000000000000d5a992f5357615f436d95fa516212812f6811dd1f1921ba4129e84e3d487b6c97520995d8a65f6771dbba9d150c7ab0000000000000000000000000000000007b01f86574a9cb7eb3b9a19b6040055a5c11b13e7071078d16b9ad71f714ed28ad25db9511964b156ee34db22385cdf00000000000000000000000000000000154c29c6e2b21a75b14159b183e625c98a04be1850b22d314225e94b313619f641ead73130c1d6feb85abd8c9e172f6323a258d66f2296fa1c71065cf23c994eb8c6c35d35120d16790fec791ad215fe00000000000000000000000000000000075be2703b8416fa07a7cb6ae8841dcab1e36b0ea24231dba617a2fed3bebf8d952d31f68c149dd17eed136fe37b01880000000000000000000000000000000001156563f1401b731cc23c4be59e69b0e6a0827df4889cd9ef9e11310f679c1603a0d9c9679c29b8dab75ae51f49bfe3000000000000000000000000000000000663faacfaa92fbc095a5dd6b1f2dd141e248f84eff1716ee71bdffd4d28ef1f4c88828e3457e8ebf0daba1416d2d6070000000000000000000000000000000018f2871f5897aad9ff6ac45a9c0e78be8f312f07af5f1dab2bc4705558070abf367f1782af896288a7754da82bf1a5141ef4055b85f37b548dac2b64608d99ca293548bebe1e24355393520c34eda60a0000000000000000000000000000000001618a284286899f501f46c4761c93b68bc8ab3157144e4013e242e1678cba20a2d978ab53b4b43145dd6062748df541000000000000000000000000000000000c25da737368775e41ddcd9c64cf99a824afacb1d404f1ef46ec7fe4ffd89673648c5207551914e6e0d12c57e7d7682c00000000000000000000000000000000097ff49c4872e2da1f6c24fd6dd4667f0bef4eb30fc197d13e8b66adc425e39841dea011d79e4d775106a19ea1978f4c00000000000000000000000000000000147426b7d9b0bdc2be051d8f6cc4249014e1bbc2369bc32eca94684483f50ced2c07be6a320effddcc1ed5cae455fc92212529248c51c95b5b26961f27e6d44ef1c2b9233bb2ed32c3eee79ca6c6eb750000000000000000000000000000000000cf68f7ab056c4689af95b361ee3e3b1c1c48f18b5aa655cce1a2be217010814b3f07dedf6f9a7b835cb13e2afd7136000000000000000000000000000000000dd6d0fb94048dab34410dba4e682f020ed54a655099fbb6f6e94a31511960f0447d7e94143eea88195291b225d11246000000000000000000000000000000001864c6ad3f2f794239a179647d68734e23b3520b79952bda20acf2f5afe1b76bc18e35b852d35a5cf3b02a3ce86f640700000000000000000000000000000000015ea24562d7bc59d813b77b2a4943f9e98842b5a41c0c7026077a02ddfd3d5fecf352d4399f507fb12ada4ac495ddece9888dd839d9b8c236394c44d358f452a4588ae65d24ffe2bd345fc745de9d37,00000000000000000000000000000000080f0e50f90e001a442965ba7900997fcc89246742590b99add6d90428d3b61516664654bc8fb423f701e85a342a668100000000000000000000000000000000003fa9e84ddd754047649b7cfcf5bd78852abb298b3bbe6575c4c7dbc2e7595499a9f42f379a2463aa74f29e5c73a9040000000000000000000000000000000009e72d3c418726f6400b8cd8b9b649005f3b25ade40cd6f77a0c3cbdbef461e917d4453c9e07ded45301d21df4ec44db0000000000000000000000000000000015a06cac223217602ccfba4f4586cb184994bf08b324bf977dbb3884c394aed0622da7dcf5712970554d73b18e2733c5,240480, +0000000000000000000000000000000007340f432a5cd5aff1a1d98c6ea1c94be24de2d15a4e112925586c30979e23a5db93643308d3299e370b1f26bdd09eac00000000000000000000000000000000155027caae88381a60af71b2fa770e58efccfbb7642f5ef6b1591bf77e415eb117ab564aff8d9ebcd576f813b793ad2c000000000000000000000000000000000f604238d1b28f010ce8e45f2fe61d3ea20b902a4debbabcd54ce0ecd44a9540fe2bfe847178656fef0a5fd7e6d012b3000000000000000000000000000000000d7f503ede395dfa5682aadedc98bfe28d3fbfb52f42ecabc9eebc0e0a6616d3671604709f28255f50b62bee641d2711f812322dc2a7d5faa9e4877638faf8492d84e0f4c4c65ca3aadcb7eafed2106400000000000000000000000000000000176e1f9eac4dab0d253c0ff41b7600437b53a5ac5278d544a9620648e0bc4dc56aff0bda973fd1338f77fa174d8b13b90000000000000000000000000000000012919a18343cc166e2dfb92ff07bbf838779ef0479985bb85b3b82f9d0632b3f7a19d387f725a21729a77c58dd4d1d1d0000000000000000000000000000000017eb269ed75fe0403021ce70505bb60a711c91c551931605bb2a0773fafa07aeb47cdda382c0aa64f40f5e6e0e6bc77d000000000000000000000000000000000bed8ca999a4691646124a140fcc17dec02b74bd28b599c807abcaaff13bff65aff3892897652cd33b4ba5e4cc0198a9c1f6d538c5b4ae15c84581f8fd4c61160ed395816557fde197e1a013ba41ba0f000000000000000000000000000000001344d6902f5fdbb59a4c975847db0191beac284eb17cd92360e59f42cd7796cf2aa282bbd4cb074c4ee10b489ee3f2f60000000000000000000000000000000002158eb3429d0532792532fcceecc404e95a879be68b3685ae94016ca3762438b3320553ab6d5fbda3a0b615a04d996f00000000000000000000000000000000118f6fd8f60edf7088a0b4b49338bfcfc9c38be230460d7516f317b27c07600f504c8cc87acb0c95515c3acdc1b125ce0000000000000000000000000000000014eb422d44ec6931ac9860a6a017a907e8ed76de91bb7557e818dbacb19fb51457a1f45cca91f1d1d75a3567a3375b5cf2f6a4713eb692f7667fba2a3dc35363c3ba163519d95757daddefae11a95853000000000000000000000000000000000f2c72c53fdb1b0cd13a1f20407c64c46e4a0e461778b0e2d48c4f20be7c655c639b38f758fa9199b8395f706df10e7a0000000000000000000000000000000016e6c75cdfbc20c5dbc2dbd1caa66be92911264d407ce3c689ef3ae1dca44dffacb4c0d8a78ac959e47ac5c454f607bc0000000000000000000000000000000011c5d80d52e864b0a46fb48488f497fb85f51ac040c77b1d01336860b972858c0a6e59914112f6cd6c1612c604d26f56000000000000000000000000000000001136aa7eb63d6f85d665d0539975a9a51a9a3f5bd8731910c32130b1ec8b07c39eb42e4f61e7d22bed933d9fce1810581022e50c3fe7b2a65aab79de6d9e47c457d197e145592dd0611b1dc39941513b000000000000000000000000000000001306612f5119d33f177b8804443d14d04c8e059e28f63aa10ac6a1b25975327f378d5d24f0236e05849f07e99af93ae20000000000000000000000000000000017340f8887292264d498f84fce4af83573aa6cf1d57d99d364f2b84e1734fa4f9a1e07ddc81a2135ad5f5e0ed2989585000000000000000000000000000000000f65073250019ea69339379aacbeae7520c1ae10c8912ff827b702bdab2e15404cfc939389587364d811054b7d9f2b350000000000000000000000000000000019742f83ba0c9d36aa1d595fcedc3cdfa6c6f08579e66b8956fb32ac03530114ed4266738c57175e7a10313c8dd42deab80011c7a4aa905d4db6d4f6ae46eac9eb8bb18613d4ac5e5567990d7e8fdd96000000000000000000000000000000000b2513f906db531d052e8e6f1cb8d7d3c41c7ec3158b370268d1de204ed8fe7618b64ae35029d1718153b5bdb8439dd90000000000000000000000000000000001664c367a2d4170f463c90351cb321608e2a49fca6f3258bf10d32c39747084cf9d2c38d5241888aaad97985cb09a450000000000000000000000000000000014de15b86461cda9f1be69f43a9ceadfe7b7d1548a206f3237d93c7c01ee554c4245fb73827ed0ab72b99a62215faeae000000000000000000000000000000000b25e458522be9fbdde4554b1a0d9af157aeb7d3ec1f89185b193c0429125dafa554d7a531ef9502d443a26112b940b8f397789685a736375ead2312874174795586e12b230669a90d072fa636128c7d0000000000000000000000000000000006862c0b0e3d7bc4507bea1df82080745aff21b7549b372085776be2f88aedd4cff00ab8258aa21e63340963bd0d937b0000000000000000000000000000000017199c5ec3a2dbc1f1e8d74648cf8da247e35cb07df22629b3845274d29e473819a31bc344f2a2bd6c790530cfcc0126000000000000000000000000000000000e7fd1ff41d86a02014229c5085c886988dfaddcb60f5c7c81063e8289aba846337d61bdde57e276fe6c65bdfb48751f0000000000000000000000000000000010efa6aaf7650edb0c74d30125e36cb67cffd1c7f57932d92ab4aaf36f8d9245d7c75dc2b3bc8f3f328589b16e26230e28e325fea39d61269c576626984f85ea43cd683b08c3ce111aac0005adda39c5000000000000000000000000000000000935de4b16f5f9c0accee77b5820cf36c24aad9953d40a2409b7e6040f09f85da7d2252843f9f8005316146caae539800000000000000000000000000000000008a8c542111951b32bb0b50f7631f8938d22e298193edffefa3e0f5c861ac8205ea9b865f9420ad74cd22b37c5cb56200000000000000000000000000000000012ddd660879a1f52ae6284e14f2ae6ea381ff3f321458cb76bfa566b04ae19f3793468d0aab652a82671be74332a3b7a0000000000000000000000000000000005eb148c35732f7ababc73861b71fe4ea5e25bcdd675e975fadd0a9e0fc54e175b2e39dcf0323f4a9802a68baecd25df3cfd9bc41303803a0b4edd121b818a126bece309dfee4133aa5314cb8a91d08d000000000000000000000000000000000bc351eebfd3f3c332268055af1655c8729cea44eaae803607198cf747280adc0d3dedba137828834af3e7179ccff4c3000000000000000000000000000000000d8a6cca17e1c6ceace7c0ab1333ba76ed6c3b114bf99ff80127c6a17eb0585bf6fcce871deb7385e9a8896a21c065ba0000000000000000000000000000000013222db97e31e28946adecda10c9ccc9aa9fce33e0aca51d6483d2f0c5bc3f33994ad516215f8333e22167164ef5459500000000000000000000000000000000144d3707b1898d35c65ae2c89b1570971a9494e8bd23df835f565059554eb7b5cb66a6eec890058316aef43d6c6ff55c8e08fed30e422868f37c422d1efdcc93912d55b0a731479af863dca4705e0c5000000000000000000000000000000000138da93a9a4948d41a6fc6d057a217faf5efad863b45ae8eab311360c033362213edb0ff90bad6c95f60b8e1131336e6000000000000000000000000000000000f41766d9b57b3210d315a2b8f90aabe591c1de6037ec79c0d72a283f0ac3094436bb97b82b7ad12ff4f471a41227bb50000000000000000000000000000000009aa4f5b674782b7adce6bf75ad676480f96a58d68dd7ef8d1fa488cfab794f06e7754e9315430189eed265913db8b300000000000000000000000000000000004e2a4a48f02079c0ed50c1daa91b1216af481a982c7aa64d8ba90449ed886cdeddd0cc08f1f8764f7f8c5988fe677f5674ecdf795b48d62f0db0f9cce057fe570d15c78f2eb7a77b66e4895a45804880000000000000000000000000000000019c927bbffd96aeb9342666e1974d30f9dc215e8eca41c24244c63c106331ddad20d64c79faf8c5baa45cd30b561e167000000000000000000000000000000000523f063de96c9b77bfe5c5045a007e155b45dbe68c5f1162884f1d942bb385bd34c2a37e5e67e6dae4a23d600d75d1f000000000000000000000000000000000c221006f5bfc8baf43826258d0588d7c0fc345d68de1add1693bb897959c2cfdbb9c165e82c0c787529cd7be85afbc50000000000000000000000000000000004218e3d52b42a4504611929f94024326f38e78bba2aba105db3ffb4a51f8906b060ce2302e22ded60714d652a234c1f288fc80d07393f629ef2732879332a253b49d26ca7b2bef7cc49ee40530b2b3400000000000000000000000000000000189e5063a36b0edd736bcd9f997f4b08c62d33b27560e2e2b7b40039e7c63b75757f23746e70a330110d975ca683941300000000000000000000000000000000013393485ae494b1f1467cac9a8840c695d619aa1a78c40674038c053f264c1e20481f2005abc7f0545346f5a982d05e0000000000000000000000000000000003f2be501504f4d37e12acdc54b3280671ca0762a063fd3bc04473ed5a051cae3767044c002b7ed1abe88b2143af08750000000000000000000000000000000009d5952af88514996336e1ff19409e3e4eb3079f6dea22f9738f4a331ce842b151e0b842b68cddc10a711afa6d3242b256e69f4ce8fbd8f86f546fd6d129f9760edce7c5e178dffaf987bf565e9bb7e9000000000000000000000000000000000a79444c673e630f46bbc5a9e06e8c023978a78e3c58d72910a04c3733ad873c0d0de61448076b2fd3764cc17d86d94f00000000000000000000000000000000110cfd215d67d4a091578203855fa0e85feb4dfd0076fbfad20bd092fb91b528a4117850955f5fb6568fc5844e17bbfc0000000000000000000000000000000012ece0577512182c50dbb4a485256e705410108d9ba9c8d57780d49e2e25a0f89ed1fe917797b902aafcb8f7d98fe931000000000000000000000000000000000217cf1dffac7ae162181d43ef12e3e88da4840f1573d7ffa271f64d8d54861099be37b644e96e650dc613975d8a00a4ab40e86212189e6f5925df810141c132eab20c123166cd8d3c6f40f5dcf1b1cd0000000000000000000000000000000010bec428b2865aa7c077c168dc28dc549481c6f8367a5b84cbbad661b0225cf0fda3e840d96c4e4efc36c20d48f23d5d000000000000000000000000000000000ded3a1e9e2eded0a11211a217f9355070361f0a5887a7e19c74edc8768000311cb9dd8513977ecfb45416cda0908cca000000000000000000000000000000000b99ffddc79e825f0b73f2d0229d66e51624d854d00bdee5aa7a884dcafa1888963e2a2149db0f6e40ce3c67941a391000000000000000000000000000000000147618970c71965684bdf0d6cbe1de189bd23bddb2b861c9636efdcb7a96dff27bb1ac70485b562e78485a1e8e56531cb96a5b6129c58113bca713e6905c026c0bfdb6d679c203cbe2b256b0a49ecece0000000000000000000000000000000001a402aba8fb28dd37f1be11fca037baa99a6b57188ccab66208a50bb6967dcacd1943cca73e34f6b2e2f72407103a73000000000000000000000000000000000c0bd64d043fa4e3ea566cb84f9139091891231ff500b67e5fd451805f79003f6303352a4f0c236063d60d9088fae88c0000000000000000000000000000000002861fa7d0222711ffcadac86e7b9e7b494f5561c22544bd0876fb6e1b2e680d0f7074c2800312cb233de2412ccbbc8600000000000000000000000000000000015945f0c83e738a17cb1283d08d63ecf12a7272bc62812006ed78254bfc45ca7c42306cb79bb16ed17bea600a4d62b5d9d8147c4453cdeed971242d316e350abead3dd08e93ee54738a4a5aed23affb0000000000000000000000000000000002268793f6872f7715d802c0d96f3b3d850249d8e70aaa97f19793d2c92e7cef384aaac603eb51525c7ceccdd0211fc40000000000000000000000000000000002507d680a2db16746810e966d1ba5547ac98d08c8402aed0859203e6dae0cbd87a9ddcc05119c1ca08fca2fd733882200000000000000000000000000000000192426b6438b2abc7386599afbe09081ed4908fbeb807a65bcb7c6676aa76e5e0c2c87612cd109cb124c73b9c8e0591a0000000000000000000000000000000017f125a2ef5246e7a19e1b2741b31b9224511ffefe63ccfffaef1b7949e88af573e267d6c7617ea97bbaee6d50eef67e1ba8e52986d3bb0421eb53b18ca8c21b9f7e631f16b99ec56748baeb541b32e5,0000000000000000000000000000000018c2f533f464f9768308a56209711cf9b6653e8d38591d782ae2374905f99f75c0d42c63af4b534056c28599a9da874400000000000000000000000000000000071d4d708f00875545f381e164f77183e14faab599e472b2857c15091254ddaf5c2e9df809875336f92ebcf5b7628da500000000000000000000000000000000099b207cf6ed022289c27393c32d0b83aed6c7b57323e746374c1a8e2ade071a5293168e72f7aab82f6c2e39b97b03830000000000000000000000000000000005dada01b4dfb6a52d998210a67ccedc11d6aca2405e0836280e2f7c8fd7c8dd271c815a2e9ea1dba6f1ab0d6e89d756,240480, +00000000000000000000000000000000195d3f440857011bf9b764ff270b8ba1d9d13daf48933e49c12ea20d335b58bcbef1353d9698a7e795b4370ee385f12b000000000000000000000000000000000716c151efc6e611b5b15c749eaf02816a86e267428750741b167404a21116f2025d0d07c447b9c7bee8edcc2c7b76d30000000000000000000000000000000012ba0bf62b35327111d09b402db2b75b2e835cbe581638af2fdd6d06034774533e6501be3de84e7075e4184e11fd81a8000000000000000000000000000000000329b14859d004c146047b03870371f53936e078ddc69294ff1fd6f42cf2a354a921e5f2e5c125c454e20af97dcf769e7d39b55aadd47afa3cd35cb85a89e729ca236ada965b99f64ab302a84952babd00000000000000000000000000000000042286dd205ac86fdec3fee779059e2ad59adb62505f7b78606c128244b031c53dc40ebc2f5afdba348892d5ef4c10e7000000000000000000000000000000000f960010d4818846b3a0291c6fe1aa53bf0eafbc0e0968e3ee82324452a7c1a8041c06b4db9cd36a07c119c9fd2f9038000000000000000000000000000000001876da0dca72869708b8ff9ea0b74ad6be25ba82ccc76660246413a04344f2b72e5a7f6fddb58e9dc0bfaa6b33a5fadf000000000000000000000000000000001538ad1673f117493d998941d9356fb9907f70c279bde8ae8813b9c7b371344456f8e67cf02bf3401ee06d55604cadf9c41ece17a6d8b4a22994227b37a9d73e17a88859683afd5d226e113246e70cb1000000000000000000000000000000000d91319b4a5e047ffc8a68e10c34b2b90e7f3f08f9e3ec53ed12bba5f66c168c20c6583ba2016f0137caed834845f7470000000000000000000000000000000018d5542919674d2fc32430175405d806ae4abe3e1236df2188bf4c9ddf66c0974036e28414890212ff8dad244d11e3c700000000000000000000000000000000160b128f1ffeb97edf0e62dff85e3f90fa48567ab777a7937a2c0e4659df180fae4565107c2236a5f2808e42a03a4ab40000000000000000000000000000000003ee74d214ec491331fb9db8243e75570daba9feb587671496cea4b480d80ee162c6294c082203534bee450c384f645e69700dfa3b6e5fba735d1fec3b3adc90719ec301c406ac40673f4e5677da3227000000000000000000000000000000001951afa33800a366944c43bb42b8c5c8beb9ea2e1cead8b84e0f94af51e4a156d9454c0f08d1b13c692c41cc480fdefb00000000000000000000000000000000077f4543fadad6f2f8ae8d5d98f64965bf9626971e7efef5221cb4d95d51b8764324cf4a11d0ff5330d58df70cb79d92000000000000000000000000000000000417251cd0c1b32505377e51bb30ac8a8a3c059644b9ddb5a058b3c6e1110e1c71ee19f549b15090144dcf4668d0d50100000000000000000000000000000000052133be345adc562238c4ecbaf76ca4159fc11ff563ab393317b03065ab668e7df401831baf7027f0577f5791b1ca3019e8eed297661c06c92075629e163e80a08835254f7af8c0f179400be114ba7b00000000000000000000000000000000067bd52b7a3193d31a4f1ffb76432c8d4108442616f17056d310fbfee2ffaade9437e2bdb8425cf83233f0c632efc1170000000000000000000000000000000011b045d6eebe1bc8218b696b5e81e78db78eadf1b5d987060c1bdd73aa65666f77e1d6bb6f3d939d64cb3e6bda08994c0000000000000000000000000000000016eb5ea5067413b72632f5300efbe0d01a284b2a59b68d0333c269da9302bf0f0cdc923acb27e51bbbbc1d4086e6b06a000000000000000000000000000000000ff37b8812963d9efaa1e6deb5cfd34eec70620fdb65808739295a819e03ebcc8f501b8194d0b3c72717fc922b785194199ca6fb7f6df8a2e72971c5738ad75d84935e922587acf3a6b6debf3c37bb5e00000000000000000000000000000000149b5e0df255281c1b518427094cc0903fe89eac9a6dcdc379b8ca30f3696d89824c201601fc4b0795a3c859a82893170000000000000000000000000000000016ee9e7d957f439d078f3c5da98d114a1b5bc4da9c17e117e1f540dcbf83a349bba94def4b87b63247f190e3b5813cb00000000000000000000000000000000005d4f56bea105be4bf1fcaf4f25df30f85968d59e60b1c438c28ea0f480851f5ab9c05a7ca6677e6f12c7dd3ed67c2e0000000000000000000000000000000000dc0e87ca5a8b339b485ff3da2b9854a07e9663c43344dfb5ecf3ea055eadf67405c43013e15367fbaa55f1bd8e222f98159c6b98bce6ed31c30957280d8f7820e9376093d1ec9ac68ce0777d02b084b000000000000000000000000000000000b0575fe2adc9ad66209cb2191efc2946672e4e81b96d50493d2125d9c83165f0c4d3f714539eecef9de0706cc20da9b000000000000000000000000000000001511649f0cb6b86111d2830812231ad37df5500d7ce1086241591dc3cf40b30f1c53dda3133b2f7fff253c94d5eb98720000000000000000000000000000000005b15e4e32f4f4e46c1560792a9973f6ad63f5176694734f379375f16a08c162a4a820385d3ea6c191bd87fea4f5c8cb00000000000000000000000000000000089218403fef08dcc6e679b49a74557dafed3278d41ff36a9801db091b91de0d46d779a40574fa4a3f2baaa1a14be098ef1bc580e0b52b10b049f07d5115a60ba96d14a39e48ddee3c219f11c3b2a82a0000000000000000000000000000000001c35a3fdea92b28c9ab4bd9ea592b998853a73be844b9dcb500ed6704bbf3ca4ed4216dc24b50254b6ca75c4ca3e7fc000000000000000000000000000000001815292d2a365dd7f41ecf3f9a89e040bab717241cefb3155a097eb9885d64fa55f5de7023f2ecfd33f483ff304666520000000000000000000000000000000013df522c72805b890aef97864ec6769f569504fca2d6a6beae97f80dc92643f8014daf3dafc0040dd7b985c0d9b2c462000000000000000000000000000000001155ad4373a8304fa6301cf48b4ace135d6a0c08cb06d624f42f88073e43612ced3cc37235422171b43af2b4ebbd5662d06f6ed682c56611fd060ed2b3b1dc48974769ed6dc504ca3e0b9f68b77e63c5000000000000000000000000000000000bb9afedf7417ca31beb96486b024af13c06007585d785efd1e78444daa9bc3c03e1d64b560e8d6a18ccf77a8c3c8d05000000000000000000000000000000001652d3adcf1612e487a9ca198801afb9ec30267148502684c2b91c05ebf6c48e2ce33f9c0a986daab81d5359ec1b503c000000000000000000000000000000000baf3d34bf4a78e3b9dfa637c6392c7f4d7ad0ec315d10748784b5b60221bd9da0f4b75c57c139ac2db329e270d559de0000000000000000000000000000000000c30e553fa2324d552bdbc7d2dc86531340c4894495ee9a38b64f5bb6f92314021a2a00c4bcd8837e55a0ae2676a9b761d7b314ae9d9e78f628ec5a207d12e2dcb690688d256fe46e0affdfcc9775ae00000000000000000000000000000000159a1e4e87c35aaaeacdf21efbf8ed99fd6a2ddd7e990c12407b1417edaf185b8f1df9bafbddfaf3d581b5d97d7718300000000000000000000000000000000012239ef7b1e1009c81098aa4aaad8ee9e003530db5afd49867aec47f46d5e29d44b5e62d80d9e832937a299633e863c80000000000000000000000000000000016af6f74392461a9294d9f848508651ca5c0cb50494ee7c6a334bd770580b924a17beb7824b489e7e101ccd50aa0d5cf000000000000000000000000000000001912a0f54ba4fbecaa55c150ae93455e1db6b238c032fa7992bc8456f183c09b6005dd6398a77ab91cf547919ce7485b03a0c47621401fc20d2c78f7e30814de9a6f838d4328a5b5be628b833c31a6fd000000000000000000000000000000000cf1cf7a09a12f51d10059425042ef8e140718ff11d2f17897a0156034f73ed29496d93b8695cdf609280d319c9bb742000000000000000000000000000000000b2c4d26fa1eb72eed1a24f27229d2675e0c6f91e3a4eba7d34b0fc1bf5a9b4eb49c3492d9586669abaf25a656e1f95d0000000000000000000000000000000012c5c83a03087b2449b71e9037591fa265d710ff6d869bfa18ac37cbdcc93024f673128db3dbad9e3517501af12f2540000000000000000000000000000000000ffe5824245e43953e3d0adcd5fdc1a97ffc87f8c5473fdb0fed57000fd126a9925ba7415c698248c51c1f3e12b270d5e4ac6a5e740e073c5ef8af389e70c2cb8ee8c4c04c2ab4c48c579e83e181005b00000000000000000000000000000000036aa888e40882b2d6ac71d66c88543e32b4a0a7c959eec560e3d26114d8aeca63fd87dcbb3171622c989a6c7a204ac60000000000000000000000000000000006a5e552e6d2dc95ab8636a8be16bc79572b47860bb88934bf04c195ec01fd71eb91e45f24c58bc2812ed5fa10c8dd7d0000000000000000000000000000000015fa3ffcbd4e562a4bc29975cf8c1eedf442e37374fc87128e6f68bcdf6e996f6f054e0b8c608e651753de96655b2c100000000000000000000000000000000019bba7c0b170dfc1f8fdbf7a2e09ca0c4027a6aa6930d15dc2772a0f20e5e56f0d11644094dc866595f801ba5552e6c4c1e20d8003fec60f68c03942185fed934ebc197c2863174442d1a1c8d1424d31000000000000000000000000000000000341f46ec06a8def4f044328bcdaa308798469c767d10e5db34b0ffb6f550421c67c6fab7b63cbc7504e55847cee419e0000000000000000000000000000000006952e5f791c37dfebcfe69cdef196dff66563b29e94927e3ab34365773b93e72251a63af4ff294af88d45fe0899a2c3000000000000000000000000000000000874dfe75b31450e99dea063c090e32d24fbff9b681b64a9dca5f967f82003005b003d17eb869bd3b37d4a412bcb28fb0000000000000000000000000000000014203b69e8af4e25232777f503d5e82d6121256fafdff1b037f65d5aaad0f09ce882151d6bb4705328400f00089dcc7a7713ea72a2ee99442232472ab3dea9307a02fa1279129d994af5588af4fe7af4000000000000000000000000000000001403fa3f418107e0bf7f3f4bfcf621812d32b1b744ab5a4c37b5cf946a5e5dabd675c2b70bd355590a9883436c5e32dd00000000000000000000000000000000069e006f168bed4439fb46db9ba4f279f72ed608c12a05eed172608693f42cb1f04aaa54191f4b0b35f967bf03d0e63b0000000000000000000000000000000003f9ce029f6fe605802de64701ccdf52bf4aa299400a6e1c36f5a1f9173bc11a38e7628f123fdcae01d2b260f77c577c0000000000000000000000000000000009c9732809f60635115cb479c80457c6cd8dad092111d663c0cda0da1fa71c9bd6795ad013d2efaa4599c8ac5c88e5f26f128420cf6ab4616a05b287191105f25c7212f2c39c3230fa56bc27cd06ebfd00000000000000000000000000000000115e08d8e4dff7adcfe46a416625be0ac26ea2d7900f5fed497809a6d46e7faa5b47c52ab3bbeb9fb16d82b549707ed6000000000000000000000000000000000dd1b31446e44f64ea5046dca5174ae854f6bb5d95886fb95aa136d432f1a8c03ef1a5f9320f89c82f764049a7f678a40000000000000000000000000000000014879783c07e6986cd393fa1e0ca8a7e23b2c9efa595229fc0b6a11b9c232ba33e92962a1087fe2ba0532d7b541827900000000000000000000000000000000013dc6e2bdb2801333e7f914b99f30b40125fa1ebd49b141d88a8c090b15ec3250a13812a19c3c0751a4e5ed100a6f0ba12bacb3419c34369dbfd1c968334f76bc50885028758a975cc812a04e6feabd6000000000000000000000000000000000a2cceef36ec78dc702b6731dbaf8cea1dc2b41fee1b235673c6941729bc5631e69ff37900479391a4d10b300fbf3eb40000000000000000000000000000000002f4881fd626f4ac434bc1e59716e5e5ee14dcb9adca4d639ebc9d86e323d274ad8ec0a4b1e6ff92e1fe7928d48924b000000000000000000000000000000000174cac80e7bc63989f58759e123513b611e9849b44d43a362f2eb84421ad008f3ae9e9f0f233e49fc8e10c1824ba948200000000000000000000000000000000143641099c8a6c8153dc8ce74debe795dd6c4487e8234f164f9f8dcdea6a53619c04a8fac215421f985557b5b956c20a5b00f26af6f59620c7130a6d12cf2091b5f52a6b638484fc1f242dc1773be256,0000000000000000000000000000000009807ffe8fa881b235b1181d2d3f147dbe21042524fb0c0b4c90fb122d160c7b895034ab32e20324dfca564ca6e3183c0000000000000000000000000000000010f6da88525da3e86ee56cd5514a436e3ce4128e437a876be130a70c42444a05ac269326c84dca532ca2e546860027c00000000000000000000000000000000011396a7317918841ba171ea46bbddc9bb5a08db7c82b90008c6982b4b79a4dafc151081bbdb7b9fb79784e603e15eb9e00000000000000000000000000000000070b8580f303b83c643a484dd031b780ff4ca2ec805d8c538a0b0c791cc7f8163654f5e5a41776a8681500a6690e24a4,240480, +000000000000000000000000000000000f38906bd058e4d32403fc3d39fa57bf49c0da65ef42fb129332b91c184185de4f9f0bfe8908a44833ff4ac4d65b88180000000000000000000000000000000014ea6fffa6dc462463c15feace841697698bc521f608ed0d16be5097bf42aefcd1f73182f37b6279f989e9668a8076d1000000000000000000000000000000000f56d296323177ce53c6977fb60e445278e59ed1cf92e3f68c570eb7a9e5f8afbec5e2ef64674bbb54d7016c829f72750000000000000000000000000000000001b29012ff3460cbe4a07bdc65885718f217cf177866823a7cbeae18bda67f65913ea20bb69e0ffb31bd82f19862113dacc5a8ec806f2f273120457865582b08697904a2c6510bfe9ea21eaf682fa4fd000000000000000000000000000000000a4126bff91ada057ceb9a75d577120c7ac8c9ba62151602414364cf88a3e12dfac90b5590db3e40c16163177ad4e7520000000000000000000000000000000004a3768d326c4ebcd5ffed89341e8d04f89e674f3f2aded3205a7193e11c20115b3c4d595b959d6e39a03d76f6b5925b0000000000000000000000000000000006e0ae4a9c45bb69c3a1c65e26e4869f2eb18fefe584e4598ba99c0044e8d911145a5db3f57194ceb6201e7eab9a81b20000000000000000000000000000000005be2ba6b147f3f2052c4877c90ca364427c6721ab64dd35e89f14f3179564d8812b9013e3e3db22f69afd739229682b98c15a259b4dbb8c300a39f0af558a9827112f6b4c5eae3d43bbfe057eb113cf0000000000000000000000000000000004c36cf955fc81bdba4ea8d2ecf934adaa57fa4073199f77bd0428d3ef80a7d7102179d4a44ef0de887bcb3ae915408900000000000000000000000000000000138bd3ec7a1b6fb65d1df6bc1d2ada35aa52b06729c10b5d45b9bb7cbbdf41677942b99eb9c2d32e3e73da7d5f9cfed40000000000000000000000000000000000b0291ca10245e2f7a963fa07ec62b15f6bf9e7a5a7839840ebcbe538dfecaf2114c7864a16564a5b3c85c15d97fb7a000000000000000000000000000000000b436e912b8a71cf8050d10d59017eca6e494e5440f02d2816924ac9cc2034bedb1cce6eff5c42f3dc57a74cf1b51cc0a0e68bdc97fd642581f7e62ecf134df2c05570713c96fa733d3db96ace88f0f0000000000000000000000000000000000c105ac7475ed9517a0b07f25a030a5616952d817f3893181e352907c7cf4ec9f5f3006e37b1da97e9cae4a1213584e20000000000000000000000000000000002c112c18268934823d5946d2322d0faec497d8e18736da91d2af744d90f74136c49370a4b43952152c62820d25e52ee000000000000000000000000000000000fe2818a397d70543e752e7022f12bab10f1b1289cee61a0230d545296ec872e34d8df6edf7ce9980f3c153e6e51d96d000000000000000000000000000000000f479e6a52bfaab3a31aa9a461adbec8a390daa8eb6273f9e425eeed764a6dbad44d12778bb888aa5808df272edde401e5512cac411cd103fcd7497fdf47d1221999bcecdba30467f06ec356483484fe00000000000000000000000000000000016106cb42ffc41d5b23bc5b06001473bdfe556d375fac6a0cb0a12494e9c02ca2dd6133356846e1759a2c485faf5e890000000000000000000000000000000003cec25b0f5d1db0ead5319d6dd15517657d1fec442facda4335ae0bbeff606fa9caa6a4c00445001180aaeef895d7fd0000000000000000000000000000000016ce3573fbe27a8d23b3ebd22aec989d61fbd0e41a519c5e2f1d650f2ad73adcfc8c840fb12bce83b722a0cc69164e21000000000000000000000000000000001434d13d44fd8dcf776c2a045734dff7c09ded31c9e3a4b5e765cf26fbfea4cbb4ac15c06599012a7f2cd572bfafd78ba32f6861298bcfd4668653544b4551d7357d64f733365a5f08ebf297a09fd4ca0000000000000000000000000000000019923ffba0d08ebf1bd43393142d61022430356081c18e37804172082c7ace987ece2594f4852e84604a77235c7795e000000000000000000000000000000000123acf9e1a86846ae27d5fc0358afa34fe9d6b68232c9ebf2d47cc169779c4bd24f225ad30886fdf68166adfd9898abf000000000000000000000000000000000a6061d4cef29d1e3535d54a2e36373e2c16f91543f53e1aca94c4abdabc663049673f2327ea8bb574244d7f5c99e981000000000000000000000000000000000b1f3e1d43575a74584ec7a3280f8b7196f9b99b5e911ed33ba6bde1188c82d906f0f8e6fc2b285fefa0ce59116e449524301fc5c3ab842d7f6a278fcd32249f1daf86a31dd254ab9a21941fffca98a1000000000000000000000000000000000373d36dd0fac76a0fc46ba5da279ca3be5a1f8d799570004e429256787110d4fb746f65a8527d0ba681a81b9980bd5c00000000000000000000000000000000057933c2b3e482ae026159211c4742264f7e890efbaeb6e14f3bf66c80923289af095dc97b751a117e181ef917d049b000000000000000000000000000000000068816ad2369bb57b3430c657284858d3736c327284e7410b61ed444786bcb34a66db9c16aca583aa9722aa8d7975b440000000000000000000000000000000007fcd7dbc062d28f6ef906f6a455337e517e1d6e6c02c7c0b2b2685b79f56ca3436c1bfa0ab96e4a5eb0c2e2c321c0dc17a920aef58100de67c482ae1fabf7ec87cf3447bde1e19d9aaff825695706740000000000000000000000000000000007bb0ab060cc12002e043724c0fd0c8bad30e08b65ba9f2fe5d09d18cac4bb2d50e29ee14590ca7bfc505f3ee3d4f93d000000000000000000000000000000000e680653d29eb5d90f21802f543eac3102a1de6d2a5bc943a53dd9b80bdcaa6951ced2eae5e2a25448b40468f1923ebc000000000000000000000000000000000b7494b494019e3ef36d5c620ac56483fc6b1c8fe5c6f67537b19f56ef01db327812095fdf805d3dfe678a3ed8bb6226000000000000000000000000000000000291e5b98ecaf7aef0374647d28fb9f8785a64d9165de407d062403047da14d4ecd19fad8575070b278608e16b71d387d76d5eebc3d099448ce4a8ea6dec047b0f062c6361ddb9e95ec898442423a31800000000000000000000000000000000186536e3ae3edd9cc6bc24fda6589ed26e72e06121e97e1ead65b200fa0578c6e53d1154dc7b14e7eccc3a53237685060000000000000000000000000000000012fefaf6c76ae7197b99571e41a19b14846fc4499e8e964ff750e7c3ffef6ab3dc19eeb42c5f6ba44a573bca7a15166b000000000000000000000000000000000a135db813a44a21174cea3a0b34fb49f273877203ccb66bce44b2b58794818d8bc1df27544ecbf780823467e2e4ee6b0000000000000000000000000000000009b08f70cdf4e349e1a73935de9fb2ad9f4feb8cf5f835be78383fda2af94d81af253ebce08cef825764151d5713ae60cd4cc1453dec7ae335db989886fc0964ee73e12bab69ce1f1458d1416471176a0000000000000000000000000000000007976df2d47c14374e554401c4d3330bbf6f1e6b8fafcea1e1974af61e8ebf493dc0473d34b30b0b1cbee082550d85c200000000000000000000000000000000177cd64db8334dccb17fb207e467e5b09e891b05df7658d9b439e3cb72bf3e0a70e84f96fb5e448f33c003c279cb38d800000000000000000000000000000000094d739a02b8ea6ff8113019597f41df4728b270770edc5e68b1f5c32775f0c706e3f31c0a82059c1ee150b89097376a0000000000000000000000000000000006ed888aa4bdbee94ec67500e30d654071774fe22464dd5b900fdc17b445754293504b10d044aac8fa0c289f0b2d9dce6d207c08e51d64a9a47f5353faac77fbb184e1123d38e39bbada85534cbcd3150000000000000000000000000000000014a16b856b04ac4b687c79f2b4e1dd6d45db25b382e0ba6687afac648c9b6384cdcfa89812f1a726bb4d1c22ebaa6668000000000000000000000000000000000764088e337df6db30ce8aa23aefd91d9e35be911c9e89ac62a1e06c3d06e28efac256490400fac4490f595cd03c127e000000000000000000000000000000000894856fa1c8488fce182a9c7749f7953e6a73879b6e743fdb8c780275447122f512806fa83d5ad528f8f61598ed01d20000000000000000000000000000000002b33bfd09e0ff452c3336bde08df0102162488bc83c27052447a1e5d16c9c68bc529f96ee3787a26d2009f22a1246342e1910b704d39b6a64cc7a44e44ba3e8b7e64ddfa90dfa6b5ef571f9ff7d7f0b00000000000000000000000000000000133e2d092352d3ecef5b67a09c2be268fcd4fe1f7360a8ce3ef5f33bf689242961a140d9c8afcc1e2fab3ad4e3dba49d00000000000000000000000000000000101eb285f0c462a22406846d82ca6a278520b65132d2008b124f6647a642c221b0c3bbd4a0abe8af7417e7aefb81b5b20000000000000000000000000000000010958cbc317f1186aab69ac24be87647b8013b678b0eabc6270167bdc9c0cefbaf4d9a34dc41524b709f1b881e6bfa34000000000000000000000000000000000d92c47257fd0c4d6baa4c81efe65852840479b9bfda5cc06b253f167069ca7367924c0c67d6497a1e9abcce7d0ce9502eda0eb154d5f9b0e25a828c6f77541701004cd0293c61ae4d36aa3038d0f1840000000000000000000000000000000014ad0f935ba129b47ecaad63b9dda44e7ef7933f182a0f5226141c8f0ede026ca2f11db7f4924b5c582461688dad6359000000000000000000000000000000001453716381f13bf6ebf8fff2ed7bcb90f7beb44269008af5880a355dd03de5c84c14f5aaf69fda043b422aab0c694784000000000000000000000000000000000e983c9e9b799eccfdb56444d31948067d46adf275d7f39a70aaa8bfd0fe1b83632c23d87f4e993c8191901e9a607217000000000000000000000000000000000267c8b8c5e09b59277736caad12ec6986f206d1c1f48023356d8bc877a594c8bbd98981cec6382bf9bdb9a5fa38275ecaf6dcd51a851eb200c7f5fc3e106ac5ffc432f756b942b1b9a5dde31cb2a3760000000000000000000000000000000002e28c245e71a7f6206427ee512f3250612785ce29b369682fbf767d06ac08f91de8ac9f82951574cce46cee1aa757720000000000000000000000000000000019b0dc35eacd961e0ca7d54a0e37c4ace37eb0200d5489316f3371412717c57c8f17c1379721f4dd67b3fde24f50d4cd0000000000000000000000000000000013b9741f7a32e5e5b1ae5400e32dd6fcc1fd43b68df54ade57c934720b1289a51deae77b1726e1955b6430f37928e2bf000000000000000000000000000000000693980b347ed7ee6cd93f565c87efb36fb304d7e9ae24e2b9f902bfc962b6c7fbab93287147f5ac892db2a709c9ab42106d4a893a68b7fcb8be96faedef65181c239dc2cd752c85ae7800ca84fc2dfd000000000000000000000000000000000ad6b7cfc6cefa5783093b7d700360b354d0698d27ecefb7d5928ac5bd6c299e4001474d205cf3b85a32c600ddaf1a360000000000000000000000000000000017172c3d5acf59b70b340fc703e9b7801aeb4857ffbe7a9d5daa0f32ad80d1c0ef2f0b3b7d1fd83a757c076872425fc7000000000000000000000000000000001291f55fa7d14b14c578d57178cc707cabcdc4bfb444cecabda271cbfba2ab361947d045ed46d9edbd215fa4c8164e56000000000000000000000000000000000f64ed6c989eec5222239d888d08dfd638a0e35eff2266410dab0498941fcd1683654064107fb7e53b8c02fbe98a25622b9e1cfbf140f4a3b1d06be656ad6ee5169a9cfa7cbe6efbf8173843d406acd30000000000000000000000000000000001d25b5bfcedc6d7ff7e9fcf729f858759936235d23ad45b14dfd0229bf3e50fc68799d19ef019b36728285bf7ecd0b4000000000000000000000000000000000326e300ba07935e0233a03ac891f18dc7b5a9ad9a28264136228e9e23e8f2aa31b7f5e5f3cb3354984f57a868a5d00c000000000000000000000000000000000dc92060e3403df3a92b15ba3e437ef0c403fcfc9c3545e544a78874e5d9b5e63b9ba6060c29022fe2594c2e6fbb6a840000000000000000000000000000000006a01e85f59dc45b1501309a350137d71147c30fb70da6b7637a9b1dd884aeb7e554215474784ecd3bef18d15d2c0524dbc68f77d40330ad5b8cfcda42edf57899454571c6c6465c4107e662a269aeb5,000000000000000000000000000000000b7fc0b44723ff0d1cb7c43e470d4d432fc4bbc7f9d98ddb3d91434a5574956fdf15f898e579236426ea44677998665d00000000000000000000000000000000176586b6f157e408138391e3767d0c1c8457857f4cfae571267ed64ac86ff8a4b61a28b406e1caecffaae6a399f4ec9c000000000000000000000000000000000a420992f850db20d4f7d2ddff33e4dc79bc0c39caee533920c5d03d1c2619d8ced769ac09f664c0921829bd7edb446b0000000000000000000000000000000017e4000f4d03a6707174c3adb74966896bcc0eaabf4ff83cce92a666fbd13b59efa2c767442062b6c8b1a3abd604f0ac,240480, +0000000000000000000000000000000016ac5146ffc26d1f0c33645931bddfb84756e1c7b03f4838467d1b6701ee9478ae03c910b6b4f5c14135487bd2c14bf8000000000000000000000000000000000e1d082f16e4d5c5f0b6fbe5178aef6f781a6fb165f775cf0cd4dd55f2b498a79edf373007113dcdf6b977a87e1fded2000000000000000000000000000000000bb94be280df1aed761651c0292f88037d172b675bae1933ec12323b947023b437d080aac9e196fe5e06e5c4137284d200000000000000000000000000000000064190f9725bfd5d56c31aef7fd1590c975e2ce549fa6d5c71b254851149e0a0dab0b9de3acfc2d64231c79bc0ebaa37ebb3c942d3a1a15cee806fdb0fc3635483743a5b0ee9c40a48700bad5da53ae70000000000000000000000000000000012199d02d3f1bd8c4005878c3302e6a731ea69d69accdd690b4667e847b079563d32e18eb7a440b8005ca936da5e73cf00000000000000000000000000000000125b0dbdb0058639513b007a84d3a3e6302f5d846f22f99a55181f097e200981d9013c00d688a11eb976120f1a5da64200000000000000000000000000000000081e723506635433528fe4a40fe4ecb8a9c3d8cd701c043c0418d149951651e21632cd85f03db33b89efadd69e009ebe000000000000000000000000000000000956af2e67f8ae676abc783c4ec9f85c50ea130410cee8216fe036cd0521b8ea38966288afe7d35c28b30f7ca5c6edc0c193d751c4f24f4808621979f07f03b2eabba75f08bb49682b9df2da7a85a7730000000000000000000000000000000003e11a4e9dfe82cb495e9e698b16c257ea3f4ecb24749751e7334e0f31fbd6677545e4bf9ff78a82853560f7e7ba2ae2000000000000000000000000000000000caea2c527cb3aeae427e92fc364365b1f55e7128a544009be2ff7a5236d1cf8ffd5a5cefc87820bff5bdf1c6bfa165d00000000000000000000000000000000064a3186774da8bb5d013debf46ccb0d894592c414f32de6f77da47f4d42b0c8a13a2ba4f14b9883d564fd8ff6a4c90200000000000000000000000000000000072f6c48b6a05039e3a4dfc6b73501d6d4ca7e840b119da9c074bd4cd2adf4f2c6e9e6325ebf6f97c3f0b00e6b9bfac6dee4eef524f133183b4af95e4445f3ee5084b96c32e284ebebc5b87f3d76150b0000000000000000000000000000000019ddf708ab31f6f6f725f0e4f65d11248d3a79af30927a6f2673901fef9819b189502cb952bd4742d2b8e84acebb5196000000000000000000000000000000000d928535c47eafa5da4ce4f91467fc31aff8b86b850e4582a597b334491b14da71763f9aedb15ed32856382069c094ce0000000000000000000000000000000004d6b3545d067aa0768cda9dc3cca0f58eb546345b96f7d6b9355d47770e00286d962a6b3a64ca2ce22fdb4834a4bb6e000000000000000000000000000000000f4ef9366d342b309076299816c1ed9b424b68886a5c69e21e785f97cb0f99ae3a99ff6b5244dab817094449048a7552da514f21c8eab0edb2405e673297bb595edc21027890ad680f1663fd960ce478000000000000000000000000000000000236c5b4c57ee4facec5d4ff37a478c505217af66e029c3382613442c58875c75cb423789f6703ff3c1c0d80991c9e3a0000000000000000000000000000000016c052de3336002f362d9b0cb386b800860527e0fe81a1a6df0ccde31f3265e6246191b3febd1ea48e9391c44593ab0700000000000000000000000000000000078dcb04ca93c676a9a924e59f924d9d3af872849bc30ca633d4025aecd981ba12e626337635ea77886a45f4da84104f00000000000000000000000000000000027df6394b195222bb8357bd684088e3e2a398f0fb0cb812ca5dcdcd1fa1279cfd03db62e0f8b2800d4b8b48238931656aeac9a669c962817c01069cffbd948d9d8ce764e92859f31fdaf85f5aefab77000000000000000000000000000000000485ce58b387083172102145fdb3e26c6ffca8b35af0e1d84ce9cbd89055be083bddd3da56443924049a056fdb2ef092000000000000000000000000000000000d998b234a69d584c78ed054b1322ceb33f73cafb5b23c1703a9fd609edcabd44f1a642802b9c0b6fde6a6828b50c1200000000000000000000000000000000019235ff13567bd007d77e4dfab139cd57dbb309a3cf6a6198a548c4e6915778094ddf2b05a91f5478169757bf5a56cb300000000000000000000000000000000110f6ea19a7f62bc3e78f4c5c1c6d3efdf1a7f563576e758218b2c363fff8ad8fab0e72431619e4ebc93d2d739fc786c40273bda92c9b1b677edd905d76d75875e5b77841befb2bcaf1fca7674dffd5a0000000000000000000000000000000001d45da76e3016c00fe65bb50f7067e4f06364ad8348184831c4932ea0e0f3a170ab5147e4670ee1b16924105b6fdb6f000000000000000000000000000000000b3468206db0613369b2b0750c98da65b660fc07c30cab4e459c311dab683b6b313b99ec0fbe92ba07f8aab43a12a2c9000000000000000000000000000000000f58a57c449a41105837d5e2419a34201cc921ec77408d6c0c7a2eb227be98ec1f6f6eb9fc088daa0d4c78928a1eacda000000000000000000000000000000000ba53b872dcb9fcabf35e673b467523ea77accfc1b38a5f92d7b9d269c28aa00d00b08d70eae6ed4d2e82bdb06008f9ab77e16276f9464fa2063230d6c1a4152553536c610062f18565c030e80b5cb540000000000000000000000000000000002b82e2b582b247271543117b939fd17ba8bdd617a223873296f7bd75de4790f0d5d8fe523792bc7fb4764d3739669d80000000000000000000000000000000006eb554347efc5f2ee79949bafc012e6d9964ce19459b3867865709d903fe3d11bc617f30f6279a9e62ea104565953600000000000000000000000000000000006a543fe5cfbae629fd3256575e3eb4e0b65864aad6c7f359e169038bf090ed9bd92fef32fe1ac20b2a8c90fbb6081690000000000000000000000000000000013ee42b0693b2f3b9b977fbae5c856e9e4c5e70120b5c29e0a9f898f6d04b7fe351e17b02716a44febcf0a00a9cdd9220be15b654ce22ae4e32987babc4863ffe2bd8a459d0f01f68fe84a75326889900000000000000000000000000000000001ae7368f84e354e5758554aa9c72ab4b00a644cfb9a4ecba38dc72227d297749bbc98c8f5d6149143b31442359d8013000000000000000000000000000000000abf087f77c79cb8c69e4289fae87b2ed483442daec3851a5ba32c43e342be29433b2deac6dbfa7a787547a7361ed0a00000000000000000000000000000000000fa01cff7aea64b649951a8d85fef0bd475f31e47c706b96ee2753df9987508b5e5456cc49e88ec3aad720a2535f6940000000000000000000000000000000018874d020e2eec0e286dce324b91f15b2a4f293d32956b27524f478983f0e0c5b43df802b60f4f001753f12d449cd821c8f1fe94bce21966427380b6d357a3599e9db03a7694159335ffba26fe29e4650000000000000000000000000000000018f7d19362e2cba91023455e115cd90f02aeafcb026349393ca4105e270ab1cf589621b40965fdc9795f66ea0f6a053100000000000000000000000000000000170ce0eb304e0e1047617b709c834b67a8989212e5bf1cbd5a33242be94bb141d5366e636c01a229943bead9a7baf43900000000000000000000000000000000077a17356b3b31faf90f709042938b9e901817f7379b7bd486d18e47d22b0430ba70fb3006e9afa67d7dac71ffaf152400000000000000000000000000000000064aca92c41561e195fa8239800c97d5242ff0f8ce76b0d119063e2ffa09c26e01d23d5728765a59bb9587e885450ad1c6d34471ed00035a484f97f4e8123d40ca23b017b94df65540a5551b905e57b3000000000000000000000000000000000876a57dc24ad58416f910ee3ce220630a1297e6bc691c908e6cc16f975b146872d71661bbb869361623c61670627eb0000000000000000000000000000000000760fc65097d215ab9aeb3d5a5153977e1e399e2cc0b0cb9befb0266d98ac13512a0eadaba4e051bf56794621c551ec60000000000000000000000000000000003c8e205e53075a96c14ec26345c75881a0d67c7ce0d62d73c83dc353cd7b555cde52ffc5659ab0db2179a899f0fd694000000000000000000000000000000000d7e8a7fe6b751f7f478698f4f0d30cd0a435a2295a958cabedf4668769819b4cbd4e8b7721eeb5ced3f913156abcaaff3abd467168bf5e57f71017b5779bdd400dbf416f34f105fe747ea2f8cf4a21000000000000000000000000000000000180546f697349adb2918129f4d0a979bb114d1b58e5baa6cc221a09d7083469bfaa61f80f1e3a6ccde0da54b24d59db70000000000000000000000000000000004074338380e3d7c0facbbc71d83e78b53191af9ba13ba0cba6015bf4f28e4b0b52ffb34c7867a335848f57b5ce5ef5200000000000000000000000000000000148a800ec38cfc2386497d9aacb4327d5953a6612cd4067ac13fb977046688e80032125d4b0e7cb49913e489796a50ea00000000000000000000000000000000132438d18d942e6dd3f69d117abf83c2fa18418e5145cc43b3cb8d18c873935e41279a9e13596f2863be7aeae9b73d172809801eb18d38a61ef8a80f13086d6b1f85ba751cdb8d17fbb9ad5f8d0f835c0000000000000000000000000000000018b3102ce91af86cd10162d3a43e488a0d7b7807dfb9624c3cae76f342e86f8ef1200444a57e2ed7f819828357a6dfe80000000000000000000000000000000017137b470f3c8d1a03e7252e18f4466c9ff809408cbb2043d6b226ae2746d890b267ce3255114b2e073eb66e93c55eb200000000000000000000000000000000054dc1c981c9166d0bd3a54064c33f15ab856b240770ed44adaf9f32d4429babcd0baf2c5b8a1ff80728e9c63e806cd3000000000000000000000000000000001897595f836342ab54bc2e1b72f433bfe3b5bc989727de48575abe89386aaad9b1549af3ca55f39feec14355b29dc9e33521c9cf035b094d754db994fce3161842a9509ec8288699680c0ac7761eac68000000000000000000000000000000000467f1a3093c72aba4c2d9e8171057cf88146eb32f38db0761a5ab2027f2213c89e12c67a338b4b342a73384109988d2000000000000000000000000000000000ab26c871d140c9c4e0512afe9fb576409ffdcb95417f8c6cdc0d964011dfb1e745045766bbbc08ff7dbd6935934bba300000000000000000000000000000000183488902b886200e63465098be87a905810b2e8ebe0364316da798e423dbb267743a0d2e3d93303623fb17df0e74ce30000000000000000000000000000000012c7e79f9ba36cc47762139d191e6625c850a03d5b6e0648032d1669575704c91e48a9ae432bb3553ec66e86e082de689c8c2998d141b9cd3a82507b6dd97e8d32e9e759169c575eb484e9a1559427da0000000000000000000000000000000012ef4988956e026a79e5e904ad3d7ca56793321d62cad46de3cbde8570be5f0ac86d386216152b37053741fe342de7c60000000000000000000000000000000014ff7804312754d23b251a42aea65207695d4df65cac4f87fc96cb920843c022f24cd27731224db751cfb621886249540000000000000000000000000000000006ea693105a1b2afc79dbf75504c256c519f927ea0d79ddd1997a49638a67151dc81b84473208e8078cf71d456f2de0c00000000000000000000000000000000122d367c147c91517679432d3c7b56f2d529d70040109f803b89a04fd8540a6c565354ae420e1bd4ad4ff61427332629dc83c1ea9e4f4fc12a7190e6c71c4f35d1a676d39e30fe688a05820dd989664000000000000000000000000000000000156e7f8f1412cec315eb76f10c92143157313b8eda0677a6c0236de5fd27e5660ec3eb7369f1604082c59e1aa5f94dd900000000000000000000000000000000018ca9f505a88ed2bf595fa9b55d2356748770af16b35bd5db448990b7d41c3aac53aa490791f7ac09d2f5a087f938f70000000000000000000000000000000017c76ca9ddfcc26b028928364ee35829c6e57fda40773a6bc0c259a1b3cdea715c664d7bd0340192aaf7dec7ad20a2ed00000000000000000000000000000000082a255966c4f9d0ad6bd3d88b136cb2cfca09ed6ae378c914c28ff3338a2cd466cafd839f3fff4a30b33ee56e684f4e00be1b9098f1873ce155a66899877c7b48ddda363ae1d2353cb3816f1ab15ef0,00000000000000000000000000000000075c71e21ce327a97024c8ab5fcbef4fff76260a4f8c8489167166c4a85d25096c617cceef73097a4bb956be3eae8b780000000000000000000000000000000016270f3ac86c0ec43b9472499c4d845eab488a34ad9e2148c72cbb1db13623c5dbbc8327c47ce596521bd1f54f119a660000000000000000000000000000000007ad4914ceda9fbc161121c818bd05953836a581dcdc78bebcd82ef548671c899581681c908a337618a445f77c6b7cf400000000000000000000000000000000173f401cb78024e844adcc88fcf0e52d32de134f6300216ea0da7747752ae3ddf4d181b8d266b53d1b699921f9871425,240480, +0000000000000000000000000000000017f40eea638a3d4fec417701c936d61c5b233c9d6b3e94ba9addd0fa0b20adf9f8e07c6b629977445c7750acfd18001c0000000000000000000000000000000005f1ca1ca9cf67c33e3ff174a65adfde2db62e74edf30b5b0156d6b9dc86dd619ad8863c055096685d611ac015ba884f0000000000000000000000000000000001683dc67710880b8af76b464291c17fb0ee4eff3f648ac0772f4a777025c8cda0342d8f5aae3123da7fac57b965685900000000000000000000000000000000143d919ce2cf00838b10fc65374e770bec3db8ecb17d2db08b6a10ac38657bb109f54c1b3040b661c3914ded6f7eb80fa9cbdaa0ddbf854861eac6621255b2102e5343666c735d0384049d5680d105d4000000000000000000000000000000000616299341f2921adf083d1190c212a7941bc0d9fee50b05b265f2e8c339fc3dd9f94607631f485e394f5a7d71ae73d00000000000000000000000000000000006b2f12e22369e8aff45b6c05a2bb72a706dc46a5d1393aaa9e5a7931ccff33a5df2967189114c3fc5dbf69d080e39dc000000000000000000000000000000000981e1b119d04343e075a80dfc189000b4cfb4e321575817aece6009e6b3a6233d1409e8e584f0ac9caaed1f43e40d7d0000000000000000000000000000000001ce4693e8c14032c35497e0f9a586a4541d8a1a68ad014b0850753a04215be2bb60cd7c2fa9be4f4f09a562d7b29f3892073d958260a55b70b580f3ee27c42553b5b858b66f6928fe74b4de91c34bda0000000000000000000000000000000012d634764207dd7a0201703f855365f7750291c810ff292b3e8dee682d7d8eebd6d6f3b3dc8b0c9e25bd2860e031311b0000000000000000000000000000000000eb0859d79fcdef546026fcd380f5c936e64a5665d73f56d92c03dfb50c534a00c857c86ec43275ce69cccc0b53137f000000000000000000000000000000000131bf000fd117ef722b33a1cebd28899fb012e1113f767d0ed46fdad82a32e4327b883fbe29abba1bb7ba3ecc1cbab2000000000000000000000000000000000e24ef1e44029366ae1daf06524d8beacb2b99f60f419cc2ec1a49013b79fb7a4781dbd37785f32ec67c0a28d61a3cea2117f11d78dfead915a94f11fa7e904a96204ddf1835d3501639b83cd5f716f500000000000000000000000000000000067b6eda41cb8da47a424a02a142e2b98b9c69e7023cf616040993f41507798882194229cb6572806e82e9e5eb837b37000000000000000000000000000000000e38693cddf130d3645fd60ade780db84fa700e5bfb74ebea49cc95ab001bde442f363b4e4c61f683b3e67f1ec8c2af80000000000000000000000000000000006d593005cbccc55c5e336e19aded70da65a7fe42b6a85070e491a4ae54e18ac213556a91d5d62786b6d4d1305525a76000000000000000000000000000000000ff86216f5388114dc06deffa7b52a273b22fe0bc8d50804b491fac83e13915c2dd1b8c2779a46b5c313c4e1c05eb2979087caa1e89e48f05bad1d720477199410941a6105f911d589e1f94a851e0715000000000000000000000000000000000262cf4727703fb227bd7fce6cd3f25c1897011ab892e79fa47446711d6867ca82b9b95f129f7ca24dcb60ac75173d4700000000000000000000000000000000136b5a304807e029d0a77b2ed505ee5c920248242f0f95aa07e9bc2e13d35f6f67451d028dc19d26095b55cdc2fae4fc000000000000000000000000000000000b511b2e19da7bfeb183f0aec91bc7db3e7c913f1c282e12d5d2f422a49e7fa78a5f35656dc9c980324717a5ad386dc30000000000000000000000000000000012eae443aae59fdf907bcfe3ee4366e252bb57e268fd569d742456f348429f009f67bf92f9dadd401104ccd2549cecc8255603b470c056b3dfb3acae0dd45bcb3d014765a5181760336deeabff3f00be0000000000000000000000000000000016a827938d8b98e3446445ce23f48a7ba0d634de826dd1ee3c71467eb57bd4c24e0d1b4190f47bd841183baceaa2593e0000000000000000000000000000000011d360e0c18b45ace82eaee902475127d8f18aa4a2ec2081a453f1c85ffe3c59c0f7016f966574a7c51bc14f1935568400000000000000000000000000000000186b5d452c6dcc1ddb4f47b07e01b6d64644f6d01cba8498c3059cc494a68bd25eef35cae05885b9f2689683e65161410000000000000000000000000000000000ff826e5a62affbfd6d2062bd329fcb561f287046870b8be461767759cb0d5f1ac904ecd1f136c5ccd784bc11088233e0eab0e2486316956291feb44de6389b20f8bafe9cc890d86d27a598bab0f3c4000000000000000000000000000000001010e75c52ed0acebe30fc588961c849b7b6298bb8d859f9a9401737c467921c5e3cda101cd4e38e4318233d12b6c7b9000000000000000000000000000000001884db518fbe4d621403ce00521878c0d419d8cf476a1dfda59b7d3c7af2bd91058bbbf54ac0c5cf9a217beb78e3f98e0000000000000000000000000000000001272cf0ad917738bba052e88baf88347d60f63f5b875d604cf0531c1ba7d43e868bc70a682b7274067106f611f08ae60000000000000000000000000000000006e3236f6a66bd37af4be230d4edda6eaaed661f206ca4852d3004b5f358f184d80be6af81c62e5bc8c88e7a1072fe21fb9436456262e5149d02b33a1078e198bbb681699b3f485625784df444bfff670000000000000000000000000000000004fd1e2fd0d28db08224fa7e880abb8c48dfd0e488df4d2ae5f6649f448193acbe943baf22af4b12fd763e3e4ddaa08d0000000000000000000000000000000008df68f276f356ade28500eeae3b755c9af9b5acac5f5f60827b5b2044b2405129b00e5271baf9a80847d3b720026b3a0000000000000000000000000000000005e683d1556f513e6d093704405f312687c3b9e2de3b2840fff32e88186c89b18d1ac558d960b1196594730a9bc107480000000000000000000000000000000018161f8d23c394d10ba576fb0ceee530ebb95a670f2589d84c0646f693086ecb7ed80e556f3ed9434d7fa488430ccf430e2724d3501e3d79b85266fd83a2a6156eeb48e749a61676a1c92ab9bdd6b8990000000000000000000000000000000017860708943449c2227c0f50cf1274652dd32e999d5f9b1a8d672feedde15e9f1af484a7b9462a62dd745bb6d3c7295a00000000000000000000000000000000064f8cb707494f82ffb6374641817a466af65f5c7d83cc2964e6cb8efd021e0c40934a3ffbb0d91bf8a7a616dbe8d220000000000000000000000000000000000eb37cc9d56fa0dbf050b557aaeec76f9f6d0a6c448ea298af78004e41ecd8a1df8fe8640e77cb76b593ee17658326ff00000000000000000000000000000000092ab597967544fda640b145edcb3ae6c3f027c2111dbc282ebdd48eb93287ae4729cb30e45c1c8999b3a45b099dbf0ca49344fe6ea9274a103f323f3d9381e91ae48233dd579944e12afdeaf854000f00000000000000000000000000000000124fa4d48ffc5732fb21d465b559e995891fef98370a1eb73c9264988f75caa93fc134fde7f93c794582ba5cbf6bc685000000000000000000000000000000000b71d012abc1558e49831f053757518643ae04f79234fa92023db9c5483bbd872d24eb87a78960f12930094c4f8fb70e000000000000000000000000000000000651cf0016efea086d98e5bda8e1959e20e4947e302eeb021d196897cffde3e2c28f783521b2a28b8de1ad1a131f5e67000000000000000000000000000000000555ff8a930cc11d320afc3e0635a6f93da1487a5764d56636be4e5803d740a73d84666f6141ed5ee6b778a463823fbeb44aeaf3ba8b03e7ef7201415de7365365b828f2c1a38d09153e51432d35b9a7000000000000000000000000000000000974e769869719f0ee30895df837cff50d47382461c557abc4b8806b04776f401b76a5e630a6ccbd3484980d03ff58d300000000000000000000000000000000098157f0190e6bacbf34c20310f6471166750ea1b235e46a5fae313f90dddc799f21548088322910bb0fd7e41beb23450000000000000000000000000000000007f00d7d18719db9d91e2c32f51083b42c4fcb43c38087f86879ad6bc99600d4c395586187d26d041ff49dbbe517fca2000000000000000000000000000000000510cea4a7463bc5882d0cc25fa967a0b02072627bd57f9a5863fe5255953732846d4907fa301789bf02af9c1b25211c53961d33104649cbfccecc7eaf33b7a2a486c77dca363ffc9fbc9ce4e8c1adff000000000000000000000000000000000bf264c0b7bf68c595b89453ebbd7fe2e64f4ae2c7268ad51f4578c35d48040277f3dac9021997af02e492039348efaa00000000000000000000000000000000083a4fea41cb1e02e5002259f5f7b335c81e15cca93cbc884dc1b08ee981c55f2dd3c0db1a35ac9907435edd7f0ba625000000000000000000000000000000001468e508a02ed7b61f752ac38313345338d2b2d018f719f391c0f3fa1dd1602d9476f3d8829720d17021a459a2732e96000000000000000000000000000000000629edb2530c38ead8717b289c08036c12630cd8c9ae875111749ed893b8cbce40bcaeaf13df4044147bb665ecc2319ea04e97c20b42dc265271740f27f1a833bc5b324bcb843a8f9f8a68231c663d57000000000000000000000000000000001635830ebf227be126e13c634a84f3649d498e0999ad2dc73b9c7360db120dc2216addfe18c00676ed185efa1e789d8c000000000000000000000000000000000471e3cfca449bde0ba2b1e2a5b63d53badcb34da3251313190a35daf694d70ba385976d1f875242386fc74ae0173d18000000000000000000000000000000000986cf3f1eef587bcc70f66f25c60f353e6b15bd105fde9254487e9b522159658d0fc6b6a8a3ea38c27865f1ea4d76490000000000000000000000000000000015a2eccb9c10bc273cb712ee04bef01a11e486bc6a4d220a0f653582af6ba1bac0b5108250626ddf126f16f4015c9d2cb688426bbe9ae054acb6c1fdd4195f8a113727f5617642a5b3c0c65566e22527000000000000000000000000000000001213cbd035615f09189171b3e22630d72df2df93fa8c14427bb00c34f5b55bc8d1b1a59404bed6549b582537a397eaab00000000000000000000000000000000161072d8ebec2841f0f34cb38a3e1b2094a597640a34178ee951e5c993646ecfc3a4c0dd753e7e76f3a6da5a091f9f7100000000000000000000000000000000077e9c95b6c6f726902392c3a16b5cc71cd9d4cec58c00eadca6091e45bc095e53006ce8ac8827565e867531013821950000000000000000000000000000000018cdf909bd9f38e57ee24c0f51a5f9f703eb3d190dfbf75be00969e9e8f8fee331cf32d93c3a956d12f374f8752c2c79cf365a86a8d08db5cd95f239a2f3d22279556975ecc3baae0b774b0323dbb1b6000000000000000000000000000000000cbc27995eaeef2bef14919d48a008a0b0467856f8a6659d6e68e47a2d9d41d217c5913aa1d67911325dbd4fc38e36eb0000000000000000000000000000000010639740654bad5c4ec93f2496f4dc54a7642bc92ed03372ad4edc5fedcdfcf37158d3f02279d4e15078e9d5a7f8b5df000000000000000000000000000000000155ff4d6dfa031b0cc2f57df41c1e1b1c81bf5a5cc1e3aa93920e93c2e2e7a71b56ac410a87855400025badf6dae8e60000000000000000000000000000000018e637da048e7e84b9d1654113978fb148a54d86e1d011d7f5a86cd4f1e5bc15abc5b67d00129f53c0c021cf933f399c528715199c9f47fd6337b6b0e807e230b1397885fded024431c70e453f55f3650000000000000000000000000000000015d8f6e47b8f07b3e07ae0952a7c8f79519ce2828e3e26b284b8f2fae7432b337de17089b5c32f0081ec6c6916f2f53f0000000000000000000000000000000010ecfcdb02cff772db667266cb3f99f1dc28004ffcadca7a9c64b3b5853c09b7793ca0aadb155257bd64fa7bccb390450000000000000000000000000000000011096a52f3272955947304ba037e8b3fce6b2f07f2352c08d5932f4d2306ca511a74dc044d0f0e1e260ff40b0fac5e0e00000000000000000000000000000000130facbe0c1c6d077e9dcab647a44b049a1aba3df500bf27d1c268f71a59635e702c9ee1bdd68fbfcff7ae5b3e6bd83bc32e8643f38f8177b788b8c2bdc25b668308d914fce35c6f9023a769334a51d1,000000000000000000000000000000000b47d58802579e662f34908a4060becd40434e4934ff58790df2a69a759223ca29f42e658ab475cb92bd9c46566811c7000000000000000000000000000000000091d3a4c58a669d3bf0377abfe28d1817168b2a86375928d95df3459c83334669a59aba95ab2b9957d5ded0bd8925910000000000000000000000000000000005aa9c3fe0067338675099ee32f93bc8a5e9ead94b120dfa391651da40cf1ef5ff79d193b0b14b5926f10660aca6c11500000000000000000000000000000000058200992b111461f4d737533301734a5c3731c9f2e7b55e18887ebff4d5b74dbbfd23773606f54cd6a930b85b89aabd,240480, +000000000000000000000000000000000bc3eba5594666cec699a47fa13ee74722a80b4e5a1b28553de37c3163f2fdb8b801e35a19f6d99cd207d1642c0d4ad400000000000000000000000000000000104b334afeab36961824611f0fb58bcac55c9d36604ba73e9c8809085c019bd967cae7147c766df6565ddfcc0cd5fc9700000000000000000000000000000000026c3a6a4ace8638bf8ba7434b59c3aebd4bb274cbdcb898ec6a547b215f32d10395f3bb85a9eaecff5ef6d5ef2b50e000000000000000000000000000000000065bc419f9496b5e81ce72a13fc1bdce4738c2e3faacd80676be31db65ba3e7941ea75e370b6d6c0e7b2cdcce80a2fa14f8bfa3d47ed33a05fe3da738797f18ca5d5b8658055de5a9f85bafe6078f7fe000000000000000000000000000000000e7f1f5ead0f212439b4c47599581982712d2e6ba056f36cb04033ff5eebd81b5b41b874a78aeaa98562899418ab04c900000000000000000000000000000000095e45da9a4b2578cedd13af71e289d0067ecca1f09c014a294e0b250d1e8243ff98a9761030ac855a9d897cfe9fafdd00000000000000000000000000000000030b44b150d1337a3ed6a77f7b6332d7c8103da1aef0d445ff7467b4863e4c830fb782a81d01a6bf97e8d52bf333e78d0000000000000000000000000000000013bb76800375a45b847a96ef6edff3fc3c30e3d45bb4afe04230107f6a1802794e1dc23431797bc5e79e0d5ac6357eee4b0d302be94d437b8055586aa77ec1fe616e30552b4d7d3471ea219c148be069000000000000000000000000000000000602e0bd3d34415ddd517a73acaed5750dcfd68b633d51003edf79a169ad7a3ca2541d7a131c317c957a9597a753b5080000000000000000000000000000000007a964539081fff51e0ec24bb71257f6a1c513fb0047aad84b80180b22133246a1f62958ead75e4b2a68f973d17f1230000000000000000000000000000000000f48fe0f5b5a95e48bde4d8be1b2f352d748c1201b064bc88309a709b285f81260d4535d3e1dc7f1d6ee23ead35abd9f00000000000000000000000000000000135b480fc8a72248f7a4898fffc6c18b7f2f5b1af5cc3846610c299c8da252fb71190d9f761e037c6b47595bab6a03e56765d7f1079b142e513e604d577e7caf82cacae59fb98c6f8990522372dc906f0000000000000000000000000000000004773cd2884e52c2551c1ea0abb78fa17caacfffe13f18b75588484b8bfe781e623075bdf827fc82c6ed7e1d4d62081d0000000000000000000000000000000007e6023fc0e409bfc7d0b7ca65fa0e8d88bf1b4c202a8d1f0e1c3479b0963a646d16795fc5a128a54e624357050fed4000000000000000000000000000000000039f6eaaf99bcc9f4d8fb994a040af0d29c37960e9015d4e48466a9e554da30975c5534e76a1f08a55ed8ce7375b70100000000000000000000000000000000003d2b097d4afdde83a01cf2b4f9d12f77c8e92a8cadc225d40f974ccf385ae65bed1972a365d55e24231d58abed4395a2eeee02d9309af8c74c78e21836f4e6a7a6df5406617e2b4e9d300e37d8a2bfa00000000000000000000000000000000047b8c550310ae246e43b513d39e507f1dace7bcb543a49ae0854a397f62c408ae3632c94d172669ef3e013781796ecf000000000000000000000000000000001592914e260afaca80c0a240426c2828239ad5e256a707530f49cd65e9da2e4bb14a7d6d5978f52c04130a0d434cf4ca0000000000000000000000000000000006c0b8448ad87350db130373778d414deb738d3be97fba25c816826f59e3e926f44956c2e2056b7d769278cf56cf6fe0000000000000000000000000000000000a42d716fd83071bfa014a9b7af6c164d494f0347aed953bd2c1c97ade087a8bbea9f53c507fc0b22d520f28cc5d480cf8449caedd55f0a08825cc1a9e985201c8a7a54d1c4dd96f0ac54214743941810000000000000000000000000000000018026c9f6c86219d0be88955ce0afc3cb637b1c3a531aa2722c56816d368688181ef2fedf1525daec6d9b1651b71f27c000000000000000000000000000000000b40b15bb0621209bf9e33ebc27a7502d90fd3af62a1bb8f54a874a14c105df34ae34a43fc3805c1e4817ba30c048ac7000000000000000000000000000000000465262367e30ccc24632d39bf3af9cb160e97049d855176f665a185c138d5c529d11e53e56c65506e3e30be7b48c6730000000000000000000000000000000009485991319a311052d883b45911be12cf7648b5ca104ffe77594472f7047c803b8e9fb753b98645e630b9913bbc947e28ec5f9dc48931da70ba0cfa7251953e24c4c95cd019e00ac6fda095c1302a01000000000000000000000000000000000fcc0aca0d873cb8733ff7e2ea02b3736b737821af2db06ee6508e161f6159f9d944372c513a03cc4c9e30a707dca0930000000000000000000000000000000015c3774f4e0b30c9532beaa2f7f9b777f8d46bfd3888d6835f4a5a046153a98062efb17f78807fa17b3a995ce720c0b900000000000000000000000000000000083d48e01d2fb58244861a74a1261063f7d20b412c8a44f9945fbe373cb4b9a7ffd4c4ba4054ece0abddb6c14c013ceb00000000000000000000000000000000133c4976454b7be427c4c2ed437bc2e882854d2ddce42d2f97cd3fab1fcf60c3272aaa123a0cbecce1a774946bb7a8a0dc6046b43e6982f11f39412cbdef14f8e330d37fbe6dfa9ddf3656b86f4f60e7000000000000000000000000000000000f6ae7de1dba3b3030b208f61d182013231c4666f134b007b52d36bceb6f3cd77577be7b11abc097cf9618d351d61e270000000000000000000000000000000005803904e3e640e51900805f930638ddd8b86cc1bd50cbd32a142e10d85044cc52ff769bf1b40dcfb7269c913d00b01e000000000000000000000000000000000e6997b1f8bb649c56de5c4bf9968d19712abd22fb7dabee19e0aebd1b13adcd3e8b202975b4edc917d93adf087fb539000000000000000000000000000000000a32384fe03280962c5f575b47192e5ef3111fbbb0a01bda2db1e9733471f11eac0a37df8ae1a891de311770c482c06b0adf4625ec80149b7810767c985c2aa0187987b3649cab8c59a892404ff2aeb2000000000000000000000000000000000531fad86551ac6dee15fbd62cb13f38d8d5c89d23a031b9977f110efcf16501534757bc5b93f0250ff02d6cfdf2009a000000000000000000000000000000000e6d78343049a68514271fc785de053ed7f50a7774b87f264c42e03e6f8f86285477f8cc57ae066ef0fde237c8d1ddb30000000000000000000000000000000013e313484da4d6b85634c5306444bdbe45d7db823616d72821eb64a2bb5f352a4f7e4273fb6557039fa563ce1b091bea0000000000000000000000000000000009a40a984be66c3442fc8946cc42eca722187dd819be9ab34a9c3b4b0de7de3d5f126c175fe84c51a6f09e18623214f9345fd17367ecb06b29d764b22dc1e262ba1a339b6f0e0c77384245e3d41cda970000000000000000000000000000000008a76db551280cd43d4608e9fc629a021675bfdf9bc5a021546b92f3734acff1e97928850716b94d15b7dbcc4a1e0aee0000000000000000000000000000000000b2262872c268782e8f27ee8fefe0827d45131555e755c0a65a7c8b4185269bd621412b653348d7c1111d681f38d946000000000000000000000000000000000dabcf0f847045e01ef70ceaa32455f4c962e4657b840f97a1cff7cf5073cbf4ca8ea75a4887076f155e27e8d7406c95000000000000000000000000000000000a9c0ed94170eddfc485d9f1a770a8b493d4a59bd7156d6cd4b95b55bffa1b597ae9d6fbe529dc0833634d75906a4aba5ce5e62dd15958e6298cdf4a4e899e53644a48494d04fa6d1f73f2dbd645817c00000000000000000000000000000000170ac69c2bf9b48715f445524cab902b18ce6dea7b258481cc59986ae61c8fcb6708b1457be299a6e2f6f34dfd936fdb00000000000000000000000000000000107e855593b6f3bd2982a65167ecead47039065c9ae6e1bf963f81d441f0ebb411eec4b3ed1cff73044f68a4c114806a00000000000000000000000000000000063b470d158ebb4828e875c3dd0ca29a4fd2cd2af356233885a871cb5b77402090f29709c6d6a78f612c8ca4df2f4119000000000000000000000000000000000db75a60fa0b425b8cd2c955e21846ce3c407cb3f96c472cb412498143cc60212de0dfd0bf4de53ae3b345232180b4ad853396021d32530351deec5c266a65519471dce2087485781f33a1423755ef38000000000000000000000000000000000389e79154f627463a7966252deab10b5e809b0c2a9e90989c56d4076b834e2081ddae1c02a9e01b71d96b772766fc680000000000000000000000000000000009109473c7aa614334fde410951a69ac45967f7550890e01b05279b6dff394775dac51d583ae0aa82edda18ecc5e66240000000000000000000000000000000019dd51ec6783c1618a7f12298e38cc75d4fa32fc31438f67eb15419a2f0e9d4b5f70ea59b69e531c868475cada519569000000000000000000000000000000001121c7a6cbbb54d5e30a11a73c158237dedac46385aa15d93592a30fb64fcd94a674cc77afd21a611f704734337905596dfc62eb59bb84b3b6599bf3ce7af229096a8fd5925d4743a5ea386a26c9a6d000000000000000000000000000000000178670fb06f5eb8a4f182913f46f66147deb3f9f634d620ed55da2ccc88895e75f76f55b979e1ba3c3db29710050c7bd0000000000000000000000000000000011adec68ef139716ee081db7122e911ec5a6e1fd7f681a96a713dddc2b742b6e7cf7485b8f45e7ebdec8b1174c02eaf100000000000000000000000000000000089dac9a47cbdfead8536d6cfe8b94d316123bd92ddf30091e16711ff4651c4e2d8dcaf6c72bc159d7de9fd832c6f5be000000000000000000000000000000000c40b871930f0c6826a943a229112f8bf9a3b7d7e07139e1a7d99f97601b6ca8cf3638e0265743dd732cee17fadf996721d35ee6d29ee4816b91d1664b5957767b4b8066775b37c3b3d08729c949d6e5000000000000000000000000000000001040c4cd3c28a752295b115fd80c8ef0e538e1a3906e0d326e46585d633140bd6b8231f50d50c8e7a9018a625c4bdc530000000000000000000000000000000008b966d9433bfc3bede4ddb005cd0c256a168437c31b8ecc83e6fefa6f4b1f2bfd057c78f82bb76279b74a2f7de493b5000000000000000000000000000000000c0f75db7a17e4b712666b16c31b10bb935e7127eb9a0e59e35ec54814a9de9012210ff1862aef5f765d4f7f673c4962000000000000000000000000000000001015e63589a8b56aa643a79c5a433dcd8f4933a10edc9921bcaa7098af435f7879a40868e25d1ca6f7852800df29c2eb3d283067bac390f556891a531dfacfc4795358229bc9a651c0aa71d601bdd56d000000000000000000000000000000000fab22ab380043b01d312004057488ffc958168f8fe4d9c86af622030121e14a46c4308d711d5fa9a414b9ef75d51ba300000000000000000000000000000000047c738fe5272e695f421ed463ce0d6308e05c23b6bd0973df9b55ca96d89c0771a45d53b4d17f30d8cf08edbf94490c0000000000000000000000000000000017bcb3ed735e5a302f76002ae82f4ac74889fa0e966f0fb611fa6a6a09440bc923f447eb6aebe47eef917753b7427efe000000000000000000000000000000000b189d5b64578eb53ad850c826082265e506ab620a9ab9684cc2a53718f26befc35e9431af012306a6190f144a9632bf873724ba35e4e8b731db36f5067aeafd33f2e966977bd0962fd57cd5ccbfe87b00000000000000000000000000000000049fff545ac239696c995eacc560580a0328af07376f5ec819902e30d5e7e40d5fe07295c4ccf54d5c06134370373c1b000000000000000000000000000000000bff448d5ab544a8cae0cacd216a6b6d48f0abe1b4bc946d95c1a8c4ae44bf049c3b572675a5e20c1b4188fa27a867a70000000000000000000000000000000011dbc52baa00712f66def2fa8fc77bcb07431d3285774e2517dcca65e611f07aac265856cdef0c1637def44c382230fe00000000000000000000000000000000090af0898dd578123c65d1f818c3f33866e4acea19aeafbb31bd8da029ed1daa2d7ab3b22147eb32a09021f7a78fdf2acc5934c02b63797010cc8474e90fa5dc88d73dbe5f9be605bf335057fba47ea3,000000000000000000000000000000000d52fcbe9f1776477a9d2149ca55e0651fe9d098a67209ce2e7d772d4901ff2c70be432b53dc94886651865a81ba8c620000000000000000000000000000000006b54871379e2be969f86c72cda9acab9bc99f73de987f17ab8b25c63c55ffa2cff61b87e8c30d9f712afb62a2b9cfcb0000000000000000000000000000000005652612b19c38650d1babd4772722ae2c560e2914f2e246725cea86dbe1275a981a592eb55077ee4b7c6090e84d2ed3000000000000000000000000000000000ee37a6d42ce69aa67cdcacb19efc230c6c34969a2e081ac77e8f9d45128a6e8fff923c7647a0f168fee18342bc6d845,240480, +000000000000000000000000000000000f114e56d10dba7945d125fe1ab7871d9510771548d8388a2aec8a481de92572645b73631f9a60285c3eebcacb3bc0f5000000000000000000000000000000000667d3f31955df11e4e7896a1856fbd4e573f1cfc906b3953b5806a5d01dcdb96009d9f148156a3828e822435f722c5e000000000000000000000000000000000d7740ae776eb4766999f5671315c8965ccc84ff71757e361fbbb55babeefb96265c97df8892acdd6a9166641f656e62000000000000000000000000000000000166529d1a76ad784557384cb971728dba298baacc2f2a39ee36516bc7a761e9a7c29e385cf5784efb9f6e60e998b01e864a1ee754f6b0a86923e5501d54e6f47d7ab9e1483294ce98be13b7db52937100000000000000000000000000000000133e0b08430d9318d98bcf58b3d8f51c7b717fab56fe25f434bf521f830c7d4247d87d3df910490be2ad38adaa8eec26000000000000000000000000000000000e15afaee4f1ce6290ddfbc13cb887e540efc3fd8150dfbf3a5e7c759ccb8f334ba26953c7bbc43b5234b857159f6722000000000000000000000000000000000e4cc685524d42ea5e435afec7b3d7d025e93ea06407a28c246a39dee8ae77514a0bb2d5031f7367d658027299762bea0000000000000000000000000000000001b231237f7b0538d51adfa4ff92bf313507996cf5255f191875970ed4d946cffa8620b44045f4bcfd8f89baadd331fd93064d187f7d21b8b0a7092943de13b96c2e1ac9b578637a6e64331e2d40f53900000000000000000000000000000000084128f1848b2b244e4812eccba01287b9d07e85450459c8c42b01180bdd843058d9926f39e2fb5f610651a00233e31f000000000000000000000000000000000055ee70765f2cccac966dc08abd4bba0d004b379a2c6bf188f300f5d413f84e77ca1d462219bfb820d7f585b914a52f0000000000000000000000000000000002dd8f1d1cd85a5e6ac793f7e1e3cff887204aa4a5fed92f2088c06eae95842ab2c04d30d56f4b0fcfe61379e8e7c6940000000000000000000000000000000013e318f8b6f4165a8096c76ada440154901de42d69c38e66d9df4ffe5476666ecf7068e7163f29f04972682c43f3b0fd5e676b40c09f80be5d9398a9ec20cb811cf6819a130445203d792a4d34fc3e950000000000000000000000000000000003415c8bab713aa18d3f0d54e0101ba36793e6e9dd3471f8eed9a15e00d8312732a9ce88b5f0c30207aed92eb173ac680000000000000000000000000000000008a7e145e9576be8ba2fd980fb1735a2b73d1bf5f3e108878b721b6ed8378b5e0f03ecac179a6d148541096ba483b40200000000000000000000000000000000029e5554752db8bb87d58275268f24ccfcf3e0923744d57473d54a72e2cccb847eaa8f3bf638833a934c43930fbf30990000000000000000000000000000000000e0f2ead2697110a132c4ce1643b97fc652dd0660deadaa4e0c45e7ebfa64cb6a6fbbaac7c4e2b725beeadf6881ae5893f63a87972dd11f5239c35ce269e4b9239e3ae906ab117f1f045d3acfd16ca00000000000000000000000000000000014325fcc087aa108f152b42759cbc02cfa24e7e7cb995c78ccaa9a283ec2029c08cd747d599e0685d365ee99eeafca880000000000000000000000000000000011da603d3a1128329af19e596ebeaa4bad034c59581e9fa2e42a0260032f84654bf5ce22ee32c34eed7515d7fb0fade0000000000000000000000000000000000189cdb5b934cc1ec7ea0cf4b8158a1416712bb59c1650e6d244de33bebfffd3691b499b3ff8255b1b513deba709f7d3000000000000000000000000000000000e7ab2b279d0d5933df25d8fc4faeb8ca907e7bb8588e618b92737fcb6959380abc205118d2e3fc128b89a2ead5ca906145e3456d5ca6aa5910430e5a19567c327b757377aef98c4f46fe9a1f52cdc5e000000000000000000000000000000000895b6777e677732c74cfa82d5348c4c8ddd63ce10347836f5140b9a64dfe631804ea3be8e20bd4438f5e7fa14a121d80000000000000000000000000000000002422cc4781f007f732239ff9eedc126777d6ca0f0365dd90bab6b68c9e3d02ce726726a6d30d7d51a1f0b45aec1854100000000000000000000000000000000048af8a79663aefaff77a934f0af3a09ba02077c13a794ddb88e5c679ce348b3ab0fa217954ce1422f4e212d1383ebdc000000000000000000000000000000001190fec6c510b0b16e1505f737b25dc2401e9fc2c95bca92aa5d6e93b284b766bfed93a80b137e5fcb339983a86acd41ce27de5d3a5ef941d058a458f3ad2a386f1d66945789e51fa330fd65da4cd5080000000000000000000000000000000014fbf4d005f43563fb7408d1f20f672c8983120c66462ba9156b64a287e66960fecb41ca129b6b14466a5a0de91b81c50000000000000000000000000000000004fb283724950174d60f64af7bc8a7d059431332c8f17769df33f6607d72633aae3a8d595cb8d5af3f8909297844b3a0000000000000000000000000000000000e187476a19280ad9f33a55c50f37f765e343f92938e247ec9fe099c7f3df65e24af14885539bfcf3efe3bde9f2700ce000000000000000000000000000000000f086e6b9e845fe3b0c5100f82bc8aeaed166bed9fa4d34bc03ed86342a997101c508a4c096c4f67cb5791cc1a1fdb8187bf5c4624e86aaead712987f313e5db8f2fe6787fc33481ed6e5c4d3e96d5be0000000000000000000000000000000018dbe48c54347635d4b6bc17ff5ba390a73925f1b180d2c516eafc0936aa9bddaf7317cc0c211fb2a7f7bb096369a45d0000000000000000000000000000000015544c177a4b8018ed60c2639b43236957c2d995fb0f32523654584b0bf052e0930366a93406e1ec5c6d2edb955e811d000000000000000000000000000000000802d2cdbc5e15b25c77ded4bdba087f1d5760e6ebf9549a37f3314b1e88d3d6f58da9d8c6e9ef85028a271b83dd6242000000000000000000000000000000001577bfeaf213ca8b0983cb178e9634dd18f74baf02f6ca31b2e3b287d80a32d4cf11afc71df09ca5bb0bc8e60fc7ffa968cfa3fd0692c9ce56538bf70e77e2a47534d9472ac702c53f2dbe68217d53df0000000000000000000000000000000007c059044ce0c15bc527b19ce85cade8b1d5a9cc6dd304ce9a3c461e631e17c4feec52a0ab5cfab6a2270c75f73df86e00000000000000000000000000000000076344286cedc8c180e3bd762f12ac08f0ecc51293b9f9b8e7c0056ceba1bbb6fab4ee39cf559fdbd601db6c3d201199000000000000000000000000000000000bf6e708d0a4fd85c7566804e19f21f7a00bcc3bd7135f6639ad30aafef2ed1e72c84c8995b0e59738c2bf1e4040621b0000000000000000000000000000000018ff3d0ade15b690b6e306adaa5c10796b78ed7f8a984f637271cccfd39fd17c1e8288a11b051ca94de2a9bd04fa96d7a36b13ef742bfe88882a4e635b5fdbd9b079e1adf3423dd4962835c68c9617c500000000000000000000000000000000025cb808922f6deb0bed979b80a675d9324cf25c53de373534d771afd919a182af9aa1dc26a2d0284887121bf4d6b6470000000000000000000000000000000018970aa4f456c1b203817322df2e222516bce67ff9ace069599061c6229596e506c0286171f3551302e45b7d3b69a39f000000000000000000000000000000000a57d0da60f03fd4a5664546f9809c771ab6188aca5102c31f26b09950cadc26b0275417ddd9c4f4cf29794b739733cf0000000000000000000000000000000004ebf2bd93d7921d8bd97ee71cadf91145e064a33651da2604ed6fc8e08b1b8305005f12fd4e6b68b7b6a3b5cf123b1324c54daa7de8446e5a26cdbd6741cc90bfd26c544fdf221d47d509c978723c3b000000000000000000000000000000000c8ff29d0333e3f38fd8af91ecdca49e54ea5dced71b60d693b1bbade99ae668e4f994f7a5417a08a8ddafa410d437f300000000000000000000000000000000078ac1d0898a9e6cae29fe6b50e435e5f543d0ee233346728c46d659c4338295f27b42fc4b2851ad5035feab2bea8871000000000000000000000000000000000b3a566d2ef4467f21c27e4a3dec99a26c304b32ba1fcce8276a8518383a7de44de5b4011ba738dbb8761e67e36115560000000000000000000000000000000015a0aab8c3d51fc3fc8aa35dcd07f8a08188976883f9d3ccc87ee148525f2115ca46726a2e3c550167c169977b216d6217ff7a416011549f144a3a65238d62395f4f76afc09496902c064b27739c6d0a00000000000000000000000000000000115589e8e1440edcfe72c008f6e9cdf13fb7baaf70aee16166e7f32f4651db784f4c5cac15d91ee13001169fa777f0d00000000000000000000000000000000000f86710678b01c8f648bab2289e8f90648d9470cb13d5145ade526696d22508a4a59164290586c2c000dfc55b4a20350000000000000000000000000000000019b300961b40b0d9fe6e292e9357d04f0483ab3a8cc6f8f522153c51d22de8e96a812adf720d13ff7d05d1e68264638a000000000000000000000000000000000a80b61ab051ce413ec838167fce393f88c8a25f403bdf07cb60391fb15306a5271a7042d36f7c46b5978106a7b5293c4615de9bd7aebf1acedd9d40fddda34e4a85bc253c5e92c20d984f6c4cec533c000000000000000000000000000000000567c33d22805319418cb1ea7eca6205a6c44f1f881c03e37bf3c66a1baa5153473cc73b8c25d497b0b0057ceb0395960000000000000000000000000000000014d7a2bfeea6a746e709f6108eb32581ba38a617e4450b3567c77a992988d91f4da31b209286f8e9fd0d7b8628aa6c4e000000000000000000000000000000000ae6c9fbf0e06f2e38e91699cd21596ba90f92f6022a4f3c7c8a6557b7e1331283bd4d7a7d31d77d9d7cf70a2945ea1600000000000000000000000000000000066b8132c73e1da8ae7fec9169770a188b686f223fd0306441356040bc9070f34a47fe1bb8c94de9fd7606c18b1d2b1dd38f1a0417a5a366dd2d8f5ce229afb6f34c1b663ad6eb1d9ff12f38412f00f7000000000000000000000000000000001460040d0a19c37fb0736ebdac0324d8a38c94a73fc5f602b7ea5b7255be9d4b6ffc22fea5043d948420e9ae3476f56a000000000000000000000000000000000b37c0078ab8babcefa8874c6cd1c5184d713b976852d087ed84337073fab3054899859d0fac2f4351bb75ee0e534fa70000000000000000000000000000000004150f3b98e6166d9d6b0388342042dd8eff9b8e1239f479330b64c5b316f98fc7bb401b737efb87e1f6663ca4efa26700000000000000000000000000000000043e6131c1ff621fd6f8caf0939487a927550343e24425ada33cf622de757e6e75c9affff9f04373a954557181641617364da9c6b07aada98107447afbb189626180c5eef31f7f2cf26d5d76ab0c74590000000000000000000000000000000009fa1754bbc957d2a8317a2eed859457073571379cc7c6d65bc6a0b5829f8142db77654eb98a2bb0cfa5223a27d756cd000000000000000000000000000000000cfe8b8fbbff7507d3d74f4f550b4c85e19b8929d3728a462e12b4008c79014103153c69ed8dc6b743e1b6fb4720bad00000000000000000000000000000000017ca0c08c320c12502a1dbc841425694bde68b7806eddbb40702e58ed26c7e112f9a821a6c67afed174f51896ec2287300000000000000000000000000000000014d08df9cf825b07a387642ac9959e8cd15ea8e752231a3047fa30816acb1ecb79f1755484af9a98b993f50128c2bf5031aa8d860e3b598ad0c4e9f93f26d153f8a8d8d0dd614ba868ed055c517532f000000000000000000000000000000000273b64e867a9111e257c9b32484655e4d7e676ec50f174d9ebc9fc4262c037b176ada941dd8c1abf645e275dde04f4a0000000000000000000000000000000008a63b9604e96a5034d92e3790411f3112c2c7cdaa056f9f1bdfc0b164c37fc9f58dbb566337132cd1626f9ca2618f800000000000000000000000000000000006a661167c9fb6c26bfe0a3902f309fa683fd22729bfcb433756182e7e1a406bf44ae1d13ef0228534881daa339394e400000000000000000000000000000000193c6c5ec200d225c43c6e37cfd15e16e49b7d87e5515bb7b4c918903966f4f6ae0d42af6b98f6efdedc9b0301fa1c0f290c467c4827c9252b82ff523633ba116c52d15df9cd4e3121ff0e9f754ced5f,000000000000000000000000000000001403c7e3059135ebcf5e752011fdfaf66e348135314f3f4239b066e1c6192ffcaf89bad4228fcc2be19a64f4f5386f5e000000000000000000000000000000000aadbd8d0e53d5b409f7fa508089337bcf36212a3f613b37a95757793dd6b0ca99d1b3578ad8020d46e29c9c4197ea070000000000000000000000000000000019e43bb32f92ed187fc32d9dbe24a486e38316a3cec0fd7f7c19b313af43a10fd63738b78e609e04a083de6761d53a90000000000000000000000000000000001490da7d36ff16304b27f6e57412975497e9f3a6d35cb162464bcf69fe141d34ae27a33afc75a2802eb120e90d4897bb,240480, +00000000000000000000000000000000038ee0c2c409d8832437ea450ed705589c82791b8319fd0ba6fb4d302d3c5b73ea0521a0253716e5810f03fca2e9dc720000000000000000000000000000000018c9d748aa685bf6e11e6e4b6ad2290ceff59c8837a088b41a08983fb2c5ef077adb0730b298c5df9aa02a820a19a4bd00000000000000000000000000000000015d248426e362ad2489c0c6a567d80b22d54d6a79e198198a771fae4c4e97eb317da9feba8eaafc9460ef45b1a5e5690000000000000000000000000000000005a2342412801cb37911a04d7ee3b1e5d3dce2a06e0658d59f2ddcaa9ba32804a1ddbe8f4d00f4436aad1346ed1ea5344aaa57782608de34c6334ce5039c67767f6da7b315dcfc772f03aaf3dd1e67b90000000000000000000000000000000019d49748f05458cb9b316e433b0d341e23bb5aaa724b824bd147596761c11efe8f4940eae09e302e563e14e96b814f4a0000000000000000000000000000000018011e7ee4988da168adbcf81cd14a9232edacc06bbfef0fc78dc0f96b5ac86ea67be8661442b5ef60e3889f3137182200000000000000000000000000000000175a2ae3bdade6551b23656c16884ba0fd4247df4ba7471cf81022d7e224b23490db153c8289f95467ddf9671f8b6cf90000000000000000000000000000000013c58c0f55c46bced98faf3865e3b6a836252f252e97b6d2a799b574dc569f09ce33082880a4d0c3b8a2c7c0d4c30eae22c1cde67b0e8ec7217c6ec72f36d8a1e73794297819de9ef6f1e52acbd3ec4a000000000000000000000000000000000ee45d5689a8ea6132d5ace000699a157c1cea3c0c98b38d504153d64fcaf1702ac7a1cb0889539d6b15489fef415aef000000000000000000000000000000000b320e0cdedbdc1fc5733488e6d2aece6386a030adc36b0a69dc3809827319947049f3861c2edc859797d30a3689322b00000000000000000000000000000000194096079b3a1d6ab1080dc71bf6d5734bc7b5e7f30bbb0f9b95c9495a6bc4adf76e198fc66accbbbaac215a8932d8c5000000000000000000000000000000000ec07be0cfa9b3d3a64c016471d9e6d25228b46dcaca6e197be00b9ca5087162c35f1d6326a3cf83f568cb06da8c5220895341f4363b688c4e9660fb0cd17f6c111a5c92e732205fab0d0da0175f6832000000000000000000000000000000000a7f3a3fcf2e7b0ada6d4fce179bdf229454002f1271a39d5e99daae72da549c6ccfc7c574f35bb9784100675c30b1120000000000000000000000000000000000fad14ab095fa09bea919ada313727e7aa5aa06a1cc7746d006e3eaf70f79c5e4001a8a8de03540b45e0598b22710e00000000000000000000000000000000015345ade62c5691690c181da09d8f39c1ead42046987b8c7c975d40690a286a816f8cca519731d0ca23349c54b30d8570000000000000000000000000000000019f0a32361bb6ecd8b1d87c2e15d31c0e0cf995eac9facd5eca123c0799c465f156b0142d98e0f315e9b3595974a7b824c5718fed7503c5e2a97fd6ab0294d6c42b1d35067e9d5ec1077176a4bd3126f0000000000000000000000000000000017af46e78904915e348734d2450fc6e1938bcf002989f855082e3b4ff3366d81ee8d28293609c3c3b11568668b1305f80000000000000000000000000000000018b0b3859763c2654fc00792a5193b7317fa5051bcfd15ea42be2fda0f43adf322219f34e54b2446ef73a4562151f9a70000000000000000000000000000000015c23509a1b324c649ff878d004ab5f253d041670ef172ec4dabec7a525d5ddb8f9f62f383e3f71b0e9c98532e247d560000000000000000000000000000000003a38564a55fdbe05b047281fa153f736edbf48c901749005473255333590f967171a6fc88751eaf57a5335bbfb6ebe86d055ad484f5054e8bd0d073cd556deba05418ef1235d08ecbf8717b550933fa00000000000000000000000000000000100322c4a92c136437714a6586c82a6842027ee218bf1fdfffaf95ce47c9c8b6c8f61115b092dff81ff2e645d0a7a4340000000000000000000000000000000013a91ed8629acb5e770683015c3c248255d673d4b2e6c96334d1c80326d1a8b4b655c81175e4a914a45fb37c1f178bd10000000000000000000000000000000019075c2eea3f64f42be82fdb8f83f2c68c08e858702a0225d869143c0b017b76a7a40d809116ffbdff6700b288f5ca3b000000000000000000000000000000000598ee9ba9d56400b59c7f5977aef1e179855a37179fbfe97b95f19137b6034568e5c7f616943b4aca804272955d42334cccbb062c27a67ae2783ab65a47ce166330cfced1f11b85f87483e0250b138400000000000000000000000000000000025a526b137aaab5ac1b5f8179a18b06feb7c905b4a843cd55e31b7464c2b6d432b569e9bfc3222511c18255102aba5b00000000000000000000000000000000090c20c9f78a242e52daa339d5cc1c3f35aff7ab802a3e4366597db8b6ca43d30fa0fe8d9484e49fa4fd0bf5509f19e6000000000000000000000000000000000e928b2173e32e5fc9c373a2a6f126e1a3a472c01a5e87677be0d29907022b9a7dbec3340cfc89e67377ce472c2d5d4c00000000000000000000000000000000147b4eaa2dcee39b918b7cdf24483b29466120677e5d42b51353a9b2fa207bd911d9b391142a13a212d0ab38adcbe10796111cb1181f048f51349aa2953bba2af50f7b7b5d2328d435bd63a7df5cfe5c00000000000000000000000000000000007790cde9ff8af2d7597d33909f00963eafa228817de1ebf4233ef0831202700b99641318186aec80ac913a1b1143eb0000000000000000000000000000000009d42ea1386d8b019dcd26068ab156f399c35b7d492722a20da0c915f7abe44ba688d9486f4bbb44268542c5a49168930000000000000000000000000000000010611f233bc1c4af0a14e1d1b945c91c077ec3dda592e2f852e2de41e09331664e1a92f9a0b7416c50327bc943a17b9e00000000000000000000000000000000048614243262dd070a754f40652b96a03326fc51273dddabed85df0654890ff38e0da7abb8190e4ebefdd6f78a5fec509d7f0c0c7e927bed3fb930fe2d0109f58678969ac8e14fabdf4ccdd0823f706d0000000000000000000000000000000008451d24fdc873c61db44e57372d43c35a2a8098255f9aad3a6b244913b86bff6444042e391685b1244f009c5ccde935000000000000000000000000000000001177c2da9972a2b96afaf866f97dc149482fbaaa93e194803c09c8334c2c7025e08cad4f7898959a57b07a545ecf76ad0000000000000000000000000000000016f40426cbd1f0f4ca5ae1dfa4c3960a6fbd51a1b5b24ff5d03fb9911e908406a0ecf4f20a78a280d24dc9bdd1c0799b00000000000000000000000000000000194a8c55f549da1842cc3173f3eb7bfd70df26b43a3059a3590992e34fb19b2caac4149f64d442965e166225b9013e2b11ce517fad2609f2ab8d44ae6263623a7903b2cbec683570949a96fad78fc6d3000000000000000000000000000000000a97664c1d7624cae0e969c728a84130fe260581305435ff8ec701cdc51a73977f58c891ecee637eb6b7c972069ebbb80000000000000000000000000000000003f4ed6a9e9f4229f0fb35394bbc10da9adbf4985d4453da64eb312ec88cb15bdc189a3b5df1af3107a36fc001ec92ad000000000000000000000000000000000ac552c5f6170a70563fcdca8e0c6a7c6135af2f9d5ae6f60a2c459d1be4cf76ebcdf9bcd891db8a1e2fc905a23a97b4000000000000000000000000000000001734a46c99e776d1ed4b807f5b313562e0989ad5c67dbcb961c134f8b7b7601c23308839569dc224bdf7c370c4498303b17d28cbcb9efde6d9cdc4c9cda385ce598ac8468d4fc94cc8e98ca3bfadf440000000000000000000000000000000000a523182c886671435ccc75cbc78293274802c6142465acb31a1809e43b1d656ed9c808068de167b1ab126ed0f73a4490000000000000000000000000000000007c4616080b5a002fea3589d54c7510884a3ece705d27dee315851746b1ee748e8a08d3516d8c6afe1c0482b960a9c62000000000000000000000000000000000dd1bd9b4b9c140aeb97887a0266bfb5696813fea034b78bb7d0cf1cca15b5bb0ed92a97841c8d8cc614f7721b8b7e040000000000000000000000000000000012a41a8941b6f0e4c87f8188718f9bc75305d41d6f4441eb9682473340fce0bbb463e1b922d3af8daea32b8a8ac9c3b4a9516e93416bc7b0f3c5ef5da6112abb73fc285a14093ed19d8eddf241169119000000000000000000000000000000001763ab2b361681955735ae00b69f26e06469391af993c8dc6f2e1dffb52ca01e49d58d6e2249e7433ccfb5ddaf8fead40000000000000000000000000000000003858f3bb01b2393aa4d4d7889bdeb0bb9bcde0dcb9b39c4ffe0fcd0b865baaff75b676c715be275929ff4303c416e0800000000000000000000000000000000086d64bd1302b0b3a620b87ac29cac3d9e606513ec8b47898cd852bf552c1364291aaa842616b92c8936e076e59451bd000000000000000000000000000000000967c9f59c15ed02c9b2da6e76fb0bf3d445ba849010afb7f9c994b1ef6a05ad577570d4adad043796eb90e51537ce5187fed462636eb57506f870ed1c8f66e211758327f4c19bf909a6419312c58945000000000000000000000000000000000e6b0da7b406bcac2dbb90fbf430fda6442cc2860ce633ab84404dfbb426949d55ecd72992da1a2e8e1ce229b599232c000000000000000000000000000000000fbe3a345ffc8fb85cedc4b8dedf9d952c41b4ff6f1c7ff4cf91b2276621969d905aa9aae5fc89bc516f96b9bd1bb3c10000000000000000000000000000000018c2a7fcc35099c41bb851ff66abb047e2af9cf4fa9fc45f030124ea2c7efd26e594abbfc7a7f258c8081a3a80d15105000000000000000000000000000000000a27cd33c2121c9c542e27b52a13275ef7e81dc0c6ece883b65e71d2bc3e7246f95aef7c6b41eace382a1400568cf298c373d64034c78482d6673c6906553151887c8aa28ab2930659671b8cb98a595700000000000000000000000000000000158bd8e6198d22b52efb7f3b945668666e1190a4a8e70307ba5c1b737316a8f8568092f219f683c0f53f56f25745d4e600000000000000000000000000000000097e64e4553371c81a9bf553ddd9719f59b329284eca0d76f023d603c29a034d123ab777cf173c5f2bbc66412d69d4ce000000000000000000000000000000001298cd5501e136a06ad4fcf87a75c0c7b96c73e844863b74bf6aa581a0ea98c2b1f608c668743a3e37ad5ca2074af9340000000000000000000000000000000017ff9f1336d7f2152f17daddde9d3e1679cab8120ed2c0288b0908d4e2099a08c9bc6f79425f004ea3ac4d684abff6dcf29c901f9769a42610958a8cd53eaacd9e5c4656106fab536052518b4989911700000000000000000000000000000000115baaab8f0331894da531ab557bb454e2003010ba1dc1d96e3d983d49b1312585c6d4c43d85dc074b23b2fb28c8a1d6000000000000000000000000000000000db1621b721c8a54ece26a355b190af5f3e1dc1b43e0827a1912ace651cbad4b980e77a4c3566aa809157229b234c808000000000000000000000000000000000c594e0ed3f7ee55886e251deef9732aea3de11f094ec53907a843b755add8fa5d00779a66621e615ba7772ee821c4030000000000000000000000000000000004e80aeff6c4b85188903b4d2dcac4f94f7cb4285a38f94b0becb556d83dce8735d1db5810b409d45a8dd1b9a6dde29c125c12599e84b7e648aab52cd68fcca7f1a5f56c854f3c36e0445ab7e2df2b740000000000000000000000000000000000371a74468ce2ad90e19b7fe3f57159dffb1b0422b32ad693b2fe6c45c5d371b97a90054095da887019d25c1ee8197800000000000000000000000000000000010575e1ec9a3e609ca086ef8bca679c4548482d9e0da2e51878158ac8e5b29d824c31ad7ff642041e748efc50c2514e000000000000000000000000000000000ef36130380f1e84b2f462b5f970abb8535431b79813015261015c6d7e74f038b47504de01794840d93fbbb4b386e17500000000000000000000000000000000018419e85fc2d75f007d1e0e02c1975332e03d42c3b41c50c3538c3625e702161cdcf8913babd2995aea7566ff15abf2bb9a1d051e33a617c25e17b7ca8ae6b02f16c759cae0df7fbd403372eb2407f6,00000000000000000000000000000000125406a942ae0119575453beb4c093d2696d3bea7bc031d7a586439197f848e1d5a82b925b4e96138a3460eecf198ffa000000000000000000000000000000000befcee6bd1412c54674a3d519dd2813b87b18f2ab3375a731197e9f539f8f8fff634f15647e7fea3c65b93594343c2000000000000000000000000000000000011e4d432ee6babd502a9cbbb5cf4839dc6da6176b6bb0ba51d99a3587465f5f3f83f4d4cf2c7e6187de93b859ca61d800000000000000000000000000000000168509010b867aa198fc294a5879ce14a51503c1d0e8fbc02ec08cf62afbd357ceac24b633bd0fa99f83dda92e10724b,240480, +00000000000000000000000000000000070a0d060c6e9bad0d1bb15417578daaa8b7a5c984c4947dba10fd874d93fd1e3994337c87799d78a674087678d9168f00000000000000000000000000000000128985b69d5d6ea0ad0b19eba7c2b430f5242a7e89626c66fb83b58ca7cb65a479de4b2fca6886cf55b8cfb52394102a000000000000000000000000000000000bb0bced708571662af042d18956b5b7d797b61aba70823618682287deebe69bf1f9a94ca4059e0570e25a39e60b9a8b00000000000000000000000000000000193f0793324dc78c40f356dde030b632feeb1609a1bd75ce88f0d313a0864dbf1f5e92826870866ab9b3c98cd1c12aa508c35887835bf4497d673936f40ed44145c5d5009fae16eb0f3ee9168831abf7000000000000000000000000000000000a61a310f90a5ffde617b78f784b2e699cd77e7c3e7c483a2ccb768f94d68e59a2a4521410c22ef6f21ba589ec3abdcc000000000000000000000000000000000e6568c83e0f7e459b27a28e5bf954983c5dee478a009c244da16041e710ddc67479cdb3da6f47e7203fedb8f765b2490000000000000000000000000000000001c5cf6b948b85a1c426fe932cd87605f1fbf6c932756eb1bfb43beaf012bec4612d8dd0840efd4cba3f5394beb65112000000000000000000000000000000000e02d5bc20c40d7cc2165a21ab37c6e4eb71322c01a43f2085f93b5b02bcabcd668dab90323db0f9288737d757997631a0154f7f8d52319c9e5cd59052e91b84640efe83ac814d95370e46aff4334cf400000000000000000000000000000000165287d72eca1ecda5fe16a555245b0a34a04beaf9177466bfd88bbc675442d206e70f7a2063b6ed0e15e9406232f5ea0000000000000000000000000000000004c0608bd7e01e65a15716b0c505111a3abb0abac3efb846e05e8db59c063950dcee052f04d1c4e9e492bc6740fafe6d000000000000000000000000000000000de897f7ebaf9089f7e198ee41e1efd7d84fbec7327799b9293a489965cd36159442eb0dc1f79f6b1f122f592b013bb30000000000000000000000000000000009774586dc359e5d20486f00dcea6ff93948c5a8b74058645d1048fe46ae3330dd56d85204d328f43f15e674020f353ec252ac28ea29b5459cd2ae5bce4bf08a102280c093b9962cafb481016a212709000000000000000000000000000000000438ee51a560aa419ad6ae45e1014c38b7c43f1f6a512bccc2d4f10a35838369b71799fab4b6a754fd938c1a1b874fc0000000000000000000000000000000000c1491c85965c0b74d08f5866ca727fd30bf641a6ada0ab8363ff01916c37d10b1b7eccff79b396c587d9beca2c826c0000000000000000000000000000000001452f254ceae9626443265ba31a1a750a425f2a7789e69cde16b70eb319c744a6221e74a9e2881c6bafea161d29638df0000000000000000000000000000000011bd6a1bbded174e9cb95d74492f7b07a755339a6c40f2a1a76debccc0f3a32c7017ca4e6679fb2c038c751f19986f526d3bb5ee3410dfad575b0fbe71ac5df2048f74b52e777fe0955d6e244d434f3b00000000000000000000000000000000139157c34aaf70cbfaa82be655281b085e37d6406df4cf8e291b221394e91d9e3cf04d431f15436064d0bfc8cbe13701000000000000000000000000000000000353fcf6e587e71e59d8f05d4085961d37b1f62694dd5c7f40efb5875b90459dd66c4d2d6c01a40834307ae9e82c2e08000000000000000000000000000000000a4975c9872fd167d0ff4cc80a6ce179b1e6e1eb21c8de80321451b1deffe68d8a13db26218f14935b64af25d63644c10000000000000000000000000000000001e8a2824f21cda745a24844ac0336994fb18e30608ac61201a932c0a5a58f1acd56cbd9353bfab4944efcf2859ad5915c30684c596976bf46384e6afb2bad6f821c4a62338d7a6eb204ed75070b1973000000000000000000000000000000000537d7a9d7d9dc451cba4d50630caed32e182cbbd95212577b8c2855c327530e447a4f3d73c7d63fa3ad5111254c9ed90000000000000000000000000000000006984b32955fac4ad3c0d181c81b98534ebaddc316d51a40baa1028bacd6a93a20d4bd6cad6a0f8cf7ade96bcd4d68dd000000000000000000000000000000000720c392a663884ad4d8daeb7279ac41717ea602108c76519da13a45a77d2acafee842828f5ccfcd786bf7ea88afd01600000000000000000000000000000000081f1d3e37ebaacc11671bfe1670ed65ece2aee0e3b5d746a8d618b44bd4b7dea905eb8e958bc026a092b2bd5a7b87cb11009058bb8e23b0a4294b5cae63aff10265e729d3601d85dd7f1e8063ce260a00000000000000000000000000000000005af33731879a574f39dca99c5c1b9517eda13121221be77a0c1bac82fbf29b37889c15a9d32531a3f6bf9137ce82dc000000000000000000000000000000000c62939f00d70a07a85804cd97fd34b9764565bdba225cdd7549729ceb9735bf4d09a80ec3055c483e1e24b66c41e403000000000000000000000000000000000e415677988c9d4656e59f77c608926c83028f91bf4c0634120b5f774ba07180b98141ffdf727cf9d0fc7a4cb52f4393000000000000000000000000000000000c9c37eaca857151a0c4a49b079f2f061e6a8ebb77e11eb32b29227529562f8dc8e2646e25469491eec5a07b11943f203e5489447bb9a5b661bcff2d9a4153a5aad975abdec380301b6d4ce019bf2cdf00000000000000000000000000000000015113f8f9100cd18427ff48038e1070fd835fce6c0812b7bafa679ac733c80bef56492ec3ca08c1117bd0edf19cb26f000000000000000000000000000000000789cd90c0be1de5d0b359c030d4b9d8aef93951e26870e37c375b9e7879cf277971a05babd319a3a6ac53f00f3254e40000000000000000000000000000000019b1cb91c9a1b1ee49c3837339778806bf0c093f171c92c9931ad43e35fc61cc08dafaf55b7b9e0f49dac28a12bcf92d00000000000000000000000000000000066c7864631333226f191e313436453e59f48f91d42e68874fa4da45eeda1f6f7f6342204e64e124d5ecd861f02ef4f00444d520ee01d87407747a4ac37abb7bd4e4c4f1735ca7458cc2e4dcb1d6297c00000000000000000000000000000000129d887d694be0ef2f84c343a9aebd0a2aaf19a4e78586470351ffaf0b1309593363bd9c6e7fe39a6e59445d935414ef000000000000000000000000000000000596d7061c2399b6a9be7d4d495e58c0377b18db1e45cf3eb431d10cb8b15ae42548a86a26086d57b1a71cb5857d7917000000000000000000000000000000000cce7181fc87dfe1bb493043279a5d93cb2d980eed38dab2ace8c9fb335c2890447434d80df6e7c95729933ada7b9d8f000000000000000000000000000000000f0e1274ff70bc6d3f1d0d5b251ae528ed94aa3a1b9bbdb260892bfaa6213892071b8a6407abe26105b2f81df90569492035cab8f8120ea8e91389707a290db4ee69875d7429c6857e74e8bd40dc7360000000000000000000000000000000001192050735b114c19eb2bb9aa01f04d1fd9bed4df877113a14f7fbc9c31acc10db3ed0e0d15d8433e7408bc237c985b9000000000000000000000000000000000a8a66cda780790311b56836fe69479c7b94dbc6c82ed5886887dbb539a40390ebb2683c04078ed105e639a2ed8732a1000000000000000000000000000000001678ddff677b99011c73e0c9875b5b2ba063170f4d565d261b4c6d3263ccce0334b5bbb7ee08692568037fa96782e48b000000000000000000000000000000000ae15f79ad7f790f8ceaf7709f4b5da71642da0c1f7c442eeaeb165c7dacd8a4892fdfc8447a03a7c56e12513499e43c4bec711286827f0941ffbb451a8eba871239341a60e3aaef23487175c9d2e8260000000000000000000000000000000007fcb5ea5358074d06b64c5f46454e682dd9ac2127374c83f3ac5ad46bc5fd2fff7c5a80ffc669a1c159ee8c9a01bd37000000000000000000000000000000001010ada1bd493d6282ac2d3582480f50074a02fdf412c63e93c5857974626ff464150c20bdf23a87692bfe69a075eeb300000000000000000000000000000000086bb5664a8738f02af5517aec4c6db47653a6d76bd4b5e37ba4d8b27a7819e82e6a4c7ba4f8377e06a5878e7c0bffbc000000000000000000000000000000000be1463ab76e468e47e1711c158dc9bb10d1278f5cc676cff937f60ba457061bacdad7b8d3286f40219963b147cce4bd369d91a4d575d4c142b98a53115a792ec50a290608ad316465487762e83f3a86000000000000000000000000000000000c3329d1e1c76b0bcc7ca3766b2cc5ec8169690f45e0ea3e37b7173bfd6c884921c7523ff25391a85b47d5de395ca63b00000000000000000000000000000000081ff066c008d5a4c893a636d24e9752c6a06666dcbf80082167610e73a32d70aae3e58c88ffaa27f05260b86b11f72a000000000000000000000000000000001178e88c652d257888cda1c0b65ee2c0636184194fef9e6ae3791a85417c43a31fe75893773ff3e7b4d4cda9eafa8de40000000000000000000000000000000019657ec4604ab5e8812237a28e5ff320a0d728c60c541142ffd87fec2c703665638e5eebc33e308d5582cd043d08d788ee472561535a7710db521976cef0c92a4ed89861ecb397cbcfafa477756e8e120000000000000000000000000000000010789200f69d8acc70f108145804b62b521a30a04176c449f52bedff5975ad7b273aaf4a32f8461ced8e92b2229e2cef000000000000000000000000000000001178c36174cdb783b5b09d419ae4a154512bf9ce07368521d1576b2f1bf39f98be29bf533bad16ba9d96aae621612aa70000000000000000000000000000000002580f2115d1814667b6178b6bffca6a4d992eb66e9601c0d21e32a5f3b69e3f85e1205c877b2dc2696a0e872c5bbc6c0000000000000000000000000000000002c94d7ff016d57bd5f589971344c6499577bc2234e18e6c8dfd7d27a205442a4236ac54fe279d1bbca76467530140b42cfdcb8240f183abec526344e8ceca6a007c35b757928803f854225d3a6ca36100000000000000000000000000000000108b6fef7396ef71b46339d421726f83b08320599d66da18234011720d2b524d24075a255d2771f1ae904958c50a9046000000000000000000000000000000000723d5045b65c0887da1bb01d874714ac86d21441119a93a1d5758957215f399f5ef1cbc00558db01b295bf0cc988cab000000000000000000000000000000000994914a3df9d3094dab0c0c41a45315dce5968a99e6171fc609ac9e50bee5ccac771efaa04067467e95709bd924973f000000000000000000000000000000000ac746602f804f52e9a485c30412adf92eb9af3f6daa8f23b974339a0ffa6f5aa1b70a80a9f19cde2a69a4b7251ecf5d60659743dc1977a698371cc302b7579b6d7d13632a31b47df369365fb02aff790000000000000000000000000000000000a2ffeaff148dc5f70fcf53e7e8d7b6100cd6e7df5b3fa4aa33bced243f15b4f77f48d25f74366a693404b6ed7d3075000000000000000000000000000000000f3e1b34ac8fde4caedf3d8c3e24db02de3f91487db300f09c779e7e4e96ae55229288abd946abcc3a8adaf18a0c89e000000000000000000000000000000000166a68c5191dd7f9d44eade2ef1a9b522dc062bba9c55e2ff03aef400e5d2765a12816b4ba51e10bc21e06113c8ddc5100000000000000000000000000000000109c00de20f7e827375c1841348e684fdb248fad116e9643dbda8be2bd06b71db264e9f2c40dec2092e7d518540a6d82652a5d4fdf6d6703c857fc7b10a741b95fbce91fe823d827cc7203be3b3bce0a0000000000000000000000000000000014ddb61173359514226c150a3343576b04fb1b06fabd8fe2f921fb3b90baf5513447c107f6d2f96c8b03274bfe451dca0000000000000000000000000000000001d1064860f6c4d62a282147308e80ceb0c5dd62f39b3232a231b1b287e497df31cbc5a3905a7687eb2f24447e50a395000000000000000000000000000000000859611bb3962955f92bff861e03d07bab7fe1f69e90c6bc7928be8d1758c9194ff7a52b16472d04564607b742543eaf0000000000000000000000000000000008a3e8396901a205a071aad06ba9812207171f33775eb358de4232826a5f0ff50ec3e137b1344b583849e8a5b424b46676a30abda185e7d280804952fc0c074ad907fea2aa54da4c3190895270169b20,0000000000000000000000000000000008c9db83241e7f3ae6c2eac8fdcff5f2d35318e24c3b4130e9bb7048a3b84a52fa3f222a8190121d2a5b8835bf911bb200000000000000000000000000000000002db79cbcbabf41bd8c715e024f4687bc0d058d76b8dbe58ffdb80918212ab6e9b35256fde583c0fe903c34a4c41ba70000000000000000000000000000000019f37d05f5c9e65c6f004e1aef03ff0e1899f0739c9cc4e9038e18f9d45678388454d144495b2cd993eb3691bf3e96f5000000000000000000000000000000000d8e0d7715ed71291729bf480f5fee7ae04264015732677488472bedc0dbacf8b35eef7adcce196e3bba9cac0991be81,240480, +00000000000000000000000000000000064a134260b753af73df3764ab662e3b1bd624c8f3248e9bcf7676d8fb0825ab85ea33387d4641c81fb8ba3757e0870a000000000000000000000000000000000d67eff1936a395cd3f808ed7fc89f8b6a227c4849a6941d4bf762af6e41ae41c8114aeccc2565ba01fd902df530df1e000000000000000000000000000000000110ca2339832e7a9468844b94b3ced0c9216654bef1c8a5cf66385a99d5d452f978bbb7fe15fb477f56753488fc909b00000000000000000000000000000000173210b548d1b98b926539049996713f53108cd2911105235c1d5258360d5620d330951db67219ffaa304a67fd6219f39f4db766964c7855daea58d1205fe8da572aef06e0ca64912cec7c87bcb2f51f000000000000000000000000000000000f7c3795ac3d511f93a3d85e65261e4c09cd316787f74ced6e472a3993b7b5b0ce5a7c91d99559a8e0791f712cb4e1700000000000000000000000000000000018eacb2c5fa9221881c6311256a69c7616748deb3235c61cc11412860450151a25e3d6a220bb23e0b3e3325044fba68300000000000000000000000000000000121827286873ad31f58cb3889fd01cb7d0f91ff1c241295f6ef2dd0e8aa8638b63a7e6061efc2e7ca1d3579b4868f0460000000000000000000000000000000003a57315175d70880b2b53c67d61831ab066b08d7ac68637364ab1c1f3efad96d42a3cf5189c45012c1f73a1b97bdb4c1deebc727d98bdec47b5a1fc48916dca1e42345ff5474a5fd6cab0ae99e9f10800000000000000000000000000000000180648e5d0bf727101417f515cb9578bdde3e9f6c4176d516454ea7c32c1712610cc8bbed303bd1afd48f580ec11b77c000000000000000000000000000000000d6ffa9b85d69b67abb77f5c8bd776eae82d1cb055d2dcdea31ac66b1825014ec7f7a2aea320ef9f6897c9aac8c0706900000000000000000000000000000000073214fedbade28cc60ecfa4e1fe2fbc05f3d71528aca315312d50214f680956bb9e0fc12783843b00b3f4f0f52efe2700000000000000000000000000000000128f87e7da7b53f28944aeb26ef0f6c99d84038af51a1d242501ec84b5a6a8593ef1a0f6b523478d9fa12e36c2fdbe694b964d74259c216c1eccd7f2b52ffa5fcf151d47bd69bd2768e6466b32eb4fe50000000000000000000000000000000001443980d7450af1e19949fb328776cb7238a9b26240cddc565aa9d52c5592083b1533e8103dc07eac80e4bd830f209f000000000000000000000000000000000afdbea7f1cec534c03d3269d50017372f7ccbcba9f096fdb2754af4d6b4956decbab2b0afb69f97a03beeb20b4ccc31000000000000000000000000000000000a83dfa3197dc65097601457a97d0df7710e001e90657b150e289515609f13997b454167a7589ef218893309460139f300000000000000000000000000000000029c362244510c342358130f877de947acad5a379295f3149d5c713274316e06a169501f889e4b9cbf86f10b9521c1bb124ceb1dbc8004a4b1f8b422d394b0480bca7c0f38aafd8f06ba090a98a1d3c60000000000000000000000000000000010a83f13a185c70ca3f724dd84efcfa3ec463d7c05360056f8b5304864b20025b0a82c9d542ba08b645e2334f176472d000000000000000000000000000000000848a6a18bcf64d083e118190805d68f7ffea8b5a66e0807b9cd3733d31ffa5cc25dbfa6ada604646dcd8dfa622e08a30000000000000000000000000000000009962205c0ba43e5101fc3d5353f429a57a97bcb84baa0942a7e7facdfb0d032b9307aed8bd2ac9094a2e5b1460db7140000000000000000000000000000000019b1012661a10d31a4a73d0cb31f7eec0e7be729a42baf560c1e90a9124fe8d5fe31ecbb6d4954dba7d943a7af773eaa5a2bf15b2ed08b33056a0733c920741f86730dcda9c06aa0e3c135a844cef916000000000000000000000000000000000e7f02c1d2ceae60f314f51374b338c329f2eaa82553c3fc1643c7f1910ca24e277f3d658f552a47f780d4d9e0ac5e030000000000000000000000000000000014b6b56afc4afed5199191ec13dbeedd797f14ed493c25658a9658f031ac8d43de12e6a8c4b1671c9e5ef78da1a55e2600000000000000000000000000000000194d8a50618ff55ba3fa5602d41cbbeadc01a348ad1484c5e9aee5fb7241fcd9018f436e3c6c6dc64beaa241513a6c8300000000000000000000000000000000052681eac4bd59e160b67ebb27582a6d3ad5286d652787a0e160026607acfbfc5b9f38b9b171375079d052cb242b87fe8c3c919f31d72ab414f91938089430bbbeaa53ad7a73224fd3f204b80fa1ab87000000000000000000000000000000000d96ce83d917204e674ad9f5e5728651f5f23df25236b0fe769be48adf482ed8c36ad9c9abb6efa3719bd35324bd700800000000000000000000000000000000107f55ab0e5b60dbcc0632c345a9e93818014d7657b264031709275744e1c6722ec63aa209e655878a57704ca6cb3bc10000000000000000000000000000000018d97fba324431fa28b8845d94f62fc9eacc0253134b923908f06889d375405b51610ac21a75bdfb27e3533dd4debc22000000000000000000000000000000001667856804a5471238ffd64bf3bf266ce3a2351ebc68265674bc86ce6faa8dd50a3dfa00c647fb4265951b3a9607ab99f749063165c6db0eb038cb9f1a573de25bf377e1fee94f31df5987f7b2450aff000000000000000000000000000000000fde2fd0349e7a47a9b6858014d551aea569ef9802629bd9520e303ef0487c9d2d399682ac16ce6fa03adb6f4b478fa5000000000000000000000000000000001858ae58920dd0abd8ad94d2f9f946c53e050fe89c61f62fccad37e17f8723a4fbecb6b1be1e3cb853f045d0dca8e53e00000000000000000000000000000000093615a7f9d12e92c90706a47abe9620c4db41e95e42e478949745d6b73e021422e40b969e9e34263778c8a4d4907445000000000000000000000000000000001006ae7963b1e1c4d8c2c85175aca958758fb380019825b09ca3f728b5356254ae4fc670aa29812320b921b48a069df622d292cbcb836843acdd5a3fb404024174cd5c1cef632d1b9b6a73f2c5f705a3000000000000000000000000000000000ac407b75ea77789748e7607b5d6edb1d891875aeef2802715ddc393818fc8cbe82cde9f96377e3ac60107ddcda7e6610000000000000000000000000000000006e63e49356c38b816736d1d7c360ceaaba875c53c98ec68cb825962531855dc6410a125b914b0ad99f6f4327f5450890000000000000000000000000000000018ffb4ac95b8ffde112c8bdbf07a1c97b1d30a42dd4a97c82617698617ceb169e8702437ff6082a2ae387b462cd86256000000000000000000000000000000000497c4b3788c4d6c9b4cd8b3d3569ac4b4332b2f76c5f03f112e089bb79d33152b2469f7ad3eadb8b954775aab73f47de816dd1bfe025685f2eff0856f9c162d73a58fdeae0dfbeb5ce076e9f9ec1a700000000000000000000000000000000003e16f2f5a2fe15fa02b6217aed7dc688dd2670c09c02791cafeccfceb7d99ce826bccf213f6a7c6064687519f9283de00000000000000000000000000000000095e6638ac74815dc451b3ec85a6a8cc18643b541e8be99052ff6dad39c971f2e8bee976ab2ed5e1cdacf92816249ded000000000000000000000000000000000f2703c08b1d707fb6de215de80b53ffbf2ac48f3dd059d2a952b1031189248fad27beec5c8591ac93625a08e3420f0200000000000000000000000000000000024ae36412ba6f2fdeb0777b892f1ed7bab0527879d93f7b71b62f437f5c1ad1f04a5a7380ae5990a455f11870c7208304f117d41a011d36f55d0cb53d4f98de3b1a6cb55dc8a76b29d393bc21826ea0000000000000000000000000000000000f7ab1908c6d4b152835f950b604b55fdda7eb55c6b90c05e98626ba7cd014683bd3e219fd0d5983e9dcfaaa5d389e560000000000000000000000000000000010b285c2884dbdd540d6dfeca704e00839337f12d2267f6a3fc731fa0f724cde19e268782b4b9c2e11ec3aef9a72a6ed0000000000000000000000000000000014a40cc55570e8f45369bd9dc622e05f03989bce6a98a0d87f4fa7add67eee3e2ad9a297615dde05e64203e86153ec230000000000000000000000000000000007f2b6a092adc595e4857e821579801301396321d4a20bccb3296a031d74a62bd79ea4ea094d2e545943138d2fc930fb6b6f5ee0549b28a1bb317cb020ae0e031dbc381075772ff582718fa49db486d200000000000000000000000000000000108834a685455dc0be10aaf54607a06100673140b012ef23a16d3df204a81dd8505d62ca3e0278a2581abc59e0fbc421000000000000000000000000000000000bca7130de9896e8d6858022f24308af7ca66fb4c91f38b30f717c5491996ef4cdb01f4d38a730f9ba9ca5af5ad1de7700000000000000000000000000000000007d60ded107a06114afaf741dc8826f9e14bac6014eba26089c4e31a73b0f30c4b6e22533ac0db7e73621cecf753590000000000000000000000000000000000b538213a703f7a0bbcffb4aa8ce25ba2a538bf599d3c0251f5e8acddfd596c9912d4cf9a1bd8d3ec070713328ca992205edf9812adf95c9844b2da06f75d96e742c0620d1cb0d47dfd9b68d0bb76128000000000000000000000000000000000cdf0b9bc829cd8537918d665e5bf344d309678d01ee80c71a6d6efb45ee8a7beca35bb5ee046e0a3fac76e1771520ff00000000000000000000000000000000014e5be9dca2f8ee4da18e5ec9c4caa891dd78acc47f553af584308c72988435b85ad21b14abf8421bdb9e25164d568f000000000000000000000000000000000accdde22a1c479e47a17b8da6f1d2b7f780ac278c68a68090e5402977d897bd734f5af8164118d613f480c1f65e5d8e00000000000000000000000000000000029614458afdf6b572bea02a0af987d178c43650ca1c80a297b1d31e259aabd3e2a2c8e4b2c044466924dd6e5e3483e6f64a71e4e7652860038df67c99d97b1e5a063370e65217531253419bf2e6365b0000000000000000000000000000000004e45cc43d4d10ed878e18df156062c799a687b8e6beedad9fa6f66ad855cd053af6918e234ff9a43561da7e67f3dee10000000000000000000000000000000009c9ae47a76c199c93c38e7213c8d6c030cfca709714c703839b9ae9b65207e83486f9c8c16373e2b37756f3fd4355fd0000000000000000000000000000000001594ce9c2e229491b22317452938115747515ce62a0d49f4dd12667f5b3e7b541b3775c9b1363cc185a539b9f7596330000000000000000000000000000000016bf68e05e32168c69ad67331d7bc88a6d130fe8aed3e42eddfeb1d92add266eb69487b246a3ca961ea6ac0a35f8da78059bebd962501b8381b67c22055ba01667d916932713d7ca427cd80d8f76b41900000000000000000000000000000000080d165c57354f87008eb97610d4a596f180e48ed3190779591a0f7e07278f8d2fa6cd21d1b10e6347f11bd9731fdfed0000000000000000000000000000000008d5a1e66ec76743ca366be80fd1cbd5efc9112dbcfa84ce6c44e8df03140ca5f07d4bafc6c6ce5f2f190ede55fe8718000000000000000000000000000000000d0e1d2e5ef384a4fb314fdce54ab7895f895b3bc669acffd48e92c6320024d4f371f42071fceea550c8cf68615b00960000000000000000000000000000000010beae4ffbb68cf6e5d0683dc0629411ee14563f84788d50b1c8755b0b06092cc0f0ef7b55a39d51945b5178e374f8e047b3448b9b404e184f7ff20466aef3dbd4e08375673ca31fdb303c88243fface00000000000000000000000000000000161486d422462460923bd98834f0cc270982087697747fe40eb9153a7923d48eda191e4e7a75964f18f1df9365901a360000000000000000000000000000000017ab168a4ec81c8db4a74d529670fe6332b3870004f696f3a143cd1a62abd747d94afac9485e5dc19b0f4262dd379c990000000000000000000000000000000001e9cc85f03039ea53253f0fa2420012171fe39ed8696ddfbed57b80b73476171e59631388d75fe43aafde52aa14a64100000000000000000000000000000000109a5d5449002f4bdca44c0bd141175d5ca1cee449302f0314fcb5f282f022a7a3cef77f4e9fb515107e797726ff51d767d9d30b38b252a0661c12dc69127ac380f3f756144801633e99bc2ffa2f463c,000000000000000000000000000000000aaa5de171664fcb45439b17a024806ff7e07d02294e0592ca74752a5b66f3365d1b49d6893b3bac3b8b0d10d026e48d000000000000000000000000000000000418354ce1820ecf848321a07ce22117303e5a15169a9cbfd141fb4797de8871d84d577e86270a9cbfe31c088ceed0250000000000000000000000000000000016884caa03ea641e0660a790975d77c5bb03568f873800d0559b69e3e0afcc10ddf031bb5c25c46f136f0791bbd3cc8f0000000000000000000000000000000002bdf659df76cbaaec030448e8f4bbd6b424037a8dfd7c4b8ccaa2224b0852c168f49c6f45c04f23abc85b8df21953ce,240480, +000000000000000000000000000000000062bad6816308f1c8c6941980caf71929a4006083dd29827902ffc92ebd9b14f1ef662f3a0125b1e74dabd039f9106400000000000000000000000000000000118e4ae76e2c321a5b89eb19b58f58f44e80dcbc7bd6d619579da40e1156aab32fe81df8eeb1bd047f96d65aed8b3b6a000000000000000000000000000000000c8c93e1beeb4efe52a96e5d5612338721e3e487c13c18b02475f9ccd8fafc2c95101aed291951f2031bee5216dba26f0000000000000000000000000000000016fba44e9aa39a12ae27e3c36de1f14e3f37ffb0ceaf5fed2a0d9815eab02c5aae91b254812a8f3a2e3654cec01a341caaea75e63204e177d404898aa51555767f813c3f3ed283405ed1ee829b04c85c0000000000000000000000000000000013716488daf8586719c52fcec80d35f17d4c595b66c7f2138244f3c8cea69b819778bfb50e49ca1d092e57c51674fca00000000000000000000000000000000019cee25c4731bf48602ceab23b5fc4f764993443e3622107b4c33b29c23d1b5916380431b7ecd94a0ce99811fe6dadba000000000000000000000000000000000562b28b245b7c1ee531a320fa0f4e12d7c171c7e3932ffda6cfebb123fa7f5993e5ed5e7b7d295405e5031b339994bf00000000000000000000000000000000180c4a8158a26d34123c870bc694382352a8e4de712b650d3e45e6baa16d6950ec15d3a4e032c1d1ae8fea18faa6f3d8db48a90ddcd791e6a9debfabcb1c71c88e7ad98f9e739ee752b381b28d7656f20000000000000000000000000000000008472d40e0505d6b8b92500e8e9711112048611fcdcca2377481ae86a7f6da1571f179183301e2194a42dac3873a3ba5000000000000000000000000000000000e2c5b61c050a8a12298f76b5f15383e72b90b001fa26889b67a24bb374b63c1e00979b05450e44ed63e72042af6d46e000000000000000000000000000000000e8723eace9c7a72b3e6097afc9bcadde61462e2ee03fcd5ad1b1c0dcf39f437f80530c2a1c5e6ecdaac14e8715f02e30000000000000000000000000000000002e21e0f451d035a5257fb09e9ed17b27f0994e6d85ddaf8d33153628adb194c97db17656351c029be4d3125bd29dc22ad1795823d3834496b0a1c2c07431f9d76071db77834005fa1228393ad4ce3f40000000000000000000000000000000000dce49634595869d7858e95a301bcff8112eb73dca8a22042137456d6d4887998a541489ff09f8e006176e6beee4e300000000000000000000000000000000010835f7336dc49e62706da4ef21d8e3173629b16742c317c1b397d4f17ced40a56520ea63557d7ac7f251568f4eb3a220000000000000000000000000000000017446ebe659a4510a362ee3b406b636bea8f381503e51ac21031c7cc92acd23046d62c2f32cda01b680c0f107142ae7d0000000000000000000000000000000006ef82deabd8983ebe4255d8e06f4a1b3585c057b2a1ca3c3e1cf04b582b65792e9980e3a1735a8ad58b053b16ca03d036d56e38fe63e573b02203be04ef9e1a044e1754eb2db50c6f9804abc4a40f46000000000000000000000000000000000cd8e7422ee179a0499178c3848cc4fbc87fc25c8c882f036a03cd9d3f273f7f2bf71bd3c9cf5e30c42b1ee6e90b36fb0000000000000000000000000000000005005a471d77a35e922b6d6a45b13a90947c2b31d8e7a2e4b6388265b039ce23ed958495dbf904186bef60fd547b941c0000000000000000000000000000000006c337380065eb8a5f63cb20fc61a9eec4ccf0e23c4e0f231a5bc4d765271b9c5697bbde692b4828ae22ea12423ad932000000000000000000000000000000000f7a0080cbe72a6e6473f66ed729f58683a80815a1748e52f7b67a6bf2846b7df8e7dd8599f87fe63706e9823bfe00d21a6b36f4674ab19202037d59fd8e14369e5d3d71acc3c76985b813d81ca6e24a000000000000000000000000000000000c94834474ac91547546d7d179b2091e33c8812c1b582ff186e69b63011177283a74b549aa342a7f3882ee82ad8ecc03000000000000000000000000000000000d72c4308e9ae695acedb9413445bf6a40d59ca78bd4f74ddbc1bcd8508cfb521bfcca99c98dad8022d3d1ccdd98bca9000000000000000000000000000000001487d006830d00d84a567c5d031019035443fae4791a05253f91249b32a4b3e7b3ce7eae885b8caeaea411a90b3445e0000000000000000000000000000000000d94f17aa100503f605732a48e4f55c394a8df1421a3d7c78bc85f4cb7a53744eadcf76e1620fc54204b123d6071cd3bad85286877fa7e5a9a61dba9df5ce35083beca7c2f5ecad13d226fa32b9720e900000000000000000000000000000000101cfa8d9c7522277f2bb4bae6c09e8b93a876c749c91c61784feeb105be61c2479375abdaa81deafc2fe754ed6cd9da00000000000000000000000000000000089ebbdd489ff670a70218f5aaca78d4e7ade483c7f20de4a84d39217be8f560fbf7bbe36f3f8b8361ba16d17ce609d200000000000000000000000000000000094f094372b2315fabc219099200e7b9e2f3a2f6fef2ede6f83c82f44792da03aaad06b8cd06dc3f140746bee2a45706000000000000000000000000000000000cde6cf9a3a7018b2b1c0c26b5850820080c7e4b56e615d577a78565431c93de78348d2851d5ad9f120ddaa9ff3da31b8fa5387c5712832b52c9c72e10c6f69e9c1c5b278aa379140e75e404c4f50a2c00000000000000000000000000000000059bb8e5dc5f0cd31cf674ea78b80b67b8a8a753e51284a2ab37d3f29459250d904e70ed00481b73556970a7f5424e5900000000000000000000000000000000043c6a53c413bfa2f4bb14ef296afd97ce801a37fe63d11a842f8d66160794c1a651d70f4c836af2c73cb1bc58c706460000000000000000000000000000000003e7b67da1513656f7b08fc5a77682477349ac57e53687c82b6d98772b5f929a2b06b0c7e14481d522aa94fa3a6e1cde00000000000000000000000000000000109e07928216eaea36fbb20a38711e73fdc26e18a6967b54f308b10116a5c8af0c8411406ef6ab1050b61c23bb746b0a3023298162ebe7f4ae6aee45a8a6ba602c3942a8bd6b35636fc6b85596a582e000000000000000000000000000000000166f26d3d26cd48e498578900a8c830ce9b80f162c4b430749651b945d9f60ae6a26306ad7711a1f9d3428946074912d00000000000000000000000000000000165f1bc59c9c36d12754097ea83e9a63fb4ae5d1b93a1b9239a6f338cddf4a9b30415d58076852288c6a467ce9b6b9eb00000000000000000000000000000000198e73619cb93fa6a2bc700cd400519d11a7d3d6d945ffac9754a6faf37da8596b49b7a3a4f2cd899ec9c84f1e79b7ed000000000000000000000000000000000a4740820d60034d37bb85e3e622783852779d36d6e61f81a7eabcd094993dd7d81900277550bb4299d550d2805466aa8ff2430d2f82c6d5e7424836ecea15af0ba2d0bd6498e65c65b6cd281a7b8f28000000000000000000000000000000001714857b0ee07b94ea928ff57aae9fe003c0c85d8564456955d14fc8d4ae14a7c9bc303983af3e2999c6db2d000ea51d0000000000000000000000000000000016512cb60aa372cf5098ad514291d8168ed31bd755861dbd9ef020252c01379d343a9c058839cdec8d14f2fb9da0db80000000000000000000000000000000000af74d8ac711b6590e7041e80ca40dd4db659e42b950bdd68c56d676de654c1a47867bfe6483dfe1971eb7c1d1a70bd10000000000000000000000000000000019e56ca1ef3fffa9e131fc5bc93100577b062cf9b2acd234c79e5e54aa799a389f30002b4bd683edec5fb100f1800d66415eea22058493dbf6ac248fd2ad8b4734ebe33761f2177089a3feda396001c00000000000000000000000000000000019d1d1e1e2dd4ab86df81a8246c902a573d1fd1598050663342e411a1d1b3c8849473c689afcc8e0ce5e51a9dc9c3b6200000000000000000000000000000000190d7c923bdd6336fe3e0509563b2eb6067354d8807f66e6052e97d5997464b9f07f29f3022f78779a5c4ac155a703ce00000000000000000000000000000000128591bb699c18a7b9e6e4e894654853f6a68233dfe8c744b42e057711b8d0efb3a98bab6aaa40ae7675d9200a8427d600000000000000000000000000000000045e0560e0936b16d1e055d3d3f4e0fb42d129546abddebeb78e871d1442f4796d939929d354b0326b95e50fd5208fa9ff79e3ef5d32a751b713180be37d44ae55c59c5a8121c132c5098ff972d8a97400000000000000000000000000000000092373dfd7d4375d6bcffa415e5b36a31499e881a80be32400105a6d56b34d64f4fed09f12640a43289a710f034b71e6000000000000000000000000000000000fa75d6510b3b58a32635a7a6cb4b9255aa7af46905cafc893f29b7866e12565765bcde498dbe87df3d1dd53ab5628320000000000000000000000000000000010dfd3456cb6a8bc853b390380a13f045ab43abd289fd05e7f98839477dea1fb1fbe38ca4f5bdd6691446ac0219e453000000000000000000000000000000000112567397f3fda84db6042817a99aeccd0c46a11fd3ba44e2600deafaaab7014dba98cdcadf81b97272fb7f275ee8a4e039bc7274a3ab172285d853d368da0950203a48ef61b3c7564644762279c1ff30000000000000000000000000000000007b397f093e69874d2bd3592489d93c80d0191b157e71d08a6ebe73063f77e7c5e084a24b34da2aa6354b1815a694185000000000000000000000000000000000fcede3a39dd5f905d072dafdb6f56d85726f6f362f91f079fcd47a8c1d3bdcf199d64edf17e3db1dfc96a3e59f69bfe0000000000000000000000000000000010cfa13c84e750d8af8bbb88bd6d16adf3bc7b532447c2e6accb359a5576be08c1b25f336047fb8e01a4d7f9080d0392000000000000000000000000000000000ca0e88b5c2035bcd3a65e8bf1aa219cf428b6f80617040ae02a0ed41559804844df373ac61a85899bec83e5a6243ed42c47d0b1fd24c1c66a3cb0deb7d51ea19f0fc492f637ed5d4d03e102cbdd055500000000000000000000000000000000021f3b793680e0e3127fa53034e9fcf286f5279cd167ac1e8ba051c440aa265ec6d28fcc2f6d3bad126180efd4503fe900000000000000000000000000000000182b429f27996ee070ed27e7015bd70191b814bd02ca6558a9be81d6898161aa525197c1672ae75da92729f2fae9fa3c000000000000000000000000000000000a20b3922e07da4ef6696de85754eabf1f58f7f5d37accb6cde4f62066e789bc64bc8ad6ac827b8c955acc858b03d053000000000000000000000000000000000814faebd3b60fa1a8fb86b3cb57d36b9c85d4b28e97a2251e6bc1fed1ccb18f17664321f38f3723cf8b09a2161c6aeaab4aca860ae4bc20d33808533c9a70108b153bc4b2256003ad4bbc11dc92898500000000000000000000000000000000159f9d329f929a65e41c7a0d4c05e11db61ca7d6d82f8b92a780bac66568694656f4c845a730861fde9a313fa49bdf0e000000000000000000000000000000000d556bdc8dc959b00f74209dff27023c5521d387a40bf20ae2a98f3f55318eddd347bf1e9d856f43a4b5fcd26c3567ad0000000000000000000000000000000009b4b0cedf477ef1e0f99627bdd7a7afeb9e29afbac553a516fab479913b23a9be5e0b38994215a9e23849bb664201ee0000000000000000000000000000000010899f4dc55ac5d1f56a7b8d55ce7f6a5e0a8647bf1ef6e9050f00c5fcac9f679f138018b9aa611be73d3bdc0af2056e297500a2747f9a68b2d8d9ca5b0390369d919897c53d422cb76c5a283c38669e000000000000000000000000000000000226c8a6b27437972ce29c2ed7e5cca4b6691e3a5dbbe713b5d309ff2f4cbb95e8f1571314444d65ff5fbc3281f9354f000000000000000000000000000000000282a49d0c560d873676967700c1062013a2d4beee96a09af7e14436fda4e3d2a32ab8ee4e591decec39a811ddff130400000000000000000000000000000000167bfe499f1f4609e67134e12ad91aadc37bdabd0055ecf7f96162c39a02a86e62a7b3d39f514f63edd82d04beb1958a00000000000000000000000000000000191673ea5470e4704e361f5ead1c56371d6aee3035d92d9e1b96fd119c4f877cde6451411e441fb45aa9fcb90fe4c66ba87ca4cf226c212c80f3db5e4e781ad7391fb73b1124d01cf893169d1c50ca99,000000000000000000000000000000001488532d83fddf0bfd69b32f965790b3fe4cd9f64e8d17e78189c346518c91e69db2f0b742cdd5804b3db3777dd931230000000000000000000000000000000016205c470c6371d73b012a14d519bf214ff10de458605097da1b798977bd938727c5be19a10f4f492f301d2ab6c38ed000000000000000000000000000000000142cc08f61d3c9bd4c7bfd0b7a0b8693af6120898fcaff49a7fb5abdaf1d15bf70eb033d6ff09a75995547e6856c595f00000000000000000000000000000000164b2807e19135ca3b66bac9aceb371165c930ae063f3cb5a06efb8985a1e0c39023d8f01df517713796083e8c2cceb7,240480, +00000000000000000000000000000000023bec14deefcc20a90e439bc16912e90191dc7142234b1870e4e8d70c56f695d5cd30a68930ff9b007bdcae8ca90d870000000000000000000000000000000000053a6e226f3bd82150e08ec3690f36616d5ab745b36a9990baac7ad3429a41bc60c7f7000ceda4cc9298b10043639e000000000000000000000000000000000b81b331589ac332093928faa60d6819d3b5559d32d37d2cc13c78aafa1cc34e32d317695c1c4b4979baa1865ced90150000000000000000000000000000000010dbac5e52f9a046ab88aa36b3c5f6952720e174bf8f1732e886e66e5803aab63642185aa24ea08c991edaf8375bcadd9abfe7e05e8a210604355a77f64386a01323407d9f25397769cc6dd141bc6643000000000000000000000000000000001875ef3f90df03d49ce6cede2c791b4d8503b75acff2dcb1c7c88026394dfe11481da72de4ff58ee9a98e75577b6398c000000000000000000000000000000000c8ee603d1404e64ea3ff08c70b3dbffd318736ae95f9a96ca07ddaa449818e6c5a17b2970f572f53c90be893e5c323b000000000000000000000000000000000f31af63c68481f527092b261d29d5c2daa95873b68899c28ac7753d95a64f455ebabedfe6e72246e494cc5fa2a9bd040000000000000000000000000000000009fd06bc51d4dc51de9fad6d1eb763809cdb5ccdba8e0427859d878904bdf295983b318f311856728078e7cbbecb0c5b64be08e7c2fd15ac0116ca941b85615c6deb38fe85e2c0fd22997e394b8a67690000000000000000000000000000000003ce75ecf6b605ce73f4e215b1aad4799f91e624daf0deae3a273968490bdbdbd0250686ee91a1c24c2e2f2b6024fa49000000000000000000000000000000000e4d9b65d71b7593310fb5145677d170663c0ca29636f7b7c50ec1988bd2d2f1c41d542d4cd8fa23fad94bd6a84aef5b000000000000000000000000000000000fa4accea53a6362651f6c6ad2a68d20b5f549f8eb961718e0c14cd05249a121e442a6a588eafc83d6a43d8baa66882400000000000000000000000000000000121e325406767852620ddc45677495fe3e0851fd3c70922896a3e92033347d2fe8d07f4db8f26b8127ec39d619d596030c391dff1c0c303c77b2a1fff24f50250dc793338f7d7f8f1d54bf7d87ab37da0000000000000000000000000000000003a0ac3ac37932b71672b9c48bdbd368d64c11f57ccb952f633bcd10ec19134c65fb2cbad655d773a90cbec2d9232b3b0000000000000000000000000000000007553c470bd8f38a48490dadea29df81ad901ecaaf1eab35b1f497bb58acce77b883e03e78702930dda72e2277139a2b00000000000000000000000000000000044973913824b3326b72e62ccbabd8c9f1b5dc81b423d0dca37b6f33972d993a681c326730717036bc6f0286da9177430000000000000000000000000000000017b0407d2864cfb39dbb0a5fa8deb4ed4a690a4042153e829f51c56bd0f2953a440d8305a318e6d6f67970d473753021a2d728e013e5fc3e1ca24c105a0c268cbb4f152a97e318f3aae33186ea6bc93a000000000000000000000000000000000b7478dda7053590ed013b7c23431a21626e748c3843e2332bde0bd3890ecea95b6104bac420a8be5f3dd9b075203616000000000000000000000000000000000e6dea641181cf796f62b196652f952ee2a26ba998cce1cfe9d65ae49198d10badffa561e2bd818eb2a7f350c122fa820000000000000000000000000000000003c79917ad5a9c7f046b34e5491ed015695aecb00760f3009dde4cfbf88ad1c03e44117fcb6cdbd5ecaa8df8760a3da100000000000000000000000000000000034e22ddbdeb9dea46c71ca2144ffcc8356c1a525c5ada69a6d5e5c1786aaaf0cf532e31a2f78371e04a72e8222ed4c7e8da0c8da19dc441f53c54551579fec5d820ce2e3599824b24b7c5bf1847c5890000000000000000000000000000000017964112272360a38d3bddf89da922ab50be076bf71a094fc8afde109d3817cc2db633e6408f5716b76d70e30ae00c0d0000000000000000000000000000000009bed28bbf43846ab97b92aab9ce094b077bbc59db648dbb469f21842058ef20318a1a8c18045b3de555bd8c76132ff0000000000000000000000000000000001297110789c7aecb0fec577f6f4a4de14608d9aa26a8de68289adea7f6b53b766b840d315152ea346f8c10b2d2729e730000000000000000000000000000000002b551c6a7846b96c6895e55ec435397af70eb435dc1c562ac71a44c36936c2c6d3e6a1e3545513516513391aedaf9ca76e90965adfc2fe52e4341895e6b6154fd7a097e052b59e4935c8267a6f0e63800000000000000000000000000000000003d463ee4d177d78849fdecba52b7e83ca90d54177ed39e82b4e80c17994a6a2bfd9c46edc0ddb256f8955428f30eca0000000000000000000000000000000011dd976dfeb8ecb7d7f5cd10c235131709fb16d8a827e83d7084266c2504cd1f5276ae3333bc7fbb4ebab48c0d97a9930000000000000000000000000000000005fd19477fffc246f5991603b48085d95256b273631bcfc16f19c6980a3ba01ac098061faa149b475bfce37d586464b800000000000000000000000000000000103ac3dd682aee109dd7fbf60b50c28cf7e37642f05b424773a06f6cfaf7e9fb01d5074ade97ef6cb0ace2e1fe07d54c7f3f352c7b7a9e2eb6c87edfc99e2df3148966760168f6abb13ee482f223a01d0000000000000000000000000000000003208ce7f51a96dee053cbaa66fbdb921c2c3b42ead78b39b4f1df7ab49f05cb88d0f4ac18de5839749416eba5535d4b0000000000000000000000000000000001ff7f9db52aaa0fddc8e96a67b99353b92d7032f59d200bf69da3b446d08435d2ddaeb93584d3b68a1934566187922b0000000000000000000000000000000005f05ccfa5704652cecfb42979c538823fb9d11a00222a963d00f1a4b9a040a0222dcf45baad40c6574d85e5617dbbea0000000000000000000000000000000018637b8c3ef111f6ad4538464c250d780e7f081802bdf720f4c925154f4667c5d50cdbc4dbb7d0b2747b97d2ba2280bfd35c4286f19a9fe8117e37132ce4ce76e28afee25ecca2f66de3cd5e1c83235f000000000000000000000000000000000eb400becfa5521b824a4288885fe46642c31576238e94f95e9b4bcbf62845ee9d9ee122f87d36fbe668f0e605fa2ce00000000000000000000000000000000003c8cbdeea0d09590e1719ddffa0a116723f0fe85585583f3f271ead66fbc2107873181915cc41eed3ec6e2c5669e9d3000000000000000000000000000000000e61c0768561517405952c6462f1c5df95be272251d8a7060624b62f9be310cef64436eb2c4c04e8352d7b75fea1756200000000000000000000000000000000036cd74a8efa8a1fce7587f07d5c2a6c4b7ef161b0faae037c9bbe63bd0c92b83e514c8c1bae4a5d9866c0889b1b914f3c2b40b7968a39fe8e4f24acc25b6c727887c3c44cc89cf62eb14a78ae47e8680000000000000000000000000000000013019d0fc8b93da2c79e473d713d94af33eaffda65a7a49d0cbae9f5259b8323e6f29b83da9608ba7d6ec004fb0710eb000000000000000000000000000000001505d30bf8f7c51994d896d91e8e2259782e2b49bda834015477f18c29e64da4d31f8b96edd080267b77a9539afca06a000000000000000000000000000000000eba929531615d9c0f59c4b33c1fc34b81e9c77cd8c6887099d850b3e39326d7caee1feeb101222f22bea1e9853d06ea0000000000000000000000000000000019d88f62cae047ddf2cefe497495f890d9ab8499e56f72488af65095e992427bf821f63555a67b0afb00d6fb441080a010325465403dbd4898beb740884cc325923ec3e1d7483540377d8bbd02c11382000000000000000000000000000000000b7c8f3d0c56b3b7d96c0a24fea3394551a186f87acbbbbce41d1313b23762945bae2e911725da4211614b456b508c0500000000000000000000000000000000125316f64bdd0c5bcd26a0e5bcfc3139045b3a44c8a8dd1cebbfaeb83b963c5a5abd4a5961465cff261c0e49189278d800000000000000000000000000000000095a327f488b901fe7dcc9f9ce6f4f25876bb09b053b64e9f4de9506a0fb95fc0cd443473c2cc5436750581d39b8e51f0000000000000000000000000000000015d406b31c791ae2d25ce462304c0bcf341686d7967c9dbb6734bc28b02123b1730d0a673fa8071dd90950d9411a2b3909545b90dbe35b0d5764bc72d45717e0c3aca6aa77c73178fa8a3ee9fec9cdb3000000000000000000000000000000000c7029af9422246d0a30784431d6bf9eca09481589438fe9a6d2fe1d5e526ec3d176a3d550204aadb85353d99bfe3ce50000000000000000000000000000000014a0dcb26c40693ad19a1edccda05055a27ca24544e933d01dfb964571071f94c94233f81e1ead0925d24e6d3df2c21500000000000000000000000000000000147a55ebd83c746128ba9c7ac57be125ca5c95f80f891e2c5893caa779484bdc1f9c3b3ccc4223b2343ba939251f7fdc00000000000000000000000000000000125622a040d8b157432ad81b8a83a9b1f0920b92680bbb65050b4862b89017b3bfaf81a3402ccb383265ba7200ce677feef0f8014102664a300ea9a30fdc7afeae3cc338fd45cd421a1bfea98e304c810000000000000000000000000000000013b394fd7a0f3d94e5fe4cf5cce3627d425ec848912395565b3e61ffe89e56be799c4779d3b9a0222ecc6538ca3346e40000000000000000000000000000000014ac1a87b333caed0f557fa5692d1138a8c1e92d1f9acdc9f357e2a46f27513dea42f367b046d389dc831610be4fbcf40000000000000000000000000000000011fa243a0aa8b0c01c7636387d60021afe6efc223b7deb69d030651c369643188b9dd5e08d6d031d71dd11eca1e825ac0000000000000000000000000000000015bf8fd7fe438407db7f1b0b586b2c285777c5b6dbef9e45b46cc0a50dc831f32a70e7d4316d4869bc769ff6de58ac30c8f1e08cdd72ed200253211e3b9947cb2a5fa24079b6920b4a4d3f1fd78146e80000000000000000000000000000000005ea57c269c9d43d3f17a83df04c95ea7e7bd85aad1dc2dd285ccdbd52bfe707a1d2476417e848ab119e62fea30520af000000000000000000000000000000000b99768ffbe95e315b244bf996cf34f8ac356664adda5aa7f4ff8d513b2eb5934b8ffe0fd9af94bc9b934e0a8bbd51ba0000000000000000000000000000000003b02c259df189370dd2700c5cccfc8b212a4b332a083adf9771503f5bd0c9ef040590320fe4a86c555a4ea87531268100000000000000000000000000000000003ebb1e610bd055d037a410cce3ae06aa654950aee0210ed0ee79f7a332be7342e308347d7b17a146a8b4c623029e08a7e25b1a60b6c6080ccf1bfdc37aabbc2bf92079d9356844f7f12867b3e2b2800000000000000000000000000000000015c4da691b5e6242af870e06b29bcde467b4644f01080eca60a28c7f941590192be30e6a4270a36dc8959b80235600aa00000000000000000000000000000000080f3d3d5c35ee24179f51ad854a37ac4ff867a2736a0e3e8f3312ac98c7016beea6ffe2bad1dd4842d6ec77995ff97600000000000000000000000000000000130c29dc633aaefc831b0bccb13fde1212fdce8cdd17beaaf1d06e74ef5b1b69bcc219c8d63f054690af1b6dc7c0d647000000000000000000000000000000000767290aaa1ed4c1dfa5603d976df0715b417599445ca577ded7d99e685118bbec71443fe1d9a65e0f23436353df152cdcb456eaad2b7c71ca32277206c1a1dbfa7e0e84950cbf14aadd455fb58e398a00000000000000000000000000000000133e997857f47f8d6278b8ad86f4692ba0dec9da336f2726704db593af368dda7aefc0b218ce1674f415e0d9e2dee5c60000000000000000000000000000000018db87da1272bd386f7d8b5245dc2de30e82739723b680dedd36f4ac4cf5042bcbada1e1bb307ba444431d73a4248f9c0000000000000000000000000000000006580be3e67c7a615408aaf9c95c0956678af0e2b1f536f1e69588193387f8a05b03d5e1060ca60c4fec9eaf3e72d39900000000000000000000000000000000050bd9879ef9eea147678f552cedacaee84562e6561b3b7338fa8f9d514099291c3f2a3723fdb22c88f1c9243d411ccba6e7b19245341fdfc5927cdae57f59de5f3fc8c37f8653e5aaca87db682034ce,000000000000000000000000000000000d8f69d90c871c08ae09e7b3e62e36514fd056c41fb596fec2fc9ce8509ab4f6675d7e85aa6b4b3197f5ab781f6f2e490000000000000000000000000000000011c4bd3cd156c34065e408efcaa5e13ad23d114458b71c2a6345f4aaf82af76cd4362db7ba9ee7e1e92ce72e242f570a000000000000000000000000000000000712dbbf20e9b24d20511d01717a3783608386408a258c2261fcdad5fbcab36c6bd21473c3d93ef8518975256c65a945000000000000000000000000000000000d13747be82153aea8076fd7813ecd7f60a214c31e88e25b14dee5cdb9336599e40b136d9ae6deb85606d35406b2675d,240480, +0000000000000000000000000000000017da08f2faa32570d95b9efd2d2fe358faec1ffe304750dca1dc3a273be3427c70904d58864f76afa19b0fe33ab1535f0000000000000000000000000000000017de677b713202f23baecef2b0618da140af624e56b876f2d7a20cd437c3868ea00ff6cd9c8908c1ef323ad294edd9670000000000000000000000000000000011d50aad957c54868aed6d848b2e67094b129282cc2df56c41d6ffe976d02ee83a592c33370d3715588a074db503b3e8000000000000000000000000000000000b8aeb019d120959b21627c1dcfdfb67ade22a948fe433172994d4a34084ac9e1c11333a9c663c87acf50962e21c728e92898d9cbad829a5346c0925c15b585de18869adfe796e46cbd56828540571b70000000000000000000000000000000001312ebeee36fff8152324a3ed24c37eee50b3099619a33c7a6316470ae722548b4b9e0f0640453caf53f374dba504830000000000000000000000000000000005ea81d2e5d9edeb3ed6c200b75beb731c31ad666e6e37db72ffd0265378bffc2724047c7c0c6e3f1598345fd390e9270000000000000000000000000000000017617a836beb12e637c5bbadd4fbf1ca2f5cc3280814ff5cbb5890b31cf2d2faee9e3ea8134af97ad4feace50aa194140000000000000000000000000000000002606deb5d57dce5b3d2e5f7ccec3ad036992beae238673641ad6042479ec3cf83bcc0fd03b7dacb9b4bb6c181ea9cc8c193fe87634fb0bdaa1700466881b557c470a62464e8521be311a95dff65eca6000000000000000000000000000000001203ef36896bfad2a2841689a964328fe4ce3d83798671630d0c8876e67ceda03d99555aac46d984f1d3bc38ffc134c50000000000000000000000000000000013e7461c256c8ff9144b17f8cc2e270aa94b64be62588280baca2ae6b6efc4d32b3800eb84da62561e0e96d5f0387a3f0000000000000000000000000000000009454b6a810647350cf0b364eb1c2b719670af45bdba9d7d1a534e23d4e810c3ef4d9318532e46fd104a83bb10159a30000000000000000000000000000000001034546c4288f642daeccf5b56beed2ca2d946bb4391d056df9c6fd6771048903fa330ec16d59d05540cd715333c4bc73dd9c99a5aea019436e3c91030d03ebefbf6ea6ac69222f1870fadae32f55ae6000000000000000000000000000000000d7782404dc6721f52648fc6969db33a9aa209f8baf5faa9678437c76c9e1635fa6d22d94aedefc90112223bb81ce33f0000000000000000000000000000000001e442e548d3045d1589817d0b57dfcd66fc64ff978186f784bd576faf57607170d49364a72189328c9837c9a2d8b0a0000000000000000000000000000000000da2b207bb7720aeca2e6ea02b65076770b960d4b7a96ed941a7f409757b952031a472384298acc3948bdc485088501c00000000000000000000000000000000048f85bc05ed78c692138f27c3541ced11b6b0ec158b43d133c3450a905416682fbb8c83dea06a06d294c48289ddb829e74ab390c3f73c62eb1435226e9b4f9b921ea1918a61a614b9bdbe9eebd1cd790000000000000000000000000000000017134f787c920bc15cf2228a186dfa1d10194087f28b6dd8f03e1c86226928f0eb1c27020a5cc74d94b50c4b4e36b8020000000000000000000000000000000012fa1fdcbaa81c4cc1e37447cae51beb29e55bb19b91e2b575afa3754589ee0151cd9e83573edaaefd341f381d34f4f8000000000000000000000000000000000ecafd00cc87a773a13909512466ed11288c842716e1ca5c37a4d9a4cd7585136c86f32140fdf02e2997a6e19e3d76a200000000000000000000000000000000104cf007ea863dbd473d7dbab6f55e74062b18986e9bc09bcfdc9c23e4bff8683f73aa998a5cce59ded10499d18a0ecc4dee3e2bfae3820f611c30df232c1d9c6bf58d40b3530858c79f840720d78d72000000000000000000000000000000000ffffc98e55f4ba9a642c40678d625690464bea39d085dbc9c99b4c36ea8bff5154eae3c315e1dec29aa669840accf290000000000000000000000000000000000a3df9595167048c52b8170596d4127968194aef7fbaea4594a27c6af05c54bb772928a7749d74311038d1c115e91b2000000000000000000000000000000000b317a3abd808e94a7197e0d3b2515a147774f78d0cd7d36e1156da28a26e33bfa76d75c6e3ae346f9ace050c9911cc6000000000000000000000000000000000fb5fbcc2f74fc30ae7e32143f219db7dfe5db6ecb09cedad8f087b6df56bf9693c8b7d78aace064e7c31785f6869541795fc8e20dd30622876a94afce1c1a76e3b689d6848903c21103cfce6a8a95680000000000000000000000000000000011e4b907a72f34af899a6c4de211af5fbe0265e5bf24d406798de53ecea273d5df4f4953d13fd7c9dc3bb0f0c143e3e4000000000000000000000000000000001623de5e87b6e1ee920e1b7d979fb9c431c12abb47b93876f9ddfaf28a7b673c18be634f96b813f7e0574c55b628a8790000000000000000000000000000000018ba994b02dad759ee79301b42ea20d7545844c0ea4bff2f95dc9420194cc4196fff12cc09bc0cef03cb7ba868c273700000000000000000000000000000000004b3527c8d148bd9e6006bd298ff8d7fe320748dd3f6d23449e874fc0c2f58d933c1e038a74f60fb6032cce41a3dbf5725b49f325e76733eb3c1a2cee5467157b2ee80987abae43d2c4b93e5157f083800000000000000000000000000000000129641af11fa92056236ef135843b2189d46d870381261d5781a5fd6f2c5cc1861ebb2e801f19f3adf2216609a9e196f0000000000000000000000000000000007b4007c55e47f6bf3aa420ad75fd191ffe0fe824fd30c3f1961a8168922476fdb3869822704999b044feead470e3b8f00000000000000000000000000000000174209113e2d8c363b04f49487176dc6d9eb4ecc0b22daa7ecaa5548d038b3b7c23ebda4f1b6845425cee13493385302000000000000000000000000000000000a58c80a02b7f93db01d2f8e0005839625e6c4f121f3d69115f435526a7f7cb53177caab4db86273bc2d2f0474235f31df49b30dd6aff459f64906eb1a9c9b2067d4f1b75057874b2fee17923bcb906e000000000000000000000000000000001738a03b46a8ca3f3d1f4f4447497c59f114005400f06813b24ff462ebc6f27c1c3c788b5f83f65958cadb34fddd08f40000000000000000000000000000000004dcfff2bc9ca0282016f38df484655cce7b872b1ff047351ae6b903e05f457d7fefae93104f9dfb549980394dfad2760000000000000000000000000000000017cd89434225dba07be137a73892faf0258b3fb19e6c8cec412fcda912c0613f2a925ad50ae485187020a371ff2dbc59000000000000000000000000000000000f1f9f87d3401e7b3b59331a89d9535adc973f869b81bfd8892a37117d8597ebab2800c966e623469792f4ae2a8eb232959e0a33b1fa12e0ba960761b09921b81746b8df23e808a8de09e7f5cbe2bf41000000000000000000000000000000000bdcb1d2a782541ff7884dde4167ba060fbd4b117944ae69aa2ff685b9bd7d475f45adce0c9f92695b4f4ecdd48cb9b50000000000000000000000000000000012a55432678043888bb9e7e47efb17700b3e702e389d0f58dd454224a02da3f190b2fef4c9d3e2074c7bef813fb56fb0000000000000000000000000000000000efa51ba64f1e7a1a269dc083179a222afac916778a967098582f55a41394bff3747f8d024261959f6d399f44a40d0fe000000000000000000000000000000000845dd0974c5789a85c3cb09ea441f2c433f0606928ee1b177eb851530d6e6b620b4fdcaffb8f75623435dff99b3ad9526ca68383528f6a871c237ae5214b49c18c4f3e2f3ef5dfba39e69eb181143d700000000000000000000000000000000180beba92bdb95c7803fca0407e29929ee64e03d61cad96ea0e6c469c5a888cc5ca5eb20983b3418a8da6596a5f1b2ba000000000000000000000000000000001322f7356eb3069fe20063f4be22c44426162dc8fc117e4e382bc4e33bdf3d971ef662fffc1d58ce187c33a43a4c853e000000000000000000000000000000001601a0aadaba846f11ba5c9f48e13bda1007ffdc1b8bbc9e85e83e569e9ee17a1e9e780a50ce617e6c780b8155675f2100000000000000000000000000000000105b2c213aa43ead42d9cfdf1d6c0559c25b4b86af43d4493bd75b76986d0d4f1d9b3bf9e3922b5c08a37a1629cab7d8f1f95a9d1d4e8e7d0f17a954177253709d988c3a77c77d35b8bf70294bb358c20000000000000000000000000000000017bc70346765b7160a0a5e556805c7944304acbecde06cadba474c51f05f22445c3d943674cc8215f973cdf11b9ea2e9000000000000000000000000000000000bfdbe202619a1d95359941c249b25462d3ecf09fabb878943a8a37cb9eb94abd7e6399f8d82f90ffcf904f4466cc5b1000000000000000000000000000000000f048db8530a288fef10a5ef9bb3cdd9f3d3b0ef4824609efad96bdf52d7c3b10ef628fa04f8b6513485e55f653f4b990000000000000000000000000000000004ec35f59287eadb1738bb50b0e2ad9d280bedfdb0a201e72594bfc4322ade0b7ffd6b532ebc7796cfc71f88a194bef4b481f986998d863c98e55a7661136a8f19d7d4c57f6036cd642ae16c82cdcfb30000000000000000000000000000000014424c77af7ace8ebf66f556cf219919712d96d24438466ad620221ce1ae9b2cd75b9c526e25df7fbf3c9250583757f500000000000000000000000000000000198aa00723781714152b3494b76ea3ee043b363b3fa81806cdf7e440b4cea907f226a3c038fb95c932710dc9aad4c9dd000000000000000000000000000000001360e4c775f6fa5e987231dce25ec67f61429ca9fd8160c3074383c30a8c0d7ff068b1d1215b2c0cc87129d9c9aecbc9000000000000000000000000000000001280ee6160800c4b0f82d5c2775238b4b223d8a0ac9a8f8013f138d554ba31c9fedb30e0eb5c330da17f5785b2717422ad872848d72367467094675a819f9aa6107183aa0c8685d5d84c27b3aaab33c1000000000000000000000000000000000f1f84251204d9f9328f79a45d15b311984df0715579633a82b5a9f680f6645cbe748b0fa64b9ce1e696e20a5645d6d300000000000000000000000000000000156901506e502a09917f76d825614824dfbc34d019ed53c2ec5395b51512da512b27541bc53331444eac2f618ffd5357000000000000000000000000000000000ea8736a97a33112bea9d07b729e973e3a942422f1d2b24c30e96637b535ccfc10cb5930bb59ed90bef604453df8772100000000000000000000000000000000187378477f60e3eaa225e89d8532bd95babd4a5c51729cca800d364b61575704992639dc5035138664e8e074ed0820033c2c60541fe17fa8e71d58184a055fa8b1dd0bfd16ac2baa912b4472c6056122000000000000000000000000000000000e5281c1c9210269a7f5ccd02cd5a7d3648b56d9ca6a4ee50beadf151c2601e0291fe7f1b89b694500e6c636d4e445c4000000000000000000000000000000000d5d5399f49697e46013558dfff544383b25f3b60681ba5fa2c5e6edfd3924267d0992abe65cbd5109ba8a1c6eadc7e30000000000000000000000000000000012a2104aa92871dd8e41ae1ae6dc18ceb7d0f361a5a4fc67936454b8866b8aec1602dd596459cccf6d9e1319ec3299d4000000000000000000000000000000000268795f6f9892f5b476c3a534673538647300203a51a8ff60b530094608b5fdf16297f02ab7ba41d6fe556885f064a4ff07c19ad4f10ab47e73b6698f9febf3f28087614759e082e6e717588c1caff7000000000000000000000000000000000a5585961328c52e0fefff16e66e3367e34339dac1a20cbc5e89b78804b8bc265e6e3fec1da6a62cd8a46be2f08a6d960000000000000000000000000000000016fbbd698784beec5a636332c0b20fdcb68fd3015cc6d18b541346a5e6af76613e6fcb14c888a2b8133c0f4132fc079300000000000000000000000000000000041805e0adf2a32153b89d1131226cf0ebd77cde3116a168e792ae8b88ba2edcb1fe7275658a384251b805d282ee039c00000000000000000000000000000000024213e4a8504cbae4875617b9b78473e7842ff72415ceacfaaf2e8b415f9f7e411989bada8101be72f9295dfbddfa3f240c881fdbfc414d3e85ead1cdf166ed6929d0b2ccbc35f0811473757b6b41af,0000000000000000000000000000000003c4f051d528166f256d9356aa9cb885db5680c51990d9474a948848888fb82a9b86daa7a2273725ac8ec564ebbf15db00000000000000000000000000000000010a6c4c7067f511ca8f1b66bf9ffcbb275c7575540909262f7c4332c3d75b2f6d2f3ad2848c0d455410afb1cd60c835000000000000000000000000000000000ee5e582554b3930c5670d4e3542bf32e8b871849d7859eafc077bb2b533e936d462f614057f9fc09c4010afab501c1f0000000000000000000000000000000017fdbcaa065d301adb94a60dd20dbae71512d369fc82c556ea0dff66843be768be942e060752591c6eb0718985d8e313,240480, +000000000000000000000000000000001353960aff58d45691c5378a0676a8e837260f5819cbbac9cd75c8cc4c6f1e17b9dc9843eabc0b1dfb27ff7631e4e52d000000000000000000000000000000000d6279a43d3526c035e88b0b640b04d42ea573ed07323aaac1d9d5570a8be64782682892415ba2be5cbb13f56e3a44db000000000000000000000000000000001250fd14fd003f88eb6e0e80e9f2ebe204475fc6c06cd10fa45608a17b7039afe0326474ff80c357f86c2825cdf7a16d00000000000000000000000000000000186cd91cfc8ae625e302946f2b393ea67e1107c0bfe938f5f36d28879fa0c0780c847aac77d0310d43211152c1d5f5d314d5455ff1717bdd545f4daa37e145121e7bd9636d7a2b65633e5ca5a63f2d98000000000000000000000000000000000e55a98e8b1e59600e86cabb5e8db8ee622009b1618ff0df3e93fb55b80985bf2a8ed060aeaba53773274d4186934f75000000000000000000000000000000000bb7215fc43f465f51fc8265477fc8c79493966f040e02f0eacc4ebcb3414b84fd94ded822bd24dd5ad5720f12bb8313000000000000000000000000000000000b23328e15cda8a576ea352b5dd7ce382ec781deca6c23f646e42f0cf63e28669539579ea51e3c0afebbb58e1e8e3243000000000000000000000000000000001716019236169bdb4af7bf7d7ce0aeee7900b74023acbb16f6965c2abcf28917bf88d0f9d5bc26a81710496f7821fd4682cd8da62bd901355a60b37ca14ce65d427bcf9551203cae7c346a49b4fa8626000000000000000000000000000000001718a4d6f5e78524d8df23d2c589abf04e3567d2176539a30b9f73c6251de573caa60c2881f3da99c48d48e9aacd7402000000000000000000000000000000000ce0e35721379077e6eb3b572f7f7718bbf775b116521c14acbd3ff19549c75d50bf70ce84326cbc3f9e5e53605d8ecd0000000000000000000000000000000007cb3305ef0d2cd7de4dceaf25d2eff44d4f437e065f6b244bf1b0611c891626eafc4b759d55b45d76e94b85852df1de0000000000000000000000000000000011cb56d2ed32a46bd951836f8e0f92d3824a4cddf011eecf1e2d92d81bff407a04abdfcffd60ccecda6e9443b328d51eea2c7fc2050e9c1ebd05d15f197b4b1be61c6820c8d27ade57d85109d7f982490000000000000000000000000000000011ba705da23100f853882dd166d81ee1d7621550d156b14f7c2123e2681887ec3724626061db68b2c63987325b27d6230000000000000000000000000000000014271414fe078a80587269398afd127ce34c8dc2a4851f76613b81dc99d766d75c703949c1093b04d66a301a79d89bc30000000000000000000000000000000011b7935ff284b0f812b5da5b28ed338dc4c21ebbc7fee04db834732b11fd76092db0e8d80368255b0f1205129081e9af00000000000000000000000000000000104ff0ad2e3db08d3b4890b2e54f29e456e627cefc3a4f07c1109b764dae4142480e3e5312ada43fec9ba96ce587e8a4e3bf7e661d54796c71437354d7d3182770f10ab450827512a423d3dc82d5b43d000000000000000000000000000000000c60749ef36d63960022f3127d0ab4e12acf05ba1e1a136dec89be388b9d7144c1d78c04df658727763dbaa9725bd8b90000000000000000000000000000000019932b1c205a765bc9de0cc136999deb153222a9dd9e9ec3660fb6daef56242d08791d440888e69ca0da2bbe0fcb7d79000000000000000000000000000000001764790d12f5ff79ee4f2c9fadd5dfb1cf47db70b9e86018bbdbffd1be18df193c7dfa71533afa381053a77e02719c6400000000000000000000000000000000044b2b0211cbb407281ab2abc4725c2cd791b313bab8779954a2461ce445cdae60d4a9efad9f90f80e66b1438514e0f0d3a364e7b217dfd649d1e08f76393372d8768bb0fc85c79ef4652417ef1637fc00000000000000000000000000000000175cf9e7eead650e7ae4fd657bc288b6b6392773bf1bbea48e17172a5019637fbb2bc0a3d0d1e3b8054564935c908db200000000000000000000000000000000136da2a625cf72403d0861b9cd947cdad12b1f1e6cdefc4aab6756536425285a7953a1b892df40ec12ac3430fec889cd000000000000000000000000000000000c2d10c6d71cff4e1deba1984bfd17166571e64659ac91b64c343cdf587c29d52a2266c00a57c01feddb1df6439d21d1000000000000000000000000000000000384a782fb31278f49c840bb8f0552ac2734ef36bb3d115be7df20333aa747c92db990f7e879399235d122fdba0eed76eef7b05d5c725ed31269ae9c56dc7ae35048af39ab114319680d4af69be7e7c3000000000000000000000000000000000a9a821cc63e7c9857b0f39f7444a1e00a422f7cd5d0575c26bc5c6b98313abfde51e3f6d5f4c817193bdf391344e5ba0000000000000000000000000000000010daa8c7194a75cea757b6ae4eee85006eda459ff2cf155b1b5f19c3ad341972f72e28b781c4878e8919c7e5abe9a1d5000000000000000000000000000000001154d5d5764aa2b8818a9dc5dce30ba2197a86d0bdc7dee3e600462e295cc3a69dfbf8db34acf138e7a1f16b62a45717000000000000000000000000000000000b4243a09b05a958d78ba8ae25fd3fa85d520b95e56f1dff44e556b221a075f8dd3370313886d9dbfc56a75697454d72acecaee3dd4dc11e341b3dd0073842d90f641d4dd467a6596f337a6147bd30a9000000000000000000000000000000001820f953fd22b71ce00bbe9e9b78fcf5fb28bcb925f6b5dbf5711e00470ed7fd2f38d7291d40514ab4258807f29150270000000000000000000000000000000007b737b56a2ba33f76bcf66c0b26fb44d5f79879273f6ab21ecbfe6a5744da289464ca2b46c55edaadfe3210b907f3f7000000000000000000000000000000001735d1b39c5369bbf886c5063a96dd12b85e56fd9d8ff9d84520918e1dfeccb62bbbe1c2ab440ccecd0fe66f6ec55853000000000000000000000000000000000e591b7709bf00bb2a87e9edb95720de19adc41a42378cf9ebb930c6d3f5993a1d7b6320040d5c69908685d978be8f980cba585b847bec40515a257cb839c7e5d677d17b7313c258e83d630e65cfb5d2000000000000000000000000000000001732ac410b2a7d10110bbf7709dc6fdc91ce742f8cb9b2c3ba37ba5f0934f8622c675753a26d04a176e24a630d090d81000000000000000000000000000000001111a52da6aca10cf40127fa8ab7683505305e0d474eed28a5e1735ee6877aa00c1bd598420876f2154b814660f3fe7600000000000000000000000000000000098c6d19c2ff42c2c57a4924693325de1a91135e3474ec699b70439d034469e72e844a5511e23dff3948a66cc2a2165300000000000000000000000000000000175fb79e5e54963cdbb133f38dccea2d1abc3cdf005c17e8f2de6dba9b9dbdeff7719983aa9ddb602f0cf966fdd430e0b8cd305c650d2e1cfa91ef0aca9dd0d785d7570d6fb67e61fb9b6817116a05440000000000000000000000000000000004e88468d35d72dba6b3e4b9ca216b75b5d20c447064a48bee6a6ddf994b1e22fd6ee8abd60c627622daffcda219645a0000000000000000000000000000000015eb2ae16e3310b4c4ff557f0615519c13f29109d9863418fdfbe6309b5bac4463456df8ebb0b6d9022e294cc16265ea000000000000000000000000000000001288ffe0ffdb96708558d914bc412758770d048c4d50523e2b134f8468d11a57da97e42bea303ab7137e2d26c0b3b8f30000000000000000000000000000000003ce563b63c50b09a80b71a1a82995238a9de31aaf189c6d29307924b6f0990854507b7dc1644f689c5abcf931dd5a3c825e5f9d81273f306a065fd064ae24bc2c5ce8dbff6b22128753663a218da8a30000000000000000000000000000000009e39ce653485caf699ae1d1d9cf2b8c5ea85b80ea042279e57f0beb81056159e49f73d67e7b1f9ece9f9ece7dcd2cf50000000000000000000000000000000008d6492cc335660c54e4a34b29b337b5800f1ef992d124524c799c04c852ccd3cfc01bf39515cb8b96151753147e8c49000000000000000000000000000000000ca779d87aaa3a6552f9f1a10b0d2e635be90022326db04e6072f326b919ee55d4124b9268f55751dc0f18172bd327ae00000000000000000000000000000000112eea543d6609d0acfaeb7be98be609f03304f50c3814ee8a010283146e6b5dbf170c7314598cac06efb9ced1ac2930307ff9660ad0c24cbb139486638a2556687f88fb93a290a1d174bf87d780b3fd0000000000000000000000000000000006624dd7f6eb043da41a36a15752f370eeb3cb2e6bd88b337b370fe0660c5ba8fe64f62e112f91d2524e9324f3a049fb000000000000000000000000000000000415b964484c9246385cf95461ab955ed0390e20209ed405d84fa8c8af9fa7ab39ce89049691a63c61b12bbf6aa2a4e80000000000000000000000000000000014411d7b2db7c9ee78ea14c6a315df3d90827b511db2e2423d660176384d8f8afd284879b22f5aeed73afb2eca4be52200000000000000000000000000000000105bfb471340e76f28901edbdbfe2ba246a8824b501ae2d4a73cffd2690181347c1e6530804614e88e2bb13a8edef8f4bfa8ee3b44c70ba2512c00a1aaecede2180b08ac3ac8c550d70407f0c12e027d0000000000000000000000000000000002b17f4b0b0231be229d87f075998435560ce9046a8b0e8f15e3a9f07cd52f3316f6d8c00d6a872362e7066715cf990e0000000000000000000000000000000003110eb232154f8a06834e2ddd33c0207ea552f439a6127b652bc261158209a00654e50341d333cd1b206a915fe0691d0000000000000000000000000000000007940e209c8934c185e4392f12fc0afe3d234dd1ef3f92df18d76be8fc42bdcdd6d1ea8d5bb6f07b3f3caecbeb5ef27f00000000000000000000000000000000012ec903a8442f68c03300ab02ddd08ec935d97bec9050d26a5e276584592df3ab87d596f90768d2c0918099b28963be58aa85b50e5f4ffe375599cbb912f41d35acbb85a324880148f9b9003c4265bd0000000000000000000000000000000010fdc16bff0fea02b325c672fe06297e0669094e2710d0baf3838f3e234c3f776bb3fd41b967c9ebbc72a6bc6eca70850000000000000000000000000000000009d64ce322e39d5b2d0872760a61a831877c450b1cfac6cacec52d4070b0f179dce90afbdefdaa8466f6a6e2e83ee8da000000000000000000000000000000000cddca46f3b24e05b76e61b4584bc716ca7036afdd914731a61347e453a26d07549e9808e553ee056bd47e53c75eac8f000000000000000000000000000000000451cccaebe1a188d3eaadd40090ca594f071c8b6d0e0d82f5b2d43fa784f8437e4226104c4cfdb24ece1ed75375aa616810c6cd59b14ef4f6a4c2702cc53c65b3dc84988372c1195980417c583fd7ff0000000000000000000000000000000005832ad778dca8dfcfbe741dcf311024d76341d5920b6830cb75893a112c9d86719583d1dfa7287281fb73fe21650c3500000000000000000000000000000000044feb86b4816e45ffb98e9a670fcb039fd9d8844a2c7ff9b7752f20e619195fe6ab1148f30afa393936d3605fa4c8da0000000000000000000000000000000018db9365370a8c703364ba6d9c48b3512da46cc603a43c3fb91c0a8ee59777d7cf9ac646c3e4274bd950d7de92ebce840000000000000000000000000000000017bd82310e251701cafbf8c4dc5b9e6c88085b0df287b6dde7887e1f64f2d9487a25b31abe07aec7d99a75baa5983195c5ebc09190ba3df49d8ea55cfd18370b9d443f9d9084cf84f2236ef4723d2d470000000000000000000000000000000002c1df194f01dcb503dcc8a283f059b82d141274c8f37cdb6441aa33f84f16dd288d566752a93ca23d26ef5834c0658c000000000000000000000000000000001700fa4459dd4e609453284f4f7dab479342675a87c1cb42b601908296557f39256f1597ed3b9ec38ad0a40a2c728f0d00000000000000000000000000000000135ed4f475eb99397cf204f971215a0303316a3ed8b62b303b4bf756ff753410b7fe263c4e97fd4c4b399c319ff3ad98000000000000000000000000000000000a487e179bf1b73627af9d7d2b43bc0e43127a8fbfeaea7ce958ddd53ecb27741eda187745e3917f1cbb60adf0286f5413a56b176fc835b7e825c817d432b9ec6d51b0a66483dfbf12166ee979b664cc,000000000000000000000000000000001327c57e16f03fbf652bbacd16cf574113860eb87b8f2f6e498dc5dcc4f2fa63859d922d88ccd6683d503d0962db5336000000000000000000000000000000000cb06948c539cbf686f6936b6a1ebef2e148d98c531da36272e0334afca5c2b16a52da542a0fdbc3bf764eb877f5778a0000000000000000000000000000000003acddfb5bc4fd5579d3f592977365840be4d3cff96434e5ff4f01ea798e4401930a1f5d91f8de3ff98504dce398c2ef000000000000000000000000000000000a5a332805f704613eb085d6639f99667d0d9247cae34eabcfa399eed551f24c5d5cb05d6458530ae270b1be682e71f4,240480, +000000000000000000000000000000000b1b06a76e5bdcb6c1c2f1952b49e1820a9d8557229fbff8740269a0b819b91cfd0de20db0afd76a2cb0fbc5fac12ec5000000000000000000000000000000000347654df2084082efd32cba2b270f66b0ed30fa8713b27949fc9d270ee8eaa9b9a7896d7a52dfd8faa3e0cd112a24e0000000000000000000000000000000000bf5b7a2c0c8bc223ab334bd1df5d9fd4bc0c635379ed2b32da13f6178e07217bb88a4bc2eae0b975f2e566f657d23aa0000000000000000000000000000000017042f8585a07304995853270b1b03bb08484104f7498a12bc865f2a0e37e662fc4b0331b94ee5690efe74056567000bdedf65658ec3cca48fd48e844337159af090c5d1f5e9d713ac6d0fe1e1f193d2000000000000000000000000000000000fcbc73d0628537eae417f8efc67af0a4c9c375d82406086bdff669911fe1307576333c389f189f49677cbbfe2ee98730000000000000000000000000000000019d552b85b1445660ca49518d202afdc67b0eb5be02c8d3482dc1b12e5d40a4ff95a49ce47809e4d6644d04aeb67b3c2000000000000000000000000000000000ed536c0f19f592180291bbce59a72ce5e516199dcbd4fbba736cae2edbe3cfb860ead0325dcc8f8d9be1ac126dc6cda000000000000000000000000000000000f5d4f0c0ae3e76b1c41edbbebcf1ff17c7cefd41e7ef8f75dfc10170834d05820149d5f721a8c6460cd0181571fca97db65ad6bcd6f485eefebda0badfc64e9e7dfe7e911f3ccf4f4fb9528dfebdae6000000000000000000000000000000000d6207f6684f8d2f083c963551bbf0a674ba40e691a34ebe6164ff80ba9bab2cc23024a896d7b906fb74c95016a9adfb00000000000000000000000000000000145855e7d610b50cde39db8995b127145d68fc9bea3f075f65b7793acbb14bbb313a1a39bd96fbea6641baae02612b000000000000000000000000000000000005b533ee83cf72f0e4d9c9ddcc6b91f4364e50a106becf766987c490d559d0f733839ecc706bbc9c2c75b243814068a3000000000000000000000000000000000cd8fba13b9ba7557c7577da183bf50810fb14eec7380e3b3d4f2fed62bb36f2b5ff288736bed0578fb6f47fb6d22ac86e0fa09884a7ff4c801ea0722cf6bfa58a46fc3d36058e8c395ea8fe56d9fca4000000000000000000000000000000000fd6a466f2eb12f6337ae9f9b847ac1481820013142af1a474229c5f5f5e1c0bb2d9678c19c7a3a1aa22cfc7b5052e0e0000000000000000000000000000000002a0340f5a0caf5c66719f7d546972bb4b89147989280542787d281901ff036b7c69d41418c21c43127c0158593aa5cb000000000000000000000000000000000deeee37ef96f26a4907e1a8a8f3f030dc09102799bd0c6dbeb1d208a0c86a423d0da6313e0be03c026da5614a6a576b0000000000000000000000000000000007220475449add59b3cc6570701528dcbdedacb9a3d39674ad4aef4d94114f24d2bff32f40b25af97ba883905ea6838a27a3377d7b9ff3aee2ce1194a22d7115b09a9fd53fcfa5e7f76bd9fdd35559610000000000000000000000000000000009d7023ebb73df81455f74cb2708c14ccecacd49521a0cf67ecb6edc8756e286ede59eed54d89eee5f77f178ea8fdee900000000000000000000000000000000002ad48fc3192634e7b01604678473e286afb0efe67a4377bb885d38b59ea00202241fb28c93232ce7c9a3dabb136a53000000000000000000000000000000001934664f2bfffb254f0415d6769f4e2ac710ee88cd822bf5da5df3a2541f887e4155dbb7e8056efb2a0370d6f9173e3b0000000000000000000000000000000019df518e1ebafe95adf683279729a3298fc8d7eb39c9a3dfe4b6665153f970e243e50dfb16fb87b3be54192f69766659446a62ef5760c995cb3cd0984d607c232c1eb0df5516a501ce448a189a3134d8000000000000000000000000000000001870048d360f397877321904563d35bfd0817ce464e0078e9605a4744e2723f49f9cb21dd3d6f37f1f9aff5a6a99bc530000000000000000000000000000000000e29dd0da13ac451d013d4a38408827cb0e739772e1f250d31e4192ddc13d651ab576ed6b8f4ee44e928fa663244999000000000000000000000000000000001646183099579322e0115ab0b3bd6c814e216ae6b2b80206354925565b7bcd97bc12668b7f3530a95409456ac99bf01200000000000000000000000000000000092f6f594ad0d92c9c64f78c819c44320e6bb5dc1dc8fbe58acc7ce3c101e49a74ae6d50b1a668a3b7436dc445e3da345f0c1a7c2dd281f7d2f006497f99f65d6a1e22f1d9aacb08724b3576aa19e19f0000000000000000000000000000000000428ff447de18dcc11b2c5c679bc2efd125464f589013c6964ea6cab33d9b7cbcce3a5d6177bf43114ee256f23fefa10000000000000000000000000000000000d1ded695e88dae6dfa702375959831f4bda688fc0faa289dcfb90a07f3a7963f2c9070958561909a2051a852cc15e1000000000000000000000000000000000c39bf1d11fc5693167890246c81133faee93a8639f459429757965e0b62e372153ce53c61f2c539247dbe7747b27d1c000000000000000000000000000000000e84ecb6dd9cbd4133c22350f07a976ae13dcbe4c6ae09ccb023f2118fa2dec68c20ba2266f9b571bbe30dde97480e0a94c1476ae0a62c502aa096a371e30ca885dc13fc417e3dc9bc00bcdf516764100000000000000000000000000000000015e040fc8753f06ed1112cc06e2cb7142a4fc984834f01faae718c17cde782d5953547857ca9aeee1c4a7d91df060d330000000000000000000000000000000006789ac15d719a7159b650b757f7d3cf58fca02d3b8f3685478ad5e5b1dca0508dea7a8203ece97c7c6d32b2f194458d000000000000000000000000000000001824d75634043cac3fd17ff0bb141daf7010f70b5941d8f75f1ae076713afaa7e0a0a25fc71038baf1b1255d64c914c6000000000000000000000000000000000a2f71bf85af6392a8a070596e30225bec9e3dc12c70e8df7c545bd6bbcee56799db2c9a8d2504c4f90ecf6a5e18abc9b677bc9f1f7572f808e969aa50efc519192ab8653c71090e5cf8cdeb1a3544dd0000000000000000000000000000000008bd859ff1f22d682f86e1a0e3bdf3a332ae78d64814720687a3de44c9bdd7506d2696b4daf81a94d33f64983967fdc2000000000000000000000000000000000d7b4b958e0087f8edf18a4370ff98700764c126808d5c52afd3e71ee326c766c1e5712dfa351cf5b3c518e52133ce780000000000000000000000000000000013a145331bdd9c93e63edbabb9f6c541a7c4dccb1705f07eb353a0407074a76022a8e5f5f2535b41ecf6474649e257bf000000000000000000000000000000000a12e461b7439bff0dddb560dba21ec53ce88f71fd3dc10723f3d8742ed63a1ab725f7e9619ca1ccb729564dfbdb1be7f5ca580a25a5c87015f57f7c23cc51a0beb5926c84d44659e45512da51aa0cf4000000000000000000000000000000001430a8184c5055008a06ea22ca9c997d1a24ddce7e374937c32ed1e487c80537b238a589b5e50b86fa194666bd3410e80000000000000000000000000000000005c78c94f457bdda242deab79524bd2beac82bb1cb427dcb2872b56d1f46d11fc9d69ba132004958fabc5da7d6d103fc000000000000000000000000000000000e985e8ca038b5dadc9fcaf22699e75cad9d2effa47fe7d4c579ee056b1e34ccc540372111a665041062fc6c39e05d170000000000000000000000000000000018c865243534fbde740de0ffbdeab0d38ee878c20f5d84c0226d1f2b14ed3359f5b5b909808b6b3789bfcab3be75c4cdfa1cc45c35e266a82899d8ea0c9c1f96f96140eace41a8758a87975b088f0231000000000000000000000000000000000c5b10541ec34dc0a8b8e42d9d6fd6f4f71e1fe56b5afa323f4ade35c0170b5e224a66771326d9edbddf2bd38c6c68ce0000000000000000000000000000000019cf33c19936f7489a1bbc095d0f5c6ddc1f43bccf7e8d1b30fb8e8cd1ef747b483b9a8e9faf21cba7cb17fbee887ad70000000000000000000000000000000010e83916faa7bc9de9feb8a7f34ac6f2aced06a771b662cbce846107245edb9c07632782300e838957788a8d88c8253c00000000000000000000000000000000066127bed5ac9f2871500fdd68a03ade57c35449d4b4186b9fac7c89e91b4ebf2f2a02e94d0b578aaf60b32017f147a493d2908aa9266844eb265c2b1c17f8357a5ff039836ba83c837909f6a9d0bc03000000000000000000000000000000000cb5a734a28b44f04d39ffae049fe8b63b138411661ca6dba00c72cadd47b50ad4b71e858e817561682d6ca378ebbe870000000000000000000000000000000000baf4d689baa09aaf763ae7e142b801223c8ff58f2b541ee4c44ab2460fb8f6dfc1e9f61a8d73aeb92d7d08c281cf410000000000000000000000000000000008a0c736f19bd0005c9d25f88565b1355e53fa3403021577de536712ec986567184f4dd626127ee80dd03cdf9044b2ba00000000000000000000000000000000063ffb7a3b4e057a9ffe233296c11fb462136fc4b187be6f9e36f9e6d335a3d673ef8b9ae6f60c146a075a1789f389cf3b94325aad8a2c80971a781bf6f6bebad63ee37405ab7e903fb7094beef14d06000000000000000000000000000000000c33d89595d039722222b9b9ee7ff1a0dae896a8de97f202d3aca00bd81d0169f14676efc4b051bbd339dce862d8b60b000000000000000000000000000000001109a24dc6f70bea47e040b24df395bf561cf5f1ee79e90c9b0480fff0795677483a85e6f2e9ded4f36ca849ff39d6f60000000000000000000000000000000009c7878f3a4e4e3149b72149a7da91bf527c4d7c94b15ba80b02e0e50b02a2c482ecae9f458a881c87e669986514f6d70000000000000000000000000000000004284448e42187c128578b801f76d421fc508cfee9360a7203a91d6f9cc7ccb6ed3211fc5df9e15f14aea98bc298b2f95143a8e734824840346078aec03d6760564870c5ee2b2dc13f8a39ac452be9f5000000000000000000000000000000000271ec1a3f8e3364ba8e101b49c0bb17e2b7c7f27a4aa4d4db5c07203195050f30c1a05d33c524a84b1a2f0ce31a587200000000000000000000000000000000082ce9d1da5d7f192c537b2bd617b36b65f88b308fe1ff85e47c64b62dc62324458493d1cd1da9f5fe308d27545fb6510000000000000000000000000000000000b30356b59eb04258096d0c3f357fb04471583cfe6a060de5279bf2cff4413678c1716ba87d0b6de6b6e79a96ec26030000000000000000000000000000000003c02470a14211fef14d754f6f71efb33a06a76e099093a5b9512f907ff819e1e0e15f14995febe48852007bb5c380bd0dbee37fea759c2a58cf360c654f85298e8ff44b3f900e8229c3f838345d053b00000000000000000000000000000000172df3290c3c5044d590eea59980d02e02d4fc6fe7948168492362de8f0a85df0c3d09d8cd8b206cc4d1608311ef4c130000000000000000000000000000000010e4d14065315a0d9e48204e47955ee9652b08318251a7836f32e6fc015d4856444172de44b3b88efa1b54dad346e9b1000000000000000000000000000000001549b9c85cb2fc2c7495d7ef6aa1452e58937baf58717037069e6bc6d72ced3a163f800991cd26510e71aa64c44f66170000000000000000000000000000000007814c2f1734fcc8cbf9fcba06b936c86d0452a2370f8c9480b97105e42f9babfe0869cecda7e15500e9d8d868290201b92f9db82d0976f4c379622c4028002ede2ab17f647bca3bbfb159045cdb342b0000000000000000000000000000000014f849e9749a5ff6b7b10daac7f5934be5f783d49c8593367c4243664e01b1d3552e878802d7dfee823e0122e9fd46f90000000000000000000000000000000000d0b32d7904dbf08269ca3c6ae3fe582501f55e32337ae361fe4a58dada560db54205e56a399aed33bce8758a05ebcb000000000000000000000000000000000cb21440baba44c3cc6943c8cfa2fe544a652f06423d3de06c2ff734ebbb544da07ba8982b3009b6c4857b73ceca570100000000000000000000000000000000174ef591975fdaa0e3cb05bbb4140abcb38f685ce4de77c95e2cec1911985557b77d9229940b8c9157ccf9fb553e8e0d98df4ba50cd5cb5a02d5f50b3ba23e5f5b0172a46cc315a0a94fed05551a68af,0000000000000000000000000000000006da1222c7ae02843ff289931fcfcb315f621972f45e4fb4160b8bf48cd8102d50fb53d2c699afd36892d91f5e608784000000000000000000000000000000000523048c5de2d0139965c976d8c3328666e99c249104712719e992334442245e955cd6b14a1e3d666220617d78edcc630000000000000000000000000000000009f669d4e7d89fa8d999d8d5a6323da9445583344276bd6a29494a91174aeeb29132926a893d5a0eeee9c3048ebc0dd200000000000000000000000000000000099ee1c33d6f09a8d063393d2a8debeaba93027e31f7b23c5170b6747f56bd6e6494de966dc280dd67a38d39ae35a336,240480, +000000000000000000000000000000000b46cd281a09b85d977e88cb2251cc61cf13181522cf1c344b114783c4fa22d08f2d008faea3dfee8ea7c56aa35ee39a0000000000000000000000000000000012b530bd34f5b31161355436c7dc444d41d634b0ac3292c0096558618005fe82d189e4b208a48240dfdb4d50ad44dc620000000000000000000000000000000014d334e7835d4bcee2698ca4f643f9692f28d8090ebb8ed8f67c60639c64eb116067e5e8a6d04a0c88c765c608d57ef1000000000000000000000000000000000578cb4e243b4b20c99afefcdc0e0a9e308ab4bec6d655a8c4981a711515c807067472a6fca30060d776213b8ed98d74e49662df81f6bd455ee554704ff0c7d5a589d91e017d7ab4c500d36378c17c8900000000000000000000000000000000046ad1212696acdbb686543d284d7cf2e1e8e8c10af76c6ba51d55f060c006dbab25d3a789c71c428f5bdde9aafbf6d5000000000000000000000000000000000a6a880d52fed6a45bdc61d9ee78d8fe472e76ccbe155bddd0e2a967f4d116bb9f2dd4c62cc6f7224b835c8060213ecd000000000000000000000000000000000786544589eda15edc433edcbaa46d4953da72473f04169ea64dc114b99f0a58181d41dce1fcaf7f3109f66aef02e53900000000000000000000000000000000030759c3bdeafc94fc8fc0b03ddcd96869459bf54ace74582aa06c179323ef076aef89c09ce8e7bf9109ab2e8c8fb0be79eb26c79d78ab84c4d7e48e56889a601fda17901037a74fd79355e7536f3953000000000000000000000000000000000e6addfe0db96a7377fcab1fb92183fd7d7f13ec003fdfe0740bcc8cf03d8cc602d5d808b4bc874f34944a65b249997a0000000000000000000000000000000014a4337107e716113d8ba0fc7f75e85edd1c132e2b3dadb3f9cdec1440f261513646525314b5c0de6fd372472aafe877000000000000000000000000000000000d472ee0484ed831f8ddf7ad86faef5443df8b943c6fd4c3f94c8d52d9eed6fbb53107170a60f25be52219ca4816788f00000000000000000000000000000000035d06ffc452c65a31f80c3f8a0c1e2d15e32d993ec06c50499bc0fb8f669acd3d2182ba23d942489ea922baf61dd49cd2918ddc2bfb7f7cb3d7e74b43b9a972c5b51ac20ea83f41d5b51034b5714c0b000000000000000000000000000000000ef1f5f6b3041939557368d613279043d1aceaf5fee3ed90b3b756ad409d700fb41e62b3758c8c2d325db7a37f339c610000000000000000000000000000000004d66040a8e055399bacb6a1e762b698afbfabf789caeb957fb7a3dccb01d7dff5414e90f5a14961c4e980b298f834ec0000000000000000000000000000000006efe9e66078000c26d375e87ffaca643aae9cd3f8337f5718e0e268b74f4b7838f7661dc0ce60f557e162a21ff467160000000000000000000000000000000014ab782a3b2c06af7e9c2f28f1604cbfa8a676a874853bf38195780751d306936cefd1cc38c2192cb756e28793d2abb3e9a8159fd7915c15db69355514d9dd26c66fbd14af969ee576401b1b782fc6d300000000000000000000000000000000057270788a199a894b37a526a26bc4d293780d365a6b66247e7417884d543dd752ef7c89f2f4b38f4b51e6f9d86b45ad0000000000000000000000000000000000b59fedd6798487ec09d226a7406b27f04f7983075b4659ca6a78c6bb8aa83828fafdc6488518e2cba6fa4193de938c000000000000000000000000000000001105c18d92b4192833302814ee9b176831e57fb64b703ab3c2d3f440ab302c8fdf7ddc81933d3b1adaad16038dd6dc1f00000000000000000000000000000000020509b08e6ed980df29da649051c7095edcd4eed4ce95cd797da430cd09062a110bae21b6f73daff2053fc0289041fac818ce6e33e581595e83cf8d33a62edc26ed38c22f20c6949a94e2652bb954cc0000000000000000000000000000000007be348ccf6a76827d3b9b33e7a89378c133c9b226e47dcb205ee061423ee6e1b838bc262a7befae7c15aa385ced00bf000000000000000000000000000000000689787c19192ad55b9c6c260a5ec3aa203ef71f0b746eebf10f82526c4fadaa8570936d7049c1a46e7f3cdc455a63a6000000000000000000000000000000000306965b09678d481aa4c754d56a0bb4565f16f7523cd0b404fbd39dfc3b6ed483f5239fa30f13aa3e87918ca039d5ee0000000000000000000000000000000000a2586143f9610a96eb0ef86593988770db5ed49663eab72f8c368b9388bdfbcd02fc6bee09f4fe055813d140ca0fa89ab338e94b31d22947dbeb20fce3150127249d2db6107d95bdd032eb24c49645000000000000000000000000000000000018f46dfdde786a88e582ff6addbecb4f58e12c2625e3d6440f2e5b5781beaa95cad6f63b7d132e84700e7bd344fe3200000000000000000000000000000000185a4fc339a95a50551d53c18bb0dc3b74e9c164729c2b0d919392f7aad2be3ebae3b8f676ab81ea05233b3039918ab50000000000000000000000000000000015395b020a9d0bb336066c1347dd91c557b6ae7b8817cd8a2cba9e5bb149ca3401d661227c26d52a9be234faea894c8a00000000000000000000000000000000103d9d7e33a0767554e13b57dc756981488a3c7dfcc026ea84b35b0af21193e301226cb5a4760962707d19a95841be9296acb797236dbd0316fdd355f07b9b45c9bc626f73105e87c376af4d7dc075d30000000000000000000000000000000018359aad8af59cdda484232b885d1b14956ec04b5584684b13a64d97b8310c283e5d66637dd75de405f5f4bc65a6879a000000000000000000000000000000000849fd55e4f3d4dfc643dfede6356826eef21290b84f7e8e226deabbc84273d95f7be5479e9656dc907ec367a7ebf8f60000000000000000000000000000000006ee01b54eb7834b4de53f821ad46f467cadffce6df09751b728d0952bfe615253d7ad173892a52c6181810a815bd90600000000000000000000000000000000161472d45b56dd9fd276fc607f2eef84c5c843ea05799e732d7eb6dce96c632335949e1b3a06815e410e919f4cdc3fb360bc12a8b34e717b2c410d026660c14182250d7c66f8f88dd4cc94e550421caf00000000000000000000000000000000107ef91cf3a3068c4e5644676f7bc7c5f9ecc361524bf3fe2ebfc606f22f8f83b38c0d4bae89f3cdff6119cc27fedf820000000000000000000000000000000006a7f7cad2fa9db8824e4e30da7158f7737d2536554b904ed835c37add0341c07c5220db0f9801da2587a456300c7b75000000000000000000000000000000000f6dc3adda42dbccb1d1e3fea8918f5572e8b26ba3011429e754edd28559b731853761d33777f4e767094f80e63d417700000000000000000000000000000000107d93537a79173ba9367732fa3a28113ec37e053cdf31ce6970dedfa8a9b4cd55238289be9a6f40319e3dfedd132f95537f0f732fee8b882d254a81704d2310c05dde186232f3cffc05401fa98302150000000000000000000000000000000019dc19a1663bb05ebfc0b7cc23ea9e07376de413f77e15a685a3f11fc19bf0ddf38d5671e2a5e6e31624cbcd47a19cf60000000000000000000000000000000019e78aae57f327fbe8ce794afc22bddde08ff9bc9ad3527601cb1fd5dc0b8ed8fdf3b210f86760954b48bf61d74162220000000000000000000000000000000013954a533bf871e99f4a7d81a8b9931c480ce7fc47260c3708c590ade42e6b7bb887d4d24aa18642d010a8170cf85d34000000000000000000000000000000000a561d3f64ba31a6d45ffcf1bcac95f8f665133a1e962e31351ec78e369042bd3afb0c43d12b3087168c1142107241f31a22bc0bec2501a505cc8e00a24792bb380ed451ab6f56fde07ace8b6c9348a20000000000000000000000000000000007149094366e29537b0ad7239ce04bf49f253e4b746b9fe440dbf9b425bfff21064fce66e286e08c87dd83e22a3b499b00000000000000000000000000000000045ead132e0d03c842656cfc82a45c8b4a3b0cee7a5d071c5f235791ff7b5ead071b2c529b446a15aa8837aafc11222d00000000000000000000000000000000013159458f2123698ad4e7d41da47ad7d5083b928839e346a32f2307ee69f643ca11335d50e47d328b0079f1873cc7e800000000000000000000000000000000167edcf807ee723ba70e352367705448047c6b5223fe703381af6bb103cbb24da739ed005b14fab5699fbae6574505a7c7b10c801fb9d929432cbbe994b404d3baa5633628f396d20d047fe2c2ac2914000000000000000000000000000000000feb6f6f85903b3c8e4d6ec2ff234775f12727fdf7c35eade09c9773b004270f659b00248338f0b749d6715778f1f4d90000000000000000000000000000000003300794df19b9e472e8b869a2762c07a9251cdb96b508dfecdbd62fc3c3843b37118d216a64519bc3bdb71e40f9bd700000000000000000000000000000000005fa144135a5d6cf1c73055750ab6582b4c6d368566172b75902b1fc7a6f5de2a251ca7efc7ac6cc6c0bded14df02b700000000000000000000000000000000004239a7bfdefbe78116a588810328024b1bcebaf8f28f09387dcab66dcf2b02c94002df09d12db369fef9dd960783c0b84f2f3f31d9869799ed8bfc2cb129dbbeeb096d771730ae2863c4ddece66158d00000000000000000000000000000000007c8a24005575a3098c12ffa65095bfe227ee59e5e978a7ccab7a9a72391fea61690648c102ce24af723945bbcafece0000000000000000000000000000000000323d57bec7dfbb4614c8c3b286860fbadbf71901fa006149053ea614dafd56b1f3d6a86fa55bf1cbdfe8af4ff08dec000000000000000000000000000000001180b2b0b9c4c12f6d06eec07bbf6f5a220722015fe5365d1c4ca9e58ac9c8f67964d8230152d7a2220575c756bdf8b0000000000000000000000000000000001969a364c447f07d0820586bade587ccc816e50696aa0c5ea4f1daf6cd577769a890b44caa013d93e7f21f5ea269aa85c62206fadb762c23bf77f69f69bd492674bb92edb39248ad2a432f819304e6ea0000000000000000000000000000000008a51c01c3bbed13d42a4da626a8b89e2811db1d83d7de3332b36881ad14a5c8668ece4f5ed2b71204810457aa3d75cb000000000000000000000000000000000658a56aaf627e3f776d3f03caa2c00425bf197c6fa20c92f563f48260109a8f935d0d1638f5039486ce0c0100834fcd00000000000000000000000000000000126d1964f2d964c290cd7364e175ca4a855149e5c4ba488829a436b09ee5e21f6c964e439739f15317873088726bd51f000000000000000000000000000000001803186f88833393bd853970ca4fe414a43b7a619ded1f9c830444b4d43a94e9146146e2284d690436b395bf1e3fad15a6f950de53d07fda75ab43f73982c2684edb06317568df15b8712dff2ef782830000000000000000000000000000000002dc3756c7f4bb47559cd720a3acf4159290d7413e0498877d1fe321cbcb7cdda90b6c8b4ef8e27b2642b82ab9b3174d000000000000000000000000000000000c7490f1ccfdd91aa37a3044d265cb0612bfd9c065c370adb813b2d96f02d44041e79921d1b8935dcdb8c83ea4460ef30000000000000000000000000000000007beb34bfb9ba9b6fb590c7e830400888095d1958b252d187c184de91f165e12599d66345341292fdcb662deadcded030000000000000000000000000000000001ce203d58bebe1eb5b7cbc6038f75b2f7534bce9f50e7e4c91d6cc5ac1bb68d9fd8ce99206c5ec92bcabb71672c6ac195a373fab5176d124f783a36eb2346dddd5c4eba9e24e4c0cdc4f925e2e24cc9000000000000000000000000000000000765acace3e238e51bdaa08c0f6d737c9de55b5ce9ac3523335f0d35bfab6f4e7e2944b8aa4ee031ae9d39d4db96e9ac000000000000000000000000000000000b0fd488a6f9e92c4bdb5e82b52a0035f9a0aed7f69ec65303632017669f34d11552f849326e4dd204d58f50f3ad124800000000000000000000000000000000033991f66588b5e39eb78c7cbf62a74bbde2fa1b7c96164cb58040f0887c485b372e0ef4def9d38da9c6f5c4df2d59a700000000000000000000000000000000187d41fa7905739078d2c2f8775394f830d20352a9d91e97568c6929412f356009239bc9e1da3a8c766e89d09893b5b5319d855218eee020f9cf8e4c0b6004902f0b16eedba8a1c911476af34f65dd40,000000000000000000000000000000000dedf92894c567ee656051a7f02384edc7206152af6d3c5f662ca02559a3cc349c6b034c6fadceeccf652a396dbec6c900000000000000000000000000000000089deb173bda620678247a7218408594efff7ab0cebbf627b93ed37e553cf944e09232b92afe2f5f31d29bb9ae442c26000000000000000000000000000000000178bc39b2ca8b032d3cde53d2da3f8797026d78c76c51381b377c79992f027cf55ba4e182773c99c99ea6293a948e5c00000000000000000000000000000000195d9cb91537e77e7a4be4370b982b6d36190342ef6ebc2250a9cc8ef6ef45211736ce1f41da899178c1adcc4927a9ba,240480, +0000000000000000000000000000000004638ececd7f626a069b1bc3ad9d0f7cc71e5f0c1b11711fbfee1f81466b574f7a11de8161e55eb574ab367f56b9d6480000000000000000000000000000000013ef4f403f139771afe7e97815d3b3777818a3054d02125d3a25138e504c8c2c6696061572322aa19ace9ffd8e3ff308000000000000000000000000000000001910f776582f5acbaea626d2378e9da133b63afa087f25c2cfbcd1e7b34f6a237f2e9adccc303f9d5efe22496ee2ab75000000000000000000000000000000001963bd62098614c4ca2fc2a9e2d679c2b74bf9d322d34377cc63c3b8e7f8a4eb7d6d440d081e044606402fb3f51b0cee2a397c2f19a8c4e66df0e062f179be2f45a0c5e104588222a1a78447512f299b000000000000000000000000000000001935ecdf4f21cc6b510321b4ed2663e339954cd7399b9d67f1d9e2ea7fb9bfff8531f83ab59d3f0546393dc289ab2dd7000000000000000000000000000000000f390b86fb4cd4c1a072a83e1d1198a57a650fa6e94b69d983b693c592bc0c8fcd9a46c6883adacc4c7e2546dcd079fe00000000000000000000000000000000136beae11ea54ae26a8d69015ea7793675625b2013dbeb081a5ed877832849d67ac709b81fcc4fb322b262ec3776c0c00000000000000000000000000000000011f1df574f63f679b6464df463b58948fd337a4b3f159229ba0313cc040303345e75a3a2b0ed0dedfbefa89d8331d074f193d5a575c80a3e7599923bf5a8ba8a48e8f98322d1d8eb1da42e446d518c1b0000000000000000000000000000000001510378fdcde4027999edc99d49bfb46423ceb0d740829e310f8a381a7632fd0d6b6aa3533c2702a5ca76d386ac0145000000000000000000000000000000000bae237bfcc061552ca07eb14300cf557c974611885aa6894f7933f7dc7a0a1cc3b5587bbe1ea5fa604e3cee1db5f7c9000000000000000000000000000000001743207a1814990c798dc3de272a02b1b194a485bf09faea382dedd957861dc15cbf981f9906cda50cce2899785b9a6300000000000000000000000000000000106f902004c66c80437392e92cad73bed6b73010bdc7b6a75de4c01f0b6fbe5b8d1f47378279bfa42b3af05120854ced07f2013742ddf2d35448feb80b6b7aaf2925d3975ce28ed2b1ac789886ae26e400000000000000000000000000000000123362e41268f7821fcd2294b032c6a51c6d80506eb052ab6132267fa248a1a60c3e4eb2e75a1674bee1c9d46d82b9180000000000000000000000000000000006296670461ca67081cd76528446867e1a4905f88742d0ed8d1f7baf86e0a5e5ee86c8b0eeef07c14dd821dee0143bea00000000000000000000000000000000058bff9544e4e02c063158a52a68c93c7544e8157d37159dbb99b51b09e3d8f5b307bfe63a10fa409e20a35219ac244a000000000000000000000000000000000b135edfbf53187004d0977db94eeabf426ad7bea84ad76c6ac771fa186a073d430af76d717070e3c4057a7a2da095984e637a80a4eb1b2caba68b6828aa18f956c62baa7c5e9e591a15156c5abb605000000000000000000000000000000000133d3a223112dc5665e78fba8fc8d040d133858d984e66d2382d5e629f9369dd127e93c7a4da77fad98a0520ebedfeba000000000000000000000000000000000e88515db391bcdeeed2a9f64d27387af0391bf832164fba79100b560d8150debbd703c140dae3ba9b1ec35c1f45670600000000000000000000000000000000042583722c69a19f413392c6a2b75c8ca969be85eb951056d7e1d94e046dba49c346d5774009b8463a40b0576ccc1a6e000000000000000000000000000000000ee61a9eb6ad497c57405a44d798868e22b4fd5b8c480e9938cfdd3f1817eaaf331a9988368680158c59c2801add0a7a27671631f9afd9d2e86f263f5c17c3c11c7f6e43efb6d75cb2cb8250094f228900000000000000000000000000000000020352de9b4e8ea1acc8589bb22e23dfd0ae3a80de9e21bdb3f6391dd05a012e635de9e1f5f450bf4aab05728c054f8a000000000000000000000000000000001733593b94ec800bb59ed97dacbefda5ad882a8023346bdff8f471c5613c67247e27d72cb4ed8cdaa0f236018dd2128a0000000000000000000000000000000011f272a3b25bc519fc3e229211b846042031e22fbed22ecd0d1a4ef1d05feacf105772d71157e3d7293575aca257cd5f00000000000000000000000000000000153b4b4d7d65f7bd13d20fee4812f04706c96cd1a0d27b7e139c47299805e0ac86e8941aa38d90331c78a61c2dd56aa3c2decb1f482f3eb48e7f52b89f6452b659812ef79bb42fb25f03aa9969faf9bc00000000000000000000000000000000143e1f6dd9397f0e89a46c6ec995bf6c87ec8a72b309f050dc5b3134e00e2a16327767cb0573ca5ea9776215a5815df500000000000000000000000000000000186cb3af2cdb4562bf2d0c180079547cfb345cc3943fd7f9203fabbdc1547079cb9ed854f9b1a47f513e318cd409df83000000000000000000000000000000000c8c9197fa5a1e66b371a653c5d18c01fed8d17a8aa92d89b2cbd954b9fd2931fa61abb6676e4851dc9481732c6195610000000000000000000000000000000009026b259e840cb5264f6aad6ebbb09661f5b6d980389817309aef99e4e0cb228d3a7a06e6c25bdb1aeafe5acdc44441911eb1de54fa8ccb746336b681504fd08f995c864a8dae2aa866862f81f0e7850000000000000000000000000000000007bceb74ba86c07d0fca20e4febd3b12b1fe9f786c9a5da0531550244f40261d7ce728498fbfcfe16cc235db6ed42e11000000000000000000000000000000000883104ffcc0d040d70bda04dcf67c1197c39e200d4d9daf5f3c185638a13dffe3dcac94fce4175187dad867e8d2b78c000000000000000000000000000000001404e48e86f199486db7d40076cc8dc4e2aa2c1b6d4bed8f027512e2c71817905b26ff4f0551f9c08a2a7a27b2075b6c000000000000000000000000000000000b789a6addb98ea43c0f9e85831a75b8ee1977936c17929fb45d4c06b4f1ec33b9b41e32b52cde542c9e4b64d27c686cfd0a61dbcb0c657e824cbcf4670a31a95ecbd47a9b93812cd5124f3ac9450c1b000000000000000000000000000000000654e7f3985bf90dd1e3169382690fdc0f804eb6384ce407a060f539804fe6e0451094abaa0dad611c15d3ee52f31a92000000000000000000000000000000000deeec957d58a2246ff8f7b7448f5198647576c16c1717369ad155ae36d5a6bdb42c8d6a1f0a095891fb0890b6203f950000000000000000000000000000000013a01a6ca4c296f59cfa4a5f5399d28af76ffcb8b218c861d5e6dc603e140f730f632028c8da46c823d87bff5ca703280000000000000000000000000000000003698f659e86b96613ca74a480c81e749bce4b74324976c1d241a0911d078926fb2adfcc3f901a7a015a02f525ddbb808118e9c70cc5def8e7d258e05273937c514131f39e0cc9fd2a3620dbffc7ce3c0000000000000000000000000000000016ce72e1798ffd84b52ac664a184c6cf5ce1ca2aa263c9d056355cf610517e9c7bf7f057c342f6e3ae801b84c2082c0f0000000000000000000000000000000010992af1438eec10881b5e2e3fa3b1e91b6b5313ea58dcc0cd2159f8ba6ce5912d81b38956929620e04b3596f6835a6f0000000000000000000000000000000014315dbebd532d0c835e8e85a02c0814574cf040a20c18d06573718223c8ea15b7ea69f0cf342dd09037258398ba4bef00000000000000000000000000000000136d13a83e72525b2d4af54d14d5e21d8bd9bed18543836b02ae0a7e51d433c93aa1943e85f978a8a9ff4454d8c5d120c445931b79e2b826aca02d1bfbb00c2dfb6d30ac2ef97a4ded18243b1afce7730000000000000000000000000000000002c1bf7dc75006c2941b89a2de52fdcdd1b4fbda5b14fe3fc165915b90fd9d93cdb8105898ef59d5b374707f0afaccd600000000000000000000000000000000049a16efcf81de84e443666bf990f6aad2145f9c9c2c61a752e256e8f447dfb27b462e4553544971807f909a666af12f0000000000000000000000000000000000aa4702fb69d791ef958826753d3f74f61c7a591ab94bb6c1bd5d82d94c5877121ecfc1e769d0d16ebe491b775ad96e0000000000000000000000000000000008cd7f2562eca6c53a37382fcdb04be53998f45c2241bfebac3d1fb08d8e1d4df3182f2bd63861d0de72d58072356ccc982ae6de98df906922e660d461009ba6c04cc6497f3645a66385c775b21b210b000000000000000000000000000000000a6b30c4ddb692ae33c903693cdba00ba48efa48e90b9cec9dc747004e57a8d5a05b5522634fc0de306d38c28390dbf4000000000000000000000000000000000601341b3c4057767a910bf30dd16324ad7abcb55b7e98e73584f26d7f87d8a8d24ff2113c12ceb3077bf65e0912b2360000000000000000000000000000000019dc9c50f613470abdb5c763c0272e88e34ed38e617d6757f4e70d05b8ec9f67a023b4ec1363e7e60e38cff64e18f0510000000000000000000000000000000013fb1858f7efeec5fd03d9f7f4513e3e9103c340eee7bfe48ed3cd3dd073b96add9450a17f12e161f1d44669b1b2f813000674ac5d09c6c599173bbe9a43726c120c3a60a96d43954727a2f33ac4320d000000000000000000000000000000000d6c135bfe0fc7af93571a69b7c37ba691f051d69582cf159cbeb0bd59b48342172a82a3eec2e3d440805934e1574f2a0000000000000000000000000000000001b04e56cb3bb221caedd3582943f89a33b955f624f9e473941f1dd987f2898339142a654d11d87bf8bd2fd0fe0d4c1a000000000000000000000000000000000f185fd420b761a1e38d542558b0beffba916f369b37296fdd8878a7c3d2ac9d3ab1d8e45ad799f0d81bb439b5e5058100000000000000000000000000000000002d10ce460c414fa1094ef2b7de8f1ce024b6d086d10067be0fed4e45dc25c8e50bef361d39a2743be1e1ea4fb7e2ef773f8e9637886d795b75e7ecaee512005c1780e7ab17b9f20ae9232595478bb2000000000000000000000000000000001972ea36bae504d7047639ce6e0e6c3b16afe89fa3d6c6b33c910c8d4b70782d8165912e5bfbe8bd84f78f9f23f7f956000000000000000000000000000000000ae55c4fc1c01f1bbdb060191e8551a7ba5ebd3dbadd138202090d7dc6765fd1ef5fe8204ae76a8bcfa03ee5985a35280000000000000000000000000000000018ebed295805e0fc14f1c7b0e6ee12ca48cb7177c1d367a613e0d6cfaaac5128fefce0e8f38d4e2f11ae0d327be466a400000000000000000000000000000000157068d89fe48e77e0f62e3b5b0293423f35c5f4ffb9e0577f5aa49e91cea6bd312a0e65ec08af9c1f53de6499409c8d759d0bab12ac790cc3a16e88f1a108e670681f117d4fc7d01f8c5a2d6ca7fe8e00000000000000000000000000000000120e4a8935c08032dbfc19a43e2a770b12b05cf1dc229e12f683f0d7f604bc13666bf318fcc38038b618ef83c9448b870000000000000000000000000000000004e3f851be46bd85f37c8b1d84507f4ed63ea76bc305cc26a6f4cfa2135d5affcd3b319d9f57619e21c964c6246fc3f600000000000000000000000000000000138733f352029373b19e1c40d5958a04257e2b344770e1bbb8f377bcfb1c7225ae7a8b0b0e57795ec06a08e13c90d7de00000000000000000000000000000000093e85783c556a017829e28bc42b607b1035890fb9743bf0e279df4dd8a695c1dd07a76a213087c3a8a7e614b29b7a1ecce865074a8a41f8a3f40228046c5be68bdb50ced10bb73ac8472f0525302938000000000000000000000000000000000be1ae00f9ba0a2e57f94728508e0029b1bafd52c91ce718ba41790a3541117d1a9f846d68440978cdef016c3b9ae422000000000000000000000000000000001947683154204c9fc93e3aeac17b417453a24d01804e8acbf6f67947f5962dce875f49d05e6ae65384602828784f852c000000000000000000000000000000000859dc1c00b49cd1292cdc65c6aa4b11e27637b949c7db508930c557ee3ce00f98f9cd3dd0f6d73a646d176a91d75c070000000000000000000000000000000015a7a6984b5f42aadebba1e1f4682aaf1a2d01c9ce2afab7fed2269373467787bb1361b493dcdd862180e9159ec2ad5785e2f9597c9b687150864e90ab042f4f012a54d92cf9d2ece09e4a081ec9773f,00000000000000000000000000000000047cc33d9decfd059724bbb014fb9cd95de187e2dd29cf4a9bf9ad06d415e2cacb5a4e49266c13e0c38f2563d1a21d6a0000000000000000000000000000000011c79d93fa870d541e79ad4037c20b85d3cec053587f9df60dc11e7dc2727d79059f75bef76474c09fe61ed10b317cad0000000000000000000000000000000003df3f0db20c5ffea4dc9f4d9333d76141447bba50da18e521e89aae1e63750c71b392570d79e597967dfc9952e903c60000000000000000000000000000000014e83ea645b1019ac2dfafe370f616af0c5baeabe217ac1f9ecf78877717475b25911b339557422526a8163434439923,240480, +000000000000000000000000000000000cd1d25f285c2073175ecad5bba4987cc52012eadc7b19dbaa20fa82d7a6cfb8a52f33469b6308d921eb4b3b23f7022d000000000000000000000000000000001707b67a23d9212d30c06f26f0040c38389b185057e80236d2c828a8d9ade4f72eee1d6eccd78e4f4d71e2c28ee9539e0000000000000000000000000000000008e5c04effd14d915b9afc2083afa2b6d4008cfa0e47144a41982d8b5a8e77922a2609384e2c5d18c871ae24a7d505b7000000000000000000000000000000000f414acb056fff2cd6d9b408adb6eb7f34c8f66a66ee93945a3381d46c2d181613047ca6d4067614c190da444223cab685431a1df7678e49ee049b75ea968ca255ef456dd58cce57b64edffac1ac223c0000000000000000000000000000000008ea841aced2d0b8dd688947648a8ff18d0f6f03f63ee1c331f126dd4fc0da3d386535156b80902bdc1f65add7769cd70000000000000000000000000000000017a32ad2763d99c38c954f62466e78c0332e48875e15afbbe9c78376f1bab12346c73a573738353e2162d3928091dede0000000000000000000000000000000010ea738884dbfe5bc35d031bde9aa4109b1fca529502e236aebb5ad0bf71dd2f3db250d924415b0bfca56519f8ca5d290000000000000000000000000000000013699e29cc1871f51a469898be8b3c732b5cf7860286e655e65bd8176832804d17b48d0ff85eb023360db78162581297b6ccbc0b600f11f1b89061d94c6fbdc9b1d389244fb29a5d140dab8842d44eaa00000000000000000000000000000000004d504e62b2825651381ae862fd33407309851d5291591cd0f541fd092800d709ede00a9134e65ee752eaceb0a344b50000000000000000000000000000000016481efba290c37aa4ecbf940c76ee5df199b0c1f90fddebd2db28120bb5a14dd9f4a067b6d4889aeb683cca0f6ab337000000000000000000000000000000001400c89942cc63417ca4cb05c9d81dda3251e5611c27fc7727c3e803170672f103bff26f7216a0b646533aac3171488d0000000000000000000000000000000019889641be9db08880543ff476b9d4c72167092548ba49a3f3ace4518d3874f4f7993ae7b8cec90f092f144ec9d66c1a54dfe31190469897c30ac3736ab166220dd3702df5bc897835347713d03a8d04000000000000000000000000000000001927fe80adc6dbb581349c603103ad8831e645d9275af8669939b83829182cc6e2a30df2fdeda6d3aa2e2a6126e00ba3000000000000000000000000000000000b6d7934d5ca1098a85a0c60acca075220105e221b802b1be97c2967820bffc2937fc3278ed0f26905c60d44d5fd8dc000000000000000000000000000000000057acc1379f23c0d1d37427d400eb1b0a89f3736c83d3ffd797ae279e01e2acddd84082f13f3c8b8f1bf7c275702a9c700000000000000000000000000000000038dbcd7e08d34c553850a52336991a7d48968e98057e930790d78b5c6368eb2fe01571b60c4aefb653ec04122953d56eff1ceff9e5184dd9fea44da4f07529823dc9b100f776cef6f6881120f7de11a00000000000000000000000000000000014052031b88af979b7edb06c99c2e46bd9df2c862c7e1b71321754841fad67fc3242b51141e49ad86b61344aec913f40000000000000000000000000000000014806a86d078ee9bdde99257b67f50dc2ddf9bbf01dde931742ee6f739aa986cfdca06cd32d23d86f2c14c3b09033d29000000000000000000000000000000000e0561e795d35ceb8bd9e3b276406ec1f697a38ada25d1dbe08715a28bdd3d6ce6e0aac01f7dfc7c2b403850ab213b4700000000000000000000000000000000146a65209b09487e00e356e3b29952280ebc6a06343c4ce14efa0c6281bc2482698bd02295bc35125275ff5f5bb867dfb273e4c6266c1f5cf022902fe1310d2191af91c47995486342bc61cd361eab8500000000000000000000000000000000021592cd7f4cf9cab3be53561c889c9ee865961aad51339f6393dd6a0b7dcc8a7c48b753c947b15cd3add01abd3d76d8000000000000000000000000000000000f9e1a80bad58055a8577700c177889c4d702de04343c1202eaed9485a76493158547b20bcb552b66c42a0c86df809ed0000000000000000000000000000000013908dcff1945cf06f038e3caac9a7fbb3a6466ca18627e93468a875759a2b5599a96834ff21fcd6bfbba82b79536b9a0000000000000000000000000000000001b6354665109c5a64613c3bd7d805b3a34098708f3d41c7b77db031ac6fa0b2d4e2f2f70c84ac78687b0c0f9bf334771342b5cd4ad3179f406941ef6ea15d0aecdf9f6d96dc334c39b7dca89d256d4f0000000000000000000000000000000019394063202186e141dcebce7b8f0f267ba6057a0f993bb1cbe22a5bc528323823bfd1597a87017d478186a18f09a47800000000000000000000000000000000148437bcc43d432d70b47dadac8e738616c97d38d0f84dc132599626612f7bba74988bb23ae47ac15e6f70c059d607ed00000000000000000000000000000000180851594710f4bb5be7ae0104a383081c50f59e4e048614660ab5a4e2661e171510f5b112d8cf97a6af27d56d137c860000000000000000000000000000000000599f3f82f29b493ffe9ee3a8363b9a599a5ef3c9c5c680d4e003f4ac5a7de0562cba8e2a4c6da7d07cbe86c3f7bfb85b36620f65ed84fc0bb344b4b73f4eba4b1680a47b28b47f6d10f9ee82398125000000000000000000000000000000000cfdce7997601afbe484901893a1b5fc0b83e8d238d41d2f889a58fd4d884df1c667a000b53b587df2c42ad46aa2c3e0000000000000000000000000000000000c50bf3e06400cb10494cd09bd89f3c96ff49c9f74dd5325f9489ed6be13b59bd7b0b2351411ac854d430405b8a2a3de0000000000000000000000000000000001db313a34ca4073e4fa2287e234ac32bc579742de22e5218f7873b922f5804894826e6054925a394f125fce850f33ef000000000000000000000000000000000e0627a66d286e8d4d3654b32fc5f552a7ca12f0bd47eb6dee0dde22ee48165247c067a0f4c3d422bf3562d38a3c0cf1249ca9bcf879a770b0a054422a6ea97ae795118ae45532c1523c842696de6d170000000000000000000000000000000005285ba39f5bd981fce2fdf853706d70992acab2dc6d4c4198144fab397392a60d631056b580d0d98f3f350414ce554e0000000000000000000000000000000013bddbc1180f155872376fcdfaff2fb12d3d9645b81bd1475a5323ea855cea820ed7eb693791caa9bd3fa5c66036439700000000000000000000000000000000125644d32df397def58dff875d7e3f14166e765ed49a3991f45b38d74db3985fc7f5052058d85594c8b97afcf850e11b000000000000000000000000000000000fca4662eb1b39f576ee820385fba88ddd2fc01fcfb9d9f874453ad725cd5defb357be028fae97ce71bc5ac26d11c1bac014a0aa616e809b674390b4553bf2d9bf325e73d3a935eba94488dddee4e8950000000000000000000000000000000015b97d7c74c8ec102083b41d7ce5490466e1c0e261b5ea5c756d3f9ae79dd2d8ec6eb5075cfb76dfcf7bfdd80442f7d10000000000000000000000000000000016812f845faf96b8b69ac7a6af3c8947aa25877199e3c12552527706a17b768bbea259ea61ea82c4624a96cbcdf4040d00000000000000000000000000000000123ad55e5cb5ac5bdd3ca0a5afa7c3f8e4b98ad91a205f073fb546fe799ffc57b3c1c3a6209547ffc6ef05fd24be6f5d00000000000000000000000000000000017719f31946aedabe0e9d88ef3f90eb6ceda884f5e3d2ece368373785b2d8bf0f9677731803b25accfcb6cb716e0aa4ab722a1c20f068b6955a44073914c418a082345796912ca634e79983a24ec4bb0000000000000000000000000000000000497e3480d58027c780f47cc35a121ee0cd76c4e84d9a2f9002c04a1c286be990167a0138049ad70467132818f48ec9000000000000000000000000000000000ec0ddf938553105400f70989140ca322d996f48ffb1b35641ca36a6ba9ac1daac1603c100822f80cf62ec3bfb442158000000000000000000000000000000000a0b6ebee28a792df46d2f727af812c15fc91a471e0d8e34b25b26048f3b9606d8375b5b268c40fb04ef8f098e1d03340000000000000000000000000000000017843dd19bfabbd0cfa41fb58e70a8900397d17ccea783087ece90962560f5cf090e8d9eaa873a6a6ebac45219ea97a68b314f83cc3ad501caa44b4c3ca8cf68c70ff6920f445d3a7ada212b6a19ba3e000000000000000000000000000000000b27c82d71f7e4aab9a68596669596df3f62071e921e131ba4d9e59d8d81d370e077e93a4a6a43e059661227f40b38c800000000000000000000000000000000093004917ceb2fb4a1b33960ff74943d520f86e83aa02b9a6c85e4b9a489e9331863cd30cb6ad6f099d03289b4ada5520000000000000000000000000000000016f04e35186c7deaf730708e1678089bf3e73c1164bca24bf8f70c4f6cccd5bbb34bbb5dc313ee428aec4ac9c638a01a00000000000000000000000000000000011052348cec9dc3e85e01abcca5a652461f08a9f5d72b3fc27140a6a571137f0065ed7ccf9ec8cebe314ad9a214d5ed94ffab83099c69845cc87727d459ae300a5725ec53176424ab0ec8bd3f80eaff0000000000000000000000000000000007083dfb0738d58ba8933a1f60283e5da8bf90af5aae4053ca573ee7223d3b80e4bdc30b4a831ce6af9f52f393e9742600000000000000000000000000000000130c627b7d3a527c94cfeba9f514e75eac047e1b6088c082229a8c95d0765a0898ce1e45694ca2c7935bb8e41e44e8b10000000000000000000000000000000009610645b074e652a08f2b89dbe594afa3009d795ef211f7c036a56274b1e1bd69a035c4f356b6b21f69b9cec2bf7c32000000000000000000000000000000001020f3cdef468af700269aa1e9d928e71b8c521f23586c9b0155314f0073da7de04ca41ececd5edcc052af72c05f0e4bb1d80be637e2abd98d0433150e14b629d98fc0918c7dfc179204669ab465e90300000000000000000000000000000000123540047f0768b0af841aa4aeceaf3dac31ea832daed86c8cbd1d33ed0282c6f697d5881f9022af032e90ff82efb6bc000000000000000000000000000000000113daffbe413075f5f4f6fb42f37b6e9d5e5822aa24d6f865792f63e6078584246bcf8b17117385db1d6233974f6ed9000000000000000000000000000000001067b46fd221b6995d25d4bf0adb088e0554d858d4e5d9d6b59e1ae2a7d57188d559b0208918a8944aedd62b1ebd4f4700000000000000000000000000000000087dae77e483d5c0baa37b9b96dad5ca92b5869fa253bffad24dd8747446f7ce60858b52438e58233210d86f470f765fe670a57ce4dcfa680e60ef33ba99c437e4fdb160ea1012de36f4b59613a6af8500000000000000000000000000000000039d09a094d655c139cb9714aa258d9548473162548048b0f07c9317a41a7e5dbaa5aca156992c8a509d4071d9ae4394000000000000000000000000000000000f0273a38b1b9d006efa43c15a53f026587a676912d0275968608519e97994ea9c6a147e377f68b1738ebeaa178f9c1000000000000000000000000000000000132cd92417578d2e46884f1c1a1080b1916c8c8404d2533a4de02bf8575c80ce7e8097c2ddd1f95737355521c0ec21ce0000000000000000000000000000000019adbf09a268a3ed8eff936d25fbe8af2874e44d2580c7941dc14fb89c5da963b468a7088c4a763eac89f4d15deaaf5e54a999fdf391d3944318c54680e69b58ce3778683b6f2c607d64450ed32c6d89000000000000000000000000000000000756dced467ea32c3c425590b7690a45e250e464ac6927ad3f5d2d8d2826961b8dd7572db609375c8d06cc3b9bc3a157000000000000000000000000000000000b79b4eecbaf1d0f8a89f9ef8fc144b3aff38148ae260da7c20e9dd3866d946585df7ed12c8b7005e7b0e1387c9db41d000000000000000000000000000000000afc403b008b70e19f17b1ef37c9c396577a585b6c34b23d09621b891efc00ef9460c3f4b5f3e851ef63620dc06c824300000000000000000000000000000000024e06f3f3b18c026a166c41f75d7bcf699480f5b6811463c27606c9ec1232fd249a46235b7f5b5a2ed3b53231b333150563ae7b444cca7ebaba36b8d59aaa5c0e6c1454a4a2907866247e752e640a7d,0000000000000000000000000000000004f2480d0b7e84a996965b76055f6879aab5bab26656e4e5ca7220adc17f14b5946047484327bbc5952d9f2ffa5f92af0000000000000000000000000000000002f7260c55c500b54df4391a55eb4adefa7d19bcbec82a107fc0d222c898b97360a679a02ab3023ce2ebdcffd125ddf30000000000000000000000000000000002cddfa94c8f6b3f29c2fe018b1f8679d0e98c8c2656f86f327b9cbcba574cc52643ab423b458994a03347460deef6570000000000000000000000000000000014eb4c84f71ef5935e707a11a92ba34780677ac7eb198e160585ad92aa5c1ea21e3a77be940fe344e7e170680e2a8d53,240480, +000000000000000000000000000000000c2c4c039047d297049afd0e8f0375bd4294d628d3a22078d93b684b737e8c4b6ad3ee544ecbeaad6b3c75d8d217f3a20000000000000000000000000000000004c77a2c0943c6f997ce2e785461f8ec253c47273ded4e1af43ae882766ef8c168e66d831abc2b3b3a0849bbc210cbd40000000000000000000000000000000004456a6c267a5cc6b7d9a9f573270855186a1b621cfdc465fe71ddb4d614565d9d36b13985b31396587919226843c6230000000000000000000000000000000009487cdd8a0cf7f40e9087fd3121cb480730f4302339d25fa12128033239662ed65579a59b837bf1bc5fa87db15b15335b59d128b5ac47106b4114cf225dceb621d177141ef5783943f40a54ad94e990000000000000000000000000000000000ba43205e8392168824f77bac344d60c1a9a0b14ab55538c3bfea4a64984cf381a2f61c64f1ba1bcfd8a7973e43f6e80000000000000000000000000000000000e95e5ac415c3e3e7c9feb6e7a2af3e8189afca06ae1fe54bbeb31783810860921ab3c76a475fb227b3c8299e3f1caf00000000000000000000000000000000001e3cb2106a23e77a126013087751c4d2a419a51beedc3a33faa6c933bedb3d34ee9c6450c583642426edb352e04da98000000000000000000000000000000000ab5af4c98aca1fc3fa55355351d12f3bc639662bf8b5b772152988d676b00ef39f767237a2fa3be936e83d1dd77da86a057c0405e24b7373f67197b2109b633a02589711b6a92ff49ca024f893d7ecc0000000000000000000000000000000012f3d927316ba638bd6294f7dd2f3f166d20285ee1662ae4dc145835704a17127078343d26042a5c397bfef31754186200000000000000000000000000000000162893d6252361c340057bcac31986458b8b55a8a4283f5a06ce1730098f9838dad1bca264374e7261bb9d08c177c1820000000000000000000000000000000017264aead0ec41a079827296f3d32c12adfca7cb6c674070d54087438d57b6ccca4822b2337263e60075d469b4ce0ccc000000000000000000000000000000000480cae035bd3bf1b4a4a766bcd5f188833e9587e1aff0e1f10e36ebbf2f3ae76bc0946e7c336efc3ee00bf42e7efbb9677b05905180182427efeb848b2ba2eafbabc7519ab33db14de0746afb607191000000000000000000000000000000000d13375356b1518e37a13b43b7d192eb74bd69636f91c570c41a741a8763c03caf8d13c7364f57c867a4a3983e88060f000000000000000000000000000000000f6f78dffb404faab88ac7373e0c765209c0af80514d438e18393bfcfeb60d9a5e13158d399f43162033571ee4a75dbc0000000000000000000000000000000010c379860638ccf3b6cb8479aa38881b0004197e3e367a1d5ef7c7fcf075689d283b87022e2825b5c789ac6a448467320000000000000000000000000000000002dc392872cf2fcd8e196f10c1ded175300070e4e38aa58c89c81e1aa5faa08d770a5ad90a8295a890551f9329a13cee53e7f69582f4c106ee5bfccba1d5f557736c1b75b6e3777cfde47d552e6bdcac0000000000000000000000000000000010383a21acda7c8f3f3be980bff2d57fa0a5b2dc424164dd2ce53c0b20ca399d6227913b7b550462180b01c231e4813200000000000000000000000000000000078aec90354721f0a31e1377b3652bcb1f388ab36f1866c955f3ea8dfe6ac2c25bc4cea14f54aee71595c2c1bd2dc4910000000000000000000000000000000007dfeec77213d952c183452b98ad936e8854608d950c0c1451262cdc7d6de5aed0db07a8d74b3e8f674967cb4839c4d00000000000000000000000000000000015c09e4ed2ea76d10d196f7a733ccc272b94dc436d6bb5fafad2fae4a96372c2c6f1325d1554746814ae292d8e6b1e3634c87bfb629b817e7ab97def7400b0a83e47af8d628787ff814733fdf34ba8d500000000000000000000000000000000138656fa091cc6613b1fcff04a3efb4f9c393985b2c78fa838eecbbbb8b6dafd88d9c72441f9bc735649480b5187acac000000000000000000000000000000000a35cec4819ca3321917cea5aa589db8cf61882fd1135031dc41a8207a8e71d326312799291b160a646148c382ed086b0000000000000000000000000000000005b6e4c02c9c54630c96271073513cac3a42d47a7272f62a21c7ad4c85c19b60b70d04719626cf4273f6c5691719931700000000000000000000000000000000166a20da734a47d7e28cce8f0c2d679fa6c738a7a1ca9089dc67ba2b1c92a83b024b8991f131e7e8802a617153de4554bebb60069acf431e1671e3d00e4da0d70fa11ed4099b21d45a2b811f99dd9cca000000000000000000000000000000000a4432a544deda931b1f62759320ada2963062e722bc1b748c9bd0d026ffae10f228be36ea0ab076358924f4c06b6feb0000000000000000000000000000000000e955b1b1b28d2044b6be371c58bc85097c77145b239e913bb0729757518c465d9e69338066f7496aa6a2038ea604f900000000000000000000000000000000017ca2a7d52c3a82ab8abf9fc1bc187389b6e4904e161541008e5b3ba0981870e01060d1272a6d59bfcfb294c942403f000000000000000000000000000000001870649a50e0978185551f213eefd9305d33e92b3f8c39752b6ebe18ae86ad97f92acef05971dceee3b3729becea18168b1ee2765e762f1b8c2451270cd5a755758fd733d7922a537aa9f1fd7d0c95960000000000000000000000000000000013713effa20d5039ced751ebafe1516f062f11ee05ffad37281cfee9d7a49ab14c065709832f6674bfbf2c9f379bc9c9000000000000000000000000000000000295f7ef148430209b48c292b024474f05036edfdee082c56aea05a62f1fba3ee7a540955423f78614c8385da8ef60040000000000000000000000000000000007408c97321b6d7c27e5e442a9e35b054e743c34d845874aeb1ccf4e903ca7803ed7fb1288327865f9e0ff0a388e92b400000000000000000000000000000000081808d03722a2d48846a693059c2662dee614f181dc406825544d30a6adc0f9d84a712eff80bddd4a27a036e4bf7359d5009fd559714d5692de5500ec8cae9c04ae1ab1c7c6e08c8738ef22da19ceca000000000000000000000000000000000880b646a674723c15b240ff56d2031e5db724251b1402a68df8b26261ffc9fb60a81abf165c6832137dc7a7293142d200000000000000000000000000000000172354b62bfb8d388b5a984411414738302725a508e8abeacdcb46454371d5e9cf762028fb65921d5c3dd8c67d42a981000000000000000000000000000000000a1af459bc3122dcef78359e468f4094d609ae3da09ca5aa6efb71a7494dafa2373a3906bac1f324d98b3eaa982a27d500000000000000000000000000000000092ac3b47253c7f090df076914cdc08a715faf153e8e365392b4859fca1db14d3f7fb998c97de9ad99b7d0b357252f086330c755ef708d8eb129475785f24be9e7836385ac918c60ad36e80e2f3281b80000000000000000000000000000000003b23eff722c078a781771d8b75d519e7a062ca3e4252ecca877845920158fb20d79a9ce449d9087426b113da0091826000000000000000000000000000000000c9026e8d3fee6282492393db504a2c41db19d8fbb83260624b05ba4107d6cb2c90d645a3c16862b27cc3fcce9bf89840000000000000000000000000000000018b8648d0a42285d474f809519696df9e1ad5c35d8e848ad74fbee37425aee8844a8be8cb4d3331670ee294ddb9a290200000000000000000000000000000000068cad37ee8578f4b502ac2ef4371a10e5432e57fe20d0cb074dc427831872113d3514a0b199d813b796b8357fa2a3dbc2431888d05cae840dde4c26911db1893659fdc345d5433556d9bf75e51fe3740000000000000000000000000000000013200f0aea4c60937be47213b6149b0ae76767f3559e0519f774af4a5d9431e2dd7ea74b42cc3ceb28ccf0d2f01116f30000000000000000000000000000000001c5bff08fd16ecb68f21289a3e7b9a2ec5da1357d604710a18e78ab780f8ef0343d5d9ee7f7988a009329b17e498beb00000000000000000000000000000000125453772eb9d1335ce4dbcc8f2ab8426fe89a0e49fec51d4e96718a38570aa82dbef452368141be2df260fb131c50b2000000000000000000000000000000000432cdd445519775b9914a986a0941cc829b4a15cd361df9ae7129547b24f7a6a15cd8fe9393fa1551db2d761a208b8ec9a72369cda74e5c86c6559cbc4f4db1b3ab24c5150c7decea862ede3c02c505000000000000000000000000000000000396cb6d7b44f92b716ed02985d351b4e8cd1bbb95f239e4f29d7379428470be395e2faeb8e3a910007aaa490d3c336d0000000000000000000000000000000000ad0c0623fdf50c2b504777554dbab3cde1b9705e976561873d7c22b81f49c7654a7c76e558fad1518ed73a0d3c3570000000000000000000000000000000001241d5bed68e02a2ddeb3ccbe109a161abe81edd7affb72182c5163851211c4763e6aecf766053b61ce575de893985f800000000000000000000000000000000183696d2a48feef6088f4e9f75a5055e8c54b3813658b593958490ddd4245ac495a8ff966861b20f26047f07fa8609a0c2f50989b04fc29c4c4a0090fb11e860c15f89a66f3bb8281e4678ba63ff3f9a000000000000000000000000000000000fe0ce41aa9e7cb2bcb4e01721b7b1d99fca4e9b7c4df09bec00bd346fc57c25118ba70d5333b7f3eef2659c64520a470000000000000000000000000000000005c932e09c62b7ddaf3f5c420c60740befa7cdff5bb812e0f089c45098d71b57004b7a207f0cdd34daaa3282cf6e9f7e000000000000000000000000000000001874200ead9776c1ecd6a54a57e5d0f9577910a4b3afb9b051622f658fe3ef6cc5070af60e7ef910562720e9716158d6000000000000000000000000000000000c2c657e58e400a67e59deee8c28234ff4688e781a2f6f2f0d0b186a5e4012695a522dfa0770cfd543f55939a05e20b09fc9abf1c76ff11ab538f46ce768ba597eb5f2f63073ec67e8de10aa1d666720000000000000000000000000000000000f0b561e5860321249b9ff434c604d26c3275824fc4ab9c1ce5c5858605ddaafae83ae27e523bf6006932f6c7f33d0a7000000000000000000000000000000000b47aab85bbd909599aa85c5eda363b67790ac6729fd8b1f4f53f66dd796cf2fa3496407b1bfaea4dc8eae53519054e70000000000000000000000000000000000cab1ebd23bc05c53bc9e8481c469eac3ee1b140af545bebed10a8fe50698d2ed883219881929207c0addf2f687198d0000000000000000000000000000000007742de55b799950e6f786f4eef45d0fb67e0475272ad68a183135b70047abab6c2ed51ede16c39be7b986df334e9e75d4167723682bc0e7476797b3be5e14b8de3e4e23b4ca33c50a2096cda8766dd7000000000000000000000000000000000923861332988bc843a65ec5dd4637f9dca8a15e71b82c780fe60d768213d118d8948ab554e30bb9253e900a9b7d87f200000000000000000000000000000000132b1faef49e7966a05783ba526e71134bfb577b13116548352da37e91e617d7c72ed2645e672ebbc517e079247dfb0e00000000000000000000000000000000000a46a8893a194ebfe077afd05fb25d4680f1e4991a3ec29475fa5651d086d20b38136155a65a4c70af31de5a78af59000000000000000000000000000000001344eb957594028b4228cbdb8efb03cc7cf49ec43b2ca5481eae1df6f2df3d5be9a7c4e4e78f8c39be546e29a83c92f49644c3727f78dd12811092190d5d10adcd5b9fc581dd783c97d4e7b5130f309a0000000000000000000000000000000012d7111303563a6358e5ce9155d7a153b5781062c2f6b919efc67ddfb4c61ef03be8828ca6339397b84763a5f8a7e8330000000000000000000000000000000010a2a0ea9973728d3fb1b5906ee84b2635c687c11398ebf605cad30216df3b7b4e3ee1653d4b323a690e6ba614ebec30000000000000000000000000000000000b93d5de37b892d4de9407a820c73ecfd6cd9fa565db82e7e8c14c8406823f705ff0adf6bd6add5ddc5f72c91e52e840000000000000000000000000000000000dcb320ceba5436df8f099c5a77f34376c96d830f5e8ab80667d156d89f6bf8998c148ef9a53847ed395871ab86f6d280df9846c84354ab7f947caca7800e12e38d8e6651527e6831f4d8b1bd66c4f3d,000000000000000000000000000000000ff3e299e6b9fc488d6db991cf9d226d330846e87c4a5cd3e5d4ac955bc2c3c2d1ee5ff230098b48f594d256495f489800000000000000000000000000000000097fdb8fc95d64c7070d8c71e0fd2a933d1e1ad3fefa230f079bc5743828317cd1b0c6d4253ba9c3c6ec6b12d53afa700000000000000000000000000000000002448bbb179d82eaa453cd5452752b9a083b52694cc65c5d9b47f72eff118d7aa06b0fba18eafe93d2937d7399c001af0000000000000000000000000000000009dec4c0aff2a46b07855c87e08832811633709ddfbbc30dbb7e78b3de43bd95e383baa6ff4c58188f0c7643e0ea5a40,240480, +0000000000000000000000000000000008afabec8a9985cbbc6246825785654c1d2eb7da5a01f76c4af4d0096b9baed3c33dbe492d14a6f9e762f06eb3d198f800000000000000000000000000000000027c592315dee4bcc892acc6f41a6eff5219c308253f7cd715d0e4a32c03c6d0d0e8568e146e9e799ac3025486c77fc30000000000000000000000000000000015b4ee27a3aa518a1ec1b447bb8f9128301c85b7176296d68dad3339b1dee78715b2f031a7fb6ba376145c97ceafeef60000000000000000000000000000000004b7e30ec7cc024ced863ce511cef3cabe954a4e5843dd636d776645a44225a36ed7e153ab5bf5d18f23c6444751875c8a71abe11a893fce872f6b8a020b6d84241df03eb934b50cbf3571df4800a83300000000000000000000000000000000119949d36d8d8e2bc1c26ded5f5fb01225a980a28b934ed3862480dc9297a3758e0f08ccaab3a09b5e5c0e4215e3246c0000000000000000000000000000000004a82dc22316ee6af39d937b662d1f1f2dc855c2ca8f33ec3274d833e87d594633fc7fab247911e0f46564397910d6ce00000000000000000000000000000000196900a09d8504ed960d41f4a8a2cde2e5dac61b008d3f6eb47e86d7b2ce6fcdc0f85157e3ab1571094d9fdaa75d0d500000000000000000000000000000000010c52ef9407eb4ec57844aebbcc3ea5000b1940d035dcc2a873327affaaabdd79e3560cbd29c63ce04f6279056d6eed1bbf28e5bca314391550d3a0fce50b1220965860e72c8c3865a2d4c599d31d3f1000000000000000000000000000000000e43655ae05dc6cfa93113dc26cea895d1c5bc73f20454c7b441dbc5ac80035b290514b13b31b41931ea5336d8d9a6a7000000000000000000000000000000001199a873958c63147e6b82625dfea15ce90dd41ceb4e315f67221eb874ef32c6a2953412e7e981659c72239a7a72bfe6000000000000000000000000000000001845af5936b4d7487ffe59137ba2f86daea3770cf37fd560969ee48243389941a1072205e049ddaa06c0ac56b7edc8930000000000000000000000000000000003cc831177f24614f93a118b896434105f05a277051a852fb9973a775fc54f779c2a1f3d64c457e5231dc22d6aef606b58b208a6845aeb2bf31999042c59b7b130a7ce5297e88023953b1aef63616fe400000000000000000000000000000000005e63584bc85ba58615985f6a466afe05268e545e0062cd7214e0b6fc8b87537c745b754cd9a1144948bc88b3c43acd000000000000000000000000000000000635b6a49090ccede3ed2ef203f0ed164783e3df4d9a7d93319515cb9230bd841b61a097f39e30175793b3e934d8e426000000000000000000000000000000001861e65f47a9da1584c45bc79a66045d86bc1709c2d1cf6cd2930a9fcc8c4efaa6536b5015be8d54789e8f574f93f9f70000000000000000000000000000000009290ce63d55eb436794acf11be9d896f03e7608a1bc8528f61ec9473f054bc9fbbda1072440e58e2f6ba080a01180173b53b6cf9e0ce1661c4960283be790abf956c2d6433529b8f3a32b92b227aebe0000000000000000000000000000000018feed9500bff884d2bb58554da2180c68267b6d3a45c2c7cee4c3f8524252d3faaa5eff971bf40123587e669fe66bbb000000000000000000000000000000001441bd3b58b4a4a87c2459f873c0692f5977b775af984bab46dd76cb9f775d2faebcb77b2854c9f1faa33f6c5de61c6a00000000000000000000000000000000123a890c3362c77e5b5cf9846d9c9e43fb3242d5a831e640ad080993fa0547854c8d11cc22f7f7b426528bf1154d2300000000000000000000000000000000000ff4a59ea98d13cfd353ae61e18d3c7018688f755561e6a1da5f09acc4277e8d49645087115acc64f992ea778a11f39bb049228435ade4c4c565e65f39f13a84c747c312afcdaff352560b9fb3cfebcc0000000000000000000000000000000006b019d005141e82393a2ca04469d1f6fd7b9456001ffef4c34eff6b2e91df58e99fd07944f52b108bd41ab6c4d6bbf200000000000000000000000000000000109ae87042029856befff0c916db5437e1e058a96f2970d8816b3becc93a1a50d6d336d5451303715f3e272147a36caa0000000000000000000000000000000000fc381b8dc9dc02d34db13e34732a10d0dfcf676c224a05a3bffd888b0af7c415b38af0b6afe6b464ffca42947c6ee5000000000000000000000000000000000087040d09c39ccd06c9ecc360fa02147a32e8036ad6e4b6bdf5b3883722a4e5a887dd022d53706d2585fe558696be6656197f5ad17062d2ecbdc8887bcdd32e5ed4c48cefd9e14d622a0b800d970330000000000000000000000000000000000e35c27b29df0fa9298bb9ab6a38b3450782223e2115d79152f9baa924d762d583b3ebe88e42f33028814ec78e5b319d00000000000000000000000000000000190c65667627a16f0af0ac7f23af0803bca810f3986b906b7b4f126d98473d52badf45e90e2e45bb390242fa8c40135100000000000000000000000000000000103f0283a5673c16bcc0f74f259c2eb077061947da04e467dfebf62aa005491e32b85cb73418b624a30dbaa01672921e000000000000000000000000000000000465466955c908607191faf15f0768dce42488c488eb4a065977f21ac7484766bc0abf23961ea2ba46dcc04956abf6c7721d9d7fe10104cafcad71307e785321ab87b2b69593535caecbf0e166cfda5b00000000000000000000000000000000082346e352e845a54cd4267f93b85b2c8623d4650e00c1c56082b73ee31f63588d2c117d3cdecc0378fbbf8956b082040000000000000000000000000000000001a7f43c2bb19cb32345c43c950536f8e85815b86364f278f6ec8169eca80917c2b8fc08d59b20cf55f25dc468e7bd7f00000000000000000000000000000000085a5cb020df10f9b4c7afc01b1d11700579dec1e85e766507def2e6cf5b714174f7be9cce3b18533a5ebfeec2b4e481000000000000000000000000000000001836d7506d1cc984fb777b8ee935d6f5b110644f59e96ff44d8329336d59a3e1d2b53a05d35e97f634baa4fdc11a6cd8461531ecb61365908019c1e8074a4c322df2b356eea3f3eea9aa1e0e1fc5525e000000000000000000000000000000000c1c59828ec6257a02679cff0bee0d665d449d2a158bc6d877e84cc0fe2161c297dde09b778d5e1249c515833e483004000000000000000000000000000000000f5e82589bfb7781e4110f1486752b00cbdf96cdf4191d75053c6d6d646e1c989add011361031a11559e156d64139fbf0000000000000000000000000000000015053afa7fb2b4e4b70f3c8a570fef8288fdc22dd951b6ba8a40b6087b9ab04ede21f0ddfa84d6d18914041bcf244c110000000000000000000000000000000003f399800cba51ab35624d866831ab6506392cb3acf549787153ffaf08cc451acea46c7a612821dd96c45f8b75133d88569c1c1ae2d18bbe36ed50db1bf30957802b09a982fbed49d4968815552e010d000000000000000000000000000000000e26242c8f73116079369ef4265f624abd4377e4e3485c28197663de9de9f5618c3b6ee602ff6bebd1c242aef7295b2200000000000000000000000000000000066ceb3ea6067220bd28fa1164237782859d27c1d3087a42b4d09bcc343611e4ed2be014a27f5b394c67643dc00f57cf00000000000000000000000000000000157f9d30de52110ea7a2a35ddfe67d9fad7223c5e3307e797dd0df3621520a421958a2835205e3c4777923f47d47e5310000000000000000000000000000000016ebb41beb85b9489a6d5482f8a3330a5c5c5e5718e8efb8b67362f9d8e9c313e9e563275ba38c207c5bf3d89c406ea62061d33b2f7e786effbd2e93101a56ba1bb62c1a773a08b72ca82f5183bea35b0000000000000000000000000000000005d1c9109b5b7409f94ae3f7dd9e8ae4908a9b378fea4ea284cbd33d1e59b605577b63892aaa8ec14d415f34e22fec520000000000000000000000000000000005afed05e62599f20f7eca019f41d770c630cf6359cb5601464be821691fba5205c16e7b580e6881047214f938e5104b00000000000000000000000000000000105637a2aa4725d8e080dec3b731a111ea4c94b79f898dfd51f645501ef0c8d68ea8e80fde28ff96e927e44306ebbb1d00000000000000000000000000000000080cfeea754474ceb37973234d5dc3269f8ca99bd862d4d2d1a602321fc709945a3209e5ff2cc962cfa6d03017c9a1354129b150752d2d5551a622231ab067931678454aaeb23f76168219406f0d50ee00000000000000000000000000000000137762ea5c80033aaf17570451b15a062feedde810f11ebdbe9a79a3275dc12613e0505835c122bd5f9afea7dba84203000000000000000000000000000000000d89c04e45e60769a63fcd73df2a138c457bb549195f2c4eebb3be1ea46149f286756795be8328b5b886f497d8167b34000000000000000000000000000000000be43d515083c8c10f467618685a43d4d5f6457204bacd278445943a9f44f7189b561a0e1bc59d2757fcfab2e3f93a4a0000000000000000000000000000000011a52583227c6dcdc1784d3633fd584612a9f3bbc1922477396dcd5af84413e5e9382a34a71b3a72491ea09fab2fc6bf366c32d5d3c132f32a6ac3cfe1dabb649c59ae224338f747ad98b193e8346729000000000000000000000000000000000073acefe33525dd2d5204cce72371ed82c7e4b58d1b4e7f4b4994f9c58b02d9d6206fefb3552446b6b355e860ace43c0000000000000000000000000000000007344eaeaae71e17930e769e02bcb4f44ddf3d040ffa0b081f25901cc125a37a58a6a5d13e7b0ba493802ccdaa054e29000000000000000000000000000000000a65fec6ad29ec3eee9ddc7ded2297f49d03ff18a255f1e6d29d2a67c20713f319d79d513af0c58ae3cddfd1f6240ff50000000000000000000000000000000019d5f00d9e2b271f4e9ac779a096386f08ae124f77fb8183405d48ea7f16e685805442dc67a392aefc643ea95b4f1fcfd997516cac28a3968ac6946b5bffaace0856a52e38fdcca11ddfa16cf5a568f50000000000000000000000000000000018230bf1a873aa04855af1426da30f1b3ef4b64eec613b9f660222e3827b325c318baea031b463c7e9f775165d22ec8f00000000000000000000000000000000017faafa1294fac53e1de8cae9601acc62d76a5f01a39ce49d65f3f5d2cd5cca33eb90bb4116b3ea36f912ae2b81b6cf000000000000000000000000000000000fc3ef5ea59849a87fcd45500989f1744cb5570ee88e34a952cec32cea2eb5900b64d8d0d04ef5c51e8fdcccd46412490000000000000000000000000000000001c53aa8aaae8422fa4fddc86cacdefa89c37592c8e67e472a23627514623a90901a619af79e93561a0dc65215837274e881ec65fdc2f58e46d3ee45a06d0c5ac844ee5b62872c7ba21f6b48621a3371000000000000000000000000000000000e3db6885c2db9244548e11b8c49b73f85e4104b413f54308497262fdff1957495859830114528a22c45d39a554ba82700000000000000000000000000000000181b1bfe2d9a1c563e73356d73f4ed3e7061a79c610bc97c911ab1a0213d123c9f83ed6706e862087a796ce14c5cf53d0000000000000000000000000000000013f5fdceddce771588869b945bd6025e5ce485fe78a362356720b474b83998f27e535cfd8d33ee51cfc68e5d514f915c0000000000000000000000000000000007e8fd7ba457a3cefd50c641847425cf2262deb1d6945a0bd740eadf38dcaa616edc48c3912508d663349f089b8b56fadcd9b95e49473277a665ca0f9a8309df9ed6ee4f25d803aa967fb8f688273e650000000000000000000000000000000004b20b0408da7b704694b47607928a655077015f2174fe01bac9a0b3a61dae087b0b593f58d2947d8d84f75bbfb327c900000000000000000000000000000000106d623b2007c5d7128e03e540325ba763e992a651e2e5c78936f82ee2ff72d89a1a914345486cd0a04440c75beb190b000000000000000000000000000000001847348e5ef429cfdf1ba4d265d8c5ebcbec3d5dd4611ba36e2754fbd3d327273bf2eb7b7ba4b3888d059dc87f034739000000000000000000000000000000000bcb0a9dfe5189bc965e9721407b4cb3ed4171510aa4d4e5d5f0823a1c2827643e1278f9c0ee960c54ef8f6c208eee7b334582482a9038ab906880e43a4a9d39e73b6c63604eba0c8f6399eb5c288638,000000000000000000000000000000001205b70b29ee04212589f8a70a71e004f517d3354e714c1b4fe42cf93faf1a8ed40dbc1b5089ddb53bb052c9cb74c0e8000000000000000000000000000000000f619082734dd9de653b61cf2fb927199f228637db70797bd2a21fdd48b6ecd4c4f712097037534024880a436fdd63680000000000000000000000000000000000592eca560be6ae256abe1796f7ec394a8085c127437f6590c8d41501a482c61456392cb320b9e801044dcba7802df9000000000000000000000000000000000a6d20b8009708ca01a274aed6dece732c5eed5aae5e4c2f3793b5fa1f8cb8c95037ce387bda2e7476e9c493507c7fbc,240480, +000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d93884d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf494c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e8964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b890000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e2787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c94400000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f300000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46eaaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d10000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12dac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5bbb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4000000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb81596fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f818767200000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c000000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e977955054196b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b660000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a93b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b38173173459836dd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b7010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9300000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd394c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a65905400000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ecf0000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bdb3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746,,,invalid input parameters, invalid input length for G2 multiplication +0000000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d93884d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf494c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e8964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b890000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e2787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c94400000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f300000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46eaaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d10000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12dac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5bbb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4000000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb81596fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f818767200000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c000000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e977955054196b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b660000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a93b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b38173173459836dd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b7010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9300000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd394c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a65905400000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ecf0000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bdb3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746,,,invalid input parameters, invalid input length for G2 multiplication +,,,invalid input parameters, invalid number of pairs +00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220f0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d93884d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf494c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e8964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b890000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e2787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c94400000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f300000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46eaaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d10000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12dac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5bbb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4000000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb81596fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f818767200000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c000000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e977955054196b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b660000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a93b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b38173173459836dd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b7010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9300000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd394c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a65905400000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ecf0000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bdb3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746,,,invalid point: point is not on curve +00000000000000000000000000000000039b10ccd664da6f273ea134bb55ee48f09ba585a7e2bb95b5aec610631ac49810d5d616f67ba0147e6d1be476ea220e0000000000000000000000000000000000fbcdff4e48e07d1f73ec42fe7eb026f5c30407cfd2f22bbbfe5b2a09e8a7bb4884178cb6afd1c95f80e646929d30040000000000000000000000000000000001ed3b0e71acb0adbf44643374edbf4405af87cfc0507db7e8978889c6c3afbe9754d1182e98ac3060d64994d31ef576000000000000000000000000000000001681a2bf65b83be5a2ca50430949b6e2a099977482e9405b593f34d2ed877a3f0d1bddc37d0cec4d59d7df74b2b8f2dfb3c940fe79b6966489b527955de7599194a9ac69a6ff58b8d99e7b1084f0464e0000000000000000000000000000000018c0ada6351b70661f053365deae56910798bd2ace6e2bf6ba4192d1a229967f6af6ca1c9a8a11ebc0a232344ee0f6d6000000000000000000000000000000000cc70a587f4652039d8117b6103858adcd9728f6aebe230578389a62da0042b7623b1c0436734f463cfdd187d20903240000000000000000000000000000000009f50bd7beedb23328818f9ffdafdb6da6a4dd80c5a9048ab8b154df3cad938ccede829f1156f769d9e149791e8e0cd900000000000000000000000000000000079ba50d2511631b20b6d6f3841e616e9d11b68ec3368cd60129d9d4787ab56c4e9145a38927e51c9cd6271d493d93884d0e25bf3f6fc9f4da25d21fdc71773f1947b7a8a775b8177f7eca990b05b71d0000000000000000000000000000000003632695b09dbf86163909d2bb25995b36ad1d137cf252860fd4bb6c95749e19eb0c1383e9d2f93f2791cb0cf6c8ed9d000000000000000000000000000000001688a855609b0bbff4452d146396558ff18777f329fd4f76a96859dabfc6a6f6977c2496280dbe3b1f8923990c1d6407000000000000000000000000000000000c8567fee05d05af279adc67179468a29d7520b067dbb348ee315a99504f70a206538b81a457cce855f4851ad48b7e80000000000000000000000000000000001238dcdfa80ea46e1500026ea5feadb421de4409f4992ffbf5ae59fa67fd82f38452642a50261b849e74b4a33eed70cc973f40c12c92b703d7b7848ef8b4466d40823aad3943a312b57432b91ff68be1000000000000000000000000000000000149704960cccf9d5ea414c73871e896b1d4cf0a946b0db72f5f2c5df98d2ec4f3adbbc14c78047961bc9620cb6cfb5900000000000000000000000000000000140c5d25e534fb1bfdc19ba4cecaabe619f6e0cd3d60b0f17dafd7bcd27b286d4f4477d00c5e1af22ee1a0c67fbf177c00000000000000000000000000000000029a1727041590b8459890de736df15c00d80ab007c3aee692ddcdf75790c9806d198e9f4502bec2f0a623491c3f877d0000000000000000000000000000000008a94c98baa9409151030d4fae2bd4a64c6f11ea3c99b9661fdaed226b9a7c2a7d609be34afda5d18b8911b6e015bf494c51f97bcdda93904ae26991b471e9ea942e2b5b8ed26055da11c58bc7b5002a000000000000000000000000000000001156d478661337478ab0cbc877a99d9e4d9824a2b3f605d41404d6b557b3ffabbf42635b0bbcb854cf9ed8b8637561a8000000000000000000000000000000001147ed317d5642e699787a7b47e6795c9a8943a34a694007e44f8654ba96390cf19f010dcf695e22c21874022c6ce291000000000000000000000000000000000c6dccdf920fd5e7fae284115511952633744c6ad94120d9cae6acda8a7c23c48bd912cba6c38de5159587e1e6cad519000000000000000000000000000000001944227d462bc2e5dcc6f6db0f83dad411ba8895262836f975b2b91e06fd0e2138862162acc04e9e65050b34ccbd1a4e8964d5867927bc3e35a0b4c457482373969bff5edff8a781d65573e07fd87b890000000000000000000000000000000019c31e3ab8cc9c920aa8f56371f133b6cb8d7b0b74b23c0c7201aca79e5ae69dc01f1f74d2492dcb081895b17d106b4e000000000000000000000000000000001789b0d371bd63077ccde3dbbebf3531368feb775bced187fb31cc6821481664600978e323ff21085b8c08e0f21daf72000000000000000000000000000000000009eacfe8f4a2a9bae6573424d07f42bd6af8a9d55f71476a7e3c7a4b2b898550c1e72ec13afd4eff22421a03af1d31000000000000000000000000000000000410bd4ea74dcfa33f2976aa1b571c67cbb596ab10f76a8aaf4548f1097e55b3373bff02683f806cb84e1e0e877819e2787c38b944eadbd03fd3187f450571740f6cd00e5b2e560165846eb800e5c94400000000000000000000000000000000147f09986691f2e57073378e8bfd58804241eed7934f6adfe6d0a6bac4da0b738495778a303e52113e1c80e698476d50000000000000000000000000000000000762348b84c92a8ca6de319cf1f8f11db296a71b90fe13e1e4bcd25903829c00a5d2ad4b1c8d98c37eaad7e042ab023d0000000000000000000000000000000011d1d94530d4a2daf0e902a5c3382cd135938557f94b04bccea5e16ea089c5e020e13524c854a316662bd68784fe31f300000000000000000000000000000000070828522bec75b6a492fd9bca7b54dac6fbbf4f0bc3179d312bb65c647439e3868e4d5b21af5a64c93aeee8a9b7e46eaaee7ae2a237e8e53560c79e7baa9adf9c00a0ea4d6f514e7a6832eb15cef1e1000000000000000000000000000000000690a0869204c8dced5ba0ce13554b2703a3f18afb8fa8fa1c457d79c58fdc25471ae85bafad52e506fc1917fc3becff0000000000000000000000000000000010f7dbb16f8571ede1cec79e3f9ea03ae6468d7285984713f19607f5cab902b9a6b7cbcfd900be5c2e407cc093ea0e6700000000000000000000000000000000151caf87968433cb1f85fc1854c57049be22c26497a86bfbd66a2b3af121d894dba8004a17c6ff96a5843c2719fa32d10000000000000000000000000000000011f0270f2b039409f70392879bcc2c67c836c100cf9883d3dc48d7adbcd52037d270539e863a951acd47ecaa1ca4db12dac6ed3ef45c1d7d3028f0f89e5458797996d3294b95bebe049b76c7d0db317c0000000000000000000000000000000017fae043c8fd4c520a90d4a6bd95f5b0484acc279b899e7b1d8f7f7831cc6ba37cd5965c4dc674768f5805842d433af30000000000000000000000000000000008ddd7b41b8fa4d29fb931830f29b46f4015ec202d51cb969d7c832aafc0995c875cd45eff4a083e2d5ecb5ad185b64f0000000000000000000000000000000015d384ab7e52420b83a69827257cb52b00f0199ed2240a142812b46cf67e92b99942ac59fb9f9efd7dd822f5a36c799f00000000000000000000000000000000074b3a16a9cc4be9da0ac8e2e7003d9c1ec89244d2c33441b31af76716cce439f805843a9a44701203231efdca551d5bbb30985756c3ca075114c92f231575d6befafe4084517f1166a47376867bd108000000000000000000000000000000000e25365988664e8b6ade2e5a40da49c11ff1e084cc0f8dca51f0d0578555d39e3617c8cadb2abc2633b28c5895ab0a9e00000000000000000000000000000000169f5fd768152169c403475dee475576fd2cc3788179453b0039ff3cb1b7a5a0fff8f82d03f56e65cad579218486c3b600000000000000000000000000000000087ccd7f92032febc1f75c7115111ede4acbb2e429cbccf3959524d0b79c449d431ff65485e1aecb442b53fec80ecb4000000000000000000000000000000000135d63f264360003b2eb28f126c6621a40088c6eb15acc4aea89d6068e9d5a47f842aa4b4300f5cda5cc5831edb81596fb730105809f64ea522983d6bbb62f7e2e8cbf702685e9be10e2ef71f818767200000000000000000000000000000000159da74f15e4c614b418997f81a1b8a3d9eb8dd80d94b5bad664bff271bb0f2d8f3c4ceb947dc6300d5003a2f7d7a829000000000000000000000000000000000cdd4d1d4666f385dd54052cf5c1966328403251bebb29f0d553a9a96b5ade350c8493270e9b5282d8a06f9fa8d7b1d900000000000000000000000000000000189f8d3c94fdaa72cc67a7f93d35f91e22206ff9e97eed9601196c28d45b69c802ae92bcbf582754717b0355e08d37c000000000000000000000000000000000054b0a282610f108fc7f6736b8c22c8778d082bf4b0d0abca5a228198eba6a868910dd5c5c440036968e977955054196b6a9408625b0ca8fcbfb21d34eec2d8e24e9a30d2d3b32d7a37d110b13afbfea000000000000000000000000000000000f29b0d2b6e3466668e1328048e8dbc782c1111ab8cbe718c85d58ded992d97ca8ba20b9d048feb6ed0aa1b4139d02d3000000000000000000000000000000000d1f0dae940b99fbfc6e4a58480cac8c4e6b2fe33ce6f39c7ac1671046ce94d9e16cba2bb62c6749ef73d45bea21501a000000000000000000000000000000001902ccece1c0c763fd06934a76d1f2f056563ae6d8592bafd589cfebd6f057726fd908614ccd6518a21c66ecc2f78b660000000000000000000000000000000017f6b113f8872c3187d20b0c765d73b850b54244a719cf461fb318796c0b8f310b5490959f9d9187f99c8ed3e25e42a93b77283d0a7bb9e17a27e66851792fdd605cc0a339028b8985390fd024374c76000000000000000000000000000000000576b8cf1e69efdc277465c344cadf7f8cceffacbeca83821f3ff81717308b97f4ac046f1926e7c2eb42677d7afc257c000000000000000000000000000000000cc1524531e96f3c00e4250dd351aedb5a4c3184aff52ec8c13d470068f5967f3674fe173ee239933e67501a9decc6680000000000000000000000000000000001610cfcaea414c241b44cf6f3cc319dcb51d6b8de29c8a6869ff7c1ebb7b747d881e922b42e8fab96bde7cf23e8e4cd0000000000000000000000000000000017d4444dc8b6893b681cf10dac8169054f9d2f61d3dd5fd785ae7afa49d18ebbde9ce8dde5641adc6b38173173459836dd994eae929aee7428fdda2e44f8cb12b10b91c83b22abc8bbb561310b62257c000000000000000000000000000000000ca8f961f86ee6c46fc88fbbf721ba760186f13cd4cce743f19dc60a89fd985cb3feee34dcc4656735a326f515a729e400000000000000000000000000000000174baf466b809b1155d524050f7ee58c7c5cf728c674e0ce549f5551047a4479ca15bdf69b403b03fa74eb1b26bbff6c0000000000000000000000000000000000e8c8b587c171b1b292779abfef57202ed29e7fe94ade9634ec5a2b3b4692a4f3c15468e3f6418b144674be70780d5b000000000000000000000000000000001865e99cf97d88bdf56dae32314eb32295c39a1e755cd7d1478bea8520b9ff21c39b683b92ae15568420c390c42b123b7010b134989c8368c7f831f9dd9f9a890e2c1435681107414f2e8637153bbf6a0000000000000000000000000000000017eccd446f10018219a1bd111b8786cf9febd49f9e7e754e82dd155ead59b819f0f20e42f4635d5044ec5d550d847623000000000000000000000000000000000403969d2b8f914ff2ea3bf902782642e2c6157bd2a343acf60ff9125b48b558d990a74c6d4d6398e7a3cc2a16037346000000000000000000000000000000000bd45f61f142bd78619fb520715320eb5e6ebafa8b078ce796ba62fe1a549d5fb9df57e92d8d2795988eb6ae18cf9d9300000000000000000000000000000000097db1314e064b8e670ec286958f17065bce644cf240ab1b1b220504560d36a0b43fc18453ff3a2bb315e219965f5bd394c68bc8d91ac8c489ee87dbfc4b94c93c8bbd5fc04c27db8b02303f3a65905400000000000000000000000000000000018244ab39a716e252cbfb986c7958b371e29ea9190010d1f5e1cfdb6ce4822d4055c37cd411fc9a0c46d728f2c13ed00000000000000000000000000000000001985d3c667c8d68c9adb92bdc7a8af959c17146544997d97116120a0f55366bd7ad7ffa28d93ee51222ff9222779675000000000000000000000000000000000c70fd4e3c8f2a451f83fb6c046431b38251b7bae44cf8d36df69a03e2d3ce6137498523fcf0bcf29b5d69e8f265e24d00000000000000000000000000000000047b9163a218f7654a72e0d7c651a2cf7fd95e9784a59e0bf119d081de6c0465d374a55fbc1eff9828c9fd29abf4c4bdb3682accc3939283b870357cf83683350baf73aa0d3d68bda82a0f6ae7e51746,,,invalid point: point is not on curve +00000000000000000000000000000000197bfd0342bbc8bee2beced2f173e1a87be576379b343e93232d6cef98d84b1d696e5612ff283ce2cfdccb2cfb65fa0c00000000000000000000000000000000184e811f55e6f9d84d77d2f79102fd7ea7422f4759df5bf7f6331d550245e3f1bcf6a30e3b29110d85e0ca16f9f6ae7a000000000000000000000000000000000f10e1eb3c1e53d2ad9cf2d398b2dc22c5842fab0a74b174f691a7e914975da3564d835cd7d2982815b8ac57f507348f000000000000000000000000000000000767d1c453890f1b9110fda82f5815c27281aba3f026ee868e4176a0654feea41a96575e0c4d58a14dbfbcc05b5010b1000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000103121a2ceaae586d240843a398967325f8eb5a93e8fea99b62b9f88d8556c80dd726a4b30e84a36eeabaf3592937f2700000000000000000000000000000000086b990f3da2aeac0a36143b7d7c824428215140db1bb859338764cb58458f081d92664f9053b50b3fbd2e4723121b68000000000000000000000000000000000f9e7ba9a86a8f7624aa2b42dcc8772e1af4ae115685e60abc2c9b90242167acef3d0be4050bf935eed7c3b6fc7ba77e000000000000000000000000000000000d22c3652d0dc6f0fc9316e14268477c2049ef772e852108d269d9c38dba1d4802e8dae479818184c08f9a569d8784510000000000000000000000000000000000000000000000000000000000000002,,,invalid point: subgroup check failed diff --git a/evm/src/test/resources/org/hyperledger/besu/evm/precompile/invalid_subgroup_for_pairing.csv b/evm/src/test/resources/org/hyperledger/besu/evm/precompile/invalid_subgroup_for_pairing.csv index b0c4d6de21b..acc3197d1b1 100644 --- a/evm/src/test/resources/org/hyperledger/besu/evm/precompile/invalid_subgroup_for_pairing.csv +++ b/evm/src/test/resources/org/hyperledger/besu/evm/precompile/invalid_subgroup_for_pairing.csv @@ -1,101 +1,101 @@ input,result,gas,notes -000000000000000000000000000000000ded5634c6bab9610e70f3a9be2bb39c51f2ddd762d22d6c3f0961af19350c4ca4bae333bf4cf586d8cd1e0a0a6c674e0000000000000000000000000000000012309fe1d843245e2cb58d419ff06ed8e93ec901c01d0c5be453e11d10f930afa0c35428ef0c8dc13ce99c990af166630000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1d9f0000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a400000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1d9f0000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40,,,invalid input parameters, G1 point is not in the expected subgroup -0000000000000000000000000000000014881e90a100e5c1e07588d68c69b9b217d2c57b2bd8782ea7579abfac3a38275677a8dee7e689c1b185712ea38bf0ba00000000000000000000000000000000061e48ceb68db1cb84ed39f9772f937922198b47082d7517c8258e46a490fc20e4e01971711853ab653d064f8adc79e200000000000000000000000000000000173518c27d76414f3027ed3e4936ad1b6aaebf0a98462df3e3a55aa38e19fb121acc6eeadb7689c84fec18580b63784c0000000000000000000000000000000007c116829ccc1d5505dcce029f5c8b7c8a9cbc9dd562a874766a5654483aec977a0fb9e611f9471d2f0c91fd6534892f000000000000000000000000000000001560b267e66200e69d6298250456d88500a0d79ea69870358a6cb39609fb6596e539b28bfea9d1c7054b5b51a4c0f3950000000000000000000000000000000000cc196f93e5a2dd3a41a1ba23fd2e53bf376c910117e9216663e30091b96c897080bca189ce3107720dac1c54dc4fc70000000000000000000000000000000014881e90a100e5c1e07588d68c69b9b217d2c57b2bd8782ea7579abfac3a38275677a8dee7e689c1b185712ea38bf0ba00000000000000000000000000000000061e48ceb68db1cb84ed39f9772f937922198b47082d7517c8258e46a490fc20e4e01971711853ab653d064f8adc79e20000000000000000000000000000000015237996817e97b29ef5b4ee49e6aea7129bdc4a46707f99df3ab8af36eb4123e93496d94846f7807b3bd2c87d3ca039000000000000000000000000000000000df666f7728483689a746e5b39f4848a9f2d831cb17d4e5f7fb67de5d497f6399de5f37ba9e6ab76937e26450000d600000000000000000000000000000000000e4ffecf86b371ddc9ee6a72b5ada74790b590acc51c6c5c479c43c532a9507b4b4909bdc901b00932371a109fd38b7e000000000000000000000000000000000354f92ccabccc9390072671ccc8d3ea0a0c44f85816f20bd6e8b485e648e8ba0d5b845a8835395ce65b8101015ea83f,,,invalid input parameters, G2 point is not in the expected subgroup -00000000000000000000000000000000199fa649608972d295befae38d36940663d2b67bb286a3d549c75deef39ef8068728bbba2cafac44c102499601e4bd860000000000000000000000000000000002dac960f96822774a4956fc0ba97a235d0a2c10d81d9adf7b88215250c934b68c3de07a97adcaee2aaad0d3d84ecf6800000000000000000000000000000000000eb3c91515d4a41209a73564741a8ccf901a624df9db22e195a5d02d24b7bc0a12756b15b8d006cb991a7e088eaef1000000000000000000000000000000000704ce8afc808b0161f6f61b22d990d713ae398779e6e74e9b5771daf006ce0bba3a8088edf75156f0e48b92ee8409b00000000000000000000000000000000018fe81e05aff0620f4bdbe4a715e015650497afab62921eba0ab86b649e5a2fd3d54041868928519f537e36448688a0d00000000000000000000000000000000162bd97161201ea3c26f8dd1204a9c6b61b762bdf573cb5d20b6b255f30208ca7d96aa47b46fb8c6bf0922075f1c1ca8000000000000000000000000000000000d5bb4fa8b494c0adf4b695477d4a05f0ce48f7f971ef53952f685e9fb69dc8db1603e4a58292ddab7129bb5911d6cea0000000000000000000000000000000004a568c556641f0e0a2f44124b77ba70e4e560d7e030f1a21eff41eeec0d3c437b43488c535cdabf19a70acc777bacca00000000000000000000000000000000000eb3c91515d4a41209a73564741a8ccf901a624df9db22e195a5d02d24b7bc0a12756b15b8d006cb991a7e088eaef1000000000000000000000000000000000704ce8afc808b0161f6f61b22d990d713ae398779e6e74e9b5771daf006ce0bba3a8088edf75156f0e48b92ee8409b00000000000000000000000000000000018fe81e05aff0620f4bdbe4a715e015650497afab62921eba0ab86b649e5a2fd3d54041868928519f537e36448688a0d00000000000000000000000000000000162bd97161201ea3c26f8dd1204a9c6b61b762bdf573cb5d20b6b255f30208ca7d96aa47b46fb8c6bf0922075f1c1ca8,,,invalid input parameters, G1 point is not in the expected subgroup -0000000000000000000000000000000009d928f478fea86b1e3c1ebf59b230af42e4a539dd43ff17b9ca250605401273edc42806bf2cb887c6093729238fc0560000000000000000000000000000000005b6040ce6d0802719af5b9c0fe48366c5a97bcad2c6ec25f64cd73f39dc73a22d0084c69f2e86637c584d2de55d6064000000000000000000000000000000000aaeceb367eaf2ab8afb5070b5f5611f19fed17e16f6b01cd20f5f1a7e550d4689c89d6490878877c46492e8fbc9db600000000000000000000000000000000019dc59fbefa7abf12f1aa92e420f52595a06e819e974c07b62a3ae459c62545922016abd8e91301aca8f48ed793b91af0000000000000000000000000000000012f5352825a95ff0a0f0948b150e5eb1ab53d6591dc54ca8b37941fa94a0fa68af8c35aaa26c73951d2dd52e9c7a55bf000000000000000000000000000000000531e610f3706a1c67b31909a97232acef0e23c35c657dc82d9508fcf33bfff777386dc8265accb52d0da207957d25160000000000000000000000000000000009d928f478fea86b1e3c1ebf59b230af42e4a539dd43ff17b9ca250605401273edc42806bf2cb887c6093729238fc0560000000000000000000000000000000005b6040ce6d0802719af5b9c0fe48366c5a97bcad2c6ec25f64cd73f39dc73a22d0084c69f2e86637c584d2de55d6064000000000000000000000000000000000cf95f6bc3f14cc8a657da35f212d55d3633ca0d224794ac1e49cbf1e1db7757dac2bb08880f7b13f2ac0c2b95ff483c00000000000000000000000000000000115bf27014a31726503f0665301f9e183de41e002db568916aaaeaa4c8046475860db3b993b2ac2af58b12614f0b9f2d000000000000000000000000000000000983ee62076e9c5f8e6ac3ba8e5f98823a37ff20432f0b4e7d9e008660965f551bb5538e6ba28541ad514e87d128da1b0000000000000000000000000000000019c9930e3e009986608463c80aa0049fcac5e897474e519b52c80960f1dada5a48332d810f1f9a34ee479383b5b556b3,,,invalid input parameters, G2 point is not in the expected subgroup -000000000000000000000000000000000f240a4daf7acbe8f4c5a9cb40ccc6d99dd712b8dae4c13e9d3839b0c88c5eef6f050144a4b731267e2132a81b1a635c000000000000000000000000000000001931c93a957ba9005fa69004c7e984b9dc86f45ec03eeed77addb3e1bee3c2424105df8e8faacae2b07623cd3cc7261e000000000000000000000000000000000805892f21889cab3cfe62226eaff6a8d3586d4396692b379efc7e90b0eaad4c9afbdf0f56b30f0c07ae0bc4013343b30000000000000000000000000000000007853f0e75c8dee034c2444299da58c98f22de367a90550dbc635fb52c9a8f61ccc100f70f10208944e48d09507fdce100000000000000000000000000000000064afd6b3ef7ff7ec34f1fa330877b42958a46a7698c6d21adf73bfdfcab7793b312e21e5988652e655f2d42edb8a673000000000000000000000000000000000ea8a2217c3dbcc0f6e562de9cb2f334c896577d0b3a7108d96b1aba2d705dbf531e870d4023cec2c0533455013242330000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad6242000000000000000000000000000000000353d6521a17474856ad69582ce225f27d60f5a8319bea8cefded2c3f6b862d76fe633c77ed8ccdf99d2b10430253fc8000000000000000000000000000000000805892f21889cab3cfe62226eaff6a8d3586d4396692b379efc7e90b0eaad4c9afbdf0f56b30f0c07ae0bc4013343b30000000000000000000000000000000007853f0e75c8dee034c2444299da58c98f22de367a90550dbc635fb52c9a8f61ccc100f70f10208944e48d09507fdce100000000000000000000000000000000064afd6b3ef7ff7ec34f1fa330877b42958a46a7698c6d21adf73bfdfcab7793b312e21e5988652e655f2d42edb8a673000000000000000000000000000000000ea8a2217c3dbcc0f6e562de9cb2f334c896577d0b3a7108d96b1aba2d705dbf531e870d4023cec2c053345501324233,,,invalid input parameters, G1 point is not in the expected subgroup -0000000000000000000000000000000012d75b62dd32798ea12b99f51a496bbf61bca93cd233dd288e00259893dbe7c73b6342860d41fd86878a6de608dba0ed00000000000000000000000000000000018e963e1cdb31ce3f636091952c17ab4c03c0f55940196b5772002850c6c86a3da13a9a5fbfaa8256fb0a9c4139bfc60000000000000000000000000000000018330d288ddc1786744f95c4b3bacbdfd10ab560fc283c1ecde1bcbdc53cb284198f340241242763c77b4cd3b8c729200000000000000000000000000000000019a3d86d9b3ff0b03da1686c3234bb2b8109ab12ffd7599ef9e3489fcee40e50036dd952f7a4129473340fb725458cc50000000000000000000000000000000010f70de44c7e0510c8ff8327dd83ea4087f9f8b5a53fbe18615fa31a3b57862b52a7726b3e9403afe93dcafc5606dfe500000000000000000000000000000000199e6da569d0eb7938d52b396c9bc6d8b563dd6e64edd60e2e25c493475a0cbd953e7ff54309d189eba2b3827ad614180000000000000000000000000000000012d75b62dd32798ea12b99f51a496bbf61bca93cd233dd288e00259893dbe7c73b6342860d41fd86878a6de608dba0ed00000000000000000000000000000000018e963e1cdb31ce3f636091952c17ab4c03c0f55940196b5772002850c6c86a3da13a9a5fbfaa8256fb0a9c4139bfc600000000000000000000000000000000192b17cf1539a57ee64701bcd07ed0b067a6ab12ef17408ad3ba1db0d94889cc481c877588fd70270287aa8f279659af000000000000000000000000000000001779c74b7e83c8d931510b555de206dee2d9edbb49b473c586c5b7046d1389e6f612cf96ee76446a21b02f135492fa1c0000000000000000000000000000000007165050fd4407a69143a98001c290f8b1aef02d64d28ee046744009f4afa376da95b4f6a3dd36427a297ed25e6bb3320000000000000000000000000000000005a9238272385614f6df896801f7f9af9cda1ac0ce3c816174693bd52b1799386c4f6355fc36f9ecd566a3a4b20e612b,,,invalid input parameters, G2 point is not in the expected subgroup -000000000000000000000000000000000d612430d9b8956188fefa4cd839ec8dffee18678f83d4984b08e227880ec127e63860bf5d7a27c309fb425b90c0afa500000000000000000000000000000000100556e6391ac2a05b15e552e67d6509e21c4770cfe2f8126fd1d9663995839420a4915656d6292d0767892833369bbc000000000000000000000000000000000aeb5c087644595d0912879f61959d2731ff55260c682ed2bc5fc55c13964ef7c1f70aeb55876d2264d558c31371ca69000000000000000000000000000000000e173848f4570525b03a2b2c86f4dcdb8b28dd6d18c1354cad31028eb1b8b44432c2346edaace093e3954c7fa6d338a4000000000000000000000000000000001949b0902506d111ef6318edcd7a58ca4d69f5804a028aee73c3786cb2db168c6a73b77194f7a021ae6ae43ac78ade340000000000000000000000000000000017c5e28ba6103d97e2f3d3611c0c78f06406e0da8a49ae29c7d460b52f75136920784cd500aa3593858b877697eb84240000000000000000000000000000000007dc719ae9e3f1e11d3ed4747a546a7b973ccb1967adb1b3066645a8bde9632bcfa3530e768f088ddbc022b169e67cbf000000000000000000000000000000000bbf9cf884b19c84045da1cead7dcd9fdbf39d764ff1ad60d83ed1e4fd0ce0554f0fb618203952cf02a7c4ba466c66b8000000000000000000000000000000000aeb5c087644595d0912879f61959d2731ff55260c682ed2bc5fc55c13964ef7c1f70aeb55876d2264d558c31371ca69000000000000000000000000000000000e173848f4570525b03a2b2c86f4dcdb8b28dd6d18c1354cad31028eb1b8b44432c2346edaace093e3954c7fa6d338a4000000000000000000000000000000001949b0902506d111ef6318edcd7a58ca4d69f5804a028aee73c3786cb2db168c6a73b77194f7a021ae6ae43ac78ade340000000000000000000000000000000017c5e28ba6103d97e2f3d3611c0c78f06406e0da8a49ae29c7d460b52f75136920784cd500aa3593858b877697eb8424,,,invalid input parameters, G1 point is not in the expected subgroup -0000000000000000000000000000000013b23b5a61ad3ed0ed74a57023285338c77c01dd93e17fd93d4225d27a7168dbdae3fff0c8ebcfc30887df348deea7ae000000000000000000000000000000001245282782135d41eee4dbd9b69a586e1ae2e17426643494427eabec93ba3c056089ce9c8a667727c3464178f7c87655000000000000000000000000000000000d35887ecdfce840b79f60f77af0e7cc01289ff20e1558a14b57a73f3fdd8e66ca496a73ce888d1c8cd0888d8219412b000000000000000000000000000000000cca9ce1b3c5bb6d02d5c36549438a4854dacacab7b173b434d09e496256edc2372a08edb2de26e5369af03dd0e2d73e00000000000000000000000000000000152d022429b54ac4bb70dacc888a2a3409cd7d2db0614ba0afe236c7bff311967718a70eb7943f93a6dbc88d3c008e8b00000000000000000000000000000000191e99ff1e02809121098597de4d176dfc85586d3e13609b3399c08da7c9cd6fa006e8b95551878a74e140cf166625c30000000000000000000000000000000013b23b5a61ad3ed0ed74a57023285338c77c01dd93e17fd93d4225d27a7168dbdae3fff0c8ebcfc30887df348deea7ae000000000000000000000000000000001245282782135d41eee4dbd9b69a586e1ae2e17426643494427eabec93ba3c056089ce9c8a667727c3464178f7c8765500000000000000000000000000000000179657b434fbb3ecb4857b4559c8cdaa1448b2a4bf4314349341a45bd3a2cdeb2aa6be75ee1aaaef0f90d086df55ccc40000000000000000000000000000000011ec9abd1a497dc7cc4b4b9e2cef8a7abc767969703d79ff51d7a4f1ab93ae9fc7e8bd4852da22ec067d326fffa54757000000000000000000000000000000000a45435d1f78ad93aeef8a88dad39aebc0cf38cc16fbb02c571a269bbec773e71b5ee9d09a505afc9a7fe11b06f040cd0000000000000000000000000000000000b6ee9d2b0fbd996455169ef07afa3be3c6535d65545c8503c3d77dd0576eecd2f81b50dabbb62328edfe236981d5c1,,,invalid input parameters, G2 point is not in the expected subgroup -000000000000000000000000000000000b14b12ecaa94f9656be54772be9b22a2495d4ff873b0bb971c27ab1d8b940c84cabcf921f6f75e93942c38cddeb8751000000000000000000000000000000000c9ad793ae80ad0e1c39670876d679bc7f930c1df1f9202593d1901079687b77f18e4b1536bd7c4152ec197321dc945a0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf2300000000000000000000000000000000005c35f3372f1ec9845bd04ea722fbed2be1388abf59e622dd3dafb4b3af49bc5fba9e20235e7e58973fedf4b8b720691000000000000000000000000000000001111d18d621070509805d306a31c109701288fd55d4c0644349deb080c6591b6e852b4f7e009b80019513de7f2fce17d00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ec0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf2300000000000000000000000000000000005c35f3372f1ec9845bd04ea722fbed2be1388abf59e622dd3dafb4b3af49bc5fba9e20235e7e58973fedf4b8b720691000000000000000000000000000000001111d18d621070509805d306a31c109701288fd55d4c0644349deb080c6591b6e852b4f7e009b80019513de7f2fce17d,,,invalid input parameters, G1 point is not in the expected subgroup -000000000000000000000000000000000e96f4879493d577aa02e9793aba3c680de9296dadef569d70484297398cca1e3d8acadc6c188345d98d5208f5b2d7800000000000000000000000000000000003bee60fe5a42f31ae4218a6c6fff2ffb51329c86f8d3c381fb090bba0274eef8cf0d351fb4bbe2d51e25d9f7b2094ee000000000000000000000000000000001664847d2bfb8939cccda0bc7b7ef02fcc1188fcade83341db7c4dfdd4a1a3bdd627379544dd405bcdb58caf4be39cb40000000000000000000000000000000001a293caf801299370cd133fd54d2086c6d1aa96ec32a1c9c57679f45785c9e6fa91018720c2d0515b38790914ab61d20000000000000000000000000000000017efcbccb71f6bbfa515e89d59b5fcdda4bf37c90fc6dafe9c6b0fe3d42a372ad9c66e5d60e04eff30ea7c1952f3fed30000000000000000000000000000000018893c6a3c621ba6c1119529a07d6c2f9c1d4bee5e0090c4c0204d8d4f3e200ab3300dcdf1cfe61370d68849806ddd8e000000000000000000000000000000000e96f4879493d577aa02e9793aba3c680de9296dadef569d70484297398cca1e3d8acadc6c188345d98d5208f5b2d7800000000000000000000000000000000003bee60fe5a42f31ae4218a6c6fff2ffb51329c86f8d3c381fb090bba0274eef8cf0d351fb4bbe2d51e25d9f7b2094ee00000000000000000000000000000000060a0cdf9ea84b21c7399b21e337dab42f357c30f50b076b4f95bf8623b88e557cc8b346e41a996adbfbaf0a3697ae8e00000000000000000000000000000000003746a481d5760263d43ee3c2ed6493caddcc59754fb8534ff57ff9bf167f356d901277b9935210ddac4dfff8f21161000000000000000000000000000000000d80e40583153e6e0adf9f95ebc4fd501f5aa426ff77038b69f0fe8259268f9224628ead1a63b7ecb4218ba825ee45b60000000000000000000000000000000005d0421b213f88469887d67c68316a98c18d2035a601dec994f6a4b91fb6c3b7e5ac7741cd07c956908869e1023dd32d,,,invalid input parameters, G2 point is not in the expected subgroup -000000000000000000000000000000001576c6f620bbe36a7df78985dbffd355c032bb667f4757501bb27a426c99e1949a1bc65afebaeac0668ff3d375b7837d000000000000000000000000000000001651783a82f2a8e1e389923bfb53836492c00e447b288873071bd8682a0ef30864f4d0fc9a626bf701bdfe1a48f4479f000000000000000000000000000000001335276775545fbb4c701beb57cb34312108c9f1d46b4aa4b09a16faf0e648b4e80848bf5e75ed8730715f0107afc9820000000000000000000000000000000006ffff8736bab41b4ee5681b741a81fc870e648001027161144254d04c678e4f954e9f191bd8b26201aec681cbf0654b00000000000000000000000000000000026ede90d14fa0885baad21f9631bae058573251cbef5757bb8cfad061f3bdc78834fa5862dea19a2236c014b0f1652e0000000000000000000000000000000009844d0cf7f6f3401145d8d720defa577ca46b49e04e39c4c139ec6811a574e7dd5ce3acd00d1ce9496f10dd15c6d946000000000000000000000000000000000a6ff5f01a97c0f3c89ac0a460861dc9040f00693bfae22d81ea9a46b6c570436f0688ed0deef5cdcc5e2142f195b5c000000000000000000000000000000000193a17880edffe5b2ebedf0dc25e479cac3b136db9b6b24009ea0a9ca526d6dd9714d10d64c999d4334baa081b9f2fbe000000000000000000000000000000001335276775545fbb4c701beb57cb34312108c9f1d46b4aa4b09a16faf0e648b4e80848bf5e75ed8730715f0107afc9820000000000000000000000000000000006ffff8736bab41b4ee5681b741a81fc870e648001027161144254d04c678e4f954e9f191bd8b26201aec681cbf0654b00000000000000000000000000000000026ede90d14fa0885baad21f9631bae058573251cbef5757bb8cfad061f3bdc78834fa5862dea19a2236c014b0f1652e0000000000000000000000000000000009844d0cf7f6f3401145d8d720defa577ca46b49e04e39c4c139ec6811a574e7dd5ce3acd00d1ce9496f10dd15c6d946,,,invalid input parameters, G1 point is not in the expected subgroup -0000000000000000000000000000000016e8fb30c523be94fed64c81cefcbed03d7a2c85ce975601a441a920d70980be8b7c682ebf2aa3cc36ad1bf2d98a711e00000000000000000000000000000000056d7a36324b92ce3bf1644a2a0f6e717b0e9d94e9e5231f0c999c0f6eac936f5c1b1ff4aef04b821454a951882fd45000000000000000000000000000000000069ce7ace071ac2f7034200da8eb9252c55cbb50a591c563684013fa8071d7289a44ce5b100db6af1e8732b8e5b3b7fb0000000000000000000000000000000019535c703e9dec227c92db508c5c0553b986b93e3a90b35f472ad8e5b217bc7658ceb7ea452b76984d3d17859fbd7fc300000000000000000000000000000000008b3677ecfb10faae0478be5be5c72fcd7137042b5972fffbd15ac95d6104687a81fa3db232edbbb8703280412eddd30000000000000000000000000000000012a628e23e5c7f0225eaa75b6c9199d1d3a0316d474d1fa729640c295ec156ce3bd49f193d993165b9a69ad6cb2ed75c0000000000000000000000000000000016e8fb30c523be94fed64c81cefcbed03d7a2c85ce975601a441a920d70980be8b7c682ebf2aa3cc36ad1bf2d98a711e00000000000000000000000000000000056d7a36324b92ce3bf1644a2a0f6e717b0e9d94e9e5231f0c999c0f6eac936f5c1b1ff4aef04b821454a951882fd4500000000000000000000000000000000005b6ff8cf47cdedab38d984013db8401fa4783e7c8e78160c7a420de86636683b8dc292e1f0494dba5865f217e33e7c40000000000000000000000000000000008287d216fc45ee3e210630f0dc4893dfbba90b3bfd34986b64b04a2ec6fea0496cb0f2910cbb99a70896e5afcf809e8000000000000000000000000000000000a1a11f1b52c35b9e061c57b7b7f5f76f4db764607c93c2dddf245170f4c6fcf5915d72130e6f8f0c0c5b29a4970aeb30000000000000000000000000000000009255413a3db5889608069b7a72879b659ee5e1e71165752a9173cf1ef749050fefb86584ce90cbd3c3aa3db023b3322,,,invalid input parameters, G2 point is not in the expected subgroup -00000000000000000000000000000000117e2e3a8753660374fb2b812038bfd8f15d36d26c7b07fb546610405cd4442c79166060c8b8e767ea4084a583bbbcca0000000000000000000000000000000011247def4be1da163e724a175f74afde615df1417cdc2491158fd560442e56ebbcb61f51f9712ac93131024e27a1caa90000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc00000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc0,,,invalid input parameters, G1 point is not in the expected subgroup -0000000000000000000000000000000018de31d75ba31dc1441bee38f5fb9e9d347e0243e6c3220de664e04a0af17c776fbb483f63ebeac48c7811adc0fff6bc000000000000000000000000000000001053f5c7a65dfb41b7631a22e60337769beabd663fa76eaa1e01f0c0e3c901f40a22b34adfb13b38e298eb83f08d7b1400000000000000000000000000000000079daf601b5fdd1c00552382a819e1924a517226864aa3565d384dc399e6e124c3ad61b236e19321078eab585579572d0000000000000000000000000000000008ff51b4d60d9fb1718c5622b1a3353675ca0144e12e68875481394755107b7f3364d1320283ce34737d5ea0b7d9b40f0000000000000000000000000000000000f430d35de439bf8ef82df4dc61459cf9783d66531e8075f323dbf102035032972a4830ba6bf391b568e6b691723ea600000000000000000000000000000000042df3a5c7b534c48c9af9f48e5cb296e2e514e3322630989c3da9696485797fe2ae036e13484e26cb59b48a479d3f520000000000000000000000000000000018de31d75ba31dc1441bee38f5fb9e9d347e0243e6c3220de664e04a0af17c776fbb483f63ebeac48c7811adc0fff6bc000000000000000000000000000000001053f5c7a65dfb41b7631a22e60337769beabd663fa76eaa1e01f0c0e3c901f40a22b34adfb13b38e298eb83f08d7b1400000000000000000000000000000000180168b2c9a59a9bedcf002336a6ce5d5ff36937948f5d3aa9fc1ef1912d9fed526edcaa00b5ede76906c40994937da00000000000000000000000000000000018d47a4f6e4320a230b8a4e5f5eea7807fb5b0dccc72af182e8bb811f2470dfd485f291b8197899af84332e3674d56950000000000000000000000000000000000dbfed96298ad2f57fe7b1eff4791abdae5fd0ca3573e66270f8c9f6adcbf6e54a746789a496714f5cc06eb2b8190070000000000000000000000000000000002939de2a2f7ec3d44dc5ae679967a5d7efcf80df8a21d9708c036c602a5050487f80a39cc36c7e587f0f2610fed86b2,,,invalid input parameters, G2 point is not in the expected subgroup -0000000000000000000000000000000001ac9e5f4fdccfe8d2979ad5329b07b23b497c2b7f26283f8c79548387610580568bdd9dc040261d6c265aac581b4665000000000000000000000000000000000eaf31d6368bb9d762ea067eed73b008dcb4d84643471dc1d64104eaefaf500858d9a9323b7fa2ba3c22bf15e7db6b9d0000000000000000000000000000000007457f2601621a99050d8993244f026b9a62ff7055b325e6f1edd1cf54065785f003cf7c8a4bb1f7bdf14e220e490ada000000000000000000000000000000000928eb76b428dde37546a27f3d77605c293738f448fbdd6d618747b0de04004aa4419cc5601600419c6e1d470c15982e0000000000000000000000000000000008074e9f5473492dd2e536f7b305be4e5c564cfc9218934d03dde6dc5118064ebaa5c26fdd1123a9c31336c37c1234900000000000000000000000000000000002bba1f9b7da6abd2b322c8f11c749b2a284552eab25a77d21b38b028da477a3ffec1901a015e81fe2893576a41e4c0b00000000000000000000000000000000172447291285a20c3ffcdef805260f00fbd4c5d3a42ee5b17d5ec05a723aadf4c2acc49f839e222155f29d0c6384bb0a000000000000000000000000000000001490dae85f858ed057fdb61c22ebf3d45c3af02afa53f32b2542808bb4db5bb1b9603e377f0b6d214e03050379f8006c0000000000000000000000000000000007457f2601621a99050d8993244f026b9a62ff7055b325e6f1edd1cf54065785f003cf7c8a4bb1f7bdf14e220e490ada000000000000000000000000000000000928eb76b428dde37546a27f3d77605c293738f448fbdd6d618747b0de04004aa4419cc5601600419c6e1d470c15982e0000000000000000000000000000000008074e9f5473492dd2e536f7b305be4e5c564cfc9218934d03dde6dc5118064ebaa5c26fdd1123a9c31336c37c1234900000000000000000000000000000000002bba1f9b7da6abd2b322c8f11c749b2a284552eab25a77d21b38b028da477a3ffec1901a015e81fe2893576a41e4c0b,,,invalid input parameters, G1 point is not in the expected subgroup -000000000000000000000000000000000515390641039576df1debf767b899deed928bba057d8564e7d117b324dd7f45e38939ffa1056fea35e1df8ba38815ad000000000000000000000000000000000013af9258135a09e5710a22dc437a4efdfee1f6c25cc08fb026c7ea2accddb9446333a91a532742c7713f6639e0cc460000000000000000000000000000000017203225f7e7b55e52793698b8a1166de5cb3d130c7df6f74ae428c4f39e7d042d41eb3aed266ca8cd3be1618cfcbdf60000000000000000000000000000000016b8053ab19a08a8c23a166bfe7a89be6eed05f305525146d9ff6e03e47ba125403cb2f000672d08a2b59051242f61ea000000000000000000000000000000001136f9a9369aa3561d65e54869f36ae9d6945654ac16335aaf717e0f848463ad401a99e83b3581c79d4221f65d649e9c000000000000000000000000000000000302ecf71033b917efac49d7a22db2e5c59b656639c4d0aefdb431749718d560b7e5580b4532048a693ccc09fb8a44a6000000000000000000000000000000000515390641039576df1debf767b899deed928bba057d8564e7d117b324dd7f45e38939ffa1056fea35e1df8ba38815ad000000000000000000000000000000000013af9258135a09e5710a22dc437a4efdfee1f6c25cc08fb026c7ea2accddb9446333a91a532742c7713f6639e0cc46000000000000000000000000000000001330e5c71d9b0209c8f8c04b4a28ad70d826b2c781493ac50fe58d0d6b740de3ed08f9829aa9b207ecf623a36dc8de610000000000000000000000000000000019a81ae1c6c08004a75b8fb5426ec899a09becce68dfb63fa7335420f05075bcf42c85dfa688ae75398c3ae5e4d28d12000000000000000000000000000000001597318859306f3814d20faee54926d5e8ec01005fae8aa408989afd5a1d7add8956d8fd3e75f9e446fb8b2a31050b6b00000000000000000000000000000000151e7c5bbcaf1e9581ac48e2b9bc63bf4576ee5d938981550f00fe9e3a42d389d6b56acd0ac9254ceaa71519b5049a4c,,,invalid input parameters, G2 point is not in the expected subgroup -000000000000000000000000000000000a2e9dcb8461de77fffb1281df7360c947108baef671602902c3bfa4afe60e0b4810cd46bd91276cd58e33fbf4973301000000000000000000000000000000000c039ea7e94fca4a32a275691131df8e3d470d6026aa306c8ae855e9e411878247368615094621e6af5486809b467a7d00000000000000000000000000000000138ddc71e9709e595ce2631c9155069818d07f225f80511eac21c5655721df83f83e2abbf56e2d5ebc9698f9128f0579000000000000000000000000000000000080f7ae78277c94833c0f12aa2cd98285621e0d92a89b45cac848f5a75ea652e98cd085150b2d7babaa9a365123d90e000000000000000000000000000000001147cec62010d42cd84651ec61ad8263699786f0f34aa7f9de75ac841861fae2e8ce3795fde8e7038c8506f573cf0e0f00000000000000000000000000000000138ed243889db13546c69583c1aa93ee98366220ad80c590ad112adcb4b7c87fb48a34483e76d4a5c55a30f2adf1a6a6000000000000000000000000000000000077b7a4c4644b21ac3ef56db1163f7b2e07a817cfd9d4c6830a97d0ae0b620e0b235376d590162c378899ba12eadb5900000000000000000000000000000000022beafe4b4ab44434c9dabae45a395b5b8da15da2fc2e723c1b30b5efc95e880846844f27eb47dfae8657fa27ab64ef00000000000000000000000000000000138ddc71e9709e595ce2631c9155069818d07f225f80511eac21c5655721df83f83e2abbf56e2d5ebc9698f9128f0579000000000000000000000000000000000080f7ae78277c94833c0f12aa2cd98285621e0d92a89b45cac848f5a75ea652e98cd085150b2d7babaa9a365123d90e000000000000000000000000000000001147cec62010d42cd84651ec61ad8263699786f0f34aa7f9de75ac841861fae2e8ce3795fde8e7038c8506f573cf0e0f00000000000000000000000000000000138ed243889db13546c69583c1aa93ee98366220ad80c590ad112adcb4b7c87fb48a34483e76d4a5c55a30f2adf1a6a6,,,invalid input parameters, G1 point is not in the expected subgroup -0000000000000000000000000000000004db2336ff5016a6f0f34b2881eb80cfbb35f557ef492f2dc54a654c8c574651fc89f3f211a46510ebffcf5c890e5ee700000000000000000000000000000000103d17d59c26d8c85426b73593843e3863cd672043b7a392cdb05bf5dd69cd11583423a4b146dcd3fbcde4ef4349bd78000000000000000000000000000000000e07265d2762e8e398c83efe1c43452d91b90b7a4271c09ff693c83745a6c01b73561ffe3da9300c8e7e1602dbaab0bc000000000000000000000000000000000375579c16a167fd9f9f61d5177705f157aa0df3451971029a9444432db119fb33b8c07de33fc822eab46ed4ae47cf8200000000000000000000000000000000098cee454129e946d758f95e649a601b6e682467093cf32c76e5cd1328111f9d48cbb2850d4d2faafe429161b090f8ab0000000000000000000000000000000012a3fdf296743b2eb3f389eb6dbea52da21e7dbaad180177184afb7976e06fd4ad488399d39b760ca146278cb3759d7e0000000000000000000000000000000004db2336ff5016a6f0f34b2881eb80cfbb35f557ef492f2dc54a654c8c574651fc89f3f211a46510ebffcf5c890e5ee700000000000000000000000000000000103d17d59c26d8c85426b73593843e3863cd672043b7a392cdb05bf5dd69cd11583423a4b146dcd3fbcde4ef4349bd78000000000000000000000000000000000e87688c9f6f48450528cec78f874d87dcfab499bce391e964a0abd601d675eec8a2b4cc6c44811cbe918913a230975c000000000000000000000000000000000451c1a17567b55123da563e173b2d92ab635c4957d116fdff8eb5a3e00bfbe079abead42b9c990b56db919719d066160000000000000000000000000000000003206eb27b9e53bc451bdbe5059133a51d656d9d80654647163a070741761af3e712e2042acc5ca29fe72cafb3a98ec7000000000000000000000000000000001869b7fd294c230aa5901db9cca31e6d3801069e0795e4c7c63dea8adc1e4a5709ec62670e2abeeb040031b7fcc8521a,,,invalid input parameters, G2 point is not in the expected subgroup -0000000000000000000000000000000007248a496f2cc918cc3cf0240c5c3d0dce36228a199766106999205b5767db09c2ff5cc720691ebc6bb1c316159d1c000000000000000000000000000000000019ab8e34bc4c0ba409c7edf7bbd4d0cf03f7185a2baa3f22ebd6234503d56e9b0d84a5d8d46ef9e3ac6430029be82b2b00000000000000000000000000000000143220e1cd08ffaa6db4795ed4aa35f3b12cce724fcad005367328972f2364f34096e32f1f1cb7a4287ab636d0030322000000000000000000000000000000000f2de47a37a55edbb75ff0bcc446611d690d7f9efdd09ca1ebb6f1d64a330bed420bcc85aed8b95316fcac3aa7d1f2230000000000000000000000000000000016afb044b8b8c64547e000f80b25576aa329a4319dcd4f1bbe15d12e6f3bbdddbb52140e6297c637311ef0c7a31cafab0000000000000000000000000000000019e6803c07fbaa075093f6a69f9dde05ba3d3f58e67389d7f096e56df49f8270008ed422b64fcdadf7cbbc8334037682000000000000000000000000000000000b231eac9869c94f055f22c16ef5efb687a7399da838c9456bb2469b3e394dd21e462bcc9298d5b56173c7b76f79ee9d0000000000000000000000000000000013a1bb963b7fbe1c3f6bb78cce8c3122773428ccbd87cbb2e76f306a8c5369577267e9b4ccb3417a235fc9045e8828f100000000000000000000000000000000143220e1cd08ffaa6db4795ed4aa35f3b12cce724fcad005367328972f2364f34096e32f1f1cb7a4287ab636d0030322000000000000000000000000000000000f2de47a37a55edbb75ff0bcc446611d690d7f9efdd09ca1ebb6f1d64a330bed420bcc85aed8b95316fcac3aa7d1f2230000000000000000000000000000000016afb044b8b8c64547e000f80b25576aa329a4319dcd4f1bbe15d12e6f3bbdddbb52140e6297c637311ef0c7a31cafab0000000000000000000000000000000019e6803c07fbaa075093f6a69f9dde05ba3d3f58e67389d7f096e56df49f8270008ed422b64fcdadf7cbbc8334037682,,,invalid input parameters, G1 point is not in the expected subgroup -000000000000000000000000000000000ee60801e3aaacf27167207a03b42594cdca4cdfe3a1f8e5f87b0e423ad36c56725dc79a26c157f41699e89884d27c1b0000000000000000000000000000000017c92b0beee470e126830843170bf0a89d13856ff6b0209e23bbf2a68e0668bd81db29de726ec7593124bea89771da97000000000000000000000000000000000a1beec4d223edbf1d09c448ea27038327bd8621842d3c7851779286060abd8130e4a641100627057498a421b59e90a600000000000000000000000000000000076a9559f5e33a2d5bdff6a89037c12e58ef920a3d8b9b4ca2a78299dca5dbf07c80d51014ee9288af8725e6c4312225000000000000000000000000000000000755036dd19c7d703343bc29238ae9332823514aeb4779b3378e8f7a3463a4089eef3fe79608db22b703b5d88c65bd80000000000000000000000000000000000ee3d82959dc04b5d59428142411e48e7a2fcfcff91e5554b725868c62d139bfd763720c9e2727c1b1fdc0bfd60f7297000000000000000000000000000000000ee60801e3aaacf27167207a03b42594cdca4cdfe3a1f8e5f87b0e423ad36c56725dc79a26c157f41699e89884d27c1b0000000000000000000000000000000017c92b0beee470e126830843170bf0a89d13856ff6b0209e23bbf2a68e0668bd81db29de726ec7593124bea89771da97000000000000000000000000000000000360e8bf07554b56d6f27de6a4b1f9184adb7980311e8f4128f619d9c8e64e98bcfca8eb3cab93e183882827fce2d06800000000000000000000000000000000010b0b114b55b17531576f782b6bee122cb5fb1e40d5af37ebaa1d4dad55a0f5d130c9a58cf27b1594eebd0b1492c0400000000000000000000000000000000018b70f0c047aaf7e17cb96015642e0d32f3135999de6dd8bded2c600866485ea75ab3a3e37581ae7dcfa1f54381f542300000000000000000000000000000000028cbef4b476a9cfb8daa35b1321a486b8c94f8b8df2352a0c8d509325706eee328b8dc3657122afb81be1fa2e60aafe,,,invalid input parameters, G2 point is not in the expected subgroup -000000000000000000000000000000001573cb3b6f4e841fe97549f42e06db33abeb2bc481cc0bc8c5e7bd81948432dcbd34b64c5766e1e491bc131e77a011aa000000000000000000000000000000000e205be14db0dfb21e58b302d45916de4f4bb48ac95228326cf49c50e6c8588a655a343b90645f73dd03c9aa51c5659a000000000000000000000000000000000a109e3cf72bba0e695bd756478c72884210868c95f0dd6ced22b7254eed0432c2345bbf8addbbd11349fec6e354d2350000000000000000000000000000000018aaf98535812ee760e17ad933acbe6b57bc1310b6fef550210053f06a611ce9df5dfcfdb028497468802c08cc2117320000000000000000000000000000000007590fa8cb07f9dc2ce7b2090685730104ca65a101c8f93965264913bf1deae794b71ff226a805a273248dc641225585000000000000000000000000000000000fbca4e20854a6069c298224eca9a064b7ddc4d834f8671ac45cb5d1d39265fa435ff1750cde5135e170824e516d6d2c000000000000000000000000000000001510f39616d7f576980055d0547c603d882dbe85dd0b634577fae134f210736007345d035d815306db660de4a07fc24300000000000000000000000000000000064d356ad7bd2edcd3622b1fc225fe319f86b5f7da875cd57fe5adc5bdb6443c5b09d676950e2d069bd4303b8f920692000000000000000000000000000000000a109e3cf72bba0e695bd756478c72884210868c95f0dd6ced22b7254eed0432c2345bbf8addbbd11349fec6e354d2350000000000000000000000000000000018aaf98535812ee760e17ad933acbe6b57bc1310b6fef550210053f06a611ce9df5dfcfdb028497468802c08cc2117320000000000000000000000000000000007590fa8cb07f9dc2ce7b2090685730104ca65a101c8f93965264913bf1deae794b71ff226a805a273248dc641225585000000000000000000000000000000000fbca4e20854a6069c298224eca9a064b7ddc4d834f8671ac45cb5d1d39265fa435ff1750cde5135e170824e516d6d2c,,,invalid input parameters, G1 point is not in the expected subgroup -0000000000000000000000000000000002ff87f9bcde30db0919444e2582a5e987fdda67264ed7abf60a93b37288143dd44db75403988cb5b3bcd8d34ebdb4e2000000000000000000000000000000000c672f8bf6e9e747cccb9438be047e56d1cfbd0808864e601de59b0b28b5438cdd10f1194ff5abe8694a598ff3dc1481000000000000000000000000000000000c01823cb218303c7e611f6caf6994615cc3805bb4310bb0bb82b56740c4314ed0a2f9409c8fa6b9f10dead667880fe000000000000000000000000000000000013eb3436ceac3f12dcdd9e71707b85b7cd872ce144c502078d4fd3ec8b4ee579410cbaf2e3db1df9ba6b55f14fbbb0c000000000000000000000000000000000ecb26e5814d5bc66fbbcdff5dd5934a597c4344487e7e63138c31d47a8201433be5ce14205660cae129891f5b7aa8e000000000000000000000000000000000040241f695cc864e99e1dcb04440135c4f3d90a86310441647ab265e9fedf51592b0c11d5e91d2d28ac4ed19245e9cc90000000000000000000000000000000002ff87f9bcde30db0919444e2582a5e987fdda67264ed7abf60a93b37288143dd44db75403988cb5b3bcd8d34ebdb4e2000000000000000000000000000000000c672f8bf6e9e747cccb9438be047e56d1cfbd0808864e601de59b0b28b5438cdd10f1194ff5abe8694a598ff3dc1481000000000000000000000000000000000a2e0129c31f3427340e234df5facbb5d1994a4c8a058fe84f7d5c62c7d00013cc0133d8b0faf497aa8f7c04abc59c3400000000000000000000000000000000160da6086a089fc1a31b89a0f0c95ff2bd85debf5f0d1372f3ca6fc26348d8a8d0b03525b27f7d7db6603f0e55d3a08f000000000000000000000000000000001893edc299faf35510b92c97ef665f458b19e4097299b4f8b0f8a2ea1f31343c9d2f4c35dbdfb36bf7f3e30686e8c3240000000000000000000000000000000009b8731ed8df8ae90ec344c9dea1eeee98f4000b5fe30460e58f30e4ea17821389d613458c7721fd1d759ec4f2bcb325,,,invalid input parameters, G2 point is not in the expected subgroup -000000000000000000000000000000000e42f5097dabc6b0fef4dccbfc659da40b66671d7efbc19dbc14a42a368402156469c818f233ad911574899db01ed86d00000000000000000000000000000000128bbee344b7a19212b549cefaaec31c0644c986ab863005e4d3ecf532be633597ac3906e53ca79b22de916ab92b4ad70000000000000000000000000000000009003b42c08b5c7d3ee9f6abb96e08e6f537da25cd0cf7eb85a49067746c03566e133b54153380286ef5725db5b41058000000000000000000000000000000000f09b7b754c255e0e3b8435ade64d6960285759495659dfdb9b117806397baf8d3c87e30bee02c9e1b22fa3efcc58f300000000000000000000000000000000003582c08a8de4bbd20ebfa833517a75682618fba2702b6c71a4785f70dbdede4e86ad8e04aae1f50a6bb75842ab74aea000000000000000000000000000000000ec013f22e64a4d4fb6f964e8319feb1ddbcfb71329186545d9b9d7f97d1f6a56c8aad03d20e9c30966ca932e1f2bc670000000000000000000000000000000002f78becf8a5717fbfcbe110bd6d4ba683658c1a45afeaeecf7742502d3df54b262de4fe30bd41ed0019cc79b595b5ad000000000000000000000000000000000dbae184899af1ad6221b75fc62cadcd61c623e011672b691a5aa498684f49796220fc5a73ea7170ed2fa44b9f3c39090000000000000000000000000000000009003b42c08b5c7d3ee9f6abb96e08e6f537da25cd0cf7eb85a49067746c03566e133b54153380286ef5725db5b41058000000000000000000000000000000000f09b7b754c255e0e3b8435ade64d6960285759495659dfdb9b117806397baf8d3c87e30bee02c9e1b22fa3efcc58f300000000000000000000000000000000003582c08a8de4bbd20ebfa833517a75682618fba2702b6c71a4785f70dbdede4e86ad8e04aae1f50a6bb75842ab74aea000000000000000000000000000000000ec013f22e64a4d4fb6f964e8319feb1ddbcfb71329186545d9b9d7f97d1f6a56c8aad03d20e9c30966ca932e1f2bc67,,,invalid input parameters, G1 point is not in the expected subgroup -0000000000000000000000000000000002dd02e23d8a9b7aaba94e8ce97a2d988291b6efb1d4d8d1c53b65581b737f992a428cb72ad38d4c6d470d6b50c869f0000000000000000000000000000000000661ddb1f0edbaa208c5e1db143ff41be5a35442da7b3e12d2928310ba5b64847f4909be93f860858f752cffce5e73870000000000000000000000000000000018fc47e1aaeefc1ea653eb42543940ba58044b247eef2627331142988f3a1a4fe72583738c7caa00c562d010089754eb000000000000000000000000000000000b5f8e9d774d99c3b6d224790d5c48367d68167aafc309ad8b351d1de8b601211737235bb0ff1b01e508ecf36a40db5d0000000000000000000000000000000013509c76fe4383e47d8ea35ba6ebcba13579bbf43cee0cbe27291d2601c3d74428bc9d5d1200718c86977abd16cb77a7000000000000000000000000000000000efbeccb9e9f348ba98ce0fd191c2e81de4925d7185f01b39a51aa44b30496f1fa3be4582c539889bef7b5783f730aa70000000000000000000000000000000002dd02e23d8a9b7aaba94e8ce97a2d988291b6efb1d4d8d1c53b65581b737f992a428cb72ad38d4c6d470d6b50c869f0000000000000000000000000000000000661ddb1f0edbaa208c5e1db143ff41be5a35442da7b3e12d2928310ba5b64847f4909be93f860858f752cffce5e7387000000000000000000000000000000000629fc2184297509e1dc0707b707067fa8b9a079d95f271ca230c32705c7e783f91ce1ef0323aedc6aa2febf750392b8000000000000000000000000000000000f29cb4bdb7d53671f9f1b1ee514cc916a657b7c17f298f409ad92ad7b2ca36b0298bd0581db7837994b781b829980c30000000000000000000000000000000014b839ecd27f2d6fdb92a36792c62ef503fb0189d3174447c4b1132e5f66d7970a005e315e6906ecd2ce6c72742d210500000000000000000000000000000000091bbfcfbd929cc8ecd000cf8ccf083c2f8cbd84f19b48b1ad20781e675ca7a77887b962435520e3f707fc0631fdc8f1,,,invalid input parameters, G2 point is not in the expected subgroup -000000000000000000000000000000000f67855b0cf915ab52190c7e69129d40dbeffae28fe110745edefacdca42bf979c5c5da1c6ebd78b43667cbfe360874c0000000000000000000000000000000014c5523f37b011c632e9e8eb08611e5a244ace2746264706b92a5a933b34bb9932c95c493ea7e8fd049d80acddd05e860000000000000000000000000000000012b63d867bbfe5505a83f765b85b0e8f182c796857f11125e691969c8e81eae927a5e1b137c81473e8b99ea0727c873c0000000000000000000000000000000010a8241f3b30c54a5d2fd7adca9be47b001f2268973e955ffa61d57793256a2d82fefa5e449548c64fa5aefda6d933220000000000000000000000000000000008e7d2f2e34a60504a5437275f358d0e742c445080c5e46053ea459893d6865e8d20906b2b58f1d1446a100cb1c9a513000000000000000000000000000000001369bfc5f24e2a7a7ace891444764ca32a32513c8ddf654f418c6eb7690e2cba77a4c4d4b98eb546d4e4046dd3c3267200000000000000000000000000000000153310de30b7a485753dd8443f8638c12b21083f6133a1c093648bcb566b33f73631c6fc558f32abeb0d6df8430e61a900000000000000000000000000000000005be397e9f77556ad952dba0540f46cbc7db910d5203cb976e168a7be3a3b8557c5f08d51cca9379552694a291d67fb0000000000000000000000000000000012b63d867bbfe5505a83f765b85b0e8f182c796857f11125e691969c8e81eae927a5e1b137c81473e8b99ea0727c873c0000000000000000000000000000000010a8241f3b30c54a5d2fd7adca9be47b001f2268973e955ffa61d57793256a2d82fefa5e449548c64fa5aefda6d933220000000000000000000000000000000008e7d2f2e34a60504a5437275f358d0e742c445080c5e46053ea459893d6865e8d20906b2b58f1d1446a100cb1c9a513000000000000000000000000000000001369bfc5f24e2a7a7ace891444764ca32a32513c8ddf654f418c6eb7690e2cba77a4c4d4b98eb546d4e4046dd3c32672,,,invalid input parameters, G1 point is not in the expected subgroup -000000000000000000000000000000000f3300926ec62bbc0a73e4e0404de3a55dc0ad4fdc393b291ccf9f80818b6a69c0a3f9c6edf024db295a353b6aa71a5400000000000000000000000000000000195b21606667c62a181b275184014fcef97218b451c3642e2e4dcf5502d03cbcc88839e827732feb32e77e52f702529c00000000000000000000000000000000198fe6684d3de4ec9c9ed5c59c9e662c4eb1b026995859db3a753ffa96c6cef7a063a607fc54599f706820fb864d7b0500000000000000000000000000000000116af6c03511ba2bcc32ea54c2fe2bedfb0848d2ee3017547cb25acf892400203e3ce4ea1986ecde0cdd383d42b61e180000000000000000000000000000000004a22ed8f30766b6b1dfc7286616066ccc262875e3de42e2c9528f6d23a7fda67ddb371f9ec419ae4dec48a354ffe21300000000000000000000000000000000086651d7ab0a4b1dfb9a56e42443f42d48c1f16866338d997adba078736d32bf4387b95da2da33e714662f800838a077000000000000000000000000000000000f3300926ec62bbc0a73e4e0404de3a55dc0ad4fdc393b291ccf9f80818b6a69c0a3f9c6edf024db295a353b6aa71a5400000000000000000000000000000000195b21606667c62a181b275184014fcef97218b451c3642e2e4dcf5502d03cbcc88839e827732feb32e77e52f702529c000000000000000000000000000000001367e0843d866d010ab03723026aa9ab1f930d3579daeabadc1fdefc06047ade30b36326d79bbae74293d3daf2e6c7a4000000000000000000000000000000000550be918ba9e28ddacb89ec526db63072bc14a62ec7ac06775f951de5b32af93d2d8f95389a6384cde62dffb941fb5a0000000000000000000000000000000014f59aa4f0603d5f166caa5692d9b068ee10a6f45670c6f4d492a4d1fa7a13a5146efcae33a841b5acf75a32b853b2770000000000000000000000000000000010ca0d9ba50c730006c95bc5439e2d39a40a9055ddac935a900bdeafe7141990db20ad0a840d3297c4b9b5f69aada6df,,,invalid input parameters, G2 point is not in the expected subgroup -000000000000000000000000000000000b9590b1d0d292d9967d759060a551f4e8e4c1c0066a9a3c0be515085847fa26b77462e3bae9e2621f28e01f897df0bf000000000000000000000000000000000a00d39c6c14d18dc5888b8a8835c3ef6f83ead2fa380be24ae857677b904b3868ceabdbb3abf3c186c5353a67fad0a60000000000000000000000000000000017d978d60fc89b0429c1a6424231fe9274cedad5d78d9c4ac5aa2dd5e70e8238a0bb1904bb4b6ee5de5cd1ac514c62a8000000000000000000000000000000000d4ce85a95dbc40f405f4e7ebf9121cdcd22766737c39618ad0fb3e10a6e53be1faceaa96073b2a877ab808483ec9b6f0000000000000000000000000000000016c61599ae4da787fa6db233fc28f5c56f7133d403901800ab5fa19d058fb27ecb34ca2e56ffa7628ed004c9e62092700000000000000000000000000000000001e64e4adfdafbb423b1b9f8973738c690713911f68f658d234e57dc35b9554e0f7ba345dd7920b429a12b9c74775222000000000000000000000000000000000c453b5a2f6c721f0303b6b9205e51ccab7829345e0cf13e457491ff35e0f38dc5ac2e5b0d8195e3d322704c38d3280600000000000000000000000000000000092ddd55120b22e2853983dd3dea9bad3e4bf23e46dce297aa3db85856907d1b980185c5a6e02890f24a5463e7895b950000000000000000000000000000000017d978d60fc89b0429c1a6424231fe9274cedad5d78d9c4ac5aa2dd5e70e8238a0bb1904bb4b6ee5de5cd1ac514c62a8000000000000000000000000000000000d4ce85a95dbc40f405f4e7ebf9121cdcd22766737c39618ad0fb3e10a6e53be1faceaa96073b2a877ab808483ec9b6f0000000000000000000000000000000016c61599ae4da787fa6db233fc28f5c56f7133d403901800ab5fa19d058fb27ecb34ca2e56ffa7628ed004c9e62092700000000000000000000000000000000001e64e4adfdafbb423b1b9f8973738c690713911f68f658d234e57dc35b9554e0f7ba345dd7920b429a12b9c74775222,,,invalid input parameters, G1 point is not in the expected subgroup -000000000000000000000000000000000eb7d975030858b57bae3e7a8a7e297f9d7d6b2bcf27fa8bb9b73cf60a1a1a5e6638adc726eb2e7fd5500353de95179e0000000000000000000000000000000015c783845a98d2b12a8cada673e1df4c266b34302cf7d004e00612584d0913283d43150337c36dc3912bbd757e6a370b0000000000000000000000000000000014a0222d6d0a809cb5598db18e35f5b7f5c49ec5574fbb5d801d9f890ede23d20c62a22321394b6efd1a45b9780a4efd0000000000000000000000000000000017c8bc1b8f7ebbbcd6203f6ffcc758a787a96533a77290affc18b76fc6dce7c2ba8f169dd8ecdb8b240c92664852bfc70000000000000000000000000000000001a673ad0af4bdd53027b145472d1e8dfa05029b7ba5bcb7dcc63b5e6369b2e693f27e5665167a8dc8b6d3c9b85eccbb00000000000000000000000000000000085efb596145e58b4eae7aedc8f7aff6949f1edf8fbe0c88e6dcad46423cf7b2d5f8f54bbdcf8f1427438e6a4da71914000000000000000000000000000000000eb7d975030858b57bae3e7a8a7e297f9d7d6b2bcf27fa8bb9b73cf60a1a1a5e6638adc726eb2e7fd5500353de95179e0000000000000000000000000000000015c783845a98d2b12a8cada673e1df4c266b34302cf7d004e00612584d0913283d43150337c36dc3912bbd757e6a370b000000000000000000000000000000001425d6d5cfd04d310d8ef3ff1a3b3d227771f3bb3cb552c64ca1debd3e2e75e7a96b9a0d5768ff041098d743de445e790000000000000000000000000000000008b12959963c17ce7247490d9806e0ab02f9aeed8d8faf4591b955885f5aae509358a19c1eab19dacb72cb67b9c22c46000000000000000000000000000000000a7108fb7442fa6c50b472dda60d6c277bba83aa21ac1630bd3df6f116c8a58e60e8c4a303a041b9825b65301d388e410000000000000000000000000000000000faf3042f00060eff3e5e13e1a964c9dd605677596c49cbeccd6d0374c6a4ab9742cac63d1801631d5fa5e00d399fd7,,,invalid input parameters, G2 point is not in the expected subgroup -000000000000000000000000000000000f6d131c756d031041ba97dd20719cbfc864de16dddf00d282f6c41de683d193a015a781232ea5513321c131505fa4ee0000000000000000000000000000000008d754d62b8f1fb0d0e9dfe4746d9c5f9e4a296c6799d6379a4a1a07583d0959c62b05011b636b72cdb87d3c92d94f3600000000000000000000000000000000061bf88e4c5f6bfacbf1e2e72566d9c44a001237f22d7f6a7240ee0bd3c8d175786f7c2ace267774fa5615d7bd3ec8f800000000000000000000000000000000192c987bed20d707400d3211e480b8760021a33dfbaef829b1e865f92b37cd3962e56a38d4c06c5ad73bea5e7321b176000000000000000000000000000000000dbc62270cf7623e571f6ebcd29f426f214cf1c8f71edf9aeefbeb6037b7e71bfe41a55828cd4353bae2e1e927812aa80000000000000000000000000000000005f6e7064a9708e20bce8e5002c352b13d4531d87be1320bcf3322d5e2b851a54aefaf6f9ae26e277ecad307c448511c00000000000000000000000000000000041fd1625afa48a446454d6613c17cc6a65b3ec8b8f2125c0eb7b8e5d07968397d43969a6579226f496d9b24dbb71b820000000000000000000000000000000006131c506f243b5ac40354f826ac1838839eee9f61301aabd88e499d40e57df3122edc8b36f0a8b16b72f9ac783efd3e00000000000000000000000000000000061bf88e4c5f6bfacbf1e2e72566d9c44a001237f22d7f6a7240ee0bd3c8d175786f7c2ace267774fa5615d7bd3ec8f800000000000000000000000000000000192c987bed20d707400d3211e480b8760021a33dfbaef829b1e865f92b37cd3962e56a38d4c06c5ad73bea5e7321b176000000000000000000000000000000000dbc62270cf7623e571f6ebcd29f426f214cf1c8f71edf9aeefbeb6037b7e71bfe41a55828cd4353bae2e1e927812aa80000000000000000000000000000000005f6e7064a9708e20bce8e5002c352b13d4531d87be1320bcf3322d5e2b851a54aefaf6f9ae26e277ecad307c448511c,,,invalid input parameters, G1 point is not in the expected subgroup -000000000000000000000000000000000c9856c54b638559ce67cffb3e784b9649bbeabe135c89ecb8108341a16ef3c5ac49e9243144792a4f9371f2d6f8f6b6000000000000000000000000000000000e17a67cdee23e13c95e5b951c5f51703660ff614fbba363e5a73ea74b86af6893ae90a929df3d7e2af8852dbad77a01000000000000000000000000000000000c01d2e1592a61cda87dab7fb2a45d7468a8ce56f74fd7c8d401e224b9610d675cbdc0d4719dd2dc10f47547bc641dd90000000000000000000000000000000002d57ac9ac9b4cd7a477e162965c763ec1d0c5752b478449839e0ebd7a1110374fe90aaa2006bd03f08f9242c0419e99000000000000000000000000000000000d50b11e1ea90ef8ef0f7a95ad41ce2cd30cf804e46a96762fadbc275ad513827e6e75b95968a00b42085aa89bb0839e000000000000000000000000000000001250e40358d42dd3a6aa2dc93d89010f2a7b33e5b426ffd7e6eeb9bc02f8f22fd6e91c4fae603f14ed682617dd503eda000000000000000000000000000000000c9856c54b638559ce67cffb3e784b9649bbeabe135c89ecb8108341a16ef3c5ac49e9243144792a4f9371f2d6f8f6b6000000000000000000000000000000000e17a67cdee23e13c95e5b951c5f51703660ff614fbba363e5a73ea74b86af6893ae90a929df3d7e2af8852dbad77a0100000000000000000000000000000000048dfa619f3f77c794d119ff97e04826b5bf49d71591b778b502205875b97b17c9c9d2ae95c8d7035e9d2ade10a511ff0000000000000000000000000000000002fe9b3108c8911b07e4ee52a8d4df17bebc8153f837665513ad5c8af53cd7387100204fafefc2febb2f5e18c9d958b10000000000000000000000000000000016c98a41fac6665657b00bfe9336942e7aa145bea3131224ffa35ec9b9cd4d629b4ceeca98a15ad07e03ffcf898f8a390000000000000000000000000000000011a0d16ccdf98beffdb0953c509261f18567a1656acdd895395b480ebf8843e7d3b965e2151c58e9b610e9e3b5491832,,,invalid input parameters, G2 point is not in the expected subgroup -000000000000000000000000000000000f4eebd0bb8233e020629f72258d3cec7fcd3c181fa913977a405ef7437f6fdc0ee34fe6a7e349730e695f383e4478ad00000000000000000000000000000000080577fec3a6cc706037b43cd39f4bace98191511cb26cc606c11659b63112b519123a00b62158ab7a24dfe086705b470000000000000000000000000000000011e8ecf1e341f0146c59a79a8428bb01d2399d3f87d90d057f63e6cb9837432154d17975f70df175a016735caf85120a0000000000000000000000000000000002a5bd53e4f4c5b9682e1af1f7e09dd305e7342d1688f62885b5e59f173a9fc731cec481559ad693030004a5fbd90a9d000000000000000000000000000000000f9601f95e12bf05c35deb204558d44a60fd630c05f4060b7bd9ff943946e8eab507422afe00a3e7706b8ed013f712c20000000000000000000000000000000003bf6fecc0c7414a69c2b48e2c16e88d988ea8ae9d8b59017ecb89394732a20e4321cb5e4fb071aec7d2736220a455370000000000000000000000000000000018fe22fe0b39f508823e2332712f988efcce291d93c416c544272e88cc0902d79e41b01af005feddaaef6c42a26c824700000000000000000000000000000000164fe8d2934cda6f0b863bb42d13f91c52a49706182f8d337d6a629da65113818d1c177508d6735cc86a6b3837ce49320000000000000000000000000000000011e8ecf1e341f0146c59a79a8428bb01d2399d3f87d90d057f63e6cb9837432154d17975f70df175a016735caf85120a0000000000000000000000000000000002a5bd53e4f4c5b9682e1af1f7e09dd305e7342d1688f62885b5e59f173a9fc731cec481559ad693030004a5fbd90a9d000000000000000000000000000000000f9601f95e12bf05c35deb204558d44a60fd630c05f4060b7bd9ff943946e8eab507422afe00a3e7706b8ed013f712c20000000000000000000000000000000003bf6fecc0c7414a69c2b48e2c16e88d988ea8ae9d8b59017ecb89394732a20e4321cb5e4fb071aec7d2736220a45537,,,invalid input parameters, G1 point is not in the expected subgroup -000000000000000000000000000000000d6e0830ba9839db89621518e1ed86e9d035d7fb03b18d8a615acd58ff5c8c3dbdcf1acfcb8a910e8d3d0da770057cde0000000000000000000000000000000018c38d6e458e22cdd381010568606d26770b9eaaeb38e02a80cd0a0d584577fca7b391bbabe902825a3338682f7d3173000000000000000000000000000000000e5d5bfbe5ab3da8c033663cc457cf69062e1de6bcda6420826301d4e5fb4c47a5c90432f15608aa50678ad083f618c700000000000000000000000000000000146cd56ad5d977fa6471f7c12ee3fd66dfbe4b276b846cf4a74619cfae4a5101e85d01db615e0e6d28a5eca7a92cd05d0000000000000000000000000000000010fa99a243f1de85f40bf6a08e3e9ed3e478562468f0975bb79b1b80c9eef8c433c18ccf6e5149eb99c21f74494eed6e0000000000000000000000000000000018504d4687cf9f055c5cf8360fda2cb2123ea3a3e9c8f58853a68063e60a7ba24f2d1c153090ba2e77156cbb5ded79d7000000000000000000000000000000000d6e0830ba9839db89621518e1ed86e9d035d7fb03b18d8a615acd58ff5c8c3dbdcf1acfcb8a910e8d3d0da770057cde0000000000000000000000000000000018c38d6e458e22cdd381010568606d26770b9eaaeb38e02a80cd0a0d584577fca7b391bbabe902825a3338682f7d31730000000000000000000000000000000016267be431a654019b3f02389fb2f2f307f3bb56590c3ff1924554b2ee6fbbb68381b5ba4a8668fc541dd7e3fe9afc9a000000000000000000000000000000000c41d1daa258be71001babb484d98501c9ea015aad96e398aa6015b9ef618f6a56a45d45383dc34c70ca05242490ae18000000000000000000000000000000000dadc282d8cc0c066f28881cf440fb5a621449dc16488d02e34492fe8af53fd1749886fe6df2e377190e4043d6e3dd290000000000000000000000000000000018a30f1ae8927af27147ee5ea3c7aa848b0e303d16018fc2c3ca4bd088ec5ad3a808e7699ca009a4bf6f5b707edc1fd8,,,invalid input parameters, G2 point is not in the expected subgroup -0000000000000000000000000000000009f71faeb58c4d442272ea0116a03f106008ed68030bce1bc53a3964ec4f9758f508735c8c17017645ccb77124b46cb40000000000000000000000000000000016a2ea9e548342538ccfdae6806ec9ce0b7b892875155f898017929d7105001e8de041bfcab7398cccd060dcee3b6e84000000000000000000000000000000000579462f03502615d26d76e05c758a7f835e4e70f1adfdec8c7ef26580544326d66ba4e23be3c078e1f187ab498ab9dc0000000000000000000000000000000000d386d0af32f630bdf6c13ee82fd35e42961ec4b11ee8fa196d69bed775b8ecfe74409693c531631aa716957e5fe6d7000000000000000000000000000000000ce8a020608c1e7d70d7a03d11b84f48d6b3b77c5836b7c914627f3a84d3d0b3de513029c1379b20ee38a19928e57f44000000000000000000000000000000000dd10882001be7fa52ff21f1a0adab02e688539f098901fe515b61fbdbca02c4146f6c17f7ad7cf59f5e8acd85da60b000000000000000000000000000000000128e019ff92e7171d3c791bd4cf75b0f47c2a9d8722b4a8279f1178db6dddf8a4c00083a935168518a1c26a56b23624f0000000000000000000000000000000008d0c5f3300e73682f4756e6ff1d6722dde576beb587301ded34427d6935e59e76cc8a8cb0ea5f659db9ad5435851e53000000000000000000000000000000000579462f03502615d26d76e05c758a7f835e4e70f1adfdec8c7ef26580544326d66ba4e23be3c078e1f187ab498ab9dc0000000000000000000000000000000000d386d0af32f630bdf6c13ee82fd35e42961ec4b11ee8fa196d69bed775b8ecfe74409693c531631aa716957e5fe6d7000000000000000000000000000000000ce8a020608c1e7d70d7a03d11b84f48d6b3b77c5836b7c914627f3a84d3d0b3de513029c1379b20ee38a19928e57f44000000000000000000000000000000000dd10882001be7fa52ff21f1a0adab02e688539f098901fe515b61fbdbca02c4146f6c17f7ad7cf59f5e8acd85da60b0,,,invalid input parameters, G1 point is not in the expected subgroup -00000000000000000000000000000000167a40d69e2d2e5683ec69af7b226c5456d05241d9c9eb24e988ee2dba845eab361a956cdea64cf971dafea08f0056260000000000000000000000000000000014e5b8ae5032a924da081d3063f31889da110d00330903c6f988684b7d77b7b1bdcbefbf70941d0eb5a34bcb010a3bf200000000000000000000000000000000043a000741027fbd191e2399b7cb5fdb4db3728d81d8b9d25362e8a1d397ec99d1b2339a3a511d0969e7e727f98d4163000000000000000000000000000000000cbda70eab332bf962d123aaf08b8b96680055f1946dd5fdb8818fbb330b816d83062ffaa79e18f1f4f6d53deda53cf200000000000000000000000000000000160a7048e508da288270e8ac5793e9461eaa282b85ce5350b6a661209efa6699874aae71515dc4265125d490a5771ff900000000000000000000000000000000041aa7292f258125d729c1761a3a6f7979a7a92ff179be678ebe301de3ffe12e4a863becbfb2bd0067e42aefc5f5617200000000000000000000000000000000167a40d69e2d2e5683ec69af7b226c5456d05241d9c9eb24e988ee2dba845eab361a956cdea64cf971dafea08f0056260000000000000000000000000000000014e5b8ae5032a924da081d3063f31889da110d00330903c6f988684b7d77b7b1bdcbefbf70941d0eb5a34bcb010a3bf200000000000000000000000000000000158feb06ab69f5da37037c554aa8b4b511e9de7e13a08c2f9a8899f5a504b191195e4de1ab7d937913f60e24a1b3d3f8000000000000000000000000000000000beeea216d85a767af2facfa7fa0399c095a23b7a974ddc3d36b7e79822d7691e53f85c976b8cc948e155f196b7f168c0000000000000000000000000000000009802e94ad5cd6b071a0911cbbc1de29a79a5a4fc145cc79db03119c3c8dac88fa3740bdb35b0b0e6e34127ee1bb081c0000000000000000000000000000000004599d1e0bfe642ff275fbf1dc86bbf83b87c241de09570183faf4e2c1cefc6b8b2354aa2be4aa9f69d6d61546d3a685,,,invalid input parameters, G2 point is not in the expected subgroup -000000000000000000000000000000000130761fb4d4720b8e69b849e1472f15c24cf909920c598aaecd34c5b420a1989a6ea95f89c362a61e4a11f88982f9a6000000000000000000000000000000000d707b24e8720ecd4a14d9f658060303d233253caa63bafd0f475b2985fcebd326924c936a9d66b21b9a75085aaeb54e0000000000000000000000000000000009faa74f66ec0384f0458893c0026f73688c764e8df9ce056a88a2ed0b84ed8f88d1b683443a3269a3db838f8aeb808a000000000000000000000000000000000949c4be2708c1aac86aff39290ab6a8e0f332e7a098bbd64227a175473d9dfe136e07548b282f69a94a15e2c32dada10000000000000000000000000000000014f2c7c7da781e2f50803e3a948381c3c439b127949f79824df1e5722c206efccd6c0ec5dd75ef63d8b1fa301c83356900000000000000000000000000000000176753460d241f38aff41bafdad51688ab0dc9a5fb3643977c7b9d282ad4532fcca1e725715227780ec28bf1c32bbc1d0000000000000000000000000000000010ad2405965f283c845edf92cf34508c0ca625816690d739fec9776d261784a946e083112d11c0776edd04403eaa5b820000000000000000000000000000000007e400896eaaddf797643b05a53f612f73737a2c438762d3f6216d53761f28bc88f402a4f02f36d26bbb431616b1b5690000000000000000000000000000000009faa74f66ec0384f0458893c0026f73688c764e8df9ce056a88a2ed0b84ed8f88d1b683443a3269a3db838f8aeb808a000000000000000000000000000000000949c4be2708c1aac86aff39290ab6a8e0f332e7a098bbd64227a175473d9dfe136e07548b282f69a94a15e2c32dada10000000000000000000000000000000014f2c7c7da781e2f50803e3a948381c3c439b127949f79824df1e5722c206efccd6c0ec5dd75ef63d8b1fa301c83356900000000000000000000000000000000176753460d241f38aff41bafdad51688ab0dc9a5fb3643977c7b9d282ad4532fcca1e725715227780ec28bf1c32bbc1d,,,invalid input parameters, G1 point is not in the expected subgroup -000000000000000000000000000000000220d192e0c635f05870113aac899f996622cd21ad251fb4c300a0ae0051dfd93b0b622f54e149fc4e5bf280ef31b0c6000000000000000000000000000000000dcfd7def563a0937b5173d20f830e6a2350a9d6f1b8a2193e5f755142e850bb7f95519ca187dfe735c197c21688528c0000000000000000000000000000000002479a989dbf27141bd9f467447218dfa6ef60781a7231f089d5f1f1d8dca2ce9606a75c09f63f37f9cc1ee61dceb32500000000000000000000000000000000037c2f1b96170f6847138232bac663e4940bca602717c877f58ff7f5259778246085d499ec6bbeaade18f738df333cc700000000000000000000000000000000115a30e7b71e48efc7166b9a8d2e1ecec6c6e4c058065a18277ca547cf1ebc193e635eb9b65d14c9c94ebde3c83a55dc000000000000000000000000000000000b68916150436f74c61b071a339909f78701d1f3045f7201f076991d7447570fd90887126ca54d96c1310e59ad489190000000000000000000000000000000000220d192e0c635f05870113aac899f996622cd21ad251fb4c300a0ae0051dfd93b0b622f54e149fc4e5bf280ef31b0c6000000000000000000000000000000000dcfd7def563a0937b5173d20f830e6a2350a9d6f1b8a2193e5f755142e850bb7f95519ca187dfe735c197c21688528c00000000000000000000000000000000147610c1075d625dc36388a2bd451ad2c15eae8cbd30393bb9fa30fc233b29a0f5483579752a748bc0f5ca9c90875e0e0000000000000000000000000000000003dff69511f2a53e859b6769ef4c257acf31062eb72f8668f57da60f225ac052badef9df25d43148b4d3baa64c29eccc0000000000000000000000000000000006d2a71dd9c1b302e22c4cecd14dc385e068e63e28e167c060de3ceac8410461d83f961c9292cad30afc0084855ebf1d000000000000000000000000000000000890ce53931aa18e42f00379b40c8e205582f71707a67761acced851fc91755505ae960951dec4ef46353b66e0b928c1,,,invalid input parameters, G2 point is not in the expected subgroup -00000000000000000000000000000000050ae21faa31371d791e3f6163f1c18c577367badb337aa6fa7c8214ec58bacfb24300a81d8226e81d7f47730c734386000000000000000000000000000000000987a0c7abd7c72140bcdba9cf171389ea7971493187c567b0970f05d129b4202124d72bd01038bc77023171de379762000000000000000000000000000000000d4b71595321913e94b9e6ee6ae2391cd5037e8b55e61fd96c745539bcb4bc4fcbab678749f6d47fd2c95001da4715f70000000000000000000000000000000005436e1be9029a2f7cfac51a305fbb7e760a9171c79a5bfce6b161493fb2855df09b3345ddbc8b04318c6b2e0225b74a000000000000000000000000000000000b8f317d36c50c8d039ad137f7d9adbefb3fc147a9cfd6cceb02c75b95fe307c3d6f673a216484f3211f045c2ba9012c000000000000000000000000000000000156bc67e17c8eaa905fb7d9f3f251cc81eeecc86de791c8be30c34aea896755fe2bac28cdb035222afe6237614878c300000000000000000000000000000000065856fe1dcbef934cef47b177ecb7df76cc8796624400d5c0518aa9438bcadf397234808d099bed89ab674560ffbb1800000000000000000000000000000000071b2ff64379ed3e20cda000602c3504616dd673aebbe7690e797d6428ecfbdb29f11138169f3462dffd319cad68b96e000000000000000000000000000000000d4b71595321913e94b9e6ee6ae2391cd5037e8b55e61fd96c745539bcb4bc4fcbab678749f6d47fd2c95001da4715f70000000000000000000000000000000005436e1be9029a2f7cfac51a305fbb7e760a9171c79a5bfce6b161493fb2855df09b3345ddbc8b04318c6b2e0225b74a000000000000000000000000000000000b8f317d36c50c8d039ad137f7d9adbefb3fc147a9cfd6cceb02c75b95fe307c3d6f673a216484f3211f045c2ba9012c000000000000000000000000000000000156bc67e17c8eaa905fb7d9f3f251cc81eeecc86de791c8be30c34aea896755fe2bac28cdb035222afe6237614878c3,,,invalid input parameters, G1 point is not in the expected subgroup -000000000000000000000000000000000e4bfbc392b1de6b17a32387d694f634cb484be4531f425d72833a30ed5aa73a958d5081b30cf98484762ba51542f87b0000000000000000000000000000000010e6acae9df1b5567854f4bbca0ab07c6a0e726bc6ec7a1d2ce8b04e1677776d0ee2642950db040365d581a52a41832300000000000000000000000000000000036a2ac8ecf17fc72ed792c0d8939060117aa0d6c13854fdcf56ed0d1ed3b39da9a67aadfdff484850f9cdf439495712000000000000000000000000000000000a1bd875b74e1ebec19591eb137e68ca7f0db1b3056d341b6bad28c3f45e87688e73fe28e9ef44c7d494442ee0b9472e0000000000000000000000000000000002e26e5a36c008bddc431048d999b7fb44961bb4d931b2dec0cb1d1b0987587f44cd31d429a6cef05d3c060ac828ba7d00000000000000000000000000000000050640087ed6c04ffed759f63e211ff5880c8b06933c1e812954a7a4240a9c644175c4ca3048a4ed68d034f6cbdcf175000000000000000000000000000000000e4bfbc392b1de6b17a32387d694f634cb484be4531f425d72833a30ed5aa73a958d5081b30cf98484762ba51542f87b0000000000000000000000000000000010e6acae9df1b5567854f4bbca0ab07c6a0e726bc6ec7a1d2ce8b04e1677776d0ee2642950db040365d581a52a4183230000000000000000000000000000000003fea73d9ecfb879ea601865d3279ef9b271fdeb14e1094745d5718bf214f1fba99245746716bbf012e2ccff5e22887d0000000000000000000000000000000011b1aa3049157abfa01f3bef33583635873d4bc66801785f41ca51b9a11d95b49a8bbaa543fbe68db86c3560ae258954000000000000000000000000000000001740661de7e1b6e1eff626acc4e37c877d00a0ceb8008d99e76a1dfe826b4dd0ee69b150535caa20386d644394dcf6b800000000000000000000000000000000090c0bc61286187a58f2cf49e2ce6f49d721b4146d973dd7716db271da770450dcba33b7aae37d6842a43063c88704e8,,,invalid input parameters, G2 point is not in the expected subgroup -0000000000000000000000000000000002cd3682c1f87aac1ea91b3f4ac577f0c9531c7702864c444b4163435b4770c9a2ebf94fae2d45a63adecc314d9d536400000000000000000000000000000000103a1eaebc0ee53e21d5bda78bd912f20cc28a13e5e209e062a52ddec3453fa9c364a6b403a68d9db8a40a07755ee1eb00000000000000000000000000000000024494aab30849df790185a4f939954b724c387c9a366fbe833b628577654174f705d05e7d7dbcd29b8873aecd55df0b000000000000000000000000000000000863054fe3e4838d2caec7103e3d0453e86a17fff0dfdb84dd819f31756032e9e97b7be89b636e5e0b642718f6da217b0000000000000000000000000000000015c8bb4fcb6d9cf941b722136d8d76d847fd6d5c643f4c0049c9746e76e49726fd463ce7899f4df66d04e5d48e523e6a000000000000000000000000000000000f101bea4e1bf610d2782ede91da95eb2b0be9ce60485465b9e94cbb9530b416c4394862f0ba7ee8067bb48e94c07c530000000000000000000000000000000001b32ce5c51441e1abbbcc0b6af95380f2de24876ba377ebea44fede8886acbe23aa18681bb08130252bc04193939853000000000000000000000000000000000e5d718c9980140b41fb971fe596070982bf92937df77ef44040ffc27162bb443a60f5c64411ffefc24524ff32fa17ff00000000000000000000000000000000024494aab30849df790185a4f939954b724c387c9a366fbe833b628577654174f705d05e7d7dbcd29b8873aecd55df0b000000000000000000000000000000000863054fe3e4838d2caec7103e3d0453e86a17fff0dfdb84dd819f31756032e9e97b7be89b636e5e0b642718f6da217b0000000000000000000000000000000015c8bb4fcb6d9cf941b722136d8d76d847fd6d5c643f4c0049c9746e76e49726fd463ce7899f4df66d04e5d48e523e6a000000000000000000000000000000000f101bea4e1bf610d2782ede91da95eb2b0be9ce60485465b9e94cbb9530b416c4394862f0ba7ee8067bb48e94c07c53,,,invalid input parameters, G1 point is not in the expected subgroup -0000000000000000000000000000000006e340bc57495a5c01bd44eb6dee60e94feadd69bd0274e7c9755f7b0e7b4e59eb70a2cd7e4da9d1a5f60d93a5df19080000000000000000000000000000000013d7c393a2191ac8d13a0743fecd768a7e15d279be409ccea1d58ba39e91f592b425955ddfc8443861a2899de02cd0fc000000000000000000000000000000000369067d9012509bbc75333fcfa37cd42dc3d6331a55b4720f9efb937916692ed7c7cf4739142ea13fb9130e9dae92fa0000000000000000000000000000000005306d73dd33e26ece5d8cf2ea8d7e25976f7b95dc7091420c2e91b1d0b2ef96db69a364c3cdc3b24a49ec5e307553490000000000000000000000000000000008a9fe15ea4e16e675c42c25b29655683844949b9aff4448f0b79ff08ec2a6526cad973f80123943b863ba9f30b45666000000000000000000000000000000001754caabe499e1ad04ae65d696507096939dc826578db55738e5024601eb052a9fc15f20b601253533847f79a5f6a6ae0000000000000000000000000000000006e340bc57495a5c01bd44eb6dee60e94feadd69bd0274e7c9755f7b0e7b4e59eb70a2cd7e4da9d1a5f60d93a5df19080000000000000000000000000000000013d7c393a2191ac8d13a0743fecd768a7e15d279be409ccea1d58ba39e91f592b425955ddfc8443861a2899de02cd0fc000000000000000000000000000000000ddc7df1bb36d54beb969a244a67c3ae87cab2eddce9397869421999497ab00a3e0ee29e384f3182b52158304790b05c0000000000000000000000000000000006f527de4412dd61845ab1e4536a114c7ac2bee2f3d22c16c660b3189b1666849f70462d6d650846331dc630dfaa075e0000000000000000000000000000000015aae1fad9e6cb3adc8bd93c432c9321a9d2f7cb961671c8e517da7024d1e35b519a2cc662d795173441479fa35f2e830000000000000000000000000000000008485999f40a4a363ddb3da273f7522344f284fb5bcee383a590c7c1287baa10f21312428d6818eebc96ec59d716fae1,,,invalid input parameters, G2 point is not in the expected subgroup -0000000000000000000000000000000015845de9692590d7fbf4187b50f4b2d067983b0a2209ed2bc6d60914be106decc5dfefba112baaf98ba3bf9e4d102b660000000000000000000000000000000004f2ca0858d056f769bc8e6ebf7cca745ca647ba497940b25b31c0541297626cd26ac10f8ae8476b5c868a95226da290000000000000000000000000000000000b1374a47c7c1c833f3856b0fe5ecaefaf2a8f96148eb540482288b56897d9e7e4269ea3a2c3742993b751bd9e484f2d000000000000000000000000000000000bc7fadac70d0a401e61683562cc83ffe107924ba1788bb6e06b0c60f22de0d93b10b63afcca343cad0572209b03b12b000000000000000000000000000000000c22c75d826d2700a8bad4e9c271d8b505ab2911dc257909c69dfdde2bcf332e5f13592eccabf578f48f6078550c1e9c00000000000000000000000000000000057db54159019d1e291131d28a936ac1337f2884f0c4bfc4d8adaa75bc0edf8b0f3030725e33a3d1a2e7e9ea39512fc70000000000000000000000000000000000232940188006769a382a4958383aa6702b2cbfb9c2907a989938ac618f23e241767b021e8ae11c23617ab393d4f85a0000000000000000000000000000000016a672061fe76ed943e36b2d6fa4aadf579db96eba5e4c60cda2884ddcbb0f37668638a3167d8858cd296917eaeff8e0000000000000000000000000000000000b1374a47c7c1c833f3856b0fe5ecaefaf2a8f96148eb540482288b56897d9e7e4269ea3a2c3742993b751bd9e484f2d000000000000000000000000000000000bc7fadac70d0a401e61683562cc83ffe107924ba1788bb6e06b0c60f22de0d93b10b63afcca343cad0572209b03b12b000000000000000000000000000000000c22c75d826d2700a8bad4e9c271d8b505ab2911dc257909c69dfdde2bcf332e5f13592eccabf578f48f6078550c1e9c00000000000000000000000000000000057db54159019d1e291131d28a936ac1337f2884f0c4bfc4d8adaa75bc0edf8b0f3030725e33a3d1a2e7e9ea39512fc7,,,invalid input parameters, G1 point is not in the expected subgroup -0000000000000000000000000000000018611593c7663a36330e0640bc912221a17d0ec9d2a250f1701303249655c7088bdc07b024878a260753d1dde306fd4900000000000000000000000000000000069d26308af5907c15532f31dca8c884b9e54a0da5e428c66fd79d980774cca106e2ccf3f93fe3e01669c594b9770a800000000000000000000000000000000001a38d296c4b7b8351164b935b915c08caf6af9d5233ecfe609e4ed855589bdbc9fb0adc55bb5cc6a2526bd82ab9287f00000000000000000000000000000000184ac28f62c7101bec49879af5da794740f9ba99afab4fcb576fa1149f3b701079915934c624f022b0d3213adc884c2c0000000000000000000000000000000004f64835227f459e76aae3397dfe53eddb1e97c8afd8beaba09382fe79ab378c3f03d6962dfee823c426cab426e51d2d000000000000000000000000000000000577263fd875f6388b723f6abc78aa3b0cc2e6b0ab53beb59286e30d1e982114c161fc0ef490bb1347fd8a3f3cba72710000000000000000000000000000000018611593c7663a36330e0640bc912221a17d0ec9d2a250f1701303249655c7088bdc07b024878a260753d1dde306fd4900000000000000000000000000000000069d26308af5907c15532f31dca8c884b9e54a0da5e428c66fd79d980774cca106e2ccf3f93fe3e01669c594b9770a800000000000000000000000000000000000c072a812b26927b5a672359032e71597002d8c45572db6c575e25c81181a522ca304867b1bcb583c58b2ad6bb1695d0000000000000000000000000000000003c4f5f548f50e835f402105cbd315081e0e8773dd91f3874687ace6fd5ffa66b6ebf6dac4580357b870f8835ed15cdd000000000000000000000000000000000f039e2e73334386b28ed4cc52db09b69c942c0c87f81328a9b7fbe8553eed48a60224ae9d52baf4f62a3fb0d58d451d000000000000000000000000000000001974da8867204f048b81d0c7a90d224dc5b87f3c4f47d1dfb09afc292430bf51d69e3538fdd13ca0749dee76c2ba9b17,,,invalid input parameters, G2 point is not in the expected subgroup -000000000000000000000000000000000754f3a9ea93a01fa244ef6badeac64fbb4dc81d3489c4195a01148e268e2af46011e6c593b4b873fa1470eecc6f79ef0000000000000000000000000000000018043830aa55a8c7efd5dc246622c71bbb4bc6a5d69cf594f46f62d8c3e2a5d4ba1d3990b5445f8b446e404a00259a8a000000000000000000000000000000000e49e94cfa35d8ade2b76865cc8be04737d00b48b195078c8085cbe782232a544cdb548373bd8ad0282674ba5c96fe0700000000000000000000000000000000047d59661f095c41bcc27da5f260f13a3fce334bba216b45df548894bdebc691fe779ccd63d99a9872973ab165a90c01000000000000000000000000000000000772e9a9c22bc7352fdf74915bc464de99ecd96420ef1af6e8bd5a05d73fff89c78e28eb340d4967e906f28afe1320490000000000000000000000000000000018bccff27bf9d7cb2159b9f2d1faabbf8591b53ca8e67e661d9f44f6dba6296e3e46ac32c50128bb5fb076cb8f214e27000000000000000000000000000000000252149178c606d2d6c0311e9f4a66cf348869efc09ec887cf99088ec754c01551796aaf168dc1a09cb741ab3c9d6891000000000000000000000000000000000db7baeeb5acfb22d680e032965a4d417b2f2f6717d3667d786e006327140c1288ff44842142eb1d2730b3be64fcf420000000000000000000000000000000000e49e94cfa35d8ade2b76865cc8be04737d00b48b195078c8085cbe782232a544cdb548373bd8ad0282674ba5c96fe0700000000000000000000000000000000047d59661f095c41bcc27da5f260f13a3fce334bba216b45df548894bdebc691fe779ccd63d99a9872973ab165a90c01000000000000000000000000000000000772e9a9c22bc7352fdf74915bc464de99ecd96420ef1af6e8bd5a05d73fff89c78e28eb340d4967e906f28afe1320490000000000000000000000000000000018bccff27bf9d7cb2159b9f2d1faabbf8591b53ca8e67e661d9f44f6dba6296e3e46ac32c50128bb5fb076cb8f214e27,,,invalid input parameters, G1 point is not in the expected subgroup -0000000000000000000000000000000003455a16c1cf9a408bf17cbb0b8ec2a5f24ba41d5b8a06a10750797f3371bcf361d3b6902d73949df3a24f5a8b9977c10000000000000000000000000000000016480e13653604d05f025b8264fb35a2cf06dc6a90ec7751ed80ee81cd064864655d133ce398e293c289a572dd98605e0000000000000000000000000000000015015aeb1965917cb5b55092d7eeb54915e21fbd5b9a62530b3dd5b8ae07d6f491df46e1987f565223a83cbc90f91735000000000000000000000000000000000658266a6bb01958b791b288ea9f5e138316724398218be522bf816ee5b5b34ef4acc83b82959f52792af6940816bb41000000000000000000000000000000000bf8d18b55a57e67c76882f1a1ad845480196f28556ef569a6a6054426bfa39459ac030b594201be76968cc33c301dd4000000000000000000000000000000000fd8b264f9bd71e00e3987cd221d2e9fbbee34ddcc5c563f02dd150451050bd20b3bd3a6ce1284fb0ebff0c6c1318fa10000000000000000000000000000000003455a16c1cf9a408bf17cbb0b8ec2a5f24ba41d5b8a06a10750797f3371bcf361d3b6902d73949df3a24f5a8b9977c10000000000000000000000000000000016480e13653604d05f025b8264fb35a2cf06dc6a90ec7751ed80ee81cd064864655d133ce398e293c289a572dd98605e000000000000000000000000000000000c5c8e759f71292c8b25dca51bfd40daec58d2d2befe991b27993ae32489b3ef70c0c3f873b3f47ffb013fd31732f763000000000000000000000000000000000a78f4e402668544cada859953182f14421c54446c264312daa5f27cb18a3955a277e5620b0f28f4f9bf233c49f3341f000000000000000000000000000000000e27d9b1bf12c01ab05fa5ed5b2e70444c0582aa5b430d0e10feb69dcddddadeec88c024ec87e23737ec1385461edaad00000000000000000000000000000000065984e81590e368aa6de051129761e94ca218df58ec0132ec22aa7d4128b9ace20e7da35c56a674ed1031d202e9f313,,,invalid input parameters, G2 point is not in the expected subgroup -0000000000000000000000000000000005a54ee5e3dc05c38ade7a42c71baf5a1467938f97c0cdf0742441cd339f22542b1ca6cd215d385b3fd6ba74ec996a520000000000000000000000000000000005f63d18f8bbc1c7b119ee0d58b688477129b2558be0084865297fe119224aec83b3633a646a3d423029cb52f0294aa20000000000000000000000000000000015e2547357626e6160105e5254d8deb80155c42d7b0c13bbe350c3395317913ccea5db61494aa2634857a8c83baa42a0000000000000000000000000000000000629ebc8d1798e2f9280493d2de7c159c558156782487d307ba358fd2bf696c29518d6cf2f975509bdefa89033f1cfa20000000000000000000000000000000011e3f568b9d1793519d5a8cd3bac7cf35091665f981ecb7a9e942f630c5f18fe7cd9747ff539816993b70573410410ac000000000000000000000000000000000889dedf6f29ed9959d8eb7276ae6122fec5a1bfae72c793902e1e3062be444b89a95129dd59f74ea0849b5a2aefd48b000000000000000000000000000000001963e29f92f6f72be2afa4635221b0d2f6afe9ada4582bd7ca4b77eb77fc4503578f38fb49aa1838751db8cf1ca0b0cd0000000000000000000000000000000009856a48f12966554afbcde1971499ee3ae40c9c5c3aef13bc415fddb97545ed84d5f50d2a26b9c16c4403a487dca6140000000000000000000000000000000015e2547357626e6160105e5254d8deb80155c42d7b0c13bbe350c3395317913ccea5db61494aa2634857a8c83baa42a0000000000000000000000000000000000629ebc8d1798e2f9280493d2de7c159c558156782487d307ba358fd2bf696c29518d6cf2f975509bdefa89033f1cfa20000000000000000000000000000000011e3f568b9d1793519d5a8cd3bac7cf35091665f981ecb7a9e942f630c5f18fe7cd9747ff539816993b70573410410ac000000000000000000000000000000000889dedf6f29ed9959d8eb7276ae6122fec5a1bfae72c793902e1e3062be444b89a95129dd59f74ea0849b5a2aefd48b,,,invalid input parameters, G1 point is not in the expected subgroup -0000000000000000000000000000000010df7793a66234599791ae07db9953bba83492389b150a05063105c191c6f32559b9533f1875ed154fa813586e84d0c40000000000000000000000000000000018de32e27e5c97b8b39380dde927e2143bca5d5591d770b2a7a77d7b59bdd533f04c41aef3a5a60df55ed90742869b7400000000000000000000000000000000182f0aa672674c27d8f3fbcc0fc7c1608c01188cf5c28b8ac08d6f99a4e3e214aa3ba61a19607b513b4d01f1d4424a990000000000000000000000000000000000b47be5b5249eab6f67aca9c9349d4a83956550cd21856485132c26e3be3ca133050d79507e2e8cafb67b44a7f4b4da00000000000000000000000000000000060efe7e7a4ca52063b49f6c839666deebe2e8e563de18b210fb477e86420aefa38f89e926cc41334e80f4d47d810d97000000000000000000000000000000000c2243c2a34286b146462a643979a72e75f7ff31f9f043164a5514d3e5da8b0cd891e97af2dd3d6c6f3584b390e5faf60000000000000000000000000000000010df7793a66234599791ae07db9953bba83492389b150a05063105c191c6f32559b9533f1875ed154fa813586e84d0c40000000000000000000000000000000018de32e27e5c97b8b39380dde927e2143bca5d5591d770b2a7a77d7b59bdd533f04c41aef3a5a60df55ed90742869b740000000000000000000000000000000015f7e6dbb904f4f1fbef282667d77c846c0dac6d78f4daaf32123b4326f1c6e42b15cb06ed7b3b46c8877e684ca7f9de00000000000000000000000000000000004a87f0ef3285709a3ded240f529e8e10991a82c7fb40de0bd256a9fafdbc212eabb8121f52c6bf67a45bf36246921c00000000000000000000000000000000156a1d58dd64734149b1e37b1b69fa4ea4452c40d2d041f5cf3de3adadf3bfdf433626fb96c46f9104b1c16114108599000000000000000000000000000000000fc3a0e58ed3fb583e2e1c3b0db5fac2a6d75694aa3d0732243b5a75688fa0ffb5fc9023545b9585df224ee9f6c5d171,,,invalid input parameters, G2 point is not in the expected subgroup -000000000000000000000000000000001639642196e9d6d720e17ace198232d1c0a7c05660c2766b9d28147504308ee48d949755ffcd1c95e024fa299657c8d10000000000000000000000000000000017a5975410b3a80e339b200ec63d7b7d550514653726e3ae6a97a383545118bd4221bba3e27922a826057b244ad025f30000000000000000000000000000000017aa7a3f1ebbdec6abe12abea12ef50a3daabbf96a5f2ebfb517885f0b7aba1e927c682b15521529cb9e1f87c59be99e0000000000000000000000000000000016e23f7effbb9dd34ec1f6974115e7f0d23cc4553d86e6d61a0c98f47d09510e06b3f987c5bcf4bc30e20ae9684da74e000000000000000000000000000000000f3905dd4f99cfcfa6152db53106b4d1f6e24518a822da9388d8ca1dd654a4b8315697328571691f105d1abe9aad3dae0000000000000000000000000000000006bfd10d33df9326a55b35aa6d2bc3e831d4c3b5959aaa35613156e5e19343b74e34ed2670c43ba1a45cd3d91f055c9a000000000000000000000000000000000024c53fb66f77329f70394626073ae298c6aaba115aa6af7bdaf3d2fb74a07441d46eeb398feec036727e86891db2030000000000000000000000000000000016b96c27d4342c47caafa584ec9847c79870eaebcce158535d8da95d7a847ecdc5057425fb3dd862303d1ac03162317e0000000000000000000000000000000017aa7a3f1ebbdec6abe12abea12ef50a3daabbf96a5f2ebfb517885f0b7aba1e927c682b15521529cb9e1f87c59be99e0000000000000000000000000000000016e23f7effbb9dd34ec1f6974115e7f0d23cc4553d86e6d61a0c98f47d09510e06b3f987c5bcf4bc30e20ae9684da74e000000000000000000000000000000000f3905dd4f99cfcfa6152db53106b4d1f6e24518a822da9388d8ca1dd654a4b8315697328571691f105d1abe9aad3dae0000000000000000000000000000000006bfd10d33df9326a55b35aa6d2bc3e831d4c3b5959aaa35613156e5e19343b74e34ed2670c43ba1a45cd3d91f055c9a,,,invalid input parameters, G1 point is not in the expected subgroup -000000000000000000000000000000000680d1062d24bb96b09d9dc9791364b5138e7c36c163589f919354101459de2fa76440616351d1d9bcd961673b106684000000000000000000000000000000000ca650f50b670befbf16d65b486e5c98dea6857862cf23d7bdd2dee2abc0855273495b047431ed03bb1c72b5403c2a4e0000000000000000000000000000000015b2586a23d909e6fd7ef6e58595817bec1389faed80db6d59db219435e7fb1b6492178a849c12bf6418341529d141330000000000000000000000000000000013ee3539c49e0c26e78f46c67b1e3993eed56c72dda43936b419e1340a3aa223ca09a2dca3ca56f2f9578b4a3f885aa00000000000000000000000000000000018e5fd242eca2314b3ade4a1e913177a499d72b539907839c5025b7de69efa24b08b3eb1e85fd05724db82b29edac344000000000000000000000000000000000478706e91d6213e4d4fd9a6c5051c88d86c7271aed7c74ef3b43e904f8ccfe4108f94660807316e8f1eabb85b734d50000000000000000000000000000000000680d1062d24bb96b09d9dc9791364b5138e7c36c163589f919354101459de2fa76440616351d1d9bcd961673b106684000000000000000000000000000000000ca650f50b670befbf16d65b486e5c98dea6857862cf23d7bdd2dee2abc0855273495b047431ed03bb1c72b5403c2a4e000000000000000000000000000000001600e44d53c706898736f5cccab585af5d04f7da91cad6f97b991b51adbc22ce27744f12c0a24804b6fc3dcc53011a59000000000000000000000000000000000751b090922b29f5c299340b12a56a77fd9c32f27995a59cd032cd09ead834d9faf5e151fbf7a0d810738de1beba5b4100000000000000000000000000000000114e35fcef45e87312a90fca21fef50c46b8a0f114df5c67ac9872ccadf858f319977138f03279ddd1edcab4602838eb000000000000000000000000000000000145c6cd707efd8d271dda5b9af26173e337a169c01b28a44f5d4ad06b125a05e867f0b1f873bbc048ea6cec06967c33,,,invalid input parameters, G2 point is not in the expected subgroup -000000000000000000000000000000000d6427dedfdcd624c896ca8e1ef717e212985ae23a4f830ee466c75b37ea2e994e7653b9717928f1be4583aae73f70040000000000000000000000000000000017be03dd7c9c14000aaec774812a8d8bc4aa2f1ecca9a894fd1385864b28a38091d3fb5706ee7c36086c7d50e35eb5b9000000000000000000000000000000000ce359fe7d997e151b94af2f5e167aa4834caf5a07ff056fe049d4b2c2780b35e8ecf9426444da4725a3e66de6691d960000000000000000000000000000000004c7ff987f866ff3919fb4769cc704928af09406c8f5a39e6fdcde5f4ef8a188cff406f853261bd3abd0f67c6cad1f3f000000000000000000000000000000000914584031ca57b9b0bcc35fbe76d967aee164b5eeeaa5e29c02901194fb0f88f5a249040c37ab47a715d34b2329a2b30000000000000000000000000000000007a42352ff521b8e6267a5d0dd1eadcb63db1fec68cff31bee1b2080b451ac731caf0efb86758b26b0c78df5cee8864800000000000000000000000000000000141d878adfaa6a3982cd0de93b4d64ba840a07c026ca443d6d4c2b6c36cf882e109d80df63b1626c112f9a89809788080000000000000000000000000000000005a5888d22a2f654a58d9a03c68d59cde9ab5e5356b2288033ba58fe2dbacf533e59344bdf30eed07698261d6269fc70000000000000000000000000000000000ce359fe7d997e151b94af2f5e167aa4834caf5a07ff056fe049d4b2c2780b35e8ecf9426444da4725a3e66de6691d960000000000000000000000000000000004c7ff987f866ff3919fb4769cc704928af09406c8f5a39e6fdcde5f4ef8a188cff406f853261bd3abd0f67c6cad1f3f000000000000000000000000000000000914584031ca57b9b0bcc35fbe76d967aee164b5eeeaa5e29c02901194fb0f88f5a249040c37ab47a715d34b2329a2b30000000000000000000000000000000007a42352ff521b8e6267a5d0dd1eadcb63db1fec68cff31bee1b2080b451ac731caf0efb86758b26b0c78df5cee88648,,,invalid input parameters, G1 point is not in the expected subgroup -000000000000000000000000000000000da87318eb51b90ca822bff1df4dbc040fb1d74129242d832e1096e813dec5d91f950f44bbb07980dcaaee3366f03a0c00000000000000000000000000000000157d5f5c8ad06af803b9362b22519a23def6dbb700db517c85a663bf1bef9665d6e81e9b02ccaac97f0940d223b83ac400000000000000000000000000000000176835484cf24c47b154b7c35877eaf5194e0e1d8f053842fb5ff8fa833dabebc887f3d34b825fe9cf2c374a2066124a000000000000000000000000000000000d31805157ecafb751536c484fd0c81664de9524a1420c969d54260dd5264bc5454a3234d1e5b090bbb8ee1066b685ee00000000000000000000000000000000196b4f58c12a7d7ac4d720cf9b6c44efdab88e06dad0023a01572cc2ba7bc0a4baf7fa45c06f04ff3d067ba191a84e6000000000000000000000000000000000038cb5d328ac9d1fab9c402b8ed9e72ccd462ca80075132f6be141457ec25a6c84a15e42b78cb64cf05ef18b003e4652000000000000000000000000000000000da87318eb51b90ca822bff1df4dbc040fb1d74129242d832e1096e813dec5d91f950f44bbb07980dcaaee3366f03a0c00000000000000000000000000000000157d5f5c8ad06af803b9362b22519a23def6dbb700db517c85a663bf1bef9665d6e81e9b02ccaac97f0940d223b83ac4000000000000000000000000000000000065c10395b93e8d04f5eb67d8e06e8649139d261ad8fbf5ff2c7d6d364c87837fa8e744834ec9341c714318195896770000000000000000000000000000000009fcc05ee486d42c3b2d480a309e4bb19563f7a27b4a10182ccb2d233ed42943ba472681d1f847249cbd11e4d79ea51f00000000000000000000000000000000053a516998d7c2b2bfa70349bf3f5e3967df827f65044634071450761ae8860e893824b1ae3f204b0ffbf8b091ddd277000000000000000000000000000000000d7cd03cc2cf97a164c7dd65b961df2866e82bf6fa979e56d0ca3f9638319772f56eab2e2134a33515dbcc137da8ec8e,,,invalid input parameters, G2 point is not in the expected subgroup -000000000000000000000000000000000be4cb58fd1dab8fccaa410e1c301be4fd2e7bae95cc1717d2aecaa705d717c67d7f20611dd1403d9350798642fa021d00000000000000000000000000000000075100bfbdb3f6271e5d8bcd4472580e56fd507b73aef3c3c5084d77e11a61b69bea0b85ba62885f7da36d223fca20760000000000000000000000000000000007649efeb3e0bee49b9adb13f8e5d7db1c06d7fde08a3f3082194153bf4b3615aff1450e47fae88ac93f55a389a319da0000000000000000000000000000000008334731582fb1b6125d7ee1da0124fe88f0c70a0a3f6188636976c31ba6a72beed927fe598386f328e4ae534729a57c0000000000000000000000000000000010b57d80fce5cdc90bc93b3bc7a1affadd19fb00aeec2ca9a6287bf4e40fb74616986a44f2f7d945f58501a965f37f3000000000000000000000000000000000180dcae46ee41bccd422b3cc2b34cad26f6816dd08ba51b2f12835e7439ae2d46933de28ac04bbcad68a188e7e90ee8d000000000000000000000000000000000a869358fd3d64849fd62e513db8fce1ab2618d3524acecbd04fef6b8c77703258cc557587f316cbb74bc5af5cb5551100000000000000000000000000000000059eb0e90e0910c24ee2145921ccee83707d8f89a188dcaae7d5c75b7113cacff92a2a030e67927bfec0da39f2bf65ed0000000000000000000000000000000007649efeb3e0bee49b9adb13f8e5d7db1c06d7fde08a3f3082194153bf4b3615aff1450e47fae88ac93f55a389a319da0000000000000000000000000000000008334731582fb1b6125d7ee1da0124fe88f0c70a0a3f6188636976c31ba6a72beed927fe598386f328e4ae534729a57c0000000000000000000000000000000010b57d80fce5cdc90bc93b3bc7a1affadd19fb00aeec2ca9a6287bf4e40fb74616986a44f2f7d945f58501a965f37f3000000000000000000000000000000000180dcae46ee41bccd422b3cc2b34cad26f6816dd08ba51b2f12835e7439ae2d46933de28ac04bbcad68a188e7e90ee8d,,,invalid input parameters, G1 point is not in the expected subgroup -0000000000000000000000000000000010eeac4d171a17d607dc544c559226db50d40193b435ce7528086eee21ec437e986c89dbe05931083768221e4bf06ede00000000000000000000000000000000140ef3af9f3dfe760e8c9dbe8d24abecfe611699ad337a97481a1553e9cabdb2e8a8cb48bc032bd02738cd26cd1388c300000000000000000000000000000000165bb8a97dd4b60ed7fa432f019f7f09a19c8e7a9b70e7370ae668d4597a3cf12c06fe062d880611e34ad9e586c193c00000000000000000000000000000000008c684f30de5c67f675e98400d854397e8cc6a139dca7e9ee179309a9617ce0ae034bfbd0faba7a2f9e7ee39de8770c900000000000000000000000000000000030e524c87a658e44df117fa0a877afcf8a4907979c932921a631a209dd58ddcaf693c7321c537e7e2a5adafe5761fa0000000000000000000000000000000000cd44b77f2d92706b3db5e374f13f6f12e3b030c6341d31e1c55d627e6af06a1b64498e590dbff08ee6354902263ff260000000000000000000000000000000010eeac4d171a17d607dc544c559226db50d40193b435ce7528086eee21ec437e986c89dbe05931083768221e4bf06ede00000000000000000000000000000000140ef3af9f3dfe760e8c9dbe8d24abecfe611699ad337a97481a1553e9cabdb2e8a8cb48bc032bd02738cd26cd1388c300000000000000000000000000000000062e54c21986cff68cd57903594d0e9f2f8348191aea6abc19e8b895e6ca59bf2eb731d10e426ad98726c51adc2d53b4000000000000000000000000000000000594d2a92e0b979c95daf6f78216be8c33ef23f0bebcda479326a1d63ec8234b283ee29fff7fccd441ab4ea6e17371a40000000000000000000000000000000019179f35ebf4ad9f8f9dd577b2db1ed58a988954b4bf4232301b713809be318e746f5b2bd663a21de755125f414bae9700000000000000000000000000000000172d8ad43611623865bff86df2bea58a02b7d6a2c41b85b98d6a2bd4f5e1bef25c978ac0bbb7f4746297c6e0e40b4e23,,,invalid input parameters, G2 point is not in the expected subgroup -00000000000000000000000000000000054dce4bc0b703b8957539c594d8d443fec161c3cb2f806f8eaa8158a665d84cfd551c0b7c0a08bede0cbeb780a6ca5b000000000000000000000000000000000cb3d3f9bbda28f8fe597907a76a8250567f4e481b6e9409e03ead60eab77153cf8f25ffcebb243a310094740cc2e2a9000000000000000000000000000000000a71dc159647864abd64655bf5ef956f21ad55529bdb49ac910ef628cc62a3d43b2b9ee26180232fa29f4b0e8371286b000000000000000000000000000000000c72d0fbe0a7604c9fab394b285ebf1c322c95013651bd21f88865e269eafa65e135ff90f5b249a794cef4e6cfcb56270000000000000000000000000000000019ac0043071372ba077bc8d91a4ac80fb5b8c8131981c4dfc698ba9ae50b506f93149eb73e4bc4f4ded94d6824473817000000000000000000000000000000000113368be2a531d2958d887c046fc26155436fc6a1ef44fdf16447163b7bc48fbb499506d8d5c8041d21116c4f22e685000000000000000000000000000000000b7244995b7819857f716288dc59eee9ba5ac7bfe010937ea0b67ee71388a3792e5b7feb6890a436db4f1b26df18b38c0000000000000000000000000000000009a0b73360bc0ca3b632c0116f21ffdaecf37e4d6c904c98d6225a08d7caadf5024ad6b457cf31b924118ea147ff10fb000000000000000000000000000000000a71dc159647864abd64655bf5ef956f21ad55529bdb49ac910ef628cc62a3d43b2b9ee26180232fa29f4b0e8371286b000000000000000000000000000000000c72d0fbe0a7604c9fab394b285ebf1c322c95013651bd21f88865e269eafa65e135ff90f5b249a794cef4e6cfcb56270000000000000000000000000000000019ac0043071372ba077bc8d91a4ac80fb5b8c8131981c4dfc698ba9ae50b506f93149eb73e4bc4f4ded94d6824473817000000000000000000000000000000000113368be2a531d2958d887c046fc26155436fc6a1ef44fdf16447163b7bc48fbb499506d8d5c8041d21116c4f22e685,,,invalid input parameters, G1 point is not in the expected subgroup -000000000000000000000000000000000cc6bbb9914ae46b57eaaa8d3d22274a355bd7488e5b537169c995ef2bee187ab66497423f14fdcd01373a609981b3ea000000000000000000000000000000000037d9461da369d186cd812a9ade7690b2b8b54ae386b7342a69af832ff4f51e5db9baa3c6b4a65d798a1aeb41d8787d0000000000000000000000000000000013a6e129d4dd4aa93cff5489ee879763e2a2231939e609d2d72f07e630b37d09f3057a36fd5cdfc9c81675c996f8ba0f000000000000000000000000000000000e8d7ad082e8f9a718fc2ea712853ed9ab4e8b1a8ca9988f77c70fc759f1fe2d4bd73696e539f130be13b2862efbdf770000000000000000000000000000000009897223b041568c9ef2884baa28477241e525de05f2c2f15441854a0e8660786a0c7b85a6d9d1074fed2b44d75efedb0000000000000000000000000000000007b52401891bd8003af4b07b04b15b79bd05fcb54739491352d295b5545ddba34da0b0aff36a3e7e4b433011be580174000000000000000000000000000000000cc6bbb9914ae46b57eaaa8d3d22274a355bd7488e5b537169c995ef2bee187ab66497423f14fdcd01373a609981b3ea000000000000000000000000000000000037d9461da369d186cd812a9ade7690b2b8b54ae386b7342a69af832ff4f51e5db9baa3c6b4a65d798a1aeb41d8787d000000000000000000000000000000000cca0d111237ec521889baa4987714c6bb539399b058b6635fd043821377fd6cfdb74923610c8235afe2be99188cbe820000000000000000000000000000000007fa2b99221675b38204c2eea94b2378b1d711ea5ba4f41d35c37f77ee2466f22c88725da9fcdabb6153292b7cd9aa1e000000000000000000000000000000001069adc30f99e0ddfd39775cee5ddd786fcc077cbaa8737f7031745d02f06168fd5d3c4936704d15955bcfa08b0925180000000000000000000000000000000003b388a8d9baabf0bf708e2fe28eacd7f704bc588185adbcea8e0a2bb8f9bc9b045918d97bb27b2033c6722b6e6692de,,,invalid input parameters, G2 point is not in the expected subgroup -000000000000000000000000000000001055ab14a2407bf095a954cf1c926f2c520dda187c44522a7e924e38543e5b87e7642227821a4e0b3ab0289b32161a060000000000000000000000000000000018aa24044066526fa9ed980ae7b3110a4fde7ba0a5fd289803fa5175d30766f01a266917f821169c7ec31fd46e1a14ac000000000000000000000000000000000b6e16f2a6cb821abc43c447da207cc3013f2f750c844f42f0fdf47160a38501bf502073bbeb565122bb3de61b3a5ab800000000000000000000000000000000040f5f3aab5d416e9a084fa298814f894ba599315fe10af20f836e624680582413b4a54623cda8ae2663ee094e4db775000000000000000000000000000000000d32ac715a094813c7b46ce2e932365bfd62ec5e584e047b0c56ed6eca3c58268ae01be31b833be7ba5c2588ebb9859d000000000000000000000000000000000850b9044f129e51658a02cfa49d40a2b09239823cba4d8fe423fa1b4815750811daf745e7e02b317a7318aad0734ddc000000000000000000000000000000000765a76c441227592ba30d6b1d3d9898467352398efc0e8416e0be8c9f87bcac8d5eaa5d7b2a8adfae8303909bef28da00000000000000000000000000000000107c0eff2fa09afb743c294408408451e3039da8db8c0beef32f07864223817075fa557a89244cdc293d631311773947000000000000000000000000000000000b6e16f2a6cb821abc43c447da207cc3013f2f750c844f42f0fdf47160a38501bf502073bbeb565122bb3de61b3a5ab800000000000000000000000000000000040f5f3aab5d416e9a084fa298814f894ba599315fe10af20f836e624680582413b4a54623cda8ae2663ee094e4db775000000000000000000000000000000000d32ac715a094813c7b46ce2e932365bfd62ec5e584e047b0c56ed6eca3c58268ae01be31b833be7ba5c2588ebb9859d000000000000000000000000000000000850b9044f129e51658a02cfa49d40a2b09239823cba4d8fe423fa1b4815750811daf745e7e02b317a7318aad0734ddc,,,invalid input parameters, G1 point is not in the expected subgroup -00000000000000000000000000000000182197c7a0cefeb530f51c664dcf8a74f9f70165ffc416ba454e9c356bade393e30d037347b1a020dcefb09ee65590a6000000000000000000000000000000001030be8d38736ac8e555d1681b14f73f2ca58faebeaff17b6006bf7876e733642d229075c8dfb0a9ba4e832e384aeb8b0000000000000000000000000000000019094370a6f19e946f587e9b117332ce5ad91860cc103015e94c6aac6d2c00f3e71471c241ea1d425e391707b27b851d0000000000000000000000000000000009ad1d1312011907676574a7867ac02059d9b0e29dab709f6ffc1b75b3598658427f10ab52d1129417ef42c30998f55f0000000000000000000000000000000003103495c759d8901898acd98679d92e048ca41244782045a6d9419b3ff87c351f97a333899fb445b8620099f7b9cce100000000000000000000000000000000051f7aaac39348f70109ae7a016026ea52c03e4ec90d03ce05aeea74f66bbf82e17be35cc45f492f50246f0de8dc68c500000000000000000000000000000000182197c7a0cefeb530f51c664dcf8a74f9f70165ffc416ba454e9c356bade393e30d037347b1a020dcefb09ee65590a6000000000000000000000000000000001030be8d38736ac8e555d1681b14f73f2ca58faebeaff17b6006bf7876e733642d229075c8dfb0a9ba4e832e384aeb8b0000000000000000000000000000000014229a1108fcd75131295caee98f8e4e075cf4bb5e169024e07a533f65316cb5d19193d3cfee8b2216663829be6d374600000000000000000000000000000000156cdf98f622a393d920b200e6d9efede7413137eceb79d66f85a18e33ef6946a515737c58e198f877fa39458877b99300000000000000000000000000000000006e5cee8e0f47c0ee4a574fcca6e150901a7de58f3f2eb3480f1ff8c14effa4bb9d00837501f22f6cc465e4026c9d7100000000000000000000000000000000124b6c0836bdd10657a3e1f978b48b9221b1cfc2767b6b99319fb69c5fdcc1b133f1c2fd093c62d6d1e398f32c4e8b71,,,invalid input parameters, G2 point is not in the expected subgroup -0000000000000000000000000000000019c9b755000f9f1b6ff22885f45bc1f5f65d080ecc129d29e1cd60aa14fd20646643be51d2fb3417cbbd39361bc72b62000000000000000000000000000000001741fd3c4a7e883094c0e84d851f45cc1b81af5bcace67dfddd3f19e8817697a386d1965a4e17c60b00ecbff84779b97000000000000000000000000000000000e87ea967f6c4dd7135efcd9a59368a2d19dd1385aefa34d7d9bd7f5094d779a7150667dcec463c9ab63d2ffc8ee4f6d000000000000000000000000000000000a3a010f176efe1a7bdb77dedf6b6271c845d662dca5062ebbac4e9c3b8946db0adfff37a6faa3196a99fb3ef05f09c5000000000000000000000000000000000350ad257d47f270c4340e3cb124ce961316573dea14c9584d20221d922a43c2e94324ec14bd1e4a1eb955861783a8f100000000000000000000000000000000070edff58ac1f8c13f62327cf0adbd748285fcd84ed7be23dfc82a0ae32f8c8f5f6b0679f795874cb0082718fb07a1ca000000000000000000000000000000000d5be6f99bb9a2379d1e542ece048164fa5d14e0c6c459180717b3da46e8446e9def576635ac1124e1390196fe97f39e000000000000000000000000000000001482d8339b402e3bffe61aaa298c8bae4286f1fbfc877a66e21cfe239bbee383d701d95a6c2b8193d67df5a551bb7aba000000000000000000000000000000000e87ea967f6c4dd7135efcd9a59368a2d19dd1385aefa34d7d9bd7f5094d779a7150667dcec463c9ab63d2ffc8ee4f6d000000000000000000000000000000000a3a010f176efe1a7bdb77dedf6b6271c845d662dca5062ebbac4e9c3b8946db0adfff37a6faa3196a99fb3ef05f09c5000000000000000000000000000000000350ad257d47f270c4340e3cb124ce961316573dea14c9584d20221d922a43c2e94324ec14bd1e4a1eb955861783a8f100000000000000000000000000000000070edff58ac1f8c13f62327cf0adbd748285fcd84ed7be23dfc82a0ae32f8c8f5f6b0679f795874cb0082718fb07a1ca,,,invalid input parameters, G1 point is not in the expected subgroup -00000000000000000000000000000000104c749e3f7b40bf6df55f9414bd146ac306b46a6210ae4ceff6fe2a58220ddbc69208ada5f692120dcfce39b1e43fb5000000000000000000000000000000000663a0e62ea68ac23a6e27958baabbb5deca3905aa138a54d6198724e5fdf0abb9288cdb52cf1d44e93f06571a654f75000000000000000000000000000000001116ecf077865395ea40fa9cf05753b87ac29ccf9ecfebfa1031fef0defa1d77634c2177647f069532e00f7fb657577f0000000000000000000000000000000005c7960dd84874fc00ab199d00e8bf1ea035a7eec443328bf2bc28d0006979f5032763a4d33f031e698895e03b27314f0000000000000000000000000000000004e00e32a506bff708c51fcc4101c8ebe7f1695d6a4606b6648b04710fdae313b99219963921451d0fc78dd59970ea8b0000000000000000000000000000000019dc4b721ec4a4303809c47da68099fc10706eb08cd4f6f91641ac680661e93a91e2067a84c12f9f55f84e27ed76ae2700000000000000000000000000000000104c749e3f7b40bf6df55f9414bd146ac306b46a6210ae4ceff6fe2a58220ddbc69208ada5f692120dcfce39b1e43fb5000000000000000000000000000000000663a0e62ea68ac23a6e27958baabbb5deca3905aa138a54d6198724e5fdf0abb9288cdb52cf1d44e93f06571a654f75000000000000000000000000000000000da3982205a45000b16e1ec7a48effad4ae1affd4acd8bd13fcdf2bf05b6845ebda9be2df6d6325389d711111641aae500000000000000000000000000000000150c1bb67fe3d88dbfb452d5b4582d9a1c350ac01cc209740be70d63e266c926cc6b0171eaea913b7456daf595b83ce900000000000000000000000000000000138d0bbb52fb6248731c8fbb6818d1379fc5c2549a5a0d663a56c41b3b2e8c7c4fd77830c455a40e59aaa65124ebcee50000000000000000000000000000000015e6b4628b75f9786ee26e41cb8b86c6e6a97ac18aa7662b39e9c96e169a3192b64b863d7f9739237255fea6cab12fcb,,,invalid input parameters, G2 point is not in the expected subgroup -000000000000000000000000000000001638395680014bc04e2ca42bf864dde47a0d708ae81ba4a6aa2e2476837750aaf5f9f6e41a5a23df432ae92fd221737e0000000000000000000000000000000009419792539e0ae995b8d853d9ef513bc54766475e37bb3dc2dae3d7fb9b02b0eb2327f24a751d2de344b9f5131ef23700000000000000000000000000000000197ff997d6c5efa3d7de8e16f26082bf13a2401d6df5f5c33c6614c36105f347e40216c907bdad9c1df6ebbd44f41c3f000000000000000000000000000000000f27a0bf92329730d776a83583177993b2b354a212a9c004f9f8892a750c477b8d1e68c13127f03b1629bc8392d06f5b0000000000000000000000000000000011b239cc6914a321385d907527b85713a0d842f5be80752f4c5758586dc1de944b6e4578bbe324f16838115e9c866bca0000000000000000000000000000000000cf93c5b48cd9de51ccaa45124217cabf466d07d6fdf4a7bb810443339ec4af5b74931bd07eb9fd31c284c05f3f539e0000000000000000000000000000000010b91e082484fff0da28b06f06e02c699d741f2ef788250e3fbf2ba8fc1d7d78a1ca63b76dadfb71015fbefc0eb70eef000000000000000000000000000000000c2fe842c659c875af0f2cb1a978ac9058981cc6c76ff057f326162d4322805974505e6a35499bd0c58b5d6db3aa222900000000000000000000000000000000197ff997d6c5efa3d7de8e16f26082bf13a2401d6df5f5c33c6614c36105f347e40216c907bdad9c1df6ebbd44f41c3f000000000000000000000000000000000f27a0bf92329730d776a83583177993b2b354a212a9c004f9f8892a750c477b8d1e68c13127f03b1629bc8392d06f5b0000000000000000000000000000000011b239cc6914a321385d907527b85713a0d842f5be80752f4c5758586dc1de944b6e4578bbe324f16838115e9c866bca0000000000000000000000000000000000cf93c5b48cd9de51ccaa45124217cabf466d07d6fdf4a7bb810443339ec4af5b74931bd07eb9fd31c284c05f3f539e,,,invalid input parameters, G1 point is not in the expected subgroup -000000000000000000000000000000000ac27e4d19924f4bfe30432554f25d456cdb4724c106409e46612e1c91e8cf5fc2cdcd6b6fd6bfe040e910795441befd0000000000000000000000000000000007e9227d849e467fbf5fadcc016dedcc04f4c66f23464195782746fde628a107d77ca5b5c9bcc8bbb14fc14208fa5de300000000000000000000000000000000094860f23d182a14d1a64d9693ce9309ef4e775f24aa3807571c9b8281fc0d6157cdb5a00b34b66be1849994c264c4b000000000000000000000000000000000062b4a3ef95b2522c894c0b492673c3800fdf8645998a899e27dc3a23c0530d96b558d1c6364477943726740cdbc88f0000000000000000000000000000000000daa2f2f2c1020339666be4b1c1e12f8d44625a9508bc5590314789d02fe0e2e676d8d240bff89b669b9290fe1d0f8a8000000000000000000000000000000000f7a710af0b04d20b7d515f2627b572a5a17a13975ee81bcb8fd90600d5fb2f161a9ab3635bd16649c95385bcd604f5f000000000000000000000000000000000ac27e4d19924f4bfe30432554f25d456cdb4724c106409e46612e1c91e8cf5fc2cdcd6b6fd6bfe040e910795441befd0000000000000000000000000000000007e9227d849e467fbf5fadcc016dedcc04f4c66f23464195782746fde628a107d77ca5b5c9bcc8bbb14fc14208fa5de300000000000000000000000000000000055d8c63d7c04bf5db81465f2fc372b798d77ab0ecf795bf57debeafbbc8998f91f71b0dbcc440e70df3d5a1cb682ad8000000000000000000000000000000001497d9519e835c0644d30ae38ee66faf39b1939320c2e0a45b7862508ea3bd7eaa3a754a679bff81af26c6aa141adae00000000000000000000000000000000001be6ce3109582f31e02051ef0e4d266f310048e04a7ac8dc5e04d2b7f2766440f1ae63e5da6dbf831b21fd7cb9cc0a80000000000000000000000000000000003d4acbd20192ee2fc55acd5c90cf5b897cb42a4c0a777970d11955cdf11d5ab22444ac9cfb666c3e509ce832cef219f,,,invalid input parameters, G2 point is not in the expected subgroup -0000000000000000000000000000000015866ee89fe4f68e45155fb98124e8453e1ca25347d84f70ebfc32cf76c5d48e3a3e5ccfb1b505db7b493cfcc73ef92d0000000000000000000000000000000013deaec2d2482c457050256d157968ea3d15f9b61b4573353e83daf824b28289343bcbc3bf97ecec9d65ad08804861440000000000000000000000000000000013467fcb424ae0eb012228fd2083d92e6d242427670ca6e2cc1166166edee5a94b78d2c2f8715a996bf2b4e5112e49f0000000000000000000000000000000000c23c01e0061b0fc7579723e072b12e86c8f12f4c2a039bdc5b1f3384441ccefec187e0380efae31a819d92fd6462ce80000000000000000000000000000000014f9a055a5e468955f6d7485fdffed2b33174777f99d9d0af160b0a083912b05da45f35c73053120f61525c173a24e59000000000000000000000000000000000cfdcb6adf8f04fac2cba8f322339fb0614f46b77b0d91f0ec167eca06fcce080ee0e63023fb94712dbe7591843b6fe1000000000000000000000000000000000d90bd38049f2a8de869d8a748c9ff3120542f38fca6e8d5fbbff86baaabf0f19dbf449cf23c043dfea322d99837f7110000000000000000000000000000000000ede89c8bb8299726ec685765f10167c5b844e427d3c15da6ec2c1d97de174819d52caa96d5cc938e93dd09bbd1e0d80000000000000000000000000000000013467fcb424ae0eb012228fd2083d92e6d242427670ca6e2cc1166166edee5a94b78d2c2f8715a996bf2b4e5112e49f0000000000000000000000000000000000c23c01e0061b0fc7579723e072b12e86c8f12f4c2a039bdc5b1f3384441ccefec187e0380efae31a819d92fd6462ce80000000000000000000000000000000014f9a055a5e468955f6d7485fdffed2b33174777f99d9d0af160b0a083912b05da45f35c73053120f61525c173a24e59000000000000000000000000000000000cfdcb6adf8f04fac2cba8f322339fb0614f46b77b0d91f0ec167eca06fcce080ee0e63023fb94712dbe7591843b6fe1,,,invalid input parameters, G1 point is not in the expected subgroup -00000000000000000000000000000000119c7c85e5efaf08b91dc496758b962098cc0eb60f4a770bfafa91809ae4a95b43f96b69c8ddc897701487f22d2e049c0000000000000000000000000000000007467ac896ae9f7d2cfdfbab082c89d3c17a6dfdf1f69b4b38fe6d5ede6848a45e8b0d728eb8c68752ec59c8e0504dcd000000000000000000000000000000001047ce33c70d58e3191a558ce2fd95c20bb62abae7d924cec8a4067fb33e8dacd796d65c049be7bacdb969f61db5b26500000000000000000000000000000000096e7081a7b2377331f86d8418bd577cd5cc1d45e60d39b519ff2b3a50ddb2d5f6dccc0066167f42498a3d29ef5ce2e30000000000000000000000000000000011159939a04c129b007f2aa2d59ae006e8d89c41dd465cba551737d06d3fb2c1161aee98e86cb8c0321f42e514316030000000000000000000000000000000000c25d9cdc8dbeec82c47d5ef12f21a7e58a8eddc1e738e635ba04f2ebe12440090f432c0d1518217a5531266441f1c2500000000000000000000000000000000119c7c85e5efaf08b91dc496758b962098cc0eb60f4a770bfafa91809ae4a95b43f96b69c8ddc897701487f22d2e049c0000000000000000000000000000000007467ac896ae9f7d2cfdfbab082c89d3c17a6dfdf1f69b4b38fe6d5ede6848a45e8b0d728eb8c68752ec59c8e0504dcd0000000000000000000000000000000013a11383f2d3a3c28e3ea750fece5790e37f66b306ecca417c83840bed70c034e4b82b0850f719fb0b3b203a25dffae40000000000000000000000000000000017d067d9cfbbcf605c5da3532b2eda5900d71340508f08c05d0051772e65b0f85b9efe5b6d63a7b64b25ede8df7f25c9000000000000000000000000000000000e44847884ee8eacd5417e042e8299af8313ce177ecdd034a91d3bdd441437510808d44e328e810c46bd851ceb6085dc0000000000000000000000000000000013288506fd52bf37aaa975b533f1182a824b79d2d876ad6ff705efeec7f732bed99d2da8f31c00a2db4d97c4118bcb88,,,invalid input parameters, G2 point is not in the expected subgroup -000000000000000000000000000000000b7aa89ed719e2af5ad32ca923f1d2d52d767f6bd33d8967d2619b54472c8881ad06441b2595931d734a0fd10ccd7f190000000000000000000000000000000014bd6118d65e19e4cb79af164f523f1c80b0f0a0f0063cf1d28e11ae7987381c0b9707e43754b75f36ca8523bc5f7da600000000000000000000000000000000056a29b523b0cf85ab04b0a496e078dba5529cb9699e567ca42f9ee3e3f07b61ae29b0ce17cad23131375f624a366157000000000000000000000000000000000acb91d1f057c7aec1f7561614a95f8db2252cc879bbc2595a5f607d8b0ecd6e6e3ec19849eacfca62d870b049ce84910000000000000000000000000000000010d9459e07178af8e125c2f66de699cfafb5f87a63454e24d0ed88b6c804a9ff204f146ecf4d6db62234ace0a944acb20000000000000000000000000000000007256a68e23b43a3b6475b3cf209ec108bac13631ca448cc860672c65c1760a8299fe941ed5bcbbbcf63a683e86806ae0000000000000000000000000000000005d4453da747eaef90007eb8ebf6088e8617dad362f2a95638fca7312bc5cdd8200f32b17a0b483052e6784d286c2cb80000000000000000000000000000000012f0e56ed3e3f628a13493d0ade2321310cf62927b40887202042981fc9a81d6cc69be130346b7bc244a2119b2632a5600000000000000000000000000000000056a29b523b0cf85ab04b0a496e078dba5529cb9699e567ca42f9ee3e3f07b61ae29b0ce17cad23131375f624a366157000000000000000000000000000000000acb91d1f057c7aec1f7561614a95f8db2252cc879bbc2595a5f607d8b0ecd6e6e3ec19849eacfca62d870b049ce84910000000000000000000000000000000010d9459e07178af8e125c2f66de699cfafb5f87a63454e24d0ed88b6c804a9ff204f146ecf4d6db62234ace0a944acb20000000000000000000000000000000007256a68e23b43a3b6475b3cf209ec108bac13631ca448cc860672c65c1760a8299fe941ed5bcbbbcf63a683e86806ae,,,invalid input parameters, G1 point is not in the expected subgroup -0000000000000000000000000000000015827c619b2a73a750f6469160ff323c15adaf55e893933a5c2e5c2f0df8bc426421408773a3e8cb8b1695973f7c0b760000000000000000000000000000000000af4a7d29f10cd080d9989b341fc030a5dd51512f776fb1da7a46d542c2a6a2ad7c1309af30423b717825fd5dc0356300000000000000000000000000000000198d09947dc088c1d33d776d64765766b508764f12a28fe0119277d6e171af7c9ff83f6823558e8b1a4284857663afb700000000000000000000000000000000130d5e5315f8df8d0142d06bad7a51b08e5b3c2d49b84c9d6b177b9bb628a852ff65c1a93982dcb1b31a2dc0941904750000000000000000000000000000000018cb011868591c6b44b7ce49f82470aa6461a737173e1d88d249c0e83fc6e4e6a15f8397e515efe7dc7302ccc2e369ae0000000000000000000000000000000004de1c5539b2ef536a66c8f3d7cd49ed948c081c08cba8826d2ccdf9d159b931ea10eeb8b3f465dce0143b179059169f0000000000000000000000000000000015827c619b2a73a750f6469160ff323c15adaf55e893933a5c2e5c2f0df8bc426421408773a3e8cb8b1695973f7c0b760000000000000000000000000000000000af4a7d29f10cd080d9989b341fc030a5dd51512f776fb1da7a46d542c2a6a2ad7c1309af30423b717825fd5dc035630000000000000000000000000000000004286a9efe902158cc624080ebc5fd9b5e0e6e31554c745d5f0c34ef1de2a487f77d405577559dac59babc731ca779110000000000000000000000000000000009c09c9c9e2c75bc6a81357c03b339668c7d8cc8a3f65790ad53b62f4c21acbf64fa66fdd75dc24c05214b712ad7fdb60000000000000000000000000000000018bf7e4fe271fe195377639c5743c1ef3eccb09c86d64d2a4dad2d7d4ff0feb46252d749f82a27141dc0007649b032bb0000000000000000000000000000000018113c4584b81eb814a9f1ebd041062d0db8bec46c2c1ffb7f863bdd2fc3fac470b01c32b6f2453ab048eefe362aa1b8,,,invalid input parameters, G2 point is not in the expected subgroup -000000000000000000000000000000000c3f4ece90cf7d380efd3f81e66110a5923bd604422fca0fac2a16fb9a8d8b34cc1fb86a15009e8cc2c0d9fd8fbc0fac0000000000000000000000000000000012ea55d042ae590abd4c2687e0f152384d37665ddf26787d49bc9f6d40a579bcb23521c59ce91c418b9f4801375892aa00000000000000000000000000000000098af17ffd4d28bad76ce1ee669e7cdac1eec9facc260440636be88618302ab5a0826141b4fc914a389816d04597826a0000000000000000000000000000000011bef78afedf5c62daee5e86386c45826a524352fea40f68b07b7794df8eced4eaf0fb55b6990b4fb417ecc597b61e48000000000000000000000000000000000d64f0a4df4f858defde17b31476045c3dea78de4ec8082822c1699c0b9619464c75f0e57ebd12ad9e4e2e6b291b538c00000000000000000000000000000000031f12dc8a9c5445d575f99e2a4b4217ba5c0be58ac00977236440ab0ac7e2c8dab72a64464e4480aab7eaf1d629c7e700000000000000000000000000000000033f3c31337bc48622d27a9a3224a2acdb5c538a59b497a4a85840c81cff667ed0a0e4e3f4bb23a9ae53c1e79ea54cbb000000000000000000000000000000000cf0dc22af4530260cde26aa0eedc83a0ec3ae87d024e6907f3d22070e1054b3d4f24d5ace7218ed44763af6ec3f25ee00000000000000000000000000000000098af17ffd4d28bad76ce1ee669e7cdac1eec9facc260440636be88618302ab5a0826141b4fc914a389816d04597826a0000000000000000000000000000000011bef78afedf5c62daee5e86386c45826a524352fea40f68b07b7794df8eced4eaf0fb55b6990b4fb417ecc597b61e48000000000000000000000000000000000d64f0a4df4f858defde17b31476045c3dea78de4ec8082822c1699c0b9619464c75f0e57ebd12ad9e4e2e6b291b538c00000000000000000000000000000000031f12dc8a9c5445d575f99e2a4b4217ba5c0be58ac00977236440ab0ac7e2c8dab72a64464e4480aab7eaf1d629c7e7,,,invalid input parameters, G1 point is not in the expected subgroup -000000000000000000000000000000001679e0889aac1501b67c4ccee84942e05b1720f48b6390ab82ff76c0ab95defb79b4371770f2e07a9e7ee8de4d76b43500000000000000000000000000000000162fa6099ca3e5e8e27dea6ea0d4d13c5c150d281fd6beb2079ddf2d714bf9458184100c88440109d7526812ee0f56e000000000000000000000000000000000087bda5b07cf72c2b350e663670f094c352097330b307cbe2f7b4224841b6eb23c36ba62d4ee591e5ca68383ec0256f6000000000000000000000000000000001163d4985e0f25d36a1f8dd97b61413b0015a966a88d98eddb2ea2d5eabdd83a44fb7e37cee90cc50df2f95dbfa97979000000000000000000000000000000001652067ee82320191cc5b188e61ee2d1b94c781e8e5798c89224920ed1d12a2cb41066f69cdeffe8a4d5e3aa1be4c83300000000000000000000000000000000139cd806423ee99d913e8b0e5ddfb6b1b62478254fe39d6836fbc632de9435e1464a556b1f9466efebe93636dfde7749000000000000000000000000000000001679e0889aac1501b67c4ccee84942e05b1720f48b6390ab82ff76c0ab95defb79b4371770f2e07a9e7ee8de4d76b43500000000000000000000000000000000162fa6099ca3e5e8e27dea6ea0d4d13c5c150d281fd6beb2079ddf2d714bf9458184100c88440109d7526812ee0f56e00000000000000000000000000000000017028dd744482e290c523ae5944c8a149afd9d344fea7ac0171ee3a5ff0c2add53ab20c477a584bf61e007af5840eb4a000000000000000000000000000000000863382ee8a43f02396030768905e44c8f9504b7315b00e379b92060e4f01e1b4e0f837b24cb42354e7211419a5516480000000000000000000000000000000007e2af64687f7d581d5a2bbdf225d1ab3dbea326ec89c08852807696c8d13cc907ef4289bf5ef9826c1fb27673b28bfb000000000000000000000000000000000ffc910160c8a0b826600fcdbec027e7d4874c9774324bddd2dd4428eb81c22618a49378502917ad9fcd96bdb1371285,,,invalid input parameters, G2 point is not in the expected subgroup -000000000000000000000000000000001902b8c58eae3ca8d2261a637902587c2c0e75d32abc894967b6837ea34252e4558966f931789ccd76c1bbe3e092b180000000000000000000000000000000000dd20f71f5054c79d5e357f69f8d7345b5a036241774a72743271f2dc8f6e8c29a3babc2b65ed8193cc0636fb2e86f740000000000000000000000000000000003e06e2dcfbd695e9bda0baee1276ceab637fd1fbe2d2d6458c923c35b00edc7edf4f9e797aea59ff8cfceada0615a02000000000000000000000000000000000a04a2ed5e42fac7f064b43d64151a6c517ecf22dbc7563a3e9f35f555a9992fe45cf6a728ba94607df7c96f7e0a334b00000000000000000000000000000000090fac97f9f524168bc930d26ea1627ceaf187398d6bfc5a019c8467d75cd31a41c7eb9fda35fc85bd92b4cfca92dbff000000000000000000000000000000000f37b91dc935c28668c27d38328a511148c1739b65f2816dc53e42a8f059c9b2be7417a6f97c9a2597b1a0f06b7afc65000000000000000000000000000000001687dbd36c7f96f8be47f08bb75bc72f91e63e26d0157a9a9c8f531f3e73bfbd9870fe9abd0a7a3fe73b997e48d0ffb8000000000000000000000000000000000183ba882bdaf1dc850cb4e98158895effda1734fa64810cb15640e6cc027bd006e5c1a088cc2c65e8af29b64fe41d4c0000000000000000000000000000000003e06e2dcfbd695e9bda0baee1276ceab637fd1fbe2d2d6458c923c35b00edc7edf4f9e797aea59ff8cfceada0615a02000000000000000000000000000000000a04a2ed5e42fac7f064b43d64151a6c517ecf22dbc7563a3e9f35f555a9992fe45cf6a728ba94607df7c96f7e0a334b00000000000000000000000000000000090fac97f9f524168bc930d26ea1627ceaf187398d6bfc5a019c8467d75cd31a41c7eb9fda35fc85bd92b4cfca92dbff000000000000000000000000000000000f37b91dc935c28668c27d38328a511148c1739b65f2816dc53e42a8f059c9b2be7417a6f97c9a2597b1a0f06b7afc65,,,invalid input parameters, G1 point is not in the expected subgroup -0000000000000000000000000000000010f38e6e4f562be50152c1d10eee8f8990cb8f884035bdce111e178d1286afe2f2f02c3a36858ae2ce902d6a2872ff1000000000000000000000000000000000141dc64999baf42240b933f30ee895188b561b880f90b5f1ca8df0ac75be7d95bc15d55e321720c172171e9c4c59e800000000000000000000000000000000000548814c4b6d72cfb817b49b3141302be7d7b378e50ff9f7d66e31cd04e1f024bba334110817990264d26cbcff7170510000000000000000000000000000000011f9d186fab00b9ede155a82ec5a5e587a1c6091005c4c6e90672d15c434953426440799c5ede15a7976f18bf345595a0000000000000000000000000000000018d480ece4609a56220d4db100b68ca06ee4271b84e1a81112fbb0616cb34d2b0ec974de31f7d6957b186dbd8a8f8ad3000000000000000000000000000000000c3c1b79130f73d516c1bbe38c572be2616991b523a9370c98df9313be9f5015c3e8d51947201c6b27e8cb9c7291bd660000000000000000000000000000000010f38e6e4f562be50152c1d10eee8f8990cb8f884035bdce111e178d1286afe2f2f02c3a36858ae2ce902d6a2872ff1000000000000000000000000000000000141dc64999baf42240b933f30ee895188b561b880f90b5f1ca8df0ac75be7d95bc15d55e321720c172171e9c4c59e8000000000000000000000000000000000006d89d908b733aba090432e795c564d1badd5b8529fc53507c4850e3d97978062b38790ef45562f3d4978491b5ae893b0000000000000000000000000000000003f7fc037328c13d13fdf4b2c251ce10c41ac5d042122f0e4e4f5a71cc9e1463f62d96aed44123d0e612e5296b53fdb80000000000000000000000000000000000aa2e027a929f7637f437b333a795a2e8d1a92cda31feb5a72fa1c66fadd23813177f9360204b0512d8f460b5bb161d00000000000000000000000000000000139a2a989d462e1793caba250c2ba9c46f31b3a06f43a0f4cbaa021b5d52d254f18d9517ddf3c21780f2a2c59533c5db,,,invalid input parameters, G2 point is not in the expected subgroup -0000000000000000000000000000000003ac8ec0fbf8c4a6774d8567a45b033a4a622d88d8b2025eecd746d084617b67342cb1030068ef6dceea78cf97210b6a00000000000000000000000000000000090b3a144dd409afa163ef513af313e545330a66c33d45b32d61cbdeccc66f78062060a2bfab2a88ec0cc47ec3525f19000000000000000000000000000000000d542ceffc583a6022306479b2365171c3610b7f615619802caf2f81d78f2b5166114485dfaacfdfc27c6450f8c344550000000000000000000000000000000010f5a12712658a5359c0a310f6d833c0b4623c51da6c035dfddcc4c201ccb27ac0a534da459a82488c32e1d4ced9b8af000000000000000000000000000000001878dfc18d1744c6f837b36436b82cd9c270916e5206f709e7eb30fcbd4157f65639103f367f1af2684a51d93e3dc7fb000000000000000000000000000000000ca3a300efdfd9812b6213a848d7a2f865d3fbe8c73527997f18460485626921063bd5b7842b8a47ccadcebb5539a54b0000000000000000000000000000000009aafc73979c000236c08e089828880f54645b5ff4c1dcfea0ff41ffe8e3fce8ba0dbcebf0d4205bb6616a737b6d3542000000000000000000000000000000001399a2072604d50f92ee186924ce32c4e887803dc258b7495aa2f3d2187571045db7f360d2614b198f83bc8024b06559000000000000000000000000000000000d542ceffc583a6022306479b2365171c3610b7f615619802caf2f81d78f2b5166114485dfaacfdfc27c6450f8c344550000000000000000000000000000000010f5a12712658a5359c0a310f6d833c0b4623c51da6c035dfddcc4c201ccb27ac0a534da459a82488c32e1d4ced9b8af000000000000000000000000000000001878dfc18d1744c6f837b36436b82cd9c270916e5206f709e7eb30fcbd4157f65639103f367f1af2684a51d93e3dc7fb000000000000000000000000000000000ca3a300efdfd9812b6213a848d7a2f865d3fbe8c73527997f18460485626921063bd5b7842b8a47ccadcebb5539a54b,,,invalid input parameters, G1 point is not in the expected subgroup -0000000000000000000000000000000013a366c2748305c4ca702c053ddcf15df4a4a7858cda813d001f59bdbb419de6ae3fd24b22fbeebe58d5278caf04c0c800000000000000000000000000000000140759404192c97274c06a69609a1f927195dc0e9df312f483b075e0647a9df1225c22284edece061a2a1c3dd6c4af030000000000000000000000000000000018059cd50cc71b1060ee01c10860bccaf2abbf84cc09266f2818b7625be9368138784dfacf0a1413f19bed9c09294fed00000000000000000000000000000000138939b9b91fcc8ee3aeb35de9476576cc84adbfc513a72fb74c6b897a9d6bb2037d65489709de062b238c5d0587345f000000000000000000000000000000000a9f2a8303b70df25b27158d7fbe06db9b71f6b30b8d8f3d3ad3e81ed310af6ba00eaa104c4c8755c3c24b37b5a9bae90000000000000000000000000000000014297a57a543d963d777ce5e3e5b07d19d69f56ff3efafa2753889522f10dac3fcabcc77466ef236d331361955b571670000000000000000000000000000000013a366c2748305c4ca702c053ddcf15df4a4a7858cda813d001f59bdbb419de6ae3fd24b22fbeebe58d5278caf04c0c800000000000000000000000000000000140759404192c97274c06a69609a1f927195dc0e9df312f483b075e0647a9df1225c22284edece061a2a1c3dd6c4af030000000000000000000000000000000009a457949f0a3a5ef91fbe3beb52dcf95a9f71db70bef860d2fd60cf9330b99e1d103eea022f32f0db603a635925f2940000000000000000000000000000000018e2ce9366745f5691d3c8f907bffed5f9da928cf9c9997db1311bc47b34d9d0a076ea5b7845e0dbf1c3dab60edca5de00000000000000000000000000000000089bcfe3a5596fe5f3c61326de93eb63abb0f56a7b9a5c5f4ebd883ea681607352b955deb581b57da99e4fd302e136c70000000000000000000000000000000007cb502742faf77a7223dc713a243af8998652e45f51b32dabe766c7a771bf4642ea9f25fd924e61a0f0a6e0f27c99dd,,,invalid input parameters, G2 point is not in the expected subgroup -0000000000000000000000000000000003f23275ed56b4aa4a1fe367219e8c84142d60d6b9983e0abb8ca21a88e008286a902b2b92369ec14d7f45f7b66b9a0300000000000000000000000000000000130b8d95a4672e467f122fc010ac3ef7f6b6d0ecd044413e51f7c27ed22f7ab7a28f60245b9ba83d4ffc98b9a990510e000000000000000000000000000000000e5af1420546c1a5a0e0c2bd9241bb7c7a26dd52f4f358fc868bea457a60bd4f6bc5b60b27069fb4f6760813a91ada740000000000000000000000000000000017426a65d239b1d9505bef2b476799c394fcc7bfdca36a1ee5a600351334dadc238b64cf8a667a25d4880a31b73c53a9000000000000000000000000000000000f151587944aad17429b51b1c16193c1e1c93cb412538d1475473666c997e012ce618eb841c4e9e064a08ab83d7fa60e0000000000000000000000000000000015c2e049c532db585807319c23ec077a51f288fcffb2cb6528d3697221e8542e3fc85d18b079ea1b217fae30858a36f20000000000000000000000000000000010a1fe14b9981a917e49b71f549b7b548629ad0003b43a9eff26e2cfa7fd8ddb21056e26dc78c88d30c32e62af40a83d0000000000000000000000000000000019f408194aa79434edd5f2a3adcc5c55ee9c1f616641b29ef21fbba8cae342df67ef438095dd7677ea1959f9a855974d000000000000000000000000000000000e5af1420546c1a5a0e0c2bd9241bb7c7a26dd52f4f358fc868bea457a60bd4f6bc5b60b27069fb4f6760813a91ada740000000000000000000000000000000017426a65d239b1d9505bef2b476799c394fcc7bfdca36a1ee5a600351334dadc238b64cf8a667a25d4880a31b73c53a9000000000000000000000000000000000f151587944aad17429b51b1c16193c1e1c93cb412538d1475473666c997e012ce618eb841c4e9e064a08ab83d7fa60e0000000000000000000000000000000015c2e049c532db585807319c23ec077a51f288fcffb2cb6528d3697221e8542e3fc85d18b079ea1b217fae30858a36f2,,,invalid input parameters, G1 point is not in the expected subgroup -000000000000000000000000000000000bc43aa42d656bdc233b332698247bad1904aff059eaa3f9b943ce5d4ae4f414dd361062a243f38129b954f17389ec280000000000000000000000000000000018dc8ec1d798981f662a8ce10f25f31197a2d168d3c047d2f6a214f1554202d072baf004c61b6d58f4f0410a4520b985000000000000000000000000000000001217fe0908fca8686b63337b0de6d3b3e4853466a990d8feb8a127cec95fd8dfc97be2ac57587d5f9ae1f5c10848e5910000000000000000000000000000000005c60861ac4863f7b9c38952daa88c2414ec8ac14f99fc765042b718da08136537765dcbc28cc6a0c279d491cf95b4b500000000000000000000000000000000154b289077530a86091d21c8be9c25ccd250da8d77caf853955b0d169e1ac40b5e0fd539b09b61b293035ebcbd0e21f5000000000000000000000000000000000356ddbe9454937c441dcfc98fe7b0cf8a746464f77230229328cafe6ad9ad1b5cc7a60e50bd8431b0996e3c42882777000000000000000000000000000000000bc43aa42d656bdc233b332698247bad1904aff059eaa3f9b943ce5d4ae4f414dd361062a243f38129b954f17389ec280000000000000000000000000000000018dc8ec1d798981f662a8ce10f25f31197a2d168d3c047d2f6a214f1554202d072baf004c61b6d58f4f0410a4520b9850000000000000000000000000000000001874e45ffd1349b4ed2e361dd7093a2ba41281b2d78f123bf9f6f73892962a0bcd6dc6e4159e505509cf7839aa79a20000000000000000000000000000000000ae0404355b78d20c1c3f5d65373d905696b166e76de62feb33f819dba26d39ef78621f819e998f6f2c82c65ddc22fc90000000000000000000000000000000008a499023de01bbe12958e10a3ca967f0f6047c705345beb8fb835c26df4eee908448b39191321f3eacfbd0951861c40000000000000000000000000000000000a56e3272f0474f980511540610b29e9a722a868025ac424ce8f76f342721a92d2544a420d3472f13186a0837d7e2c43,,,invalid input parameters, G2 point is not in the expected subgroup -0000000000000000000000000000000003e157886f141c2ce7d9ff32af44df6b7407af027005aac1149ebbe74b3b810f834b019b3e67a04531e2554f122d959600000000000000000000000000000000159eef0ff7bbe471a7ef8e666ffe35f427e3ef5bf9eeb4693dd4127467cf2615fa6b289be07452bec5c35b6d8d8ef2a100000000000000000000000000000000028316eaa131ef5303b012bfdd145bcb3106b362f410ce05810b8c83e10b1a8f80167b546b8b86c1368d7099fb5a0deb000000000000000000000000000000000bb3a353a2c16bd73c62fefd820927898dfced930d9639c5f63e62d8e8d31fa028cefb0d57ed16299eccdf3700b62bf200000000000000000000000000000000198272cf5c6e8a4f4cf4692fb7363687d7ba52deae88a7b976863309feb4a475db150073593567352ab62a150d862ca20000000000000000000000000000000019af00f2cf92494f532052962b62c34d0999a984b4bf36abd74a485fb9089ee0967071886b97f541ae80c6f7b8bc73070000000000000000000000000000000014bcf3f26683234584d79b436cc608462f1e2c20b5ecc5019988d8e30137859a4b6d0e1135dd5bbea0781b8ed3f0653700000000000000000000000000000000090ef29bf63ca97ae8388588227e1d1a0653c43b16a35a63f2ab4f0b11fd8005d9a85d30a7406491d983f347e4dfb9f100000000000000000000000000000000028316eaa131ef5303b012bfdd145bcb3106b362f410ce05810b8c83e10b1a8f80167b546b8b86c1368d7099fb5a0deb000000000000000000000000000000000bb3a353a2c16bd73c62fefd820927898dfced930d9639c5f63e62d8e8d31fa028cefb0d57ed16299eccdf3700b62bf200000000000000000000000000000000198272cf5c6e8a4f4cf4692fb7363687d7ba52deae88a7b976863309feb4a475db150073593567352ab62a150d862ca20000000000000000000000000000000019af00f2cf92494f532052962b62c34d0999a984b4bf36abd74a485fb9089ee0967071886b97f541ae80c6f7b8bc7307,,,invalid input parameters, G1 point is not in the expected subgroup -0000000000000000000000000000000003b6f466571daced9c0d6dd76b5f7cc91f20d92c0fc2f051a97524aee838be57eb977af49bf020a252db1b49693892ee00000000000000000000000000000000002b99dffcb6c171f66632d0cbc9aac74e6f4823fa4690e273a5c16baef618b80d2daf81d8c6b4c5240e1c329ba91b41000000000000000000000000000000000a32e330b87bb0c2984ce443412953a879f396221cd21c2f7ae46699b02c76352d3b13759d70541fc67cdc0e65fa6d4f0000000000000000000000000000000006a134cfd54f8e524544b170a4ae0b3da02da61b56633ace68b05c511a425a0a17d3e3e155a592e6176f707100174d1f00000000000000000000000000000000132f34e6b61e7fc7764b3113a4761cde446de56d3bfadc7f285bcf11132ce8d52c656cd9cddf176755dc228277557dbc0000000000000000000000000000000019d74adf4504a87de20b5a53d4e668be279d5850dc13b1699769d2279a23903f6f789dd897c2180ed895351e4f90d7e50000000000000000000000000000000003b6f466571daced9c0d6dd76b5f7cc91f20d92c0fc2f051a97524aee838be57eb977af49bf020a252db1b49693892ee00000000000000000000000000000000002b99dffcb6c171f66632d0cbc9aac74e6f4823fa4690e273a5c16baef618b80d2daf81d8c6b4c5240e1c329ba91b4100000000000000000000000000000000025c97744f862c85507620fef6d4b90a1e37ab2d6c5ed2d794880b43bdf854ea77e87e90b5a487c56fe29e28bf7ce01100000000000000000000000000000000120515b8665151db933e51722fdac7a83d2f299623a38529508b25218f0d57aeb0c6f260e0ef3741ea1f89bc653e5d700000000000000000000000000000000019d1111f074ac541a381472a4d9dc6b76cf64e86d92018460e977460b46e17924dfa522a5bfaccbfd8bd0711950f41f6000000000000000000000000000000000433ad85586f9392cc6079c1f4f37eed99fc65da9a32206912465116f879efcf9e83d8b325433ee31235142aff89a49c,,,invalid input parameters, G2 point is not in the expected subgroup -0000000000000000000000000000000009b488e21aeb418e8913f6bf721b3398693a3875788a3e013717fbba3c6ddaafb4073378d121cc1f2f99c072b7f8eba600000000000000000000000000000000144346f013254cec17d8423f534d54e2496df08193ed65304fe300b47a68c8d322b6ad84f748529928d64298be5ac1f200000000000000000000000000000000102c92272571b73a7df754728d7293fd8050d9dd2b8605c3f7722e6de541b7fc6a81b01c1cf15e5241ee4ee1f81ab39d000000000000000000000000000000000af1cd6f23bbd3e9ef75eed6d6d99a7cdd24574881b3609e45c4adbf82e08259d14701fcc5b6338ecf52166aecca003700000000000000000000000000000000026a1a4c3eb54de2ba4509dc806db9efc7e26247d501cb59c525b8dd15d03b91abafa9ba5816c22e1f8ca159cda34bd500000000000000000000000000000000170b510ec227fe8534a2cbb0f405756491c4f6832df552bd23980ab0946725371b3c24fa8b93a38bdcd47e1026e1d2a0000000000000000000000000000000000d327350067f7401a228c4fbcc7375f2edb058505ab34341df865a82781448d8e053b478e97a3ff79458b264a0dc186a0000000000000000000000000000000014a92d6662933a9eec6134002fb0e23a0930a964bed5bf84886bc3819516af19fb8bee2c0291c518119f4f4198eb67dc00000000000000000000000000000000102c92272571b73a7df754728d7293fd8050d9dd2b8605c3f7722e6de541b7fc6a81b01c1cf15e5241ee4ee1f81ab39d000000000000000000000000000000000af1cd6f23bbd3e9ef75eed6d6d99a7cdd24574881b3609e45c4adbf82e08259d14701fcc5b6338ecf52166aecca003700000000000000000000000000000000026a1a4c3eb54de2ba4509dc806db9efc7e26247d501cb59c525b8dd15d03b91abafa9ba5816c22e1f8ca159cda34bd500000000000000000000000000000000170b510ec227fe8534a2cbb0f405756491c4f6832df552bd23980ab0946725371b3c24fa8b93a38bdcd47e1026e1d2a0,,,invalid input parameters, G1 point is not in the expected subgroup -000000000000000000000000000000001756d051ce0ee9ac0fd83b9a069086cfb62164d5131a2c7be22122cc64fe74590ab5b69e02b37a6075384df9552d1d6b0000000000000000000000000000000013826bf44ff233e612a9dc8d47cbb3aff4f1fb5abf0ffbd35f4124531bca696371357301d12ed89a2974de5027c2c59f000000000000000000000000000000000ec934504ad116a80cf15a8d9a3a0bd5db18139560adbc6de32b5871198df9ecfe122369dbce5a19eeeffffd510f403b00000000000000000000000000000000007e3f75ccfc96dbe63e7b877420bccfccf2a7a56994fcea725c1b9f1823d93b0913ba1293f32493983ebe18ae27ce6b000000000000000000000000000000000ce8b2413d344263a5e598900af1524bf863e92fc3c8a2b1f335e9029081de05c70b50b97bef75044d8083e92f99b88a000000000000000000000000000000000a47a4c7b8b35b0729b43db9785a9f15c7357815e5d1ddf02d14003923120a734a1edd931d39d9261b55c145f8c69443000000000000000000000000000000001756d051ce0ee9ac0fd83b9a069086cfb62164d5131a2c7be22122cc64fe74590ab5b69e02b37a6075384df9552d1d6b0000000000000000000000000000000013826bf44ff233e612a9dc8d47cbb3aff4f1fb5abf0ffbd35f4124531bca696371357301d12ed89a2974de5027c2c59f000000000000000000000000000000000f3f5ca684120f4b7132153ba02995e88c50ac830aa65e23978ea6be09bc838249adf113e9b463cb01fe0eee43262d5f000000000000000000000000000000001270984624e5da5aeff659f5b75d3e7e5ec655ba342e318b0643672f6e71b84916ef767c58daea149f8029bb046e548700000000000000000000000000000000064c7217b420841cff11994a5f9ba682f7df02be4c8ba2027b67d33bb51b0b956137f61ac037d5551d5ca2b880e4140c000000000000000000000000000000001813131d845fc7bd523c7a295f2738580d9b39f6198ff19112b9dd38276c3045942e74c59b4392f59de70e7cd2ee87b0,,,invalid input parameters, G2 point is not in the expected subgroup -0000000000000000000000000000000003dfcd47087531272c3fe93d82878c5a689eba15fd67534bc2fa045b1995eca650e19e020455f0b1cbe599fb27b8352d0000000000000000000000000000000014cc297621e8b9d4ac8b1ced78c53d61261e899de0077b73f4f119e6fc50d5aa10b5111640eb3390057510825a20213e000000000000000000000000000000001150494bc162c0f414d31816adb18256b7d9fc6593f89b30b76522566667dc302050acfba7106031e99bf580fad24aad0000000000000000000000000000000005c920cd2ddd5d660e3246962b466f34a28449fe1790b9312f81fa70e13c1835970d4b807352cf7d89efa093120d527a000000000000000000000000000000000d384fa4729576214cf631ac1e6e2af54176954bd63f13cf15f2cd3c1db4cde4758d260ea4ffc0606aae700bca7ca7ff000000000000000000000000000000001824caf3b35915f528dfbc82bc5d56b5f8e7ba2b02056f6e27cbdbb0a54de8d4749446f14b116ff36b9fa773808c647d0000000000000000000000000000000011d4918642919c801fff0962062a387a4dffe693ec09cd3d0286a18e3a22c84fc09e8396ca82e6054d8535cd888179230000000000000000000000000000000016a1f0c7fec5647dcce688d3e4e526749bbf23c1fcd9e9168ace47399f9198c9b3a6b8aeca68febde1b7beeea0641aa2000000000000000000000000000000001150494bc162c0f414d31816adb18256b7d9fc6593f89b30b76522566667dc302050acfba7106031e99bf580fad24aad0000000000000000000000000000000005c920cd2ddd5d660e3246962b466f34a28449fe1790b9312f81fa70e13c1835970d4b807352cf7d89efa093120d527a000000000000000000000000000000000d384fa4729576214cf631ac1e6e2af54176954bd63f13cf15f2cd3c1db4cde4758d260ea4ffc0606aae700bca7ca7ff000000000000000000000000000000001824caf3b35915f528dfbc82bc5d56b5f8e7ba2b02056f6e27cbdbb0a54de8d4749446f14b116ff36b9fa773808c647d,,,invalid input parameters, G1 point is not in the expected subgroup -00000000000000000000000000000000111650bcc4c7deaa92ef43d8355198c1c0bc402fd758933765495eaf2a6c11ea6b5b6fb4a89b00040c900fdec791c7b20000000000000000000000000000000005519640447380e96adae5042193695193484d61ce0cf26acca8c96932be68e61ad6cd23515f13f8fa4fbdd6ca5390e40000000000000000000000000000000000c6f11a5306aff663038d949d08092275c7c507f68605bf9a4b591138f578f9c454ce12176d4759e1c95f3243185b9b0000000000000000000000000000000018b28c875d620249ecc25cff0ced2b3766aa66254906c69b7157b6e418a332293723b4b268d6f9d97f566b4998997adf000000000000000000000000000000000ce5e55ebe8326ee5650122f4b39dc96fe95aa4c48d26f70580fd97be90782bebfdb2d94e784786c4188ef99ecc33f55000000000000000000000000000000000632c0e5c998679e92ad269e587e831da5dbeaff3eda614d904e11c0e4dba3c87b40101cfe2f579e8015731d0ff22ac000000000000000000000000000000000111650bcc4c7deaa92ef43d8355198c1c0bc402fd758933765495eaf2a6c11ea6b5b6fb4a89b00040c900fdec791c7b20000000000000000000000000000000005519640447380e96adae5042193695193484d61ce0cf26acca8c96932be68e61ad6cd23515f13f8fa4fbdd6ca5390e4000000000000000000000000000000001780ae947388f0e055883d231f8809eb8fcc5e30eec44cbfaf11c52d4fc14bf54480c439e805559f64bd6f2085e12f1600000000000000000000000000000000142aaed1757c6f6ed83d532333e6f8f340625864fae2e71101d1ca6787045189dcb408c433caa3a19e9220f623398aa5000000000000000000000000000000000e29f5acc8998ea0d02d72559230f119ab9f8c4a013c63baa553e6ef7f5a5d38427f5b1e82b0879201ffb5ff3e23911c0000000000000000000000000000000000b934ca967385631e767483d6279afb80ea063b624491d5d837bbe4509e1f54a90b9ccb153039fbec7716dc77e28755,,,invalid input parameters, G2 point is not in the expected subgroup -0000000000000000000000000000000018bccb5411a58583445efe99e16d0b1fbb8ec71c6c8175a73e8d289f102d6925b68374d8986bbe9353640565fe30d3200000000000000000000000000000000013036f4649ab1dddd84a12d5a3efb93f8187824211bda276cef8376ffc90f5728bdef3b5b1bcafd59fd9ecf3bf9acb76000000000000000000000000000000000a43335eb6ff3bf2daeeb1eaf44c2782eeb517e82e55203a247b7a396e26fdf85f93695753c52c68819b58c95f361820000000000000000000000000000000000c240b7896b3dd0c318dc9ffcaa001d20bff288def3ce42752d660fd705e1544e292a5a0aa3a9a80ae91cb47cb938989000000000000000000000000000000000e5195bcc4ee8b149a769322165b6a3157ee7d04546643390adc812b6296675dbd31168b268df869a6722a7c8f51c79d00000000000000000000000000000000004af7dc8a5c552f00d55b996d193a9571173ea829eba8fadfa7becc2f4149ee7c6c4d2c8c7b1970df33cc56e4506573000000000000000000000000000000000d7cf8be632d98ad21137a983fa55acd08492a9d1e9d6caaf520713a10f5cd71c9a155ce9ba65044f42228959e893556000000000000000000000000000000000ddbd265cad3a9c525a30ebab137fb1857a9847e66c3381d51de1040a48835701a1f5627281f6cb36181d8c1c337e58b000000000000000000000000000000000a43335eb6ff3bf2daeeb1eaf44c2782eeb517e82e55203a247b7a396e26fdf85f93695753c52c68819b58c95f361820000000000000000000000000000000000c240b7896b3dd0c318dc9ffcaa001d20bff288def3ce42752d660fd705e1544e292a5a0aa3a9a80ae91cb47cb938989000000000000000000000000000000000e5195bcc4ee8b149a769322165b6a3157ee7d04546643390adc812b6296675dbd31168b268df869a6722a7c8f51c79d00000000000000000000000000000000004af7dc8a5c552f00d55b996d193a9571173ea829eba8fadfa7becc2f4149ee7c6c4d2c8c7b1970df33cc56e4506573,,,invalid input parameters, G1 point is not in the expected subgroup -00000000000000000000000000000000042f294cc86c53cbc520ce6368a7149676d8bc4acf708485057c8caae31409ee1586a735c3e1f416104aede85e40a38300000000000000000000000000000000153de67ca08cdb77e92091e8f04f75d17ee5525c5ec3ccdaca907b5ebc1cfcb6ce9d6a4358999cb00ae5e824d008e7fa0000000000000000000000000000000007e00b1cd95e3f9cbb2bf80404abd9768da125c42b746c2afde0121fccfdcc2431c618d646764bc5137657d2f0fcbda3000000000000000000000000000000001170cf72d827f929cb9efef52b559f8459cdd4d60464e0b3bc6e55bb6cf83cc2e9d6314b2b80e4e4f6a3c6292d1517b50000000000000000000000000000000012a7806f98848dd9c79f74e4a25812a6fae59ca73472fd20db2ecb8f732ea59294647831e03b58c60f7a71d9892ff26700000000000000000000000000000000165e6e0a602c7a1a3334a880ee47c4c440c27cfc1ab1ea6d9df592e98d21f85519d1ddf402f48ce7dc8a87439b3f42f600000000000000000000000000000000042f294cc86c53cbc520ce6368a7149676d8bc4acf708485057c8caae31409ee1586a735c3e1f416104aede85e40a38300000000000000000000000000000000153de67ca08cdb77e92091e8f04f75d17ee5525c5ec3ccdaca907b5ebc1cfcb6ce9d6a4358999cb00ae5e824d008e7fa000000000000000000000000000000000c8373f2969862e64e7a4c319a5e4db5019391ee2fd502c86bd074de5b3a1eff7917b3517175eb28efb9aa10057df87b000000000000000000000000000000000648b6bee01e4bd215ff4b51580d254e09a9b88d879ec2a0a42b0fe80792da10a9fc4d1c47058cabddbb48e94f0df98b000000000000000000000000000000000286667f7ed2e43aa08ba81a8ae4dd0487a4d11425b1e572359783ebda7c181b7dd62857a28ae3b05302cf2773f72bc5000000000000000000000000000000000349436e7b0b86db8e77d7a4dcbeb16c541b994e6f71c45098fd198991a27b4fb48025d0808000d54fe6b9500da80db7,,,invalid input parameters, G2 point is not in the expected subgroup -000000000000000000000000000000000b09f1c099c4743404de9034dadea6120bb4b120e81d415d047d575423d8261af3aeadeca04610d4bbefcd5fbd48d7360000000000000000000000000000000019e08ce27700db908d40e8907434068819f874b79b1540a03b856ba8898be43bf7d97b17685ab54de67602b8fc41f90700000000000000000000000000000000010714e7b0316ac3ddc1836a569befe3965800dc3cd2d9ecca097f2eebfebcce7cdc92df0110e4b872a673d5a0ebbda40000000000000000000000000000000016700d8c04f159b7a019cd0f7ade116448e0657880d364f19d1f6ea099222abab3b766d3088bd9eb870cdb3eece5ee4d00000000000000000000000000000000054f6e8c85be6d914162702dbdeb82801d598e504bfec39a2edd1035f69deccb605af437fd4ecdb23979e993904edbfe00000000000000000000000000000000183e494cd0b25d5ee1e8f1ca4054fffa4d730f547e072af920c88b9d613deb21dac38043c385fc9f9bbd6e708602ae1b00000000000000000000000000000000155d3e886cce6f257513529e40c21b5657ef1ff1f4e71bc32b968db3e05652b1ac780da573fe1a1b94b7fef86e7c260f000000000000000000000000000000001184cf09544ec2826d0101d2b79095da6e5f77d453203c52ea17b6476360ccf166ef092eccf86dbe3a260f7fd25a279400000000000000000000000000000000010714e7b0316ac3ddc1836a569befe3965800dc3cd2d9ecca097f2eebfebcce7cdc92df0110e4b872a673d5a0ebbda40000000000000000000000000000000016700d8c04f159b7a019cd0f7ade116448e0657880d364f19d1f6ea099222abab3b766d3088bd9eb870cdb3eece5ee4d00000000000000000000000000000000054f6e8c85be6d914162702dbdeb82801d598e504bfec39a2edd1035f69deccb605af437fd4ecdb23979e993904edbfe00000000000000000000000000000000183e494cd0b25d5ee1e8f1ca4054fffa4d730f547e072af920c88b9d613deb21dac38043c385fc9f9bbd6e708602ae1b,,,invalid input parameters, G1 point is not in the expected subgroup -00000000000000000000000000000000065a0b9822a814adda6f22f58f0ce0d6db9b32dbb2766077b6fb9bdf084ba584dc749d746740804f826d17634509875f000000000000000000000000000000000d4284a951847bad1b602396a5d5193c3a794826f58122c9c16c6e8e18f6ec2d0e17d8ca3cda9c3bbc92c51794ec7fb600000000000000000000000000000000177f2a7306144321cec932fbc1a10d58073d6915bf9ca97a05b54fe05f525ed0c327dbdb1205b70bf7ef8cf35a61c4e400000000000000000000000000000000089dfb5d4a99380761f75a94deeb6a48854164687f1055b22328d45b9792cf884ae597db1d1a93f3f2633d14969bb260000000000000000000000000000000000b6598d4c8c590f2fbbea7c48899ff43d73087becda4974184eb3ebab605e8f90497caa2fce915f7214dfe244277a437000000000000000000000000000000000acbeeaa0ddf12bb717fd32ea32ef63d137e61b5294c162d3b67e02dcf1075838bd0208d7f8edaf15f023611b774c14a00000000000000000000000000000000065a0b9822a814adda6f22f58f0ce0d6db9b32dbb2766077b6fb9bdf084ba584dc749d746740804f826d17634509875f000000000000000000000000000000000d4284a951847bad1b602396a5d5193c3a794826f58122c9c16c6e8e18f6ec2d0e17d8ca3cda9c3bbc92c51794ec7fb6000000000000000000000000000000001a002703d6da9def84f6ce69f02ce952562a7bab2413e8d58631a3387a1f4556402fba6a39b37bda22d08a68b0230b17000000000000000000000000000000001366c2a752aad0e111adb716b75459c067e275c692bc440731510d56135c3238b410538dae3ec328bcd817384d8a6b6e0000000000000000000000000000000003321a09e7290272d75882b64d2c958d4434df3499c65fbbd1236add534db804081d29b0b34167e05f4de1d8065b5b530000000000000000000000000000000001b6e16cbfe9bd8a291dcadd4599f6edb147e261bb76caca726bc89b3fa1863922e202bd5fc9afa5bfec9923f43fb526,,,invalid input parameters, G2 point is not in the expected subgroup -0000000000000000000000000000000012e6297c3ba79bbec9a84699ee4268a37617d21e3ce984ba1134041e539f0a5f0ac11165e835ed1aba23d3b2c5f804d50000000000000000000000000000000006c8881033aa6aef80b52f0744c0c9058f6ee2d7eeba8a749ab15af41b19be8d6aae30d820ed0a0b50ce327c7baa1a2b0000000000000000000000000000000012ff0494d308d3e7321ad4c4000e9dcd19552d5e4bce8504760f066e2fb2509279b01f1568e3c3f6216bd5328cbf72db000000000000000000000000000000000038c6e8f0fab30b5c8e4323c1fd29527845c29e1a26c70b8e5284f7ca55fb55ad4ad5389b5280927b98907132f26b76000000000000000000000000000000000aef946b9b9e9fcabb36507c1cf441df2f5ccd71ef9281dafa5e25bf07d69556e4143ab402dfb38aa756bb6ee009a6890000000000000000000000000000000015f69bc7b0a6f2cb64fd0897b421e339fcc8637efced8bf33f5aed809a38b49a2e6376d18b1bff0ef70df1b7187ad04800000000000000000000000000000000019a5a9faf36413a1e48a97458b7c416a634e1ed92fbd89fbe4593f42abad0ada72f50f7a1e1a802158ccdf923b497e4000000000000000000000000000000000e0b851da6a8005b83b2afd272a6cd017bec39d9f55b3230b600c50fb9bd5cc1bd229671f6b2c7f1d78652e4534745190000000000000000000000000000000012ff0494d308d3e7321ad4c4000e9dcd19552d5e4bce8504760f066e2fb2509279b01f1568e3c3f6216bd5328cbf72db000000000000000000000000000000000038c6e8f0fab30b5c8e4323c1fd29527845c29e1a26c70b8e5284f7ca55fb55ad4ad5389b5280927b98907132f26b76000000000000000000000000000000000aef946b9b9e9fcabb36507c1cf441df2f5ccd71ef9281dafa5e25bf07d69556e4143ab402dfb38aa756bb6ee009a6890000000000000000000000000000000015f69bc7b0a6f2cb64fd0897b421e339fcc8637efced8bf33f5aed809a38b49a2e6376d18b1bff0ef70df1b7187ad048,,,invalid input parameters, G1 point is not in the expected subgroup -000000000000000000000000000000000c321b54ea1ae6e893f2c28e72a3e4bed4a9aebaf147716178d3392c959e79b6f3393d738f3722a0339c773da3ae56760000000000000000000000000000000005d2c477e1c9333eb642bb40709b042816cc54134fad935942cc08eb2db0c1582f9bf06518d4fd5577df4634332e70e50000000000000000000000000000000019123b0d9c362184620c90834730c58ec5f9becdb2f3c6c00fd157ac83c16a815efb5057011d00c774d0de626274d58a000000000000000000000000000000001936fe98ecb82299a85304213a3e30c02d90ea871661b34f664a825184c2d1ebad84d144df88c479b9526222a7fe9ead00000000000000000000000000000000081a6abf02a0a236ba6006a95a8ab3f186e72f05b00f2b686049cf980898d64a71bf2e41b6b276ed6556cf83a3247b6e0000000000000000000000000000000012162cab3a7d92acc12efec9672ecb4cb30ae208eabc77608748e968a84ec0de81678df45d1655e45a19162b06354a99000000000000000000000000000000000c321b54ea1ae6e893f2c28e72a3e4bed4a9aebaf147716178d3392c959e79b6f3393d738f3722a0339c773da3ae56760000000000000000000000000000000005d2c477e1c9333eb642bb40709b042816cc54134fad935942cc08eb2db0c1582f9bf06518d4fd5577df4634332e70e50000000000000000000000000000000008b24839939deb424bb0c7bb171064e01453008ada6e3f14fd3a86db79162d0f9a851eb6abcb7dd27f93df0f7ff3320300000000000000000000000000000000036bdf42585414a8fd616bf967ed2ad4eb2b30a7581a58691c6d58e5d54fa152b42427c98fb3094c718943d9d014730c0000000000000000000000000000000002b542c2d6e8862495b2a6937f1fefbba53c228af512918bcc5b39083125c41340f1b5f1c60696c7c07f6705dfbcae9100000000000000000000000000000000030516a5aa32954dc083531035c55aa623d5f53c07b7bcff54cb9bf04d567a293ee55b2027cdcb864b133eac0f3d5274,,,invalid input parameters, G2 point is not in the expected subgroup -00000000000000000000000000000000018a649f727e9ac88994760a97b129d3347d30174d54a1442542123a76f805e1a48e7a71f3704eaca90b3c17c538d26b000000000000000000000000000000000a5c006871d73a11d525df1921d256f880c2a3c0aea04ed27e83d5c264d3f2196c997347299fb04149c4083876bbf3d5000000000000000000000000000000000995b9bb378a7c98ed661b493ad17b3aca367cc6aa6db24fc421d82455bca4edae6c891c191023ef2113f3c7eba79662000000000000000000000000000000000213eb30b55a6ab8efdaa67c6c99362dc62022041a6ee76f7c72cc13ffaffddf88bc68ce3d4ed36d54285b177bffc1eb000000000000000000000000000000001202977411cd6674c957c74471e269ded8140f72483b5bf81846ec60be1748080e67e38c8520b0b71793f2be9d2a5b1b000000000000000000000000000000000d49d0b96d12bbb9ae56cae73bf240cac03daa2743557e6a78f029883752ba011cbe216618b28cc173a186750602eb7800000000000000000000000000000000076ed600ed860f16ec5dbae3f09471302bf85fde7702b3376b0d670f93560e77699bed969e7001570f44dc5e37aaa830000000000000000000000000000000000c993a8b08d2eb00bcee05e1c09e8a37834fac53643643402f60fbfe2cc7d795f5c68f3d6a32c8604c37211585830426000000000000000000000000000000000995b9bb378a7c98ed661b493ad17b3aca367cc6aa6db24fc421d82455bca4edae6c891c191023ef2113f3c7eba79662000000000000000000000000000000000213eb30b55a6ab8efdaa67c6c99362dc62022041a6ee76f7c72cc13ffaffddf88bc68ce3d4ed36d54285b177bffc1eb000000000000000000000000000000001202977411cd6674c957c74471e269ded8140f72483b5bf81846ec60be1748080e67e38c8520b0b71793f2be9d2a5b1b000000000000000000000000000000000d49d0b96d12bbb9ae56cae73bf240cac03daa2743557e6a78f029883752ba011cbe216618b28cc173a186750602eb78,,,invalid input parameters, G1 point is not in the expected subgroup -00000000000000000000000000000000012065f7dc3b8d6d6dded1106ecd071ac0e5f73c2aea8d088bda7687ccbb34625e2da53befb200d0885b25c228b3637a000000000000000000000000000000000d222a3c0a560f9e8a6624d9100b72e62f515f1d9384ac966d99c1761264ff8e88f308e57ddc94f156655dc310b3976e00000000000000000000000000000000109fe60cebfba62b89ae166733a097629026ccee41c95ad0260c96b772293e1403247b0451d149d527212c228ca6733a000000000000000000000000000000000a497d6c0285f7d5434c42605077528e24eee8185a615c39d2caabc570bcc01b40eadb937d78e6ceb8572115671053c8000000000000000000000000000000000f0d14ceab429a46e5568200034dba88a713899a12602529fd015e2c792191d8ef492a4d685ed09a75f638ad56f02ef10000000000000000000000000000000004d4b477fa154cd86a0934130c27f4eefed4b986da5afadf558d4fa003d2480a93b351798a24c2232e3c09c0bc33e7a400000000000000000000000000000000012065f7dc3b8d6d6dded1106ecd071ac0e5f73c2aea8d088bda7687ccbb34625e2da53befb200d0885b25c228b3637a000000000000000000000000000000000d222a3c0a560f9e8a6624d9100b72e62f515f1d9384ac966d99c1761264ff8e88f308e57ddc94f156655dc310b3976e00000000000000000000000000000000129c37bc44471eeb38b484699156862250e40df9415876f8a0da3d2f2504bd5dfe76e7b67f103a94c20751a656c5c1020000000000000000000000000000000011f24e9760017ed9120e3e0c25f57ff9e70f4f15265cf884d9d978225996f21905b32c0f918e5ac30e095767779cac8f00000000000000000000000000000000012e10463254df4bb4765a10ba47f47b6ca7ccf1ed289b8a9ccc4ee5cffde83a45077c3f093d0da6aaa5f38ebc3e5f0d0000000000000000000000000000000006755c3c202da2f65be8880e12485e2f8ea85b8e6c4b21b559e2a45b5f0977e01bba8685e4d7acb4fdc045a4cb6bba9d,,,invalid input parameters, G2 point is not in the expected subgroup -00000000000000000000000000000000182c68fe02eb491e1c0939304135485dcd2955c643ec67198d4a07075f0ec96441ffc1274e75dd36b103053660811643000000000000000000000000000000001308eb23be0860718e6ce06c2f24f9f94b7a72c557559ba8e1a9e3bc4eb4df009472adbfb26689900a65ee80b976a5c200000000000000000000000000000000030cc52d7901d0360d10f344cecc8325412788cc30a912d5de3fa9bdab18db44efea235c5d34bab526f3b8ecee2cbb8d000000000000000000000000000000000cda35f561c19ebd85a445ce8bb1618b446c7013c07606ce58e0b5627a5c9e7cb200e2b8ee12a0564730279e75b469b500000000000000000000000000000000055ad0655a96f6dab5a432e7d2fef57a6a11113070444089df23b4b911e0994b90aaaaa2c62d06756f4704fa218f7c350000000000000000000000000000000011d22438d7c162d34802a664c254abaae07659902e1f1bfc2bdffa6c17eb11bff5276474cc3cec9507e28685f1c21bb00000000000000000000000000000000005711605edddc03aee2e53b0945162616b969fc4ad2c15819df360533120dd2ded321aa929d67dbc84ecefeed531a49d0000000000000000000000000000000012adb2a59f85343c923642ea4be500595ec8c76755c0c219e5484a7a0f53a4c3f9740cf6973aab349973295791472e5900000000000000000000000000000000030cc52d7901d0360d10f344cecc8325412788cc30a912d5de3fa9bdab18db44efea235c5d34bab526f3b8ecee2cbb8d000000000000000000000000000000000cda35f561c19ebd85a445ce8bb1618b446c7013c07606ce58e0b5627a5c9e7cb200e2b8ee12a0564730279e75b469b500000000000000000000000000000000055ad0655a96f6dab5a432e7d2fef57a6a11113070444089df23b4b911e0994b90aaaaa2c62d06756f4704fa218f7c350000000000000000000000000000000011d22438d7c162d34802a664c254abaae07659902e1f1bfc2bdffa6c17eb11bff5276474cc3cec9507e28685f1c21bb0,,,invalid input parameters, G1 point is not in the expected subgroup -0000000000000000000000000000000001d70e0c28a776a0fef6d7d6a729f03d59c20a4c1f28bcb28645b996c307483837361d146b73fc808041893ac1738365000000000000000000000000000000000a68b5a13a6e5ffb88ad2618be4a197271d08e55781d21c367209c08fab2545b99f8e1e0b523854f075a915856f7483d0000000000000000000000000000000008c7569bb4e8d3b213dff2c132e5954e9622edc874dc82fcd674405cfda14dbeeb323d1605d06a92231f44339952333300000000000000000000000000000000092decf1271f8cb90a67c6cbf7eb0cca0c2d71c698470193ef495e494a8a1ac3b6bd78fa6e4367874ba18a00f6eca025000000000000000000000000000000000dfeff0f041a1868cd0ede471164f24dcac619c56515b8eec5c8aea870a79a2d03f0e1526eb1e8cbcba908969b5e952700000000000000000000000000000000109aa32bee0a83dae428e388a39ece51fd3f392ec841ffaa2554972528b7f55ca36b19ef6ae585e91995a50c0848cccf0000000000000000000000000000000001d70e0c28a776a0fef6d7d6a729f03d59c20a4c1f28bcb28645b996c307483837361d146b73fc808041893ac1738365000000000000000000000000000000000a68b5a13a6e5ffb88ad2618be4a197271d08e55781d21c367209c08fab2545b99f8e1e0b523854f075a915856f7483d00000000000000000000000000000000065860fd6efa478810b70e56ca788706bccb63191649d7b9e92941efe264bd4720b1f536d90a034b681ec9ee16f50b5c0000000000000000000000000000000014e4277fc0e4827510d55f27162d85d362f99ce57a7baf74f62567d42efd3afefb61782a6ba85d4d0e27184dc15b30ea0000000000000000000000000000000004a18f9b07e2c61210d7b00d54dd1e2895692b928adc0a4597d4ff7efbfd7215e764572275cb78f29d106aec578ed1a200000000000000000000000000000000025c5527f68d69ca8d86ffaaa3330784b9a9e32069cdc2e147c81d9a39eb181e39592a126d428e6f7963d34292f44e4c,,,invalid input parameters, G2 point is not in the expected subgroup -0000000000000000000000000000000010f18fc4c3d422a64f6c8935636cade47636934cc6c7d5077428ca6e2641068f4668a792a20c6bd4890da09de9765409000000000000000000000000000000000795df122ae83682617000aa2f80fb82cf933b2e5765fde4409249ac06d8eaf2a92938fb7bf4cc5717e4c604b68afc1f000000000000000000000000000000001825f573c335f0e3ad6ca9f721b529ab1a84585094c034058fce2f84185d99ab78e568b7cf129adb65501c266db679ff00000000000000000000000000000000114d2a8a69b83b46acc0dc3cde307b4690d2335c18b583874a0f5f6ed6b4e4ae63fb114d32479d56d3d2ce6393a128a900000000000000000000000000000000088c08b1b66f37b98e443f9d390b9934ee8edee075788a6ff9620c386f8ec4c1f6455704574b65086170c8a37f1728be000000000000000000000000000000000366a281910a6cb906b8acdb68180c6068b555c00d84b2cd3153ce5b8dc64532b3977d186202d1d6c00673b7ffe42c1a00000000000000000000000000000000067458ca402c19488e2515037abf9323ab8288e0e11f7cdee18b3da50cfa377435cfde1f63dcdc451ce65a05641cae370000000000000000000000000000000010ed9c895629bdafae66ea176388be4e4ce45cb13ecbe0869ce57f0f48852b6b8c47bcc4a14fc5327f1df372ad9f5d4a000000000000000000000000000000001825f573c335f0e3ad6ca9f721b529ab1a84585094c034058fce2f84185d99ab78e568b7cf129adb65501c266db679ff00000000000000000000000000000000114d2a8a69b83b46acc0dc3cde307b4690d2335c18b583874a0f5f6ed6b4e4ae63fb114d32479d56d3d2ce6393a128a900000000000000000000000000000000088c08b1b66f37b98e443f9d390b9934ee8edee075788a6ff9620c386f8ec4c1f6455704574b65086170c8a37f1728be000000000000000000000000000000000366a281910a6cb906b8acdb68180c6068b555c00d84b2cd3153ce5b8dc64532b3977d186202d1d6c00673b7ffe42c1a,,,invalid input parameters, G1 point is not in the expected subgroup -0000000000000000000000000000000018339c70bcf5f8cbf7f3d67dcc7b40a937045b53243d6bcd417d467b1a264b960793171fda821c4206d10b4fe8488e170000000000000000000000000000000017907a4fd66b2a8b113352975ed70c0833a6fb51568ada67bf9f8cdc770e30a3a5fa305200691f4e8dbf210cbefeb002000000000000000000000000000000000b46ea3a7acd5615741210a761f7ef55e7381f52593f02e20aedc0753861acfabb5333dc5bfd829656511070b642e7fb0000000000000000000000000000000000989e2bbad608bc55d0749e0de844e001934d28a58178377d8607a1c5d5c85eb346ab94bd527b36b965626457f6aa300000000000000000000000000000000016ffa6209c14e0803789f94886e96bfbab47e07ef745557f1d1dd48e887086424cac57dd9a624de73bb7f3521de3fbe2000000000000000000000000000000000fac98b30fb441d9426f61bcdbb149b103fa9ec3b85cbd5f755d57474bbeebe796b96fac0ba1d4b75897dea8e54796970000000000000000000000000000000018339c70bcf5f8cbf7f3d67dcc7b40a937045b53243d6bcd417d467b1a264b960793171fda821c4206d10b4fe8488e170000000000000000000000000000000017907a4fd66b2a8b113352975ed70c0833a6fb51568ada67bf9f8cdc770e30a3a5fa305200691f4e8dbf210cbefeb002000000000000000000000000000000000b349ddaccbdd1381d9eb4ae7e65db31733fe3c24f38c21f5a20298bd8e01db7ebbe3c703327a5f81c89c040a5e17c67000000000000000000000000000000000dbadd08c77f8c210439d48fb55c741dc83aa9adbe7153bee1ddf1d3df824938c14d85a528c285db28df1d0fb22b8e820000000000000000000000000000000014d76b082d032e23130d6e55c0560080a5aa607f6cdfcd683d4e2450aa0788a99a005ccbdefdb3f626a045fd7cb9ed6400000000000000000000000000000000124130e29fb52ffc0d8e8ec760619de7397813b8ce598afe97f9076899f13dbe35417a6f94a2c4bc0341c933ab8c30b1,,,invalid input parameters, G2 point is not in the expected subgroup -000000000000000000000000000000001977147ab98c2e88f46f038c821e85fdf476bfe7bf7b7fd633ecfcf1fd2ef339b3b5cc686f4a8937e16dfa29ad8123f7000000000000000000000000000000000ef669778c61c21dbd4681483c51269a74a3ba1b1f75796b4680f0e30a286aa0173147b206f91c84731ec8ead074d187000000000000000000000000000000000e0f7595e4c136b4d8bbd1eeb021df7dd2bcf1d9f98e4fa293f7edab635e019af87c138275fefacd806213177af40eca0000000000000000000000000000000005dc209d6c86f1871637998c10490a70371f9e00a68d1363dfaeb62046473dfb4bbd3b18b943439f75c45a1ee7f264a90000000000000000000000000000000003d215567d1e8f504a72658d48fa51374ac77234552c17db4033af780133d8516bb0769678ecb50b8b9eb950c2dd73e80000000000000000000000000000000004d780849b731012e1e5732d5f6d32c659a95c3e1c8f5ef4841fe82afc6f0aa309b1e02dc2554a4a4ee781be2be2149f000000000000000000000000000000000073439cedc08916d00609c6152ee2844be85550ff5c199e9e9dddf09bedfc00d907dc85255651cff3e28a74d3b4836c000000000000000000000000000000000e57e74af4f2d6c08843152cce6d095d00679998463bfb41bef9c57ba427e14715e9e0da0e8c5193a798cf92590b34b4000000000000000000000000000000000e0f7595e4c136b4d8bbd1eeb021df7dd2bcf1d9f98e4fa293f7edab635e019af87c138275fefacd806213177af40eca0000000000000000000000000000000005dc209d6c86f1871637998c10490a70371f9e00a68d1363dfaeb62046473dfb4bbd3b18b943439f75c45a1ee7f264a90000000000000000000000000000000003d215567d1e8f504a72658d48fa51374ac77234552c17db4033af780133d8516bb0769678ecb50b8b9eb950c2dd73e80000000000000000000000000000000004d780849b731012e1e5732d5f6d32c659a95c3e1c8f5ef4841fe82afc6f0aa309b1e02dc2554a4a4ee781be2be2149f,,,invalid input parameters, G1 point is not in the expected subgroup -0000000000000000000000000000000005585544fc7ed448c7939c42235549edc98c5d9a793ec918274b49687e8b9267d53c2e5d66a44d889e8f2e94abab43490000000000000000000000000000000002a091dc69d5af394d408a3b5b60debffddac4db228c613b2e2f98b932aa8f90d9f5a77129fc0fe69b93ca64b0081eb9000000000000000000000000000000000f694959a69b0d692e167e95524555d58d06621d77f46c54b7f0da0a45c4cabdc8ac916b0f2052cb99b6f0f5bca36fd9000000000000000000000000000000000df814c1cf62a7a089984a3afe3f7636157ee17bbe0dde1aa2e56fe7168a3fee7c6102999dfd55b282f1a6a4ca0e0cda0000000000000000000000000000000002ce0806a288c2b9d7e03204f573b06c440be782469cf7f1478f53d5b017fa9ea3b1025cc5de378e2186c150cbd1bc0f0000000000000000000000000000000007d3c1d2c5806e73119cafe9efda5a8419679c89d17f7a90fa6302485d4efe9e44b287f573d576d7f45589c7501e40ff0000000000000000000000000000000005585544fc7ed448c7939c42235549edc98c5d9a793ec918274b49687e8b9267d53c2e5d66a44d889e8f2e94abab43490000000000000000000000000000000002a091dc69d5af394d408a3b5b60debffddac4db228c613b2e2f98b932aa8f90d9f5a77129fc0fe69b93ca64b0081eb900000000000000000000000000000000199d1db3ab960c003575ba7a53f489159aa2005bc9a30bc23e952b57ac892a2340a8adbe21eb07bcbac255be231c49120000000000000000000000000000000014303bc0d1c9748ebbbc187486650cd7651e51ebeb1bb428e153ca5e83fc4b118c7575efc03ac0ba500c6149f91db23f000000000000000000000000000000000b6383243f6914d41d2947ee74ccd299a20741e33bdad3aff2d05de662fb4d7b9b9266085bb71dee8c13dfbb121cdd320000000000000000000000000000000005d515210d948c48ee9f7ced4c23a5eb12c6bc26c974d518af4e14c1b2b2c5a286c2bc4d51ff5974eb939b4395f9d8ed,,,invalid input parameters, G2 point is not in the expected subgroup -000000000000000000000000000000000ba91d035c7a2da7e14c400bb137b66a4d8f563004bcf57a01f21223225b93f860eaab9c429b915a130115c8f61c606b00000000000000000000000000000000196c7f0f6b99772d1a5e97afd92a6ab4916a2f2df709bf6393a60fe29623b25dbdc989f22715fda427bf8cb84cdfd033000000000000000000000000000000000b362ce289c7edfb507b579fc8d344c5e7bfa8d58bf3629a41af7ec1faecfb9b95139d9a8159804691047e4b18bc1cd600000000000000000000000000000000112bac785b4033f11b08e845c8c5ce78e40138b0cc0b998dd8e0213bbfe0e5b96a83ff2b53ee9699b18efffdf879602a00000000000000000000000000000000157885a39334664681d8425bd60e9c1d12dd1ef45b9a3c40956df105cd3534ff9378203755119ed302a54adba9e5858d0000000000000000000000000000000008237a9ffe95f89a24e1e3a82d8f127766e2173505a9ef0e715b1ec711619664a12d86d247e530049ee542ee4d20cc7000000000000000000000000000000000072c644635936a91dcaee40e3b4794e634c315a39a9cb5cb99ef6784b332fdcfaafdc80e228cd19d0104d5796f584c350000000000000000000000000000000002318bea9077484e9c1937dfa63774b5ecf6fc63ff06e5cb653553d5111a981c09c907069ffe11b5704ea60a99873283000000000000000000000000000000000b362ce289c7edfb507b579fc8d344c5e7bfa8d58bf3629a41af7ec1faecfb9b95139d9a8159804691047e4b18bc1cd600000000000000000000000000000000112bac785b4033f11b08e845c8c5ce78e40138b0cc0b998dd8e0213bbfe0e5b96a83ff2b53ee9699b18efffdf879602a00000000000000000000000000000000157885a39334664681d8425bd60e9c1d12dd1ef45b9a3c40956df105cd3534ff9378203755119ed302a54adba9e5858d0000000000000000000000000000000008237a9ffe95f89a24e1e3a82d8f127766e2173505a9ef0e715b1ec711619664a12d86d247e530049ee542ee4d20cc70,,,invalid input parameters, G1 point is not in the expected subgroup -00000000000000000000000000000000077dfb9ac791b3471c6cbcb0b37b65adb0bc4e40341b85c13867bfdaf32365ddfb749ebfd965abfa22996773eab505540000000000000000000000000000000015e771a0f0149cfad7910a4a1971b39323948bc7530936db5d99a53b51bd656bdce093cc2b91ebad0f91a95034afa0e3000000000000000000000000000000000200775a5848ac14b5e762ae7d4b492d2dc0bd5e80ef7fc760d42ea6fb07ffd2944409052cfb773875df676a188da65b00000000000000000000000000000000140ec7de210c890e4c795eed394d32d77f6acad0a3628da2ec805d4cf2de9822b5a73f06bdbeeba0fc1068c26da675b20000000000000000000000000000000018e7877a3f27c5400b08bd2769616745e4657f6fe262f9d7b88330917f977efa463b3226f3433da95a82568d990b1fa50000000000000000000000000000000015801af4934193336cc67fe8f4be5d2093909006f8bdd3382d60fd5c6bce4b86071370eefbce7a04dbcfb825858f90eb00000000000000000000000000000000077dfb9ac791b3471c6cbcb0b37b65adb0bc4e40341b85c13867bfdaf32365ddfb749ebfd965abfa22996773eab505540000000000000000000000000000000015e771a0f0149cfad7910a4a1971b39323948bc7530936db5d99a53b51bd656bdce093cc2b91ebad0f91a95034afa0e30000000000000000000000000000000017bbbfef68883fbf6fa9cdcef37ef145d7fbe9532164530e9c08d196ee41f38784b257087ad53f4939426451fb5f955c0000000000000000000000000000000018bb97090375c21600b6d3ee0629cff78116aa59d7b665c08590add8cdff8247c38b588a25e0b11eee5111c63f8c619e000000000000000000000000000000000e181f7327776df8ec16258115303029c68e1b72fcad6395c6d7d477368e1697076333a2102447a225fb3f3d725b3b0b000000000000000000000000000000000291d82b95e9a2c3f766994c304dae7f19f1efc789f68c4e58eda102da36cd0d7eec3d5a1b1e88c63462c8ec0e4393a4,,,invalid input parameters, G2 point is not in the expected subgroup -00000000000000000000000000000000121a9b867c86195dc4aee07081c1ad62f066b471bb5a14f296943b263fb9a25e6805e3171624e7e7e45b78f175a1861300000000000000000000000000000000071e1c35979d6f43170e79c0db5cceccff01f17cc2980b771a6cc38e0b27438a9db8e00eb943142d992c6a395fe4aacc000000000000000000000000000000001819d13cf4522a9362bbeb0bbbb0a498c3f34da1c9e3b2c54d08f7c8acd9ee756983fe80405579effb79d673407390ef000000000000000000000000000000000f870e5978f4a6e3b655fb2a05541ac0673e7b10136adaf28be4dfc9022d4cc8a60e17d125dfe53fbe10c644ff37e02a0000000000000000000000000000000010207ef774cddd10db2bca0a051ceb12900c407ee265dea4615553c193d7475b5ba3198b7e0160740e4fd015dca33e1d0000000000000000000000000000000017937be546e06fd2eab4c969a029534c02fb770646d43edeb5e6c8bc0c2b5f35576c375bf860fd1087ce099d4377d24e0000000000000000000000000000000001a8b8cdcd160565a1df9cb5ccb06a62fbaf32b2cac4ec9a552773313c940688638775983815cb246d4eaafe91c3451100000000000000000000000000000000082a1237c161831a37589ff711f7873d5e092d8a4690b983c9ccbbf980422ed177a3ebbd4b4ae4b557bcb3ae532f1823000000000000000000000000000000001819d13cf4522a9362bbeb0bbbb0a498c3f34da1c9e3b2c54d08f7c8acd9ee756983fe80405579effb79d673407390ef000000000000000000000000000000000f870e5978f4a6e3b655fb2a05541ac0673e7b10136adaf28be4dfc9022d4cc8a60e17d125dfe53fbe10c644ff37e02a0000000000000000000000000000000010207ef774cddd10db2bca0a051ceb12900c407ee265dea4615553c193d7475b5ba3198b7e0160740e4fd015dca33e1d0000000000000000000000000000000017937be546e06fd2eab4c969a029534c02fb770646d43edeb5e6c8bc0c2b5f35576c375bf860fd1087ce099d4377d24e,,,invalid input parameters, G1 point is not in the expected subgroup -000000000000000000000000000000000a86f90fcd9b63a0cd5f53356c170699d78d03f180d59c38ff770560bda30bf412fdcf236b6720d94684ef1aab97bafc00000000000000000000000000000000030bcbb272ab20a2f27d45295875e3c29a8ad088bb8173feb6f6f4d2c81bbc91d673c23239e36bdafada8c7d50b54b440000000000000000000000000000000011e01c96294c726ed3ef22a5c6597d8202604c4fb14058bf44fa64ae6d342f6f77f151c8ecd99f793e79f0ad29c309f40000000000000000000000000000000000e186664d8c2a2e136ff1be2192573b94c083bf242b2c01c984a792e43a11a10599481a9a79f6a8d5b074bc16019725000000000000000000000000000000000fc48ade56f841489c4824411130fb5b7e0e83b613fa09099f318cef207ce035d5d6e7ecff5057c15ee764ec8a7fa20300000000000000000000000000000000003f1b261043887af57fab48b505ed7aa44132457d71a390646b70fcd073e677a7e275a89dd0a2bf32beae3b2bcbd6e9000000000000000000000000000000000a86f90fcd9b63a0cd5f53356c170699d78d03f180d59c38ff770560bda30bf412fdcf236b6720d94684ef1aab97bafc00000000000000000000000000000000030bcbb272ab20a2f27d45295875e3c29a8ad088bb8173feb6f6f4d2c81bbc91d673c23239e36bdafada8c7d50b54b44000000000000000000000000000000000781404020a416e2596ab361e02674e25cfb365420d35d5db7146f563a7675a942383da44ae4df49c45b38e371c82a2a0000000000000000000000000000000010c546bda090a13ccf0fec03bdcb87b41f5aed3b4e6740690afb9dadc57d773aae2d22a2d8323336c5b1dc5798725495000000000000000000000000000000000bea6aaaadbbe8102212279f1458c461d3a0d54e341c91b5e16e0ce5ba1517a13cd1d43e1d0b25a63b7cc57ece5369f3000000000000000000000000000000000b0b676e5cc2f6ac383f5dd42d379c552579f601de0cf4f34ac637383a31e393df40f5c0f95b5a8f57cd6fa4de01caeb,,,invalid input parameters, G2 point is not in the expected subgroup -0000000000000000000000000000000014372fb746da15863e9ee4e06099c7e513bdbe53ca772a4b61c81eaa7f841399422f7902893d5ee7f7d59d530e3674b10000000000000000000000000000000006ba991efa65ef8dfac8b07915cab83b5267babba1291e4662a81fdcb455faf33596f6730b6f5b3eac2076054a4ccf6600000000000000000000000000000000042ee88071289a2adeb69cbab5a3ac8c7935576bc434062091cdf1cada4b67a2501c179b5980b53256f623840a5aee5700000000000000000000000000000000063b0819dd470047a704f20f5f7c65ea0899f25603dfc7e8b8d5f0d0d323180aa921e43d63b45acc8fe9054326a8d9bb000000000000000000000000000000000615e2e5b0389017cd3ce7c15740caf3b897fbe4a59c68247c3c4229bf661257f56bcc10f55fc722f96424f5617d259700000000000000000000000000000000166f7cadf7cb9ac5a8cfa83fe4aaac0e32fd4de3e38e0d39e010d50f5b3d383243d6870505f2a285b7c5f6fc1b13f0f0000000000000000000000000000000000d1ed017ef4702bcd3bfbbcff36000af6a1d26ab363e68ea5629027e0b90352bf1d8e03c13a7955da6c15507cc1c9f47000000000000000000000000000000000e09830e54fe9eddd416479a1740f6f1b7693f2d153d322f27779b16bb6451d7657df85a55da75a4aee0a2e33b3a46e600000000000000000000000000000000042ee88071289a2adeb69cbab5a3ac8c7935576bc434062091cdf1cada4b67a2501c179b5980b53256f623840a5aee5700000000000000000000000000000000063b0819dd470047a704f20f5f7c65ea0899f25603dfc7e8b8d5f0d0d323180aa921e43d63b45acc8fe9054326a8d9bb000000000000000000000000000000000615e2e5b0389017cd3ce7c15740caf3b897fbe4a59c68247c3c4229bf661257f56bcc10f55fc722f96424f5617d259700000000000000000000000000000000166f7cadf7cb9ac5a8cfa83fe4aaac0e32fd4de3e38e0d39e010d50f5b3d383243d6870505f2a285b7c5f6fc1b13f0f0,,,invalid input parameters, G1 point is not in the expected subgroup -0000000000000000000000000000000009f0c6f9fac38e8c83183499b8918a1ffbc52f2400882edb66594f496ff38ffec77368f28e4f20767257e200f48255700000000000000000000000000000000002a05bfde9523ac13ba3518cd5b308c4985484f996e7192140d681d9934d501111a81445031d84d4a47a9727202c07620000000000000000000000000000000003004acd2a95d932b84233e80bebb9fd92b302809725d5ca0921a5d8983950ff521d89bcc2d1bc1e7c186c702bf7aa270000000000000000000000000000000011744ffc7092744a79e345be8b51569c5df8eb10b4e49957ade8df4ee4ede566b3825eec89027d70d188ff858a8b6cf4000000000000000000000000000000000e46cd26b21a8a933eac05ed526a2b8fe195e5a5435e79c7f385fb69a90190acd06e25e9b63af7862616c79add032597000000000000000000000000000000000ad60c22b3690c78c23682ba902a18e708e88430a55a9038975a43b4606ef4c6e2b8e648a25097b3a34bf6e4024d00280000000000000000000000000000000009f0c6f9fac38e8c83183499b8918a1ffbc52f2400882edb66594f496ff38ffec77368f28e4f20767257e200f48255700000000000000000000000000000000002a05bfde9523ac13ba3518cd5b308c4985484f996e7192140d681d9934d501111a81445031d84d4a47a9727202c07620000000000000000000000000000000007d87d13752c52bf0510cee94274f6f4a6e0675de9a4a864ba5058dd8771b6c5000e957cfca5279e64f09c21111322ec0000000000000000000000000000000007999819b5b57104c9432a9d4dc6ad377f0c6f0dd630155fc489aed1f8d18ce0386222813726cb786635778b74967bce0000000000000000000000000000000016b66e0ebcbf6043f6a7fe52bf527a9b763cd68d901933068966e6dbb9817e1287ebc2de9c3729df8b4228a4f92d9732000000000000000000000000000000000ee19b863d5ce19afce76e489e122948597ac6a5ee07e2d856a49377285ac93d6674cc5429e02bbd051d4edf7988ba89,,,invalid input parameters, G2 point is not in the expected subgroup -000000000000000000000000000000001269c2717ba196d5004865af806d4a99b8c238583db14f9c02da70b0275cf35a3a5276eec0c8e6934f11e0d5cc8b7c9f0000000000000000000000000000000017971814f15aaf3f6672b3a720cf6726aa042dbd82ac508a8f7ac5ddf17f377891199ba2fd01d990868347d45e3b37ae0000000000000000000000000000000001587e32753adc85c98cf1322115772b0e282ef4e6a75944fc86091e81aad076508e3d727f4df0e30924fff6b67c312e000000000000000000000000000000000ae96d3a1b79985e56f80df8ac4d9792229ca580b156dbbe71a9db470447fa4dfa19fc8a8a2e2f0fae28a24b7d6153d100000000000000000000000000000000114101ad0d29ddfd2fc436d2a270711c444c8c257785f4b4c549e9c795f6dd9834d3744995d2188c0c968752a7f68892000000000000000000000000000000000d30d9cc1e2273af745dd47a596a2202ca4fb655f9f9beeb0a87631e2461f29206163fd921761fde69654cb02e23505c0000000000000000000000000000000010cda048fed479f7bcd388a0acaa977b134055f5ea92b2a689793e301d58190c67031920ccf1cd97ecf9f429f5a022e00000000000000000000000000000000014c410faae20d54049aa7c644ec1ef0388367ac847f6781e62ec88eb9262ffff5f19cf5f4ebe791a44ad9a84fd78aca70000000000000000000000000000000001587e32753adc85c98cf1322115772b0e282ef4e6a75944fc86091e81aad076508e3d727f4df0e30924fff6b67c312e000000000000000000000000000000000ae96d3a1b79985e56f80df8ac4d9792229ca580b156dbbe71a9db470447fa4dfa19fc8a8a2e2f0fae28a24b7d6153d100000000000000000000000000000000114101ad0d29ddfd2fc436d2a270711c444c8c257785f4b4c549e9c795f6dd9834d3744995d2188c0c968752a7f68892000000000000000000000000000000000d30d9cc1e2273af745dd47a596a2202ca4fb655f9f9beeb0a87631e2461f29206163fd921761fde69654cb02e23505c,,,invalid input parameters, G1 point is not in the expected subgroup -000000000000000000000000000000001252aaecb588ffcdee3e4fd92ff5164feaf9aa39acbc71c704d8180611d30fe13e59bba805101dc1cecf77b254bc65510000000000000000000000000000000009dc3de2e8aa94dbcc25c8775e9bd0ae0fa8581df790e562e67f24c08efaab59a0a8062478a09c262040c5f0558971c3000000000000000000000000000000000b0a6f9e0b58db3015e1dc63f9d377895d25f48e8a05371ad90c3ef5f3085a76b888d38693eefdf3b1eedf80eab1736200000000000000000000000000000000006dfb36e1c281cf1c5a8be9a631cab94aa956bacf8e9e787c0e2bab4440f03f0efc56d5a058fde2e18696c569676d3b00000000000000000000000000000000118791bba7507725b7106bc889b68c3daa56dc3100d8378cf156268f249dbafd01025cb722d58246c95ac856dd5d0411000000000000000000000000000000000f942ab8fd1e71f6d4498403116ef41954e7967222f894b93ae31f061cafaa1ed3464520dc5123aad4b0a352a85efbf8000000000000000000000000000000001252aaecb588ffcdee3e4fd92ff5164feaf9aa39acbc71c704d8180611d30fe13e59bba805101dc1cecf77b254bc65510000000000000000000000000000000009dc3de2e8aa94dbcc25c8775e9bd0ae0fa8581df790e562e67f24c08efaab59a0a8062478a09c262040c5f0558971c3000000000000000000000000000000000c13d99118e4946773d0ae54a37895411e39ba0de604c5f69d0b3ddcd50c4261c38c510bc1e018dbdf449e303e398d820000000000000000000000000000000018427393a7ed2dee713e83e58a6537c5c6baeb69ceac3b574e02af78215d99b8cd01f0c944d075300d35176099b0aa8100000000000000000000000000000000140ab2527b79327e07344a673e688debec28aa29219a5b1646a3c2b599a9d374cf5e139ab00aa237bb8e29d021d766ea0000000000000000000000000000000017164f154d26566ecc983d38d77d694208864c024c3ffc69f19f84550e86eddd8dbb055a8cf543717ce3d65e1c64c53a,,,invalid input parameters, G2 point is not in the expected subgroup +000000000000000000000000000000000ded5634c6bab9610e70f3a9be2bb39c51f2ddd762d22d6c3f0961af19350c4ca4bae333bf4cf586d8cd1e0a0a6c674e0000000000000000000000000000000012309fe1d843245e2cb58d419ff06ed8e93ec901c01d0c5be453e11d10f930afa0c35428ef0c8dc13ce99c990af166630000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1d9f0000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a400000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1d9f0000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40,,,invalid point: subgroup check failed +0000000000000000000000000000000014881e90a100e5c1e07588d68c69b9b217d2c57b2bd8782ea7579abfac3a38275677a8dee7e689c1b185712ea38bf0ba00000000000000000000000000000000061e48ceb68db1cb84ed39f9772f937922198b47082d7517c8258e46a490fc20e4e01971711853ab653d064f8adc79e200000000000000000000000000000000173518c27d76414f3027ed3e4936ad1b6aaebf0a98462df3e3a55aa38e19fb121acc6eeadb7689c84fec18580b63784c0000000000000000000000000000000007c116829ccc1d5505dcce029f5c8b7c8a9cbc9dd562a874766a5654483aec977a0fb9e611f9471d2f0c91fd6534892f000000000000000000000000000000001560b267e66200e69d6298250456d88500a0d79ea69870358a6cb39609fb6596e539b28bfea9d1c7054b5b51a4c0f3950000000000000000000000000000000000cc196f93e5a2dd3a41a1ba23fd2e53bf376c910117e9216663e30091b96c897080bca189ce3107720dac1c54dc4fc70000000000000000000000000000000014881e90a100e5c1e07588d68c69b9b217d2c57b2bd8782ea7579abfac3a38275677a8dee7e689c1b185712ea38bf0ba00000000000000000000000000000000061e48ceb68db1cb84ed39f9772f937922198b47082d7517c8258e46a490fc20e4e01971711853ab653d064f8adc79e20000000000000000000000000000000015237996817e97b29ef5b4ee49e6aea7129bdc4a46707f99df3ab8af36eb4123e93496d94846f7807b3bd2c87d3ca039000000000000000000000000000000000df666f7728483689a746e5b39f4848a9f2d831cb17d4e5f7fb67de5d497f6399de5f37ba9e6ab76937e26450000d600000000000000000000000000000000000e4ffecf86b371ddc9ee6a72b5ada74790b590acc51c6c5c479c43c532a9507b4b4909bdc901b00932371a109fd38b7e000000000000000000000000000000000354f92ccabccc9390072671ccc8d3ea0a0c44f85816f20bd6e8b485e648e8ba0d5b845a8835395ce65b8101015ea83f,,,invalid point: subgroup check failed +00000000000000000000000000000000199fa649608972d295befae38d36940663d2b67bb286a3d549c75deef39ef8068728bbba2cafac44c102499601e4bd860000000000000000000000000000000002dac960f96822774a4956fc0ba97a235d0a2c10d81d9adf7b88215250c934b68c3de07a97adcaee2aaad0d3d84ecf6800000000000000000000000000000000000eb3c91515d4a41209a73564741a8ccf901a624df9db22e195a5d02d24b7bc0a12756b15b8d006cb991a7e088eaef1000000000000000000000000000000000704ce8afc808b0161f6f61b22d990d713ae398779e6e74e9b5771daf006ce0bba3a8088edf75156f0e48b92ee8409b00000000000000000000000000000000018fe81e05aff0620f4bdbe4a715e015650497afab62921eba0ab86b649e5a2fd3d54041868928519f537e36448688a0d00000000000000000000000000000000162bd97161201ea3c26f8dd1204a9c6b61b762bdf573cb5d20b6b255f30208ca7d96aa47b46fb8c6bf0922075f1c1ca8000000000000000000000000000000000d5bb4fa8b494c0adf4b695477d4a05f0ce48f7f971ef53952f685e9fb69dc8db1603e4a58292ddab7129bb5911d6cea0000000000000000000000000000000004a568c556641f0e0a2f44124b77ba70e4e560d7e030f1a21eff41eeec0d3c437b43488c535cdabf19a70acc777bacca00000000000000000000000000000000000eb3c91515d4a41209a73564741a8ccf901a624df9db22e195a5d02d24b7bc0a12756b15b8d006cb991a7e088eaef1000000000000000000000000000000000704ce8afc808b0161f6f61b22d990d713ae398779e6e74e9b5771daf006ce0bba3a8088edf75156f0e48b92ee8409b00000000000000000000000000000000018fe81e05aff0620f4bdbe4a715e015650497afab62921eba0ab86b649e5a2fd3d54041868928519f537e36448688a0d00000000000000000000000000000000162bd97161201ea3c26f8dd1204a9c6b61b762bdf573cb5d20b6b255f30208ca7d96aa47b46fb8c6bf0922075f1c1ca8,,,invalid point: subgroup check failed +0000000000000000000000000000000009d928f478fea86b1e3c1ebf59b230af42e4a539dd43ff17b9ca250605401273edc42806bf2cb887c6093729238fc0560000000000000000000000000000000005b6040ce6d0802719af5b9c0fe48366c5a97bcad2c6ec25f64cd73f39dc73a22d0084c69f2e86637c584d2de55d6064000000000000000000000000000000000aaeceb367eaf2ab8afb5070b5f5611f19fed17e16f6b01cd20f5f1a7e550d4689c89d6490878877c46492e8fbc9db600000000000000000000000000000000019dc59fbefa7abf12f1aa92e420f52595a06e819e974c07b62a3ae459c62545922016abd8e91301aca8f48ed793b91af0000000000000000000000000000000012f5352825a95ff0a0f0948b150e5eb1ab53d6591dc54ca8b37941fa94a0fa68af8c35aaa26c73951d2dd52e9c7a55bf000000000000000000000000000000000531e610f3706a1c67b31909a97232acef0e23c35c657dc82d9508fcf33bfff777386dc8265accb52d0da207957d25160000000000000000000000000000000009d928f478fea86b1e3c1ebf59b230af42e4a539dd43ff17b9ca250605401273edc42806bf2cb887c6093729238fc0560000000000000000000000000000000005b6040ce6d0802719af5b9c0fe48366c5a97bcad2c6ec25f64cd73f39dc73a22d0084c69f2e86637c584d2de55d6064000000000000000000000000000000000cf95f6bc3f14cc8a657da35f212d55d3633ca0d224794ac1e49cbf1e1db7757dac2bb08880f7b13f2ac0c2b95ff483c00000000000000000000000000000000115bf27014a31726503f0665301f9e183de41e002db568916aaaeaa4c8046475860db3b993b2ac2af58b12614f0b9f2d000000000000000000000000000000000983ee62076e9c5f8e6ac3ba8e5f98823a37ff20432f0b4e7d9e008660965f551bb5538e6ba28541ad514e87d128da1b0000000000000000000000000000000019c9930e3e009986608463c80aa0049fcac5e897474e519b52c80960f1dada5a48332d810f1f9a34ee479383b5b556b3,,,invalid point: subgroup check failed +000000000000000000000000000000000f240a4daf7acbe8f4c5a9cb40ccc6d99dd712b8dae4c13e9d3839b0c88c5eef6f050144a4b731267e2132a81b1a635c000000000000000000000000000000001931c93a957ba9005fa69004c7e984b9dc86f45ec03eeed77addb3e1bee3c2424105df8e8faacae2b07623cd3cc7261e000000000000000000000000000000000805892f21889cab3cfe62226eaff6a8d3586d4396692b379efc7e90b0eaad4c9afbdf0f56b30f0c07ae0bc4013343b30000000000000000000000000000000007853f0e75c8dee034c2444299da58c98f22de367a90550dbc635fb52c9a8f61ccc100f70f10208944e48d09507fdce100000000000000000000000000000000064afd6b3ef7ff7ec34f1fa330877b42958a46a7698c6d21adf73bfdfcab7793b312e21e5988652e655f2d42edb8a673000000000000000000000000000000000ea8a2217c3dbcc0f6e562de9cb2f334c896577d0b3a7108d96b1aba2d705dbf531e870d4023cec2c0533455013242330000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad6242000000000000000000000000000000000353d6521a17474856ad69582ce225f27d60f5a8319bea8cefded2c3f6b862d76fe633c77ed8ccdf99d2b10430253fc8000000000000000000000000000000000805892f21889cab3cfe62226eaff6a8d3586d4396692b379efc7e90b0eaad4c9afbdf0f56b30f0c07ae0bc4013343b30000000000000000000000000000000007853f0e75c8dee034c2444299da58c98f22de367a90550dbc635fb52c9a8f61ccc100f70f10208944e48d09507fdce100000000000000000000000000000000064afd6b3ef7ff7ec34f1fa330877b42958a46a7698c6d21adf73bfdfcab7793b312e21e5988652e655f2d42edb8a673000000000000000000000000000000000ea8a2217c3dbcc0f6e562de9cb2f334c896577d0b3a7108d96b1aba2d705dbf531e870d4023cec2c053345501324233,,,invalid point: subgroup check failed +0000000000000000000000000000000012d75b62dd32798ea12b99f51a496bbf61bca93cd233dd288e00259893dbe7c73b6342860d41fd86878a6de608dba0ed00000000000000000000000000000000018e963e1cdb31ce3f636091952c17ab4c03c0f55940196b5772002850c6c86a3da13a9a5fbfaa8256fb0a9c4139bfc60000000000000000000000000000000018330d288ddc1786744f95c4b3bacbdfd10ab560fc283c1ecde1bcbdc53cb284198f340241242763c77b4cd3b8c729200000000000000000000000000000000019a3d86d9b3ff0b03da1686c3234bb2b8109ab12ffd7599ef9e3489fcee40e50036dd952f7a4129473340fb725458cc50000000000000000000000000000000010f70de44c7e0510c8ff8327dd83ea4087f9f8b5a53fbe18615fa31a3b57862b52a7726b3e9403afe93dcafc5606dfe500000000000000000000000000000000199e6da569d0eb7938d52b396c9bc6d8b563dd6e64edd60e2e25c493475a0cbd953e7ff54309d189eba2b3827ad614180000000000000000000000000000000012d75b62dd32798ea12b99f51a496bbf61bca93cd233dd288e00259893dbe7c73b6342860d41fd86878a6de608dba0ed00000000000000000000000000000000018e963e1cdb31ce3f636091952c17ab4c03c0f55940196b5772002850c6c86a3da13a9a5fbfaa8256fb0a9c4139bfc600000000000000000000000000000000192b17cf1539a57ee64701bcd07ed0b067a6ab12ef17408ad3ba1db0d94889cc481c877588fd70270287aa8f279659af000000000000000000000000000000001779c74b7e83c8d931510b555de206dee2d9edbb49b473c586c5b7046d1389e6f612cf96ee76446a21b02f135492fa1c0000000000000000000000000000000007165050fd4407a69143a98001c290f8b1aef02d64d28ee046744009f4afa376da95b4f6a3dd36427a297ed25e6bb3320000000000000000000000000000000005a9238272385614f6df896801f7f9af9cda1ac0ce3c816174693bd52b1799386c4f6355fc36f9ecd566a3a4b20e612b,,,invalid point: subgroup check failed +000000000000000000000000000000000d612430d9b8956188fefa4cd839ec8dffee18678f83d4984b08e227880ec127e63860bf5d7a27c309fb425b90c0afa500000000000000000000000000000000100556e6391ac2a05b15e552e67d6509e21c4770cfe2f8126fd1d9663995839420a4915656d6292d0767892833369bbc000000000000000000000000000000000aeb5c087644595d0912879f61959d2731ff55260c682ed2bc5fc55c13964ef7c1f70aeb55876d2264d558c31371ca69000000000000000000000000000000000e173848f4570525b03a2b2c86f4dcdb8b28dd6d18c1354cad31028eb1b8b44432c2346edaace093e3954c7fa6d338a4000000000000000000000000000000001949b0902506d111ef6318edcd7a58ca4d69f5804a028aee73c3786cb2db168c6a73b77194f7a021ae6ae43ac78ade340000000000000000000000000000000017c5e28ba6103d97e2f3d3611c0c78f06406e0da8a49ae29c7d460b52f75136920784cd500aa3593858b877697eb84240000000000000000000000000000000007dc719ae9e3f1e11d3ed4747a546a7b973ccb1967adb1b3066645a8bde9632bcfa3530e768f088ddbc022b169e67cbf000000000000000000000000000000000bbf9cf884b19c84045da1cead7dcd9fdbf39d764ff1ad60d83ed1e4fd0ce0554f0fb618203952cf02a7c4ba466c66b8000000000000000000000000000000000aeb5c087644595d0912879f61959d2731ff55260c682ed2bc5fc55c13964ef7c1f70aeb55876d2264d558c31371ca69000000000000000000000000000000000e173848f4570525b03a2b2c86f4dcdb8b28dd6d18c1354cad31028eb1b8b44432c2346edaace093e3954c7fa6d338a4000000000000000000000000000000001949b0902506d111ef6318edcd7a58ca4d69f5804a028aee73c3786cb2db168c6a73b77194f7a021ae6ae43ac78ade340000000000000000000000000000000017c5e28ba6103d97e2f3d3611c0c78f06406e0da8a49ae29c7d460b52f75136920784cd500aa3593858b877697eb8424,,,invalid point: subgroup check failed +0000000000000000000000000000000013b23b5a61ad3ed0ed74a57023285338c77c01dd93e17fd93d4225d27a7168dbdae3fff0c8ebcfc30887df348deea7ae000000000000000000000000000000001245282782135d41eee4dbd9b69a586e1ae2e17426643494427eabec93ba3c056089ce9c8a667727c3464178f7c87655000000000000000000000000000000000d35887ecdfce840b79f60f77af0e7cc01289ff20e1558a14b57a73f3fdd8e66ca496a73ce888d1c8cd0888d8219412b000000000000000000000000000000000cca9ce1b3c5bb6d02d5c36549438a4854dacacab7b173b434d09e496256edc2372a08edb2de26e5369af03dd0e2d73e00000000000000000000000000000000152d022429b54ac4bb70dacc888a2a3409cd7d2db0614ba0afe236c7bff311967718a70eb7943f93a6dbc88d3c008e8b00000000000000000000000000000000191e99ff1e02809121098597de4d176dfc85586d3e13609b3399c08da7c9cd6fa006e8b95551878a74e140cf166625c30000000000000000000000000000000013b23b5a61ad3ed0ed74a57023285338c77c01dd93e17fd93d4225d27a7168dbdae3fff0c8ebcfc30887df348deea7ae000000000000000000000000000000001245282782135d41eee4dbd9b69a586e1ae2e17426643494427eabec93ba3c056089ce9c8a667727c3464178f7c8765500000000000000000000000000000000179657b434fbb3ecb4857b4559c8cdaa1448b2a4bf4314349341a45bd3a2cdeb2aa6be75ee1aaaef0f90d086df55ccc40000000000000000000000000000000011ec9abd1a497dc7cc4b4b9e2cef8a7abc767969703d79ff51d7a4f1ab93ae9fc7e8bd4852da22ec067d326fffa54757000000000000000000000000000000000a45435d1f78ad93aeef8a88dad39aebc0cf38cc16fbb02c571a269bbec773e71b5ee9d09a505afc9a7fe11b06f040cd0000000000000000000000000000000000b6ee9d2b0fbd996455169ef07afa3be3c6535d65545c8503c3d77dd0576eecd2f81b50dabbb62328edfe236981d5c1,,,invalid point: subgroup check failed +000000000000000000000000000000000b14b12ecaa94f9656be54772be9b22a2495d4ff873b0bb971c27ab1d8b940c84cabcf921f6f75e93942c38cddeb8751000000000000000000000000000000000c9ad793ae80ad0e1c39670876d679bc7f930c1df1f9202593d1901079687b77f18e4b1536bd7c4152ec197321dc945a0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf2300000000000000000000000000000000005c35f3372f1ec9845bd04ea722fbed2be1388abf59e622dd3dafb4b3af49bc5fba9e20235e7e58973fedf4b8b720691000000000000000000000000000000001111d18d621070509805d306a31c109701288fd55d4c0644349deb080c6591b6e852b4f7e009b80019513de7f2fce17d00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ec0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf2300000000000000000000000000000000005c35f3372f1ec9845bd04ea722fbed2be1388abf59e622dd3dafb4b3af49bc5fba9e20235e7e58973fedf4b8b720691000000000000000000000000000000001111d18d621070509805d306a31c109701288fd55d4c0644349deb080c6591b6e852b4f7e009b80019513de7f2fce17d,,,invalid point: subgroup check failed +000000000000000000000000000000000e96f4879493d577aa02e9793aba3c680de9296dadef569d70484297398cca1e3d8acadc6c188345d98d5208f5b2d7800000000000000000000000000000000003bee60fe5a42f31ae4218a6c6fff2ffb51329c86f8d3c381fb090bba0274eef8cf0d351fb4bbe2d51e25d9f7b2094ee000000000000000000000000000000001664847d2bfb8939cccda0bc7b7ef02fcc1188fcade83341db7c4dfdd4a1a3bdd627379544dd405bcdb58caf4be39cb40000000000000000000000000000000001a293caf801299370cd133fd54d2086c6d1aa96ec32a1c9c57679f45785c9e6fa91018720c2d0515b38790914ab61d20000000000000000000000000000000017efcbccb71f6bbfa515e89d59b5fcdda4bf37c90fc6dafe9c6b0fe3d42a372ad9c66e5d60e04eff30ea7c1952f3fed30000000000000000000000000000000018893c6a3c621ba6c1119529a07d6c2f9c1d4bee5e0090c4c0204d8d4f3e200ab3300dcdf1cfe61370d68849806ddd8e000000000000000000000000000000000e96f4879493d577aa02e9793aba3c680de9296dadef569d70484297398cca1e3d8acadc6c188345d98d5208f5b2d7800000000000000000000000000000000003bee60fe5a42f31ae4218a6c6fff2ffb51329c86f8d3c381fb090bba0274eef8cf0d351fb4bbe2d51e25d9f7b2094ee00000000000000000000000000000000060a0cdf9ea84b21c7399b21e337dab42f357c30f50b076b4f95bf8623b88e557cc8b346e41a996adbfbaf0a3697ae8e00000000000000000000000000000000003746a481d5760263d43ee3c2ed6493caddcc59754fb8534ff57ff9bf167f356d901277b9935210ddac4dfff8f21161000000000000000000000000000000000d80e40583153e6e0adf9f95ebc4fd501f5aa426ff77038b69f0fe8259268f9224628ead1a63b7ecb4218ba825ee45b60000000000000000000000000000000005d0421b213f88469887d67c68316a98c18d2035a601dec994f6a4b91fb6c3b7e5ac7741cd07c956908869e1023dd32d,,,invalid point: subgroup check failed +000000000000000000000000000000001576c6f620bbe36a7df78985dbffd355c032bb667f4757501bb27a426c99e1949a1bc65afebaeac0668ff3d375b7837d000000000000000000000000000000001651783a82f2a8e1e389923bfb53836492c00e447b288873071bd8682a0ef30864f4d0fc9a626bf701bdfe1a48f4479f000000000000000000000000000000001335276775545fbb4c701beb57cb34312108c9f1d46b4aa4b09a16faf0e648b4e80848bf5e75ed8730715f0107afc9820000000000000000000000000000000006ffff8736bab41b4ee5681b741a81fc870e648001027161144254d04c678e4f954e9f191bd8b26201aec681cbf0654b00000000000000000000000000000000026ede90d14fa0885baad21f9631bae058573251cbef5757bb8cfad061f3bdc78834fa5862dea19a2236c014b0f1652e0000000000000000000000000000000009844d0cf7f6f3401145d8d720defa577ca46b49e04e39c4c139ec6811a574e7dd5ce3acd00d1ce9496f10dd15c6d946000000000000000000000000000000000a6ff5f01a97c0f3c89ac0a460861dc9040f00693bfae22d81ea9a46b6c570436f0688ed0deef5cdcc5e2142f195b5c000000000000000000000000000000000193a17880edffe5b2ebedf0dc25e479cac3b136db9b6b24009ea0a9ca526d6dd9714d10d64c999d4334baa081b9f2fbe000000000000000000000000000000001335276775545fbb4c701beb57cb34312108c9f1d46b4aa4b09a16faf0e648b4e80848bf5e75ed8730715f0107afc9820000000000000000000000000000000006ffff8736bab41b4ee5681b741a81fc870e648001027161144254d04c678e4f954e9f191bd8b26201aec681cbf0654b00000000000000000000000000000000026ede90d14fa0885baad21f9631bae058573251cbef5757bb8cfad061f3bdc78834fa5862dea19a2236c014b0f1652e0000000000000000000000000000000009844d0cf7f6f3401145d8d720defa577ca46b49e04e39c4c139ec6811a574e7dd5ce3acd00d1ce9496f10dd15c6d946,,,invalid point: subgroup check failed +0000000000000000000000000000000016e8fb30c523be94fed64c81cefcbed03d7a2c85ce975601a441a920d70980be8b7c682ebf2aa3cc36ad1bf2d98a711e00000000000000000000000000000000056d7a36324b92ce3bf1644a2a0f6e717b0e9d94e9e5231f0c999c0f6eac936f5c1b1ff4aef04b821454a951882fd45000000000000000000000000000000000069ce7ace071ac2f7034200da8eb9252c55cbb50a591c563684013fa8071d7289a44ce5b100db6af1e8732b8e5b3b7fb0000000000000000000000000000000019535c703e9dec227c92db508c5c0553b986b93e3a90b35f472ad8e5b217bc7658ceb7ea452b76984d3d17859fbd7fc300000000000000000000000000000000008b3677ecfb10faae0478be5be5c72fcd7137042b5972fffbd15ac95d6104687a81fa3db232edbbb8703280412eddd30000000000000000000000000000000012a628e23e5c7f0225eaa75b6c9199d1d3a0316d474d1fa729640c295ec156ce3bd49f193d993165b9a69ad6cb2ed75c0000000000000000000000000000000016e8fb30c523be94fed64c81cefcbed03d7a2c85ce975601a441a920d70980be8b7c682ebf2aa3cc36ad1bf2d98a711e00000000000000000000000000000000056d7a36324b92ce3bf1644a2a0f6e717b0e9d94e9e5231f0c999c0f6eac936f5c1b1ff4aef04b821454a951882fd4500000000000000000000000000000000005b6ff8cf47cdedab38d984013db8401fa4783e7c8e78160c7a420de86636683b8dc292e1f0494dba5865f217e33e7c40000000000000000000000000000000008287d216fc45ee3e210630f0dc4893dfbba90b3bfd34986b64b04a2ec6fea0496cb0f2910cbb99a70896e5afcf809e8000000000000000000000000000000000a1a11f1b52c35b9e061c57b7b7f5f76f4db764607c93c2dddf245170f4c6fcf5915d72130e6f8f0c0c5b29a4970aeb30000000000000000000000000000000009255413a3db5889608069b7a72879b659ee5e1e71165752a9173cf1ef749050fefb86584ce90cbd3c3aa3db023b3322,,,invalid point: subgroup check failed +00000000000000000000000000000000117e2e3a8753660374fb2b812038bfd8f15d36d26c7b07fb546610405cd4442c79166060c8b8e767ea4084a583bbbcca0000000000000000000000000000000011247def4be1da163e724a175f74afde615df1417cdc2491158fd560442e56ebbcb61f51f9712ac93131024e27a1caa90000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc00000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc0,,,invalid point: subgroup check failed +0000000000000000000000000000000018de31d75ba31dc1441bee38f5fb9e9d347e0243e6c3220de664e04a0af17c776fbb483f63ebeac48c7811adc0fff6bc000000000000000000000000000000001053f5c7a65dfb41b7631a22e60337769beabd663fa76eaa1e01f0c0e3c901f40a22b34adfb13b38e298eb83f08d7b1400000000000000000000000000000000079daf601b5fdd1c00552382a819e1924a517226864aa3565d384dc399e6e124c3ad61b236e19321078eab585579572d0000000000000000000000000000000008ff51b4d60d9fb1718c5622b1a3353675ca0144e12e68875481394755107b7f3364d1320283ce34737d5ea0b7d9b40f0000000000000000000000000000000000f430d35de439bf8ef82df4dc61459cf9783d66531e8075f323dbf102035032972a4830ba6bf391b568e6b691723ea600000000000000000000000000000000042df3a5c7b534c48c9af9f48e5cb296e2e514e3322630989c3da9696485797fe2ae036e13484e26cb59b48a479d3f520000000000000000000000000000000018de31d75ba31dc1441bee38f5fb9e9d347e0243e6c3220de664e04a0af17c776fbb483f63ebeac48c7811adc0fff6bc000000000000000000000000000000001053f5c7a65dfb41b7631a22e60337769beabd663fa76eaa1e01f0c0e3c901f40a22b34adfb13b38e298eb83f08d7b1400000000000000000000000000000000180168b2c9a59a9bedcf002336a6ce5d5ff36937948f5d3aa9fc1ef1912d9fed526edcaa00b5ede76906c40994937da00000000000000000000000000000000018d47a4f6e4320a230b8a4e5f5eea7807fb5b0dccc72af182e8bb811f2470dfd485f291b8197899af84332e3674d56950000000000000000000000000000000000dbfed96298ad2f57fe7b1eff4791abdae5fd0ca3573e66270f8c9f6adcbf6e54a746789a496714f5cc06eb2b8190070000000000000000000000000000000002939de2a2f7ec3d44dc5ae679967a5d7efcf80df8a21d9708c036c602a5050487f80a39cc36c7e587f0f2610fed86b2,,,invalid point: subgroup check failed +0000000000000000000000000000000001ac9e5f4fdccfe8d2979ad5329b07b23b497c2b7f26283f8c79548387610580568bdd9dc040261d6c265aac581b4665000000000000000000000000000000000eaf31d6368bb9d762ea067eed73b008dcb4d84643471dc1d64104eaefaf500858d9a9323b7fa2ba3c22bf15e7db6b9d0000000000000000000000000000000007457f2601621a99050d8993244f026b9a62ff7055b325e6f1edd1cf54065785f003cf7c8a4bb1f7bdf14e220e490ada000000000000000000000000000000000928eb76b428dde37546a27f3d77605c293738f448fbdd6d618747b0de04004aa4419cc5601600419c6e1d470c15982e0000000000000000000000000000000008074e9f5473492dd2e536f7b305be4e5c564cfc9218934d03dde6dc5118064ebaa5c26fdd1123a9c31336c37c1234900000000000000000000000000000000002bba1f9b7da6abd2b322c8f11c749b2a284552eab25a77d21b38b028da477a3ffec1901a015e81fe2893576a41e4c0b00000000000000000000000000000000172447291285a20c3ffcdef805260f00fbd4c5d3a42ee5b17d5ec05a723aadf4c2acc49f839e222155f29d0c6384bb0a000000000000000000000000000000001490dae85f858ed057fdb61c22ebf3d45c3af02afa53f32b2542808bb4db5bb1b9603e377f0b6d214e03050379f8006c0000000000000000000000000000000007457f2601621a99050d8993244f026b9a62ff7055b325e6f1edd1cf54065785f003cf7c8a4bb1f7bdf14e220e490ada000000000000000000000000000000000928eb76b428dde37546a27f3d77605c293738f448fbdd6d618747b0de04004aa4419cc5601600419c6e1d470c15982e0000000000000000000000000000000008074e9f5473492dd2e536f7b305be4e5c564cfc9218934d03dde6dc5118064ebaa5c26fdd1123a9c31336c37c1234900000000000000000000000000000000002bba1f9b7da6abd2b322c8f11c749b2a284552eab25a77d21b38b028da477a3ffec1901a015e81fe2893576a41e4c0b,,,invalid point: subgroup check failed +000000000000000000000000000000000515390641039576df1debf767b899deed928bba057d8564e7d117b324dd7f45e38939ffa1056fea35e1df8ba38815ad000000000000000000000000000000000013af9258135a09e5710a22dc437a4efdfee1f6c25cc08fb026c7ea2accddb9446333a91a532742c7713f6639e0cc460000000000000000000000000000000017203225f7e7b55e52793698b8a1166de5cb3d130c7df6f74ae428c4f39e7d042d41eb3aed266ca8cd3be1618cfcbdf60000000000000000000000000000000016b8053ab19a08a8c23a166bfe7a89be6eed05f305525146d9ff6e03e47ba125403cb2f000672d08a2b59051242f61ea000000000000000000000000000000001136f9a9369aa3561d65e54869f36ae9d6945654ac16335aaf717e0f848463ad401a99e83b3581c79d4221f65d649e9c000000000000000000000000000000000302ecf71033b917efac49d7a22db2e5c59b656639c4d0aefdb431749718d560b7e5580b4532048a693ccc09fb8a44a6000000000000000000000000000000000515390641039576df1debf767b899deed928bba057d8564e7d117b324dd7f45e38939ffa1056fea35e1df8ba38815ad000000000000000000000000000000000013af9258135a09e5710a22dc437a4efdfee1f6c25cc08fb026c7ea2accddb9446333a91a532742c7713f6639e0cc46000000000000000000000000000000001330e5c71d9b0209c8f8c04b4a28ad70d826b2c781493ac50fe58d0d6b740de3ed08f9829aa9b207ecf623a36dc8de610000000000000000000000000000000019a81ae1c6c08004a75b8fb5426ec899a09becce68dfb63fa7335420f05075bcf42c85dfa688ae75398c3ae5e4d28d12000000000000000000000000000000001597318859306f3814d20faee54926d5e8ec01005fae8aa408989afd5a1d7add8956d8fd3e75f9e446fb8b2a31050b6b00000000000000000000000000000000151e7c5bbcaf1e9581ac48e2b9bc63bf4576ee5d938981550f00fe9e3a42d389d6b56acd0ac9254ceaa71519b5049a4c,,,invalid point: subgroup check failed +000000000000000000000000000000000a2e9dcb8461de77fffb1281df7360c947108baef671602902c3bfa4afe60e0b4810cd46bd91276cd58e33fbf4973301000000000000000000000000000000000c039ea7e94fca4a32a275691131df8e3d470d6026aa306c8ae855e9e411878247368615094621e6af5486809b467a7d00000000000000000000000000000000138ddc71e9709e595ce2631c9155069818d07f225f80511eac21c5655721df83f83e2abbf56e2d5ebc9698f9128f0579000000000000000000000000000000000080f7ae78277c94833c0f12aa2cd98285621e0d92a89b45cac848f5a75ea652e98cd085150b2d7babaa9a365123d90e000000000000000000000000000000001147cec62010d42cd84651ec61ad8263699786f0f34aa7f9de75ac841861fae2e8ce3795fde8e7038c8506f573cf0e0f00000000000000000000000000000000138ed243889db13546c69583c1aa93ee98366220ad80c590ad112adcb4b7c87fb48a34483e76d4a5c55a30f2adf1a6a6000000000000000000000000000000000077b7a4c4644b21ac3ef56db1163f7b2e07a817cfd9d4c6830a97d0ae0b620e0b235376d590162c378899ba12eadb5900000000000000000000000000000000022beafe4b4ab44434c9dabae45a395b5b8da15da2fc2e723c1b30b5efc95e880846844f27eb47dfae8657fa27ab64ef00000000000000000000000000000000138ddc71e9709e595ce2631c9155069818d07f225f80511eac21c5655721df83f83e2abbf56e2d5ebc9698f9128f0579000000000000000000000000000000000080f7ae78277c94833c0f12aa2cd98285621e0d92a89b45cac848f5a75ea652e98cd085150b2d7babaa9a365123d90e000000000000000000000000000000001147cec62010d42cd84651ec61ad8263699786f0f34aa7f9de75ac841861fae2e8ce3795fde8e7038c8506f573cf0e0f00000000000000000000000000000000138ed243889db13546c69583c1aa93ee98366220ad80c590ad112adcb4b7c87fb48a34483e76d4a5c55a30f2adf1a6a6,,,invalid point: subgroup check failed +0000000000000000000000000000000004db2336ff5016a6f0f34b2881eb80cfbb35f557ef492f2dc54a654c8c574651fc89f3f211a46510ebffcf5c890e5ee700000000000000000000000000000000103d17d59c26d8c85426b73593843e3863cd672043b7a392cdb05bf5dd69cd11583423a4b146dcd3fbcde4ef4349bd78000000000000000000000000000000000e07265d2762e8e398c83efe1c43452d91b90b7a4271c09ff693c83745a6c01b73561ffe3da9300c8e7e1602dbaab0bc000000000000000000000000000000000375579c16a167fd9f9f61d5177705f157aa0df3451971029a9444432db119fb33b8c07de33fc822eab46ed4ae47cf8200000000000000000000000000000000098cee454129e946d758f95e649a601b6e682467093cf32c76e5cd1328111f9d48cbb2850d4d2faafe429161b090f8ab0000000000000000000000000000000012a3fdf296743b2eb3f389eb6dbea52da21e7dbaad180177184afb7976e06fd4ad488399d39b760ca146278cb3759d7e0000000000000000000000000000000004db2336ff5016a6f0f34b2881eb80cfbb35f557ef492f2dc54a654c8c574651fc89f3f211a46510ebffcf5c890e5ee700000000000000000000000000000000103d17d59c26d8c85426b73593843e3863cd672043b7a392cdb05bf5dd69cd11583423a4b146dcd3fbcde4ef4349bd78000000000000000000000000000000000e87688c9f6f48450528cec78f874d87dcfab499bce391e964a0abd601d675eec8a2b4cc6c44811cbe918913a230975c000000000000000000000000000000000451c1a17567b55123da563e173b2d92ab635c4957d116fdff8eb5a3e00bfbe079abead42b9c990b56db919719d066160000000000000000000000000000000003206eb27b9e53bc451bdbe5059133a51d656d9d80654647163a070741761af3e712e2042acc5ca29fe72cafb3a98ec7000000000000000000000000000000001869b7fd294c230aa5901db9cca31e6d3801069e0795e4c7c63dea8adc1e4a5709ec62670e2abeeb040031b7fcc8521a,,,invalid point: subgroup check failed +0000000000000000000000000000000007248a496f2cc918cc3cf0240c5c3d0dce36228a199766106999205b5767db09c2ff5cc720691ebc6bb1c316159d1c000000000000000000000000000000000019ab8e34bc4c0ba409c7edf7bbd4d0cf03f7185a2baa3f22ebd6234503d56e9b0d84a5d8d46ef9e3ac6430029be82b2b00000000000000000000000000000000143220e1cd08ffaa6db4795ed4aa35f3b12cce724fcad005367328972f2364f34096e32f1f1cb7a4287ab636d0030322000000000000000000000000000000000f2de47a37a55edbb75ff0bcc446611d690d7f9efdd09ca1ebb6f1d64a330bed420bcc85aed8b95316fcac3aa7d1f2230000000000000000000000000000000016afb044b8b8c64547e000f80b25576aa329a4319dcd4f1bbe15d12e6f3bbdddbb52140e6297c637311ef0c7a31cafab0000000000000000000000000000000019e6803c07fbaa075093f6a69f9dde05ba3d3f58e67389d7f096e56df49f8270008ed422b64fcdadf7cbbc8334037682000000000000000000000000000000000b231eac9869c94f055f22c16ef5efb687a7399da838c9456bb2469b3e394dd21e462bcc9298d5b56173c7b76f79ee9d0000000000000000000000000000000013a1bb963b7fbe1c3f6bb78cce8c3122773428ccbd87cbb2e76f306a8c5369577267e9b4ccb3417a235fc9045e8828f100000000000000000000000000000000143220e1cd08ffaa6db4795ed4aa35f3b12cce724fcad005367328972f2364f34096e32f1f1cb7a4287ab636d0030322000000000000000000000000000000000f2de47a37a55edbb75ff0bcc446611d690d7f9efdd09ca1ebb6f1d64a330bed420bcc85aed8b95316fcac3aa7d1f2230000000000000000000000000000000016afb044b8b8c64547e000f80b25576aa329a4319dcd4f1bbe15d12e6f3bbdddbb52140e6297c637311ef0c7a31cafab0000000000000000000000000000000019e6803c07fbaa075093f6a69f9dde05ba3d3f58e67389d7f096e56df49f8270008ed422b64fcdadf7cbbc8334037682,,,invalid point: subgroup check failed +000000000000000000000000000000000ee60801e3aaacf27167207a03b42594cdca4cdfe3a1f8e5f87b0e423ad36c56725dc79a26c157f41699e89884d27c1b0000000000000000000000000000000017c92b0beee470e126830843170bf0a89d13856ff6b0209e23bbf2a68e0668bd81db29de726ec7593124bea89771da97000000000000000000000000000000000a1beec4d223edbf1d09c448ea27038327bd8621842d3c7851779286060abd8130e4a641100627057498a421b59e90a600000000000000000000000000000000076a9559f5e33a2d5bdff6a89037c12e58ef920a3d8b9b4ca2a78299dca5dbf07c80d51014ee9288af8725e6c4312225000000000000000000000000000000000755036dd19c7d703343bc29238ae9332823514aeb4779b3378e8f7a3463a4089eef3fe79608db22b703b5d88c65bd80000000000000000000000000000000000ee3d82959dc04b5d59428142411e48e7a2fcfcff91e5554b725868c62d139bfd763720c9e2727c1b1fdc0bfd60f7297000000000000000000000000000000000ee60801e3aaacf27167207a03b42594cdca4cdfe3a1f8e5f87b0e423ad36c56725dc79a26c157f41699e89884d27c1b0000000000000000000000000000000017c92b0beee470e126830843170bf0a89d13856ff6b0209e23bbf2a68e0668bd81db29de726ec7593124bea89771da97000000000000000000000000000000000360e8bf07554b56d6f27de6a4b1f9184adb7980311e8f4128f619d9c8e64e98bcfca8eb3cab93e183882827fce2d06800000000000000000000000000000000010b0b114b55b17531576f782b6bee122cb5fb1e40d5af37ebaa1d4dad55a0f5d130c9a58cf27b1594eebd0b1492c0400000000000000000000000000000000018b70f0c047aaf7e17cb96015642e0d32f3135999de6dd8bded2c600866485ea75ab3a3e37581ae7dcfa1f54381f542300000000000000000000000000000000028cbef4b476a9cfb8daa35b1321a486b8c94f8b8df2352a0c8d509325706eee328b8dc3657122afb81be1fa2e60aafe,,,invalid point: subgroup check failed +000000000000000000000000000000001573cb3b6f4e841fe97549f42e06db33abeb2bc481cc0bc8c5e7bd81948432dcbd34b64c5766e1e491bc131e77a011aa000000000000000000000000000000000e205be14db0dfb21e58b302d45916de4f4bb48ac95228326cf49c50e6c8588a655a343b90645f73dd03c9aa51c5659a000000000000000000000000000000000a109e3cf72bba0e695bd756478c72884210868c95f0dd6ced22b7254eed0432c2345bbf8addbbd11349fec6e354d2350000000000000000000000000000000018aaf98535812ee760e17ad933acbe6b57bc1310b6fef550210053f06a611ce9df5dfcfdb028497468802c08cc2117320000000000000000000000000000000007590fa8cb07f9dc2ce7b2090685730104ca65a101c8f93965264913bf1deae794b71ff226a805a273248dc641225585000000000000000000000000000000000fbca4e20854a6069c298224eca9a064b7ddc4d834f8671ac45cb5d1d39265fa435ff1750cde5135e170824e516d6d2c000000000000000000000000000000001510f39616d7f576980055d0547c603d882dbe85dd0b634577fae134f210736007345d035d815306db660de4a07fc24300000000000000000000000000000000064d356ad7bd2edcd3622b1fc225fe319f86b5f7da875cd57fe5adc5bdb6443c5b09d676950e2d069bd4303b8f920692000000000000000000000000000000000a109e3cf72bba0e695bd756478c72884210868c95f0dd6ced22b7254eed0432c2345bbf8addbbd11349fec6e354d2350000000000000000000000000000000018aaf98535812ee760e17ad933acbe6b57bc1310b6fef550210053f06a611ce9df5dfcfdb028497468802c08cc2117320000000000000000000000000000000007590fa8cb07f9dc2ce7b2090685730104ca65a101c8f93965264913bf1deae794b71ff226a805a273248dc641225585000000000000000000000000000000000fbca4e20854a6069c298224eca9a064b7ddc4d834f8671ac45cb5d1d39265fa435ff1750cde5135e170824e516d6d2c,,,invalid point: subgroup check failed +0000000000000000000000000000000002ff87f9bcde30db0919444e2582a5e987fdda67264ed7abf60a93b37288143dd44db75403988cb5b3bcd8d34ebdb4e2000000000000000000000000000000000c672f8bf6e9e747cccb9438be047e56d1cfbd0808864e601de59b0b28b5438cdd10f1194ff5abe8694a598ff3dc1481000000000000000000000000000000000c01823cb218303c7e611f6caf6994615cc3805bb4310bb0bb82b56740c4314ed0a2f9409c8fa6b9f10dead667880fe000000000000000000000000000000000013eb3436ceac3f12dcdd9e71707b85b7cd872ce144c502078d4fd3ec8b4ee579410cbaf2e3db1df9ba6b55f14fbbb0c000000000000000000000000000000000ecb26e5814d5bc66fbbcdff5dd5934a597c4344487e7e63138c31d47a8201433be5ce14205660cae129891f5b7aa8e000000000000000000000000000000000040241f695cc864e99e1dcb04440135c4f3d90a86310441647ab265e9fedf51592b0c11d5e91d2d28ac4ed19245e9cc90000000000000000000000000000000002ff87f9bcde30db0919444e2582a5e987fdda67264ed7abf60a93b37288143dd44db75403988cb5b3bcd8d34ebdb4e2000000000000000000000000000000000c672f8bf6e9e747cccb9438be047e56d1cfbd0808864e601de59b0b28b5438cdd10f1194ff5abe8694a598ff3dc1481000000000000000000000000000000000a2e0129c31f3427340e234df5facbb5d1994a4c8a058fe84f7d5c62c7d00013cc0133d8b0faf497aa8f7c04abc59c3400000000000000000000000000000000160da6086a089fc1a31b89a0f0c95ff2bd85debf5f0d1372f3ca6fc26348d8a8d0b03525b27f7d7db6603f0e55d3a08f000000000000000000000000000000001893edc299faf35510b92c97ef665f458b19e4097299b4f8b0f8a2ea1f31343c9d2f4c35dbdfb36bf7f3e30686e8c3240000000000000000000000000000000009b8731ed8df8ae90ec344c9dea1eeee98f4000b5fe30460e58f30e4ea17821389d613458c7721fd1d759ec4f2bcb325,,,invalid point: subgroup check failed +000000000000000000000000000000000e42f5097dabc6b0fef4dccbfc659da40b66671d7efbc19dbc14a42a368402156469c818f233ad911574899db01ed86d00000000000000000000000000000000128bbee344b7a19212b549cefaaec31c0644c986ab863005e4d3ecf532be633597ac3906e53ca79b22de916ab92b4ad70000000000000000000000000000000009003b42c08b5c7d3ee9f6abb96e08e6f537da25cd0cf7eb85a49067746c03566e133b54153380286ef5725db5b41058000000000000000000000000000000000f09b7b754c255e0e3b8435ade64d6960285759495659dfdb9b117806397baf8d3c87e30bee02c9e1b22fa3efcc58f300000000000000000000000000000000003582c08a8de4bbd20ebfa833517a75682618fba2702b6c71a4785f70dbdede4e86ad8e04aae1f50a6bb75842ab74aea000000000000000000000000000000000ec013f22e64a4d4fb6f964e8319feb1ddbcfb71329186545d9b9d7f97d1f6a56c8aad03d20e9c30966ca932e1f2bc670000000000000000000000000000000002f78becf8a5717fbfcbe110bd6d4ba683658c1a45afeaeecf7742502d3df54b262de4fe30bd41ed0019cc79b595b5ad000000000000000000000000000000000dbae184899af1ad6221b75fc62cadcd61c623e011672b691a5aa498684f49796220fc5a73ea7170ed2fa44b9f3c39090000000000000000000000000000000009003b42c08b5c7d3ee9f6abb96e08e6f537da25cd0cf7eb85a49067746c03566e133b54153380286ef5725db5b41058000000000000000000000000000000000f09b7b754c255e0e3b8435ade64d6960285759495659dfdb9b117806397baf8d3c87e30bee02c9e1b22fa3efcc58f300000000000000000000000000000000003582c08a8de4bbd20ebfa833517a75682618fba2702b6c71a4785f70dbdede4e86ad8e04aae1f50a6bb75842ab74aea000000000000000000000000000000000ec013f22e64a4d4fb6f964e8319feb1ddbcfb71329186545d9b9d7f97d1f6a56c8aad03d20e9c30966ca932e1f2bc67,,,invalid point: subgroup check failed +0000000000000000000000000000000002dd02e23d8a9b7aaba94e8ce97a2d988291b6efb1d4d8d1c53b65581b737f992a428cb72ad38d4c6d470d6b50c869f0000000000000000000000000000000000661ddb1f0edbaa208c5e1db143ff41be5a35442da7b3e12d2928310ba5b64847f4909be93f860858f752cffce5e73870000000000000000000000000000000018fc47e1aaeefc1ea653eb42543940ba58044b247eef2627331142988f3a1a4fe72583738c7caa00c562d010089754eb000000000000000000000000000000000b5f8e9d774d99c3b6d224790d5c48367d68167aafc309ad8b351d1de8b601211737235bb0ff1b01e508ecf36a40db5d0000000000000000000000000000000013509c76fe4383e47d8ea35ba6ebcba13579bbf43cee0cbe27291d2601c3d74428bc9d5d1200718c86977abd16cb77a7000000000000000000000000000000000efbeccb9e9f348ba98ce0fd191c2e81de4925d7185f01b39a51aa44b30496f1fa3be4582c539889bef7b5783f730aa70000000000000000000000000000000002dd02e23d8a9b7aaba94e8ce97a2d988291b6efb1d4d8d1c53b65581b737f992a428cb72ad38d4c6d470d6b50c869f0000000000000000000000000000000000661ddb1f0edbaa208c5e1db143ff41be5a35442da7b3e12d2928310ba5b64847f4909be93f860858f752cffce5e7387000000000000000000000000000000000629fc2184297509e1dc0707b707067fa8b9a079d95f271ca230c32705c7e783f91ce1ef0323aedc6aa2febf750392b8000000000000000000000000000000000f29cb4bdb7d53671f9f1b1ee514cc916a657b7c17f298f409ad92ad7b2ca36b0298bd0581db7837994b781b829980c30000000000000000000000000000000014b839ecd27f2d6fdb92a36792c62ef503fb0189d3174447c4b1132e5f66d7970a005e315e6906ecd2ce6c72742d210500000000000000000000000000000000091bbfcfbd929cc8ecd000cf8ccf083c2f8cbd84f19b48b1ad20781e675ca7a77887b962435520e3f707fc0631fdc8f1,,,invalid point: subgroup check failed +000000000000000000000000000000000f67855b0cf915ab52190c7e69129d40dbeffae28fe110745edefacdca42bf979c5c5da1c6ebd78b43667cbfe360874c0000000000000000000000000000000014c5523f37b011c632e9e8eb08611e5a244ace2746264706b92a5a933b34bb9932c95c493ea7e8fd049d80acddd05e860000000000000000000000000000000012b63d867bbfe5505a83f765b85b0e8f182c796857f11125e691969c8e81eae927a5e1b137c81473e8b99ea0727c873c0000000000000000000000000000000010a8241f3b30c54a5d2fd7adca9be47b001f2268973e955ffa61d57793256a2d82fefa5e449548c64fa5aefda6d933220000000000000000000000000000000008e7d2f2e34a60504a5437275f358d0e742c445080c5e46053ea459893d6865e8d20906b2b58f1d1446a100cb1c9a513000000000000000000000000000000001369bfc5f24e2a7a7ace891444764ca32a32513c8ddf654f418c6eb7690e2cba77a4c4d4b98eb546d4e4046dd3c3267200000000000000000000000000000000153310de30b7a485753dd8443f8638c12b21083f6133a1c093648bcb566b33f73631c6fc558f32abeb0d6df8430e61a900000000000000000000000000000000005be397e9f77556ad952dba0540f46cbc7db910d5203cb976e168a7be3a3b8557c5f08d51cca9379552694a291d67fb0000000000000000000000000000000012b63d867bbfe5505a83f765b85b0e8f182c796857f11125e691969c8e81eae927a5e1b137c81473e8b99ea0727c873c0000000000000000000000000000000010a8241f3b30c54a5d2fd7adca9be47b001f2268973e955ffa61d57793256a2d82fefa5e449548c64fa5aefda6d933220000000000000000000000000000000008e7d2f2e34a60504a5437275f358d0e742c445080c5e46053ea459893d6865e8d20906b2b58f1d1446a100cb1c9a513000000000000000000000000000000001369bfc5f24e2a7a7ace891444764ca32a32513c8ddf654f418c6eb7690e2cba77a4c4d4b98eb546d4e4046dd3c32672,,,invalid point: subgroup check failed +000000000000000000000000000000000f3300926ec62bbc0a73e4e0404de3a55dc0ad4fdc393b291ccf9f80818b6a69c0a3f9c6edf024db295a353b6aa71a5400000000000000000000000000000000195b21606667c62a181b275184014fcef97218b451c3642e2e4dcf5502d03cbcc88839e827732feb32e77e52f702529c00000000000000000000000000000000198fe6684d3de4ec9c9ed5c59c9e662c4eb1b026995859db3a753ffa96c6cef7a063a607fc54599f706820fb864d7b0500000000000000000000000000000000116af6c03511ba2bcc32ea54c2fe2bedfb0848d2ee3017547cb25acf892400203e3ce4ea1986ecde0cdd383d42b61e180000000000000000000000000000000004a22ed8f30766b6b1dfc7286616066ccc262875e3de42e2c9528f6d23a7fda67ddb371f9ec419ae4dec48a354ffe21300000000000000000000000000000000086651d7ab0a4b1dfb9a56e42443f42d48c1f16866338d997adba078736d32bf4387b95da2da33e714662f800838a077000000000000000000000000000000000f3300926ec62bbc0a73e4e0404de3a55dc0ad4fdc393b291ccf9f80818b6a69c0a3f9c6edf024db295a353b6aa71a5400000000000000000000000000000000195b21606667c62a181b275184014fcef97218b451c3642e2e4dcf5502d03cbcc88839e827732feb32e77e52f702529c000000000000000000000000000000001367e0843d866d010ab03723026aa9ab1f930d3579daeabadc1fdefc06047ade30b36326d79bbae74293d3daf2e6c7a4000000000000000000000000000000000550be918ba9e28ddacb89ec526db63072bc14a62ec7ac06775f951de5b32af93d2d8f95389a6384cde62dffb941fb5a0000000000000000000000000000000014f59aa4f0603d5f166caa5692d9b068ee10a6f45670c6f4d492a4d1fa7a13a5146efcae33a841b5acf75a32b853b2770000000000000000000000000000000010ca0d9ba50c730006c95bc5439e2d39a40a9055ddac935a900bdeafe7141990db20ad0a840d3297c4b9b5f69aada6df,,,invalid point: subgroup check failed +000000000000000000000000000000000b9590b1d0d292d9967d759060a551f4e8e4c1c0066a9a3c0be515085847fa26b77462e3bae9e2621f28e01f897df0bf000000000000000000000000000000000a00d39c6c14d18dc5888b8a8835c3ef6f83ead2fa380be24ae857677b904b3868ceabdbb3abf3c186c5353a67fad0a60000000000000000000000000000000017d978d60fc89b0429c1a6424231fe9274cedad5d78d9c4ac5aa2dd5e70e8238a0bb1904bb4b6ee5de5cd1ac514c62a8000000000000000000000000000000000d4ce85a95dbc40f405f4e7ebf9121cdcd22766737c39618ad0fb3e10a6e53be1faceaa96073b2a877ab808483ec9b6f0000000000000000000000000000000016c61599ae4da787fa6db233fc28f5c56f7133d403901800ab5fa19d058fb27ecb34ca2e56ffa7628ed004c9e62092700000000000000000000000000000000001e64e4adfdafbb423b1b9f8973738c690713911f68f658d234e57dc35b9554e0f7ba345dd7920b429a12b9c74775222000000000000000000000000000000000c453b5a2f6c721f0303b6b9205e51ccab7829345e0cf13e457491ff35e0f38dc5ac2e5b0d8195e3d322704c38d3280600000000000000000000000000000000092ddd55120b22e2853983dd3dea9bad3e4bf23e46dce297aa3db85856907d1b980185c5a6e02890f24a5463e7895b950000000000000000000000000000000017d978d60fc89b0429c1a6424231fe9274cedad5d78d9c4ac5aa2dd5e70e8238a0bb1904bb4b6ee5de5cd1ac514c62a8000000000000000000000000000000000d4ce85a95dbc40f405f4e7ebf9121cdcd22766737c39618ad0fb3e10a6e53be1faceaa96073b2a877ab808483ec9b6f0000000000000000000000000000000016c61599ae4da787fa6db233fc28f5c56f7133d403901800ab5fa19d058fb27ecb34ca2e56ffa7628ed004c9e62092700000000000000000000000000000000001e64e4adfdafbb423b1b9f8973738c690713911f68f658d234e57dc35b9554e0f7ba345dd7920b429a12b9c74775222,,,invalid point: subgroup check failed +000000000000000000000000000000000eb7d975030858b57bae3e7a8a7e297f9d7d6b2bcf27fa8bb9b73cf60a1a1a5e6638adc726eb2e7fd5500353de95179e0000000000000000000000000000000015c783845a98d2b12a8cada673e1df4c266b34302cf7d004e00612584d0913283d43150337c36dc3912bbd757e6a370b0000000000000000000000000000000014a0222d6d0a809cb5598db18e35f5b7f5c49ec5574fbb5d801d9f890ede23d20c62a22321394b6efd1a45b9780a4efd0000000000000000000000000000000017c8bc1b8f7ebbbcd6203f6ffcc758a787a96533a77290affc18b76fc6dce7c2ba8f169dd8ecdb8b240c92664852bfc70000000000000000000000000000000001a673ad0af4bdd53027b145472d1e8dfa05029b7ba5bcb7dcc63b5e6369b2e693f27e5665167a8dc8b6d3c9b85eccbb00000000000000000000000000000000085efb596145e58b4eae7aedc8f7aff6949f1edf8fbe0c88e6dcad46423cf7b2d5f8f54bbdcf8f1427438e6a4da71914000000000000000000000000000000000eb7d975030858b57bae3e7a8a7e297f9d7d6b2bcf27fa8bb9b73cf60a1a1a5e6638adc726eb2e7fd5500353de95179e0000000000000000000000000000000015c783845a98d2b12a8cada673e1df4c266b34302cf7d004e00612584d0913283d43150337c36dc3912bbd757e6a370b000000000000000000000000000000001425d6d5cfd04d310d8ef3ff1a3b3d227771f3bb3cb552c64ca1debd3e2e75e7a96b9a0d5768ff041098d743de445e790000000000000000000000000000000008b12959963c17ce7247490d9806e0ab02f9aeed8d8faf4591b955885f5aae509358a19c1eab19dacb72cb67b9c22c46000000000000000000000000000000000a7108fb7442fa6c50b472dda60d6c277bba83aa21ac1630bd3df6f116c8a58e60e8c4a303a041b9825b65301d388e410000000000000000000000000000000000faf3042f00060eff3e5e13e1a964c9dd605677596c49cbeccd6d0374c6a4ab9742cac63d1801631d5fa5e00d399fd7,,,invalid point: subgroup check failed +000000000000000000000000000000000f6d131c756d031041ba97dd20719cbfc864de16dddf00d282f6c41de683d193a015a781232ea5513321c131505fa4ee0000000000000000000000000000000008d754d62b8f1fb0d0e9dfe4746d9c5f9e4a296c6799d6379a4a1a07583d0959c62b05011b636b72cdb87d3c92d94f3600000000000000000000000000000000061bf88e4c5f6bfacbf1e2e72566d9c44a001237f22d7f6a7240ee0bd3c8d175786f7c2ace267774fa5615d7bd3ec8f800000000000000000000000000000000192c987bed20d707400d3211e480b8760021a33dfbaef829b1e865f92b37cd3962e56a38d4c06c5ad73bea5e7321b176000000000000000000000000000000000dbc62270cf7623e571f6ebcd29f426f214cf1c8f71edf9aeefbeb6037b7e71bfe41a55828cd4353bae2e1e927812aa80000000000000000000000000000000005f6e7064a9708e20bce8e5002c352b13d4531d87be1320bcf3322d5e2b851a54aefaf6f9ae26e277ecad307c448511c00000000000000000000000000000000041fd1625afa48a446454d6613c17cc6a65b3ec8b8f2125c0eb7b8e5d07968397d43969a6579226f496d9b24dbb71b820000000000000000000000000000000006131c506f243b5ac40354f826ac1838839eee9f61301aabd88e499d40e57df3122edc8b36f0a8b16b72f9ac783efd3e00000000000000000000000000000000061bf88e4c5f6bfacbf1e2e72566d9c44a001237f22d7f6a7240ee0bd3c8d175786f7c2ace267774fa5615d7bd3ec8f800000000000000000000000000000000192c987bed20d707400d3211e480b8760021a33dfbaef829b1e865f92b37cd3962e56a38d4c06c5ad73bea5e7321b176000000000000000000000000000000000dbc62270cf7623e571f6ebcd29f426f214cf1c8f71edf9aeefbeb6037b7e71bfe41a55828cd4353bae2e1e927812aa80000000000000000000000000000000005f6e7064a9708e20bce8e5002c352b13d4531d87be1320bcf3322d5e2b851a54aefaf6f9ae26e277ecad307c448511c,,,invalid point: subgroup check failed +000000000000000000000000000000000c9856c54b638559ce67cffb3e784b9649bbeabe135c89ecb8108341a16ef3c5ac49e9243144792a4f9371f2d6f8f6b6000000000000000000000000000000000e17a67cdee23e13c95e5b951c5f51703660ff614fbba363e5a73ea74b86af6893ae90a929df3d7e2af8852dbad77a01000000000000000000000000000000000c01d2e1592a61cda87dab7fb2a45d7468a8ce56f74fd7c8d401e224b9610d675cbdc0d4719dd2dc10f47547bc641dd90000000000000000000000000000000002d57ac9ac9b4cd7a477e162965c763ec1d0c5752b478449839e0ebd7a1110374fe90aaa2006bd03f08f9242c0419e99000000000000000000000000000000000d50b11e1ea90ef8ef0f7a95ad41ce2cd30cf804e46a96762fadbc275ad513827e6e75b95968a00b42085aa89bb0839e000000000000000000000000000000001250e40358d42dd3a6aa2dc93d89010f2a7b33e5b426ffd7e6eeb9bc02f8f22fd6e91c4fae603f14ed682617dd503eda000000000000000000000000000000000c9856c54b638559ce67cffb3e784b9649bbeabe135c89ecb8108341a16ef3c5ac49e9243144792a4f9371f2d6f8f6b6000000000000000000000000000000000e17a67cdee23e13c95e5b951c5f51703660ff614fbba363e5a73ea74b86af6893ae90a929df3d7e2af8852dbad77a0100000000000000000000000000000000048dfa619f3f77c794d119ff97e04826b5bf49d71591b778b502205875b97b17c9c9d2ae95c8d7035e9d2ade10a511ff0000000000000000000000000000000002fe9b3108c8911b07e4ee52a8d4df17bebc8153f837665513ad5c8af53cd7387100204fafefc2febb2f5e18c9d958b10000000000000000000000000000000016c98a41fac6665657b00bfe9336942e7aa145bea3131224ffa35ec9b9cd4d629b4ceeca98a15ad07e03ffcf898f8a390000000000000000000000000000000011a0d16ccdf98beffdb0953c509261f18567a1656acdd895395b480ebf8843e7d3b965e2151c58e9b610e9e3b5491832,,,invalid point: subgroup check failed +000000000000000000000000000000000f4eebd0bb8233e020629f72258d3cec7fcd3c181fa913977a405ef7437f6fdc0ee34fe6a7e349730e695f383e4478ad00000000000000000000000000000000080577fec3a6cc706037b43cd39f4bace98191511cb26cc606c11659b63112b519123a00b62158ab7a24dfe086705b470000000000000000000000000000000011e8ecf1e341f0146c59a79a8428bb01d2399d3f87d90d057f63e6cb9837432154d17975f70df175a016735caf85120a0000000000000000000000000000000002a5bd53e4f4c5b9682e1af1f7e09dd305e7342d1688f62885b5e59f173a9fc731cec481559ad693030004a5fbd90a9d000000000000000000000000000000000f9601f95e12bf05c35deb204558d44a60fd630c05f4060b7bd9ff943946e8eab507422afe00a3e7706b8ed013f712c20000000000000000000000000000000003bf6fecc0c7414a69c2b48e2c16e88d988ea8ae9d8b59017ecb89394732a20e4321cb5e4fb071aec7d2736220a455370000000000000000000000000000000018fe22fe0b39f508823e2332712f988efcce291d93c416c544272e88cc0902d79e41b01af005feddaaef6c42a26c824700000000000000000000000000000000164fe8d2934cda6f0b863bb42d13f91c52a49706182f8d337d6a629da65113818d1c177508d6735cc86a6b3837ce49320000000000000000000000000000000011e8ecf1e341f0146c59a79a8428bb01d2399d3f87d90d057f63e6cb9837432154d17975f70df175a016735caf85120a0000000000000000000000000000000002a5bd53e4f4c5b9682e1af1f7e09dd305e7342d1688f62885b5e59f173a9fc731cec481559ad693030004a5fbd90a9d000000000000000000000000000000000f9601f95e12bf05c35deb204558d44a60fd630c05f4060b7bd9ff943946e8eab507422afe00a3e7706b8ed013f712c20000000000000000000000000000000003bf6fecc0c7414a69c2b48e2c16e88d988ea8ae9d8b59017ecb89394732a20e4321cb5e4fb071aec7d2736220a45537,,,invalid point: subgroup check failed +000000000000000000000000000000000d6e0830ba9839db89621518e1ed86e9d035d7fb03b18d8a615acd58ff5c8c3dbdcf1acfcb8a910e8d3d0da770057cde0000000000000000000000000000000018c38d6e458e22cdd381010568606d26770b9eaaeb38e02a80cd0a0d584577fca7b391bbabe902825a3338682f7d3173000000000000000000000000000000000e5d5bfbe5ab3da8c033663cc457cf69062e1de6bcda6420826301d4e5fb4c47a5c90432f15608aa50678ad083f618c700000000000000000000000000000000146cd56ad5d977fa6471f7c12ee3fd66dfbe4b276b846cf4a74619cfae4a5101e85d01db615e0e6d28a5eca7a92cd05d0000000000000000000000000000000010fa99a243f1de85f40bf6a08e3e9ed3e478562468f0975bb79b1b80c9eef8c433c18ccf6e5149eb99c21f74494eed6e0000000000000000000000000000000018504d4687cf9f055c5cf8360fda2cb2123ea3a3e9c8f58853a68063e60a7ba24f2d1c153090ba2e77156cbb5ded79d7000000000000000000000000000000000d6e0830ba9839db89621518e1ed86e9d035d7fb03b18d8a615acd58ff5c8c3dbdcf1acfcb8a910e8d3d0da770057cde0000000000000000000000000000000018c38d6e458e22cdd381010568606d26770b9eaaeb38e02a80cd0a0d584577fca7b391bbabe902825a3338682f7d31730000000000000000000000000000000016267be431a654019b3f02389fb2f2f307f3bb56590c3ff1924554b2ee6fbbb68381b5ba4a8668fc541dd7e3fe9afc9a000000000000000000000000000000000c41d1daa258be71001babb484d98501c9ea015aad96e398aa6015b9ef618f6a56a45d45383dc34c70ca05242490ae18000000000000000000000000000000000dadc282d8cc0c066f28881cf440fb5a621449dc16488d02e34492fe8af53fd1749886fe6df2e377190e4043d6e3dd290000000000000000000000000000000018a30f1ae8927af27147ee5ea3c7aa848b0e303d16018fc2c3ca4bd088ec5ad3a808e7699ca009a4bf6f5b707edc1fd8,,,invalid point: subgroup check failed +0000000000000000000000000000000009f71faeb58c4d442272ea0116a03f106008ed68030bce1bc53a3964ec4f9758f508735c8c17017645ccb77124b46cb40000000000000000000000000000000016a2ea9e548342538ccfdae6806ec9ce0b7b892875155f898017929d7105001e8de041bfcab7398cccd060dcee3b6e84000000000000000000000000000000000579462f03502615d26d76e05c758a7f835e4e70f1adfdec8c7ef26580544326d66ba4e23be3c078e1f187ab498ab9dc0000000000000000000000000000000000d386d0af32f630bdf6c13ee82fd35e42961ec4b11ee8fa196d69bed775b8ecfe74409693c531631aa716957e5fe6d7000000000000000000000000000000000ce8a020608c1e7d70d7a03d11b84f48d6b3b77c5836b7c914627f3a84d3d0b3de513029c1379b20ee38a19928e57f44000000000000000000000000000000000dd10882001be7fa52ff21f1a0adab02e688539f098901fe515b61fbdbca02c4146f6c17f7ad7cf59f5e8acd85da60b000000000000000000000000000000000128e019ff92e7171d3c791bd4cf75b0f47c2a9d8722b4a8279f1178db6dddf8a4c00083a935168518a1c26a56b23624f0000000000000000000000000000000008d0c5f3300e73682f4756e6ff1d6722dde576beb587301ded34427d6935e59e76cc8a8cb0ea5f659db9ad5435851e53000000000000000000000000000000000579462f03502615d26d76e05c758a7f835e4e70f1adfdec8c7ef26580544326d66ba4e23be3c078e1f187ab498ab9dc0000000000000000000000000000000000d386d0af32f630bdf6c13ee82fd35e42961ec4b11ee8fa196d69bed775b8ecfe74409693c531631aa716957e5fe6d7000000000000000000000000000000000ce8a020608c1e7d70d7a03d11b84f48d6b3b77c5836b7c914627f3a84d3d0b3de513029c1379b20ee38a19928e57f44000000000000000000000000000000000dd10882001be7fa52ff21f1a0adab02e688539f098901fe515b61fbdbca02c4146f6c17f7ad7cf59f5e8acd85da60b0,,,invalid point: subgroup check failed +00000000000000000000000000000000167a40d69e2d2e5683ec69af7b226c5456d05241d9c9eb24e988ee2dba845eab361a956cdea64cf971dafea08f0056260000000000000000000000000000000014e5b8ae5032a924da081d3063f31889da110d00330903c6f988684b7d77b7b1bdcbefbf70941d0eb5a34bcb010a3bf200000000000000000000000000000000043a000741027fbd191e2399b7cb5fdb4db3728d81d8b9d25362e8a1d397ec99d1b2339a3a511d0969e7e727f98d4163000000000000000000000000000000000cbda70eab332bf962d123aaf08b8b96680055f1946dd5fdb8818fbb330b816d83062ffaa79e18f1f4f6d53deda53cf200000000000000000000000000000000160a7048e508da288270e8ac5793e9461eaa282b85ce5350b6a661209efa6699874aae71515dc4265125d490a5771ff900000000000000000000000000000000041aa7292f258125d729c1761a3a6f7979a7a92ff179be678ebe301de3ffe12e4a863becbfb2bd0067e42aefc5f5617200000000000000000000000000000000167a40d69e2d2e5683ec69af7b226c5456d05241d9c9eb24e988ee2dba845eab361a956cdea64cf971dafea08f0056260000000000000000000000000000000014e5b8ae5032a924da081d3063f31889da110d00330903c6f988684b7d77b7b1bdcbefbf70941d0eb5a34bcb010a3bf200000000000000000000000000000000158feb06ab69f5da37037c554aa8b4b511e9de7e13a08c2f9a8899f5a504b191195e4de1ab7d937913f60e24a1b3d3f8000000000000000000000000000000000beeea216d85a767af2facfa7fa0399c095a23b7a974ddc3d36b7e79822d7691e53f85c976b8cc948e155f196b7f168c0000000000000000000000000000000009802e94ad5cd6b071a0911cbbc1de29a79a5a4fc145cc79db03119c3c8dac88fa3740bdb35b0b0e6e34127ee1bb081c0000000000000000000000000000000004599d1e0bfe642ff275fbf1dc86bbf83b87c241de09570183faf4e2c1cefc6b8b2354aa2be4aa9f69d6d61546d3a685,,,invalid point: subgroup check failed +000000000000000000000000000000000130761fb4d4720b8e69b849e1472f15c24cf909920c598aaecd34c5b420a1989a6ea95f89c362a61e4a11f88982f9a6000000000000000000000000000000000d707b24e8720ecd4a14d9f658060303d233253caa63bafd0f475b2985fcebd326924c936a9d66b21b9a75085aaeb54e0000000000000000000000000000000009faa74f66ec0384f0458893c0026f73688c764e8df9ce056a88a2ed0b84ed8f88d1b683443a3269a3db838f8aeb808a000000000000000000000000000000000949c4be2708c1aac86aff39290ab6a8e0f332e7a098bbd64227a175473d9dfe136e07548b282f69a94a15e2c32dada10000000000000000000000000000000014f2c7c7da781e2f50803e3a948381c3c439b127949f79824df1e5722c206efccd6c0ec5dd75ef63d8b1fa301c83356900000000000000000000000000000000176753460d241f38aff41bafdad51688ab0dc9a5fb3643977c7b9d282ad4532fcca1e725715227780ec28bf1c32bbc1d0000000000000000000000000000000010ad2405965f283c845edf92cf34508c0ca625816690d739fec9776d261784a946e083112d11c0776edd04403eaa5b820000000000000000000000000000000007e400896eaaddf797643b05a53f612f73737a2c438762d3f6216d53761f28bc88f402a4f02f36d26bbb431616b1b5690000000000000000000000000000000009faa74f66ec0384f0458893c0026f73688c764e8df9ce056a88a2ed0b84ed8f88d1b683443a3269a3db838f8aeb808a000000000000000000000000000000000949c4be2708c1aac86aff39290ab6a8e0f332e7a098bbd64227a175473d9dfe136e07548b282f69a94a15e2c32dada10000000000000000000000000000000014f2c7c7da781e2f50803e3a948381c3c439b127949f79824df1e5722c206efccd6c0ec5dd75ef63d8b1fa301c83356900000000000000000000000000000000176753460d241f38aff41bafdad51688ab0dc9a5fb3643977c7b9d282ad4532fcca1e725715227780ec28bf1c32bbc1d,,,invalid point: subgroup check failed +000000000000000000000000000000000220d192e0c635f05870113aac899f996622cd21ad251fb4c300a0ae0051dfd93b0b622f54e149fc4e5bf280ef31b0c6000000000000000000000000000000000dcfd7def563a0937b5173d20f830e6a2350a9d6f1b8a2193e5f755142e850bb7f95519ca187dfe735c197c21688528c0000000000000000000000000000000002479a989dbf27141bd9f467447218dfa6ef60781a7231f089d5f1f1d8dca2ce9606a75c09f63f37f9cc1ee61dceb32500000000000000000000000000000000037c2f1b96170f6847138232bac663e4940bca602717c877f58ff7f5259778246085d499ec6bbeaade18f738df333cc700000000000000000000000000000000115a30e7b71e48efc7166b9a8d2e1ecec6c6e4c058065a18277ca547cf1ebc193e635eb9b65d14c9c94ebde3c83a55dc000000000000000000000000000000000b68916150436f74c61b071a339909f78701d1f3045f7201f076991d7447570fd90887126ca54d96c1310e59ad489190000000000000000000000000000000000220d192e0c635f05870113aac899f996622cd21ad251fb4c300a0ae0051dfd93b0b622f54e149fc4e5bf280ef31b0c6000000000000000000000000000000000dcfd7def563a0937b5173d20f830e6a2350a9d6f1b8a2193e5f755142e850bb7f95519ca187dfe735c197c21688528c00000000000000000000000000000000147610c1075d625dc36388a2bd451ad2c15eae8cbd30393bb9fa30fc233b29a0f5483579752a748bc0f5ca9c90875e0e0000000000000000000000000000000003dff69511f2a53e859b6769ef4c257acf31062eb72f8668f57da60f225ac052badef9df25d43148b4d3baa64c29eccc0000000000000000000000000000000006d2a71dd9c1b302e22c4cecd14dc385e068e63e28e167c060de3ceac8410461d83f961c9292cad30afc0084855ebf1d000000000000000000000000000000000890ce53931aa18e42f00379b40c8e205582f71707a67761acced851fc91755505ae960951dec4ef46353b66e0b928c1,,,invalid point: subgroup check failed +00000000000000000000000000000000050ae21faa31371d791e3f6163f1c18c577367badb337aa6fa7c8214ec58bacfb24300a81d8226e81d7f47730c734386000000000000000000000000000000000987a0c7abd7c72140bcdba9cf171389ea7971493187c567b0970f05d129b4202124d72bd01038bc77023171de379762000000000000000000000000000000000d4b71595321913e94b9e6ee6ae2391cd5037e8b55e61fd96c745539bcb4bc4fcbab678749f6d47fd2c95001da4715f70000000000000000000000000000000005436e1be9029a2f7cfac51a305fbb7e760a9171c79a5bfce6b161493fb2855df09b3345ddbc8b04318c6b2e0225b74a000000000000000000000000000000000b8f317d36c50c8d039ad137f7d9adbefb3fc147a9cfd6cceb02c75b95fe307c3d6f673a216484f3211f045c2ba9012c000000000000000000000000000000000156bc67e17c8eaa905fb7d9f3f251cc81eeecc86de791c8be30c34aea896755fe2bac28cdb035222afe6237614878c300000000000000000000000000000000065856fe1dcbef934cef47b177ecb7df76cc8796624400d5c0518aa9438bcadf397234808d099bed89ab674560ffbb1800000000000000000000000000000000071b2ff64379ed3e20cda000602c3504616dd673aebbe7690e797d6428ecfbdb29f11138169f3462dffd319cad68b96e000000000000000000000000000000000d4b71595321913e94b9e6ee6ae2391cd5037e8b55e61fd96c745539bcb4bc4fcbab678749f6d47fd2c95001da4715f70000000000000000000000000000000005436e1be9029a2f7cfac51a305fbb7e760a9171c79a5bfce6b161493fb2855df09b3345ddbc8b04318c6b2e0225b74a000000000000000000000000000000000b8f317d36c50c8d039ad137f7d9adbefb3fc147a9cfd6cceb02c75b95fe307c3d6f673a216484f3211f045c2ba9012c000000000000000000000000000000000156bc67e17c8eaa905fb7d9f3f251cc81eeecc86de791c8be30c34aea896755fe2bac28cdb035222afe6237614878c3,,,invalid point: subgroup check failed +000000000000000000000000000000000e4bfbc392b1de6b17a32387d694f634cb484be4531f425d72833a30ed5aa73a958d5081b30cf98484762ba51542f87b0000000000000000000000000000000010e6acae9df1b5567854f4bbca0ab07c6a0e726bc6ec7a1d2ce8b04e1677776d0ee2642950db040365d581a52a41832300000000000000000000000000000000036a2ac8ecf17fc72ed792c0d8939060117aa0d6c13854fdcf56ed0d1ed3b39da9a67aadfdff484850f9cdf439495712000000000000000000000000000000000a1bd875b74e1ebec19591eb137e68ca7f0db1b3056d341b6bad28c3f45e87688e73fe28e9ef44c7d494442ee0b9472e0000000000000000000000000000000002e26e5a36c008bddc431048d999b7fb44961bb4d931b2dec0cb1d1b0987587f44cd31d429a6cef05d3c060ac828ba7d00000000000000000000000000000000050640087ed6c04ffed759f63e211ff5880c8b06933c1e812954a7a4240a9c644175c4ca3048a4ed68d034f6cbdcf175000000000000000000000000000000000e4bfbc392b1de6b17a32387d694f634cb484be4531f425d72833a30ed5aa73a958d5081b30cf98484762ba51542f87b0000000000000000000000000000000010e6acae9df1b5567854f4bbca0ab07c6a0e726bc6ec7a1d2ce8b04e1677776d0ee2642950db040365d581a52a4183230000000000000000000000000000000003fea73d9ecfb879ea601865d3279ef9b271fdeb14e1094745d5718bf214f1fba99245746716bbf012e2ccff5e22887d0000000000000000000000000000000011b1aa3049157abfa01f3bef33583635873d4bc66801785f41ca51b9a11d95b49a8bbaa543fbe68db86c3560ae258954000000000000000000000000000000001740661de7e1b6e1eff626acc4e37c877d00a0ceb8008d99e76a1dfe826b4dd0ee69b150535caa20386d644394dcf6b800000000000000000000000000000000090c0bc61286187a58f2cf49e2ce6f49d721b4146d973dd7716db271da770450dcba33b7aae37d6842a43063c88704e8,,,invalid point: subgroup check failed +0000000000000000000000000000000002cd3682c1f87aac1ea91b3f4ac577f0c9531c7702864c444b4163435b4770c9a2ebf94fae2d45a63adecc314d9d536400000000000000000000000000000000103a1eaebc0ee53e21d5bda78bd912f20cc28a13e5e209e062a52ddec3453fa9c364a6b403a68d9db8a40a07755ee1eb00000000000000000000000000000000024494aab30849df790185a4f939954b724c387c9a366fbe833b628577654174f705d05e7d7dbcd29b8873aecd55df0b000000000000000000000000000000000863054fe3e4838d2caec7103e3d0453e86a17fff0dfdb84dd819f31756032e9e97b7be89b636e5e0b642718f6da217b0000000000000000000000000000000015c8bb4fcb6d9cf941b722136d8d76d847fd6d5c643f4c0049c9746e76e49726fd463ce7899f4df66d04e5d48e523e6a000000000000000000000000000000000f101bea4e1bf610d2782ede91da95eb2b0be9ce60485465b9e94cbb9530b416c4394862f0ba7ee8067bb48e94c07c530000000000000000000000000000000001b32ce5c51441e1abbbcc0b6af95380f2de24876ba377ebea44fede8886acbe23aa18681bb08130252bc04193939853000000000000000000000000000000000e5d718c9980140b41fb971fe596070982bf92937df77ef44040ffc27162bb443a60f5c64411ffefc24524ff32fa17ff00000000000000000000000000000000024494aab30849df790185a4f939954b724c387c9a366fbe833b628577654174f705d05e7d7dbcd29b8873aecd55df0b000000000000000000000000000000000863054fe3e4838d2caec7103e3d0453e86a17fff0dfdb84dd819f31756032e9e97b7be89b636e5e0b642718f6da217b0000000000000000000000000000000015c8bb4fcb6d9cf941b722136d8d76d847fd6d5c643f4c0049c9746e76e49726fd463ce7899f4df66d04e5d48e523e6a000000000000000000000000000000000f101bea4e1bf610d2782ede91da95eb2b0be9ce60485465b9e94cbb9530b416c4394862f0ba7ee8067bb48e94c07c53,,,invalid point: subgroup check failed +0000000000000000000000000000000006e340bc57495a5c01bd44eb6dee60e94feadd69bd0274e7c9755f7b0e7b4e59eb70a2cd7e4da9d1a5f60d93a5df19080000000000000000000000000000000013d7c393a2191ac8d13a0743fecd768a7e15d279be409ccea1d58ba39e91f592b425955ddfc8443861a2899de02cd0fc000000000000000000000000000000000369067d9012509bbc75333fcfa37cd42dc3d6331a55b4720f9efb937916692ed7c7cf4739142ea13fb9130e9dae92fa0000000000000000000000000000000005306d73dd33e26ece5d8cf2ea8d7e25976f7b95dc7091420c2e91b1d0b2ef96db69a364c3cdc3b24a49ec5e307553490000000000000000000000000000000008a9fe15ea4e16e675c42c25b29655683844949b9aff4448f0b79ff08ec2a6526cad973f80123943b863ba9f30b45666000000000000000000000000000000001754caabe499e1ad04ae65d696507096939dc826578db55738e5024601eb052a9fc15f20b601253533847f79a5f6a6ae0000000000000000000000000000000006e340bc57495a5c01bd44eb6dee60e94feadd69bd0274e7c9755f7b0e7b4e59eb70a2cd7e4da9d1a5f60d93a5df19080000000000000000000000000000000013d7c393a2191ac8d13a0743fecd768a7e15d279be409ccea1d58ba39e91f592b425955ddfc8443861a2899de02cd0fc000000000000000000000000000000000ddc7df1bb36d54beb969a244a67c3ae87cab2eddce9397869421999497ab00a3e0ee29e384f3182b52158304790b05c0000000000000000000000000000000006f527de4412dd61845ab1e4536a114c7ac2bee2f3d22c16c660b3189b1666849f70462d6d650846331dc630dfaa075e0000000000000000000000000000000015aae1fad9e6cb3adc8bd93c432c9321a9d2f7cb961671c8e517da7024d1e35b519a2cc662d795173441479fa35f2e830000000000000000000000000000000008485999f40a4a363ddb3da273f7522344f284fb5bcee383a590c7c1287baa10f21312428d6818eebc96ec59d716fae1,,,invalid point: subgroup check failed +0000000000000000000000000000000015845de9692590d7fbf4187b50f4b2d067983b0a2209ed2bc6d60914be106decc5dfefba112baaf98ba3bf9e4d102b660000000000000000000000000000000004f2ca0858d056f769bc8e6ebf7cca745ca647ba497940b25b31c0541297626cd26ac10f8ae8476b5c868a95226da290000000000000000000000000000000000b1374a47c7c1c833f3856b0fe5ecaefaf2a8f96148eb540482288b56897d9e7e4269ea3a2c3742993b751bd9e484f2d000000000000000000000000000000000bc7fadac70d0a401e61683562cc83ffe107924ba1788bb6e06b0c60f22de0d93b10b63afcca343cad0572209b03b12b000000000000000000000000000000000c22c75d826d2700a8bad4e9c271d8b505ab2911dc257909c69dfdde2bcf332e5f13592eccabf578f48f6078550c1e9c00000000000000000000000000000000057db54159019d1e291131d28a936ac1337f2884f0c4bfc4d8adaa75bc0edf8b0f3030725e33a3d1a2e7e9ea39512fc70000000000000000000000000000000000232940188006769a382a4958383aa6702b2cbfb9c2907a989938ac618f23e241767b021e8ae11c23617ab393d4f85a0000000000000000000000000000000016a672061fe76ed943e36b2d6fa4aadf579db96eba5e4c60cda2884ddcbb0f37668638a3167d8858cd296917eaeff8e0000000000000000000000000000000000b1374a47c7c1c833f3856b0fe5ecaefaf2a8f96148eb540482288b56897d9e7e4269ea3a2c3742993b751bd9e484f2d000000000000000000000000000000000bc7fadac70d0a401e61683562cc83ffe107924ba1788bb6e06b0c60f22de0d93b10b63afcca343cad0572209b03b12b000000000000000000000000000000000c22c75d826d2700a8bad4e9c271d8b505ab2911dc257909c69dfdde2bcf332e5f13592eccabf578f48f6078550c1e9c00000000000000000000000000000000057db54159019d1e291131d28a936ac1337f2884f0c4bfc4d8adaa75bc0edf8b0f3030725e33a3d1a2e7e9ea39512fc7,,,invalid point: subgroup check failed +0000000000000000000000000000000018611593c7663a36330e0640bc912221a17d0ec9d2a250f1701303249655c7088bdc07b024878a260753d1dde306fd4900000000000000000000000000000000069d26308af5907c15532f31dca8c884b9e54a0da5e428c66fd79d980774cca106e2ccf3f93fe3e01669c594b9770a800000000000000000000000000000000001a38d296c4b7b8351164b935b915c08caf6af9d5233ecfe609e4ed855589bdbc9fb0adc55bb5cc6a2526bd82ab9287f00000000000000000000000000000000184ac28f62c7101bec49879af5da794740f9ba99afab4fcb576fa1149f3b701079915934c624f022b0d3213adc884c2c0000000000000000000000000000000004f64835227f459e76aae3397dfe53eddb1e97c8afd8beaba09382fe79ab378c3f03d6962dfee823c426cab426e51d2d000000000000000000000000000000000577263fd875f6388b723f6abc78aa3b0cc2e6b0ab53beb59286e30d1e982114c161fc0ef490bb1347fd8a3f3cba72710000000000000000000000000000000018611593c7663a36330e0640bc912221a17d0ec9d2a250f1701303249655c7088bdc07b024878a260753d1dde306fd4900000000000000000000000000000000069d26308af5907c15532f31dca8c884b9e54a0da5e428c66fd79d980774cca106e2ccf3f93fe3e01669c594b9770a800000000000000000000000000000000000c072a812b26927b5a672359032e71597002d8c45572db6c575e25c81181a522ca304867b1bcb583c58b2ad6bb1695d0000000000000000000000000000000003c4f5f548f50e835f402105cbd315081e0e8773dd91f3874687ace6fd5ffa66b6ebf6dac4580357b870f8835ed15cdd000000000000000000000000000000000f039e2e73334386b28ed4cc52db09b69c942c0c87f81328a9b7fbe8553eed48a60224ae9d52baf4f62a3fb0d58d451d000000000000000000000000000000001974da8867204f048b81d0c7a90d224dc5b87f3c4f47d1dfb09afc292430bf51d69e3538fdd13ca0749dee76c2ba9b17,,,invalid point: subgroup check failed +000000000000000000000000000000000754f3a9ea93a01fa244ef6badeac64fbb4dc81d3489c4195a01148e268e2af46011e6c593b4b873fa1470eecc6f79ef0000000000000000000000000000000018043830aa55a8c7efd5dc246622c71bbb4bc6a5d69cf594f46f62d8c3e2a5d4ba1d3990b5445f8b446e404a00259a8a000000000000000000000000000000000e49e94cfa35d8ade2b76865cc8be04737d00b48b195078c8085cbe782232a544cdb548373bd8ad0282674ba5c96fe0700000000000000000000000000000000047d59661f095c41bcc27da5f260f13a3fce334bba216b45df548894bdebc691fe779ccd63d99a9872973ab165a90c01000000000000000000000000000000000772e9a9c22bc7352fdf74915bc464de99ecd96420ef1af6e8bd5a05d73fff89c78e28eb340d4967e906f28afe1320490000000000000000000000000000000018bccff27bf9d7cb2159b9f2d1faabbf8591b53ca8e67e661d9f44f6dba6296e3e46ac32c50128bb5fb076cb8f214e27000000000000000000000000000000000252149178c606d2d6c0311e9f4a66cf348869efc09ec887cf99088ec754c01551796aaf168dc1a09cb741ab3c9d6891000000000000000000000000000000000db7baeeb5acfb22d680e032965a4d417b2f2f6717d3667d786e006327140c1288ff44842142eb1d2730b3be64fcf420000000000000000000000000000000000e49e94cfa35d8ade2b76865cc8be04737d00b48b195078c8085cbe782232a544cdb548373bd8ad0282674ba5c96fe0700000000000000000000000000000000047d59661f095c41bcc27da5f260f13a3fce334bba216b45df548894bdebc691fe779ccd63d99a9872973ab165a90c01000000000000000000000000000000000772e9a9c22bc7352fdf74915bc464de99ecd96420ef1af6e8bd5a05d73fff89c78e28eb340d4967e906f28afe1320490000000000000000000000000000000018bccff27bf9d7cb2159b9f2d1faabbf8591b53ca8e67e661d9f44f6dba6296e3e46ac32c50128bb5fb076cb8f214e27,,,invalid point: subgroup check failed +0000000000000000000000000000000003455a16c1cf9a408bf17cbb0b8ec2a5f24ba41d5b8a06a10750797f3371bcf361d3b6902d73949df3a24f5a8b9977c10000000000000000000000000000000016480e13653604d05f025b8264fb35a2cf06dc6a90ec7751ed80ee81cd064864655d133ce398e293c289a572dd98605e0000000000000000000000000000000015015aeb1965917cb5b55092d7eeb54915e21fbd5b9a62530b3dd5b8ae07d6f491df46e1987f565223a83cbc90f91735000000000000000000000000000000000658266a6bb01958b791b288ea9f5e138316724398218be522bf816ee5b5b34ef4acc83b82959f52792af6940816bb41000000000000000000000000000000000bf8d18b55a57e67c76882f1a1ad845480196f28556ef569a6a6054426bfa39459ac030b594201be76968cc33c301dd4000000000000000000000000000000000fd8b264f9bd71e00e3987cd221d2e9fbbee34ddcc5c563f02dd150451050bd20b3bd3a6ce1284fb0ebff0c6c1318fa10000000000000000000000000000000003455a16c1cf9a408bf17cbb0b8ec2a5f24ba41d5b8a06a10750797f3371bcf361d3b6902d73949df3a24f5a8b9977c10000000000000000000000000000000016480e13653604d05f025b8264fb35a2cf06dc6a90ec7751ed80ee81cd064864655d133ce398e293c289a572dd98605e000000000000000000000000000000000c5c8e759f71292c8b25dca51bfd40daec58d2d2befe991b27993ae32489b3ef70c0c3f873b3f47ffb013fd31732f763000000000000000000000000000000000a78f4e402668544cada859953182f14421c54446c264312daa5f27cb18a3955a277e5620b0f28f4f9bf233c49f3341f000000000000000000000000000000000e27d9b1bf12c01ab05fa5ed5b2e70444c0582aa5b430d0e10feb69dcddddadeec88c024ec87e23737ec1385461edaad00000000000000000000000000000000065984e81590e368aa6de051129761e94ca218df58ec0132ec22aa7d4128b9ace20e7da35c56a674ed1031d202e9f313,,,invalid point: subgroup check failed +0000000000000000000000000000000005a54ee5e3dc05c38ade7a42c71baf5a1467938f97c0cdf0742441cd339f22542b1ca6cd215d385b3fd6ba74ec996a520000000000000000000000000000000005f63d18f8bbc1c7b119ee0d58b688477129b2558be0084865297fe119224aec83b3633a646a3d423029cb52f0294aa20000000000000000000000000000000015e2547357626e6160105e5254d8deb80155c42d7b0c13bbe350c3395317913ccea5db61494aa2634857a8c83baa42a0000000000000000000000000000000000629ebc8d1798e2f9280493d2de7c159c558156782487d307ba358fd2bf696c29518d6cf2f975509bdefa89033f1cfa20000000000000000000000000000000011e3f568b9d1793519d5a8cd3bac7cf35091665f981ecb7a9e942f630c5f18fe7cd9747ff539816993b70573410410ac000000000000000000000000000000000889dedf6f29ed9959d8eb7276ae6122fec5a1bfae72c793902e1e3062be444b89a95129dd59f74ea0849b5a2aefd48b000000000000000000000000000000001963e29f92f6f72be2afa4635221b0d2f6afe9ada4582bd7ca4b77eb77fc4503578f38fb49aa1838751db8cf1ca0b0cd0000000000000000000000000000000009856a48f12966554afbcde1971499ee3ae40c9c5c3aef13bc415fddb97545ed84d5f50d2a26b9c16c4403a487dca6140000000000000000000000000000000015e2547357626e6160105e5254d8deb80155c42d7b0c13bbe350c3395317913ccea5db61494aa2634857a8c83baa42a0000000000000000000000000000000000629ebc8d1798e2f9280493d2de7c159c558156782487d307ba358fd2bf696c29518d6cf2f975509bdefa89033f1cfa20000000000000000000000000000000011e3f568b9d1793519d5a8cd3bac7cf35091665f981ecb7a9e942f630c5f18fe7cd9747ff539816993b70573410410ac000000000000000000000000000000000889dedf6f29ed9959d8eb7276ae6122fec5a1bfae72c793902e1e3062be444b89a95129dd59f74ea0849b5a2aefd48b,,,invalid point: subgroup check failed +0000000000000000000000000000000010df7793a66234599791ae07db9953bba83492389b150a05063105c191c6f32559b9533f1875ed154fa813586e84d0c40000000000000000000000000000000018de32e27e5c97b8b39380dde927e2143bca5d5591d770b2a7a77d7b59bdd533f04c41aef3a5a60df55ed90742869b7400000000000000000000000000000000182f0aa672674c27d8f3fbcc0fc7c1608c01188cf5c28b8ac08d6f99a4e3e214aa3ba61a19607b513b4d01f1d4424a990000000000000000000000000000000000b47be5b5249eab6f67aca9c9349d4a83956550cd21856485132c26e3be3ca133050d79507e2e8cafb67b44a7f4b4da00000000000000000000000000000000060efe7e7a4ca52063b49f6c839666deebe2e8e563de18b210fb477e86420aefa38f89e926cc41334e80f4d47d810d97000000000000000000000000000000000c2243c2a34286b146462a643979a72e75f7ff31f9f043164a5514d3e5da8b0cd891e97af2dd3d6c6f3584b390e5faf60000000000000000000000000000000010df7793a66234599791ae07db9953bba83492389b150a05063105c191c6f32559b9533f1875ed154fa813586e84d0c40000000000000000000000000000000018de32e27e5c97b8b39380dde927e2143bca5d5591d770b2a7a77d7b59bdd533f04c41aef3a5a60df55ed90742869b740000000000000000000000000000000015f7e6dbb904f4f1fbef282667d77c846c0dac6d78f4daaf32123b4326f1c6e42b15cb06ed7b3b46c8877e684ca7f9de00000000000000000000000000000000004a87f0ef3285709a3ded240f529e8e10991a82c7fb40de0bd256a9fafdbc212eabb8121f52c6bf67a45bf36246921c00000000000000000000000000000000156a1d58dd64734149b1e37b1b69fa4ea4452c40d2d041f5cf3de3adadf3bfdf433626fb96c46f9104b1c16114108599000000000000000000000000000000000fc3a0e58ed3fb583e2e1c3b0db5fac2a6d75694aa3d0732243b5a75688fa0ffb5fc9023545b9585df224ee9f6c5d171,,,invalid point: subgroup check failed +000000000000000000000000000000001639642196e9d6d720e17ace198232d1c0a7c05660c2766b9d28147504308ee48d949755ffcd1c95e024fa299657c8d10000000000000000000000000000000017a5975410b3a80e339b200ec63d7b7d550514653726e3ae6a97a383545118bd4221bba3e27922a826057b244ad025f30000000000000000000000000000000017aa7a3f1ebbdec6abe12abea12ef50a3daabbf96a5f2ebfb517885f0b7aba1e927c682b15521529cb9e1f87c59be99e0000000000000000000000000000000016e23f7effbb9dd34ec1f6974115e7f0d23cc4553d86e6d61a0c98f47d09510e06b3f987c5bcf4bc30e20ae9684da74e000000000000000000000000000000000f3905dd4f99cfcfa6152db53106b4d1f6e24518a822da9388d8ca1dd654a4b8315697328571691f105d1abe9aad3dae0000000000000000000000000000000006bfd10d33df9326a55b35aa6d2bc3e831d4c3b5959aaa35613156e5e19343b74e34ed2670c43ba1a45cd3d91f055c9a000000000000000000000000000000000024c53fb66f77329f70394626073ae298c6aaba115aa6af7bdaf3d2fb74a07441d46eeb398feec036727e86891db2030000000000000000000000000000000016b96c27d4342c47caafa584ec9847c79870eaebcce158535d8da95d7a847ecdc5057425fb3dd862303d1ac03162317e0000000000000000000000000000000017aa7a3f1ebbdec6abe12abea12ef50a3daabbf96a5f2ebfb517885f0b7aba1e927c682b15521529cb9e1f87c59be99e0000000000000000000000000000000016e23f7effbb9dd34ec1f6974115e7f0d23cc4553d86e6d61a0c98f47d09510e06b3f987c5bcf4bc30e20ae9684da74e000000000000000000000000000000000f3905dd4f99cfcfa6152db53106b4d1f6e24518a822da9388d8ca1dd654a4b8315697328571691f105d1abe9aad3dae0000000000000000000000000000000006bfd10d33df9326a55b35aa6d2bc3e831d4c3b5959aaa35613156e5e19343b74e34ed2670c43ba1a45cd3d91f055c9a,,,invalid point: subgroup check failed +000000000000000000000000000000000680d1062d24bb96b09d9dc9791364b5138e7c36c163589f919354101459de2fa76440616351d1d9bcd961673b106684000000000000000000000000000000000ca650f50b670befbf16d65b486e5c98dea6857862cf23d7bdd2dee2abc0855273495b047431ed03bb1c72b5403c2a4e0000000000000000000000000000000015b2586a23d909e6fd7ef6e58595817bec1389faed80db6d59db219435e7fb1b6492178a849c12bf6418341529d141330000000000000000000000000000000013ee3539c49e0c26e78f46c67b1e3993eed56c72dda43936b419e1340a3aa223ca09a2dca3ca56f2f9578b4a3f885aa00000000000000000000000000000000018e5fd242eca2314b3ade4a1e913177a499d72b539907839c5025b7de69efa24b08b3eb1e85fd05724db82b29edac344000000000000000000000000000000000478706e91d6213e4d4fd9a6c5051c88d86c7271aed7c74ef3b43e904f8ccfe4108f94660807316e8f1eabb85b734d50000000000000000000000000000000000680d1062d24bb96b09d9dc9791364b5138e7c36c163589f919354101459de2fa76440616351d1d9bcd961673b106684000000000000000000000000000000000ca650f50b670befbf16d65b486e5c98dea6857862cf23d7bdd2dee2abc0855273495b047431ed03bb1c72b5403c2a4e000000000000000000000000000000001600e44d53c706898736f5cccab585af5d04f7da91cad6f97b991b51adbc22ce27744f12c0a24804b6fc3dcc53011a59000000000000000000000000000000000751b090922b29f5c299340b12a56a77fd9c32f27995a59cd032cd09ead834d9faf5e151fbf7a0d810738de1beba5b4100000000000000000000000000000000114e35fcef45e87312a90fca21fef50c46b8a0f114df5c67ac9872ccadf858f319977138f03279ddd1edcab4602838eb000000000000000000000000000000000145c6cd707efd8d271dda5b9af26173e337a169c01b28a44f5d4ad06b125a05e867f0b1f873bbc048ea6cec06967c33,,,invalid point: subgroup check failed +000000000000000000000000000000000d6427dedfdcd624c896ca8e1ef717e212985ae23a4f830ee466c75b37ea2e994e7653b9717928f1be4583aae73f70040000000000000000000000000000000017be03dd7c9c14000aaec774812a8d8bc4aa2f1ecca9a894fd1385864b28a38091d3fb5706ee7c36086c7d50e35eb5b9000000000000000000000000000000000ce359fe7d997e151b94af2f5e167aa4834caf5a07ff056fe049d4b2c2780b35e8ecf9426444da4725a3e66de6691d960000000000000000000000000000000004c7ff987f866ff3919fb4769cc704928af09406c8f5a39e6fdcde5f4ef8a188cff406f853261bd3abd0f67c6cad1f3f000000000000000000000000000000000914584031ca57b9b0bcc35fbe76d967aee164b5eeeaa5e29c02901194fb0f88f5a249040c37ab47a715d34b2329a2b30000000000000000000000000000000007a42352ff521b8e6267a5d0dd1eadcb63db1fec68cff31bee1b2080b451ac731caf0efb86758b26b0c78df5cee8864800000000000000000000000000000000141d878adfaa6a3982cd0de93b4d64ba840a07c026ca443d6d4c2b6c36cf882e109d80df63b1626c112f9a89809788080000000000000000000000000000000005a5888d22a2f654a58d9a03c68d59cde9ab5e5356b2288033ba58fe2dbacf533e59344bdf30eed07698261d6269fc70000000000000000000000000000000000ce359fe7d997e151b94af2f5e167aa4834caf5a07ff056fe049d4b2c2780b35e8ecf9426444da4725a3e66de6691d960000000000000000000000000000000004c7ff987f866ff3919fb4769cc704928af09406c8f5a39e6fdcde5f4ef8a188cff406f853261bd3abd0f67c6cad1f3f000000000000000000000000000000000914584031ca57b9b0bcc35fbe76d967aee164b5eeeaa5e29c02901194fb0f88f5a249040c37ab47a715d34b2329a2b30000000000000000000000000000000007a42352ff521b8e6267a5d0dd1eadcb63db1fec68cff31bee1b2080b451ac731caf0efb86758b26b0c78df5cee88648,,,invalid point: subgroup check failed +000000000000000000000000000000000da87318eb51b90ca822bff1df4dbc040fb1d74129242d832e1096e813dec5d91f950f44bbb07980dcaaee3366f03a0c00000000000000000000000000000000157d5f5c8ad06af803b9362b22519a23def6dbb700db517c85a663bf1bef9665d6e81e9b02ccaac97f0940d223b83ac400000000000000000000000000000000176835484cf24c47b154b7c35877eaf5194e0e1d8f053842fb5ff8fa833dabebc887f3d34b825fe9cf2c374a2066124a000000000000000000000000000000000d31805157ecafb751536c484fd0c81664de9524a1420c969d54260dd5264bc5454a3234d1e5b090bbb8ee1066b685ee00000000000000000000000000000000196b4f58c12a7d7ac4d720cf9b6c44efdab88e06dad0023a01572cc2ba7bc0a4baf7fa45c06f04ff3d067ba191a84e6000000000000000000000000000000000038cb5d328ac9d1fab9c402b8ed9e72ccd462ca80075132f6be141457ec25a6c84a15e42b78cb64cf05ef18b003e4652000000000000000000000000000000000da87318eb51b90ca822bff1df4dbc040fb1d74129242d832e1096e813dec5d91f950f44bbb07980dcaaee3366f03a0c00000000000000000000000000000000157d5f5c8ad06af803b9362b22519a23def6dbb700db517c85a663bf1bef9665d6e81e9b02ccaac97f0940d223b83ac4000000000000000000000000000000000065c10395b93e8d04f5eb67d8e06e8649139d261ad8fbf5ff2c7d6d364c87837fa8e744834ec9341c714318195896770000000000000000000000000000000009fcc05ee486d42c3b2d480a309e4bb19563f7a27b4a10182ccb2d233ed42943ba472681d1f847249cbd11e4d79ea51f00000000000000000000000000000000053a516998d7c2b2bfa70349bf3f5e3967df827f65044634071450761ae8860e893824b1ae3f204b0ffbf8b091ddd277000000000000000000000000000000000d7cd03cc2cf97a164c7dd65b961df2866e82bf6fa979e56d0ca3f9638319772f56eab2e2134a33515dbcc137da8ec8e,,,invalid point: subgroup check failed +000000000000000000000000000000000be4cb58fd1dab8fccaa410e1c301be4fd2e7bae95cc1717d2aecaa705d717c67d7f20611dd1403d9350798642fa021d00000000000000000000000000000000075100bfbdb3f6271e5d8bcd4472580e56fd507b73aef3c3c5084d77e11a61b69bea0b85ba62885f7da36d223fca20760000000000000000000000000000000007649efeb3e0bee49b9adb13f8e5d7db1c06d7fde08a3f3082194153bf4b3615aff1450e47fae88ac93f55a389a319da0000000000000000000000000000000008334731582fb1b6125d7ee1da0124fe88f0c70a0a3f6188636976c31ba6a72beed927fe598386f328e4ae534729a57c0000000000000000000000000000000010b57d80fce5cdc90bc93b3bc7a1affadd19fb00aeec2ca9a6287bf4e40fb74616986a44f2f7d945f58501a965f37f3000000000000000000000000000000000180dcae46ee41bccd422b3cc2b34cad26f6816dd08ba51b2f12835e7439ae2d46933de28ac04bbcad68a188e7e90ee8d000000000000000000000000000000000a869358fd3d64849fd62e513db8fce1ab2618d3524acecbd04fef6b8c77703258cc557587f316cbb74bc5af5cb5551100000000000000000000000000000000059eb0e90e0910c24ee2145921ccee83707d8f89a188dcaae7d5c75b7113cacff92a2a030e67927bfec0da39f2bf65ed0000000000000000000000000000000007649efeb3e0bee49b9adb13f8e5d7db1c06d7fde08a3f3082194153bf4b3615aff1450e47fae88ac93f55a389a319da0000000000000000000000000000000008334731582fb1b6125d7ee1da0124fe88f0c70a0a3f6188636976c31ba6a72beed927fe598386f328e4ae534729a57c0000000000000000000000000000000010b57d80fce5cdc90bc93b3bc7a1affadd19fb00aeec2ca9a6287bf4e40fb74616986a44f2f7d945f58501a965f37f3000000000000000000000000000000000180dcae46ee41bccd422b3cc2b34cad26f6816dd08ba51b2f12835e7439ae2d46933de28ac04bbcad68a188e7e90ee8d,,,invalid point: subgroup check failed +0000000000000000000000000000000010eeac4d171a17d607dc544c559226db50d40193b435ce7528086eee21ec437e986c89dbe05931083768221e4bf06ede00000000000000000000000000000000140ef3af9f3dfe760e8c9dbe8d24abecfe611699ad337a97481a1553e9cabdb2e8a8cb48bc032bd02738cd26cd1388c300000000000000000000000000000000165bb8a97dd4b60ed7fa432f019f7f09a19c8e7a9b70e7370ae668d4597a3cf12c06fe062d880611e34ad9e586c193c00000000000000000000000000000000008c684f30de5c67f675e98400d854397e8cc6a139dca7e9ee179309a9617ce0ae034bfbd0faba7a2f9e7ee39de8770c900000000000000000000000000000000030e524c87a658e44df117fa0a877afcf8a4907979c932921a631a209dd58ddcaf693c7321c537e7e2a5adafe5761fa0000000000000000000000000000000000cd44b77f2d92706b3db5e374f13f6f12e3b030c6341d31e1c55d627e6af06a1b64498e590dbff08ee6354902263ff260000000000000000000000000000000010eeac4d171a17d607dc544c559226db50d40193b435ce7528086eee21ec437e986c89dbe05931083768221e4bf06ede00000000000000000000000000000000140ef3af9f3dfe760e8c9dbe8d24abecfe611699ad337a97481a1553e9cabdb2e8a8cb48bc032bd02738cd26cd1388c300000000000000000000000000000000062e54c21986cff68cd57903594d0e9f2f8348191aea6abc19e8b895e6ca59bf2eb731d10e426ad98726c51adc2d53b4000000000000000000000000000000000594d2a92e0b979c95daf6f78216be8c33ef23f0bebcda479326a1d63ec8234b283ee29fff7fccd441ab4ea6e17371a40000000000000000000000000000000019179f35ebf4ad9f8f9dd577b2db1ed58a988954b4bf4232301b713809be318e746f5b2bd663a21de755125f414bae9700000000000000000000000000000000172d8ad43611623865bff86df2bea58a02b7d6a2c41b85b98d6a2bd4f5e1bef25c978ac0bbb7f4746297c6e0e40b4e23,,,invalid point: subgroup check failed +00000000000000000000000000000000054dce4bc0b703b8957539c594d8d443fec161c3cb2f806f8eaa8158a665d84cfd551c0b7c0a08bede0cbeb780a6ca5b000000000000000000000000000000000cb3d3f9bbda28f8fe597907a76a8250567f4e481b6e9409e03ead60eab77153cf8f25ffcebb243a310094740cc2e2a9000000000000000000000000000000000a71dc159647864abd64655bf5ef956f21ad55529bdb49ac910ef628cc62a3d43b2b9ee26180232fa29f4b0e8371286b000000000000000000000000000000000c72d0fbe0a7604c9fab394b285ebf1c322c95013651bd21f88865e269eafa65e135ff90f5b249a794cef4e6cfcb56270000000000000000000000000000000019ac0043071372ba077bc8d91a4ac80fb5b8c8131981c4dfc698ba9ae50b506f93149eb73e4bc4f4ded94d6824473817000000000000000000000000000000000113368be2a531d2958d887c046fc26155436fc6a1ef44fdf16447163b7bc48fbb499506d8d5c8041d21116c4f22e685000000000000000000000000000000000b7244995b7819857f716288dc59eee9ba5ac7bfe010937ea0b67ee71388a3792e5b7feb6890a436db4f1b26df18b38c0000000000000000000000000000000009a0b73360bc0ca3b632c0116f21ffdaecf37e4d6c904c98d6225a08d7caadf5024ad6b457cf31b924118ea147ff10fb000000000000000000000000000000000a71dc159647864abd64655bf5ef956f21ad55529bdb49ac910ef628cc62a3d43b2b9ee26180232fa29f4b0e8371286b000000000000000000000000000000000c72d0fbe0a7604c9fab394b285ebf1c322c95013651bd21f88865e269eafa65e135ff90f5b249a794cef4e6cfcb56270000000000000000000000000000000019ac0043071372ba077bc8d91a4ac80fb5b8c8131981c4dfc698ba9ae50b506f93149eb73e4bc4f4ded94d6824473817000000000000000000000000000000000113368be2a531d2958d887c046fc26155436fc6a1ef44fdf16447163b7bc48fbb499506d8d5c8041d21116c4f22e685,,,invalid point: subgroup check failed +000000000000000000000000000000000cc6bbb9914ae46b57eaaa8d3d22274a355bd7488e5b537169c995ef2bee187ab66497423f14fdcd01373a609981b3ea000000000000000000000000000000000037d9461da369d186cd812a9ade7690b2b8b54ae386b7342a69af832ff4f51e5db9baa3c6b4a65d798a1aeb41d8787d0000000000000000000000000000000013a6e129d4dd4aa93cff5489ee879763e2a2231939e609d2d72f07e630b37d09f3057a36fd5cdfc9c81675c996f8ba0f000000000000000000000000000000000e8d7ad082e8f9a718fc2ea712853ed9ab4e8b1a8ca9988f77c70fc759f1fe2d4bd73696e539f130be13b2862efbdf770000000000000000000000000000000009897223b041568c9ef2884baa28477241e525de05f2c2f15441854a0e8660786a0c7b85a6d9d1074fed2b44d75efedb0000000000000000000000000000000007b52401891bd8003af4b07b04b15b79bd05fcb54739491352d295b5545ddba34da0b0aff36a3e7e4b433011be580174000000000000000000000000000000000cc6bbb9914ae46b57eaaa8d3d22274a355bd7488e5b537169c995ef2bee187ab66497423f14fdcd01373a609981b3ea000000000000000000000000000000000037d9461da369d186cd812a9ade7690b2b8b54ae386b7342a69af832ff4f51e5db9baa3c6b4a65d798a1aeb41d8787d000000000000000000000000000000000cca0d111237ec521889baa4987714c6bb539399b058b6635fd043821377fd6cfdb74923610c8235afe2be99188cbe820000000000000000000000000000000007fa2b99221675b38204c2eea94b2378b1d711ea5ba4f41d35c37f77ee2466f22c88725da9fcdabb6153292b7cd9aa1e000000000000000000000000000000001069adc30f99e0ddfd39775cee5ddd786fcc077cbaa8737f7031745d02f06168fd5d3c4936704d15955bcfa08b0925180000000000000000000000000000000003b388a8d9baabf0bf708e2fe28eacd7f704bc588185adbcea8e0a2bb8f9bc9b045918d97bb27b2033c6722b6e6692de,,,invalid point: subgroup check failed +000000000000000000000000000000001055ab14a2407bf095a954cf1c926f2c520dda187c44522a7e924e38543e5b87e7642227821a4e0b3ab0289b32161a060000000000000000000000000000000018aa24044066526fa9ed980ae7b3110a4fde7ba0a5fd289803fa5175d30766f01a266917f821169c7ec31fd46e1a14ac000000000000000000000000000000000b6e16f2a6cb821abc43c447da207cc3013f2f750c844f42f0fdf47160a38501bf502073bbeb565122bb3de61b3a5ab800000000000000000000000000000000040f5f3aab5d416e9a084fa298814f894ba599315fe10af20f836e624680582413b4a54623cda8ae2663ee094e4db775000000000000000000000000000000000d32ac715a094813c7b46ce2e932365bfd62ec5e584e047b0c56ed6eca3c58268ae01be31b833be7ba5c2588ebb9859d000000000000000000000000000000000850b9044f129e51658a02cfa49d40a2b09239823cba4d8fe423fa1b4815750811daf745e7e02b317a7318aad0734ddc000000000000000000000000000000000765a76c441227592ba30d6b1d3d9898467352398efc0e8416e0be8c9f87bcac8d5eaa5d7b2a8adfae8303909bef28da00000000000000000000000000000000107c0eff2fa09afb743c294408408451e3039da8db8c0beef32f07864223817075fa557a89244cdc293d631311773947000000000000000000000000000000000b6e16f2a6cb821abc43c447da207cc3013f2f750c844f42f0fdf47160a38501bf502073bbeb565122bb3de61b3a5ab800000000000000000000000000000000040f5f3aab5d416e9a084fa298814f894ba599315fe10af20f836e624680582413b4a54623cda8ae2663ee094e4db775000000000000000000000000000000000d32ac715a094813c7b46ce2e932365bfd62ec5e584e047b0c56ed6eca3c58268ae01be31b833be7ba5c2588ebb9859d000000000000000000000000000000000850b9044f129e51658a02cfa49d40a2b09239823cba4d8fe423fa1b4815750811daf745e7e02b317a7318aad0734ddc,,,invalid point: subgroup check failed +00000000000000000000000000000000182197c7a0cefeb530f51c664dcf8a74f9f70165ffc416ba454e9c356bade393e30d037347b1a020dcefb09ee65590a6000000000000000000000000000000001030be8d38736ac8e555d1681b14f73f2ca58faebeaff17b6006bf7876e733642d229075c8dfb0a9ba4e832e384aeb8b0000000000000000000000000000000019094370a6f19e946f587e9b117332ce5ad91860cc103015e94c6aac6d2c00f3e71471c241ea1d425e391707b27b851d0000000000000000000000000000000009ad1d1312011907676574a7867ac02059d9b0e29dab709f6ffc1b75b3598658427f10ab52d1129417ef42c30998f55f0000000000000000000000000000000003103495c759d8901898acd98679d92e048ca41244782045a6d9419b3ff87c351f97a333899fb445b8620099f7b9cce100000000000000000000000000000000051f7aaac39348f70109ae7a016026ea52c03e4ec90d03ce05aeea74f66bbf82e17be35cc45f492f50246f0de8dc68c500000000000000000000000000000000182197c7a0cefeb530f51c664dcf8a74f9f70165ffc416ba454e9c356bade393e30d037347b1a020dcefb09ee65590a6000000000000000000000000000000001030be8d38736ac8e555d1681b14f73f2ca58faebeaff17b6006bf7876e733642d229075c8dfb0a9ba4e832e384aeb8b0000000000000000000000000000000014229a1108fcd75131295caee98f8e4e075cf4bb5e169024e07a533f65316cb5d19193d3cfee8b2216663829be6d374600000000000000000000000000000000156cdf98f622a393d920b200e6d9efede7413137eceb79d66f85a18e33ef6946a515737c58e198f877fa39458877b99300000000000000000000000000000000006e5cee8e0f47c0ee4a574fcca6e150901a7de58f3f2eb3480f1ff8c14effa4bb9d00837501f22f6cc465e4026c9d7100000000000000000000000000000000124b6c0836bdd10657a3e1f978b48b9221b1cfc2767b6b99319fb69c5fdcc1b133f1c2fd093c62d6d1e398f32c4e8b71,,,invalid point: subgroup check failed +0000000000000000000000000000000019c9b755000f9f1b6ff22885f45bc1f5f65d080ecc129d29e1cd60aa14fd20646643be51d2fb3417cbbd39361bc72b62000000000000000000000000000000001741fd3c4a7e883094c0e84d851f45cc1b81af5bcace67dfddd3f19e8817697a386d1965a4e17c60b00ecbff84779b97000000000000000000000000000000000e87ea967f6c4dd7135efcd9a59368a2d19dd1385aefa34d7d9bd7f5094d779a7150667dcec463c9ab63d2ffc8ee4f6d000000000000000000000000000000000a3a010f176efe1a7bdb77dedf6b6271c845d662dca5062ebbac4e9c3b8946db0adfff37a6faa3196a99fb3ef05f09c5000000000000000000000000000000000350ad257d47f270c4340e3cb124ce961316573dea14c9584d20221d922a43c2e94324ec14bd1e4a1eb955861783a8f100000000000000000000000000000000070edff58ac1f8c13f62327cf0adbd748285fcd84ed7be23dfc82a0ae32f8c8f5f6b0679f795874cb0082718fb07a1ca000000000000000000000000000000000d5be6f99bb9a2379d1e542ece048164fa5d14e0c6c459180717b3da46e8446e9def576635ac1124e1390196fe97f39e000000000000000000000000000000001482d8339b402e3bffe61aaa298c8bae4286f1fbfc877a66e21cfe239bbee383d701d95a6c2b8193d67df5a551bb7aba000000000000000000000000000000000e87ea967f6c4dd7135efcd9a59368a2d19dd1385aefa34d7d9bd7f5094d779a7150667dcec463c9ab63d2ffc8ee4f6d000000000000000000000000000000000a3a010f176efe1a7bdb77dedf6b6271c845d662dca5062ebbac4e9c3b8946db0adfff37a6faa3196a99fb3ef05f09c5000000000000000000000000000000000350ad257d47f270c4340e3cb124ce961316573dea14c9584d20221d922a43c2e94324ec14bd1e4a1eb955861783a8f100000000000000000000000000000000070edff58ac1f8c13f62327cf0adbd748285fcd84ed7be23dfc82a0ae32f8c8f5f6b0679f795874cb0082718fb07a1ca,,,invalid point: subgroup check failed +00000000000000000000000000000000104c749e3f7b40bf6df55f9414bd146ac306b46a6210ae4ceff6fe2a58220ddbc69208ada5f692120dcfce39b1e43fb5000000000000000000000000000000000663a0e62ea68ac23a6e27958baabbb5deca3905aa138a54d6198724e5fdf0abb9288cdb52cf1d44e93f06571a654f75000000000000000000000000000000001116ecf077865395ea40fa9cf05753b87ac29ccf9ecfebfa1031fef0defa1d77634c2177647f069532e00f7fb657577f0000000000000000000000000000000005c7960dd84874fc00ab199d00e8bf1ea035a7eec443328bf2bc28d0006979f5032763a4d33f031e698895e03b27314f0000000000000000000000000000000004e00e32a506bff708c51fcc4101c8ebe7f1695d6a4606b6648b04710fdae313b99219963921451d0fc78dd59970ea8b0000000000000000000000000000000019dc4b721ec4a4303809c47da68099fc10706eb08cd4f6f91641ac680661e93a91e2067a84c12f9f55f84e27ed76ae2700000000000000000000000000000000104c749e3f7b40bf6df55f9414bd146ac306b46a6210ae4ceff6fe2a58220ddbc69208ada5f692120dcfce39b1e43fb5000000000000000000000000000000000663a0e62ea68ac23a6e27958baabbb5deca3905aa138a54d6198724e5fdf0abb9288cdb52cf1d44e93f06571a654f75000000000000000000000000000000000da3982205a45000b16e1ec7a48effad4ae1affd4acd8bd13fcdf2bf05b6845ebda9be2df6d6325389d711111641aae500000000000000000000000000000000150c1bb67fe3d88dbfb452d5b4582d9a1c350ac01cc209740be70d63e266c926cc6b0171eaea913b7456daf595b83ce900000000000000000000000000000000138d0bbb52fb6248731c8fbb6818d1379fc5c2549a5a0d663a56c41b3b2e8c7c4fd77830c455a40e59aaa65124ebcee50000000000000000000000000000000015e6b4628b75f9786ee26e41cb8b86c6e6a97ac18aa7662b39e9c96e169a3192b64b863d7f9739237255fea6cab12fcb,,,invalid point: subgroup check failed +000000000000000000000000000000001638395680014bc04e2ca42bf864dde47a0d708ae81ba4a6aa2e2476837750aaf5f9f6e41a5a23df432ae92fd221737e0000000000000000000000000000000009419792539e0ae995b8d853d9ef513bc54766475e37bb3dc2dae3d7fb9b02b0eb2327f24a751d2de344b9f5131ef23700000000000000000000000000000000197ff997d6c5efa3d7de8e16f26082bf13a2401d6df5f5c33c6614c36105f347e40216c907bdad9c1df6ebbd44f41c3f000000000000000000000000000000000f27a0bf92329730d776a83583177993b2b354a212a9c004f9f8892a750c477b8d1e68c13127f03b1629bc8392d06f5b0000000000000000000000000000000011b239cc6914a321385d907527b85713a0d842f5be80752f4c5758586dc1de944b6e4578bbe324f16838115e9c866bca0000000000000000000000000000000000cf93c5b48cd9de51ccaa45124217cabf466d07d6fdf4a7bb810443339ec4af5b74931bd07eb9fd31c284c05f3f539e0000000000000000000000000000000010b91e082484fff0da28b06f06e02c699d741f2ef788250e3fbf2ba8fc1d7d78a1ca63b76dadfb71015fbefc0eb70eef000000000000000000000000000000000c2fe842c659c875af0f2cb1a978ac9058981cc6c76ff057f326162d4322805974505e6a35499bd0c58b5d6db3aa222900000000000000000000000000000000197ff997d6c5efa3d7de8e16f26082bf13a2401d6df5f5c33c6614c36105f347e40216c907bdad9c1df6ebbd44f41c3f000000000000000000000000000000000f27a0bf92329730d776a83583177993b2b354a212a9c004f9f8892a750c477b8d1e68c13127f03b1629bc8392d06f5b0000000000000000000000000000000011b239cc6914a321385d907527b85713a0d842f5be80752f4c5758586dc1de944b6e4578bbe324f16838115e9c866bca0000000000000000000000000000000000cf93c5b48cd9de51ccaa45124217cabf466d07d6fdf4a7bb810443339ec4af5b74931bd07eb9fd31c284c05f3f539e,,,invalid point: subgroup check failed +000000000000000000000000000000000ac27e4d19924f4bfe30432554f25d456cdb4724c106409e46612e1c91e8cf5fc2cdcd6b6fd6bfe040e910795441befd0000000000000000000000000000000007e9227d849e467fbf5fadcc016dedcc04f4c66f23464195782746fde628a107d77ca5b5c9bcc8bbb14fc14208fa5de300000000000000000000000000000000094860f23d182a14d1a64d9693ce9309ef4e775f24aa3807571c9b8281fc0d6157cdb5a00b34b66be1849994c264c4b000000000000000000000000000000000062b4a3ef95b2522c894c0b492673c3800fdf8645998a899e27dc3a23c0530d96b558d1c6364477943726740cdbc88f0000000000000000000000000000000000daa2f2f2c1020339666be4b1c1e12f8d44625a9508bc5590314789d02fe0e2e676d8d240bff89b669b9290fe1d0f8a8000000000000000000000000000000000f7a710af0b04d20b7d515f2627b572a5a17a13975ee81bcb8fd90600d5fb2f161a9ab3635bd16649c95385bcd604f5f000000000000000000000000000000000ac27e4d19924f4bfe30432554f25d456cdb4724c106409e46612e1c91e8cf5fc2cdcd6b6fd6bfe040e910795441befd0000000000000000000000000000000007e9227d849e467fbf5fadcc016dedcc04f4c66f23464195782746fde628a107d77ca5b5c9bcc8bbb14fc14208fa5de300000000000000000000000000000000055d8c63d7c04bf5db81465f2fc372b798d77ab0ecf795bf57debeafbbc8998f91f71b0dbcc440e70df3d5a1cb682ad8000000000000000000000000000000001497d9519e835c0644d30ae38ee66faf39b1939320c2e0a45b7862508ea3bd7eaa3a754a679bff81af26c6aa141adae00000000000000000000000000000000001be6ce3109582f31e02051ef0e4d266f310048e04a7ac8dc5e04d2b7f2766440f1ae63e5da6dbf831b21fd7cb9cc0a80000000000000000000000000000000003d4acbd20192ee2fc55acd5c90cf5b897cb42a4c0a777970d11955cdf11d5ab22444ac9cfb666c3e509ce832cef219f,,,invalid point: subgroup check failed +0000000000000000000000000000000015866ee89fe4f68e45155fb98124e8453e1ca25347d84f70ebfc32cf76c5d48e3a3e5ccfb1b505db7b493cfcc73ef92d0000000000000000000000000000000013deaec2d2482c457050256d157968ea3d15f9b61b4573353e83daf824b28289343bcbc3bf97ecec9d65ad08804861440000000000000000000000000000000013467fcb424ae0eb012228fd2083d92e6d242427670ca6e2cc1166166edee5a94b78d2c2f8715a996bf2b4e5112e49f0000000000000000000000000000000000c23c01e0061b0fc7579723e072b12e86c8f12f4c2a039bdc5b1f3384441ccefec187e0380efae31a819d92fd6462ce80000000000000000000000000000000014f9a055a5e468955f6d7485fdffed2b33174777f99d9d0af160b0a083912b05da45f35c73053120f61525c173a24e59000000000000000000000000000000000cfdcb6adf8f04fac2cba8f322339fb0614f46b77b0d91f0ec167eca06fcce080ee0e63023fb94712dbe7591843b6fe1000000000000000000000000000000000d90bd38049f2a8de869d8a748c9ff3120542f38fca6e8d5fbbff86baaabf0f19dbf449cf23c043dfea322d99837f7110000000000000000000000000000000000ede89c8bb8299726ec685765f10167c5b844e427d3c15da6ec2c1d97de174819d52caa96d5cc938e93dd09bbd1e0d80000000000000000000000000000000013467fcb424ae0eb012228fd2083d92e6d242427670ca6e2cc1166166edee5a94b78d2c2f8715a996bf2b4e5112e49f0000000000000000000000000000000000c23c01e0061b0fc7579723e072b12e86c8f12f4c2a039bdc5b1f3384441ccefec187e0380efae31a819d92fd6462ce80000000000000000000000000000000014f9a055a5e468955f6d7485fdffed2b33174777f99d9d0af160b0a083912b05da45f35c73053120f61525c173a24e59000000000000000000000000000000000cfdcb6adf8f04fac2cba8f322339fb0614f46b77b0d91f0ec167eca06fcce080ee0e63023fb94712dbe7591843b6fe1,,,invalid point: subgroup check failed +00000000000000000000000000000000119c7c85e5efaf08b91dc496758b962098cc0eb60f4a770bfafa91809ae4a95b43f96b69c8ddc897701487f22d2e049c0000000000000000000000000000000007467ac896ae9f7d2cfdfbab082c89d3c17a6dfdf1f69b4b38fe6d5ede6848a45e8b0d728eb8c68752ec59c8e0504dcd000000000000000000000000000000001047ce33c70d58e3191a558ce2fd95c20bb62abae7d924cec8a4067fb33e8dacd796d65c049be7bacdb969f61db5b26500000000000000000000000000000000096e7081a7b2377331f86d8418bd577cd5cc1d45e60d39b519ff2b3a50ddb2d5f6dccc0066167f42498a3d29ef5ce2e30000000000000000000000000000000011159939a04c129b007f2aa2d59ae006e8d89c41dd465cba551737d06d3fb2c1161aee98e86cb8c0321f42e514316030000000000000000000000000000000000c25d9cdc8dbeec82c47d5ef12f21a7e58a8eddc1e738e635ba04f2ebe12440090f432c0d1518217a5531266441f1c2500000000000000000000000000000000119c7c85e5efaf08b91dc496758b962098cc0eb60f4a770bfafa91809ae4a95b43f96b69c8ddc897701487f22d2e049c0000000000000000000000000000000007467ac896ae9f7d2cfdfbab082c89d3c17a6dfdf1f69b4b38fe6d5ede6848a45e8b0d728eb8c68752ec59c8e0504dcd0000000000000000000000000000000013a11383f2d3a3c28e3ea750fece5790e37f66b306ecca417c83840bed70c034e4b82b0850f719fb0b3b203a25dffae40000000000000000000000000000000017d067d9cfbbcf605c5da3532b2eda5900d71340508f08c05d0051772e65b0f85b9efe5b6d63a7b64b25ede8df7f25c9000000000000000000000000000000000e44847884ee8eacd5417e042e8299af8313ce177ecdd034a91d3bdd441437510808d44e328e810c46bd851ceb6085dc0000000000000000000000000000000013288506fd52bf37aaa975b533f1182a824b79d2d876ad6ff705efeec7f732bed99d2da8f31c00a2db4d97c4118bcb88,,,invalid point: subgroup check failed +000000000000000000000000000000000b7aa89ed719e2af5ad32ca923f1d2d52d767f6bd33d8967d2619b54472c8881ad06441b2595931d734a0fd10ccd7f190000000000000000000000000000000014bd6118d65e19e4cb79af164f523f1c80b0f0a0f0063cf1d28e11ae7987381c0b9707e43754b75f36ca8523bc5f7da600000000000000000000000000000000056a29b523b0cf85ab04b0a496e078dba5529cb9699e567ca42f9ee3e3f07b61ae29b0ce17cad23131375f624a366157000000000000000000000000000000000acb91d1f057c7aec1f7561614a95f8db2252cc879bbc2595a5f607d8b0ecd6e6e3ec19849eacfca62d870b049ce84910000000000000000000000000000000010d9459e07178af8e125c2f66de699cfafb5f87a63454e24d0ed88b6c804a9ff204f146ecf4d6db62234ace0a944acb20000000000000000000000000000000007256a68e23b43a3b6475b3cf209ec108bac13631ca448cc860672c65c1760a8299fe941ed5bcbbbcf63a683e86806ae0000000000000000000000000000000005d4453da747eaef90007eb8ebf6088e8617dad362f2a95638fca7312bc5cdd8200f32b17a0b483052e6784d286c2cb80000000000000000000000000000000012f0e56ed3e3f628a13493d0ade2321310cf62927b40887202042981fc9a81d6cc69be130346b7bc244a2119b2632a5600000000000000000000000000000000056a29b523b0cf85ab04b0a496e078dba5529cb9699e567ca42f9ee3e3f07b61ae29b0ce17cad23131375f624a366157000000000000000000000000000000000acb91d1f057c7aec1f7561614a95f8db2252cc879bbc2595a5f607d8b0ecd6e6e3ec19849eacfca62d870b049ce84910000000000000000000000000000000010d9459e07178af8e125c2f66de699cfafb5f87a63454e24d0ed88b6c804a9ff204f146ecf4d6db62234ace0a944acb20000000000000000000000000000000007256a68e23b43a3b6475b3cf209ec108bac13631ca448cc860672c65c1760a8299fe941ed5bcbbbcf63a683e86806ae,,,invalid point: subgroup check failed +0000000000000000000000000000000015827c619b2a73a750f6469160ff323c15adaf55e893933a5c2e5c2f0df8bc426421408773a3e8cb8b1695973f7c0b760000000000000000000000000000000000af4a7d29f10cd080d9989b341fc030a5dd51512f776fb1da7a46d542c2a6a2ad7c1309af30423b717825fd5dc0356300000000000000000000000000000000198d09947dc088c1d33d776d64765766b508764f12a28fe0119277d6e171af7c9ff83f6823558e8b1a4284857663afb700000000000000000000000000000000130d5e5315f8df8d0142d06bad7a51b08e5b3c2d49b84c9d6b177b9bb628a852ff65c1a93982dcb1b31a2dc0941904750000000000000000000000000000000018cb011868591c6b44b7ce49f82470aa6461a737173e1d88d249c0e83fc6e4e6a15f8397e515efe7dc7302ccc2e369ae0000000000000000000000000000000004de1c5539b2ef536a66c8f3d7cd49ed948c081c08cba8826d2ccdf9d159b931ea10eeb8b3f465dce0143b179059169f0000000000000000000000000000000015827c619b2a73a750f6469160ff323c15adaf55e893933a5c2e5c2f0df8bc426421408773a3e8cb8b1695973f7c0b760000000000000000000000000000000000af4a7d29f10cd080d9989b341fc030a5dd51512f776fb1da7a46d542c2a6a2ad7c1309af30423b717825fd5dc035630000000000000000000000000000000004286a9efe902158cc624080ebc5fd9b5e0e6e31554c745d5f0c34ef1de2a487f77d405577559dac59babc731ca779110000000000000000000000000000000009c09c9c9e2c75bc6a81357c03b339668c7d8cc8a3f65790ad53b62f4c21acbf64fa66fdd75dc24c05214b712ad7fdb60000000000000000000000000000000018bf7e4fe271fe195377639c5743c1ef3eccb09c86d64d2a4dad2d7d4ff0feb46252d749f82a27141dc0007649b032bb0000000000000000000000000000000018113c4584b81eb814a9f1ebd041062d0db8bec46c2c1ffb7f863bdd2fc3fac470b01c32b6f2453ab048eefe362aa1b8,,,invalid point: subgroup check failed +000000000000000000000000000000000c3f4ece90cf7d380efd3f81e66110a5923bd604422fca0fac2a16fb9a8d8b34cc1fb86a15009e8cc2c0d9fd8fbc0fac0000000000000000000000000000000012ea55d042ae590abd4c2687e0f152384d37665ddf26787d49bc9f6d40a579bcb23521c59ce91c418b9f4801375892aa00000000000000000000000000000000098af17ffd4d28bad76ce1ee669e7cdac1eec9facc260440636be88618302ab5a0826141b4fc914a389816d04597826a0000000000000000000000000000000011bef78afedf5c62daee5e86386c45826a524352fea40f68b07b7794df8eced4eaf0fb55b6990b4fb417ecc597b61e48000000000000000000000000000000000d64f0a4df4f858defde17b31476045c3dea78de4ec8082822c1699c0b9619464c75f0e57ebd12ad9e4e2e6b291b538c00000000000000000000000000000000031f12dc8a9c5445d575f99e2a4b4217ba5c0be58ac00977236440ab0ac7e2c8dab72a64464e4480aab7eaf1d629c7e700000000000000000000000000000000033f3c31337bc48622d27a9a3224a2acdb5c538a59b497a4a85840c81cff667ed0a0e4e3f4bb23a9ae53c1e79ea54cbb000000000000000000000000000000000cf0dc22af4530260cde26aa0eedc83a0ec3ae87d024e6907f3d22070e1054b3d4f24d5ace7218ed44763af6ec3f25ee00000000000000000000000000000000098af17ffd4d28bad76ce1ee669e7cdac1eec9facc260440636be88618302ab5a0826141b4fc914a389816d04597826a0000000000000000000000000000000011bef78afedf5c62daee5e86386c45826a524352fea40f68b07b7794df8eced4eaf0fb55b6990b4fb417ecc597b61e48000000000000000000000000000000000d64f0a4df4f858defde17b31476045c3dea78de4ec8082822c1699c0b9619464c75f0e57ebd12ad9e4e2e6b291b538c00000000000000000000000000000000031f12dc8a9c5445d575f99e2a4b4217ba5c0be58ac00977236440ab0ac7e2c8dab72a64464e4480aab7eaf1d629c7e7,,,invalid point: subgroup check failed +000000000000000000000000000000001679e0889aac1501b67c4ccee84942e05b1720f48b6390ab82ff76c0ab95defb79b4371770f2e07a9e7ee8de4d76b43500000000000000000000000000000000162fa6099ca3e5e8e27dea6ea0d4d13c5c150d281fd6beb2079ddf2d714bf9458184100c88440109d7526812ee0f56e000000000000000000000000000000000087bda5b07cf72c2b350e663670f094c352097330b307cbe2f7b4224841b6eb23c36ba62d4ee591e5ca68383ec0256f6000000000000000000000000000000001163d4985e0f25d36a1f8dd97b61413b0015a966a88d98eddb2ea2d5eabdd83a44fb7e37cee90cc50df2f95dbfa97979000000000000000000000000000000001652067ee82320191cc5b188e61ee2d1b94c781e8e5798c89224920ed1d12a2cb41066f69cdeffe8a4d5e3aa1be4c83300000000000000000000000000000000139cd806423ee99d913e8b0e5ddfb6b1b62478254fe39d6836fbc632de9435e1464a556b1f9466efebe93636dfde7749000000000000000000000000000000001679e0889aac1501b67c4ccee84942e05b1720f48b6390ab82ff76c0ab95defb79b4371770f2e07a9e7ee8de4d76b43500000000000000000000000000000000162fa6099ca3e5e8e27dea6ea0d4d13c5c150d281fd6beb2079ddf2d714bf9458184100c88440109d7526812ee0f56e00000000000000000000000000000000017028dd744482e290c523ae5944c8a149afd9d344fea7ac0171ee3a5ff0c2add53ab20c477a584bf61e007af5840eb4a000000000000000000000000000000000863382ee8a43f02396030768905e44c8f9504b7315b00e379b92060e4f01e1b4e0f837b24cb42354e7211419a5516480000000000000000000000000000000007e2af64687f7d581d5a2bbdf225d1ab3dbea326ec89c08852807696c8d13cc907ef4289bf5ef9826c1fb27673b28bfb000000000000000000000000000000000ffc910160c8a0b826600fcdbec027e7d4874c9774324bddd2dd4428eb81c22618a49378502917ad9fcd96bdb1371285,,,invalid point: subgroup check failed +000000000000000000000000000000001902b8c58eae3ca8d2261a637902587c2c0e75d32abc894967b6837ea34252e4558966f931789ccd76c1bbe3e092b180000000000000000000000000000000000dd20f71f5054c79d5e357f69f8d7345b5a036241774a72743271f2dc8f6e8c29a3babc2b65ed8193cc0636fb2e86f740000000000000000000000000000000003e06e2dcfbd695e9bda0baee1276ceab637fd1fbe2d2d6458c923c35b00edc7edf4f9e797aea59ff8cfceada0615a02000000000000000000000000000000000a04a2ed5e42fac7f064b43d64151a6c517ecf22dbc7563a3e9f35f555a9992fe45cf6a728ba94607df7c96f7e0a334b00000000000000000000000000000000090fac97f9f524168bc930d26ea1627ceaf187398d6bfc5a019c8467d75cd31a41c7eb9fda35fc85bd92b4cfca92dbff000000000000000000000000000000000f37b91dc935c28668c27d38328a511148c1739b65f2816dc53e42a8f059c9b2be7417a6f97c9a2597b1a0f06b7afc65000000000000000000000000000000001687dbd36c7f96f8be47f08bb75bc72f91e63e26d0157a9a9c8f531f3e73bfbd9870fe9abd0a7a3fe73b997e48d0ffb8000000000000000000000000000000000183ba882bdaf1dc850cb4e98158895effda1734fa64810cb15640e6cc027bd006e5c1a088cc2c65e8af29b64fe41d4c0000000000000000000000000000000003e06e2dcfbd695e9bda0baee1276ceab637fd1fbe2d2d6458c923c35b00edc7edf4f9e797aea59ff8cfceada0615a02000000000000000000000000000000000a04a2ed5e42fac7f064b43d64151a6c517ecf22dbc7563a3e9f35f555a9992fe45cf6a728ba94607df7c96f7e0a334b00000000000000000000000000000000090fac97f9f524168bc930d26ea1627ceaf187398d6bfc5a019c8467d75cd31a41c7eb9fda35fc85bd92b4cfca92dbff000000000000000000000000000000000f37b91dc935c28668c27d38328a511148c1739b65f2816dc53e42a8f059c9b2be7417a6f97c9a2597b1a0f06b7afc65,,,invalid point: subgroup check failed +0000000000000000000000000000000010f38e6e4f562be50152c1d10eee8f8990cb8f884035bdce111e178d1286afe2f2f02c3a36858ae2ce902d6a2872ff1000000000000000000000000000000000141dc64999baf42240b933f30ee895188b561b880f90b5f1ca8df0ac75be7d95bc15d55e321720c172171e9c4c59e800000000000000000000000000000000000548814c4b6d72cfb817b49b3141302be7d7b378e50ff9f7d66e31cd04e1f024bba334110817990264d26cbcff7170510000000000000000000000000000000011f9d186fab00b9ede155a82ec5a5e587a1c6091005c4c6e90672d15c434953426440799c5ede15a7976f18bf345595a0000000000000000000000000000000018d480ece4609a56220d4db100b68ca06ee4271b84e1a81112fbb0616cb34d2b0ec974de31f7d6957b186dbd8a8f8ad3000000000000000000000000000000000c3c1b79130f73d516c1bbe38c572be2616991b523a9370c98df9313be9f5015c3e8d51947201c6b27e8cb9c7291bd660000000000000000000000000000000010f38e6e4f562be50152c1d10eee8f8990cb8f884035bdce111e178d1286afe2f2f02c3a36858ae2ce902d6a2872ff1000000000000000000000000000000000141dc64999baf42240b933f30ee895188b561b880f90b5f1ca8df0ac75be7d95bc15d55e321720c172171e9c4c59e8000000000000000000000000000000000006d89d908b733aba090432e795c564d1badd5b8529fc53507c4850e3d97978062b38790ef45562f3d4978491b5ae893b0000000000000000000000000000000003f7fc037328c13d13fdf4b2c251ce10c41ac5d042122f0e4e4f5a71cc9e1463f62d96aed44123d0e612e5296b53fdb80000000000000000000000000000000000aa2e027a929f7637f437b333a795a2e8d1a92cda31feb5a72fa1c66fadd23813177f9360204b0512d8f460b5bb161d00000000000000000000000000000000139a2a989d462e1793caba250c2ba9c46f31b3a06f43a0f4cbaa021b5d52d254f18d9517ddf3c21780f2a2c59533c5db,,,invalid point: subgroup check failed +0000000000000000000000000000000003ac8ec0fbf8c4a6774d8567a45b033a4a622d88d8b2025eecd746d084617b67342cb1030068ef6dceea78cf97210b6a00000000000000000000000000000000090b3a144dd409afa163ef513af313e545330a66c33d45b32d61cbdeccc66f78062060a2bfab2a88ec0cc47ec3525f19000000000000000000000000000000000d542ceffc583a6022306479b2365171c3610b7f615619802caf2f81d78f2b5166114485dfaacfdfc27c6450f8c344550000000000000000000000000000000010f5a12712658a5359c0a310f6d833c0b4623c51da6c035dfddcc4c201ccb27ac0a534da459a82488c32e1d4ced9b8af000000000000000000000000000000001878dfc18d1744c6f837b36436b82cd9c270916e5206f709e7eb30fcbd4157f65639103f367f1af2684a51d93e3dc7fb000000000000000000000000000000000ca3a300efdfd9812b6213a848d7a2f865d3fbe8c73527997f18460485626921063bd5b7842b8a47ccadcebb5539a54b0000000000000000000000000000000009aafc73979c000236c08e089828880f54645b5ff4c1dcfea0ff41ffe8e3fce8ba0dbcebf0d4205bb6616a737b6d3542000000000000000000000000000000001399a2072604d50f92ee186924ce32c4e887803dc258b7495aa2f3d2187571045db7f360d2614b198f83bc8024b06559000000000000000000000000000000000d542ceffc583a6022306479b2365171c3610b7f615619802caf2f81d78f2b5166114485dfaacfdfc27c6450f8c344550000000000000000000000000000000010f5a12712658a5359c0a310f6d833c0b4623c51da6c035dfddcc4c201ccb27ac0a534da459a82488c32e1d4ced9b8af000000000000000000000000000000001878dfc18d1744c6f837b36436b82cd9c270916e5206f709e7eb30fcbd4157f65639103f367f1af2684a51d93e3dc7fb000000000000000000000000000000000ca3a300efdfd9812b6213a848d7a2f865d3fbe8c73527997f18460485626921063bd5b7842b8a47ccadcebb5539a54b,,,invalid point: subgroup check failed +0000000000000000000000000000000013a366c2748305c4ca702c053ddcf15df4a4a7858cda813d001f59bdbb419de6ae3fd24b22fbeebe58d5278caf04c0c800000000000000000000000000000000140759404192c97274c06a69609a1f927195dc0e9df312f483b075e0647a9df1225c22284edece061a2a1c3dd6c4af030000000000000000000000000000000018059cd50cc71b1060ee01c10860bccaf2abbf84cc09266f2818b7625be9368138784dfacf0a1413f19bed9c09294fed00000000000000000000000000000000138939b9b91fcc8ee3aeb35de9476576cc84adbfc513a72fb74c6b897a9d6bb2037d65489709de062b238c5d0587345f000000000000000000000000000000000a9f2a8303b70df25b27158d7fbe06db9b71f6b30b8d8f3d3ad3e81ed310af6ba00eaa104c4c8755c3c24b37b5a9bae90000000000000000000000000000000014297a57a543d963d777ce5e3e5b07d19d69f56ff3efafa2753889522f10dac3fcabcc77466ef236d331361955b571670000000000000000000000000000000013a366c2748305c4ca702c053ddcf15df4a4a7858cda813d001f59bdbb419de6ae3fd24b22fbeebe58d5278caf04c0c800000000000000000000000000000000140759404192c97274c06a69609a1f927195dc0e9df312f483b075e0647a9df1225c22284edece061a2a1c3dd6c4af030000000000000000000000000000000009a457949f0a3a5ef91fbe3beb52dcf95a9f71db70bef860d2fd60cf9330b99e1d103eea022f32f0db603a635925f2940000000000000000000000000000000018e2ce9366745f5691d3c8f907bffed5f9da928cf9c9997db1311bc47b34d9d0a076ea5b7845e0dbf1c3dab60edca5de00000000000000000000000000000000089bcfe3a5596fe5f3c61326de93eb63abb0f56a7b9a5c5f4ebd883ea681607352b955deb581b57da99e4fd302e136c70000000000000000000000000000000007cb502742faf77a7223dc713a243af8998652e45f51b32dabe766c7a771bf4642ea9f25fd924e61a0f0a6e0f27c99dd,,,invalid point: subgroup check failed +0000000000000000000000000000000003f23275ed56b4aa4a1fe367219e8c84142d60d6b9983e0abb8ca21a88e008286a902b2b92369ec14d7f45f7b66b9a0300000000000000000000000000000000130b8d95a4672e467f122fc010ac3ef7f6b6d0ecd044413e51f7c27ed22f7ab7a28f60245b9ba83d4ffc98b9a990510e000000000000000000000000000000000e5af1420546c1a5a0e0c2bd9241bb7c7a26dd52f4f358fc868bea457a60bd4f6bc5b60b27069fb4f6760813a91ada740000000000000000000000000000000017426a65d239b1d9505bef2b476799c394fcc7bfdca36a1ee5a600351334dadc238b64cf8a667a25d4880a31b73c53a9000000000000000000000000000000000f151587944aad17429b51b1c16193c1e1c93cb412538d1475473666c997e012ce618eb841c4e9e064a08ab83d7fa60e0000000000000000000000000000000015c2e049c532db585807319c23ec077a51f288fcffb2cb6528d3697221e8542e3fc85d18b079ea1b217fae30858a36f20000000000000000000000000000000010a1fe14b9981a917e49b71f549b7b548629ad0003b43a9eff26e2cfa7fd8ddb21056e26dc78c88d30c32e62af40a83d0000000000000000000000000000000019f408194aa79434edd5f2a3adcc5c55ee9c1f616641b29ef21fbba8cae342df67ef438095dd7677ea1959f9a855974d000000000000000000000000000000000e5af1420546c1a5a0e0c2bd9241bb7c7a26dd52f4f358fc868bea457a60bd4f6bc5b60b27069fb4f6760813a91ada740000000000000000000000000000000017426a65d239b1d9505bef2b476799c394fcc7bfdca36a1ee5a600351334dadc238b64cf8a667a25d4880a31b73c53a9000000000000000000000000000000000f151587944aad17429b51b1c16193c1e1c93cb412538d1475473666c997e012ce618eb841c4e9e064a08ab83d7fa60e0000000000000000000000000000000015c2e049c532db585807319c23ec077a51f288fcffb2cb6528d3697221e8542e3fc85d18b079ea1b217fae30858a36f2,,,invalid point: subgroup check failed +000000000000000000000000000000000bc43aa42d656bdc233b332698247bad1904aff059eaa3f9b943ce5d4ae4f414dd361062a243f38129b954f17389ec280000000000000000000000000000000018dc8ec1d798981f662a8ce10f25f31197a2d168d3c047d2f6a214f1554202d072baf004c61b6d58f4f0410a4520b985000000000000000000000000000000001217fe0908fca8686b63337b0de6d3b3e4853466a990d8feb8a127cec95fd8dfc97be2ac57587d5f9ae1f5c10848e5910000000000000000000000000000000005c60861ac4863f7b9c38952daa88c2414ec8ac14f99fc765042b718da08136537765dcbc28cc6a0c279d491cf95b4b500000000000000000000000000000000154b289077530a86091d21c8be9c25ccd250da8d77caf853955b0d169e1ac40b5e0fd539b09b61b293035ebcbd0e21f5000000000000000000000000000000000356ddbe9454937c441dcfc98fe7b0cf8a746464f77230229328cafe6ad9ad1b5cc7a60e50bd8431b0996e3c42882777000000000000000000000000000000000bc43aa42d656bdc233b332698247bad1904aff059eaa3f9b943ce5d4ae4f414dd361062a243f38129b954f17389ec280000000000000000000000000000000018dc8ec1d798981f662a8ce10f25f31197a2d168d3c047d2f6a214f1554202d072baf004c61b6d58f4f0410a4520b9850000000000000000000000000000000001874e45ffd1349b4ed2e361dd7093a2ba41281b2d78f123bf9f6f73892962a0bcd6dc6e4159e505509cf7839aa79a20000000000000000000000000000000000ae0404355b78d20c1c3f5d65373d905696b166e76de62feb33f819dba26d39ef78621f819e998f6f2c82c65ddc22fc90000000000000000000000000000000008a499023de01bbe12958e10a3ca967f0f6047c705345beb8fb835c26df4eee908448b39191321f3eacfbd0951861c40000000000000000000000000000000000a56e3272f0474f980511540610b29e9a722a868025ac424ce8f76f342721a92d2544a420d3472f13186a0837d7e2c43,,,invalid point: subgroup check failed +0000000000000000000000000000000003e157886f141c2ce7d9ff32af44df6b7407af027005aac1149ebbe74b3b810f834b019b3e67a04531e2554f122d959600000000000000000000000000000000159eef0ff7bbe471a7ef8e666ffe35f427e3ef5bf9eeb4693dd4127467cf2615fa6b289be07452bec5c35b6d8d8ef2a100000000000000000000000000000000028316eaa131ef5303b012bfdd145bcb3106b362f410ce05810b8c83e10b1a8f80167b546b8b86c1368d7099fb5a0deb000000000000000000000000000000000bb3a353a2c16bd73c62fefd820927898dfced930d9639c5f63e62d8e8d31fa028cefb0d57ed16299eccdf3700b62bf200000000000000000000000000000000198272cf5c6e8a4f4cf4692fb7363687d7ba52deae88a7b976863309feb4a475db150073593567352ab62a150d862ca20000000000000000000000000000000019af00f2cf92494f532052962b62c34d0999a984b4bf36abd74a485fb9089ee0967071886b97f541ae80c6f7b8bc73070000000000000000000000000000000014bcf3f26683234584d79b436cc608462f1e2c20b5ecc5019988d8e30137859a4b6d0e1135dd5bbea0781b8ed3f0653700000000000000000000000000000000090ef29bf63ca97ae8388588227e1d1a0653c43b16a35a63f2ab4f0b11fd8005d9a85d30a7406491d983f347e4dfb9f100000000000000000000000000000000028316eaa131ef5303b012bfdd145bcb3106b362f410ce05810b8c83e10b1a8f80167b546b8b86c1368d7099fb5a0deb000000000000000000000000000000000bb3a353a2c16bd73c62fefd820927898dfced930d9639c5f63e62d8e8d31fa028cefb0d57ed16299eccdf3700b62bf200000000000000000000000000000000198272cf5c6e8a4f4cf4692fb7363687d7ba52deae88a7b976863309feb4a475db150073593567352ab62a150d862ca20000000000000000000000000000000019af00f2cf92494f532052962b62c34d0999a984b4bf36abd74a485fb9089ee0967071886b97f541ae80c6f7b8bc7307,,,invalid point: subgroup check failed +0000000000000000000000000000000003b6f466571daced9c0d6dd76b5f7cc91f20d92c0fc2f051a97524aee838be57eb977af49bf020a252db1b49693892ee00000000000000000000000000000000002b99dffcb6c171f66632d0cbc9aac74e6f4823fa4690e273a5c16baef618b80d2daf81d8c6b4c5240e1c329ba91b41000000000000000000000000000000000a32e330b87bb0c2984ce443412953a879f396221cd21c2f7ae46699b02c76352d3b13759d70541fc67cdc0e65fa6d4f0000000000000000000000000000000006a134cfd54f8e524544b170a4ae0b3da02da61b56633ace68b05c511a425a0a17d3e3e155a592e6176f707100174d1f00000000000000000000000000000000132f34e6b61e7fc7764b3113a4761cde446de56d3bfadc7f285bcf11132ce8d52c656cd9cddf176755dc228277557dbc0000000000000000000000000000000019d74adf4504a87de20b5a53d4e668be279d5850dc13b1699769d2279a23903f6f789dd897c2180ed895351e4f90d7e50000000000000000000000000000000003b6f466571daced9c0d6dd76b5f7cc91f20d92c0fc2f051a97524aee838be57eb977af49bf020a252db1b49693892ee00000000000000000000000000000000002b99dffcb6c171f66632d0cbc9aac74e6f4823fa4690e273a5c16baef618b80d2daf81d8c6b4c5240e1c329ba91b4100000000000000000000000000000000025c97744f862c85507620fef6d4b90a1e37ab2d6c5ed2d794880b43bdf854ea77e87e90b5a487c56fe29e28bf7ce01100000000000000000000000000000000120515b8665151db933e51722fdac7a83d2f299623a38529508b25218f0d57aeb0c6f260e0ef3741ea1f89bc653e5d700000000000000000000000000000000019d1111f074ac541a381472a4d9dc6b76cf64e86d92018460e977460b46e17924dfa522a5bfaccbfd8bd0711950f41f6000000000000000000000000000000000433ad85586f9392cc6079c1f4f37eed99fc65da9a32206912465116f879efcf9e83d8b325433ee31235142aff89a49c,,,invalid point: subgroup check failed +0000000000000000000000000000000009b488e21aeb418e8913f6bf721b3398693a3875788a3e013717fbba3c6ddaafb4073378d121cc1f2f99c072b7f8eba600000000000000000000000000000000144346f013254cec17d8423f534d54e2496df08193ed65304fe300b47a68c8d322b6ad84f748529928d64298be5ac1f200000000000000000000000000000000102c92272571b73a7df754728d7293fd8050d9dd2b8605c3f7722e6de541b7fc6a81b01c1cf15e5241ee4ee1f81ab39d000000000000000000000000000000000af1cd6f23bbd3e9ef75eed6d6d99a7cdd24574881b3609e45c4adbf82e08259d14701fcc5b6338ecf52166aecca003700000000000000000000000000000000026a1a4c3eb54de2ba4509dc806db9efc7e26247d501cb59c525b8dd15d03b91abafa9ba5816c22e1f8ca159cda34bd500000000000000000000000000000000170b510ec227fe8534a2cbb0f405756491c4f6832df552bd23980ab0946725371b3c24fa8b93a38bdcd47e1026e1d2a0000000000000000000000000000000000d327350067f7401a228c4fbcc7375f2edb058505ab34341df865a82781448d8e053b478e97a3ff79458b264a0dc186a0000000000000000000000000000000014a92d6662933a9eec6134002fb0e23a0930a964bed5bf84886bc3819516af19fb8bee2c0291c518119f4f4198eb67dc00000000000000000000000000000000102c92272571b73a7df754728d7293fd8050d9dd2b8605c3f7722e6de541b7fc6a81b01c1cf15e5241ee4ee1f81ab39d000000000000000000000000000000000af1cd6f23bbd3e9ef75eed6d6d99a7cdd24574881b3609e45c4adbf82e08259d14701fcc5b6338ecf52166aecca003700000000000000000000000000000000026a1a4c3eb54de2ba4509dc806db9efc7e26247d501cb59c525b8dd15d03b91abafa9ba5816c22e1f8ca159cda34bd500000000000000000000000000000000170b510ec227fe8534a2cbb0f405756491c4f6832df552bd23980ab0946725371b3c24fa8b93a38bdcd47e1026e1d2a0,,,invalid point: subgroup check failed +000000000000000000000000000000001756d051ce0ee9ac0fd83b9a069086cfb62164d5131a2c7be22122cc64fe74590ab5b69e02b37a6075384df9552d1d6b0000000000000000000000000000000013826bf44ff233e612a9dc8d47cbb3aff4f1fb5abf0ffbd35f4124531bca696371357301d12ed89a2974de5027c2c59f000000000000000000000000000000000ec934504ad116a80cf15a8d9a3a0bd5db18139560adbc6de32b5871198df9ecfe122369dbce5a19eeeffffd510f403b00000000000000000000000000000000007e3f75ccfc96dbe63e7b877420bccfccf2a7a56994fcea725c1b9f1823d93b0913ba1293f32493983ebe18ae27ce6b000000000000000000000000000000000ce8b2413d344263a5e598900af1524bf863e92fc3c8a2b1f335e9029081de05c70b50b97bef75044d8083e92f99b88a000000000000000000000000000000000a47a4c7b8b35b0729b43db9785a9f15c7357815e5d1ddf02d14003923120a734a1edd931d39d9261b55c145f8c69443000000000000000000000000000000001756d051ce0ee9ac0fd83b9a069086cfb62164d5131a2c7be22122cc64fe74590ab5b69e02b37a6075384df9552d1d6b0000000000000000000000000000000013826bf44ff233e612a9dc8d47cbb3aff4f1fb5abf0ffbd35f4124531bca696371357301d12ed89a2974de5027c2c59f000000000000000000000000000000000f3f5ca684120f4b7132153ba02995e88c50ac830aa65e23978ea6be09bc838249adf113e9b463cb01fe0eee43262d5f000000000000000000000000000000001270984624e5da5aeff659f5b75d3e7e5ec655ba342e318b0643672f6e71b84916ef767c58daea149f8029bb046e548700000000000000000000000000000000064c7217b420841cff11994a5f9ba682f7df02be4c8ba2027b67d33bb51b0b956137f61ac037d5551d5ca2b880e4140c000000000000000000000000000000001813131d845fc7bd523c7a295f2738580d9b39f6198ff19112b9dd38276c3045942e74c59b4392f59de70e7cd2ee87b0,,,invalid point: subgroup check failed +0000000000000000000000000000000003dfcd47087531272c3fe93d82878c5a689eba15fd67534bc2fa045b1995eca650e19e020455f0b1cbe599fb27b8352d0000000000000000000000000000000014cc297621e8b9d4ac8b1ced78c53d61261e899de0077b73f4f119e6fc50d5aa10b5111640eb3390057510825a20213e000000000000000000000000000000001150494bc162c0f414d31816adb18256b7d9fc6593f89b30b76522566667dc302050acfba7106031e99bf580fad24aad0000000000000000000000000000000005c920cd2ddd5d660e3246962b466f34a28449fe1790b9312f81fa70e13c1835970d4b807352cf7d89efa093120d527a000000000000000000000000000000000d384fa4729576214cf631ac1e6e2af54176954bd63f13cf15f2cd3c1db4cde4758d260ea4ffc0606aae700bca7ca7ff000000000000000000000000000000001824caf3b35915f528dfbc82bc5d56b5f8e7ba2b02056f6e27cbdbb0a54de8d4749446f14b116ff36b9fa773808c647d0000000000000000000000000000000011d4918642919c801fff0962062a387a4dffe693ec09cd3d0286a18e3a22c84fc09e8396ca82e6054d8535cd888179230000000000000000000000000000000016a1f0c7fec5647dcce688d3e4e526749bbf23c1fcd9e9168ace47399f9198c9b3a6b8aeca68febde1b7beeea0641aa2000000000000000000000000000000001150494bc162c0f414d31816adb18256b7d9fc6593f89b30b76522566667dc302050acfba7106031e99bf580fad24aad0000000000000000000000000000000005c920cd2ddd5d660e3246962b466f34a28449fe1790b9312f81fa70e13c1835970d4b807352cf7d89efa093120d527a000000000000000000000000000000000d384fa4729576214cf631ac1e6e2af54176954bd63f13cf15f2cd3c1db4cde4758d260ea4ffc0606aae700bca7ca7ff000000000000000000000000000000001824caf3b35915f528dfbc82bc5d56b5f8e7ba2b02056f6e27cbdbb0a54de8d4749446f14b116ff36b9fa773808c647d,,,invalid point: subgroup check failed +00000000000000000000000000000000111650bcc4c7deaa92ef43d8355198c1c0bc402fd758933765495eaf2a6c11ea6b5b6fb4a89b00040c900fdec791c7b20000000000000000000000000000000005519640447380e96adae5042193695193484d61ce0cf26acca8c96932be68e61ad6cd23515f13f8fa4fbdd6ca5390e40000000000000000000000000000000000c6f11a5306aff663038d949d08092275c7c507f68605bf9a4b591138f578f9c454ce12176d4759e1c95f3243185b9b0000000000000000000000000000000018b28c875d620249ecc25cff0ced2b3766aa66254906c69b7157b6e418a332293723b4b268d6f9d97f566b4998997adf000000000000000000000000000000000ce5e55ebe8326ee5650122f4b39dc96fe95aa4c48d26f70580fd97be90782bebfdb2d94e784786c4188ef99ecc33f55000000000000000000000000000000000632c0e5c998679e92ad269e587e831da5dbeaff3eda614d904e11c0e4dba3c87b40101cfe2f579e8015731d0ff22ac000000000000000000000000000000000111650bcc4c7deaa92ef43d8355198c1c0bc402fd758933765495eaf2a6c11ea6b5b6fb4a89b00040c900fdec791c7b20000000000000000000000000000000005519640447380e96adae5042193695193484d61ce0cf26acca8c96932be68e61ad6cd23515f13f8fa4fbdd6ca5390e4000000000000000000000000000000001780ae947388f0e055883d231f8809eb8fcc5e30eec44cbfaf11c52d4fc14bf54480c439e805559f64bd6f2085e12f1600000000000000000000000000000000142aaed1757c6f6ed83d532333e6f8f340625864fae2e71101d1ca6787045189dcb408c433caa3a19e9220f623398aa5000000000000000000000000000000000e29f5acc8998ea0d02d72559230f119ab9f8c4a013c63baa553e6ef7f5a5d38427f5b1e82b0879201ffb5ff3e23911c0000000000000000000000000000000000b934ca967385631e767483d6279afb80ea063b624491d5d837bbe4509e1f54a90b9ccb153039fbec7716dc77e28755,,,invalid point: subgroup check failed +0000000000000000000000000000000018bccb5411a58583445efe99e16d0b1fbb8ec71c6c8175a73e8d289f102d6925b68374d8986bbe9353640565fe30d3200000000000000000000000000000000013036f4649ab1dddd84a12d5a3efb93f8187824211bda276cef8376ffc90f5728bdef3b5b1bcafd59fd9ecf3bf9acb76000000000000000000000000000000000a43335eb6ff3bf2daeeb1eaf44c2782eeb517e82e55203a247b7a396e26fdf85f93695753c52c68819b58c95f361820000000000000000000000000000000000c240b7896b3dd0c318dc9ffcaa001d20bff288def3ce42752d660fd705e1544e292a5a0aa3a9a80ae91cb47cb938989000000000000000000000000000000000e5195bcc4ee8b149a769322165b6a3157ee7d04546643390adc812b6296675dbd31168b268df869a6722a7c8f51c79d00000000000000000000000000000000004af7dc8a5c552f00d55b996d193a9571173ea829eba8fadfa7becc2f4149ee7c6c4d2c8c7b1970df33cc56e4506573000000000000000000000000000000000d7cf8be632d98ad21137a983fa55acd08492a9d1e9d6caaf520713a10f5cd71c9a155ce9ba65044f42228959e893556000000000000000000000000000000000ddbd265cad3a9c525a30ebab137fb1857a9847e66c3381d51de1040a48835701a1f5627281f6cb36181d8c1c337e58b000000000000000000000000000000000a43335eb6ff3bf2daeeb1eaf44c2782eeb517e82e55203a247b7a396e26fdf85f93695753c52c68819b58c95f361820000000000000000000000000000000000c240b7896b3dd0c318dc9ffcaa001d20bff288def3ce42752d660fd705e1544e292a5a0aa3a9a80ae91cb47cb938989000000000000000000000000000000000e5195bcc4ee8b149a769322165b6a3157ee7d04546643390adc812b6296675dbd31168b268df869a6722a7c8f51c79d00000000000000000000000000000000004af7dc8a5c552f00d55b996d193a9571173ea829eba8fadfa7becc2f4149ee7c6c4d2c8c7b1970df33cc56e4506573,,,invalid point: subgroup check failed +00000000000000000000000000000000042f294cc86c53cbc520ce6368a7149676d8bc4acf708485057c8caae31409ee1586a735c3e1f416104aede85e40a38300000000000000000000000000000000153de67ca08cdb77e92091e8f04f75d17ee5525c5ec3ccdaca907b5ebc1cfcb6ce9d6a4358999cb00ae5e824d008e7fa0000000000000000000000000000000007e00b1cd95e3f9cbb2bf80404abd9768da125c42b746c2afde0121fccfdcc2431c618d646764bc5137657d2f0fcbda3000000000000000000000000000000001170cf72d827f929cb9efef52b559f8459cdd4d60464e0b3bc6e55bb6cf83cc2e9d6314b2b80e4e4f6a3c6292d1517b50000000000000000000000000000000012a7806f98848dd9c79f74e4a25812a6fae59ca73472fd20db2ecb8f732ea59294647831e03b58c60f7a71d9892ff26700000000000000000000000000000000165e6e0a602c7a1a3334a880ee47c4c440c27cfc1ab1ea6d9df592e98d21f85519d1ddf402f48ce7dc8a87439b3f42f600000000000000000000000000000000042f294cc86c53cbc520ce6368a7149676d8bc4acf708485057c8caae31409ee1586a735c3e1f416104aede85e40a38300000000000000000000000000000000153de67ca08cdb77e92091e8f04f75d17ee5525c5ec3ccdaca907b5ebc1cfcb6ce9d6a4358999cb00ae5e824d008e7fa000000000000000000000000000000000c8373f2969862e64e7a4c319a5e4db5019391ee2fd502c86bd074de5b3a1eff7917b3517175eb28efb9aa10057df87b000000000000000000000000000000000648b6bee01e4bd215ff4b51580d254e09a9b88d879ec2a0a42b0fe80792da10a9fc4d1c47058cabddbb48e94f0df98b000000000000000000000000000000000286667f7ed2e43aa08ba81a8ae4dd0487a4d11425b1e572359783ebda7c181b7dd62857a28ae3b05302cf2773f72bc5000000000000000000000000000000000349436e7b0b86db8e77d7a4dcbeb16c541b994e6f71c45098fd198991a27b4fb48025d0808000d54fe6b9500da80db7,,,invalid point: subgroup check failed +000000000000000000000000000000000b09f1c099c4743404de9034dadea6120bb4b120e81d415d047d575423d8261af3aeadeca04610d4bbefcd5fbd48d7360000000000000000000000000000000019e08ce27700db908d40e8907434068819f874b79b1540a03b856ba8898be43bf7d97b17685ab54de67602b8fc41f90700000000000000000000000000000000010714e7b0316ac3ddc1836a569befe3965800dc3cd2d9ecca097f2eebfebcce7cdc92df0110e4b872a673d5a0ebbda40000000000000000000000000000000016700d8c04f159b7a019cd0f7ade116448e0657880d364f19d1f6ea099222abab3b766d3088bd9eb870cdb3eece5ee4d00000000000000000000000000000000054f6e8c85be6d914162702dbdeb82801d598e504bfec39a2edd1035f69deccb605af437fd4ecdb23979e993904edbfe00000000000000000000000000000000183e494cd0b25d5ee1e8f1ca4054fffa4d730f547e072af920c88b9d613deb21dac38043c385fc9f9bbd6e708602ae1b00000000000000000000000000000000155d3e886cce6f257513529e40c21b5657ef1ff1f4e71bc32b968db3e05652b1ac780da573fe1a1b94b7fef86e7c260f000000000000000000000000000000001184cf09544ec2826d0101d2b79095da6e5f77d453203c52ea17b6476360ccf166ef092eccf86dbe3a260f7fd25a279400000000000000000000000000000000010714e7b0316ac3ddc1836a569befe3965800dc3cd2d9ecca097f2eebfebcce7cdc92df0110e4b872a673d5a0ebbda40000000000000000000000000000000016700d8c04f159b7a019cd0f7ade116448e0657880d364f19d1f6ea099222abab3b766d3088bd9eb870cdb3eece5ee4d00000000000000000000000000000000054f6e8c85be6d914162702dbdeb82801d598e504bfec39a2edd1035f69deccb605af437fd4ecdb23979e993904edbfe00000000000000000000000000000000183e494cd0b25d5ee1e8f1ca4054fffa4d730f547e072af920c88b9d613deb21dac38043c385fc9f9bbd6e708602ae1b,,,invalid point: subgroup check failed +00000000000000000000000000000000065a0b9822a814adda6f22f58f0ce0d6db9b32dbb2766077b6fb9bdf084ba584dc749d746740804f826d17634509875f000000000000000000000000000000000d4284a951847bad1b602396a5d5193c3a794826f58122c9c16c6e8e18f6ec2d0e17d8ca3cda9c3bbc92c51794ec7fb600000000000000000000000000000000177f2a7306144321cec932fbc1a10d58073d6915bf9ca97a05b54fe05f525ed0c327dbdb1205b70bf7ef8cf35a61c4e400000000000000000000000000000000089dfb5d4a99380761f75a94deeb6a48854164687f1055b22328d45b9792cf884ae597db1d1a93f3f2633d14969bb260000000000000000000000000000000000b6598d4c8c590f2fbbea7c48899ff43d73087becda4974184eb3ebab605e8f90497caa2fce915f7214dfe244277a437000000000000000000000000000000000acbeeaa0ddf12bb717fd32ea32ef63d137e61b5294c162d3b67e02dcf1075838bd0208d7f8edaf15f023611b774c14a00000000000000000000000000000000065a0b9822a814adda6f22f58f0ce0d6db9b32dbb2766077b6fb9bdf084ba584dc749d746740804f826d17634509875f000000000000000000000000000000000d4284a951847bad1b602396a5d5193c3a794826f58122c9c16c6e8e18f6ec2d0e17d8ca3cda9c3bbc92c51794ec7fb6000000000000000000000000000000001a002703d6da9def84f6ce69f02ce952562a7bab2413e8d58631a3387a1f4556402fba6a39b37bda22d08a68b0230b17000000000000000000000000000000001366c2a752aad0e111adb716b75459c067e275c692bc440731510d56135c3238b410538dae3ec328bcd817384d8a6b6e0000000000000000000000000000000003321a09e7290272d75882b64d2c958d4434df3499c65fbbd1236add534db804081d29b0b34167e05f4de1d8065b5b530000000000000000000000000000000001b6e16cbfe9bd8a291dcadd4599f6edb147e261bb76caca726bc89b3fa1863922e202bd5fc9afa5bfec9923f43fb526,,,invalid point: subgroup check failed +0000000000000000000000000000000012e6297c3ba79bbec9a84699ee4268a37617d21e3ce984ba1134041e539f0a5f0ac11165e835ed1aba23d3b2c5f804d50000000000000000000000000000000006c8881033aa6aef80b52f0744c0c9058f6ee2d7eeba8a749ab15af41b19be8d6aae30d820ed0a0b50ce327c7baa1a2b0000000000000000000000000000000012ff0494d308d3e7321ad4c4000e9dcd19552d5e4bce8504760f066e2fb2509279b01f1568e3c3f6216bd5328cbf72db000000000000000000000000000000000038c6e8f0fab30b5c8e4323c1fd29527845c29e1a26c70b8e5284f7ca55fb55ad4ad5389b5280927b98907132f26b76000000000000000000000000000000000aef946b9b9e9fcabb36507c1cf441df2f5ccd71ef9281dafa5e25bf07d69556e4143ab402dfb38aa756bb6ee009a6890000000000000000000000000000000015f69bc7b0a6f2cb64fd0897b421e339fcc8637efced8bf33f5aed809a38b49a2e6376d18b1bff0ef70df1b7187ad04800000000000000000000000000000000019a5a9faf36413a1e48a97458b7c416a634e1ed92fbd89fbe4593f42abad0ada72f50f7a1e1a802158ccdf923b497e4000000000000000000000000000000000e0b851da6a8005b83b2afd272a6cd017bec39d9f55b3230b600c50fb9bd5cc1bd229671f6b2c7f1d78652e4534745190000000000000000000000000000000012ff0494d308d3e7321ad4c4000e9dcd19552d5e4bce8504760f066e2fb2509279b01f1568e3c3f6216bd5328cbf72db000000000000000000000000000000000038c6e8f0fab30b5c8e4323c1fd29527845c29e1a26c70b8e5284f7ca55fb55ad4ad5389b5280927b98907132f26b76000000000000000000000000000000000aef946b9b9e9fcabb36507c1cf441df2f5ccd71ef9281dafa5e25bf07d69556e4143ab402dfb38aa756bb6ee009a6890000000000000000000000000000000015f69bc7b0a6f2cb64fd0897b421e339fcc8637efced8bf33f5aed809a38b49a2e6376d18b1bff0ef70df1b7187ad048,,,invalid point: subgroup check failed +000000000000000000000000000000000c321b54ea1ae6e893f2c28e72a3e4bed4a9aebaf147716178d3392c959e79b6f3393d738f3722a0339c773da3ae56760000000000000000000000000000000005d2c477e1c9333eb642bb40709b042816cc54134fad935942cc08eb2db0c1582f9bf06518d4fd5577df4634332e70e50000000000000000000000000000000019123b0d9c362184620c90834730c58ec5f9becdb2f3c6c00fd157ac83c16a815efb5057011d00c774d0de626274d58a000000000000000000000000000000001936fe98ecb82299a85304213a3e30c02d90ea871661b34f664a825184c2d1ebad84d144df88c479b9526222a7fe9ead00000000000000000000000000000000081a6abf02a0a236ba6006a95a8ab3f186e72f05b00f2b686049cf980898d64a71bf2e41b6b276ed6556cf83a3247b6e0000000000000000000000000000000012162cab3a7d92acc12efec9672ecb4cb30ae208eabc77608748e968a84ec0de81678df45d1655e45a19162b06354a99000000000000000000000000000000000c321b54ea1ae6e893f2c28e72a3e4bed4a9aebaf147716178d3392c959e79b6f3393d738f3722a0339c773da3ae56760000000000000000000000000000000005d2c477e1c9333eb642bb40709b042816cc54134fad935942cc08eb2db0c1582f9bf06518d4fd5577df4634332e70e50000000000000000000000000000000008b24839939deb424bb0c7bb171064e01453008ada6e3f14fd3a86db79162d0f9a851eb6abcb7dd27f93df0f7ff3320300000000000000000000000000000000036bdf42585414a8fd616bf967ed2ad4eb2b30a7581a58691c6d58e5d54fa152b42427c98fb3094c718943d9d014730c0000000000000000000000000000000002b542c2d6e8862495b2a6937f1fefbba53c228af512918bcc5b39083125c41340f1b5f1c60696c7c07f6705dfbcae9100000000000000000000000000000000030516a5aa32954dc083531035c55aa623d5f53c07b7bcff54cb9bf04d567a293ee55b2027cdcb864b133eac0f3d5274,,,invalid point: subgroup check failed +00000000000000000000000000000000018a649f727e9ac88994760a97b129d3347d30174d54a1442542123a76f805e1a48e7a71f3704eaca90b3c17c538d26b000000000000000000000000000000000a5c006871d73a11d525df1921d256f880c2a3c0aea04ed27e83d5c264d3f2196c997347299fb04149c4083876bbf3d5000000000000000000000000000000000995b9bb378a7c98ed661b493ad17b3aca367cc6aa6db24fc421d82455bca4edae6c891c191023ef2113f3c7eba79662000000000000000000000000000000000213eb30b55a6ab8efdaa67c6c99362dc62022041a6ee76f7c72cc13ffaffddf88bc68ce3d4ed36d54285b177bffc1eb000000000000000000000000000000001202977411cd6674c957c74471e269ded8140f72483b5bf81846ec60be1748080e67e38c8520b0b71793f2be9d2a5b1b000000000000000000000000000000000d49d0b96d12bbb9ae56cae73bf240cac03daa2743557e6a78f029883752ba011cbe216618b28cc173a186750602eb7800000000000000000000000000000000076ed600ed860f16ec5dbae3f09471302bf85fde7702b3376b0d670f93560e77699bed969e7001570f44dc5e37aaa830000000000000000000000000000000000c993a8b08d2eb00bcee05e1c09e8a37834fac53643643402f60fbfe2cc7d795f5c68f3d6a32c8604c37211585830426000000000000000000000000000000000995b9bb378a7c98ed661b493ad17b3aca367cc6aa6db24fc421d82455bca4edae6c891c191023ef2113f3c7eba79662000000000000000000000000000000000213eb30b55a6ab8efdaa67c6c99362dc62022041a6ee76f7c72cc13ffaffddf88bc68ce3d4ed36d54285b177bffc1eb000000000000000000000000000000001202977411cd6674c957c74471e269ded8140f72483b5bf81846ec60be1748080e67e38c8520b0b71793f2be9d2a5b1b000000000000000000000000000000000d49d0b96d12bbb9ae56cae73bf240cac03daa2743557e6a78f029883752ba011cbe216618b28cc173a186750602eb78,,,invalid point: subgroup check failed +00000000000000000000000000000000012065f7dc3b8d6d6dded1106ecd071ac0e5f73c2aea8d088bda7687ccbb34625e2da53befb200d0885b25c228b3637a000000000000000000000000000000000d222a3c0a560f9e8a6624d9100b72e62f515f1d9384ac966d99c1761264ff8e88f308e57ddc94f156655dc310b3976e00000000000000000000000000000000109fe60cebfba62b89ae166733a097629026ccee41c95ad0260c96b772293e1403247b0451d149d527212c228ca6733a000000000000000000000000000000000a497d6c0285f7d5434c42605077528e24eee8185a615c39d2caabc570bcc01b40eadb937d78e6ceb8572115671053c8000000000000000000000000000000000f0d14ceab429a46e5568200034dba88a713899a12602529fd015e2c792191d8ef492a4d685ed09a75f638ad56f02ef10000000000000000000000000000000004d4b477fa154cd86a0934130c27f4eefed4b986da5afadf558d4fa003d2480a93b351798a24c2232e3c09c0bc33e7a400000000000000000000000000000000012065f7dc3b8d6d6dded1106ecd071ac0e5f73c2aea8d088bda7687ccbb34625e2da53befb200d0885b25c228b3637a000000000000000000000000000000000d222a3c0a560f9e8a6624d9100b72e62f515f1d9384ac966d99c1761264ff8e88f308e57ddc94f156655dc310b3976e00000000000000000000000000000000129c37bc44471eeb38b484699156862250e40df9415876f8a0da3d2f2504bd5dfe76e7b67f103a94c20751a656c5c1020000000000000000000000000000000011f24e9760017ed9120e3e0c25f57ff9e70f4f15265cf884d9d978225996f21905b32c0f918e5ac30e095767779cac8f00000000000000000000000000000000012e10463254df4bb4765a10ba47f47b6ca7ccf1ed289b8a9ccc4ee5cffde83a45077c3f093d0da6aaa5f38ebc3e5f0d0000000000000000000000000000000006755c3c202da2f65be8880e12485e2f8ea85b8e6c4b21b559e2a45b5f0977e01bba8685e4d7acb4fdc045a4cb6bba9d,,,invalid point: subgroup check failed +00000000000000000000000000000000182c68fe02eb491e1c0939304135485dcd2955c643ec67198d4a07075f0ec96441ffc1274e75dd36b103053660811643000000000000000000000000000000001308eb23be0860718e6ce06c2f24f9f94b7a72c557559ba8e1a9e3bc4eb4df009472adbfb26689900a65ee80b976a5c200000000000000000000000000000000030cc52d7901d0360d10f344cecc8325412788cc30a912d5de3fa9bdab18db44efea235c5d34bab526f3b8ecee2cbb8d000000000000000000000000000000000cda35f561c19ebd85a445ce8bb1618b446c7013c07606ce58e0b5627a5c9e7cb200e2b8ee12a0564730279e75b469b500000000000000000000000000000000055ad0655a96f6dab5a432e7d2fef57a6a11113070444089df23b4b911e0994b90aaaaa2c62d06756f4704fa218f7c350000000000000000000000000000000011d22438d7c162d34802a664c254abaae07659902e1f1bfc2bdffa6c17eb11bff5276474cc3cec9507e28685f1c21bb00000000000000000000000000000000005711605edddc03aee2e53b0945162616b969fc4ad2c15819df360533120dd2ded321aa929d67dbc84ecefeed531a49d0000000000000000000000000000000012adb2a59f85343c923642ea4be500595ec8c76755c0c219e5484a7a0f53a4c3f9740cf6973aab349973295791472e5900000000000000000000000000000000030cc52d7901d0360d10f344cecc8325412788cc30a912d5de3fa9bdab18db44efea235c5d34bab526f3b8ecee2cbb8d000000000000000000000000000000000cda35f561c19ebd85a445ce8bb1618b446c7013c07606ce58e0b5627a5c9e7cb200e2b8ee12a0564730279e75b469b500000000000000000000000000000000055ad0655a96f6dab5a432e7d2fef57a6a11113070444089df23b4b911e0994b90aaaaa2c62d06756f4704fa218f7c350000000000000000000000000000000011d22438d7c162d34802a664c254abaae07659902e1f1bfc2bdffa6c17eb11bff5276474cc3cec9507e28685f1c21bb0,,,invalid point: subgroup check failed +0000000000000000000000000000000001d70e0c28a776a0fef6d7d6a729f03d59c20a4c1f28bcb28645b996c307483837361d146b73fc808041893ac1738365000000000000000000000000000000000a68b5a13a6e5ffb88ad2618be4a197271d08e55781d21c367209c08fab2545b99f8e1e0b523854f075a915856f7483d0000000000000000000000000000000008c7569bb4e8d3b213dff2c132e5954e9622edc874dc82fcd674405cfda14dbeeb323d1605d06a92231f44339952333300000000000000000000000000000000092decf1271f8cb90a67c6cbf7eb0cca0c2d71c698470193ef495e494a8a1ac3b6bd78fa6e4367874ba18a00f6eca025000000000000000000000000000000000dfeff0f041a1868cd0ede471164f24dcac619c56515b8eec5c8aea870a79a2d03f0e1526eb1e8cbcba908969b5e952700000000000000000000000000000000109aa32bee0a83dae428e388a39ece51fd3f392ec841ffaa2554972528b7f55ca36b19ef6ae585e91995a50c0848cccf0000000000000000000000000000000001d70e0c28a776a0fef6d7d6a729f03d59c20a4c1f28bcb28645b996c307483837361d146b73fc808041893ac1738365000000000000000000000000000000000a68b5a13a6e5ffb88ad2618be4a197271d08e55781d21c367209c08fab2545b99f8e1e0b523854f075a915856f7483d00000000000000000000000000000000065860fd6efa478810b70e56ca788706bccb63191649d7b9e92941efe264bd4720b1f536d90a034b681ec9ee16f50b5c0000000000000000000000000000000014e4277fc0e4827510d55f27162d85d362f99ce57a7baf74f62567d42efd3afefb61782a6ba85d4d0e27184dc15b30ea0000000000000000000000000000000004a18f9b07e2c61210d7b00d54dd1e2895692b928adc0a4597d4ff7efbfd7215e764572275cb78f29d106aec578ed1a200000000000000000000000000000000025c5527f68d69ca8d86ffaaa3330784b9a9e32069cdc2e147c81d9a39eb181e39592a126d428e6f7963d34292f44e4c,,,invalid point: subgroup check failed +0000000000000000000000000000000010f18fc4c3d422a64f6c8935636cade47636934cc6c7d5077428ca6e2641068f4668a792a20c6bd4890da09de9765409000000000000000000000000000000000795df122ae83682617000aa2f80fb82cf933b2e5765fde4409249ac06d8eaf2a92938fb7bf4cc5717e4c604b68afc1f000000000000000000000000000000001825f573c335f0e3ad6ca9f721b529ab1a84585094c034058fce2f84185d99ab78e568b7cf129adb65501c266db679ff00000000000000000000000000000000114d2a8a69b83b46acc0dc3cde307b4690d2335c18b583874a0f5f6ed6b4e4ae63fb114d32479d56d3d2ce6393a128a900000000000000000000000000000000088c08b1b66f37b98e443f9d390b9934ee8edee075788a6ff9620c386f8ec4c1f6455704574b65086170c8a37f1728be000000000000000000000000000000000366a281910a6cb906b8acdb68180c6068b555c00d84b2cd3153ce5b8dc64532b3977d186202d1d6c00673b7ffe42c1a00000000000000000000000000000000067458ca402c19488e2515037abf9323ab8288e0e11f7cdee18b3da50cfa377435cfde1f63dcdc451ce65a05641cae370000000000000000000000000000000010ed9c895629bdafae66ea176388be4e4ce45cb13ecbe0869ce57f0f48852b6b8c47bcc4a14fc5327f1df372ad9f5d4a000000000000000000000000000000001825f573c335f0e3ad6ca9f721b529ab1a84585094c034058fce2f84185d99ab78e568b7cf129adb65501c266db679ff00000000000000000000000000000000114d2a8a69b83b46acc0dc3cde307b4690d2335c18b583874a0f5f6ed6b4e4ae63fb114d32479d56d3d2ce6393a128a900000000000000000000000000000000088c08b1b66f37b98e443f9d390b9934ee8edee075788a6ff9620c386f8ec4c1f6455704574b65086170c8a37f1728be000000000000000000000000000000000366a281910a6cb906b8acdb68180c6068b555c00d84b2cd3153ce5b8dc64532b3977d186202d1d6c00673b7ffe42c1a,,,invalid point: subgroup check failed +0000000000000000000000000000000018339c70bcf5f8cbf7f3d67dcc7b40a937045b53243d6bcd417d467b1a264b960793171fda821c4206d10b4fe8488e170000000000000000000000000000000017907a4fd66b2a8b113352975ed70c0833a6fb51568ada67bf9f8cdc770e30a3a5fa305200691f4e8dbf210cbefeb002000000000000000000000000000000000b46ea3a7acd5615741210a761f7ef55e7381f52593f02e20aedc0753861acfabb5333dc5bfd829656511070b642e7fb0000000000000000000000000000000000989e2bbad608bc55d0749e0de844e001934d28a58178377d8607a1c5d5c85eb346ab94bd527b36b965626457f6aa300000000000000000000000000000000016ffa6209c14e0803789f94886e96bfbab47e07ef745557f1d1dd48e887086424cac57dd9a624de73bb7f3521de3fbe2000000000000000000000000000000000fac98b30fb441d9426f61bcdbb149b103fa9ec3b85cbd5f755d57474bbeebe796b96fac0ba1d4b75897dea8e54796970000000000000000000000000000000018339c70bcf5f8cbf7f3d67dcc7b40a937045b53243d6bcd417d467b1a264b960793171fda821c4206d10b4fe8488e170000000000000000000000000000000017907a4fd66b2a8b113352975ed70c0833a6fb51568ada67bf9f8cdc770e30a3a5fa305200691f4e8dbf210cbefeb002000000000000000000000000000000000b349ddaccbdd1381d9eb4ae7e65db31733fe3c24f38c21f5a20298bd8e01db7ebbe3c703327a5f81c89c040a5e17c67000000000000000000000000000000000dbadd08c77f8c210439d48fb55c741dc83aa9adbe7153bee1ddf1d3df824938c14d85a528c285db28df1d0fb22b8e820000000000000000000000000000000014d76b082d032e23130d6e55c0560080a5aa607f6cdfcd683d4e2450aa0788a99a005ccbdefdb3f626a045fd7cb9ed6400000000000000000000000000000000124130e29fb52ffc0d8e8ec760619de7397813b8ce598afe97f9076899f13dbe35417a6f94a2c4bc0341c933ab8c30b1,,,invalid point: subgroup check failed +000000000000000000000000000000001977147ab98c2e88f46f038c821e85fdf476bfe7bf7b7fd633ecfcf1fd2ef339b3b5cc686f4a8937e16dfa29ad8123f7000000000000000000000000000000000ef669778c61c21dbd4681483c51269a74a3ba1b1f75796b4680f0e30a286aa0173147b206f91c84731ec8ead074d187000000000000000000000000000000000e0f7595e4c136b4d8bbd1eeb021df7dd2bcf1d9f98e4fa293f7edab635e019af87c138275fefacd806213177af40eca0000000000000000000000000000000005dc209d6c86f1871637998c10490a70371f9e00a68d1363dfaeb62046473dfb4bbd3b18b943439f75c45a1ee7f264a90000000000000000000000000000000003d215567d1e8f504a72658d48fa51374ac77234552c17db4033af780133d8516bb0769678ecb50b8b9eb950c2dd73e80000000000000000000000000000000004d780849b731012e1e5732d5f6d32c659a95c3e1c8f5ef4841fe82afc6f0aa309b1e02dc2554a4a4ee781be2be2149f000000000000000000000000000000000073439cedc08916d00609c6152ee2844be85550ff5c199e9e9dddf09bedfc00d907dc85255651cff3e28a74d3b4836c000000000000000000000000000000000e57e74af4f2d6c08843152cce6d095d00679998463bfb41bef9c57ba427e14715e9e0da0e8c5193a798cf92590b34b4000000000000000000000000000000000e0f7595e4c136b4d8bbd1eeb021df7dd2bcf1d9f98e4fa293f7edab635e019af87c138275fefacd806213177af40eca0000000000000000000000000000000005dc209d6c86f1871637998c10490a70371f9e00a68d1363dfaeb62046473dfb4bbd3b18b943439f75c45a1ee7f264a90000000000000000000000000000000003d215567d1e8f504a72658d48fa51374ac77234552c17db4033af780133d8516bb0769678ecb50b8b9eb950c2dd73e80000000000000000000000000000000004d780849b731012e1e5732d5f6d32c659a95c3e1c8f5ef4841fe82afc6f0aa309b1e02dc2554a4a4ee781be2be2149f,,,invalid point: subgroup check failed +0000000000000000000000000000000005585544fc7ed448c7939c42235549edc98c5d9a793ec918274b49687e8b9267d53c2e5d66a44d889e8f2e94abab43490000000000000000000000000000000002a091dc69d5af394d408a3b5b60debffddac4db228c613b2e2f98b932aa8f90d9f5a77129fc0fe69b93ca64b0081eb9000000000000000000000000000000000f694959a69b0d692e167e95524555d58d06621d77f46c54b7f0da0a45c4cabdc8ac916b0f2052cb99b6f0f5bca36fd9000000000000000000000000000000000df814c1cf62a7a089984a3afe3f7636157ee17bbe0dde1aa2e56fe7168a3fee7c6102999dfd55b282f1a6a4ca0e0cda0000000000000000000000000000000002ce0806a288c2b9d7e03204f573b06c440be782469cf7f1478f53d5b017fa9ea3b1025cc5de378e2186c150cbd1bc0f0000000000000000000000000000000007d3c1d2c5806e73119cafe9efda5a8419679c89d17f7a90fa6302485d4efe9e44b287f573d576d7f45589c7501e40ff0000000000000000000000000000000005585544fc7ed448c7939c42235549edc98c5d9a793ec918274b49687e8b9267d53c2e5d66a44d889e8f2e94abab43490000000000000000000000000000000002a091dc69d5af394d408a3b5b60debffddac4db228c613b2e2f98b932aa8f90d9f5a77129fc0fe69b93ca64b0081eb900000000000000000000000000000000199d1db3ab960c003575ba7a53f489159aa2005bc9a30bc23e952b57ac892a2340a8adbe21eb07bcbac255be231c49120000000000000000000000000000000014303bc0d1c9748ebbbc187486650cd7651e51ebeb1bb428e153ca5e83fc4b118c7575efc03ac0ba500c6149f91db23f000000000000000000000000000000000b6383243f6914d41d2947ee74ccd299a20741e33bdad3aff2d05de662fb4d7b9b9266085bb71dee8c13dfbb121cdd320000000000000000000000000000000005d515210d948c48ee9f7ced4c23a5eb12c6bc26c974d518af4e14c1b2b2c5a286c2bc4d51ff5974eb939b4395f9d8ed,,,invalid point: subgroup check failed +000000000000000000000000000000000ba91d035c7a2da7e14c400bb137b66a4d8f563004bcf57a01f21223225b93f860eaab9c429b915a130115c8f61c606b00000000000000000000000000000000196c7f0f6b99772d1a5e97afd92a6ab4916a2f2df709bf6393a60fe29623b25dbdc989f22715fda427bf8cb84cdfd033000000000000000000000000000000000b362ce289c7edfb507b579fc8d344c5e7bfa8d58bf3629a41af7ec1faecfb9b95139d9a8159804691047e4b18bc1cd600000000000000000000000000000000112bac785b4033f11b08e845c8c5ce78e40138b0cc0b998dd8e0213bbfe0e5b96a83ff2b53ee9699b18efffdf879602a00000000000000000000000000000000157885a39334664681d8425bd60e9c1d12dd1ef45b9a3c40956df105cd3534ff9378203755119ed302a54adba9e5858d0000000000000000000000000000000008237a9ffe95f89a24e1e3a82d8f127766e2173505a9ef0e715b1ec711619664a12d86d247e530049ee542ee4d20cc7000000000000000000000000000000000072c644635936a91dcaee40e3b4794e634c315a39a9cb5cb99ef6784b332fdcfaafdc80e228cd19d0104d5796f584c350000000000000000000000000000000002318bea9077484e9c1937dfa63774b5ecf6fc63ff06e5cb653553d5111a981c09c907069ffe11b5704ea60a99873283000000000000000000000000000000000b362ce289c7edfb507b579fc8d344c5e7bfa8d58bf3629a41af7ec1faecfb9b95139d9a8159804691047e4b18bc1cd600000000000000000000000000000000112bac785b4033f11b08e845c8c5ce78e40138b0cc0b998dd8e0213bbfe0e5b96a83ff2b53ee9699b18efffdf879602a00000000000000000000000000000000157885a39334664681d8425bd60e9c1d12dd1ef45b9a3c40956df105cd3534ff9378203755119ed302a54adba9e5858d0000000000000000000000000000000008237a9ffe95f89a24e1e3a82d8f127766e2173505a9ef0e715b1ec711619664a12d86d247e530049ee542ee4d20cc70,,,invalid point: subgroup check failed +00000000000000000000000000000000077dfb9ac791b3471c6cbcb0b37b65adb0bc4e40341b85c13867bfdaf32365ddfb749ebfd965abfa22996773eab505540000000000000000000000000000000015e771a0f0149cfad7910a4a1971b39323948bc7530936db5d99a53b51bd656bdce093cc2b91ebad0f91a95034afa0e3000000000000000000000000000000000200775a5848ac14b5e762ae7d4b492d2dc0bd5e80ef7fc760d42ea6fb07ffd2944409052cfb773875df676a188da65b00000000000000000000000000000000140ec7de210c890e4c795eed394d32d77f6acad0a3628da2ec805d4cf2de9822b5a73f06bdbeeba0fc1068c26da675b20000000000000000000000000000000018e7877a3f27c5400b08bd2769616745e4657f6fe262f9d7b88330917f977efa463b3226f3433da95a82568d990b1fa50000000000000000000000000000000015801af4934193336cc67fe8f4be5d2093909006f8bdd3382d60fd5c6bce4b86071370eefbce7a04dbcfb825858f90eb00000000000000000000000000000000077dfb9ac791b3471c6cbcb0b37b65adb0bc4e40341b85c13867bfdaf32365ddfb749ebfd965abfa22996773eab505540000000000000000000000000000000015e771a0f0149cfad7910a4a1971b39323948bc7530936db5d99a53b51bd656bdce093cc2b91ebad0f91a95034afa0e30000000000000000000000000000000017bbbfef68883fbf6fa9cdcef37ef145d7fbe9532164530e9c08d196ee41f38784b257087ad53f4939426451fb5f955c0000000000000000000000000000000018bb97090375c21600b6d3ee0629cff78116aa59d7b665c08590add8cdff8247c38b588a25e0b11eee5111c63f8c619e000000000000000000000000000000000e181f7327776df8ec16258115303029c68e1b72fcad6395c6d7d477368e1697076333a2102447a225fb3f3d725b3b0b000000000000000000000000000000000291d82b95e9a2c3f766994c304dae7f19f1efc789f68c4e58eda102da36cd0d7eec3d5a1b1e88c63462c8ec0e4393a4,,,invalid point: subgroup check failed +00000000000000000000000000000000121a9b867c86195dc4aee07081c1ad62f066b471bb5a14f296943b263fb9a25e6805e3171624e7e7e45b78f175a1861300000000000000000000000000000000071e1c35979d6f43170e79c0db5cceccff01f17cc2980b771a6cc38e0b27438a9db8e00eb943142d992c6a395fe4aacc000000000000000000000000000000001819d13cf4522a9362bbeb0bbbb0a498c3f34da1c9e3b2c54d08f7c8acd9ee756983fe80405579effb79d673407390ef000000000000000000000000000000000f870e5978f4a6e3b655fb2a05541ac0673e7b10136adaf28be4dfc9022d4cc8a60e17d125dfe53fbe10c644ff37e02a0000000000000000000000000000000010207ef774cddd10db2bca0a051ceb12900c407ee265dea4615553c193d7475b5ba3198b7e0160740e4fd015dca33e1d0000000000000000000000000000000017937be546e06fd2eab4c969a029534c02fb770646d43edeb5e6c8bc0c2b5f35576c375bf860fd1087ce099d4377d24e0000000000000000000000000000000001a8b8cdcd160565a1df9cb5ccb06a62fbaf32b2cac4ec9a552773313c940688638775983815cb246d4eaafe91c3451100000000000000000000000000000000082a1237c161831a37589ff711f7873d5e092d8a4690b983c9ccbbf980422ed177a3ebbd4b4ae4b557bcb3ae532f1823000000000000000000000000000000001819d13cf4522a9362bbeb0bbbb0a498c3f34da1c9e3b2c54d08f7c8acd9ee756983fe80405579effb79d673407390ef000000000000000000000000000000000f870e5978f4a6e3b655fb2a05541ac0673e7b10136adaf28be4dfc9022d4cc8a60e17d125dfe53fbe10c644ff37e02a0000000000000000000000000000000010207ef774cddd10db2bca0a051ceb12900c407ee265dea4615553c193d7475b5ba3198b7e0160740e4fd015dca33e1d0000000000000000000000000000000017937be546e06fd2eab4c969a029534c02fb770646d43edeb5e6c8bc0c2b5f35576c375bf860fd1087ce099d4377d24e,,,invalid point: subgroup check failed +000000000000000000000000000000000a86f90fcd9b63a0cd5f53356c170699d78d03f180d59c38ff770560bda30bf412fdcf236b6720d94684ef1aab97bafc00000000000000000000000000000000030bcbb272ab20a2f27d45295875e3c29a8ad088bb8173feb6f6f4d2c81bbc91d673c23239e36bdafada8c7d50b54b440000000000000000000000000000000011e01c96294c726ed3ef22a5c6597d8202604c4fb14058bf44fa64ae6d342f6f77f151c8ecd99f793e79f0ad29c309f40000000000000000000000000000000000e186664d8c2a2e136ff1be2192573b94c083bf242b2c01c984a792e43a11a10599481a9a79f6a8d5b074bc16019725000000000000000000000000000000000fc48ade56f841489c4824411130fb5b7e0e83b613fa09099f318cef207ce035d5d6e7ecff5057c15ee764ec8a7fa20300000000000000000000000000000000003f1b261043887af57fab48b505ed7aa44132457d71a390646b70fcd073e677a7e275a89dd0a2bf32beae3b2bcbd6e9000000000000000000000000000000000a86f90fcd9b63a0cd5f53356c170699d78d03f180d59c38ff770560bda30bf412fdcf236b6720d94684ef1aab97bafc00000000000000000000000000000000030bcbb272ab20a2f27d45295875e3c29a8ad088bb8173feb6f6f4d2c81bbc91d673c23239e36bdafada8c7d50b54b44000000000000000000000000000000000781404020a416e2596ab361e02674e25cfb365420d35d5db7146f563a7675a942383da44ae4df49c45b38e371c82a2a0000000000000000000000000000000010c546bda090a13ccf0fec03bdcb87b41f5aed3b4e6740690afb9dadc57d773aae2d22a2d8323336c5b1dc5798725495000000000000000000000000000000000bea6aaaadbbe8102212279f1458c461d3a0d54e341c91b5e16e0ce5ba1517a13cd1d43e1d0b25a63b7cc57ece5369f3000000000000000000000000000000000b0b676e5cc2f6ac383f5dd42d379c552579f601de0cf4f34ac637383a31e393df40f5c0f95b5a8f57cd6fa4de01caeb,,,invalid point: subgroup check failed +0000000000000000000000000000000014372fb746da15863e9ee4e06099c7e513bdbe53ca772a4b61c81eaa7f841399422f7902893d5ee7f7d59d530e3674b10000000000000000000000000000000006ba991efa65ef8dfac8b07915cab83b5267babba1291e4662a81fdcb455faf33596f6730b6f5b3eac2076054a4ccf6600000000000000000000000000000000042ee88071289a2adeb69cbab5a3ac8c7935576bc434062091cdf1cada4b67a2501c179b5980b53256f623840a5aee5700000000000000000000000000000000063b0819dd470047a704f20f5f7c65ea0899f25603dfc7e8b8d5f0d0d323180aa921e43d63b45acc8fe9054326a8d9bb000000000000000000000000000000000615e2e5b0389017cd3ce7c15740caf3b897fbe4a59c68247c3c4229bf661257f56bcc10f55fc722f96424f5617d259700000000000000000000000000000000166f7cadf7cb9ac5a8cfa83fe4aaac0e32fd4de3e38e0d39e010d50f5b3d383243d6870505f2a285b7c5f6fc1b13f0f0000000000000000000000000000000000d1ed017ef4702bcd3bfbbcff36000af6a1d26ab363e68ea5629027e0b90352bf1d8e03c13a7955da6c15507cc1c9f47000000000000000000000000000000000e09830e54fe9eddd416479a1740f6f1b7693f2d153d322f27779b16bb6451d7657df85a55da75a4aee0a2e33b3a46e600000000000000000000000000000000042ee88071289a2adeb69cbab5a3ac8c7935576bc434062091cdf1cada4b67a2501c179b5980b53256f623840a5aee5700000000000000000000000000000000063b0819dd470047a704f20f5f7c65ea0899f25603dfc7e8b8d5f0d0d323180aa921e43d63b45acc8fe9054326a8d9bb000000000000000000000000000000000615e2e5b0389017cd3ce7c15740caf3b897fbe4a59c68247c3c4229bf661257f56bcc10f55fc722f96424f5617d259700000000000000000000000000000000166f7cadf7cb9ac5a8cfa83fe4aaac0e32fd4de3e38e0d39e010d50f5b3d383243d6870505f2a285b7c5f6fc1b13f0f0,,,invalid point: subgroup check failed +0000000000000000000000000000000009f0c6f9fac38e8c83183499b8918a1ffbc52f2400882edb66594f496ff38ffec77368f28e4f20767257e200f48255700000000000000000000000000000000002a05bfde9523ac13ba3518cd5b308c4985484f996e7192140d681d9934d501111a81445031d84d4a47a9727202c07620000000000000000000000000000000003004acd2a95d932b84233e80bebb9fd92b302809725d5ca0921a5d8983950ff521d89bcc2d1bc1e7c186c702bf7aa270000000000000000000000000000000011744ffc7092744a79e345be8b51569c5df8eb10b4e49957ade8df4ee4ede566b3825eec89027d70d188ff858a8b6cf4000000000000000000000000000000000e46cd26b21a8a933eac05ed526a2b8fe195e5a5435e79c7f385fb69a90190acd06e25e9b63af7862616c79add032597000000000000000000000000000000000ad60c22b3690c78c23682ba902a18e708e88430a55a9038975a43b4606ef4c6e2b8e648a25097b3a34bf6e4024d00280000000000000000000000000000000009f0c6f9fac38e8c83183499b8918a1ffbc52f2400882edb66594f496ff38ffec77368f28e4f20767257e200f48255700000000000000000000000000000000002a05bfde9523ac13ba3518cd5b308c4985484f996e7192140d681d9934d501111a81445031d84d4a47a9727202c07620000000000000000000000000000000007d87d13752c52bf0510cee94274f6f4a6e0675de9a4a864ba5058dd8771b6c5000e957cfca5279e64f09c21111322ec0000000000000000000000000000000007999819b5b57104c9432a9d4dc6ad377f0c6f0dd630155fc489aed1f8d18ce0386222813726cb786635778b74967bce0000000000000000000000000000000016b66e0ebcbf6043f6a7fe52bf527a9b763cd68d901933068966e6dbb9817e1287ebc2de9c3729df8b4228a4f92d9732000000000000000000000000000000000ee19b863d5ce19afce76e489e122948597ac6a5ee07e2d856a49377285ac93d6674cc5429e02bbd051d4edf7988ba89,,,invalid point: subgroup check failed +000000000000000000000000000000001269c2717ba196d5004865af806d4a99b8c238583db14f9c02da70b0275cf35a3a5276eec0c8e6934f11e0d5cc8b7c9f0000000000000000000000000000000017971814f15aaf3f6672b3a720cf6726aa042dbd82ac508a8f7ac5ddf17f377891199ba2fd01d990868347d45e3b37ae0000000000000000000000000000000001587e32753adc85c98cf1322115772b0e282ef4e6a75944fc86091e81aad076508e3d727f4df0e30924fff6b67c312e000000000000000000000000000000000ae96d3a1b79985e56f80df8ac4d9792229ca580b156dbbe71a9db470447fa4dfa19fc8a8a2e2f0fae28a24b7d6153d100000000000000000000000000000000114101ad0d29ddfd2fc436d2a270711c444c8c257785f4b4c549e9c795f6dd9834d3744995d2188c0c968752a7f68892000000000000000000000000000000000d30d9cc1e2273af745dd47a596a2202ca4fb655f9f9beeb0a87631e2461f29206163fd921761fde69654cb02e23505c0000000000000000000000000000000010cda048fed479f7bcd388a0acaa977b134055f5ea92b2a689793e301d58190c67031920ccf1cd97ecf9f429f5a022e00000000000000000000000000000000014c410faae20d54049aa7c644ec1ef0388367ac847f6781e62ec88eb9262ffff5f19cf5f4ebe791a44ad9a84fd78aca70000000000000000000000000000000001587e32753adc85c98cf1322115772b0e282ef4e6a75944fc86091e81aad076508e3d727f4df0e30924fff6b67c312e000000000000000000000000000000000ae96d3a1b79985e56f80df8ac4d9792229ca580b156dbbe71a9db470447fa4dfa19fc8a8a2e2f0fae28a24b7d6153d100000000000000000000000000000000114101ad0d29ddfd2fc436d2a270711c444c8c257785f4b4c549e9c795f6dd9834d3744995d2188c0c968752a7f68892000000000000000000000000000000000d30d9cc1e2273af745dd47a596a2202ca4fb655f9f9beeb0a87631e2461f29206163fd921761fde69654cb02e23505c,,,invalid point: subgroup check failed +000000000000000000000000000000001252aaecb588ffcdee3e4fd92ff5164feaf9aa39acbc71c704d8180611d30fe13e59bba805101dc1cecf77b254bc65510000000000000000000000000000000009dc3de2e8aa94dbcc25c8775e9bd0ae0fa8581df790e562e67f24c08efaab59a0a8062478a09c262040c5f0558971c3000000000000000000000000000000000b0a6f9e0b58db3015e1dc63f9d377895d25f48e8a05371ad90c3ef5f3085a76b888d38693eefdf3b1eedf80eab1736200000000000000000000000000000000006dfb36e1c281cf1c5a8be9a631cab94aa956bacf8e9e787c0e2bab4440f03f0efc56d5a058fde2e18696c569676d3b00000000000000000000000000000000118791bba7507725b7106bc889b68c3daa56dc3100d8378cf156268f249dbafd01025cb722d58246c95ac856dd5d0411000000000000000000000000000000000f942ab8fd1e71f6d4498403116ef41954e7967222f894b93ae31f061cafaa1ed3464520dc5123aad4b0a352a85efbf8000000000000000000000000000000001252aaecb588ffcdee3e4fd92ff5164feaf9aa39acbc71c704d8180611d30fe13e59bba805101dc1cecf77b254bc65510000000000000000000000000000000009dc3de2e8aa94dbcc25c8775e9bd0ae0fa8581df790e562e67f24c08efaab59a0a8062478a09c262040c5f0558971c3000000000000000000000000000000000c13d99118e4946773d0ae54a37895411e39ba0de604c5f69d0b3ddcd50c4261c38c510bc1e018dbdf449e303e398d820000000000000000000000000000000018427393a7ed2dee713e83e58a6537c5c6baeb69ceac3b574e02af78215d99b8cd01f0c944d075300d35176099b0aa8100000000000000000000000000000000140ab2527b79327e07344a673e688debec28aa29219a5b1646a3c2b599a9d374cf5e139ab00aa237bb8e29d021d766ea0000000000000000000000000000000017164f154d26566ecc983d38d77d694208864c024c3ffc69f19f84550e86eddd8dbb055a8cf543717ce3d65e1c64c53a,,,invalid point: subgroup check failed diff --git a/evm/src/test/resources/org/hyperledger/besu/evm/precompile/pairing.csv b/evm/src/test/resources/org/hyperledger/besu/evm/precompile/pairing.csv index 00a5e950095..ea04bb5b739 100644 --- a/evm/src/test/resources/org/hyperledger/besu/evm/precompile/pairing.csv +++ b/evm/src/test/resources/org/hyperledger/besu/evm/precompile/pairing.csv @@ -1,104 +1,104 @@ input,result,gas,notes -0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1d9f0000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40,0000000000000000000000000000000000000000000000000000000000000000,138000, -00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed200000000000000000000000000000000192fa5d8732ff9f38e0b1cf12eadfd2608f0c7a39aced7746837833ae253bb57ef9c0d98a4b69eeb2950901917e99d1e0000000000000000000000000000000009aeb10c372b5ef1010675c6a4762fda33636489c23b581c75220589afbc0cc46249f921eea02dd1b761e036ffdbae220000000000000000000000000000000002d225447600d49f932b9dd3ca1e6959697aa603e74d8666681a2dca8160c3857668ae074440366619eb8920256c4e4a00000000000000000000000000000000174882cdd3551e0ce6178861ff83e195fecbcffd53a67b6f10b4431e423e28a480327febe70276036f60bb9c99cf7633,0000000000000000000000000000000000000000000000000000000000000000,138000, -0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f000000000000000000000000000000000a69d6d9f79e19b38e6bf5a245dc820bddbdfe038d50932f76d0e4629d759f8ca6d573fcfc39256305daedf452f9fdf40000000000000000000000000000000015f5949369e58487afcecf8018775d1b0a73e913bf77e13d2e5a843bbbeba7d1978ca27ae8bfc87d30f567dd396b980e00000000000000000000000000000000182198bb38a0353b8db25389e56ab0d8679a1bda008a65dad77e4c95bc6804f6311eb16c761e1a5e2a5f87cfada49fa4000000000000000000000000000000000eb5483959e98c30e71db52615f63521378b156f142d46f3bb285b94aef39d80feacec335b797c5a68dc17ba89d43e0f,0000000000000000000000000000000000000000000000000000000000000000,138000, -0000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e4000000000000000000000000000000000286f09f931c07507ba4aafb7d43befe0b1d25b27ecc9199b19a9dc20bc7ec0329479ef224e00dece67ec0d61f1ca5ae0000000000000000000000000000000014e6ed154b5552be5c463b730b2134f83e0071dcdadfaa68e6c7c7f6e17dabb7daf06e409177bc4b38cfdb8248157618000000000000000000000000000000000f145e998dc6eb0c2b2be87db62949c7bfa63e8b01c8634248010fd623cfaec5d6c6c193331440957d333bf0c988b7b10000000000000000000000000000000002a1ab3eea343cfdea5779f64b3bddbf0769aded60e54a7507338f044310ba239430663394f110e560594d6042a99f1c,0000000000000000000000000000000000000000000000000000000000000000,138000, -0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d3000000000000000000000000000000000d1007ca90451229d3780d66d3aed7c9d8fc82e9d45549e8586600e38eb6763f3c466e2f6ba6ba1dafd8f00cc452dda20000000000000000000000000000000001d017d920a262b6d6597bab532f83270f41526409510e80278d1c3595ceabb9ceba8ae32b1817297ff78ea7a0d252e8000000000000000000000000000000000935b7a59d2e51bbb2f9b54ccb06ebee9d189fa82f0e97d10c8020badb3de7fe15731b5895faed8cad92ae76e2e1b649000000000000000000000000000000000792dadd48a20040ad43facedc109747411895180813349d41d0e5b389176bfb15895d41665be8d1afa80835ef818eca,0000000000000000000000000000000000000000000000000000000000000000,138000, -000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f9000000000000000000000000000000000095353ad699b89ac82ca7ef631775b2b3a6e3ed8dd320440cdb929baa428e63cb902a83857cc0e2621470544c69e84aa000000000000000000000000000000000892559ade1060b0eef2cbc1c74de62a7ff076a3621e5f0f159672a549f1201f2ffb3ac12c8b12cb86ae3e386c33e219000000000000000000000000000000000750df4632a7126ddb08658a4001f949b9764d9cc43a9393cc55d8fdbb15d4a1186dd87a6433d111888a7804540ad9fc0000000000000000000000000000000017554bd444665df044b91b0b2614017bbfcd7acc7f8c5a16cea2861235578ce2b27dcced9fba234999fa478cd3f6e42d,0000000000000000000000000000000000000000000000000000000000000000,138000, -000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1b00000000000000000000000000000000175dadb6ee656ec6aebf8d0e5edaee3f119c74e0ea64e374be9e8ab9fd3d085fceeedf4ed8de676ebe9065d83b0542ad0000000000000000000000000000000005cd6a875329c23e4918976cf997e93e403957acfc999f8159a630d21ab6f1762925c063784237262bedc82402ad81bb0000000000000000000000000000000003274bcb8db35e50164d136c2a98b5a6d2fb5f9767d0ee11c1358bf7ca5ed96d9122f8c1051ba3c658cc89777d03dfa5000000000000000000000000000000000380a240443dff85b6542f75db28b87c39e278cdb8d9627efbbc63b229e6ce783f6fb0114c8e91c2fd6ea71c95bb99a4,0000000000000000000000000000000000000000000000000000000000000000,138000, -0000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442a000000000000000000000000000000000834cf1b4149d100c41b1bca0495e455002eb6596bddcb94ae48d0c65957e8b313372f8e0d6e57504664b266f38293150000000000000000000000000000000000de2875fbd14760bac4c2cc7d3f239177efe9f7f61f767be420d44f24c9fb863efd60dcd732986db8c5b72470617ea60000000000000000000000000000000000bc9535ebf11c2dcc8c7d3bcd09d7d14035635fccb5fddb7df29ce8855e79f99809781d6ffbbcb33d1227314609abee00000000000000000000000000000000039bbfb4d969d702255e3be7f255a97529a19687ce38cb70637c37894d4102591feef428b0afe8c9ef50310ae3b83091,0000000000000000000000000000000000000000000000000000000000000000,138000, -0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795000000000000000000000000000000000fc09c241899fa6e8cc3b31830e9c9f2777d2bc6758260c9f6af5fce56c9dc1a8daedb5bcb7d7669005ccf6bfacf71050000000000000000000000000000000018e95921a76bc37308e2f10afb36a812b622afe19c8db84465ab8b3293c7d371948ee0578dbb025eed7ed60686109aa0000000000000000000000000000000001558cdfbac6ea2c4c1f4b9a2e809b19e9f4ba47b78d2b18185ed8c97c2f9c2990beadc78b85c123b4c3c08d5c5b3bbef000000000000000000000000000000000ea4dfdd12b9a4b9a3172671a6eafed7508af296813ec5700b697d9239ae484bcf7ab630e5b6830d6d95675be5174bb2,0000000000000000000000000000000000000000000000000000000000000000,138000, -0000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a1900000000000000000000000000000000000b36d8fb9bd156f618ab8049d41dfe0698218764c0abb10e12fae43c8810b8e2a5201364e2778f6f433b199bb8f9a6800000000000000000000000000000000000707eb15411b63722b4308c0ed4288320078d2463ae659ad4fb3f9ef8124f379df92d64e077403e50727388adb59ac00000000000000000000000000000000158e1249d5b91614924acb23899c6bae408697dec0982c10d0459746499f4e6739afb9d5129568106ed1a1caefeaa9640000000000000000000000000000000019e841562e4aa75321143f8ce1e5ec6158fa5cb8b98c839a486188260c18ee8a7600930f23aa39eac2eb520d6a0fba90,0000000000000000000000000000000000000000000000000000000000000000,138000, -0000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac94300000000000000000000000000000000186a9661d6fb539e8687ac214301b2d7623caedd76f4055089befba6ef2c96263d810921ad7783d229f82783c9def424000000000000000000000000000000000447f3e20caa1f99fbaccab7bde2bd37fe77cea691ebf2b9499f95bbbb77afe72b7039eb0c05970b61360fcf8ade73730000000000000000000000000000000005e11f828eda86c10a1d7929def547ac06885da278afae59c5d95453caf0a2d8ed186fa7c6d0a7ab6e9142cfa4b338190000000000000000000000000000000003d954e61b6ab71042b19e804efccd4956b56662f27f70a9255cec0c464b86c0e83721ad3785dec62dd4a9dd3d6d5d53,0000000000000000000000000000000000000000000000000000000000000000,138000, -000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e0000000000000000000000000000000002b94534aa0ba923bda34cbe92b3cd7a3e263741b120240ff5bdb8b718f094d3867e3fcabeab4a7be39c8f8c4fdd10d900000000000000000000000000000000048711cf6a82534d64d072355cb8fe647808e7e8b2d9ac9ed52eb7fe121647a721dd1234c71ecd163d91701eb7331cac00000000000000000000000000000000141ef2e23a1ecc7ef2ed3ea915492e79cfffe60b5e0de8441e878bd0653843d79c724e3c5ebe2321361df99f8932ddc200000000000000000000000000000000085513b4009f29b3e00a91c2c4be418368560802ba4194cbd2f4fa3d72a55fcae547014434514a8b2a8fe3e0b28d2773,0000000000000000000000000000000000000000000000000000000000000000,138000, -0000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276b0000000000000000000000000000000009143507a24313ee33401955fc46562c9b20c9917df3b40ccbd7ed43b1349d4551cfd98a4976d6fec5fc289460c8d89900000000000000000000000000000000060566b79df5cc975e669da8ca3a7fa91bf3f5c9fb871c3d62f4a3e79dbc341b89d38b588e5414bc385d5e3cbf3ab9310000000000000000000000000000000016bf40b8cc4c01a87aafae0c4439b623a51ba9a383756a550b69d627d6f45209f0d87e4f9be9edff35c986f7b9c49e3f000000000000000000000000000000001842d9172bce51a164fbdbdb108d0faae07e4642f21c80e40ac31e737657472ae3dfe552b65349629c210a068c4afc0e,0000000000000000000000000000000000000000000000000000000000000000,138000, -00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a5787681000000000000000000000000000000000ab19bbddd661e9db8fe4cb307ecebdc5e03efbb95c5b44716c7075bd60efcfc67de0bfd7c46ad989a613946c90a4c1000000000000000000000000000000000120800e7f344cda816299fa37f603ade06beb3b10907f5af896d6b4e42f7f865b756f14164db84411c56cb2ea81f60be000000000000000000000000000000000f688ddd257e66362af1437b6922d3397a7c3dd6dea6bca8ebd6375e75bf2de40bc287cbf3434388191e56b92949c83b0000000000000000000000000000000005252465784aff8c1c707da58b5808c69583bf852d68f96912bc53f8dae4536b09ccbbd25a49d9e744118992b92b6792,0000000000000000000000000000000000000000000000000000000000000000,138000, -0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f768000000000000000000000000000000000e3165efe00f69aee84ac56d2161f07c017abfaadeaad34f8c96799d68bae0e6f9b557bbf9137e7826f49f29c58d1ef9000000000000000000000000000000000de0dce7ea371ad60f21f2cb61cb582b5072408a7efc91edf05b36a1a3b58fd9e6cf808d75157eedccc8f1c93a8ae07d0000000000000000000000000000000016d911943d80427385ebac1d1b293914a9e4dd9db06c1d6a758192d63c8fc9368e02eae7fb0e3a7859408f215cfa76ca0000000000000000000000000000000007bfdc6afb8acec625e50ecbc08a5cdb7862b795866323679885ba5cba3fd51f181078e03fe35e96e6383c077eed1bf5,0000000000000000000000000000000000000000000000000000000000000000,138000, -00000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f42000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931d000000000000000000000000000000000a68dccbe3452731f075580fe6102b8ee5265007ee19c56d95bcb096a3a6ac444f4145b980f41afcb0a865853b279bc600000000000000000000000000000000164767ea55a9038ac2dd254d8c8a4970dba93dacdf5416aecaa407914719cab165e7a32784b2c41652a86358737d831f000000000000000000000000000000000da9441fbc6578c85fdeca49082c9ebbf183de894d67c65158380ee56132d3cdb44b100d72b6d3b82688defb75d2aa390000000000000000000000000000000017d570e4f6e46550679d5d12c347414da207060f594620e2f8db66df8e0b06c912290b207a268e782d4b45db19a199db,0000000000000000000000000000000000000000000000000000000000000000,138000, -000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80000000000000000000000000000000000874389c02d4cf1c61bc54c4c24def11dfbe7880bc998a95e70063009451ee8226fec4b278aade3a7cea55659459f1d500000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79e000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd8000000000000000000000000000000000118cd94e36ab177de95f52f180fdbdc584b8d30436eb882980306fa0625f07a1f7ad3b4c38a921c53d14aa9a6ba5b8d600000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79e000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e,0000000000000000000000000000000000000000000000000000000000000001,161000, -00000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a67000000000000000000000000000000000013a80ede40df002b72f6b33b1f0e3862d505efbe0721dce495d18920d542c98cdd2daf5164dbd1a2fee917ba943debe0000000000000000000000000000000001c2d8d353d5983f22a5313ddd58fdc0d9c994b2915dbc87a9b65b7b98ff00b62e140a27dc322d42b3ad190c1b3728dd0000000000000000000000000000000010412f3625947b38bb380a6ed059f1677b7a7afcb91517837c563dadd0e285b95740a200ddff6570d4d92bb636b625bb0000000000000000000000000000000015f4f9a480a57bd1b2388532ab045a1ba93d2f6589a3022c585fe06a1d611165c99d70be06251812405c9c37d6e9f7730000000000000000000000000000000001a78e6c5062a6634a56e9853ff5afacb2e7cf31fd0ea5f0d8c8ac6174c88133cf2f63450ec4590544c9a0e37daac1f900000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a6700000000000000000000000000000000018c690fc5571f69793ec3c82915ac9513726ec891312f4f11dd3ba0ee95cc98b50d925099b0642e58a106e8456bbcbed0000000000000000000000000000000001c2d8d353d5983f22a5313ddd58fdc0d9c994b2915dbc87a9b65b7b98ff00b62e140a27dc322d42b3ad190c1b3728dd0000000000000000000000000000000010412f3625947b38bb380a6ed059f1677b7a7afcb91517837c563dadd0e285b95740a200ddff6570d4d92bb636b625bb0000000000000000000000000000000015f4f9a480a57bd1b2388532ab045a1ba93d2f6589a3022c585fe06a1d611165c99d70be06251812405c9c37d6e9f7730000000000000000000000000000000001a78e6c5062a6634a56e9853ff5afacb2e7cf31fd0ea5f0d8c8ac6174c88133cf2f63450ec4590544c9a0e37daac1f9,0000000000000000000000000000000000000000000000000000000000000001,161000, -0000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e880000000000000000000000000000000008d7489c2d78f17b2b9b1d535f21588d8761b8fb323b08fa9af8a60f39b26e98af76aa883522f21e083c8a14c2e7edb6000000000000000000000000000000000818e567aea83eaf3142984bb736b443743659626c407987b604a30c79756081fa6ae6beeb2e6c652dbfe9cf62d44e3900000000000000000000000000000000193f0317305fde1046acda2c9491e376aa67244f68ef6495845d049e1293082af91f880be935d9d8ad0e25ad918caae200000000000000000000000000000000109224b8178be58ea4e4a194ca66bef9d14f6fc2c625d25feaa4f32e0f4d72d91024d96839bc96e6a624c5ad6221bd94000000000000000000000000000000000e42decf8a987efaeb4ede37236b637e61249bf6245679be7fd4d633e2d814ed4748b73890ad3c4fcbcfb4960cb67ae70000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e88000000000000000000000000000000001129c94e0c06f51f1f808a62e42a5449dd159289c14a09c4cc382c91bcfe878b6f3555767c310de1b1c275eb3d17bcf5000000000000000000000000000000000818e567aea83eaf3142984bb736b443743659626c407987b604a30c79756081fa6ae6beeb2e6c652dbfe9cf62d44e3900000000000000000000000000000000193f0317305fde1046acda2c9491e376aa67244f68ef6495845d049e1293082af91f880be935d9d8ad0e25ad918caae200000000000000000000000000000000109224b8178be58ea4e4a194ca66bef9d14f6fc2c625d25feaa4f32e0f4d72d91024d96839bc96e6a624c5ad6221bd94000000000000000000000000000000000e42decf8a987efaeb4ede37236b637e61249bf6245679be7fd4d633e2d814ed4748b73890ad3c4fcbcfb4960cb67ae7,0000000000000000000000000000000000000000000000000000000000000001,161000, -0000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000aba6a0b58b49f7c6c2802afd2a5ed1320bf062c7b93135f3c0ed7a1d7b1ee27b2b986cde732a60fa585ca6ab7cc154b000000000000000000000000000000000ca0d865f8c8ce0a476f7a6edb3ce4bd5e6c3a8d905d8fb5a10e66542f4325a9963c2f8d96f804f4d295f8993b5204df0000000000000000000000000000000005a966f6254f0ef4f93f082a97abe07db56f00c2ade047d2f0027edef6f00a0dfecaa24d50faa778fa29087302211f7e00000000000000000000000000000000121c51da366557c09af1bbd927521da88dfab3e2e9a95b6effb0a968795486f281f0c887e37f51837557b9e3808987130000000000000000000000000000000001a5524975400b1e88f3fff8dd34dadf5d75564cfc0026df31ee9c2c1d48b0f69a48e1e4a48cc4b7db61f023a79157800000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000f46a7dee0cb471ddef3a50670a5bfc443b8455877f1ff602b21faff1eff07fc6bf27930ca2159f01479359548339560000000000000000000000000000000000ca0d865f8c8ce0a476f7a6edb3ce4bd5e6c3a8d905d8fb5a10e66542f4325a9963c2f8d96f804f4d295f8993b5204df0000000000000000000000000000000005a966f6254f0ef4f93f082a97abe07db56f00c2ade047d2f0027edef6f00a0dfecaa24d50faa778fa29087302211f7e00000000000000000000000000000000121c51da366557c09af1bbd927521da88dfab3e2e9a95b6effb0a968795486f281f0c887e37f51837557b9e3808987130000000000000000000000000000000001a5524975400b1e88f3fff8dd34dadf5d75564cfc0026df31ee9c2c1d48b0f69a48e1e4a48cc4b7db61f023a7915780,0000000000000000000000000000000000000000000000000000000000000001,161000, -0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b0900000000000000000000000000000000082543b58a13354d0cce5dc3fb1d91d1de6d5927290b2ff51e4e48f40cdf2d490730843b53a92865140153888d73d4af0000000000000000000000000000000002b51851ef3b44481d13f42e5111fa4fec04be0bf6acc7e59dec3a8c8113e5bb7b604c6dbdc5e8eddc2a1ffb81bc2baf0000000000000000000000000000000018ddb483ae75402852b7f285277ff7308ff78a3364cca8b0e0e1fa9182de275fd55c1e8ec3dbde180379c4280787ba8000000000000000000000000000000000170539890c89a4f91acd59efd413b5d1059f0c8fd8718e8f722e865dd106a4eb02e6fb0cd71b34ebc4b94375b52e4dd60000000000000000000000000000000001c2e9392f5d4b75efc5ff10fe97f37e2671cad7e4710765866e92aec99b0130e6ff1314502d069fb7b5f86bfce4300e0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b090000000000000000000000000000000011dbce34af6cb14d3e4d49f2482e1b058609f25dca79e2ca48e289ace9d1c8db177b7bc35daad79aa5fdac77728bd5fc0000000000000000000000000000000002b51851ef3b44481d13f42e5111fa4fec04be0bf6acc7e59dec3a8c8113e5bb7b604c6dbdc5e8eddc2a1ffb81bc2baf0000000000000000000000000000000018ddb483ae75402852b7f285277ff7308ff78a3364cca8b0e0e1fa9182de275fd55c1e8ec3dbde180379c4280787ba8000000000000000000000000000000000170539890c89a4f91acd59efd413b5d1059f0c8fd8718e8f722e865dd106a4eb02e6fb0cd71b34ebc4b94375b52e4dd60000000000000000000000000000000001c2e9392f5d4b75efc5ff10fe97f37e2671cad7e4710765866e92aec99b0130e6ff1314502d069fb7b5f86bfce4300e,0000000000000000000000000000000000000000000000000000000000000001,161000, -00000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000097ec91c728ae2d290489909bbee1a30048a7fa90bcfd96fe1d9297545867cbfee0939f20f1791329460a4fe1ac719290000000000000000000000000000000011bbc566a10eadf16009c1d2655cfae6adfb0f56f5e55b31dc000414be1b4cee9a0b9f7d9eab4c6829037c327914d5640000000000000000000000000000000009b28329096d8644dfcba6e92477eafff29f7477da4581ce76d1493f03034d7f5d3acaadbe42c76a83ca51db79d456d10000000000000000000000000000000019f75a303fdede5d97f3e521b03ef6b9d7c008d770b59ce3ac38900b340895e008342701ad1b41830b9c010936f4ff1700000000000000000000000000000000161aa1853edbb56fa3bd685c9c6b88e466dfa3c4f194f6774b4d9b1f30b016993bd0d65e8e9d6dea6caa196ff735bd6700000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000108248cdc6f503c7bad30eac875d92a75feccbdbe7b5394f8557a92bb12a796430a2c60ca23c6ecd259e5b01e53891820000000000000000000000000000000011bbc566a10eadf16009c1d2655cfae6adfb0f56f5e55b31dc000414be1b4cee9a0b9f7d9eab4c6829037c327914d5640000000000000000000000000000000009b28329096d8644dfcba6e92477eafff29f7477da4581ce76d1493f03034d7f5d3acaadbe42c76a83ca51db79d456d10000000000000000000000000000000019f75a303fdede5d97f3e521b03ef6b9d7c008d770b59ce3ac38900b340895e008342701ad1b41830b9c010936f4ff1700000000000000000000000000000000161aa1853edbb56fa3bd685c9c6b88e466dfa3c4f194f6774b4d9b1f30b016993bd0d65e8e9d6dea6caa196ff735bd67,0000000000000000000000000000000000000000000000000000000000000001,161000, -000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed302500000000000000000000000000000000130f139ca118869de846d1d938521647b7d27a95b127bbc53578c7b66d88d541adb525e7028a147bf332607bd760deac000000000000000000000000000000000ae7289aa9bf20c4a9c807f2b3ac32f0db24e9a0a360c92e5ce4f8253f0e3e7853f771597c8141d705062bef12d4fea80000000000000000000000000000000001d2f610d79110f93145faad2e34f3408316b1dc3a72852e811b324577d9037035e24af25002ddd100cd9283b70ddcad0000000000000000000000000000000012947315d5c0ec670619125eed0de3dd259a008baee4379b82accf2391e70a2bdad264cda04c3bc1b5394a62559fa0ef000000000000000000000000000000001239e687c4d3417c3c9b655035f8d8a649c255f9a8e6f03b785eed0d416a1cd6ef7c8b45563acb4616af24f64dbccac4000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed30250000000000000000000000000000000006f1fe4d98675ffc62d4d5dd0af9968faca4d0ef425d56fa31b80aea892820e270f6da17aec9eb83c6cc9f84289ecbff000000000000000000000000000000000ae7289aa9bf20c4a9c807f2b3ac32f0db24e9a0a360c92e5ce4f8253f0e3e7853f771597c8141d705062bef12d4fea80000000000000000000000000000000001d2f610d79110f93145faad2e34f3408316b1dc3a72852e811b324577d9037035e24af25002ddd100cd9283b70ddcad0000000000000000000000000000000012947315d5c0ec670619125eed0de3dd259a008baee4379b82accf2391e70a2bdad264cda04c3bc1b5394a62559fa0ef000000000000000000000000000000001239e687c4d3417c3c9b655035f8d8a649c255f9a8e6f03b785eed0d416a1cd6ef7c8b45563acb4616af24f64dbccac4,0000000000000000000000000000000000000000000000000000000000000001,161000, -0000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e70580000000000000000000000000000000010e4280374c532ed0df44ac0bac82572f839afcfb8b696eea617d5bd1261288dfa90a7190200687d470992fb4827ff32000000000000000000000000000000001179ee329771b5913d07818e70f6ce5a58d74ea0b573eaa1bd3d97e45d3eeb27fcc7d37dba127af7a38354cb6ff48f7c000000000000000000000000000000000c898abe6eb76ef99f5143cfb8d840a918bcc9096ce25caa45d0bf5d20814cb01b024f1fd2cbecb6bef65d9456070dd90000000000000000000000000000000008e2a4fd746e86f90484f9b9b7b47b6afe5833762e515ccb276c554f00df88dd9aa0fb792c5f419dda0465cfed838e7c0000000000000000000000000000000012b5e6f7070c0045ade96f548ed6428c5030fa20c6f6f37a42fde9dbb5cd01def0fd8585bf8aeef913e7d42b9ef22efa0000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e705800000000000000000000000000000000091ce9e6c4bab3ad3d275cf5888387646c3d9bb53ace7bd0c118fce3e44fcd96241b58e5af53978272f56d04b7d7ab79000000000000000000000000000000001179ee329771b5913d07818e70f6ce5a58d74ea0b573eaa1bd3d97e45d3eeb27fcc7d37dba127af7a38354cb6ff48f7c000000000000000000000000000000000c898abe6eb76ef99f5143cfb8d840a918bcc9096ce25caa45d0bf5d20814cb01b024f1fd2cbecb6bef65d9456070dd90000000000000000000000000000000008e2a4fd746e86f90484f9b9b7b47b6afe5833762e515ccb276c554f00df88dd9aa0fb792c5f419dda0465cfed838e7c0000000000000000000000000000000012b5e6f7070c0045ade96f548ed6428c5030fa20c6f6f37a42fde9dbb5cd01def0fd8585bf8aeef913e7d42b9ef22efa,0000000000000000000000000000000000000000000000000000000000000001,161000, -0000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000ebdef273e2288c784c061bef6a45cd49b0306ac1e9faab263c6ff73dea4627189c8f10a823253d86a8752769cc4f8f2000000000000000000000000000000000fe2e61bc8e9085d2b472a6791d4851762d6401fd3e7d3f3ba61620dc70b773f2102df1c9d6f1462144662fb2f15359700000000000000000000000000000000031f160cde626ca11f67613884a977fb5d3248d78ddbf23e50e52c3ba4090268c1f6cd8156fa41d848a482a0ca39eb04000000000000000000000000000000000eb61ba51124be7f3ee9be1488aa83cbd2333aa7e09ae67fef63c890534cb37ca7de3d16046b984e72db21e1f5c57a8a0000000000000000000000000000000006bf6f5d65aa7d19613141018ac8bf5d1e6fe494a9f30da215a2313a0241779006bce33a776aeedae5de5ea6ee5a9b9e0000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000b4322c2fb5d5dd2c65b45f74ca75002c97444d8d4e5680d0369d32d180c93b294e30ef42f21ac274f77ad89633ab1b9000000000000000000000000000000000fe2e61bc8e9085d2b472a6791d4851762d6401fd3e7d3f3ba61620dc70b773f2102df1c9d6f1462144662fb2f15359700000000000000000000000000000000031f160cde626ca11f67613884a977fb5d3248d78ddbf23e50e52c3ba4090268c1f6cd8156fa41d848a482a0ca39eb04000000000000000000000000000000000eb61ba51124be7f3ee9be1488aa83cbd2333aa7e09ae67fef63c890534cb37ca7de3d16046b984e72db21e1f5c57a8a0000000000000000000000000000000006bf6f5d65aa7d19613141018ac8bf5d1e6fe494a9f30da215a2313a0241779006bce33a776aeedae5de5ea6ee5a9b9e,0000000000000000000000000000000000000000000000000000000000000001,161000, -000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b10000000000000000000000000000000011efaeec0b1a4057b1e0053263afe40158790229c5bfb08062c90a252f59eca36085ab35e4cbc70483d29880c5c2f8c200000000000000000000000000000000196044a5cdbc5300ee837dca745a44379070e9297697f5db28df4a37307cc740abed45cc778a3f4e3b8c9890ab6c3c70000000000000000000000000000000001176f5de6a3577ad67863bd3d9152ab9e8184964c6ac276e95946788f5a76394047580077c0971d874a40d510eb0443e00000000000000000000000000000000147dd55dff69213c5760e8d22b700dd7a9c7c33c434a3be95bd5281b97b464fb934a3dff7c23f3e59c5d8d26faa426bf0000000000000000000000000000000019efcf03ddb0934b0f0dba3569809d5b48b863d50d3be4973b504244414e1e1db56adff51d33265ce102b320c552781f000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b100000000000000000000000000000000081162fe2e65a642993ba283df9bc8d60bfe495b2dc5623f0467c87bc7570980be2654c8cc8838fb362c677f3a3cb1e900000000000000000000000000000000196044a5cdbc5300ee837dca745a44379070e9297697f5db28df4a37307cc740abed45cc778a3f4e3b8c9890ab6c3c70000000000000000000000000000000001176f5de6a3577ad67863bd3d9152ab9e8184964c6ac276e95946788f5a76394047580077c0971d874a40d510eb0443e00000000000000000000000000000000147dd55dff69213c5760e8d22b700dd7a9c7c33c434a3be95bd5281b97b464fb934a3dff7c23f3e59c5d8d26faa426bf0000000000000000000000000000000019efcf03ddb0934b0f0dba3569809d5b48b863d50d3be4973b504244414e1e1db56adff51d33265ce102b320c552781f,0000000000000000000000000000000000000000000000000000000000000001,161000, -00000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e2728390000000000000000000000000000000006dc2ccb10213d3f6c3f10856888cb2bf6f1c7fcb2a17d6e63596c29281682cafd4c72696ecd6af3cce31c440144ebd10000000000000000000000000000000005d8edbabf37a47a539d84393bb2747d0a35a52b80a7c99616c910479306e204e5db1f0fa3fe69f35af3164c7e5726b50000000000000000000000000000000005015082d6975649fbc172035da04f8aeb6d0dd88fdfac3fbd68ec925dc199413ed670488dc6588f9bd34c4ff527f149000000000000000000000000000000001312d53088ca58dfc325772b8dc0e1b20cebf7b2d5b6b4c560759987b44060bf4a59a68d1a5623bbb3cc5b0bc3986b810000000000000000000000000000000012110cd462c6fabf04f67d652639d19640c46f51aadd6c4f9a6dd7806cffb6192d95c198f4c8284151feaa2e2a0dbc1f00000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e272839000000000000000000000000000000001324e51f295ea95adedc9730dac2e1ab6d85838840e3955103d76677ce9a7359215f8d954286950bed1be3bbfebabeda0000000000000000000000000000000005d8edbabf37a47a539d84393bb2747d0a35a52b80a7c99616c910479306e204e5db1f0fa3fe69f35af3164c7e5726b50000000000000000000000000000000005015082d6975649fbc172035da04f8aeb6d0dd88fdfac3fbd68ec925dc199413ed670488dc6588f9bd34c4ff527f149000000000000000000000000000000001312d53088ca58dfc325772b8dc0e1b20cebf7b2d5b6b4c560759987b44060bf4a59a68d1a5623bbb3cc5b0bc3986b810000000000000000000000000000000012110cd462c6fabf04f67d652639d19640c46f51aadd6c4f9a6dd7806cffb6192d95c198f4c8284151feaa2e2a0dbc1f,0000000000000000000000000000000000000000000000000000000000000001,161000, -000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b0000000000000000000000000000000006d38cc6cc1a950a18e92e16287f201af4c014aba1a17929dd407d0440924ce5f08fad8fe0c50f7f733b285bf282acfc00000000000000000000000000000000117fd5016ddb779a6979d2bffe18032d9a5cdc5a6c7feeaa412381983d49ab894cb067f671163ccbe6225c3d85219db6000000000000000000000000000000000dcf01077dcce35c283bea662f4e4d16f871717eb78e630d9f95a200cc104fe67b0d69d95f6704d9812b46c92b1bc9de00000000000000000000000000000000121f212cd7251697ef6a7e3aa93eb0d7d0157cf1247d4411430c36c7277bf8acfccc4ed8590b5e8d0f760e0e4ed7e95a0000000000000000000000000000000007d22d78b486f575e01e21e1239cbedc4628ba7e01ecf4a3459bd78a9716e2969f26ea3f2449685f60397e1ab2aa7352000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b00000000000000000000000000000000132d85236d655190323279a01acc8cbc6fb736d951e3999589f0559cb61ea93e2e1c526ed08ef08046c3d7a40d7cfdaf00000000000000000000000000000000117fd5016ddb779a6979d2bffe18032d9a5cdc5a6c7feeaa412381983d49ab894cb067f671163ccbe6225c3d85219db6000000000000000000000000000000000dcf01077dcce35c283bea662f4e4d16f871717eb78e630d9f95a200cc104fe67b0d69d95f6704d9812b46c92b1bc9de00000000000000000000000000000000121f212cd7251697ef6a7e3aa93eb0d7d0157cf1247d4411430c36c7277bf8acfccc4ed8590b5e8d0f760e0e4ed7e95a0000000000000000000000000000000007d22d78b486f575e01e21e1239cbedc4628ba7e01ecf4a3459bd78a9716e2969f26ea3f2449685f60397e1ab2aa7352,0000000000000000000000000000000000000000000000000000000000000001,161000, -0000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000fb24d3d4063fd054cd5b7288498f107114ff323226aca58d3336444fc79c010db15094ceda6eb99770c168d459f0da0000000000000000000000000000000000224cbea61c5136987d8dbc8deafa78ae002255c031bb54335bcf99e56a57768aa127506fca1761e8b835e67e88bb4dd0000000000000000000000000000000018cbf072b544df760c051d394ff68ad2dd5a8c731377fa2a5f61e61481ad5b42645704a2d083c7d45ed4774e5448141e000000000000000000000000000000000740b8b7d7bce78a51809713656c94cf98de72887676050f65f74c57cbe574278dd3634c44e057ea95babcc3d230e3c40000000000000000000000000000000006696058a191c7012a4ee7c973c2005ac51af02a85cbb60e3164809a583b4431dda2b59e1c9ceeb652b3ac7021d116a60000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000a4ec4acf91be994fe45f08dbeb2bbd053275861d11a486693fd6e5bfa3736134396f6b1c3ad146642f2e972ba609d0b000000000000000000000000000000000224cbea61c5136987d8dbc8deafa78ae002255c031bb54335bcf99e56a57768aa127506fca1761e8b835e67e88bb4dd0000000000000000000000000000000018cbf072b544df760c051d394ff68ad2dd5a8c731377fa2a5f61e61481ad5b42645704a2d083c7d45ed4774e5448141e000000000000000000000000000000000740b8b7d7bce78a51809713656c94cf98de72887676050f65f74c57cbe574278dd3634c44e057ea95babcc3d230e3c40000000000000000000000000000000006696058a191c7012a4ee7c973c2005ac51af02a85cbb60e3164809a583b4431dda2b59e1c9ceeb652b3ac7021d116a6,0000000000000000000000000000000000000000000000000000000000000001,161000, -0000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe976000000000000000000000000000000000824e1631f054b666893784b1e7edb44b9a53596f718a6e5ba606dc1020cb6e269e9edf828de1768df0dd8ab8440e053000000000000000000000000000000001522e0a4ccd607f117fc6fc8f9abcd704e9850d96adb95d9bfaab210b76bfb2c5dc75163b922bd7a886541250bc1d8630000000000000000000000000000000018a6e4327d633108a292a51abed43e95230e951e4476dc385ceea9c72ed528bf3e06c42d10cefbd4aa75b134936e4747000000000000000000000000000000001198587188e793ad2ec2fa0fa1d0da9b61ed48444fe6722e523aeac270f17f73f56b1e726ab811bb54a6e42e506d70a20000000000000000000000000000000004bedd94182e0f16c71223ac3d68ab327d28ee0ccdcd2c2db07faf69e1babe3fbf3ba09c28b146eca7ab047b592947030000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe9760000000000000000000000000000000011dc30871a7a9b33e2882f6b24ccd192aad215edfc6c6bd9acd064dff4a43f41b4c212068875e896daf127547bbeca58000000000000000000000000000000001522e0a4ccd607f117fc6fc8f9abcd704e9850d96adb95d9bfaab210b76bfb2c5dc75163b922bd7a886541250bc1d8630000000000000000000000000000000018a6e4327d633108a292a51abed43e95230e951e4476dc385ceea9c72ed528bf3e06c42d10cefbd4aa75b134936e4747000000000000000000000000000000001198587188e793ad2ec2fa0fa1d0da9b61ed48444fe6722e523aeac270f17f73f56b1e726ab811bb54a6e42e506d70a20000000000000000000000000000000004bedd94182e0f16c71223ac3d68ab327d28ee0ccdcd2c2db07faf69e1babe3fbf3ba09c28b146eca7ab047b59294703,0000000000000000000000000000000000000000000000000000000000000001,161000, -00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000c2ffe6be05eccd9170b6c181966bb8c1c3ed10e763613112238cabb41370e2a5bb5fef967f4f8f2af944dbef09d265e00000000000000000000000000000000148b7dfc21521d79ff817c7a0305f1048851e283be13c07d5c04d28b571d48172838399ba539529e8d037ffd1f7295580000000000000000000000000000000003015abea326c15098f5205a8b2d3cd74d72dac59d60671ca6ef8c9c714ea61ffdacd46d1024b5b4f7e6b3b569fabaf20000000000000000000000000000000011f0c512fe7dc2dd8abdc1d22c2ecd2e7d1b84f8950ab90fc93bf54badf7bb9a9bad8c355d52a5efb110dca891e4cc3d0000000000000000000000000000000019774010814d1d94caf3ecda3ef4f5c5986e966eaf187c32a8a5a4a59452af0849690cf71338193f2d8435819160bcfb00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000dd1137e592119c134103b9e29e4f14b48387a767d4effae44f807e5b579e7f9c2f60105495f070d0a6ab2410f62844d00000000000000000000000000000000148b7dfc21521d79ff817c7a0305f1048851e283be13c07d5c04d28b571d48172838399ba539529e8d037ffd1f7295580000000000000000000000000000000003015abea326c15098f5205a8b2d3cd74d72dac59d60671ca6ef8c9c714ea61ffdacd46d1024b5b4f7e6b3b569fabaf20000000000000000000000000000000011f0c512fe7dc2dd8abdc1d22c2ecd2e7d1b84f8950ab90fc93bf54badf7bb9a9bad8c355d52a5efb110dca891e4cc3d0000000000000000000000000000000019774010814d1d94caf3ecda3ef4f5c5986e966eaf187c32a8a5a4a59452af0849690cf71338193f2d8435819160bcfb,0000000000000000000000000000000000000000000000000000000000000001,161000, -0000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad6242000000000000000000000000000000000353d6521a17474856ad69582ce225f27d60f5a8319bea8cefded2c3f6b862d76fe633c77ed8ccdf99d2b10430253fc8000000000000000000000000000000000805892f21889cab3cfe62226eaff6a8d3586d4396692b379efc7e90b0eaad4c9afbdf0f56b30f0c07ae0bc4013343b30000000000000000000000000000000007853f0e75c8dee034c2444299da58c98f22de367a90550dbc635fb52c9a8f61ccc100f70f10208944e48d09507fdce100000000000000000000000000000000064afd6b3ef7ff7ec34f1fa330877b42958a46a7698c6d21adf73bfdfcab7793b312e21e5988652e655f2d42edb8a673000000000000000000000000000000000ea8a2217c3dbcc0f6e562de9cb2f334c896577d0b3a7108d96b1aba2d705dbf531e870d4023cec2c0533455013242330000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad62420000000000000000000000000000000016ad3b981f689f51f46e3e5e166986e4e71655dcc1e928327751ffdcfff8934caec5cc37327b3320202c4efbcfda6ae3000000000000000000000000000000000805892f21889cab3cfe62226eaff6a8d3586d4396692b379efc7e90b0eaad4c9afbdf0f56b30f0c07ae0bc4013343b30000000000000000000000000000000007853f0e75c8dee034c2444299da58c98f22de367a90550dbc635fb52c9a8f61ccc100f70f10208944e48d09507fdce100000000000000000000000000000000064afd6b3ef7ff7ec34f1fa330877b42958a46a7698c6d21adf73bfdfcab7793b312e21e5988652e655f2d42edb8a673000000000000000000000000000000000ea8a2217c3dbcc0f6e562de9cb2f334c896577d0b3a7108d96b1aba2d705dbf531e870d4023cec2c053345501324233,0000000000000000000000000000000000000000000000000000000000000001,161000, -00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff170000000000000000000000000000000011798ea9c137acf6ef9483b489c0273d4f69296959922a352b079857953263372b8d339115f0576cfabedc185abf2086000000000000000000000000000000001498b1412f52b07a0e4f91cbf5e1852ea38fc111613523f1e61b97ebf1fd7fd2cdf36d7f73f1e33719c0b63d7bf66b8f0000000000000000000000000000000004c56d3ee9931f7582d7eebeb598d1be208e3b333ab976dc7bb271969fa1d6caf8f467eb7cbee4af5d30e5c66d00a4e2000000000000000000000000000000000de29857dae126c0acbe966da6f50342837ef5dd9994ad929d75814f6f33f77e5b33690945bf6e980031ddd90ebc76ce00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000cbcd06a1c576af16d0d77ff8bcc3669a486d044cc7b85db03661a92f4c5c44a28d028521dfcfc292d8ecd05aed6ab940000000000000000000000000000000011798ea9c137acf6ef9483b489c0273d4f69296959922a352b079857953263372b8d339115f0576cfabedc185abf2086000000000000000000000000000000001498b1412f52b07a0e4f91cbf5e1852ea38fc111613523f1e61b97ebf1fd7fd2cdf36d7f73f1e33719c0b63d7bf66b8f0000000000000000000000000000000004c56d3ee9931f7582d7eebeb598d1be208e3b333ab976dc7bb271969fa1d6caf8f467eb7cbee4af5d30e5c66d00a4e2000000000000000000000000000000000de29857dae126c0acbe966da6f50342837ef5dd9994ad929d75814f6f33f77e5b33690945bf6e980031ddd90ebc76ce00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff170000000000000000000000000000000011798ea9c137acf6ef9483b489c0273d4f69296959922a352b079857953263372b8d339115f0576cfabedc185abf2086000000000000000000000000000000001498b1412f52b07a0e4f91cbf5e1852ea38fc111613523f1e61b97ebf1fd7fd2cdf36d7f73f1e33719c0b63d7bf66b8f00000000000000000000000000000000153ba4ab4fecc724c843b8f78db2db1943e91051b8cb9be2eb7e610a570f1f5925b7981334951b505cce1a3992ff05c9000000000000000000000000000000000c1e79925e9ebfd99e5d11489c56a994e0f855a759f0652cc9bb5151877cfea5c37896f56b949167b9cd2226f14333dd,0000000000000000000000000000000000000000000000000000000000000000,184000, -0000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d3f000000000000000000000000000000001755d8a095e087ca66f8a118e0d2c7d5e4d8427dda8fe3049080f4aff12a8746f8c2679c310f4be0d94c5bef0414a7a600000000000000000000000000000000069c84c6419ed5c0441975ee8410065a56c65f07a4b545ff596b657dc4620c7405fd4d092b281e272773d2281a6359a8000000000000000000000000000000000e751ccbd475fe7eda1c62df626c1d37e8ae6853cc9b2109beef3e8c6f26d41a5e4e0a91bbc3371c7ab6ba780b5db41600000000000000000000000000000000184097644c9b44d543ebc0934825610590cc9f8b17ed08e9c06592bf85591d2702b18cf48a70b378926057e541eb8ac50000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c81292925400000000000000000000000000000000065d3d4be1589a6f00c85c208c44916a35349a096b09219704b4c31861ef58e6b4ea5be57a175b429e482b1038718d6c000000000000000000000000000000001755d8a095e087ca66f8a118e0d2c7d5e4d8427dda8fe3049080f4aff12a8746f8c2679c310f4be0d94c5bef0414a7a600000000000000000000000000000000069c84c6419ed5c0441975ee8410065a56c65f07a4b545ff596b657dc4620c7405fd4d092b281e272773d2281a6359a8000000000000000000000000000000000e751ccbd475fe7eda1c62df626c1d37e8ae6853cc9b2109beef3e8c6f26d41a5e4e0a91bbc3371c7ab6ba780b5db41600000000000000000000000000000000184097644c9b44d543ebc0934825610590cc9f8b17ed08e9c06592bf85591d2702b18cf48a70b378926057e541eb8ac50000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d3f000000000000000000000000000000001755d8a095e087ca66f8a118e0d2c7d5e4d8427dda8fe3049080f4aff12a8746f8c2679c310f4be0d94c5bef0414a7a600000000000000000000000000000000069c84c6419ed5c0441975ee8410065a56c65f07a4b545ff596b657dc4620c7405fd4d092b281e272773d2281a6359a8000000000000000000000000000000000b8bf51e6509e81b70ff44d6e0df8f9f7bc8e33126e9f1b5a8419414878a2209c05df56cf590c8e33f484587f4a1f6950000000000000000000000000000000001c07a85ece4a1c5072fe722fb264bd1d3aaabf9db9809d5a6cb3fe17157d8fd1bfa730a26e34c87279ea81abe141fe6,0000000000000000000000000000000000000000000000000000000000000000,184000, -00000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9af000000000000000000000000000000000213e5d2d46523203ae07f36fdeb6c304fb86f552fb9adb566711c31262629efb0b1561585f85d2ac7be174682229bd8000000000000000000000000000000000b3336b5a4f7c0d16db9615e77bcdd55b7cb5b5c1591d835f34f5c1f1468e3cef954608667fb97a32e4595f43b845612000000000000000000000000000000001869606dde1688e5ae9f1c466c5897fce7794f3735234b5af1ad3617f0688529499bbdc9f0b911840a3d99fd9c49150d00000000000000000000000000000000001bfd33df4a6059608ada794e03d7456e78317145eb4d5677c00d482ac4cf470053d33583cf602feb67b6f972c9973900000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda00000000000000000000000000000000055caff20f9f3f2ea27c64caeb6bb1880f326c64eb6ed6ee7d15da7bfeb16518f76a75f061cd347f7322e0cec634f0fc000000000000000000000000000000000213e5d2d46523203ae07f36fdeb6c304fb86f552fb9adb566711c31262629efb0b1561585f85d2ac7be174682229bd8000000000000000000000000000000000b3336b5a4f7c0d16db9615e77bcdd55b7cb5b5c1591d835f34f5c1f1468e3cef954608667fb97a32e4595f43b845612000000000000000000000000000000001869606dde1688e5ae9f1c466c5897fce7794f3735234b5af1ad3617f0688529499bbdc9f0b911840a3d99fd9c49150d00000000000000000000000000000000001bfd33df4a6059608ada794e03d7456e78317145eb4d5677c00d482ac4cf470053d33583cf602feb67b6f972c9973900000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9af000000000000000000000000000000000213e5d2d46523203ae07f36fdeb6c304fb86f552fb9adb566711c31262629efb0b1561585f85d2ac7be174682229bd8000000000000000000000000000000000b3336b5a4f7c0d16db9615e77bcdd55b7cb5b5c1591d835f34f5c1f1468e3cef954608667fb97a32e4595f43b845612000000000000000000000000000000000197b17c5b695db49c7c8b6fd6f314da7cfdfc4dbe61c76475839c89064870fad5104234c09aee7bafc1660263b6959e0000000000000000000000000000000019e514b65a358640ea90cd3cf547d591f5ff1a13ad99c568ef70c558cbec26dd1e582cc92d849fcfce9749068d361372,0000000000000000000000000000000000000000000000000000000000000000,184000, -000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c30000000000000000000000000000000004ac6e6077d4eddd0e23f30cfd64b7aa1525c85424224e70c15d7535e02aea7a312ef24ba2dcf70b926acb851da2530c0000000000000000000000000000000006ad07d3e8f45cedfb4279913bf0a29e37604810463d6020b4fa8c8c4977d69cffaa33e1149706f04eb237194dcafa520000000000000000000000000000000002c536dd2f05f4a7eaa33fd884262b22a2ab2a88e7b63cb08ebb67fc0f143da7d6b18dd394c424161f7cf703acdc82f50000000000000000000000000000000002d1d9ff74e20ea9b03c478784f57e7a58a21ca2b1e552319f33305f367f5ae4daf8138505f953db4f86c0ec1d96d5f0000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000003c0c619be1382199bee84862a0ac6bf4c891d22f722b6af5bfef0edd1ed8c7e9af5efb5d3fc546801f3e019329ae4e80000000000000000000000000000000004ac6e6077d4eddd0e23f30cfd64b7aa1525c85424224e70c15d7535e02aea7a312ef24ba2dcf70b926acb851da2530c0000000000000000000000000000000006ad07d3e8f45cedfb4279913bf0a29e37604810463d6020b4fa8c8c4977d69cffaa33e1149706f04eb237194dcafa520000000000000000000000000000000002c536dd2f05f4a7eaa33fd884262b22a2ab2a88e7b63cb08ebb67fc0f143da7d6b18dd394c424161f7cf703acdc82f50000000000000000000000000000000002d1d9ff74e20ea9b03c478784f57e7a58a21ca2b1e552319f33305f367f5ae4daf8138505f953db4f86c0ec1d96d5f0000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c30000000000000000000000000000000004ac6e6077d4eddd0e23f30cfd64b7aa1525c85424224e70c15d7535e02aea7a312ef24ba2dcf70b926acb851da2530c0000000000000000000000000000000006ad07d3e8f45cedfb4279913bf0a29e37604810463d6020b4fa8c8c4977d69cffaa33e1149706f04eb237194dcafa5200000000000000000000000000000000173bdb0d0a79f1f2607867ddbf2581b4c1cc20fc0bced60ed8756aa4e79cb87c47fa722b1c8fdbe99a8208fc532327b600000000000000000000000000000000172f37eac49dd7f09adf602ebe562e5d0bd52ee2419fc08dc7fda241c0319b3f43b3ec79ab5aac246a783f13e268d4bb,0000000000000000000000000000000000000000000000000000000000000000,184000, -0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8db000000000000000000000000000000001913ce14bcd1d7bbb47f8efd92d7ffd155ed1990a1dbf1ee7d5e6d592a92bcbec6e865199362950afd6c8fc49b3e10a400000000000000000000000000000000020df729079e76cf06f84e3355e683e093dafad38c2ba92cf7a9faa0515f2f44d814f971046ea20116cc4b0014d7ec350000000000000000000000000000000018db123e05404eea8707f9356f417c3966312b9e41765a6fd8449879ddc4c9850c38434481b235a5bc35db1b8ee86d43000000000000000000000000000000000b4162715717e9065a3849a9294cfe39b351e57ab5a6790f3e725ad9fbf0e4b9d6a3554e872af9c37df33bb896dada5c0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a030000000000000000000000000000000014c22dcacf2e21ff447c94d81067c626b1217e58b7dc98aacab2ea3fc00b1c5e66f660d19f1c69b16571e49d13c8e1d0000000000000000000000000000000001913ce14bcd1d7bbb47f8efd92d7ffd155ed1990a1dbf1ee7d5e6d592a92bcbec6e865199362950afd6c8fc49b3e10a400000000000000000000000000000000020df729079e76cf06f84e3355e683e093dafad38c2ba92cf7a9faa0515f2f44d814f971046ea20116cc4b0014d7ec350000000000000000000000000000000018db123e05404eea8707f9356f417c3966312b9e41765a6fd8449879ddc4c9850c38434481b235a5bc35db1b8ee86d43000000000000000000000000000000000b4162715717e9065a3849a9294cfe39b351e57ab5a6790f3e725ad9fbf0e4b9d6a3554e872af9c37df33bb896dada5c0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8db000000000000000000000000000000001913ce14bcd1d7bbb47f8efd92d7ffd155ed1990a1dbf1ee7d5e6d592a92bcbec6e865199362950afd6c8fc49b3e10a400000000000000000000000000000000020df729079e76cf06f84e3355e683e093dafad38c2ba92cf7a9faa0515f2f44d814f971046ea20116cc4b0014d7ec35000000000000000000000000000000000125ffac343f97afc413ae80d40a309dfe461fe6b20eb84f8eec3a2718ec2c9f1273bcba2fa1ca59fdc924e471173d68000000000000000000000000000000000ebfaf78e267fd93f0e35e0d19feae9db125660a3dde99b028be77c6fac0116a4808aab02a29063c3c0bc4476924d04f,0000000000000000000000000000000000000000000000000000000000000000,184000, -00000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da82577218080000000000000000000000000000000008be924b49e05c45419e328340f1cbcdd3350bacf832a372417d8331c942df200493a3f7f2e46ad2cdaf3544cfd8cd8600000000000000000000000000000000028cd100457f4e930fc0f55996a6b588c5361816bb853d1f522806e5ec1c455eb200343476feeb07ca77e961fc2adc1f000000000000000000000000000000000f6adad0a3bab3610165be2fadb1b020f25488a0af3d418b7d7cf1165812e17aefcbc23308ebcd31d22ba4ca5773dd87000000000000000000000000000000001657ff792e3d89d5d35767bd0cc788411b0420665a5e0704f4d2399b9d9a5ad3c027ee030fdf495e5a6e2a4c69d0571200000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000010338047b7c67c122ffb13466935623ef2338b32bbf5452f78f7abe9a13a16824c11f5520c9dac256b9d257da88d92a30000000000000000000000000000000008be924b49e05c45419e328340f1cbcdd3350bacf832a372417d8331c942df200493a3f7f2e46ad2cdaf3544cfd8cd8600000000000000000000000000000000028cd100457f4e930fc0f55996a6b588c5361816bb853d1f522806e5ec1c455eb200343476feeb07ca77e961fc2adc1f000000000000000000000000000000000f6adad0a3bab3610165be2fadb1b020f25488a0af3d418b7d7cf1165812e17aefcbc23308ebcd31d22ba4ca5773dd87000000000000000000000000000000001657ff792e3d89d5d35767bd0cc788411b0420665a5e0704f4d2399b9d9a5ad3c027ee030fdf495e5a6e2a4c69d0571200000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da82577218080000000000000000000000000000000008be924b49e05c45419e328340f1cbcdd3350bacf832a372417d8331c942df200493a3f7f2e46ad2cdaf3544cfd8cd8600000000000000000000000000000000028cd100457f4e930fc0f55996a6b588c5361816bb853d1f522806e5ec1c455eb200343476feeb07ca77e961fc2adc1f000000000000000000000000000000000a96371995c5333949b5e9869599fcb67222c2e44447d133e9b3e18a9e9e14a92ee03dcba86832cde7d35b35a88bcd240000000000000000000000000000000003a912710b425cc477c43ff93684249649732b1e99270bba725e990559169b505e8411fba174b6a15f90d5b3962f5399,0000000000000000000000000000000000000000000000000000000000000000,184000, -000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e0000000000000000000000000000000000a5048d860b997a9fb352e58284ebbc026622d9be73de79b2807a0c9b431f41f379c255a2db0dd67413c18217cb21b7200000000000000000000000000000000045a701a3f46ca801c02a5419c836b2ab3d74ebd6f4fd1e7dddb1965b49c9a278f6e89950e7c35ebc6724569d34e364c0000000000000000000000000000000004cb55008ccb5b2b8ece69fac7283f5a9ef9e622e2a0e42bed5bdd77faa550882643afc1759b1a327c4f2277e13a3d4f000000000000000000000000000000001690dee40c6c824dc2588fc47dbf93f68ac250b9357e1112db72ded905ed7b101b5f877bdc42d56afb5b6202403a91c4000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000008418a39124b40643dddcd6afe1dbdf930303bca65226c2fee1b95de6e080e25451f8b4f2b2b7c4633e1de6a5a7d47cb000000000000000000000000000000000a5048d860b997a9fb352e58284ebbc026622d9be73de79b2807a0c9b431f41f379c255a2db0dd67413c18217cb21b7200000000000000000000000000000000045a701a3f46ca801c02a5419c836b2ab3d74ebd6f4fd1e7dddb1965b49c9a278f6e89950e7c35ebc6724569d34e364c0000000000000000000000000000000004cb55008ccb5b2b8ece69fac7283f5a9ef9e622e2a0e42bed5bdd77faa550882643afc1759b1a327c4f2277e13a3d4f000000000000000000000000000000001690dee40c6c824dc2588fc47dbf93f68ac250b9357e1112db72ded905ed7b101b5f877bdc42d56afb5b6202403a91c4000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e0000000000000000000000000000000000a5048d860b997a9fb352e58284ebbc026622d9be73de79b2807a0c9b431f41f379c255a2db0dd67413c18217cb21b7200000000000000000000000000000000045a701a3f46ca801c02a5419c836b2ab3d74ebd6f4fd1e7dddb1965b49c9a278f6e89950e7c35ebc6724569d34e364c000000000000000000000000000000001535bce9acb48b6ebc4d3dbb7c236d7cc57d656210e42e9379d4f528fc0ba59bf868503d3bb8e5cd3dafdd881ec56d5c00000000000000000000000000000000037033062d13644c88c317f1c58c18e0d9b4facbbe0701ac8bbdf3c7f0c37b14034c7882d5112a94bea39dfdbfc518e7,0000000000000000000000000000000000000000000000000000000000000000,184000, -0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dcb00000000000000000000000000000000153cec9690a6420a10e5a5a8ca46fd9d9f90e2a139886a07b375eeecce9083a5f5418e6baf64ef0f34176e432bc5343a000000000000000000000000000000000d87c1f37f83ae78a51af9c420e2584a64337d2d2dd8dc3b64f252c521901924e5eec1d9899594db5e64c93c7a01ef020000000000000000000000000000000017078538092ace26cc88b94360871fc9a6bb9992172158ef3a16467919955083accf8d55d48c7ec462a743dbbca7b448000000000000000000000000000000000289b703157a02fc1d687a5aa595495be8bbb3eb0d70554728255a44b7820e0ee82d984d5493c800f1d9d8ca0c9381dc0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000ada13f88a645bc6082c6321e0cf2b7ac45c633fe2f0cb36aeb187fe2e50e7510df2a86b98979e8551636e94488c8ce000000000000000000000000000000000153cec9690a6420a10e5a5a8ca46fd9d9f90e2a139886a07b375eeecce9083a5f5418e6baf64ef0f34176e432bc5343a000000000000000000000000000000000d87c1f37f83ae78a51af9c420e2584a64337d2d2dd8dc3b64f252c521901924e5eec1d9899594db5e64c93c7a01ef020000000000000000000000000000000017078538092ace26cc88b94360871fc9a6bb9992172158ef3a16467919955083accf8d55d48c7ec462a743dbbca7b448000000000000000000000000000000000289b703157a02fc1d687a5aa595495be8bbb3eb0d70554728255a44b7820e0ee82d984d5493c800f1d9d8ca0c9381dc0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dcb00000000000000000000000000000000153cec9690a6420a10e5a5a8ca46fd9d9f90e2a139886a07b375eeecce9083a5f5418e6baf64ef0f34176e432bc5343a000000000000000000000000000000000d87c1f37f83ae78a51af9c420e2584a64337d2d2dd8dc3b64f252c521901924e5eec1d9899594db5e64c93c7a01ef020000000000000000000000000000000002f98cb2305518737e92ee72e2c48d0dbdbbb1f2dc63b9d02d1a8c27dd1ba5a071dc72a8dcc7813b5757bc244357f6630000000000000000000000000000000017775ae72405e39e2db32d5b9db6637b7bbb9799e614bd783f0b785c3f2ee815367e67b15cc037fec8252735f36c28cf,0000000000000000000000000000000000000000000000000000000000000000,184000, -000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6300000000000000000000000000000000057f975064a29ba6ad20d6e6d97a15bd314d6cd419948d974a16923d52b38b9203f95937a0a0493a693099e4fa17ea540000000000000000000000000000000014396ce4abfc32945a6b2b0eb4896a6b19a041d4eae320ba18507ec3828964e56719fffaa47e57ea4a2e3bd1a149b6b600000000000000000000000000000000048b3e4ba3e2d1e0dbf5955101cf038dc22e87b0855a57b631ef119d1bd19d56c38a1d72376284c8598e866b6dba37530000000000000000000000000000000007c0b98cda33be53cf4ef29d0500ff5e7a3c2df6f83dfc1c36211d7f9c696b77dfa6571169cf7935d2fb5a6463cceac6000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a3700000000000000000000000000000000162e49ebd1b50c7837336509e48ace0e7856f00ec45a76b96d1dd88eea300a8118357cafabf32ee2d06b601def523e4800000000000000000000000000000000057f975064a29ba6ad20d6e6d97a15bd314d6cd419948d974a16923d52b38b9203f95937a0a0493a693099e4fa17ea540000000000000000000000000000000014396ce4abfc32945a6b2b0eb4896a6b19a041d4eae320ba18507ec3828964e56719fffaa47e57ea4a2e3bd1a149b6b600000000000000000000000000000000048b3e4ba3e2d1e0dbf5955101cf038dc22e87b0855a57b631ef119d1bd19d56c38a1d72376284c8598e866b6dba37530000000000000000000000000000000007c0b98cda33be53cf4ef29d0500ff5e7a3c2df6f83dfc1c36211d7f9c696b77dfa6571169cf7935d2fb5a6463cceac6000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6300000000000000000000000000000000057f975064a29ba6ad20d6e6d97a15bd314d6cd419948d974a16923d52b38b9203f95937a0a0493a693099e4fa17ea540000000000000000000000000000000014396ce4abfc32945a6b2b0eb4896a6b19a041d4eae320ba18507ec3828964e56719fffaa47e57ea4a2e3bd1a149b6b6000000000000000000000000000000001575d39e959d14b96f261265417ca949a248c3d46e2abb093541c103dadf58cd5b21e28c79f17b376070799492457358000000000000000000000000000000001240585d5f4c28467bccb5193e4aad78ea3b1d8dfb4716a3310fb5215a478aac3f05a8ed478486c9e703a59b9c32bfe5,0000000000000000000000000000000000000000000000000000000000000000,184000, -00000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f18032000000000000000000000000000000000b6069a2c375471d34029d2a776e56b86b0210c35d3eb530bf116205b70995e4929fc90349a7db057168dbe6c39857970000000000000000000000000000000014251a0a154731f73513b99d830f70b6fc4bcf05d11f52d2cbe9795ee8ffc5a5f717ad25770b8ecad6d0e9f8066e0cba000000000000000000000000000000001172684b21c4dfe02a55e13b57bbf105c954daec849d4c6df5276b02872c004fdf09d24f4eef366bc82eb72fe91bf70d000000000000000000000000000000001151aeb9441c5a8fabe80867b5c791420645241eae1400bbcc064d75bedd39de2ef585138fe9f65725efa1b1e5888d0300000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000efe47bf2b11dd10608a309c8aaefdbcbc2bb5e6adceef375371cface8f79668e2b7c2ce9990063a3b53e419e80e2a79000000000000000000000000000000000b6069a2c375471d34029d2a776e56b86b0210c35d3eb530bf116205b70995e4929fc90349a7db057168dbe6c39857970000000000000000000000000000000014251a0a154731f73513b99d830f70b6fc4bcf05d11f52d2cbe9795ee8ffc5a5f717ad25770b8ecad6d0e9f8066e0cba000000000000000000000000000000001172684b21c4dfe02a55e13b57bbf105c954daec849d4c6df5276b02872c004fdf09d24f4eef366bc82eb72fe91bf70d000000000000000000000000000000001151aeb9441c5a8fabe80867b5c791420645241eae1400bbcc064d75bedd39de2ef585138fe9f65725efa1b1e5888d0300000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f18032000000000000000000000000000000000b6069a2c375471d34029d2a776e56b86b0210c35d3eb530bf116205b70995e4929fc90349a7db057168dbe6c39857970000000000000000000000000000000014251a0a154731f73513b99d830f70b6fc4bcf05d11f52d2cbe9795ee8ffc5a5f717ad25770b8ecad6d0e9f8066e0cba00000000000000000000000000000000088ea99f17bb06ba20c5c67aeb8fbbd19b2270986ee7c6517209679e6f84f5d43fa22daf6264c993f1d048d016e3b39e0000000000000000000000000000000008af6330f5638c0a9f339f4e8d841b955e322766457112039b2a852b37d3bc45efb67aeb216a09a8940f5e4e1a771da8,0000000000000000000000000000000000000000000000000000000000000000,184000, -000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f000000000000000000000000000000000161b70d0f384e589d8117938602f3d696f941c24e3c1ca5a9be090b670456c9df315d6fde52daed55c9d8335928a7a3c00000000000000000000000000000000186bb9e6f5ba70dd2c66a641d3b711844977939904c59946d4e9f49ac2d8c00890a43ccb20d4a62bfff63ce4a0a44e8e000000000000000000000000000000001995b9d697bded656236430e78726f0f6ef963db9a5a24d455c12db38aeab0f8629e5dc2d04920156f2a057d69613096000000000000000000000000000000001119b13caf82c18fadcb65c9c166914bfd822534bb9def3feae6c9e572c97c84e97fab3b345cf59358436a404075493d000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c1872290000000000000000000000000000000001099fe889d8f5ddcad09328997c7c3098ef4b4d74ab1d9f6fcbc33a03cafb59c7b28931da67950d1389fbcedca3fb5bb00000000000000000000000000000000161b70d0f384e589d8117938602f3d696f941c24e3c1ca5a9be090b670456c9df315d6fde52daed55c9d8335928a7a3c00000000000000000000000000000000186bb9e6f5ba70dd2c66a641d3b711844977939904c59946d4e9f49ac2d8c00890a43ccb20d4a62bfff63ce4a0a44e8e000000000000000000000000000000001995b9d697bded656236430e78726f0f6ef963db9a5a24d455c12db38aeab0f8629e5dc2d04920156f2a057d69613096000000000000000000000000000000001119b13caf82c18fadcb65c9c166914bfd822534bb9def3feae6c9e572c97c84e97fab3b345cf59358436a404075493d000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f000000000000000000000000000000000161b70d0f384e589d8117938602f3d696f941c24e3c1ca5a9be090b670456c9df315d6fde52daed55c9d8335928a7a3c00000000000000000000000000000000186bb9e6f5ba70dd2c66a641d3b711844977939904c59946d4e9f49ac2d8c00890a43ccb20d4a62bfff63ce4a0a44e8e00000000000000000000000000000000006b5813a1c1f934e8e564a7cad93dc7f57de7a9592aedeb116fa4ed6bc6452bbc0da23be10adfea4ad4fa82969e7a150000000000000000000000000000000008e760ad89fd250a9d5041ec81e51b8b66f5265037e7237f7c4a08bb83e7799f352c54c37cf70a6c61bb95bfbf8a616e,0000000000000000000000000000000000000000000000000000000000000000,184000, -000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5e0000000000000000000000000000000017d1ffcad218efd8b09c68eba34dbbc30b0a62ae250368ee37e5f6fd40479b8580563416afdbd92c0622c341331e20a30000000000000000000000000000000009f0eb3805ed78aa3952a0a437966258ed38cb72912756253a7a2f9113f0dd9a4e187062b0423e0587d93e904d88f50d0000000000000000000000000000000001bca57e985906695e14882f2aaeef75de5009e8717eb59962e978aa11e9d0a4d9a9e203df774cb1e993b1c6ecd6048c000000000000000000000000000000000695b11cc32740c91546eb7d554ca8b1f3afc942ad977345031be8b94b78b57a87ab049ca2d3676e039efccbf24d0c47000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a9000000000000000000000000000000001750d2e78525453f113b76f18abf2334de9755c03786fbc9233cda2364d57ed493f4fe6c2b565f4d82ff8113e9b63c4d0000000000000000000000000000000017d1ffcad218efd8b09c68eba34dbbc30b0a62ae250368ee37e5f6fd40479b8580563416afdbd92c0622c341331e20a30000000000000000000000000000000009f0eb3805ed78aa3952a0a437966258ed38cb72912756253a7a2f9113f0dd9a4e187062b0423e0587d93e904d88f50d0000000000000000000000000000000001bca57e985906695e14882f2aaeef75de5009e8717eb59962e978aa11e9d0a4d9a9e203df774cb1e993b1c6ecd6048c000000000000000000000000000000000695b11cc32740c91546eb7d554ca8b1f3afc942ad977345031be8b94b78b57a87ab049ca2d3676e039efccbf24d0c47000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5e0000000000000000000000000000000017d1ffcad218efd8b09c68eba34dbbc30b0a62ae250368ee37e5f6fd40479b8580563416afdbd92c0622c341331e20a30000000000000000000000000000000009f0eb3805ed78aa3952a0a437966258ed38cb72912756253a7a2f9113f0dd9a4e187062b0423e0587d93e904d88f50d0000000000000000000000000000000018446c6ba126e030ed071f87189cbd618627419c82065d26044759f6e4c7257f45021dfad1dcb34dd06b4e391329a61f00000000000000000000000000000000136b60cd7658a5d135d4bc38edff042570c7824245ed9f7a6414e9e7ab3840a99700fb620e809891b66003340db29e64,0000000000000000000000000000000000000000000000000000000000000000,184000, -000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d79000000000000000000000000000000000546a0cb9d9f1ef9ec4a1e576fa0047557a56c0217baed8691c4085b88c84a0e12d44043aab8671393d02c4a764407ee00000000000000000000000000000000131884c1386980a181353548da9602db70ab495a661e76235c4b0a32b54acb0dfd8846e17bebd731e8041c4aebb8776600000000000000000000000000000000135b3db43511dbd8b3bd5a91880d6da1a2bd1383000e0d6f0a521bf88a5836a3b5f7cb9c0c02aa861a1c2d339f3c11f20000000000000000000000000000000000e1337271bd3302a1cab762161ccfbf2a18b7800e6efe58cf897d4adbfe4cb3bf14f4b59307fffc548179bda70c18bf000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb0000000000000000000000000000000001a28b7856a22db6e79ac4165e60addbb7dfe1f19d815032bc68fea905bd0d7709c2dafc65fe51493c964de678a30d32000000000000000000000000000000000546a0cb9d9f1ef9ec4a1e576fa0047557a56c0217baed8691c4085b88c84a0e12d44043aab8671393d02c4a764407ee00000000000000000000000000000000131884c1386980a181353548da9602db70ab495a661e76235c4b0a32b54acb0dfd8846e17bebd731e8041c4aebb8776600000000000000000000000000000000135b3db43511dbd8b3bd5a91880d6da1a2bd1383000e0d6f0a521bf88a5836a3b5f7cb9c0c02aa861a1c2d339f3c11f20000000000000000000000000000000000e1337271bd3302a1cab762161ccfbf2a18b7800e6efe58cf897d4adbfe4cb3bf14f4b59307fffc548179bda70c18bf000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d79000000000000000000000000000000000546a0cb9d9f1ef9ec4a1e576fa0047557a56c0217baed8691c4085b88c84a0e12d44043aab8671393d02c4a764407ee00000000000000000000000000000000131884c1386980a181353548da9602db70ab495a661e76235c4b0a32b54acb0dfd8846e17bebd731e8041c4aebb877660000000000000000000000000000000006a5d436046e0ac1975e4d24bb3e3f35c1ba3801f37705505cdeb6a86c58bf8068b43462a55155799fe2d2cc60c398b900000000000000000000000000000000191fde77c7c2b397a950f0542d2edd183a5e9404e516146697a755561ab2a9705f970b491e4c0003657d864258f391ec,0000000000000000000000000000000000000000000000000000000000000000,184000, -000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e82000000000000000000000000000000000113dc3354146ca79eb103b31b61fe8bc6f33dcb9c59a7c39d989bd9411c1afce4239034f84e6b00a084be061c73e69c0000000000000000000000000000000000ae33bf68f24978c7ea9fc58d8d76047ec45d01fdbc880e6a5ba02a22a49a3a8253afe0678ecfa6013f4849da3401df70000000000000000000000000000000012c5b00376a1dd31378ec44f2dc8e321e17185d903cfc5c15345a01c33f2f151b21b938d31816550594a7a1e7216c5b00000000000000000000000000000000013d79f825c44775c68e90932d0496a5cae53f04a1edb19f8abeb5948a3dd325dfec4a8b6f58c7fbca9cf3c09b909d8b2000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e270000000000000000000000000000000007c17aaf82c2aa6bf01695157bcd0c2b34734dfbd572b0abe79c8dd3eef7ce6eb9c5e7de55b36ddf87f05e55ba9ac28b00000000000000000000000000000000113dc3354146ca79eb103b31b61fe8bc6f33dcb9c59a7c39d989bd9411c1afce4239034f84e6b00a084be061c73e69c0000000000000000000000000000000000ae33bf68f24978c7ea9fc58d8d76047ec45d01fdbc880e6a5ba02a22a49a3a8253afe0678ecfa6013f4849da3401df70000000000000000000000000000000012c5b00376a1dd31378ec44f2dc8e321e17185d903cfc5c15345a01c33f2f151b21b938d31816550594a7a1e7216c5b00000000000000000000000000000000013d79f825c44775c68e90932d0496a5cae53f04a1edb19f8abeb5948a3dd325dfec4a8b6f58c7fbca9cf3c09b909d8b2000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e82000000000000000000000000000000000113dc3354146ca79eb103b31b61fe8bc6f33dcb9c59a7c39d989bd9411c1afce4239034f84e6b00a084be061c73e69c0000000000000000000000000000000000ae33bf68f24978c7ea9fc58d8d76047ec45d01fdbc880e6a5ba02a22a49a3a8253afe0678ecfa6013f4849da3401df700000000000000000000000000000000073b61e6c2de0969138ce3671582c9b58305c5abefb54cfe13eb3284c2be04d26c906c717fd29aaf60b485e18de8e4fb0000000000000000000000000000000006297267dd3b6f3de2329e837302427ab6235b3ad4a9f8c6bb45795852d3c3c61fe75747bbc78043102fc3f646f5d1f9,0000000000000000000000000000000000000000000000000000000000000000,184000, -000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b811580000000000000000000000000000000006ccaf6c08f831be9c99a97714f5257a985cc2a29b5f5c81bc8d794dd0d8d1a41eb5413bed654c0140dbacfd0dda9e1800000000000000000000000000000000144e9cf91580800dfaa47c98ff7d002a576be76d9e44ae1f8335a3f733e1162af0636372e143174d872c7ea89f4c743900000000000000000000000000000000101e143b838c8a3f5f80fb1412081091b875230f1e2f9cf374d4bcd595392f6daa9552dbb6d5834e27b1b3dafe061ed300000000000000000000000000000000072463400b3e875395a1cdd31d73d51396e34347cd86d9f6f43f42253b3cdb24b89ed7434b1522af95ba1ee2d29ed1bb000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000017147eda83f35d0b6c8894317da5b2e991818479674d7dd1aef6bfaebacbb61ad4b2a17ce7e799939f8c2004af4799530000000000000000000000000000000006ccaf6c08f831be9c99a97714f5257a985cc2a29b5f5c81bc8d794dd0d8d1a41eb5413bed654c0140dbacfd0dda9e1800000000000000000000000000000000144e9cf91580800dfaa47c98ff7d002a576be76d9e44ae1f8335a3f733e1162af0636372e143174d872c7ea89f4c743900000000000000000000000000000000101e143b838c8a3f5f80fb1412081091b875230f1e2f9cf374d4bcd595392f6daa9552dbb6d5834e27b1b3dafe061ed300000000000000000000000000000000072463400b3e875395a1cdd31d73d51396e34347cd86d9f6f43f42253b3cdb24b89ed7434b1522af95ba1ee2d29ed1bb000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b811580000000000000000000000000000000006ccaf6c08f831be9c99a97714f5257a985cc2a29b5f5c81bc8d794dd0d8d1a41eb5413bed654c0140dbacfd0dda9e1800000000000000000000000000000000144e9cf91580800dfaa47c98ff7d002a576be76d9e44ae1f8335a3f733e1162af0636372e143174d872c7ea89f4c74390000000000000000000000000000000009e2fdaeb5f35c5aeb9aaca231439c45ac022875d55575cbf25c15cb6177c6b67416ad22fa7e7cb1924d4c2501f98bd80000000000000000000000000000000012dcaeaa2e415f46b579d9e325d7d7c3cd94083d25fe38c872f1907bbb741aff660d28bb663edd502444e11d2d60d8f0,0000000000000000000000000000000000000000000000000000000000000000,184000, -0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00ccd000000000000000000000000000000001354146aa546754e10ada6e0fe98f04f5f3a3f8a8350d0295e02b8e9c80735b04c3061412e08ddb13c80ac36e5638e540000000000000000000000000000000012ab26513534b4dc1b71eec46b73199c4157ba9369e66fbe4d2d8f62237fc7c6fad31854ebd878f989b8c5cf35c7cfe0000000000000000000000000000000000eb731bc99cdadf7f2280385c7e17d72d34bcbdbdc725d5bc94e841036115e8cb95df08084221696f9be479821fbdd7400000000000000000000000000000000143ba7d3f66445249d9a81a6949f24ff40e7c4d270fa044a8b80200a4369b07806c5497a0ef9e9dbb87b9e63694623ee0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f10000000000000000000000000000000016c1c9ca735535f801c58a9e35a80ce122d20abb327b44db4dea31b899982c4e136a2430c51cf3a31adc5611621f9dde000000000000000000000000000000001354146aa546754e10ada6e0fe98f04f5f3a3f8a8350d0295e02b8e9c80735b04c3061412e08ddb13c80ac36e5638e540000000000000000000000000000000012ab26513534b4dc1b71eec46b73199c4157ba9369e66fbe4d2d8f62237fc7c6fad31854ebd878f989b8c5cf35c7cfe0000000000000000000000000000000000eb731bc99cdadf7f2280385c7e17d72d34bcbdbdc725d5bc94e841036115e8cb95df08084221696f9be479821fbdd7400000000000000000000000000000000143ba7d3f66445249d9a81a6949f24ff40e7c4d270fa044a8b80200a4369b07806c5497a0ef9e9dbb87b9e63694623ee0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00ccd000000000000000000000000000000001354146aa546754e10ada6e0fe98f04f5f3a3f8a8350d0295e02b8e9c80735b04c3061412e08ddb13c80ac36e5638e540000000000000000000000000000000012ab26513534b4dc1b71eec46b73199c4157ba9369e66fbe4d2d8f62237fc7c6fad31854ebd878f989b8c5cf35c7cfe0000000000000000000000000000000000b49e02d9fb238a258f3a4307b6a2f64912b7fa91712b5639de24e90c09f9797654e0f7e2d31e968c040b867de03cd370000000000000000000000000000000005c56a16431ba175ad81260faeac87d8238f86b2828b0e74dbb0b296b34745ac17e6b684a25a16240183619c96b986bd,0000000000000000000000000000000000000000000000000000000000000000,184000, -0000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe90000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000068edef3169c58920509ed4e7069229bd8038a45d2ce5773451cc18b396d2838c9539ecb52298a27eebd714afacb907c0000000000000000000000000000000004c5346765a62f2d2e700aadccf747acb3322c250435ce2cf358c08f1e286427cabace052327c4b30135c8482c5c0eb90000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be100000000000000000000000000000000084486ebc81878331aab7d6f53ca3c773fda7b181b56a93e5ee0bfa189afbb7fd7a05c5bea35ec1054c0e1ddc2e2dac20000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000068edef3169c58920509ed4e7069229bd8038a45d2ce5773451cc18b396d2838c9539ecb52298a27eebd714afacb907c0000000000000000000000000000000004c5346765a62f2d2e700aadccf747acb3322c250435ce2cf358c08f1e286427cabace052327c4b30135c8482c5c0eb90000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe90000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000137232f722e38e084611ba67d2e28a3b8c73c13f20b6bb4c22141115bd43cdeb555861335f2a75d7cb418eb505341a2f00000000000000000000000000000000153bdd82d3d9b76d1cab9d087654652ab1451f5fef4f449273d81211d88891fc53f131f98e2c3b4cb8c937b7d3a39bf20000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be100000000000000000000000000000000084486ebc81878331aab7d6f53ca3c773fda7b181b56a93e5ee0bfa189afbb7fd7a05c5bea35ec1054c0e1ddc2e2dac20000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000137232f722e38e084611ba67d2e28a3b8c73c13f20b6bb4c22141115bd43cdeb555861335f2a75d7cb418eb505341a2f00000000000000000000000000000000153bdd82d3d9b76d1cab9d087654652ab1451f5fef4f449273d81211d88891fc53f131f98e2c3b4cb8c937b7d3a39bf2,0000000000000000000000000000000000000000000000000000000000000001,207000, -00000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193a000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000f6fdc4e5dceb555c9eb4c912fedbfb3cb1b842345f73ded02cfaf8d397c4378809721094aa4a4113a368e0787effeb500000000000000000000000000000000143ac06258c579c11c05569669a2a10babc63ecc86f85c91791d8ea48af700a2067c5f13d2700b8d5cf59bcca8fbf7c600000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000a5b95d6031e92578f6b5de5b8873e87486fd818214be93814753dcf6665229758248a6529892265fcc2b2ba45f89171000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000f6fdc4e5dceb555c9eb4c912fedbfb3cb1b842345f73ded02cfaf8d397c4378809721094aa4a4113a368e0787effeb500000000000000000000000000000000143ac06258c579c11c05569669a2a10babc63ecc86f85c91791d8ea48af700a2067c5f13d2700b8d5cf59bcca8fbf7c600000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193a000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000a91359bdbb1314481305b25135ded23995bc761ad8dd4d264612313bd34b2ab9e14def566af5bee7fc871f8780fabf60000000000000000000000000000000005c65187e0ba6cd92f16511fd9a90bcbb8b10cb86c8cb62dee1343fc6bb9f582182fa0eadee3f4725d0964335703b2e500000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000a5b95d6031e92578f6b5de5b8873e87486fd818214be93814753dcf6665229758248a6529892265fcc2b2ba45f89171000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000a91359bdbb1314481305b25135ded23995bc761ad8dd4d264612313bd34b2ab9e14def566af5bee7fc871f8780fabf60000000000000000000000000000000005c65187e0ba6cd92f16511fd9a90bcbb8b10cb86c8cb62dee1343fc6bb9f582182fa0eadee3f4725d0964335703b2e5,0000000000000000000000000000000000000000000000000000000000000001,207000, -000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece173822000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc500000000000000000000000000000000192f3e8ae2588f9223de77f5e872115f1edec96d6a0f403a47879410c2562e79853c9a706e423b83fbf3154234edb6f80000000000000000000000000000000015084258d58fd1a07bbdb2e90df5a56ae15a787037eff4fe55f660e45f04820c6fc8982303b5e82074cf0cdcbde61307000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b60000000000000000000000000000000002a534a7e1432aa317f782a581f974d23ec75420611165d0486ecd377660fa7c2e8235f829c64501d1b9bf3131e87289000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc500000000000000000000000000000000192f3e8ae2588f9223de77f5e872115f1edec96d6a0f403a47879410c2562e79853c9a706e423b83fbf3154234edb6f80000000000000000000000000000000015084258d58fd1a07bbdb2e90df5a56ae15a787037eff4fe55f660e45f04820c6fc8982303b5e82074cf0cdcbde61307000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece173822000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc50000000000000000000000000000000000d1d35f57275708273d2fc05ad99b78459882178975d2851fa93e90345ac7aa996f658e4311c47bbe0beabdcb11f3b30000000000000000000000000000000004f8cf9163f014f9cf5df4cd3556076c831cd314bb951dc1113a71bc97ac7417aee367dbad9e17df452ff323421997a4000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b60000000000000000000000000000000002a534a7e1432aa317f782a581f974d23ec75420611165d0486ecd377660fa7c2e8235f829c64501d1b9bf3131e87289000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc50000000000000000000000000000000000d1d35f57275708273d2fc05ad99b78459882178975d2851fa93e90345ac7aa996f658e4311c47bbe0beabdcb11f3b30000000000000000000000000000000004f8cf9163f014f9cf5df4cd3556076c831cd314bb951dc1113a71bc97ac7417aee367dbad9e17df452ff323421997a4,0000000000000000000000000000000000000000000000000000000000000001,207000, -0000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10c000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f000000000000000000000000000000000477b55bd7fff14e0d1807bfc21edb9481be01c12abb1460d78b1aafe42953730167e32e694c2ddfb0d442e8cea57d460000000000000000000000000000000004b884c6ea36f189dbc3c0e9cf88f08baf5d868579998f63b752e61fcce3cf2c901bb9b51959d3597c4ef53cff41fc260000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000d4197b85280f1a5e4cfdd6a7acce516b34a5e12cf55081a858a2ad517d12733aa294a2ca1adf81bc9bf22922fbfd99f000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f000000000000000000000000000000000477b55bd7fff14e0d1807bfc21edb9481be01c12abb1460d78b1aafe42953730167e32e694c2ddfb0d442e8cea57d460000000000000000000000000000000004b884c6ea36f189dbc3c0e9cf88f08baf5d868579998f63b752e61fcce3cf2c901bb9b51959d3597c4ef53cff41fc260000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10c000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f0000000000000000000000000000000015895c8e617ff54c3e039ff6812cd142e2b949c3c8c9fe5e8fa5b7f11287a2b11d441cd04807d220092abd17315a2d650000000000000000000000000000000015488d234f48f5106f57e6cc73c2bc4bb519c4ff79eb835bafddec8129cd26f78e90464997fa2ca63db00ac300bdae850000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000d4197b85280f1a5e4cfdd6a7acce516b34a5e12cf55081a858a2ad517d12733aa294a2ca1adf81bc9bf22922fbfd99f000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f0000000000000000000000000000000015895c8e617ff54c3e039ff6812cd142e2b949c3c8c9fe5e8fa5b7f11287a2b11d441cd04807d220092abd17315a2d650000000000000000000000000000000015488d234f48f5106f57e6cc73c2bc4bb519c4ff79eb835bafddec8129cd26f78e90464997fa2ca63db00ac300bdae85,0000000000000000000000000000000000000000000000000000000000000001,207000, -0000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119bf00000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac800000000000000000000000000000000183b7b917aaaa94f0ea9959273ed4701102346be2a9d72531bd18fef908ecb0579a6ac10ed42a91f1147fc3a05b2e81900000000000000000000000000000000070983b1582a97d9797782e4f960a298aaa8ec509720495acdbf176d8ecb9ec9e041c2b5ed6b7dfb46fdeaae3fb341500000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa00000000000000000000000000000000145688bf2f7a76a4341564a725a16dd5009b4a5174d766e6bf337a8bcbb11c797b82173d92aa796da6b168e734be90ec00000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac800000000000000000000000000000000183b7b917aaaa94f0ea9959273ed4701102346be2a9d72531bd18fef908ecb0579a6ac10ed42a91f1147fc3a05b2e81900000000000000000000000000000000070983b1582a97d9797782e4f960a298aaa8ec509720495acdbf176d8ecb9ec9e041c2b5ed6b7dfb46fdeaae3fb341500000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119bf00000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac80000000000000000000000000000000001c59658bed53d4b3c721223cf5e65d6545404c6c8e7a06c4b5f42b166222b1ea50553edc41156e0a8b703c5fa4cc2920000000000000000000000000000000012f78e38e1554ec0d1a424d149eb0a3eb9ce5f345c64c9649971bb3367e5575a3e6a3d48c3e8820473011551c04c695b0000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa00000000000000000000000000000000145688bf2f7a76a4341564a725a16dd5009b4a5174d766e6bf337a8bcbb11c797b82173d92aa796da6b168e734be90ec00000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac80000000000000000000000000000000001c59658bed53d4b3c721223cf5e65d6545404c6c8e7a06c4b5f42b166222b1ea50553edc41156e0a8b703c5fa4cc2920000000000000000000000000000000012f78e38e1554ec0d1a424d149eb0a3eb9ce5f345c64c9649971bb3367e5575a3e6a3d48c3e8820473011551c04c695b,0000000000000000000000000000000000000000000000000000000000000001,207000, -0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b2000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b0000000000000000000000000000000010f7bf9f6711ba723bb71a004a90109ee22be6643d56d410da18103ef44a1b3d50f10c4b94222c7f05fd3c28acbdc8ee00000000000000000000000000000000007af41f09e6d0adcb1935d6a93ea1f6156fa0157a63f265a3a7ceffe82f6635b8511e7e8f21e8f3be7a73513ff597b10000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a030000000000000000000000000000000003942eae34fd3104cead334a2cbb2131eaa10b59d07949479331a8f4cc66761cd5d23eb8a861ae618f3a2e01b25080f9000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b0000000000000000000000000000000010f7bf9f6711ba723bb71a004a90109ee22be6643d56d410da18103ef44a1b3d50f10c4b94222c7f05fd3c28acbdc8ee00000000000000000000000000000000007af41f09e6d0adcb1935d6a93ea1f6156fa0157a63f265a3a7ceffe82f6635b8511e7e8f21e8f3be7a73513ff597b10000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b2000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b000000000000000000000000000000000909524ad26e2c280f648db5f8bb9c38824b6520b62e3eae8d18c2620266dae6cdbaf3b31d31d380b401c3d75341e1bd0000000000000000000000000000000019861dcb2f9915ec800271df9a0d0ae14f07ab6f79212059c38903a10e818fee665ae1802232170bfb848caec00a12fa0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a030000000000000000000000000000000003942eae34fd3104cead334a2cbb2131eaa10b59d07949479331a8f4cc66761cd5d23eb8a861ae618f3a2e01b25080f9000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b000000000000000000000000000000000909524ad26e2c280f648db5f8bb9c38824b6520b62e3eae8d18c2620266dae6cdbaf3b31d31d380b401c3d75341e1bd0000000000000000000000000000000019861dcb2f9915ec800271df9a0d0ae14f07ab6f79212059c38903a10e818fee665ae1802232170bfb848caec00a12fa,0000000000000000000000000000000000000000000000000000000000000001,207000, -00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0b0000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a0000000000000000000000000000000003a6cc74cc398f38d535b4341faa37c968daf2009c3f05ace1f938b33bbe4002d81d18d30c2c856b21afe7a22b83c37a000000000000000000000000000000000452d1b2da6392f9df1bfd35e4575c565333703b2f83f56e0a88a0c8195968c5321296b07f6750584e23597304a5472e00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f60463000000000000000000000000000000000575bd953fc6600f5b48faea1032cf2b6615bf34cc9c526fdcc5042a292812d35fef2884bf51e017eb24c174b2bc20a00000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a0000000000000000000000000000000003a6cc74cc398f38d535b4341faa37c968daf2009c3f05ace1f938b33bbe4002d81d18d30c2c856b21afe7a22b83c37a000000000000000000000000000000000452d1b2da6392f9df1bfd35e4575c565333703b2f83f56e0a88a0c8195968c5321296b07f6750584e23597304a5472e00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0b0000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a00000000000000000000000000000000165a45756d46576175e5f38223a1750dfb9c598457460d12853799edbaf2b621468ee72ba5277a94984f185dd47be7310000000000000000000000000000000015ae40375f1c53a06bffaa805ef450811143db49c4011d515ca831d8dd578d5eec99694e31ecafa76bdba68cfb5a637d00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f60463000000000000000000000000000000000575bd953fc6600f5b48faea1032cf2b6615bf34cc9c526fdcc5042a292812d35fef2884bf51e017eb24c174b2bc20a00000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a00000000000000000000000000000000165a45756d46576175e5f38223a1750dfb9c598457460d12853799edbaf2b621468ee72ba5277a94984f185dd47be7310000000000000000000000000000000015ae40375f1c53a06bffaa805ef450811143db49c4011d515ca831d8dd578d5eec99694e31ecafa76bdba68cfb5a637d,0000000000000000000000000000000000000000000000000000000000000001,207000, -0000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751a0000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf780000000000000000000000000000000007a9cf48dbe005c5c59b2c731cf4117e5fadc9cb2cd8f486f1ed58b2909092ee8f36d88b8f719db94715641b418ab4240000000000000000000000000000000004ba40d4766b91bf8da1cc2526f62791a1b5f6fc24ffc54b522dd30cde2d29a6a6f81e8429d518710843d43705f3b4e60000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a000000000000000000000000000000000032e4fbb8dab462ff0352c2d3925b0e97ca662189129928ccc1714364e4f01d8b026887d808342091ad442b6e11635910000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf780000000000000000000000000000000007a9cf48dbe005c5c59b2c731cf4117e5fadc9cb2cd8f486f1ed58b2909092ee8f36d88b8f719db94715641b418ab4240000000000000000000000000000000004ba40d4766b91bf8da1cc2526f62791a1b5f6fc24ffc54b522dd30cde2d29a6a6f81e8429d518710843d43705f3b4e60000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751a0000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf7800000000000000000000000000000000125742a15d9fe0d485807b4326579b5904c981b9c6ac1e38754379ee662063358f75277321e2624672e99be4be74f687000000000000000000000000000000001546d115c31454dabd79db911c558545c2c15488ce854d741502ff941883cc7d77b3e17a877ee78eb1bb2bc8fa0bf5c50000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a000000000000000000000000000000000032e4fbb8dab462ff0352c2d3925b0e97ca662189129928ccc1714364e4f01d8b026887d808342091ad442b6e11635910000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf7800000000000000000000000000000000125742a15d9fe0d485807b4326579b5904c981b9c6ac1e38754379ee662063358f75277321e2624672e99be4be74f687000000000000000000000000000000001546d115c31454dabd79db911c558545c2c15488ce854d741502ff941883cc7d77b3e17a877ee78eb1bb2bc8fa0bf5c5,0000000000000000000000000000000000000000000000000000000000000001,207000, -0000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df910000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c7952820000000000000000000000000000000008ceb842a17953578013ceee519a28ef1b37f73e13564def5ffe08a64dc53aa680784e26138176c89269477ee003d16700000000000000000000000000000000159791b6f2c26ed611ca40bfbd2059c15cfec9d073a84254ad9b509ef786d62d17fdc67ab13092cf0b7b3482866f4c320000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000ae6134f1fec83a52e5358db260eb9dc6b918f7a803aae5715854ebee2b9bbecea9ab0d955f2e13e2c47a96b234ecb1a0000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c7952820000000000000000000000000000000008ceb842a17953578013ceee519a28ef1b37f73e13564def5ffe08a64dc53aa680784e26138176c89269477ee003d16700000000000000000000000000000000159791b6f2c26ed611ca40bfbd2059c15cfec9d073a84254ad9b509ef786d62d17fdc67ab13092cf0b7b3482866f4c320000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df910000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c79528200000000000000000000000000000000113259a798069342cb07d8c7f1b183e8493f5446e02ec4d00732c9faa8ebbb7d9e33b1d89dd289372795b8811ffbd944000000000000000000000000000000000469803346bd77c4395166f6862b5316077881b47fdcd06ab9958201ff2a1ff706ae398400236d30ae83cb7d79905e790000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000ae6134f1fec83a52e5358db260eb9dc6b918f7a803aae5715854ebee2b9bbecea9ab0d955f2e13e2c47a96b234ecb1a0000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c79528200000000000000000000000000000000113259a798069342cb07d8c7f1b183e8493f5446e02ec4d00732c9faa8ebbb7d9e33b1d89dd289372795b8811ffbd944000000000000000000000000000000000469803346bd77c4395166f6862b5316077881b47fdcd06ab9958201ff2a1ff706ae398400236d30ae83cb7d79905e79,0000000000000000000000000000000000000000000000000000000000000001,207000, -000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c04000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf000000000000000000000000000000001272c63693873e1dabe2c2739310f627d3d9b5bcaa615402c3849ffd8dfe72b40fea4a068064655f2c8f46f074e6518d0000000000000000000000000000000000161a8e5e1de10938e5bce241ae73d76173022127822d744b23e656095c28f2f8d142ceb48b72a1dbc36b6143f8af95000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000000d9bd58946a4d26e3f97e5fe96e574d6f93562c0fb0c187c0c586208fe9a4d9383d3ca22b272ff3eb7e624ad7fb9ea7000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf000000000000000000000000000000001272c63693873e1dabe2c2739310f627d3d9b5bcaa615402c3849ffd8dfe72b40fea4a068064655f2c8f46f074e6518d0000000000000000000000000000000000161a8e5e1de10938e5bce241ae73d76173022127822d744b23e656095c28f2f8d142ceb48b72a1dbc36b6143f8af95000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c04000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf00000000000000000000000000000000078e4bb3a5f8a87c9f38e542b03ab6af909d95c84923bebca3ac32a368b283700ec1b5f830ef9aa08d6fb90f8b19591e0000000000000000000000000000000019eaf75bdb6205911235ead4019d390003044963cc02e54b1c0cec4aed54cd3125dabd2ffcc88d5dde3b949ebc06fb16000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000000d9bd58946a4d26e3f97e5fe96e574d6f93562c0fb0c187c0c586208fe9a4d9383d3ca22b272ff3eb7e624ad7fb9ea7000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf00000000000000000000000000000000078e4bb3a5f8a87c9f38e542b03ab6af909d95c84923bebca3ac32a368b283700ec1b5f830ef9aa08d6fb90f8b19591e0000000000000000000000000000000019eaf75bdb6205911235ead4019d390003044963cc02e54b1c0cec4aed54cd3125dabd2ffcc88d5dde3b949ebc06fb16,0000000000000000000000000000000000000000000000000000000000000001,207000, -000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e2946000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a220000000000000000000000000000000018a236ea02b1971d6e193a6eb92e1298956679d86864042fb6a0c36dd91c0e385944d779dedd0149fa8a1b3d6a07949d00000000000000000000000000000000048eac7d116b5a7906bce070e2b51ee7c4c493f1415abdb6fd2d35676036d3b741d14b7135419645a6906018e9d3f150000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000f77a58fb4b4165bf86d30b6349b84780d72b24e8eddce16c73a1f5a06de0638045a64978eb9c477d806f1955e818165000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a220000000000000000000000000000000018a236ea02b1971d6e193a6eb92e1298956679d86864042fb6a0c36dd91c0e385944d779dedd0149fa8a1b3d6a07949d00000000000000000000000000000000048eac7d116b5a7906bce070e2b51ee7c4c493f1415abdb6fd2d35676036d3b741d14b7135419645a6906018e9d3f150000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e2946000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a2200000000000000000000000000000000015edb0036ce4f7cdd026d478a1d9a3ecf10d1ac8b210e8fb0900f331d94e7ebc5672884d276feb5bf74e4c295f8160e000000000000000000000000000000001572656d28148c21445ec74560968def9fb2b793b22a55086a039d39967a226cdcdab48d7c1269ba136e9fe7162bb95b000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000f77a58fb4b4165bf86d30b6349b84780d72b24e8eddce16c73a1f5a06de0638045a64978eb9c477d806f1955e818165000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a2200000000000000000000000000000000015edb0036ce4f7cdd026d478a1d9a3ecf10d1ac8b210e8fb0900f331d94e7ebc5672884d276feb5bf74e4c295f8160e000000000000000000000000000000001572656d28148c21445ec74560968def9fb2b793b22a55086a039d39967a226cdcdab48d7c1269ba136e9fe7162bb95b,0000000000000000000000000000000000000000000000000000000000000001,207000, -000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d318400000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c95384000000000000000000000000000000000003c96c6f20d7ac31ee7ca77d11e8d25ea78cdf13e5f4d317752320e059e19196f14c15b5a18ca712f3a7cc6f09be6d4000000000000000000000000000000000ebd71f61fcddf1652675f577bbaeec26b892dd954965b057ffb431d6e37cc5425a2a42a0059482c2bd75adb2a120b0b000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000018bc060c3f6be35b724dee72bcda5cc376dc1f35561f7e70c9fe11eda256edd30aca8c19018433483186beb5b9b2792700000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c95384000000000000000000000000000000000003c96c6f20d7ac31ee7ca77d11e8d25ea78cdf13e5f4d317752320e059e19196f14c15b5a18ca712f3a7cc6f09be6d4000000000000000000000000000000000ebd71f61fcddf1652675f577bbaeec26b892dd954965b057ffb431d6e37cc5425a2a42a0059482c2bd75adb2a120b0b000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d318400000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c9538400000000000000000000000000000000019c47b2347726bd72c33dd3e722d1fb179fe7d93b525c58defdea092f112dd0aaf973ea3573b358e8ac483390f63c3d7000000000000000000000000000000000b439ff419b20783f8b4485ec790be14f8ee1dab9eeeb7b9e7358f83887929cff9095bd4b0fab7d38e27a524d5ed9fa0000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000018bc060c3f6be35b724dee72bcda5cc376dc1f35561f7e70c9fe11eda256edd30aca8c19018433483186beb5b9b2792700000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c9538400000000000000000000000000000000019c47b2347726bd72c33dd3e722d1fb179fe7d93b525c58defdea092f112dd0aaf973ea3573b358e8ac483390f63c3d7000000000000000000000000000000000b439ff419b20783f8b4485ec790be14f8ee1dab9eeeb7b9e7358f83887929cff9095bd4b0fab7d38e27a524d5ed9fa0,0000000000000000000000000000000000000000000000000000000000000001,207000, -000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae920000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e4000000000000000000000000000000000174ffb89d7715866562d9882acb81ce40758644ca3e0decd546c8f5c349b24fce88214956e7540fac36bcfc105cf34a0000000000000000000000000000000003e06c5f607ccf1e2991828034fcdf91106295e7174b4dca21926169451ee58e737d535af45073e2378206e03c81c421000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f3555650000000000000000000000000000000007f7dc55c90fa181c55c9b83b7736ee84b3f19d960318e75661dd22c0546d62f4c9e07b915f9295a3c9fe6a62c84fc190000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e4000000000000000000000000000000000174ffb89d7715866562d9882acb81ce40758644ca3e0decd546c8f5c349b24fce88214956e7540fac36bcfc105cf34a0000000000000000000000000000000003e06c5f607ccf1e2991828034fcdf91106295e7174b4dca21926169451ee58e737d535af45073e2378206e03c81c421000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae920000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e400000000000000000000000000000000188c12319c08d113e5b8ce2e18802b092401c540294704d291ea09ab336743d45023deb55a6cabf00dc84303efa2b761000000000000000000000000000000001620a58ad903177c218a25360e4ecd465414b59ddc39c4f5459e7137b1921095ab2eaca3bd038c1d827cf91fc37de68a000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f3555650000000000000000000000000000000007f7dc55c90fa181c55c9b83b7736ee84b3f19d960318e75661dd22c0546d62f4c9e07b915f9295a3c9fe6a62c84fc190000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e400000000000000000000000000000000188c12319c08d113e5b8ce2e18802b092401c540294704d291ea09ab336743d45023deb55a6cabf00dc84303efa2b761000000000000000000000000000000001620a58ad903177c218a25360e4ecd465414b59ddc39c4f5459e7137b1921095ab2eaca3bd038c1d827cf91fc37de68a,0000000000000000000000000000000000000000000000000000000000000001,207000, -0000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b1700000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000b15d92f2301075ab0e3215aa72cf9b130bc8e1bcd9fa36375c4b9d7da430ae3e2b24f417336d8729f44542ee7f561d300000000000000000000000000000000197d90090501e8cdea28eb7963231f1a7b5f716cc3a086acb6e7626600d6544132cac943e8d5cefb5daf0a2f8d4006290000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce0000000000000000000000000000000005b1ce5cb2ae0e9175f2bd557d7869233d65008e0f47c52914fa44c4a6234b70eed236bc5499bb0412d0cbb61c98f93b0000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000b15d92f2301075ab0e3215aa72cf9b130bc8e1bcd9fa36375c4b9d7da430ae3e2b24f417336d8729f44542ee7f561d300000000000000000000000000000000197d90090501e8cdea28eb7963231f1a7b5f716cc3a086acb6e7626600d6544132cac943e8d5cefb5daf0a2f8d4006290000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b1700000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000eeb38bb167edf3f9a38865b9c1eb32633babd6925e56f5bf16c18c91c6deb403bf9b0bd3e1d278d1abaabd1180a48d800000000000000000000000000000000008381e1347dfdcc60f2bc3ce0288dbce917da182fe48c12b049703af5daa1e2ebe136bac87e31045c4ff5d072bfa4820000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce0000000000000000000000000000000005b1ce5cb2ae0e9175f2bd557d7869233d65008e0f47c52914fa44c4a6234b70eed236bc5499bb0412d0cbb61c98f93b0000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000eeb38bb167edf3f9a38865b9c1eb32633babd6925e56f5bf16c18c91c6deb403bf9b0bd3e1d278d1abaabd1180a48d800000000000000000000000000000000008381e1347dfdcc60f2bc3ce0288dbce917da182fe48c12b049703af5daa1e2ebe136bac87e31045c4ff5d072bfa482,0000000000000000000000000000000000000000000000000000000000000001,207000, -00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ec0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf2300000000000000000000000000000000005c35f3372f1ec9845bd04ea722fbed2be1388abf59e622dd3dafb4b3af49bc5fba9e20235e7e58973fedf4b8b720691000000000000000000000000000000001111d18d621070509805d306a31c109701288fd55d4c0644349deb080c6591b6e852b4f7e009b80019513de7f2fce17d00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d0000000000000000000000000000000007efcb9da7b7ff0f4a1d92489ad76c59158bcc42c5c7a93067772a6d9ef1d3b6df9360d0fc1214e7dec02aaaf7b118bf0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf2300000000000000000000000000000000005c35f3372f1ec9845bd04ea722fbed2be1388abf59e622dd3dafb4b3af49bc5fba9e20235e7e58973fedf4b8b720691000000000000000000000000000000001111d18d621070509805d306a31c109701288fd55d4c0644349deb080c6591b6e852b4f7e009b80019513de7f2fce17d00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ec0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf23000000000000000000000000000000000143db2b6c68dfa02055ea2cbd11bee04a663c2d8fde6b0919355d755bbbc5a5e23021dfc7b6c1a76460020b4748da41a0000000000000000000000000000000008ef405cd76f7649b315d4afa02f9c40634ebbaf96390c7b3292e798ea4b646d36594b06d14a47ffa0adc2180d02c92e00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d0000000000000000000000000000000007efcb9da7b7ff0f4a1d92489ad76c59158bcc42c5c7a93067772a6d9ef1d3b6df9360d0fc1214e7dec02aaaf7b118bf0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf23000000000000000000000000000000000143db2b6c68dfa02055ea2cbd11bee04a663c2d8fde6b0919355d755bbbc5a5e23021dfc7b6c1a76460020b4748da41a0000000000000000000000000000000008ef405cd76f7649b315d4afa02f9c40634ebbaf96390c7b3292e798ea4b646d36594b06d14a47ffa0adc2180d02c92e,0000000000000000000000000000000000000000000000000000000000000001,207000, -0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c5000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000000e7e532ee4b892af39f8a3db7a05cc77a6eb0b3d977c17076bac4a52d5ba003a0ac1f902a4257791a45370eb88426a70000000000000000000000000000000016a556050e4905fa74b5061e3874f05cc7a6c5b049bd3bb7c34adef5a77c393239a600542a4401c3e61978ee6515a30e0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000eb29e948adc9e1816c67a7865517fbc91610b2eb30da1d8a1e15c5f62e71a1fd1f40d4d59b23bea7edeba79829010e6000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000000e7e532ee4b892af39f8a3db7a05cc77a6eb0b3d977c17076bac4a52d5ba003a0ac1f902a4257791a45370eb88426a70000000000000000000000000000000016a556050e4905fa74b5061e3874f05cc7a6c5b049bd3bb7c34adef5a77c393239a600542a4401c3e61978ee6515a30e0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c5000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000019192cb74b345d6f577c1d788bab500fea089ad11a0d514ef0760dfbc95556207dffe06e8711a8869fb9c8f1477b840400000000000000000000000000000000035bbbe52b36e09fd666a1980ad6bc7a9cd085d4a9c7d707a3e5f3ab4f34bcf1e505ffaa870ffe3bd3e587119aea079d0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000eb29e948adc9e1816c67a7865517fbc91610b2eb30da1d8a1e15c5f62e71a1fd1f40d4d59b23bea7edeba79829010e6000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000019192cb74b345d6f577c1d788bab500fea089ad11a0d514ef0760dfbc95556207dffe06e8711a8869fb9c8f1477b840400000000000000000000000000000000035bbbe52b36e09fd666a1980ad6bc7a9cd085d4a9c7d707a3e5f3ab4f34bcf1e505ffaa870ffe3bd3e587119aea079d,0000000000000000000000000000000000000000000000000000000000000001,207000, -000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d2900000000000000000000000000000000179c34ba9578d5ff90272a2c7f756794670a047f79a53215da69937152bad0f86576945b12176d3e13cac38d26335c51000000000000000000000000000000000dcc715907e4e17824e24c1f513c09597965941e3ed0aaad6d0c59029b54fb039d716a998c9c418110bd49c5e365507f000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d0000000000000000000000000000000002f2e4467cdc15f1e57d75d6f5c172637df589590863bb437cc5166314e6362b7cd0d7499176b94529979849624cb432000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d2900000000000000000000000000000000179c34ba9578d5ff90272a2c7f756794670a047f79a53215da69937152bad0f86576945b12176d3e13cac38d26335c51000000000000000000000000000000000dcc715907e4e17824e24c1f513c09597965941e3ed0aaad6d0c59029b54fb039d716a998c9c418110bd49c5e365507f000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d29000000000000000000000000000000000264dd2fa407109abaf47d89c3d64542fd6d470579dfe0a98cc73f2fa3f6252bb9356ba39f3c92c1a6343c72d9cc4e5a000000000000000000000000000000000c34a091319b052226395b96f20fa37deb11b766b4b46811fa24799e5b5bfb20813a956524b7be7ea941b63a1c9a5a2c000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d0000000000000000000000000000000002f2e4467cdc15f1e57d75d6f5c172637df589590863bb437cc5166314e6362b7cd0d7499176b94529979849624cb432000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d29000000000000000000000000000000000264dd2fa407109abaf47d89c3d64542fd6d470579dfe0a98cc73f2fa3f6252bb9356ba39f3c92c1a6343c72d9cc4e5a000000000000000000000000000000000c34a091319b052226395b96f20fa37deb11b766b4b46811fa24799e5b5bfb20813a956524b7be7ea941b63a1c9a5a2c000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d2900000000000000000000000000000000179c34ba9578d5ff90272a2c7f756794670a047f79a53215da69937152bad0f86576945b12176d3e13cac38d26335c51000000000000000000000000000000000dcc715907e4e17824e24c1f513c09597965941e3ed0aaad6d0c59029b54fb039d716a998c9c418110bd49c5e365507f,0000000000000000000000000000000000000000000000000000000000000000,230000, -000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c853000000000000000000000000000000001643567a0f22b90fefee96c8e2f5851623384c2c68bce9589cdf64c933d494a8d805edce2fd18a6db80f4819391dd1f9000000000000000000000000000000000e4e40ab1969bf9f00ee3b984947ae95bf7b9579bdaeeee926638f9566f8ab26debb4c8d4009535cb6422b2c2ab7282d000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000cab5ed8dc53e9c891df449bd199776adbfc193fc8d6bebf9716610fd4db6def608df059bf29fe43dbf1bf0aa52c1b7f0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c853000000000000000000000000000000001643567a0f22b90fefee96c8e2f5851623384c2c68bce9589cdf64c933d494a8d805edce2fd18a6db80f4819391dd1f9000000000000000000000000000000000e4e40ab1969bf9f00ee3b984947ae95bf7b9579bdaeeee926638f9566f8ab26debb4c8d4009535cb6422b2c2ab7282d000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c8530000000000000000000000000000000003bdbb702a5d2d8a5b2d10ed605627c1413eff588ac82966ca516dd7c2dc617b46a612308182759201efb7e6c6e1d8b2000000000000000000000000000000000bb2d13f201626fb4a2d6c1dfa03fe41a4fbb60b35d623d640cd430b8fb84afd3ff0b371714aaca303bcd4d3d548827e000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000cab5ed8dc53e9c891df449bd199776adbfc193fc8d6bebf9716610fd4db6def608df059bf29fe43dbf1bf0aa52c1b7f0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c8530000000000000000000000000000000003bdbb702a5d2d8a5b2d10ed605627c1413eff588ac82966ca516dd7c2dc617b46a612308182759201efb7e6c6e1d8b2000000000000000000000000000000000bb2d13f201626fb4a2d6c1dfa03fe41a4fbb60b35d623d640cd430b8fb84afd3ff0b371714aaca303bcd4d3d548827e000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c853000000000000000000000000000000001643567a0f22b90fefee96c8e2f5851623384c2c68bce9589cdf64c933d494a8d805edce2fd18a6db80f4819391dd1f9000000000000000000000000000000000e4e40ab1969bf9f00ee3b984947ae95bf7b9579bdaeeee926638f9566f8ab26debb4c8d4009535cb6422b2c2ab7282d,0000000000000000000000000000000000000000000000000000000000000000,230000, -000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f700000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000a27fe0a29c761ce29a731ead969b1db3ae9ef4c05493cc370a128d97ef956c55d9a500991b3e7bf9600383633778ebb000000000000000000000000000000000dbb997ef4970a472bfcf03e959acb90bb13671a3d27c91698975a407856505e93837f46afc965363f21c35a3d194ec0000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf1000000000000000000000000000000001528dcaae381eb764333992e28ed557034ba5413c5b64df40ef3150d2771e5d1cd8c211ca22075c7436e2582960ab3b400000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000a27fe0a29c761ce29a731ead969b1db3ae9ef4c05493cc370a128d97ef956c55d9a500991b3e7bf9600383633778ebb000000000000000000000000000000000dbb997ef4970a472bfcf03e959acb90bb13671a3d27c91698975a407856505e93837f46afc965363f21c35a3d194ec0000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f700000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000fd913e00fb884cc217475cb69e1fafc298d5c38ee3bd5fbf68fa9c777b79f5ec111aff51fa0184023fec7c9cc881bf0000000000000000000000000000000000c45786b44e8dc531f1eb777adb0e146a963e46ab65d49a8ce9978607e5aa5c58b2880b8018a9ac97add3ca5c2e65beb000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf1000000000000000000000000000000001528dcaae381eb764333992e28ed557034ba5413c5b64df40ef3150d2771e5d1cd8c211ca22075c7436e2582960ab3b400000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000fd913e00fb884cc217475cb69e1fafc298d5c38ee3bd5fbf68fa9c777b79f5ec111aff51fa0184023fec7c9cc881bf0000000000000000000000000000000000c45786b44e8dc531f1eb777adb0e146a963e46ab65d49a8ce9978607e5aa5c58b2880b8018a9ac97add3ca5c2e65beb000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f700000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000a27fe0a29c761ce29a731ead969b1db3ae9ef4c05493cc370a128d97ef956c55d9a500991b3e7bf9600383633778ebb000000000000000000000000000000000dbb997ef4970a472bfcf03e959acb90bb13671a3d27c91698975a407856505e93837f46afc965363f21c35a3d194ec0,0000000000000000000000000000000000000000000000000000000000000000,230000, -0000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d200000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000eb3f5d24d1a4f520032534f6f81a6806c54df33cbd10c30203423aa4f33620b474cda321e924802b636daaeb34400470000000000000000000000000000000016f004f1dfbf140de042e4f57303928a576d9064f2da5b3ad392331f5c43327c7d2a6fd57456d5ef58b54a3e5ec275080000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c5100000000000000000000000000000000010ee94e9470765ac32b5648f1cd7d745a793dbd46dc95fa32db86929eec385e50cb35755120480be0956a2a342a46d900000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000eb3f5d24d1a4f520032534f6f81a6806c54df33cbd10c30203423aa4f33620b474cda321e924802b636daaeb34400470000000000000000000000000000000016f004f1dfbf140de042e4f57303928a576d9064f2da5b3ad392331f5c43327c7d2a6fd57456d5ef58b54a3e5ec275080000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d200000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000b4d1c17ec6597484ae95466d3ca0656f8226c5127b4068f46fcaef6a77d9418d75f25cc92c1b7fd03c825514cbbaa640000000000000000000000000000000003110cf859c0d28c6ad8c2c0d0481a4d0d09bb2000aab784939e9f819a6dc3a7a18190293cfd2a106149b5c1a13d35a30000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c5100000000000000000000000000000000010ee94e9470765ac32b5648f1cd7d745a793dbd46dc95fa32db86929eec385e50cb35755120480be0956a2a342a46d900000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000b4d1c17ec6597484ae95466d3ca0656f8226c5127b4068f46fcaef6a77d9418d75f25cc92c1b7fd03c825514cbbaa640000000000000000000000000000000003110cf859c0d28c6ad8c2c0d0481a4d0d09bb2000aab784939e9f819a6dc3a7a18190293cfd2a106149b5c1a13d35a30000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d200000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000eb3f5d24d1a4f520032534f6f81a6806c54df33cbd10c30203423aa4f33620b474cda321e924802b636daaeb34400470000000000000000000000000000000016f004f1dfbf140de042e4f57303928a576d9064f2da5b3ad392331f5c43327c7d2a6fd57456d5ef58b54a3e5ec27508,0000000000000000000000000000000000000000000000000000000000000000,230000, -0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531f0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d0000000000000000000000000000000002a36d5efd3381c35ff4f361cd813a96c3e5185141c5985073b45d1319c5f392442b7aa6a253b7eb22d1b5052812be00000000000000000000000000000000000f745dd17966b6befa7f740ea360241162505d6269226ffda90546863d0fff124d8fea13c763cfb69c2f8f12b81d431f0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab0000000000000000000000000000000004acd0ba7577ffe37bdeeaf5810b5a8a4a6b51c3c02bec4e0c6f0cfb4f12283120d283c12ecb7e4be7063fefb37a578c0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d0000000000000000000000000000000002a36d5efd3381c35ff4f361cd813a96c3e5185141c5985073b45d1319c5f392442b7aa6a253b7eb22d1b5052812be00000000000000000000000000000000000f745dd17966b6befa7f740ea360241162505d6269226ffda90546863d0fff124d8fea13c763cfb69c2f8f12b81d431f0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531f0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d00000000000000000000000000000000175da48b3c4c64d6eb26b45475ca7240a0923333b1bf7a6ef37c758ddceb0291da8085580f004814972d4afad7ececab000000000000000000000000000000000a8cb418c0192fdb509c33a79feb88c60226ee228a62a2c1be2b8c1ab9a0f711d11c15eae9f030491dcf70ed47e2678c0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab0000000000000000000000000000000004acd0ba7577ffe37bdeeaf5810b5a8a4a6b51c3c02bec4e0c6f0cfb4f12283120d283c12ecb7e4be7063fefb37a578c0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d00000000000000000000000000000000175da48b3c4c64d6eb26b45475ca7240a0923333b1bf7a6ef37c758ddceb0291da8085580f004814972d4afad7ececab000000000000000000000000000000000a8cb418c0192fdb509c33a79feb88c60226ee228a62a2c1be2b8c1ab9a0f711d11c15eae9f030491dcf70ed47e2678c0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531f0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d0000000000000000000000000000000002a36d5efd3381c35ff4f361cd813a96c3e5185141c5985073b45d1319c5f392442b7aa6a253b7eb22d1b5052812be00000000000000000000000000000000000f745dd17966b6befa7f740ea360241162505d6269226ffda90546863d0fff124d8fea13c763cfb69c2f8f12b81d431f,0000000000000000000000000000000000000000000000000000000000000000,230000, -00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e7879000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000000397dbdcc3edf976e8c507f5e70299da8c7765772115bf8edf7dc9024050c2ed98746c2bf7dd4400ab1fb89af991e43f00000000000000000000000000000000139bd5f917f59e2cb6c41c59024c12cdaf95285f3947b80267f36e3bd2701f9548b561c49003fc5ddeee3fe7bc8f5b5b00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e20000000000000000000000000000000012ee6884c9d68bdabe8f4aa92aa613129993aad6a7aafffef1922c910cbd3f8b4ae8a810c59a0b9de0a79d4e5db13232000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000000397dbdcc3edf976e8c507f5e70299da8c7765772115bf8edf7dc9024050c2ed98746c2bf7dd4400ab1fb89af991e43f00000000000000000000000000000000139bd5f917f59e2cb6c41c59024c12cdaf95285f3947b80267f36e3bd2701f9548b561c49003fc5ddeee3fe7bc8f5b5b00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e7879000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000001669360d7591ed2362569fc05c4912fcd7ffe60dd26f533087b3099eb6603336863793d2b976bbff0edf4765066dc66c0000000000000000000000000000000006653bf1218a486d94578b5d40ff9a09b4e22325ba3d5abcff3d64652440d68ed5f69e3a215003a1db10c01843704f5000000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e20000000000000000000000000000000012ee6884c9d68bdabe8f4aa92aa613129993aad6a7aafffef1922c910cbd3f8b4ae8a810c59a0b9de0a79d4e5db13232000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000001669360d7591ed2362569fc05c4912fcd7ffe60dd26f533087b3099eb6603336863793d2b976bbff0edf4765066dc66c0000000000000000000000000000000006653bf1218a486d94578b5d40ff9a09b4e22325ba3d5abcff3d64652440d68ed5f69e3a215003a1db10c01843704f5000000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e7879000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000000397dbdcc3edf976e8c507f5e70299da8c7765772115bf8edf7dc9024050c2ed98746c2bf7dd4400ab1fb89af991e43f00000000000000000000000000000000139bd5f917f59e2cb6c41c59024c12cdaf95285f3947b80267f36e3bd2701f9548b561c49003fc5ddeee3fe7bc8f5b5b,0000000000000000000000000000000000000000000000000000000000000000,230000, -000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b00000000000000000000000000000000197ed996d62fc0628d8ea4adee487df31c794e05e7c327aaa140c6be0109031bb763c5f84bc35a0597dc61e93d23a9bf000000000000000000000000000000001056c1f3c6ae36be26430d142d34b0e807685c79935496414e004cb85900d85a18454bde9c0f2650f19db35eb3dd468d000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e5590000000000000000000000000000000006abf7ef1d5e3484992225b5a59791a68cc7e1e0f8aaf2415a9f759f2dff53f62aecf23e0443fdf37bb3775be9f5c981000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b00000000000000000000000000000000197ed996d62fc0628d8ea4adee487df31c794e05e7c327aaa140c6be0109031bb763c5f84bc35a0597dc61e93d23a9bf000000000000000000000000000000001056c1f3c6ae36be26430d142d34b0e807685c79935496414e004cb85900d85a18454bde9c0f2650f19db35eb3dd468d000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b000000000000000000000000000000000082385363502637bd8d030855032ee447fdfd7f0bc1eb14c5f00be2f5a7f30867483a066590a5fa22229e16c2dc00ec0000000000000000000000000000000009aa4ff672d1afdc24d89aa21616fbef5d0eef0b60307c7e193085e89db01dca0666b4201544d9aec8614ca14c22641e000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e5590000000000000000000000000000000006abf7ef1d5e3484992225b5a59791a68cc7e1e0f8aaf2415a9f759f2dff53f62aecf23e0443fdf37bb3775be9f5c981000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b000000000000000000000000000000000082385363502637bd8d030855032ee447fdfd7f0bc1eb14c5f00be2f5a7f30867483a066590a5fa22229e16c2dc00ec0000000000000000000000000000000009aa4ff672d1afdc24d89aa21616fbef5d0eef0b60307c7e193085e89db01dca0666b4201544d9aec8614ca14c22641e000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b00000000000000000000000000000000197ed996d62fc0628d8ea4adee487df31c794e05e7c327aaa140c6be0109031bb763c5f84bc35a0597dc61e93d23a9bf000000000000000000000000000000001056c1f3c6ae36be26430d142d34b0e807685c79935496414e004cb85900d85a18454bde9c0f2650f19db35eb3dd468d,0000000000000000000000000000000000000000000000000000000000000000,230000, -0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f50000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f0000000000000000000000000000000008789ffe0a8676c6a56742a30a48e5e65b88aafd71859d704fb9f69e5e274ccb6942bc51ad36c5671406052aacf19df9000000000000000000000000000000000c7607f4fc69a25aff00a54369f213c4587404644358da4abf26d151dfa4905ba9731dcfb12e2a3f2c551cacd0f4e47f0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf9250000000000000000000000000000000001b7a86c4142843a854dd0937bdbfd833a34fb15303d753e3f41eaf19f4fd9a6af785804d5ae2c3b99044cc13e6ca4b60000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f0000000000000000000000000000000008789ffe0a8676c6a56742a30a48e5e65b88aafd71859d704fb9f69e5e274ccb6942bc51ad36c5671406052aacf19df9000000000000000000000000000000000c7607f4fc69a25aff00a54369f213c4587404644358da4abf26d151dfa4905ba9731dcfb12e2a3f2c551cacd0f4e47f0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f50000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f00000000000000000000000000000000118871ec2ef96fd3a5b465133902c6f108eea08781ff754f1776dc029889a958b56943ad041d3a98a5f8fad5530e0cb2000000000000000000000000000000000d8b09f53d16443f4c1b0272d95999130c034720b02c3874a80a014f170c65c87538e22f0025d5c08da9e3532f0ac62c0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf9250000000000000000000000000000000001b7a86c4142843a854dd0937bdbfd833a34fb15303d753e3f41eaf19f4fd9a6af785804d5ae2c3b99044cc13e6ca4b60000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f00000000000000000000000000000000118871ec2ef96fd3a5b465133902c6f108eea08781ff754f1776dc029889a958b56943ad041d3a98a5f8fad5530e0cb2000000000000000000000000000000000d8b09f53d16443f4c1b0272d95999130c034720b02c3874a80a014f170c65c87538e22f0025d5c08da9e3532f0ac62c0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f50000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f0000000000000000000000000000000008789ffe0a8676c6a56742a30a48e5e65b88aafd71859d704fb9f69e5e274ccb6942bc51ad36c5671406052aacf19df9000000000000000000000000000000000c7607f4fc69a25aff00a54369f213c4587404644358da4abf26d151dfa4905ba9731dcfb12e2a3f2c551cacd0f4e47f,0000000000000000000000000000000000000000000000000000000000000000,230000, -000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567300000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000b060965391bfd4afe3271c6ddb91eecb8c7a60451c469d63bb178b1361617000f589c33c35b5deda2f072c6edf2eb370000000000000000000000000000000011c8c988379cd2b82cb8ebd81c3e14d2c01c09dde5690b97623c0876c7554f52ccbaa33d17fb0f0cf331cc85749340cd000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000008151a15a13daeee49a82737118d488005fa7ed1869bc458f8af88e7341e0a48b5d8f129f6eb071fb07c11887f4d543800000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000b060965391bfd4afe3271c6ddb91eecb8c7a60451c469d63bb178b1361617000f589c33c35b5deda2f072c6edf2eb370000000000000000000000000000000011c8c988379cd2b82cb8ebd81c3e14d2c01c09dde5690b97623c0876c7554f52ccbaa33d17fb0f0cf331cc85749340cd000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567300000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000efb08850063e94f4ce935ef65928deaabafa580a1c0a8e92b7f59efc09adf240f5363caedf8a212170e8d39120cbf74000000000000000000000000000000000838486201e313e21e62bbde270d9804a45b41a70e1c072804f4ca2a2f5ba6d151f15cc19958f0f2c6cd337a8b6c69de000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000008151a15a13daeee49a82737118d488005fa7ed1869bc458f8af88e7341e0a48b5d8f129f6eb071fb07c11887f4d543800000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000efb08850063e94f4ce935ef65928deaabafa580a1c0a8e92b7f59efc09adf240f5363caedf8a212170e8d39120cbf74000000000000000000000000000000000838486201e313e21e62bbde270d9804a45b41a70e1c072804f4ca2a2f5ba6d151f15cc19958f0f2c6cd337a8b6c69de000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567300000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000b060965391bfd4afe3271c6ddb91eecb8c7a60451c469d63bb178b1361617000f589c33c35b5deda2f072c6edf2eb370000000000000000000000000000000011c8c988379cd2b82cb8ebd81c3e14d2c01c09dde5690b97623c0876c7554f52ccbaa33d17fb0f0cf331cc85749340cd,0000000000000000000000000000000000000000000000000000000000000000,230000, -000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc300000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000f05a111b41a54e0ca78c3a1fff3b80bee7c1505a06b9a4faf36a73b87121d2952cc4f4c4e0dcb6633cad12b0caffc620000000000000000000000000000000018daa0f9a2bb347517eee63463b9d6a5e850446e8a94d0986f2921bf81a9f7541e8fee9d7bbb6d9181021af945fce3e3000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a07060000000000000000000000000000000000876cf6553b21053e0d7a4449cd137fd946f2de0f7032f535f54914a8ae7da5afbe765bdfa3a0cdea0a50e1ed43bce800000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000f05a111b41a54e0ca78c3a1fff3b80bee7c1505a06b9a4faf36a73b87121d2952cc4f4c4e0dcb6633cad12b0caffc620000000000000000000000000000000018daa0f9a2bb347517eee63463b9d6a5e850446e8a94d0986f2921bf81a9f7541e8fee9d7bbb6d9181021af945fce3e3000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc300000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000afb70d8856591b980a2e4144357f4cb75fb367f5319786fb7fa2b656f9ed8facbdfb0b26346349986342ed4f34fae4900000000000000000000000000000000012670f096c4b225332cc181df91d6317c27071668f04226f807b0e17506fed0001c11613598926e38fce506ba02c6c8000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a07060000000000000000000000000000000000876cf6553b21053e0d7a4449cd137fd946f2de0f7032f535f54914a8ae7da5afbe765bdfa3a0cdea0a50e1ed43bce800000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000afb70d8856591b980a2e4144357f4cb75fb367f5319786fb7fa2b656f9ed8facbdfb0b26346349986342ed4f34fae4900000000000000000000000000000000012670f096c4b225332cc181df91d6317c27071668f04226f807b0e17506fed0001c11613598926e38fce506ba02c6c8000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc300000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000f05a111b41a54e0ca78c3a1fff3b80bee7c1505a06b9a4faf36a73b87121d2952cc4f4c4e0dcb6633cad12b0caffc620000000000000000000000000000000018daa0f9a2bb347517eee63463b9d6a5e850446e8a94d0986f2921bf81a9f7541e8fee9d7bbb6d9181021af945fce3e3,0000000000000000000000000000000000000000000000000000000000000000,230000, -00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a9035050000000000000000000000000000000017d9e9e2008501981068cb0403e73c270d99defd468cc9dc2d5bbc57750a4a58236f8f7a8df4f8b607095b6a80e7de49000000000000000000000000000000000ebddf4fc74f25be3c358b72a20d1c093f980adfc943b898266592f691e11413c60151a0085d6c9aec8c2d329abbac0d00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c2000000000000000000000000000000001093356407cff41779ce8f3d53dfe7a04edc8ce7192ddfeeb4329c38152cf1875d0df9ffeced95f1c7fae7d124649f21000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a9035050000000000000000000000000000000017d9e9e2008501981068cb0403e73c270d99defd468cc9dc2d5bbc57750a4a58236f8f7a8df4f8b607095b6a80e7de49000000000000000000000000000000000ebddf4fc74f25be3c358b72a20d1c093f980adfc943b898266592f691e11413c60151a0085d6c9aec8c2d329abbac0d00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a903505000000000000000000000000000000000227280838fae5023ab2dcb23f6470b056dd6c87acf848e339d5164981a6abcbfb3c7084235f0749b2f5a4957f17cc62000000000000000000000000000000000b43329a7230c0dc0ee61c43a13e90ce24df40a52a415a2740cb3faa64cfe21058aaae5ea8f69364cd72d2cd6543fe9e00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c2000000000000000000000000000000001093356407cff41779ce8f3d53dfe7a04edc8ce7192ddfeeb4329c38152cf1875d0df9ffeced95f1c7fae7d124649f21000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a903505000000000000000000000000000000000227280838fae5023ab2dcb23f6470b056dd6c87acf848e339d5164981a6abcbfb3c7084235f0749b2f5a4957f17cc62000000000000000000000000000000000b43329a7230c0dc0ee61c43a13e90ce24df40a52a415a2740cb3faa64cfe21058aaae5ea8f69364cd72d2cd6543fe9e00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a9035050000000000000000000000000000000017d9e9e2008501981068cb0403e73c270d99defd468cc9dc2d5bbc57750a4a58236f8f7a8df4f8b607095b6a80e7de49000000000000000000000000000000000ebddf4fc74f25be3c358b72a20d1c093f980adfc943b898266592f691e11413c60151a0085d6c9aec8c2d329abbac0d,0000000000000000000000000000000000000000000000000000000000000000,230000, -000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa300000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000001fdc4256cc997934a65c68ab9767b09c7aad14b5765dbeedb72ab2429231cb333ab9f9143414359376d76857e8972d9000000000000000000000000000000001362f417875259b47cfd9e4c5feda52b949dcbf5b8178318428fd3e70c384020e58f515b9a24af5597cfa037d42491c6000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e0000000000000000000000000000000007007c89288b69f16870dc857a02cd071db8178e578fd2b78fcd5edb5050dcded107a1c1c0071d45e4c4af364bc9400800000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000001fdc4256cc997934a65c68ab9767b09c7aad14b5765dbeedb72ab2429231cb333ab9f9143414359376d76857e8972d9000000000000000000000000000000001362f417875259b47cfd9e4c5feda52b949dcbf5b8178318428fd3e70c384020e58f515b9a24af5597cfa037d42491c6000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa300000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000018034dc4ccb64f0700b5e12b89d531cd9ccc7a399c1f36d08bbe277ccd8dd970eb00606d6e12bca68291897a817637d200000000000000000000000000000000069e1dd2b22d8ce5ce1e0969e35e07abcfd97f8f3b6d8fa724a0feb9ea78b603391caea3172f50aa222f5fc82bdb18e5000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e0000000000000000000000000000000007007c89288b69f16870dc857a02cd071db8178e578fd2b78fcd5edb5050dcded107a1c1c0071d45e4c4af364bc9400800000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000018034dc4ccb64f0700b5e12b89d531cd9ccc7a399c1f36d08bbe277ccd8dd970eb00606d6e12bca68291897a817637d200000000000000000000000000000000069e1dd2b22d8ce5ce1e0969e35e07abcfd97f8f3b6d8fa724a0feb9ea78b603391caea3172f50aa222f5fc82bdb18e5000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa300000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000001fdc4256cc997934a65c68ab9767b09c7aad14b5765dbeedb72ab2429231cb333ab9f9143414359376d76857e8972d9000000000000000000000000000000001362f417875259b47cfd9e4c5feda52b949dcbf5b8178318428fd3e70c384020e58f515b9a24af5597cfa037d42491c6,0000000000000000000000000000000000000000000000000000000000000000,230000, -0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c8000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000010fb029e35b3f6e156b8751415f180ee3960cd3bb6ba9b8e456715ec70b1ba1410b8bfb77998f744d3f462533b59e26c000000000000000000000000000000001472654d9aa210a41d74e3661e05a9eb6b292719b46aa65f94b6abd514bf05f679dae89d21008245d79a381b0d7f51be0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000a76ccda2ca736ce935b4b88e08bbf183f69e2b3f5a471662a5de571976e7d4264021db88b919c896bbbb8128732c3e3000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000010fb029e35b3f6e156b8751415f180ee3960cd3bb6ba9b8e456715ec70b1ba1410b8bfb77998f744d3f462533b59e26c000000000000000000000000000000001472654d9aa210a41d74e3661e05a9eb6b292719b46aa65f94b6abd514bf05f679dae89d21008245d79a381b0d7f51be0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c8000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000009060f4c03cbefb8f46332a22d5a2be92b167e493cca773121c9bcb485ff3c100df3404737bb08bae60a9dacc4a5c83f00000000000000000000000000000000058eac9c9eddd5f62da6c450254602ebf94e246b3f1a6c5fd27a26cbe1f1f02da4d1176190537db9e264c7e4f28058ed0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000a76ccda2ca736ce935b4b88e08bbf183f69e2b3f5a471662a5de571976e7d4264021db88b919c896bbbb8128732c3e3000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000009060f4c03cbefb8f46332a22d5a2be92b167e493cca773121c9bcb485ff3c100df3404737bb08bae60a9dacc4a5c83f00000000000000000000000000000000058eac9c9eddd5f62da6c450254602ebf94e246b3f1a6c5fd27a26cbe1f1f02da4d1176190537db9e264c7e4f28058ed0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c8000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000010fb029e35b3f6e156b8751415f180ee3960cd3bb6ba9b8e456715ec70b1ba1410b8bfb77998f744d3f462533b59e26c000000000000000000000000000000001472654d9aa210a41d74e3661e05a9eb6b292719b46aa65f94b6abd514bf05f679dae89d21008245d79a381b0d7f51be,0000000000000000000000000000000000000000000000000000000000000000,230000, -000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb21100000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d7000000000000000000000000000000000520cfc8c536a1d4e685c4eacbc2000d70abd72e1bf8ce3839d79f5cfa069ed31aafb15542f23b8d1af678bab05a2d410000000000000000000000000000000017cfffda12d21c98b79ac31c5bb696783afb7d69c2bedf0fb070cf7714959db14957a4763564b65b7ed214d7b48d399c000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c00000000000000000000000000000000124a601a06d5094945ec8528c5457ea3f8ca710137b6ad48ee7ad93db53c056059dbc8b02d9edf5e2786c575a0bff89a00000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d7000000000000000000000000000000000520cfc8c536a1d4e685c4eacbc2000d70abd72e1bf8ce3839d79f5cfa069ed31aafb15542f23b8d1af678bab05a2d410000000000000000000000000000000017cfffda12d21c98b79ac31c5bb696783afb7d69c2bedf0fb070cf7714959db14957a4763564b65b7ed214d7b48d399c000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb21100000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d70000000000000000000000000000000014e04221744944c56495e2cb7789acc9f3cb7456d78c44872d593343fcaa575103fc4ea96e61c4729f0887454fa57d6a000000000000000000000000000000000231121026adca019380e499e795165f297bce1b30c633afb6c00329e21b5872d5545b887bef49a43b2ceb284b72710f000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c00000000000000000000000000000000124a601a06d5094945ec8528c5457ea3f8ca710137b6ad48ee7ad93db53c056059dbc8b02d9edf5e2786c575a0bff89a00000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d70000000000000000000000000000000014e04221744944c56495e2cb7789acc9f3cb7456d78c44872d593343fcaa575103fc4ea96e61c4729f0887454fa57d6a000000000000000000000000000000000231121026adca019380e499e795165f297bce1b30c633afb6c00329e21b5872d5545b887bef49a43b2ceb284b72710f000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb21100000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d7000000000000000000000000000000000520cfc8c536a1d4e685c4eacbc2000d70abd72e1bf8ce3839d79f5cfa069ed31aafb15542f23b8d1af678bab05a2d410000000000000000000000000000000017cfffda12d21c98b79ac31c5bb696783afb7d69c2bedf0fb070cf7714959db14957a4763564b65b7ed214d7b48d399c,0000000000000000000000000000000000000000000000000000000000000000,230000, -000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a00000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000007f786ea1cc7cd69ae1061d6b914278dfc7ebe8a714aa8cd04323860314c3b4b36054169dd5c6c60e67bfa3902d216f50000000000000000000000000000000019675b09a4de34af3c6e79452b57b31b6d499200e996008a9e7d1c910ca0ad2a352dc39cb3fd7333182476095b7aeec3000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c04200000000000000000000000000000000001b8c085fd1f34fb273da7d651602b326fef7c357c2fb7845f4c17ce95152042af9e51e7d7699b50f3605bacab563a100000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000007f786ea1cc7cd69ae1061d6b914278dfc7ebe8a714aa8cd04323860314c3b4b36054169dd5c6c60e67bfa3902d216f50000000000000000000000000000000019675b09a4de34af3c6e79452b57b31b6d499200e996008a9e7d1c910ca0ad2a352dc39cb3fd7333182476095b7aeec3000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a00000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000012098b001cb819309d0b45df8a37854967f88cfa823a69f262fe9a40c564bad8e8a6be94d3f7939ed38305c6fd2d93b6000000000000000000000000000000000099b6e094a1b1eb0ead2e7117f3f9bbf72db98409ef1234c8b3b60fea1048f9e97e3c61fd568ccca1da89f6a484bbe8000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c04200000000000000000000000000000000001b8c085fd1f34fb273da7d651602b326fef7c357c2fb7845f4c17ce95152042af9e51e7d7699b50f3605bacab563a100000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000012098b001cb819309d0b45df8a37854967f88cfa823a69f262fe9a40c564bad8e8a6be94d3f7939ed38305c6fd2d93b6000000000000000000000000000000000099b6e094a1b1eb0ead2e7117f3f9bbf72db98409ef1234c8b3b60fea1048f9e97e3c61fd568ccca1da89f6a484bbe8000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a00000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000007f786ea1cc7cd69ae1061d6b914278dfc7ebe8a714aa8cd04323860314c3b4b36054169dd5c6c60e67bfa3902d216f50000000000000000000000000000000019675b09a4de34af3c6e79452b57b31b6d499200e996008a9e7d1c910ca0ad2a352dc39cb3fd7333182476095b7aeec3,0000000000000000000000000000000000000000000000000000000000000000,230000, -0000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b0000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000fbe421858e4109c51de57b77da4f9c4c1f950099532d9e30e2f7a8b8b4fb9f708cde1a497050d0944e089978b15321e0000000000000000000000000000000019f48a0bf0f27df65ba766a65e831a0801a4ebcd1995a6002a803f88aead1503b7c39fde8ef5c4672020307241958a880000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000c8bd020743550a6d27f0052d0037547db204e3fd752abf6758d899a3793fd3cd50c3073df6258c20a2f8e4797cbab700000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000fbe421858e4109c51de57b77da4f9c4c1f950099532d9e30e2f7a8b8b4fb9f708cde1a497050d0944e089978b15321e0000000000000000000000000000000019f48a0bf0f27df65ba766a65e831a0801a4ebcd1995a6002a803f88aead1503b7c39fde8ef5c4672020307241958a880000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b0000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000a42cfd1e09bd5fdf93d4ffec5a6b312a27dfb7b5e5238dc590158156b613c2d15de1e5a1a4ef2f6751e766874ea788d00000000000000000000000000000000000c87de488d68a3ef74410fe4c892cf62d25fb7d9ef6cbf3cb093184803e12066e86020225e3b9899decf8dbe6a20230000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000c8bd020743550a6d27f0052d0037547db204e3fd752abf6758d899a3793fd3cd50c3073df6258c20a2f8e4797cbab700000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000a42cfd1e09bd5fdf93d4ffec5a6b312a27dfb7b5e5238dc590158156b613c2d15de1e5a1a4ef2f6751e766874ea788d00000000000000000000000000000000000c87de488d68a3ef74410fe4c892cf62d25fb7d9ef6cbf3cb093184803e12066e86020225e3b9899decf8dbe6a20230000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b0000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000fbe421858e4109c51de57b77da4f9c4c1f950099532d9e30e2f7a8b8b4fb9f708cde1a497050d0944e089978b15321e0000000000000000000000000000000019f48a0bf0f27df65ba766a65e831a0801a4ebcd1995a6002a803f88aead1503b7c39fde8ef5c4672020307241958a88,0000000000000000000000000000000000000000000000000000000000000000,230000, -0000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff1480000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb00000000000000000000000000000000094a36d86483ac6f068017e4b978c7ea1ee58c429aad5994287f809c69fd5235532487d81f6a46ab827f2e0cb4c6df9e0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff1480000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa0000000000000000000000000000000007cdcfd000a86a408f39ef7cb0a4060dff8965956fbf6b939576a69674fcd5c735056da7988943506f8c35c2de8feaaf0000000000000000000000000000000003484fbf03d06907efbf3eff8b95789484254dc09e42208b7457619a31f795356a2cdfb24bb6e95c192b49a11c6fb9630000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb00000000000000000000000000000000094a36d86483ac6f068017e4b978c7ea1ee58c429aad5994287f809c69fd5235532487d81f6a46ab827f2e0cb4c6df9e0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa0000000000000000000000000000000007cdcfd000a86a408f39ef7cb0a4060dff8965956fbf6b939576a69674fcd5c735056da7988943506f8c35c2de8feaaf0000000000000000000000000000000003484fbf03d06907efbf3eff8b95789484254dc09e42208b7457619a31f795356a2cdfb24bb6e95c192b49a11c6fb9630000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff1480000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb00000000000000000000000000000000094a36d86483ac6f068017e4b978c7ea1ee58c429aad5994287f809c69fd5235532487d81f6a46ab827f2e0cb4c6df9e0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff1480000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa0000000000000000000000000000000007cdcfd000a86a408f39ef7cb0a4060dff8965956fbf6b939576a69674fcd5c735056da7988943506f8c35c2de8feaaf0000000000000000000000000000000003484fbf03d06907efbf3eff8b95789484254dc09e42208b7457619a31f795356a2cdfb24bb6e95c192b49a11c6fb9630000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb00000000000000000000000000000000094a36d86483ac6f068017e4b978c7ea1ee58c429aad5994287f809c69fd5235532487d81f6a46ab827f2e0cb4c6df9e0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa0000000000000000000000000000000007cdcfd000a86a408f39ef7cb0a4060dff8965956fbf6b939576a69674fcd5c735056da7988943506f8c35c2de8feaaf0000000000000000000000000000000003484fbf03d06907efbf3eff8b95789484254dc09e42208b7457619a31f795356a2cdfb24bb6e95c192b49a11c6fb963,0000000000000000000000000000000000000000000000000000000000000001,299000, -00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d5000000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000db912ff1f62be087194f6503b3b273b48bd0907afde777109522329e54cde1092afd48366af3f334c0df42ee98d8d5b00000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d5000000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b56000000000000000000000000000000000337fa3e53480c7865182ecbc7252aa6f9987685dbb814182447183d9da514732157ccffa4188d31eee96bc89c33f3f00000000000000000000000000000000004c5340a5240c4d6f1e095290ac5b6b5d121c5cadc6f30e5dd4855a9cf985e357b1a847cc060bd86aaef85ccf35ee41c00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000db912ff1f62be087194f6503b3b273b48bd0907afde777109522329e54cde1092afd48366af3f334c0df42ee98d8d5b00000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b56000000000000000000000000000000000337fa3e53480c7865182ecbc7252aa6f9987685dbb814182447183d9da514732157ccffa4188d31eee96bc89c33f3f00000000000000000000000000000000004c5340a5240c4d6f1e095290ac5b6b5d121c5cadc6f30e5dd4855a9cf985e357b1a847cc060bd86aaef85ccf35ee41c00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d5000000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000db912ff1f62be087194f6503b3b273b48bd0907afde777109522329e54cde1092afd48366af3f334c0df42ee98d8d5b00000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d5000000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b56000000000000000000000000000000000337fa3e53480c7865182ecbc7252aa6f9987685dbb814182447183d9da514732157ccffa4188d31eee96bc89c33f3f00000000000000000000000000000000004c5340a5240c4d6f1e095290ac5b6b5d121c5cadc6f30e5dd4855a9cf985e357b1a847cc060bd86aaef85ccf35ee41c00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000db912ff1f62be087194f6503b3b273b48bd0907afde777109522329e54cde1092afd48366af3f334c0df42ee98d8d5b00000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b56000000000000000000000000000000000337fa3e53480c7865182ecbc7252aa6f9987685dbb814182447183d9da514732157ccffa4188d31eee96bc89c33f3f00000000000000000000000000000000004c5340a5240c4d6f1e095290ac5b6b5d121c5cadc6f30e5dd4855a9cf985e357b1a847cc060bd86aaef85ccf35ee41c,0000000000000000000000000000000000000000000000000000000000000001,299000, -00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d800000000000000000000000000000000013d98eb6ddf8b68db36819b25d9a7b4a4ed2b1d2593dd6a6e79dc6adaaefd4d8d129d8d949c7421641374a5192b3fd5a00000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000f62f8cda209f1223a7695ed860de2c2b144bd6402ecd61838eded3f40d3df90fe10bd5d92245112e3ce822cb33f8d4b0000000000000000000000000000000018bb0bcf262b7f4583d1375ecce64bd6bb1fcc64fa4b6a93bd9ffbe870fe79df0f29baa92eb844e5c04d09c966e810dc00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d800000000000000000000000000000000013d98eb6ddf8b68db36819b25d9a7b4a4ed2b1d2593dd6a6e79dc6adaaefd4d8d129d8d949c7421641374a5192b3fd5a00000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000f62f8cda209f1223a7695ed860de2c2b144bd6402ecd61838eded3f40d3df90fe10bd5d92245112e3ce822cb33f8d4b0000000000000000000000000000000018bb0bcf262b7f4583d1375ecce64bd6bb1fcc64fa4b6a93bd9ffbe870fe79df0f29baa92eb844e5c04d09c966e810dc00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d800000000000000000000000000000000013d98eb6ddf8b68db36819b25d9a7b4a4ed2b1d2593dd6a6e79dc6adaaefd4d8d129d8d949c7421641374a5192b3fd5a00000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000f62f8cda209f1223a7695ed860de2c2b144bd6402ecd61838eded3f40d3df90fe10bd5d92245112e3ce822cb33f8d4b0000000000000000000000000000000018bb0bcf262b7f4583d1375ecce64bd6bb1fcc64fa4b6a93bd9ffbe870fe79df0f29baa92eb844e5c04d09c966e810dc00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d800000000000000000000000000000000013d98eb6ddf8b68db36819b25d9a7b4a4ed2b1d2593dd6a6e79dc6adaaefd4d8d129d8d949c7421641374a5192b3fd5a00000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000f62f8cda209f1223a7695ed860de2c2b144bd6402ecd61838eded3f40d3df90fe10bd5d92245112e3ce822cb33f8d4b0000000000000000000000000000000018bb0bcf262b7f4583d1375ecce64bd6bb1fcc64fa4b6a93bd9ffbe870fe79df0f29baa92eb844e5c04d09c966e810dc,0000000000000000000000000000000000000000000000000000000000000001,299000, -000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a800000000000000000000000000000000064be06bf988929a026a0ac78603eb822b9f6048ff829083cafc465aabb5e623509c8159ef889974c43634088195185a0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000b6a1b64528770227d79763e494d2d060d50a0530eacb8684147954b6ad194e0a0efd35ff457956b499f58f2177528ee00000000000000000000000000000000048431899516d3d0b8c327d80596e68cf41c94739c6e0fa7ef196332539f2aeeef71890a2db81b9a358e1b4f467a5b34000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a800000000000000000000000000000000064be06bf988929a026a0ac78603eb822b9f6048ff829083cafc465aabb5e623509c8159ef889974c43634088195185a0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000b6a1b64528770227d79763e494d2d060d50a0530eacb8684147954b6ad194e0a0efd35ff457956b499f58f2177528ee00000000000000000000000000000000048431899516d3d0b8c327d80596e68cf41c94739c6e0fa7ef196332539f2aeeef71890a2db81b9a358e1b4f467a5b34000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a800000000000000000000000000000000064be06bf988929a026a0ac78603eb822b9f6048ff829083cafc465aabb5e623509c8159ef889974c43634088195185a0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000b6a1b64528770227d79763e494d2d060d50a0530eacb8684147954b6ad194e0a0efd35ff457956b499f58f2177528ee00000000000000000000000000000000048431899516d3d0b8c327d80596e68cf41c94739c6e0fa7ef196332539f2aeeef71890a2db81b9a358e1b4f467a5b34000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a800000000000000000000000000000000064be06bf988929a026a0ac78603eb822b9f6048ff829083cafc465aabb5e623509c8159ef889974c43634088195185a0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000b6a1b64528770227d79763e494d2d060d50a0530eacb8684147954b6ad194e0a0efd35ff457956b499f58f2177528ee00000000000000000000000000000000048431899516d3d0b8c327d80596e68cf41c94739c6e0fa7ef196332539f2aeeef71890a2db81b9a358e1b4f467a5b34,0000000000000000000000000000000000000000000000000000000000000001,299000, -0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f60000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee410000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000f20033541ee3c68655e2c49f5e2fc8afd33255764267e55b3985790d6bb531db7171fa81caae98449ae3c6bb49225b50000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee410000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f60000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e000000000000000000000000000000000400e7e4eda0a9c13465af099ece14d8b30662fea5e222c6ab71b8fb44562dcc42c5255319741ea56b7cbaa2eab957c9000000000000000000000000000000000b04a27de02c7e71bbc51fcf3268b1eb734b754ae6e1c86ceb2ae0c7d0b40851e24dd497a93abf96168f7a705aeebc6a0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000f20033541ee3c68655e2c49f5e2fc8afd33255764267e55b3985790d6bb531db7171fa81caae98449ae3c6bb49225b50000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e000000000000000000000000000000000400e7e4eda0a9c13465af099ece14d8b30662fea5e222c6ab71b8fb44562dcc42c5255319741ea56b7cbaa2eab957c9000000000000000000000000000000000b04a27de02c7e71bbc51fcf3268b1eb734b754ae6e1c86ceb2ae0c7d0b40851e24dd497a93abf96168f7a705aeebc6a0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f60000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee410000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000f20033541ee3c68655e2c49f5e2fc8afd33255764267e55b3985790d6bb531db7171fa81caae98449ae3c6bb49225b50000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee410000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f60000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e000000000000000000000000000000000400e7e4eda0a9c13465af099ece14d8b30662fea5e222c6ab71b8fb44562dcc42c5255319741ea56b7cbaa2eab957c9000000000000000000000000000000000b04a27de02c7e71bbc51fcf3268b1eb734b754ae6e1c86ceb2ae0c7d0b40851e24dd497a93abf96168f7a705aeebc6a0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000f20033541ee3c68655e2c49f5e2fc8afd33255764267e55b3985790d6bb531db7171fa81caae98449ae3c6bb49225b50000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e000000000000000000000000000000000400e7e4eda0a9c13465af099ece14d8b30662fea5e222c6ab71b8fb44562dcc42c5255319741ea56b7cbaa2eab957c9000000000000000000000000000000000b04a27de02c7e71bbc51fcf3268b1eb734b754ae6e1c86ceb2ae0c7d0b40851e24dd497a93abf96168f7a705aeebc6a,0000000000000000000000000000000000000000000000000000000000000001,299000, -00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886200000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a6168542000000000000000000000000000000000352645e60bb10bc86d6c65a7b0d1dc290ff759c1c2e729a081d4b508b165b46b552ddbcd57a3546658a2aa53b8c224900000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886200000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a10000000000000000000000000000000005f8533875eac92050d86039e855238880b460eeed8c645abfa580b2789a478ddd98b5643be0a68cde274ac22b35b6ec000000000000000000000000000000000b94a5563380e67aa08e1baf868e36e8d3633c3d748cea822ad21f9d579aa1e774c41be88fdc58b61b2390c024fe466c00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a6168542000000000000000000000000000000000352645e60bb10bc86d6c65a7b0d1dc290ff759c1c2e729a081d4b508b165b46b552ddbcd57a3546658a2aa53b8c224900000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a10000000000000000000000000000000005f8533875eac92050d86039e855238880b460eeed8c645abfa580b2789a478ddd98b5643be0a68cde274ac22b35b6ec000000000000000000000000000000000b94a5563380e67aa08e1baf868e36e8d3633c3d748cea822ad21f9d579aa1e774c41be88fdc58b61b2390c024fe466c00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886200000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a6168542000000000000000000000000000000000352645e60bb10bc86d6c65a7b0d1dc290ff759c1c2e729a081d4b508b165b46b552ddbcd57a3546658a2aa53b8c224900000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886200000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a10000000000000000000000000000000005f8533875eac92050d86039e855238880b460eeed8c645abfa580b2789a478ddd98b5643be0a68cde274ac22b35b6ec000000000000000000000000000000000b94a5563380e67aa08e1baf868e36e8d3633c3d748cea822ad21f9d579aa1e774c41be88fdc58b61b2390c024fe466c00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a6168542000000000000000000000000000000000352645e60bb10bc86d6c65a7b0d1dc290ff759c1c2e729a081d4b508b165b46b552ddbcd57a3546658a2aa53b8c224900000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a10000000000000000000000000000000005f8533875eac92050d86039e855238880b460eeed8c645abfa580b2789a478ddd98b5643be0a68cde274ac22b35b6ec000000000000000000000000000000000b94a5563380e67aa08e1baf868e36e8d3633c3d748cea822ad21f9d579aa1e774c41be88fdc58b61b2390c024fe466c,0000000000000000000000000000000000000000000000000000000000000001,299000, -000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000000fc75b0eb2b6afed9d04e4c957ca64c2c595c1a00d295a23113cbb79f4e827b1ff0a40566039e32cd84024a9bd39fc3000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000001585d5f8db92696a596141237f5c78c524db68b482c469cca16c436c040d1d720387aafaa4282383c293d37eceb092d0000000000000000000000000000000013d1a7dfade2113a492ab236c090386e8d6d4ff5bf9ea02bfd80bd389d1b06fc72c00060d6fe3c74ac60775e1f45ae3f000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000000fc75b0eb2b6afed9d04e4c957ca64c2c595c1a00d295a23113cbb79f4e827b1ff0a40566039e32cd84024a9bd39fc3000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000001585d5f8db92696a596141237f5c78c524db68b482c469cca16c436c040d1d720387aafaa4282383c293d37eceb092d0000000000000000000000000000000013d1a7dfade2113a492ab236c090386e8d6d4ff5bf9ea02bfd80bd389d1b06fc72c00060d6fe3c74ac60775e1f45ae3f000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000000fc75b0eb2b6afed9d04e4c957ca64c2c595c1a00d295a23113cbb79f4e827b1ff0a40566039e32cd84024a9bd39fc3000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000001585d5f8db92696a596141237f5c78c524db68b482c469cca16c436c040d1d720387aafaa4282383c293d37eceb092d0000000000000000000000000000000013d1a7dfade2113a492ab236c090386e8d6d4ff5bf9ea02bfd80bd389d1b06fc72c00060d6fe3c74ac60775e1f45ae3f000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000000fc75b0eb2b6afed9d04e4c957ca64c2c595c1a00d295a23113cbb79f4e827b1ff0a40566039e32cd84024a9bd39fc3000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000001585d5f8db92696a596141237f5c78c524db68b482c469cca16c436c040d1d720387aafaa4282383c293d37eceb092d0000000000000000000000000000000013d1a7dfade2113a492ab236c090386e8d6d4ff5bf9ea02bfd80bd389d1b06fc72c00060d6fe3c74ac60775e1f45ae3f,0000000000000000000000000000000000000000000000000000000000000001,299000, -0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb10000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000010093a3820fda13babfc82cc313c6e20c503af71d2c1940cb5b2c879da00bb5d3bfb3aa17c3bab75b99fd74a8b742b52000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb10000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000c7f7169a5067d4a6cf6c21254ce79f8b7b4ed0d0144107900749f2e0ead7c7cfc25c156a0f4cba09cf51b9d03a51e3f00000000000000000000000000000000185b5357fa6340abc3ae41a686a623684e425c1a6f85865a8a8be52a24d5ca7f975b6604571a5f603667ced874cf2afa0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000010093a3820fda13babfc82cc313c6e20c503af71d2c1940cb5b2c879da00bb5d3bfb3aa17c3bab75b99fd74a8b742b52000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000c7f7169a5067d4a6cf6c21254ce79f8b7b4ed0d0144107900749f2e0ead7c7cfc25c156a0f4cba09cf51b9d03a51e3f00000000000000000000000000000000185b5357fa6340abc3ae41a686a623684e425c1a6f85865a8a8be52a24d5ca7f975b6604571a5f603667ced874cf2afa0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb10000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000010093a3820fda13babfc82cc313c6e20c503af71d2c1940cb5b2c879da00bb5d3bfb3aa17c3bab75b99fd74a8b742b52000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb10000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000c7f7169a5067d4a6cf6c21254ce79f8b7b4ed0d0144107900749f2e0ead7c7cfc25c156a0f4cba09cf51b9d03a51e3f00000000000000000000000000000000185b5357fa6340abc3ae41a686a623684e425c1a6f85865a8a8be52a24d5ca7f975b6604571a5f603667ced874cf2afa0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000010093a3820fda13babfc82cc313c6e20c503af71d2c1940cb5b2c879da00bb5d3bfb3aa17c3bab75b99fd74a8b742b52000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000c7f7169a5067d4a6cf6c21254ce79f8b7b4ed0d0144107900749f2e0ead7c7cfc25c156a0f4cba09cf51b9d03a51e3f00000000000000000000000000000000185b5357fa6340abc3ae41a686a623684e425c1a6f85865a8a8be52a24d5ca7f975b6604571a5f603667ced874cf2afa,0000000000000000000000000000000000000000000000000000000000000001,299000, -0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000059a7b662af14e0d3c7016cbafedd42173501fc97199c07114f47acdabd930332af4dea84202253b42b6d947b33de27c0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b272000000000000000000000000000000001949a50c589ec63db98d39491100e8e407345f9b3874f3a28e9b77d2fc28bf31ef976841c4276cb669dc4f3cca42fffd0000000000000000000000000000000001cd05bfae784b11f1c102a7b0268fc480d19cd7c65a3583f4624fc0bc8aa3c97a4c164b3803bc6ccc4e5d5d928110cf0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000059a7b662af14e0d3c7016cbafedd42173501fc97199c07114f47acdabd930332af4dea84202253b42b6d947b33de27c0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b272000000000000000000000000000000001949a50c589ec63db98d39491100e8e407345f9b3874f3a28e9b77d2fc28bf31ef976841c4276cb669dc4f3cca42fffd0000000000000000000000000000000001cd05bfae784b11f1c102a7b0268fc480d19cd7c65a3583f4624fc0bc8aa3c97a4c164b3803bc6ccc4e5d5d928110cf0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000059a7b662af14e0d3c7016cbafedd42173501fc97199c07114f47acdabd930332af4dea84202253b42b6d947b33de27c0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b272000000000000000000000000000000001949a50c589ec63db98d39491100e8e407345f9b3874f3a28e9b77d2fc28bf31ef976841c4276cb669dc4f3cca42fffd0000000000000000000000000000000001cd05bfae784b11f1c102a7b0268fc480d19cd7c65a3583f4624fc0bc8aa3c97a4c164b3803bc6ccc4e5d5d928110cf0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000059a7b662af14e0d3c7016cbafedd42173501fc97199c07114f47acdabd930332af4dea84202253b42b6d947b33de27c0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b272000000000000000000000000000000001949a50c589ec63db98d39491100e8e407345f9b3874f3a28e9b77d2fc28bf31ef976841c4276cb669dc4f3cca42fffd0000000000000000000000000000000001cd05bfae784b11f1c102a7b0268fc480d19cd7c65a3583f4624fc0bc8aa3c97a4c164b3803bc6ccc4e5d5d928110cf,0000000000000000000000000000000000000000000000000000000000000001,299000, -000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab400000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d00000000000000000000000000000000102ba7f9db164318194ab17f615ca8cc741dab773e8609023c58a722f1e4f209eb4bc3cff7a2b71c08bdd421068b9ff700000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab400000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c66400000000000000000000000000000000082961f3752eb7324800bc217514792b2111abe52df54973615737b8ec3a9f2db36dc1782d20782df8efae4bd7b8559600000000000000000000000000000000075729f6b9337b3f25da9d33cdbed7207a589a342cee61e8e99e030244b814accc93b26a0ca6d9ba08acf29511ef15be000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d00000000000000000000000000000000102ba7f9db164318194ab17f615ca8cc741dab773e8609023c58a722f1e4f209eb4bc3cff7a2b71c08bdd421068b9ff700000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c66400000000000000000000000000000000082961f3752eb7324800bc217514792b2111abe52df54973615737b8ec3a9f2db36dc1782d20782df8efae4bd7b8559600000000000000000000000000000000075729f6b9337b3f25da9d33cdbed7207a589a342cee61e8e99e030244b814accc93b26a0ca6d9ba08acf29511ef15be000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab400000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d00000000000000000000000000000000102ba7f9db164318194ab17f615ca8cc741dab773e8609023c58a722f1e4f209eb4bc3cff7a2b71c08bdd421068b9ff700000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab400000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c66400000000000000000000000000000000082961f3752eb7324800bc217514792b2111abe52df54973615737b8ec3a9f2db36dc1782d20782df8efae4bd7b8559600000000000000000000000000000000075729f6b9337b3f25da9d33cdbed7207a589a342cee61e8e99e030244b814accc93b26a0ca6d9ba08acf29511ef15be000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d00000000000000000000000000000000102ba7f9db164318194ab17f615ca8cc741dab773e8609023c58a722f1e4f209eb4bc3cff7a2b71c08bdd421068b9ff700000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c66400000000000000000000000000000000082961f3752eb7324800bc217514792b2111abe52df54973615737b8ec3a9f2db36dc1782d20782df8efae4bd7b8559600000000000000000000000000000000075729f6b9337b3f25da9d33cdbed7207a589a342cee61e8e99e030244b814accc93b26a0ca6d9ba08acf29511ef15be,0000000000000000000000000000000000000000000000000000000000000001,299000, -0000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c50000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000b760253acb4c395332c1e3584f60d965a4b0b4f5274f457d05bdafb08546282829ae2c61e482a43136afa03ca0102e90000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c50000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca000000000000000000000000000000000144811cb59ebf7e5a380ca9c2b30dc987778224453ea65ab9fcc5ddd0a91a47aac13a459cf5ecc5bffc5f3c0502e8cc0000000000000000000000000000000007b4c5f3cf21e53b36ed096b1d0998c2be68f6977cbe3e12a63ec77c545316c556bce0a891a762b8af6a4304d0d911e60000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000b760253acb4c395332c1e3584f60d965a4b0b4f5274f457d05bdafb08546282829ae2c61e482a43136afa03ca0102e90000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca000000000000000000000000000000000144811cb59ebf7e5a380ca9c2b30dc987778224453ea65ab9fcc5ddd0a91a47aac13a459cf5ecc5bffc5f3c0502e8cc0000000000000000000000000000000007b4c5f3cf21e53b36ed096b1d0998c2be68f6977cbe3e12a63ec77c545316c556bce0a891a762b8af6a4304d0d911e60000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c50000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000b760253acb4c395332c1e3584f60d965a4b0b4f5274f457d05bdafb08546282829ae2c61e482a43136afa03ca0102e90000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c50000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca000000000000000000000000000000000144811cb59ebf7e5a380ca9c2b30dc987778224453ea65ab9fcc5ddd0a91a47aac13a459cf5ecc5bffc5f3c0502e8cc0000000000000000000000000000000007b4c5f3cf21e53b36ed096b1d0998c2be68f6977cbe3e12a63ec77c545316c556bce0a891a762b8af6a4304d0d911e60000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000b760253acb4c395332c1e3584f60d965a4b0b4f5274f457d05bdafb08546282829ae2c61e482a43136afa03ca0102e90000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca000000000000000000000000000000000144811cb59ebf7e5a380ca9c2b30dc987778224453ea65ab9fcc5ddd0a91a47aac13a459cf5ecc5bffc5f3c0502e8cc0000000000000000000000000000000007b4c5f3cf21e53b36ed096b1d0998c2be68f6977cbe3e12a63ec77c545316c556bce0a891a762b8af6a4304d0d911e6,0000000000000000000000000000000000000000000000000000000000000001,299000, -000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a600000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a470000000000000000000000000000000000cff9184748200fc11245bb213f9d00c3eef7f4698174e9e7a1ff6cf072a30d5f28173aed5fbbdf46b444282225790500000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a600000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca2000000000000000000000000000000000207d4a04d23b1cc880275ea6075f929ea097e464b208c94bf7cb545c76add5a557e5fe08ce4070c77be430e21b38e660000000000000000000000000000000017e907545d9a6a5733fd81aeea0dd92221328dc5b2e745b3102a28f9cbe013b548a061b1ffd55b18059e523a5908d7cd000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a470000000000000000000000000000000000cff9184748200fc11245bb213f9d00c3eef7f4698174e9e7a1ff6cf072a30d5f28173aed5fbbdf46b444282225790500000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca2000000000000000000000000000000000207d4a04d23b1cc880275ea6075f929ea097e464b208c94bf7cb545c76add5a557e5fe08ce4070c77be430e21b38e660000000000000000000000000000000017e907545d9a6a5733fd81aeea0dd92221328dc5b2e745b3102a28f9cbe013b548a061b1ffd55b18059e523a5908d7cd000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a600000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a470000000000000000000000000000000000cff9184748200fc11245bb213f9d00c3eef7f4698174e9e7a1ff6cf072a30d5f28173aed5fbbdf46b444282225790500000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a600000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca2000000000000000000000000000000000207d4a04d23b1cc880275ea6075f929ea097e464b208c94bf7cb545c76add5a557e5fe08ce4070c77be430e21b38e660000000000000000000000000000000017e907545d9a6a5733fd81aeea0dd92221328dc5b2e745b3102a28f9cbe013b548a061b1ffd55b18059e523a5908d7cd000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a470000000000000000000000000000000000cff9184748200fc11245bb213f9d00c3eef7f4698174e9e7a1ff6cf072a30d5f28173aed5fbbdf46b444282225790500000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca2000000000000000000000000000000000207d4a04d23b1cc880275ea6075f929ea097e464b208c94bf7cb545c76add5a557e5fe08ce4070c77be430e21b38e660000000000000000000000000000000017e907545d9a6a5733fd81aeea0dd92221328dc5b2e745b3102a28f9cbe013b548a061b1ffd55b18059e523a5908d7cd,0000000000000000000000000000000000000000000000000000000000000001,299000, -000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000012feb2cdef2060f089c32a68f91d4ac9e0a1461cbf4bd1bf8ed26782a700052ee2fb73af689490ba12233c8dd133158d00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c0130000000000000000000000000000000016b19dd160140ab5592e4e1f46ad0e3e413ceed148adfb0bf5b240a161b22b7dac5b45a389770a634bc8551f72dd12710000000000000000000000000000000009f439fffd4bbbf789bd0b5521a9dcea6e66282a167ce9b26d6543fba82101003d31f4a0ed3592f820d0a6d81c004954000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000012feb2cdef2060f089c32a68f91d4ac9e0a1461cbf4bd1bf8ed26782a700052ee2fb73af689490ba12233c8dd133158d00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c0130000000000000000000000000000000016b19dd160140ab5592e4e1f46ad0e3e413ceed148adfb0bf5b240a161b22b7dac5b45a389770a634bc8551f72dd12710000000000000000000000000000000009f439fffd4bbbf789bd0b5521a9dcea6e66282a167ce9b26d6543fba82101003d31f4a0ed3592f820d0a6d81c004954000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000012feb2cdef2060f089c32a68f91d4ac9e0a1461cbf4bd1bf8ed26782a700052ee2fb73af689490ba12233c8dd133158d00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c0130000000000000000000000000000000016b19dd160140ab5592e4e1f46ad0e3e413ceed148adfb0bf5b240a161b22b7dac5b45a389770a634bc8551f72dd12710000000000000000000000000000000009f439fffd4bbbf789bd0b5521a9dcea6e66282a167ce9b26d6543fba82101003d31f4a0ed3592f820d0a6d81c004954000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000012feb2cdef2060f089c32a68f91d4ac9e0a1461cbf4bd1bf8ed26782a700052ee2fb73af689490ba12233c8dd133158d00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c0130000000000000000000000000000000016b19dd160140ab5592e4e1f46ad0e3e413ceed148adfb0bf5b240a161b22b7dac5b45a389770a634bc8551f72dd12710000000000000000000000000000000009f439fffd4bbbf789bd0b5521a9dcea6e66282a167ce9b26d6543fba82101003d31f4a0ed3592f820d0a6d81c004954,0000000000000000000000000000000000000000000000000000000000000001,299000, -0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc00000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000c28402cd28b39ce814adfdb8453fd646f5ae3e41d718e5af1fd250e3b0cabf2efa01f045f3dce88c84f0b19b3fefbb00000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc00000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000006a9c650ba974e0fa2fdf6d3659220f47d76f581ec156662b4e9dc4470164e68df977370d2bcf1cad4191031fdc1476f000000000000000000000000000000001068554cf7ba1173150be2cfb7ab4503ecea55b5f29f7d24086ba68b610637b5f0192bf1fe04557b68c1eafa9736daeb0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000c28402cd28b39ce814adfdb8453fd646f5ae3e41d718e5af1fd250e3b0cabf2efa01f045f3dce88c84f0b19b3fefbb00000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000006a9c650ba974e0fa2fdf6d3659220f47d76f581ec156662b4e9dc4470164e68df977370d2bcf1cad4191031fdc1476f000000000000000000000000000000001068554cf7ba1173150be2cfb7ab4503ecea55b5f29f7d24086ba68b610637b5f0192bf1fe04557b68c1eafa9736daeb0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc00000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000c28402cd28b39ce814adfdb8453fd646f5ae3e41d718e5af1fd250e3b0cabf2efa01f045f3dce88c84f0b19b3fefbb00000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc00000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000006a9c650ba974e0fa2fdf6d3659220f47d76f581ec156662b4e9dc4470164e68df977370d2bcf1cad4191031fdc1476f000000000000000000000000000000001068554cf7ba1173150be2cfb7ab4503ecea55b5f29f7d24086ba68b610637b5f0192bf1fe04557b68c1eafa9736daeb0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000c28402cd28b39ce814adfdb8453fd646f5ae3e41d718e5af1fd250e3b0cabf2efa01f045f3dce88c84f0b19b3fefbb00000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000006a9c650ba974e0fa2fdf6d3659220f47d76f581ec156662b4e9dc4470164e68df977370d2bcf1cad4191031fdc1476f000000000000000000000000000000001068554cf7ba1173150be2cfb7ab4503ecea55b5f29f7d24086ba68b610637b5f0192bf1fe04557b68c1eafa9736daeb,0000000000000000000000000000000000000000000000000000000000000001,299000, -0000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c3585000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc012040000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec00000000000000000000000000000000003b90ede51e98dd9163b911431789b534aef452b9bd1b423a5d8c2ea1652cd05aa308568a7031d958fc2f32e9cb37526000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc012040000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c3585000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000011396b6eafe9d8f61a831ef9d6688e586602c5138ddc65d1bf69a9916c1e8db31ddf432b1406a597c7dfb49c1339727900000000000000000000000000000000183398716b5783fb7971e27306f651b8a91efc0462ef799742c8eaeeaf919d08348e8c1700b1b850e220b0e0133f98a70000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec00000000000000000000000000000000003b90ede51e98dd9163b911431789b534aef452b9bd1b423a5d8c2ea1652cd05aa308568a7031d958fc2f32e9cb37526000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000011396b6eafe9d8f61a831ef9d6688e586602c5138ddc65d1bf69a9916c1e8db31ddf432b1406a597c7dfb49c1339727900000000000000000000000000000000183398716b5783fb7971e27306f651b8a91efc0462ef799742c8eaeeaf919d08348e8c1700b1b850e220b0e0133f98a70000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c3585000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc012040000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec00000000000000000000000000000000003b90ede51e98dd9163b911431789b534aef452b9bd1b423a5d8c2ea1652cd05aa308568a7031d958fc2f32e9cb37526000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc012040000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c3585000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000011396b6eafe9d8f61a831ef9d6688e586602c5138ddc65d1bf69a9916c1e8db31ddf432b1406a597c7dfb49c1339727900000000000000000000000000000000183398716b5783fb7971e27306f651b8a91efc0462ef799742c8eaeeaf919d08348e8c1700b1b850e220b0e0133f98a70000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec00000000000000000000000000000000003b90ede51e98dd9163b911431789b534aef452b9bd1b423a5d8c2ea1652cd05aa308568a7031d958fc2f32e9cb37526000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000011396b6eafe9d8f61a831ef9d6688e586602c5138ddc65d1bf69a9916c1e8db31ddf432b1406a597c7dfb49c1339727900000000000000000000000000000000183398716b5783fb7971e27306f651b8a91efc0462ef799742c8eaeeaf919d08348e8c1700b1b850e220b0e0133f98a7,0000000000000000000000000000000000000000000000000000000000000001,299000, -000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d57950000000000000000000000000000000005bd0ff24e15f0682c6d1a09096fca081991bd3f9f10a2a18d3f1c7470e9a2bc0ac3b149b7750aedce9c1ae6bd773820000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd00000000000000000000000000000000189be781abc010602e9262930d8dfdb2d7df81be0de1656554cb5afa3d059f1cc389678008ea84ba23ed5a54e9b07827000000000000000000000000000000001476dab5bd29af19c4e8f947b4255e4b86625fd4451b902fd10180e9ce7ed639c6e65683fabf0824a2a00185e82c3df5000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d57950000000000000000000000000000000005bd0ff24e15f0682c6d1a09096fca081991bd3f9f10a2a18d3f1c7470e9a2bc0ac3b149b7750aedce9c1ae6bd773820000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd00000000000000000000000000000000189be781abc010602e9262930d8dfdb2d7df81be0de1656554cb5afa3d059f1cc389678008ea84ba23ed5a54e9b07827000000000000000000000000000000001476dab5bd29af19c4e8f947b4255e4b86625fd4451b902fd10180e9ce7ed639c6e65683fabf0824a2a00185e82c3df5000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d57950000000000000000000000000000000005bd0ff24e15f0682c6d1a09096fca081991bd3f9f10a2a18d3f1c7470e9a2bc0ac3b149b7750aedce9c1ae6bd773820000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd00000000000000000000000000000000189be781abc010602e9262930d8dfdb2d7df81be0de1656554cb5afa3d059f1cc389678008ea84ba23ed5a54e9b07827000000000000000000000000000000001476dab5bd29af19c4e8f947b4255e4b86625fd4451b902fd10180e9ce7ed639c6e65683fabf0824a2a00185e82c3df5000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d57950000000000000000000000000000000005bd0ff24e15f0682c6d1a09096fca081991bd3f9f10a2a18d3f1c7470e9a2bc0ac3b149b7750aedce9c1ae6bd773820000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd00000000000000000000000000000000189be781abc010602e9262930d8dfdb2d7df81be0de1656554cb5afa3d059f1cc389678008ea84ba23ed5a54e9b07827000000000000000000000000000000001476dab5bd29af19c4e8f947b4255e4b86625fd4451b902fd10180e9ce7ed639c6e65683fabf0824a2a00185e82c3df5,0000000000000000000000000000000000000000000000000000000000000001,299000, -00000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1d9f0000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40,,115000,invalid input parameters, invalid input length for pairing -000000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1d9f0000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40,,138000,invalid input parameters, invalid input length for pairing -,,115000,invalid input parameters, Invalid number of pairs -0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f570000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1d9f0000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40,,138000,invalid input parameters, G1 point is not on curve -0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1da00000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40,,138000,invalid input parameters, G2 point is not on curve -000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80000000000000000000000000000000000874389c02d4cf1c61bc54c4c24def11dfbe7880bc998a95e70063009451ee8226fec4b278aade3a7cea55659459f1d500000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79e000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd9000000000000000000000000000000000118cd94e36ab177de95f52f180fdbdc584b8d30436eb882980306fa0625f07a1f7ad3b4c38a921c53d14aa9a6ba5b8d600000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79e000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e,,161000,invalid input parameters, G1 point is not on curve -000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80000000000000000000000000000000000874389c02d4cf1c61bc54c4c24def11dfbe7880bc998a95e70063009451ee8226fec4b278aade3a7cea55659459f1d500000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79e000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd8000000000000000000000000000000000118cd94e36ab177de95f52f180fdbdc584b8d30436eb882980306fa0625f07a1f7ad3b4c38a921c53d14aa9a6ba5b8d600000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79f000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e,,161000,invalid input parameters, G2 point is not on curve +0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1d9f0000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40,0000000000000000000000000000000000000000000000000000000000000000,108000, +00000000000000000000000000000000117dbe419018f67844f6a5e1b78a1e597283ad7b8ee7ac5e58846f5a5fd68d0da99ce235a91db3ec1cf340fe6b7afcdb0000000000000000000000000000000013316f23de032d25e912ae8dc9b54c8dba1be7cecdbb9d2228d7e8f652011d46be79089dd0a6080a73c82256ce5e4ed200000000000000000000000000000000192fa5d8732ff9f38e0b1cf12eadfd2608f0c7a39aced7746837833ae253bb57ef9c0d98a4b69eeb2950901917e99d1e0000000000000000000000000000000009aeb10c372b5ef1010675c6a4762fda33636489c23b581c75220589afbc0cc46249f921eea02dd1b761e036ffdbae220000000000000000000000000000000002d225447600d49f932b9dd3ca1e6959697aa603e74d8666681a2dca8160c3857668ae074440366619eb8920256c4e4a00000000000000000000000000000000174882cdd3551e0ce6178861ff83e195fecbcffd53a67b6f10b4431e423e28a480327febe70276036f60bb9c99cf7633,0000000000000000000000000000000000000000000000000000000000000000,108000, +0000000000000000000000000000000008ab7b556c672db7883ec47efa6d98bb08cec7902ebb421aac1c31506b177ac444ffa2d9b400a6f1cbdc6240c607ee110000000000000000000000000000000016b7fa9adf4addc2192271ce7ad3c8d8f902d061c43b7d2e8e26922009b777855bffabe7ed1a09155819eabfa87f276f000000000000000000000000000000000a69d6d9f79e19b38e6bf5a245dc820bddbdfe038d50932f76d0e4629d759f8ca6d573fcfc39256305daedf452f9fdf40000000000000000000000000000000015f5949369e58487afcecf8018775d1b0a73e913bf77e13d2e5a843bbbeba7d1978ca27ae8bfc87d30f567dd396b980e00000000000000000000000000000000182198bb38a0353b8db25389e56ab0d8679a1bda008a65dad77e4c95bc6804f6311eb16c761e1a5e2a5f87cfada49fa4000000000000000000000000000000000eb5483959e98c30e71db52615f63521378b156f142d46f3bb285b94aef39d80feacec335b797c5a68dc17ba89d43e0f,0000000000000000000000000000000000000000000000000000000000000000,108000, +0000000000000000000000000000000015ff9a232d9b5a8020a85d5fe08a1dcfb73ece434258fe0e2fddf10ddef0906c42dcb5f5d62fc97f934ba900f17beb330000000000000000000000000000000009cfe4ee2241d9413c616462d7bac035a6766aeaab69c81e094d75b840df45d7e0dfac0265608b93efefb9a8728b98e4000000000000000000000000000000000286f09f931c07507ba4aafb7d43befe0b1d25b27ecc9199b19a9dc20bc7ec0329479ef224e00dece67ec0d61f1ca5ae0000000000000000000000000000000014e6ed154b5552be5c463b730b2134f83e0071dcdadfaa68e6c7c7f6e17dabb7daf06e409177bc4b38cfdb8248157618000000000000000000000000000000000f145e998dc6eb0c2b2be87db62949c7bfa63e8b01c8634248010fd623cfaec5d6c6c193331440957d333bf0c988b7b10000000000000000000000000000000002a1ab3eea343cfdea5779f64b3bddbf0769aded60e54a7507338f044310ba239430663394f110e560594d6042a99f1c,0000000000000000000000000000000000000000000000000000000000000000,108000, +0000000000000000000000000000000017a17b82e3bfadf3250210d8ef572c02c3610d65ab4d7366e0b748768a28ee6a1b51f77ed686a64f087f36f641e7dca900000000000000000000000000000000077ea73d233ccea51dc4d5acecf6d9332bf17ae51598f4b394a5f62fb387e9c9aa1d6823b64a074f5873422ca57545d3000000000000000000000000000000000d1007ca90451229d3780d66d3aed7c9d8fc82e9d45549e8586600e38eb6763f3c466e2f6ba6ba1dafd8f00cc452dda20000000000000000000000000000000001d017d920a262b6d6597bab532f83270f41526409510e80278d1c3595ceabb9ceba8ae32b1817297ff78ea7a0d252e8000000000000000000000000000000000935b7a59d2e51bbb2f9b54ccb06ebee9d189fa82f0e97d10c8020badb3de7fe15731b5895faed8cad92ae76e2e1b649000000000000000000000000000000000792dadd48a20040ad43facedc109747411895180813349d41d0e5b389176bfb15895d41665be8d1afa80835ef818eca,0000000000000000000000000000000000000000000000000000000000000000,108000, +000000000000000000000000000000000c1243478f4fbdc21ea9b241655947a28accd058d0cdb4f9f0576d32f09dddaf0850464550ff07cab5927b3e4c863ce90000000000000000000000000000000015fb54db10ffac0b6cd374eb7168a8cb3df0a7d5f872d8e98c1f623deb66df5dd08ff4c3658f2905ec8bd02598bd4f9000000000000000000000000000000000095353ad699b89ac82ca7ef631775b2b3a6e3ed8dd320440cdb929baa428e63cb902a83857cc0e2621470544c69e84aa000000000000000000000000000000000892559ade1060b0eef2cbc1c74de62a7ff076a3621e5f0f159672a549f1201f2ffb3ac12c8b12cb86ae3e386c33e219000000000000000000000000000000000750df4632a7126ddb08658a4001f949b9764d9cc43a9393cc55d8fdbb15d4a1186dd87a6433d111888a7804540ad9fc0000000000000000000000000000000017554bd444665df044b91b0b2614017bbfcd7acc7f8c5a16cea2861235578ce2b27dcced9fba234999fa478cd3f6e42d,0000000000000000000000000000000000000000000000000000000000000000,108000, +000000000000000000000000000000000328f09584b6d6c98a709fc22e184123994613aca95a28ac53df8523b92273eb6f4e2d9b2a7dcebb474604d54a210719000000000000000000000000000000001220ebde579911fe2e707446aaad8d3789fae96ae2e23670a4fd856ed82daaab704779eb4224027c1ed9460f39951a1b00000000000000000000000000000000175dadb6ee656ec6aebf8d0e5edaee3f119c74e0ea64e374be9e8ab9fd3d085fceeedf4ed8de676ebe9065d83b0542ad0000000000000000000000000000000005cd6a875329c23e4918976cf997e93e403957acfc999f8159a630d21ab6f1762925c063784237262bedc82402ad81bb0000000000000000000000000000000003274bcb8db35e50164d136c2a98b5a6d2fb5f9767d0ee11c1358bf7ca5ed96d9122f8c1051ba3c658cc89777d03dfa5000000000000000000000000000000000380a240443dff85b6542f75db28b87c39e278cdb8d9627efbbc63b229e6ce783f6fb0114c8e91c2fd6ea71c95bb99a4,0000000000000000000000000000000000000000000000000000000000000000,108000, +0000000000000000000000000000000002ebfa98aa92c32a29ebe17fcb1819ba82e686abd9371fcee8ea793b4c72b6464085044f818f1f5902396df0122830cb00000000000000000000000000000000001184715b8432ed190b459113977289a890f68f6085ea111466af15103c9c02467da33e01d6bff87fd57db6ccba442a000000000000000000000000000000000834cf1b4149d100c41b1bca0495e455002eb6596bddcb94ae48d0c65957e8b313372f8e0d6e57504664b266f38293150000000000000000000000000000000000de2875fbd14760bac4c2cc7d3f239177efe9f7f61f767be420d44f24c9fb863efd60dcd732986db8c5b72470617ea60000000000000000000000000000000000bc9535ebf11c2dcc8c7d3bcd09d7d14035635fccb5fddb7df29ce8855e79f99809781d6ffbbcb33d1227314609abee00000000000000000000000000000000039bbfb4d969d702255e3be7f255a97529a19687ce38cb70637c37894d4102591feef428b0afe8c9ef50310ae3b83091,0000000000000000000000000000000000000000000000000000000000000000,108000, +0000000000000000000000000000000009d6424e002439998e91cd509f85751ad25e574830c564e7568347d19e3f38add0cab067c0b4b0801785a78bcbeaf246000000000000000000000000000000000ef6d7db03ee654503b46ff0dbc3297536a422e963bda9871a8da8f4eeb98dedebd6071c4880b4636198f4c2375dc795000000000000000000000000000000000fc09c241899fa6e8cc3b31830e9c9f2777d2bc6758260c9f6af5fce56c9dc1a8daedb5bcb7d7669005ccf6bfacf71050000000000000000000000000000000018e95921a76bc37308e2f10afb36a812b622afe19c8db84465ab8b3293c7d371948ee0578dbb025eed7ed60686109aa0000000000000000000000000000000001558cdfbac6ea2c4c1f4b9a2e809b19e9f4ba47b78d2b18185ed8c97c2f9c2990beadc78b85c123b4c3c08d5c5b3bbef000000000000000000000000000000000ea4dfdd12b9a4b9a3172671a6eafed7508af296813ec5700b697d9239ae484bcf7ab630e5b6830d6d95675be5174bb2,0000000000000000000000000000000000000000000000000000000000000000,108000, +0000000000000000000000000000000002d1cdb93191d1f9f0308c2c55d0208a071f5520faca7c52ab0311dbc9ba563bd33b5dd6baa77bf45ac2c3269e945f4800000000000000000000000000000000072a52106e6d7b92c594c4dacd20ef5fab7141e45c231457cd7e71463b2254ee6e72689e516fa6a8f29f2a173ce0a1900000000000000000000000000000000000b36d8fb9bd156f618ab8049d41dfe0698218764c0abb10e12fae43c8810b8e2a5201364e2778f6f433b199bb8f9a6800000000000000000000000000000000000707eb15411b63722b4308c0ed4288320078d2463ae659ad4fb3f9ef8124f379df92d64e077403e50727388adb59ac00000000000000000000000000000000158e1249d5b91614924acb23899c6bae408697dec0982c10d0459746499f4e6739afb9d5129568106ed1a1caefeaa9640000000000000000000000000000000019e841562e4aa75321143f8ce1e5ec6158fa5cb8b98c839a486188260c18ee8a7600930f23aa39eac2eb520d6a0fba90,0000000000000000000000000000000000000000000000000000000000000000,108000, +0000000000000000000000000000000000641642f6801d39a09a536f506056f72a619c50d043673d6d39aa4af11d8e3ded38b9c3bbc970dbc1bd55d68f94b50d0000000000000000000000000000000009ab050de356a24aea90007c6b319614ba2f2ed67223b972767117769e3c8e31ee4056494628fb2892d3d37afb6ac94300000000000000000000000000000000186a9661d6fb539e8687ac214301b2d7623caedd76f4055089befba6ef2c96263d810921ad7783d229f82783c9def424000000000000000000000000000000000447f3e20caa1f99fbaccab7bde2bd37fe77cea691ebf2b9499f95bbbb77afe72b7039eb0c05970b61360fcf8ade73730000000000000000000000000000000005e11f828eda86c10a1d7929def547ac06885da278afae59c5d95453caf0a2d8ed186fa7c6d0a7ab6e9142cfa4b338190000000000000000000000000000000003d954e61b6ab71042b19e804efccd4956b56662f27f70a9255cec0c464b86c0e83721ad3785dec62dd4a9dd3d6d5d53,0000000000000000000000000000000000000000000000000000000000000000,108000, +000000000000000000000000000000000fd4893addbd58fb1bf30b8e62bef068da386edbab9541d198e8719b2de5beb9223d87387af82e8b55bd521ff3e47e2d000000000000000000000000000000000f3a923b76473d5b5a53501790cb02597bb778bdacb3805a9002b152d22241ad131d0f0d6a260739cbab2c2fe602870e0000000000000000000000000000000002b94534aa0ba923bda34cbe92b3cd7a3e263741b120240ff5bdb8b718f094d3867e3fcabeab4a7be39c8f8c4fdd10d900000000000000000000000000000000048711cf6a82534d64d072355cb8fe647808e7e8b2d9ac9ed52eb7fe121647a721dd1234c71ecd163d91701eb7331cac00000000000000000000000000000000141ef2e23a1ecc7ef2ed3ea915492e79cfffe60b5e0de8441e878bd0653843d79c724e3c5ebe2321361df99f8932ddc200000000000000000000000000000000085513b4009f29b3e00a91c2c4be418368560802ba4194cbd2f4fa3d72a55fcae547014434514a8b2a8fe3e0b28d2773,0000000000000000000000000000000000000000000000000000000000000000,108000, +0000000000000000000000000000000002cb4b24c8aa799fd7cb1e4ab1aab1372113200343d8526ea7bc64dfaf926baf5d90756a40e35617854a2079cd07fba40000000000000000000000000000000003327ca22bd64ebd673cc6d5b02b2a8804d5353c9d251637c4273ad08d581cc0d58da9bea27c37a0b3f4961dbafd276b0000000000000000000000000000000009143507a24313ee33401955fc46562c9b20c9917df3b40ccbd7ed43b1349d4551cfd98a4976d6fec5fc289460c8d89900000000000000000000000000000000060566b79df5cc975e669da8ca3a7fa91bf3f5c9fb871c3d62f4a3e79dbc341b89d38b588e5414bc385d5e3cbf3ab9310000000000000000000000000000000016bf40b8cc4c01a87aafae0c4439b623a51ba9a383756a550b69d627d6f45209f0d87e4f9be9edff35c986f7b9c49e3f000000000000000000000000000000001842d9172bce51a164fbdbdb108d0faae07e4642f21c80e40ac31e737657472ae3dfe552b65349629c210a068c4afc0e,0000000000000000000000000000000000000000000000000000000000000000,108000, +00000000000000000000000000000000024ad70f2b2105ca37112858e84c6f5e3ffd4a8b064522faae1ecba38fabd52a6274cb46b00075deb87472f11f2e67d90000000000000000000000000000000010a502c8b2a68aa30d2cb719273550b9a3c283c35b2e18a01b0b765344ffaaa5cb30a1e3e6ecd3a53ab67658a5787681000000000000000000000000000000000ab19bbddd661e9db8fe4cb307ecebdc5e03efbb95c5b44716c7075bd60efcfc67de0bfd7c46ad989a613946c90a4c1000000000000000000000000000000000120800e7f344cda816299fa37f603ade06beb3b10907f5af896d6b4e42f7f865b756f14164db84411c56cb2ea81f60be000000000000000000000000000000000f688ddd257e66362af1437b6922d3397a7c3dd6dea6bca8ebd6375e75bf2de40bc287cbf3434388191e56b92949c83b0000000000000000000000000000000005252465784aff8c1c707da58b5808c69583bf852d68f96912bc53f8dae4536b09ccbbd25a49d9e744118992b92b6792,0000000000000000000000000000000000000000000000000000000000000000,108000, +0000000000000000000000000000000000704cc57c8e0944326ddc7c747d9e7347a7f6918977132eea269f161461eb64066f773352f293a3ac458dc3ccd5026a000000000000000000000000000000001099d3c2bb2d082f2fdcbed013f7ac69e8624f4fcf6dfab3ee9dcf7fbbdb8c49ee79de40e887c0b6828d2496e3a6f768000000000000000000000000000000000e3165efe00f69aee84ac56d2161f07c017abfaadeaad34f8c96799d68bae0e6f9b557bbf9137e7826f49f29c58d1ef9000000000000000000000000000000000de0dce7ea371ad60f21f2cb61cb582b5072408a7efc91edf05b36a1a3b58fd9e6cf808d75157eedccc8f1c93a8ae07d0000000000000000000000000000000016d911943d80427385ebac1d1b293914a9e4dd9db06c1d6a758192d63c8fc9368e02eae7fb0e3a7859408f215cfa76ca0000000000000000000000000000000007bfdc6afb8acec625e50ecbc08a5cdb7862b795866323679885ba5cba3fd51f181078e03fe35e96e6383c077eed1bf5,0000000000000000000000000000000000000000000000000000000000000000,108000, +00000000000000000000000000000000130535a29392c77f045ac90e47f2e7b3cffff94494fe605aad345b41043f6663ada8e2e7ecd3d06f3b8854ef92212f42000000000000000000000000000000001699a3cc1f10cd2ed0dc68eb916b4402e4f12bf4746893bf70e26e209e605ea89e3d53e7ac52bd07713d3c8fc671931d000000000000000000000000000000000a68dccbe3452731f075580fe6102b8ee5265007ee19c56d95bcb096a3a6ac444f4145b980f41afcb0a865853b279bc600000000000000000000000000000000164767ea55a9038ac2dd254d8c8a4970dba93dacdf5416aecaa407914719cab165e7a32784b2c41652a86358737d831f000000000000000000000000000000000da9441fbc6578c85fdeca49082c9ebbf183de894d67c65158380ee56132d3cdb44b100d72b6d3b82688defb75d2aa390000000000000000000000000000000017d570e4f6e46550679d5d12c347414da207060f594620e2f8db66df8e0b06c912290b207a268e782d4b45db19a199db,0000000000000000000000000000000000000000000000000000000000000000,108000, +000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80000000000000000000000000000000000874389c02d4cf1c61bc54c4c24def11dfbe7880bc998a95e70063009451ee8226fec4b278aade3a7cea55659459f1d500000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79e000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd8000000000000000000000000000000000118cd94e36ab177de95f52f180fdbdc584b8d30436eb882980306fa0625f07a1f7ad3b4c38a921c53d14aa9a6ba5b8d600000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79e000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e,0000000000000000000000000000000000000000000000000000000000000001,151000, +00000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a67000000000000000000000000000000000013a80ede40df002b72f6b33b1f0e3862d505efbe0721dce495d18920d542c98cdd2daf5164dbd1a2fee917ba943debe0000000000000000000000000000000001c2d8d353d5983f22a5313ddd58fdc0d9c994b2915dbc87a9b65b7b98ff00b62e140a27dc322d42b3ad190c1b3728dd0000000000000000000000000000000010412f3625947b38bb380a6ed059f1677b7a7afcb91517837c563dadd0e285b95740a200ddff6570d4d92bb636b625bb0000000000000000000000000000000015f4f9a480a57bd1b2388532ab045a1ba93d2f6589a3022c585fe06a1d611165c99d70be06251812405c9c37d6e9f7730000000000000000000000000000000001a78e6c5062a6634a56e9853ff5afacb2e7cf31fd0ea5f0d8c8ac6174c88133cf2f63450ec4590544c9a0e37daac1f900000000000000000000000000000000043c4ff154778330b4d5457b7811b551dbbf9701b402230411c527282fb5d2ba12cb445709718d5999e79fdd74c0a6700000000000000000000000000000000018c690fc5571f69793ec3c82915ac9513726ec891312f4f11dd3ba0ee95cc98b50d925099b0642e58a106e8456bbcbed0000000000000000000000000000000001c2d8d353d5983f22a5313ddd58fdc0d9c994b2915dbc87a9b65b7b98ff00b62e140a27dc322d42b3ad190c1b3728dd0000000000000000000000000000000010412f3625947b38bb380a6ed059f1677b7a7afcb91517837c563dadd0e285b95740a200ddff6570d4d92bb636b625bb0000000000000000000000000000000015f4f9a480a57bd1b2388532ab045a1ba93d2f6589a3022c585fe06a1d611165c99d70be06251812405c9c37d6e9f7730000000000000000000000000000000001a78e6c5062a6634a56e9853ff5afacb2e7cf31fd0ea5f0d8c8ac6174c88133cf2f63450ec4590544c9a0e37daac1f9,0000000000000000000000000000000000000000000000000000000000000001,151000, +0000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e880000000000000000000000000000000008d7489c2d78f17b2b9b1d535f21588d8761b8fb323b08fa9af8a60f39b26e98af76aa883522f21e083c8a14c2e7edb6000000000000000000000000000000000818e567aea83eaf3142984bb736b443743659626c407987b604a30c79756081fa6ae6beeb2e6c652dbfe9cf62d44e3900000000000000000000000000000000193f0317305fde1046acda2c9491e376aa67244f68ef6495845d049e1293082af91f880be935d9d8ad0e25ad918caae200000000000000000000000000000000109224b8178be58ea4e4a194ca66bef9d14f6fc2c625d25feaa4f32e0f4d72d91024d96839bc96e6a624c5ad6221bd94000000000000000000000000000000000e42decf8a987efaeb4ede37236b637e61249bf6245679be7fd4d633e2d814ed4748b73890ad3c4fcbcfb4960cb67ae70000000000000000000000000000000009f9a78a70b9973c43182ba54bb6e363c6984d5f7920c1d347c5ff82e6093e73f4fb5e3cd985c9ddf9af936b16200e88000000000000000000000000000000001129c94e0c06f51f1f808a62e42a5449dd159289c14a09c4cc382c91bcfe878b6f3555767c310de1b1c275eb3d17bcf5000000000000000000000000000000000818e567aea83eaf3142984bb736b443743659626c407987b604a30c79756081fa6ae6beeb2e6c652dbfe9cf62d44e3900000000000000000000000000000000193f0317305fde1046acda2c9491e376aa67244f68ef6495845d049e1293082af91f880be935d9d8ad0e25ad918caae200000000000000000000000000000000109224b8178be58ea4e4a194ca66bef9d14f6fc2c625d25feaa4f32e0f4d72d91024d96839bc96e6a624c5ad6221bd94000000000000000000000000000000000e42decf8a987efaeb4ede37236b637e61249bf6245679be7fd4d633e2d814ed4748b73890ad3c4fcbcfb4960cb67ae7,0000000000000000000000000000000000000000000000000000000000000001,151000, +0000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000aba6a0b58b49f7c6c2802afd2a5ed1320bf062c7b93135f3c0ed7a1d7b1ee27b2b986cde732a60fa585ca6ab7cc154b000000000000000000000000000000000ca0d865f8c8ce0a476f7a6edb3ce4bd5e6c3a8d905d8fb5a10e66542f4325a9963c2f8d96f804f4d295f8993b5204df0000000000000000000000000000000005a966f6254f0ef4f93f082a97abe07db56f00c2ade047d2f0027edef6f00a0dfecaa24d50faa778fa29087302211f7e00000000000000000000000000000000121c51da366557c09af1bbd927521da88dfab3e2e9a95b6effb0a968795486f281f0c887e37f51837557b9e3808987130000000000000000000000000000000001a5524975400b1e88f3fff8dd34dadf5d75564cfc0026df31ee9c2c1d48b0f69a48e1e4a48cc4b7db61f023a79157800000000000000000000000000000000010fcfe8af8403a52400bf79e1bd0058f66b9cab583afe554aa1d82a3e794fffad5f0e19d385263b2dd9ef69d1154f10a000000000000000000000000000000000f46a7dee0cb471ddef3a50670a5bfc443b8455877f1ff602b21faff1eff07fc6bf27930ca2159f01479359548339560000000000000000000000000000000000ca0d865f8c8ce0a476f7a6edb3ce4bd5e6c3a8d905d8fb5a10e66542f4325a9963c2f8d96f804f4d295f8993b5204df0000000000000000000000000000000005a966f6254f0ef4f93f082a97abe07db56f00c2ade047d2f0027edef6f00a0dfecaa24d50faa778fa29087302211f7e00000000000000000000000000000000121c51da366557c09af1bbd927521da88dfab3e2e9a95b6effb0a968795486f281f0c887e37f51837557b9e3808987130000000000000000000000000000000001a5524975400b1e88f3fff8dd34dadf5d75564cfc0026df31ee9c2c1d48b0f69a48e1e4a48cc4b7db61f023a7915780,0000000000000000000000000000000000000000000000000000000000000001,151000, +0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b0900000000000000000000000000000000082543b58a13354d0cce5dc3fb1d91d1de6d5927290b2ff51e4e48f40cdf2d490730843b53a92865140153888d73d4af0000000000000000000000000000000002b51851ef3b44481d13f42e5111fa4fec04be0bf6acc7e59dec3a8c8113e5bb7b604c6dbdc5e8eddc2a1ffb81bc2baf0000000000000000000000000000000018ddb483ae75402852b7f285277ff7308ff78a3364cca8b0e0e1fa9182de275fd55c1e8ec3dbde180379c4280787ba8000000000000000000000000000000000170539890c89a4f91acd59efd413b5d1059f0c8fd8718e8f722e865dd106a4eb02e6fb0cd71b34ebc4b94375b52e4dd60000000000000000000000000000000001c2e9392f5d4b75efc5ff10fe97f37e2671cad7e4710765866e92aec99b0130e6ff1314502d069fb7b5f86bfce4300e0000000000000000000000000000000013c5ebfb853f0c8741f12057b6b845c4cdbf72aecbeafc8f5b5978f186eead8685f2f3f125e536c465ade1a00f212b090000000000000000000000000000000011dbce34af6cb14d3e4d49f2482e1b058609f25dca79e2ca48e289ace9d1c8db177b7bc35daad79aa5fdac77728bd5fc0000000000000000000000000000000002b51851ef3b44481d13f42e5111fa4fec04be0bf6acc7e59dec3a8c8113e5bb7b604c6dbdc5e8eddc2a1ffb81bc2baf0000000000000000000000000000000018ddb483ae75402852b7f285277ff7308ff78a3364cca8b0e0e1fa9182de275fd55c1e8ec3dbde180379c4280787ba8000000000000000000000000000000000170539890c89a4f91acd59efd413b5d1059f0c8fd8718e8f722e865dd106a4eb02e6fb0cd71b34ebc4b94375b52e4dd60000000000000000000000000000000001c2e9392f5d4b75efc5ff10fe97f37e2671cad7e4710765866e92aec99b0130e6ff1314502d069fb7b5f86bfce4300e,0000000000000000000000000000000000000000000000000000000000000001,151000, +00000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000097ec91c728ae2d290489909bbee1a30048a7fa90bcfd96fe1d9297545867cbfee0939f20f1791329460a4fe1ac719290000000000000000000000000000000011bbc566a10eadf16009c1d2655cfae6adfb0f56f5e55b31dc000414be1b4cee9a0b9f7d9eab4c6829037c327914d5640000000000000000000000000000000009b28329096d8644dfcba6e92477eafff29f7477da4581ce76d1493f03034d7f5d3acaadbe42c76a83ca51db79d456d10000000000000000000000000000000019f75a303fdede5d97f3e521b03ef6b9d7c008d770b59ce3ac38900b340895e008342701ad1b41830b9c010936f4ff1700000000000000000000000000000000161aa1853edbb56fa3bd685c9c6b88e466dfa3c4f194f6774b4d9b1f30b016993bd0d65e8e9d6dea6caa196ff735bd6700000000000000000000000000000000053a12f6a1cb64272c34e042b7922fabe879275b837ba3b116adfe1eb2a6dc1c1fa6df40c779a7cdb8ed8689b8bc5ba800000000000000000000000000000000108248cdc6f503c7bad30eac875d92a75feccbdbe7b5394f8557a92bb12a796430a2c60ca23c6ecd259e5b01e53891820000000000000000000000000000000011bbc566a10eadf16009c1d2655cfae6adfb0f56f5e55b31dc000414be1b4cee9a0b9f7d9eab4c6829037c327914d5640000000000000000000000000000000009b28329096d8644dfcba6e92477eafff29f7477da4581ce76d1493f03034d7f5d3acaadbe42c76a83ca51db79d456d10000000000000000000000000000000019f75a303fdede5d97f3e521b03ef6b9d7c008d770b59ce3ac38900b340895e008342701ad1b41830b9c010936f4ff1700000000000000000000000000000000161aa1853edbb56fa3bd685c9c6b88e466dfa3c4f194f6774b4d9b1f30b016993bd0d65e8e9d6dea6caa196ff735bd67,0000000000000000000000000000000000000000000000000000000000000001,151000, +000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed302500000000000000000000000000000000130f139ca118869de846d1d938521647b7d27a95b127bbc53578c7b66d88d541adb525e7028a147bf332607bd760deac000000000000000000000000000000000ae7289aa9bf20c4a9c807f2b3ac32f0db24e9a0a360c92e5ce4f8253f0e3e7853f771597c8141d705062bef12d4fea80000000000000000000000000000000001d2f610d79110f93145faad2e34f3408316b1dc3a72852e811b324577d9037035e24af25002ddd100cd9283b70ddcad0000000000000000000000000000000012947315d5c0ec670619125eed0de3dd259a008baee4379b82accf2391e70a2bdad264cda04c3bc1b5394a62559fa0ef000000000000000000000000000000001239e687c4d3417c3c9b655035f8d8a649c255f9a8e6f03b785eed0d416a1cd6ef7c8b45563acb4616af24f64dbccac4000000000000000000000000000000001354dd8a230fde7c983dcf06fa9ac075b3ab8f56cdd9f15bf870afce2ae6e7c65ba91a1df6255b6f640bb51d7fed30250000000000000000000000000000000006f1fe4d98675ffc62d4d5dd0af9968faca4d0ef425d56fa31b80aea892820e270f6da17aec9eb83c6cc9f84289ecbff000000000000000000000000000000000ae7289aa9bf20c4a9c807f2b3ac32f0db24e9a0a360c92e5ce4f8253f0e3e7853f771597c8141d705062bef12d4fea80000000000000000000000000000000001d2f610d79110f93145faad2e34f3408316b1dc3a72852e811b324577d9037035e24af25002ddd100cd9283b70ddcad0000000000000000000000000000000012947315d5c0ec670619125eed0de3dd259a008baee4379b82accf2391e70a2bdad264cda04c3bc1b5394a62559fa0ef000000000000000000000000000000001239e687c4d3417c3c9b655035f8d8a649c255f9a8e6f03b785eed0d416a1cd6ef7c8b45563acb4616af24f64dbccac4,0000000000000000000000000000000000000000000000000000000000000001,151000, +0000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e70580000000000000000000000000000000010e4280374c532ed0df44ac0bac82572f839afcfb8b696eea617d5bd1261288dfa90a7190200687d470992fb4827ff32000000000000000000000000000000001179ee329771b5913d07818e70f6ce5a58d74ea0b573eaa1bd3d97e45d3eeb27fcc7d37dba127af7a38354cb6ff48f7c000000000000000000000000000000000c898abe6eb76ef99f5143cfb8d840a918bcc9096ce25caa45d0bf5d20814cb01b024f1fd2cbecb6bef65d9456070dd90000000000000000000000000000000008e2a4fd746e86f90484f9b9b7b47b6afe5833762e515ccb276c554f00df88dd9aa0fb792c5f419dda0465cfed838e7c0000000000000000000000000000000012b5e6f7070c0045ade96f548ed6428c5030fa20c6f6f37a42fde9dbb5cd01def0fd8585bf8aeef913e7d42b9ef22efa0000000000000000000000000000000003f76a6dc6da31a399b93f4431bfabb3e48d86745eaa4b24d6337305006e3c7fc7bfcc85c85e2f3514cd389fec4e705800000000000000000000000000000000091ce9e6c4bab3ad3d275cf5888387646c3d9bb53ace7bd0c118fce3e44fcd96241b58e5af53978272f56d04b7d7ab79000000000000000000000000000000001179ee329771b5913d07818e70f6ce5a58d74ea0b573eaa1bd3d97e45d3eeb27fcc7d37dba127af7a38354cb6ff48f7c000000000000000000000000000000000c898abe6eb76ef99f5143cfb8d840a918bcc9096ce25caa45d0bf5d20814cb01b024f1fd2cbecb6bef65d9456070dd90000000000000000000000000000000008e2a4fd746e86f90484f9b9b7b47b6afe5833762e515ccb276c554f00df88dd9aa0fb792c5f419dda0465cfed838e7c0000000000000000000000000000000012b5e6f7070c0045ade96f548ed6428c5030fa20c6f6f37a42fde9dbb5cd01def0fd8585bf8aeef913e7d42b9ef22efa,0000000000000000000000000000000000000000000000000000000000000001,151000, +0000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000ebdef273e2288c784c061bef6a45cd49b0306ac1e9faab263c6ff73dea4627189c8f10a823253d86a8752769cc4f8f2000000000000000000000000000000000fe2e61bc8e9085d2b472a6791d4851762d6401fd3e7d3f3ba61620dc70b773f2102df1c9d6f1462144662fb2f15359700000000000000000000000000000000031f160cde626ca11f67613884a977fb5d3248d78ddbf23e50e52c3ba4090268c1f6cd8156fa41d848a482a0ca39eb04000000000000000000000000000000000eb61ba51124be7f3ee9be1488aa83cbd2333aa7e09ae67fef63c890534cb37ca7de3d16046b984e72db21e1f5c57a8a0000000000000000000000000000000006bf6f5d65aa7d19613141018ac8bf5d1e6fe494a9f30da215a2313a0241779006bce33a776aeedae5de5ea6ee5a9b9e0000000000000000000000000000000009439f061c7d5fada6e5431c77fd093222285c98449951f6a6c4c8f225b316144875bc764be5ca51c7895773a9f1a640000000000000000000000000000000000b4322c2fb5d5dd2c65b45f74ca75002c97444d8d4e5680d0369d32d180c93b294e30ef42f21ac274f77ad89633ab1b9000000000000000000000000000000000fe2e61bc8e9085d2b472a6791d4851762d6401fd3e7d3f3ba61620dc70b773f2102df1c9d6f1462144662fb2f15359700000000000000000000000000000000031f160cde626ca11f67613884a977fb5d3248d78ddbf23e50e52c3ba4090268c1f6cd8156fa41d848a482a0ca39eb04000000000000000000000000000000000eb61ba51124be7f3ee9be1488aa83cbd2333aa7e09ae67fef63c890534cb37ca7de3d16046b984e72db21e1f5c57a8a0000000000000000000000000000000006bf6f5d65aa7d19613141018ac8bf5d1e6fe494a9f30da215a2313a0241779006bce33a776aeedae5de5ea6ee5a9b9e,0000000000000000000000000000000000000000000000000000000000000001,151000, +000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b10000000000000000000000000000000011efaeec0b1a4057b1e0053263afe40158790229c5bfb08062c90a252f59eca36085ab35e4cbc70483d29880c5c2f8c200000000000000000000000000000000196044a5cdbc5300ee837dca745a44379070e9297697f5db28df4a37307cc740abed45cc778a3f4e3b8c9890ab6c3c70000000000000000000000000000000001176f5de6a3577ad67863bd3d9152ab9e8184964c6ac276e95946788f5a76394047580077c0971d874a40d510eb0443e00000000000000000000000000000000147dd55dff69213c5760e8d22b700dd7a9c7c33c434a3be95bd5281b97b464fb934a3dff7c23f3e59c5d8d26faa426bf0000000000000000000000000000000019efcf03ddb0934b0f0dba3569809d5b48b863d50d3be4973b504244414e1e1db56adff51d33265ce102b320c552781f000000000000000000000000000000001478ee0ffebf22708a6ab88855081daba5ee2f279b5a2ee5f5f8aec8f97649c8d5634fec3f8b28ad60981e6f29a091b100000000000000000000000000000000081162fe2e65a642993ba283df9bc8d60bfe495b2dc5623f0467c87bc7570980be2654c8cc8838fb362c677f3a3cb1e900000000000000000000000000000000196044a5cdbc5300ee837dca745a44379070e9297697f5db28df4a37307cc740abed45cc778a3f4e3b8c9890ab6c3c70000000000000000000000000000000001176f5de6a3577ad67863bd3d9152ab9e8184964c6ac276e95946788f5a76394047580077c0971d874a40d510eb0443e00000000000000000000000000000000147dd55dff69213c5760e8d22b700dd7a9c7c33c434a3be95bd5281b97b464fb934a3dff7c23f3e59c5d8d26faa426bf0000000000000000000000000000000019efcf03ddb0934b0f0dba3569809d5b48b863d50d3be4973b504244414e1e1db56adff51d33265ce102b320c552781f,0000000000000000000000000000000000000000000000000000000000000001,151000, +00000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e2728390000000000000000000000000000000006dc2ccb10213d3f6c3f10856888cb2bf6f1c7fcb2a17d6e63596c29281682cafd4c72696ecd6af3cce31c440144ebd10000000000000000000000000000000005d8edbabf37a47a539d84393bb2747d0a35a52b80a7c99616c910479306e204e5db1f0fa3fe69f35af3164c7e5726b50000000000000000000000000000000005015082d6975649fbc172035da04f8aeb6d0dd88fdfac3fbd68ec925dc199413ed670488dc6588f9bd34c4ff527f149000000000000000000000000000000001312d53088ca58dfc325772b8dc0e1b20cebf7b2d5b6b4c560759987b44060bf4a59a68d1a5623bbb3cc5b0bc3986b810000000000000000000000000000000012110cd462c6fabf04f67d652639d19640c46f51aadd6c4f9a6dd7806cffb6192d95c198f4c8284151feaa2e2a0dbc1f00000000000000000000000000000000150d43c64cb1dbb7b981f455e90b740918e2d63453ca17d8eeecb68e662d2581f8aa1aea5b095cd8fc2a941d6e272839000000000000000000000000000000001324e51f295ea95adedc9730dac2e1ab6d85838840e3955103d76677ce9a7359215f8d954286950bed1be3bbfebabeda0000000000000000000000000000000005d8edbabf37a47a539d84393bb2747d0a35a52b80a7c99616c910479306e204e5db1f0fa3fe69f35af3164c7e5726b50000000000000000000000000000000005015082d6975649fbc172035da04f8aeb6d0dd88fdfac3fbd68ec925dc199413ed670488dc6588f9bd34c4ff527f149000000000000000000000000000000001312d53088ca58dfc325772b8dc0e1b20cebf7b2d5b6b4c560759987b44060bf4a59a68d1a5623bbb3cc5b0bc3986b810000000000000000000000000000000012110cd462c6fabf04f67d652639d19640c46f51aadd6c4f9a6dd7806cffb6192d95c198f4c8284151feaa2e2a0dbc1f,0000000000000000000000000000000000000000000000000000000000000001,151000, +000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b0000000000000000000000000000000006d38cc6cc1a950a18e92e16287f201af4c014aba1a17929dd407d0440924ce5f08fad8fe0c50f7f733b285bf282acfc00000000000000000000000000000000117fd5016ddb779a6979d2bffe18032d9a5cdc5a6c7feeaa412381983d49ab894cb067f671163ccbe6225c3d85219db6000000000000000000000000000000000dcf01077dcce35c283bea662f4e4d16f871717eb78e630d9f95a200cc104fe67b0d69d95f6704d9812b46c92b1bc9de00000000000000000000000000000000121f212cd7251697ef6a7e3aa93eb0d7d0157cf1247d4411430c36c7277bf8acfccc4ed8590b5e8d0f760e0e4ed7e95a0000000000000000000000000000000007d22d78b486f575e01e21e1239cbedc4628ba7e01ecf4a3459bd78a9716e2969f26ea3f2449685f60397e1ab2aa7352000000000000000000000000000000000f46bb86e827aa9c0c570d93f4d7d6986668c0099e4853927571199e1ce9e756d9db951f5b0325acafb2bf6e8fec2a1b00000000000000000000000000000000132d85236d655190323279a01acc8cbc6fb736d951e3999589f0559cb61ea93e2e1c526ed08ef08046c3d7a40d7cfdaf00000000000000000000000000000000117fd5016ddb779a6979d2bffe18032d9a5cdc5a6c7feeaa412381983d49ab894cb067f671163ccbe6225c3d85219db6000000000000000000000000000000000dcf01077dcce35c283bea662f4e4d16f871717eb78e630d9f95a200cc104fe67b0d69d95f6704d9812b46c92b1bc9de00000000000000000000000000000000121f212cd7251697ef6a7e3aa93eb0d7d0157cf1247d4411430c36c7277bf8acfccc4ed8590b5e8d0f760e0e4ed7e95a0000000000000000000000000000000007d22d78b486f575e01e21e1239cbedc4628ba7e01ecf4a3459bd78a9716e2969f26ea3f2449685f60397e1ab2aa7352,0000000000000000000000000000000000000000000000000000000000000001,151000, +0000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000fb24d3d4063fd054cd5b7288498f107114ff323226aca58d3336444fc79c010db15094ceda6eb99770c168d459f0da0000000000000000000000000000000000224cbea61c5136987d8dbc8deafa78ae002255c031bb54335bcf99e56a57768aa127506fca1761e8b835e67e88bb4dd0000000000000000000000000000000018cbf072b544df760c051d394ff68ad2dd5a8c731377fa2a5f61e61481ad5b42645704a2d083c7d45ed4774e5448141e000000000000000000000000000000000740b8b7d7bce78a51809713656c94cf98de72887676050f65f74c57cbe574278dd3634c44e057ea95babcc3d230e3c40000000000000000000000000000000006696058a191c7012a4ee7c973c2005ac51af02a85cbb60e3164809a583b4431dda2b59e1c9ceeb652b3ac7021d116a60000000000000000000000000000000010cde0dbf4e18009c94ba648477624bbfb3732481d21663dd13cea914d6c54ec060557010ebe333d5e4b266e1563c631000000000000000000000000000000000a4ec4acf91be994fe45f08dbeb2bbd053275861d11a486693fd6e5bfa3736134396f6b1c3ad146642f2e972ba609d0b000000000000000000000000000000000224cbea61c5136987d8dbc8deafa78ae002255c031bb54335bcf99e56a57768aa127506fca1761e8b835e67e88bb4dd0000000000000000000000000000000018cbf072b544df760c051d394ff68ad2dd5a8c731377fa2a5f61e61481ad5b42645704a2d083c7d45ed4774e5448141e000000000000000000000000000000000740b8b7d7bce78a51809713656c94cf98de72887676050f65f74c57cbe574278dd3634c44e057ea95babcc3d230e3c40000000000000000000000000000000006696058a191c7012a4ee7c973c2005ac51af02a85cbb60e3164809a583b4431dda2b59e1c9ceeb652b3ac7021d116a6,0000000000000000000000000000000000000000000000000000000000000001,151000, +0000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe976000000000000000000000000000000000824e1631f054b666893784b1e7edb44b9a53596f718a6e5ba606dc1020cb6e269e9edf828de1768df0dd8ab8440e053000000000000000000000000000000001522e0a4ccd607f117fc6fc8f9abcd704e9850d96adb95d9bfaab210b76bfb2c5dc75163b922bd7a886541250bc1d8630000000000000000000000000000000018a6e4327d633108a292a51abed43e95230e951e4476dc385ceea9c72ed528bf3e06c42d10cefbd4aa75b134936e4747000000000000000000000000000000001198587188e793ad2ec2fa0fa1d0da9b61ed48444fe6722e523aeac270f17f73f56b1e726ab811bb54a6e42e506d70a20000000000000000000000000000000004bedd94182e0f16c71223ac3d68ab327d28ee0ccdcd2c2db07faf69e1babe3fbf3ba09c28b146eca7ab047b592947030000000000000000000000000000000008c0a4c543b7506e9718658902982b4ab7926cd90d4986eceb17b149d8f5122334903300ad419b90c2cb56dc6d2fe9760000000000000000000000000000000011dc30871a7a9b33e2882f6b24ccd192aad215edfc6c6bd9acd064dff4a43f41b4c212068875e896daf127547bbeca58000000000000000000000000000000001522e0a4ccd607f117fc6fc8f9abcd704e9850d96adb95d9bfaab210b76bfb2c5dc75163b922bd7a886541250bc1d8630000000000000000000000000000000018a6e4327d633108a292a51abed43e95230e951e4476dc385ceea9c72ed528bf3e06c42d10cefbd4aa75b134936e4747000000000000000000000000000000001198587188e793ad2ec2fa0fa1d0da9b61ed48444fe6722e523aeac270f17f73f56b1e726ab811bb54a6e42e506d70a20000000000000000000000000000000004bedd94182e0f16c71223ac3d68ab327d28ee0ccdcd2c2db07faf69e1babe3fbf3ba09c28b146eca7ab047b59294703,0000000000000000000000000000000000000000000000000000000000000001,151000, +00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000c2ffe6be05eccd9170b6c181966bb8c1c3ed10e763613112238cabb41370e2a5bb5fef967f4f8f2af944dbef09d265e00000000000000000000000000000000148b7dfc21521d79ff817c7a0305f1048851e283be13c07d5c04d28b571d48172838399ba539529e8d037ffd1f7295580000000000000000000000000000000003015abea326c15098f5205a8b2d3cd74d72dac59d60671ca6ef8c9c714ea61ffdacd46d1024b5b4f7e6b3b569fabaf20000000000000000000000000000000011f0c512fe7dc2dd8abdc1d22c2ecd2e7d1b84f8950ab90fc93bf54badf7bb9a9bad8c355d52a5efb110dca891e4cc3d0000000000000000000000000000000019774010814d1d94caf3ecda3ef4f5c5986e966eaf187c32a8a5a4a59452af0849690cf71338193f2d8435819160bcfb00000000000000000000000000000000159d94fb0cf6f4e3e26bdeb536d1ee9c511a29d32944da43420e86c3b5818e0f482a7a8af72880d4825a50fee6bc8cd8000000000000000000000000000000000dd1137e592119c134103b9e29e4f14b48387a767d4effae44f807e5b579e7f9c2f60105495f070d0a6ab2410f62844d00000000000000000000000000000000148b7dfc21521d79ff817c7a0305f1048851e283be13c07d5c04d28b571d48172838399ba539529e8d037ffd1f7295580000000000000000000000000000000003015abea326c15098f5205a8b2d3cd74d72dac59d60671ca6ef8c9c714ea61ffdacd46d1024b5b4f7e6b3b569fabaf20000000000000000000000000000000011f0c512fe7dc2dd8abdc1d22c2ecd2e7d1b84f8950ab90fc93bf54badf7bb9a9bad8c355d52a5efb110dca891e4cc3d0000000000000000000000000000000019774010814d1d94caf3ecda3ef4f5c5986e966eaf187c32a8a5a4a59452af0849690cf71338193f2d8435819160bcfb,0000000000000000000000000000000000000000000000000000000000000001,151000, +0000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad6242000000000000000000000000000000000353d6521a17474856ad69582ce225f27d60f5a8319bea8cefded2c3f6b862d76fe633c77ed8ccdf99d2b10430253fc8000000000000000000000000000000000805892f21889cab3cfe62226eaff6a8d3586d4396692b379efc7e90b0eaad4c9afbdf0f56b30f0c07ae0bc4013343b30000000000000000000000000000000007853f0e75c8dee034c2444299da58c98f22de367a90550dbc635fb52c9a8f61ccc100f70f10208944e48d09507fdce100000000000000000000000000000000064afd6b3ef7ff7ec34f1fa330877b42958a46a7698c6d21adf73bfdfcab7793b312e21e5988652e655f2d42edb8a673000000000000000000000000000000000ea8a2217c3dbcc0f6e562de9cb2f334c896577d0b3a7108d96b1aba2d705dbf531e870d4023cec2c0533455013242330000000000000000000000000000000019c822a4d44ac22f6fbaef356c37ceff93c1d6933e8c8f3b55784cfe62e5705930be48607c3f7a4a2ca146945cad62420000000000000000000000000000000016ad3b981f689f51f46e3e5e166986e4e71655dcc1e928327751ffdcfff8934caec5cc37327b3320202c4efbcfda6ae3000000000000000000000000000000000805892f21889cab3cfe62226eaff6a8d3586d4396692b379efc7e90b0eaad4c9afbdf0f56b30f0c07ae0bc4013343b30000000000000000000000000000000007853f0e75c8dee034c2444299da58c98f22de367a90550dbc635fb52c9a8f61ccc100f70f10208944e48d09507fdce100000000000000000000000000000000064afd6b3ef7ff7ec34f1fa330877b42958a46a7698c6d21adf73bfdfcab7793b312e21e5988652e655f2d42edb8a673000000000000000000000000000000000ea8a2217c3dbcc0f6e562de9cb2f334c896577d0b3a7108d96b1aba2d705dbf531e870d4023cec2c053345501324233,0000000000000000000000000000000000000000000000000000000000000001,151000, +00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff170000000000000000000000000000000011798ea9c137acf6ef9483b489c0273d4f69296959922a352b079857953263372b8d339115f0576cfabedc185abf2086000000000000000000000000000000001498b1412f52b07a0e4f91cbf5e1852ea38fc111613523f1e61b97ebf1fd7fd2cdf36d7f73f1e33719c0b63d7bf66b8f0000000000000000000000000000000004c56d3ee9931f7582d7eebeb598d1be208e3b333ab976dc7bb271969fa1d6caf8f467eb7cbee4af5d30e5c66d00a4e2000000000000000000000000000000000de29857dae126c0acbe966da6f50342837ef5dd9994ad929d75814f6f33f77e5b33690945bf6e980031ddd90ebc76ce00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000cbcd06a1c576af16d0d77ff8bcc3669a486d044cc7b85db03661a92f4c5c44a28d028521dfcfc292d8ecd05aed6ab940000000000000000000000000000000011798ea9c137acf6ef9483b489c0273d4f69296959922a352b079857953263372b8d339115f0576cfabedc185abf2086000000000000000000000000000000001498b1412f52b07a0e4f91cbf5e1852ea38fc111613523f1e61b97ebf1fd7fd2cdf36d7f73f1e33719c0b63d7bf66b8f0000000000000000000000000000000004c56d3ee9931f7582d7eebeb598d1be208e3b333ab976dc7bb271969fa1d6caf8f467eb7cbee4af5d30e5c66d00a4e2000000000000000000000000000000000de29857dae126c0acbe966da6f50342837ef5dd9994ad929d75814f6f33f77e5b33690945bf6e980031ddd90ebc76ce00000000000000000000000000000000189bf269a72de2872706983835afcbd09f6f4dfcabe0241b4e9fe1965a250d230d6f793ab17ce7cac456af7be4376be6000000000000000000000000000000000d4441801d287ba8de0e2fb6b77f766dbff07b4027098ce463cab80e01eb31d9f5dbd7ac935703d68c7032fa5128ff170000000000000000000000000000000011798ea9c137acf6ef9483b489c0273d4f69296959922a352b079857953263372b8d339115f0576cfabedc185abf2086000000000000000000000000000000001498b1412f52b07a0e4f91cbf5e1852ea38fc111613523f1e61b97ebf1fd7fd2cdf36d7f73f1e33719c0b63d7bf66b8f00000000000000000000000000000000153ba4ab4fecc724c843b8f78db2db1943e91051b8cb9be2eb7e610a570f1f5925b7981334951b505cce1a3992ff05c9000000000000000000000000000000000c1e79925e9ebfd99e5d11489c56a994e0f855a759f0652cc9bb5151877cfea5c37896f56b949167b9cd2226f14333dd,0000000000000000000000000000000000000000000000000000000000000000,194000, +0000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d3f000000000000000000000000000000001755d8a095e087ca66f8a118e0d2c7d5e4d8427dda8fe3049080f4aff12a8746f8c2679c310f4be0d94c5bef0414a7a600000000000000000000000000000000069c84c6419ed5c0441975ee8410065a56c65f07a4b545ff596b657dc4620c7405fd4d092b281e272773d2281a6359a8000000000000000000000000000000000e751ccbd475fe7eda1c62df626c1d37e8ae6853cc9b2109beef3e8c6f26d41a5e4e0a91bbc3371c7ab6ba780b5db41600000000000000000000000000000000184097644c9b44d543ebc0934825610590cc9f8b17ed08e9c06592bf85591d2702b18cf48a70b378926057e541eb8ac50000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c81292925400000000000000000000000000000000065d3d4be1589a6f00c85c208c44916a35349a096b09219704b4c31861ef58e6b4ea5be57a175b429e482b1038718d6c000000000000000000000000000000001755d8a095e087ca66f8a118e0d2c7d5e4d8427dda8fe3049080f4aff12a8746f8c2679c310f4be0d94c5bef0414a7a600000000000000000000000000000000069c84c6419ed5c0441975ee8410065a56c65f07a4b545ff596b657dc4620c7405fd4d092b281e272773d2281a6359a8000000000000000000000000000000000e751ccbd475fe7eda1c62df626c1d37e8ae6853cc9b2109beef3e8c6f26d41a5e4e0a91bbc3371c7ab6ba780b5db41600000000000000000000000000000000184097644c9b44d543ebc0934825610590cc9f8b17ed08e9c06592bf85591d2702b18cf48a70b378926057e541eb8ac50000000000000000000000000000000003299542a0c40efbb55d169a92ad11b4d6d7a6ed949cb0d6477803fbedcf74e4bd74de854c4c8b7f200c85c8129292540000000000000000000000000000000013a3d49e58274c2b4a534b95b7071b6d2f42b17b887bf128627c0f8894c19d3d69c1a419373ca4bd1bb6d4efc78e1d3f000000000000000000000000000000001755d8a095e087ca66f8a118e0d2c7d5e4d8427dda8fe3049080f4aff12a8746f8c2679c310f4be0d94c5bef0414a7a600000000000000000000000000000000069c84c6419ed5c0441975ee8410065a56c65f07a4b545ff596b657dc4620c7405fd4d092b281e272773d2281a6359a8000000000000000000000000000000000b8bf51e6509e81b70ff44d6e0df8f9f7bc8e33126e9f1b5a8419414878a2209c05df56cf590c8e33f484587f4a1f6950000000000000000000000000000000001c07a85ece4a1c5072fe722fb264bd1d3aaabf9db9809d5a6cb3fe17157d8fd1bfa730a26e34c87279ea81abe141fe6,0000000000000000000000000000000000000000000000000000000000000000,194000, +00000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9af000000000000000000000000000000000213e5d2d46523203ae07f36fdeb6c304fb86f552fb9adb566711c31262629efb0b1561585f85d2ac7be174682229bd8000000000000000000000000000000000b3336b5a4f7c0d16db9615e77bcdd55b7cb5b5c1591d835f34f5c1f1468e3cef954608667fb97a32e4595f43b845612000000000000000000000000000000001869606dde1688e5ae9f1c466c5897fce7794f3735234b5af1ad3617f0688529499bbdc9f0b911840a3d99fd9c49150d00000000000000000000000000000000001bfd33df4a6059608ada794e03d7456e78317145eb4d5677c00d482ac4cf470053d33583cf602feb67b6f972c9973900000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda00000000000000000000000000000000055caff20f9f3f2ea27c64caeb6bb1880f326c64eb6ed6ee7d15da7bfeb16518f76a75f061cd347f7322e0cec634f0fc000000000000000000000000000000000213e5d2d46523203ae07f36fdeb6c304fb86f552fb9adb566711c31262629efb0b1561585f85d2ac7be174682229bd8000000000000000000000000000000000b3336b5a4f7c0d16db9615e77bcdd55b7cb5b5c1591d835f34f5c1f1468e3cef954608667fb97a32e4595f43b845612000000000000000000000000000000001869606dde1688e5ae9f1c466c5897fce7794f3735234b5af1ad3617f0688529499bbdc9f0b911840a3d99fd9c49150d00000000000000000000000000000000001bfd33df4a6059608ada794e03d7456e78317145eb4d5677c00d482ac4cf470053d33583cf602feb67b6f972c9973900000000000000000000000000000000121b540a0465b39f2f093112c20a9822fc82497105778937c9d5cdcfe039d62998d47d4f41c76482c31f39a79352beda0000000000000000000000000000000014a461f829e0a76ba89f42eb57dffb4f5544df2008163bd0ea1af824f7ff910b27418a0e4f86cb8046dc1f3139cab9af000000000000000000000000000000000213e5d2d46523203ae07f36fdeb6c304fb86f552fb9adb566711c31262629efb0b1561585f85d2ac7be174682229bd8000000000000000000000000000000000b3336b5a4f7c0d16db9615e77bcdd55b7cb5b5c1591d835f34f5c1f1468e3cef954608667fb97a32e4595f43b845612000000000000000000000000000000000197b17c5b695db49c7c8b6fd6f314da7cfdfc4dbe61c76475839c89064870fad5104234c09aee7bafc1660263b6959e0000000000000000000000000000000019e514b65a358640ea90cd3cf547d591f5ff1a13ad99c568ef70c558cbec26dd1e582cc92d849fcfce9749068d361372,0000000000000000000000000000000000000000000000000000000000000000,194000, +000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c30000000000000000000000000000000004ac6e6077d4eddd0e23f30cfd64b7aa1525c85424224e70c15d7535e02aea7a312ef24ba2dcf70b926acb851da2530c0000000000000000000000000000000006ad07d3e8f45cedfb4279913bf0a29e37604810463d6020b4fa8c8c4977d69cffaa33e1149706f04eb237194dcafa520000000000000000000000000000000002c536dd2f05f4a7eaa33fd884262b22a2ab2a88e7b63cb08ebb67fc0f143da7d6b18dd394c424161f7cf703acdc82f50000000000000000000000000000000002d1d9ff74e20ea9b03c478784f57e7a58a21ca2b1e552319f33305f367f5ae4daf8138505f953db4f86c0ec1d96d5f0000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000003c0c619be1382199bee84862a0ac6bf4c891d22f722b6af5bfef0edd1ed8c7e9af5efb5d3fc546801f3e019329ae4e80000000000000000000000000000000004ac6e6077d4eddd0e23f30cfd64b7aa1525c85424224e70c15d7535e02aea7a312ef24ba2dcf70b926acb851da2530c0000000000000000000000000000000006ad07d3e8f45cedfb4279913bf0a29e37604810463d6020b4fa8c8c4977d69cffaa33e1149706f04eb237194dcafa520000000000000000000000000000000002c536dd2f05f4a7eaa33fd884262b22a2ab2a88e7b63cb08ebb67fc0f143da7d6b18dd394c424161f7cf703acdc82f50000000000000000000000000000000002d1d9ff74e20ea9b03c478784f57e7a58a21ca2b1e552319f33305f367f5ae4daf8138505f953db4f86c0ec1d96d5f0000000000000000000000000000000001383bc4d6c748d5c76ab4ba04f8fcd4c0fed9a49ea080c548893440819833ad72a8249f77391d5fbff78329eb319d3830000000000000000000000000000000016404bd07b6c6480af2d23301940e61817ee2e61fc625c100b31e1b324c369a583b61048dd57ab97b80b1fe6cd64c5c30000000000000000000000000000000004ac6e6077d4eddd0e23f30cfd64b7aa1525c85424224e70c15d7535e02aea7a312ef24ba2dcf70b926acb851da2530c0000000000000000000000000000000006ad07d3e8f45cedfb4279913bf0a29e37604810463d6020b4fa8c8c4977d69cffaa33e1149706f04eb237194dcafa5200000000000000000000000000000000173bdb0d0a79f1f2607867ddbf2581b4c1cc20fc0bced60ed8756aa4e79cb87c47fa722b1c8fdbe99a8208fc532327b600000000000000000000000000000000172f37eac49dd7f09adf602ebe562e5d0bd52ee2419fc08dc7fda241c0319b3f43b3ec79ab5aac246a783f13e268d4bb,0000000000000000000000000000000000000000000000000000000000000000,194000, +0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8db000000000000000000000000000000001913ce14bcd1d7bbb47f8efd92d7ffd155ed1990a1dbf1ee7d5e6d592a92bcbec6e865199362950afd6c8fc49b3e10a400000000000000000000000000000000020df729079e76cf06f84e3355e683e093dafad38c2ba92cf7a9faa0515f2f44d814f971046ea20116cc4b0014d7ec350000000000000000000000000000000018db123e05404eea8707f9356f417c3966312b9e41765a6fd8449879ddc4c9850c38434481b235a5bc35db1b8ee86d43000000000000000000000000000000000b4162715717e9065a3849a9294cfe39b351e57ab5a6790f3e725ad9fbf0e4b9d6a3554e872af9c37df33bb896dada5c0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a030000000000000000000000000000000014c22dcacf2e21ff447c94d81067c626b1217e58b7dc98aacab2ea3fc00b1c5e66f660d19f1c69b16571e49d13c8e1d0000000000000000000000000000000001913ce14bcd1d7bbb47f8efd92d7ffd155ed1990a1dbf1ee7d5e6d592a92bcbec6e865199362950afd6c8fc49b3e10a400000000000000000000000000000000020df729079e76cf06f84e3355e683e093dafad38c2ba92cf7a9faa0515f2f44d814f971046ea20116cc4b0014d7ec350000000000000000000000000000000018db123e05404eea8707f9356f417c3966312b9e41765a6fd8449879ddc4c9850c38434481b235a5bc35db1b8ee86d43000000000000000000000000000000000b4162715717e9065a3849a9294cfe39b351e57ab5a6790f3e725ad9fbf0e4b9d6a3554e872af9c37df33bb896dada5c0000000000000000000000000000000006bc68c6510c15a5d7bc6eebce04f7c5fce3bb02f9f89ea14ab0dfb43645b6346af7e25a8e044e842b7a3d06fe9b1a0300000000000000000000000000000000053ee41f6a51c49b069f12de32e3e6b0b355cd2c3ba87a149c7de86136a5d9c5b7b59f2d1237964e548d1b62ec36c8db000000000000000000000000000000001913ce14bcd1d7bbb47f8efd92d7ffd155ed1990a1dbf1ee7d5e6d592a92bcbec6e865199362950afd6c8fc49b3e10a400000000000000000000000000000000020df729079e76cf06f84e3355e683e093dafad38c2ba92cf7a9faa0515f2f44d814f971046ea20116cc4b0014d7ec35000000000000000000000000000000000125ffac343f97afc413ae80d40a309dfe461fe6b20eb84f8eec3a2718ec2c9f1273bcba2fa1ca59fdc924e471173d68000000000000000000000000000000000ebfaf78e267fd93f0e35e0d19feae9db125660a3dde99b028be77c6fac0116a4808aab02a29063c3c0bc4476924d04f,0000000000000000000000000000000000000000000000000000000000000000,194000, +00000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da82577218080000000000000000000000000000000008be924b49e05c45419e328340f1cbcdd3350bacf832a372417d8331c942df200493a3f7f2e46ad2cdaf3544cfd8cd8600000000000000000000000000000000028cd100457f4e930fc0f55996a6b588c5361816bb853d1f522806e5ec1c455eb200343476feeb07ca77e961fc2adc1f000000000000000000000000000000000f6adad0a3bab3610165be2fadb1b020f25488a0af3d418b7d7cf1165812e17aefcbc23308ebcd31d22ba4ca5773dd87000000000000000000000000000000001657ff792e3d89d5d35767bd0cc788411b0420665a5e0704f4d2399b9d9a5ad3c027ee030fdf495e5a6e2a4c69d0571200000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000010338047b7c67c122ffb13466935623ef2338b32bbf5452f78f7abe9a13a16824c11f5520c9dac256b9d257da88d92a30000000000000000000000000000000008be924b49e05c45419e328340f1cbcdd3350bacf832a372417d8331c942df200493a3f7f2e46ad2cdaf3544cfd8cd8600000000000000000000000000000000028cd100457f4e930fc0f55996a6b588c5361816bb853d1f522806e5ec1c455eb200343476feeb07ca77e961fc2adc1f000000000000000000000000000000000f6adad0a3bab3610165be2fadb1b020f25488a0af3d418b7d7cf1165812e17aefcbc23308ebcd31d22ba4ca5773dd87000000000000000000000000000000001657ff792e3d89d5d35767bd0cc788411b0420665a5e0704f4d2399b9d9a5ad3c027ee030fdf495e5a6e2a4c69d0571200000000000000000000000000000000024ca57c2dc2a7deec3082f2f2110b6788c57a8cdc43515044d275fe7d6f20540055bde823b7b091134fb811d23468ce0000000000000000000000000000000009cd91a281b96a881b20946fda164a987243c052378fcd8fee3926b75576dfa1d29a0aaca4b653da4e61da82577218080000000000000000000000000000000008be924b49e05c45419e328340f1cbcdd3350bacf832a372417d8331c942df200493a3f7f2e46ad2cdaf3544cfd8cd8600000000000000000000000000000000028cd100457f4e930fc0f55996a6b588c5361816bb853d1f522806e5ec1c455eb200343476feeb07ca77e961fc2adc1f000000000000000000000000000000000a96371995c5333949b5e9869599fcb67222c2e44447d133e9b3e18a9e9e14a92ee03dcba86832cde7d35b35a88bcd240000000000000000000000000000000003a912710b425cc477c43ff93684249649732b1e99270bba725e990559169b505e8411fba174b6a15f90d5b3962f5399,0000000000000000000000000000000000000000000000000000000000000000,194000, +000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e0000000000000000000000000000000000a5048d860b997a9fb352e58284ebbc026622d9be73de79b2807a0c9b431f41f379c255a2db0dd67413c18217cb21b7200000000000000000000000000000000045a701a3f46ca801c02a5419c836b2ab3d74ebd6f4fd1e7dddb1965b49c9a278f6e89950e7c35ebc6724569d34e364c0000000000000000000000000000000004cb55008ccb5b2b8ece69fac7283f5a9ef9e622e2a0e42bed5bdd77faa550882643afc1759b1a327c4f2277e13a3d4f000000000000000000000000000000001690dee40c6c824dc2588fc47dbf93f68ac250b9357e1112db72ded905ed7b101b5f877bdc42d56afb5b6202403a91c4000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000008418a39124b40643dddcd6afe1dbdf930303bca65226c2fee1b95de6e080e25451f8b4f2b2b7c4633e1de6a5a7d47cb000000000000000000000000000000000a5048d860b997a9fb352e58284ebbc026622d9be73de79b2807a0c9b431f41f379c255a2db0dd67413c18217cb21b7200000000000000000000000000000000045a701a3f46ca801c02a5419c836b2ab3d74ebd6f4fd1e7dddb1965b49c9a278f6e89950e7c35ebc6724569d34e364c0000000000000000000000000000000004cb55008ccb5b2b8ece69fac7283f5a9ef9e622e2a0e42bed5bdd77faa550882643afc1759b1a327c4f2277e13a3d4f000000000000000000000000000000001690dee40c6c824dc2588fc47dbf93f68ac250b9357e1112db72ded905ed7b101b5f877bdc42d56afb5b6202403a91c4000000000000000000000000000000001305e1b9706c7fc132aea63f0926146557d4dd081b7a2913dae02bab75b0409a515d0f25ffa3eda81cf4764de15741f60000000000000000000000000000000011bf87b12734a6360d3dda4b452deede34470fba8e62a68f79153cc288a8e7fed98c74af862883b9861d2195a58262e0000000000000000000000000000000000a5048d860b997a9fb352e58284ebbc026622d9be73de79b2807a0c9b431f41f379c255a2db0dd67413c18217cb21b7200000000000000000000000000000000045a701a3f46ca801c02a5419c836b2ab3d74ebd6f4fd1e7dddb1965b49c9a278f6e89950e7c35ebc6724569d34e364c000000000000000000000000000000001535bce9acb48b6ebc4d3dbb7c236d7cc57d656210e42e9379d4f528fc0ba59bf868503d3bb8e5cd3dafdd881ec56d5c00000000000000000000000000000000037033062d13644c88c317f1c58c18e0d9b4facbbe0701ac8bbdf3c7f0c37b14034c7882d5112a94bea39dfdbfc518e7,0000000000000000000000000000000000000000000000000000000000000000,194000, +0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dcb00000000000000000000000000000000153cec9690a6420a10e5a5a8ca46fd9d9f90e2a139886a07b375eeecce9083a5f5418e6baf64ef0f34176e432bc5343a000000000000000000000000000000000d87c1f37f83ae78a51af9c420e2584a64337d2d2dd8dc3b64f252c521901924e5eec1d9899594db5e64c93c7a01ef020000000000000000000000000000000017078538092ace26cc88b94360871fc9a6bb9992172158ef3a16467919955083accf8d55d48c7ec462a743dbbca7b448000000000000000000000000000000000289b703157a02fc1d687a5aa595495be8bbb3eb0d70554728255a44b7820e0ee82d984d5493c800f1d9d8ca0c9381dc0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000ada13f88a645bc6082c6321e0cf2b7ac45c633fe2f0cb36aeb187fe2e50e7510df2a86b98979e8551636e94488c8ce000000000000000000000000000000000153cec9690a6420a10e5a5a8ca46fd9d9f90e2a139886a07b375eeecce9083a5f5418e6baf64ef0f34176e432bc5343a000000000000000000000000000000000d87c1f37f83ae78a51af9c420e2584a64337d2d2dd8dc3b64f252c521901924e5eec1d9899594db5e64c93c7a01ef020000000000000000000000000000000017078538092ace26cc88b94360871fc9a6bb9992172158ef3a16467919955083accf8d55d48c7ec462a743dbbca7b448000000000000000000000000000000000289b703157a02fc1d687a5aa595495be8bbb3eb0d70554728255a44b7820e0ee82d984d5493c800f1d9d8ca0c9381dc0000000000000000000000000000000012662b26f03fc8179f090f29894e86155cff4ec2def43393e054f417bbf375edd79f5032a5333ab4eba4418306ed0153000000000000000000000000000000000f26fdf1af1b8ad442ef4494627c815ca01ae84510944788b87f4aa2c8600ed310b9579318bc617a689b916bb7731dcb00000000000000000000000000000000153cec9690a6420a10e5a5a8ca46fd9d9f90e2a139886a07b375eeecce9083a5f5418e6baf64ef0f34176e432bc5343a000000000000000000000000000000000d87c1f37f83ae78a51af9c420e2584a64337d2d2dd8dc3b64f252c521901924e5eec1d9899594db5e64c93c7a01ef020000000000000000000000000000000002f98cb2305518737e92ee72e2c48d0dbdbbb1f2dc63b9d02d1a8c27dd1ba5a071dc72a8dcc7813b5757bc244357f6630000000000000000000000000000000017775ae72405e39e2db32d5b9db6637b7bbb9799e614bd783f0b785c3f2ee815367e67b15cc037fec8252735f36c28cf,0000000000000000000000000000000000000000000000000000000000000000,194000, +000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6300000000000000000000000000000000057f975064a29ba6ad20d6e6d97a15bd314d6cd419948d974a16923d52b38b9203f95937a0a0493a693099e4fa17ea540000000000000000000000000000000014396ce4abfc32945a6b2b0eb4896a6b19a041d4eae320ba18507ec3828964e56719fffaa47e57ea4a2e3bd1a149b6b600000000000000000000000000000000048b3e4ba3e2d1e0dbf5955101cf038dc22e87b0855a57b631ef119d1bd19d56c38a1d72376284c8598e866b6dba37530000000000000000000000000000000007c0b98cda33be53cf4ef29d0500ff5e7a3c2df6f83dfc1c36211d7f9c696b77dfa6571169cf7935d2fb5a6463cceac6000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a3700000000000000000000000000000000162e49ebd1b50c7837336509e48ace0e7856f00ec45a76b96d1dd88eea300a8118357cafabf32ee2d06b601def523e4800000000000000000000000000000000057f975064a29ba6ad20d6e6d97a15bd314d6cd419948d974a16923d52b38b9203f95937a0a0493a693099e4fa17ea540000000000000000000000000000000014396ce4abfc32945a6b2b0eb4896a6b19a041d4eae320ba18507ec3828964e56719fffaa47e57ea4a2e3bd1a149b6b600000000000000000000000000000000048b3e4ba3e2d1e0dbf5955101cf038dc22e87b0855a57b631ef119d1bd19d56c38a1d72376284c8598e866b6dba37530000000000000000000000000000000007c0b98cda33be53cf4ef29d0500ff5e7a3c2df6f83dfc1c36211d7f9c696b77dfa6571169cf7935d2fb5a6463cceac6000000000000000000000000000000001837f0f18bed66841b4ff0b0411da3d5929e59b957a0872bce1c898a4ef0e13350bf4c7c8bcff4e61f24feca1acd5a370000000000000000000000000000000003d2c7fe67cada2213e842ac5ec0dec8ec205b762f2a9c05fa12fa120c80eba30676834f0560d11ce9939fe210ad6c6300000000000000000000000000000000057f975064a29ba6ad20d6e6d97a15bd314d6cd419948d974a16923d52b38b9203f95937a0a0493a693099e4fa17ea540000000000000000000000000000000014396ce4abfc32945a6b2b0eb4896a6b19a041d4eae320ba18507ec3828964e56719fffaa47e57ea4a2e3bd1a149b6b6000000000000000000000000000000001575d39e959d14b96f261265417ca949a248c3d46e2abb093541c103dadf58cd5b21e28c79f17b376070799492457358000000000000000000000000000000001240585d5f4c28467bccb5193e4aad78ea3b1d8dfb4716a3310fb5215a478aac3f05a8ed478486c9e703a59b9c32bfe5,0000000000000000000000000000000000000000000000000000000000000000,194000, +00000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f18032000000000000000000000000000000000b6069a2c375471d34029d2a776e56b86b0210c35d3eb530bf116205b70995e4929fc90349a7db057168dbe6c39857970000000000000000000000000000000014251a0a154731f73513b99d830f70b6fc4bcf05d11f52d2cbe9795ee8ffc5a5f717ad25770b8ecad6d0e9f8066e0cba000000000000000000000000000000001172684b21c4dfe02a55e13b57bbf105c954daec849d4c6df5276b02872c004fdf09d24f4eef366bc82eb72fe91bf70d000000000000000000000000000000001151aeb9441c5a8fabe80867b5c791420645241eae1400bbcc064d75bedd39de2ef585138fe9f65725efa1b1e5888d0300000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000efe47bf2b11dd10608a309c8aaefdbcbc2bb5e6adceef375371cface8f79668e2b7c2ce9990063a3b53e419e80e2a79000000000000000000000000000000000b6069a2c375471d34029d2a776e56b86b0210c35d3eb530bf116205b70995e4929fc90349a7db057168dbe6c39857970000000000000000000000000000000014251a0a154731f73513b99d830f70b6fc4bcf05d11f52d2cbe9795ee8ffc5a5f717ad25770b8ecad6d0e9f8066e0cba000000000000000000000000000000001172684b21c4dfe02a55e13b57bbf105c954daec849d4c6df5276b02872c004fdf09d24f4eef366bc82eb72fe91bf70d000000000000000000000000000000001151aeb9441c5a8fabe80867b5c791420645241eae1400bbcc064d75bedd39de2ef585138fe9f65725efa1b1e5888d0300000000000000000000000000000000181dc6fd3668d036a37d60b214d68f1a6ffe1949ec6b22f923e69fb373b9c70e8bcc5cdace068024c631c27f28d994e5000000000000000000000000000000000b02ca2b0e6e0989ea917719b89caf1aa84b959e45b6238813bf02f40db95fbb3bf43d3017c3f9c57eab1be617f18032000000000000000000000000000000000b6069a2c375471d34029d2a776e56b86b0210c35d3eb530bf116205b70995e4929fc90349a7db057168dbe6c39857970000000000000000000000000000000014251a0a154731f73513b99d830f70b6fc4bcf05d11f52d2cbe9795ee8ffc5a5f717ad25770b8ecad6d0e9f8066e0cba00000000000000000000000000000000088ea99f17bb06ba20c5c67aeb8fbbd19b2270986ee7c6517209679e6f84f5d43fa22daf6264c993f1d048d016e3b39e0000000000000000000000000000000008af6330f5638c0a9f339f4e8d841b955e322766457112039b2a852b37d3bc45efb67aeb216a09a8940f5e4e1a771da8,0000000000000000000000000000000000000000000000000000000000000000,194000, +000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f000000000000000000000000000000000161b70d0f384e589d8117938602f3d696f941c24e3c1ca5a9be090b670456c9df315d6fde52daed55c9d8335928a7a3c00000000000000000000000000000000186bb9e6f5ba70dd2c66a641d3b711844977939904c59946d4e9f49ac2d8c00890a43ccb20d4a62bfff63ce4a0a44e8e000000000000000000000000000000001995b9d697bded656236430e78726f0f6ef963db9a5a24d455c12db38aeab0f8629e5dc2d04920156f2a057d69613096000000000000000000000000000000001119b13caf82c18fadcb65c9c166914bfd822534bb9def3feae6c9e572c97c84e97fab3b345cf59358436a404075493d000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c1872290000000000000000000000000000000001099fe889d8f5ddcad09328997c7c3098ef4b4d74ab1d9f6fcbc33a03cafb59c7b28931da67950d1389fbcedca3fb5bb00000000000000000000000000000000161b70d0f384e589d8117938602f3d696f941c24e3c1ca5a9be090b670456c9df315d6fde52daed55c9d8335928a7a3c00000000000000000000000000000000186bb9e6f5ba70dd2c66a641d3b711844977939904c59946d4e9f49ac2d8c00890a43ccb20d4a62bfff63ce4a0a44e8e000000000000000000000000000000001995b9d697bded656236430e78726f0f6ef963db9a5a24d455c12db38aeab0f8629e5dc2d04920156f2a057d69613096000000000000000000000000000000001119b13caf82c18fadcb65c9c166914bfd822534bb9def3feae6c9e572c97c84e97fab3b345cf59358436a404075493d000000000000000000000000000000001329a75975b714c861064d743092866d61c4467e0c0316b78142e6db7e74538a376a09487cb09ee89583d547c187229000000000000000000000000000000000096713619bf088bd9e12752cab83e9cdd58296ada8d338c86a749f00ba014087a3836ce10adaaf2e815f431235bff4f000000000000000000000000000000000161b70d0f384e589d8117938602f3d696f941c24e3c1ca5a9be090b670456c9df315d6fde52daed55c9d8335928a7a3c00000000000000000000000000000000186bb9e6f5ba70dd2c66a641d3b711844977939904c59946d4e9f49ac2d8c00890a43ccb20d4a62bfff63ce4a0a44e8e00000000000000000000000000000000006b5813a1c1f934e8e564a7cad93dc7f57de7a9592aedeb116fa4ed6bc6452bbc0da23be10adfea4ad4fa82969e7a150000000000000000000000000000000008e760ad89fd250a9d5041ec81e51b8b66f5265037e7237f7c4a08bb83e7799f352c54c37cf70a6c61bb95bfbf8a616e,0000000000000000000000000000000000000000000000000000000000000000,194000, +000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5e0000000000000000000000000000000017d1ffcad218efd8b09c68eba34dbbc30b0a62ae250368ee37e5f6fd40479b8580563416afdbd92c0622c341331e20a30000000000000000000000000000000009f0eb3805ed78aa3952a0a437966258ed38cb72912756253a7a2f9113f0dd9a4e187062b0423e0587d93e904d88f50d0000000000000000000000000000000001bca57e985906695e14882f2aaeef75de5009e8717eb59962e978aa11e9d0a4d9a9e203df774cb1e993b1c6ecd6048c000000000000000000000000000000000695b11cc32740c91546eb7d554ca8b1f3afc942ad977345031be8b94b78b57a87ab049ca2d3676e039efccbf24d0c47000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a9000000000000000000000000000000001750d2e78525453f113b76f18abf2334de9755c03786fbc9233cda2364d57ed493f4fe6c2b565f4d82ff8113e9b63c4d0000000000000000000000000000000017d1ffcad218efd8b09c68eba34dbbc30b0a62ae250368ee37e5f6fd40479b8580563416afdbd92c0622c341331e20a30000000000000000000000000000000009f0eb3805ed78aa3952a0a437966258ed38cb72912756253a7a2f9113f0dd9a4e187062b0423e0587d93e904d88f50d0000000000000000000000000000000001bca57e985906695e14882f2aaeef75de5009e8717eb59962e978aa11e9d0a4d9a9e203df774cb1e993b1c6ecd6048c000000000000000000000000000000000695b11cc32740c91546eb7d554ca8b1f3afc942ad977345031be8b94b78b57a87ab049ca2d3676e039efccbf24d0c47000000000000000000000000000000001195502bc48c44b37e3f8f4e6f40295c1156f58dbc00b04b3018d237b574a20512599d18af01c50192db37cb8eb2c8a90000000000000000000000000000000002b03f02b45aa15b39e030c4b88c89a285dff5c4bbfe16f643f3f87d91db774f8ab7019285fda0b236ff7eec16496e5e0000000000000000000000000000000017d1ffcad218efd8b09c68eba34dbbc30b0a62ae250368ee37e5f6fd40479b8580563416afdbd92c0622c341331e20a30000000000000000000000000000000009f0eb3805ed78aa3952a0a437966258ed38cb72912756253a7a2f9113f0dd9a4e187062b0423e0587d93e904d88f50d0000000000000000000000000000000018446c6ba126e030ed071f87189cbd618627419c82065d26044759f6e4c7257f45021dfad1dcb34dd06b4e391329a61f00000000000000000000000000000000136b60cd7658a5d135d4bc38edff042570c7824245ed9f7a6414e9e7ab3840a99700fb620e809891b66003340db29e64,0000000000000000000000000000000000000000000000000000000000000000,194000, +000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d79000000000000000000000000000000000546a0cb9d9f1ef9ec4a1e576fa0047557a56c0217baed8691c4085b88c84a0e12d44043aab8671393d02c4a764407ee00000000000000000000000000000000131884c1386980a181353548da9602db70ab495a661e76235c4b0a32b54acb0dfd8846e17bebd731e8041c4aebb8776600000000000000000000000000000000135b3db43511dbd8b3bd5a91880d6da1a2bd1383000e0d6f0a521bf88a5836a3b5f7cb9c0c02aa861a1c2d339f3c11f20000000000000000000000000000000000e1337271bd3302a1cab762161ccfbf2a18b7800e6efe58cf897d4adbfe4cb3bf14f4b59307fffc548179bda70c18bf000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb0000000000000000000000000000000001a28b7856a22db6e79ac4165e60addbb7dfe1f19d815032bc68fea905bd0d7709c2dafc65fe51493c964de678a30d32000000000000000000000000000000000546a0cb9d9f1ef9ec4a1e576fa0047557a56c0217baed8691c4085b88c84a0e12d44043aab8671393d02c4a764407ee00000000000000000000000000000000131884c1386980a181353548da9602db70ab495a661e76235c4b0a32b54acb0dfd8846e17bebd731e8041c4aebb8776600000000000000000000000000000000135b3db43511dbd8b3bd5a91880d6da1a2bd1383000e0d6f0a521bf88a5836a3b5f7cb9c0c02aa861a1c2d339f3c11f20000000000000000000000000000000000e1337271bd3302a1cab762161ccfbf2a18b7800e6efe58cf897d4adbfe4cb3bf14f4b59307fffc548179bda70c18bf000000000000000000000000000000000d7e1651f3e172dcca8774a7a0d58ab47178d3e759933289e1d3eb0da414160ff9e890a608bf8ccdf2820c4aea6e11cb00000000000000000000000000000000185e8671e2ddb8e36380e39fe4eafefbac9769935603c28caac7d3f7f0f3e8ad14e925024b55aeb67d68b219875c9d79000000000000000000000000000000000546a0cb9d9f1ef9ec4a1e576fa0047557a56c0217baed8691c4085b88c84a0e12d44043aab8671393d02c4a764407ee00000000000000000000000000000000131884c1386980a181353548da9602db70ab495a661e76235c4b0a32b54acb0dfd8846e17bebd731e8041c4aebb877660000000000000000000000000000000006a5d436046e0ac1975e4d24bb3e3f35c1ba3801f37705505cdeb6a86c58bf8068b43462a55155799fe2d2cc60c398b900000000000000000000000000000000191fde77c7c2b397a950f0542d2edd183a5e9404e516146697a755561ab2a9705f970b491e4c0003657d864258f391ec,0000000000000000000000000000000000000000000000000000000000000000,194000, +000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e82000000000000000000000000000000000113dc3354146ca79eb103b31b61fe8bc6f33dcb9c59a7c39d989bd9411c1afce4239034f84e6b00a084be061c73e69c0000000000000000000000000000000000ae33bf68f24978c7ea9fc58d8d76047ec45d01fdbc880e6a5ba02a22a49a3a8253afe0678ecfa6013f4849da3401df70000000000000000000000000000000012c5b00376a1dd31378ec44f2dc8e321e17185d903cfc5c15345a01c33f2f151b21b938d31816550594a7a1e7216c5b00000000000000000000000000000000013d79f825c44775c68e90932d0496a5cae53f04a1edb19f8abeb5948a3dd325dfec4a8b6f58c7fbca9cf3c09b909d8b2000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e270000000000000000000000000000000007c17aaf82c2aa6bf01695157bcd0c2b34734dfbd572b0abe79c8dd3eef7ce6eb9c5e7de55b36ddf87f05e55ba9ac28b00000000000000000000000000000000113dc3354146ca79eb103b31b61fe8bc6f33dcb9c59a7c39d989bd9411c1afce4239034f84e6b00a084be061c73e69c0000000000000000000000000000000000ae33bf68f24978c7ea9fc58d8d76047ec45d01fdbc880e6a5ba02a22a49a3a8253afe0678ecfa6013f4849da3401df70000000000000000000000000000000012c5b00376a1dd31378ec44f2dc8e321e17185d903cfc5c15345a01c33f2f151b21b938d31816550594a7a1e7216c5b00000000000000000000000000000000013d79f825c44775c68e90932d0496a5cae53f04a1edb19f8abeb5948a3dd325dfec4a8b6f58c7fbca9cf3c09b909d8b2000000000000000000000000000000001454d4a82163a155446467164904cefd7e1e3c67ae99bf65c581a75c72716fb011e2fd030eaf3d36977fbb0ff5156e2700000000000000000000000000000000123f973ab6bd3c2e5b0512a0c77ea0ac3003fd891e1262137f9444cd07b927b564e618205ba09220320ea1aa4564e82000000000000000000000000000000000113dc3354146ca79eb103b31b61fe8bc6f33dcb9c59a7c39d989bd9411c1afce4239034f84e6b00a084be061c73e69c0000000000000000000000000000000000ae33bf68f24978c7ea9fc58d8d76047ec45d01fdbc880e6a5ba02a22a49a3a8253afe0678ecfa6013f4849da3401df700000000000000000000000000000000073b61e6c2de0969138ce3671582c9b58305c5abefb54cfe13eb3284c2be04d26c906c717fd29aaf60b485e18de8e4fb0000000000000000000000000000000006297267dd3b6f3de2329e837302427ab6235b3ad4a9f8c6bb45795852d3c3c61fe75747bbc78043102fc3f646f5d1f9,0000000000000000000000000000000000000000000000000000000000000000,194000, +000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b811580000000000000000000000000000000006ccaf6c08f831be9c99a97714f5257a985cc2a29b5f5c81bc8d794dd0d8d1a41eb5413bed654c0140dbacfd0dda9e1800000000000000000000000000000000144e9cf91580800dfaa47c98ff7d002a576be76d9e44ae1f8335a3f733e1162af0636372e143174d872c7ea89f4c743900000000000000000000000000000000101e143b838c8a3f5f80fb1412081091b875230f1e2f9cf374d4bcd595392f6daa9552dbb6d5834e27b1b3dafe061ed300000000000000000000000000000000072463400b3e875395a1cdd31d73d51396e34347cd86d9f6f43f42253b3cdb24b89ed7434b1522af95ba1ee2d29ed1bb000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000017147eda83f35d0b6c8894317da5b2e991818479674d7dd1aef6bfaebacbb61ad4b2a17ce7e799939f8c2004af4799530000000000000000000000000000000006ccaf6c08f831be9c99a97714f5257a985cc2a29b5f5c81bc8d794dd0d8d1a41eb5413bed654c0140dbacfd0dda9e1800000000000000000000000000000000144e9cf91580800dfaa47c98ff7d002a576be76d9e44ae1f8335a3f733e1162af0636372e143174d872c7ea89f4c743900000000000000000000000000000000101e143b838c8a3f5f80fb1412081091b875230f1e2f9cf374d4bcd595392f6daa9552dbb6d5834e27b1b3dafe061ed300000000000000000000000000000000072463400b3e875395a1cdd31d73d51396e34347cd86d9f6f43f42253b3cdb24b89ed7434b1522af95ba1ee2d29ed1bb000000000000000000000000000000000178e6828261ee6855b38234ed15c27551bb1648ac6ec9a9e70744643cd1f134b2309dd0c34b1e59ddfe3f831ab814c90000000000000000000000000000000002ec930fb58c898ede931384c5a5f9edd2f5c70b8c3794edb83a12f23be5400949f95e81c96c666c1a72dffb50b811580000000000000000000000000000000006ccaf6c08f831be9c99a97714f5257a985cc2a29b5f5c81bc8d794dd0d8d1a41eb5413bed654c0140dbacfd0dda9e1800000000000000000000000000000000144e9cf91580800dfaa47c98ff7d002a576be76d9e44ae1f8335a3f733e1162af0636372e143174d872c7ea89f4c74390000000000000000000000000000000009e2fdaeb5f35c5aeb9aaca231439c45ac022875d55575cbf25c15cb6177c6b67416ad22fa7e7cb1924d4c2501f98bd80000000000000000000000000000000012dcaeaa2e415f46b579d9e325d7d7c3cd94083d25fe38c872f1907bbb741aff660d28bb663edd502444e11d2d60d8f0,0000000000000000000000000000000000000000000000000000000000000000,194000, +0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00ccd000000000000000000000000000000001354146aa546754e10ada6e0fe98f04f5f3a3f8a8350d0295e02b8e9c80735b04c3061412e08ddb13c80ac36e5638e540000000000000000000000000000000012ab26513534b4dc1b71eec46b73199c4157ba9369e66fbe4d2d8f62237fc7c6fad31854ebd878f989b8c5cf35c7cfe0000000000000000000000000000000000eb731bc99cdadf7f2280385c7e17d72d34bcbdbdc725d5bc94e841036115e8cb95df08084221696f9be479821fbdd7400000000000000000000000000000000143ba7d3f66445249d9a81a6949f24ff40e7c4d270fa044a8b80200a4369b07806c5497a0ef9e9dbb87b9e63694623ee0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f10000000000000000000000000000000016c1c9ca735535f801c58a9e35a80ce122d20abb327b44db4dea31b899982c4e136a2430c51cf3a31adc5611621f9dde000000000000000000000000000000001354146aa546754e10ada6e0fe98f04f5f3a3f8a8350d0295e02b8e9c80735b04c3061412e08ddb13c80ac36e5638e540000000000000000000000000000000012ab26513534b4dc1b71eec46b73199c4157ba9369e66fbe4d2d8f62237fc7c6fad31854ebd878f989b8c5cf35c7cfe0000000000000000000000000000000000eb731bc99cdadf7f2280385c7e17d72d34bcbdbdc725d5bc94e841036115e8cb95df08084221696f9be479821fbdd7400000000000000000000000000000000143ba7d3f66445249d9a81a6949f24ff40e7c4d270fa044a8b80200a4369b07806c5497a0ef9e9dbb87b9e63694623ee0000000000000000000000000000000001ea88d0f329135df49893406b4f9aee0abfd74b62e7eb5576d3ddb329fc4b1649b7c228ec39c6577a069c0811c952f100000000000000000000000000000000033f481fc62ab0a249561d180da39ff641a540c9c109cde41946a0e85d18c9d60b41dbcdec370c5c9f22a9ee9de00ccd000000000000000000000000000000001354146aa546754e10ada6e0fe98f04f5f3a3f8a8350d0295e02b8e9c80735b04c3061412e08ddb13c80ac36e5638e540000000000000000000000000000000012ab26513534b4dc1b71eec46b73199c4157ba9369e66fbe4d2d8f62237fc7c6fad31854ebd878f989b8c5cf35c7cfe0000000000000000000000000000000000b49e02d9fb238a258f3a4307b6a2f64912b7fa91712b5639de24e90c09f9797654e0f7e2d31e968c040b867de03cd370000000000000000000000000000000005c56a16431ba175ad81260faeac87d8238f86b2828b0e74dbb0b296b34745ac17e6b684a25a16240183619c96b986bd,0000000000000000000000000000000000000000000000000000000000000000,194000, +0000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe90000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000068edef3169c58920509ed4e7069229bd8038a45d2ce5773451cc18b396d2838c9539ecb52298a27eebd714afacb907c0000000000000000000000000000000004c5346765a62f2d2e700aadccf747acb3322c250435ce2cf358c08f1e286427cabace052327c4b30135c8482c5c0eb90000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be100000000000000000000000000000000084486ebc81878331aab7d6f53ca3c773fda7b181b56a93e5ee0bfa189afbb7fd7a05c5bea35ec1054c0e1ddc2e2dac20000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000068edef3169c58920509ed4e7069229bd8038a45d2ce5773451cc18b396d2838c9539ecb52298a27eebd714afacb907c0000000000000000000000000000000004c5346765a62f2d2e700aadccf747acb3322c250435ce2cf358c08f1e286427cabace052327c4b30135c8482c5c0eb90000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be10000000000000000000000000000000011bc8afe71676e6730702a46ef817060249cd06cd82e6981085012ff6d013aa4470ba3a2c71e13ef653e1e223d1ccfe90000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000137232f722e38e084611ba67d2e28a3b8c73c13f20b6bb4c22141115bd43cdeb555861335f2a75d7cb418eb505341a2f00000000000000000000000000000000153bdd82d3d9b76d1cab9d087654652ab1451f5fef4f449273d81211d88891fc53f131f98e2c3b4cb8c937b7d3a39bf20000000000000000000000000000000008d8c4a16fb9d8800cce987c0eadbb6b3b005c213d44ecb5adeed713bae79d606041406df26169c35df63cf972c94be100000000000000000000000000000000084486ebc81878331aab7d6f53ca3c773fda7b181b56a93e5ee0bfa189afbb7fd7a05c5bea35ec1054c0e1ddc2e2dac20000000000000000000000000000000013a3de1d25380c44ca06321151e89ca22210926c1cd4e3c1a9c3aa6c709ab5fdd00f8df19243ce058bc753ccf03424ed000000000000000000000000000000001657dbebf712cbda6f15d1d387c87b3fb9b386d5d754135049728a2a856ba2944c741024131a93c78655fdb7bfe3c80300000000000000000000000000000000137232f722e38e084611ba67d2e28a3b8c73c13f20b6bb4c22141115bd43cdeb555861335f2a75d7cb418eb505341a2f00000000000000000000000000000000153bdd82d3d9b76d1cab9d087654652ab1451f5fef4f449273d81211d88891fc53f131f98e2c3b4cb8c937b7d3a39bf2,0000000000000000000000000000000000000000000000000000000000000001,237000, +00000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193a000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000f6fdc4e5dceb555c9eb4c912fedbfb3cb1b842345f73ded02cfaf8d397c4378809721094aa4a4113a368e0787effeb500000000000000000000000000000000143ac06258c579c11c05569669a2a10babc63ecc86f85c91791d8ea48af700a2067c5f13d2700b8d5cf59bcca8fbf7c600000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000a5b95d6031e92578f6b5de5b8873e87486fd818214be93814753dcf6665229758248a6529892265fcc2b2ba45f89171000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000f6fdc4e5dceb555c9eb4c912fedbfb3cb1b842345f73ded02cfaf8d397c4378809721094aa4a4113a368e0787effeb500000000000000000000000000000000143ac06258c579c11c05569669a2a10babc63ecc86f85c91791d8ea48af700a2067c5f13d2700b8d5cf59bcca8fbf7c600000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000fa57c1436615442bbb049d08ac46e501c07736cd239298752bb94d1904bd38cc687759987cadd99bd3c4d45ba07193a000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000a91359bdbb1314481305b25135ded23995bc761ad8dd4d264612313bd34b2ab9e14def566af5bee7fc871f8780fabf60000000000000000000000000000000005c65187e0ba6cd92f16511fd9a90bcbb8b10cb86c8cb62dee1343fc6bb9f582182fa0eadee3f4725d0964335703b2e500000000000000000000000000000000120ddc1cd9e3a7b298673b1036d162c31dbb35d6e83b39b2564b3be16e446a836c96907e8a6af1e677e906bf5ed73159000000000000000000000000000000000a5b95d6031e92578f6b5de5b8873e87486fd818214be93814753dcf6665229758248a6529892265fcc2b2ba45f89171000000000000000000000000000000000dd75b4aebed3bd6bd020c3af671aaed67bf1582aceb6c8b5a476968c0c500753e4d0f3276341b79d87af38850893d92000000000000000000000000000000000e9b3be06afd6157eb6df52be4f2db2bcccd650f720661f8d6fcff3f71d69e152e17100ce60b7b90a7f798c4cdd02209000000000000000000000000000000000a91359bdbb1314481305b25135ded23995bc761ad8dd4d264612313bd34b2ab9e14def566af5bee7fc871f8780fabf60000000000000000000000000000000005c65187e0ba6cd92f16511fd9a90bcbb8b10cb86c8cb62dee1343fc6bb9f582182fa0eadee3f4725d0964335703b2e5,0000000000000000000000000000000000000000000000000000000000000001,237000, +000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece173822000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc500000000000000000000000000000000192f3e8ae2588f9223de77f5e872115f1edec96d6a0f403a47879410c2562e79853c9a706e423b83fbf3154234edb6f80000000000000000000000000000000015084258d58fd1a07bbdb2e90df5a56ae15a787037eff4fe55f660e45f04820c6fc8982303b5e82074cf0cdcbde61307000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b60000000000000000000000000000000002a534a7e1432aa317f782a581f974d23ec75420611165d0486ecd377660fa7c2e8235f829c64501d1b9bf3131e87289000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc500000000000000000000000000000000192f3e8ae2588f9223de77f5e872115f1edec96d6a0f403a47879410c2562e79853c9a706e423b83fbf3154234edb6f80000000000000000000000000000000015084258d58fd1a07bbdb2e90df5a56ae15a787037eff4fe55f660e45f04820c6fc8982303b5e82074cf0cdcbde61307000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b600000000000000000000000000000000175bdd42583cbbf733242510c152380525aff7649273acef1ec20569804ffba7f029ca06878dbafde84540cece173822000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc50000000000000000000000000000000000d1d35f57275708273d2fc05ad99b78459882178975d2851fa93e90345ac7aa996f658e4311c47bbe0beabdcb11f3b30000000000000000000000000000000004f8cf9163f014f9cf5df4cd3556076c831cd314bb951dc1113a71bc97ac7417aee367dbad9e17df452ff323421997a4000000000000000000000000000000000e3ccaa4fa358a5a885094cbb0b8baa106fbcca66edbe31511ac2f6f3d14edbd8701979d6e4690853555c625091392b60000000000000000000000000000000002a534a7e1432aa317f782a581f974d23ec75420611165d0486ecd377660fa7c2e8235f829c64501d1b9bf3131e87289000000000000000000000000000000000057bbf62cdf3c56e146f60f8ce6b6bdebe7aae7d9410c6902c7a505b589ae26ce3ab67d9b8da047185f9d37ab27595e000000000000000000000000000000000843e55c07bba3573592d3f649938654a5c51f9ced0f92bcb3e4f431141fe91a1de3695324b21e31dd2ae0a328055cc50000000000000000000000000000000000d1d35f57275708273d2fc05ad99b78459882178975d2851fa93e90345ac7aa996f658e4311c47bbe0beabdcb11f3b30000000000000000000000000000000004f8cf9163f014f9cf5df4cd3556076c831cd314bb951dc1113a71bc97ac7417aee367dbad9e17df452ff323421997a4,0000000000000000000000000000000000000000000000000000000000000001,237000, +0000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10c000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f000000000000000000000000000000000477b55bd7fff14e0d1807bfc21edb9481be01c12abb1460d78b1aafe42953730167e32e694c2ddfb0d442e8cea57d460000000000000000000000000000000004b884c6ea36f189dbc3c0e9cf88f08baf5d868579998f63b752e61fcce3cf2c901bb9b51959d3597c4ef53cff41fc260000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000d4197b85280f1a5e4cfdd6a7acce516b34a5e12cf55081a858a2ad517d12733aa294a2ca1adf81bc9bf22922fbfd99f000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f000000000000000000000000000000000477b55bd7fff14e0d1807bfc21edb9481be01c12abb1460d78b1aafe42953730167e32e694c2ddfb0d442e8cea57d460000000000000000000000000000000004b884c6ea36f189dbc3c0e9cf88f08baf5d868579998f63b752e61fcce3cf2c901bb9b51959d3597c4ef53cff41fc260000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000cbf7a31e6fef4f4664bca4bc87ec7c0b12ced7224300aa4e1a6a7cbdedfcef07482b5d20fa607e3f03fdd6dd03fd10c000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f0000000000000000000000000000000015895c8e617ff54c3e039ff6812cd142e2b949c3c8c9fe5e8fa5b7f11287a2b11d441cd04807d220092abd17315a2d650000000000000000000000000000000015488d234f48f5106f57e6cc73c2bc4bb519c4ff79eb835bafddec8129cd26f78e90464997fa2ca63db00ac300bdae850000000000000000000000000000000001bc359baeac07a93aca770174ea6444aac9f04affdaa77c8a47b30c60ee2b527c061a4344139264e541d4134f42bfd0000000000000000000000000000000000d4197b85280f1a5e4cfdd6a7acce516b34a5e12cf55081a858a2ad517d12733aa294a2ca1adf81bc9bf22922fbfd99f000000000000000000000000000000000bcec23e092111b38a2f7dc957cf455312ffd33528d084204314492440d29248cb5719346a4f7a490d17ba149e30de5200000000000000000000000000000000194605e5680cc80bd2685949efa3cce90d345b9151ba72f3adf226dd299c23464c4344a42b8834131a51a4156038585f0000000000000000000000000000000015895c8e617ff54c3e039ff6812cd142e2b949c3c8c9fe5e8fa5b7f11287a2b11d441cd04807d220092abd17315a2d650000000000000000000000000000000015488d234f48f5106f57e6cc73c2bc4bb519c4ff79eb835bafddec8129cd26f78e90464997fa2ca63db00ac300bdae85,0000000000000000000000000000000000000000000000000000000000000001,237000, +0000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119bf00000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac800000000000000000000000000000000183b7b917aaaa94f0ea9959273ed4701102346be2a9d72531bd18fef908ecb0579a6ac10ed42a91f1147fc3a05b2e81900000000000000000000000000000000070983b1582a97d9797782e4f960a298aaa8ec509720495acdbf176d8ecb9ec9e041c2b5ed6b7dfb46fdeaae3fb341500000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa00000000000000000000000000000000145688bf2f7a76a4341564a725a16dd5009b4a5174d766e6bf337a8bcbb11c797b82173d92aa796da6b168e734be90ec00000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac800000000000000000000000000000000183b7b917aaaa94f0ea9959273ed4701102346be2a9d72531bd18fef908ecb0579a6ac10ed42a91f1147fc3a05b2e81900000000000000000000000000000000070983b1582a97d9797782e4f960a298aaa8ec509720495acdbf176d8ecb9ec9e041c2b5ed6b7dfb46fdeaae3fb341500000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa0000000000000000000000000000000005aa892b0a056ff61706430f1daa3f0263dc01337eadabd8a7fd58152affd9aaa329e8c11ea98692134d9718cb4119bf00000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac80000000000000000000000000000000001c59658bed53d4b3c721223cf5e65d6545404c6c8e7a06c4b5f42b166222b1ea50553edc41156e0a8b703c5fa4cc2920000000000000000000000000000000012f78e38e1554ec0d1a424d149eb0a3eb9ce5f345c64c9649971bb3367e5575a3e6a3d48c3e8820473011551c04c695b0000000000000000000000000000000006b06ae8cb0981bf5167ad51e19d132db77548c4376697f855c8397b835743c42771096ed7b0a4b18af9494e42ee89aa00000000000000000000000000000000145688bf2f7a76a4341564a725a16dd5009b4a5174d766e6bf337a8bcbb11c797b82173d92aa796da6b168e734be90ec00000000000000000000000000000000073341309b6fbabb18f3cf0842817905e9248db98b582dc0efb2b741a80cdbb13d0df4bce920f257996b95029891a36f0000000000000000000000000000000012d19e09dc254bd1e84afce75aa215c96dd38bcac3f6d4cf08d9e2e8d20345b7c534a0b14ffcdfd4fa3600730e2eeac80000000000000000000000000000000001c59658bed53d4b3c721223cf5e65d6545404c6c8e7a06c4b5f42b166222b1ea50553edc41156e0a8b703c5fa4cc2920000000000000000000000000000000012f78e38e1554ec0d1a424d149eb0a3eb9ce5f345c64c9649971bb3367e5575a3e6a3d48c3e8820473011551c04c695b,0000000000000000000000000000000000000000000000000000000000000001,237000, +0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b2000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b0000000000000000000000000000000010f7bf9f6711ba723bb71a004a90109ee22be6643d56d410da18103ef44a1b3d50f10c4b94222c7f05fd3c28acbdc8ee00000000000000000000000000000000007af41f09e6d0adcb1935d6a93ea1f6156fa0157a63f265a3a7ceffe82f6635b8511e7e8f21e8f3be7a73513ff597b10000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a030000000000000000000000000000000003942eae34fd3104cead334a2cbb2131eaa10b59d07949479331a8f4cc66761cd5d23eb8a861ae618f3a2e01b25080f9000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b0000000000000000000000000000000010f7bf9f6711ba723bb71a004a90109ee22be6643d56d410da18103ef44a1b3d50f10c4b94222c7f05fd3c28acbdc8ee00000000000000000000000000000000007af41f09e6d0adcb1935d6a93ea1f6156fa0157a63f265a3a7ceffe82f6635b8511e7e8f21e8f3be7a73513ff597b10000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a0300000000000000000000000000000000166ce33c0482b5957c6e746c16908ba579d6402b230bc977d3ff29ac2a4a800748d9c14608f2519e2ac4d1fe4daf29b2000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b000000000000000000000000000000000909524ad26e2c280f648db5f8bb9c38824b6520b62e3eae8d18c2620266dae6cdbaf3b31d31d380b401c3d75341e1bd0000000000000000000000000000000019861dcb2f9915ec800271df9a0d0ae14f07ab6f79212059c38903a10e818fee665ae1802232170bfb848caec00a12fa0000000000000000000000000000000015dc9f87213e4781863ad43f6bbccd547967d9bcf6a35d95d530cbfbf0d7307981aee5bc4ccd41254841651717393a030000000000000000000000000000000003942eae34fd3104cead334a2cbb2131eaa10b59d07949479331a8f4cc66761cd5d23eb8a861ae618f3a2e01b25080f9000000000000000000000000000000000dca3b392f75583b5266a8def02bd66bf44f26b8a0a27aced57299756cffaf9e1af3538beb08b2a5939b745c8f016fee000000000000000000000000000000000d7feafc9ec0935d5b7be7cd5e2a3c57b667aba9fcc87fd5b8a585010be6958c4e7538a6d2a1f46c9641ff7b8598d74b000000000000000000000000000000000909524ad26e2c280f648db5f8bb9c38824b6520b62e3eae8d18c2620266dae6cdbaf3b31d31d380b401c3d75341e1bd0000000000000000000000000000000019861dcb2f9915ec800271df9a0d0ae14f07ab6f79212059c38903a10e818fee665ae1802232170bfb848caec00a12fa,0000000000000000000000000000000000000000000000000000000000000001,237000, +00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0b0000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a0000000000000000000000000000000003a6cc74cc398f38d535b4341faa37c968daf2009c3f05ace1f938b33bbe4002d81d18d30c2c856b21afe7a22b83c37a000000000000000000000000000000000452d1b2da6392f9df1bfd35e4575c565333703b2f83f56e0a88a0c8195968c5321296b07f6750584e23597304a5472e00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f60463000000000000000000000000000000000575bd953fc6600f5b48faea1032cf2b6615bf34cc9c526fdcc5042a292812d35fef2884bf51e017eb24c174b2bc20a00000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a0000000000000000000000000000000003a6cc74cc398f38d535b4341faa37c968daf2009c3f05ace1f938b33bbe4002d81d18d30c2c856b21afe7a22b83c37a000000000000000000000000000000000452d1b2da6392f9df1bfd35e4575c565333703b2f83f56e0a88a0c8195968c5321296b07f6750584e23597304a5472e00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f6046300000000000000000000000000000000148b5454f9b9868aefd2accc3318ddabfe618c5026e8c04f8a6bce76cd88e350bebcd779f2021fe7ceda3e8b4d438a0b0000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a00000000000000000000000000000000165a45756d46576175e5f38223a1750dfb9c598457460d12853799edbaf2b621468ee72ba5277a94984f185dd47be7310000000000000000000000000000000015ae40375f1c53a06bffaa805ef450811143db49c4011d515ca831d8dd578d5eec99694e31ecafa76bdba68cfb5a637d00000000000000000000000000000000171fbc9cec717964c4324aa0d7dcf56a59b947c24a9092157f4f8c78ae43b8e4222fd1e8acdbf5989d0d17ea10f60463000000000000000000000000000000000575bd953fc6600f5b48faea1032cf2b6615bf34cc9c526fdcc5042a292812d35fef2884bf51e017eb24c174b2bc20a00000000000000000000000000000000019e05ccf064f7cdad9748d328170b3e4bcfa6787dbfa93011d16f6d031648faa10dbfb7cc4d7c884d75480c4c864bb75000000000000000000000000000000001999d5f54ee66b3c0dedf9f46450e0ed463fa9c6cd9e0db317a35ec6ce78efae9bea9b64e3b2aaf7f70fbcace71b075a00000000000000000000000000000000165a45756d46576175e5f38223a1750dfb9c598457460d12853799edbaf2b621468ee72ba5277a94984f185dd47be7310000000000000000000000000000000015ae40375f1c53a06bffaa805ef450811143db49c4011d515ca831d8dd578d5eec99694e31ecafa76bdba68cfb5a637d,0000000000000000000000000000000000000000000000000000000000000001,237000, +0000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751a0000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf780000000000000000000000000000000007a9cf48dbe005c5c59b2c731cf4117e5fadc9cb2cd8f486f1ed58b2909092ee8f36d88b8f719db94715641b418ab4240000000000000000000000000000000004ba40d4766b91bf8da1cc2526f62791a1b5f6fc24ffc54b522dd30cde2d29a6a6f81e8429d518710843d43705f3b4e60000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a000000000000000000000000000000000032e4fbb8dab462ff0352c2d3925b0e97ca662189129928ccc1714364e4f01d8b026887d808342091ad442b6e11635910000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf780000000000000000000000000000000007a9cf48dbe005c5c59b2c731cf4117e5fadc9cb2cd8f486f1ed58b2909092ee8f36d88b8f719db94715641b418ab4240000000000000000000000000000000004ba40d4766b91bf8da1cc2526f62791a1b5f6fc24ffc54b522dd30cde2d29a6a6f81e8429d518710843d43705f3b4e60000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a00000000000000000000000000000000016d2c22eabd4a06a5ae67b890a25fbede7d0e96c625b80329b19be6aa861f44b6e85778130d0bdf69f2abd491ee9751a0000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf7800000000000000000000000000000000125742a15d9fe0d485807b4326579b5904c981b9c6ac1e38754379ee662063358f75277321e2624672e99be4be74f687000000000000000000000000000000001546d115c31454dabd79db911c558545c2c15488ce854d741502ff941883cc7d77b3e17a877ee78eb1bb2bc8fa0bf5c50000000000000000000000000000000018724e2b9a2f383329207ee85577805f35d5c5bb9f6903e3c962e57ab7eb9d1639d1e9adbde53499863b299f576325a000000000000000000000000000000000032e4fbb8dab462ff0352c2d3925b0e97ca662189129928ccc1714364e4f01d8b026887d808342091ad442b6e11635910000000000000000000000000000000004506802747afd8777904c46ad9bf0b06859a1b395ca3474a93ca4151ca158d2fd41b3a21e0ce0bc950b3241256e10d800000000000000000000000000000000115f41d2c173c3c2c7ecdff1a4aaa3c2e67c803db7a588d6143fe913961eef743d8b1f9d32e3ef1fc0475f41572faf7800000000000000000000000000000000125742a15d9fe0d485807b4326579b5904c981b9c6ac1e38754379ee662063358f75277321e2624672e99be4be74f687000000000000000000000000000000001546d115c31454dabd79db911c558545c2c15488ce854d741502ff941883cc7d77b3e17a877ee78eb1bb2bc8fa0bf5c5,0000000000000000000000000000000000000000000000000000000000000001,237000, +0000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df910000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c7952820000000000000000000000000000000008ceb842a17953578013ceee519a28ef1b37f73e13564def5ffe08a64dc53aa680784e26138176c89269477ee003d16700000000000000000000000000000000159791b6f2c26ed611ca40bfbd2059c15cfec9d073a84254ad9b509ef786d62d17fdc67ab13092cf0b7b3482866f4c320000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000ae6134f1fec83a52e5358db260eb9dc6b918f7a803aae5715854ebee2b9bbecea9ab0d955f2e13e2c47a96b234ecb1a0000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c7952820000000000000000000000000000000008ceb842a17953578013ceee519a28ef1b37f73e13564def5ffe08a64dc53aa680784e26138176c89269477ee003d16700000000000000000000000000000000159791b6f2c26ed611ca40bfbd2059c15cfec9d073a84254ad9b509ef786d62d17fdc67ab13092cf0b7b3482866f4c320000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000f1afe9b199362f51cc84edb1d3cf2faf8e5bc0a734a646851ab83e213f73a3734114f255b611ec18db75694dcb0df910000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c79528200000000000000000000000000000000113259a798069342cb07d8c7f1b183e8493f5446e02ec4d00732c9faa8ebbb7d9e33b1d89dd289372795b8811ffbd944000000000000000000000000000000000469803346bd77c4395166f6862b5316077881b47fdcd06ab9958201ff2a1ff706ae398400236d30ae83cb7d79905e790000000000000000000000000000000010fcf5e5e478ac6442b218ce261878d8f61b405c0b9549512e23ead1f26a2240771993f8c039fbce4008a1707aeaaf25000000000000000000000000000000000ae6134f1fec83a52e5358db260eb9dc6b918f7a803aae5715854ebee2b9bbecea9ab0d955f2e13e2c47a96b234ecb1a0000000000000000000000000000000019cc0ec24da141f27b38a53aef0b3d93c4c2b981c1b248014be277002d39d7bde66f6957a659a89adcd3477dfe4f897a000000000000000000000000000000000e4c01d7425e35be84e3cf806aa76a079cf4557732980f7e8f8ce9a879483e28f223694ed8dd45706e12272f4c79528200000000000000000000000000000000113259a798069342cb07d8c7f1b183e8493f5446e02ec4d00732c9faa8ebbb7d9e33b1d89dd289372795b8811ffbd944000000000000000000000000000000000469803346bd77c4395166f6862b5316077881b47fdcd06ab9958201ff2a1ff706ae398400236d30ae83cb7d79905e79,0000000000000000000000000000000000000000000000000000000000000001,237000, +000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c04000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf000000000000000000000000000000001272c63693873e1dabe2c2739310f627d3d9b5bcaa615402c3849ffd8dfe72b40fea4a068064655f2c8f46f074e6518d0000000000000000000000000000000000161a8e5e1de10938e5bce241ae73d76173022127822d744b23e656095c28f2f8d142ceb48b72a1dbc36b6143f8af95000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000000d9bd58946a4d26e3f97e5fe96e574d6f93562c0fb0c187c0c586208fe9a4d9383d3ca22b272ff3eb7e624ad7fb9ea7000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf000000000000000000000000000000001272c63693873e1dabe2c2739310f627d3d9b5bcaa615402c3849ffd8dfe72b40fea4a068064655f2c8f46f074e6518d0000000000000000000000000000000000161a8e5e1de10938e5bce241ae73d76173022127822d744b23e656095c28f2f8d142ceb48b72a1dbc36b6143f8af95000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000019275491a51599736722295659dd5589f4e3f558e3d45137a66b4c8066c7514ae66ec35c862cd00bce809db528040c04000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf00000000000000000000000000000000078e4bb3a5f8a87c9f38e542b03ab6af909d95c84923bebca3ac32a368b283700ec1b5f830ef9aa08d6fb90f8b19591e0000000000000000000000000000000019eaf75bdb6205911235ead4019d390003044963cc02e54b1c0cec4aed54cd3125dabd2ffcc88d5dde3b949ebc06fb16000000000000000000000000000000000f75bc9feb74110697c9f353686910c6246e587dd71d744aab99917f1aea7165b41deb333e6bd14843f28b2232f799830000000000000000000000000000000000d9bd58946a4d26e3f97e5fe96e574d6f93562c0fb0c187c0c586208fe9a4d9383d3ca22b272ff3eb7e624ad7fb9ea7000000000000000000000000000000000040d03956c821010969a67c91a6546800c5aa7ac392b16a9895136c941f4ca9f378c55446161562feace3b5b65f3c4f000000000000000000000000000000000e4b299f9fb25caec655d21c390bdad3c1256ca29faa33466a13aaa6d86310106d95fc8d8a0409fbd228fd3be7965cdf00000000000000000000000000000000078e4bb3a5f8a87c9f38e542b03ab6af909d95c84923bebca3ac32a368b283700ec1b5f830ef9aa08d6fb90f8b19591e0000000000000000000000000000000019eaf75bdb6205911235ead4019d390003044963cc02e54b1c0cec4aed54cd3125dabd2ffcc88d5dde3b949ebc06fb16,0000000000000000000000000000000000000000000000000000000000000001,237000, +000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e2946000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a220000000000000000000000000000000018a236ea02b1971d6e193a6eb92e1298956679d86864042fb6a0c36dd91c0e385944d779dedd0149fa8a1b3d6a07949d00000000000000000000000000000000048eac7d116b5a7906bce070e2b51ee7c4c493f1415abdb6fd2d35676036d3b741d14b7135419645a6906018e9d3f150000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000f77a58fb4b4165bf86d30b6349b84780d72b24e8eddce16c73a1f5a06de0638045a64978eb9c477d806f1955e818165000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a220000000000000000000000000000000018a236ea02b1971d6e193a6eb92e1298956679d86864042fb6a0c36dd91c0e385944d779dedd0149fa8a1b3d6a07949d00000000000000000000000000000000048eac7d116b5a7906bce070e2b51ee7c4c493f1415abdb6fd2d35676036d3b741d14b7135419645a6906018e9d3f150000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000a896c5a84cbd03e52ae77000eb0285f5704993664a744a89ff6b346efd2efec1a519b67229a3b87e1f80e6aa17e2946000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a2200000000000000000000000000000000015edb0036ce4f7cdd026d478a1d9a3ecf10d1ac8b210e8fb0900f331d94e7ebc5672884d276feb5bf74e4c295f8160e000000000000000000000000000000001572656d28148c21445ec74560968def9fb2b793b22a55086a039d39967a226cdcdab48d7c1269ba136e9fe7162bb95b000000000000000000000000000000000a87d0ccfb9c01148703d48993de04059d22a4cc48c5dabd2571ad4f7e60d6abfbcc5fb3bf363fd311fec675486c2a20000000000000000000000000000000000f77a58fb4b4165bf86d30b6349b84780d72b24e8eddce16c73a1f5a06de0638045a64978eb9c477d806f1955e818165000000000000000000000000000000000b50dc0957eccf5ad941b148a3824e82464bb7345a05125a0aa64f6ba34e34e767d4f679e9916faaacf82b3c79c9bddc00000000000000000000000000000000087152b3cb0db88776a7144fbafc1b210d150b637ca7148e3df600989231bce613fcf8e310fcc53aa2dc934bcbf86a2200000000000000000000000000000000015edb0036ce4f7cdd026d478a1d9a3ecf10d1ac8b210e8fb0900f331d94e7ebc5672884d276feb5bf74e4c295f8160e000000000000000000000000000000001572656d28148c21445ec74560968def9fb2b793b22a55086a039d39967a226cdcdab48d7c1269ba136e9fe7162bb95b,0000000000000000000000000000000000000000000000000000000000000001,237000, +000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d318400000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c95384000000000000000000000000000000000003c96c6f20d7ac31ee7ca77d11e8d25ea78cdf13e5f4d317752320e059e19196f14c15b5a18ca712f3a7cc6f09be6d4000000000000000000000000000000000ebd71f61fcddf1652675f577bbaeec26b892dd954965b057ffb431d6e37cc5425a2a42a0059482c2bd75adb2a120b0b000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000018bc060c3f6be35b724dee72bcda5cc376dc1f35561f7e70c9fe11eda256edd30aca8c19018433483186beb5b9b2792700000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c95384000000000000000000000000000000000003c96c6f20d7ac31ee7ca77d11e8d25ea78cdf13e5f4d317752320e059e19196f14c15b5a18ca712f3a7cc6f09be6d4000000000000000000000000000000000ebd71f61fcddf1652675f577bbaeec26b892dd954965b057ffb431d6e37cc5425a2a42a0059482c2bd75adb2a120b0b000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000001450bddfa14033ed8cdb94386715013ed9b2c4f9d65944e9d32c0b3545a085113e173e5afcfccb78878414a464d318400000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c9538400000000000000000000000000000000019c47b2347726bd72c33dd3e722d1fb179fe7d93b525c58defdea092f112dd0aaf973ea3573b358e8ac483390f63c3d7000000000000000000000000000000000b439ff419b20783f8b4485ec790be14f8ee1dab9eeeb7b9e7358f83887929cff9095bd4b0fab7d38e27a524d5ed9fa0000000000000000000000000000000000d35ffa284655a94c3050213f4f14e927c162818bbfd0480bad2e07000dd3081274056715c96408f243589d83365c9f20000000000000000000000000000000018bc060c3f6be35b724dee72bcda5cc376dc1f35561f7e70c9fe11eda256edd30aca8c19018433483186beb5b9b2792700000000000000000000000000000000094fdcc2119b4f674b5639653dfabcac59c2adb1ee2ec06c55c3f148c9361351ff0acb2519e4638cb2cde98efaec8f4400000000000000000000000000000000051d5edcbd6eadac808222f0423bada165fcb98f98a89f335c981262b0ca7ea1c536d41aa41b49b25f0c43f53c9538400000000000000000000000000000000019c47b2347726bd72c33dd3e722d1fb179fe7d93b525c58defdea092f112dd0aaf973ea3573b358e8ac483390f63c3d7000000000000000000000000000000000b439ff419b20783f8b4485ec790be14f8ee1dab9eeeb7b9e7358f83887929cff9095bd4b0fab7d38e27a524d5ed9fa0,0000000000000000000000000000000000000000000000000000000000000001,237000, +000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae920000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e4000000000000000000000000000000000174ffb89d7715866562d9882acb81ce40758644ca3e0decd546c8f5c349b24fce88214956e7540fac36bcfc105cf34a0000000000000000000000000000000003e06c5f607ccf1e2991828034fcdf91106295e7174b4dca21926169451ee58e737d535af45073e2378206e03c81c421000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f3555650000000000000000000000000000000007f7dc55c90fa181c55c9b83b7736ee84b3f19d960318e75661dd22c0546d62f4c9e07b915f9295a3c9fe6a62c84fc190000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e4000000000000000000000000000000000174ffb89d7715866562d9882acb81ce40758644ca3e0decd546c8f5c349b24fce88214956e7540fac36bcfc105cf34a0000000000000000000000000000000003e06c5f607ccf1e2991828034fcdf91106295e7174b4dca21926169451ee58e737d535af45073e2378206e03c81c421000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f35556500000000000000000000000000000000120935947070451885bf0c328bd83def193831ab9353844a01130074f16a1ff4d20df8459b5ad6a57d5f1959d37aae920000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e400000000000000000000000000000000188c12319c08d113e5b8ce2e18802b092401c540294704d291ea09ab336743d45023deb55a6cabf00dc84303efa2b761000000000000000000000000000000001620a58ad903177c218a25360e4ecd465414b59ddc39c4f5459e7137b1921095ab2eaca3bd038c1d827cf91fc37de68a000000000000000000000000000000000344cafaca754db423544657de1b77025164ccc702f8d45697fb73602302a3cb4511c38f0a76a37415d683398f3555650000000000000000000000000000000007f7dc55c90fa181c55c9b83b7736ee84b3f19d960318e75661dd22c0546d62f4c9e07b915f9295a3c9fe6a62c84fc190000000000000000000000000000000014b0862ac988a169342a4abacfebc5e7e7e8f8ff1166c6ca8fa53613c5fc28fd8b02d9c8d5e7a264b2fa59cd33a0f33c000000000000000000000000000000000f0f79631e7790192c18187144388373d52653cf11dd076688877fa9b5cf58e65fe4332874c301563089b9b3fa2322e400000000000000000000000000000000188c12319c08d113e5b8ce2e18802b092401c540294704d291ea09ab336743d45023deb55a6cabf00dc84303efa2b761000000000000000000000000000000001620a58ad903177c218a25360e4ecd465414b59ddc39c4f5459e7137b1921095ab2eaca3bd038c1d827cf91fc37de68a,0000000000000000000000000000000000000000000000000000000000000001,237000, +0000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b1700000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000b15d92f2301075ab0e3215aa72cf9b130bc8e1bcd9fa36375c4b9d7da430ae3e2b24f417336d8729f44542ee7f561d300000000000000000000000000000000197d90090501e8cdea28eb7963231f1a7b5f716cc3a086acb6e7626600d6544132cac943e8d5cefb5daf0a2f8d4006290000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce0000000000000000000000000000000005b1ce5cb2ae0e9175f2bd557d7869233d65008e0f47c52914fa44c4a6234b70eed236bc5499bb0412d0cbb61c98f93b0000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000b15d92f2301075ab0e3215aa72cf9b130bc8e1bcd9fa36375c4b9d7da430ae3e2b24f417336d8729f44542ee7f561d300000000000000000000000000000000197d90090501e8cdea28eb7963231f1a7b5f716cc3a086acb6e7626600d6544132cac943e8d5cefb5daf0a2f8d4006290000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce00000000000000000000000000000000144f438d86d1d808d528ea60c5d343b427124af6e43d4d9652368ddc508daab32fd9c9425cba44fba72e3449e366b1700000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000eeb38bb167edf3f9a38865b9c1eb32633babd6925e56f5bf16c18c91c6deb403bf9b0bd3e1d278d1abaabd1180a48d800000000000000000000000000000000008381e1347dfdcc60f2bc3ce0288dbce917da182fe48c12b049703af5daa1e2ebe136bac87e31045c4ff5d072bfa4820000000000000000000000000000000008797f704442e133d3b77a5f0020aa304d36ce326ea75ca47e041e4d8a721754e0579ce82b96a69142cb7185998d18ce0000000000000000000000000000000005b1ce5cb2ae0e9175f2bd557d7869233d65008e0f47c52914fa44c4a6234b70eed236bc5499bb0412d0cbb61c98f93b0000000000000000000000000000000006a3a773638c0b4a13e7ea399ac319f5ea55ed533aca32a933d69d8198ae997a66d1e32a02683e7fc5c1ec597106848f00000000000000000000000000000000155ef036f60a5b11697581265293cc4c6eebd3fdf500540529b6997c27a3be31212aee5cdfea6cd95d6d5bf83a8ce5aa000000000000000000000000000000000eeb38bb167edf3f9a38865b9c1eb32633babd6925e56f5bf16c18c91c6deb403bf9b0bd3e1d278d1abaabd1180a48d800000000000000000000000000000000008381e1347dfdcc60f2bc3ce0288dbce917da182fe48c12b049703af5daa1e2ebe136bac87e31045c4ff5d072bfa482,0000000000000000000000000000000000000000000000000000000000000001,237000, +00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ec0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf2300000000000000000000000000000000005c35f3372f1ec9845bd04ea722fbed2be1388abf59e622dd3dafb4b3af49bc5fba9e20235e7e58973fedf4b8b720691000000000000000000000000000000001111d18d621070509805d306a31c109701288fd55d4c0644349deb080c6591b6e852b4f7e009b80019513de7f2fce17d00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d0000000000000000000000000000000007efcb9da7b7ff0f4a1d92489ad76c59158bcc42c5c7a93067772a6d9ef1d3b6df9360d0fc1214e7dec02aaaf7b118bf0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf2300000000000000000000000000000000005c35f3372f1ec9845bd04ea722fbed2be1388abf59e622dd3dafb4b3af49bc5fba9e20235e7e58973fedf4b8b720691000000000000000000000000000000001111d18d621070509805d306a31c109701288fd55d4c0644349deb080c6591b6e852b4f7e009b80019513de7f2fce17d00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d000000000000000000000000000000001211464c91c7e78b00fe156da874407e4eeb7f422dbd698effb9a83357bf226d3f189f2db541eb17db3ed555084e91ec0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf23000000000000000000000000000000000143db2b6c68dfa02055ea2cbd11bee04a663c2d8fde6b0919355d755bbbc5a5e23021dfc7b6c1a76460020b4748da41a0000000000000000000000000000000008ef405cd76f7649b315d4afa02f9c40634ebbaf96390c7b3292e798ea4b646d36594b06d14a47ffa0adc2180d02c92e00000000000000000000000000000000000707c711f77bb425cddc71ecf96a18b6eb0bed7f012c4f6cc9431003f2e1ac17f7c1f68c4965a4fcc273a3db93451d0000000000000000000000000000000007efcb9da7b7ff0f4a1d92489ad76c59158bcc42c5c7a93067772a6d9ef1d3b6df9360d0fc1214e7dec02aaaf7b118bf0000000000000000000000000000000006a90568fa25b401756e3f86b5300c4d3b626dc6274f4685e8a9f56ec5ca2afce36a1fdc6d3414edc8780c4e650f10dc0000000000000000000000000000000012e41e8e0dd10b3ee31fa866753aa5d9db7669153b141114cdb2ef7fa6df5db27aef0cc70e76a741eae504b038ecf23000000000000000000000000000000000143db2b6c68dfa02055ea2cbd11bee04a663c2d8fde6b0919355d755bbbc5a5e23021dfc7b6c1a76460020b4748da41a0000000000000000000000000000000008ef405cd76f7649b315d4afa02f9c40634ebbaf96390c7b3292e798ea4b646d36594b06d14a47ffa0adc2180d02c92e,0000000000000000000000000000000000000000000000000000000000000001,237000, +0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c5000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000000e7e532ee4b892af39f8a3db7a05cc77a6eb0b3d977c17076bac4a52d5ba003a0ac1f902a4257791a45370eb88426a70000000000000000000000000000000016a556050e4905fa74b5061e3874f05cc7a6c5b049bd3bb7c34adef5a77c393239a600542a4401c3e61978ee6515a30e0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000eb29e948adc9e1816c67a7865517fbc91610b2eb30da1d8a1e15c5f62e71a1fd1f40d4d59b23bea7edeba79829010e6000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000000e7e532ee4b892af39f8a3db7a05cc77a6eb0b3d977c17076bac4a52d5ba003a0ac1f902a4257791a45370eb88426a70000000000000000000000000000000016a556050e4905fa74b5061e3874f05cc7a6c5b049bd3bb7c34adef5a77c393239a600542a4401c3e61978ee6515a30e0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000b4e7355aea3488234552d3dddfa2d1ad3164056407770e6c54f764193c9dc044cb7f2b157a1c4153b2045867d6f99c5000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000019192cb74b345d6f577c1d788bab500fea089ad11a0d514ef0760dfbc95556207dffe06e8711a8869fb9c8f1477b840400000000000000000000000000000000035bbbe52b36e09fd666a1980ad6bc7a9cd085d4a9c7d707a3e5f3ab4f34bcf1e505ffaa870ffe3bd3e587119aea079d0000000000000000000000000000000004b3c0e8b240b79c55f02833c2c20fa158e35c941e9e8e48247b96cb1d4923641b97e766637a3ced9fbef275ca9bd1ea000000000000000000000000000000000eb29e948adc9e1816c67a7865517fbc91610b2eb30da1d8a1e15c5f62e71a1fd1f40d4d59b23bea7edeba79829010e6000000000000000000000000000000001310a8cebed1491bb6399abe3a08fb25ad6ca00feb5db62069bc5bd45a57c167aaf06a628a3f18aa990bb389173855b100000000000000000000000000000000134655489380a9ae9cfbc3f4c6a1aa5b6dbe0a994e681915602c1d197c54bf3da6fb2df54eec3634ea87bf3fa92a69740000000000000000000000000000000019192cb74b345d6f577c1d788bab500fea089ad11a0d514ef0760dfbc95556207dffe06e8711a8869fb9c8f1477b840400000000000000000000000000000000035bbbe52b36e09fd666a1980ad6bc7a9cd085d4a9c7d707a3e5f3ab4f34bcf1e505ffaa870ffe3bd3e587119aea079d,0000000000000000000000000000000000000000000000000000000000000001,237000, +000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d2900000000000000000000000000000000179c34ba9578d5ff90272a2c7f756794670a047f79a53215da69937152bad0f86576945b12176d3e13cac38d26335c51000000000000000000000000000000000dcc715907e4e17824e24c1f513c09597965941e3ed0aaad6d0c59029b54fb039d716a998c9c418110bd49c5e365507f000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d0000000000000000000000000000000002f2e4467cdc15f1e57d75d6f5c172637df589590863bb437cc5166314e6362b7cd0d7499176b94529979849624cb432000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d2900000000000000000000000000000000179c34ba9578d5ff90272a2c7f756794670a047f79a53215da69937152bad0f86576945b12176d3e13cac38d26335c51000000000000000000000000000000000dcc715907e4e17824e24c1f513c09597965941e3ed0aaad6d0c59029b54fb039d716a998c9c418110bd49c5e365507f000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d29000000000000000000000000000000000264dd2fa407109abaf47d89c3d64542fd6d470579dfe0a98cc73f2fa3f6252bb9356ba39f3c92c1a6343c72d9cc4e5a000000000000000000000000000000000c34a091319b052226395b96f20fa37deb11b766b4b46811fa24799e5b5bfb20813a956524b7be7ea941b63a1c9a5a2c000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d0000000000000000000000000000000002f2e4467cdc15f1e57d75d6f5c172637df589590863bb437cc5166314e6362b7cd0d7499176b94529979849624cb432000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d29000000000000000000000000000000000264dd2fa407109abaf47d89c3d64542fd6d470579dfe0a98cc73f2fa3f6252bb9356ba39f3c92c1a6343c72d9cc4e5a000000000000000000000000000000000c34a091319b052226395b96f20fa37deb11b766b4b46811fa24799e5b5bfb20813a956524b7be7ea941b63a1c9a5a2c000000000000000000000000000000001465358836eb5c6e173e425f675aa231f9c62e9b122584078f2ab9af7440a4ce4ac2cd21ce35a0017b01e4913b40f73d00000000000000000000000000000000170e2da3bca3d0a8659e31df4d8a3a73e681c22beb21577bea6bbc3de1cabff8a1db28b51fdd46ba906767b69db2f679000000000000000000000000000000001360612f80227a2fc50a2dbdb3a49db16bd9f0ae401e2fb69408d990284cec05a1c29696f98b16d83a3dab6eac8678310000000000000000000000000000000001223232338ce1ac91e28b4c00ef4e3561f21f34fc405e479599cced3a86b7c36f541370bfd0176f785326f741699d2900000000000000000000000000000000179c34ba9578d5ff90272a2c7f756794670a047f79a53215da69937152bad0f86576945b12176d3e13cac38d26335c51000000000000000000000000000000000dcc715907e4e17824e24c1f513c09597965941e3ed0aaad6d0c59029b54fb039d716a998c9c418110bd49c5e365507f,0000000000000000000000000000000000000000000000000000000000000000,280000, +000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c853000000000000000000000000000000001643567a0f22b90fefee96c8e2f5851623384c2c68bce9589cdf64c933d494a8d805edce2fd18a6db80f4819391dd1f9000000000000000000000000000000000e4e40ab1969bf9f00ee3b984947ae95bf7b9579bdaeeee926638f9566f8ab26debb4c8d4009535cb6422b2c2ab7282d000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000cab5ed8dc53e9c891df449bd199776adbfc193fc8d6bebf9716610fd4db6def608df059bf29fe43dbf1bf0aa52c1b7f0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c853000000000000000000000000000000001643567a0f22b90fefee96c8e2f5851623384c2c68bce9589cdf64c933d494a8d805edce2fd18a6db80f4819391dd1f9000000000000000000000000000000000e4e40ab1969bf9f00ee3b984947ae95bf7b9579bdaeeee926638f9566f8ab26debb4c8d4009535cb6422b2c2ab7282d000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c8530000000000000000000000000000000003bdbb702a5d2d8a5b2d10ed605627c1413eff588ac82966ca516dd7c2dc617b46a612308182759201efb7e6c6e1d8b2000000000000000000000000000000000bb2d13f201626fb4a2d6c1dfa03fe41a4fbb60b35d623d640cd430b8fb84afd3ff0b371714aaca303bcd4d3d548827e000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000cab5ed8dc53e9c891df449bd199776adbfc193fc8d6bebf9716610fd4db6def608df059bf29fe43dbf1bf0aa52c1b7f0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c8530000000000000000000000000000000003bdbb702a5d2d8a5b2d10ed605627c1413eff588ac82966ca516dd7c2dc617b46a612308182759201efb7e6c6e1d8b2000000000000000000000000000000000bb2d13f201626fb4a2d6c1dfa03fe41a4fbb60b35d623d640cd430b8fb84afd3ff0b371714aaca303bcd4d3d548827e000000000000000000000000000000000ab6e2a649ed97be4574603b3b4a210f0748d8cddf132079e0543ec776ceb63902e48598b7698cf79fd5130cebaf0250000000000000000000000000000000000d55b3115d2bfcd1b93c631a71b2356c887b32452aae53ffd01a719121d58834be1e0fa4f22a01bbde0d40f55ad38f2c0000000000000000000000000000000002fec3b2e25d9300b9757cbe77857d7220d91a53fc29f3b7a0da5c4e0815882d1cc51a40a60fa8e1ae01296c209eda0a00000000000000000000000000000000041ff1a77aca41f7aaeec13fb5238c24d038e2e566b611203c430d7ac6251d545ed4a60e9e0087d6baa36272c7b1c853000000000000000000000000000000001643567a0f22b90fefee96c8e2f5851623384c2c68bce9589cdf64c933d494a8d805edce2fd18a6db80f4819391dd1f9000000000000000000000000000000000e4e40ab1969bf9f00ee3b984947ae95bf7b9579bdaeeee926638f9566f8ab26debb4c8d4009535cb6422b2c2ab7282d,0000000000000000000000000000000000000000000000000000000000000000,280000, +000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f700000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000a27fe0a29c761ce29a731ead969b1db3ae9ef4c05493cc370a128d97ef956c55d9a500991b3e7bf9600383633778ebb000000000000000000000000000000000dbb997ef4970a472bfcf03e959acb90bb13671a3d27c91698975a407856505e93837f46afc965363f21c35a3d194ec0000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf1000000000000000000000000000000001528dcaae381eb764333992e28ed557034ba5413c5b64df40ef3150d2771e5d1cd8c211ca22075c7436e2582960ab3b400000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000a27fe0a29c761ce29a731ead969b1db3ae9ef4c05493cc370a128d97ef956c55d9a500991b3e7bf9600383633778ebb000000000000000000000000000000000dbb997ef4970a472bfcf03e959acb90bb13671a3d27c91698975a407856505e93837f46afc965363f21c35a3d194ec0000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f700000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000fd913e00fb884cc217475cb69e1fafc298d5c38ee3bd5fbf68fa9c777b79f5ec111aff51fa0184023fec7c9cc881bf0000000000000000000000000000000000c45786b44e8dc531f1eb777adb0e146a963e46ab65d49a8ce9978607e5aa5c58b2880b8018a9ac97add3ca5c2e65beb000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf1000000000000000000000000000000001528dcaae381eb764333992e28ed557034ba5413c5b64df40ef3150d2771e5d1cd8c211ca22075c7436e2582960ab3b400000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000fd913e00fb884cc217475cb69e1fafc298d5c38ee3bd5fbf68fa9c777b79f5ec111aff51fa0184023fec7c9cc881bf0000000000000000000000000000000000c45786b44e8dc531f1eb777adb0e146a963e46ab65d49a8ce9978607e5aa5c58b2880b8018a9ac97add3ca5c2e65beb000000000000000000000000000000001654e99ebd103ed5709ae412a6df1751add90d4d56025667a4640c1d51435e7cad5464ff2c8b08cca56e34517b05acf10000000000000000000000000000000004d8353f55fdfb2407e80e881a5e57672fbcf7712dcec4cb583dbd93cf3f1052511fdee20f338a387690da7d69f4f6f700000000000000000000000000000000123a19e1427bac55eabdaec2aeeefadfca6e2b7581a5726c393bede2efd78af04e6cb986aa8d8d5c845bbbc28d62e7a00000000000000000000000000000000018026687f43591dac03a16fce0c4b8020469ec309bdbf9f0f270cf75e262abf4ae55d46f0b4ff130b7bbe2430bd0c9f4000000000000000000000000000000000a27fe0a29c761ce29a731ead969b1db3ae9ef4c05493cc370a128d97ef956c55d9a500991b3e7bf9600383633778ebb000000000000000000000000000000000dbb997ef4970a472bfcf03e959acb90bb13671a3d27c91698975a407856505e93837f46afc965363f21c35a3d194ec0,0000000000000000000000000000000000000000000000000000000000000000,280000, +0000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d200000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000eb3f5d24d1a4f520032534f6f81a6806c54df33cbd10c30203423aa4f33620b474cda321e924802b636daaeb34400470000000000000000000000000000000016f004f1dfbf140de042e4f57303928a576d9064f2da5b3ad392331f5c43327c7d2a6fd57456d5ef58b54a3e5ec275080000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c5100000000000000000000000000000000010ee94e9470765ac32b5648f1cd7d745a793dbd46dc95fa32db86929eec385e50cb35755120480be0956a2a342a46d900000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000eb3f5d24d1a4f520032534f6f81a6806c54df33cbd10c30203423aa4f33620b474cda321e924802b636daaeb34400470000000000000000000000000000000016f004f1dfbf140de042e4f57303928a576d9064f2da5b3ad392331f5c43327c7d2a6fd57456d5ef58b54a3e5ec275080000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d200000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000b4d1c17ec6597484ae95466d3ca0656f8226c5127b4068f46fcaef6a77d9418d75f25cc92c1b7fd03c825514cbbaa640000000000000000000000000000000003110cf859c0d28c6ad8c2c0d0481a4d0d09bb2000aab784939e9f819a6dc3a7a18190293cfd2a106149b5c1a13d35a30000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c5100000000000000000000000000000000010ee94e9470765ac32b5648f1cd7d745a793dbd46dc95fa32db86929eec385e50cb35755120480be0956a2a342a46d900000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000b4d1c17ec6597484ae95466d3ca0656f8226c5127b4068f46fcaef6a77d9418d75f25cc92c1b7fd03c825514cbbaa640000000000000000000000000000000003110cf859c0d28c6ad8c2c0d0481a4d0d09bb2000aab784939e9f819a6dc3a7a18190293cfd2a106149b5c1a13d35a30000000000000000000000000000000001bb1e11a1ccc0b70ce46114caca7ac1aba2a607fea8c6a0e01785e17559b271a0e8b5afbfa8705ecb77420473e81c510000000000000000000000000000000018f2289ba50f703f87f0516d517e2f6309fe0dc7aca87cc534554c0e57c4bdc5cde0ca896033b7f3d96995d5cbd563d200000000000000000000000000000000000353798691ffba215b6458a47823d149e4e2e48c9e5f65df61d6b995889f3b0e2b34824e4ffa73296d03148c607c26000000000000000000000000000000001190ba585a928413dc3cef3d77b2cff99b053cadcb13b2529c74171a094d479a259678dd43a3ef2a2e597223eb7fd35c000000000000000000000000000000000eb3f5d24d1a4f520032534f6f81a6806c54df33cbd10c30203423aa4f33620b474cda321e924802b636daaeb34400470000000000000000000000000000000016f004f1dfbf140de042e4f57303928a576d9064f2da5b3ad392331f5c43327c7d2a6fd57456d5ef58b54a3e5ec27508,0000000000000000000000000000000000000000000000000000000000000000,280000, +0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531f0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d0000000000000000000000000000000002a36d5efd3381c35ff4f361cd813a96c3e5185141c5985073b45d1319c5f392442b7aa6a253b7eb22d1b5052812be00000000000000000000000000000000000f745dd17966b6befa7f740ea360241162505d6269226ffda90546863d0fff124d8fea13c763cfb69c2f8f12b81d431f0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab0000000000000000000000000000000004acd0ba7577ffe37bdeeaf5810b5a8a4a6b51c3c02bec4e0c6f0cfb4f12283120d283c12ecb7e4be7063fefb37a578c0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d0000000000000000000000000000000002a36d5efd3381c35ff4f361cd813a96c3e5185141c5985073b45d1319c5f392442b7aa6a253b7eb22d1b5052812be00000000000000000000000000000000000f745dd17966b6befa7f740ea360241162505d6269226ffda90546863d0fff124d8fea13c763cfb69c2f8f12b81d431f0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531f0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d00000000000000000000000000000000175da48b3c4c64d6eb26b45475ca7240a0923333b1bf7a6ef37c758ddceb0291da8085580f004814972d4afad7ececab000000000000000000000000000000000a8cb418c0192fdb509c33a79feb88c60226ee228a62a2c1be2b8c1ab9a0f711d11c15eae9f030491dcf70ed47e2678c0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab0000000000000000000000000000000004acd0ba7577ffe37bdeeaf5810b5a8a4a6b51c3c02bec4e0c6f0cfb4f12283120d283c12ecb7e4be7063fefb37a578c0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d00000000000000000000000000000000175da48b3c4c64d6eb26b45475ca7240a0923333b1bf7a6ef37c758ddceb0291da8085580f004814972d4afad7ececab000000000000000000000000000000000a8cb418c0192fdb509c33a79feb88c60226ee228a62a2c1be2b8c1ab9a0f711d11c15eae9f030491dcf70ed47e2678c0000000000000000000000000000000012ecb4c2f259efb4416025e236108eff7862e54f796605cc7eb12f3e5275c80ef42aadd2acfbf84d5206f6884d8e3eab000000000000000000000000000000001554412fc407e6b6cf3cbcc0c240524d1a0bf9c1335926715ac1c5a5a79ecdf2fdd97c3d828881b3d2f8c0104c85531f0000000000000000000000000000000018b0cd0360c5d5bf8254725c19976345cd84d32d0d770286444fe29dfdbc495dd58407ee8d48ec1004971f249453b8460000000000000000000000000000000009a6ea13f5a5a279ec3bb86cc028a1685d84135ed5fe99cd6b6fb380a42c3af5497e3ba5ea558618487cf953172a376d0000000000000000000000000000000002a36d5efd3381c35ff4f361cd813a96c3e5185141c5985073b45d1319c5f392442b7aa6a253b7eb22d1b5052812be00000000000000000000000000000000000f745dd17966b6befa7f740ea360241162505d6269226ffda90546863d0fff124d8fea13c763cfb69c2f8f12b81d431f,0000000000000000000000000000000000000000000000000000000000000000,280000, +00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e7879000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000000397dbdcc3edf976e8c507f5e70299da8c7765772115bf8edf7dc9024050c2ed98746c2bf7dd4400ab1fb89af991e43f00000000000000000000000000000000139bd5f917f59e2cb6c41c59024c12cdaf95285f3947b80267f36e3bd2701f9548b561c49003fc5ddeee3fe7bc8f5b5b00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e20000000000000000000000000000000012ee6884c9d68bdabe8f4aa92aa613129993aad6a7aafffef1922c910cbd3f8b4ae8a810c59a0b9de0a79d4e5db13232000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000000397dbdcc3edf976e8c507f5e70299da8c7765772115bf8edf7dc9024050c2ed98746c2bf7dd4400ab1fb89af991e43f00000000000000000000000000000000139bd5f917f59e2cb6c41c59024c12cdaf95285f3947b80267f36e3bd2701f9548b561c49003fc5ddeee3fe7bc8f5b5b00000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e7879000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000001669360d7591ed2362569fc05c4912fcd7ffe60dd26f533087b3099eb6603336863793d2b976bbff0edf4765066dc66c0000000000000000000000000000000006653bf1218a486d94578b5d40ff9a09b4e22325ba3d5abcff3d64652440d68ed5f69e3a215003a1db10c01843704f5000000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e20000000000000000000000000000000012ee6884c9d68bdabe8f4aa92aa613129993aad6a7aafffef1922c910cbd3f8b4ae8a810c59a0b9de0a79d4e5db13232000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000001669360d7591ed2362569fc05c4912fcd7ffe60dd26f533087b3099eb6603336863793d2b976bbff0edf4765066dc66c0000000000000000000000000000000006653bf1218a486d94578b5d40ff9a09b4e22325ba3d5abcff3d64652440d68ed5f69e3a215003a1db10c01843704f5000000000000000000000000000000000010dac3e5885cc55f3e53b3fdd5d28b2d78ceeea2b669757a187de0ce3f28b586e451b119cdb7dc8b97d603f2bb700e2000000000000000000000000000000000712a9656fa95abf8c8c5d0d18a599c4cae3a0ae4bda12c0759ea60fe9f3b698d3c357edebb9f461d95762b1a24e7879000000000000000000000000000000001431c5161fc51024c5708496a1f9545c3d4c05ef9e2c91154e22ebfe251017fc61ba54c679ba2ad6b8314bfd8d6272c900000000000000000000000000000000098f2e8b6d3fcf9fb27e912af57b45d3d35a7c5471b9ea2c85262c0efb44c435cd949f23d7d40f14b6b6d4d92cb8412e000000000000000000000000000000000397dbdcc3edf976e8c507f5e70299da8c7765772115bf8edf7dc9024050c2ed98746c2bf7dd4400ab1fb89af991e43f00000000000000000000000000000000139bd5f917f59e2cb6c41c59024c12cdaf95285f3947b80267f36e3bd2701f9548b561c49003fc5ddeee3fe7bc8f5b5b,0000000000000000000000000000000000000000000000000000000000000000,280000, +000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b00000000000000000000000000000000197ed996d62fc0628d8ea4adee487df31c794e05e7c327aaa140c6be0109031bb763c5f84bc35a0597dc61e93d23a9bf000000000000000000000000000000001056c1f3c6ae36be26430d142d34b0e807685c79935496414e004cb85900d85a18454bde9c0f2650f19db35eb3dd468d000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e5590000000000000000000000000000000006abf7ef1d5e3484992225b5a59791a68cc7e1e0f8aaf2415a9f759f2dff53f62aecf23e0443fdf37bb3775be9f5c981000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b00000000000000000000000000000000197ed996d62fc0628d8ea4adee487df31c794e05e7c327aaa140c6be0109031bb763c5f84bc35a0597dc61e93d23a9bf000000000000000000000000000000001056c1f3c6ae36be26430d142d34b0e807685c79935496414e004cb85900d85a18454bde9c0f2650f19db35eb3dd468d000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b000000000000000000000000000000000082385363502637bd8d030855032ee447fdfd7f0bc1eb14c5f00be2f5a7f30867483a066590a5fa22229e16c2dc00ec0000000000000000000000000000000009aa4ff672d1afdc24d89aa21616fbef5d0eef0b60307c7e193085e89db01dca0666b4201544d9aec8614ca14c22641e000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e5590000000000000000000000000000000006abf7ef1d5e3484992225b5a59791a68cc7e1e0f8aaf2415a9f759f2dff53f62aecf23e0443fdf37bb3775be9f5c981000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b000000000000000000000000000000000082385363502637bd8d030855032ee447fdfd7f0bc1eb14c5f00be2f5a7f30867483a066590a5fa22229e16c2dc00ec0000000000000000000000000000000009aa4ff672d1afdc24d89aa21616fbef5d0eef0b60307c7e193085e89db01dca0666b4201544d9aec8614ca14c22641e000000000000000000000000000000001889ef0e20d5ddbeeb4380b97ed7d4be97ef0def051d232598b2459a72845d97fa5c1264802ab18d76b15d8fbd25e55900000000000000000000000000000000135519fb1c21b215b1f982009db41b30d7af69a3fada207e0c915d01c8b1a22df3bf0dc0ad10020c3e4b88a41609e12a000000000000000000000000000000000caecf650a12bb629ebd3b978ef9c2d4486f8ce21d515451ecdf01d27740f41b719d5a952e737c83641953a8c8b3a1bb000000000000000000000000000000001641ca29ff6016af335499dfc7167b3d961a25b7f61008c27b3cb13d3cb28fb5096413b1c7f1ca18e5d3b5017d6fed1b00000000000000000000000000000000197ed996d62fc0628d8ea4adee487df31c794e05e7c327aaa140c6be0109031bb763c5f84bc35a0597dc61e93d23a9bf000000000000000000000000000000001056c1f3c6ae36be26430d142d34b0e807685c79935496414e004cb85900d85a18454bde9c0f2650f19db35eb3dd468d,0000000000000000000000000000000000000000000000000000000000000000,280000, +0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f50000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f0000000000000000000000000000000008789ffe0a8676c6a56742a30a48e5e65b88aafd71859d704fb9f69e5e274ccb6942bc51ad36c5671406052aacf19df9000000000000000000000000000000000c7607f4fc69a25aff00a54369f213c4587404644358da4abf26d151dfa4905ba9731dcfb12e2a3f2c551cacd0f4e47f0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf9250000000000000000000000000000000001b7a86c4142843a854dd0937bdbfd833a34fb15303d753e3f41eaf19f4fd9a6af785804d5ae2c3b99044cc13e6ca4b60000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f0000000000000000000000000000000008789ffe0a8676c6a56742a30a48e5e65b88aafd71859d704fb9f69e5e274ccb6942bc51ad36c5671406052aacf19df9000000000000000000000000000000000c7607f4fc69a25aff00a54369f213c4587404644358da4abf26d151dfa4905ba9731dcfb12e2a3f2c551cacd0f4e47f0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f50000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f00000000000000000000000000000000118871ec2ef96fd3a5b465133902c6f108eea08781ff754f1776dc029889a958b56943ad041d3a98a5f8fad5530e0cb2000000000000000000000000000000000d8b09f53d16443f4c1b0272d95999130c034720b02c3874a80a014f170c65c87538e22f0025d5c08da9e3532f0ac62c0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf9250000000000000000000000000000000001b7a86c4142843a854dd0937bdbfd833a34fb15303d753e3f41eaf19f4fd9a6af785804d5ae2c3b99044cc13e6ca4b60000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f00000000000000000000000000000000118871ec2ef96fd3a5b465133902c6f108eea08781ff754f1776dc029889a958b56943ad041d3a98a5f8fad5530e0cb2000000000000000000000000000000000d8b09f53d16443f4c1b0272d95999130c034720b02c3874a80a014f170c65c87538e22f0025d5c08da9e3532f0ac62c0000000000000000000000000000000008726a32d489a5ea1c1b314dc4d400d995d0eb8b49d47e65a6ac8fd0e6ec0cda1c637ee314c0c5d1ad72cd3588ebf925000000000000000000000000000000001849697df83d625fc5cdd722c76faf542a42506fc3479d8127eee7af57611c7d6f33a7f9dba5d3c420fab33ec19305f50000000000000000000000000000000009c7164f8d40c7e9ca571c46f8edf1c4a961779e55f6b10ffc44d76da78adadb83195d757949be39631c6a53d2d67fae0000000000000000000000000000000012cd5149125e7cc21bb5349be7fe03d5854ee73ba515021b6dc87e81ce1e1fa3e386fcb0de80977b9329e72ad54f929f0000000000000000000000000000000008789ffe0a8676c6a56742a30a48e5e65b88aafd71859d704fb9f69e5e274ccb6942bc51ad36c5671406052aacf19df9000000000000000000000000000000000c7607f4fc69a25aff00a54369f213c4587404644358da4abf26d151dfa4905ba9731dcfb12e2a3f2c551cacd0f4e47f,0000000000000000000000000000000000000000000000000000000000000000,280000, +000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567300000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000b060965391bfd4afe3271c6ddb91eecb8c7a60451c469d63bb178b1361617000f589c33c35b5deda2f072c6edf2eb370000000000000000000000000000000011c8c988379cd2b82cb8ebd81c3e14d2c01c09dde5690b97623c0876c7554f52ccbaa33d17fb0f0cf331cc85749340cd000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000008151a15a13daeee49a82737118d488005fa7ed1869bc458f8af88e7341e0a48b5d8f129f6eb071fb07c11887f4d543800000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000b060965391bfd4afe3271c6ddb91eecb8c7a60451c469d63bb178b1361617000f589c33c35b5deda2f072c6edf2eb370000000000000000000000000000000011c8c988379cd2b82cb8ebd81c3e14d2c01c09dde5690b97623c0876c7554f52ccbaa33d17fb0f0cf331cc85749340cd000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567300000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000efb08850063e94f4ce935ef65928deaabafa580a1c0a8e92b7f59efc09adf240f5363caedf8a212170e8d39120cbf74000000000000000000000000000000000838486201e313e21e62bbde270d9804a45b41a70e1c072804f4ca2a2f5ba6d151f15cc19958f0f2c6cd337a8b6c69de000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000008151a15a13daeee49a82737118d488005fa7ed1869bc458f8af88e7341e0a48b5d8f129f6eb071fb07c11887f4d543800000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000efb08850063e94f4ce935ef65928deaabafa580a1c0a8e92b7f59efc09adf240f5363caedf8a212170e8d39120cbf74000000000000000000000000000000000838486201e313e21e62bbde270d9804a45b41a70e1c072804f4ca2a2f5ba6d151f15cc19958f0f2c6cd337a8b6c69de000000000000000000000000000000001688c63e325569855bc2e51d668cef112b2479efa33519fe7f45eab89e275e2c4652cf8c2814f179935ccf1d24d8bd0f0000000000000000000000000000000011ebf7d4984237ac0173807f31be64575e7cccb36ce94e666e8149b9c292ebdb68d30ed4ba68f8e00982ee7780b2567300000000000000000000000000000000093c423917d10edc429acd927def56ab4f07254b3892762aa7056f24224528aa0f528fe8538ca996ca63506c84af73270000000000000000000000000000000003fd3ba68878485e25ccaa2539eed0a97743ae9f5b848e9d83c8ea60f7ad0f1cc6d94a59498f79dcab2bfcc2fdbacfed000000000000000000000000000000000b060965391bfd4afe3271c6ddb91eecb8c7a60451c469d63bb178b1361617000f589c33c35b5deda2f072c6edf2eb370000000000000000000000000000000011c8c988379cd2b82cb8ebd81c3e14d2c01c09dde5690b97623c0876c7554f52ccbaa33d17fb0f0cf331cc85749340cd,0000000000000000000000000000000000000000000000000000000000000000,280000, +000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc300000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000f05a111b41a54e0ca78c3a1fff3b80bee7c1505a06b9a4faf36a73b87121d2952cc4f4c4e0dcb6633cad12b0caffc620000000000000000000000000000000018daa0f9a2bb347517eee63463b9d6a5e850446e8a94d0986f2921bf81a9f7541e8fee9d7bbb6d9181021af945fce3e3000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a07060000000000000000000000000000000000876cf6553b21053e0d7a4449cd137fd946f2de0f7032f535f54914a8ae7da5afbe765bdfa3a0cdea0a50e1ed43bce800000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000f05a111b41a54e0ca78c3a1fff3b80bee7c1505a06b9a4faf36a73b87121d2952cc4f4c4e0dcb6633cad12b0caffc620000000000000000000000000000000018daa0f9a2bb347517eee63463b9d6a5e850446e8a94d0986f2921bf81a9f7541e8fee9d7bbb6d9181021af945fce3e3000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc300000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000afb70d8856591b980a2e4144357f4cb75fb367f5319786fb7fa2b656f9ed8facbdfb0b26346349986342ed4f34fae4900000000000000000000000000000000012670f096c4b225332cc181df91d6317c27071668f04226f807b0e17506fed0001c11613598926e38fce506ba02c6c8000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a07060000000000000000000000000000000000876cf6553b21053e0d7a4449cd137fd946f2de0f7032f535f54914a8ae7da5afbe765bdfa3a0cdea0a50e1ed43bce800000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000afb70d8856591b980a2e4144357f4cb75fb367f5319786fb7fa2b656f9ed8facbdfb0b26346349986342ed4f34fae4900000000000000000000000000000000012670f096c4b225332cc181df91d6317c27071668f04226f807b0e17506fed0001c11613598926e38fce506ba02c6c8000000000000000000000000000000000bb6f731b345bb1319b9acab09c186449a51dad8b6526251bc58e958cfd933137067e6f778b019f131cc7b23e08a0706000000000000000000000000000000001979a4f3e444c5950d0e2d71f97e99578b3058a6e414dfca313b898c4e02787e6eed89a2d1b05f31cff4af1e12bbedc300000000000000000000000000000000039d8e90425810a0b2fb5c915905863eb2da363ad4188e42cedce678bdd0f51eca0a96b78ab9e082d59dcd10e3c3c97a000000000000000000000000000000001973250dc31d16f658323d021dddc5439ef4396b6ed735f108cd7b27feb1b508daf863ab6431a77ec0b10cf7e001244f000000000000000000000000000000000f05a111b41a54e0ca78c3a1fff3b80bee7c1505a06b9a4faf36a73b87121d2952cc4f4c4e0dcb6633cad12b0caffc620000000000000000000000000000000018daa0f9a2bb347517eee63463b9d6a5e850446e8a94d0986f2921bf81a9f7541e8fee9d7bbb6d9181021af945fce3e3,0000000000000000000000000000000000000000000000000000000000000000,280000, +00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a9035050000000000000000000000000000000017d9e9e2008501981068cb0403e73c270d99defd468cc9dc2d5bbc57750a4a58236f8f7a8df4f8b607095b6a80e7de49000000000000000000000000000000000ebddf4fc74f25be3c358b72a20d1c093f980adfc943b898266592f691e11413c60151a0085d6c9aec8c2d329abbac0d00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c2000000000000000000000000000000001093356407cff41779ce8f3d53dfe7a04edc8ce7192ddfeeb4329c38152cf1875d0df9ffeced95f1c7fae7d124649f21000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a9035050000000000000000000000000000000017d9e9e2008501981068cb0403e73c270d99defd468cc9dc2d5bbc57750a4a58236f8f7a8df4f8b607095b6a80e7de49000000000000000000000000000000000ebddf4fc74f25be3c358b72a20d1c093f980adfc943b898266592f691e11413c60151a0085d6c9aec8c2d329abbac0d00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a903505000000000000000000000000000000000227280838fae5023ab2dcb23f6470b056dd6c87acf848e339d5164981a6abcbfb3c7084235f0749b2f5a4957f17cc62000000000000000000000000000000000b43329a7230c0dc0ee61c43a13e90ce24df40a52a415a2740cb3faa64cfe21058aaae5ea8f69364cd72d2cd6543fe9e00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c2000000000000000000000000000000001093356407cff41779ce8f3d53dfe7a04edc8ce7192ddfeeb4329c38152cf1875d0df9ffeced95f1c7fae7d124649f21000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a903505000000000000000000000000000000000227280838fae5023ab2dcb23f6470b056dd6c87acf848e339d5164981a6abcbfb3c7084235f0749b2f5a4957f17cc62000000000000000000000000000000000b43329a7230c0dc0ee61c43a13e90ce24df40a52a415a2740cb3faa64cfe21058aaae5ea8f69364cd72d2cd6543fe9e00000000000000000000000000000000078cca0bfd6957f9aff9731b45fdbdbeca6691f6fe6bf0b7847859c77478037e14864b202b235953ac7da231367324c200000000000000000000000000000000096ddc8631aff282d14d1878ef6bc537159abe9dda5732d0b2fe3668e184049cc19e05fec4666a0df204182edb9b0b8a000000000000000000000000000000000eff44a5e3b9fc8ffe31771fbcabea6efbd68384c5931216a2b7465aaa2566ee116b7daeea632677f35379107f7334f0000000000000000000000000000000000c3c942373f69c2c9631cef1c6bbb1a4567d5b95500409d4f2c6bf4a66ee263e6f167e22790badea0eac4a541a9035050000000000000000000000000000000017d9e9e2008501981068cb0403e73c270d99defd468cc9dc2d5bbc57750a4a58236f8f7a8df4f8b607095b6a80e7de49000000000000000000000000000000000ebddf4fc74f25be3c358b72a20d1c093f980adfc943b898266592f691e11413c60151a0085d6c9aec8c2d329abbac0d,0000000000000000000000000000000000000000000000000000000000000000,280000, +000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa300000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000001fdc4256cc997934a65c68ab9767b09c7aad14b5765dbeedb72ab2429231cb333ab9f9143414359376d76857e8972d9000000000000000000000000000000001362f417875259b47cfd9e4c5feda52b949dcbf5b8178318428fd3e70c384020e58f515b9a24af5597cfa037d42491c6000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e0000000000000000000000000000000007007c89288b69f16870dc857a02cd071db8178e578fd2b78fcd5edb5050dcded107a1c1c0071d45e4c4af364bc9400800000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000001fdc4256cc997934a65c68ab9767b09c7aad14b5765dbeedb72ab2429231cb333ab9f9143414359376d76857e8972d9000000000000000000000000000000001362f417875259b47cfd9e4c5feda52b949dcbf5b8178318428fd3e70c384020e58f515b9a24af5597cfa037d42491c6000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa300000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000018034dc4ccb64f0700b5e12b89d531cd9ccc7a399c1f36d08bbe277ccd8dd970eb00606d6e12bca68291897a817637d200000000000000000000000000000000069e1dd2b22d8ce5ce1e0969e35e07abcfd97f8f3b6d8fa724a0feb9ea78b603391caea3172f50aa222f5fc82bdb18e5000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e0000000000000000000000000000000007007c89288b69f16870dc857a02cd071db8178e578fd2b78fcd5edb5050dcded107a1c1c0071d45e4c4af364bc9400800000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000018034dc4ccb64f0700b5e12b89d531cd9ccc7a399c1f36d08bbe277ccd8dd970eb00606d6e12bca68291897a817637d200000000000000000000000000000000069e1dd2b22d8ce5ce1e0969e35e07abcfd97f8f3b6d8fa724a0feb9ea78b603391caea3172f50aa222f5fc82bdb18e5000000000000000000000000000000000b3a1dfe2d1b62538ed49648cb2a8a1d66bdc4f7a492eee59942ab810a306876a7d49e5ac4c6bb1613866c158ded993e000000000000000000000000000000001300956110f47ca8e2aacb30c948dfd046bf33f69bf54007d76373c5a66019454da45e3cf14ce2b9d53a50c9b4366aa300000000000000000000000000000000081da74d812a6718e351c062e93f9edb24eff830be5c44c3f21cca606f5b1287de8ba65a60d42cbf9740c9522fcdc9eb000000000000000000000000000000000eb1d38fd394b7e78dfaeb3b3b97d3d928c16472ee74ae0be1ec3efa510b9bb64cec369793219ceab55a0ed0ece23de80000000000000000000000000000000001fdc4256cc997934a65c68ab9767b09c7aad14b5765dbeedb72ab2429231cb333ab9f9143414359376d76857e8972d9000000000000000000000000000000001362f417875259b47cfd9e4c5feda52b949dcbf5b8178318428fd3e70c384020e58f515b9a24af5597cfa037d42491c6,0000000000000000000000000000000000000000000000000000000000000000,280000, +0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c8000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000010fb029e35b3f6e156b8751415f180ee3960cd3bb6ba9b8e456715ec70b1ba1410b8bfb77998f744d3f462533b59e26c000000000000000000000000000000001472654d9aa210a41d74e3661e05a9eb6b292719b46aa65f94b6abd514bf05f679dae89d21008245d79a381b0d7f51be0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000a76ccda2ca736ce935b4b88e08bbf183f69e2b3f5a471662a5de571976e7d4264021db88b919c896bbbb8128732c3e3000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000010fb029e35b3f6e156b8751415f180ee3960cd3bb6ba9b8e456715ec70b1ba1410b8bfb77998f744d3f462533b59e26c000000000000000000000000000000001472654d9aa210a41d74e3661e05a9eb6b292719b46aa65f94b6abd514bf05f679dae89d21008245d79a381b0d7f51be0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c8000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000009060f4c03cbefb8f46332a22d5a2be92b167e493cca773121c9bcb485ff3c100df3404737bb08bae60a9dacc4a5c83f00000000000000000000000000000000058eac9c9eddd5f62da6c450254602ebf94e246b3f1a6c5fd27a26cbe1f1f02da4d1176190537db9e264c7e4f28058ed0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000a76ccda2ca736ce935b4b88e08bbf183f69e2b3f5a471662a5de571976e7d4264021db88b919c896bbbb8128732c3e3000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000009060f4c03cbefb8f46332a22d5a2be92b167e493cca773121c9bcb485ff3c100df3404737bb08bae60a9dacc4a5c83f00000000000000000000000000000000058eac9c9eddd5f62da6c450254602ebf94e246b3f1a6c5fd27a26cbe1f1f02da4d1176190537db9e264c7e4f28058ed0000000000000000000000000000000007c00b3e7e50a860e99cdc92235f45a555c343304a067a71b6aaade016ef99bc50e3b2c5e3335d4bdacb816d3c765630000000000000000000000000000000000f8a45100cd8afcbb7c05c2d62bfedbf250d68d0fde0a1593cd2ed2f5f4278e1baa9e24625c263764e4347ed78cce6c8000000000000000000000000000000000b8e764aa5afa4a6e8227d1bc720eeffd72d963458a4963a3bbe697d3da11186a30d90f7a4eda5630f6967095816913300000000000000000000000000000000085d05b570cd58def6ac2f7e80dc18658dc5d0e6a1f5a5cf4d18745e03494654eb1a6d5399ec2c5288890ade446317d00000000000000000000000000000000010fb029e35b3f6e156b8751415f180ee3960cd3bb6ba9b8e456715ec70b1ba1410b8bfb77998f744d3f462533b59e26c000000000000000000000000000000001472654d9aa210a41d74e3661e05a9eb6b292719b46aa65f94b6abd514bf05f679dae89d21008245d79a381b0d7f51be,0000000000000000000000000000000000000000000000000000000000000000,280000, +000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb21100000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d7000000000000000000000000000000000520cfc8c536a1d4e685c4eacbc2000d70abd72e1bf8ce3839d79f5cfa069ed31aafb15542f23b8d1af678bab05a2d410000000000000000000000000000000017cfffda12d21c98b79ac31c5bb696783afb7d69c2bedf0fb070cf7714959db14957a4763564b65b7ed214d7b48d399c000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c00000000000000000000000000000000124a601a06d5094945ec8528c5457ea3f8ca710137b6ad48ee7ad93db53c056059dbc8b02d9edf5e2786c575a0bff89a00000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d7000000000000000000000000000000000520cfc8c536a1d4e685c4eacbc2000d70abd72e1bf8ce3839d79f5cfa069ed31aafb15542f23b8d1af678bab05a2d410000000000000000000000000000000017cfffda12d21c98b79ac31c5bb696783afb7d69c2bedf0fb070cf7714959db14957a4763564b65b7ed214d7b48d399c000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb21100000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d70000000000000000000000000000000014e04221744944c56495e2cb7789acc9f3cb7456d78c44872d593343fcaa575103fc4ea96e61c4729f0887454fa57d6a000000000000000000000000000000000231121026adca019380e499e795165f297bce1b30c633afb6c00329e21b5872d5545b887bef49a43b2ceb284b72710f000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c00000000000000000000000000000000124a601a06d5094945ec8528c5457ea3f8ca710137b6ad48ee7ad93db53c056059dbc8b02d9edf5e2786c575a0bff89a00000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d70000000000000000000000000000000014e04221744944c56495e2cb7789acc9f3cb7456d78c44872d593343fcaa575103fc4ea96e61c4729f0887454fa57d6a000000000000000000000000000000000231121026adca019380e499e795165f297bce1b30c633afb6c00329e21b5872d5545b887bef49a43b2ceb284b72710f000000000000000000000000000000001517dd04b165c50d2b1ef2f470c821c080f604fe1a23f2fa5481f3a63e0f56e05c89c7403d4067a5f6e59d4a338d0b5c0000000000000000000000000000000007b6b1d032aadd51052f228d7e062e336bacda83bbce657678b5f9634174f0c3c4d0374e83b520a192783a8a5f3fb21100000000000000000000000000000000042280b112fdbbd94f647e5b1f4b51d864f85063a5b66e1f1fe5b1a8d280f9bf1db81ad3588f93f8801ff1a3f66b96330000000000000000000000000000000001e0887904228790d03d8b6d17bebdd8659deafa2ebd9b07069ce89fe228824a39966953d14dda1bd6ccce5faf16e4d7000000000000000000000000000000000520cfc8c536a1d4e685c4eacbc2000d70abd72e1bf8ce3839d79f5cfa069ed31aafb15542f23b8d1af678bab05a2d410000000000000000000000000000000017cfffda12d21c98b79ac31c5bb696783afb7d69c2bedf0fb070cf7714959db14957a4763564b65b7ed214d7b48d399c,0000000000000000000000000000000000000000000000000000000000000000,280000, +000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a00000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000007f786ea1cc7cd69ae1061d6b914278dfc7ebe8a714aa8cd04323860314c3b4b36054169dd5c6c60e67bfa3902d216f50000000000000000000000000000000019675b09a4de34af3c6e79452b57b31b6d499200e996008a9e7d1c910ca0ad2a352dc39cb3fd7333182476095b7aeec3000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c04200000000000000000000000000000000001b8c085fd1f34fb273da7d651602b326fef7c357c2fb7845f4c17ce95152042af9e51e7d7699b50f3605bacab563a100000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000007f786ea1cc7cd69ae1061d6b914278dfc7ebe8a714aa8cd04323860314c3b4b36054169dd5c6c60e67bfa3902d216f50000000000000000000000000000000019675b09a4de34af3c6e79452b57b31b6d499200e996008a9e7d1c910ca0ad2a352dc39cb3fd7333182476095b7aeec3000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a00000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000012098b001cb819309d0b45df8a37854967f88cfa823a69f262fe9a40c564bad8e8a6be94d3f7939ed38305c6fd2d93b6000000000000000000000000000000000099b6e094a1b1eb0ead2e7117f3f9bbf72db98409ef1234c8b3b60fea1048f9e97e3c61fd568ccca1da89f6a484bbe8000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c04200000000000000000000000000000000001b8c085fd1f34fb273da7d651602b326fef7c357c2fb7845f4c17ce95152042af9e51e7d7699b50f3605bacab563a100000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000012098b001cb819309d0b45df8a37854967f88cfa823a69f262fe9a40c564bad8e8a6be94d3f7939ed38305c6fd2d93b6000000000000000000000000000000000099b6e094a1b1eb0ead2e7117f3f9bbf72db98409ef1234c8b3b60fea1048f9e97e3c61fd568ccca1da89f6a484bbe8000000000000000000000000000000000475e66c9e4e434c4872b8537e0ab930165b39f41e04b208d74d3033e1d69dfb4b134ae3a9dc46347d30a6805508c0420000000000000000000000000000000019e585e1d9adf34a98a7cd38de35aa243d7853c19bc21747213c11240d5fa41ff3b21ae033dd664aaac8fa45354a470a00000000000000000000000000000000137e91115129cbaa1ae2bbb79abe5505436bb51ddceeb011d56dc5c3c396b6b00067d6e6108bafca40fc717737487b27000000000000000000000000000000001592fec7d33bffa7f3eebf038e3194513736cc41a143471fb8c55a44c7521c07e4d8368e5c6ee21ed0478f949f3e224e0000000000000000000000000000000007f786ea1cc7cd69ae1061d6b914278dfc7ebe8a714aa8cd04323860314c3b4b36054169dd5c6c60e67bfa3902d216f50000000000000000000000000000000019675b09a4de34af3c6e79452b57b31b6d499200e996008a9e7d1c910ca0ad2a352dc39cb3fd7333182476095b7aeec3,0000000000000000000000000000000000000000000000000000000000000000,280000, +0000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b0000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000fbe421858e4109c51de57b77da4f9c4c1f950099532d9e30e2f7a8b8b4fb9f708cde1a497050d0944e089978b15321e0000000000000000000000000000000019f48a0bf0f27df65ba766a65e831a0801a4ebcd1995a6002a803f88aead1503b7c39fde8ef5c4672020307241958a880000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000c8bd020743550a6d27f0052d0037547db204e3fd752abf6758d899a3793fd3cd50c3073df6258c20a2f8e4797cbab700000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000fbe421858e4109c51de57b77da4f9c4c1f950099532d9e30e2f7a8b8b4fb9f708cde1a497050d0944e089978b15321e0000000000000000000000000000000019f48a0bf0f27df65ba766a65e831a0801a4ebcd1995a6002a803f88aead1503b7c39fde8ef5c4672020307241958a880000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b0000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000a42cfd1e09bd5fdf93d4ffec5a6b312a27dfb7b5e5238dc590158156b613c2d15de1e5a1a4ef2f6751e766874ea788d00000000000000000000000000000000000c87de488d68a3ef74410fe4c892cf62d25fb7d9ef6cbf3cb093184803e12066e86020225e3b9899decf8dbe6a20230000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000c8bd020743550a6d27f0052d0037547db204e3fd752abf6758d899a3793fd3cd50c3073df6258c20a2f8e4797cbab700000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000a42cfd1e09bd5fdf93d4ffec5a6b312a27dfb7b5e5238dc590158156b613c2d15de1e5a1a4ef2f6751e766874ea788d00000000000000000000000000000000000c87de488d68a3ef74410fe4c892cf62d25fb7d9ef6cbf3cb093184803e12066e86020225e3b9899decf8dbe6a20230000000000000000000000000000000002291ff240598e2c129ea12292e4a2fc86e03da9bd9fbbb8bddd6f25797003a4688ba2ed3bafd8dfcf0ddd44c3288c1e000000000000000000000000000000000d7541c9c54a95f3789ca7637348378f8956fd451c3266c8f1a34906bf1cf8e7499fcf8ad1f1a73dafcf71b86833ff3b0000000000000000000000000000000016aed55f56416b8f450283c4afea4c606100eed9bf7b8fea9ab4d04797a7bfe3bf0f10cf229f8ce3156869d75beabe6b0000000000000000000000000000000007e5c03e51a513c6f77179bcb5f7d147dcee32426b4365b1c95f434be7f83a5883d1ee5b0e01a636b3e5377542314b75000000000000000000000000000000000fbe421858e4109c51de57b77da4f9c4c1f950099532d9e30e2f7a8b8b4fb9f708cde1a497050d0944e089978b15321e0000000000000000000000000000000019f48a0bf0f27df65ba766a65e831a0801a4ebcd1995a6002a803f88aead1503b7c39fde8ef5c4672020307241958a88,0000000000000000000000000000000000000000000000000000000000000000,280000, +0000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff1480000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb00000000000000000000000000000000094a36d86483ac6f068017e4b978c7ea1ee58c429aad5994287f809c69fd5235532487d81f6a46ab827f2e0cb4c6df9e0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff1480000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa0000000000000000000000000000000007cdcfd000a86a408f39ef7cb0a4060dff8965956fbf6b939576a69674fcd5c735056da7988943506f8c35c2de8feaaf0000000000000000000000000000000003484fbf03d06907efbf3eff8b95789484254dc09e42208b7457619a31f795356a2cdfb24bb6e95c192b49a11c6fb9630000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb00000000000000000000000000000000094a36d86483ac6f068017e4b978c7ea1ee58c429aad5994287f809c69fd5235532487d81f6a46ab827f2e0cb4c6df9e0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa0000000000000000000000000000000007cdcfd000a86a408f39ef7cb0a4060dff8965956fbf6b939576a69674fcd5c735056da7988943506f8c35c2de8feaaf0000000000000000000000000000000003484fbf03d06907efbf3eff8b95789484254dc09e42208b7457619a31f795356a2cdfb24bb6e95c192b49a11c6fb9630000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff1480000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb00000000000000000000000000000000094a36d86483ac6f068017e4b978c7ea1ee58c429aad5994287f809c69fd5235532487d81f6a46ab827f2e0cb4c6df9e0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa000000000000000000000000000000001233421a38d77c59bbe1b83992a7a6c964ede5ef83c5a72bd1ba2c0a81b4205ce9a6925718cabcaf4a72ca3d216fbffc0000000000000000000000000000000016b8c22b35af7d925b5c68b6b7b63442e051fdc45542f233f2d97106c4b960eeb47f204c659d16a3a0d3b65ee38ff1480000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb0000000000000000000000000000000010b6db11d4fc3a2b449b8fd189d2e4ed4591bf4258d7b92b3eb152048cb3a3eecb87782691e9b954377fd1f34b38cb0d0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa0000000000000000000000000000000007cdcfd000a86a408f39ef7cb0a4060dff8965956fbf6b939576a69674fcd5c735056da7988943506f8c35c2de8feaaf0000000000000000000000000000000003484fbf03d06907efbf3eff8b95789484254dc09e42208b7457619a31f795356a2cdfb24bb6e95c192b49a11c6fb9630000000000000000000000000000000018d31bd5a7e94ceb18d803969a2001c6eb3bfbcf82c27e88ca60d4c46807d12f116ca71c67d27270c2332205a4ea11bb00000000000000000000000000000000094a36d86483ac6f068017e4b978c7ea1ee58c429aad5994287f809c69fd5235532487d81f6a46ab827f2e0cb4c6df9e0000000000000000000000000000000016114be17b400ba35875d9009b4d8974023a57d32508c9f658a0d82a8efc6b379ce4a3dbf5ca7130c5581f5008806934000000000000000000000000000000000c68cd7b9d3c3d6c559fa3d52da48ebe68e40a44863c332bb90dd151d1281dd3faa34e6c7b07c277affbdbc1b0a43cfa0000000000000000000000000000000007cdcfd000a86a408f39ef7cb0a4060dff8965956fbf6b939576a69674fcd5c735056da7988943506f8c35c2de8feaaf0000000000000000000000000000000003484fbf03d06907efbf3eff8b95789484254dc09e42208b7457619a31f795356a2cdfb24bb6e95c192b49a11c6fb963,0000000000000000000000000000000000000000000000000000000000000001,409000, +00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d5000000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000db912ff1f62be087194f6503b3b273b48bd0907afde777109522329e54cde1092afd48366af3f334c0df42ee98d8d5b00000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d5000000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b56000000000000000000000000000000000337fa3e53480c7865182ecbc7252aa6f9987685dbb814182447183d9da514732157ccffa4188d31eee96bc89c33f3f00000000000000000000000000000000004c5340a5240c4d6f1e095290ac5b6b5d121c5cadc6f30e5dd4855a9cf985e357b1a847cc060bd86aaef85ccf35ee41c00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000db912ff1f62be087194f6503b3b273b48bd0907afde777109522329e54cde1092afd48366af3f334c0df42ee98d8d5b00000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b56000000000000000000000000000000000337fa3e53480c7865182ecbc7252aa6f9987685dbb814182447183d9da514732157ccffa4188d31eee96bc89c33f3f00000000000000000000000000000000004c5340a5240c4d6f1e095290ac5b6b5d121c5cadc6f30e5dd4855a9cf985e357b1a847cc060bd86aaef85ccf35ee41c00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d5000000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000db912ff1f62be087194f6503b3b273b48bd0907afde777109522329e54cde1092afd48366af3f334c0df42ee98d8d5b00000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b560000000000000000000000000000000016c917abe637da21e60378ea7c2682306aded4ff17ccfea742e9ba63590be1b0fd5432ff0d3b72cdcb15943763cbb6bb00000000000000000000000000000000153bdddfe73f21c3593b128d3885f621935585ba1715e1d989e87cf7271897eea3917b81f0f342790f0f7a330ca0c68f00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000c47feeb1a1d2891d986b1660810859c1bba427d43a69b4e5ddeaf77116418138bfc2b7b4aa4c0cc6df10bd116721d5000000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b56000000000000000000000000000000000337fa3e53480c7865182ecbc7252aa6f9987685dbb814182447183d9da514732157ccffa4188d31eee96bc89c33f3f00000000000000000000000000000000004c5340a5240c4d6f1e095290ac5b6b5d121c5cadc6f30e5dd4855a9cf985e357b1a847cc060bd86aaef85ccf35ee41c00000000000000000000000000000000190f4dc14439eccc46d46c5c9b15eeba0bbf2dbca11af4183408afdb15c7bfa26f107cf5fda0c1e0236aab95728eac2e000000000000000000000000000000000db912ff1f62be087194f6503b3b273b48bd0907afde777109522329e54cde1092afd48366af3f334c0df42ee98d8d5b00000000000000000000000000000000135b96feb4f1e712661ce0d13842de1198c589f335141ab1fd7ffc6b9d58de82c300e9fe6dacdefe8e68b6db9298da5100000000000000000000000000000000046a3563d167d8b0a9f74e0c6514fdabd795110cf48caa014947ca90a9eeda3d07dd7dce58d3f2b7b86fab1143946b56000000000000000000000000000000000337fa3e53480c7865182ecbc7252aa6f9987685dbb814182447183d9da514732157ccffa4188d31eee96bc89c33f3f00000000000000000000000000000000004c5340a5240c4d6f1e095290ac5b6b5d121c5cadc6f30e5dd4855a9cf985e357b1a847cc060bd86aaef85ccf35ee41c,0000000000000000000000000000000000000000000000000000000000000001,409000, +00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d800000000000000000000000000000000013d98eb6ddf8b68db36819b25d9a7b4a4ed2b1d2593dd6a6e79dc6adaaefd4d8d129d8d949c7421641374a5192b3fd5a00000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000f62f8cda209f1223a7695ed860de2c2b144bd6402ecd61838eded3f40d3df90fe10bd5d92245112e3ce822cb33f8d4b0000000000000000000000000000000018bb0bcf262b7f4583d1375ecce64bd6bb1fcc64fa4b6a93bd9ffbe870fe79df0f29baa92eb844e5c04d09c966e810dc00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d800000000000000000000000000000000013d98eb6ddf8b68db36819b25d9a7b4a4ed2b1d2593dd6a6e79dc6adaaefd4d8d129d8d949c7421641374a5192b3fd5a00000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000f62f8cda209f1223a7695ed860de2c2b144bd6402ecd61838eded3f40d3df90fe10bd5d92245112e3ce822cb33f8d4b0000000000000000000000000000000018bb0bcf262b7f4583d1375ecce64bd6bb1fcc64fa4b6a93bd9ffbe870fe79df0f29baa92eb844e5c04d09c966e810dc00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d800000000000000000000000000000000013d98eb6ddf8b68db36819b25d9a7b4a4ed2b1d2593dd6a6e79dc6adaaefd4d8d129d8d949c7421641374a5192b3fd5a00000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000a9e191c9775f57810a511c8bd3dca14b3328e20f0983ca72e42e561b5dd1693209b42a11f2faeecd6307dd34cc01d60000000000000000000000000000000000146061b13546754c74a705776656100a9577f1ff939a82ba990d6b885b27c450f824555829bbb19f9b1f636991799cf00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d8000000000000000000000000000000000062783335b87300c97b38e03e5b1318d15a499b29a473c187f930bf34bc1214b4d822725678cbde978c7b5ae6d4bad5100000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000f62f8cda209f1223a7695ed860de2c2b144bd6402ecd61838eded3f40d3df90fe10bd5d92245112e3ce822cb33f8d4b0000000000000000000000000000000018bb0bcf262b7f4583d1375ecce64bd6bb1fcc64fa4b6a93bd9ffbe870fe79df0f29baa92eb844e5c04d09c966e810dc00000000000000000000000000000000021203675e0ae188ec782160e21492a6ee39fa97d922c1ef9bbfd79b82b3fad54fab11ba633fb8f02cf92249d85d9d800000000000000000000000000000000013d98eb6ddf8b68db36819b25d9a7b4a4ed2b1d2593dd6a6e79dc6adaaefd4d8d129d8d949c7421641374a5192b3fd5a00000000000000000000000000000000117821e6c87bb0e04882e95d36dce18ca33a2c8bd0efd5532b33d597804c08ff1799b2d64a95cc84bd31ba45c3b1e822000000000000000000000000000000000887c07c8a9ebe3154950746a4506ff192bb4a05dccb0f4a1a8ac2b8ca0da07190129ba44d9bc8e6c2666027c67d2ddc000000000000000000000000000000000f62f8cda209f1223a7695ed860de2c2b144bd6402ecd61838eded3f40d3df90fe10bd5d92245112e3ce822cb33f8d4b0000000000000000000000000000000018bb0bcf262b7f4583d1375ecce64bd6bb1fcc64fa4b6a93bd9ffbe870fe79df0f29baa92eb844e5c04d09c966e810dc,0000000000000000000000000000000000000000000000000000000000000001,409000, +000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a800000000000000000000000000000000064be06bf988929a026a0ac78603eb822b9f6048ff829083cafc465aabb5e623509c8159ef889974c43634088195185a0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000b6a1b64528770227d79763e494d2d060d50a0530eacb8684147954b6ad194e0a0efd35ff457956b499f58f2177528ee00000000000000000000000000000000048431899516d3d0b8c327d80596e68cf41c94739c6e0fa7ef196332539f2aeeef71890a2db81b9a358e1b4f467a5b34000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a800000000000000000000000000000000064be06bf988929a026a0ac78603eb822b9f6048ff829083cafc465aabb5e623509c8159ef889974c43634088195185a0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000b6a1b64528770227d79763e494d2d060d50a0530eacb8684147954b6ad194e0a0efd35ff457956b499f58f2177528ee00000000000000000000000000000000048431899516d3d0b8c327d80596e68cf41c94739c6e0fa7ef196332539f2aeeef71890a2db81b9a358e1b4f467a5b34000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a800000000000000000000000000000000064be06bf988929a026a0ac78603eb822b9f6048ff829083cafc465aabb5e623509c8159ef889974c43634088195185a0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000e96f685e6f87677cda23177f9fe7fd15726ab31e4d85a5725e93d558bdf61437dbc2c9ebcfc6a94705fa70de88a81bd00000000000000000000000000000000157ce060a46912c992587fde3db4c64a705ab7115717031778176f6ea311cb352f3a76f4839be4658470e4b0b9854f77000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a80000000000000000000000000000000013b5317e3ff7540048b19ceebd47c15538d7eb3bf402823b9c348c464afb1000ce0f7ea4c1cb668af5c8cbf77e6a92510000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000b6a1b64528770227d79763e494d2d060d50a0530eacb8684147954b6ad194e0a0efd35ff457956b499f58f2177528ee00000000000000000000000000000000048431899516d3d0b8c327d80596e68cf41c94739c6e0fa7ef196332539f2aeeef71890a2db81b9a358e1b4f467a5b34000000000000000000000000000000000e4979375cd880e26d00461de629bac880c12e24ede4a7c702f151c34a728a69a021e37b6a1af520a5f47d3a33f8c8a800000000000000000000000000000000064be06bf988929a026a0ac78603eb822b9f6048ff829083cafc465aabb5e623509c8159ef889974c43634088195185a0000000000000000000000000000000011f9a369401d2c376c77b4b414e345e6108b11594b26521b51afe6318648af232bf9f1455a99dc2f9b0207cc78339510000000000000000000000000000000000863492499f4791e71bd8d58dd2444a34e66dd3e3ca1cb3669f4182fafc9ef080a1d8111b3dd754f2405032350732b32000000000000000000000000000000000b6a1b64528770227d79763e494d2d060d50a0530eacb8684147954b6ad194e0a0efd35ff457956b499f58f2177528ee00000000000000000000000000000000048431899516d3d0b8c327d80596e68cf41c94739c6e0fa7ef196332539f2aeeef71890a2db81b9a358e1b4f467a5b34,0000000000000000000000000000000000000000000000000000000000000001,409000, +0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f60000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee410000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000f20033541ee3c68655e2c49f5e2fc8afd33255764267e55b3985790d6bb531db7171fa81caae98449ae3c6bb49225b50000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee410000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f60000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e000000000000000000000000000000000400e7e4eda0a9c13465af099ece14d8b30662fea5e222c6ab71b8fb44562dcc42c5255319741ea56b7cbaa2eab957c9000000000000000000000000000000000b04a27de02c7e71bbc51fcf3268b1eb734b754ae6e1c86ceb2ae0c7d0b40851e24dd497a93abf96168f7a705aeebc6a0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000f20033541ee3c68655e2c49f5e2fc8afd33255764267e55b3985790d6bb531db7171fa81caae98449ae3c6bb49225b50000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e000000000000000000000000000000000400e7e4eda0a9c13465af099ece14d8b30662fea5e222c6ab71b8fb44562dcc42c5255319741ea56b7cbaa2eab957c9000000000000000000000000000000000b04a27de02c7e71bbc51fcf3268b1eb734b754ae6e1c86ceb2ae0c7d0b40851e24dd497a93abf96168f7a705aeebc6a0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f60000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee410000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000f20033541ee3c68655e2c49f5e2fc8afd33255764267e55b3985790d6bb531db7171fa81caae98449ae3c6bb49225b50000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e0000000000000000000000000000000016002a054bdf3cd916b5f8aca47d97feb170e8864da2eff8bbbf19a5b25ac857dbe6daab97dfe15a4e82455d154652e2000000000000000000000000000000000efc6f6c595368288f5687e710e2faebf12bd63a0ca34a527c05f1d925fcedd23c5e2b6708194069a36f858fa510ee410000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000ae10eb4f791aa31e5bd7b6c4d68b04c6744262d8f5e9469b3987b101ff5a3066794e05694a9167b7050c3944b6d84f60000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e000000000000000000000000000000000400e7e4eda0a9c13465af099ece14d8b30662fea5e222c6ab71b8fb44562dcc42c5255319741ea56b7cbaa2eab957c9000000000000000000000000000000000b04a27de02c7e71bbc51fcf3268b1eb734b754ae6e1c86ceb2ae0c7d0b40851e24dd497a93abf96168f7a705aeebc6a0000000000000000000000000000000017f16cffb737dadd52b3c5be258733dc47301474b7351c8dcb8ddb4c519018be08b64efea3336f2b6cfa78e0669dccf9000000000000000000000000000000000f20033541ee3c68655e2c49f5e2fc8afd33255764267e55b3985790d6bb531db7171fa81caae98449ae3c6bb49225b50000000000000000000000000000000000a8382a5f73a7d15c3ee35e5fcaf7142e6d91d71ef30ce7da9c8db2f80c95441dc93674bed244096b71aea40d43c318000000000000000000000000000000000733e9a022695ed6908caf6ec7e67211c6d5ac16ba3fb8e244227f5da787e69e7311fac1e8d102a2d84e6ba98903ff6e000000000000000000000000000000000400e7e4eda0a9c13465af099ece14d8b30662fea5e222c6ab71b8fb44562dcc42c5255319741ea56b7cbaa2eab957c9000000000000000000000000000000000b04a27de02c7e71bbc51fcf3268b1eb734b754ae6e1c86ceb2ae0c7d0b40851e24dd497a93abf96168f7a705aeebc6a,0000000000000000000000000000000000000000000000000000000000000001,409000, +00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886200000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a6168542000000000000000000000000000000000352645e60bb10bc86d6c65a7b0d1dc290ff759c1c2e729a081d4b508b165b46b552ddbcd57a3546658a2aa53b8c224900000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886200000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a10000000000000000000000000000000005f8533875eac92050d86039e855238880b460eeed8c645abfa580b2789a478ddd98b5643be0a68cde274ac22b35b6ec000000000000000000000000000000000b94a5563380e67aa08e1baf868e36e8d3633c3d748cea822ad21f9d579aa1e774c41be88fdc58b61b2390c024fe466c00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a6168542000000000000000000000000000000000352645e60bb10bc86d6c65a7b0d1dc290ff759c1c2e729a081d4b508b165b46b552ddbcd57a3546658a2aa53b8c224900000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a10000000000000000000000000000000005f8533875eac92050d86039e855238880b460eeed8c645abfa580b2789a478ddd98b5643be0a68cde274ac22b35b6ec000000000000000000000000000000000b94a5563380e67aa08e1baf868e36e8d3633c3d748cea822ad21f9d579aa1e774c41be88fdc58b61b2390c024fe466c00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886200000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a6168542000000000000000000000000000000000352645e60bb10bc86d6c65a7b0d1dc290ff759c1c2e729a081d4b508b165b46b552ddbcd57a3546658a2aa53b8c224900000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a1000000000000000000000000000000001408beb1c3951d79fa43477c5af6894ee3c2ea9605f8ae64a78b51ee7e16ae9641134a9a75735972dbd7b53dd4c9f3bf000000000000000000000000000000000e6c6c9405ff001faa8d8c06bcbd75ee91140f477ef8283d3c5eb3039f16543ca9e7e4162177a7499edb6f3fdb01643f00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a61685420000000000000000000000000000000016aead8bd8c4d5ddc444e15bc83e8f14d377d5e8d756a0255f1387506b9a9add69592241dbd9cab95474d55ac473886200000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a10000000000000000000000000000000005f8533875eac92050d86039e855238880b460eeed8c645abfa580b2789a478ddd98b5643be0a68cde274ac22b35b6ec000000000000000000000000000000000b94a5563380e67aa08e1baf868e36e8d3633c3d748cea822ad21f9d579aa1e774c41be88fdc58b61b2390c024fe466c00000000000000000000000000000000062168f0bfd29c44074430158708a1e3b6808bae633ce9506b32eb9124db1a0668d83f2076adffb568ccf289a6168542000000000000000000000000000000000352645e60bb10bc86d6c65a7b0d1dc290ff759c1c2e729a081d4b508b165b46b552ddbcd57a3546658a2aa53b8c224900000000000000000000000000000000050b449c2425926d961af37c4c88e671eac676a1f828def54b76dc04960d0222fb5832ed44c45d5fbb59549d9d24c236000000000000000000000000000000000c6e811987b30ed77c804e647f867186d425411e514e9bf31099cc0f695195729ae970766b2738a928e776511a44f8a10000000000000000000000000000000005f8533875eac92050d86039e855238880b460eeed8c645abfa580b2789a478ddd98b5643be0a68cde274ac22b35b6ec000000000000000000000000000000000b94a5563380e67aa08e1baf868e36e8d3633c3d748cea822ad21f9d579aa1e774c41be88fdc58b61b2390c024fe466c,0000000000000000000000000000000000000000000000000000000000000001,409000, +000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000000fc75b0eb2b6afed9d04e4c957ca64c2c595c1a00d295a23113cbb79f4e827b1ff0a40566039e32cd84024a9bd39fc3000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000001585d5f8db92696a596141237f5c78c524db68b482c469cca16c436c040d1d720387aafaa4282383c293d37eceb092d0000000000000000000000000000000013d1a7dfade2113a492ab236c090386e8d6d4ff5bf9ea02bfd80bd389d1b06fc72c00060d6fe3c74ac60775e1f45ae3f000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000000fc75b0eb2b6afed9d04e4c957ca64c2c595c1a00d295a23113cbb79f4e827b1ff0a40566039e32cd84024a9bd39fc3000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000001585d5f8db92696a596141237f5c78c524db68b482c469cca16c436c040d1d720387aafaa4282383c293d37eceb092d0000000000000000000000000000000013d1a7dfade2113a492ab236c090386e8d6d4ff5bf9ea02bfd80bd389d1b06fc72c00060d6fe3c74ac60775e1f45ae3f000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000000fc75b0eb2b6afed9d04e4c957ca64c2c595c1a00d295a23113cbb79f4e827b1ff0a40566039e32cd84024a9bd39fc3000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000018a8b48aabc6c003a58593a40b55e54b122994f9ab58cc229d1a0e6a3670244cfe73854f07117dc77dd5c2c81314a17e00000000000000000000000000000000062f6a0a8b9dd56001f0f57f82bb7468d709fb8f33e6729369b015685995ef27abebff9dda55c38b0d9e88a1e0b9fc6c000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000019049c394e547b9b714b5969adcf068b381def6af2b27d1d361d06e9576273a8febb5bf94b5061ccec7afdb5642c0ae8000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000001585d5f8db92696a596141237f5c78c524db68b482c469cca16c436c040d1d720387aafaa4282383c293d37eceb092d0000000000000000000000000000000013d1a7dfade2113a492ab236c090386e8d6d4ff5bf9ea02bfd80bd389d1b06fc72c00060d6fe3c74ac60775e1f45ae3f000000000000000000000000000000000c60b948942652a8214d8776b77a6c559ca77eb3a537b0a9abadc3058eac8c1d7840f091acd6c0056d5a71468a2b1ceb0000000000000000000000000000000000fc75b0eb2b6afed9d04e4c957ca64c2c595c1a00d295a23113cbb79f4e827b1ff0a40566039e32cd84024a9bd39fc3000000000000000000000000000000000a8679f08643ff1c4db54e58de15a4828fc80e3f9d80a932b26b49d5c13831b1dc5dc29af2e080eb08e71938e5010fc400000000000000000000000000000000110957f7e9f8e0806bb3d2a811b91c926feab046ef983495f3f768a6cc6e4a6d95bb92facb77d989e53ce5489aa64b3c0000000000000000000000000000000001585d5f8db92696a596141237f5c78c524db68b482c469cca16c436c040d1d720387aafaa4282383c293d37eceb092d0000000000000000000000000000000013d1a7dfade2113a492ab236c090386e8d6d4ff5bf9ea02bfd80bd389d1b06fc72c00060d6fe3c74ac60775e1f45ae3f,0000000000000000000000000000000000000000000000000000000000000001,409000, +0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb10000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000010093a3820fda13babfc82cc313c6e20c503af71d2c1940cb5b2c879da00bb5d3bfb3aa17c3bab75b99fd74a8b742b52000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb10000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000c7f7169a5067d4a6cf6c21254ce79f8b7b4ed0d0144107900749f2e0ead7c7cfc25c156a0f4cba09cf51b9d03a51e3f00000000000000000000000000000000185b5357fa6340abc3ae41a686a623684e425c1a6f85865a8a8be52a24d5ca7f975b6604571a5f603667ced874cf2afa0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000010093a3820fda13babfc82cc313c6e20c503af71d2c1940cb5b2c879da00bb5d3bfb3aa17c3bab75b99fd74a8b742b52000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000c7f7169a5067d4a6cf6c21254ce79f8b7b4ed0d0144107900749f2e0ead7c7cfc25c156a0f4cba09cf51b9d03a51e3f00000000000000000000000000000000185b5357fa6340abc3ae41a686a623684e425c1a6f85865a8a8be52a24d5ca7f975b6604571a5f603667ced874cf2afa0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb10000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000010093a3820fda13babfc82cc313c6e20c503af71d2c1940cb5b2c879da00bb5d3bfb3aa17c3bab75b99fd74a8b742b52000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000d81a0809479694fde24e5a3ee7d32deacc25e77f241024666bc3372e80379a722863ea8105f345f1d09e462fc5a8c6c0000000000000000000000000000000001a5be923f1ca5ee876d660fbca5896f1634ef6a83ff8c64dca4ed76d1db2ba4875099fa5a39a09f839731278b307fb10000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000009f7d7b21882455e9f1f24ea120f3eb69f739c1320c37eb2b17e0a271cb03ac6e2b0c55d3518548a005f28b5748b7f59000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000c7f7169a5067d4a6cf6c21254ce79f8b7b4ed0d0144107900749f2e0ead7c7cfc25c156a0f4cba09cf51b9d03a51e3f00000000000000000000000000000000185b5357fa6340abc3ae41a686a623684e425c1a6f85865a8a8be52a24d5ca7f975b6604571a5f603667ced874cf2afa0000000000000000000000000000000013fe38343072af8ef1d8247c3d46b4fd190086ceddfeb767787031368da6a6a6ae849cfc26a24ead499338e37fa337e30000000000000000000000000000000010093a3820fda13babfc82cc313c6e20c503af71d2c1940cb5b2c879da00bb5d3bfb3aa17c3bab75b99fd74a8b742b52000000000000000000000000000000000ba48cbd776dd03a5b69aed3a31b7d151a8d98cd9adc3b9987cf2ac94644a364ebf3d30cf31742e2152aeba0eebc9ceb0000000000000000000000000000000008793a44c730949a9e50e9439d579ff0991dfc49a67a29b1701989ab065e6e937b14ac1bbca5a3dbf79a61837ad18394000000000000000000000000000000000c7f7169a5067d4a6cf6c21254ce79f8b7b4ed0d0144107900749f2e0ead7c7cfc25c156a0f4cba09cf51b9d03a51e3f00000000000000000000000000000000185b5357fa6340abc3ae41a686a623684e425c1a6f85865a8a8be52a24d5ca7f975b6604571a5f603667ced874cf2afa,0000000000000000000000000000000000000000000000000000000000000001,409000, +0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000059a7b662af14e0d3c7016cbafedd42173501fc97199c07114f47acdabd930332af4dea84202253b42b6d947b33de27c0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b272000000000000000000000000000000001949a50c589ec63db98d39491100e8e407345f9b3874f3a28e9b77d2fc28bf31ef976841c4276cb669dc4f3cca42fffd0000000000000000000000000000000001cd05bfae784b11f1c102a7b0268fc480d19cd7c65a3583f4624fc0bc8aa3c97a4c164b3803bc6ccc4e5d5d928110cf0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000059a7b662af14e0d3c7016cbafedd42173501fc97199c07114f47acdabd930332af4dea84202253b42b6d947b33de27c0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b272000000000000000000000000000000001949a50c589ec63db98d39491100e8e407345f9b3874f3a28e9b77d2fc28bf31ef976841c4276cb669dc4f3cca42fffd0000000000000000000000000000000001cd05bfae784b11f1c102a7b0268fc480d19cd7c65a3583f4624fc0bc8aa3c97a4c164b3803bc6ccc4e5d5d928110cf0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000059a7b662af14e0d3c7016cbafedd42173501fc97199c07114f47acdabd930332af4dea84202253b42b6d947b33de27c0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b2720000000000000000000000000000000000b76cdde0e1205c918e6e6d324ac3f35d42ebe9bb101f1cd8955acdfa8836f22f1497bced2c93495022b0c335bcaaae0000000000000000000000000000000018340c2a8b079b88595aa50e93251d12e3a5aead2d2add3b72ce82e03a26525aa45fe9b379504392edb0a2a26d7e99dc0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000146696840e8e988d0eab90ea935dd8b5f1272bbb81eb524e523c57d34ad7c5f0f3b721566f51dac4774826b84cc1c82f0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b272000000000000000000000000000000001949a50c589ec63db98d39491100e8e407345f9b3874f3a28e9b77d2fc28bf31ef976841c4276cb669dc4f3cca42fffd0000000000000000000000000000000001cd05bfae784b11f1c102a7b0268fc480d19cd7c65a3583f4624fc0bc8aa3c97a4c164b3803bc6ccc4e5d5d928110cf0000000000000000000000000000000018c6df81d810deaac0b143edf79956c92af7941f7b279db345f838bd583177912fc2eb367616ae165e261014a4d7b1b900000000000000000000000000000000059a7b662af14e0d3c7016cbafedd42173501fc97199c07114f47acdabd930332af4dea84202253b42b6d947b33de27c0000000000000000000000000000000008691df5b245399f24118badfbef3e01a4acd53dc9ab149e407c733df6122fa91f5cbe2f9d247cdbac18b266d3d8f18300000000000000000000000000000000053e6eef4ffdbe239c8bbade8cfc90461d54f281ee6180c271412bf2d64e005d3f0291d3401c324e41067f4dfcc4b272000000000000000000000000000000001949a50c589ec63db98d39491100e8e407345f9b3874f3a28e9b77d2fc28bf31ef976841c4276cb669dc4f3cca42fffd0000000000000000000000000000000001cd05bfae784b11f1c102a7b0268fc480d19cd7c65a3583f4624fc0bc8aa3c97a4c164b3803bc6ccc4e5d5d928110cf,0000000000000000000000000000000000000000000000000000000000000001,409000, +000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab400000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d00000000000000000000000000000000102ba7f9db164318194ab17f615ca8cc741dab773e8609023c58a722f1e4f209eb4bc3cff7a2b71c08bdd421068b9ff700000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab400000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c66400000000000000000000000000000000082961f3752eb7324800bc217514792b2111abe52df54973615737b8ec3a9f2db36dc1782d20782df8efae4bd7b8559600000000000000000000000000000000075729f6b9337b3f25da9d33cdbed7207a589a342cee61e8e99e030244b814accc93b26a0ca6d9ba08acf29511ef15be000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d00000000000000000000000000000000102ba7f9db164318194ab17f615ca8cc741dab773e8609023c58a722f1e4f209eb4bc3cff7a2b71c08bdd421068b9ff700000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c66400000000000000000000000000000000082961f3752eb7324800bc217514792b2111abe52df54973615737b8ec3a9f2db36dc1782d20782df8efae4bd7b8559600000000000000000000000000000000075729f6b9337b3f25da9d33cdbed7207a589a342cee61e8e99e030244b814accc93b26a0ca6d9ba08acf29511ef15be000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab400000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d00000000000000000000000000000000102ba7f9db164318194ab17f615ca8cc741dab773e8609023c58a722f1e4f209eb4bc3cff7a2b71c08bdd421068b9ff700000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c6640000000000000000000000000000000011d7aff6c4512f68031aeb94ce3733ac43659f9fc58fc94c05d99ae80a7656f66b3e3e86843387d1c10f51b4284755150000000000000000000000000000000012a9e7f3804c6b5b25410a82758cd5b6ea1eb150c696b0d67d92cf9eb1f8e17752184d94a4ad2645b1520d6aee1094ed000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d0000000000000000000000000000000009d569f05e69a38231d0f636e1ef040af059a00db4ff09bd2ad82b7e04cc041a33603c2eb9b148e3b1412bdef9740ab400000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c66400000000000000000000000000000000082961f3752eb7324800bc217514792b2111abe52df54973615737b8ec3a9f2db36dc1782d20782df8efae4bd7b8559600000000000000000000000000000000075729f6b9337b3f25da9d33cdbed7207a589a342cee61e8e99e030244b814accc93b26a0ca6d9ba08acf29511ef15be000000000000000000000000000000000c6b634d90c2664b9fa4ccbca35913d23696825350e21f0a6dd5e9abb17497a0a499e1b7b928a57ba8c730158f63b75d00000000000000000000000000000000102ba7f9db164318194ab17f615ca8cc741dab773e8609023c58a722f1e4f209eb4bc3cff7a2b71c08bdd421068b9ff700000000000000000000000000000000042120affcefe4735ae25e192d1cf34e40afdc6d2ebdacde2e23d30709fecfb71960bc9131e3702b27b6fcd5c7a98d170000000000000000000000000000000001998caf5163b0dccec7c8423c4c56a7d0f0b26d9034f707ed07f636f42dac590a2674c1667d70be385c4e626815c66400000000000000000000000000000000082961f3752eb7324800bc217514792b2111abe52df54973615737b8ec3a9f2db36dc1782d20782df8efae4bd7b8559600000000000000000000000000000000075729f6b9337b3f25da9d33cdbed7207a589a342cee61e8e99e030244b814accc93b26a0ca6d9ba08acf29511ef15be,0000000000000000000000000000000000000000000000000000000000000001,409000, +0000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c50000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000b760253acb4c395332c1e3584f60d965a4b0b4f5274f457d05bdafb08546282829ae2c61e482a43136afa03ca0102e90000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c50000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca000000000000000000000000000000000144811cb59ebf7e5a380ca9c2b30dc987778224453ea65ab9fcc5ddd0a91a47aac13a459cf5ecc5bffc5f3c0502e8cc0000000000000000000000000000000007b4c5f3cf21e53b36ed096b1d0998c2be68f6977cbe3e12a63ec77c545316c556bce0a891a762b8af6a4304d0d911e60000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000b760253acb4c395332c1e3584f60d965a4b0b4f5274f457d05bdafb08546282829ae2c61e482a43136afa03ca0102e90000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca000000000000000000000000000000000144811cb59ebf7e5a380ca9c2b30dc987778224453ea65ab9fcc5ddd0a91a47aac13a459cf5ecc5bffc5f3c0502e8cc0000000000000000000000000000000007b4c5f3cf21e53b36ed096b1d0998c2be68f6977cbe3e12a63ec77c545316c556bce0a891a762b8af6a4304d0d911e60000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c50000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000b760253acb4c395332c1e3584f60d965a4b0b4f5274f457d05bdafb08546282829ae2c61e482a43136afa03ca0102e90000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca0000000000000000000000000000000018bc90cd83e1271bf0e39b0c80989f0ddcffc960ae466c64ad340cc32607dbdc73eac5b9145e1339fa02a0c3fafcc1df00000000000000000000000000000000124c4bf66a5e015f142e9e4b26421414a60e54ed76c6d4acc0f20b24a25ddf5ec7ef1f561fac9d470a94bcfb2f2698c50000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000e8b0f968ccb230517ef8980be559f410a2c4035a1101e6796d4f7a5ee5c93a19c111d38930bd5bca69405fc35fea7c20000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca000000000000000000000000000000000144811cb59ebf7e5a380ca9c2b30dc987778224453ea65ab9fcc5ddd0a91a47aac13a459cf5ecc5bffc5f3c0502e8cc0000000000000000000000000000000007b4c5f3cf21e53b36ed096b1d0998c2be68f6977cbe3e12a63ec77c545316c556bce0a891a762b8af6a4304d0d911e60000000000000000000000000000000018129b2f00be24717c906d215beaaa136758aa1730bd0bbe9c0de9b3cbb3c0ea47911817fa322b907cc6fc720cabde05000000000000000000000000000000000b760253acb4c395332c1e3584f60d965a4b0b4f5274f457d05bdafb08546282829ae2c61e482a43136afa03ca0102e90000000000000000000000000000000001462f8080d9b51235a8aa652445f509e3e13e3073769e9a047e8b2bfa5b227f4354bef017d18bf06f7ec98c169abf1e000000000000000000000000000000000070fdbc18112b49bd83f4347922797f2bbd68bf2592ad59041c97948ba7a091bdb3622c804803ad605604ba364dbdca000000000000000000000000000000000144811cb59ebf7e5a380ca9c2b30dc987778224453ea65ab9fcc5ddd0a91a47aac13a459cf5ecc5bffc5f3c0502e8cc0000000000000000000000000000000007b4c5f3cf21e53b36ed096b1d0998c2be68f6977cbe3e12a63ec77c545316c556bce0a891a762b8af6a4304d0d911e6,0000000000000000000000000000000000000000000000000000000000000001,409000, +000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a600000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a470000000000000000000000000000000000cff9184748200fc11245bb213f9d00c3eef7f4698174e9e7a1ff6cf072a30d5f28173aed5fbbdf46b444282225790500000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a600000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca2000000000000000000000000000000000207d4a04d23b1cc880275ea6075f929ea097e464b208c94bf7cb545c76add5a557e5fe08ce4070c77be430e21b38e660000000000000000000000000000000017e907545d9a6a5733fd81aeea0dd92221328dc5b2e745b3102a28f9cbe013b548a061b1ffd55b18059e523a5908d7cd000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a470000000000000000000000000000000000cff9184748200fc11245bb213f9d00c3eef7f4698174e9e7a1ff6cf072a30d5f28173aed5fbbdf46b444282225790500000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca2000000000000000000000000000000000207d4a04d23b1cc880275ea6075f929ea097e464b208c94bf7cb545c76add5a557e5fe08ce4070c77be430e21b38e660000000000000000000000000000000017e907545d9a6a5733fd81aeea0dd92221328dc5b2e745b3102a28f9cbe013b548a061b1ffd55b18059e523a5908d7cd000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a600000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a470000000000000000000000000000000000cff9184748200fc11245bb213f9d00c3eef7f4698174e9e7a1ff6cf072a30d5f28173aed5fbbdf46b444282225790500000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca20000000000000000000000000000000017f93d49ec5c34cdc31931cbe2d5b3ad7a6dcd3ea864862aa7b41d5b2f4618c9c92da01e246ff8f34240bcf1de4c1c450000000000000000000000000000000002180a95dbe57c43171e2607593dd3b54344bdbf409dcd0c5706a9a72ad0e26ed60b9e4cb17ea4e7b460adc5a6f6d2de000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a4700000000000000000000000000000000193118d1f237c68a8a0961fb220c0fd6a08853908a039dd57f8ed334063e5316bf83e8c3c3f44420734abbd7ddda31a600000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca2000000000000000000000000000000000207d4a04d23b1cc880275ea6075f929ea097e464b208c94bf7cb545c76add5a557e5fe08ce4070c77be430e21b38e660000000000000000000000000000000017e907545d9a6a5733fd81aeea0dd92221328dc5b2e745b3102a28f9cbe013b548a061b1ffd55b18059e523a5908d7cd000000000000000000000000000000001667fdc9b89d12fb0704fdec910cab1b51ac04219ef6e50f996688b2ceb26dca0e9e8594c5b81fca2e8fc2c8d8fa9a470000000000000000000000000000000000cff9184748200fc11245bb213f9d00c3eef7f4698174e9e7a1ff6cf072a30d5f28173aed5fbbdf46b444282225790500000000000000000000000000000000156901359e5b399168e90ccad27a054d147aa9c4a731294180e395e8e2d458f5537fdac591cdc82fd8bffa4c9fa126ed00000000000000000000000000000000143872757c0a25d85e95a86c5e09175fdbeaf59bae3d1e8a367902d59c662cc3a293ae252443b3201671ad1dbaed8ca2000000000000000000000000000000000207d4a04d23b1cc880275ea6075f929ea097e464b208c94bf7cb545c76add5a557e5fe08ce4070c77be430e21b38e660000000000000000000000000000000017e907545d9a6a5733fd81aeea0dd92221328dc5b2e745b3102a28f9cbe013b548a061b1ffd55b18059e523a5908d7cd,0000000000000000000000000000000000000000000000000000000000000001,409000, +000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000012feb2cdef2060f089c32a68f91d4ac9e0a1461cbf4bd1bf8ed26782a700052ee2fb73af689490ba12233c8dd133158d00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c0130000000000000000000000000000000016b19dd160140ab5592e4e1f46ad0e3e413ceed148adfb0bf5b240a161b22b7dac5b45a389770a634bc8551f72dd12710000000000000000000000000000000009f439fffd4bbbf789bd0b5521a9dcea6e66282a167ce9b26d6543fba82101003d31f4a0ed3592f820d0a6d81c004954000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000012feb2cdef2060f089c32a68f91d4ac9e0a1461cbf4bd1bf8ed26782a700052ee2fb73af689490ba12233c8dd133158d00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c0130000000000000000000000000000000016b19dd160140ab5592e4e1f46ad0e3e413ceed148adfb0bf5b240a161b22b7dac5b45a389770a634bc8551f72dd12710000000000000000000000000000000009f439fffd4bbbf789bd0b5521a9dcea6e66282a167ce9b26d6543fba82101003d31f4a0ed3592f820d0a6d81c004954000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000012feb2cdef2060f089c32a68f91d4ac9e0a1461cbf4bd1bf8ed26782a700052ee2fb73af689490ba12233c8dd133158d00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c01300000000000000000000000000000000034f7418d96bdbe4f1ed5996fc9e9e99233a5cb3aad717b3717e91ff94fecaa67250ba5b27dcf59c6e36aae08d22983a00000000000000000000000000000000100cd7ea3c342aa2c15e9c6121a1cfecf611235add08290cf9cb8ea54e8ff523e17a0b5dc41e6d07992e5927e3ff6157000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000007025f1c4a5f85a9c1587d4d4a2e620d83d60568343940ffd85e6b1e4fb0f0f53bb08c4f48bf6f45a7dbc3722ecc951e00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c0130000000000000000000000000000000016b19dd160140ab5592e4e1f46ad0e3e413ceed148adfb0bf5b240a161b22b7dac5b45a389770a634bc8551f72dd12710000000000000000000000000000000009f439fffd4bbbf789bd0b5521a9dcea6e66282a167ce9b26d6543fba82101003d31f4a0ed3592f820d0a6d81c004954000000000000000000000000000000000217a4c563d730ef545e452038813301933ccc6638321ee5e217dad0be2e3ddc855a14054d0d72b6bcc692a5fb1ac7300000000000000000000000000000000012feb2cdef2060f089c32a68f91d4ac9e0a1461cbf4bd1bf8ed26782a700052ee2fb73af689490ba12233c8dd133158d00000000000000000000000000000000162ea8f985c83d59361ee6beb49cf2a797d8c909e2eccfc61fdc5359d5ac9b10fbaeef2eebea1667b5b9bf8f5d603d6e0000000000000000000000000000000018344ca9d4913e817264ed8119fe4d136f2041b0a99d4b5fe7f2b7f268256eec9fceb27fa61c4225f47babd17759c0130000000000000000000000000000000016b19dd160140ab5592e4e1f46ad0e3e413ceed148adfb0bf5b240a161b22b7dac5b45a389770a634bc8551f72dd12710000000000000000000000000000000009f439fffd4bbbf789bd0b5521a9dcea6e66282a167ce9b26d6543fba82101003d31f4a0ed3592f820d0a6d81c004954,0000000000000000000000000000000000000000000000000000000000000001,409000, +0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc00000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000c28402cd28b39ce814adfdb8453fd646f5ae3e41d718e5af1fd250e3b0cabf2efa01f045f3dce88c84f0b19b3fefbb00000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc00000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000006a9c650ba974e0fa2fdf6d3659220f47d76f581ec156662b4e9dc4470164e68df977370d2bcf1cad4191031fdc1476f000000000000000000000000000000001068554cf7ba1173150be2cfb7ab4503ecea55b5f29f7d24086ba68b610637b5f0192bf1fe04557b68c1eafa9736daeb0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000c28402cd28b39ce814adfdb8453fd646f5ae3e41d718e5af1fd250e3b0cabf2efa01f045f3dce88c84f0b19b3fefbb00000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000006a9c650ba974e0fa2fdf6d3659220f47d76f581ec156662b4e9dc4470164e68df977370d2bcf1cad4191031fdc1476f000000000000000000000000000000001068554cf7ba1173150be2cfb7ab4503ecea55b5f29f7d24086ba68b610637b5f0192bf1fe04557b68c1eafa9736daeb0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc00000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000c28402cd28b39ce814adfdb8453fd646f5ae3e41d718e5af1fd250e3b0cabf2efa01f045f3dce88c84f0b19b3fefbb00000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000013574b997ee8988aa81db0e2ddb98be2e7005603076fac5cb246f65c869aa7bb3f148c8dde970e34e5e5efce023e633c000000000000000000000000000000000998bc9d41c5d527360fc4e68ba067d3778cf5cf00e5959b5ec52c1595aabe6e2e92d40cb34faa84513d150568c8cfc00000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000dd8d1bd66f4accbc9d0c7dabef7af72f51c67a0d61384647533ad92bba44a312f0be0fa52163176f1aff4e64c00aefb0000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000006a9c650ba974e0fa2fdf6d3659220f47d76f581ec156662b4e9dc4470164e68df977370d2bcf1cad4191031fdc1476f000000000000000000000000000000001068554cf7ba1173150be2cfb7ab4503ecea55b5f29f7d24086ba68b610637b5f0192bf1fe04557b68c1eafa9736daeb0000000000000000000000000000000009ec00ea2da59d937d3154d86dbed2957667253401bce9de80e0ffe6df32f36b06404b9e3af08e912a0b4ef091f93efb000000000000000000000000000000000c28402cd28b39ce814adfdb8453fd646f5ae3e41d718e5af1fd250e3b0cabf2efa01f045f3dce88c84f0b19b3fefbb00000000000000000000000000000000001cdfae9234096578b9413f926ef8c6831f2c0f700e25d7553a746aef44238e493f8032e09f67f2fed9676c9611f60e70000000000000000000000000000000019c8bae08d3926997146f7827f00cde863684dd4050ea5da64f6798e7a930d3c1f34046bea0f44232594f5469db566280000000000000000000000000000000006a9c650ba974e0fa2fdf6d3659220f47d76f581ec156662b4e9dc4470164e68df977370d2bcf1cad4191031fdc1476f000000000000000000000000000000001068554cf7ba1173150be2cfb7ab4503ecea55b5f29f7d24086ba68b610637b5f0192bf1fe04557b68c1eafa9736daeb,0000000000000000000000000000000000000000000000000000000000000001,409000, +0000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c3585000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc012040000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec00000000000000000000000000000000003b90ede51e98dd9163b911431789b534aef452b9bd1b423a5d8c2ea1652cd05aa308568a7031d958fc2f32e9cb37526000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc012040000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c3585000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000011396b6eafe9d8f61a831ef9d6688e586602c5138ddc65d1bf69a9916c1e8db31ddf432b1406a597c7dfb49c1339727900000000000000000000000000000000183398716b5783fb7971e27306f651b8a91efc0462ef799742c8eaeeaf919d08348e8c1700b1b850e220b0e0133f98a70000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec00000000000000000000000000000000003b90ede51e98dd9163b911431789b534aef452b9bd1b423a5d8c2ea1652cd05aa308568a7031d958fc2f32e9cb37526000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000011396b6eafe9d8f61a831ef9d6688e586602c5138ddc65d1bf69a9916c1e8db31ddf432b1406a597c7dfb49c1339727900000000000000000000000000000000183398716b5783fb7971e27306f651b8a91efc0462ef799742c8eaeeaf919d08348e8c1700b1b850e220b0e0133f98a70000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c3585000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc012040000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec00000000000000000000000000000000003b90ede51e98dd9163b911431789b534aef452b9bd1b423a5d8c2ea1652cd05aa308568a7031d958fc2f32e9cb37526000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000008c7a67b89960da4309888bc6ce31e7efe74867165a8aceda7c7290f8a92687100ccbcd39d4d5a67f21f4b63ecc638320000000000000000000000000000000001cd7978ce28629ed1a9c5433c555b1ebb584f80909599282467e7b2471f591bea1d73e7b0a247aed7de4f1fecc012040000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec0000000000000000000000000000000001648030be79658c134e016a211d311841988065957b35e9bc1580fb6e05e291e747b7a960a50e26a2a3c0cd1634c3585000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000011396b6eafe9d8f61a831ef9d6688e586602c5138ddc65d1bf69a9916c1e8db31ddf432b1406a597c7dfb49c1339727900000000000000000000000000000000183398716b5783fb7971e27306f651b8a91efc0462ef799742c8eaeeaf919d08348e8c1700b1b850e220b0e0133f98a70000000000000000000000000000000014153e01c9e495c5c01c82b3cad9eaf20cf78369ccbabf57fb160ded309cbd1caea3d3df38a7ea5490c67f168e9acec00000000000000000000000000000000003b90ede51e98dd9163b911431789b534aef452b9bd1b423a5d8c2ea1652cd05aa308568a7031d958fc2f32e9cb37526000000000000000000000000000000000c78d84157dc0b102c3843e4c8e88f244cc1b2a27043e07b2fab694a58f93d47e4cf9ca1158a8e30e3d43f94a20d33b50000000000000000000000000000000004842fe0df312f735a9d8af0c2ff7c561ed9cf4add5e3e9402bcff1190f3f36ca91de8edc9472b3ebd27ee2d9afdf8770000000000000000000000000000000011396b6eafe9d8f61a831ef9d6688e586602c5138ddc65d1bf69a9916c1e8db31ddf432b1406a597c7dfb49c1339727900000000000000000000000000000000183398716b5783fb7971e27306f651b8a91efc0462ef799742c8eaeeaf919d08348e8c1700b1b850e220b0e0133f98a7,0000000000000000000000000000000000000000000000000000000000000001,409000, +000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d57950000000000000000000000000000000005bd0ff24e15f0682c6d1a09096fca081991bd3f9f10a2a18d3f1c7470e9a2bc0ac3b149b7750aedce9c1ae6bd773820000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd00000000000000000000000000000000189be781abc010602e9262930d8dfdb2d7df81be0de1656554cb5afa3d059f1cc389678008ea84ba23ed5a54e9b07827000000000000000000000000000000001476dab5bd29af19c4e8f947b4255e4b86625fd4451b902fd10180e9ce7ed639c6e65683fabf0824a2a00185e82c3df5000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d57950000000000000000000000000000000005bd0ff24e15f0682c6d1a09096fca081991bd3f9f10a2a18d3f1c7470e9a2bc0ac3b149b7750aedce9c1ae6bd773820000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd00000000000000000000000000000000189be781abc010602e9262930d8dfdb2d7df81be0de1656554cb5afa3d059f1cc389678008ea84ba23ed5a54e9b07827000000000000000000000000000000001476dab5bd29af19c4e8f947b4255e4b86625fd4451b902fd10180e9ce7ed639c6e65683fabf0824a2a00185e82c3df5000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d57950000000000000000000000000000000005bd0ff24e15f0682c6d1a09096fca081991bd3f9f10a2a18d3f1c7470e9a2bc0ac3b149b7750aedce9c1ae6bd773820000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd0000000000000000000000000000000001652a688dbfd63a1c89452335bdaf248c97c9c6e5a3ad5a126577a6b9ab57075b22987ea8697b459611a5ab164f328400000000000000000000000000000000058a37347c5637808632ae6e8f264e8bde14ebb0ae69828f962f51b728321fea57c5a97ab694f7db175efe7a17d36cb6000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d579500000000000000000000000000000000144401f7eb69f6321eae8dad39dbe2cf4ae58e455474701dd9f1b62c85c7536813e84eb4f9def511eb62e5194288728b000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd00000000000000000000000000000000189be781abc010602e9262930d8dfdb2d7df81be0de1656554cb5afa3d059f1cc389678008ea84ba23ed5a54e9b07827000000000000000000000000000000001476dab5bd29af19c4e8f947b4255e4b86625fd4451b902fd10180e9ce7ed639c6e65683fabf0824a2a00185e82c3df5000000000000000000000000000000001555535228eb9a24f460df9894d59aa06fc848a8bf8d6c3b51653b1d85734b3c5a2bece161309bd478d356fa198d57950000000000000000000000000000000005bd0ff24e15f0682c6d1a09096fca081991bd3f9f10a2a18d3f1c7470e9a2bc0ac3b149b7750aedce9c1ae6bd773820000000000000000000000000000000000e619d79792ac685030311a31a21203e5172d2e5d20ecf69a1e64158e7fe903b3695fd15432d3ca35562b5a8bd9cbdc20000000000000000000000000000000012394a621a503d1d92df3306649a6c6979816cabeb8f8d27450ec883c4e75f6f7411f3bfd068dc8dee58cdb8ebbd91bd00000000000000000000000000000000189be781abc010602e9262930d8dfdb2d7df81be0de1656554cb5afa3d059f1cc389678008ea84ba23ed5a54e9b07827000000000000000000000000000000001476dab5bd29af19c4e8f947b4255e4b86625fd4451b902fd10180e9ce7ed639c6e65683fabf0824a2a00185e82c3df5,0000000000000000000000000000000000000000000000000000000000000001,409000, +00000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1d9f0000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40,,115000,invalid input parameters, invalid number of pairs +000000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1d9f0000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40,,108000,invalid input parameters, invalid input length for pairing +,,115000,invalid input parameters, invalid number of pairs +0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f570000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1d9f0000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40,,108000,invalid point: point is not on curve +0000000000000000000000000000000012196c5a43d69224d8713389285f26b98f86ee910ab3dd668e413738282003cc5b7357af9a7af54bb713d62255e80f560000000000000000000000000000000006ba8102bfbeea4416b710c73e8cce3032c31c6269c44906f8ac4f7874ce99fb17559992486528963884ce429a992fee0000000000000000000000000000000017c9fcf0504e62d3553b2f089b64574150aa5117bd3d2e89a8c1ed59bb7f70fb83215975ef31976e757abf60a75a1da00000000000000000000000000000000008f5a53d704298fe0cfc955e020442874fe87d5c729c7126abbdcbed355eef6c8f07277bee6d49d56c4ebaf334848624000000000000000000000000000000001302dcc50c6ce4c28086f8e1b43f9f65543cf598be440123816765ab6bc93f62bceda80045fbcad8598d4f32d03ee8fa000000000000000000000000000000000bbb4eb37628d60b035a3e0c45c0ea8c4abef5a6ddc5625e0560097ef9caab208221062e81cd77ef72162923a1906a40,,108000,invalid point: point is not on curve +000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80000000000000000000000000000000000874389c02d4cf1c61bc54c4c24def11dfbe7880bc998a95e70063009451ee8226fec4b278aade3a7cea55659459f1d500000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79e000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd9000000000000000000000000000000000118cd94e36ab177de95f52f180fdbdc584b8d30436eb882980306fa0625f07a1f7ad3b4c38a921c53d14aa9a6ba5b8d600000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79e000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e,,151000,invalid point: point is not on curve +000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd80000000000000000000000000000000000874389c02d4cf1c61bc54c4c24def11dfbe7880bc998a95e70063009451ee8226fec4b278aade3a7cea55659459f1d500000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79e000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e000000000000000000000000000000001830f52d9bff64a623c6f5259e2cd2c2a08ea17a8797aaf83174ea1e8c3bd3955c2af1d39bfa474815bfe60714b7cd8000000000000000000000000000000000118cd94e36ab177de95f52f180fdbdc584b8d30436eb882980306fa0625f07a1f7ad3b4c38a921c53d14aa9a6ba5b8d600000000000000000000000000000000197737f831d4dc7e708475f4ca7ca15284db2f3751fcaac0c17f517f1ddab35e1a37907d7b99b39d6c8d9001cd50e79f000000000000000000000000000000000af1a3f6396f0c983e7c2d42d489a3ae5a3ff0a553d93154f73ac770cd0af7467aa0cef79f10bbd34621b3ec9583a834000000000000000000000000000000001918cb6e448ed69fb906145de3f11455ee0359d030e90d673ce050a360d796de33ccd6a941c49a1414aca1c26f9e699e0000000000000000000000000000000019a915154a13249d784093facc44520e7f3a18410ab2a3093e0b12657788e9419eec25729944f7945e732104939e7a9e,,151000,invalid point: point is not on curve diff --git a/gradle.properties b/gradle.properties index df2f0da93de..93f56e01379 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,9 @@ -version=23.10.3-SNAPSHOT - org.gradle.welcome=never + +# Optional - set custom build version +# version=24.5.6-acme +# versionappendcommit=true + # Set exports/opens flags required by Google Java Format and ErrorProne plugins. (JEP-396) org.gradle.jvmargs=-Xmx4g \ --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ @@ -14,3 +17,5 @@ org.gradle.jvmargs=-Xmx4g \ --add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED \ --add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED \ --add-opens jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED +# Could be moved to sonar properties after https://sonarsource.atlassian.net/browse/SONARGRADL-134 +systemProp.sonar.gradle.skipCompile=true \ No newline at end of file diff --git a/gradle/allowed-licenses.json b/gradle/allowed-licenses.json new file mode 100644 index 00000000000..f7a3d8be8d4 --- /dev/null +++ b/gradle/allowed-licenses.json @@ -0,0 +1,74 @@ +{ + "allowedLicenses": [ + { + "moduleLicense": "Apache License, Version 2.0", + "moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0" + }, + { + "moduleLicense": "BSD Zero Clause License", + "moduleLicenseUrl": "https://opensource.org/licenses/0BSD" + }, + { + "moduleLicense" : "The 2-Clause BSD License", + "moduleLicenseUrl" : "https://opensource.org/licenses/BSD-2-Clause" + }, + { + "moduleLicense": "The 3-Clause BSD License", + "moduleLicenseUrl": "https://opensource.org/licenses/BSD-3-Clause" + }, + { + "moduleLicense": "Bouncy Castle Licence", + "moduleLicenseUrl": "https://www.bouncycastle.org/licence.html" + }, + { + "moduleLicense": "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0", + "moduleLicenseUrl": "https://oss.oracle.com/licenses/CDDL" + }, + { + "moduleLicense" : "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.1", + "moduleLicenseUrl" : "https://oss.oracle.com/licenses/CDDL-1.1" + }, + { + "moduleLicense": "Eclipse Distribution License - v 1.0", + "moduleLicenseUrl": "http://www.eclipse.org/legal/epl-v10.html" + }, + { + "moduleLicense": "Eclipse Public License - v 2.0", + "moduleLicenseUrl": "https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt" + }, + { + "moduleLicense": "MIT License", + "moduleLicenseUrl": "https://opensource.org/licenses/MIT" + }, + { + "moduleLicense": "CC0-1.0", + "moduleLicenseUrl": "https://creativecommons.org/publicdomain/zero/1.0/legalcode" + }, + { + "moduleLicense": "Public-Domain" + }, + { + "moduleLicense": "Unicode/ICU License", + "moduleLicenseUrl": "https://raw.githubusercontent.com/unicode-org/icu/main/icu4c/LICENSE" + }, + { + "moduleLicense": "Creative Commons Legal Code", + "moduleVersion": "1.0.3", + "moduleName": "org.reactivestreams:reactive-streams" + }, + { + "moduleLicense": "MIT-0", + "moduleVersion": "1.0.4", + "moduleName": "org.reactivestreams:reactive-streams" + }, + { + "moduleLicense": "Eclipse Public License - v 1.0", + "moduleVersion": "4.13.2", + "moduleName": "junit:junit" + }, + { + "moduleName": "org.jetbrains.kotlin:kotlin-stdlib-common", + "moduleVersion": "1.9.22" + } + ] +} \ No newline at end of file diff --git a/gradle/check-licenses.gradle b/gradle/check-licenses.gradle deleted file mode 100644 index 3939c191166..00000000000 --- a/gradle/check-licenses.gradle +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * Check that the licenses of our 3rd parties are in our acceptedLicenses list. - * - * run it with "gradle checkLicenses" - * - * To add new accepted licenses you need to update this script. - * Some products may be available with multiple licenses. In this case you must update - * this script to add it in the downloadLicenses#licenses. - */ - -// Some parts of this code comes from Zipkin/https://github.com/openzipkin/zipkin/pull/852 -// Zipkin itself is under Apache License. - -/** - * The lists of the license we accept. - */ -ext.acceptedLicenses = [ - 'Apache License, Version 2.0', - 'BSD 3-Clause', - 'BSD License', - 'Bouncy Castle Licence', - 'CC0 1.0 Universal License', - 'Common Development and Distribution License 1.0', - 'Eclipse Public License 1.0', - 'Eclipse Public License - v 2.0', - 'MIT License', - 'Mozilla Public License 1.0', - 'Mozilla Public License Version 1.1', - 'Mozilla Public License Version 2.0', - 'Oracle Free Use Terms and Conditions (FUTC)', - 'Public Domain (CC0) License 1.0', - 'Public Domain', - 'Unicode/ICU License', - 'New BSD License' -]*.toLowerCase() - -/** - * This is the configuration we need for our licenses plugin: 'com.github.hierynomus.license' - * This plugin generates a list of dependencies. - */ -downloadLicenses { - includeProjectDependencies = false - reportByDependency = false - reportByLicenseType = true - dependencyConfiguration = 'runtimeClasspath' - excludeDependencies = [ - // only used for static analysis, not actually shipped - 'com.google.code.findbugs:jFormatString:3.0.0', - 'com.google.errorprone:javac:.*', - 'org.checkerframework:dataflow-shaded:.*', - 'org.checkerframework:dataflow-errorprone:.*', - ] - - ext.apache = license('Apache License, Version 2.0', 'http://opensource.org/licenses/Apache-2.0') - ext.bsd = license('BSD License', 'http://www.opensource.org/licenses/bsd-license.php') - ext.bsd3Clause = license('BSD 3-Clause', 'http://opensource.org/licenses/BSD-3-Clause') - ext.cc0 = license('Public Domain (CC0) License 1.0', 'https://creativecommons.org/publicdomain/zero/1.0') - ext.cddl = license('Common Development and Distribution License 1.0', 'http://opensource.org/licenses/CDDL-1.0') - ext.epl1 = license('Eclipse Public License 1.0', 'https://www.eclipse.org/legal/epl-v10.html') - ext.epl2 = license('Eclipse Public License - v 2.0', 'https://www.eclipse.org/legal/epl-v20.html') - ext.mit = license('MIT License', 'http://www.opensource.org/licenses/mit-license.php') - aliases = [ - (apache) : [ - 'Apache 2', - 'Apache 2.0', - 'Apache License 2.0', - 'Apache Software License - Version 2.0', - 'Apache Software License 2.0', - 'Apache v2', - 'Apache-2.0', - 'The Apache License, Version 2.0', - 'The Apache Software License, Version 2.0', - 'The Apache Software License, version 2.0', - license('', 'http://www.apache.org/licenses/LICENSE-2.0.txt') - ], - (bsd) : [ - 'BSD', - 'BSD-2-Clause', - 'BSD licence', - 'The BSD License', - ], - (bsd3Clause) : [ - '3-Clause BSD License', - 'BSD 3-Clause "New" or "Revised" License (BSD-3-Clause)', - 'BSD 3-Clause', - 'BSD Licence 3', - 'BSD License 3', - 'BSD-3-Clause', - 'Eclipse Distribution License - v 1.0', - 'Eclipse Distribution License (New BSD License)', - 'EDL 1.0', - 'New BSD License', - 'The BSD 3-Clause License', - license('', 'http://asm.ow2.org/license.html'), - license('BSD 3-Clause', 'http://www.scala-lang.org/license.html'), - license('BSD 3-clause', 'http://opensource.org/licenses/BSD-3-Clause'), - ], - (cc0) : [ - 'CC0' - ], - (cddl) : [ - 'CDDL 1.0', - 'CDDL 1.1', - 'Common Development and Distribution License (CDDL) v1.0', - 'CDDL + GPLv2 with classpath exception', - ], - (epl1) : [ - 'Eclipse Public License - Version 1.0', - 'Eclipse Public License - v 1.0', - 'Eclipse Public License 1.0 ', - ], - (epl2) : [ - 'Eclipse Public License - v 2.0', - 'Eclipse Public License version 2.0', - 'Eclipse Public License v2.0' - ], - (mit) : [ - 'MIT', - 'The MIT License', - 'The MIT License (MIT)', - 'SPDX-License-Identifier: MIT', - ], - ] - licenses = [ - (group('besu')) : apache, - (group('besu.ethereum.api')) : apache, - (group('besu.consensus')) : apache, - (group('besu.ethereum')) : apache, - (group('besu.metrics')) : apache, - (group('besu.plugins')) : apache, - (group('besu.services')) : apache, - - // JNA is dual licensed under Apache v2.0 or LGPL 2 licenses - // Explicitly declare that we are using the Apache v2.0 license - (group('net.java.dev.jna')) : apache, - - // RocksDB is dual licensed under Apache v2.0 or GPL 2 licenses - // Explicitly declare that we are using the Apache v2.0 license - (group('org.rocksdb')) : apache, - - // Logback is dual licensed under EPL v1.0 or LGPL v2.1 - // Explicitly declare that we are using the EPL v1.0 license - (group('ch.qos.logback')) : epl1, - - // JavaMail, JAXB, Glassfish and Java Servlet are dual licensed under CDDL 1.1 or GPL 2 w/ Classpath Exception - // Explicitly declare that we are using the CDDL 1.1 license - (group('com.sun.mail')) : cddl, - (group('javax.servlet')) : cddl, - (group('javax.xml.bind')) : cddl, - (group('org.glassfish')) : cddl, - - // javax.persistence is dual licensed under EPL 1.0 and EDL 1.0. - // Explicitly declare that we are using the EPL 1.0 license - (group('javax.persistence')) : epl1, - - // jnr-posix is released under a tri EPL v2.0/GPL/LGPL license - 'com.github.jnr:jnr-posix:3.1.15' : epl2, - - // io.netty:netty-tcnative-boringssl-static license markings are not machine readable. - // io.netty:netty-tcnative-classes license markings are not machine readable. - // Explicitly state Apache 2 License for license scanning. - 'io.netty:netty-tcnative-boringssl-static:2.0.46.Final' : apache, - 'io.netty:netty-tcnative-classes:2.0.46.Final' : apache, - - // ANTLR license markings are not all machine readable. - // Explicitly state BSD License for license scanning. - 'org.antlr:antlr-runtime:3.5.2': bsd, - 'org.antlr:ST4:4.3.1': bsd, - ] -} - -task lazyDownloadLicenses() { - if (gradle.startParameter.taskNames.contains('clean') || !file("$rootProject.buildDir/reports/license/license-dependency.xml").exists()) { - dependsOn ':downloadLicenses' - } -} - -task checkLicenses { - description "Verify that all dependencies use accepted licenses." - dependsOn ':lazyDownloadLicenses' - - def bads = "" - doLast { - def xml = new XmlParser().parse("${rootProject.buildDir}/reports/license/license-dependency.xml") - xml.each { license -> - if (!acceptedLicenses.contains((license.@name).toLowerCase())) { - def depStrings = [] - license.dependency.each { depStrings << it.text() } - bads = bads + depStrings + " => ${license.@name} \n" - } - } - if (bads != "") { - throw new GradleException("Some 3rd parties are using licenses not in our accepted licenses list:\n" + - bads + - "If it's a license acceptable for us, add it in the file check-licenses.gradle\n" + - "Be careful, some 3rd parties may accept multiple licenses.\n" + - "In this case, select the one you want to use by changing downloadLicenses.licenses\n" - ) - } - } -} diff --git a/gradle/license-normalizer-bundle.json b/gradle/license-normalizer-bundle.json new file mode 100644 index 00000000000..06c6f8d27de --- /dev/null +++ b/gradle/license-normalizer-bundle.json @@ -0,0 +1,107 @@ +/** + * Copyright 2018 Evgeny Naumenko + * Copyright Hyperledger Besu contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +{ + "bundles" : [ + { "bundleName" : "Apache-1.1", "licenseName" : "Apache Software License, Version 1.1", "licenseUrl" : "https://www.apache.org/licenses/LICENSE-1.1" }, + { "bundleName" : "Apache-2.0", "licenseName" : "Apache License, Version 2.0", "licenseUrl" : "https://www.apache.org/licenses/LICENSE-2.0" }, + { "bundleName" : "0BSD", "licenseName" : "BSD Zero Clause License", "licenseUrl" : "https://opensource.org/licenses/0BSD" }, + { "bundleName" : "BSD-2-Clause", "licenseName" : "The 2-Clause BSD License", "licenseUrl" : "https://opensource.org/licenses/BSD-2-Clause" }, + { "bundleName" : "BSD-3-Clause", "licenseName" : "The 3-Clause BSD License", "licenseUrl" : "https://opensource.org/licenses/BSD-3-Clause" }, + { "bundleName" : "CC0-1.0", "licenseName" : "Creative Commons Legal Code", "licenseUrl" : "https://creativecommons.org/publicdomain/zero/1.0/legalcode" }, + { "bundleName" : "CDDL-1.0", "licenseName" : "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0", "licenseUrl" : "https://oss.oracle.com/licenses/CDDL" }, + { "bundleName" : "CDDL-1.1", "licenseName" : "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.1", "licenseUrl" : "https://oss.oracle.com/licenses/CDDL-1.1" }, + { "bundleName" : "CPL-1.0", "licenseName" : "Common Public License - v 1.0", "licenseUrl" : "https://www.eclipse.org/legal/cpl-v10.html" }, + { "bundleName" : "EPL-1.0", "licenseName" : "Eclipse Public License - v 1.0", "licenseUrl" : "http://www.eclipse.org/legal/epl-v10.html" }, + { "bundleName" : "EPL-2.0", "licenseName" : "Eclipse Public License - v 2.0", "licenseUrl" : "https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt" }, + { "bundleName" : "EDL-1.0", "licenseName" : "Eclipse Distribution License - v 1.0", "licenseUrl" : "https://www.eclipse.org/org/documents/edl-v10.html" }, + { "bundleName" : "GPL-1.0", "licenseName" : "GNU GENERAL PUBLIC LICENSE, Version 1", "licenseUrl" : "https://www.gnu.org/licenses/gpl-1.0" }, + { "bundleName" : "GPL-2.0-only", "licenseName" : "GNU GENERAL PUBLIC LICENSE, Version 2", "licenseUrl" : "https://www.gnu.org/licenses/gpl-2.0" }, + { "bundleName" : "GPL-3.0-only", "licenseName" : "GNU GENERAL PUBLIC LICENSE, Version 3", "licenseUrl" : "https://www.gnu.org/licenses/gpl-3.0" }, + { "bundleName" : "GPL-2.0 WITH Classpath-exception-2.0", "licenseName" : "GNU GENERAL PUBLIC LICENSE, Version 2 + Classpath Exception", "licenseUrl" : "https://openjdk.java.net/legal/gplv2+ce.html" }, + { "bundleName" : "LGPL-2.1-only", "licenseName" : "GNU LESSER GENERAL PUBLIC LICENSE, Version 2.1", "licenseUrl" : "https://www.gnu.org/licenses/lgpl-2.1" }, + { "bundleName" : "LGPL-3.0-only", "licenseName" : "GNU LESSER GENERAL PUBLIC LICENSE, Version 3", "licenseUrl" : "https://www.gnu.org/licenses/lgpl-3.0" }, + { "bundleName" : "MIT", "licenseName" : "MIT License", "licenseUrl" : "https://opensource.org/licenses/MIT" }, + { "bundleName" : "MPL-1.1", "licenseName" : "Mozilla Public License Version 1.1", "licenseUrl" : "https://www.mozilla.org/en-US/MPL/1.1" }, + { "bundleName" : "MPL-2.0", "licenseName" : "Mozilla Public License, Version 2.0", "licenseUrl" : "https://www.mozilla.org/en-US/MPL/2.0" }, + { "bundleName" : "Public-Domain", "licenseName" : "PUBLIC DOMAIN", "licenseUrl" : "" } + ], + "transformationRules" : [ + { "bundleName" : "0BSD", "licenseNamePattern" : "BSD Zero Clause License" }, + { "bundleName" : "0BSD", "licenseNamePattern" : "BSD$" }, + { "bundleName" : "0BSD", "licenseNamePattern" : "BSD( |-)clause.*" }, + { "bundleName" : "0BSD", "licenseNamePattern" : "(The )?BSD( |-)(l|L)icen(s|c)e.*" }, + { "bundleName" : "Apache-2.0", "licenseNamePattern" : ".*The Apache Software License, Version 2\\.0.*" }, + { "bundleName" : "Apache-2.0", "licenseNamePattern" : ".*?Apache( |-|_)2.*" }, + { "bundleName" : "Apache-2.0", "licenseNamePattern" : "ASL 2\\.0" }, + { "bundleName" : "Apache-2.0", "licenseNamePattern" : ".*Apache License,?( Version)? 2.*" }, + { "bundleName" : "Apache-2.0", "licenseUrlPattern" : ".*(www\\.)?opensource\\.org/licenses/Apache-2\\.0.*" }, + { "bundleName" : "Apache-2.0", "licenseUrlPattern" : ".*www\\.apache\\.org/licenses/LICENSE-2\\.0.*" }, + { "bundleName" : "Apache-2.0", "licenseFileContentPattern" : ".*Apache License,?( Version)? 2.*" }, + { "bundleName" : "Apache-1.1", "licenseFileContentPattern" : ".*Apache Software License, Version 1\\.1.*" }, + { "bundleName" : "LGPL-2.1-only", "licenseUrlPattern" : ".*www\\.gnu\\.org/licenses/old-licenses/lgpl-2\\.1\\.html" }, + { "bundleName" : "CC0-1.0", "licenseNamePattern" : "CC0(( |-)1(\\.0)?)?" }, + { "bundleName" : "CC0-1.0", "licenseUrlPattern" : ".*(www\\.)?creativecommons\\.org/publicdomain/zero/1\\.0/" }, + { "bundleName" : "CDDL-1.0", "licenseFileContentPattern" : ".*CDDL.*1\\.0" }, + { "bundleName" : "CDDL-1.0", "licenseUrlPattern" : ".*CDDL.*.?1\\.0" }, + { "bundleName" : "CDDL-1.0", "licenseNamePattern" : "CDDL-1\\.0" }, + { "bundleName" : "CDDL-1.1", "licenseUrlPattern" : ".*CDDL.*.?1\\.1" }, + { "bundleName" : "CDDL-1.0", "licenseNamePattern" : "Common Development and Distribution License( \\(CDDL\\),?)? (version )?(.?\\s?)?1\\.0" }, + { "bundleName" : "CDDL-1.1", "licenseNamePattern" : "Common Development and Distribution License( \\(CDDL\\),?)? (version )?(.?\\s?)?1\\.1" }, + { "bundleName" : "BSD-3-Clause", "licenseNamePattern" : ".*BSD( |-)3-clause.*" }, + { "bundleName" : "BSD-3-Clause", "licenseUrlPattern" : ".*(www\\.)?opensource\\.org/licenses/BSD-3-Clause" }, + { "bundleName" : "BSD-3-Clause", "licenseNamePattern" : ".*?(The )New BSD License.*" }, + { "bundleName" : "BSD-3-Clause", "licenseNamePattern" : ".*?Modified BSD License.*" }, + { "bundleName" : "BSD-2-Clause", "licenseNamePattern" : "BSD( |-)2-clause.*" }, + { "bundleName" : "BSD-2-Clause", "licenseUrlPattern" : ".*(www\\.)?opensource\\.org/licenses/BSD-2-Clause" }, + { "bundleName" : "BSD-2-Clause", "licenseUrlPattern" : ".*(www\\.)?opensource\\.org/licenses/bsd-license(\\.php)?" }, + { "bundleName" : "CDDL-1.0", "licenseNamePattern" : "Common Development and Distribution( License)?" }, + { "bundleName" : "CDDL-1.0", "licenseNamePattern" : "CDDL( |-)1(\\.0)" }, + { "bundleName" : "CDDL-1.1", "licenseNamePattern" : "CDDL 1\\.1" }, + { "bundleName" : "CDDL-1.1", "licenseUrlPattern" : ".*(www\\.).opensource\\.org/licenses/CDDL-1\\.0" }, + { "bundleName" : "EPL-1.0", "licenseNamePattern" : "Eclipse Publish License.*(v|version)\\.?\\s?1(\\.?0)?" }, + { "bundleName" : "EPL-1.0", "licenseNamePattern" : "Eclipse Public License.*(v|version)\\.?\\s?1(\\.?0)?" }, + { "bundleName" : "EPL-2.0", "licenseNamePattern" : "Eclipse Public License.*(v|version)\\.?\\s?2(\\.?0)?" }, + { "bundleName" : "EPL-2.0", "licenseUrlPattern" : ".*(www\\.).opensource\\.org/licenses/EPL-2\\.0" }, + { "bundleName" : "EPL-2.0", "licenseUrlPattern" : ".*http.?://www\\.eclipse\\.org/legal/epl-.?2\\.?0.*" }, + { "bundleName" : "EPL-2.0", "licenseUrlPattern" : ".*http.?://www\\.eclipse\\.org/org.*/epl-.?2\\.?0.*" }, + { "bundleName" : "EPL-2.0", "licenseUrlPattern" : ".*http.?://projects\\.eclipse\\.org/.*/epl-.?2\\.?0.*" }, + { "bundleName" : "EDL-1.0", "licenseNamePattern" : "Eclipse Distribution License.*(v|version)\\.?\\s?1(\\.0)?" }, + { "bundleName" : "EDL-1.0", "licenseNamePattern" : "Eclipse Distribution License \\(New BSD License\\)" }, + { "bundleName" : "EDL-1.0", "licenseUrlPattern" : ".*http.?://(www\\.)?eclipse\\.org/org.*/edl-.?1\\.?0.*" }, + { "bundleName" : "GPL-2.0-only", "licenseUrlPattern" : ".*(www\\.)?opensource\\.org/licenses/GPL-2\\.0" }, + { "bundleName" : "GPL-2.0 WITH Classpath-exception-2.0", "licenseNamePattern" : "GNU General Public License, version 2.*classpath exception" }, + { "bundleName" : "GPL-2.0 WITH Classpath-exception-2.0", "licenseNamePattern" : "GNU General Public License, version 2.*cp?e" }, + { "bundleName" : "GPL-2.0 WITH Classpath-exception-2.0", "licenseNamePattern" : "GNU General Public License, version 2.*, with the classpath exception" }, + { "bundleName" : "GPL-2.0 WITH Classpath-exception-2.0", "licenseNamePattern" : "GPL2 w/ CPE" }, + { "bundleName" : "GPL-3.0-only", "licenseUrlPattern" : ".*(www\\.)?opensource\\.org/licenses/GPL-3\\.0" }, + { "bundleName" : "LGPL-2.1-only", "licenseUrlPattern" : ".*(www\\.)?opensource\\.org/licenses/LGPL-2\\.1" }, + { "bundleName" : "LGPL-2.1-only", "licenseUrlPattern" : ".*(www\\.)?gnu\\.org/licenses(/old-licenses)?/lgpl-2\\.1(\\.(html|txt))?" }, + { "bundleName" : "LGPL-2.1-only", "licenseNamePattern" : "LGPL 2\\.1" }, + { "bundleName" : "LGPL-2.1-only", "licenseNamePattern" : "LGPL.*(v|version)\\.?\\s?2\\.1" }, + { "bundleName" : "LGPL-2.1-only", "licenseUrlPattern" : ".*(www\\.)?repository.jboss.org/licenses/lgpl-2.1\\.txt" }, + { "bundleName" : "LGPL-3.0-only", "licenseUrlPattern" : ".*(www\\.).opensource\\.org/licenses/LGPL-3\\.0" }, + { "bundleName" : "LGPL-3.0-only", "licenseNamePattern" : "lgplv?3" }, + { "bundleName" : "LGPL-3.0-only", "licenseUrlPattern" : ".*(www\\.)?gnu\\.org/licenses(/old-licenses)?/lgpl(-3)?(\\.(html|txt))?" }, + { "bundleName" : "MIT", "licenseNamePattern" : "(The\\s)?MIT(\\slicen(c|s)e)?(\\s\\(MIT\\))?" }, + { "bundleName" : "MIT", "licenseUrlPattern" : ".*(www\\.)?opensource\\.org/licenses/MIT(\\.php)?" }, + { "bundleName" : "MPL-1.1", "licenseNamePattern" : "MPL 1\\.1" }, + { "bundleName" : "MPL-2.0", "licenseUrlPattern" : ".*(www\\.).opensource\\.org/licenses/MPL-2\\.0" }, + { "bundleName" : "Public-Domain", "licenseNamePattern" : "((public)\\s(domain)).*", "transformUrl" : false }, + { "bundleName" : "Public-Domain", "licenseFileContentPattern" : ".*(Creative)\\s(Commons).*", "transformUrl" : false }, + { "bundleName" : "Public-Domain", "licenseFileContentPattern" : ".*((Public)\\s(Domain)).*", "transformUrl" : false } + ] +} \ No newline at end of file diff --git a/gradle/formatter.properties b/gradle/spotless/greclipse.properties similarity index 100% rename from gradle/formatter.properties rename to gradle/spotless/greclipse.properties diff --git a/gradle/spotless/java.current.license b/gradle/spotless/java.current.license new file mode 100644 index 00000000000..fb92e548e28 --- /dev/null +++ b/gradle/spotless/java.current.license @@ -0,0 +1,14 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ \ No newline at end of file diff --git a/gradle/spotless/java.former.date.license b/gradle/spotless/java.former.date.license new file mode 100644 index 00000000000..cc53a42a8c1 --- /dev/null +++ b/gradle/spotless/java.former.date.license @@ -0,0 +1,14 @@ +/* + * Copyright $YEAR ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ \ No newline at end of file diff --git a/gradle/spotless/java.former.license b/gradle/spotless/java.former.license new file mode 100644 index 00000000000..c936fcf470e --- /dev/null +++ b/gradle/spotless/java.former.license @@ -0,0 +1,14 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ \ No newline at end of file diff --git a/gradle/spotless/sh.license b/gradle/spotless/sh.license new file mode 100644 index 00000000000..97909d35073 --- /dev/null +++ b/gradle/spotless/sh.license @@ -0,0 +1,15 @@ +## +## Copyright contributors to Hyperledger Besu. +## +## Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +## an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +## specific language governing permissions and limitations under the License. +## +## SPDX-License-Identifier: Apache-2.0 +## + diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 99ff1238216..3778a2d60ad 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -1,5 +1,5 @@ - + true false @@ -9,6 +9,14 @@ + + + + + + + + @@ -33,60 +41,57 @@ - - - - - - - - - - - + + + - - + + - - - + + + - - + + - - + + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + + + + @@ -99,29 +104,24 @@ - - - + + + - - - + + + - - - + + + - - - - - - - - + + + @@ -129,29 +129,19 @@ - - - - - - - - + + + - - - + + + - - - - - - - - + + + @@ -169,9 +159,14 @@ - - - + + + + + + + + @@ -184,33 +179,30 @@ - - - - - - + + + - - - - - - + + + - - - + + + - - + + - - - + + + + + + @@ -220,43 +212,29 @@ - - - - - - + + + - - + + - - - + + + - - + + - - - - - - - - - - - - - - + + + - - + + @@ -266,37 +244,37 @@ - - - - - - + + + - - + + - - - + + + + + + - - - + + + - - + + - - - + + + - - + + @@ -306,48 +284,64 @@ - - + + + + + + + - - - + + + - - + + - - + + - - - + + + + + + + + + + + + + + - - - + + + - - + + - - + + - - - + + + - - - + + + - - + + @@ -361,43 +355,51 @@ - - - + + + - - - + + + - - + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + + + + + + + + + @@ -432,6 +434,17 @@ + + + + + + + + + + + @@ -459,6 +472,14 @@ + + + + + + + + @@ -467,9 +488,12 @@ - - - + + + + + + @@ -488,6 +512,14 @@ + + + + + + + + @@ -496,6 +528,14 @@ + + + + + + + + @@ -504,6 +544,14 @@ + + + + + + + + @@ -520,55 +568,33 @@ - - - - - - - - - - - - - - + + + - - - - + + - - - - - - + + + - - + + - - - - - - - - - + + + - - - + + + - - + + @@ -579,6 +605,11 @@ + + + + + @@ -587,41 +618,33 @@ - - - + + + - - + + - - - + + + - - + + - - - + + + - - - - - - - + + - - - - - - + + + @@ -632,12 +655,20 @@ - - - + + + + + + + + + + + - - + + @@ -645,28 +676,30 @@ - - - + + + + + - - - + + + - - + + - - - + + + - - + + @@ -677,14 +710,9 @@ - - - - - - - - + + + @@ -692,9 +720,17 @@ - - - + + + + + + + + + + + @@ -713,6 +749,14 @@ + + + + + + + + @@ -723,131 +767,88 @@ + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - - - - - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - - - - - - - - + + @@ -855,25 +856,30 @@ - - - + + + + + + + + - - - + + + - - + + - - - + + + - - + + @@ -884,20 +890,20 @@ - - - + + + - - + + - - - + + + - - + + @@ -908,14 +914,14 @@ - - - + + + - - - + + + @@ -923,28 +929,23 @@ - - - + + + - - + + - - - - - - + + + - - - - + + - - + + @@ -952,14 +953,9 @@ - - - - - - - - + + + @@ -970,69 +966,56 @@ - - - + + + - - + + - - - + + + - - - - - - + + + - - - - + + - - - - - - + + + - - - + + + - - + + - - - - - - + + + - - - + + + - - - - + + - - - + + + @@ -1040,22 +1023,22 @@ - - - + + + - - - - - - + + + - - - + + + + + + @@ -1066,30 +1049,30 @@ - - - - - - + + + - - - + + + - - + + - - - + + + + + + - - - + + + @@ -1097,9 +1080,14 @@ - - - + + + + + + + + @@ -1110,27 +1098,6 @@ - - - - - - - - - - - - - - - - - - - - - @@ -1139,39 +1106,31 @@ - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - - - - - - - - + + @@ -1182,59 +1141,57 @@ - - - - - - + + + - - - - + + - - - + + + - - + + - - - + + + - - - + + + - - - - + + - - + + + + + + + - - - + + + - - + + - - - + + + - - + + @@ -1260,9 +1217,6 @@ - - - @@ -1272,6 +1226,22 @@ + + + + + + + + + + + + + + + + @@ -1283,6 +1253,17 @@ + + + + + + + + + + + @@ -1294,15 +1275,20 @@ - - - + + + + + + + + - - + + - - + + @@ -1313,6 +1299,11 @@ + + + + + @@ -1321,91 +1312,99 @@ - - - + + + - - + + + + - - - + + + - - + + + + - - + + - - - - + + - - - + + + - - + + - - - + + + - - - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - - - - + + + - - + + - - - + + + + + + + + + + + + + + @@ -1416,25 +1415,49 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - + + + - - + + - - - + + + @@ -1445,222 +1468,252 @@ - - - + + + + + + + + + + + - - + + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + - - - + + + - - + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + @@ -1671,46 +1724,51 @@ - - - + + + - - + + - - - + + + - - + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + + + + + + @@ -1718,216 +1776,216 @@ - - - - - - + + + - - - + + + + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - - - - + + + - - + + - - - + + + + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - - - - + + + - - - + + + + + + - - - + + + - - + + - - - + + + - - + + - - - - - - + + + - - + + - - - + + + + + + - - - + + + - - + + @@ -1935,416 +1993,407 @@ - - - + + + - - - + + + - - - - - - + + + - - - + + + + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - - - - - + + + - - - + + + + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - + + - - + + + + + - - - + + + - - + + - - + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + + + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - + + - - + + - - - + + + - - - - - + + - - - - - - + + + - - + + - - - + + + - - - - - + + - - - - - - + + + - - + + - - - + + + - - - - - + + - - - + + + - - + + - - + + @@ -2397,15 +2446,15 @@ - - - + + + - - + + - - + + @@ -2429,15 +2478,15 @@ - - - + + + - - + + - - + + @@ -2450,25 +2499,20 @@ - - - - - - - - + + + - - + + - - - + + + - - + + @@ -2479,12 +2523,12 @@ - - - + + + - - + + @@ -2495,12 +2539,41 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + @@ -2519,17 +2592,17 @@ - - - + + + - - - + + + - - + + @@ -2593,14 +2666,6 @@ - - - - - - - - @@ -2617,22 +2682,6 @@ - - - - - - - - - - - - - - - - @@ -2665,54 +2714,49 @@ - - - - - - - - + + + - - + + - - - + + + - - + + - - - - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + + + + @@ -2720,6 +2764,11 @@ + + + + + @@ -2730,12 +2779,12 @@ - - - + + + - - + + @@ -2764,59 +2813,75 @@ - - - + + + + + + + + + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + + + + + + - - - + + + + + + - - - + + + - - - + + + @@ -2835,35 +2900,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2880,51 +2916,51 @@ - - - + + + - - + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - - + + + @@ -2937,54 +2973,46 @@ - - - + + + - - + + - - - + + + - - + + - - - + + + - - - - - - - + + - - - - - - + + + - - - + + + - - - + + + - - + + @@ -2995,36 +3023,20 @@ - - - - - - - - - - - + + + - - + + - - - + + + - - - - - - - - - - + + @@ -3085,16 +3097,16 @@ - - - - - + + + + + @@ -3120,7 +3132,27 @@ - + + + + + + + + + + + + + + + + + + + + + @@ -3143,20 +3175,20 @@ - - - + + + - - + + - - - + + + - - + + @@ -3168,16 +3200,24 @@ - - - - - - + + + + + + + + + + + + + + @@ -3188,6 +3228,19 @@ + + + + + + + + + + + + + @@ -3198,16 +3251,16 @@ - - - - - + + + + + @@ -3218,259 +3271,253 @@ - - - - - - - - - - - - - - - - + + + - - - - - - + + + - - - - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + + + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - - + + + - - - - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - - + + + - - + + - - - + + + + + + - - - + + + - - - + + + + + + - - + + + + - - - + + + - - - + + + + + + - - + + + + - - - + + + - - + + - - - + + + - - + + - - - + + + + + + - - - + + + - - - + + + - - + + - - - - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + @@ -3489,35 +3536,25 @@ - - - - - - - - + + + - - + + - - - + + + - - - - - - - + + - - - + + + @@ -3528,44 +3565,52 @@ - - - + + + + + + + + + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + @@ -3576,518 +3621,1055 @@ - - - + + + - - + + + + - - + + + + + - - - + + + - - - + + + - - + + + + + + + + + + + + - - + + + + - - - + + + - - + + + + + + + - - - + + + - - + + - - - + + + - - + + - - - + + + + + + - - - + + + - - + + + + - - - + + + - - + + - - - + + + + + + - - - + + + + + + - - - + + + + + + - - - + + + + + + - - - + + + - - + + - - - + + + + + + - - - + + + + + + - - - + + + - - + + - - - + + + + + + - - - + + + - - + + - - - + + + - - - + + + - - + + - - - + + + - - + + + + - - - + + + - - + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + + + + + + + + + + + + - - - + + + - - + + - - - + + + + + + + + + + + + + + - - - + + + + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + + + + - - - + + + + + + - - - + + + - - + + - - - + + + - - + + - - - + + + + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + + + + - - - + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4095,12 +4677,20 @@ - - - + + + + + + + + + + + - - + + @@ -4116,44 +4706,41 @@ - - - + + + - - + + - - - + + + - - - - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + @@ -4188,159 +4775,154 @@ - - - - - - - - - - - + + + - - - - - + + - - - - - - + + + - - + + - - - + + + - - - - - + + - - - - - - + + + - - + + - - - + + + - - + + - - + + + + + + + - - - + + + + + + - - + + + + - - + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + + + + - - - + + + - - + + + + - - - + + + - - + + - - - + + + - - + + @@ -4351,6 +4933,14 @@ + + + + + + + + @@ -4372,6 +4962,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -4388,25 +5002,57 @@ - - - + + + + + + + + + + + + + + + + - - - + + + + + + + + - - + + - - - + + + + + + + + + + + + + + + + + @@ -4415,51 +5061,37 @@ - - - - - - - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - - - - - - - - - + + @@ -4470,49 +5102,65 @@ - - - + + + + + + - - - + + + + + + - - + + + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + + + + + + + + + @@ -4524,9 +5172,6 @@ - - - @@ -4539,71 +5184,118 @@ - - - + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + + + + + + + + + + + + + + + + + @@ -4622,14 +5314,6 @@ - - - - - - - - @@ -4638,14 +5322,6 @@ - - - - - - - - @@ -4654,23 +5330,34 @@ - - - + + + + + + + + + + + + + + - - + + - - - + + + - - + + - - + + @@ -4681,15 +5368,15 @@ - - - + + + - - + + - - + + @@ -4700,26 +5387,26 @@ - - - + + + - - + + - - + + - - - + + + - - + + - - + + @@ -4730,50 +5417,33 @@ - - - - - - - - - + + + - - - - - - - - - + + + - - - - + + - - - - - - + + + - - - + + + - - + + - - + + @@ -4784,26 +5454,17 @@ - - - - - - - - - + + + - - - + + + - - - - - + + @@ -4814,52 +5475,20 @@ - - - - - - - - + + + - - + + - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + @@ -4870,41 +5499,30 @@ - - - - - - - - - - - - - - + + + - - - + + + - - + + - - - + + + - - + + - - - + + + @@ -4912,20 +5530,41 @@ - - - + + + + + + + + + + + - - + + - - - + + + - - + + + + + + + + + + + + + + + @@ -4949,12 +5588,20 @@ - - - + + + + + + + + + + + - - + + @@ -4986,6 +5633,11 @@ + + + + + @@ -5007,6 +5659,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -5025,14 +5744,6 @@ - - - - - - - - @@ -5041,12 +5752,12 @@ - - - + + + - - + + @@ -5057,6 +5768,16 @@ + + + + + + + + + + @@ -5065,6 +5786,14 @@ + + + + + + + + @@ -5073,6 +5802,14 @@ + + + + + + + + @@ -5081,25 +5818,25 @@ - - - + + + - - + + - - - + + + - - - + + + - - + + @@ -5118,6 +5855,14 @@ + + + + + + + + @@ -5134,43 +5879,66 @@ - - - + + + + + + + + + + + - - + + - - - + + + - - + + + + + + + + + + + + - - - + + + - - + + - - - + + + - - - + + + - - - + + + + + + + + @@ -5191,17 +5959,12 @@ - - - + + + - - - - - - - + + @@ -5219,285 +5982,276 @@ - - - + + + - - + + - - - + + + - - + + - - - - + + - - - + + + - - + + - - - - - - + + + - - - + + + - - + + - - - + + + + + + - - - + + + - - + + - - + + - - - + + + - - + + - - - - + + - - - + + + - - + + - - - + + + - - + + - - - - - - - + + - - - + + + - - - - - - - + + - - + + - - - + + + - - + + - - - + + + - - + + + + + - - - + + + - - + + - - - - + + - - + + + + - - - + + + - - + + - - - + + + - - - - + + - - + + - - - + + + - - + + + + + - - - + + + + + + - - + + - - - + + + - - + + - - - + + + - - + + + + + - - - + + + - - + + - - - + + + - - + + - - - - - - + + + - - - + + + - - + + - - - + + + - - - - + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - + + - - - + + + - - - + + + - - + + @@ -5515,27 +6269,27 @@ - - - - - - + + + - - + + - - + + - - - + + + + + + - - + + diff --git a/gradle/versions.gradle b/gradle/versions.gradle index 34a56730ee0..b7a286e69a3 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -15,13 +15,9 @@ dependencyManagement { dependencies { + applyMavenExclusions = false - dependencySet(group: 'org.antlr', version: '4.11.1') { - entry 'antlr4' - entry 'antlr4-runtime' - } - - dependencySet(group:'com.fasterxml.jackson.core', version:'2.14.2') { + dependencySet(group:'com.fasterxml.jackson.core', version:'2.17.2') { entry 'jackson-databind' entry 'jackson-datatype' entry 'jackson-datatype-jdk8' @@ -29,69 +25,63 @@ dependencyManagement { dependency 'com.github.ben-manes.caffeine:caffeine:3.1.8' - dependencySet(group: 'com.github.tomakehurst', version: '2.35.0') { - entry'wiremock-jre8-standalone' - entry'wiremock-jre8' - } + dependency 'com.github.oshi:oshi-core:6.6.3' - dependency 'com.google.auto.service:auto-service:1.0.1' + dependency 'com.google.auto.service:auto-service:1.1.1' - dependencySet(group: 'com.google.dagger', version: '2.45') { + dependencySet(group: 'com.google.dagger', version: '2.52') { entry'dagger-compiler' entry'dagger' } - dependencySet(group: 'com.google.errorprone', version: '2.18.0') { - entry 'error_prone_annotation' - entry 'error_prone_check_api' - entry 'error_prone_core' - entry 'error_prone_test_helpers' - } + dependency 'org.hyperledger.besu:besu-errorprone-checks:1.0.0' + + dependency 'com.google.guava:guava:33.3.0-jre' - dependency 'com.google.guava:guava:31.1-jre' + dependency 'com.graphql-java:graphql-java:22.2' - dependency 'com.graphql-java:graphql-java:20.1' + dependency 'com.splunk.logging:splunk-library-javalogging:1.11.8' - dependency 'com.splunk.logging:splunk-library-javalogging:1.11.5' + dependency 'com.squareup.okhttp3:okhttp:4.12.0' - dependency 'com.squareup.okhttp3:okhttp:4.10.0' + dependency 'commons-codec:commons-codec:1.16.0' - dependency 'commons-codec:commons-codec:1.15' + dependency 'commons-io:commons-io:2.16.1' - dependency 'commons-io:commons-io:2.11.0' + dependency 'commons-net:commons-net:3.11.1' - dependency 'dnsjava:dnsjava:3.5.2' + dependency 'dnsjava:dnsjava:3.6.1' - dependencySet(group: 'info.picocli', version: '4.7.1') { + dependencySet(group: 'info.picocli', version: '4.7.6') { entry 'picocli' entry 'picocli-codegen' } - dependencySet(group: 'io.grpc', version: '1.59.0') { + dependencySet(group: 'io.grpc', version: '1.66.0') { entry 'grpc-all' entry 'grpc-core' entry 'grpc-netty' entry 'grpc-stub' } - dependency 'io.kubernetes:client-java:18.0.0' + dependency 'io.kubernetes:client-java:18.0.1' - dependency 'io.netty:netty-all:4.1.100.Final' - dependency 'io.netty:netty-tcnative-boringssl-static:2.0.62.Final' - dependency group: 'io.netty', name: 'netty-transport-native-epoll', version:'4.1.100.Final', classifier: 'linux-x86_64' - dependency group: 'io.netty', name: 'netty-transport-native-kqueue', version:'4.1.100.Final', classifier: 'osx-x86_64' - dependency 'io.netty:netty-transport-native-unix-common:4.1.100.Final' + dependency 'io.netty:netty-all:4.1.112.Final' + dependency 'io.netty:netty-tcnative-boringssl-static:2.0.66.Final' + dependency group: 'io.netty', name: 'netty-transport-native-epoll', version:'4.1.112.Final', classifier: 'linux-x86_64' + dependency group: 'io.netty', name: 'netty-transport-native-kqueue', version:'4.1.112.Final', classifier: 'osx-x86_64' + dependency 'io.netty:netty-transport-native-unix-common:4.1.112.Final' - dependency 'io.opentelemetry:opentelemetry-api:1.24.0' - dependency 'io.opentelemetry:opentelemetry-exporter-otlp:1.24.0' - dependency 'io.opentelemetry:opentelemetry-extension-trace-propagators:1.24.0' - dependency 'io.opentelemetry.proto:opentelemetry-proto:0.19.0-alpha' - dependency 'io.opentelemetry:opentelemetry-sdk-metrics:1.24.0' - dependency 'io.opentelemetry:opentelemetry-sdk-trace:1.24.0' - dependency 'io.opentelemetry:opentelemetry-sdk:1.24.0' - dependency 'io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.24.0-alpha' - dependency 'io.opentelemetry:opentelemetry-semconv:1.24.0-alpha' - dependency 'io.opentelemetry.instrumentation:opentelemetry-okhttp-3.0:1.24.0-alpha' + dependency 'io.opentelemetry:opentelemetry-api:1.41.0' + dependency 'io.opentelemetry:opentelemetry-exporter-otlp:1.41.0' + dependency 'io.opentelemetry:opentelemetry-extension-trace-propagators:1.41.0' + dependency 'io.opentelemetry:opentelemetry-sdk-metrics:1.41.0' + dependency 'io.opentelemetry:opentelemetry-sdk-trace:1.41.0' + dependency 'io.opentelemetry:opentelemetry-sdk:1.41.0' + dependency 'io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.41.0' + dependency 'io.opentelemetry.instrumentation:opentelemetry-okhttp-3.0:2.7.0-alpha' + dependency 'io.opentelemetry.proto:opentelemetry-proto:1.3.2-alpha' + dependency 'io.opentelemetry.semconv:opentelemetry-semconv:1.27.0-alpha' dependency 'io.opentracing.contrib:opentracing-okhttp3:3.0.0' dependency 'io.opentracing:opentracing-api:0.33.0' @@ -99,15 +89,30 @@ dependencyManagement { dependency 'io.pkts:pkts-core:3.0.10' - dependency 'io.prometheus:simpleclient:0.9.0' - dependency 'io.prometheus:simpleclient_common:0.9.0' - dependency 'io.prometheus:simpleclient_hotspot:0.9.0' - dependency 'io.prometheus:simpleclient_pushgateway:0.9.0' - dependency 'io.prometheus:simpleclient_guava:0.16.0' + dependencySet(group: 'io.prometheus', version: '0.16.0') { + entry 'simpleclient' + entry 'simpleclient_common' + entry 'simpleclient_hotspot' + entry 'simpleclient_pushgateway' + entry 'simpleclient_guava' + } dependency 'io.reactivex.rxjava2:rxjava:2.2.21' - dependencySet(group: 'io.vertx', version: '4.3.5') { + dependencySet(group: 'io.tmio', version: '2.4.2') { + entry 'tuweni-bytes' + entry 'tuweni-config' + entry 'tuweni-concurrent' + entry 'tuweni-crypto' + entry 'tuweni-devp2p' + entry 'tuweni-io' + entry 'tuweni-net' + entry 'tuweni-rlp' + entry 'tuweni-toml' + entry 'tuweni-units' + } + + dependencySet(group: 'io.vertx', version: '4.5.9') { entry 'vertx-auth-jwt' entry 'vertx-codegen' entry 'vertx-core' @@ -120,117 +125,121 @@ dependencyManagement { dependency 'junit:junit:4.13.2' - dependency 'net.java.dev.jna:jna:5.13.0' + dependency 'net.java.dev.jna:jna:5.14.0' + + dependencySet(group: 'org.antlr', version: '4.11.1') { + entry 'antlr4' + entry 'antlr4-runtime' + } - dependency 'org.apache.commons:commons-compress:1.23.0' - dependency 'org.apache.commons:commons-lang3:3.12.0' - dependency 'org.apache.commons:commons-text:1.10.0' + dependency 'org.apache.commons:commons-collections4:4.4' + dependency 'org.apache.commons:commons-compress:1.27.1' + dependency 'org.apache.commons:commons-lang3:3.17.0' + dependency 'org.apache.commons:commons-text:1.12.0' - dependencySet(group: 'org.apache.logging.log4j', version: '2.20.0') { + dependencySet(group: 'org.apache.logging.log4j', version: '2.23.1') { entry 'log4j-api' entry 'log4j-core' entry 'log4j-jul' entry 'log4j-slf4j2-impl' } - dependencySet(group: 'io.tmio', version: '2.4.2') { - entry 'tuweni-bytes' - entry 'tuweni-config' - entry 'tuweni-concurrent' - entry 'tuweni-crypto' - entry 'tuweni-devp2p' - entry 'tuweni-dns-discovery' - entry 'tuweni-io' - entry 'tuweni-net' - entry 'tuweni-rlp' - entry 'tuweni-toml' - entry 'tuweni-units' - } - - dependency 'org.assertj:assertj-core:3.24.2' + dependency 'org.assertj:assertj-core:3.26.3' - dependency 'org.awaitility:awaitility:4.2.0' + dependency 'org.awaitility:awaitility:4.2.2' - dependencySet(group: 'org.bouncycastle', version: '1.76') { + dependencySet(group: 'org.bouncycastle', version: '1.78.1') { entry'bcpkix-jdk18on' entry'bcprov-jdk18on' } - dependency 'org.fusesource.jansi:jansi:2.4.0' - dependency 'org.openjdk.jol:jol-core:0.17' - dependency 'tech.pegasys:jc-kzg-4844:0.8.0' + dependency 'org.fusesource.jansi:jansi:2.4.1' - dependencySet(group: 'org.hyperledger.besu', version: '0.8.2') { + dependencySet(group: 'org.hyperledger.besu', version: '0.9.5') { entry 'arithmetic' entry 'ipa-multipoint' entry 'bls12-381' entry 'secp256k1' entry 'secp256r1' entry 'blake2bf' + entry 'gnark' } - dependencySet(group: 'org.immutables', version: '2.9.3') { + dependencySet(group: 'org.immutables', version: '2.10.1') { entry 'value-annotations' entry 'value' } - dependency 'org.java-websocket:Java-WebSocket:1.5.3' + dependency 'org.java-websocket:Java-WebSocket:1.5.7' + + dependencySet(group: 'org.jacoco', version: '0.8.12') { + entry 'org.jacoco.agent' + entry 'org.jacoco.core' + } - dependency 'org.jetbrains.kotlin:kotlin-stdlib:1.8.10' + dependency 'org.jetbrains.kotlin:kotlin-stdlib:2.0.20' - dependencySet(group: 'org.junit.jupiter', version: '5.8.2') { + dependencySet(group: 'org.junit.jupiter', version: '5.11.0') { entry 'junit-jupiter' entry 'junit-jupiter-api' entry 'junit-jupiter-engine' entry 'junit-jupiter-params' } + dependency 'org.openjdk.jol:jol-core:0.17' + dependency 'org.junit.platform:junit-platform-runner:1.9.2' - dependency 'org.junit.vintage:junit-vintage-engine:5.9.2' + dependency 'org.junit.vintage:junit-vintage-engine:5.10.1' - dependencySet(group: 'org.jupnp', version:'2.7.0') { + dependencySet(group: 'org.jupnp', version:'3.0.2') { entry 'org.jupnp.support' entry 'org.jupnp' } - dependencySet(group: 'org.mockito', version:'4.11.0') { + dependencySet(group: 'org.mockito', version:'5.13.0') { entry 'mockito-core' entry 'mockito-junit-jupiter' } - dependencySet(group: 'org.openjdk.jmh', version:'1.36') { + dependencySet(group: 'org.openjdk.jmh', version:'1.37') { entry 'jmh-core' entry 'jmh-generator-annprocess' } - dependency 'org.owasp.encoder:encoder:1.2.3' + dependency 'org.owasp.encoder:encoder:1.3.1' - dependency 'org.rocksdb:rocksdbjni:8.3.2' + dependency 'org.rocksdb:rocksdbjni:8.3.2' // 8.9.1 causes a bug with a FOREST canary - dependencySet(group: 'org.slf4j', version:'2.0.7') { + dependencySet(group: 'org.slf4j', version:'2.0.16') { entry 'slf4j-api' entry 'slf4j-nop' } - dependency 'org.springframework.security:spring-security-crypto:6.0.2' + dependency 'org.springframework.security:spring-security-crypto:6.3.3' - dependency 'org.testcontainers:testcontainers:1.17.6' + dependency 'org.testcontainers:testcontainers:1.20.1' - dependency 'org.web3j:quorum:4.9.5' - dependencySet(group: 'org.web3j', version: '4.9.7') { + dependency 'org.web3j:quorum:4.10.0' + dependencySet(group: 'org.web3j', version: '4.12.1') { entry 'abi' entry 'besu' entry 'core' entry 'crypto' } - dependency 'org.xerial.snappy:snappy-java:1.1.9.1' + dependencySet(group: 'org.wiremock', version: '3.9.1') { + entry 'wiremock' + } + + dependency 'org.xerial.snappy:snappy-java:1.1.10.6' dependency 'org.yaml:snakeyaml:2.0' - dependency 'tech.pegasys.discovery:discovery:22.2.0' + dependency 'org.apache.maven:maven-artifact:3.9.6' + + dependency 'tech.pegasys:jc-kzg-4844:1.0.0' - dependency 'com.github.oshi:oshi-core:6.4.1' + dependency 'tech.pegasys.discovery:discovery:24.6.0' } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae..e6441136f3d 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 070cb702f09..a4413138c96 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index aef870434d9..1aa94a42690 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +80,11 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx1024m" "-Xms256m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +131,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ @@ -205,6 +214,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. diff --git a/gradlew.bat b/gradlew.bat index ac1b06f9382..7101f8e4676 100755 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,13 +41,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/metrics/core/build.gradle b/metrics/core/build.gradle index 295cdada976..1438a198ae5 100644 --- a/metrics/core/build.gradle +++ b/metrics/core/build.gradle @@ -22,7 +22,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } @@ -48,11 +49,11 @@ dependencies { implementation 'io.netty:netty-all' implementation 'io.opentelemetry:opentelemetry-api' implementation 'io.opentelemetry:opentelemetry-sdk' - implementation 'io.opentelemetry:opentelemetry-semconv' implementation 'io.opentelemetry:opentelemetry-sdk-trace' implementation 'io.opentelemetry:opentelemetry-sdk-metrics' implementation 'io.opentelemetry:opentelemetry-exporter-otlp' implementation 'io.opentelemetry:opentelemetry-sdk-extension-autoconfigure' + implementation 'io.opentelemetry.semconv:opentelemetry-semconv' implementation 'io.prometheus:simpleclient' implementation 'io.prometheus:simpleclient_common' diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/BesuMetricCategory.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/BesuMetricCategory.java index 27be3ff9321..4151320177b 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/BesuMetricCategory.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/BesuMetricCategory.java @@ -53,9 +53,12 @@ public enum BesuMetricCategory implements MetricCategory { /** Transaction pool besu metric category. */ TRANSACTION_POOL("transaction_pool"), /** Stratum besu metric category. */ - STRATUM("stratum"); + STRATUM("stratum"), + /** Block processing besu metric category. */ + BLOCK_PROCESSING("block_processing"); private static final Optional BESU_PREFIX = Optional.of("besu_"); + /** The constant DEFAULT_METRIC_CATEGORIES. */ public static final Set DEFAULT_METRIC_CATEGORIES; diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricCategoryRegistryImpl.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricCategoryRegistryImpl.java index 14699e7ff32..b0a31793bcc 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricCategoryRegistryImpl.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricCategoryRegistryImpl.java @@ -25,6 +25,9 @@ public class MetricCategoryRegistryImpl implements MetricCategoryRegistry { private final List metricCategories = new ArrayList<>(); + /** Default constructor */ + public MetricCategoryRegistryImpl() {} + /** * Gets metric categories. * diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsConfigurationModule.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsConfigurationModule.java index 8c44e869623..3559958abae 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsConfigurationModule.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsConfigurationModule.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.metrics; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; @@ -25,6 +24,9 @@ /** Dagger module for providing the {@link MetricsConfiguration} instance. */ @Module public class MetricsConfigurationModule { + /** Default constructor */ + public MetricsConfigurationModule() {} + @Provides @Singleton MetricsConfiguration provideMetricsConfiguration() { diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsSystemModule.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsSystemModule.java index 05dec0fb1f2..044085ef426 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsSystemModule.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/MetricsSystemModule.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.metrics; import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration; @@ -30,6 +28,8 @@ */ @Module public class MetricsSystemModule { + /** Default constructor */ + public MetricsSystemModule() {} @Provides @Singleton diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/ReplaceableDoubleSupplier.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/ReplaceableDoubleSupplier.java new file mode 100644 index 00000000000..319ba74e253 --- /dev/null +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/ReplaceableDoubleSupplier.java @@ -0,0 +1,55 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.metrics; + +import java.util.function.DoubleSupplier; + +/** + * This class provides a replaceable double supplier. It allows to replace the current double + * supplier with a new one. + */ +public class ReplaceableDoubleSupplier implements DoubleSupplier { + private DoubleSupplier currentSupplier; + + /** + * Constructs a new ReplaceableDoubleSupplier with the given initial supplier. + * + * @param currentSupplier the initial double supplier + */ + public ReplaceableDoubleSupplier(final DoubleSupplier currentSupplier) { + this.currentSupplier = currentSupplier; + } + + /** + * Gets a double value from the current supplier. + * + * @return the double value supplied by the current supplier + */ + @Override + public double getAsDouble() { + return currentSupplier.getAsDouble(); + } + + /** + * Replaces the current double supplier with a new one. + * + * @param newSupplier the new double supplier + * @return this ReplaceableDoubleSupplier + */ + public ReplaceableDoubleSupplier replaceDoubleSupplier(final DoubleSupplier newSupplier) { + currentSupplier = newSupplier; + return this; + } +} diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/RunnableCounter.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/RunnableCounter.java index a942bcf3826..1ae4bbb6927 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/RunnableCounter.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/RunnableCounter.java @@ -23,10 +23,13 @@ public class RunnableCounter implements Counter { /** The Backed counter. */ protected final Counter backedCounter; + /** The Task. */ protected final Runnable task; + /** The Step. */ protected final int step; + /** The Step counter. */ protected final AtomicLong stepCounter; diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/RunnableTimedCounter.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/RunnableTimedCounter.java index 8b5ea7d2c90..9742f3527f5 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/RunnableTimedCounter.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/RunnableTimedCounter.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/SyncDurationMetrics.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/SyncDurationMetrics.java new file mode 100644 index 00000000000..d6fc9370097 --- /dev/null +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/SyncDurationMetrics.java @@ -0,0 +1,91 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.metrics; + +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; +import org.hyperledger.besu.plugin.services.metrics.OperationTimer; + +import java.util.HashMap; + +/** + * This class manages the synchronization duration metrics for the Hyperledger Besu project. It + * provides methods to start and stop timers for various synchronization phases. + */ +public class SyncDurationMetrics { + + /** A {@link SyncDurationMetrics} instance that does not record any metrics. */ + public static final SyncDurationMetrics NO_OP_SYNC_DURATION_METRICS = + new SyncDurationMetrics(new NoOpMetricsSystem()); + + private final LabelledMetric timer; + + private final HashMap timers = new HashMap<>(); + + /** + * Creates a new {@link SyncDurationMetrics} instance. + * + * @param metricsSystem The {@link MetricsSystem} to use to record metrics. + */ + public SyncDurationMetrics(final MetricsSystem metricsSystem) { + timer = + metricsSystem.createSimpleLabelledTimer( + BesuMetricCategory.SYNCHRONIZER, "sync_duration", "Time taken to sync", "name"); + } + + /** + * Starts a timer for the given synchronization phase. + * + * @param label The synchronization phase to start the timer for. + */ + public void startTimer(final Labels label) { + timers.computeIfAbsent(label.name(), k -> timer.labels(label.name()).startTimer()); + } + + /** + * Stops the timer for the given synchronization phase. + * + * @param label The synchronization phase to stop the timer for. + */ + public void stopTimer(final Labels label) { + OperationTimer.TimingContext context = timers.remove(label.name()); + if (context != null) { + context.stopTimer(); + } + } + + /** Enum representing the different synchronization phases. */ + public enum Labels { + /** + * Total time taken to get into sync. It is useful for SNAP and CHECKPOINT sync-modes only. + * + *

    Total sync duration includes the separate stages mentioned below, some of which occur in + * parallel. + * + *

    Total sync duration excludes the backwards sync stage due to implementation challenges. + * The backwards sync should be a very short duration following the other sync stages. + */ + TOTAL_SYNC_DURATION, + /** Time taken to download the chain data (headers, blocks, receipts). */ + CHAIN_DOWNLOAD_DURATION, + /** Time taken to download the initial world state, before the healing step. */ + SNAP_INITIAL_WORLD_STATE_DOWNLOAD_DURATION, + /** Time taken to heal the world state, after the initial download. */ + SNAP_WORLD_STATE_HEALING_DURATION, + /** Time taken to do the flat database heal. */ + FLAT_DB_HEAL; + } +} diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/noop/NoOpMetricsSystem.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/noop/NoOpMetricsSystem.java index 54ccc70d33e..2d1ee26cfd1 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/noop/NoOpMetricsSystem.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/noop/NoOpMetricsSystem.java @@ -36,35 +36,46 @@ public class NoOpMetricsSystem implements ObservableMetricsSystem { /** The constant NO_OP_COUNTER. */ public static final Counter NO_OP_COUNTER = new NoOpCounter(); + /** The constant NO_OP_GAUGE. */ public static final LabelledGauge NO_OP_GAUGE = new NoOpValueCollector(); private static final OperationTimer.TimingContext NO_OP_TIMING_CONTEXT = () -> 0; + /** The constant NO_OP_OPERATION_TIMER. */ public static final OperationTimer NO_OP_OPERATION_TIMER = () -> NO_OP_TIMING_CONTEXT; /** The constant NO_OP_LABELLED_1_COUNTER. */ public static final LabelledMetric NO_OP_LABELLED_1_COUNTER = new LabelCountingNoOpMetric<>(1, NO_OP_COUNTER); + /** The constant NO_OP_LABELLED_2_COUNTER. */ public static final LabelledMetric NO_OP_LABELLED_2_COUNTER = new LabelCountingNoOpMetric<>(2, NO_OP_COUNTER); + /** The constant NO_OP_LABELLED_3_COUNTER. */ public static final LabelledMetric NO_OP_LABELLED_3_COUNTER = new LabelCountingNoOpMetric<>(3, NO_OP_COUNTER); + /** The constant NO_OP_LABELLED_1_OPERATION_TIMER. */ public static final LabelledMetric NO_OP_LABELLED_1_OPERATION_TIMER = new LabelCountingNoOpMetric<>(1, NO_OP_OPERATION_TIMER); + /** The constant NO_OP_LABELLED_1_GAUGE. */ public static final LabelledGauge NO_OP_LABELLED_1_GAUGE = new LabelledGaugeNoOpMetric(1, NO_OP_GAUGE); + /** The constant NO_OP_LABELLED_2_GAUGE. */ public static final LabelledGauge NO_OP_LABELLED_2_GAUGE = new LabelledGaugeNoOpMetric(2, NO_OP_GAUGE); + /** The constant NO_OP_LABELLED_3_GAUGE. */ public static final LabelledGauge NO_OP_LABELLED_3_GAUGE = new LabelledGaugeNoOpMetric(3, NO_OP_GAUGE); + /** Default constructor */ + public NoOpMetricsSystem() {} + @Override public LabelledMetric createLabelledCounter( final MetricCategory category, @@ -93,6 +104,15 @@ public static LabelledMetric getCounterLabelledMetric(final int labelCo } } + @Override + public LabelledMetric createSimpleLabelledTimer( + final MetricCategory category, + final String name, + final String help, + final String... labelNames) { + return getOperationTimerLabelledMetric(labelNames.length); + } + @Override public LabelledMetric createLabelledTimer( final MetricCategory category, @@ -176,6 +196,7 @@ public static class LabelCountingNoOpMetric implements LabelledMetric { /** The Label count. */ final int labelCount; + /** The Fake metric. */ final T fakeMetric; @@ -203,6 +224,7 @@ public T labels(final String... labels) { public static class LabelledGaugeNoOpMetric implements LabelledGauge { /** The Label count. */ final int labelCount; + /** The Label values cache. */ final List labelValuesCache = new ArrayList<>(); diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/noop/NoOpValueCollector.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/noop/NoOpValueCollector.java index 42884a92ff6..144e7f3187c 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/noop/NoOpValueCollector.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/noop/NoOpValueCollector.java @@ -24,6 +24,9 @@ public class NoOpValueCollector implements LabelledGauge { private final List labelValuesCreated = new ArrayList<>(); + /** Default constructor */ + public NoOpValueCollector() {} + @Override public synchronized void labels(final DoubleSupplier valueSupplier, final String... labelValues) { final String labelValuesString = String.join(",", labelValues); diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/DebugMetricReader.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/DebugMetricReader.java index ea81e91aff9..bffb00c9fdf 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/DebugMetricReader.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/DebugMetricReader.java @@ -1,5 +1,5 @@ /* - * Copyright Besu Contributors + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,6 +15,7 @@ package org.hyperledger.besu.metrics.opentelemetry; import java.util.Collection; +import javax.annotation.Nonnull; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.metrics.InstrumentType; @@ -22,8 +23,6 @@ import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.metrics.export.CollectionRegistration; import io.opentelemetry.sdk.metrics.export.MetricReader; -import io.opentelemetry.sdk.metrics.internal.export.MetricProducer; -import org.jetbrains.annotations.NotNull; class DebugMetricReader implements MetricReader { private CollectionRegistration registration; @@ -31,11 +30,11 @@ class DebugMetricReader implements MetricReader { public DebugMetricReader() {} public Collection getAllMetrics() { - return MetricProducer.asMetricProducer(this.registration).collectAllMetrics(); + return registration.collectAllMetrics(); } @Override - public void register(final @NotNull CollectionRegistration registration) { + public void register(final @Nonnull CollectionRegistration registration) { this.registration = registration; } @@ -51,7 +50,7 @@ public CompletableResultCode shutdown() { @Override public AggregationTemporality getAggregationTemporality( - final @NotNull InstrumentType instrumentType) { + final @Nonnull InstrumentType instrumentType) { return AggregationTemporality.CUMULATIVE; } } diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/MetricsOtelPushService.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/MetricsOtelPushService.java index 3a8fbf3a622..7b7c1850674 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/MetricsOtelPushService.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/MetricsOtelPushService.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.metrics.opentelemetry; diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetrySystem.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetrySystem.java index 2c43eb8668e..ca1dc5dd3a3 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetrySystem.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/opentelemetry/OpenTelemetrySystem.java @@ -50,6 +50,7 @@ import io.opentelemetry.api.trace.TracerProvider; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdkBuilder; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.metrics.data.DoublePointData; @@ -61,7 +62,7 @@ import io.opentelemetry.sdk.metrics.data.SummaryPointData; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.SdkTracerProvider; -import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; +import io.opentelemetry.semconv.ResourceAttributes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -110,15 +111,18 @@ public OpenTelemetrySystem( .merge( Resource.create( Attributes.builder().put(ResourceAttributes.SERVICE_NAME, jobName).build())); - AutoConfiguredOpenTelemetrySdk autoSdk = + AutoConfiguredOpenTelemetrySdkBuilder autoSdkBuilder = AutoConfiguredOpenTelemetrySdk.builder() .addMeterProviderCustomizer( (provider, config) -> provider.setResource(resource).registerMetricReader(debugMetricReader)) - .addTracerProviderCustomizer((provider, config) -> provider.setResource(resource)) - .setResultAsGlobal(setAsGlobal) - .build(); - OpenTelemetrySdk sdk = autoSdk.getOpenTelemetrySdk(); + .addTracerProviderCustomizer((provider, config) -> provider.setResource(resource)); + + if (setAsGlobal) { + autoSdkBuilder.setResultAsGlobal(); + } + + OpenTelemetrySdk sdk = autoSdkBuilder.build().getOpenTelemetrySdk(); this.sdkMeterProvider = sdk.getSdkMeterProvider(); this.sdkTracerProvider = sdk.getSdkTracerProvider(); } @@ -227,6 +231,15 @@ public LabelledMetric createLabelledCounter( }); } + @Override + public LabelledMetric createSimpleLabelledTimer( + final MetricCategory category, + final String name, + final String help, + final String... labelNames) { + return createLabelledTimer(category, name, help, labelNames); + } + @Override public LabelledMetric createLabelledTimer( final MetricCategory category, diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/MetricsConfiguration.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/MetricsConfiguration.java index 6a2424df50a..4a542fce00b 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/MetricsConfiguration.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/MetricsConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -31,15 +31,19 @@ /** The Metrics configuration. */ public class MetricsConfiguration { private static final String DEFAULT_METRICS_HOST = "127.0.0.1"; + /** The constant DEFAULT_METRICS_PORT. */ public static final int DEFAULT_METRICS_PORT = 9545; private static final MetricsProtocol DEFAULT_METRICS_PROTOCOL = MetricsProtocol.PROMETHEUS; private static final String DEFAULT_METRICS_PUSH_HOST = "127.0.0.1"; + /** The constant DEFAULT_METRICS_PUSH_PORT. */ public static final int DEFAULT_METRICS_PUSH_PORT = 9001; + /** The constant DEFAULT_METRICS_TIMERS_ENABLED. */ public static final Boolean DEFAULT_METRICS_TIMERS_ENABLED = true; + /** The constant DEFAULT_METRICS_IDLE_TIMEOUT_SECONDS. */ public static final int DEFAULT_METRICS_IDLE_TIMEOUT_SECONDS = 60; diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/MetricsHttpService.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/MetricsHttpService.java index 2c2023c190a..94a198dc0e4 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/MetricsHttpService.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/MetricsHttpService.java @@ -15,7 +15,6 @@ package org.hyperledger.besu.metrics.prometheus; import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.collect.Streams.stream; import org.hyperledger.besu.metrics.MetricsService; import org.hyperledger.besu.plugin.services.MetricsSystem; @@ -26,13 +25,12 @@ import java.net.InetSocketAddress; import java.net.SocketException; import java.nio.charset.StandardCharsets; +import java.util.Locale; import java.util.Optional; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.CompletableFuture; -import com.google.common.base.Splitter; -import com.google.common.collect.Iterables; import io.netty.handler.codec.http.HttpResponseStatus; import io.prometheus.client.exporter.common.TextFormat; import io.vertx.core.Handler; @@ -41,6 +39,7 @@ import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.http.HttpServerResponse; +import io.vertx.core.net.HostAndPort; import io.vertx.ext.web.Router; import io.vertx.ext.web.RoutingContext; import org.slf4j.Logger; @@ -157,25 +156,16 @@ private Handler checkAllowlistHostHeader() { } private Optional getAndValidateHostHeader(final RoutingContext event) { - final String hostHeader = event.request().host(); - if (hostHeader == null) { - return Optional.empty(); - } - final Iterable splitHostHeader = Splitter.on(':').split(hostHeader); - final long hostPieces = stream(splitHostHeader).count(); - if (hostPieces > 1) { - // If the host contains a colon, verify the host is correctly formed - host [ ":" port ] - if (hostPieces > 2 || !Iterables.get(splitHostHeader, 1).matches("\\d{1,5}+")) { - return Optional.empty(); - } - } - return Optional.ofNullable(Iterables.get(splitHostHeader, 0)); + return Optional.ofNullable(event.request().authority()).map(HostAndPort::host); } private boolean hostIsInAllowlist(final String hostHeader) { if (config.getHostsAllowlist().stream() .anyMatch( - allowlistEntry -> allowlistEntry.toLowerCase().equals(hostHeader.toLowerCase()))) { + allowlistEntry -> + allowlistEntry + .toLowerCase(Locale.ROOT) + .equals(hostHeader.toLowerCase(Locale.ROOT)))) { return true; } else { LOG.trace("Host not in allowlist: '{}'", hostHeader); diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystem.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystem.java index fa07670521a..653f448311f 100644 --- a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystem.java +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystem.java @@ -32,7 +32,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.DoubleSupplier; import java.util.function.Supplier; -import java.util.stream.Collectors; import java.util.stream.Stream; import com.google.common.collect.ImmutableSet; @@ -41,6 +40,7 @@ import io.prometheus.client.Collector.MetricFamilySamples.Sample; import io.prometheus.client.CollectorRegistry; import io.prometheus.client.Counter; +import io.prometheus.client.Histogram; import io.prometheus.client.Summary; import io.prometheus.client.hotspot.BufferPoolsExports; import io.prometheus.client.hotspot.ClassLoadingExports; @@ -48,6 +48,7 @@ import io.prometheus.client.hotspot.MemoryPoolsExports; import io.prometheus.client.hotspot.StandardExports; import io.prometheus.client.hotspot.ThreadExports; +import io.vertx.core.impl.ConcurrentHashSet; /** The Prometheus metrics system. */ public class PrometheusMetricsSystem implements ObservableMetricsSystem { @@ -58,6 +59,7 @@ public class PrometheusMetricsSystem implements ObservableMetricsSystem { cachedCounters = new ConcurrentHashMap<>(); private final Map> cachedTimers = new ConcurrentHashMap<>(); + private final Set totalSuffixedCounters = new ConcurrentHashSet<>(); private final Set enabledCategories; private final boolean timersEnabled; @@ -95,7 +97,7 @@ public LabelledMetric crea final String name, final String help, final String... labelNames) { - final String metricName = convertToPrometheusName(category, name); + final String metricName = convertToPrometheusCounterName(category, name); return cachedCounters.computeIfAbsent( metricName, (k) -> { @@ -138,6 +140,27 @@ public LabelledMetric createLabelledTimer( }); } + @Override + public LabelledMetric createSimpleLabelledTimer( + final MetricCategory category, + final String name, + final String help, + final String... labelNames) { + final String metricName = convertToPrometheusName(category, name); + return cachedTimers.computeIfAbsent( + metricName, + (k) -> { + if (timersEnabled && isCategoryEnabled(category)) { + final Histogram histogram = + Histogram.build(metricName, help).labelNames(labelNames).buckets(1D).create(); + addCollectorUnchecked(category, histogram); + return new PrometheusSimpleTimer(histogram); + } else { + return NoOpMetricsSystem.getOperationTimerLabelledMetric(labelNames.length); + } + }); + } + @Override public void createGauge( final MetricCategory category, @@ -185,9 +208,7 @@ private void addCollectorUnchecked(final MetricCategory category, final Collecto category, key -> Collections.newSetFromMap(new ConcurrentHashMap<>())); final List newSamples = - metric.collect().stream() - .map(metricFamilySamples -> metricFamilySamples.name) - .collect(Collectors.toList()); + metric.collect().stream().map(metricFamilySamples -> metricFamilySamples.name).toList(); metrics.stream() .filter( @@ -230,6 +251,9 @@ private Observation createObservationFromSample( if (familySamples.type == Collector.Type.SUMMARY) { return convertSummarySampleNamesToLabels(category, sample, familySamples); } + if (familySamples.type == Collector.Type.COUNTER) { + return convertCounterNamesToLabels(category, sample, familySamples); + } return new Observation( category, convertFromPrometheusName(category, sample.name), @@ -237,6 +261,20 @@ private Observation createObservationFromSample( sample.labelValues); } + private Observation convertCounterNamesToLabels( + final MetricCategory category, final Sample sample, final MetricFamilySamples familySamples) { + final List labelValues = new ArrayList<>(sample.labelValues); + if (sample.name.endsWith("_created")) { + labelValues.add("created"); + } + + return new Observation( + category, + convertFromPrometheusCounterName(category, familySamples.name), + sample.value, + labelValues); + } + private Observation convertHistogramSampleNamesToLabels( final MetricCategory category, final Sample sample, final MetricFamilySamples familySamples) { final List labelValues = new ArrayList<>(sample.labelValues); @@ -259,6 +297,8 @@ private Observation convertSummarySampleNamesToLabels( labelValues.add("sum"); } else if (sample.name.endsWith("_count")) { labelValues.add("count"); + } else if (sample.name.endsWith("_created")) { + labelValues.add("created"); } else { labelValues.add(labelValues.size() - 1, "quantile"); } @@ -280,11 +320,34 @@ public String convertToPrometheusName(final MetricCategory category, final Strin return prometheusPrefix(category) + name; } + /** + * Convert to prometheus counter name. Prometheus adds a _total suffix to the name if not present, + * so we remember if the original name already has it, to be able to convert back correctly + * + * @param category the category + * @param name the name + * @return the name as string + */ + public String convertToPrometheusCounterName(final MetricCategory category, final String name) { + if (name.endsWith("_total")) { + totalSuffixedCounters.add(name); + } + return convertToPrometheusName(category, name); + } + private String convertFromPrometheusName(final MetricCategory category, final String metricName) { final String prefix = prometheusPrefix(category); return metricName.startsWith(prefix) ? metricName.substring(prefix.length()) : metricName; } + private String convertFromPrometheusCounterName( + final MetricCategory category, final String metricName) { + final String unPrefixedName = convertFromPrometheusName(category, metricName); + return totalSuffixedCounters.contains(unPrefixedName + "_total") + ? unPrefixedName + "_total" + : unPrefixedName; + } + private String prometheusPrefix(final MetricCategory category) { return category.getApplicationPrefix().orElse("") + category.getName() + "_"; } diff --git a/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusSimpleTimer.java b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusSimpleTimer.java new file mode 100644 index 00000000000..24799cb33d2 --- /dev/null +++ b/metrics/core/src/main/java/org/hyperledger/besu/metrics/prometheus/PrometheusSimpleTimer.java @@ -0,0 +1,35 @@ +/* + * Copyright ConsenSys AG. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.metrics.prometheus; + +import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; +import org.hyperledger.besu.plugin.services.metrics.OperationTimer; + +import io.prometheus.client.Histogram; + +class PrometheusSimpleTimer implements LabelledMetric { + + private final Histogram histogram; + + public PrometheusSimpleTimer(final Histogram histogram) { + this.histogram = histogram; + } + + @Override + public OperationTimer labels(final String... labels) { + final Histogram.Child metric = histogram.labels(labels); + return () -> metric.startTimer()::observeDuration; + } +} diff --git a/metrics/core/src/test-support/java/org/hyperledger/besu/metrics/StubMetricsSystem.java b/metrics/core/src/test-support/java/org/hyperledger/besu/metrics/StubMetricsSystem.java index 9e4b9d1ef96..2e0ea006db0 100644 --- a/metrics/core/src/test-support/java/org/hyperledger/besu/metrics/StubMetricsSystem.java +++ b/metrics/core/src/test-support/java/org/hyperledger/besu/metrics/StubMetricsSystem.java @@ -75,6 +75,15 @@ public LabelledMetric createLabelledTimer( return labelValues -> NoOpMetricsSystem.NO_OP_OPERATION_TIMER; } + @Override + public LabelledMetric createSimpleLabelledTimer( + final MetricCategory category, + final String name, + final String help, + final String... labelNames) { + return labelValues -> NoOpMetricsSystem.NO_OP_OPERATION_TIMER; + } + @Override public void createGauge( final MetricCategory category, diff --git a/metrics/core/src/test/java/org/hyperledger/besu/metrics/ReplaceableDoubleSupplierTest.java b/metrics/core/src/test/java/org/hyperledger/besu/metrics/ReplaceableDoubleSupplierTest.java new file mode 100644 index 00000000000..60164c09b39 --- /dev/null +++ b/metrics/core/src/test/java/org/hyperledger/besu/metrics/ReplaceableDoubleSupplierTest.java @@ -0,0 +1,36 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.metrics; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +public class ReplaceableDoubleSupplierTest { + + @Test + public void shouldWorkAsNormalSupplier() { + final var rds = new ReplaceableDoubleSupplier(() -> 1.0); + assertThat(rds.getAsDouble()).isEqualTo(1.0); + } + + @Test + public void shouldReturnValueFromNewSupplierIfReplaced() { + final var rds = new ReplaceableDoubleSupplier(() -> 1.0); + assertThat(rds.getAsDouble()).isEqualTo(1.0); + rds.replaceDoubleSupplier(() -> 2.0); + assertThat(rds.getAsDouble()).isEqualTo(2.0); + } +} diff --git a/metrics/core/src/test/java/org/hyperledger/besu/metrics/RunnableTimedCounterTest.java b/metrics/core/src/test/java/org/hyperledger/besu/metrics/RunnableTimedCounterTest.java index a6b79eca32c..3bc3a4f8b8b 100644 --- a/metrics/core/src/test/java/org/hyperledger/besu/metrics/RunnableTimedCounterTest.java +++ b/metrics/core/src/test/java/org/hyperledger/besu/metrics/RunnableTimedCounterTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/metrics/core/src/test/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystemTest.java b/metrics/core/src/test/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystemTest.java index 7c62f1f0b6c..2ddf86d347b 100644 --- a/metrics/core/src/test/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystemTest.java +++ b/metrics/core/src/test/java/org/hyperledger/besu/metrics/prometheus/PrometheusMetricsSystemTest.java @@ -17,6 +17,7 @@ import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; +import static java.util.function.Predicate.not; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.hyperledger.besu.metrics.BesuMetricCategory.DEFAULT_METRIC_CATEGORIES; @@ -51,6 +52,11 @@ public class PrometheusMetricsSystemTest { Comparator.comparing(observation -> observation.getCategory().getName()) .thenComparing(Observation::getMetricName) .thenComparing((o1, o2) -> o1.getLabels().equals(o2.getLabels()) ? 0 : 1); + private static final Comparator WITH_VALUES = + Comparator.comparing(observation -> observation.getCategory().getName()) + .thenComparing(Observation::getMetricName) + .thenComparing((o1, o2) -> o1.getLabels().equals(o2.getLabels()) ? 0 : 1) + .thenComparing((o1, o2) -> o1.getValue().equals(o2.getValue()) ? 0 : 1); @BeforeEach public void resetGlobalOpenTelemetry() { @@ -66,11 +72,17 @@ public void shouldCreateObservationFromCounter() { counter.inc(); assertThat(metricsSystem.streamObservations()) - .containsExactly(new Observation(PEERS, "connected", 1.0, emptyList())); + .usingElementComparator(this::compareCounters) + .containsExactlyInAnyOrder( + new Observation(PEERS, "connected", 1.0, emptyList()), + new Observation(PEERS, "connected", null, List.of("created"))); counter.inc(); assertThat(metricsSystem.streamObservations()) - .containsExactly(new Observation(PEERS, "connected", 2.0, emptyList())); + .usingElementComparator(this::compareCounters) + .containsExactly( + new Observation(PEERS, "connected", 2.0, emptyList()), + new Observation(PEERS, "connected", null, List.of("created"))); } @Test @@ -83,26 +95,36 @@ public void shouldHandleDuplicateCounterCreation() { counter1.labels().inc(); assertThat(metricsSystem.streamObservations()) - .containsExactly(new Observation(PEERS, "connected", 1.0, emptyList())); + .usingElementComparator(this::compareCounters) + .containsExactly( + new Observation(PEERS, "connected", 1.0, emptyList()), + new Observation(PEERS, "connected", null, List.of("created"))); counter2.labels().inc(); assertThat(metricsSystem.streamObservations()) - .containsExactly(new Observation(PEERS, "connected", 2.0, emptyList())); + .usingElementComparator(this::compareCounters) + .containsExactly( + new Observation(PEERS, "connected", 2.0, emptyList()), + new Observation(PEERS, "connected", null, List.of("created"))); } @Test public void shouldCreateSeparateObservationsForEachCounterLabelValue() { final LabelledMetric counter = - metricsSystem.createLabelledCounter(PEERS, "connected", "Some help string", "labelName"); + metricsSystem.createLabelledCounter( + PEERS, "connected_total", "Some help string", "labelName"); counter.labels("value1").inc(); counter.labels("value2").inc(); counter.labels("value1").inc(); assertThat(metricsSystem.streamObservations()) + .usingElementComparator(this::compareCounters) .containsExactlyInAnyOrder( - new Observation(PEERS, "connected", 2.0, singletonList("value1")), - new Observation(PEERS, "connected", 1.0, singletonList("value2"))); + new Observation(PEERS, "connected_total", 2.0, singletonList("value1")), + new Observation(PEERS, "connected_total", 1.0, singletonList("value2")), + new Observation(PEERS, "connected_total", null, List.of("value1", "created")), + new Observation(PEERS, "connected_total", null, List.of("value2", "created"))); } @Test @@ -138,11 +160,18 @@ public void shouldIncrementCounterBySpecifiedAmount() { counter.inc(5); assertThat(metricsSystem.streamObservations()) - .containsExactly(new Observation(PEERS, "connected", 5.0, emptyList())); + .usingElementComparator(this::compareCounters) + .containsExactly( + new Observation(PEERS, "connected", 5.0, emptyList()), + new Observation(PEERS, "connected", null, List.of("created"))); counter.inc(6); assertThat(metricsSystem.streamObservations()) - .containsExactly(new Observation(PEERS, "connected", 11.0, emptyList())); + .usingDefaultElementComparator() + .usingElementComparator(this::compareCounters) + .containsExactly( + new Observation(PEERS, "connected", 11.0, emptyList()), + new Observation(PEERS, "connected", null, List.of("created"))); } @Test @@ -162,7 +191,8 @@ public void shouldCreateObservationsFromTimer() { new Observation(RPC, "request", null, asList("quantile", "0.99")), new Observation(RPC, "request", null, asList("quantile", "1.0")), new Observation(RPC, "request", null, singletonList("sum")), - new Observation(RPC, "request", null, singletonList("count"))); + new Observation(RPC, "request", null, singletonList("count")), + new Observation(RPC, "request", null, singletonList("created"))); } @Test @@ -192,7 +222,8 @@ public void shouldCreateObservationsFromTimerWithLabels() { new Observation(RPC, "request", null, asList("method", "quantile", "0.99")), new Observation(RPC, "request", null, asList("method", "quantile", "1.0")), new Observation(RPC, "request", null, asList("method", "sum")), - new Observation(RPC, "request", null, asList("method", "count"))); + new Observation(RPC, "request", null, asList("method", "count")), + new Observation(RPC, "request", null, asList("method", "created"))); } @Test @@ -251,6 +282,8 @@ public void shouldOnlyObserveEnabledMetrics() { counterR.labels("op").inc(); assertThat(localMetricSystem.streamObservations()) + .usingRecursiveFieldByFieldElementComparator() + .filteredOn(not(this::isCreatedSample)) .containsExactly(new Observation(RPC, "name", 1.0, singletonList("op"))); } @@ -280,4 +313,18 @@ public void returnsNoOpMetricsWhenPushEnabled() { assertThat(localMetricSystem).isInstanceOf(PrometheusMetricsSystem.class); } + + private boolean isCreatedSample(final Observation obs) { + // Simple client 0.10.0 add a _created sample to every counter, histogram and summary, that we + // may want to ignore + return obs.getLabels().contains("created"); + } + + private int compareCounters(final Observation obs1, final Observation obs2) { + // for created samples ignore values + if (obs1.getLabels().contains("created") && obs2.getLabels().contains("created")) { + return IGNORE_VALUES.compare(obs1, obs2); + } + return WITH_VALUES.compare(obs1, obs2); + } } diff --git a/metrics/rocksdb/build.gradle b/metrics/rocksdb/build.gradle index 292909565d6..d46488daf8f 100644 --- a/metrics/rocksdb/build.gradle +++ b/metrics/rocksdb/build.gradle @@ -22,7 +22,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } diff --git a/metrics/rocksdb/src/main/java/org/hyperledger/besu/metrics/rocksdb/RocksDBStats.java b/metrics/rocksdb/src/main/java/org/hyperledger/besu/metrics/rocksdb/RocksDBStats.java index 01e7aca7a36..edb837985dd 100644 --- a/metrics/rocksdb/src/main/java/org/hyperledger/besu/metrics/rocksdb/RocksDBStats.java +++ b/metrics/rocksdb/src/main/java/org/hyperledger/besu/metrics/rocksdb/RocksDBStats.java @@ -22,6 +22,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Locale; import io.prometheus.client.Collector; import org.rocksdb.HistogramData; @@ -34,10 +35,13 @@ public class RocksDBStats { /** The Labels. */ static final List LABELS = Collections.singletonList("quantile"); + /** The Label 50. */ static final List LABEL_50 = Collections.singletonList("0.5"); + /** The Label 95. */ static final List LABEL_95 = Collections.singletonList("0.95"); + /** The Label 99. */ static final List LABEL_99 = Collections.singletonList("0.99"); @@ -160,6 +164,9 @@ public class RocksDBStats { HistogramType.READ_NUM_MERGE_OPERANDS, }; + /** Default constructor */ + private RocksDBStats() {} + /** * Register rocks db metrics. * @@ -175,7 +182,7 @@ public static void registerRocksDBMetrics( return; } for (final TickerType ticker : TICKERS) { - final String promCounterName = ticker.name().toLowerCase(); + final String promCounterName = ticker.name().toLowerCase(Locale.ROOT); metricsSystem.createLongGauge( category, promCounterName, @@ -192,7 +199,7 @@ private static Collector histogramToCollector( final Statistics stats, final HistogramType histogram) { return new Collector() { final String metricName = - KVSTORE_ROCKSDB_STATS.getName() + "_" + histogram.name().toLowerCase(); + KVSTORE_ROCKSDB_STATS.getName() + "_" + histogram.name().toLowerCase(Locale.ROOT); @Override public List collect() { diff --git a/nat/build.gradle b/nat/build.gradle index f6c814ad113..b0a85971746 100644 --- a/nat/build.gradle +++ b/nat/build.gradle @@ -22,7 +22,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } @@ -45,6 +46,4 @@ dependencies { testImplementation 'org.assertj:assertj-core' testImplementation 'org.junit.jupiter:junit-jupiter' testImplementation 'org.mockito:mockito-core' - - testRuntimeOnly 'org.junit.vintage:junit-vintage-engine' } diff --git a/nat/src/main/java/org/hyperledger/besu/nat/NatMethod.java b/nat/src/main/java/org/hyperledger/besu/nat/NatMethod.java index bbd1c02c40c..ed7d46a712c 100644 --- a/nat/src/main/java/org/hyperledger/besu/nat/NatMethod.java +++ b/nat/src/main/java/org/hyperledger/besu/nat/NatMethod.java @@ -18,7 +18,7 @@ public enum NatMethod { /** Upnp nat method. */ UPNP, - /** Upnpp 2 ponly nat method. */ + /** Upnp p2p only nat method. */ UPNPP2PONLY, /** Docker nat method. */ DOCKER, diff --git a/nat/src/main/java/org/hyperledger/besu/nat/core/IpDetector.java b/nat/src/main/java/org/hyperledger/besu/nat/core/IpDetector.java index 55e5861d2ec..faff6398638 100644 --- a/nat/src/main/java/org/hyperledger/besu/nat/core/IpDetector.java +++ b/nat/src/main/java/org/hyperledger/besu/nat/core/IpDetector.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.nat.core; import java.util.Optional; diff --git a/nat/src/main/java/org/hyperledger/besu/nat/core/NatMethodDetector.java b/nat/src/main/java/org/hyperledger/besu/nat/core/NatMethodDetector.java index 08cee0dd5b3..73c161b2455 100644 --- a/nat/src/main/java/org/hyperledger/besu/nat/core/NatMethodDetector.java +++ b/nat/src/main/java/org/hyperledger/besu/nat/core/NatMethodDetector.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.nat.core; import org.hyperledger.besu.nat.NatMethod; diff --git a/nat/src/main/java/org/hyperledger/besu/nat/core/domain/NatPortMapping.java b/nat/src/main/java/org/hyperledger/besu/nat/core/domain/NatPortMapping.java index 3b1ea6a14e2..d980c90bd47 100644 --- a/nat/src/main/java/org/hyperledger/besu/nat/core/domain/NatPortMapping.java +++ b/nat/src/main/java/org/hyperledger/besu/nat/core/domain/NatPortMapping.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.nat.core.domain; /** This class describes a NAT configuration. */ diff --git a/nat/src/main/java/org/hyperledger/besu/nat/core/domain/NatServiceType.java b/nat/src/main/java/org/hyperledger/besu/nat/core/domain/NatServiceType.java index 2702254cadd..666ba9b2880 100644 --- a/nat/src/main/java/org/hyperledger/besu/nat/core/domain/NatServiceType.java +++ b/nat/src/main/java/org/hyperledger/besu/nat/core/domain/NatServiceType.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.nat.core.domain; /** diff --git a/nat/src/main/java/org/hyperledger/besu/nat/core/domain/NetworkProtocol.java b/nat/src/main/java/org/hyperledger/besu/nat/core/domain/NetworkProtocol.java index a8ab4939bab..bc2f0fb39d8 100644 --- a/nat/src/main/java/org/hyperledger/besu/nat/core/domain/NetworkProtocol.java +++ b/nat/src/main/java/org/hyperledger/besu/nat/core/domain/NetworkProtocol.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.nat.core.domain; /** diff --git a/nat/src/main/java/org/hyperledger/besu/nat/docker/DockerDetector.java b/nat/src/main/java/org/hyperledger/besu/nat/docker/DockerDetector.java index 5504def9c8d..1188759f630 100644 --- a/nat/src/main/java/org/hyperledger/besu/nat/docker/DockerDetector.java +++ b/nat/src/main/java/org/hyperledger/besu/nat/docker/DockerDetector.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.nat.docker; import org.hyperledger.besu.nat.NatMethod; @@ -26,6 +25,8 @@ /** The Docker detector. */ public class DockerDetector implements NatMethodDetector { + /** Default constructor */ + public DockerDetector() {} @Override public Optional detect() { @@ -33,6 +34,8 @@ public Optional detect() { return stream .filter(line -> line.contains("/docker")) .findFirst() + // fallback to looking for /.dockerenv in case we are running on Docker for Mac + .or(() -> Optional.ofNullable(Files.exists(Paths.get("/.dockerenv")) ? "docker" : null)) .map(__ -> NatMethod.DOCKER); } catch (IOException e) { return Optional.empty(); diff --git a/nat/src/main/java/org/hyperledger/besu/nat/docker/DockerNatManager.java b/nat/src/main/java/org/hyperledger/besu/nat/docker/DockerNatManager.java index da2fd0e1a7d..002fe7b4ec4 100644 --- a/nat/src/main/java/org/hyperledger/besu/nat/docker/DockerNatManager.java +++ b/nat/src/main/java/org/hyperledger/besu/nat/docker/DockerNatManager.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.nat.docker; import org.hyperledger.besu.nat.NatMethod; diff --git a/nat/src/main/java/org/hyperledger/besu/nat/docker/HostBasedIpDetector.java b/nat/src/main/java/org/hyperledger/besu/nat/docker/HostBasedIpDetector.java index 34922624c7b..c5a5b6c4419 100644 --- a/nat/src/main/java/org/hyperledger/besu/nat/docker/HostBasedIpDetector.java +++ b/nat/src/main/java/org/hyperledger/besu/nat/docker/HostBasedIpDetector.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.nat.docker; import org.hyperledger.besu.nat.core.IpDetector; @@ -26,7 +25,11 @@ public class HostBasedIpDetector implements IpDetector { private static final String HOSTNAME = "HOST_IP"; + /** Default constructor */ + public HostBasedIpDetector() {} + @Override + @SuppressWarnings("AddressSelection") public Optional detectAdvertisedIp() { try { return Optional.of(InetAddress.getByName(HOSTNAME).getHostAddress()); diff --git a/nat/src/main/java/org/hyperledger/besu/nat/kubernetes/KubernetesDetector.java b/nat/src/main/java/org/hyperledger/besu/nat/kubernetes/KubernetesDetector.java index 4176216869c..8165a0c3c8b 100644 --- a/nat/src/main/java/org/hyperledger/besu/nat/kubernetes/KubernetesDetector.java +++ b/nat/src/main/java/org/hyperledger/besu/nat/kubernetes/KubernetesDetector.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.nat.kubernetes; import org.hyperledger.besu.nat.NatMethod; @@ -33,6 +32,9 @@ public class KubernetesDetector implements NatMethodDetector { Optional.ofNullable(System.getenv("KUBERNETES_SERVICE_HOST")); private static final Path KUBERNETES_WATERMARK_FILE = Paths.get("var/run/secrets/kubernetes.io"); + /** Default constructor */ + public KubernetesDetector() {} + @Override public Optional detect() { return KUBERNETES_SERVICE_HOST diff --git a/nat/src/main/java/org/hyperledger/besu/nat/kubernetes/KubernetesNatManager.java b/nat/src/main/java/org/hyperledger/besu/nat/kubernetes/KubernetesNatManager.java index e60b7788642..f8af2bdddbd 100644 --- a/nat/src/main/java/org/hyperledger/besu/nat/kubernetes/KubernetesNatManager.java +++ b/nat/src/main/java/org/hyperledger/besu/nat/kubernetes/KubernetesNatManager.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.nat.kubernetes; import org.hyperledger.besu.nat.NatMethod; @@ -43,7 +42,7 @@ import org.slf4j.LoggerFactory; /** - * This class describes the behaviour of the Kubernetes NAT manager. Kubernetes Nat manager add + * This class describes the behavior of the Kubernetes NAT manager. Kubernetes Nat manager add * support for Kubernetes’s NAT implementation when Besu is being run from a Kubernetes cluster */ public class KubernetesNatManager extends AbstractNatManager { diff --git a/nat/src/main/java/org/hyperledger/besu/nat/upnp/UpnpNatManager.java b/nat/src/main/java/org/hyperledger/besu/nat/upnp/UpnpNatManager.java index 22cc1ce13f1..292fb0b0079 100644 --- a/nat/src/main/java/org/hyperledger/besu/nat/upnp/UpnpNatManager.java +++ b/nat/src/main/java/org/hyperledger/besu/nat/upnp/UpnpNatManager.java @@ -101,7 +101,10 @@ public UpnpNatManager() { new BesuUpnpRegistryListener() { @Override public void remoteDeviceAdded(final Registry registry, final RemoteDevice device) { - LOG.debug("UPnP Device discovered: " + device.getDetails().getFriendlyName()); + LOG.atDebug() + .setMessage("UPnP Device discovered: {}") + .addArgument(device.getDetails().getFriendlyName()) + .log(); inspectDeviceRecursive(device, recognizedServices.keySet()); } }; @@ -466,7 +469,7 @@ public void failure( futures.add(future); } - // return a future that completes succeessfully only when each of our port delete requests + // return a future that completes successfully only when each of our port delete requests // complete return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); } diff --git a/nat/src/test/java/org/hyperledger/besu/nat/NatServiceTest.java b/nat/src/test/java/org/hyperledger/besu/nat/NatServiceTest.java index ae51ab6a352..62bc8834914 100644 --- a/nat/src/test/java/org/hyperledger/besu/nat/NatServiceTest.java +++ b/nat/src/test/java/org/hyperledger/besu/nat/NatServiceTest.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.nat; import static org.assertj.core.api.Assertions.assertThat; diff --git a/nat/src/test/java/org/hyperledger/besu/nat/core/AbstractNatManagerTest.java b/nat/src/test/java/org/hyperledger/besu/nat/core/AbstractNatManagerTest.java index 768d2a2bd01..330a21dba08 100644 --- a/nat/src/test/java/org/hyperledger/besu/nat/core/AbstractNatManagerTest.java +++ b/nat/src/test/java/org/hyperledger/besu/nat/core/AbstractNatManagerTest.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.nat.core; import static org.assertj.core.api.Assertions.assertThat; diff --git a/pki/build.gradle b/pki/build.gradle index c9021b3a83e..15f1e8c7d6f 100644 --- a/pki/build.gradle +++ b/pki/build.gradle @@ -22,7 +22,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } @@ -34,12 +35,9 @@ dependencies { implementation 'io.tmio:tuweni-bytes' implementation 'org.bouncycastle:bcpkix-jdk18on' - testImplementation 'junit:junit' - testImplementation 'org.assertj:assertj-core' testImplementation 'org.junit.jupiter:junit-jupiter' testImplementation 'org.mockito:mockito-core' - - testRuntimeOnly 'org.junit.vintage:junit-vintage-engine' + testImplementation 'org.mockito:mockito-junit-jupiter' } configurations { testArtifacts } diff --git a/pki/src/main/java/org/hyperledger/besu/pki/cms/CmsCreator.java b/pki/src/main/java/org/hyperledger/besu/pki/cms/CmsCreator.java index 57bc78908e3..088bf2eaed6 100644 --- a/pki/src/main/java/org/hyperledger/besu/pki/cms/CmsCreator.java +++ b/pki/src/main/java/org/hyperledger/besu/pki/cms/CmsCreator.java @@ -134,8 +134,9 @@ public static String getPreferredSignatureAlgorithm(final PublicKey pub) { case "RSA" -> { return "SHA256WithRSAEncryption"; } - default -> throw new UnsupportedOperationException( - "Private key algorithm not supported: " + pub.getAlgorithm()); + default -> + throw new UnsupportedOperationException( + "Private key algorithm not supported: " + pub.getAlgorithm()); } } } diff --git a/pki/src/main/java/org/hyperledger/besu/pki/config/PkiKeyStoreConfiguration.java b/pki/src/main/java/org/hyperledger/besu/pki/config/PkiKeyStoreConfiguration.java index 57e330558cc..83ce80f0e4d 100644 --- a/pki/src/main/java/org/hyperledger/besu/pki/config/PkiKeyStoreConfiguration.java +++ b/pki/src/main/java/org/hyperledger/besu/pki/config/PkiKeyStoreConfiguration.java @@ -29,6 +29,7 @@ public class PkiKeyStoreConfiguration { /** The constant DEFAULT_KEYSTORE_TYPE. */ public static String DEFAULT_KEYSTORE_TYPE = "PKCS12"; + /** The constant DEFAULT_CERTIFICATE_ALIAS. */ public static String DEFAULT_CERTIFICATE_ALIAS = "validator"; diff --git a/pki/src/main/java/org/hyperledger/besu/pki/crl/CRLUtil.java b/pki/src/main/java/org/hyperledger/besu/pki/crl/CRLUtil.java index a298736b4ad..17d3ecea765 100644 --- a/pki/src/main/java/org/hyperledger/besu/pki/crl/CRLUtil.java +++ b/pki/src/main/java/org/hyperledger/besu/pki/crl/CRLUtil.java @@ -27,6 +27,8 @@ /** The CRL util. */ public class CRLUtil { + /** Default constructor */ + private CRLUtil() {} /** * Load CRLs cert store. diff --git a/pki/src/main/java/org/hyperledger/besu/pki/keystore/KeyStoreWrapper.java b/pki/src/main/java/org/hyperledger/besu/pki/keystore/KeyStoreWrapper.java index 36e2390e1b4..d9a5f199fc8 100644 --- a/pki/src/main/java/org/hyperledger/besu/pki/keystore/KeyStoreWrapper.java +++ b/pki/src/main/java/org/hyperledger/besu/pki/keystore/KeyStoreWrapper.java @@ -26,8 +26,10 @@ public interface KeyStoreWrapper { /** The constant KEYSTORE_TYPE_JKS. */ String KEYSTORE_TYPE_JKS = "JKS"; + /** The constant KEYSTORE_TYPE_PKCS11. */ String KEYSTORE_TYPE_PKCS11 = "PKCS11"; + /** The constant KEYSTORE_TYPE_PKCS12. */ String KEYSTORE_TYPE_PKCS12 = "PKCS12"; diff --git a/pki/src/test/java/org/hyperledger/besu/pki/cms/CmsCreationAndValidationTest.java b/pki/src/test/java/org/hyperledger/besu/pki/cms/CmsCreationAndValidationTest.java deleted file mode 100644 index ceb30e1e00f..00000000000 --- a/pki/src/test/java/org/hyperledger/besu/pki/cms/CmsCreationAndValidationTest.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.besu.pki.cms; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.hyperledger.besu.pki.util.TestCertificateUtils.Algorithm.EC; -import static org.hyperledger.besu.pki.util.TestCertificateUtils.Algorithm.RSA; - -import org.hyperledger.besu.pki.util.TestCertificateUtils.Algorithm; - -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.cert.X509Certificate; - -import org.apache.tuweni.bytes.Bytes; -import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; -import org.bouncycastle.cms.CMSProcessableByteArray; -import org.bouncycastle.cms.CMSSignedData; -import org.bouncycastle.cms.CMSSignedDataGenerator; -import org.bouncycastle.cms.CMSTypedData; -import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder; -import org.bouncycastle.operator.ContentSigner; -import org.bouncycastle.operator.DigestCalculatorProvider; -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; -import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EnumSource; - -public class CmsCreationAndValidationTest { - - private static final CmsTestKeystores rsaTestKeystores = new CmsTestKeystores(RSA); - private static final CmsTestKeystores ecTestKeystores = new CmsTestKeystores(EC); - - private CmsTestKeystores getCmsTestKeystores(final Algorithm algorithm) { - return switch (algorithm) { - case RSA -> rsaTestKeystores; - case EC -> ecTestKeystores; - }; - } - - @ParameterizedTest - @EnumSource(value = Algorithm.class) - public void cmsValidationWithEmptyCmsMessage(final Algorithm algorithm) { - final Bytes data = Bytes.random(32); - - assertThat(getCmsTestKeystores(algorithm).getCmsValidator().validate(Bytes.EMPTY, data)) - .isFalse(); - } - - @ParameterizedTest - @EnumSource(value = Algorithm.class) - public void cmsValidationWithTrustedSelfSignedCertificate(final Algorithm algorithm) { - final CmsCreator cmsCreator = - new CmsCreator(getCmsTestKeystores(algorithm).getKeystoreWrapper(), "trusted_selfsigned"); - final Bytes data = Bytes.random(32); - - final Bytes cms = cmsCreator.create(data); - - assertThat(getCmsTestKeystores(algorithm).getCmsValidator().validate(cms, data)).isTrue(); - } - - @ParameterizedTest - @EnumSource(value = Algorithm.class) - public void cmsValidationWithUntrustedSelfSignedCertificate(final Algorithm algorithm) { - final CmsCreator cmsCreator = - new CmsCreator(getCmsTestKeystores(algorithm).getKeystoreWrapper(), "untrusted_selfsigned"); - final Bytes data = Bytes.random(32); - - final Bytes cms = cmsCreator.create(data); - - assertThat(getCmsTestKeystores(algorithm).getCmsValidator().validate(cms, data)).isFalse(); - } - - @ParameterizedTest - @EnumSource(value = Algorithm.class) - public void cmsValidationWithTrustedChain(final Algorithm algorithm) { - final CmsCreator cmsCreator = - new CmsCreator(getCmsTestKeystores(algorithm).getKeystoreWrapper(), "trusted"); - final Bytes data = Bytes.random(32); - - final Bytes cms = cmsCreator.create(data); - - assertThat(getCmsTestKeystores(algorithm).getCmsValidator().validate(cms, data)).isTrue(); - } - - @ParameterizedTest - @EnumSource(value = Algorithm.class) - public void cmsValidationWithUntrustedChain(final Algorithm algorithm) { - final CmsCreator cmsCreator = - new CmsCreator(getCmsTestKeystores(algorithm).getKeystoreWrapper(), "untrusted"); - final Bytes data = Bytes.random(32); - - final Bytes cms = cmsCreator.create(data); - - assertThat(getCmsTestKeystores(algorithm).getCmsValidator().validate(cms, data)).isFalse(); - } - - @ParameterizedTest - @EnumSource(value = Algorithm.class) - public void cmsValidationWithExpiredCertificate(final Algorithm algorithm) { - final CmsCreator cmsCreator = - new CmsCreator(getCmsTestKeystores(algorithm).getKeystoreWrapper(), "expired"); - final Bytes data = Bytes.random(32); - - final Bytes cms = cmsCreator.create(data); - - assertThat(getCmsTestKeystores(algorithm).getCmsValidator().validate(cms, data)).isFalse(); - } - - @ParameterizedTest - @EnumSource(value = Algorithm.class) - public void cmsValidationWithRevokedCertificate(final Algorithm algorithm) { - final CmsCreator cmsCreator = - new CmsCreator(getCmsTestKeystores(algorithm).getKeystoreWrapper(), "revoked"); - final Bytes data = Bytes.random(32); - - final Bytes cms = cmsCreator.create(data); - - assertThat(getCmsTestKeystores(algorithm).getCmsValidator().validate(cms, data)).isFalse(); - } - - @ParameterizedTest - @EnumSource(value = Algorithm.class) - public void cmsValidationWithoutCRLConfigDisablesCRLCheck(final Algorithm algorithm) { - final CmsCreator cmsCreator = - new CmsCreator(getCmsTestKeystores(algorithm).getKeystoreWrapper(), "revoked"); - final Bytes data = Bytes.random(32); - - final Bytes cms = cmsCreator.create(data); - - CmsValidator cmsValidator = getCmsTestKeystores(algorithm).getCmsValidatorWithoutCrl(); - - // Because we don't have a CRL CertStore, revocation is not checked - assertThat(cmsValidator.validate(cms, data)).isTrue(); - } - - @ParameterizedTest - @EnumSource(value = Algorithm.class) - public void cmsValidationWithWrongSignedData(final Algorithm algorithm) { - final CmsCreator cmsCreator = - new CmsCreator(getCmsTestKeystores(algorithm).getKeystoreWrapper(), "trusted"); - final Bytes otherData = Bytes.random(32); - final Bytes cms = cmsCreator.create(otherData); - - final Bytes expectedData = Bytes.random(32); - assertThat(getCmsTestKeystores(algorithm).getCmsValidator().validate(cms, expectedData)) - .isFalse(); - } - - @ParameterizedTest - @EnumSource(value = Algorithm.class) - public void cmsValidationWithInvalidSignature(final Algorithm algorithm) throws Exception { - // Create a CMS message signed with a certificate, but create SignerInfo using another - // certificate to trigger the signature verification to fail. - - final PrivateKey privateKey = - getCmsTestKeystores(algorithm).getKeystoreWrapper().getPrivateKey("trusted"); - final PublicKey publicKey = - getCmsTestKeystores(algorithm).getKeystoreWrapper().getPublicKey("trusted"); - final X509Certificate signerCertificate = - (X509Certificate) - getCmsTestKeystores(algorithm).getKeystoreWrapper().getCertificate("trusted"); - final X509Certificate otherCertificate = - (X509Certificate) - getCmsTestKeystores(algorithm) - .getKeystoreWrapper() - .getCertificate("trusted_selfsigned"); - - final ContentSigner contentSigner = - new JcaContentSignerBuilder(CmsCreator.getPreferredSignatureAlgorithm(publicKey)) - .build(privateKey); - - final CMSSignedDataGenerator cmsGenerator = new CMSSignedDataGenerator(); - cmsGenerator.addCertificate(new JcaX509CertificateHolder(signerCertificate)); - cmsGenerator.addCertificate(new JcaX509CertificateHolder(otherCertificate)); - - final DigestCalculatorProvider digestCalculatorProvider = - new JcaDigestCalculatorProviderBuilder().setProvider("BC").build(); - cmsGenerator.addSignerInfoGenerator( - new JcaSignerInfoGeneratorBuilder(digestCalculatorProvider) - .build(contentSigner, otherCertificate)); - - final Bytes expectedData = Bytes.random(32); - final CMSTypedData cmsData = new CMSProcessableByteArray(expectedData.toArray()); - final CMSSignedData cmsSignedData = cmsGenerator.generate(cmsData, true); - final Bytes cmsBytes = Bytes.wrap(cmsSignedData.getEncoded()); - - assertThat(getCmsTestKeystores(algorithm).getCmsValidator().validate(cmsBytes, expectedData)) - .isFalse(); - } -} diff --git a/pki/src/test/java/org/hyperledger/besu/pki/cms/CmsTestKeystores.java b/pki/src/test/java/org/hyperledger/besu/pki/cms/CmsTestKeystores.java deleted file mode 100644 index 3a2d4c04f8f..00000000000 --- a/pki/src/test/java/org/hyperledger/besu/pki/cms/CmsTestKeystores.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.pki.cms; - -import static org.hyperledger.besu.pki.util.TestCertificateUtils.createCRL; -import static org.hyperledger.besu.pki.util.TestCertificateUtils.createKeyPair; -import static org.hyperledger.besu.pki.util.TestCertificateUtils.createSelfSignedCertificate; -import static org.hyperledger.besu.pki.util.TestCertificateUtils.issueCertificate; - -import org.hyperledger.besu.pki.keystore.KeyStoreWrapper; -import org.hyperledger.besu.pki.keystore.SoftwareKeyStoreWrapper; -import org.hyperledger.besu.pki.util.TestCertificateUtils.Algorithm; - -import java.security.KeyPair; -import java.security.KeyStore; -import java.security.cert.Certificate; -import java.security.cert.X509CRL; -import java.security.cert.X509Certificate; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -public class CmsTestKeystores { - private KeyStore keystore; - private KeyStore truststore; - private List CRLs; - - private KeyStoreWrapper keystoreWrapper; - private KeyStoreWrapper truststoreWrapper; - private KeyStoreWrapper truststoreWrapperWithoutCrl; - private CmsValidator cmsValidator; - private CmsValidator cmsValidatorWithoutCrl; - - public CmsTestKeystores(final Algorithm algorithm) { - try { - init(algorithm); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public void init(final Algorithm algorithm) throws Exception { - final Instant notBefore = Instant.now().minus(1, ChronoUnit.DAYS); - final Instant notAfter = Instant.now().plus(1, ChronoUnit.DAYS); - - /* - Create self-signed certificate - */ - final KeyPair selfsignedKeyPair = createKeyPair(algorithm); - final X509Certificate selfsignedCertificate = - createSelfSignedCertificate("selfsigned", notBefore, notAfter, selfsignedKeyPair); - - /* - Create trusted chain (ca -> interca -> partneraca -> partneravalidator) - */ - final KeyPair caKeyPair = createKeyPair(algorithm); - final X509Certificate caCertificate = - createSelfSignedCertificate("ca", notBefore, notAfter, caKeyPair); - - final KeyPair interCAKeyPair = createKeyPair(algorithm); - final X509Certificate interCACertificate = - issueCertificate( - caCertificate, caKeyPair, "interca", notBefore, notAfter, interCAKeyPair, true); - - final KeyPair partnerACAPair = createKeyPair(algorithm); - final X509Certificate partnerACACertificate = - issueCertificate( - interCACertificate, - interCAKeyPair, - "partneraca", - notBefore, - notAfter, - partnerACAPair, - true); - - final KeyPair parterAValidatorKeyPair = createKeyPair(algorithm); - final X509Certificate partnerAValidatorCertificate = - issueCertificate( - partnerACACertificate, - partnerACAPair, - "partneravalidator", - notBefore, - notAfter, - parterAValidatorKeyPair, - false); - - /* - Create expired certificate - */ - final KeyPair expiredKeyPair = createKeyPair(algorithm); - final X509Certificate expiredCertificate = - issueCertificate( - caCertificate, - caKeyPair, - "expired", - notBefore, - notBefore.plus(1, ChronoUnit.SECONDS), - expiredKeyPair, - true); - - /* - Create revoked and revoked certificates - */ - final KeyPair revokedKeyPair = createKeyPair(algorithm); - final X509Certificate revokedCertificate = - issueCertificate( - caCertificate, caKeyPair, "revoked", notBefore, notAfter, revokedKeyPair, true); - - /* - Create untrusted chain (untrusted_selfsigned -> unstrusted_partner) - */ - final KeyPair untrustedSelfSignedKeyPair = createKeyPair(algorithm); - final X509Certificate untrustedSelfsignedCertificate = - createSelfSignedCertificate( - "untrusted_selfsigned", notBefore, notAfter, untrustedSelfSignedKeyPair); - - final KeyPair untrustedIntermediateKeyPair = createKeyPair(algorithm); - final X509Certificate untrustedIntermediateCertificate = - issueCertificate( - untrustedSelfsignedCertificate, - untrustedSelfSignedKeyPair, - "unstrusted_partner", - notBefore, - notAfter, - untrustedIntermediateKeyPair, - true); - - /* - Create truststore wrapper with 3 trusted certificates: 'ca', 'interca' and 'selfsigned' - */ - truststore = KeyStore.getInstance("PKCS12"); - truststore.load(null, null); - - truststore.setCertificateEntry("ca", caCertificate); - truststore.setCertificateEntry("interca", interCACertificate); - truststore.setCertificateEntry("selfsigned", selfsignedCertificate); - - /* - Create keystore with certificates used in tests - */ - keystore = KeyStore.getInstance("PKCS12"); - keystore.load(null, null); - - keystore.setKeyEntry( - "trusted_selfsigned", - selfsignedKeyPair.getPrivate(), - "".toCharArray(), - new Certificate[] {selfsignedCertificate}); - keystore.setKeyEntry( - "untrusted_selfsigned", - untrustedSelfSignedKeyPair.getPrivate(), - "".toCharArray(), - new Certificate[] {untrustedSelfsignedCertificate}); - keystore.setKeyEntry( - "expired", - expiredKeyPair.getPrivate(), - "".toCharArray(), - new Certificate[] {expiredCertificate}); - keystore.setKeyEntry( - "revoked", - revokedKeyPair.getPrivate(), - "".toCharArray(), - new Certificate[] {revokedCertificate}); - keystore.setKeyEntry( - "trusted", - parterAValidatorKeyPair.getPrivate(), - "".toCharArray(), - new Certificate[] {partnerAValidatorCertificate, partnerACACertificate}); - keystore.setKeyEntry( - "untrusted", - untrustedIntermediateKeyPair.getPrivate(), - "".toCharArray(), - new Certificate[] {untrustedIntermediateCertificate, untrustedSelfsignedCertificate}); - - /* - Create CRLs for all CA certificates (mostly empty, only ca has one revoked certificate) - */ - final X509CRL caCRL = createCRL(caCertificate, caKeyPair, Set.of(revokedCertificate)); - final X509CRL intercaCRL = - createCRL(interCACertificate, interCAKeyPair, Collections.emptyList()); - final X509CRL partnerACACRL = - createCRL(partnerACACertificate, partnerACAPair, Collections.emptyList()); - final X509CRL selfsignedCRL = - createCRL(selfsignedCertificate, selfsignedKeyPair, Collections.emptyList()); - CRLs = List.of(caCRL, intercaCRL, partnerACACRL, selfsignedCRL); - - keystoreWrapper = new SoftwareKeyStoreWrapper(null, keystore, ""); - - truststoreWrapper = new SoftwareKeyStoreWrapper(CRLs, truststore, ""); - - truststoreWrapperWithoutCrl = new SoftwareKeyStoreWrapper(null, truststore, ""); - - cmsValidator = new CmsValidator(truststoreWrapper); - - cmsValidatorWithoutCrl = new CmsValidator(truststoreWrapperWithoutCrl); - } - - public KeyStore getKeystore() { - return keystore; - } - - public KeyStore getTruststore() { - return truststore; - } - - public List getCRLs() { - return CRLs; - } - - public KeyStoreWrapper getKeystoreWrapper() { - return keystoreWrapper; - } - - public KeyStoreWrapper getTruststoreWrapper() { - return truststoreWrapper; - } - - public CmsValidator getCmsValidator() { - return cmsValidator; - } - - public KeyStoreWrapper getTruststoreWrapperWithoutCrl() { - return truststoreWrapperWithoutCrl; - } - - public CmsValidator getCmsValidatorWithoutCrl() { - return cmsValidatorWithoutCrl; - } -} diff --git a/pki/src/test/java/org/hyperledger/besu/pki/keystore/BaseKeyStoreFileWrapperTest.java b/pki/src/test/java/org/hyperledger/besu/pki/keystore/BaseKeyStoreFileWrapperTest.java deleted file mode 100644 index 254e8bf23c7..00000000000 --- a/pki/src/test/java/org/hyperledger/besu/pki/keystore/BaseKeyStoreFileWrapperTest.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.pki.keystore; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.nio.file.Path; -import java.security.cert.Certificate; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -@RunWith(Parameterized.class) -public abstract class BaseKeyStoreFileWrapperTest { - protected static final String KEYSTORE_VALID_KEY_ALIAS = "partner1client1"; - protected static final String KEYSTORE_INVALID_KEY_ALIAS = "partner1clientinvalid"; - protected static final String TRUSTSTORE_VALID_CERTIFICATE_ALIAS = "interca"; - protected static final String TRUSTSTORE_INVALID_CERTIFICATE_ALIAS = "interca-invalid"; - - @Parameterized.Parameter public String keyStoreWrapperDescription; - - @Parameterized.Parameter(1) - public boolean keystoreWrapperConfiguredWithTruststore; - - @Parameterized.Parameter(2) - public KeyStoreWrapper keyStoreWrapper; - - protected static Path toPath(final String path) throws Exception { - return null == path - ? null - : Path.of(BaseKeyStoreFileWrapperTest.class.getResource(path).toURI()); - } - - @Test - public void getPublicKey_WithValidAlias_ReturnsExpectedValue() { - assertThat(keyStoreWrapper.getPublicKey(KEYSTORE_VALID_KEY_ALIAS)) - .as("Public key is not null") - .isNotNull(); - } - - @Test - public void getPublicKey_WithInvalidAlias_ReturnsExpectedValue() { - assertThat(keyStoreWrapper.getPublicKey(KEYSTORE_INVALID_KEY_ALIAS)) - .as("Public key is null") - .isNull(); - } - - @Test - public void getPrivateKey_WithValidAlias_ReturnsExpectedValue() { - assertThat(keyStoreWrapper.getPrivateKey(KEYSTORE_VALID_KEY_ALIAS)) - .as("Private key is not null") - .isNotNull(); - } - - @Test - public void getPrivateKey_WithInvalidAlias_ReturnsExpectedValue() { - assertThat(keyStoreWrapper.getPrivateKey(KEYSTORE_INVALID_KEY_ALIAS)) - .as("Private key is null") - .isNull(); - } - - @Test - public void getCertificate_WithValidAlias_ReturnsExpectedValue() { - assertThat(keyStoreWrapper.getCertificate(KEYSTORE_VALID_KEY_ALIAS)) - .as("Certificate is not null") - .isNotNull(); - } - - @Test - public void getCertificate_WithInvalidAlias_ReturnsExpectedValue() { - assertThat(keyStoreWrapper.getCertificate(KEYSTORE_INVALID_KEY_ALIAS)) - .as("Certificate is null") - .isNull(); - } - - @Test - public void getCertificateChain_WithValidAlias_ReturnsExpectedValue() { - assertThat(keyStoreWrapper.getCertificateChain(KEYSTORE_VALID_KEY_ALIAS)) - .as("Certificate chain is not null") - .isNotNull(); - } - - @Test - public void getCertificateChain_WithInvalidAlias_ReturnsExpectedValue() { - assertThat(keyStoreWrapper.getCertificateChain(KEYSTORE_INVALID_KEY_ALIAS)) - .as("Certificate is null") - .isNull(); - } - - @Test - public void getCertificate_FromTruststore_WithValidAlias_ReturnsExpectedValue() { - final Certificate certificate = - keyStoreWrapper.getCertificate(TRUSTSTORE_VALID_CERTIFICATE_ALIAS); - if (keystoreWrapperConfiguredWithTruststore) { - assertThat(certificate).as("Certificate is not null").isNotNull(); - } else { - assertThat(certificate).as("Certificate is null").isNull(); - } - } - - @Test - public void getCertificate_FromTruststore_WithInvalidAlias_ReturnsExpectedValue() { - assertThat(keyStoreWrapper.getPrivateKey(TRUSTSTORE_INVALID_CERTIFICATE_ALIAS)) - .as("Certificate is null") - .isNull(); - } - - @Test - public void getCRLS_Check() { - assertThat(keyStoreWrapper.getCRLs()).as("CRLs is not null").isNotNull(); - assertThat(keyStoreWrapper.getCRLs().size()).as("CRLs size matches").isEqualTo(2); - } -} diff --git a/pki/src/test/java/org/hyperledger/besu/pki/keystore/CryptoTestUtil.java b/pki/src/test/java/org/hyperledger/besu/pki/keystore/CryptoTestUtil.java deleted file mode 100644 index 4176cac0faa..00000000000 --- a/pki/src/test/java/org/hyperledger/besu/pki/keystore/CryptoTestUtil.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.pki.keystore; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Optional; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class CryptoTestUtil { - - private static final Logger LOG = LoggerFactory.getLogger(CryptoTestUtil.class); - - private CryptoTestUtil() {} - - public static boolean isNSSLibInstalled() { - try { - final String nssLibPath = getNSSLibPath(); - return nssLibPath != null && !nssLibPath.trim().isEmpty(); - } catch (final Exception e) { - LOG.info("NSS library does not seem to be installed!", e); - } - return false; - } - - public static String getNSSLibPath() throws IOException, InterruptedException { - String nssLibPath = ""; - final String centOS_nssPathCmd = - "whereis libnssdbm3 | grep -o \"\\/.*libnssdbm3\\.[0-9a-z]* \" | sed 's/\\/libnssdbm3.*//g'"; - final String debian_nssPathCmd = - "whereis libnss3 | grep -o \".*libnss3.[0-9a-z]\" | sed 's/lib.* \\(\\/.*\\)\\/lib.*/\\1/'"; - final String macOS_nssPathCmd = "dirname `which certutil` | sed 's/bin/lib/g'"; - - nssLibPath = executeSystemCmd(centOS_nssPathCmd).orElse(nssLibPath); - LOG.info("centOS_nssPathCmd: {}", nssLibPath); - if ("".equals(nssLibPath)) { - nssLibPath = executeSystemCmd(debian_nssPathCmd).orElse(nssLibPath); - LOG.info("debian_nssPathCmd: {}", nssLibPath); - } - if ("".equals(nssLibPath)) { - nssLibPath = executeSystemCmd(macOS_nssPathCmd).orElse(nssLibPath); - LOG.info("macOS_nssPathCmd: {}", nssLibPath); - } - LOG.info("Detected NSS library path: {}", nssLibPath); - return nssLibPath; - } - - public static Optional executeSystemCmd(final String cmd) - throws IOException, InterruptedException { - final Process p = Runtime.getRuntime().exec(new String[] {"/bin/sh", "-c", cmd}); - try { - if (p.waitFor() == 0) { - final java.util.Scanner s = - new java.util.Scanner(p.getInputStream(), StandardCharsets.UTF_8.name()) - .useDelimiter("\\A"); - if (s.hasNext()) { - return Optional.of(s.next().replace("\r", "").replace("\n", "")); - } - } - } finally { - if (p != null) { - p.destroy(); - } - } - return Optional.empty(); - } -} diff --git a/pki/src/test/java/org/hyperledger/besu/pki/keystore/HardwareKeyStoreFileWrapperTest.java b/pki/src/test/java/org/hyperledger/besu/pki/keystore/HardwareKeyStoreFileWrapperTest.java deleted file mode 100644 index 99208e01c5b..00000000000 --- a/pki/src/test/java/org/hyperledger/besu/pki/keystore/HardwareKeyStoreFileWrapperTest.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.pki.keystore; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; - -import org.hyperledger.besu.pki.PkiException; - -import java.nio.file.Path; -import java.security.Provider; -import java.security.Security; -import java.util.Arrays; -import java.util.Collection; -import java.util.Optional; -import java.util.stream.Stream; - -import org.junit.Assume; -import org.junit.Before; -import org.junit.Test; -import org.junit.jupiter.api.condition.OS; -import org.junit.runners.Parameterized; - -public class HardwareKeyStoreFileWrapperTest extends BaseKeyStoreFileWrapperTest { - - private static final String config = "/keystore/partner1client1/nss.cfg"; - private static final String crl = "/keystore/partner1client1/crl.pem"; - private static final String configName = "NSScrypto-partner1client1"; - private static final String validKeystorePassword = "test123"; - - @Parameterized.Parameters(name = "{index}: {0}") - public static Collection data() { - return Arrays.asList( - new Object[][] { - { - "HardwareKeyStoreWrapper[PKCS11 keystore/truststore]", - true, - CryptoTestUtil.isNSSLibInstalled() ? getHardwareKeyStoreWrapper(configName) : null - } - }); - } - - private static KeyStoreWrapper getHardwareKeyStoreWrapper(final String cfgName) { - try { - final Path path = toPath(config); - final Path crlPath = toPath(crl); - final Optional existingProvider = - Stream.of(Security.getProviders()) - .filter(p -> p.getName().equals("SunPKCS11" + cfgName)) - .findAny(); - return existingProvider - .map(provider -> new HardwareKeyStoreWrapper(validKeystorePassword, provider, crlPath)) - .orElseGet(() -> new HardwareKeyStoreWrapper(validKeystorePassword, path, crlPath)); - } catch (final Exception e) { - if (OS.MAC.isCurrentOs()) { - // nss3 is difficult to setup on mac correctly, don't let it break unit tests for dev - // machines. - Assume.assumeNoException("Failed to initialize hardware keystore", e); - } - // Not a mac, probably a production build. Full failure. - throw new PkiException("Failed to initialize hardware keystore", e); - } - } - - @Before - public void beforeMethod() { - Assume.assumeTrue( - "Test ignored due to NSS library not being installed/detected.", - CryptoTestUtil.isNSSLibInstalled()); - } - - @Test - public void getPkcs11Provider() throws Exception { - final HardwareKeyStoreWrapper sut = - (HardwareKeyStoreWrapper) getHardwareKeyStoreWrapper(configName); - assertThatThrownBy(() -> sut.getPkcs11ProviderForConfig("no-library")) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void init_keystorePassword_config() throws Exception { - new HardwareKeyStoreWrapper(validKeystorePassword, toPath(config), toPath(crl)); - } - - @Test - public void init_keystorePassword_config_invalid() throws Exception { - final String config = "invalid"; - assertThatThrownBy( - () -> new HardwareKeyStoreWrapper(validKeystorePassword, toPath(config), toPath(crl))) - .isInstanceOf(NullPointerException.class); - } - - @Test - public void init_keystorePassword_config_missing_pw() throws Exception { - assertThatThrownBy(() -> new HardwareKeyStoreWrapper(null, toPath(config), toPath(crl))) - .isInstanceOf(PkiException.class); - } - - @Test - public void init_keystorePassword_provider_missing_pw() throws Exception { - final Provider p = null; - assertThatThrownBy(() -> new HardwareKeyStoreWrapper(validKeystorePassword, p, toPath(crl))) - .isInstanceOf(PkiException.class); - } -} diff --git a/pki/src/test/java/org/hyperledger/besu/pki/keystore/HardwareKeyStoreWrapperTest.java b/pki/src/test/java/org/hyperledger/besu/pki/keystore/HardwareKeyStoreWrapperTest.java deleted file mode 100644 index bbbe68f8232..00000000000 --- a/pki/src/test/java/org/hyperledger/besu/pki/keystore/HardwareKeyStoreWrapperTest.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.besu.pki.keystore; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - -import java.security.KeyStore; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.cert.Certificate; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; - -@RunWith(MockitoJUnitRunner.class) -public class HardwareKeyStoreWrapperTest { - - private static final String KEY_ALIAS = "keyalias"; - private static final String CERTIFICATE_ALIAS = "certalias"; - private static final char[] PASSWORD = "password".toCharArray(); - - @Mock private KeyStore keyStore; - @Mock private PrivateKey privateKey; - @Mock private PublicKey publicKey; - @Mock private Certificate certificate; - - private HardwareKeyStoreWrapper keyStoreWrapper; - - @Before - public void before() { - keyStoreWrapper = new HardwareKeyStoreWrapper(null, keyStore, new String(PASSWORD)); - } - - @Test - public void getPrivateKey() throws Exception { - when(keyStore.getKey(KEY_ALIAS, PASSWORD)).thenReturn(privateKey); - - assertThat(keyStoreWrapper.getPrivateKey(KEY_ALIAS)).isNotNull(); - } - - @Test - public void getPublicKey() throws Exception { - // Get public key from certificate - when(keyStore.getCertificate(KEY_ALIAS)).thenReturn(certificate); - when(certificate.getPublicKey()).thenReturn(publicKey); - - assertThat(keyStoreWrapper.getPublicKey(KEY_ALIAS)).isNotNull(); - } - - @Test - public void getCertificate() throws Exception { - when(keyStore.getCertificate(CERTIFICATE_ALIAS)).thenReturn(certificate); - - assertThat(keyStoreWrapper.getCertificate(CERTIFICATE_ALIAS)).isNotNull(); - } - - @Test - public void getCertificateChain() throws Exception { - when(keyStore.getCertificateChain(CERTIFICATE_ALIAS)) - .thenReturn(new Certificate[] {certificate}); - - assertThat(keyStoreWrapper.getCertificateChain(CERTIFICATE_ALIAS)).hasSize(1); - } -} diff --git a/pki/src/test/java/org/hyperledger/besu/pki/keystore/SoftwareKeyStoreFileWrapperTest.java b/pki/src/test/java/org/hyperledger/besu/pki/keystore/SoftwareKeyStoreFileWrapperTest.java deleted file mode 100644 index a194d8ec690..00000000000 --- a/pki/src/test/java/org/hyperledger/besu/pki/keystore/SoftwareKeyStoreFileWrapperTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.pki.keystore; - -import org.hyperledger.besu.pki.PkiException; - -import java.nio.file.Path; -import java.util.Arrays; -import java.util.Collection; - -import org.junit.runners.Parameterized; - -public class SoftwareKeyStoreFileWrapperTest extends BaseKeyStoreFileWrapperTest { - - private static final String p12KeyStore = "/keystore/partner1client1/keys.p12"; - private static final String jksKeyStore = "/keystore/partner1client1/keystore.jks"; - private static final String trustStore = "/keystore/partner1client1/truststore.jks"; - private static final String crl = "/keystore/partner1client1/crl.pem"; - private static final String validKeystorePassword = "test123"; - - @Parameterized.Parameters(name = "{index}: {0}") - public static Collection data() { - return Arrays.asList( - new Object[][] { - { - "SoftwareKeyStoreWrapper[PKCS12 keystore only]", - false, - getPKCS12SoftwareKeyStoreWrapper() - }, - { - "SoftwareKeyStoreWrapper[JKS keystore only]", - false, - getJKSSoftwareKeyStoreWrapper(false) - }, - { - "SoftwareKeyStoreWrapper[JKS keystore/truststore]", - true, - getJKSSoftwareKeyStoreWrapper(true) - } - }); - } - - private static KeyStoreWrapper getPKCS12SoftwareKeyStoreWrapper() { - try { - return new SoftwareKeyStoreWrapper( - KeyStoreWrapper.KEYSTORE_TYPE_PKCS12, - toPath(p12KeyStore), - validKeystorePassword, - toPath(crl)); - } catch (final Exception e) { - throw new PkiException("Failed to initialize software keystore", e); - } - } - - private static KeyStoreWrapper getJKSSoftwareKeyStoreWrapper(final boolean setupTruststore) { - try { - final Path keystoreLocation = toPath(jksKeyStore); - if (setupTruststore) { - final Path truststoreLocation = toPath(trustStore); - // password shouldn't be needed for retrieving certificate from truststore - return new SoftwareKeyStoreWrapper( - KeyStoreWrapper.KEYSTORE_TYPE_JKS, - keystoreLocation, - validKeystorePassword, - KeyStoreWrapper.KEYSTORE_TYPE_JKS, - truststoreLocation, - null, - toPath(crl)); - } - return new SoftwareKeyStoreWrapper( - KeyStoreWrapper.KEYSTORE_TYPE_JKS, keystoreLocation, validKeystorePassword, toPath(crl)); - } catch (final Exception e) { - throw new PkiException("Failed to initialize software keystore", e); - } - } -} diff --git a/pki/src/test/java/org/hyperledger/besu/pki/keystore/SoftwareKeyStoreWrapperTest.java b/pki/src/test/java/org/hyperledger/besu/pki/keystore/SoftwareKeyStoreWrapperTest.java deleted file mode 100644 index e198084ac44..00000000000 --- a/pki/src/test/java/org/hyperledger/besu/pki/keystore/SoftwareKeyStoreWrapperTest.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.besu.pki.keystore; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.pki.keystore.KeyStoreWrapper.KEYSTORE_TYPE_PKCS12; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.nio.file.Path; -import java.security.KeyStore; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.cert.Certificate; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; - -@RunWith(MockitoJUnitRunner.class) -public class SoftwareKeyStoreWrapperTest { - - private static final String KEY_ALIAS = "keyalias"; - private static final String CERTIFICATE_ALIAS = "certalias"; - private static final char[] PASSWORD = "password".toCharArray(); - - private SoftwareKeyStoreWrapper keyStoreWrapper; - - @Mock private KeyStore keyStore; - @Mock private KeyStore trustStore; - @Mock private PrivateKey privateKey; - @Mock private PublicKey publicKey; - @Mock private Certificate certificate; - - @Before - public void before() { - keyStoreWrapper = new SoftwareKeyStoreWrapper(keyStore, new String(PASSWORD), null, ""); - } - - @Test - public void getPrivateKey() throws Exception { - when(keyStore.containsAlias(KEY_ALIAS)).thenReturn(true); - when(keyStore.getKey(KEY_ALIAS, PASSWORD)).thenReturn(privateKey); - - assertThat(keyStoreWrapper.getPrivateKey(KEY_ALIAS)).isNotNull(); - } - - @Test - public void getPrivateKeyCaching() throws Exception { - when(keyStore.containsAlias(KEY_ALIAS)).thenReturn(true); - when(keyStore.getKey(KEY_ALIAS, PASSWORD)).thenReturn(privateKey); - - keyStoreWrapper.getPrivateKey(KEY_ALIAS); - keyStoreWrapper.getPrivateKey(KEY_ALIAS); - - verify(keyStore, times(1)).getKey(eq(KEY_ALIAS), eq(PASSWORD)); - } - - @Test - public void getPrivateKeyFallbackToTrustStore() throws Exception { - keyStoreWrapper = - new SoftwareKeyStoreWrapper( - keyStore, new String(PASSWORD), trustStore, new String(PASSWORD)); - - when(keyStore.containsAlias(KEY_ALIAS)).thenReturn(false); - when(trustStore.containsAlias(KEY_ALIAS)).thenReturn(true); - when(trustStore.getKey(KEY_ALIAS, PASSWORD)).thenReturn(privateKey); - - assertThat(keyStoreWrapper.getPrivateKey(KEY_ALIAS)).isNotNull(); - - verify(trustStore).getKey(eq(KEY_ALIAS), eq(PASSWORD)); - } - - @Test - public void getPublicKey() throws Exception { - when(keyStore.containsAlias(KEY_ALIAS)).thenReturn(true); - when(keyStore.getKey(KEY_ALIAS, PASSWORD)).thenReturn(publicKey); - - assertThat(keyStoreWrapper.getPublicKey(KEY_ALIAS)).isNotNull(); - } - - @Test - public void getPublicKeyCaching() throws Exception { - when(keyStore.containsAlias(KEY_ALIAS)).thenReturn(true); - when(keyStore.getKey(KEY_ALIAS, PASSWORD)).thenReturn(publicKey); - - keyStoreWrapper.getPublicKey(KEY_ALIAS); - keyStoreWrapper.getPublicKey(KEY_ALIAS); - - verify(keyStore, times(1)).getKey(eq(KEY_ALIAS), eq(PASSWORD)); - } - - @Test - public void getPublicKeyFallbackToTrustStore() throws Exception { - keyStoreWrapper = - new SoftwareKeyStoreWrapper( - keyStore, new String(PASSWORD), trustStore, new String(PASSWORD)); - - when(keyStore.containsAlias(KEY_ALIAS)).thenReturn(false); - when(trustStore.containsAlias(KEY_ALIAS)).thenReturn(true); - when(trustStore.getKey(KEY_ALIAS, PASSWORD)).thenReturn(publicKey); - - assertThat(keyStoreWrapper.getPublicKey(KEY_ALIAS)).isNotNull(); - - verify(trustStore).getKey(eq(KEY_ALIAS), eq(PASSWORD)); - } - - @Test - public void getCertificate() throws Exception { - when(keyStore.getCertificate(CERTIFICATE_ALIAS)).thenReturn(certificate); - - assertThat(keyStoreWrapper.getCertificate(CERTIFICATE_ALIAS)).isNotNull(); - } - - @Test - public void getCertificateCaching() throws Exception { - when(keyStore.getCertificate(CERTIFICATE_ALIAS)).thenReturn(certificate); - - keyStoreWrapper.getCertificate(CERTIFICATE_ALIAS); - keyStoreWrapper.getCertificate(CERTIFICATE_ALIAS); - - verify(keyStore, times(1)).getCertificate(eq(CERTIFICATE_ALIAS)); - } - - @Test - public void getCertificateFallbackToTrustStore() throws Exception { - keyStoreWrapper = - new SoftwareKeyStoreWrapper( - keyStore, new String(PASSWORD), trustStore, new String(PASSWORD)); - - when(keyStore.getCertificate(CERTIFICATE_ALIAS)).thenReturn(null); - when(trustStore.getCertificate(CERTIFICATE_ALIAS)).thenReturn(certificate); - - assertThat(keyStoreWrapper.getCertificate(CERTIFICATE_ALIAS)).isNotNull(); - - verify(trustStore).getCertificate(eq(CERTIFICATE_ALIAS)); - } - - @Test - public void getCertificateChain() throws Exception { - when(keyStore.getCertificateChain(CERTIFICATE_ALIAS)) - .thenReturn(new Certificate[] {certificate}); - - assertThat(keyStoreWrapper.getCertificateChain(CERTIFICATE_ALIAS)).hasSize(1); - } - - @Test - public void getCertificateChainFallbackToTrustStore() throws Exception { - keyStoreWrapper = - new SoftwareKeyStoreWrapper( - keyStore, new String(PASSWORD), trustStore, new String(PASSWORD)); - - when(keyStore.getCertificateChain(CERTIFICATE_ALIAS)).thenReturn(null); - when(trustStore.getCertificateChain(CERTIFICATE_ALIAS)) - .thenReturn(new Certificate[] {certificate}); - - assertThat(keyStoreWrapper.getCertificateChain(CERTIFICATE_ALIAS)).hasSize(1); - - verify(trustStore).getCertificateChain(eq(CERTIFICATE_ALIAS)); - } - - @Test - public void loadKeyStoreFromFile() { - SoftwareKeyStoreWrapper loadedKeyStore = - new SoftwareKeyStoreWrapper( - KEYSTORE_TYPE_PKCS12, - Path.of("src/test/resources/keystore/keystore"), - "validator", - KEYSTORE_TYPE_PKCS12, - Path.of("src/test/resources/keystore/keystore"), - "validator", - null); - - assertThat(loadedKeyStore.getPublicKey("validator")).isNotNull(); - assertThat(loadedKeyStore.getPrivateKey("validator")).isNotNull(); - assertThat(loadedKeyStore.getCertificate("validator")).isNotNull(); - // CA -> INTERCA -> PARTNERACA -> VALIDATOR - assertThat(loadedKeyStore.getCertificateChain("validator")).hasSize(4); - } -} diff --git a/pki/src/test/java/org/hyperledger/besu/pki/util/TestCertificateUtils.java b/pki/src/test/java/org/hyperledger/besu/pki/util/TestCertificateUtils.java index 190e90213c9..e9502e30e66 100644 --- a/pki/src/test/java/org/hyperledger/besu/pki/util/TestCertificateUtils.java +++ b/pki/src/test/java/org/hyperledger/besu/pki/util/TestCertificateUtils.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.pki.util; import org.hyperledger.besu.pki.cms.CmsCreator; diff --git a/pki/src/test/resources/keystore/partner1client1/keys.p12 b/pki/src/test/resources/keystore/partner1client1/keys.p12 deleted file mode 100644 index fb3d451add6..00000000000 Binary files a/pki/src/test/resources/keystore/partner1client1/keys.p12 and /dev/null differ diff --git a/pki/src/test/resources/keystore/partner1client1/keystore.jks b/pki/src/test/resources/keystore/partner1client1/keystore.jks deleted file mode 100644 index a51a778eff9..00000000000 Binary files a/pki/src/test/resources/keystore/partner1client1/keystore.jks and /dev/null differ diff --git a/pki/src/test/resources/keystore/partner1client1/nss.cfg b/pki/src/test/resources/keystore/partner1client1/nss.cfg deleted file mode 100644 index 6cd0f7eb876..00000000000 --- a/pki/src/test/resources/keystore/partner1client1/nss.cfg +++ /dev/null @@ -1,6 +0,0 @@ - -name = NSScrypto-partner1client1 -nssSecmodDirectory = ./src/test/resources/keystore/partner1client1/nssdb -nssDbMode = readOnly -nssModule = keystore - diff --git a/pki/src/test/resources/keystore/partner1client1/truststore.jks b/pki/src/test/resources/keystore/partner1client1/truststore.jks deleted file mode 100644 index fcfd0460219..00000000000 Binary files a/pki/src/test/resources/keystore/partner1client1/truststore.jks and /dev/null differ diff --git a/plugin-api/build.gradle b/plugin-api/build.gradle index 4ce23dfe351..bee57ab5aef 100644 --- a/plugin-api/build.gradle +++ b/plugin-api/build.gradle @@ -22,6 +22,7 @@ jar { 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash, 'Automatic-Module-Name': 'org.hyperledger.besu.plugin.api' ) } @@ -34,6 +35,7 @@ dependencies { api 'io.tmio:tuweni-units' implementation 'com.google.guava:guava' implementation project(':evm') + compileOnly 'io.vertx:vertx-core' } configurations { testArtifacts } @@ -69,7 +71,7 @@ Calculated : ${currentHash} tasks.register('checkAPIChanges', FileStateChecker) { description = "Checks that the API for the Plugin-API project does not change without deliberate thought" files = sourceSets.main.allJava.files - knownHash = 'nB1LhUpMWYFQpBdNJ/3Q79c8kLgUgPmEFzlRMlLUl1Y=' + knownHash = 'V/bdVbzJLjdwch266dHHuxIGwiCRhS4w3jDwHt4TWqg=' } check.dependsOn('checkAPIChanges') diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/BesuPlugin.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/BesuPlugin.java index 5a54808a6c1..1d7acf3a3ee 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/BesuPlugin.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/BesuPlugin.java @@ -63,6 +63,9 @@ default void beforeExternalServices() {} */ void start(); + /** Hook to execute plugin setup code after external services */ + default void afterExternalServicePostMainLoop() {} + /** * Called when the plugin is being reloaded. This method will be called through a dedicated JSON * RPC endpoint. If not overridden this method does nothing for convenience. The plugin should @@ -84,4 +87,24 @@ default CompletableFuture reloadConfiguration() { * started. */ void stop(); + + /** + * Retrieves the version information of the plugin. It constructs a version string using the + * implementation title and version from the package information. If either the title or version + * is not available, it defaults to the class's simple name and "Unknown Version", respectively. + * + * @return A string representing the plugin's version information, formatted as "Title/vVersion". + */ + default String getVersion() { + Package pluginPackage = this.getClass().getPackage(); + String implTitle = + Optional.ofNullable(pluginPackage.getImplementationTitle()) + .filter(title -> !title.isBlank()) + .orElseGet(() -> this.getClass().getSimpleName()); + String implVersion = + Optional.ofNullable(pluginPackage.getImplementationVersion()) + .filter(version -> !version.isBlank()) + .orElse(""); + return implTitle + "/" + implVersion; + } } diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BadBlockCause.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BadBlockCause.java new file mode 100644 index 00000000000..b9f41a34a14 --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BadBlockCause.java @@ -0,0 +1,41 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.data; + +/** Represents the reason a block is marked as "bad" */ +public interface BadBlockCause { + + /** + * The reason why the block was categorized as bad + * + * @return The reason enum + */ + BadBlockReason getReason(); + + /** + * A more descriptive explanation for why the block was marked bad + * + * @return the description + */ + String getDescription(); + + /** An enum representing the reason why a block is marked bad */ + enum BadBlockReason { + /** Standard spec-related validation failures */ + SPEC_VALIDATION_FAILURE, + /** This block is bad because it descends from a bad block */ + DESCENDS_FROM_BAD_BLOCK, + } +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockBody.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockBody.java index 858285365ad..18a5e07ce6a 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockBody.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockBody.java @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.plugin.data; import org.hyperledger.besu.datatypes.Transaction; @@ -51,10 +49,10 @@ public interface BlockBody { Optional> getWithdrawals(); /** - * Returns the list of deposits of the block. + * Returns the list of requests of the block. * - * @return The list of deposits of the block. + * @return The list of requests of the block. */ @Unstable - Optional> getDeposits(); + Optional> getRequests(); } diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockContext.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockContext.java index caacc741f4d..a2fdc2819c1 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockContext.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockContext.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockHeader.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockHeader.java index edd552e118d..ed8d6b87456 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockHeader.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockHeader.java @@ -121,21 +121,20 @@ public interface BlockHeader extends ProcessableBlockHeader { Optional getWithdrawalsRoot(); /** - * The Keccak 256-bit hash of the root node of the trie structure populated with each deposit in - * the deposits list portion of the block. + * The Keccak 256-bit hash of the root node of the trie structure populated with each request in + * the request list portion of the block. * * @return The Keccak 256-bit hash of the root node of the trie structure populated with each - * deposit in the deposit list portion of the block. + * request in the request list portion of the block. */ @Unstable - Optional getDepositsRoot(); + Optional getRequestsRoot(); /** * The excess_blob_gas of this header. * * @return The excess_blob_gas of this header. */ - @Unstable Optional getExcessBlobGas(); /** @@ -143,6 +142,5 @@ public interface BlockHeader extends ProcessableBlockHeader { * * @return The blob_gas_used of this header. */ - @Unstable Optional getBlobGasUsed(); } diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockTraceResult.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockTraceResult.java index 1ee4779cc0c..fb86222af0c 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockTraceResult.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/BlockTraceResult.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -100,6 +100,9 @@ public static Builder builder() { public static class Builder { List transactionTraceResults = new ArrayList<>(); + /** Constructs a new builder instance. */ + private Builder() {} + /** * Adds a transaction trace result to the builder. * diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/ConsolidationRequest.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/ConsolidationRequest.java new file mode 100644 index 00000000000..5d7e66b4aeb --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/ConsolidationRequest.java @@ -0,0 +1,45 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.data; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.PublicKey; +import org.hyperledger.besu.plugin.Unstable; + +/** A consolidation request is an operation sent to the Beacon Node for processing. */ +@Unstable +public interface ConsolidationRequest { + + /** + * Withdrawal credential (0x01) associated with the validator + * + * @return withdrawal credential address + */ + Address getSourceAddress(); + + /** + * Public key of the address that sends the consolidation + * + * @return public key of sender + */ + PublicKey getSourcePubkey(); + + /** + * Public key of the address to receives the consolidation + * + * @return public key of target + */ + PublicKey getTargetPubkey(); +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/Deposit.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/Deposit.java deleted file mode 100644 index 50e67898d75..00000000000 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/Deposit.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright contributors to Hyperledger Besu - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.plugin.data; - -import org.hyperledger.besu.datatypes.BLSSignature; -import org.hyperledger.besu.datatypes.PublicKey; -import org.hyperledger.besu.datatypes.Quantity; -import org.hyperledger.besu.plugin.Unstable; - -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt64; - -/** - * A deposit is a system-level operation to support validator deposits that are pushed from the EVM - * to beacon chain. - */ -@Unstable -public interface Deposit { - - /** - * Public key of the address that sends the deposit - * - * @return public key of sender - */ - PublicKey getPubkey(); - - /** - * Withdrawal credential that contains info that will be used for verifying the destination of - * valid withdrawals - * - * @return withdrawal credential - */ - Bytes32 getWithdrawalCredentials(); - - /** - * Amount of ether to be sent to the deposit contract - * - * @return deposit ether amount - */ - Quantity getAmount(); - - /** - * Signature that will be used together with the public key to verify the validity of this Deposit - * - * @return signature - */ - BLSSignature getSignature(); - - /** - * A monotonically increasing index, starting from 0 that increments by 1 per deposit to uniquely - * identify each deposit - * - * @return deposit index - */ - UInt64 getIndex(); -} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/DepositRequest.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/DepositRequest.java new file mode 100644 index 00000000000..096554d3c74 --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/DepositRequest.java @@ -0,0 +1,68 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.data; + +import org.hyperledger.besu.datatypes.BLSSignature; +import org.hyperledger.besu.datatypes.PublicKey; +import org.hyperledger.besu.datatypes.Quantity; +import org.hyperledger.besu.plugin.Unstable; + +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt64; + +/** + * A deposit is a system-level operation to support validator deposits that are pushed from the EVM + * to beacon chain. + */ +@Unstable +public interface DepositRequest { + + /** + * Public key of the address that sends the deposit + * + * @return public key of sender + */ + PublicKey getPubkey(); + + /** + * Withdrawal credential that contains info that will be used for verifying the destination of + * valid withdrawals + * + * @return withdrawal credential + */ + Bytes32 getWithdrawalCredentials(); + + /** + * Amount of ether to be sent to the deposit contract + * + * @return deposit ether amount + */ + Quantity getAmount(); + + /** + * Signature that will be used together with the public key to verify the validity of this Deposit + * + * @return signature + */ + BLSSignature getSignature(); + + /** + * A monotonically increasing index, starting from 0 that increments by 1 per deposit to uniquely + * identify each deposit + * + * @return deposit index + */ + UInt64 getIndex(); +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/PrivateTransaction.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/PrivateTransaction.java index 4ab171ecae9..fe918bf8f17 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/PrivateTransaction.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/PrivateTransaction.java @@ -94,6 +94,16 @@ public interface PrivateTransaction { */ Address getSender(); + /** + * An unlimited size byte array specifying the EVM-code for the account initialization procedure. + * + *

    Only present if this is a contract creation transaction, which is only true if {@link + * #getTo} is empty. + * + * @return if present, the contract init code. + */ + Optional getInit(); + /** * The chainId, computed from the 'V' portion of the signature. Used for replay protection. If * replay protection is not enabled this value will not be present. diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/Request.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/Request.java new file mode 100644 index 00000000000..2540f91098b --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/Request.java @@ -0,0 +1,30 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.data; + +import org.hyperledger.besu.datatypes.RequestType; +import org.hyperledger.besu.plugin.Unstable; + +/** A request is an operation sent to the Beacon Node */ +@Unstable +public interface Request { + + /** + * Retrieves the type of this request. + * + * @return The {@link RequestType} representing the type of this request. + */ + RequestType getType(); +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/Signature.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/Signature.java index 034581c6114..5d354fb6156 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/Signature.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/Signature.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionProcessingResult.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionProcessingResult.java index 22f35ea37bc..58d7372bd3a 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionProcessingResult.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionProcessingResult.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -88,4 +88,11 @@ public interface TransactionProcessingResult { * @return the revert reason. */ Optional getRevertReason(); + + /** + * Return the reason why the transaction is invalid or empty if the transaction is successful + * + * @return the optional invalid reason as a string + */ + Optional getInvalidReason(); } diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionSelectionResult.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionSelectionResult.java index 5c037983d79..66b1c1f2d65 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionSelectionResult.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionSelectionResult.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.plugin.data; import java.util.Objects; @@ -43,6 +42,13 @@ protected interface Status { */ boolean discard(); + /** + * Should the score of this transaction be decremented? + * + * @return yes if the score of this transaction needs to be decremented + */ + boolean penalize(); + /** * Name of this status * @@ -53,29 +59,34 @@ protected interface Status { private enum BaseStatus implements Status { SELECTED, - BLOCK_FULL(true, false), - BLOCK_OCCUPANCY_ABOVE_THRESHOLD(true, false), - BLOCK_SELECTION_TIMEOUT(true, false), - TX_EVALUATION_TOO_LONG(false, true), - INVALID_TRANSIENT(false, false), - INVALID(false, true); + BLOCK_FULL(true, false, false), + BLOBS_FULL(false, false, false), + BLOCK_OCCUPANCY_ABOVE_THRESHOLD(true, false, false), + BLOCK_SELECTION_TIMEOUT(true, false, false), + TX_EVALUATION_TOO_LONG(true, false, true), + INVALID_TX_EVALUATION_TOO_LONG(true, true, true), + INVALID_TRANSIENT(false, false, true), + INVALID(false, true, false); private final boolean stop; private final boolean discard; + private final boolean penalize; BaseStatus() { this.stop = false; this.discard = false; + this.penalize = false; } - BaseStatus(final boolean stop, final boolean discard) { + BaseStatus(final boolean stop, final boolean discard, final boolean penalize) { this.stop = stop; this.discard = discard; + this.penalize = penalize; } @Override public String toString() { - return name() + " (stop=" + stop + ", discard=" + discard + ")"; + return name() + " (stop=" + stop + ", discard=" + discard + ", penalize=" + penalize + ")"; } @Override @@ -87,38 +98,65 @@ public boolean stop() { public boolean discard() { return discard; } + + @Override + public boolean penalize() { + return penalize; + } } /** The transaction has been selected to be included in the new block */ public static final TransactionSelectionResult SELECTED = new TransactionSelectionResult(BaseStatus.SELECTED); + /** The transaction has not been selected since the block is full. */ public static final TransactionSelectionResult BLOCK_FULL = new TransactionSelectionResult(BaseStatus.BLOCK_FULL); + + /** The block already contains the max number of allowed blobs. */ + public static final TransactionSelectionResult BLOBS_FULL = + new TransactionSelectionResult(BaseStatus.BLOBS_FULL); + /** There was no more time to add transaction to the block */ public static final TransactionSelectionResult BLOCK_SELECTION_TIMEOUT = new TransactionSelectionResult(BaseStatus.BLOCK_SELECTION_TIMEOUT); - /** Transaction took too much to evaluate */ + + /** Transaction took too much to evaluate, but it was not invalid */ public static final TransactionSelectionResult TX_EVALUATION_TOO_LONG = new TransactionSelectionResult(BaseStatus.TX_EVALUATION_TOO_LONG); + + /** Transaction took too much to evaluate, and it was invalid */ + public static final TransactionSelectionResult INVALID_TX_EVALUATION_TOO_LONG = + new TransactionSelectionResult(BaseStatus.INVALID_TX_EVALUATION_TOO_LONG); + /** * The transaction has not been selected since too large and the occupancy of the block is enough * to stop the selection. */ public static final TransactionSelectionResult BLOCK_OCCUPANCY_ABOVE_THRESHOLD = new TransactionSelectionResult(BaseStatus.BLOCK_OCCUPANCY_ABOVE_THRESHOLD); + /** * The transaction has not been selected since its gas limit is greater than the block remaining * gas, but the selection should continue. */ public static final TransactionSelectionResult TX_TOO_LARGE_FOR_REMAINING_GAS = TransactionSelectionResult.invalidTransient("TX_TOO_LARGE_FOR_REMAINING_GAS"); + + /** + * The transaction has not been selected since there is not enough remaining blob gas in the block + * to fit the blobs of the tx, but selection should continue. + */ + public static final TransactionSelectionResult TX_TOO_LARGE_FOR_REMAINING_BLOB_GAS = + TransactionSelectionResult.invalidTransient("TX_TOO_LARGE_FOR_REMAINING_BLOB_GAS"); + /** * The transaction has not been selected since its current price is below the configured min * price, but the selection should continue. */ public static final TransactionSelectionResult CURRENT_TX_PRICE_BELOW_MIN = TransactionSelectionResult.invalidTransient("CURRENT_TX_PRICE_BELOW_MIN"); + /** * The transaction has not been selected since its blob price is below the current network blob * price, but the selection should continue. @@ -197,6 +235,15 @@ public boolean discard() { return status.discard(); } + /** + * Should the score of this transaction be decremented? + * + * @return yes if the score of this transaction needs to be decremented + */ + public boolean penalize() { + return status.penalize(); + } + /** * Is the candidate transaction selected for block inclusion? * diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionSimulationResult.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionSimulationResult.java new file mode 100644 index 00000000000..9c878be71d6 --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionSimulationResult.java @@ -0,0 +1,76 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.data; + +import org.hyperledger.besu.datatypes.Transaction; + +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes; + +/** + * TransactionSimulationResult + * + * @param transaction tx + * @param result res + */ +public record TransactionSimulationResult( + Transaction transaction, TransactionProcessingResult result) { + + /** + * Was the simulation successful? + * + * @return boolean + */ + public boolean isSuccessful() { + return result.isSuccessful(); + } + + /** + * Was the transaction invalid? + * + * @return invalid + */ + public boolean isInvalid() { + return result.isInvalid(); + } + + /** + * Return the optional revert reason + * + * @return the optional revert reason + */ + public Optional getRevertReason() { + return result.getRevertReason(); + } + + /** + * Return the optional invalid reason + * + * @return the optional invalid reason + */ + public Optional getInvalidReason() { + return result.getInvalidReason(); + } + + /** + * Estimated gas used by the transaction + * + * @return estimated gas used + */ + public long getGasEstimate() { + return transaction.getGasLimit() - result.getGasRemaining(); + } +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionTraceResult.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionTraceResult.java index 8b6619de285..41471d40210 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionTraceResult.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionTraceResult.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/Withdrawal.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/Withdrawal.java index ab4594eb308..cbb3b559953 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/Withdrawal.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/Withdrawal.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/WithdrawalRequest.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/WithdrawalRequest.java new file mode 100644 index 00000000000..f07d0d08d03 --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/WithdrawalRequest.java @@ -0,0 +1,49 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.data; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.GWei; +import org.hyperledger.besu.datatypes.PublicKey; +import org.hyperledger.besu.plugin.Unstable; + +/** + * A withdrawal request is an operation sent to the Beacon Node for processing validator withdrawal + * requests (partial or complete) + */ +@Unstable +public interface WithdrawalRequest { + + /** + * Withdrawal credential (0x01) associated with the validator + * + * @return withdrawal credential address + */ + Address getSourceAddress(); + + /** + * Public key of the validator that is going to request a withdrawal + * + * @return public key of validator + */ + PublicKey getValidatorPubkey(); + + /** + * The amount for withdrawal + * + * @return withdrawal amount + */ + GWei getAmount(); +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuConfiguration.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuConfiguration.java index 9f830162ecb..c10a697384f 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuConfiguration.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuConfiguration.java @@ -14,13 +14,31 @@ */ package org.hyperledger.besu.plugin.services; +import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.plugin.Unstable; +import org.hyperledger.besu.plugin.services.storage.DataStorageConfiguration; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import java.nio.file.Path; +import java.util.Optional; /** Generally useful configuration provided by Besu. */ public interface BesuConfiguration extends BesuService { + /** + * Get the configured RPC http host. + * + * @return the configured RPC http host. + */ + Optional getRpcHttpHost(); + + /** + * Get the configured RPC http port. + * + * @return the configured RPC http port. + */ + Optional getRpcHttpPort(); + /** * Location of the working directory of the storage in the file system running the client. * @@ -36,12 +54,27 @@ public interface BesuConfiguration extends BesuService { Path getDataPath(); /** - * Database version. This sets the list of segmentIdentifiers that should be initialized. + * Database format. This sets the list of segmentIdentifiers that should be initialized. + * + * @return Database format. + */ + @Unstable + @Deprecated + DataStorageFormat getDatabaseFormat(); + + /** + * The runtime value of the min gas price + * + * @return min gas price in wei + */ + @Unstable + Wei getMinGasPrice(); + + /** + * Database storage configuration. * - * @return Database version. + * @return Database storage configuration. */ @Unstable - default int getDatabaseVersion() { - return 1; - } + DataStorageConfiguration getDataStorageConfiguration(); } diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuEvents.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuEvents.java index 9d5b6394511..110a66dee54 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuEvents.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuEvents.java @@ -17,6 +17,8 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Transaction; import org.hyperledger.besu.plugin.data.AddedBlockContext; +import org.hyperledger.besu.plugin.data.BadBlockCause; +import org.hyperledger.besu.plugin.data.BlockHeader; import org.hyperledger.besu.plugin.data.LogWithMetadata; import org.hyperledger.besu.plugin.data.PropagatedBlockContext; import org.hyperledger.besu.plugin.data.SyncStatus; @@ -91,6 +93,14 @@ public interface BesuEvents extends BesuService { */ void removeBlockReorgListener(long listenerIdentifier); + /** + * Add an initial sync completion listener. + * + * @param listener to subscribe to initial sync completion events + * @return id of listener subscription + */ + long addInitialSyncCompletionListener(final InitialSyncCompletionListener listener); + /** * Add a listener watching new transactions added to the node. * @@ -156,6 +166,22 @@ public interface BesuEvents extends BesuService { */ void removeLogListener(long listenerIdentifier); + /** + * Add listener to track bad blocks. These are intrinsically bad blocks that have failed + * validation or descend from a bad block that has failed validation. + * + * @param listener The listener that will receive bad block events. + * @return The id of the listener to be used to remove the listener. + */ + long addBadBlockListener(BadBlockListener listener); + + /** + * Remove the bad block listener with the associated id. + * + * @param listenerIdentifier The id of the listener that was returned from addBadBlockListener. + */ + void removeBadBlockListener(long listenerIdentifier); + /** The listener interface for receiving new block propagated events. */ interface BlockPropagatedListener { @@ -232,7 +258,8 @@ interface LogListener { /** * Invoked for each log (both added and removed) when a new block is added to the blockchain. * - * @param logWithMetadata the log with associated metadata. see https://eth.wiki/json-rpc/API + * @param logWithMetadata the log with associated metadata. see + * https://ethereum.org/en/developers/docs/apis/json-rpc/ */ void onLogEmitted(LogWithMetadata logWithMetadata); } @@ -259,4 +286,16 @@ interface InitialSyncCompletionListener { /** Emitted when initial sync restarts */ void onInitialSyncRestart(); } + + /** The interface defining bad block listeners */ + interface BadBlockListener { + + /** + * Fires when a bad block is encountered on the network + * + * @param badBlockHeader The bad block's header + * @param cause The reason why the block was marked bad + */ + void onBadBlockAdded(BlockHeader badBlockHeader, BadBlockCause cause); + } } diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuService.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuService.java index 19b48d0a45b..62340dd59d6 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuService.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuService.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.plugin.services; import org.hyperledger.besu.plugin.BesuContext; diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BlockchainService.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BlockchainService.java index e9356ae76ef..84a573aadc3 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BlockchainService.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BlockchainService.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,9 +14,15 @@ */ package org.hyperledger.besu.plugin.services; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.plugin.Unstable; +import org.hyperledger.besu.plugin.data.BlockBody; import org.hyperledger.besu.plugin.data.BlockContext; +import org.hyperledger.besu.plugin.data.BlockHeader; +import org.hyperledger.besu.plugin.data.TransactionReceipt; +import java.util.List; import java.util.Optional; /** A service that plugins can use to query blocks by number */ @@ -29,4 +35,76 @@ public interface BlockchainService extends BesuService { * @return the BlockContext */ Optional getBlockByNumber(final long number); + + /** + * Get the hash of the chain head + * + * @return chain head hash + */ + Hash getChainHeadHash(); + + /** + * Get the receipts for a block by block hash + * + * @param blockHash the block hash + * @return the transaction receipts + */ + Optional> getReceiptsByBlockHash(Hash blockHash); + + /** + * Store a block + * + * @param blockHeader the block header + * @param blockBody the block body + * @param receipts the transaction receipts + */ + void storeBlock(BlockHeader blockHeader, BlockBody blockBody, List receipts); + + /** + * Get the block header of the chain head + * + * @return chain head block header + */ + BlockHeader getChainHeadHeader(); + + /** + * Return the base fee for the next block + * + * @return base fee of the next block or empty if the fee market does not support base fee + */ + Optional getNextBlockBaseFee(); + + /** + * Get the block hash of the safe block + * + * @return the block hash of the safe block + */ + Optional getSafeBlock(); + + /** + * Get the block hash of the finalized block + * + * @return the block hash of the finalized block + */ + Optional getFinalizedBlock(); + + /** + * Set the finalized block for non-PoS networks + * + * @param blockHash Hash of the finalized block + * @throws IllegalArgumentException if the block hash is not on the chain + * @throws UnsupportedOperationException if the network is a PoS network + */ + void setFinalizedBlock(Hash blockHash) + throws IllegalArgumentException, UnsupportedOperationException; + + /** + * Set the safe block for non-PoS networks + * + * @param blockHash Hash of the finalized block + * @throws IllegalArgumentException if the block hash is not on the chain + * @throws UnsupportedOperationException if the network is a PoS network + */ + void setSafeBlock(Hash blockHash) throws IllegalArgumentException, UnsupportedOperationException; + ; } diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/MetricsSystem.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/MetricsSystem.java index a72eb182b08..80e02a6dba7 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/MetricsSystem.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/MetricsSystem.java @@ -89,6 +89,34 @@ default OperationTimer createTimer( LabelledMetric createLabelledTimer( MetricCategory category, String name, String help, String... labelNames); + /** + * Creates a simple Timer. + * + * @param category The {@link MetricCategory} this timer is assigned to. + * @param name A name for this metric. + * @param help A human readable description of the metric. + * @return The created Timer instance. + */ + default OperationTimer createSimpleTimer( + final MetricCategory category, final String name, final String help) { + return createSimpleLabelledTimer(category, name, help).labels(); + } + + /** + * Creates a simple Timer with assigned labels. + * + * @param category The {@link MetricCategory} this timer is assigned to. + * @param name A name for this metric. + * @param help A human readable description of the metric. + * @param labelNames An array of labels to assign to the Timer. + * @return The created Timer instance. + */ + LabelledMetric createSimpleLabelledTimer( + final MetricCategory category, + final String name, + final String help, + final String... labelNames); + /** * Creates a gauge for displaying double vales. A gauge is a metric to report the current value. * The metric value may go up or down. diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/PluginTransactionValidatorService.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/PluginTransactionValidatorService.java deleted file mode 100644 index 993cbdafe1f..00000000000 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/PluginTransactionValidatorService.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.besu.plugin.services; - -import org.hyperledger.besu.plugin.Unstable; -import org.hyperledger.besu.plugin.services.txvalidator.PluginTransactionValidatorFactory; - -/** Transaction validator for addition of transactions to the transaction pool */ -@Unstable -public interface PluginTransactionValidatorService extends BesuService { - - /** - * Returns the transaction validator factory - * - * @return the transaction validator factory - */ - PluginTransactionValidatorFactory get(); - - /** - * Registers the transaction validator factory with the service - * - * @param transactionValidatorFactory transaction validator factory to be used - */ - void registerTransactionValidatorFactory( - PluginTransactionValidatorFactory transactionValidatorFactory); -} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/PluginVersionsProvider.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/PluginVersionsProvider.java index 26604a6488f..63b9a524b1a 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/PluginVersionsProvider.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/PluginVersionsProvider.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.plugin.services; import java.util.Collection; diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/RpcEndpointService.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/RpcEndpointService.java index 73f43a1017a..512d9877c78 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/RpcEndpointService.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/RpcEndpointService.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,6 +15,7 @@ package org.hyperledger.besu.plugin.services; import org.hyperledger.besu.plugin.services.rpc.PluginRpcRequest; +import org.hyperledger.besu.plugin.services.rpc.PluginRpcResponse; import java.util.function.Function; @@ -54,4 +55,13 @@ public interface RpcEndpointService extends BesuService { */ void registerRPCEndpoint( String namespace, String functionName, Function function); + + /** + * Allow to call any of the enabled in-process RPC methods + * + * @param methodName the method to invoke + * @param params the list of parameters accepted by the method + * @return the result of the method + */ + PluginRpcResponse call(String methodName, Object[] params); } diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TraceService.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TraceService.java index b084d3712c7..cf9dc841768 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TraceService.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TraceService.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TransactionPoolValidatorService.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TransactionPoolValidatorService.java new file mode 100644 index 00000000000..0dfe0f31240 --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TransactionPoolValidatorService.java @@ -0,0 +1,39 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.services; + +import org.hyperledger.besu.plugin.Unstable; +import org.hyperledger.besu.plugin.services.txvalidator.PluginTransactionPoolValidator; +import org.hyperledger.besu.plugin.services.txvalidator.PluginTransactionPoolValidatorFactory; + +/** Transaction validator for addition of transactions to the transaction pool */ +@Unstable +public interface TransactionPoolValidatorService extends BesuService { + + /** + * Returns the transaction validator to be used in the txpool + * + * @return the transaction validator + */ + PluginTransactionPoolValidator createTransactionValidator(); + + /** + * Registers the transaction validator factory with the service + * + * @param pluginTransactionPoolValidatorFactory transaction validator factory to be used + */ + void registerPluginTransactionValidatorFactory( + PluginTransactionPoolValidatorFactory pluginTransactionPoolValidatorFactory); +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TransactionSelectionService.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TransactionSelectionService.java index aab6e717bad..daa07930d8c 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TransactionSelectionService.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TransactionSelectionService.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,30 +12,28 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.plugin.services; import org.hyperledger.besu.plugin.Unstable; +import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelector; import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelectorFactory; -import java.util.Optional; - /** Transaction selection service interface */ @Unstable public interface TransactionSelectionService extends BesuService { /** - * Returns the (Optional) transaction selector factory + * Create a transaction selector plugin * - * @return the transaction selector factory + * @return the transaction selector plugin */ - Optional get(); + PluginTransactionSelector createPluginTransactionSelector(); /** * Registers the transaction selector factory with the service * * @param transactionSelectorFactory transaction selector factory to be used */ - void registerTransactionSelectorFactory( + void registerPluginTransactionSelectorFactory( PluginTransactionSelectorFactory transactionSelectorFactory); } diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TransactionSimulationService.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TransactionSimulationService.java new file mode 100644 index 00000000000..dffdb0cfe51 --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TransactionSimulationService.java @@ -0,0 +1,42 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.services; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.datatypes.Transaction; +import org.hyperledger.besu.evm.tracing.OperationTracer; +import org.hyperledger.besu.plugin.Unstable; +import org.hyperledger.besu.plugin.data.TransactionSimulationResult; + +import java.util.Optional; + +/** Transaction simulation service interface */ +@Unstable +public interface TransactionSimulationService extends BesuService { + /** + * Simulate transaction execution at the block identified by the hash + * + * @param transaction tx + * @param blockHash the hash of the block + * @param operationTracer the tracer + * @param isAllowExceedingBalance should ignore the sender balance during the simulation? + * @return the result of the simulation + */ + Optional simulate( + Transaction transaction, + Hash blockHash, + OperationTracer operationTracer, + boolean isAllowExceedingBalance); +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TrieLogService.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TrieLogService.java index f3180c6db14..5230d6a6642 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TrieLogService.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/TrieLogService.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.plugin.services; @@ -20,6 +19,7 @@ import org.hyperledger.besu.plugin.services.trielogs.TrieLogProvider; import java.util.List; +import java.util.Optional; /** * A service interface for registering observers for trie log events. @@ -40,7 +40,7 @@ public interface TrieLogService extends BesuService { * * @return the TrieLogFactory implementation */ - TrieLogFactory getTrieLogFactory(); + Optional getTrieLogFactory(); /** * Configure a TrieLogProvider implementation to use for retrieving stored TrieLogs. diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/exception/PluginRpcEndpointException.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/exception/PluginRpcEndpointException.java index 9a38b891eb0..51b1e1e0078 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/exception/PluginRpcEndpointException.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/exception/PluginRpcEndpointException.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,28 +14,69 @@ */ package org.hyperledger.besu.plugin.services.exception; +import org.hyperledger.besu.plugin.services.rpc.RpcMethodError; + /** Base exception class for problems encountered in the RpcEndpointService. */ public class PluginRpcEndpointException extends RuntimeException { + /** The error */ + private final RpcMethodError rpcMethodError; + + /** The data associated with the exception */ + private final String data; + /** - * Constructs a new PluginRpcEndpointException exception with the specified message. + * Constructs a new PluginRpcEndpointException exception with the specified error. * - * @param message the detail message (which is saved for later retrieval by the {@link - * #getMessage()} method). + * @param rpcMethodError the error. */ - public PluginRpcEndpointException(final String message) { - super(message); + public PluginRpcEndpointException(final RpcMethodError rpcMethodError) { + this(rpcMethodError, null); } /** - * Constructs a new PluginRpcEndpointException exception with the specified message. + * Constructs a new PluginRpcEndpointException exception with the specified error and message. * - * @param message the detail message (which is saved for later retrieval by the {@link - * #getMessage()} method). + * @param rpcMethodError the error. + * @param data the data associated with the exception that could be parsed to extract more + * information to return in the error response. + */ + public PluginRpcEndpointException(final RpcMethodError rpcMethodError, final String data) { + this(rpcMethodError, data, null); + } + + /** + * Constructs a new PluginRpcEndpointException exception with the specified error, message and + * cause. + * + * @param rpcMethodError the error. + * @param data the data associated with the exception that could be parsed to extract more + * information to return in the error response. * @param cause the cause (which is saved for later retrieval by the {@link #getCause()} method). * (A {@code null} value is permitted, and indicates that the cause is nonexistent or * unknown.) */ - public PluginRpcEndpointException(final String message, final Throwable cause) { - super(message, cause); + public PluginRpcEndpointException( + final RpcMethodError rpcMethodError, final String data, final Throwable cause) { + super(rpcMethodError.getMessage(), cause); + this.rpcMethodError = rpcMethodError; + this.data = data; + } + + /** + * Get the error + * + * @return the error + */ + public RpcMethodError getRpcMethodError() { + return rpcMethodError; + } + + /** + * Get the data associated with the exception + * + * @return data as string, could be null. + */ + public String getData() { + return data; } } diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/p2p/P2PService.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/p2p/P2PService.java new file mode 100644 index 00000000000..0e9e46e799a --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/p2p/P2PService.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.services.p2p; + +import org.hyperledger.besu.plugin.services.BesuService; + +/** Service to enable and disable P2P service. */ +public interface P2PService extends BesuService { + + /** Enables P2P discovery. */ + void enableDiscovery(); + + /** Disables P2P discovery. */ + void disableDiscovery(); +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/permissioning/NodeMessagePermissioningProvider.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/permissioning/NodeMessagePermissioningProvider.java index dc6d8071fe1..88efdad8a1c 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/permissioning/NodeMessagePermissioningProvider.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/permissioning/NodeMessagePermissioningProvider.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.plugin.services.permissioning; import org.hyperledger.besu.plugin.data.EnodeURL; diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/rlp/RlpConverterService.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/rlp/RlpConverterService.java new file mode 100644 index 00000000000..016fab7fa46 --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/rlp/RlpConverterService.java @@ -0,0 +1,74 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.services.rlp; + +import org.hyperledger.besu.plugin.data.BlockBody; +import org.hyperledger.besu.plugin.data.BlockHeader; +import org.hyperledger.besu.plugin.data.TransactionReceipt; +import org.hyperledger.besu.plugin.services.BesuService; + +import org.apache.tuweni.bytes.Bytes; + +/** RLP Serialiaztion/Deserialization service. */ +public interface RlpConverterService extends BesuService { + + /** + * Builds a block header from RLP. + * + * @param rlp the RLP to build the block header from. + * @return the block header. + */ + BlockHeader buildHeaderFromRlp(final Bytes rlp); + + /** + * Builds a block body from RLP. + * + * @param rlp the RLP to build the block body from. + * @return the block body. + */ + BlockBody buildBodyFromRlp(final Bytes rlp); + + /** + * Builds a transaction receipt from RLP. + * + * @param rlp the RLP to build the transaction receipt from. + * @return the transaction receipt. + */ + TransactionReceipt buildReceiptFromRlp(final Bytes rlp); + + /** + * RLP encodes a block header. + * + * @param blockHeader the block header to build RLP from. + * @return the RLP. + */ + Bytes buildRlpFromHeader(final BlockHeader blockHeader); + + /** + * RLP encodes a block body. + * + * @param blockBody the block body to build RLP from. + * @return the RLP. + */ + Bytes buildRlpFromBody(final BlockBody blockBody); + + /** + * RLP encodes a transaction receipt. + * + * @param receipt the transaction receipt to build RLP from. + * @return the RLP. + */ + Bytes buildRlpFromReceipt(final TransactionReceipt receipt); +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/rpc/PluginRpcRequest.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/rpc/PluginRpcRequest.java index c360f35edc1..eab35b4b043 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/rpc/PluginRpcRequest.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/rpc/PluginRpcRequest.java @@ -1,5 +1,5 @@ /* - * Copyright ConsenSys AG. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/rpc/PluginRpcResponse.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/rpc/PluginRpcResponse.java new file mode 100644 index 00000000000..7096a8a4227 --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/rpc/PluginRpcResponse.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.services.rpc; + +/** The interface Plugin rpc response. */ +public interface PluginRpcResponse extends RpcResponse { + + /** + * Get the result, unfortunately there is no typing yet, so call must know how to interact with + * the response + * + * @return the result + */ + Object getResult(); +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/rpc/RpcMethodError.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/rpc/RpcMethodError.java new file mode 100644 index 00000000000..efba770701f --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/rpc/RpcMethodError.java @@ -0,0 +1,54 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.services.rpc; + +import java.util.Optional; + +/** + * The {@code RpcMethodError} interface defines the structure for RPC error handling within the + * context of plugins. It provides methods to retrieve error code, message, and an optional data + * decoder function. + */ +public interface RpcMethodError { + + /** The error code for all invalid params */ + static final int INVALID_PARAMS_ERROR_CODE = -32602; + + /** + * Retrieves the error code associated with the RPC error. + * + * @return An integer representing the error code. + */ + int getCode(); + + /** + * Retrieves the message associated with the RPC error. + * + * @return A {@code String} containing the error message. + */ + String getMessage(); + + /** + * Some errors have additional data associated with them, that is possible to decode to provide a + * more detailed error response. + * + * @param data the additional data to decode + * @return an optional containing the decoded data if the error has it and the decoding is + * successful, otherwise empty. + */ + default Optional decodeData(final String data) { + return Optional.empty(); + } +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/rpc/RpcResponse.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/rpc/RpcResponse.java new file mode 100644 index 00000000000..3fafbb1d108 --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/rpc/RpcResponse.java @@ -0,0 +1,26 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.services.rpc; + +/** Represent a Json RPC response */ +public interface RpcResponse { + + /** + * Get the response type + * + * @return the response type + */ + RpcResponseType getType(); +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/rpc/RpcResponseType.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/rpc/RpcResponseType.java new file mode 100644 index 00000000000..b02b45c8577 --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/rpc/RpcResponseType.java @@ -0,0 +1,27 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.services.rpc; + +/** Various types of responses that the RPC component may produce. */ +public enum RpcResponseType { + /** No response */ + NONE, + /** Successful response */ + SUCCESS, + /** Error response */ + ERROR, + /** Not authorized response */ + UNAUTHORIZED +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/DataStorageConfiguration.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/DataStorageConfiguration.java new file mode 100644 index 00000000000..22fdf922f3e --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/DataStorageConfiguration.java @@ -0,0 +1,39 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.services.storage; + +import org.hyperledger.besu.plugin.Unstable; + +/** Data storage configuration */ +@Unstable +public interface DataStorageConfiguration { + + /** + * Database format. This sets the list of segmentIdentifiers that should be initialized. + * + * @return Database format. + */ + @Unstable + DataStorageFormat getDatabaseFormat(); + + /** + * Whether receipt compaction is enabled. When enabled this reduces the storage needed for + * receipts. + * + * @return Whether receipt compaction is enabled + */ + @Unstable + boolean getReceiptCompactionEnabled(); +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/DataStorageFormat.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/DataStorageFormat.java new file mode 100644 index 00000000000..8e056abc49a --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/DataStorageFormat.java @@ -0,0 +1,23 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.services.storage; + +/** Supported database storage format */ +public enum DataStorageFormat { + /** Original format. Store all tries */ + FOREST, + /** New format. Store one trie, and trie logs to roll forward and backward */ + BONSAI; +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/SegmentIdentifier.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/SegmentIdentifier.java index c535966876f..efcac957dda 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/SegmentIdentifier.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/SegmentIdentifier.java @@ -39,12 +39,12 @@ public interface SegmentIdentifier { /** * Not all segments are in all DB versions. This queries the segment to see if it is in the DB - * version. + * format. * - * @param version Version of the DB - * @return true if the segment is in that DB version + * @param format Version of the DB + * @return true if the segment is in that DB format */ - default boolean includeInDatabaseVersion(final int version) { + default boolean includeInDatabaseFormat(final DataStorageFormat format) { return true; } @@ -56,4 +56,22 @@ default boolean includeInDatabaseVersion(final int version) { * @return true if the segment contains only static data */ boolean containsStaticData(); + + /** + * This flag defines which segment is eligible for the high spec flag, so basically what column + * family is involved with high spec flag + * + * @return true if the segment is involved with the high spec flag + */ + boolean isEligibleToHighSpecFlag(); + + /** + * Enable garbage collection for static data. This should be enabled for static data which can be + * deleted or pruned. + * + * @return true if enabled, false otherwise. + */ + default boolean isStaticDataGarbageCollectionEnabled() { + return false; + } } diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/SegmentedKeyValueStorage.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/SegmentedKeyValueStorage.java index df8a8c48941..4f734c7c977 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/SegmentedKeyValueStorage.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/SegmentedKeyValueStorage.java @@ -39,15 +39,35 @@ public interface SegmentedKeyValueStorage extends Closeable { Optional get(SegmentIdentifier segment, byte[] key) throws StorageException; /** - * Find the key and corresponding value "nearest to" the specified key. Nearest is defined as - * either matching the supplied key or the key lexicographically prior to it. + * Finds the key and corresponding value that is "nearest before" the specified key. "Nearest + * before" is defined as the closest key that is either exactly matching the supplied key or + * lexicographically before it. * - * @param segmentIdentifier segment to scan - * @param key key for which we are searching for the nearest match. - * @return Optional of NearestKeyValue-wrapped matched key and corresponding value. - * @throws StorageException the storage exception + * @param segmentIdentifier The segment to scan for the nearest key. + * @param key The key for which we are searching for the nearest match before it. + * @return An Optional of NearestKeyValue, wrapping the matched key and its corresponding value, + * if found. + * @throws StorageException If an error occurs during the retrieval process. + */ + Optional getNearestBefore(final SegmentIdentifier segmentIdentifier, Bytes key) + throws StorageException; + + /** + * Finds the key and corresponding value that is "nearest after" the specified key. "Nearest + * after" is defined as the closest key that is either exactly matching the supplied key or + * lexicographically after it. + * + *

    This method aims to find the next key in sequence after the provided key, considering the + * order of keys within the specified segment. It is particularly useful for iterating over keys + * in a sorted manner starting from a given key. + * + * @param segmentIdentifier The segment to scan for the nearest key. + * @param key The key for which we are searching for the nearest match after it. + * @return An Optional of NearestKeyValue, wrapping the matched key and its corresponding value, + * if found. + * @throws StorageException If an error occurs during the retrieval process. */ - Optional getNearestTo(final SegmentIdentifier segmentIdentifier, Bytes key) + Optional getNearestAfter(final SegmentIdentifier segmentIdentifier, Bytes key) throws StorageException; /** diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/SnappableKeyValueStorage.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/SnappableKeyValueStorage.java index 18486ff02fd..acbb879497b 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/SnappableKeyValueStorage.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/SnappableKeyValueStorage.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.plugin.services.storage; diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/SnappedKeyValueStorage.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/SnappedKeyValueStorage.java index 64f762eb772..f5aaf8a070f 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/SnappedKeyValueStorage.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/storage/SnappedKeyValueStorage.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.plugin.services.storage; diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/sync/SynchronizationService.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/sync/SynchronizationService.java new file mode 100644 index 00000000000..ad9682429d0 --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/sync/SynchronizationService.java @@ -0,0 +1,62 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.services.sync; + +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.plugin.data.BlockBody; +import org.hyperledger.besu.plugin.data.BlockHeader; +import org.hyperledger.besu.plugin.services.BesuService; + +/** Synchronization service wraps the sync state and sync event lifecycle. */ +public interface SynchronizationService extends BesuService { + + /** + * Enables P2P discovery. + * + * @param head the head of the chain. + * @param safeBlock the safe block. + * @param finalizedBlock the finalized block. + */ + void fireNewUnverifiedForkchoiceEvent(Hash head, Hash safeBlock, Hash finalizedBlock); + + /** + * Set the head of the chain. + * + * @param blockHeader the block header + * @param blockBody the block body + * @return true if the head was set, false otherwise. + */ + boolean setHead(final BlockHeader blockHeader, final BlockBody blockBody); + + /** + * Adds the block header and body to the head of the chain directly, without using a block + * importer or validation. + * + * @param blockHeader the block header + * @param blockBody the block body + * @return true if the head was set, false otherwise. + */ + boolean setHeadUnsafe(BlockHeader blockHeader, BlockBody blockBody); + + /** + * Returns whether the initial chain and worldstate sync is complete. + * + * @return true if the initial sync phase is done, false otherwise. + */ + boolean isInitialSyncPhaseDone(); + + /** Disables the worldstate trie for update. */ + void disableWorldStateTrie(); +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/sync/WorldStateConfiguration.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/sync/WorldStateConfiguration.java new file mode 100644 index 00000000000..3f66ffb86e1 --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/sync/WorldStateConfiguration.java @@ -0,0 +1,26 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.services.sync; + +/** interface for worldstate configuration * */ +public interface WorldStateConfiguration { + + /** + * Returns whether the trie is disabled. + * + * @return true if the trie is disabled, false otherwise. + */ + boolean isTrieDisabled(); +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/tracer/BlockAwareOperationTracer.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/tracer/BlockAwareOperationTracer.java index f7dcc6113cb..b84c4b69f32 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/tracer/BlockAwareOperationTracer.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/tracer/BlockAwareOperationTracer.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/transactionpool/TransactionPoolService.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/transactionpool/TransactionPoolService.java new file mode 100644 index 00000000000..01b1f5768b3 --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/transactionpool/TransactionPoolService.java @@ -0,0 +1,26 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.services.transactionpool; + +import org.hyperledger.besu.plugin.services.BesuService; + +/** Service to enable and disable the transaction pool. */ +public interface TransactionPoolService extends BesuService { + /** Enables the transaction pool. */ + void disableTransactionPool(); + + /** Disables the transaction pool. */ + void enableTransactionPool(); +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLog.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLog.java index 1a763ade96e..68b44918e6a 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLog.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLog.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -127,6 +127,7 @@ Optional getStorageByStorageSlotKey( * Optional */ Optional getPriorAccount(final Address address); + /** * Gets the account value for a specific address, if available. * @@ -167,10 +168,17 @@ default boolean isUnchanged() { } /** - * Checks if the updated value represents a cleared state. + * Checks if the last step performed a 'clear'. + * + * @return true if the last step performed a 'clear', false otherwise. + */ + boolean isLastStepCleared(); + + /** + * Checks if a 'clear' has been performed at least once. * - * @return true if the updated value is cleared, false otherwise + * @return true if a 'clear' has been performed at least once, false otherwise. */ - boolean isCleared(); + boolean isClearedAtLeastOnce(); } } diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogAccumulator.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogAccumulator.java index 6984ca48a1e..d85ce2a714a 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogAccumulator.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogAccumulator.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -23,7 +23,7 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; -/** Accumulator interface tor provding trie updates for creating TrieLogs. */ +/** Accumulator interface for providing trie updates for creating TrieLogs. */ public interface TrieLogAccumulator { /** diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogEvent.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogEvent.java index dc5e60761d9..e383a6cedde 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogEvent.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogEvent.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogFactory.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogFactory.java index 143bb880950..41defb707de 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogFactory.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogFactory.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogProvider.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogProvider.java index 5df4f73c453..b20e73283cb 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogProvider.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/trielogs/TrieLogProvider.java @@ -1,5 +1,5 @@ /* - * Copyright contributors to Hyperledger Besu + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -19,8 +19,19 @@ import java.util.List; import java.util.Optional; +import org.apache.tuweni.bytes.Bytes; + /** Trielog provider interface for a given block hash. */ public interface TrieLogProvider { + /** + * Saves the TrieLog layer for the given block hash. + * + * @param blockHash the block hash + * @param blockNumber the block number + * @param trieLog the associated TrieLog layer + */ + void saveRawTrieLogLayer(final Hash blockHash, final long blockNumber, final Bytes trieLog); + /** * Returns the TrieLog layer for the given block hash. * @@ -30,6 +41,14 @@ public interface TrieLogProvider { */ > Optional getTrieLogLayer(final Hash blockHash); + /** + * Get the raw TrieLog layer for the given block hash. + * + * @param blockHash the block hash + * @return the raw TrieLog layer bytes for the given block hash + */ + Optional getRawTrieLogLayer(final Hash blockHash); + /** * Returns the TrieLog layer for the given block number. * @@ -39,6 +58,14 @@ public interface TrieLogProvider { */ > Optional getTrieLogLayer(final long blockNumber); + /** + * Get the raw TrieLog layer for the given block number. + * + * @param blockNumber the block number + * @return the raw TrieLog layer bytes for the given block number + */ + Optional getRawTrieLogLayer(final long blockNumber); + /** * Returns the TrieLog layers for the given block number range. * diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txselection/PluginTransactionSelector.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txselection/PluginTransactionSelector.java index 716a05e7d8b..e0bf1cf3130 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txselection/PluginTransactionSelector.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txselection/PluginTransactionSelector.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,9 +12,10 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.plugin.services.txselection; +import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.SELECTED; + import org.hyperledger.besu.datatypes.PendingTransaction; import org.hyperledger.besu.plugin.Unstable; import org.hyperledger.besu.plugin.data.TransactionProcessingResult; @@ -24,6 +25,22 @@ /** Interface for the transaction selector */ @Unstable public interface PluginTransactionSelector { + /** Plugin transaction selector that unconditionally select every transaction */ + PluginTransactionSelector ACCEPT_ALL = + new PluginTransactionSelector() { + @Override + public TransactionSelectionResult evaluateTransactionPreProcessing( + TransactionEvaluationContext evaluationContext) { + return SELECTED; + } + + @Override + public TransactionSelectionResult evaluateTransactionPostProcessing( + TransactionEvaluationContext evaluationContext, + TransactionProcessingResult processingResult) { + return SELECTED; + } + }; /** * Method that returns an OperationTracer that will be used when executing transactions that are @@ -39,39 +56,41 @@ default BlockAwareOperationTracer getOperationTracer() { * Method called to decide whether a transaction is added to a block. The result can also indicate * that no further transactions can be added to the block. * - * @param pendingTransaction candidate transaction + * @param evaluationContext The current selection context * @return TransactionSelectionResult that indicates whether to include the transaction */ TransactionSelectionResult evaluateTransactionPreProcessing( - PendingTransaction pendingTransaction); + TransactionEvaluationContext evaluationContext); /** * Method called to decide whether a processed transaction is added to a block. The result can * also indicate that no further transactions can be added to the block. * - * @param pendingTransaction candidate transaction + * @param evaluationContext The current selection context * @param processingResult the transaction processing result * @return TransactionSelectionResult that indicates whether to include the transaction */ TransactionSelectionResult evaluateTransactionPostProcessing( - PendingTransaction pendingTransaction, TransactionProcessingResult processingResult); + TransactionEvaluationContext evaluationContext, + TransactionProcessingResult processingResult); /** * Method called when a transaction is selected to be added to a block. * - * @param pendingTransaction The transaction that has been selected. + * @param evaluationContext The current selection context * @param processingResult The result of processing the selected transaction. */ default void onTransactionSelected( - final PendingTransaction pendingTransaction, + final TransactionEvaluationContext evaluationContext, final TransactionProcessingResult processingResult) {} + /** * Method called when a transaction is not selected to be added to a block. * - * @param pendingTransaction The transaction that has not been selected. + * @param evaluationContext The current selection context * @param transactionSelectionResult The transaction selection result */ default void onTransactionNotSelected( - final PendingTransaction pendingTransaction, + final TransactionEvaluationContext evaluationContext, final TransactionSelectionResult transactionSelectionResult) {} } diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txselection/PluginTransactionSelectorFactory.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txselection/PluginTransactionSelectorFactory.java index dc6311bb557..e0b7acc5840 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txselection/PluginTransactionSelectorFactory.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txselection/PluginTransactionSelectorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.plugin.services.txselection; import org.hyperledger.besu.plugin.Unstable; diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txselection/TransactionEvaluationContext.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txselection/TransactionEvaluationContext.java new file mode 100644 index 00000000000..8938ef881b4 --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txselection/TransactionEvaluationContext.java @@ -0,0 +1,65 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.services.txselection; + +import org.hyperledger.besu.datatypes.PendingTransaction; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.plugin.data.ProcessableBlockHeader; + +import com.google.common.base.Stopwatch; + +/** + * This interface defines the context for evaluating a transaction. It provides methods to get the + * pending transaction, the evaluation timer, and the transaction gas price. + * + * @param the type of the pending transaction + */ +public interface TransactionEvaluationContext { + + /** + * Gets the pending block header + * + * @return the pending block header + */ + ProcessableBlockHeader getPendingBlockHeader(); + + /** + * Gets the pending transaction. + * + * @return the pending transaction + */ + PT getPendingTransaction(); + + /** + * Gets the stopwatch used for timing the evaluation. + * + * @return the evaluation timer + */ + Stopwatch getEvaluationTimer(); + + /** + * Gets the gas price of the transaction. + * + * @return the transaction gas price + */ + Wei getTransactionGasPrice(); + + /** + * Gets the min gas price for block inclusion + * + * @return the min gas price + */ + Wei getMinGasPrice(); +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txvalidator/PluginTransactionPoolValidator.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txvalidator/PluginTransactionPoolValidator.java new file mode 100644 index 00000000000..0aa22f9b9c1 --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txvalidator/PluginTransactionPoolValidator.java @@ -0,0 +1,40 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.services.txvalidator; + +import org.hyperledger.besu.datatypes.Transaction; +import org.hyperledger.besu.plugin.Unstable; + +import java.util.Optional; + +/** Interface for the transaction validator plugin for txpool usage */ +@Unstable +public interface PluginTransactionPoolValidator { + /** Plugin transaction pool validator that unconditionally validates every transaction */ + PluginTransactionPoolValidator VALIDATE_ALL = + (transaction, isLocal, hasPriority) -> Optional.empty(); + + /** + * Method called to decide whether a transaction can be added to the transaction pool. + * + * @param transaction candidate transaction + * @param isLocal if the transaction was sent to this node via API + * @param hasPriority if the transaction has priority + * @return Optional.empty() if the transaction is valid, an Optional containing an error message, + * if not + */ + Optional validateTransaction( + final Transaction transaction, final boolean isLocal, final boolean hasPriority); +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txvalidator/PluginTransactionPoolValidatorFactory.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txvalidator/PluginTransactionPoolValidatorFactory.java new file mode 100644 index 00000000000..8dec48c49d9 --- /dev/null +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txvalidator/PluginTransactionPoolValidatorFactory.java @@ -0,0 +1,29 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.services.txvalidator; + +import org.hyperledger.besu.plugin.Unstable; + +/** Interface for a factory that creates transaction validators for txpool usage */ +@Unstable +public interface PluginTransactionPoolValidatorFactory { + + /** + * Create a transaction validator for txpool usage + * + * @return the transaction validator + */ + PluginTransactionPoolValidator createTransactionValidator(); +} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txvalidator/PluginTransactionValidator.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txvalidator/PluginTransactionValidator.java deleted file mode 100644 index ab47b31f0d7..00000000000 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txvalidator/PluginTransactionValidator.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.besu.plugin.services.txvalidator; - -import org.hyperledger.besu.datatypes.Transaction; -import org.hyperledger.besu.plugin.Unstable; - -import java.util.Optional; - -/** Interface for the transaction validator plugin */ -@Unstable -public interface PluginTransactionValidator { - - /** - * Method called to decide whether a transaction can be added to the transaction pool. - * - * @param transaction candidate transaction - * @return Optional.empty() if the transaction is valid, an Optional containing an error message, - * if not - */ - Optional validateTransaction(final Transaction transaction); -} diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txvalidator/PluginTransactionValidatorFactory.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txvalidator/PluginTransactionValidatorFactory.java deleted file mode 100644 index bfbf86bd1be..00000000000 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txvalidator/PluginTransactionValidatorFactory.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.hyperledger.besu.plugin.services.txvalidator; - -import org.hyperledger.besu.plugin.Unstable; - -/** Interface for a factory that creates transaction validators */ -@Unstable -public interface PluginTransactionValidatorFactory { - - /** - * Create a transaction validator - * - * @return the transaction validator - */ - PluginTransactionValidator create(); -} diff --git a/plugins/rocksdb/build.gradle b/plugins/rocksdb/build.gradle index f76e8d80ae0..e5ca782504a 100644 --- a/plugins/rocksdb/build.gradle +++ b/plugins/rocksdb/build.gradle @@ -20,7 +20,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } @@ -41,6 +42,7 @@ dependencies { implementation project(':util') implementation 'com.fasterxml.jackson.core:jackson-databind' + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8' implementation 'com.google.guava:guava' implementation 'info.picocli:picocli' implementation 'io.opentelemetry:opentelemetry-api' diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBKeyValuePrivacyStorageFactory.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBKeyValuePrivacyStorageFactory.java index 355cc056924..6debcc046e5 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBKeyValuePrivacyStorageFactory.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBKeyValuePrivacyStorageFactory.java @@ -22,11 +22,15 @@ import org.hyperledger.besu.plugin.services.storage.SegmentIdentifier; import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.DatabaseMetadata; +import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.PrivacyVersionedStorageFormat; +import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.VersionedStorageFormat; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.EnumSet; import java.util.List; +import java.util.Optional; import java.util.Set; import org.slf4j.Logger; @@ -39,12 +43,13 @@ public class RocksDBKeyValuePrivacyStorageFactory implements PrivacyKeyValueStorageFactory { private static final Logger LOG = LoggerFactory.getLogger(RocksDBKeyValuePrivacyStorageFactory.class); - private static final int DEFAULT_VERSION = 1; - private static final Set SUPPORTED_VERSIONS = Set.of(1); - + private static final Set SUPPORTED_VERSIONS = + EnumSet.of( + PrivacyVersionedStorageFormat.FOREST_WITH_RECEIPT_COMPACTION, + PrivacyVersionedStorageFormat.BONSAI_WITH_RECEIPT_COMPACTION); private static final String PRIVATE_DATABASE_PATH = "private"; private final RocksDBKeyValueStorageFactory publicFactory; - private Integer databaseVersion; + private DatabaseMetadata databaseMetadata; /** * Instantiates a new RocksDb key value privacy storage factory. @@ -66,9 +71,9 @@ public KeyValueStorage create( final BesuConfiguration commonConfiguration, final MetricsSystem metricsSystem) throws StorageException { - if (databaseVersion == null) { + if (databaseMetadata == null) { try { - databaseVersion = readDatabaseVersion(commonConfiguration); + databaseMetadata = readDatabaseMetadata(commonConfiguration); } catch (final IOException e) { throw new StorageException("Failed to retrieve the RocksDB database meta version", e); } @@ -83,9 +88,9 @@ public SegmentedKeyValueStorage create( final BesuConfiguration commonConfiguration, final MetricsSystem metricsSystem) throws StorageException { - if (databaseVersion == null) { + if (databaseMetadata == null) { try { - databaseVersion = readDatabaseVersion(commonConfiguration); + databaseMetadata = readDatabaseMetadata(commonConfiguration); } catch (final IOException e) { throw new StorageException("Failed to retrieve the RocksDB database meta version", e); } @@ -114,37 +119,145 @@ public void close() throws IOException { * private database exists there may be a "privacyVersion" field in the metadata file otherwise * use the default version */ - private int readDatabaseVersion(final BesuConfiguration commonConfiguration) throws IOException { + private DatabaseMetadata readDatabaseMetadata(final BesuConfiguration commonConfiguration) + throws IOException { final Path dataDir = commonConfiguration.getDataPath(); final boolean privacyDatabaseExists = commonConfiguration.getStoragePath().resolve(PRIVATE_DATABASE_PATH).toFile().exists(); - final int privacyDatabaseVersion; - if (privacyDatabaseExists) { - privacyDatabaseVersion = DatabaseMetadata.lookUpFrom(dataDir).maybePrivacyVersion().orElse(1); - LOG.info( - "Existing private database detected at {}. Version {}", dataDir, privacyDatabaseVersion); + final boolean privacyMetadataExists = DatabaseMetadata.isPresent(dataDir); + DatabaseMetadata privacyMetadata; + if (privacyDatabaseExists && !privacyMetadataExists) { + throw new StorageException( + "Privacy database exists but metadata file not found, without it there is no safe way to open the database"); + } + if (privacyMetadataExists) { + final var existingPrivacyMetadata = DatabaseMetadata.lookUpFrom(dataDir); + final var maybeExistingPrivacyVersion = + existingPrivacyMetadata.getVersionedStorageFormat().getPrivacyVersion(); + if (maybeExistingPrivacyVersion.isEmpty()) { + privacyMetadata = existingPrivacyMetadata.upgradeToPrivacy(); + privacyMetadata.writeToDirectory(dataDir); + LOG.info( + "Upgraded existing database at {} to privacy database. Metadata {}", + dataDir, + existingPrivacyMetadata); + } else { + privacyMetadata = existingPrivacyMetadata; + final int existingPrivacyVersion = maybeExistingPrivacyVersion.getAsInt(); + final var runtimeVersion = + PrivacyVersionedStorageFormat.defaultForNewDB( + commonConfiguration.getDataStorageConfiguration()); + + if (existingPrivacyVersion > runtimeVersion.getPrivacyVersion().getAsInt()) { + final var maybeDowngradedMetadata = + handleVersionDowngrade(dataDir, privacyMetadata, runtimeVersion); + if (maybeDowngradedMetadata.isPresent()) { + privacyMetadata = maybeDowngradedMetadata.get(); + privacyMetadata.writeToDirectory(dataDir); + } + } else if (existingPrivacyVersion < runtimeVersion.getPrivacyVersion().getAsInt()) { + final var maybeUpgradedMetadata = + handleVersionUpgrade(dataDir, privacyMetadata, runtimeVersion); + if (maybeUpgradedMetadata.isPresent()) { + privacyMetadata = maybeUpgradedMetadata.get(); + privacyMetadata.writeToDirectory(dataDir); + } + } else { + LOG.info("Existing privacy database at {}. Metadata {}", dataDir, privacyMetadata); + } + } } else { - privacyDatabaseVersion = DEFAULT_VERSION; + privacyMetadata = DatabaseMetadata.defaultForNewPrivateDb(); LOG.info( - "No existing private database detected at {}. Using version {}", + "No existing private database at {}. Using default metadata for new db {}", dataDir, - privacyDatabaseVersion); + privacyMetadata); Files.createDirectories(dataDir); - new DatabaseMetadata(publicFactory.getDefaultVersion(), privacyDatabaseVersion) - .writeToDirectory(dataDir); + privacyMetadata.writeToDirectory(dataDir); } - if (!SUPPORTED_VERSIONS.contains(privacyDatabaseVersion)) { - final String message = "Unsupported RocksDB Metadata version of: " + privacyDatabaseVersion; + if (!SUPPORTED_VERSIONS.contains(privacyMetadata.getVersionedStorageFormat())) { + final String message = "Unsupported RocksDB Metadata version of: " + privacyMetadata; LOG.error(message); throw new StorageException(message); } - return privacyDatabaseVersion; + return privacyMetadata; + } + + private Optional handleVersionDowngrade( + final Path dataDir, + final DatabaseMetadata existingPrivacyMetadata, + final VersionedStorageFormat runtimeVersion) { + // here we put the code, or the messages, to perform an automated, or manual, downgrade of the + // database, if supported, otherwise we just prevent Besu from starting since it will not + // recognize the newer version. + // In case we do an automated downgrade, then we also need to update the metadata on disk to + // reflect the change to the runtime version, and return it. + + // Besu supports both formats of receipts so no downgrade is needed + if (runtimeVersion == PrivacyVersionedStorageFormat.BONSAI_WITH_VARIABLES + || runtimeVersion == PrivacyVersionedStorageFormat.FOREST_WITH_VARIABLES) { + LOG.warn( + "Database contains compacted receipts but receipt compaction is not enabled, new receipts will " + + "be not stored in the compacted format. If you want to remove compacted receipts from the " + + "database it is necessary to resync Besu. Besu can support both compacted and non-compacted receipts."); + return Optional.empty(); + } + + // for the moment there are supported automated downgrades, so we just fail. + String error = + String.format( + "Database unsafe downgrade detect: DB at %s is %s with version %s but version %s is expected. " + + "Please check your config and review release notes for supported downgrade procedures.", + dataDir, + existingPrivacyMetadata.getVersionedStorageFormat().getFormat().name(), + existingPrivacyMetadata.getVersionedStorageFormat().getVersion(), + runtimeVersion.getVersion()); + + throw new StorageException(error); + } + + private Optional handleVersionUpgrade( + final Path dataDir, + final DatabaseMetadata existingPrivacyMetadata, + final VersionedStorageFormat runtimeVersion) { + // here we put the code, or the messages, to perform an automated, or manual, upgrade of the + // database. + // In case we do an automated upgrade, then we also need to update the metadata on disk to + // reflect the change to the runtime version, and return it. + + // Besu supports both formats of receipts so no upgrade is needed other than updating metadata + final VersionedStorageFormat existingVersionedStorageFormat = + existingPrivacyMetadata.getVersionedStorageFormat(); + if ((existingVersionedStorageFormat == PrivacyVersionedStorageFormat.BONSAI_WITH_VARIABLES + && runtimeVersion == PrivacyVersionedStorageFormat.BONSAI_WITH_RECEIPT_COMPACTION) + || (existingVersionedStorageFormat == PrivacyVersionedStorageFormat.FOREST_WITH_VARIABLES + && runtimeVersion == PrivacyVersionedStorageFormat.FOREST_WITH_RECEIPT_COMPACTION)) { + final DatabaseMetadata metadata = new DatabaseMetadata(runtimeVersion); + try { + metadata.writeToDirectory(dataDir); + return Optional.of(metadata); + } catch (IOException e) { + throw new StorageException("Database upgrade to use receipt compaction failed", e); + } + } + + // for the moment there are no planned automated upgrades, so we just fail. + String error = + String.format( + "Database unsafe upgrade detect: DB at %s is %s with version %s but version %s is expected. " + + "Please check your config and review release notes for supported upgrade procedures.", + dataDir, + existingVersionedStorageFormat.getFormat().name(), + existingVersionedStorageFormat.getVersion(), + runtimeVersion.getVersion()); + + throw new StorageException(error); } @Override public int getVersion() { - return databaseVersion; + return databaseMetadata.getVersionedStorageFormat().getPrivacyVersion().getAsInt(); } } diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBKeyValueStorageFactory.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBKeyValueStorageFactory.java index 0f08e3c0f9b..d53c9e57fde 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBKeyValueStorageFactory.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBKeyValueStorageFactory.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -14,7 +14,11 @@ */ package org.hyperledger.besu.plugin.services.storage.rocksdb; -import org.hyperledger.besu.ethereum.worldstate.DataStorageFormat; +import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.BaseVersionedStorageFormat.BONSAI_WITH_RECEIPT_COMPACTION; +import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.BaseVersionedStorageFormat.BONSAI_WITH_VARIABLES; +import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.BaseVersionedStorageFormat.FOREST_WITH_RECEIPT_COMPACTION; +import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.BaseVersionedStorageFormat.FOREST_WITH_VARIABLES; + import org.hyperledger.besu.plugin.services.BesuConfiguration; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.plugin.services.exception.StorageException; @@ -22,10 +26,12 @@ import org.hyperledger.besu.plugin.services.storage.KeyValueStorageFactory; import org.hyperledger.besu.plugin.services.storage.SegmentIdentifier; import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; +import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.BaseVersionedStorageFormat; import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.DatabaseMetadata; import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBConfiguration; import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBConfigurationBuilder; import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBFactoryConfiguration; +import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.VersionedStorageFormat; import org.hyperledger.besu.plugin.services.storage.rocksdb.segmented.OptimisticRocksDBColumnarKeyValueStorage; import org.hyperledger.besu.plugin.services.storage.rocksdb.segmented.RocksDBColumnarKeyValueStorage; import org.hyperledger.besu.plugin.services.storage.rocksdb.segmented.TransactionDBRocksDBColumnarKeyValueStorage; @@ -34,8 +40,9 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.EnumSet; import java.util.List; -import java.util.Set; +import java.util.Optional; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -49,13 +56,11 @@ public class RocksDBKeyValueStorageFactory implements KeyValueStorageFactory { private static final Logger LOG = LoggerFactory.getLogger(RocksDBKeyValueStorageFactory.class); - private static final int DEFAULT_VERSION = 1; - private static final Set SUPPORTED_VERSIONS = Set.of(1, 2); + private static final EnumSet SUPPORTED_VERSIONED_FORMATS = + EnumSet.of(FOREST_WITH_RECEIPT_COMPACTION, BONSAI_WITH_RECEIPT_COMPACTION); private static final String NAME = "rocksdb"; private final RocksDBMetricsFactory rocksDBMetricsFactory; - - private final int defaultVersion; - private Integer databaseVersion; + private DatabaseMetadata databaseMetadata; private RocksDBColumnarKeyValueStorage segmentedStorage; private RocksDBConfiguration rocksDBConfiguration; @@ -69,19 +74,16 @@ public class RocksDBKeyValueStorageFactory implements KeyValueStorageFactory { * @param configuration the configuration * @param configuredSegments the segments * @param ignorableSegments the ignorable segments - * @param defaultVersion the default version * @param rocksDBMetricsFactory the rocks db metrics factory */ public RocksDBKeyValueStorageFactory( final Supplier configuration, final List configuredSegments, final List ignorableSegments, - final int defaultVersion, final RocksDBMetricsFactory rocksDBMetricsFactory) { this.configuration = configuration; this.configuredSegments = configuredSegments; this.ignorableSegments = ignorableSegments; - this.defaultVersion = defaultVersion; this.rocksDBMetricsFactory = rocksDBMetricsFactory; } @@ -90,59 +92,13 @@ public RocksDBKeyValueStorageFactory( * * @param configuration the configuration * @param configuredSegments the segments - * @param defaultVersion the default version - * @param rocksDBMetricsFactory the rocks db metrics factory - */ - public RocksDBKeyValueStorageFactory( - final Supplier configuration, - final List configuredSegments, - final int defaultVersion, - final RocksDBMetricsFactory rocksDBMetricsFactory) { - this(configuration, configuredSegments, List.of(), defaultVersion, rocksDBMetricsFactory); - } - - /** - * Instantiates a new Rocks db key value storage factory. - * - * @param configuration the configuration - * @param configuredSegments the segments - * @param ignorableSegments the ignorable segments - * @param rocksDBMetricsFactory the rocks db metrics factory - */ - public RocksDBKeyValueStorageFactory( - final Supplier configuration, - final List configuredSegments, - final List ignorableSegments, - final RocksDBMetricsFactory rocksDBMetricsFactory) { - this( - configuration, - configuredSegments, - ignorableSegments, - DEFAULT_VERSION, - rocksDBMetricsFactory); - } - - /** - * Instantiates a new Rocks db key value storage factory. - * - * @param configuration the configuration - * @param configuredSegments the segments * @param rocksDBMetricsFactory the rocks db metrics factory */ public RocksDBKeyValueStorageFactory( final Supplier configuration, final List configuredSegments, final RocksDBMetricsFactory rocksDBMetricsFactory) { - this(configuration, configuredSegments, List.of(), DEFAULT_VERSION, rocksDBMetricsFactory); - } - - /** - * Gets default version. - * - * @return the default version - */ - int getDefaultVersion() { - return defaultVersion; + this(configuration, configuredSegments, List.of(), rocksDBMetricsFactory); } @Override @@ -166,8 +122,6 @@ public SegmentedKeyValueStorage create( final BesuConfiguration commonConfiguration, final MetricsSystem metricsSystem) throws StorageException { - final boolean isForestStorageFormat = - DataStorageFormat.FOREST.getDatabaseVersion() == commonConfiguration.getDatabaseVersion(); if (requiresInit()) { init(commonConfiguration); } @@ -182,43 +136,43 @@ public SegmentedKeyValueStorage create( .collect(Collectors.joining(", "))); } - // It's probably a good idea for the creation logic to be entirely dependent on the database - // version. Introducing intermediate booleans that represent database properties and dispatching - // creation logic based on them is error-prone. - switch (databaseVersion) { - case 1, 2 -> { - if (segmentedStorage == null) { - final List segmentsForVersion = - configuredSegments.stream() - .filter(segmentId -> segmentId.includeInDatabaseVersion(databaseVersion)) - .collect(Collectors.toList()); - if (isForestStorageFormat) { - LOG.debug("FOREST mode detected, using TransactionDB."); - segmentedStorage = - new TransactionDBRocksDBColumnarKeyValueStorage( - rocksDBConfiguration, - segmentsForVersion, - ignorableSegments, - metricsSystem, - rocksDBMetricsFactory); - } else { - LOG.debug("Using OptimisticTransactionDB."); - segmentedStorage = - new OptimisticRocksDBColumnarKeyValueStorage( - rocksDBConfiguration, - segmentsForVersion, - ignorableSegments, - metricsSystem, - rocksDBMetricsFactory); - } + if (segmentedStorage == null) { + final List segmentsForFormat = + configuredSegments.stream() + .filter( + segmentId -> + segmentId.includeInDatabaseFormat( + databaseMetadata.getVersionedStorageFormat().getFormat())) + .toList(); + + // It's probably a good idea for the creation logic to be entirely dependent on the database + // version. Introducing intermediate booleans that represent database properties and + // dispatching + // creation logic based on them is error-prone. + switch (databaseMetadata.getVersionedStorageFormat().getFormat()) { + case FOREST -> { + LOG.debug("FOREST mode detected, using TransactionDB."); + segmentedStorage = + new TransactionDBRocksDBColumnarKeyValueStorage( + rocksDBConfiguration, + segmentsForFormat, + ignorableSegments, + metricsSystem, + rocksDBMetricsFactory); + } + case BONSAI -> { + LOG.debug("BONSAI mode detected, Using OptimisticTransactionDB."); + segmentedStorage = + new OptimisticRocksDBColumnarKeyValueStorage( + rocksDBConfiguration, + segmentsForFormat, + ignorableSegments, + metricsSystem, + rocksDBMetricsFactory); } - return segmentedStorage; } - default -> throw new IllegalStateException( - String.format( - "Developer error: A supported database version (%d) was detected but there is no associated creation logic.", - databaseVersion)); } + return segmentedStorage; } /** @@ -233,7 +187,7 @@ protected Path storagePath(final BesuConfiguration commonConfiguration) { private void init(final BesuConfiguration commonConfiguration) { try { - databaseVersion = readDatabaseVersion(commonConfiguration); + databaseMetadata = readDatabaseMetadata(commonConfiguration); } catch (final IOException e) { final String message = "Failed to retrieve the RocksDB database meta version: " @@ -251,33 +205,160 @@ private boolean requiresInit() { return segmentedStorage == null; } - private int readDatabaseVersion(final BesuConfiguration commonConfiguration) throws IOException { + private DatabaseMetadata readDatabaseMetadata(final BesuConfiguration commonConfiguration) + throws IOException { final Path dataDir = commonConfiguration.getDataPath(); - final boolean databaseExists = commonConfiguration.getStoragePath().toFile().exists(); final boolean dataDirExists = dataDir.toFile().exists(); - final int databaseVersion; - if (databaseExists) { - databaseVersion = DatabaseMetadata.lookUpFrom(dataDir).getVersion(); - LOG.info( - "Existing database detected at {}. Version {}. Compacting database...", - dataDir, - databaseVersion); + final boolean databaseExists = commonConfiguration.getStoragePath().toFile().exists(); + final boolean metadataExists = DatabaseMetadata.isPresent(dataDir); + DatabaseMetadata metadata; + if (databaseExists && !metadataExists) { + throw new StorageException( + "Database exists but metadata file not found, without it there is no safe way to open the database"); + } + if (metadataExists) { + metadata = DatabaseMetadata.lookUpFrom(dataDir); + + if (!metadata + .getVersionedStorageFormat() + .getFormat() + .equals(commonConfiguration.getDataStorageConfiguration().getDatabaseFormat())) { + handleFormatMismatch(commonConfiguration, dataDir, metadata); + } + + final var runtimeVersion = + BaseVersionedStorageFormat.defaultForNewDB( + commonConfiguration.getDataStorageConfiguration()); + + if (metadata.getVersionedStorageFormat().getVersion() > runtimeVersion.getVersion()) { + final var maybeDowngradedMetadata = + handleVersionDowngrade(dataDir, metadata, runtimeVersion); + if (maybeDowngradedMetadata.isPresent()) { + metadata = maybeDowngradedMetadata.get(); + metadata.writeToDirectory(dataDir); + } + } + + if (metadata.getVersionedStorageFormat().getVersion() < runtimeVersion.getVersion()) { + final var maybeUpgradedMetadata = handleVersionUpgrade(dataDir, metadata, runtimeVersion); + if (maybeUpgradedMetadata.isPresent()) { + metadata = maybeUpgradedMetadata.get(); + metadata.writeToDirectory(dataDir); + } + } + + LOG.info("Existing database at {}. Metadata {}. Processing WAL...", dataDir, metadata); } else { - databaseVersion = commonConfiguration.getDatabaseVersion(); - LOG.info("No existing database detected at {}. Using version {}", dataDir, databaseVersion); + + metadata = DatabaseMetadata.defaultForNewDb(commonConfiguration); + LOG.info( + "No existing database at {}. Using default metadata for new db {}", dataDir, metadata); if (!dataDirExists) { Files.createDirectories(dataDir); } - new DatabaseMetadata(databaseVersion).writeToDirectory(dataDir); + metadata.writeToDirectory(dataDir); } - if (!SUPPORTED_VERSIONS.contains(databaseVersion)) { - final String message = "Unsupported RocksDB Metadata version of: " + databaseVersion; + if (!isSupportedVersionedFormat(metadata.getVersionedStorageFormat())) { + final String message = "Unsupported RocksDB metadata: " + metadata; LOG.error(message); throw new StorageException(message); } - return databaseVersion; + return metadata; + } + + private static void handleFormatMismatch( + final BesuConfiguration commonConfiguration, + final Path dataDir, + final DatabaseMetadata existingMetadata) { + String error = + String.format( + "Database format mismatch: DB at %s is %s but config expects %s. " + + "Please check your config.", + dataDir, + existingMetadata.getVersionedStorageFormat().getFormat().name(), + commonConfiguration.getDataStorageConfiguration().getDatabaseFormat()); + + throw new StorageException(error); + } + + private Optional handleVersionDowngrade( + final Path dataDir, + final DatabaseMetadata existingMetadata, + final BaseVersionedStorageFormat runtimeVersion) { + // here we put the code, or the messages, to perform an automated, or manual, downgrade of the + // database, if supported, otherwise we just prevent Besu from starting since it will not + // recognize the newer version. + // In case we do an automated downgrade, then we also need to update the metadata on disk to + // reflect the change to the runtime version, and return it. + + // Besu supports both formats of receipts so no downgrade is needed + if (runtimeVersion == BONSAI_WITH_VARIABLES || runtimeVersion == FOREST_WITH_VARIABLES) { + LOG.warn( + "Database contains compacted receipts but receipt compaction is not enabled, new receipts will " + + "be not stored in the compacted format. If you want to remove compacted receipts from the " + + "database it is necessary to resync Besu. Besu can support both compacted and non-compacted receipts."); + return Optional.empty(); + } + + // for the moment there are supported automated downgrades, so we just fail. + String error = + String.format( + "Database unsafe downgrade detect: DB at %s is %s with version %s but version %s is expected. " + + "Please check your config and review release notes for supported downgrade procedures.", + dataDir, + existingMetadata.getVersionedStorageFormat().getFormat().name(), + existingMetadata.getVersionedStorageFormat().getVersion(), + runtimeVersion.getVersion()); + + throw new StorageException(error); + } + + private Optional handleVersionUpgrade( + final Path dataDir, + final DatabaseMetadata existingMetadata, + final BaseVersionedStorageFormat runtimeVersion) { + // here we put the code, or the messages, to perform an automated, or manual, upgrade of the + // database. + // In case we do an automated upgrade, then we also need to update the metadata on disk to + // reflect the change to the runtime version, and return it. + + // Besu supports both formats of receipts so no upgrade is needed other than updating metadata + final VersionedStorageFormat existingVersionedStorageFormat = + existingMetadata.getVersionedStorageFormat(); + if ((existingVersionedStorageFormat == BONSAI_WITH_VARIABLES + && runtimeVersion == BONSAI_WITH_RECEIPT_COMPACTION) + || (existingVersionedStorageFormat == FOREST_WITH_VARIABLES + && runtimeVersion == FOREST_WITH_RECEIPT_COMPACTION)) { + final DatabaseMetadata metadata = new DatabaseMetadata(runtimeVersion); + try { + metadata.writeToDirectory(dataDir); + return Optional.of(metadata); + } catch (IOException e) { + throw new StorageException("Database upgrade to use receipt compaction failed", e); + } + } + + // for the moment there are no planned automated upgrades, so we just fail. + String error = + String.format( + "Database unsafe downgrade detect: DB at %s is %s with version %s but version %s is expected. " + + "Please check your config and review release notes for supported downgrade procedures.", + dataDir, + existingVersionedStorageFormat.getFormat().name(), + existingVersionedStorageFormat.getVersion(), + runtimeVersion.getVersion()); + + throw new StorageException(error); + } + + private boolean isSupportedVersionedFormat(final VersionedStorageFormat versionedStorageFormat) { + return SUPPORTED_VERSIONED_FORMATS.stream() + .anyMatch( + vsf -> + vsf.getFormat().equals(versionedStorageFormat.getFormat()) + && vsf.getVersion() == versionedStorageFormat.getVersion()); } @Override diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBTransaction.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBTransaction.java index 53f7a8fedac..24ab13c4726 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBTransaction.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBTransaction.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDbIterator.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDbIterator.java index 9a5f9c4a184..ef7f10e6c4e 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDbIterator.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDbIterator.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.plugin.services.storage.rocksdb; import static com.google.common.base.Preconditions.checkState; diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDbSegmentIdentifier.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDbSegmentIdentifier.java index 76d130395d9..c077323654c 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDbSegmentIdentifier.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDbSegmentIdentifier.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/BaseVersionedStorageFormat.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/BaseVersionedStorageFormat.java new file mode 100644 index 00000000000..ad3557636d2 --- /dev/null +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/BaseVersionedStorageFormat.java @@ -0,0 +1,90 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.services.storage.rocksdb.configuration; + +import org.hyperledger.besu.plugin.services.storage.DataStorageConfiguration; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; + +import java.util.OptionalInt; + +/** Base versioned data storage format */ +public enum BaseVersionedStorageFormat implements VersionedStorageFormat { + /** Original Forest version, not used since replace by FOREST_WITH_VARIABLES */ + FOREST_ORIGINAL(DataStorageFormat.FOREST, 1), + /** + * Current Forest version, with blockchain variables in a dedicated column family, in order to + * make BlobDB more effective + */ + FOREST_WITH_VARIABLES(DataStorageFormat.FOREST, 2), + /** + * Current Forest version, with receipts using compaction, in order to make Receipts use less disk + * space + */ + FOREST_WITH_RECEIPT_COMPACTION(DataStorageFormat.FOREST, 3), + /** Original Bonsai version, not used since replace by BONSAI_WITH_VARIABLES */ + BONSAI_ORIGINAL(DataStorageFormat.BONSAI, 1), + /** + * Current Bonsai version, with blockchain variables in a dedicated column family, in order to + * make BlobDB more effective + */ + BONSAI_WITH_VARIABLES(DataStorageFormat.BONSAI, 2), + /** + * Current Bonsai version, with receipts using compaction, in order to make Receipts use less disk + * space + */ + BONSAI_WITH_RECEIPT_COMPACTION(DataStorageFormat.BONSAI, 3); + + private final DataStorageFormat format; + private final int version; + + BaseVersionedStorageFormat(final DataStorageFormat format, final int version) { + this.format = format; + this.version = version; + } + + /** + * Return the default version for new db for a specific format + * + * @param configuration data storage configuration + * @return the version to use for new db + */ + public static BaseVersionedStorageFormat defaultForNewDB( + final DataStorageConfiguration configuration) { + return switch (configuration.getDatabaseFormat()) { + case FOREST -> FOREST_WITH_RECEIPT_COMPACTION; + case BONSAI -> BONSAI_WITH_RECEIPT_COMPACTION; + }; + } + + @Override + public DataStorageFormat getFormat() { + return format; + } + + @Override + public int getVersion() { + return version; + } + + @Override + public OptionalInt getPrivacyVersion() { + return OptionalInt.empty(); + } + + @Override + public String toString() { + return "BaseVersionedStorageFormat{" + "format=" + format + ", version=" + version + '}'; + } +} diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/DatabaseMetadata.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/DatabaseMetadata.java index 1faf610cdc9..a72db7c322b 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/DatabaseMetadata.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/DatabaseMetadata.java @@ -14,19 +14,24 @@ */ package org.hyperledger.besu.plugin.services.storage.rocksdb.configuration; +import org.hyperledger.besu.plugin.services.BesuConfiguration; +import org.hyperledger.besu.plugin.services.exception.StorageException; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; + import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Path; -import java.util.Optional; +import java.util.Arrays; +import java.util.OptionalInt; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonGetter; import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DatabindException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,79 +40,50 @@ public class DatabaseMetadata { private static final Logger LOG = LoggerFactory.getLogger(DatabaseMetadata.class); private static final String METADATA_FILENAME = "DATABASE_METADATA.json"; - private static final ObjectMapper MAPPER = new ObjectMapper(); - private final int version; - - private Optional privacyVersion; + private static final ObjectMapper MAPPER = + new ObjectMapper() + .registerModule(new Jdk8Module()) + .setSerializationInclusion(JsonInclude.Include.NON_ABSENT) + .enable(SerializationFeature.INDENT_OUTPUT); + private final VersionedStorageFormat versionedStorageFormat; /** * Instantiates a new Database metadata. * - * @param version the version + * @param versionedStorageFormat the version storage format */ - @JsonCreator - public DatabaseMetadata(@JsonProperty("version") final int version) { - this(version, Optional.empty()); + public DatabaseMetadata(final VersionedStorageFormat versionedStorageFormat) { + this.versionedStorageFormat = versionedStorageFormat; } /** - * Instantiates a new Database metadata. + * Return the default metadata for new db for a specific format * - * @param version the version - * @param privacyVersion the privacy version + * @param besuConfiguration besu configuration + * @return the metadata to use for new db */ - public DatabaseMetadata(final int version, final Optional privacyVersion) { - this.version = version; - this.privacyVersion = privacyVersion; + public static DatabaseMetadata defaultForNewDb(final BesuConfiguration besuConfiguration) { + return new DatabaseMetadata( + BaseVersionedStorageFormat.defaultForNewDB( + besuConfiguration.getDataStorageConfiguration())); } /** - * Instantiates a new Database metadata. + * Return the default metadata for new db when privacy feature is enabled * - * @param version the version - * @param privacyVersion the privacy version + * @return the metadata to use for new db */ - public DatabaseMetadata(final int version, final int privacyVersion) { - this(version, Optional.of(privacyVersion)); + public static DatabaseMetadata defaultForNewPrivateDb() { + return new DatabaseMetadata(PrivacyVersionedStorageFormat.FOREST_WITH_RECEIPT_COMPACTION); } /** - * Gets version. + * Return the version storage format contained in this metadata * - * @return the version + * @return version storage format */ - public int getVersion() { - return version; - } - - /** - * Sets privacy version. - * - * @param privacyVersion the privacy version - */ - @JsonSetter("privacyVersion") - public void setPrivacyVersion(final int privacyVersion) { - this.privacyVersion = Optional.of(privacyVersion); - } - - /** - * Gets privacy version. - * - * @return the privacy version - */ - @JsonInclude(JsonInclude.Include.NON_NULL) - @JsonGetter("privacyVersion") - public Integer getPrivacyVersion() { - return privacyVersion.orElse(null); - } - - /** - * Maybe privacy version. - * - * @return the optional - */ - public Optional maybePrivacyVersion() { - return privacyVersion; + public VersionedStorageFormat getVersionedStorageFormat() { + return versionedStorageFormat; } /** @@ -122,6 +98,17 @@ public static DatabaseMetadata lookUpFrom(final Path dataDir) throws IOException return resolveDatabaseMetadata(getDefaultMetadataFile(dataDir)); } + /** + * Is the metadata file present in the specified data dir? + * + * @param dataDir the dir to search for the metadata file + * @return true is the metadata file exists, false otherwise + * @throws IOException if there is an error trying to access the metadata file + */ + public static boolean isPresent(final Path dataDir) throws IOException { + return getDefaultMetadataFile(dataDir).exists(); + } + /** * Write to directory. * @@ -129,16 +116,17 @@ public static DatabaseMetadata lookUpFrom(final Path dataDir) throws IOException * @throws IOException the io exception */ public void writeToDirectory(final Path dataDir) throws IOException { - try { - final DatabaseMetadata currentMetadata = - MAPPER.readValue(getDefaultMetadataFile(dataDir), DatabaseMetadata.class); - if (currentMetadata.maybePrivacyVersion().isPresent()) { - setPrivacyVersion(currentMetadata.getPrivacyVersion()); - } - MAPPER.writeValue(getDefaultMetadataFile(dataDir), this); - } catch (FileNotFoundException fnfe) { - MAPPER.writeValue(getDefaultMetadataFile(dataDir), this); - } + writeToFile(getDefaultMetadataFile(dataDir)); + } + + private void writeToFile(final File file) throws IOException { + MAPPER.writeValue( + file, + new V2( + new MetadataV2( + versionedStorageFormat.getFormat(), + versionedStorageFormat.getVersion(), + versionedStorageFormat.getPrivacyVersion()))); } private static File getDefaultMetadataFile(final Path dataDir) { @@ -147,15 +135,137 @@ private static File getDefaultMetadataFile(final Path dataDir) { private static DatabaseMetadata resolveDatabaseMetadata(final File metadataFile) throws IOException { - DatabaseMetadata databaseMetadata; try { - databaseMetadata = MAPPER.readValue(metadataFile, DatabaseMetadata.class); + try { + return tryReadAndMigrateV1(metadataFile); + } catch (DatabindException dbe) { + return tryReadV2(metadataFile); + } } catch (FileNotFoundException fnfe) { - databaseMetadata = new DatabaseMetadata(1, 1); + throw new StorageException( + "Database exists but metadata file " + + metadataFile.toString() + + " not found, without it there is no safe way to open the database", + fnfe); } catch (JsonProcessingException jpe) { throw new IllegalStateException( String.format("Invalid metadata file %s", metadataFile.getAbsolutePath()), jpe); } - return databaseMetadata; } + + private static DatabaseMetadata tryReadAndMigrateV1(final File metadataFile) throws IOException { + final V1 v1 = MAPPER.readValue(metadataFile, V1.class); + // when migrating from v1, this version will automatically migrate the db to the variables + // storage, so we use the `_WITH_VARIABLES` variants + final VersionedStorageFormat versionedStorageFormat; + if (v1.privacyVersion().isEmpty()) { + versionedStorageFormat = + switch (v1.version()) { + case 1 -> BaseVersionedStorageFormat.FOREST_WITH_VARIABLES; + case 2 -> BaseVersionedStorageFormat.BONSAI_WITH_VARIABLES; + default -> throw new StorageException("Unsupported db version: " + v1.version()); + }; + } else { + versionedStorageFormat = + switch (v1.privacyVersion().getAsInt()) { + case 1 -> + switch (v1.version()) { + case 1 -> PrivacyVersionedStorageFormat.FOREST_WITH_VARIABLES; + case 2 -> PrivacyVersionedStorageFormat.BONSAI_WITH_VARIABLES; + default -> throw new StorageException("Unsupported db version: " + v1.version()); + }; + default -> + throw new StorageException( + "Unsupported db privacy version: " + v1.privacyVersion().getAsInt()); + }; + } + + final DatabaseMetadata metadataV2 = new DatabaseMetadata(versionedStorageFormat); + // writing the metadata will migrate to v2 + metadataV2.writeToFile(metadataFile); + return metadataV2; + } + + private static DatabaseMetadata tryReadV2(final File metadataFile) throws IOException { + final V2 v2 = MAPPER.readValue(metadataFile, V2.class); + return new DatabaseMetadata(fromV2(v2.v2)); + } + + private static VersionedStorageFormat fromV2(final MetadataV2 metadataV2) { + if (metadataV2.privacyVersion().isEmpty()) { + return Arrays.stream(BaseVersionedStorageFormat.values()) + .filter( + vsf -> + vsf.getFormat().equals(metadataV2.format()) + && vsf.getVersion() == metadataV2.version()) + .findFirst() + .orElseThrow( + () -> { + final String message = "Unsupported RocksDB metadata: " + metadataV2; + LOG.error(message); + throw new StorageException(message); + }); + } + return Arrays.stream(PrivacyVersionedStorageFormat.values()) + .filter( + vsf -> + vsf.getFormat().equals(metadataV2.format()) + && vsf.getVersion() == metadataV2.version() + && vsf.getPrivacyVersion().equals(metadataV2.privacyVersion())) + .findFirst() + .orElseThrow( + () -> { + final String message = "Unsupported RocksDB metadata: " + metadataV2; + LOG.error(message); + throw new StorageException(message); + }); + } + + /** + * Update an existing base storage to support privacy feature + * + * @return the update metadata with the privacy support + */ + public DatabaseMetadata upgradeToPrivacy() { + return new DatabaseMetadata( + switch (versionedStorageFormat.getFormat()) { + case FOREST -> + switch (versionedStorageFormat.getVersion()) { + case 1 -> PrivacyVersionedStorageFormat.FOREST_ORIGINAL; + case 2 -> PrivacyVersionedStorageFormat.FOREST_WITH_VARIABLES; + case 3 -> PrivacyVersionedStorageFormat.FOREST_WITH_RECEIPT_COMPACTION; + default -> + throw new StorageException( + "Unsupported database with format FOREST and version " + + versionedStorageFormat.getVersion()); + }; + case BONSAI -> + switch (versionedStorageFormat.getVersion()) { + case 1 -> PrivacyVersionedStorageFormat.BONSAI_ORIGINAL; + case 2 -> PrivacyVersionedStorageFormat.BONSAI_WITH_VARIABLES; + case 3 -> PrivacyVersionedStorageFormat.BONSAI_WITH_RECEIPT_COMPACTION; + default -> + throw new StorageException( + "Unsupported database with format BONSAI and version " + + versionedStorageFormat.getVersion()); + }; + }); + } + + @Override + public String toString() { + return "versionedStorageFormat=" + versionedStorageFormat; + } + + @JsonSerialize + @SuppressWarnings("unused") + private record V1(int version, OptionalInt privacyVersion) {} + + @JsonSerialize + @SuppressWarnings("unused") + private record V2(MetadataV2 v2) {} + + @JsonSerialize + @SuppressWarnings("unused") + private record MetadataV2(DataStorageFormat format, int version, OptionalInt privacyVersion) {} } diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/PrivacyVersionedStorageFormat.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/PrivacyVersionedStorageFormat.java new file mode 100644 index 00000000000..ca5988dc75f --- /dev/null +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/PrivacyVersionedStorageFormat.java @@ -0,0 +1,96 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.services.storage.rocksdb.configuration; + +import org.hyperledger.besu.plugin.services.storage.DataStorageConfiguration; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; + +import java.util.OptionalInt; + +/** Privacy enabled versioned data storage format */ +public enum PrivacyVersionedStorageFormat implements VersionedStorageFormat { + /** Original Forest version, not used since replace by FOREST_WITH_VARIABLES */ + FOREST_ORIGINAL(BaseVersionedStorageFormat.FOREST_ORIGINAL, 1), + /** + * Current Forest version, with blockchain variables in a dedicated column family, in order to + * make BlobDB more effective + */ + FOREST_WITH_VARIABLES(BaseVersionedStorageFormat.FOREST_WITH_VARIABLES, 1), + /** + * Current Forest version, with receipts using compaction, in order to make Receipts use less disk + * space + */ + FOREST_WITH_RECEIPT_COMPACTION(BaseVersionedStorageFormat.FOREST_WITH_RECEIPT_COMPACTION, 2), + /** Original Bonsai version, not used since replace by BONSAI_WITH_VARIABLES */ + BONSAI_ORIGINAL(BaseVersionedStorageFormat.BONSAI_ORIGINAL, 1), + /** + * Current Bonsai version, with blockchain variables in a dedicated column family, in order to + * make BlobDB more effective + */ + BONSAI_WITH_VARIABLES(BaseVersionedStorageFormat.BONSAI_WITH_VARIABLES, 1), + /** + * Current Bonsai version, with receipts using compaction, in order to make Receipts use less disk + * space + */ + BONSAI_WITH_RECEIPT_COMPACTION(BaseVersionedStorageFormat.BONSAI_WITH_RECEIPT_COMPACTION, 2); + + private final VersionedStorageFormat baseVersionedStorageFormat; + private final OptionalInt privacyVersion; + + PrivacyVersionedStorageFormat( + final VersionedStorageFormat baseVersionedStorageFormat, final int privacyVersion) { + this.baseVersionedStorageFormat = baseVersionedStorageFormat; + this.privacyVersion = OptionalInt.of(privacyVersion); + } + + /** + * Return the default version for new db for a specific format + * + * @param configuration data storage configuration + * @return the version to use for new db + */ + public static VersionedStorageFormat defaultForNewDB( + final DataStorageConfiguration configuration) { + return switch (configuration.getDatabaseFormat()) { + case FOREST -> FOREST_WITH_RECEIPT_COMPACTION; + case BONSAI -> BONSAI_WITH_RECEIPT_COMPACTION; + }; + } + + @Override + public DataStorageFormat getFormat() { + return baseVersionedStorageFormat.getFormat(); + } + + @Override + public int getVersion() { + return baseVersionedStorageFormat.getVersion(); + } + + @Override + public OptionalInt getPrivacyVersion() { + return privacyVersion; + } + + @Override + public String toString() { + return "PrivateVersionedStorageFormat{" + + "versionedStorageFormat=" + + baseVersionedStorageFormat + + ", privacyVersion=" + + privacyVersion + + '}'; + } +} diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/RocksDBCLIOptions.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/RocksDBCLIOptions.java index df7dcc4b5f6..76b136eebea 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/RocksDBCLIOptions.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/RocksDBCLIOptions.java @@ -22,21 +22,26 @@ public class RocksDBCLIOptions { /** The constant DEFAULT_MAX_OPEN_FILES. */ public static final int DEFAULT_MAX_OPEN_FILES = 1024; + /** The constant DEFAULT_CACHE_CAPACITY. */ public static final long DEFAULT_CACHE_CAPACITY = 134217728; /** The constant DEFAULT_BACKGROUND_THREAD_COUNT. */ public static final int DEFAULT_BACKGROUND_THREAD_COUNT = 4; + /** The constant DEFAULT_IS_HIGH_SPEC. */ public static final boolean DEFAULT_IS_HIGH_SPEC = false; /** The constant MAX_OPEN_FILES_FLAG. */ public static final String MAX_OPEN_FILES_FLAG = "--Xplugin-rocksdb-max-open-files"; + /** The constant CACHE_CAPACITY_FLAG. */ public static final String CACHE_CAPACITY_FLAG = "--Xplugin-rocksdb-cache-capacity"; + /** The constant BACKGROUND_THREAD_COUNT_FLAG. */ public static final String BACKGROUND_THREAD_COUNT_FLAG = "--Xplugin-rocksdb-background-thread-count"; + /** The constant IS_HIGH_SPEC. */ public static final String IS_HIGH_SPEC = "--Xplugin-rocksdb-high-spec-enabled"; diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/RocksDBConfigurationBuilder.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/RocksDBConfigurationBuilder.java index 4ca88a5e1d0..8e98b6cdac5 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/RocksDBConfigurationBuilder.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/RocksDBConfigurationBuilder.java @@ -31,6 +31,9 @@ public class RocksDBConfigurationBuilder { private int backgroundThreadCount = DEFAULT_BACKGROUND_THREAD_COUNT; private boolean isHighSpec = DEFAULT_IS_HIGH_SPEC; + /** Instantiates a new Rocks db configuration builder. */ + public RocksDBConfigurationBuilder() {} + /** * Database dir. * diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/VersionedStorageFormat.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/VersionedStorageFormat.java new file mode 100644 index 00000000000..f46f1662e33 --- /dev/null +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/VersionedStorageFormat.java @@ -0,0 +1,43 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.services.storage.rocksdb.configuration; + +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; + +import java.util.OptionalInt; + +/** Represent a specific version of a data storage format */ +public interface VersionedStorageFormat { + /** + * Get the data storage format + * + * @return the data storage format + */ + DataStorageFormat getFormat(); + + /** + * Get the version of the data storage format + * + * @return the version of the data storage format + */ + int getVersion(); + + /** + * Get the version of the privacy db, in case the privacy feature is enabled, or empty otherwise + * + * @return the optional privacy version + */ + OptionalInt getPrivacyVersion(); +} diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/OptimisticRocksDBColumnarKeyValueStorage.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/OptimisticRocksDBColumnarKeyValueStorage.java index fa5786e6b9a..d2eaa350239 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/OptimisticRocksDBColumnarKeyValueStorage.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/OptimisticRocksDBColumnarKeyValueStorage.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueSnapshot.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueSnapshot.java index b1a0920a516..72976a9d84b 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueSnapshot.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueSnapshot.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.plugin.services.storage.rocksdb.segmented; @@ -77,7 +76,7 @@ public Optional get(final SegmentIdentifier segment, final byte[] key) } @Override - public Optional getNearestTo( + public Optional getNearestBefore( final SegmentIdentifier segmentIdentifier, final Bytes key) throws StorageException { try (final RocksIterator rocksIterator = snapTx.getIterator(segmentIdentifier)) { @@ -88,6 +87,17 @@ public Optional getNearestTo( } } + @Override + public Optional getNearestAfter( + final SegmentIdentifier segmentIdentifier, final Bytes key) throws StorageException { + try (final RocksIterator rocksIterator = snapTx.getIterator(segmentIdentifier)) { + rocksIterator.seek(key.toArrayUnsafe()); + return Optional.of(rocksIterator) + .filter(AbstractRocksIterator::isValid) + .map(it -> new NearestKeyValue(Bytes.of(it.key()), Optional.of(it.value()))); + } + } + @Override public Stream> stream(final SegmentIdentifier segment) { throwIfClosed(); diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorage.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorage.java index 952da71f0bc..0bf107ddb0e 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorage.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorage.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors.. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -70,15 +70,24 @@ public abstract class RocksDBColumnarKeyValueStorage implements SegmentedKeyValueStorage { private static final Logger LOG = LoggerFactory.getLogger(RocksDBColumnarKeyValueStorage.class); - static final String DEFAULT_COLUMN = "default"; private static final int ROCKSDB_FORMAT_VERSION = 5; private static final long ROCKSDB_BLOCK_SIZE = 32768; + /** RocksDb blockcache size when using the high spec option */ protected static final long ROCKSDB_BLOCKCACHE_SIZE_HIGH_SPEC = 1_073_741_824L; + /** RocksDb memtable size when using the high spec option */ - protected static final long ROCKSDB_MEMTABLE_SIZE_HIGH_SPEC = 1_073_741_824L; + protected static final long ROCKSDB_MEMTABLE_SIZE_HIGH_SPEC = 536_870_912L; + + /** Max total size of all WAL file, after which a flush is triggered */ + protected static final long WAL_MAX_TOTAL_SIZE = 1_073_741_824L; + + /** Expected size of a single WAL file, to determine how many WAL files to keep around */ + protected static final long EXPECTED_WAL_FILE_SIZE = 67_108_864L; + /** RocksDb number of log files to keep on disk */ private static final long NUMBER_OF_LOG_FILES_TO_KEEP = 7; + /** RocksDb Time to roll a log file (1 day = 3600 * 24 seconds) */ private static final long TIME_TO_ROLL_LOG_FILE = 86_400L; @@ -95,11 +104,13 @@ public abstract class RocksDBColumnarKeyValueStorage implements SegmentedKeyValu private final MetricsSystem metricsSystem; private final RocksDBMetricsFactory rocksDBMetricsFactory; private final RocksDBConfiguration configuration; + /** RocksDB DB options */ protected DBOptions options; /** RocksDb transactionDB options */ protected TransactionDBOptions txOptions; + /** RocksDb statistics */ protected final Statistics stats = new Statistics(); @@ -108,8 +119,10 @@ public abstract class RocksDBColumnarKeyValueStorage implements SegmentedKeyValu /** Map of the columns handles by name */ protected Map columnHandlesBySegmentIdentifier; + /** Column descriptors */ protected List columnDescriptors; + /** Column handles */ protected List columnHandles; @@ -139,7 +152,6 @@ public RocksDBColumnarKeyValueStorage( this.rocksDBMetricsFactory = rocksDBMetricsFactory; try { - final ColumnFamilyOptions columnFamilyOptions = new ColumnFamilyOptions(); trimmedSegments = new ArrayList<>(defaultSegments); final List existingColumnFamilies = RocksDB.listColumnFamilies(new Options(), configuration.getDatabaseDir().toString()); @@ -151,14 +163,9 @@ public RocksDBColumnarKeyValueStorage( .noneMatch(existed -> Arrays.equals(existed, ignorableSegment.getId()))) .forEach(trimmedSegments::remove); columnDescriptors = - trimmedSegments.stream().map(this::createColumnDescriptor).collect(Collectors.toList()); - columnDescriptors.add( - new ColumnFamilyDescriptor( - DEFAULT_COLUMN.getBytes(StandardCharsets.UTF_8), - columnFamilyOptions - .setTtl(0) - .setCompressionType(CompressionType.LZ4_COMPRESSION) - .setTableFormatConfig(createBlockBasedTableConfig(configuration)))); + trimmedSegments.stream() + .map(segment -> createColumnDescriptor(segment, configuration)) + .collect(Collectors.toList()); setGlobalOptions(configuration, stats); @@ -169,6 +176,80 @@ public RocksDBColumnarKeyValueStorage( } } + /** + * Create a Column Family Descriptor for a given segment It defines basically the different + * options to apply to the corresponding Column Family + * + * @param segment the segment identifier + * @param configuration RocksDB configuration + * @return a column family descriptor + */ + private ColumnFamilyDescriptor createColumnDescriptor( + final SegmentIdentifier segment, final RocksDBConfiguration configuration) { + + BlockBasedTableConfig basedTableConfig = createBlockBasedTableConfig(segment, configuration); + + final var options = + new ColumnFamilyOptions() + .setTtl(0) + .setCompressionType(CompressionType.LZ4_COMPRESSION) + .setTableFormatConfig(basedTableConfig); + + if (segment.containsStaticData()) { + options + .setEnableBlobFiles(true) + .setEnableBlobGarbageCollection(segment.isStaticDataGarbageCollectionEnabled()) + .setMinBlobSize(100) + .setBlobCompressionType(CompressionType.LZ4_COMPRESSION); + } + + return new ColumnFamilyDescriptor(segment.getId(), options); + } + + /*** + * Create a Block Base Table configuration for each segment, depending on the configuration in place + * and the segment itself + * + * @param segment The segment related to the column family + * @param config RocksDB configuration + * @return Block Base Table configuration + */ + private BlockBasedTableConfig createBlockBasedTableConfig( + final SegmentIdentifier segment, final RocksDBConfiguration config) { + final LRUCache cache = + new LRUCache( + config.isHighSpec() && segment.isEligibleToHighSpecFlag() + ? ROCKSDB_BLOCKCACHE_SIZE_HIGH_SPEC + : config.getCacheCapacity()); + return new BlockBasedTableConfig() + .setFormatVersion(ROCKSDB_FORMAT_VERSION) + .setBlockCache(cache) + .setFilterPolicy(new BloomFilter(10, false)) + .setPartitionFilters(true) + .setCacheIndexAndFilterBlocks(false) + .setBlockSize(ROCKSDB_BLOCK_SIZE); + } + + /*** + * Set Global options (DBOptions) + * + * @param configuration RocksDB configuration + * @param stats The statistics object + */ + private void setGlobalOptions(final RocksDBConfiguration configuration, final Statistics stats) { + options = new DBOptions(); + options + .setCreateIfMissing(true) + .setMaxOpenFiles(configuration.getMaxOpenFiles()) + .setStatistics(stats) + .setCreateMissingColumnFamilies(true) + .setLogFileTimeToRoll(TIME_TO_ROLL_LOG_FILE) + .setKeepLogFileNum(NUMBER_OF_LOG_FILES_TO_KEEP) + .setEnv(Env.getDefault().setBackgroundThreads(configuration.getBackgroundThreadCount())) + .setMaxTotalWalSize(WAL_MAX_TOTAL_SIZE) + .setRecycleLogFileNum(WAL_MAX_TOTAL_SIZE / EXPECTED_WAL_FILE_SIZE); + } + /** * Parse RocksDBException and wrap in StorageException * @@ -214,40 +295,6 @@ protected static StorageException parseRocksDBException( } } - private ColumnFamilyDescriptor createColumnDescriptor(final SegmentIdentifier segment) { - final var options = - new ColumnFamilyOptions() - .setTtl(0) - .setCompressionType(CompressionType.LZ4_COMPRESSION) - .setTableFormatConfig(createBlockBasedTableConfig(configuration)); - - if (segment.containsStaticData()) { - options - .setEnableBlobFiles(true) - .setEnableBlobGarbageCollection(false) - .setMinBlobSize(100) - .setBlobCompressionType(CompressionType.LZ4_COMPRESSION); - } - - return new ColumnFamilyDescriptor(segment.getId(), options); - } - - private void setGlobalOptions(final RocksDBConfiguration configuration, final Statistics stats) { - options = new DBOptions(); - options - .setCreateIfMissing(true) - .setMaxOpenFiles(configuration.getMaxOpenFiles()) - .setStatistics(stats) - .setCreateMissingColumnFamilies(true) - .setLogFileTimeToRoll(TIME_TO_ROLL_LOG_FILE) - .setKeepLogFileNum(NUMBER_OF_LOG_FILES_TO_KEEP) - .setEnv(Env.getDefault().setBackgroundThreads(configuration.getBackgroundThreadCount())); - - if (configuration.isHighSpec()) { - options.setDbWriteBufferSize(ROCKSDB_MEMTABLE_SIZE_HIGH_SPEC); - } - } - void initMetrics() { metrics = rocksDBMetricsFactory.create(metricsSystem, configuration, getDB(), stats); } @@ -280,19 +327,6 @@ void initColumnHandles() throws RocksDBException { })); } - BlockBasedTableConfig createBlockBasedTableConfig(final RocksDBConfiguration config) { - final LRUCache cache = - new LRUCache( - config.isHighSpec() ? ROCKSDB_BLOCKCACHE_SIZE_HIGH_SPEC : config.getCacheCapacity()); - return new BlockBasedTableConfig() - .setFormatVersion(ROCKSDB_FORMAT_VERSION) - .setBlockCache(cache) - .setFilterPolicy(new BloomFilter(10, false)) - .setPartitionFilters(true) - .setCacheIndexAndFilterBlocks(false) - .setBlockSize(ROCKSDB_BLOCK_SIZE); - } - /** * Safe method to map segment identifier to column handle. * @@ -320,7 +354,7 @@ public Optional get(final SegmentIdentifier segment, final byte[] key) } @Override - public Optional getNearestTo( + public Optional getNearestBefore( final SegmentIdentifier segmentIdentifier, final Bytes key) throws StorageException { try (final RocksIterator rocksIterator = @@ -332,6 +366,19 @@ public Optional getNearestTo( } } + @Override + public Optional getNearestAfter( + final SegmentIdentifier segmentIdentifier, final Bytes key) throws StorageException { + + try (final RocksIterator rocksIterator = + getDB().newIterator(safeColumnHandle(segmentIdentifier))) { + rocksIterator.seek(key.toArrayUnsafe()); + return Optional.of(rocksIterator) + .filter(AbstractRocksIterator::isValid) + .map(it -> new NearestKeyValue(Bytes.of(it.key()), Optional.of(it.value()))); + } + } + @Override public Stream> stream(final SegmentIdentifier segmentIdentifier) { final RocksIterator rocksIterator = getDB().newIterator(safeColumnHandle(segmentIdentifier)); diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBSnapshot.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBSnapshot.java index 7fc2d048cf8..a70fb0b14b8 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBSnapshot.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBSnapshot.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.plugin.services.storage.rocksdb.segmented; diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBSnapshotTransaction.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBSnapshotTransaction.java index ad3438d381b..e2366d0348b 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBSnapshotTransaction.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBSnapshotTransaction.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,7 +11,6 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.plugin.services.storage.rocksdb.segmented; @@ -179,6 +178,7 @@ public Stream streamKeys(final SegmentIdentifier segmentId) { rocksIterator.seekToFirst(); return RocksDbIterator.create(rocksIterator).toStreamKeys(); } + /** * Returns a stream of key-value pairs starting from the specified key. This method is used to * retrieve a stream of data reading through the transaction, starting from the given key. If no diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/TransactionDBRocksDBColumnarKeyValueStorage.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/TransactionDBRocksDBColumnarKeyValueStorage.java index 4825154561a..60879b658e6 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/TransactionDBRocksDBColumnarKeyValueStorage.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/TransactionDBRocksDBColumnarKeyValueStorage.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/helper/Conditions.java b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/helper/Conditions.java index 085e9313536..41f1b294af4 100644 --- a/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/helper/Conditions.java +++ b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/helper/Conditions.java @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.plugin.services.helper; import java.io.IOException; diff --git a/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/NearestKeyValueStorageTest.java b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/NearestKeyValueStorageTest.java new file mode 100644 index 00000000000..54cd03215fc --- /dev/null +++ b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/NearestKeyValueStorageTest.java @@ -0,0 +1,336 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.services.storage.rocksdb; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier.TRIE_BRANCH_STORAGE; +import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_BACKGROUND_THREAD_COUNT; +import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_CACHE_CAPACITY; +import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_IS_HIGH_SPEC; +import static org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBCLIOptions.DEFAULT_MAX_OPEN_FILES; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.ethereum.storage.keyvalue.KeyValueSegmentIdentifier; +import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; +import org.hyperledger.besu.plugin.services.BesuConfiguration; +import org.hyperledger.besu.plugin.services.storage.DataStorageConfiguration; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; +import org.hyperledger.besu.plugin.services.storage.SegmentIdentifier; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage.NearestKeyValue; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; +import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBFactoryConfiguration; +import org.hyperledger.besu.services.kvstore.LayeredKeyValueStorage; +import org.hyperledger.besu.services.kvstore.SegmentedInMemoryKeyValueStorage; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.IntStream; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class NearestKeyValueStorageTest { + + @TempDir private static Path tempDir; + + private static RocksDBKeyValueStorageFactory rocksdbStorageFactory; + private static BesuConfiguration commonConfiguration; + + @BeforeAll + public static void setup() throws IOException { + rocksdbStorageFactory = + new RocksDBKeyValueStorageFactory( + () -> + new RocksDBFactoryConfiguration( + DEFAULT_MAX_OPEN_FILES, + DEFAULT_BACKGROUND_THREAD_COUNT, + DEFAULT_CACHE_CAPACITY, + DEFAULT_IS_HIGH_SPEC), + Arrays.asList(KeyValueSegmentIdentifier.values()), + RocksDBMetricsFactory.PUBLIC_ROCKS_DB_METRICS); + + Utils.createDatabaseMetadataV2(tempDir, DataStorageFormat.BONSAI, 2); + + mockCommonConfiguration(tempDir); + } + + @Test + public void testNearestRocksdbWithInMemoryKeyValueStorage() { + final SegmentedKeyValueStorage rockdDBKeyValueStorage = + getRocksDBKeyValueStorage(TRIE_BRANCH_STORAGE); + final SegmentedKeyValueStorageTransaction rocksDbTransaction = + rockdDBKeyValueStorage.startTransaction(); + + final SegmentedKeyValueStorage inMemoryDBKeyValueStorage = getInMemoryDBKeyValueStorage(); + final SegmentedKeyValueStorageTransaction inMemoryDBTransaction = + inMemoryDBKeyValueStorage.startTransaction(); + IntStream.range(1, 10) + .forEach( + i -> { + final byte[] key = Bytes.fromHexString("0x000" + i).toArrayUnsafe(); + final byte[] value = Bytes.fromHexString("0FFF").toArrayUnsafe(); + rocksDbTransaction.put(TRIE_BRANCH_STORAGE, key, value); + inMemoryDBTransaction.put(TRIE_BRANCH_STORAGE, key, value); + // different common prefix, and reversed order of bytes: + final byte[] key2 = Bytes.fromHexString("0x010" + (10 - i)).toArrayUnsafe(); + final byte[] value2 = Bytes.fromHexString("0FFF").toArrayUnsafe(); + rocksDbTransaction.put(TRIE_BRANCH_STORAGE, key2, value2); + inMemoryDBTransaction.put(TRIE_BRANCH_STORAGE, key2, value2); + // different size: + final byte[] key3 = Bytes.fromHexString("0x01011" + (10 - i)).toArrayUnsafe(); + final byte[] value3 = Bytes.fromHexString("0FFF").toArrayUnsafe(); + rocksDbTransaction.put(TRIE_BRANCH_STORAGE, key3, value3); + inMemoryDBTransaction.put(TRIE_BRANCH_STORAGE, key3, value3); + final byte[] key4 = Bytes.fromHexString("0x0" + (10 - i)).toArrayUnsafe(); + final byte[] value4 = Bytes.fromHexString("0FFF").toArrayUnsafe(); + rocksDbTransaction.put(TRIE_BRANCH_STORAGE, key4, value4); + inMemoryDBTransaction.put(TRIE_BRANCH_STORAGE, key4, value4); + }); + rocksDbTransaction.commit(); + inMemoryDBTransaction.commit(); + + // compare rocksdb implementation with inmemory implementation + rockdDBKeyValueStorage.stream(TRIE_BRANCH_STORAGE) + .forEach( + pair -> { + final Bytes key = Bytes.of(pair.getKey()); + assertThat( + isNearestKeyValueTheSame( + inMemoryDBKeyValueStorage.getNearestBefore(TRIE_BRANCH_STORAGE, key), + rockdDBKeyValueStorage.getNearestBefore(TRIE_BRANCH_STORAGE, key))) + .isTrue(); + assertThat( + isNearestKeyValueTheSame( + inMemoryDBKeyValueStorage.getNearestAfter(TRIE_BRANCH_STORAGE, key), + rockdDBKeyValueStorage.getNearestAfter(TRIE_BRANCH_STORAGE, key))) + .isTrue(); + + final Bytes biggerKey = Bytes.concatenate(key, Bytes.of(0x01)); + assertThat( + isNearestKeyValueTheSame( + inMemoryDBKeyValueStorage.getNearestBefore( + TRIE_BRANCH_STORAGE, biggerKey), + rockdDBKeyValueStorage.getNearestBefore(TRIE_BRANCH_STORAGE, biggerKey))) + .isTrue(); + assertThat( + isNearestKeyValueTheSame( + inMemoryDBKeyValueStorage.getNearestAfter(TRIE_BRANCH_STORAGE, biggerKey), + rockdDBKeyValueStorage.getNearestAfter(TRIE_BRANCH_STORAGE, biggerKey))) + .isTrue(); + + final Bytes smallerKey = key.slice(0, key.size() - 1); + assertThat( + isNearestKeyValueTheSame( + inMemoryDBKeyValueStorage.getNearestBefore( + TRIE_BRANCH_STORAGE, smallerKey), + rockdDBKeyValueStorage.getNearestBefore(TRIE_BRANCH_STORAGE, smallerKey))) + .isTrue(); + assertThat( + isNearestKeyValueTheSame( + inMemoryDBKeyValueStorage.getNearestAfter( + TRIE_BRANCH_STORAGE, smallerKey), + rockdDBKeyValueStorage.getNearestAfter(TRIE_BRANCH_STORAGE, smallerKey))) + .isTrue(); + + final Bytes reversedKey = key.reverse(); + assertThat( + isNearestKeyValueTheSame( + inMemoryDBKeyValueStorage.getNearestBefore( + TRIE_BRANCH_STORAGE, reversedKey), + rockdDBKeyValueStorage.getNearestBefore( + TRIE_BRANCH_STORAGE, reversedKey))) + .isTrue(); + assertThat( + isNearestKeyValueTheSame( + inMemoryDBKeyValueStorage.getNearestAfter( + TRIE_BRANCH_STORAGE, reversedKey), + rockdDBKeyValueStorage.getNearestAfter(TRIE_BRANCH_STORAGE, reversedKey))) + .isTrue(); + }); + } + + @Test + public void testNearestRocksdbWithLayeredKeyValueStorage() { + final SegmentedKeyValueStorage rockdDBKeyValueStorage = + getRocksDBKeyValueStorage(TRIE_BRANCH_STORAGE); + final SegmentedKeyValueStorageTransaction rocksDbTransaction = + rockdDBKeyValueStorage.startTransaction(); + + final SegmentedKeyValueStorage inMemoryDBKeyValueStorage = getInMemoryDBKeyValueStorage(); + final SegmentedKeyValueStorageTransaction inMemoryDBTransaction = + inMemoryDBKeyValueStorage.startTransaction(); + + final LayeredKeyValueStorage layeredDBKeyValueStorage = + new LayeredKeyValueStorage(inMemoryDBKeyValueStorage); + final SegmentedKeyValueStorageTransaction layeredDBTransaction = + layeredDBKeyValueStorage.startTransaction(); + + IntStream.range(1, 10) + .forEach( + i -> { + final byte[] key = Bytes.fromHexString("0x000" + i).toArrayUnsafe(); + final byte[] value = Bytes.fromHexString("0FFF").toArrayUnsafe(); + rocksDbTransaction.put(TRIE_BRANCH_STORAGE, key, value); + // as we have several layers I store sometimes in the child layer and sometimes in the + // parent + if (i % 2 == 0) { + inMemoryDBTransaction.put(TRIE_BRANCH_STORAGE, key, value); + } else { + layeredDBTransaction.put(TRIE_BRANCH_STORAGE, key, value); + } + // different common prefix, and reversed order of bytes: + final byte[] key2 = Bytes.fromHexString("0x010" + (10 - i)).toArrayUnsafe(); + final byte[] value2 = Bytes.fromHexString("0FFF").toArrayUnsafe(); + rocksDbTransaction.put(TRIE_BRANCH_STORAGE, key2, value2); + // as we have several layers I store sometimes in the child layer and sometimes in the + // parent + if (i % 2 == 0) { + inMemoryDBTransaction.put(TRIE_BRANCH_STORAGE, key2, value2); + } else { + layeredDBTransaction.put(TRIE_BRANCH_STORAGE, key2, value2); + } + // different size: + final byte[] key3 = Bytes.fromHexString("0x01011" + (10 - i)).toArrayUnsafe(); + final byte[] value3 = Bytes.fromHexString("0FFF").toArrayUnsafe(); + rocksDbTransaction.put(TRIE_BRANCH_STORAGE, key3, value3); + // as we have several layers I store sometimes in the child layer and sometimes in the + // parent + if (i % 2 == 0) { + inMemoryDBTransaction.put(TRIE_BRANCH_STORAGE, key3, value3); + } else { + layeredDBTransaction.put(TRIE_BRANCH_STORAGE, key3, value3); + } + final byte[] key4 = Bytes.fromHexString("0x0" + (10 - i)).toArrayUnsafe(); + final byte[] value4 = Bytes.fromHexString("0FFF").toArrayUnsafe(); + rocksDbTransaction.put(TRIE_BRANCH_STORAGE, key4, value4); + // as we have several layers I store sometimes in the child layer and sometimes in the + // parent + if (i % 2 == 0) { + inMemoryDBTransaction.put(TRIE_BRANCH_STORAGE, key4, value4); + } else { + layeredDBTransaction.put(TRIE_BRANCH_STORAGE, key4, value4); + } + }); + rocksDbTransaction.commit(); + inMemoryDBTransaction.commit(); + layeredDBTransaction.commit(); + + // compare rocksdb implementation with inmemory implementation + rockdDBKeyValueStorage.stream(TRIE_BRANCH_STORAGE) + .forEach( + pair -> { + final Bytes key = Bytes.of(pair.getKey()); + assertThat( + isNearestKeyValueTheSame( + layeredDBKeyValueStorage.getNearestBefore(TRIE_BRANCH_STORAGE, key), + rockdDBKeyValueStorage.getNearestBefore(TRIE_BRANCH_STORAGE, key))) + .isTrue(); + assertThat( + isNearestKeyValueTheSame( + layeredDBKeyValueStorage.getNearestAfter(TRIE_BRANCH_STORAGE, key), + rockdDBKeyValueStorage.getNearestAfter(TRIE_BRANCH_STORAGE, key))) + .isTrue(); + + final Bytes biggerKey = Bytes.concatenate(key, Bytes.of(0x01)); + assertThat( + isNearestKeyValueTheSame( + layeredDBKeyValueStorage.getNearestBefore(TRIE_BRANCH_STORAGE, biggerKey), + rockdDBKeyValueStorage.getNearestBefore(TRIE_BRANCH_STORAGE, biggerKey))) + .isTrue(); + assertThat( + isNearestKeyValueTheSame( + layeredDBKeyValueStorage.getNearestAfter(TRIE_BRANCH_STORAGE, biggerKey), + rockdDBKeyValueStorage.getNearestAfter(TRIE_BRANCH_STORAGE, biggerKey))) + .isTrue(); + + final Bytes smallerKey = key.slice(0, key.size() - 1); + assertThat( + isNearestKeyValueTheSame( + layeredDBKeyValueStorage.getNearestBefore( + TRIE_BRANCH_STORAGE, smallerKey), + rockdDBKeyValueStorage.getNearestBefore(TRIE_BRANCH_STORAGE, smallerKey))) + .isTrue(); + assertThat( + isNearestKeyValueTheSame( + layeredDBKeyValueStorage.getNearestAfter(TRIE_BRANCH_STORAGE, smallerKey), + rockdDBKeyValueStorage.getNearestAfter(TRIE_BRANCH_STORAGE, smallerKey))) + .isTrue(); + + final Bytes reversedKey = key.reverse(); + assertThat( + isNearestKeyValueTheSame( + layeredDBKeyValueStorage.getNearestBefore( + TRIE_BRANCH_STORAGE, reversedKey), + rockdDBKeyValueStorage.getNearestBefore( + TRIE_BRANCH_STORAGE, reversedKey))) + .isTrue(); + assertThat( + isNearestKeyValueTheSame( + layeredDBKeyValueStorage.getNearestAfter( + TRIE_BRANCH_STORAGE, reversedKey), + rockdDBKeyValueStorage.getNearestAfter(TRIE_BRANCH_STORAGE, reversedKey))) + .isTrue(); + }); + } + + private SegmentedKeyValueStorage getRocksDBKeyValueStorage(final SegmentIdentifier segment) { + return rocksdbStorageFactory.create( + List.of(segment), commonConfiguration, new NoOpMetricsSystem()); + } + + private SegmentedKeyValueStorage getInMemoryDBKeyValueStorage() { + return new SegmentedInMemoryKeyValueStorage(); + } + + private static void mockCommonConfiguration(final Path tempDataDir) { + commonConfiguration = mock(BesuConfiguration.class); + when(commonConfiguration.getStoragePath()).thenReturn(tempDataDir); + when(commonConfiguration.getDataPath()).thenReturn(tempDataDir); + DataStorageConfiguration dataStorageConfiguration = mock(DataStorageConfiguration.class); + when(dataStorageConfiguration.getDatabaseFormat()).thenReturn(DataStorageFormat.BONSAI); + lenient() + .when(commonConfiguration.getDataStorageConfiguration()) + .thenReturn(dataStorageConfiguration); + } + + private boolean isNearestKeyValueTheSame( + final Optional v1, final Optional v2) { + if (v1.isPresent() && v2.isPresent()) { + final NearestKeyValue nearestKeyValue1 = v1.get(); + final NearestKeyValue nearestKeyValue2 = v2.get(); + if (nearestKeyValue1.key().equals(nearestKeyValue2.key())) { + if (nearestKeyValue1.value().isPresent() && nearestKeyValue2.value().isPresent()) { + return Arrays.equals(nearestKeyValue1.value().get(), nearestKeyValue2.value().get()); + } + } else if (nearestKeyValue1.value().isEmpty() && nearestKeyValue2.value().isEmpty()) { + return true; + } + } else if (v1.isEmpty() && v2.isEmpty()) { + return true; + } + return false; + } +} diff --git a/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBKeyValuePrivacyStorageFactoryTest.java b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBKeyValuePrivacyStorageFactoryTest.java index e0ed3f7b396..17a3fb874a4 100644 --- a/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBKeyValuePrivacyStorageFactoryTest.java +++ b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBKeyValuePrivacyStorageFactoryTest.java @@ -15,14 +15,21 @@ package org.hyperledger.besu.plugin.services.storage.rocksdb; import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.plugin.services.storage.DataStorageFormat.BONSAI; +import static org.hyperledger.besu.plugin.services.storage.DataStorageFormat.FOREST; import static org.hyperledger.besu.plugin.services.storage.rocksdb.segmented.RocksDBColumnarKeyValueStorageTest.TestSegment; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; import org.hyperledger.besu.metrics.ObservableMetricsSystem; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.BesuConfiguration; +import org.hyperledger.besu.plugin.services.storage.DataStorageConfiguration; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.plugin.services.storage.SegmentIdentifier; +import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.BaseVersionedStorageFormat; import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.DatabaseMetadata; +import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.PrivacyVersionedStorageFormat; import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBFactoryConfiguration; import java.nio.file.Files; @@ -32,30 +39,32 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) public class RocksDBKeyValuePrivacyStorageFactoryTest { - private static final int DEFAULT_VERSION = 1; - private static final int DEFAULT_PRIVACY_VERSION = 1; - @Mock private RocksDBFactoryConfiguration rocksDbConfiguration; @Mock private BesuConfiguration commonConfiguration; + @Mock private DataStorageConfiguration dataStorageConfiguration; @TempDir private Path temporaryFolder; private final ObservableMetricsSystem metricsSystem = new NoOpMetricsSystem(); private final SegmentIdentifier segment = TestSegment.BAR; - private final List segments = List.of(segment); + private final List segments = List.of(TestSegment.DEFAULT, segment); @Test - public void shouldDetectVersion1DatabaseIfNoMetadataFileFound() throws Exception { + public void shouldDetectVersion1MetadataIfPresent() throws Exception { final Path tempDataDir = temporaryFolder.resolve("data"); final Path tempDatabaseDir = temporaryFolder.resolve("db"); final Path tempPrivateDatabaseDir = tempDatabaseDir.resolve("private"); Files.createDirectories(tempPrivateDatabaseDir); Files.createDirectories(tempDataDir); - when(commonConfiguration.getStoragePath()).thenReturn(tempDatabaseDir); - when(commonConfiguration.getDataPath()).thenReturn(tempDataDir); + mockCommonConfiguration(tempDataDir, tempDatabaseDir); + + Utils.createDatabaseMetadataV1Privacy( + tempDataDir, PrivacyVersionedStorageFormat.FOREST_ORIGINAL); final RocksDBKeyValuePrivacyStorageFactory storageFactory = new RocksDBKeyValuePrivacyStorageFactory( @@ -65,23 +74,18 @@ public void shouldDetectVersion1DatabaseIfNoMetadataFileFound() throws Exception RocksDBMetricsFactory.PRIVATE_ROCKS_DB_METRICS)); // Side effect is creation of the Metadata version file - storageFactory.create(segment, commonConfiguration, metricsSystem); + try (final var storage = storageFactory.create(segment, commonConfiguration, metricsSystem)) { - assertThat(DatabaseMetadata.lookUpFrom(tempDataDir).maybePrivacyVersion()).isNotEmpty(); - - assertThat(DatabaseMetadata.lookUpFrom(tempDataDir).getVersion()).isEqualTo(DEFAULT_VERSION); - - assertThat(DatabaseMetadata.lookUpFrom(tempDataDir).maybePrivacyVersion().get()) - .isEqualTo(DEFAULT_VERSION); + assertThat(DatabaseMetadata.lookUpFrom(tempDataDir).getVersionedStorageFormat()) + .isEqualTo(PrivacyVersionedStorageFormat.FOREST_WITH_RECEIPT_COMPACTION); + } } @Test public void shouldCreateCorrectMetadataFileForLatestVersion() throws Exception { final Path tempDataDir = temporaryFolder.resolve("data"); final Path tempDatabaseDir = temporaryFolder.resolve("db"); - when(commonConfiguration.getStoragePath()).thenReturn(tempDatabaseDir); - when(commonConfiguration.getDataPath()).thenReturn(tempDataDir); - when(commonConfiguration.getDatabaseVersion()).thenReturn(DEFAULT_VERSION); + mockCommonConfiguration(tempDataDir, tempDatabaseDir); final RocksDBKeyValuePrivacyStorageFactory storageFactory = new RocksDBKeyValuePrivacyStorageFactory( @@ -91,42 +95,99 @@ public void shouldCreateCorrectMetadataFileForLatestVersion() throws Exception { RocksDBMetricsFactory.PRIVATE_ROCKS_DB_METRICS)); // Side effect is creation of the Metadata version file - storageFactory.create(segment, commonConfiguration, metricsSystem); + try (final var storage = storageFactory.create(segment, commonConfiguration, metricsSystem)) { + assertThat(DatabaseMetadata.lookUpFrom(tempDataDir).getVersionedStorageFormat()) + .isEqualTo(PrivacyVersionedStorageFormat.FOREST_WITH_RECEIPT_COMPACTION); + } + } - assertThat(DatabaseMetadata.lookUpFrom(tempDataDir).maybePrivacyVersion()).isNotEmpty(); + @ParameterizedTest + @EnumSource(DataStorageFormat.class) + public void shouldUpdateCorrectMetadataFileForLatestVersion( + final DataStorageFormat dataStorageFormat) throws Exception { + final Path tempDataDir = temporaryFolder.resolve("data"); + final Path tempDatabaseDir = temporaryFolder.resolve("db"); + mockCommonConfiguration(tempDataDir, tempDatabaseDir, dataStorageFormat); - assertThat(DatabaseMetadata.lookUpFrom(tempDataDir).getVersion()).isEqualTo(DEFAULT_VERSION); + final RocksDBKeyValueStorageFactory storageFactory = + new RocksDBKeyValueStorageFactory( + () -> rocksDbConfiguration, segments, RocksDBMetricsFactory.PRIVATE_ROCKS_DB_METRICS); - assertThat(DatabaseMetadata.lookUpFrom(tempDataDir).maybePrivacyVersion().get()) - .isEqualTo(DEFAULT_PRIVACY_VERSION); + try (final var storage = storageFactory.create(segment, commonConfiguration, metricsSystem)) { + final BaseVersionedStorageFormat expectedBaseVersion = + dataStorageFormat == BONSAI + ? BaseVersionedStorageFormat.BONSAI_WITH_RECEIPT_COMPACTION + : BaseVersionedStorageFormat.FOREST_WITH_RECEIPT_COMPACTION; + assertThat(DatabaseMetadata.lookUpFrom(tempDataDir).getVersionedStorageFormat()) + .isEqualTo(expectedBaseVersion); + } + storageFactory.close(); + + final RocksDBKeyValuePrivacyStorageFactory privacyStorageFactory = + new RocksDBKeyValuePrivacyStorageFactory(storageFactory); + + try (final var storage = + privacyStorageFactory.create(segment, commonConfiguration, metricsSystem)) { + final PrivacyVersionedStorageFormat expectedPrivacyVersion = + dataStorageFormat == BONSAI + ? PrivacyVersionedStorageFormat.BONSAI_WITH_RECEIPT_COMPACTION + : PrivacyVersionedStorageFormat.FOREST_WITH_RECEIPT_COMPACTION; + assertThat(DatabaseMetadata.lookUpFrom(tempDataDir).getVersionedStorageFormat()) + .isEqualTo(expectedPrivacyVersion); + } + privacyStorageFactory.close(); } - @Test - public void shouldUpdateCorrectMetadataFileForLatestVersion() throws Exception { + @ParameterizedTest + @EnumSource(DataStorageFormat.class) + public void shouldUpdateCorrectMetadataFileForLatestVersionWithReceiptCompaction( + final DataStorageFormat dataStorageFormat) throws Exception { final Path tempDataDir = temporaryFolder.resolve("data"); final Path tempDatabaseDir = temporaryFolder.resolve("db"); - when(commonConfiguration.getStoragePath()).thenReturn(tempDatabaseDir); - when(commonConfiguration.getDataPath()).thenReturn(tempDataDir); - when(commonConfiguration.getDatabaseVersion()).thenReturn(DEFAULT_VERSION); + mockCommonConfiguration(tempDataDir, tempDatabaseDir, dataStorageFormat); final RocksDBKeyValueStorageFactory storageFactory = new RocksDBKeyValueStorageFactory( () -> rocksDbConfiguration, segments, RocksDBMetricsFactory.PRIVATE_ROCKS_DB_METRICS); - storageFactory.create(segment, commonConfiguration, metricsSystem); - - assertThat(DatabaseMetadata.lookUpFrom(tempDataDir).maybePrivacyVersion()).isEmpty(); - - assertThat(DatabaseMetadata.lookUpFrom(tempDataDir).getVersion()).isEqualTo(DEFAULT_VERSION); + try (final var storage = storageFactory.create(segment, commonConfiguration, metricsSystem)) { + final BaseVersionedStorageFormat expectedBaseVersion = + dataStorageFormat == BONSAI + ? BaseVersionedStorageFormat.BONSAI_WITH_RECEIPT_COMPACTION + : BaseVersionedStorageFormat.FOREST_WITH_RECEIPT_COMPACTION; + assertThat(DatabaseMetadata.lookUpFrom(tempDataDir).getVersionedStorageFormat()) + .isEqualTo(expectedBaseVersion); + } + storageFactory.close(); final RocksDBKeyValuePrivacyStorageFactory privacyStorageFactory = new RocksDBKeyValuePrivacyStorageFactory(storageFactory); - privacyStorageFactory.create(segment, commonConfiguration, metricsSystem); + try (final var storage = + privacyStorageFactory.create(segment, commonConfiguration, metricsSystem)) { + final PrivacyVersionedStorageFormat expectedPrivacyVersion = + dataStorageFormat == BONSAI + ? PrivacyVersionedStorageFormat.BONSAI_WITH_RECEIPT_COMPACTION + : PrivacyVersionedStorageFormat.FOREST_WITH_RECEIPT_COMPACTION; + assertThat(DatabaseMetadata.lookUpFrom(tempDataDir).getVersionedStorageFormat()) + .isEqualTo(expectedPrivacyVersion); + } + privacyStorageFactory.close(); + } - assertThat(DatabaseMetadata.lookUpFrom(tempDataDir).maybePrivacyVersion()).isNotEmpty(); + private void mockCommonConfiguration(final Path tempDataDir, final Path tempDatabaseDir) { + mockCommonConfiguration(tempDataDir, tempDatabaseDir, FOREST); + } - assertThat(DatabaseMetadata.lookUpFrom(tempDataDir).maybePrivacyVersion().get()) - .isEqualTo(DEFAULT_PRIVACY_VERSION); + private void mockCommonConfiguration( + final Path tempDataDir, + final Path tempDatabaseDir, + final DataStorageFormat dataStorageFormat) { + when(commonConfiguration.getStoragePath()).thenReturn(tempDatabaseDir); + when(commonConfiguration.getDataPath()).thenReturn(tempDataDir); + when(dataStorageConfiguration.getDatabaseFormat()).thenReturn(dataStorageFormat); + lenient() + .when(commonConfiguration.getDataStorageConfiguration()) + .thenReturn(dataStorageConfiguration); } } diff --git a/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBKeyValueStorageFactoryTest.java b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBKeyValueStorageFactoryTest.java index c3a8c32eb27..7148f601cbd 100644 --- a/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBKeyValueStorageFactoryTest.java +++ b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBKeyValueStorageFactoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -17,116 +17,162 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.fail; +import static org.hyperledger.besu.plugin.services.storage.DataStorageFormat.BONSAI; +import static org.hyperledger.besu.plugin.services.storage.DataStorageFormat.FOREST; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; import org.hyperledger.besu.metrics.ObservableMetricsSystem; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.BesuConfiguration; import org.hyperledger.besu.plugin.services.exception.StorageException; +import org.hyperledger.besu.plugin.services.storage.DataStorageConfiguration; +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; import org.hyperledger.besu.plugin.services.storage.SegmentIdentifier; +import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.BaseVersionedStorageFormat; import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.DatabaseMetadata; import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBFactoryConfiguration; import org.hyperledger.besu.plugin.services.storage.rocksdb.segmented.RocksDBColumnarKeyValueStorageTest.TestSegment; -import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) public class RocksDBKeyValueStorageFactoryTest { - private static final String METADATA_FILENAME = "DATABASE_METADATA.json"; - private static final int DEFAULT_VERSION = 1; - @Mock private RocksDBFactoryConfiguration rocksDbConfiguration; @Mock private BesuConfiguration commonConfiguration; + @Mock private DataStorageConfiguration dataStorageConfiguration; @TempDir public Path temporaryFolder; private final ObservableMetricsSystem metricsSystem = new NoOpMetricsSystem(); private final SegmentIdentifier segment = TestSegment.FOO; - private final List segments = List.of(segment); + private final List segments = List.of(TestSegment.DEFAULT, segment); - @Test - public void shouldCreateCorrectMetadataFileForLatestVersion() throws Exception { + @ParameterizedTest + @EnumSource(DataStorageFormat.class) + public void shouldCreateCorrectMetadataFileForLatestVersionForNewDb( + final DataStorageFormat dataStorageFormat) throws Exception { final Path tempDataDir = temporaryFolder.resolve("data"); final Path tempDatabaseDir = temporaryFolder.resolve("db"); - when(commonConfiguration.getStoragePath()).thenReturn(tempDatabaseDir); - when(commonConfiguration.getDataPath()).thenReturn(tempDataDir); - when(commonConfiguration.getDatabaseVersion()).thenReturn(DEFAULT_VERSION); + mockCommonConfiguration(tempDataDir, tempDatabaseDir, dataStorageFormat); final RocksDBKeyValueStorageFactory storageFactory = new RocksDBKeyValueStorageFactory( () -> rocksDbConfiguration, segments, RocksDBMetricsFactory.PUBLIC_ROCKS_DB_METRICS); - // Side effect is creation of the Metadata version file - storageFactory.create(segment, commonConfiguration, metricsSystem); + try (final var storage = storageFactory.create(segment, commonConfiguration, metricsSystem)) { + // Side effect is creation of the Metadata version file + final BaseVersionedStorageFormat expectedVersion = + dataStorageFormat == BONSAI + ? BaseVersionedStorageFormat.BONSAI_WITH_RECEIPT_COMPACTION + : BaseVersionedStorageFormat.FOREST_WITH_RECEIPT_COMPACTION; + assertThat(DatabaseMetadata.lookUpFrom(tempDataDir).getVersionedStorageFormat()) + .isEqualTo(expectedVersion); + } + } + + @ParameterizedTest + @EnumSource(DataStorageFormat.class) + public void shouldCreateCorrectMetadataFileForLatestVersionForNewDbWithReceiptCompaction( + final DataStorageFormat dataStorageFormat) throws Exception { + final Path tempDataDir = temporaryFolder.resolve("data"); + final Path tempDatabaseDir = temporaryFolder.resolve("db"); + mockCommonConfiguration(tempDataDir, tempDatabaseDir, dataStorageFormat); + + final RocksDBKeyValueStorageFactory storageFactory = + new RocksDBKeyValueStorageFactory( + () -> rocksDbConfiguration, segments, RocksDBMetricsFactory.PUBLIC_ROCKS_DB_METRICS); - assertThat(DatabaseMetadata.lookUpFrom(tempDataDir).getVersion()).isEqualTo(DEFAULT_VERSION); + try (final var storage = storageFactory.create(segment, commonConfiguration, metricsSystem)) { + // Side effect is creation of the Metadata version file + final BaseVersionedStorageFormat expectedVersion = + dataStorageFormat == BONSAI + ? BaseVersionedStorageFormat.BONSAI_WITH_RECEIPT_COMPACTION + : BaseVersionedStorageFormat.FOREST_WITH_RECEIPT_COMPACTION; + assertThat(DatabaseMetadata.lookUpFrom(tempDataDir).getVersionedStorageFormat()) + .isEqualTo(expectedVersion); + } } @Test - public void shouldDetectVersion1DatabaseIfNoMetadataFileFound() throws Exception { + public void shouldFailIfDbExistsAndNoMetadataFileFound() throws Exception { final Path tempDataDir = temporaryFolder.resolve("data"); final Path tempDatabaseDir = temporaryFolder.resolve("db"); Files.createDirectories(tempDatabaseDir); Files.createDirectories(tempDataDir); - when(commonConfiguration.getStoragePath()).thenReturn(tempDatabaseDir); - when(commonConfiguration.getDataPath()).thenReturn(tempDataDir); + mockCommonConfiguration(tempDataDir, tempDatabaseDir, FOREST); final RocksDBKeyValueStorageFactory storageFactory = new RocksDBKeyValueStorageFactory( () -> rocksDbConfiguration, segments, RocksDBMetricsFactory.PUBLIC_ROCKS_DB_METRICS); - storageFactory.create(segment, commonConfiguration, metricsSystem); - - assertThat(DatabaseMetadata.lookUpFrom(tempDataDir).getVersion()).isEqualTo(DEFAULT_VERSION); + try (final var storage = storageFactory.create(segment, commonConfiguration, metricsSystem)) { + fail("Must fail if db is present but metadata is not"); + } catch (StorageException se) { + assertThat(se) + .hasMessage( + "Database exists but metadata file not found, without it there is no safe way to open the database"); + } } @Test - public void shouldDetectCorrectVersionIfMetadataFileExists() throws Exception { + public void shouldDetectCorrectMetadataV1AndUpgrade() throws Exception { final Path tempDataDir = temporaryFolder.resolve("data"); final Path tempDatabaseDir = temporaryFolder.resolve("db"); Files.createDirectories(tempDataDir); - when(commonConfiguration.getStoragePath()).thenReturn(tempDatabaseDir); - when(commonConfiguration.getDataPath()).thenReturn(tempDataDir); - when(commonConfiguration.getDatabaseVersion()).thenReturn(DEFAULT_VERSION); + mockCommonConfiguration(tempDataDir, tempDatabaseDir, BONSAI); + + Utils.createDatabaseMetadataV1(tempDataDir, BONSAI); final RocksDBKeyValueStorageFactory storageFactory = new RocksDBKeyValueStorageFactory( () -> rocksDbConfiguration, segments, RocksDBMetricsFactory.PUBLIC_ROCKS_DB_METRICS); - storageFactory.create(segment, commonConfiguration, metricsSystem); - - assertThat(DatabaseMetadata.lookUpFrom(tempDataDir).getVersion()).isEqualTo(DEFAULT_VERSION); - assertThat(storageFactory.isSegmentIsolationSupported()).isTrue(); + try (final var storage = storageFactory.create(segment, commonConfiguration, metricsSystem)) { + assertThat(DatabaseMetadata.lookUpFrom(tempDataDir).getVersionedStorageFormat()) + .isEqualTo(BaseVersionedStorageFormat.BONSAI_WITH_RECEIPT_COMPACTION); + assertThat(storageFactory.isSegmentIsolationSupported()).isTrue(); + } } @Test - public void shouldDetectCorrectVersionInCaseOfRollback() throws Exception { + public void shouldFailInCaseOfUnmanagedRollback() throws Exception { final Path tempDataDir = temporaryFolder.resolve("data"); final Path tempDatabaseDir = temporaryFolder.resolve("db"); Files.createDirectories(tempDatabaseDir); Files.createDirectories(tempDataDir); - when(commonConfiguration.getStoragePath()).thenReturn(tempDatabaseDir); - when(commonConfiguration.getDataPath()).thenReturn(tempDataDir); + mockCommonConfiguration(tempDataDir, tempDatabaseDir, BONSAI); + + Utils.createDatabaseMetadataV1(tempDataDir, BONSAI); final RocksDBKeyValueStorageFactory storageFactory = new RocksDBKeyValueStorageFactory( - () -> rocksDbConfiguration, segments, 2, RocksDBMetricsFactory.PUBLIC_ROCKS_DB_METRICS); + () -> rocksDbConfiguration, segments, RocksDBMetricsFactory.PUBLIC_ROCKS_DB_METRICS); storageFactory.create(segment, commonConfiguration, metricsSystem); storageFactory.close(); + Utils.createDatabaseMetadataV2(tempDataDir, BONSAI, 1); + final RocksDBKeyValueStorageFactory rolledbackStorageFactory = new RocksDBKeyValueStorageFactory( - () -> rocksDbConfiguration, segments, 1, RocksDBMetricsFactory.PUBLIC_ROCKS_DB_METRICS); - rolledbackStorageFactory.create(segment, commonConfiguration, metricsSystem); + () -> rocksDbConfiguration, segments, RocksDBMetricsFactory.PUBLIC_ROCKS_DB_METRICS); + assertThatThrownBy( + () -> rolledbackStorageFactory.create(segment, commonConfiguration, metricsSystem)) + .isInstanceOf(StorageException.class) + .hasMessageStartingWith("Database unsafe downgrade detect"); } @Test @@ -135,9 +181,9 @@ public void shouldThrowExceptionWhenVersionNumberIsInvalid() throws Exception { final Path tempDatabaseDir = temporaryFolder.resolve("db"); Files.createDirectories(tempDatabaseDir); Files.createDirectories(tempDataDir); - when(commonConfiguration.getStoragePath()).thenReturn(tempDatabaseDir); - when(commonConfiguration.getDataPath()).thenReturn(tempDataDir); - new DatabaseMetadata(-1).writeToDirectory(tempDataDir); + mockCommonConfiguration(tempDataDir, tempDatabaseDir, FOREST); + + Utils.createDatabaseMetadataV1(tempDataDir, 99); assertThatThrownBy( () -> new RocksDBKeyValueStorageFactory( @@ -145,23 +191,57 @@ public void shouldThrowExceptionWhenVersionNumberIsInvalid() throws Exception { segments, RocksDBMetricsFactory.PUBLIC_ROCKS_DB_METRICS) .create(segment, commonConfiguration, metricsSystem)) - .isInstanceOf(StorageException.class); + .isInstanceOf(StorageException.class) + .hasMessageStartingWith("Unsupported db version"); } @Test - public void shouldSetSegmentationFieldDuringCreation() throws Exception { + public void shouldThrowExceptionWhenExistingDatabaseFormatDiffersFromConfig() throws Exception { + + final DataStorageFormat actualDatabaseFormat = FOREST; + final DataStorageFormat expectedDatabaseFormat = BONSAI; + final Path tempDataDir = temporaryFolder.resolve("data"); final Path tempDatabaseDir = temporaryFolder.resolve("db"); Files.createDirectories(tempDatabaseDir); Files.createDirectories(tempDataDir); - when(commonConfiguration.getStoragePath()).thenReturn(tempDatabaseDir); - when(commonConfiguration.getDataPath()).thenReturn(tempDataDir); + mockCommonConfiguration(tempDataDir, tempDatabaseDir, BONSAI); + + Utils.createDatabaseMetadataV2(tempDataDir, FOREST, 2); + + assertThatThrownBy( + () -> + new RocksDBKeyValueStorageFactory( + () -> rocksDbConfiguration, + segments, + RocksDBMetricsFactory.PUBLIC_ROCKS_DB_METRICS) + .create(segment, commonConfiguration, metricsSystem)) + .isInstanceOf(StorageException.class) + .hasMessage( + "Database format mismatch: DB at %s is %s but config expects %s. " + + "Please check your config.", + tempDataDir.toAbsolutePath(), actualDatabaseFormat, expectedDatabaseFormat); + } + + @Test + public void shouldDetectCorrectMetadataV2AndSetSegmentationFieldDuringCreation() + throws Exception { + final Path tempDataDir = temporaryFolder.resolve("data"); + final Path tempDatabaseDir = temporaryFolder.resolve("db"); + Files.createDirectories(tempDatabaseDir); + Files.createDirectories(tempDataDir); + mockCommonConfiguration(tempDataDir, tempDatabaseDir, FOREST); + + Utils.createDatabaseMetadataV2(tempDataDir, FOREST, 2); final RocksDBKeyValueStorageFactory storageFactory = new RocksDBKeyValueStorageFactory( () -> rocksDbConfiguration, segments, RocksDBMetricsFactory.PUBLIC_ROCKS_DB_METRICS); - storageFactory.create(segment, commonConfiguration, metricsSystem); - assertThatCode(storageFactory::isSegmentIsolationSupported).doesNotThrowAnyException(); + try (final var storage = storageFactory.create(segment, commonConfiguration, metricsSystem)) { + assertThat(DatabaseMetadata.lookUpFrom(tempDataDir).getVersionedStorageFormat()) + .isEqualTo(BaseVersionedStorageFormat.FOREST_WITH_RECEIPT_COMPACTION); + assertThatCode(storageFactory::isSegmentIsolationSupported).doesNotThrowAnyException(); + } } @Test @@ -170,12 +250,9 @@ public void shouldThrowExceptionWhenMetaDataFileIsCorrupted() throws Exception { final Path tempDatabaseDir = temporaryFolder.resolve("db"); Files.createDirectories(tempDatabaseDir); Files.createDirectories(tempDataDir); - when(commonConfiguration.getStoragePath()).thenReturn(tempDatabaseDir); - when(commonConfiguration.getDataPath()).thenReturn(tempDataDir); + mockCommonConfiguration(tempDataDir, tempDatabaseDir, FOREST); - final String badVersion = "{\"🦄\":1}"; - Files.write( - tempDataDir.resolve(METADATA_FILENAME), badVersion.getBytes(Charset.defaultCharset())); + Utils.createDatabaseMetadataRaw(tempDataDir, "{\"🦄\":1}"); assertThatThrownBy( () -> @@ -184,11 +261,11 @@ public void shouldThrowExceptionWhenMetaDataFileIsCorrupted() throws Exception { segments, RocksDBMetricsFactory.PUBLIC_ROCKS_DB_METRICS) .create(segment, commonConfiguration, metricsSystem)) - .isInstanceOf(IllegalStateException.class); + .isInstanceOf(IllegalStateException.class) + .hasMessageStartingWith("Invalid metadata file"); + ; - final String badValue = "{\"version\":\"iomedae\"}"; - Files.write( - tempDatabaseDir.resolve(METADATA_FILENAME), badValue.getBytes(Charset.defaultCharset())); + Utils.createDatabaseMetadataRaw(tempDataDir, "{\"version\"=1}"); assertThatThrownBy( () -> @@ -197,18 +274,21 @@ public void shouldThrowExceptionWhenMetaDataFileIsCorrupted() throws Exception { segments, RocksDBMetricsFactory.PUBLIC_ROCKS_DB_METRICS) .create(segment, commonConfiguration, metricsSystem)) - .isInstanceOf(IllegalStateException.class); + .isInstanceOf(IllegalStateException.class) + .hasMessageStartingWith("Invalid metadata file"); + ; } @Test + @DisabledOnOs(OS.WINDOWS) public void shouldCreateDBCorrectlyIfSymlink() throws Exception { final Path tempRealDataDir = Files.createDirectories(temporaryFolder.resolve("real-data-dir")); final Path tempSymLinkDataDir = Files.createSymbolicLink(temporaryFolder.resolve("symlink-data-dir"), tempRealDataDir); final Path tempDatabaseDir = temporaryFolder.resolve("db"); - when(commonConfiguration.getStoragePath()).thenReturn(tempDatabaseDir); - when(commonConfiguration.getDataPath()).thenReturn(tempSymLinkDataDir); - when(commonConfiguration.getDatabaseVersion()).thenReturn(DEFAULT_VERSION); + mockCommonConfiguration(tempSymLinkDataDir, tempDatabaseDir, FOREST); + + Utils.createDatabaseMetadataV2(tempSymLinkDataDir, FOREST, 2); final RocksDBKeyValueStorageFactory storageFactory = new RocksDBKeyValueStorageFactory( @@ -216,8 +296,19 @@ public void shouldCreateDBCorrectlyIfSymlink() throws Exception { // Ensure that having created everything via a symlink data dir the DB meta-data has been // created correctly - storageFactory.create(segment, commonConfiguration, metricsSystem); - assertThat(DatabaseMetadata.lookUpFrom(tempRealDataDir).getVersion()) - .isEqualTo(DEFAULT_VERSION); + try (final var storage = storageFactory.create(segment, commonConfiguration, metricsSystem)) { + assertThat(DatabaseMetadata.lookUpFrom(tempRealDataDir).getVersionedStorageFormat()) + .isEqualTo(BaseVersionedStorageFormat.FOREST_WITH_RECEIPT_COMPACTION); + } + } + + private void mockCommonConfiguration( + final Path tempDataDir, final Path tempDatabaseDir, final DataStorageFormat format) { + when(commonConfiguration.getStoragePath()).thenReturn(tempDatabaseDir); + when(commonConfiguration.getDataPath()).thenReturn(tempDataDir); + lenient().when(dataStorageConfiguration.getDatabaseFormat()).thenReturn(format); + lenient() + .when(commonConfiguration.getDataStorageConfiguration()) + .thenReturn(dataStorageConfiguration); } } diff --git a/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/Utils.java b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/Utils.java new file mode 100644 index 00000000000..a5d4f1ba913 --- /dev/null +++ b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/Utils.java @@ -0,0 +1,91 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.services.storage.rocksdb; + +import org.hyperledger.besu.plugin.services.storage.DataStorageFormat; +import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.PrivacyVersionedStorageFormat; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; + +public class Utils { + public static final String METADATA_FILENAME = "DATABASE_METADATA.json"; + + public static void createDatabaseMetadataV1( + final Path tempDataDir, final DataStorageFormat dataStorageFormat) throws IOException { + createDatabaseMetadataV1(tempDataDir, dataStorageFormatToV1(dataStorageFormat)); + } + + public static void createDatabaseMetadataV1(final Path tempDataDir, final int version) + throws IOException { + final String content = "{\"version\":" + version + "}"; + Files.write(tempDataDir.resolve(METADATA_FILENAME), content.getBytes(Charset.defaultCharset())); + } + + public static void createDatabaseMetadataV1Privacy( + final Path tempDataDir, final PrivacyVersionedStorageFormat privacyVersionedStorageFormat) + throws IOException { + createDatabaseMetadataV1Privacy( + tempDataDir, + dataStorageFormatToV1(privacyVersionedStorageFormat.getFormat()), + privacyVersionedStorageFormat.getPrivacyVersion().getAsInt()); + } + + public static void createDatabaseMetadataV1Privacy( + final Path tempDataDir, final int version, final int privacyVersion) throws IOException { + final String content = + "{\"version\":" + version + ",\"privacyVersion\":" + privacyVersion + "}"; + Files.write(tempDataDir.resolve(METADATA_FILENAME), content.getBytes(Charset.defaultCharset())); + } + + public static void createDatabaseMetadataV2( + final Path tempDataDir, final DataStorageFormat dataStorageFormat, final int version) + throws IOException { + final String content = + "{\"v2\":{\"format\":\"" + dataStorageFormat + "\",\"version\":" + version + "}}"; + Files.write(tempDataDir.resolve(METADATA_FILENAME), content.getBytes(Charset.defaultCharset())); + } + + public static void createDatabaseMetadataV2Privacy( + final Path tempDataDir, + final DataStorageFormat dataStorageFormat, + final int version, + final int privacyVersion) + throws IOException { + final String content = + "{\"v2\":{\"format\":\"" + + dataStorageFormat + + "\",\"version\":" + + version + + ",\"privacyVersion\":" + + privacyVersion + + "}}"; + Files.write(tempDataDir.resolve(METADATA_FILENAME), content.getBytes(Charset.defaultCharset())); + } + + public static void createDatabaseMetadataRaw(final Path tempDataDir, final String content) + throws IOException { + Files.write(tempDataDir.resolve(METADATA_FILENAME), content.getBytes(Charset.defaultCharset())); + } + + private static int dataStorageFormatToV1(final DataStorageFormat dataStorageFormat) { + return switch (dataStorageFormat) { + case FOREST -> 1; + case BONSAI -> 2; + }; + } +} diff --git a/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/DatabaseMetadataTest.java b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/DatabaseMetadataTest.java index 569819f8312..56f5f768625 100644 --- a/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/DatabaseMetadataTest.java +++ b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/DatabaseMetadataTest.java @@ -12,11 +12,12 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.plugin.services.storage.rocksdb.configuration; import static org.assertj.core.api.Assertions.assertThat; +import org.hyperledger.besu.plugin.services.exception.StorageException; + import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -28,31 +29,56 @@ class DatabaseMetadataTest { @TempDir public Path temporaryFolder; @Test - void getVersion() { - final DatabaseMetadata databaseMetadata = new DatabaseMetadata(42); - assertThat(databaseMetadata).isNotNull(); - assertThat(databaseMetadata.getVersion()).isEqualTo(42); + void readingMetadataV1() throws Exception { + final Path tempDataDir = createAndWrite("data", "DATABASE_METADATA.json", "{\"version\":2}"); + + final DatabaseMetadata databaseMetadata = DatabaseMetadata.lookUpFrom(tempDataDir); + assertThat(databaseMetadata.getVersionedStorageFormat()) + .isEqualTo(BaseVersionedStorageFormat.BONSAI_WITH_VARIABLES); + } + + @Test + void readingMetadataV1Privacy() throws Exception { + final Path tempDataDir = + createAndWrite("data", "DATABASE_METADATA.json", "{\"version\":1,\"privacyVersion\":1}"); + + final DatabaseMetadata databaseMetadata = DatabaseMetadata.lookUpFrom(tempDataDir); + assertThat(databaseMetadata.getVersionedStorageFormat()) + .isEqualTo(PrivacyVersionedStorageFormat.FOREST_WITH_VARIABLES); + } + + @Test + void readingMetadataV2() throws Exception { + final Path tempDataDir = + createAndWrite( + "data", "DATABASE_METADATA.json", "{\"v2\":{\"format\":\"FOREST\",\"version\":2}}"); + + final DatabaseMetadata databaseMetadata = DatabaseMetadata.lookUpFrom(tempDataDir); + assertThat(databaseMetadata.getVersionedStorageFormat()) + .isEqualTo(BaseVersionedStorageFormat.FOREST_WITH_VARIABLES); } @Test - void metaFileShouldMayContain() throws Exception { + void readingMetadataV2Privacy() throws Exception { final Path tempDataDir = createAndWrite( - "data", "DATABASE_METADATA.json", "{\"version\":42 , \"privacyVersion\":55}"); + "data", + "DATABASE_METADATA.json", + "{\"v2\":{\"format\":\"FOREST\",\"version\":2,\"privacyVersion\":1}}"); final DatabaseMetadata databaseMetadata = DatabaseMetadata.lookUpFrom(tempDataDir); - assertThat(databaseMetadata).isNotNull(); - assertThat(databaseMetadata.getVersion()).isEqualTo(42); - assertThat(databaseMetadata.maybePrivacyVersion()).isNotEmpty(); - assertThat(databaseMetadata.maybePrivacyVersion().get()).isEqualTo(55); + assertThat(databaseMetadata.getVersionedStorageFormat()) + .isEqualTo(PrivacyVersionedStorageFormat.FOREST_WITH_VARIABLES); } @Test - void metaFileShouldBeSoughtIntoDataDirFirst() throws Exception { + void unsupportedMetadata() throws Exception { final Path tempDataDir = createAndWrite("data", "DATABASE_METADATA.json", "{\"version\":42}"); - final DatabaseMetadata databaseMetadata = DatabaseMetadata.lookUpFrom(tempDataDir); - assertThat(databaseMetadata).isNotNull(); - assertThat(databaseMetadata.getVersion()).isEqualTo(42); + try { + DatabaseMetadata.lookUpFrom(tempDataDir); + } catch (final StorageException se) { + assertThat(se).hasMessage("Unsupported db version: 42"); + } } private Path createAndWrite(final String dir, final String file, final String content) diff --git a/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/LayeredKeyValueStorageTest.java b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/LayeredKeyValueStorageTest.java new file mode 100644 index 00000000000..6ecc057d6f9 --- /dev/null +++ b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/LayeredKeyValueStorageTest.java @@ -0,0 +1,339 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.plugin.services.storage.rocksdb.segmented; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.plugin.services.storage.SegmentIdentifier; +import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; +import org.hyperledger.besu.services.kvstore.LayeredKeyValueStorage; + +import java.util.List; +import java.util.NavigableMap; +import java.util.Optional; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.commons.lang3.tuple.Pair; +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class LayeredKeyValueStorageTest { + + @Mock private SegmentedKeyValueStorage parentStorage; + + private LayeredKeyValueStorage layeredKeyValueStorage; + private SegmentIdentifier segmentId; + + @BeforeEach + void setUp() { + segmentId = mock(SegmentIdentifier.class); + layeredKeyValueStorage = new LayeredKeyValueStorage(parentStorage); + } + + @Test + void shouldReturnEmptyStreamWhenParentAndLayerAreEmpty() { + when(parentStorage.stream(segmentId)).thenReturn(Stream.empty()); + Stream> result = layeredKeyValueStorage.stream(segmentId); + assertTrue(result.collect(Collectors.toList()).isEmpty()); + } + + private ConcurrentMap>> + createSegmentMap() { + ConcurrentMap>> map = + new ConcurrentHashMap<>(); + NavigableMap> segmentMap = new TreeMap<>(); + map.put(segmentId, segmentMap); + return map; + } + + @Test + void shouldReturnParentDataWhenLayerIsEmpty() { + byte[] key1 = {1}; + byte[] value1 = {10}; + + when(parentStorage.stream(segmentId)).thenReturn(Stream.of(Pair.of(key1, value1))); + + Stream> result = layeredKeyValueStorage.stream(segmentId); + + List> resultList = result.collect(Collectors.toList()); + assertEquals(1, resultList.size()); + assertArrayEquals(key1, resultList.get(0).getKey()); + assertArrayEquals(value1, resultList.get(0).getValue()); + } + + @Test + void shouldReturnLayerDataWhenParentIsEmpty() { + byte[] key1 = {1}; + byte[] value1 = {10}; + + when(parentStorage.stream(segmentId)).thenReturn(Stream.empty()); + + var hashValueStore = createSegmentMap(); + hashValueStore.get(segmentId).put(Bytes.wrap(key1), Optional.of(value1)); + layeredKeyValueStorage = new LayeredKeyValueStorage(hashValueStore, parentStorage); + + Stream> result = layeredKeyValueStorage.stream(segmentId); + List> resultList = result.toList(); + assertEquals(1, resultList.size()); + assertArrayEquals(key1, resultList.get(0).getKey()); + assertArrayEquals(value1, resultList.get(0).getValue()); + } + + @Test + void shouldMergeParentAndLayerData() { + byte[] key1 = {1}; + byte[] value1 = {10}; + byte[] key2 = {2}; + byte[] value2 = {20}; + byte[] key3 = {3}; + byte[] value3 = {30}; + + when(parentStorage.stream(segmentId)) + .thenReturn(Stream.of(Pair.of(key1, value1), Pair.of(key3, value3))); + + var hashValueStore = createSegmentMap(); + hashValueStore.get(segmentId).put(Bytes.wrap(key2), Optional.of(value2)); + layeredKeyValueStorage = new LayeredKeyValueStorage(hashValueStore, parentStorage); + + Stream> result = layeredKeyValueStorage.stream(segmentId); + + List> resultList = result.toList(); + assertEquals(3, resultList.size()); + assertArrayEquals(key1, resultList.get(0).getKey()); + assertArrayEquals(value1, resultList.get(0).getValue()); + assertArrayEquals(key2, resultList.get(1).getKey()); + assertArrayEquals(value2, resultList.get(1).getValue()); + assertArrayEquals(key3, resultList.get(2).getKey()); + assertArrayEquals(value3, resultList.get(2).getValue()); + } + + @Test + void shouldPreferLayerDataOverParentDataForSameKey() { + byte[] key = {1}; + byte[] parentValue = {10}; + byte[] layerValue = {20}; + + when(parentStorage.stream(segmentId)).thenReturn(Stream.of(Pair.of(key, parentValue))); + + var hashValueStore = createSegmentMap(); + hashValueStore.get(segmentId).put(Bytes.wrap(key), Optional.of(layerValue)); + layeredKeyValueStorage = new LayeredKeyValueStorage(hashValueStore, parentStorage); + + Stream> result = layeredKeyValueStorage.stream(segmentId); + + List> resultList = result.toList(); + assertEquals(1, resultList.size()); + assertArrayEquals(key, resultList.get(0).getKey()); + // Layer value should be returned + assertArrayEquals(layerValue, resultList.get(0).getValue()); + } + + @Test + void shouldNotStreamKeyIfLayerKeyIsEmpty() { + byte[] key1 = {1}; + byte[] value1 = {10}; + byte[] key2 = {2}; + byte[] value2 = {20}; + + when(parentStorage.stream(segmentId)) + .thenReturn(Stream.of(Pair.of(key1, value1), Pair.of(key2, value2))); + + var hashValueStore = createSegmentMap(); + hashValueStore.get(segmentId).put(Bytes.wrap(key1), Optional.empty()); + + layeredKeyValueStorage = new LayeredKeyValueStorage(hashValueStore, parentStorage); + + var resultList = layeredKeyValueStorage.stream(segmentId).toList(); + assertEquals(1, resultList.size()); + assertArrayEquals(key2, resultList.get(0).getKey()); + assertArrayEquals(value2, resultList.get(0).getValue()); + } + + /** + * Tests that the stream method correctly handles multiple layers where the current layer + * overrides the parent layers. + */ + @Test + void shouldStreamWithMultipleLayersAndCurrentLayerOverrides() { + byte[] key1 = {1}; + byte[] value1 = {10}; + byte[] key2 = {2}; + byte[] value2 = {20}; + byte[] key3 = {3}; + byte[] value3 = {30}; + + // Parent Layer 0 + when(parentStorage.stream(segmentId)) + .thenReturn(Stream.of(Pair.of(key1, null), Pair.of(key2, value2))); + + // Parent Layer 1 + var parentLayer1 = createSegmentMap(); + parentLayer1.get(segmentId).put(Bytes.wrap(key1), Optional.of(value1)); + parentLayer1.get(segmentId).put(Bytes.wrap(key2), Optional.of(value2)); + + // Current Layer + var currentLayer = createSegmentMap(); + currentLayer.get(segmentId).put(Bytes.wrap(key1), Optional.empty()); + currentLayer.get(segmentId).put(Bytes.wrap(key3), Optional.of(value3)); + + layeredKeyValueStorage = + new LayeredKeyValueStorage( + currentLayer, new LayeredKeyValueStorage(parentLayer1, parentStorage)); + + Stream> result = layeredKeyValueStorage.stream(segmentId); + + List> resultList = result.toList(); + assertEquals(2, resultList.size()); + assertArrayEquals(key2, resultList.get(0).getKey()); + assertArrayEquals(value2, resultList.get(0).getValue()); + assertArrayEquals(key3, resultList.get(1).getKey()); + assertArrayEquals(value3, resultList.get(1).getValue()); + } + + /** + * Tests that the stream method correctly handles multiple layers where the current layer + * overrides the parent layers with specific values. + */ + @Test + void shouldStreamWithMultipleLayersAndCurrentLayerOverridesWithValues() { + byte[] key1 = {1}; + byte[] value1 = {10}; + byte[] key2 = {2}; + byte[] value2 = {20}; + byte[] key3 = {3}; + byte[] value3 = {30}; + + // Parent Layer 0 + when(parentStorage.stream(segmentId)) + .thenReturn(Stream.of(Pair.of(key1, value1), Pair.of(key2, value2))); + + // Parent Layer 1 + var parentLayer1 = createSegmentMap(); + parentLayer1.get(segmentId).put(Bytes.wrap(key1), Optional.empty()); + parentLayer1.get(segmentId).put(Bytes.wrap(key2), Optional.of(value2)); + + // Current Layer + var currentLayer = createSegmentMap(); + currentLayer.get(segmentId).put(Bytes.wrap(key1), Optional.of(value1)); + currentLayer.get(segmentId).put(Bytes.wrap(key3), Optional.of(value3)); + + layeredKeyValueStorage = + new LayeredKeyValueStorage( + currentLayer, new LayeredKeyValueStorage(parentLayer1, parentStorage)); + + Stream> result = layeredKeyValueStorage.stream(segmentId); + + List> resultList = result.toList(); + assertEquals(3, resultList.size()); + assertArrayEquals(key1, resultList.get(0).getKey()); + assertArrayEquals(value1, resultList.get(0).getValue()); + assertArrayEquals(key2, resultList.get(1).getKey()); + assertArrayEquals(value2, resultList.get(1).getValue()); + assertArrayEquals(key3, resultList.get(2).getKey()); + assertArrayEquals(value3, resultList.get(2).getValue()); + } + + /** + * Tests that the stream method correctly handles multiple layers where the current layer + * overrides the parent layers with empty values. + */ + @Test + void shouldStreamWithMultipleLayersAndCurrentLayerOverridesWithEmptyValues() { + byte[] key1 = {1}; + byte[] value1 = {10}; + byte[] key2 = {2}; + byte[] value2 = {20}; + byte[] key3 = {3}; + byte[] value3 = {30}; + + // Parent Layer 0 + when(parentStorage.stream(segmentId)) + .thenReturn(Stream.of(Pair.of(key1, null), Pair.of(key2, value2))); + + // Parent Layer 1 + var parentLayer1 = createSegmentMap(); + parentLayer1.get(segmentId).put(Bytes.wrap(key1), Optional.empty()); + parentLayer1.get(segmentId).put(Bytes.wrap(key2), Optional.of(value2)); + + // Current Layer + var currentLayer = createSegmentMap(); + currentLayer.get(segmentId).put(Bytes.wrap(key1), Optional.of(value1)); + currentLayer.get(segmentId).put(Bytes.wrap(key3), Optional.of(value3)); + + layeredKeyValueStorage = + new LayeredKeyValueStorage( + currentLayer, new LayeredKeyValueStorage(parentLayer1, parentStorage)); + + Stream> result = layeredKeyValueStorage.stream(segmentId); + + List> resultList = result.toList(); + assertEquals(3, resultList.size()); + assertArrayEquals(key1, resultList.get(0).getKey()); + assertArrayEquals(value1, resultList.get(0).getValue()); + assertArrayEquals(key2, resultList.get(1).getKey()); + assertArrayEquals(value2, resultList.get(1).getValue()); + assertArrayEquals(key3, resultList.get(2).getKey()); + assertArrayEquals(value3, resultList.get(2).getValue()); + } + + /** + * Tests that the stream method correctly handles a parent layer and a current layer where the + * current layer overrides the parent layer. + */ + @Test + void shouldStreamWithParentLayerAndCurrentLayerOverrides() { + byte[] key1 = {1}; + byte[] value1 = {10}; + byte[] key2 = {2}; + byte[] value2 = {20}; + byte[] key3 = {3}; + byte[] value3 = {30}; + + // Parent Layer 0 + when(parentStorage.stream(segmentId)) + .thenReturn(Stream.of(Pair.of(key1, null), Pair.of(key2, value2))); + + // Current Layer + var currentLayer = createSegmentMap(); + currentLayer.get(segmentId).put(Bytes.wrap(key1), Optional.of(value1)); + currentLayer.get(segmentId).put(Bytes.wrap(key3), Optional.of(value3)); + + layeredKeyValueStorage = new LayeredKeyValueStorage(currentLayer, parentStorage); + + Stream> result = layeredKeyValueStorage.stream(segmentId); + + List> resultList = result.toList(); + assertEquals(3, resultList.size()); + assertArrayEquals(key1, resultList.get(0).getKey()); + assertArrayEquals(value1, resultList.get(0).getValue()); + assertArrayEquals(key2, resultList.get(1).getKey()); + assertArrayEquals(value2, resultList.get(1).getValue()); + assertArrayEquals(key3, resultList.get(2).getKey()); + assertArrayEquals(value3, resultList.get(2).getValue()); + } +} diff --git a/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/OptimisticTransactionDBRocksDBColumnarKeyValueStorageTest.java b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/OptimisticTransactionDBRocksDBColumnarKeyValueStorageTest.java index 7e8cef52cc2..d2082e85912 100644 --- a/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/OptimisticTransactionDBRocksDBColumnarKeyValueStorageTest.java +++ b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/OptimisticTransactionDBRocksDBColumnarKeyValueStorageTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -39,7 +39,7 @@ protected SegmentedKeyValueStorage createSegmentedStore() throws Exception { new RocksDBConfigurationBuilder() .databaseDir(Files.createTempDirectory("segmentedStore")) .build(), - Arrays.asList(TestSegment.FOO, TestSegment.BAR), + Arrays.asList(TestSegment.DEFAULT, TestSegment.FOO, TestSegment.BAR), List.of(), new NoOpMetricsSystem(), RocksDBMetricsFactory.PUBLIC_ROCKS_DB_METRICS); diff --git a/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorageTest.java b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorageTest.java index 5462d239b91..e28828c396e 100644 --- a/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorageTest.java +++ b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/RocksDBColumnarKeyValueStorageTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -194,13 +194,17 @@ public void dbShouldIgnoreExperimentalSegmentsIfNotExisted(@TempDir final Path t SegmentedKeyValueStorage store = createSegmentedStore( testPath, - Arrays.asList(TestSegment.FOO, TestSegment.BAR, TestSegment.EXPERIMENTAL), + Arrays.asList( + TestSegment.DEFAULT, TestSegment.FOO, TestSegment.BAR, TestSegment.EXPERIMENTAL), List.of(TestSegment.EXPERIMENTAL)); store.close(); // new db will be backward compatible with db without knowledge of experimental column family store = - createSegmentedStore(testPath, Arrays.asList(TestSegment.FOO, TestSegment.BAR), List.of()); + createSegmentedStore( + testPath, + Arrays.asList(TestSegment.DEFAULT, TestSegment.FOO, TestSegment.BAR), + List.of()); store.close(); } @@ -212,14 +216,18 @@ public void dbShouldNotIgnoreExperimentalSegmentsIfExisted(@TempDir final Path t SegmentedKeyValueStorage store = createSegmentedStore( testPath, - Arrays.asList(TestSegment.FOO, TestSegment.BAR, TestSegment.EXPERIMENTAL), + Arrays.asList( + TestSegment.DEFAULT, TestSegment.FOO, TestSegment.BAR, TestSegment.EXPERIMENTAL), List.of()); store.close(); // new db will not be backward compatible with db without knowledge of experimental column // family try { - createSegmentedStore(testPath, Arrays.asList(TestSegment.FOO, TestSegment.BAR), List.of()); + createSegmentedStore( + testPath, + Arrays.asList(TestSegment.DEFAULT, TestSegment.FOO, TestSegment.BAR), + List.of()); fail("DB without knowledge of experimental column family should fail"); } catch (StorageException e) { assertThat(e.getMessage()).contains("Unhandled column families"); @@ -230,7 +238,8 @@ public void dbShouldNotIgnoreExperimentalSegmentsIfExisted(@TempDir final Path t store = createSegmentedStore( testPath, - Arrays.asList(TestSegment.FOO, TestSegment.BAR, TestSegment.EXPERIMENTAL), + Arrays.asList( + TestSegment.DEFAULT, TestSegment.FOO, TestSegment.BAR, TestSegment.EXPERIMENTAL), List.of(TestSegment.EXPERIMENTAL)); store.close(); } @@ -242,27 +251,35 @@ public void dbWillBeBackwardIncompatibleAfterExperimentalSegmentsAreAdded( SegmentedKeyValueStorage store = createSegmentedStore( testPath, - Arrays.asList(TestSegment.FOO, TestSegment.BAR, TestSegment.EXPERIMENTAL), + Arrays.asList( + TestSegment.DEFAULT, TestSegment.FOO, TestSegment.BAR, TestSegment.EXPERIMENTAL), List.of(TestSegment.EXPERIMENTAL)); store.close(); // new db will be backward compatible with db without knowledge of experimental column family store = - createSegmentedStore(testPath, Arrays.asList(TestSegment.FOO, TestSegment.BAR), List.of()); + createSegmentedStore( + testPath, + Arrays.asList(TestSegment.DEFAULT, TestSegment.FOO, TestSegment.BAR), + List.of()); store.close(); // Create new db without ignoring experimental colum family will add column to db store = createSegmentedStore( testPath, - Arrays.asList(TestSegment.FOO, TestSegment.BAR, TestSegment.EXPERIMENTAL), + Arrays.asList( + TestSegment.DEFAULT, TestSegment.FOO, TestSegment.BAR, TestSegment.EXPERIMENTAL), List.of()); store.close(); // Now, the db will be backward incompatible with db without knowledge of experimental column // family try { - createSegmentedStore(testPath, Arrays.asList(TestSegment.FOO, TestSegment.BAR), List.of()); + createSegmentedStore( + testPath, + Arrays.asList(TestSegment.DEFAULT, TestSegment.FOO, TestSegment.BAR), + List.of()); fail("DB without knowledge of experimental column family should fail"); } catch (StorageException e) { assertThat(e.getMessage()).contains("Unhandled column families"); @@ -291,76 +308,84 @@ public void createStoreMustCreateMetrics() throws Exception { // Actual call - final SegmentedKeyValueStorage store = + try (final SegmentedKeyValueStorage store = createSegmentedStore( - folder, metricsSystemMock, List.of(TestSegment.FOO), List.of(TestSegment.EXPERIMENTAL)); - - KeyValueStorage keyValueStorage = new SegmentedKeyValueStorageAdapter(TestSegment.FOO, store); - - // Assertions - assertThat(keyValueStorage).isNotNull(); - verify(metricsSystemMock, times(4)) - .createLabelledTimer( - eq(BesuMetricCategory.KVSTORE_ROCKSDB), - labelledTimersMetricsNameArgs.capture(), - labelledTimersHelpArgs.capture(), - any()); - assertThat(labelledTimersMetricsNameArgs.getAllValues()) - .containsExactly( - "read_latency_seconds", - "remove_latency_seconds", - "write_latency_seconds", - "commit_latency_seconds"); - assertThat(labelledTimersHelpArgs.getAllValues()) - .containsExactly( - "Latency for read from RocksDB.", - "Latency of remove requests from RocksDB.", - "Latency for write to RocksDB.", - "Latency for commits to RocksDB."); - - verify(metricsSystemMock, times(2)) - .createLongGauge( - eq(BesuMetricCategory.KVSTORE_ROCKSDB), - longGaugesMetricsNameArgs.capture(), - longGaugesHelpArgs.capture(), - any(LongSupplier.class)); - assertThat(longGaugesMetricsNameArgs.getAllValues()) - .containsExactly("rocks_db_table_readers_memory_bytes", "rocks_db_files_size_bytes"); - assertThat(longGaugesHelpArgs.getAllValues()) - .containsExactly( - "Estimated memory used for RocksDB index and filter blocks in bytes", - "Estimated database size in bytes"); - - verify(metricsSystemMock) - .createLabelledCounter( - eq(BesuMetricCategory.KVSTORE_ROCKSDB), - labelledCountersMetricsNameArgs.capture(), - labelledCountersHelpArgs.capture(), - any()); - assertThat(labelledCountersMetricsNameArgs.getValue()).isEqualTo("rollback_count"); - assertThat(labelledCountersHelpArgs.getValue()) - .isEqualTo("Number of RocksDB transactions rolled back."); + folder, + metricsSystemMock, + List.of(TestSegment.DEFAULT, TestSegment.FOO), + List.of(TestSegment.EXPERIMENTAL))) { + + KeyValueStorage keyValueStorage = new SegmentedKeyValueStorageAdapter(TestSegment.FOO, store); + + // Assertions + assertThat(keyValueStorage).isNotNull(); + verify(metricsSystemMock, times(4)) + .createLabelledTimer( + eq(BesuMetricCategory.KVSTORE_ROCKSDB), + labelledTimersMetricsNameArgs.capture(), + labelledTimersHelpArgs.capture(), + any()); + assertThat(labelledTimersMetricsNameArgs.getAllValues()) + .containsExactly( + "read_latency_seconds", + "remove_latency_seconds", + "write_latency_seconds", + "commit_latency_seconds"); + assertThat(labelledTimersHelpArgs.getAllValues()) + .containsExactly( + "Latency for read from RocksDB.", + "Latency of remove requests from RocksDB.", + "Latency for write to RocksDB.", + "Latency for commits to RocksDB."); + + verify(metricsSystemMock, times(2)) + .createLongGauge( + eq(BesuMetricCategory.KVSTORE_ROCKSDB), + longGaugesMetricsNameArgs.capture(), + longGaugesHelpArgs.capture(), + any(LongSupplier.class)); + assertThat(longGaugesMetricsNameArgs.getAllValues()) + .containsExactly("rocks_db_table_readers_memory_bytes", "rocks_db_files_size_bytes"); + assertThat(longGaugesHelpArgs.getAllValues()) + .containsExactly( + "Estimated memory used for RocksDB index and filter blocks in bytes", + "Estimated database size in bytes"); + + verify(metricsSystemMock) + .createLabelledCounter( + eq(BesuMetricCategory.KVSTORE_ROCKSDB), + labelledCountersMetricsNameArgs.capture(), + labelledCountersHelpArgs.capture(), + any()); + assertThat(labelledCountersMetricsNameArgs.getValue()).isEqualTo("rollback_count"); + assertThat(labelledCountersHelpArgs.getValue()) + .isEqualTo("Number of RocksDB transactions rolled back."); + } } public enum TestSegment implements SegmentIdentifier { + DEFAULT("default".getBytes(StandardCharsets.UTF_8)), FOO(new byte[] {1}), BAR(new byte[] {2}), EXPERIMENTAL(new byte[] {3}), - STATIC_DATA(new byte[] {4}, true); + STATIC_DATA(new byte[] {4}, true, false); private final byte[] id; private final String nameAsUtf8; private final boolean containsStaticData; + private final boolean eligibleToHighSpecFlag; TestSegment(final byte[] id) { - this(id, false); + this(id, false, false); } - TestSegment(final byte[] id, final boolean containsStaticData) { + TestSegment( + final byte[] id, final boolean containsStaticData, final boolean eligibleToHighSpecFlag) { this.id = id; this.nameAsUtf8 = new String(id, StandardCharsets.UTF_8); this.containsStaticData = containsStaticData; + this.eligibleToHighSpecFlag = eligibleToHighSpecFlag; } @Override @@ -377,6 +402,11 @@ public byte[] getId() { public boolean containsStaticData() { return containsStaticData; } + + @Override + public boolean isEligibleToHighSpecFlag() { + return eligibleToHighSpecFlag; + } } protected abstract SegmentedKeyValueStorage createSegmentedStore() throws Exception; diff --git a/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/TransactionDBRocksDBColumnarKeyValueStorageTest.java b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/TransactionDBRocksDBColumnarKeyValueStorageTest.java index bed64f58404..513a03cc347 100644 --- a/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/TransactionDBRocksDBColumnarKeyValueStorageTest.java +++ b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/segmented/TransactionDBRocksDBColumnarKeyValueStorageTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -36,7 +36,7 @@ public class TransactionDBRocksDBColumnarKeyValueStorageTest protected SegmentedKeyValueStorage createSegmentedStore() throws Exception { return new TransactionDBRocksDBColumnarKeyValueStorage( new RocksDBConfigurationBuilder().databaseDir(getTempSubFolder(folder)).build(), - Arrays.asList(TestSegment.FOO, TestSegment.BAR), + Arrays.asList(TestSegment.DEFAULT, TestSegment.FOO, TestSegment.BAR), List.of(), new NoOpMetricsSystem(), RocksDBMetricsFactory.PUBLIC_ROCKS_DB_METRICS); diff --git a/privacy-contracts/src/main/solidity/generateWrappers.sh b/privacy-contracts/src/main/solidity/generateWrappers.sh index 068d7a1e3ac..5d7fd7f9fce 100755 --- a/privacy-contracts/src/main/solidity/generateWrappers.sh +++ b/privacy-contracts/src/main/solidity/generateWrappers.sh @@ -1,4 +1,18 @@ #!/usr/bin/env bash +## +## Copyright contributors to Hyperledger Besu. +## +## Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +## an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +## specific language governing permissions and limitations under the License. +## +## SPDX-License-Identifier: Apache-2.0 +## targets=" FlexiblePrivacyGroupManagementInterface diff --git a/scripts/dco_check.sh b/scripts/dco_check.sh index 86716237f71..dd446753987 100755 --- a/scripts/dco_check.sh +++ b/scripts/dco_check.sh @@ -1,4 +1,18 @@ #!/bin/bash +## +## Copyright contributors to Hyperledger Besu. +## +## Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +## an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +## specific language governing permissions and limitations under the License. +## +## SPDX-License-Identifier: Apache-2.0 +## status=0 while IFS= read -r -a line; do diff --git a/services/kvstore/build.gradle b/services/kvstore/build.gradle index 0415f6702b8..5f379b60a5b 100644 --- a/services/kvstore/build.gradle +++ b/services/kvstore/build.gradle @@ -22,7 +22,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } diff --git a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/InMemoryKeyValueStorage.java b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/InMemoryKeyValueStorage.java index dbd62eaff9a..930783d7be1 100644 --- a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/InMemoryKeyValueStorage.java +++ b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/InMemoryKeyValueStorage.java @@ -18,7 +18,7 @@ import java.io.PrintStream; import java.nio.charset.StandardCharsets; -import java.util.Map; +import java.util.NavigableMap; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -50,11 +50,16 @@ public byte[] getId() { public boolean containsStaticData() { return false; } + + @Override + public boolean isEligibleToHighSpecFlag() { + return false; + } }; - private static ConcurrentMap>> asSegmentMap( - final Map> initialMap) { - final ConcurrentMap>> segmentMap = + private static ConcurrentMap>> + asSegmentMap(final NavigableMap> initialMap) { + final ConcurrentMap>> segmentMap = new ConcurrentHashMap<>(); segmentMap.put(SEGMENT_IDENTIFIER, initialMap); return segmentMap; @@ -73,7 +78,7 @@ public InMemoryKeyValueStorage() { * * @param initialMap the initial map */ - public InMemoryKeyValueStorage(final Map> initialMap) { + public InMemoryKeyValueStorage(final NavigableMap> initialMap) { super(SEGMENT_IDENTIFIER, new SegmentedInMemoryKeyValueStorage(asSegmentMap(initialMap))); rwLock = ((SegmentedInMemoryKeyValueStorage) storage).rwLock; } diff --git a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/InMemoryStoragePlugin.java b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/InMemoryStoragePlugin.java index 648de893ff4..6783a82c1c4 100644 --- a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/InMemoryStoragePlugin.java +++ b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/InMemoryStoragePlugin.java @@ -40,6 +40,9 @@ public class InMemoryStoragePlugin implements BesuPlugin { private InMemoryKeyValueStorageFactory factory; private InMemoryKeyValueStorageFactory privacyFactory; + /** Default constructor */ + public InMemoryStoragePlugin() {} + @Override public void register(final BesuContext context) { LOG.debug("Registering plugin"); diff --git a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/KeyComparator.java b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/KeyComparator.java new file mode 100644 index 00000000000..ff9dd00dd8a --- /dev/null +++ b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/KeyComparator.java @@ -0,0 +1,61 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.services.kvstore; + +import org.apache.tuweni.bytes.Bytes; + +/** + * This class is a comparator that allows comparing two byte arrays from left to right. + * + *

    For example: + * + *

    >0x01 is smaller than 0x0101 or 0x01 is smaller than 0x02. + */ +public class KeyComparator { + + /** Instantiates a new KeyComparator */ + public KeyComparator() {} + + /** + * Compares two keys from left to right. + * + *

    This method performs a byte-by-byte comparison between two keys, starting from the left + * (most significant byte). It is designed to compare keys in a way that reflects their + * hierarchical or sequential order. + * + *

    The method returns: - A negative integer if {@code key1} is lexicographically less than + * key2. - Zero if key1 and key2 are equal. - A positive integer if key1 is lexicographically + * greater than key2. + * + *

    If the keys are of unequal length but identical for the length of the shorter key (prefix), + * the shorter key is considered to be lexicographically less than the longer key. This is + * consistent with the lexicographic ordering used by rocksdb. + * + * @param key1 the first key compare. + * @param key2 the second key to compare with. + * @return the value 0 if key1 is equal to key2; a value less than 0 if key1 is lexicographically + * less than key2; and a value greater than 0 if key1 is lexicographically greater than key2. + */ + public static int compareKeyLeftToRight(final Bytes key1, final Bytes key2) { + int minLength = Math.min(key1.size(), key2.size()); + for (int i = 0; i < minLength; i++) { + int compare = Byte.compareUnsigned(key1.get(i), key2.get(i)); + if (compare != 0) { + return compare; + } + } + return Integer.compare(key1.size(), key2.size()); + } +} diff --git a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/LayeredKeyValueStorage.java b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/LayeredKeyValueStorage.java index 6713772a03d..9f44c3bc79c 100644 --- a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/LayeredKeyValueStorage.java +++ b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/LayeredKeyValueStorage.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -11,10 +11,13 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ package org.hyperledger.besu.services.kvstore; +import static java.util.Spliterator.DISTINCT; +import static java.util.Spliterator.ORDERED; +import static java.util.Spliterator.SORTED; + import org.hyperledger.besu.plugin.services.exception.StorageException; import org.hyperledger.besu.plugin.services.storage.SegmentIdentifier; import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; @@ -22,13 +25,18 @@ import org.hyperledger.besu.plugin.services.storage.SnappedKeyValueStorage; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; +import java.util.NavigableMap; import java.util.Optional; +import java.util.Spliterators; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.Lock; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; +import java.util.stream.StreamSupport; import com.google.common.collect.Streams; import org.apache.commons.lang3.tuple.Pair; @@ -60,7 +68,7 @@ public LayeredKeyValueStorage(final SegmentedKeyValueStorage parent) { * @param parent the parent key value storage for this layered storage. */ public LayeredKeyValueStorage( - final ConcurrentMap>> map, + final ConcurrentMap>> map, final SegmentedKeyValueStorage parent) { super(map); this.parent = parent; @@ -82,7 +90,7 @@ public Optional get(final SegmentIdentifier segmentId, final byte[] key) try { Bytes wrapKey = Bytes.wrap(key); final Optional foundKey = - hashValueStore.computeIfAbsent(segmentId, __ -> new HashMap<>()).get(wrapKey); + hashValueStore.computeIfAbsent(segmentId, __ -> newSegmentMap()).get(wrapKey); if (foundKey == null) { return parent.get(segmentId, key); } else { @@ -94,49 +102,135 @@ public Optional get(final SegmentIdentifier segmentId, final byte[] key) } @Override - public Optional getNearestTo( + public Optional getNearestBefore( + final SegmentIdentifier segmentIdentifier, final Bytes key) throws StorageException { + return getNearest( + key, + k -> super.getNearestBefore(segmentIdentifier, k), + k -> parent.getNearestBefore(segmentIdentifier, k), + false); + } + + @Override + public Optional getNearestAfter( final SegmentIdentifier segmentIdentifier, final Bytes key) throws StorageException { - Optional ourNearest = super.getNearestTo(segmentIdentifier, key); - Optional parentNearest = parent.getNearestTo(segmentIdentifier, key); + return getNearest( + key, + k -> super.getNearestAfter(segmentIdentifier, k), + k -> parent.getNearestAfter(segmentIdentifier, k), + true); + } + + private Optional getNearest( + final Bytes key, + final Function> ourNearestFunction, + final Function> parentNearestFunction, + final boolean isAfter) + throws StorageException { + + final Optional ourNearest = ourNearestFunction.apply(key); + final Optional parentNearest = parentNearestFunction.apply(key); if (ourNearest.isPresent() && parentNearest.isPresent()) { - // Both are present, return the one closer to the key - int ourDistance = ourNearest.get().key().commonPrefixLength(key); - int parentDistance = parentNearest.get().key().commonPrefixLength(key); - return (ourDistance <= parentDistance) ? ourNearest : parentNearest; + return compareNearest(ourNearest, parentNearest, key, isAfter); } else if (ourNearest.isPresent()) { - // Only ourNearest is present return ourNearest; } else { - // return parentNearest, which may be an empty Optional return parentNearest; } } + private Optional compareNearest( + final Optional ourNearest, + final Optional parentNearest, + final Bytes key, + final boolean isAfter) { + + final int ourDistance = ourNearest.get().key().compareTo(key); + final int parentDistance = parentNearest.get().key().compareTo(key); + if (ourDistance == 0) { + return ourNearest; + } else if (parentDistance == 0) { + return parentNearest; + } else { + final int ourCommonPrefixLength = ourNearest.get().key().commonPrefixLength(key); + final int parentCommonPrefixLength = parentNearest.get().key().commonPrefixLength(key); + if (ourCommonPrefixLength != parentCommonPrefixLength) { + return ourCommonPrefixLength > parentCommonPrefixLength ? ourNearest : parentNearest; + } else { + // When searching for a key, if isAfter is true, we choose the next smallest key after our + // target because both found keys are after it. + // If isAfter is false, meaning we're doing a seekForPrev, we select the largest key that + // comes before our target, as it's the nearest one. + // For example : if the searched key is 0x0101 and we found 0x0001 and 0x0100 when isAfter + // == false we will take 0x0100 + if (ourNearest.get().key().compareTo(parentNearest.get().key()) > 0) { + return isAfter ? parentNearest : ourNearest; + } else { + return isAfter ? ourNearest : parentNearest; + } + } + } + } + @Override public Stream> stream(final SegmentIdentifier segmentId) { throwIfClosed(); + var ourLayerState = hashValueStore.computeIfAbsent(segmentId, s -> newSegmentMap()); - final Lock lock = rwLock.readLock(); - lock.lock(); - try { - // copy of our in memory store to use for streaming and filtering: - var ourLayerState = - Optional.ofNullable(hashValueStore.get(segmentId)) - .map(HashMap::new) - .orElse(new HashMap<>()); + PeekingIterator>> ourIterator = + new PeekingIterator<>(ourLayerState.entrySet().stream().iterator()); + PeekingIterator> parentIterator = + new PeekingIterator<>(parent.stream(segmentId).iterator()); - return Streams.concat( - ourLayerState.entrySet().stream() - .filter(entry -> entry.getValue().isPresent()) - .map( - bytesEntry -> - Pair.of(bytesEntry.getKey().toArrayUnsafe(), bytesEntry.getValue().get())) - // since we are layered, concat a parent stream filtered by our map entries: - , - parent.stream(segmentId).filter(e -> !ourLayerState.containsKey(Bytes.of(e.getLeft())))); - } finally { - lock.unlock(); + return StreamSupport.stream( + Spliterators.spliteratorUnknownSize( + new LayeredIterator(ourIterator, parentIterator), ORDERED | SORTED | DISTINCT), + false) + .filter(e -> e.getValue() != null); + } + + private static class LayeredIterator implements Iterator> { + private final PeekingIterator>> ourIterator; + private final PeekingIterator> parentIterator; + + LayeredIterator( + final PeekingIterator>> ourIterator, + final PeekingIterator> parentIterator) { + this.ourIterator = ourIterator; + this.parentIterator = parentIterator; + } + + @Override + public boolean hasNext() { + return ourIterator.hasNext() || parentIterator.hasNext(); + } + + private Pair mapEntryToPair(final Map.Entry> entry) { + byte[] value = entry.getValue().orElse(null); + return Pair.of(entry.getKey().toArrayUnsafe(), value); + } + + @Override + public Pair next() { + var ourPeek = ourIterator.peek(); + var parentPeek = parentIterator.peek(); + + if (ourPeek == null || parentPeek == null) { + return ourPeek == null ? parentIterator.next() : mapEntryToPair(ourIterator.next()); + } + + // otherwise compare: + int comparison = ourPeek.getKey().compareTo(Bytes.wrap(parentPeek.getKey())); + if (comparison < 0) { + return mapEntryToPair(ourIterator.next()); + } else if (comparison == 0) { + // skip dupe key from parent, return ours: + parentIterator.next(); + return mapEntryToPair(ourIterator.next()); + } else { + return parentIterator.next(); + } } } @@ -186,7 +280,7 @@ public Stream streamKeys(final SegmentIdentifier segmentId) { @Override public boolean tryDelete(final SegmentIdentifier segmentId, final byte[] key) { hashValueStore - .computeIfAbsent(segmentId, __ -> new HashMap<>()) + .computeIfAbsent(segmentId, __ -> newSegmentMap()) .put(Bytes.wrap(key), Optional.empty()); return true; } @@ -206,7 +300,7 @@ public void commit() throws StorageException { .forEach( entry -> hashValueStore - .computeIfAbsent(entry.getKey(), __ -> new HashMap<>()) + .computeIfAbsent(entry.getKey(), __ -> newSegmentMap()) .putAll(entry.getValue())); // put empty rather than remove in order to not ask parent in case of deletion @@ -214,7 +308,7 @@ public void commit() throws StorageException { .forEach( segmentEntry -> hashValueStore - .computeIfAbsent(segmentEntry.getKey(), __ -> new HashMap<>()) + .computeIfAbsent(segmentEntry.getKey(), __ -> newSegmentMap()) .putAll( segmentEntry.getValue().stream() .collect( @@ -246,4 +340,30 @@ private void throwIfClosed() { throw new StorageException("Storage has been closed"); } } + + private static class PeekingIterator implements Iterator { + private final Iterator iterator; + private E next; + + public PeekingIterator(final Iterator iterator) { + this.iterator = iterator; + this.next = iterator.hasNext() ? iterator.next() : null; + } + + public E peek() { + return next; + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public E next() { + E oldNext = next; + next = iterator.hasNext() ? iterator.next() : null; + return oldNext; + } + } } diff --git a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedInMemoryKeyValueStorage.java b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedInMemoryKeyValueStorage.java index ff8ca7249cb..a7cedca362b 100644 --- a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedInMemoryKeyValueStorage.java +++ b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedInMemoryKeyValueStorage.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -15,6 +15,7 @@ package org.hyperledger.besu.services.kvstore; import static java.util.stream.Collectors.toUnmodifiableSet; +import static org.hyperledger.besu.services.kvstore.KeyComparator.compareKeyLeftToRight; import org.hyperledger.besu.plugin.services.exception.StorageException; import org.hyperledger.besu.plugin.services.storage.SegmentIdentifier; @@ -24,18 +25,22 @@ import org.hyperledger.besu.plugin.services.storage.SnappedKeyValueStorage; import java.io.PrintStream; +import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.NavigableMap; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -49,11 +54,35 @@ public class SegmentedInMemoryKeyValueStorage implements SnappedKeyValueStorage, SnappableKeyValueStorage, SegmentedKeyValueStorage { /** protected access for the backing hash map. */ - final ConcurrentMap>> hashValueStore; + final ConcurrentMap>> hashValueStore; /** protected access to the rw lock. */ protected final ReadWriteLock rwLock = new ReentrantReadWriteLock(); + /** + * Create a navigable segment map, with a compatible Bytes comparator + * + * @return segment map + */ + protected static NavigableMap> newSegmentMap() { + return newSegmentMap(Collections.emptyMap()); + } + + /** + * Create and populate a navigable segment map, with a compatible Bytes comparator. + * + * @param sourceMap sourcemap to initialize the segmentmap with. + * @return populated segment map + */ + protected static NavigableMap> newSegmentMap( + final Map> sourceMap) { + // comparing by string to prevent Bytes comparator from collapsing zeroes + NavigableMap> segMap = + new ConcurrentSkipListMap<>(Comparator.comparing(Bytes::toHexString)); + segMap.putAll(sourceMap); + return segMap; + } + /** Instantiates a new In memory key value storage. */ public SegmentedInMemoryKeyValueStorage() { this(new ConcurrentHashMap<>()); @@ -65,7 +94,8 @@ public SegmentedInMemoryKeyValueStorage() { * @param hashValueStore the hash value store */ protected SegmentedInMemoryKeyValueStorage( - final ConcurrentMap>> hashValueStore) { + final ConcurrentMap>> + hashValueStore) { this.hashValueStore = hashValueStore; } @@ -79,8 +109,8 @@ public SegmentedInMemoryKeyValueStorage(final List segments) segments.stream() .collect( Collectors - .>> - toConcurrentMap(s -> s, s -> new ConcurrentHashMap<>()))); + .>> + toConcurrentMap(s -> s, s -> newSegmentMap()))); } @Override @@ -107,7 +137,7 @@ public Optional get(final SegmentIdentifier segmentIdentifier, final byt lock.lock(); try { return hashValueStore - .computeIfAbsent(segmentIdentifier, s -> new HashMap<>()) + .computeIfAbsent(segmentIdentifier, s -> newSegmentMap()) .getOrDefault(Bytes.wrap(key), Optional.empty()); } finally { lock.unlock(); @@ -115,26 +145,67 @@ public Optional get(final SegmentIdentifier segmentIdentifier, final byt } @Override - public Optional getNearestTo( + public Optional getNearestBefore( + final SegmentIdentifier segmentIdentifier, final Bytes key) throws StorageException { + return getNearest( + segmentIdentifier, + e -> + compareKeyLeftToRight(e.getKey(), key) <= 0 + && e.getKey().commonPrefixLength(key) >= e.getKey().size(), + e -> compareKeyLeftToRight(e.getKey(), key) < 0, + false); + } + + @Override + public Optional getNearestAfter( final SegmentIdentifier segmentIdentifier, final Bytes key) throws StorageException { + return getNearest( + segmentIdentifier, + e -> + compareKeyLeftToRight(e.getKey(), key) >= 0 + && e.getKey().commonPrefixLength(key) >= e.getKey().size(), + e -> compareKeyLeftToRight(e.getKey(), key) >= 0, + true); + } + + private Optional getNearest( + final SegmentIdentifier segmentIdentifier, + final Predicate>> samePrefixPredicate, + final Predicate>> fallbackPredicate, + final boolean useMin) + throws StorageException { final Lock lock = rwLock.readLock(); lock.lock(); try { - // TODO: revisit this for sort performance - Comparator>> comparing = - Comparator.comparing( - (Map.Entry> a) -> a.getKey().commonPrefixLength(key)) - .thenComparing(Map.Entry.comparingByKey()); - return this.hashValueStore - .computeIfAbsent(segmentIdentifier, s -> new HashMap<>()) - .entrySet() - .stream() - // only return keys equal to or less than - .filter(e -> e.getKey().compareTo(key) <= 0) - .sorted(comparing.reversed()) - .findFirst() - .map(z -> new NearestKeyValue(z.getKey(), z.getValue())); + final Map> segmentMap = + this.hashValueStore.computeIfAbsent(segmentIdentifier, s -> newSegmentMap()); + + final Function>>, Optional> + findNearest = + (predicate) -> { + final Stream>> filteredStream = + segmentMap.entrySet().stream().filter(predicate); + // Depending on the useMin flag, find either the minimum or maximum entry according + // to key order + final Optional>> sortedStream = + useMin + ? filteredStream.min( + (t1, t2) -> compareKeyLeftToRight(t1.getKey(), t2.getKey())) + : filteredStream.max( + (t1, t2) -> compareKeyLeftToRight(t1.getKey(), t2.getKey())); + return sortedStream.map( + entry -> new NearestKeyValue(entry.getKey(), entry.getValue())); + }; + + // First, attempt to find a key-value pair that matches the same prefix + final Optional withSamePrefix = findNearest.apply(samePrefixPredicate); + if (withSamePrefix.isPresent()) { + return withSamePrefix; + } + // If a matching entry with a common prefix is not found, the next step is to search for the + // nearest key that comes after or before the requested one. + return findNearest.apply(fallbackPredicate); } finally { lock.unlock(); } @@ -164,9 +235,10 @@ public Stream> stream(final SegmentIdentifier segmentIdenti lock.lock(); try { return ImmutableSet.copyOf( - hashValueStore.computeIfAbsent(segmentIdentifier, s -> new HashMap<>()).entrySet()) + hashValueStore.computeIfAbsent(segmentIdentifier, s -> newSegmentMap()).entrySet()) .stream() .filter(bytesEntry -> bytesEntry.getValue().isPresent()) + .sorted(Map.Entry.comparingByKey()) .map( bytesEntry -> Pair.of(bytesEntry.getKey().toArrayUnsafe(), bytesEntry.getValue().get())); @@ -199,7 +271,7 @@ public Stream streamKeys(final SegmentIdentifier segmentIdentifier) { lock.lock(); try { return ImmutableMap.copyOf( - hashValueStore.computeIfAbsent(segmentIdentifier, s -> new HashMap<>())) + hashValueStore.computeIfAbsent(segmentIdentifier, s -> newSegmentMap())) .entrySet() .stream() .filter(bytesEntry -> bytesEntry.getValue().isPresent()) @@ -244,8 +316,7 @@ public SegmentedInMemoryKeyValueStorage takeSnapshot() { return new SegmentedInMemoryKeyValueStorage( hashValueStore.entrySet().stream() .collect( - Collectors.toConcurrentMap( - Map.Entry::getKey, e -> new ConcurrentHashMap<>(e.getValue())))); + Collectors.toConcurrentMap(Map.Entry::getKey, e -> newSegmentMap(e.getValue())))); } @Override @@ -258,9 +329,13 @@ public class SegmentedInMemoryTransaction implements SegmentedKeyValueStorageTra /** protected access to updatedValues map for the transaction. */ protected Map>> updatedValues = new HashMap<>(); + /** protected access to deletedValues set for the transaction. */ protected Map> removedKeys = new HashMap<>(); + /** Default constructor */ + public SegmentedInMemoryTransaction() {} + @Override public void put( final SegmentIdentifier segmentIdentifier, final byte[] key, final byte[] value) { @@ -287,7 +362,7 @@ public void commit() throws StorageException { .forEach( entry -> hashValueStore - .computeIfAbsent(entry.getKey(), __ -> new HashMap<>()) + .computeIfAbsent(entry.getKey(), __ -> newSegmentMap()) .putAll(entry.getValue())); removedKeys.entrySet().stream() @@ -295,7 +370,7 @@ public void commit() throws StorageException { entry -> { var keyset = hashValueStore - .computeIfAbsent(entry.getKey(), __ -> new HashMap<>()) + .computeIfAbsent(entry.getKey(), __ -> newSegmentMap()) .keySet(); keyset.removeAll(entry.getValue()); }); diff --git a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedKeyValueStorageAdapter.java b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedKeyValueStorageAdapter.java index 404689be420..28c2d4c7964 100644 --- a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedKeyValueStorageAdapter.java +++ b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedKeyValueStorageAdapter.java @@ -36,6 +36,7 @@ public class SegmentedKeyValueStorageAdapter implements KeyValueStorage { private static final Logger LOG = LoggerFactory.getLogger(SegmentedKeyValueStorageAdapter.class); private final SegmentIdentifier segmentIdentifier; + /** The storage to wrap. */ protected final SegmentedKeyValueStorage storage; diff --git a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedKeyValueStorageTransactionValidatorDecorator.java b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedKeyValueStorageTransactionValidatorDecorator.java index c026cd4efd1..5d026dfc9e1 100644 --- a/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedKeyValueStorageTransactionValidatorDecorator.java +++ b/services/kvstore/src/main/java/org/hyperledger/besu/services/kvstore/SegmentedKeyValueStorageTransactionValidatorDecorator.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.services.kvstore; import static com.google.common.base.Preconditions.checkState; diff --git a/services/kvstore/src/test/java/org/hyperledger/besu/services/kvstore/AbstractSegmentedKeyValueStorageTest.java b/services/kvstore/src/test/java/org/hyperledger/besu/services/kvstore/AbstractSegmentedKeyValueStorageTest.java deleted file mode 100644 index 47aee3ae580..00000000000 --- a/services/kvstore/src/test/java/org/hyperledger/besu/services/kvstore/AbstractSegmentedKeyValueStorageTest.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * - */ -package org.hyperledger.besu.services.kvstore; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage.SEGMENT_IDENTIFIER; - -import org.hyperledger.besu.kvstore.AbstractKeyValueStorageTest; -import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; -import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorageTransaction; - -import java.util.stream.IntStream; - -import org.apache.tuweni.bytes.Bytes; -import org.junit.jupiter.api.Test; - -public abstract class AbstractSegmentedKeyValueStorageTest extends AbstractKeyValueStorageTest { - public abstract SegmentedKeyValueStorage createSegmentedStore(); - - @Test - public void assertSegmentedIsNearestTo() throws Exception { - try (final var store = this.createSegmentedStore()) { - - // create 10 entries - final SegmentedKeyValueStorageTransaction tx = store.startTransaction(); - IntStream.range(1, 10) - .forEach( - i -> { - final byte[] key = bytesFromHexString("000" + i); - final byte[] value = bytesFromHexString("0FFF"); - tx.put(SEGMENT_IDENTIFIER, key, value); - // different common prefix, and reversed order of bytes: - final byte[] key2 = bytesFromHexString("010" + (10 - i)); - final byte[] value2 = bytesFromHexString("0FFF"); - tx.put(SEGMENT_IDENTIFIER, key2, value2); - }); - tx.commit(); - - // assert 0009 is closest to 000F - var val = store.getNearestTo(SEGMENT_IDENTIFIER, Bytes.fromHexString("000F")); - assertThat(val).isPresent(); - assertThat(val.get().key()).isEqualTo(Bytes.fromHexString("0009")); - - // assert 0109 is closest to 010D - var val2 = store.getNearestTo(SEGMENT_IDENTIFIER, Bytes.fromHexString("010D")); - assertThat(val2).isPresent(); - assertThat(val2.get().key()).isEqualTo(Bytes.fromHexString("0109")); - - // assert 0103 is closest to 0103 - var val3 = store.getNearestTo(SEGMENT_IDENTIFIER, Bytes.fromHexString("0103")); - assertThat(val3).isPresent(); - assertThat(val3.get().key()).isEqualTo(Bytes.fromHexString("0103")); - - // assert 0003 is closest to 0003 - var val4 = store.getNearestTo(SEGMENT_IDENTIFIER, Bytes.fromHexString("0003")); - assertThat(val4).isPresent(); - assertThat(val4.get().key()).isEqualTo(Bytes.fromHexString("0003")); - - // assert 0001 is closest to 0001 - var val5 = store.getNearestTo(SEGMENT_IDENTIFIER, Bytes.fromHexString("0001")); - assertThat(val5).isPresent(); - assertThat(val5.get().key()).isEqualTo(Bytes.fromHexString("0001")); - - // assert 0000 is not present - var val6 = store.getNearestTo(SEGMENT_IDENTIFIER, Bytes.fromHexString("0000")); - assertThat(val6).isNotPresent(); - } - } -} diff --git a/services/kvstore/src/test/java/org/hyperledger/besu/services/kvstore/InMemoryKeyValueStorageTest.java b/services/kvstore/src/test/java/org/hyperledger/besu/services/kvstore/InMemoryKeyValueStorageTest.java deleted file mode 100644 index 55a10ffa8d1..00000000000 --- a/services/kvstore/src/test/java/org/hyperledger/besu/services/kvstore/InMemoryKeyValueStorageTest.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright ConsenSys AG. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.services.kvstore; - -import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; -import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; - -public class InMemoryKeyValueStorageTest extends AbstractSegmentedKeyValueStorageTest { - - @Override - protected KeyValueStorage createStore() { - return new InMemoryKeyValueStorage(); - } - - @Override - public SegmentedKeyValueStorage createSegmentedStore() { - return new SegmentedInMemoryKeyValueStorage(); - } -} diff --git a/services/kvstore/src/test/java/org/hyperledger/besu/services/kvstore/KeyComparatorTest.java b/services/kvstore/src/test/java/org/hyperledger/besu/services/kvstore/KeyComparatorTest.java new file mode 100644 index 00000000000..b6f536f8b6c --- /dev/null +++ b/services/kvstore/src/test/java/org/hyperledger/besu/services/kvstore/KeyComparatorTest.java @@ -0,0 +1,63 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.services.kvstore; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; + +public class KeyComparatorTest { + + @Test + public void testEmptyVs01() { + Bytes key1 = Bytes.EMPTY; + Bytes key2 = Bytes.fromHexString("0x01"); + int result = KeyComparator.compareKeyLeftToRight(key1, key2); + assertEquals(-1, result, "Empty key should be considered smaller than 0x01"); + } + + @Test + public void test01Vs02() { + Bytes key1 = Bytes.fromHexString("0x01"); + Bytes key2 = Bytes.fromHexString("0x02"); + int result = KeyComparator.compareKeyLeftToRight(key1, key2); + assertEquals(-1, result, "0x01 should be considered smaller than 0x02"); + } + + @Test + public void test01Vs0100() { + Bytes key1 = Bytes.fromHexString("0x01"); + Bytes key2 = Bytes.fromHexString("0x0100"); + int result = KeyComparator.compareKeyLeftToRight(key1, key2); + assertEquals(-1, result, "0x01 should be considered smaller than 0x0100"); + } + + @Test + public void test01Vs0201() { + Bytes key1 = Bytes.fromHexString("0x01"); + Bytes key2 = Bytes.fromHexString("0x0201"); + int result = KeyComparator.compareKeyLeftToRight(key1, key2); + assertEquals(-1, result, "0x01 should be considered smaller than 0x0201"); + } + + @Test + public void test0101Vs02() { + Bytes key1 = Bytes.fromHexString("0x0101"); + Bytes key2 = Bytes.fromHexString("0x02"); + int result = KeyComparator.compareKeyLeftToRight(key1, key2); + assertEquals(-1, result, "0x0101 should be considered smaller than 0x02"); + } +} diff --git a/services/kvstore/src/test/java/org/hyperledger/besu/services/kvstore/LayeredKeyValueStorageTest.java b/services/kvstore/src/test/java/org/hyperledger/besu/services/kvstore/LayeredKeyValueStorageTest.java deleted file mode 100644 index 2117c1c199b..00000000000 --- a/services/kvstore/src/test/java/org/hyperledger/besu/services/kvstore/LayeredKeyValueStorageTest.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * - */ -package org.hyperledger.besu.services.kvstore; - -import static org.hyperledger.besu.services.kvstore.InMemoryKeyValueStorage.SEGMENT_IDENTIFIER; - -import org.hyperledger.besu.plugin.services.storage.KeyValueStorage; -import org.hyperledger.besu.plugin.services.storage.SegmentedKeyValueStorage; - -public class LayeredKeyValueStorageTest extends AbstractSegmentedKeyValueStorageTest { - @Override - protected KeyValueStorage createStore() { - return new SegmentedKeyValueStorageAdapter(SEGMENT_IDENTIFIER, createSegmentedStore()); - } - - @Override - public SegmentedKeyValueStorage createSegmentedStore() { - return new LayeredKeyValueStorage(new SegmentedInMemoryKeyValueStorage()); - } -} diff --git a/services/pipeline/build.gradle b/services/pipeline/build.gradle index b86edbf344b..af9f065feda 100644 --- a/services/pipeline/build.gradle +++ b/services/pipeline/build.gradle @@ -22,7 +22,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } diff --git a/services/pipeline/src/main/java/org/hyperledger/besu/services/pipeline/AsyncOperationProcessor.java b/services/pipeline/src/main/java/org/hyperledger/besu/services/pipeline/AsyncOperationProcessor.java index f64454fa9f1..e17296c026d 100644 --- a/services/pipeline/src/main/java/org/hyperledger/besu/services/pipeline/AsyncOperationProcessor.java +++ b/services/pipeline/src/main/java/org/hyperledger/besu/services/pipeline/AsyncOperationProcessor.java @@ -83,7 +83,11 @@ private void outputNextCompletedTask(final WritePipe outputPipe) { waitForAnyFutureToComplete(); outputCompletedTasks(outputPipe); } catch (final InterruptedException e) { - LOG.trace("Interrupted while waiting for processing to complete", e); + LOG.atTrace() + .setMessage("Interrupted while waiting for processing to complete: Message=({})") + .addArgument(e.getMessage()) + .setCause(e) + .log(); } catch (final ExecutionException e) { throw new AsyncOperationException("Async operation failed. " + e.getMessage(), e); } catch (final TimeoutException e) { diff --git a/services/pipeline/src/main/java/org/hyperledger/besu/services/pipeline/Pipe.java b/services/pipeline/src/main/java/org/hyperledger/besu/services/pipeline/Pipe.java index 3697e0e3ba2..63fb01bad13 100644 --- a/services/pipeline/src/main/java/org/hyperledger/besu/services/pipeline/Pipe.java +++ b/services/pipeline/src/main/java/org/hyperledger/besu/services/pipeline/Pipe.java @@ -44,6 +44,7 @@ public class Pipe implements ReadPipe, WritePipe { private final Counter abortedItemCounter; private final AtomicBoolean closed = new AtomicBoolean(); private final AtomicBoolean aborted = new AtomicBoolean(); + private String pipeName = ""; /** * Instantiates a new Pipe. @@ -52,16 +53,28 @@ public class Pipe implements ReadPipe, WritePipe { * @param inputCounter the input counter * @param outputCounter the output counter * @param abortedItemCounter the aborted item counter + * @param pipeName the name of the pipe */ public Pipe( final int capacity, final Counter inputCounter, final Counter outputCounter, - final Counter abortedItemCounter) { + final Counter abortedItemCounter, + final String pipeName) { queue = new ArrayBlockingQueue<>(capacity); this.inputCounter = inputCounter; this.outputCounter = outputCounter; this.abortedItemCounter = abortedItemCounter; + this.pipeName = pipeName; + } + + /** + * Get the name of this pipe + * + * @return the name of the pipe + */ + public String getPipeName() { + return pipeName; } @Override @@ -110,7 +123,7 @@ public T get() { } } } catch (final InterruptedException e) { - LOG.trace("Interrupted while waiting for next item", e); + LOG.trace("Interrupted while waiting for next item from pipe {}", pipeName); } return null; } @@ -140,7 +153,7 @@ public void put(final T value) { return; } } catch (final InterruptedException e) { - LOG.trace("Interrupted while waiting to add to output", e); + LOG.trace("Interrupted while waiting to add to output to pipe {}", pipeName); } } } diff --git a/services/pipeline/src/main/java/org/hyperledger/besu/services/pipeline/Pipeline.java b/services/pipeline/src/main/java/org/hyperledger/besu/services/pipeline/Pipeline.java index 1115455b1ec..507e87a8c0c 100644 --- a/services/pipeline/src/main/java/org/hyperledger/besu/services/pipeline/Pipeline.java +++ b/services/pipeline/src/main/java/org/hyperledger/besu/services/pipeline/Pipeline.java @@ -18,6 +18,7 @@ import org.hyperledger.besu.services.pipeline.exception.AsyncOperationException; import org.hyperledger.besu.util.ExceptionUtils; +import org.hyperledger.besu.util.log.LogUtil; import java.util.Collection; import java.util.List; @@ -88,6 +89,19 @@ public class Pipeline { this.stages = stages; this.pipes = pipes; this.completerStage = completerStage; + + if (LOG.isTraceEnabled()) { + StringBuilder sb = new StringBuilder(); + sb.append("Building pipeline "); + sb.append(name); + sb.append(". Stages: "); + for (Stage nextStage : stages) { + sb.append(nextStage.getName()); + sb.append(" -> "); + } + sb.append("END"); + LOG.trace("{}", sb.toString()); + } } /** @@ -174,15 +188,18 @@ private Future runWithErrorHandling(final ExecutorService executorService, fi if (t instanceof CompletionException || t instanceof CancellationException || t instanceof AsyncOperationException) { - LOG.debug("Unhandled exception in pipeline. Aborting.", t); + LOG.trace("Unhandled exception in pipeline. Aborting.", t); } else { - LOG.info("Unexpected exception in pipeline. Aborting.", t); + LOG.info( + LogUtil.summarizeBesuStackTrace( + "Unexpected exception in pipeline. Aborting.", t)); + LOG.debug("Unexpected exception in pipeline. Aborting.", t); } try { abort(t); } catch (final Throwable t2) { // Seems excessive but exceptions that propagate out of this method won't be logged - // because the executor just completes the future exceptionally and we never + // because the executor just completes the future exceptionally, and we never // need to call get on it which would normally expose the error. LOG.error("Failed to abort pipeline after error", t2); } diff --git a/services/pipeline/src/main/java/org/hyperledger/besu/services/pipeline/PipelineBuilder.java b/services/pipeline/src/main/java/org/hyperledger/besu/services/pipeline/PipelineBuilder.java index 3bc6656e700..aaf68f5e3f1 100644 --- a/services/pipeline/src/main/java/org/hyperledger/besu/services/pipeline/PipelineBuilder.java +++ b/services/pipeline/src/main/java/org/hyperledger/besu/services/pipeline/PipelineBuilder.java @@ -421,6 +421,7 @@ private static Pipe createPipe( newBufferSize, outputCounter.labels(labelName, "added"), outputCounter.labels(labelName, "removed"), - outputCounter.labels(labelName, "aborted")); + outputCounter.labels(labelName, "aborted"), + stageName); } } diff --git a/services/pipeline/src/main/java/org/hyperledger/besu/services/pipeline/exception/AsyncOperationException.java b/services/pipeline/src/main/java/org/hyperledger/besu/services/pipeline/exception/AsyncOperationException.java index dbcaf4426e9..1b1dda97a3e 100644 --- a/services/pipeline/src/main/java/org/hyperledger/besu/services/pipeline/exception/AsyncOperationException.java +++ b/services/pipeline/src/main/java/org/hyperledger/besu/services/pipeline/exception/AsyncOperationException.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/BatchingReadPipeTest.java b/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/BatchingReadPipeTest.java index fb90128fbb3..da0908e8148 100644 --- a/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/BatchingReadPipeTest.java +++ b/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/BatchingReadPipeTest.java @@ -31,7 +31,8 @@ public class BatchingReadPipeTest { - private final Pipe source = new Pipe<>(10, NO_OP_COUNTER, NO_OP_COUNTER, NO_OP_COUNTER); + private final Pipe source = + new Pipe<>(10, NO_OP_COUNTER, NO_OP_COUNTER, NO_OP_COUNTER, "source_pipe"); private final Counter batchCounter = mock(Counter.class); private final BatchingReadPipe batchingPipe = new BatchingReadPipe<>(source, 3, batchCounter); diff --git a/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/CompleterStageTest.java b/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/CompleterStageTest.java index ef3e29afebe..ea4a81a9d98 100644 --- a/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/CompleterStageTest.java +++ b/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/CompleterStageTest.java @@ -24,7 +24,8 @@ public class CompleterStageTest { - private final Pipe pipe = new Pipe<>(10, NO_OP_COUNTER, NO_OP_COUNTER, NO_OP_COUNTER); + private final Pipe pipe = + new Pipe<>(10, NO_OP_COUNTER, NO_OP_COUNTER, NO_OP_COUNTER, "test_pipe"); private final List output = new ArrayList<>(); private final CompleterStage stage = new CompleterStage<>("name", pipe, output::add); diff --git a/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/FlatMapProcessorTest.java b/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/FlatMapProcessorTest.java index 1012d3b0123..4e3b8cfa513 100644 --- a/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/FlatMapProcessorTest.java +++ b/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/FlatMapProcessorTest.java @@ -28,8 +28,10 @@ public class FlatMapProcessorTest { - private final Pipe input = new Pipe<>(10, NO_OP_COUNTER, NO_OP_COUNTER, NO_OP_COUNTER); - private final Pipe output = new Pipe<>(10, NO_OP_COUNTER, NO_OP_COUNTER, NO_OP_COUNTER); + private final Pipe input = + new Pipe<>(10, NO_OP_COUNTER, NO_OP_COUNTER, NO_OP_COUNTER, "input_pipe"); + private final Pipe output = + new Pipe<>(10, NO_OP_COUNTER, NO_OP_COUNTER, NO_OP_COUNTER, "output_pipe"); @SuppressWarnings("unchecked") private final Function> mapper = mock(Function.class); diff --git a/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/IteratorSourceStageTest.java b/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/IteratorSourceStageTest.java index 02052615b75..915a90b1fd3 100644 --- a/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/IteratorSourceStageTest.java +++ b/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/IteratorSourceStageTest.java @@ -22,7 +22,8 @@ public class IteratorSourceStageTest { - private final Pipe output = new Pipe<>(10, NO_OP_COUNTER, NO_OP_COUNTER, NO_OP_COUNTER); + private final Pipe output = + new Pipe<>(10, NO_OP_COUNTER, NO_OP_COUNTER, NO_OP_COUNTER, "output_pipe"); private final IteratorSourceStage stage = new IteratorSourceStage<>("name", Iterators.forArray("a", "b", "c", "d"), output); diff --git a/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/MapProcessorTest.java b/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/MapProcessorTest.java index 0d7a913c3e3..7698e8d3279 100644 --- a/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/MapProcessorTest.java +++ b/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/MapProcessorTest.java @@ -27,8 +27,10 @@ public class MapProcessorTest { - private final Pipe input = new Pipe<>(10, NO_OP_COUNTER, NO_OP_COUNTER, NO_OP_COUNTER); - private final Pipe output = new Pipe<>(10, NO_OP_COUNTER, NO_OP_COUNTER, NO_OP_COUNTER); + private final Pipe input = + new Pipe<>(10, NO_OP_COUNTER, NO_OP_COUNTER, NO_OP_COUNTER, "intput_pipe"); + private final Pipe output = + new Pipe<>(10, NO_OP_COUNTER, NO_OP_COUNTER, NO_OP_COUNTER, "output_pipe"); @SuppressWarnings("unchecked") private final Function processor = mock(Function.class); diff --git a/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/PipeTest.java b/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/PipeTest.java index c838bc887a3..c4a7a88b527 100644 --- a/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/PipeTest.java +++ b/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/PipeTest.java @@ -30,7 +30,8 @@ public class PipeTest { private final Counter inputCounter = mock(Counter.class); private final Counter outputCounter = mock(Counter.class); private final Counter abortedItemCounter = mock(Counter.class); - private final Pipe pipe = new Pipe<>(5, inputCounter, outputCounter, abortedItemCounter); + private final Pipe pipe = + new Pipe<>(5, inputCounter, outputCounter, abortedItemCounter, "test_pipe"); @Test public void shouldNotHaveMoreWhenEmptyAndClosed() { diff --git a/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/ProcessingStageTest.java b/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/ProcessingStageTest.java index efbe16da668..a07530f5497 100644 --- a/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/ProcessingStageTest.java +++ b/services/pipeline/src/test/java/org/hyperledger/besu/services/pipeline/ProcessingStageTest.java @@ -34,9 +34,9 @@ public class ProcessingStageTest { private final Pipe inputPipe = - new Pipe<>(10, NO_OP_COUNTER, NO_OP_COUNTER, NO_OP_COUNTER); + new Pipe<>(10, NO_OP_COUNTER, NO_OP_COUNTER, NO_OP_COUNTER, "input_pipe"); private final Pipe outputPipe = - new Pipe<>(10, NO_OP_COUNTER, NO_OP_COUNTER, NO_OP_COUNTER); + new Pipe<>(10, NO_OP_COUNTER, NO_OP_COUNTER, NO_OP_COUNTER, "output_pipe"); @Mock private Processor singleStep; private ProcessingStage stage; diff --git a/services/tasks/build.gradle b/services/tasks/build.gradle index 5cfa2411f39..c584c6987b3 100644 --- a/services/tasks/build.gradle +++ b/services/tasks/build.gradle @@ -22,7 +22,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } diff --git a/services/tasks/src/main/java/org/hyperledger/besu/services/tasks/CachingTaskCollection.java b/services/tasks/src/main/java/org/hyperledger/besu/services/tasks/CachingTaskCollection.java index 586ab62a2ae..106d818c39d 100644 --- a/services/tasks/src/main/java/org/hyperledger/besu/services/tasks/CachingTaskCollection.java +++ b/services/tasks/src/main/java/org/hyperledger/besu/services/tasks/CachingTaskCollection.java @@ -34,10 +34,12 @@ public class CachingTaskCollection implements TaskCollection { // The underlying collection private final TaskCollection wrappedCollection; + /** * A cache of tasks to operate on before going to {@link CachingTaskCollection#wrappedCollection} */ private final Queue> cache = new ArrayDeque<>(); + // Tasks that have been removed, but not marked completed yet private final Set> outstandingTasks = new HashSet<>(); diff --git a/services/tasks/src/main/java/org/hyperledger/besu/services/tasks/InMemoryTaskQueue.java b/services/tasks/src/main/java/org/hyperledger/besu/services/tasks/InMemoryTaskQueue.java index 0d743cac24e..fa125be7387 100644 --- a/services/tasks/src/main/java/org/hyperledger/besu/services/tasks/InMemoryTaskQueue.java +++ b/services/tasks/src/main/java/org/hyperledger/besu/services/tasks/InMemoryTaskQueue.java @@ -32,6 +32,9 @@ public class InMemoryTaskQueue implements TaskCollection { private final Set> unfinishedOutstandingTasks = new HashSet<>(); private final AtomicBoolean closed = new AtomicBoolean(false); + /** Default constructor. */ + public InMemoryTaskQueue() {} + @Override public synchronized void add(final T taskData) { assertNotClosed(); diff --git a/services/tasks/src/main/java/org/hyperledger/besu/services/tasks/TasksPriorityProvider.java b/services/tasks/src/main/java/org/hyperledger/besu/services/tasks/TasksPriorityProvider.java index 95153058a93..41adf96cd64 100644 --- a/services/tasks/src/main/java/org/hyperledger/besu/services/tasks/TasksPriorityProvider.java +++ b/services/tasks/src/main/java/org/hyperledger/besu/services/tasks/TasksPriorityProvider.java @@ -1,20 +1,17 @@ /* + * Copyright contributors to Hyperledger Besu. * - * * Copyright Hyperledger Besu Contributors. - * * - * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * * the License. You may obtain a copy of the License at - * * - * * http://www.apache.org/licenses/LICENSE-2.0 - * * - * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * * specific language governing permissions and limitations under the License. - * * - * * SPDX-License-Identifier: Apache-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.services.tasks; /** The interface Tasks priority provider. */ diff --git a/settings.gradle b/settings.gradle index 3a1c245a7f6..322383b32ca 100644 --- a/settings.gradle +++ b/settings.gradle @@ -16,19 +16,11 @@ pluginManagement { repositories { - /* - * Temporary repository to host the improved version of the - * com.github.hierynomus.license plugin. Can be removed when an - * official version with the fix is release upstream - */ + gradlePluginPortal() maven { - url = uri('https://raw.githubusercontent.com/ConsenSys/license-gradle-plugin-fix-artifacts/main/') - content { - includeGroup('com.github.hierynomus.license') - includeGroup('com.hierynomus.gradle.plugins') - } + url 'https://artifacts.consensys.net/public/maven/maven/' + content { includeGroupByRegex('tech\\.pegasys(\\..*)?') } } - gradlePluginPortal() } } @@ -36,6 +28,7 @@ rootProject.name='besu' include 'acceptance-tests:test-plugins' include 'acceptance-tests:dsl' include 'acceptance-tests:tests' +include 'acceptance-tests:tests:shanghai' include 'besu' include 'config' include 'consensus:clique' @@ -49,7 +42,6 @@ include 'crypto:services' include 'datatypes' include 'enclave' include 'evm' -include 'errorprone-checks' include 'ethereum:api' include 'ethereum:blockcreation' include 'ethereum:core' @@ -76,5 +68,6 @@ include 'privacy-contracts' include 'services:kvstore' include 'services:pipeline' include 'services:tasks' +include 'testfuzz' include 'testutil' include 'util' diff --git a/testfuzz/README.md b/testfuzz/README.md new file mode 100644 index 00000000000..8f436d70117 --- /dev/null +++ b/testfuzz/README.md @@ -0,0 +1,29 @@ +# BesuFuzz + +BesuFuzz is where all the besu guided fuzzing tools live. + +## eof-container + +Performs differential fuzzing between Ethereum clients based on +the [txparse eofparse](https://github.com/holiman/txparse/blob/main/README.md#eof-parser-eofparse) +format. Note that only the inital `OK` and `err` values are used to determine if +there is a difference. + +### Prototypical CLI Usage: + +```shell +BesuFuzz eof-container \ + --tests-dir=~/git/ethereum/tests/EOFTests \ + --client=evm1=evmone-eofparse \ + --client=revm=revme bytecode +``` + +### Prototypical Gradle usage: + +```shell +./gradlew fuzzEvmone fuzzReth +``` + +There are pre-written Gradle targets for `fuzzEthereumJS`, `fuzzEvmone`, +`fuzzGeth`, `fuzzNethermind`, and `fuzzReth`. Besu is always a fuzzing target. +The `fuzzAll` target will fuzz all clients. \ No newline at end of file diff --git a/testfuzz/build.gradle b/testfuzz/build.gradle new file mode 100644 index 00000000000..3346f3e56db --- /dev/null +++ b/testfuzz/build.gradle @@ -0,0 +1,168 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'application' +apply plugin: 'java-library' +apply plugin: 'jacoco' + +jar { + archiveBaseName = 'besu-test-fuzz' + manifest { + attributes( + 'Specification-Title': archiveBaseName, + 'Specification-Version': project.version, + 'Implementation-Title': archiveBaseName, + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash + ) + } +} + +dependencies { + implementation project(':besu') + implementation project(':crypto:algorithms') + implementation project(':datatypes') + implementation project(':ethereum:referencetests') + implementation project(':evm') + implementation project(':util') + + implementation 'com.fasterxml.jackson.core:jackson-databind' + implementation 'com.google.guava:guava' + implementation 'info.picocli:picocli' + implementation 'io.tmio:tuweni-bytes' + implementation 'org.jacoco:org.jacoco.agent' + implementation 'org.jacoco:org.jacoco.core' +} + +application { + applicationName = 'BesuFuzz' + mainClass = 'org.hyperledger.besu.testfuzz.BesuFuzz' + applicationDefaultJvmArgs = [ + '-javaagent:$APP_HOME/lib/jacocoagent.jar' + ] +} + +def corpusDir = "${buildDir}/generated/corpus" + +tasks.register("runFuzzer", JavaExec) { + doNotTrackState("Produces no artifacts") + classpath = sourceSets.main.runtimeClasspath + mainClass = 'org.hyperledger.besu.testfuzz.BesuFuzz' + + args = [ + "eof-container", + "--tests-dir=${projectDir}/../ethereum/referencetests/src/reference-test/external-resources/EOFTests", + "--corpus-dir=${corpusDir}" + ] + doFirst { + mkdir corpusDir + } +} + +// Adds guidance to the fuzzer but with a 90% performance drop. +tasks.register("fuzzGuided") { + doLast { + runFuzzer.args += "--guidance-regexp=org/(hyperledger/besu|apache/tuweni)" + runFuzzer.args += "--new-corpus-dir=${corpusDir}/.." + } + finalizedBy("runFuzzer") +} + +// This fuzzes besu as an external client. Besu fuzzing as a local client is enabled by default. +tasks.register("fuzzBesu") { + dependsOn(":installDist") + doLast { + runFuzzer.args += "--client=besu=../build/install/besu/bin/evmtool code-validate" + } + finalizedBy("runFuzzer") +} + +tasks.register("fuzzEvmone") { + doLast { + runFuzzer.args += "--client=evm1=evmone-eofparse" + } + finalizedBy("runFuzzer") +} + +tasks.register("fuzzEthereumJS") { + doLast { + runFuzzer.args += "--client=etjs=tsx ../../../ethereumjs/ethereumjs-monorepo/packages/evm/scripts/eofContainerValidator.ts" + } + finalizedBy("runFuzzer") +} + +tasks.register("fuzzGeth") { + doLast { + runFuzzer.args += "--client=geth=eofdump eofparser" + } + finalizedBy("runFuzzer") +} + +tasks.register("fuzzNethermind") { + doLast { + runFuzzer.args += "--client=neth=netheofparse -x" + } + finalizedBy("runFuzzer") +} + +tasks.register("fuzzReth") { + doLast { + runFuzzer.args += "--client=revm=revme bytecode --eof-runtime" + } + finalizedBy("runFuzzer") +} + +tasks.register("fuzzAll") { + dependsOn fuzzEvmone, fuzzEthereumJS, fuzzGeth, fuzzNethermind, fuzzReth +} + +jacoco { + applyTo run + applyTo runFuzzer +} + +// Copies jacoco into the lib directory +tasks.register("copyJacoco", Copy) { + // The jacocoagent.jar is embedded within the jar + from zipTree(configurations.jacocoAgent.singleFile).filter { it.name == 'jacocoagent.jar' }.singleFile + into layout.buildDirectory.dir("install/${application.applicationName}/lib") +} + +installDist.finalizedBy copyJacoco + +startScripts { + defaultJvmOpts = [ + "-Dsecp256k1.randomize=false" + ] + unixStartScriptGenerator.template = resources.text.fromFile("${projectDir}/src/main/scripts/unixStartScript.txt") + windowsStartScriptGenerator.template = resources.text.fromFile("${projectDir}/src/main/scripts/windowsStartScript.txt") + doLast { tweakStartScript(startScripts) } +} + +static def tweakStartScript(createScriptTask) { + def shortenWindowsClasspath = { line -> + line.replaceAll(/^set CLASSPATH=.*$/, "set CLASSPATH=%APP_HOME%/lib/*") + } + + createScriptTask.unixScript.text = createScriptTask.unixScript.text.replace('BESU_HOME', '\$APP_HOME') + createScriptTask.windowsScript.text = createScriptTask.windowsScript.text.replace('BESU_HOME', '%~dp0..') + + // Prevent the error originating from the 8191 chars limit on Windows + createScriptTask.windowsScript.text = + createScriptTask.windowsScript + .readLines() + .collect(shortenWindowsClasspath) + .join('\r\n') +} diff --git a/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/BesuFuzz.java b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/BesuFuzz.java new file mode 100644 index 00000000000..6cde7588045 --- /dev/null +++ b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/BesuFuzz.java @@ -0,0 +1,38 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.testfuzz; + +import org.hyperledger.besu.util.LogConfigurator; + +/** The main entry point for the EVM (Ethereum Virtual Machine) tool. */ +public final class BesuFuzz { + + /** Default constructor for the EvmTool class. */ + public BesuFuzz() { + // this is here only for Javadoc linting + } + + /** + * The main entry point for the EVM (Ethereum Virtual Machine) tool. + * + * @param args The command line arguments. + */ + public static void main(final String... args) { + LogConfigurator.setLevel("", "DEBUG"); + final BesuFuzzCommand besuFuzzCommand = new BesuFuzzCommand(); + + besuFuzzCommand.execute(args); + } +} diff --git a/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/BesuFuzzCommand.java b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/BesuFuzzCommand.java new file mode 100644 index 00000000000..22165b5ffe8 --- /dev/null +++ b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/BesuFuzzCommand.java @@ -0,0 +1,78 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.testfuzz; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import org.hyperledger.besu.util.LogConfigurator; + +import java.io.InputStream; +import java.io.PrintWriter; + +import picocli.CommandLine; +import picocli.CommandLine.Command; + +/** + * This is the root command for the `BesuFuzz` command line tool. It is a collection of fuzzers that + * are guided by Besu's implementations. + */ +@Command( + description = "Executes Besu based fuzz tests", + abbreviateSynopsis = true, + name = "evm", + mixinStandardHelpOptions = true, + versionProvider = VersionProvider.class, + sortOptions = false, + header = "Usage:", + synopsisHeading = "%n", + descriptionHeading = "%nDescription:%n%n", + optionListHeading = "%nOptions:%n", + footerHeading = "%n", + footer = "Hyperledger Besu is licensed under the Apache License 2.0", + subcommands = {EofContainerSubCommand.class}) +@SuppressWarnings("java:S106") +public class BesuFuzzCommand implements Runnable { + + PrintWriter out; + InputStream in; + + /** Default Constructor */ + BesuFuzzCommand() { + // this method is here only for JavaDoc linting + } + + void execute(final String... args) { + execute(System.in, new PrintWriter(System.out, true, UTF_8), args); + } + + void execute(final InputStream input, final PrintWriter output, final String[] args) { + final CommandLine commandLine = new CommandLine(this).setOut(output); + out = output; + in = input; + + // don't require exact case to match enum values + commandLine.setCaseInsensitiveEnumValuesAllowed(true); + + commandLine.setExecutionStrategy(new CommandLine.RunLast()); + commandLine.execute(args); + } + + @Override + public void run() { + LogConfigurator.setLevel("", "OFF"); + System.out.println("No default command, please select a subcommand"); + System.exit(1); + } +} diff --git a/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/EofContainerSubCommand.java b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/EofContainerSubCommand.java new file mode 100644 index 00000000000..27c4547d268 --- /dev/null +++ b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/EofContainerSubCommand.java @@ -0,0 +1,292 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.testfuzz; + +import static org.hyperledger.besu.testfuzz.EofContainerSubCommand.COMMAND_NAME; + +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.Hash; +import org.hyperledger.besu.ethereum.referencetests.EOFTestCaseSpec; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.MainnetEVMs; +import org.hyperledger.besu.evm.internal.EvmConfiguration; +import org.hyperledger.besu.testfuzz.javafuzz.Fuzzer; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.core.JsonParser.Feature; +import com.fasterxml.jackson.core.util.DefaultIndenter; +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import com.fasterxml.jackson.core.util.Separators; +import com.fasterxml.jackson.core.util.Separators.Spacing; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.google.common.base.Stopwatch; +import org.apache.tuweni.bytes.Bytes; +import picocli.CommandLine; +import picocli.CommandLine.Option; + +/** Fuzzes the parsing and validation of an EOF container. */ +@SuppressWarnings({"java:S106", "CallToPrintStackTrace"}) // we use lots the console, on purpose +@CommandLine.Command( + name = COMMAND_NAME, + description = "Fuzzes EOF container parsing and validation", + mixinStandardHelpOptions = true, + versionProvider = VersionProvider.class) +public class EofContainerSubCommand implements Runnable { + + static final String COMMAND_NAME = "eof-container"; + + @Option( + names = {"--corpus-dir"}, + paramLabel = "", + description = "Directory to store corpus files") + private final Path corpusDir = Path.of("corpus"); + + @Option( + names = {"--tests-dir"}, + paramLabel = "", + description = "Directory where EOF tests references file tree lives") + private final Path testsDir = null; + + @Option( + names = {"--client"}, + paramLabel = "=", + description = "Add a client for differential fuzzing") + private final Map clients = new LinkedHashMap<>(); + + @Option( + names = {"--no-local-client"}, + description = "Don't include built-in Besu with fuzzing") + private final Boolean noLocalClient = false; + + @Option( + names = {"--time-limit-ns"}, + defaultValue = "5000", + description = "Time threshold, in nanoseconds, that results in a fuzz error if exceeded") + private long timeThresholdMicros = 5_000; + + @Option( + names = {"--time-limit-warmup"}, + defaultValue = "2000", + description = "Minimum number of fuzz tests before a time limit fuzz error can occur") + private long timeThresholdIterations = 2_000; + + @Option( + names = {"--guidance-regexp"}, + description = "Regexp for classes that matter for guidance metric") + private String guidanceRegexp; + + @Option( + names = {"--new-corpus-dir"}, + description = "Directory to write hex versions of guidance added contracts") + private File newCorpusDir = null; + + @CommandLine.ParentCommand private final BesuFuzzCommand parentCommand; + + static final ObjectMapper eofTestMapper = createObjectMapper(); + static final JavaType javaType = + eofTestMapper + .getTypeFactory() + .constructParametricType(Map.class, String.class, EOFTestCaseSpec.class); + + List fuzzingClients = new ArrayList<>(); + EVM evm = MainnetEVMs.pragueEOF(EvmConfiguration.DEFAULT); + long validContainers; + long totalContainers; + + /** + * Default constructor for the EofContainerSubCommand class. This constructor initializes the + * parentCommand to null. + */ + public EofContainerSubCommand() { + this(null); + } + + /** + * Constructs a new EofContainerSubCommand with the specified parent command. + * + * @param parentCommand The parent command for this subcommand. + */ + public EofContainerSubCommand(final BesuFuzzCommand parentCommand) { + this.parentCommand = parentCommand; + } + + private static ObjectMapper createObjectMapper() { + final ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.setDefaultPrettyPrinter( + (new DefaultPrettyPrinter()) + .withSeparators( + Separators.createDefaultInstance().withObjectFieldValueSpacing(Spacing.BOTH)) + .withObjectIndenter(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE.withIndent(" ")) + .withArrayIndenter(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE.withIndent(" "))); + objectMapper.disable(Feature.AUTO_CLOSE_SOURCE); + SimpleModule serializers = new SimpleModule("Serializers"); + serializers.addSerializer(Address.class, ToStringSerializer.instance); + serializers.addSerializer(Bytes.class, ToStringSerializer.instance); + objectMapper.registerModule(serializers); + + return objectMapper; + } + + @Override + public void run() { + // load test dir into corpus dir + if (testsDir != null) { + File f = testsDir.toFile(); + if (f.isDirectory()) { + try (var files = Files.walk(f.toPath(), Integer.MAX_VALUE)) { + files.forEach( + ff -> { + File file = ff.toFile(); + if (file.isFile()) { + extractFile(file, corpusDir.toFile()); + } + }); + } catch (IOException e) { + parentCommand.out.println("Exception walking " + f + ": " + e.getMessage()); + } + } + } + + if (!noLocalClient) { + fuzzingClients.add(new InternalClient("this")); + } + clients.forEach((k, v) -> fuzzingClients.add(new StreamingClient(k, v.split(" ")))); + System.out.println("Fuzzing client set: " + clients.keySet()); + + try { + new Fuzzer( + this::parseEOFContainers, + corpusDir.toString(), + this::fuzzStats, + guidanceRegexp, + newCorpusDir) + .start(); + } catch (NoSuchAlgorithmException + | ClassNotFoundException + | InvocationTargetException + | IllegalAccessException + | NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + private void extractFile(final File f, final File initialCorpus) { + final Map eofTests; + try { + eofTests = eofTestMapper.readValue(f, javaType); + } catch (IOException e) { + // presume parse failed because it's a corpus file + return; + } + for (var entry : eofTests.entrySet()) { + int index = 0; + for (var vector : entry.getValue().getVector().entrySet()) { + try (FileOutputStream fos = + new FileOutputStream( + new File( + initialCorpus, + f.toPath().getFileName() + "_" + (index++) + "_" + vector.getKey()))) { + Bytes codeBytes = Bytes.fromHexString(vector.getValue().code()); + evm.getCodeUncached(codeBytes); + fos.write(codeBytes.toArrayUnsafe()); + } catch (IOException e) { + parentCommand.out.println("Invalid file " + f + ": " + e.getMessage()); + e.printStackTrace(); + System.exit(1); + } + } + } + } + + void parseEOFContainers(final byte[] bytes) { + Bytes eofUnderTest = Bytes.wrap(bytes); + String eofUnderTestHexString = eofUnderTest.toHexString(); + + AtomicBoolean passHappened = new AtomicBoolean(false); + AtomicBoolean failHappened = new AtomicBoolean(false); + + Map resultMap = + fuzzingClients.stream() + .parallel() + .map( + client -> { + Stopwatch stopwatch = Stopwatch.createStarted(); + String value = client.differentialFuzz(eofUnderTestHexString); + stopwatch.stop(); + long elapsedMicros = stopwatch.elapsed(TimeUnit.MICROSECONDS); + if (elapsedMicros > timeThresholdMicros + && totalContainers > timeThresholdIterations) { + Hash name = Hash.hash(eofUnderTest); + parentCommand.out.printf( + "%s: slow validation %d µs%n", client.getName(), elapsedMicros); + try { + Files.writeString( + Path.of("slow-" + name + "-" + client.getName() + ".hex"), + eofUnderTestHexString); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + if (value.toLowerCase(Locale.ROOT).startsWith("ok")) { + passHappened.set(true); + } else if (value.toLowerCase(Locale.ROOT).startsWith("err")) { + failHappened.set(true); + } else { + // unexpected output: trigger a mismatch + passHappened.set(true); + failHappened.set(true); + } + return Map.entry(client.getName(), value); + }) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + if (passHappened.get() && failHappened.get()) { + for (var entry : resultMap.entrySet()) { + parentCommand.out.println(entry.getKey() + ": " + entry.getValue()); + } + parentCommand.out.println("code: " + eofUnderTest.toUnprefixedHexString()); + parentCommand.out.println("size: " + eofUnderTest.size()); + parentCommand.out.println(); + } else { + if (passHappened.get()) { + validContainers++; + } + totalContainers++; + } + } + + String fuzzStats() { + return " / %5.2f%% valid %d/%d" + .formatted((100.0 * validContainers) / totalContainers, validContainers, totalContainers); + } +} diff --git a/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/FuzzingClient.java b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/FuzzingClient.java new file mode 100644 index 00000000000..441228baf95 --- /dev/null +++ b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/FuzzingClient.java @@ -0,0 +1,22 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.testfuzz; + +interface FuzzingClient { + + String getName(); + + String differentialFuzz(String data); +} diff --git a/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/InternalClient.java b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/InternalClient.java new file mode 100644 index 00000000000..a6d7035fe80 --- /dev/null +++ b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/InternalClient.java @@ -0,0 +1,65 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.testfuzz; + +import org.hyperledger.besu.evm.Code; +import org.hyperledger.besu.evm.EVM; +import org.hyperledger.besu.evm.MainnetEVMs; +import org.hyperledger.besu.evm.code.CodeInvalid; +import org.hyperledger.besu.evm.code.CodeV1; +import org.hyperledger.besu.evm.code.EOFLayout; +import org.hyperledger.besu.evm.code.EOFLayout.EOFContainerMode; +import org.hyperledger.besu.evm.internal.EvmConfiguration; + +import org.apache.tuweni.bytes.Bytes; + +class InternalClient implements FuzzingClient { + String name; + final EVM evm; + + public InternalClient(final String clientName) { + this.name = clientName; + this.evm = MainnetEVMs.pragueEOF(EvmConfiguration.DEFAULT); + } + + @Override + public String getName() { + return name; + } + + @Override + @SuppressWarnings("java:S2142") + public String differentialFuzz(final String data) { + try { + Bytes clientData = Bytes.fromHexString(data); + Code code = evm.getCodeUncached(clientData); + if (code.getEofVersion() < 1) { + return "err: legacy EVM"; + } else if (!code.isValid()) { + return "err: " + ((CodeInvalid) code).getInvalidReason(); + } else { + EOFLayout layout = ((CodeV1) code).getEofLayout(); + if (EOFContainerMode.INITCODE.equals(layout.containerMode().get())) { + return "err: initcode container when runtime mode expected"; + } + return "OK %d/%d/%d" + .formatted( + layout.getCodeSectionCount(), layout.getSubcontainerCount(), layout.dataLength()); + } + } catch (RuntimeException e) { + return "fail: " + e.getMessage(); + } + } +} diff --git a/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/SingleQueryClient.java b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/SingleQueryClient.java new file mode 100644 index 00000000000..716d9fe250e --- /dev/null +++ b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/SingleQueryClient.java @@ -0,0 +1,89 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.testfuzz; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@SuppressWarnings({"java:S106", "CallToPrintStackTrace"}) // we use lots the console, on purpose +class SingleQueryClient implements FuzzingClient { + final String name; + String[] command; + Pattern okRegexp; + String okRegexpStr; + int okGroup; + Pattern failRegexp; + int failGroup; + String failRegexpStr; + + public SingleQueryClient( + final String clientName, + final String okRegexp, + final int okGroup, + final String errorRegexp, + final int failGroup, + final String... command) { + this.name = clientName; + this.okRegexp = Pattern.compile(okRegexp); + this.okRegexpStr = okRegexp; + this.okGroup = okGroup; + this.failRegexp = Pattern.compile(errorRegexp); + this.failGroup = failGroup; + this.failRegexpStr = errorRegexp; + this.command = command; + } + + @Override + public String getName() { + return name; + } + + @Override + @SuppressWarnings("java:S2142") + public String differentialFuzz(final String data) { + if (!data.startsWith("0xef")) { + return "err: invalid_magic"; + } + try { + List localCommand = new ArrayList<>(command.length + 1); + localCommand.addAll(Arrays.asList(command)); + localCommand.add(data); + Process p = new ProcessBuilder().command(localCommand).redirectErrorStream(true).start(); + if (!p.waitFor(1, TimeUnit.SECONDS)) { + System.out.println("Process Hang for " + name); + return "fail: process took more than 1 sec " + p.pid(); + } + String s = new String(p.getInputStream().readAllBytes(), StandardCharsets.UTF_8); + Matcher m = okRegexp.matcher(s); + if (m.find()) { + return "OK " + m.group(okGroup); + } + m = failRegexp.matcher(s); + if (m.find()) { + return "err: " + m.group(failGroup); + } + return "fail: SingleClientQuery failed to get data"; + } catch (InterruptedException | IOException e) { + e.printStackTrace(); + return "fail: " + e.getMessage(); + } + } +} diff --git a/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/StreamingClient.java b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/StreamingClient.java new file mode 100644 index 00000000000..feefc34ffb3 --- /dev/null +++ b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/StreamingClient.java @@ -0,0 +1,52 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.testfuzz; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; + +class StreamingClient implements FuzzingClient { + final String name; + final BufferedReader reader; + final PrintWriter writer; + + public StreamingClient(final String clientName, final String... command) { + try { + Process p = new ProcessBuilder().redirectErrorStream(true).command(command).start(); + this.name = clientName; + this.reader = new BufferedReader(p.inputReader(StandardCharsets.UTF_8)); + this.writer = new PrintWriter(p.getOutputStream(), true, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public String getName() { + return name; + } + + @Override + public String differentialFuzz(final String data) { + try { + writer.println(data); + return reader.readLine(); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } +} diff --git a/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/VersionProvider.java b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/VersionProvider.java new file mode 100644 index 00000000000..6a184dd90e9 --- /dev/null +++ b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/VersionProvider.java @@ -0,0 +1,47 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.testfuzz; + +import org.hyperledger.besu.BesuInfo; + +import picocli.CommandLine; + +/** + * The VersionProvider class is responsible for providing the version of the Hyperledger Besu EVM + * tool. It implements the IVersionProvider interface from the picocli library. + * + *

    Usage: In the pattern layout configuration, replace {@code %msg} with {@code %msgc}. + */ +@Plugin(name = "BesuLogMessageConverter", category = PatternConverter.CATEGORY) +@ConverterKeys({"msgc"}) +public class BesuLogMessageConverter extends LogEventPatternConverter { + + private BesuLogMessageConverter() { + super("BesuLogMessageConverter", null); + } + + /** + * Creates new instance of this class. Required by Log4j2. + * + * @param options Array of options + * @return instance of this class + */ + @SuppressWarnings("unused") // used by Log4j2 + public static BesuLogMessageConverter newInstance(final String[] options) { + return new BesuLogMessageConverter(); + } + + @Override + public void format(final LogEvent event, final StringBuilder toAppendTo) { + final String filteredString = formatBesuLogMessage(event.getMessage().getFormattedMessage()); + toAppendTo.append(filteredString); + } + + /** + * Format Besu log message. + * + * @param input The log message + * @return The formatted log message + */ + public static String formatBesuLogMessage(final String input) { + final StringBuilder builder = new StringBuilder(input.length()); + char prevChar = 0; + + for (int i = 0; i < input.length(); i++) { + final char c = input.charAt(i); + + if (c == 0x0A) { + if (prevChar == 0x0D) { + builder.append(prevChar); + } + builder.append(c); + } else if (c == 0x09 || !Character.isISOControl(c)) { + builder.append(c); + } + + prevChar = c; + } + + return builder.toString(); + } +} diff --git a/util/src/main/java/org/hyperledger/besu/util/number/ByteUnits.java b/util/src/main/java/org/hyperledger/besu/util/number/ByteUnits.java index 461c5543634..70e58c44aeb 100644 --- a/util/src/main/java/org/hyperledger/besu/util/number/ByteUnits.java +++ b/util/src/main/java/org/hyperledger/besu/util/number/ByteUnits.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -12,7 +12,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ - package org.hyperledger.besu.util.number; /** The Byte units. */ diff --git a/util/src/main/java/org/hyperledger/besu/util/platform/PlatformDetector.java b/util/src/main/java/org/hyperledger/besu/util/platform/PlatformDetector.java index 1367c721062..ece589bb1a5 100644 --- a/util/src/main/java/org/hyperledger/besu/util/platform/PlatformDetector.java +++ b/util/src/main/java/org/hyperledger/besu/util/platform/PlatformDetector.java @@ -43,6 +43,8 @@ public class PlatformDetector { private static String _glibc; private static String _jemalloc; + private PlatformDetector() {} + /** * Gets OS type. * diff --git a/util/src/test/java/org/hyperledger/besu/util/NetworkUtilityTest.java b/util/src/test/java/org/hyperledger/besu/util/NetworkUtilityTest.java index 74ea89fc7a8..627212d1f33 100644 --- a/util/src/test/java/org/hyperledger/besu/util/NetworkUtilityTest.java +++ b/util/src/test/java/org/hyperledger/besu/util/NetworkUtilityTest.java @@ -17,6 +17,7 @@ import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; +import java.net.DatagramSocket; import java.net.InetSocketAddress; import java.net.ServerSocket; @@ -35,9 +36,45 @@ public void urlForSocketAddressHandlesIPv6() { } @Test - public void assertPortIsNotAvailable() throws IOException { + public void assertPortIsNotAvailableForTcp() throws IOException { final ServerSocket serverSocket = new ServerSocket(8541); - assertThat(!NetworkUtility.isPortAvailable(8541)).isEqualTo(true); + assertThat(NetworkUtility.isPortUnavailableForTcp(8541)).isEqualTo(true); serverSocket.close(); } + + @Test + public void assertPortIsNotAvailableForUdp() throws IOException { + final DatagramSocket datagramSocket = new DatagramSocket(8541); + assertThat(NetworkUtility.isPortUnavailableForUdp(8541)).isEqualTo(true); + datagramSocket.close(); + } + + @Test + public void assertLocalhostIdentification() { + assertThat(NetworkUtility.isLocalhostAddress("127.0.0.1")).isTrue(); + assertThat(NetworkUtility.isLocalhostAddress("::1")).isTrue(); + assertThat(NetworkUtility.isLocalhostAddress("192.168.1.1")).isFalse(); + assertThat(NetworkUtility.isLocalhostAddress("::ffff:c0a8:101")).isFalse(); + } + + @Test + public void assertIpV4Address() { + assertThat(NetworkUtility.isIpV4Address("127.0.0.1")).isTrue(); + assertThat(NetworkUtility.isIpV4Address("10.0.0.0")).isTrue(); + assertThat(NetworkUtility.isIpV4Address("172.16.1.1")).isTrue(); + assertThat(NetworkUtility.isIpV4Address("127.0.0.")).isFalse(); + assertThat(NetworkUtility.isIpV4Address("256.256.256.256")).isFalse(); + // ipv6 compatible ipv4 address + assertThat(NetworkUtility.isIpV4Address("::ffff:c0a8:5801")).isTrue(); + assertThat(NetworkUtility.isIpV4Address("0:0:0:0:0:ffff:c0a8:5801")).isTrue(); + assertThat(NetworkUtility.isIpV4Address("0000:0000:0000:0000:0000:ffff:c0a8:5801")).isTrue(); + } + + @Test + public void assertIpV6Address() { + assertThat(NetworkUtility.isIpV6Address("::1")).isTrue(); + assertThat(NetworkUtility.isIpV6Address("::")).isTrue(); + assertThat(NetworkUtility.isIpV6Address("2001:db8:3333:4444:5555:6666:7777:8888")).isTrue(); + assertThat(NetworkUtility.isIpV6Address("00:00::00:00::00:00")).isFalse(); + } } diff --git a/util/src/test/java/org/hyperledger/besu/util/log/LogUtilTest.java b/util/src/test/java/org/hyperledger/besu/util/log/LogUtilTest.java new file mode 100644 index 00000000000..89397c900ef --- /dev/null +++ b/util/src/test/java/org/hyperledger/besu/util/log/LogUtilTest.java @@ -0,0 +1,128 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.util.log; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +public class LogUtilTest { + + static final String exceptionMessage = "test message"; + static final String contextMessage = "context message"; + + @Test + public void testMessageSummarizeStackTrace() { + Throwable throwable = new Throwable(exceptionMessage); + String namespace = "org.hyperledger.besu"; + String result = LogUtil.summarizeStackTrace(contextMessage, throwable, namespace); + var lines = + assertFoundInTrace(result, LogUtil.BESU_NAMESPACE, contextMessage, exceptionMessage, 1); + assertThat(lines.size()).isEqualTo(3); + } + + @Test + public void testSummarizeStackTraceToArbitrary() { + Throwable throwable = new Throwable("test message"); + String namespace = "java.lang"; + String result = LogUtil.summarizeStackTrace(contextMessage, throwable, namespace); + var lines = assertFoundInTrace(result, namespace, contextMessage, exceptionMessage, 1); + assertThat(lines.size()).isGreaterThan(3); + } + + @Test + public void testSummarizeLambdaStackTrace() { + String result = ""; + try { + Supplier lambda = () -> besuStackHelper(null); + assertThat(lambda.get()).isNotNull(); + } catch (Exception e) { + String namespace = "org.hyperledger.besu"; + result = LogUtil.summarizeStackTrace(contextMessage, e, namespace); + } + var lines = + assertFoundInTrace( + result, LogUtil.BESU_NAMESPACE, contextMessage, "NullPointerException", 1); + assertThat(lines).hasSize(5); + } + + @Test + public void assertSummarizeStopsAtFirstBesu() { + String result = ""; + try { + Supplier lambda = () -> besuStackHelper((null)); + Supplier lambda2 = lambda::get; + assertThat(lambda2.get()).isNotNull(); + } catch (Exception e) { + result = LogUtil.summarizeBesuStackTrace("besuSummary", e); + } + var lines = + assertFoundInTrace( + result, LogUtil.BESU_NAMESPACE, "besuSummary", "NullPointerException", 1); + assertThat(lines).hasSize(5); + assertThat(lines.get(0)).contains("besuSummary"); + } + + @Test + public void assertMaxDepth() { + String result = ""; + try { + recurseTimesAndThrow(30); + } catch (Exception ex) { + result = LogUtil.summarizeStackTrace("besuSummary", ex, "java.lang"); + } + System.out.println(result); + List lines = Arrays.asList(result.split("\n")); + assertThat(lines).hasSize(22); + assertThat(lines.get(0)).contains("besuSummary"); + } + + private List assertFoundInTrace( + final String result, + final String namespace, + final String ctxMessage, + final String excMessage, + final int times) { + System.out.println(result); + assertThat(result).containsOnlyOnce(ctxMessage); + assertThat(result).containsOnlyOnce(excMessage); + + List lines = Arrays.asList(result.split("\n")); + assertThat( + lines.stream() + .filter(s -> s.contains("at: ")) + .filter(s -> s.contains(namespace)) + .count()) + .isEqualTo(times); + return lines; + } + + @SuppressWarnings("InfiniteRecursion") + private void recurseTimesAndThrow(final int times) { + if (times < 1) { + throw new RuntimeException("FakeStackOverflowError"); + } + recurseTimesAndThrow(times - 1); + } + + private Optional besuStackHelper(final Object o) { + return Optional.of(o); + } +} diff --git a/util/src/test/java/org/hyperledger/besu/util/log4j/plugin/BesuLogMessageConverterTest.java b/util/src/test/java/org/hyperledger/besu/util/log4j/plugin/BesuLogMessageConverterTest.java new file mode 100644 index 00000000000..777e5c31a38 --- /dev/null +++ b/util/src/test/java/org/hyperledger/besu/util/log4j/plugin/BesuLogMessageConverterTest.java @@ -0,0 +1,39 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.util.log4j.plugin; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +public class BesuLogMessageConverterTest { + + @Test + public void logCleanup() { + final StringBuilder testDataBuilder = new StringBuilder("log "); + for (int i = 0; i <= 0x001F; i++) { + testDataBuilder.append((char) i); + } + for (int i = 0x007F; i <= 0x009F; i++) { + testDataBuilder.append((char) i); + } + testDataBuilder.append((char) 0x0D).append((char) 0x0A).append("message"); + + String testData = testDataBuilder.toString(); + String cleanedData = BesuLogMessageConverter.formatBesuLogMessage(testData); + String expectedData = String.format("log %c%c%c%cmessage", 0x09, 0x0A, 0x0D, 0x0A); + assertThat(cleanedData).isEqualTo(expectedData); + } +} diff --git a/util/src/test/java/org/hyperledger/besu/util/number/PositiveNumberTest.java b/util/src/test/java/org/hyperledger/besu/util/number/PositiveNumberTest.java index 5e51e79e801..022966d71b4 100644 --- a/util/src/test/java/org/hyperledger/besu/util/number/PositiveNumberTest.java +++ b/util/src/test/java/org/hyperledger/besu/util/number/PositiveNumberTest.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/wslsh.bat b/wslsh.bat new file mode 100644 index 00000000000..57e1d623070 --- /dev/null +++ b/wslsh.bat @@ -0,0 +1,2 @@ +set WSLENV=architecture/u +wsl /bin/bash %* \ No newline at end of file

    The getVersion method returns a string array containing the version of the Hyperledger Besu + * EVM tool. + */ +public class VersionProvider implements CommandLine.IVersionProvider { + + /** + * Default constructor for the VersionProvider class. This constructor does not perform any + * operations. + */ + public VersionProvider() { + // this constructor is here only for javadoc linting + } + + /** + * This method returns the version of the Hyperledger Besu EVM tool. + * + * @return A string array containing the version of the Hyperledger Besu EVM tool. + */ + @Override + public String[] getVersion() { + return new String[] {"Hyperledger Besu evm " + BesuInfo.shortVersion()}; + } +} diff --git a/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/javafuzz/Corpus.java b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/javafuzz/Corpus.java new file mode 100644 index 00000000000..56eba76d370 --- /dev/null +++ b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/javafuzz/Corpus.java @@ -0,0 +1,449 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.testfuzz.javafuzz; + +import org.hyperledger.besu.crypto.MessageDigestFactory; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.math.BigInteger; +import java.nio.file.Files; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +/** + * Ported from ... because + * fields like max input size were not configurable. + */ +@SuppressWarnings("CatchAndPrintStackTrace") +public class Corpus { + private final ArrayList inputs; + private final int maxInputSize; + private static final int[] INTERESTING8 = {-128, -1, 0, 1, 16, 32, 64, 100, 127}; + private static final int[] INTERESTING16 = { + -32768, -129, 128, 255, 256, 512, 1000, 1024, 4096, 32767, -128, -1, 0, 1, 16, 32, 64, 100, 127 + }; + private static final int[] INTERESTING32 = { + -2147483648, + -100663046, + -32769, + 32768, + 65535, + 65536, + 100663045, + 2147483647, + -32768, + -129, + 128, + 255, + 256, + 512, + 1000, + 1024, + 4096, + 32767, + -128, + -1, + 0, + 1, + 16, + 32, + 64, + 100, + 127 + }; + private String corpusPath; + private int seedLength; + + /** + * Create a corpus + * + * @param dirs The directory to store the corpus files + */ + public Corpus(final String dirs) { + this.maxInputSize = 0xc001; // 48k+1 + this.corpusPath = null; + this.inputs = new ArrayList<>(); + if (dirs != null) { + String[] arr = dirs.split(",", -1); + for (String s : arr) { + File f = new File(s); + if (!f.exists()) { + f.mkdirs(); + } + if (f.isDirectory()) { + if (this.corpusPath == null) { + this.corpusPath = f.getPath(); + } + this.loadDir(f); + } else { + try { + this.inputs.add(Files.readAllBytes(f.toPath())); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + this.seedLength = this.inputs.size(); + } + + int getLength() { + return this.inputs.size(); + } + + private boolean randBool() { + return ThreadLocalRandom.current().nextBoolean(); + } + + private int rand(final int max) { + return ThreadLocalRandom.current().nextInt(0, max); + } + + private void loadDir(final File dir) { + for (final File f : dir.listFiles()) { + if (f.isFile()) { + try { + this.inputs.add(Files.readAllBytes(f.toPath())); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + + byte[] generateInput() throws NoSuchAlgorithmException { + if (this.seedLength != 0) { + this.seedLength--; + return this.inputs.get(this.seedLength); + } + if (this.inputs.isEmpty()) { + byte[] buf = new byte[] {}; + this.putBuffer(buf); + return buf; + } + byte[] buf = this.inputs.get(this.rand(this.inputs.size())); + return this.mutate(buf); + } + + void putBuffer(final byte[] buf) throws NoSuchAlgorithmException { + if (this.inputs.contains(buf)) { + return; + } + + this.inputs.add(buf); + + writeCorpusFile(buf); + } + + private void writeCorpusFile(final byte[] buf) throws NoSuchAlgorithmException { + if (this.corpusPath != null) { + MessageDigest md = MessageDigestFactory.create("SHA-256"); + md.update(buf); + byte[] digest = md.digest(); + String hex = String.format("%064x", new BigInteger(1, digest)); + try (FileOutputStream fos = new FileOutputStream(this.corpusPath + "/" + hex)) { + fos.write(buf); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private String dec2bin(final int dec) { + String bin = Integer.toBinaryString(dec); + String padding = new String(new char[32 - bin.length()]).replace("\0", "0"); + return padding + bin; + } + + private int exp2() { + String bin = dec2bin(this.rand((int) Math.pow(2, 32))); + int count = 0; + for (int i = 0; i < 32; i++) { + if (bin.charAt(i) == '0') { + count++; + } else { + break; + } + } + return count; + } + + int chooseLen(final int n) { + int x = this.rand(100); + if (x < 90) { + return this.rand(Math.min(8, n)) + 1; + } else if (x < 99) { + return this.rand(Math.min(32, n)) + 1; + } else { + return this.rand(n) + 1; + } + } + + static void copy( + final byte[] src, final int srcPos, final byte[] dst, final int dstPos, final int length) { + System.arraycopy(src, srcPos, dst, dstPos, Math.min(length, src.length - srcPos)); + } + + static void copy(final byte[] src, final int srcPos, final byte[] dst, final int dstPos) { + System.arraycopy(src, srcPos, dst, dstPos, Math.min(src.length - srcPos, dst.length - dstPos)); + } + + static byte[] concatZeros(final byte[] a, final int n) { + byte[] c = new byte[a.length + n]; + Arrays.fill(c, (byte) 0); + System.arraycopy(a, 0, c, 0, a.length); + return c; + } + + byte[] mutate(final byte[] buf) { + byte[] res = buf.clone(); + int nm = 1 + this.exp2(); + for (int i = 0; i < nm; i++) { + int x = this.rand(16); + if (x == 0) { + // Remove a range of bytes. + if (res.length <= 1) { + i--; + continue; + } + int pos0 = this.rand(res.length); + int pos1 = pos0 + this.chooseLen(res.length - pos0); + copy(res, pos1, res, pos0, res.length - pos0); + res = Arrays.copyOfRange(res, 0, res.length - (pos1 - pos0)); + } else if (x == 1) { + // Insert a range of random bytes. + int pos = this.rand(res.length + 1); + int n = this.chooseLen(10); + res = concatZeros(res, n); + copy(res, pos, res, pos + n); + for (int k = 0; k < n; k++) { + res[pos + k] = (byte) this.rand(256); + } + } else if (x == 2) { + // Duplicate a range of bytes. + if (res.length <= 1) { + i--; + continue; + } + int src = this.rand(res.length); + int dst = this.rand(res.length); + while (src == dst) { + dst = this.rand(res.length); + } + int n = this.chooseLen(res.length - src); + byte[] tmp = new byte[n]; + Arrays.fill(tmp, (byte) 0); + copy(res, src, tmp, 0); + res = concatZeros(res, n); + copy(res, dst, res, dst + n); + System.arraycopy(tmp, 0, res, dst, n); + } else if (x == 3) { + // Copy a range of bytes. + if (res.length <= 1) { + i--; + continue; + } + int src = this.rand(res.length); + int dst = this.rand(res.length); + while (src == dst) { + dst = this.rand(res.length); + } + int n = this.chooseLen(res.length - src); + copy(res, src + n, res, dst); + } else if (x == 4) { + // Bit flip. Spooky! + if (res.length <= 1) { + i--; + continue; + } + int pos = this.rand(res.length); + res[pos] ^= (byte) (1 << (byte) this.rand(8)); + } else if (x == 5) { + // Set a byte to a random value. + if (res.length <= 1) { + i--; + continue; + } + int pos = this.rand(res.length); + res[pos] ^= (byte) (this.rand(255) + 1); + } else if (x == 6) { + // Swap 2 bytes. + if (res.length <= 1) { + i--; + continue; + } + int src = this.rand(res.length); + int dst = this.rand(res.length); + while (src == dst) { + dst = this.rand(res.length); + } + byte tmp1 = res[src]; + res[src] = res[dst]; + res[dst] = tmp1; + } else if (x == 7) { + // Add/subtract from a byte. + if (res.length == 0) { + i--; + continue; + } + int pos = this.rand(res.length); + int v = this.rand(35) + 1; + if (this.randBool()) { + res[pos] += (byte) v; + } else { + res[pos] -= (byte) v; + } + } else if (x == 8) { + // Add/subtract from a uint16. + i--; + // if (res.length < 2) { + // i--; + // continue; + // } + // int pos = this.rand(res.length - 1); + // int v = this.rand(35) + 1; + // if (this.randBool()) { + // v = 0 - v; + // } + // + // if (this.randBool()) { + // res[pos] = + // } else { + // + // } + + } else if (x == 9) { + i--; + // Add/subtract from a uint32. + } else if (x == 10) { + // Replace a byte with an interesting value. + if (res.length == 0) { + i--; + continue; + } + int pos = this.rand(res.length); + res[pos] = (byte) INTERESTING8[this.rand(INTERESTING8.length)]; + } else if (x == 11) { + // Replace an uint16 with an interesting value. + if (res.length < 2) { + i--; + continue; + } + int pos = this.rand(res.length - 1); + if (this.randBool()) { + res[pos] = (byte) (INTERESTING16[this.rand(INTERESTING16.length)] & 0xFF); + res[pos + 1] = (byte) ((INTERESTING16[this.rand(INTERESTING16.length)] >> 8) & 0xFF); + } else { + res[pos + 1] = (byte) (INTERESTING16[this.rand(INTERESTING16.length)] & 0xFF); + res[pos] = (byte) ((INTERESTING16[this.rand(INTERESTING16.length)] >> 8) & 0xFF); + } + } else if (x == 12) { + // Replace an uint32 with an interesting value. + if (res.length < 4) { + i--; + continue; + } + int pos = this.rand(res.length - 3); + if (this.randBool()) { + res[pos] = (byte) (INTERESTING32[this.rand(INTERESTING32.length)] & 0xFF); + res[pos + 1] = (byte) ((INTERESTING32[this.rand(INTERESTING32.length)] >> 8) & 0xFF); + res[pos + 2] = (byte) ((INTERESTING32[this.rand(INTERESTING32.length)] >> 16) & 0xFF); + res[pos + 3] = (byte) ((INTERESTING32[this.rand(INTERESTING32.length)] >> 24) & 0xFF); + } else { + res[pos + 3] = (byte) (INTERESTING32[this.rand(INTERESTING32.length)] & 0xFF); + res[pos + 2] = (byte) ((INTERESTING32[this.rand(INTERESTING32.length)] >> 8) & 0xFF); + res[pos + 1] = (byte) ((INTERESTING32[this.rand(INTERESTING32.length)] >> 16) & 0xFF); + res[pos] = (byte) ((INTERESTING32[this.rand(INTERESTING32.length)] >> 24) & 0xFF); + } + } else if (x == 13) { + // Replace an ascii digit with another digit. + List digits = new ArrayList<>(); + for (int k = 0; k < res.length; k++) { + if (res[k] >= 48 && res[k] <= 57) { + digits.add(k); + } + } + if (digits.isEmpty()) { + i--; + continue; + } + int pos = this.rand(digits.size()); + int was = res[digits.get(pos)]; + int now = was; + while (now == was) { + now = this.rand(10) + 48; + } + res[digits.get(pos)] = (byte) now; + } else if (x == 14) { + // Splice another input. + if (res.length < 4 || this.inputs.size() < 2) { + i--; + continue; + } + byte[] other = this.inputs.get(this.rand(this.inputs.size())); + if (other.length < 4) { + i--; + continue; + } + // Find common prefix and suffix. + int idx0 = 0; + while (idx0 < res.length && idx0 < other.length && res[idx0] == other[idx0]) { + idx0++; + } + int idx1 = 0; + while (idx1 < res.length + && idx1 < other.length + && res[res.length - idx1 - 1] == other[other.length - idx1 - 1]) { + idx1++; + } + int diff = Math.min(res.length - idx0 - idx1, other.length - idx0 - idx1); + if (diff < 4) { + i--; + continue; + } + copy(other, idx0, res, idx0, Math.min(other.length, idx0 + this.rand(diff - 2) + 1) - idx0); + } else if (x == 15) { + // Insert a part of another input. + if (res.length < 4 || this.inputs.size() < 2) { + i--; + continue; + } + byte[] other = this.inputs.get(this.rand(this.inputs.size())); + if (other.length < 4) { + i--; + continue; + } + int pos0 = this.rand(res.length + 1); + int pos1 = this.rand(other.length - 2); + int n = this.chooseLen(other.length - pos1 - 2) + 2; + res = concatZeros(res, n); + copy(res, pos0, res, pos0 + n); + if (n >= 0) System.arraycopy(other, pos1, res, pos0, n); + } + } + + if (res.length > this.maxInputSize) { + res = Arrays.copyOfRange(res, 0, this.maxInputSize); + } + return res; + } +} diff --git a/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/javafuzz/FuzzTarget.java b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/javafuzz/FuzzTarget.java new file mode 100644 index 00000000000..26f3522e116 --- /dev/null +++ b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/javafuzz/FuzzTarget.java @@ -0,0 +1,31 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.testfuzz.javafuzz; + +/** + * Adapted from ... because + * I wanted it to be a functional interface + */ +@FunctionalInterface +public interface FuzzTarget { + + /** + * The target to fuzz + * + * @param data data proviced by the fuzzer + */ + void fuzz(byte[] data); +} diff --git a/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/javafuzz/Fuzzer.java b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/javafuzz/Fuzzer.java new file mode 100644 index 00000000000..f5d7f537a27 --- /dev/null +++ b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/javafuzz/Fuzzer.java @@ -0,0 +1,284 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.testfuzz.javafuzz; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import org.hyperledger.besu.crypto.Hash; +import org.hyperledger.besu.crypto.MessageDigestFactory; + +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.tuweni.bytes.Bytes; +import org.jacoco.core.data.ExecutionData; +import org.jacoco.core.data.ExecutionDataReader; +import org.jacoco.core.data.IExecutionDataVisitor; +import org.jacoco.core.data.ISessionInfoVisitor; +import org.jacoco.core.data.SessionInfo; + +/** + * Ported from ... because + * JaCoCo APIs changed. + */ +@SuppressWarnings({"java:S106", "CallToPrintStackTrace"}) // we use lots the console, on purpose +public class Fuzzer { + private final FuzzTarget target; + private final Corpus corpus; + private final Object agent; + private final Method getExecutionDataMethod; + private final Pattern guidanceRegexp; + private final File newCorpusDir; + private long executionsInSample; + private long lastSampleTime; + private long totalExecutions; + private long totalCoverage; + + Supplier fuzzStats; + + /** + * Create a new fuzzer + * + * @param target The target to fuzz + * @param dirs the list of corpus dirs and files, comma separated. + * @param fuzzStats additional fuzzing data from the client + * @param guidanceRegexp Regexp of (slash delimited) class names to check for guidance. + * @param newCorpusDir Direcroty to dump hex encoded versions of guidance discovered tests. + * @throws ClassNotFoundException If Jacoco RT is not found (because jacocoagent.jar is not + * loaded) + * @throws NoSuchMethodException If the wrong version of Jacoco is loaded + * @throws InvocationTargetException If the wrong version of Jacoco is loaded + * @throws IllegalAccessException If the wrong version of Jacoco is loaded + * @throws NoSuchAlgorithmException If the SHA-256 crypto algo cannot be loaded. + */ + public Fuzzer( + final FuzzTarget target, + final String dirs, + final Supplier fuzzStats, + final String guidanceRegexp, + final File newCorpusDir) + throws ClassNotFoundException, + NoSuchMethodException, + InvocationTargetException, + IllegalAccessException, + NoSuchAlgorithmException { + this.target = target; + this.corpus = new Corpus(dirs); + this.fuzzStats = fuzzStats; + this.newCorpusDir = newCorpusDir; + if (newCorpusDir != null) { + if (newCorpusDir.exists() && newCorpusDir.isFile()) { + throw new IllegalArgumentException("New corpus directory already exists as a file"); + } + newCorpusDir.mkdirs(); + } + this.guidanceRegexp = + guidanceRegexp == null || guidanceRegexp.isBlank() ? null : Pattern.compile(guidanceRegexp); + Class c = Class.forName("org.jacoco.agent.rt.RT"); + Method getAgentMethod = c.getMethod("getAgent"); + this.agent = getAgentMethod.invoke(null); + this.getExecutionDataMethod = agent.getClass().getMethod("getExecutionData", boolean.class); + fileNameForBuffer(new byte[0]); + } + + void writeCrash(final byte[] buf) { + Bytes hash = Hash.sha256(Bytes.wrap(buf)); + String filepath = "crash-" + hash.toUnprefixedHexString(); + try (FileOutputStream fos = new FileOutputStream(filepath)) { + fos.write(buf); + System.out.printf("crash was written to %s%n", filepath); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + void logStats(final String type) { + long rss = + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024 / 1024; + long endTime = System.currentTimeMillis(); + long execs_per_second = -1; + if ((endTime - this.lastSampleTime) != 0) { + execs_per_second = (this.executionsInSample * 1000 / (endTime - this.lastSampleTime)); + } + this.lastSampleTime = endTime; + this.executionsInSample = 0; + + System.out.printf( + "#%d %s cov: %d corp: %d exec/s: %d rss: %d MB %s%n", + this.totalExecutions, + type, + this.totalCoverage, + this.corpus.getLength(), + execs_per_second, + rss, + fuzzStats.get()); + } + + /** + * Runs the fuzzer until the VM is shut down + * + * @throws InvocationTargetException if the wrong version of jacoco is loaded + * @throws IllegalAccessException if the wrong version of jacoco is loaded + * @throws NoSuchAlgorithmException if our favorite hash algo is not loaded + */ + @SuppressWarnings("java:S2189") // the endless loop is on purpose + public void start() + throws InvocationTargetException, IllegalAccessException, NoSuchAlgorithmException { + System.out.printf("#0 READ units: %d%n", this.corpus.getLength()); + this.totalCoverage = 0; + this.totalExecutions = 0; + this.executionsInSample = 0; + this.lastSampleTime = System.currentTimeMillis(); + + Map hitMap = new HashMap<>(); + // preload some values so we don't get false hits in coverage we don't care about. + hitMap.put("org/hyperledger/besu/testfuzz/EofContainerSubCommand", 100); + hitMap.put("org/hyperledger/besu/testfuzz/Fuzzer", 100); + hitMap.put("org/hyperledger/besu/testfuzz/Fuzzer$HitCounter", 100); + hitMap.put("org/hyperledger/besu/testfuzz/InternalClient", 100); + + while (true) { + byte[] buf = this.corpus.generateInput(); + // The next version will run this in a different thread. + try { + this.target.fuzz(buf); + } catch (Exception e) { + e.printStackTrace(System.out); + this.writeCrash(buf); + System.exit(1); + break; + } + + this.totalExecutions++; + this.executionsInSample++; + + long newCoverage = getHitCount(hitMap); + if (newCoverage > this.totalCoverage) { + this.totalCoverage = newCoverage; + this.corpus.putBuffer(buf); + if (totalExecutions > corpus.getLength()) { + this.logStats("NEW"); + if (newCorpusDir != null) { + + String filename = fileNameForBuffer(buf); + try (var pw = + new PrintWriter( + new BufferedWriter( + new OutputStreamWriter( + new FileOutputStream(new File(newCorpusDir, filename)), UTF_8)))) { + pw.println(Bytes.wrap(buf).toHexString()); + } catch (IOException e) { + e.printStackTrace(System.out); + } + } + } + } else if ((System.currentTimeMillis() - this.lastSampleTime) > 30000) { + this.logStats("PULSE"); + } + } + } + + private static String fileNameForBuffer(final byte[] buf) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigestFactory.create(MessageDigestFactory.SHA256_ALG); + md.update(buf); + byte[] digest = md.digest(); + return String.format("new-%064x.hex", new BigInteger(1, digest)); + } + + private long getHitCount(final Map hitMap) + throws IllegalAccessException, InvocationTargetException { + if (guidanceRegexp == null) { + return 1; + } + byte[] dumpData = (byte[]) this.getExecutionDataMethod.invoke(this.agent, false); + ExecutionDataReader edr = new ExecutionDataReader(new ByteArrayInputStream(dumpData)); + HitCounter hc = new HitCounter(hitMap, guidanceRegexp); + edr.setExecutionDataVisitor(hc); + edr.setSessionInfoVisitor(hc); + try { + edr.read(); + } catch (IOException e) { + e.printStackTrace(); + this.writeCrash(dumpData); + } + + return hc.getHits(); + } + + static class HitCounter implements IExecutionDataVisitor, ISessionInfoVisitor { + long hits = 0; + Map hitMap; + Pattern guidanceRegexp; + + public HitCounter(final Map hitMap, final Pattern guidanceRegexp) { + this.hitMap = hitMap; + this.guidanceRegexp = guidanceRegexp; + } + + @Override + public void visitClassExecution(final ExecutionData executionData) { + String name = executionData.getName(); + Matcher matcher = guidanceRegexp.matcher(name); + int hit = 0; + if (!matcher.find()) { + return; + } + if (matcher.start() != 0) { + return; + } + for (boolean b : executionData.getProbes()) { + if (b) { + hit++; + } + } + if (hitMap.containsKey(name)) { + int theHits = hitMap.get(name); + if (theHits < hit) { + hitMap.put(name, hit); + } else { + hit = theHits; + } + } else { + hitMap.put(name, hit); + } + hits += hit; + } + + public long getHits() { + return hits; + } + + @Override + public void visitSessionInfo(final SessionInfo sessionInfo) { + // nothing to do. Data parser requires a session listener. + } + } +} diff --git a/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/javafuzz/README.md b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/javafuzz/README.md new file mode 100644 index 00000000000..276e404fa0a --- /dev/null +++ b/testfuzz/src/main/java/org/hyperledger/besu/testfuzz/javafuzz/README.md @@ -0,0 +1,14 @@ +## Credits & Acknowledgments + +These classes were ported from [Javafuzz](https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/javafuzz/). +Javafuzz is a port of [fuzzitdev/jsfuzz](https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/jsfuzz). + +Which in turn based based on [go-fuzz](https://github.com/dvyukov/go-fuzz) originally developed by [Dmitry Vyukov's](https://twitter.com/dvyukov). +Which is in turn heavily based on [Michal Zalewski](https://twitter.com/lcamtuf) [AFL](http://lcamtuf.coredump.cx/afl/). + +## Changes + +* Increased max binary size to 48k+1 +* ported AbstractFuzzTarget to a functional interface FuzzTarget +* Fixed some incompatibilities with JaCoCo +* Besu style required changes (formatting, final params, etc.) \ No newline at end of file diff --git a/testfuzz/src/main/scripts/unixStartScript.txt b/testfuzz/src/main/scripts/unixStartScript.txt new file mode 100644 index 00000000000..59d8b83fbc7 --- /dev/null +++ b/testfuzz/src/main/scripts/unixStartScript.txt @@ -0,0 +1,200 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## ${applicationName} start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: \$0 may be a link +PRG="\$0" +# Need this for relative symlinks. +while [ -h "\$PRG" ] ; do + ls=`ls -ld "\$PRG"` + link=`expr "\$ls" : '.*-> \\(.*\\)\$'` + if expr "\$link" : '/.*' > /dev/null; then + PRG="\$link" + else + PRG=`dirname "\$PRG"`"/\$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"\$PRG\"`/${appHomeRelativePath}" >/dev/null +APP_HOME="`pwd -P`" +cd "\$SAVED" >/dev/null + +APP_NAME="${applicationName}" +APP_BASE_NAME=`basename "\$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and ${optsEnvironmentVar} to pass JVM options to this script. +DEFAULT_JVM_OPTS=${defaultJvmOpts} + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "\$*" +} + +die () { + echo + echo "\$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MSYS* | MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$classpath +<% if ( mainClassName.startsWith('--module ') ) { %>MODULE_PATH=$modulePath<% } %> + +# Determine the Java command to use to start the JVM. +if [ -n "\$JAVA_HOME" ] ; then + if [ -x "\$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="\$JAVA_HOME/jre/sh/java" + else + JAVACMD="\$JAVA_HOME/bin/java" + fi + if [ ! -x "\$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: \$JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "\$cygwin" = "false" -a "\$darwin" = "false" -a "\$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ \$? -eq 0 ] ; then + if [ "\$MAX_FD" = "maximum" -o "\$MAX_FD" = "max" ] ; then + MAX_FD="\$MAX_FD_LIMIT" + fi + ulimit -n \$MAX_FD + if [ \$? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: \$MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: \$MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if \$darwin; then + GRADLE_OPTS="\$GRADLE_OPTS \\"-Xdock:name=\$APP_NAME\\" \\"-Xdock:icon=\$APP_HOME/media/gradle.icns\\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "\$cygwin" = "true" -o "\$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "\$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "\$CLASSPATH"` +<% if ( mainClassName.startsWith('--module ') ) { %> MODULE_PATH=`cygpath --path --mixed "\$MODULE_PATH"`<% } %> + JAVACMD=`cygpath --unix "\$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in \$ROOTDIRSRAW ; do + ROOTDIRS="\$ROOTDIRS\$SEP\$dir" + SEP="|" + done + OURCYGPATTERN="(^(\$ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "\$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="\$OURCYGPATTERN|(\$GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "\$@" ; do + CHECK=`echo "\$arg"|egrep -c "\$OURCYGPATTERN" -` + CHECK2=`echo "\$arg"|egrep -c "^-"` ### Determine if an option + + if [ \$CHECK -ne 0 ] && [ \$CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args\$i`=`cygpath --path --ignore --mixed "\$arg"` + else + eval `echo args\$i`="\"\$arg\"" + fi + i=`expr \$i + 1` + done + case \$i in + 0) set -- ;; + 1) set -- "\$args0" ;; + 2) set -- "\$args0" "\$args1" ;; + 3) set -- "\$args0" "\$args1" "\$args2" ;; + 4) set -- "\$args0" "\$args1" "\$args2" "\$args3" ;; + 5) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" ;; + 6) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" ;; + 7) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" ;; + 8) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" "\$args7" ;; + 9) set -- "\$args0" "\$args1" "\$args2" "\$args3" "\$args4" "\$args5" "\$args6" "\$args7" "\$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\\\n "\$i" | sed "s/'/'\\\\\\\\''/g;1s/^/'/;\\\$s/\\\$/' \\\\\\\\/" ; done + echo " " +} +APP_ARGS=`save "\$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- -javaagent:\$APP_HOME/lib/jacocoagent.jar \$DEFAULT_JVM_OPTS \$JAVA_OPTS \$${optsEnvironmentVar} <% if ( appNameSystemProperty ) { %>"\"-D${appNameSystemProperty}=\$APP_BASE_NAME\"" <% } %>-classpath "\"\$CLASSPATH\"" <% if ( mainClassName.startsWith('--module ') ) { %>--module-path "\"\$MODULE_PATH\"" <% } %>${mainClassName} "\$APP_ARGS" + +unset BESU_USING_JEMALLOC +if [ "\$darwin" = "false" -a "\$msys" = "false" ]; then + # check if jemalloc is available + TEST_JEMALLOC=\$(LD_PRELOAD=libjemalloc.so sh -c true 2>&1) + + # if jemalloc is available the output is empty, otherwise the output has an error line + if [ -z "\$TEST_JEMALLOC" ]; then + export LD_PRELOAD=libjemalloc.so + export BESU_USING_JEMALLOC=true + else + # jemalloc not available, as fallback limit malloc to 2 arenas + export MALLOC_ARENA_MAX=2 + fi +fi + +exec "\$JAVACMD" "\$@" diff --git a/testfuzz/src/main/scripts/windowsStartScript.txt b/testfuzz/src/main/scripts/windowsStartScript.txt new file mode 100644 index 00000000000..2ce40892ef2 --- /dev/null +++ b/testfuzz/src/main/scripts/windowsStartScript.txt @@ -0,0 +1,91 @@ +@rem +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem ${applicationName} startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=.\ + +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME%${appHomeRelativePath} + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and ${optsEnvironmentVar} to pass JVM options to this script. +set DEFAULT_JVM_OPTS=${defaultJvmOpts} + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=$classpath +<% if ( mainClassName.startsWith('--module ') ) { %>set MODULE_PATH=$modulePath<% } %> + +@rem Execute ${applicationName} +"%JAVA_EXE%" -javaagent:%APP_HOME%/lib/jacocoagent.jar %DEFAULT_JVM_OPTS% %JAVA_OPTS% %${optsEnvironmentVar}% <% if ( appNameSystemProperty ) { %>"-D${appNameSystemProperty}=%APP_BASE_NAME%"<% } %> -classpath "%CLASSPATH%" <% if ( mainClassName.startsWith('--module ') ) { %>--module-path "%MODULE_PATH%" <% } %>${mainClassName} %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable ${exitEnvironmentVar} if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%${exitEnvironmentVar}%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/testutil/build.gradle b/testutil/build.gradle index 491c213207a..5b4f06e7acc 100644 --- a/testutil/build.gradle +++ b/testutil/build.gradle @@ -22,7 +22,8 @@ jar { 'Specification-Title': archiveBaseName, 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, - 'Implementation-Version': calculateVersion() + 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash ) } } diff --git a/testutil/src/main/java/org/hyperledger/besu/kvstore/AbstractKeyValueStorageTest.java b/testutil/src/main/java/org/hyperledger/besu/kvstore/AbstractKeyValueStorageTest.java index 60a66336765..a2f68e1777d 100644 --- a/testutil/src/main/java/org/hyperledger/besu/kvstore/AbstractKeyValueStorageTest.java +++ b/testutil/src/main/java/org/hyperledger/besu/kvstore/AbstractKeyValueStorageTest.java @@ -40,6 +40,8 @@ /** The Abstract key value storage test. */ @Disabled public abstract class AbstractKeyValueStorageTest { + /** Default Constructor */ + protected AbstractKeyValueStorageTest() {} /** * Create store key value storage. @@ -56,18 +58,20 @@ public abstract class AbstractKeyValueStorageTest { */ @Test public void twoStoresAreIndependent() throws Exception { - final KeyValueStorage store1 = createStore(); - final KeyValueStorage store2 = createStore(); + try (final KeyValueStorage store1 = createStore()) { + try (final KeyValueStorage store2 = createStore()) { - final KeyValueStorageTransaction tx = store1.startTransaction(); - final byte[] key = bytesFromHexString("0001"); - final byte[] value = bytesFromHexString("0FFF"); + final KeyValueStorageTransaction tx = store1.startTransaction(); + final byte[] key = bytesFromHexString("0001"); + final byte[] value = bytesFromHexString("0FFF"); - tx.put(key, value); - tx.commit(); + tx.put(key, value); + tx.commit(); - final Optional result = store2.get(key); - assertThat(result).isEmpty(); + final Optional result = store2.get(key); + assertThat(result).isEmpty(); + } + } } /** @@ -77,20 +81,21 @@ public void twoStoresAreIndependent() throws Exception { */ @Test public void put() throws Exception { - final KeyValueStorage store = createStore(); - final byte[] key = bytesFromHexString("0F"); - final byte[] firstValue = bytesFromHexString("0ABC"); - final byte[] secondValue = bytesFromHexString("0DEF"); - - KeyValueStorageTransaction tx = store.startTransaction(); - tx.put(key, firstValue); - tx.commit(); - assertThat(store.get(key)).contains(firstValue); - - tx = store.startTransaction(); - tx.put(key, secondValue); - tx.commit(); - assertThat(store.get(key)).contains(secondValue); + try (final KeyValueStorage store = createStore()) { + final byte[] key = bytesFromHexString("0F"); + final byte[] firstValue = bytesFromHexString("0ABC"); + final byte[] secondValue = bytesFromHexString("0DEF"); + + KeyValueStorageTransaction tx = store.startTransaction(); + tx.put(key, firstValue); + tx.commit(); + assertThat(store.get(key)).contains(firstValue); + + tx = store.startTransaction(); + tx.put(key, secondValue); + tx.commit(); + assertThat(store.get(key)).contains(secondValue); + } } /** @@ -100,16 +105,17 @@ public void put() throws Exception { */ @Test public void streamKeys() throws Exception { - final KeyValueStorage store = createStore(); - final KeyValueStorageTransaction tx = store.startTransaction(); - final List keys = - Stream.of("0F", "10", "11", "12") - .map(this::bytesFromHexString) - .collect(toUnmodifiableList()); - keys.forEach(key -> tx.put(key, bytesFromHexString("0ABC"))); - tx.commit(); - assertThat(store.stream().map(Pair::getKey).collect(toUnmodifiableSet())) - .containsExactlyInAnyOrder(keys.toArray(new byte[][] {})); + try (final KeyValueStorage store = createStore()) { + final KeyValueStorageTransaction tx = store.startTransaction(); + final List keys = + Stream.of("0F", "10", "11", "12") + .map(this::bytesFromHexString) + .collect(toUnmodifiableList()); + keys.forEach(key -> tx.put(key, bytesFromHexString("0ABC"))); + tx.commit(); + assertThat(store.stream().map(Pair::getKey).collect(toUnmodifiableSet())) + .containsExactlyInAnyOrder(keys.toArray(new byte[][] {})); + } } /** @@ -119,18 +125,19 @@ public void streamKeys() throws Exception { */ @Test public void getAllKeysThat() throws Exception { - final KeyValueStorage store = createStore(); - final KeyValueStorageTransaction tx = store.startTransaction(); - tx.put(bytesFromHexString("0F"), bytesFromHexString("0ABC")); - tx.put(bytesFromHexString("10"), bytesFromHexString("0ABC")); - tx.put(bytesFromHexString("11"), bytesFromHexString("0ABC")); - tx.put(bytesFromHexString("12"), bytesFromHexString("0ABC")); - tx.commit(); - Set keys = store.getAllKeysThat(bv -> Bytes.wrap(bv).toString().contains("1")); - assertThat(keys.size()).isEqualTo(3); - assertThat(keys) - .containsExactlyInAnyOrder( - bytesFromHexString("10"), bytesFromHexString("11"), bytesFromHexString("12")); + try (final KeyValueStorage store = createStore()) { + final KeyValueStorageTransaction tx = store.startTransaction(); + tx.put(bytesFromHexString("0F"), bytesFromHexString("0ABC")); + tx.put(bytesFromHexString("10"), bytesFromHexString("0ABC")); + tx.put(bytesFromHexString("11"), bytesFromHexString("0ABC")); + tx.put(bytesFromHexString("12"), bytesFromHexString("0ABC")); + tx.commit(); + Set keys = store.getAllKeysThat(bv -> Bytes.wrap(bv).toString().contains("1")); + assertThat(keys.size()).isEqualTo(3); + assertThat(keys) + .containsExactlyInAnyOrder( + bytesFromHexString("10"), bytesFromHexString("11"), bytesFromHexString("12")); + } } /** @@ -140,17 +147,18 @@ public void getAllKeysThat() throws Exception { */ @Test public void containsKey() throws Exception { - final KeyValueStorage store = createStore(); - final byte[] key = bytesFromHexString("ABCD"); - final byte[] value = bytesFromHexString("DEFF"); + try (final KeyValueStorage store = createStore()) { + final byte[] key = bytesFromHexString("ABCD"); + final byte[] value = bytesFromHexString("DEFF"); - assertThat(store.containsKey(key)).isFalse(); + assertThat(store.containsKey(key)).isFalse(); - final KeyValueStorageTransaction transaction = store.startTransaction(); - transaction.put(key, value); - transaction.commit(); + final KeyValueStorageTransaction transaction = store.startTransaction(); + transaction.put(key, value); + transaction.commit(); - assertThat(store.containsKey(key)).isTrue(); + assertThat(store.containsKey(key)).isTrue(); + } } /** @@ -160,18 +168,19 @@ public void containsKey() throws Exception { */ @Test public void removeExisting() throws Exception { - final KeyValueStorage store = createStore(); - final byte[] key = bytesFromHexString("0F"); - final byte[] value = bytesFromHexString("0ABC"); - - KeyValueStorageTransaction tx = store.startTransaction(); - tx.put(key, value); - tx.commit(); - - tx = store.startTransaction(); - tx.remove(key); - tx.commit(); - assertThat(store.get(key)).isEmpty(); + try (final KeyValueStorage store = createStore()) { + final byte[] key = bytesFromHexString("0F"); + final byte[] value = bytesFromHexString("0ABC"); + + KeyValueStorageTransaction tx = store.startTransaction(); + tx.put(key, value); + tx.commit(); + + tx = store.startTransaction(); + tx.remove(key); + tx.commit(); + assertThat(store.get(key)).isEmpty(); + } } /** @@ -181,15 +190,16 @@ public void removeExisting() throws Exception { */ @Test public void removeExistingSameTransaction() throws Exception { - final KeyValueStorage store = createStore(); - final byte[] key = bytesFromHexString("0F"); - final byte[] value = bytesFromHexString("0ABC"); - - KeyValueStorageTransaction tx = store.startTransaction(); - tx.put(key, value); - tx.remove(key); - tx.commit(); - assertThat(store.get(key)).isEmpty(); + try (final KeyValueStorage store = createStore()) { + final byte[] key = bytesFromHexString("0F"); + final byte[] value = bytesFromHexString("0ABC"); + + KeyValueStorageTransaction tx = store.startTransaction(); + tx.put(key, value); + tx.remove(key); + tx.commit(); + assertThat(store.get(key)).isEmpty(); + } } /** @@ -199,13 +209,14 @@ public void removeExistingSameTransaction() throws Exception { */ @Test public void removeNonExistent() throws Exception { - final KeyValueStorage store = createStore(); - final byte[] key = bytesFromHexString("0F"); + try (final KeyValueStorage store = createStore()) { + final byte[] key = bytesFromHexString("0F"); - KeyValueStorageTransaction tx = store.startTransaction(); - tx.remove(key); - tx.commit(); - assertThat(store.get(key)).isEmpty(); + KeyValueStorageTransaction tx = store.startTransaction(); + tx.remove(key); + tx.commit(); + assertThat(store.get(key)).isEmpty(); + } } /** @@ -216,39 +227,38 @@ public void removeNonExistent() throws Exception { @Test public void concurrentUpdate() throws Exception { final int keyCount = 1000; - final KeyValueStorage store = createStore(); - - final CountDownLatch finishedLatch = new CountDownLatch(2); - final Function updater = - (value) -> - new Thread( - () -> { - try { - for (int i = 0; i < keyCount; i++) { - KeyValueStorageTransaction tx = store.startTransaction(); - tx.put(Bytes.minimalBytes(i).toArrayUnsafe(), value); - tx.commit(); + try (final KeyValueStorage store = createStore()) { + + final CountDownLatch finishedLatch = new CountDownLatch(2); + final Function updater = + (value) -> + new Thread( + () -> { + try { + for (int i = 0; i < keyCount; i++) { + KeyValueStorageTransaction tx = store.startTransaction(); + tx.put(Bytes.minimalBytes(i).toArrayUnsafe(), value); + tx.commit(); + } + } finally { + finishedLatch.countDown(); } - } finally { - finishedLatch.countDown(); - } - }); + }); - // Run 2 concurrent transactions that write a bunch of values to the same keys - final byte[] a = Bytes.of(10).toArrayUnsafe(); - final byte[] b = Bytes.of(20).toArrayUnsafe(); - updater.apply(a).start(); - updater.apply(b).start(); + // Run 2 concurrent transactions that write a bunch of values to the same keys + final byte[] a = Bytes.of(10).toArrayUnsafe(); + final byte[] b = Bytes.of(20).toArrayUnsafe(); + updater.apply(a).start(); + updater.apply(b).start(); - finishedLatch.await(); + finishedLatch.await(); - for (int i = 0; i < keyCount; i++) { - final byte[] key = Bytes.minimalBytes(i).toArrayUnsafe(); - final byte[] actual = store.get(key).get(); - assertThat(Arrays.equals(actual, a) || Arrays.equals(actual, b)).isTrue(); + for (int i = 0; i < keyCount; i++) { + final byte[] key = Bytes.minimalBytes(i).toArrayUnsafe(); + final byte[] actual = store.get(key).get(); + assertThat(Arrays.equals(actual, a) || Arrays.equals(actual, b)).isTrue(); + } } - - store.close(); } /** @@ -258,34 +268,35 @@ public void concurrentUpdate() throws Exception { */ @Test public void transactionCommit() throws Exception { - final KeyValueStorage store = createStore(); - // Add some values - KeyValueStorageTransaction tx = store.startTransaction(); - tx.put(bytesOf(1), bytesOf(1)); - tx.put(bytesOf(2), bytesOf(2)); - tx.put(bytesOf(3), bytesOf(3)); - tx.commit(); - - // Start transaction that adds, modifies, and removes some values - tx = store.startTransaction(); - tx.put(bytesOf(2), bytesOf(3)); - tx.put(bytesOf(2), bytesOf(4)); - tx.remove(bytesOf(3)); - tx.put(bytesOf(4), bytesOf(8)); - - // Check values before committing have not changed - assertThat(store.get(bytesOf(1))).contains(bytesOf(1)); - assertThat(store.get(bytesOf(2))).contains(bytesOf(2)); - assertThat(store.get(bytesOf(3))).contains(bytesOf(3)); - assertThat(store.get(bytesOf(4))).isEmpty(); - - tx.commit(); - - // Check that values have been updated after commit - assertThat(store.get(bytesOf(1))).contains(bytesOf(1)); - assertThat(store.get(bytesOf(2))).contains(bytesOf(4)); - assertThat(store.get(bytesOf(3))).isEmpty(); - assertThat(store.get(bytesOf(4))).contains(bytesOf(8)); + try (final KeyValueStorage store = createStore()) { + // Add some values + KeyValueStorageTransaction tx = store.startTransaction(); + tx.put(bytesOf(1), bytesOf(1)); + tx.put(bytesOf(2), bytesOf(2)); + tx.put(bytesOf(3), bytesOf(3)); + tx.commit(); + + // Start transaction that adds, modifies, and removes some values + tx = store.startTransaction(); + tx.put(bytesOf(2), bytesOf(3)); + tx.put(bytesOf(2), bytesOf(4)); + tx.remove(bytesOf(3)); + tx.put(bytesOf(4), bytesOf(8)); + + // Check values before committing have not changed + assertThat(store.get(bytesOf(1))).contains(bytesOf(1)); + assertThat(store.get(bytesOf(2))).contains(bytesOf(2)); + assertThat(store.get(bytesOf(3))).contains(bytesOf(3)); + assertThat(store.get(bytesOf(4))).isEmpty(); + + tx.commit(); + + // Check that values have been updated after commit + assertThat(store.get(bytesOf(1))).contains(bytesOf(1)); + assertThat(store.get(bytesOf(2))).contains(bytesOf(4)); + assertThat(store.get(bytesOf(3))).isEmpty(); + assertThat(store.get(bytesOf(4))).contains(bytesOf(8)); + } } /** @@ -295,34 +306,35 @@ public void transactionCommit() throws Exception { */ @Test public void transactionRollback() throws Exception { - final KeyValueStorage store = createStore(); - // Add some values - KeyValueStorageTransaction tx = store.startTransaction(); - tx.put(bytesOf(1), bytesOf(1)); - tx.put(bytesOf(2), bytesOf(2)); - tx.put(bytesOf(3), bytesOf(3)); - tx.commit(); - - // Start transaction that adds, modifies, and removes some values - tx = store.startTransaction(); - tx.put(bytesOf(2), bytesOf(3)); - tx.put(bytesOf(2), bytesOf(4)); - tx.remove(bytesOf(3)); - tx.put(bytesOf(4), bytesOf(8)); - - // Check values before committing have not changed - assertThat(store.get(bytesOf(1))).contains(bytesOf(1)); - assertThat(store.get(bytesOf(2))).contains(bytesOf(2)); - assertThat(store.get(bytesOf(3))).contains(bytesOf(3)); - assertThat(store.get(bytesOf(4))).isEmpty(); - - tx.rollback(); - - // Check that values have not changed after rollback - assertThat(store.get(bytesOf(1))).contains(bytesOf(1)); - assertThat(store.get(bytesOf(2))).contains(bytesOf(2)); - assertThat(store.get(bytesOf(3))).contains(bytesOf(3)); - assertThat(store.get(bytesOf(4))).isEmpty(); + try (final KeyValueStorage store = createStore()) { + // Add some values + KeyValueStorageTransaction tx = store.startTransaction(); + tx.put(bytesOf(1), bytesOf(1)); + tx.put(bytesOf(2), bytesOf(2)); + tx.put(bytesOf(3), bytesOf(3)); + tx.commit(); + + // Start transaction that adds, modifies, and removes some values + tx = store.startTransaction(); + tx.put(bytesOf(2), bytesOf(3)); + tx.put(bytesOf(2), bytesOf(4)); + tx.remove(bytesOf(3)); + tx.put(bytesOf(4), bytesOf(8)); + + // Check values before committing have not changed + assertThat(store.get(bytesOf(1))).contains(bytesOf(1)); + assertThat(store.get(bytesOf(2))).contains(bytesOf(2)); + assertThat(store.get(bytesOf(3))).contains(bytesOf(3)); + assertThat(store.get(bytesOf(4))).isEmpty(); + + tx.rollback(); + + // Check that values have not changed after rollback + assertThat(store.get(bytesOf(1))).contains(bytesOf(1)); + assertThat(store.get(bytesOf(2))).contains(bytesOf(2)); + assertThat(store.get(bytesOf(3))).contains(bytesOf(3)); + assertThat(store.get(bytesOf(4))).isEmpty(); + } } /** @@ -332,9 +344,10 @@ public void transactionRollback() throws Exception { */ @Test public void transactionCommitEmpty() throws Exception { - final KeyValueStorage store = createStore(); - final KeyValueStorageTransaction tx = store.startTransaction(); - tx.commit(); + try (final KeyValueStorage store = createStore()) { + final KeyValueStorageTransaction tx = store.startTransaction(); + tx.commit(); + } } /** @@ -344,143 +357,120 @@ public void transactionCommitEmpty() throws Exception { */ @Test public void transactionRollbackEmpty() throws Exception { - final KeyValueStorage store = createStore(); - final KeyValueStorageTransaction tx = store.startTransaction(); - tx.rollback(); + try (final KeyValueStorage store = createStore()) { + final KeyValueStorageTransaction tx = store.startTransaction(); + tx.rollback(); + } } - /** - * Transaction put after commit. - * - * @throws Exception the exception - */ + /** Transaction put after commit. */ @Test - public void transactionPutAfterCommit() throws Exception { + public void transactionPutAfterCommit() { Assertions.assertThatThrownBy( () -> { - final KeyValueStorage store = createStore(); - final KeyValueStorageTransaction tx = store.startTransaction(); - tx.commit(); - tx.put(bytesOf(1), bytesOf(1)); + try (final KeyValueStorage store = createStore()) { + final KeyValueStorageTransaction tx = store.startTransaction(); + tx.commit(); + tx.put(bytesOf(1), bytesOf(1)); + } }) .isInstanceOf(IllegalStateException.class); } - /** - * Transaction remove after commit. - * - * @throws Exception the exception - */ + /** Transaction remove after commit. */ @Test - public void transactionRemoveAfterCommit() throws Exception { + public void transactionRemoveAfterCommit() { Assertions.assertThatThrownBy( () -> { - final KeyValueStorage store = createStore(); - final KeyValueStorageTransaction tx = store.startTransaction(); - tx.commit(); - tx.remove(bytesOf(1)); + try (final KeyValueStorage store = createStore()) { + final KeyValueStorageTransaction tx = store.startTransaction(); + tx.commit(); + tx.remove(bytesOf(1)); + } }) .isInstanceOf(IllegalStateException.class); } - /** - * Transaction put after rollback. - * - * @throws Exception the exception - */ + /** Transaction put after rollback. */ @Test - public void transactionPutAfterRollback() throws Exception { + public void transactionPutAfterRollback() { Assertions.assertThatThrownBy( () -> { - final KeyValueStorage store = createStore(); - final KeyValueStorageTransaction tx = store.startTransaction(); - tx.rollback(); - tx.put(bytesOf(1), bytesOf(1)); + try (final KeyValueStorage store = createStore()) { + final KeyValueStorageTransaction tx = store.startTransaction(); + tx.rollback(); + tx.put(bytesOf(1), bytesOf(1)); + } }) .isInstanceOf(IllegalStateException.class); } - /** - * Transaction remove after rollback. - * - * @throws Exception the exception - */ + /** Transaction remove after rollback. */ @Test - public void transactionRemoveAfterRollback() throws Exception { + public void transactionRemoveAfterRollback() { Assertions.assertThatThrownBy( () -> { - final KeyValueStorage store = createStore(); - final KeyValueStorageTransaction tx = store.startTransaction(); - tx.rollback(); - tx.remove(bytesOf(1)); + try (final KeyValueStorage store = createStore()) { + final KeyValueStorageTransaction tx = store.startTransaction(); + tx.rollback(); + tx.remove(bytesOf(1)); + } }) .isInstanceOf(IllegalStateException.class); } - /** - * Transaction commit after rollback. - * - * @throws Exception the exception - */ + /** Transaction commit after rollback. */ @Test - public void transactionCommitAfterRollback() throws Exception { + public void transactionCommitAfterRollback() { Assertions.assertThatThrownBy( () -> { - final KeyValueStorage store = createStore(); - final KeyValueStorageTransaction tx = store.startTransaction(); - tx.rollback(); - tx.commit(); + try (final KeyValueStorage store = createStore()) { + final KeyValueStorageTransaction tx = store.startTransaction(); + tx.rollback(); + tx.commit(); + } }) .isInstanceOf(IllegalStateException.class); } - /** - * Transaction commit twice. - * - * @throws Exception the exception - */ + /** Transaction commit twice. */ @Test - public void transactionCommitTwice() throws Exception { + public void transactionCommitTwice() { Assertions.assertThatThrownBy( () -> { - final KeyValueStorage store = createStore(); - final KeyValueStorageTransaction tx = store.startTransaction(); - tx.commit(); - tx.commit(); + try (final KeyValueStorage store = createStore()) { + final KeyValueStorageTransaction tx = store.startTransaction(); + tx.commit(); + tx.commit(); + } }) .isInstanceOf(IllegalStateException.class); } - /** - * Transaction rollback after commit. - * - * @throws Exception the exception - */ + /** Transaction rollback after commit. */ @Test - public void transactionRollbackAfterCommit() throws Exception { + public void transactionRollbackAfterCommit() { Assertions.assertThatThrownBy( () -> { - final KeyValueStorage store = createStore(); - final KeyValueStorageTransaction tx = store.startTransaction(); - tx.commit(); - tx.rollback(); + try (final KeyValueStorage store = createStore()) { + final KeyValueStorageTransaction tx = store.startTransaction(); + tx.commit(); + tx.rollback(); + } }) .isInstanceOf(IllegalStateException.class); } - /** - * Transaction rollback twice. - * - * @throws Exception the exception - */ + /** Transaction rollback twice. */ @Test - public void transactionRollbackTwice() throws Exception { + public void transactionRollbackTwice() { Assertions.assertThatThrownBy( () -> { - final KeyValueStorage store = createStore(); - final KeyValueStorageTransaction tx = store.startTransaction(); - tx.rollback(); - tx.rollback(); + try (final KeyValueStorage store = createStore()) { + final KeyValueStorageTransaction tx = store.startTransaction(); + tx.rollback(); + tx.rollback(); + } }) .isInstanceOf(IllegalStateException.class); } @@ -492,19 +482,20 @@ public void transactionRollbackTwice() throws Exception { */ @Test public void twoTransactions() throws Exception { - final KeyValueStorage store = createStore(); + try (final KeyValueStorage store = createStore()) { - final KeyValueStorageTransaction tx1 = store.startTransaction(); - final KeyValueStorageTransaction tx2 = store.startTransaction(); + final KeyValueStorageTransaction tx1 = store.startTransaction(); + final KeyValueStorageTransaction tx2 = store.startTransaction(); - tx1.put(bytesOf(1), bytesOf(1)); - tx2.put(bytesOf(2), bytesOf(2)); + tx1.put(bytesOf(1), bytesOf(1)); + tx2.put(bytesOf(2), bytesOf(2)); - tx1.commit(); - tx2.commit(); + tx1.commit(); + tx2.commit(); - assertThat(store.get(bytesOf(1))).contains(bytesOf(1)); - assertThat(store.get(bytesOf(2))).contains(bytesOf(2)); + assertThat(store.get(bytesOf(1))).contains(bytesOf(1)); + assertThat(store.get(bytesOf(2))).contains(bytesOf(2)); + } } /** diff --git a/testutil/src/main/java/org/hyperledger/besu/testutil/BlockTestUtil.java b/testutil/src/main/java/org/hyperledger/besu/testutil/BlockTestUtil.java index d2909a84f3b..9f31283bfcf 100644 --- a/testutil/src/main/java/org/hyperledger/besu/testutil/BlockTestUtil.java +++ b/testutil/src/main/java/org/hyperledger/besu/testutil/BlockTestUtil.java @@ -146,6 +146,7 @@ public static ChainResources getOutdatedForkResources() { public static ChainResources getUpgradedForkResources() { return forkUpgradedSupplier.get(); } + /** * Gets Eth Ref Test resources. * diff --git a/testutil/src/main/java/org/hyperledger/besu/testutil/DeterministicEthScheduler.java b/testutil/src/main/java/org/hyperledger/besu/testutil/DeterministicEthScheduler.java index 6959816a128..14728aeaf7d 100644 --- a/testutil/src/main/java/org/hyperledger/besu/testutil/DeterministicEthScheduler.java +++ b/testutil/src/main/java/org/hyperledger/besu/testutil/DeterministicEthScheduler.java @@ -139,6 +139,7 @@ public void failAfterTimeout(final CompletableFuture promise, final Durat public interface TimeoutPolicy { /** A policy that never timeouts */ TimeoutPolicy NEVER_TIMEOUT = () -> false; + /** A policy that timeouts on every task */ TimeoutPolicy ALWAYS_TIMEOUT = () -> true; diff --git a/testutil/src/main/java/org/hyperledger/besu/testutil/JsonTestParameters.java b/testutil/src/main/java/org/hyperledger/besu/testutil/JsonTestParameters.java index f4fc7c49a36..f95ac9348ea 100644 --- a/testutil/src/main/java/org/hyperledger/besu/testutil/JsonTestParameters.java +++ b/testutil/src/main/java/org/hyperledger/besu/testutil/JsonTestParameters.java @@ -37,9 +37,12 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonFactoryBuilder; +import com.fasterxml.jackson.core.StreamReadConstraints; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import org.apache.tuweni.bytes.Bytes; /** * Utility class for generating JUnit test parameters from json files. Each set of test parameters @@ -73,7 +76,7 @@ private Collector(@Nullable final Predicate includes, final Predicate testParameters = new ArrayList<>(256); /** - * Add. + * Add standard reference test. * * @param name the name * @param fullPath the full path of the test @@ -86,6 +89,31 @@ public void add( new Object[] {name, value, runTest && includes(name) && includes(fullPath)}); } + /** + * Add EOF test. + * + * @param name the name + * @param fullPath the full path of the test + * @param fork the fork to be tested + * @param code the code to be tested + * @param containerKind the containerKind, if specified + * @param value the value + * @param runTest the run test + */ + public void add( + final String name, + final String fullPath, + final String fork, + final Bytes code, + final String containerKind, + final S value, + final boolean runTest) { + testParameters.add( + new Object[] { + name, fork, code, containerKind, value, runTest && includes(name) && includes(fullPath) + }); + } + private boolean includes(final String name) { // If there is no specific includes, everything is included unless it is ignored, otherwise, // only what is in includes is included whether or not it is ignored. @@ -121,11 +149,17 @@ public interface Generator { } private static final ObjectMapper objectMapper = - new ObjectMapper().registerModule(new Jdk8Module()); + new ObjectMapper( + new JsonFactoryBuilder() + .streamReadConstraints( + StreamReadConstraints.builder().maxStringLength(Integer.MAX_VALUE).build()) + .build()) + .registerModule(new Jdk8Module()); // The type to which the json file is directly mapped private final Class jsonFileMappedType; - // The final type of the test case spec, which may or may not not be same than jsonFileMappedType + + // The final type of the test case spec, which may or may not be same than jsonFileMappedType // Note that we don't really use this field as of now, but as this is the actual type of the final // spec used by tests, it feels "right" to have it passed explicitly at construction and having it // around could prove useful later. @@ -235,7 +269,13 @@ public Collection generate(final String... paths) { return generate(getFilteredFiles(paths)); } - private Collection generate(final Collection filteredFiles) { + /** + * Generate collection. + * + * @param filteredFiles the filtered files + * @return the collection + */ + public Collection generate(final Collection filteredFiles) { checkState(generator != null, "Missing generator function"); final Collector collector = @@ -268,7 +308,7 @@ private Collection getFilteredFiles(final String[] paths) { final List files = new ArrayList<>(); for (final String path : paths) { final URL url = classLoader.getResource(path); - checkState(url != null, "Cannot find test directory " + path); + checkState(url != null, "Cannot find test directory %s", path); final Path dir; try { dir = Paths.get(url.toURI()); diff --git a/testutil/src/main/java/org/hyperledger/besu/testutil/MockExecutorService.java b/testutil/src/main/java/org/hyperledger/besu/testutil/MockExecutorService.java index a84d9b57879..0dc93a69fad 100644 --- a/testutil/src/main/java/org/hyperledger/besu/testutil/MockExecutorService.java +++ b/testutil/src/main/java/org/hyperledger/besu/testutil/MockExecutorService.java @@ -36,6 +36,14 @@ public class MockExecutorService implements ExecutorService { private final List> tasks = new ArrayList<>(); + /** Default constructor */ + public MockExecutorService() {} + + /** + * Gets tasks. + * + * @return the tasks + */ /** * Gets futures. * diff --git a/testutil/src/main/java/org/hyperledger/besu/testutil/MockScheduledExecutor.java b/testutil/src/main/java/org/hyperledger/besu/testutil/MockScheduledExecutor.java index fbcb5f1a065..14f62b7404e 100644 --- a/testutil/src/main/java/org/hyperledger/besu/testutil/MockScheduledExecutor.java +++ b/testutil/src/main/java/org/hyperledger/besu/testutil/MockScheduledExecutor.java @@ -25,6 +25,8 @@ /** The mock scheduled executor */ public class MockScheduledExecutor extends MockExecutorService implements ScheduledExecutorService { + /** Default constructor */ + public MockScheduledExecutor() {} @Override public ScheduledFuture schedule( diff --git a/testutil/src/main/java/org/hyperledger/enclave/testutil/EnclaveKeyUtils.java b/testutil/src/main/java/org/hyperledger/enclave/testutil/EnclaveKeyUtils.java index 39b75abba10..acefd47d671 100644 --- a/testutil/src/main/java/org/hyperledger/enclave/testutil/EnclaveKeyUtils.java +++ b/testutil/src/main/java/org/hyperledger/enclave/testutil/EnclaveKeyUtils.java @@ -34,6 +34,9 @@ public class EnclaveKeyUtils { private static final Logger LOG = LoggerFactory.getLogger(EnclaveKeyUtils.class); + /** Default constructor */ + private EnclaveKeyUtils() {} + /** * Utility method to load the enclave public key. Possible input values are the names of the *.pub * files in the resources folder. diff --git a/testutil/src/main/java/org/hyperledger/enclave/testutil/TesseraTestHarness.java b/testutil/src/main/java/org/hyperledger/enclave/testutil/TesseraTestHarness.java index b7e19126453..f27224fa1d5 100644 --- a/testutil/src/main/java/org/hyperledger/enclave/testutil/TesseraTestHarness.java +++ b/testutil/src/main/java/org/hyperledger/enclave/testutil/TesseraTestHarness.java @@ -55,6 +55,7 @@ public class TesseraTestHarness implements EnclaveTestHarness { private final int thirdPartyPort = 9081; private final int q2TPort = 9082; + /** The constant p2pPort. */ public static final int p2pPort = 9001; diff --git a/testutil/src/main/java/org/hyperledger/enclave/testutil/TesseraTestHarnessFactory.java b/testutil/src/main/java/org/hyperledger/enclave/testutil/TesseraTestHarnessFactory.java index cf02a160288..0cfe8b76c9b 100644 --- a/testutil/src/main/java/org/hyperledger/enclave/testutil/TesseraTestHarnessFactory.java +++ b/testutil/src/main/java/org/hyperledger/enclave/testutil/TesseraTestHarnessFactory.java @@ -30,6 +30,9 @@ public class TesseraTestHarnessFactory { private static final String storage = "memory"; + /** Default constructor */ + private TesseraTestHarnessFactory() {} + /** * Create tessera test harness. * diff --git a/util/build.gradle b/util/build.gradle index 9136a20b7e6..c0d32361e0c 100644 --- a/util/build.gradle +++ b/util/build.gradle @@ -23,6 +23,7 @@ jar { 'Specification-Version': project.version, 'Implementation-Title': archiveBaseName, 'Implementation-Version': calculateVersion(), + 'Commit-Hash': getGitCommitDetails(40).hash, 'Automatic-Module-Name': 'org.hyperledger.besu.internal.util' ) } @@ -31,6 +32,8 @@ jar { dependencies { api 'org.slf4j:slf4j-api' + annotationProcessor 'org.apache.logging.log4j:log4j-core' + implementation 'com.google.guava:guava' implementation 'net.java.dev.jna:jna' implementation 'org.apache.commons:commons-lang3' @@ -42,6 +45,4 @@ dependencies { testImplementation 'org.assertj:assertj-core' testImplementation 'org.junit.jupiter:junit-jupiter' testImplementation 'org.mockito:mockito-core' - - testRuntimeOnly 'org.junit.vintage:junit-vintage-engine' } diff --git a/util/src/main/java/org/hyperledger/besu/util/EndianUtils.java b/util/src/main/java/org/hyperledger/besu/util/EndianUtils.java index 025eeea7ab2..b80d27216ab 100644 --- a/util/src/main/java/org/hyperledger/besu/util/EndianUtils.java +++ b/util/src/main/java/org/hyperledger/besu/util/EndianUtils.java @@ -18,6 +18,7 @@ public class EndianUtils { // next two methods adopted from: // https://github.com/bcgit/bc-java/blob/master/core/src/main/java/org/bouncycastle/util/Pack.java + private EndianUtils() {} /** * Long to big endian. diff --git a/util/src/main/java/org/hyperledger/besu/util/FutureUtils.java b/util/src/main/java/org/hyperledger/besu/util/FutureUtils.java index c75d97ee311..ea82306528a 100644 --- a/util/src/main/java/org/hyperledger/besu/util/FutureUtils.java +++ b/util/src/main/java/org/hyperledger/besu/util/FutureUtils.java @@ -25,6 +25,8 @@ /** The Future utils. */ public class FutureUtils { + private FutureUtils() {} + /** * Returns a new CompletionStage that, when the provided stage completes exceptionally, is * executed with the provided stage's exception as the argument to the supplied function. diff --git a/util/src/main/java/org/hyperledger/besu/util/NetworkUtility.java b/util/src/main/java/org/hyperledger/besu/util/NetworkUtility.java index d0c2c347cd8..3851b3fe099 100644 --- a/util/src/main/java/org/hyperledger/besu/util/NetworkUtility.java +++ b/util/src/main/java/org/hyperledger/besu/util/NetworkUtility.java @@ -16,6 +16,7 @@ import java.io.IOException; import java.net.DatagramSocket; +import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -26,6 +27,7 @@ import java.util.function.Supplier; import com.google.common.base.Suppliers; +import com.google.common.net.InetAddresses; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,9 +35,22 @@ public class NetworkUtility { /** The constant INADDR_ANY. */ public static final String INADDR_ANY = "0.0.0.0"; + + /** The constant INADDR_NONE. */ + public static final String INADDR_NONE = "255.255.255.255"; + /** The constant INADDR6_ANY. */ public static final String INADDR6_ANY = "0:0:0:0:0:0:0:0"; + /** The constant INADDR6_NONE. */ + public static final String INADDR6_NONE = "::"; + + /** The constant INADDR_LOCALHOST. */ + public static final String INADDR_LOCALHOST = "127.0.0.1"; + + /** The constant INADDR6_LOCALHOST. */ + public static final String INADDR6_LOCALHOST = "::1"; + private static final Logger LOG = LoggerFactory.getLogger(NetworkUtility.class); private NetworkUtility() {} @@ -119,7 +134,20 @@ public static boolean isNetworkInterfaceAvailable(final String ipAddress) * @return the boolean */ public static boolean isUnspecifiedAddress(final String ipAddress) { - return INADDR_ANY.equals(ipAddress) || INADDR6_ANY.equals(ipAddress); + return INADDR_ANY.equals(ipAddress) + || INADDR6_ANY.equals(ipAddress) + || INADDR_NONE.equals(ipAddress) + || INADDR6_NONE.equals(ipAddress); + } + + /** + * Returns whether host address string is local host address. + * + * @param ipAddress the host address as a string + * @return true if the host address is a local host address + */ + public static boolean isLocalhostAddress(final String ipAddress) { + return INADDR_LOCALHOST.equals(ipAddress) || INADDR6_LOCALHOST.equals(ipAddress); } /** @@ -137,40 +165,66 @@ public static void checkPort(final int port, final String portTypeName) { } /** - * Is port available for tcp. + * Is port unavailable for tcp. * * @param port the port - * @return the boolean + * @return true if the port is unavailable for TCP */ - public static boolean isPortAvailableForTcp(final int port) { + public static boolean isPortUnavailableForTcp(final int port) { try (final ServerSocket serverSocket = new ServerSocket()) { serverSocket.setReuseAddress(true); serverSocket.bind(new InetSocketAddress(port)); - return true; + serverSocket.close(); + return false; } catch (IOException ex) { LOG.trace(String.format("Failed to open port %d for TCP", port), ex); } - return false; + return true; } - private static boolean isPortAvailableForUdp(final int port) { + /** + * Is port unavailable for udp. + * + * @param port the port + * @return true if the port is unavailable for UDP + */ + public static boolean isPortUnavailableForUdp(final int port) { try (final DatagramSocket datagramSocket = new DatagramSocket(null)) { datagramSocket.setReuseAddress(true); datagramSocket.bind(new InetSocketAddress(port)); - return true; + datagramSocket.close(); + return false; } catch (IOException ex) { LOG.trace(String.format("failed to open port %d for UDP", port), ex); } - return false; + return true; } /** - * Is port available. + * Is hostAddress string an ip v4 address * - * @param port the port - * @return the boolean + * @param hostAddress the host address as a string + * @return true if the host address is an ip v4 address */ - public static boolean isPortAvailable(final int port) { - return isPortAvailableForTcp(port) && isPortAvailableForUdp(port); + public static boolean isIpV4Address(final String hostAddress) { + try { + return InetAddresses.forString(hostAddress) instanceof Inet4Address; + } catch (final IllegalArgumentException e) { + return false; + } + } + + /** + * Is hostAddress string an ip v6 address + * + * @param hostAddress the host address as a string + * @return true if the host address is an ip v6 address + */ + public static boolean isIpV6Address(final String hostAddress) { + try { + return InetAddresses.forString(hostAddress) instanceof Inet6Address; + } catch (final IllegalArgumentException e) { + return false; + } } } diff --git a/util/src/main/java/org/hyperledger/besu/util/Subscribers.java b/util/src/main/java/org/hyperledger/besu/util/Subscribers.java index 6704ed95773..7ca1e712744 100644 --- a/util/src/main/java/org/hyperledger/besu/util/Subscribers.java +++ b/util/src/main/java/org/hyperledger/besu/util/Subscribers.java @@ -165,9 +165,7 @@ public boolean unsubscribe(final long subscriberId) { } @Override - public void forEach(final Consumer action) { - return; - } + public void forEach(final Consumer action) {} @Override public int getSubscriberCount() { diff --git a/util/src/main/java/org/hyperledger/besu/util/io/RollingFileReader.java b/util/src/main/java/org/hyperledger/besu/util/io/RollingFileReader.java index c7cf98edda3..e9d3ee807a6 100644 --- a/util/src/main/java/org/hyperledger/besu/util/io/RollingFileReader.java +++ b/util/src/main/java/org/hyperledger/besu/util/io/RollingFileReader.java @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.util.io; import java.io.Closeable; diff --git a/util/src/main/java/org/hyperledger/besu/util/io/RollingFileWriter.java b/util/src/main/java/org/hyperledger/besu/util/io/RollingFileWriter.java index 93e7e1d6f0c..81e1e5e8d70 100644 --- a/util/src/main/java/org/hyperledger/besu/util/io/RollingFileWriter.java +++ b/util/src/main/java/org/hyperledger/besu/util/io/RollingFileWriter.java @@ -11,9 +11,7 @@ * specific language governing permissions and limitations under the License. * * SPDX-License-Identifier: Apache-2.0 - * */ - package org.hyperledger.besu.util.io; import java.io.Closeable; diff --git a/util/src/main/java/org/hyperledger/besu/util/log/FramedLogMessage.java b/util/src/main/java/org/hyperledger/besu/util/log/FramedLogMessage.java index f68c3ed7fe0..a67b12b415f 100644 --- a/util/src/main/java/org/hyperledger/besu/util/log/FramedLogMessage.java +++ b/util/src/main/java/org/hyperledger/besu/util/log/FramedLogMessage.java @@ -1,5 +1,5 @@ /* - * Copyright Hyperledger Besu Contributors. + * Copyright contributors to Hyperledger Besu. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at diff --git a/util/src/main/java/org/hyperledger/besu/util/log/LogUtil.java b/util/src/main/java/org/hyperledger/besu/util/log/LogUtil.java new file mode 100644 index 00000000000..9c570613f67 --- /dev/null +++ b/util/src/main/java/org/hyperledger/besu/util/log/LogUtil.java @@ -0,0 +1,94 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.util.log; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +/** Utility class for logging. */ +public class LogUtil { + static ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + static final String BESU_NAMESPACE = "org.hyperledger.besu"; + static final int MAX_SUMMARY_DEPTH = 20; + + private LogUtil() {} + + /** + * Throttles logging to a given logger. + * + * @param logger logger as a String consumer + * @param logMessage message to log + * @param shouldLog AtomicBoolean to track whether the message should be logged + * @param logRepeatDelay delay in seconds between repeated logs + */ + public static void throttledLog( + final Consumer logger, + final String logMessage, + final AtomicBoolean shouldLog, + final int logRepeatDelay) { + + if (shouldLog.compareAndSet(true, false)) { + logger.accept(logMessage); + + final Runnable runnable = () -> shouldLog.set(true); + executor.schedule(runnable, logRepeatDelay, TimeUnit.SECONDS); + } + } + + /** + * Summarizes the stack trace of a throwable to the first class in the namespace. Useful for + * limiting exceptionally deep stack traces to the last relevant point in besu code. + * + * @param contextMessage message to prepend to the summary + * @param throwable exception to summarize + * @param namespace namespace to summarize to + * @return summary of the StackTrace + */ + public static String summarizeStackTrace( + final String contextMessage, final Throwable throwable, final String namespace) { + StackTraceElement[] stackTraceElements = throwable.getStackTrace(); + + List stackTraceSummary = new ArrayList<>(); + int depth = 0; + for (StackTraceElement element : stackTraceElements) { + stackTraceSummary.add(String.format("\tat: %s", element)); + if (element.getClassName().startsWith(namespace) || ++depth >= MAX_SUMMARY_DEPTH) { + break; + } + } + + return String.format( + "%s\nThrowable summary: %s\n%s", + contextMessage, throwable, String.join("\n", stackTraceSummary)); + } + + /** + * Summarizes the stack trace of a throwable to the first besu class in the namespace. Useful for + * limiting exceptionally deep stack traces to the last relevant point in besu code. + * + * @param contextMessage message to prepend to the summary + * @param throwable exception to summarize + * @return summary of the StackTrace + */ + public static String summarizeBesuStackTrace( + final String contextMessage, final Throwable throwable) { + return summarizeStackTrace(contextMessage, throwable, BESU_NAMESPACE); + } +} diff --git a/util/src/main/java/org/hyperledger/besu/util/log4j/plugin/BesuLogMessageConverter.java b/util/src/main/java/org/hyperledger/besu/util/log4j/plugin/BesuLogMessageConverter.java new file mode 100644 index 00000000000..5598f92f9d2 --- /dev/null +++ b/util/src/main/java/org/hyperledger/besu/util/log4j/plugin/BesuLogMessageConverter.java @@ -0,0 +1,80 @@ +/* + * Copyright contributors to Hyperledger Besu. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.util.log4j.plugin; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.pattern.ConverterKeys; +import org.apache.logging.log4j.core.pattern.LogEventPatternConverter; +import org.apache.logging.log4j.core.pattern.PatternConverter; + +/** + * Besu Log4j2 plugin for cleaner message logging. + * + *